# 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 而导致的服务器失联问题，其关键参数为：

``` bash
[Peer]
AllowedIPs = 0.0.0.0/0
```

# **问题表现和知识储备**

启用服务时，所有入站流量 (inbound traffic) 均被断开，但出站流量 (outbound traffic) 均能够正常透过 Wireguard 隧道访问。

Wireguard 在路由所有流量至隧道时（即设置 AllowedIps = 0.0.0.0/0 (IPv4) 或 ::/0 (IPv6)），[wg-quick](https://man.archlinux.org/man/wg-quick.8) 会配置一系列的策略路由规则和默认路由来完成这一目标。

# **[wg-quick 做了什么？](https://www.wireguard.com/netns/#routing-all-your-traffic)**

wg-quick 通过等价以下命令的结果完成路由所有流量的目标：

```bash
# 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

```

使用以下命令查看结果

``` bash
# 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.

<br/>

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 的流量情况

``` bash 
# tcpdump -i eth0 host <client_ip>

IP <client_ip>.<port> > <eth0_ip>.<listen_port>: Flags [.], ack 1, win 1028, length 0
......

```

发现只有入向流量，没有出向流量。

``` bash
# 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>的包通过eth0接口传出。

``` bash 
# ip rule add from <eth0_ip> lookup main
```

若要有更好的体验，可以使用 Wireguard 配置中的 PostUp 和 PostDown 控制

``` bash 
[Interface]
PostUp = ip rule add from <eth0_ip> lookup main
PostDown = ip rule del from <eth0_ip> lookup main
```

> 作者：Aiden <br/> 
原文地址：https://telegra.ph/Linux-Wireguard%E5%92%8C%E7%AD%96%E7%95%A5%E8%B7%AF%E7%94%B1-03-17
