四LWIP學習筆記之網際協議(IP)


一、相關知識

1、概述

2、IP地址

  A 類地址中只能有 125 個網絡號被分配使用,即全世界只有 125 個機構能使用 A 類網絡號,這樣的機構通常是很大的機構,因為每個 A 類網絡號內包含了上億個主機地址,通常沒有任何一個機構能夠使用這么多的主機,因此數以百萬計的 A 類網絡地址將被浪費掉。

  B 類網絡地址(128~191)占據了整個 32bit 地址空間的四分之一,它用前兩個字節的后 14bit表示網絡號,后兩個字節表示該網絡內的主機號。B 類網絡的網絡號有 16384 個,但其中的16 個網絡號(172.16 到 172.31)被保留為 B 類網絡專用地址,所以可用的 B 類網絡號有 16368 個,每個網絡能容納 65534 個主機(216­2)。使用 B 類地址的機構也應該是足夠大的機構。 

  C 類網絡地址(192~223)占據了整個 32bit 地址空間的八分之一,該類地址用前三個字節中的后21bit 表示網絡號,最后一個字節表示該網絡內的主機號。C 類地址網絡號可達 2097152 個,但其中的 256 個網絡號(192.168.0 至 192.168.255)被保留為 C 類網絡專用地址,因此可用的 C 類網絡號有 2096896 個,每個網絡能容納 254 個主機。C 類網絡地址是為具有較少主機和路由器的小型機構設計的,通常該類網絡中的主機號往往不夠用。 

  D 類網絡地址(224~239)占據了整個 32bit 地址空間的十六分之一,是一組專門為組播保留的地址。該類地址並不指向任何特定的網絡,每一個 D 類地址定義了在網絡中的一組主機,這組機器可以基於這個地址進行多播通信。

  E 類網絡地址(224~239)占據了整個 32bit 地址空間的十六分之一,為將來使用而保留的。其中全 1 的 IP 地址(255.255.255.255)用作特殊用途,表示當前子網的廣播地址。 

 

        

 

  A類網絡地址:0.xxx.xxx.xxx、127.xxx.xxx.xxx、10.xxx.xxx.xxx(3)

  B類網絡地址:172.16.0.0~172.31.255.255(16)

  C類網絡地址:192.168.0.0~192.168.255.255(256)

 

3、特殊IP地址

  下面說說幾個具有特殊用途的 IP 地址,它們不能分配給任何主機。   

  環回地址 :127.xxx.xxx.xxx(通常使用127.0.0.1),PS:環回地址使得 A 類地址少了一個網絡號   

  網絡地址 :對任意一個 IP 地址來說,將其地址結構中的主機號全部取 0,就得到了主機所處的網絡地址。 例如,某主機 IP 地址為 134.89.32.33,它屬於 B 類地址,將其后兩個主機號字節全部取 0,則得到這個主機所處的網絡地址為 134.89.0.0。 PS:網絡地址占用了 A、B、C 三類地址中的每個網絡號下的一個主機號。   

  直接廣播地址 :在一個網絡內,直接廣播地址是指對應主機號全部取 1 而得到的 IP 地址,廣播地址代表本網絡內的所有網絡設備,使用該地址可以向屬於同一個網段內的所有網絡設備傳送數據。例如:一個標准 C 類地址 202.197.15.44,由於它的網絡號由前面 3 個字節組成,主機號僅是最后一個字節,將主機號位全部取 1 得到的地址是 202.197.15.255,這個地址即是這個網絡的廣播地址。 A、B、C 三類網絡的網絡地址和廣播地址結構如下所示:  

  A 類主機號:網絡地址為:x.0.0.0,廣播地址為:x.255.255.255;  

  B 類主機號:網絡地址為:x.x.0.0,廣播地址為:x.x.255.255;  

  C 類主機號:網絡地址為:x.x.x.0,廣播地址為:x.x.x.255。   

  這也是為什么在計算表 10­1 中的網絡最大主機數時,需要減 2 的原因 

  受限廣播地址 :IP 地址 32 位全部為 1,即“255.255.255.255”,代表本地受限廣播。該地址用於向本地網絡中的所有主機發送廣播消息 本網絡上的特定主機 ,PS:廣播地址本質上是個 E 類地址。 

  本網絡上的特定主機 :當用戶想與本網絡內部特定主機通信時,可以將網絡號對應字節全部設置為 0 進行簡化。如當具有 B 類地址的某個主機發送數據包時,數據包中的目標 IP 地址為 0.0.11.32,則表示數據包要發送到網絡中主機號為 11.32 的主機處。PS:該地址本質上是個 A 類地址。 

  本網絡本主機 :IP 地址 32 位全部為 0,即“0.0.0.0”,表示本網絡上的本主機。這個地址通常用某個主機啟動時,需要通信,但暫時不知道自己的 IP 地址,此時主機為了獲得一個有效 IP 地址,將發送一個數 據包給有限廣播地址,並用全 0 地址來標志自己。接收方知道發送方還沒有 IP 地址,就會采用一種特殊的方式來發送回答,這地址不應該作為目的地址使用。PS:該地址本質上是個 A 類地址。 

  下面講的這幾個專用地址卻可以被分配給多個主機,當然這些主機之間應該互不相關,即處於互相獨立的專用網內,例如目前很流行的以太網局域網。在每一類地址中部分地址為專用地址,如表 10­2 所示。 

          

4、子網划分與子網掩碼

  現在流行一種擴展的分組編址方案來節省網絡號的使用,這種方法稱為子網編址,它是一種將網絡進一步划分為子網的設計,允許多個物理網絡共享一個網絡前綴,但每個子網都有自己的子網地址。 

          

  划分子網有諸多的好處:第一,減少標准 IP 編址中地址浪費現象。 第二,節省通信流量。 第三,由於每個網段的主機數減少,使得主機的管理更加方便。 

  子網掩碼,標准的 IP 地址,一眼就能夠看出 IP 地址的種類(第一個字節就行)並獲取其中的網絡號,但對於子網編址,要想得到其網絡號和子網號,就必須使用子網掩碼。子網掩碼的長度也是 32 位,左邊是網絡位(包括子網位),右邊是主機位,所有網絡位都用 1 表示,所有主機位都用 0 表示,這就形成了子網掩碼。 

5、網絡地址轉換(NAT)

  企業內部使用的局域網路由器都是具有 NAT 功能的,具有 NAT 功能的路由器至少要有一個內部端口和一個外部端口,內部端口是路由器為了與局域網內的用戶通信而使用的,它使用一個內部專用 IP 地址,例如常見的路由器內部 IP 地址可以為 192.168.1.1;外部端口是路由器用來與外部網絡通信用的,它通常具有一個有效的 IP 地址,假設為一個有效的 C 類地址 222.197.179.21。NAT 的功能可簡單描述為:當內部網絡用戶連接互聯網時,NAT 將用戶的內部 IP 地址轉換成一個外部公共 IP 地址,反之,數據從外部返回時,NAT 將目的地址替換成用戶的內部 IP 地址。 

  假如我們的局域網用戶(其使用的專用地址為 192.168.1.78)需要使用 TCP 協議與 Internet 中的一個 HTTP 服務器進行通信(IP 地址為 130.21.45.20,服務端口號為 80),則它將發送一個包含源 IP 地址、源端口號、目的 IP 地址、目的端口號的 IP 分組到路由器處,這里假設這四個值分別為:(192.168.1.78,1234,130.21.45.20,80) 

  NAT轉換:(222.178.197.215678130.21.45.2080

  服務器返回:130.21.45.2080222.178.197.215678

  NAT轉換:130.21.45.2080192.168.1.781234) 

  這樣,經過 NAT 兩次簡單的轉換,局域網用戶就實現了與外部網絡的數據包交互,上述整個過程對所有用戶來說是透明的,但所有局域網用戶能夠通過同一個 IP 地址與外部進行通信。 

6、單播、多播與廣播

  多播:多播地址是 D 類地址,在 LwIP 中提供了一個宏來判斷某個地址是否為多播地址, 通過判斷它的最高四位是否為 1110(e)就能知道該地址是不是 D 類多播地址。 

