Estou experimentando o netfilter em um contêiner Docker. Eu tenho três contêineres, um "roteador" e dois "endpoints". Cada um deles é conectado via pipework
, portanto, existe uma ponte externa (host) para cada conexão de endpoint<->roteador. Algo assim:
containerA (eth1) -- hostbridgeA -- (eth1) containerR
containerB (eth1) -- hostbridgeB -- (eth2) containerR
Então, dentro do contêiner "roteador" containerR
, tenho uma ponte br0
configurada assim:
bridge name bridge id STP enabled interfaces
br0 8000.3a047f7a7006 no eth1
eth2
Eu tenho net.bridge.bridge-nf-call-iptables=0
no host porque isso estava interferindo em alguns dos meus outros testes.
containerA
tem IP 192.168.10.1/24
e containerB
tem 192.168.10.2/24
.
Tenho então um conjunto de regras muito simples que rastreia pacotes encaminhados:
flush ruleset
table bridge filter {
chain forward {
type filter hook forward priority 0; policy accept;
meta nftrace set 1
}
}
Com isso, descubro que apenas os pacotes ARP são rastreados, e não os pacotes ICMP. Em outras palavras, se eu executar nft monitor
while containerA
is ping containerB
, poderei ver os pacotes ARP rastreados, mas não os pacotes ICMP. Isso me surpreende, porque com base no meu entendimento dos tipos de cadeia de filtros de ponte do nftables , a única vez que um pacote não passaria pelo forward
estágio seria se fosse enviado input
para o host (neste caso containerR
). De acordo com o diagrama de fluxo de pacotes do Linux:
Eu ainda esperaria que os pacotes ICMP seguissem o caminho de encaminhamento, assim como o ARP. Eu vejo os pacotes se rastrear o pré e o pós-roteamento. Então minha pergunta é: o que está acontecendo aqui? Existe uma tabela de fluxo ou outro curto-circuito que não conheço? É específico para rede de contêineres e/ou Docker? Posso verificar com VMs em vez de containers , mas estou interessado se outras pessoas souberem ou já encontraram isso.
Editar: desde então, criei uma configuração semelhante com um conjunto de máquinas virtuais Alpine no VirtualBox. Os pacotes ICMP chegam à forward
cadeia, então parece que algo no host, ou Docker, está interferindo nas minhas expectativas. Deixarei isso sem resposta até que eu, ou outra pessoa, consiga identificar o motivo, caso seja útil para outras pessoas saberem.
Obrigado!
Exemplo mínimo reproduzível
Para isso estou utilizando o Alpine Linux 3.19.1 em uma VM, com o community
repositório habilitado em /etc/apk/respositories
:
# Prerequisites of host
apk add bridge bridge-utils iproute2 docker openrc
service docker start
# When using linux bridges instead of openvswitch, disable iptables on bridges
sysctl net.bridge.bridge-nf-call-iptables=0
# Pipework to let me avoid docker's IPAM
git clone https://github.com/jpetazzo/pipework.git
cp pipework/pipework /usr/local/bin/
# Create two containers each on their own network (bridge)
pipework brA $(docker create -itd --name hostA alpine:3.19) 192.168.10.1/24
pipework brB $(docker create -itd --name hostB alpine:3.19) 192.168.10.2/24
# Create bridge-filtering container then connect it to both of the other networks
R=$(docker create --cap-add NET_ADMIN -itd --name hostR alpine:3.19)
pipework brA -i eth1 $R 0/0
pipework brB -i eth2 $R 0/0
# Note: `hostR` doesn't have/need an IP address on the bridge for this example
# Add bridge tools and netfilter to the bridging container
docker exec hostR apk add bridge bridge-utils nftables
docker exec hostR brctl addbr br
docker exec hostR brctl addif br eth1 eth2
docker exec hostR ip link set dev br up
# hostA should be able to ping hostB
docker exec hostA ping -c 1 192.168.10.2
# 64 bytes from 192.168.10.2...
# Set nftables rules
docker exec hostR nft add table bridge filter
docker exec hostR nft add chain bridge filter forward '{type filter hook forward priority 0;}'
docker exec hostR nft add rule bridge filter forward meta nftrace set 1
# Now ping hostB from hostA while nft monitor is running...
docker exec hostA ping -c 4 192.168.10.2 & docker exec hostR nft monitor
# Ping will succeed, nft monitor will not show any echo-request/-response packets traced, only arps
# Example:
trace id abc bridge filter forward packet: iif "eth2" oif "eth1" ether saddr ... daddr ... arp operation request
trace id abc bridge filter forward rule meta nfrtrace set 1 (verdict continue)
trace id abc bridge filter forward verdict continue
trace id abc bridge filter forward policy accept
...
trace id def bridge filter forward packet: iif "eth1" oif "eth2" ether saddr ... daddr ... arp operation reply
trace id def bridge filter forward rule meta nfrtrace set 1 (verdict continue)
trace id def bridge filter forward verdict continue
trace id def bridge filter forward policy accept
# Add tracing in prerouting and the icmp packets are visible:
docker exec hostR nft add chain bridge filter prerouting '{type filter hook prerouting priority 0;}'
docker exec hostR nft add rule bridge filter prerouting meta nftrace set 1
# Run again
docker exec hostA ping -c 4 192.168.10.2 & docker exec hostR nft monitor
# Ping still works (obviously), but we can see its packets in prerouting, which then disappear from the forward chain, but ARP shows up in both.
# Example:
trace id abc bridge filter prerouting packet: iif "eth1" ether saddr ... daddr ... ... icmp type echo-request ...
trace id abc bridge filter prerouting rule meta nfrtrace set 1 (verdict continue)
trace id abc bridge filter prerouting verdict continue
trace id abc bridge filter prerouting policy accept
...
trace id def bridge filter prerouting packet: iif "eth2" ether saddr ... daddr ... ... icmp type echo-reply ...
trace id def bridge filter prerouting rule meta nfrtrace set 1 (verdict continue)
trace id def bridge filter prerouting verdict continue
trace id def bridge filter prerouting policy accept
...
trace id 123 bridge filter prerouting packet: iif "eth1" ether saddr ... daddr ... ... arp operation request
trace id 123 bridge filter prerouting rule meta nfrtrace set 1 (verdict continue)
trace id 123 bridge filter prerouting verdict continue
trace id 123 bridge filter prerouting policy accept
trace id 123 bridge filter forward packet: iif "eth1" oif "eth2" ether saddr ... daddr ... arp operation request
trace id 123 bridge filter forward rule meta nfrtrace set 1 (verdict continue)
trace id 123 bridge filter forward verdict continue
trace id 123 bridge filter forward policy accept
...
trace id 456 bridge filter prerouting packet: iif "eth2" ether saddr ... daddr ... ... arp operation reply
trace id 456 bridge filter prerouting rule meta nfrtrace set 1 (verdict continue)
trace id 456 bridge filter prerouting verdict continue
trace id 456 bridge filter prerouting policy accept
trace id 456 bridge filter forward packet: iif "eth2" oif "eth1" ether saddr ... daddr ... arp operation reply
trace id 456 bridge filter forward rule meta nfrtrace set 1 (verdict continue)
trace id 456 bridge filter forward verdict continue
trace id 456 bridge filter forward policy accept
# Note the trace id matching across prerouting and forward chains
Eu tentei isso com openvswitch também, mas para simplificar, usei um exemplo de ponte Linux que produz o mesmo resultado de qualquer maneira. A única diferença real com o openvswitch é que net.bridge.bridge-nf-call-iptables=0
não é necessário, IIRC.