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

De Wiki BPF
Ir para navegação Ir para pesquisar
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: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:Cgnat diagrama2.png|miniaturadaimagem|776x776px|esquerda]]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 6Gbps 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 motherboard possui gerência 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 motherboard. 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 motherboard 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á está 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. Existe também a possibilidade de não precisar do dummy se for trabalhar sem os IPs públicos nas interfaces virtuais e usarmos blackholes para se proteger dos static-loops.<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)'''.
 
  
Se não quiser trabalhar com os IPs públicos nas interfaces virtuais dummy, basta substituir a linha "'''/sbin/ip addr add 192.0.0.0/32 dev dummy0 &> /dev/null'''" por "'''/sbin/ip route add blackhole 192.0.0.0/32'''" ou se houverem mais IPs, por exemplo, "'''/sbin/ip route add blackhole 192.0.0.0/27"'''.Isso é para proteção contra static-loops, já que não teremos mais os IPs públicos carregados, e deve ser feito para todos os prefixos públicos usados na caixa CGNAT.
+
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'''.
  
== Habilitando ALGs ==
+
A seguir veremos um exemplo de datasheet da '''motherboard S2600WF''':
O ALG (Application Layer Gateway) é necessário para alguns serviços que apresentam problemas quando passando por NAT; eles precisam de um controle especial e se não forem ativados podem acontecer problemas com por exemplo: VPNs PPTP. Abaixo um exemplo de ALG PPTP:<pre>
+
[[Arquivo: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'''.
Adicione em /etc/sysctl.conf:
 
  
net.netfilter.nf_conntrack_helper=1
+
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'''.
  
Depois para carregar o sysctl:
+
=== Tuning antes do CPU Affinity ===
 +
Também é importante, para aumento de performance, que seja '''desabilitado na BIOS o HT (Hyper Threading)'''.
  
# sysctl -p
+
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'''.
  
Carregando módulos kernel 4.x:
+
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]'''
  
# modprobe nf_nat_proto_gre
+
=== Código do script '''set_irq_affinity''' ===
# modprobe nf_nat_pptp
+
#!/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
  
Carregando módulos kernel 5.x:
+
=== 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
  
# modprobe nf_nat_pptp
+
=== 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
  
Para que carreguem automaticamente no boot adicione as linhas em
+
=== Salvando a configuração e criando o LACP ===
/etc/modules:
+
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.
  
nf_nat_proto_gre    ===> somente em kernel 4.x
+
Antes precisaremos instalar o pacote '''ifenslave''' para que o bonding funcione:
nf_nat_pptp
+
# apt install ifenslave
</pre>
+
# 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
  
== Scripts CGNAT ==
+
=== Atualizando o Kernel ===
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. Com o lançamento do Debian Buster, o nf_tables passou à vir como padrão no lugar do antigo x_tables e aconselha-se à usá-lo pois será o novo sistema de Firewall adotado por todas as grandes distribuições Linux.
+
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
  
Vamos criar um ambiente pro nosso artigo: crie um diretório '''/root/cgnat''' que é onde ficarão todos os scripts que usaremos abaixo:
+
# apt update
 +
# apt install -t bullseye-backports linux-image-amd64
 +
# reboot
  
=== frw.sh - formato x_tables ===
+
=== Protegendo contra static loop e preparando o ambiente do CGNAT ===
[[Arquivo:Frw.png|nenhum|miniaturadaimagem|806x806px]]
+
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 '''[[Recomendacao Mitigacao DDoS|Recomendações sobre Mitigação DDoS]]''' temos outras dicas de segurança sobre o assunto '''DDoS'''.
  
=== '''frw.sh - formato nf_tables''' ===
+
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:
[[Arquivo:Cgnat nft2.png|nenhum|miniaturadaimagem|653x653px]]
+
# > /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'''.
  
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''' como pode ser visto abaixo:
+
=== 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'''.
  
=== Abaixo as regras do cgnat-bras1.conf - formato x_tables ===
+
'''Se o conntrack estourar, seu CGNAT terá problemas e causará indisponibilidades'''. Para consultar a quantidade de conntracks em uso:
/sbin/iptables -t nat -N CGNATOUT_0
+
# cat /proc/sys/net/netfilter/nf_conntrack_count
 +
Para listar as '''conntracks''':
 +
# cat /proc/net/nf_conntrack
  
/sbin/iptables -t nat -N CGNATIN_0
+
=== 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 -F CGNATOUT_0
+
# 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 -F CGNATIN_0
+
=== 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.0 -p tcp -j SNAT --to 192.0.0.0:1-2048
+
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 CGNATOUT_0 -s 100.64.0.0 -p udp -j SNAT --to 192.0.0.0:1-2048
+
=== 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 tcp --dport 1:2048 -j DNAT --to 100.64.0.0
+
Caso não consigam baixar por algum motivo o '''GRCN''', abaixo o código do script '''cgnat-nft.py''':
 +
#!/usr/bin/env python3
 +
<nowiki>#</nowiki> -*- coding: utf-8 -*-
 +
<nowiki>'''</nowiki># -*- coding: latin-1 -*-<nowiki>'''</nowiki>
 +
 +
<nowiki>'''</nowiki>
 +
GRCN is free software; you can redistribute it and/or modify
 +
it under the terms of the GNU General Public License as published by
 +
the Free Software Foundation; either version 2 of the License, or
 +
(at your option) any later version.
 +
<nowiki>#</nowiki>
 +
This program is distributed in the hope that it will be useful,
 +
but WITHOUT ANY WARRANTY; without even the implied warranty of
 +
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +
GNU General Public License for more details.
 +
<nowiki>#</nowiki>
 +
You should have received a copy of the GNU General Public License
 +
along with this program; if not, write to the Free Software
 +
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 +
<nowiki>'''</nowiki>
 +
 +
import os
 +
import sys
 +
import time
 +
import ipaddress
 +
 +
__author__ = 'Beiriz'
 +
__version__= 4.001
 +
__datebegin__= "27/07/2020 (31/03/2023)"
 +
__com1__ = "add rule ip nat"
 +
 +
<nowiki>#</nowiki>-----------------------------------------------------------------------
 +
fazer_regras_in = False #Este valor deve ser alterado para True caso haja interesse de gerar também as regras de CGNAT no sentido IN: 'fazer_regras_in = True'. OBS: CGNAT do tipo OUT sempre serão geradas.
 +
indice = 0
 +
txt_publico = ""
 +
txt_privado = ""
 +
masc_subrede_privada = 0 #mascara da subrede de IPs privados que serão atendidos por 1 IP público
 +
qt_ips_publicos = 0 #Quantidade de IPs públicos na rede informada
 +
qt_ips_privados = 0 #Quantidade de IPs privados na rede informada
 +
qt_ips_privados_por_ip_publico = 0 #Quantos IPs privados vão sair por um único IP público ( A relação PRI/PUB)
 +
qt_portas_por_ip = 2016 #quantidade de portas que serão reservadas por IP privado.
 +
numero_porta_incial = 1024
 +
numero_porta_final = 65535
 +
relacao = '1/32'
 +
qt_total_portas = (numero_porta_final-(numero_porta_incial-1))
 +
<nowiki>#</nowiki>As 2 confs abaixo trabalham em conjunto para ajustar o range total de portas de cada IP público
 +
relacao_portas = {'1/4':16128, '1/8':8064, '1/16':4032, '1/32':2016, '1/64':1008, '1/128':504, '1/256':252}
 +
relacao_mascara = {'1/4':30, '1/8':29, '1/16':28, '1/32':27, '1/64':26, '1/128':25, '1/256':24}
 +
