IP校驗和詳解

 簡介    

      IP校驗和主要是用來保證數據(IP包頭)的完整性的.它用的算法很是簡單,就是反碼求和校驗.須要注意的是反碼求和又叫1的補碼(one'scomplement),而2的補碼就是咱們一般說的補碼求和了.校驗算法具體以下。html

一、發送方

  1. 將校驗和字段置爲0,而後將IP包頭按16比特分紅多個單元,如包頭長度不是16比特的倍數,則用0比特填充到16比特的倍數;python

  2. 對各個單元採用反碼加法運算(即高位溢出位會加到低位,一般的補碼運算是直接丟掉溢出的高位),將獲得的和的反碼填入校驗和字段;算法

  3. 發送數據包.網絡

二、接收方

  1. 將IP包頭按16比特分紅多個單元,如包頭長度不是16比特的倍數,則用0比特填充到16比特的倍數;oop

  2. 對各個單元採用反碼加法運算,檢查獲得的和是否符合是全1(有的實現可能對獲得的和會取反碼,而後判斷最終值是否是全0);spa

  3. 若是是全1則進行下步處理,不然意味着包已變化從而丟棄之.須要強調的是反碼和是採用高位溢出加到低位的,如3比特的反碼和運算:100b+101b=010b(由於100b+101b=1001b,高位溢出1,其應該加到低位,即001b+1b(高位溢出位)=010b),.net

校驗和源碼

一、RFC1071源碼

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

  unsigned short csum(unsigned char *addr, int count)

  {

  /* Compute Internet Checksum for "count" bytes

  * beginning at location "addr".

  */

  register long sum = 0;

  while( count > 1 )

  {

  /* This is the inner loop */

  sum += * (unsigned short) addr++;

  count -= 2;

  }

  /* Add left-over byte, if any */

  if( count > 0 )

  sum += * (unsigned char *) addr;

  /* Fold 32-bit sum to 16 bits */

  while (sum>>16)

  sum = (sum & 0xffff) + (sum >> 16);

  return ~sum;

  }

  第一個while循環是作普通加法(2進制補碼加法),由於IP包頭和TCP整個報文段比較短(沒達到2^17數量級),因此不可能致使4字節的sum溢出(unsigned long 通常至少爲4字節)).unix

  緊接着的一個判斷語句是爲了能處理輸入數據是奇數個字節的這種狀況.再接着的數據循環是實現反碼算法(在前面的普通加法獲得的數據的基礎上),由反碼和的高位溢出加到低位的性質,可獲得"32位的數據的高位比特移位16比特,再加上原來的低16比特,不影響最終結果" 這個等價運算,由於sum的最初值(剛開始循環時)可能很大,因此這個等價運算需循環進行,直到sum的高比特(16比特以上)全爲0.對於32 位的 sum,事實上這個運算循環至多隻有兩輪,因此也有程序直接用兩條"sum = (sum & 0xffff) + (sum >> 16);"代替了整個循環.最後,對和取反返回.code

二、對數據長度沒限制的實現

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

  unsigned short cksum (struct ip *ip, int len)

  {

  long sum = 0; /* assume 32 bit long, 16 bit short */

  while ( len >1 )

  {

  sum += *((unsigned short *) ip)++;

  if (sum & 8x00000000) /* if high-order bit set, fold */

  sum = (sum & 0xFFFF) + (sum>> 16) ;

  len -= 2;

  }

  if ( len ) /* take care of left over byte */

  sum += ( unsigned short ) * (unsignedl char *) ip;

  while ( sum >> 16)

  sum =(sum & 0xFFFF) + (sum>> 16);

  return ~sum;
  }

  這個實現與前面的一個的最大的不一樣是對數據的長度沒什麼限制了,由於它在第一個循環的加法運算中實時檢測sum的高位的值,一旦發現其有溢出的危險,就及時運用等價運算關係消除了這個危險.htm

3. 彙編的實現

Linux 2.6內核中的校驗算法,使用匯編語言編寫的,顯然效率要高些。代碼以下:
    unsigned short 
