Mudanças entre as edições de "CGNAT na pratica"

De Wiki BPF
Ir para: navegação, pesquisa
(Remoção dos negritos nos cabeçalhos para que o Table of Contents fique uniforme)
 
(32 revisões intermediárias por 3 usuários não estão sendo mostradas)
Linha 1: Linha 1:
== <big>Objetivo:</big> ==
+
__TOC__
Com o esgotamento do IPv4 mundialmente, precisamos tomar algumas providências para que a Internet não pare. As que vejo de imediatas são: IPv6 e CGNAT (Carrier Grade NAT). O IPv6 é a real solução para os problemas de esgotamento e o CGNAT seria a "gambiarra" necessária para continuar com o IPv4 até que a Internet esteja 100% em IPv6. Nesse artigo será explicado como montar uma caixa CGNAT funcional para atender as necessidades básicas. O que será colocado abaixo, funciona muito bem aqui no nosso AS e nos permite avançar com o IPv6. No nosso exemplo as caixas PPPoE (B-RAS - Broadband Remote Access Server) não fazem NAT, pois o recurso de controlar estados de conexões e controles de NAT, consomem muito processamento e podem atrapalhar a performance da caixa PPPoE.
+
[[Arquivo:773px-Cgnat nic br.jpg|nenhum|miniaturadaimagem|773x773px]]
  
== Diagrama de exemplo: ==
+
==Objetivo==
[[Arquivo:Diagrama exemplo do CGNAT.png|esquerda|miniaturadaimagem|495x495px]]
+
Com o esgotamento do IPv4 mundialmente, precisamos tomar algumas providências para que a Internet não pare. As que vejo de imediatas são: '''IPv6''' e '''CGNAT (Carrier Grade NAT)'''. O '''IPv6''' é a real solução para os problemas de esgotamento e o CGNAT seria a "gambiarra" necessária para continuar com o IPv4 até que a Internet esteja 100% em IPv6. Nesse artigo será explicado como montar uma caixa CGNAT Determinística usando '''GNU/Linux''' e '''Mikrotik RouterOS'''. Esse artigo foi baseado no treinamento da '''Semana de Capacitação''' do '''NIC.br''' e que pode ser encontrado com o título '''CONCEITOS E IMPLEMENTAÇÃO DE CGNAT''' [https://semanacap.bcp.nic.br/6-online/ '''aqui'''] como palestra e material de apoio e o vídeo do treinamento no '''Youtube''' '''[https://www.youtube.com/watch?v=1q7J3NkQVSc aqui]'''.
  
No '''B-RAS''' é configurado um '''PBR (Policy Based Routing)''' onde apenas IPs do bloco 100.64.0.0/10 são roteados diretamente para a '''caixa CGNAT'''. Qualquer IPv4 público ou IPv6, são roteados diretamente para o '''Router/Firewall'''. Isso evita processamento e tráfego desnecessário na '''caixa de CGNAT'''. Através dessa manobra evitamos qualquer uso de stateful e NAT no '''B-RAS''', aumentando a performance do mesmo. No diagrama ao lado a linha vermelha simboliza o tráfego do bloco 100.64.0.0/10 indo para o '''CGNAT'''. A linha amarela seria o tráfego já nateado e sendo roteado para o '''Router/Firewall'''. A linha verde é o tráfego mais limpo, sem "gambiarras" e o real objetivo que devemos seguir para uma '''Internet''' melhor.
+
== Diagrama ==
 +
[[Arquivo:776px-Cgnat diagrama2.png|esquerda|miniaturadaimagem|776x776px]]
 +
No '''BNG''' é configurado uma '''PBR (Policy Based Routing)''' onde apenas IPs do bloco '''100.64.0.0/22''' serão roteados diretamente para a '''caixa CGNAT'''. Qualquer IPv4 público ou IPv6, serão roteados diretamente para a '''Borda'''. Isso evita processamento e tráfego desnecessário na '''caixa CGNAT'''.  
  
O '''Router/Firewall''' é um equipamento onde podemos inserir algumas regras de Firewall stateless para filtrar alguns pacotes indesejados como alguns tipos de spoofing, alguns pacotes UDP de certas portas usadas para ataques de amplificação, etc. Nunca! Jamais faça uso de regras stateful nesse nível de Firewall, pois determinados ataques podem estourar suas tabelas de controle de estados de conexões e derrubar todo o tráfego.
+
No diagrama ao lado a linha '''amarela''' simboliza o tráfego do bloco '''100.64.0.0/22''' indo para o '''CGNAT'''. A linha '''vermelha''' seria o tráfego já traduzido para um IP da rede '''198.18.0.0/27''' e encaminhado para a '''Borda'''. A linha '''verde''' é o tráfego mais limpo, sem "gambiarras" e o real objetivo que devemos seguir para uma '''Internet''' melhor usando IPv6.
  
O '''Router de Borda''' dispensa comentários não é mesmo? Vamos voltar para o conteúdo que realmente interessa.
+
A '''Borda''' é um equipamento onde podemos inserir algumas regras de filtros de pacotes stateless para filtrar alguns pacotes indesejados como por exemplo: determinados spoofings e BOGONs. Também onde serão feitas ACLs para filtros BGP. '''Ação 1''' e '''2''' do '''MANRS'''.
  
O objetivo deste artigo é exemplificar algumas configurações básicas e deixar para o leitor criar suas próprias ferramentas para facilitar a criação das regras que na maioria das vezes são em grande quantidade e inviável de se fazer manualmente.
+
O '''Cliente''' nesse diagrama aparece conectado com o '''IPv4''' de CGNAT '''100.64.0.2''' e '''IPv6 2001:0db8:f18:0:a941:6164:1a79:c0f3'''. Todo o acesso IPv4 desse cliente e nesse exemplo, para a Internet, sairá com o IP '''198.18.0.0''' usando as portas entre '''5056''' e '''7071''', conforme mostraremos no script gerador de regras de CGNAT.
 +
<br><br><br><br><br><br><br><br><br><br><br><br><br><br>
  
Algumas ferramentas de uso público serão comentadas e passadas as devidas referências.
 
  
== Dados técnicos do equipamento usado no exemplo: ==
 
* Intel Dual Xeon E5-2630 v2 @ 3.1GHz.
 
* 16Gb de RAM.
 
* 2 interfaces Intel X520-SR2 (2 portas ópticas de 10GbE).
 
* 1 SSD 120Gb.
 
  
== Dados técnicos do Sistema usado: ==
+
== CGNAT no GNU/Linux ==
* Debian 9 (stretch) 64 bits.
 
* kernel 4.19 (backports do Debian).
 
* Netfilter/IPTables.
 
  
== '''Gráficos CGNAT em produção:''' ==
+
=== Hardware e Sistema que utilizaremos no GNU/Linux ===
[[Arquivo:Zabbix cgnat.png|miniaturadaimagem|980x980px|nenhum]]
+
* 2x Intel® Xeon® Silver 4215R Processor (3.20 GHz, 11M Cache, 8 núcleos/16 threads). Ambiente NUMA (non-uniform memory access).
 +
* 32Gb de ram.
 +
* 2x SSD 240 Gb RAID1.
 +
* 2x Interfaces de rede Intel XL710-QDA2 (2 portas de 40 Gbps).
 +
* GNU/Linux Debian 11 (Bullseye).
  
[[Arquivo:Bmon.png|miniaturadaimagem|980x980px|nenhum]]
+
Vamos configurar um LACP com as duas portas de cada interface, para que possamos ter um backup, caso algum módulo apresente algum problema. Seu ambiente de produção pode ser diferente e por isso precisamos ter alguns cuidados na hora de montarmos o conjunto de hardware e não obtermos surpresas.
[[Arquivo:Htop.png|miniaturadaimagem|980x980px|nenhum]]
 
  
== Descrição: ==
+
1º Verifique algumas especificações da interface de rede que será usada. Por exemplo a '''Intel XL710-QDA2''':
Como todo bom sistema não basta apenas instalar os programas e por um passe de mágica tudo funcionar como deveria. Estamos falando aqui de um sistema que se mal configurado, a caixa apresentará problemas de performance e você não conseguirá alcançar seu objetivo que é montar uma '''caixa CGNAT''' BBB (Bom Bonito e Barato). Para isso você vai precisar instalar um sistema Debian 9 64 bits e remover todos os serviços desnecessários. Faça uma instalação bem clean, Mantenha o serviço sshd rodando para que possa acessar remotamente e continuar configurando o equipamento. Lembre-se: serviços desnecessários rodando, poderão ser uma vulnerabilidade no futuro. O sistema aqui em produção gerencia um tráfego de CGNAT, em horário de pico, de 3Gbps e com bastante folga de recursos. Abaixo continuaremos com as configurações do sistema.
 
  
== CPU Affinity: ==
+
* 2 portas de 40 Gbps.
Balancear o tráfego das interfaces de rede em seus devidos cores é importantíssimo para o melhor aproveitamento do hardware. Essa configuração é um pouco mais complexa quando estamos trabalhando com arquitetura '''NUMA'''. Usando '''NUMA''' é importante verificar se a mother board possui gerencia de slots por CPU. Dessa forma você pode colocar uma interface de 10GbE em um slot gerenciado pela CPU0 e uma outra interface de 10GbE em outro slot gerenciado pela CPU1. Isso você pode procurar no manual técnico da mother board. Para checar qual CPU é responsável pela sua interface de rede, use o comando abaixo substituindo '''<interface>''' pela interface desejada. Obs.: se o valor retornado for '''-1''' sua mother board não é boa pra CPU affinity. Os valores retornados para um equipamento com 2 CPUs deveriam ser 0 ou 1. Onde 0 seria CPU0 e 1, CPU1.<blockquote><code># cat /sys/class/net/'''<interface>'''/device/numa_node</code></blockquote>Para saber os cores relacionados à CPU podemos usar os seguintes comandos abaixo: <blockquote><code># cat /sys/devices/system/node/node0/cpulist</code></blockquote><blockquote><code>0-5</code></blockquote><blockquote><code># cat /sys/devices/system/node/node1/cpulist</code></blockquote><blockquote><code>6-11</code></blockquote>Um conjunto de ferramentas que costumo usar é o netutils-linux que pode ser encontrado [https://github.com/strizhechenko/netutils-linux aqui]. Nele encontrará ferramentas para setar o cpu affinity, maximizar a frequência das CPUs e aumentar o buffer de rx das interfaces de rede.
+
* PCIe 3.0 x8 (8.0 GT/s).
  
== Variáveis /proc: ==
+
Com essa informação seu equipamento não poderá possuir slots PCIe inferiores a esta especificação, caso contrário terá problemas de desempenho.
Vamos fazer alguns ajustes nas variáveis do /proc porque como o sistema vai utilizar o recurso de conntracks, precisamos mexer em alguns timeouts tcp/udp e outras variáveis. Isso faz parte do tuning para que o sistema fique mais leve e performático. Caso não façamos essas  alterações, corremos o risco de estourar a tabela de conntracks e interromper os acessos.
 
  
<blockquote><code>echo 5 > /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_syn_sent</code></blockquote><blockquote><code>echo 5 > /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_syn_recv</code></blockquote><blockquote><code>echo 300 > /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_established</code></blockquote><blockquote><code>echo 10 > /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_fin_wait</code></blockquote><blockquote><code>echo 10 > /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_close_wait</code></blockquote><blockquote><code>echo 10 > /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_last_ack</code></blockquote><blockquote><code>echo 10 > /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_time_wait</code></blockquote><blockquote><code>echo 10 > /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_close</code></blockquote><blockquote><code>echo 3 > /proc/sys/net/netfilter/nf_conntrack_tcp_max_retrans</code></blockquote><blockquote><code>echo 300 > /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_unacknowledged</code></blockquote><blockquote><code>echo 10 > /proc/sys/net/netfilter/nf_conntrack_udp_timeout</code></blockquote><blockquote><code>echo 180 > /proc/sys/net/netfilter/nf_conntrack_udp_timeout_stream</code></blockquote><blockquote><code>echo 10 > /proc/sys/net/netfilter/nf_conntrack_icmp_timeout</code></blockquote><blockquote><code>echo 600 > /proc/sys/net/netfilter/nf_conntrack_generic_timeout</code></blockquote>
+
Você também precisa estar atento para as limitações de barramento por '''versão''' x '''lane''' ('''x1'''):
  
== Variáveis sysctl: ==
+
* PCIe 1.0/1.1 - 2.5 GT/s - (8b/10b encoding) - 2 Gbps.
Agora temos umas poucas variáveis de '''sysctl''' para justar em '''/etc/sysctl.conf''':<blockquote><code>net.core.default_qdisc=fq</code></blockquote><blockquote><code>net.ipv4.tcp_congestion_control=bbr</code></blockquote><blockquote><code>net.core.rmem_max = 2147483647</code></blockquote><blockquote><code>net.core.wmem_max = 2147483647</code></blockquote><blockquote><code>net.ipv4.tcp_rmem = 4096 87380 2147483647</code></blockquote><blockquote><code>net.ipv4.tcp_wmem = 4096 65536 2147483647</code></blockquote><blockquote><code>net.ipv4.conf.all.forwarding=1</code></blockquote><blockquote><code>net.ipv6.conf.all.forwarding=1</code></blockquote>
+
* PCIe 2.0/2.1 - 5.0 GT/s - (8b/10b encoding)  - 4 Gbps.
 +
* PCIe 3.0/3.1 - 8.0 GT/s - (128b/130b encoding) - ~7,88 Gbps.
 +
* PCIe 4.0 - 16 GT/s - (128b/130b encoding) - ~15,76 Gbps.
  
== '''Ativando o BBR (TCP Congestion Control do Google):''' ==
+
=== Calculando a capacidade ===
O Google desenvolveu um TCP Congestion Control que já no kernel da distro Debian e pode ser facilmente habilitado. A finalidade é ter um melhor controle do congestionamento dos fluxos de TCP. Seria um substituto para o '''CUBIC''' e '''New Reno'''. Nas variáveis '''sysctl''' acima especificamos o '''BBR''' mas precisamo carregar o módulo do kernel antes de usá-lo. Basta adicionarmos o '''tcp_bbr''' em '''/etc/modules''' que o mesmo será carregado após o boot do sistema. Também adicionei o '''dummy''' que não tem nenhum relacionamento com o '''BBR''', mas que será usado para criarmos as interfaces virtuais com os IPs públicos que serão usados no NAT.<blockquote><code>tcp_bbr</code></blockquote><blockquote><code>dummy</code></blockquote>
+
Se observarmos a '''XL710-QDA2''' é '''PCIe 3.0 x8 (8 lanes)''' ou seja o barramento irá suportar:
  
== Arquivos de configuração do Sistema: ==
+
* '''8.0 GT/s * (128b/130b encoding) * 8 lanes = 63,01 Gbps'''
Os arquivos de configurações do Sistema, são aqueles que usaremos para fazer algumas optimizações, levantar as interfaces de rede e carregar as regras de '''CGNAT''' propriamente ditas. Esses arquivos foram alterados para o nosso exemplo e são mais para dar uma ideia de como podem ser feitos. Cabe ao leitor decidir a melhor forma de implementar o que será colocado à seguir. O objetivo é fazer com que o sysadmin melhore e crie novas maneiras de se fazer o proposto nesse artigo.
 
  
=== /etc/network/interfaces: ===
+
O objetivo do '''LACP''' nesse caso, '''não seria alcançar os''' '''80 Gbps de capacidade''' em cada interface, mesmo porque cada barramento das interfaces é '''limitado em 63,01 Gbps''', mas manteremos um '''backup dos 40 Gbps'''.  
[[Arquivo:Interfaces.png|nenhum|miniaturadaimagem|469x469px]]
 
  
O '''/usr/local/bin/rx-buffers-increase''' é uma das ferramentas do '''netutils-linux''', citado mais acima. A interface '''ens5f0''' é a interface externa do '''CGNAT''' que fala com o '''Router/Firewall''' e a '''ens5f1''', a interface interna do '''CGNAT''' que fala com o '''B-RAS'''.
+
Nessa configuração teríamos teoricamente '''63,01 Gbps de entrada e 63,01 Gbps de saída'''. Mas para esse cenário precisaremos fazer uma coisa chamada '''CPU Affinity'''. Nesse caso colocaríamos um processador dedicado para cada interface de rede. É um cenário mais complexo do que com 1 processador apenas, inclusive necessitamos de olhar o datasheet da motherboard e identificar quais slots PCIe são diretamente controlados por qual CPU. Se temos a '''CPU0''' e '''CPU1''', uma interface precisará ficar no slot controlado pela '''CPU0''' e a outra interface no slot controlado pela '''CPU1''' e '''observar a quantidade de lanes no slot para ver se suporta a mesma quantidade de lanes da interface de rede'''.
  
=== /etc/rc.local: ===
+
Falando um pouco sobre PPS (Packet Per Second) para calcular por exemplo 1 Gbps de tráfego na ethernet, a quantidade de PPS que o sistema precisaria suportar encaminhar teríamos: 1.000.000.000/8/1518 = 82.345 packets per second.
[[Arquivo:Rc local.png|nenhum|miniaturadaimagem|674x674px]]As duas primeiras linhas desabilitam o '''TSO''' e '''LRO''' das interfaces. Segundo muitos docs que li é aconselhado para uma melhor performance em roteamento. O ganho não é absurdo mas ajuda. Em seguida é criado uma interface '''dummy0''' com o IP público '''192.0.0.0/32''' que será usado para o '''CGNAT'''. Em casos reais são criados diversos IPs virtuais na dummy0 e nesse caso faz-se necessário criar um script para gerar em tempo de boot, todos os IPs que serão usados na caixa. Fica aqui como exercício de casa para se pensar. O '''rss-ladder''' é o programa que faz o cpu affinity citado acima. No exemplo acima estou setando todos os cores para as duas interfaces porque estou usando apenas uma Intel X520-SR2. O melhor seria uma interface recebendo o link externo entrante e trabalhando nos cores corretos, por exemplo, 0-5 e a outra interface X520-SR2 para o tráfego sainte nos cores 6-11. Mas lembre-se de checar quais cores estão gerenciando determinada interface de rede. Lembre-se da casadinha slot/cores. Primeiro você identifica por qual node a interface é gerenciada e depois quais cores pertencem à aquele node. Mostrei isso bem acima. O programa '''maximize-cpu-freq''' faz exatamente o que o nome sugere. Em seguida executo os scripts que criarão as regras de NAT. Mais à frente veremos ele. Fazemos as optimizações do '''/proc''' e criamos a rota de retorno do '''100.64.0.0/27''' de volta para o '''B-RAS (192.168.254.2)'''.
 
  
== Scripts CGNAT: ==
+
Existe um comando no GNU/Linux para você saber se o seu equipamento com processadores físicos, conseguirá trabalhar com o '''CPU Affinity''':
Nesse ponto vamos criar nossas regras de NAT e finalmente dar forma ao objetivo do nosso artigo. Primeiro vamos entender o seguinte: o '''Netfilter/IPTables''' trabalha com '''first''' '''match wins''' ou seja, o pacote quando encontra a regra que se satisfaz, da match e descarta o restante das regras. Em uma caixa CGNAT vão existir muitas mas muitas regras. A nossa aqui por exemplo tem mais de 20.000 regras de NAT. Agora imagina um pacote ter que passar por essas 20.000 regras e só dar match lá no final. Isso vai causar um bom uso de processamento desnecessário e não queremos isso. Então para resolver esse problema usamos regras com chains específicas, assim reduzimos o processamento pois o pacote vai seguir as chains reduzidas e encontrar seu match rapidamente. Isso ficará mais claro com o exemplo mais à frente mas sugiro estudar Netfilter/IPTables para uma base melhor de entendimento.
+
# cat /sys/class/net/<interface>/device/numa_node
 +
Se o resultado do comando acima for '''-1''' então esse equipamento não trabalhará com o '''CPU Affinity'''. Isso porque cada interface precisa estar sendo gerenciada por um node específico. Se são 2 processadores então o resultado deveria ser '''0 de CPU0''' ou '''1 de CPU1'''.
  
Vamos criar um ambiente pro nosso artigo: crie um diretório '''/root/cgnat''' que é onde ficarão todos os scripts que usaremos abaixo:
+
A seguir veremos um exemplo de datasheet da '''motherboard S2600WF''':
 +
[[Arquivo:903px-S2600wf.png|nenhum|miniaturadaimagem|903x903px]]
 +
Se observarmos o datasheet acima veremos que temos o '''PCIe Riser #1''', '''Riser #2''' e '''Riser #3'''. Cada Riser possui slots PCIe que são gerenciados por determinada CPU. Se colocássemos as duas interfaces de rede nos '''slots do Riser #2''' e '''Riser #3''', estaríamos pendurando tudo apenas no '''processador 2'''. Isso foi apenas para mostrar a complexidade de quando usamos um equipamento '''NUMA''' e estamos somente escolhendo o hardware adequado. Ainda não chegamos na configuração do '''CPU Affinity'''.
  
=== frw.sh: ===
+
Para sabermos quais cores estão relacionados para uma determinada CPU, utilizamos os comandos abaixo:
[[Arquivo:Frw.png|nenhum|miniaturadaimagem|806x806px]]Não vamos entrar em detalhes sobre as regras em si porque isso já sairia muito do artigo. Mas reparem que são criados duas '''chains CGNATOUT''' e '''CGNATIN''' e todo tráfego que sair pela interface '''ens5f0''' será direcionados para a '''CGNATOUT''' e o que entrar irá para a '''CGNATIN'''. Abaixo são criadas outras chains para confinar o bloco '''100.64.0.0/27''' e cada bloco deve ter sua chain de confinamento. Dentro dessa chain fazemos o '''CGNAT''' '''determinístico''' com pode ser visto abaixo:
+
# cat /sys/devices/system/node/node0/cpulist
 +
0-7
 +
 +
# cat /sys/devices/system/node/node1/cpulist
 +
8-15
 +
No exemplo acima a '''CPU0''' '''tem os cores de 0 a 7''' e a '''CPU1, os cores de 8 a 15''', ou seja, é um equipamento com '''16 cores'''.
  
=== Abaixo as regras do cgnat-bras1.conf: ===
+
=== Tuning antes do CPU Affinity ===
/sbin/iptables -t nat -N CGNATOUT_0
+
Também é importante, para aumento de performance, que seja '''desabilitado na BIOS o HT (Hyper Threading)'''.
  
/sbin/iptables -t nat -N CGNATIN_0
+
Antes de configurarmos algumas coisas no nosso ambiente, precisaremos instalar uma ferramenta importante para o nosso tuning; vamos instalar o pacote '''ethtool'''. Ele servirá para fazermos alguns ajustes nas nossas interfaces de rede. Alguns fabricantes podem não permitir certas alterações mas com as interfaces da Intel sempre obtive os resultados esperados.
 +
# apt install ethtool
 +
No nosso exemplo acima vimos que o equipamento possui '''16 cores''' sendo que '''8 cores por CPU'''. Então, para esse caso,  faremos um ajuste nas interfaces para ficarem preparadas para receberem '''8 cores em cada através das IRQs'''. Usamos o parâmetro '''-l''' do '''ethtool''' para listar o '''Pre-set maximums combined''' da interface e o parâmetro '''-L''' para alterar esse valor. Façamos então a alteração:
 +
# ethtool -L enp5s0f0 combined 8
 +
# ethtool -L enp5s0f1 combined 8
 +
# ethtool -L enp6s0f0 combined 8
 +
# ethtool -L enp6s0f1 combined 8
 +
Com os comandos acima deixamos preparadas as interfaces para aceitarem '''8 cores em cada uma através das IRQs'''.
  
/sbin/iptables -t nat -F CGNATOUT_0
+
Não podemos usar o programa '''irqbalance''' para o '''CPU Affinity''', pois este faz migração de contextos entre os cores e isso é ruim. Como no nosso exemplo estamos usando uma interface Intel, utilizaremos um script da própria Intel para realizar o '''CPU Affinity''' de forma mais fácil. Esse script se chama '''set_irq_affinity''' e vem acompanhado com os fontes do driver da interface. Ex.: '''[https://www.intel.com/content/www/us/en/download/18026/intel-network-adapter-driver-for-pcie-40-gigabit-ethernet-network-connections-under-linux.html Intel Network Adapter]'''
  
/sbin/iptables -t nat -F CGNATIN_0
+
=== Código do script '''set_irq_affinity''' ===
 +
#!/bin/bash
 +
# SPDX-License-Identifier: BSD-3-Clause
 +
# Copyright (c) 2015 - 2019, Intel Corporation
 +
#
 +
# Affinitize interrupts to cores
 +
#
 +
# typical usage is (as root):
 +
# set_irq_affinity -x local eth1 <eth2> <eth3>
 +
# set_irq_affinity -s eth1
 +
#
 +
# to get help:
 +
# set_irq_affinity
 +
 +
usage()
 +
{
 +
        echo
 +
        echo "Usage: option -s <interface> to show current settings only"
 +
        echo "Usage: $0 [-x|-X] [all|local|remote [<node>]|one <core>|custom|<cores>] <interface> ..."
 +
        echo "  Options: "
 +
        echo "    -s            Shows current affinity settings"
 +
        echo "    -x            Configure XPS as well as smp_affinity"
 +
        echo "    -X            Disable XPS but set smp_affinity"
 +
        echo "    [all] is the default value"
 +
        echo "    [remote [<node>]] can be followed by a specific node number"
 +
        echo "  Examples:"
 +
        echo "    $0 -s eth1            # Show settings on eth1"
 +
 +
        echo "    $0 all eth1 eth2      # eth1 and eth2 to all cores"
 +
        echo "    $0 one 2 eth1        # eth1 to core 2 only"
 +
        echo "    $0 local eth1        # eth1 to local cores only"
 +
        echo "    $0 remote eth1        # eth1 to remote cores only"
 +
        echo "    $0 custom eth1        # prompt for eth1 interface"
 +
        echo "    $0 0-7,16-23 eth0    # eth1 to cores 0-7 and 16-23"
 +
        echo
 +
        exit 1
 +
}
 +
 +
usageX()
 +
{
 +
        echo "options -x and -X cannot both be specified, pick one"
 +
        exit 1
 +
}
 +
 +
if [ "$1" == "-x" ]; then
 +
        XPS_ENA=1
 +
        shift
 +
fi
 +
 +
if [ "$1" == "-s" ]; then
 +
        SHOW=1
 +
        echo Show affinity settings
 +
        shift
 +
fi
 +
 +
if [ "$1" == "-X" ]; then
 +
        if [ -n "$XPS_ENA" ]; then
 +
                usageX
 +
        fi
 +
        XPS_DIS=2
 +
        shift
 +
fi
 +
 +
if [ "$1" == -x ]; then
 +
        usageX
 +
fi
 +
 +
if [ -n "$XPS_ENA" ] && [ -n "$XPS_DIS" ]; then
 +
        usageX
 +
fi
 +
 +
if [ -z "$XPS_ENA" ]; then
 +
        XPS_ENA=$XPS_DIS
 +
fi
 +
 +
SED=`which sed`
 +
if <nowiki>[[ ! -x $SED ]]</nowiki>; then
 +
        echo " $0: ERROR: sed not found in path, this script requires sed"
 +
        exit 1
 +
fi
 +
 +
num='^[0-9]+$'
 +
 +
# search helpers
 +
NOZEROCOMMA="s/^[0,]*//"
 +
# Vars
 +
AFF=$1
 +
shift
 +
 +
case "$AFF" in
 +
    remote)    <nowiki>[[ $1 =~ $num ]]</nowiki> && rnode=$1 && shift ;;
 +
    one)        <nowiki>[[ $1 =~ $num ]]</nowiki> && cnt=$1 && shift ;;
 +
    all)        ;;
 +
    local)      ;;
 +
    custom)    ;;
 +
    [0-9]*)    ;;
 +
    -h|--help)  usage ;;
 +
    "")        usage ;;
 +
    *)          IFACES=$AFF && AFF=all ;;      # Backwards compat mode
 +
