TCP/IP實現(六) IP選項處理
一.概述
IP選項是IP固定首部之後的選項部分,由於IP首部長度是用4bit來計數,以4個位元組為表示的,所以首部長度最多為60個位元組,IP選項最多為40個位元組。IP選項欄位可能包含0~多個單獨選項。選項包含兩類:單位元組與多位元組。單位元組選項只有1個位元組的型別欄位,多位元組欄位包含1個位元組的型別欄位,1個位元組的長度欄位,以及之後的資料欄位(資料欄位的第一個位元組一般是資料偏移,如在源路由選項中代表下一個待處理的欄位)。其結構如下圖所示:
常見的IP選項有以下幾種:
- NOP選項 IPOPT_NOP 單位元組選項 用於與後續選項湊成4位元組,使後續選項落在4位元組邊界上,比如常與多位元組選項中的type,len,offset湊成4個位元組。
- EOL選項 IPOPT_EOL 單位元組選項 用於補充在IP選項的最後部分,因為IP選項的總長度必須是4位元組的倍數。
- LSRR選項 IPOPT_LSRR 多位元組選項 寬鬆路由選項
- SSRR選項 IPOPT_SSRR 多位元組選項 嚴格路由選項
- Record Route IPOPT_RR 多位元組選項 記錄路由選項
- Timestamp IPOPT_RR 多位元組選項 時間戳選項
可以在其上設定IP選項的套接字包括TCP,UDP和原始IP套接字,要清除這些選項可以使用setsockopt(第四個值引數為空或第五個值長度引數為0)設定。TCP套接字自動使用來自SYN所在資料報的原路徑選項的逆轉,而不必我們使用setsockopt告訴核心使用什麼路徑來發送。
二.IP選項使用
1.LSRR(寬鬆源路由選項) & SSRR(嚴格源路由選項)
雖然IP選項最多隻含有40個位元組,但是傳遞給函式setsockopt的引數卻是一個指向大小小於等於44的緩衝區,這是因為緩衝區中指定的源路由路徑中的第一個地址會被賦值到IP資料報的目的地址中,然後這個地址就可以被移除了。每到一個指定的路由節點都會將offset所指的地址設定為下一目的地址,在將從當前路由外出的介面地址放至offset處,隨後將offset後移。其傳輸過程中填充步驟的示意圖如下:
具體的呼叫setsockopt的寫法在此不再贅述,可以參考UNP p564 ~ p566。
三.IP選項處理實現
在博文《TCP/IP實現(五) IP協議》中提到,IP選項是由IP層的ipintr函式在驗證IP固定首部之後,匹配目的地址之前進行處理的。在這期間會呼叫ip_dooptions處理IP選項。其總體實現如下:
int ip_dooptions(mbuf* m)
{
struct ip* = mtod(m, ip*);//指向mbuf中的ip資料報部分
u_char* *cp = (u_char*)(ip + 1); //指向ip選項
int cnt = (ip->ip_hl << 2) - sizeof(struct ip); // 計算ip選項長度
int optlen = 0, icmp_type = ICMP_PARAMROB;// 預設ICMP報文為引數錯誤
for(; cnt > 0; cnt - optlen, cp += optlen) { // 遍歷選項
opt = cp[type];// 選項型別
if(opt == IPOPT_EOL) // EOL為尾部填充選項,結束遍歷
break;
if(opt == IPOPT_NOP) // 填充選項,不做處理
optlen = 1; // 選項長度
else {
optlen = cp[len];
if(optlen <= 0 || optlen > cnt) { // 檢查選項長度
goto bad; // 返送ICMP報文
}
switch(opt){
處理各選項
default: // 未識別選項不做處理
break;
} // switch
} //else
} //for
判斷是否根據源路由進行轉發
return 1;
bad:
//回覆ICMP報文;
icmp_error(xx, icmp_type, code,...);
}
1.記錄路由選項IPOPT_RR
首先檢查資料偏移offset是否過小(小於4),若是則傳送ICMP_PARAMROB引數問題ICMP報文,code為出錯位元組在分組內的偏移。接著計算該IP選項的可用儲存空間,若不夠則不記錄,否則查詢路由,記錄外出介面的IP地址,若無路由資訊則返回ICMP住居不可達報文。選項處理程式碼如下:
case IPORT_RR:
if((off = cp[offset]) < 4) {
code = &cp[offset] - (u_char*)ip;
goto bad;
}
if(off + sizeof(struct in_addr) > optlen) // 選項的儲存空間不足
break;
//將目的地址拷貝到一個儲存地址的全域性buf中
bcopy((caddr_t)(&ip->ip_dst), (caddr_t)&ipaddr.sin_addr, sizeof(ipaddr.sin_addr));
if(鄰接網路和路由搜尋失敗){
icmp_type = ICMP_UNREACH;
code = ICMP_UNREACH_HOST;
goto bad;
}
若目的地址就是本機則將接收介面的地址拷貝到選項中,否則將外出介面的地址拷貝到選項中
break;
2.源路由選項LSRR & SSRR
源路由選項分為寬鬆源路由選項和嚴格源路由選項。寬鬆源路由選項只指定某些中間路由器的路由,而嚴格路由選項卻包含了源站到目的棧之間的全部路由。從源站出發時會將IP資料報的目的地址設定為源路由選項中的offset所指的地址,且每到一個源路由選項中所指明的站,都會將IP資料報的目的地址設定為源路由表項中的下一站,即offset偏移處的地址。實現程式碼如下:
case IPOPT_LSRR:
case IPOPT_SSRR:
if((off = cp[offset]) < 4){ // 偏移過小
code = &cp[offset] - (u_char *)ip; //計算錯誤位元組在分組中的偏移
goto bad;
}
// 第一步
將IP資料報中的目的地址與本地地址進行匹配
if(若無匹配的本地地址){
if(是嚴格路由選項) { //說明源路由失敗,因為指定了每一個節點
icmp_type = ICMP_UNREACH;
code = ICMP_UNREACH_SRCFALL;// 源路由失敗ICMP報文
goto bad;
}
// 執行到此處說明是寬鬆路由協議,寬鬆路由允許經過未指定的節點,對於未指定的節點不做過多處理
// 若無其它選項則會交還IP處理函式ipintr繼續處理,否則處理其它選項
break;
}
// 允許到此處說明本站是源路由站之一
if(源路由表項中已經沒有下一個路由項了) {
//說明已到達終點
break;
}
// 執行到此處說明還未到終點
// 檢查源路由表項中的下一站地址
if(是嚴格路由協議){
下一站只能在鄰接網路上,因此呼叫呼叫ifa_ifwithdstaddr匹配各介面的目的地址(SLIP介面就指定了目的地址),
若匹配失敗則繼續呼叫ifa_ifwithnet匹配鄰接網路
}else {
//寬鬆路由
進行路由查詢
}
if(下一站地址搜尋失敗) {
icmp_type = ICMP_UNREACH;
code = ICMP_UNREACH_SRCFALL;// 源路由失敗ICMP報文
goto bad;
}
將IP地址設定未下一站地址,即將cp[off]處的地址設定為下一站地址
將外出介面的地址放至cp[off]處
break;
當我們呼叫getsockopt函式獲取源路徑時,會發現得到的原路徑是反向的,且最後一個地址是收到IP資料報源站地址,在源路由中的最後一個地址(即傳送端設定的目的地址,也就是接收者的本機地址)也會被放至到第一個位元組(選項型別之前)。示意圖如下:
這個過程是通過兩個函式和一個結構體實現的,save_rte函式將IP資料報中的源路由及源地址儲存到結構體ip_srcrt中,接著再由函式ip_srcroute將其逆轉儲存到一個mbuf中,儲存後的格式如上圖所示。對於其具體實現在此不進行贅述,可以參考TCP V2 P205 ~ P207。
3.時間戳選項IPOPT_TS
待補充