天天看點

TCP/IP協定棧之LwIP(三)---網際尋址與路由(IPv4 + ARP + IPv6)一、網際尋址原理二、ARP協定三、IP協定更多文章:

文章目錄

  • 一、網際尋址原理
    • 1.1 網絡位址IPv4
    • 1.2 網際尋址與路由協定
    • 1.3 網絡位址IPv6
    • 1.4 網絡位址轉換與IP隧道
  • 二、ARP協定
    • 2.1 ARP協定簡介
    • 2.2 ARP快取記錄描述
    • 2.3 ARP資料包描述
    • 2.4 ARP層資料處理函數
    • 2.5 ARP攻擊
  • 三、IP協定
    • 3.1 IP分片與路徑MTU發現
    • 3.2 IPv4資料報描述
    • 3.3 IPv4資料報操作函數
    • 3.4 IPv6資料報描述
    • 3.5 IPv6資料報操作函數
  • 更多文章:

一、網際尋址原理

前一篇網絡接口層管理介紹了資料鍊路層可以靠MAC位址在同一個資料鍊路區域網路内尋址。拿以太網為例,在一個資料鍊路中主機常與以太網交換機(可以看作是有多個端口的網橋)相連,它們根據資料鍊路層中每個幀的目标MAC位址,決定從哪個網橋接口發送資料,選擇發送接口的依據就是交換機的位址轉發表(Forwarding Table,可根據交換機自學算法自動生成)。

當網絡裝置位址總數并不是很多的情況下,有了唯一位址(比如MAC位址)就可以定位互相通信的主體。然而,當位址總數越來越多時,如何高效地從中找出通信的目标位址将成為一個重要的問題。為此人們發現位址除了具有唯一性還需要具有層次性。MAC位址雖然具有唯一性,但沒有層次性,也即當網絡裝置增加到一定程度,需要将網絡分成多個資料鍊路時,MAC位址無法跨鍊路尋址,是真正負責最終通信的位址。要實作跨區域網路的網際尋址,就要靠具有層次性的網絡IP位址實作了。

1.1 網絡位址IPv4

網絡IP位址是怎樣實作分層的呢?IP位址由網絡号和主機号兩部分組成,即使通信主體的IP位址不同,若主機号不同,網絡号相同,說明它們處于同一個網段。網絡号相同的主機在組織結構、提供商類型和地域分布上都比較集中,為IP尋址帶來了極大的友善。下面先看下IP位址的分類:

TCP/IP協定棧之LwIP(三)---網際尋址與路由(IPv4 + ARP + IPv6)一、網際尋址原理二、ARP協定三、IP協定更多文章:

網絡IP位址是一個32bit整數位址,隻有具有有效IP位址的主機才能跨越資料鍊路實作網際尋址。目前的IP位址多采用分類編址,把所有IP位址劃分為A/B/C/D/E五類,前三類由網絡号和主機号組成,D類都是多點傳播位址,E類保留未用,各類IP位址特點如下表示:

TCP/IP協定棧之LwIP(三)---網際尋址與路由(IPv4 + ARP + IPv6)一、網際尋址原理二、ARP協定三、IP協定更多文章:

MAC位址中除了單點傳播位址還有多點傳播和廣播位址,在IP位址中也有一些特殊的網絡位址。列舉幾個如下:

  • 網絡位址:主機号全部取0;
  • 直接廣播位址:主機号全部取1;
  • 受限廣播位址:IP位址32位全部為1,将廣播限制在最小範圍内,若采用标準IP編址則跟直接廣播位址一緻,若采用子網編址則廣播被限制在本子網内,屬于E類位址;
  • 多點傳播位址:即上面介紹的D類位址;
  • 本網絡上的特定主機:網絡号全部取0;
  • 本網絡本主機:IP位址32位全部為0;
  • 環回位址:127.x.x.x,通常使用127.0.0.1;

前面介紹的特殊位址不能被配置設定給任何主機使用,下面還有一些專用位址(也叫私有位址)可以被配置設定給多個主機使用,當然這些主機應該處于互相獨立的專用網内,例如以太網區域網路:

TCP/IP協定棧之LwIP(三)---網際尋址與路由(IPv4 + ARP + IPv6)一、網際尋址原理二、ARP協定三、IP協定更多文章:

随着網際網路的覆寫範圍逐漸增大,網絡位址會越來越不足以應對需求,直接使用A/B/C類位址顯得浪費資源,為了根據需求更精細的使用IP位址,又增加了“子網路遮罩”的識别碼将A/B/C類網絡位址細分為更多的子網。子網路遮罩也是一個32位的整數位址,它對應IP位址網絡辨別部分的位全部為“1”,對于IP位址主機辨別的部分全部為“0”,由此一個IP位址可以不再受限于自己的類别,而是可以用這樣的子網路遮罩自由地定位自己的網絡辨別長度。舉例圖示如下:

TCP/IP協定棧之LwIP(三)---網際尋址與路由(IPv4 + ARP + IPv6)一、網際尋址原理二、ARP協定三、IP協定更多文章:

1.2 網際尋址與路由協定

前面談到MAC位址隻能在本資料鍊路内尋址,不能跨區域網路尋址,這裡介紹的網絡IP位址可以實作跨區域網路實作網際尋址。MAC尋址靠交換機的位址轉發表(記錄MAC位址與發送接口的表),IP尋址則靠路由控制表(Routing Table)。路由控制表中記錄着網絡位址與下一步應該發送至路由器的位址,在發送IP資料包時,根據IP包首部中的目标IP位址從路由控制表中找到與該位址具有相同網絡位址的記錄,根據該記錄将IP資料包轉發給相應的下一個路由器,如果路由控制表中存在多條相同的網絡位址記錄,則根據最長比對原則選擇相同位數最多網絡位址的下一個路由器作為轉發目标。

路由控制表的生成有靜态和動态兩種方式,靜态路由控制表需要手工配置且不自動重新整理,動态路由控制表可以根據路由協定自動生成并自動重新整理,是以一般使用動态路由表。由于網絡位址的分層特性,可以将内部的多個子網路遮罩合并,對外呈現出同一個網絡位址,通過這種路由資訊的聚合有效減少了路由表的項數。但路由控制表也很難包含所有的網絡及子網資訊,一般都會配置一個預設路由(預設網關),當在路由控制表中查不到比對項時,就把預設路由作為該IP資料包的轉發目标。

動态路由控制表是靠路由協定交換路由資訊生成的,那麼路由協定是如何生成路由控制表的呢?前面介紹網絡号相同的主機在組織結構、提供商類型和地域分布上都比較集中,實際上制定路由政策時對一個企業或組織内部的網絡授權給該企業或組織内部自行決定,這樣可以根據自己的需求建構合适的網絡也能對外屏蔽企業内部的網絡細節,以此為準在一個或多個網絡群體中采用的小型網絡機關叫做自治系統(AS: Autonomous System),自治系統内部動态路由采用的是域内路由協定(IGP: Interior Gateway Protocol),自治系統之間的路由控制則采用域間路由協定(EGP: Exterior Gateway Protocol)。下面給出一個關系圖示:

