zoukankan      html  css  js  c++  java
  • IP/TCP/UDP checsum

      今天调试bug时, 忘了将原始的check_sum值reset,导致发包-抓包后发现。check-sum 错误。

    来看一看check-sum:简单讲就是对要计算的数据,以16bit为单元进行累加,然后取反

    checksum在收包和发包时意义不一样

    TCP收包时:

    /*
     *    @csum: Checksum (must include start/offset pair)
     *    @csum_start: Offset from skb->head where checksumming should start
     *    @csum_offset: Offset from csum_start where checksum should be stored
     *    @ip_summed: Driver fed us an IP checksum
     */
    struct sk_buff {
        union {
            __wsum        csum;
            struct {
                __u16    csum_start;
                __u16    csum_offset;
            };
        };
    
        __u8    ip_summed:2,
                

    skb->ip_summed一般的取值如下 ;skb->csum:存放硬件或者软件计算的payload的checksum不包括伪头

    /* Don't change this without changing skb_csum_unnecessary! */
    #define CHECKSUM_NONE        0
    #define CHECKSUM_UNNECESSARY    1
    #define CHECKSUM_COMPLETE    2
    #define CHECKSUM_PARTIAL    3 
    • CHECKSUM_UNNECESSARY

        CHECKSUM_UNNECESSARY表示底层硬件已经计算了CSUM;所以TCP层在收到包后,发现skb->ip_summedCHECKSUM_UNNECESSARY就不会再检查checksum:

    • CHECKSUM_NONE

        csum中的校验和无效,可能有以下几种原因:设备不支持硬件校验和计算;设备计算了硬件校验和,但发现该数据帧已经损坏。部分驱动不会丢弃,而是将ip_summed设置为CHECKSUM_NONE,然后交给上层协议栈重新计算并处理这种错误。

    • CHECKSUM_COMPLETE

      网卡已经计算了L4层报头和payload的校验和,并且skb->csum已经被赋值,此时L4层的接收者只需要加伪头并验证校验结果。

    TCP发包时:

    skb->ip_summed用于L4校验和的状态,以通知底层网卡是否还需要处理校验和;此时ip_summed可以被设置的值有下面两种

    •  CHECKSUM_NONE

      CHECKSUM_NONE表示协议栈已经计算了校验和,设备不需要做任何事情

    •  CHECKSUM_PARTIAL

         CHECKSUM_PARTIAL表示使用硬件checksum ,协议栈已经计算L4层的伪头的校验和

    skb->csum表示为csum_start和csum_offset,它表示硬件网卡存放将要计算的校验值的地址,和最后填充的便宜。这个域在输出包时使用,只在校验值在硬件计算的情况下才对于网卡真正有意义。硬件checksum功能只能用于非分片报文

    1. TCP校验和覆盖TCP首部和TCP数据,而IP首部中的校验和只覆盖IP的首部,不覆盖IP数据报中的任何数据。
    2. TCP的校验和是必需的,而UDP的校验和是可选的。
    3. TCP和UDP计算校验和时,都要加上一个12字节的伪首部。

      伪首部共有12字节,包含如下信息:源IP地址、目的IP地址、保留字节(置0)、传输层协议号(TCP是6)、TCP报文长度(报头+数据)。伪首部是为了增加TCP校验和的检错能力:如检查TCP报文是否收错了(目的IP地址)、传输层协议是否选对了(传输层协议号)等。

    RFC 793的TCP校验和定义

      The checksum field is the 16 bit one's complement of the one's complement sum of all 16-bit words in the header and text. If a segment contains an odd number of header and text octets to be checksummed, the last octet is padded on the right with zeros to form a 16-bit word for checksum purposes. The pad is not transmitted as part of the segment. While computingthe checksum, the checksum field itself is replaced with zeros.

      把伪首部、TCP报头、TCP数据分为16位的字,如果总长度为奇数个字节,则在最后增添一个位都为0的字节。把TCP报头中的校验和字段置为0;

      校验和的计算与顺序无关, 可以从数据块开始计算, 也可以从未尾开始向前计算

    RFC 1071的IP校验和定义

    1. Adjacent octets to be checksummed are paired to form 16-bit integers, and the 1's complement sum of these 16-bit integers is formed.

    2. To generate a checksum, the checksum field itself is cleared, the 16-bit 1's complement sum is computed over the octets concerned, and the 1's complement of this sum is placed in the checksum field.

    3. To check a checksum, the 1's complement sum is computed over the same set of octets, including the checksum field. If the result is all 1 bits (-0 in 1's complement arithmetic), the check succeeds.

    内核协议栈中:

    为了提高计算效率, TCP包的校验和并不一次算出,而是采用32位部分累加和(sk->csum)进行增量计算.
    csum_partial()用来计算数据块的32位部分累加和, 累加和可以用csum_fold()折叠为16位校验和.csum_partial_copy_nocheck()可在拷贝用户数据的同时计算出它的部分累加和. 为了加快执行速度, csum_partial()将8个32位字分为一组用分立的指令进行32位累加,这样可加长循环体中指令长度, 提高CPU指令流水线的效率.
     
    
    
    TCP包接收校验的初始化
    static __sum16 tcp_v4_checksum_init(struct sk_buff *skb)
    {
        const struct iphdr *iph = ip_hdr(skb);
        //如果TCP包本身的校验已经完成
        if (skb->ip_summed == CHECKSUM_COMPLETE) {
            if (!tcp_v4_check(skb->len, iph->saddr, iph->daddr, skb->csum)) { //附加伪头进行校验
                skb->ip_summed = CHECKSUM_UNNECESSARY;
                return 0;
            }
        }
        //生成包含伪头的累加和
        skb->csum = csum_tcpudp_nofold(iph->saddr, iph->daddr, skb->len, IPPROTO_TCP, 0);
        if (skb->len <= 76) {
            return __skb_checksum_complete(skb); //计算数据部分校验和
        }
        return 0;
    }
        附加伪头进行校验
    static inline __sum16 tcp_v4_check(int len, __be32 saddr, __be32 daddr, __wsum base)
    {
        return csum_tcpudp_magic(saddr, daddr, len, IPPROTO_TCP, base);
    }
    static inline __sum16 csum_tcpudp_magic(__be32 saddr, __be32 daddr, unsigned short len, unsigned short proto, __wsum sum)
    {
        return csum_fold(csum_tcpudp_nofold(saddr, daddr, len, proto, sum));
    }
        生成包含伪头的累加和(源,目的,长度,协议号)
    static inline __wsum csum_tcpudp_nofold(__be32 saddr, __be32 daddr, unsigned short len, unsigned short proto, __wsum sum)
    {
        __asm__(
                "addl %1, %0    ;
    "          //addl 加法
                "adcl %2, %0    ;
    "          //adcl 带进位的加法
                "adcl %3, %0    ;
    "
                "adcl $0, %0    ;
    "          //如果有进位,进行累加
                : "=r" (sum)
                : "g" (daddr), "g"(saddr), "g"((len + proto) << 8), "0"(sum)
               );
    
        return sum;
    }
        将32位累加和折叠成16位校验和
    static inline __sum16 csum_fold(__wsum sum)
    {
        __asm__(
                "addl %1, %0            ;
    "
                "adcl $0xffff, %0       ;
    "
                : "=r" (sum)
                : "r" ((__force u32)sum << 16), "0" ((__force u32)sum & 0xffff0000)
               );
        return (__force __sum16)(~(__force u32)sum >> 16);
    }
        基于伪头累加和,完成全包校验
    static __inline__ int tcp_checksum_complete(struct sk_buff *skb)
    {
        return skb->ip_summed != CHECKSUM_UNNECESSARY && __tcp_checksum_complete(skb);
    }
    __sum16 __skb_checksum_complete(struct sk_buff *skb)
    {
        return __skb_checksum_complete_head(skb, skb->len);
    }
    __sum16 __skb_checksum_complete_head(struct sk_buff *skb, int len)
    {
        __sum16 sum;
    
        sum = csum_fold(skb_checksum(skb, 0, len, skb->csum));
        if (likely(!sum)) {
            if (unlikely(skb->ip_summed == CHECKSUM_COMPLETE))
                netdev_rx_csum_fault(skb->dev);
            skb->ip_summed = CHECKSUM_UNNECESSARY;
        }
        return sum;
    }
    __wsum skb_checksum(const struct sk_buff *skb, int offset, int len, __wsum csum)
    {
        int start = skb_headlen(skb);
        int i, copy = start - offset;
        int pos = 0;
    
        /* Checksum header. */
        if (copy > 0) {
            if (copy > len)
                copy = len;
    
            csum = csum_partial(skb->data + offset, copy, csum);
            if ((len -= copy) == 0)
                return csum;
    
            offset += copy;
            pos     = copy;
        }
        ......
    }
        计算32位中间累加和
    unsigned int csum_partial(const unsigned char * buff, int len, unsigned int sum)
    {
        //arch/x86/lib/checksum_32.S 汇编文件
    }
        基于TCP用户数据的中间累加和, 生成TCP包校验码
    void tcp_v4_send_check(struct sock *sk, int len, struct sk_buff *skb)
    {
        struct inet_sock *inet = inet_sk(sk);
        struct tcphdr *th = tcp_hdr(skb);
    
        if (skb->ip_summed == CHECKSUM_PARTIAL) {
            th->check = ~tcp_v4_check(len, inet->saddr, inet->daddr, 0); //附加伪头进行校验
            skb->csum_start = skb_transport_header(skb) - skb->head;
            skb->csum_offset = offsetof(struct tcphdr, check);
        } else {
            //完整的tcp校验和计算方法
            th->check = tcp_v4_check(len, inet->saddr, inet->daddr, csum_partial((char *)th, th->doff << 2, skb->csum));
        }
    }
        在拷贝用户数据时同时计算累加和
    unsigned int csum_partial_copy_nocheck(const char *src, char *dst, int len, int sum)
    {
        return csum_partial_copy_generic(src, dst, len, sum, NULL, NULL); // arch/x86/lib/checksum_32.S
    }
        ip头校验和计算
    static inline __sum16 ip_fast_csum(const void *iph, unsigned int ihl)
    {
        unsigned int sum;
    
        __asm__ __volatile__(
                "movl (%1), %0      ;
    "
                "subl $4, %2         ;
    "
                "jbe 2f              ;
    "
                "addl 4(%1), %0     ;
    "  //sum = sum + *(iph+4)
                "adcl 8(%1), %0     ;
    "  //sum = sum + *(iph+8) + carry
                "adcl 12(%1), %0    ;
    "  //sum = sum + *(iph+12) + carry
                "1:                adcl 16(%1), %0     ;
    " //sum = sum + *(iph+16) + carry
                "lea 4(%1), %1      ;
    "  //iph = iph + 4
                "decl %2            ;
    "
                "jne 1b             ;
    "
                "adcl $0, %0        ;
    "
                "movl %0, %2       ;
    "
                "shrl $16, %0       ;
    "
                "addw %w2, %w0   ;
    "
                "adcl $0, %0        ;
    "
                "notl %0            ;
    "
                "2:                                    ;
    "
                /* Since the input registers which are loaded with iph and ihl are modified, we must also specify them as outputs,
                   or gcc will assume they contain their original values. */
                : "=r" (sum), "=r" (iph), "=r" (ihl)
                : "1" (iph), "2" (ihl)
                       : "memory"
                           );
        return (__force __sum16)sum;
    }
        递减ip->ttl,更新校验和
    static inline int ip_decrease_ttl(struct iphdr *iph)
    {
        u32 check = (__force u32)iph->check;
        check += (__force u32)htons(0x0100);
        iph->check = (__force __sum16)(check + (check>=0xFFFF));
        return --iph->ttl;
    }
    static inline __wsum csum_add(__wsum csum, __wsum addend)
    {
        u32 res = (__force u32)csum;
        res += (__force u32)addend;
        return (__force __wsum)(res + (res < (__force u32)addend));
    }
    
    static inline __wsum csum_sub(__wsum csum, __wsum addend)
    {
        return csum_add(csum, ~addend);
    }
    
    static inline __wsum csum_block_add(__wsum csum, __wsum csum2, int offset)
    {
        u32 sum = (__force u32)csum2;
        if (offset & 1)
            sum = ((sum & 0xFF00FF)<<8) + ((sum>>8) & 0xFF00FF);
        return csum_add(csum, (__force __wsum)sum);
    }
    static inline __wsum csum_block_sub(__wsum csum, __wsum csum2, int offset)
    {
        u32 sum = (__force u32)csum2;
        if (offset & 1)
            sum = ((sum & 0xFF00FF)<<8) + ((sum>>8) & 0xFF00FF);
        return csum_sub(csum, (__force __wsum)sum);
    }
    [/函数实现]
    转载:https://www.cnblogs.com/super-king/p/3284884.html

     https://hustcat.github.io/checksum-in-kernel/

    https://www.kernel.org/doc/Documentation/networking/checksum-offloads.txt

    https://w180112.pixnet.net/blog/post/200083785

    http://blog.chinaunix.net/uid-25518484-id-5709671.html

  • 相关阅读:
    iOS13使用bluetooth作为peripheral发送广播问题
    替代AttributeString的一个Label的类目
    Xcode拖动选中代码
    判断地图定位授权状态
    QLPreViewController的初步实用
    iOS的多版本配置(版本分离,多环境配置)
    -[NSBundle initWithURL:]: nil URL argument'
    xib的UIScrollView自适应高度
    ab工具-压力测试工具
    UIImageView的属性contentMode
  • 原文地址:https://www.cnblogs.com/codestack/p/13633566.html
Copyright © 2011-2022 走看看