ip_fast_csum(unsigned char * iph,
      unsigned int ihl)
    {
    unsigned int sum;
    
    __asm__ __volatile__(
        "movl (%1), %0 
;\n"
        "subl $4, %2 ;\n"
        "jbe 2f ;\n"
        "addl 
4(%1), %0 ;\n"
        "adcl 8(%1), %0 ;\n"
        "adcl 12(%1), %0 
;\n"
    "1:     adcl 16(%1), %0 ;\n"
        "lea 4(%1), %1 ;\n"
      
  "decl %2 ;\n"
        "jne 1b ;\n"
        "adcl $0, %0 ;\n"
        "movl %0, %2 ;\n"
        "shrl $16, %0 ;\n"
        "addw %w2, %w0 
;\n"
        "adcl $0, %0 ;\n"
        "notl %0 ;\n"
    "2: ;\n"
  
  /* 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(sum);
  
  }

幾個細節問題

一、數據部分改變時的重校驗

  考慮這樣的應用場景:路由器轉發IP報文時,有可能只更改了IP數據包頭的部份內容(如更改了TTL,分片了或SNAT更改了源IP等~~~),卻須要重校驗的問題.爲提升轉發效率,要求重校驗算法儘量快,故出現了以下所示的重校驗算法(只是一個簡單的示例):

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

  UpdateTTL(struct ip_hdr *ipptr, unsigned char n)

  {

  unsigned long sum;

  unsigned short old;

  old = ntohs(*(unsigned short *)&ipptr->ttl);

  ipptr->ttl -= n;

  sum = old + (~ntohs(*(unsigned short *)&ipptr->ttl) & 0xffff);

  sum += ntohs(ipptr->Checksum);

  sum = (sum & 0xffff) + (sum>>16);

  ipptr->Checksum = htons(sum + (sum>>16));

  }

  算法的實現依據是這樣的.假設包頭原校驗和爲~C,改變的字段的原始值是m,更改後的值是m',設~C'爲重校驗和,則有 ~C' = ~(C+(-m)+m') = ~C+(m-m') = ~C+m+~m'等價關係的成立基於反碼的運算性質:取反運算知足結合律,按位取反運算與符號取反(及相反數)是等價的(即~C=-C).

  若是有多個字段改變,只是上面的公式中的m和m'有多個而已,直接用反碼加法搞定便可。

二、爲何採用反碼和運算

  IP數據包校驗要求速度快,因此只採用了簡單的和校驗,爲何採用反碼和而不是補碼和呢?

  1. 反碼和的溢出有後效性(蔓延性)

    反碼和將高位溢出加到低位,致使這個溢出會對後面操做有永久影響,有後效性;而補碼和直接將高位和溢出,致使這個溢出對後面的操做再無影響,所以無後效性

  2. 反碼校驗無需考慮字節序

    正由於反碼和的溢出有後效性,致使大端字節序(big-endian)和小端字節序(little-endian)對同一數據序列(如兩個16比特的序列)產生的校驗和也只是字節序相反,而補碼和由於將溢出丟掉了,不一樣字節序之間的校驗和大不相同且沒什麼聯繫。

  基於以上的理由,校驗和運算既可選擇在數據被轉換成網絡字節序前,也可選擇在以後,只要保證被校驗的字段和填寫的校驗和字段的字節序保持一致就能夠了。(這其實能夠看做是負負得正,計算校驗和與字節序有關,而後寫校驗和字段與字節序有關,而後直接計算校驗和再寫校驗和字段則與字節序無關。)

參考文章節

  1. http://blog.chinaunix.net/u/20/showart_438512.html,關於IP校驗和的

  2. http://blog.chinaunix.net/u/12313/showart_176114.html,關於網絡校驗和

  3. http://www.wesoho.com/article/Delphi/2143.htm,關於IP校驗和的

  4. http://blog.chinaunix.net/u/20/showart_438418.html,關於補碼和反碼的

相關文章
相關標籤/搜索