為什麼我在公司裡訪問不了家裡的電腦?

2023.01.18

為什麼我在公司裡訪問不了家裡的電腦?

端口已經被udp用過了,TCP再用,那豈不是端口重複佔用(address already in use)?

上篇文章「為什麼我們家裡的IP都是192.168開頭的?」提到,因為IPv4地址有限,最大42億個。為了更好的利用這有限的IP數量,網絡分為局域網和廣域網,將IP分為了私有IP和公網IP,一個局域網裡的N多台機器都可以共用一個公網IP,從而大大增加了"可用IP數量"。

圖片

收發數據就像收發快遞

當我們需要發送網絡包的時候,在IP層,需要填入源IP地址,和目的IP地址,也就是對應快遞的發貨地址和收貨地址。

圖片

IP報頭里含有發送和接收IP地址

但是我們家裡的局域網內,基本上都用192.168.xx.xx這樣的私有IP。

如果我們在發送網絡包的時候,這麼填。對方在回數據包的時候該怎麼回?畢竟千家萬戶人用的都是192.168.0.1,網絡怎麼知道該發給誰?

所以肯定需要將這個192.168.xx私有IP轉換成公有IP。

因此在上篇文章最後,留了這麼個問題。局域網內用的是私有IP,公網用的都是公有IP。一個局域網裡的私有IP想訪問局域網外的公有IP,必然要做個IP轉換,這是在哪裡做的轉換呢?

圖片

私有IP和公有IP在哪進行轉換

答案是NAT設備,全稱Network Address Translation,網絡地址轉換。基本上家用路由器都支持這功能。

我們來聊下它是怎麼工作的。

NAT的工作原理

為了簡單,我們假設你很富,你家里分到了一個公網IP地址 20.20.20.20,對應配到了你家自帶NAT功能的家用路由器上,你家裡需要上網的設備有很多,比如你的手機,電腦都需要上網,他們構成了一個局域網,用的都是私有IP,比如192.168.xx。其中你在電腦上執行ifconfig命令,發現家裡的電腦IP是192.168.30.5。你要訪問的公網IP地址是30.30.30.30。

於是就有下面這樣一張圖

圖片

內網IP訪問公網IP

當你準備發送數據包的時候,你的電腦內核協議棧就會構造一個IP數據包。這個IP數據包報頭里的發送端IP地址填的就是192.168.30.5,接收端IP地址就是30.30.30.30。將數據包發到NAT路由器中。

此時NAT路由器會將IP數據包裡的源IP地址修改一下,私有IP地址192.168.30.5改寫為公網IP地址20.20.20.20,這叫SNAT(Source Network Address Translation,源地址轉換)。並且還會在NAT路由器內部留下一條 192.168.30.5 -> 20.20.20.20的映射記錄,這個信息會在後面用到。之後IP數據包經過公網裡各個路由器的轉發,發到了接收端30.30.30.30,到這裡發送流程結束。

圖片

SNAT

如果接收端處理完數據了,需要發一個響應給你的電腦,那就需要將發送端IP地址填上自己的30.30.30.30,將接收端地址填為你的公網IP地址20.20.20.20,發往NAT路由器。NAT路由器收到公網來的消息之後,會檢查下自己之前留下的映射信息,發現之前留下了這麼一條 192.168.30.5 -> 20.20.20.20記錄,就會將這個數據包的目的IP地址修改一下,變成內網IP地址192.168.30.5, 這也叫DNAT(Destination Network Address Translation,目的地址轉換)。之後將其轉發給你的電腦上。

圖片

DNAT

整個過程下來,NAT悄悄的改了IP數據包的發送和接收端IP地址,但對真正的發送方和接收方來說,他們卻對這件事情,一無所知。

這就是NAT的工作原理。

NAPT的原理

到這裡,相信大家都有一個很大的疑問。

局域網裡並不只有一台機器,局域網內每台機器都在NAT下留下的映射信息都會是 192.168.xx.xx -> 20.20.20.20,發送消息是沒啥事,但接收消息的時候就不知道該回給誰了。

圖片

NAT的問題

這問題相當致命,因此實際上大部分時候不會使用普通的NAT。

那怎麼辦呢?

問題出在我們沒辦法區分內網裡的多個網絡連接。

於是乎。

我們可以加入其他信息去區分內網裡的各個網絡連接,很自然就能想到端口。

但IP數據包(網絡層)本身是沒有端口信息的。常見的傳輸層協議TCP和UDP數據報文裡才有端口的信息。

圖片

TCP報頭有端口號

圖片

UDP報頭也有端口號

於是流程就變成了下面這樣子。