esac
 +
 +
# append the interfaces listed to the string with spaces
 +
while [ "$#" -ne "0" ] ; do
 +
        IFACES+=" $1"
 +
        shift
 +
done
 +
 +
# for now the user must specify interfaces
 +
if [ -z "$IFACES" ]; then
 +
        usage
 +
        exit 2
 +
fi
 +
 +
notfound()
 +
{
 +
        echo $MYIFACE: not found
 +
        exit 15
 +
}
 +
 +
# check the interfaces exist
 +
for MYIFACE in $IFACES; do
 +
        grep -q $MYIFACE /proc/net/dev || notfound
 +
done
 +
 +
# support functions
 +
 +
build_mask()
 +
{
 +
        VEC=$core
 +
        if [ $VEC -ge 32 ]
 +
        then
 +
                MASK_FILL=""
 +
                MASK_ZERO="00000000"
 +
                let "IDX = $VEC / 32"
 +
                for ((i=1; i<=$IDX;i++))
 +
                do
 +
                        MASK_FILL="${MASK_FILL},${MASK_ZERO}"
 +
                done
 +
 +
                let "VEC -= 32 * $IDX"
 +
                MASK_TMP=$((1<<$VEC))
 +
                MASK=$(printf "%X%s" $MASK_TMP $MASK_FILL)
 +
        else
 +
                MASK_TMP=$((1<<$VEC))
 +
                MASK=$(printf "%X" $MASK_TMP)
 +
        fi
 +
}
 +
 +
