ss-redir 透明代理

shadowsocks-libev,基于 libev 库开发的 shadowsocks 代理套件;主要组件:ss-localss-redirss-tunnelss-server。2018/03/19 更新,支持 SSR 透明代理,客户端须使用 shadowsocksr-libev(libev 版本)。

组件区别

ss-server

shadowsocks 服务端程序,核心部件之一,各大版本均提供 ss-server 程序。

ss-local

shadowsocks 客户端程序,核心部件之一,各大版本均提供 ss-local 程序。
ss-local 主要提供 socks5 代理,根据 OSI 模型,socks5 是会话层协议;socks5 支持代理 TCP、UDP,是一个全能的代理协议。

在 Linux 上,我们可以使用 ss-local + privoxy,来间接使用 ss-local 提供的 socks5 代理。privoxy 有一个 socks5 转发功能,也就是说,我们只要将 privoxy 当做普通 http 代理来使用就好了,它会自动使用 socks5 协议与后端的 ss-local 进行通信。

ss-redir

shadowsocks-libev 提供的socks5 透明代理工具,也就是今天这篇文章的主题 - 实现透明代理!

正向代理
正向代理,即平常我们所说的代理,比如 http 代理、socks5 代理等,都属于正向代理。
正向代理的特点就是:如果需要使用正向代理访问互联网,就必须在客户端进行相应的代理设置

透明代理
透明代理和正向代理的作用是一样的,都是为了突破某些网络限制,访问网络资源。
但是透明代理对于客户端是透明的,客户端不需要进行相应的代理设置,就能使用透明代理访问互联网

反向代理
当然,这个不在本文的讨论范畴之内,不过既然提到了前两种代理,就顺便说说反向代理。
反向代理是针对服务端来说的,它的目的不是为了让我们突破互联网限制,而是为了实现负载均衡。

举个栗子:
ss-local,提供 socks5 正向代理,我们必须通过专业代理软件来配置,才能使用它,不然是不会经过代理的;
ss-redir,提供 socks5 透明代理,我们只要配置了适当的 iptables 规则,就可以不修改任何软件设置使用它。

ss-tunnel

shadowsocks-libev 提供的本地端口转发工具,通常用于解决 dns 污染问题。

假设 ss-tunnel 监听本地端口 53,转发的远程目的地为 8.8.8.8:53;系统 dns 为 127.0.0.1。
去程:上层应用请求 dns 解析 -> ss-tunnel 接收 -> ss 隧道 -> ss-server 接收 -> 8.8.8.8:53;
回程:8.8.8.8:53 响应 dns 请求 -> ss-server 接收 -> ss 隧道 -> ss-tunnel 接收 -> 上层应用。

方案说明

总体方案
版本:shadowsocks-libev(SS)或 shadowsocksr-libev(SSR)
系统:RHEL/CentOS 7.3(VPS)、ArchLinux for ARMv8(树莓派3B)
端口:ss-redir:60080、ss-tunnel:60053、chinadns:65353、pdnsd:53
内网:使用 ss-redir(开启 udp_relay)代理所有 tcp/udp 流量(dns 除外,必须指向网关)
本机:使用 ss-redir 代理 tcp 流量,使用 ss-tunnel 转发 dns 解析请求,其它 udp 流量无法代理

详细说明
由于笔者才疏学浅,刚开始居然以为 TCP 透明代理和 UDP 透明代理是一样的,只要无脑 REDIRECT 到 ss-redir 监听端口就可以了,因为我很少使用 UDP 代理(DNS 使用 ss-tunnel 解析),因此也没有发现什么不对劲的地方。直到后来,我才知道远远没有这么简单!

首先我们来了解一下 iptables 的 REDIRECT 重定向的意义,REDIRECT 其实就是 DNAT 目的地址转换,只不过它的目的地址为 127.0.0.1,因此给它取了个形象的名字 - 重定向;DNAT 其实是很粗暴的,就是修改数据包的目的 IP 和目的 Port;那么在 ss-redir 中,它是怎么获取数据包原来的目的 IP 和目的 Port 的呢(就像发快递,你总得知道这个快件要发往哪吧,不然你收到来有什么用)?答案是,借助 Linux 的连接跟踪机制。