————ip_addr.h————————————————
#define ip_addr_ismulticast(addr1)
(((addr1)­>addr & ntohl(0xf0000000UL)) == ntohl(0xe0000000UL))
————————————————————————————

  廣播:在前面講到廣播分為直接廣播和有限廣播,在以太網中將這兩者看作相同的方式,在 LwIP 中,判斷一個數據報中的目的地址是否為廣播地址的函數叫做ip_addr_isbroadcast 

————ip_addr.c——————
//兩個特殊 IP 地址的定義
#define IP_ADDR_ANY_VALUE 0x00000000UL //某些使用規范中,全 0 也代表所有主機
#define IP_ADDR_BROADCAST_VALUE 0xffffffffUL //全 1,受限廣播地址
//函數功能:判斷一個目的 IP 地址是否是廣播地址
//參數 netif:本地網絡接口結構
//返回值:是廣播地址則返回非 0 值
u8_t ip_addr_isbroadcast(struct ip_addr *addr, struct netif *netif)
{
  u32_t addr2test;
  addr2test = addr­>addr; //取得 IP 地址結構中的 32 位整數
  if ((~addr2test == IP_ADDR_ANY_VALUE) || //如果 32 位為全 0 或者全 1
    (addr2test == IP_ADDR_ANY_VALUE)) //都是廣播地址
    return 1; //返回非 0 值
  //判斷是否為受限廣播
  else if ((netif­>flags & NETIF_FLAG_BROADCAST) == 0) //如果網絡接口不支持廣播
    return 0; //則上層應用不應該出現廣播地址,直接返回 0
  else if (addr2test == netif­>ip_addr.addr) //如果目的 IP 地址和本接口的 IP 地址一樣
    return 0; //也不是廣播地址
  //如果目的 IP 地址與本網絡接口處於同一子網中,且其主機位全部為 0,則是受限廣播
  else if (ip_addr_netcmp(addr, &(netif­>ip_addr), &(netif­>netmask)) //同一網段
    && ((addr2test & ~netif­>netmask.addr) == //且主機位全部為 0
    (IP_ADDR_BROADCAST_VALUE & ~netif­>netmask.addr)))
    return 1; //返回非 0 值
  else
  return 0;
}
————————————————————

  單播:除了組播和廣播,剩下的就是單播

二、數據報

1、數據報組成結構

  IP 數據報有自己的組織格式,如圖 10­4 所示,它通常由兩部分組成,即 IP 首部和數據。 

  

  第一個字段是 4bit 的版本號(VER),包含了創建數據報所使用的 IP 協議版本信息,例如對於 IPv4,該值為 4,對於 IPv6,該值為 6。 

  接下來的 4bit 字段用於記錄首部長度,這個長度以字為單位。 (最大為15)

  再下來是一個 8bit 的服務類型字段(Type Of Service,TOS),該字段主要用於描述當前 IP 數據報急需的服務類型,如最小延時、最大吞吐量、最高可靠性、最小費用等。路由器在轉發數據報時,可以根據這個字段的值來為數據報選擇最合理的路由路徑。 

  16 位的總長度字段描述了整個 IP 數據報(IP 首部和數據區)的總字節數(一般1500,成為MTU)。當一個很大的 IP 數據報需要發送時,IP 層首先要檢查底層接口設備的 MTU,然 后將大的數據報划分為幾個分片包,最后分別遞交給底層發送; 

  接下來的三個字段:16 位標識字段、3 位標志和 13 位片偏移字段常在 IP 數據報分片時使用。 

  生存時間(TTL)字段描述該 IP 數據報最多能被轉發的次數,每經過一次轉發,該值會減 1,當該值為 0 時,路由器會丟棄掉分組,同時一個 ICMP 差錯報文會被返回至源主機。 

  8 位協議字段和以太網數據幀中的協議類型字段功能相似,不過這里它用來描述該 IP 數據報中的數據是來自於哪個上層協議,例如,該值為 1 表示 ICMP 協議,為 2 表示 IGMP 協議,為 6 表示 TCP協議,為 17 表 UDP 協議。事實上,該字段的值也間接的指出了 IP 數據報的數據區域中數據的格式,因為每一種上層協議都使用了一種獨立的數據格式。 

