3
votes

I am building a Netfilter module which modifies the TCP payload for packets destined to a specific port. I do not modify neither the IP header nor the TCP header. The module is called at the first point in the Netfilter directly after a packet is received (NF_INET_PRE_ROUTING). Therefore I have to recalculate the TCP checksum field in each modified packet. I already saw some posts here and used there methods to recalculate TCP checksum, but non of these methods worked. Below is the two methods I used:

Method 1:

tcplen = (skb->len - (iph->ihl << 2)); /* tcplen is the length of the 
                                        * skb - the ip-header length */
tcph->check = 0;
tcph->check = csum_tcpudp_magic(iph->saddr, iph->daddr,
                                tcplen, IPPROTO_TCP,
                                csum_partial(tcph, tcplen, 0));

skb->ip_summed = CHECKSUM_UNNECESSARY;

Method 2:

tcph->check = ~(~tcph->check + ~new_field + new_field);
skb->ip_summed = CHECKSUM_UNNECESSARY;

In the two cases, I get the following error:

verif_dss_csum csum is wrong: 0x874a data_seq 2760801057 dss_csum_added 1 overflowed 0 iterations 1

Any solution for this problem? I am developing my module for Linux Kernel 4.4.83

2
I can not find any error. BTW: isiph->ihl the original ipheader->len (which includes the TCP header+ payload)? Second thought is padding, but I guess csum_partial() will handle that.wildplasser
I found the error. It is not related to TCP checksum. It is related to MP-TCP checksum so I disabled it. The above syntax is correctIoT

2 Answers

2
votes

**The Above code only helps with calculating checksum of pseudo header ** here is the code for calculating IP header Checksum and TCP/UDP Checksum

void UpdateChecksum(char *prefix,struct sk_buff *skb){
struct iphdr *ip_header;

ip_header = ip_hdr(skb);
skb->ip_summed = CHECKSUM_NONE; //stop offloading
skb->csum_valid = 0;
ip_header->check = 0;
ip_header->check = ip_fast_csum((u8 *)ip_header, ip_header->ihl);


if ( (ip_header->protocol == IPPROTO_TCP) || (ip_header->protocol == IPPROTO_UDP) ) {

  if(skb_is_nonlinear(skb))
      skb_linearize(skb);  // very important.. You need this.

  if (ip_header->protocol == IPPROTO_TCP) {
     struct tcphdr *tcpHdr;
     unsigned int tcplen;

     tcpHdr = tcp_hdr(skb);
     skb->csum =0;
     tcplen = ntohs(ip_header->tot_len) - ip_header->ihl*4;
     tcpHdr->check = 0;
     tcpHdr->check = tcp_v4_check(tcplen, ip_header->saddr, ip_header->daddr, csum_partial((char *)tcpHdr, tcplen, 0));

     //printk(KERN_INFO "%s: TCP Len :%d, Computed TCP Checksum :%x : Network : %x\n",prefix,tcplen,tcpHdr->check,htons(tcpHdr->check));

  } else if (ip_header->protocol == IPPROTO_UDP) {
       struct udphdr *udpHdr;
       unsigned int udplen;

       udpHdr = udp_hdr(skb);
       skb->csum =0;
       udplen = ntohs(ip_header->tot_len) - ip_header->ihl*4;
       udpHdr->check = 0;
       udpHdr->check = udp_v4_check(udplen,ip_header->saddr, ip_header->daddr,csum_partial((char *)udpHdr, udplen, 0));;

       //printk(KERN_INFO "%s: UDP Len :%d, Computed UDP Checksum :%x : Network : %x\n",prefix,udplen,udpHdr->check,htons(udpHdr->check));
  }

}

}

1
votes

There is no need to recalculate the ipv4 header checksum, if you change only the tcp payload. So if I understand you correctly, it should be just:

/* ... */
struct iphdr *ip_header = ip_hdr(skb);
if (ip_header->protocol == IPPROTO_TCP) {
    struct tcphdr *tcp_header = tcp_hdr(skb);
    if ((unsigned int)ntohs(tcp_header->dest) == some_port) {
        unsigned char *data = (char *)tcp_header + tcp_hdrlen(skb);
        /* altering TCP-payload */
        tcp_header->check = 0;
        tcp_header->check = tcp_v4_check(ntohs(ip_header->tot_len) - (ip_header->ihl << 2),
                                     ip_header->saddr, ip_header->daddr,
                                     csum_partial(tcp_header, tcp_header->doff << 2, 0));
/* ... */
/* e.g return NF_ACCEPT */