一、相關知識
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 個主機(2162)。使用 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。
這也是為什么在計算表 101 中的網絡最大主機數時,需要減 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 類地址。
下面講的這幾個專用地址卻可以被分配給多個主機,當然這些主機之間應該互不相關,即處於互相獨立的專用網內,例如目前很流行的以太網局域網。在每一類地址中部分地址為專用地址,如表 102 所示。
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.21,5678,130.21.45.20,80)
服務器返回:(130.21.45.20,80,222.178.197.21,5678)
NAT轉換:(130.21.45.20,80,192.168.1.78,1234)
這樣,經過 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 數據報有自己的組織格式,如圖 104 所示,它通常由兩部分組成,即 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、分片插入與檢查