Roteamento rápido e NAT no Linux

À medida que os endereços IPv4 se esgotam, muitas operadoras de telecomunicações enfrentam a necessidade de fornecer aos seus clientes acesso à rede usando tradução de endereços. Neste artigo, direi como você pode obter desempenho Carrier Grade NAT em servidores comuns.

Um pouco de história

O tópico do esgotamento do espaço de endereço IPv4 não é mais novo. A certa altura, surgiram listas de espera no RIPE, depois surgiram bolsas nas quais foram negociados blocos de endereços e foram celebrados acordos de arrendamento. Gradualmente, as operadoras de telecomunicações começaram a fornecer serviços de acesso à Internet usando tradução de endereços e portas. Alguns não conseguiram obter endereços suficientes para emitir um endereço “branco” para cada assinante, enquanto outros começaram a poupar dinheiro recusando-se a comprar endereços no mercado secundário. Os fabricantes de equipamentos de rede apoiaram esta ideia, porque essa funcionalidade geralmente requer módulos de extensão ou licenças adicionais. Por exemplo, na linha de roteadores MX da Juniper (exceto os mais recentes MX104 e MX204), você pode executar NAPT em uma placa de serviço MS-MIC separada, Cisco ASR1k requer uma licença CGN, Cisco ASR9k requer um módulo A9K-ISM-100 separado e uma licença A9K-CGN -LIC para ele. Em geral, o prazer custa muito dinheiro.

IPTables

A tarefa de realizar NAT não requer recursos computacionais especializados, podendo ser resolvida por processadores de uso geral, que são instalados, por exemplo, em qualquer roteador doméstico. Na escala de uma operadora de telecomunicações, esse problema pode ser resolvido usando servidores comuns rodando FreeBSD (ipfw/pf) ou GNU/Linux (iptables). Não consideraremos o FreeBSD, porque... Parei de usar este sistema operacional há muito tempo, então vamos nos ater ao GNU/Linux.

Habilitar a tradução de endereços não é nada difícil. Primeiro você precisa registrar uma regra no iptables na tabela nat:

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

O sistema operacional carregará o módulo nf_conntrack, que monitorará todas as conexões ativas e realizará as conversões necessárias. Existem várias sutilezas aqui. Em primeiro lugar, como se trata de NAT na escala de uma operadora de telecomunicações, é necessário ajustar os timeouts, pois com valores padrão o tamanho da tabela de tradução crescerá rapidamente para valores catastróficos. Abaixo está um exemplo das configurações que usei em meus 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

E em segundo lugar, como o tamanho padrão da tabela de tradução não foi projetado para funcionar nas condições de uma operadora de telecomunicações, ele precisa ser aumentado:

net.netfilter.nf_conntrack_max = 3145728

Também é necessário aumentar o número de buckets para a tabela hash que armazena todos os broadcasts (esta é uma opção no módulo nf_conntrack):

options nf_conntrack hashsize=1572864

Após essas manipulações simples, obtém-se um design totalmente funcional que pode traduzir um grande número de endereços de clientes em um conjunto de endereços externos. Contudo, o desempenho desta solução deixa muito a desejar. Em minhas primeiras tentativas de usar GNU/Linux para NAT (por volta de 2013), consegui obter desempenho de cerca de 7 Gbit/s a 0.8 Mpps por servidor (Xeon E5-1650v2). Desde então, muitas otimizações diferentes foram feitas na pilha de rede do kernel GNU/Linux, o desempenho de um servidor no mesmo hardware aumentou para quase 18-19 Gbit/s a 1.8-1.9 Mpps (estes eram os valores máximos) , mas a demanda por volume de tráfego processado por um servidor cresceu muito mais rápido. Como resultado, foram desenvolvidos esquemas para equilibrar a carga nos diferentes servidores, mas tudo isso aumentou a complexidade de configuração, manutenção e manutenção da qualidade dos serviços prestados.

Tabelas NFT

Hoje em dia, uma tendência da moda em “bolsas de mudança” de software é o uso de DPDK e XDP. Muitos artigos foram escritos sobre este tema, muitos discursos diferentes foram feitos e produtos comerciais estão aparecendo (por exemplo, SKAT da VasExperts). Mas dados os recursos limitados de programação das operadoras de telecomunicações, é bastante problemático criar você mesmo qualquer “produto” baseado nessas estruturas. Será muito mais difícil operar tal solução no futuro; em particular, terão de ser desenvolvidas ferramentas de diagnóstico. Por exemplo, o tcpdump padrão com DPDK não funcionará assim e não “verá” os pacotes enviados de volta aos fios usando XDP. Em meio a toda a conversa sobre novas tecnologias para encaminhamento de pacotes para o espaço do usuário, elas passaram despercebidas relatórios и artigos Pablo Neira Ayuso, mantenedor do iptables, sobre o desenvolvimento de flow offloading em nftables. Vamos dar uma olhada mais de perto nesse mecanismo.

A ideia principal é que se o roteador passou pacotes de uma sessão em ambas as direções do fluxo (a sessão TCP entrou no estado ESTABLISHED), então não há necessidade de passar os pacotes subsequentes desta sessão por todas as regras de firewall, porque todas essas verificações ainda terminarão com o pacote sendo transferido para o roteamento. E na verdade não precisamos selecionar uma rota - já sabemos para qual interface e para qual host precisamos enviar pacotes nesta sessão. Tudo o que resta é armazenar essas informações e utilizá-las para roteamento em um estágio inicial do processamento de pacotes. Ao realizar o NAT, é necessário armazenar adicionalmente informações sobre alterações em endereços e portas traduzidas pelo módulo nf_conntrack. Sim, claro, neste caso vários policiais e outras regras de informação e estatísticas em iptables param de funcionar, mas no âmbito da tarefa de um NAT permanente separado ou, por exemplo, de uma fronteira, isso não é tão importante, porque os serviços são distribuídos entre dispositivos.

Configuração

Para usar esta função precisamos:

  • Use um kernel novo. Apesar do fato de a funcionalidade em si ter aparecido no kernel 4.16, por muito tempo ela foi muito “crua” e causou regularmente pânico no kernel. Tudo se estabilizou por volta de dezembro de 2019, quando os kernels LTS 4.19.90 e 5.4.5 foram lançados.
  • Reescreva as regras do iptables no formato nftables usando uma versão bastante recente do nftables. Funciona exatamente na versão 0.9.0

Se tudo em princípio estiver claro com o primeiro ponto, o principal é não esquecer de incluir o módulo na configuração durante a montagem (CONFIG_NFT_FLOW_OFFLOAD=m), então o segundo ponto requer explicação. As regras do nftables são descritas de maneira completamente diferente das do iptables. Documentação revela quase todos os pontos, também há especiais conversores regras de iptables para nftables. Portanto, darei apenas um exemplo de configuração de NAT e descarregamento de fluxo. Uma pequena lenda, por exemplo: , - estas são as interfaces de rede pelas quais o tráfego passa; na realidade, podem haver mais de duas delas. , — o endereço inicial e final do intervalo de endereços “brancos”.

A configuração do NAT é muito simples:

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

Com o descarregamento de fluxo é um pouco mais complicado, mas bastante compreensível:

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

Essa, na verdade, é toda a configuração. Agora todo o tráfego TCP/UDP cairá na tabela fastnat e será processado muito mais rapidamente.

Descobertas

Para deixar claro o quão “muito mais rápido” isso é, vou anexar uma captura de tela da carga em dois servidores reais, com o mesmo hardware (Xeon E5-1650v2), configurados de forma idêntica, usando o mesmo kernel Linux, mas realizando NAT em iptables (NAT4) e em nftables (NAT5).

Roteamento rápido e NAT no Linux

Não há gráfico de pacotes por segundo na captura de tela, mas no perfil de carga desses servidores o tamanho médio dos pacotes fica em torno de 800 bytes, então os valores chegam a até 1.5Mpps. Como você pode ver, o servidor com nftables possui uma enorme reserva de desempenho. Atualmente, este servidor processa até 30 Gbit/s a 3 Mpps e é claramente capaz de atender à limitação da rede física de 40 Gbps, ao mesmo tempo em que possui recursos de CPU livres.

Espero que este material seja útil para engenheiros de rede que estão tentando melhorar o desempenho de seus servidores.

Fonte: habr.com

Adicionar um comentário