first commit
This commit is contained in:
commit
c4cea8c3b3
9 files changed
+2095
No files matched your search
@@ -0,0 +1,3 @@
|
|||||||
|
# Formato: <protocolo> <ip_destino:porta_destino> <novo_ip:nova_porta>
|
||||||
|
tcp 10.0.0.87:80 10.15.0.45:80
|
||||||
|
tcp 10.0.0.87:443 10.15.0.45:443
|
||||||
+256
@@ -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())
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
10.15.0.0/16
|
||||||
|
172.17.0.0/16
|
||||||
@@ -0,0 +1,283 @@
|
|||||||
|
#include <linux/bpf.h>
|
||||||
|
#include <linux/if_ether.h>
|
||||||
|
#include <linux/ip.h>
|
||||||
|
#include <linux/tcp.h>
|
||||||
|
#include <linux/udp.h>
|
||||||
|
#include <linux/pkt_cls.h>
|
||||||
|
#include <linux/in.h>
|
||||||
|
#include <bpf/bpf_helpers.h>
|
||||||
|
#include <bpf/bpf_endian.h>
|
||||||
|
|
||||||
|
// 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";
|
||||||
@@ -0,0 +1,380 @@
|
|||||||
|
#include <linux/bpf.h>
|
||||||
|
#include <linux/if_ether.h>
|
||||||
|
#include <linux/ip.h>
|
||||||
|
#include <linux/tcp.h>
|
||||||
|
#include <linux/udp.h>
|
||||||
|
#include <linux/in.h>
|
||||||
|
#include <bpf/bpf_helpers.h>
|
||||||
|
#include <bpf/bpf_endian.h>
|
||||||
|
|
||||||
|
/* 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";
|
||||||
+270
@@ -0,0 +1,270 @@
|
|||||||
|
#include <linux/bpf.h>
|
||||||
|
#include <linux/if_ether.h>
|
||||||
|
#include <linux/ip.h>
|
||||||
|
#include <linux/tcp.h>
|
||||||
|
#include <linux/udp.h>
|
||||||
|
#include <linux/in.h>
|
||||||
|
#include <bpf/bpf_helpers.h>
|
||||||
|
|
||||||
|
#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";
|
||||||
@@ -0,0 +1,280 @@
|
|||||||
|
#include <linux/bpf.h>
|
||||||
|
#include <linux/if_ether.h>
|
||||||
|
#include <linux/ip.h>
|
||||||
|
#include <linux/tcp.h>
|
||||||
|
#include <linux/udp.h>
|
||||||
|
#include <linux/in.h>
|
||||||
|
#include <bpf/bpf_helpers.h>
|
||||||
|
|
||||||
|
#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";
|
||||||
@@ -0,0 +1,323 @@
|
|||||||
|
#include <linux/bpf.h>
|
||||||
|
#include <linux/if_ether.h>
|
||||||
|
#include <linux/ip.h>
|
||||||
|
#include <linux/tcp.h>
|
||||||
|
#include <linux/udp.h>
|
||||||
|
#include <linux/in.h>
|
||||||
|
#include <bpf/bpf_helpers.h>
|
||||||
|
#include <bpf/bpf_endian.h>
|
||||||
|
|
||||||
|
#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";
|
||||||
@@ -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()
|
||||||
Reference in new issue
Block a user