Iptables - 无法使我的自定义 SNAT 规则发挥作用

Iptables - 无法使我的自定义 SNAT 规则发挥作用

简单来说,我有两个网络,一个用于 docker,另一个用于 Libvirt。我需要让 docker 网络中的一个容器能够访问 Libvirt 网络中的所有虚拟机。因此,我添加了一条 SNAT 规则,以匹配来自“172.17.0.4”(容器 IP)并发往“192.168.122.0/24”(libvirt 网络)的任何数据包,并针对这些数据包进行 SNAT,如下所示

sudo iptables -t NAT -I POSTROUTING 1 -s 172.17.0.4 -d 192.168.122.0/24 -j SNAT --to 192.168.122.1

但不幸的是,当我尝试发送一些回声时,没有任何数据包符合规则,并且我在 docker 级别收到“目标端口不可达”错误,并且数据包永远无法到达任何虚拟机。

经过调查后,我在 libvirt 创建的过滤表中发现了以下链:

Chain LIBVIRT_FWO (1 references)
target     prot opt source               destination         
ACCEPT     all  --  192.168.121.0/24     anywhere            
REJECT     all  --  anywhere             anywhere             reject-with icmp-port- 
unreachable
ACCEPT     all  --  192.168.122.0/24     anywhere            
REJECT     all  --  anywhere             anywhere             reject-with icmp-port-unreachable

以下是容器中 ip route 命令的输出:

default via 172.17.0.1 dev eth0 
172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.4 

有任何想法吗?

答案1

您不需要任何自定义 NAT 规则。

离开容器的流量将符合MASQUERADEDocker 为网络安装的规则(例如-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE),因此您的虚拟机将看到来自 libvirt 桥的流量(192.168.122.1)。

配置

我有一个名为的 Docker 容器example-container附加到docker0

$ docker ps
[...]
0ddd3554466b   alpine                         "/bin/sh"                5 seconds ago   Up 4 seconds                                                                                             example-container

该容器具有以下网络配置:

/ # ip addr show eth0
2642: eth0@if2643: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fdc9:87ad:f15:5bea:0:242:ac11:2/64 scope global flags 02
       valid_lft forever preferred_lft forever
    inet6 fe80::42:acff:fe11:2/64 scope link
       valid_lft forever preferred_lft forever

我有一个名为的虚拟机example-vm连接到 libvirtdefault网络:

$ virsh list
 Id   Name         State
----------------------------
 2    example-vm   running

虚拟机具有以下网络配置:

[root@localhost ~]# ip addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 52:54:00:30:7b:a6 brd ff:ff:ff:ff:ff:ff
    altname enp1s0
    inet 192.168.122.70/24 brd 192.168.122.255 scope global dynamic noprefixroute eth0
       valid_lft 3548sec preferred_lft 3548sec
    inet6 fe80::2ccd:72ed:1fea:ccda/64 scope link noprefixroute
       valid_lft forever preferred_lft forever

从容器到虚拟机

从容器内部,我可以访问ping虚拟机:

/ # ping -c2 192.168.122.70
PING 192.168.122.70 (192.168.122.70): 56 data bytes
64 bytes from 192.168.122.70: seq=0 ttl=63 time=0.262 ms
64 bytes from 192.168.122.70: seq=1 ttl=63 time=0.286 ms

--- 192.168.122.70 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.262/0.274/0.286 ms

如果我tcpdump在执行此操作时在虚拟机内运行,我会看到:

[root@localhost ~]# tcpdump -i eth0 -n icmp
listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
07:23:38.661246 IP 192.168.122.1 > 192.168.122.70: ICMP echo request, id 9, seq 0, length 64
07:23:38.661277 IP 192.168.122.70 > 192.168.122.1: ICMP echo reply, id 9, seq 0, length 64
07:23:39.661247 IP 192.168.122.1 > 192.168.122.70: ICMP echo request, id 9, seq 1, length 64
07:23:39.661261 IP 192.168.122.70 > 192.168.122.1: ICMP echo reply, id 9, seq 1, length 64

从虚拟机到容器

类似地,我可以从虚拟机 ping 该容器:

[root@localhost ~]# ping -c2 172.17.0.2
PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.
64 bytes from 172.17.0.2: icmp_seq=1 ttl=63 time=0.149 ms
64 bytes from 172.17.0.2: icmp_seq=2 ttl=63 time=0.127 ms

--- 172.17.0.2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1035ms
rtt min/avg/max/mdev = 0.127/0.138/0.149/0.011 ms

tcpdump在容器中显示:

/ # tcpdump -i eth0 -n
listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
12:24:48.690361 IP 172.17.0.1 > 172.17.0.2: ICMP echo request, id 2, seq 1, length 64
12:24:48.690373 IP 172.17.0.2 > 172.17.0.1: ICMP echo reply, id 2, seq 1, length 64
12:24:49.723189 IP 172.17.0.1 > 172.17.0.2: ICMP echo request, id 2, seq 2, length 64
12:24:49.723204 IP 172.17.0.2 > 172.17.0.1: ICMP echo reply, id 2, seq 2, length 64

Netfilter 规则

这是有效的,因为 Docker 安装了以下规则:

host# iptables -t nat -S POSTROUTING
-P POSTROUTING ACCEPT
[...]
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
[...]

当流量离开容器时,它会触发规则MASQUERADE。由于 libvirtvirbr0接口是该数据包的出口接口,因此它会被重写为网桥的地址,这就是为什么在tcpdump虚拟机的输出中我们看到ping来自 192.168.122.1 的请求。

同样,libvirt安装以下规则:

host# iptables -t nat -S LIBVIRT_PRT
[...]
-A LIBVIRT_PRT -s 192.168.122.0/24 -d 224.0.0.0/24 -j RETURN
-A LIBVIRT_PRT -s 192.168.122.0/24 -d 255.255.255.255/32 -j RETURN
-A LIBVIRT_PRT -s 192.168.122.0/24 ! -d 192.168.122.0/24 -p tcp -j MASQUERADE --to-ports 1024-65535
-A LIBVIRT_PRT -s 192.168.122.0/24 ! -d 192.168.122.0/24 -p udp -j MASQUERADE --to-ports 1024-65535
-A LIBVIRT_PRT -s 192.168.122.0/24 ! -d 192.168.122.0/24 -j MASQUERADE
[...]

它们完成相同的事情;从虚拟机到容器的请求被重写为docker0桥的地址。

答案2

由于未知原因,我在 iptables 过滤表中发现了以下条目

-A LIBVIRT_FWI -o virbr1 -j REJECT --reject-with icmp-port-unreachable

删除后,我可以从 docker 容器访问 libvirt 网络,没有任何问题。

感谢 Tero 和 Larsks 的帮助。

相关内容