什么是连接跟踪?顾名思义,就是跟踪并记录网络连接的状态。Linux 会为每个经过网络堆栈的数据包都生成一个新的连接记录项,此后,所有属于此连接的数据包都会被唯一分配给这个连接,并标识连接的状态。因此,做了 NAT(网络地址转换)的数据包在内核中都是有记录的。而 ss-redir 只要使用 netfilter 提供的 API 即可从连接记录项中获取数据包原本的目的地址和目的端口,来进行代理。

但是,上面这种情况只针对 TCP;对于 UDP,如果你做了 DNAT,就无法再获取数据包的原目的地址和目的端口了,具体的技术细节我不清楚,我们只需要知道 UDP 透明代理没有这么简单。

那么该怎么透明代理 UDP 呢?利用 TPROXY 技术。TPROXY 是在 Kernel 2.6.28 引进的全新透明代理技术,TPROXY 完全不同于传统的 DNAT 方式。TPROXY 实现的透明代理有以下特点:

  • 不对 IP 报文做改动(不做 DNAT);
  • 应用层可用非本机 IP 与其它主机建立 TCP/UDP 连接;
  • Kernel 通过 iptables-tproxy 和策略路由将非本机流量送到 socket 层;
  • 仍需要通过其它技术拦截做代理的流量到代理服务器(WCCP 或 PBR 策略路由)。

因为不做 DNAT,因此可以将它应用于 ss-redir 的 UDP 透明代理,这样就不需要担心数据包原目的地址和原目的端口问题了。但是,目前 TPROXY 只能应用于 PREROUTING 链的 mangle 表,因此,只能透明代理来自内网的 UDP 流量,对于本机的 UDP 则无能为力,这算是一个小小的遗憾吧。

注意,TPROXY 可以透明代理 TCP 和 UDP,但是在 ss-redir 中,透明代理 TCP 要使用 REDIRECT,透明代理 UDP 要使用 TPROXY,为什么不统一使用 TPROXY 呢?因为 TPROXY 只能代理来自内网中的 TCP、UDP 数据,如果全部使用 TPROXY,那么本机的 TCP 就无法代理了。

既然 ss-redir 都能代理 TCP 和 UDP 了(网关本机的 DNS 可通过 ss-tunnel 解决),那我上面提到的 chinadns、pdnsd 是不是就是多余的呢?当然不是,待我慢慢道来。

假设,我们只使用 ss-redir 和 ss-tunnel,那么内网主机(以下简称“内网”)和网关主机(以下简称“本机”)的 TCP、UDP 数据包走向如下:

  • 环境:ss-redir 监听 0.0.0.0:60080/tcp&udp;ss-tunnel 监听 0.0.0.0:53/tcp&udp(上游 DNS 设为 8.8.8.8)。
  • 内网:将 DNS 设为 8.8.8.8:53/udp。来自内网的所有 TCP、UDP 流量都能无障碍的经 ss-redir 代理出去。
  • 本机:将 DNS 设为 127.0.0.1:53/udp(指向 ss-tunnel)。TCP 流量被 ss-redir 代理,DNS 请求被 ss-tunnel 代理。

我们暂且称它为 一号方案,这样确实是可行的,我也在自家网络中验证过。但是,不出意外的话,你访问 https://www.jd.com,会自动跳转到 https://global.jd.com(京东全球售),惊不惊喜?意不意外?这是什么原因导致的呢?因为这货:DNS 智能解析。DNS 智能解析是什么?简单的说,就是 DNS 服务器会根据请求者的 IP 地址归属地智能的选择要解析到的 IP 地址。以 www.jd.com 为例,当内网主机请求解析该域名时,会发送一个 DNS 请求包,该数据包到达网关主机时,被 TPROXY 截获,到了 ss-redir 的手上,ss-redir 将它进行处理后又通过 ss 隧道发送到了 ss-server 服务器,ss-server 解开此数据包后,发现这是一个发往 8.8.8.8:53 的 DNS 请求,于是又将它发往 8.8.8.8:53/udp,此时注意了,经 ss-server 发出的 DNS 请求包的源地址是 ss-server 服务器的地址,问题就出在这,当支持 DNS 智能解析的 DNS 服务器接收到该数据包时,它发现这是一个日本(假设你的 ss 服务器位于日本)IP,于是它就返回了一个京东全球售的 IP 地址,最终原路返回到达内网主机,浏览器开始向此 IP 的服务器发起 HTTP 请求,此服务器发现请求的域名不是 global.jd.com,于是又使用 301/302 重定向技术,将域名重定向到 global.jd.com,最终你的 www.jd.com 变为了 global.jd.com。

