This commit is contained in:
rodrigo committed 2025-02-26 19:17:06 -03:00
1 parent d9412a360d
commit 6081ec0c1c
9 files changed
-1140

No files matched your search

-68
View File
@@ -1,68 +0,0 @@
#!/bin/bash
# Script simplificado para compilar e carregar programas XDP
# Interface a ser utilizada
IFACE=${1:-"enp0s6"}
MODE=${2:-"generic"} # generic é mais compatível; native é mais rápido
TYPE=${3:-"simple"} # simple ou accelerated
echo "Compilando e carregando programa XDP"
echo "Interface: $IFACE"
echo "Modo: $MODE"
echo "Tipo: $TYPE"
# Compilar o programa correto
if [ "$TYPE" == "simple" ]; then
echo "=== Compilando programa simple_xdp_filter ==="
clang -O2 -Wall -target bpf -g -D__TARGET_ARCH_arm64 -c simple_xdp_filter.c -o simple_xdp_filter.o -I/usr/include/bpf -I/usr/include/
if [ $? -ne 0 ]; then
echo "Erro na compilação!"
exit 1
fi
PROG_FILE="simple_xdp_filter.o"
else
echo "=== Compilando programa xdp_accelerated_nat ==="
clang -O2 -Wall -target bpf -g -D__TARGET_ARCH_arm64 -c xdp_accelerated_nat.c -o xdp_accelerated_nat.o -I/usr/include/bpf -I/usr/include/
if [ $? -ne 0 ]; then
echo "Erro na compilação!"
exit 1
fi
PROG_FILE="xdp_accelerated_nat.o"
fi
# Remover qualquer programa XDP existente
echo "Removendo programas XDP existentes da interface $IFACE"
ip link set dev $IFACE xdp off
# Carregar o programa
echo "Carregando programa $PROG_FILE na interface $IFACE (modo $MODE)..."
ip link set dev $IFACE xdp obj $PROG_FILE section xdp mode $MODE
if [ $? -ne 0 ]; then
echo "Erro ao carregar programa XDP."
echo "Tentando modo alternativo (skb)..."
ip link set dev $IFACE xdp obj $PROG_FILE section xdp mode skb
if [ $? -ne 0 ]; then
echo "Falha também no modo skb."
echo "Verifique se seu kernel suporta XDP."
exit 1
fi
fi
echo "Programa XDP carregado com sucesso!"
echo "Verificando status:"
ip link show dev $IFACE | grep xdp
echo "Use 'bpftool map dump' para ver os mapas"
if [ "$TYPE" == "simple" ]; then
# Mostrar estatísticas iniciais
echo "Estatísticas iniciais:"
bpftool map dump name stats_map
fi
-3
View File
@@ -1,3 +0,0 @@
# 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
View File
@@ -1,256 +0,0 @@
#!/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())
-93
View File
@@ -1,93 +0,0 @@
#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>
// Definir arquitetura ARM64 explicitamente
#ifndef __TARGET_ARCH_arm64
#define __TARGET_ARCH_arm64
#endif
// Mapa para redes internas (para SNAT)
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 regras DNAT
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 64);
__type(key, __u16); // Porta destino
__type(value, __u32); // IP interno
} dnat_rules SEC(".maps");
// Estatísticas
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, 4);
__type(key, __u32);
__type(value, __u64);
} stats_map SEC(".maps");
// Incrementar contador de estatísticas
static __always_inline void increment_counter(__u32 index) {
__u64 *counter;
counter = bpf_map_lookup_elem(&stats_map, &index);
if (counter)
(*counter)++;
}
// Programa XDP simplificado para inspeção de tráfego
SEC("xdp")
int simple_xdp_filter(struct xdp_md *ctx) {
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
struct ethhdr *eth = data;
// Verificação básica de limites
if (data + sizeof(struct ethhdr) > data_end)
return XDP_PASS;
// Verificar se é um pacote 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;
// Incrementar contador de pacotes processados
increment_counter(0);
// Verificar se é um pacote TCP ou UDP
if (ip->protocol == IPPROTO_TCP) {
struct tcphdr *tcp = (void *)(ip + 1);
if ((void *)(tcp + 1) > data_end)
return XDP_PASS;
// Contar pacotes TCP
increment_counter(1);
// Verificar se é tráfego HTTPS (porta 443)
__u16 dport = bpf_ntohs(tcp->dest);
if (dport == 443) {
increment_counter(3); // Contar pacotes HTTPS
}
} else if (ip->protocol == IPPROTO_UDP) {
// Contar pacotes UDP
increment_counter(2);
}
// Permitir que todos os pacotes passem
return XDP_PASS;
}
char _license[] SEC("license") = "GPL";
-2
View File
@@ -1,2 +0,0 @@
10.15.0.0/16
172.17.0.0/16
-116
View File
@@ -1,116 +0,0 @@
#!/bin/bash
# Script para configurar NAT BPF com TC
# Parâmetros
INTERFACE=${1:-"enp0s6"}
TARGET_IP=${2:-"10.15.0.45"}
GATEWAY_IP=${3:-"10.0.0.87"}
echo "=== Configurando NAT BPF com TC ==="
echo "Interface: $INTERFACE"
echo "IP alvo (container): $TARGET_IP"
echo "Gateway IP: $GATEWAY_IP"
# Compilar programa TC BPF
echo "Compilando programa TC BPF..."
clang -O2 -Wall -target bpf -g -D__TARGET_ARCH_arm64 -c tc_nat.c -o tc_nat.o -I/usr/include/bpf -I/usr/include/
if [ $? -ne 0 ]; then
echo "Erro na compilação do programa TC!"
exit 1
fi
# Verificar seções no arquivo objeto BPF
echo "Verificando seções no arquivo objeto..."
llvm-objdump -h tc_nat.o
# Remover programas TC existentes na interface
echo "Removendo programas TC existentes na interface $INTERFACE..."
tc filter del dev $INTERFACE ingress 2>/dev/null
tc filter del dev $INTERFACE egress 2>/dev/null
tc qdisc del dev $INTERFACE clsact 2>/dev/null
# Configurar qdisc
echo "Configurando qdisc no $INTERFACE..."
tc qdisc add dev $INTERFACE clsact
# Carregar programas TC
echo "Carregando programa TC ingress..."
tc filter add dev $INTERFACE ingress prio 1 handle 1 bpf da obj tc_nat.o sec tc_ingress
if [ $? -ne 0 ]; then
echo "Erro ao carregar o programa ingress!"
exit 1
fi
echo "Carregando programa TC egress..."
tc filter add dev $INTERFACE egress prio 1 handle 1 bpf da obj tc_nat.o sec tc_egress
if [ $? -ne 0 ]; then
echo "Erro ao carregar o programa egress!"
exit 1
fi
# Encontrar IDs dos mapas
echo "Encontrando IDs dos mapas BPF..."
DNAT_MAP_ID=$(bpftool map list | grep -m1 "name dnat_port_map" | awk '{print $1}' | tr -d ':')
SNAT_MAP_ID=$(bpftool map list | grep -m1 "name snat_networks" | awk '{print $1}' | tr -d ':')
if [ -z "$DNAT_MAP_ID" ] || [ -z "$SNAT_MAP_ID" ]; then
echo "Não foi possível encontrar todos os mapas BPF necessários."
exit 1
fi
echo "IDs dos mapas: DNAT=$DNAT_MAP_ID, SNAT=$SNAT_MAP_ID"
# Limpar mapas existentes
echo "Limpando mapas existentes..."
echo "Limpando DNAT..."
bpftool map dump id $DNAT_MAP_ID -j | grep -o '"key": [0-9]*' | awk '{print $2}' | \
while read key; do
[ ! -z "$key" ] && bpftool map delete id $DNAT_MAP_ID key hex $(printf "%04x" $key | sed 's/\(..\)\(..\)/\2 \1/')
done
echo "Limpando SNAT..."
bpftool map dump id $SNAT_MAP_ID -j | grep -o '"key": [0-9]*' | awk '{print $2}' | \
while read key; do
[ ! -z "$key" ] && bpftool map delete id $SNAT_MAP_ID key hex $(printf "%08x" $key | sed 's/\(..\)\(..\)\(..\)\(..\)/\4 \3 \2 \1/')
done
# Configurar regras DNAT
echo "Configurando regras DNAT..."
# Função auxiliar para converter IP para valores em little-endian
ip_to_hex_le() {
local ip=$1
IFS='.' read -r -a octets <<< "$ip"
printf "%02x %02x %02x %02x" "${octets[3]}" "${octets[2]}" "${octets[1]}" "${octets[0]}"
}
# Função auxiliar para converter porta para little-endian
port_to_hex_le() {
local port=$1
printf "%02x %02x" $((port & 0xFF)) $(((port >> 8) & 0xFF))
}
# Obter valores em hex
TARGET_IP_HEX=$(ip_to_hex_le $TARGET_IP)
# Configurar redirecionamento HTTP
echo "Configurando redirecionamento HTTP (porta 80)..."
bpftool map update id $DNAT_MAP_ID key hex "$(port_to_hex_le 80)" value hex "$TARGET_IP_HEX $(port_to_hex_le 80) 00 00"
# Configurar redirecionamento HTTPS
echo "Configurando redirecionamento HTTPS (porta 443)..."
bpftool map update id $DNAT_MAP_ID key hex "$(port_to_hex_le 443)" value hex "$TARGET_IP_HEX $(port_to_hex_le 443) 00 00"
# Configurar redes para SNAT
echo "Configurando redes para SNAT..."
bpftool map update id $SNAT_MAP_ID key hex "00 00 0f 0a" value hex "01" # 10.15.0.0/16
bpftool map update id $SNAT_MAP_ID key hex "00 00 11 ac" value hex "01" # 172.17.0.0/16
# Habilitar IP forwarding
echo "Habilitando ip_forward..."
echo 1 > /proc/sys/net/ipv4/ip_forward
echo "NAT BPF configurado com sucesso. Utilize 'tc filter show dev $INTERFACE ingress' para verificar."
echo "Para ver logs: cat /sys/kernel/debug/tracing/trace_pipe"
-80
View File
@@ -1,80 +0,0 @@
#!/bin/bash
# Script para verificar se o NAT está funcionando corretamente
INTERFACE=${1:-"enp0s6"}
TARGET_IP=${2:-"10.15.0.45"}
echo "=== Verificando Configuração NAT ==="
echo "Interface: $INTERFACE"
echo "Container alvo: $TARGET_IP"
# Verificar se os programas TC estão carregados
echo -e "\n[Programas TC]"
tc filter show dev $INTERFACE ingress | grep -i bpf
tc filter show dev $INTERFACE egress | grep -i bpf
# Verificar mapas BPF
echo -e "\n[Mapas BPF]"
DNAT_MAP_ID=$(bpftool map list | grep -m1 "name dnat_port_map" | awk '{print $1}' | tr -d ':')
SNAT_MAP_ID=$(bpftool map list | grep -m1 "name snat_networks" | awk '{print $1}' | tr -d ':')
if [ -n "$DNAT_MAP_ID" ]; then
echo "Mapa DNAT (ID: $DNAT_MAP_ID):"
bpftool map dump id $DNAT_MAP_ID
else
echo "Mapa DNAT não encontrado"
fi
if [ -n "$SNAT_MAP_ID" ]; then
echo -e "\nMapa SNAT (ID: $SNAT_MAP_ID):"
bpftool map dump id $SNAT_MAP_ID
else
echo -e "\nMapa SNAT não encontrado"
fi
# Verificar IP forwarding
echo -e "\n[IP Forwarding]"
IP_FWD=$(cat /proc/sys/net/ipv4/ip_forward)
echo "IP forwarding: $IP_FWD"
if [ "$IP_FWD" != "1" ]; then
echo "AVISO: IP forwarding está desativado. Ative com:"
echo " echo 1 > /proc/sys/net/ipv4/ip_forward"
fi
# Verificar conectividade com o alvo
echo -e "\n[Conectividade]"
ping -c 1 -W 1 $TARGET_IP > /dev/null
if [ $? -eq 0 ]; then
echo "Ping para $TARGET_IP: OK"
else
echo "Ping para $TARGET_IP: FALHOU"
fi
# Testar HTTP/HTTPS
echo -e "\n[Teste HTTP]"
curl -s -m 2 --head http://$TARGET_IP > /dev/null
if [ $? -eq 0 ]; then
echo "Acesso HTTP direto para $TARGET_IP: OK"
else
echo "Acesso HTTP direto para $TARGET_IP: FALHOU"
fi
# Obter IP externo
EXTERNAL_IP=$(ip -br addr show $INTERFACE | awk '{print $3}' | cut -d'/' -f1)
echo -e "\n[Teste NAT]"
echo "IP externo: $EXTERNAL_IP"
curl -s -m 2 --head http://$EXTERNAL_IP > /dev/null
if [ $? -eq 0 ]; then
echo "Acesso HTTP via NAT ($EXTERNAL_IP): OK"
else
echo "Acesso HTTP via NAT ($EXTERNAL_IP): FALHOU"
fi
# Verificar logs recentes
echo -e "\n[Logs Recentes]"
echo "Últimas 5 linhas do trace_pipe (pode estar vazio se não houver atividade recente):"
cat /sys/kernel/debug/tracing/trace_pipe | head -n 5
echo -e "\nPara monitorar logs em tempo real: cat /sys/kernel/debug/tracing/trace_pipe"
echo "Para visualizar conexões atuais: ss -tan"
-216
View File
@@ -1,216 +0,0 @@
#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>
// Definir arquitetura ARM64 explicitamente
#ifndef __TARGET_ARCH_arm64
#define __TARGET_ARCH_arm64
#endif
// Definições para contornar erros de compilação
#ifndef __section
#define __section(NAME) \
__attribute__((section(NAME), used))
#endif
// Substituir o SEC original pela versão compatível com ARM64
#undef SEC
#define SEC(NAME) __section(NAME)
/* 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;
__u8 padding; // Adicionado para alinhamento em ARM64
} __attribute__((packed));
/* 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;
__u8 padding; // Adicionado para alinhamento em ARM64
} __attribute__((packed));
/* Mapas BPF para NAT */
struct {
__uint(type, BPF_MAP_TYPE_HASH); // Alterado de LRU_HASH para HASH para maior compatibilidade
__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 - simplificada para evitar loops */
static __always_inline __u16 ip_checksum(struct iphdr *ip) {
__u32 sum = 0;
// Para ARM64, calcular o checksum sem depender de loops
__u16 *iph = (__u16 *)ip;
// Zeramos o campo checksum do IP header
ip->check = 0;
// Calculamos a soma de palavras de 16 bits do header IP
// O tamanho padrão do header IP é 20 bytes (5 palavras de 32 bits ou 10 de 16 bits)
sum += iph[0];
sum += iph[1];
sum += iph[2];
sum += iph[3];
sum += iph[4];
sum += iph[5];
sum += iph[6];
sum += iph[7];
sum += iph[8];
sum += iph[9];
// Dobrar 32-bit para 16-bit para obter o checksum final
sum = (sum & 0xFFFF) + (sum >> 16);
sum = (sum & 0xFFFF) + (sum >> 16);
return ~sum; // Complemento de 1
}
/* Função simplificada para atualizar checksum */
static __always_inline __u16 update_checksum(__u16 old_checksum, __u32 old_value, __u32 new_value) {
__u32 sum = ~((__u32)old_checksum) & 0xFFFF;
sum += ~(old_value >> 16) & 0xFFFF;
sum += ~old_value & 0xFFFF;
sum += (new_value >> 16) & 0xFFFF;
sum += new_value & 0xFFFF;
sum = (sum & 0xFFFF) + (sum >> 16);
sum = (sum & 0xFFFF) + (sum >> 16);
return ~sum & 0xFFFF;
}
/* 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) {
if (stat_type == 0) stats->processed++;
else if (stat_type == 1) stats->snat_hits++;
else if (stat_type == 2) stats->dnat_hits++;
else if (stat_type == 3) stats->new_conns++;
else if (stat_type == 4) stats->errors++;
}
}
/* 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);
return (flag && *flag == 1) ? 1 : 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;
// Verificação básica de limites
if (data + sizeof(struct ethhdr) > data_end)
return XDP_PASS;
struct ethhdr *eth = data;
// 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;
// Incrementar contador de pacotes processados
update_stats(0);
// Restante do código simplificado para compatibilidade ARM64
// No caso de XDP modo genérico, vamos deixar a maioria do processamento
// NAT para o TC/iptables padrão para garantir compatibilidade
// Aplicar SNAT básico para rede interna
if (should_apply_snat(ip->saddr)) {
update_stats(1); // SNAT hit
// Permitir que o TC/iptables faça o trabalho real de SNAT
return XDP_PASS;
}
// Apenas passar pacotes normalmente
return XDP_PASS;
}
/* Simplificação extrema para compatibilidade */
char _license[] SEC("license") = "GPL";
-306
View File
@@ -1,306 +0,0 @@
#!/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...")
# Corrigido para incluir diretórios de inclusão e target para ARM64
cmd = "clang -O2 -Wall -target bpf -g -D__TARGET_ARCH_arm64 -c xdp_accelerated_nat.c -o xdp_accelerated_nat.o -I/usr/include/bpf -I/usr/include/"
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}...")
# Adicionando a flag --force para substituir qualquer programa existente
# Usando 'generic' como modo XDP para maior compatibilidade
cmd = f"ip link set dev {interface} xdp obj {program_path} sec 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. Verifique os erros acima.")
sys.exit(1)
# Desanexar qualquer programa existente
detach_xdp_program(args.interface)
# Anexar o novo programa
print("Tentando anexar o programa XDP à interface...")
if not attach_xdp_program(args.interface, "xdp_accelerated_nat.o"):
print("Falha ao anexar o programa XDP à interface")
# Tentar modo alternativo
print("Tentando modo alternativo (generic)...")
if not run_cmd(f"ip link set dev {args.interface} xdp obj xdp_accelerated_nat.o section xdp mode generic", True):
print("Todas as tentativas de carregamento falharam. Verifique se o kernel suporta XDP.")
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()