relacao_ips_masq = {'1/4':4, '1/8':8, '1/16':16, '1/32':32, '1/64':64, '1/128':128, '1/256':256}
 +
<nowiki>#</nowiki>-------------------------------------------------
 +
 +
os.system('cls' if os.name == 'nt' else 'clear')
 +
titulo = "GRCN - Gerador de Regras CGNAT em nftables - %s - v%s - %s" % (__author__, __version__,__datebegin__)
 +
print("#"*100)
 +
print("    %s" %(titulo))
 +
print("#"*100)
 +
 +
<nowiki>#</nowiki>------------------------------------------------- Parâmetros informados / manual:
 +
 +
try:
 +
<nowiki> </nowiki> #Indice:
 +
<nowiki> </nowiki> indice = int(sys.argv[1])
 +
<nowiki> </nowiki> #Blocos:
 +
<nowiki> </nowiki> txt_publico = str(sys.argv[2])
 +
<nowiki> </nowiki> txt_privado = str(sys.argv[3])
 +
<nowiki> </nowiki> #RELAÇÃO_IP_PUBLICO_X_CLIENTE
 +
<nowiki> </nowiki> try:
 +
<nowiki> </nowiki>  relacao = str(sys.argv[4])
 +
<nowiki> </nowiki> except:
 +
<nowiki> </nowiki>  relacao = '1/32'
 +
<nowiki> </nowiki> finally:
 +
<nowiki> </nowiki>  qt_portas_por_ip = relacao_portas[relacao]
 +
<nowiki> </nowiki>  print("\n\t[ Ídice inicial: %i | público: %s | privado: %s | %i portas/IP (%s)]\t\n" % (indice, txt_publico, txt_privado, qt_portas_por_ip, relacao))
 +
 +
except:
 +
<nowiki> </nowiki> print("\nErro! Informe pelo menos os parâmetros obrigatórios deste script.\n")
 +
<nowiki> </nowiki> print("## Manual de Instruções:")
 +
<nowiki> </nowiki> print("\n###### Exemplo básico (1/32):\n")
 +
<nowiki> </nowiki> print("```")
 +
<nowiki> </nowiki> print("%spython %s <INDICE> <BLOCO_PUBLICO> <BLOCO_PRIVADO>" %(' '*6, sys.argv[0]))
 +
<nowiki> </nowiki> print("%spython %s 0 192.0.2.0/24 100.69.0.0/22" %(' '*6, sys.argv[0]))
 +
<nowiki> </nowiki> print("```")
 +
<nowiki> </nowiki> print("\n###### Exemplo avançado:\n")
 +
<nowiki> </nowiki> print("```")
 +
<nowiki> </nowiki> print("%s python %s <INDICE> <BLOCO_PUBLICO> <BLOCO_PRIVADO> <RELAÇÃO_IP_PUBLICO_X_CLIENTE>(OPCIONAL)" %(' '*6, sys.argv[0]))
 +
<nowiki> </nowiki> print("%s python %s 0 192.0.2.0/24 100.69.0.0/22 1/16")
 +
<nowiki> </nowiki> print("```")
 +
<nowiki> </nowiki> print("\n###### Parâmetros:\n")
 +
<nowiki> </nowiki> print("* INDICE: Inteiro >=0 que vai ser o sufixo do nome das regras únicas. Exemplo *CGNATOUT_XXX*;\n")
 +
<nowiki> </nowiki> print("* BLOCO_PUBLICO: É o bloco de IPs públicos por onde o bloco CGNAT vai sair para a internet. Exemplo: *192.0.2.0/24*\n")
 +
<nowiki> </nowiki> print("* BLOCO_PRIVADO: É o bloco de IPs privados que serão entregues ao assinante.\n")
 +
<nowiki> </nowiki> print("* RELAÇÃO_IP_PUBLICO_X_CLIENTE (OPCIONAL):")
 +
<nowiki> </nowiki> print("    - 1/4  - 16128 portas por IP;")
 +
<nowiki> </nowiki> print("    - 1/8  - 8064 portas por IP;")
 +
<nowiki> </nowiki> print("    - 1/16  - 4032 portas por IP;")
 +
<nowiki> </nowiki> print("    - 1/32  - 2016 portas por IP (Configuração padrão, quando este último parâmetro não é informado);")
 +
<nowiki> </nowiki> print("    - 1/64  - 1008 portas por IP (Atenção! Não recomendado pelas boas práticas);")
 +
<nowiki> </nowiki> print("    - 1/128 - 504 portas por IP (Atenção! Não recomendado pelas boas práticas);")
 +
<nowiki> </nowiki> print("    - 1/256 - 252 portas por IP (Atenção! Não recomendado pelas boas práticas);")
 +
<nowiki> </nowiki> print("\n####### Observações:\n")
 +
<nowiki> </nowiki> print("* Este script vai dividir o <BLOCO_PRIVADO> em N sub-redes privadas. Cada sub-rede privada sai por um único IP público e dela, cada IP privado sai com uma fração das portas de seu IP público.\n")
 +
<nowiki> </nowiki> print("* Se <BLOCO_PUBLICO> for um /24 e <BLOCO_PRIVADO> um /19 e a relação for 1/32, serão colocados exatamente 32 IPs privados (assinantes) atrás de um IP público. Cada IP privado vai sair com 2016 portas de seu IP público (65535-1023)/32. O famoso *1:32*.\n")
 +
<nowiki> </nowiki> print("\n")
 +
<nowiki> </nowiki> print("\nATENÇÃO! Por boas práticas, o script PAROU de gerar as regras CGNAT do tipo IN. Caso queira continuar gerando-as, edite o cgnat-nft.py, alterando o valor *fazer_regras_in* de *False* para *True*;")
 +
<nowiki> </nowiki> print("\nFIM deste manual!\n")
 +
<nowiki> </nowiki> exit(0)
 +
<nowiki>#</nowiki>exit(0)
 +
<nowiki>#</nowiki>------------------------------------------------- trata os parâmetros informados:
 +
 +
try:
 +
<nowiki> </nowiki> if sys.version_info >= (3,0):
 +
<nowiki> </nowiki>  rede_publica = ipaddress.ip_network(str(txt_publico), strict=False)
 +
<nowiki> </nowiki>  rede_privada = ipaddress.ip_network(str(txt_privado), strict=False)
 +
<nowiki> </nowiki> else:
 +
<nowiki> </nowiki>  rede_publica = ipaddress.ip_network(unicode(txt_publico), strict=False)
 +
<nowiki> </nowiki>  rede_privada = ipaddress.ip_network(unicode(txt_privado), strict=False)
 +
<nowiki> </nowiki> qt_ips_publicos = int(rede_publica.num_addresses)
 +
<nowiki> </nowiki> qt_ips_privados = int(rede_privada.num_addresses)
 +
<nowiki> </nowiki> qt_ips_privados_por_ip_publico = int( qt_ips_privados / qt_ips_publicos )
 +
<nowiki> </nowiki> # Nome arquivo de destino
 +
<nowiki> </nowiki> nome_arquivo_destino = ("cgnat-%i-%i.conf" % (indice,(indice + qt_ips_publicos - 1)))
 +
<nowiki> </nowiki> # calcula a máscara das subnets privadas baseado na relação PRI/PUB:
 +
<nowiki> </nowiki> subnets_privadas = list(rede_privada.subnets(new_prefix=relacao_mascara[relacao]))
 +
except:
 +
<nowiki> </nowiki> print("\nErro! Informe parâmetros válidos para este script:\n\nRespeite a relação de IP público x IP privado: 1:32, 1:16, 1:8, etc\n\nEncerrando!\n")
 +
<nowiki> </nowiki> exit(0)
 +
 +
if (qt_ips_publicos * relacao_ips_masq[relacao]) > qt_ips_privados:
 +
<nowiki> </nowiki>  print("\nErro! Quantidade de IPs privados insuficiente!")
 +
<nowiki> </nowiki>  exit(0)
 +
 +
print(" - Índice das regras: %i;" % (indice))
 +
print(" - Rede pública: %s (%i IPs);" % (txt_publico,qt_ips_publicos))
 +
print(" - Rede privada: %s (%i IPs);" % (txt_privado,qt_ips_privados))
 +
print(" - Quantidade de IPs privados por IP público: %i (%i sub-redes /%i);" % (qt_ips_privados_por_ip_publico, qt_ips_publicos, relacao_mascara[relacao]))
 +
print(" - Total de portas públicas: %i;" % (qt_total_portas))
 +
print(" - Portas por IP privado: %i;" % (qt_portas_por_ip))
 +
print(" - Arquivo de destino (conf): '%s';" % (nome_arquivo_destino))
 +
print("\n")
 +
 +
if fazer_regras_in:
 +
<nowiki> </nowiki> print("\nATENÇÃO!\n  Variável fazer_regras_in=True\n  Mesmo não sendo boas práticas, SERÃO geradas regras de CGNAT do tipo IN!\n")
 +
 +
<nowiki>#</nowiki>------------------------------------------------- Abre o arquivo onde as regras serão armazenadas (destino):
 +
try:
 +
<nowiki> </nowiki> caminho_deste_script = os.path.dirname(os.path.realpath(__file__))+'/'
 +
<nowiki> </nowiki> arquivo_destino = open(caminho_deste_script+nome_arquivo_destino, "w")
 +
except (OSError, IOError) as e:
 +
<nowiki> </nowiki> print ("\nErro!\nFalha ao abrir a escrita do arquivo onde as regras serão armazenadas (destino)")
 +
<nowiki> </nowiki> sys.exit(1)
 +
 +
arquivo_destino.write("# %s\n" %(titulo))
 +
arquivo_destino.write("# - blocos %s -> %s;\n# - /%i de IPs privados / IP público;\n# - %i portas / IP privado;\n" %(
 +
<nowiki> </nowiki> txt_privado,
 +
<nowiki> </nowiki> txt_publico,
 +
<nowiki> </nowiki> masc_subrede_privada,
 +
<nowiki> </nowiki> qt_portas_por_ip
 +
))
 +
 +
<nowiki>#</nowiki>-------------------------------------------------------------------------- principal
 +
 +
if sys.version_info >= (3,0):
 +
<nowiki> </nowiki> input("Tecle [ENTER]...")
 +
else:
 +
<nowiki> </nowiki> raw_input("Tecle [ENTER]...")
 +
momento_incial = time.time()
 +
 +
<nowiki>#</nowiki>exit(0)
 +
 +
print("\n")
 +
indice_subnet_privada = 0
 +
for ip_publico in rede_publica:
 +
<nowiki> </nowiki> arquivo_destino.write("# %s #INDICE %i / IP PUBLICO %s\n" % ('-' * 40, indice, str(ip_publico)))
 +
<nowiki> </nowiki> arquivo_destino.write("add chain ip nat CGNATOUT_%i\n" % (indice))
 +
<nowiki> </nowiki> if fazer_regras_in:
 +
<nowiki> </nowiki>  arquivo_destino.write("add chain ip nat CGNATIN_%i\n" % (indice))
 +
<nowiki> </nowiki> arquivo_destino.write("flush chain ip nat CGNATOUT_%i\n" % (indice))
 +
<nowiki> </nowiki> if fazer_regras_in:
 +
<nowiki> </nowiki>  arquivo_destino.write("flush chain ip nat CGNATIN_%i\n" % (indice))
 +
<nowiki> </nowiki> #print(subnets_privadas)
 +
<nowiki> </nowiki> #print(indice_subnet_privada)
 +
<nowiki> </nowiki> subnet = subnets_privadas[indice_subnet_privada]
 +
<nowiki> </nowiki> # Zera o range de portas para o prox IP publico
 +
<nowiki> </nowiki> porta_ini = numero_porta_incial
 +
<nowiki> </nowiki> ###porta_fim = qt_portas_por_ip
 +
<nowiki> </nowiki> porta_fim = (numero_porta_incial + (qt_portas_por_ip -1))
 +
<nowiki> </nowiki> print("%s INDICE=%i - IP_PUBLICO=%s -> SUBNET_PRIVADA_%i=%s" % ("=" * 40, indice, str(ip_publico), (indice_subnet_privada+1), str(subnet)))
 +
<nowiki> </nowiki> for ip_privado in ipaddress.ip_network(subnet):
 +
<nowiki> </nowiki>  #trp = "1-2048"
 +
<nowiki> </nowiki>  trp = "%i-%i" % (porta_ini,porta_fim)
 +
<nowiki> </nowiki>  print("%s IP PRIVADO %s:%s" %("-"*60,str(ip_privado),trp))
 +
<nowiki> </nowiki>  #Regras para cada IP privado
 +
<nowiki> </nowiki>  arquivo_destino.write("%s CGNATOUT_%i ip protocol tcp ip saddr %s counter snat to %s:%s\n" % (
 +
<nowiki> </nowiki>    __com1__,
 +
<nowiki> </nowiki>    indice,
 +
<nowiki> </nowiki>    str(ip_privado),
 +
<nowiki> </nowiki>    str(ip_publico),
 +
<nowiki> </nowiki>    trp
 +
<nowiki> </nowiki>  ))
 +
<nowiki> </nowiki>  arquivo_destino.write("%s CGNATOUT_%i ip protocol udp ip saddr %s counter snat to %s:%s\n" % (
 +
<nowiki> </nowiki>    __com1__,
 +
<nowiki> </nowiki>    indice,
 +
<nowiki> </nowiki>    str(ip_privado),
 +
<nowiki> </nowiki>    str(ip_publico),
 +
<nowiki> </nowiki>    trp
 +
<nowiki> </nowiki>  ))
 +
 +
<nowiki> </nowiki>  if fazer_regras_in:
 +
<nowiki> </nowiki>    arquivo_destino.write("%s CGNATIN_%i ip daddr %s tcp dport %s counter dnat to %s\n" % (
 +
<nowiki> </nowiki>      __com1__,
 +
<nowiki> </nowiki>      indice,
 +
<nowiki> </nowiki>      str(ip_publico),
 +
<nowiki> </nowiki>      trp,
 +
<nowiki> </nowiki>      str(ip_privado)
 +
<nowiki> </nowiki>    ))
 +
<nowiki> </nowiki>    arquivo_destino.write("%s CGNATIN_%i ip daddr %s udp dport %s counter dnat to %s\n" % (
 +
<nowiki> </nowiki>      __com1__,
 +
<nowiki> </nowiki>      indice,
 +
<nowiki> </nowiki>      str(ip_publico),
 +
<nowiki> </nowiki>      trp,
 +
<nowiki> </nowiki>      str(ip_privado)
 +
<nowiki> </nowiki>    ))
 +
 +
<nowiki> </nowiki>  #incrementa o range de portas para o próximo IP privado
 +
<nowiki> </nowiki>  porta_ini += qt_portas_por_ip
 +
<nowiki> </nowiki>  porta_fim += qt_portas_por_ip
 +
<nowiki> </nowiki>  if porta_fim > numero_porta_final:
 +
<nowiki> </nowiki>    porta_fim = numero_porta_final
 +
<nowiki> </nowiki> #regras finais para a subrede x IP público
 +
<nowiki> </nowiki> #arquivo_destino.write("\n")
 +
<nowiki> </nowiki> arquivo_destino.write("%s CGNATOUT_%i counter snat to %s\n" % (
 +
<nowiki> </nowiki>  __com1__,
 +
<nowiki> </nowiki>  indice,
 +
<nowiki> </nowiki>  str(ip_publico)
 +
<nowiki> </nowiki> ))
 +
<nowiki> </nowiki> arquivo_destino.write("%s CGNATOUT ip saddr %s counter jump CGNATOUT_%i\n" % (
 +
<nowiki> </nowiki>  __com1__,
 +
<nowiki> </nowiki>  str(subnet),
 +
<nowiki> </nowiki>  indice
 +
<nowiki> </nowiki> ))
 +
<nowiki> </nowiki> if fazer_regras_in:
 +
<nowiki> </nowiki>  arquivo_destino.write("%s CGNATIN ip daddr %s/32 counter jump CGNATIN_%i\n" % (
 +
<nowiki> </nowiki>    __com1__,
 +
<nowiki> </nowiki>    str(ip_publico),
 +
<nowiki> </nowiki>    indice
 +
<nowiki> </nowiki>  ))
 +
<nowiki> </nowiki> #for ip_privado in subnet.subnets(new_prefix=32):
 +
<nowiki> </nowiki> #  print("    %s" % (str(ip_privado)))
 +
<nowiki> </nowiki> #arquivo_destino.write("\n")
 +
<nowiki> </nowiki> indice+=1
 +
<nowiki> </nowiki> indice_subnet_privada+=1
 +
 +
<nowiki>#</nowiki>-------------------------------------------------------------------------- final
 +
 +
<nowiki>#</nowiki>Fecha o arquivo onde as regras serão armazenadas (destino):
 +
try:
 +
<nowiki> </nowiki> arquivo_destino.close()
 +
except (OSError, IOError) as e:
 +
<nowiki> </nowiki> print ("\nErro!\nFalha ao salvar o arquivo onde as regras serão armazenadas (destino)")
 +
<nowiki> </nowiki> sys.exit(1)
 +
 +
print("\nFIM!\n\nAs regras foram geradas no arquivo:\n%s\n\nDuração: %.3f segundos" %(
 +
<nowiki> </nowiki> caminho_deste_script + nome_arquivo_destino,
 +
<nowiki> </nowiki> (time.time()-momento_incial)
 +
))
 +
Nosso sistema de regras CGNAT será dividido em 2 partes:
  
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 1:2048 -j DNAT --to 100.64.0.0
+
* 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 CGNATOUT_0 -s 100.64.0.1 -p tcp -j SNAT --to 192.0.0.0:2049-4096
+
* 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 CGNATOUT_0 -s 100.64.0.1 -p udp -j SNAT --to 192.0.0.0:2049-4096
+
Nosso script de CGNAT base '''/root/scripts/frw-nft.sh''':
 +
#!/usr/sbin/nft -f
 +
# limpa todas as regras da memoria
 +
flush ruleset
 +
 +
# 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 CGNATIN_0 -d 192.0.0.0 -p tcp --dport 2049:4096 -j DNAT --to 100.64.0.1
+
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 CGNATIN_0 -d 192.0.0.0 -p udp --dport 2049:4096 -j DNAT --to 100.64.0.1
+
=== 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 CGNATOUT_0 -s 100.64.0.2 -p tcp -j SNAT --to 192.0.0.0:4097-6144
+
=== Executando o gerador de regras ===
 +
# ./cgnat-nft.py 0 198.18.0.0/27 100.64.0.0/22 1/32
 +
[[Arquivo: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: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:Regras02.png|nenhum|miniaturadaimagem|1029x1029px]]
  
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.2 -p udp -j SNAT --to 192.0.0.0:4097-6144
+
=== 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 CGNATIN_0 -d 192.0.0.0 -p tcp --dport 4097:6144 -j DNAT --to 100.64.0.2
+
=== 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 udp --dport 4097:6144 -j DNAT --to 100.64.0.2
+
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 CGNATOUT_0 -s 100.64.0.3 -p tcp -j SNAT --to 192.0.0.0:6145-8192
+
=== 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.3 -p udp -j SNAT --to 192.0.0.0:6145-8192
+
== 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 CGNATIN_0 -d 192.0.0.0 -p tcp --dport 6145:8192 -j DNAT --to 100.64.0.3
+
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 udp --dport 6145:8192 -j DNAT --to 100.64.0.3
+
=== 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 CGNATOUT_0 -s 100.64.0.4 -p tcp -j SNAT --to 192.0.0.0:8193-10240
+
=== 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.4 -p udp -j SNAT --to 192.0.0.0:8193-10240
+
=== 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 CGNATIN_0 -d 192.0.0.0 -p tcp --dport 8193:10240 -j DNAT --to 100.64.0.4
+
=== 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 udp --dport 8193:10240 -j DNAT --to 100.64.0.4
+
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 CGNATOUT_0 -s 100.64.0.5 -p tcp -j SNAT --to 192.0.0.0:10241-12288
+
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.5 -p udp -j SNAT --to 192.0.0.0:10241-12288
+
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 CGNATIN_0 -d 192.0.0.0 -p tcp --dport 10241:12288 -j DNAT --to 100.64.0.5
+
=== Recomendações de segurança ===
  
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 10241:12288 -j DNAT --to 100.64.0.5
+
* 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 CGNATOUT_0 -s 100.64.0.6 -p tcp -j SNAT --to 192.0.0.0:12289-14336
+
* 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.6 -p udp -j SNAT --to 192.0.0.0:12289-14336
+
* Habilite o '''TCP SynCookies'''.
  
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 12289:14336 -j DNAT --to 100.64.0.6
+
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 udp --dport 12289:14336 -j DNAT --to 100.64.0.6
+
=== 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 CGNATOUT_0 -s 100.64.0.7 -p tcp -j SNAT --to 192.0.0.0:14337-16384
+
=== 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.7 -p udp -j SNAT --to 192.0.0.0:14337-16384
+
'''https://cgnat.remontti.com.br/'''
  
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 14337:16384 -j DNAT --to 100.64.0.7
+
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 udp --dport 14337:16384 -j DNAT --to 100.64.0.7
+
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 CGNATOUT_0 -s 100.64.0.8 -p tcp -j SNAT --to 192.0.0.0:16385-18432
+
== Conclusão ==
 
+
Essa documentação foi útil? Compartilhe, divulgue e ajude outras pessoas. Meus contatos podem ser vistos [[Sobre mim|aqui]].
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.8 -p udp -j SNAT --to 192.0.0.0:16385-18432
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 16385:18432 -j DNAT --to 100.64.0.8
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 16385:18432 -j DNAT --to 100.64.0.8
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.9 -p tcp -j SNAT --to 192.0.0.0:18433-20480
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.9 -p udp -j SNAT --to 192.0.0.0:18433-20480
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 18433:20480 -j DNAT --to 100.64.0.9
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 18433:20480 -j DNAT --to 100.64.0.9
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.10 -p tcp -j SNAT --to 192.0.0.0:20481-22528
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.10 -p udp -j SNAT --to 192.0.0.0:20481-22528
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 20481:22528 -j DNAT --to 100.64.0.10
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 20481:22528 -j DNAT --to 100.64.0.10
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.11 -p tcp -j SNAT --to 192.0.0.0:22529-24576
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.11 -p udp -j SNAT --to 192.0.0.0:22529-24576
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p tcp --dport 22529:24576 -j DNAT --to 100.64.0.11
 
 
 
/sbin/iptables -t nat -A CGNATIN_0 -d 192.0.0.0 -p udp --dport 22529:24576 -j DNAT --to 100.64.0.11
 
 
 
/sbin/iptables -t nat -A CGNATOUT_0 -s 100.64.0.12 -p tcp -j SNAT --to 192.0.0.0:24577-26624
 
 
 
/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'''
 
 
 
=== Abaixo as regras do cgnat-bras1.conf - formato nf_tables ===
 
add chain ip nat CGNATOUT_0
 
 
 
add chain ip nat CGNATIN_0
 
 
 
flush chain ip nat CGNATOUT_0
 
 
 
flush chain ip nat CGNATIN_0
 
 
 
add rule ip nat CGNATOUT_0 ip protocol tcp ip saddr 100.64.0.0 counter snat to 192.0.0.0:1-2048
 
 
 
add rule ip nat CGNATOUT_0 ip protocol udp ip saddr 100.64.0.0 counter snat to 192.0.0.0:1-2048
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 tcp dport 1-2048 counter dnat to 100.64.0.0
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 udp dport 1-2048 counter dnat to 100.64.0.0
 
 
 
add rule ip nat CGNATOUT_0 ip protocol tcp ip saddr 100.64.0.1 counter snat to 192.0.0.0:2049-4096
 
 
 
add rule ip nat CGNATOUT_0 ip protocol udp ip saddr 100.64.0.1 counter snat to 192.0.0.0:2049-4096
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 tcp dport 2049-4096 counter dnat to 100.64.0.1
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 udp dport 2049-4096 counter dnat to 100.64.0.1
 
 
 
add rule ip nat CGNATOUT_0 ip protocol tcp ip saddr 100.64.0.2 counter snat to 192.0.0.0:4097-6144
 
 
 
add rule ip nat CGNATOUT_0 ip protocol udp ip saddr 100.64.0.2 counter snat to 192.0.0.0:4097-6144
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 tcp dport 4097-6144 counter dnat to 100.64.0.2
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 udp dport 4097-6144 counter dnat to 100.64.0.2
 
 
 
add rule ip nat CGNATOUT_0 ip protocol tcp ip saddr 100.64.0.3 counter snat to 192.0.0.0:6145-8192
 
 
 
add rule ip nat CGNATOUT_0 ip protocol udp ip saddr 100.64.0.3 counter snat to 192.0.0.0:6145-8192
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 tcp dport 6145-8192 counter dnat to 100.64.0.3
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 udp dport 6145-8192 counter dnat to 100.64.0.3
 
 
 
add rule ip nat CGNATOUT_0 ip protocol tcp ip saddr 100.64.0.4 counter snat to 192.0.0.0:8193-10240
 
 
 
add rule ip nat CGNATOUT_0 ip protocol udp ip saddr 100.64.0.4 counter snat to 192.0.0.0:8193-10240
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 tcp dport 8193-10240 counter dnat to 100.64.0.4
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 udp dport 8193-10240 counter dnat to 100.64.0.4
 
 
 
add rule ip nat CGNATOUT_0 ip protocol tcp ip saddr 100.64.0.5 counter snat to 192.0.0.0:10241-12288
 
 
 
add rule ip nat CGNATOUT_0 ip protocol udp ip saddr 100.64.0.5 counter snat to 192.0.0.0:10241-12288
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 tcp dport 10241-12288 counter dnat to 100.64.0.5
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 udp dport 10241-12288 counter dnat to 100.64.0.5
 
 
 
add rule ip nat CGNATOUT_0 ip protocol tcp ip saddr 100.64.0.6 counter snat to 192.0.0.0:12289-14336
 
 
 
add rule ip nat CGNATOUT_0 ip protocol udp ip saddr 100.64.0.6 counter snat to 192.0.0.0:12289-14336
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 tcp dport 12289-14336 counter dnat to 100.64.0.6
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 udp dport 12289-14336 counter dnat to 100.64.0.6
 
 
 
add rule ip nat CGNATOUT_0 ip protocol tcp ip saddr 100.64.0.7 counter snat to 192.0.0.0:14337-16384
 
 
 
add rule ip nat CGNATOUT_0 ip protocol udp ip saddr 100.64.0.7 counter snat to 192.0.0.0:14337-16384
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 tcp dport 14337-16384 counter dnat to 100.64.0.7
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 udp dport 14337-16384 counter dnat to 100.64.0.7
 
 
 
add rule ip nat CGNATOUT_0 ip protocol tcp ip saddr 100.64.0.8 counter snat to 192.0.0.0:16385-18432
 
 
 
add rule ip nat CGNATOUT_0 ip protocol udp ip saddr 100.64.0.8 counter snat to 192.0.0.0:16385-18432
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 tcp dport 16385-18432 counter dnat to 100.64.0.8
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 udp dport 16385-18432 counter dnat to 100.64.0.8
 
 
 
add rule ip nat CGNATOUT_0 ip protocol tcp ip saddr 100.64.0.9 counter snat to 192.0.0.0:18433-20480
 
 
 
add rule ip nat CGNATOUT_0 ip protocol udp ip saddr 100.64.0.9 counter snat to 192.0.0.0:18433-20480
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 tcp dport 18433-20480 counter dnat to 100.64.0.9
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 udp dport 18433-20480 counter dnat to 100.64.0.9
 
 
 
add rule ip nat CGNATOUT_0 ip protocol tcp ip saddr 100.64.0.10 counter snat to 192.0.0.0:20481-22528
 
 
 
add rule ip nat CGNATOUT_0 ip protocol udp ip saddr 100.64.0.10 counter snat to 192.0.0.0:20481-22528
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 tcp dport 20481-22528 counter dnat to 100.64.0.10
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 udp dport 20481-22528 counter dnat to 100.64.0.10
 
 
 
add rule ip nat CGNATOUT_0 ip protocol tcp ip saddr 100.64.0.11 counter snat to 192.0.0.0:22529-24576
 
 
 
add rule ip nat CGNATOUT_0 ip protocol udp ip saddr 100.64.0.11 counter snat to 192.0.0.0:22529-24576
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 tcp dport 22529-24576 counter dnat to 100.64.0.11
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 udp dport 22529-24576 counter dnat to 100.64.0.11
 
 
 
add rule ip nat CGNATOUT_0 ip protocol tcp ip saddr 100.64.0.12 counter snat to 192.0.0.0:24577-26624
 
 
 
add rule ip nat CGNATOUT_0 ip protocol udp ip saddr 100.64.0.12 counter snat to 192.0.0.0:24577-26624
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 tcp dport 24577-26624 counter dnat to 100.64.0.12
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 udp dport 24577-26624 counter dnat to 100.64.0.12
 
 
 
add rule ip nat CGNATOUT_0 ip protocol tcp ip saddr 100.64.0.13 counter snat to 192.0.0.0:26625-28672
 
 
 
add rule ip nat CGNATOUT_0 ip protocol udp ip saddr 100.64.0.13 counter snat to 192.0.0.0:26625-28672
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 tcp dport 26625-28672 counter dnat to 100.64.0.13
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 udp dport 26625-28672 counter dnat to 100.64.0.13
 
 
 
add rule ip nat CGNATOUT_0 ip protocol tcp ip saddr 100.64.0.14 counter snat to 192.0.0.0:28673-30720
 
 
 
add rule ip nat CGNATOUT_0 ip protocol udp ip saddr 100.64.0.14 counter snat to 192.0.0.0:28673-30720
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 tcp dport 28673-30720 counter dnat to 100.64.0.14
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 udp dport 28673-30720 counter dnat to 100.64.0.14
 
 
 
add rule ip nat CGNATOUT_0 ip protocol tcp ip saddr 100.64.0.15 counter snat to 192.0.0.0:30721-32768
 
 
 
add rule ip nat CGNATOUT_0 ip protocol udp ip saddr 100.64.0.15 counter snat to 192.0.0.0:30721-32768
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 tcp dport 30721-32768 counter dnat to 100.64.0.15
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 udp dport 30721-32768 counter dnat to 100.64.0.15
 
 
 
add rule ip nat CGNATOUT_0 ip protocol tcp ip saddr 100.64.0.16 counter snat to 192.0.0.0:32769-34816
 
 
 
add rule ip nat CGNATOUT_0 ip protocol udp ip saddr 100.64.0.16 counter snat to 192.0.0.0:32769-34816
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 tcp dport 32769-34816 counter dnat to 100.64.0.16
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 udp dport 32769-34816 counter dnat to 100.64.0.16
 
 
 
add rule ip nat CGNATOUT_0 ip protocol tcp ip saddr 100.64.0.17 counter snat to 192.0.0.0:34817-36864
 
 
 
add rule ip nat CGNATOUT_0 ip protocol udp ip saddr 100.64.0.17 counter snat to 192.0.0.0:34817-36864
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 tcp dport 34817-36864 counter dnat to 100.64.0.17
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 udp dport 34817-36864 counter dnat to 100.64.0.17
 
 
 
add rule ip nat CGNATOUT_0 ip protocol tcp ip saddr 100.64.0.18 counter snat to 192.0.0.0:36865-38912
 
 
 
add rule ip nat CGNATOUT_0 ip protocol udp ip saddr 100.64.0.18 counter snat to 192.0.0.0:36865-38912
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 tcp dport 36865-38912 counter dnat to 100.64.0.18
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 udp dport 36865-38912 counter dnat to 100.64.0.18
 
 
 
add rule ip nat CGNATOUT_0 ip protocol tcp ip saddr 100.64.0.19 counter snat to 192.0.0.0:38913-40960
 
 
 
add rule ip nat CGNATOUT_0 ip protocol udp ip saddr 100.64.0.19 counter snat to 192.0.0.0:38913-40960
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 tcp dport 38913-40960 counter dnat to 100.64.0.19
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 udp dport 38913-40960 counter dnat to 100.64.0.19
 
 
 
add rule ip nat CGNATOUT_0 ip protocol tcp ip saddr 100.64.0.20 counter snat to 192.0.0.0:40961-43008
 
 
 
add rule ip nat CGNATOUT_0 ip protocol udp ip saddr 100.64.0.20 counter snat to 192.0.0.0:40961-43008
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 tcp dport 40961-43008 counter dnat to 100.64.0.20
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 udp dport 40961-43008 counter dnat to 100.64.0.20
 
 
 
add rule ip nat CGNATOUT_0 ip protocol tcp ip saddr 100.64.0.21 counter snat to 192.0.0.0:43009-45056
 
 
 
add rule ip nat CGNATOUT_0 ip protocol udp ip saddr 100.64.0.21 counter snat to 192.0.0.0:43009-45056
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 tcp dport 43009-45056 counter dnat to 100.64.0.21
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 udp dport 43009-45056 counter dnat to 100.64.0.21
 
 
 
add rule ip nat CGNATOUT_0 ip protocol tcp ip saddr 100.64.0.22 counter snat to 192.0.0.0:45057-47104
 
 
 
add rule ip nat CGNATOUT_0 ip protocol udp ip saddr 100.64.0.22 counter snat to 192.0.0.0:45057-47104
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 tcp dport 45057-47104 counter dnat to 100.64.0.22
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 udp dport 45057-47104 counter dnat to 100.64.0.22
 
 
 
add rule ip nat CGNATOUT_0 ip protocol tcp ip saddr 100.64.0.23 counter snat to 192.0.0.0:47105-49152
 
 
 
add rule ip nat CGNATOUT_0 ip protocol udp ip saddr 100.64.0.23 counter snat to 192.0.0.0:47105-49152
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 tcp dport 47105-49152 counter dnat to 100.64.0.23
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 udp dport 47105-49152 counter dnat to 100.64.0.23
 
 
 
add rule ip nat CGNATOUT_0 ip protocol tcp ip saddr 100.64.0.24 counter snat to 192.0.0.0:49153-51200
 
 
 
add rule ip nat CGNATOUT_0 ip protocol udp ip saddr 100.64.0.24 counter snat to 192.0.0.0:49153-51200
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 tcp dport 49153-51200 counter dnat to 100.64.0.24
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 udp dport 49153-51200 counter dnat to 100.64.0.24
 
 
 
add rule ip nat CGNATOUT_0 ip protocol tcp ip saddr 100.64.0.25 counter snat to 192.0.0.0:51201-53248
 
 
 
add rule ip nat CGNATOUT_0 ip protocol udp ip saddr 100.64.0.25 counter snat to 192.0.0.0:51201-53248
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 tcp dport 51201-53248 counter dnat to 100.64.0.25
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 udp dport 51201-53248 counter dnat to 100.64.0.25
 
 
 
add rule ip nat CGNATOUT_0 ip protocol tcp ip saddr 100.64.0.26 counter snat to 192.0.0.0:53249-55296
 
 
 
add rule ip nat CGNATOUT_0 ip protocol udp ip saddr 100.64.0.26 counter snat to 192.0.0.0:53249-55296
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 tcp dport 53249-55296 counter dnat to 100.64.0.26
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 udp dport 53249-55296 counter dnat to 100.64.0.26
 
 
 
add rule ip nat CGNATOUT_0 ip protocol tcp ip saddr 100.64.0.27 counter snat to 192.0.0.0:55297-57344
 
 
 
add rule ip nat CGNATOUT_0 ip protocol udp ip saddr 100.64.0.27 counter snat to 192.0.0.0:55297-57344
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 tcp dport 55297-57344 counter dnat to 100.64.0.27
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 udp dport 55297-57344 counter dnat to 100.64.0.27
 
 
 
add rule ip nat CGNATOUT_0 ip protocol tcp ip saddr 100.64.0.28 counter snat to 192.0.0.0:57345-59392
 
 
 
add rule ip nat CGNATOUT_0 ip protocol udp ip saddr 100.64.0.28 counter snat to 192.0.0.0:57345-59392
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 tcp dport 57345-59392 counter dnat to 100.64.0.28
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 udp dport 57345-59392 counter dnat to 100.64.0.28
 
 
 
add rule ip nat CGNATOUT_0 ip protocol tcp ip saddr 100.64.0.29 counter snat to 192.0.0.0:59393-61440
 
 
 
add rule ip nat CGNATOUT_0 ip protocol udp ip saddr 100.64.0.29 counter snat to 192.0.0.0:59393-61440
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 tcp dport 59393-61440 counter dnat to 100.64.0.29
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 udp dport 59393-61440 counter dnat to 100.64.0.29
 
 
 
add rule ip nat CGNATOUT_0 ip protocol tcp ip saddr 100.64.0.30 counter snat to 192.0.0.0:61441-63488
 
 
 
add rule ip nat CGNATOUT_0 ip protocol udp ip saddr 100.64.0.30 counter snat to 192.0.0.0:61441-63488
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 tcp dport 61441-63488 counter dnat to 100.64.0.30
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 udp dport 61441-63488 counter dnat to 100.64.0.30
 
 
 
add rule ip nat CGNATOUT_0 ip protocol tcp ip saddr 100.64.0.31 counter snat to 192.0.0.0:63489-65535
 
 
 
add rule ip nat CGNATOUT_0 ip protocol udp ip saddr 100.64.0.31 counter snat to 192.0.0.0:63489-65535
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 tcp dport 63489-65535 counter dnat to 100.64.0.31
 
 
 
add rule ip nat CGNATIN_0 ip daddr 192.0.0.0 udp dport 63489-65535 counter dnat to 100.64.0.31
 
 
 
add rule ip nat CGNATOUT_0 counter snat to 192.0.0.0
 
 
 
'''add rule ip nat CGNATOUT ip saddr 100.64.0.0/27 counter jump CGNATOUT_0'''
 
 
 
'''add rule ip nat CGNATIN ip daddr 192.0.0.0/32 counter jump CGNATIN_0'''
 
 
 
As 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.
 
 
 
Live que fiz sobre CGNAT com NFTables: https://www.youtube.com/watch?v=5uOFtkplDts
 
 
 
PDF da Live CGNAT com NFTables: https://debianbrasil.gitlab.io/FiqueEmCasaUseDebian/arquivos/2020-06-03-cgnat-com-nftables.pdf
 
 
 
Qualquer dúvida sobre o artigo só me procurar:
 
 
 
Autor: [https://wiki.brasilpeeringforum.org/w/Usu%C3%A1rio:Gondim Marcelo Gondim]
 
[[Categoria:Roteamento]]
 
[[Categoria:Infraestrutura]]
 

Edição das 13h59min de 9 de maio de 2023

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

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:

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, abaixo o código do script cgnat-nft.py:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''# -*- coding: latin-1 -*-'''

'''
GRCN is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
#
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
#
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
'''

import os
import sys
import time
import ipaddress

__author__ = 'Beiriz'
__version__= 4.001
__datebegin__= "27/07/2020 (31/03/2023)"
__com1__ = "add rule ip nat"

#-----------------------------------------------------------------------
fazer_regras_in = False #Este valor deve ser alterado para True caso haja interesse de gerar também as regras de CGNAT no sentido IN: 'fazer_regras_in = True'. OBS: CGNAT do tipo OUT sempre serão geradas.
indice = 0
txt_publico = ""
txt_privado = ""
masc_subrede_privada = 0 #mascara da subrede de IPs privados que serão atendidos por 1 IP público
qt_ips_publicos = 0 #Quantidade de IPs públicos na rede informada
qt_ips_privados = 0 #Quantidade de IPs privados na rede informada
qt_ips_privados_por_ip_publico = 0 #Quantos IPs privados vão sair por um único IP público ( A relação PRI/PUB)
qt_portas_por_ip = 2016 #quantidade de portas que serão reservadas por IP privado.
numero_porta_incial = 1024
numero_porta_final = 65535
relacao = '1/32'
qt_total_portas = (numero_porta_final-(numero_porta_incial-1))
#As 2 confs abaixo trabalham em conjunto para ajustar o range total de portas de cada IP público
relacao_portas = {'1/4':16128, '1/8':8064, '1/16':4032, '1/32':2016, '1/64':1008, '1/128':504, '1/256':252}
relacao_mascara = {'1/4':30, '1/8':29, '1/16':28, '1/32':27, '1/64':26, '1/128':25, '1/256':24}
relacao_ips_masq = {'1/4':4, '1/8':8, '1/16':16, '1/32':32, '1/64':64, '1/128':128, '1/256':256}
#-------------------------------------------------

os.system('cls' if os.name == 'nt' else 'clear')
titulo = "GRCN - Gerador de Regras CGNAT em nftables - %s - v%s - %s" % (__author__, __version__,__datebegin__)
print("#"*100)
print("    %s" %(titulo))
print("#"*100)

#------------------------------------------------- Parâmetros informados / manual:

try:
  #Indice:
  indice = int(sys.argv[1])
  #Blocos:
  txt_publico = str(sys.argv[2])
  txt_privado = str(sys.argv[3])
  #RELAÇÃO_IP_PUBLICO_X_CLIENTE
  try:
    relacao = str(sys.argv[4])
  except:
    relacao = '1/32'
  finally:
    qt_portas_por_ip = relacao_portas[relacao]
    print("\n\t[ Ídice inicial: %i | público: %s | privado: %s | %i portas/IP (%s)]\t\n" % (indice, txt_publico, txt_privado, qt_portas_por_ip, relacao))

except:
  print("\nErro! Informe pelo menos os parâmetros obrigatórios deste script.\n")
  print("## Manual de Instruções:")
  print("\n###### Exemplo básico (1/32):\n")
  print("```")
  print("%spython %s <INDICE> <BLOCO_PUBLICO> <BLOCO_PRIVADO>" %(' '*6, sys.argv[0]))
  print("%spython %s 0 192.0.2.0/24 100.69.0.0/22" %(' '*6, sys.argv[0]))
  print("```")
  print("\n###### Exemplo avançado:\n")
  print("```")
  print("%s python %s <INDICE> <BLOCO_PUBLICO> <BLOCO_PRIVADO> <RELAÇÃO_IP_PUBLICO_X_CLIENTE>(OPCIONAL)" %(' '*6, sys.argv[0]))
  print("%s python %s 0 192.0.2.0/24 100.69.0.0/22 1/16")
  print("```")
  print("\n###### Parâmetros:\n")
  print("* INDICE: Inteiro >=0 que vai ser o sufixo do nome das regras únicas. Exemplo *CGNATOUT_XXX*;\n")
  print("* BLOCO_PUBLICO: É o bloco de IPs públicos por onde o bloco CGNAT vai sair para a internet. Exemplo: *192.0.2.0/24*\n")
  print("* BLOCO_PRIVADO: É o bloco de IPs privados que serão entregues ao assinante.\n")
  print("* RELAÇÃO_IP_PUBLICO_X_CLIENTE (OPCIONAL):")
  print("    - 1/4   - 16128 portas por IP;")
  print("    - 1/8   - 8064 portas por IP;")
  print("    - 1/16  - 4032 portas por IP;")
  print("    - 1/32  - 2016 portas por IP (Configuração padrão, quando este último parâmetro não é informado);")
  print("    - 1/64  - 1008 portas por IP (Atenção! Não recomendado pelas boas práticas);")
  print("    - 1/128 - 504 portas por IP (Atenção! Não recomendado pelas boas práticas);")
  print("    - 1/256 - 252 portas por IP (Atenção! Não recomendado pelas boas práticas);")
  print("\n####### Observações:\n")
  print("* Este script vai dividir o <BLOCO_PRIVADO> em N sub-redes privadas. Cada sub-rede privada sai por um único IP público e dela, cada IP privado sai com uma fração das portas de seu IP público.\n")
  print("* Se <BLOCO_PUBLICO> for um /24 e <BLOCO_PRIVADO> um /19 e a relação for 1/32, serão colocados exatamente 32 IPs privados (assinantes) atrás de um IP público. Cada IP privado vai sair com 2016 portas de seu IP público (65535-1023)/32. O famoso *1:32*.\n")
  print("\n")
  print("\nATENÇÃO! Por boas práticas, o script PAROU de gerar as regras CGNAT do tipo IN. Caso queira continuar gerando-as, edite o cgnat-nft.py, alterando o valor *fazer_regras_in* de *False* para *True*;")
  print("\nFIM deste manual!\n")
  exit(0)
#exit(0)
#------------------------------------------------- trata os parâmetros informados:

try:
  if sys.version_info >= (3,0):
    rede_publica = ipaddress.ip_network(str(txt_publico), strict=False)
    rede_privada = ipaddress.ip_network(str(txt_privado), strict=False)
  else:
    rede_publica = ipaddress.ip_network(unicode(txt_publico), strict=False)
    rede_privada = ipaddress.ip_network(unicode(txt_privado), strict=False)
  qt_ips_publicos = int(rede_publica.num_addresses)
  qt_ips_privados = int(rede_privada.num_addresses)
  qt_ips_privados_por_ip_publico = int( qt_ips_privados / qt_ips_publicos )
  # Nome arquivo de destino
  nome_arquivo_destino = ("cgnat-%i-%i.conf" % (indice,(indice + qt_ips_publicos - 1)))
  # calcula a máscara das subnets privadas baseado na relação PRI/PUB:
  subnets_privadas = list(rede_privada.subnets(new_prefix=relacao_mascara[relacao]))
except:
  print("\nErro! Informe parâmetros válidos para este script:\n\nRespeite a relação de IP público x IP privado: 1:32, 1:16, 1:8, etc\n\nEncerrando!\n")
  exit(0)

if (qt_ips_publicos * relacao_ips_masq[relacao]) > qt_ips_privados:
   print("\nErro! Quantidade de IPs privados insuficiente!")
   exit(0)

print(" - Índice das regras: %i;" % (indice))
print(" - Rede pública: %s (%i IPs);" % (txt_publico,qt_ips_publicos))
print(" - Rede privada: %s (%i IPs);" % (txt_privado,qt_ips_privados))
print(" - Quantidade de IPs privados por IP público: %i (%i sub-redes /%i);" % (qt_ips_privados_por_ip_publico, qt_ips_publicos, relacao_mascara[relacao]))
print(" - Total de portas públicas: %i;" % (qt_total_portas))
print(" - Portas por IP privado: %i;" % (qt_portas_por_ip))
print(" - Arquivo de destino (conf): '%s';" % (nome_arquivo_destino))
print("\n")

if fazer_regras_in:
  print("\nATENÇÃO!\n  Variável fazer_regras_in=True\n  Mesmo não sendo boas práticas, SERÃO geradas regras de CGNAT do tipo IN!\n")

#------------------------------------------------- Abre o arquivo onde as regras serão armazenadas (destino):
try:
  caminho_deste_script = os.path.dirname(os.path.realpath(__file__))+'/'
  arquivo_destino = open(caminho_deste_script+nome_arquivo_destino, "w")
except (OSError, IOError) as e:
  print ("\nErro!\nFalha ao abrir a escrita do arquivo onde as regras serão armazenadas (destino)")
  sys.exit(1)

arquivo_destino.write("# %s\n" %(titulo))
arquivo_destino.write("# - blocos %s -> %s;\n# - /%i de IPs privados / IP público;\n# - %i portas / IP privado;\n" %(
  txt_privado,
  txt_publico,
  masc_subrede_privada,
  qt_portas_por_ip
))

#-------------------------------------------------------------------------- principal

if sys.version_info >= (3,0):
  input("Tecle [ENTER]...")
else:
  raw_input("Tecle [ENTER]...")
momento_incial = time.time()

#exit(0)

print("\n")
indice_subnet_privada = 0
for ip_publico in rede_publica:
  arquivo_destino.write("# %s #INDICE %i / IP PUBLICO %s\n" % ('-' * 40, indice, str(ip_publico)))
  arquivo_destino.write("add chain ip nat CGNATOUT_%i\n" % (indice))
  if fazer_regras_in:
    arquivo_destino.write("add chain ip nat CGNATIN_%i\n" % (indice))
  arquivo_destino.write("flush chain ip nat CGNATOUT_%i\n" % (indice))
  if fazer_regras_in:
    arquivo_destino.write("flush chain ip nat CGNATIN_%i\n" % (indice))
  #print(subnets_privadas)
  #print(indice_subnet_privada)
  subnet = subnets_privadas[indice_subnet_privada]
  # Zera o range de portas para o prox IP publico
  porta_ini = numero_porta_incial
  ###porta_fim = qt_portas_por_ip
  porta_fim = (numero_porta_incial + (qt_portas_por_ip -1))
  print("%s INDICE=%i - IP_PUBLICO=%s -> SUBNET_PRIVADA_%i=%s" % ("=" * 40, indice, str(ip_publico), (indice_subnet_privada+1), str(subnet)))
  for ip_privado in ipaddress.ip_network(subnet):
    #trp = "1-2048"
    trp = "%i-%i" % (porta_ini,porta_fim)
    print("%s IP PRIVADO %s:%s" %("-"*60,str(ip_privado),trp))
    #Regras para cada IP privado
    arquivo_destino.write("%s CGNATOUT_%i ip protocol tcp ip saddr %s counter snat to %s:%s\n" % (
      __com1__,
      indice,
      str(ip_privado),
      str(ip_publico),
      trp
    ))
    arquivo_destino.write("%s CGNATOUT_%i ip protocol udp ip saddr %s counter snat to %s:%s\n" % (
      __com1__,
      indice,
      str(ip_privado),
      str(ip_publico),
      trp
    ))

    if fazer_regras_in:
      arquivo_destino.write("%s CGNATIN_%i ip daddr %s tcp dport %s counter dnat to %s\n" % (
        __com1__,
        indice,
        str(ip_publico),
        trp,
        str(ip_privado)
      ))
      arquivo_destino.write("%s CGNATIN_%i ip daddr %s udp dport %s counter dnat to %s\n" % (
        __com1__,
        indice,
        str(ip_publico),
        trp,
        str(ip_privado)
      ))

    #incrementa o range de portas para o próximo IP privado
    porta_ini += qt_portas_por_ip
    porta_fim += qt_portas_por_ip
    if porta_fim > numero_porta_final:
      porta_fim = numero_porta_final
  #regras finais para a subrede x IP público
  #arquivo_destino.write("\n")
  arquivo_destino.write("%s CGNATOUT_%i counter snat to %s\n" % (
    __com1__,
    indice,
    str(ip_publico)
  ))
  arquivo_destino.write("%s CGNATOUT ip saddr %s counter jump CGNATOUT_%i\n" % (
    __com1__,
    str(subnet),
    indice
  ))
  if fazer_regras_in:
    arquivo_destino.write("%s CGNATIN ip daddr %s/32 counter jump CGNATIN_%i\n" % (
      __com1__,
      str(ip_publico),
      indice
    ))
  #for ip_privado in subnet.subnets(new_prefix=32):
  #  print("    %s" % (str(ip_privado)))
  #arquivo_destino.write("\n")
  indice+=1
  indice_subnet_privada+=1

#-------------------------------------------------------------------------- final

#Fecha o arquivo onde as regras serão armazenadas (destino):
try:
  arquivo_destino.close()
except (OSError, IOError) as e:
  print ("\nErro!\nFalha ao salvar o arquivo onde as regras serão armazenadas (destino)")
  sys.exit(1)

print("\nFIM!\n\nAs regras foram geradas no arquivo:\n%s\n\nDuração: %.3f segundos" %(
  caminho_deste_script + nome_arquivo_destino,
  (time.time()-momento_incial)
))

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

# 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
Arquivo:Grcn.png
1022x1022px

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

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.

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

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. Meus contatos podem ser vistos aqui.