nftables、masquerade:数据包通过错误的出站接口

nftables、masquerade:数据包通过错误的出站接口

我所做的非常简单,我使用端口转发并在 POSTROUTING 链上启用伪装:

table inet nat {
    chain prerouting {
        type nat hook prerouting priority -100; policy accept;
        ip daddr 198.51.100.105 counter dnat to 10.8.0.105 comment "host.example.com"
    }
    
    chain postrouting {
        type nat hook postrouting priority 100; policy accept;
        counter masquerade
    }
}

我的机器上有以下接口:

1: lo
2: ens18
3: ens19
4: wg0

路线是:

default via 203.0.113.1 dev ens18 onlink
10.8.0.0/24 dev wg0 proto kernel scope link src 10.8.0.1
198.51.100.0/24 dev ens19 proto kernel scope link src 198.51.100.3
203.0.113.0/24 dev ens18 proto kernel scope link src 203.0.113.134

规则是:

0:      from all lookup local
32764:  from all to 198.51.100.0/24 lookup subnets
32765:  from 198.51.100.0/24 lookup subnets
32766:  from all lookup main
32767:  from all lookup default

的输出ip route get 198.51.100.105是:

local 198.51.100.105 dev lo table local src 198.51.100.3 uid 0
    cache <local>

的输出ip route show table subnets

default via 198.51.100.1 dev ens19
198.51.100.0/24 dev ens19 scope link src 198.51.100.3

当我现在 ping民众从外部 VPS 或我家里的 DSL地址198.51.100.105,我可以验证不仅收到了 ICMP 数据包,而且还回复了:

ICMP 请求

root@debian:~# tcpdump -i ens19 icmp
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on ens19, link-type EN10MB (Ethernet), snapshot length 262144 bytes
21:35:51.686208 IP 192.0.2.2 > 198.51.100.105: ICMP echo request, id 14855, seq 1, length 64

ICMP 回复

root@debian:~# tcpdump -i ens18 icmp
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on ens18, link-type EN10MB (Ethernet), snapshot length 262144 bytes
21:35:38.797502 IP 198.51.100.105 > 192.0.2.2: ICMP echo reply, id 5107, seq 1, length 64

但正如您所看到的,响应来自错误的接口。应该是ens19,但是使用了ens18,我不明白为什么。

  1. 为什么不遵守路线/规则?子网198.51.100.0/24没有任何关系ens18,甚至是不同的VLAN。

  2. 难道 nftables 不能为伪装的数据包强制使用不同的出站接口吗?

非常感谢您的帮助。

编辑: 的输出ip -br link; ip -4 -br addr; ip -4 route; ip rule; ip rule show table subnets是:

ip -br link; ip -4 -br addr; ip -4 route; ip rule; ip rule show table subnets
lo               UNKNOWN        00:00:00:00:00:00 <LOOPBACK,UP,LOWER_UP>
ens18            UP             a6:ea:02:1c:XX:XX <BROADCAST,MULTICAST,UP,LOWER_UP>
ens19            UP             ac:71:fe:18:XX:XX <BROADCAST,MULTICAST,UP,LOWER_UP>
wg0              UNKNOWN        <POINTOPOINT,NOARP,UP,LOWER_UP>
lo               UNKNOWN        127.0.0.1/8
ens18            UP             203.0.113.134/24
ens19            UP             198.51.100.3/24 198.51.100.100/24 198.51.100.101/24 198.51.100.102/24 198.51.100.103/24 198.51.100.104/24 198.51.100.105/24
wg0              UNKNOWN        10.8.0.1/24
default via 203.0.113.1 dev ens18 onlink
10.8.0.0/24 dev wg0 proto kernel scope link src 10.8.0.1
198.51.100.0/24 dev ens19 proto kernel scope link src 198.51.100.3
203.0.113.0/24 dev ens18 proto kernel scope link src 203.0.113.134
0:      from all lookup local
32764:  from all to 198.51.100.0/24 lookup subnets
32765:  from 198.51.100.0/24 lookup subnets
32766:  from all lookup main
32767:  from all lookup default
32764:  from all to 198.51.100.0/24 lookup subnets
32765:  from 198.51.100.0/24 lookup subnets

线卫

命令输出wg

interface: wg0
  public key: XrSd2TftIpiL3zhXXX=
  private key: (hidden)
  listening port: 51820

peer: gZ89rFX6DvBtdeuYXXX=
  endpoint: 233.252.0.0:39126
  allowed ips: 10.8.0.0/24
  latest handshake: 20 seconds ago
  transfer: 3.42 MiB received, 4.08 MiB sent

命令输出:systemctl status [email protected]

[email protected] - WireGuard via wg-quick(8) for wg0
     Loaded: loaded (/lib/systemd/system/[email protected]; enabled; preset: enabled)
     Active: active (exited) since Sat 2023-08-05 23:42:48 CEST; 14h ago
       Docs: man:wg-quick(8)
             man:wg(8)
             https://www.wireguard.com/
             https://www.wireguard.com/quickstart/
             https://git.zx2c4.com/wireguard-tools/about/src/man/wg-quick.8
             https://git.zx2c4.com/wireguard-tools/about/src/man/wg.8
    Process: 690 ExecStart=/usr/bin/wg-quick up wg0 (code=exited, status=0/SUCCESS)
   Main PID: 690 (code=exited, status=0/SUCCESS)
        CPU: 27ms

Aug 05 23:42:48 debian systemd[1]: Starting [email protected] - WireGuard via wg-quick(8) for wg0...
Aug 05 23:42:48 debian wg-quick[690]: [#] ip link add wg0 type wireguard
Aug 05 23:42:48 debian wg-quick[690]: [#] wg setconf wg0 /dev/fd/63
Aug 05 23:42:48 debian wg-quick[690]: [#] ip -4 address add 10.8.0.1/24 dev wg0
Aug 05 23:42:48 debian wg-quick[690]: [#] ip link set mtu 1420 up dev wg0
Aug 05 23:42:48 debian systemd[1]: Finished [email protected] - WireGuard via wg-quick(8) for wg0.

服务器:/etc/wireguard/wg0.conf

[Interface]
Address = 10.8.0.1/24
SaveConfig = true
ListenPort = 51820
PrivateKey = 8BspU4XXX=

[Peer]
PublicKey = gZ89rFX6DvBtdeuYXXX=
AllowedIPs = 10.8.0.0/24
Endpoint = 233.252.0.0:58642

对等点:/etc/wireguard/wg0.conf

[Interface]
# Client Private Key
PrivateKey = iO00+qQDXXX=
Address = 10.8.0.107/24

[Peer]
# Server Public Key
PublicKey = XrSd2TftIpiL3zhXXX=
AllowedIPs = 10.8.0.0/24
PersistentKeepalive = 25
Endpoint = 198.51.100.3:51820

答案1

备注和调整(可能是混淆造成的):

  • 我假设 WireGuard 对等方使用 10.8.0.105 而不是 10.8.0.107,以匹配nftables规则集。
  • 233.252.0.0 会在模拟中引起问题(尤其是在同行)因为它是一个多播地址。我将在下面使用 192.0.2.233(与 192.0.2.2 没有网络关系)。

这个问题是为了解决使用 dnat 规则进行转发的问题,而不仅仅是本地流量。 WireGuard 隧道封套还有一个隐藏的问题也已在最后修复。

带有 ping 测试的路由堆栈的行为(包括预路由中发生的实际 dnat 和回复中将发生的取消伪装)可以用这两个命令来总结,这两个命令查询内核将使用什么路由:

# ip route get from 192.0.2.2 iif ens19 to 10.8.0.105
10.8.0.105 from 192.0.2.2 dev wg0 
    cache iif ens19 
# ip route get from 10.8.0.105 iif wg0 to 192.0.2.2
192.0.2.2 from 10.8.0.105 via 203.0.113.1 dev ens18 
    cache iif wg0 

在这里可以看到回复使用了错误的接口。该地址将被重写连线条目内容:仍然是 198.51.100.105,即使它没有出现在上面。

这是由缺失的规则引起的:任何来自(返回)的东西工作组0应该使用表格子网。固定为:

ip rule add iif wg0 lookup subnets

这也解决了这个问题rp_filter=1其中上面的第一个路由测试只会失败RTNETLINK answers: Invalid cross-device link,即使通常应该添加工作组0路线也在此表中。

现在给予:

# ip route get from 10.8.0.105 iif wg0 to 192.0.2.2
192.0.2.2 from 10.8.0.105 via 198.51.100.1 dev ens19 table subnets 
    cache iif wg0 

ping 测试现在可以正常工作了。


还有一个额外的隐藏的 WireGuard 信封路由问题。

组合:

  • 没有启用严格反向路径转发 (RFC 3704
  • 让对等方首先联系服务器(请参阅最后的附加问题)
  • (至少)内核实现确定它应该使用最初联系的相同源进行回复

允许 WireGuard 发挥作用,所以ping 10.8.0.1同行收到回复并允许任何后续 WireGuard 流量继续使用相同的信封地址。

当没有声明本地(非路由)流的源地址时,路由堆栈必须弄清楚给定路由应使用哪一个地址。这对于 UDP 来说尤其重要,其中套接字通常保持未绑定状态(即:具有源 0.0.0.0 又名 INADDR_ANY)。对于 TCP 服务器来说,这不是问题,因为之后创建的重复建立的套接字accept(2)不再绑定到 0.0.0.0,而是绑定到正确的地址:然后它将将此地址呈现给路由堆栈。此处,WireGuard 使用 UDP 和 INADDR_ANY。特别是它不绑定到 198.51.100.3。这意味着它显示为源 0.0.0.0,并将传出源 IP 地址的解析留给内核的路由堆栈。

如果服务器的 WireGuard 一直在启动第一个数据包(而不是同行这样做),它会使用 203.0.113.134 而不是 198.51.100.3:路由堆栈没有特定的ip规则对于 0.0.0.0:ip规则 32765: from 198.51.100.0/24 lookup subnets不匹配并且没有应用特殊的策略路由。最后,UDP 数据包使用 ens18 作为 203.0.113.134 离开。

看起来内核实现至少继续使用它被查询的相同地址。这并不可靠,使用 UDP 服务的多宿主需要特殊支持(例如:使用IP_PKTINFO)因此从应用程序中。

WireGuard 寻求的结果:

# ip route get from 198.51.100.105 to 192.0.2.233
192.0.2.233 from 198.51.100.105 via 198.51.100.1 dev ens19 table subnets uid 0 
    cache 

至少如果它是第一个发起流量的实际结果:

# ip route get from 0.0.0.0 to 192.0.2.233
192.0.2.233 via 203.0.113.1 dev ens18 src 203.0.113.134 uid 0 
    cache 

要真正修复 WireGuard 隧道多宿主路由本身,可以使用每 L4 协议路由规则:

ip rule add iif lo ipproto udp sport 51820 lookup subnets

iif lo是一种特殊语法,表示本地发起(非转发)流量,它与接口无关lo)。

给予:

# ip route get from 0.0.0.0 ipproto udp sport 51820 to 192.0.2.233
192.0.2.233 via 198.51.100.1 dev ens19 table subnets src 198.51.100.3 uid 0 
    cache 

尽管将 INADDR_ANY 作为源,但 UDP 源端口 51820 现在会选择子网路由表。

相关内容