我又來了,這篇文章還是來做(da)推(guang)介(gao)我自己的!俗話說事不過三,我覺得我下次得換個說法了,不然估計要被厭惡了,但是我是好心吶,一定要相信我純潔的眼神。由於這兩年接觸到了比較多的這方面的知識,不想忘了,我決定把他們記錄下來,所以決定在GitBook用半年時間上面寫下來,這是目前寫的一節,后面會在gitbook上不斷更新,歡迎大家star,主要是在寫完之前歡迎各位給出指正的意見。最最重要的,地址在這里:https://rogerzhu.gitbooks.io/-tcp-udp-ip/content/,或者在gitbook上搜索“三十天學不會TCP,UDP/IP編程”。
這是第一次在這一系列文章里面詳細介紹一個數據包格式,在計算機網絡編程上面,數據包格式可能是接觸的最多的一個概念了。如果你要實現一個什么協議,第一件事應該就是搞清楚數據格式,我的做法一般都是先看一遍數據包的文檔,在紙上畫一遍數據包,然后再打開wireshark,對着抓到的包多鼓搗幾遍一般就差不多能掌握了。
IPv4頭數據包
一個IPv4數據包格式如下所示(傾情獻上我的手繪版):

除去最后的data部分就是一個IP頭數據包了,那么就從最低位開始一一開始詳細介紹,下面的單位都是位,不是字節:
基本信息
0-3: 版本,標識了IP頭的版本,IPv4里面自然這個值是4(0100)。
4-7: IP頭的長度,以32bit word為單位,一共四位,所以最大長度是15*32bit,也就是60個字節,注意這是頭的長度,而不是IP數據包最大的長度。按照圖中的格式,去掉Data和If any的option部分,可以一共數出來是5行,也就是5個32bit長度,那么可以得出IP頭最少也要20個字節,所以這個字段最小值是5,小於這個值都是不合法的。
8-15: TOS,全稱Type of service, 這個字段是根據有些特殊的網絡狀況的不同來定義想IP層提供怎樣的服務的。比如在有的網絡上希望有的數據包可以優先得到傳輸,因為他們可能含有相對於其他數據包來說,比較重要的信息。這三個字段來完成這一服務的本質就是在三個方面提供一個平衡,這三個方面就是,低延遲,高可靠性和高吞吐量。具體來說可以將這個8位分為5個部分:
-
8-10: 優先級
-
11: 1代表開啟低延遲模式,0代表關閉
-
12: 1代表開啟高吞吐量模式,0代表關閉
-
13: 1代表開啟高可靠性模式,0代表關閉
-
14-15: 保留位
優先級的仍然可以展開成為8個不同的級別,不過一般這種單純羅列的東西超過三層就基本上會產生發散注意力的尿性,所以就不展開了,只要能記住這個3位用來表示數據包的不同的重要性或優先級就行了。后面三位就是從三個不同的角度來促使某個數據包能夠因為其重要性最先被處理。但是在大多數網絡中,真的要同設置了三位,那么反而會導致更差的后果。因為在很多情況下,這三個本身就是魚和熊掌不可兼得的,所以一般只需要選擇一個就好,很少的情況下需要打開兩個。
16-31: 總長度字段,總長度是以字節(8 bit)為單位的,包括頭部長度,所以說一個IP數據包最長是65,535個字節。不過一般這么長的數據包一般都是不切合實際,所以說在最初的標准里推薦的是不超過576個字節,包括頭部的長度。
標識與分片
32-47:標識符,用來標識和歸組數據包,為什么要歸組呢?因為如果一個數據包比較大,往往就需要分片,分片簡單的說就是把一個大的IP數據包分成好幾包小的,為了標哪些是哪一個大包的組成部分,就需要一個標識符來組織他們,這樣對端才能把分開的數據包重新組合成為一個整的數據包。但是可以看到,這個字段只有16個bits的長度,所以標識最多只可能有65536個不同的數,在現在一個數據包可能以十萬百萬計的網絡通信上,就會出現一個問題,標識符在循環了一圈之后又要出現重復的數啦。這個問題咋辦呢?答案是簡單的丟棄數據包並且發送標識錯誤的信息給發送端。
48-50:分片標識,上面說了如果一個IP數據包過大,就需要分片,那么這個3個bit就用來表示該數據包的分片情況。其中
-
48: 保留位,必須為0
-
49: 0表示是分片的數據包,1表示不是分片的數據包
-
50: 0表示是一組分片數據包的最后一片,1表示后面還有分片的數據包
總結一下,其實就是這三位如果是000,就是表示這只是一組分片數據包中間的一片,如果是001就表示這是最后一片了,IP層可以根據需要組裝數據包交給上一層,只要是01,后面以為就可以忽略了,因為這是一片完整的數據包。
51-63:偏移量,這個是配合標識符字段使用的,標識出改數據包在整個分片數據包的位置,這樣才能讓IP層按照正確的順序拼湊出數據包。
生存和上層信息
64-71: TTL,數據包還剩的生存時間,這樣翻譯感覺有點挫,一般這個名詞都不會被翻譯,直接用英文的原文Time to live。這個值最初被設計出來的時候是以秒作為單位的,也就是該數據包還能存活多少秒,如果這個字段的值到0了,那么這個數據包就得消失了並且向發送端發送能標識錯誤的消息。但是由於現在路由器的處理時間和發送時間都遠遠小於1秒,所以這個慢慢的就被用作標識還能經過多少個路由器轉發,俗稱跳數,現在一般都標為128。但是在一些特殊的測試目的的場景下,我們就可以利用這個字段來測試出發送主機和目的主機之間的路徑信息。
72-79:協議,就是標識這個IP數據包的data是啥協議,常見的比如TCP,UDP。
80-95:頭部效驗和,這是使用CRC算法來檢測頭部,注意,僅僅時檢測頭部信息有沒有錯,如果有錯那么該數據包將不得通過。
地址信息
96-127: 發送端IP地址,32位。
128-159:接收端IP地址,32位。
選項(如果有)
這個選項字段是IP層數據包為了配合一些其他協議的發送或者應答而應運而生的,如果只是單純的列出有哪些選項保證就會被滾輪滾過去,而且大部分是根本不會遇到的,所以我決定再介紹ICMP的時候再介紹我能接觸到的。
如何記憶數據包格式
我覺得這是如果涉及網絡編程的一個難題,因為有太多協議了,不可能把所有數據包的格式都記住,但是這也不代表啥數據包格式都記不住。在我看來,最基礎的比如IPv4的頭數據包格式,TCP的格式,UDP的格式,ICMP,ARP大概的樣式都應該記住,雖然說這些玩意兒一百度就能百度出來,但是我覺得在面試時這些都是能反應你熟練程度的一個參考吧。
經過我的幾次探索,我覺得我找到了一條適合我的方法,就是用筆畫一畫。我覺得計算機一個最大的弊端就是讓人們忘了筆和紙的作用,很多時候動動手真的能加深大腦的記憶,雖然我也不知為什么。第二個就是分類,比如這個IP頭吧,我會把他主要分為上面黑體字的那幾類,結合着幾類,其實再加一些簡單的邏輯推理就不難記住這些。比如基本信息,基本信息嘛,總要有長度,版本號嘛,再對照書一看,真有,但是少了一個TOS,沒關系,這是特殊的功能,不在人腦一般認知的基本信息之內,這一次的miss hit就可以讓你記住有一個TOS,強化了記憶。
抓個活物來看看
在對這個包頭格式有一定了解的基礎上就可以找一個真實的IP數據包看看,一是加深印象,二是看看隨着年代的發展,標准里會不會有坑。我還是用哪個SMB數據包來觀察一下:

展開IP這一層的信息,wireshark已經把一串二進制翻譯成了人眼可見的信息,當然,你要是有興趣也可以在二進制信息里自己尋找和發掘。我感覺這東西和考古差不多,在一堆一眼看上去差不多的東西里面發現自己需要的,偶爾弄弄還是很有成就感的,搞多了傷眼睛。
對比第一個部分介紹的,可以看到這個IP頭是20個字節,也就最小長度,沒有選項。有一個天藍色的字段上面貌似沒有找到,但其實這就是TOS字段,有興趣可以搜一下這個名詞,還是很復雜的。不過因為是0,就認為啥也沒有發生就行。還可以看到標識符,這個數據包並不分片,是一個整體。還有就是這個IP數據包里所蘊含的是一個TCP的數據,也可以看到兩個IP地址。
所以說,至少在相信wireshark的情況下,標准並沒有坑我,或者說SMB發送的IP包是符合標准的額,我們可元氣滿滿的額進入下一個話題了。