當你準備發送數據包的時候,你的電腦內核協議棧就會先構造一個TCP或者UDP數據報頭,裡面寫入端口號,比如發送端口是5000,接收端口是3000,然後在這個基礎上,加入IP數據報頭,填入發送端和接收端的IP地址。

那數據包長這樣。

圖片

數據包的構成

假設,發送端IP地址填的就是192.168.30.5,接收端IP地址就是30.30.30.30。

將數據包發到NAT路由器中。

此時NAT路由器會將IP數據包裡的源IP地址和端口號修改一下,從192.168.30.5:5000改寫成20.20.20.20:6000。並且還會在NAT路由器內部留下一條 192.168.30.5:5000 -> 20.20.20.20:6000的映射記錄。之後數據包經過公網裡各個路由器的轉發,發到了接收端30.30.30.30:3000,到這裡發送流程結束。

圖片

NAPT發送數據

接收端響應時,就會在數據包裡填入發送端地址是30.30.30.30:3000,將接收端是20.20.20.20:6000,發往NAT路由器。NAT路由器發現下自己之前留下過這麼一條 192.168.30.5:5000 -> 20.20.20.20:6000的記錄,就會將這個數據包的目的IP地址和端口修改一下,變回原來的192.168.30.5:5000。之後將其轉發給你的電腦上。

圖片

NAPT接收數據

如果局域網內有多個設備,他們就會映射到不同的公網端口上,畢竟端口最大可達65535,完全夠用。這樣大家都可以相安無事。

像這種同時轉換IP和端口的技術,就是NAPT(Network Address Port Transfer , 網絡地址端口轉換 )。

看到這裡,問題就來了。

那這麼說只有用到端口的網絡協議才能被NAT識別出來並轉發?

但這怎麼解釋ping命令?ping基於ICMP協議,而ICMP協議報文裡並不帶端口信息。我依然可以正常的ping通公網機器並收到回包。

圖片

ping報頭

事實上針對ICMP協議,NAT路由器做了特殊處理。ping報文頭里有個Identifier的信息,它其實指的是放出ping命令的進程id。

對NAT路由器來說,這個Identifier的作用就跟端口一樣。

另外,當我們去抓包的時候,就會發現有兩個Identifier,一個後面帶個BE(Big Endian),另一個帶個LE(Little Endian)。

其實他們都是同一個數值,只不過大小端不同,讀出來的值不一樣。就好像同樣的數字345,反著讀就成了543。這是為了兼容不同操作系統(比如linux和Windows)下大小端不同的情況。

圖片

內網穿透是什麼

看到這裡,我們大概也發現了。使用了NAT上網的話,前提得內網機器主動請求公網IP,這樣NAT才能將內網的IP端口轉成外網IP端口。

反過來公網的機器想主動請求內網機器,就會被攔在NAT路由器上,此時由於NAT路由器並沒有任何相關的IP端口的映射記錄,因此也就不會轉發數據給內網裡的任何一台機器。

舉個現實中的場景就是,你在你家裡的電腦上啟動了一個HTTP服務,地址是192.168.30.5:5000,此時你在公司辦公室裡想通過手機去訪問一下,卻發現訪問不了。

那問題就來了,有沒有辦法讓外網機器訪問到內網的服務?

有。

大家應該聽過一句話叫,"沒有什麼是加中間層不能解決的,如果有,那就再加一層"。

放在這裡,依然適用。

說到底,因為NAT的存在,我們只能從內網主動發起連接,否則NAT設備不會記錄相應的映射關係,沒有映射關係也就不能轉發數據。

所以我們就在公網上加一台服務器x,並暴露一個訪問域名,再讓內網的服務主動連接服務器x,這樣NAT路由器上就有對應的映射關係。接著,所有人都去訪問服務器x,服務器x將數據轉發給內網機器,再原路返迴響應,這樣數據就都通了。這就是所謂的內網穿透。

像上面提到的服務器x,你也不需要自己去搭,已經有很多現成的方案,花錢就完事了,比如花某殼。

圖片

內網穿透

到這裡,我們就可以回答文章標題的問題。

為什麼我在公司裡訪問不了家裡的電腦?

那是因為家裡的電腦在局域網內,局域網和廣域網之間有個NAT路由器。由於NAT路由器的存在,外網服務無法主動連通局域網內的電腦。

兩個內網的聊天軟件如何建立通訊

好了,問題就叒來了。

我家機子是在我們小區的局域網裡,班花家的機子也是在她們小區的局域網裡。都在局域網裡,且NAT只能從內網連到外網,那我電腦上登錄的QQ是怎麼和班花電腦裡的QQ連上的呢?

圖片

兩個局域網內的服務無法直接連通

