Docker quebra a rede da ponte libvirt

Esta questão está me deixando louco. Eu executo uma nova instalação do Ubuntu 18.04, com:

  • ufw para gerenciar o firewall
  • uma ponte br0
  • LXD e libvirt (KVM)

Eu tentei estoque docker.io pacotes e pacotes formam o próprio repositório deb do docker.

Quero poder implantar contêineres docker escolhendo o ip para vincular sua porta (por exemplo. - p 10.58.26.6: 98800: 98800) e, em seguida, abra a porta com UFW.

Mas o docker parece criar regras iptables que pertubam à ponte br0 (por exemplo . host não pode ping libvirt convidados)

Eu olhei ao redor e não consigo encontrar uma solução boa e consciente de segurança.

Fazendo manualmente iptables -I FORWARD -i br0 -o br0 -j ACCEPTparece fazer tudo funcionar.

Também definindo "iptables": false para o daemon docker permite que a ponte se comporte normalmente, mas quebra a rede de saída de contêineres do docker.

Encontrei esta solução que parecia simples, editando um único arquivo UFW https://stackoverflow.com/a/51741599/1091772, mas não funciona.

Qual seria a melhor prática e a maneira segura de resolver isso permanentemente, sobrevivendo às reinicializações ?

EDITAR:Acabei adicionando -A ufw-before-forward -i br0 -o br0 -j ACCEPT no final do /etc/ufw/before.rules antes do COMMIT. Posso considerar isso como uma correção ou não levanta alguns problemas ?

O problema, na verdade, um recurso: br_netfilter

A partir da descrição, acredito que a única explicação lógica é que o código do netfilter da ponte está habilitado: destinado entre outros usos para firewalling de ponte com estado ou para alavancagem iptables'correspondências e alvos do caminho da ponte sem ter que (ou ser capaz de) duplicá-los todos em ebtables. Desconsiderando bastante camadas de rede, o código de Ponte ethernet, na camada de rede 2, agora faz upcalls para iptables trabalhando no nível IP, ou seja, camada de rede 3. Ele pode ser ativado apenas globalmente ainda: seja para host e todos os contêineres, ou para nenhum. Uma vez entendido o que está acontecendo e sabendo o que procurar, escolhas adaptadas podem ser feitas.

O projeto netfilter descreve os vários ebtables/iptables interaccao quando br_netfilter está ativado. Especialmente de interesse é o Secção 7 explicar por que algumas regras sem efeito aparente às vezes são necessárias para evitar efeitos não intencionais do caminho da ponte, como usar:

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

para evitar dois sistemas na mesma LAN a ser NATed perto... a ponte (veja o exemplo abaixo).

Você tem algumas opções para evitar seu problema, mas a escolha que você tomou é provavelmente a melhor se você não quiser saber todos os detalhes nem verificar se algumas regras iptables (às vezes escondidas em outros namespaces) seriam interrompidas:

  • impedir permanentemente o br_netfilter módulo a ser carregado. Geralmente blacklist não é suficiente, install deve ser usado. Esta é uma escolha propensa a problemas para aplicativos que dependem de br_netfilter: obviamente Docker, Kubernetes, ...

    echo install br_netfilter /bin/true > /etc/modprobe.d/disable-br-netfilter.conf
  • Tenha o módulo carregado, mas desative seus efeitos. Para iptables"efeitos que são:

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

    Se colocar isso na inicialização, o módulo deve ser carregado primeiro ou essa alternância ainda não existirá.

Essas duas escolhas anteriores certamente interromperão iptables corresponder -m physdev: O xt_physdev módulo quando ele próprio carregado, auto-carrega o br_netfilter módulo (isso aconteceria mesmo se uma regra adicionada de um contêiner acionasse o carregamento). Agora br_netfilter não será carregado, -m physdev provavelmente nunca irá corresponder.

  • Trabalhe em torno do efeito de br_netfilter quando necessário, como OP: adicione essas regras aparentes no-op em várias cadeias (PREROUTING, FORWARD, POSTROUTING) conforme descrito em Secção 7. Por exemplo:

    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

    Essas regras nunca devem corresponder porque o tráfego na mesma LAN IP não é roteado, exceto por algumas configurações raras do DNAT. Mas graças a br_netfilter eles combinam, porque eles são chamados pela primeira vez alternar quadros ("atualizados" para pacotes IP) atravessando o ponte. Então eles são chamados novamente para golear pacotes que atravessam o encaminhador para uma interface não relacionada (mas não corresponderá então).

  • Não coloque um IP na ponte: coloque esse IP em uma extremidade de um veth interface com sua outra extremidade na ponte: isso deve garantir que a ponte não interaja com o roteamento, mas não é isso que está fazendo a maioria dos produtos comuns de contêiner/VM.

  • Você pode até ocultar a ponte em seu próprio namespace de rede isolado (isso só seria útil se quisesse isolar de outros ebtables regras desta vez).

  • Mude tudo para nftables que entre os objetivos declarados evitará estes problemas de interação de Ponte. Por enquanto, o firewalling da ponte não tem suporte com estado disponível, ainda está WIP mas é prometido ser mais limpo quando disponível, porque não haverá qualquer "upcall".

Você deve pesquisar o que aciona o carregamento de br_netfilter (exemplo: -m physdev) e veja se você pode evitá-lo ou não, para escolher como proceder.


Exemplo com namespaces de rede

Vamos reproduzir alguns efeitos usando um namespace de rede. Note que em nenhum lugar qualquer ebtables regra será usada. Observe também que este exemplo depende do legado usual iptables, nao iptables sobre nftables como ativado por padrão no Debian buster.

Vamos reproduzir um caso simples semelhante com muitos usos de contêiner: um roteador 192.168.0.1/192.0.2.100 fazendo NAT com dois hosts atrás: 192.168.0.101 e 192.168.0.102, vinculado a uma ponte no roteador. Os dois hosts podem se comunicar diretamente na mesma LAN, através da ponte.

#!/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

Vamos carregar o módulo do kernel br_netfilter (para ter certeza de que não será mais tarde) e desative seus efeitos com a alternância (não por namespace) bridge-NF-call-iptables, disponível apenas no namespace inicial:

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

Aviso: novamente, isso pode atrapalhar iptables regras como -m physdev em qualquer lugar do host ou em contêineres que dependem br_netfilter carregado e habilitado.

Vamos adicionar alguns contadores de tráfego de ping icmp.

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

Vamos fazer ping:

# 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

Os contadores não combinam:

# 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

Vamos habilitar bridge-NF-call-iptables e ping novamente:

# 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

Desta vez, os pacotes comutados obtiveram uma correspondência na cadeia de filtro/avanço do 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

Vamos colocar uma política DROP (que zera os contadores padrão) e tentar novamente:

# 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

O código da ponte filtrou os quadros/pacotes comutados via iptables. Vamos adicionar a regra de bypass (que irá zerar novamente os contadores padrão) como em OP e tente novamente:

# 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

Vamos ver o que agora é realmente recebido no host2 durante um ping do host1:

# 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

... em vez da fonte 192.168.0.101. A Regra do baile de máscaras também foi chamada do caminho da ponte. Para evitar isso, adicione (conforme explicado em Secção 7's exemplo) uma regra de exceção antes, ou estado de uma interface de saída não-ponte, se possível em tudo (agora ele está disponível você pode até mesmo usar -m physdev se tem que ser uma ponte...).


Aleatoriamente relacionados:

Lkml / netfilter-dev: br_netfilter: ativar em netns não iniciais: ajudaria a habilitar esse recurso por namespace em vez de globalmente, limitando assim as interações entre hosts e contêineres.

netfilter-dev: netfilter: physdev: relax br_netfilter dependência: apenas tentando excluir um não existente physdev regra pode criar problemas.

netfilter-dev: suporte de rastreamento de conexão para bridge: Código do netfilter da ponte de WIP para preparar firewalling stateful da ponte usando nftables, desta vez mais elegantemente. Acho que uma das últimas etapas para se livrar do iptables ('s kernel side API).

Se as ameaças acima não resolverem seu problema, veja como resolvi o problema no meu Debian Stretch.

  • 1º, salve seus iptables atuais

    iptables-save > your-current-iptables.rules
  • 2º, excluir TODO o Docker criou regras

    iptables -D <DOCKER-CHAIN-RULES> <target-line-number>
  • 3º, adicione regras itpables para aceitar qualquer tráfego para entrada, encaminhamento e saída

    iptables -I INPUT -j ACCEPTiptables -I FORWARD -j ACCEPTiptables -I OUTPUT -j ACCEPT
  • 4º, reinicie seu Docker

    service docker restart

Uma vez concluído o Passo 3, você pode fazer ping no seu host LIBVERT KVM Bloqueado de outro PC, você verá as respostas do ICMP.

Reiniciar o Docker também adicionará suas regras iptables necessárias de volta à sua máquina, mas não bloqueará mais seus hosts KVM em Ponte.

Se a solução acima não funcionar para você, você pode restaurar o iptables usando o seguinte comando:

  • Restaurar iptables

    iptables-restore < your-current-iptables.rules