Tutorial: mitigar ataques DDoS em VPS com eBPF/XDP no Ubuntu 22.04 — passo a passo prático
Redes & Segurança · Por Kevin Marcondes · Publicado em · 9 min de leitura
Tutorial: mitigar ataques DDoS em VPS com eBPF/XDP no Ubuntu 22.04 — passo a passo. Você vai compilar um programa XDP simples, anexá-lo à interface de rede, validar contadores e bloquear IPs com bpftool. Essa abordagem de baixa latência filtra tráfego malicioso antes da pilha. Teste com cuidado e tenha acesso de recuperação.
Pré‑requisitos, riscos e objetivo
Vamos filtrar pacotes no driver com XDP (Express Data Path) usando eBPF. Exige acesso root e pode derrubar a conectividade se aplicado errado. Teste em ambiente isolado ou tenha console out‑of‑band.
O objetivo é bloquear fontes óbvias (IPs spoofados e reservados), descartar fragmentos IP e, opcionalmente, UDP para portas HTTP quando sob ataque. Integre com práticas de hardening.
Antes de começar, garanta o básico e instale ferramentas.
- Ubuntu Server 22.04 com kernel padrão.
- Acesso root ou sudo com privilégios.
- Clang/LLVM, headers do kernel, bpftool, iproute2 e xdp-tools.
- Janela de manutenção e acesso alternativo (ex.: console do provedor).
- Checklist complementar: veja checklist de segurança para VPS.
sudo apt update
sudo apt install -y clang llvm libbpf-dev libelf-dev build-essential \
linux-headers-$(uname -r) bpftool iproute2 git make pkg-config xdp-tools
Escreva um programa XDP básico de mitigação (C)
O programa abaixo faz parsing mínimo de Ethernet e IPv4. Ele descarta pacotes com IP de origem inválido (RFC1918, loopback, multicast e reservados) e fragmentos IP. Também oferece a opção de dropar UDP para 80/443 durante ataques. Mantém contadores de drops por CPU.
Ajuste as macros no topo conforme seu cenário. Se você usa QUIC (UDP/443), desative o drop de UDP.
// xdp_ddos_kern.c
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <linux/tcp.h>
// Ajustes rápidos
#define DROP_UDP_TO_HTTP 1 // 1 = drop UDP para portas 80/443; 0 = permitir
#define DROP_FRAGMENTS 1 // 1 = drop de fragmentos IP; 0 = permitir
struct counters { __u64 packets; __u64 bytes; };
struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__uint(max_entries, 1);
__type(key, __u32);
__type(value, struct counters);
} drop_stats SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 16384);
__type(key, __u32); // IPv4 (host-order)
__type(value, __u8); // 1 = bloquear
} blocked_ips SEC(".maps");
static __always_inline int count_drop(__u32 len) {
__u32 key = 0;
struct counters *c = bpf_map_lookup_elem(&drop_stats, &key);
if (c) {
__sync_fetch_and_add(&c->packets, 1);
__sync_fetch_and_add(&c->bytes, len);
}
return XDP_DROP;
}
static __always_inline bool is_bad_src_v4(__u32 s) {
// s em host-order
// 10/8
if ((s & 0xFF000000) == 0x0A000000) return true;
// 172.16/12
if ((s & 0xFFF00000) == 0xAC100000) return true;
// 192.168/16
if ((s & 0xFFFF0000) == 0xC0A80000) return true;
// 127/8 (loopback)
if ((s & 0xFF000000) == 0x7F000000) return true;
// 0/8
if ((s & 0xFF000000) == 0x00000000) return true;
// 169.254/16 (link-local)
if ((s & 0xFFFF0000) == 0xA9FE0000) return true;
// 224/4 (multicast) e 240/4 (reservado)
if ((s & 0xF0000000) == 0xE0000000) return true;
if ((s & 0xF0000000) == 0xF0000000) return true;
return false;
}
SEC("xdp")
int xdp_ddos(struct xdp_md *ctx) {
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end)
return XDP_PASS;
if (bpf_ntohs(eth->h_proto) != ETH_P_IP)
return XDP_PASS; // só IPv4 aqui
struct iphdr *ip = (void *)(eth + 1);
if ((void *)(ip + 1) > data_end)
return XDP_PASS;
// Drop por fragmentação
if (DROP_FRAGMENTS) {
if (ip->frag_off & bpf_htons(IP_MF | IP_OFFSET))
return count_drop((__u32)(data_end - data));
}
__u32 saddr = bpf_ntohl(ip->saddr);
if (is_bad_src_v4(saddr))
return count_drop((__u32)(data_end - data));
// Blocklist dinâmica (hash)
__u8 *blocked = bpf_map_lookup_elem(&blocked_ips, &saddr);
if (blocked && *blocked == 1)
return count_drop((__u32)(data_end - data));
// Regras UDP opcionais
if (DROP_UDP_TO_HTTP && ip->protocol == IPPROTO_UDP) {
struct udphdr *udp = (void *)(ip + 1);
if ((void *)(udp + 1) > data_end)
return XDP_PASS;
__u16 dport = bpf_ntohs(udp->dest);
if (dport == 80 || dport == 443) {
return count_drop((__u32)(data_end - data));
}
}
return XDP_PASS;
}
char _license[] SEC("license") = "GPL";
Compile o programa para eBPF
Compile o objeto eBPF com clang. O resultado xdp_ddos_kern.o será carregado na interface de rede. Execute como root ou com sudo. Se alterar macros, recompile antes de recarregar.
cat > xdp_ddos_kern.c <<'EOF'
[cole aqui o código C da seção anterior]
EOF
clang -O2 -g -target bpf -c xdp_ddos_kern.c -o xdp_ddos_kern.o
ls -lh xdp_ddos_kern.o
Anexe o XDP à interface (nativo ou genérico)
Identifique a interface WAN da VPS. Tente o modo nativo (driver) primeiro. Se o driver não suportar, use o modo genérico (skb). Ele funciona na maioria dos casos, com overhead maior.
Atenção: anexar XDP pode interromper conexões ativas. Tenha SSH alternativo ou console pronto. Para acesso seguro, considere um bastion SSH com 2FA. Se preferir, o pacote xdp-tools oferece o xdp-loader.
IFACE=eth0 # ajuste se necessário
ip -brief link
# Modo nativo (driver). Se falhar, tente o genérico abaixo.
sudo ip link set dev "$IFACE" xdp obj xdp_ddos_kern.o sec xdp
# Alternativa: modo genérico (fallback)
# sudo ip link set dev "$IFACE" xdpgeneric obj xdp_ddos_kern.o sec xdp
Como confirmar que funcionou (status e contadores)
Verifique se o programa XDP está anexado e colete contadores de drops. Gere tráfego de teste. Em produção, monitore os números e ajuste regras. Para gráficos, veja Prometheus e Grafana em VPS.
# Verificar XDP na interface
ih=$(ip -details link show dev "$IFACE" | sed -n '/prog/xdp/,$p')
echo "$ih" | sed -n '1,15p'
# Listar programas e mapas no netns
yes | sudo bpftool net -a
# Achar ID do mapa de contadores e exibir
STATS_ID=$(sudo bpftool map show | awk '/drop_stats/ {print $1}' | sed 's/id //')
echo "Mapa drop_stats id=$STATS_ID"
sudo bpftool map dump id "$STATS_ID"
Bloqueie IPs dinamicamente com bpftool (durante o ataque)
A blocklist funciona em tempo real. Para adicionar um IP de origem suspeito, atualize o mapa hash. Use o ID do mapa blocked_ips. Remova a entrada quando o ataque cessar. Evite bloqueios amplos que atinjam clientes legítimos.
# Descobrir o ID do mapa blocked_ips
MAP_ID=$(sudo bpftool map show | awk '/blocked_ips/ {print $1}' | sed 's/id //')
echo "blocked_ips id=$MAP_ID"
# Exemplo: bloquear 203.0.113.45 (hex: CB 00 71 2D)
sudo bpftool map update id "$MAP_ID" key hex CB 00 71 2D value hex 01
# Conferir
sudo bpftool map lookup id "$MAP_ID" key hex CB 00 71 2D
# Remover da blocklist
# sudo bpftool map delete id "$MAP_ID" key hex CB 00 71 2D
Torne persistente com systemd (carregar no boot)
Crie um diretório para o objeto e um serviço systemd que anexa o XDP após a rede ficar on‑line. Ajuste o IFACE no unit file. Para atualizar regras, recompile e reinicie o serviço.
Cuidado: mudanças incorretas podem bloquear o servidor no boot. Tenha acesso de recuperação do provedor.
sudo mkdir -p /opt/xdp
sudo cp xdp_ddos_kern.o /opt/xdp/
sudo tee /etc/systemd/system/xdp-ddos.service > /dev/null <<'UNIT'
[Unit]
Description=XDP DDoS mitigation (attach program on interface)
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/sbin/ip link set dev eth0 xdp obj /opt/xdp/xdp_ddos_kern.o sec xdp
ExecStop=/usr/sbin/ip link set dev eth0 xdp off
[Install]
WantedBy=multi-user.target
UNIT
sudo systemctl daemon-reload
sudo systemctl enable --now xdp-ddos.service
sudo systemctl status xdp-ddos.service --no-pager
Solução de problemas comuns e próximos passos
Algumas falhas recorrentes podem ser contornadas com checagens rápidas. Se nada carregar, teste o modo genérico, valide headers do kernel e faça recompilação limpa. Combine XDP com camadas adicionais, como firewall L7 e limites de taxa a montante. Para banir tentativas de login, use fail2ban com webhooks.
- ip link: "Operation not supported": driver sem XDP nativo — use xdpgeneric.
- bpftool: "map not found": confirme o anexo e o nome do mapa.
- Sem contadores: gere tráfego ou insira um IP em blocked_ips.
- Perdeu acesso remoto: desanexe via console: ip link set dev IFACE xdp off.
- Alvo HTTP/3: se usa QUIC, desative DROP_UDP_TO_HTTP e recompile.
# Remover o programa XDP em caso de emergência
sudo ip link set dev "$IFACE" xdp off || sudo ip link set dev "$IFACE" xdpgeneric off
Perguntas frequentes
eBPF/XDP substitui um serviço anti‑DDoS da operadora ou CDN?
Não. XDP mitiga no host com latência mínima, filtrando antes da pilha de rede. Isso ajuda contra tráfego malformado, spoofado e UDP indesejado, mas não supera a capacidade do link. Para ataques volumétricos, é preciso mitigação a montante.
Posso usar XDP se minha placa de rede não suporta o modo do driver?
Sim. Use o modo genérico (skb), que roda após a recepção do pacote e tem overhead maior. O comando ip link oferece a opção “xdpgeneric”. Se o nativo falhar, o genérico é um fallback funcional para VPS.
Quais ataques esse exemplo ajuda a mitigar?
Bloqueia fontes spoofadas comuns (RFC1918, loopback, multicast e reservados), descarta fragmentos IP e, opcionalmente, UDP para portas HTTP. É útil contra floods básicos. Você pode estender a lógica com rate limits por IP e blocklists externas.
Como monitorar os contadores do XDP em produção?
Use bpftool para ler mapas periodicamente e exportar para seu sistema de métricas. Uma opção simples é coletar via script e enviar para um exporter. Para visualização, veja o guia de Prometheus e Grafana em VPS.
É seguro bloquear UDP na porta 443?
Apenas durante um ataque e se você não usa QUIC ou HTTP/3. Bloquear UDP/443 interrompe QUIC. Se seus clientes usam HTTP/3, desative a macro correspondente no código, recompile e reanexe o programa.
Precisa adaptar o XDP ao seu tráfego real?
Se quiser ajuda para ajustar regras, integrar métricas e automatizar a blocklist durante incidentes, podemos conversar sobre seu ambiente e objetivos.
Falar sobre mitigação com XDPVeja também
- tutorial: configurar mTLS entre Traefik e serviços em Docker Compose usando smallstep CA e certificados automáticos — passo a passo
- Tutorial: integrar Sentry ao pipeline de deploy com GitHub Actions para upload automático de source maps e criação de issues (PHP e JavaScript) — passo a passo
- Configurar retenção e compactação de logs de containers Docker com journald + logrotate + rsyslog em VPS com pouco disco
- tutorial: configurar GitHub Actions para testes de integração com PostgreSQL em containers efêmeros e rollback automático de migrações — guia prático