1. 程式人生 > >TCP/IP實現(五) IP協議

TCP/IP實現(五) IP協議

一.IP首部

1.概述

        ip資料報的首部格式如下:

                             

       首部長度是已4位元組為單位的,因此首部最長為15 * 4 = 60個位元組。生存時間規定了資料報可以經過的最多路由器數。標識欄位ip_id唯一標識主機發送的每一份資料報,通常每傳送一份報文其值就加1。

2.大端序與小端序

       大端序又叫網路位元組序,其高位在低位元組處,而小端序一般也叫做主機位元組序,其高位在高位元組處。其實這是由於TCO/IP協議的傳輸次序造成的,由於TCP/IP首部中所有的二進位制整數都是以如下方式傳遞的:4個位元組32bit值的傳輸次序為首先是0~7bit,其次是8 ~ 15 bit,然後是16 ~ 23bit,最後是24 ~ 31bit,所以傳輸資料之前必須將首部轉化為網路位元組序。我們以一個int來舉例說明:

                       

      可以看到由於傳送順序的原因導致接收端先收到低位資料,從快取讀入之後位元組序變反了

 

二.IP選路

       IP層可以從UDP,TCP,ICMP和IGMP接收資料報並進行傳送,或者從一個網路介面接收資料報並進行轉發(如果開啟了路由功能)。IP層在記憶體中維護了一張路由表,每次都會根據路由表項進行轉發。

1.路由表項及匹配原則

      路由表中包含以下表項:

      1)目的IP地址。既可以是一個完整的主機地址,也可以是一個網路地址。

      2)下一跳路由器的IP地址,或是直接連線的網路地址。

      3)兩個標誌。一個用於表明目的地址(即第一條)是主機地址還是網路地址,另一個表明下一跳是一個路由器還是一個直接相連的介面。

      4)為資料報的傳輸指定一個網路介面。

     匹配原則如下:

      1)優先匹配能與目的IP地址完全匹配的表目

      2)次優先匹配與目的網路號相匹配的表目

      3)搜尋預設表目

      若以上均失敗,則回覆ICMP“主機不可達”或“網路不可達”報文

      可以使用命令netstat -rn來檢視主機路由表,-r選項列出路由表,-n選項以數字格式列印處IP地址,如圖所示:

                 

       所有資料報都會被閘道器轉發到10.1.16.254, Flags標誌的含義如下:U 該路由可以使用;G 該路由是到一個閘道器(路由器),若無該標誌則說明目的地是直接相連的;H 該路由是到一個主機,若無該標誌說明是到一個網路 ,即以一個網路號或是網路號與子網號的組合,D 該路由是由重定向報文建立的; M 該路由已被重定向報文修改。Genmask是目的地址的子網掩碼

      核心中有一個變數叫做ipforwarding,用以決定主機是否轉發IP資料報。

2.ICMP重定向差錯

       只有當主機可以選擇路由時才可能看到ICMP重定向報文,它的作用是高速傳送方,可以直接傳送到某個路由或直接鏈路上,而無需經過自己。舉個例子:

                                                              

 

1)假定主機發送一份IP資料報到R1。這時長髮生,因為R1是預設路由

2)R1收到後檢查路由表發現R2是下一跳,當它把資料報傳送給R2時,發現傳送介面與接收的介面是同一個(這意味著源端可以直接到達下一跳,而無需經過自己),此時該路由器傳送重定向報文給源端,告訴它以後將資料傳送給R2而不是R1

      其優點是將所有的智慧特性放到了路由端,所有主機在啟動時只需要一個預設路由,之後通過重定向報文進行逐步學習

3.路由發現協議

     一般,主機在引導後會以廣播或多播的形式傳送一份路由器請求報文(即ICMP路由器請求報文)。一臺或更多路由會回覆ICMP路由器通告報文。報文格式如下:

                              

                  

      生存時間指的是通告地址的有效時長(秒),預設為30分鐘,當路由器上的某一埠關閉時,路由器可以在該介面上傳送一生存時間為0的通告報文

     當路由器啟動時,它會隨機的在所有廣播或多播介面上傳送通告報文,一般兩次通告的時間間隔為450到600秒.

 

