Назад | Перейти на главную страницу

Докер ломает сеть моста libvirt

Эта проблема сводит меня с ума. Я запускаю новую установку Ubuntu 18.04 с:

Я попробовал стандартный пакет docker.io, а пакеты образуют собственный репозиторий deb docker.

Я хочу иметь возможность развертывать контейнеры докеров, выбирая ip для привязки его порта (например, -p 10.58.26.6:98800:98800), а затем открывать порт с помощью UFW.

Но докер, похоже, создает правила iptables, которые нарушают мост br0 (например, хост не может пинговать гостей libvirt)

Я осмотрелся и не нашел хорошего, безопасного решения.

Вручную делаю iptables -I FORWARD -i br0 -o br0 -j ACCEPTвроде заставляет все работать.

Также установка "iptables": false демон docker позволяет мосту вести себя нормально, но нарушает выходную сеть контейнеров докеров.

Я нашел это решение, которое казалось простым, отредактировав один файл UFW https://stackoverflow.com/a/51741599/1091772, но это совсем не работает.

Каков наилучший и безопасный способ решить эту проблему навсегда, выживая до перезагрузки?

РЕДАКТИРОВАТЬ: В итоге я добавил -A ufw-before-forward -i br0 -o br0 -j ACCEPT в конце /etc/ufw/before.rules перед COMMIT. Могу ли я считать это исправлением или это не вызывает проблем?

Проблема, собственно, особенность: br_netfilter

Из описания я считаю, что единственное логическое объяснение состоит в том, что код сетевого фильтра моста включен: предназначен, среди прочего, для межсетевого экрана моста с отслеживанием состояния или для использования iptables'совпадения и цели из пути моста без необходимости (или возможности) дублировать их все в ebtables. Совершенно игнорируя иерархию сети, код Ethernet-моста на сетевом уровне 2 теперь выполняет восходящие вызовы iptables работает на уровне IP, т.е. на сетевом уровне 3. Он может быть включен пока только глобально: либо для хоста и всех контейнеров, либо ни для одного. Поняв, что происходит, и зная, что искать, можно будет сделать адаптированный выбор.

Проект netfilter описывает различные ebtables/iptables взаимодействия когда br_netfilter включен. Особенно интересен Раздел 7 объясняя, почему некоторые правила без видимого эффекта иногда необходимы, чтобы избежать непреднамеренных эффектов от пути моста, например, использование:

iptables -t nat -A POSTROUTING -s 172.16.1.0/24 -d 172.16.1.0/24 -j ACCEPT
iptables -t nat -A POSTROUTING -s 172.16.1.0/24 -j MASQUERADE

чтобы две системы в одной локальной сети не преобразовывались через ... мост (см. пример ниже).

У вас есть несколько вариантов, чтобы избежать вашей проблемы, но выбор, который вы выбрали, вероятно, лучший, если вы не хотите знать все детали или проверять, будут ли нарушены некоторые правила iptables (иногда скрытые в других пространствах имен):

  • навсегда предотвратить br_netfilter модуль для загрузки. Обычно blacklist недостаточно, install необходимо использовать. Это выбор, связанный с проблемами для приложений, использующих br_netfilter: очевидно, Docker, Kubernetes, ...

    echo install br_netfilter /bin/true > /etc/modprobe.d/disable-br-netfilter.conf
    
  • Загрузите модуль, но отключите его эффекты. Для iptables'эффекты, то есть:

    sysctl -w net.bridge.bridge-nf-call-iptables=0
    

    Если поставить это при запуске, модуль должен быть загружен первым, иначе этот переключатель еще не существует.

Эти два предыдущих выбора наверняка нарушат iptables соответствие -m physdev: The xt_physdev модуль при загрузке автоматически загружает br_netfilter модуль (это произойдет, даже если правило, добавленное из контейнера, инициирует загрузку). Сейчас br_netfilter не будет загружен, -m physdev вероятно, никогда не совпадет.

  • При необходимости можно обойти эффект br_netfilter, например OP: добавить эти очевидные правила запрета операций в различных цепочках (PREROUTING, FORWARD, POSTROUTING), как описано в Раздел 7. Например:

    iptables -t nat -A POSTROUTING -s 172.18.0.0/16 -d 172.18.0.0/16 -j ACCEPT
    
    iptables -A FORWARD -i br0 -o br0 -j ACCEPT
    

    Эти правила никогда не должны совпадать, потому что трафик в одной IP LAN не маршрутизируется, за исключением некоторых редких настроек DNAT. Но благодаря br_netfilter они совпадают, потому что они в первую очередь переключился кадры ("обновленные" до IP-пакетов), проходящие через мост. Затем их снова призывают направлен пакеты, проходящие через маршрутизатор к несвязанному интерфейсу (но тогда не будет совпадать).

  • Не помещайте IP на мост: поместите этот IP на один конец veth интерфейс со своим другим концом на мосту: это должно гарантировать, что мост не будет взаимодействовать с маршрутизацией, но это не то, что делает большинство общих продуктов контейнеров / виртуальных машин.

  • Вы даже можете скрыть мост в его собственном изолированном сетевом пространстве имен (это будет полезно, только если вы хотите изолировать его от других ebtables на этот раз правила).

  • Переключите все на столы которые среди заявленных целей позволят избежать этих вопросы мостового взаимодействия. На данный момент межсетевой экран моста не имеет поддержки с отслеживанием состояния, он все еще НЗП но обещают, что он будет чище, когда будет доступен, потому что не будет никакого "апскола".