TCP/IP協定棧之LwIP(三)---網際尋址與路由(IPv4 + ARP + IPv6)一、網際尋址原理二、ARP協定三、IP協定更多文章:

路由協定被分為EGP和IGP兩個層次:EGP主要在區域網絡之間進行路由選擇,常用的路由協定是BGP(Border Gateway Protocol);IGP主要在區域網絡内部進行路由選擇,常用的路由協定有RIP(Routing Information Protocol)、RIP2、OSPF(Open Shortest Path First)等,主要路由協定的特點如下表示:

TCP/IP協定棧之LwIP(三)---網際尋址與路由(IPv4 + ARP + IPv6)一、網際尋址原理二、ARP協定三、IP協定更多文章:

距離向量算法是根據距離和方向決定目标網絡或目标主機位置的,路由器之間可以互換目标網絡的方向及其距離資訊,并以此為基礎制作路由控制表。其中RIP/RIP2就是使用距離向量算法實作的協定,這裡的距離機關是“跳數“(所經過的路由器個數),通過定期全網廣播,每經過一個路由器距離增加1,根據距離向量生成距離向量表,再抽出較小距離的路由生成路由控制表,大概圖示如下:

TCP/IP協定棧之LwIP(三)---網際尋址與路由(IPv4 + ARP + IPv6)一、網際尋址原理二、ARP協定三、IP協定更多文章:

鍊路狀态算法是路由器在了解網絡整體連接配接狀态的基礎上生成路由控制表的一種方法,在該方法中每個路由器必須保持同樣的資訊才能進行正确的路由選擇。OSPF就是一種鍊路狀态型路由協定,路由器之間交換鍊路狀态生成網絡拓撲資訊,然後根據這個拓撲資訊生成路由控制表。RIP/RIP2路由協定中要求途中所經過的路由器個數越少越好,OSPF路由協定則可以給每條鍊路賦予一個權重(每種鍊路的網絡帶寬不同),并始終選擇一個總權重最小的路徑作為最終路由。這裡使用了圖論中的最短路徑算法,RIP/RIP2使用了無權最短路徑算法,OSPF使用了有權最短路徑算法(參考Dijkstra算法),算法原理這裡就不展開介紹了。給出OSPF路由協定的工作原理圖示如下:

TCP/IP協定棧之LwIP(三)---網際尋址與路由(IPv4 + ARP + IPv6)一、網際尋址原理二、ARP協定三、IP協定更多文章:

BGP邊界網關協定所使用的路徑向量算法跟距離向量算法類似,不過RIP/RIP2是以所經過的路由器個數作為距離代價,BGP則把所要經過的AS自治系統的個數作為距離代價。BGP路由協定一般選擇所經過AS數最少的路徑,但需要遵循各個AS之間簽約的細節進行更細顆粒度的路由選擇。

1.3 網絡位址IPv6

IPv6是為了從根本上解決IPv4位址耗盡的問題而被标準化的網際協定,IPv4的位址長度為4個8位位元組即32比特,而IPv6的位址長度為8個16位位元組即128比特,位址空間是IPv4的2^96倍,足以為人們所能想象到的所有主機和路由器配置設定位址了。IPv6位數是IPv4的四倍,如果還用IPv4的十進制表示方法顯得太長,是以IPv6采用16位位元組為一組,且中間出現連續的0時可以将這些0省略一次,IPv6的表示方法如下:

TCP/IP協定棧之LwIP(三)---網際尋址與路由(IPv4 + ARP + IPv6)一、網際尋址原理二、ARP協定三、IP協定更多文章:

IPv6位址也分單點傳播位址與多點傳播位址,但沒有廣播位址,主要是考慮到廣播降低了網絡性能,IPv6使用多點傳播位址來替代IPv4中必須使用廣播的情況。IPv6的全局單點傳播位址格式如下:

TCP/IP協定棧之LwIP(三)---網際尋址與路由(IPv4 + ARP + IPv6)一、網際尋址原理二、ARP協定三、IP協定更多文章:

現在IPv6的網絡中常使用的格式為:廣域網絡ID n = 48,子網ID m = 16,主機接口ID 128-n-m = 64。多點傳播位址第一位元組全1,可簡略表示為FF00::/8。除了單點傳播與多點傳播位址外,跟IPv4類似,也有一些私有位址,在不與網際網路直接接入的區域網路内部,可以使用唯一本地位址,在不跨路由器的同一鍊路上可以使用鍊路本地單點傳播位址。這些位址結構如下表示:

TCP/IP協定棧之LwIP(三)---網際尋址與路由(IPv4 + ARP + IPv6)一、網際尋址原理二、ARP協定三、IP協定更多文章:

在IPv6的環境下,可以同時将這些IP位址(上表列舉的多種類型IP位址)同時配置在同一個NIC上,按需靈活使用。

1.4 網絡位址轉換與IP隧道

前面介紹的IP位址除了全局位址還有私有位址,私有位址隻能在區域網路内使用,不能直接接入網際網路,這主要是為了緩解IPv4位址短缺問題出現的區域網路私有位址複用方案。那麼,區域網路内被配置設定私有IP位址的主機如何通路網際網路呢?這就需要路由器通過NAT(Network Address Translation)功能來實作了,一個支援NAT功能的路由器至少要有一個内部位址和一個外部位址,内部位址也即私有位址是用于與區域網路内的使用者通信的,外部位址也即全局位址是用于與外部網際網路通信的。當區域網路内主機通路外部網際網路時,NAT将使用者的内部IP位址轉換為一個外部公共IP位址;反之,資料從外部傳回時,NAT将目的位址替換為使用者的内部IP位址,實作多個私有位址共有一個全局位址通路網際網路的功能。NAT工作過程如下圖示:

TCP/IP協定棧之LwIP(三)---網際尋址與路由(IPv4 + ARP + IPv6)一、網際尋址原理二、ARP協定三、IP協定更多文章:

在NAT路由器内部會自動生成一張位址轉換表,用于記錄私有位址與全局位址的映射關系。當區域網路内的多台主機同時都要與外部進行通信時,僅僅轉換IP位址已經不夠用了,需要将傳輸層TCP/UDP的端口号一起轉換,這種轉換方式稱為NAPT(Network Address Port Translation),轉換過程如下圖示:

TCP/IP協定棧之LwIP(三)---網際尋址與路由(IPv4 + ARP + IPv6)一、網際尋址原理二、ARP協定三、IP協定更多文章:

現在IPv6使用率還比較低,IPv4到IPv6的切換需要一個循序漸進的過程,是以很長一段時間内免不了IPv4與IPv6共存的情況,IPv4與IPv6混合組網也需要能互相通信。借鑒前面介紹的全局位址與私有位址的轉換方案,IPv4與IPv6能否實作互相轉換呢?為了解決這個問題,出現了NAT-PT(Network Address Translator - Protocol Translator)技術,能實作IPv6與IPv4網絡位址的互相轉換,轉換過程如下圖示:

TCP/IP協定棧之LwIP(三)---網際尋址與路由(IPv4 + ARP + IPv6)一、網際尋址原理二、ARP協定三、IP協定更多文章:

NAT網絡位址轉換都依賴于自己生成的轉換表,存在一些限制或開銷較大等缺點,比如兩個互相通信的主機都處于IPv6網絡,這兩個IPv6網絡無法直接進行通信,需要通過中間的IPv4網絡互相通信,如果在兩個IPv6間建立一個IP隧道,顯然比兩端都進行NAT位址轉換更加簡單高效。IP隧道怎麼建立呢?通常情況下一個資料包隻有一個IP首部,要進入中間網絡時為了符合待穿越網絡的IP位址要求,需要在原IP首部前再追加一個IP首部,待離開該網絡時再删除臨時追加的IP首部,進而完成資料包穿越中間網絡的目的,這個利用臨時追加、過後删除IP首部的技術稱為IP隧道,穿越過程如下圖示:

TCP/IP協定棧之LwIP(三)---網際尋址與路由(IPv4 + ARP + IPv6)一、網際尋址原理二、ARP協定三、IP協定更多文章:

二、ARP協定

2.1 ARP協定簡介

前面分别介紹了IP位址進行網際尋址,MAC位址進行鍊路内尋址,需要兩者配合一起完成主機尋址的任務。資料包從一個主機發送到另一個主機時,目标IP位址一直不變,作為查詢路由控制表進行路由轉發的依據,但實際轉發還是需要在鍊路層根據MAC位址選擇相應的轉發接口,是以目标MAC位址一直指向下一個網絡接口,整個尋址過程如下:

TCP/IP協定棧之LwIP(三)---網際尋址與路由(IPv4 + ARP + IPv6)一、網際尋址原理二、ARP協定三、IP協定更多文章:

前面介紹路由控制表的下一個路由位址是一個IP位址,但實際是通過把資料幀的目标MAC位址設定為下一個路由接口的MAC位址實作轉發的,這就要知道每個IP位址對應的MAC位址。

ARP(Address Resolution Protocol)就是一種解決IP位址與MAC位址映射問題的協定,以目标IP位址為線索,用來定位下一個應該接收資料包的網絡裝置對應的MAC位址。為了實作IP位址與MAC位址的轉換,ARP協定引入了ARP快取記錄的概念,ARP快取記錄中記錄了一條條<IP位址,MAC位址>對,如果目标主機在同一個鍊路上,通過查詢ARP快取記錄可以直接将比對的MAC位址裝入以太網幀首部發送到目标主機,如果目标主機不在同一鍊路上,通過查詢ARP快取記錄将比對的下一跳路由器的MAC位址裝入以太網幀首部轉發給下一跳路由器繼續尋址。ARP快取記錄結構如下圖示:

TCP/IP協定棧之LwIP(三)---網際尋址與路由(IPv4 + ARP + IPv6)一、網際尋址原理二、ARP協定三、IP協定更多文章:

從上圖看ARP快取記錄也分靜态與動态,靜态主要是多點傳播與廣播位址,實際尋址時常靠ARP協定自動生成并動态重新整理維護。ARP快取記錄的建立跟ARP資料包密切相關,在以太網中,ARP資料包與IP資料包是兩個互相獨立的部分,它們都封裝在以太網幀中發送。ARP資料包有兩種:一是ARP請求包,它是通過以太網廣播的方式發送的,用于向具有某個IP位址的主機發送請求,希望該主機傳回其MAC位址;二是ARP應答包,收到ARP請求的主機會比對該資料包中的IP位址與自己的IP位址是否相符,若是,則該主機向源主機傳回一個ARP應答包,向源主機報告自己的MAC位址,源主機通過提取ARP應答包中的相關字段來更新ARP快取記錄。

RARP(Reverse Address Resolution Protocol)是将ARP反過來,從MAC位址定位IP位址的一種協定。平時我們使用電腦設定IP位址常通過DHCP(Dynamic Host Configuration Protocol)自動配置設定擷取IP位址,但對于受限的嵌入式裝置,在無法通過DHCP動态擷取到IP位址的情況下,可以假設一台RARP伺服器,并在這個伺服器上注冊裝置的MAC位址及其IP位址。當裝置接入網絡插電啟動時,通過向該伺服器發送RARP請求包向RARP伺服器請求IP位址,該裝置通過來自RARP伺服器的應答包設定自己的IP位址。

2.2 ARP快取記錄描述

ARP協定的核心在于ARP快取記錄的建立、更新與查詢,ARP快取記錄由緩存表項(entry)組成,每個表項記錄一組IP位址與MAC位址的綁定資訊,為了便于管理,還包含了與資料包發送控制、緩存表項管理相關的狀态、控制資訊等。LwIP中描述緩存表項的資料結構與圖示如下:

// rt-thread\components\net\lwip-1.4.1\src\netif\etharp.c

struct etharp_entry {
#if ARP_QUEUEING
  /** Pointer to queue of pending outgoing packets on this ARP entry. */
  struct etharp_q_entry *q;
#else /* ARP_QUEUEING */
  /** Pointer to a single pending outgoing packet on this ARP entry. */
  struct pbuf *q;
#endif /* ARP_QUEUEING */
  ip_addr_t ipaddr;
  struct netif *netif;
  struct eth_addr ethaddr;
  u8_t state;
  u8_t ctime;
};

struct etharp_q_entry {
  struct etharp_q_entry *next;
  struct pbuf *p;
};

enum etharp_state {
  ETHARP_STATE_EMPTY = 0,
  ETHARP_STATE_PENDING,
  ETHARP_STATE_STABLE,
  ETHARP_STATE_STABLE_REREQUESTING
#if ETHARP_SUPPORT_STATIC_ENTRIES
  ,ETHARP_STATE_STATIC
#endif /* ETHARP_SUPPORT_STATIC_ENTRIES */
};

static struct etharp_entry arp_table[ARP_TABLE_SIZE];
           
TCP/IP協定棧之LwIP(三)---網際尋址與路由(IPv4 + ARP + IPv6)一、網際尋址原理二、ARP協定三、IP協定更多文章:

ARP快取記錄是以數組方式定義的,每個緩存表項etharp_entry除了儲存IP位址與MAC位址外,還儲存因暫時查不到表項而不知道目的MAC位址的資料包,在接收到目标主機的ARP應答前,可能有不止一個資料包待發送,這裡可以使用資料包緩沖隊列etharp_q_entry(實際上是一個單向連結清單)來描述這些資料包。

緩存表項還包含了描述該entry狀态的成員state,使用枚舉類型etharp_state進行管理,初始化時ARP快取記錄項的狀态都為ETHARP_STATE_EMPTY;若該表項隻記錄了IP位址還未記錄對于的MAC位址則其狀态為ETHARP_STATE_PENDING;若該表項記錄了一對完整的IP位址與MAC位址則其狀态為ETHARP_STATE_STABLE;由于ARP快取記錄項需要定期更新,原本處于stable狀态的表項處于更新過程中,還未接收到ARP應答包完成更新時的狀态為ETHARP_STATE_STABLE_REREQUESTING。

為了保持緩存表中各個表項的有效性,ARP子產品必須設定一定的逾時檢查機制,每個處于非ETHARP_STATE_EMPTY狀态的表現都有生存時間,用ctime成員描述該緩存表項entry定時器的逾時時間,若該表項的生存時間逾時系統将會删除該表項。最後一個字段是前面網絡接口層介紹過的netif結構體指針,在發送資料包時起着至關重要的作用。

