使用 Dnsmasq + NGINX + Socks5(Goproxy) 做反向 HTTPS 4 层透明代理 —— NGINX 7 层和 4 层代理以及各种介绍
现在流量在经过此节点时有部分域名需要走 socks5 或 https 代理通道进行处理。
有以下要求:
- 不影响其他流量、域名;
- 客户端不进行任何处理;
- 同一个域名访问;
- 尽量低侵入;
- 尽量低占用;
那么要怎么处理呢?
先进一段科普。
HTTP/HTTPS 代理的分类
分类依据:代理是否对客户端透明
- Common Proxy 普通代理:需要在客户端填入代理地址等信息,客户端知道有代理存在。
- Transparent Proxy 透明代理:不需要在客户端进行代理设置。代理对客户端是透明的,客户端不知道有代理的存在。
分类依据:代理是否加解密 HTTPS
- Tunnel Proxy 隧道代理:透明传输流量的代理。代理服务器不会解密 TLS/SSL 或感知其代理流量的具体内容,而直接将其转发到下一个目的地。客户端与目标服务器建立 TLS/SSL 连接,与代理无关,整个链路上有 1 个 TLS/SSL 链接。
- Man-in-the-Middle (MITM) Proxy 中间人代理:代理服务器解密 HTTPS 流量,使用自签名证书或其他域名和普通受信任证书与客户端建立 TLS/SSL 连接,由代理解密流量,再由代理服务器与目标服务器建立 TLS/SSL 连接加密流量(SSL 终结/SSL 卸载)。整个链路上有 2 个 TLS/SSL 链接,客户端 --- 第一次TLS --- 代理 --- 另一次TLS --- 目标服务器。
注意:在中间人代理的情况下,客户端实际上是在 TLS 握手过程中获取了代理服务器的自签名证书,证书链的校验默认是不成功的。所以代理自签名证书中的根 CA 证书必须在客户端上受信任。
因此,客户端在这个过程中是知道代理的。如果将自签名根 CA 证书安装到客户端,则实现了透明代理。但你需要在客户端上手动安装自签名根 CA 证书,这个过程是不透明的,且安装根证书有一定风险。
NGINX 7 层和 4 层代理
我们主要关注 HTTPS 代理,因为 HTTP 代理真的很随便。
于是 NGINX 给你了 2 种方案,7 层(L7)代理和 4 层(L4)代理。
- 7 层代理属于中间人代理,需要对流量进行加解密。
- 4 层代理则属于隧道代理,不需要对流量进行加解密。
如果你无法安装根 CA 根证书,或是使用另一个域名和普通被信任的证书,那么 7 层代理不可以实现透明代理。
7 层代理
假如你使用 NGINX 作为过你网站的反向代理,如 LNMP 架构,那么这就是一次标准的 7 层代理。
NGINX 配置文件如下:
server {
listen 443;
location / {
proxy_pass http://example.com;
#proxy_pass https://example.com;
}
}
4 层代理
需要 ngx_stream_core_module 模块支持和 ngx_stream_ssl_preread_module。
但 NGINX 如何在不解密流量的情况下拿到客户端想访问的域名?
4 层只能拿到五元组信息(源IP地址,源端口,目的IP地址,目的端口和传输层协议。
因此,NGINX 必须查看上层信息才能找到域名,因此 NGINX stream 模块不能算作严格意义上的 4 层代理。
SNI
为了在不解密流量的情况下拿到 HTTPS 流量的目标域名,原版 HTTPS 是不行的,必须使用在 RFC 6066 中定义的 TLS 扩展协议: SNI(Server Name Indication) 服务器名称指示。
SNI 最初用于在一个 IP 和端口上同时支持多个域名,因为流量已经加密,TLS 握手时 HTTP 交互还没开始,所以服务器拿不到 Host 标头(在 HTTP 1.1 加入)也就不知道你要访问哪个域名。
SNI 会在 Client Hello 包中加入明文(TLS 连接建立前)的 Server Name: example.com 字段,于是 NGINX 可以从这里获取到域名。
当然 SNI 也有 SNI 前置的假域名和加密的 ESNI,但那又是另一回事了。
也因此,NGINX 的 4 层 HTTPS 代理必须在 Client Hello 包中包含 SNI 字段(ngx_stream_ssl_preread_module 模块)。
NGINX 配置文件如下:
stream {
server {
listen 443;
ssl_preread on;
proxy_pass $ssl_preread_server_name:$server_port;
}
}
注意这是 stream{},不可以写在 http{} 内。
ssl_preread on
:启用在预读阶段从 ClientHello 消息中提取信息;$ssl_preread_server_name
:通过 SNI 请求的服务器名称;
应用场景
现在流量在经过此节点时有部分域名需要走 socks5 或 http 代理通道进行处理。
有以下要求:
- 不影响其他流量、域名;
- 客户端不进行任何处理;
- 尽量低侵入;
- 尽量低占用;
有以下方案
- 使用 CLASH redir-port + 防火墙重定向流量;缺点:防火墙配置复杂,容易出错,优点:协议支持多,分流能力强;
- 使用 CLASH tun + 防火墙重定向流量;缺点:防火墙配置复杂,容易出错、tun 兼容问题,优点:协议支持多,分流能力强;
- 使用 Goproxy 作为反向代理服务器;缺点:需要安装根 CA 证书,优点:配置简单;
- 使用 Dnsmasq 重定向域名 + NGINX 4 层代理 + 透明代理服务端;缺点:组件较多,优点:低侵入性;
方案 4
当然我们选择第 4 种方案,其实前 3 种方案我都试过了。
首先我们使用 Dnsmasq 将要访问的域名重定向到本地或 NGINX 所在地。
当然,这个用 hosts 就可以,我使用 Dnsmasq 主要是服务器上已经有了。
address=/example.com/127.0.0.1
普通的 NGINX 是不能直接处理 https 代理和 socks5 代理的,所以我们起一个 4 层代理,将流量转发到一个透明代理,由透明代理再去访问 https 代理和 socks5 代理。
然后起一个 NGINX 的 4 层代理,监听本地的 443 和 80 端口,不需要加主机名,因为 dnsmasq 已经分流过了,其他的流量只会路过而已。
443:
stream {
server {
listen 443;
ssl_preread on;
proxy_pass $ssl_preread_server_name:$server_port;
}
}
80:
server{
listen 80;
location / {
proxy_buffering off;
proxy_pass http://$http_host;
}
}
这样一来,流量就变成了是从本机发出的。
接下来,我们可以利用 iptables 等工具将 NGINX 发出的流量重定向到透明代理,只重定向了 nginx 用户发出的流量,所以对其他流量是没有影响的。
iptables:
iptables -t nat -A OUTPUT -p tcp -m owner --uid-owner 995 --dport 80 -j REDIRECT --to-ports 23457
iptables -t nat -A OUTPUT -p tcp -m owner --uid-owner 995 --dport 443 -j REDIRECT --to-ports 23457
995 是我这里 nginx 用户的 uid。
将 nginx 用户发出的目标为 80 和 443 的流量都重定向到 23456 端口。
firewalld:
firewall-cmd --permanent --zone=public --add-forward-port=port=80:proto=tcp:toport=23456 --uid-owner=995
firewall-cmd --permanent --zone=public --add-forward-port=port=443:proto=tcp:toport=23456 --uid-owner=995
不是特别确定这两条命令是否有用,但你可以使用 --direct 命令来使用 iptabels 语法。
最后,我们使用 https://github.com/snail007/goproxy 的透明代理模式(代理协议转换)将 socks5 或 https 代理转换为透明代理。
./proxy sps --redir --forever -p :23456 -P socks5://foo:bar@example.com:443 -P https://foo:bar@example.com:443
sps
代表透明代理模式;--redir
iptables 透明代理模式;--forever
守护运行;-p
代表下游/监听端口;-P
代表上游,可以指定多个;
主要参考
- https://www.alibabacloud.com/blog/how-to-use-nginx-as-an-https-forward-proxy-server_595799
- https://snail007.github.io/goproxy/manual/zh/
- https://xdays.me/Clash%E9%80%8F%E6%98%8E%E4%BB%A3%E7%90%86/
- https://1-riverfish.github.io/2018/12/04/Linux%E7%BB%88%E7%AB%AF%E4%BB%A3%E7%90%86%E8%AE%BE%E7%BD%AE/
- https://github.com/zfl9/ss-tproxy/wiki/Linux-%E9%80%8F%E6%98%8E%E4%BB%A3%E7%90%86