Вам следует поискать, что вызывает загрузку br_netfilter (например: -m physdev) и посмотрите, сможете ли вы этого избежать, чтобы выбрать, как действовать дальше.


Пример с сетевыми пространствами имен

Давайте воспроизведем некоторые эффекты, используя сетевое пространство имен. Обратите внимание, что нигде нет ebtables будет использоваться правило. Также обратите внимание, что в этом примере используется обычное устаревшее iptablesне iptables через nftables как включено по умолчанию в Debian buster.

Давайте воспроизведем простой случай, похожий на использование многих контейнеров: маршрутизатор 192.168.0.1/192.0.2.100 выполняет NAT с двумя узлами позади: 192.168.0.101 и 192.168.0.102, соединенными с мостом на маршрутизаторе. Два хоста могут связываться напрямую в одной локальной сети через мост.

#!/bin/sh

for ns in host1 host2 router; do
    ip netns del $ns 2>/dev/null || :
    ip netns add $ns
    ip -n $ns link set lo up
done

ip netns exec router sysctl -q -w net.ipv4.conf.default.forwarding=1

ip -n router link add bridge0 type bridge
ip -n router link set bridge0 up
ip -n router address add 192.168.0.1/24 dev bridge0

for i in 1 2; do
    ip -n host$i link add eth0 type veth peer netns router port$i
    ip -n host$i link set eth0 up
    ip -n host$i address add 192.168.0.10$i/24 dev eth0
    ip -n host$i route add default via 192.168.0.1
    ip -n router link set port$i up master bridge0
done

#to mimic a standard NAT router, iptables rule voluntarily made as it is to show the last "effect"
ip -n router link add name eth0 type dummy
ip -n router link set eth0 up
ip -n router address add 192.0.2.100/24 dev eth0
ip -n router route add default via 192.0.2.1
ip netns exec router iptables -t nat -A POSTROUTING -s 192.168.0.0/24 -j MASQUERADE

Загрузим модуль ядра br_netfilter (чтобы убедиться, что это не будет позже) и отключите его эффекты с помощью переключателя (not-per-namespace) мост-NF-call-iptables, доступно только в начальном пространстве имен:

modprobe br_netfilter
sysctl -w net.bridge.bridge-nf-call-iptables=0

Предупреждение: опять же, это может нарушить iptables правила вроде -m physdev где угодно на хосте или в контейнерах, которые полагаются на br_netfilter загружен и включен.

Давайте добавим счетчики трафика icmp ping.

ip netns exec router iptables -A FORWARD -p icmp --icmp-type echo-request
ip netns exec router iptables -A FORWARD -p icmp --icmp-type echo-reply

Пингуем:

# ip netns exec host1 ping -n -c2 192.168.0.102
PING 192.168.0.102 (192.168.0.102) 56(84) bytes of data.
64 bytes from 192.168.0.102: icmp_seq=1 ttl=64 time=0.047 ms
64 bytes from 192.168.0.102: icmp_seq=2 ttl=64 time=0.058 ms

--- 192.168.0.102 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1017ms
rtt min/avg/max/mdev = 0.047/0.052/0.058/0.009 ms

Счетчики не совпадают:

# ip netns exec router iptables -v -S FORWARD
-P FORWARD ACCEPT -c 0 0
-A FORWARD -p icmp -m icmp --icmp-type 8 -c 0 0
-A FORWARD -p icmp -m icmp --icmp-type 0 -c 0 0

Давайте включим мост-NF-call-iptables и снова пинг:

# sysctl -w net.bridge.bridge-nf-call-iptables=1
net.bridge.bridge-nf-call-iptables = 1
# ip netns exec host1 ping -n -c2 192.168.0.102
PING 192.168.0.102 (192.168.0.102) 56(84) bytes of data.
64 bytes from 192.168.0.102: icmp_seq=1 ttl=64 time=0.094 ms
64 bytes from 192.168.0.102: icmp_seq=2 ttl=64 time=0.163 ms

--- 192.168.0.102 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1006ms
rtt min/avg/max/mdev = 0.094/0.128/0.163/0.036 ms

На этот раз коммутируемые пакеты совпали в цепочке filter / FORWARD iptables:

# ip netns exec router iptables -v -S FORWARD
-P FORWARD ACCEPT -c 4 336
-A FORWARD -p icmp -m icmp --icmp-type 8 -c 2 168
-A FORWARD -p icmp -m icmp --icmp-type 0 -c 2 168

Давайте установим политику DROP (которая обнуляет счетчики по умолчанию) и попробуем еще раз:

# ip netns exec host1 ping -n -c2 192.168.0.102
PING 192.168.0.102 (192.168.0.102) 56(84) bytes of data.

--- 192.168.0.102 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 1008ms

# ip netns exec router iptables -v -S FORWARD
-P FORWARD DROP -c 2 168
-A FORWARD -p icmp -m icmp --icmp-type 8 -c 4 336
-A FORWARD -p icmp -m icmp --icmp-type 0 -c 2 168

Код моста фильтрует коммутируемые кадры / пакеты через iptables. Давайте добавим правило обхода (которое снова обнулит счетчики по умолчанию), как в OP, и повторите попытку:

# ip netns exec router iptables -A FORWARD -i bridge0 -o bridge0 -j ACCEPT
# ip netns exec host1 ping -n -c2 192.168.0.102
PING 192.168.0.102 (192.168.0.102) 56(84) bytes of data.
64 bytes from 192.168.0.102: icmp_seq=1 ttl=64 time=0.132 ms
64 bytes from 192.168.0.102: icmp_seq=2 ttl=64 time=0.123 ms

--- 192.168.0.102 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1024ms
rtt min/avg/max/mdev = 0.123/0.127/0.132/0.012 ms

# ip netns exec router iptables -v -S FORWARD
-P FORWARD DROP -c 0 0
-A FORWARD -p icmp -m icmp --icmp-type 8 -c 6 504
-A FORWARD -p icmp -m icmp --icmp-type 0 -c 4 336
-A FORWARD -i bridge0 -o bridge0 -c 4 336 -j ACCEPT

Давайте посмотрим, что теперь фактически получается на host2 во время ping от host1:

# ip netns exec host2 tcpdump -l -n -s0 -i eth0 -p icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
02:16:11.068795 IP 192.168.0.1 > 192.168.0.102: ICMP echo request, id 9496, seq 1, length 64
02:16:11.068817 IP 192.168.0.102 > 192.168.0.1: ICMP echo reply, id 9496, seq 1, length 64
02:16:12.088002 IP 192.168.0.1 > 192.168.0.102: ICMP echo request, id 9496, seq 2, length 64
02:16:12.088063 IP 192.168.0.102 > 192.168.0.1: ICMP echo reply, id 9496, seq 2, length 64

... вместо источника 192.168.0.101. Правило MASQUERADE также вызывалось из пути моста. Чтобы избежать этого, добавьте (как описано в Раздел 7пример) правило исключения раньше или указать исходящий интерфейс без моста, если это вообще возможно (теперь это доступно, вы даже можете использовать -m physdev если это должен быть мост ...).


Случайно связанные:

LKML / netfilter-dev: br_netfilter: включить в не начальных netns: это помогло бы включить эту функцию для каждого пространства имен, а не глобально, тем самым ограничив взаимодействие между хостами и контейнерами.

netfilter-dev: netfilter: Physdev: расслабить зависимость br_netfilter: просто попытка удалить несуществующий Physdev правило может создать проблемы.

netfilter-dev: поддержка отслеживания соединений для моста: Код netfilter моста WIP для подготовки межсетевого экрана моста с отслеживанием состояния с использованием nftables, на этот раз более элегантно. Я думаю, это один из последних шагов по избавлению от iptables (API на стороне ядра).

Если вышеуказанные угрозы не решают вашу проблему, вот как я решил проблему на моем Debian Stretch.

  • Во-первых, сохраните текущие iptables

    iptables-save > your-current-iptables.rules
    
  • 2-й, удалить ВСЕ правила, созданные докером

    iptables -D <DOCKER-CHAIN-RULES> <target-line-number>
    
  • В-третьих, добавьте правила itpables для приема любого трафика на INPUT, FORWARD и OUTPUT

    iptables -I INPUT -j ACCEPT
    iptables -I FORWARD -j ACCEPT
    iptables -I OUTPUT -j ACCEPT
    
  • В-четвертых, перезапустите Docker

    service docker restart
    

После завершения шага 3 вы можете проверить связь с заблокированным хостом libvert KVM с другого ПК, вы увидите ответы ICMP.

Перезапуск Docker также добавит необходимые правила iptables обратно на ваш компьютер, но больше не будет блокировать ваши подключенные к мосту KVM-хосты.

Если вышеуказанное решение не работает для вас, вы можете восстановить iptables, используя следующую команду:

  • Восстановить iptables

    iptables-restore < your-current-iptables.rules