commit c4cea8c3b326667f779e65acd3233c43e7197473 Author: Rodrigo Martins Date: Wed Feb 26 18:37:03 2025 -0300 first commit diff --git a/dnat_rules.txt b/dnat_rules.txt new file mode 100644 index 0000000..ab29c49 --- /dev/null +++ b/dnat_rules.txt @@ -0,0 +1,3 @@ +# Formato: +tcp 10.0.0.87:80 10.15.0.45:80 +tcp 10.0.0.87:443 10.15.0.45:443 \ No newline at end of file diff --git a/nat_config.py b/nat_config.py new file mode 100644 index 0000000..2983b5c --- /dev/null +++ b/nat_config.py @@ -0,0 +1,256 @@ +#!/usr/bin/env python3 + +import os +import sys +import socket +import struct +import json +import subprocess +import argparse +import ipaddress +import time +import re + +def run_cmd(cmd, show_output=False): + """Executa um comando shell e retorna sua saída""" + if show_output: + print(f"Executando: {cmd}") + + result = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + if result.returncode != 0: + print(f"Erro ao executar: {cmd}") + print(f"Saída de erro: {result.stderr}") + return None + return result.stdout.strip() + +def find_map_ids_by_name(map_name): + """Encontra todos os IDs de mapas BPF com um determinado nome""" + output = run_cmd("bpftool map list") + if not output: + return [] + + map_ids = [] + for line in output.split('\n'): + if f"name {map_name}" in line: + match = re.search(r'^(\d+):', line) + if match: + map_ids.append(match.group(1)) + + return map_ids + +def find_map_id(map_name): + """Encontra o primeiro ID de um mapa BPF pelo nome""" + map_ids = find_map_ids_by_name(map_name) + return map_ids[0] if map_ids else None + +def clean_old_maps(): + """Limpa todos os mapas antigos com nomes relacionados a NAT""" + map_names = ["dnat_port_map", "snat_networks", "hairpin_map"] + count = 0 + + for map_name in map_names: + map_ids = find_map_ids_by_name(map_name) + for map_id in map_ids: + print(f"Limpando mapa antigo: {map_name} (ID: {map_id})") + run_cmd(f"bpftool map dump id {map_id} -j | grep -o '\"key\": \"[^\"]*\"' | " + f"sed 's/\"key\": \"\\([^\"]*\\)\"/\\1/g' | while read key; do " + f"[ ! -z \"$key\" ] && bpftool map delete id {map_id} key hex $key; done") + count += 1 + + return count + +def load_tc_program(interface, object_file="tc_nat.o"): + """Carrega programas TC para interface especificada""" + print(f"Carregando programas TC para interface {interface}...") + + # Compilar programa BPF + print("Compilando programa TC BPF...") + cmd = f"clang -O2 -Wall -target bpf -g -D__TARGET_ARCH_arm64 -c tc_nat.c -o {object_file} -I/usr/include/bpf -I/usr/include/" + if run_cmd(cmd) is None: + return False + + # Remover programas existentes + run_cmd(f"tc filter del dev {interface} ingress 2>/dev/null") + run_cmd(f"tc filter del dev {interface} egress 2>/dev/null") + run_cmd(f"tc qdisc del dev {interface} clsact 2>/dev/null") + + # Configurar qdisc + run_cmd(f"tc qdisc add dev {interface} clsact") + + # Carregar programas TC + ingress_cmd = f"tc filter add dev {interface} ingress prio 1 handle 1 bpf da obj {object_file} sec tc_ingress" + egress_cmd = f"tc filter add dev {interface} egress prio 1 handle 1 bpf da obj {object_file} sec tc_egress" + + if run_cmd(ingress_cmd, show_output=True) is None or run_cmd(egress_cmd, show_output=True) is None: + return False + + print("Programas TC carregados com sucesso!") + print("Aguardando criação dos mapas...") + time.sleep(1) + return True + +def setup_dnat_rule_hex(map_id, external_port, internal_ip, internal_port): + """Configura uma regra DNAT usando strings hexadecimais diretamente""" + # Converter porta para hex (little-endian) + port_hex = f"{external_port & 0xff:02x} {(external_port >> 8) & 0xff:02x}" + + # Converter IP para hex (formato de rede) + octets = list(map(int, internal_ip.split('.'))) + ip_hex = f"{octets[3]:02x} {octets[2]:02x} {octets[1]:02x} {octets[0]:02x}" + + # Converter porta interna para hex (little-endian) + int_port_hex = f"{internal_port & 0xff:02x} {(internal_port >> 8) & 0xff:02x}" + + # Valor: IP (4 bytes) + porta (2 bytes) + padding (2 bytes) + value_hex = f"{ip_hex} {int_port_hex} 00 00" + + # Update map usando bpftool com valores hex + cmd = f"bpftool map update id {map_id} key hex {port_hex} value hex {value_hex}" + success = run_cmd(cmd, show_output=True) is not None + + if success: + print(f"Regra DNAT configurada: {external_port} -> {internal_ip}:{internal_port}") + else: + print(f"Falha ao configurar regra DNAT: {external_port} -> {internal_ip}:{internal_port}") + + return success + +def setup_snat_network_hex(map_id, network): + """Configura uma rede para SNAT usando strings hexadecimais diretamente""" + # Parse da rede (exemplo: "10.15.0.0/16") + ip, prefix = network.split('/') + prefix = int(prefix) + + if prefix != 16: + print(f"AVISO: Rede {network} não é /16. Apenas redes /16 são totalmente suportadas.") + + # Extrair octetos do IP + octets = list(map(int, ip.split('.'))) + + # Para /16, montamos o prefixo + network_hex = f"00 00 {octets[1]:02x} {octets[0]:02x}" + + # Valor é 1 byte: habilitado + value_hex = "01" # 1 = habilitado + + # Update map usando bpftool com valores hex + cmd = f"bpftool map update id {map_id} key hex {network_hex} value hex {value_hex}" + success = run_cmd(cmd, show_output=True) is not None + + if success: + print(f"Rede SNAT configurada: {network}") + else: + print(f"Falha ao configurar rede SNAT: {network}") + + return success + +def clear_map(map_id): + """Limpa todas as entradas de um mapa BPF usando comandos diretos""" + print(f"Limpando mapa ID {map_id}...") + + # Verificar se o mapa existe + if run_cmd(f"bpftool map show id {map_id}") is None: + print(f" Mapa ID {map_id} não encontrado") + return False + + # Obter conteúdo do mapa + map_content = run_cmd(f"bpftool map dump id {map_id}") + if not map_content or "key:" not in map_content: + print(" Mapa já está vazio") + return True + + # Usar abordagem direta para limpar + run_cmd(f"echo '{map_content}' | grep -o 'key: [0-9a-fx ]*' | sed 's/key: //g' | while read -r key; do [ ! -z \"$key\" ] && bpftool map delete id {map_id} key hex $key; done") + + # Verificar se o mapa está vazio + map_content = run_cmd(f"bpftool map dump id {map_id}") + if not map_content or "key:" not in map_content: + print(" Mapa limpo com sucesso") + return True + else: + print(" Não foi possível limpar completamente o mapa") + return False + +def main(): + parser = argparse.ArgumentParser(description="Configuração de NAT BPF para Docker") + parser.add_argument("interface", help="Interface de rede externa (ex: enp0s6)") + parser.add_argument("--target", default="10.15.0.45", help="IP do container alvo (ex: 10.15.0.45)") + parser.add_argument("--networks", default="10.15.0.0/16,172.17.0.0/16", + help="Redes para SNAT, separadas por vírgulas (ex: 10.15.0.0/16,172.17.0.0/16)") + parser.add_argument("--http-port", type=int, default=80, help="Porta HTTP para redirecionar") + parser.add_argument("--https-port", type=int, default=443, help="Porta HTTPS para redirecionar") + parser.add_argument("--reload", action="store_true", help="Recarregar o programa TC") + parser.add_argument("--clean", action="store_true", help="Limpar mapas antigos antes de configurar") + + args = parser.parse_args() + + print("=== Configuração de NAT BPF para Docker ===") + print(f"Interface: {args.interface}") + print(f"Container alvo: {args.target}") + print(f"Redes para SNAT: {args.networks}") + print(f"Portas: HTTP={args.http_port}, HTTPS={args.https_port}") + print("") + + # Limpar mapas antigos se solicitado + if args.clean: + print("Limpando mapas antigos...") + count = clean_old_maps() + print(f"Total de {count} mapas antigos foram limpos.") + + # Carregar programa TC se necessário + if args.reload: + if not load_tc_program(args.interface): + print("Falha ao carregar programas TC. Abortando.") + return 1 + + # Encontrar IDs dos mapas + dnat_map_id = find_map_id("dnat_port_map") + snat_map_id = find_map_id("snat_networks") + + if not dnat_map_id or not snat_map_id: + print("Mapas BPF não encontrados. Carregando programas TC...") + if not load_tc_program(args.interface): + print("Falha ao carregar programas TC. Abortando.") + return 1 + + # Tentar novamente após carregar os programas + dnat_map_id = find_map_id("dnat_port_map") + snat_map_id = find_map_id("snat_networks") + + if not dnat_map_id or not snat_map_id: + print("Ainda não foi possível encontrar os mapas. Verifique o carregamento dos programas TC.") + return 1 + + print(f"IDs dos mapas: DNAT={dnat_map_id}, SNAT={snat_map_id}") + + # Limpar mapas existentes + clear_map(dnat_map_id) + clear_map(snat_map_id) + + # Configurar regras DNAT usando método hexadecimal direto + setup_dnat_rule_hex(dnat_map_id, args.http_port, args.target, args.http_port) + setup_dnat_rule_hex(dnat_map_id, args.https_port, args.target, args.https_port) + + # Configurar redes SNAT + for network in args.networks.split(','): + setup_snat_network_hex(snat_map_id, network.strip()) + + # Habilitar IP forwarding + print("Habilitando IP forwarding...") + with open("/proc/sys/net/ipv4/ip_forward", "w") as f: + f.write("1") + + # Verificar configuração final + print("\nConfiguração final:") + print("Regras DNAT:") + os.system(f"bpftool map dump id {dnat_map_id}") + print("\nRedes SNAT:") + os.system(f"bpftool map dump id {snat_map_id}") + + print("\nNAT BPF configurado com sucesso!") + print("Para ver logs em tempo real: cat /sys/kernel/debug/tracing/trace_pipe") + + return 0 + +if __name__ == "__main__": + sys.exit(main()) diff --git a/snat_networks.txt b/snat_networks.txt new file mode 100644 index 0000000..ca3c2f7 --- /dev/null +++ b/snat_networks.txt @@ -0,0 +1,2 @@ +10.15.0.0/16 +172.17.0.0/16 \ No newline at end of file diff --git a/tc_nat.c b/tc_nat.c new file mode 100644 index 0000000..24f290c --- /dev/null +++ b/tc_nat.c @@ -0,0 +1,283 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Adicionar definição para ARM64 +#ifndef __TARGET_ARCH_arm64 +#define __TARGET_ARCH_arm64 +#endif + +// Estrutura para regras DNAT +struct dnat_rule { + __u32 new_ip; // IP destino após NAT + __u16 new_port; // Porta destino após NAT +}; + +// Mapa para regras DNAT baseadas na porta +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 64); + __type(key, __u16); // Porta destino + __type(value, struct dnat_rule); // Regra DNAT +} dnat_port_map SEC(".maps"); + +// Mapa para definir redes para SNAT (masquerade) +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 16); + __type(key, __u32); // Prefixo de rede (/16) + __type(value, __u8); // Flag (1=ativo) +} snat_networks SEC(".maps"); + +// Mapa para armazenar configurações de hairpinning +struct hairpin_key { + __u32 dst_ip; // IP destino + __u16 dst_port; // Porta destino +}; + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 64); + __type(key, struct hairpin_key); // IP e porta destino + __type(value, __u8); // Flag (1=ativo) +} hairpin_map SEC(".maps"); + +// Função para calcular checksum com bounds checking +static inline __u16 compute_ip_checksum(struct iphdr *iph) { + __u32 csum = 0; + __u16 *next_iph_u16 = (__u16 *)iph; + + // IP header sem o campo de checksum (5 words, 10 bytes) +#pragma unroll + for (int i = 0; i < 10; i++) { + csum += *next_iph_u16++; + } + + // Fold 32-bit to 16-bit + csum = (csum & 0xffff) + (csum >> 16); + csum = (csum & 0xffff) + (csum >> 16); + + return ~csum; +} + +// Gancho TC para INGRESS (DNAT) +SEC("tc_ingress") +int nat_ingress_func(struct __sk_buff *skb) { + // Cabeçalhos + void *data = (void *)(long)skb->data; + void *data_end = (void *)(long)skb->data_end; + + // Validação de ethernet + struct ethhdr *eth = data; + if ((void *)(eth + 1) > data_end) + return TC_ACT_OK; + + // Verificar se é IPv4 + if (eth->h_proto != bpf_htons(ETH_P_IP)) + return TC_ACT_OK; + + // Validação de cabeçalho IP + struct iphdr *ip = (void *)(eth + 1); + if ((void *)(ip + 1) > data_end) + return TC_ACT_OK; + + // Verificar tamanho do cabeçalho IP + if (ip->ihl != 5) // Apenas cabeçalho padrão sem opções + return TC_ACT_OK; + + // Depuração - imprimir informações do pacote + bpf_printk("INGRESS: IP Source %pI4, Dest %pI4", &ip->saddr, &ip->daddr); + + // DNAT para TCP + if (ip->protocol == IPPROTO_TCP) { + struct tcphdr *tcp = (void *)(ip + 1); + if ((void *)(tcp + 1) > data_end) + return TC_ACT_OK; + + __u16 dport = bpf_ntohs(tcp->dest); + bpf_printk("INGRESS: TCP port %d", dport); + + // Procurar regra para esta porta + struct dnat_rule *rule = bpf_map_lookup_elem(&dnat_port_map, &dport); + if (rule) { + bpf_printk("Regra encontrada: %d -> %pI4:%d", + dport, &rule->new_ip, rule->new_port); + + // Guardar o IP e porta originais + __u32 old_ip = ip->daddr; + __u16 old_port = tcp->dest; + + // Aplicar a regra DNAT + ip->daddr = rule->new_ip; + tcp->dest = bpf_htons(rule->new_port); + + // Recalcular checksum IP + ip->check = 0; + ip->check = compute_ip_checksum(ip); + + // Recalcular checksum TCP (parcialmente) + if (tcp->check) { + // Primeiro ajustar para o IP de destino alterado + __u32 check = ~bpf_ntohs(tcp->check); + check += ~bpf_ntohs(old_port) & 0xffff; + check += bpf_ntohs(tcp->dest); + check += ~((old_ip >> 16) & 0xffff) + ~(old_ip & 0xffff); + check += ((ip->daddr >> 16) & 0xffff) + (ip->daddr & 0xffff); + + // Dobrar para obter o checksum final + check = (check & 0xffff) + (check >> 16); + check = (check & 0xffff) + (check >> 16); + tcp->check = bpf_htons(~check); + } + + bpf_printk("DNAT aplicado: %pI4:%d -> %pI4:%d", + &old_ip, bpf_ntohs(old_port), + &ip->daddr, bpf_ntohs(tcp->dest)); + } + } + // DNAT para UDP + else if (ip->protocol == IPPROTO_UDP) { + struct udphdr *udp = (void *)(ip + 1); + if ((void *)(udp + 1) > data_end) + return TC_ACT_OK; + + __u16 dport = bpf_ntohs(udp->dest); + bpf_printk("INGRESS: UDP port %d", dport); + + // Procurar regra para esta porta + struct dnat_rule *rule = bpf_map_lookup_elem(&dnat_port_map, &dport); + if (rule) { + bpf_printk("Regra encontrada: %d -> %pI4:%d", + dport, &rule->new_ip, rule->new_port); + + // Guardar o IP e porta originais + __u32 old_ip = ip->daddr; + __u16 old_port = udp->dest; + + // Aplicar a regra DNAT + ip->daddr = rule->new_ip; + udp->dest = bpf_htons(rule->new_port); + + // Recalcular checksum IP + ip->check = 0; + ip->check = compute_ip_checksum(ip); + + // Recalcular checksum UDP (caso exista) + if (udp->check) { + // Ajuste similar ao do TCP + __u32 check = ~bpf_ntohs(udp->check); + check += ~bpf_ntohs(old_port) & 0xffff; + check += bpf_ntohs(udp->dest); + check += ~((old_ip >> 16) & 0xffff) + ~(old_ip & 0xffff); + check += ((ip->daddr >> 16) & 0xffff) + (ip->daddr & 0xffff); + + // Dobrar para obter o checksum final + check = (check & 0xffff) + (check >> 16); + check = (check & 0xffff) + (check >> 16); + udp->check = bpf_htons(~check); + } + + bpf_printk("DNAT aplicado: %pI4:%d -> %pI4:%d", + &old_ip, bpf_ntohs(old_port), + &ip->daddr, bpf_ntohs(udp->dest)); + } + } + + return TC_ACT_OK; +} + +// Gancho TC para EGRESS (SNAT/Masquerade) +SEC("tc_egress") +int nat_egress_func(struct __sk_buff *skb) { + // Cabeçalhos + void *data = (void *)(long)skb->data; + void *data_end = (void *)(long)skb->data_end; + + // Validação de ethernet + struct ethhdr *eth = data; + if ((void *)(eth + 1) > data_end) + return TC_ACT_OK; + + // Verificar se é IPv4 + if (eth->h_proto != bpf_htons(ETH_P_IP)) + return TC_ACT_OK; + + // Validação de cabeçalho IP + struct iphdr *ip = (void *)(eth + 1); + if ((void *)(ip + 1) > data_end) + return TC_ACT_OK; + + // Depuração - imprimir informações do pacote + bpf_printk("EGRESS: IP Source %pI4, Dest %pI4", &ip->saddr, &ip->daddr); + + // Verificar se o IP de origem está em nossas redes para SNAT/MASQUERADE + __u32 src_prefix = ip->saddr & 0xFFFF0000; // /16 + __u8 *do_snat = bpf_map_lookup_elem(&snat_networks, &src_prefix); + + // Processar SNAT/Masquerade apenas para pacotes de redes internas + if (do_snat && *do_snat) { + // Guardar o IP original + __u32 old_ip = ip->saddr; + + // IP fixo para SNAT: 10.0.0.87 + __u32 new_ip = 0x5700000A; // 10.0.0.87 em ordem de rede + + bpf_printk("SNAT: Aplicando masquerade para %pI4 -> %pI4", + &old_ip, &new_ip); + + // Aplicar SNAT + ip->saddr = new_ip; + + // Recalcular checksum IP + ip->check = 0; + ip->check = compute_ip_checksum(ip); + + // Recalcular checksums L4 (TCP ou UDP) + if (ip->protocol == IPPROTO_TCP) { + struct tcphdr *tcp = (void *)(ip + 1); + if ((void *)(tcp + 1) <= data_end && tcp->check) { + // Ajustar checksum TCP para o IP de origem alterado + __u32 check = ~bpf_ntohs(tcp->check); + check += ~((old_ip >> 16) & 0xffff) + ~(old_ip & 0xffff); + check += ((new_ip >> 16) & 0xffff) + (new_ip & 0xffff); + + // Dobrar para obter o checksum final + check = (check & 0xffff) + (check >> 16); + check = (check & 0xffff) + (check >> 16); + tcp->check = bpf_htons(~check); + + bpf_printk("SNAT TCP: porta %d, checksum atualizado", + bpf_ntohs(tcp->source)); + } + } + else if (ip->protocol == IPPROTO_UDP) { + struct udphdr *udp = (void *)(ip + 1); + if ((void *)(udp + 1) <= data_end && udp->check) { + // Ajustar checksum UDP para o IP de origem alterado + __u32 check = ~bpf_ntohs(udp->check); + check += ~((old_ip >> 16) & 0xffff) + ~(old_ip & 0xffff); + check += ((new_ip >> 16) & 0xffff) + (new_ip & 0xffff); + + // Dobrar para obter o checksum final + check = (check & 0xffff) + (check >> 16); + check = (check & 0xffff) + (check >> 16); + udp->check = bpf_htons(~check); + + bpf_printk("SNAT UDP: porta %d, checksum atualizado", + bpf_ntohs(udp->source)); + } + } + + bpf_printk("SNAT concluído: %pI4 -> %pI4", &old_ip, &ip->saddr); + } + + return TC_ACT_OK; +} + +char __license[] SEC("license") = "GPL"; diff --git a/xdp_accelerated_nat.c b/xdp_accelerated_nat.c new file mode 100644 index 0000000..4c78ea6 --- /dev/null +++ b/xdp_accelerated_nat.c @@ -0,0 +1,380 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +/* Definições para contornar erros de compilação */ +#ifndef __section +# define __section(NAME) \ + __attribute__((section(NAME), used)) +#endif + +/* Estrutura para conexões NAT */ +struct nat_conn { + __u32 orig_src_ip; // IP de origem original + __u16 orig_src_port; // Porta de origem original + __u32 orig_dst_ip; // IP de destino original + __u16 orig_dst_port; // Porta de destino original + __u32 new_src_ip; // Novo IP de origem (para SNAT) + __u16 new_src_port; // Nova porta de origem (para SNAT) + __u32 new_dst_ip; // Novo IP de destino (para DNAT) + __u16 new_dst_port; // Nova porta de destino (para DNAT) + __u8 proto; // Protocolo (TCP/UDP) + __u8 state; // Estado da conexão (1=ativo) + __u64 last_update; // Timestamp da última atualização +}; + +/* Chave para consulta rápida de DNAT em pacotes de entrada */ +struct dnat_key { + __u32 dst_ip; + __u16 dst_port; + __u8 proto; +}; + +/* Chave para busca de conexões de saída */ +struct conn_key { + __u32 src_ip; + __u16 src_port; + __u32 dst_ip; + __u16 dst_port; + __u8 proto; +}; + +/* Mapas BPF para NAT */ +struct { + __uint(type, BPF_MAP_TYPE_LRU_HASH); + __uint(max_entries, 65536); + __type(key, struct conn_key); + __type(value, struct nat_conn); +} conn_track_map SEC(".maps"); + +/* Mapa para regras DNAT estáticas */ +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 1024); + __type(key, struct dnat_key); + __type(value, struct nat_conn); +} dnat_rules SEC(".maps"); + +/* Mapa para redes que devem ter SNAT aplicado */ +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 256); + __type(key, __u32); // Prefixo de rede (/16) + __type(value, __u8); // Flag (1=ativo) +} snat_networks SEC(".maps"); + +/* Mapa para estatísticas */ +struct nat_stats { + __u64 processed; // Pacotes processados + __u64 snat_hits; // Acertos de SNAT + __u64 dnat_hits; // Acertos de DNAT + __u64 new_conns; // Novas conexões + __u64 errors; // Erros +}; + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(max_entries, 1); + __type(key, __u32); + __type(value, struct nat_stats); +} stats_map SEC(".maps"); + +/* Configuração para IP do gateway (usado para SNAT) */ +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(max_entries, 1); + __type(key, __u32); + __type(value, __u32); +} gateway_ip_map SEC(".maps"); + +/* Função para calcular IPv4 checksum */ +static __always_inline __u16 ip_checksum(struct iphdr *ip) { + __u32 csum = 0; + __u16 *next_ip_u16 = (__u16 *)ip; + __u16 pad_bytes = ip->ihl * 4; + + ip->check = 0; + + for (int i = 0; i < pad_bytes / 2; i++) { + csum += *next_ip_u16++; + } + + csum = (csum & 0xffff) + (csum >> 16); + csum = (csum & 0xffff) + (csum >> 16); + + return ~csum; +} + +/* Função para atualizar o checksum TCP/UDP após alteração de IP/porta */ +static __always_inline void update_l4_checksum(__u16 *csum, __u32 old_addr, __u32 new_addr, __u16 old_port, __u16 new_port) { + __u32 check = ~bpf_ntohs(*csum); + + // Ajustar para mudança de IP + check += ~((old_addr >> 16) & 0xffff) + ((new_addr >> 16) & 0xffff); + check += ~(old_addr & 0xffff) + (new_addr & 0xffff); + + // Ajustar para mudança de porta + check += ~old_port & 0xffff; + check += new_port; + + // Finalizar + check = (check & 0xffff) + (check >> 16); + check = (check & 0xffff) + (check >> 16); + *csum = bpf_htons(~check); +} + +/* Função para atualizar estatísticas */ +static __always_inline void update_stats(__u32 stat_type) { + __u32 key = 0; + struct nat_stats *stats; + + stats = bpf_map_lookup_elem(&stats_map, &key); + if (stats) { + switch (stat_type) { + case 0: stats->processed++; break; + case 1: stats->snat_hits++; break; + case 2: stats->dnat_hits++; break; + case 3: stats->new_conns++; break; + case 4: stats->errors++; break; + } + } +} + +/* Função para tentar DNAT em pacotes de entrada */ +static __always_inline int try_dnat(struct iphdr *ip, void *data_end, __u8 proto, __u16 dport, struct nat_conn *conn) { + struct dnat_key key = {0}; + struct nat_conn *rule; + + key.dst_ip = ip->daddr; + key.dst_port = dport; + key.proto = proto; + + // Buscar regra DNAT + rule = bpf_map_lookup_elem(&dnat_rules, &key); + if (!rule) { + return 0; // Nenhuma regra DNAT para este destino + } + + // Preencher a conexão com os valores da regra DNAT + conn->orig_dst_ip = ip->daddr; + conn->orig_dst_port = dport; + conn->new_dst_ip = rule->new_dst_ip; + conn->new_dst_port = rule->new_dst_port; + + // Aplicar DNAT + ip->daddr = conn->new_dst_ip; + + // Modificar a porta de destino dependendo do protocolo + if (proto == IPPROTO_TCP) { + struct tcphdr *tcp = (void *)(ip + 1); + if ((void *)(tcp + 1) > data_end) + return 0; + + tcp->dest = bpf_htons(conn->new_dst_port); + + // Atualizar checksum TCP + if (tcp->check) { + update_l4_checksum(&tcp->check, conn->orig_dst_ip, conn->new_dst_ip, + bpf_htons(conn->orig_dst_port), bpf_htons(conn->new_dst_port)); + } + } else if (proto == IPPROTO_UDP) { + struct udphdr *udp = (void *)(ip + 1); + if ((void *)(udp + 1) > data_end) + return 0; + + udp->dest = bpf_htons(conn->new_dst_port); + + // Atualizar checksum UDP se não for zero + if (udp->check) { + update_l4_checksum(&udp->check, conn->orig_dst_ip, conn->new_dst_ip, + bpf_htons(conn->orig_dst_port), bpf_htons(conn->new_dst_port)); + } + } + + // Recalcular IP checksum + ip->check = ip_checksum(ip); + + update_stats(2); // DNAT hit + return 1; +} + +/* Função para verificar e aplicar SNAT para redes internas */ +static __always_inline int should_apply_snat(__u32 src_ip) { + // Extrair o prefixo /16 + __u32 prefix = src_ip & 0xFFFF0000; + __u8 *flag; + + flag = bpf_map_lookup_elem(&snat_networks, &prefix); + if (flag && *flag == 1) { + return 1; + } + + return 0; +} + +/* Programa principal XDP para aceleração de NAT */ +SEC("xdp") +int xdp_accelerate_nat(struct xdp_md *ctx) { + void *data = (void *)(long)ctx->data; + void *data_end = (void *)(long)ctx->data_end; + struct ethhdr *eth = data; + + // Verificar limites do frame + if (data + sizeof(struct ethhdr) > data_end) + return XDP_PASS; + + // Só processa IPv4 + if (eth->h_proto != bpf_htons(ETH_P_IP)) + return XDP_PASS; + + struct iphdr *ip = (void *)(eth + 1); + if ((void *)(ip + 1) > data_end) + return XDP_PASS; + + // Ignorar pacotes não-TCP/UDP por enquanto + if (ip->protocol != IPPROTO_TCP && ip->protocol != IPPROTO_UDP) + return XDP_PASS; + + update_stats(0); // Pacote processado + + __u16 src_port = 0; + __u16 dst_port = 0; + + // Extrair portas base no protocolo + if (ip->protocol == IPPROTO_TCP) { + struct tcphdr *tcp = (void *)(ip + 1); + if ((void *)(tcp + 1) > data_end) + return XDP_PASS; + + src_port = bpf_ntohs(tcp->source); + dst_port = bpf_ntohs(tcp->dest); + } else if (ip->protocol == IPPROTO_UDP) { + struct udphdr *udp = (void *)(ip + 1); + if ((void *)(udp + 1) > data_end) + return XDP_PASS; + + src_port = bpf_ntohs(udp->source); + dst_port = bpf_ntohs(udp->dest); + } + + struct conn_key key = {0}; + struct nat_conn conn = {0}, *conn_ptr; + + // Criar chave de conexão + key.src_ip = ip->saddr; + key.dst_ip = ip->daddr; + key.src_port = src_port; + key.dst_port = dst_port; + key.proto = ip->protocol; + + // Procurar a conexão na tabela + conn_ptr = bpf_map_lookup_elem(&conn_track_map, &key); + + if (conn_ptr) { + // Conexão existente, atualizar timestamp + conn_ptr->last_update = bpf_ktime_get_ns(); + } else { + // Nova conexão potencial + __u32 gateway_key = 0; + __u32 *gateway_ip; + + // Preencher conexão original + conn.orig_src_ip = ip->saddr; + conn.orig_src_port = src_port; + conn.orig_dst_ip = ip->daddr; + conn.orig_dst_port = dst_port; + conn.proto = ip->protocol; + conn.state = 1; + conn.last_update = bpf_ktime_get_ns(); + + // Verificar se é um pacote de entrada que precisa de DNAT + if (try_dnat(ip, data_end, ip->protocol, dst_port, &conn)) { + // Atualizar campos de conexão após DNAT + key.dst_ip = conn.new_dst_ip; + key.dst_port = conn.new_dst_port; + } + // Verificar se é um pacote de saída que precisa de SNAT + else if (should_apply_snat(ip->saddr)) { + gateway_ip = bpf_map_lookup_elem(&gateway_ip_map, &gateway_key); + if (!gateway_ip) { + update_stats(4); // Erro - gateway IP não configurado + return XDP_PASS; // Sem IP gateway, não pode fazer SNAT + } + + conn.new_src_ip = *gateway_ip; + conn.new_src_port = src_port; // Usar mesma porta por enquanto + + // Aplicar SNAT + __u32 old_ip = ip->saddr; + ip->saddr = conn.new_src_ip; + + // Atualizar checksum de nível 4 + if (ip->protocol == IPPROTO_TCP) { + struct tcphdr *tcp = (void *)(ip + 1); + if ((void *)(tcp + 1) > data_end) + return XDP_PASS; + + if (tcp->check) { + update_l4_checksum(&tcp->check, old_ip, conn.new_src_ip, + bpf_htons(src_port), bpf_htons(conn.new_src_port)); + } + } else if (ip->protocol == IPPROTO_UDP) { + struct udphdr *udp = (void *)(ip + 1); + if ((void *)(udp + 1) > data_end) + return XDP_PASS; + + if (udp->check) { + update_l4_checksum(&udp->check, old_ip, conn.new_src_ip, + bpf_htons(src_port), bpf_htons(conn.new_src_port)); + } + } + + // Recalcular IP checksum + ip->check = ip_checksum(ip); + + update_stats(1); // SNAT hit + } else { + // Pacote normal sem NAT, apenas passa + return XDP_PASS; + } + + // Criar conexão reversa para resposta + struct conn_key reverse_key = {0}; + struct nat_conn reverse_conn = {0}; + + // Configurar chave reversa + reverse_key.src_ip = conn.new_dst_ip ? conn.new_dst_ip : ip->daddr; + reverse_key.src_port = conn.new_dst_port ? conn.new_dst_port : dst_port; + reverse_key.dst_ip = conn.new_src_ip ? conn.new_src_ip : ip->saddr; + reverse_key.dst_port = conn.new_src_port ? conn.new_src_port : src_port; + reverse_key.proto = ip->protocol; + + // Configurar conexão reversa + reverse_conn.orig_src_ip = reverse_key.src_ip; + reverse_conn.orig_src_port = reverse_key.src_port; + reverse_conn.orig_dst_ip = reverse_key.dst_ip; + reverse_conn.orig_dst_port = reverse_key.dst_port; + reverse_conn.new_src_ip = conn.orig_dst_ip; + reverse_conn.new_src_port = conn.orig_dst_port; + reverse_conn.new_dst_ip = conn.orig_src_ip; + reverse_conn.new_dst_port = conn.orig_src_port; + reverse_conn.proto = ip->protocol; + reverse_conn.state = 1; + reverse_conn.last_update = conn.last_update; + + // Inserir ambas as conexões (original e reversa) no mapa + bpf_map_update_elem(&conn_track_map, &key, &conn, BPF_ANY); + bpf_map_update_elem(&conn_track_map, &reverse_key, &reverse_conn, BPF_ANY); + + update_stats(3); // Nova conexão + } + + return XDP_PASS; +} + +char _license[] SEC("license") = "GPL"; diff --git a/xdp_ddos_bak.c b/xdp_ddos_bak.c new file mode 100644 index 0000000..dd00a35 --- /dev/null +++ b/xdp_ddos_bak.c @@ -0,0 +1,270 @@ +#include +#include +#include +#include +#include +#include +#include + +#define MAX_HIGH_PRIORITY_PACKETS_PER_SECOND 2000 // Limite de pacotes por segundo para alta prioridade +#define MAX_LOW_PRIORITY_PACKETS_PER_SECOND 500 // Limite de pacotes por segundo para baixa prioridade +#define MAX_PACKETS_PER_IP 60000 // Limite de pacotes por IP antes de bloquear +#define MAX_ICMP_PACKETS_PER_IP 50 // Limite de pacotes ICMP por IP antes de bloquear +#define MAX_SYN_PACKETS_PER_IP 100 // Limite de pacotes SYN por IP antes de bloquear +#define TIMEOUT_SEC 1 // Tempo para expiração do contador (em segundos) + +// Estrutura para armazenar contagem de pacotes e o último timestamp visto para um IP +struct ip_stats { + __u64 pkt_count; // Contador de pacotes + __u64 icmp_count; // Contador de pacotes ICMP + __u64 syn_count; // Contador de pacotes SYN + __u64 last_seen; // Timestamp do último pacote + __u64 high_priority_count; // Contador de pacotes de alta prioridade + __u64 low_priority_count; // Contador de pacotes de baixa prioridade +}; + +// Mapa de hash para armazenar estatísticas de IPs +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 100000); // Número máximo de entradas no mapa + __type(key, __u32); // Tipo da chave (endereço IP) + __type(value, struct ip_stats); // Tipo do valor (estatísticas do IP) +} ddos_map SEC(".maps"); + +// Mapa de hash para armazenar a blacklist +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 100000); // Número máximo de entradas no mapa + __type(key, __u32); // Tipo da chave (endereço IP) + __type(value, __u32); // Tipo do valor (dummy value) +} blacklist_map SEC(".maps"); + +// Lista de permissões estática (whitelist) de IPs confiáveis +const __u32 whitelist[] = { + 0x0A000001, // 10.0.0.1 + 0x0A630101, // 192.168.0.1 + 0x0a630102, + 0x0a630104 +}; + +// Função auxiliar para obter o timestamp atual em nanossegundos +static __inline __u64 get_time_ns(void) { + return bpf_ktime_get_ns(); +} + +// Função para verificar se o IP está na lista de permissões +static __inline int is_whitelisted(__u32 ip) { + int i; + for (i = 0; i < sizeof(whitelist) / sizeof(whitelist[0]); i++) { + if (ip == whitelist[i]) { + return 1; + } + } + return 0; +} + +// Função para verificar se o IP está na lista negra +static __inline int is_blacklisted(__u32 ip) { + __u32 *value = bpf_map_lookup_elem(&blacklist_map, &ip); + return value != NULL; +} + +// Função para verificar se a porta é de alta prioridade +static __inline int is_high_priority_port(__u16 port) { + return (port == 80 || port == 443 || port == 32100 || port == 32400); +} + +// Função para verificar se a porta é de baixa prioridade +static __inline int is_low_priority_port(__u16 port) { + return !is_high_priority_port(port); +} + +// Função para verificar se a porta deve ser filtrada +static __inline int is_filtered_port(__u16 port) { + // Intervalo de portas 1-1024 + if (port >= 1 && port <= 1024) { + return 1; + } + // Portas específicas + if (port == 8117 || port == 8085 || port == 8086 || port == 32100 || port == 32400) { + return 1; + } + return 0; +} + +// Função para verificar se o pacote é destinado ao servidor e deve ser filtrado +static __inline int is_destined_to_server(struct iphdr *ip, void *data_end) { + if (ip->protocol == IPPROTO_TCP) { + struct tcphdr *tcp = (void *)(ip + 1); + if ((void *)(tcp + 1) > data_end) + return 0; + // Verifique se a porta está na lista de portas a serem filtradas + __u16 dest_port = __constant_ntohs(tcp->dest); + if (is_filtered_port(dest_port)) { + return 1; + } + } else if (ip->protocol == IPPROTO_UDP) { + struct udphdr *udp = (void *)(ip + 1); + if ((void *)(udp + 1) > data_end) + return 0; + // Verifique se a porta está na lista de portas a serem filtradas + __u16 dest_port = __constant_ntohs(udp->dest); + if (is_filtered_port(dest_port)) { + return 1; + } + } else if (ip->protocol == IPPROTO_ICMP) { + return 1; + } + return 0; +} + +// Programa principal XDP +SEC("xdp") +int xdp_ddos_filter(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; + + // Verifica se é um pacote IP + if (eth->h_proto != __constant_htons(ETH_P_IP)) + return XDP_PASS; + + struct iphdr *ip = (void *)(eth + 1); + if ((void *)(ip + 1) > data_end) + return XDP_PASS; + + // Verifica se o cabeçalho IP é válido + if (ip->ihl != 5 || ip->version != 4) + return XDP_PASS; + + // Verifica se o IP está na lista de permissões + __u32 src_ip = ip->saddr; + if (is_whitelisted(src_ip)) { + return XDP_PASS; + } + + // Verifica se o IP está na lista negra + if (is_blacklisted(src_ip)) { + return XDP_DROP; + } + + // Verifica se o pacote é destinado ao servidor e deve ser filtrado + if (!is_destined_to_server(ip, data_end)) + return XDP_PASS; + + __u16 dest_port = 0; + struct tcphdr *tcp = NULL; + + // Verifica se é um pacote TCP ou UDP + if (ip->protocol == IPPROTO_TCP) { + tcp = (void *)(ip + 1); + if ((void *)(tcp + 1) > data_end) + return XDP_PASS; + dest_port = __constant_ntohs(tcp->dest); + + // Verifica se é um pacote SYN + if (tcp->syn && !tcp->ack) { + // Consulta o mapa para verificar se o IP já está registrado + struct ip_stats *entry = bpf_map_lookup_elem(&ddos_map, &src_ip); + + if (entry) { + __u64 now = get_time_ns(); + + // Reinicia o contador se o tempo decorrido for maior que um segundo + if ((now - entry->last_seen) > TIMEOUT_SEC * 1000000000ULL) { + entry->syn_count = 0; + entry->last_seen = now; + } + + // Incrementa o contador de pacotes SYN + entry->syn_count++; + entry->last_seen = now; + + // Bloqueia o tráfego se o limite de pacotes SYN for excedido + if (entry->syn_count > MAX_SYN_PACKETS_PER_IP) { + return XDP_DROP; + } + } else { + // Adiciona um novo IP ao mapa + struct ip_stats new_entry = { + .syn_count = 1, + .last_seen = get_time_ns() + }; + bpf_map_update_elem(&ddos_map, &src_ip, &new_entry, BPF_ANY); + } + } + } else if (ip->protocol == IPPROTO_UDP) { + struct udphdr *udp = (void *)(ip + 1); + if ((void *)(udp + 1) > data_end) + return XDP_PASS; + dest_port = __constant_ntohs(udp->dest); + } + + // Consulta o mapa para verificar se o IP já está registrado + struct ip_stats *entry = bpf_map_lookup_elem(&ddos_map, &src_ip); + + if (entry) { + __u64 now = get_time_ns(); + + // Reinicia o contador se o tempo decorrido for maior que um segundo + if ((now - entry->last_seen) > TIMEOUT_SEC * 1000000000ULL) { + entry->pkt_count = 0; + entry->icmp_count = 0; + entry->syn_count = 0; + entry->high_priority_count = 0; + entry->low_priority_count = 0; + entry->last_seen = now; + } + + // Rate limiting para pacotes de alta prioridade + if (is_high_priority_port(dest_port)) { + entry->high_priority_count++; + if (entry->high_priority_count > MAX_HIGH_PRIORITY_PACKETS_PER_SECOND) { + return XDP_DROP; + } + } + + // Rate limiting para pacotes de baixa prioridade + if (is_low_priority_port(dest_port)) { + entry->low_priority_count++; + if (entry->low_priority_count > MAX_LOW_PRIORITY_PACKETS_PER_SECOND) { + return XDP_DROP; + } + } + + // Incrementa o contador de pacotes + entry->pkt_count++; + entry->last_seen = now; + + // Filtra pacotes ICMP + if (ip->protocol == IPPROTO_ICMP) { + entry->icmp_count++; + if (entry->icmp_count > MAX_ICMP_PACKETS_PER_IP) { + return XDP_DROP; + } + } + + // Bloqueia o tráfego se o limite de pacotes for excedido + if (entry->pkt_count > MAX_PACKETS_PER_IP) { + return XDP_DROP; + } + } else { + // Adiciona um novo IP ao mapa + struct ip_stats new_entry = { + .pkt_count = 1, + .icmp_count = (ip->protocol == IPPROTO_ICMP) ? 1 : 0, + .syn_count = (ip->protocol == IPPROTO_TCP && tcp && tcp->syn && !tcp->ack) ? 1 : 0, + .last_seen = get_time_ns(), + .high_priority_count = is_high_priority_port(dest_port) ? 1 : 0, + .low_priority_count = is_low_priority_port(dest_port) ? 1 : 0 + }; + bpf_map_update_elem(&ddos_map, &src_ip, &new_entry, BPF_ANY); + } + + return XDP_PASS; +} + +char __license[] SEC("license") = "GPL"; \ No newline at end of file diff --git a/xdp_ddos_filter copy.c b/xdp_ddos_filter copy.c new file mode 100644 index 0000000..f3c4b43 --- /dev/null +++ b/xdp_ddos_filter copy.c @@ -0,0 +1,280 @@ +#include +#include +#include +#include +#include +#include +#include + +#define MAX_HIGH_PRIORITY_PACKETS_PER_SECOND 10000 // Limite de pacotes por segundo para alta prioridade +#define MAX_LOW_PRIORITY_PACKETS_PER_SECOND 2000 // Limite de pacotes por segundo para baixa prioridade +#define MAX_PACKETS_PER_IP 100000 // Limite de pacotes por IP antes de bloquear +#define MAX_ICMP_PACKETS_PER_IP 200 // Limite de pacotes ICMP por IP antes de bloquear +#define MAX_SYN_PACKETS_PER_IP 500 // Limite de pacotes SYN por IP antes de bloquear +#define TIMEOUT_SEC 5 // Tempo para expiração do contador (em segundos) + +// Estrutura para armazenar contagem de pacotes e o último timestamp visto para um IP +struct ip_stats { + __u64 last_seen; // Campo mais acessado primeiro + __u32 pkt_count; // Reduzido para u32 + __u16 high_priority_count; + __u16 low_priority_count; + __u16 icmp_count; + __u16 syn_count; + __u32 pad; // Padding para alinhamento de 8 bytes +} __attribute__((aligned(8))); + +// Mapa de hash para armazenar estatísticas de IPs +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 100000); // Número máximo de entradas no mapa + __type(key, __u32); // Tipo da chave (endereço IP) + __type(value, struct ip_stats); // Tipo do valor (estatísticas do IP) +} ddos_map SEC(".maps"); + +// Mapa de hash para armazenar a blacklist +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 100000); // Número máximo de entradas no mapa + __type(key, __u32); // Tipo da chave (endereço IP) + __type(value, __u32); // Tipo do valor (dummy value) +} blacklist_map SEC(".maps"); + +// Lista de permissões estática (whitelist) de IPs confiáveis +const __u32 whitelist[] = { + 0x0A000001, // 10.0.0.1 + 0x0A630101, // 192.168.0.1 + 0x0a630102, + 0x0a630104 +}; + +// Função auxiliar para obter o timestamp atual em nanossegundos +static __inline __u64 get_time_ns(void) { + return bpf_ktime_get_ns(); +} + +// Função para verificar se o IP está na lista de permissões +static __inline int is_whitelisted(__u32 ip) { + int i; + for (i = 0; i < sizeof(whitelist) / sizeof(whitelist[0]); i++) { + if (ip == whitelist[i]) { + return 1; + } + } + return 0; +} + +// Função para verificar se o IP está na lista negra +static __inline int is_blacklisted(__u32 ip) { + __u32 *value = bpf_map_lookup_elem(&blacklist_map, &ip); + return value != NULL; +} + +// Otimização: Função de verificação de porta simplificada usando switch +static __inline int is_high_priority_port(__u16 port) { + switch (port) { + case 32100: + case 80: + case 443: + case 3306: + case 5432: + case 8443: + return 1; + default: + return 0; + } +} + +// Função para verificar se a porta é de baixa prioridade +static __inline int is_low_priority_port(__u16 port) { + return !is_high_priority_port(port); +} + +// Otimização: Função de verificação de porta filtrada simplificada +static __inline int is_filtered_port(__u16 port) { + // Early return para portas privilegiadas + if (port <= 1024) return 1; + + switch (port) { + case 32100: + case 8080: + case 8443: + case 10050: + case 10051: + case 8085: + case 8117: + return 1; + default: + return 0; + } +} + +// Otimização: Função is_destined_to_server com early returns +static __inline int is_destined_to_server(struct iphdr *ip, void *data_end) { + // Early return para ICMP + if (ip->protocol == IPPROTO_ICMP) return 1; + + __u16 dest_port; + + if (ip->protocol == IPPROTO_TCP) { + struct tcphdr *tcp = (void *)(ip + 1); + if ((void *)(tcp + 1) > data_end) + return 0; + dest_port = __constant_ntohs(tcp->dest); + } else if (ip->protocol == IPPROTO_UDP) { + struct udphdr *udp = (void *)(ip + 1); + if ((void *)(udp + 1) > data_end) + return 0; + dest_port = __constant_ntohs(udp->dest); + } else { + return 0; + } + + return is_filtered_port(dest_port); +} + +// Programa principal XDP otimizado +SEC("xdp") +int xdp_ddos_filter(struct xdp_md *ctx) { + void *data_end = (void *)(long)ctx->data_end; + void *data = (void *)(long)ctx->data; + + // Early returns para validação de cabeçalhos + struct ethhdr *eth = data; + if ((void *)(eth + 1) > data_end) + return XDP_PASS; + + if (eth->h_proto != __constant_htons(ETH_P_IP)) + return XDP_PASS; + + struct iphdr *ip = (void *)(eth + 1); + if ((void *)(ip + 1) > data_end || ip->ihl != 5 || ip->version != 4) + return XDP_PASS; + + __u32 src_ip = ip->saddr; + + // Early return para IPs permitidos + if (is_whitelisted(src_ip)) + return XDP_PASS; + + // Early return para IPs bloqueados + if (is_blacklisted(src_ip)) + return XDP_DROP; + + // Verifica se o pacote é destinado ao servidor e deve ser filtrado + if (!is_destined_to_server(ip, data_end)) + return XDP_PASS; + + __u16 dest_port = 0; + struct tcphdr *tcp = NULL; + + // Verifica se é um pacote TCP ou UDP + if (ip->protocol == IPPROTO_TCP) { + tcp = (void *)(ip + 1); + if ((void *)(tcp + 1) > data_end) + return XDP_PASS; + dest_port = __constant_ntohs(tcp->dest); + + // Verifica se é um pacote SYN + if (tcp->syn && !tcp->ack) { + // Consulta o mapa para verificar se o IP já está registrado + struct ip_stats *entry = bpf_map_lookup_elem(&ddos_map, &src_ip); + + if (entry) { + __u64 now = get_time_ns(); + + // Reinicia o contador se o tempo decorrido for maior que um segundo + if ((now - entry->last_seen) > TIMEOUT_SEC * 1000000000ULL) { + entry->syn_count = 0; + entry->last_seen = now; + } + + // Incrementa o contador de pacotes SYN + entry->syn_count++; + entry->last_seen = now; + + // Bloqueia o tráfego se o limite de pacotes SYN for excedido + if (entry->syn_count > MAX_SYN_PACKETS_PER_IP) { + return XDP_DROP; + } + } else { + // Adiciona um novo IP ao mapa + struct ip_stats new_entry = { + .syn_count = 1, + .last_seen = get_time_ns() + }; + bpf_map_update_elem(&ddos_map, &src_ip, &new_entry, BPF_ANY); + } + } + } else if (ip->protocol == IPPROTO_UDP) { + struct udphdr *udp = (void *)(ip + 1); + if ((void *)(udp + 1) > data_end) + return XDP_PASS; + dest_port = __constant_ntohs(udp->dest); + } + + // Consulta o mapa para verificar se o IP já está registrado + struct ip_stats *entry = bpf_map_lookup_elem(&ddos_map, &src_ip); + + if (entry) { + __u64 now = get_time_ns(); + + // Reinicia o contador se o tempo decorrido for maior que um segundo + if ((now - entry->last_seen) > TIMEOUT_SEC * 1000000000ULL) { + entry->pkt_count = 0; + entry->icmp_count = 0; + entry->syn_count = 0; + entry->high_priority_count = 0; + entry->low_priority_count = 0; + entry->last_seen = now; + } + + // Rate limiting para pacotes de alta prioridade + if (is_high_priority_port(dest_port)) { + entry->high_priority_count++; + if (entry->high_priority_count > MAX_HIGH_PRIORITY_PACKETS_PER_SECOND) { + return XDP_DROP; + } + } + + // Rate limiting para pacotes de baixa prioridade + if (is_low_priority_port(dest_port)) { + entry->low_priority_count++; + if (entry->low_priority_count > MAX_LOW_PRIORITY_PACKETS_PER_SECOND) { + return XDP_DROP; + } + } + + // Incrementa o contador de pacotes + entry->pkt_count++; + entry->last_seen = now; + + // Filtra pacotes ICMP + if (ip->protocol == IPPROTO_ICMP) { + entry->icmp_count++; + if (entry->icmp_count > MAX_ICMP_PACKETS_PER_IP) { + return XDP_DROP; + } + } + + // Bloqueia o tráfego se o limite de pacotes for excedido + if (entry->pkt_count > MAX_PACKETS_PER_IP) { + return XDP_DROP; + } + } else { + // Adiciona um novo IP ao mapa + struct ip_stats new_entry = { + .pkt_count = 1, + .icmp_count = (ip->protocol == IPPROTO_ICMP) ? 1 : 0, + .syn_count = (ip->protocol == IPPROTO_TCP && tcp && tcp->syn && !tcp->ack) ? 1 : 0, + .last_seen = get_time_ns(), + .high_priority_count = is_high_priority_port(dest_port) ? 1 : 0, + .low_priority_count = is_low_priority_port(dest_port) ? 1 : 0 + }; + bpf_map_update_elem(&ddos_map, &src_ip, &new_entry, BPF_ANY); + } + + return XDP_PASS; +} + +char __license[] SEC("license") = "GPL"; \ No newline at end of file diff --git a/xdp_ddos_filter.c b/xdp_ddos_filter.c new file mode 100644 index 0000000..62a5dca --- /dev/null +++ b/xdp_ddos_filter.c @@ -0,0 +1,323 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_HIGH_PRIORITY_PACKETS_PER_SECOND 2000 // Limite de pacotes por segundo para alta prioridade +#define MAX_LOW_PRIORITY_PACKETS_PER_SECOND 500 // Limite de pacotes por segundo para baixa prioridade +#define MAX_PACKETS_PER_IP 60000 // Limite de pacotes por IP antes de bloquear +#define MAX_ICMP_PACKETS_PER_IP 50 // Limite de pacotes ICMP por IP antes de bloquear +#define MAX_SYN_PACKETS_PER_IP 100 // Limite de pacotes SYN por IP antes de bloquear +#define TIMEOUT_SEC 1 // Tempo para expiração do contador (em segundos) + +// Estrutura para armazenar contagem de pacotes e o último timestamp visto para um IP +struct ip_stats { + __u64 pkt_count; // Contador de pacotes + __u64 icmp_count; // Contador de pacotes ICMP + __u64 syn_count; // Contador de pacotes SYN + __u64 last_seen; // Timestamp do último pacote + __u64 high_priority_count; // Contador de pacotes de alta prioridade + __u64 low_priority_count; // Contador de pacotes de baixa prioridade +}; + +// Estruturas para NAT +struct nat_entry { + __u32 new_ip; // IP de destino após NAT + __u16 new_port; // Porta de destino após NAT + __u16 proto; // Protocolo (TCP/UDP) +} __attribute__((aligned(8))); + +// Modificar mapa DNAT para usar porta como chave +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 10000); + __type(key, __u16); // Porta de destino + __type(value, struct nat_entry); // Nova configuração +} dnat_map SEC(".maps"); + +struct masquerade_key { + __u32 src_ip; // IP de origem + __u32 dst_ip; // IP de destino + __u16 dst_port; // Porta de destino + __u16 proto; // Protocolo +} __attribute__((aligned(8))); + +// Mapa de hash para armazenar estatísticas de IPs +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 100000); // Número máximo de entradas no mapa + __type(key, __u32); // Tipo da chave (endereço IP) + __type(value, struct ip_stats); // Tipo do valor (estatísticas do IP) +} ddos_map SEC(".maps"); + +// Mapa de hash para armazenar a blacklist +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 100000); // Número máximo de entradas no mapa + __type(key, __u32); // Tipo da chave (endereço IP) + __type(value, __u32); // Tipo do valor (dummy value) +} blacklist_map SEC(".maps"); + +// Mapas adicionais para NAT +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 10000); + __type(key, struct masquerade_key); + __type(value, __u32); // IP mascarado +} masquerade_map SEC(".maps"); + +// Adicionar estrutura para SNAT +struct snat_entry { + __u32 new_ip; // IP após NAT + __u16 new_port; // Porta após NAT +} __attribute__((aligned(8))); + +// Adicionar mapa para SNAT +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 10000); + __type(key, struct masquerade_key); + __type(value, struct snat_entry); +} snat_map SEC(".maps"); + +// Lista de permissões estática (whitelist) de IPs confiáveis +const __u32 whitelist[] = { + 0x0A000001, // 10.0.0.1 + 0x0A630101, // 192.168.0.1 + 0x0a630102, + 0x0a630104 +}; + +// Adicionar 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 auxiliar para obter o timestamp atual em nanossegundos +static __inline __u64 get_time_ns(void) { + return bpf_ktime_get_ns(); +} + +// Função para verificar se o IP está na lista de permissões +static __inline int is_whitelisted(__u32 ip) { + int i; + for (i = 0; i < sizeof(whitelist) / sizeof(whitelist[0]); i++) { + if (ip == whitelist[i]) { + return 1; + } + } + return 0; +} + +// Função para verificar se o IP está na lista negra +static __inline int is_blacklisted(__u32 ip) { + __u32 *value = bpf_map_lookup_elem(&blacklist_map, &ip); + return value != NULL; +} + +// Função para verificar se a porta é de alta prioridade +static __inline int is_high_priority_port(__u16 port) { + return (port == 80 || port == 443 || port == 32100 || port == 32400); +} + +// Função para verificar se a porta é de baixa prioridade +static __inline int is_low_priority_port(__u16 port) { + return !is_high_priority_port(port); +} + +// Função para verificar se a porta deve ser filtrada +static __inline int is_filtered_port(__u16 port) { + // Intervalo de portas 1-1024 + if (port >= 1 && port <= 1024) { + return 1; + } + // Portas específicas + if (port == 8117 || port == 8085 || port == 8086 || port == 32100 || port == 32400) { + return 1; + } + return 0; +} + +// Função para verificar se o pacote é destinado ao servidor e deve ser filtrado +static __inline int is_destined_to_server(struct iphdr *ip, void *data_end) { + if (ip->protocol == IPPROTO_TCP) { + struct tcphdr *tcp = (void *)(ip + 1); + if ((void *)(tcp + 1) > data_end) + return 0; + // Verifique se a porta está na lista de portas a serem filtradas + __u16 dest_port = __constant_ntohs(tcp->dest); + if (is_filtered_port(dest_port)) { + return 1; + } + } else if (ip->protocol == IPPROTO_UDP) { + struct udphdr *udp = (void *)(ip + 1); + if ((void *)(udp + 1) > data_end) + return 0; + // Verifique se a porta está na lista de portas a serem filtradas + __u16 dest_port = __constant_ntohs(udp->dest); + if (is_filtered_port(dest_port)) { + return 1; + } + } else if (ip->protocol == IPPROTO_ICMP) { + return 1; + } + return 0; +} + +// Programa principal XDP +SEC("xdp") +int xdp_ddos_filter(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; + + // Verifica se é um pacote IP + if (eth->h_proto != __constant_htons(ETH_P_IP)) + return XDP_PASS; + + struct iphdr *ip = (void *)(eth + 1); + if ((void *)(ip + 1) > data_end) + return XDP_PASS; + + // Verifica se o cabeçalho IP é válido + if (ip->ihl != 5 || ip->version != 4) + return XDP_PASS; + + // Verifica se o IP está na lista de permissões + __u32 src_ip = ip->saddr; + if (is_whitelisted(src_ip)) { + return XDP_PASS; + } + + // Verifica se o IP está na lista negra + if (is_blacklisted(src_ip)) { + return XDP_DROP; + } + + // Verifica se o pacote é destinado ao servidor e deve ser filtrado + if (!is_destined_to_server(ip, data_end)) + return XDP_PASS; + + __u16 dest_port = 0; + struct tcphdr *tcp = NULL; + + // Verifica se é um pacote TCP ou UDP + if (ip->protocol == IPPROTO_TCP) { + tcp = (void *)(ip + 1); + if ((void *)(tcp + 1) > data_end) + return XDP_PASS; + dest_port = __constant_ntohs(tcp->dest); + + // Verifica se é um pacote SYN + if (tcp->syn && !tcp->ack) { + // Consulta o mapa para verificar se o IP já está registrado + struct ip_stats *entry = bpf_map_lookup_elem(&ddos_map, &src_ip); + + if (entry) { + __u64 now = get_time_ns(); + + // Reinicia o contador se o tempo decorrido for maior que um segundo + if ((now - entry->last_seen) > TIMEOUT_SEC * 1000000000ULL) { + entry->syn_count = 0; + entry->last_seen = now; + } + + // Incrementa o contador de pacotes SYN + entry->syn_count++; + entry->last_seen = now; + + // Bloqueia o tráfego se o limite de pacotes SYN for excedido + if (entry->syn_count > MAX_SYN_PACKETS_PER_IP) { + return XDP_DROP; + } + } else { + // Adiciona um novo IP ao mapa + struct ip_stats new_entry = { + .syn_count = 1, + .last_seen = get_time_ns() + }; + bpf_map_update_elem(&ddos_map, &src_ip, &new_entry, BPF_ANY); + } + } + } else if (ip->protocol == IPPROTO_UDP) { + struct udphdr *udp = (void *)(ip + 1); + if ((void *)(udp + 1) > data_end) + return XDP_PASS; + dest_port = __constant_ntohs(udp->dest); + } + + // Consulta o mapa para verificar se o IP já está registrado + struct ip_stats *entry = bpf_map_lookup_elem(&ddos_map, &src_ip); + + if (entry) { + __u64 now = get_time_ns(); + + // Reinicia o contador se o tempo decorrido for maior que um segundo + if ((now - entry->last_seen) > TIMEOUT_SEC * 1000000000ULL) { + entry->pkt_count = 0; + entry->icmp_count = 0; + entry->syn_count = 0; + entry->high_priority_count = 0; + entry->low_priority_count = 0; + entry->last_seen = now; + } + + // Rate limiting para pacotes de alta prioridade + if (is_high_priority_port(dest_port)) { + entry->high_priority_count++; + if (entry->high_priority_count > MAX_HIGH_PRIORITY_PACKETS_PER_SECOND) { + return XDP_DROP; + } + } + + // Rate limiting para pacotes de baixa prioridade + if (is_low_priority_port(dest_port)) { + entry->low_priority_count++; + if (entry->low_priority_count > MAX_LOW_PRIORITY_PACKETS_PER_SECOND) { + return XDP_DROP; + } + } + + // Incrementa o contador de pacotes + entry->pkt_count++; + entry->last_seen = now; + + // Filtra pacotes ICMP + if (ip->protocol == IPPROTO_ICMP) { + entry->icmp_count++; + if (entry->icmp_count > MAX_ICMP_PACKETS_PER_IP) { + return XDP_DROP; + } + } + + // Bloqueia o tráfego se o limite de pacotes for excedido + if (entry->pkt_count > MAX_PACKETS_PER_IP) { + return XDP_DROP; + } + } else { + // Adiciona um novo IP ao mapa + struct ip_stats new_entry = { + .pkt_count = 1, + .icmp_count = (ip->protocol == IPPROTO_ICMP) ? 1 : 0, + .syn_count = (ip->protocol == IPPROTO_TCP && tcp && tcp->syn && !tcp->ack) ? 1 : 0, + .last_seen = get_time_ns(), + .high_priority_count = is_high_priority_port(dest_port) ? 1 : 0, + .low_priority_count = is_low_priority_port(dest_port) ? 1 : 0 + }; + bpf_map_update_elem(&ddos_map, &src_ip, &new_entry, BPF_ANY); + } + + return XDP_PASS; +} + +char __license[] SEC("license") = "GPL"; \ No newline at end of file diff --git a/xdp_nat_tools.py b/xdp_nat_tools.py new file mode 100644 index 0000000..00aa272 --- /dev/null +++ b/xdp_nat_tools.py @@ -0,0 +1,298 @@ +#!/usr/bin/env python3 + +import os +import sys +import time +import socket +import struct +import argparse +import subprocess +import ipaddress +from bcc import BPF + +# Constantes +DEFAULT_INTERFACE = "enp0s6" +DEFAULT_GATEWAY_IP = "10.0.0.87" +DNAT_RULES_FILE = "dnat_rules.txt" +SNAT_NETWORKS_FILE = "snat_networks.txt" + +def run_cmd(cmd, show_output=False): + """Executa um comando shell e retorna sua saída""" + if show_output: + print(f"Executando: {cmd}") + + result = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + if result.returncode != 0: + print(f"Erro ao executar: {cmd}") + print(f"Saída de erro: {result.stderr}") + return None + return result.stdout.strip() + +def ip_to_int(ip_str): + """Converte um endereço IP em inteiro.""" + return struct.unpack("!I", socket.inet_aton(ip_str))[0] + +def int_to_ip(ip_int): + """Converte um inteiro em endereço IP.""" + return socket.inet_ntoa(struct.pack("!I", ip_int)) + +def compile_xdp_program(): + """Compila o programa XDP.""" + print("Compilando o programa XDP...") + cmd = "clang -O2 -target bpf -c xdp_accelerated_nat.c -o xdp_accelerated_nat.o" + return run_cmd(cmd, True) is not None + +def attach_xdp_program(interface, program_path): + """Anexa o programa XDP à interface.""" + print(f"Anexando programa XDP à interface {interface}...") + cmd = f"ip link set dev {interface} xdp obj {program_path} section xdp" + return run_cmd(cmd, True) is not None + +def detach_xdp_program(interface): + """Remove o programa XDP da interface.""" + print(f"Removendo programa XDP da interface {interface}...") + cmd = f"ip link set dev {interface} xdp off" + return run_cmd(cmd, True) is not None + +def load_dnat_rules(file_path): + """Carrega regras DNAT de um arquivo.""" + rules = [] + + try: + with open(file_path, 'r') as f: + for line in f: + line = line.strip() + if not line or line.startswith('#'): + continue + + # Formato: proto dst_ip:dst_port new_ip:new_port + # Exemplo: tcp 10.0.0.87:80 10.15.0.45:80 + parts = line.split() + if len(parts) != 3: + print(f"Formato inválido na linha: {line}") + continue + + proto_name, dst, new = parts + + # Parse proto + if proto_name.lower() == "tcp": + proto = socket.IPPROTO_TCP + elif proto_name.lower() == "udp": + proto = socket.IPPROTO_UDP + else: + print(f"Protocolo inválido: {proto_name}") + continue + + # Parse IP e portas + try: + dst_ip, dst_port = dst.split(':') + new_ip, new_port = new.split(':') + + rule = { + 'proto': proto, + 'dst_ip': dst_ip, + 'dst_port': int(dst_port), + 'new_ip': new_ip, + 'new_port': int(new_port) + } + rules.append(rule) + except ValueError: + print(f"Erro ao analisar IP/porta na linha: {line}") + continue + + print(f"Carregadas {len(rules)} regras DNAT") + return rules + except FileNotFoundError: + print(f"Arquivo de regras DNAT não encontrado: {file_path}") + return [] + +def load_snat_networks(file_path): + """Carrega redes SNAT de um arquivo.""" + networks = [] + + try: + with open(file_path, 'r') as f: + for line in f: + line = line.strip() + if not line or line.startswith('#'): + continue + + # Formato: rede/prefixo + # Exemplo: 10.15.0.0/16 + try: + network = ipaddress.ip_network(line) + networks.append(str(network)) + except ValueError: + print(f"Rede inválida na linha: {line}") + continue + + print(f"Carregadas {len(networks)} redes SNAT") + return networks + except FileNotFoundError: + print(f"Arquivo de redes SNAT não encontrado: {file_path}") + return [] + +def update_gateway_ip(bpf_obj, gateway_ip): + """Atualiza o IP do gateway no mapa BPF.""" + try: + ip_int = ip_to_int(gateway_ip) + key = ctypes.c_uint32(0) + value = ctypes.c_uint32(ip_int) + bpf_obj["gateway_ip_map"][key] = value + print(f"IP do gateway configurado: {gateway_ip}") + except Exception as e: + print(f"Erro ao configurar IP do gateway: {e}") + +def update_dnat_rules(bpf_obj, rules): + """Atualiza regras DNAT no mapa BPF.""" + try: + dnat_map = bpf_obj["dnat_rules"] + + # Limpar mapa existente + for k in list(dnat_map.keys()): + del dnat_map[k] + + # Inserir novas regras + for rule in rules: + key = dnat_map.Key() + key.dst_ip = ip_to_int(rule['dst_ip']) + key.dst_port = rule['dst_port'] + key.proto = rule['proto'] + + value = dnat_map.Value() + value.new_dst_ip = ip_to_int(rule['new_ip']) + value.new_dst_port = rule['new_port'] + + dnat_map[key] = value + + print(f"{len(rules)} regras DNAT atualizadas") + except Exception as e: + print(f"Erro ao atualizar regras DNAT: {e}") + +def update_snat_networks(bpf_obj, networks): + """Atualiza redes SNAT no mapa BPF.""" + try: + snat_map = bpf_obj["snat_networks"] + + # Limpar mapa existente + for k in list(snat_map.keys()): + del snat_map[k] + + # Inserir novas redes + for net_str in networks: + network = ipaddress.ip_network(net_str) + if network.prefixlen != 16: + print(f"Aviso: Rede {net_str} não é /16. Pode não funcionar corretamente.") + + # Extrair prefixo /16 + prefix = int(network.network_address) & 0xFFFF0000 + key = ctypes.c_uint32(prefix) + value = ctypes.c_uint8(1) + + snat_map[key] = value + + print(f"{len(networks)} redes SNAT atualizadas") + except Exception as e: + print(f"Erro ao atualizar redes SNAT: {e}") + +def print_stats(bpf_obj): + """Imprime estatísticas de NAT.""" + stats = bpf_obj["stats_map"][0] + print("\nEstatísticas de NAT:") + print(f" Pacotes processados: {stats.processed}") + print(f" Acertos de SNAT: {stats.snat_hits}") + print(f" Acertos de DNAT: {stats.dnat_hits}") + print(f" Novas conexões: {stats.new_conns}") + print(f" Erros: {stats.errors}") + +def print_connections(bpf_obj): + """Imprime conexões ativas.""" + conn_map = bpf_obj["conn_track_map"] + print("\nConexões ativas:") + + count = 0 + for k, v in conn_map.items(): + proto_name = "TCP" if k.proto == socket.IPPROTO_TCP else "UDP" + src_ip = int_to_ip(k.src_ip) + dst_ip = int_to_ip(k.dst_ip) + + # Mostrar apenas as 20 primeiras conexões + if count < 20: + print(f" {proto_name}: {src_ip}:{k.src_port} -> {dst_ip}:{k.dst_port}") + count += 1 + + if count > 20: + print(f" ... e mais {count - 20} conexões") + print(f" Total: {count} conexões ativas") + +def main(): + parser = argparse.ArgumentParser(description='Ferramenta para configuração de aceleração NAT com XDP') + parser.add_argument('-i', '--interface', default=DEFAULT_INTERFACE, help=f'Interface de rede (padrão: {DEFAULT_INTERFACE})') + parser.add_argument('-g', '--gateway', default=DEFAULT_GATEWAY_IP, help=f'IP do gateway para SNAT (padrão: {DEFAULT_GATEWAY_IP})') + parser.add_argument('--compile', action='store_true', help='Compilar e carregar o programa XDP') + parser.add_argument('--unload', action='store_true', help='Descarregar o programa XDP') + parser.add_argument('--stats', action='store_true', help='Mostrar estatísticas') + parser.add_argument('--monitor', action='store_true', help='Monitorar estatísticas continuamente') + + args = parser.parse_args() + + # Descarregar programa + if args.unload: + detach_xdp_program(args.interface) + sys.exit(0) + + # Compilar e carregar programa + if args.compile: + if not compile_xdp_program(): + print("Falha ao compilar o programa XDP") + sys.exit(1) + + # Desanexar qualquer programa existente + detach_xdp_program(args.interface) + + # Anexar o novo programa + if not attach_xdp_program(args.interface, "xdp_accelerated_nat.o"): + print("Falha ao anexar o programa XDP à interface") + sys.exit(1) + + # Carregar o objeto BPF para interagir com o programa carregado + try: + b = BPF(obj="xdp_accelerated_nat.o") + except Exception as e: + print(f"Erro ao carregar o objeto BPF: {e}") + sys.exit(1) + + # Carregar regras DNAT e redes SNAT + dnat_rules = load_dnat_rules(DNAT_RULES_FILE) + snat_networks = load_snat_networks(SNAT_NETWORKS_FILE) + + # Atualizar configurações + update_gateway_ip(b, args.gateway) + if dnat_rules: + update_dnat_rules(b, dnat_rules) + if snat_networks: + update_snat_networks(b, snat_networks) + + # Mostrar estatísticas + if args.stats: + print_stats(b) + print_connections(b) + + # Monitorar estatísticas + if args.monitor: + try: + print("Monitorando estatísticas (Ctrl+C para sair)...") + while True: + os.system('clear') + print_stats(b) + print_connections(b) + time.sleep(1) + except KeyboardInterrupt: + print("\nMonitoramento interrompido.") + + print("\nPrograma XDP NAT acelerado em execução.") + print("Use '--stats' para visualizar estatísticas.") + print("Use '--unload' para descarregar o programa.") + +if __name__ == "__main__": + main()