從字面上來看,很多人會(huì)認(rèn)為 TCP/IP 是 TCP、IP 這兩種協(xié)議,實(shí)際上TCP/IP 協(xié)議族指的是在 IP 協(xié)議通信過程中用到的協(xié)議的統(tǒng)稱
前言
可以看到協(xié)議的分層從上往下依次是
- Ethernet II:網(wǎng)絡(luò)接口層以太網(wǎng)幀頭部信息
- Internet Protocol Version 4:互聯(lián)網(wǎng)層 IP 包頭部信息
- Transmission Control Protocol:傳輸層的數(shù)據(jù)段頭部信息,此處是 TCP 協(xié)議
- Hypertext Transfer Protocol:應(yīng)用層 HTTP 的信息
網(wǎng)絡(luò)分層
應(yīng)用層(Application Layer)
應(yīng)用層的本質(zhì)是規(guī)定了應(yīng)用程序之間如何相互傳遞報(bào)文, 以 HTTP 協(xié)議為例,它規(guī)定了:
- 報(bào)文的類型,是請(qǐng)求報(bào)文還是響應(yīng)報(bào)文
- 報(bào)文的語法,報(bào)文分為幾段,各段是什么含義、用什么分隔,每個(gè)部分的每個(gè)字段什么什么含義
- 進(jìn)程應(yīng)該以什么樣的時(shí)序發(fā)送報(bào)文和處理響應(yīng)報(bào)文
HTTP 客戶端和 HTTP 服務(wù)端的首要工作就是根據(jù) HTTP 協(xié)議的標(biāo)準(zhǔn)組裝和解析 HTTP 數(shù)據(jù)包,每個(gè) HTTP 報(bào)文格式由三部分組成:
- 起始行(start line),起始行根據(jù)是請(qǐng)求報(bào)文還是響應(yīng)報(bào)文分為「請(qǐng)求行」和「響應(yīng)行」。這個(gè)例子中起始行是GET / HTTP/1.1,表示這是一個(gè) GET 請(qǐng)求,請(qǐng)求的 URL 為/,協(xié)議版本為HTTP 1.1,起始行最后會(huì)有一個(gè)空行CRLF(rn)與下面的首部分隔開
- 首部(header),首部采用形如key:value的方式,比如常見的User-Agent、ETag、Content-Length都屬于 HTTP 首部,每個(gè)首部直接也是用空行分隔
- 可選的實(shí)體(entity),實(shí)體是 HTTP 真正要傳輸?shù)膬?nèi)容,比如下載一個(gè)圖片文件,傳輸?shù)囊欢?HTML等
以本例的請(qǐng)求報(bào)文格式為例:
除了我們熟知的 HTTP 協(xié)議,還有下面這些非常常用的應(yīng)用層協(xié)議
- 域名解析協(xié)議 DNS
- 收發(fā)郵件 SMTP 和 POP3 協(xié)議
- 時(shí)鐘同步協(xié)議 NTP
- 網(wǎng)絡(luò)文件共享協(xié)議 NFS
傳輸層(Transport Layer)
傳輸層的作用是為兩臺(tái)主機(jī)之間的「應(yīng)用進(jìn)程」提供端到端的邏輯通信,相隔幾千公里的兩臺(tái)主機(jī)的進(jìn)程就好像在直接通信一樣。
雖然是叫傳輸層,但是并不是將數(shù)據(jù)包從一臺(tái)主機(jī)傳送到另一臺(tái),而是對(duì)傳輸行為進(jìn)行控制,這本小冊(cè)介紹的主要內(nèi)容 TCP 協(xié)議就被稱為傳輸控制協(xié)議(Transmission Control Protocol),為下面兩層協(xié)議提供數(shù)據(jù)包的重傳、流量控制、擁塞控制等。
假設(shè)你正在電腦上用微信跟女朋友聊天,用 QQ 跟技術(shù)大佬們討論技術(shù)細(xì)節(jié),當(dāng)電腦收到一個(gè)數(shù)據(jù)包時(shí),它怎么知道這是一條微信的聊天內(nèi)容,還是一條 QQ 的消息呢?
這就是端口號(hào)的作用。傳輸層用端口號(hào)來標(biāo)識(shí)不同的應(yīng)用程序,主機(jī)收到數(shù)據(jù)包以后根據(jù)目標(biāo)端口號(hào)將數(shù)據(jù)包傳遞給對(duì)應(yīng)的應(yīng)用程序進(jìn)行處理。比如這個(gè)例子中,目標(biāo)端口號(hào)為 80,百度的服務(wù)器就根據(jù)這個(gè)目標(biāo)端口號(hào)將請(qǐng)求交給監(jiān)聽 80 端口的應(yīng)用程序(可能是 Nginx 等負(fù)載均衡器)處理。
網(wǎng)絡(luò)互連層(Internet Layer)
網(wǎng)絡(luò)互連層提供了主機(jī)到主機(jī)的通信,將傳輸層產(chǎn)生的的數(shù)據(jù)包封裝成分組數(shù)據(jù)包發(fā)送到目標(biāo)主機(jī),并提供路由選擇的能力。
IP 協(xié)議是網(wǎng)絡(luò)層的主要協(xié)議,TCP 和 UDP 都是用 IP 協(xié)議作為網(wǎng)絡(luò)層協(xié)議。這一層的主要作用是給包加上源地址和目標(biāo)地址,將數(shù)據(jù)包傳送到目標(biāo)地址。
IP 協(xié)議是一個(gè)無連接的協(xié)議,也不具備重發(fā)機(jī)制,這也是 TCP 協(xié)議復(fù)雜的原因之一就是基于了這樣一個(gè)「不靠譜」的協(xié)議。
網(wǎng)絡(luò)訪問層(Network Access Layer)
網(wǎng)絡(luò)訪問層也有說法叫做網(wǎng)絡(luò)接口層,以太網(wǎng)、Wifi、藍(lán)牙工作在這一層,網(wǎng)絡(luò)訪問層提供了主機(jī)連接到物理網(wǎng)絡(luò)需要的硬件和相關(guān)的協(xié)議。這一層我們不做重點(diǎn)討論。
分層的好處是什么呢?
分層的本質(zhì)是通過分離關(guān)注點(diǎn)而讓復(fù)雜問題簡(jiǎn)單化,通過分層可以做到:
- 各層獨(dú)立:限制了依賴關(guān)系的范圍,各層之間使用標(biāo)準(zhǔn)化的接口,各層不需要知道上下層是如何工作的,增加或者修改一個(gè)應(yīng)用層協(xié)議不會(huì)影響傳輸層協(xié)議
- 靈活性更好:比如路由器不需要應(yīng)用層和傳輸層,分層以后路由器就可以只用加載更少的幾個(gè)協(xié)議層
- 易于測(cè)試和維護(hù):提高了可測(cè)試性,可以獨(dú)立的測(cè)試特定層,某一層有了更好的實(shí)現(xiàn)可以整體替換掉
- 能促進(jìn)標(biāo)準(zhǔn)化:每一層職責(zé)清楚,方便進(jìn)行標(biāo)準(zhǔn)化
TCP概述-可靠的、面向連接的、基于字節(jié)流、全雙工的協(xié)議
TCP 是面向連接的協(xié)議
面向連接(connection-oriented):面向連接的協(xié)議要求正式發(fā)送數(shù)據(jù)之前需要通過「握手」建立一個(gè)邏輯連接,結(jié)束通信時(shí)也是通過有序的四次揮手來斷開連接。
無連接(connectionless):無連接的協(xié)議則不需要
三次握手
通過三次握手協(xié)商好雙方后續(xù)通信的起始序列號(hào)、窗口縮放大小等信息。
TCP 協(xié)議是可靠的
IP 是一種無連接、不可靠的協(xié)議:它盡最大可能將數(shù)據(jù)報(bào)從發(fā)送者傳輸給接收者,但并不保證包到達(dá)的順序會(huì)與它們被傳輸?shù)捻樞蛞恢拢膊槐WC包是否重復(fù),甚至都不保證包是否會(huì)達(dá)到接收者。不保證有序、去重、完整。
TCP 要想在 IP 基礎(chǔ)上構(gòu)建可靠的傳輸層協(xié)議,必須有一個(gè)復(fù)雜的機(jī)制來保障可靠性。主要有下面幾個(gè)方面:
- 對(duì)每個(gè)包提供校驗(yàn)和
- 包的序列號(hào)解決了接收數(shù)據(jù)的亂序、重復(fù)問題
- 超時(shí)重傳
- 流量控制、擁塞控制
校驗(yàn)和(checksum) 每個(gè) TCP 包首部中都有兩字節(jié)用來表示校驗(yàn)和,防止在傳輸過程中有損壞。如果收到一個(gè)校驗(yàn)和有差錯(cuò)的報(bào)文,TCP 不會(huì)發(fā)送任何確認(rèn)直接丟棄它,等待發(fā)送端重傳。
包的序列號(hào)保證了接收數(shù)據(jù)的亂序和重復(fù)問題假設(shè)我們往 TCP 套接字里寫 3000 字節(jié)的數(shù)據(jù)導(dǎo)致 TCP發(fā)送了 3 個(gè)數(shù)據(jù)包,每個(gè)數(shù)據(jù)包大小為 1000 字節(jié):第一個(gè)包序列號(hào)為[1~1001),第二個(gè)包序列號(hào)為 [10012001),第三個(gè)包序號(hào)為[20013001)
假如因?yàn)榫W(wǎng)絡(luò)的原因?qū)е碌诙€(gè)、第三個(gè)包先到接收端,第一個(gè)包最后才到,接收端也不會(huì)因?yàn)樗麄兊竭_(dá)的順序不一致把包弄錯(cuò),TCP 會(huì)根據(jù)他們的序號(hào)進(jìn)行重新的排列然后把結(jié)果傳遞給上層應(yīng)用程序。
如果 TCP 接收到重復(fù)的數(shù)據(jù),可能的原因是超時(shí)重傳了兩次但這個(gè)包并沒有丟失,接收端會(huì)收到兩次同樣的數(shù)據(jù),它能夠根據(jù)包序號(hào)丟棄重復(fù)的數(shù)據(jù)。
超時(shí)重傳 TCP 發(fā)送數(shù)據(jù)后會(huì)啟動(dòng)一個(gè)定時(shí)器,等待對(duì)端確認(rèn)收到這個(gè)數(shù)據(jù)包。如果在指定的時(shí)間內(nèi)沒有收到 ACK 確認(rèn),就會(huì)重傳數(shù)據(jù)包,然后等待更長(zhǎng)時(shí)間,如果還沒有收到就再重傳,在多次重傳仍然失敗以后,TCP 會(huì)放棄這個(gè)包。后面我們講到超時(shí)重傳模塊的時(shí)候會(huì)詳細(xì)介紹這部分內(nèi)容。
TCP 是面向字節(jié)流的協(xié)議
TCP 是一種字節(jié)流(byte-stream)協(xié)議,流的含義是沒有固定的報(bào)文邊界。
假設(shè)你調(diào)用 2 次 write 函數(shù)往 socket 里依次寫 500 字節(jié)、800 字節(jié)。write 函數(shù)只是把字節(jié)拷貝到內(nèi)核緩沖區(qū),最終會(huì)以多少條報(bào)文發(fā)送出去是不確定的,如下圖所示
上面出現(xiàn)的情況取決于諸多因素:路徑最大傳輸單元 MTU、發(fā)送窗口大小、擁塞窗口大小等。
當(dāng)接收方從 TCP 套接字讀數(shù)據(jù)時(shí),它是沒法得知對(duì)方每次寫入的字節(jié)是多少的。接收端可能分2 次每次 650 字節(jié)讀取,也有可能先分三次,一次 100 字節(jié),一次 200 字節(jié),一次 1000 字節(jié)進(jìn)行讀取。
TCP 是全雙工的協(xié)議
在 TCP 中發(fā)送端和接收端可以是客戶端/服務(wù)端,也可以是服務(wù)器/客戶端,通信的雙方在任意時(shí)刻既可以是接收數(shù)據(jù)也可以是發(fā)送數(shù)據(jù),每個(gè)方向的數(shù)據(jù)流都獨(dú)立管理序列號(hào)、滑動(dòng)窗口大小、MSS 等信息。
在 TCP 中發(fā)送端和接收端可以是客戶端/服務(wù)端,也可以是服務(wù)器/客戶端,通信的雙方在任意時(shí)刻既可以是接收數(shù)據(jù)也可以是發(fā)送數(shù)據(jù),每個(gè)方向的數(shù)據(jù)流都獨(dú)立管理序列號(hào)、滑動(dòng)窗口大小、MSS 等信息。
小結(jié)與思考
TCP 是一個(gè)可靠的(reliable)、面向連接的(connection-oriented)、基于字節(jié)流(byte-stream)、全雙工(full-duplex)的協(xié)議。發(fā)送端在發(fā)送數(shù)據(jù)以后啟動(dòng)一個(gè)定時(shí)器,如果超時(shí)沒有收到對(duì)端確認(rèn)會(huì)進(jìn)行重傳,接收端利用序列號(hào)對(duì)收到的包進(jìn)行排序、丟棄重復(fù)數(shù)據(jù),TCP 還提供了流量控制、擁塞控制等機(jī)制保證了穩(wěn)定性。
TCP提供了一種字節(jié)流服務(wù),而收發(fā)雙方都不保持記錄的邊界,應(yīng)用程序應(yīng)該如何提供他們自己的記錄標(biāo)識(shí)呢?
答:除了U D P的檢驗(yàn)和,其他都是必需的。I P檢驗(yàn)和只覆蓋了 I P首部,而其他字段都緊接著I P首部開始。
17.2 為什么我們已經(jīng)討論的所有 I n t e r n e t協(xié)議( I P, ICMP, IGMP, UDP, TCP)收到有檢驗(yàn)和錯(cuò)的分組都僅作丟棄處理?
答:源I P地址、源端口號(hào)或者協(xié)議字段可能被破壞了。
17.3 T C P提供了一種字節(jié)流服務(wù),而收發(fā)雙方都不保持記錄的邊界。應(yīng)用程序如何提供它們
自己的記錄標(biāo)識(shí)?
答:很多I n t e r n e t應(yīng)用使用一個(gè)回車和換行來標(biāo)記每個(gè)應(yīng)用記錄的結(jié)束。這是 NVT ASCII采用的編碼( 2 6 . 4節(jié)) 。另外一種技術(shù)是在每個(gè)記錄之前加上一個(gè)記錄的字節(jié)計(jì)數(shù), D N S(習(xí)題1 4 . 4)和Sun RPC( 2 9 . 2節(jié))采用了這種技術(shù)。
17.4 為什么在T C P首部的開始便是源和目的的端口號(hào)?
答:就像我們?cè)? . 5節(jié)所看到的,一個(gè)I C M P差錯(cuò)報(bào)文必須至少返回引起差錯(cuò)的 I P數(shù)據(jù)報(bào)中除了I P首部的前8 個(gè)字節(jié)。當(dāng)T C P收到一個(gè)I C M P差錯(cuò)報(bào)文時(shí),它需要檢查兩個(gè)端口號(hào)以決定差錯(cuò)對(duì)應(yīng)于哪個(gè)連接。因此,端口號(hào)必須包含在T C P首部的前8個(gè)字節(jié)里。
17.5 為什么T C P首部有一個(gè)首部長(zhǎng)度字段而 U D P首部(圖11 - 2)中卻沒有?
TCP首部的最后有一些選項(xiàng),但 U D P首部中沒有選項(xiàng)。
packetdrill-google協(xié)議棧測(cè)試神器-TODO
以 centos7 為例
- 首先從 github 上 clone 最新的源碼 github.com/google/pack…
- 進(jìn)入源碼目錄cd gtests/net/packetdrill
- 安裝 bison和 flex 庫:sudo yum install -y bison flex
- 為避免 offload 機(jī)制對(duì)包大小的影響,修改 netdev.c 注釋掉 set_device_offload_flags 函數(shù)所有內(nèi)容
- 執(zhí)行 ./configure
- 修改 Makefile,去掉第一行的末尾的 -static
- 執(zhí)行 make 命令編譯
- 確認(rèn)編譯無誤地生成了 packetdrill 可執(zhí)行文件
詳解
tcp基石-剖析首部字段
這篇文章來講講 TCP 報(bào)文首部相關(guān)的概念,這些頭部是支撐 TCP 復(fù)雜功能的基石。完整的 TCP 頭部如下圖所示:
我們用一次訪問百度網(wǎng)頁抓包的例子來開始。
源端口號(hào)、目標(biāo)端口號(hào)
在第一個(gè)包的詳情中,首先看到的高亮部分的源端口號(hào)(Src Port)和目標(biāo)端口號(hào)(Dst Port),這個(gè)例子中本地源端口號(hào)為 61024,百度目標(biāo)端口號(hào)是 80。
TCP 報(bào)文頭部里沒有源 ip 和目標(biāo) ip 地址,只有源端口號(hào)和目標(biāo)端口號(hào)。
這也是初學(xué) wireshark 抓包時(shí)很多人會(huì)有的一個(gè)疑問:過濾 ip 地址為 172.19.214.24 包的條件為什么不是 “tcp.addr == 172.19.214.24”,而是 “ip.addr == 172.19.214.24”
TCP 的報(bào)文里是沒有源 ip 和目標(biāo) ip 的,因?yàn)槟鞘?IP 層協(xié)議的事情,TCP 層只有源端口和目標(biāo)端口。
源 IP、源端口、目標(biāo) IP、目標(biāo)端口構(gòu)成了 TCP 連接的「四元組」。一個(gè)四元組可以唯一標(biāo)識(shí)一個(gè)連接。
序列號(hào)(Sequence number)
TCP 是面向字節(jié)流的協(xié)議,通過 TCP 傳輸?shù)淖止?jié)流的每個(gè)字節(jié)都分配了序列號(hào),序列號(hào)(Sequence number)指的是本報(bào)文段第一個(gè)字節(jié)的序列號(hào)。
序列號(hào)加上報(bào)文的長(zhǎng)度,就可以確定傳輸?shù)氖悄囊欢螖?shù)據(jù)。序列號(hào)是一個(gè) 32 位的無符號(hào)整數(shù),達(dá)到 2^32-1 后循環(huán)到 0。
在 SYN 報(bào)文中,序列號(hào)用于交換彼此的初始序列號(hào),在其它報(bào)文中,序列號(hào)用于保證包的順序。
因?yàn)榫W(wǎng)絡(luò)層(IP 層)不保證包的順序,TCP 協(xié)議利用序列號(hào)來解決網(wǎng)絡(luò)包亂序、重復(fù)的問題,以保證數(shù)據(jù)包以正確的順序組裝傳遞給上層應(yīng)用。
如果發(fā)送方發(fā)送的是四個(gè)報(bào)文序列號(hào)分別是1、2、3、4,但到達(dá)接收方的順序是 2、4、3、1,接收方就可以通過序列號(hào)的大小順序組裝出原始的數(shù)據(jù)。
初始序列號(hào)(Initial Sequence Number, ISN)
在建立連接之初,通信雙方都會(huì)各自選擇一個(gè)序列號(hào),稱之為初始序列號(hào)。在建立連接時(shí),通信雙方通過 SYN 報(bào)文交換彼此的 ISN,如下圖所示:
初始建立連接的過程中 SYN 報(bào)文交換過程如下圖所示:
其中第 2 步和第 3 步可以合并一起,這就是三次握手的過程:
初始序列號(hào)是如何生成的
__be16 sport, __be16 dport)
{
u32 hash[MD5_DIGEST_WORDS];
net_secret_init();
hash[0] = (__force u32)saddr;
hash[1] = (__force u32)daddr;
hash[2] = ((__force u16)sport << 16) + (__force u16)dport;
//一個(gè)長(zhǎng)度為 16 的 int 數(shù)組,只有在第一次調(diào)用 net_secret_init 的時(shí)時(shí)候會(huì)將將這個(gè)數(shù)組的值初始化為隨機(jī)值。在系統(tǒng)重啟前保持不變。
hash[3] = net_secret[15];
md5_transform(hash, net_secret);
return seq_scale(hash[0]);
}
static u32 seq_scale(u32 seq)
{
return seq + (ktime_to_ns(ktime_get_real()) >> 6);
}
可以看到初始序列號(hào)的計(jì)算函數(shù) secure_tcp_sequence_number() 的邏輯是通過源地址、目標(biāo)地址、源端口、目標(biāo)端口和隨機(jī)因子通過 MD5 進(jìn)行進(jìn)行計(jì)算。如果僅有這幾個(gè)因子,對(duì)于四元組相同的請(qǐng)求,計(jì)算出的初始序列號(hào)總是相同,這必然有很大的安全風(fēng)險(xiǎn),所以函數(shù)的最后將計(jì)算出的序列號(hào)通過 seq_scale 函數(shù)再次計(jì)算。
seq_scale 函數(shù)加入了時(shí)間因子,對(duì)于四元組相同的連接,序列號(hào)也不會(huì)重復(fù)了。
序列號(hào)回繞了怎么處理
序列號(hào)是一個(gè) 32 位的無符號(hào)整數(shù),從前面介紹的初始序列號(hào)計(jì)算算法可以知道,ISN 并不是從 0 開始,所以同一個(gè)連接的序列號(hào)是有可能溢出回繞(sequence wraparound)的。TCP 的很多校驗(yàn)比如丟包、亂序判斷都是通過比較包的序號(hào)來實(shí)現(xiàn)的,我們來看看 linux 內(nèi)核是如何處理的,代碼如下所示。
{
return (__s32)(seq1-seq2) < 0;
}
其中 __u32 表示無符號(hào)的 32 位整數(shù),__s32 表示有符號(hào)的 32 位整數(shù)。為什么 seq1 - seq2 轉(zhuǎn)為有符號(hào)的 32 位整數(shù)就可以判斷 seq1 和 seq2 的大小了呢?
以 seq1 為 0xFFFFFFFF、seq2 為 0x02(回繞)為例,它們相減的結(jié)果如下。
seq1 - seq2 = 0xFFFFFFFF - 0x02 = 0xFFFFFFFD
0xFFFFFFFD 最高位為 1,表示為負(fù)數(shù),實(shí)際值為 -(0x00000002 + 1) = -3,這樣即使 seq2 回繞了,也可以知道 seq1
確認(rèn)號(hào)
TCP 使用確認(rèn)號(hào)(Acknowledgment number, ACK)來告知對(duì)方下一個(gè)期望接收的序列號(hào),小于此確認(rèn)號(hào)的所有字節(jié)都已經(jīng)收到。
關(guān)于確認(rèn)號(hào)有幾個(gè)注意點(diǎn):
- 不是所有的包都需要確認(rèn)的
- 不是收到了數(shù)據(jù)包就立馬需要確認(rèn)的,可以延遲一會(huì)再確認(rèn)
- ACK 包本身不需要被確認(rèn),否則就會(huì)無窮無盡死循環(huán)了
- 確認(rèn)號(hào)永遠(yuǎn)是表示小于此確認(rèn)號(hào)的字節(jié)都已經(jīng)收到
TCP Flags
TCP 有很多種標(biāo)記,有些用來發(fā)起連接同步初始序列號(hào),有些用來確認(rèn)數(shù)據(jù)包,還有些用來結(jié)束連接。TCP 定義了一個(gè) 8 位的字段用來表示 flags,大部分都只用到了后 6 個(gè),如下圖所示
下面這個(gè)是 wireshark 第一個(gè) SYN 包的 flags 截圖
我們通常所說的 SYN、ACK、FIN、RST 其實(shí)只是把 flags 對(duì)應(yīng)的 bit 位置為 1 而已,這些標(biāo)記可以組合使用,比如 SYN+ACK,F(xiàn)IN+ACK 等
- SYN(Synchronize):用于發(fā)起連接數(shù)據(jù)包同步雙方的初始序列號(hào)
- ACK(Acknowledge):確認(rèn)數(shù)據(jù)包
- RST(Reset):這個(gè)標(biāo)記用來強(qiáng)制斷開連接,通常是之前建立的連接已經(jīng)不在了、包不合法、或者實(shí)在無能為力處理
- FIN(Finish):通知對(duì)方我發(fā)完了所有數(shù)據(jù),準(zhǔn)備斷開連接,后面我不會(huì)再發(fā)數(shù)據(jù)包給你了。
- PSH(Push):告知對(duì)方這些數(shù)據(jù)包收到以后應(yīng)該馬上交給上層應(yīng)用,不能緩存起來
窗口大小
可以看到用于表示窗口大小的"Window Size" 只有 16 位,可能 TCP 協(xié)議設(shè)計(jì)者們認(rèn)為 16 位的窗口大小已經(jīng)夠用了,也就是最大窗口大小是 65535 字節(jié)(64KB)。就像網(wǎng)傳蓋茨曾經(jīng)說過:“640K內(nèi)存對(duì)于任何人來說都足夠了”一樣。
自己挖的坑當(dāng)然要自己填,因此TCP 協(xié)議引入了「TCP 窗口縮放」選項(xiàng) 作為窗口縮放的比例因子,比例因子值的范圍是 0 ~ 14,其中最小值 0 表示不縮放,最大值 14。比例因子可以將窗口擴(kuò)大到原來的 2 的 n 次方,比如窗口大小縮放前為 1050,縮放因子為 7,則真正的窗口大小為 1050 * 128 = 134400,如下圖所示
可選項(xiàng)
可選項(xiàng)的格式入下所示
以 MSS 為例,kind=2,length=4,value=1460
常用的選項(xiàng)有以下幾個(gè):
- MSS:最大段大小選項(xiàng),是 TCP 允許的從對(duì)方接收的最大報(bào)文段
- SACK:選擇確認(rèn)選項(xiàng)
- Window Scale:窗口縮放選項(xiàng)
網(wǎng)絡(luò)數(shù)據(jù)包大小-MUT與MSS
前面的文章中介紹過一個(gè)應(yīng)用層的數(shù)據(jù)包會(huì)經(jīng)過傳輸層、網(wǎng)絡(luò)層的層層包裝,交給網(wǎng)絡(luò)接口層傳輸。假設(shè)上層的應(yīng)用調(diào)用 write 等函數(shù)往 socket 寫入了 10KB 的數(shù)據(jù),TCP 會(huì)如何處理呢?是直接加上 TCP 頭直接交給網(wǎng)絡(luò)層嗎?這篇文章我們來講講這相關(guān)的知識(shí)
MUT
數(shù)據(jù)鏈路層傳輸?shù)膸笮∈怯邢拗频模荒馨岩粋€(gè)太大的包直接塞給鏈路層,這個(gè)限制被稱為「最大傳輸單元(Maximum Transmission Unit, MTU)」
下圖是以太網(wǎng)的幀格式,以太網(wǎng)的幀最小的幀是 64 字節(jié),除去 14 字節(jié)頭部和 4 字節(jié) CRC 字段,有效荷載最小為 46 字節(jié)。最大的幀是 1518 字節(jié),除去 14 字節(jié)頭部和 4 字節(jié) CRC,有效荷載最大為 1500,這個(gè)值就是以太網(wǎng)的 MTU。因此如果傳輸 100KB 的數(shù)據(jù),至少需要 (100 * 1024 / 1500) = 69 個(gè)以太網(wǎng)幀。
不同的數(shù)據(jù)鏈路層的 MTU 是不同的。通過netstat -i 可以查看網(wǎng)卡的 mtu,比如在 我的 centos 機(jī)器上可以看到
IP分段
IPv4 數(shù)據(jù)報(bào)的最大大小為 65535 字節(jié),這已經(jīng)遠(yuǎn)遠(yuǎn)超過了以太網(wǎng)的 MTU,而且有些網(wǎng)絡(luò)還會(huì)開啟巨幀(Jumbo Frame)能達(dá)到 9000 字節(jié)。當(dāng)一個(gè) IP 數(shù)據(jù)包大于 MTU 時(shí),IP 會(huì)把數(shù)據(jù)報(bào)文進(jìn)行切割為多個(gè)小的片段(小于 MTU),使得這些小的報(bào)文可以通過鏈路層進(jìn)行傳輸。
IP 頭部中有一個(gè)表示分片偏移量的字段,用來表示該分段在原始數(shù)據(jù)報(bào)文中的位置,如下圖所示
前面我們提到 IP 協(xié)議不會(huì)對(duì)丟包進(jìn)行重傳,那么 IP 分段中有分片丟失、損壞的話,會(huì)發(fā)生什么呢?這種情況下,目標(biāo)主機(jī)將沒有辦法將分段的數(shù)據(jù)包重組為一個(gè)完整的數(shù)據(jù)包,依賴于傳輸層是否進(jìn)行重傳。
利用 IP 包分片的策略,有一種對(duì)應(yīng)的網(wǎng)絡(luò)攻擊方式IP fragment attack,就是一直傳More fragments = 1的包,導(dǎo)致接收方一直緩存分片,從而可能導(dǎo)致接收方內(nèi)存耗盡。
因?yàn)橛?MTU 的存在,TCP 每次發(fā)包的大小也限制了,這就是下面要介紹的 MSS。
MSS
TCP 為了避免被發(fā)送方分片,會(huì)主動(dòng)把數(shù)據(jù)分割成小段再交給網(wǎng)絡(luò)層,最大的分段大小稱之為 MSS(Max Segment Size)。
MSS = MTU - IP header頭大小 - TCP 頭大小
這樣一個(gè) MSS 的數(shù)據(jù)恰好能裝進(jìn)一個(gè) MTU 而不用分片。在以太網(wǎng)中 TCP 的 MSS = 1500(MTU) - 20(IP 頭大小) - 20(TCP 頭大小)= 1460。
為什么有時(shí)候抓包看到的單個(gè)數(shù)據(jù)包大于 MTU
這就要說到 TSO(TCP Segment Offload)特性了,TSO 特性是指由網(wǎng)卡代替 CPU 實(shí)現(xiàn) packet 的分段和合并,節(jié)省系統(tǒng)資源,因此 TCP 可以抓到超過 MTU 的包,但是不是真正傳輸?shù)膯蝹€(gè)包會(huì)超過鏈路的 MTU。
-
通信
+關(guān)注
關(guān)注
18文章
6069瀏覽量
136286 -
互聯(lián)網(wǎng)
+關(guān)注
關(guān)注
54文章
11184瀏覽量
103671 -
IP
+關(guān)注
關(guān)注
5文章
1716瀏覽量
149815 -
TCP協(xié)議
+關(guān)注
關(guān)注
1文章
101瀏覽量
12105
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論