借助openwrt路由器实现移动网络下的智能翻墙

前提

该方案需要你拥有一台已经刷成openwrt且安装了shadowsocks-libev-spec的路由器,路由器已实现透明代理翻墙,且根据中国ip列表自动分流,最后已部署好pptp vpn server,这些具体实现都不在本文讨论范围之内,以下假定已满足该条件。

背景及需求

当我们通过该op路由器联网时可以很方便、零成本的实现翻墙,即使在有Wi-Fi的地方我们也可以通过前文所述的pac来自动翻墙,但是当我们处于移动网络下时如何方便快捷的翻墙是个问题。我们可以制作运营商描述文件,但是这个方法体验不是太好,也有很多已知的问题。

所以在移动网络下最方便的解决方案是vpn技术,但是vpn智能分流是靠路由表来实现,以Anyconnect vpn为例,首先用路由表做分流,本身就不太精确,而且由于Anyconnect vpn的限制,翻墙后速度最多也只有2Mbps。并且,服务端ocserv默认限制路由表最长为64条,即使修改过源代码重新编译,客户端也只能接受200多条,限制十分明显。而且Anyconnect vpn部署繁杂,太耗时间。

自然我们就会想到,能否通过openwrt路由器提供的pptp vpn服务,来远程连接到路由器,又在路由器上按照国内ip智能分流呢?这样虽然我们所有的流量还是先走的vpn,但是由于这个国内节点离我们相对较近,所以对于访问国内、国外网站的速度影响很小,实际测试下来,正常的网页浏览几乎感受不到。

而且,随着家庭宽带的提速,下行和上行都有很好的表现,所以,这里提供的方案是一个很好的、适合在移动网络下翻墙的解决方案。但是如果是大流量、实时性强的应用,比如视频,则取决于家庭带宽的下行和上行表现,尤其是上行带宽是对vpn的主要影响因素。

同样,该方案适合任何在国内部署了shadowsocks client节点的server,只需要再部署pptp vpn server(或者任何vpn技术)即可。

ss-redir透明代理分析

直观感受似乎是vpn拨上去了就可以按照openwrt设置的shadowsocks那样智能翻墙了,但其实你会发现,只有国内网站可以打开,Google依然无法打开。所以我们先分析一下ss-redir透明代理在iptables中添加的规则。

在openwrt上面我们查看一下nat表,这里摘选关键部分如下:

1
2
3
4
5
6
root@OpenWrt:~# iptables -t nat -nL
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
SS_SPEC_WAN_AC tcp -- 0.0.0.0/0 0.0.0.0/0 ! match-set ss_spec_lan_ac src /* _SS_SPEC_RULE_ */
REDIRECT tcp -- 192.168.1.128/25 0.0.0.0/0 tcp dpt:80 redir ports 8118
delegate_prerouting all -- 0.0.0.0/0 0.0.0.0/0

PREROUTING链中,前两条正是我们需要分析的。PREROUTING链是在数据包进入nat表后,未查询路由表前会进入该链处理。这里能看到使用了match-set这个关键字,其实使用了ipset这个模块。这个模块可以按照网段、ip、掩码、端口号以及接口等规则划入一个set,再在iptables中用作匹配的项。

我们可以通过ipset list | grep 'Name'来看到openwrt上已经配置来哪些set:

1
2
3
4
5
root@OpenWrt:~# ipset list | grep Name
Name: china
Name: local
Name: ss_spec_wan_ac
Name: ss_spec_lan_ac

看到了我们感兴趣的ss_spec_wan_acss_spec_lan_ac。可以通过ipset list ss_spec_lan_ac来查看具体的内容:

1
2
3
4
5
6
7
8
9
root@OpenWrt:~# ipset list ss_spec_lan_ac
Name: ss_spec_lan_ac
Type: hash:net
Revision: 4
Header: family inet hashsize 1024 maxelem 65536
Size in memory: 8536
References: 3
Members:
192.168.1.99

这个set其实就是在shadowsocks的luci配置界面中,接口-LAN->内网访问控制中被透明代理排除的局域网ip。同样,ss_spec_wan_ac则是中国的ip网段。这里我们就不详细去看了。

所以,上述PREROUTING链中的第一条rule就是:将所有的tcp且源ip地址不在ss_spec_lan_ac这个set中的流量转到SS_SPEC_WAN_AC这个链去处理。

完整来看一下 SS_SPEC_WAN_ACSS_SPEC_WAN_FW 这两条链的内容:

1
2
3
4
5
6
7
8
Chain SS_SPEC_WAN_AC (3 references)
target prot opt source destination
RETURN all -- 0.0.0.0/0 0.0.0.0/0 match-set ss_spec_wan_ac dst
SS_SPEC_WAN_FW all -- 0.0.0.0/0 0.0.0.0/0
Chain SS_SPEC_WAN_FW (1 references)
target prot opt source destination
REDIRECT tcp -- 0.0.0.0/0 0.0.0.0/0 redir ports 1080

WAN_AC把目的ip在ss_spec_wan_ac这个set中的所有流量RETURN,也即这些国内的流量不作处理。剩下的流量全部转到SS_SPEC_WAN_FW这个链去处理。这个链中我们可以看到,针对所有的tcp流量全部重定向到1080端口,也即shadowsocks侦听的本地端口,REDIRECT是一种特殊的DNAT

看着似乎都正常,可是为什么我们的vpn流量就无法从shadowsocks走呢?
这是因为最后一条REDIRECT的规则它针对的流量类型是tcp,而我们pptp vpn的流量是GRE,是直接封装进ip包头的,并不属于tcp协议。

感谢 浪打浪哥 的提醒,我先前的理解有误。之前我误以为是协议的原因,GRE的流量不会匹配到上述将TCP流量重定向到1080端口的规则,然而在我将下文中加上的规则

1
iptables -t nat -I PREROUTING -p all -s 10.0.0.0/24 -m set ! --match-set ss_spec_lan_ac src -j SS_SPEC_WAN_AC

中的 -p all 替换为 -p 47,即指定GRE流量时,发现客户端并不能翻墙。
使用命令iptables -t nat -nL -vx | head 看到这条规则并没有匹配到任何流量。
ssvpn
然而当我再更改为-p tcp时,客户端又可以翻墙了,再查看统计,确实匹配到了流量。
ssvpn

这就不得不问,为什么默认的规则就无法翻墙呢?

如果你盯着上图超过三秒钟你可能就发现了其中的玄机,没错,就是关键字 br-lan ! 默认的规则里面指定了接口br-lan,导致来自VPN(即ppp接口)的流量无法匹配到该规则。

我们可以执行命令iptables-save > iptables.rules将所有的规则导出来,在其中可以看到

1
-A PREROUTING -i br-lan -p tcp -m set ! --match-set ss_spec_lan_ac src -m comment --comment _SS_SPEC_RULE_ -j SS_SPEC_WAN_AC

就是因为存在这个-i br-lan导致的原因。

至于GRE流量,猜测在nat表中解析时已经剥离了GRE报头,在filterINPUT链中应该可以匹配到GRE流量。通过实验验证,验证了该猜想。
ssvpn
filterINPUT链中确实匹配到了GRE流量。

实现

所以写iptables规则时,协议无疑还是是tcp,只是要另外指定接口或者源IP范围即可。

所以我们只需要把这个规则写进去就好了。这里写进/etc/init.d/shadowsocks这个shadowsocks的启动程序中。

start()这个函数的末尾加上INSERT这句,即如下:

1
2
3
4
5
6
7
start() {
rules && start_redir
case "$(uci_get_by_type udp_forward tunnel_enable)" in
1|on|true|yes|enabled) start_tunnel;;
esac
iptables -t nat -I PREROUTING -p tcp -s 10.0.0.0/24 -m set ! --match-set ss_spec_lan_ac src -j SS_SPEC_WAN_AC
}

stop()这个函数的开头加上DELETE这句,即如下:

1
2
3
4
5
6
stop() {
iptables -t nat -D PREROUTING -p tcp -s 10.0.0.0/24 -m set ! --match-set ss_spec_lan_ac src -j SS_SPEC_WAN_AC
/usr/bin/ss-rules -f
killall -q -9 ss-redir
killall -q -9 ss-tunnel
}

这里通过-p all来将所有类型的协议转发到shadowsocks的代理端口。pptpd中的三个iptables rule都是插在filter表中的,而我们手动添加的一条需要插在nat表中,这是因为nat表先于filter表处理,且filter表只能作包过滤。

这样当我们启动shadowsocks的时候会自动将该规则加入到iptables中。

pptp vpn

  • 为了让iOS及Mac OS能连接openwrt的pptp vpn server,需要在openwrt pptpd服务页面中开启MPPE加密
  • Linux连接的时候要手动在Advanced菜单中选用MPPE加密
  • MacOS需要在vpn配置中选中通过VPN连接发送所有流量
如果您觉得这篇文章对您有帮助,不妨支持我一下!