我有一个服务器,其中一些 KVM 虚拟机使用 libvirt 和一个 brige 网络 (192.168.123.0/24) 运行。我正在尝试将一个端口从主机的公共 IP 转发到其中一个虚拟机(192.168.123.103)。从互联网访问时它可以正常工作,并且有一个附加规则它也可以在主机上工作,但我无法让它在虚拟网络内的机器上工作。
这是 iptables 配置的相关(我认为)部分:
# Generated by iptables-save v1.6.0 on Tue Sep 13 16:16:26 2016
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-A PREROUTING -d x.x.x.x/32 -p tcp -m tcp --dport 7000 -j DNAT --to-destination 192.168.123.103
-A OUTPUT -d x.x.x.x/32 -o lo -p tcp -m tcp --dport 7000 -j DNAT --to-destination 192.168.123.103
-A POSTROUTING -s 192.168.123.0/24 -d 224.0.0.0/24 -j RETURN
-A POSTROUTING -s 192.168.123.0/24 -d 255.255.255.255/32 -j RETURN
-A POSTROUTING -s 192.168.123.0/24 ! -d 192.168.123.0/24 -p tcp -j MASQUERADE --to-ports 1024-65535
-A POSTROUTING -s 192.168.123.0/24 ! -d 192.168.123.0/24 -p udp -j MASQUERADE --to-ports 1024-65535
-A POSTROUTING -s 192.168.123.0/24 ! -d 192.168.123.0/24 -j MASQUERADE
COMMIT
# Completed on Tue Sep 13 16:16:26 2016
# Generated by iptables-save v1.6.0 on Tue Sep 13 16:16:26 2016
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -i lo -j ACCEPT
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -m conntrack --ctstate INVALID -j DROP
-A FORWARD -i lo -j ACCEPT
-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -m conntrack --ctstate INVALID -j DROP
-A FORWARD -d 192.168.123.103/32 -p tcp -m tcp --dport 7000 -j ACCEPT
-A FORWARD -s 192.168.123.0/24 -i virbr1 -j ACCEPT
-A FORWARD -i virbr1 -o virbr1 -j ACCEPT
COMMIT
# Completed on Tue Sep 13 16:16:26 2016
当从 192.168.123.0/24 网络中的另一台机器访问 192.168.123.103:7000 时,我看到一个从 192.168.123.36 到 xxxx 的 SYN 数据包,然后将目标替换为 192.168.123.103 的相同数据包,然后从 192.168 进行 SYN-ACK .123.103 到 192.168.123.36,然后从 192.168.123.36 到 192.168.123.103 重复 RST 数据包。
有谁知道如何使这项工作?
我现在看到的唯一解决方法是强制 VM 内的 DNS 指向私有 IP,但如果我可以将公共 DNS 保留在那里,我更愿意。
第一:忽略 KVM、VM 和桥接器。想象一下,任何地方都没有虚拟机,也没有网桥。这个网桥只是“局域网”,里面有服务器,还有一个路由器(KVM 主机)将局域网路由到外部。它有助于思考它并且它更通用。
总结这些数据包发生了什么:
网络 L 中的客户端 C (192.168.123.36) 向不同网络 O 中的另一个 IP B (xxxx) 发送数据包。
路由器 R 将目标转换为同一网络 L 中的服务器 S (192.168.123.103) 并将其发送回网络 L。R 不会更改源 C。
S 接收到来自同一网络中的客户端的数据包并直接应答,无需路由器 R。
C 收到来自 S 的未知 SYN+ACK 用于从未询问的连接(请记住 C 请求 B 并且不知道 S)。C 将 RST 发送回 S 以中止它。
C 仍在等待来自 B 的 SYN+ACK,它永远不会到来。C 重试...
如何让事情发挥作用?找到一种方法,让从 S 返回的流由 R 再次路由。路由器 R 知道该流,因此它将能够将其处理回 C。对于 S 通过 R 将数据包发送回,目标 musn'不要在网络 L 中。最简单的配置是使用 B,因为您已经知道它(并且您已经在某些规则中拥有它,因此不会增加复杂性)。因此,路由器 R 还必须在连接开始时将源地址从 C 更改为 B。
在路由器 R 中更改源地址的规则必须在 POSTROUTING 中,但随后数据包不再携带目标 B(它已在 PREROUTING 中更改),这增加了识别它的难度。
请注意 MASQUERADE 规则不是问题的一部分,只有 DNAT 以及 C 和 S 在同一个 LAN 中的事实会导致它。
解决它的两种方法:
由于数据包被路由并从同一接口返回的唯一方法是使用 DNAT 规则,因此假设所有来自 L 并传出到 L 的数据包都是此类数据包。这很简单,但可能需要仔细检查安全考虑,尤其是在规则使用 IP 而从不使用接口名称的情况下。
附加到您的规则:
不要假设任何事情,在 PREROUTING 中标记即将被 DNATed 的数据包,然后在 POSTROUTING 中使用该标记来识别它。只需注意路由器内部的数据包后面有一个标记,当然不是在线上。在此 conntrack 将处理流之后,这里只需要并用于第一个数据包。此解决方案允许解决各种限制,例如想要
-i interface
在 POSTROUTING 中使用,打算在 PREROUTING 中使用 SNAT(如这里)......只需在第一步中标记它并在第二步中使用标记来完成它。因此,改为插入这些规则:
注意:S 将只能看到 LAN L 中所有客户端的 IP xxxx。为此还有其他方法(例如:使用 NETMAP 和假的未使用 LAN 来唯一映射每个客户端以用于日志记录)