IP校驗和詳解

ForTechnology發表於2011-08-05
IP校驗和詳解

一、校驗和演算法

   之前一直只知道IP校驗和演算法反碼求和相關的,但具體細節不清楚,今天瞭解了下。

   IP校驗和主要是用來保證資料(IP包頭)的完整性的.它用的演算法非常簡單,就是反碼求和校驗.需要注意的是反碼求和又叫1的補碼(one'scomplement),而2的補碼就是我們通常說的補碼求和了.校驗演算法具體如下.

1、傳送方

   i)將校驗和欄位置為0,然後將IP包頭按16位元分成多個單元,如包頭長度不是16位元的倍數,則用0位元填充到16位元的倍數;

   ii)對各個單元採用反碼加法運算(即高位溢位位會加到低位,通常的補碼運算是直接丟掉溢位的高位),將得到的和的反碼填入校驗和欄位;

   iii)傳送資料包.


2、接收方

   i)將IP包頭按16位元分成多個單元,如包頭長度不是16位元的倍數,則用0位元填充到16位元的倍數;

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

   iii)如果是全1則進行下步處理,否則意味著包已變化從而丟棄之.

   需要強調的是反碼和是採用高位溢位加到低位的,如3位元的反碼和運算:100b+101b=010b(因為100b+101b=1001b,高位溢位1,其應該加到低位,即001b+1b(高位溢位位)=010b),具體細節請參考文章:http://blog.chinaunix.net/u/20/showart_438418.html


二、校驗和原始碼

   網上流傳多組實現,常見的有如下兩種(如追求效率可改寫為彙編程式碼):

1、RFC1071原始碼
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位元組)).
    緊接著的一個判斷語句是為了能處理輸入資料是奇數個位元組的這種情況.
    再接著的資料迴圈是實現反碼演算法(在前面的普通加法得到的資料的基礎上),由反碼和的高位溢位加到低位的性質,可得到"32位的資料的高位位元移位16位元,再加上原來的低16位元,不影響最終結果" 這個等價運算,因為sum的最初值(剛開始迴圈時)可能很大,所以這個等價運算需迴圈進行,直到sum的高位元(16位元以上)全為0.對於32位的 sum,事實上這個運算迴圈至多隻有兩輪,所以也有程式直接用兩條"sum = (sum & 0xffff) + (sum >> 16);"代替了整個迴圈.
    最後,對和取反返回.

2、對資料長度沒限制的實現

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的高位的值,一旦發現其有溢位的危險,就及時運用等價運算關係消除了這個危險.

三、幾個細節問題

  1、資料部分改變時的重校驗
   考慮這樣的應用場景:路由器轉發IP報文時,有可能只更改了IP資料包頭的部分內容(如更改了TTL,分片了或SNAT更改了源IP等~~~),卻需要重校驗的問題.為提高轉發效率,要求重校驗演算法儘可能快,故出現瞭如下所示的重校驗演算法(只是一個簡單的示例):
UpdateTTL(struct ip_hdr *ipptr, unsigned char n)     
{
     unsigned long sum;
     unsigned short old;

     ld = 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'有多個而已,直接用反碼加法搞定即可。

2、為什麼採用反碼和運算
   IP資料包校驗要求速度快,所以只採用了簡單的和校驗,為什麼採用反碼和而不是補碼和呢?
   i)反碼和的溢位有後效性(蔓延性)
    反碼和將高位溢位加到低位,導致這個溢位會對後面操作有永久影響,有後效性;而補碼和直接將高位和溢位,導致這個溢位對後面的操作再無影響,因此無後效性
   ii)反碼校驗無需考慮位元組序
正因為反碼和的溢位有後效性,導致大端位元組序(big-endian)和小端位元組序(little-endian)對同一資料序列(如兩個16位元的序列)產生的校驗和也只是位元組序相反,而補碼和因為將溢位丟掉了,不同位元組序之間的校驗和大不相同且沒什麼聯絡。
   基於以上的理由,校驗和運算既可選擇在資料被轉換成網路位元組序前,也可選擇在之後,只要保證被校驗的欄位和填寫的校驗和欄位的位元組序保持一致就可以了。(這其實可以看作是負負得正,計算校驗和與位元組序有關,然後寫校驗和欄位與位元組序有關,然後直接計算校驗和再寫校驗和欄位則與位元組序無關了~~)

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/25897606/viewspace-704277/,如需轉載,請註明出處,否則將追究法律責任。

相關文章