2.3 ARP資料包描述

前面介紹ARP快取記錄的生成是靠ARP請求與應答資料包實作的,下面介紹下ARP資料包的組織結構,由于ARP資料包是被封裝在以太網幀中發送的,是以下圖也列出了以太網幀首部(前篇網絡接口層介紹過):

TCP/IP協定棧之LwIP(三)---網際尋址與路由(IPv4 + ARP + IPv6)一、網際尋址原理二、ARP協定三、IP協定更多文章:

以太網首部這裡就不再介紹了,ARP資料包中硬體類型字段表示發送方想要知道的硬體接口類型,對于以太網MAC位址該類型值為1;協定類型字段表示要映射的協定位址類型,若要映射為IP位址該類型值為0x0800(與以太網幀首部的幀類型字段使用相同的一組值)。硬體位址長度字段對于以太網MAC位址該長度值為6,協定位址字段對于ARP協定表示IP位址的長度,由于ARP協定隻适用于IPv4,不能用于IPv6(IPv6使用ICMPv6中的鄰居探索消息替代ARP協定的功能),是以該協定長度值為4。

操作字段OP指出ARP資料包的類型,它們可以是ARP請求(值為1)、ARP應答(值為2)、RARP請求(值為3)、RARP應答(值為4)。剩餘的四個字段比較好了解就不解釋了,ARP資料包的描述如下:

// rt-thread\components\net\lwip-1.4.1\src\include\netif\etharp.h

#ifndef ETHARP_HWADDR_LEN
#define ETHARP_HWADDR_LEN     6
#endif

PACK_STRUCT_BEGIN
struct eth_addr {
  PACK_STRUCT_FIELD(u8_t addr[ETHARP_HWADDR_LEN]);
} PACK_STRUCT_STRUCT;
PACK_STRUCT_END

PACK_STRUCT_BEGIN
/** Ethernet header */
struct eth_hdr {
  PACK_STRUCT_FIELD(struct eth_addr dest);
  PACK_STRUCT_FIELD(struct eth_addr src);
  PACK_STRUCT_FIELD(u16_t type);
} PACK_STRUCT_STRUCT;
PACK_STRUCT_END

PACK_STRUCT_BEGIN
/** the ARP message, see RFC 826 ("Packet format") */
struct etharp_hdr {
  PACK_STRUCT_FIELD(u16_t hwtype);
  PACK_STRUCT_FIELD(u16_t proto);
  PACK_STRUCT_FIELD(u8_t  hwlen);
  PACK_STRUCT_FIELD(u8_t  protolen);
  PACK_STRUCT_FIELD(u16_t opcode);
  PACK_STRUCT_FIELD(struct eth_addr shwaddr);
  PACK_STRUCT_FIELD(struct ip_addr2 sipaddr);
  PACK_STRUCT_FIELD(struct eth_addr dhwaddr);
  PACK_STRUCT_FIELD(struct ip_addr2 dipaddr);
} PACK_STRUCT_STRUCT;
PACK_STRUCT_END

#define SIZEOF_ETH_HDR    14
#define SIZEOF_ETHARP_HDR 28
#define SIZEOF_ETHARP_PACKET (SIZEOF_ETH_HDR + SIZEOF_ETHARP_HDR)

/** 5 seconds period */
#define ARP_TMR_INTERVAL 5000

#define ETHTYPE_ARP       0x0806U
#define ETHTYPE_IP        0x0800U

/** ARP message types (opcodes) */
#define ARP_REQUEST 1
#define ARP_REPLY   2
           

上面定義的資料結構都使用了宏PACK_STRUCT_FIELD來禁止編譯器的位元組自動對齊,因為我們需要使用結構體中的字段去直接操作資料包中的資料,而資料包中的資料都是以位元組方式對齊的,若編譯器改變了對齊方式會造成嚴重問題,是以網絡資料包中常使用該宏來禁止編譯器位元組字對齊。

2.4 ARP層資料處理函數

ARP快取記錄的操作主要是緩存表的建立、查詢與更新,緩存表項的建立或更新需要ARP請求/應答資料包的發送與接收,下面先給出ARP層資料處理的總流程圖示:

TCP/IP協定棧之LwIP(三)---網際尋址與路由(IPv4 + ARP + IPv6)一、網際尋址原理二、ARP協定三、IP協定更多文章:

由上面的流程圖可以看出,ARP表項查詢通過函數etharp_query完成,如果查詢不到某表項則通過etharp_request發送ARP請求包,待通過etharp_arp_intput接收到ARP應答包後,将獲得的緩存表項資訊通過etharp_update_arp_entry更新到緩存表中。相關的操作函數與功能描述如下:

操作函數 功能描述

err_t etharp_output(struct netif *netif,

struct pbuf *q, ip_addr_t *ipaddr)

發送一個IP資料包到目的IP位址處,根據IP

位址類型調用相應的處理函數遞交資料包;

err_t etharp_query(struct netif *netif,

ip_addr_t *ipaddr, struct pbuf *q)

根據單點傳播IP位址查詢ARP快取記錄獲得對應的

MAC位址,如果查得MAC位址則将該資料包

發送出去,否則發送一個ARP請求包;

err_t etharp_request(struct netif *netif,

ip_addr_t *ipaddr)

建構一個ARP請求包并以MAC廣播發送出去;

static s8_t etharp_find_entry(ip_addr_t *ipaddr,

u8_t flags)

根據IP位址查詢比對的ARP表項或建立一個

新的表項;

static err_t etharp_send_ip(struct netif *netif,

struct pbuf *p, struct eth_addr *src,

struct eth_addr *dst)

填寫以太網幀首部,調用網卡注冊的資料包

發送函數;

err_t ethernet_input(struct pbuf *p,

struct netif *netif)

校驗接收到的資料包,并根據幀類型調用相應

的處理函數遞交資料包;

static void etharp_arp_input(struct netif *netif,

struct eth_addr *ethaddr, struct pbuf *p)

處理ARP資料包,更新ARP快取記錄,對ARP

請求包進行應答;

static void etharp_ip_input(struct netif *netif,

struct pbuf *p)

使用接收到的源IP與源MAC更新ARP快取記錄項;

static err_t etharp_update_arp_entry(

struct netif *netif, ip_addr_t *ipaddr,

struct eth_addr *ethaddr, u8_t flags)

更新ARP快取記錄項,如果一個表項從pending

狀态變為stable狀态,将其後的緩沖資料包發

送出去;

static void etharp_free_entry(int i) 釋放指定的ARP快取記錄項;
void etharp_tmr(void) 定期删除生存時間逾時的緩存表項;

err_t etharp_add_static_entry(ip_addr_t *ipaddr,

struct eth_addr *ethaddr)

添加一個新的靜态ARP快取記錄項;
err_t etharp_remove_static_entry(ip_addr_t *ipaddr) 移除一個靜态ARP快取記錄項;
void etharp_cleanup_netif(struct netif *netif) 清除指定網卡接口上的所有ARP快取記錄項;

