Docker разрывает сеть libvirt bridge

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

  • ufw для управления брандмауэром
  • мост br0
  • lxd и libvirt (KVM)

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

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

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

Я осмотрел все вокруг и не могу найти хорошего решения, учитывающего безопасность.

Вручную выполняя iptables -I FORWARD -i br0 -o br0 -j ACCEPTкажется, все работает.

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

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

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

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

Проблема, на самом деле, особенность: br_netфильтр

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

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

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

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

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

  • постоянно предотвращать br_netфильтр модуль, который нужно загрузить. Обычно blacklist этого недостаточно, install должен быть использован. Это выбор, подверженный проблемам для приложений, полагающихся на br_netфильтр: очевидно, 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: В xt_physdev модуль при загрузке автоматически загружает br_netфильтр модуль (это произошло бы, даже если бы правило, добавленное из контейнера, вызвало загрузку). Сейчас br_netфильтр не будет загружен, -m physdev вероятно, они никогда не совпадут.

  • Обходите эффект br_netfilter, когда это необходимо, например, OP: добавьте эти очевидные правила отсутствия операций в различные цепочки (ПРЕДВАРИТЕЛЬНАЯ маршрутизация, ПЕРЕСЫЛКА, ПОСЛЕДУЮЩАЯ МАРШРУТИЗАЦИЯ), как описано в раздел 7. Например:

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

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

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

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

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

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


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

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

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

#!/bin/shfor ns in host1 host2 router; do    ip netns del $ns 2>/dev/null || :    ip netns add $ns    ip -n $ns link set lo updoneip netns exec router sysctl -q -w net.ipv4.conf.default.forwarding=1ip -n router link add bridge0 type bridgeip -n router link set bridge0 upip -n router address add 192.168.0.1/24 dev bridge0for 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 bridge0done#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 dummyip -n router link set eth0 upip -n router address add 192.0.2.100/24 dev eth0ip -n router route add default via 192.0.2.1ip netns exec router iptables -t nat -A POSTROUTING -s 192.168.0.0/24 -j MASQUERADE

Давайте загрузим модуль ядра br_netфильтр (чтобы быть уверенным, что это не произойдет позже) и отключите его эффекты с помощью переключателя (не для каждого пространства имен) мост-nf-вызов-iptables, доступный только в начальном пространстве имен:

modprobe br_netfiltersysctl -w net.bridge.bridge-nf-call-iptables=0

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

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

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

Давайте проверим:

# ip netns exec host1 ping -n -c2 192.168.0.102PING 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 ms64 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 1017msrtt 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-вызов-iptables и снова пинг:

# sysctl -w net.bridge.bridge-nf-call-iptables=1net.bridge.bridge-nf-call-iptables = 1# ip netns exec host1 ping -n -c2 192.168.0.102PING 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 ms64 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 1006msrtt min/avg/max/mdev = 0.094/0.128/0.163/0.036 ms

На этот раз переключенные пакеты получили совпадение в цепочке фильтрации/пересылки 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

Давайте установим политику УДАЛЕНИЯ (которая обнуляет счетчики по умолчанию) и попробуем еще раз:

# ip netns exec host1 ping -n -c2 192.168.0.102PING 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.102PING 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 ms64 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 1024msrtt 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

Давайте посмотрим, что теперь фактически получено на хост2 во время пинга с хоста1:

# ip netns exec host2 tcpdump -l -n -s0 -i eth0 -p icmptcpdump: verbose output suppressed, use -v or -vv for full protocol decodelistening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes02:16:11.068795 IP 192.168.0.1 > 192.168.0.102: ICMP echo request, id 9496, seq 1, length 6402:16:11.068817 IP 192.168.0.102 > 192.168.0.1: ICMP echo reply, id 9496, seq 1, length 6402:16:12.088002 IP 192.168.0.1 > 192.168.0.102: ICMP echo request, id 9496, seq 2, length 6402: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. Правило МАСКАРАДА также было вызвано из пути моста. Чтобы избежать этого, либо добавьте (как описано в раздел 7пример) правило исключения раньше или укажите исходящий интерфейс без моста, если это вообще возможно (теперь он доступен, вы даже можете использовать -m physdev если это должен быть мост...).


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

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

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

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

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

  • 1-й, сохраните ваши текущие iptables

    iptables-save > your-current-iptables.rules
  • 2-й, исключить все Docker создал правила

    iptables -D <DOCKER-CHAIN-RULES> <target-line-number>
  • 3-й, добавьте правила itpables для приема любого трафика для ВВОДА, ПЕРЕСЫЛКИ и ВЫВОДА

    iptables -I INPUT -j ACCEPTiptables -I FORWARD -j ACCEPTiptables -I OUTPUT -j ACCEPT
  • 4-й, перезагрузите свой докер

    service docker restart

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

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

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

  • Восстановление iptables

    iptables-restore < your-current-iptables.rules