4.動態選路協議

     1)概述

              選路協議是相鄰路由器間進行通訊(並進行路由更新)時採用的協議,比如RIP,OSPF和BGP。路由器上有一個程序稱為路由守護程式,它執行選路協議,根據協議內容與相鄰路由器進行通訊和更新路由表。動態選路並不影響前面所說的IP層的選路機制,它是一種路由通訊與更新路由表的協議。

     2)IGP與EGP

              Internet是以一個個小的網路組成的(如一所高效或公司的網路),每個小的網路內可以使用獨立的選路協議,稱之為內部網路協議IGP或域內選路協議。而各個小的網路之間採用的選路協議稱之為外部閘道器協議EGP或分隔選路協議。IGP由OSPF和RIP協議,EGP有BGP協議

     3)RIP選路協議(版本1)

               RIP報文有著自己的協議格式,RIP報文是包含在UDP中進行傳送的,其協議格式如圖所示:                               

               協議運作過程如下:

  •         初始化:在啟動一個路由守護程式後,從該路由的所有開啟介面向外廣播請求報文,或指定到某些目的地址的路由,或要求所有路由資訊。
  •         接收到請求:傳送自己所有的路由資訊或是根據請求的目的IP進行回覆,對於後一種情況,若有到達指明地址的路由則度量為該路由到該地址的度量,否則為16,即不可達。
  •         接收到相應:更新路由表,會選擇度量小的表項。
  •         定期選路更新:每過30秒,部分或所有路由器會將完整的路由表傳送給相鄰路由器。
  •         觸發更新:當某一條路由的度量發生變化時,對該條路由進行更新。無需傳送完整路由表,而只需向相鄰路由傳送發生變化的表項
  •         超時刪除:每條路由都有一個與之相關的定時器,如果發現某一條路由在3分鐘內未更新,就將該路由的度量設為16(不可達),再過60秒則刪除。

     4)RIP選路協議(版本2)

               RIP版本2對RIP進行了一些擴充,這些擴充並不改變協議本身,只是用那些必須為0的欄位來儲存一些額外欄位。,RIP與RIP-2可有進行互操作。其協議欄位如下:                            

     5)OSPF選路協議

               OSPF協議採用的鏈路狀態協議不同於RIP的距離向量協議,當鏈路出現故障時OSPF可以更快的穩定下路,且OSPF協議直接使用IP。且OSPF協議可以根據服務型別的不同選擇出更好的路徑,因為OSPF可以對每個IP服務型別計算各自的路由集,即對於一個目的地可以有多個路由表項。OSPF協議會根據吞吐量,往返時間,可靠性或其它效能(每種IP服務型別)為每個介面指定一個費用。總的來說OSPF協議克服了RIP的所有限制,並增加了很多優點。

 

三.部分實現

1.概述

       在博文《TCP/IP實現 (三) 乙太網的資料收發》中已經介紹了網路介面如何將資料放至IP輸入佇列ipintrq中去,並如何產生一個軟體中斷。在軟體中斷處理期間,IP層呼叫ipintr函式不斷從ipintrq中取出並處理分組,直至佇列為空。若果分組已到達最終目的地則IP層對資料進行重組後總至適當的運輸層協議。若沒有到達最終目的地,且主機被配置為一個路由器,則IP把分組傳給ip_forward函式(處理IP首部(如TTL),處理ICMP重定向等),最後交由ip_output函式進行傳送,ip_output函式即從ip_forward接收轉發報文,也從運輸層接收上層資料包,該函式也會處理部分IP首部還進行IP分片。IP層處理如下圖所示:

         

 