不只是 www.jd.com,有时候 www.taobao.com 也会被重定向到 world.taobao.com,一般大一点的全球性网站都会。这样会带来个问题:我无法正确访问本地区的服务器,就像上面的京东,我本来是国内用户,却访问的全球站的服务器。那要如何解决这个问题呢?答案就是 chinadns。

那么 chinadns 是如何解决这个问题呢?大致原理如下:

  • 首先,chinadns 需要配置至少两个上游 DNS 服务器,其中,至少一个国内 DNS 服务器,至少一个国外 DNS 服务器。
  • 当 chinadns 接收到 DNS 解析请求后,会同时向这些 DNS 服务器发送 DNS 解析请求,如果国内 DNS 服务器返回的是一个国外 IP,那么就过滤掉这个响应结果,使用国外 DNS 服务器返回的 IP 地址,最后将它返回给请求客户端;如果国内 DNS 服务器返回的是一个国内 IP,那么就直接采用这个响应结果,最后将它返回给请求客户端。

这样一来,我请求 www.jd.com 时,chinadns 首先接收到这个解析请求,然后它向 114.114.114.114 和 ss-tunnel(8.8.8.8)同时发送 DNS 请求,一般来说,114 的响应结果会第一个到达 chinadns(毕竟离得近,一般 10-20ms 的响应时间),此时 chinadns 查询预先指定的 chnroutes 大陆地址列表,发现这是一个国内 IP 地址,于是直接将它返回给了请求客户端。这样一来,我就不会被跳转到 global.jd.com 了(其余网站同理)。

此时我们升级到了 二号方案,内网主机和网关主机的 TCP、UDP 数据包走向如下:

  • 环境:ss-redir 监听 0.0.0.0:60080/tcp&udp;ss-tunnel 监听 0.0.0.0:60053/tcp&udp,ss-tunnel 的上游 DNS 设置为 8.8.8.8:53/udp;chinadns 监听 0.0.0.0:53/udp,chinadns 的国内上游 DNS 设置为 114.114.114.114,chinadns 的国外上游 DNS 设置为 127.0.0.1:60053(即 ss-tunnel,也即 8.8.8.8)。
  • 内网:系统 DNS 设置为 192.168.1.1(假设网关为 192.168.1.1,即指向 chinadns)。TCP 流量由 ss-redir 代理,DNS 由 chinadns/ss-tunnel 处理,其它 UDP 流量由 ss-redir 代理。
  • 本机:系统 DNS 设置为 127.0.0.1(即指向 chinadns)。TCP 流量由 ss-redir 代理,DNS 由 chinadns/ss-tunnel 处理。

那么,我们还有优化的空间吗?肯定是有的,最明显的地方就是,在网关上缓存 DNS 响应结果,来加速 DNS 的解析。因为 chinadns 没有 DNS 缓存的功能,因此只能借助 pdnsd 了(这里只介绍 pdnsd,你可以自由的使用 dnsmasq、dnsforwarder 等类似软件)。那么最终的 三号方案 如下:

  • 环境:ss-redir 监听 0.0.0.0:60080/tcp&udp;ss-tunnel 监听 0.0.0.0:60053/tcp&udp,ss-tunnel 的上游 DNS 设置为 8.8.8.8:53/udp;chinadns 监听 0.0.0.0:65353/udp,chinadns 的国内上游 DNS 设置为 114.114.114.114,chinadns 的国外上游 DNS 设置为 127.0.0.1:60053(即 ss-tunnel,也即 8.8.8.8);pdnsd 监听 0.0.0.0:53/udp,pdnsd 的上游 DNS 设置为 127.0.0.1:65353(即指向 chinadns)。
  • 内网:系统 DNS 设置为 192.168.1.1(假设网关为 192.168.1.1,即指向 pdnsd)。TCP 流量由 ss-redir 代理,DNS 由 pdnsd/chinadns/ss-tunnel 处理,其它 UDP 流量由 ss-redir 代理。
  • 本机:系统 DNS 设置为 127.0.0.1(即指向 pdnsd)。TCP 流量由 ss-redir 代理,DNS 由 pdnsd/chinadns/ss-tunnel 处理。