2.5 ARP攻擊

這裡大家可能會發現ARP協定有一個很大的漏洞,如果網絡中的所有使用者都規規矩矩,按照上述流程使用ARP協定就不會存在任何問題。但當某些主機收到一個ARP請求後(ARP請求是以廣播方式發送的,區域網路内所有使用者都能收到),它不管請求包中的IP位址與本地位址是否相符,都會産生一個ARP應答包,告訴請求的使用者本主機的MAC位址就是你請求的目的IP比對的MAC位址,由于發送ARP請求的源主機不具備任何容錯、認證功能,它會直接把ARP應答資料加入到自己的ARP快取記錄項中。源主機在以後都會将具有該目的IP位址的資料包發送到這個僞裝主機上,這些僞裝主機能輕松實作資料的竊取,這就是ARP攻擊的基本原理。

ARP攻擊通過僞裝IP位址和MAC位址實作ARP欺騙,能夠在網絡中産生大量的ARP通信量使網絡阻塞,攻擊者隻要持續不斷地發出僞造的ARP響應包,就能更改目标主機ARP快取記錄項。ARP木馬病毒隻需感染一台主機,就可能導緻整個區域網路都無法上網,嚴重的甚至可能帶來整個網絡的癱瘓。

怎麼防禦ARP攻擊呢?最有效也是最笨的方法就是采用靜态ARP快取記錄,即在程式初始化時将可信任的IP和MAC位址對固定寫入到ARP快取記錄中,但這種方法違背了ARP動态位址解析的原則,使用上受到很大的限制。

IPv6不再使用ARP協定,轉而使用ICMPv6中的鄰居探索消息替代ARP協定的功能,但在應對IP與MAC僞裝攻擊方面并沒有明顯改進。在IPv4中是ARP欺騙,到了IPv6中換為了NA(Neighbor Advertisement)欺騙,攻擊原理類似,使用時仍需要注意防禦可能的欺騙攻擊。

三、IP協定

IP協定在發包之前并不需要建立與對端目标位址之間的連接配接,上層如果遇到需要發送給IP層的資料,該資料會立即被壓縮成IP包發送出去,是以IP層是面向無連接配接的,隻提供盡力服務(Best Effort,為了把資料包發送到目标位址,盡最大努力),自身并不做最終收到與否的驗證。IP資料包在途中可能會發生丢包、錯位以及資料量翻倍等問題,為了提供通信的可靠性,上層傳輸層中的TCP協定提供了面向連接配接的可靠通信服務,IP層隻需要專注完成自身的尋址送達任務即可。

3.1 IP分片與路徑MTU發現

前面介紹了資料包的尋址轉發原理,但資料包在從一個主機送達另一個主機的過程中,需要經過很多資料鍊路,不同資料鍊路的最大傳輸單元MTU(Maximum Transmission Unit)不盡相同,如果要想在資料鍊路上正确傳輸,就不能超過其MTU的限制。鑒于IP層屬于資料鍊路上一層,它必須不受限于不同資料鍊路的MTU大小,是以IP層提供了針對不同資料鍊路對IP封包進行分割與重組的能力,以便能将資料報分割為不超過相應MTU的大小進行有效傳輸。下面列出各種資料鍊路及其MTU大小供參考:

TCP/IP協定棧之LwIP(三)---網際尋址與路由(IPv4 + ARP + IPv6)一、網際尋址原理二、ARP協定三、IP協定更多文章:

如果IP資料包經過路由器轉發到另一個MTU更小的資料鍊路上,需要路由器對資料包再分片才能繼續轉發,是以IPv4首部包含了便于IP分片重組的字段,但會增大路由器的處理負荷并降低網絡的使用率。随着人們對網絡安全和傳輸速度要求的提高,路由器需要處理的任務也越來越多,可以避免讓路由器承擔IP資料包分片任務嗎?為此産生了一種新技術”路徑MTU發現“(Path MTU Discovery),也即發現從發送端主機到接收端主機之間不需要分片時最大MTU的大小(路徑中存在的所有資料鍊路中最小的MTU)。路徑MTU發現過程如下圖示:

TCP/IP協定棧之LwIP(三)---網際尋址與路由(IPv4 + ARP + IPv6)一、網際尋址原理二、ARP協定三、IP協定更多文章:

上圖以UDP資料包的發送為例說明路徑MTU發現過程,換成TCP資料包情況類似。使用路徑MTU發現功能後,IP資料包隻需要在源主機必要時分片,到達目的主機後再重組為完整IP資料包,經過中間路由器時隻需要轉發,不需要再進行分片處理,提高了轉發速率和網絡使用率。

在IPv6首部直接取消了跟資料包分片與重組的字段,是以IPv6中的”路徑MTU發現“功能是必不可少的,IPv6的分片處理也隻能在發送端主機上進行,路由器不參與分片。不過IPv6中最小MTU為1280位元組(IPv4中最小MTU為68位元組),在資源受限的嵌入式系統中不需要進行”路徑MTU發現“,直接在發送IP包時以1280位元組分片發出。

3.2 IPv4資料報描述

通過IP進行通信時,需要在資料前加入IP首部資訊,正如通過資料鍊路通信時需要鍊路幀首部資訊一樣。IP首部中包含了用于IP協定進行發包控制時所有的必要資訊,了解IP首部結構能對IP所提供的功能有一個詳細的把握。IPv4資料報的格式如下圖示:

TCP/IP協定棧之LwIP(三)---網際尋址與路由(IPv4 + ARP + IPv6)一、網際尋址原理二、ARP協定三、IP協定更多文章:

版本Ver字段辨別IP首部的版本号資訊,IPv4的版本号為4,IPv6的版本号為6。首部長度(IHL: Internet Header Length)表明首部大小,以4位元組為機關,對于沒有可選項的IP包首部長度則設定為5(4*5 = 20位元組)。TOS字段表明服務品質(優先度、最低延遲、最大吞吐、最大可靠性、最小代價、最大安全等),但因實作TOS控制及其複雜目前幾乎所有的網絡都無視了該字段;有人提議将該字段分為DSCP(Differentiated Services Code Point)段與ECN(Explicit Congestion Notification)段進行品質控制。總長度TL字段表示IP首部與資料部分合起來的總位元組數,因該字段長16比特,故IPv4包最大長度為65535位元組。

辨別ID字段用于分片重組,同一個分片的辨別值相同,不同分片的辨別值不同。片偏移FO用來辨別被分片的每一個分段相對于原始資料的位置,因其有13位最多可表示8192個相對位置,以8位元組為機關,最大可表示原始資料8*8192 = 65535位元組的位置。标志Flags表示包被分片的相關資訊,每一位含義如下圖示:

TCP/IP協定棧之LwIP(三)---網際尋址與路由(IPv4 + ARP + IPv6)一、網際尋址原理二、ARP協定三、IP協定更多文章:

生存時間TTL字段指可以中轉多少個路由,每經過一個路由器該值減1,TTL減為0時丢棄該包。協定Protocol字段表示IP包傳輸層的上層協定編号,常用的協定如下表示:

TCP/IP協定棧之LwIP(三)---網際尋址與路由(IPv4 + ARP + IPv6)一、網際尋址原理二、ARP協定三、IP協定更多文章:

首部校驗和HC隻校驗資料報的首部,不校驗資料部分,主要用于確定IP資料報首部不被破壞,校驗和采用二進制反碼求和。源位址表示發送端IP位址,目的位址表示接收端IP位址。可選項包含安全級别、源路徑、路徑記錄、時間戳等資訊,通常隻在進行實驗或診斷時使用。填充項則為了将首部長度調整為32比特的整數倍。資料部分是實際要發送的資料,IP協定上層的首部也作為資料進行處理。在LwIP中描述IPv4的資料結構如下:

// rt-thread\components\net\lwip-1.4.1\src\include\ipv4\lwip\ip.h

PACK_STRUCT_BEGIN
struct ip_hdr {
  /* version / header length */
  PACK_STRUCT_FIELD(u8_t _v_hl);
  /* type of service */
  PACK_STRUCT_FIELD(u8_t _tos);
  /* total length */
  PACK_STRUCT_FIELD(u16_t _len);
  /* identification */
  PACK_STRUCT_FIELD(u16_t _id);
  /* fragment offset field */
  PACK_STRUCT_FIELD(u16_t _offset);
#define IP_RF 0x8000U        /* reserved fragment flag */
#define IP_DF 0x4000U        /* dont fragment flag */
#define IP_MF 0x2000U        /* more fragments flag */
#define IP_OFFMASK 0x1fffU   /* mask for fragmenting bits */
  /* time to live */
  PACK_STRUCT_FIELD(u8_t _ttl);
  /* protocol*/
  PACK_STRUCT_FIELD(u8_t _proto);
  /* checksum */
  PACK_STRUCT_FIELD(u16_t _chksum);
  /* source and destination IP addresses */
  PACK_STRUCT_FIELD(ip_addr_p_t src);
  PACK_STRUCT_FIELD(ip_addr_p_t dest); 
} PACK_STRUCT_STRUCT;
PACK_STRUCT_END

typedef struct ip_addr ip_addr_t;
struct ip_addr {
  u32_t addr;
};

#define IPH_V(hdr)  ((hdr)->_v_hl >> 4)
#define IPH_HL(hdr) ((hdr)->_v_hl & 0x0f)
#define IPH_TOS(hdr) ((hdr)->_tos)
#define IPH_LEN(hdr) ((hdr)->_len)
#define IPH_ID(hdr) ((hdr)->_id)
#define IPH_OFFSET(hdr) ((hdr)->_offset)
#define IPH_TTL(hdr) ((hdr)->_ttl)
#define IPH_PROTO(hdr) ((hdr)->_proto)
#define IPH_CHKSUM(hdr) ((hdr)->_chksum)

#define IPH_VHL_SET(hdr, v, hl) (hdr)->_v_hl = (((v) << 4) | (hl))
#define IPH_TOS_SET(hdr, tos) (hdr)->_tos = (tos)
#define IPH_LEN_SET(hdr, len) (hdr)->_len = (len)
#define IPH_ID_SET(hdr, id) (hdr)->_id = (id)
#define IPH_OFFSET_SET(hdr, off) (hdr)->_offset = (off)
#define IPH_TTL_SET(hdr, ttl) (hdr)->_ttl = (u8_t)(ttl)
#define IPH_PROTO_SET(hdr, proto) (hdr)->_proto = (u8_t)(proto)
#define IPH_CHKSUM_SET(hdr, chksum) (hdr)->_chksum = (chksum)
           

IP資料報首部結構體依然使用PACK_STRUCT_FIELD禁止編譯器自對齊,為了友善擷取或設定IP首部相應字段的值,定義了一系列的宏如上面的代碼所示,宏變量hdr為指向IP首部結構ip_hdr型變量的指針。

3.3 IPv4資料報操作函數

IP層資料報的處理任務并不複雜,除了前面介紹的IP資料報的分片重組外,主要是IP資料報的接收、發送、轉發等操作,資料包處理函數的調用關系圖(傳輸層以UDP協定為例)如下:

TCP/IP協定棧之LwIP(三)---網際尋址與路由(IPv4 + ARP + IPv6)一、網際尋址原理二、ARP協定三、IP協定更多文章:

IPv4主要的操作函數如下表示:

操作函數 功能描述

err_t ip_output(struct pbuf *p,

ip_addr_t *src, ip_addr_t *dest,

u8_t ttl, u8_t tos, u8_t proto)

被上層傳輸層協定調用以發送資料報,該函數先

通過ip_route查找網絡接口,再調用ip_output_if

完成資料報的發送;

struct netif *ip_route(ip_addr_t *dest)

根據目的IP位址選擇一個最合适的網路接口

(即與目的IP處于同一子網);

err_t ip_output_if(struct pbuf *p,

ip_addr_t *src, ip_addr_t *dest,

u8_t ttl, u8_t tos, u8_t proto,

struct netif *netif)

填寫IP首部中的各個字段值,并發送資料報,

如果資料報太大則需分片發送;

u16_t inet_chksum(void *dataptr,

u16_t len)

計算校驗和并取反碼;

static u16_t lwip_standard_chksum(

void *dataptr, int len)

對資料區域資料進行16bit求和,傳回網絡位元組序

的16bit結果;

err_t ip_frag(struct pbuf *p,

struct netif *netif, ip_addr_t *dest)

将資料報p進行分片發送;

err_t ip_input(struct pbuf *p,

struct netif *inp)

處理收到的IP資料報,如果為分片資料報則進行重組,

根據IP首部記錄的協定類型字段遞交給相應的上層協定

處理函數;

struct pbuf * ip_reass(struct pbuf *p) 重裝IP分片資料報;
void ip_reass_tmr(void)

定時釋放生存時間逾時的重裝結構體及其後挂接的資料

分片;

static void ip_forward(struct pbuf *p,

struct ip_hdr *iphdr, struct netif *inp)

将資料包p通過合适的接口轉發出去。

下面以ip_output與ip_input函數為例,簡單看下IP資料報是如何發送出去的,又是如何将接收到的資料報遞交給上層的:

// rt-thread\components\net\lwip-1.4.1\src\core\ipv4\ip.c

err_t ip_output(struct pbuf *p, ip_addr_t *src, ip_addr_t *dest,
          u8_t ttl, u8_t tos, u8_t proto)
{
  struct netif *netif;

  if ((netif = ip_route(dest)) == NULL) {
    return ERR_RTE;
  }
  return ip_output_if(p, src, dest, ttl, tos, proto, netif);
}

struct netif *ip_route(ip_addr_t *dest)
{
  struct netif *netif;

  /* iterate through netifs */
  for (netif = netif_list; netif != NULL; netif = netif->next) {
    /* network mask matches? */
    if (netif_is_up(netif)) {
      if (ip_addr_netcmp(dest, &(netif->ip_addr), &(netif->netmask))) {
        /* return netif on which to forward IP packet */
        return netif;
      }
    }
  }
  if ((netif_default == NULL) || (!netif_is_up(netif_default))) {
    return NULL;
  }
  /* no matching netif found, use default netif */
  return netif_default;
}

