TCP/IP Checksum 吐槽

算法原理:

假定 output[2] 爲輸出結果,input[n]爲待計算校驗和的內存塊。python

1)全部奇數位[0,2,4……] byte 累加進 結果的奇數位內存 output[0],若是溢出,則進位給偶數位的 output[1];c++

2)全部偶數位[1,3,5……] byte 累加進 結果的偶數位內存 output[1],若是溢出,則進位給奇數位的 output[0];算法

3)最後對 output[2] 求反碼便可網絡

示例代碼app

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import struct
import sys

def ip_cksum(s):
    
    a = 0
    b = 0

    #  偶數序號的  unsigned char 互相累加
    for i in xrange(0, len(s), 2):
        a += struct.unpack('B', s[i])[0]

    # 奇數序號的 unsigned char 互相累加
    for i in xrange(1, len(s), 2):
        b += struct.unpack('B', s[i])[0]


    # 縮小值爲 unsigned char
    while a > 256 or b > 256:
        b += a/256 # a 超過 byte 的部分進位給 b
        a = a%256

        a += b/256 # b 超過 byte 的部分進位給 a
        b = b%256

    # 取反
    a = ~a & 0xff
    b = ~b & 0xff
    
    # 校驗和做爲字符串
    v = chr(a) + chr(b)
    
    # 校驗和做爲 unsigned short
    v = struct.unpack('H', v)[0]
    
    return v


if __name__ == '__main__':
    for i in sys.argv[1:]:
        print ip_cksum(i)
View Code

 

關於TCP/IP 校驗和計算的代碼,網上不少,但很多都有些問題,這裏做一番簡單分析less

1.最尾部 byte 處理依賴機序

來自 http://locklessinc.com/articles/tcp_checksum/ 的 C 代碼片斷:socket

 1 unsigned short checksum1(const char *buf, unsigned size)
 2 {
 3     unsigned sum = 0;
 4     int i;
 5 
 6     /* Accumulate checksum */
 7     for (i = 0; i < size - 1; i += 2)
 8     {
 9         unsigned short word16 = *(unsigned short *) &buf[i];
10         sum += word16;
11     }
12 
13     /* Handle odd-sized case */
14     if (size & 1)
15     {
16         unsigned short word16 = (unsigned char) buf[i];
17         sum += word16;
18     }
19 
20     /* Fold to get the ones-complement result */
21     while (sum >> 16) sum = (sum & 0xFFFF)+(sum >> 16);
22 
23     /* Invert to get the negative in ones-complement arithmetic */
24     return ~sum;
25 }

注意第16行,對於buffer 長度非偶數狀況的處理, 致使此代碼只可在 Little-Endian (如x86) 機器上運行。只需對最後一個 byte 補一個’\0'的 byte,湊夠兩個 byte 而後轉爲 unsinged short 相加便可。tcp

2.多內存塊的計算

來自 python 網絡包建立、解析庫 dpkt 的代碼 dpkt.pyide

 1 try:
 2     import dnet
 3     def in_cksum_add(s, buf):
 4         return dnet.ip_cksum_add(buf, s)
 5     def in_cksum_done(s):
 6         return socket.ntohs(dnet.ip_cksum_carry(s))
 7 except ImportError:
 8     import array
 9     def in_cksum_add(s, buf):
10         n = len(buf)
11         cnt = (n / 2) * 2
12         a = array.array('H', buf[:cnt])
13         if cnt != n:
14             a.append(struct.unpack('H', buf[-1] + '\x00')[0])
15         return s + sum(a)
16     def in_cksum_done(s):
17         s = (s >> 16) + (s & 0xffff)
18         s += (s >> 16)
19         return socket.ntohs(~s & 0xffff)

它這裏會有兩個實現,一個是調用dnet庫的實現(見2-6行),一個是用python本身實現的版本(見8-19行)。
dnet 庫是 C 實現的一個庫,但和 dpkt 庫是同一個做者,這裏都有一個共同的問題:對於 in_cksum_add 進的內存塊,若是爲奇數長度,則尾部會追加一個byte '\x00' (見14行),這裏就致使了問題。其實呢,尾部的那個 byte 應該留給下一個接下來的內存塊一塊兒計算,當且僅當全部的內存塊都處理完畢(即 in_cksum_done 時),多餘一個 byte 時才該追加 byte '\x00'。svn

3.經典的實現

