什麼是HTTP隧道,怎麼理解HTTP隧道呢?

什麼是HTTP隧道,怎麼理解HTTP隧道呢?它是幹什麼用的?主要應用在什麼地方?是為什麼情景而生的?


看標籤題主應該問的是技術上的HTTP隧道而不是應用軟體,剛好我這兩天在實現SOCKS5和HTTP代理讀了一些RFC,如果有不對的地方還請大家多多指正。

先概括的說一下,HTTP tunnel是HTTP/1.1中引入的一個功能,主要為了解決明文的HTTP proxy無法代理跑在TLS中的流量(也就是https)的問題,同時提供了作為任意流量的TCP通道的能力。

其實在增加了HTTP tunnel功能之後,支持CONNECT報文的HTTP代理伺服器和SOCKS5的代理伺服器功能上已經幾乎一樣了(還是有不一樣的地方的,詳見後面)。由於CONNECT報文已經能支持任意類型TCP流量了,即使到HTTP/2也沒有對它進行大的修改,基本保持了原有設計。

HTTP tunnel是哪裡定義的?

tunnel以及tunnel相關的CONNECT報文最早主要定義在兩個文件中:

一個是 RFC2616 HTTP/1.1 中的5.1.1 Method,主要提到了HTTP1.1的所有報文中有一個CONNECT報文,但是寫這個文件的時候CONNECT報文做什麼還沒有定下來。

另一個是 RFC2817 HTTP Upgrade to TLS ,中的多個章節(5.2、5.3等多個地方)提到了CONNECT報文要解決的問題和具體實現。

最後關於HTTP/2,在 RFC 7540 HTTP/2 中的8.3節對http2版本的connect報文做了定義,基本和之前的功能沒有太大的出入。

HTTP proxy是怎麼工作的?

在HTTP tunnel出來之前,HTTP proxy工作在中間人模式。

也就是說在一次請求中,客戶端(瀏覽器)明文的請求代理伺服器。代理伺服器明文去請求遠端伺服器(網站),拿到返回結果,再將返回結果返回給客戶端。整個過程對代理伺服器來說都是可見的,代理能看到你要請求的path,你請求中的header(包括例如auth頭裡面的用戶名密碼),代理也能看到網站返回給你的cookie。和中間人攻擊的中間人是一種情況(笑)

這種模式要求代理對請求進行適當的改寫,RFC2616 5.1.2中要求請求代理的報文中Request-URI必須使用絕對路徑,這樣代理才能從中解出真正請求的目標,然後代理需要對這個請求進行改寫再發送給遠端伺服器。

例如瀏覽器在不使用HTTP proxy時,發送的請求如下(省略了和示例無關的header,下同):

GET / HTTP/1.1

Host: stackoverflow.com

Connection: keep-alive

使用了代理後,發送的報文將變為:

GET http://stackoverflow.com/ HTTP/1.1

Host: stackoverflow.com

Proxy-Connection: keep-alive

代理從請求的第一行中得知要請求的目標是stackoverflow.com,埠為默認埠(80),將第一行改寫後,向網站伺服器發送請求:

GET / HTTP/1.1

Host: stackoverflow.com

Connection: keep-alive

順帶一提上面這個例子可以看到有一個Proxy-Connection的header也被改寫了,這個是HTTP/1.1的一個黑歷史,現在它已經不是標準header了,並在RFC7230中被建議不要使用。然而現在瀏覽器(比如chrome)仍然在發送這個header,因此代理伺服器還是要對它做處理,處理方式就是當做Connection header改寫後發送給遠端。

為什麼需要HTTP tunnel?

從前一條可以看出,如果我們想在復用現有的HTTP proxy的傳輸方式來代理HTTPS流量,那麼就會變成瀏覽器和代理握手跑TLS,代理拿到明文的請求報文,代理和網站握手跑TLS。

但是代理沒有,也不可能有網站的私鑰證書,所以這麼做會導致瀏覽器和代理之間的TLS無法建立,證書校驗根本通不過。

HTTP tunnel以及CONNECT報文解決了這個問題,代理伺服器不再作為中間人,不再改寫瀏覽器的請求,而是把瀏覽器和遠端伺服器之間通信的數據原樣透傳,這樣瀏覽器就可以直接和遠端伺服器進行TLS握手並傳輸加密的數據。

HTTP tunnel的工作流程是什麼樣的?

普通的一次HTTP請求,header部分以連續兩組CRLF(
)作為標記結束,如果後面還有內容,也就是content部分的話,需要在header裡面加入Content-Length頭,值為content部分有多長,通信的對方(無論是伺服器,還是接收伺服器返回結果時的客戶端)會按照這個長度來讀後面那麼多個byte,數寫錯了就跑飛了(笑)

對於CONNECT報文的請求,是沒有content部分的,只有Request-Line和header。Request-Line和header均為僅供代理伺服器使用的,不能傳給遠端伺服器。請求的header部分一旦結束(連續的兩組CRLF),後面所有的數據都被視為應該發給遠端伺服器(網站)的數據,代理需要把它們直接轉發,而且不限長度,直到從客戶端的TCP讀通道關閉。

對於CONNECT報文的返回值,代理伺服器在和遠端伺服器成功建立連接後,可以(標準說的是可以,但是一般都會)向客戶端(瀏覽器)返回任意一個2xx狀態碼,此時表示含義是和遠端伺服器建立連接成功,這個2xx返回報文的header部分一旦結束(連續的兩組CRLF),後面所有的數據均為遠端伺服器返回的數據,同理代理會直接轉發遠端伺服器的返回數據給客戶端,直到從遠端伺服器的TCP讀通道關閉。

HTTP tunnel與SOCKS5代理有什麼關係和區別?

其實HTTP代理伺服器對於CONNECT報文的行為和SOCKS5代理伺服器(RFC1928 ,這個很短)的行為已經非常想像了。

其中相同的地方:

  • 都能在請求中指明要請求的目標和埠。

  • 都會建立能傳輸任何流量的TCP通道,代理伺服器對流量內容不關心。

  • 都是在報文前部的描述信息結束後,將後續所有數據視為轉發的客戶端和服務端數據,直到通道關閉。

  • 都可以進行客戶端的身份驗證,而且有多重身份驗證協議可選。

不同的地方:

  • SOCKS5的報文無論請求還是返回,內容均是固定的。而CONNECT報文作為HTTP/1.1的報文之一,當然也包括了HTTP/1.1可以傳輸任何自定義header的功能,雖然一般代理不會響應CONNECT報文上的非標準header,但是自己實現一個客戶端和伺服器通過header傳輸一些其它數據也是符合標準的。

  • SOCKS5 request報文中的address,根據address type(ATYP)位元組的值,可以顯式聲明為IPv4地址、IPv6地址、域名(domain)。而CONNECT報文中的Request-URI根據RFC要求必須為authority形式(即沒有http://前綴,只包含host,以及可選的port,其中host和port要用":"分隔),也就是說CONNECT報文不會顯式的區分IP地址和域名,均作為host傳輸。

  • 由於CONNECT報文還是http,所以也可以跑在TLS里,也就是說客戶端和代理跑一層TLS,這一層裡面客戶端和遠端伺服器再跑一層TLS。這樣當代理伺服器需要用戶名密碼驗證,而驗證方式又是Basic時,可以通過TLS來保護代理請求報文中的明文用戶名密碼。

最後吐一口槽……雖然HTTP代理原理並不複雜,但是相比 SOCKS5代理,實現HTTP代理不但RFC字多,RFC多,而且還有後面RFC覆蓋前面或前面的一部分的情況,需要消耗大量時間讀多個版本來確認各種corner case,以及到底要不要兼容歷史情況…………(微笑)


HTTP隧道(HTTP tunnel)的理解可以對比在TCP/IP上構建PPTP隧道建立VPN鏈接類似,HTTP隧道技術是在HTTP協議上構建的一個隧道。它的作用是把所有要傳輸的數據全部封裝到HTTP協議里再進行傳送。(解釋來自:http://www.cnblogs.com/wengzhiwen/archive/2009/08/26/http_tunnel.html)

一個應用實例:HTTP Tunnel的應用實例

在木馬上的應用:反彈埠原理與HTTP隧道技術


推薦閱讀:

如何使用Nginx轉發非80埠的非HTTP請求?
基於 HTTP 連接下 token 安全問題?
如何讓html img標籤發送的http請求附加某個http header?
怎樣學習 HTTP 協議?
大家在工作中那些地方用到了http協議的細節?

TAG:前端開發 | Web標準 | HTTP |