Linux WireGuard 和策略路由

本文不涉及任何 Wireguard 安装及配置教程,也无深入讨论 Wireguard/Wireguard-DKMS 的工作原理;仅对配套工具 wireguard-tools (及其Linux命令 wg/wg-quick )所产生的过程和结果作讨论。
测试环境为 Debian GNU/Linux 10 (buster),内核版本 4.19.0-10-amd64
On 28 January 2020, Linus Torvalds merged David Miller's net-next tree, and WireGuard entered the mainline Linux kernel tree.Wireguard 已被并入 Linux 5.6 内核主线中,成为 Linux VPN 的新标准。
本文的灵感来源于两次折腾 wg 而导致的服务器失联问题,其关键参数为:
[Peer]
AllowedIPs = 0.0.0.0/0
问题表现和知识储备
启用服务时,所有入站流量 (inbound traffic) 均被断开,但出站流量 (outbound traffic) 均能够正常透过 Wireguard 隧道访问。
Wireguard 在路由所有流量至隧道时(即设置 AllowedIps = 0.0.0.0/0 (IPv4) 或 ::/0 (IPv6)),wg-quick 会配置一系列的策略路由规则和默认路由来完成这一目标。
wg-quick 做了什么?
wg-quick 通过等价以下命令的结果完成路由所有流量的目标:
# wg set wg0 fwmark 51820
# ip route add default dev wg0 table 51820
# ip rule add not fwmark 51820 table 51820
# ip rule add table main suppress_prefixlength 0
使用以下命令查看结果
# ip route list 51820
default dev wg0 scope link
# ip rule list
0: from all lookup local
32764: from all lookup main suppress_prefixlength 0
32765: not from all fwmark 0xca6c lookup 51820
32766: from all lookup main
32767: from all lookup default
注:51820 (DEC) = 0xca6c (HEX)
- 首先,绑定一个 fwmark (firewall mark, 防火墙标记) == 0xca6c 到接口 wg0 上,使得所有从这个接口发出的流量均带上标记 0xca6c 。
- 之后,创建 ID 为 51820 的路由表,添加一条默认路由,使得所有匹配至这张表的流量路由至 wg0 接口。
- 最后,添加两条策略路由(下详解),其优先级于上述结果中为 32765 和 32764 。
策略路由做了什么?
- 所有流量先匹配 local 路由表(私有地址会匹配到相应的记录)
- 再匹配主路由表中,除了默认路由 (default) 以外的记录**
- 如果带有 fwmark:0xca6c,则忽略本规则,否则匹配 51820 路由表*
- 匹配主路由表和默认路由
注:*suppress_prefixlength NUMBER
reject routing decisions that have a prefix length of NUMBER or less.
suppress_prefixlength 会在查找目标路由表时拒绝前缀等于或低于 NUMBER 的路由决策。在此处即查找 main 路由表时,拒绝匹配到长度等于(或低于,但不可能)0 bit 的 CIDR 前缀,然后进入下一条策略路由;但高于 0 bit 的记录则会被正确匹配。换句话说,因为默认路由的子网掩码是 /0(即前缀长度为0),所以未能匹配到 main 表中其他记录的流量会进入下一条策略路由规则中。
注**:因为 Wireguard 发出的流量都含 fwmark:0xca6c ,故 peer 的 endpoint 会忽略本条规则走主路由表通过物理接口传出,其他流量则走 51820 路由表从 wg0 接口传出。这种写法在 endpoint的 IP 地址发生变化时无需修改 main 路由表,解决了 endpoint 频繁变化导致的问题。
问题成因
利用 tcpdump 观察网卡接口(eth0)和 wg0 的流量情况
# tcpdump -i eth0 host <client_ip>
IP <client_ip>.<port> > <eth0_ip>.<listen_port>: Flags [.], ack 1, win 1028, length 0
......
发现只有入向流量,没有出向流量。
# tcpdump -i wg0
IP <eth0_ip>.<listen_port> > <client_ip>.<port>: Flags [S.], seq 4067566177, ack 2424611520, win 65520, options [mss 1260,nop,nop,sackOK,nop,wscale 6], length 0
本应由 eth0 返回的流量却进了 wg0 接口。
流量的实际情况就变成了这样:
客户端请求 -> CGNAT -> Internet -> 服务器
服务器回包 -> Internet -> Wireguard VPN Peer -> Internet -> CGNAT -->客户端
不合理的返回路径导致回包的源 IP 变成了 Wireguard Endpoint 的 IP 地址,运营商防火墙无法识别其来源,故客户端无法收到回包。故服务器无法正常提供相应端口的服务。
解决问题
解决问题的关键在于将回包路径修改为:
服务器回包 -> Internet -> CGNAT ->客户端
即让源 IP 为的包通过eth0接口传出。
# ip rule add from <eth0_ip> lookup main
若要有更好的体验,可以使用 Wireguard 配置中的 PostUp 和 PostDown 控制
[Interface]
PostUp = ip rule add from <eth0_ip> lookup main
PostDown = ip rule del from <eth0_ip> lookup main
作者:Aiden
原文地址:https://telegra.ph/Linux-Wireguard%E5%92%8C%E7%AD%96%E7%95%A5%E8%B7%AF%E7%94%B1-03-17




