nftables: paquetes de transmisión duplicados entre segmentos

Aug 18 2020

Tenemos una caja Debian Buster (nftables 0.9.0, kernel 4.19) conectada a cuatro segmentos de red diferentes. Tres de estos segmentos albergan dispositivos que ejecutan Syncthing, que ejecuta su propio descubrimiento local a través de transmisiones al puerto UDP 21027. Por lo tanto, no todos los dispositivos pueden "verse" entre sí ya que las transmisiones no cruzan segmentos; la caja Buster en sí no participa en el clúster de sincronización.

Si bien podríamos resolver esto ejecutando los servidores de descubrimiento o retransmisión de Syncthing en la caja Buster, se ha solicitado que no los usemos (motivos relacionados con la configuración y los dispositivos que se desplazan a otros sitios). Por lo tanto, estamos buscando una solución basada en nftables; Tengo entendido que esto no se hace normalmente, pero para que funcione, tenemos que:

  • Haga coincidir los paquetes entrantes en UDP 21027
  • Copie esos paquetes a las otras interfaces de segmento en las que deben verse
  • Cambie la IP de destino de los nuevos paquetes para que coincida con la dirección de transmisión del nuevo segmento (mientras conserva la IP de origen, ya que el protocolo de descubrimiento puede confiar en ella)
  • Emite las nuevas emisiones sin que se vuelvan a duplicar

Solo tres de los segmentos adjuntos participan con dispositivos; todos están enmascarados como subred como /24.

  • El segmento A (eth0, 192.168.0.1) no debe reenviarse
  • El segmento B (eth1, 192.168.1.1) debe reenviarse solo al segmento A
  • El segmento C (eth2, 192.168.2.1) debe reenviarse tanto a A como a B

Lo más cercano que tenemos a una regla de trabajo para esto hasta ahora es (otras reglas de filtrado local y DNAT/MASQ omitidas por brevedad):

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
    }
}

Los contadores muestran que se están aplicando las reglas, aunque sin las daddr setreglas la dirección de transmisión sigue siendo la misma que en el segmento de origen. nft monitor tracemuestra que al menos algunos paquetes llegan a la interfaz deseada con la IP de destino correcta, pero luego aterrizan en el enlace de entrada de la caja y no son vistos por otros dispositivos en el segmento.

¿El resultado que estamos buscando aquí se puede lograr en la práctica y, de ser así, con qué reglas?

Respuestas

1 A.B Aug 21 2020 at 03:53

Todavía es posible usar nftables en la familia netdev (en lugar de la familia ip ) para este caso, ya que solo se necesita la entrada (nftables aún no tiene salida disponible). El comportamiento de dupy fwden el gancho de entrada es exactamente el mismo que el de tc - mirred y .mirrorredirect

También abordé un detalle menor: reescriba la dirección de origen de Ethernet en la nueva dirección MAC de la interfaz de salida de Ethernet, como se habría hecho para un paquete verdaderamente enrutado, incluso si funciona para usted sin esto. Por lo tanto, las direcciones MAC de las interfaces deben conocerse de antemano. Puse los dos requeridos ( eth0 's y eth1 's) en definiciones de variables/macro, que deben editarse con los valores correctos.

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
    }
}
1 T2PS Aug 19 2020 at 21:44

Editar: para cualquiera que encuentre esto más tarde, la respuesta aceptada de AB brinda una solución puramente nft.

Gracias a la sugerencia de AB, esto ahora funciona usando tc en lugar de reglas 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

Mi entendimiento de estos filtros es que hacen coincidir los paquetes de transmisión entrantes para el puerto UDP 21027, los NAT a la dirección de transmisión para cada una de las otras subredes previstas ( ingress, para cambiar la IP de destino en lugar de la IP de origen que nat egressalteraría), luego duplicar / redirigir los paquetes NAT a las colas de salida de las otras interfaces.

Siendo un novato con tc, esta puede no ser la mejor manera de resolver el problema, pero funciona en términos de hacer que las transmisiones de anuncios viajen a través de segmentos (y Syncthing está felizmente descubriendo nuevos nodos).