2.IP輸入

         在介面層中將資料報放入IP輸入佇列併產生軟體中斷,之後再處理中斷時由核心呼叫ipintr函式迴圈從ipintrq佇列中取出分組,進行逐個處理,直至佇列為空,處理過程如下所述。

1)ipintr()函式

        ipintr函式處理處理IP選項,資料檢驗,地址匹配及交付上層協議或交由ip_forward進行轉發 。處理過程如下:首先判斷是否由介面被分配了IP地址(通過判斷IP地址連結串列是否為空),若未分配則丟棄所有分組。接著檢查IP首部長度,IP首部檢驗和以及IP資料長度,若任何一個出現了差錯都會增加相應錯誤計數。接著處理IP選項,關於IP選項在下一篇博文中介紹。之後遍歷ip地址連結串列,檢查是否有匹配的IP地址,匹配情況如下:1)與某介面地址完全匹配; 2)與接收介面相關的廣播地址匹配(在下面說明);3)與某個介面相關的多播組之一(在IP多播中介紹);4)與受限廣播地址之一匹配(全1和全0)。若有一個匹配的則進行重組並交由運輸層協議。上述第二條中指的是與接收介面以下的地址相匹配的:

                                       

  虛擬碼如下:

void ipintr()
{
    while(ipintrq不為空){
        if(沒有介面分配了IP地址) {
            丟棄資料包;
            return;
        }
        // 第一步:資料包檢驗
        增加資料包計數;
        檢查首部長度,檢驗和及資料長度,若出現錯誤則丟棄並增加錯誤計數
        
        // 第二步:處理IP選項
        在後一片博文中介紹        

        // 第三部:匹配IP地址,交付或嘗試轉發
        for(in_ifaddr* ia = in_ifaddr; ia; ia = ia->next) {// 遍歷地址連結串列
            // 完全匹配
            if(ia->sin_addr.s_addr == ip->id_dst.s_addr) {// 如果某個介面的IP與目的IP相同
                ip->ips_delivered++; // 增加交付計數,通過netstat -s可以檢視:xxx incoming packets delivered
                重組並交由上次協議
                continue;
            }

            // 匹配廣播地址
            if(ia->ia_ifp == m->m_pkthdr.rcvif && ia->ia_ifp->if_flags & IFF_BOARDCAST) { 
            // 如果該IP的相關介面是接收介面,且該介面可用於接收廣播資料
                // 匹配上述的四種地址
                if(ia->ia_broadaddr.sin_addr.s_addr == ip->ip_dst.s_addr) { // 與接收介面的定向網路地址相匹配
                    ip->ips_delivered++; 
                    重組
                    查詢協議交換表,交由上次協議(通過pr_input指標指向相應協議的接收函式,如:udp_input)
                    continue;
                }
                if(ia->ia_netbroadaddr.sin_addr.s_addr == ip->ip_dst.s_addr) { // 與接收介面的網路廣播地址相匹配
                    // 同上
                }
                // 其它兩個略寫
            }
            匹配受限廣播地址全1和全0,若匹配成功則重組交付上層協議
        } // for
        ip_forward(m);//沒有匹配的IP,呼叫ip_forward嘗試進行分組轉發
    }// while
}

      此外ipintr還進行IP選項處理,在其它章節說明。

2)ip_forward()資料包轉發函式

      該函式負責IP資料報的轉發處理,真正的轉發操作由ip_optput負責。ip_forward接收兩個引數,一個是存有IP資料報的mbuf(一種快取結構),另一個是int srcrt,若非0則說明該分組設定了源路由選項,即設定了傳送路徑。

      ip_forward函式對以下幾種型別的資料報不進行轉發:1.m_buf::flags設定了M_BCAST的分組,任何支援廣播的網路介面必須為收到的廣播分組設定M_BCAST標誌。介面不轉發鏈路層廣播分組(注意:鏈路層廣播分組與IP層廣播是不同的概念)。2.對於目的地址是環回網路,網路0和E類地址,D類地址的不進行轉發。D類多播地址由其它函式負責。理解了下面這兩句話也就明白了鏈路層廣播與IP廣播的本質區別:鏈路層地址是用於傳送至下一跳的,IP地址是為了尋找網路上的一點的。RFC 1122不允許以鏈路層廣播的方式傳送一個定址到單播IP地址的分組(即以廣播的方式搜尋下一跳)

      值得注意的是當呼叫ip_output後,ip_forward會根據返回的值,來判斷是否出錯,若出錯則回覆相應的ICMP報文