差点忘了,对于以上一、二、三号方案,还有一个 ipset 没介绍。ipset 在其中的作用也很简单,就是进行分流,发往大陆地址段的流量不走代理,发往非大陆地址段的流量才走代理。有点类似于 SS 客户端的“绕过局域网及中国大陆地址”代理模式。为什么要绕过大陆地址段呢?很简单呀,一是为了速度,二是为了节流。发往大陆地址的数据包是没有必要经 ss-redir 代理的,如果走代理,速度会很慢,而且在使用迅雷下载的时候,那流量是刷刷刷的往上走,然后就被断网了(ss 服务商一般都会限制流量的)。

那为什么不使用“GFWList代理模式”呢?这不是更节流吗?毕竟只有在 GFWList 中的网站才会走代理,其它的管你大陆还是国外都是走直连的。这个问题我也纠结了很久,但是我最终还是选择的“绕过大陆地址段”。理由如下:

  • gfwlist 模式:gfwlist 模式是仅被墙的网站才会走代理,但实际上,很多国外网站虽没被墙,但是访问速度也是慢的要死,甚至有时候抽风会访问失败,要缓解这种情况,需要手动在 gfwlist 列表中加入这些域名,让它们走代理出去。
  • 绕过大陆地址:此模式就不会有上述问题,因为,只要是国外网站都是会走代理的,不管它有没有被墙。其实不用太过纠结流量问题,浏览网页不需要什么流量,只有下载,看视频才是流量杀手。再说更新的问题,gfwlist 模式需要经常更新 gfwlist.txt,但是绕过大陆地址模式却不需要,毕竟大陆地址段很少会变化。

对于二号方案、三号方案,有一个问题需要注意,那就是内网主机的 DNS 服务器必须指向 192.168.1.1(即网关的 chinadns/pdnsd)。为什么呢?假设你将 DNS 设为 8.8.8.8,那么你会发现 DNS 解析不了了,查看 ss-redir 的日志会发现这个错误:ERROR: [udp] remote_recv_bind: Address already in use,意思是“地址被占用”。那具体是什么端口被什么进程给占用了呢?因为 TPROXY 进行透明代理时,为了回复内网主机的 DNS 请求,要在本机上绑定一个不存在的 8.8.8.8:53/udp 地址,但是 53/udp 端口被 chinadns/pdnsd 给占用了,导致 TPROXY 无法绑定这个端口,从而透明代理失败。不过,你可以使用一个支持非 53 端口的 DNS 服务器,比如 OpenDNS 就支持 443 端口的 DNS 查询,不过,这样做是费力不讨好的,对于一般的操作系统,你是无法通过常规方法将系统 DNS 指定为 443 端口的。所以,还是老老实实的将 DNS 指向 chinadns/pdnsd 吧。

为了避免出现类似的端口被占用问题,我特意的将 ss-redir、ss-tunnel、chinadns 的监听端口设为高位端口 60000+,因为几乎没有哪个公共服务会使用这些端口,也就不会出现类似的地址被占用问题了。PS:我测试过,如果将 ss-redir 监听在 53 端口,内网主机使用 8.8.8.8 进行 DNS 解析,却不会出现端口被占用问题,尽管如此,我还是将它们的端口设的高一些,避免不必要的踩坑。

最后来说明 SS 透明代理与 SSR 透明代理的相关问题。SSR 是 SS 的一个分支,SSR 最主要的特点就是:支持协议插件(protocol)与混淆插件(obfs)。因为部分地区的 SS 已经被 GFW 识别,导致无法正常使用(限速、间歇性断流等等问题),而 SSR 的混淆功能貌似可以缓解 GFW 的干扰。

SS 透明代理与 SSR 透明代理总体上是没有多大区别的,顶多就是添加了两个参数(协议、混淆)。如果你的地区可以正常使用 SS,是不建议使用 SSR 透明代理的。因为 shadowsocksr-libev 维护力量不足(大神都维护 Python 版去了,然而 Python 版没有 ss-redir),导致更新滞后,一些较新的协议插件、混淆插件还不支持。

其实也不是必须要使用 ss-libev、ssr-libev,也可以使用其它任意版本的 ss、ssr(如 python 版),然后再配合 redsocks 来做透明代理。但为啥我不使用这种方式呢?因为额外套了一层 redsocks,延迟可能更高一些(当然我没有测试过,只是理论来说),如果你实在是不想安装 ss-libev、ssr-libev,那么你可以采用这种方式来进行全局透明代理,它们的限制都是一样的,都无法代理本机的 udp 流量。

