nftables: pacotes de broadcast duplicados entre segmentos
Temos uma caixa Debian Buster (nftables 0.9.0, kernel 4.19) anexada a quatro segmentos de rede diferentes. Três desses segmentos são o lar de dispositivos que executam o Syncthing, que executa sua própria descoberta local por meio de transmissões para a porta UDP 21027. Os dispositivos, portanto, não podem "ver" uns aos outros, pois as transmissões não cruzam os segmentos; a própria caixa Buster não participa do cluster de sincronização.
Embora pudéssemos resolver isso executando os servidores de descoberta ou retransmissão do Syncthing na caixa Buster, foi solicitado que não os usássemos (razões relacionadas à configuração e dispositivos que fazem roaming para outros sites). Portanto, estamos procurando uma solução baseada em nftables; meu entendimento é que isso não é feito normalmente, mas para fazer isso funcionar, temos que:
- Corresponde aos pacotes recebidos em UDP 21027
- Copie esses pacotes para a(s) outra(s) interface(s) de segmento em que eles precisam ser vistos
- Altere o IP de destino do(s) novo(s) pacote(s) para corresponder ao endereço de transmissão do novo segmento (enquanto preserva o IP de origem, pois o protocolo de descoberta pode confiar nele)
- Emita as novas transmissões sem que sejam duplicadas novamente
Apenas três dos segmentos anexados participam com dispositivos; todos são mascarados de sub-rede como /24.
- O segmento A (eth0, 192.168.0.1) não deve ser encaminhado
- O segmento B (eth1, 192.168.1.1) deve ser encaminhado apenas para o segmento A
- O segmento C (eth2, 192.168.2.1) deve ser encaminhado para A e B
O mais próximo que temos de uma regra de trabalho para isso até agora é (outras regras DNAT/MASQ e filtragem local omitidas por brevidade):
table ip mangle {
chain repeater {
type filter hook prerouting priority -152; policy accept;
ip protocol tcp return
udp dport != 21027 return
iifname "eth1" ip saddr 192.168.2.0/24 counter ip daddr set 192.168.1.255 return
iifname "eth0" ip saddr 192.168.2.0/24 counter ip daddr set 192.168.0.255 return
iifname "eth0" ip saddr 192.168.1.0/24 counter ip daddr set 192.168.0.255 return
iifname "eth2" ip saddr 192.168.2.0/24 counter dup to 192.168.0.255 device "eth0" nftrace set 1
iifname "eth2" ip saddr 192.168.2.0/24 counter dup to 192.168.1.255 device "eth1" nftrace set 1
iifname "eth1" ip saddr 192.168.1.0/24 counter dup to 192.168.0.255 device "eth0" nftrace set 1
}
}
Os contadores mostram que as regras estão sendo atingidas, embora sem as daddr set
regras o endereço de broadcast permaneça o mesmo do segmento de origem. nft monitor trace
mostra que pelo menos alguns pacotes estão alcançando a interface pretendida com o IP de destino correto, mas estão aterrissando no gancho de entrada da própria caixa e não são vistos por outros dispositivos no segmento.
O resultado que buscamos aqui é alcançável na prática e, em caso afirmativo, com quais regras?
Respostas
Ainda é possível usar nftables na família netdev (ao invés da família ip ) para este caso, já que apenas ingress é necessário (nftables ainda não tem egress disponível). O comportamento de dup
e fwd
no gancho de entrada é exatamente o mesmo que tc - mirred e .mirror
redirect
Também abordei um pequeno detalhe: reescrever o endereço de origem Ethernet para o endereço MAC da nova interface de saída Ethernet, como teria sido feito para um pacote verdadeiramente roteado, mesmo que funcione para você sem isso. Portanto, os endereços MAC das interfaces devem ser conhecidos de antemão. Coloquei os dois necessários ( eth0 's e eth1 's) em variáveis/definições de macro, que devem ser editadas com os valores corretos.
define eth0mac = 02:0a:00:00:00:01
define eth1mac = 02:0b:00:00:00:01
table netdev statelessnat
delete table netdev statelessnat
table netdev statelessnat {
chain b { type filter hook ingress device eth1 priority 0;
pkttype broadcast ether type ip ip daddr 192.168.1.255 udp dport 21027 jump b-to-a
}
chain c { type filter hook ingress device eth2 priority 0;
pkttype broadcast ether type ip ip daddr 192.168.2.255 udp dport 21027 counter jump c-to-b-a
}
chain b-to-a {
ether saddr set $eth0mac ip daddr set 192.168.0.255 fwd to eth0
}
chain c-to-b-a {
ether saddr set $eth1mac ip daddr set 192.168.1.255 dup to eth1 goto b-to-a
}
}
Editar: para quem encontrar isso mais tarde, a resposta aceita da AB fornece uma solução puramente NFT.
Graças à sugestão de AB, isso agora está funcionando usando tc em vez de regras puramente nftables:
tc qdisc add dev eth2 ingress
tc filter add dev eth2 ingress \
protocol ip u32 \
match ip dst 192.168.2.255 \
match ip protocol 17 0xff \
match ip dport 21027 0xffff \
action nat ingress 192.168.2.255/32 192.168.0.255 \
pipe action mirred egress mirror dev eth0 \
pipe action nat ingress 192.168.0.255/32 192.168.1.255 \
pipe action mirred egress redirect dev eth1
tc qdisc add dev eth1 ingress
tc filter add dev eth1 ingress \
protocol ip u32 \
match ip dst 192.168.1.255 \
match ip protocol 17 0xff \
match ip dport 21027 0xffff \
action nat ingress 192.168.1.255/32 192.168.0.255 \
pipe action mirred egress redirect dev eth0
Meu entendimento sobre esses filtros é que eles correspondem aos pacotes de transmissão de entrada para a porta UDP 21027, NAT para o endereço de transmissão para cada uma das outras sub-redes pretendidas ( ingress
, para alterar o IP de destino em vez do IP de origem que nat egress
alteraria) e, em seguida, duplicar/redirecionar os pacotes com NAT para as filas de saída das outras interfaces.
Sendo um novato com tc, esta pode não ser a melhor maneira de resolver o problema, mas funciona em termos de fazer com que as transmissões de anúncio viajem pelos segmentos (e o Syncthing está feliz em descobrir novos nós).