連接一個(gè) IP 不存在的主機(jī)時(shí),握手過(guò)程是怎樣的?
本文轉(zhuǎn)載自微信公眾號(hào)「小白debug」,作者小白。轉(zhuǎn)載本文請(qǐng)聯(lián)系小白debug公眾號(hào)。
鴿了好長(zhǎng)時(shí)間了,最近很忙。以前工作忙完,就抽空寫文章。
現(xiàn)在忙完工作,還要一三五學(xué)駕照,二四六看家具。有同感的老鐵們不要舉手,拉到右下角點(diǎn)個(gè)"在看"就好了。
真的,全怪某音。
扯遠(yuǎn)了,回到今天的主題。
方兄最近寫了篇很贊的文章 寫給想去字節(jié)寫 Go 的你 ,里面提到了兩個(gè)問(wèn)題。
連接一個(gè) IP 不存在的主機(jī)時(shí),握手過(guò)程是怎樣的?
連接一個(gè) IP 地址存在但端口號(hào)不存在的主機(jī)時(shí),握手過(guò)程又是怎樣的呢?
讓我回想起曾經(jīng)也被面試官問(wèn)過(guò)類似的問(wèn)題,意識(shí)到應(yīng)該很多朋友會(huì)對(duì)這個(gè)問(wèn)題感興趣。
所以來(lái)給大家嘮嘮。
這兩個(gè)問(wèn)題可以延伸出非常多的點(diǎn)。
看完了,說(shuō)不定能加分!
正常情況的握手過(guò)程是怎么樣的
上面提到的問(wèn)題,其實(shí)是指TCP的三次握手流程。這絕對(duì)是面試八股文里的老股了。
我們簡(jiǎn)單回顧下基礎(chǔ)知識(shí)點(diǎn)。
正常情況下的TCP三次握手
在服務(wù)端啟動(dòng)好后會(huì)調(diào)用 listen() 方法,進(jìn)入到 LISTEN 狀態(tài),然后靜靜等待客戶端的連接請(qǐng)求到來(lá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)建立完成。
通過(guò)抓包可以直觀看出三次握手的流程。
正常三次握手抓包
連一個(gè) IP 不存在的主機(jī)時(shí),握手過(guò)程是怎樣的
那不存在的IP,分兩種,局域網(wǎng)內(nèi)和局域網(wǎng)外的。
家用路由器局域網(wǎng)互聯(lián)
我以我家里的情況舉例。
家里有一臺(tái)家用路由器。本質(zhì)上它的功能已經(jīng)集成了我們常說(shuō)的路由器,交換機(jī)和無(wú)線接入點(diǎn)的功能了。
其中路由器和交換機(jī)在之前寫過(guò)的 《硬核圖解!30張圖帶你搞懂!路由器,集線器,交換機(jī),網(wǎng)橋,光貓有啥區(qū)別?》里已經(jīng)詳細(xì)介紹過(guò)了,就不再說(shuō)一遍了。無(wú)線接入點(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è)可以通過(guò) ifconfig查到。
符合這個(gè)形式的這些個(gè)設(shè)備,本質(zhì)上就是通過(guò)各種設(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)橥ㄟ^(guò) 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 寫的,大家不看代碼也沒(méi)關(guān)系,放出來(lái)只是方便大家自己復(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]))
- }
- }
然后嘗試抓包。
連一個(gè)不存在的IP(局域網(wǎng)內(nèi))抓包
可以發(fā)現(xiàn)根本沒(méi)有三次握手的包,只有一些 ARP 包,在詢問(wèn)“誰(shuí)是 192.168.31.7,告訴一下 192.168.31.6” 。
這里有三個(gè)問(wèn)題
- 為什么會(huì)發(fā)ARP請(qǐng)求?
- 為什么沒(méi)有TCP握手包?
- ARP本身是沒(méi)有重試機(jī)制的,為什么ARP請(qǐng)求會(huì)發(fā)那么多遍?
首先我們看下正常情況下執(zhí)行connect,也就是第一次握手 的流程。
正常connect的流程
應(yīng)用層執(zhí)行connect過(guò)后,會(huì)通過(guò)socket層,操作系統(tǒng)接口,進(jìn)程會(huì)從用戶態(tài)進(jìn)入到內(nèi)核態(tài),此時(shí)進(jìn)入 傳輸層,因?yàn)槭荰CP第一次握手,會(huì)加入TCP頭,且置SYN標(biāo)志。
tcp報(bào)頭的SYN
然后進(jìn)入網(wǎng)絡(luò)層,我想要連的是 192.168.31.7 ,雖然它是我瞎編的,但I(xiàn)P頭還是得老老實(shí)實(shí)把它加進(jìn)去。
此時(shí)需要重點(diǎn)介紹的是鄰居子系統(tǒng),它在網(wǎng)絡(luò)層和數(shù)據(jù)鏈路層之間??梢酝ㄟ^(guò)ARP協(xié)議將目的IP轉(zhuǎn)為對(duì)應(yīng)的MAC地址,然后數(shù)據(jù)鏈路層就可以用這個(gè)MAC地址組裝幀頭。
我們看下那么ARP協(xié)議的流程是
ARP流程
1.先到本地ARP表查一下有沒(méi)有 192.168.31.7 對(duì)應(yīng)的 mac地址,有的話就返回,這里顯然是不可能會(huì)有的。
可以通過(guò) 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)容就是 前面提到的 “誰(shuí)是 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)都是本地沒(méi)有查到 ARP 緩存記錄的情況,這時(shí)候會(huì)把SYN報(bào)文放進(jìn)一個(gè)隊(duì)列(叫unresolved_queue)里暫存起來(lái),然后發(fā)起ARP請(qǐng)求;等ARP層收到ARP回應(yīng)報(bào)文之后,會(huì)再?gòu)木彺嬷腥〕? SYN 報(bào)文,組裝 MAC 幀頭,完成剛剛沒(méi)完成的發(fā)送流程。
如果經(jīng)過(guò) ARP 流程能正常返回 MAC 地址,那皆大歡喜,直接給數(shù)據(jù)鏈路層,經(jīng)過(guò) ring buffer 后傳到網(wǎng)卡,發(fā)出去。
但因?yàn)楝F(xiàn)在這個(gè)IP是瞎編的,因此不可能得到目的地址 MAC ,所以消息也一直沒(méi)法到數(shù)據(jù)鏈路層。整個(gè)流程卡在了ARP流程中。
而抓包是在數(shù)據(jù)鏈路層之后進(jìn)行的,因此 TCP 第一次握手的包一直沒(méi)能抓到,只能抓到為了獲得 192.168.31.7 的MAC地址的ARP請(qǐng)求。
發(fā)送數(shù)據(jù)時(shí),是在經(jīng)過(guò)數(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é)議,對(duì)于 TCP 層來(lái)說(shuō),第一次握手的消息,已經(jīng)發(fā)出去了,但是一直沒(méi)有收到 ACK。也不知道消息是出去后是遇到什么事了。為了保證可靠性,它會(huì)不斷重發(fā)。
而每一次重發(fā),都會(huì)因?yàn)橥瑯拥脑?沒(méi)有目的 MAC 地址)而尬在了 ARP 那個(gè)流程里。因此,才看到好幾次重復(fù)的 ARP 消息。
那回到剛剛的三個(gè)問(wèn)題
- 為什么會(huì)發(fā) ARP 請(qǐng)求?
因?yàn)槟康牡刂肥窍咕幍?,本地ARP表沒(méi)有目的機(jī)器的MAC地址,因此發(fā)出ARP消息。
- 為什么沒(méi)有 TCP 握手包?
因?yàn)閰f(xié)議棧的數(shù)據(jù)到了網(wǎng)絡(luò)層后,在數(shù)據(jù)鏈路層前,就因?yàn)闆](méi)有目的MAC地址,沒(méi)法發(fā)出。因此抓包軟件抓不到相關(guān)數(shù)據(jù)。
- 為什么 ARP 請(qǐng)求會(huì)發(fā)那么多遍?
因?yàn)?TCP 協(xié)議的可靠性,會(huì)重發(fā)第一次握手的消息,但每一次都因?yàn)闆](méi)有目的 MAC 地址而失敗,每次都會(huì)發(fā)出ARP請(qǐng)求。
小結(jié)
連一個(gè) IP 不存在的主機(jī)時(shí),如果目的IP在局域網(wǎng)內(nèi),則第一次握手會(huì)失敗,接著不斷嘗試重發(fā)握手的請(qǐng)求。同時(shí),本機(jī)會(huì)不斷發(fā)出ARP請(qǐng)求,企圖獲得目的機(jī)器的 MAC 地址。并且,因?yàn)闆](méi)能獲得目的 MAC 地址,這些 TCP 握手請(qǐng)求最終都發(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。
先抓包看一下。
連一個(gè)不存在的IP(局域網(wǎng)外)抓包
這次的現(xiàn)象是能發(fā)出 TCP 第一次握手的 SYN包。
這里有兩個(gè)問(wèn)題
- 為什么連局域網(wǎng)外的 IP 現(xiàn)象跟連局域網(wǎng)內(nèi)不一致?
- TCP 第一次握手的重試規(guī)律好像不太對(duì)?
為什么連局域網(wǎng)外的IP現(xiàn)象跟連局域網(wǎng)內(nèi)不一致?
這個(gè)問(wèn)題的答案其實(shí)在上面 ARP 的流程里已經(jīng)提到過(guò)了,如果目的 IP 跟本機(jī) IP 不在同一個(gè)局域網(wǎng)下,那么會(huì)去獲取默認(rèn)網(wǎng)關(guān)的 MAC 地址,這里就是指獲取家用路由器的MAC地址。
此時(shí)ARP流程成功返回家用路由器的 MAC 地址,數(shù)據(jù)鏈路層加入幀頭,消息通過(guò)網(wǎng)卡發(fā)到了家用路由器上。
消息會(huì)通過(guò)互聯(lián)網(wǎng)一直傳遞到某個(gè)局域網(wǎng)為 10.225.31.xx 的路由器上,那個(gè)路由器 發(fā)出ARP 請(qǐng)求,詢問(wèn)他們局域網(wǎng)內(nèi)的機(jī)器有沒(méi)有叫 10.225.31.11的 (結(jié)果當(dāng)然沒(méi)有)。
最終沒(méi)能發(fā)送成功,發(fā)送端也就遲遲收不到目的機(jī)的第二次握手響應(yīng)。
因此觸發(fā)TCP重傳。
TCP第一次握手的重試規(guī)律好像不太對(duì)?
在 Linux 中,第一次握手的 SYN 重傳次數(shù),是通過(guò) tcp_syn_retries 參數(shù)控制的??梢酝ㄟ^(guò)下面的方式查看
- $cat /proc/sys/net/ipv4/tcp_syn_retries
- 6
這里的含義是指 syn重傳 會(huì)發(fā)生6次。
而每次重試都會(huì)間隔一定的時(shí)間,這里的間隔一般是 1s,2s,4s,8s, 16s, 32s .
SYN重傳
而事實(shí)上,看我的截圖,是先重試4次,每次都是1s,之后才是 1s,2s,4s,8s, 16s, 32s 的重試。
這跟我們知道的不太一樣。
這個(gè)是因?yàn)槲矣玫氖莔acOS抓的包,跟linux就不是一個(gè)系統(tǒng),各自的TCP協(xié)議棧在sync重傳方面的實(shí)現(xiàn)都可能會(huì)有一定的差異。
我還聽(tīng)說(shuō) oppo 和 vivo 的 syn重傳 是0.5s起步的。而 windows 的 syn重傳 還有自己的專利。
這些冷知識(shí)大家可以不用在意。面試的時(shí)候知道linux的就夠了,剩下的可以用來(lái)裝逼。畢竟面試官不在意"茴"字到底有幾種寫法。
連IP 地址存在但端口號(hào)不存在的主機(jī)的握手過(guò)程
前面提到的是IP地址壓根就不存在的情況。假如IP地址存在但端口號(hào)是瞎編的呢?
目的IP是回環(huán)地址
連回環(huán)地址,端口不存在抓包
現(xiàn)象也比較簡(jiǎn)單,已經(jīng)IP地址是存在的,也就是在互聯(lián)網(wǎng)中這個(gè)機(jī)器是存在的。
那么我們可以正常發(fā)消息到目的IP,因?yàn)閷?duì)應(yīng)的MAC地址和IP都是正確的,所以,數(shù)據(jù)從數(shù)據(jù)鏈路層到網(wǎng)絡(luò)層都很OK。
直到傳輸層,TCP協(xié)議在識(shí)別到這個(gè)端口號(hào)對(duì)應(yīng)的進(jìn)程根本不存在時(shí),就會(huì)把數(shù)據(jù)丟棄,響應(yīng)一個(gè)RST消息給發(fā)送端。
連回環(huán)地址時(shí)端口不存在
RST是什么?
我們都是到TCP正常情況下斷開連接是用四次揮手,那是正常時(shí)候的優(yōu)雅做法。
但異常情況下,收發(fā)雙方都不一定正常,連揮手這件事本身都可能做不到,所以就需要一個(gè)機(jī)制去強(qiáng)行關(guān)閉連接。
RST 就是用于這種情況,一般用來(lái)異常地關(guān)閉一個(gè)連接。它在TCP包頭中,在收到置了這個(gè)標(biāo)志位的數(shù)據(jù)包后,連接就會(huì)被關(guān)閉,此時(shí)接收到 RST的一方,一般會(huì)看到一個(gè) connection reset 或 connection refused 的報(bào)錯(cuò)。
TCP報(bào)頭RST位
目的IP在局域網(wǎng)內(nèi)
剛剛提到我的本機(jī)IP是 192.168.31.6 ,局域網(wǎng)內(nèi)有臺(tái) 192.168.31.1 。同樣嘗試連一個(gè)不存在的端口。
連存在的局域網(wǎng)內(nèi)IP,端口不存在抓包
此時(shí)現(xiàn)象跟前者一致。
唯一不同的是,前者是回環(huán)地址,RST數(shù)據(jù)是從本機(jī)的傳輸層返回的。而這次的情況,RST數(shù)據(jù)是從目的機(jī)器的傳輸層返回的。
連外網(wǎng)地址時(shí)端口不存在
目的IP在局域網(wǎng)外
找一個(gè)存在的外網(wǎng)ip,這里我拿了最近剛白嫖的阿里云服務(wù)器地址 47.102.221.141 。(炫耀)
進(jìn)行連接連接,發(fā)現(xiàn)與前面兩種情況是一致的,目的機(jī)器在收到我的請(qǐng)求后,立馬就通過(guò) RST標(biāo)志位 斷開了這次的連接。
連存在的局域網(wǎng)外IP,端口不存在抓包
這一點(diǎn)跟前面兩種情況一致。
熟悉小白的朋友們都知道,每次搞事情做測(cè)試,都會(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, 抓包。
連baidu,端口不存在抓包
現(xiàn)象卻不一致。沒(méi)有 RST 。而且觸發(fā)了第一次握手的重試消息。這是為什么?
這是因?yàn)閎aidu的機(jī)器,作為線上生產(chǎn)的機(jī)器,會(huì)設(shè)置一系列安全策略,比如只對(duì)外暴露某些端口,除此之外的端口,都一律拒絕。
所以很多發(fā)到 8080端口的消息都在防火墻這一層就被拒絕掉了,根本到不了目的主機(jī)里,而RST是在目的主機(jī)的TCP/IP協(xié)議棧里發(fā)出的,都還沒(méi)到這一層,就更不可能發(fā)RST了。因此發(fā)送端發(fā)現(xiàn)消息沒(méi)有回應(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請(qǐng)求獲得目的主機(jī)的MAC地址,同時(shí)不能發(fā)出TCP握手消息。
- 如果IP在局域網(wǎng)外,會(huì)將消息通過(guò)路由器發(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ā)到不對(duì)外暴露的端口,那么這種情況,發(fā)送端就會(huì)不斷重試第一次握手。
最后留個(gè)問(wèn)題,連一個(gè) 不存在的局域網(wǎng)外IP的主機(jī)時(shí),我們可以看到TCP的重發(fā)規(guī)律是:開始時(shí),每隔1s重發(fā)五次 TCP SYN消息,接著2s,4s,8s,16s,32s都重發(fā)一次;
對(duì)比連一個(gè) 不存在的局域網(wǎng)內(nèi)IP的主機(jī)時(shí),卻是每隔1s重發(fā)了4次ARP請(qǐng)求,接著過(guò)了32s后才再發(fā)出一次ARP請(qǐng)求。已知ARP請(qǐng)求是沒(méi)有重傳機(jī)制的,它的重試就是TCP重試觸發(fā)的,但兩者規(guī)律不一致,是為什么?