Enrutamiento rápido y NAT en Linux

A medida que las direcciones IPv4 se agotan, muchos operadores de telecomunicaciones se enfrentan a la necesidad de organizar el acceso de sus clientes a la red mediante la traducción de direcciones. En este artículo, le diré cómo obtener un rendimiento de nivel NAT de Carrier Grade en servidores básicos.

Un poco de historia


El tema de quedarse sin espacio de direcciones IPv4 ya no es nuevo. En algún momento, aparecieron listas de espera en RIPE, luego hubo intercambios en los que intercambiaron bloques de direcciones y concluyeron transacciones por su alquiler. Gradualmente, los operadores de telecomunicaciones comenzaron a proporcionar servicios de acceso a Internet a través de la traducción de direcciones y puertos. Alguien no logró obtener suficientes direcciones para dar una dirección "blanca" a cada suscriptor, mientras que alguien comenzó a ahorrar dinero al negarse a comprar direcciones en el mercado secundario. Los fabricantes de equipos de red respaldaron esta idea, ya que Esta funcionalidad generalmente requiere módulos de expansión o licencias adicionales. Por ejemplo, con Juniper en la línea de enrutadores MX (a excepción de los últimos MX104 y MX204), NAPT se puede realizar en una tarjeta de servicio MS-MIC separada, Cisco ASR1k requiere una licencia GN,en Cisco ASR9k, un módulo A9K-ISM-100 separado y una licencia A9K-CGN-LIC. En general, el placer cuesta mucho dinero.

Iptables


La tarea de realizar NAT no requiere recursos informáticos especializados; los procesadores de propósito general instalados, por ejemplo, en cualquier enrutador doméstico, pueden resolverlo. A escala de operador, este problema puede resolverse utilizando servidores básicos que ejecutan FreeBSD (ipfw / pf) o GNU / Linux (iptables). No consideraremos FreeBSD, porque Me negué a usar este sistema operativo durante mucho tiempo, así que centrémonos en GNU / Linux.

Activar la traducción de direcciones no es nada difícil. Primero debe escribir la regla en iptables en la tabla nat:

iptables -t nat -A POSTROUTING -s 100.64.0.0/10 -j SNAT --to <pool_start_addr>-<pool_end_addr> --persistent

El sistema operativo cargará el módulo nf_conntrack, que supervisará todas las conexiones activas y realizará las conversiones necesarias. Hay varias sutilezas. En primer lugar, dado que estamos hablando de NAT en la escala del operador, es necesario ajustar los tiempos de espera, porque con los valores predeterminados, el tamaño de la tabla de traducción crecerá rápidamente a valores catastróficos. A continuación se muestra un ejemplo de la configuración que utilicé en mis servidores:

net.ipv4.ip_forward = 1
net.ipv4.ip_local_port_range = 8192 65535

net.netfilter.nf_conntrack_generic_timeout = 300
net.netfilter.nf_conntrack_tcp_timeout_syn_sent = 60
net.netfilter.nf_conntrack_tcp_timeout_syn_recv = 60
net.netfilter.nf_conntrack_tcp_timeout_established = 600
net.netfilter.nf_conntrack_tcp_timeout_fin_wait = 60
net.netfilter.nf_conntrack_tcp_timeout_close_wait = 45
net.netfilter.nf_conntrack_tcp_timeout_last_ack = 30
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 120
net.netfilter.nf_conntrack_tcp_timeout_close = 10
net.netfilter.nf_conntrack_tcp_timeout_max_retrans = 300
net.netfilter.nf_conntrack_tcp_timeout_unacknowledged = 300
net.netfilter.nf_conntrack_udp_timeout = 30
net.netfilter.nf_conntrack_udp_timeout_stream = 60
net.netfilter.nf_conntrack_icmpv6_timeout = 30
net.netfilter.nf_conntrack_icmp_timeout = 30
net.netfilter.nf_conntrack_events_retry_timeout = 15
net.netfilter.nf_conntrack_checksum=0

Y en segundo lugar, dado que el tamaño predeterminado de la tabla de traducción no está diseñado para funcionar en las condiciones de un operador de telecomunicaciones, debe aumentarse:

net.netfilter.nf_conntrack_max = 3145728

También es necesario aumentar el número de depósitos para una tabla hash que almacena todas las traducciones (esta es una opción del módulo nf_conntrack):

options nf_conntrack hashsize=1572864

Después de estas manipulaciones simples, se obtiene una construcción completamente funcional, que puede traducir una gran cantidad de direcciones de clientes en un grupo externo. Sin embargo, el rendimiento de esta solución es pobre. En mis primeros intentos de usar GNU / Linux para NAT (aproximadamente 2013), pude obtener un rendimiento de aproximadamente 7 Gbit / s a ​​0.8Mpps por servidor (Xeon E5-1650v2). Desde entonces, se han realizado muchas optimizaciones diferentes en la pila de red del kernel GNU / Linux, el rendimiento de un servidor en el mismo hardware ha crecido casi a 18-19 Gbit / s a ​​1.8-1.9 Mpps (estos fueron los valores límite), pero la necesidad de volumen de tráfico, procesado por un solo servidor, creció mucho más rápido. Como resultado, se desarrollaron esquemas de equilibrio de carga para diferentes servidores, pero todo esto aumentó la complejidad de la configuración,servicio y mantenimiento de la calidad de los servicios prestados.

Nftables


Hoy en día, el uso de DPDK y XDP es una dirección de moda en el software de "transferencia de paquetes". Se han escrito muchos artículos sobre este tema, se han realizado muchas presentaciones diferentes, aparecen productos comerciales (por ejemplo, SKAT de VasExperts). Pero en las condiciones de recursos limitados de los programadores de los operadores de telecomunicaciones, es bastante problemático cortar algún tipo de "participación" sobre la base de estos marcos. Para operar una solución de este tipo en el futuro será mucho más difícil, en particular, se deberán desarrollar herramientas de diagnóstico. Por ejemplo, un tcpdump normal con DPDK simplemente no funcionará, y no "verá" los paquetes enviados de vuelta a los cables utilizando XDP. En medio de todo lo que se habla sobre las nuevas tecnologías para enviar el reenvío de paquetes al espacio de usuario, los informes y artículos pasaron desapercibidosPablo Neira Ayuso, mantenedor de iptables, sobre el desarrollo de la descarga de flujo en nftables. Veamos este mecanismo con más detalle.

La idea principal es que si el enrutador pasó los paquetes de una sesión en ambos lados de la secuencia (la sesión TCP entró en el estado ESTABLECIDO), entonces no hay necesidad de pasar los paquetes posteriores de esta sesión a través de todas las reglas del firewall, porque todas estas verificaciones finalizarán de la misma manera transfiriendo el paquete a la ruta. Sí, y en realidad no es necesario realizar la elección de la ruta: ya sabemos qué interfaz y qué host deben reenviar los paquetes dentro de esta sesión. Solo queda guardar esta información y usarla para el enrutamiento en una etapa temprana del procesamiento de paquetes. Al realizar NAT, es necesario guardar adicionalmente información sobre cambios en direcciones y puertos convertidos por el módulo nf_conntrack. Sí, por supuesto, en este caso varios polisers y otras reglas de información estadística en iptables dejan de funcionar,pero como parte de la tarea de un NAT permanente separado o, por ejemplo, un borde, esto no es tan importante, porque los servicios se distribuyen entre dispositivos.

Configuración


Para usar esta función necesitamos:

  • Usa un grano fresco. A pesar del hecho de que la funcionalidad en sí misma apareció en el kernel 4.16, durante bastante tiempo fue muy "en bruto" y regularmente se llamaba kernel panic. Todo se estabilizó alrededor de diciembre de 2019, cuando se lanzaron los núcleos LTS 4.19.90 y 5.4.5.
  • Reescribe las reglas de iptables en formato nftables usando una versión bastante reciente de nftables. Funciona bien en la versión 0.9.0

Si todo está claro en principio con el primer párrafo, lo principal es no olvidar incluir el módulo en la configuración durante el ensamblaje (CONFIG_NFT_FLOW_OFFLOAD = m), entonces el segundo párrafo requiere explicación. Las reglas de nftables se describen de manera bastante diferente que en iptables. La documentación revela casi todos los puntos, también hay convertidores de reglas especiales de iptables a nftables. Por lo tanto, solo daré un ejemplo de configuración de NAT y descarga de flujo. Una pequeña leyenda para un ejemplo: <i_if>, <o_if> son las interfaces de red a través de las cuales pasa el tráfico, en realidad puede haber más de dos. <pool_addr_start>, <pool_addr_end>: la dirección inicial y final del rango de direcciones "blancas".

La configuración de NAT es muy simple:

#! /usr/sbin/nft -f

table nat {
        chain postrouting {
                type nat hook postrouting priority 100;
                oif <o_if> snat to <pool_addr_start>-<pool_addr_end> persistent
        }
}

La descarga de flujo es un poco más complicada, pero comprensible:
#! /usr/sbin/nft -f

table inet filter {
        flowtable fastnat {
                hook ingress priority 0
                devices = { <i_if>, <o_if> }
        }

        chain forward {
                type filter hook forward priority 0; policy accept;
                ip protocol { tcp , udp } flow offload @fastnat;
        }
}

Eso, de hecho, es toda la configuración. Ahora todo el tráfico TCP / UDP irá a la tabla fastnat y se procesará mucho más rápido.

resultados


Para dejar en claro cuán "mucho más rápido" es esto, adjuntaré una captura de pantalla de la carga en dos servidores reales con el mismo hardware (Xeon E5-1650v2), igualmente configurado, usando el mismo kernel de Linux, pero ejecutando NAT en iptables (NAT4) y en nftables (NAT5).



No hay un gráfico de paquete por segundo en la captura de pantalla, pero en el perfil de carga de estos servidores el tamaño promedio del paquete es de alrededor de 800 bytes, por lo que los valores suben a 1.5Mpps. Como puede ver, el margen de rendimiento del servidor con nftables es enorme. Actualmente, este servidor procesa hasta 30 Gbit / s a ​​3Mpps y es claramente capaz de encontrarse con la limitación física de la red de 40Gbps, mientras tiene recursos de CPU libres.

Espero que este material sea útil para los ingenieros de redes que intentan mejorar el rendimiento de sus servidores.

All Articles