show_affinity()
 +
{
 +
        # returns the MASK variable
 +
        build_mask
 +
 +
        SMP_I=`sed -E "${NOZEROCOMMA}" /proc/irq/$IRQ/smp_affinity`
 +
        HINT=`sed -E "${NOZEROCOMMA}" /proc/irq/$IRQ/affinity_hint`
 +
        printf "ACTUAL  %s %d %s <- /proc/irq/$IRQ/smp_affinity\n" $IFACE $core $SMP_I
 +
        printf "HINT    %s %d %s <- /proc/irq/$IRQ/affinity_hint\n" $IFACE $core $HINT
 +
        IRQ_CHECK=`grep '[-,]' /proc/irq/$IRQ/smp_affinity_list`
 +
        if [ ! -z $IRQ_CHECK ]; then
 +
                printf " WARNING -- SMP_AFFINITY is assigned to multiple cores $IRQ_CHECK\n"
 +
        fi
 +
        if [ "$SMP_I" != "$HINT" ]; then
 +
                printf " WARNING -- SMP_AFFINITY VALUE does not match AFFINITY_HINT \n"
 +
        fi
 +
        printf "NODE    %s %d %s <- /proc/irq/$IRQ/node\n" $IFACE $core `cat /proc/irq/$IRQ/node`
 +
        printf "LIST    %s %d [%s] <- /proc/irq/$IRQ/smp_affinity_list\n" $IFACE $core `cat /proc/irq/$IRQ/smp_affinity_list`
 +
        printf "XPS    %s %d %s <- /sys/class/net/%s/queues/tx-%d/xps_cpus\n" $IFACE $core `cat /sys/class/net/$IFACE/queues/tx-$((n-1))/xps_cpus` $IFACE $((n-1))
 +
        if [ -z `ls /sys/class/net/$IFACE/queues/tx-$((n-1))/xps_rxqs` ]; then
 +
                echo "WARNING: xps rxqs not supported on $IFACE"
 +
        else
 +
                printf "XPSRXQs %s %d %s <- /sys/class/net/%s/queues/tx-%d/xps_rxqs\n" $IFACE $core `cat /sys/class/net/$IFACE/queues/tx-$((n-1))/xps_rxqs` $IFACE $((n-1))
 +
        fi
 +
        printf "TX_MAX  %s %d %s <- /sys/class/net/%s/queues/tx-%d/tx_maxrate\n" $IFACE $core `cat /sys/class/net/$IFACE/queues/tx-$((n-1))/tx_maxrate` $IFACE $((n-1))
 +
        printf "BQLIMIT %s %d %s <- /sys/class/net/%s/queues/tx-%d/byte_queue_limits/limit\n" $IFACE $core `cat /sys/class/net/$IFACE/queues/tx-$((n-1))/byte_queue_limits/limit` $IFACE $((n-1))
 +
        printf "BQL_MAX %s %d %s <- /sys/class/net/%s/queues/tx-%d/byte_queue_limits/limit_max\n" $IFACE $core `cat /sys/class/net/$IFACE/queues/tx-$((n-1))/byte_queue_limits/limit_max` $IFACE $((n-1))
 +
        printf "BQL_MIN %s %d %s <- /sys/class/net/%s/queues/tx-%d/byte_queue_limits/limit_min\n" $IFACE $core `cat /sys/class/net/$IFACE/queues/tx-$((n-1))/byte_queue_limits/limit_min` $IFACE $((n-1))
 +
        if [ -z `ls /sys/class/net/$IFACE/queues/rx-$((n-1))/rps_flow_cnt` ]; then
 +
                echo "WARNING: aRFS is not supported on $IFACE"
 +
        else
 +
                printf "RPSFCNT %s %d %s <- /sys/class/net/%s/queues/rx-%d/rps_flow_cnt\n" $IFACE $core `cat /sys/class/net/$IFACE/queues/rx-$((n-1))/rps_flow_cnt` $IFACE $((n-1))
 +
        fi
 +
        if [ -z `ls /sys/class/net/$IFACE/queues/rx-$((n-1))/rps_cpus` ]; then
 +
                echo "WARNING: rps_cpus is not available on $IFACE"
 +
        else
 +
                printf "RPSCPU  %s %d %s <- /sys/class/net/%s/queues/rx-%d/rps_cpus\n" $IFACE $core `cat /sys/class/net/$IFACE/queues/rx-$((n-1))/rps_cpus` $IFACE $((n-1))
 +
        fi
 +
        echo
 +
}
 +
 +
set_affinity()
 +
{
 +
        # returns the MASK variable
 +
        build_mask
 +
 +
        printf "%s" $MASK > /proc/irq/$IRQ/smp_affinity
 +
        printf "%s %d %s -> /proc/irq/$IRQ/smp_affinity\n" $IFACE $core $MASK
 +
        SMP_I=`sed -E "${NOZEROCOMMA}" /proc/irq/$IRQ/smp_affinity`
 +
        if [ "$SMP_I" != "$MASK" ]; then
 +
                printf " ACTUAL\t%s %d %s <- /proc/irq/$IRQ/smp_affinity\n" $IFACE $core $SMP_I
 +
                printf " WARNING -- SMP_AFFINITY setting failed\n"
 +
        fi
 +
        case "$XPS_ENA" in
 +
        1)
 +
                printf "%s %d %s -> /sys/class/net/%s/queues/tx-%d/xps_cpus\n" $IFACE $core $MASK $IFACE $((n-1))
 +
                printf "%s" $MASK > /sys/class/net/$IFACE/queues/tx-$((n-1))/xps_cpus
 +
        ;;
 +
        2)
 +
                MASK=0
 +
                printf "%s %d %s -> /sys/class/net/%s/queues/tx-%d/xps_cpus\n" $IFACE $core $MASK $IFACE $((n-1))
 +
                printf "%s" $MASK > /sys/class/net/$IFACE/queues/tx-$((n-1))/xps_cpus
 +
        ;;
 +
        *)
 +
        esac
 +
}
 +
 +
# Allow usage of , or -
 +
#
 +
