299 lines
10 KiB
Python
299 lines
10 KiB
Python
#!/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()
|