由於最近要解析 TCP 報文中 option 段的一塊數據,因此不得不詳細瞭解下 TCP/IP 報文。雖然以前看過,很長時間沒這麼細緻地用過,致使了健忘,藉着這個機會,經過 tcpdump 抓包分析,詳細捋一遍 TCP/IP 報文。html
若是那樣乾巴巴地講這個東西比較暈,並且網上的文章一大堆,沒有什麼創新。我選擇換一個角度來切入 TCP/IP 協議。首先經過 tcpdump 準備報文。
【1】我在 192.168.1.22
這臺機器的 10000
端口啓一個 redis
服務。
【2】經過 tcpdump 這個工具來抓取數據包,命令以下:python
tcpdump -w /tmp/logs -i eth0 port 10000 -s0
【3】在 192.168.1.26
這臺機器上訪問 192.168.1.22:10000
這個 redis 實例,能夠用 redis-cli
客戶端,也能夠用 telnet
,發送一個 ping
, 獲得對端回覆 pong
。
【4】中止抓包,用 tcpdump 讀取這個數據包(-x
以16進制形式展現,便於後面分析)linux
tcpdump -r /tmp/logs -n -nn -A -x| vim -
其中有一個數據包是這樣的,這也是這篇文章要分析的:git
10:54:54.270967 IP 192.168.1.26.61096 > 192.168.1.22.10000: Flags [P.], seq 1041414875:1041414889, ack 658186233, win 115, options [nop,nop,TS val 2377448931 ecr 2741547141], length 14 0x0000: [4560 0042 7567 0000 3d06 6F3C C0A8 011A 0x0010: C0A8 0116] {eea8 2710 3e12 badb 273b 1ff9 0x0020: 8018 0073 64b0 0000 0101 080a 8db4 fde3 0x0030: a368 b085} 2a31 0d0a 2434 0d0a 7069 6e67 0x0040: 0d0a
注意:
【1】以前在文章經常使用 shell 中介紹過抓包神器 tcpdump,還不會的小夥伴能夠偷瞄一眼。
【2】上面報文數據中的 [
、]
、{
和 }
是爲了方便區分數據,我本身加上的。[]
包圍的部分爲本報文中的 IP 頭,{}
包圍的部分爲本報文中的 TCP 頭。github
IP 報文總體結構以下,由於抓到的數據包是 redis
服務,所以在傳輸層爲 TCP 協議。redis
解析數據包以前,先把 IP 協議拿出來,以下:
能夠看到,IP 報文頭部採用固定長度(20B) + 可變長度
構成,下面的 TCP 頭部也是這樣。
而後下面對着抓到的數據包進行分析:
【1】0x4
4bit, ip 協議版本0x4
表示 IPv4。
【2】0x5
4bit,ip首部長度
該字段表示單位是32bits(4字節) ,因此這個 ip 包的頭部有 5*4=20B
,這就能夠推出,該 IP 報文頭沒有可選字段。4bit 能夠表示最大的數爲 0xF,所以,IP 頭部的最大長度爲 15*4=60B
。該報文的 IP 頭部我已經在報文中標註出來了。
【3】0x60
8bit,服務類型 TOS
該段數據組成爲 3bit 優先權字段(現已被忽略) + 4bit TOS 字段 + 1bit 保留字段(須爲0)。
4bit TOS 字段分別表示自小時延、最大吞吐量、最高可用性和最小費用。只能置其中 1bit,全爲 0 表示通常服務。如今大多數的TCP/IP實現都不支持TOS特性 。能夠看到,本報文 TOS 字段爲全 0。
【4】0x0042
16bit, IP 報文總長度
單位字節,換算下來,該數據報的長度爲 66 字節,數一下上面的報文,剛好 66B。
從佔位數來算, IP 數據報最長爲 2^16=65535B
,但大部分網絡的鏈路層 MTU(最大傳輸單元)沒有這麼大,一些上層協議或主機也不會接受這麼大的,故超長 IP 數據報在傳輸時會被分片。
【5】0x7567
16bit,標識
惟一的標識主機發送的每個數據報。一般每發送一個報文,它的值+1。當 IP 報文分片時,該標識字段值被複制到全部數據分片的標識字段中,使得這些分片在達到最終目的地時能夠依照標識字段的內容從新組成原先的數據。
【6】0x0000
3bit 標誌 + 13bit 片偏移
3bit 標誌對應 R、DF、MF。目前只有後兩位有效,DF位:爲1表示不分片,爲0表示分片。MF:爲1表示「更多的片」,爲0表示這是最後一片。
13bit 片位移:本分片在原先數據報文中相對首位的偏移位。(須要再乘以8)
【7】0x3d
8bit 生存時間TTL
IP 報文所容許經過的路由器的最大數量。每通過一個路由器,TTL減1,當爲 0 時,路由器將該數據報丟棄。TTL 字段是由發送端初始設置一個 8 bit字段.推薦的初始值由分配數字 RFC 指定。發送 ICMP 回顯應答時常常把 TTL 設爲最大值 255。TTL能夠防止數據報陷入路由循環。本報文該值爲 61。
【8】0x06
8bit 協議
指出 IP 報文攜帶的數據使用的是哪一種協議,以便目的主機的IP層能知道要將數據報上交到哪一個進程。TCP 的協議號爲6,UDP 的協議號爲17。ICMP 的協議號爲1,IGMP 的協議號爲2。該 IP 報文攜帶的數據使用 TCP 協議,獲得了驗證。
【9】0x6F3C
16bit IP 首部校驗和
由發送端填充。以本報文爲例,先說這個值是怎麼計算出來的。算法
# 將校驗和字段 16bit 值抹去變爲 `0x0000`,而後將首部 20字節值相加 0x4560 + 0x0042 + 0x7567 + 0x0000 + 0x3d06 + 0x0000 + 0xC0A8 + 0x011A + 0xC0A8 +0x0116 = 0x27B95 # 將上述結果的進位 2 與低 16bit 相加 0x7B95 + 0x2 = 0x7B97 # 0x7B97 按位取反 ~(0x7B97) = 0x8468
結果 0x8468
即爲該字段值!
接收端驗證的時候,進行如下計算shell
# 20B 首部值相加 0x27B95 + 0x8468 = 0x2FFFD # 將上述結果的進位 2 與低 16bit 相加 0xFFFD + 0x2 = 0xFFFF # 0xFFFF 按位取反 ~(0xFFFF) = 0 <-- 正確
【10】0xC0A8011A
32bit 源地址
能夠經過一下 python 程序將 hex 轉換成咱們熟悉的點分 IP 表示法vim
>>> import socket >>> import struct >>> int_ip=int("0xC0A8011A",16) >>> socket.inet_ntoa(struct.pack('I',socket.htonl(int_ip))) '192.168.1.26'
本報文中的 src addr 爲 192.168.1.26
,剛好就是發起請求的 IP。
【11】0xC0A80116
32bit 目的地址
通過計算爲 192.168.1.22
,剛好就是啓 redis 服務那臺機器的 IP。網絡
因爲該報文首部長度爲 20B,所以沒有可變長部分。
本報文攜帶的數據使用的 TCP 協議,所以下面開始分析 TCP 協議。
與上面的 IP 報文同樣, TCP 報文頭也才用採用固定長度(20B) + 可變長度
的形式。
首先仍是看 TCP 協議的格式,從網上找了一張圖,以下:
注: TCP 的頭部必須是 4字節的倍數,而大多數選項不是4字節倍數,不足的用 NOP
填充。
【1】0xeea8
16bit,源端口
解析獲得 61096,這與 tcpdump 讀包顯示的是一致的。16bit 決定了端口號的最大值爲 65535.
【2】0x2710
16bit,目的端口
解析獲得 10000。
【3】0x273b1ff9
32bit,序號
解析獲得 1041414875,這與上面 tcpdump 顯示的 seq 段是一致的。
【4】0x273b1ff9
32bit,確認號
解析獲得 658186233,這與上面 tcpdump 顯示的 ack 段是一致的。
【5】0x8
4bit,TCP 報文首部長度
也叫 offset,其實也就是數據從哪裏開始。8 * 4 = 32B
,所以該 TCP 報文的可選部分長度爲 32 - 20 = 12B
,這個資源仍是很緊張的! 同 IP 頭部相似,最大長度爲 60B
。
【6】0b000000
6bit, 保留位
保留爲從此使用,但目前應置爲 0。
【7】0b011000
6bit,TCP 標誌位
上圖能夠看到,從左到右依次是緊急 URG、確認 ACK、推送 PSH、復位 RST、同步 SYN 、終止 FIN。
從抓包能夠看出,該報文是帶了 ack 的,因此 ACK 標誌位置爲 1。關於標誌位的知識這裏就不展開了。
【8】0x0073
16bit,滑動窗口大小
解析獲得十進制 115,跟 tcpdump 解析的 win 字段一致。
【9】0x64b0
16bit,校驗和
由發送端填充,接收端對 TCP 報文段執行 CRC 算法,以檢驗 TCP 報文段在傳輸過程當中是否損壞,若是損壞這丟棄。
檢驗範圍包括首部和數據兩部分,這也是 TCP 可靠傳輸的一個重要保障。
【10】0x0000
16bit,緊急指針
僅在 URG = 1 時纔有意義,它指出本報文段中的緊急數據的字節數。
當 URG = 1 時,發送方 TCP 就把緊急數據插入到本報文段數據的最前面,而在緊急數據後面的數據還是普通數據。
下面是 TCP 可選項,其格式以下:
常見的可選項以下圖:
【11】0x01
NOP 填充,沒有 Length 和 Value 字段, 用於將TCP Header的長度補齊至 32bit 的倍數。
【12】0x01
同上。
【13】0x080a
可選項類型爲時間戳,len爲 10B,value 爲0x8db4 0xfde3 0xa368 0xb085
,加上 0x080a
,剛好 10B!
啓用 Timestamp Option後,該字段包含2 個 32bit 的Timestamp(TSval 和 TSecr)。
【14】0x8db4 0xfde3
解析後獲得 2377448931,剛好與 tcpdump 解析到的 TS 字段的 val一致!
【15】0xa368 0xb085
解析後獲得 2741547141,剛好與 tcpdump 解析到的 TS 字段的 ecr一致!
上面分析得知,該 IP 報文長度爲 66B,IP 頭長度爲 20B,TCP 頭部長度爲 32B,所以獲得數據的長度爲 66 - 20 - 32 = 14B
,這與 tcpdump 解析到的 len 字段一致!下面來分析這個具體的數據。
這裏涉及到 redis 協議,不知道的小夥伴能夠查看這篇文檔redis 協議說明。
在抓包時,用客戶端向 redis 服務端發送了一個 ping
命令,轉換成 redis 協議以下:
*1\r\n $4\r\n ping\r\n
下面看抓包數據解析,這須要對照 ascii 碼錶來看,在 linux 下能夠用 man 7 ascii
這個命令來得到,或者在這裏查看ascii碼錶。
0x2a31 -> *1 0x0d0a -> \r\n 0x2434 -> $4 0x0d0a -> \r\n 0x7069 0x6e67 -> ping 0x0d0a -> \r\n
既然詳細說到 TCP/IP 協議,那補充一下 tcpdump filter 的幾點用法。
filter能夠簡單地分爲三類:type
, dir
和 proto
。
type 區分報文的類型,主要由 host(主機), net(網絡,支持 CIDR) 和 port(支持範圍,如 portrange 21-23) 組成。
dir 區分方向,主要由 src 和 dst 組成。
proto 區分協議支持 tcp、udp 、icmp 等。
下面說幾個 filter 表達式。proto[x:y]
start at offset x into the proto header and read y bytes[x]
abbreviation for [x:1]
注意:單位是字節,不是位!
舉幾個栗子:
【1】打印 80 端口,有數據的 tcp 包
tcpdump 'tcp port 80 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)'
ip[2:2]
從 ip 報文的第3個字節開始讀2個字節,這個剛好就是 ip 包的總長度,單位是字節ip[0]&0xf
取的是 ip 報文第 1 個字節的低 4 位,<< 2
(乘以 4),爲 ip 頭部長度,單位是字節tcp[12]&0xf0
取的是 tcp 報文第 13 個字節的高 4 位,>> 2
其實等價於 >> 4
而後 << 2
,爲 tcp 頭部長度,單位是字節。
因此 ((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2))
表示的數據長度。
【2】打印 80 端口,長度超過 576 的 ip 包
tcpdump 'port 80 and ip[2:2] > 576'
【3】打印特定 TCP Flag 的數據包
TCP Flags 在 tcpdump 抓取的報文中的體現:[S]
:SYN(開始鏈接)[.]
: 沒有 Flag[P]
: PSH(推送數據)[F]
: FIN (結束鏈接)[R]
: RST(重置鏈接)[S.]
SYN-ACK,就是 SYN 報文的應答報文。
tcpdump 'tcp[13] & 16!=0' # 等價於 tcpdump 'tcp[tcpflags] == tcp-ack'
打印出全部的 ACK 包。
tcpdump 'tcp[13] & 4!=0' # 等價於 tcpdump 'tcp[tcpflags] == tcp-rst'
打印出全部的 RST 包,即包含 [R]
標誌的包。
更多 tcpdump filter 能夠查看 PCAP-FILTER 或者 man tcpdump
!
好了,這個 IP 包的解析就到此爲止了,照着 TCP/IP 協議分析了一遍, 發現協議也就那麼回事兒,沒有想象的那麼難,不要懼怕協議!
【1】經常使用的TCP Option
【2】IP報文格式詳解
【3】TCP 報文結構