Вопрос

I am trying to change some fields in the IP and TCP header in a netfilter postrouting hook, however I can't seem to get the kernels TCP checksum function to work properly to amend it afterwards.

The checksum is fine during the TCP handshake, but as soon as the packet has any payload the checksum is wrong.

I have pulled this checksum code together from digging around the TCP source. I am fairly sure tcplen is correct, matching the expected TCP header + payload size.

static unsigned int posthook_fn(
    unsigned int hooknum, 
    struct sk_buff *skb,
    const struct net_device *in, 
    const struct net_device *out,
    int (*okfn)(struct sk_buff *))
{
  struct iphdr *iph;
  struct tcphdr *tcph;
  iph = ip_hdr(skb);
  tcph = (struct tcphdr *)(skb->data + iph->ihl * 4);

  tcph->source = port;
  iph->saddr = addr;

  tcplen = (skb->len - (ip_header->ihl << 2));
  tcph->check = 0; 
  tcph->check = tcp_v4_check(tcph, tcplen, 
        iph->saddr, 
        iph->daddr, 
        csum_partial((char *)tcph, tcplen, 0)); 
  skb->ip_summed = CHECKSUM_NONE; //stop offloading

  ip_header->check = ip_fast_csum((u8 *)iph, iph->ihl);         

  return NF_ACCEPT;
}

Am I correct in thinking that tcp_v4_check calculates the psuedo header and csum_partial calculates the unfolded checksum for the payload and tcp_header?

I really want to avoid writing the function myself as the kernel will be much faster as the underlying functions use assembly for the calculation.

Is there an alternative method that might work? Or is there something I am missing out?

Это было полезно?

Решение

There is no need for extra call to skb_is_nonlinear(), since include/linux/skbuff.h:

static inline int skb_linearize(struct sk_buff *skb)
{
         return skb_is_nonlinear(skb) ? __skb_linearize(skb) : 0;
}

Also, you have to:

ip_header->check = 0

before:

ip_header->check = ip_fast_csum((u8 *)iph, iph->ihl);

Другие советы

It's taken a while to get here but the problem seems to be that the socket buffer isn't always linear, the following code ensures that it is before calculating the checksum.

if (skb_is_nonlinear(skb)) {
    if (skb_linearize(skb) != 0) {
        return NF_DROP;
    }
    iph = ip_hdr(skb);
    tcph = (void *)iph + (iph->ihl << 2);
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top