來自 wireshark 的 in_cksum.c

  1 /*
  2  * Checksum routine for Internet Protocol family headers (Portable Version).
  3  *
  4  * This routine is very heavily used in the network
  5  * code and should be modified for each CPU to be as fast as possible.
  6  */
  7 
  8 #define ADDCARRY(x)  {if ((x) > 65535) (x) -= 65535;}
  9 #define REDUCE {l_util.l = sum; sum = l_util.s[0] + l_util.s[1]; ADDCARRY(sum);}
 10 
 11 int
 12 in_cksum(const vec_t *vec, int veclen)
 13 {
 14     register const guint16 *w;
 15     register int sum = 0;
 16     register int mlen = 0;
 17     int byte_swapped = 0;
 18 
 19     union {
 20         guint8    c[2];
 21         guint16    s;
 22     } s_util;
 23     union {
 24         guint16 s[2];
 25         guint32    l;
 26     } l_util;
 27 
 28     for (; veclen != 0; vec++, veclen--) {
 29         if (vec->len == 0)
 30             continue;
 31         w = (const guint16 *)(const void *)vec->ptr;
 32         if (mlen == -1) {
 33             /*
 34              * The first byte of this chunk is the continuation
 35              * of a word spanning between this chunk and the
 36              * last chunk.
 37              *
 38              * s_util.c[0] is already saved when scanning previous
 39              * chunk.
 40              */
 41             s_util.c[1] = *(const guint8 *)w;
 42             sum += s_util.s;
 43             w = (const guint16 *)(const void *)((const guint8 *)w + 1);
 44             mlen = vec->len - 1;
 45         } else
 46             mlen = vec->len;
 47         /*
 48          * Force to even boundary.
 49          */
 50         if ((1 & (unsigned long) w) && (mlen > 0)) {
 51             REDUCE;
 52             sum <<= 8;
 53             s_util.c[0] = *(const guint8 *)w;
 54             w = (const guint16 *)(const void *)((const guint8 *)w + 1);
 55             mlen--;
 56             byte_swapped = 1;
 57         }
 58         /*
 59          * Unroll the loop to make overhead from
 60          * branches &c small.
 61          */
 62         while ((mlen -= 32) >= 0) {
 63             sum += w[0]; sum += w[1]; sum += w[2]; sum += w[3];
 64             sum += w[4]; sum += w[5]; sum += w[6]; sum += w[7];
 65             sum += w[8]; sum += w[9]; sum += w[10]; sum += w[11];
 66             sum += w[12]; sum += w[13]; sum += w[14]; sum += w[15];
 67             w += 16;
 68         }
 69         mlen += 32;
 70         while ((mlen -= 8) >= 0) {
 71             sum += w[0]; sum += w[1]; sum += w[2]; sum += w[3];
 72             w += 4;
 73         }
 74         mlen += 8;
 75         if (mlen == 0 && byte_swapped == 0)
 76             continue;
 77         REDUCE;
 78         while ((mlen -= 2) >= 0) {
 79             sum += *w++;
 80         }
 81         if (byte_swapped) {
 82             REDUCE;
 83             sum <<= 8;
 84             byte_swapped = 0;
 85             if (mlen == -1) {
 86                 s_util.c[1] = *(const guint8 *)w;
 87                 sum += s_util.s;
 88                 mlen = 0;
 89             } else
 90                 mlen = -1;
 91         } else if (mlen == -1)
 92             s_util.c[0] = *(const guint8 *)w;
 93     }
 94     if (mlen == -1) {
 95         /* The last mbuf has odd # of bytes. Follow the
 96            standard (the odd byte may be shifted left by 8 bits
 97            or not as determined by endian-ness of the machine) */
 98         s_util.c[1] = 0;
 99         sum += s_util.s;
100     }
101     REDUCE;
102     return (~sum & 0xffff);
103 }

1)92行是當前內存塊還餘一個 byte ,則會 s_util 等待下個內存卡再處理——恰當的處理前面提到的第二個問題

2)94行是全部內存塊處理完畢後,對尾部最後一個 byte 的處理 ——恰當的處理了前面提到的第一個問題

3)看點:指針非對齊的狀況下處理

50行會先將未對其的1個 byte 暫存,這樣可迫使指針對齊,但又爲了讓同奇位、同偶位內存相加,因此使 sum<<8;81行,若是前面sum是已經左移過的,則再次 sum<<8,讓sum迴歸最初的奇偶次序

注:REDUCE 宏實現的功能是將大於 short 的值(即大於65535)轉化爲 short 能表示的值.

相關文章
相關標籤/搜索