TCP打洞技術

2021-09-06 19:18:54 字數 3715 閱讀 8690

//轉

建立穿越nat裝置的p2p的tcp連線僅僅比udp複雜一點點,tcp協議的「打洞」從協議層來看是與udp

的「打洞」過程非常相似的。雖然如此,基於tcp協議的打洞至今為止還沒有被非常好的理解,這也

造成了對其提供支援的nat裝置不是非常多。在nat裝置支援的前提下,基於tcp的「打洞」技術實際上

與基於udp的「打洞」技術一樣快捷、可靠。實際上,僅僅要nat裝置支援的話,基於tcp的p2p技術

的健壯性將比基於udp的技術的更強一些,由於tcp協議的狀態機給出了一種標準的方法來精確的

獲取某個tcp session的生命期,而udp協議則無法做到這一點。

4.1 套接字和tcpport的重用

實現基於tcp協議的p2p「打洞」過程中,最基本的問題不是來自於tcp協議,而是來自於來自於應用

程式的api介面。這是因為標準的伯克利(berkeley)套接字的api是環繞著構建client/server程式

而設計的,api同意tcp流套接字通過呼叫connect()函式來建立向外的連線,或者通過listen()和

accept函式接受來自外部的連線,可是,api不提供類似udp那樣的,同乙個port既能夠向外連線,

又可以接受來自外部的連線。並且更糟的是,tcp的套接字通常僅同意建立1對1的響應,即應用程

序在將乙個套接字繫結到本地的乙個port以後,不論什麼試圖將第二個套接字繫結到該port的操作都會

失敗。為了讓tcp「打洞」可以順利工作,我們須要使用乙個本地的tcpport來監聽來自外部的tcp連線,同一時候

建立多個向外的tcp連線。幸運的是,全部的主流作業系統都可以支援特殊的tcp套接字引數,通常

叫做「so_reuseaddr」,該引數同意應用程式將多個套接字繫結到本地的乙個endpoint(僅僅要全部要

繫結的套接字都設定了so_reuseaddr引數就可以)。bsd系統引入了so_reuseport引數,該引數用於區分

port重用還是位址重用,在這種系統裡面,上述全部的引數必須都設定才行。

4.2 開啟p2p的tcp流

假定clienta希望建立與b的tcp連線。我們像通常一樣假定a和b已經與公網上的已知servers建立了tcp

連線。server記錄下來每乙個聯入的client的公網和內網的endpoints,如同為udp服務的時候一樣。

從協議層來看,tcp「打洞」與udp「打洞」是差點兒全然同樣的過程。

1、clienta使用其與servers的連線向server傳送請求,要求servers協助其連線clientb。

2、s將b的公網和內網的tcp endpoint返回給a,同一時候,s將a的公網和內網的endpoint傳送給b。

3、clienta和b使用連線s的port非同步地發起向對方的公網、內網endpoint的tcp連線,同一時候監聽

各自的本地tcpport是否有外部的連線聯入。

4、a和b開始等待向外的連線是否成功,檢查是否有新連線聯入。假設向外的連線因為某種網路

錯誤而失敗,如:「連線被重置」或者「節點無法訪問」,client僅僅須要延遲一小段時間(比如

延遲一秒鐘),然後又一次發起連線就可以,延遲的時間和反覆連線的次數能夠由應用程式編寫者

來確定。

5、tcp連線建立起來以後,client之間應該開始鑑權操作,確保眼下聯入的連線就是所希望的

連線。假設鑑權失敗,client將關閉連線,而且繼續等待新的連線聯入。client通常採用

「先入為主」的策略,僅僅接受第乙個通過鑑權操作的client,然後將進入p2p通訊過程不再繼續

等待是否有新的連線聯入。

(圖 7)

與udp不同的是,使用udp協議的每乙個client僅僅須要乙個套接字就可以完畢與servers通訊,

並同一時候與多個p2pclient通訊的任務,而tcpclient必須處理多個套接字繫結到同乙個本地

tcpport的問題,如圖7所看到的。

如今來看更加實際的一種情景,a與b分別位於不同的nat裝置後面,如圖5所看到的,而且假定圖中

的port號是tcp協議的port號,而不是udp的port號。圖中向外的連線代表a和b向對方的內網

endpoint發起的連線,這些連線也許會失敗或者無法連線到對方。如同使用udp協議進行「打洞」

操作遇到的問題一樣,tcp的「打洞」操作也會遇到內網的ip與「偽」公網ip反覆造成連線失敗或者

錯誤連線之類的問題。

client向彼此公網endpoint發起連線的操作,會使得各自的nat裝置開啟新的「洞」同意a與b的

tcp資料通過。假設nat裝置支援tcp「打洞」操作的話,乙個在client之間的基於tcp協議的流

通道就會自己主動建立起來。假設a向b傳送的第乙個syn包發到了b的nat裝置,而b在此前沒有向

a傳送syn包,b的nat裝置會丟棄這個包,這會引起a的「連線失敗」或「無法連線」問題。而此時,

因為a已經向b傳送過syn包,b發往a的syn包將被看作是由a發往b的包的回應的一部分,

所以b發往a的syn包會順利地通過a的nat裝置,到達a,從而建立起a與b的p2p連線。

4.3 從應用程式的角度來看tcp「打洞」

從應用程式的角度來看,在進行tcp「打洞」的時候都發生了什麼呢?假定a首先向b發出syn包,

該包發往b的公網endpoint,而且被b的nat裝置丟棄,可是b發往a的公網endpoint的syn包則

通過a的nat到達了a,然後,會發生下面的兩種結果中的一種,詳細是哪一種取決於作業系統

對tcp協議的實現:

(1)a的tcp實現會發現收到的syn包就是其發起連線並希望聯入的b的syn包,通俗一點來說

就是「說曹操,曹操到」的意思,本來a要去找b,結果b自己找上門來了。a的tcp協議棧因此

會把b做為a向b發起連線connect的一部分,並覺得連線已經成功。程式a呼叫的非同步connect()

函式將成功返回,a的listen()等待從外部聯入的函式將沒有不論什麼反映。此時,b聯入a的操作

在a程式的內部被理解為a聯入b連線成功,而且a開始使用這個連線與b開始p2p通訊。

因為收到的syn包中不包括a須要的ack資料,因此,a的tcp將用syn-ack包回應b的公網endpoint,

而且將使用先前a發向b的syn包一樣的序列號。一旦b的tcp收到由a發來的syn-ack包,則把自己

的ack包發給a,然後兩端建立起tcp連線。簡單的說,第一種,就是即使a發往b的syn包被b的nat

丟棄了,可是因為b發往a的包到達了a。結果是,a覺得自己連線成功了,b也覺得自己連線成功

了,無論是誰成功了,總之連線是已經建立起來了。

(2)第二種結果是,a的tcp實現沒有像(1)中所講的那麼「智慧型」,它沒有發現如今聯入的b

就是自己希望聯入的。就好比在機場接人,明明遇到了自己想要接的人卻不認識,誤覺得是其他

的人,安排別人給接走了,後來才知道是自己錯過了機會,可是不管怎樣,人已經接到了任務

已經完畢了。然後,a通過常規的listen()函式和accept()函式得到與b的連線,而由a發起的向

b的公網endpoint的連線會以失敗告終。雖然a向b的連線失敗,a仍然得到了b發起的向a的連線,

等效於a與b之間已經聯通,無論中間過程怎樣,a與b已經連線起來了,結果是a和b的基於tcp協議

的p2p連線已經建立起來了。

第一種結果適用於基於bsd的作業系統對於tcp的實現,而另外一種結果更加普遍一些,多數linux和

windows系統都會依照另外一種結果來處理。

TCP打洞技術

轉 建立穿越nat裝置的p2p的tcp連線只比udp複雜一點點,tcp協議的 打洞 從協議層來看是與udp 的 打洞 過程非常相似的。儘管如此,基於tcp協議的打洞至今為止還沒有被很好的理解,這也 造成了對其提供支援的nat裝置不是很多。在nat裝置支援的前提下,基於tcp的 打洞 技術實際上 與基...

TCP打洞技術

建立穿越nat裝置的p2p的tcp連線只比udp複雜一點點,tcp協議的 打洞 從協議層來看是與udp 的 打洞 過程非常相似的。儘管如此,基於tcp協議的打洞至今為止還沒有被很好的理解,這也 造成了對其提供支援的nat裝置不是很多。在nat裝置支援的前提下,基於tcp的 打洞 技術實際上 與基於u...

TCP打洞技術

建立穿越nat裝置的p2p的tcp連線只比udp複雜一點點,tcp協議的 打洞 從協議層來看是與udp 的 打洞 過程非常相似的。儘管如此,基於tcp協議的打洞至今為止還沒有被很好的理解,這也 造成了對其提供支援的nat裝置不是很多。在nat裝置支援的前提下,基於tcp的 打洞 技術實際上 與基於u...