Total Pageviews

Tuesday, 27 August 2019

linux桌面系统上的全局代理程序:ss-tproxy

Linux 透明代理

什么是正向代理?

代理软件通常分为客户端(client)和服务端(server),server 运行在境外服务器(通常为 Linux 服务器),client 运行在本地主机(如 Windows、Linux、Android、iOS),client 与 server 之间通常使用 tcp 或 udp 协议进行数据通信。大多数 client 被实现为一个 http、socks5 代理服务器,一个软件如果想通过 client 进行科学上网,需要使用 http、socks5 协议与 client 进行数据交互,这是绝大多数人的使用方式。这种代理方式,我们称之为 正向代理。所谓正向代理就是,一个软件如果想要使用 client 的代理服务,需要经过特定的设置,否则不会经过 client 的代理。

什么是透明代理?

在正向代理中,一个软件如果想走 client 的代理服务,我们必须显式配置该软件,对该软件来说,有没有走代理是很明确的,大家都“心知肚明”。而透明代理则与正向代理相反,当我们设置好合适的防火墙规则(仅以 Linux 的 iptables 为例),我们将不再需要显式配置这些软件来让其经过代理或者不经过代理(直连),因为这些软件发出的流量会自动被 iptables 规则所处理,那些我们认为需要代理的流量,会被通过合适的方法发送到 client 进程,而那些我们不需要代理的流量,则直接放行(直连)。这个过程对于我们使用的软件来说是完全透明的,软件自身对其一无所知。这就叫做 透明代理。注意,所谓透明是对我们使用的软件透明,而非对 client、server 或目标网站透明,理解这一点非常重要。

透明代理如何工作?

在正向代理中,期望使用代理的软件会通过 http、socks5 协议与 client 进程进行交互,以此完成代理操作。而在透明代理中,我们的软件发出的流量是完全正常的流量,并没有像正向代理那样,使用 http、socks5 等专用协议,这些流量经过 iptables 规则的处理后,会被通过“合适的方法”发送给 client 进程(当然是指那些我们认为需要走代理的流量)。注意,此时 client 进程接收到不再是 http、socks5 协议数据,而是经过 iptables 处理的“透明代理数据”,“透明代理数据”从本质上来说与正常数据没有区别,只是多了一些“元数据”在里面,使得 client 进程可以通过 netfilter 或操作系统提供的 API 接口来获取这些元数据(元数据其实就是原始目的地址和原始目的端口)。那么这个“合适的方法”是什么?目前来说有两种:
  • REDIRECT:只支持 TCP 协议的透明代理。
  • TPROXY:支持 TCP 和 UDP 协议的透明代理。
因此,对于 TCP 透明代理,有两种实现方式,一种是 REDIRECT,一种是 TPROXY;而对于 UDP 透明代理,只能通过 TPROXY 方式来实现。为方便叙述,本文以 纯 TPROXY 模式 指代 TCP 和 UDP 都使用 TPROXY 来实现,以 REDIRECT + TPROXY 模式 指代 TCP 使用 REDIRECT 实现,而 UDP 使用 TPROXY 来实现,有时候简称 REDIRECT 模式,它们都是一个意思。

此脚本的作用及由来

通过上面的介绍,其实可以知道,在构建透明代理的过程中,需要的仅仅是 iptables、iproute2 以及 ss/ssr/v2ray 等支持透明代理的软件,那 ss-tproxy 脚本的作用是什么呢?如果你尝试搭建过透明代理,那么你就会体会到,这一过程其实并不容易,你需要设置许多繁琐的 iptables 规则,还要应对国内复杂的 DNS 环境,另外还要考虑 UDP 透明代理的支持,此外你通常还希望这个透明代理能实现分流操作,而不是一股脑的全走代理。于是就有了 ss-tproxy 脚本,该脚本的目的就是辅助大家快速地搭建一个透明代理环境,该透明代理支持 gfwlist、chnroute 等常见分流模式,以及一个无污染的 DNS 解析服务;除此之外,ss-tproxy 脚本不做任何其它事情;因此你仍然需要 iptables、iproute2 以及 ss/ssr/v2ray 等支持透明代理的软件,因为透明代理的底层服务是由它们共同运作的,理解这一点非常重要。
为什么叫做 ss-tproxy?因为该脚本最早的时候只支持 ss 的透明代理,当然现在它并不局限于特定的代理软件。
另外还有一点需要注意,透明代理使用的 client 与正向代理使用的 client 通常是不同的,因为正向代理的 client 是 http、socks5 服务器,而透明代理的 client 则是透明代理服务器,它们之间有本质上的区别。对于 ss,你需要使用 ss-libev 版本(ss-redir),ssr 则需要使用 ssr-libev 版本(ssr-redir),而对于 v2ray,配置好 dokodemo-door 入站协议即可。再次强调,透明代理只是 client 不同,并不关心你的 server 是什么版本,因此你的 vps 上,可以运行所有与之兼容的 server 版本,以 ss/ssr 为例,你可以使用 python 版的 ss、ssr,也可以使用 golang 版的 ss、ssr 等等,只要它们之间可以兼容。
ss-tproxy 可以运行在 Linux 软路由/网关、Linux 物理机、Linux 虚拟机等环境中,可以透明代理 ss-tproxy 主机本身以及所有网关指向 ss-tproxy 主机的其它主机的 TCP、UDP 流量。也就是说,你可以在任意一台 Linux 主机上部署 ss-tproxy 脚本,然后同一局域网内的其它主机可以随时将其网关及 DNS 指向 ss-tproxy 主机,这样它们的 TCP 和 UDP 流量就会自动走代理了。
ss-tproxy v4.0 简介
  • 去除不常用的 global 分流模式
  • 支持 IPv4、IPv6 双协议栈的透明代理(可配置)
  • 使用 chinadns-ng 替代原版 chinadns,修复若干问题
  • 完美兼容"端口映射",只代理"主动出站"的流量,规则更加细致化
  • 支持配置要代理的黑名单端口,这样可以比较好的处理 BT/PT 流量
  • 支持自定义 dnsmasq/chinadns 端口,支持加载外部 dnsmasq 配置
  • ss-tproxy stop 后,支持重定向内网主机发出的 DNS 到本地直连 DNS
  • 支持网络可用性检查,无需利用其它的 hook 来避免脚本自启失败问题
  • 脚本逻辑优化及结构调整,尽量提高脚本的可移植性,去除非核心依赖
v4.0 只剩下 gfwlistchnroutechnlist 3 种分流模式,相关介绍:
  • gfwlist 分流模式:gfwlist.txt 中的域名走代理,其余走直连,即黑名单模式。
  • chnroute 分流模式:除了国内地址、保留地址之外,其余均走代理,即白名单模式。
  • chnlist 分流模式:本质还是 gfwlist 模式,只是域名列表为国内域名,即回国模式。
有人可能会有疑问,为什么使用 ss-tproxy 后,虽然可以访问谷歌,但依旧无法 ping 谷歌,这是为什么呢?这是因为 ping 走的是 ICMP 协议,没有哪个代理软件会去支持 ICMP 的代理,因为 ICMP 的代理并没有任何实际意义。

相关依赖

核心依赖:
  • iptables:核心部件,用于配置 IPv4 的透明代理规则。
  • ip6tables:核心部件,用于配置 IPv6 的透明代理规则。
  • xt_TPROXY:TPROXY 内核模块,这是必须的,无论什么模式。
  • ip:通常位于 iproute2 软件包,用于配置策略路由(TPROXY)。
  • ipset:ipset 用于存储 gfwlist 的黑名单 IP,以及 chnroute 的白名单 IP。
  • dnsmasq:基础 DNS 服务,对于 gfwlist 模式,该 dnsmasq 需要支持 --ipset 选项。
  • chinadns-ng:chnroute 模式的 DNS 服务,注意是 chinadns-ng,而不是原版 chinadns。
如果某些模式你基本不用,那么对应的依赖就不用管。比如,你不打算使用 IPv6 透明代理,则无需关心 ip6tables,又比如你不打算使用 chnroute 模式,也无需关心 chinadns-ng,安装依赖之前先检查当前系统是否已有对应依赖。
可选依赖:
  • curl:用于更新 chnlist、gfwlist、chnroute 分流模式的相关列表。
  • base64:用于更新 gfwlist 的域名列表,gfwlist.txt 是 base64 格式编码的。
  • perl:用于更新 gfwlist 的域名列表,gfwlist.txt 是 adblock plus 规则,要进行转换。

安装脚本

git clone https://github.com/zfl9/ss-tproxy
cd ss-tproxy
chmod +x ss-tproxy
cp -af ss-tproxy /usr/local/bin
mkdir -p /etc/ss-tproxy
cp -af ss-tproxy.conf gfwlist* chnroute* /etc/ss-tproxy
cp -af ss-tproxy.service /etc/systemd/system # 可选,安装 service 文件

卸载脚本

ss-tproxy stop
ss-tproxy flush-postrule
ss-tproxy delete-gfwlist
rm -fr /usr/local/bin/ss-tproxy /etc/ss-tproxy # 删除脚本及配置文件
升级脚本前请先卸载脚本,如果有残留规则无法清除,请务必重启系统。

文件列表

  • ss-tproxy:shell 脚本,欢迎各位大佬一起来改进此脚本。
  • ss-tproxy.conf:配置文件,本质是 shell 脚本,修改需重启生效。
  • ss-tproxy.service:systemd 服务文件,用于 ss-tproxy 的开机自启。
  • chnroute.set:IPv4 国内地址及保留地址的 ipset 文件,不要手动修改。
  • chnroute6.set:IPv6 国内地址及保留地址的 ipset 文件,不要手动修改。
  • gfwlist.txt:存储 gfwlist、chnlist 分流模式的黑名单域名,不要手动修改。
  • gfwlist.ext:存储 gfwlist、chnlist 分流模式的扩展黑名单,可配置,重启生效。
ss-tproxy 只是一个 shell 脚本,并不是常驻后台的服务,因此所有的修改都需要 restart 来生效。

配置说明

  • 注释:井号开头的行为注释行,配置文件本质上是一个 shell 脚本,对于同名变量或函数,后定义的会覆盖先定义的。
  • mode:分流模式,默认为 chnroute 模式,可根据需要修改为 gfwlist 模式;需要说明的是,如果想使用 chnlist 回国模式,那么 mode 依旧为 gfwlist,gfwlist 模式与 chnlist 模式共享 gfwlist.txtgfwlist.ext 文件,因此使用 chnlist 模式前,需要先执行 ss-tproxy update-chnlist 将 gfwlist.txt 替换为国内域名,同时手动编辑 gfwlist.ext 扩展黑名单,将其中的 Telegram IPv4/IPv6 地址段注释,此外你还需要修改 dns_direct/dns_direct6 为本地直连 DNS(如 Google 公共 DNS),然后修改 dns_remote/dns_remote6 为大陆 DNS(如 114 公共 DNS,走国内代理)。
  • ipv4/ipv6:启用 IPv4/IPv6 透明代理,你需要确保本机代理进程能正确处理 IPv4/IPv6 相关数据包,脚本不检查它。
  • tproxy:true 为纯 TPROXY,false 为 REDIRECT/TPROXY 混合,ss/ssr 只能使用 false,v2ray 经配置后可使用 true。
  • proxy_svraddr4/proxy_svraddr6:填写 VPS 服务器的外网 IPv4/IPv6 地址,IP 或域名都可以,填域名要注意,这个域名最好不要有多个 IP 地址与之对应,因为脚本内部只会获取其中某个 IP,这极有可能与本机代理进程解析出来的 IP 不一致,这可能会导致 iptables 规则死循环,应尽量避免这种情况,比如你可以将该域名与其中某个 IP 的映射关系写到 ss-tproxy 主机的 /etc/hosts 文件中,这样解析结果就是可预期的。允许填写多个 VPS 地址,用空格隔开,填写多个地址的目的是方便切换代理,比如我现在有两个 VPS,A、B,假设你先使用 A,因为某些因素,导致 A 的网络性能低下,那么你可能需要切换到 B,如果只填写了 A 的地址,就需要去修改 ss-tproxy.conf,将地址改为 B,修改启动与关闭命令,最后还得重启 ss-tproxy 脚本,很麻烦,更麻烦的是,如果现在 A 的网络又好了,那么你可能又想切换回 A,那么你又得重复上述步骤。但现在,你不需要这么做,你完全可以在 proxy_svraddr 中填写 A 和 B 的地址,假设你默认使用 A(proxy_startcmd 启动 A 代理进程),那么启动 ss-tproxy 后,使用的就是 A,此后如果想切换为 B,仅需停止 A 代理进程,再启动 B 代理进程(切回来的步骤则相反),该过程无需操作 ss-tproxy;这种配置下应注意 proxy_stopcmd,stopcmd 最好能停止 A 和 B 进程,不然切换进程后执行 ss-tproxy stop 可能不会正确停止相关的代理进程。另外,你只需填写实际会使用到的 VPS 地址,比如本机代理进程仅使用 IPv4 访问 VPS,则 proxy_svraddr6 可能是空的,反之,如果本机代理进程仅使用 IPv6 访问 VPS,则 proxy_svraddr4 可能是空的;这两个数组是否为空与 ipv4ipv6 选项没有必然的联系,比如你可以启用 IPv4 和 IPv6 透明代理,但是本机代理进程仅使用 IPv4 访问 VPS,这是完全可以的,但不允许 proxy_svraddr4 与 proxy_svraddr6 都为空,你至少需要填写一个地址。
  • proxy_svrport:填写 VPS 上代理服务器的外部监听端口,格式同 ipts_proxy_dst_port,填写不正确会导致 iptables 规则死循环。如果是 v2ray 动态端口,如端口号 1000 到 2000 都是代理监听端口,则填 1000:2000(含边界)。
  • proxy_tcpport/proxy_udpport:本机代理进程的透明代理监听端口,前者为 TCP 端口,后者为 UDP 端口,通常情况下它们是相同的,根据实际情况修改。需要注意的是,ss-tproxy v3.0 之后都要求代理软件支持 UDP,否则 DNS 是无法正常解析的,请务必检查 UDP 代理的连通情况,对于 ss/ssr,它们的 UDP 代理数据是通过 UDP 协议进行传递的,某些 ISP 可能会对 UDP 恶意丢包,v2ray 某些协议则为 UDP over TCP,对于这种情况则无需担心 UDP 丢包问题。
  • proxy_startcmd/proxy_stopcmd:前者是启动本机代理进程的 shell 命令,后者是关闭本机代理进程的 shell 命令。这些命令应该能快速执行完毕,否则会导致透明代理长期处于半启动或半关闭状态。具体的 startcmd、stopcmd 示例见后。
  • dnsmasq_bind_port:dnsmasq 监听端口,默认 53,如果端口已被占用则修改为其它未占用的端口,如 60053
  • dnsmasq_conf_dir/dnsmasq_conf_file:dnsmasq 外部配置文件/目录,被作为 conf-dirconf-file 选项值。
  • ipts_set_snat:是否设置 IPv4 的 MASQUERADE 规则,通常保持为 false 即可。有两种情况需要将其设置为 true:第一种,ss-tproxy 部署在出口路由位置且确实需要 MASQUERADE 规则(即该主机至少两张网卡,一张连接内网,一张连接公网,要进行源地址转换);第二种,在设置为 false 的情况下,代理不正常,那么也需要将其改为 true,曾经遇到过这种情况,可能与路由器的配置有关。注意,MASQUERADE 规则在 ss-tproxy stop 仍然是有效的,如果你想清空这些残留规则,可以执行 ss-tproxy flush-postrule 命令。
  • ipts_set_snat6:是否设置 IPv6 的 MASQUERADE 规则,默认为 true,通常你必须将其设置为 true,因为 IPv6 的透明代理需要利用 ULA 内网地址,如果设置为 false,那么其它内网主机将无法访问 IPv6 网络,因为 ULA 地址是不可以在公网上被路由的,因此你必须进行 SNAT 源地址转换。除非你使用 GUA 地址进行透明代理,但这样会让配置变得复杂且不易使用,因为 GUA 地址是动态变化的,运营商不会给你分配一个固定的 GUA 地址段。ss-tproxy stop 之后,这些规则仍然有效,如果你想清空这些残留规则,可以执行 ss-tproxy flush-postrule 命令。
  • ipts_intranet/ipts_intranet6:填写需要代理的内网网段,可以填写多个,空格隔开,当然也可以一个都不填,这表示只代理 ss-tproxy 主机自身的流量。对于 IPv6 透明代理,此选项有非常多值得讨论的地方,具体请看后文。
  • ipts_reddns_onstop:当 ss-tproxy stop 之后,是否使用 iptables 规则将内网主机发往 ss-tproxy 主机的 DNS 请求重定向至本地直连 DNS(即 dns_direct/dns_direct6),为什么要这么做呢?因为其它内网主机的 DNS 是指向 ss-tproxy 主机的,但是现在我们已经关闭了 ss-tproxy(dnsmasq 进程关闭了),所以这些内网主机会因为无法解析 DNS 而无法正常上网,而设置此选项后,这些 DNS 请求会被重定向给 114.114.114.114 等国内直连 DNS,这样它们就又可以正常上网了,在 ss-tproxy start 前,这些规则会自动删除,如果你需要手动删除这些规则,可以执行 ss-tproxy flush-postrule 命令。该选项的默认值为 true,如果 ss-tproxy 主机上有正常运行的 DNS 服务,那么这个选项应该设置为 false。
  • ipts_proxy_dst_port:告诉 ss-tproxy,黑名单地址的哪些目的端口需要走代理。所谓黑名单地址,对于 gfwlist/chnlist 模式来说,就是 gfwlist.txt/gfwlist.ext 里面的域名、IP、网段,对于 chnroute 模式来说,就是 chnroute/chnroute6 之外的地址(即国外地址),当然黑名单地址还包括 proxy_svraddr4/proxy_svraddr6 中所指定的 VPS 地址。该选项的默认值为 1:65535,因此只要我们访问黑名单地址,就会走代理,因为所有端口号都在其中。如果觉得端口范围太大,那么你可以修改这个选项的值,比如设置为 1:1023,8080,在这种配置下,只有当我们访问黑名单地址的 1 到 1023 和 8080 这些目的端口时才会走代理,访问黑名单地址的其它目的端口是不会走代理的,因此可以利用此选项来放行 BT、PT 流量,因为这些流量的目的端口通常都在 1024 以上。修改此选项需要足够小心,配置不当会导致某些常用软件无法正常走代理,因为它们使用的端口号可能不在你所指定的范围之内,因此指定为 1:65535 可能是最保险的一种做法。
  • opts_ss_netstat:告诉 ss-tproxy,使用 ss 还是 netstat 命令进行端口检测,目前检测本机代理进程是否正常运行的方式是直接检测其是否已监听对应的端口,虽然这种方式有时候并不准确,但是我现在貌似并没有其它更好的便携方法来做这个事情。选项的默认值为 auto,表示自动模式,所谓自动模式就是,如果当前系统有 ss 命令则使用 ss 命令进行检测,如果没有 ss 命令但是有 netstat 命令则使用 netstat 命令进行检测,而 ss 选项值则是明确告诉 ss-tproxy 使用 ss进行检测,同理,netstat 选项也是明确告诉 ss-tproxy 使用 netstat 进行端口检测。通常情况下保持 auto 即可。
  • opts_overwrite_resolv:如果设置为 true,则表示直接使用 I/O 重定向方式修改 /etc/resolv.conf 文件,这个操作是不可逆的,但是可移植性好;如果设置为 false,则表示使用 mount -o bind 魔法来暂时性修改 /etc/resolv.conf文件,当 ss-tproxy stop 之后,/etc/resolv.conf 会恢复为原来的文件,也就是说这个修改操作是可逆的,但是这个方式可能某些系统会不支持,默认为 false,如果遇到问题请修改为 true
  • opts_ip_for_check_net:指定一个允许 Ping 的 IP 地址(IPv4 或 IPv6 都行),用于检查外部网络的连通情况,默认为 114.114.114.114,注意这个 IP 地址应该为公网 IP,如果你填一个私有 IP,即使检测成功,也不能保证外网是可访问的,因为这仅代表我可以访问这个内网。根据实际网络环境进行更改,一般改为延迟较低且较稳定的一个 IP。
IPv6 透明代理的实施方式
如果你现在的网络已经支持 IPv6,那么你从 ISP 分配到的应该是 GUA 公网地址,但是 GUA 地址是随时会变化的(DHCP),这对于透明代理来说可是个糟糕的事情,因为 iptables 截获其它主机发出的数据包的原理是匹配它们的源 IP,也就是填写在 ipts_intranet6 选项中的 IP 段,如果你在这里填写 GUA 地址段,那么当 GUA 地址变更后,你需要及时的修改 ipts_intranet6 为最新的 GUA 地址段,否则 ss-tproxy 就将无法拿到其它设备发出的流量。显然,这样的体验非常糟糕。
那怎么办呢?一个可行的方案是,给我们的设备分配一个固定的 ULA 地址段,ULA 地址其实就相当于 IPv4 的 192.168.1.0/24 私有地址段,私有地址段只能在内部网络中使用,在公网上不允许被路由。但是这没关系,我们配置 ULA 地址的目的是为了让我们的设备有固定的 IP 段,而不会像 GUA 地址那样随时都发生变更。分配 ULA 地址后,我们的设备就有了两个 IPv6 地址,一个是 GUA 公网地址,一个是 ULA 私有地址。
ss-tproxy 的默认网关应保持不变,还是原有的 GUA 默认网关,以确保 ss-tproxy 主机能够正常访问 IPv6;而其它主机的默认网关则应修改为 ss-tproxy 主机的 ULA 地址,而不再是 DHCP 分配的 GUA 默认网关,这样它们发出的流量才会经过 ss-tproxy。然后我们在 ipts_intranet6 中填写此 ULA 网段,同时启用 ipts_set_snat6 选项,做源地址转换,因为 ULA 地址需要转换为 GUA 地址才能被路由。这其实和我们现在的 IPv4 NAT 网络差不多,不同的是,这些设备具有一个 IPv6 公网地址,因此我们仍然可以从外部 IPv6 网络访问这些具有 GUA 地址的设备。
但在实际测试中发现一个棘手的问题,我们的设备具有两个 IPv6 地址,当设备准备发送一个 IP 包时,会遇到源地址选择问题,比如它可能会选择 GUA 地址作为该 IP 包的源 IP,也可能会选择 ULA 地址作为该 IP 包的源 IP,如果它选择 ULA 地址作为源 IP,那没什么问题,符合我们的预期,但如果它选择 GUA 地址作为源 IP,就有问题了,这种情况将导致 ss-tproxy 主机的 iptables 规则无法拿到这些数据包,因为它们的源 IP 不再是 ULA 私有地址,而是 GUA 公网地址,这些数据包实际上会直接转发到国内网络,并没有走代理。
要避免这个问题目前有三种方案:
  1. 禁用除 ss-tproxy 主机外的其它主机的 GUA 地址,这样它们始终会使用 ULA 地址作为源 IP,但这会让 IPv6 变得毫无优势,因为这种配置下,完全等于我们之前的 IPv4 NAT 网络,没有任何本质区别。
  2. 配置除 ss-tproxy 主机外的其它主机的路由条目,强制使用 ULA 地址作为源 IP,而不是 GUA 地址,比如可以使用 ip route 的 src 选项,但是对于其它操作系统,尚不清楚如何干预源地址的选择。
  3. 另外还有一种方式,即在要走代理的设备上都分别部署 ss-tproxy 脚本,这样我们甚至不需要关心什么 GUA、ULA,因为我们只代理本机的流量,无需关心其它主机过来的流量,但这实际上并不太现实。
proxy_startcmdproxy_stopcmd
先说 ss-redir,首先配置好 ss-redir 的配置文件,如:
{
    "server": "服务器地址",
    "server_port": 服务器端口,
    "local_address": "本地监听地址",
    "local_port": 本地监听端口,
    "method": "加密方式",
    "password": "用户密码",
    "no_delay": true,
    "fast_open": true,
    "reuse_port": true
}
服务器地址、服务器端口、本地监听端口应与 ss-tproxy.conf 中填写的一致,如果仅代理 ss-tproxy 主机自身的流量,本地监听地址可以为 127.0.0.1::1,否则必须为 0.0.0.0::。然后 proxy_startcmdproxy_stopcmd 可以这么写:
#proxy_startcmd='(ss-redir -c /etc/ss.json -u -v 
>/var/log/ss-redir.log &)' # -v 表示记录详细日志 proxy_startcmd='(ss-redir -c /etc/ss.json -u >/var/log/ss-redir.log &)' # 这里就不记录详细日志了 proxy_stopcmd='kill -9 $(pidof ss-redir)'
ssr-redir 也差不多,配置就不贴出来了,网上一大堆,注意事项同上,proxy_startcmdproxy_stopcmd 例子:
#proxy_startcmd='(ssr-redir -c /etc/ssr.json -u -v 
>/var/log/ssr-redir.log &)' proxy_startcmd='(ssr-redir -c /etc/ssr.json -u >/var/log/ssr-redir.log &)' proxy_stopcmd='kill -9 $(pidof ssr-redir)'
最后说下 v2ray,只关心本机代理进程的配置,v2ray 的透明代理配置比较简单,只需要在原有客户端配置的基础上,加上一个 dokodemo-door 入站协议即可。由于 v2ray 配置复杂,在报告透明代理有问题之前,请务必检查你的配置是否有问题,这里不想解答任何 v2ray 配置问题,原则上不建议在 v2ray 上配置任何分流或路由规则,脚本会为你做这些事,如果你硬要这么做,那么出问题请自行解决,这里不提供相关的指导。下面是一个简单的配置示例:
{
  "log": {
    "access": "/var/log/v2ray/access.log",
    "error": "/var/log/v2ray/error.log",
    "loglevel": "info" // 调试时请改为 debug
  },

  "inbounds": [
    {
      "protocol": "dokodemo-door",
      "listen": "0.0.0.0", // 如果仅代理本机,可填环回地址
      "port": 60080, // 本地监听端口必须与配置文件中的一致
      "settings": {
        "network": "tcp,udp", // 注意这里是 tcp + udp
        "followRedirect": true
      },
      "streamSettings": {
        "sockopt": {
          //"tproxy": "tproxy" // tproxy + tproxy 模式
          "tproxy": "redirect" // redirect + tproxy 模式
        }
      }
    }
  ],

  "outbounds": [
    {
      "protocol": "shadowsocks",
      "settings": {
        "servers": [
          {
            "address": "node.proxy.net", // 服务器地址
            "port": 12345,               // 服务器端口
            "method": "aes-128-gcm",     // 加密方式
            "password": "password"       // 用户密码
          }
        ]
      }
    }
  ]
}
v2ray 的 proxy_startcmdproxy_stopcmd 例子,假设使用 systemctl 进行启动与停止,则:
proxy_startcmd='systemctl start v2ray'
proxy_stopcmd='systemctl stop v2ray'
钩子函数
ss-tproxy 脚本支持 4 个钩子函数,分别是 pre_start(启动前执行)、post_start(启动后执行)、pre_stop(停止前执行)、post_stop(停止后执行)。举个例子,在不修改 ss-tproxy 脚本的前提下,设置一些额外的 iptables 规则,假设我需要在 ss-tproxy 启动后添加某些规则,然后在 ss-tproxy 停止后再删除这些规则,则修改 ss-tproxy.conf,添加以下内容:
post_start() {
    iptables -A ...
    iptables -A ...
    iptables -A ...
}

post_stop() {
    iptables -D ...
    iptables -D ...
    iptables -D ...
}
需要注意的是,shell 中的函数是不允许重复定义的,虽然这不会有任何报错,但是实际只有最后一个函数生效。另外对于需要添加 iptables 规则的情况,可以考虑将 iptables 规则添加到 ss-tproxy 的自定义链上,这些自定义链在 ss-tproxy 停止后会自动删除,因此你只需要关心 post_start() 钩子函数的内容;目前有这几个自定义链:
$ipts -t mangle -N SSTP_PREROUTING
$ipts -t mangle -N SSTP_OUTPUT
$ipts -t nat    -N SSTP_PREROUTING
$ipts -t nat    -N SSTP_OUTPUT
$ipts -t nat    -N SSTP_POSTROUTING
它们分别挂接到去掉 SSTP_ 前缀的同名预定义链上,如下:
$ipts -t mangle -A PREROUTING  -j SSTP_PREROUTING
$ipts -t mangle -A OUTPUT      -j SSTP_OUTPUT
$ipts -t nat    -A PREROUTING  -j SSTP_PREROUTING
$ipts -t nat    -A OUTPUT      -j SSTP_OUTPUT
$ipts -t nat    -A POSTROUTING -j SSTP_POSTROUTING
脚本开机自启
比较简单,对于 SysVinit 发行版,直接在 /etc/rc.d/rc.local 开机脚本中加上 ss-tproxy 的启动命令即可:
/usr/local/bin/ss-tproxy start
对于 Systemd 发行版,将 ss-tproxy.service 服务文件放到 /etc/systemd/system/ss-tproxy.service,然后执行:
systemctl daemon-reload
systemctl enable ss-tproxy
如果使用 Systemd 管理 ss-tproxy,应避免使用 ss-tproxy start|stop|restart 这几个命令,当然除了这几个命令外,其它命令都是可以执行的,比如 ss-tproxy statusss-tproxy update-gfwlist,为什么呢?因为 systemctl 启动一个脚本之后,systemctl 会在内部保存一个状态,即脚本已经 running,然后只有当你下次使用 systemctl 停止该脚本的时候,systemctl 内部才会将这个状态改为 stopped。所以当你执行 systemctl start ss-tproxy 后,这个服务的状态就是 running,如果你执行 ss-tproxy stop 来停止脚本,那么这个服务状态是不会变的,依旧是 running,但实际上它已经 stopped 了,而当你执行 systemctl start ss-tproxy 来启动脚本时,systemctl 并不会在内部执行 ss-tproxy start,因为这个服务的状态是 running,说明已经启动了,就不会再次启动了。这样一来就完全混乱了,你以为执行完毕后 ss-tproxy 就启动了,然而实际上,执行 ss-tproxy status 看下还是 stopped 的。为避免这种情况,请务必使用 systemctl start|stop|restart ss-tproxy,而不是 ss-tproxy start|stop|restart,换句话说,不要交叉使用这两套命令。
脚本命令行选项
  • ss-tproxy help:查看帮助信息
  • ss-tproxy version:查看版本号
  • ss-tproxy start:启动透明代理
  • ss-tproxy stop:关闭透明代理
  • ss-tproxy restart:重启透明代理
  • ss-tproxy status:查看代理状态
  • ss-tproxy show-iptables:查看当前的 iptables 规则
  • ss-tproxy flush-postrule:清空遗留的 iptables 规则
  • ss-tproxy flush-dnscache:清空 dnsmasq 的查询缓存
  • ss-tproxy delete-gfwlist:删除 gfwlist 黑名单 ipset
  • ss-tproxy update-chnlist:更新 chnlist(restart 生效)
  • ss-tproxy update-gfwlist:更新 gfwlist(restart 生效)
  • ss-tproxy update-chnroute:更新 chnroute(restart 生效)
  • 在任意位置指定 -x 选项可启用调试,如 ss-tproxy start -x
ss-tproxy delete-gfwlist 的作用:在 gfwlist/chnlist 模式下,ss-tproxy restartss-tproxy stop; ss-tproxy start 并不会移除 gfwlist 这个 ipset,如果你进行了 ss-tproxy update-gfwlistss-tproxy update-chnlist 操作,或者修改了 /etc/ss-tproxy/gfwlist.ext 文件,建议在 start 前执行一下此步骤,防止因为之前遗留的 gfwlist 列表导致奇怪的问题。注意,如果执行了 ss-tproxy delete-gfwlist 那么你可能还需要清空内网主机的 dns 缓存,并重启浏览器等被代理的应用。
如果你需要修改 ss-tproxy.conf 的特殊配置项,请先执行 ss-tproxy stop,然后再修改配置文件,最后再执行 ss-tproxy start 来生效,而不是改好配置后执行 ss-tproxy restart,这会出现不可预估的错误,需要遵循这个约定的配置项有:
  • ipv4
  • ipv6
  • proxy_stopcmd
  • ipts_rt_tab
  • ipts_rt_mark
  • opts_overwrite_resolv
  • file_dnsserver_pid
对于其它配置项,都可以在改完配置后,执行 ss-tproxy restart 命令来生效,无需遵循上述约定。
钩子函数小技巧
1、某些系统的 TPROXY 模块可能需要手动加载,对于这种情况,可以利用 pre_start() 钩子来加载它:
pre_start() {
    # 加载 TPROXY 模块
    modprobe xt_TPROXY
}
2、chnroute 模式下,想放行某些不在 chnroute 中的 IP,可以利用 post_start() 将它们加到 ipset 中:
post_start() {
    # 定义要放行的 IPv4 地址
    local chnroute_append_list=(11.22.33.44 44.33.22.11)
    for ipaddr in "${chnroute_append_list[@]}"; do
        ipset add chnroute $ipaddr &>/dev/null
    done

    # 定义要放行的 IPv6 地址
    local chnroute_append_list6=(2400:da00::6666 2001:dc7:1000::1)
    for ipaddr in "${chnroute_append_list6[@]}"; do
        ipset add chnroute6 $ipaddr &>/dev/null
    done
}
如果还想放行某些域名,可以利用 dnsmasq_conf_file/dnsmasq_conf_dir 选项,首先创建一个 dnsmasq 配置文件,比如在 /etc/ss-tproxy 目录下创建 chnroute_ignore.conf,假设想放行 github.com 以及 github.io 两个域名,则配置内容如下:
server = /github.com/114.114.114.114
server = /github.io/114.114.114.114
ipset = /github.com/chnroute,chnroute6
ipset = /github.io/chnroute,chnroute6
然后在 ss-tproxy.conf 的 dnsmasq_conf_file 数组中写上该配置文件的绝对路径,如 dnsmasq_conf_file=(/etc/ss-tproxy/chnroute_ignore.conf),注意这只适合 chnroute 模式,如果想让配置更加智能些,即只在 chnroute 模式下加载该 dnsmasq 配置,可以将原有的 dnsmasq_conf_file 注释掉,然后在它下面写上一个简单的判断语句即可:
if [ "$mode" = 'chnroute' ]; then
    dnsmasq_conf_file=(/etc/ss-tproxy/chnroute_ignore.conf)
else
    dnsmasq_conf_file=()
fi
3、不想让某些内网主机走 ss-tproxy 的透明代理,即使它们将网关设为 ss-tproxy 主机,那么可以这么做:
post_start() {
    # 定义要放行的 IPv4 地址
    local intranet_ignore_list=(192.168.1.100 192.168.1.200)
    for ipaddr in "${intranet_ignore_list[@]}"; do
        iptables -t mangle -I SSTP_PREROUTING -s $ipaddr -j RETURN
        iptables -t nat    -I SSTP_PREROUTING -s $ipaddr -j RETURN
    done

    # 定义要放行的 IPv6 地址
    local intranet_ignore_list6=(fd00:abcd::1111 fd00:abcd::2222)
    for ipaddr in "${intranet_ignore_list6[@]}"; do
        ip6tables -t mangle -I SSTP_PREROUTING -s $ipaddr -j RETURN
        ip6tables -t nat    -I SSTP_PREROUTING -s $ipaddr -j RETURN
    done
}
切换代理小技巧
如果觉得切换代理要修改 ss-tproxy.conf 很麻烦,可以这么做:将 proxy_startcmd 和 proxy_stopcmd 改为空调用,即 proxy_startcmd='true'proxy_stopcmd='true',然后配置好 proxy_svraddr4/6,将所有可能会用到的服务器地址都放进去,最后执行 ss-tproxy start 启动,因为我们没有填写任何代理进程的启动和停止命令,所以会显示代理进程未运行,没关系,现在我们要做的就是启动对应的代理进程,假设为 ss-redir 且使用 systemd 管理,则执行 systemctl start ss-redir,现在你再执行 ss-tproxy status 就会看到对应的状态正常了,当然代理也是正常的,如果需要换为 v2ray,假设也是使用 systemd 管理,那么只需要先关闭 ss-redir,然后再启动 v2ray 就行了,即 systemctl stop ss-redirsystemctl start v2ray,这相当于启动了一个代理框架,切换代理无需操作 ss-tproxy,直接切换进程即可。
FAQ
如果透明代理未正常工作,请先自行按照如下顺序进行一个简单的排查:
  1. 检查 ss-tproxy.conf 以及代理软件的配置是否正确,此文详细说明了许多配置细节,它们并不是废话,请务必仔细阅读此文。如果确认配置无误,那么请务必开启代理进程的详细日志(debug/verbose logging),以及 dnsmasq、chinadns-ng 的详细日志(ss-tproxy.conf),日志是调试的基础。
  2. 如果 ss-tproxy 在配置正确的情况下出现运行时报错,请在执行 ss-tproxy 相关命令时带上 -x 调试选项,以查看是哪条命令报的错。出现这种错误通常是脚本自身的问题,可直接通过 issue 报告此错误,但是你需要提供尽可能详细的信息,别一句话就应付了我,这等同于应付了你自己。
  3. 如果 ss-tproxy status 显示的状态不正常,那么通常都是配置问题,pxy/tcp 显示 stopped 表示代理进程的 TCP 端口未监听,pxy/udp 显示 stopped 表示代理进程的 UDP 端口未监听,dnsmasq 和 chinadns-ng 显示 stopped 时请查看它们各自的日志文件,可能是监听端口被占用了,等等。
  4. 在 ss-tproxy 主机上检查 DNS 是否工作正常,域名解析是访问互联网的第一步,这一步如果出问题,后面的就不用测试了。这里选择 dig 作为 DNS 调试工具,因此请先安装 dig 工具。在调试 DNS 之前,先开启几个终端,分别 tail -f 代理进程、dnsmasq、chinadns-ng 的日志文件;然后再开一个终端,执行 dig www.baidu.comdig www.google.com,观察 dig 以及前面几个终端的日志输出,发现不对的地方可以先尝试自行解决,如果解决不了,请通过 issue 报告它。对于 ss/ssr,最常见的错误就是代理的 udp relay 未开启,因此请先确保 udp relay 是否正常,udp relay 不正常会导致 dig www.google.com 解析失败;如果确认已开启 udp relay,那么你还要注意是否出现了 udp 丢包,某些 ISP 会对 udp 数据包进行恶意性丢弃,检查 udp 是否丢包通常需要检查本地以及 vps 上的代理进程的详细日志输出。
  5. 如果 ss-tproxy 主机的 DNS 工作正常,说明 UDP 透明代理应该是正常的,那么接下来应该检查 TCP 透明代理,最简单的方式就是使用 curl 工具进行检测,首先安装 curl 工具,然后执行 curl -4vsSkL https://www.baidu.comcurl -4vsSkL https://www.google.com,如果启用了 ss-tproxy 的 IPv6 透明代理支持,则还应该进行 IPv6 的网页浏览测试,即执行 curl -6vsSkL https://ipv6.baidu.comcurl -6vsSkL https://ipv6.google.com,观察它们的输出是否正常(即是否能够正常获取 HTML 源码),同时观察代理进程、dnsmasq、chinadns-ng 的日志输出。
  6. 如果 ss-tproxy 主机的 DNS 以及 curl 测试都没问题,那么就进行最后一步,在其它内网主机上分别测试 DNS 以及 TCP 透明代理(最简单的就是浏览器访问百度、谷歌),同时你也应该观察代理进程、dnsmasq、chinadns-ng 的日志输出。对于某些系统,可能会优先使用 IPv6 网络(特别是解析 DNS 时),因此如果你没有启用 ss-tproxy 的 IPv6 透明代理,那么请通过各种手段禁用 IPv6(或者进行其它一些妥当的处理),否则会影响透明代理的正常使用。
在报告问题时,请务必提供详细信息,而不是单纯一句话,xxx 不能工作,这对于问题的解决没有任何帮助。

from https://github.com/zfl9/ss-tproxy
--------------------

先说 ss/ssr 透明代理吧,ss-redir 是 ss-libevssr-libev 中的一个工具,配合 iptables 可以在 Linux 上实现 ss、ssr 透明代理,ss-redir 的 TCP 透明代理是通过 REDIRECT 方式实现的,而 UDP 透明代理是通过 TPROXY 方式实现的。注意:ss-redir 只存在于ss-libev 版本
当然,ss/ssr 透明代理并不是只能用 ss-redir 来实现,使用 ss-local + redsocks2 同样可以实现 socks5(ss-local 是 socks5 服务器)全局透明代理;ss-local + redsocks2 实际上是 ss-redir 的分体实现,TCP 使用 REDIRECT 方式实现,UDP 使用 TPROXY 方式实现。
最后说一下 v2ray 的透明代理,其实原理和 ss/ssr-libev 一样,v2ray 可以看作是 ss-local、ss-redir、ss-server 的合体,同一个 v2ray 程序既可以作为 server 端,也可以作为 client 端。所以 v2ray 的透明代理也有两种实现方式,一是利用对应的 “ss-redir”(dokodemo-door入站协议),二是利用对应的 “ss-local”(socks入站协议) + redsocks2(redsocks2 可与任意 socks5 代理组合以实现透明代理)。

方案说明

用过 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、绕过大陆地址段这些分流模式可选择),这样只要设备连到该路由就能直接无缝上网,完全感觉不到“墙”的存在。如果忽略分流模式(即全部流量都走代理出去),那么实现是很简单的(几条 iptables 就可以搞定,但是这太简单粗暴了,很多国内网站走代理会非常慢,体验很不好);但是如果要自己实现 gfwlist、绕过大陆地址段这些模式,就稍微有点点复杂了,尤其是 gfwlist 模式。但这种透明代理的模式的确很诱人,毕竟只要设置一次就可以让所有内网设备上 Internet,于是我开始摸索如何在 Linux 中实现类似 OpenWrt 的代理模式,而我摸索出来的成果就是 ss-tproxy 透明代理脚本,ss-tproxy 可以运行在 Linux 软路由(网关)、Linux 物理机、Linux 虚拟机等环境中;可以用来透明代理 ss-tproxy 主机本身以及所有网关指向 ss-tproxy 主机的内网主机的 TCP、UDP 流量。

代理脚本

安装依赖

curl

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

# ArchLinux
pacman -S curl
Bash

perl5

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

# ArchLinux
pacman -S perl
Bash

ipset

# CentOS
yum -y install ipset

# ArchLinux
pacman -S ipset
Bash

TPROXY

TPROXY 是一个 Linux 内核模块,在 Linux 2.6.28 后进入官方内核。一般正常的发行版都没有裁剪 TPROXY 模块,TPROXY 模块缺失问题主要出现在无线路由固件上(某些精简型发行版也会去掉 TPROXY 模块,比如 Alpine)。使用以下方法可以检测当前内核是否包含 TPROXY 模块。
# 查找 TPROXY 模块
find /lib/modules/$(uname -r) -type f -name '*.ko*' | grep 'xt_TPROXY'

# 正常情况下的输出
/lib/modules/4.16.8-1-ARCH/kernel/net/netfilter/xt_TPROXY.ko.xz

# 尝试手动加载模块
modprobe xt_TPROXY
Bash

iproute2

# CentOS
yum -y install iproute

# ArchLinux
pacman -S iproute2
Bash

dnsmasq

对于 gfwlist/chnlist 模式,需要确保该 dnsmasq 支持 --ipset 选项。
# CentOS
yum -y install dnsmasq

# ArchLinux
pacman -S dnsmasq
Bash

chinadns

这是原版 chinadns,适用于 ss-tproxy v3.0,ss-tproxy v4.0 请用 chinadns-ng。
# 获取
wget https://github.com/shadowsocks/ChinaDNS/releases/download/1.3.2/chinadns-1.3.2.tar.gz

# 解压
tar xf chinadns-1.3.2.tar.gz

# 安装
cd chinadns-1.3.2/
./configure
make && make install
Bash

chinadns-ng

chinadns-ng 是我利用业余时间用 C 语言编写的另一个 chinadns,修复若干问题,优化了性能。
# 获取源码
git clone https://github.com/zfl9/chinadns-ng

# 编译安装
cd chinadns-ng
make && sudo make install
Bash

v2ray

安装很简单,直接使用 v2ray 官方提供的 shell 脚本即可,默认配置开机自启
# 官方安装脚本
bash <(curl -4sSkL https://install.direct/go.sh)

# 安装的文件有
/etc/v2ray/config.json      配置文件
/usr/bin/v2ray/v2ray        V2Ray 程序
/usr/bin/v2ray/v2ctl        V2Ray 工具
/usr/bin/v2ray/geoip.dat    IP 数据文件
/usr/bin/v2ray/geosite.dat  域名数据文件

# 是否安装成功
/usr/bin/v2ray/v2ray -help  # 查看帮助
Bash

ss-libev

ArchLinux 建议使用 pacman -S shadowsocks-libev 安装,方便快捷,更新也及时。
CentOS/RHEL 或其它发行版,强烈建议 编译安装,仓库安装的可能会有问题(版本太老或者根本用不了)。
下面的代码完全摘自 ss-libev 官方 README.md,随着时间的推移可能有变化,最好照着最新 README.md 来做。
# Installation of basic build dependencies
## Debian / Ubuntu
sudo apt-get install --no-install-recommends gettext build-essential autoconf libtool libpcre3-dev asciidoc xmlto libev-dev libc-ares-dev automake libmbedtls-dev libsodium-dev
## CentOS / Fedora / RHEL
sudo yum install gettext gcc autoconf libtool automake make asciidoc xmlto c-ares-devel libev-devel
## Arch
sudo pacman -S gettext gcc autoconf libtool automake make asciidoc xmlto c-ares libev

# Installation of Libsodium
export LIBSODIUM_VER=1.0.13
wget https://download.libsodium.org/libsodium/releases/old/libsodium-$LIBSODIUM_VER.tar.gz
tar xvf libsodium-$LIBSODIUM_VER.tar.gz
pushd libsodium-$LIBSODIUM_VER
./configure --prefix=/usr && make
sudo make install
popd
sudo ldconfig

# Installation of MbedTLS
export MBEDTLS_VER=2.6.0
wget https://tls.mbed.org/download/mbedtls-$MBEDTLS_VER-gpl.tgz
tar xvf mbedtls-$MBEDTLS_VER-gpl.tgz
pushd mbedtls-$MBEDTLS_VER
make SHARED=1 CFLAGS=-fPIC
sudo make DESTDIR=/usr install
popd
sudo ldconfig

# Start building
git clone https://github.com/shadowsocks/shadowsocks-libev.git
cd shadowsocks-libev
git submodule update --init --recursive
./autogen.sh && ./configure && make
sudo make install
Bash

ssr-libev

shadowsocksr-backup/shadowsocksr-libev(貌似已停止更新,但目前使用没问题,只是一些新特性不支持,如有更好的源请告诉我~)
https://github.com/shadowsocksrr/shadowsocksr-libev/tree/Akkariiin/master,另一个 ssr-libev 源,Akkariiin-* 分支目前仍在更新
本文仍以 shadowsocksr-backup/shadowsocks-libev 为例,毕竟另一个源我没试过,但是这个源我自己用过大半年,没有任何问题,很稳定
# Installation of basic build dependencies
## Debian / Ubuntu
sudo apt-get install --no-install-recommends gettext build-essential autoconf libtool libpcre3-dev asciidoc xmlto libev-dev libc-ares-dev automake libmbedtls-dev libsodium-dev
## CentOS / Fedora / RHEL
sudo yum install gettext gcc autoconf libtool automake make asciidoc xmlto c-ares-devel libev-devel
## Arch
sudo pacman -S gettext gcc autoconf libtool automake make asciidoc xmlto c-ares libev

# Installation of Libsodium
export LIBSODIUM_VER=1.0.13
wget https://download.libsodium.org/libsodium/releases/old/libsodium-$LIBSODIUM_VER.tar.gz
tar xvf libsodium-$LIBSODIUM_VER.tar.gz
pushd libsodium-$LIBSODIUM_VER
./configure --prefix=/usr && make
sudo make install
popd
sudo ldconfig

# Installation of MbedTLS
export MBEDTLS_VER=2.6.0
wget https://tls.mbed.org/download/mbedtls-$MBEDTLS_VER-gpl.tgz
tar xvf mbedtls-$MBEDTLS_VER-gpl.tgz
pushd mbedtls-$MBEDTLS_VER
make SHARED=1 CFLAGS=-fPIC
sudo make DESTDIR=/usr install
popd
sudo ldconfig

# Start building
git clone https://github.com/shadowsocksr-backup/shadowsocksr-libev.git
cd shadowsocksr-libev
./configure --prefix=/usr/local/ssr-libev && make && make install
cd /usr/local/ssr-libev/bin
mv ss-redir ssr-redir
mv ss-local ssr-local
ln -sf ssr-local ssr-tunnel
mv ssr-* /usr/local/bin/
rm -fr /usr/local/ssr-libev
Bash
如果编译失败,可以看下这个 issue:https://github.com/shadowsocksrr/shadowsocksr-libev/issues/40(gcc 版本过高导致的)
解决方法就是使用此分支:git clone -b Akkariiin/develop https://github.com/shadowsocksrr/shadowsocksr-libev.git

haveged

如果有时候启动 ss-redir、ss-tunnel 会失败,且错误提示如下,则需要安装 haveged 或 rng-utils/rng-tools。
This system doesn't provide enough entropy to quickly generate high-quality random numbers
Installing the rng-utils/rng-tools or haveged packages may help.
On virtualized Linux environments, also consider using virtio-rng.
The service will not start until enough entropy has been collected.

该系统不能提供足够的熵来快速生成高质量的随机数,
安装 rng-utils/rng-tools 或 haveged 软件包可能会有所帮助。
在虚拟化的 Linux 环境中,请考虑使用 virtio-rng。
shadowsocks 服务只有在收集到足够的熵后才会启动。
None
这里以 haveged 为例,当然,你也可以选择安装 rng-utils/rng-tools,都是一样的:
# ArchLinux
pacman -S haveged
systemctl enable haveged
systemctl start haveged

# CentOS
yum -y install haveged
## CentOS 6.x
chkconfig haveged on
service haveged start
## CentOS 7.x
systemctl enable haveged
systemctl start haveged
Bash

代理测试

测试 IPv4
这里就简单的使用 curl 进行测试,如果有网页源码输出,基本就没什么问题:
# 访问 IPv4 的百度和谷歌
curl -4sSkL https://www.baidu.com
curl -4sSkL https://www.google.com
Bash
测试 IPv6
对于 ss-tproxy v4.0,如果启用了 ipv6 透明代理,可进行一些简单的 ipv6 测试:
# 访问 IPv6 的百度和谷歌
curl -6sSkL https://ipv6.baidu.com
curl -6sSkL https://ipv6.google.com
Bash
测试分流
也可以使用 curl 进行一个简单的分流模式验证,原理就是检查访问网站用的外部 IP:
# global 模式: 理论显示代理 IP
# gfwlist 模式: 理论显示本地 IP
# chnroute 模式: 理论显示本地 IP
curl -4sSkL https://myip.ipip.net
Bash

原理解析

这里的脚本解析仅针对 ss-tproxy v3.0,ss-tproxy v4.0 变化太多了,暂时没时间整理。

脚本浅析

开头部分主要是检查脚本相关的一些文件是否存在
# 默认的 ss-tproxy.conf 配置文件路径
ss_tproxy_config='/etc/ss-tproxy/ss-tproxy.conf'

# 设置 PATH,确保不会在运行过程中出现命令找不到的情况
PATH=$PATH:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin

# 检查 ss-tproxy 脚本相关的文件是否存在,以及加载配置文件
[ ! -f "$ss_tproxy_config"  ] && { echo "[ERR] No such file or directory: '$ss_tproxy_config'"  1>&2; exit 1; } || source "$ss_tproxy_config"
[ ! -f "$file_gfwlist_txt"  ] && { echo "[ERR] No such file or directory: '$file_gfwlist_txt'"  1>&2; exit 1; }
[ ! -f "$file_gfwlist_ext"  ] && { echo "[ERR] No such file or directory: '$file_gfwlist_ext'"  1>&2; exit 1; }
[ ! -f "$file_chnroute_txt" ] && { echo "[ERR] No such file or directory: '$file_chnroute_txt'" 1>&2; exit 1; }
[ ! -f "$file_chnroute_set" ] && { echo "[ERR] No such file or directory: '$file_chnroute_set'" 1>&2; exit 1; }
Bash
中间部分都是一些 function 函数,目前定义的函数有
function check_command { ... }      # 检查相关命令是否存在
function update_chnonly { ... }     # 为 chnonly 模式更新列表
function update_gfwlist { ... }     # 为 gfwlist 模式更新列表
function update_chnroute { ... }    # 为 chnroute 模式更新列表
function flush_dnscache { ... }     # 清空 dnsmasq 上的 DNS 缓存
function start_proxy { ... }        # 运行 proxy_runcmd 启动命令
function start_resolver { ... }     # 开启无污染的 DNS 解析及分流
function start_iptables { ... }     # 设置相关模式的 iptables 规则
function start_dnsproxy { ... }     # 更新 /etc/resolv.conf 解析文件
function start { ... }              # 脚本启动的主逻辑,调用上述函数
function stop { ... }               # 脚本关闭的主逻辑,清理相关规则
function status { ... }             # 脚本状态的主逻辑,ss/netstat 检测
function show_iptables { ... }      # 查看 mangle 表和 nat 表的相关规则
function flush_iptables { ... }     # 清空 raw、mangle、nat、filter 表
function help { ... }               # 打印 ss-tproxy 脚本的使用&帮助信息
Bash
结尾部分就是处理命令行参数,然后调用上面写好的函数
case $1 in
    start)              check_command; start; status;;
    stop)               check_command; stop; status;;
    status)             check_command; status;;
    r*)                 check_command; stop; status; echo; start; status;;
    show-iptables)      show_iptables;;
    flush-iptables)     flush_iptables;;
    flush-gfwlist)      ipset -F gfwlist &>/dev/null;;
    flush-dnscache)     check_command; flush_dnscache;;
    update-chnonly)     check_command; update_chnonly;;
    update-gfwlist)     check_command; update_gfwlist;;
    update-chnroute)    check_command; update_chnroute;;
    c*)                 check_command;;
    h*)                 help;;
    *)                  help; exit 1;;
esac
Bash

dns 解析模块

开头部分
遍历 proxy_server 数组,如果是域名,则解析为对应 IP,稍后会加到 dnsmasq 的静态解析列表中(相当于 hosts 文件),之所以要这么做,是因为 v2ray 的一个特性造成的,如果给 v2ray 配置的代理服务器为域名,那么 v2ray 每次连接到代理服务器之前,都会去解析这个域名(而不是像 ss-libev、ssr-libev 那样,启动的时候就先解析出对应的服务器 IP),如果没有把这个域名加入静态解析列表,那么此刻代理就会陷入死循环,因为 v2ray 自己发出去的 dns 解析请求被导向代理隧道了(global 模式、chnroute 模式),而代理本身就是要先解析出这个域名对应的 IP 才能连接到我们的代理服务器;而如果我们在启动代理之前就先解析出对应的 IP,并加入 dnsmasq 的静态解析列表,就不会有问题了,因为当 v2ray 发出的 dns 解析请求到达 dnsmasq 时,dnsmasq 会从静态解析列表中返回这个 IP,然后就能正常连接 v2ray 代理服务器了。
for server in "${proxy_server[@]}"; do
    if [ $(grep -Ec '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' <<<"$server") -eq 0 ]; then
        server_addrs+=("/$server/$(ping -nq -c1 -t1 -W1 $server | head -n1 | awk -F'[()]' '{print $2}')")
    fi
done
Bash
global 模式
比较简单,因为不需要分流,全部解析都交给远程 dns 去解析(8.8.8.8,走代理),之所以加个 dnsmasq,而不是直接用 iptables 代理到 8.8.8.8,是为了利用 dnsmasq 的缓存功能来加速 DNS 解析,不然每次解析都要请求远程服务器,多慢啊,有了 DNS 缓存,我第二次解析同一个域名就直接可以返回给请求客户端了,因此,所有 mode 我都加入了 dnsmasq 来作缓存。
dnsmasq -C <(cat <<EOF
$([ "$dnsmasq_log_enable" = 'true' ] && echo 'log-queries')
log-facility = $dnsmasq_log_file
log-async = 20
domain-needed
cache-size = $dnsmasq_cache_size
$([ $(dnsmasq --help | grep -c min-cache-ttl) -ne 0 ] && echo "min-cache-ttl = $dnsmasq_cache_time")
no-negcache
no-resolv
port = 60053
server = ${dns_remote/:/#}
$(for server_addr in "${server_addrs[@]}"; do echo "address = $server_addr"; done)
EOF
)
Bash
gfwlist 模式
和 global 模式的配置差不多,只不过默认使用国内 dns 进行解析(114.114.114.114,走直连),然后对位于 gfwlist.txt、gfwlist.ext 中的域名使用远程 dns 进行解析(8.8.8.8,走代理),也就是所谓的黑名单模式,当然这里还使用了 dnsmasq 的 ipset 功能,黑名单中的这些域名解析出的 IP 地址会自动加入到我们指定的 ipset 列表中,这样我们在 iptables 规则中就可以动态的让这些 IP 走代理,从而完成分流(域名到 IP 的映射)。
dnsmasq -C <(cat <<EOF
$([ "$dnsmasq_log_enable" = 'true' ] && echo 'log-queries')
log-facility = $dnsmasq_log_file
log-async = 20
domain-needed
cache-size = $dnsmasq_cache_size
$([ $(dnsmasq --help | grep -c min-cache-ttl) -ne 0 ] && echo "min-cache-ttl = $dnsmasq_cache_time")
no-negcache
no-resolv
port = 60053
server = $dns_direct
$(for server_addr in "${server_addrs[@]}"; do echo "address = $server_addr"; done)
$(perl -pe "s@^.*+\$@server=/$&/${dns_remote/:/#}\nipset=/$&/gfwlist@" $file_gfwlist_txt <(
perl -ne 'print unless m@^\s|\s\n$|^#|^\d++\.\d++\.\d++\.\d++(?:/\d++)?$@' $file_gfwlist_ext
))
EOF
)
Bash
chnroute 模式
这里的 dnsmasq 就是起到一个缓存的作用,因为 chinadns 不支持 dns 缓存,为了提高解析性能(抗住大并发的 dns 解析请求),所以很有必要加上 dnsmasq;这里解释一下第一段代码的作用,为什么要将直连 dns 加入 chnroute 列表,要理解这个,首先要理解 chinadns 的分流原理(个人理解,有错误还请指出);chinadns 要运行,首先要给它配置两组 dns 服务器,一组是 国内 dns,一组是 可信 dns(国外 dns),所以至少需要给它配置两个上游 dns,就假设为 114.114.114.114(国内 dns)、8.8.8.8(可信 dns,走代理),然后还要给它指定一个 chnroute 列表(大陆地址段);当 chinadns 收到一个解析请求时,它会同时向这两组 dns 发出请求,然后:
  • 如果国内 dns 先返回(正常情况,因为直连肯定比代理快),那么 chinadns 会检查国内 dns 返回的 ip 是否为大陆地址(chnroute 列表),如果是,则 chinadns 会直接将这个解析出来的 IP 返回给请求客户端,然后完成解析(忽略可信 dns 的结果);如果国内 dns 返回的 ip 不是大陆地址(chnroute 列表),那么 chinadns 会过滤掉国内 dns 的解析结果,然后等待可信 dns 的解析结果并将其返回给请求客户端,完成解析。
  • 如果可信 dns 先返回(非正常情况,比如给可信 dns 加了本地缓存,导致它最先返回),那么 chinadns 会直接返回可信 dns 的解析结果,而不会考虑国内 dns 解析结果,如果是这样的话,那么其实 chinadns 的分流就完全出问题了,根本就没有分流,全都是返回远程 dns 解析出来的结果,所以一定不能给 chinadns 的可信 dns 设置 dns 缓存(如给它套一层 dnsmasq/pdnsd/dnsforwarder)。
那这和第一段代码有什么关联?当然有关联,你可曾想过,chinadns 如何知道你传递给它的这些 dns 哪个是国内 dns,哪个是国外 dns?那肯定得通过 chnroute.txt 列表来判断啊,如果这个 dns 是大陆地址,那么它就是 国内 dns,否则它就是 可信 dns,而为了保险,我干脆就将 dns_direct 加入这个列表,这样就可以避免 chinadns 将我们的国内 dns 认为是国外 dns 了(我印象中出现过这种情况,而且 issues 里面也有一个人遇到过这个问题)。
# 将直连 dns 加入列表
temp_chnroute_txt=$(mktemp)
cat $file_chnroute_txt >$temp_chnroute_txt
echo "$dns_direct/32" >>$temp_chnroute_txt
# 将内网地址段加入列表
for intranet in "${ipts_intranet[@]}"; do echo "$intranet" >>$temp_chnroute_txt; done

# 运行 chinadns(分流)
chinadns_params="-b 0.0.0.0 -p 65353 -s $dns_direct,$dns_remote -c $temp_chnroute_txt"
[ "$chinadns_mutation" = 'true' ] && chinadns_params+=' -m'
[ "$chinadns_verbose"  = 'true' ] && chinadns_params+=' -v'
(chinadns $chinadns_params </dev/null &>>$chinadns_logfile &)

# 运行 dnsmasq(缓存)
dnsmasq -C <(cat <<EOF
$([ "$dnsmasq_log_enable" = 'true' ] && echo 'log-queries')
log-facility = $dnsmasq_log_file
log-async = 20
domain-needed
cache-size = $dnsmasq_cache_size
$([ $(dnsmasq --help | grep -c min-cache-ttl) -ne 0 ] && echo "min-cache-ttl = $dnsmasq_cache_time")
no-negcache
no-resolv
port = 60053
server = 127.0.0.1#65353
$(for server_addr in "${server_addrs[@]}"; do echo "address = $server_addr"; done)
EOF
)
Bash

iptables 规则

start_iptables() 这个函数基本上是最复杂的函数了,但只要理清楚了这里面的关系和执行逻辑,就很容易理解。
设置相关内核参数
# 启用软路由功能
sysctl -w net.ipv4.ip_forward=1 &>/dev/null

# 禁止发送 icmp 重定向
for dir in $(ls /proc/sys/net/ipv4/conf); do
    sysctl -w net.ipv4.conf.$dir.send_redirects=0 &>/dev/null
done
Bash
检查 ipts_non_snat 选项的函数
function check_snat_rule {
    if [ "$ipts_non_snat" != 'true' ]; then
        if ! iptables -t nat -C POSTROUTING -s $intranet ! -d $intranet -j MASQUERADE &>/dev/null; then
            iptables -t nat -A POSTROUTING -s $intranet ! -d $intranet -j MASQUERADE
        fi
    fi
}
Bash
遍历 ipts_intranet 内网地址数组,找出非标网段
function dec2bin {
    for ((n = $1; n > 0; n >>= 1)); do bit="$((n & 1))$bit"; done
    printf " d\n" "$bit" # 这有个显示问题,其实是 % 08d
}

function net2bin {
    net=$(awk -F/ '{print $1}' <<<$1)
    len=$(awk -F/ '{print $2}' <<<$1)
    IFS='.' read -ra bytes <<<"$net"
    for byte in "${bytes[@]}"; do result+="$(dec2bin $byte)"; done
    echo "${result:0:len}"
}

for cidr in "${ipts_intranet[@]}"; do
    curnet=$(net2bin $cidr)
    for stdnet in 00001010 101011000001 1010100111111110 1100000010101000; do
        [[ "$curnet" == "$stdnet"* ]] && continue 2
    done
    ipts_intranet_nonstd+=($cidr)
done
Bash
设置外层 iptables 自定义规则链、设置 ip rule 策略路由
iptables -t mangle -N SSTP_OUT
iptables -t mangle -N SSTP_PRE
iptables -t nat    -N SSTP_OUT
iptables -t nat    -N SSTP_PRE

iptables -t mangle -A OUTPUT     -j SSTP_OUT
iptables -t mangle -A PREROUTING -j SSTP_PRE
iptables -t nat    -A OUTPUT     -j SSTP_OUT
iptables -t nat    -A PREROUTING -j SSTP_PRE

# 之所以要设置 ip rule 规则是因为 TPROXY 代理方式需要
ip route add local 0/0 dev lo     table $ipts_rt_tab
ip rule  add fwmark $ipts_rt_mark table $ipts_rt_tab
Bash
纯 tproxy 方式 - gfwlist 分流
# 将黑名单中的 ip 和网段加入 ipset
ipset -N gfwlist hash:net &>/dev/null
perl -ne 'print if m@^\d++\.\d++\.\d++\.\d++(?:/\d++)?$@' $file_gfwlist_ext | xargs -n1 ipset -A gfwlist &>/dev/null

# 打上 iptables 标记,mark 了的会走代理
iptables -t mangle -N SETMARK
iptables -t mangle -A SETMARK -p udp -d ${dns_remote%:*}     -j MARK --set-mark $ipts_rt_mark
iptables -t mangle -A SETMARK -m set --match-set gfwlist dst -j MARK --set-mark $ipts_rt_mark

# 本机发出去的 TCP 和 UDP 走一下 SETMARK 链
iptables -t mangle -A SSTP_OUT -p tcp -j SETMARK
iptables -t mangle -A SSTP_OUT -p udp -j SETMARK

# 将发往 127.0.0.1:53/udp 的包重定向给 dnsmasq
iptables -t nat    -A SSTP_OUT -p udp -d 127.0.0.1 --dport 53 -j REDIRECT --to-ports 60053

# 遍历内网地址段,处理内网主机发过来的 TCP 和 UDP
for intranet in "${ipts_intranet[@]}"; do
    # 将内网主机发出的 dns 请求包重定向给 dnsmasq
    iptables -t mangle -A SSTP_PRE -p udp -s $intranet --dport 53 -m mark ! --mark $ipts_rt_mark -j ACCEPT
    iptables -t nat    -A SSTP_PRE -p udp -s $intranet --dport 53 -m mark ! --mark $ipts_rt_mark -j REDIRECT --to-ports 60053

    # 让内网主机发出的 TCP 和 UDP 走一下 SETMARK 链
    iptables -t mangle -A SSTP_PRE -p tcp -s $intranet            -m mark ! --mark $ipts_rt_mark -j SETMARK
    iptables -t mangle -A SSTP_PRE -p udp -s $intranet            -m mark ! --mark $ipts_rt_mark -j SETMARK

    # 检查是否需要设置 SNAT/MASQUERADE 规则
    check_snat_rule
done

# 将所有打了标记的 TCP 和 UDP 包透明地转发到代理的监听端口
iptables -t mangle -A SSTP_PRE -m mark --mark $ipts_rt_mark -p tcp -j TPROXY --on-ip 127.0.0.1 --on-port $proxy_tcport
iptables -t mangle -A SSTP_PRE -m mark --mark $ipts_rt_mark -p udp -j TPROXY --on-ip 127.0.0.1 --on-port $proxy_udport
Bash
纯 tproxy 方式 - global/chnroute 分流
# 打上 iptables 标记
iptables -t mangle -N SETMARK
iptables -t mangle -A SETMARK -d 0/8        -j RETURN
iptables -t mangle -A SETMARK -d 10/8       -j RETURN
iptables -t mangle -A SETMARK -d 127/8      -j RETURN
iptables -t mangle -A SETMARK -d 169.254/16 -j RETURN
iptables -t mangle -A SETMARK -d 172.16/12  -j RETURN
iptables -t mangle -A SETMARK -d 192.168/16 -j RETURN
iptables -t mangle -A SETMARK -d 224/4 -j RETURN
iptables -t mangle -A SETMARK -d 240/4 -j RETURN
for cidr in "${ipts_intranet_nonstd[@]}"; do
    iptables -t mangle -A SETMARK -d $cidr -j RETURN
done
if ! [ "$proxy_dports" ]; then
    for server in "${proxy_server[@]}"; do
        iptables -t mangle -A SETMARK -d $server -j RETURN
    done
else
    for server in "${proxy_server[@]}"; do
        iptables -t mangle -A SETMARK -d $server -p tcp -m multiport --dports $proxy_dports -j RETURN
        iptables -t mangle -A SETMARK -d $server -p udp -m multiport --dports $proxy_dports -j RETURN
    done
fi
if [ "$mode" = chnroute ]; then
    ipset -X chnroute &>/dev/null
    ipset -R <$file_chnroute_set
    iptables -t mangle -A SETMARK -m set --match-set chnroute dst -j RETURN
fi
iptables -t mangle -A SETMARK -j MARK --set-mark $ipts_rt_mark

# 本机 TCP 和 UDP 走标记链
iptables -t mangle -A SSTP_OUT -p tcp -j SETMARK
iptables -t mangle -A SSTP_OUT -p udp -j SETMARK

# 本机 DNS 重定向给 dnsmasq
iptables -t nat    -A SSTP_OUT -p udp -d 127.0.0.1 --dport 53 -j REDIRECT --to-ports 60053

# 遍历内网地址段,处理 TCP 和 UDP
for intranet in "${ipts_intranet[@]}"; do
    # 将内网主机发出的 dns 请求重定向给 dnsmasq
    iptables -t mangle -A SSTP_PRE -p udp -s $intranet --dport 53 -m mark ! --mark $ipts_rt_mark -j ACCEPT
    iptables -t nat    -A SSTP_PRE -p udp -s $intranet --dport 53 -m mark ! --mark $ipts_rt_mark -j REDIRECT --to-ports 60053

    # 让内网主机发出的 TCP 和 UDP 走一下标记链
    iptables -t mangle -A SSTP_PRE -p tcp -s $intranet            -m mark ! --mark $ipts_rt_mark -j SETMARK
    iptables -t mangle -A SSTP_PRE -p udp -s $intranet            -m mark ! --mark $ipts_rt_mark -j SETMARK

    # 检查是否需要设置 SNAT/MASQUERADE 规则
    check_snat_rule
done

# 将所有打了标记的 TCP 和 UDP 包透明地转发到代理的监听端口
iptables -t mangle -A SSTP_PRE -m mark --mark $ipts_rt_mark -p tcp -j TPROXY --on-ip 127.0.0.1 --on-port $proxy_tcport
iptables -t mangle -A SSTP_PRE -m mark --mark $ipts_rt_mark -p udp -j TPROXY --on-ip 127.0.0.1 --on-port $proxy_udport
Bash
传统模式 - gfwlist 分流
# 导入黑名单 ip 和网段
ipset -N gfwlist hash:net &>/dev/null
perl -ne 'print if m@^\d++\.\d++\.\d++\.\d++(?:/\d++)?$@' $file_gfwlist_ext | xargs -n1 ipset -A gfwlist &>/dev/null

# TCP 使用重定向方式进行代理
iptables -t nat -N TCPCHAIN
iptables -t nat -A TCPCHAIN -p tcp -m set --match-set gfwlist dst -j REDIRECT --to-ports $proxy_tcport

# UDP 使用 TPROXY 方式进行代理
iptables -t mangle -N UDPCHAIN
iptables -t mangle -A UDPCHAIN -d ${dns_remote%:*}            -j MARK --set-mark $ipts_rt_mark
iptables -t mangle -A UDPCHAIN -m set --match-set gfwlist dst -j MARK --set-mark $ipts_rt_mark

# 让本机 TCP 和 UDP 走一下代理链
iptables -t nat    -A SSTP_OUT -p tcp -j TCPCHAIN
iptables -t mangle -A SSTP_OUT -p udp -j UDPCHAIN

# 将本机 dns 请求重定向到 dnsmasq
iptables -t nat    -A SSTP_OUT -p udp -d 127.0.0.1 --dport 53 -j REDIRECT --to-ports 60053

# 遍历内网地址段,处理 TCP 和 UDP
for intranet in "${ipts_intranet[@]}"; do
    # 处理 dns,重定向到 dnsmasq
    iptables -t mangle -A SSTP_PRE -p udp -s $intranet --dport 53 -m mark ! --mark $ipts_rt_mark -j ACCEPT
    iptables -t nat    -A SSTP_PRE -p udp -s $intranet --dport 53 -m mark ! --mark $ipts_rt_mark -j REDIRECT --to-ports 60053

    # 处理 TCP、UDP,走代理链
    iptables -t nat    -A SSTP_PRE -p tcp -s $intranet                                -j TCPCHAIN
    iptables -t mangle -A SSTP_PRE -p udp -s $intranet -m mark ! --mark $ipts_rt_mark -j UDPCHAIN

    # 检查 SNAT 规则
    check_snat_rule
done

# 将打了标记的 UDP 包透明地转发到代理进程的 UDP 监听端口
iptables -t mangle -A SSTP_PRE -m mark --mark $ipts_rt_mark -p udp -j TPROXY --on-ip 127.0.0.1 --on-port $proxy_udport
Bash
传统模式 - global/chnroute 分流
# TCP 使用重定向方式进行代理
iptables -t nat -N TCPCHAIN
iptables -t nat -A TCPCHAIN -d 0/8        -j RETURN
iptables -t nat -A TCPCHAIN -d 10/8       -j RETURN
iptables -t nat -A TCPCHAIN -d 127/8      -j RETURN
iptables -t nat -A TCPCHAIN -d 169.254/16 -j RETURN
iptables -t nat -A TCPCHAIN -d 172.16/12  -j RETURN
iptables -t nat -A TCPCHAIN -d 192.168/16 -j RETURN
iptables -t nat -A TCPCHAIN -d 224/4 -j RETURN
iptables -t nat -A TCPCHAIN -d 240/4 -j RETURN
for cidr in "${ipts_intranet_nonstd[@]}"; do
    iptables -t nat -A TCPCHAIN -d $cidr -j RETURN
done
for server in "${proxy_server[@]}"; do
    iptables -t nat -A TCPCHAIN -d $server $([ "$proxy_dports" ] && echo "-p tcp -m multiport --dports $proxy_dports") -j RETURN
done
if [ "$mode" = chnroute ]; then
    ipset -X chnroute &>/dev/null
    ipset -R <$file_chnroute_set
    iptables -t nat -A TCPCHAIN -m set --match-set chnroute dst -j RETURN
fi
iptables -t nat -A TCPCHAIN -p tcp -j REDIRECT --to-ports $proxy_tcport

# UDP 使用 TPROXY 方式进行代理
iptables -t mangle -N UDPCHAIN
iptables -t mangle -A UDPCHAIN -d 0/8        -j RETURN
iptables -t mangle -A UDPCHAIN -d 10/8       -j RETURN
iptables -t mangle -A UDPCHAIN -d 127/8      -j RETURN
iptables -t mangle -A UDPCHAIN -d 169.254/16 -j RETURN
iptables -t mangle -A UDPCHAIN -d 172.16/12  -j RETURN
iptables -t mangle -A UDPCHAIN -d 192.168/16 -j RETURN
iptables -t mangle -A UDPCHAIN -d 224/4 -j RETURN
iptables -t mangle -A UDPCHAIN -d 240/4 -j RETURN
for cidr in "${ipts_intranet_nonstd[@]}"; do
    iptables -t mangle -A UDPCHAIN -d $cidr -j RETURN
done
for server in "${proxy_server[@]}"; do
    iptables -t mangle -A UDPCHAIN -d $server $([ "$proxy_dports" ] && echo "-p udp -m multiport --dports $proxy_dports") -j RETURN
done
if [ "$mode" = chnroute ]; then
    iptables -t mangle -A UDPCHAIN -m set --match-set chnroute dst -j RETURN
fi
iptables -t mangle -A UDPCHAIN -j MARK --set-mark $ipts_rt_mark

# 让本机 TCP 和 UDP 走一下代理链
iptables -t nat    -A SSTP_OUT -p tcp -j TCPCHAIN
iptables -t mangle -A SSTP_OUT -p udp -j UDPCHAIN

# 将本机 dns 请求重定向到 dnsmasq
iptables -t nat    -A SSTP_OUT -p udp -d 127.0.0.1 --dport 53 -j REDIRECT --to-ports 60053

# 遍历内网地址段,处理 TCP 和 UDP
for intranet in "${ipts_intranet[@]}"; do
    # 处理 dns,重定向到 dnsmasq
    iptables -t mangle -A SSTP_PRE -p udp -s $intranet --dport 53 -m mark ! --mark $ipts_rt_mark -j ACCEPT
    iptables -t nat    -A SSTP_PRE -p udp -s $intranet --dport 53 -m mark ! --mark $ipts_rt_mark -j REDIRECT --to-ports 60053

    # 处理 TCP、UDP,走代理链
    iptables -t nat    -A SSTP_PRE -p tcp -s $intranet                                -j TCPCHAIN
    iptables -t mangle -A SSTP_PRE -p udp -s $intranet -m mark ! --mark $ipts_rt_mark -j UDPCHAIN

    # 检查 SNAT 规则
    check_snat_rule
done

# 将打了标记的 UDP 包透明地转发到代理进程的 UDP 监听端口
iptables -t mangle -A SSTP_PRE -m mark --mark $ipts_rt_mark -p udp -j TPROXY --on-ip 127.0.0.1 --on-port $proxy_udport
Bash

常见问题

这里有些问答可能仅适用于 ss-tproxy v3.0,请仔细甄别,切勿盲目照搬照抄。
建议在 github 上报告相关问题,disqus 评论在国内无法访问,而且也不好查看。
做 代理网关/单臂路由/旁路网关 时,ss-tproxy 主机代理正常,其它主机代理异常
首先排查是不是 ipts_non_snat 选项的问题,默认我设置的是 false,双重否定其实就是肯定,也就是说设置为 false 的话就表示会设置 SNAT 规则,通常情况下,只有你真的需要 SNAT 规则时你才需要将其设置为 false,比如你在网络出口位置使用 ss-tproxy(一头连接内网,一头连接公网),那么就需要设置 SNAT 规则,但其实这种情况下你的系统上早就已经设置好了 SNAT 规则,不然你是怎么上网的,对吧。因此,无论什么情况,都先尝试将 ipts_non_snat 选项设为 true,除非你在设置为 true 的情况下代理有问题,那么你才需要将其设置为 false。另外,这个选项如果你设置过 false,并启动了 ss-tproxy,那么除非你手动清除这个 SNAT 规则或者重启系统(nat 表,POSTROUTING 链),否则系统中还是会存在这个 SNAT 规则的,保险起见,在切换 ipts_non_snat 选项后,先执行一遍 ss-tproxy flush-iptables 命令(ss-tproxy v4.0 无此问题)。
某些系统自带一些 iptables 规则,可能会影响 ss-tproxy 的正常使用
如果你对 iptables 规则不是很了解,请在启动 ss-tproxy 之前将已有的 iptables 规则清空,通常情况下执行命令 ss-tproxy flush-iptables 即可清空,但最好自己再检查一下,因为据反馈有些系统的规则还是无法彻底清除,那么怎么检查呢?执行 flush-iptables 之后,手动执行下面几个命令,查看 raw、mangle、nat、filter 表的规则,如果都是空的那说明就是彻底清空了:
iptables -nL -t raw
iptables -nL -t mangle
iptables -nL -t nat
iptables -nL -t filter
Bash
比如 raw 表,默认是这样的,其它的表也类似,只不过预定义规则链不一样而已:
$ iptables -nL -t raw
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
Bash
那如果执行了 flush-iptables 后还是有残留规则,该怎么办呢?请自行谷歌。如果系统每次开机都会自动设置一些规则,而且你还不知道如何关闭它,那么也可以在 ss-tproxy.conf 的 pre_start 钩子函数中加入 ss-tproxy flush-iptables 清理命令,如下,这样每次启动 ss-tproxy 都会自动清空规则:
function pre_start {
    ss-tproxy flush-iptables
}
Bash
ss-tproxy status 显示不准确(已运行却显示 stopped)
首先检查 ss-tproxy.conf 中的 opts_ss_netstat 选项值是否设置正确,默认是 auto 自选选择模式,即如果有 ss 就优先使用 ss 命令,如果没有才会去使用 netstat 命令;如果自动选择模式对你不适用,那么可以改为 ss 或 netstat 来明确告诉 ss-tproxy 你要使用哪个端口检测命令(有些系统没有 ss,而有些系统却只有 ss);如果确定不是这个选项的问题,那么我估计你遇到了这个问题:执行 ss-tproxy start|restart 后,发现 pxy/tcp 和 pxy/udp 的状态是 stopped 的,然后再次执行 ss-tproxy status 查看状态,却又发现是 running 的,且代理进程也正常启动了,代理也是正常的;这个其实不是 ss-tproxy 的问题,应该是 ss、netstat 的检测延迟问题,因为第一次状态检测(start|restart)是在 proxy_runcmd 运行之后立即执行的,因为某些原因(具体什么原因我也没详细了解),导致 ss/netstat 没检测到对应的监听端口(或者还有一种情况,就是代理进程此时可能还没监听端口,还在处理其它事,如还在处理命令行选项等等,反正都有可能),然后你再次执行 status 命令查看状态时,因为存在一个时间间隔,所以基本上就能正常检测到了。
ss-tproxy 现在是否支持 ipv6 的透明代理(暂时不支持
ipv6 的支持已经在计划之中,但可能没有这么快,毕竟没有那么多业余时间来写,挺忙的。
ipv6 已经在 ss-tproxy v4.0 中实现,欢迎大家进行测试,我目前并没有 ipv6 环境,测试是在某位热心网友协助下进行的,在此表示感谢。
特别注意,ss-tproxy v3 要求代理服务器支持 udp 转发
就拿 ss/ssr 来说,ss-redir/ssr-redir 需要开启 udp relay 功能,然后 ss-server/ssr-server 也需要开启 udp relay 功能,如果服务器还有防火墙规则,请注意放行对应的 udp 端口,此外还请确认你当地的 ISP 没有对 udp 流量进行恶意丢包。对于 v2ray(vmess),因为它的 udp 是通过 tcp 转发出去的(即 udp over tcp),所以不需要放行服务器的防火墙端口,也不需要担心 ISP 对 udp 流量的恶意干扰,因为根本没有走 udp,而是走的 tcp。如果你因为各种原因无法使用 udp relay,请考虑使用 v2ray,而且现在网络环境越来越差,ss/ssr 貌似有些顶不住了。
如何只对指定的 host/addr 进行代理,其它的通通走直连
其实非常简单,使用 gfwlist 模式即可;gfwlist 模式会读取 gfwlist.txt、gfwlist.ext 两个黑名单文件,如果你只想代理某些域名、IP、网段,其它的都不想代理,可以直接将 gfwlist.txt 文件清空(执行命令 true >/etc/ss-tproxy/gfwlist.txt),然后编辑 gfwlist.ext 文件,填写要代理的域名、IP、网段即可(文件中有格式说明)。注意,在这种模式下就不要执行 update-chnonlyupdate-gfwlist 命令了,因为它们会操作 gfwlist.txt 文件。
如何将 socks5 代理转换为 ss-tproxy v3 可用的透明代理
如果你因为各种原因无法编译 ss-libev、ssr-libev,但又想使用 ss-tproxy 的透明代理功能,也可以使用 redsocks2 来将任意支持 udp 的 socks5 代理转换为透明代理,这样即使你没有 ss-libev、ssr-libev,也可以使用 ss-tproxy 的透明代理功能(即使用 python 版 ss/ssr + redsocks2 来做透明代理)。当然因为我的系统只安装了 libev 版本,没有安装 python 版本,所以这里依旧使用 ssr-local 作为例子讲解(如果使用 python 版的 ss/ssr,那么你只需要将对应的 ssr-local 替换为 sslocal 命令,但记得开启 udp relay),怎么编译 redsocks2(注意是 redsocks2,不是原版 redsocks)这里就不多说了,官方 readme 有详细说明;我们只需要关注 redsocks2 的配置文件(假设文件路径为 /etc/redsocks2.conf):
base {
  daemon = on;
  log_info = on;
  reuseport = on;
  redirector = iptables;
  log = "file:/var/log/redsocks2.log";
}

redsocks {
  local_ip = 0.0.0.0;
  local_port = 60080;
  ip = 127.0.0.1;
  port = 61080;
  type = socks5;
}

redudp {
  local_ip = 0.0.0.0;
  local_port = 60080;
  ip = 127.0.0.1;
  port = 61080;
  type = socks5;
}
Ini
base 是 redsocks2 的配置,redsocks 是 tcp 透明代理的配置(REDIRECT),redudp 是 udp 透明代理的配置(TPROXY),ip 和 port 是 socks5 服务器的监听地址,在这里就是 ssr-local 的监听地址,而 local_ip、local_port 则是 redsocks2 进程的监听地址,注意是 0.0.0.0:60080/tcp+udp,端口需要和 ss-tproxy.conf 里面的 proxy_tcport、proxy_udport 相同,监听地址也必须为 0.0.0.0。然后就是配置 ss-tproxy.conf,假设 vps 地址为 1.2.3.4:
proxy_tproxy='false'
proxy_server=(1.2.3.4)
proxy_tcport='60080'
proxy_udport='60080'
proxy_runcmd='start_ssrlocal_redsocks2'
proxy_kilcmd='kill -9 $(pidof ssr-local) $(pidof redsocks2)'
Bash
然后在 ss-tproxy.conf 的任意位置定义我们的 start_ssrlocal_redsocks2 函数,比如在文件末尾添加:
function start_ssrlocal_redsocks2 {
    (ssr-local -s1.2.3.4 -p8080 -mnone -kpassword -Oorigin -oplain -b0.0.0.0 -l61080 -u </dev/null &>/dev/null &)
    /usr/local/bin/redsocks2 -c /etc/redsocks2.conf
}
Bash
iptables: No chain/target/match by that name
如果是 iptables -j TPROXY 这条命令报的错(使用 bash -x /usr/local/bin/ss-tproxy start 查看调试信息),那就是没有 TPROXY 模块。
Syntax error: redirection unexpected
如果运行脚本时报了这个错误,你应该检查一下你的 bash 是不是正常版本,或者使用 ls -l /bin/bash 看下这个文件是否软连接到了其它 shell。
ss-tproxy.conf 中的函数不可重复定义
特别注意,因为 ss-tproxy 和 ss-tproxy.conf 都是一个 bash 脚本,所以这两个文件的内容也必须符合 bash 的语法规则,比如你不能在里面重复定义一个函数,虽然这不会报错,但是只有最后一个函数才会生效,这可能会坑死你,如果你定义了多个同名的 bash 函数,请将它们合并为一个!
BT/PT 问题(global/chnroute 模式)
如果你经常使用 BT/PT 下载,请务必使用 gfwlist 模式,如果使用 global/chnroute 模式,可能会消耗大量的 VPS 流量,甚至可能导致 VPS 被封(因为很多主机商都不允许 BT/PT 下载)。因为使用 iptables 来识别 BT/PT 流量的效率太低(基本都是使用 string 模块来匹配,我个人是无法接受的),所以最好的办法还是使用 gfwlist 模式,因为 gfwlist 模式下只有被墙了的网站才会走代理,其它的都是走直连(绝大多数 BT/PT 流量)。
对于 ss-tproxy v4.0,你可以指定要代理的目的端口,可以利用此选项来避免 BT/PT 流量走代理的问题,具体请看 4.0 的 README。
默认的 8.8.8.8:53 DNS 有问题
请关闭 chinadns 的压缩指针,即将 ss-tproxy.conf 中的 chinadns_mutation 改为 false(新版已默认关闭),启用压缩指针时,有时候使用 1.1.1.1、8.8.8.8、8.8.4.4 这些国外 DNS 会有问题,无法正常解析 DNS,导致的现象就是国内网站可以上,但是国外网站不能上。ss-tproxy v4.0 无此问题。
start 时部分组件 stopped
如果是 dnsmasq 或 chinadns 启动失败,请先检查 /var/log 下面的日志,看看是不是监听地址被占用了(按道理来说,v3 最新版本已经将它们两个的监听端口调的很高了,基本没有与之冲突的监听地址);如果是 pxy/tcp 或 pxy/udp 启动失败,请检查 ss-tproxy.conf 里面的 proxy_tcport 和 proxy_udport 端口是否与 proxy_runcmd 启动的进程的监听端口一致,因为默认情况下,ss-redir 或 ssr-redir 的监听端口是 1080,而 ss-tproxy 设置的是 60080,当然这个端口是可以随便改的,但是我觉得还是使用高位端口好一些,省得那么多端口冲突。
切换模式后不能正常代理了
从其它模式切换到 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-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 配置细节
这里说的 DHCP 配置仅针对“代理网关”模式,其实如何配置 DHCP 完全取决于你自己的需求,第一种:将路由器 DHCP 分配的 Gateway 和 DNS 改为 ss-tproxy 主机的地址,然后给 ss-tproxy 主机配置静态 IP,注意不能使用 DHCP 来获取,因为获取到的 Gateway 是指向自己的,显然有问题,这种方式下,所有内网主机默认通过 ss-tproxy 代理上网,如果某些主机不想走代理,可以手动将这些主机的 Gateway 和 DNS 改为原来的,就可以恢复直连了,不会走代理。第二种:不改变任何 DHCP 配置,默认情况下除 ss-tproxy 主机本身外,其它主机都是走直连的,如果你想让某些主机走代理上网,可以手动将这些主机的 Gateway 和 DNS 改为 ss-tproxy 主机的 IP,这样就可以走代理上网了。当然如果你的路由器支持为不同的主机设置不同的 Gateway 和 DNS,那么也可以不修改任何内网主机的配置,直接在路由上配置对应的 DHCP 规则就行了。
ss-tproxy 支持什么发行版
一般都支持,没有限制,因为没有使用与特定发行版相关的命令和特性(我自己用的是 ArchLinux),当然我说的是普通 x86、arm 发行版,如果是路由器的那种系统,应该会有点问题,但是移植起来应该不难。我测试过的系统有:ArchLinux、RHEL、CentOS、Debian、Alpine;经过几个网友测试,OpenWrt 貌似也可以,当然需要自己改一些东西,比如 OpenWrt 自带的 bash 是有问题的,阉割版,运行起来会报错,换为标准版 bash 就没问题。
ss-tproxy 只能运行在网关上吗
显然不是,你可以在一台普通的 linux 主机(甚至是桥接模式下的虚拟机)上运行,并且这种方式也是能够透明代理其它主机的 TCP 和 UDP 的哦。
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 呢?假设这里的副路由是一个树莓派,那么我不管在什么网络下(公司、酒店),只要将树莓派插上,然后我的设备(手机、笔记本)只需要连接树莓派就能无缝上网了,同时又不会影响内网中的其它用户,一举两得(当然我只是举个栗子,实际上可能没想的这么方便)。
ss-tproxy 可以运行在内网主机上吗(代理网关)
可以。先解释一下这里的“代理网关”(不知道叫什么好),由网友 @feiyu 启发。他的意思是,将 ss-tproxy 部署在一台普通的内网主机上(该主机的网络配置不变),然后将其他内网主机的网关和 DNS 指向这台部署了 ss-tproxy 的主机,进行代理。方案是可行的,我在 VMware 环境中测试通过。注意,这个“代理网关”可以是一台真机,也可以是一台虚拟机(桥接模式),比如在 Windows 系统中运行一个 VMware 或 VirtualBox 的 Linux 虚拟机,在这个虚拟机上跑 ss-tproxy 当然也是可以的(你还别说,真有不少人是这样用的,我在公司也是这么用的)。
切换模式、切换节点不方便,能否避免直接修改文件
这确实是个问题,切换节点需要修改配置文件,切换模式需要修改配置文件,有没有更加简便一些的方式?抱歉,没有。不过因为 ss-tproxy.conf 是一个 shell 脚本,所以我们可以在这里面做些文章。如果你有很多个节点(付费机场一般都是,当然 ss-tproxy 可能更适合自建代理的用户),可以这样做:
# 编辑 /etc/ss-tproxy/ss-tproxy.conf 配置文件,在尾部添加两行:
[ "$2" ] && ln -sf /etc/ss-tproxy/$2.conf /etc/ss-tproxy/node.conf
source /etc/ss-tproxy/node.conf

# 在 /etc/ss-tproxy 下创建节点文件,如 node-1.conf、node-2.conf
cd /etc/ss-tproxy
touch node-1.conf node-2.conf

# 编辑 node-1.conf、node-2.conf,每个文件都代表一个不同的节点
# 以 node-1.conf 为例,使用 gfwlist 模式,服务器为 ss-node-1
mode='gfwlist'
proxy_tproxy='false'
proxy_server=(ss-node-1)
proxy_tcport='60080'
proxy_udport='60080'
proxy_runcmd='systemctl start 1'
proxy_kilcmd='systemctl stop 1'

# node-N.conf 中可以包含 ss-tproxy.conf 中的所有变量,如 node2
mode='chnroute'
proxy_tproxy='false'
proxy_server=(ss-node-2)
proxy_tcport='60080'
proxy_udport='60080'
proxy_runcmd='systemctl start 2'
proxy_kilcmd='systemctl stop 2'
dns_remote='8.8.4.4:53'

# 语法:ss-tproxy start|stop|restart [node-1|node-2|node-N]
# 说明:第 2 个参数其实就是节点文件的名称(注意没有 .conf 后缀名)
ss-tproxy start|stop|restart node-1 # 切换至 /etc/ss-tproxy/node-1.conf
ss-tproxy start|stop|restart node-2 # 切换至 /etc/ss-tproxy/node-2.conf
# 另外注意,这个节点名称的参数是可选的,默认就是上次切换后的那个节点,举个栗子:
ss-tproxy restart node-1 # 切换至 /etc/ss-tproxy/node-1.conf(内部会执行 ln -sf 来切换)
ss-tproxy restart        # 依旧使用 node-1.conf 节点,因为 node.conf 仍然指向 node-1.conf
ss-tproxy restart node-2 # 切换至 /etc/ss-tproxy/node-2.conf(内部会执行 ln -sf 来切换)
ss-tproxy restart        # 依旧使用 node-2.conf 节点,因为 node.conf 仍然指向 node-2.conf
Bash
配置文件以及其它文件的路径是否可更改
当然是可以的,比如你想将 /etc/ss-tproxy 默认目录改为 /opt/ss-tproxy,只需要修改两个地方,一个是 ss-tproxy 脚本的第 3 行,将 main_conf 变量改为 /opt/ss-tproxy/ss-tproxy.conf;另一个是 /opt/ss-tproxy/ss-tproxy.conf 里面的 file_* 变量,改为 /opt/ss-tproxy 目录下的就行了。顺便说一句,ss-tproxy 脚本本身也并不是说一定要放到 /usr/local/bin 目录下,只是我个人喜欢将第三方命令放到这个目录而已。
CentOS 7.x 必须关闭 firewalld 防火墙
当然 RHEL 7.x 也一样,因为 iptables 和 firewalld 都是 netfilter 的用户空间配置工具,而默认情况下,RHEL/CentOS 7.x 会将 firewalld 服务设置为开机自启动,而且还会设置一些防火墙规则,如果你不关闭这个服务,可能在做代理网关时,会遇到无法上网的情况,所以请务必执行 systemctl disable firewalld 来关闭它(然后重启系统)。当然也不是说一定要关闭 firewalld,如果你对 iptables 和 firewalld 比较熟悉,完全可以自由支配它们。
为什么必须使用 bash,而不能使用 sh、zsh 这些 shell
因为 ss-tproxy 脚本里面用到了非常多的 bash 高级重定向,而且有些 built-in 命令和 sh、zsh 的用法不同,无法兼容。
为什么使用 ipip.net 的 chnroute 源,而不是 APNIC 的源
这个怎么说呢,ipip.net 的源我觉得挺好的,用了很久也没啥问题,当然如果需要,你也可以自己改为其它的 chnroute 源,比如改为 APNIC 的大陆地址段列表。但是 ss-tproxy.conf 中并未提供这个选项,该怎么改呢?其实不难,你不需要去修改脚本,只需要在 ss-tproxy.conf 文件末尾添加以下内容(基本框架可以照抄,具体的更新命令可以自定义),ss-tproxy v4.0 已切换至 APNIC 源:
if [ "$1" = 'update-chnroute' ]; then

    # 自定义的更新命令 - begin
    curl -4sSkL 'http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest' | grep ipv4 | grep CN | awk -F'|' '{ printf("%s/%d\n", $4, 32-log($5)/log(2)) }' >$file_chnroute_txt
    # 自定义的更新命令 - end

    echo '-N chnroute hash:net' >$file_chnroute_set; sed -r 's/^.+$/-A chnroute &/' $file_chnroute_txt >>$file_chnroute_set
    reserved_address=('0.0.0.0/8' '10.0.0.0/8' '127.0.0.0/8' '169.254.0.0/16' '172.16.0.0/12' '192.168.0.0/16' '224.0.0.0/4' '240.0.0.0/4')
    for addr in "${reserved_address[@]}"; do echo "$addr" >>$file_chnroute_txt; done
    exit 0
fi
Bash
其实你可以完全模仿这个例子,来重写 ss-tproxy 中定义的全部函数,并且还可以添加额外的 ss-tproxy COMMAND,就看你怎么写了。
如何提高代理的性能(当然仅针对 ss-redir、ssr-redir 这些)
首先,优先选择 C 语言版的 SS/SSR,当然 Python 版也没问题,然后服务器的监听端口我个人觉得 80 和 443 比较好一点,8080 也可以,貌似这些常用端口可以减少 QoS 的影响,当然这只是我个人的一些意见,然后就是修改内核参数,ss-tproxy 主机的 sysctl.conf 以及 vps 的 sysctl.conf 都建议修改一下,最好同步设置。另外我还有一个提速小技巧,那就是在 ss-tproxy 中启动多个 ss-redir、ssr-redir,它们都监听同一个地址和端口(是的你没听错),对于 ss-redir,需要添加一个 --reuse-port 选项来启用端口重用,而对于 ssr-redir,默认情况下就启用了端口重用,而且也没有这个选项。为了简单,这里就以 ss-redir 为例,老规矩,修改 ss-tproxy.conf,添加一个函数,用来启动 N 个 ss-redir 进程:
function start_multiple_ssredir {
    ss_addr='1.2.3.4'
    ss_port='8443'
    ss_method='aes-128-gcm'
    ss_passwd='passwd.for.test'
    (ss-redir -s$ss_addr -p$ss_port -m$ss_method -k$ss_passwd -b0.0.0.0 -l60080 -u --reuse-port </dev/null &>/dev/null &)
    for ((i = 1; i < $1; ++i)); do
        (ss-redir -s$ss_addr -p$ss_port -m$ss_method -k$ss_passwd -b0.0.0.0 -l60080 --reuse-port </dev/null &>/dev/null &)
    done
}
Bash
然后修改我们的启动命令,即 proxy_runcmd,将它改为 proxy_runcmd='start_multiple_ssredir 4',其中 4 可以改为任意正整数,这个数值的意思是启动多少个 ss-redir 进程,一般建议最多启动 CPU 核心数个 ss-redir 进程,太多了性能反而会下降,当然你也可以将它改为 1,此时只会启动一个进程,也就没有所谓的加速了(多个 ss-redir 进程的加速仅针对多线程下载、多终端并发访问的情况,话虽如此,但是效果还是很明显的)。
ss-libev、ssr-libev 如何进行多节点负载均衡(SO_REUSEPORT)
其实非常简单,和上面的多进程 ss-libev、ssr-libev 加速差不多,只不过 ss_addr 不同而已,反正只要这些 ss-redir、ssr-redir 进程监听同一个 addr:port(127.0.0.1:60080),就可以正常使用,实际上 ss-tproxy 并不关心你使用的是哪个 proxy_server,也不关心你使用的是 ss-libev 还是 ssr-libev 还是 v2ray。有必要声明是,通过 SO_REUSEPORT 端口重用实现的负载均衡是“平均分配”的,假设有 4 个进程同时监听 127.0.0.1:60080,那么内核会将客户端连接平均分配给这 4 个进程(每个进程分配到的概率为 25%)。例子:假设我有 2 个 ss 服务器(1.1.1.1、2.2.2.2),2 个 ssr 服务器(3.3.3.3、4.4.4.4),我想同时使用这 4 个服务器(负载均衡),该如何做?
proxy_server=(1.1.1.1 2.2.2.2 3.3.3.3 4.4.4.4)
proxy_runcmd='start_load_balancing'

function start_load_balancing {
    (ss-redir  -s1.1.1.1 -p80  -maes-128-gcm -kpassword -b0.0.0.0 -l60080 -u --reuse-port </dev/null &>/dev/null &)
    (ss-redir  -s2.2.2.2 -p80  -maes-128-gcm -kpassword -b0.0.0.0 -l60080 -u --reuse-port </dev/null &>/dev/null &)
    (ssr-redir -s3.3.3.3 -p443 -maes-128-cfb -kpassword -b0.0.0.0 -l60080 -u              </dev/null &>/dev/null &)
    (ssr-redir -s4.4.4.4 -p443 -maes-128-cfb -kpassword -b0.0.0.0 -l60080 -u              </dev/null &>/dev/null &)
}
Bash
使用“代理网关”模式时,ss-tproxy stop 后,内网主机无法上网
如果 ss-tproxy 主机上没有运行 DNS 服务器(注意,如果代理网关上已经有一个运行在 53 端口上的 dns 服务器,就不要再执行这里的操作了,有些人看都不没看清就直接照抄过去),那就会出现这个问题,因为你的 DNS 设为了 ss-tproxy 主机的 IP,而你执行 stop 操作后,ss-tproxy 上的 dnsmasq 就会被 kill(注意 kill 的是 ss-tproxy 运行的那个 dnsmasq 进程,系统运行的或者你自己运行的 dnsmasq 进程不会被 kill),使得内网主机无法解析 DNS,从而无法上网。解决方法也很简单,修改 ss-tproxy.conf,添加两个钩子函数,post_stop 钩子函数的作用是在 stop 之后启动一个 dnsmasq,监听 0.0.0.0:53 地址,给内网主机提供普通的 DNS 服务,pre_start 钩子函数的作用是在 start 之前 kill 这个 53 端口的 dnsmasq,因为 ss-tproxy 代理启动后,这个 dnsmasq 进程就没有存在的必要了。ss-tproxy v4.0 自带重定向选项,不需要这么麻烦。
function post_stop {
    dnsmasq --conf-file=/dev/null --no-resolv --server=${dns_direct} &>/dev/null
}

function pre_start {
    if [ "$opts_ss_netstat" != "netstat" -a "$(command -v ss &>/dev/null && echo 'true')" ]; then
        ss -lnpu | awk -F, '/:53[ \t].+"dnsmasq"/ {print $(NF-1)}' | awk -F= '{print $2}' | sort | uniq | xargs kill -9 &>/dev/null
    else
        netstat -lnpu | awk -F/ '/:53[ \t].+dnsmasq/ {print $(NF-1)}' | awk '{print $NF}' | sort | uniq | xargs kill -9 &>/dev/null
    fi
}
Bash
为什么使用 ping 命令来解析域名?而不是 dig、nslookup、getent
起初我是使用 getent hosts $domain_name 来解析域名的,但是后来我发现在某些系统上没有 getent 命令,所以我就改为了 ping 来解析。
为什么不支持自定义 dnsmasq 和 chinadns 的端口,只能使用默认的
因为没有这个必要,默认情况下,dnsmasq 监听 60053 端口,chinadns 监听 65353 端口,基本上没哪个进程会监听这两个高位端口,我之所以设置为高位端口也是为了尽可能避免端口冲突问题,在早期版本中,dnsmasq 是监听在 53 端口的,但是我收到了很多关于 dnsmasq 监听端口冲突的反馈,虽然端口冲突问题不难解决,但是我为了一劳永逸,直接将这个端口改为了 60053,从根本上避免了这个问题。ss-tproxy v4.0 支持自定义 dnsmasq、chinadns-ng 监听端口,支持加载外部的 dnsmasq 配置,只要这些配置与 ss-tproxy 使用的配置不冲突就行。
为什么 gfwlist 模式需要用到 perl?而不是使用 sed、awk、grep 这些
因为 Perl 的正则表达式真的很强大,而且没有兼容性问题,sed、awk、grep 基本都有兼容性问题。注:ss-tproxy v4.0 中 Perl 不再是必要依赖。
支持 ss-libev 或 ssr-libev 与 kcptun 一起使用吗
可以,当然需要进行一些特殊改造。ss-libev + kcptun 和 ssr-libev + kcptun 操作起来都差不多,为了简单,这里就以 ss-libev 为例。首先我假设你已经在 vps 上运行了 ss-server 和 kcptun-server,并且 ss-server 监听 0.0.0.0:8053/tcp+udp(监听 0.0.0.0 是为了处理 ss-redir 的 udp relay,因为 kcptun 只支持 tcp 协议的加速),kcptun-server 监听 0.0.0.0:8080/udp(用来加速 ss-redir 的 tcp relay,会被封装为 kcp 协议,使用 udp 传输);当然这些端口都是可以自定义的,我并没有规定一定要使用这两个端口!然后编辑 ss-tproxy.conf,修改这几条配置(假设 vps 地址为 1.2.3.4):
proxy_server=(1.2.3.4)
proxy_runcmd='start_sslibev_kcptun'
proxy_kilcmd='kill -9 $(pidof ss-redir) $(pidof kcptun-client)'
Bash
注意,kcptun 的 client 和 server 程序默认并不是叫做 kcptun-clientkcptun-server,而是一个很难听的名字,如 client_linux_amd64server_linux_amd64,我为了好记,将它改为了 kcptun-client、kcptun-server;如果你没有改这个名字,那么你就需要修改一下上面的 kcptun-client 为对应的 client 二进制文件名,但是我建议你改一下,可以省去不少麻烦。然后你可能注意到了 start_sslibev_kcptun 这个命令,这实际上是我们待会要定义的一个函数,方便启动 ss-redir 和 kcptun-client,而不用在 proxy_runcmd 中写很长的启动命令;然后,在 ss-tproxy.conf 的任意位置添加以下配置(我个人喜欢在文件末尾添加),假设 method 为 aes-128-gcm,password 为 passwd.for.test:
function start_sslibev_kcptun {
    (kcptun-client -l :8080 -r 1.2.3.4:8080 --mode fast2 </dev/null &>/dev/null &)
    (ss-redir -s127.0.0.1 -p8080 -maes-128-gcm -kpasswd.for.test -b0.0.0.0 -l$proxy_tcport    </dev/null &>/dev/null &)
    (ss-redir -s1.2.3.4   -p8053 -maes-128-gcm -kpasswd.for.test -b0.0.0.0 -l$proxy_udport -U </dev/null &>/dev/null &)
}
Bash
延伸:除了 ss、ssr、v2ray,ss-tproxy 还支持其它的吗
首先你要清楚一点:ss-tproxy 并不关心你使用什么代理,它关心的仅仅是 proxy_tcportproxy_udportproxy_serverproxy_dports
  • proxy_tcport:用来接收 TCP 透明代理数据包的端口(REDIRECT/TPROXY),随便填,只要这个端口能处理 TCP 透明代理数据包
  • proxy_udport:用来接收 UDP 透明代理数据包的端口(TPROXY),随便填,只要这个端口能处理 UDP 透明代理数据包
  • proxy_server:告诉 ss-tproxy,哪些目标地址需要放行,显然,与代理有关的目标 IP 必须填上,如 VPS 的 IP/域名
  • proxy_dports:告诉 ss-tproxy,proxy_server 上的哪些端口需要放行,默认是 proxy_server 上的全部端口都放行
如果还不明白,我用通俗的语言来解释这 4 个核心选项的作用:我们将 ss-tproxy 主机看作一根水管,一头是入口(数据包都从这里进去),一头是出口(数据包都从这里出去),里面流的当前就是水了(数据包),那么 proxy_tcport 和 proxy_udport 就是告诉 ss-tproxy,你要将数据包送到哪里去(当然这里的数据包是指需要代理的数据包,放行的流量是不会被送到这个入口的),而 proxy_server 和 proxy_dports 就是告诉 ss-tproxy,这些数据包经过处理后会从哪里出来(也即目标地址、目标端口);管子内部随便你怎么接,你只要能保证数据包能流通,ss-tproxy 就会正确的配置好代理和分流。
这里的管子接法其实就是指底层代理的组合方式,管你是 ss-redir + kcptun,还是 ssr-redir + udp2raw,你只要能保证两头没问题,就可以使用。
为什么 REDIRECT + TPROXY 模式的代理需要监听 0.0.0.0
我在 README 里面强调过,ss-redir 和 ssr-redir 的监听地址需要指定为 0.0.0.0(当然其它代理软件也一样,如果是 REDIRECT + TPROXY 组合方式的话),如果你不指定这个监听地址,或者指定为监听 127.0.0.1,那么你会发现内网主机是无法正常代理上网的,为什么呢?据我个人猜测,应该是 iptables 的 REDIRECT 的问题(听说改为 DNAT 可以避免这个问题,但经过验证貌似也是有问题的),具体什么原因我也不想深究了,没多大意义。
如果只代理 ss-tproxy 本机的流量,不关心其它内网主机,那么完全可以将监听地址改为 127.0.0.1、::1。
如何在 ss-tproxy 中集成 koolproxy 等广告过滤软件
ss-tproxy v4.0 规则变动较大,下面的规则可能不再适用,个人觉得使用浏览器插件进行广告过滤也挺好的。
adbyby 和 koolproxy 是路由固件中常见的两个广告过滤插件,因为 adbyby 不支持 https 过滤,所以这里就以 koolproxy 作为例子讲解。首先打开 https://firmware.koolshare.cn/binary/KoolProxy/,下载对应平台的 koolproxy 二进制文件,然后将它放到 /opt/koolproxy 目录(当然放哪里没要求),并命名为 koolproxy,然后加上可执行权限;进入 /opt/koolproxy 目录,执行 ./koolproxy --cert 生成 koolproxy HTTPS 证书等文件;因为待会我们需要用一个非 root 用户来运行 koolproxy 进程(可以从 /etc/passwd 中随便找一个已存在的用户,比如 daemon),所以我们要先将 /opt/koolproxy 目录的所有者改为 daemon,不然 koolproxy 自动更新广告过滤规则时会遇到权限问题,即执行命令 chown -R daemon:daemon /opt/koolproxy;最后编辑 ss-tproxy.conf,在文件末尾添加这两个钩子函数,就可以实现 koolproxy + ss-tproxy 组合方式的 广告过滤 + 透明代理(广告过滤或多或少都会影响性能,如果设备性能不太好,不建议集成 koolproxy 等广告过滤软件,这种情况下在客户端进行广告过滤会好一些):
function post_start {
    su -s/bin/sh -c'/opt/koolproxy/koolproxy -d -l2 -p65080 -b/opt/koolproxy/data' daemon
    if [ "$proxy_tproxy" = 'true' ]; then
        iptables -t mangle -I SSTP_OUT -m owner ! --uid-owner daemon -p tcp -m multiport --dports 80,443 -j RETURN
        iptables -t nat    -I SSTP_OUT -m owner ! --uid-owner daemon -p tcp -m multiport --dports 80,443 -j REDIRECT --to-ports 65080
        for intranet in "${ipts_intranet[@]}"; do
            iptables -t mangle -I SSTP_PRE -m mark ! --mark $ipts_rt_mark -p tcp -m multiport --dports 80,443 -s $intranet ! -d $intranet -j RETURN
            iptables -t nat    -I SSTP_PRE -m mark ! --mark $ipts_rt_mark -p tcp -m multiport --dports 80,443 -s $intranet ! -d $intranet -j REDIRECT --to-ports 65080
        done
    else
        iptables -t nat -I SSTP_OUT -m owner ! --uid-owner daemon -p tcp -m multiport --dports 80,443 -j REDIRECT --to-ports 65080
        for intranet in "${ipts_intranet[@]}"; do
            iptables -t nat -I SSTP_PRE -s $intranet ! -d $intranet -p tcp -m multiport --dports 80,443 -j REDIRECT --to-ports 65080
        done
    fi
}

function post_stop {
    kill -9 $(pidof koolproxy) &>/dev/null 
}
}
from https://www.zfl9.com/ss-redir.html
------

debian desktop os上的透明代理网关 

ESXi 透明代理虚拟机

0. 项目背景

由于工作的环境是 ESXi ,上面运行着一堆虚拟机,用来做部署方案测试使用。因为要经常访问 GitHub 以及要去 gcr.k8s.io 上拉去镜像;而且在写 Dockerfile build 镜像的时候,也需要去 GitHub 下载 release 包;使用helm初始化时需要的docker 镜像无法pull那个速度比百度网盘还慢啊啊啊啊,气死人。我觉着 GFW 的存在严重第影响了我的工作效率,遂决定搓一个虚拟机来当代理网关,或者叫旁路网关。被需要代理的机器仅仅需要修改网关和 DNS 为透明代理服务器 IP 即可。
题外话:其实用软路由 LEDE/OpenWrt 实现最合适,而且占用资源也极低,但因为使用软路由发生了一次事故,所以就不再用软路由了。那时候刚入职实习,在 ESXi 上装了个 LEDE 软路由,然后办公室的网络就瘫痪了。。

1.实现功能

  1. 透明代理,客户端仅仅需要修改默认网关为上游透明网关即可,无需安装其他代理软件
  2. 国外/国内域名分开解析,解决运营商DNS域名污染问题
  3. 加快客户端访问GitHub、Google等网站速度,clone速度峰值 15MB/S
  4. Docker pull 镜像速度 15MB/S,clone torvalds/linux
  5. 需要代理的内网机器仅仅需要修改网关和 DNS 即可实现透明代理

2. 实现效果

1. wget 下载 GitHub release 上的文件,以 Linux为例

163M Aug 23 21:35 v5.3-rc5.tar.gz 163M 的文件用时不到 30s


3. 实现过程

0. project

主要使用到 ss-tproxy 这个项目,按照项目上的 README 部署部署起来就 ojbk
大佬的博客ss/ssr/v2ray/socks5 透明代理 ,很详细,建议认真读完

1. OS

首先虚拟机的系统我是使用的 Debian 10,使用 netinst 镜像安装好的,当然你也可以使用 Ubuntu ,选择 Debian 是因为 Debian 可以再精简一些,安装后的占用不到 700MB 。至于 Alpine 可能要费点功夫,因为编译需要的包比较麻烦。

2. 安装编译环境和依赖

Debian 和 Ubuntu 的话就一把梭子就行哈
apt update
apt install -y git
apt install -y --no-install-recommends --no-install-suggests  \
    gettext build-essential autoconf libtool  libsodium-dev libmbedtls-dev \
    libpcre3-dev asciidoc xmlto libev-dev libc-ares-dev automake curl wget \
    dnsmasq iproute2 ipset perl haveged gawk
Bash

3. 安装爱国软件

这里根据你的代理软件安装配置好就行,我就剽窃一下 shadowsocks-libev 官方的 wiki
# Installation of libsodium
export LIBSODIUM_VER=1.0.16
wget https://download.libsodium.org/libsodium/releases/libsodium-$LIBSODIUM_VER.tar.gz
tar xvf libsodium-$LIBSODIUM_VER.tar.gz
pushd libsodium-$LIBSODIUM_VER
./configure --prefix=/usr && make
make install
popd
ldconfig

# Installation of MbedTLS
export MBEDTLS_VER=2.6.0
wget https://tls.mbed.org/download/mbedtls-$MBEDTLS_VER-gpl.tgz
tar xvf mbedtls-$MBEDTLS_VER-gpl.tgz
pushd mbedtls-$MBEDTLS_VER
make SHARED=1 CFLAGS="-O2 -fPIC"
make DESTDIR=/usr install
popd
ldconfig

# Installation of shadowsocks-libev
git clone https://github.com/shadowsocks/shadowsocks-libev.git --depth=1
cd shadowsocks-libev
git submodule update --init --recursive
./autogen.sh && ./configure && make
make install
Bash

4. 安装 Chinadns

安装 Chinadns 实现域名分流,国内的域名交给国内的 DNS (119.29.29.29 或 223.6.6.6) 来解析,国外的域名交给 国外的 DNS (8.8.8.8 或 1.1.1.1)来解析
# Installation of chinadns-ng
git clone https://github.com/zfl9/chinadns-ng --depth=1
cd chinadns-ng
make && make install
Bash

5. 安装 ss-tproxy

# Installation of ss-tproxy
git clone https://github.com/zfl9/ss-tproxy --depth=1
cd ss-tproxy
chmod +x ss-tproxy
cp -af ss-tproxy /usr/local/bin
mkdir -p /etc/ss-tproxy
cp -af ss-tproxy.conf gfwlist* chnroute* /etc/ss-tproxy
cp -af ss-tproxy.service /etc/systemd/system
Bash

6. 配置 ss-redir

cat >> /etc/ss.json << EOF
{
    "server":"",
    "mode":"tcp_and_udp",
    "server_port":,
    "local_port":,
    "local_address":"0.0.0.0",
    "reuse_port": true,
    "no_delay": true,
    "password":"",
    "timeout":60,
    "method":"chacha20"
}
EOF
Bash
"server":"", 代理服务器的 IP 
"mode":"tcp_and_udp", 代理协议
"server_port":, 代理服务器端口
"local_port":, 本地端口,要和
"local_address":"0.0.0.0",一定要填,不然只能本机代理,其他及其用不了,坑我一次
"reuse_port": true,
"no_delay": true,
"password":"", 密码
"timeout":60,
"method":"" 加密协议
Ini

7. 配置 ss-tproxy

剽窃一下官方的配置文件 /etc/ss-tproxy/ss-tproxy.conf
## mode
#mode='gfwlist' # gfwlist 分流 (黑名单)
mode='chnroute' # chnroute 分流 (白名单)

## ipv4/6
ipv4='true'     # true:启用ipv4透明代理; false:关闭ipv4透明代理
ipv6='false'    # true:启用ipv6透明代理; false:关闭ipv6透明代理

## tproxy
tproxy='false'  # true:TPROXY+TPROXY; false:REDIRECT+TPROXY

## proxy
proxy_svraddr4=()      # 服务器的 IPv4 地址或域名,允许填写多个服务器地址,空格隔开
proxy_svraddr6=()      # 服务器的 IPv6 地址或域名,允许填写多个服务器地址,空格隔开
proxy_svrport='8080'   # 服务器的外网监听端口,格式同 ipts_proxy_dst_port,不可留空
proxy_tcpport='1080'   # ss/ssr/v2ray 等本机进程的 TCP 监听端口,该端口支持透明代理
proxy_udpport='1080'   # ss/ssr/v2ray 等本机进程的 UDP 监听端口,该端口支持透明代理
proxy_startcmd='cmd1'  # 用于启动本机代理进程的 shell 命令,该命令应该能立即执行完毕
# shadowsocks-libev 的启动命令是 ss-redir -c /etc/ss.json -u  

proxy_stopcmd='cmd2' # 用于关闭本机代理进程的 shell 命令,该命令应该能立即执行完毕

## dnsmasq
dnsmasq_bind_port='53' # dnsmasq 服务器监听端口,见 README。

dnsmasq_cache_size='4096' # DNS 缓存条目,不建议过大,4096 足够

dnsmasq_cache_time='3600' # DNS 缓存时间,单位是秒,最大 3600 秒

dnsmasq_log_enable='false' # 记录详细日志,除非进行调试,否则不建议启用

dnsmasq_log_file='/var/log/dnsmasq.log' # 日志文件,如果不想保存日志可以改为 /dev/null

dnsmasq_conf_dir=() # `--conf-dir` 选项的参数,可以填多个,空格隔开

dnsmasq_conf_file=() # `--conf-file` 选项的参数,可以填多个,空格隔开

## chinadns
chinadns_bind_port='65353' # chinadns-ng 服务器监听端口,通常不用改动

chinadns_verbose='false' # 记录详细日志,除非进行调试,否则不建议启用

chinadns_logfile='/var/log/chinadns.log' # 日志文件,如果不想保存日志可以改为 /dev/null

## dns
dns_direct='119.29.29.29' # 本地 IPv4 DNS,不能指定端口,也可以填组织、公司内部 DNS

dns_direct6='240C::6666' # 本地 IPv6 DNS,不能指定端口,也可以填组织、公司内部 DNS

dns_remote='8.8.8.8#53' # 远程 IPv4 DNS,必须指定端口,提示:访问远程 DNS 会走代理

dns_remote6='2001:4860:4860::8888#53' # 远程 IPv6 DNS,必须指定端口,提示:访问远程 DNS 会走代理

## ipts
ipts_rt_tab='233' # iproute2 路由表名或表 ID,除非产生冲突,否则不建议改动该选项

ipts_rt_mark='0x2333' # iproute2 策略路由的防火墙标记,除非产生冲突,否则不建议改动该选项

ipts_set_snat='false' # 设置 iptables 的 MASQUERADE 规则,布尔值,`true/false`,详见 README

ipts_set_snat6='true' # 设置 ip6tables 的 MASQUERADE 规则,布尔值,`true/false`,详见 README

ipts_intranet=(10.20.172.0/24) # 要代理的 IPv4 内网网段,可填多个,空格隔开,该选项的具体说明请看 README

ipts_intranet6=(fd00::/8) # 要代理的 IPv6 内网网段,可填多个,空格隔开,该选项的具体说明请看 README

ipts_reddns_onstop='true' # ss-tproxy stop 后,是否将其它主机发至本机的 DNS 请求重定向至本地直连 DNS,详见 README

ipts_proxy_dst_port='1:65535' # 目标 IP 的哪些端口走代理,目标 IP 就是黑名单 IP,多个用逗号隔开,冒号为端口范围(含边界)

## opts
opts_ss_netstat='auto' # auto/ss/netstat,用哪个端口检测命令,见 README

opts_overwrite_resolv='false' # true/false,定义如何修改 resolv.conf,见 README

opts_ip_for_check_net='114.114.114.114' # 用来检测外网是否可访问的 IP,该 IP 需要允许 ping

 ## file
file_gfwlist_txt='/etc/ss-tproxy/gfwlist.txt' # gfwlist 黑名单文件 (默认规则)

file_gfwlist_ext='/etc/ss-tproxy/gfwlist.ext' # gfwlist 黑名单文件 (扩展规则)

file_chnroute_set='/etc/ss-tproxy/chnroute.set' # chnroute 地址段文件 (iptables)

file_chnroute6_set='/etc/ss-tproxy/chnroute6.set' # chnroute6 地址段文件 (ip6tables)

file_dnsserver_pid='/etc/ss-tproxy/.dnsserver.pid' # dnsmasq 和 chinadns-ng 的 pid 文件



8. 启动

启动前,需先关闭本机的 dnsmasq 进程,不然会提示 53 端口已占用。

systemctl stop dnsmasq
systemctl disable dnsmasq
systemctl enable haveged # 启动 haveged 用来产生足够多的熵,供加密算法用
ss-tproxy start

Bash

4. 结语

完成以上该能跑起来了,需要注意的是,透明网关要和需要代理的机器在同一网段,不可跨网段,只能在一个 LAN 局域网里。最后祝 GFW 早点倒吧。
完整脚本,在 Debian 10 下测试通过:
#!/bin/bash
apt update
set -xue
apt install -y git
apt install -y --no-install-recommends --no-install-suggests  \
    gettext build-essential autoconf libtool  libsodium-dev libmbedtls-dev \
    libpcre3-dev asciidoc xmlto libev-dev libc-ares-dev automake curl wget \
    dnsmasq iproute2 ipset perl haveged gawk

mkdir -p ~/gateway
cd ~/gateway
# Installation of chinadns-ng
git clone https://github.com/zfl9/chinadns-ng --depth=1
cd chinadns-ng
make && make install
cd ../

# Installation of libsodium
export LIBSODIUM_VER=1.0.16
wget https://download.libsodium.org/libsodium/releases/libsodium-$LIBSODIUM_VER.tar.gz
tar xvf libsodium-$LIBSODIUM_VER.tar.gz
pushd libsodium-$LIBSODIUM_VER
./configure --prefix=/usr && make
make install
popd
ldconfig

# Installation of MbedTLS
export MBEDTLS_VER=2.6.0
wget https://tls.mbed.org/download/mbedtls-$MBEDTLS_VER-gpl.tgz
tar xvf mbedtls-$MBEDTLS_VER-gpl.tgz
pushd mbedtls-$MBEDTLS_VER
make SHARED=1 CFLAGS="-O2 -fPIC"
make DESTDIR=/usr install
popd
ldconfig

# Installation of shadowsocks-libev
git clone https://github.com/shadowsocks/shadowsocks-libev.git --depth=1
cd shadowsocks-libev
git submodule update --init --recursive
./autogen.sh && ./configure && make
make install
cd ../

# Installation of ss-tproxy
git clone https://github.com/zfl9/ss-tproxy --depth=1
cd ss-tproxy
chmod +x ss-tproxy
cp -af ss-tproxy /usr/local/bin
mkdir -p /etc/ss-tproxy
cp -af ss-tproxy.conf gfwlist* chnroute* /etc/ss-tproxy
cp -af ss-tproxy.service /etc/systemd/system

cat >> /etc/ss.json << EOF
{
    "server":"",
    "mode":"tcp_and_udp",
    "server_port":8080,
    "local_port":1080,
    "local_address":"0.0.0.0",
    "reuse_port": true,
    "no_delay": true,
    "password":"",
    "timeout":60,
    "method":"chacha20"
}
EOF

systemctl enable haveged
systemctl start haveged
systemctl disable dnsmasq

-----

基于shadowsocks构建局域网翻墙

理论概述

大致的思路
  1. NAT实现局域网机器与internet联通
  2. dnsmasq+China DNS+ss-tunnel 解决DNS污染
  3. iptables+ipset+shadowsocks实现数据翻墙
大致的网络架构图如下:


基础环境

基本的网络结构是,用一台最小规格的centos服务器用于网关服务器,只是用于网络流量转发。购买EIP访问外部internet,局域网中的所有数据流量通过该服务器进行中转。
局域网中的机器通过NAT转发到网关服务器中的EIP网卡上,从而实现访问internet网络。外部的机器如果要访问局域网内的某个服务端口,采用DNAT映射的方式访问。

dns污染

大致原理,利用dnsmasq在网关服务器上搭建一个dns服务中转&缓存,局域网内所有机器均设置网络为第一dns服务器。
dns服务器的搭建模式,使用dnsmasq作为dns的缓存服务器,dnsmasq默认dns解析采用chinadns,chinadns在解析的使用默认使用两个地址解析,一个使用电信默认的dns服务器114.114.114.114. 另一个使用ss-tunnel转发到境外服务器上的shadowsocks服务器利用境外地址解析,对比后获得是否已经污染,从而获取没有污染的域名地址。

数据穿墙

利用ipset构建一个国内地址池,将局域网内网地址及国内地址直接通过网关服务器转发至公网,其余地址数据通过shadowsocks转发境外的shadowsock服务器。

搭建基础网络环境

搭建dns

安装dnsmasq
yum update
yum install dnsmasq
配置dnsmasq,修改/etc/dnsmasq.conf
no-resolv
server=127.0.0.1#5353   #注意这里的端口是chinadns的监听端口
启动dnsmasq
service dnsmasq start
安装chinadns
下载并解压chinadns,下载地址
编译并安装
./configure && make
src/chinadns -m -c chnroute.txt
cp ./src/chinadns /usr/local/bin/
启动chinadns
chinadns -c ./chinadns-1.3.2/chnroute.txt \
 -m -p 5353 \ #这里的端口号要与前面配置dnsmasq一致 
 -s 114.114.114.114,127.0.0.1:5300 \
        1> /var/log/$NAME.log \
        2> /var/log/$NAME.err.log &
注:其中的5300是后续ss-tunnel的本地监听端口
安装ss-redir
安装依赖库
yum install epel-release -y
yum install gcc gettext libtool automake make pcre-devel asciidoc xmlto \
c-ares-devel libev-devel libsodium-devel mbedtls-devel -y
下载,地址
下载shadowsocks-libdev
编译&安装
# Installation of Libsodium
export LIBSODIUM_VER=1.0.13
wget https://download.libsodium.org/libsodium/releases/libsodium-$LIBSODIUM_VER.tar.gz
tar xvf libsodium-$LIBSODIUM_VER.tar.gz
pushd libsodium-$LIBSODIUM_VER
./configure --prefix=/usr && make
sudo make install
popd
sudo ldconfig

# Installation of MbedTLS
export MBEDTLS_VER=2.6.0
wget https://tls.mbed.org/download/mbedtls-$MBEDTLS_VER-gpl.tgz
tar xvf mbedtls-$MBEDTLS_VER-gpl.tgz
pushd mbedtls-$MBEDTLS_VER
make SHARED=1 CFLAGS=-fPIC
sudo make DESTDIR=/usr install
popd
sudo ldconfig

# Start building
./autogen.sh && ./configure && make
sudo make install
启动ss-tunnel
nohup ss-tunnel -s  -p  -b 0.0.0.0 -l 5300 \
-k  -m aes-256-cfb -L 8.8.8.8:53 -u &
注:server-ip: shadowsocks 服务端Ip地址 server-port: shadowsocks 服务端Ip地址 password: shadowsocks 服务访问密码 5300是ss-tunnel本地监听端口

配置转发

开启转发规则

修改/etc/sysctl.conf
net.ipv4.ip_forward=1
重启服务
sysctl -p

配置IPSET

curl -sL http://f.ip.cn/rt/chnroutes.txt | egrep -v '^$|^#' > cidr_cn
sudo ipset -N cidr_cn hash:net
for i in `cat cidr_cn`; do echo ipset -A cidr_cn $i >> ipset.sh; done
chmod +x ipset.sh && sudo ./ipset.sh
rm -f ipset.cidr_cn.rules
sudo ipset -S > ipset.cidr_cn.rules
sudo cp ./ipset.cidr_cn.rules /etc/ipset.cidr_cn.rules

配置IPTables

iptables -t nat -N shadowsocks
# 保留地址、私有地址、回环地址 不走代理
iptables -t nat -A shadowsocks -d 0/8 -j RETURN
iptables -t nat -A shadowsocks -d 127/8 -j RETURN
iptables -t nat -A shadowsocks -d 10/8 -j RETURN
iptables -t nat -A shadowsocks -d 169.254/16 -j RETURN
iptables -t nat -A shadowsocks -d 172.16/12 -j RETURN
iptables -t nat -A shadowsocks -d 192.168/16 -j RETURN
iptables -t nat -A shadowsocks -d 224/4 -j RETURN
iptables -t nat -A shadowsocks -d 240/4 -j RETURN
# 以下IP为局域网内不走代理的设备IP
iptables -t nat -A shadowsocks -s 10.0.0.111 -j RETURN
# 发往shadowsocks服务器的数据不走代理,否则陷入死循环
# 替换111.111.111.111为你的ss服务器ip/域名
iptables -t nat -A shadowsocks -d  111.111.111.111 -j RETURN   

# 大陆地址不走代理,因为这毫无意义,绕一大圈很费劲的
iptables -t nat -A shadowsocks -m set --match-set cidr_cn dst -j RETURN
# 其余的全部重定向至ss-redir监听端口1080(端口号随意,统一就行)
iptables -t nat -A shadowsocks ! -p icmp -j REDIRECT --to-ports 1080
# OUTPUT链添加一条规则,重定向至shadowsocks链
iptables -t nat -A OUTPUT ! -p icmp -j shadowsocks
iptables -t nat -A PREROUTING ! -p icmp -j shadowsocks

配置默认网关

有两种方式都可以:
  1. 修改默认的route关系
route del default
route add default gw 10.0.0.111 eth0
注: eth0是网卡名称,不同环境可能不一样
  1. 增加一个NAT转发规则:
iptables -t nat -A POSTROUTING -o eth0 -s 10.0.0.0/16 -j SNAT --to 10.0.0.111 


ipset 建议使用apnic集合,刷新的脚本见下:

if [ -f china_cidr.txt ]; then
echo "cidr txt existing, try to delete it..."
rm -f china_cidr.txt
fi

echo "try to get cidr from apnic..."
wget -O- 'http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest' | awk -F\| '/CN\|ipv4/ { printf("%s/%d\n", $4, 32-log($5)/log(2)) }' > china_cidr.txt
echo "cidr generated in china_cidr.txt"

if [ -f china_ipset.sh ]; then
echo "ipset script exiting, try to delete it..."
rm -f china_ipset.sh
fi
------------------------- 


https://github.com/gyhua96/ss_global_proxy

https://github.com/chaosworld/global-proxy-for-linux-with-shadowsocks-libev
----------------------

ipt2socks


utility for converting iptables(redirect/tproxy) to socks5

类似 redsocksredsocks2 的实用工具,用于将 iptables(REDIRECT/TPROXY) 流量转换为 socks5(tcp/udp) 流量。除此之外,ipt2socks 不提供任何非必要的功能(即:KISS 原则,keep it simple, stupid,保持简单和愚蠢)。ipt2socks 可以为仅支持 socks5 传入协议的“本地代理”提供 iptables 透明代理 传入协议的支持,比如 ss/ssr 的 ss-local/ssr-local、v2ray 的 socks5 传入协议、trojan 的 socks5 客户端等等。

简要说明

  • IPv4 和 IPv6 双栈支持,支持 纯 TPROXY 透明代理模式,专为 ss-tproxy 而写。
  • TCP 透明代理提供 REDIRECT、TPROXY 两种方式,UDP 透明代理为 TPROXY 方式。
  • UDP 透明代理支持 Full Cone NAT,前提是后端的 socks5 服务器支持 Full Cone NAT。
  • 多线程 + SO_REUSEPORT 端口重用,每个线程运行各自独立的事件循环,性能提升显著。

如何编译

动态链接 libuv:适用于本地编译,使用包管理器安装 libuv 依赖库即可(如 yum install libuv-devel):
git clone https://github.com/zfl9/ipt2socks
cd ipt2socks
make && sudo make install
ipt2socks 默认安装到 /usr/local/bin/ipt2socks,可安装到其它目录,如 make install DESTDIR=/opt/local/bin
静态链接 libuv:适用于交叉编译,此方式编译出来的 ipt2socks 不依赖任何第三方库,可直接拷贝到目标系统运行:
# 进入某个目录
cd /opt

# 获取 libuv 源码包
libuv_version="1.32.0" # 定义 libuv 版本号
wget https://github.com/libuv/libuv/archive/v$libuv_version.tar.gz -Olibuv-$libuv_version.tar.gz
tar xvf libuv-$libuv_version.tar.gz

# 进入源码目录,编译
cd libuv-$libuv_version
./autogen.sh
./configure --prefix=/opt/libuv --enable-shared=no --enable-static=yes CC="gcc -O3"
make && sudo make install
cd ..

# 获取 ipt2socks 源码
git clone https://github.com/zfl9/ipt2socks

# 进入源码目录,编译
cd ipt2socks
make INCLUDES="-I/opt/libuv/include" LDFLAGS="-L/opt/libuv/lib" && sudo make install

如何运行

# -s 指定 socks5 服务器 ip
# -p 指定 socks5 服务器端口
ipt2socks -s 127.0.0.1 -p 1080
ipt2socks 启动后,配置相应的 iptables 规则即可。这里就不详细介绍了,有兴趣的请戳 ss-tproxy
全部参数
$ ipt2socks --help
usage: ipt2socks <options...>. the existing options are as follows:
 -s, --server-addr <addr>           socks5 server ip address, <required>
 -p, --server-port <port>           socks5 server port number, <required>
 -a, --auth-username <user>         username for socks5 authentication
 -k, --auth-password <passwd>       password for socks5 authentication
 -b, --listen-addr4 <addr>          listen ipv4 address, default: 127.0.0.1
 -B, --listen-addr6 <addr>          listen ipv6 address, default: ::1
 -l, --listen-port <port>           listen port number, default: 60080
 -j, --thread-nums <num>            number of worker threads, default: 1
 -n, --nofile-limit <num>           set nofile limit, maybe need root priv
 -o, --udp-timeout <sec>            udp socket idle timeout, default: 300
 -c, --cache-size <size>            max size of udp lrucache, default: 256
 -f, --buffer-size <size>           buffer size of tcp socket, default: 8192
 -u, --run-user <user>              run the ipt2socks with the specified user
 -G, --graceful                     gracefully close the tcp connection pair
 -R, --redirect                     use redirect instead of tproxy (for tcp)
 -T, --tcp-only                     listen tcp only, aka: disable udp proxy
 -U, --udp-only                     listen udp only, aka: disable tcp proxy
 -4, --ipv4-only                    listen ipv4 only, aka: disable ipv6 proxy
 -6, --ipv6-only                    listen ipv6 only, aka: disable ipv4 proxy
 -v, --verbose                      print verbose log, default: <disabled>
 -V, --version                      print ipt2socks version number and exit
 -h, --help                         print ipt2socks help information and exit
  • -s 选项指定 socks5 服务器的监听地址。
  • -p 选项指定 socks5 服务器的监听端口。
  • -a 选项指定 socks5 服务器的认证用户。
  • -k 选项指定 socks5 服务器的认证密码。
  • -b 选项指定 ipt2socks 的 IPv4 监听地址。
  • -B 选项指定 ipt2socks 的 IPv6 监听地址。
  • -l 选项指定 ipt2socks 的透明代理监听端口。
  • -j 选项指定 ipt2socks 的线程数,默认为 1。
  • -n 选项设置 ipt2socks 进程的 nofile 限制值。
  • -o 选项设置 ipt2socks 的 UDP 空闲超时(秒)。
  • -c 选项设置 ipt2socks 的 UDP 缓存最大大小。
  • -f 选项设置 ipt2socks 的 TCP 接收缓冲区大小。
  • -u 选项设置 ipt2socks 的用户ID,run_as_user
  • -G 选项指示 ipt2socks 优雅地关闭 TCP 代理连接对。
  • -R 选项指示 ipt2socks 使用 REDIRECT 而非 TPROXY。
  • -T 选项指示 ipt2socks 仅启用 TCP 透明代理监听端口。
  • -U 选项指示 ipt2socks 仅启用 UDP 透明代理监听端口。
  • -4 选项指示 ipt2socks 仅启用 IPv4 协议栈的透明代理。
  • -6 选项指示 ipt2socks 仅启用 IPv6 协议栈的透明代理。
  • -v 选项指示 ipt2socks 在运行期间打印详细的日志信息。
  • -V 选项打印 ipt2socks 的版本号,然后退出 ipt2socks 进程。
  • -h 选项打印 ipt2socks 的帮助信息,然后退出 ipt2socks 进程。
以普通用户运行 ipt2socks
  • sudo setcap cap_net_bind_service,cap_net_admin+ep /usr/local/bin/ipt2socks
  • 如果以 root 用户启动 ipt2socks,也可以指定 -u nobody 选项切换至 nobody 用户
Enjoy it!

from https://github.com/zfl9/ipt2socks
----

ip2socks go version, utility for converting iptables(redirect/tproxy) to socks5.

ip2socks-go

ip2socks go version
Test on archlinux and asus ac86u merlin ...
$ ipt2socks-go --help  // no help option and use long style please ... ~~~~
usage: ipt2socks <options...>. the existing options are as follows:
 -s, --server-addr <addr>           socks5 server ip address, <required>
 -p, --server-port <port>           socks5 server port number, <required>
 -a, --auth-username <user>         username for socks5 authentication
 -k, --auth-password <passwd>       password for socks5 authentication
 -b, --listen-addr4 <addr>          listen ipv4 address, default: 127.0.0.1
 -B, --listen-addr6 <addr>          listen ipv6 address, default: ::1
 -l, --listen-port <port>           listen port number, default: 60080
 -j, --thread-nums <num>            number of worker threads, default: 1
 -n, --nofile-limit <num>           set nofile limit, maybe need root priv
 -o, --udp-timeout <sec>            udp socket idle timeout, default: 300
 -c, --cache-size <size>            max size of udp lrucache, default: 256
 -f, --buffer-size <size>           buffer size of tcp socket, default: 8192
 -u, --run-user <user>              run the ipt2socks with the specified user
 -G, --graceful                     gracefully close the tcp connection pair
 -R, --redirect                     use redirect instead of tproxy (for tcp)
 -T, --tcp-only                     listen tcp only, aka: disable udp proxy
 -U, --udp-only                     listen udp only, aka: disable tcp proxy
 -4, --ipv4-only                    listen ipv4 only, aka: disable ipv6 proxy
 -6, --ipv6-only                    listen ipv6 only, aka: disable ipv4 proxy
 -v, --verbose                      print verbose log, default: <disabled>
 -V, --version                      print ipt2socks version number and exit
 -h, --help                         print ipt2socks help information and exit
from https://github.com/lcdbin/ip2socks-go
-----

linux全局智能分流方案,也可用于路由器上.

介绍

这里有两种方案,都可以实现全局智能分流。第一种方案的思路是使用 ipset 载入 chnroute 的 IP 列表并使用 iptables 实现带自动分流国内外流量的全局代理。为什么不用 PAC 呢?因为 PAC 这种东西只对浏览器有用。难道你在浏览器之外就不需要科学上网了吗?反正我是不信的…… <br > 首先将本项目克隆到你的机器上,比如在/home/yang/目录下执行:

$ git clone https://github.com/yangchuansheng/turn-socks-to-vpn.git

方案一

安装相关软件(本教程为Archlinux平台)

  • shadowsocks-libev
  • ipset
$ pacman -S badvpn shadowsocks-libev ipset

配置shadowsocks-libev(略过)

假设shadowsocks配置文件为/etc/shadowsocks1.json

获取中国IP段

保存在cn_rules.conf中

修改启动和关闭脚本

$ vim ss-up.sh
#!/bin/bash

SOCKS_SERVER=$SERVER_IP # SOCKS 服务器的 IP 地址,改成你自己的服务器地址
# Setup the ipset
ipset -N chnroute hash:net maxelem 65536

for ip in $(cat '/home/yang/turn-socks-to-vpn/cn_rules.conf'); do
  ipset add chnroute $ip
done

# 在nat表中新增一个链,名叫:SHADOWSOCKS
iptables -t nat -N SHADOWSOCKS

# Allow connection to the server
iptables -t nat -A SHADOWSOCKS -d $SOCKS_SERVER -j RETURN

# Allow connection to reserved networks
iptables -t nat -A SHADOWSOCKS -d 0.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 10.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 127.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 169.254.0.0/16 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 172.16.0.0/12 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 192.168.0.0/16 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 224.0.0.0/4 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 240.0.0.0/4 -j RETURN

# Allow connection to chinese IPs
iptables -t nat -A SHADOWSOCKS -p tcp -m set --match-set chnroute dst -j RETURN
iptables -t nat -A SHADOWSOCKS -p icmp -m set --match-set chnroute dst -j RETURN

# Redirect to Shadowsocks
# 把1081改成你的shadowsocks本地端口
iptables -t nat -A SHADOWSOCKS -p tcp -j REDIRECT --to-port 1081
iptables -t nat -A SHADOWSOCKS -p icmp -j REDIRECT --to-port 1081

# 将SHADOWSOCKS链中所有的规则追加到OUTPUT链中
iptables -t nat -A OUTPUT -p tcp -j SHADOWSOCKS
iptables -t nat -A OUTPUT -p icmp -j SHADOWSOCKS

  这是在启动 shadowsocks 之前执行的脚本,用来设置 iptables 规则,对全局应用代理并将 chnroute 导入 ipset 来实现自动分流。注意要把服务器 IP 和本地端口相关的代码全部替换成你自己的。   这里就有一个坑了,就是在把 chnroute.txt 加入 ipset 的时候。因为 chnroute.txt 是一个 IP 段列表,而中国持有的 IP 数量上还是比较大的,所以如果使用 hash:ip 来导入的话会使内存溢出。我在第二次重新配置的时候就撞进了这个大坑……   但是你也不能尝试把整个列表导入 iptables。虽然导入 iptables 不会导致内存溢出,但是 iptables 是线性查表,即使你全部导入进去,也会因为低下的性能而抓狂。 <br > ss-down.sh是用来清除上述规则的脚本,不用作任何修改 <br > 接着执行

$ chmod +x ss-up.sh
$ chmod +x ss-down.sh

配置ss-redir服务

首先,默认的 ss-local 并不能用来作为 iptables 流量转发的目标,因为它是 socks5 代理而非透明代理。我们至少要把 systemd 执行的程序改成 ss-redir。其次,上述两个脚本还不能自动执行,必须让 systemd 分别在启动 shadowsocks 之前和关闭之后将脚本执行,这样才能自动配置好 iptables 规则。

$ vim /usr/lib/systemd/system/shadowsocks-libev@.service
[Unit]
Description=Shadowsocks-Libev Client Service
After=network.target

[Service]
User=root
CapabilityBoundingSet=~CAP_SYS_ADMIN
ExecStart=
ExecStartPre=/home/yang/turn-socks-to-vpn/ss-up.sh
ExecStart=/usr/bin/ss-redir -u -c /etc/%i.json
ExecStopPost=/home/yang/turn-socks-to-vpn/ss-down.sh

[Install]
WantedBy=multi-user.target

然后启动服务

$ systemctl start shadowsocks-libev@shadowsocks1

开机自启

$ systemctl enable shadowsocks-libev@shadowsocks1

六、配置ss-tunnel服务,用来提供本地DNS解析

$ vim /usr/lib/systemd/system/shadowsocks-libev-tunnel@.service
[Unit]
Description=Shadowsocks-Libev Client Service Tunnel Mode
After=network.target

[Service]
Type=simple
User=nobody
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
ExecStart=/usr/bin/ss-tunnel -c /etc/%i.json -l 53 -L 8.8.8.8:53 -u

[Install]
WantedBy=multi-user.target

启动服务

$ systemctl start shadowsocks-libev-tunnel@shadowsocks1

开机自启

$ systemctl enable shadowsocks-libev-tunnel@shadowsocks1

配置系统 DNS 服务器设置

  • 可参见 https://developers.google.com/speed/public-dns/docs/using 中 Changing your DNS servers settings 中 Linux 一节

  • 图形界面以 GNOME 3 为例:

  • 打开所有程序列表,并 -> 设置 – 硬件分类 – 网络

  • 如果要对当前的网络配置进行编辑 -> 单击齿轮按钮

  • 选中 IPv4

  • DNS 栏目中,将自动拨向关闭

  • 在服务器中填入 127.0.0.1 (或103.214.195.99:7300)并应用

  • 选中 IPv6

  • DNS 栏目中,将自动拨向关闭

  • 在服务器中填入 ::1 并应用

  • 请务必确保只填入这两个地址,填入其它地址可能会导致系统选择其它 DNS 服务器绕过程序的代理

  • 重启网络连接

  • 直接修改系统文件修改 DNS 服务器设置:

  • 自动获取地址(DHCP)时:

  • 以 root 权限进入 /etc/dhcp 或 /etc/dhcp3 目录(视乎 dhclient.conf 文件位置)

  • 直接修改 dhclient.conf 文件,修改或添加 prepend domain-name-servers 一项即可

  • 如果 prepend domain-name-servers 一项被 # 注释则需要把注释去掉以使配置生效,不需要添加新的条目

  • dhclient.conf 文件可能存在多个 prepend domain-name-servers 项,是各个网络接口的配置项目,直接修改总的配置项目即可

  • 使用 service network(/networking) restart 或 ifdown/ifup 或 ifconfig stop/start 重启网络服务/网络端口

  • 非自动获取地址(DHCP)时:

  • 以 root 权限进入 /etc 目录

  • 直接修改 resolv.conf 文件里的 nameserver 即可

  • 如果重启后配置被覆盖,则需要修改或新建 /etc/resolvconf/resolv.conf.d 文件,内容和 resolv.conf 一样

  • 使用 service network(/networking) restart 或 ifdown/ifup 或 ifconfig stop/start 重启网络服务/网络端口

打开流量转发

$ cat /etc/sysctl.d/30-ipforward.conf
net.ipv4.ip_forward=1

net.ipv6.conf.all.forwarding = 1

net.ipv4.tcp_congestion_control=westwood

net.ipv4.tcp_syn_retries = 5

net.ipv4.tcp_synack_retries = 5

编辑完成后,执行以下命令使变动立即生效

 $ sysctl -p

方案一固然可以实现全局智能分流,可这里有一个问题,它并不能让连接到此电脑上的设备也实现智能分流,也就是说,它还不能当成一个翻墙路由器使用,下面我们介绍的方案二便可以解决这个问题。前面的步骤大致相似,到后面略有不同。

方案二

安装相关软件(本教程为Archlinux平台)

  • badvpn
  • pdnsd
  • shadowsocks
$ pacman -S badvpn pdnsd shadowsocks

配置shadowsocks(略过)

假设shadowsocks配置文件为/etc/shadowsocks1.json

获取中国IP段

保存在cn_rules.conf中

修改iptables启动和关闭脚本

$ vim sstunnel-up.sh
#!/bin/bash
# 后面将会用ss-tunnel开启本地dns解析服务,所以将本地dns的udp请求转发到pdnsd的dns端口
# 至于为什么多此一举,而不直接将pdnsd的本地端口设置为53,是因为53端口已经被污染了,所以通过此方法来欺骗GFW
iptables -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to 127.0.0.1:10053

<br > sstunnel-down.sh是用来清除上述规则的脚本

#!/bin/bash
iptables -t nat -F OUTPUT

配置pdnsd服务,用来提供本地DNS解析

将pdnsd.conf复制到/etc目录下,然后修改pdnsd.service

$ vim /usr/lib/systemd/system/pdnsd.service
[Unit]
Description=proxy name server
Wants=network-online.target
After=network-online.target

[Service]
ExecStartPre=/home/yang/turn-socks-to-vpn/sstunnel-up.sh
ExecStart=/usr/bin/pdnsd
ExecStopPost=/home/yang/turn-socks-to-vpn/sstunnel-down.sh

[Install]
WantedBy=multi-user.target

启动服务

$ systemctl start pdnsd

开机自启

$ systemctl enable pdnsd

配置系统 DNS 服务器设置

  • 可参见 https://developers.google.com/speed/public-dns/docs/using 中 Changing your DNS servers settings 中 Linux 一节

  • 图形界面以 GNOME 3 为例:

  • 打开所有程序列表,并 -> 设置 – 硬件分类 – 网络

  • 如果要对当前的网络配置进行编辑 -> 单击齿轮按钮

  • 选中 IPv4

  • DNS 栏目中,将自动拨向关闭

  • 在服务器中填入 127.0.0.1 (或103.214.195.99:7300)并应用

  • 选中 IPv6

  • DNS 栏目中,将自动拨向关闭

  • 在服务器中填入 ::1 并应用

  • 请务必确保只填入这两个地址,填入其它地址可能会导致系统选择其它 DNS 服务器绕过程序的代理

  • 重启网络连接

  • 直接修改系统文件修改 DNS 服务器设置:

  • 自动获取地址(DHCP)时:

  • 以 root 权限进入 /etc/dhcp 或 /etc/dhcp3 目录(视乎 dhclient.conf 文件位置)

  • 直接修改 dhclient.conf 文件,修改或添加 prepend domain-name-servers 一项即可

  • 如果 prepend domain-name-servers 一项被 # 注释则需要把注释去掉以使配置生效,不需要添加新的条目

  • dhclient.conf 文件可能存在多个 prepend domain-name-servers 项,是各个网络接口的配置项目,直接修改总的配置项目即可

  • 使用 service network(/networking) restart 或 ifdown/ifup 或 ifconfig stop/start 重启网络服务/网络端口

  • 非自动获取地址(DHCP)时:

  • 以 root 权限进入 /etc 目录

  • 直接修改 resolv.conf 文件里的 nameserver 即可

  • 如果重启后配置被覆盖,则需要修改或新建 /etc/resolvconf/resolv.conf.d 文件,内容和 resolv.conf 一样

  • 使用 service network(/networking) restart 或 ifdown/ifup 或 ifconfig stop/start 重启网络服务/网络端口

修改路由表启动和终止脚本

将socksfwd复制到/usr/local/bin目录下,然后修改相关参数

$ vim /usr/local/bin/socksfwd
#!/bin/bash
SOCKS_SERVER=$SERVER_IP # SOCKS 服务器的 IP 地址
SOCKS_PORT=1081 # 本地SOCKS 服务器的端口
GATEWAY_IP=172.16.68.254 # 家用网关(路由器)的 IP 地址
TUN_NETWORK_DEV=tun0 # 选一个不冲突的 tun 设备号
TUN_NETWORK_PREFIX=10.0.0 # 选一个不冲突的内网 IP 段的前缀


start_fwd() {
ip tuntap del dev "$TUN_NETWORK_DEV" mode tun
# 添加虚拟网卡
ip tuntap add dev "$TUN_NETWORK_DEV" mode tun
# 给虚拟网卡绑定IP地址
ip addr add "$TUN_NETWORK_PREFIX.1/24" dev "$TUN_NETWORK_DEV"
# 启动虚拟网卡
ip link set "$TUN_NETWORK_DEV" up
ip route del default via "$GATEWAY_IP"
ip route add "$SOCKS_SERVER" via "$GATEWAY_IP"
# 特殊ip段走家用网关(路由器)的 IP 地址(如局域网联机)
# ip route add "172.16.39.0/24" via "$GATEWAY_IP"
# 国内网段走家用网关(路由器)的 IP 地址
for i in $(cat /home/yang/turn-socks-to-vpn/cn_rules.conf)
do
ip route add "$i" via "$GATEWAY_IP"
done
# 将默认网关设为虚拟网卡的IP地址
ip route add 0.0.0.0/1 via "$TUN_NETWORK_PREFIX.1"
ip route add 128.0.0.0/1 via "$TUN_NETWORK_PREFIX.1"
# 将socks5转为vpn
badvpn-tun2socks --tundev "$TUN_NETWORK_DEV" --netif-ipaddr "$TUN_NETWORK_PREFIX.2" --netif-netmask 255.255.255.0 --socks-server-addr "127.0.0.1:$SOCKS_PORT"
TUN2SOCKS_PID="$!"
}


stop_fwd() {
ip route del 128.0.0.0/1 via "$TUN_NETWORK_PREFIX.1"
ip route del 0.0.0.0/1 via "$TUN_NETWORK_PREFIX.1"
for i in $(cat /home/yang/bin/路由表/cn_rules.conf)
do
ip route del "$i" via "$GATEWAY_IP"
done
ip route del "172.16.39.0/24" via "$GATEWAY_IP"
ip route del "$SOCKS_SERVER" via "$GATEWAY_IP"
ip route add default via "$GATEWAY_IP"
ip link set "$TUN_NETWORK_DEV" down
ip addr del "$TUN_NETWORK_PREFIX.1/24" dev "$TUN_NETWORK_DEV"
ip tuntap del dev "$TUN_NETWORK_DEV" mode tun
}



start_fwd
trap stop_fwd INT TERM
wait "$TUN2SOCKS_PID"

<br > 将socksfwd.service复制到/usr/lib/systemd/system/目录下,然后启动服务

$ systemctl start socksfwd

开机自启

$ systemctl enable socksfwd

打开流量转发

将30-ipforward.conf复制到/etc/sysctl.d/目录下,然后执行以下命令使变动立即生效

$ sysctl -p
from   https://github.com/houzhenggang/turn-socks-to-vpn
---
https://guide.v2fly.org/app/transparent_proxy.html
https://guide.v2fly.org/app/tproxy.html
-------

Introduction

This is a docker image for bypass transparent proxy of v2ray, using techniques in this article: https://guide.v2fly.org/app/tproxy.html

The image uses network namespace to isolate ip route and iptables settings and accelerate deployment.

It should be deployed on a device other than router, like Raspberry Pi or NAS, using Docker network that is attched (bridged) to host physical network, there are several choices:

  • macvlan: easy to deploy, but due to the limitation of macvlan, the host itself cannot connect to the container, so it can't use the transparent proxy
  • bridge to host: unfortunately, docker has a very poor support for bridging to host network, it's feasible, but very tricky
  • host: Docker host network just doesn't create network namespace and the script in the image will pollute host network namespace without cleaning it up, so host network is not recommended

Deployment

Suppose the device IP address is 192.168.1.250, and the gateway is 192.168.1.254, the container's address will be 192.168.1.251.

First, create a macvlan:

docker network create -d macvlan \
    --subnet=192.168.1.0/24 \
    --ip-range=192.168.1.251/32 \
    --gateway=192.168.1.254 \
    -o parent=ens3 v2ray

Then start the container:

docker run -d \
	--name=v2ray \
	--privileged=true \
	--network v2ray \
	--env VMESS_SERVER=<VMESS_SERVER> \
	--env VMESS_PORT=<VMESS_PORT> \
	--env VMESS_ID=<VMESS_ID> \
	--env NETWORK=192.168.1.0/24 \
	--env ADDRESS=192.168.1.251/24 \
	--env GATEWAY=192.168.1.254 \
	core2duo/v2ray-bypass:200202

Testing

After the container is running, change the default route and DNS namespace to the container address in client:

ip route change default via 192.168.1.251
echo "nameserver 192.168.1.251" > /etc/resolve.conf

The client should work then.

Work with router

Use the container as a default route is not a good idea, as all the traffic will go through v2ray and bring extra performance pressure on it. The better way is to collaborate with the router, redirect the traffic to the proxy only when it's necessary.

The solution is to use dnsmasq and ipset. You will need a dnsmasq configuration that indicates which domain will use v2ray DNS and will be added into ipset, something like this:

server=/.google.com/192.168.1.251#53
ipset=/.google.com/gfwlist

See gfwlist2dnsmasq for a tool that can automatically generate the configuration.

Then on router, create the ipset if it doesn't exist.

ipset create gfwlist hash:ip

Use iptables to mark traffics to ip that is in the ipset and route them to the proxy:

ip rule add pref 10 fwmark 0xAA lookup 10
ip route add default via 192.168.1.251 table 10
iptables -t mangle -A PREROUTING -m set --match-set gfwlist dst -j MARK --set-mark 0xAA

Done, all devices connected to the router should be able to use the transparent proxy now.

from https://github.com/core2duoe6420/v2ray-tproxy 

--------

Linux Transparent Proxy library for Golang.

Golang TProxy GoDoc Go Report Card

Golang TProxy provides an easy to use wrapper for the Linux Transparent Proxy functionality.

Transparent Proxy (TProxy for short) provides the ability to transparently proxy traffic through a userland program without the need for conntrack overhead caused by using NAT to force the traffic into the proxy.

Another feature of TProxy is the ability to connect to remote hosts using the same client information as the original client making the connection. For example, if the connection 10.0.0.1:50073 -> 8.8.8.8:80 was intercepted, the service could make a connection to 8.8.8.8:80 pretending to come from 10.0.0.1:50073.

The linux kernel and IPTables handle diverting the packets back into the proxy for those remote connections by matching incoming packets to any locally bound sockets with the same details.

This is done in three steps. (Please note, this is from my understanding of how it works, which may be wrong in some places, so please correct me if I have described something wrong)

Step 1 - Binding a listener socket with the IP_TRANSPARENT socket option

Preparing a socket to receive connections with TProxy is really no different than what is normally done when setting up a socket to listen for connections. The only difference in the process is before the socket is bound, the IP_TRANSPARENT socket option.

syscall.SetsockoptInt(fileDescriptor, syscall.SOL_IP, syscall.IP_TRANSPARENT, 1)

Step 2 - Setting the IP_TRANSPARENT socket option on outbound connections

Same goes for making connections to a remote host pretending to be the client, the IP_TRANSPARENT socket option is set and the Linux kernel will allow the bind so along as a connection was intercepted with those details being used for the bind

Step 3 - Adding IPTables and routing rules to redirect traffic in both directions

Finally IPTables and routing rules need to be setup to tell Linux to redirect the desired traffic to the proxy application.

First make a new chain in the mangle table called DIVERT and add a rule to direct any TCP traffic with a matching local socket to the DIVERT chain

iptables -t mangle -N DIVERT
iptables -t mangle -A PREROUTING -p tcp -m socket -j DIVERT

Then in the DIVERT chain add rules to add routing mark of 1 to packets in the DIVERT chain and accept the packets

iptables -t mangle -A DIVERT -j MARK --set-mark 1
iptables -t mangle -A DIVERT -j ACCEPT

And add routing rules to direct traffic with mark 1 to the local loopback device so the Linux kernal can pipe the traffic into the existing socket.

ip rule add fwmark 1 lookup 100
ip route add local 0.0.0.0/0 dev lo table 100

Finally add a IPTables rule to catch new traffic on any desired port and send it to the TProxy server

iptables -t mangle -A PREROUTING -p tcp --dport 80 -j TPROXY --tproxy-mark 0x1/0x1 --on-port 8080

To test this out and see it work, try running the example in example/tproxy_example.go on a virtual machine and route some traffic through it.

from  https://github.com/LiamHaworth/go-tproxy