Iptables — не могу заставить работать мои собственные правила SNAT

Проще говоря, у меня есть две сети: одна для докера, другая для Libvirt. Мне нужно разрешить одному контейнеру из сети докеров обращаться ко всем виртуальным машинам в сети 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

Но, к сожалению, когда я попытался отправить несколько эхо-сигналов, ни один из пакетов не соответствовал правилу, и я получаю ошибку «Порт назначения недоступен» на уровне докера, и пакеты никогда не достигают ни одной из виртуальных машин.

Изучив это, я нашел приведенную ниже цепочку в таблице фильтров, созданной 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 

Любые идеи?

2 ответа

Вам не нужны какие-либо специальные правила NAT.

Трафик, выходящий из контейнеров, будет соответствовать правилу, которое Docker устанавливает для сети (например,-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE), поэтому ваши виртуальные машины будут видеть трафик, исходящий от моста libvirt (192.168.122.1).

Конфигурация

У меня есть контейнер Docker с именемexample-containerприкреплен к:

      $ 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 -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

Если я при этом запущу виртуальную машину, я увижу:

      [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

Из виртуальной машины в контейнер

Аналогично я могу пропинговать контейнер с виртуальной машины:

      [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 -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

Правила сетевого фильтра

Это работает, потому что 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мост.

По неизвестной причине я нашел следующую запись в таблице фильтров iptables.

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

После его удаления я могу без проблем получить доступ к сети libvirt из контейнера докеров.

Спасибо Теро и Ларксу за вашу помощь.

Другие вопросы по тегам