2、數據結構

  為了方便對 IP 數據報首部字段進行讀取或寫入操作,在 LwIP 中定義了一個名為 ip_hdr 的結構體來描述數據報首部,如下代碼所示: 

————ip.h——————————————
PACK_STRUCT_BEGIN //禁止編譯器自對齊
struct ip_hdr 
{   PACK_STRUCT_FIELD(u16_t _v_hl_tos);
//前三個字段:版本號+首部長度+服務類型   PACK_STRUCT_FIELD(u16_t _len); //總長度   PACK_STRUCT_FIELD(u16_t _id); //標識字段   PACK_STRUCT_FIELD(u16_t _offset); //3 位標志位和 13 位片偏移字段   #define IP_RF 0x8000 //標志位第一位(保留位)掩碼   #define IP_DF 0x4000 //標志位第二位(不分片標志)掩碼   #define IP_MF 0x2000 //標志位第三位(更多分片位)掩碼   #define IP_OFFMASK 0x1fff //13 位片偏移字段的掩碼   PACK_STRUCT_FIELD(u16_t _ttl_proto); //TTL 字段+協議字段   PACK_STRUCT_FIELD(u16_t _chksum); //首部校驗和字段   PACK_STRUCT_FIELD(struct ip_addr src); //源 IP 地址   PACK_STRUCT_FIELD(struct ip_addr dest); //目的 IP 地址 } PACK_STRUCT_STRUCT; PACK_STRUCT_END //下面定義了幾個宏,用於對 IP 首部中各個字段值的讀取,宏變量 hdr 為指向 //IP 首部結構 ip_hdr 型變量的指針 #define IPH_V(hdr) (ntohs((hdr)­>_v_hl_tos) >> 12) //獲取版本號 #define IPH_HL(hdr) ((ntohs((hdr)­>_v_hl_tos) >> 8) & 0x0f) //獲取首部長度 #define IPH_TOS(hdr) (ntohs((hdr)­>_v_hl_tos) & 0xff) //獲取服務類型 #define IPH_LEN(hdr) ((hdr)­>_len) //獲取數據報總長度(網絡字節序) #define IPH_ID(hdr) ((hdr)­>_id) //獲取數據報標識字段(網絡字節序) #define IPH_OFFSET(hdr) ((hdr)­>_offset) //獲取標志位+片偏移字段(網絡字節序) #define IPH_TTL(hdr) (ntohs((hdr)­>_ttl_proto) >> 8) //獲取 TTL 字段 #define IPH_PROTO(hdr) (ntohs((hdr)­>_ttl_proto) & 0xff) //獲取協議字段 #define IPH_CHKSUM(hdr) ((hdr)­>_chksum) //獲取首部校驗和字段(網絡字節序) //下面定義幾個宏,用於填寫 IP 首部各個字段的值,宏變量 hdr 為指向 //IP 首部結構 ip_hdr 型變量的指針 #define IPH_VHLTOS_SET(hdr, v, hl, tos) (hdr)­>_v_hl_tos = //版本號+首部長度+服務類型 (htons(((v) << 12) | ((hl) << 8) | (tos))) #define IPH_LEN_SET(hdr, len) (hdr)­>_len = (len) //數據報總長度(len 應為網絡字節序) #define IPH_ID_SET(hdr, id) (hdr)­>_id = (id) //數據報標識字段(id 應為網絡字節序) #define IPH_OFFSET_SET(hdr, off) (hdr)­>_offset = (off) //標志位+片偏移字段 #define IPH_TTL_SET(hdr, ttl) (hdr)­>_ttl_proto = //TTL 字段 (htons(IPH_PROTO(hdr) | ((u16_t)(ttl) << 8))) #define IPH_PROTO_SET(hdr, proto) (hdr)­>_ttl_proto = //協議字段 (htons((proto) | (IPH_TTL(hdr) << 8))) #define IPH_CHKSUM_SET(hdr, chksum) (hdr)­>_chksum = (chksum) //首部校驗和 ————————————————————————————————————

三、IP層輸出

1、發送數據報

