Tutorial: mitigar ataques DDoS em VPS com eBPF/XDP no Ubuntu 22.04 — passo a passo prático

Redes & Segurança · Por · Publicado em · 9 min de leitura

Linhas e pontos laranja conectados formando uma rede abstrata sobre fundo escuro
Ilustração abstrata que representa tráfego e conexões de rede Foto: U.Lucas Dubé-Cantin / Pexels

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 XDP

Veja também