parse_range () {
 +
        RANGE=${@//,/ }
 +
        RANGE=${RANGE//-/..}
 +
        LIST=""
 +
        for r in $RANGE; do
 +
                # eval lets us use vars in {#..#} range
 +
                <nowiki>[[ $r =~ '..' ]]</nowiki> && r="$(eval echo {$r})"
 +
                LIST+=" $r"
 +
        done
 +
        echo $LIST
 +
}
 +
 +
# Affinitize interrupts
 +
#
 +
doaff()
 +
{
 +
        CORES=$(parse_range $CORES)
 +
        ncores=$(echo $CORES | wc -w)
 +
        n=1
 +
 +
        # this script only supports interrupt vectors in pairs,
 +
        # modification would be required to support a single Tx or Rx queue
 +
        # per interrupt vector
 +
 +
        queues="${IFACE}-.*TxRx"
 +
 +
        irqs=$(grep "$queues" /proc/interrupts | cut -f1 -d:)
 +
        [ -z "$irqs" ] && irqs=$(grep $IFACE /proc/interrupts | cut -f1 -d:)
 +
        [ -z "$irqs" ] && irqs=$(for i in `ls -1 /sys/class/net/${IFACE}/device/msi_irqs | sort -n` ;do grep -w $i: /proc/interrupts | egrep -v 'fdir|async|misc|ctrl' | cut -f 1 -d :; done)
 +
        [ -z "$irqs" ] && echo "Error: Could not find interrupts for $IFACE"
 +
 +
        if [ "$SHOW" == "1" ] ; then
 +
                echo "TYPE IFACE CORE MASK -> FILE"
 +
                echo "============================"
 +
        else
 +
                echo "IFACE CORE MASK -> FILE"
 +
                echo "======================="
 +
        fi
 +
 +
        for IRQ in $irqs; do
 +
                [ "$n" -gt "$ncores" ] && n=1
 +
                j=1
 +
                # much faster than calling cut for each
 +
                for i in $CORES; do
 +
                        [ $((j++)) -ge $n ] && break
 +
                done
 +
                core=$i
 +
                if [ "$SHOW" == "1" ] ; then
 +
                        show_affinity
 +
                else
 +
                        set_affinity
 +
                fi
 +
                ((n++))
 +
        done
 +
}
 +
 +
# these next 2 lines would allow script to auto-determine interfaces
 +
#[ -z "$IFACES" ] && IFACES=$(ls /sys/class/net)
 +
#[ -z "$IFACES" ] && echo "Error: No interfaces up" && exit 1
 +
 +
# echo IFACES is $IFACES
 +
 +
CORES=$(</sys/devices/system/cpu/online)
 +
[ "$CORES" ] || CORES=$(grep ^proc /proc/cpuinfo | cut -f2 -d:)
 +
 +
# Core list for each node from sysfs
 +
node_dir=/sys/devices/system/node
 +
for i in $(ls -d $node_dir/node*); do
 +
        i=${i/*node/}
 +
        corelist[$i]=$(<$node_dir/node${i}/cpulist)
 +
done
 +
 +
for IFACE in $IFACES; do
 +
        # echo $IFACE being modified
 +
 +
        dev_dir=/sys/class/net/$IFACE/device
 +
        [ -e $dev_dir/numa_node ] && node=$(<$dev_dir/numa_node)
 +
        [ "$node" ] && [ "$node" -gt 0 ] || node=0
 +
 +
        case "$AFF" in
 +
        local)
 +
                CORES=${corelist[$node]}
 +
        ;;
 +
        remote)
 +
                [ "$rnode" ] || { [ $node -eq 0 ] && rnode=1 || rnode=0; }
 +
                CORES=${corelist[$rnode]}
 +
        ;;
 +
        one)
 +
                [ -n "$cnt" ] || cnt=0
 +
                CORES=$cnt
 +
        ;;
 +
        all)
 +
                CORES=$CORES
 +
        ;;
 +
        custom)
 +
                echo -n "Input cores for $IFACE (ex. 0-7,15-23): "
 +
                read CORES
 +
        ;;
 +
        [0-9]*)
 +
                CORES=$AFF
 +
        ;;
 +
        *)
 +
                usage
 +
                exit 1
 +
        ;;
 +
        esac
 +
 +
        # call the worker function
 +
        doaff
 +
done
 +
 +
# check for irqbalance running
 +
IRQBALANCE_ON=`ps ax | grep -v grep | grep -q irqbalance; echo $?`
 +
if [ "$IRQBALANCE_ON" == "0" ] ; then
 +
        echo " WARNING: irqbalance is running and will"
 +
        echo "          likely override this script's affinitization."
 +
        echo "          Please stop the irqbalance service and/or execute"
 +
        echo "          'killall irqbalance'"
 +
        exit 2
 +
fi
  
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.0 -p tcp -j SNAT --to 192.0.0.0:1-2048
+
=== CPU Affinity ===
 +
Agora que preparamos as interfaces, façamos os apontamentos dos cores da seguinte forma. Vamos supor que colocamos o script em '''/root/scripts''':
 +
# /root/scripts/set_irq_affinity 0-7 enp5s0f0
 +
# /root/scripts/set_irq_affinity 0-7 enp5s0f1
 +
# /root/scripts/set_irq_affinity 8-15 enp6s0f0
 +
# /root/scripts/set_irq_affinity 8-15 enp6s0f1
  
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.0 -p udp -j SNAT --to 192.0.0.0:1-2048
+
=== Mais alguns tunings ===
 +
Vamos fazer mais alguns ajustes nas interfaces com o '''ethtool'''. Dessa vez vamos aumentar os '''Rings RX''' e '''TX'''. Mas antes vamos listar os valores que podemos usar:
 +
# ethtool -g enp5s0f0
 +
Ring parameters for enp5s0f0:
 +
Pre-set maximums:
 +
RX:             4096
 +
RX Mini:        n/a
 +
RX Jumbo:       n/a
 +
TX:             4096
 +
Current hardware settings:
 +
RX:             512
 +
RX Mini:        n/a
 +
RX Jumbo:       n/a
 +
TX:             512
 +
Acima vemos que o '''valor máximo é de 4096 tanto para TX''', '''quanto para RX''' mas está '''configurado para 512 em RX e TX'''. Façamos então:
 +
# ethtool -G enp5s0f0 rx 4096 tx 4096
 +
# ethtool -G enp5s0f1 rx 4096 tx 4096
 +
# ethtool -G enp6s0f0 rx 4096 tx 4096
 +
# ethtool -G enp6s0f1 rx 4096 tx 4096
 +
Vamos desabilitar as seguintes options das interfaces: '''TSO''', '''GRO''' e '''GSO'''.  
 +
# ethtool -K enp5s0f0 tso off gro off gso off
 +
# ethtool -K enp5s0f1 tso off gro off gso off
 +
# ethtool -K enp6s0f0 tso off gro off gso off
 +
# ethtool -K enp6s0f1 tso off gro off gso off
 +
Aumentaremos o '''txqueuelen''' para '''10000''':
 +
# ip link set enp5s0f0 txqueuelen 10000
 +
# ip link set enp5s0f1 txqueuelen 10000
 +
# ip link set enp6s0f0 txqueuelen 10000
 +
# ip link set enp6s0f1 txqueuelen 10000
  
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 1:2048 -j DNAT --to 100.64.0.0
+
=== Salvando a configuração e criando o LACP ===
 +
Tudo que fizemos até o momento será perdido no próximo reboot do sistema, então faremos com que esses comandos sejam executados sempre que o sistema iniciar. Para isso vamos deixar o nosso arquivo '''/etc/network/interfaces''' configurado conforme nosso diagrama, usando '''LACP''' e executando nossos comandos anteriores.
  
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 1:2048 -j DNAT --to 100.64.0.0
+
Antes precisaremos instalar o pacote '''ifenslave''' para que o bonding funcione:
 +
# apt install ifenslave
 +
# modprobe bonding
 +
# echo "bonding" >> /etc/modules
 +
Abaixo o nosso '''/etc/network/interfaces''' já com todas as configurações que fizemos anteriormente e seguindo nosso diagrama de exemplo:
 +
# This file describes the network interfaces available on your system
 +
# and how to activate them. For more information, see interfaces(5).
 +
 +
source /etc/network/interfaces.d/*
 +
 +
# The loopback network interface
 +
auto lo
 +
iface lo inet loopback
 +
 
 +
auto bond0
 +
iface bond0 inet static
 +
       bond-slaves enp5s0f0 enp5s0f1
 +
       bond_mode 802.3ad
 +
       bond-ad_select bandwidth
 +
       bond_miimon 100
 +
       bond_downdelay 200
 +
       bond_updelay 200
 +
       bond-lacp-rate 1
 +
       bond-xmit-hash-policy layer2+3
 +
       address 10.0.10.172/24
 +
       gateway 10.0.10.1
 +
       pre-up /usr/sbin/ethtool -L enp5s0f0 combined 8
 +
       pre-up /usr/sbin/ethtool -L enp5s0f1 combined 8
 +
       pre-up /root/scripts/set_irq_affinity 0-7 enp5s0f0
 +
       pre-up /root/scripts/set_irq_affinity 0-7 enp5s0f1
 +
       pre-up /usr/sbin/ethtool -G enp5s0f0 rx 4096 tx 4096
 +
       pre-up /usr/sbin/ethtool -G enp5s0f1 rx 4096 tx 4096
 +
       pre-up /usr/sbin/ethtool -K enp5s0f0 tso off gro off gso off
 +
       pre-up /usr/sbin/ethtool -K enp5s0f1 tso off gro off gso off
 +
       pre-up /usr/sbin/ip link set enp5s0f0 txqueuelen 10000
 +
       pre-up /usr/sbin/ip link set enp5s0f1 txqueuelen 10000
 +
 +
auto bond1
 +
iface bond1 inet static
 +
        bond-slaves enp6s0f0 enp6s0f1
 +
        bond_mode 802.3ad
 +
        bond-ad_select bandwidth
 +
        bond_miimon 100
 +
        bond_downdelay 200
 +
        bond_updelay 200
 +
        bond-lacp-rate 1
 +
        bond-xmit-hash-policy layer2+3
 +
        address 192.168.0.1/24
 +
        pre-up /usr/sbin/ethtool -L enp6s0f0 combined 8
 +
        pre-up /usr/sbin/ethtool -L enp6s0f1 combined 8
 +
        pre-up /root/scripts/set_irq_affinity 8-15 enp6s0f0
 +
        pre-up /root/scripts/set_irq_affinity 8-15 enp6s0f1
 +
        pre-up /usr/sbin/ethtool -G enp6s0f0 rx 4096 tx 4096
 +
        pre-up /usr/sbin/ethtool -G enp6s0f1 rx 4096 tx 4096
 +
        pre-up /usr/sbin/ethtool -K enp6s0f0 tso off gro off gso off
 +
        pre-up /usr/sbin/ethtool -K enp6s0f1 tso off gro off gso off
 +
        pre-up /usr/sbin/ip link set enp6s0f0 txqueuelen 10000
 +
        pre-up /usr/sbin/ip link set enp6s0f1 txqueuelen 10000
  
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.1 -p tcp -j SNAT --to 192.0.0.0:2049-4096
+
=== Atualizando o Kernel ===
 +
Colocaremos o '''kernel do backports'''. Para isso deixe o seu '''/etc/apt/sources''' conforme abaixo e rode os comandos na sequência:
 +
deb <nowiki>http://security.debian.org/debian-security</nowiki> bullseye-security main contrib non-free
 +
deb <nowiki>http://deb.debian.org/debian</nowiki> bullseye main non-free contrib
 +
deb <nowiki>http://deb.debian.org/debian</nowiki> bullseye-updates main contrib non-free
 +
deb <nowiki>http://deb.debian.org/debian</nowiki> bullseye-backports main contrib non-free
  
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.1 -p udp -j SNAT --to 192.0.0.0:2049-4096
+
# apt update
 +
# apt install -t bullseye-backports linux-image-amd64
 +
# reboot
  
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 2049:4096 -j DNAT --to 100.64.0.1
+
=== Protegendo contra static loop e preparando o ambiente do CGNAT ===
 +
O '''static loop''' é algo que, definitivamente, pode derrubar toda a sua operação se não for devidamente tratado e pode ser facilmente explorado por pessoas mal intencionadas. A causa do problema é uma rota estática para um prefixo IP (seja IPv4 ou IPv6), que aponta para um next-hop e nesse destino não existe nenhuma informação sobre o prefixo IP na tabela de rotas local, obrigando o pacote a retornar para o seu gateway default e ficando nesse loop até que '''expire o TTL (Time To Live) do pacote'''. Isso ocorre muito nos casos em que temos concentradores PPPoE (BNG) e caixas CGNAT como esta que estaremos fazendo. Em '''[[Recomendações sobre Mitigação DDoS]]''' temos outras dicas de segurança sobre o assunto '''DDoS'''.
  
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 2049:4096 -j DNAT --to 100.64.0.1
+
Crie um arquivo /etc/rc.local e dentro colocaremos algumas coisas como as blackholes para cada prefixo IPv4 público que usaremos no nosso servidor de exemplo e rotas de retorno para o nosso BNG:
 +
# > /etc/rc.local
 +
# chmod +x /etc/rc.local
 +
Dentro teremos:
 +
#!/bin/sh -e
 +
/usr/sbin/ip route add blackhole 198.18.0.0/27 metric 254
 +
/usr/sbin/ip route add 100.64.0.0/22 via 192.168.0.2
 +
No exemplo acima estamos colocando em '''blackhole''' o nosso prefixo IPv4 público deste tutorial que é o '''198.18.0.0/27''' e adicionando uma rota de retorno do prefixo '''100.64.0.0/22''' usado no nosso '''BNG''' para o '''next-hop 192.168.0.2'''.
  
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.2 -p tcp -j SNAT --to 192.0.0.0:4097-6144
+
=== Redução dos tempos de timeouts e outros ajustes ===
 +
Os tempos padrões dos '''timeouts''' de '''tcp''' e '''udp''' são altos para o nosso sistema de CGNAT, ainda mais quando estamos '''diminuindo a quantidade de portas tcp/udp por assinante''' e com isso podemos rapidamente estourar esse limite, fazendo com que o sistema pare de funcionar. Abaixo estou colocando os valores que sempre usei e não percebi problemas, mas você pode ajustar conforme achar mais prudente. Adicionaremos as configurações abaixo também no nosso '''/etc/rc.local''':
 +
echo 5 > /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_syn_sent
 +
echo 5 > /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_syn_recv
 +
echo 86400 > /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_established
 +
echo 10 > /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_fin_wait
 +
echo 10 > /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_close_wait
 +
echo 10 > /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_last_ack
 +
echo 10 > /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_time_wait
 +
echo 10 > /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_close
 +
echo 300 > /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_max_retrans
 +
echo 300 > /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_unacknowledged
 +
echo 10 > /proc/sys/net/netfilter/nf_conntrack_udp_timeout
 +
echo 180 > /proc/sys/net/netfilter/nf_conntrack_udp_timeout_stream
 +
echo 10 > /proc/sys/net/netfilter/nf_conntrack_icmp_timeout
 +
echo 600 > /proc/sys/net/netfilter/nf_conntrack_generic_timeout
 +
Em '''/etc/sysctl.conf''' adicionaremos:
 +
net.core.default_qdisc=fq
 +
net.ipv4.tcp_congestion_control=bbr
 +
net.core.rmem_max = 2147483647
 +
net.core.wmem_max = 2147483647
 +
net.ipv4.tcp_rmem = 4096 87380 2147483647
 +
net.ipv4.tcp_wmem = 4096 65536 2147483647
 +
net.ipv4.conf.all.forwarding=1
 +
net.netfilter.nf_conntrack_helper=1
 +
net.netfilter.nf_conntrack_buckets = 512000
 +
net.netfilter.nf_conntrack_max = 4096000
 +
vm.swappiness=10
 +
As configurações acima melhoram o uso de memória, habilita o encaminhamento dos pacotes e aumenta a quantidade máxima de '''conntracks''' do sistema para '''4096000'''.
  
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.2 -p udp -j SNAT --to 192.0.0.0:4097-6144
+
'''Se o conntrack estourar, seu CGNAT terá problemas e causará indisponibilidades'''. Para consultar a quantidade de conntracks em uso:
 +
# cat /proc/sys/net/netfilter/nf_conntrack_count
 +
Para listar as '''conntracks''':
 +
# cat /proc/net/nf_conntrack
  
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 4097:6144 -j DNAT --to 100.64.0.2
+
=== Ajustando a data e horário do sistema ===
 +
Uma tarefa muito importante a ser feita nos servidores, é garantir que o horário e data estejam corretos e para isso usaremos o programa '''chrony'''. Eu prefiro usar sempre horário UTC nos servidores e fazer a conversão quando necessário:
 +
# apt install chrony
 +
Basta copiar e colar os comandos abaixo, para configurar o '''chrony''':
 +
# cat << EOF > /etc/chrony/chrony.conf
 +
confdir /etc/chrony/conf.d
 +
sourcedir /run/chrony-dhcp
 +
sourcedir /etc/chrony/sources.d
 +
keyfile /etc/chrony/chrony.keys
 +
driftfile /var/lib/chrony/chrony.drift
 +
ntsdumpdir /var/lib/chrony
 +
logdir /var/log/chrony
 +
maxupdateskew 100.0
 +
rtcsync
 +
makestep 1 3
 +
leapsectz right/UTC
 +
EOF
  
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 4097:6144 -j DNAT --to 100.64.0.2
+
# cat << EOF > /etc/chrony/sources.d/nic.sources
 +
server a.st1.ntp.br iburst nts
 +
server b.st1.ntp.br iburst nts
 +
server c.st1.ntp.br iburst nts
 +
server d.st1.ntp.br iburst nts
 +
EOF
 +
Aqui reiniciamos o serviço e configuramos o '''timezone''':
 +
# systemctl restart chronyd.service
 +
# timedatectl set-timezone "UTC"
  
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.3 -p tcp -j SNAT --to 192.0.0.0:6145-8192
+
=== Habilitando ALG (Application Layer Gateway) ===
 +
No arquivo '''/etc/modules''' adicionaremos os módulos que usaremos no nosso CGNAT, inclusive os ALGs'''.''' Sem eles alguns serviços, ainda muito utilizados, apresentarão problemas.
  
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.3 -p udp -j SNAT --to 192.0.0.0:6145-8192
+
Em '''/etc/modules''' adicionaremos mais os módulos abaixo:
 +
nf_conntrack
 +
nf_nat_pptp
 +
nf_nat_h323
 +
nf_nat_sip
 +
nf_nat_irc
 +
nf_nat_ftp
 +
nf_nat_tftp
  
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 6145:8192 -j DNAT --to 100.64.0.3
+
=== Preparando ambiente e gerador de regras de CGNAT ===
 +
Antes de começarmos nossas regras de CGNAT precisaremos de alguns pacotes:
 +
# apt install python3-pip nftables
 +
# pip install ipaddress
 +
Vamos precisar também de um gerador de regras de CGNAT para '''nftables'''. Porque criar as regras manualmente não é uma tarefa rápida e para isso usaremos um programa em python criado por '''José Beiriz''' e disponibilizado aqui: '''[https://github.com/Beiriz/GRCN GRCN]'''
  
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 6145:8192 -j DNAT --to 100.64.0.3
+
Caso não consigam baixar por algum motivo o '''GRCN''', aqui '''https://github.com/gondimcodes/GRCN''' também pode ser encontrado o script '''cgnat-nft.py.'''
  
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.4 -p tcp -j SNAT --to 192.0.0.0:8193-10240
+
Nosso sistema de regras CGNAT será dividido em 2 partes:
  
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.4 -p udp -j SNAT --to 192.0.0.0:8193-10240
+
* O script base que colocaremos em '''/root/scripts''' chamado de '''frw-nft.sh'''. Esse script conterá as regras básicas do CGNAT e este incluirá a chamada para os outros arquivos de regras propriamente ditos do CGNAT.  
  
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 8193:10240 -j DNAT --to 100.64.0.4
+
* Essa outra parte é composta pelos arquivos de regras de CGNAT, onde são feitas as traduções de IPs privados '''100.64.0.0/10 (Shared Address Space - RFC6598)''', para os '''IPs públicos'''. A seguir o '''frw-nft.sh''':
  
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 8193:10240 -j DNAT --to 100.64.0.4
+
Nosso script de CGNAT base '''/root/scripts/frw-nft.sh''':
 +
#!/usr/sbin/nft -f
 +
# limpa todas as regras da memoria
 +
flush ruleset
 +
 +
add table ip filter
 +
add ct helper ip filter pptp-vpn { type "pptp" protocol tcp; }
 +
add ct helper ip filter ftp-padrao { type "ftp" protocol tcp; }
 +
add ct helper ip filter sip-padrao { type "sip" protocol udp; }
 +
add chain ip filter PREROUTING { type filter hook prerouting priority filter; }
 +
add rule ip filter PREROUTING tcp dport 1723 ct helper set "pptp-vpn"
 +
add rule ip filter PREROUTING tcp dport 21 ct helper set "ftp-padrao"
 +
add rule ip filter PREROUTING ip protocol udp ct helper set "sip-padrao"
 +
 +
# regras base para o CGNAT
 +
add table ip nat
 +
 +
add chain ip nat POSTROUTING { type nat hook postrouting priority 100; policy accept; }
 +
 +
add chain ip nat CGNATOUT
 +
 +
# libera o proprio CGNAT para acessar a Internet - para atualizacoes por exemplo
 +
add rule ip nat POSTROUTING oifname "bond0" ip saddr 10.0.10.172 counter snat to 198.18.0.0  
 +
 +
# faz o jump para as regras de CGNAT
 +
add rule ip nat POSTROUTING oifname "bond0" counter jump CGNATOUT
 +
 +
# carrega os arquivos de regras de CGNAT
 +
'''include "/root/scripts/cgnat-0-31.conf"'''
 +
A última linha do script acima, em '''negrito''', é o arquivo de regras CGNAT que iremos gerar e será chamado pelo script quando for executado.
  
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.5 -p tcp -j SNAT --to 192.0.0.0:10241-12288
+
Após a criação do script, alteramos a permissão dele  para ficar como executável e adicionamos ele em nosso '''/etc/rc.local''':
 +
# chmod 700 /root/scripts/frw-nft.sh
 +
# echo "/root/scripts/frw-nft.sh" >> /etc/rc.local
  
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.5 -p udp -j SNAT --to 192.0.0.0:10241-12288
+
=== Gerando nossas regras de CGNAT ===
 +
Colocaremos o script '''cgnat-nft.py''' em '''/root/scripts/'''. Como estamos trabalhando '''no modelo determinístico de 1/32''', basta pegarmos nosso bloco privado '''100.64.0.0/22 (1024 IPs)''' e nosso bloco público '''198.18.0.0/27 (32 IPs)''' e executarmos em linha de comando:
 +
# cd /root/scripts
 +
# ./cgnat-nft.py 0 198.18.0.0/27 100.64.0.0/22 1/32
 +
Se digitar apenas '''./cgnat-nft.py''' será apresentado um help dos parâmetros mas é bem simples o seu uso. No comando acima '''temos o número 0 como índice'''. Muito cuidado com o índice, porque ele é muito importante para a performance e para cada novo arquivo gerado, esse índice precisará ser incrementado. O comando acima criará automaticamente o arquivo chamado '''cgnat-0-31.conf''', aquele mesmo visto no script base sendo carregado com o '''include'''. Onde esse '''0-31''' quer dizer que nesse arquivo '''os índices vão de 0 a 31'''. Se for gerar um novo arquivo com o comando acima, o próximo índice a ser usado seria o '''32'''. Por exemplo:
 +
# ./cgnat-nft.py '''32''' 198.18.0.32/27 100.64.4.0/22 1/32
 +
Esse comando acima criará novas regras no arquivo chamado '''cgnat-32-63.conf''', na sequência inclua esse novo arquivo dentro do '''/root/scripts/frw-nft.sh''' e '''execute o /root/scripts/frw-nft.sh novamente''' para carregar as novas regras. A seguir daremos uma olhada nas regras geradas nesses arquivos.
  
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 10241:12288 -j DNAT --to 100.64.0.5
+
=== Executando o gerador de regras ===
 +
# ./cgnat-nft.py 0 198.18.0.0/27 100.64.0.0/22 1/32
 +
[[Arquivo:1022px-Grcn.png|nenhum|miniaturadaimagem|1022x1022px]]
 +
Após teclar '''ENTER''' será gerado o arquivo '''cgnat-0-31.conf''' com as regras conforme a tela abaixo de exemplo:
 +
[[Arquivo:1027px-Regras01.png|nenhum|miniaturadaimagem|1027x1027px]]
 +
Na tela abaixo se observarmos o '''retângulo vermelho''' veremos a regra que faz o '''NAT de tudo que não for TCP ou UDP''' e por fim a regra que faz o '''jump''' '''de tudo que for origem 100.64.0.0/27''' para o '''CGNATOUT_0''' onde esse '''0 é o índice'''.
 +
[[Arquivo:1029px-Regras02.png|nenhum|miniaturadaimagem|1029x1029px]]
  
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 10241:12288 -j DNAT --to 100.64.0.5
+
=== Explicando a função dos índices ===
 +
O sistema de avaliação de regras de filtros de pacotes e NAT no GNU/Linux é do tipo '''First Match Win''', o que significa que a pesquisa das regras se encerra quando o sistema encontra uma regra que dê match. O sistema fica muito mais otimizado e performático quando quebramos as regras e separamos em '''CHAINS''' e é aí que entram os '''índices'''. Porque as '''CHAINS''' não podem ter o mesmo nome, senão não haveria separação das regras. A seguir veremos por exemplo que quando houver um pacote relacionado com o prefixo de origem '''100.64.0.0/27''', este será encaminhado para a chain '''CGNATOUT_0''', que é onde estão as regras de CGNAT para esse bloco IP. Desse jeito a checagem para esse prefixo não percorre todas as regras de NAT contidas na memória.
 +
[[Arquivo:Regras03.png|nenhum|miniaturadaimagem|1034x1034px]]
  
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.6 -p tcp -j SNAT --to 192.0.0.0:12289-14336
+
=== Novo script gerador de regras nftables com suporte a netmap ===
 +
Com a versão nova que virá do '''Debian''', a '''versão 12 (Bookworm)''', teremos também uma versão nova do '''nftables''' '''1.0.6''' e essa versão já suporta o equivalente ao '''netmap''' que temos no '''Mikrotik''' e com isso teremos menos regras na memória e provavelmente mais performance. O sistema novo conta também com o '''kernel 6.1.27''' que possui diversas '''melhorias na pilha tcp/ip'''. Para aqueles que já quiserem testar nesse novo ambiente, fiz uma modificação no script python mostrado anteriormente, para gerar regras nesse novo formato e um arquivo tabela com o relacionamento de portas e IPs para quebra de sigilo tecnológico. Aqui '''https://github.com/gondimcodes/GRCN''' o novo código e estarei solicitando ao '''José Beiriz''' para incorporá-lo no '''GRCN'''.
 +
Para gerar as regras é só executar da mesma maneira. Exemplo:<pre>
 +
# ./cgnat-nft-netmap.py 0 198.18.0.0/27 100.64.0.0/22 1/32
 +
</pre>Abaixo exemplos de como ficam as novas regras e na memória:
 +
[[Arquivo:Nftables netmap1.png|nenhum|commoldura|926x926px]]
 +
[[Arquivo:Nftables netmap2.png|nenhum|commoldura|929x929px]]
 +
Exemplo do arquivo '''tabela-0-31.txt''' que foi gerado:
 +
[[Arquivo:Nftables netmap3.png|nenhum|commoldura|963x963px]]
  
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.6 -p udp -j SNAT --to 192.0.0.0:12289-14336
+
=== Simulando um acesso do cliente e observando os resultados ===
 +
Para testar as regras, foi criado um ambiente virtual de laboratório usando um '''Proxmox''' e criando 3 VMs: '''CGNAT''', '''BNG''' e '''CLIENTE'''. Do router de testes capturei os pacotes para demonstrar como funciona o CGNAT. A seguir teremos o acesso por parte do cliente e a captura dos pacotes somente para uma '''POC (Proof of Concept)''', para demonstrar que o CGNAT está funcionando e alocando a porta, dentro do range de portas, corretamente para um determinado cliente.
  
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 12289:14336 -j DNAT --to 100.64.0.6
+
Abaixo temos um exemplo de captura bem simples de pacote mostrando que o '''IP 198.18.0.0''' com '''porta origem''' '''6767/TCP''' acessou o '''200.147.41.220 na porta 443/TCP''', um acesso para o site do '''UOL'''.
 +
[[Arquivo:Cgnat sniffer.png|nenhum|miniaturadaimagem|1039x1039px]]
 +
Se olharmos os dados marcados acima e procurarmos pelo IP '''198.18.0.0 e porta 6767''' no nosso arquivo de configuração do CGNAT, '''acharemos o IP 100.64.0.2''' '''que utiliza o range de portas entre 5056 e 7071'''. Abaixo o nosso arquivo de regras de CGNAT para comprovar o range de portas utilizados.
 +
[[Arquivo:Regras5.png|nenhum|miniaturadaimagem|1041x1041px]]
  
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 12289:14336 -j DNAT --to 100.64.0.6
+
=== Monitorando o tráfego em tempo real ===
 +
Monitorando o '''tráfego Mbps/PPS''' com a ferramenta '''bmon'''. Para instalar o software no Debian basta fazer:
 +
# apt install bmon
 +
Para monitorar as interfaces faríamos algo assim onde '''-b''' para '''bits/s''' e o '''-p''' para '''selecionar as interfaces que quer monitorar'''. Para monitorar nosso '''bond0''' e '''bond1''' o comando seria esse abaixo:
 +
# bmon -b -p bond0,bond1
 +
Abaixo uma tela de exemplo do '''bmon''' em execução:
 +
[[Arquivo:Bmon cgnat.png|nenhum|miniaturadaimagem|1040x1040px]]
  
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.7 -p tcp -j SNAT --to 192.0.0.0:14337-16384
+
== CGNAT no Mikrotik RouterOS ==
 +
Uma boa opção para caixa CGNAT com custo x benefício acessível seria uma '''CCR1036-8G-2S+''' onde se for configurada  somente para fazer '''CGNAT''', com o '''mínimo de regras de filtro''' e '''Fasttrack habilitado''', já alcancei '''13 Gbps de tráfego ou 26 Gbps agregado''' fazendo um '''bonding com as 2 interfaces ópticas de 10Gbps'''.
  
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.7 -p udp -j SNAT --to 192.0.0.0:14337-16384
+
Essa imagem abaixo foi retirada do '''datasheet da CCR1036-8G-2S+''':
 +
[[Arquivo:Datasheet ccr1036.png|nenhum|miniaturadaimagem|1041x1041px]]
  
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 14337:16384 -j DNAT --to 100.64.0.7
+
=== Configurando o sistema ===
 +
Instale um '''Mikrotik RouterOS do zero''', procure utilizar a '''versão mais estável possível'''. Como não utilizei ainda em produção o RouterOS 7.x, '''sugiro utilizar a versão 6.48.6 Long-term''', que até o momento, é a versão considerada mais estável. O processo de configurar um '''CGNAT Determinístico no Mikrotik RouterOS''' será bem mais simples que no '''Debian GNU/Linux''' mas a capacidade alcançada com o '''GNU/Linux''' será bem superior ao visto aqui.
  
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 14337:16384 -j DNAT --to 100.64.0.7
+
=== Sobre Fasttrack ===
 +
O '''Fasttrack''' é um recurso muito importante que aumentará a performance da sua caixa CGNAT, '''acelerando o encaminhamento de pacotes''' e '''diminuindo o consumo de CPU'''. Neste momento não faremos isso. Quando chegarmos no processo de criação das regras de CGNAT, ele será habilitado e será mostrado quais as regras que fazem isso.
  
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.8 -p tcp -j SNAT --to 192.0.0.0:16385-18432
+
=== Configurando o bonding ===
 +
Como usaremos as '''duas portas de 10GbE sfp+ da CCR''', utilizaremos vlans para separar a rede que se comunicará com a Internet, da rede com o BNG. A seguir veremos como deixar o nosso bonding. Na sequência configuramos nossas vlans de entrada e saída e em cima delas '''os IPs do diagrama''', como fizemos com o Debian. Vamos definir a '''vlan 101''' '''para a interface que fará a comunicação com a Internet''' e '''por onde será feito o CGNAT''' e a '''vlan 102 que fará a comunicação com o BNG'''.
 +
[[Arquivo:Cgnat mk1.png|nenhum|miniaturadaimagem|1043x1043px]]
 +
[[Arquivo:Cgnat mk2.png|nenhum|miniaturadaimagem|1237x1237px]]
  
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.8 -p udp -j SNAT --to 192.0.0.0:16385-18432
+
=== Configurando os IPs e rotas ===
 +
O objetivo deste artigo é ser bem simples para entendermos os conceitos e por isso estamos utilizando '''rotas estáticas''' e não estamos envolvendo outros protocolos como o '''OSPF'''. Nada impediria de utilizar a mesma técnica apresentada aqui em um cenário com '''OSPF''', por exemplo.
  
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 16385:18432 -j DNAT --to 100.64.0.8
+
A seguir veremos que na '''vlan-101-borda''' configuramos o '''IP 10.0.10.172/24''' e na '''vlan-102-bng''' configuramos o '''IP 192.168.0.1/24'''.
  
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 16385:18432 -j DNAT --to 100.64.0.8
+
Como rotas criamos uma '''default route''' apontando para o '''IP 10.0.10.1''', criamos uma rota para '''100.64.0.0/22''' com '''next-hop 192.168.0.2''' e para nos '''protegermos de static loop''' teremos nossas rotas de '''blackhole''' quando formos gerar as regras de CGNAT.
  
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.9 -p tcp -j SNAT --to 192.0.0.0:18433-20480
+
Na imagem aparece como '''unreachable''' porque esse equipamento, que está sendo usado como lab, não está conectado em uma switch.
 +
[[Arquivo:Cgnat mk3.png|nenhum|miniaturadaimagem|1040x1040px]]
  
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.9 -p udp -j SNAT --to 192.0.0.0:18433-20480
+
=== Recomendações de segurança ===
  
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 18433:20480 -j DNAT --to 100.64.0.9
+
* Utilize credenciais de acesso '''com senhas fortes''', não esqueça o login admin sem senha (padrão no Mikrotik RouterOS).
  
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 18433:20480 -j DNAT --to 100.64.0.9
+
* Desabilite todos os serviços que não for utilizar e os que ficarem abertos, especifique neles o acesso apenas da sua rede de gerência. Não deixe qualquer serviço aberto para a Internet.
  
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.10 -p tcp -j SNAT --to 192.0.0.0:20481-22528
+
* Habilite o '''TCP SynCookies'''.
  
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.10 -p udp -j SNAT --to 192.0.0.0:20481-22528
+
Procure criar suas regras de filtros de pacotes sempre na '''Table Raw''', ela não agride tanto a performance do equipamento mas '''necessita de muita atenção''' porque ela pode afetar os acessos dos assinantes. Isso porque uma regra genérica demais será analisada tanto com destino a caixa, quanto destino ao cliente e o mesmo pode ocorrer no sentido inverso, do cliente para a Internet.
 +
[[Arquivo:Cgnat mk4.png|nenhum|miniaturadaimagem|1041x1041px]]
  
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 20481:22528 -j DNAT --to 100.64.0.10
+
=== Acertando data e hora ===
 +
Configure o '''NTP client da caixa''' e mantenha a data e horário sincronizados.
 +
[[Arquivo:Cgnat mk5.png|nenhum|miniaturadaimagem|1042x1042px]]
  
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 20481:22528 -j DNAT --to 100.64.0.10
+
=== Criando as regras de CGNAT ===
 +
Para simplificar nossa vida, '''Rudimar Remontti''' criou em seu blog, um sistema para gerar regras de '''CGNAT Determinístico''' de forma simples e performática, '''utilizando regras netmap da Mikrotik'''. Para tanto o link é este:
  
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.11 -p tcp -j SNAT --to 192.0.0.0:22529-24576
+
'''https://cgnat.remontti.com.br/'''
  
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.11 -p udp -j SNAT --to 192.0.0.0:22529-24576
+
O sistema é bem completo, simples, irá gerar as '''regras de CGNAT''' e nossas '''blackholes''' para '''bloqueio de static loop'''. Também no final teremos uma '''tabela de associação''' que devemos guardar para fazer as quebras de sigilo solicitadas nos Ofícios Judiciais.
  
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 22529:24576 -j DNAT --to 100.64.0.11
+
Ao acessar o site e seguindo o nosso diagrama completaremos as informações conforme mostrado a seguir.
 +
[[Arquivo:Cgnat remontti1.png|nenhum|miniaturadaimagem|1047x1047px]]
 +
O site irá gerar automaticamente os comandos de onde faremos uma cópia e executaremos no nosso equipamento '''Mikrotik RouterOS'''.
 +
[[Arquivo:Cgnat remontti2.png|nenhum|miniaturadaimagem|1049x1049px]]
 +
No final da página é gerado uma tabela do mapeamento das portas, isso deve ser salvo como documento importante pois será usado para quebra de sigilo tecnológico.
 +
[[Arquivo:Cgnat remontti3.png|nenhum|miniaturadaimagem|1052x1052px]]
 +
O conceito é o mesmo, quebrar as regras em blocos menores para chegarmos no nosso '''First Match Win mais rápido''' e não termos que percorrer todas as regras em memória.
 +
[[Arquivo:Cgnat remontti4.png|nenhum|miniaturadaimagem|1053x1053px]]
 +
Abaixo como ficaram as regras que habilita o '''Fasttrack''' no nosso equipamento, aumentando em muito a performance de encaminhamento dos pacotes.
 +
[[Arquivo:Cgnat mk6.png|nenhum|miniaturadaimagem|1056x1056px]]
  
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 22529:24576 -j DNAT --to 100.64.0.11
+
== Conclusão ==
 +
Essa documentação foi útil? Compartilhe, divulgue e ajude outras pessoas.
  
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.12 -p tcp -j SNAT --to 192.0.0.0:24577-26624
+
Autor: [[Usuário:Gondim|Marcelo Gondim]]
 
+
[[Categoria:Infraestrutura]]
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.12 -p udp -j SNAT --to 192.0.0.0:24577-26624
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 24577:26624 -j DNAT --to 100.64.0.12
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 24577:26624 -j DNAT --to 100.64.0.12
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.13 -p tcp -j SNAT --to 192.0.0.0:26625-28672
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.13 -p udp -j SNAT --to 192.0.0.0:26625-28672
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 26625:28672 -j DNAT --to 100.64.0.13
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 26625:28672 -j DNAT --to 100.64.0.13
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.14 -p tcp -j SNAT --to 192.0.0.0:28673-30720
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.14 -p udp -j SNAT --to 192.0.0.0:28673-30720
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 28673:30720 -j DNAT --to 100.64.0.14
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 28673:30720 -j DNAT --to 100.64.0.14
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.15 -p tcp -j SNAT --to 192.0.0.0:30721-32768
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.15 -p udp -j SNAT --to 192.0.0.0:30721-32768
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 30721:32768 -j DNAT --to 100.64.0.15
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 30721:32768 -j DNAT --to 100.64.0.15
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.16 -p tcp -j SNAT --to 192.0.0.0:32769-34816
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.16 -p udp -j SNAT --to 192.0.0.0:32769-34816
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 32769:34816 -j DNAT --to 100.64.0.16
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 32769:34816 -j DNAT --to 100.64.0.16
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.17 -p tcp -j SNAT --to 192.0.0.0:34817-36864
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.17 -p udp -j SNAT --to 192.0.0.0:34817-36864
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 34817:36864 -j DNAT --to 100.64.0.17
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 34817:36864 -j DNAT --to 100.64.0.17
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.18 -p tcp -j SNAT --to 192.0.0.0:36865-38912
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.18 -p udp -j SNAT --to 192.0.0.0:36865-38912
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 36865:38912 -j DNAT --to 100.64.0.18
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 36865:38912 -j DNAT --to 100.64.0.18
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.19 -p tcp -j SNAT --to 192.0.0.0:38913-40960
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.19 -p udp -j SNAT --to 192.0.0.0:38913-40960
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 38913:40960 -j DNAT --to 100.64.0.19
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 38913:40960 -j DNAT --to 100.64.0.19
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.20 -p tcp -j SNAT --to 192.0.0.0:40961-43008
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.20 -p udp -j SNAT --to 192.0.0.0:40961-43008
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 40961:43008 -j DNAT --to 100.64.0.20
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 40961:43008 -j DNAT --to 100.64.0.20
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.21 -p tcp -j SNAT --to 192.0.0.0:43009-45056
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.21 -p udp -j SNAT --to 192.0.0.0:43009-45056
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 43009:45056 -j DNAT --to 100.64.0.21
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 43009:45056 -j DNAT --to 100.64.0.21
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.22 -p tcp -j SNAT --to 192.0.0.0:45057-47104
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.22 -p udp -j SNAT --to 192.0.0.0:45057-47104
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 45057:47104 -j DNAT --to 100.64.0.22
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 45057:47104 -j DNAT --to 100.64.0.22
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.23 -p tcp -j SNAT --to 192.0.0.0:47105-49152
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.23 -p udp -j SNAT --to 192.0.0.0:47105-49152
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 47105:49152 -j DNAT --to 100.64.0.23
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 47105:49152 -j DNAT --to 100.64.0.23
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.24 -p tcp -j SNAT --to 192.0.0.0:49153-51200
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.24 -p udp -j SNAT --to 192.0.0.0:49153-51200
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 49153:51200 -j DNAT --to 100.64.0.24
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 49153:51200 -j DNAT --to 100.64.0.24
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.25 -p tcp -j SNAT --to 192.0.0.0:51201-53248
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.25 -p udp -j SNAT --to 192.0.0.0:51201-53248
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 51201:53248 -j DNAT --to 100.64.0.25
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 51201:53248 -j DNAT --to 100.64.0.25
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.26 -p tcp -j SNAT --to 192.0.0.0:53249-55296
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.26 -p udp -j SNAT --to 192.0.0.0:53249-55296
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 53249:55296 -j DNAT --to 100.64.0.26
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 53249:55296 -j DNAT --to 100.64.0.26
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.27 -p tcp -j SNAT --to 192.0.0.0:55297-57344
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.27 -p udp -j SNAT --to 192.0.0.0:55297-57344
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 55297:57344 -j DNAT --to 100.64.0.27
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 55297:57344 -j DNAT --to 100.64.0.27
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.28 -p tcp -j SNAT --to 192.0.0.0:57345-59392
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.28 -p udp -j SNAT --to 192.0.0.0:57345-59392
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 57345:59392 -j DNAT --to 100.64.0.28
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 57345:59392 -j DNAT --to 100.64.0.28
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.29 -p tcp -j SNAT --to 192.0.0.0:59393-61440
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.29 -p udp -j SNAT --to 192.0.0.0:59393-61440
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 59393:61440 -j DNAT --to 100.64.0.29
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 59393:61440 -j DNAT --to 100.64.0.29
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.30 -p tcp -j SNAT --to 192.0.0.0:61441-63488
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.30 -p udp -j SNAT --to 192.0.0.0:61441-63488
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 61441:63488 -j DNAT --to 100.64.0.30
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 61441:63488 -j DNAT --to 100.64.0.30
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.31 -p tcp -j SNAT --to 192.0.0.0:63489-65535
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.31 -p udp -j SNAT --to 192.0.0.0:63489-65535
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 63489:65535 -j DNAT --to 100.64.0.31
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 63489:65535 -j DNAT --to 100.64.0.31
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -j SNAT --to 192.0.0.0
 
 
 
'''/sbin/iptables -t nat -A CGNATOUT -s 100.64.0.0/27 -j CGNATOUT_0'''
 
 
 
'''/sbin/iptables -t nat -A CGNATIN -d 192.0.0.0/32 -j CGNATIN_0'''
 
 
 
Essas duas regras em negrito acima, fazem o redirecionamento do tráfego para a chain correta, dessa forma evita que um pacote percorra todas as regras buscando um match. Essa é mágica que deixa o sistema bem leve.
 
 
 
Bem espero que esse artigo seja útil e ajude ao máximo. O objetivo era mostrar, dar uma ideia e agora fica com vocês a tarefa de desenvolver o script que vai facilitar a criação de todas as regras. Porque o que foi mostrado é apenas um /27 saindo com um IP público e todos sabemos que no mundo real isso é um grão de areia. Fica o restante como dever de casa mas posso dizer que esse ambiente existe e funciona muito bem pro nosso AS.
 
 
 
Qualquer dúvida sobre o artigo só me procurar:
 
 
 
'''Marcelo Gondim <gondim@linuxinfo.com.br>'''
 
 
 
'''Telegram: @Marcelo_Gondim'''
 
 
 
*
 

Edição atual tal como às 23h55min de 10 de dezembro de 2023

773px-Cgnat nic br.jpg

Objetivo

Com o esgotamento do IPv4 mundialmente, precisamos tomar algumas providências para que a Internet não pare. As que vejo de imediatas são: IPv6 e CGNAT (Carrier Grade NAT). O IPv6 é a real solução para os problemas de esgotamento e o CGNAT seria a "gambiarra" necessária para continuar com o IPv4 até que a Internet esteja 100% em IPv6. Nesse artigo será explicado como montar uma caixa CGNAT Determinística usando GNU/Linux e Mikrotik RouterOS. Esse artigo foi baseado no treinamento da Semana de Capacitação do NIC.br e que pode ser encontrado com o título CONCEITOS E IMPLEMENTAÇÃO DE CGNAT aqui como palestra e material de apoio e o vídeo do treinamento no Youtube aqui.

Diagrama

776px-Cgnat diagrama2.png

No BNG é configurado uma PBR (Policy Based Routing) onde apenas IPs do bloco 100.64.0.0/22 serão roteados diretamente para a caixa CGNAT. Qualquer IPv4 público ou IPv6, serão roteados diretamente para a Borda. Isso evita processamento e tráfego desnecessário na caixa CGNAT.

No diagrama ao lado a linha amarela simboliza o tráfego do bloco 100.64.0.0/22 indo para o CGNAT. A linha vermelha seria o tráfego já traduzido para um IP da rede 198.18.0.0/27 e encaminhado para a Borda. A linha verde é o tráfego mais limpo, sem "gambiarras" e o real objetivo que devemos seguir para uma Internet melhor usando IPv6.

A Borda é um equipamento onde podemos inserir algumas regras de filtros de pacotes stateless para filtrar alguns pacotes indesejados como por exemplo: determinados spoofings e BOGONs. Também onde serão feitas ACLs para filtros BGP. Ação 1 e 2 do MANRS.

O Cliente nesse diagrama aparece conectado com o IPv4 de CGNAT 100.64.0.2 e IPv6 2001:0db8:f18:0:a941:6164:1a79:c0f3. Todo o acesso IPv4 desse cliente e nesse exemplo, para a Internet, sairá com o IP 198.18.0.0 usando as portas entre 5056 e 7071, conforme mostraremos no script gerador de regras de CGNAT.














CGNAT no GNU/Linux

Hardware e Sistema que utilizaremos no GNU/Linux

  • 2x Intel® Xeon® Silver 4215R Processor (3.20 GHz, 11M Cache, 8 núcleos/16 threads). Ambiente NUMA (non-uniform memory access).
  • 32Gb de ram.
  • 2x SSD 240 Gb RAID1.
  • 2x Interfaces de rede Intel XL710-QDA2 (2 portas de 40 Gbps).
  • GNU/Linux Debian 11 (Bullseye).

Vamos configurar um LACP com as duas portas de cada interface, para que possamos ter um backup, caso algum módulo apresente algum problema. Seu ambiente de produção pode ser diferente e por isso precisamos ter alguns cuidados na hora de montarmos o conjunto de hardware e não obtermos surpresas.

1º Verifique algumas especificações da interface de rede que será usada. Por exemplo a Intel XL710-QDA2:

  • 2 portas de 40 Gbps.
  • PCIe 3.0 x8 (8.0 GT/s).

Com essa informação seu equipamento não poderá possuir slots PCIe inferiores a esta especificação, caso contrário terá problemas de desempenho.

Você também precisa estar atento para as limitações de barramento por versão x lane (x1):

  • PCIe 1.0/1.1 - 2.5 GT/s - (8b/10b encoding) - 2 Gbps.
  • PCIe 2.0/2.1 - 5.0 GT/s - (8b/10b encoding)  - 4 Gbps.
  • PCIe 3.0/3.1 - 8.0 GT/s - (128b/130b encoding) - ~7,88 Gbps.
  • PCIe 4.0 - 16 GT/s - (128b/130b encoding) - ~15,76 Gbps.

Calculando a capacidade

Se observarmos a XL710-QDA2 é PCIe 3.0 x8 (8 lanes) ou seja o barramento irá suportar:

  • 8.0 GT/s * (128b/130b encoding) * 8 lanes = 63,01 Gbps

O objetivo do LACP nesse caso, não seria alcançar os 80 Gbps de capacidade em cada interface, mesmo porque cada barramento das interfaces é limitado em 63,01 Gbps, mas manteremos um backup dos 40 Gbps.

Nessa configuração teríamos teoricamente 63,01 Gbps de entrada e 63,01 Gbps de saída. Mas para esse cenário precisaremos fazer uma coisa chamada CPU Affinity. Nesse caso colocaríamos um processador dedicado para cada interface de rede. É um cenário mais complexo do que com 1 processador apenas, inclusive necessitamos de olhar o datasheet da motherboard e identificar quais slots PCIe são diretamente controlados por qual CPU. Se temos a CPU0 e CPU1, uma interface precisará ficar no slot controlado pela CPU0 e a outra interface no slot controlado pela CPU1 e observar a quantidade de lanes no slot para ver se suporta a mesma quantidade de lanes da interface de rede.

Falando um pouco sobre PPS (Packet Per Second) para calcular por exemplo 1 Gbps de tráfego na ethernet, a quantidade de PPS que o sistema precisaria suportar encaminhar teríamos: 1.000.000.000/8/1518 = 82.345 packets per second.

Existe um comando no GNU/Linux para você saber se o seu equipamento com processadores físicos, conseguirá trabalhar com o CPU Affinity:

# cat /sys/class/net/<interface>/device/numa_node

Se o resultado do comando acima for -1 então esse equipamento não trabalhará com o CPU Affinity. Isso porque cada interface precisa estar sendo gerenciada por um node específico. Se são 2 processadores então o resultado deveria ser 0 de CPU0 ou 1 de CPU1.

A seguir veremos um exemplo de datasheet da motherboard S2600WF:

903px-S2600wf.png

Se observarmos o datasheet acima veremos que temos o PCIe Riser #1, Riser #2 e Riser #3. Cada Riser possui slots PCIe que são gerenciados por determinada CPU. Se colocássemos as duas interfaces de rede nos slots do Riser #2 e Riser #3, estaríamos pendurando tudo apenas no processador 2. Isso foi apenas para mostrar a complexidade de quando usamos um equipamento NUMA e estamos somente escolhendo o hardware adequado. Ainda não chegamos na configuração do CPU Affinity.

Para sabermos quais cores estão relacionados para uma determinada CPU, utilizamos os comandos abaixo:

# cat /sys/devices/system/node/node0/cpulist
0-7

# cat /sys/devices/system/node/node1/cpulist
8-15

No exemplo acima a CPU0 tem os cores de 0 a 7 e a CPU1, os cores de 8 a 15, ou seja, é um equipamento com 16 cores.

Tuning antes do CPU Affinity

Também é importante, para aumento de performance, que seja desabilitado na BIOS o HT (Hyper Threading).

Antes de configurarmos algumas coisas no nosso ambiente, precisaremos instalar uma ferramenta importante para o nosso tuning; vamos instalar o pacote ethtool. Ele servirá para fazermos alguns ajustes nas nossas interfaces de rede. Alguns fabricantes podem não permitir certas alterações mas com as interfaces da Intel sempre obtive os resultados esperados.

# apt install ethtool

No nosso exemplo acima vimos que o equipamento possui 16 cores sendo que 8 cores por CPU. Então, para esse caso,  faremos um ajuste nas interfaces para ficarem preparadas para receberem 8 cores em cada através das IRQs. Usamos o parâmetro -l do ethtool para listar o Pre-set maximums combined da interface e o parâmetro -L para alterar esse valor. Façamos então a alteração:

# ethtool -L enp5s0f0 combined 8
# ethtool -L enp5s0f1 combined 8
# ethtool -L enp6s0f0 combined 8
# ethtool -L enp6s0f1 combined 8

Com os comandos acima deixamos preparadas as interfaces para aceitarem 8 cores em cada uma através das IRQs.

Não podemos usar o programa irqbalance para o CPU Affinity, pois este faz migração de contextos entre os cores e isso é ruim. Como no nosso exemplo estamos usando uma interface Intel, utilizaremos um script da própria Intel para realizar o CPU Affinity de forma mais fácil. Esse script se chama set_irq_affinity e vem acompanhado com os fontes do driver da interface. Ex.: Intel Network Adapter

Código do script set_irq_affinity

#!/bin/bash
# SPDX-License-Identifier: BSD-3-Clause
# Copyright (c) 2015 - 2019, Intel Corporation
#
# Affinitize interrupts to cores
#
# typical usage is (as root):
# set_irq_affinity -x local eth1 <eth2> <eth3>
# set_irq_affinity -s eth1
#
# to get help:
# set_irq_affinity

usage()
{
        echo
        echo "Usage: option -s <interface> to show current settings only"
        echo "Usage: $0 [-x|-X] [all|local|remote [<node>]|one <core>|custom|<cores>] <interface> ..."
        echo "  Options: "
        echo "    -s            Shows current affinity settings"
        echo "    -x            Configure XPS as well as smp_affinity"
        echo "    -X            Disable XPS but set smp_affinity"
        echo "    [all] is the default value"
        echo "    [remote [<node>]] can be followed by a specific node number"
        echo "  Examples:"
        echo "    $0 -s eth1            # Show settings on eth1"

        echo "    $0 all eth1 eth2      # eth1 and eth2 to all cores"
        echo "    $0 one 2 eth1         # eth1 to core 2 only"
        echo "    $0 local eth1         # eth1 to local cores only"
        echo "    $0 remote eth1        # eth1 to remote cores only"
        echo "    $0 custom eth1        # prompt for eth1 interface"
        echo "    $0 0-7,16-23 eth0     # eth1 to cores 0-7 and 16-23"
        echo
        exit 1
}

usageX()
{
        echo "options -x and -X cannot both be specified, pick one"
        exit 1
}

if [ "$1" == "-x" ]; then
        XPS_ENA=1
        shift
fi

if [ "$1" == "-s" ]; then
        SHOW=1
        echo Show affinity settings
        shift
fi

if [ "$1" == "-X" ]; then
        if [ -n "$XPS_ENA" ]; then
                usageX
        fi
        XPS_DIS=2
        shift
fi

if [ "$1" == -x ]; then
        usageX
fi

if [ -n "$XPS_ENA" ] && [ -n "$XPS_DIS" ]; then
        usageX
fi

if [ -z "$XPS_ENA" ]; then
        XPS_ENA=$XPS_DIS
fi

SED=`which sed`
if [[ ! -x $SED ]]; then
        echo " $0: ERROR: sed not found in path, this script requires sed"
        exit 1
fi

num='^[0-9]+$'

# search helpers
NOZEROCOMMA="s/^[0,]*//"
# Vars
AFF=$1
shift

case "$AFF" in
    remote)     [[ $1 =~ $num ]] && rnode=$1 && shift ;;
    one)        [[ $1 =~ $num ]] && cnt=$1 && shift ;;
    all)        ;;
    local)      ;;
    custom)     ;;
    [0-9]*)     ;;
    -h|--help)  usage ;;
    "")         usage ;;
    *)          IFACES=$AFF && AFF=all ;;       # Backwards compat mode