err_t ip_output_if(struct pbuf *p, ip_addr_t *src, ip_addr_t *dest,
             u8_t ttl, u8_t tos, u8_t proto, struct netif *netif)
{
  struct ip_hdr *iphdr;

  /* Should the IP header be generated or is it already included in p? */
  if (dest != IP_HDRINCL) {
    u16_t ip_hlen = IP_HLEN;
    /* generate IP header */
    if (pbuf_header(p, IP_HLEN)) {
      return ERR_BUF;
    }
    iphdr = (struct ip_hdr *)p->payload;
    IPH_TTL_SET(iphdr, ttl);
    IPH_PROTO_SET(iphdr, proto);
    /* dest cannot be NULL here */
    ip_addr_copy(iphdr->dest, *dest);
    IPH_VHL_SET(iphdr, 4, ip_hlen / 4);
    IPH_TOS_SET(iphdr, tos);
    IPH_LEN_SET(iphdr, htons(p->tot_len));
    IPH_OFFSET_SET(iphdr, 0);
    IPH_ID_SET(iphdr, htons(ip_id));
    ++ip_id;

    if (ip_addr_isany(src)) {
      ip_addr_copy(iphdr->src, netif->ip_addr);
    } else {
      /* src cannot be NULL here */
      ip_addr_copy(iphdr->src, *src);
    }
    IPH_CHKSUM_SET(iphdr, 0);
    IPH_CHKSUM_SET(iphdr, inet_chksum(iphdr, ip_hlen));
  } else {
    /* IP header already included in p */
    iphdr = (struct ip_hdr *)p->payload;
    ip_addr_copy(dest_addr, iphdr->dest);
    dest = &dest_addr;
  }

  if (ip_addr_cmp(dest, &netif->ip_addr)) {
    /* Packet to self, enqueue it for loopback */
    return netif_loop_output(netif, p, dest);
  }
  /* don't fragment if interface has mtu set to 0 [loopif] */
  if (netif->mtu && (p->tot_len > netif->mtu)) {
    return ip_frag(p, netif, dest);
  }
  return netif->output(netif, p, dest);
}
           

前兩個函數比較簡單,函數功能已經在上表介紹過了,從IP層發送資料包到資料鍊路層主要是靠函數ip_output_if實作的。ip_output_if的作用主要也是将資料包pbuf的payload指針移到IP首部,開始填充IP首部中的各字段值,最後發送資料分三種情況處理:如果目的IP是本網卡位址則調用環回輸出函數(在前篇網絡接口層介紹過netif_loop_output);如果資料報太大則調用ip_frag函數對資料報分片發送;剩下的情況直接調用接口注冊的output函數(即網卡驅動接口函數)發送資料報。下面再介紹下從網絡接口層傳送到IP層的資料包如何處理:

// rt-thread\components\net\lwip-1.4.1\src\core\ipv4\ip.c

err_t ip_input(struct pbuf *p, struct netif *inp)
{
  struct ip_hdr *iphdr;
  struct netif *netif;
  u16_t iphdr_hlen;
  u16_t iphdr_len;

  /* identify the IP header */
  iphdr = (struct ip_hdr *)p->payload;
  if (IPH_V(iphdr) != 4) {
    pbuf_free(p);
    return ERR_OK;
  }
  /* obtain IP header length in number of 32-bit words */
  iphdr_hlen = IPH_HL(iphdr);
  /* calculate IP header length in bytes */
  iphdr_hlen *= 4;
  /* obtain ip length in bytes */
  iphdr_len = ntohs(IPH_LEN(iphdr));

  /* header length exceeds first pbuf length, or ip length exceeds total pbuf length? */
  if ((iphdr_hlen > p->len) || (iphdr_len > p->tot_len)) {
    /* free (drop) packet pbufs */
    pbuf_free(p);
    return ERR_OK;
  }
  /* verify checksum */
  if (inet_chksum(iphdr, iphdr_hlen) != 0) {
    pbuf_free(p);
    return ERR_OK;
  }
  /* Trim pbuf. This should have been done at the netif layer,
   * but we'll do it anyway just to be sure that its done. */
  pbuf_realloc(p, iphdr_len);

  /* copy IP addresses to aligned ip_addr_t */
  ip_addr_copy(current_iphdr_dest, iphdr->dest);
  ip_addr_copy(current_iphdr_src, iphdr->src);

  /* match packet against an interface, i.e. is this packet for us? */
  {
    /* start trying with inp. if that's not acceptable, start walking the
       list of configured netifs.
       'first' is used as a boolean to mark whether we started walking the list */
    int first = 1;
    netif = inp;
    do {
      /* interface is up and configured? */
      if ((netif_is_up(netif)) && (!ip_addr_isany(&(netif->ip_addr)))) {
        /* unicast to this interface address? */
        if (ip_addr_cmp(&current_iphdr_dest, &(netif->ip_addr)) ||
            /* or broadcast on this interface network address? */
            ip_addr_isbroadcast(&current_iphdr_dest, netif)) {
          /* break out of for loop */
          break;
        }
      }
      if (first) {
        first = 0;
        netif = netif_list;
      } else {
        netif = netif->next;
      }
      if (netif == inp) {
        netif = netif->next;
      }
    } while(netif != NULL);
  }
  /* broadcast or multicast packet source address? Compliant with RFC 1122: 3.2.1.3 */
  {  if ((ip_addr_isbroadcast(&current_iphdr_src, inp)) ||
         (ip_addr_ismulticast(&current_iphdr_src))) {
      /* free (drop) packet pbufs */
      pbuf_free(p);
      return ERR_OK;
    }
  }
  /* packet not for us? */
  if (netif == NULL) {
    /* non-broadcast packet? */
    if (!ip_addr_isbroadcast(&current_iphdr_dest, inp)) {
      /* try to forward IP packet on (other) interfaces */
      ip_forward(p, iphdr, inp);
    } 
    pbuf_free(p);
    return ERR_OK;
  }
  /* packet consists of multiple fragments? */
  if ((IPH_OFFSET(iphdr) & PP_HTONS(IP_OFFMASK | IP_MF)) != 0) {
    /* reassemble the packet*/
    p = ip_reass(p);
    /* packet not fully reassembled yet? */
    if (p == NULL) {
      return ERR_OK;
    }
    iphdr = (struct ip_hdr *)p->payload;
  }
  
  if (iphdr_hlen > IP_HLEN) {
    pbuf_free(p);
    return ERR_OK;
  }

  current_netif = inp;
  current_header = iphdr;
  /* raw input did not eat the packet? */
  if (raw_input(p, inp) == 0)
  {
    switch (IPH_PROTO(iphdr)) {
    case IP_PROTO_UDP:
      udp_input(p, inp);
      break;
    case IP_PROTO_TCP:
      tcp_input(p, inp);
      break;
    case IP_PROTO_ICMP:
      icmp_input(p, inp);
      break;
    case IP_PROTO_IGMP:
      igmp_input(p, inp, &current_iphdr_dest);
      break;
    default:
      /* send ICMP destination protocol unreachable unless is was a broadcast */
      if (!ip_addr_isbroadcast(&current_iphdr_dest, inp) &&
          !ip_addr_ismulticast(&current_iphdr_dest)) {
        p->payload = iphdr;
        icmp_dest_unreach(p, ICMP_DUR_PROTO);
      }
      pbuf_free(p);
    }
  }
  current_netif = NULL;
  current_header = NULL;
  ip_addr_set_any(&current_iphdr_src);
  ip_addr_set_any(&current_iphdr_dest);

  return ERR_OK;
}
           

