From 06864531fd8ab70c6e9eee2abc21996fdb9bce1c Mon Sep 17 00:00:00 2001 From: Rodrigo Martins Date: Sun, 23 Mar 2025 21:07:24 -0300 Subject: [PATCH] commit Signed-off-by: Rodrigo Martins --- Makefile | 29 +++ bridge_xdp.c | 52 +++++ bridge_xdp.h | 15 ++ bridge_xdp_user.h | 9 + loader.c | 37 ++++ xdp_ddos_filter.c | 488 ++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 630 insertions(+) create mode 100644 Makefile create mode 100644 bridge_xdp.c create mode 100644 bridge_xdp.h create mode 100644 bridge_xdp_user.h create mode 100644 loader.c create mode 100644 xdp_ddos_filter.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..19e0c32 --- /dev/null +++ b/Makefile @@ -0,0 +1,29 @@ +LLC ?= llc +CLANG ?= clang +CC ?= gcc + +ARCH := arm64 +INCLUDES := -I/usr/include/bpf -I/usr/include/ -I/usr/include/x86_64-linux-gnu +LIBS := -lbpf -lelf -lz +CFLAGS := -g -O2 + +all: bridge_xdp.o loader + +bridge_xdp.o: bridge_xdp.c + $(CLANG) -S \ + -target bpf \ + -D __BPF_TRACING__ \ + $(INCLUDES) \ + -Wall \ + $(CFLAGS) \ + -g \ + -emit-llvm -c -o bridge_xdp.ll bridge_xdp.c + $(LLC) -march=bpf -mcpu=v3 -mattr=+alu32 -filetype=obj \ + -debugger-tune=gdb \ + -o bridge_xdp.o bridge_xdp.ll + +loader: loader.c + $(CC) -Wall $(INCLUDES) $(CFLAGS) -o loader loader.c $(LIBS) + +clean: + rm -f bridge_xdp.o bridge_xdp.ll loader diff --git a/bridge_xdp.c b/bridge_xdp.c new file mode 100644 index 0000000..6154633 --- /dev/null +++ b/bridge_xdp.c @@ -0,0 +1,52 @@ +#include +#include +#include +#include +#include "bridge_xdp.h" + +struct { + __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); + __uint(max_entries, 1); + __type(key, __u32); + __type(value, struct packet_stats); +} stats_map SEC(".maps"); + +SEC("xdp") +int bridge_filter(struct xdp_md *ctx) +{ + void *data_end = (void *)(long)ctx->data_end; + void *data = (void *)(long)ctx->data; + struct ethhdr *eth = data; + + // Verificação básica do header ethernet + if ((void*)(eth + 1) > data_end) + return XDP_ABORTED; + + // Atualizar estatísticas + __u32 key = 0; + struct packet_stats *stats = bpf_map_lookup_elem(&stats_map, &key); + if (stats) { + stats->packets++; + stats->bytes += (data_end - data); + } + + // Sempre permitir tráfego broadcast/multicast + if (eth->h_dest[0] & 1) + return XDP_PASS; + + // Verificar se é tráfego IPv4 + if (eth->h_proto == bpf_htons(ETH_P_IP)) { + struct iphdr *iph = (void*)(eth + 1); + if ((void*)(iph + 1) > data_end) + return XDP_PASS; // Se não conseguir verificar, passa pro kernel + + // Permitir todo tráfego local (entre containers) + if ((iph->daddr & 0xffff0000) == 0x0a0f0000) // 10.15.0.0/16 + return XDP_PASS; + } + + // Para outros protocolos, deixa passar + return XDP_PASS; +} + +char _license[] SEC("license") = "GPL"; diff --git a/bridge_xdp.h b/bridge_xdp.h new file mode 100644 index 0000000..1b36f49 --- /dev/null +++ b/bridge_xdp.h @@ -0,0 +1,15 @@ +#ifndef BRIDGE_XDP_H +#define BRIDGE_XDP_H + +#include +#include +#include +#include + +// Estrutura para estatísticas +struct packet_stats { + __u64 packets; + __u64 bytes; +}; + +#endif diff --git a/bridge_xdp_user.h b/bridge_xdp_user.h new file mode 100644 index 0000000..d01cccb --- /dev/null +++ b/bridge_xdp_user.h @@ -0,0 +1,9 @@ +#ifndef BRIDGE_XDP_USER_H +#define BRIDGE_XDP_USER_H + +struct packet_stats { + __u64 packets; + __u64 bytes; +}; + +#endif diff --git a/loader.c b/loader.c new file mode 100644 index 0000000..c8c4d47 --- /dev/null +++ b/loader.c @@ -0,0 +1,37 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include "bridge_xdp.h" + +int main(int argc, char **argv) +{ + if (argc != 2) { + printf("Uso: %s \n", argv[0]); + return 1; + } + + // Carregar programa XDP + struct bpf_object *obj = bpf_object__open("bridge_xdp.o"); + if (!obj) { + printf("Erro ao abrir objeto BPF\n"); + return 1; + } + + if (bpf_object__load(obj)) { + printf("Erro ao carregar objeto BPF\n"); + return 1; + } + + // Anexar à interface + int prog_fd = bpf_program__fd(bpf_object__find_program_by_name(obj, "bridge_filter")); + unsigned int flags = XDP_FLAGS_UPDATE_IF_NOEXIST | XDP_FLAGS_DRV_MODE; + if (bpf_xdp_attach(if_nametoindex(argv[1]), prog_fd, flags, NULL) != 0) { + printf("Erro ao anexar XDP\n"); + return 1; + } + + printf("XDP anexado com sucesso à interface %s\n", argv[1]); + return 0; +} diff --git a/xdp_ddos_filter.c b/xdp_ddos_filter.c new file mode 100644 index 0000000..e299640 --- /dev/null +++ b/xdp_ddos_filter.c @@ -0,0 +1,488 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Otimização: constantes ajustadas para seu ambiente específico +#define MAX_HIGH_PRIORITY_PACKETS_PER_SECOND 3000 // Aumentado para serviços críticos (HTTP/HTTPS) +#define MAX_LOW_PRIORITY_PACKETS_PER_SECOND 500 +#define MAX_PACKETS_PER_IP 200000 +#define MAX_ICMP_PACKETS_PER_IP 30 // Reduzido conforme política ICMP restritiva +#define MAX_SYN_PACKETS_PER_IP 80 // Reduzido para proteção preventiva +#define TIMEOUT_SEC 1 +#define PACKET_COST 1 +#define HIGH_PRIO_TOKEN_RATE 3000 // Aumentado para combinar com QoS HTB +#define LOW_PRIO_TOKEN_RATE 500 +#define MAX_TOKENS 3000 +#define DOCKER_NETWORK_PREFIX 0x0A0F0000 // 10.15.0.0 +#define DOCKER_NETWORK_MASK 0xFFFF0000 // /16 +#define WG_NETWORK_PREFIX 0x0A630100 // 10.99.1.0 +#define WG_NETWORK_MASK 0xFFFFFF00 // /24 +#define NGINX_SERVER_IP 0x0A0F002D // 10.15.0.45 + +// Estrutura de dados otimizada com tipos consistentes +struct ip_stats { + __u32 pkt_count; + __u32 last_seen; + __u32 icmp_count; + __u32 syn_count; + __u32 high_prio_tokens; + __u32 low_prio_tokens; + __u16 flags; // Expandido para mais flags + __u16 padding; // Mantém alinhamento 4-bytes +} __attribute__((packed)); + +// Estrutura para configuração de redes internas +struct network_config { + __u32 prefix; + __u32 mask; +}; + +// Estrutura para configuração de portas +struct port_config { + __u8 priority; // 0=baixa, 1=média, 2=alta + __u8 is_filtered; // 0=não, 1=sim +}; + +// Adicionar flags para comportamento de tráfego +#define FLAG_POTENTIAL_SCANNER 0x01 +#define FLAG_SYN_FLOOD 0x02 +#define FLAG_ICMP_FLOOD 0x04 +#define FLAG_MARKED_SUSPICIOUS 0x08 + +struct { + __uint(type, BPF_MAP_TYPE_LRU_HASH); + __uint(max_entries, 100000); + __type(key, __u32); + __type(value, struct ip_stats); +} ddos_map SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 1000); + __type(key, __u32); + __type(value, __u32); +} whitelist_map SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_LRU_HASH); + __uint(max_entries, 100000); + __type(key, __u32); + __type(value, __u32); +} blacklist_map SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 100); + __type(key, __u32); + __type(value, struct network_config); +} internal_networks SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 65536); + __type(key, __u16); + __type(value, struct port_config); +} port_config_map SEC(".maps"); + +// Mapa para tempos de expiração +struct { + __uint(type, BPF_MAP_TYPE_LRU_HASH); + __uint(max_entries, 100000); + __type(key, __u32); + __type(value, __u32); +} expiration_map SEC(".maps"); + +// Mapa para estatísticas de tráfego para integração com userspace +struct traffic_stats { + __u64 total_packets; + __u64 dropped_packets; + __u64 passed_packets; + __u64 blacklisted_count; + __u64 syn_flood_count; + __u64 icmp_flood_count; +}; + +struct { + __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); + __uint(max_entries, 1); + __type(key, __u32); + __type(value, struct traffic_stats); +} stats_map SEC(".maps"); + +// Fast-path para serviços críticos - pré-verifica IPs confiáveis +struct { + __uint(type, BPF_MAP_TYPE_LRU_HASH); + __uint(max_entries, 1000); + __type(key, __u32); + __type(value, __u8); // 1 = confiável +} trusted_hosts_map SEC(".maps"); + +// Mapa para interface de redirecionamento +struct { + __uint(type, BPF_MAP_TYPE_XSKMAP); + __uint(max_entries, 1); + __type(key, int); + __type(value, int); +} xsks_map SEC(".maps"); + +// Função otimizada para timestamp +static __always_inline __u32 get_time_sec(void) { + return bpf_ktime_get_ns() / 1000000000; +} + +static __always_inline int is_whitelisted(__u32 ip) { + return bpf_map_lookup_elem(&whitelist_map, &ip) != NULL; +} + +static __always_inline int is_blacklisted(__u32 ip) { + __u32 now = get_time_sec(); + __u32 *block_time = bpf_map_lookup_elem(&blacklist_map, &ip); + + if (block_time) { + // Se passou mais de 1 hora, remove da blacklist + if (now - *block_time > 3600) { + bpf_map_delete_elem(&blacklist_map, &ip); + return 0; + } + return 1; + } + return 0; +} + +static __always_inline int is_internal_network(__u32 ip) { + struct network_config *net; + __u32 key = 0; + + // Iteração limitada pelo verificador + #pragma unroll + for (int i = 0; i < 8; i++) { + key = i; + net = bpf_map_lookup_elem(&internal_networks, &key); + if (!net) + break; + + if ((ip & net->mask) == net->prefix) + return 1; + } + return 0; +} + +static __always_inline int get_port_priority(__u16 port) { + struct port_config *cfg = bpf_map_lookup_elem(&port_config_map, &port); + if (cfg) + return cfg->priority; + return 0; +} + +static __always_inline int is_filtered_port(__u16 port) { + struct port_config *cfg = bpf_map_lookup_elem(&port_config_map, &port); + if (cfg) + return cfg->is_filtered; + return (port <= 1024); +} + +// Função para atualizar estatísticas de tráfego +static __always_inline void update_stats(__u32 action, __u8 flags) { + __u32 key = 0; + struct traffic_stats *stats = bpf_map_lookup_elem(&stats_map, &key); + if (stats) { + stats->total_packets++; + + if (action == XDP_DROP) { + stats->dropped_packets++; + if (flags & FLAG_SYN_FLOOD) + stats->syn_flood_count++; + if (flags & FLAG_ICMP_FLOOD) + stats->icmp_flood_count++; + } else if (action == XDP_PASS) { + stats->passed_packets++; + } + + if (flags & FLAG_MARKED_SUSPICIOUS) + stats->blacklisted_count++; + } +} + +// Versão otimizada do programa principal +SEC("xdp") +int xdp_ddos_filter(struct xdp_md *ctx) { + void *data_end = (void *)(long)ctx->data_end; + void *data = (void *)(long)ctx->data; + __u8 flags = 0; + + // Otimização: verificação de tamanho mínimo do pacote mais eficiente + if (__builtin_expect(data + sizeof(struct ethhdr) + sizeof(struct iphdr) > data_end, 0)) + return XDP_PASS; + + struct ethhdr *eth = data; + if (__builtin_expect(eth->h_proto != __constant_htons(ETH_P_IP), 0)) + return XDP_PASS; + + struct iphdr *ip = (void *)(eth + 1); + if (__builtin_expect((void *)(ip + 1) > data_end || ip->ihl != 5 || ip->version != 4, 0)) + return XDP_PASS; + + __u32 src_ip = ip->saddr; + __u32 dst_ip = ip->daddr; + + // Fast path 1: Permitir tráfego de redes internas + if (__builtin_expect(is_internal_network(src_ip), 0)) { + return XDP_PASS; + } + + // Fast path 2: Verificar whitelist estática + if (__builtin_expect(is_whitelisted(src_ip), 0)) { + return XDP_PASS; + } + + // Fast path 3: WireGuard - verificação prioritária para UDP porta 32100 + if (ip->protocol == IPPROTO_UDP) { + struct udphdr *udp = (void *)(ip + 1); + if ((void *)(udp + 1) <= data_end) { + __u16 dest_port = __constant_ntohs(udp->dest); + + if (dest_port == 32100) { // WireGuard port + // Permitir todo tráfego WireGuard sem verificação adicional + update_stats(XDP_PASS, 0); + return XDP_PASS; + } + } + } + + // Fast path 4: Verificar IPs já confiáveis (hosts que já passaram por verificação) + __u8 *trusted = bpf_map_lookup_elem(&trusted_hosts_map, &src_ip); + if (__builtin_expect(trusted != NULL && *trusted == 1, 0)) { + return XDP_PASS; + } + + // Fast path 5: Rejeitar IPs na blacklist + if (__builtin_expect(is_blacklisted(src_ip), 0)) { + update_stats(XDP_DROP, FLAG_MARKED_SUSPICIOUS); + return XDP_DROP; + } + + // Ignorar pacotes que não são destinados ao nosso servidor NGINX ou serviços expostos + if (dst_ip != NGINX_SERVER_IP) { + // Verificar protocolo e porta para classificação de tráfego + __u16 dest_port = 0; + int is_syn = 0; + int is_icmp = 0; + + if (ip->protocol == IPPROTO_TCP) { + struct tcphdr *tcp = (void *)(ip + 1); + if (__builtin_expect((void *)(tcp + 1) > data_end, 0)) + return XDP_PASS; + + dest_port = __constant_ntohs(tcp->dest); + is_syn = tcp->syn && !tcp->ack; + + // Detecção de port scan - múltiplas flags ativas (inválido) + if ((tcp->syn && tcp->rst) || (tcp->fin && tcp->rst) || (tcp->fin && tcp->syn)) { + flags |= FLAG_POTENTIAL_SCANNER; + } + + } else if (ip->protocol == IPPROTO_UDP) { + struct udphdr *udp = (void *)(ip + 1); + if (__builtin_expect((void *)(udp + 1) > data_end, 0)) + return XDP_PASS; + + dest_port = __constant_ntohs(udp->dest); + + } else if (ip->protocol == IPPROTO_ICMP) { + is_icmp = 1; + // Verificar tipo de ICMP + struct icmphdr *icmp = (void *)(ip + 1); + if (__builtin_expect((void *)(icmp + 1) > data_end, 0)) + return XDP_PASS; + + // Permitir apenas echo-reply (0) e limitar echo-request (8) + if (icmp->type != 0 && icmp->type != 8) { + update_stats(XDP_DROP, 0); + return XDP_DROP; + } + } else { + // Outros protocolos passam sem filtro adicional + return XDP_PASS; + } + + // Otimização: Se não for para portas filtradas, deixar passar + if (__builtin_expect(!is_filtered_port(dest_port) && !is_icmp, 1)) { + // Se for um cliente fazendo várias conexões legítimas, marcá-lo como confiável + struct ip_stats *entry = bpf_map_lookup_elem(&ddos_map, &src_ip); + if (entry && entry->pkt_count > 5 && entry->flags == 0) { + __u8 mark_trusted = 1; + bpf_map_update_elem(&trusted_hosts_map, &src_ip, &mark_trusted, BPF_ANY); + } + return XDP_PASS; + } + + // Prioridade do pacote + int priority = get_port_priority(dest_port); + + // Rate limiting baseado em tokens + struct ip_stats *entry = bpf_map_lookup_elem(&ddos_map, &src_ip); + __u32 now = get_time_sec(); + + if (entry) { + // Cálculo de tokens disponíveis + __u32 elapsed = 0; + if (__builtin_expect(now > entry->last_seen, 1)) { + elapsed = now - entry->last_seen; + } + + // Implementação de token bucket + if (elapsed > 0) { + // Adicionar tokens com limite máximo + __u16 new_high_tokens = entry->high_prio_tokens + (HIGH_PRIO_TOKEN_RATE * elapsed); + entry->high_prio_tokens = new_high_tokens > MAX_TOKENS ? MAX_TOKENS : new_high_tokens; + + __u16 new_low_tokens = entry->low_prio_tokens + (LOW_PRIO_TOKEN_RATE * elapsed); + entry->low_prio_tokens = new_low_tokens > MAX_TOKENS ? MAX_TOKENS : new_low_tokens; + + // Resetar contadores se passou tempo suficiente + if (elapsed >= TIMEOUT_SEC) { + entry->icmp_count = 0; + entry->syn_count = 0; + entry->flags &= ~(FLAG_SYN_FLOOD | FLAG_ICMP_FLOOD); + } + } + + // Atualizar flags e contadores + entry->last_seen = now; + entry->pkt_count++; + + if (flags & FLAG_POTENTIAL_SCANNER) { + entry->flags |= FLAG_POTENTIAL_SCANNER; + } + + if (is_icmp) { + entry->icmp_count++; + if (__builtin_expect(entry->icmp_count > MAX_ICMP_PACKETS_PER_IP, 0)) { + entry->flags |= FLAG_ICMP_FLOOD; + __u32 one = 1; + bpf_map_update_elem(&blacklist_map, &src_ip, &one, BPF_ANY); + update_stats(XDP_DROP, FLAG_ICMP_FLOOD); + return XDP_DROP; + } + } + + if (is_syn) { + entry->syn_count++; + if (__builtin_expect(entry->syn_count > MAX_SYN_PACKETS_PER_IP, 0)) { + entry->flags |= FLAG_SYN_FLOOD; + __u32 one = 1; + bpf_map_update_elem(&blacklist_map, &src_ip, &one, BPF_ANY); + update_stats(XDP_DROP, FLAG_SYN_FLOOD); + return XDP_DROP; + } + } + + // Blacklist se exceder total de pacotes + if (__builtin_expect(entry->pkt_count > MAX_PACKETS_PER_IP, 0)) { + __u32 one = 1; + entry->flags |= FLAG_MARKED_SUSPICIOUS; + bpf_map_update_elem(&blacklist_map, &src_ip, &one, BPF_ANY); + update_stats(XDP_DROP, FLAG_MARKED_SUSPICIOUS); + return XDP_DROP; + } + + // Aplicar token bucket conforme prioridade + if (priority == 2) { // Alta prioridade + if (entry->high_prio_tokens >= PACKET_COST) { + entry->high_prio_tokens -= PACKET_COST; + update_stats(XDP_PASS, 0); + return XDP_PASS; + } + } else if (priority == 1) { // Média prioridade + // Média prioridade usa metade de cada bucket + if (entry->high_prio_tokens >= PACKET_COST/2 && + entry->low_prio_tokens >= PACKET_COST/2) { + entry->high_prio_tokens -= PACKET_COST/2; + entry->low_prio_tokens -= PACKET_COST/2; + update_stats(XDP_PASS, 0); + return XDP_PASS; + } + } else { // Baixa prioridade + if (entry->low_prio_tokens >= PACKET_COST) { + entry->low_prio_tokens -= PACKET_COST; + update_stats(XDP_PASS, 0); + return XDP_PASS; + } + } + + update_stats(XDP_DROP, 0); + return XDP_DROP; + + } else { + // Primeira vez que vemos este IP + struct ip_stats new_entry = { + .pkt_count = 1, + .last_seen = now, + .icmp_count = is_icmp ? 1 : 0, + .syn_count = is_syn ? 1 : 0, + .high_prio_tokens = MAX_TOKENS - PACKET_COST, + .low_prio_tokens = MAX_TOKENS - PACKET_COST, + .flags = flags + }; + + bpf_map_update_elem(&ddos_map, &src_ip, &new_entry, BPF_ANY); + update_stats(XDP_PASS, 0); + return XDP_PASS; + } + } + + // Processar pacotes destinados ao servidor NGINX + if (dst_ip == NGINX_SERVER_IP) { + struct ip_stats *entry = bpf_map_lookup_elem(&ddos_map, &src_ip); + __u32 now = get_time_sec(); + + // Verificar cabeçalhos TCP para tráfego HTTP(S) + if (ip->protocol == IPPROTO_TCP) { + struct tcphdr *tcp = (void *)(ip + 1); + if (__builtin_expect((void *)(tcp + 1) > data_end, 0)) + return XDP_PASS; + + __u16 dest_port = __constant_ntohs(tcp->dest); + if (dest_port == 80 || dest_port == 443) { + // Lógica específica para proteção de HTTP/HTTPS + + // Se for SYN, fazer rate limiting específico + if (tcp->syn && !tcp->ack) { + if (entry) { + entry->syn_count++; + if (entry->syn_count > MAX_SYN_PACKETS_PER_IP) { + update_stats(XDP_DROP, FLAG_SYN_FLOOD); + return XDP_DROP; + } + } else { + // Primeiro SYN deste IP + struct ip_stats new_entry = { + .pkt_count = 1, + .last_seen = now, + .syn_count = 1, + .high_prio_tokens = MAX_TOKENS - PACKET_COST, + .low_prio_tokens = MAX_TOKENS - PACKET_COST + }; + bpf_map_update_elem(&ddos_map, &src_ip, &new_entry, BPF_ANY); + } + } + + // Permitir o pacote (processamento de entrada HTTP é prioritário) + update_stats(XDP_PASS, 0); + return XDP_PASS; + } + } + } + + // Se chegou até aqui, passa o tráfego + update_stats(XDP_PASS, 0); + return XDP_PASS; +} + +char __license[] SEC("license") = "GPL"; \ No newline at end of file