ss/ssr/v2ray/socks5 透明代理

先说 ss/ssr 透明代理吧,ss-redir 是 ss-libevssr-libev 中的一个工具,配合 iptables 可以在 Linux 上实现 ss、ssr 透明代理,ss-redir 的透明代理是通过 DNAT 实现的,但是 udp 包在经过 DNAT 后会无法获取原目的地址,所以 ss-redir 无法代理经过 DNAT 的 udp 包;但是 ss-redir 提供了另一种 udp 透明代理方式:xt_TPROXY 内核模块(不涉及 NAT 操作),配合 iproute2 即可实现 udp 的透明代理,但缺点是只能代理来自内网主机的 udp 流量。强调一点,利用 ss-redir 实现透明代理必须使用 ss-libev 或 ssr-libev,python、go 等实现版本没有 ss-redir、ss-tunnel 程序。当然,ss、ssr 透明代理并不是只能用 ss-redir 来实现,使用 ss-local + redsocks/tun2socks 同样可以实现 socks5(ss-local 是 socks5 服务器)全局透明代理,ss-local + redsocks 实际上是 ss-redir 的分体实现,都是通过 NAT 进行代理的,因此也不能代理本机的 udp,当然内网的 udp 也不能代理,因为 redsocks 不支持 xt_TPROXY 方式(redsocks2 支持 TPROXY 模块,但是依旧无法代理本机 udp,不考虑)。所以这里只讨论 ss-local + tun2socks,这个组合方式其实和 Android 上的 VPN 模式差不多(ss-redir 或 ss-local + redsocks 则是 NAT 模式),因为不涉及 NAT 操作,所以能够代理所有 tcp、udp 流量(包括本机、内网的 udp)。很显然,利用 tun2socks 可以实现任意 socks5 透明代理(不只是 ss/ssr,ssh、v2ray 都可以,只要能提供 socks5 本地代理)。最后再说一下 v2ray 的透明代理,其实原理和 ss/ssr-libev 一样,v2ray 可以看作是 ss-local、ss-redir、ss-tunnel 三者的合体,因为一个 v2ray 客户端可以同时充当这三个角色(当然端口要不一样);所以 v2ray 的透明代理也有两种实现方式,一是利用对应的 ss-redir/ss-tunnel + iptables,二是利用对应的 ss-local + tun2socks(这其实就是前面说的 socks5 代理)。

组件区别

ss-server

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

ss-local

shadowsocks 客户端程序,核心部件之一,各大版本均提供 ss-local 程序。
ss-local 是运行在本地的 socks5 代理服务器,根据 OSI 模型,socks5 是会话层协议,支持 TCP 和 UDP 的代理。

但是现在只有少数软件直接支持 socks5 代理协议,绝大多数都只支持 http 代理协议。好在我们可以利用 privoxy 将 socks5 代理转换为 http 代理,使用 privoxy 还有一个好处,那就是可以实现 gfwlist 分流模式(不过现在的 ss-tproxy 脚本也可以了),如果你对它感兴趣,可以看看 ss-local 终端代理

ss-redir

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

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

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

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

举个栗子:
ss-local 提供 socks5 正向代理,要让软件使用该代理,必须对软件进行相应的代理配置,否则不会走代理;
ss-redir 提供 socks5 透明代理,配置合适网络规则后,软件会在不知情的情况下走代理,不需要额外配置。

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 接收 -> 上层应用。

方案说明

用过 Linux SS/SSR 客户端(尤其指命令行界面)的都知道,它们比 Windows/Android 中的 SS/SSR 客户端难用多了,安装好就只有一个 ss-local(libev 版还有 ss-redir、ss-tunnel,但我相信大部分人装得都是 python 版的),启动 ss-local 后并不会像 Windows/Android 那样自动配置系统代理,此时它仅仅是一个本地 socks5 代理服务器,默认监听 127.0.0.1:1080,如果需要利用该 socks5 代理上外网,必须在命令中指定对应的代理,如 curl -4sSkL -x socks5h://127.0.0.1:1080 https://www.google.com

但我想大部分人要的代理效果都不是这种的,太原始了。那能不能配置所谓的“系统代理”呢,可以是可以,但是好像只支持 http 类型的代理,即在当前 shell 中设置 http_proxyhttps_proxy 环境变量,假设存在一个 http 代理(支持 CONNECT 请求方法),监听地址是 127.0.0.1:8118,可以这样做:export http_proxy=http://127.0.0.1:8118; export https_proxy=$http_proxy。执行完后,git、curl、wget 等命令会自动从环境变量中读取 http 代理信息,然后通过 http 代理连接目的服务器。

那问题来了,ss-local 提供的是 socks5 代理,不能直接使用怎么办?也简单,Linux 中有很多将 socks5 包装为 http 代理的工具,比如 privoxy。只需要在 /etc/privoxy/config 里面添加一行 forward-socks5 / 127.0.0.1:1080 .,启动 privoxy,默认监听 127.0.0.1:8118 端口,注意别搞混了,8118 是 privoxy 提供的 http 代理地址,而 1080 是 ss-local 提供的 socks5 代理地址,发往 8118 端口的数据会被 privoxy 处理并转发给 ss-local。所以我们现在可以执行 export http_proxy=http://127.0.0.1:8118; export https_proxy=$http_proxy 来配置当前终端的 http 代理,这样 git、curl、wget 这些就会自动走 ss-local 出去了。

当然我们还可以利用 privoxy 灵活的配置,实现 Windows/Android 中的 gfwlist 分流模式。gfwlist.txt 其实是对应的 Adblock Plus 规则的 base64 编码文件,显然不能直接照搬到 privoxy 上。这个问题其实已经有人解决了,利用 snachx/gfwlist2privoxy python 脚本就可轻松搞定。但其实我也重复的造了一个轮子:zfl9/gfwlist2privoxy,我并不是吃饱了没事干,是因为我当时运行不了他的脚本(也不知道什么原因),所以花了点时间用 shell 脚本实现了一个 gfwlist2privoxy(但其实我是用 perl 转换的,只不过用 shell 包装了一下)。脚本转换出来的是一个 gfwlist.action 文件,我们只需将该 gfwlist.action 文件放到 /etc/privoxy 目录,然后在 config 中添加一行 actionsfile gfwlist.action(当然之前 forward-socks5 那行要注释掉),重启 privoxy 就可以实现 gfwlist 分流了。

但仅仅依靠 http_proxyhttps_proxy 环境变量实现的终端代理效果不是很好,因为有些命令根本不理会你的 http_proxyhttps_proxy 变量,它们依旧走的直连。但又有大神想出了一个巧妙的方法,即 rofl0r/proxychains-ng,其原理是通过 LD_PRELOAD 特殊环境变量提前加载指定的动态库,来替换 glibc 中的同名库函数。这个 LD_PRELOAD 指向的其实就是 proxychains-ng 实现的 socket 包装库,这个包装库会读取 proxychains-ng 的配置文件(这里面配置代理信息),之后执行的所有命令调用的 socket 函数其实都是 proxychains-ng 动态库中的同名函数,于是就实现了全局代理,而命令对此一无所知。将 proxychains-ng 与 privoxy 结合起来基本上可以完美实现 ss/ssr 的本地全局 gfwlist 代理(小技巧,在 shell 中执行 exec proxychains -q bash 可以实现当前终端的全局代理,如果需要每个终端都自动全局代理,可以在 bashrc 文件中加入这行)。

但是很多人对此依然无法满足,因为他们想实现 OpenWrt 那种路由级别的全局透明代理(并且还有 gfwlist、绕过大陆地址段这些分流模式可选择),这样只要设备连到 WiFi 就能直接无缝上网,完全感觉不到“墙”的存在。如果忽略分流模式(即全部流量都走代理出去),那么实现是很简单的(几条 iptables 就可以搞定,但是这太简单粗暴了,很多国内网站走代理会非常慢,体验很不好);但是如果要自己实现 gfwlist、绕过大陆地址段这些模式,恐怕很多人都会望而却步,因为确实复杂了一些。但这种透明代理的模式的确很诱人,毕竟只要设置一次就可以让所有内网设备上 Internet,所以我开始摸索如何在 Linux 软路由中(我用的是树莓派 3B,目前还凑合,当然也可以直接在 Windows 中利用 VMware/VirtualBox 建一个桥接模式的虚拟机,做所谓的 代理网关)实现类似 OpenWrt 的代理模式,而我摸索出来的成果就是 ss-tproxy 透明代理脚本。

ss-tproxy 脚本经过几次大改进,现在已经支持 ss/ssr/v2ray/socks5 4 种类型的透明代理了,具体的原理在开头也提了一些,这里并不打算讲述 ss-tproxy 实现各种代理模式、分流模式的原理,因为我快编不下去了(写的太烂,怕被笑话)。脚本目前实现了 4 种分流模式:global(全局模式,不分流)、gfwlist(仅代理 gfwlist 域名)、chnonly(仅代理大陆域名,国外翻回国内)、chnroute(绕过大陆地址段,其余均走代理),15 个 mode:

标识了 tcponly 的 mode 表示只代理 tcp 流量(dns 通过 tcp 方式解析),udp 不会被处理,主要用于不支持 udp relay 的 ss/ssr/socks5 代理。除了 tun2socks mode 外,其它的 mode 均不能代理 ss-tproxy 本机的 udp 流量(dns 会另外处理),tun2socks mode 之所以能代理本机 udp 是因为它不是依靠 iptables-DNAT/TPROXY 实现的,而是通过策略路由 + tun 虚拟网卡。还有,chnonly 模式并未单独分出来,因为它本质上与 gfwlist 模式完全相同,只不过域名列表不一样而已,所以如果要使用 chnonly 分流模式,请选择对应的 gfwlist mode(当然还需要几个步骤,后面会说明)。

安装依赖

  • v2ray_global: v2ray-core, xt_TPROXY, iproute2, dnsmasq
  • v2ray_gfwlist: v2ray-core, xt_TPROXY, iproute2, ipset, perl, dnsmasq
  • v2ray_chnroute: v2ray-core, xt_TPROXY, iproute2, ipset, chinadns, dnsmasq
  • tproxy_global: ss/ssr-libev, haveged, xt_TPROXY, iproute2, dnsmasq
  • tproxy_gfwlist: ss/ssr-libev, haveged, xt_TPROXY, iproute2, ipset, perl, dnsmasq
  • tproxy_chnroute: ss/ssr-libev, haveged, xt_TPROXY, iproute2, ipset, chinadns, dnsmasq
  • tproxy_global_tcp: ss/ssr-libev, haveged, dnsforwarder
  • tproxy_gfwlist_tcp: ss/ssr-libev, haveged, ipset, perl, dnsmasq, dnsforwarder
  • tproxy_chnroute_tcp: ss/ssr-libev, haveged, ipset, chinadns, dnsforwarder
  • tun2socks_global: socks5 代理, tun2socks, iproute2, dnsmasq
  • tun2socks_gfwlist: socks5 代理, tun2socks, iproute2, ipset, perl, dnsmasq
  • tun2socks_chnroute: socks5 代理, tun2socks, iproute2, ipset, chinadns, dnsmasq
  • tun2socks_global_tcp: socks5 代理, tun2socks, iproute2, dnsforwarder
  • tun2socks_gfwlist_tcp: socks5 代理, tun2socks, iproute2, ipset, perl, dnsmasq, dnsforwarder
  • tun2socks_chnroute_tcp: socks5 代理, tun2socks, iproute2, ipset, chinadns, dnsforwarder
  • haveged 依赖项是可选的,主要用于防止系统的熵过低,从而导致 ss-redir、ss-tunnel、ss-local 启动失败等问题
  • *gfwlist* 模式更新列表时依赖 curl、base64;*chnroute* 模式更新列表时依赖 curl;建议安装,以备不时之需
  • *gfwlist* 模式中的 perl 其实可以使用 sed 替代,但由于更新 gfwlist 列表依赖 perl5 v5.10.0+,所以直接用了 perl

curl

请检查 curl 是否支持 HTTPS 协议,使用 curl --version 可查看(Protocols)

perl5

Perl5 的版本最好 v5.10.0+ 以上(使用 perl -v 命令可查看)

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,都是一样的:

dnsmasq

chinadns

dnsforwarder

如果 make 报错,提示 undefined reference to rpl_malloc,请编辑 config.h.in 文件,把里面的 #undef malloc#undef realloc 删掉,然后再编译,即:./configure --enable-downloader=nomake && make install

v2ray

安装很简单,直接使用 v2ray 官方提供的 shell 脚本即可,默认配置开机自启

ss-libev

ArchLinux 建议使用 pacman -S shadowsocks-libev 安装,方便快捷,更新也及时。
CentOS/RHEL 或其它发行版,强烈建议 编译安装,仓库安装的可能会有问题(版本太老或者根本用不了)。
下面的代码完全摘自 ss-libev 官方 README.md,随着时间的推移可能有变化,最好照着最新 README.md 来做。

ssr-libev

shadowsocksr-backup/shadowsocksr-libev(貌似已停止更新,但目前使用没问题,只是一些新特性不支持,如有更好的源请告诉我~)
https://github.com/shadowsocksrr/shadowsocksr-libev/tree/Akkariiin/master,另一个 ssr-libev 源,Akkariiin-* 分支目前仍在更新
本文仍以 shadowsocksr-backup/shadowsocks-libev 为例,毕竟另一个源我没试过,但是这个源我自己用了大半年,没有任何问题,很稳定

tun2socks

请选择与 CPU 相对应的二进制程序,下载后请去掉后缀名,放到 /usr/local/bin,添加可执行权限。

代理脚本

脚本简介

ss-tproxy 脚本运行于 Linux 系统(网关、软路由、虚拟机、普通 PC),用于实现 SS/SSR/V2Ray/Socks5 全局透明代理功能。普遍用法是将 ss-tproxy 部署在 Linux 软路由(或位于桥接模式下的 Linux 虚拟机),透明代理内网主机的 TCP、UDP 数据流(具体玩法可参考 ss-tproxy 常见问题)。脚本目前实现了 4 种分流模式:global(全局模式,不分流)、gfwlist(仅代理 gfwlist 域名)、chnonly(仅代理大陆域名,国外翻回国内)、chnroute(绕过大陆地址段,其余均走代理)。脚本目前定义了 15 种代理模式,如下(下文中的“本机”指运行 ss-tproxy 的主机):

标识了 tcponly 的 mode 表示只代理 tcp 流量(dns 通过 tcp 方式解析),udp 不会被处理,主要用于不支持 udp relay 的 ss/ssr/socks5 代理。除了 tun2socks mode 外,其它的 mode 均不能代理 ss-tproxy 本机的 udp 流量(dns 会另外处理),tun2socks mode 之所以能代理本机 udp 是因为它不是依靠 iptables-DNAT/TPROXY 实现的,而是通过策略路由 + tun 虚拟网卡。还有,chnonly 模式并未单独分出来,因为它本质上与 gfwlist 模式完全相同,只不过域名列表不一样而已,所以如果要使用 chnonly 分流模式,请选择对应的 gfwlist mode(当然还需要几个步骤,后面会说明)。

脚本依赖

  • ss-tproxy 脚本相关依赖的安装参考
  • v2ray_global: v2ray-core, xt_TPROXY, iproute2, dnsmasq
  • v2ray_gfwlist: v2ray-core, xt_TPROXY, iproute2, ipset, perl, dnsmasq
  • v2ray_chnroute: v2ray-core, xt_TPROXY, iproute2, ipset, chinadns, dnsmasq
  • tproxy_global: ss/ssr-libev, haveged, xt_TPROXY, iproute2, dnsmasq
  • tproxy_gfwlist: ss/ssr-libev, haveged, xt_TPROXY, iproute2, ipset, perl, dnsmasq
  • tproxy_chnroute: ss/ssr-libev, haveged, xt_TPROXY, iproute2, ipset, chinadns, dnsmasq
  • tproxy_global_tcp: ss/ssr-libev, haveged, dnsforwarder
  • tproxy_gfwlist_tcp: ss/ssr-libev, haveged, ipset, perl, dnsmasq, dnsforwarder
  • tproxy_chnroute_tcp: ss/ssr-libev, haveged, ipset, chinadns, dnsforwarder
  • tun2socks_global: socks5 代理, tun2socks, iproute2, dnsmasq
  • tun2socks_gfwlist: socks5 代理, tun2socks, iproute2, ipset, perl, dnsmasq
  • tun2socks_chnroute: socks5 代理, tun2socks, iproute2, ipset, chinadns, dnsmasq
  • tun2socks_global_tcp: socks5 代理, tun2socks, iproute2, dnsforwarder
  • tun2socks_gfwlist_tcp: socks5 代理, tun2socks, iproute2, ipset, perl, dnsmasq, dnsforwarder
  • tun2socks_chnroute_tcp: socks5 代理, tun2socks, iproute2, ipset, chinadns, dnsforwarder
  • haveged 依赖项是可选的,主要用于防止系统的熵过低,从而导致 ss-redir、ss-tunnel、ss-local 启动失败等问题
  • *gfwlist* 模式更新列表时依赖 curl、base64;*chnroute* 模式更新列表时依赖 curl;建议安装,以备不时之需
  • *gfwlist* 模式中的 perl 其实可以使用 sed 替代,但由于更新 gfwlist 列表依赖 perl5 v5.10.0+,所以直接用了 perl
  • tun2socks 依赖 net-tools 包中的 ifconfig 命令,如果启动 tun2socks 失败,可能是没有安装 net-tools 的原因

端口占用

  • 请确保相关端口未被其它进程占用,如果有请自行解决
  • v2ray_global: dnsmasq=53/udp
  • v2ray_gfwlist: dnsmasq=53/udp
  • v2ray_chnroute: chinadns=65353/udp, dnsmasq=53/udp
  • tproxy_global: ss-redir=60080/tcp+udp, ss-tunnel=60053/udp, dnsmasq=53/udp
  • tproxy_gfwlist: ss-redir=60080/tcp+udp, ss-tunnel=60053/udp, dnsmasq=53/udp
  • tproxy_chnroute: ss-redir=60080/tcp+udp, ss-tunnel=60053/udp, chinadns=65353/udp, dnsmasq=53/udp
  • tproxy_global_tcp: ss-redir=60080/tcp, dnsforwarder=53/udp
  • tproxy_gfwlist_tcp: ss-redir=60080/tcp, dnsforwarder=60053/udp, dnsmasq=53/udp
  • tproxy_chnroute_tcp: ss-redir=60080/tcp, dnsforwarder=60053/udp, chinadns=65353/udp, dnsforwarder=53/udp
  • tun2socks_global: dnsmasq=53/udp
  • tun2socks_gfwlist: dnsmasq=53/udp
  • tun2socks_chnroute: chinadns=60053/udp, dnsmasq=53/udp
  • tun2socks_global_tcp: dnsforwarder=53/udp
  • tun2socks_gfwlist_tcp: dnsforwarder=60053/udp, dnsmasq=53/udp
  • tun2socks_chnroute_tcp: dnsforwarder=60053/udp, chinadns=65353/udp, dnsforwarder=53/udp

脚本用法

安装

  • git clone https://github.com/zfl9/ss-tproxy.git
  • cd ss-tproxy
  • cp -af ss-tproxy /usr/local/bin
  • chmod 0755 /usr/local/bin/ss-tproxy
  • chown root:root /usr/local/bin/ss-tproxy
  • mkdir -m 0755 -p /etc/tproxy
  • cp -af ss-tproxy.conf gfwlist.* chnroute.* /etc/tproxy
  • chmod 0644 /etc/tproxy/* && chown -R root:root /etc/tproxy

卸载

  • ss-tproxy stop
  • rm -fr /etc/tproxy /usr/local/bin/ss-tproxy

简介

  • ss-tproxy:脚本文件
  • ss-tproxy.conf:配置文件
  • ss-tproxy.service:服务文件
  • gfwlist.txt:gfwlist 域名文件,不可配置
  • gfwlist.ext:gfwlsit 黑名单文件,可配置
  • chnroute.set:chnroute for ipset,不可配置
  • chnroute.txt:chnroute for chinadns,不可配置

配置

  • 脚本的配置文件为 /etc/tproxy/ss-tproxy.conf,修改后重启脚本才能生效
  • 默认模式为 tproxy_chnroute,这也是 v1 版本中的模式,根据你的需要更改
  • 如果使用 v2ray* 模式,则修改 v2ray 配置 段中的相关 V2Ray 客户端的信息
  • 如果使用 tproxy* 模式,则修改 ss/ssr 配置 段中的相关 SS/SSR 服务器信息
  • 如果使用 tun2socks* 模式,则修改 socks5 配置 段中的相关 socks5 代理信息
  • dns_remote 为远程 DNS 服务器(走代理),默认为 Google DNS,根据需要修改
  • dns_direct 为直连 DNS 服务器(走直连),默认为 114 公共DNS,根据需要修改
  • iptables_intranet 指定要代理的内网网段,默认为 192.168.0.0/16,根据需要修改
  • 如需配置 gfwlist 黑名单,请编辑 /etc/tproxy/gfwlist.ext,修改后需重启脚本生效

如果你需要使用 chnonly 模式(国外翻进国内),请选择 *gfwlist* 代理模式,比如 tproxy_gfwlist。chnonly 模式下,你必须修改 ss-tproxy.conf 中的 dns_remote 为国内的 DNS,如 dns_remote='114.114.114.114:53',并将 dns_direct 改为本地 DNS(国外的),如 dns_direct='8.8.8.8';因为 chnonly 模式与 gfwlist 模式共享 gfwlist.txt、gfwlist.ext 文件,所以在第一次使用时你必须先运行 ss-tproxy update-chnonly 将默认的 gfwlist.txt 内容替换为大陆域名(更新列表时,也应使用 ss-tproxy update-chnonly),并且注释掉 gfwlist.ext 中的 Telegram IP 段,因为这是为正常翻墙设置的,反之亦然。

如果使用 v2ray 模式,你必须配置 v2ray 客户端的 dokodemo-door 传入协议,并且需要两个 dokodemo-door 配置,一个用于实现类似 ss-redir 的透明代理,另一个用于实现类似 ss-tunnel 的端口转发(解析 DNS)。具体配置可参考(原有的 inbound 可以不动,通常该 inbound 是一个 socks5 传入协议,你只要保证不同的 inbound 的监听端口不冲突就行):

自启(Systemd)

  • mv -f 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 主机使用 PPPoE 拨号上网(或其它耗时较长的方式),配置自启后,可能导致 ss-tproxy 在网络还未完全准备好的情况下先运行。此时如果 ss-tproxy.conf 中的 v2ray_serversocks5_remoteserver_addr 为域名,则会导致 ss-tproxy 启动失败(甚至卡一段时间,因为它一直在尝试解析这些域名,直到超时为止)。要恢复正常的代理,只能在启动完成后手动进行 ss-tproxy restart。如果你想避免这种情况,请尽量将 ss-tproxy.conf 中的 v2ray_serversocks5_remoteserver_addr 替换为 IP 形式,或者将这些域名添加到 ss-tproxy 主机的 /etc/hosts 文件,如果你和我一样使用的 ArchLinux,那么最好的解决方式就是利用 netctl 的 hook 启动 ss-tproxy(如拨号成功后再启动 ss-tproxy),具体配置可参考 Arch 官方文档

用法

  • ss-tproxy help:查看帮助
  • ss-tproxy start:启动代理
  • ss-tproxy stop:关闭代理
  • ss-tproxy restart:重启代理
  • ss-tproxy status:代理状态
  • ss-tproxy check-depend:检查依赖
  • ss-tproxy flush-cache:清空 DNS 缓存
  • ss-tproxy flush-gfwlist:清空 ipset-gfwlist IP 列表
  • ss-tproxy update-gfwlist:更新 gfwlist(restart 生效)
  • ss-tproxy update-chnonly:更新 chnonly(restart 生效)
  • ss-tproxy update-chnroute:更新 chnroute(restart 生效)
  • ss-tproxy dump-ipts:显示 iptables 的 mangle、nat 表规则
  • ss-tproxy flush-ipts:清空 raw、mangle、nat、filter 表规则

ss-tproxy flush-gfwlist 的作用:因为 *gfwlist* 模式下 ss-tproxy restartss-tproxy stop; ss-tproxy start 不会清空 ipset-gfwlist 列表,如果你进行了 ss-tproxy update-gfwlistss-tproxy update-chnonly 操作,或修改了 /etc/tproxy/gfwlist.ext 文件,建议在 start 前执行一下此步骤,防止因为之前遗留的 ipset-gfwlist 列表导致稀奇古怪的问题。注意,如果执行了 ss-tproxy flush-gfwlist 那么你可能需要清空内网主机的 dns 缓存,并重启浏览器等被代理的应用。

日志

脚本默认关闭了日志输出,如果需要,请修改 ss-tproxy.conf,打开相应的 log/verbose 选项

  • ss-redir:/var/log/ss-redir.log
  • ss-tunnel:/var/log/ss-tunnel.log
  • tun2socks:/var/log/tun2socks.log
  • dnsmasq:/var/log/dnsmasq.log
  • chinadns:/var/log/chinadns.log
  • dnsforwarder:/var/log/dnsforwarder.log

已知问题

  • v2ray 模式下,在 Android 中访问部分 Google 网站不是很稳定,其根本原因是因为 v2ray 的 dokodemo-door 传入协议对 Google 开发的 QUIC(Quick UDP Internet Connections)协议支持不好导致的,虽然我已经在 dokodemo-door 中配置了 "domainOverride": ["quic"](讨论此问题的 Issue:https://github.com/v2ray/v2ray-core/issues/819),但是测试后还是发现 domainOverride 效果不是很好,一会可以一会又不可以,并且 Google Play 的加载速度明显比其它类型的代理慢了许多。目前的解决方案有两个:一是使用 redsocks 对 v2ray 的 socks5 inbound 进行透明转发;二是使用脚本自带的 tun2socks 模式对 v2ray 的 socks5 inbound 进行透明转发(推荐此方式)。当然最好的解决方式还是希望 v2ray 官方能够解决此问题,虽然目前只有部分 Google 的网站启用了 QUIC,但是随着时间的推移,启用 QUIC 的网站肯定会更多,这个问题也会越严重!

更新计划

  • 内网主机黑名单(全走代理)、白名单(全走直连)支持,方便部分游戏用户
  • 精简 ss-tproxy 脚本,特别是 check_dependstart_dnsstatus 这几个函数

代理测试

简单测试
所谓的简单测试就是看能不能上网(这里使用 curl 进行测试,你可以用浏览器直观的测试),如果 ss-tproxy 运行在网关上,那么建议在网关及内网中都测试一遍,如果只是在普通主机上使用,只要在这台主机上进行测试就行了。如果测试成功,足以说明 DNS 解析和 TCP 代理正常工作,如果测试失败,请参考后面的 调试方法,进行调试。

油管测速
想要测试 ss/ssr 的速度(这里仅指单线程速度,多线程速度意义不是很大),最简单和直观的方法就是打开 YouTube,选择一个 4k 视频(比如:https://www.youtube.com/watch?v=lM02vNMRRB0),[设置]->[画质]->[4K],然后在视频区域右键选择 [详细统计信息],最终效果如下图所示:
YouTube 4k Test by 树莓派 3B
其中,Connection Speed 即为单线程连接速度,注意单位是 Kbps,换算为 Mbps 需要除以 1024,但是也可以粗略的除以 1000,因为好算。上图是某新加坡 CN2 SSR节点在树莓派 3B 上的速度测试(树莓派做全局代理,在 Windows10 的 Chrome 上测的),连接速度为 66k+,也就是差不多 8m/s 的单线程下载速度(100M 带宽)。然后下图是直接使用 Windows10 的 SSR 客户端测的速度,比在树莓派上的还更快(当然是同一节点相同配置),连接速度为 79k+,也就是差不多 10m/s 的单线程下载速度,在树莓派上损失了差不多 20% 的速度,这一点都不奇怪,毕竟树莓派的性能肯定比 i5 差的多,能有这速度我也心满意足了。
YouTube 4k Test by Windows10

调试方法
开始调试前,请先打开 ss-tproxy.conf 中相应的 log/verbose 选项。这里只说一下简要步骤,不会详细介绍每个细节,因为模式太多了,懒得码字。重在理解,如果不理解,我写再多也没用。我建议你开启多个终端,每个终端都 tail -f 跟踪一个日志文件(为了好分辨每次调试,可以人为的敲几个空行),日志文件的路径在上一节中列出来了,当然还有 tun2socks 模式的 socks5 代理日志以及 v2ray 模式的 v2ray 日志,日志在哪你应该清楚。

调试之前,先弄清楚每个模式中的流量走向以及每个组件的作用(chnonly 模式与 gfwlist 模式原理相同,只不过 gfwlist.txt 列表不同):

无论什么模式,首先要关注的都是 DNS,因为 iptables、iproute 这些东西只认 IP,只有获得了对应的 IP,才有下一步的可能。比如 curl https://www.baidu.com,curl 首先进行的是 DNS 解析,将 www.baidu.com 转换为 IP 地址,然后再向这个 IP 地址发起 TCP 连接。如果 curl、wget 或浏览器访问网页失败,记得先排除 DNS 问题。以 tproxy_chnroute 模式为例,开 3 个窗口,分别 tail -f dnsmasq、chinadns、ss-tunnel 的 log,然后使用 dig 进行测试,先测试 ss-tunnel/60053,再测试 chinadns/65353,最后测试 dnsmasq/53。

  • dig @127.0.0.1 -p60053 www.google.com:测试 ss-tunnel,并观察 ss-tunnel 的日志。正常情况下,dig 是能够解析的,且日志有成功的 INFO。如果没有,那就不用下一步了,先解决 ss-tunnel 的问题吧。
  • dig @127.0.0.1 -p65353 www.baidu.com:测试 chinadns(国内),并观察 chinadns、ss-tunnel 的日志。正常情况下,dig 是能够解析的,从日志中可以看到:filter 来自 127.0.0.1:60053 的响应,pass 来自 114.114.114.114:53 的响应。
  • dig @127.0.0.1 -p65353 www.google.com:测试 chinadns(国外),并观察 chinadns、ss-tunnel 的日志。正常情况下,dig 是能够解析的,从日志中可以看到:filter 来自 114.114.114.114:53 的响应,pass 来自 127.0.0.1:60053 的响应。
  • dig @127.0.0.1 -p53 www.baidu.com:测试 dnsmasq(国内),并观察 dnsmasq、chinadns、ss-tunnel 的日志。正常情况下,除了第一次解析,其它的 DNS 查询时间都是 0ms(DNS 缓存的缘故)。
  • dig @127.0.0.1 -p53 www.google.com:测试 dnsmasq(国外),并观察 dnsmasq、chinadns、ss-tunnel 的日志。正常情况下,除了第一次解析,其它的 DNS 查询时间都是 0ms(DNS 缓存的缘故)。

进行 UDP 的测试
如果是 v2ray/tproxy 模式,那么请在内网主机中测试 UDP,因为本机的 UDP 代理不了;如果是 tun2socks 模式,本机或内网都可以,无所谓。仍以 tproxy_chnroute 模式为例,在某台内网主机中,使用 dig @208.67.222.222 -p443 www.google.com,如果正常解析,则观察 ss-redir 的日志(verbose 模式),应该能看到对应 udp relay 成功的信息。

反馈代理脚本的问题
如果某个环节总是无法成功,或者你认为应该是 ss-tproxy 脚本的问题,请附带对应的 log(详细日志),以及 bash -x /usr/local/bin/ss-tproxy start 的启动调试日志(敏感信息请自行替换),并且说明你的网络环境,如果还是不懂,我强烈建议您先看一遍 提问的智慧,然后提问也不迟。

常见问题

ss-tproxy 开机自启失败
请将 ss-tproxy.conf 中的 v2ray_server、socks5_remote、server_addr 改为 IP 形式,或者将这些域名加到 ss-tproxy 主机的 /etc/hosts 文件。

start 时部分组件 stopped
请先检查 /var/log 下面的日志,看看是不是地址被占用了(具体有哪些端口,可以看前面的端口列表),如果是,请自行解决。分析日志是个好习惯。

切换模式后不能正常代理了
从其它模式切换到 gfwlist 模式时可能出现这个问题,原因还是因为内网主机的 DNS 缓存。在访问被墙网站时,比如 www.google.com,客户机首先会进行 DNS 解析,由于存在 DNS 缓存,这个 DNS 解析请求并不会被 ss-tproxy 主机的 dnsmasq 处理(因为根本没从客户机发出来),所以对应 IP 不会添加到 ipset-gfwlist 列表中,导致客户机发给该 IP 的数据包不会被 ss-tproxy 处理,也就是走直连出去了,GFW 当然不会让它通过了,也就出现了连接被重置等问题。解决方法也很简单,对于 Windows,请先关闭浏览器,然后打开 cmd.exe,执行 ipconfig /flushdns 来清空 DNS 缓存,然后重新打开浏览器,应该正常了;对于 Android,可以先打开飞行模式,然后再关闭飞行模式,或许可以清空 DNS 缓存,实在清不了就重启吧。

有时会无法访问代理服务器
如果你在 ss-tproxy 中使用的是自己的 VPS 的 SS/SSR 服务,那么在除 ss-tproxy 本机外的其他主机上会可能会出现无法访问这台 VPS 的情况(比如 SSH 连不上,但是 ping 没问题),具体表现为连接超时。起初怀疑是 ss-tproxy 主机的 iptables 规则设置不正确,然而使用 TRACE 追踪后却什么都没发现,一切正常;在 VPS 上使用 tcpdump 抓包后,发现一个很奇怪的问题:VPS 在收到来自客户端的 SYN 请求后并没有进行 SYN+ACK 回复,客户端在尝试了几次后就会显示连接超时。于是断定这肯定是 VPS 的问题,谷歌后才知道,原来是因为两个内核参数设置不正确导致的,这两个内核参数是:net.ipv4.tcp_tw_reusenet.ipv4.tcp_tw_recycle,将它们都设为 0(也就是禁用)即可解决此问题。其实这两个内核参数默认都是为 0 的,也就是说,只要你没动过 VPS 的内核参数,那么基本不会出现这种诡异的问题。我是因为盲目的照搬照抄网上所谓的内核参数优化导致的,所以没事还是别优化什么内核参数了,除非你真的知道你在做什么!

关于网关的 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 解析失败,无法正常联网。另外,网关的 DHCP 服务器建议使用 dhcpd。

ss-tproxy 脚本的运行环境
1、ss-tproxy 支持什么 linux 系统?
一般都支持,没有限制,因为没有使用与特定发行版相关的命令、功能(我自己用的是 ArchLinux)。

2、ss-tproxy 只能运行在网关上吗?
显然不是,你可以在一台普通的 linux 主机上运行(此时你不用管 ss-tproxy.conf 中的内网网段配置)。

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 部署在一台普通的内网主机上(该主机的网络配置不变),然后将其他内网主机的网关和 DNS 指向这台部署了 ss-tproxy 的主机,进行代理。方案是可行的,我在 VMware 环境中测试通过。注意,这个“代理网关”可以是一台真机,也可以是一台虚拟机(桥接模式),比如在 Windows 系统中运行一个 VMware 或 VirtualBox 的 Linux 虚拟机,在这个虚拟机上跑 ss-tproxy 当然也是可以的。

切换模式、切换节点不方便?
这确实是个不人性化的地方,切换节点需要修改 ss-tproxy.conf,切换模式需要修改 ss-tproxy.conf,有没有更加简便一些的方式?抱歉,目前没有。不过因为 ss-tproxy.conf 是一个 shell 脚本,所以我们可以在这里面做些文章。如果你有很多个节点(付费帐号一般都是),可以这样做: