tcp層的校正值難道還需要ip層的中繼資料也就是ip頭嗎?如果一切都是理想的顯然不需要,因為這違背了分層隔離的原則,下層一定不能依賴上層,但是上層可以訪問下層,還好tcp使用ip資訊正是這一點。按照封包原則,封裝到TCP層的時候,ip資訊還沒有封裝上去,但是校正值卻需要馬上進行計算,所以必須手工構造一個偽頭部來表示ip層的資訊,怎麼構造呢?在資料到tcp層的時候其實使用者肯定知道資料發往何處,源地址和目的地址都有,只不過是還沒有封裝到資料上罷了,簡單的例子就是在socket程式中,connect或者accept或者recvfrom以及sendto都會保留有地址資訊,另外偽頭中還將保留有傳輸層協議資訊,所有這一切都是為了使得通訊更加安全和縝密,試想如果一個中間人截獲了一個icmp包,然後改為了udp包或發生什麼,該udp不是隨意的而是精心構造的,但是加入了偽頭部如此之行為得逞就困難多了,因為偽頭部中有協議欄位,除此之外,任何錯誤的投遞,錯誤的資料長度以及錯誤的協議都會被檢測到。看一下偽頭吧:
struct psd_head
{
__u32 saddr; // 源網路層地址
__u32 daddr; // 目的網路層地址
__u8 mbz; //賦0
__u8 ptcl; // 傳輸層協議
__u16 tcpudpl; //傳輸層長度
};
以下是一個簡單的校正和校正碼的計算函數
void tcpv4_check_addr( __u16 * ppkgdata )
{
char * indata;
__u16 ippktlen, udppktlen,tcppktlen,wd;
__u32 ipheadlen;
__u32 sum,i,pl,el;
struct psd_head psd;
struct iphdr * ipd;
struct tcphdr * tcpd;
struct udphdr * udpd;
__u16 * databegin;
indata = (char *)ppkgdata; //從MAC開始的整個幀
ipheadlen = 14 + (indata[14]&0x0f)*4 ; //MAC和ip頭的長度和
databegin = (__u16 *)(indata + ipheadlen); //ip資料
ipd = (struct iphdr *)(indata + 14); //MAC資料
tcpd = (struct tcphdr *)(indata + ipheadlen); //ip資料
ippktlen = htons(ipd->tot_len); //ip頭和ip資料的總長度
if(ipd->protocol == 0x6){
tcppktlen = ippktlen +14 - ipheadlen; //tcp頭和tcp資料的總長度
tcpd->check = 0;
psd.saddr = ipd->saddr; //構造偽頭部
psd.daddr = ipd->daddr;
psd.mbz=0;
psd.ptcl = 0x06; //ip的下一個頭
psd.tcpudpl = htons(tcppktlen);
sum = 0;
wd = tcppktlen/2; //每次資料前移16位而不是一個位元組
for(i=0;i
sum += *databegin;
databegin++;
}
el = tcppktlen - wd*2;
if(el != 0)
sum += (*databegin&0xff);
wd = sizeof(struct psd_head)/2;
databegin = (__u16 *) &psd.saddr;
for(i=0;i
sum += *databegin;
databegin++;
}//下面這個運算式就是高低16分別相加,sum/65536就是高16位:sum
pl = (sum + sum/65536)&0xffff;
sum = 0xffff^pl;
tcpd->check = (__u16)sum; //檢驗和的計算很簡單,就是將資料相加並且回卷之後取反
}
return;
}
以上的演算法再清晰不過了,甚至將tcp,ip頭部的位移怎麼計算都表達了出來,但是這個函數並不適用於實際情況,因為在高負載網路環境下,特別是NAT或者資料過濾網關環境下,校正和的計算是一個很頻繁的過程,因此上述函數的c語言本質將很影響效率,取而代之的是用彙編實現,正如linux核心中的那樣:
static inline __sum16 csum_fold(__wsum sum)
{
__asm__(
"addl %1, %0 ;/n"
"adcl $0xffff, %0 ;/n"
: "=r" (sum)
: "r" ((__force u32)sum
"" ((__force u32)sum & 0xffff0000)
);
return (__force __sum16)(~(__force u32)sum >> 16);
}
static inline __wsum csum_tcpudp_nofold(__be32 saddr, __be32 daddr, unsigned short len, unsigned short proto, __wsum sum)
{
__asm__(
"addl %1, %0 ;/n"
"adcl %2, %0 ;/n"
"adcl %3, %0 ;/n"
"adcl $0, %0 ;/n"
: "=r" (sum)
: "g" (daddr), "g"(saddr), "g"((len + proto)
return sum;
}
static inline __sum16 csum_tcpudp_magic(__be32 saddr, __be32 daddr, unsigned short len, unsigned short proto, __wsum sum)
{
return csum_fold(csum_tcpudp_nofold(saddr,daddr,len,proto,sum));
}
別看一個小小的計算校正和,它本質上影響了網路傳輸的效率,如果用tcpv4_check_addr這個Function Compute校正和,效率慢了10倍之多,但是用彙編取而代之的話,效率雖然由於額外吸收而有所下降,但是數量級並沒有改變。