蝴蝶翅膀的力量,也许能够改变这个世界。
全部博文(81)
分类: IT业界
2018-09-23 17:29:44
(2018-10-12更新:OpenWrt的V18.06版本,mt76芯片组的2.4GWiFi有缺陷,请谨慎使用。5G是OK的。)
(声明:欢迎转载分享文章,原创文章请注明出处,转载文章请注明原文链接。由于我在发文章的时候才登录自己的博客,因此无法及时回复消息。如果有技术问题,可以发送到我的邮箱一起探讨,邮箱地址为chou dot o dot ning at gmail dot com)
缘起
作为一个程序员,工作无法离开Google,因此一直使用ShadowSocks上网。使用PC时,设置浏览器的socks5代理上网,使用手机时,使用ShadowSocks代理的APP软件。最近Google推出了Android9版本,而我的Google Pixel2手机无法从Android8升级(即使手机开了代理也不行)。因此打算在家改造一台无线路由器,直接支持ShadowSocks,被GFW收录的网站走代理,其他网站不走代理。这样家里的手机、PC、pad都不用再设置各种代理了。
本来以为在网上查找一些文章,按照其步骤指导,会很容易地打造一个基于OpenWRT和ShadowSocks的无线路由器上网方案。但依次做下来,发现有很多坑!大多数的文章只告诉你怎么做,但并不告诉你为什么这么做,导致出现问题的时候,只能自己查找资料定位问题。因此,在踏过这些坑之后,打算把整个过程记录下来,为后来者提供一些参考。
〇、准备工作
1、如果你是Windows用户,请安装putty或其他类似软件,主要用于ssh客户端登录路由器。如果你有Mac或者Ubuntu等linux主机,系统自带ssh客户端。
2、需要一个U盘,并且格式化为Fat32格式
3、最好有一些linux的基础知识
一、硬件的选择
第一步,需要选择一款支持OpenWRT的无线路由器,并不是市面上的每款无线路由器都被OpenWRT支持,因此需要在 中查找相关的硬件。
我这里推荐小米路由器3G(MiWiFi 3G),理由如下(主要是其性价比):
1、OpenWRT主干版本和最新版本支持该硬件,当前最新版本是V18.06.1,是OpenWRT和LEDE合并后的第一个版本;
2、淘宝上购买包邮价格为179元,在同类性能的路由器里面算是超级便宜的;
3、一个Wan口和两个Lan上行口为千兆,可以匹配超过百兆的家庭光宽带;
4、cpu为MediaTek的MT7621AT,mips双核880MHz,每个核支持双线程;
5、Flash为128MB,这个比较重要,因为OpenWRT支持很多模块的安装。很多低端的路由器因为降成本,Flash配置很小,安装几个模块之后,Flash就满了;
6、内存为256MB,足够跑绝大多数的应用;
7、带USB3.0口,可以挂接移动硬盘或摄像头,还可以玩一些家庭NAS、监控等一些好玩的应用;
8、Wi-Fi支持2.4G和5G双频,MediaTek的无线驱动是开源的,因此开源社区支持力度较好(玩OpenWRT很多人不建议用Broadcom的芯片,主要原因就是其无线部分是闭源的)
9、PCB版留有UART口,可供在此方面有需求的开发人员使用(对大多数人来说,串口可以不需要,有ssh远程连接就够用了)。当然,如果变砖,可以通过UART来拯救(Bootloader没有被破坏的话)。
二、路由器烧录OpenWRT firmware
下面是烧录的步骤:
1、拆包装;
2、按照其用户手册,安装小米WiFi APP,注册账号,并绑定路由器后,可在APP中查看和配置路由器
3、到 中,寻找到ROM版本:ROM for R3G 开发版,下载并将路由器更新到该版本。升级时,会提示是否恢复缺省配置,这里不需要恢复缺省配置,否则APP还需要重新绑定和配置一次;
4、登陆到 ,用之前注册的账号登录,获取其root密码。注意:如果你有多台小米路由器,每台的root密码是不同的。然后下载工具包miwifi_ssh.bin;
5、到 寻找到 mir3g-squashfs-kernel1.bin 和 mir3g-squashfs-rootfs0.bin,下载之,文件名带有前缀openwrt-18.06.1-ramips-mt7621-)。这里需要说明一下,kernel是linux的内核文件,rootfs是文件系统,嵌入式Linux的Flash一般会有多个分区,kernel和rootfs分别放在两个分区,因此需要分别下载;
6、将下载的三个文件(miwifi_ssh.bin、openwrt-18.06.1-ramips-mt7621-mir3g-squashfs-kernel1.bin、openwrt-18.06.1-ramips-mt7621-mir3g-squashfs-rootfs0.bin)拷贝到格式化为Fat32的U盘;
7、路由器下电,按住reset按钮(用回形针或缝衣针),然后路由器上电。等到路由器黄灯开始闪烁时,可以释放reset按钮。等路由器重启后,使用ssh访问路由器,用root用户登录(路由器的ip地址为192.168.31.1),密码为步骤4的密码(Mac或Ubuntu用户登录命令为ssh )。注意:这个步骤后,路由器就失去了保修。
8、在ssh命令行下
cd /extdisks/sda1 (如果你拔出再重插回U盘,这个路径可能会有变化)
mtd write openwrt-18.06.1-ramips-mt7621-mir3g-squashfs-kernel1.bin kernel1
mtd write openwrt-18.06.1-ramips-mt7621-mir3g-squashfs-rootfs0.bin rootfs0
nvram set flag_last_success=1
nvram commit
reboot
9、重启后,再次使用ssh登录(这时ip地址变更为192.168.1.1,用户名还是root,无密码),这时可以看到OpenWRT的命令行界面了。
这里解释一下上面步骤的原理:
A、小米路由器的版本缺省是没有ssh组件的,小米公司对DIY用户还是非常友好的,提供了带ssh server组件的版本。
B、通过一种特定的操作方式(插入有miwifi_ssh.bin文件的U盘,长按reset重启),将ssh功能启用,这样用户登陆后就可以做任何事情,但同时也失去了保修。
C、登陆后,如果敲入下面的命令
root@XiaoQiang:~# cat /proc/mtd
dev: size erasesize name
mtd0: 07f80000 00020000 "ALL"
mtd1: 00080000 00020000 "Bootloader"
mtd2: 00040000 00020000 "Config"
mtd3: 00040000 00020000 "Bdata"
mtd4: 00040000 00020000 "Factory"
mtd5: 00040000 00020000 "crash"
mtd6: 00040000 00020000 "crash_syslog"
mtd7: 00040000 00020000 "reserved0"
mtd8: 00400000 00020000 "kernel0"
mtd9: 00400000 00020000 "kernel1"
mtd10: 02000000 00020000 "rootfs0"
mtd11: 02000000 00020000 "rootfs1"
mtd12: 03580000 00020000 "overlay"
mtd13: 012a6000 0001f000 "ubi_rootfs"
mtd14: 030ec000 0001f000 "data"
可以看到flash的分区,这里看一下几个重要的分区:
A、Bootloader是启动分区,上电第一条指令从这里运行,然后启动linux kernel,再挂载rootfs文件系统。如果这个分区被破坏,会导致路由器变砖
B、kernel0/kernel1以及rootfs0/rootfs1是linxu内核和文件系统,双份,如果一份出错(比如路由器升级过程中断电),有备份,可以防止路由器变砖。
C、小米路由器也基于OpenWRT开发的,因此无须修改bootloader的配置,只要将kernel和rootfs刷新即可。
10、回头看上面的刷文件的命令
mtd write openwrt-18.06.1-ramips-mt7621-mir3g-squashfs-kernel1.bin kernel1 (更新kernel1)
mtd write openwrt-18.06.1-ramips-mt7621-mir3g-squashfs-rootfs0.bin rootfs0。(更新rootfs0)
nvram set flag_last_success=1(这个命令我不是很确定,可能是让bootloader从kernel1引导,即引导OpenWRT的firmware而不是小米的firmware)
nvram commit (保存配置)
reboot(重启)
三、安装OpenWRT的Web UI和shadowsocks相关组件
按照上面步骤烧入新的fireware后,只是一个最小化的系统,大多数的组件都没有安装,包括Web管理器。因此首先第一步安装Web UI进行路由器的管理。
root@OpenWrt:~# opkg update
root@OpenWrt:~# opkg install luci
Luci是OpenWRT的Web UI组件,安装完毕后,就可以通过 进行管理路由器了。
四、安装ShadowSocks
登入WebUI,进入System->Software。如果没有显示所有的软件,则需要点击“Update lists”
然后搜索 shadowsocks
Shadowsocks在嵌入式设备中的开源项目是shadowsocks-libev (),是用C语言实现的。(这里一定要提一下,shadowsocks的原始作者是clowwindy,用python实现的,其他的shadowsocks项目,包括这里的shadowsocks-libev项目,都是从其github的项目中fork出去的)
我们需要关心和安装下面的组件:
luci-app-shadowsocks-libev #shadowsocks-libev的web配置模块
shadowsocks-libev-config #对shadowsocks-libev进行配置后,会自动生成配置文件
shadowsocks-libev-ss-redir #路由器的透明代理
shadowsocks-libev-ss-tunnel #用于DNS查询报文的隧道,防止DNS污染(如果不安装,即使配置了ss-redir,谷歌仍然无法访问,因为国内的DNS解析的google的ip是错误的)
另外,下面的模块可以不安装,主要理由是它存在一些缺点,配置起来不太方便。这个模块是设置ss-redir的规则的,但是。其中目的地址检查的配置页面(被GFW屏蔽的网站,forward到ss-redir,其他网站则bypass),只能配置ip地址段,无法支持域名,因此这里就不考虑使用(比如google这么多的服务器,维护ip段还是比较麻烦的,而域名就很简单一条:*.google.com)
shadowsocks-libev-ss-rules
五、配置shadowsocks-libev
首先配置shadowsocks服务器
然后配置ss-redir
配置ss-tunnel
六、解决DNS域名污染问题
首先看一下正常的报文转发过程:
1、查找域名,发起DNS请求,从电信的DNS服务器中获得解析的ip地址;
2、浏览器的http报文发送到路由器,通过路由器的netfilter模块进行地址转换后(NAT转换),发送给目的服务器;
3、目的服务器回应的http报文,返回到路由器,再通过netfilter模块,回到浏览器。
我们访问某个被GFW屏蔽的网站,和上面的步骤相比,有了一些差别
1、首先对域名做一次判断,如果是被GFW的域名,将DNS的请求转发到ss-tunnel(不是转发给电信的DNS服务器,而是Google的8.8.8.8 DNS服务器),获得其ip地址;
2、获取ip地址后,路由器的DNS模块会调用ipset组件,在内存中产生一张gfwlist的表,将该ip地址保存到该表中;
3、浏览器的http报文tcp报文,在通过路由器的转发模块(netfilter)时,在gfwlist表中对比该ip地址,如果该ip在表中,则将tcp报文转发到ss-redir;
4、服务器返回的报文回发到ss-redir,然后再回到浏览器;
这里,很多人可能会有一个疑问,为什么需要ipset组件,需要在内存中维护一个gfwlist的ip表,而不是直接查找域名表?原因很简单,linux内核的netfilter模块只会对比ip地址,它不认识域名。
最后我们需要一张GFW的域名表,很幸运,世界上好人很多,在 可以下载到。
保存并打开该文件,可以看到
# dnsmasq rules generated by gfwlist
# Last Updated on 2018-09-10 13:19:26
#
server=/030buy.com/127.0.0.1#5353
ipset=/030buy.com/gfwlist
server=/0rz.tw/127.0.0.1#5353
ipset=/0rz.tw/gfwlist
server=/1000giri.net/127.0.0.1#5353
ipset=/1000giri.net/gfwlist
这个文件是提供给dns服务的配置文件
“server=”这个选项表示,如果解析域名*.030buy.com,则将DNS解析报文转发到本地的5353端口(即转发到ss-tunnel),这样就把被GFW屏蔽的网站的DSN请求,转发到ss-tunnel通道进行解析,解决了DNS域名污染问题
“ipset=”这个选项表示,如果解析域名 *.030buy.com,则在ipset的内存中的gfwlist表项中,添加解析完成的ip地址。
现在可以开始干活了:
首先需要安装 dnsmasq-full,因为OpenWRT缺省安装的是dnsmasq,它不支持ipset。
可以在web UI操作,也可以在ssh的命令行操作
root@OpenWrt:~# opkg update
root@OpenWrt:~# opkg install dnsmasq-full
root@OpenWrt:~# opkg remve dnsmasq
然后将之前下载的dnsmasq_gfwlist_ipset.conf 上载到路由器的/etc目录下
为了实现从pc到路由器的上载,需要安装openssh-sftp-server
root@OpenWrt:~# opkg install openssh-sftp-server
然后从pc上载该文件到路由器的/etc目录中
ningdeMBP:tmp ning$ sftp root@192.168.1.1
root@192.168.1.1's password:
Connected to 192.168.1.1.
sftp> cd /etc
sftp> put dnsmasq_gfwlist_ipset.conf.txt
Uploading dnsmasq_gfwlist_ipset.conf.txt to /etc/dnsmasq_gfwlist_ipset.conf
dnsmasq_gfwlist_ipset.conf.txt 100% 355KB 2.7MB/s 00:00
sftp> bye
不要上载到路由器的/tmp或者/var目录,这些目录是ramfs(基于内存的文件系统),系统重启后会丢失。
然后使用vi打开 /etc/dnsmasq.conf,在最后一行添加 conf-file=/etc/dnsmasq_gfwlist_ipset.conf
然后,重启dns服务
root@OpenWrt:~# /etc/init.d/dnsmasq restart
测试一下,大功告成!
root@OpenWrt:/etc# nslookup
Server: 127.0.0.1
Address: 127.0.0.1#53
Name:
canonical name = star-z-mini.c10r.facebook.com
Name: star-z-mini.c10r.facebook.com
Address 1: 157.240.15.38
canonical name = star-z-mini.c10r.facebook.com
Address 2: 2a03:2880:f10f:86:face:b00c::50fb
正常情况下对facebook的DNS访问是下面这样的:
root@OpenWrt:/etc# nslookup
Server: 127.0.0.1
Address: 127.0.0.1#53
Name:
Address 1: 74.86.12.172
*** Can't find : No answer
再测试一下ipset功能
root@OpenWrt: ipset -N gfwlist hash:ip
root@OpenWrt:/etc# ipset list
Name: gfwlist
Type: hash:ip
Revision: 4
Header: family inet hashsize 1024 maxelem 65536
Size in memory: 56
References: 0
Number of entries: 0
Members:
root@OpenWrt:/etc# nslookup
Server: 127.0.0.1
Address: 127.0.0.1#53
Name:
canonical name = star-z-mini.c10r.facebook.com
Name: star-z-mini.c10r.facebook.com
Address 1: 31.13.82.38
canonical name = star-z-mini.c10r.facebook.com
Address 2: 2a03:2880:f10f:86:face:b00c::50fb
root@OpenWrt:/etc# ipset list
Name: gfwlist
Type: hash:ip
Revision: 4
Header: family inet hashsize 1024 maxelem 65536
Size in memory: 136
References: 0
Number of entries: 2
Members:
172.217.25.202
31.13.82.38
ipset 的gfwlist表增加了两个ip地址,表明ipset表项建立成功。
七、最后一步
在命令行中,做如下的配置,即完成最后一步
ipset -N gfwlist hash:ip
iptables -t nat -I PREROUTING -p tcp -m set --match-set gfwlist dst -j REDIRECT --to-port 1100
上面命令的解析:
第一条:使用ipset命令在内存中建立一个gfwlist的表,这样dns在解释被GFW屏蔽的域名时,将域名解释成功的ip地址写入该表
第二条:最重要的一条规则,下面是每个参数的意义:
iptables: iptables是一个用户态的应用程序,可以对linux的netfilter模块进行规则配置
-t nat -I PREROUTING: 针对的是PREROUTING链的nat表,即入口处的报文
-p tcp: 针对tcp报文
-m set --match-set gfwlist dst: 对于目的地址为ipset的gfwlist表中的ip地址
-j REDIRECT --to-port 1100: 报文转发到1100端口(ss-redir的本地端口)
合起来,即对于目的地址匹配gfwlist表的tcp报文,转发到1100端口,即ss-redir的端口
对于UDP,做如下的配置(请先确认你的shadowsocks服务器支持udp的代理)
ip rule add fwmark 0x01/0x01 table 100
ip route add local 0.0.0.0/0 dev lo table 100
iptables -t mangle -A PREROUTING -p udp -m set --match-set gfwlist dst -j TPROXY --on-port 1100 --tproxy-mark 0x01/0x01
上面的UDP规则,我不清楚是否有错误,因为不知道该怎么测试。我用YouTube测试,但YouTube走的好像是tcp报文
如果遇到下面的错误,说明没有安装ip-tiny组件
root@OpenWrt:~# ip rule add fwmark 0x01/0x01 table 100
ip: invalid argument '0x01/0x01' to 'fwmark'
如果遇到下面的错误,说明没有安装iptables-mod-tproxy
root@OpenWrt:~# iptables -t mangle -A PREROUTING -p udp -m set --match-set gfwlist dst -j TPROXY --on-port 1100 --tproxy-mark 0x01/0x01
iptables v1.6.2: unknown option "--on-port"
然后在将这些规则保存起来,这样路由器重启后,不会丢失这些配置。
在Network->Firewall->Custom Rules 保存至
八、gfwlist的更新
有些网站没有收录在gfwlist,因此需要手动添加。Openwrt在家里电信的网络中,经常打开奇慢,添加到列表中就快很多。
在ssh的命令行中
root@OpenWrt:~# vi /etc/dnsmasq_gfwlist_ipset.conf
添加类似于下面的规则:
# add by ning
server=/openwrt.org/127.0.0.1#5353
ipset=/openwrt.org/gfwlist
然后重启一下DNS服务
root@OpenWrt:~# /etc/init.d/dnsmasq restart
九、一些调试手段
1、如果错误地保存了一些配置导致pc无法通过ssh连接路由器(比如我曾经配置了一条错误的iptables命令导致pc无法连接路由器),需要用针按住reset 5秒以上,看到灯出现闪烁时放开,这时路由器会恢复缺省配置;
2、使用logread命令查看日志,有些错误日志对问题定位很有帮助;
3、有些问题可能需要通过源代码才能定位,其实对于程序员来说并不算复杂,设置开发环境、下载源码编译,调试、修改。这里就不展开了。