使用 Wireguard 中的 FwMark 防止路由循环

使用 Wireguard 中的 FwMark 防止路由循环

我想设置一个 VPN 服务器,以便仅在访问服务器内的资源时使用 VPN 连接。通常,我会使用服务器的内部 IP 来执行此操作,但我想使用域名来访问此服务器。

有几种方法可以实现这一点:

  1. 使用自定义 DNS 服务器

  2. 将另一个 IP 绑定到此服务器

  3. 设置Endpoint为与AllowedIPs“客户端”中相同(我知道 wg 不使用服务器-客户端架构,但这样我更容易理解)。例如:

    # client
    [Interface]
    PrivateKey = ...
    Address = 10.0.0.2/24
    
    [Peer]
    PublicKey = ...
    AllowedIPs = 1.2.3.4/32
    Endpoint = 1.2.3.4:51820
    

长话短说,选项 3 最适合我的用例,但它会导致路由表中出现循环。在做了一些研究之后(“改进的基于规则的路由”部分wireguard 页面此解决方案),我了解到使用FwMark“服务器”配置可以解决这个问题。

所以我想出了这个:

# server
[Interface]
PrivateKey = ...
ListenPort = 51820
Address = 10.0.0.1/24
FwMark = 51820

PostUp = ip route add default dev wg0 table 2468
PostUp = ip rule add not fwmark 51820 table 2468

PostDown = ip route del default dev wg0 table 2468
PostDown = ip rule del not fwmark 51820 table 2468

[Peer]
...

不用说,这不起作用(VPN 和直接通信都超时了,所以我猜我搞乱了路由规则)我的问题是:

  1. 这为什么不起作用?
  2. FwMark = 51820“服务器”配置中的设置会标记 VPN 路由数据包吗?或者我需要类似的东西PostUp = wg set wg0 fwmark 51820
  3. 如果我2468defaultmain甚至替换会发生什么local?我想我不明白为什么官方文档必须为此设置一个新表。

谢谢

编辑:修复端口拼写错误

答案1

介绍

有两个命令:wg低级 WireGuard(“WG”)命令仅影响type wireguard接口的设置(包括设置传出信封流量的标记),wg-quick此外还将配置地址、路由,有时还会配置路由规则、附加路由表,甚至nftables将所有这些联系在一起的规则。

  1. 这个答案将解释如何让它工作

  2. FwMark = 51820标记传出的信封(不是有效载荷)。

    wg设置此参数后,将运行适当的命令。

  3. Table =告诉wg-quick在哪里添加路线。

    • 如果不提供,则默认为Table = auto。它的行为类似于,Table = main除非还有AllowedIPs = 0.0.0.0/0(或AllowedIPs = ::/0对于 IPv6),这会通过附加路由表、路由规则、标记和nftables(或者iptables如果nftables未安装)规则。这种改变的行为允许完整隧道 VPN(而不是拆分 VPN)配置正常工作。我稍后会给出一个例子。

    • 如果Table = off这样,则wg-quick根本不会触及路线。这一切都必须由管理员处理(并可能使用PreUp/PostUp规则)。

    • 其他有用的情况是选择一个任意表,稍后将使用自定义路由规则重新使用。此答案将Table = 2468在客户端上使用。wg-quick将向此路由表而不是main路由表添加路由。另一种选择可能是使用Table = off并添加任何带有条目的相关路由PostUp

    • 路由local表应保持不变。它由内核填充,并处理与系统上分配的地址相关的本地路由,用于接收发往该系统的传入流量(包括广播)。

    • 路由default表几乎从未使用过,在标准设置中为空。它可能在某些路由守护程序中使用。只有在路由表中没有默认路由的情况下,在其中包含条目才有意义main,否则该路由表将永远没有机会在默认路由规则下到达(main一旦找到 的默认路由,将停止查找)。

以下设置假设客户端运行的是 Linux,并且nftables安装及其主接口(将被命名eth0)或实际上其所有接口都有严格反向路径转发(“SRPF”)已启用(sysctlnet.conf.ipv4.all.rp_filter=1)。nftables规则如下,包括客户的极端情况段落,仅用于有信封传入流量必须经过充分标记,才能被视为使用正确的路由,并成功通过 SRPF 检查。如果没有启用此类设置,或者至少主接口 ( eth0) 设置为松散的 RPFnet.conf.ipv4.eth0.rp_filter=2)然后整个nftables部分可以忽略。那么就不再重要了,如果传入数据包使用了错误的路径:它们被接收,并将被路由堆栈使用,而不是被丢弃。以下任何部分处理nftables因此将假定客户端已经具备以下条件:

sysctl -w net.ipv4.conf.default.rp_filter=1
sysctl -w net.ipv4.conf.all.rp_filter=1

已经处理过一种情况,wg-quick其中还解释了rp_filter=1有效载荷地址必须与信封地址(使用默认路由的远程地址)区分开来:当使用默认设置Table = auto和时AllowedIPs = 0.0.0.0/0,这在客户端部署中很常见。然后wg-quick使用防火墙标记和附加nftables规则适用于这种情况。它看起来是这样的:

# wg-quick up wg0
[#] ip link add wg0 type wireguard
[#] wg setconf wg0 /dev/fd/63
[#] ip -4 address add 10.0.0.2/24 dev wg0
[#] ip link set mtu 1420 up dev wg0
[#] wg set wg0 fwmark 51820
[#] ip -4 route add 0.0.0.0/0 dev wg0 table 51820
[#] ip -4 rule add not fwmark 51820 table 51820
[#] ip -4 rule add table main suppress_prefixlength 0
[#] sysctl -q net.ipv4.conf.all.src_valid_mark=1
[#] nft -f /dev/fd/63
# ip rule
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
# ip route show table 51820
default dev wg0 scope link 
# nft list table ip wg-quick-wg0
table ip wg-quick-wg0 {
    chain preraw {
        type filter hook prerouting priority raw; policy accept;
        iifname != "wg0" ip daddr 10.0.0.2 fib saddr type != local drop
    }

    chain premangle {
        type filter hook prerouting priority mangle; policy accept;
        meta l4proto udp meta mark set ct mark
    }

    chain postmangle {
        type filter hook postrouting priority mangle; policy accept;
        meta l4proto udp meta mark 0x0000ca6c ct mark set meta mark
    }
}

WG 为传出流量设置的防火墙标记允许在路由规则中区分(传出)信封流量和(传出)有效负载流量。

nftables部分用于处理信封的回复/传入流量:

  • 按时间顺序 1,它更新于后布线连接跟踪流向 WG 设定的标记传出信封流量。

  • 然后,在预路由钩这个标记是从保存的连接跟踪流条目标记传入回复信封流量

  • 早期的预路由钩子(preraw)是为了防止接收到 WG 接口上设置的地址的非 WG 流量(不是为了路由,而是为了安全,下面就不保留了)

这些nftables规则最终允许将传入的信封流量视为路由规则中的信封流量。src_valid_mark=1所有这些努力都是为了确保流量不因 SRPF 失败。

因为wg-quick的 bash 代码只对Table = auto+触发它AllowedIPs = 0.0.0.0/0,所以必须在客户端上手动进行调整和完成(这也是过滤规则*结尾处的部分评论OP 链接的问答答案除非你在客户端上设置一些特殊的数据包路由/过滤规则,而不是使用 WireGuard 客户端为你设置的默认规则)。

客户端和服务器的配置

客户端/etc/wireguard/wg0.nft将从以下位置加载wg0.conf

table ip wg-quick-wg0        # for idempotence
delete table ip wg-quick-wg0 # for idempotence

table ip wg-quick-wg0 {
    chain postmangle {
        type filter hook postrouting priority mangle; policy accept;
        meta l4proto udp meta mark 1234 ct mark set meta mark
    }

    chain premangle {
        type filter hook prerouting priority mangle; policy accept;
        meta l4proto udp meta mark set ct mark
    }
}

客户端/etc/wireguard/wg0.conf(参考前一个文件):

[Interface]
PrivateKey = clientprivkey
FwMark = 1234
Table = 2468
Address = 10.0.0.2/24
# setting it on eth0 would have been enough
PreUp = sysctl -q -w net.ipv4.conf.all.src_valid_mark=1
PreUp = nft -f /etc/wireguard/wg0.nft
PostUp = ip -4 rule add not fwmark 1234 table 2468
# not actually needed for OP's current setup, but won't hurt 
PostUp = ip -4 rule add table main suppress_prefixlength 0
# wg-quick probably already deleted the table because of the chosen name but just in case...
PostDown = nft delete table wg-quick-wg0 2>/dev/null || true

[Peer]
PublicKey = serverpubkey
AllowedIPs = 1.2.3.4/32
Endpoint = 1.2.3.4:51820
# needed if client is behind NAT and can receive server-initiated WG traffic
PersistentKeepalive = 25

假设客户端主接口上的当前 IP 地址eth0为(NAT-ed...)192.168.1.2/24,网关为 192.168.1.1,以下是各种路由决策的结果:

# # outgoing payload
# ip route get 1.2.3.4
1.2.3.4 dev wg0 table 2468 src 10.0.0.2 uid 0 
    cache 
# # outgoing envelope: mark is set directly by WG
# ip route get 1.2.3.4 mark 1234
1.2.3.4 via 192.168.1.1 dev eth0 src 192.168.1.2 mark 0x4d2 uid 0 
    cache 

# # incoming payload
# ip route get from 1.2.3.4 iif wg0 to 10.0.0.2
local 10.0.0.2 from 1.2.3.4 dev lo table local 
    cache <local> iif wg0 
# # incoming envelope without mark: doesn't happen thanks to nftables. Would be rejected by SRPF
# ip route get from 1.2.3.4 iif eth0 to 192.168.1.2
RTNETLINK answers: Invalid cross-device link
# # incoming envelope: reply envelope traffic mark is set by nftables from conntrack entry
# ip route get from 1.2.3.4 iif eth0 to 192.168.1.2 mark 1234
local 192.168.1.2 from 1.2.3.4 dev lo table local mark 0x4d2 
    cache <local> iif eth0 

服务器设置要简单得多,因为它自己的端点是已知配置,并且 WG 内的客户端有效负载地址是 Internet 上不可路由的地址,不会与客户端的信封地址冲突。没有nftables涉及服务器。

服务器/etc/wireguard/wg0.conf

[Interface]
PrivateKey = serverprivkey
ListenPort = 51820
#no address set on wg0

[Peer]
PublicKey = clientpubkey
AllowedIPs = 10.0.0.2

如果此服务器的主接口eth0的地址为 1.2.3.4/24,网关为 1.2.3.1/24,当到达服务器时,发现远程 WG 对等体(客户端)为 192.0.2.2:45678,则各种路由情况都很简单,如下所示:

# # outgoing payload 
# ip route get 10.0.0.2
10.0.0.2 dev wg0 src 1.2.3.4 uid 0 
    cache 
# # outgoing envelope
# ip route get 192.0.2.2
192.0.2.2 via 1.2.3.1 dev eth0 src 1.2.3.4 uid 0 
    cache 

# # incoming payload
# ip route get from 10.0.0.2 iif wg0 1.2.3.4
local 1.2.3.4 from 10.0.0.2 dev lo 
    cache <local> iif wg0 
# # incoming envelope
# ip route get from 192.0.2.2 iif eth0 1.2.3.4
local 1.2.3.4 from 192.0.2.2 dev lo 
    cache <local> iif eth0 

没有添加任何地址来wg0防止常见的 UDP 多宿主复杂化。如果添加了此地址,到达 10.0.0.2 的源地址将被选为 10.0.0.1,从而触发通过隧道应答的多宿主无感知 UDP 服务的问题:对 1.2.3.4 的查询的 UDP 回复将使用 10.0.0.1 作为源,这将被客户端应用程序拒绝(甚至在此之前,由于 10.0.0.1 是不允许的,因此会被其 WG 接口丢弃)。这些问答中有更多详细信息,我在其中进行了回答:

避免遇到 UDP 多宿主问题的最好方法是不是在多宿主情况下,不添加 IP 地址wg0。当源地址选择算法无法从中选择地址时,它将选择“主”IP 地址。如果服务器已经是多宿主的,并且不知何故选择了错误的源地址,那么可以使用服务器的此附加条目wg0修复添加的路由:wg-quickwg0.conf

PostUp = ip route replace 10.0.0.2/32 dev wg0 src 1.2.3.4

这将提示地址选择算法使用 1.2.3.4 作为源以到达 10.0.0.2。这还可以允许设置 10.0.0.1(wg0即使它不会被使用)并更改整个 /24 的此类路由,然后在有多个对等点的情况下,例如使用以下而不是上面的方法:

Address = 10.0.0.1/24
PostUp = ip route replace 10.0.0.0/24 dev wg0 src 1.2.3.4

此路由更改不会保留或遵循影响实际 1.2.3.4 的某些操作(例如:主接口在管理上先关闭然后打开)。实际上,只需在 上“再次”添加 1.2.3.4 即可wg0。例如,而不是上面的(确保它是 /32,这样它就不会触发创建任何导致网络中断的其他路由):

Address = 1.2.3.4/32

这是针对作为终端节点的服务器而言的。如果它充当路由器并路由流量(例如:容器或虚拟机)而不是自己生成流量,那么就不会出现 UDP 多宿主问题,但无论如何,处理此路由器情况的设置很可能会缺失。

客户的极端情况

如果客户端不使用 SRPF(rp_filter=1),那么就像所有其他nftables设置,不需要应用以下修复。

在绕过 WG 到达服务器时,客户端上存在一些特殊情况,可以使用适当的应用程序setsockopt(sockfd, SOL_SOCKET, SO_MARK, ...)

  • 跟踪路由(--udp)

    需要路由追踪通过互联网访问服务器来查明发生了什么网络中断?

    traceroute -n --fwmark=1234 1.2.3.4
    

    在显示目标之前,这仍然会超时,因为预期的返回流量不是 UDP,而是相关的 ICMP 错误:未设置标记并被 SRPF 拒绝。可以通过添加规则标记来修复有关的交通:

    nft add rule ip wg-quick-wg0 premangle ct state related ct mark 1234 meta mark set ct mark
    
  • ping,其他跟踪路由,带有 TCP 的 socat……

    ping -n -m 1234 1.2.3.4
    traceroute -n --icmp --fwmark=1234 1.2.3.4
    socat exec:'echo foo' TCP4:1.2.3.4:3333,setsockopt-listen=1:36:L1234 
    

    还系统地标记和检索 conntrack 流的标记,而不仅仅是 UDP:

    nft add rule ip wg-quick-wg0 postmangle meta mark 1234 ct mark set meta mark
    nft add rule ip wg-quick-wg0 premangle ct mark 1234 meta mark set ct mark
    

    这使得上述 3 个命令有效,并且不会超时。

相关内容