HTTP隧道
介绍¶
HTTP隧道指的是,“利用HTTP的CONNECT
方法在两台网络受限的计算机间建立网络链接,通常一方是在受限网络的内部,一方在外部,借外部方来代理内部方的流量”。其中,网络受限包括“防火墙”、“NAT”和“访问控制”等。该隧道由中间的“代理服务器”创建,通常部署于“DMZ”区域。
在隧道中可以传输一些被限制的协议,最终借由“代理服务器”跳出受限网络。
HTTP CONNECT方法¶
HTTP隧道中最常用的方法是CONNECT
,过程如下:
客户端 代理 (proxy_connect) 服务器
| | |
(1) |-- CONNECT server.example.com:443 -->| |
| | |
| |----[ TCP connection ]----------->|
| | |
(2) |<- HTTP/1.1 200 ---| |
| Connection Established | |
| | |
| |
================== CONNECT tunnel has been establesied. ==================
| |
| | |
| | |
| [ SSL stream ] | |
(3) |---[ GET / HTTP/1.1 ]------------->| [ SSL stream ] |
| [ Host: server.example.com ] |---[ GET / HTTP/1.1 ]---------->|
| | [ Host: server.example.com ] |
| | |
| | |
| | |
| | [ SSL stream ] |
| [ SSL stream ] |<--[ HTTP/1.1 200 OK ]-----------|
(4) |<--[ HTTP/1.1 200 OK ]--------------| [ < html page > ] |
| [ < html page > ] | |
| | |
- “客户端”告诉代理自己想要连接的目标“服务器”,“代理”跟“服务器”建立TCP连接
- “代理”返回HTTP/1.1 200告诉“客户端”隧道已经建立
- “客户端”发送TCP之上(包括SSL)的数据给“代理”,再经由“代理”转发给“服务器”。
- “服务器“响应数据给”代理“,再经由”代理“转发给”客户端“
从上面流程可以看出,仅在初始化连接的时候是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状态码告诉“客户端”隧道建立成功:
接着“客户端”可以发送任何数据,所有数据都将经由“代理”发往“服务器”,同时“服务器”的响应数据也都将转发给“客户端”:
非CONNECT方法建立HTTP隧道¶
建立HTTP隧道可以是任何方法,它只是一种思想,而CONNECT
是最为常见的方式而已,因为它的命名就是显而易见的。
建立HTTP隧道的场景中,“客户端”部署在保护(受限)网络的内部,而“代理”则部署在外部。在任何时刻,“客户端”都可以将要发送的数据封装成HTTP请求发往“代理”,“代理”再将其还原成原始的“客户端”请求并中继到目标“服务器”;反之,“服务器”响应请求的数据则通过“代理”封装成HTTP响应并中继回“客户端”,最终“客户端”和“服务器”这些被封装在HTTP协议之内的数据将穿过防火墙等设备。
代理认证¶
这里的“代理认证“指的是”代理“对”客户端“进行身份认证,认证通过后才允许建立HTTP隧道。
“代理认证”的交互过程有两种形式,一种是“客户端”主动在CONNECT
请求中携带身份信息给“代理”;另一种是“客户端”在收到“代理”响应407后,再重新发送一个携带身份信息的CONNECT
请求给“代理”。
主动携带¶
客户端 代理 (proxy_connect)
| |
(1) |-- CONNECT server.example.com:443 -->|
| Proxy-Authorization: credentials |
| |
| |
| |
(2) |<- HTTP/1.1 200 ---|
| Connection Established |
| |
- “客户端”发送携带用户凭据的
CONNECT
请求给“代理”。credentials
:是用户凭据,其格式为<type> <token>
,token
的值取决于type
。比如:“Proxy-Authorization: Basic dXNlcjE6MTIzNDU2Cg==
”。type
:常见的就是Basic
(其余可参阅引用[5])。
- “代理“在身份验证通过并且与服务器建立TCP连接后返回
200
给“客户端”。
被动携带¶
客户端 代理 (proxy_connect)
| |
(1) |-- CONNECT server.example.com:443 -->|
| |
| |
| |
(2) |<- HTTP/1.1 407 Proxy Authenticate Required ---|
| Proxy-Authenticate: 1#challenge |
| |
| |
(3) |-- CONNECT server.example.com:443 -->|
| Proxy-Authorization: credentials |
| |
| |
(4) |<- HTTP1.1 200 Connection Established --|
- “客户端”先发送
CONNECT
请求给代理,但并没有携带用户凭据。 - “代理“返回
407
通知“客户端”需要进行用户身份认证,这里1#challenge
指的是可以返回至少1个Proxy-Authenticate
头部,而challenge
的格式为<type> realm=<realm>
,比如“Proxy-Authenticate: Basic realm=dev
”:type
通常为Basic
,(其余可参阅引用[5])realm
是领域的意思,与URI结合可以实现不同的用户访问不同的URI资源,比如用户的请求可能会被改写为:https://host:port/$realm/$uri
- “客户端”在收到
407
后,重新发起携带用户凭据的CONNECT
请求给“代理”。 - “代理”在身份验证通过并且与服务器建立TCP连接后返回
200
给“客户端”。
实施¶
基于Tengine二次的开发项目,支持CONNECT代理及“Basic代理认证”:
- Docker仓库:https://hub.docker.com/r/homqyy/hengine,docker运行命令示例:
docker run -d -v '/path/to/nginx.conf:/usr/local/hengine/conf/nginx.conf:ro' homqyy/hengine
- Github仓库:https://github.com/Homqyy/hengine,获取源码示例:
git clone https://github.com/Homqyy/hengine.git
nginx.conf配置如下:
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; # 用户信息
proxy_set_header Authorization ""; # 去掉认证信息
# 代理 CONNECT 请求
proxy_connect;
proxy_connect_auth on; # 进行代理认证
proxy_connect_allow 443 80; # 允许代理443和80端口
proxy_connect_connect_timeout 120s;
proxy_connect_read_timeout 120s;
proxy_connect_send_timeout 120s;
proxy_set_header Host $host;
# 转发所有非HTTP CONNECT的请求
location / {
auth_basic off;
proxy_set_header Host $host;
proxy_pass http://$host;
}
}
}
淘宝基于Nginx二次开发的项目,支持CONNECT代理,但不支持“代理认证”。参阅官方手册proxy_connect
但是可以通过Lua脚本,附上淘宝给的Lua脚本实现示例:https://github.com/chobits/ngx_http_proxy_connect_module/issues/42#issuecomment-502985437
引用¶
- 《Wiki - HTTP Tunnel》
- 《RFC - 7235 -Hypertext Transfer Protocol (HTTP/1.1): Authentication 》
- 《developer.mozilla.org - Proxy-Authorization》》
- 《developer.mozilla.org - Proxy-Authenticate》》
- 《Hypertext Transfer Protocol (HTTP) Authentication Scheme Registry》