天天看點

TCP穿越NAT

 QUOTE][i]最初由 baul 釋出[/i]

[B]偶然發現的東西,也不知道有沒有用(反正我也不懂 ),放上來給有興趣的參詳吧……

還有看到文中提到的《P2P 之 UDP穿透NAT的原理與實作(附源代碼)》,會不會是……嘿嘿

P2P 之 TCP穿透NAT的原理

翻譯:navy

原文:[url]http://www.andrew.cmu.edu/user/ggw/WBFD.pdf[/url]

由于工作需要,在網上找了很長時間P2P的資料,好像隻有《P2P 之 UDP穿透NAT的原理與實作(附源代碼)》比較有實際意義,可惜又是用UDP實作的,無奈隻好找了份英文資料啃,發現很有參考價值,就根據了解翻譯了一部分,分享給大家,由于水準有限,有錯望多多包涵。如果有人能夠繼續翻譯下去别忘了通知我哦呵呵,[email protected],感謝。

摘要

防火牆和網絡位址轉換(NAT)裝置對于傳統的P2P協定存在一定的問題。一些中間裝置抑制了來自外部網絡到内部網絡的TCP請求,這篇文章的目的就是尋找一個能夠在兩個NAT裝置内部的主機間建立TCP連接配接的方法。我們已經在兩個普通的硬體條件下實作了這個功能。

1.入門

由于32位IP位址的減少,現在出現了很多通過一個internet位址代理内部網絡上網的裝置,這就是NAT技術。這些裝置對于INTERNET已經越來越重要了,但是它們的獨立發展因為缺乏标準而對現在的internet協定造成危害。

2.技術

典型的NAT和防火牆裝置都是不允許外部位址主動請求而進入内部網絡的,如果使用者的程式需要在兩個内部網絡間建立直接性的連接配接,那麼兩個内部裝置之間必須是互相信任的。如果A和B兩個部分都初始化了TCP連接配接,NAT裝置就會認為它們之間是互相信任的,也就允許它們之間的連接配接了。

圖1是一個例子,目标是能夠讓A和B(分别在NATA和NATB後)建立TCP連接配接。

我們讨論了多種在特定的NAT裝置環境下的TCP連接配接方案。

如果我們的情況如下:

1、可預測NA的端口,可預測NB的端口,可指定源IP的特定路由

2、可預測NA的端口,可預測NB的端口,不可指定源IP的特定路由

3、随機的NA端口,可預測NB的端口,可指定源IP的特定路由

4、随機的NA端口,可預測NB的端口,不可指定源IP的特定路由

5、随機的NA端口,随機的NB端口,可指定源IP的特定路由

6、随機的NA端口,随機的NB端口,不可指定源IP的特定路由

我們必須作出這4種假設:

1、 兩個主機都不受NAT裝置所限制;

2、 我們可以配置網絡裝置使得主機看不到來自外部網絡的ICMP包(TTL超過限制),因為這些ICMP資料包無論被任何一方接收到都是中斷TCP連接配接。我們讨論的一些解決方案就依賴通過發送一個初始TTL很小的SYN包來建立TCP連接配接。一旦SYN包被路由器丢棄,ICMP TTL 逾時包就會被傳送到NAT裝置,我們不允許NAT裝置将這個逾時TTL的傳回包傳送到内部網絡,即使NAT會将這個包傳送到内部,也需要通過配置防火牆來限制這個包到達主機;

3、 即使NAT裝置看到ICMP逾時的資料包,裝置本身的映射表将不會作任何改動;

4、 内部網絡的其它主機不會占用搶占這個端口,因為如果網絡特别繁忙,這個端口可能會無效。

3.1第一種情況

我們可以通過圖2表示的順序解決問題:

1)A和B可以設定LSR(IP報頭中的一個選項)通過X路由發送SYN資料包.

2)X可以緩存它們的資料包并且發送欺騙的SYN+ACKS給NA和NB.

3)A和B可以通過由X發送來的資料進行應答.

4)X丢棄這兩個ACK包,因為它已經可以确定A和B互相應答成功.

圖2是假設A和B都事先彼此的NAT的通信端口,A知道B的通信端口是NB:5000,B知道A的是NA:4000,并且要求X不在任何NAT裝置的後面.實際中這兩個端口是預測得到的,預測過程如圖3:

3.2第二種情況

第一種情況依賴與自由設定路由,但是現在很多路由器大多都限制這樣做,并且會丢棄這樣的服務請求包。是以在實際應用中,這種方案失敗的可能性很大。如果自由設定路由不可行,我們可以通過out-of-band通道(他們預先與X連接配接好的TCP連接配接)來傳送原本必須将資料包路由到X才能看到的包。注意在圖二的第二步X已經知道了TCP的序号Q和P,因為X已經收到了這兩個SYN包,但是如果資料包沒有路由經過X就不可能收到它們。為了初始化這個連接配接,兩個主機發送初始SYN包,并且他們都知道是不可能到達目标的,但是它們都可以記住自己的SYN号(個人看法,通過鈎子獲得發送的資料SYN包)并且可以發送給X,X得到了它們的SYN包,就可以欺騙它們發送ACK包了。有兩種方法可以發送無法到達目标的資料包。簡單的方法就是每個主機發送一個SYN給對方,要求應答包不會到達内部網絡.如果NAT(防火牆)會将應答包傳回給内部網絡,通常是發送TCP的reset包(RST),如果NAT生成RST包,A和B就不能簡單地發送一個向圖2中SYN給彼此,因為如果這樣NA和NB就無法打洞了呵呵,如果NAT不發送RST包,那麼這個TCP連接配接就不會被中斷。另外一個發送無法到達目标網絡的SYN包的方法是減小TTL值,使它們無法彼此到達。如果使用者無法配置防火牆丢棄這個ICMP應答包,或者NAT不繼續傳送這個ICMP,這個TCP就不會立即關閉。這個解決方案不能使用一種簡單的欺騙,因為我們必須保證源位址的SYN包發送者不會沒有收到ICMP的RST包,否則會導緻中間裝置建立錯誤路由.僅僅依靠SYN包,NAT就可以建立從internet IP和端口到外部IP和端口的路由.由于欺騙的SYN包是錯誤的源IP(并非發出者X),這個路由将不會發送到X而是發送到NA或者NB。另外,這種方案都需要設定TTL到足夠小,以便于對方的NAT不會收到到各自發出的初始SYN包,否則就無法完成打洞。(圖4)

3.3第三種情況

比前兩種簡單,但是X将無法預見NA或者NB的端口。B将先給X發送一個SYN包告訴以便于X知道它所選用的端口号,然後X将這個資訊發送到A,A就可以向這個确定的位址和端口發送SYN,圖5是第一種情況的變形::

1)X向圖3一樣預測端口,但是它不能預測到NA的下一個端口号,但是可以預測NB的下一個端口号是5000,并且可以通知A和B這個節點已經建立了連接配接;

2)A和B同步節點X;

3)X可以欺騙A和B;

4)A和B互相發送ACK;

5)X丢棄發給它的ACK,因為它已經可以确認它們已經建立連接配接。

3.5. Simultaneous TCP open(TCP同時打開)

在一對節點都在已存在middlebox後,有一種建立直接P2P TCP連接配接的方法有時候會被使用。大多數TCP連接配接都是從一個終端發從一個SYN包到另一個終端,另一個中斷同步響應一個SYN-ACK包。無論怎樣,對于兩個終端來說,同時通過發送同步包到對方然後用一個ACK包應答來建立一個TCP連接配接是可行的。這種過程就被稱為"simultaneous open"(同時打開)

如果一個middlebox從嘗試建立一個TCP連接配接的私有網絡的外面接受一個TCP SYN包,middlebox通常以丢棄這個SYN包或者發送一個TCP RST(連接配接複位)包的方式來拒絕這個連接配接嘗試。但是,如果同步包與源和目的位址端口一起到達,那麼會讓middlebox相信一個TCP連接配接已經建立起來,然後middlebox将會允許資料包通過。特别是如果middlebox剛剛得到并轉換了一個從同樣位址和端口來的SYN包,它将認為連接配接是成立的并允許進來的SYN通過。如果用戶端A和B能彼此預測公共端口,它們各自的middlebox将配置設定下一個TCP連接配接端口,如果其中一個用戶端和另一個用戶端建立一個外部的TCP連接配接,可以在對方SYN到達本地middlebox之前就發送SYN包通過它本地自己的middlebox,那麼P2P TCP連接配接就可以工作了。

令人遺憾的是,這個方法也可能比上面說的UDP端口号預測方法更脆弱并對時效更加敏感。首先,除非在進行TCP連接配接時,兩個middleboxes是簡單的防火牆或者cone NAT,在各自嘗試猜測公共端口号來讓NAT配置設定新的連接配接時,和上面(UDP端口預測)說到的完全一樣的事情會導緻連接配接失敗。另外,如果有一方的客戶發送的同步包太迅速的到達對面的middlebox,遠端middlebox可能會用一個RST包拒絕SYN包,接下來就會導緻本地的middlebox關閉對話并且在将來SYN重發時使用了相同但無用的端口号。最終,對simultaneous open的支援作為一個TCP的特殊應用,沒有在廣泛的系統中被使用。是以,這個方法也隻為曆史因素在這裡被同樣提及;它不建議被應用程式使用。在現有NAT上想要實作P2P直接通訊的應用程式應該使用UDP。

4.4 TCP P2P applications (TCP P2P應用程式)

被程式員們廣泛使用的SOCKET API,常用于C/S結構應用設計中。在它的通常使用方式中,一個SOCKET能綁定一個TCP或UDP端口。一個應用程式不會被允許用同樣的端口(TCP or UDP)和多個SOCKET綁定來和多個外部主機同時建立連接配接(或)用一個SOCKET在端口上監聽而其他SOCKET來建立外部連接配接。但是上述單個SOCKET的端口綁定限制在UDP上不是問題,因為UDP是基于資料封包的協定。UDP P2P應用程式設計者可以用recvfrom()和sendto()函數來讓一個SOCKET不僅發送而且可以從多個主機上接受資料封包。

這不是TCP具有的情況。由于TCP,每個輸入和輸出連接配接都要和一個單獨的SOCKET有聯系。Linux Sockets API用SO_REUSEADDR選項的幫助來解決這個問題(是不是應該這麼說?),這個選項好象不起作用,但可以

(在标準Single Unix裡沒有這個函數)。Win32 API提供了一個相同的調用SetReuseAddress。使用任何上述的選擇,應用程式可以複用一個TCP端口的多個SOCKET。就是說,可以打開兩個綁定在同樣端口上的TCP stream Socket,一個用與listen(),另一個用與其它主機connect() [/B][/QUOTE]

繼續閱讀