Skip to content

Nginx+dnsmasq 实现旁路由透明代理

Published: at 18:10

又是折腾的一天。明明 ddl 就在眼前了,为什么……会这样呢……

这次折腾的是路由器级别的透明代理,不过形式稍微有点不同,正如之前发的那一串推所说,是以 DNS 的形式实现的透明代理,因此只要是连上主路由器的设备一般都能正常使用。

当然了,也是有例外的,比如有些设备强制使用自己的 DNS 设置,对于这种设备就没有办法了。不过我这里也没有这样的设备,而就手机平板而言,都是可以正常连接的。

连上主路由并能正常刷推的破手机
连上主路由并能正常刷推的破手机

当然了,前提是本机已经配置好了透明代理,这里就来简单介绍一下好了~

SNI 代理

SNIServer Name Indication 的简称,其主要的用途是能让同一个端口下提供复数张证书,由此达到多个网站共用 443 端口的目的。

那 SNI 代理又是什么呢?我们先来看 SNI。从原理来说,SNI 本身是在 TLSClientHello 的实现的,如 [RFC 3546] 定义:

In order to provide the server name, clients MAY include an extension of type “server_name” in the (extended) client hello.

我们发现,SNI 其实只是在 ClientHello 时附带的一个明文字符串(下称域名)而已,因此对于代理而言,我们就有操作的空间了。

首先,由于域名是明文的,因此我们就得到了需要访问的域名信息。在此之后,我们只要将所有的信息原封不动地转发给域名对应的服务器就可以了(当然,也包括 ClientHello)。

由于整个过程没有任何 MiTM 的因素存在,因此我们不需要对证书作任何处理;从原理上来讲,SNI 代理其实只是做了转发处理,因此实际上客户端和服务端的交互仍然是点对点的;唯一的区别可能就在 traceroute 上了。对客户端而言,从包到达 SNI 代理服务器那一刻起,之后的路径就无法掌控了。

SNI 代理因其不需要配置证书的特性而广泛地运用于 DNS 解锁流媒体服务母猪连结解 ban 等领域。同样,因为 SNI 代理是使用 DNS 的,因此对任何网络而言,配置透明代理都变得无比简单:只需要路由器能将对应需要透明代理的域名的 IP 设置为 SNI 代理的服务器 IP 就可以了。

dnsmasq+某 list

dnsmasq 是大名鼎鼎的 DNS 客户端,而某 list 也是人尽皆知的域名列表。这里我们通过将某 list 转换为 dnsmasq 规则来实现透明代理的 DNS 服务器。

首先是规则转换,我们使用的是 [1],不过这里需要修改一下。脚本的输出默认是 server=,即选择 DNS;而我们想要的是直接返回 IP,即 address=。将对应 IP 设置为本机在局域网中的 IP 就可以了。

此外,dnsmasq 还需要一个上游 DNS 服务器以返回列表以外的网站 IP。这里你可以选择任意一个没有污染的 DNS 服务器,或者使用 clash 的内置 DNS 服务器等等。我这里用的是 clash 的内置 DNS 服务器,配置的端口为 53;而 dnsmasq 配置的端口为 5353

在配置完 dnsmasq 之后,我们还需要将外部流量的 DNS 端口引导过来。我们使用 iptables 进行端口转发:

Terminal window
1
sudo iptables -t nat -A PREROUTING -p udp --dport 53 -j REDIRECT --to-port 5353

配置 SNI 代理

接下来就是整个过程的核心了:配置 SNI Proxy。我们使用 Nginxstream 来完成这一目标:

1
stream {
2
server {
3
listen 443;
4
ssl_preread on;
5
resolver 127.0.0.1 ipv6=off;
6
proxy_pass $ssl_preread_server_name:$server_port;
7
}
8
}

我们使 stream 监听 443 端口,并开启 ssl_preread 以使用 $ssl_preread_server_name,使得我们可以读到 SNI 信息。这里我们设置了 resolver,即 DNS 解析服务器。ipv6=off 是针对无 ipv6 的环境的,如果有 ipv6 的话可以删去(

配置 http 代理

https 可以正常访问之后,我们也不能把 http 丢下。相比之下,http 的配置就相对简单了。这里的关键是 proxy_bind $remote_addr transparent;,它实现了 http 的透明代理:

1
http {
2
# ...
3
server {
4
listen 80 default_server;
5
resolver 127.0.0.1 ipv6=off;
6
location / {
7
proxy_pass $scheme://$host$request_uri;
8
proxy_bind $remote_addr transparent;
9
proxy_set_header Host $host;
10
}
11
}
12
}

配置路由器

最后也是最简单的一步就是配置路由器了,我们将首选 DNS 设置为本机 IP,备用 DNS 设置为其他的 DNS,以保证在本机不可用的情况下也可以正常上网:

备用 DNS 服务器使用了阿里 DNS
备用 DNS 服务器使用了阿里 DNS

至此,整个旁路由透明代理配置完成。

结语

其实某种意义上我也不知道这算不算旁路由,只是感觉这个概念比较符合这种配置而已。毕竟我这电脑和路由器隔了整整一堵墙呢,而且只是把路由的 DNS 引导到了本机而已,网关之类的并没有任何改变。或许也正是由于离路由器远,我才能想到这样的路由器透明代理的方案吧。

从某种意义上来说,这个方案需要改进的地方还是挺多的。首先是规则问题,是不是应该用 dnsmasq-chinalist 以实现更广泛的非国内域名加速服务?其次是本地双 DNS 的问题,这个可能难以改变(因为 Nginx 需要查询真实 IP),但是不是可以对调一下,省去那一步 iptables?最后就是 meta 域名,对于顶级域,目前的 Nginx 似乎还无法正常处理,这也是一个迫切(或许)需要解决的问题。

不过从 ddl 角度来看,我大概是疯了吧,明明还有那么多事来着(

如果有帮到你/有所疑问的话,欢迎在评论区留言(