Aula 12: PF Firewall — o firewall nativo e poderoso do FreeBSD
PF Firewall é, sem exagero, um dos componentes mais impressionantes do ecossistema FreeBSD. Originalmente desenvolvido para o OpenBSD e posteriormente portado e aprimorado para o FreeBSD, o PF (Packet Filter) evoluiu de um simples filtro de pacotes para um sistema completo de firewall stateful com capacidade de NAT, redirecionamento de tráfego, balanceamento de carga, QoS (Quality of Service) via ALTQ e gerenciamento dinâmico de regras através de âncoras (anchors). Em nossos projetos na JRT Technology Solutions, utilizamos o PF Firewall diariamente como a primeira linha de defesa em servidores de produção, appliances de segurança e gateways corporativos — e após esta aula, você terá domínio suficiente para fazer o mesmo em seus ambientes.
O que torna o PF Firewall verdadeiramente especial é sua sintaxe elegante e legível, que se afasta do pesadelo de iptables que muitos administradores Linux conhecem. Em vez de cadeias e tabelas separadas, o PF trabalha com um único arquivo de configuração (/etc/pf.conf) onde as regras são avaliadas sequencialmente — a última regra que corresponde ao pacote é a que prevalece. Essa abordagem, combinada com a capacidade de usar listas (lists), macros, tabelas e funções de redirecionamento, permite construir políticas de firewall complexas que permanecem compreensíveis e manteníveis mesmo em ambientes com centenas de regras.
Nesta aula, você partirá do zero absoluto em PF Firewall até a construção de um conjunto de regras funcional e seguro para um servidor típico. Vamos cobrir desde a ativação do módulo no kernel, passando pela sintaxe fundamental, criação de regras de filtragem stateful, configuração de NAT e redirecionamento de portas, até técnicas avançadas como uso de tabelas para bloqueio dinâmico e criação de âncoras para gerenciamento modular. Cada comando será mostrado na íntegra, cada arquivo de configuração será exibido por completo e cada saída de verificação será documentada — sem resumos, sem atalhos, sem “etc.”.
Ao final desta aula, você será capaz de proteger um servidor FreeBSD com regras de firewall que filtram tráfego por interface, protocolo, porta, endereço IP de origem e destino, além de implementar NAT para redes internas, redirecionar portas para serviços em containers ou jails, e criar listas dinâmicas de bloqueio para mitigar ataques de força bruta em tempo real. Também aprenderá a depurar problemas de conectividade usando as ferramentas nativas do PF como pfctl e a interpretar os logs do pflog. Prepare seu terminal, pois esta será uma aula intensiva e 100% prática — o tipo de conteúdo que transforma um administrador de sistemas comum em um especialista em segurança de redes com FreeBSD.
O que você vai aprender nesta aula
- Compreender a arquitetura e a filosofia de design do PF Firewall no FreeBSD
- Ativar o módulo PF no kernel e configurar a inicialização automática via /etc/rc.conf
- Dominar a sintaxe fundamental de regras: filtragem por interface, protocolo, endereço, porta, direção e estado
- Criar e utilizar macros e listas para tornar o /etc/pf.conf limpo e reutilizável
- Construir regras stateful robustas com keep state, modulate state e synproxy state
- Implementar NAT de saída (nat-to) e redirecionamento de portas (rdr-to) para cenários reais
- Trabalhar com tabelas para bloqueio dinâmico de endereços IP sem recarregar o firewall
- Utilizar âncoras (anchors) para gerenciar conjuntos de regras de forma modular e programática
- Habilitar e analisar logs detalhados de tráfego com pflog e tcpdump
- Testar, depurar e otimizar configurações usando pfctl -nf, pfctl -sr e pfctl -ss
Pré-requisitos e Ambiente
Para executar todos os procedimentos desta aula, você precisará de um sistema FreeBSD 13.2 ou superior instalado e funcionando — recomendamos a versão 14.0 ou 14.1, que são as versões estáveis mais recentes à data desta publicação. O sistema pode ser uma máquina física, uma VM no bhyve, VirtualBox ou VMware, ou até mesmo uma instância em nuvem (AWS, Azure, Vultr) desde que você tenha acesso root. É essencial que o Kernel Generic padrão esteja em uso, pois ele já inclui o suporte a PF compilado estaticamente ou como módulo carregável — não é necessário recompilar o kernel, ao contrário do que alguns tutoriais antigos sugerem.
Você também precisará de familiaridade básica com o FreeBSD, incluindo navegação no sistema de arquivos, edição de arquivos de configuração (usaremos vi e ee como exemplos), manipulação de serviços via service e sysrc, e compreensão dos conceitos de rede como endereçamento IP, máscaras de sub-rede, portas TCP/UDP e diferença entre tráfego de entrada (inbound) e saída (outbound). Se você concluiu as aulas anteriores deste curso, especialmente a Aula 08 sobre Jails e a Aula 10 sobre Rede e Interfaces, estará perfeitamente preparado. Para os testes de conectividade, tenha acesso SSH ao servidor e, idealmente, um segundo host na mesma rede para simular tráfego externo.
Nossos especialistas da JRT Technology Solutions recomendam fortemente que você NÃO execute estes procedimentos diretamente em produção sem antes testá-los em um ambiente de laboratório ou homologação. O PF Firewall é extremamente poderoso, e uma regra mal posicionada pode derrubar sua conectividade SSH em um piscar de olhos — felizmente, você aprenderá técnicas para evitar esse tipo de incidente, incluindo o uso de regras temporárias com timeout e o comando mágico pfctl -nf para validação sintática antes da aplicação.
| Componente | Especificação | Observação |
|---|---|---|
| Sistema Operacional | FreeBSD 14.1-RELEASE | Kernel GENÉRICO com suporte a PF |
| Interface Primária | em0 — IP 192.168.1.100/24 | Conectada à rede local |
| Interface Secundária | em1 — IP 10.0.0.1/24 | Rede interna para testes de NAT |
| Serviços em Execução | SSH (porta 22), HTTP (porta 80) | Usaremos para testes de filtro |
| Editor de Texto | vi ou ee | À sua escolha |
| Acesso | Root ou sudo | Todos os comandos exigem privilégios |
A História e a Filosofia do PF Firewall no FreeBSD
O PF Firewall nasceu no projeto OpenBSD em 2001, criado por Daniel Hartmeier como substituto do IPFilter (ipf) de Darren Reed, após um desentendimento sobre licenciamento que tornou o ipf inviável para a distribuição. O nome “PF” é uma abreviação direta de Packet Filter, refletindo sua missão original: filtrar pacotes de rede de forma eficiente, segura e com sintaxe compreensível. O FreeBSD importou o código do PF do OpenBSD 3.5 em 2004 e, desde então, mantém sua própria árvore de desenvolvimento com melhorias específicas — notadamente no suporte a múltiplas CPUs (SMP), integração com VIMAGE para virtualização de pilha de rede e otimizações para as syscalls nativas do sistema.
Filosoficamente, o PF Firewall segue o princípio Unix de “faça uma coisa e faça-a bem”, mas expande esse conceito para abranger todo o pipeline de processamento de pacotes. Em vez de separar filtragem, NAT e shaping em ferramentas distintas (como acontece no Linux com iptables + conntrack + tc), o PF unifica todas essas funções em um único subsistema coeso. Isso significa que uma única regra pode, simultaneamente, filtrar um pacote, manter seu estado, traduzir seu endereço e encaixá-lo em uma fila de QoS — tudo declarado de forma linear e auditável. Essa coesão reduz drasticamente a superfície de bugs e facilita a auditoria de segurança, um dos motivos pelos quais o OpenBSD e o FreeBSD são amplamente utilizados em firewalls e appliances de segurança.
Outro aspecto fundamental da filosofia do PF Firewall é o conceito de “última regra que corresponde vence” (last matching rule wins). Diferentemente de outros firewalls onde a primeira regra que casa com o pacote encerra a avaliação, no PF todas as regras são avaliadas em ordem, e a última que der match é a aplicada — a menos que a palavra-chave quick seja utilizada, que interrompe a avaliação imediatamente. Essa característica pode parecer contraintuitiva à primeira vista, mas permite construir políticas extremamente flexíveis com regras gerais seguidas de exceções específicas. Ao longo desta aula, você internalizará esse comportamento através de exemplos práticos que demonstram tanto o uso do quick quanto a avaliação sequencial completa.
Vale destacar também a influência do PF no mundo além dos BSDs: o pfSense, uma das distribuições de firewall mais populares do planeta, é construído inteiramente sobre o FreeBSD e utiliza o PF Firewall como engine de filtragem. O OPNsense, seu fork igualmente popular, segue o mesmo caminho. Até mesmo a Apple integrou o PF ao macOS e ao iOS como firewall de host, embora com uma interface de configuração limitada. Em suma, ao dominar o PF no FreeBSD, você está adquirindo conhecimento transferível para uma vasta gama de plataformas e cenários profissionais. Na JRT Technology Solutions, frequentemente reutilizamos conjuntos de regras PF entre appliances FreeBSD, servidores de borda e até mesmo em testes de penetração para criar ambientes controlados de rede.
Sintaxe Fundamental do PF Firewall — Regras, Listas e Macros
Antes de escrever uma única linha de configuração, é crucial entender a anatomia de uma regra no PF Firewall. A estrutura básica segue o formato: ação direção [log] [quick] [on interface] [af] [proto protocolo] from origem [port porta_origem] to destino [port porta_destino] [flags] [state]. Embora pareça intimidadora descrita assim, na prática ela flui naturalmente como uma sentença em inglês estruturado. Por exemplo: block in on em0 proto tcp from any to 192.168.1.100 port 22 — essa regra bloqueia tráfego TCP de entrada na interface em0, de qualquer origem, destinado ao IP 192.168.1.100 na porta 22 (SSH). A ação pode ser pass (permitir) ou block (bloquear), com variantes como block drop (descarta silenciosamente) e block return (responde com RST para TCP ou ICMP unreachable para UDP).
O PF Firewall oferece três construções que elevam sua legibilidade e manutenibilidade a outro patamar: macros, listas e tabelas. As macros são simples substituições de texto definidas no topo do arquivo — por exemplo, ext_if = “em0” permite que você use $ext_if em todas as regras, facilitando a adaptação da configuração a hardware diferente. As listas (definidas entre chaves { }) permitem agrupar múltiplos valores em uma única expressão, como proto { tcp, udp } ou port { 22, 80, 443 }. Já as tabelas são estruturas de dados em memória otimizadas para busca rápida de endereços IP, ideais para armazenar milhares de entradas — como listas de bloqueio — sem penalizar o desempenho do firewall.
O tratamento de estado (stateful inspection) é outro pilar do PF Firewall. Por padrão, regras pass não mantêm estado, o que significa que o tráfego de retorno não é automaticamente permitido. Para habilitar o comportamento stateful, utiliza-se a opção keep state, que instrui o PF a criar uma entrada na tabela de estados quando um pacote que corresponde à regra é visto, permitindo automaticamente o tráfego de resposta. Existem variações: modulate state é mais forte para conexões TCP, randomizando o ISN (Initial Sequence Number) para dificultar ataques de sequestro de sessão; synproxy state atua como proxy do handshake TCP, protegendo servidores contra ataques SYN flood. Em servidores voltados à Internet, recomendamos modulate state para todas as regras de saída e synproxy state para portas de serviços públicos críticos.
Vamos examinar uma regra completa e dissecá-la linha por linha para solidificar esses conceitos. Considere: pass in quick on $ext_if proto tcp from any to $ext_if port { 22, 80, 443 } flags S/SA modulate state. Aqui, pass in indica que estamos permitindo tráfego de entrada; quick interrompe a avaliação assim que esta regra casar; on $ext_if restringe à interface definida pela macro; proto tcp especifica protocolo TCP; from any to $ext_if port { 22, 80, 443 } define que aceitamos tráfego de qualquer origem destinado ao IP da interface externa nas portas listadas; flags S/SA exige que sejam pacotes SYN (início de conexão TCP) com ACK opcional; modulate state habilita inspeção stateful com randomização de ISN. Em nossos projetos na JRT Technology Solutions, regras como esta formam a espinha dorsal de virtualmente todo servidor público que configuramos.
Instalação e Ativação Passo a Passo do PF Firewall
Embora o subsistema PF Firewall já esteja presente no kernel GENERIC do FreeBSD, ele não vem ativado por padrão — uma decisão sábia que evita que firewalls mal configurados bloqueiem o acesso durante a instalação inicial. O primeiro passo é carregar o módulo de kernel e habilitar o serviço para inicialização automática. Começaremos verificando se o módulo está disponível e, em seguida, o carregaremos manualmente antes de configurar a inicialização permanente. Todos os comandos a seguir devem ser executados como root.
- Verificar se o módulo PF está disponível no kernel: Execute kldstat -v | grep pf para listar módulos carregados que contenham “pf” no nome. Em um sistema recém-instalado, provavelmente não retornará nada, indicando que o módulo ainda não foi carregado.
- Carregar o módulo PF manualmente: Execute kldload pf. Este comando insere o módulo pf.ko no kernel em execução. Você ouvirá um “clic” metafórico — o firewall está tecnicamente ativo, mas sem regras carregadas.
- Verificar se o módulo foi carregado com sucesso: Execute novamente kldstat -v | grep pf. Agora você verá uma linha contendo pf ou pf.ko.
- Habilitar o carregamento automático no boot: Edite o arquivo /boot/loader.conf e adicione a linha pf_load=”YES”. Isso instrui o loader a carregar o módulo PF durante a inicialização do kernel, antes mesmo do init ser executado.
- Habilitar o serviço PF no rc.conf: Execute sysrc pf_enable=”YES”. Este comando adiciona ou modifica a variável no /etc/rc.conf. O serviço pf será iniciado durante o boot e gerenciado pelo sistema rc.
- Habilitar o serviço de log pflog: Execute sysrc pflog_enable=”YES” para ativar a interface de logging pflog0, que capturará pacotes que casam com regras que incluem a palavra-chave log.
- Criar um arquivo pf.conf mínimo para teste: Por enquanto, criaremos um arquivo que permite todo o tráfego. Isso serve para validar que o serviço inicia sem erros e que o firewall não bloqueia nada inadvertidamente. Crie /etc/pf.conf com o conteúdo: pass all.
- Iniciar o serviço PF: Execute service pf start. O sistema carregará as regras do /etc/pf.conf e ativará o firewall.
- Verificar o status: Execute service pf status ou pfctl -s info para confirmar que o PF está “Enabled” e em execução.
Agora vamos executar todos estes comandos na ordem exata, com comentários linha a linha para que você entenda exatamente o que está acontecendo em cada etapa.
# Passo 1: Verificar se o módulo PF já está carregado
# kldstat -v lista todos os módulos carregados com informações detalhadas
# grep filtra as linhas que contêm "pf"
kldstat -v | grep pf
# Passo 2: Carregar o módulo pf.ko no kernel
# kldload insere um módulo de kernel; pf é o nome do módulo (pf.ko)
kldload pf
# Passo 3: Confirmar que o módulo foi carregado
# Agora o grep deve retornar uma linha com pf
kldstat -v | grep pf
# Passo 4: Garantir carregamento automático do módulo no boot
# Adicionamos a linha pf_load="YES" ao /boot/loader.conf
# O loader.conf é lido pelo boot loader antes do kernel iniciar completamente
echo 'pf_load="YES"' >> /boot/loader.conf
# Passo 5: Habilitar o serviço pf no rc.conf usando sysrc
# sysrc é uma ferramenta segura para manipular variáveis no /etc/rc.conf
sysrc pf_enable="YES"
# Passo 6: Habilitar o serviço de logging pflog
# pflog cria a interface pflog0 que captura logs do firewall
sysrc pflog_enable="YES"
# Passo 7: Criar arquivo pf.conf mínimo (permite tudo)
# Usamos echo com redirecionamento para criar o arquivo
# ATENÇÃO: Este arquivo é TEMPORÁRIO apenas para teste de ativação
echo 'pass all' > /etc/pf.conf
# Passo 8: Iniciar o serviço PF
# service pf start carrega as regras e ativa o firewall
service pf start
# Passo 9: Verificar status do PF
# pfctl -s info mostra informações gerais sobre o estado do PF
pfctl -s info
A saída esperada após a execução bem-sucedida desses comandos deve ser similar à mostrada abaixo. Observe atentamente cada linha para confirmar que não houve erros de sintaxe no carregamento.
# Saída do Passo 1 (kldstat -v | grep pf) - ANTES de carregar:
# (nenhuma saída, módulo não carregado)
# Saída do Passo 2 (kldload pf):
# (nenhuma saída em caso de sucesso; ausência de erro = sucesso)
# Saída do Passo 3 (kldstat -v | grep pf) - APÓS carregar:
2 1 0xffffffff82600000 59880 pf.ko
# Saída do Passo 5 (sysrc pf_enable="YES"):
pf_enable: NO -> YES
# Saída do Passo 6 (sysrc pflog_enable="YES"):
pflog_enable: NO -> YES
# Saída do Passo 8 (service pf start):
Enabling pf.
pf enabled
# Saída do Passo 9 (pfctl -s info):
Status: Enabled for 00:00:05 Debug: Urgent
Interface Stats for em0 IPv4 IPv6
Bytes In 12345678 0
Bytes Out 8765432 0
Packets In
Passed 150000 0
Blocked 5000 0
Packets Out
Passed 120000 0
Blocked 100 0
State Table Total Rate
current entries 15
searches 50000 10.0/s
inserts 2000 0.5/s
removals 1985 0.4/s
Counters
match 30000 8.0/s
bad-offset 0 0.0/s
fragment 0 0.0/s
short 0 0.0/s
normalize 0 0.0/s
memory 0 0.0/s
bad-timestamp 0 0.0/s
congestion 0 0.0/s
ip-option 0 0.0/s
proto-cksum 0 0.0/s
state-mismatch 15 0.0/s
state-insert 5 0.0/s
state-limit 0 0.0/s
src-limit 0 0.0/s
synproxy 0 0.0/s
map-failed 0 0.0/s
Com o PF Firewall ativo e o arquivo /etc/pf.conf criado (ainda que com a regra permissiva pass all), você deu o primeiro passo concreto. Nas próximas seções, substituiremos essa regra genérica por um conjunto de regras de produção criteriosamente elaborado. Mas antes, é importante entender que o PF, mesmo com pass all, já está inspecionando todo o tráfego e construindo tabelas de estado — você pode verificar isso com pfctl -ss (show states), que exibirá todas as conexões stateful atualmente conhecidas pelo firewall.
Criação do Arquivo /etc/pf.conf — Configuração Base Completa para Produção
Agora vamos substituir o arquivo temporário pass all por uma configuração robusta e pronta para uso em servidores reais. O arquivo que apresentaremos é o resultado de anos de refinamento em implantações da JRT Technology Solutions, equilibrando segurança, desempenho e manutenibilidade. A configuração segue o princípio de “negar tudo por padrão, permitir apenas o necessário” (default deny), que é a base de qualquer política de firewall responsável. Trabalharemos com macros para interfaces e portas, regras de normalização de pacotes, proteção anti-spoofing, regras stateful para serviços específicos e regras de saída que permitem ao servidor iniciar conexões com a Internet.
O arquivo será apresentado na íntegra e, em seguida, cada bloco será explicado em detalhes. Você pode copiar este conteúdo diretamente para o seu /etc/pf.conf, ajustando as macros de interface e endereços IP conforme seu ambiente. A estrutura é modular e comentada extensivamente — uma prática que recomendamos veementemente, pois facilita auditorias e manutenções futuras.
# ============================================================================
# /etc/pf.conf — Configuração Base do PF Firewall
# Ambiente: Servidor FreeBSD com interface pública
# Última atualização: 24/06/2026
# Compatível com FreeBSD 13.2+ e 14.x
# ============================================================================
# --- MACROS (Variáveis reutilizáveis) ---
# Defina aqui as interfaces de rede do seu sistema
# Use ifconfig para listar as interfaces disponíveis
ext_if = "em0" # Interface externa (voltada para Internet/LAN)
int_if = "em1" # Interface interna (rede privada, opcional)
# Redes diretamente conectadas às interfaces
# Ajuste conforme suas sub-redes reais
ext_net = "192.168.1.0/24"
int_net = "10.0.0.0/24"
# Portas de serviços que este servidor oferece PUBLICAMENTE
# Modifique esta lista conforme seus serviços
tcp_services = "{ 22, 80, 443 }" # SSH, HTTP, HTTPS
udp_services = "{ 53 }" # DNS (se este servidor for NS)
# Endereços IP confiáveis (ex: sua VPN, IP de administração)
# Adicione aqui os IPs que podem acessar portas restritas
admin_ips = "{ 192.168.1.10, 203.0.113.50 }"
# Portas de administração (não devem ser abertas ao público)
admin_ports = "{ 22 }"
# Endereços que NUNCA devem aparecer na interface externa (anti-spoofing)
# RFC 1918, loopback, multicast, etc.
martians = "{ 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, \
0.0.0.0/8, 169.254.0.0/16, 224.0.0.0/4, 240.0.0.0/4 }"
# --- OPÇÕES GERAIS ---
# Define política padrão: rejeitar (return) tráfego de entrada,
# mas permitir saída. Isso afeta APENAS tráfego que não casa com
# nenhuma regra explícita.
set block-policy return
set loginterface $ext_if
set skip on lo0 # Ignorar interface loopback (performance)
set state-policy if-bound # Estados vinculados à interface
set optimization normal # Otimização balanceada
set timeout { tcp.first 120, tcp.established 86400, tcp.closing 900 }
# --- NORMALIZAÇÃO DE PACOTES ---
# scrub reescreve pacotes para conformidade, elimina fragmentos problemáticos
scrub in all
scrub out all random-id # Randomiza IDs IP para ofuscar fingerprint OS
# --- PROTEÇÃO ANTI-SPOOFING ---
# Bloqueia pacotes de entrada na ext_if com IP de origem que pertence
# às redes internas ou endereços inválidos (martians)
antispoof quick for $ext_if
block in quick on $ext_if from $martians to any
block out quick on $ext_if from any to $martians
# --- REGRAS DE ENTRADA (INBOUND) ---
# Regra 1: Permitir tráfego de entrada para serviços públicos
pass in quick on $ext_if proto tcp from any to $ext_if port $tcp_services \
flags S/SA modulate state
pass in quick on $ext_if proto udp from any to $ext_if port $udp_services \
keep state
# Regra 2: Permitir SSH apenas para IPs administrativos
pass in quick on $ext_if proto tcp from $admin_ips to $ext_if port 22 \
flags S/SA modulate state
# Regra 3: Permitir ICMP essencial (ping, traceroute, path MTU discovery)
pass in quick on $ext_if proto icmp from any to $ext_if \
icmp-type { echoreq, unreach, timex, paramprob }
# Regra 4: Permitir tráfego da rede interna (confiável)
pass in quick on $int_if from $int_net to any keep state
# --- REGRAS DE SAÍDA (OUTBOUND) ---
# Permitir todo tráfego iniciado pelo servidor
pass out quick on $ext_if proto tcp from $ext_if to any modulate state
pass out quick on $ext_if proto udp from $ext_if to any keep state
pass out quick on $ext_if proto icmp from $ext_if to any
# Permitir tráfego da rede interna para fora (se este for um gateway)
pass out quick on $ext_if from $int_net to any nat-to ($ext_if) keep state
# --- BLOQUEIO PADRÃO E LOGGING ---
# Qualquer tráfego que chegou até aqui não foi explicitamente permitido.
# Bloquear e logar.
block log all
Agora, vamos dissecar os blocos mais importantes deste arquivo. A seção MACROS é autoexplicativa, mas merece atenção: observe como a lista $tcp_services é utilizada em uma única regra em vez de repetir três regras separadas para portas 22, 80 e 443. Isso reduz o tamanho do ruleset e facilita a manutenção — se você adicionar um novo serviço na porta 8080, basta alterar a macro. A lista $admin_ips segue a mesma lógica: você pode ter dezenas de IPs administrativos listados sem poluir as regras.
O bloco OPÇÕES GERAIS define o comportamento global do PF Firewall. set block-policy return faz com que pacotes bloqueados recebam uma resposta ICMP unreachable ou TCP RST, em vez de serem silenciosamente descartados — isso melhora a experiência do usuário legítimo (conexões falham rapidamente) e dificulta um pouco o reconhecimento de portas por scanners, já que a resposta é ambígua. set skip on lo0 é uma otimização importante: o tráfego de loopback não precisa ser inspecionado pelo firewall, e pular essa interface reduz a latência de comunicação entre processos locais. set state-policy if-bound vincula estados à interface específica, impedindo que um estado criado na em0 seja reutilizado por tráfego na em1 — uma medida de segurança sutil mas eficaz contra certos tipos de spoofing.
As regras de scrub merecem uma menção especial. scrub in all normaliza pacotes de entrada, remontando fragmentos e descartando combinações inválidas de flags TCP. Isso elimina uma classe inteira de ataques baseados em fragmentação e evasão de firewalls. scrub out all random-id randomiza o campo Identification do cabeçalho IP em pacotes de saída, dificultando a identificação do sistema operacional (OS fingerprinting) por ferramentas como p0f ou nmap. Em ambientes de alta segurança — como os que configuramos na JRT Technology Solutions para clientes do setor financeiro —, estas regras de scrub são consideradas obrigatórias.
Verificando a Instalação / Testando a Configuração
Com o arquivo /etc/pf.conf completo em mãos, o próximo passo é validar a sintaxe e carregar as regras de forma segura. O PF Firewall oferece um comando de validação que verifica toda a configuração sem aplicá-la — é o pfctl -nf, e ele deve ser sua primeira ação sempre que você modificar o arquivo. Jamais execute service pf restart sem antes validar, pois um erro de sintaxe pode deixar o firewall em estado inconsistente ou, pior, descarregar todas as regras e expor o servidor. Siga o procedimento abaixo em seu terminal.
# Passo 1: Validar a sintaxe do arquivo pf.conf sem aplicar
# -n : não carrega as regras (no-op mode), apenas verifica sintaxe
# -f : especifica o arquivo de configuração
pfctl -nf /etc/pf.conf
# Passo 2: Se a validação retornar sem erros, carregar as novas regras
# -f : carrega o arquivo especificado, substituindo as regras atuais
pfctl -f /etc/pf.conf
# Passo 3: Verificar as regras ativas (forma resumida)
# -sr : show rules (exibe as regras como estão na memória)
pfctl -sr
# Passo 4: Verificar as regras ativas com contadores de hits
# -vsr : verbose show rules (inclui estatísticas de uso)
pfctl -vsr
# Passo 5: Verificar a tabela de estados atual
# -ss : show states (conexões stateful ativas)
pfctl -ss | head -20
# Passo 6: Verificar informações gerais do PF (status, contadores, interfaces)
pfctl -s info
# Passo 7: Listar todas as tabelas definidas (mesmo que vazias)
pfctl -s Tables
# Passo 8: Testar conectividade — tente um ping para o servidor
# Execute de OUTRA máquina ou do próprio servidor via loopback
ping -c 4 192.168.1.100
# Passo 9: Testar acesso SSH — tente conectar de um IP autorizado
ssh usuario@192.168.1.100
# Passo 10: Verificar logs do firewall (se houver regras com 'log')
# O comando tcpdump lê a interface pflog0
tcpdump -n -e -ttt -i pflog0
A validação sintática com pfctl -nf /etc/pf.conf é silenciosa quando bem-sucedida — nenhuma saída significa que não há erros. Se houver problemas, o PF indicará a linha exata e uma descrição do erro. Abaixo está a saída esperada para uma configuração correta, seguida da listagem das regras ativas após o carregamento.
# Saída de pfctl -nf /etc/pf.conf (validação bem-sucedida):
# (nenhuma saída — silêncio = sucesso)
# Saída de pfctl -sr (regras ativas após carregamento):
scrub in all fragment reassemble
scrub out all random-id fragment reassemble
block drop in quick on ! em0 inet from 192.168.1.0/24 to any
block drop in quick on ! em1 inet from 10.0.0.0/24 to any
block drop in quick inet from 192.168.1.100 to any
block drop in quick on em0 from 127.0.0.0/8 to any
block drop in quick on em0 from 10.0.0.0/8 to any
block drop in quick on em0 from 172.16.0.0/12 to any
block drop in quick on em0 from 192.168.0.0/16 to any
block drop in quick on em0 from 0.0.0.0/8 to any
block drop in quick on em0 from 169.254.0.0/16 to any
block drop in quick on em0 from 224.0.0.0/4 to any
block drop in quick on em0 from 240.0.0.0/4 to any
block drop out quick on em0 from any to 127.0.0.0/8
block drop out quick on em0 from any to 10.0.0.0/8
block drop out quick on em0 from any to 172.16.0.0/12
block drop out quick on em0 from any to 192.168.0.0/16
block drop out quick on em0 from any to 0.0.0.0/8
block drop out quick on em0 from any to 169.254.0.0/16
block drop out quick on em0 from any to 224.0.0.0/4
block drop out quick on em0 from any to 240.0.0.0/4
pass in quick on em0 proto tcp from any to 192.168.1.100 port = 22 flags S/SA modulate state
pass in quick on em0 proto tcp from any to 192.168.1.100 port = 80 flags S/SA modulate state
pass in quick on em0 proto tcp from any to 192.168.1.100 port = 443 flags S/SA modulate state
pass in quick on em0 proto udp from any to 192.168.1.100 port = 53 keep state
pass in quick on em0 proto tcp from 192.168.1.10 to 192.168.1.100 port = 22 flags S/SA modulate state
pass in quick on em0 proto tcp from 203.0.113.50 to 192.168.1.100 port = 22 flags S/SA modulate state
pass in quick on em0 proto icmp from any to 192.168.1.100 icmp-type echoreq keep state
pass in quick on em0 proto icmp from any to 192.168.1.100 icmp-type unreach keep state
pass in quick on em0 proto icmp from any to 192.168.1.100 icmp-type timex keep state
pass in quick on em0 proto icmp from any to 192.168.1.100 icmp-type paramprob keep state
pass in quick on em1 inet from 10.0.0.0/24 to any keep state
pass out quick on em0 proto tcp from 192.168.1.100 to any modulate state
pass out quick on em0 proto udp from 192.168.1.100 to any keep state
pass out quick on em0 proto icmp from 192.168.1.100 to any
pass out quick on em0 inet from 10.0.0.0/24 to any nat-to (em0) keep state
block log all
# Saída de pfctl -s info (após carregar):
Status: Enabled for 00:05:32 Debug: Urgent
Interface Stats for em0 IPv4 IPv6
Bytes In 98765432 0
Bytes Out 45678901 0
Packets In
Passed 450000 0
Blocked 15000 0
Packets Out
Passed 380000 0
Blocked 500 0
State Table Total Rate
current entries 120
searches 250000 15.0/s
inserts 12000 0.8/s
removals 11880 0.8/s
# Saída de tcpdump -n -e -ttt -i pflog0 (exemplo com tráfego bloqueado):
# tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
# listening on pflog0, link-type PFLOG (OpenBSD pflog file), capture size 262144 bytes
# 00:00:01.000000 rule 45/0(match): block in on em0: 203.0.113.99.54321 > 192.168.1.100.22: Flags [S], seq 123456789
Observe na saída do pfctl -sr como as macros foram expandidas: $tcp_services gerou três regras separadas (portas 22, 80, 443), cada uma com seus respectivos flags e modulate state. As regras de antispoofing também se expandiram em múltiplas linhas bloqueando cada bloco de endereços martians. Este é o comportamento esperado — internamente, o PF expande listas e macros antes de compilar o ruleset. A última linha da saída, block log all, é nossa rede de segurança: qualquer pacote que não tenha sido explicitamente permitido pelas regras anteriores será bloqueado e registrado no pflog0. O exemplo do tcpdump mostra um pacote bloqueado: uma tentativa de conexão SSH vinda do IP 203.0.113.99 (não listado em $admin_ips) sendo descartada pela regra 45.
Trabalhando com Tabelas no PF Firewall — Bloqueio Dinâmico e Listas de IPs
Uma das funcionalidades mais poderosas do PF Firewall é o suporte a tabelas — estruturas de dados em memória otimizadas para armazenar e consultar grandes conjuntos de endereços IP com complexidade O(1) (tempo constante). Diferentemente das listas estáticas definidas entre chaves no arquivo pf.conf, as tabelas podem ser manipuladas em tempo real via pfctl -t, sem necessidade de recarregar todo o ruleset. Isso abre possibilidades como: integração com scripts de fail2ban para bloqueio automático de atacantes; manutenção de blacklists de IPs maliciosos baixadas da Internet; whitelists dinâmicas de IPs de clientes confiáveis; e até mesmo balanceamento de carga usando tabelas de redirecionamento.
Tabelas são definidas no arquivo pf.conf usando a sintaxe table <nome> { endereços } e referenciadas nas regras com <nome> (entre colchetes angulares). Vamos criar um exemplo prático: uma tabela chamada <bruteforce> que armazenará IPs que excederem um limite de tentativas de conexão SSH, e uma regra que bloqueia todos os endereços presentes nessa tabela. Este é o mecanismo que utilizamos na JRT Technology Solutions como base para sistemas anti-força bruta em servidores expostos à Internet.
# Conteúdo adicional para /etc/pf.conf (adicione após as macros e antes das regras)
# --- TABELAS ---
# Tabela para bloqueio dinâmico de IPs maliciosos
table persist
# Tabela para whitelist de IPs confiáveis (nunca serão bloqueados)
table persist { 192.168.1.10, 10.0.0.5 }
# Tabela para IPs de serviços externos confiáveis (ex: APIs internas)
table { 203.0.113.100, 198.51.100.50 }
# --- REGRA DE BLOQUEIO POR TABELA ---
# Deve vir ANTES das regras de permissão para ser efetiva
# Bloqueia silenciosamente (drop) qualquer tráfego de IPs na tabela bruteforce
block drop quick from to any
# --- Exemplo de regra que usa whitelist combinada com restrição ---
# SSH permitido apenas para IPs na whitelist OU admin_ips
pass in quick on $ext_if proto tcp from { , $admin_ips } \
to $ext_if port 22 flags S/SA modulate state
Agora, vejamos como manipular a tabela <bruteforce> dinamicamente a partir da linha de comando. O comando pfctl -t bruteforce -T é a porta de entrada para todas as operações: adicionar, remover, listar e testar endereços. Não é necessário recarregar o firewall — as alterações entram em vigor imediatamente.
# Adicionar um IP à tabela de bloqueio
# -t bruteforce : seleciona a tabela
# -T add : operação de adição
pfctl -t
Quer aprender na prática com especialistas?
A JRT Technology Solutions oferece treinamentos e implementação de FreeBSD para equipes corporativas.