Linux網路協議棧-TCPIP協議報文格式解析(內含程式碼演示)

前言:

你是否曾有以下苦惱:在程式設計時雖然會呼叫網路方面的 API,卻不清楚具體原理;會用基本的 ifconfig 等命令,卻不太理解其輸出;對於長長的 MAC 地址、IP 地址和子網掩碼,不瞭解它們的分配機理;看了網上對路由器、交換機、TCP、UDP、DHCP、DNS 等知識點的介紹,卻依舊迷茫。不必擔心,你將會循序漸進地牢牢掌握網路方面的知識。圍繞 TCP/IP 協議的網路知識非常重要,我們今天使用電腦手機上網聊天、遊戲、購物、追劇等,其實都在不自覺地使用 TCP/IP 協議。想要成為程式設計方面的專家,除了資料結構、演算法、設計模式等知識,網路方面的知識也不可或缺。

一、傳輸層報文

1、TCP資料包的頭

typedef struct _TCP_HEADER { USHORT nSourPort ; // 源埠號16bit USHORT nDestPort ; // 目的埠號16bit UINT nSequNum ; // 序列號32bit UINT nAcknowledgeNum ; // 確認號32bit USHORT nHLenAndFlag ; // 前4位:TCP頭長度;中6位:保留;後6位:標誌位16bit USHORT nWindowSize ; // 視窗大小16bit USHORT nCheckSum ; // 檢驗和16bit USHORT nrgentPointer ; // 緊急資料偏移量16bit} TCP_HEADER, *PTCP_HEADER ;

2、UDP資料包的頭

typedef struct _UDP_HEADER { USHORT nSourPort ; // 源埠號16bit USHORT nDestPort ; // 目的埠號16bit USHORT nLength ; // 資料包長度16bit USHORT nCheckSum ; // 校驗和16bit} UDP_HEADER, *PUDP_HEADER ;

進入協議棧的過程:(從協議棧出來剛好相反)

二、網路層報文

1、IP頭

IP資料包也叫IP報文分組,傳輸在ISO網路7層結構中的網路層,它由IP報文頭和IP報文使用者資料組成,IP報文頭的長度一般在20到60個位元組之間,而一個IP分組的最大長度則不能超過65535個位元組。

下圖為IP分組的報文頭格式,報文頭的前20個位元組是固定的,後面的可變

版本號 : 4 個 bit ,用來標識 IP 版本號。這個 4 位欄位的值設定為二進位制的 0100 表示 IPv4 ,設定為 0110 表示 IPv6 。目前使用的 IP 協議版本號是 4 。

首部長度 : 4 個 bit 。標識包括選項在內的 IP 頭部欄位的長度。

服務型別 : 8 個 bit 。服務型別欄位被劃分成兩個子欄位: 3bit 的優先順序欄位和 4bit TOS 欄位,最後一位置為 0 。 4bit 的 TOS 分別代表:最小時延,最大吞吐量,最高可靠性和最小花費。 4bit 中只能將其中一個 bit 位置 1 。如果 4 個 bit 均為 0 ,則代表一般服務。

Linux網路協議棧-TCP/IP協議報文格式解析(內含程式碼演示)

Linux網路協議棧-TCP/IP協議報文格式解析(內含程式碼演示)

現在大多數的 TCP/IP 實現都不支援 TOS 特性,但自 4。3BSD Reno 以後的新版系統都對它進行了設定。另外, OSPF 和 IS-IS 都可以根據這些欄位的值進行路由策略。而類似 SLIP 這樣的協議雖然提供基於服務型別的排隊方法,允許對互動型資料優先進行處理,但它的這種排隊機制由 SLIP 自身來判斷和處理。驅動程式會先檢視協議欄位(確定是否是 TCP 段),然後檢查 TCP 信源和信宿的埠號來判斷是否是一個互動服務。

最近, TOS 欄位已經作為區分服務( Diffserv )架構的一部分被重新定義了。 Diffserv 比 TOS 定義所允許的處理更加靈活。在 Diffserv 下,能夠在一臺路由器上定義服務分類( COS ),將資料包歸類到這些分類中去。路由器可以根據它們的分類使用不同的優先順序對資料包進行轉發。每一個排序和轉發處理稱為一個 PHB 。這個架構也被簡稱為 COS 。

Linux網路協議棧-TCP/IP協議報文格式解析(內含程式碼演示)

利用開始的 6 個位構成 DSCP 位,可以使用任意數值或根據區分服務體系結構中預先定義的服務類別,最多可以定義 64 個不同的服務類別並整理到 PHB 中。 ECN 為顯式擁塞通知位。當路由器支援該特性時,這些位可以用於擁塞訊號( ECN=11 )。

總長度欄位 :

16 個 bit 。接收者用 IP 資料報總長度減去 IP 報頭長度就可以確定資料包資料有效負荷的大小。 IP 資料報最長可達 65535 位元組。

標識欄位 :

16 個 bit 。唯一的標識主機發送的每一份資料報。接收方根據分片中的標識欄位是否相同來判斷這些分片是否是同一個資料報的分片,從而進行分片的重組。通常每傳送一份報文它的值就會加 1 。

標誌欄位 :

3 個 bit 。用於標識資料報是否分片。第 1 位沒有使用,第 2 位是不分段( DF )位。當 DF 位被設定為 1 時,表示路由器不能對資料包進行分段處理。如果資料包由於不能分段而未能被轉發,那麼路由器將丟棄該資料包並向源傳送 ICMP 不可達。第 3 位是分段( MF )位。當路由器對資料包進行分段時,除了最後一個分段的 MF 位被設定為 0 外,其他的分段的 MF 位均設定為 1 ,以便接收者直到收到 MF 位為 0 的分片為止。

位偏移 :

13 個 bit 。在接收方進行資料報重組時用來標識分片的順序。用於指明分段起始點相對於報頭起始點的偏移量。由於分段到達時可能錯序,所以位偏移欄位可以使接收者按照正確的順序重組資料包。當資料包的長度超過它所要去的那個資料鏈路的MTU時,路由器要將它分片。資料包中的資料將被分成小片,每一片被封裝在獨立的資料包中。接收端使用識別符號,分段偏移以及標記域的MF位來進行重組。

生存時間 :

8個bit 。 TTL域防止丟失的資料包在無休止的傳播。該域包含一個 8 位整數,此數由產生資料包的主機設定。 TTL 值設定了資料包可以經過得最多的路由器數。 TTL的初始值由源主機設定(通常為 32 或 64 ),每經過一個處理它的路由器, TTL 值減 1 。如果一臺路由器將 TTL 減至 0 ,它將丟棄該資料包併發送一個 ICMP超時訊息給資料包的源地址。注意: TTL值經過PIX時不減1。

協議欄位 :

8 個 bit 。用來標識是哪個協議向 IP 傳送資料。 ICMP 為 1 , IGMP 為 2 , TCP 為 6 , UDP 為 17 , GRE 為 47 , ESP 為 50 。

首部校驗和 :

根據 IP 首部計算的校驗和碼。

源IP地址:

32位(bit),4個位元組,每一個位元組為0~255之間的整數,及我們日常見到的IP地址格式。

目的IP地址:

32位(bit),4個位元組,每一個位元組為0~255之間的整數,及我們日常見到的IP地址格式。

Option 選項 :

是資料報中的一個可變長的可選資訊。

Linux網路協議棧-TCP/IP協議報文格式解析(內含程式碼演示)

選項欄位以32bit 為界,不足時插入值為0填充位元組。保證IP首部始終是 32bit 的整數倍。由於Delphi裡面沒有位域這個概念,所以定義結構的時候只能整位元組了,挺懷戀C++或者Erlang的,有位域定義出來和使用起來都很方便了 。

//IP包 TIPHeader = packed record iph_verlen: byte; // 版本和長度 iph_tos: byte; // 服務型別 iph_length: word; // 總長度,2個無符號位元組所以只能65535 iph_id: word; // 標識 iph_offset: word; // 標誌和片偏移 iph_ttl: byte; // 生存時間 iph_protocol: byte; // 協議 iph_xsum: word; // 頭校驗和 iph_src: longword; // 源地址 iph_dest: longword; // 目的地址 end;

這個結構體有什麼用呢?其實在嗅探的時候就很有用了。

2、ICMP頭和報文校驗和的計算

//定義ICMP包頭typedef struct _ICMP_HEADER { BYTE bType ; // 型別8bit BYTE bCode ; // 程式碼8bit USHORT nCheckSum ; // 校驗和16bit USHORT nId ; // 標識,本程序ID16bit USHORT nSequence ; // 序列號16bit UINT nTimeStamp ; // 可選項,這裡為時間,用於計算時間32bit} ICMP_HEADER, *PICMP_HEADER ;

送ICMP報文時,必須由程式自己計算校驗和,將它填入ICMP頭部對應的域中。校驗和的計算方法是:

將資料以字(16位)為單位累加到一個雙字中(強轉換雙字型別),如果資料長度為奇數(奇數個位元組),最後一個位元組將被擴充套件到字,累加的結果是一個雙字,最後將這個雙字的高16位和低16位相加後取反,便得到了校驗和。

// 計算ICMP包校驗值// 引數1:ICMP包緩衝區// 引數2:ICMP包長度USHORT GetCheckSum ( LPBYTE lpBuf, DWORD dwSize ){ DWORD dwCheckSum = 0 ; USHORT* lpWord = (USHORT*)lpBuf ; // 累加 while ( dwSize > 1 ) { dwCheckSum += *lpWord++ ; dwSize -= 2 ; } // 如果長度是奇數 if ( dwSize == 1 ) dwCheckSum += *((LPBYTE)lpWord) ; // 高16位和低16位相加 dwCheckSum = ( dwCheckSum >> 16 ) + ( dwCheckSum & 0xFFFF ) ; // 取反 return (USHORT)(~dwCheckSum ) ;}

Linux網路協議棧-TCP/IP協議報文格式解析(內含程式碼演示)