SNI Proxy,根据 SNI 转发 HTTPS 流量

大家知道,HTTPS 协议有一个字段叫 SNI,用来标识所访问的具体目标服务(如某某网站)。有了 SNI,服务器即使只有一个 IP 地址,也可以承载多个不同域名的网站,每个网站(域名)可以配置独立的 SSL 证书。在现实世界中,SNI 的值通常是所需要访问的域名。

我在流量统计后台发现,很多人还对名叫“sniproxy”的东西感兴趣。SNI Proxy 是一个开源程序,可以根据 SNI 字段转发 HTTPS 流量。在当时我并不感兴趣,后来自然产生需求后,觉得有时这工具还挺有用。

传统的 HTTPS 流量转发

要转发或者说代理 HTTPS 流量,很容易想到 Nginx 同类工具。毕竟 Nginx 就是一个 HTTP 服务器兼反向代理程序。用 Nginx 代理 HTTP/HTTPS 流量十分常见。Nginx 相当于在客户端与后端(真实服务端)之间充当“中间人”(man in the middle)。从后端拿到 HTTPS 响应后,Nginx 会解密流量,根据配置进行处理(如执行字符串替换),再加密送回客户端,因此需要配置相应 SSL 证书。按这种方式,可以将 https://shansing.com(指定为秘密 IP 地址) 或者 https://shansing.net 的流量转发到 https://shansing.com,只要我有 shansing.com 的 SSL 证书及其私钥。

因为 HTTPS 是基于 TCP 的,所以也可以在 TCP 层转发流量。这样做的好处是我不必掌握 https://shansing.com 的私钥,就可以代理它的流量。不过这时我不得不代理 shansing.com 同个 IP 地址上的其他网站,同个端口也不能再转发到其他 IP 地址。我也不能重写域名。

使用 Nginx 代理 HTTPS,通常是比较严肃的建站用途。而 TCP 转发的场景相对小众。比如我就只是想临时变换服务器 IP 地址,或者我转发 https://www.openssl.org 的流量这样就能在别的机器上顺畅下载 OpenSSL。

SNI Proxy

使用 Nginx 代理实际上是将 HTTPS 解包为 HTTP,TCP 转发就更底层了。既然 HTTPS 本身有 SNI 字段,我就直接用它好了。

以 Debian 系为例,可以直接从官方源安装 SNI Proxy:

sudo apt install sniproxy

然后可以查看 sniproxy 状态:

sudo systemctl status sniproxy

如果报错找不到这个服务,可以自行新增 Service 文件,也就是将下列内容保存为 /lib/systemd/system/sniproxy.service

[Unit]
Description=my sniproxy Service
After=network-online.target

[Service]
Type=simple
DynamicUser=true
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
LimitNOFILE=32768
Restart=on-failure
RestartSec=10s
ExecStart=/usr/sbin/sniproxy -c /etc/sniproxy.conf -f

[Install]
WantedBy=multi-user.target

然后设置自启动:

sudo systemctl enable sniproxy

官方 GitHub 主页给了一份配置示例,我这边给一下我的:

user sniproxy

pidfile /var/run/sniproxy.pid

resolver {
    nameserver 8.8.8.8
    mode ipv4_first
}

error_log {
    syslog daemon
    priority notice
}

listen 443 {
    proto tls
    table https_hosts
}

table https_hosts {
    .*\\.example\\.com    *:443
    packages.sury.org packages.sury.org:443
    openssl.org openssl.org:443
}

其中 table 块左边的值写需要匹配的 SNI 值,可以用通配符,但要写成正则表达式的形式。右边的值表示转发目的地,既可以写 IP 地址也可以写域名,域名会自动解析。

这里有个坑,如果右值是域名,需要考虑服务器 IPv6 情况。如果服务器只有 IPv4 网络,要像我这样配置 resolver 并设 mode 为 ipv4_firstipv4_only。否则用起来容易出现转发失败的情况,此时在浏览器端报错没有共用的加密套件(SSL 握手失败),服务器 sniproxy 日志可以看到某 IPv6 地址“can not be assigned”。

使用 SNI Proxy 转发流量不需要提供证书或私钥。SNI Proxy 也不会进行解密再加密的操作。

目前我的主要用途是加速开源项目下载。比如在香港服务器上搭建 SNI Proxy,配置 openssl.org openssl.org:443 转发,在境内服务器上修改 hosts 文件将 openssl.org 指向到中间服务器 IP 地址。于是本来慢到不能下载的 OpenSSL 源码就能畅快下载。同个程序同个端口我也可以加速 packages.sury.org。注意中间服务器要配置来源 IP 策略,用防火墙阻挡非我访问。

这种场景下境内服务器不便直接使用代理客户端,用 SNI Proxy 打配合算是简单易用风险小。也有人拿它搭建流媒体 DNS 解锁服务。其他用法留待大家挖掘。

若无特别说明,本文系原创,遵循 署名-非商业性使用 3.0 (CC BY-NC 3.0) 协议,转载文章请注明来自【闪星空间】,或链接上原文地址:http://shansing.com/read/501/

2 条评论

  1. Nginx 的 stream 不香么?结合 preread 一样很好用。

    1. 感谢,确实。Nginx 也可以解析 SNI 并转发 TCP 流,感兴趣的读者可以参考:https://nginx.org/en/docs/stream/ngx_stream_ssl_preread_module.html

发表评论»

NO SPAMS! 不要发垃圾评论哦!

表情