【注】:只有報文需要轉發時才會進入ip_forward,從而檢查TTL,這樣就解釋了為什麼是使用traceroute程式時主機不會恢復ICMP。

      虛擬碼如下:

void ip_forward(mbuf *m, int srcrt) 
{
    struct ip *ip = mtod(m, struct ip*);
    if(m->m_flags & M_BCAST || ip_canforward(ip->ip_dst)) { // ip_canforward用於檢測前面說的幾種不可轉發的地址
        // 丟棄報文
        mfree(m);
        return;
    }
    // 檢查TTL
    若ip->ip_ttl過小則回覆ICMP超時報文。 // 只有報文需要轉發時才會進入ip_forward,從而檢查TTL
    更新TTL
    查詢路由表進行轉發
    若無路由資訊則回覆ICMP不可達報文
    
    // 傳送
    error =  ip_output(m, xx,xx, IP_FORWARDEING | IP_ALLOWBROADCAST,0);

    if(error == 0)
        return;
    // 根據錯誤資訊回覆ICMP報文
    switch(error) {
        case ENETUNREACH:  // 直接相連的網路
        case EHOSTUNREACH: // 未找到路由資訊
        case ENETDOWN:     // 介面關閉
            回覆主機不可達報文;
            break;
        case EMSGSIZE:    // 由於廣播報文不支援分片,因此會出現廣播報文大小大於MTU此時返回EMSGSIZE
            回覆ICMP不可達報文ICMP_UNREACH,不可達型別為:需要進行分片但設定了不分片位元(ICMP程式碼為4,ICMP_UNREACH_NEEDFRAG)
            break;
        case ENOBUFS: // 當介面層傳送佇列滿時或申請mbuf失敗
            回覆源端被關閉報文 ICMP_SOURCEQUENCH;
            break;
    }
}

3)ip_output()資料報輸出函式

       ip_output接收兩處的分組:ip_forward和運輸層協議資料,該函式的處理只要分為3步:step1.IP首部的初始化;step2.路由選擇;step3 源地址選擇與分片。在進行IP首部初始化並根據路由找到外出介面後,根據介面的MTU對分組進行分片,至於如何分片在其它博文中做說明。但注意廣播報文不可分片,若報文大小超過外出介面的MTU會將SMSGSIZE返回至呼叫函式,從而做出一些處理,如回覆ICMP報文(若是轉發),否則返回給應用,設定errno。若未指定源地址則ip_output函式會選擇輸出介面的IP地址作為源地址。(注:是不是可以自己偽造IP源地址)。最後ip_output函式將資料報教至介面層,通過函式指標if_output來呼叫不同型別介面的專用函式,如乙太網是ether_output,呼叫如下:error = (*ifp->if_output)(...)。若出錯則會返回至上層。

 

四 TCP/IP錯誤處理流程

           

        上層協議會呼叫下層協議,呼叫下層協議時會出現一些錯誤,比如IP層呼叫介面層時可能出現介面層輸出佇列滿,此時會向IP層返回ENOBUFS,若是傳送端則直接將該錯誤返回給應用程序,且設定errno的值為errno,若是接收端或是中間節點,則會向源端ICMP報文ICMP_SOURCEQUENCH,當源端收到ICMP報文後會將其轉化為相應的錯誤碼,並交給相應程序。