1. 程式人生 > >TCP/IP實現(六) IP選項處理

TCP/IP實現(六) IP選項處理

一.概述

       IP選項是IP固定首部之後的選項部分,由於IP首部長度是用4bit來計數,以4個位元組為表示的,所以首部長度最多為60個位元組,IP選項最多為40個位元組。IP選項欄位可能包含0~多個單獨選項。選項包含兩類:單位元組與多位元組。單位元組選項只有1個位元組的型別欄位,多位元組欄位包含1個位元組的型別欄位,1個位元組的長度欄位,以及之後的資料欄位(資料欄位的第一個位元組一般是資料偏移,如在源路由選項中代表下一個待處理的欄位)。其結構如下圖所示:

         

       常見的IP選項有以下幾種:

  1.      NOP選項              IPOPT_NOP      單位元組選項       用於與後續選項湊成4位元組,使後續選項落在4位元組邊界上,比如常與多位元組選項中的type,len,offset湊成4個位元組。
  2.      EOL選項              IPOPT_EOL       單位元組選項       用於補充在IP選項的最後部分,因為IP選項的總長度必須是4位元組的倍數。
  3.      LSRR選項           IPOPT_LSRR     多位元組選項       寬鬆路由選項
  4.      SSRR選項           IPOPT_SSRR    多位元組選項       嚴格路由選項
  5.      Record Route      IPOPT_RR         多位元組選項       記錄路由選項
  6.      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

      待補充