Frage

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?

War es hilfreich?

Lösung

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);

Andere Tipps

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);
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top