简单来说,我有两个网络,一个用于 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 规则。
离开容器的流量将符合MASQUERADE
Docker 为网络安装的规则(例如-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 的帮助。