我使用 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,则需要在多个地方进行额外设置。