通过 WireGuard 将流量转发回(处理应用程序后,为 oif wg0 设置 dnat)

通过 WireGuard 将流量转发回(处理应用程序后,为 oif wg0 设置 dnat)

我使用 WireGuard 作为不同 DC 中两台服务器之间的安全通信通道,以隐藏终端服务器(服务器 B)的存在。我使用 nftables 作为防火墙管理工具。

从公共服务器 A 转发流量,并保留 IP 地址(应用程序必需的)。

目标服务器 B 接收数据包,应用程序处理它们,但最终,服务器尝试将数据包返回到原始 IP(原始数据包的发送者 IP),这成为一个问题。

伪装原始 IP 看起来是一个简单的解决方案,但有必要保留服务器 B 上已有的原始 IP,以便将这些数据包路由回 WireGuard 隧道。

服务器B的tcpdump:

1:02:36.675958 wg0   In  IP 1.2.3.4.54617 > 10.0.0.2.21: Flags [S], seq 1265491449, win 64240, options [mss 1452,nop,wscale 8,nop,nop,sackOK], length 0
1:02:36.675980 docker0 Out IP 1.2.3.4.54617 > 172.16.0.2.21: Flags [S], seq 1265491449, win 64240, options [mss 1452,nop,wscale 8,nop,nop,sackOK], length 0
1:02:36.676030 docker0 In  IP 172.16.0.2.21 > 1.2.3.4.54617: Flags [S.], seq 1815055360, ack 1265491450, win 64240, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
1:02:36.676033 enp41s0 Out IP 10.0.0.2.21 > 1.2.3.4.54617: Flags [S.], seq 1815055360, ack 1265491450, win 64240, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
  • 1.2.3.4 - 原始IP
  • 10.0.0.2 - B 服务器上的 IP 线卫
  • 172.16.0.2 - docker 网络,让我们假设这是一个应用程序(一切正常)

不幸的是,我还没有找到解决这个问题的方法,所以我请求你的帮助。还可以吗?如果是,通过什么方式?

更新

我决定使用 HAProxy,但我认为这不是一个非常高性能的解决方案。所以我仍然需要这个问题的可能解决方案。

用于systemd-networkd配置 WireGuard 隧道:

# sudo cat /etc/systemd/network/99-wg0.netdev

[NetDev]
Name=wg0
Kind=wireguard
Description=WireGuard tunnel wg0

[WireGuard]
ListenPort=51820
PrivateKey=[key]

[WireGuardPeer]
PublicKey=[key]
PresharedKey=[key]
AllowedIPs=0.0.0.0/0
Endpoint=[server A]:51820

# sudo cat /etc/systemd/network/99-wg0.network

[Match]
Name=wg0

[Network]
Address=10.0.0.2/24
Address=fdc9:281f:04d7:9ee9::2/64

# sudo ip rule

0:      from all lookup local
32766:  from all lookup main
32767:  from all lookup default

nftables 配置:

# sudo nft list ruleset         
                                                                                                                                                               [0]
table inet filter {
        chain allow {
                ct state invalid drop comment "early drop of invalid connections"
                ct state { established, related } accept comment "allow tracked connections"

                ip protocol icmp accept comment "allow icmp"
                meta l4proto ipv6-icmp accept comment "allow icmp v6"

                icmp type echo-request limit rate over 10/second burst 4 packets drop comment "No ping floods"
                icmpv6 type echo-request limit rate over 10/second burst 4 packets drop comment "No ping floods"
        }

        chain wireguard {
                tcp dport 21 accept
        }

        chain input {
                type filter hook input priority filter; policy drop;

                iif "lo" accept comment "allow from loopback"

                tcp dport 22 ct state new limit rate 15/minute accept comment "Avoid brute force on SSH"
                tcp dport 22 accept comment "allow sshd"

                ip6 saddr [server A] udp dport 51820 accept comment "Accept wireguard connection from proxy1.vps-da4c9ada.ovh.zolotomc.ru"

                jump allow comment "allowed traffic for input"

                meta pkttype host limit rate 5/second counter packets 1047 bytes 43403 reject with icmpx admin-prohibited
                reject with icmpx host-unreachable
        }

        chain forward {
                type filter hook forward priority filter; policy drop;

                iif "docker0" accept comment "allow outgoing traffic from docker"

                jump allow comment "allowed traffic for forward"

                iif "wg0" jump wireguard comment "Wireguard chain"

                reject with icmpx host-unreachable
        }

        chain output {
                type filter hook output priority filter; policy accept;
        }
}

table inet nat {
        chain prerouting {
                type nat hook prerouting priority dstnat; policy accept;
                iif "wg0" jump wireguard comment "Wireguard chain"
        }

        chain wireguard {
                tcp dport 21 dnat ip to 172.16.0.2
                tcp dport 21 dnat ip6 to fe80::a8d7:f6ff:fe0b:4774
        }

        chain input {
                type nat hook input priority 100; policy accept;
        }

        chain output {
                type nat hook output priority -100; policy accept;
        }

        chain postrouting {
                type nat hook postrouting priority srcnat; policy accept;
                iif "docker0" oif != "docker0" masquerade
        }
}

我尝试使用设置数据包元信息,但它将端口重新分配给新的随机端口。然而,我开始认为这是不可能的或者太复杂了nftables规则。

答案1

正确回复所需的信息:“初始数据包来自哪个接口?”当在来自网络接口的回复中看到不同的数据包时,就会丢失。需要一种记住它并在回复中重复使用它的方法。这里是:连线它会记住所有当前跟踪的连接的列表。它还可以存储,每流一个标记,然后称为康马克。人们可以赋予一种价值以意义。

这允许申请策略路由到整个流而不仅仅是单个数据包。

博客Linux 及更高版本!那里解释说:Netfilter康马克

这个想法是在流使用 WireGuard 接口时记住信息,以便回复数据包与同一流关联,并通过 WireGuard 接口路由回来,而不是它们应该采用的通常路由。这可以由独立机构处理nftables表和关联的路由表/规则来改变数据包的正常命运。使用具有0xf00“此流来自”含义的任意标记值wg0和任意路由表 1000 来选择wg0回复。

准备足够的路由表和路由规则来覆盖回复数据包的正常路由:

ip route add default dev wg0 table 1000
ip rule add fwmark 0xf00 lookup 1000

如果也使用 IPv6(但 OP 的配置在 AllowedIPs 中缺少 IPv6),则还:

ip -6 route add default dev wg0 table 1000
ip -6 rule add fwmark 0xf00 lookup 1000

使用示例systemd-networkd

[Match]
Name=wg0

[Network]
Address=10.0.0.2/24
Address=fdc9:281f:04d7:9ee9::2/64

[Route]
Gateway=0.0.0.0
Table=1000

[RoutingPolicyRule]
Table=1000
FirewallMark=0xf00

并添加下表:

replywg0.nft

table inet replywg0         # for idempotency
delete table inet replywg0  # for idempotency

table inet replywg0 {
    chain prerouting {
        type filter hook prerouting priority -150; policy accept;
        iif wg0 ct mark set 0xf00
        iif != wg0 ct mark 0xf00 meta mark set 0xf00
    }

    chain output {
        type route hook output priority -150; policy accept;
        ct mark 0xf00 meta mark set 0xf00
    }
}

加载:

nft -f replywg0.nft

注意事项和警告:

  • 标记是唯一影响路由的标记,来自 时未设置wg0,仅设置康马克设置:否则它将选择表 1000 中定义的单个路由,并将数据包从其来源处路由回,而不是继续发送到本地系统或 Docker 容器。如果路由表 1000 包含所有(我不知道的)相关附加路由,则不需要这种特殊处理。

  • 如果输出链仅适用于 Docker 流量,那么输出链是完全可选的。它仅对以下有用当地的回复流量:收到的流量工作组0它没有发送到 Docker,而是发送到本地系统。请注意这里使用的type route是而不是type filter,因此仍然可以进行新的路由查找。无论如何,一些 UDP 服务不会在这里正确回复:IP 源地址可能是错误的(在 上设置的地址enp41s0),并且需要一些额外的不完美的 NAT 创可贴(例如表 inet nat 输出中的伪装)。

  • 如果以后对标记有其他需求,请小心(甚至 WireGuard 本身也有一个可以设置标记的选项):如果处理不当,它们可能会发生冲突。

  • FTP(出现在 OP 的设置中)很特殊:它使用额外的数据连接,并且可能需要ALG(通常在服务器情况下处于被动模式)。只要不加密,在 Linux 上就可以用内核模块处理nf_conntrack_ftp+nf_nat_ftp和足够的设置,例如:安全使用 iptables 和连接跟踪助手并具有(略有不同)等价物nftables设置。由于 RELATED 流量(数据流量)继承了康马克,该博客中描述的设置甚至可以与此答案中的设置一起使用。切换开关nf_conntrack_helper正在完全从内核 >= 6.0 中删除,因此这成为必需的设置。

  • 我假设rp_filter值不是 1,因为否则wg0一开始可能什么都不会发生。如果设置为 1,则需要在多个地方进行额外设置。

相关内容