上面這個問法其實是存在個誤解,誤以為兩個qq客戶端應用是直接建立連接的。

然而實際上並不是,兩個qq客戶端之間還隔了一個服務器。

圖片

聊天軟件會主動與公網服務器建立連接

也就是說,兩個在內網的客戶端登錄qq時都會主動向公網的聊天服務器建立連接,這時兩方的NAT路由器中都會記錄有相應的映射關係。當在其中一個qq上發送消息時,數據會先到服務器,再通過服務器轉發到另外一個客戶端上。反過來也一樣,通過這個方式讓兩台內網的機子進行數據傳輸。

兩個內網的應用如何直接建立連接

上面的情況,是兩個客戶端通過第三方服務器進行通訊,但有些場景就是要拋開第三端,直接進行兩端通信,比如P2P下載,這種該怎麼辦呢?

這種情況下,其實也還是離不開第三方服務器的幫助。

假設還是A和B兩個局域網內的機子,A內網對應的NAT設備叫NAT_A,B內網裡的NAT設備叫NAT_B,和一個第三方服務器server。

流程如下。

step1和2: A主動去連server,此時A對應的NAT_A就會留下A的內網地址和外網地址的映射關係,server也拿到了A對應的外網IP地址和端口。

step3和4: B的操作和A一樣,主動連第三方server,NAT_B內留下B的內網地址和外網地址的映射關係,然後server也拿到了B對應的外網IP地址和端口。

step5和step6以及step7: 重點來了。此時server發消息給A,讓A主動發UDP消息到B的外網IP地址和端口。此時NAT_B收到這個A的UDP數據包時,這時候根據NAT_B的設置不同,導致這時候有可能NAT_B能直接轉發數據到B,那此時A和B就通了。但也有可能不通,直接丟包,不過丟包沒關係,這個操作的目的是給NAT_A上留下有關B的映射關係。

step8和step9以及step10: 跟step5一樣熟悉的配方,此時server再發消息給B,讓B主動發UDP消息到A的外網IP地址和端口。NAT_B上也留下了關於A到映射關係,這時候由於之前NAT_A上有過關於B的映射關係,此時NAT_A就能正常接受B的數據包,並將其轉發給A。到這裡A和B就能正常進行數據通信了。這就是所謂的NAT打洞。

step11: 注意,之前我們都是用的UDP數據包,目的只是為了在兩個局域網的NAT上打個洞出來,實際上大部分應用用的都是TCP連接,所以,這時候我們還需要在A主動向B發起TCP連接。到此,我們就完成了兩端之間的通信。

圖片

NAT打洞

這裡估計大家會有疑惑。

端口已經被udp用過了,TCP再用,那豈不是端口重複佔用(address already in use)?

其實並不會,端口重複佔用的報錯常見於兩個TCP連接在不使用SO_REUSEADDR的情況下,重複使用了某個IP端口。而UDP和TCP之間卻不會報這個錯。之所以會有這個錯,主要是因為在一個linux內核中,內核收到網絡數據時,會通過五元組(傳輸協議,源IP,目的IP,源端口,目的端口)去唯一確定數據接受者。當五元組都一模一樣的時候,內核就不知道該把數據發給誰。而UDP和TCP之間"傳輸協議"不同,因此五元組也不同,所以也就不會有上面的問題。

圖片

五元組

NAPT還分為好多種類型,上面的nat打洞方案,都能成功嗎?

關於NAPT,確實還細分為好幾種類型,比如完全錐形NAT和限制型NAT啥的,但這並不是本文的重點。所以我就略過了。我們現在常見的都是錐形NAT。上面的打洞方案適用於大部分場景,這其中包括限制最多的端口受限錐形NAT。

圖片

總結

• IPV4地址有限,但通過NAT路由器,可以使得整個內網N多台機器,對外只使用一個公網IP,大大節省了IP資源。

• 內網機子主動連接公網IP,中間的NAT會將內網機子的內網IP轉換為公網IP,從而實現內網和外網的數據交互。

• 普通的NAT技術,只會修改網絡包中的發送端和接收端IP地址,當內網設備較多時,將有可能導致衝突。因此一般都會使用NAPT技術,同時修改發送端和接收端的IP地址和端口。

• 由於NAT的存在,公網IP是無法訪問內網服務的,但通過內網穿透技術,就可以讓公網IP訪問內網服務。一波操作下來,就可以在公司的網絡裡訪問家裡的電腦。

最後留個問題,有了NAT之後,原本並不富裕的IPv4地址突然就變得非常夠用了。

那我們為什麼還需要IPv6?

另外IPv6號稱地址多到每粒沙子都能擁有自己的IP地址,那我們還需要NAT嗎?