  當傳輸層協議(TCP 或 UDP)要發送數據時,它們會將數據按照自己的格式組裝在一個 pbuf中,並將 payload 指針指向協議首部,然后調用 IP 層的數據報發送函數 ip_output 發送數據。 然后調用函數 ip_output_if 將數據報發送出去。 

————ip.c——————————————
//函數功能:被傳輸層協議調用以發送數據報,該函數查找網絡接口並調用函數
// ip_output_if 完成最終的發送工作
//參數 p:傳輸層協議需要發送的數據包 pbuf,payload 指針已指向協議首部
//參數 src:源 IP 地址,若為 NULL,則使用網絡接口結構中保存的 IP 地址
//參數 dest:目的 IP 地址,若為 IP_HDRINCL,則表示 p 中已經有了填寫好的 IP
//數據報首部,且 payload 指針也已經指向了 IP 首部
//參數 ttl、tos、proto:IP 首部中的 TTL 字段、服務類型和協議類型值
err_t ip_output(struct pbuf *p, struct ip_addr *src, struct ip_addr *dest,u8_t ttl, u8_t tos, u8_t proto)
{
  struct netif *netif;
  //根據目的 IP 地址為數據報尋找一個合適的網絡接口
  if ((netif = ip_route(dest)) == NULL) 
  { //若找到,變量 netif 指向對應的接口結構     return ERR_RTE; //找不到,返回接口錯誤   }   //否則,增加 netif 為參數調用函數 ip_output_if   return ip_output_if(p, src, dest, ttl, tos, proto, netif); } //函數功能:填寫 IP 首部中的各個字段值(不處理選項字段),並發送數據報 //參數說明:netif 為發送數據報的網絡接口結構,其他參數與 ip_output 的相同 err_t ip_output_if(struct pbuf *p, struct ip_addr *src, struct ip_addr *dest,u8_t ttl, u8_t tos, u8_t proto, struct netif *netif) {   struct ip_hdr *iphdr;   static u16_t ip_id = 0; //靜態變量,記錄 IP 數據報的編號(標識字段)   if (dest != IP_HDRINCL)
  { //dest 不為 IP_HDRINCL,說明 pbuf 中未填寫 IP 首部     u16_t ip_hlen = IP_HLEN; //宏 IP_HLEN 為默認的 IP 首部長度,20     if (pbuf_header(p, IP_HLEN))
    { //移動 payload 指針,指向 pbuf 中的 IP 首部       return ERR_BUF; //失敗,返回 pbuf 空間錯誤     }     iphdr = p­>payload; //iphdr 指向數據報首部     IPH_TTL_SET(iphdr, ttl); //填寫 TTL 字段     IPH_PROTO_SET(iphdr, proto); //填寫協議字段     ip_addr_set(&(iphdr­>dest), dest); //填寫目的 IP 地址     IPH_VHLTOS_SET(iphdr, 4, ip_hlen / 4, tos); //填寫版本號+首部長度+服務類型     IPH_LEN_SET(iphdr, htons(p­>tot_len)); //填寫數據報總長度     IPH_OFFSET_SET(iphdr, 0//填寫標志位和片偏移字段,都為 0     IPH_ID_SET(iphdr, htons(ip_id)); //填寫標識字段     ++ip_id; //數據報編號值加 1     if (ip_addr_isany(src))
    { //若 src 為空,則將源 IP 地址填寫為網絡接口的 IP 地址       ip_addr_set(&(iphdr­>src), &(netif­>ip_addr));     } else
    { //若 src 不為空,則直接填寫源 IP 地址       ip_addr_set(&(iphdr­>src), src);     }     IPH_CHKSUM_SET(iphdr, 0//清 0 校驗和字段     IPH_CHKSUM_SET(iphdr, inet_chksum(iphdr, ip_hlen)); //計算並填寫首部校驗和   } else
  { //如果 pbuf 中已經填寫 IP 首部,這里不需要填寫任何字段     iphdr = p­>payload;     dest = &(iphdr­>dest); //只是用變量 dest 記錄下數據報中的目的 IP 地址   }   //下面開始發送 IP 數據報,分為三種情況來處理   if (ip_addr_cmp(dest, &netif­>ip_addr))
  { //如果目的 IP 地址是本網卡的地址,     return netif_loop_output(netif, p, dest); //則調用環回輸入,這個函數見第 8 章   }   if (netif­>mtu && (p­>tot_len > netif­>mtu))
  { //如果數據報太大(大於接口的 mtu)     return ip_frag(p,netif,dest); //則調用函數 ip_frag 對數據報分片發送   }   //剩下的情況,直接調用接口注冊的 output 函數發送數據報   return netif­>output(netif, p, dest); } —————————————————————————————————

  函數 ip_output 中還出現了一個 ip_route 函數,它比較簡單,功能是根據目的 IP 地址在系統的所有網絡接口結構中選擇一個最佳的網絡接口返回,該網絡接口將用於發送最終的數據報。 

————ip.c——————————————————
//函數功能:根據目的 IP 地址選擇一個最合適的網絡接口結構
//參數 dest:目的 IP 地址
//返回值:指向相應網絡接口結構的指針
struct netif * ip_route(struct ip_addr *dest)
{
  struct netif *netif;
  for(netif = netif_list; netif != NULL; netif = netif­>next) 
  {//依次遍歷接口結構鏈表     if (netif_is_up(netif))
    { //接口已經使能       if (ip_addr_netcmp(dest, &(netif­>ip_addr), &(netif­>netmask)))
      {//且與目的 IP 地址         return netif; //主機處於同一子網內,則返回這個接口結構       }     }   }//for   //到這里,說明沒找到合適的接口,我們把系統的默認網絡接口作為結果返回   if ((netif_default == NULL) || (!netif_is_up(netif_default)))
  {//如果默認網絡接口未配置或     return NULL; //未使能,則返回空指針   }   return netif_default; //返回默認網絡接口 } ——————————————————————————————————

  從上面的代碼看出,所謂最佳的網絡接口,就是與目的 IP 地址處於統一子網的網絡接口,這一點的判斷需要使用到子網掩碼的概念。 

2、數據報分片

  

        總長度(16位)  標識(16位)  標志(3位)  分片偏移量(13位)

  原始數據報   3020      12345      0        0

  分片1      1420      12345      1        0

  分片2      1420      12345      1        175(1400/8)

  分片3       220       12345      0        350(2800/8)

  16 位標識字段:用於標識 IP 層發送出去的每一份 IP 數據報,每發送一份報文,則該值加 1,數據包被分片時,該字段會被復制到每一個分片中。在接收端,會使用這個字段值來組裝所有分片為一個完整的數據報。

  3位標志字段:第一位保留;第二位是不分片位;第三位表示更多分片位,當該位被置1表示該分片不是最后一分片,當該為被置0表示該分片為最后一分片

  13位分片偏移量:相對於原始數據報以8個字節為單位的偏移量

————ip_frag.c————————————————————
//定義一個全局型的數組,數組大小為 IP 層允許的最大分片大小,每個分片會被先后
//拷貝到這個數組中,然后發送
#if IP_FRAG_USES_STATIC_BUF
static u8_t buf[LWIP_MEM_ALIGN_SIZE(IP_FRAG_MAX_MTU + MEM_ALIGNMENT ­ 1)];
#endif
//函數功能:將數據報 p 進行分片發送,該函數在 ip_output_if 中被調用
//參數 p:需要分片發送的數據報
//參數 netif:發送數據報的網絡接口結構指針
//參數 dest:目的 IP 地址
err_t ip_frag(struct pbuf *p, struct netif *netif, struct ip_addr *dest)
{
  struct pbuf *rambuf; //分片的 pbuf 結構
  struct pbuf *header; //以太網幀 pbuf
  struct ip_hdr *iphdr; //IP 首部指針
  u16_t nfb; //分片中允許的最大數據量
  u16_t left, cop; //待發送數據長度和當前發送的數據長度
  u16_t mtu = netif­>mtu; //網絡接口 MTU
  u16_t ofo, omf; //分片偏移量和更多分片位
  u16_t last; //是否為最后一個分片
  u16_t poff = IP_HLEN; //發送的數據在原始數據報 pbuf 中的偏移量
  u16_t tmp;
  rambuf = pbuf_alloc(PBUF_LINK, 0, PBUF_REF); //為數據分片申請一個 pbuf 結構
  if (rambuf == NULL) { //申請失敗,則返回
    return ERR_MEM;
  }
  rambuf­>tot_len = rambuf­>len = mtu; //設置 pbuf 的 len 和 tot_len 字段為接口的 MTU 值
  rambuf­>payload = LWIP_MEM_ALIGN((void *)buf); //payload 指向全局數據區域
  iphdr = rambuf­>payload; //得到分片包存儲區域
  SMEMCPY(iphdr, p­>payload, IP_HLEN); //把原始數據報首部拷貝到分片的首部
  tmp = ntohs(IPH_OFFSET(iphdr)); //暫存分片的相關字段
  ofo = tmp & IP_OFFMASK; //得到分片偏移量(對原始數據報來說應該為 0)
  omf = tmp & IP_MF; //得到更多分片標志值
  left = p­>tot_len ­ IP_HLEN; //待發送數據長度(總長度­IP 首部長度)
  nfb = (mtu ­ IP_HLEN) / 8; //一個分片中可以存放的最大數據量(8 字節為單位)
  while (left) { //待發送數據長度大於 0
    last = (left <= mtu ­ IP_HLEN); //若待發送長度小於分片最大長度,last 為 1,否則為 0
    tmp = omf | (IP_OFFMASK & (ofo)); //計算分片相關字段
    if (!last) //如果不是最后一個分片
      tmp = tmp | IP_MF; //則更多分片位置 1
    //計算分片中的數據長度,當 last 為 1 時,說明分片能裝下所有剩余數據
    cop = last ? left : nfb * 8;
    //從原始數據報中拷貝 cop 字節的數據到分片中,poff 記錄了拷貝起始位置
    poff += pbuf_copy_partial(p, (u8_t*)iphdr + IP_HLEN, cop, poff);
    //填寫分片首部中的其他字段
    IPH_OFFSET_SET(iphdr, htons(tmp)); //填寫分片相關字段
    IPH_LEN_SET(iphdr, htons(cop + IP_HLEN)); //填寫總長度
    IPH_CHKSUM_SET(iphdr, 0//清 0 校驗和
    IPH_CHKSUM_SET(iphdr, inet_chksum(iphdr, IP_HLEN)); //計算校驗和
    if (last) //若為最后一個分片,則更改 pbuf 的 len 和 tot_len 字段
      pbuf_realloc(rambuf, left + IP_HLEN);
    //到這里,我們組裝好了一個完整的分片,需要將該分片發送出去,這里重新在
    //內存堆中開辟一個 pbuf 空間,用來保存以太網幀首部
    header = pbuf_alloc(PBUF_LINK, 0, PBUF_RAM);
    if (header != NULL) { //申請成功,則做發送工作
      pbuf_chain(header, rambuf); //將兩個 pbuf 連接成一個 pbuf 鏈表
      netif­>output(netif, header, dest); //調用函數發送
      pbuf_free(header); //發送完成后,釋放 pbuf 鏈表
    } else { //若以太網首部空間申請失敗
      pbuf_free(rambuf); //釋放分片空間
      return ERR_MEM; //返回內存錯誤
    }
    left ­= cop; //待發送的總長度減少
    ofo += nfb; //分片偏移量增加
  }//while
  pbuf_free(rambuf); //釋放結構 rambuf
  return ERR_OK;
}
——————————————————————————————————

四、IP層輸入

1、數據報接收

2、分片重裝數據結構

3、分片重裝函數

4、分片插入與檢查


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM