Setup Policy Route on Openwrt X86 With Wireguard
刷系统
选择 image,我用的是 x86_64 的硬件。如果你不确定,可以选择 generic 的试试看,如果你能用 64 的,启动的时候提示你用 64 位的性能更好。
然后需要选择不同的包,主要是在下面两个里面选。
- squashfs-combined:类似传统路由器,可以任意重置路由器。但是磁盘大小也是固定大小。
- ext4-combined:磁盘可以 resize,如果你有超过 50G 的磁盘,都想要在路由器里面用,那就选这个。否则上面那个更好,毕竟有问题的时候重置很方便。这个恐怕只能重新刷系统了,麻烦多了。
准备两个 u 盘。如果你能有一个 u 盘可以启动加存放那个 img 那就更好了。
- 一个刷 debian live cd,启动的时候选择 live
- 一个里面放 openwrt 的 img
openwrt-21.02.0-x86-64-generic-ext4-combined.img
.
连接显示器,键盘。
先使用 livecd 启动之后,先确认下盘符。一般 /dev/sda 是内置的磁盘,/dev/sdb 是 livecd。确认后插入另外一个 u 盘,这个的盘符应该是 /dev/sdc。
mount /dev/sdc1 /mnt
cd /mnt
dd if=openwrt-21.02.0-x86-64-generic-ext4-combined.img of=/dev/sda
使用 cfdisk resize 下 /dev/sda2 并写到分区表。然后使用 resize2fs /dev/sda2
命令应用到文件系统。这个时候就可以使用路由器自带的 openwrt 启动了。
初始化
system -> system
- hostname
- timezone
system -> administration
- set password
- ssh access:
- interface: lan
- uncheck all the boxes
- ssh-keys
- add your pub keys
ssh [email protected]
测试连接。
调整网络配置
调整的主要原因是
- 192.168.x.x 容易和某些 vpn 什么的冲突
- 把 eth0 改成 wan 口,其他都是 lan 口,比较符合直观的感受。
调整内容
- network
- interfaces -> lan
- ip address: 10.10.10.1
- interfaces -> wan 和 wan6
- device: eth0
- devices -> br-lan
- 把 eth0 之外都选上 这个操作之后,需要把线改接第二个口了。我遇到情况是 apply 之后部分修改没生效,别慌,把接口接回去应该还能继续配置。
- interfaces -> lan
使用 dnsmasq-full 替换 dnsmasq
这一步需要通过 ssh 操作。这一步放最前面是为了避免丢失配置,因为为了省事(解决配置冲突)需要删除 /etc/config/dhcp
。
opkg update
opkg remove dnsmasq # 这一步可能会导致 dns 丢失,如果出现问题可以去 /etc/resolve.conf 里面重新配置下 dns
rm /etc/config/dhcp
opkg install dnsmasq-full
网络拓扑
openwrt -> wifi router -> pc/mac/iphone 10.10.10.1/24 -> 10.10.10.2/10.10.8.1/24 -> 10.10.8.x
这样的结构下,如果 openwrt 出问题,可以随时把外网接 wifi router 上面继续用,其他设备都不用动。等稳定之后,可以把 wifi router 改成桥接模式。
配置 wireguard
参考文档:https://openwrt.org/docs/guide-user/services/vpn/wireguard/server
规划下 wireguard 各个节点的 ip
这里需要提前规划下 wireguard 网络,我的目的是通过远端的 vps 上网,所以我的设置如下
- network: 10.10.20.1/24
- vps: 10.10.20.1
- openwrt: 10.10.20.11
- 其他设备: 10.10.20.21(mac),22(iphone)
安装配置 wireguard
- system -> software
- update lists
- install: wireguard-tools(必须) luci-proto-wireguard(通过配置 luci 配置 wireguard 必须) luci-app-wireguard(显示一些连接信息,不是必须)
这个时候可能需要重启下路由器才能继续,否则可能在菜单里面看不到 wireguard 这个选项
- network -> interfaces
- Add new interface
- name: wg0
- protocal: wireguard vpn
- create interface
- private key: 可以点那个 generate key
- listen port: 54321
- ip address: 10.10.20.11
- save
- save and apply
- Add new interface
这个时候执行 wg 命令应该可以看到 wg 设备的情况了。在 status -> wireguard 也可以看到。如果想要在这里支持 qr/code ,还需要安装一些 qrcode 相关的包。
配置 firewall
单独为 wg 创建一个 zone。允许通过 wg 访问 wan 和 lan。
- network -> firewall -> zones
- add
- name: wireguard
- forward: accept
- mss clamping
- masquerading
- covered networks: wg0
- allow forward to dest zone: lan, wan, wan6,也可以设置只允许访问 wan。
- allow forward from source zone: lan(这个好像是需要的)
- save
- save and apply
- add
允许通过 wan 访问 54321
- network -> firewall -> traffic rule
- add
- name: Allow-wireguard-from-wan
- protocol: udp
- dest zone: input
- dest port: 54321
- save
- save and apply
- add
配置 wireguard peers
- network -> interfaces -> wg0 -> edit -> peers
- add peer(客户端 peer)
- description: Mac
- pub key:
- allowed ips: 10.10.20.21/32
- add peer(vps server)
- description: vps
- endpoint host:
vps ip
- endpoint port:
vps port
- allowed ips: 0.0.0.0/0
- save
- save and apply
- restart interface
- add peer(客户端 peer)
这个时候执行 wg 命令或者去 status -> wireguard 应该能看到更多信息了。比较关键的是确认下 vps 那个有没有连接成功,看 latest handshake
是什么时候。
peer: <pub_key>
endpoint: <ip:port>
allowed ips: 0.0.0.0/0
latest handshake: 45 seconds ago
transfer: 92 B received, 212 B sent
persistent keepalive: every 25 seconds
也可以测试下是不是可以通过电脑或者手机连接这个 openwrt 上面的 wireguard。这个时候应该只能 handshake 成功,但是不能通过这个连接访问任何网站,因为还没有配置相关的路由。DNS 也不通。
配置路由
配置针对 10.10.20.0/24 网段的路由,配置之后就可以通过 wg 访问路由器的ip 10.10.10.1 了。
route add -net 10.10.20.0 netmask 255.255.255.0 dev wg0
如果你还想通过 wg 访问内网的设备(10.10.8.x),还需要配置一条路由
network -> dhcp and dns -> static leases
- 找到 wifi router 的设备,给它设置为固定 ip
network -> static routes -> add
- interface: lan
- target: 10.10.8.0
- netmask: 255.255.255.0
- gateway: 10.10.10.2(这个是 wifi router 的固定 ip)
注意几点
- 需要关闭 wifi 的防火墙,因为路由器一般默认不允许从 wan 连接。
- wireguard 客户端的路由配置里面,增加 10.10.8.1/24,默认是没有的。
增加 route table
编辑 /etc/iproute2/rt_tables
,增加一行
200 gfw
配置 dnsmasq 和 ipset
配置 ipset
安装 ipset
创建 /etc/gfw.ip.ipset
文件。里面存放需要通过 wg 访问的 ip。
8.8.8.8
1.1.1.1
创建 /etc/gfw.net.ipset
文件。里面存放需要通过 wg 访问的 cidr
# telegram
67.198.55.0/24
91.108.4.0/22
91.108.8.0/22
91.108.12.0/22
91.108.16.0/22
91.108.56.0/22
109.239.140.0/24
149.154.160.0/20
149.154.164.0/22
149.154.168.0/22
149.154.172.0/22
205.172.60.0/22
修改 /etc/config/firewall
文件。增加下面的内容
config ipset
option name 'gfw'
option match 'ip'
option storage 'hash'
option enabled '1'
option loadfile '/etc/gfw.ip.ipset'
config ipset
option name 'gfwnet'
option match 'net'
option storage 'hash'
option enabled '1'
option loadfile '/etc/gfw.net.ipset'
重启 firewall /etc/init.d/firewall restart
. 执行 ipset list
查看结果
Name: gfwip
Type: hash:ip
Revision: 4
Header: family inet hashsize 1024 maxelem 65536
Size in memory: 200
References: 0
Number of entries: 0
Members:
1.1.1.1
8.8.8.8
Name: gfwnet
Type: hash:net
Revision: 6
Header: family inet hashsize 1024 maxelem 65536
Size in memory: 1216
References: 0
Number of entries: 12
Members:
149.154.172.0/22
109.239.140.0/24
91.108.8.0/22
67.198.55.0/24
149.154.168.0/22
149.154.160.0/20
91.108.12.0/22
91.108.16.0/22
149.154.164.0/22
91.108.56.0/22
91.108.4.0/22
205.172.60.0/22
日后这两个文件 /etc/gfw.net.ipset
和 /etc/gfw.ip.ipset
就是管理静态的需要走 wg 的 ip 的文件。
配置 dnsmasq
network -> dhcp and dns
- Listen interfaces: lan, wg
这个配置之后应该可以通过 wg 访问 openwrt 的 dns 了。
创建 /etc/dnsmasq.d/gfw.conf
文件,可以根据需要自己写,也可以使用别人写好的。格式如下
server=/twitter.com/8.8.8.8
ipset=/twitter.com/gfwip
也可以使用我整理好的 https://github.com/wd/gfwlist-ipset/blob/main/gfwlist-ipset.conf
修改 /etc/config/dhcp
文件,增加一行
config dnsmasq
...
option confdir '/etc/dnsmasq.d'
重启 dnsmasq, /etc/init.d/dnsmasq restart
. 测试下
# dig twitter.com
; <<>> DiG 9.17.13 <<>> twitter.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 13344
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;twitter.com. IN A
;; ANSWER SECTION:
twitter.com. 1227 IN A 104.244.42.193
twitter.com. 1227 IN A 104.244.42.1
;; Query time: 0 msec
;; SERVER: 127.0.0.1#53(127.0.0.1) (UDP)
;; WHEN: Sun Oct 24 22:38:47 CST 2021
;; MSG SIZE rcvd: 72
# ipset list gfwip
Name: gfwip
Type: hash:ip
Revision: 4
Header: family inet hashsize 1024 maxelem 65536
Size in memory: 392
References: 0
Number of entries: 4
Members:
8.8.8.8
104.244.42.193
1.1.1.1
104.244.42.1
能看到 twitter ip 已经加入到了 ipset 里面。(这个测试里面不一定能返回正确的 twitter 的 ip,只是测试 dnsmasq + ipset 是正常工作的)。
以后需要翻墙的域名就在这个 /etc/dnsmasq.d/gfw.conf
文件里面维护。也可以在这个目录放多个文件。
配置匹配 ipset 的包走 wg
network -> firewall -> custom rules
iptables -t mangle -N fwmark
iptables -t mangle -A PREROUTING -j fwmark
iptables -t mangle -A OUTPUT -j fwmark
iptables -t mangle -A fwmark -m set --match-set gfwip dst -j MARK --set-mark 1
iptables -t mangle -A fwmark -m set --match-set gfwnet dst -j MARK --set-mark 1
路由规则
# 有 0x1 标记的,都走 gfw 这个 table
ip rule add fwmark 0x1 table gfw
# gfw 这个 table 默认都从 10.10.20.1 出去
ip route add default via 10.10.20.1 dev wg0 table gfw
配置 hotplug
上面的路由规则目前都是通过手动配置的,重启设备就没有了。需要配置一个自动添加的脚本。
创建一个文件 /etc/hotplug.d/iface/50-wg
#!/bin/sh
if [ "ifup" = "$ACTION" ] && [ "$INTERFACE" = "wg0" ]; then
ip route add 10.10.20.0/24 dev wg0
ip rule|grep -q 'fwmark 0x1 lookup gfw' || ip rule add fwmark 0x1 table gfw
ip route add default via 10.10.20.1 dev wg0 table gfw
fi
排查错误
在全部配置好之后,最好重启下 openwrt,确保重启后依然是可以上网的。如果有问题,可以参考下面的步骤。
确保 ipset ok,应该有两个set gfwip
和 gfwnet
。查询一个被屏蔽的 dns 之后能在 ipset 列表里面看到。
# ipset list
确保路由规则都在
# ip route show table gfw
default via 10.10.20.1 dev wg0
# ip rule|grep fwmark
32765: from all fwmark 0x1 lookup gfw
# ip route |grep wg
10.10.20.0/24 dev wg0 scope link
iptables 规则 ok
# iptables -t mangle -nvL PREROUTING
Chain PREROUTING (policy ACCEPT 26721 packets, 5880K bytes)
pkts bytes target prot opt in out source destination
27M 31G fwmark all -- * * 0.0.0.0/0 0.0.0.0/0
# iptables -t mangle -nvL OUTPUT
Chain OUTPUT (policy ACCEPT 6295 packets, 2828K bytes)
pkts bytes target prot opt in out source destination
307K 86M fwmark all -- * * 0.0.0.0/0 0.0.0.0/0
# iptables -t mangle -nvL fwmark
Chain fwmark (2 references)
pkts bytes target prot opt in out source destination
188K 29M MARK all -- * * 0.0.0.0/0 0.0.0.0/0 match-set gfwip dst MARK set 0x1
1417 157K MARK all -- * * 0.0.0.0/0 0.0.0.0/0 match-set gfwnet dst MARK set 0x1
启用 log system -> logging
- write system log to file: /tmp/system.log
通过看 FORWARD 和 POSTROUTING 的日志来判断可能是哪里有问题。
DEST=8.8.8.8
iptables -t filter -A FORWARD -d $DEST -j LOG --log-prefix 'FWD:'
iptables -t mangle -A POSTROUTING -d $DEST -j LOG --log-prefix 'POST:'
DEST=10.10.8.1
iptables -t filter -D FORWARD -d $DEST -j LOG --log-prefix 'FWD:'
iptables -t mangle -D POSTROUTING -d $DEST -j LOG --log-prefix 'POST:'
如果只有 FORWARD 没有 POSTROUTING,那就是包被 drop 了。同时也观察包是否有正确的 src dst 和 mark。
其他
iptv
network -> interfaces -> add
- name: iptv
- protocal: dhcp
- device: eth0
- advanced settings
- use default geteway: 去掉选择
安装 luci-app-udpxy
services -> udpxy
- enabled 选上
- status 选上
- port 4022
- source ip/interface: eth0
- 其他都留空
- save and apply
访问 http://10.10.10.1:4022/status 能看到 Multicast address
是 192.168.1.x 这样的 ip。可以找一个地址测试下是不是可以用吧。
ddns
安装 ddns-scripts-cloudflare ca-certificates
services -> ddns -> add
- hostname: x.abc.com
- ddns service provider: cloudflare.com-v4
- domain: [email protected]
- username: Bearer
- password: token from cloudflare
- path to ca certs: /etc/ssl/certs
- save
点击那个 reload 可以重复执行。点击 edit 可以看 log,如果遇到 ssl 错误,可能需要安装 libwolfssl4.8.1.66253b90
,openwrt 默认使用 wolfssl 。
穷人的防屏蔽措施
wireguard 连不上也很稀松平常,一方面 UDP 被 Qos,另外一方面就是 GFW 作怪。我在 twitter 上面无意中看到一个措施感觉还有点用。思路就是在服务器端开 N 个 wireguard 端口,客户端连接有问题的时候就换一个。
当然实际上在服务器端也不用真的开 N 个,只需要用 iptables 转发到真实的端口就行。就是类似下面这句,把其中的几个端口改改就行。
iptables -t nat -A PREROUTING -p udp --destination-port 20011:23344 -j REDIRECT --to-ports 1234
然后在 openwrt 里面,增加一个 crontab 定期检查网络是不是通的就行。这里面几个端口可能需要改改,以及 wg 设备的名字。 crontab 项目类似这个 * * * * * /etc/wg/change-wg-port.sh
.
LOG=/tmp/check_gfw.log
log() {
echo "$(date +%F_%T) $1" >> $LOG
}
check_gfw() {
code=$(curl -s -o /dev/null -w "%{http_code}" google.com)
if [ "$code" = "301" ]; then
log "google.com is ok"
else
log "google.com is not ok $code, switch port"
switch_port
fi
}
switch_port() {
current_port=$(uci get network.@wireguard_wg0[2].endpoint_port)
new_port=$(expr $current_port + 1)
if [ "$new_port" = "23344" ];then
new_port=20011
fi
log "new port is $new_port"
uci set network.@wireguard_wg0[2].endpoint_port=$new_port
uci commit network
ifup wg0
}
check_gfw
这个是我的一些检查日志,8.5 断了九分钟,8.7 断了三分钟。这个因为是 1 分钟检查一次,所以可能会有点慢,但是,总归比什么都不做强。。
2022-08-02_12:53:05 google.com is not ok 000, switch port
2022-08-05_22:24:05 google.com is not ok 000, switch port
2022-08-05_22:25:05 google.com is not ok 000, switch port
2022-08-05_22:26:05 google.com is not ok 000, switch port
2022-08-05_22:27:05 google.com is not ok 000, switch port
2022-08-05_22:28:05 google.com is not ok 000, switch port
2022-08-05_22:29:05 google.com is not ok 000, switch port
2022-08-05_22:30:05 google.com is not ok 000, switch port
2022-08-05_22:31:05 google.com is not ok 000, switch port
2022-08-05_22:32:05 google.com is not ok 000, switch port
2022-08-07_02:11:15 google.com is not ok 000, switch port
2022-08-07_02:12:05 google.com is not ok 000, switch port
2022-08-07_02:13:05 google.com is not ok 000, switch port
2022-08-07_02:14:05 google.com is not ok 000, switch port
其他
- 通过配置 port-foward -> router 开放 router 下面客户端的端口。例如 NAS bt 下载用的 p2p 端口。注意还需要在 router 里面也做这个映射。当然也可以直接把 nas 接到 openwrt。
- 如果在路由配置好之前发生了针对被污染的域名的 dns 查询,那很可能会缓存被污染的结果,目前没想到什么好办法。不过好在一般等 dns 过期之后就正常了。
TODO
- ipv6 支持:因为这个方案目前不支持 ipv6,所以如果有 app 优先使用 ipv6 查询的话,那么返回的 ipv6 地址是不会走 wg 的,这个时候就会出现不能用。解决方法就是关闭这个设备的 ipv6,或者直接关闭 openwrt 的 ipv6 支持。