如果确实需要代理本机 udp 流量,比如你只是想把 ss-tproxy 部署在内网主机上,而不是部署在网关/路由上。你也可以使用这个全局透明代理方案 - ss-tun2socks,该方案利用 tun 虚拟网卡 + tun2socks 来实现全局透明代理(包括本机 udp)。不过该方案有个瑕疵,那就是延迟比 ss-tproxy 高一些(因为冗余数据过多,导致有效载荷比较低,就像 VPN 全局代理一样),所以我并没有着重介绍这个代理方案,仅仅是给需要的人准备的。

安装依赖

curl

ipset

TPROXY

TPROXY 是一个 Linux 内核模块,在 Linux 2.6.28 后进入官方内核。一般正常的发行版都没有裁剪 TPROXY 模块,TPROXY 模块缺失问题主要出现在无线路由固件上。使用以下方法可以检测当前内核是否包含 TPROXY 模块,如果没有,请自行解决。

iproute2

haveged

如果有时候启动 ss-redir、ss-tunnel 会失败,且错误提示如下,则需要安装 haveged 或 rng-utils/rng-tools。虽然这个依赖是可选的,但强烈建议大家安装(并设为开机自启状态)。

这里以 haveged 为例,当然,你也可以选择安装 rng-utils/rng-tools,都是一样的:

pdnsd

chinadns

ss-libev

shadowsocks-libevshadowsocksr-libev 二选一,也可一并安装

ArchLinux:建议使用 pacman -S shadowsocks-libev 安装,方便快捷,更新也及时。
CentOS/RHEL 及其它 Linux 发行版,强烈建议使用 编译安装 方式,仓库安装的有些问题(CentOS 7.4 实测)。
这里只简单记录 CentOS 的安装细节,其它发行版的编译方式是相同的,只不过安装依赖的方式不一样。详见 README.md

ssr-libev

shadowsocks-libevshadowsocksr-libev 二选一,也可一并安装

github 项目地址:shadowsocksr-backup/shadowsocksr-libev,安装细节如下:

一键脚本

脚本依赖

  • curl,获取大陆地址段列表
  • ipset,保存大陆地址段列表
  • TPROXY,内核模块,透明代理 UDP
  • iproute2,策略路由,透明代理 UDP
  • haveged,防止系统出现熵过低的问题
  • pdnsd,支持永久性缓存的 DNS 代理服务器
  • chinadns,利用大陆地址段列表实现 DNS 分流
  • shadowsocks-libev,ss-redir、ss-tunnel,SS 透明代理
  • shadowsocksr-libev,ssr-redir、ssr-tunnel,SSR 透明代理
  • 注:shadowsocks-libev、shadowsocksr-libev 二选一,可一并安装

端口占用

请检查是否有端口被占用,如果有请自行解决!

  • pdnsd:0.0.0.0:53/udp
  • chinadns:0.0.0.0:65353/udp
  • ss-redir:0.0.0.0:60080/tcp+udp
  • ss-tunnel:0.0.0.0:60053/tcp+udp

脚本用法

获取

  • git clone https://github.com/zfl9/ss-tproxy.git

