文章首發(fā)于個(gè)人公眾號(hào)「小白debug」
原文鏈接:連接一個(gè) IP 不存在的主機(jī)時(shí),握手過程是怎樣的额港?
鴿了好長時(shí)間了饺窿,最近很忙。以前工作忙完移斩,就抽空寫文章肚医。
現(xiàn)在忙完工作,還要一三五學(xué)駕照叹哭,二四六看家具忍宋。有同感的老鐵們不要舉手,拉到右下角點(diǎn)個(gè)"在看"就好了风罩。
真的,全怪某音舵稠。
扯遠(yuǎn)了超升,回到今天的主題入宦。
方兄最近寫了篇很贊的文章 寫給想去字節(jié)寫 Go 的你 ,里面提到了兩個(gè)問題室琢。
連接一個(gè) IP 不存在的主機(jī)時(shí)乾闰,握手過程是怎樣的?
連接一個(gè) IP 地址存在但端口號(hào)不存在的主機(jī)時(shí)盈滴,握手過程又是怎樣的呢涯肩?
讓我回想起曾經(jīng)也被面試官問過類似的問題,意識(shí)到應(yīng)該很多朋友會(huì)對這個(gè)問題感興趣巢钓。
所以來給大家嘮嘮病苗。
這兩個(gè)問題可以延伸出非常多的點(diǎn)。
看完了症汹,說不定能加分硫朦!
正常情況的握手過程是怎么樣的
上面提到的問題,其實(shí)是指TCP的三次握手流程背镇。這絕對是面試八股文里的老股了咬展。
我們簡單回顧下基礎(chǔ)知識(shí)點(diǎn)。
在服務(wù)端啟動(dòng)好后會(huì)調(diào)用 listen()
方法瞒斩,進(jìn)入到 LISTEN
狀態(tài)破婆,然后靜靜等待客戶端的連接請求到來。
而此時(shí)客戶端主動(dòng)調(diào)用 connect(IP地址)
胸囱,就會(huì)向某個(gè)IP地址發(fā)起第一次握手祷舀,發(fā)送SYN
到目的服務(wù)器。
服務(wù)器在收到第一次握手后就會(huì)響應(yīng)客戶端旺矾,這是第二次握手蔑鹦。
客戶端在收到第二次握手的消息后,響應(yīng)服務(wù)的一個(gè)ACK
箕宙,這算第三次握手嚎朽,此時(shí)客戶端 就會(huì)進(jìn)入 ESTABLISHED
狀態(tài),認(rèn)為連接已經(jīng)建立完成柬帕。
通過抓包可以直觀看出三次握手的流程哟忍。
連一個(gè) IP 不存在的主機(jī)時(shí),握手過程是怎樣的
那不存在的IP陷寝,分兩種锅很,局域網(wǎng)內(nèi)和局域網(wǎng)外的。
我以我家里的情況舉例凤跑。
家里有一臺(tái)家用路由器爆安。本質(zhì)上它的功能已經(jīng)集成了我們常說的路由器,交換機(jī)和無線接入點(diǎn)的功能了仔引。
其中路由器和交換機(jī)在之前寫過的 《硬核圖解扔仓!30張圖帶你搞懂褐奥!路由器,集線器翘簇,交換機(jī)撬码,網(wǎng)橋,光貓有啥區(qū)別版保?》里已經(jīng)詳細(xì)介紹過了呜笑,就不再說一遍了。無線接入點(diǎn)基本可以認(rèn)為就是個(gè)放出 wifi 信號(hào)的組件彻犁。
家用路由器下叫胁,連著我的N臺(tái)設(shè)備,包括手機(jī)和電腦袖裕,他們的IP都有個(gè)共同點(diǎn)曹抬。都是 192.168.31.xx
形式的。其中急鳄,我的電腦的IP是192.168.31.6
谤民,這個(gè)可以通過 ifconfig
查到。
符合這個(gè)形式的這些個(gè)設(shè)備疾宏,本質(zhì)上就是通過各種設(shè)備(wifi或交換機(jī)等)接入到上圖路由器的e2端口张足,他們共同構(gòu)成一個(gè)局域網(wǎng)。
因此坎藐,在我家为牍,我們可以粗暴點(diǎn)認(rèn)為只要是 192.168.31.xx
形式的IP,就是局域網(wǎng)內(nèi)的IP岩馍。否則就是局域網(wǎng)外的IP碉咆,比如 192.0.2.2
。
目的IP在局域網(wǎng)內(nèi)
因?yàn)橥ㄟ^ ifconfig 可以查到我的局域網(wǎng)內(nèi)IP是192.168.31.6
蛀恩,這里盲猜末尾+1是不存在的 IP 疫铜。試了下,192.168.31.7
還真不存在双谆。
$ ping 192.168.31.7
PING 192.168.31.7 (192.168.31.7): 56 data bytes
Request timeout for icmp_seq 0
Request timeout for icmp_seq 1
Request timeout for icmp_seq 2
Request timeout for icmp_seq 3
^C
--- 192.168.31.7 ping statistics ---
5 packets transmitted, 0 packets received, 100.0% packet loss
于是寫個(gè)程序嘗試連這個(gè)IP 壳咕。下面的代碼是 golang
寫的,大家不看代碼也沒關(guān)系顽馋,放出來只是方便大家自己復(fù)現(xiàn)的時(shí)候用的谓厘。
// tcp客戶端
package main
import (
"fmt"
"io"
"net"
"os"
)
func main() {
client, err := net.Dial("tcp", "192.168.31.7:8081")
if err != nil {
fmt.Println("err:", err)
return
}
defer client.Close()
go func() {
input := make([]byte, 1024)
for {
n, err := os.Stdin.Read(input)
if err != nil {
fmt.Println("input err:", err)
continue
}
client.Write([]byte(input[:n]))
}
}()
buf := make([]byte, 1024)
for {
n, err := client.Read(buf)
if err != nil {
if err == io.EOF {
return
}
fmt.Println("read err:", err)
continue
}
fmt.Println(string(buf[:n]))
}
}
然后嘗試抓包。
可以發(fā)現(xiàn)根本沒有三次握手的包寸谜,只有一些 ARP 包竟稳,在詢問“誰是 192.168.31.7
,告訴一下 192.168.31.6
” 。
這里有三個(gè)問題
- 為什么會(huì)發(fā)ARP請求住练?
- 為什么沒有TCP握手包地啰?
- ARP本身是沒有重試機(jī)制的愁拭,為什么ARP請求會(huì)發(fā)那么多遍讲逛?
首先我們看下正常情況下執(zhí)行connect
,也就是第一次握手 的流程岭埠。
應(yīng)用層執(zhí)行connect
過后盏混,會(huì)通過socket層,操作系統(tǒng)接口惜论,進(jìn)程會(huì)從用戶態(tài)進(jìn)入到內(nèi)核態(tài)许赃,此時(shí)進(jìn)入 傳輸層,因?yàn)槭?strong>TCP第一次握手馆类,會(huì)加入TCP頭混聊,且置SYN標(biāo)志。
然后進(jìn)入網(wǎng)絡(luò)層乾巧,我想要連的是 192.168.31.7
句喜,雖然它是我瞎編的,但IP頭還是得老老實(shí)實(shí)把它加進(jìn)去沟于。
此時(shí)需要重點(diǎn)介紹的是鄰居子系統(tǒng)咳胃,它在網(wǎng)絡(luò)層和數(shù)據(jù)鏈路層之間】跆可以通過ARP協(xié)議將目的IP轉(zhuǎn)為對應(yīng)的MAC地址展懈,然后數(shù)據(jù)鏈路層就可以用這個(gè)MAC地址組裝幀頭。
我們看下那么ARP協(xié)議的流程是
1.先到本地ARP表查一下有沒有 192.168.31.7
對應(yīng)的 mac地址供璧,有的話就返回存崖,這里顯然是不可能會(huì)有的。
可以通過 arp -a 命令查看本機(jī)的 arp表都記錄了哪些信息
$ arp -a
? (192.168.31.1) at 88:c1:97:59:d1:c3 on en0 ifscope [ethernet]
? (224.0.0.251) at 1:0:4e:0:1:fb on en0 ifscope permanent [ethernet]
? (239.255.255.250) at 1:0:3e:7f:ff:fb on en0 ifscope permanent [ethernet]
2.看下 192.168.31.7
跟本機(jī)IP 192.168.31.6
在不在一個(gè)局域網(wǎng)下睡毒。如果在的話来惧,就在局域網(wǎng)內(nèi)發(fā)一個(gè) arp 廣播,內(nèi)容就是 前面提到的 “誰是 192.168.31.7
吕嘀,告訴一下 192.168.31.6
”违寞。
3.如果目的IP跟本機(jī)IP不在同一個(gè)局域網(wǎng)下,那么會(huì)去獲取默認(rèn)網(wǎng)關(guān)的MAC地址偶房,這里就是指獲取家用路由器的MAC地址趁曼。然后把消息發(fā)給家用路由器,讓路由器發(fā)到互聯(lián)網(wǎng)棕洋,找到下一跳路由器挡闰,一跳一跳的發(fā)送數(shù)據(jù),直到把消息發(fā)到目的IP上,又或者找不到目的地最終被丟棄摄悯。
4.第2和第3點(diǎn)都是本地沒有查到 ARP 緩存記錄的情況赞季,這時(shí)候會(huì)把SYN報(bào)文放進(jìn)一個(gè)隊(duì)列(叫unresolved_queue)里暫存起來,然后發(fā)起ARP請求奢驯;等ARP層收到ARP回應(yīng)報(bào)文之后申钩,會(huì)再從緩存中取出 SYN 報(bào)文,組裝 MAC 幀頭瘪阁,完成剛剛沒完成的發(fā)送流程撒遣。
如果經(jīng)過 ARP 流程能正常返回 MAC 地址,那皆大歡喜管跺,直接給數(shù)據(jù)鏈路層义黎,經(jīng)過 ring buffer
后傳到網(wǎng)卡,發(fā)出去豁跑。
但因?yàn)楝F(xiàn)在這個(gè)IP是瞎編的廉涕,因此不可能得到目的地址 MAC ,所以消息也一直沒法到數(shù)據(jù)鏈路層艇拍。整個(gè)流程卡在了ARP流程中狐蜕。
而抓包是在數(shù)據(jù)鏈路層之后進(jìn)行的,因此 TCP 第一次握手的包一直沒能抓到淑倾,只能抓到為了獲得 192.168.31.7
的MAC地址的ARP請求馏鹤。
發(fā)送數(shù)據(jù)時(shí),是在經(jīng)過數(shù)據(jù)鏈路層之后的 dev_queue_xmit_nit 方法執(zhí)行抓包操作的娇哆,這是屬于網(wǎng)卡驅(qū)動(dòng)層的方法了湃累。
順帶一提,接收端抓包是在 __netif_receive_skb_core 方法里執(zhí)行的碍讨,也屬于網(wǎng)卡驅(qū)動(dòng)層治力。感興趣的朋友們可以以這個(gè)為關(guān)鍵詞搜索相關(guān)知識(shí)點(diǎn)哈
此時(shí) 因?yàn)?TCP 協(xié)議是可靠的協(xié)議,對于 TCP 層來說勃黍,第一次握手的消息宵统,已經(jīng)發(fā)出去了,但是一直沒有收到 ACK覆获。也不知道消息是出去后是遇到什么事了马澈。為了保證可靠性,它會(huì)不斷重發(fā)弄息。
而每一次重發(fā)痊班,都會(huì)因?yàn)橥瑯拥脑颍]有目的 MAC 地址)而尬在了 ARP 那個(gè)流程里。因此摹量,才看到好幾次重復(fù)的 ARP 消息涤伐。
那回到剛剛的三個(gè)問題
-
為什么會(huì)發(fā) ARP 請求馒胆?
因?yàn)槟康牡刂肥窍咕幍模镜谹RP表沒有目的機(jī)器的MAC地址凝果,因此發(fā)出ARP消息祝迂。
-
為什么沒有 TCP 握手包?
因?yàn)閰f(xié)議棧的數(shù)據(jù)到了網(wǎng)絡(luò)層后器净,在數(shù)據(jù)鏈路層前型雳,就因?yàn)闆]有目的MAC地址,沒法發(fā)出掌动。因此抓包軟件抓不到相關(guān)數(shù)據(jù)四啰。
-
為什么 ARP 請求會(huì)發(fā)那么多遍?
因?yàn)?TCP 協(xié)議的可靠性粗恢,會(huì)重發(fā)第一次握手的消息,但每一次都因?yàn)闆]有目的 MAC 地址而失敗欧瘪,每次都會(huì)發(fā)出ARP請求眷射。
小結(jié)
連一個(gè) IP 不存在的主機(jī)時(shí),如果目的IP在局域網(wǎng)內(nèi)佛掖,則第一次握手會(huì)失敗妖碉,接著不斷嘗試重發(fā)握手的請求。同時(shí)芥被,本機(jī)會(huì)不斷發(fā)出ARP請求欧宜,企圖獲得目的機(jī)器的 MAC 地址。并且拴魄,因?yàn)闆]能獲得目的 MAC 地址冗茸,這些 TCP 握手請求最終都發(fā)不出去,
目的IP在局域網(wǎng)外
上面提到的是匹中,目的 IP 在局域網(wǎng)內(nèi)的情況夏漱,下面討論目的IP在局域網(wǎng)外的情況。
瞎編一個(gè)不是 192.168.31.xx
形式的 IP 作為這次要用的局域網(wǎng)外IP顶捷, 比如 10.225.31.11
挂绰。
先抓包看一下。
這次的現(xiàn)象是能發(fā)出 TCP 第一次握手的 SYN包
服赎。
這里有兩個(gè)問題
- 為什么連局域網(wǎng)外的 IP 現(xiàn)象跟連局域網(wǎng)內(nèi)不一致葵蒂?
- TCP 第一次握手的重試規(guī)律好像不太對?
為什么連局域網(wǎng)外的IP現(xiàn)象跟連局域網(wǎng)內(nèi)不一致重虑?
這個(gè)問題的答案其實(shí)在上面 ARP 的流程里已經(jīng)提到過了践付,如果目的 IP 跟本機(jī) IP 不在同一個(gè)局域網(wǎng)下,那么會(huì)去獲取默認(rèn)網(wǎng)關(guān)的 MAC 地址嚎尤,這里就是指獲取家用路由器的MAC地址荔仁。
此時(shí)ARP流程成功返回家用路由器的 MAC 地址,數(shù)據(jù)鏈路層加入幀頭,消息通過網(wǎng)卡發(fā)到了家用路由器上乏梁。
消息會(huì)通過互聯(lián)網(wǎng)一直傳遞到某個(gè)局域網(wǎng)為 10.225.31.xx
的路由器上次洼,那個(gè)路由器 發(fā)出ARP 請求,詢問他們局域網(wǎng)內(nèi)的機(jī)器有沒有叫 10.225.31.11
的 (結(jié)果當(dāng)然沒有)遇骑。
最終沒能發(fā)送成功卖毁,發(fā)送端也就遲遲收不到目的機(jī)的第二次握手響應(yīng)。
因此觸發(fā)TCP重傳落萎。
TCP第一次握手的重試規(guī)律好像不太對亥啦?
在 Linux 中悄晃,第一次握手的 SYN
重傳次數(shù)段标,是通過 tcp_syn_retries
參數(shù)控制的∽惶穑可以通過下面的方式查看
$cat /proc/sys/net/ipv4/tcp_syn_retries
6
這里的含義是指 syn重傳
會(huì)發(fā)生6次媒鼓。
而每次重試都會(huì)間隔一定的時(shí)間届吁,這里的間隔一般是 1s,2s绿鸣,4s疚沐,8s, 16s, 32s .
而事實(shí)上,看我的截圖潮模,是先重試4次亮蛔,每次都是1s,之后才是 1s擎厢,2s究流,4s,8s, 16s, 32s 的重試锉矢。
這跟我們知道的不太一樣梯嗽。
這個(gè)是因?yàn)?strong>我用的是macOS抓的包,跟linux就不是一個(gè)系統(tǒng)沽损,各自的TCP協(xié)議棧在sync重傳方面的實(shí)現(xiàn)都可能會(huì)有一定的差異灯节。
我還聽說 oppo
和 vivo
的 syn重傳 是0.5s起步的。而 windows
的 syn重傳 還有自己的專利绵估。
這些冷知識(shí)大家可以不用在意炎疆。面試的時(shí)候知道linux的就夠了,剩下的可以用來裝逼国裳。畢竟面試官不在意"茴"字到底有幾種寫法形入。
連IP 地址存在但端口號(hào)不存在的主機(jī)的握手過程
前面提到的是IP地址壓根就不存在的情況。假如IP地址存在但端口號(hào)是瞎編的呢缝左?
目的IP是回環(huán)地址
現(xiàn)象也比較簡單浓若,已經(jīng)IP地址是存在的,也就是在互聯(lián)網(wǎng)中這個(gè)機(jī)器是存在的蛇数。
那么我們可以正常發(fā)消息到目的IP挪钓,因?yàn)閷?yīng)的MAC地址和IP都是正確的,所以耳舅,數(shù)據(jù)從數(shù)據(jù)鏈路層到網(wǎng)絡(luò)層都很OK碌上。
直到傳輸層,TCP協(xié)議在識(shí)別到這個(gè)端口號(hào)對應(yīng)的進(jìn)程根本不存在時(shí)浦徊,就會(huì)把數(shù)據(jù)丟棄馏予,響應(yīng)一個(gè)RST消息給發(fā)送端。
RST是什么盔性?
我們都是到TCP正常情況下斷開連接是用四次揮手霞丧,那是正常時(shí)候的優(yōu)雅做法。
但異常情況下纯出,收發(fā)雙方都不一定正常蚯妇,連揮手這件事本身都可能做不到,所以就需要一個(gè)機(jī)制去強(qiáng)行關(guān)閉連接暂筝。
RST 就是用于這種情況,一般用來異常地關(guān)閉一個(gè)連接硬贯。它在TCP包頭中焕襟,在收到置了這個(gè)標(biāo)志位的數(shù)據(jù)包后,連接就會(huì)被關(guān)閉饭豹,此時(shí)接收到 RST的一方鸵赖,一般會(huì)看到一個(gè) connection reset
或 connection refused
的報(bào)錯(cuò)。
目的IP在局域網(wǎng)內(nèi)
剛剛提到我的本機(jī)IP是 192.168.31.6
拄衰,局域網(wǎng)內(nèi)有臺(tái) 192.168.31.1
它褪。同樣嘗試連一個(gè)不存在的端口。
此時(shí)現(xiàn)象跟前者一致茫打。
唯一不同的是,前者是回環(huán)地址妖混,RST數(shù)據(jù)是從本機(jī)的傳輸層返回的老赤。而這次的情況,RST數(shù)據(jù)是從目的機(jī)器的傳輸層返回的制市。
目的IP在局域網(wǎng)外
找一個(gè)存在的外網(wǎng)ip抬旺,這里我拿了最近剛白嫖的阿里云服務(wù)器地址 47.102.221.141
。(炫耀)
進(jìn)行連接連接祥楣,發(fā)現(xiàn)與前面兩種情況是一致的开财,目的機(jī)器在收到我的請求后汉柒,立馬就通過 RST標(biāo)志位 斷開了這次的連接。
這一點(diǎn)跟前面兩種情況一致碾褂。
熟悉小白的朋友們都知道,每次搞事情做測試薇搁,都會(huì)用 baidu.com
斋扰。
這次也不例外,ping 一下 baidu.com
,獲得它的 IP: 220.181.38.148
啃洋。
$ ping baidu.com
PING baidu.com (220.181.38.148): 56 data bytes
64 bytes from 220.181.38.148: icmp_seq=0 ttl=48 time=35.728 ms
64 bytes from 220.181.38.148: icmp_seq=1 ttl=48 time=38.052 ms
64 bytes from 220.181.38.148: icmp_seq=2 ttl=48 time=37.845 ms
64 bytes from 220.181.38.148: icmp_seq=3 ttl=48 time=37.210 ms
64 bytes from 220.181.38.148: icmp_seq=4 ttl=48 time=38.402 ms
64 bytes from 220.181.38.148: icmp_seq=5 ttl=48 time=37.692 ms
^C
--- baidu.com ping statistics ---
6 packets transmitted, 6 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 35.728/37.488/38.402/0.866 ms
發(fā)消息到給百度域名背后的 IP传货,且瞎隨機(jī)指定一個(gè)端口 8080, 抓包宏娄。
現(xiàn)象卻不一致。沒有 RST
孵坚。而且觸發(fā)了第一次握手的重試消息粮宛。這是為什么?
這是因?yàn)閎aidu的機(jī)器卖宠,作為線上生產(chǎn)的機(jī)器巍杈,會(huì)設(shè)置一系列安全策略,比如只對外暴露某些端口扛伍,除此之外的端口筷畦,都一律拒絕。
所以很多發(fā)到 8080端口的消息都在防火墻這一層就被拒絕掉了刺洒,根本到不了目的主機(jī)里鳖宾,而RST是在目的主機(jī)的TCP/IP協(xié)議棧里發(fā)出的,都還沒到這一層逆航,就更不可能發(fā)RST了鼎文。因此發(fā)送端發(fā)現(xiàn)消息沒有回應(yīng)(因?yàn)楸环阑饓G了),就會(huì)重傳因俐。所以才會(huì)出現(xiàn)上述抓包里的現(xiàn)象拇惋。
總結(jié)
連一個(gè) IP 不存在的主機(jī)時(shí)
如果IP在局域網(wǎng)內(nèi),會(huì)發(fā)送N次ARP請求獲得目的主機(jī)的MAC地址女揭,同時(shí)不能發(fā)出TCP握手消息蚤假。
如果IP在局域網(wǎng)外,會(huì)將消息通過路由器發(fā)出吧兔,但因?yàn)樽罱K找不到目的地磷仰,觸發(fā)TCP重試流程。
連IP 地址存在但端口號(hào)不存在的主機(jī)時(shí)
不管目的IP是回環(huán)地址還是局域網(wǎng)內(nèi)外的IP地址境蔼,目的主機(jī)的傳輸層都會(huì)在收到握手消息后灶平,發(fā)現(xiàn)端口不正確伺通,發(fā)出RST消息斷開連接。
當(dāng)然如果目的機(jī)器設(shè)置了防火墻策略逢享,限制他人將消息發(fā)到不對外暴露的端口罐监,那么這種情況,發(fā)送端就會(huì)不斷重試第一次握手瞒爬。
最后留個(gè)問題弓柱,連一個(gè) 不存在的局域網(wǎng)外IP的主機(jī)時(shí),我們可以看到TCP的重發(fā)規(guī)律是:開始時(shí)侧但,每隔1s重發(fā)五次 TCP SYN
消息矢空,接著2s,4s,8s,16s,32s都重發(fā)一次;
對比連一個(gè) 不存在的局域網(wǎng)內(nèi)IP的主機(jī)時(shí)禀横,卻是每隔1s重發(fā)了4次ARP請求
屁药,接著過了32s后才再發(fā)出一次ARP請求。已知ARP請求是沒有重傳機(jī)制的柏锄,它的重試就是TCP重試觸發(fā)的酿箭,但兩者規(guī)律不一致,是為什么趾娃?
最后
歡迎大家加我(gong中號(hào)里右下角“聯(lián)系我”)缭嫡,互相圍觀朋友圈砍一刀啥的哈哈。
如果文章對你有幫助抬闷,看下文章底部右下角械巡,做點(diǎn)正能量的事情(點(diǎn)兩下)支持一下。(卑微瘋狂暗示饶氏,拜托拜托,這對我真的很重要有勾!)
我是小白疹启,我們下期見。
別說了蔼卡,一起在知識(shí)的海洋里嗆水吧
本文由博客一文多發(fā)平臺(tái) OpenWrite 發(fā)布喊崖!