1. 前言
校正和計算是NAT功能和內容修改功能的基本功,這些操作進行後都需要修改資料頭中的校正和。
2. 16位校正和計算
2.1 基本原理
IP/ICMP/IGMP/TCP/UDP等協議的校正和演算法都是相同的,採用的都是將資料流視為16位整數流進行重複疊加計算。為了計算檢驗和,首先把檢驗和欄位置為0。然後,對有效資料範圍內中每個16位進行二進位反碼求和,結果存在檢驗和欄位中,如果資料長度為奇數則補一位元組0。當收到資料後,同樣對有效資料範圍中每個16位元進行二進位反碼的求和。由於接收方在計算過程中包含了發送方存在首部中的檢驗和,因此,如果首部在傳輸過程中沒有發生任何差錯,那麼接收方計算的結果應該為全0或全1(具體看實現了,本質一樣) 。如果結果不是全0或全1,那麼表示資料錯誤。
2.2 程式演算法
2.2.1 C實現
這是RFC1071中提供的C語言程式:
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;
}
當然,如果用組合語言實現計算速度會快得多,對於不同的CPU體系,需要各自對應編寫不同的彙編,在Linux核心原始碼中有各種CPU體系的IP校正和計算原始碼。
2.2.2 增量式修改
如果只修改了一個位元組,如只修改IP頭中的TTL,重新計算校正和時是沒必要將所有資料範圍內資料重新計算一遍的,RFC1141中提出了增量式演算法:
~C' = ~(C + (-m) + m') = ~C + (m - m') = ~C + m + ~m'
C'為修改後的校正和,C為修改前的校正和,m為修改前的數值,m'為修改後的數值,~為補碼值。
C代碼實現為:
UpdateTTL(iph,n)
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));
}
2.3 網路應用
2.3.1 IPv4
IPv4層中的校正和只包括IPv4頭部分,不包括上層協議頭和應用程式層資料,校正和是必須計算的。
2.3.2 IPv6
IPv6頭本身已經不包括校正和欄位,只靠上層協議的校正和。
2.3.3 ICMP/IGMP
ICMP/IGMP校正和計算範圍為從ICMP/IGMP開始到資料結束,不包括IP頭部分,校正和是必須計算的。
2.3.4 TCP/UDP
TCP/UDP的校正和計算有點特殊,所計算的資料範圍除了包括TCP/UDP頭開始到資料結束外,還要包括一個IP偽頭部分,所謂偽頭,只有12位元組資料,包括源地址(4位元組)、目的地址(4位元組)、協議(2位元組,第一位元組補0)和TCP/UDP包長(2位元組)。TCP的校正和是必須的,而UDP的校正和是可選的,如果UDP中校正和欄位為0,表示不進行校正和計算,因此對於UDP協議資料的修改後想偷懶的話直接將校正和設為0 就可以了。
3. 32位校正和
3.1 以太幀
以太幀校正和使用的是CRC校正,校正和為4位元組32位,演算法比較適合硬體實現,其計算和校正都是底層完成的,在IP棧以上時就不用考慮,即使上層直接是構造以太幀發送,也只需構造以太頭部即可,發送時由底層自動添加後面的校正和。
3.2 SCTP
在SCTP(協議號:132)協議中,校正和計算比較特殊,採用了和以太包校正和相似的CRC32演算法(RFC3309),計算結果是32位而不再是16位。計算範圍為從SCTP頭到資料結束,不包括IP偽頭。