This commit is contained in:
Rodrigo Bertoldo de Sousa Martins committed 2026-09-13 21:06:39 -03:00
1 parent 8e4d62c615
commit 911fcced24
4 files changed
+190 -49

No files matched your search

+45 -30
View File
@@ -178,10 +178,23 @@ static __always_inline int is_blacklisted(__u32 ip) {
}
static __always_inline int is_internal_network(__u32 ip) {
// Fast path: as duas redes internas reais (dockernet e WireGuard) já
// são constantes conhecidas em tempo de compilação (DOCKER_NETWORK_*
// / WG_NETWORK_* no topo do arquivo). Checar isso com duas comparações
// de inteiro evita um bpf_map_lookup_elem em HASH map — que envolve
// hashing + possível cache miss — em todo pacote de origem interna,
// que é o caso mais comum no fast path (ver chamada logo no início
// do programa principal).
if ((ip & DOCKER_NETWORK_MASK) == DOCKER_NETWORK_PREFIX)
return 1;
if ((ip & WG_NETWORK_MASK) == WG_NETWORK_PREFIX)
return 1;
// Fallback: redes internas adicionais configuradas dinamicamente via
// mapa (para não precisar recompilar o programa a cada rede nova).
struct network_config *net;
__u32 key = 0;
// Iteração limitada pelo verificador
#pragma unroll
for (int i = 0; i < 8; i++) {
key = i;
@@ -235,6 +248,29 @@ static __always_inline void update_stats(__u32 action, __u8 flags) {
#define ntohs(x) __builtin_bswap16(x)
#define ntohl(x) __builtin_bswap32(x)
// Redireciona para AF_XDP (kernel bypass, af_xdp_user faz bind no xsks_map)
// pacotes já aprovados nas portas de alto tráfego. IMPORTANTE: para 80/443
// isso só deve ser chamado DEPOIS que o pacote passou pelo rate-limiting —
// chamar antes deixaria um atacante contornar toda a proteção contra DDoS
// caindo direto no redirect. Para as portas já tratadas como fast-path
// (WireGuard, 5061/5161) tanto faz, pois elas já pulam o rate-limiting de
// qualquer forma.
static __always_inline int try_af_xdp_redirect(struct xdp_md *ctx, __u8 protocol, __u16 dest_port) {
int xsk_map_key = ctx->rx_queue_index;
int redirect_tcp = (protocol == IPPROTO_TCP) &&
(dest_port == 80 || dest_port == 443 || dest_port == 5061 || dest_port == 5161);
int redirect_udp = (protocol == IPPROTO_UDP) &&
(dest_port == 32100 || dest_port == 32110 || dest_port == 32101 || dest_port == 32400 ||
dest_port == 5060 || dest_port == 5160 ||
(dest_port >= 18000 && dest_port <= 19000));
if (!redirect_tcp && !redirect_udp)
return 0;
return bpf_map_lookup_elem(&xsks_map, &xsk_map_key) != NULL;
}
// Versão otimizada do programa principal
SEC("xdp")
int xdp_ddos_filter(struct xdp_md *ctx) {
@@ -277,6 +313,8 @@ int xdp_ddos_filter(struct xdp_md *ctx) {
if (dest_port == 32100 || dest_port == 32400 || dest_port == 32101 || dest_port == 32110 ||
dest_port == 5060 || dest_port == 5160 ||
(dest_port >= 18000 && dest_port <= 19000)) {
if (try_af_xdp_redirect(ctx, IPPROTO_UDP, dest_port))
return XDP_REDIRECT;
update_stats(XDP_PASS, 0);
return XDP_PASS;
}
@@ -368,6 +406,8 @@ int xdp_ddos_filter(struct xdp_md *ctx) {
__u8 mark_trusted = 1;
bpf_map_update_elem(&trusted_hosts_map, &src_ip, &mark_trusted, BPF_ANY);
}
if (try_af_xdp_redirect(ctx, IPPROTO_TCP, dest_port))
return XDP_REDIRECT;
return XDP_PASS;
}
struct ip_stats *entry = bpf_map_lookup_elem(&ddos_map, &src_ip);
@@ -543,6 +583,8 @@ update_entry_and_pass:
};
bpf_map_update_elem(&ddos_map, &src_ip, &new_entry, BPF_ANY);
update_stats(XDP_PASS, 0);
if (try_af_xdp_redirect(ctx, ip->protocol, dest_port))
return XDP_REDIRECT;
return XDP_PASS;
}
update_entry_and_drop:
@@ -576,38 +618,11 @@ update_entry_and_drop:
};
bpf_map_update_elem(&ddos_map, &src_ip, &new_entry, BPF_ANY);
update_stats(XDP_PASS, 0);
if (try_af_xdp_redirect(ctx, ip->protocol, dest_port))
return XDP_REDIRECT;
return XDP_PASS;
}
// Redirecionamento AF_XDP para portas 80, 443 (TCP) e 32100 (UDP)
int xsk_map_key = ctx->rx_queue_index;
// TCP: 80 ou 443
if (ip->protocol == IPPROTO_TCP) {
struct tcphdr *tcp = (void *)(ip + 1);
if ((void *)(tcp + 1) > data_end) return XDP_PASS;
__u16 dest_port = ntohs(tcp->dest);
// Redirecionar também 5061, 5161
if (dest_port == 80 || dest_port == 443 || dest_port == 5061 || dest_port == 5161) {
if (bpf_map_lookup_elem(&xsks_map, &xsk_map_key))
return XDP_REDIRECT;
}
}
// UDP: 32100 (WireGuard)
if (ip->protocol == IPPROTO_UDP) {
struct udphdr *udp = (void *)(ip + 1);
if ((void *)(udp + 1) > data_end) return XDP_PASS;
__u16 dest_port = ntohs(udp->dest);
// Redirecionar também 5060, 5160, 18000-19000
if (dest_port == 32100 || dest_port == 32110 || dest_port == 32101 || dest_port == 32400 ||
dest_port == 5060 || dest_port == 5160 ||
(dest_port >= 18000 && dest_port <= 19000)) {
if (bpf_map_lookup_elem(&xsks_map, &xsk_map_key))
return XDP_REDIRECT;
}
}
return XDP_PASS;
}
+73 -17
View File
@@ -43,29 +43,84 @@ struct {
__type(value, __u16); // MTU conhecido
} path_mtu_map SEC(".maps");
// Função para ajustar TCP MSS - totalmente reescrita e simplificada para maior compatibilidade
static __always_inline int adjust_tcp_mss(struct tcphdr *tcp, void *data_end, __u16 max_mss) {
// Verificação básica do cabeçalho TCP
// Atualização incremental de checksum de 16 bits (RFC 1071/1624) — a mesma
// técnica usada pelo csum_replace2() do kernel. Necessária porque XDP não
// tem acesso a helpers de checksum baseados em skb (bpf_l4_csum_replace só
// existe para programas TC/socket, não XDP).
static __always_inline __u16 csum16_replace(__u16 csum, __u16 old_val, __u16 new_val) {
__u32 sum = (__u16)~csum + (__u16)~old_val + new_val;
sum = (sum & 0xffff) + (sum >> 16);
sum = (sum & 0xffff) + (sum >> 16);
return (__u16)~sum;
}
// Clampa o MSS anunciado em pacotes SYN, reescrevendo o pacote de verdade
// (a versão anterior só incrementava uma estatística e admitia no comentário
// que não mexia no pacote — ou seja, não fazia nada útil). Como não mudamos
// o tamanho do pacote, dá para escrever em cima do buffer existente sem
// bpf_xdp_adjust_head, só recalculando o checksum do TCP.
static __always_inline int clamp_tcp_mss(struct tcphdr *tcp, void *data_end, __u16 max_mss_host) {
if ((void *)(tcp + 1) > data_end)
return 0;
// Ajustamos MSS apenas em pacotes SYN
// Ajustamos MSS apenas em pacotes SYN puros (não SYN-ACK): este
// programa roda no ingress de wg0, ou seja, só vê tráfego entrando
// pelo túnel em direção à rede interna. O SYN-ACK de volta sai por
// wg0 (egress), fora do alcance do XDP — cobrir essa direção também
// exigiria um programa TC de egress, não só XDP.
if (!tcp->syn || tcp->ack)
return 0;
// Obter comprimento do cabeçalho e verificar se há opções
__u8 tcp_hdr_len = tcp->doff * 4;
if (tcp_hdr_len <= sizeof(struct tcphdr))
return 0; // Sem opções TCP
// Simplificado: remover a complexidade de analisar as opções
// Em vez disso, vamos assumir um MSS conservador para todos os pacotes SYN
__u32 key = 0;
struct mtu_stats *stats = bpf_map_lookup_elem(&mtu_stats_map, &key);
if (stats)
stats->mss_adjusted++;
return 1; // Reportamos ajuste sem tentar modificar o pacote
__u8 *opt = (__u8 *)(tcp + 1);
__u8 *opt_end = (__u8 *)tcp + tcp_hdr_len;
if ((void *)opt_end > data_end)
return 0;
// Loop limitado (verificador do BPF exige bound estático): cabeçalho
// TCP tem no máximo 40 bytes de opções, então 10 iterações cobrem até
// opções de 4 bytes cada — mais que suficiente para chegar no MSS,
// que por convenção vem logo no início das opções.
#pragma unroll
for (int i = 0; i < 10; i++) {
if ((void *)(opt + 1) > data_end || opt >= opt_end)
break;
__u8 kind = opt[0];
if (kind == 0) // End of Option List
break;
if (kind == 1) { // NOP
opt += 1;
continue;
}
if ((void *)(opt + 2) > data_end || opt + 1 >= opt_end)
break;
__u8 opt_len = opt[1];
if (opt_len < 2 || (void *)(opt + opt_len) > data_end || opt + opt_len > opt_end)
break;
if (kind == 2 && opt_len == 4) { // Maximum Segment Size
if ((void *)(opt + 4) > data_end)
break;
__be16 *mss_ptr = (__be16 *)(opt + 2);
__u16 cur_mss = bpf_ntohs(*mss_ptr);
if (cur_mss > max_mss_host) {
__be16 old_val = *mss_ptr;
__be16 new_val = bpf_htons(max_mss_host);
*mss_ptr = new_val;
tcp->check = csum16_replace(tcp->check, old_val, new_val);
return 1;
}
return 0;
}
opt += opt_len;
}
return 0;
}
// Programa XDP principal - simplificado
@@ -135,7 +190,8 @@ int xdp_wg_optimizer(struct xdp_md *ctx) {
// Verificar se temos espaço para o cabeçalho TCP
if (tcp_ptr + sizeof(struct tcphdr) <= data_end) {
struct tcphdr *tcp = tcp_ptr;
adjust_tcp_mss(tcp, data_end, MSS_CLAMPING_VALUE);
if (clamp_tcp_mss(tcp, data_end, MSS_CLAMPING_VALUE) && stats)
stats->mss_adjusted++;
}
}