OpenWRT Shadowsocks+GFWList 流量自动分流

1. 起因

鉴于实验室以及寝室均有科技网的IPv6,同时感谢IPv6 hosts,通过修改终端PC或者OpenWRT的hosts文件,可以利用科技网的IPv6愉快地Googling以及看油管,所以一直没考虑在OpenWRT上折腾Shadowsocks的透明代理什么的,但是IPv6环境不是总有的,毕业之后,可能基本告别了原生IPv6的环境,所以在某天晚上突然觉得应该在OpenWRT上配置一下Shadowsocks透明代理。


2. 理论基础

根据调研,可简单归纳为几点

  • 利用dnsmasq-full版本进行流量分离,国内的流量走默认的链路就好,对于GFWList中的域名,为了避免DNS污染,利用Shadowsocks查询GFWList中的域名
  • GFWList中的域名查询结果保存到一个ipset中
  • 利用iptables,将匹配ipset列表中的流量,重定向到Shadowsocks

通俗地讲一下这个原理,路由器是一个指路的,管的地方就那么大,可能是一家的几台设备。在没有透明代理配置的时候是这样的:

  • 手机A:老铁,去gxxgle.com.hk咋走啊?

  • 路由器:我也不知道啊,我去查查。

    过了一会,路由器对手机A说

  • 路由器:去gxxgle.com.hk1.2.3.4大道,前边直走就行

  • 手机A:谢老铁了,我去也!

    过了一会

  • 手机A:扎心了,老铁,你骗我,这道路信息根本不完善,甚至前方道路都不通!去gxxgle.com.hk都被遣返了!
  • 路由器:怪我了?我也是从我的上级查询到!

在配置了透明代理之后

  • 手机A:老铁,去facebxxxk.com咋走啊?这次别坑我啊!
  • 路由器:小点声,最新消息,最近得到一份机密名单,你要去的地方在名单内,那个地方不能走正常大路了,甚至都不能从上级那边问到正确的线路信息,甚至从国外的老铁那边都问不到正确的信息。
  • 手机A:那可咋整啊?
  • 路由器:别急别急,我在国外有个亲戚,他能告诉我正确的线路信息,容我问问。
  • 手机A:不是从国外都问不到正确的线路信息吗?你咋问到的呢?
  • 路由器:我和国外的亲戚通过秘密的通道联络的,好了,帮你问到正确的线路信息了。
  • 手机A:辛苦辛苦,那我走了!我走大路了啊!
  • 路由器:急啥急啥,就算你拿到正确的道路信息,你走大路也出不去!据说去那里的大路查的很严的,像你这种穿着80号制服的,是重点打击对象,见一个遣返一个,你去找我的小弟1080,让他把你打扮,然后通关的时候就说去找我国外的亲戚,别说你是去facebxxxk.com,你先到我亲戚那里,然后你再转道去facebxxxk.com
  • 手机A:那我乔装打扮了之后,真的能混过去吗?
  • 路由器:暂时还能混过去,现在那边查的还没那么狠,给你套一件别的号的衣服,起码现在可以蒙混过关的,不过以后就不知道会怎么样了!
  • 手机A:好好谢谢!那我先过去了,辛苦您嘞!
  • 路由器:没事没事,下一位!
  • 手机B:大哥,我要去baidu.xxx,请问咋走咧?
  • 路由器:我给你查查!哦,你直走就行!
  • 手机B:我也是穿80号制服的,为啥就能直走咧?
  • 路由器:哪那么多废话,你要去的地方没在秘密名单里!赶紧滚!

3. 实现步骤

OpenWRT基础配置

此处略去OpenWRT的基础配置,请参考《NetGear 刷OpenWRT》,本文使用的硬件设备为Netgear WNDR4300,OpenWRT固件版本为Chaos Calmer 15.05.1。

相关软件包安装

首先确保路由器已经联网,使用命令opkg update更新软件包,手动安装某些依赖,更新软件包后,一些依赖也会自动下载,ipset安装之后,需要重启路由器。

1
opkg install iptables-mod-nat-extra ipset iptables-mod-tproxy

卸载原来的dnsmasq,安装dnsmasq-full

1
2
opkg remove dnsmasq
opkg install dnsmasq-full

官方的源中没有Shadowsocks,这时候要根据自己路由器的CPU型号进行选择,下载Shadowsocks-libev版本,下载地址,网站上还有shadowsocks-libev-spec版本,这里不使用该版本。使用PUTTY将下载的软件包上传并安装,安装的过程中会自动下载一些依赖包。

1
opkg install /tmp/shadowsocks-libev_2.4.8-2_ar71xx.ipk

重启路由器之后,因为ipset组件的生效,需要重启设备!

Shadowsocks配置

编辑/etc/shadowsocks.json,填写服务器信息,如下所示,服务器地址可以为IPv6或者IPv4地址,端口号、密码和加密方式根据Shadowsocks服务器配置的信息填写。关于Shadowsocks的服务器如何配置,请自行Google。

1
2
3
4
5
6
7
8
{
"server": "x.x.x.x",
"server_port": xxxx,
"local_port": 1080,
"password": "xxxxxx",
"timeout": 60,
"method": "aes-256-cfb"
}

启动脚本文件修改

编辑/etc/init.d/shadowsocks,修改为如下内容,需要启动ss-redirss-tunnel 这两个服务

  • ss-redir,可以重定向流量到Shadowsocks,默认使用本地TCPUDP1080端口侦听
  • ss-tunnel,查询DNS,避免DNS污染,使用的是UDP!本地侦听端口为5353,远程DNS地址8.8.8.8,端口为标准DNS端口53
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/sh /etc/rc.common

START=95

SERVICE_USE_PID=1
SERVICE_WRITE_PID=1
SERVICE_DAEMONIZE=1
SERVICE_PID_FILE=/var/run/shadowsocks.pid
CONFIG=/etc/shadowsocks.json

start() {
#service_start /usr/bin/ss-local -c $CONFIG -b 0.0.0.0
service_start /usr/bin/ss-redir -c $CONFIG -b 0.0.0.0 -f $SERVICE_PID_FILE
service_start /usr/bin/ss-tunnel -c $CONFIG -b 0.0.0.0 -l 5353 -L 8.8.8.8:53 -u
}

stop() {
#service_stop /usr/bin/ss-local
service_stop /usr/bin/ss-redir
service_stop /usr/bin/ss-tunnel
}

设置开机启动并启动Shadowsocks,并使用netstat -tulp命令,查看相关服务是否已经启动,5353以及1080端口处于侦听状态。

1
2
/etc/init.d/shadowsocks enable
/etc/init.d/shadowsocks start

ipset && iptables设置

添加一个名字为rediripset

1
ipset -N redir iphash

手动添加IP地址进行测试,查看ipset。创建的ipset存在于内存中,重启后将会消失。更多关于ipset的用法,请自行Google。

  • IP 220.181.57.217为百度的地址,用于TCP测试
  • IP 217.10.68.152stun.ekiga.net的IP地址,是nattypetester用于UDP测试的默认地址
1
2
3
4
5
6
7
8
9
10
11
12
root@OpenWrt:~# ipset add redir 220.181.57.217
root@OpenWrt:~# ipset add redir 217.10.68.152
root@OpenWrt:~# ipset list
Name: redir
Type: hash:ip
Revision: 4
Header: family inet hashsize 1024 maxelem 65536
Size in memory: 12504
References: 2
Members:
220.181.57.217
217.10.68.152

在nat表和mangle表分别添加shadowsocks链

1
2
iptables -t nat -N shadowsocks
iptables -t mangle -N shadowsocks

在nat表的shadowsocks链,不需要处理shadowsocks服务器地址以及私有IP地址段、本地回环地址、组播等地址,因此对以上这些IP地址段执行RETURN动作

1
2
3
4
5
6
7
8
9
10
iptables -t nat -A shadowsocks -d x.x.x.x -j RETURN 
# x.x.x.x为shadowsocks服务器地址
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

在nat表的shadowsocks链中,添加TCP重定向条目,将匹配名字为rediripset列表中的IP地址,重定向到本地1080端口,也就是ss-redir服务

1
iptables -t nat -A shadowsocks -p tcp -m set --match-set redir dst -j REDIRECT --to-port 1080

在mangle表的shadowsocks链,添加UDP重定向条目,将匹配名字为rediripset列表中的地址,并排除目的端口为53(DNS查询 ,该查询通过ss-tunnel),通过TPROXY转发到本地1080端口

TPROXY主要功能如下:

  • 重定向一部分经过路由选择的流量到本地路由进程(类似NAT中的REDIRECT)
  • 使用非本地IP作为SOURCE IP初始化连接
  • 无需iptables参与,在非本地IP上起监听
1
iptables -t mangle -A shadowsocks -p udp -m set --match-set redir dst ! --dport 53 -j TPROXY --on-port 1080 --tproxy-mark 0x01/0x01

还需要添加对应的路由规则和路由条目,转发打上标记的数据包。针对标记为0x01/0x01的数据包,查找table 100进行转发

1
2
3
4
ip route add local default dev lo table 100
ip rule add fwmark 0x01/0x01 lookup 100
#查看路由器表100
ip route list table 100

虽然已经创建好了shadowsocks链,但是还需要将shadowsocks链挂载到表中

img
图3-1 iptables

如果只是想让接入OpenWRT的客户端使用ss-redir方式透明转发,接入客户端的数据包到达PREROUTING链之后,在进行路由判断之前,数据包经过shadowsocks链进行过滤,因此添加

1
2
iptables -t nat -A PREROUTING -p tcp -j shadowsocks
iptables -t mangle -A PREROUTING -j shadowsocks

如果想让OpenWRT本机也能使用ss-redir进行透明转发,因为本机的数据来自协议栈的高层,数据包经过OUTPUT链之后,数据包经过shadowsocks链进行过滤

1
2
iptables -t nat -A OUTPUT -p tcp -j shadowsocks
iptables -t mangle -A OUTPUT -j shadowsocks

dnsmasq-full设置

到目前为止,实现了一个简易版的流量自动分流,只不过要手动添加IP地址,这远远达不到需求的,因此还需要dnsmasq-full的帮助!

修改/etc/dnsmasq.conf,最后添加conf-dir=/etc/dnsmasq.d,然后新建/etc/dnsmasq.d目录,切换到该目录并添加配置文件,该目录下可以添加多个配置文件。

1
2
3
4
echo "conf-dir=/etc/dnsmasq.d">>/etc/dnsmasq.conf
mkdir /etc/dnsmasq.d
cd /etc/dnsmasq.d
touch redir_test.conf

通过对配置文件redir_test.conf进行配置,可以对DNS请求进行区别对待了,添加如下测试样例:对于域名为baidu.com相关的DNS请求,转发到服务器8.8.8.8的53端口进行查询,并将查询的结果返回给查询者,同时也将查询结果加入ipset

每次修改配置文件后,执行命令/etc/init.d/dnsmasq restart重启dnsmasq服务!

1
2
server=/baidu.com/8.8.8.8#53
ipset=/google.com/redir

这样,所有到baidu.com的请求,因为解析的IP地址都加入了ipset,根据iptables的匹配结果,都重定向到了ss-redir服务的1080端口。

但是这只是个测试,了解dnsmasq如何对DNS请求区别对待的。为了避免DNS污染,需要把所有GFWList中的域名,都通过ss-tunnel服务进行查询,来获取正确的DNS应答结果,因此要在redir.conf中添加所有GFWList中的域名,并向ss-tunnel服务进行查询,例如如下所示。

1
2
3
4
5
server=/google.com/127.0.0.1#5353
ipset=/google.com/redir
server=/facebook.com/127.0.0.1#5353
ipset=/facebook.com/redir
..........

尝试过自己搭建转发类型DNS,并使用非标准端口,但是经过测试,但是仍无法避免大部分的DNS查询结果被污染。

GFWList2dnsmasq

手动创建dnsmasq配置文件工程量巨大,因此需要自动可以将GFWList转化为dnsmasq配置文件,感谢gfwlist2dnsmasq的作者,拷贝该项目到路由器/root目录(该目录已经挂载了移动硬盘,没挂载移动硬盘请考虑路由器的存储空间)

在OpenWRT上,需要先安装一些软件包

1
2
opkg update
opkg install coreutils-base64 curl ca-certificates

创建计划任务,在每天1点的时候,根据GFWList创建最文件,例如文件名为redir-2017-3-24.conf。然后删除/etc/dnsmasq.d/目录下所有与redir-*匹配的文件,最后拷贝最新的redir-2017-x-xx.conf/etc/dnsmasq.d/目录,重启dnsmasq服务

关于gfwlist2dnsmasq.sh具体如何使用,自行查看gfwlist2dnsmasq的ReadMe

1
2
3
4
0 1 * * * sh /root/gfwlist2dnsmasq/gfwlist2dnsmasq.sh -p 5353 -s redir -o /root/gfwlist2dnsmasq/dnsmasq/redir-`date "+%Y-%m-%d"`.conf \
&& rm -rf /etc/dnsmasq.d/redir-* \
&& cp /root/gfwlist2dnsmasq/dnsmasq/redir-`date "+%Y-%m-%d"`.conf /etc/dnsmasq.d/ \
&& /etc/init.d/dnsmasq restart

一些域名没有收录到GFWList,因此可以手动添加额外的配置文件,但是该配置文件名字不要和redir-*匹配!


4. 问题总结

在配置过程中,教程写的很清晰明了,但是还是因为dnsmasq的问题,耽搁了好长时间,归咎于自己考虑问题的不全面性!

dnsmasq服务不启动

最早手动添加域名到 /etc/dnsmasq.d/下的配置文件中,经过测试,发现无法解析该域名,经过排查,可能是配置文件编码格式的问题,导致了dnsmasq服务无法启动

ss-tunnel无法返回查询结果

这个问题困扰了很久,测试了很多次,发现ss-tunnel对DNS请求的结果没有响应,最开始怀疑是这个服务有问题,所以一度放弃了使用ss-tunnelGFWList域名进行查询的办法,尝试了几种办法之后,例如使用自建的转发DNS服务器,使用pdnsd,但是均以失败告终。最后决定再试试ss-tunnel

这次决定对到ss-tunnel的DNS请求进行跟踪,在路由器上安装了tcpdump

  • 抓取到本地5353端口的数据包,结果如下, 说明dnsmasq已经将GFWList域名解析转到了127.0.0.1:5353,即是ss-tunnel服务

    1
    2
    3
    4
    5
    root@OpenWrt:~# tcpdump -i lo port 5353
    tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
    listening on lo, link-type EN10MB (Ethernet), capture size 65535 bytes
    11:06:01.610434 IP 127.0.0.1.64437 > 127.0.0.1.mdns: 24001+ A (QM)? www.google.com.hk. (35)
    11:06:04.551767 IP 127.0.0.1.57007 > 127.0.0.1.mdns: 43838+ A (QM)? www.gstatic.com. (33)
  • 手动启动ss-tunnel服务,-v参数很重要,可以显示接收的请求,如下所示,说明ss-tunnel已经转发DNS请求

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    root@OpenWrt:~# /usr/bin/ss-tunnel -c /etc/shadowsocks.json -b 0.0.0.0 -l 5353 -L 8.8.8.8:53 -u -v
    2017-03-24 12:24:35 INFO: initializing ciphers... aes-256-cfb
    2017-03-24 12:24:35 INFO: tcp port reuse enabled
    2017-03-24 12:24:35 INFO: UDP relay enabled
    2017-03-24 12:24:35 INFO: udp port reuse enabled
    2017-03-24 12:24:35 INFO: listening at 0.0.0.0:5353
    2017-03-24 12:24:36 INFO: [udp] cache miss: 8.8.8.8:53 <-> 127.0.0.1:31507
    2017-03-24 12:24:36 INFO: [udp] cache miss: 8.8.8.8:53 <-> 127.0.0.1:42275
    2017-03-24 12:24:36 INFO: [udp] cache miss: 8.8.8.8:53 <-> 127.0.0.1:50455
    2017-03-24 12:24:36 INFO: [udp] cache miss: 8.8.8.8:53 <-> 127.0.0.1:62218
  • 现在还需要确认ss-tunnel是否已经转发到Shadowsocks服务器端,通过利用tcpdump分别在路由器、Shadowsocks端进行抓包对比

  • 路由器端抓取8081端口

    1
    tcpdump -i eth0.2 host 8081

    结果如下所示,发现所有的UDP请求都被Shadowsocks服务器返回一个ICMP不可达类型消息

    图4-1 路由器端抓包
  • Shadowsocks服务器抓取8081端口

    1
    tcpdump -i eth0 host 8081

    结果如下所示,发现所有的UDP请求都被接收到了,但是没有任何回应!经过对比,发现数据包里的数据载荷与在路由器端抓到的包一致,也就是说DNS请求确实被转发到Shadowsocks服务器了!

    图4-2 服务器端抓包
  • 从上述结果可以看出,是Shadowsocks服务器端出现了问题,查看了一下Shadowsocks服务器端的iptablesip6tables原来没有允许UDP通过8081端口!所有的问题,都是在这里!


修订版本信息

修订版本 时间 备注
文档创建 2017/3/24 文档创建
文档修改1 2017/11/7 添加UDP转发

参考