假定 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)
關於TCP/IP 校驗和計算的代碼,網上不少,但很多都有些問題,這裏做一番簡單分析less
來自 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
來自 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
來自 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 能表示的值.