安装

  • cd ss-tproxy
  • cp -af ss-tproxy /usr/local/bin/
  • cp -af ss-switch /usr/local/bin/
  • chown root:root /usr/local/bin/ss-tproxy /usr/local/bin/ss-switch
  • chmod +x /usr/local/bin/ss-tproxy /usr/local/bin/ss-switch
  • mkdir -m 0755 -p /etc/tproxy
  • cp -af pdnsd.conf /etc/tproxy/
  • cp -af chnroute.txt /etc/tproxy/
  • cp -af chnroute.ipset /etc/tproxy/
  • cp -af ss-tproxy.conf /etc/tproxy/
  • chown -R root:root /etc/tproxy
  • chmod 0644 /etc/tproxy/*

配置

  • vim /etc/tproxy/ss-tproxy.conf,修改后重启 ss-tproxy 生效
  • 修改开头的 ss/ssr 配置,具体的含义请参考注释(此段配置必须修改)
  • 切换 ss/ssr 节点时请修改 ss-tproxy.conf,或使用 ss-switch 切换(ss-switch -h 查看帮助)
  • chinadns_upstream="114.114.114.114,127.0.0.1:${tunnel_port}":建议将 114 改为原网络下的 DNS
  • iptables_intranet=(192.168.0.0/16):如果内网网段不是 192.168/16,请修改(可以有多个,空格隔开)
  • dns_original=(114.114.114.114 119.29.29.29 180.76.76.76):建议修改为原网络下的 DNS(最多 3 个)

自启(Systemd)

  • cp -af ss-tproxy.service /etc/systemd/system/
  • systemctl daemon-reload
  • systemctl enable ss-tproxy.service

自启(SysVinit)

  • touch /etc/rc.d/rc.local
  • chmod +x /etc/rc.d/rc.local
  • echo "/usr/local/bin/ss-tproxy start" >> /etc/rc.d/rc.local

配置 ss-tproxy 开机自启后容易出现一个问题,那就是必须再次运行 ss-tproxy restart 后才能正常代理(这之前查看运行状态,可能看不出任何问题,都是 running 状态),这是因为 ss-tproxy 启动过早了,且 server_addr 为 Hostname,且没有将 server_addr 中的 Hostname 加入 /etc/hosts 文件而导致的。因为 ss-tproxy 启动时,网络还没准备好,此时根本无法解析这个 Hostname。要避免这个问题,可以采取一个非常简单的方法,那就是将 Hostname 加入到 /etc/hosts 中,如 Hostname 为 node.proxy.net,对应的 IP 为 11.22.33.44,则只需执行 echo "11.22.33.44 node.proxy.net" >> /etc/hosts。不过得注意个问题,那就是假如这个 IP 变了,别忘了修改 /etc/hosts 文件哦。命令行获取某个域名对应的 IP 地址的方法:dig +short HOSTNAME

用法

  • ss-tproxy help:查看帮助
  • ss-tproxy start:启动代理
  • ss-tproxy stop:关闭代理
  • ss-tproxy restart:重启代理
  • ss-tproxy status:运行状态
  • ss-tproxy current_ip:查看当前 IP(一般为本地 IP)
  • ss-tproxy flush_dnsche:清空 dns 缓存(pdnsd 的缓存)
  • ss-tproxy update_chnip:更新大陆地址段列表(ipset、chinadns)

日志

如需详细日志,请打开 ss-tproxy.conf 中相关的 verbose 选项。

  • pdnsd:/var/log/pdnsd.log
  • chinadns:/var/log/chinadns.log
  • ss-redir:/var/log/ss-redir.log
  • ss-tunnel:/var/log/ss-tunnel.log

代理测试

此处的测试方法并不严谨,下节内容有更加详细的调试方法(出问题时请进行调试)。

测试 tcp

测试 tcp 非常简单,我们只要打开浏览器,看能不能访问被墙网站就知道了。

测试 udp

测试 udp 也很容易,只要在其它主机上进行 dns 测试就可以了,注意不能使用 53/udp 端口哦,因为该端口被占用了。

常见问题

ss-tproxy 启动报错 iptables: No chain/target/match by that name.
先使用 modprobe xt_TPROXY 尝试加载 TPROXY 模块,如果提示未找到,则说明内核缺少 xt_TPROXY 模块。这个问题多出现在精简版、定制版 Linux 上,比如部分无线路由的固件。如果遇到这个问题,请联系固件厂商,或者自行编译 TPROXY 模块。

pdnsd 启动失败,显示 stopped
cat /var/log/pdnsd.log 查看 pdnsd 日志,若为 error: Could not bind to udp socket: Address already in use,则说明 53/udp 端口被占用了。使用 ss -lnpu | grep :53 查看占用 53/udp 端口的程序,然后关闭它。如果是 dnsmasq 占用了该端口,请关闭 dnsmasq 的 dns 功能,修改 dnsmasq.conf 配置文件,port=0 将端口改为 0,然后重启 dnsmasq,再次尝试 ss-tproxy restart。如果是 systemd-resolve 服务占用了该端口,请使用 systemctl stop systemd-resolved.service && systemctl disable systemd-resolved.service 关闭并禁用 systemd-resolved 系统服务。对于已经安装了 dnsmasq 的用户,可以不使用 pdnsd,而是直接在 dnsmasq 中配置上游 DNS 为 ChinaDNS(127.0.0.1:65353),然后修改 ss-tproxy 脚本的 start()stop()status() 3 个函数,将里面的 pdnsd 删除,或者改为 dnsmasq 相关的语句(ss-tproxy 注释版见文末)。

关于网关的 DHCP 配置细节
基本配置不用变动,不过还是得注意一下 DNS 问题,分配给内网主机的 DNS 建议设为网关 IP(即运行 ss-tproxy 的主机)。假设整个内网的网段是 192.168.1.0/24,网关(即运行 ss-tproxy 的主机)的 IP 为 192.168.1.1,则将 DHCP 分配的 DNS 设为 192.168.1.1。不过这只是个建议,没有强制要求,你设为 114DNS、GoogleDNS、OpenDNS 也没问题。但千万别设置为 127.0.0.1 这样的地址,或者是内网中的其它主机,这种情况下 ss-tproxy 的 DNS 强制重定向就失效了,会导致内网主机的 DNS 解析失败,无法正常联网。

关于 ss-tproxy 的一些疑问
1、ss-tproxy 支持什么 linux 系统?
一般都支持,没有特定的限制,因为没有使用与特定发行版相关的命令、功能。

2、ss-tproxy 只能运行在网关上吗?
很显然不是,你完全可以在内网主机中运行它,ss-tproxy 依旧会透明代理该主机的 TCP 流量,提供无污染的 DNS 解析。但是 UDP 是无法透明代理的,如果需要 UDP 透明代理,建议使用 ss-tun2socks,而不是 ss-tproxy。

3、ss-tproxy 可以运行在副路由上吗?
可以。假设你有两个路由器,一主一副,主路由通过 PPPOE 拨号上网,其它设备连接到主路由可以上外网(无科学上网),副路由的 WAN 口连接到主路由的 LAN 口,副路由的 WAN 网卡 IP 可以动态获取,也可以静态分配,此时,副路由自己也是能够上外网的。然后,在副路由上运行 ss-tproxy,此时,副路由已经能够科学上网了,然后,我们在副路由上配置一个 LAN 网段(不要与主路由的 LAN 网段一样),假设主路由的 LAN 网段是 192.168.1.0/24,副路由的 LAN 网段是 10.10.10.0/24。然后指定一个网关 IP 给副路由的 LAN 口,假设为 10.10.10.1,开启副路由上的 DHCP,分配的地址范围为 10.10.10.100-200,分配的网关地址为 10.10.10.1,分配的 DNS 服务器为 10.10.10.1。现在,修改 ss-tproxy.conf 的内网网段为 10.10.10.0/24,重启 ss-tproxy,然后连接到副路由的设备应该是能够科学上网的。你可能会问,为什么不直接在主路由上安装 ss-tproxy 呢?假设这里的副路由是一个树莓派,那么我不管在什么网络下(公司、酒店),只要将树莓派插上,然后我的设备(手机、笔记本)只需要连接树莓派就能无缝上网了,同时又不会影响内网中的其它用户,一举两得。

4、ss-tproxy 可以运行在内网主机上吗(代理网关)?
可以。先解释一下这里的“代理网关”(不知道叫什么好),由网友 @feiyu 启发。他的意思是,将 ss-tproxy 部署在一台普通的内网主机上(该内网主机的网关不变),然后将其他内网主机的网关指向这台部署了 ss-tproxy 的主机,进行代理。方案是可行的,我在 VMware 虚拟机环境中测试通过。

5、可以不使用 ss-libev、ssr-libev 吗?
可以,将 ss-redir + ss-tunnel 替换为 redsocks + ss-local,iptables 规则大同小异,有兴趣可以自己琢磨一下。

6、可以用 dnsmasq 替代 pdnsd 吗?
可以。开启 dnsmasq 的 dns 功能,将 ChinaDNS 设为它的上游服务器,修改 ss-tproxy 脚本的 start()、status()、stop() 函数,把 pdnsd 相关的语句改为 dnsmasq 的(脚本注释见文末)。

7、可以不使用 pdnsd/dnsmasq 等 dns 缓存服务器吗?
可以。这种情况下,ChinaDNS 将成为 dns 服务器(无缓存功能,并发性能可能不理想,UDP 丢包较严重的不建议这么做)。具体的:修改 ss-tproxy.conf,将 ChinaDNS 的监听端口改为 53,然后修改 ss-tproxy 脚本的 start()、status()、stop() 函数,把 pdnsd 相关语句删掉(脚本注释见文末)。

8、ss-tproxyss-tun2socks 有什么区别?
ss-tproxy 利用 REDIRECT(DNAT) + TPROXY 进行透明代理,因为进行了 NAT,网关 UDP 将无法被代理。
ss-tun2socks 利用 tun 网卡 + tun2socks 进行透明代理,没有进行 NAT 转换,可以代理所有 TCP/UDP。
但是 ss-tun2socks 因为涉及 IP 层、传输层之间的转换,性能和延迟都比 ss-tproxy 差一些(只是一些)。

ss-tproxy 无法正常代理,如何进行调试?
首先,修改 /etc/tproxy/ss-tproxy.conf,将 pdnsd、chinadns、ss-redir、ss-tunnel 详细日志打开(pdnsd 的日志级别设置为 3),关闭 ss-tproxy,清空 pdnsd 的缓存(ss-tproxy flush),清空 /var/log 下的 4 个日志文件。

然后开 5 个终端窗口,前 4 个终端,分别 tail -f /var/log/pdnsd.logtail -f /var/log/chinadns.logtail -f /var/log/ss-tunnel.logtail -f /var/log/ss-redir.log,最后 1 个终端,启动 ss-tproxy start(先排除 ss-tproxy 启动过程中的报错,如某某命令执行出错)。

观察前 4 个终端,如果有报错,可以尝试自己解决(如果无法解决,请 email、评论区告知),如果没有报错,则在最后那个终端执行 dig @127.0.0.1 -p60053 www.google.com,如果能够解析,且 ss-tunnel 窗口能看到相关 INFO,说明 ss-tunnel 没问题,ss 服务器的 udp relay 也没问题。如果提示查询超时,且 ss-tunnel 窗口没有信息,或者有错误信息,说明 ss 的 udp relay 可能有问题,请确认是否已开启 udp relay。

然后,再测试 chinadns,执行 dig @127.0.0.1 -p65353 www.baidu.com,如果能够解析,说明国内 DNS 解析没问题,如果不能请检查 chinadns_upstream 中的国内 DNS 是否正确且可访问,同时结合 chinadns.log 日志定位问题。然后执行 dig @127.0.0.1 -p65353 www.google.com,如果能够解析,说明国外 DNS 解析也没问题,如果不能请结合 chinadns.log、ss-tunnel.log 日志定位问题。

然后,再测试 pdnsd,执行 dig @127.0.0.1 -p53 www.baidu.com,如果能够解析,则再次执行它,并观察前后两次的 query time,第二次应该为 0ms。如果不能解析,请结合 pdnsd.log、chinadns.log 日志定位问题。然后,执行 dig @127.0.0.1 -p53 www.google.com,如果能够解析,则再次执行它,并观察前后两次的 query time,第二次应该为 0ms。如果不能解析,请结合 pdnsd.log、chinadns.log、ss-tunnel.log 日志定位问题。

如果以上步骤都没有问题,则进行 ss-redir 的测试,执行 curl -4sSkL https://www.baidu.com,如果能访问,则继续执行 curl -4sSkL https://www.google.com,如果能够访问,则说明 pdnsd、chinadns、ss-tunnel、ss-redir 都正常运行。

如果以上步骤还是没问题,但是内网主机仍然无法进行代理,可能是 iptables 规则不正确,先检查 /etc/tproxy/ss-tproxy.conf 中的 iptables_intranet 是否配置正确,是不是当前的内网网段。如果可以,请查看 iptables 规则,找出可能的错误,iptables -t mangle -nvL --line-numbers 查看 mangle 表的规则,iptables -t nat -nvL --line-numbers 查看 nat 表的规则。如果觉得可能有问题,请酌情修改,如果仍无法解决,请 email、评论区告知,尽量解决。

ss-tproxy 一键脚本(注释版)
如果默认的 ss-tproxy 脚本不适合您,您也可以自己动手修改 ss-tproxy 脚本。ss-tproxy 其实就两个部分,一个是 ss-tproxy 脚本本身,一个是 ss-tproxy.conf 配置文件。ss-tproxy.conf 实际上是一个 shell 脚本,里面的配置项都是一个个变量而已。脚本注释: