Dia 28: Firewall com nftables — Regras, Sets, Chains e NAT

O nftables é o sucessor oficial do iptables no kernel Linux, disponível por predefinição desde o kernel 3.13 e adoptado como backend principal em Debian, Ubuntu, RHEL, Fedora e Arch Linux. Este artigo explora em profundidade a sintaxe, sets, chains, NAT, logging e optimização — tudo o que precisas para substituir o iptables com confiança.

ℹ O nftables unifica iptables, ip6tables, arptables e ebtables numa única ferramenta — sintaxe mais limpa e performance superior.

Neste artigo

1. Introdução ao nftables

O nftables foi desenhado para resolver as limitações do iptables: sintaxe fragmentada (iptables, ip6tables, arptables, ebtables — cada um um binário separado), impossibilidade de reordenar regras sem remover e re-adicionar, falta de estruturas de dados nativas (sets, maps) e performance sub-óptima em conjunto de regras extensas.

O kernel Linux 5.15+ já não carrega os módulos de compatibilidade do iptables por predefinição em muitas distribuições. O comando iptables-translate converte regras antigas para a sintaxe nftables, facilitando a migração.

Arquitectura: Netfilter + nf_tables

O nftables assenta no subsistema Netfilter do kernel, que fornece os hooks de rede (prerouting, input, forward, saída, postrouting). A diferença é que o nftables usa uma máquina virtual byte-code no kernel (nf_tables) em vez dos matches/targets compilados do iptables. Isto significa que novas funcionalidades não exigem compilar módulos do kernel — basta actualizar o utilitário nft no espaço de utilizador.

Feature iptables nftables
Famílias Separadas (ip/ip6/arp/eb) Unificadas (inet/ip/ip6/arp/bridge/netdev)
Sets/Maps ipset (externo) Nativo na linguagem
Reordenar regras Não (delete + insert) Sim (insert at position)
Performance Linear em chains longas Sets com lookup O(1) ou O(log n)
Sintaxe -extensões por match Linguagem consistente

Para mais detalhes sobre a arquitectura Netfilter, consulta a wiki oficial do nftables ou o guia do Arch Wiki sobre nftables.

2. Sintaxe e Estrutura

A estrutura do nftables segue uma hierarquia clara: tablechainrule. Uma tabela pertence a uma família (inet, ip, ip6, arp, bridge, netdev), contém chains, e cada chain contém regras.

Famílias disponíveis

Família Âmbito
inet IPv4 + IPv6 (recomendada para a maioria dos casos)
ip Apenas IPv4 (equivalente ao iptables)
ip6 Apenas IPv6 (equivalente ao ip6tables)
arp Tráfego ARP (equivalente ao arptables)
bridge Tráfego na bridge (equivalente ao ebtables)
netdev Por interface (ingress only, muito cedo no pipeline)

A família inet é a recomendada para firewalls de hospedeiro porque processa simultaneamente IPv4 e IPv6, eliminando a duplicação de regras que era inevitável com iptables + ip6tables.

Tabela base de filtro

O exemplo seguinte mostra um firewall de host completo com política DROP na chain input, que aceita tráfego estabelecido, loopback, SSH, HTTP/HTTPS e ICMPv6 essencial:

# Tabela base de filtro
table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;
        ct state established,related accept
        iif lo accept
        tcp dport { 22, 80, 443 } accept
        icmpv6 type { echo-request, nd-neighbor-solicit } accept
    }
    chain forward {
        type filter hook forward priority 0; policy drop;
    }
    chain output {
        type filter hook output priority 0; policy accept;
    }
}

Cada chain base declara três elementos obrigatórios: o tipo (type filter), o hook onde se liga no Netfilter (hook input) e a política predefinida quando nenhuma regra faz match (policy drop).

⚠ A política predefinida deve ser DROP — um firewall com política ACCEPT é quase o mesmo que não ter firewall.

Comandos essenciais

# Carregar regras
nft -f /etc/nftables.conf
nft list ruleset

# Adicionar tabela
nft add table inet filter

# Adicionar chain
nft add chain inet filter input '{ type filter hook input priority 0; policy drop; }'

# Adicionar regra individual
nft add rule inet filter input tcp dport 22 accept

# Apagar regra por handle (primeiro descobrir o handle)
nft -a list chain inet filter input
nft delete rule inet filter input handle 15

# Flush (limpar todas as regras)
nft flush ruleset

O -a (alias --handle) mostra o identificador interno de cada regra — necessário para a apagar individualmente. Sem -a, não há forma de referenciar uma regra específica.

3. Sets e Maps

Os sets são akiller feature do nftables — permitem agrupar endereços, portos, redes ou outros valores numa estrutura de dados nativa com lookup eficiente. No iptables, isto exigia o ipset como ferramenta externa; no nftables é parte da linguagem.

Sets simples

# Sets
table inet filter {
    set blacklist { type ipv4_addr; flags interval; }
    chain input {
        ip saddr @blacklist drop
    }
}

A flag interval permite adicionar intervalos CIDR (ex: 192.168.0.0/16) e não apenas endereços individuais. Para adicionar elementos dinamicamente:

# Adicionar endereços ao set
nft add element inet filter blacklist { 203.0.113.50 }
nft add element inet filter blacklist { 198.51.100.0/24 }

# Remover endereço
nft delete element inet filter blacklist { 203.0.113.50 }

# Listar conteúdo do set
nft list set inet filter blacklist

Sets anónimos inline

Para grupos pequenos que não mudam, usa-se um set anónimo directamente na regra — sem declarar um set nomeado. A sintaxe usa chavetas:

# Set anónimo de portos
tcp dport { 22, 80, 443, 8080-8090 } accept

# Set anónimo de endereços
ip saddr { 10.0.0.1, 10.0.0.2, 192.168.1.0/24 } accept

# Set anónimo de tipos ICMPv6
icmpv6 type { echo-request, nd-neighbor-solicit, nd-router-advert } accept

Maps (dicionários chave-valor)

Os maps são sets que mapeiam uma chave para um valor. Permitem, por exemplo, definir o porto de destino consoante o endereço de origem — numa única regra em vez de múltiplas regras encadeadas:

# Map: endereço -> porto
table inet filter {
    map portforward {
        type ipv4_addr : inet_service
        elements = { 192.168.1.100 : 8080,
                     192.168.1.101 : 8443 }
    }
    chain prerouting {
        type nat hook prerouting priority -100; policy accept;
        ip daddr 203.0.113.1 dnat tcp dport map @portforward
    }
}

Isto substitui dezenas de regras DNAT individuais por uma única regra que consulta o map. A performance é superior porque o kernel faz um único lookup em vez de avaliar cada regra sequencialmente.

Sets com timeout e contadores

Os sets podem ter timeout (útil para banir IPs temporariamente) e contadores (para monitorizar tráfego por set). A flag timeout remove automaticamente elementos após o período definido:

# Set com timeout de 1 hora e contador
table inet filter {
    set banned {
        type ipv4_addr
        flags interval, timeout
        timeout 1h
        counter
    }
    chain input {
        # Adicionar IP dinamicamente com timeout customizado
        # nft add element inet filter banned { 1.2.3.4 timeout 30m }

        ip saddr @banned drop
    }
}

4. Chains e Prioridades

O nftables tem dois tipos de chains: base chains (ligadas a um hook do Netfilter — processam tráfego real) e regular chains (não ligadas a hooks — usadas como sub-rotinas chamadas por jump ou goto).

Hooks e prioridades

O parâmetro priority determina a ordem de execução quando múltiplas base chains se ligam ao mesmo hook. Prioridades mais baixas executam primeiro. Os valores padrão do Netfilter são:

Hook Prioridade filter Prioridade nat Prioridade mangle
prerouting 0 (filter) -100 (dstnat) -150 (mangle)
input 0 (filter) 100 (srcnat) -150 (mangle)
forward 0 (filter) 100 (srcnat) -150 (mangle)
output 0 (filter) -100 (dstnat) -150 (mangle)
postrouting 100 (srcnat) -150 (mangle)

A ordem correcta é crítica: o NAT de destino (dstnat, prioridade -100) executa antes do filtro (prioridade 0), para que o filtro veja o endereço já traduzido. O NAT de origem (srcnat, prioridade 100) executa depois do filtro, para que o filtro veja o endereço original.

Jump vs Goto

jump chama outra chain e retorna à chain original após o processamento. goto chama outra chain mas não retorna — se a chain chamada não aceitar/dropar, a execução termina. Usa goto para chains finais e jump para chains que podem aceitar ou continuar:

# Regular chains para organizar lógica
table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;
        ct state established,related accept
        iif lo accept
        jump incoming-traffic
    }

    chain incoming-traffic {
        # Se aceitar aqui, retorna à chain input
        tcp dport { 22, 80, 443 } accept
        # Se não fizer match, retorna à input e aplica policy drop
    }
}

5. NAT com nftables

O NAT (Network Address Translation) no nftables usa chains do tipo nat nos hooks prerouting (DNAT) e postrouting (SNAT). Para uma revisão dos conceitos fundamentais de NAT (SNAT, DNAT, PAT), consulta o Dia 10 do curso de redes.

Masquerade (SNAT dinâmico)

O masquerade é usado quando o endereço de saída é dinâmico (DHCP, PPP). Ele detecta automaticamente o endereço da interface de saída. Para um IP fixo, usa-se snat to que é mais eficiente:

# NAT
table ip nat {
    chain postrouting {
        type nat hook postrouting priority 100; policy accept;
        oif eth0 masquerade
    }
    chain prerouting {
        type nat hook prerouting priority -100; policy accept;
        tcp dport 80 dnat to 192.168.1.10:8080
    }
}

SNAT com IP fixo

# SNAT com IP fixo (mais eficiente que masquerade)
table ip nat {
    chain postrouting {
        type nat hook postrouting priority 100; policy accept;
        oif eth0 snat to 203.0.113.1
    }
}

# SNAT com pool de IPs (round-robin)
table ip nat {
    chain postrouting {
        type nat hook postrouting priority 100; policy accept;
        oif eth0 snat to 203.0.113.1-203.0.113.5
    }
}

DNAT com redirecionamento de porto

O redirect é uma forma especial de DNAT que redireciona para a própria máquina (útil para proxies transparentes). O dnat to redireciona para outra máquina:

# Redirect local (proxy transparente)
table ip nat {
    chain prerouting {
        type nat hook prerouting priority -100; policy accept;
        tcp dport 80 redirect to 3128
    }
}

# DNAT para servidor interno
table ip nat {
    chain prerouting {
        type nat hook prerouting priority -100; policy accept;
        iif eth0 tcp dport 443 dnat to 192.168.1.50:443
    }
}

Para uma visão geral dos diferentes tipos de firewalls de rede (incluindo uma comparação iptables vs nftables vs pfSense vs OPNSense), consulta o Dia 11: Firewalls de Rede.

6. Logging e Optimização

Log de tráfego

O nftables usa a acção log para registar pacotes no log do kernel (visível via dmesg ou journalctl -k). Pode-se definir prefixo, nível e grupo:

# Log de pacotes dropados
table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;
        ct state established,related accept
        iif lo accept

        # Log antes de dropar (limitar a 10/min para não flooding)
        log prefix "nft-drop: " level warn group 0 limit rate 10/minute
        drop
    }
}

# Log + accept (para auditoria)
log prefix "nft-accept-ssh: " level info accept

O limit rate é essencial — sem ele, um ataque de flood pode encher o log do kernel em segundos. O prefixo permite filtrar facilmente com journalctl -k | grep nft-drop.

Contadores por regra

Adicionar counter a uma regra conta pacotes e bytes que fazem match. Isto é útil para diagnóstico e para identificar regras inativas:

# Contador em regras individuais
tcp dport 22 counter accept
tcp dport { 80, 443 } counter accept

# Verificar contadores
nft list ruleset -a

# Output:
# tcp dport 22 counter packets 1453 bytes 89247 accept
# tcp dport { 80, 443 } counter packets 0 bytes 0 accept

Optimização de performance

Para firewalls com milhares de regras ou tráfego elevado, a optimização faz diferença entre um firewall que processa 1 Gbps e um que processa 10 Gbps:

Recomendação Impacto
Usar sets em vez de múltiplas regras Lookup O(1) em vez de O(n) linear
Regras mais frequentes primeiro Reduz avaliações desnecessárias
ct state established,related accept no topo 95%+ do tráfego corresponde aqui
Família inet (unifica IPv4+IPv6) Menos chains, menos avaliações
Maps para DNAT/SNAT dinâmico Uma regra em vez de centenas
Limitar log com limit rate Evita degradação por I/O de log

7. Erros Comuns e Lista de Verificação

Erros comuns

Problema Causa Solução
Bloqueado fora do SSH Esqueceu ct state established,related accept Adicionar como primeira regra da chain input
NAT não funciona Falta sysctl net.ipv4.ip_forward=1 Activar forwarding no kernel
Regras não persistem após reboot Não activou o serviço systemd systemctl enable nftables
ICMPv6 bloqueado Esqueceu ND/RA essential Aceitar nd-neighbor-solicit, nd-router-advert
DNS não resolve Chain output com policy drop sem regra DNS Aceitar udp dport 53 na output
Prioridade NAT errada DNAT com prioridade positiva DNAT usa prioridade -100; SNAT usa 100

Lista de verificação pré-produção

Antes de colocar um firewall nftables em produção, verifica cada ponto:

  • ct state established,related accept é a primeira regra da chain input
  • Loopback (iif lo accept) permitido na input e output
  • ICMPv6 essencial aceito (echo-request, nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert)
  • Política DROP na input e forward
  • SSH permitido (idealmente com limit rate para prevenir brute-force)
  • Forwarding activado no kernel se a máquina faz routing/NAT (net.ipv4.ip_forward=1)
  • Serviço nftables activado (systemctl enable nftables) para persistência após reboot
  • Cópia de segurança do ficheiro /etc/nftables.conf antes de alterações
  • Testado com timeout — se SSH remoto, usar at ou systemd-tmpfiles para reverter automaticamente
  • Log de pacotes dropados com limit rate para auditoria

Migração do iptables

Para migrar regras existentes, usa-se o iptables-translate e ip6tables-translate — ferramentas que convertem regras iptables para a sintaxe nftables automaticamente:

# Converter regras iptables para nftables
iptables-translate -A INPUT -p tcp --dport 22 -j ACCEPT
# Output: nft add rule inet filter input tcp dport 22 accept

ip6tables-translate -A INPUT -p tcp --dport 22 -j ACCEPT
# Output: nft add rule ip6 filter input tcp dport 22 accept

# Converter um ficheiro completo
iptables-restore-translate -f regras-iptables.save > regras.nft

# Ver as regras actualmente carregadas no iptables
iptables-save > regras-iptables.save

A conversão automática cobre a maioria dos casos, mas vale a pena revisar o resultado — algumas construções avançadas do iptables (especialmente com módulos pouco comuns) podem não ter equivalente directo. Para comandos básicos de rede Linux que complementam o firewall, consulta o Dia 8: Redes Linux — ip, ss, nmcli e DNS.

Artigos Relacionados