esac

# append the interfaces listed to the string with spaces
while [ "$#" -ne "0" ] ; do
        IFACES+=" $1"
        shift
done

# for now the user must specify interfaces
if [ -z "$IFACES" ]; then
        usage
        exit 2
fi

notfound()
{
        echo $MYIFACE: not found
        exit 15
}

# check the interfaces exist
for MYIFACE in $IFACES; do
        grep -q $MYIFACE /proc/net/dev || notfound
done

# support functions

build_mask()
{
        VEC=$core
        if [ $VEC -ge 32 ]
        then
                MASK_FILL=""
                MASK_ZERO="00000000"
                let "IDX = $VEC / 32"
                for ((i=1; i<=$IDX;i++))
                do
                        MASK_FILL="${MASK_FILL},${MASK_ZERO}"
                done

                let "VEC -= 32 * $IDX"
                MASK_TMP=$((1<<$VEC))
                MASK=$(printf "%X%s" $MASK_TMP $MASK_FILL)
        else
                MASK_TMP=$((1<<$VEC))
                MASK=$(printf "%X" $MASK_TMP)
        fi
}

show_affinity()
{
        # returns the MASK variable
        build_mask

        SMP_I=`sed -E "${NOZEROCOMMA}" /proc/irq/$IRQ/smp_affinity`
        HINT=`sed -E "${NOZEROCOMMA}" /proc/irq/$IRQ/affinity_hint`
        printf "ACTUAL  %s %d %s <- /proc/irq/$IRQ/smp_affinity\n" $IFACE $core $SMP_I
        printf "HINT    %s %d %s <- /proc/irq/$IRQ/affinity_hint\n" $IFACE $core $HINT
        IRQ_CHECK=`grep '[-,]' /proc/irq/$IRQ/smp_affinity_list`
        if [ ! -z $IRQ_CHECK ]; then
                printf " WARNING -- SMP_AFFINITY is assigned to multiple cores $IRQ_CHECK\n"
        fi
        if [ "$SMP_I" != "$HINT" ]; then
                printf " WARNING -- SMP_AFFINITY VALUE does not match AFFINITY_HINT \n"
        fi
        printf "NODE    %s %d %s <- /proc/irq/$IRQ/node\n" $IFACE $core `cat /proc/irq/$IRQ/node`
        printf "LIST    %s %d [%s] <- /proc/irq/$IRQ/smp_affinity_list\n" $IFACE $core `cat /proc/irq/$IRQ/smp_affinity_list`
        printf "XPS     %s %d %s <- /sys/class/net/%s/queues/tx-%d/xps_cpus\n" $IFACE $core `cat /sys/class/net/$IFACE/queues/tx-$((n-1))/xps_cpus` $IFACE $((n-1))
        if [ -z `ls /sys/class/net/$IFACE/queues/tx-$((n-1))/xps_rxqs` ]; then
                echo "WARNING: xps rxqs not supported on $IFACE"
        else
                printf "XPSRXQs %s %d %s <- /sys/class/net/%s/queues/tx-%d/xps_rxqs\n" $IFACE $core `cat /sys/class/net/$IFACE/queues/tx-$((n-1))/xps_rxqs` $IFACE $((n-1))
        fi
        printf "TX_MAX  %s %d %s <- /sys/class/net/%s/queues/tx-%d/tx_maxrate\n" $IFACE $core `cat /sys/class/net/$IFACE/queues/tx-$((n-1))/tx_maxrate` $IFACE $((n-1))
        printf "BQLIMIT %s %d %s <- /sys/class/net/%s/queues/tx-%d/byte_queue_limits/limit\n" $IFACE $core `cat /sys/class/net/$IFACE/queues/tx-$((n-1))/byte_queue_limits/limit` $IFACE $((n-1))
        printf "BQL_MAX %s %d %s <- /sys/class/net/%s/queues/tx-%d/byte_queue_limits/limit_max\n" $IFACE $core `cat /sys/class/net/$IFACE/queues/tx-$((n-1))/byte_queue_limits/limit_max` $IFACE $((n-1))
        printf "BQL_MIN %s %d %s <- /sys/class/net/%s/queues/tx-%d/byte_queue_limits/limit_min\n" $IFACE $core `cat /sys/class/net/$IFACE/queues/tx-$((n-1))/byte_queue_limits/limit_min` $IFACE $((n-1))
        if [ -z `ls /sys/class/net/$IFACE/queues/rx-$((n-1))/rps_flow_cnt` ]; then
                echo "WARNING: aRFS is not supported on $IFACE"
        else
                printf "RPSFCNT %s %d %s <- /sys/class/net/%s/queues/rx-%d/rps_flow_cnt\n" $IFACE $core `cat /sys/class/net/$IFACE/queues/rx-$((n-1))/rps_flow_cnt` $IFACE $((n-1))
        fi
        if [ -z `ls /sys/class/net/$IFACE/queues/rx-$((n-1))/rps_cpus` ]; then
                echo "WARNING: rps_cpus is not available on $IFACE"
        else
                printf "RPSCPU  %s %d %s <- /sys/class/net/%s/queues/rx-%d/rps_cpus\n" $IFACE $core `cat /sys/class/net/$IFACE/queues/rx-$((n-1))/rps_cpus` $IFACE $((n-1))
        fi
        echo
}

set_affinity()
{
        # returns the MASK variable
        build_mask

        printf "%s" $MASK > /proc/irq/$IRQ/smp_affinity
        printf "%s %d %s -> /proc/irq/$IRQ/smp_affinity\n" $IFACE $core $MASK
        SMP_I=`sed -E "${NOZEROCOMMA}" /proc/irq/$IRQ/smp_affinity`
        if [ "$SMP_I" != "$MASK" ]; then
                printf " ACTUAL\t%s %d %s <- /proc/irq/$IRQ/smp_affinity\n" $IFACE $core $SMP_I
                printf " WARNING -- SMP_AFFINITY setting failed\n"
        fi
        case "$XPS_ENA" in
        1)
                printf "%s %d %s -> /sys/class/net/%s/queues/tx-%d/xps_cpus\n" $IFACE $core $MASK $IFACE $((n-1))
                printf "%s" $MASK > /sys/class/net/$IFACE/queues/tx-$((n-1))/xps_cpus
        ;;
        2)
                MASK=0
                printf "%s %d %s -> /sys/class/net/%s/queues/tx-%d/xps_cpus\n" $IFACE $core $MASK $IFACE $((n-1))
                printf "%s" $MASK > /sys/class/net/$IFACE/queues/tx-$((n-1))/xps_cpus
        ;;
        *)
        esac
}

# Allow usage of , or -
#
parse_range () {
        RANGE=${@//,/ }
        RANGE=${RANGE//-/..}
        LIST=""
        for r in $RANGE; do
                # eval lets us use vars in {#..#} range
                [[ $r =~ '..' ]] && r="$(eval echo {$r})"
                LIST+=" $r"
        done
        echo $LIST
}

# Affinitize interrupts
#
doaff()
{
        CORES=$(parse_range $CORES)
        ncores=$(echo $CORES | wc -w)
        n=1

        # this script only supports interrupt vectors in pairs,
        # modification would be required to support a single Tx or Rx queue
        # per interrupt vector

        queues="${IFACE}-.*TxRx"

        irqs=$(grep "$queues" /proc/interrupts | cut -f1 -d:)
        [ -z "$irqs" ] && irqs=$(grep $IFACE /proc/interrupts | cut -f1 -d:)
        [ -z "$irqs" ] && irqs=$(for i in `ls -1 /sys/class/net/${IFACE}/device/msi_irqs | sort -n` ;do grep -w $i: /proc/interrupts | egrep -v 'fdir|async|misc|ctrl' | cut -f 1 -d :; done)
        [ -z "$irqs" ] && echo "Error: Could not find interrupts for $IFACE"

        if [ "$SHOW" == "1" ] ; then
                echo "TYPE IFACE CORE MASK -> FILE"
                echo "============================"
        else
                echo "IFACE CORE MASK -> FILE"
                echo "======================="
        fi

        for IRQ in $irqs; do
                [ "$n" -gt "$ncores" ] && n=1
                j=1
                # much faster than calling cut for each
                for i in $CORES; do
                        [ $((j++)) -ge $n ] && break
                done
                core=$i
                if [ "$SHOW" == "1" ] ; then
                        show_affinity
                else
                        set_affinity
                fi
                ((n++))
        done
}

# these next 2 lines would allow script to auto-determine interfaces
#[ -z "$IFACES" ] && IFACES=$(ls /sys/class/net)
#[ -z "$IFACES" ] && echo "Error: No interfaces up" && exit 1

# echo IFACES is $IFACES

CORES=$(</sys/devices/system/cpu/online)
[ "$CORES" ] || CORES=$(grep ^proc /proc/cpuinfo | cut -f2 -d:)

# Core list for each node from sysfs
node_dir=/sys/devices/system/node
for i in $(ls -d $node_dir/node*); do
        i=${i/*node/}
        corelist[$i]=$(<$node_dir/node${i}/cpulist)
done

for IFACE in $IFACES; do
        # echo $IFACE being modified

        dev_dir=/sys/class/net/$IFACE/device
        [ -e $dev_dir/numa_node ] && node=$(<$dev_dir/numa_node)
        [ "$node" ] && [ "$node" -gt 0 ] || node=0

        case "$AFF" in
        local)
                CORES=${corelist[$node]}
        ;;
        remote)
                [ "$rnode" ] || { [ $node -eq 0 ] && rnode=1 || rnode=0; }
                CORES=${corelist[$rnode]}
        ;;
        one)
                [ -n "$cnt" ] || cnt=0
                CORES=$cnt
        ;;
        all)
                CORES=$CORES
        ;;
        custom)
                echo -n "Input cores for $IFACE (ex. 0-7,15-23): "
                read CORES
        ;;
        [0-9]*)
                CORES=$AFF
        ;;
        *)
                usage
                exit 1
        ;;
        esac

        # call the worker function
        doaff
done

# check for irqbalance running
IRQBALANCE_ON=`ps ax | grep -v grep | grep -q irqbalance; echo $?`
if [ "$IRQBALANCE_ON" == "0" ] ; then
        echo " WARNING: irqbalance is running and will"
        echo "          likely override this script's affinitization."
        echo "          Please stop the irqbalance service and/or execute"
        echo "          'killall irqbalance'"
        exit 2
fi

CPU Affinity

Agora que preparamos as interfaces, façamos os apontamentos dos cores da seguinte forma. Vamos supor que colocamos o script em /root/scripts:

# /root/scripts/set_irq_affinity 0-7 enp5s0f0
# /root/scripts/set_irq_affinity 0-7 enp5s0f1
# /root/scripts/set_irq_affinity 8-15 enp6s0f0
# /root/scripts/set_irq_affinity 8-15 enp6s0f1

Mais alguns tunings

Vamos fazer mais alguns ajustes nas interfaces com o ethtool. Dessa vez vamos aumentar os Rings RX e TX. Mas antes vamos listar os valores que podemos usar:

# ethtool -g enp5s0f0
Ring parameters for enp5s0f0:
Pre-set maximums:
RX:             4096
RX Mini:        n/a
RX Jumbo:       n/a
TX:             4096
Current hardware settings:
RX:             512
RX Mini:        n/a
RX Jumbo:       n/a
TX:             512

Acima vemos que o valor máximo é de 4096 tanto para TX, quanto para RX mas está configurado para 512 em RX e TX. Façamos então:

# ethtool -G enp5s0f0 rx 4096 tx 4096
# ethtool -G enp5s0f1 rx 4096 tx 4096
# ethtool -G enp6s0f0 rx 4096 tx 4096
# ethtool -G enp6s0f1 rx 4096 tx 4096

Vamos desabilitar as seguintes options das interfaces: TSO, GRO e GSO.  

# ethtool -K enp5s0f0 tso off gro off gso off
# ethtool -K enp5s0f1 tso off gro off gso off
# ethtool -K enp6s0f0 tso off gro off gso off
# ethtool -K enp6s0f1 tso off gro off gso off

Aumentaremos o txqueuelen para 10000:

# ip link set enp5s0f0 txqueuelen 10000
# ip link set enp5s0f1 txqueuelen 10000
# ip link set enp6s0f0 txqueuelen 10000
# ip link set enp6s0f1 txqueuelen 10000

Salvando a configuração e criando o LACP

Tudo que fizemos até o momento será perdido no próximo reboot do sistema, então faremos com que esses comandos sejam executados sempre que o sistema iniciar. Para isso vamos deixar o nosso arquivo /etc/network/interfaces configurado conforme nosso diagrama, usando LACP e executando nossos comandos anteriores.

Antes precisaremos instalar o pacote ifenslave para que o bonding funcione:

# apt install ifenslave
# modprobe bonding
# echo "bonding" >> /etc/modules

Abaixo o nosso /etc/network/interfaces já com todas as configurações que fizemos anteriormente e seguindo nosso diagrama de exemplo:

# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

source /etc/network/interfaces.d/*

# The loopback network interface
auto lo
iface lo inet loopback
 
auto bond0
iface bond0 inet static
       bond-slaves enp5s0f0 enp5s0f1
       bond_mode 802.3ad
       bond-ad_select bandwidth
       bond_miimon 100
       bond_downdelay 200
       bond_updelay 200
       bond-lacp-rate 1
       bond-xmit-hash-policy layer2+3
       address 10.0.10.172/24
       gateway 10.0.10.1
       pre-up /usr/sbin/ethtool -L enp5s0f0 combined 8
       pre-up /usr/sbin/ethtool -L enp5s0f1 combined 8
       pre-up /root/scripts/set_irq_affinity 0-7 enp5s0f0
       pre-up /root/scripts/set_irq_affinity 0-7 enp5s0f1
       pre-up /usr/sbin/ethtool -G enp5s0f0 rx 4096 tx 4096
       pre-up /usr/sbin/ethtool -G enp5s0f1 rx 4096 tx 4096
       pre-up /usr/sbin/ethtool -K enp5s0f0 tso off gro off gso off
       pre-up /usr/sbin/ethtool -K enp5s0f1 tso off gro off gso off
       pre-up /usr/sbin/ip link set enp5s0f0 txqueuelen 10000
       pre-up /usr/sbin/ip link set enp5s0f1 txqueuelen 10000

auto bond1
iface bond1 inet static
        bond-slaves enp6s0f0 enp6s0f1
        bond_mode 802.3ad
        bond-ad_select bandwidth
        bond_miimon 100
        bond_downdelay 200
        bond_updelay 200
        bond-lacp-rate 1
        bond-xmit-hash-policy layer2+3
        address 192.168.0.1/24
        pre-up /usr/sbin/ethtool -L enp6s0f0 combined 8
        pre-up /usr/sbin/ethtool -L enp6s0f1 combined 8
        pre-up /root/scripts/set_irq_affinity 8-15 enp6s0f0
        pre-up /root/scripts/set_irq_affinity 8-15 enp6s0f1
        pre-up /usr/sbin/ethtool -G enp6s0f0 rx 4096 tx 4096
        pre-up /usr/sbin/ethtool -G enp6s0f1 rx 4096 tx 4096
        pre-up /usr/sbin/ethtool -K enp6s0f0 tso off gro off gso off
        pre-up /usr/sbin/ethtool -K enp6s0f1 tso off gro off gso off
        pre-up /usr/sbin/ip link set enp6s0f0 txqueuelen 10000
        pre-up /usr/sbin/ip link set enp6s0f1 txqueuelen 10000

Atualizando o Kernel

Colocaremos o kernel do backports. Para isso deixe o seu /etc/apt/sources conforme abaixo e rode os comandos na sequência:

deb http://security.debian.org/debian-security bullseye-security main contrib non-free
deb http://deb.debian.org/debian bullseye main non-free contrib
deb http://deb.debian.org/debian bullseye-updates main contrib non-free
deb http://deb.debian.org/debian bullseye-backports main contrib non-free
# apt update
# apt install -t bullseye-backports linux-image-amd64
# reboot

Protegendo contra static loop e preparando o ambiente do CGNAT

O static loop é algo que, definitivamente, pode derrubar toda a sua operação se não for devidamente tratado e pode ser facilmente explorado por pessoas mal intencionadas. A causa do problema é uma rota estática para um prefixo IP (seja IPv4 ou IPv6), que aponta para um next-hop e nesse destino não existe nenhuma informação sobre o prefixo IP na tabela de rotas local, obrigando o pacote a retornar para o seu gateway default e ficando nesse loop até que expire o TTL (Time To Live) do pacote. Isso ocorre muito nos casos em que temos concentradores PPPoE (BNG) e caixas CGNAT como esta que estaremos fazendo. Em Recomendações sobre Mitigação DDoS temos outras dicas de segurança sobre o assunto DDoS.

Crie um arquivo /etc/rc.local e dentro colocaremos algumas coisas como as blackholes para cada prefixo IPv4 público que usaremos no nosso servidor de exemplo e rotas de retorno para o nosso BNG:

# > /etc/rc.local
# chmod +x /etc/rc.local

Dentro teremos:

#!/bin/sh -e
/usr/sbin/ip route add blackhole 198.18.0.0/27 metric 254
/usr/sbin/ip route add 100.64.0.0/22 via 192.168.0.2

No exemplo acima estamos colocando em blackhole o nosso prefixo IPv4 público deste tutorial que é o 198.18.0.0/27 e adicionando uma rota de retorno do prefixo 100.64.0.0/22 usado no nosso BNG para o next-hop 192.168.0.2.

Redução dos tempos de timeouts e outros ajustes

Os tempos padrões dos timeouts de tcp e udp são altos para o nosso sistema de CGNAT, ainda mais quando estamos diminuindo a quantidade de portas tcp/udp por assinante e com isso podemos rapidamente estourar esse limite, fazendo com que o sistema pare de funcionar. Abaixo estou colocando os valores que sempre usei e não percebi problemas, mas você pode ajustar conforme achar mais prudente. Adicionaremos as configurações abaixo também no nosso /etc/rc.local:

echo 5 > /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_syn_sent
echo 5 > /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_syn_recv
echo 86400 > /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_established
echo 10 > /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_fin_wait
echo 10 > /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_close_wait
echo 10 > /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_last_ack
echo 10 > /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_time_wait
echo 10 > /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_close
echo 300 > /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_max_retrans
echo 300 > /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_unacknowledged
echo 10 > /proc/sys/net/netfilter/nf_conntrack_udp_timeout
echo 180 > /proc/sys/net/netfilter/nf_conntrack_udp_timeout_stream
echo 10 > /proc/sys/net/netfilter/nf_conntrack_icmp_timeout
echo 600 > /proc/sys/net/netfilter/nf_conntrack_generic_timeout

Em /etc/sysctl.conf adicionaremos:

net.core.default_qdisc=fq
net.ipv4.tcp_congestion_control=bbr
net.core.rmem_max = 2147483647
net.core.wmem_max = 2147483647
net.ipv4.tcp_rmem = 4096 87380 2147483647
net.ipv4.tcp_wmem = 4096 65536 2147483647
net.ipv4.conf.all.forwarding=1
net.netfilter.nf_conntrack_helper=1
net.netfilter.nf_conntrack_buckets = 512000
net.netfilter.nf_conntrack_max = 4096000
vm.swappiness=10

As configurações acima melhoram o uso de memória, habilita o encaminhamento dos pacotes e aumenta a quantidade máxima de conntracks do sistema para 4096000.

Se o conntrack estourar, seu CGNAT terá problemas e causará indisponibilidades. Para consultar a quantidade de conntracks em uso:

# cat /proc/sys/net/netfilter/nf_conntrack_count

Para listar as conntracks:

# cat /proc/net/nf_conntrack

Ajustando a data e horário do sistema

Uma tarefa muito importante a ser feita nos servidores, é garantir que o horário e data estejam corretos e para isso usaremos o programa chrony. Eu prefiro usar sempre horário UTC nos servidores e fazer a conversão quando necessário:

# apt install chrony

Basta copiar e colar os comandos abaixo, para configurar o chrony:

# cat << EOF > /etc/chrony/chrony.conf
confdir /etc/chrony/conf.d
sourcedir /run/chrony-dhcp
sourcedir /etc/chrony/sources.d
keyfile /etc/chrony/chrony.keys
driftfile /var/lib/chrony/chrony.drift
ntsdumpdir /var/lib/chrony
logdir /var/log/chrony
maxupdateskew 100.0
rtcsync
makestep 1 3
leapsectz right/UTC
EOF
# cat << EOF > /etc/chrony/sources.d/nic.sources
server a.st1.ntp.br iburst nts
server b.st1.ntp.br iburst nts
server c.st1.ntp.br iburst nts
server d.st1.ntp.br iburst nts
EOF

Aqui reiniciamos o serviço e configuramos o timezone:

# systemctl restart chronyd.service
# timedatectl set-timezone "UTC"

Habilitando ALG (Application Layer Gateway)

No arquivo /etc/modules adicionaremos os módulos que usaremos no nosso CGNAT, inclusive os ALGs. Sem eles alguns serviços, ainda muito utilizados, apresentarão problemas.

Em /etc/modules adicionaremos mais os módulos abaixo:

nf_conntrack
nf_nat_pptp
nf_nat_h323
nf_nat_sip
nf_nat_irc
nf_nat_ftp
nf_nat_tftp

Preparando ambiente e gerador de regras de CGNAT

Antes de começarmos nossas regras de CGNAT precisaremos de alguns pacotes:

# apt install python3-pip nftables
# pip install ipaddress

Vamos precisar também de um gerador de regras de CGNAT para nftables. Porque criar as regras manualmente não é uma tarefa rápida e para isso usaremos um programa em python criado por José Beiriz e disponibilizado aqui: GRCN

Caso não consigam baixar por algum motivo o GRCN, aqui https://github.com/gondimcodes/GRCN também pode ser encontrado o script cgnat-nft.py.

Nosso sistema de regras CGNAT será dividido em 2 partes:

  • O script base que colocaremos em /root/scripts chamado de frw-nft.sh. Esse script conterá as regras básicas do CGNAT e este incluirá a chamada para os outros arquivos de regras propriamente ditos do CGNAT.
  • Essa outra parte é composta pelos arquivos de regras de CGNAT, onde são feitas as traduções de IPs privados 100.64.0.0/10 (Shared Address Space - RFC6598), para os IPs públicos. A seguir o frw-nft.sh:

Nosso script de CGNAT base /root/scripts/frw-nft.sh:

#!/usr/sbin/nft -f
# limpa todas as regras da memoria
flush ruleset

add table ip filter
add ct helper ip filter pptp-vpn { type "pptp" protocol tcp; }
add ct helper ip filter ftp-padrao { type "ftp" protocol tcp; }
add ct helper ip filter sip-padrao { type "sip" protocol udp; }
add chain ip filter PREROUTING { type filter hook prerouting priority filter; }
add rule ip filter PREROUTING tcp dport 1723 ct helper set "pptp-vpn" 
add rule ip filter PREROUTING tcp dport 21 ct helper set "ftp-padrao" 
add rule ip filter PREROUTING ip protocol udp ct helper set "sip-padrao"

# regras base para o CGNAT
add table ip nat

add chain ip nat POSTROUTING { type nat hook postrouting priority 100; policy accept; }

add chain ip nat CGNATOUT

# libera o proprio CGNAT para acessar a Internet - para atualizacoes por exemplo
add rule ip nat POSTROUTING oifname "bond0" ip saddr 10.0.10.172 counter snat to 198.18.0.0 

# faz o jump para as regras de CGNAT
add rule ip nat POSTROUTING oifname "bond0" counter jump CGNATOUT

# carrega os arquivos de regras de CGNAT
include "/root/scripts/cgnat-0-31.conf"

A última linha do script acima, em negrito, é o arquivo de regras CGNAT que iremos gerar e será chamado pelo script quando for executado.

Após a criação do script, alteramos a permissão dele  para ficar como executável e adicionamos ele em nosso /etc/rc.local:

# chmod 700 /root/scripts/frw-nft.sh
# echo "/root/scripts/frw-nft.sh" >> /etc/rc.local

Gerando nossas regras de CGNAT

Colocaremos o script cgnat-nft.py em /root/scripts/. Como estamos trabalhando no modelo determinístico de 1/32, basta pegarmos nosso bloco privado 100.64.0.0/22 (1024 IPs) e nosso bloco público 198.18.0.0/27 (32 IPs) e executarmos em linha de comando:

# cd /root/scripts
# ./cgnat-nft.py 0 198.18.0.0/27 100.64.0.0/22 1/32

Se digitar apenas ./cgnat-nft.py será apresentado um help dos parâmetros mas é bem simples o seu uso. No comando acima temos o número 0 como índice. Muito cuidado com o índice, porque ele é muito importante para a performance e para cada novo arquivo gerado, esse índice precisará ser incrementado. O comando acima criará automaticamente o arquivo chamado cgnat-0-31.conf, aquele mesmo visto no script base sendo carregado com o include. Onde esse 0-31 quer dizer que nesse arquivo os índices vão de 0 a 31. Se for gerar um novo arquivo com o comando acima, o próximo índice a ser usado seria o 32. Por exemplo:

# ./cgnat-nft.py 32 198.18.0.32/27 100.64.4.0/22 1/32

Esse comando acima criará novas regras no arquivo chamado cgnat-32-63.conf, na sequência inclua esse novo arquivo dentro do /root/scripts/frw-nft.sh e execute o /root/scripts/frw-nft.sh novamente para carregar as novas regras. A seguir daremos uma olhada nas regras geradas nesses arquivos.

Executando o gerador de regras

# ./cgnat-nft.py 0 198.18.0.0/27 100.64.0.0/22 1/32
1022px-Grcn.png

Após teclar ENTER será gerado o arquivo cgnat-0-31.conf com as regras conforme a tela abaixo de exemplo:

1027px-Regras01.png

Na tela abaixo se observarmos o retângulo vermelho veremos a regra que faz o NAT de tudo que não for TCP ou UDP e por fim a regra que faz o jump de tudo que for origem 100.64.0.0/27 para o CGNATOUT_0 onde esse 0 é o índice.

1029px-Regras02.png

Explicando a função dos índices

O sistema de avaliação de regras de filtros de pacotes e NAT no GNU/Linux é do tipo First Match Win, o que significa que a pesquisa das regras se encerra quando o sistema encontra uma regra que dê match. O sistema fica muito mais otimizado e performático quando quebramos as regras e separamos em CHAINS e é aí que entram os índices. Porque as CHAINS não podem ter o mesmo nome, senão não haveria separação das regras. A seguir veremos por exemplo que quando houver um pacote relacionado com o prefixo de origem 100.64.0.0/27, este será encaminhado para a chain CGNATOUT_0, que é onde estão as regras de CGNAT para esse bloco IP. Desse jeito a checagem para esse prefixo não percorre todas as regras de NAT contidas na memória.

Regras03.png

Novo script gerador de regras nftables com suporte a netmap

Com a versão nova que virá do Debian, a versão 12 (Bookworm), teremos também uma versão nova do nftables 1.0.6 e essa versão já suporta o equivalente ao netmap que temos no Mikrotik e com isso teremos menos regras na memória e provavelmente mais performance. O sistema novo conta também com o kernel 6.1.27 que possui diversas melhorias na pilha tcp/ip. Para aqueles que já quiserem testar nesse novo ambiente, fiz uma modificação no script python mostrado anteriormente, para gerar regras nesse novo formato e um arquivo tabela com o relacionamento de portas e IPs para quebra de sigilo tecnológico. Aqui https://github.com/gondimcodes/GRCN o novo código e estarei solicitando ao José Beiriz para incorporá-lo no GRCN.

Para gerar as regras é só executar da mesma maneira. Exemplo:

# ./cgnat-nft-netmap.py 0 198.18.0.0/27 100.64.0.0/22 1/32

Abaixo exemplos de como ficam as novas regras e na memória:

Nftables netmap1.png
Nftables netmap2.png

Exemplo do arquivo tabela-0-31.txt que foi gerado:

Nftables netmap3.png

Simulando um acesso do cliente e observando os resultados

Para testar as regras, foi criado um ambiente virtual de laboratório usando um Proxmox e criando 3 VMs: CGNAT, BNG e CLIENTE. Do router de testes capturei os pacotes para demonstrar como funciona o CGNAT. A seguir teremos o acesso por parte do cliente e a captura dos pacotes somente para uma POC (Proof of Concept), para demonstrar que o CGNAT está funcionando e alocando a porta, dentro do range de portas, corretamente para um determinado cliente.

Abaixo temos um exemplo de captura bem simples de pacote mostrando que o IP 198.18.0.0 com porta origem 6767/TCP acessou o 200.147.41.220 na porta 443/TCP, um acesso para o site do UOL.

Cgnat sniffer.png

Se olharmos os dados marcados acima e procurarmos pelo IP 198.18.0.0 e porta 6767 no nosso arquivo de configuração do CGNAT, acharemos o IP 100.64.0.2 que utiliza o range de portas entre 5056 e 7071. Abaixo o nosso arquivo de regras de CGNAT para comprovar o range de portas utilizados.

Regras5.png

Monitorando o tráfego em tempo real

Monitorando o tráfego Mbps/PPS com a ferramenta bmon. Para instalar o software no Debian basta fazer:

# apt install bmon

Para monitorar as interfaces faríamos algo assim onde -b para bits/s e o -p para selecionar as interfaces que quer monitorar. Para monitorar nosso bond0 e bond1 o comando seria esse abaixo:

# bmon -b -p bond0,bond1

Abaixo uma tela de exemplo do bmon em execução:

Bmon cgnat.png

CGNAT no Mikrotik RouterOS

Uma boa opção para caixa CGNAT com custo x benefício acessível seria uma CCR1036-8G-2S+ onde se for configurada somente para fazer CGNAT, com o mínimo de regras de filtro e Fasttrack habilitado, já alcancei 13 Gbps de tráfego ou 26 Gbps agregado fazendo um bonding com as 2 interfaces ópticas de 10Gbps.

Essa imagem abaixo foi retirada do datasheet da CCR1036-8G-2S+:

Datasheet ccr1036.png

Configurando o sistema

Instale um Mikrotik RouterOS do zero, procure utilizar a versão mais estável possível. Como não utilizei ainda em produção o RouterOS 7.x, sugiro utilizar a versão 6.48.6 Long-term, que até o momento, é a versão considerada mais estável. O processo de configurar um CGNAT Determinístico no Mikrotik RouterOS será bem mais simples que no Debian GNU/Linux mas a capacidade alcançada com o GNU/Linux será bem superior ao visto aqui.

Sobre Fasttrack

O Fasttrack é um recurso muito importante que aumentará a performance da sua caixa CGNAT, acelerando o encaminhamento de pacotes e diminuindo o consumo de CPU. Neste momento não faremos isso. Quando chegarmos no processo de criação das regras de CGNAT, ele será habilitado e será mostrado quais as regras que fazem isso.

Configurando o bonding

Como usaremos as duas portas de 10GbE sfp+ da CCR, utilizaremos vlans para separar a rede que se comunicará com a Internet, da rede com o BNG. A seguir veremos como deixar o nosso bonding. Na sequência configuramos nossas vlans de entrada e saída e em cima delas os IPs do diagrama, como fizemos com o Debian. Vamos definir a vlan 101 para a interface que fará a comunicação com a Internet e por onde será feito o CGNAT e a vlan 102 que fará a comunicação com o BNG.

Cgnat mk1.png
Cgnat mk2.png

Configurando os IPs e rotas

O objetivo deste artigo é ser bem simples para entendermos os conceitos e por isso estamos utilizando rotas estáticas e não estamos envolvendo outros protocolos como o OSPF. Nada impediria de utilizar a mesma técnica apresentada aqui em um cenário com OSPF, por exemplo.

A seguir veremos que na vlan-101-borda configuramos o IP 10.0.10.172/24 e na vlan-102-bng configuramos o IP 192.168.0.1/24.

Como rotas criamos uma default route apontando para o IP 10.0.10.1, criamos uma rota para 100.64.0.0/22 com next-hop 192.168.0.2 e para nos protegermos de static loop teremos nossas rotas de blackhole quando formos gerar as regras de CGNAT.

Na imagem aparece como unreachable porque esse equipamento, que está sendo usado como lab, não está conectado em uma switch.

Cgnat mk3.png

Recomendações de segurança

  • Utilize credenciais de acesso com senhas fortes, não esqueça o login admin sem senha (padrão no Mikrotik RouterOS).
  • Desabilite todos os serviços que não for utilizar e os que ficarem abertos, especifique neles o acesso apenas da sua rede de gerência. Não deixe qualquer serviço aberto para a Internet.
  • Habilite o TCP SynCookies.

Procure criar suas regras de filtros de pacotes sempre na Table Raw, ela não agride tanto a performance do equipamento mas necessita de muita atenção porque ela pode afetar os acessos dos assinantes. Isso porque uma regra genérica demais será analisada tanto com destino a caixa, quanto destino ao cliente e o mesmo pode ocorrer no sentido inverso, do cliente para a Internet.

Cgnat mk4.png

Acertando data e hora

Configure o NTP client da caixa e mantenha a data e horário sincronizados.

Cgnat mk5.png

Criando as regras de CGNAT

Para simplificar nossa vida, Rudimar Remontti criou em seu blog, um sistema para gerar regras de CGNAT Determinístico de forma simples e performática, utilizando regras netmap da Mikrotik. Para tanto o link é este:

https://cgnat.remontti.com.br/

O sistema é bem completo, simples, irá gerar as regras de CGNAT e nossas blackholes para bloqueio de static loop. Também no final teremos uma tabela de associação que devemos guardar para fazer as quebras de sigilo solicitadas nos Ofícios Judiciais.

Ao acessar o site e seguindo o nosso diagrama completaremos as informações conforme mostrado a seguir.

Cgnat remontti1.png

O site irá gerar automaticamente os comandos de onde faremos uma cópia e executaremos no nosso equipamento Mikrotik RouterOS.

Cgnat remontti2.png

No final da página é gerado uma tabela do mapeamento das portas, isso deve ser salvo como documento importante pois será usado para quebra de sigilo tecnológico.

Cgnat remontti3.png

O conceito é o mesmo, quebrar as regras em blocos menores para chegarmos no nosso First Match Win mais rápido e não termos que percorrer todas as regras em memória.

Cgnat remontti4.png

Abaixo como ficaram as regras que habilita o Fasttrack no nosso equipamento, aumentando em muito a performance de encaminhamento dos pacotes.

Cgnat mk6.png

Conclusão

Essa documentação foi útil? Compartilhe, divulgue e ajude outras pessoas.

Autor: Marcelo Gondim