我有一台运行libvirt和brigenetworking(192.168.123.0/24)的KVM虚拟机的服务器。 我试图转发一个端口从主机的公共IP到其中一个虚拟机(192.168.123.103)。 它从互联网访问时正常工作,并从主机还有一个额外的规则,但我无法从虚拟networking内的机器得到它的工作。
这是iptablesconfiguration的相关(我认为)的一部分:
# 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 xxxx/32 -p tcp -m tcp --dport 7000 -j DNAT --to-destination 192.168.123.103 -A OUTPUT -d xxxx/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/24networking中的另一台机器访问192.168.123.103:7000时,我看到一个从192.168.123.36到xxxx的SYN数据包,然后将目的地replace为192.168.123.103的同一个数据包,然后从192.168 .123.103到192.168.123.36,然后从192.168.123.36到192.168.123.103重复RST包。
有没有人有一个想法如何使这项工作?
我现在看到的唯一的解决办法是迫使虚拟机内的DNS指向私有IP,但我更喜欢,如果我可以保留公共DNS在那里。
首先:忽略KVM,虚拟机和网桥。 想象一下,没有虚拟机,任何地方都没有桥。 这个桥只是内部有服务器的“局域网”,还有一台路由器(KVM主机)将局域网路由到外部。 它有助于思考,它更通用。
总结这些数据包发生了什么事情:
LAN(L) OUTSIDE(O) C ----> R(B) ^ / | / | / | / | / S<
networkingL中的客户端C(192.168.123.36)将数据包发送到不同networkingO中的另一个IP B(xxxx)。
路由器R将目的地转换为同一networkingL中的服务器S(192.168.123.103),并将其发送回networkingL.R不改变源C.
S收到来自同一networking客户端的数据包,直接回应,而不需要Router R
C收到一个来自S的未知的SYN + ACK,用于一个从不问的连接(记住C要求B并且不知道S)。 C发回一个RST到S中止它。
C仍在等待来自B的SYN + ACK,它永远不会到来。 C重试…
如何使事情工作? find一种方法让从S返回的stream再次由R路由。路由器R知道stream,所以它将能够处理它回到C.对于S通过R发回数据包, t在networkingL.最简单的configuration是使用B,因为你已经知道它(你已经在一些规则,所以没有复杂性添加)。 因此,路由器R在连接开始时也必须将源地址从C更改为B.
在路由器R中改变源地址的规则必须在POSTROUTING中,但是这个数据包不再携带目标B(它已经在PREROUTING中进行了更改),这增加了识别它的难度。
请注意,MASQUERADE规则不是问题的一部分,只有DNAT和C和S在同一个局域网中的事实导致它。
两种解决方法:
由于分组被路由并从相同接口返回的唯一方法是使用DNAT规则,假设所有来自L和去往L的分组都是这样的分组。 这很简单,但是可能需要仔细考虑安全因素,特别是如果规则使用IP并且不使用接口名称。
附加到您的规则:
iptables -t nat -A POSTROUTING -s 192.168.123.0/24 -d 192.168.123.0/24 -j SNAT --to-source xxxx
不要假设任何东西,在PREROUTING中标记要删除的数据包,稍后在POSTROUTING中使用该标记进行标识。 只要注意一个标记跟在路由器内的数据包,当然不在线路上。 在这个conntrack处理stream程之后,这里只需要用到第一个数据包。 这个解决scheme允许解决各种限制,比如想要在POSTROUTING中使用-i interface ,打算在PREROUTING中使用SNAT(就像这里)……只需在第一步中标记它,然后在第二步中使用标记。
所以,相反,插入这些规则:
#has to be before the DNAT: DNAT is final, MARK will continue iptables -t nat -I PREROUTING -s 192.168.123.0/24 -d xxxx/32 -p tcp -m tcp --dport 7000 -j MARK --set-mark 1 iptables -t nat -A POSTROUTING -m mark --mark 1 -j SNAT --to-source xxxx
注意:对于LAN L中的所有客户端,S将只能看到IP xxxx。还有其他的方法(例如:使用NETMAP和一个假的未使用的LAN来让每个客户端唯一地映射以便logging)