網路編程中,對於資料轉送即時性要求較高的場合,大家都會選擇UDP來作為資料轉送協議,在TCP/IP協議族中UDP協議較TCP協議需要的網路系統資源更少。然而在公司專屬應用程式中,由於網路安全原因等會導致除了特定連接埠以外的IP資料無法通過專用的路由或網關。為了支援這類應用,制定了專門的支援Socks串連的socks4/socsk5協議。Socks協議允許實現此類功能的代理軟體可以允許防火牆(本文以下內容中防火牆與代理的稱謂可以等同視之)以 內的客戶通過防火牆實現對外部的訪問,甚至可以允許等待外部的串連。對於防火牆內部的軟體用戶端,僅同防火牆協商,同防火牆的特定連接埠取得聯絡,然後交換 資料,而防火牆外部的程式也直同防火牆進行資料交換,外部看不到防火牆的內部網情況,這樣起到了防火牆的監護功能,也滿足了大多數通過非常用(如http ftp等)連接埠交換資料的應用程式需求。防火牆內部的應用程式如何通過防火牆將UDP資料轉送到防火牆外部,並且接受外部的UDP資料報文,這就是所謂穿透Socks代理的UDP編程。
RFC1928描述了Socks協議的細節,告訴我們客戶程式如何同Socks代理協商,取得透過該協議對外傳輸的途徑。英文的URL為:http://www.ietf.org/rfc/rfc1928.txt,中文的翻譯參考不是很貼切(但譯者還是值得尊敬的),但對於E文不大好的可以將就一下:http://www.china-pub.com/computers/emook/0541/info.htm。建議先瞭解以上連結內容後在閱讀下文。
一般的代理軟體都實現了兩個版本的Socks協議—Socks4以及Socks5,其中Socks5協議支援UDP報文的傳輸以及多種驗證方法,該協議還考慮IP發展需要,支援Ipv6。
TCP透過代理支援兩種方法:Bind以及Connection。Connection是指作為用戶端,主動串連代理外部的服務程式,在這種方式中代理將替代客戶程式發起真正的對外部服務程式的串連,並來回傳輸在此串連中需要交流的資料;Bind方式則用在那些需要客戶機接受到伺服器串連的協議中,例如FTP協議之類的除了需要建立一個客戶--伺服器的串連報告狀態外,還需要建立一個伺服器—客戶的串連來傳輸實際的資料(當然要注意這裡的FTP協議通過Socks協議串連遠程主機,並非通過FTP代理協議)。UDP報文傳輸則意味著代理充當UDP資料轉送的中間人,將防火牆內的主機對外的資料傳遞出去,將需要引入到防火牆內的UDP資料報文轉給防火牆特定的主機。關於TCP穿透的討論和例子很多(給出一個實現的例子:http://www.codeproject.com/internet/casyncproxysocket.asp ),就不多講了,在此著重討論如何?UDP資料的穿透Socks5代理。
為了測試方便我簡單寫了一個服務進程在代理外IP為192。168。0。250上監聽UDP8100連接埠,接收到一個UDP的資料報後,返回伺服器上的目前時間給發送UDP報文的用戶端。代理採用Wingate,他在192。168。0。1上運行,Socks的標準連接埠1080來運行服務監聽程式。我的機器為192。168。0。10,你可以看到,我不能夠直接聯絡已耗用時間服務的機器,我會向代理提出我的要求,由代理進程負責UDP資料報文的轉寄。代理軟體選擇Wingate,並且為了簡單起見,採用不需要驗證客戶的驗證方法。好了,交代了背景後,下面我們就開始穿越代理的旅程吧。
無論是TCP還是UDP通過代理,首先要同代理取得聯絡。為了能夠確保在第一階段順利確保資料轉送,協議規定用戶端採用TCP方式串連聯絡Proxy 伺服器。
一旦客戶同代理的1080連接埠串連上,客戶首先要發送一個版本標識/方法選擇的TCP報文給Proxy 伺服器,具體格式為:
版本號碼(1位元組) | 可供選擇的認證方法(1位元組) | 方法序列(1-255個位元組長度)
如果是socks4協議,版本號碼就是0x04,但是這裡是支援UDP的Socks5,所以是位元組0x05。此說明對於後面的報文格式解釋的版本部分也都適用。
Socks協議定義了0-255種通過代理的認證方法:
0x00 無驗證需求
0x01 通用安全服務應用程式介面(GSSAPI)
0x02 使用者名稱/密碼(USERNAME/PASSWORD)
0x03 至 X'7F' IANA 分配(IANA ASSIGNED)
0x80 至 X'FE' 私人方法保留(RESERVED FOR PRIVATE METHODS)
0xFF 無可接受方法(NO ACCEPTABLE METHODS)
顯然,無論是發起Socks請求的用戶端還是負責轉寄Socks資料的代理都不可能完全實現所有的(起碼目前還沒有)方法,所以用戶端需要把自己能夠支援的方法列出來供Proxy 伺服器選擇。如果支援無驗證,那麼此報文的位元組序列就為:0x05 0x01 0x00,其中的0x01表示用戶端只支援一種驗證,0x00表示能夠支援的方法是編號為0x00的(無驗證)的方法。如果用戶端還支援使用者名稱/密碼的驗證方式,那麼報文就應當是:0x05 0x02 0x00 0x02。
代理接收到客戶的請求,會根據自身系統的實現返回告訴客戶驗證採用哪一種方法,返回的保文格式為:
版本號碼 | 伺服器選定的方法
如果伺服器僅支援無驗證的驗證方法,它返回位元組序列:0x05 0x00。用戶端同代理的資料報文的來回應答就是Socks協議的驗證方法選擇階段。
接下來就是根據選擇的方法來,驗證客戶身份了。雖然我們這裡不需要驗證,但是還是簡單講一下0x02的使用者名稱/口令的驗證用戶端發送報文格式:
0x01 | 使用者名稱長度(1位元組)| 使用者名稱(長度根據使用者名稱長度域指定) | 口令長度(1位元組) | 口令(長度由口令長度域指定)
不清楚為什麼報文的首位元組是0x01(按照慣例應當是0x05)。整個報文長度根據使用者名稱和口令的實際長度決定。使用者名稱和口令都不需要以’/0’結束。伺服器會根據提供的資訊進行驗證,返回如下的報文位元組序列映像為:
0x01 | 驗證結果標誌
驗證結果標誌可以為:0x00 驗證通過,其餘均表示有故障,不可以繼續下一步的協議步驟。
在通過了驗證步驟之後,接下來就是確定UDP傳輸的連接埠了。這裡面需要確定兩個重要的連接埠:1、用戶端發送UDP資料的本機連接埠,一方面可以為發送資料指定連接埠,另一方面告訴代理,如果有資料返回,就傳遞給該連接埠,構成一個UDP傳輸迴路。2、代理想在哪個連接埠接收客戶發送的UDP資料報,作為對外UDP Socket的申請方,雙方協商確定一個連接埠後,可以持續通過此連接埠向外部主機發送資料,也可以通過此連接埠由代理接收外部主機發回的UDP資料,再通過此連接埠發給UDP發送請求用戶端。用戶端會按照以下格式發送TCP資料位元組序列:
協議版本 | Socks命令 | 保留位元組 | 地址類型 | 特定地址 | 特定連接埠
Socks命令有3種:CONNECT (編號0x01) BIND (0x02) UDP(編號0x03)
保留位元組長度1,為0x00
地址類型有3種:
0X01 該地址是IPv4地址,長4個8bit位元組。
0X03 該地址包含一個完全的網域名稱。第一個8bit位元組包含了後面名稱的8bit的數目,沒有中止的’/0’。
0X04 該地址是IPv6地址,長16個8bit位元組。
特定地址一般對於多IP的主機有意義,如果不是或者不關心哪一個IP發起UDP資料轉送,就可以填0。0。0。0,地址類型選擇0x01。比較重要的就是UDP傳輸將要從哪一個UDP連接埠發起。一般為了避免因為硬性指定一個連接埠導致引起衝突,會首先產生一個UDP通訊端,用產生的通訊端既定連接埠來作為自己傳輸UDP的連接埠,並通過此步驟告知Proxy 伺服器。譬如臨時產生一個UDP通訊端,UDP選擇連接埠
2233作為傳輸UDP資料的本地連接埠,那麼此報文就為:0x05 0x03 0x00 0x00 0x00 0x00 0x00 0x08 0xb9 其中0x08 0xb9換算成10進位就是2233。
Proxy 伺服器會根據自己的連接埠佔用情況,給出一個有關Proxy 伺服器的連接埠的回複位元組序列,告訴客戶可以將UDP資料發送到此地址和連接埠中去,以實現UDP穿透代理。返回的位元組序列為:
版本 | 代理的應答 | 保留1位元組 | 地址類型 | Proxy 伺服器地址 | 綁定的代理連接埠
代理的應答可以為值:
0X00 成功協商
0X01 常見的Socks故障
0x02 不允許串連
0X03 網路不可到達
0X04 主機不可到達
0X05 串連被重設
0X06 TTL 失效
0X07 命令不支援
0X08 地址類型不支援
0X09 一直到0xff都保留
代理的地址指用戶端需要發給那一個IP,綁定的連接埠指代理將在哪一個連接埠上為客戶接收資料並轉寄出去。地址類型、地址參照上面的解釋。
通過以上的TCP協商幾個步驟後,現在用戶端明確了自己將需要發送的UDP資料發給Proxy 伺服器的某個IP的某個連接埠了。Proxy 伺服器也知道是哪一個IP發送資料報給自己,如果接收到由於轉寄此UDP資料報而從遠端目標主機傳回的資料報,他需要根據協議將收到的資料報返回給客戶的特定連接埠。此特定連接埠就是此步驟中位元組序列中綁定的代理連接埠
在傳輸UDP資料時,由於通過代理,所以需要按照一定的格式進行封裝,在需要傳送的資料之前添加一個前序,具體為:
保留2位元組的0 | 是否資料報分段重組標誌 | 地址類型 | 將要發到代理外的目標地址 | 遠端目標主機的連接埠 | 需要通過代理傳送出去的資料
是否資料報分段重組標誌為0表示該資料報文是獨立的不需要重新組合,其他的表示特定的序號,以利於UDP報文整合。
這裡的地址是最終接收此UDP資料的代理外的伺服器位址,我們這個例子中就是192。168。0。250。連接埠就是8100。根據地址類型不同,具體的需要傳送的資料起始位置也不同。如果是Ipv4,那麼資料從整個UDP報文的10位元組處開始,如果是指定了網域名稱,那麼就是從262處開始,Ipv6地址類型就從20處開始為資料欄位。這些需要我們在實際傳輸資料時注意。假如要傳送10位元組的資料9到96.96.96.96的1024連接埠,那麼傳送的資料位元組序列大致為:
00 00 00 01 60 60 60 60 04 00 09 09 09 …….09
保留 是否分段重組 Ipv4 96.96.96.96目標主機IP 連接埠1024 從此處開始為資料
而這之後,如果遠端的目標主機有資料返回,Proxy 伺服器會在將資料傳回給UDP 用戶端時將資料也做類似上面的封裝,即添加一個前序。客戶需要接收這個前序,實際上也明確通知UDP 用戶端,這個資料報是哪一個伺服器發回的。
下面就來看一看給出的代碼:見附件工程。我將支援Socks5的UDP寫成一個Java類,供大家參考。相關解釋見注釋部分。
通過上面分析我們可以大體上總結到透過Socks5進行UDP編程需要注意的幾點:
1、 Socks5編程的身分識別驗證
由於防火牆作用幾乎是隔絕內外的非正常串連,而Socket可以通過任何連接埠串連到外部,所以作為對Socket4的改進,Socket5增加了對socket協議訪問的驗證功能。這些驗證功能沒有規定一定採用什麼方法,一般看防火牆自身支援以及用戶端能夠支援什麼方法,這意味著作為用戶端必須將自己支援的方法在協商階段之初就告訴Proxy 伺服器,而Proxy 伺服器自己根據已經實現支援哪種驗證方法而選擇特定的方法回複用戶端。意味著針對不同的Proxy 伺服器以及不同的用戶端,很可能對於驗證方法支援上有區別,需要視具體的應用環境而定。這些增加了
| |
| |
| Socket5用戶端以及Proxy server軟體的編寫難度,但是增強了安全性。 2、 TCP保持重要性 要發送穿透Proxy 伺服器的UDP資料報,其實首先需要建立用戶端到Proxy 伺服器的TCP串連,通過一系列的互動,獲得Proxy 伺服器的許可才能夠發送出去(同時Proxy 伺服器業記錄下串連的在Socks5服務的客戶IP和連接埠),也確保從遠端發回的資料能夠通過代理服務啟發回給某個UDP用戶端(因為它登記了一個關於Socket UDP的通路映射)。所以為了發送UDP資料,必須建立和保持這個TCP資料。RFC1928也提到,不能取得Proxy 伺服器的通道後就關閉TCP串連,否則Proxy 伺服器以為UDP Socket通過代理的請求已經結束,不需要繼續保留UDP的對外Socket映射記錄,從而導致每發送一次UDP就要重建立立TCP串連協商UDP映射,增加不必要的麻煩。所以,我們需要保持UDP用戶端到到Proxy 伺服器的TCP串連持久,不必顯式關閉它。 3、 UDP本地連接埠選定 UDP大多數是同具體連接埠相關的,所以一定要在同Proxy 伺服器協商UDP映射時告知用戶端UDP的連接埠。一來將UDP同某個連接埠綁定,使得Proxy 伺服器接收UDP資料並轉寄,二來也告訴了Proxy 伺服器將來在某個連接埠發出去後得到的反饋資料也按照線路返回給客戶的此連接埠。這一點很重要,筆者在此處犯了錯誤導致浪費了很多時間。 4、 TCP/UDP串連二重性 可以看到Socks5 的使用會佔用至少一個TCP串連,這樣導致Proxy 伺服器的負擔很重。所以在具體的應用時,需要考慮關於Proxy 伺服器的存在的負載問題。 |