函數ip_input内容看似比較多,主要也是資料校驗與分類遞交的工作。首先根據IP首部資料校驗IP首部版本、長度、校驗和、源位址是否為單點傳播IP位址等資訊,然後根據目的位址是否與本機IP比對,如果目的位址與本機IP不比對則根據情況轉發資料報(廣播位址不轉發),如果該IP資料報分片标志置位則需進行資料報重組。如果通過了上述所有校驗,則根據首部中協定類型字段,調用相應的上層協定資料包輸入函數将IP資料報遞交給對應的上層協定繼續處理,如果找不到上層協定則向源主機傳回目的不可達ICMP封包(下一篇将會介紹ICMP原理與實作)。這裡還有一個raw_input函數,使用這個函數可以讓應用程式直接讀取IP層中的資料報,這點與BSD中提供的RawSocket(原始套接字)程式設計功能類似,下一篇介紹ICMP封包實作時會給出raw_input實作代碼。

3.4 IPv6資料報描述

IPv6的IP資料報首部格式相比IPv4發生了很大變化:IPv6為了減輕路由器負擔,省略了首部校驗和字段,由于路由器不再需要計算校驗和提高了包轉發效率;分片重組處理相關的字段成為可選項;為了便于64位CPU處理資料更友善,IPv6首部及可選項長度都是8位元組即64比特的整數倍(IPv4首部及可選字段長度都是4位元組即32比特整數倍)。IPv6資料報的格式如下圖示:

TCP/IP協定棧之LwIP(三)---網際尋址與路由(IPv4 + ARP + IPv6)一、網際尋址原理二、ARP協定三、IP協定更多文章:

IPv6的版本号字段為6。通信量類(Traffic Class)字段相當于IPv4的TOS(Type Of Service)字段,屬于保留字段。流标号(Flow Label)準備用于服務品質QoS(Quality Of Service)控制,不使用QoS時每一位可以全部設定為0,在進行QoS控制時将流标号設定為一個随機數,然後利用一種可以設定流的協定RSVP(Resource Reservation Protocol)在路由器上進行QoS設定,隻有流标号、源位址與目标位址三項完全一緻時才被認為是一個流。

IPv6有效載荷長度(Payload Length)類似于IPv4的總長度(指包含首部在内的所有長度),但IPv6的有效載荷長度不包括首部,隻表示資料部分的長度。下一個首部(Next Header)相當于IPv4中的協定字段,通常表示IP的上一層協定類型,當有IPv6擴充首部時表示後面第一個擴充首部的協定類型。跳數限制(Hop Limit)相當于IPv4中的TTL字段,為了強調可通過路由器個數而重新取名的。源位址表示發送端IP位址,目的位址表示接收端IP位址,都由128比特即16位元組構成。

IPv6的首部長度固定,無法将可選項加入其中,通過擴充首部對功能進行有效擴充。擴充首部常介于IPv6首部與TCP/UDP首部中間,可以連續使用多個擴充首部沒有長度限制(IPv4中可選項長度固定為40位元組),使用多個擴充首部的IPv6資料報結構如下圖示:

TCP/IP協定棧之LwIP(三)---網際尋址與路由(IPv4 + ARP + IPv6)一、網際尋址原理二、ARP協定三、IP協定更多文章:

IPv6常用的擴充首部如下表示:

TCP/IP協定棧之LwIP(三)---網際尋址與路由(IPv4 + ARP + IPv6)一、網際尋址原理二、ARP協定三、IP協定更多文章:

當需要對IPv6資料報進行分片時,可以添加擴充首部協定号44;使用IPsec(Internet Protocol Security,比如用于建構VPN: Virtual Private Network)時可以使用協定号50/51的ESP(Encapsulated Security Payload)與AH(Authentication Header);Mobile IPv6(常用于移動通信中當主機所連接配接子網IP發生變化時,主機IP仍保持不變以維持原通信不發生中斷)的情況下可以采用協定号60/135的目标位址選項與移動首部。在LwIP中描述IPv6的資料結構如下:

// rt-thread\components\net\lwip-1.4.1\src\include\ipv6\lwip\ip.h

/* The IPv6 header. */
struct ip_hdr {
#if BYTE_ORDER == LITTLE_ENDIAN
  u8_t tclass1:4, v:4;
  u8_t flow1:4, tclass2:4;  
#else
  u8_t v:4, tclass1:4;
  u8_t tclass2:8, flow1:4;
#endif
  u16_t flow2;
  u16_t len;                /* payload length */
  u8_t nexthdr;             /* next header */
  u8_t hoplim;              /* hop limit (TTL) */
  struct ip_addr src, dest;          /* source and destination IP addresses */
};

PACK_STRUCT_BEGIN
 struct ip_addr {
  PACK_STRUCT_FIELD(u32_t addr[4]);
} PACK_STRUCT_STRUCT;
PACK_STRUCT_END

#define IPH_PROTO(hdr) (iphdr->nexthdr)
           

IPv6的結構體相比IPv4更簡單些,其中PACK_STRUCT_FIELD禁止編譯器自對齊,宏變量hdr為指向IP首部結構ip_hdr型變量的指針。

3.5 IPv6資料報操作函數

IPv6的資料報處理函數跟IPv4差不多,沒有專門的分片重組操作了,主要是IPv6資料報的接收、發送、轉發等操作,資料包處理函數的調用關系依然可以參考IPv4中的圖示,IPv6主要的操作函數如下表示:

操作函數 功能描述

err_t ip_output(struct pbuf *p,

struct ip_addr *src, struct ip_addr *dest,

u8_t ttl, u8_t proto)

被上層傳輸層協定調用以發送資料報,該函數先

通過ip_route查找網絡接口,再調用ip_output_if

完成資料報的發送;

struct netif *ip_route(struct ip_addr *dest)

根據目的IP位址選擇一個最合适的網路接口

(即與目的IP處于同一子網);

err_t ip_output_if(struct pbuf *p,

struct ip_addr *src, struct ip_addr *dest,

u8_t ttl, u8_t proto,struct netif *netif)

填寫IP首部中的各個字段值,并發送資料報;

void ip_input(struct pbuf *p,

struct netif *inp)

處理收到的IP資料報,根據IP首部記錄的協定

類型字段遞交給相應的上層協定處理函數;

static void ip_forward(struct pbuf *p,

struct ip_hdr *iphdr)

将資料包p通過合适的接口轉發出去。

更多文章:

  • 《qemu-vexpress-a9 for LwIP stack》
  • 《TCP/IP協定棧之LwIP(二)—網絡接口管理》
  • 《TCP/IP協定棧之LwIP(四)—網絡診斷與狀态查詢》

繼續閱讀