2014-03-13 61 views
1

相信TCP校驗和函數執行以下操作:瞭解TCP校驗和函數

  1. 的僞首和TCP片段報頭和數據分裂成2個字節的塊。
  2. 如果長度不是2個字節,則在最後一個塊的末尾添加一個0字節的填充字以使其爲2個字節。
  3. 取總和的補碼得到TCP校驗和。

聽起來很簡單。因此,我寫我自己的通用checksum功能:

#include <inttypes.h> 
#include <arpa/inet.h> 

uint16_t checksum(uint16_t * data, int size) { 
    uint16_t sum = 0; 

    int i = 0, length = size/2; 

    while (i < length) sum += data[i++]; 

    if (size % 2) sum += data[i] & 0xFF00; 

    return htons(~sum); 
} 

但是其他人都寫checksum功能,這似乎更復雜。例如:

uint16_t checksum(uint16_t * addr, int len) { 
    int nleft = len; 
    int sum = 0; 

    uint16_t * w = addr; 
    uint16_t answer = 0; 

    while (nleft > 1) { 
     sum += *w++; 
     nleft -= sizeof(uint16_t); 
    } 

    if (nleft == 1) { 
     *(uint8_t *) (&answer) = *(uint8_t *) w; 
     sum += answer; 
    } 

    sum = (sum >> 16) + (sum & 0xFFFF); 
    sum += (sum >> 16); 
    answer = ~sum; 
    return (answer); 
} 

我有一個關於這個代碼的一些問題:

  1. 這句話是什麼*(uint8_t *) (&answer) = *(uint8_t *) w;實際上呢?
  2. 爲什麼我們採取的總和:

    sum = (sum >> 16) + (sum & 0xFFFF); 
    sum += (sum >> 16); 
    
  3. 有沒有計算TCP校驗和變更的方式嗎?

我真的不明白我們爲什麼做sum = (sum >> 16) + (sum & 0xFFFF)。考慮sum0xABCD

0xABCD >> 16 == 0x0000 

0xABCD & 0xFFFF == 0xABCD 

0x0000 + 0xABCD == 0xABCD 

這似乎是一個多餘的一步。下一條語句sum += (sum >> 16)也是如此。

+0

看起來像'sum =(sum >> 16)+(sum&0xFFFF)'是爲了將所有數據包裝成16位整數,因此在第二個例子中,sum是聲明爲'int sum = 0;' – deimus

+0

對於問題1,按照RPC 793填充8位零的最後一個字。 – Jiminion

+0

請注意,校驗和是以補碼形式完成的。這不同於簡單地用'sum + = ...'來包裝溢出的uint16_t。 – nos

回答

0

校驗和功能似乎只適用於big-endian處理器。

第一個while循環針對速度進行了優化。

&answer招加載最後一個字節(如果有奇數個字節)進入answer高字節,其餘低字節爲零,類似於data[i] & 0xff00你的代碼做什麼。它的工作方式是這樣的

1) take the address of answer  (&answer) 
2) convert that to a byte pointer (uint8_t *) 
2a) on a big endian processor the first byte of a 16-bit quantity is the high byte 
3) overwrite the high byte with the last byte of the data 

校驗應該與攜帶重新添加計算。它在這裏假設這個代碼是一臺機器上運行,其中一個int是32位。因此,(sum & 0xffff)是16位校驗和,以及(sum >> 16)是進位位(如果有的話)需要在被添加回去。因此,線

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

調整爲包括承載和。但是,該行代碼本身可能會生成另一個進位位。所以下一行sum += (sum >> 16)增加了(如果有的話)返回校驗和。

最後,採取答案的補碼。請注意,htons未被使用,因爲整個函數隱式假定它在大端處理器上運行。

+0

rfc 1071指出校驗和計算與字節順序無關。 – user666412

1
  1. *(uint8_t *) (&answer) = *(uint8_t *) w;在右側,它將w轉換爲uint8_t*並將其取消引用。它會截斷引用uint16_t*時指向最後一個字節的垃圾數據。在左側,它將地址(指針)answer並將其轉換爲uint8_t*並將其取消引用。因此它需要w指向的第一個字節,並將該值賦給應答的第一個字節。實際上,這行代碼爲2. Add a one byte padding of 0s to the end of the last block if it's not 2 bytes long, to make it 2 bytes.需要左側的轉換來支持大端系統......我想。
2

這句話是什麼*(uint8_t *)(&答案)= *(uint8_t *)瓦; 居然呢?

這就引起uint16_tuint8_t,所以只有最右位從w複製到answer。試想一下:

uint16_t x = 0x1234; 
uint16_t* w = &x; // *w = // 0001001000110100 

*(uint16_t *) (&answer) = *(uint16_t *) w; // answer = 0001001000110100 

*(uint8_t *) (&answer) = *(uint8_t *) w; // answer = 0000000000110100 

爲什麼我們採取的總和:

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

sum是位。 65536 ≡ 1 mod 65535,所以end-around carry expression(sum & 0xffff) + (sum >> 16)減少sum65535。這是必要的,以增加任何(最終)結果帶回到結果總和。

1
  1. 此語句容納其中分組具有奇數個字節的情況下(見RFC793或RFC1701):通過摻入的總和與2個最顯著字節的數量(answer)作爲Z和2 [A,B] + [C,D] + ... + [Z,0]最低有效字節爲0.記住+這裏總是1的補碼加法。

  2. sum是一個32位累加器。爲了補充1的補碼,我們在積累比特之後加入進位。 sum的2個最高有效字節包含進位位,如果有的話。

  3. 如果你看看RFC1701你可以在頂部看到哪些RFC更新它。沒有任何東西取代它。