斐讯N1 – 利用redsocks+ss实现全局翻墙

我们写程序难免要下载依赖库,然而大部分依赖库都托管在github之类的地方,并不是所有的依赖管理工具都支持配置代理,所以急需一种全局翻墙上网的方式。

大家都知道ss翻墙,它本质是socks5协议,socks5是4层代理协议(TCP/UDP),只不过在流量跨境的时候加了一层混淆。

ss平时浏览浏览网页是挺好用的,因为浏览器支持socks5协议封包发给本地的ss agent,可是并不是所有的软件都支持socks5,那么借助ss解决普遍的翻墙上网需求就很困难。

借助redsocks透明封包

斐讯N1是一个linux盒子,上面跑了ss的local端。

为了可以借助ss全局tcp翻墙,我需要把N1作为电脑的默认网关,这样所有上网流量都会通过N1。

在N1上,需要想办法把经过N1的原始的TCP流量拦截下来,识别其目标地址,将TCP流量封装到socks5协议中(此时相当于socks5客户端),然后将数据包的目标地址改为N1本机运行的ss local端,由ss local将socks5数据混淆发往境外。

直接使用iptables进行拦截篡改并不能实现识别目标地址与充当socks5客户端的能力,所以需要一个工作在linux内核层的特殊程序来帮我们搞定,这个程序就叫做redsocks。

我们只需要通过iptables将目标地址为外网IP的TCP流量改写到本机的redsocks端口,那么redsocks会在内核层解析到原始的TCP目标IP,然后在应用层进行socks5流量封装。

redsocks封装后的socks5流量将会在应用层转发给本机部署的ss local端,由ss local端完成进一步混淆并送往境外ss server端。

其实redsocks就是一个透明的socks5客户端,这样就不需要我们自己运行socks5客户端了。

优化Linux内核参数

redsocks作为一个代理程序,在实践过程中发现存在不稳定问题,netstat -tanlp观察到大量CLOSE_WAIT的端口,确认是redsocks没有关闭SOCKET导致了fd泄露,最终拒绝了服务。

对redsocks源代码作了简单的分析,确认redsocks作为一个TCP透明代理是没有应用层心跳的,所以当TCP连接断开时,socket层面无法感知到连接断开,所以导致逻辑上的fd泄露。

TCP协议默认有keepalive心跳,但是默认是2个小时,所以异常的TCP链接会在2小时后开始反馈到socket层面,fd才会逐渐被关闭。

解决这个问题的方法只有从linux内核参数入手,调短TCP keepalive的间隔,尽快让内核探测到连接断开的实事。

所以,下面跟随我做一些简单优化即可。

编辑/etc/sysctl.conf,增加如下配置(主要是TCP心跳优化,顺便调大了系统级fd限制,另外开启了bbr):

编辑 /etc/security/limits.conf,增加如下配置(调达了用户级fd限制):

然后重启即可。

安装redsocks

注意,原版本的redsocks存在严重BUG,运行一段时间就会hung死,所以大家不要用apt安装。

我们需要使用国人维护的redsocks2版本,它修复了大量bug并增加了一些新的特性,广泛用于openwrt镜像中,稳定性应该是有保障的。

项目地址:https://github.com/semigodking/redsocks

首先下载代码:

然后安装编译依赖libevent和openssl:

然后执行编译:

将二进制挪到标准路径下:

拷贝一份配置文件模板并挪到标准路径下:

然后对其进行如下修改:

  • bind参数:redsocks监听在本机所有网卡的12345端口(一定要改成0.0.0.0,否则后续iptables规则无法奏效),后续将在iptables规则中将TCP流量拦截到这个端口。
  • relay参数:配置指向本机运行的ss local端地址。

后续配置全部注释即可:

这些注释掉的部分的功能说明如下,你可以根据情况自己调节:

  • redudp:SOCKS5也支持UDP代理,但是因为SS对udp支持不是太好,所以就不配置UDP透明SOCKS5封包了。
  • tcpdns:redsocks2内置了一个UDP DNS转给TCP upstream DNS的功能,我也不用了。
  • autoproxy:redsocks支持TCP直连探测IP,如果可以联通那就不走SS翻墙了,以便实现国内网站加速,但是因为后续是我会iptables拦截全局TCP流量,所以即便打开autoproxy也是无效的。
  • ipcache:与autoproxy相关,一旦探测直连失败就会加入黑名单,后续一段时间就不会再直连探测,而是直接走proxy。

最后配置一下Redsocks开机启动:

然后执行:

配置iptables流量拦截

现在redsocks已经部署完成,接下来只要把发往外网的TCP流量拦截给redsocks,它就可以帮我们在应用层完成SOCKS5封包并发往本机的SS local端了。

但是外网的IP段很多,配置iptables很难覆盖,所以我们可以通过排除法来排除掉内网IP段,达到同样的效果。

首先在nat表里配置一个自定义链叫做REDSOCKS(名字不重要),把拦截和转发规则写在这里面:

然后在这个链里跳过所有的内网IP,排除后的就是发往外网IP的流量,将他们拦截给redsocks进行socks5封包即可。

RETURN就是指停止遍历该链的后续规则,也就是不拦截。

如果不属于任何一个内网IP段,那么就REDIRECT到redsocks的12345端口。

注意:网上有人反馈说内网电脑无法上网,而从N1盒子本机可以上网,这是因为REDIRECT规则只能在当前网口完成端口改写,所以大家一定要注意让redsocks监听在0.0.0.0而不是localhost,这样内网流量才能顺利流入redsocks。(iptables这一块的坑点请见:https://qiuqi.github.io/2017/06/01/iptables-dnat%E5%92%8Credirect%E5%A4%B1%E6%95%88%E7%9A%84%E5%8E%9F%E5%9B%A0/

注意:在倒数第2条规则,是我境外的ss server端IP,即发往该IP的流量虽然属于外网但是不能拦截,否则ss local发出去的翻墙包又会被拦截,再次被发往redsocks,造成递归封装。

最后呢,我们要让从内网发到N1的TCP流量,以及从N1本机发起的TCP流量,均经过REDSOCKS链的判定处理:

默认iptables规则重启后会清空,我们需要安装一个工具来保存iptables规则,并且自动帮我们开机加载规则:

首次安装会询问你是否立即保存当前的iptables规则,选择y即可。

后续修改了iptables,则需要重新保存执行:

验证效果

从N1本机验证OUTPUT链是否工作正常:

这里IP地址是我从境外解析到的谷歌IP,可见访问正常。

为什么要手动指定IP呢?因为现在是通过TCP透明拦截封包的方式使用socks5,所以域名的解析是本地完成的(实际上socks5支持dns远程解析),这就可能被DNS污染解析到错误IP。

至于如何解决DNS污染问题,后续我会再写博客说明如何自己架设一个不会被污染的本地DNS服务。

为了验证PREROUTING链是否正常翻墙,可以将内网电脑的默认网关改为N1,这样电脑所有流量都会过N1,因此就实现了电脑全局TCP翻墙。

当然这里还有一个坑,就是目前N1只拦截了目标IP是外网的TCP流量到本机redsocks,除此之外的流量仍旧是需要走N1路由表直接forward出去的,因此我们别忘记了打开N1上的ip_forward:

为了持久化配置,需要修改/etc/sysctl.conf文件,将如下的行删除注释:

另外,我们不需要在POSTROUTING配置SNAT,因为redsocks流量是应用层处理的,而非redsocks通路的回程流量可以直接发回到真实内网源IP机器而不需要再经过N1盒子socks5解密,这样少一层forward转发效率更高。

最后

如果仅仅是为了全局翻墙,可以考虑利用上述手段实现,而不用折腾openwrt。

整个过程需要对iptables和网络有一定理解,可以看一下我的另外一篇博客《2小时学会iptables》

如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~