HTTP隧道指的是,“利用HTTP的CONNECT
方法在两台网络受限的计算机间建立网络链接,通常一方是在受限网络的内部,一方在外部,借外部方来代理内部方的流量”。其中,网络受限包括“防火墙”、“NAT”和“访问控制”等。该隧道由中间的“代理服务器”创建,通常部署于“DMZ”区域。
在隧道中可以传输一些被限制的协议,最终借由“代理服务器”跳出受限网络。
HTTP隧道中最常用的方法是CONNECT
,过程如下:
HTTP/1.1 200
告诉“客户端”隧道已经建立从上面流程可以看出,仅在初始化连接的时候是HTTP,即“1”和“2”两个步骤,后续“代理”都只是简单的转发“客户端”和“服务器”的数据而已。
“代理“也可以去支持一些限制功能,比如仅代理某些特定端口和某些主机等。
“客户端”发送连接请求,告知“代理”想要连接的地址和端口,这里是developer.mozilla.org:443
:
CONNECT developer.mozilla.org:443 HTTP/1.1
Host: developer.mozilla.org:443
Proxy-Connection: keep-alive
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36
如果连接请求被“代理”允许且成功与服务器建立TCP连接,则响应2XX
状态码告诉“客户端”隧道建立成功:
HTTP/1.1 200 Connection Established
Proxy-agent: nginx
接着“客户端”可以发送任何数据,所有数据都将经由“代理”发往“服务器”,同时“服务器”的响应数据也都将转发给“客户端”:
# 以下来自客户端的数据
GET / HTTP/1.1
...
# 以下来自服务器的数据
HTTP/1.1 200 OK
...
建立HTTP隧道可以是任何方法,它只是一种思想,而CONNECT
是最为常见的方式而已,因为它的命名就是显而易见的。
建立HTTP隧道的场景中,“客户端”部署在保护(受限)网络的内部,而“代理”则部署在外部。在任何时刻,“客户端”都可以将要发送的数据封装成HTTP请求发往“代理”,“代理”再将其还原成原始的“客户端”请求并中继到目标“服务器”;反之,“服务器”响应请求的数据则通过“代理”封装成HTTP响应并中继回“客户端”,最终“客户端”和“服务器”这些被封装在HTTP协议之内的数据将穿过防火墙等设备。
这里的“代理认证“指的是”代理“对”客户端“进行身份认证,认证通过后才允许建立HTTP隧道。
“代理认证”的交互过程有两种形式,一种是“客户端”主动在CONNECT
请求中携带身份信息给“代理”;另一种是“客户端”在收到“代理”响应407
后,再重新发送一个携带身份信息的CONNECT
请求给“代理”。
CONNECT
请求给“代理”。credentials
是用户凭据,其格式为<type> <token>
,token
的值取决于type
。比如:“Proxy-Authorization: Basic dXNlcjE6MTIzNDU2Cg==
”。type
常见的就是Basic
(其余可参阅引用[5])。200
给“客户端”。CONNECT
请求给代理,但并没有携带用户凭据。“客户端”在收到407
后,重新发起携带用户凭据的CONNECT
请求给“代理”。200
给“客户端”。worker_processes 2;
user root;
error_log logs/info.log info;
events {
use epoll;
worker_connections 1024;
}
http {
#
# HTTP代理服务器(基于CONNECT方法的HTTP隧道)
#
server {
listen 10080; # 代理端口为10080
server_name your.domain.com;
# 认证配置
auth_basic web_proxy; # 领域
auth_basic_user_file user_list.htpasswd; # 用户信息
# 代理 CONNECT 请求
proxy_connect;
proxy_connect_auth on; # 进行代理认证
proxy_connect_allow 443 80; # 允许代理443和80端口
proxy_connect_connect_timeout 60s;
proxy_connect_read_timeout 60s;
proxy_connect_send_timeout 60s;
proxy_set_header Host $host;
location / {
return 403; # 拒绝所有非 CONNECT 的请求
}
}
}