first commit

This commit is contained in:
Rodrigo Bertoldo de Sousa Martins committed 2026-09-13 20:49:33 -03:00
commit 01d7cb1a80
34 files changed
+5723

No files matched your search

+1256
View File
File diff suppressed because it is too large. Load diff
+7
View File
@@ -0,0 +1,7 @@
build/
bpf/vmlinux.h
*.o
node_modules/
mysql-data/
postgres-data/
.env
+134
View File
@@ -0,0 +1,134 @@
# ─────────────────────────────────────────────────────────
# Makefile — Compila o programa eBPF sockmap
#
# Comandos:
# make → Compila tudo
# make vmlinux → Gera vmlinux.h do kernel atual
# make bpf → Compila o programa BPF
# make clean → Limpa arquivos compilados
# make docker-up → Sobe os containers Docker
# make docker-down→ Para os containers
# make load → Carrega o eBPF sockmap
# make unload → Remove o eBPF sockmap
# make status → Mostra status do sockmap
# make benchmark → Roda benchmark de latência
# ─────────────────────────────────────────────────────────
# Compilador e flags
CLANG ?= clang
BPFTOOL ?= bpftool
ARCH := $(shell uname -m | sed 's/x86_64/x86/' | sed 's/aarch64/arm64/')
# Diretórios
BPF_SRC := bpf
BUILD_DIR := build
LIBBPF_INC := /usr/include
# Arquivos
BPF_PROG := $(BPF_SRC)/sockmap.bpf.c
BPF_OBJ := $(BUILD_DIR)/sockmap.bpf.o
VMLINUX_H := $(BPF_SRC)/vmlinux.h
# Flags de compilação BPF
BPF_CFLAGS := -O2 -g \
-target bpf \
-D__TARGET_ARCH_$(ARCH) \
-I$(BPF_SRC) \
-I$(LIBBPF_INC) \
-Wall \
-Wno-unused-value \
-Wno-pointer-sign \
-Wno-compare-distinct-pointer-types
# ─── Alvos principais ───
.PHONY: all vmlinux bpf clean docker-up docker-down load unload status benchmark help
all: vmlinux bpf
@echo ""
@echo "✅ Compilação concluída! Objeto BPF em: $(BPF_OBJ)"
@echo ""
@echo "Próximos passos:"
@echo " 1. make docker-up → Subir os containers"
@echo " 2. make load → Ativar o eBPF sockmap"
@echo " 3. make benchmark → Testar a latência"
# ─── Gerar vmlinux.h ───
vmlinux: $(VMLINUX_H)
$(VMLINUX_H):
@echo "🔧 Gerando vmlinux.h a partir do kernel $(shell uname -r)..."
@mkdir -p $(BPF_SRC)
$(BPFTOOL) btf dump file /sys/kernel/btf/vmlinux format c > $(VMLINUX_H)
@echo "✅ vmlinux.h gerado com sucesso!"
# ─── Compilar programa BPF ───
bpf: $(BPF_OBJ)
$(BPF_OBJ): $(BPF_PROG) $(VMLINUX_H)
@echo "🔧 Compilando programa eBPF..."
@mkdir -p $(BUILD_DIR)
$(CLANG) $(BPF_CFLAGS) -c $(BPF_PROG) -o $(BPF_OBJ)
@echo "✅ Programa BPF compilado: $(BPF_OBJ)"
@echo ""
@echo "📋 Programas e mapas no objeto:"
@$(BPFTOOL) prog show --bpffs 2>/dev/null || true
@echo ""
# ─── Docker ───
docker-up:
@echo "🐳 Subindo containers Docker..."
cd docker && docker compose up -d --build
@echo ""
@echo "✅ Containers rodando! Acesse http://localhost:8080"
docker-down:
@echo "🐳 Parando containers Docker..."
cd docker && docker compose down -v
@echo "✅ Containers parados."
docker-logs:
cd docker && docker compose logs -f
# ─── eBPF Sockmap ───
load:
@echo "⚡ Carregando eBPF sockmap..."
sudo bash scripts/loader.sh load
unload:
@echo "⚡ Removendo eBPF sockmap..."
sudo bash scripts/loader.sh unload
status:
@sudo bash scripts/loader.sh status
# ─── Benchmark ───
benchmark:
@echo "📊 Rodando benchmark de latência..."
@echo " Acesse: http://localhost:8080/benchmark"
@curl -s http://localhost:8080/benchmark | python3 -m json.tool 2>/dev/null || \
curl -s http://localhost:8080/benchmark
@echo ""
# ─── Limpeza ───
clean:
rm -rf $(BUILD_DIR)
rm -f $(VMLINUX_H)
@echo "🧹 Limpeza concluída."
# ─── Ajuda ───
help:
@echo "eBPF Sockmap — Acelerador de comunicação entre containers Docker"
@echo ""
@echo "Comandos disponíveis:"
@echo " make Compila o programa eBPF (vmlinux.h + .o)"
@echo " make vmlinux Gera apenas o vmlinux.h"
@echo " make bpf Compila apenas o programa BPF"
@echo " make docker-up Sobe os containers (nginx + node + mysql + postgres)"
@echo " make docker-down Para e remove os containers"
@echo " make docker-logs Mostra logs dos containers"
@echo " make load Carrega e ativa o eBPF sockmap"
@echo " make unload Remove o eBPF sockmap"
@echo " make status Mostra status e estatísticas"
@echo " make benchmark Roda teste de latência"
@echo " make clean Limpa arquivos compilados"
@echo ""
+362
View File
@@ -0,0 +1,362 @@
# eBPF Sockmap — Acelerador de comunicação entre containers Docker
## 📖 O que é isso?
Este projeto usa **eBPF sockmap** para **acelerar a comunicação de rede entre containers Docker** que rodam no mesmo servidor (host).
### A stack de exemplo inclui:
| Container | Função | Porta |
|-----------|--------|-------|
| **Nginx** | Proxy reverso | 8080 → 80 |
| **Node.js** | Servidor da aplicação | 3000 |
| **MySQL** | Banco de dados relacional | 3306 |
| **PostgreSQL** | Banco de dados relacional | 5432 |
---
## 🧠 Como funciona? (Explicação para iniciantes)
### O problema: comunicação lenta entre containers
Quando dois containers Docker no mesmo host se comunicam (ex: Node.js → MySQL), os dados passam por **toda a pilha de rede do Linux**:
```
Container A (Node.js)
↓ syscall send()
↓ TCP (segmentação, controle de fluxo)
↓ IP (roteamento)
↓ Netfilter (iptables/nftables)
↓ Bridge (docker0)
↓ veth pair (interface virtual)
↓ IP (de novo)
↓ TCP (de novo)
↓ syscall recv()
Container B (MySQL)
```
Isso é **desnecessário** quando os containers estão no **mesmo host** — os dados poderiam ir direto de um para o outro!
### A solução: eBPF Sockmap
Com o sockmap, os dados são **redirecionados diretamente entre sockets**, pulando TODA essa pilha:
```
Container A (Node.js)
↓ syscall send()
↓ eBPF intercepta aqui! ⚡
↓ Dados vão direto para o socket do outro container
↓ syscall recv()
Container B (MySQL)
```
### Os dois programas BPF
O projeto tem **dois programas eBPF** que trabalham juntos:
#### 1. `sock_ops` — O Registrador
- É ativado toda vez que uma **conexão TCP é estabelecida**
- Quando o Node.js conecta no MySQL, ele **registra esses sockets** num mapa (tabela hash)
- O mapa guarda: IP origem, IP destino, porta origem, porta destino → socket
#### 2. `sk_msg` — O Redirecionador
- É ativado toda vez que um **socket registrado envia dados**
- Ele procura no mapa o socket do **outro lado** da conexão
- Se encontrar, **redireciona os dados diretamente** para lá
- Se não encontrar, deixa passar normalmente pela rede
```
┌─────────────────────────────────────────────────────────────┐
│ KERNEL LINUX │
│ │
│ ┌──────────┐ sock_ops ┌──────────────┐ │
│ │ Conexão │ ──registra socket──→ │ SOCKHASH │ │
│ │ TCP nova │ │ (mapa BPF) │ │
│ └──────────┘ │ │ │
│ │ key → sock │ │
│ ┌──────────┐ sk_msg │ key → sock │ │
│ │ Socket │ ──procura par──────→ │ key → sock │ │
│ │ envia │ └──────────────┘ │
│ │ dados │ ←─redireciona direto──────────┘ │
│ └──────────┘ │
│ │
│ ✂️ Pula: TCP/IP → Netfilter → Bridge → veth → TCP/IP │
└─────────────────────────────────────────────────────────────┘
```
---
## 📋 Requisitos
| Requisito | Mínimo | Verificar |
|-----------|--------|-----------|
| **Linux** | Kernel ≥ 5.4 | `uname -r` |
| **BTF** | Habilitado no kernel | `ls /sys/kernel/btf/vmlinux` |
| **Cgroup v2** | Ativo | `mount \| grep cgroup2` |
| **Clang/LLVM** | ≥ 11 | `clang --version` |
| **bpftool** | Instalado | `bpftool version` |
| **libbpf-dev** | Headers instalados | `dpkg -l libbpf-dev` |
| **Docker** | ≥ 20.10 | `docker --version` |
| **Docker Compose** | v2 (plugin) | `docker compose version` |
> **⚠️ IMPORTANTE:** eBPF roda **apenas no Linux**. Se você está no Windows, use **WSL2** ou uma **VM Linux**.
---
## 🚀 Guia passo a passo
### Passo 0: Entrar no Linux (se usa Windows)
Se você está no Windows, precisa usar WSL2:
```bash
# No PowerShell do Windows, abra o WSL:
wsl
# Navegue até o projeto:
cd /mnt/c/Users/rodri/build/ebpf-sockmap
```
### Passo 1: Instalar dependências
```bash
# Instala TUDO que é necessário automaticamente
sudo bash scripts/setup.sh
```
Ou instale manualmente (Ubuntu/Debian):
```bash
sudo apt update
sudo apt install -y \
clang llvm libbpf-dev \
linux-tools-common linux-tools-$(uname -r) \
linux-headers-$(uname -r) \
build-essential pkg-config curl \
docker.io docker-compose-plugin
```
### Passo 2: Compilar o programa eBPF
```bash
# Compila tudo (gera vmlinux.h + compila o .c para .o)
make
```
**O que acontece:**
1. `bpftool` gera o arquivo `vmlinux.h` a partir do kernel atual (contém todas as definições de estruturas do kernel)
2. `clang` compila o `sockmap.bpf.c` para bytecode BPF → `build/sockmap.bpf.o`
Se der tudo certo, você verá:
```
✅ Compilação concluída! Objeto BPF em: build/sockmap.bpf.o
```
### Passo 3: Subir os containers Docker
```bash
# Sobe nginx + node.js + mysql + postgresql
make docker-up
```
Aguarde até todos estarem prontos (pode levar ~30s na primeira vez):
```bash
# Verifique se está funcionando:
curl http://localhost:8080/
curl http://localhost:8080/mysql/users
curl http://localhost:8080/postgres/products
```
### Passo 4: Benchmark SEM sockmap (linha de base)
```bash
# Anota os tempos sem o sockmap ativo
bash scripts/benchmark.sh
```
Guarde os resultados para comparar depois!
### Passo 5: Ativar o eBPF Sockmap
```bash
# Carrega e ativa o sockmap
make load
```
Saída esperada:
```
[OK] Programa sock_ops carregado
[OK] Programa sk_msg carregado
[OK] sock_ops anexado ao cgroup
[OK] sk_msg anexado ao sockhash
[OK] eBPF Sockmap ATIVADO com sucesso!
```
### Passo 6: Benchmark COM sockmap (comparar!)
```bash
# Roda o mesmo benchmark, agora com sockmap ativo
bash scripts/benchmark.sh
```
Você deve ver **melhoria na latência**, especialmente nas queries ao banco de dados.
### Passo 7: Ver status e estatísticas
```bash
make status
```
### Passo 8: Desativar quando quiser
```bash
# Remove o sockmap (volta ao comportamento normal)
make unload
# Para os containers
make docker-down
```
---
## 📁 Estrutura do projeto
```
ebpf-sockmap/
├── bpf/
│ ├── sockmap.bpf.c ← Programa eBPF (sock_ops + sk_msg)
│ └── vmlinux.h ← (gerado) Definições do kernel
├── build/
│ └── sockmap.bpf.o ← (gerado) Bytecode BPF compilado
├── docker/
│ ├── docker-compose.yml ← Definição dos 4 containers
│ ├── nginx/
│ │ └── nginx.conf ← Config do proxy reverso
│ ├── app/
│ │ ├── Dockerfile ← Build do container Node.js
│ │ ├── server.js ← Servidor com endpoints de teste
│ │ └── package.json ← Dependências Node.js
│ └── db/
│ ├── mysql-init.sql ← Schema inicial MySQL
│ └── postgres-init.sql← Schema inicial PostgreSQL
├── scripts/
│ ├── loader.sh ← Carrega/descarrega o eBPF sockmap
│ ├── setup.sh ← Instala todas as dependências
│ └── benchmark.sh ← Testa latência
├── Makefile ← Comandos de build e operação
└── README.md ← Este arquivo
```
---
## 🔍 Comandos úteis
```bash
make help # Lista todos os comandos disponíveis
make # Compila tudo
make docker-up # Sobe os containers
make docker-down # Para os containers
make docker-logs # Vê logs em tempo real
make load # Ativa o sockmap
make unload # Desativa o sockmap
make status # Mostra estatísticas
make benchmark # Testa performance
make clean # Limpa compilados
```
---
## 🐛 Solução de problemas
### "bpftool não encontrado"
```bash
# Ubuntu/Debian
sudo apt install linux-tools-common linux-tools-$(uname -r)
# Se ainda não funcionar, procure o caminho:
find /usr/lib -name bpftool 2>/dev/null
```
### "BTF não encontrado" (/sys/kernel/btf/vmlinux não existe)
Seu kernel não foi compilado com BTF. Opções:
- Atualizar para um kernel mais recente da distro
- Compilar o kernel com `CONFIG_DEBUG_INFO_BTF=y`
### "Cgroup v2 não detectado"
```bash
# Adicione ao GRUB:
sudo nano /etc/default/grub
# Na linha GRUB_CMDLINE_LINUX, adicione:
# systemd.unified_cgroup_hierarchy=1
sudo update-grub
sudo reboot
```
### "Permissão negada"
O eBPF precisa de privilégios de root:
```bash
sudo make load
```
### Containers não sobem
```bash
# Veja os logs:
make docker-logs
# Verifique se as portas estão livres:
sudo lsof -i :8080
sudo lsof -i :3306
sudo lsof -i :5432
```
### WSL2: eBPF não funciona
O kernel padrão do WSL2 pode não ter suporte completo. Opções:
1. Compilar um kernel customizado para WSL2 com BTF
2. Usar uma VM Linux (VirtualBox ou VMware) com Ubuntu 22.04+
---
## 📚 Conceitos para entender melhor
### O que é eBPF?
**eBPF** (extended Berkeley Packet Filter) é uma tecnologia do Linux que permite executar pequenos programas **dentro do kernel**, de forma segura. É usado para:
- Monitoramento de rede (tcpdump, Cilium)
- Segurança (Falco, Tetragon)
- Performance (bcc tools, bpftrace)
- **Aceleração de rede** (sockmap — nosso caso!)
### O que é um Sockmap/Sockhash?
Um **sockhash** é um tipo de mapa BPF que armazena **referências a sockets**. É como uma tabela onde:
- **Chave** = informações da conexão (IPs + portas)
- **Valor** = referência ao socket do kernel
### Por que isso é mais rápido?
Sem sockmap, dados entre containers passam por ~12 camadas de processamento no kernel. Com sockmap, passam por apenas 2 (o programa BPF e a cópia de memória).
### Limitações
- Funciona apenas para containers **no mesmo host**
- Apenas **TCP** (não UDP)
- Apenas **IPv4** (esta implementação)
- Precisa de kernel moderno (≥ 5.4)
- Precisa de cgroup v2
---
## 📊 Resultados esperados
A melhoria depende do cenário, mas tipicamente:
| Métrica | Sem Sockmap | Com Sockmap | Melhoria |
|---------|-------------|-------------|----------|
| Latência query MySQL | ~0.5ms | ~0.2ms | ~60% |
| Latência query PostgreSQL | ~0.5ms | ~0.2ms | ~60% |
| Latência HTTP (nginx→node) | ~1.0ms | ~0.4ms | ~60% |
| Throughput (req/s) | ~5000 | ~8000 | ~60% |
> Os valores são aproximados e variam conforme o hardware e carga.
---
## 📜 Licença
GPL-2.0 (exigido pelo eBPF/kernel Linux)
+227
View File
@@ -0,0 +1,227 @@
// SPDX-License-Identifier: GPL-2.0
//
// sockmap.bpf.c — Programa eBPF para acelerar comunicação entre containers Docker
//
// Este programa faz duas coisas:
// 1) sock_ops: intercepta conexões TCP sendo estabelecidas e registra
// os sockets num mapa (sockhash).
// 2) sk_msg: quando um socket registrado envia dados, redireciona
// diretamente para o socket do outro lado, PULANDO toda a pilha
// de rede do kernel (TCP/IP, netfilter, bridge, veth, etc).
//
// Resultado: comunicação entre containers no MESMO host fica muito mais rápida
// porque os dados vão direto de um socket para o outro na memória.
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
// ─────────────────────────────────────────────────────────
// Chave que identifica uma conexão TCP (4-tupla + família)
// ─────────────────────────────────────────────────────────
struct sock_key {
__u32 sip4; // IP de origem (source IP)
__u32 dip4; // IP de destino (destination IP)
__u32 sport; // Porta de origem (source port)
__u32 dport; // Porta de destino (destination port)
__u32 family; // Família do socket (AF_INET = 2)
};
// ─────────────────────────────────────────────────────────
// Mapa SOCKHASH: armazena sockets indexados pela chave acima
// Capacidade: 65535 entradas (conexões simultâneas)
// ─────────────────────────────────────────────────────────
struct {
__uint(type, BPF_MAP_TYPE_SOCKHASH);
__uint(max_entries, 65535);
__type(key, struct sock_key);
__type(value, int);
} sock_hash SEC(".maps");
// ─────────────────────────────────────────────────────────
// Mapa para estatísticas
// Index 0 = conexões registradas no sockhash
// Index 1 = mensagens redirecionadas com sucesso (caminho acelerado)
// Index 2 = fallback: par não encontrado (caminho normal)
// Index 3 = conexões ignoradas (IPs externos/filtrados)
// ─────────────────────────────────────────────────────────
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, 4);
__type(key, __u32);
__type(value, __u64);
} stats SEC(".maps");
// Incrementa um contador de estatísticas
static __always_inline void stats_inc(__u32 idx)
{
__u64 *val = bpf_map_lookup_elem(&stats, &idx);
if (val)
__sync_fetch_and_add(val, 1);
}
// ─────────────────────────────────────────────────────────
// Verifica se um IP (em network byte order / big-endian) é
// endereço privado RFC-1918 ou loopback.
//
// Por que isso importa:
// O sock_ops roda para TODAS as conexões TCP do cgroup raiz,
// incluindo tráfego externo (HTTPS do mundo, metadata AWS…).
// Registrar IPs públicos no sockhash é inútil e perigoso:
// o par nunca será encontrado, o sk_msg vai ao fallback e
// entradas mortas poluem o mapa.
// Só registramos conexões onde ambos os lados são privados
// (i.e., comunicação intra-host / intra-container).
// ─────────────────────────────────────────────────────────
static __always_inline bool is_local_ip(__u32 ip_be)
{
// local_ip4 / remote_ip4 são big-endian (network byte order).
// Em x86 (little-endian) o valor numérico da u32 aparece
// byte-invertido: para 10.15.0.45 (0A 0F 00 2D em memória),
// a u32 lida vale 0x2D000F0A — ou seja, ip_be >> 24 = 45 (ERRADO!).
// bpf_ntohl() converte para host order, colocando o primeiro
// octeto (10) no byte mais significativo, onde >> 24 extrai
// corretamente 10.
__u32 ip = bpf_ntohl(ip_be);
__u8 o1 = (__u8)(ip >> 24);
__u8 o2 = (__u8)(ip >> 16);
if (o1 == 10) return true; // 10.0.0.0/8
if (o1 == 172 && o2 >= 16 && o2 <= 31) return true; // 172.16.0.0/12
if (o1 == 192 && o2 == 168) return true; // 192.168.0.0/16
if (o1 == 127) return true; // 127.0.0.0/8 loopback
return false;
}
// ─────────────────────────────────────────────────────────
// Extrai a chave de conexão a partir do contexto sock_ops
// (usado quando uma conexão TCP é estabelecida)
// ─────────────────────────────────────────────────────────
static __always_inline void
extract_key_from_ops(struct bpf_sock_ops *ops, struct sock_key *key)
{
key->sip4 = ops->local_ip4;
key->dip4 = ops->remote_ip4;
key->sport = ops->local_port; // já em host byte order
key->dport = bpf_ntohl(ops->remote_port); // converter de network para host
key->family = ops->family;
}
// ─────────────────────────────────────────────────────────
// PROGRAMA 1: sock_ops
//
// Anexado ao cgroup, é chamado em eventos de socket TCP.
// Quando uma conexão é ESTABELECIDA (ativa ou passiva),
// registramos o socket no sockhash.
// ─────────────────────────────────────────────────────────
SEC("sockops")
int bpf_sockops_handler(struct bpf_sock_ops *skops)
{
__u32 op = skops->op;
// Só processamos conexões IPv4
if (skops->family != 2) // AF_INET
return 0;
switch (op) {
// Conexão passiva: servidor aceitou uma conexão de um cliente
case BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB:
// Conexão ativa: cliente conectou a um servidor
case BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB: {
// ── Filtro: só conexões entre IPs privados RFC-1918 ───────
// Ignora tráfego externo (HTTPS público, metadata AWS 169.254.x.x).
// Bug histórico: NÃO usar ip_be >> 24 sem bpf_ntohl — em x86
// little-endian o primeiro octeto fica no byte menos significativo.
if (!is_local_ip(skops->local_ip4) || !is_local_ip(skops->remote_ip4)) {
stats_inc(3); // conta conexões externas ignoradas
return 0;
}
struct sock_key key = {};
extract_key_from_ops(skops, &key);
// BPF_ANY: atualiza mesmo se já existir (cobre reconexões keepalive)
int ret = bpf_sock_hash_update(skops, &sock_hash, &key, BPF_ANY);
if (ret == 0) {
stats_inc(0);
// ── Hints de performance TCP ──────────────────────────
// Ativa todos os callbacks relevantes de uma vez:
// STATE_CB_FLAG : notifica mudanças de estado TCP
// (necessário para limpar sockets fechados)
// RTT_CB_FLAG : callback a cada RTT (permite ajuste dinâmico)
__u32 flags = skops->bpf_sock_ops_cb_flags
| BPF_SOCK_OPS_STATE_CB_FLAG
| BPF_SOCK_OPS_RTT_CB_FLAG;
bpf_sock_ops_cb_flags_set(skops, flags);
}
break;
}
// Mudança de estado TCP: limpa o mapa em qualquer transição
// que indique encerramento, para não deixar sockets mortos.
case BPF_SOCK_OPS_STATE_CB: {
__u32 new_state = skops->args[1];
if (new_state == BPF_TCP_CLOSE ||
new_state == BPF_TCP_CLOSE_WAIT ||
new_state == BPF_TCP_FIN_WAIT1 ||
new_state == BPF_TCP_FIN_WAIT2 ||
new_state == BPF_TCP_TIME_WAIT) {
struct sock_key key = {};
extract_key_from_ops(skops, &key);
bpf_map_delete_elem(&sock_hash, &key);
}
break;
}
// RTT callback: pode ser usado no futuro para métricas por conexão.
// Por ora não fazemos nada aqui — o callback foi ativado para
// manter o kernel notificando o sock_ops sobre a saúde da conexão.
case BPF_SOCK_OPS_RTT_CB:
break;
default:
break;
}
return 0;
}
// ─────────────────────────────────────────────────────────
// PROGRAMA 2: sk_msg
//
// Anexado ao sockhash, é chamado quando qualquer socket
// registrado envia dados (sendmsg/sendfile).
// Redireciona os dados diretamente para o socket par,
// pulando toda a pilha de rede.
// ─────────────────────────────────────────────────────────
SEC("sk_msg")
int bpf_msg_redirect(struct sk_msg_md *msg)
{
// Construímos a chave do socket PAR (invertendo src/dst).
// Se A→B enviou dados, queremos encontrar no mapa a entrada
// que B registrou: {sip=B, dip=A, sport=portaB, dport=portaA}
struct sock_key key = {};
key.sip4 = msg->remote_ip4; // IP remoto = IP do par
key.dip4 = msg->local_ip4; // IP local = nosso IP
key.sport = bpf_ntohl(msg->remote_port); // remote_port está em network order
key.dport = msg->local_port; // local_port está em host order
key.family = msg->family;
// Tenta redirecionar diretamente para o socket par.
int ret = bpf_msg_redirect_hash(msg, &sock_hash, &key, BPF_F_INGRESS);
if (ret == SK_PASS) {
stats_inc(1); // Redirecionamento OK — caminho acelerado
return SK_PASS;
}
// FALLBACK CRÍTICO: se o par não está no mapa (socket ainda não
// registrado, já fechou, ou conexão externa ao cgroup), deixamos
// os dados fluírem pelo caminho normal de rede em vez de
// descartar (SK_DROP), o que causaria ERR_CONNECTION_CLOSED.
stats_inc(2);
return SK_PASS;
}
// Licença GPL obrigatória para usar helpers BPF
char _license[] SEC("license") = "GPL";
+8
View File
@@ -0,0 +1,8 @@
FROM node:20-alpine
WORKDIR /app
COPY package.json ./
RUN npm install --production
COPY server.js ./
RUN apk add --no-cache wget
EXPOSE 3000
CMD ["node", "server.js"]
+13
View File
@@ -0,0 +1,13 @@
{
"name": "sockmap-demo-app",
"version": "1.0.0",
"description": "App Node.js de demonstração para eBPF sockmap",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"mysql2": "^3.6.0",
"pg": "^8.11.0"
}
}
+219
View File
@@ -0,0 +1,219 @@
// ─────────────────────────────────────────────────────────
// server.js — Servidor Node.js de demonstração
//
// Endpoints:
// GET /health → Health check
// GET / → Página principal com info
// GET /mysql/users → Lista usuários do MySQL
// GET /postgres/products→ Lista produtos do PostgreSQL
// GET /benchmark → Teste de latência para ambos os DBs
//
// Este servidor se conecta ao MySQL e PostgreSQL.
// Com o eBPF sockmap ativo, a comunicação com os bancos
// é acelerada (dados redirecionados no nível do socket).
// ─────────────────────────────────────────────────────────
const http = require('http');
const url = require('url');
// ─── Configuração MySQL ───
const mysql = require('mysql2/promise');
const mysqlConfig = {
host: process.env.MYSQL_HOST || 'mysql',
port: parseInt(process.env.MYSQL_PORT || '3306'),
user: process.env.MYSQL_USER || 'app',
password: process.env.MYSQL_PASSWORD || 'app_password',
database: process.env.MYSQL_DATABASE || 'app_db',
waitForConnections: true,
connectionLimit: 10,
};
// ─── Configuração PostgreSQL ───
const { Pool } = require('pg');
const pgPool = new Pool({
host: process.env.POSTGRES_HOST || 'postgres',
port: parseInt(process.env.POSTGRES_PORT || '5432'),
user: process.env.POSTGRES_USER || 'app',
password: process.env.POSTGRES_PASSWORD || 'app_password',
database: process.env.POSTGRES_DATABASE || 'app_db',
max: 10,
});
let mysqlPool;
// ─── Inicializa conexão MySQL ───
async function initMySQL() {
try {
mysqlPool = mysql.createPool(mysqlConfig);
const [rows] = await mysqlPool.query('SELECT 1');
console.log('✅ MySQL conectado!');
} catch (err) {
console.error('❌ Erro ao conectar no MySQL:', err.message);
setTimeout(initMySQL, 3000);
}
}
// ─── Inicializa conexão PostgreSQL ───
async function initPostgres() {
try {
const res = await pgPool.query('SELECT 1');
console.log('✅ PostgreSQL conectado!');
} catch (err) {
console.error('❌ Erro ao conectar no PostgreSQL:', err.message);
setTimeout(initPostgres, 3000);
}
}
// ─── Mede o tempo de execução de uma query (em ms) ───
async function measureQuery(fn) {
const start = process.hrtime.bigint();
const result = await fn();
const end = process.hrtime.bigint();
const durationMs = Number(end - start) / 1_000_000;
return { result, durationMs };
}
// ─── Handlers de rotas ───
const routes = {
// Health check
'/health': async () => ({
status: 'ok',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
}),
// Página principal
'/': async () => ({
app: 'eBPF Sockmap Demo',
message: 'Servidor Node.js funcionando! 🚀',
endpoints: {
'/health': 'Health check',
'/mysql/users': 'Listar usuários (MySQL)',
'/postgres/products': 'Listar produtos (PostgreSQL)',
'/benchmark': 'Benchmark de latência nos dois bancos',
},
info: {
description: 'Com eBPF sockmap ativo, a comunicação entre este servidor e os bancos é acelerada.',
como_funciona: 'Os dados são redirecionados diretamente entre sockets, pulando a pilha TCP/IP do kernel.',
},
}),
// Listar usuários do MySQL
'/mysql/users': async () => {
const { result, durationMs } = await measureQuery(async () => {
const [rows] = await mysqlPool.query('SELECT * FROM users');
return rows;
});
return {
source: 'MySQL',
query_time_ms: durationMs.toFixed(3),
count: result.length,
data: result,
};
},
// Listar produtos do PostgreSQL
'/postgres/products': async () => {
const { result, durationMs } = await measureQuery(async () => {
const res = await pgPool.query('SELECT * FROM products');
return res.rows;
});
return {
source: 'PostgreSQL',
query_time_ms: durationMs.toFixed(3),
count: result.length,
data: result,
};
},
// Benchmark: faz várias queries e mede a latência
'/benchmark': async () => {
const iterations = 100;
const results = {
mysql: { times: [], avg: 0, min: Infinity, max: 0 },
postgres: { times: [], avg: 0, min: Infinity, max: 0 },
};
// Benchmark MySQL
for (let i = 0; i < iterations; i++) {
const { durationMs } = await measureQuery(async () => {
const [rows] = await mysqlPool.query('SELECT 1');
return rows;
});
results.mysql.times.push(durationMs);
results.mysql.min = Math.min(results.mysql.min, durationMs);
results.mysql.max = Math.max(results.mysql.max, durationMs);
}
results.mysql.avg = results.mysql.times.reduce((a, b) => a + b, 0) / iterations;
// Benchmark PostgreSQL
for (let i = 0; i < iterations; i++) {
const { durationMs } = await measureQuery(async () => {
await pgPool.query('SELECT 1');
});
results.postgres.times.push(durationMs);
results.postgres.min = Math.min(results.postgres.min, durationMs);
results.postgres.max = Math.max(results.postgres.max, durationMs);
}
results.postgres.avg = results.postgres.times.reduce((a, b) => a + b, 0) / iterations;
// Limpa arrays de tempos individuais
delete results.mysql.times;
delete results.postgres.times;
return {
benchmark: `${iterations} queries SELECT 1 em cada banco`,
dica: 'Compare os tempos com e sem eBPF sockmap!',
mysql: {
avg_ms: results.mysql.avg.toFixed(3),
min_ms: results.mysql.min.toFixed(3),
max_ms: results.mysql.max.toFixed(3),
},
postgres: {
avg_ms: results.postgres.avg.toFixed(3),
min_ms: results.postgres.min.toFixed(3),
max_ms: results.postgres.max.toFixed(3),
},
};
},
};
// ─── Servidor HTTP ───
const server = http.createServer(async (req, res) => {
const pathname = url.parse(req.url).pathname;
const handler = routes[pathname];
res.setHeader('Content-Type', 'application/json; charset=utf-8');
if (!handler) {
res.writeHead(404);
res.end(JSON.stringify({ error: 'Rota não encontrada', path: pathname }));
return;
}
try {
const data = await handler();
res.writeHead(200);
res.end(JSON.stringify(data, null, 2));
} catch (err) {
console.error(`Erro em ${pathname}:`, err.message);
res.writeHead(500);
res.end(JSON.stringify({ error: err.message }));
}
});
// ─── Inicialização ───
const PORT = 3000;
async function main() {
console.log('🔧 Iniciando servidor Node.js...');
await initMySQL();
await initPostgres();
server.listen(PORT, '0.0.0.0', () => {
console.log(`🚀 Servidor rodando na porta ${PORT}`);
console.log(`📊 Acesse http://localhost:${PORT}/benchmark para testar latência`);
});
}
main().catch(console.error);
+25
View File
@@ -0,0 +1,25 @@
-- ─────────────────────────────────────────────────────────
-- MySQL: Script de inicialização do banco de dados
-- Cria tabelas de exemplo para teste
-- ─────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(150) NOT NULL UNIQUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS requests_log (
id INT AUTO_INCREMENT PRIMARY KEY,
endpoint VARCHAR(255) NOT NULL,
method VARCHAR(10) NOT NULL,
response_time_ms FLOAT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Dados de exemplo
INSERT INTO users (name, email) VALUES
('Alice Silva', 'alice@exemplo.com'),
('Bob Santos', 'bob@exemplo.com'),
('Carol Oliveira', 'carol@exemplo.com');
+28
View File
@@ -0,0 +1,28 @@
-- ─────────────────────────────────────────────────────────
-- PostgreSQL: Script de inicialização do banco de dados
-- Cria tabelas de exemplo para teste
-- ─────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS products (
id SERIAL PRIMARY KEY,
name VARCHAR(200) NOT NULL,
price NUMERIC(10, 2) NOT NULL,
stock INT DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS orders (
id SERIAL PRIMARY KEY,
user_name VARCHAR(100) NOT NULL,
product_id INT REFERENCES products(id),
quantity INT NOT NULL DEFAULT 1,
total NUMERIC(10, 2),
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Dados de exemplo
INSERT INTO products (name, price, stock) VALUES
('Notebook Dell', 4500.00, 10),
('Mouse Logitech', 150.00, 50),
('Teclado Mecânico', 350.00, 30),
('Monitor 27"', 2200.00, 15);
+113
View File
@@ -0,0 +1,113 @@
# ─────────────────────────────────────────────────────────
# Docker Compose - Stack completa para testar eBPF Sockmap
#
# Serviços:
# nginx → Proxy reverso (porta 80 → Node.js)
# app → Servidor Node.js (porta 3000)
# mysql → Banco de dados MySQL (porta 3306)
# postgres → Banco de dados PostgreSQL (porta 5432)
#
# Todos os containers ficam na mesma rede bridge,
# e o eBPF sockmap acelera a comunicação entre eles.
# ─────────────────────────────────────────────────────────
version: "3.8"
services:
# ─── NGINX Proxy Reverso ───
nginx:
image: nginx:alpine
container_name: sockmap-nginx
ports:
- "8080:80" # Acesse http://localhost:8080
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
app:
condition: service_healthy
networks:
- sockmap-net
restart: unless-stopped
# ─── Aplicação Node.js ───
app:
build:
context: ./app
dockerfile: Dockerfile
container_name: sockmap-app
environment:
NODE_ENV: production
MYSQL_HOST: mysql
MYSQL_PORT: 3306
MYSQL_USER: app
MYSQL_PASSWORD: app_password
MYSQL_DATABASE: app_db
POSTGRES_HOST: postgres
POSTGRES_PORT: 5432
POSTGRES_USER: app
POSTGRES_PASSWORD: app_password
POSTGRES_DATABASE: app_db
depends_on:
mysql:
condition: service_healthy
postgres:
condition: service_healthy
networks:
- sockmap-net
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
interval: 5s
timeout: 3s
retries: 10
restart: unless-stopped
# ─── MySQL ───
mysql:
image: mysql:8.0
container_name: sockmap-mysql
environment:
MYSQL_ROOT_PASSWORD: root_password
MYSQL_DATABASE: app_db
MYSQL_USER: app
MYSQL_PASSWORD: app_password
volumes:
- mysql-data:/var/lib/mysql
- ./db/mysql-init.sql:/docker-entrypoint-initdb.d/init.sql:ro
networks:
- sockmap-net
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-proot_password"]
interval: 5s
timeout: 3s
retries: 30
restart: unless-stopped
# ─── PostgreSQL ───
postgres:
image: postgres:16-alpine
container_name: sockmap-postgres
environment:
POSTGRES_DB: app_db
POSTGRES_USER: app
POSTGRES_PASSWORD: app_password
volumes:
- postgres-data:/var/lib/postgresql/data
- ./db/postgres-init.sql:/docker-entrypoint-initdb.d/init.sql:ro
networks:
- sockmap-net
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d app_db"]
interval: 5s
timeout: 3s
retries: 30
restart: unless-stopped
volumes:
mysql-data:
postgres-data:
networks:
sockmap-net:
driver: bridge
ipam:
config:
- subnet: 172.28.0.0/16
+78
View File
@@ -0,0 +1,78 @@
# ─────────────────────────────────────────────────────────
# nginx.conf — Proxy reverso para o servidor Node.js
#
# Nginx recebe requisições na porta 80 e encaminha para
# o container do Node.js na porta 3000.
#
# Com o eBPF sockmap ativo, a comunicação nginx→app
# é acelerada (dados pulam a pilha de rede do kernel).
# ─────────────────────────────────────────────────────────
worker_processes auto;
events {
worker_connections 1024;
use epoll;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Formato de log com tempo de resposta
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time';
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log warn;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
keepalive_requests 100;
# Compressão gzip
gzip on;
gzip_types text/plain application/json application/javascript text/css;
# ─── Upstream: servidor Node.js ───
upstream nodejs_app {
server sockmap-app:3000;
keepalive 32; # Conexões persistentes (importante para sockmap!)
}
server {
listen 80;
server_name localhost;
# Página de status do Nginx
location /nginx-status {
stub_status on;
access_log off;
}
# Todas as outras requisições vão para o Node.js
location / {
proxy_pass http://nodejs_app;
proxy_http_version 1.1;
# Headers para proxy reverso
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Manter conexão viva (keepalive)
proxy_set_header Connection "";
# Timeouts
proxy_connect_timeout 5s;
proxy_read_timeout 30s;
proxy_send_timeout 30s;
}
}
}
+82
View File
@@ -0,0 +1,82 @@
#!/bin/bash
# ─────────────────────────────────────────────────────────
# benchmark.sh — Testa latência com e sem eBPF sockmap
#
# Execute: bash scripts/benchmark.sh
# ─────────────────────────────────────────────────────────
set -euo pipefail
BLUE='\033[0;34m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
URL="http://localhost:8080/benchmark"
CURL_OPTS="-s --connect-timeout 5 --max-time 30"
echo ""
echo -e "${BLUE}═══════════════════════════════════════════════${NC}"
echo -e "${BLUE} Benchmark de Latência — eBPF Sockmap${NC}"
echo -e "${BLUE}═══════════════════════════════════════════════${NC}"
echo ""
# Verifica se a aplicação está rodando
if ! curl $CURL_OPTS "$URL" > /dev/null 2>&1; then
echo "❌ Aplicação não está respondendo em $URL"
echo " Execute 'make docker-up' primeiro."
exit 1
fi
echo -e "${YELLOW}Teste 1: Benchmark via endpoint /benchmark${NC}"
echo "(Faz 100 queries SELECT 1 em cada banco de dados)"
echo ""
curl $CURL_OPTS "$URL" | python3 -m json.tool 2>/dev/null || curl $CURL_OPTS "$URL"
echo ""
echo -e "${YELLOW}Teste 2: Latência HTTP (nginx → node.js)${NC}"
echo "(10 requisições sequenciais para /health)"
echo ""
TOTAL=0
for i in $(seq 1 10); do
TIME=$(curl $CURL_OPTS -o /dev/null -w "%{time_total}" http://localhost:8080/health)
TIME_MS=$(echo "$TIME * 1000" | bc)
printf " Requisição %2d: %8.3f ms\n" "$i" "$TIME_MS"
TOTAL=$(echo "$TOTAL + $TIME_MS" | bc)
done
AVG=$(echo "scale=3; $TOTAL / 10" | bc)
echo ""
echo -e "${GREEN} Média: ${AVG} ms${NC}"
echo ""
echo -e "${YELLOW}Teste 3: Throughput (requisições por segundo)${NC}"
echo "(100 requisições para /health)"
echo ""
if command -v ab &>/dev/null; then
ab -n 100 -c 10 http://localhost:8080/health 2>/dev/null | grep -E "Requests per second|Time per request|Transfer rate"
elif command -v wrk &>/dev/null; then
wrk -t2 -c10 -d5s http://localhost:8080/health
else
START=$(date +%s%N)
for i in $(seq 1 100); do
curl $CURL_OPTS -o /dev/null http://localhost:8080/health &
done
wait
END=$(date +%s%N)
ELAPSED=$(echo "scale=3; ($END - $START) / 1000000" | bc)
RPS=$(echo "scale=1; 100 / ($ELAPSED / 1000)" | bc)
echo " 100 requisições em ${ELAPSED} ms"
echo -e "${GREEN} ~${RPS} requisições/segundo${NC}"
fi
echo ""
echo "─────────────────────────────────────────────"
echo "💡 Dica: Execute este benchmark antes e depois do 'make load'"
echo " para comparar a performance com e sem eBPF sockmap!"
echo ""
echo " SEM sockmap: make unload → bash scripts/benchmark.sh"
echo " COM sockmap: make load → bash scripts/benchmark.sh"
echo "─────────────────────────────────────────────"
echo ""
+403
View File
@@ -0,0 +1,403 @@
#!/bin/bash
# ─────────────────────────────────────────────────────────
# loader.sh — Carrega e anexa os programas eBPF sockmap
#
# USO:
# sudo ./scripts/loader.sh load # Carrega e ativa o sockmap
# sudo ./scripts/loader.sh unload # Remove o sockmap
# sudo ./scripts/loader.sh status # Mostra status e estatísticas
# ─────────────────────────────────────────────────────────
set -euo pipefail
# Cores para output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
BPF_OBJ="build/sockmap.bpf.o"
BPF_PIN_DIR="/sys/fs/bpf/sockmap"
CGROUP_PATH=""
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_ok() { echo -e "${GREEN}[OK]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERRO]${NC} $1"; }
# ─────────────────────────────────────────────────────────
# Detecta o caminho do cgroup v2
# Docker coloca os containers dentro de um cgroup
# ─────────────────────────────────────────────────────────
detect_cgroup() {
# Verifica se cgroup v2 está montado (unified hierarchy)
if mount | grep -q "cgroup2 on /sys/fs/cgroup"; then
CGROUP_PATH="/sys/fs/cgroup"
log_info "Cgroup v2 (unified) detectado em $CGROUP_PATH"
elif [ -d "/sys/fs/cgroup/unified" ]; then
CGROUP_PATH="/sys/fs/cgroup/unified"
log_info "Cgroup v2 (unified) detectado em $CGROUP_PATH"
else
log_error "Cgroup v2 não encontrado! eBPF sockmap requer cgroup v2."
log_error "Adicione 'systemd.unified_cgroup_hierarchy=1' aos parâmetros do kernel."
exit 1
fi
}
# ─────────────────────────────────────────────────────────
# Verifica pré-requisitos
# ─────────────────────────────────────────────────────────
check_prereqs() {
if [ "$(id -u)" -ne 0 ]; then
log_error "Este script precisa ser executado como root (sudo)!"
exit 1
fi
if ! command -v bpftool &>/dev/null; then
log_error "'bpftool' não encontrado. Instale com: apt install linux-tools-common linux-tools-$(uname -r)"
exit 1
fi
if [ ! -f "$BPF_OBJ" ]; then
log_error "Objeto BPF não encontrado em '$BPF_OBJ'. Execute 'make' primeiro!"
exit 1
fi
}
# ─────────────────────────────────────────────────────────
# LOAD: Carrega os programas BPF e os anexa
# ─────────────────────────────────────────────────────────
do_load() {
check_prereqs
detect_cgroup
# Limpa carregamento anterior se existir
if [ -d "$BPF_PIN_DIR" ]; then
log_warn "Sockmap já carregado. Removendo anterior..."
do_unload
fi
log_info "Criando diretório de pins BPF..."
mkdir -p "$BPF_PIN_DIR"
# ── 1. Carregar TODOS os programas do objeto de uma vez ──
# 'loadall' respeita as anotações SEC() de cada programa,
# carregando sock_ops e sk_msg com seus tipos corretos.
# Os programas são fixados (pinned) com o nome das funções C:
# bpf_sockops_handler → tipo sockops
# bpf_msg_redirect → tipo sk_msg
log_info "Carregando programas BPF (loadall)..."
bpftool prog loadall "$BPF_OBJ" "$BPF_PIN_DIR" \
pinmaps "$BPF_PIN_DIR"
log_ok "Programas sock_ops e sk_msg carregados"
# Confirma que os pins foram criados
if [ ! -f "$BPF_PIN_DIR/bpf_sockops_handler" ]; then
log_error "Pin bpf_sockops_handler não encontrado em $BPF_PIN_DIR!"
log_error "Programas no objeto:"
bpftool prog list 2>/dev/null | tail -10
exit 1
fi
if [ ! -f "$BPF_PIN_DIR/bpf_msg_redirect" ]; then
log_error "Pin bpf_msg_redirect não encontrado em $BPF_PIN_DIR!"
exit 1
fi
# ── 2. Anexar sock_ops ao cgroup ──
# Flag 'multi' permite coexistir com outros programas sock_ops já
# anexados (ex: systemd ou outros agentes), sem conflito.
log_info "Anexando sock_ops ao cgroup ($CGROUP_PATH)..."
bpftool cgroup attach "$CGROUP_PATH" sock_ops \
pinned "$BPF_PIN_DIR/bpf_sockops_handler" \
multi
log_ok "sock_ops anexado ao cgroup"
# ── 3. Anexar sk_msg ao mapa sockhash ──
log_info "Anexando sk_msg ao mapa sockhash..."
bpftool prog attach \
pinned "$BPF_PIN_DIR/bpf_msg_redirect" \
msg_verdict \
pinned "$BPF_PIN_DIR/sock_hash"
log_ok "sk_msg anexado ao sockhash"
echo ""
log_ok "═══════════════════════════════════════════════"
log_ok " eBPF Sockmap ATIVADO com sucesso!"
log_ok " Comunicação entre containers está acelerada."
log_ok "═══════════════════════════════════════════════"
echo ""
log_info "Use 'sudo ./scripts/loader.sh status' para ver estatísticas"
log_info "Use 'sudo ./scripts/loader.sh unload' para desativar"
}
# ─────────────────────────────────────────────────────────
# UNLOAD: Remove os programas BPF
# ─────────────────────────────────────────────────────────
do_unload() {
detect_cgroup
log_info "Desanexando programas BPF..."
# Desanexa sock_ops do cgroup
if [ -f "$BPF_PIN_DIR/bpf_sockops_handler" ]; then
bpftool cgroup detach "$CGROUP_PATH" sock_ops \
pinned "$BPF_PIN_DIR/bpf_sockops_handler" \
multi 2>/dev/null || true
log_ok "sock_ops desanexado do cgroup"
fi
# Desanexa sk_msg do mapa
if [ -f "$BPF_PIN_DIR/bpf_msg_redirect" ]; then
bpftool prog detach \
pinned "$BPF_PIN_DIR/bpf_msg_redirect" \
msg_verdict \
pinned "$BPF_PIN_DIR/sock_hash" 2>/dev/null || true
log_ok "sk_msg desanexado do sockhash"
fi
# Remove pins
log_info "Removendo pins BPF..."
rm -rf "$BPF_PIN_DIR"
log_ok "Pins removidos"
echo ""
log_ok "eBPF Sockmap DESATIVADO com sucesso."
}
# ─────────────────────────────────────────────────────────
# STATUS: Mostra status e estatísticas
# ─────────────────────────────────────────────────────────
do_status() {
echo ""
echo -e "${BLUE}═══ eBPF Sockmap Status ═══${NC}"
echo ""
if [ ! -d "$BPF_PIN_DIR" ]; then
log_warn "Sockmap NÃO está carregado."
return
fi
log_ok "Sockmap está ATIVO"
echo ""
# Mostra programas carregados
echo -e "${YELLOW}Programas BPF carregados:${NC}"
bpftool prog show pinned "$BPF_PIN_DIR/bpf_sockops_handler" 2>/dev/null || echo " sock_ops: não encontrado"
bpftool prog show pinned "$BPF_PIN_DIR/bpf_msg_redirect" 2>/dev/null || echo " sk_msg: não encontrado"
echo ""
# Mostra estatísticas
# O valor no mapa é u64 little-endian (8 bytes).
# bpftool --json pode retornar cada byte como int (10) ou string hex ("0x0a")
# dependendo da versão. O helper parse_bpftool_bytes trata ambos.
echo -e "${YELLOW}Estatísticas:${NC}"
if [ -f "$BPF_PIN_DIR/stats" ]; then
read_stat() {
local idx=$1
bpftool map lookup pinned "$BPF_PIN_DIR/stats" \
key $(printf '0x%02x 0x00 0x00 0x00' "$idx") \
--json 2>/dev/null \
| python3 -c "
import sys, json
def to_bytes(arr):
# bpftool >= 7.x retorna strings hex ('0x0a'), antigas retornam ints (10)
return bytes(int(x, 16) if isinstance(x, str) else int(x) for x in arr)
try:
d = json.load(sys.stdin)
print(int.from_bytes(to_bytes(d['value']), 'little'))
except:
print(0)
"
}
CONNS=$(read_stat 0)
REDIR=$(read_stat 1)
ERRS=$(read_stat 2)
SKIP=$(read_stat 3)
echo " Conexões registradas no sockhash : $CONNS"
echo " Mensagens redirecionadas (accel.) : $REDIR"
echo " Fallback (par não encontrado) : $ERRS"
echo " Ignoradas (IPs externos) : $SKIP"
else
echo " Mapa de estatísticas não encontrado (recompile e recarregue)"
fi
echo ""
# Mostra sockets no mapa
# NOTA: 'No space left on device' nos values é NORMAL.
# SOCKHASH armazena ponteiros de socket do kernel, não legíveis de userspace.
echo -e "${YELLOW}Conexões ativas no sockhash (IPs/portas):${NC}"
echo -e " ${BLUE}(Os 'value: No space left on device' são normais para SOCKHASH)${NC}"
if [ -f "$BPF_PIN_DIR/sock_hash" ]; then
SOCKMAP_OUT=$(bpftool map dump pinned "$BPF_PIN_DIR/sock_hash" --json 2>/dev/null \
| python3 -c "
import sys, json, socket, struct
def to_int(x):
# Aceita tanto int (10) quanto string hex ('0x0a') — depende da versão do bpftool
return int(x, 16) if isinstance(x, str) else int(x)
try:
entries = json.load(sys.stdin)
if not entries:
print(' (nenhuma conexão registrada)')
else:
print(f' Total: {len(entries)} conexão(oes):')
for e in entries[:25]:
k = e.get('key', [])
if len(k) >= 20:
# struct sock_key: sip4 dip4 sport dport family (5x u32)
# local_ip4/remote_ip4 estão em big-endian (network order)
# portas em host order (little-endian)
raw = [to_int(b) for b in k]
sip = socket.inet_ntoa(bytes(raw[0:4]))
dip = socket.inet_ntoa(bytes(raw[4:8]))
sport = struct.unpack_from('<I', bytes(raw[8:12]))[0]
dport = struct.unpack_from('<I', bytes(raw[12:16]))[0]
print(f' {sip}:{sport} → {dip}:{dport}')
except Exception as ex:
print(f' (erro ao decodificar: {ex})')
" 2>/dev/null || echo " Nenhum socket registrado")
SOCK_COUNT=$(echo "$SOCKMAP_OUT" | grep -oP 'Total: \K[0-9]+' || echo 0)
echo "$SOCKMAP_OUT"
# Aviso inteligente: usa contagem real do sockhash quando stats falham
if [ "${CONNS:-0}" -eq 0 ] 2>/dev/null && [ "${SOCK_COUNT:-0}" -gt 0 ] 2>/dev/null; then
echo ""
echo -e " ${GREEN}✔ Sockhash tem ${SOCK_COUNT} conexão(oes) ativas!${NC}"
echo -e " ${YELLOW}(Contadores de stats zerados podem indicar que os containers"
echo -e " foram reiniciados antes do sockmap ser carregado nesta sessão.)${NC}"
elif [ "${CONNS:-0}" -eq 0 ] 2>/dev/null && [ "${SOCK_COUNT:-0}" -eq 0 ] 2>/dev/null; then
echo ""
echo -e " ${YELLOW}⚠ Nenhuma conexão no sockhash.${NC}"
echo " → Reinicie os containers: cd /home/docker && docker compose restart"
echo " → Ou rode: sudo ./scripts/loader.sh diagnose"
fi
fi
echo ""
# Mostra logs do BPF (trace_pipe)
echo -e "${YELLOW}Últimas mensagens do BPF (trace_pipe):${NC}"
if [ -f /sys/kernel/debug/tracing/trace_pipe ]; then
timeout 1 cat /sys/kernel/debug/tracing/trace_pipe 2>/dev/null | tail -10 || echo " Nenhuma mensagem recente"
fi
}
# ─────────────────────────────────────────────────────────
# DIAGNOSE: Força conexões TCP internas para verificar se o sockmap
# está interceptando corretamente.
# ─────────────────────────────────────────────────────────
do_diagnose() {
if [ ! -d "$BPF_PIN_DIR" ]; then
log_error "Sockmap não está carregado. Execute 'load' primeiro."
exit 1
fi
echo ""
echo -e "${BLUE}═══ Diagnóstico do sockmap ═══${NC}"
echo ""
# Lê stats antes
CONNS_BEFORE=$(bpftool map lookup pinned "$BPF_PIN_DIR/stats" \
key 0x00 0x00 0x00 0x00 --json 2>/dev/null \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(int.from_bytes(bytes(d['value']),'little'))" 2>/dev/null || echo 0)
log_info "Detectando containers Docker em execução..."
CONTAINERS=$(docker ps --format '{{.Names}} {{.NetworkSettings.Networks}}' 2>/dev/null | head -20 || echo "")
# Testa conexões TCP para IPs conhecidos da rede 10.15.0.0/16
log_info "Tentando conexões TCP internas para ativar o sock_ops..."
# Pega IPs de containers ativos
INTERNAL_IPS=$(docker inspect $(docker ps -q 2>/dev/null) 2>/dev/null \
| python3 -c "
import sys, json
try:
data = json.load(sys.stdin)
ips = []
for c in data:
nets = c.get('NetworkSettings', {}).get('Networks', {})
for net in nets.values():
ip = net.get('IPAddress', '')
if ip.startswith('10.'):
name = c.get('Name', '?').strip('/')
port_map = c.get('NetworkSettings', {}).get('Ports', {})
port = 80
for p in port_map:
try: port = int(p.split('/')[0]); break
except: pass
ips.append(f'{ip}:{port}:{name}')
print(' '.join(ips[:10]))
except Exception as e:
print('')
" 2>/dev/null || echo "")
if [ -z "$INTERNAL_IPS" ]; then
log_warn "Nenhum container com IP 10.x detectado via Docker."
log_info "Tentando IPs conhecidos da dockernet..."
INTERNAL_IPS="10.15.0.45:80:nginx 10.15.0.42:3306:mariadb 10.15.0.54:5432:postgres 10.15.0.55:6379:redis"
fi
log_info "Testando conexões (timeout 1s cada):"
for entry in $INTERNAL_IPS; do
IP=$(echo $entry | cut -d: -f1)
PORT=$(echo $entry | cut -d: -f2)
NAME=$(echo $entry | cut -d: -f3)
if timeout 1 bash -c "echo > /dev/tcp/$IP/$PORT" 2>/dev/null; then
log_ok " $NAME ($IP:$PORT) — conexão OK"
else
log_warn " $NAME ($IP:$PORT) — sem resposta (normal se a porta estiver fechada)"
fi
done
sleep 1
# Lê stats depois
CONNS_AFTER=$(bpftool map lookup pinned "$BPF_PIN_DIR/stats" \
key 0x00 0x00 0x00 0x00 --json 2>/dev/null \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(int.from_bytes(bytes(d['value']),'little'))" 2>/dev/null || echo 0)
NEW=$((CONNS_AFTER - CONNS_BEFORE))
echo ""
if [ "$NEW" -gt 0 ]; then
log_ok "$NEW nova(s) conexão(oes) registradas no sockhash! ✅"
log_ok "O sockmap está interceptando tráfego inter-container corretamente."
else
log_warn "Nenhuma nova conexão registrada."
echo ""
echo "Possíveis causas:"
echo " 1. Containers não reiniciados após 'make load'"
echo " → Solucao: cd /home/docker && docker compose restart"
echo " 2. A rede dockernet usa macvlan (não bridge)"
echo " → Macvlan não passa pelo cgroup host, mas sim pelo kernel de rede"
echo " → Neste caso o sockmap não intercepta (limitação de design)"
echo " 3. Verificar o driver da rede:"
echo " docker network inspect dockernet | grep Driver"
fi
echo ""
}
# ─────────────────────────────────────────────────────────
# Main
# ─────────────────────────────────────────────────────────
case "${1:-help}" in
load)
do_load
;;
unload)
do_unload
;;
status)
do_status
;;
diagnose)
do_diagnose
;;
*)
echo "eBPF Sockmap Loader"
echo ""
echo "Uso: sudo $0 {load|unload|status|diagnose}"
echo ""
echo " load - Carrega e ativa o sockmap"
echo " unload - Remove o sockmap"
echo " status - Mostra status e estatísticas"
echo " diagnose - Testa se o sockmap está interceptando conexões"
;;
esac
+166
View File
@@ -0,0 +1,166 @@
#!/bin/bash
# ─────────────────────────────────────────────────────────
# setup.sh — Instala todas as dependências necessárias
#
# Execute: sudo bash scripts/setup.sh
# ─────────────────────────────────────────────────────────
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_ok() { echo -e "${GREEN}[OK]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERRO]${NC} $1"; }
echo ""
echo -e "${BLUE}═══════════════════════════════════════════════${NC}"
echo -e "${BLUE} Instalação de dependências - eBPF Sockmap${NC}"
echo -e "${BLUE}═══════════════════════════════════════════════${NC}"
echo ""
# Verifica se é root
if [ "$(id -u)" -ne 0 ]; then
log_error "Execute como root: sudo bash scripts/setup.sh"
exit 1
fi
# Detecta distribuição
if [ -f /etc/os-release ]; then
. /etc/os-release
DISTRO=$ID
else
DISTRO="unknown"
fi
log_info "Distribuição detectada: $DISTRO"
log_info "Kernel: $(uname -r)"
echo ""
# ─── Verifica versão do kernel ───
KERNEL_MAJOR=$(uname -r | cut -d. -f1)
KERNEL_MINOR=$(uname -r | cut -d. -f2)
if [ "$KERNEL_MAJOR" -lt 5 ] || ([ "$KERNEL_MAJOR" -eq 5 ] && [ "$KERNEL_MINOR" -lt 4 ]); then
log_error "Kernel $(uname -r) muito antigo! eBPF sockmap requer kernel >= 5.4"
log_error "Atualize o kernel antes de continuar."
exit 1
fi
log_ok "Kernel $(uname -r) compatível com eBPF sockmap"
# ─── Instala pacotes ───
case "$DISTRO" in
ubuntu|debian|linuxmint|pop)
log_info "Instalando pacotes (apt)..."
apt-get update -qq
apt-get install -y \
clang \
llvm \
libbpf-dev \
linux-tools-common \
"linux-tools-$(uname -r)" \
linux-headers-"$(uname -r)" \
build-essential \
pkg-config \
curl \
docker.io \
docker-compose-plugin
;;
fedora)
log_info "Instalando pacotes (dnf)..."
dnf install -y \
clang \
llvm \
libbpf-devel \
bpftool \
kernel-headers \
kernel-devel \
make \
gcc \
curl \
docker \
docker-compose-plugin
;;
arch|manjaro)
log_info "Instalando pacotes (pacman)..."
pacman -Sy --noconfirm \
clang \
llvm \
libbpf \
bpf \
linux-headers \
base-devel \
curl \
docker \
docker-compose
;;
*)
log_warn "Distribuição '$DISTRO' não reconhecida."
log_warn "Instale manualmente: clang, llvm, libbpf-dev, bpftool, linux-headers, docker"
;;
esac
# ─── Verifica ferramentas instaladas ───
echo ""
log_info "Verificando ferramentas..."
check_tool() {
if command -v "$1" &>/dev/null; then
log_ok "$1 → $($1 --version 2>&1 | head -1)"
else
log_error "$1 não encontrado!"
return 1
fi
}
check_tool clang
check_tool llc
check_tool bpftool || log_warn "bpftool pode estar em /usr/lib/linux-tools/$(uname -r)/bpftool"
check_tool docker
# ─── Verifica BTF (BPF Type Format) ───
echo ""
if [ -f /sys/kernel/btf/vmlinux ]; then
log_ok "BTF disponível em /sys/kernel/btf/vmlinux"
else
log_error "BTF não encontrado! O kernel precisa ser compilado com CONFIG_DEBUG_INFO_BTF=y"
log_error "A maioria das distros modernas já inclui BTF."
fi
# ─── Verifica cgroup v2 ───
if mount | grep -q "cgroup2"; then
log_ok "Cgroup v2 ativo"
else
log_warn "Cgroup v2 não detectado! Pode ser necessário para o sockmap."
log_warn "Adicione 'systemd.unified_cgroup_hierarchy=1' aos parâmetros de boot do kernel."
fi
# ─── Configura Docker ───
echo ""
log_info "Configurando Docker..."
systemctl enable docker 2>/dev/null || true
systemctl start docker 2>/dev/null || true
# Adiciona usuário atual ao grupo docker (se não é root direto)
ACTUAL_USER="${SUDO_USER:-$USER}"
if [ "$ACTUAL_USER" != "root" ]; then
usermod -aG docker "$ACTUAL_USER" 2>/dev/null || true
log_ok "Usuário '$ACTUAL_USER' adicionado ao grupo docker"
log_warn "Faça logout e login para o grupo docker funcionar sem sudo"
fi
echo ""
echo -e "${GREEN}═══════════════════════════════════════════════${NC}"
echo -e "${GREEN} Instalação concluída!${NC}"
echo -e "${GREEN}═══════════════════════════════════════════════${NC}"
echo ""
echo "Próximos passos:"
echo " 1. make → Compilar o programa eBPF"
echo " 2. make docker-up → Subir os containers"
echo " 3. make load → Ativar o sockmap"
echo " 4. make benchmark → Testar performance"
echo ""
+155
View File
@@ -0,0 +1,155 @@
#!/usr/sbin/nft -f
# ==============================================================
# nftables.conf — server-mv
#
# Substitui a parte de firewall do rc.local antigo (iptables/ip6tables,
# antes espalhada entre rc.local e test.sh). Regras funcionalmente
# equivalentes, com os bugs já corrigidos:
# - sem "-j DROP" solto no meio da chain (o catch-all é só a policy)
# - sem duplicação de regra de porta única + multiport
# - anti-spoof de 192.168.0.0/16 restrito à interface WAN
# - chain PORTSCAN removida (nunca era populada, sempre morta)
#
# Vantagens sobre iptables/ip6tables para este caso:
# - um ruleset só cobre IPv4 e IPv6 (table inet), então não existe
# mais o risco de esquecer de espelhar uma regra do v4 pro v6
# - "flush ruleset" no topo faz o carregamento ser atômico: se o
# arquivo tiver erro de sintaxe, o `nft -f` falha ANTES de aplicar
# qualquer coisa — você nunca fica com um firewall pela metade
# (iptables -F seguido de regras que podem falhar no meio é o
# oposto disso)
# - meters (rate-limit por IP de origem) substituem o módulo
# "recent" do iptables com sintaxe mais legível
#
# Aplicar:
# nft -c -f nftables.conf # valida sintaxe sem aplicar
# nft -f nftables.conf # aplica
# systemctl enable --now nftables # persistência no boot
#
# Depois de migrar, pare de carregar iptables/ip6tables no boot
# (remova essa parte do rc.local — ver rc.local.new) para não ter
# duas ferramentas de firewall competindo pelo mesmo netfilter.
# ==============================================================
flush ruleset
table inet filter {
# --- Meters (rate limit com estado por IP de origem) ---
# (declarados via "meter" inline nas regras que os usam, mais abaixo)
chain input {
type filter hook input priority filter; policy drop;
iifname "lo" accept
ct state established,related accept
# ICMP/ICMPv6: 2º maior volume medido no host, avaliado cedo
# de propósito (dados reais: ~66k pacotes/dia, ficava por
# último na versão antiga em iptables).
ip protocol icmp jump icmpv4
meta l4proto ipv6-icmp jump icmpv6
# Pacotes TCP malformados / scanners óbvios
tcp flags & (fin|syn|rst|psh|ack|urg) == (fin|syn|rst|psh|ack|urg) \
meter bad_packets { ip saddr timeout 5m limit rate over 5/minute } drop
tcp flags & (fin|syn|rst|psh|ack|urg) == 0x0 \
meter bad_packets6 { ip6 saddr timeout 5m limit rate over 5/minute } drop
ip frag-off & 0x1fff != 0 drop # fragmentos IPv4 (equivalente a "iptables -f")
# Anti-spoof — só faz sentido vindo da WAN
iifname "enp0s6" ip saddr 192.168.0.0/16 drop
iifname "enp0s6" ip6 saddr fe80::/10 drop
iifname "enp0s6" ip6 saddr fd00::/8 drop
ip6 daddr ff00::/8 drop
# SYN flood (limite global, equivalente ao antigo limit 10/sec burst 24)
tcp flags syn limit rate 10/second burst 24 packets accept
tcp flags syn drop
# Rate limit de conexões novas na porta SSH (8117): no máx. 3
# tentativas novas por IP a cada 60s
tcp dport 8117 ct state new \
meter ssh_meter { ip saddr timeout 60s limit rate 3/minute } accept
tcp dport 8117 ct state new meter ssh_meter6 { ip6 saddr timeout 60s limit rate 3/minute } accept
tcp dport 8117 ct state new drop
# Túneis / VPN
iifname "wg0" accept
iifname "tun0" accept
udp dport 1194 accept
# Serviços expostos na WAN
iifname "enp0s6" tcp dport { 80, 443 } accept
iifname "enp0s6" udp dport { 32100, 32101, 32110, 32400 } accept
# DHCPv6 client
udp dport 546 accept
}
chain forward {
type filter hook forward priority filter; policy accept;
ct state established,related accept
iifname "tun0" accept
iifname "wg0" accept
tcp daddr 10.15.0.45 dport { 80, 443 } accept
tcp ip6 daddr 2001:db8:15::45 dport { 80, 443 } accept
}
chain output {
type filter hook output priority filter; policy accept;
}
chain icmpv4 {
icmp type echo-reply accept
icmp type echo-request limit rate 1/second burst 3 packets accept
icmp type echo-request drop
drop
}
chain icmpv6 {
icmpv6 type destination-unreachable accept
icmpv6 type packet-too-big limit rate 5/minute accept
icmpv6 type time-exceeded limit rate 5/minute accept
icmpv6 type echo-request limit rate 1/second burst 3 packets accept
icmpv6 type echo-request drop
icmpv6 type echo-reply accept
icmpv6 type nd-router-solicit accept
icmpv6 type nd-router-advert accept
icmpv6 type nd-neighbor-solicit accept
icmpv6 type nd-neighbor-advert accept
drop
}
}
table ip nat {
chain prerouting {
type nat hook prerouting priority dstnat;
iifname "enp0s6" tcp dport 80 dnat to 10.15.0.45:80
iifname "enp0s6" tcp dport 443 dnat to 10.15.0.45:443
}
chain postrouting {
type nat hook postrouting priority srcnat;
ip saddr 10.15.0.0/16 masquerade
ip saddr 172.17.0.0/16 masquerade
oifname "wg3" masquerade
# Hairpin NAT (clientes locais acessando o IP público)
oifname "enp0s6" ip daddr 10.15.0.45 tcp dport { 80, 443 } masquerade
}
}
table ip6 nat {
chain prerouting {
type nat hook prerouting priority dstnat;
iifname "enp0s6" tcp dport 80 dnat to [2001:db8:15::45]:80
iifname "enp0s6" tcp dport 443 dnat to [2001:db8:15::45]:443
}
chain postrouting {
type nat hook postrouting priority srcnat;
ip6 saddr 2001:db8:15::/48 masquerade
oifname "enp0s6" ip6 daddr 2001:db8:15::45 tcp dport { 80, 443 } masquerade
}
}
Executable
+55
View File
@@ -0,0 +1,55 @@
#!/bin/bash
# THIS FILE IS ADDED FOR COMPATIBILITY PURPOSES
#
# rc.local — server-mv
#
# Versão nftables. O firewall (antes ~200 linhas de iptables/ip6tables
# espalhadas entre rc.local e test.sh) agora é um único arquivo
# declarativo: nftables.conf. A versão anterior, baseada em
# iptables/ip6tables, foi preservada em rc.local.iptables — troque de
# volta se algo aqui quebrar e você precisar reverter rápido.
#
# Antes de usar este arquivo em produção:
# nft -c -f /home/rodrigo/server-mv/nftables.conf # valida sintaxe
# systemctl enable nftables # persistência
# Não rode este rc.local E o antigo (rc.local.iptables) ao mesmo tempo —
# são duas ferramentas de firewall (nft e iptables-legacy/nft-em-cima-
# de-iptables) que podem conflitar se ambas tentarem gerenciar as
# mesmas chains.
set -e
# ==============================================================
# 1. FIREWALL (nftables) — carregamento atômico: se houver erro de
# sintaxe no arquivo, o "nft -f" falha ANTES de aplicar qualquer coisa,
# então nunca fica um firewall pela metade no ar.
# ==============================================================
nft -f /home/rodrigo/server-mv/nftables.conf
# ==============================================================
# 2. Ajustes de kernel dependentes de runtime (não cobertos por sysctl.conf)
# ==============================================================
echo deadline > /sys/block/sda/queue/scheduler
echo 64 > /sys/block/sda/queue/max_sectors_kb # melhor para SSD virtual
echo f > /sys/class/net/enp0s6/queues/tx-0/xps_cpus
echo f > /sys/class/net/enp0s6/queues/rx-0/rps_cpus
echo 4096 > /sys/class/net/enp0s6/queues/rx-0/rps_flow_cnt
#tc qdisc add dev enp0s6 root cake bandwidth 1000mbit besteffort
#tc qdisc add dev wg0 root cake bandwidth 500mbit besteffort dual-dsthost
touch /var/lock/subsys/local
# ==============================================================
# 3. XDP / eBPF
# ==============================================================
ip link set enp0s6 mtu 1500
ip link set dev enp0s6 xdp obj /home/rodrigo/xdp/xdp_ddos_filter.o sec xdp
ip link set dev wg0 xdp obj /home/rodrigo/xdp/xdp_wg_mtu_optimizer.o sec xdp
# A bridge do Docker muda de nome a cada recriação da rede (hash do ID),
# então esse link precisa ser reaplicado se a rede "dockernet" for recriada.
ip link set dev br-02f7820020b4 xdp obj /home/rodrigo/xdp/bridge_xdp.o sec xdp
cd /home/docker/ebpf-sockmap/ && make load
+219
View File
@@ -0,0 +1,219 @@
#!/bin/bash
# THIS FILE IS ADDED FOR COMPATIBILITY PURPOSES
#
# rc.local — server-mv
# Reescrito para unificar o que antes estava dividido entre rc.local e
# test.sh (test.sh não é mais chamado a partir daqui — ver test.sh.old).
#
# Problemas do script antigo corrigidos aqui:
# - test.sh rodava "ip6tables -F" DEPOIS que este arquivo já tinha
# criado a regra de aceite de DHCPv6 (porta 546), apagando-a.
# - test.sh adicionava "ip6tables -A INPUT -j DROP" no meio do script,
# tornando toda regra de ICMPv6 adicionada depois dela morta (nunca
# era avaliada, pois o DROP já tinha batido antes).
# - DNAT de 80/443 estava duplicado (uma vez aqui, outra em test.sh,
# essa segunda sem porta de destino explícita).
# - Regra "-s 192.168.0.0/16 -j DROP" não estava restrita à interface
# WAN (enp0s6), então valeria para qualquer interface, inclusive
# futuras redes internas nesse range.
#
# Ordem importa em iptables: -A sempre anexa ao FIM da chain, então a
# ordem das linhas abaixo é a ordem de avaliação em tempo real.
set -e
# ==============================================================
# 1. FIREWALL — flush único, no início, de tudo (v4 e v6)
# ==============================================================
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X
iptables -t mangle -F
iptables -t mangle -X
ip6tables -F
ip6tables -X
ip6tables -t nat -F
ip6tables -t nat -X
ip6tables -t mangle -F
ip6tables -t mangle -X
# Políticas padrão (definidas ANTES de qualquer regra ser criada)
iptables -P INPUT DROP
iptables -P FORWARD ACCEPT
iptables -P OUTPUT ACCEPT
ip6tables -P INPUT DROP
ip6tables -P FORWARD ACCEPT
ip6tables -P OUTPUT ACCEPT
# ==============================================================
# 2. IPv4 — chains auxiliares
# ==============================================================
iptables -N SYN_FLOOD
iptables -A SYN_FLOOD -m limit --limit 10/sec --limit-burst 24 -j RETURN
iptables -A SYN_FLOOD -j DROP
iptables -N BAD_PACKETS
iptables -A BAD_PACKETS -m recent --set --name BAD_IPS
iptables -A BAD_PACKETS -m recent --update --seconds 300 --hitcount 5 --name BAD_IPS -j DROP
iptables -N ICMPv4
iptables -A ICMPv4 -p icmp --icmp-type 0 -j ACCEPT
iptables -A ICMPv4 -p icmp --icmp-type 8 -m limit --limit 1/s --limit-burst 3 -j ACCEPT
iptables -A ICMPv4 -p icmp --icmp-type 8 -j DROP
iptables -A ICMPv4 -p icmp -j DROP
# ==============================================================
# 3. IPv4 — INPUT (ordem: mais barato/mais usado primeiro)
# ==============================================================
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# ICMP logo em seguida: medição real mostrou ~66k pacotes/dia, 2º maior
# volume do INPUT depois de established/related — não faz sentido deixar
# por último, obrigando todo ping a atravessar as demais regras primeiro.
iptables -A INPUT -p icmp -j ICMPv4
# Pacotes malformados / scan
iptables -A INPUT -p tcp --tcp-flags ALL ALL -j BAD_PACKETS
iptables -A INPUT -p tcp --tcp-flags ALL NONE -j BAD_PACKETS
iptables -A INPUT -f -j DROP
# Anti-spoof: só faz sentido na WAN (rede interna real é 10.15.0.0/16 e 10.99.1.0/24)
iptables -A INPUT -i enp0s6 -s 192.168.0.0/16 -j DROP
# SYN flood
iptables -A INPUT -p tcp --syn -j SYN_FLOOD
# Rate limit de conexões novas na porta de SSH (8117)
iptables -A INPUT -p tcp --dport 8117 -m conntrack --ctstate NEW -m recent --set --name SSH
iptables -A INPUT -p tcp --dport 8117 -m conntrack --ctstate NEW -m recent --update --seconds 60 --hitcount 3 --name SSH -j DROP
# Túneis / VPN
iptables -A INPUT -i wg0 -j ACCEPT
iptables -A INPUT -i tun0 -j ACCEPT
iptables -A INPUT -p udp --dport 1194 -j ACCEPT
# Serviços expostos na WAN
iptables -A INPUT -i enp0s6 -p tcp -m multiport --dports 80,443,8117 -j ACCEPT
iptables -A INPUT -i enp0s6 -p udp -m multiport --dports 32100,32101,32110,32400 -j ACCEPT
# ==============================================================
# 4. IPv4 — FORWARD
# ==============================================================
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD -i tun0 -j ACCEPT
iptables -A FORWARD -i wg0 -j ACCEPT
iptables -A FORWARD -p tcp -d 10.15.0.45 --dport 80 -j ACCEPT
iptables -A FORWARD -p tcp -d 10.15.0.45 --dport 443 -j ACCEPT
# ==============================================================
# 5. IPv4 — NAT
# ==============================================================
iptables -t nat -A POSTROUTING -s 10.15.0.0/16 -j MASQUERADE
iptables -t nat -A POSTROUTING -s 172.17.0.0/16 -j MASQUERADE
iptables -t nat -A POSTROUTING -o wg3 -j MASQUERADE
# DNAT (WAN -> nginx reverso interno) — porta de destino explícita, sem duplicar
iptables -t nat -A PREROUTING -i enp0s6 -p tcp --dport 80 -j DNAT --to-destination 10.15.0.45:80
iptables -t nat -A PREROUTING -i enp0s6 -p tcp --dport 443 -j DNAT --to-destination 10.15.0.45:443
# Hairpin NAT para clientes locais acessando o IP público
iptables -t nat -A POSTROUTING -o enp0s6 -p tcp -d 10.15.0.45 --dport 80 -j MASQUERADE
iptables -t nat -A POSTROUTING -o enp0s6 -p tcp -d 10.15.0.45 --dport 443 -j MASQUERADE
# ==============================================================
# 6. IPv6 — chains auxiliares
# ==============================================================
ip6tables -N SYN_FLOOD
ip6tables -A SYN_FLOOD -m limit --limit 10/sec --limit-burst 24 -j RETURN
ip6tables -A SYN_FLOOD -j DROP
ip6tables -N BAD_PACKETS
ip6tables -A BAD_PACKETS -m recent --set --name BAD_IPS6
ip6tables -A BAD_PACKETS -m recent --update --seconds 300 --hitcount 5 --name BAD_IPS6 -j DROP
ip6tables -N ICMPv6
ip6tables -A ICMPv6 -p ipv6-icmp --icmpv6-type 1 -j ACCEPT
ip6tables -A ICMPv6 -p ipv6-icmp --icmpv6-type 2 -m limit --limit 5/min -j ACCEPT
ip6tables -A ICMPv6 -p ipv6-icmp --icmpv6-type 3 -m limit --limit 5/min -j ACCEPT
ip6tables -A ICMPv6 -p ipv6-icmp --icmpv6-type 128 -m limit --limit 1/s --limit-burst 3 -j ACCEPT
ip6tables -A ICMPv6 -p ipv6-icmp --icmpv6-type 129 -j ACCEPT
ip6tables -A ICMPv6 -p ipv6-icmp --icmpv6-type 133 -j ACCEPT
ip6tables -A ICMPv6 -p ipv6-icmp --icmpv6-type 135 -j ACCEPT
ip6tables -A ICMPv6 -p ipv6-icmp --icmpv6-type 136 -j ACCEPT
ip6tables -A ICMPv6 -p ipv6-icmp -j DROP
# ==============================================================
# 7. IPv6 — INPUT
# (NOTA: nenhuma regra "-j DROP" solta aqui — a policy INPUT DROP,
# já definida no passo 1, é o catch-all. Colocar um DROP explícito
# no meio da chain era o bug que matava as regras seguintes.)
# ==============================================================
ip6tables -A INPUT -i lo -j ACCEPT
ip6tables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
ip6tables -A INPUT -p udp --dport 546 -j ACCEPT # DHCPv6 client
ip6tables -A INPUT -p tcp --tcp-flags ALL ALL -j BAD_PACKETS
ip6tables -A INPUT -p tcp --tcp-flags ALL NONE -j BAD_PACKETS
# Anti-spoof
ip6tables -A INPUT -i enp0s6 -s fe80::/10 -j DROP
ip6tables -A INPUT -i enp0s6 -s fd00::/8 -j DROP
ip6tables -A INPUT -d ff00::/8 -j DROP
ip6tables -A INPUT -p tcp --syn -j SYN_FLOOD
ip6tables -A INPUT -p ipv6-icmp -j ICMPv6
# Rate limit SSH (8117)
ip6tables -A INPUT -p tcp --dport 8117 -m conntrack --ctstate NEW -m recent --set --name SSH6
ip6tables -A INPUT -p tcp --dport 8117 -m conntrack --ctstate NEW -m recent --update --seconds 60 --hitcount 3 --name SSH6 -j DROP
ip6tables -A INPUT -i wg0 -j ACCEPT
ip6tables -A INPUT -i enp0s6 -p tcp -m multiport --dports 80,443,8117 -j ACCEPT
ip6tables -A INPUT -i enp0s6 -p udp -m multiport --dports 32100,32400 -j ACCEPT
# ==============================================================
# 8. IPv6 — FORWARD / NAT
# ==============================================================
ip6tables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
ip6tables -A FORWARD -i wg0 -j ACCEPT
ip6tables -A FORWARD -p tcp -d 2001:db8:15::45 --dport 80 -j ACCEPT
ip6tables -A FORWARD -p tcp -d 2001:db8:15::45 --dport 443 -j ACCEPT
ip6tables -t nat -A POSTROUTING -s 2001:db8:15::/48 -j MASQUERADE
ip6tables -t nat -A PREROUTING -i enp0s6 -p tcp --dport 80 -j DNAT --to-destination [2001:db8:15::45]:80
ip6tables -t nat -A PREROUTING -i enp0s6 -p tcp --dport 443 -j DNAT --to-destination [2001:db8:15::45]:443
ip6tables -t nat -A POSTROUTING -o enp0s6 -p tcp -d 2001:db8:15::45 --dport 80 -j MASQUERADE
ip6tables -t nat -A POSTROUTING -o enp0s6 -p tcp -d 2001:db8:15::45 --dport 443 -j MASQUERADE
# ==============================================================
# 9. Ajustes de kernel dependentes de runtime (não cobertos por sysctl.conf)
# ==============================================================
echo deadline > /sys/block/sda/queue/scheduler
echo 64 > /sys/block/sda/queue/max_sectors_kb # melhor para SSD virtual
echo f > /sys/class/net/enp0s6/queues/tx-0/xps_cpus
echo f > /sys/class/net/enp0s6/queues/rx-0/rps_cpus
echo 4096 > /sys/class/net/enp0s6/queues/rx-0/rps_flow_cnt
#tc qdisc add dev enp0s6 root cake bandwidth 1000mbit besteffort
#tc qdisc add dev wg0 root cake bandwidth 500mbit besteffort dual-dsthost
touch /var/lock/subsys/local
# ==============================================================
# 10. XDP / eBPF
# ==============================================================
ip link set enp0s6 mtu 1500
ip link set dev enp0s6 xdp obj /home/rodrigo/xdp/xdp_ddos_filter.o sec xdp
ip link set dev wg0 xdp obj /home/rodrigo/xdp/xdp_wg_mtu_optimizer.o sec xdp
# A bridge do Docker muda de nome a cada recriação da rede (hash do ID),
# então esse link precisa ser reaplicado se a rede "dockernet" for recriada.
ip link set dev br-02f7820020b4 xdp obj /home/rodrigo/xdp/bridge_xdp.o sec xdp
cd /home/docker/ebpf-sockmap/ && make load
+82
View File
@@ -0,0 +1,82 @@
# ==============================================================
# sysctl.conf — server-mv (Oracle Cloud ARM64, 4 vCPU / 24GB RAM)
# Reescrito para remover duplicatas e organizar por categoria.
# Aplicar com: sysctl -p /etc/sysctl.d/99-server-mv.conf
#
# IMPORTANTE: este host roda o daemon "bpftune" (confirmado via
# `bpftool prog list` — tuners tcp-buffer, tcp-conn, net-buffer e
# neigh-table ativos). Esses tuners ajustam em tempo real, via eBPF,
# exatamente os mesmos knobs que costumam ir num sysctl.conf estático
# (tcp_rmem/wmem, buffers de rede, backlog de SYN, tabela de vizinhança).
# Por isso, DE PROPÓSITO, essas chaves NÃO são fixadas aqui — deixamos
# o bpftune reagir à carga real em vez de brigar com um valor fixo
# (foi essa disputa, aliás, que gerou a bagunça do sysctl.conf antigo:
# algo/alguém reescrevendo os mesmos valores repetidamente tentando
# "vencer" o bpftune). O que continua fixo abaixo é só o que o bpftune
# não gerencia: segurança, memória, filesystem, congestion control e
# timeouts diversos.
# ==============================================================
# ==============================
# 🚀 REDE — o que o bpftune NÃO gerencia
# ==============================
net.ipv4.tcp_fastopen = 3
net.ipv4.tcp_synack_retries = 3
net.ipv4.tcp_max_tw_buckets = 200000
net.ipv4.tcp_fin_timeout = 10
net.ipv4.tcp_keepalive_time = 60
net.ipv4.tcp_keepalive_intvl = 10
net.ipv4.tcp_keepalive_probes = 6
net.ipv4.tcp_congestion_control = bbr
net.core.default_qdisc = fq_codel
net.ipv4.ip_local_port_range = 20000 65000
# RPS/RFS (referenciado pelo rc.local via /sys/class/net/.../rps_*)
net.core.rps_sock_flow_entries = 4096
# ==============================
# 🔒 SEGURANÇA DA PILHA TCP/IP
# ==============================
net.ipv4.tcp_syncookies = 1
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
# Forwarding (necessário para NAT/DNAT do Docker e WireGuard)
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
# ==============================
# 💾 GERENCIAMENTO DE MEMÓRIA
# ==============================
vm.dirty_ratio = 5
vm.dirty_background_ratio = 2
vm.dirty_bytes = 134217728
vm.dirty_background_bytes = 67108864
vm.vfs_cache_pressure = 50
vm.swappiness = 5
vm.zone_reclaim_mode = 0
# ==============================
# 📂 SISTEMA DE ARQUIVOS
# ==============================
fs.file-max = 2097152
fs.inotify.max_user_watches = 524288
fs.inotify.max_user_instances = 1024
# ==============================
# 🌎 IPv6
# ==============================
net.ipv6.conf.all.accept_ra = 0
net.ipv6.conf.default.accept_ra = 0
# gc_thresh3 (tabela de vizinhança) removido: bpftune tem um
# neigh-table-tuner ativo que ajusta isso dinamicamente.
# ==============================
# 🐳 CONNTRACK (host-level; containers herdam via netns quando não sobrescrito)
# ==============================
net.netfilter.nf_conntrack_max = 262144
+213
View File
@@ -0,0 +1,213 @@
###############################
####### SEGURANÇA REFORÇADA #######
###############################
#iptables -F
#iptables -X
#iptables -t nat -F
#iptables -t nat -X
# Proteção básica contra SYN Flood
iptables -N SYN_FLOOD
iptables -A SYN_FLOOD -m limit --limit 10/sec --limit-burst 24 -j RETURN
iptables -A SYN_FLOOD -j LOG --log-prefix "SYN Flood: "
iptables -A SYN_FLOOD -j DROP
iptables -A INPUT -p tcp --syn -j SYN_FLOOD
# Proteção contra port scanners (detecção de tentativas de conexão em portas fechadas)
iptables -N PORTSCAN
iptables -A PORTSCAN -m recent --set --name PORTSCAN
iptables -A PORTSCAN -j DROP
iptables -A INPUT -m recent --update --seconds 60 --hitcount 1 --name PORTSCAN -j DROP
# Bloqueio de IPs suspeitos após 5 tentativas de conexão inválidas
iptables -N BAD_PACKETS
iptables -A BAD_PACKETS -m recent --set --name BAD_IPS
#iptables -A BAD_PACKETS -m recent --update --seconds 300 --hitcount 5 --name BAD_IPS -j LOG --log-prefix "IP Bloqueado: "
iptables -A BAD_PACKETS -m recent --update --seconds 300 --hitcount 5 --name BAD_IPS -j DROP
iptables -A INPUT -p tcp --tcp-flags ALL ALL -j BAD_PACKETS # Pacotes inválidos
iptables -A INPUT -p tcp --tcp-flags ALL NONE -j BAD_PACKETS # NULL packets
# Rate limiting para prevenção de brute force (exemplo para SSH - ajuste a porta)
iptables -A INPUT -p tcp --dport 8117 -m state --state NEW -m recent --set --name SSH
iptables -A INPUT -p tcp --dport 8117 -m state --state NEW -m recent --update --seconds 60 --hitcount 3 --name SSH -j DROP
###############################
####### OTIMIZAÇÃO DE PERFORMANCE #######
###############################
# Ordenação estratégica das regras (mais usadas primeiro)
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT # Mover para início
# Agrupamento de portas com multiport
iptables -A INPUT -i enp0s6 -p tcp -m multiport --dports 80,443,8117 -j ACCEPT
iptables -A INPUT -i enp0s6 -p udp -m multiport --dports 32100,32400 -j ACCEPT
# Compactação de regras NAT
iptables -t nat -A PREROUTING -i enp0s6 -p tcp -m multiport --dports 80,443 -j DNAT --to-destination 10.15.0.45
###############################
####### AJUSTES DE SISTEMA #######
###############################
# Melhor tratamento de pacotes fragmentados
iptables -A INPUT -f -j DROP
# Prevenção de spoofing
#iptables -A INPUT -s 10.0.0.0/8 -j DROP
#iptables -A INPUT -s 172.16.0.0/12 -j DROP
iptables -A INPUT -s 192.168.0.0/16 -j DROP
# Logging de tráfego bloqueado (use com moderação)
#iptables -A INPUT -m limit --limit 5/min -j LOG --log-prefix "IPTables-Dropped: " --log-level 4
###############################
####### POLÍTICAS SEGURAS #######
###############################
# Política mais restritiva para FORWARD
iptables -P FORWARD ACCEPT
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -I INPUT -i wg0 -j ACCEPT
iptables -I FORWARD -i wg0 -j ACCEPT
# Limpar todas as regras IPv6
ip6tables -F
ip6tables -X
ip6tables -t mangle -F
ip6tables -t mangle -X
ip6tables -t nat -F
ip6tables -t nat -X
# Definir políticas padrão
ip6tables -P INPUT DROP
ip6tables -P FORWARD ACCEPT
ip6tables -P OUTPUT ACCEPT
# Habilitar redirecionamento IPv6
echo 1 > /proc/sys/net/ipv6/conf/all/forwarding
# --- REGRAS BÁSICAS ---
ip6tables -A INPUT -i lo -j ACCEPT
ip6tables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Permitir ICMPv6 (essencial para funcionamento do IPv6)
ip6tables -A INPUT -p ipv6-icmp -j ACCEPT
# Portas abertas (ajuste a interface)
ip6tables -A INPUT -i enp0s6 -p udp --dport 32100 -j ACCEPT
ip6tables -A INPUT -i enp0s6 -p udp --dport 32400 -j ACCEPT
ip6tables -A INPUT -i enp0s6 -p tcp --dport 8117 -j ACCEPT
ip6tables -A INPUT -i enp0s6 -p tcp --dport 80 -j ACCEPT
ip6tables -A INPUT -i enp0s6 -p tcp --dport 443 -j ACCEPT
# WireGuard
ip6tables -A INPUT -i wg0 -j ACCEPT
ip6tables -A FORWARD -i wg0 -j ACCEPT
# --- NAT IPv6 ---
ip6tables -t nat -A POSTROUTING -s 2001:db8:15::/48 -j MASQUERADE
# --- REGRAS DE SEGURANÇA ---
# Proteção contra SYN Flood (mesma configuração)
ip6tables -N SYN_FLOOD
ip6tables -A SYN_FLOOD -m limit --limit 10/sec --limit-burst 24 -j RETURN
#ip6tables -A SYN_FLOOD -j LOG --log-prefix "IPv6 SYN Flood: "
ip6tables -A SYN_FLOOD -j DROP
ip6tables -A INPUT -p tcp --syn -j SYN_FLOOD
# Prevenção de port scanners
ip6tables -N PORTSCAN
ip6tables -A PORTSCAN -m recent --set --name PORTSCAN6
ip6tables -A PORTSCAN -j DROP
ip6tables -A INPUT -m recent --update --seconds 60 --hitcount 1 --name PORTSCAN6 -j DROP
# Bloqueio de IPs suspeitos
ip6tables -N BAD_PACKETS
ip6tables -A BAD_PACKETS -m recent --set --name BAD_IPS6
#ip6tables -A BAD_PACKETS -m recent --update --seconds 300 --hitcount 5 --name BAD_IPS6 -j LOG --log-prefix "IPv6 IP Bloqueado: "
ip6tables -A BAD_PACKETS -m recent --update --seconds 300 --hitcount 5 --name BAD_IPS6 -j DROP
ip6tables -A INPUT -p tcp --tcp-flags ALL ALL -j BAD_PACKETS
ip6tables -A INPUT -p tcp --tcp-flags ALL NONE -j BAD_PACKETS
# Rate limiting SSH
ip6tables -A INPUT -p tcp --dport 8117 -m state --state NEW -m recent --set --name SSH6
ip6tables -A INPUT -p tcp --dport 8117 -m state --state NEW -m recent --update --seconds 60 --hitcount 3 --name SSH6 -j DROP
# --- OTIMIZAÇÕES ---
ip6tables -A INPUT -i enp0s6 -p tcp -m multiport --dports 80,443,8117 -j ACCEPT
ip6tables -A INPUT -i enp0s6 -p udp -m multiport --dports 32100,32400 -j ACCEPT
# --- AJUSTES IPv6 ESPECÍFICOS ---
# Bloquear pacotes IPv6 inválidos
ip6tables -A INPUT -m rpfilter --validmark -j ACCEPT
ip6tables -A INPUT -j DROP
# Prevenção de spoofing IPv6
ip6tables -A INPUT -s fe80::/10 -i enp0s6 -j DROP # Bloqueia link-local na WAN
ip6tables -A INPUT -s fd00::/8 -i enp0s6 -j DROP # Bloqueia ULA na WAN
ip6tables -A INPUT -d ff00::/8 -j DROP # Bloqueia multicast
# --- LOGGING ---
#ip6tables -A INPUT -m limit --limit 5/min -j LOG --log-prefix "IPv6 Dropped: " --log-level 4
ip6tables -A INPUT -p ipv6-icmp -j ACCEPT # Permite vizinhação, descoberta de roteador, etc.
ip6tables -A INPUT -m rpfilter --validmark -j ACCEPT # Reverse Path Filtering
###############################
####### TUNING DE KERNEL #######
###############################
# Otimização de parâmetros de rede
echo 1 > /proc/sys/net/ipv4/tcp_syncookies
echo 1 > /proc/sys/net/ipv4/conf/all/rp_filter
echo 0 > /proc/sys/net/ipv4/conf/all/accept_source_route
echo "net.ipv4.tcp_max_syn_backlog = 2048" >> /etc/sysctl.conf
echo "net.ipv4.tcp_synack_retries = 3" >> /etc/sysctl.conf
sysctl -p
# Criação da cadeia ICMPv4
iptables -N ICMPv4
# Permitir Echo Reply (tipo 0) sem limitação
iptables -A ICMPv4 -p icmp --icmp-type 0 -j ACCEPT
# Limitar Echo Request (tipo 8) para 1/s com burst de 3
iptables -A ICMPv4 -p icmp --icmp-type 8 -m limit --limit 1/s --limit-burst 3 -j ACCEPT
# Bloquear e registrar excesso de Echo Request
#iptables -A ICMPv4 -p icmp --icmp-type 8 -j LOG --log-prefix "Ping flood bloqueado: "
iptables -A ICMPv4 -p icmp --icmp-type 8 -j DROP
# Bloquear todos os outros tipos de ICMP
iptables -A ICMPv4 -p icmp -j DROP
# Aplicar cadeia ICMPv4 à entrada
iptables -A INPUT -p icmp -j ICMPv4
# Criação da cadeia ICMPv6
ip6tables -N ICMPv6
# Tipos essenciais para funcionamento básico (permitir sem limitação)
ip6tables -A ICMPv6 -p ipv6-icmp --icmpv6-type 1 -j ACCEPT # Destino inalcançável
ip6tables -A ICMPv6 -p ipv6-icmp --icmpv6-type 128 -m limit --limit 1/s --limit-burst 3 -j ACCEPT # Echo Request
ip6tables -A ICMPv6 -p ipv6-icmp --icmpv6-type 129 -j ACCEPT # Echo Reply
ip6tables -A ICMPv6 -p ipv6-icmp --icmpv6-type 133/135/136 -j ACCEPT # Descoberta de vizinhança
# Limitar outros tipos importantes
ip6tables -A ICMPv6 -p ipv6-icmp --icmpv6-type 2 -m limit --limit 5/min -j ACCEPT # Packet Too Big
ip6tables -A ICMPv6 -p ipv6-icmp --icmpv6-type 3 -m limit --limit 5/min -j ACCEPT # Time Exceeded
# Bloquear e registrar excesso de Echo Request
#ip6tables -A ICMPv6 -p ipv6-icmp --icmpv6-type 128 -j LOG --log-prefix "Ping6 flood bloqueado: "
ip6tables -A ICMPv6 -p ipv6-icmp --icmpv6-type 128 -j DROP
# Bloquear todos os outros tipos
ip6tables -A ICMPv6 -p ipv6-icmp -j DROP
# Aplicar cadeia ICMPv6 à entrada
ip6tables -A INPUT -p ipv6-icmp -j ICMPv6
+20
View File
@@ -0,0 +1,20 @@
LLC ?= llc
CLANG ?= clang
CC ?= gcc
all: bridge_xdp.o loader
bridge_xdp.o: bridge_xdp.c
$(CLANG) -S \
-target bpf \
-D __BPF_TRACING__ \
-I/usr/include/x86_64-linux-gnu \
-Wall \
-O2 -emit-llvm -c -o bridge_xdp.ll bridge_xdp.c
$(LLC) -march=bpf -filetype=obj -o bridge_xdp.o bridge_xdp.ll
loader: loader.c
$(CC) -Wall -o loader loader.c -l:libbpf.a -lelf -lz
clean:
rm -f bridge_xdp.o bridge_xdp.ll loader
BIN
View File
Binary file not shown.
+158
View File
@@ -0,0 +1,158 @@
// Exemplo AF_XDP com RX, TX, multi-queue e recycling, sem libbpf/xsk.h
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/socket.h>
#include <linux/if_xdp.h>
#include <linux/if_ether.h>
#include <sys/mman.h>
#include <poll.h>
#include <stdint.h>
#include <sys/types.h>
#define NUM_FRAMES 4096
#define FRAME_SIZE 2048
#define UMEM_SIZE (NUM_FRAMES * FRAME_SIZE)
#define RX_RING_SIZE 2048
#define TX_RING_SIZE 2048
#define FILL_RING_SIZE 2048
#define COMP_RING_SIZE 2048
#define BATCH_SIZE 64
static inline void die(const char *msg) {
perror(msg);
exit(1);
}
static size_t roundup_page(size_t sz) {
size_t pg = sysconf(_SC_PAGESIZE);
return ((sz + pg - 1) / pg) * pg;
}
// Helpers para acessar os pointers dos rings
#define RING_PROD_PTR(ring) ((volatile __u32 *)(ring))
#define RING_CONS_PTR(ring) ((volatile __u32 *)(ring) + 1)
#define RING_DESC(ring) ((void *)((volatile __u32 *)(ring) + 2))
int main(int argc, char **argv) {
if (argc < 2) {
printf("Uso: %s <iface> [queue_id]\n", argv[0]);
return 1;
}
const char *ifname = argv[1];
int queue_id = 0;
if (argc > 2)
queue_id = atoi(argv[2]);
int ifindex = if_nametoindex(ifname);
if (!ifindex) die("if_nametoindex");
// 1. Crie socket AF_XDP
int xdp_sock = socket(AF_XDP, SOCK_RAW, 0);
if (xdp_sock < 0) die("socket(AF_XDP)");
// 2. Alocar UMEM
void *umem_area = NULL;
if (posix_memalign(&umem_area, getpagesize(), UMEM_SIZE))
die("posix_memalign");
// 3. Registrar UMEM no kernel
struct xdp_umem_reg mr = {
.addr = (__u64)(uintptr_t)umem_area,
.len = UMEM_SIZE,
.chunk_size = FRAME_SIZE,
.headroom = 0,
.flags = 0,
};
if (setsockopt(xdp_sock, SOL_XDP, XDP_UMEM_REG, &mr, sizeof(mr)) < 0)
die("setsockopt(XDP_UMEM_REG)");
// 4. Configurar tamanho dos rings
int sz = FILL_RING_SIZE;
if (setsockopt(xdp_sock, SOL_XDP, XDP_UMEM_FILL_RING, &sz, sizeof(sz)) < 0)
die("setsockopt(XDP_UMEM_FILL_RING)");
sz = COMP_RING_SIZE;
if (setsockopt(xdp_sock, SOL_XDP, XDP_UMEM_COMPLETION_RING, &sz, sizeof(sz)) < 0)
die("setsockopt(XDP_UMEM_COMPLETION_RING)");
sz = RX_RING_SIZE;
if (setsockopt(xdp_sock, SOL_XDP, XDP_RX_RING, &sz, sizeof(sz)) < 0)
die("setsockopt(XDP_RX_RING)");
sz = TX_RING_SIZE;
if (setsockopt(xdp_sock, SOL_XDP, XDP_TX_RING, &sz, sizeof(sz)) < 0)
die("setsockopt(XDP_TX_RING)");
// 5. mmap dos rings (tamanho exato, sem roundup_page)
size_t fill_ring_sz = FILL_RING_SIZE * sizeof(__u64) + 8;
size_t comp_ring_sz = COMP_RING_SIZE * sizeof(__u64) + 8;
size_t rx_ring_sz = RX_RING_SIZE * sizeof(struct xdp_desc) + 8;
size_t tx_ring_sz = TX_RING_SIZE * sizeof(struct xdp_desc) + 8;
void *fill_ring = mmap(NULL, fill_ring_sz, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, xdp_sock, XDP_UMEM_PGOFF_FILL_RING);
if (fill_ring == MAP_FAILED) die("mmap(FILL_RING)");
void *comp_ring = mmap(NULL, comp_ring_sz, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, xdp_sock, XDP_UMEM_PGOFF_COMPLETION_RING);
if (comp_ring == MAP_FAILED) die("mmap(COMP_RING)");
void *rx_ring = mmap(NULL, rx_ring_sz, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, xdp_sock, XDP_PGOFF_RX_RING);
if (rx_ring == MAP_FAILED) die("mmap(RX_RING)");
void *tx_ring = mmap(NULL, tx_ring_sz, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, xdp_sock, XDP_PGOFF_TX_RING);
if (tx_ring == MAP_FAILED) die("mmap(TX_RING)");
printf("fill_ring: %p, comp_ring: %p, rx_ring: %p, tx_ring: %p\n", fill_ring, comp_ring, rx_ring, tx_ring);
// Cheque ponteiros dos rings
printf("fill_prod_ptr: %p, fill_desc: %p\n", RING_PROD_PTR(fill_ring), RING_DESC(fill_ring));
printf("rx_prod_ptr: %p, rx_desc: %p\n", RING_PROD_PTR(rx_ring), RING_DESC(rx_ring));
printf("tx_prod_ptr: %p, tx_desc: %p\n", RING_PROD_PTR(tx_ring), RING_DESC(tx_ring));
printf("comp_prod_ptr: %p, comp_desc: %p\n", RING_PROD_PTR(comp_ring), RING_DESC(comp_ring));
// 6. Preencher fill ring com endereços dos frames
__u64 *fill = (__u64 *)RING_DESC(fill_ring);
for (int i = 0; i < NUM_FRAMES; i++) {
fill[i] = i * FRAME_SIZE;
}
// Atualizar producer pointer do fill ring
*RING_PROD_PTR(fill_ring) = NUM_FRAMES;
// 7. Bind socket AF_XDP à interface/fila
struct sockaddr_xdp sxdp = {
.sxdp_family = AF_XDP,
.sxdp_ifindex = ifindex,
.sxdp_queue_id = queue_id,
.sxdp_flags = 0,
};
if (bind(xdp_sock, (struct sockaddr *)&sxdp, sizeof(sxdp)) < 0)
die("bind(AF_XDP)");
printf("Socket AF_XDP ligado na interface %s (fila %d)\n", ifname, queue_id);
// 8. Poll para pacotes
struct pollfd fds = { .fd = xdp_sock, .events = POLLIN };
struct xdp_desc *rx = (struct xdp_desc *)RING_DESC(rx_ring);
while (1) {
int ret = poll(&fds, 1, 1000);
if (ret < 0) die("poll");
if (ret == 0) continue;
__u32 rx_cons = *RING_CONS_PTR(rx_ring);
__u32 rx_prod = *RING_PROD_PTR(rx_ring);
if (!rx || rx_prod == rx_cons) continue;
while (rx_cons != rx_prod) {
struct xdp_desc *desc = &rx[rx_cons % RX_RING_SIZE];
if (!desc) break;
void *pkt = (char *)umem_area + desc->addr;
printf("Pacote RX: %u bytes (addr=%llu)\n", desc->len, (unsigned long long)desc->addr);
rx_cons++;
}
*RING_CONS_PTR(rx_ring) = rx_cons;
}
// Cleanup (não alcançado neste exemplo)
close(xdp_sock);
free(umem_area);
return 0;
}
+77
View File
@@ -0,0 +1,77 @@
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
#include <linux/if_ether.h>
#include <linux/if_vlan.h>
#include "bridge_xdp.h"
// Definição local para struct vlan_hdr (caso não esteja disponível)
#ifndef VLAN_HLEN
#define VLAN_HLEN 4
#endif
struct vlan_hdr {
__be16 h_vlan_TCI;
__be16 h_vlan_encapsulated_proto;
};
struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__uint(max_entries, 1);
__type(key, __u32);
__type(value, struct packet_stats);
} stats_map SEC(".maps");
SEC("xdp")
int bridge_filter(struct xdp_md *ctx)
{
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct ethhdr *eth = data;
__u16 h_proto;
struct iphdr *iph;
int action = XDP_PASS;
__u32 key = 0;
struct packet_stats *stats;
if ((void*)(eth + 1) > data_end)
return XDP_ABORTED;
h_proto = eth->h_proto;
// Tráfego broadcast/multicast: libera imediatamente
if (eth->h_dest[0] & 1)
goto stats;
// VLAN
if (h_proto == bpf_htons(ETH_P_8021Q) || h_proto == bpf_htons(ETH_P_8021AD)) {
struct vlan_hdr *vhdr = (void *)(eth + 1);
if ((void *)(vhdr + 1) > data_end)
return XDP_ABORTED;
h_proto = vhdr->h_vlan_encapsulated_proto;
iph = (void *)(vhdr + 1);
} else {
iph = (void *)(eth + 1);
}
// IPv4 local: libera imediatamente
if (h_proto == bpf_htons(ETH_P_IP)) {
if ((void*)(iph + 1) > data_end)
goto stats;
if ((iph->daddr & bpf_htonl(0xffff0000)) == bpf_htonl(0x0a0f0000))
goto stats;
}
// ...outros filtros podem ser adicionados aqui...
stats:
// Atualizar estatísticas apenas uma vez
stats = bpf_map_lookup_elem(&stats_map, &key);
if (stats) {
stats->packets++;
stats->bytes += (data_end - data);
}
return action;
}
char _license[] SEC("license") = "GPL";
+15
View File
@@ -0,0 +1,15 @@
#ifndef BRIDGE_XDP_H
#define BRIDGE_XDP_H
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>
// Estrutura para estatísticas
struct packet_stats {
__u64 packets;
__u64 bytes;
};
#endif
+250
View File
@@ -0,0 +1,250 @@
; ModuleID = 'bridge_xdp.c'
source_filename = "bridge_xdp.c"
target datalayout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128"
target triple = "bpf"
%struct.anon = type { ptr, ptr, ptr, ptr }
%struct.xdp_md = type { i32, i32, i32, i32, i32, i32 }
%struct.ethhdr = type { [6 x i8], [6 x i8], i16 }
%struct.packet_stats = type { i64, i64 }
@stats_map = dso_local global %struct.anon zeroinitializer, section ".maps", align 8, !dbg !0
@_license = dso_local global [4 x i8] c"GPL\00", section "license", align 1, !dbg !30
@llvm.compiler.used = appending global [3 x ptr] [ptr @_license, ptr @bridge_filter, ptr @stats_map], section "llvm.metadata"
; Function Attrs: nounwind
define dso_local noundef i32 @bridge_filter(ptr nocapture noundef readonly %0) #0 section "xdp" !dbg !67 {
%2 = alloca i32, align 4, !DIAssignID !129
call void @llvm.dbg.assign(metadata i1 undef, metadata !95, metadata !DIExpression(), metadata !129, metadata ptr %2, metadata !DIExpression()), !dbg !130
tail call void @llvm.dbg.value(metadata ptr %0, metadata !80, metadata !DIExpression()), !dbg !130
%3 = getelementptr inbounds %struct.xdp_md, ptr %0, i64 0, i32 1, !dbg !131
%4 = load i32, ptr %3, align 4, !dbg !131, !tbaa !132
%5 = zext i32 %4 to i64, !dbg !137
%6 = inttoptr i64 %5 to ptr, !dbg !138
tail call void @llvm.dbg.value(metadata ptr %6, metadata !81, metadata !DIExpression()), !dbg !130
%7 = load i32, ptr %0, align 4, !dbg !139, !tbaa !140
%8 = zext i32 %7 to i64, !dbg !141
%9 = inttoptr i64 %8 to ptr, !dbg !142
tail call void @llvm.dbg.value(metadata ptr %9, metadata !82, metadata !DIExpression()), !dbg !130
tail call void @llvm.dbg.value(metadata ptr %9, metadata !83, metadata !DIExpression()), !dbg !130
%10 = getelementptr inbounds %struct.ethhdr, ptr %9, i64 1, !dbg !143
%11 = icmp ugt ptr %10, %6, !dbg !145
br i1 %11, label %23, label %12, !dbg !146
12: ; preds = %1
call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %2) #4, !dbg !147
store i32 0, ptr %2, align 4, !dbg !148, !tbaa !149, !DIAssignID !150
call void @llvm.dbg.assign(metadata i32 0, metadata !95, metadata !DIExpression(), metadata !150, metadata ptr %2, metadata !DIExpression()), !dbg !130
%13 = call ptr inttoptr (i64 1 to ptr)(ptr noundef nonnull @stats_map, ptr noundef nonnull %2) #4, !dbg !151
tail call void @llvm.dbg.value(metadata ptr %13, metadata !96, metadata !DIExpression()), !dbg !130
%14 = icmp eq ptr %13, null, !dbg !152
br i1 %14, label %22, label %15, !dbg !154
15: ; preds = %12
%16 = load i64, ptr %13, align 8, !dbg !155, !tbaa !157
%17 = add i64 %16, 1, !dbg !155
store i64 %17, ptr %13, align 8, !dbg !155, !tbaa !157
%18 = sub nsw i64 %5, %8, !dbg !160
%19 = getelementptr inbounds %struct.packet_stats, ptr %13, i64 0, i32 1, !dbg !161
%20 = load i64, ptr %19, align 8, !dbg !162, !tbaa !163
%21 = add i64 %18, %20, !dbg !162
store i64 %21, ptr %19, align 8, !dbg !162, !tbaa !163
br label %22, !dbg !164
22: ; preds = %15, %12
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %2) #4, !dbg !165
br label %23
23: ; preds = %1, %22
%24 = phi i32 [ 2, %22 ], [ 0, %1 ], !dbg !130
ret i32 %24, !dbg !165
}
; Function Attrs: mustprogress nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
declare void @llvm.lifetime.start.p0(i64 immarg, ptr nocapture) #1
; Function Attrs: mustprogress nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
declare void @llvm.lifetime.end.p0(i64 immarg, ptr nocapture) #1
; Function Attrs: mustprogress nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) #2
; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare void @llvm.dbg.value(metadata, metadata, metadata) #3
attributes #0 = { nounwind "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" }
attributes #1 = { mustprogress nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) }
attributes #2 = { mustprogress nocallback nofree nosync nounwind speculatable willreturn memory(none) }
attributes #3 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
attributes #4 = { nounwind }
!llvm.dbg.cu = !{!2}
!llvm.module.flags = !{!61, !62, !63, !64, !65}
!llvm.ident = !{!66}
!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression())
!1 = distinct !DIGlobalVariable(name: "stats_map", scope: !2, file: !3, line: 12, type: !36, isLocal: false, isDefinition: true)
!2 = distinct !DICompileUnit(language: DW_LANG_C11, file: !3, producer: "clang version 18.1.8 ( 18.1.8-3.0.1.el9)", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !4, retainedTypes: !14, globals: !20, splitDebugInlining: false, nameTableKind: None)
!3 = !DIFile(filename: "bridge_xdp.c", directory: "/root/xdp_filter")
!4 = !{!5}
!5 = !DICompositeType(tag: DW_TAG_enumeration_type, name: "xdp_action", file: !6, line: 6313, baseType: !7, size: 32, elements: !8)
!6 = !DIFile(filename: "/usr/include/linux/bpf.h", directory: "")
!7 = !DIBasicType(name: "unsigned int", size: 32, encoding: DW_ATE_unsigned)
!8 = !{!9, !10, !11, !12, !13}
!9 = !DIEnumerator(name: "XDP_ABORTED", value: 0)
!10 = !DIEnumerator(name: "XDP_DROP", value: 1)
!11 = !DIEnumerator(name: "XDP_PASS", value: 2)
!12 = !DIEnumerator(name: "XDP_TX", value: 3)
!13 = !DIEnumerator(name: "XDP_REDIRECT", value: 4)
!14 = !{!15, !16, !17}
!15 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: null, size: 64)
!16 = !DIBasicType(name: "long", size: 64, encoding: DW_ATE_signed)
!17 = !DIDerivedType(tag: DW_TAG_typedef, name: "__u16", file: !18, line: 24, baseType: !19)
!18 = !DIFile(filename: "/usr/include/asm-generic/int-ll64.h", directory: "")
!19 = !DIBasicType(name: "unsigned short", size: 16, encoding: DW_ATE_unsigned)
!20 = !{!21, !30, !0}
!21 = !DIGlobalVariableExpression(var: !22, expr: !DIExpression())
!22 = distinct !DIGlobalVariable(name: "bpf_map_lookup_elem", scope: !2, file: !23, line: 56, type: !24, isLocal: true, isDefinition: true)
!23 = !DIFile(filename: "/usr/include/bpf/bpf_helper_defs.h", directory: "")
!24 = !DIDerivedType(tag: DW_TAG_const_type, baseType: !25)
!25 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !26, size: 64)
!26 = !DISubroutineType(types: !27)
!27 = !{!15, !15, !28}
!28 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !29, size: 64)
!29 = !DIDerivedType(tag: DW_TAG_const_type, baseType: null)
!30 = !DIGlobalVariableExpression(var: !31, expr: !DIExpression())
!31 = distinct !DIGlobalVariable(name: "_license", scope: !2, file: !3, line: 52, type: !32, isLocal: false, isDefinition: true)
!32 = !DICompositeType(tag: DW_TAG_array_type, baseType: !33, size: 32, elements: !34)
!33 = !DIBasicType(name: "char", size: 8, encoding: DW_ATE_signed_char)
!34 = !{!35}
!35 = !DISubrange(count: 4)
!36 = distinct !DICompositeType(tag: DW_TAG_structure_type, file: !3, line: 7, size: 256, elements: !37)
!37 = !{!38, !44, !49, !52}
!38 = !DIDerivedType(tag: DW_TAG_member, name: "type", scope: !36, file: !3, line: 8, baseType: !39, size: 64)
!39 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !40, size: 64)
!40 = !DICompositeType(tag: DW_TAG_array_type, baseType: !41, size: 192, elements: !42)
!41 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
!42 = !{!43}
!43 = !DISubrange(count: 6)
!44 = !DIDerivedType(tag: DW_TAG_member, name: "max_entries", scope: !36, file: !3, line: 9, baseType: !45, size: 64, offset: 64)
!45 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !46, size: 64)
!46 = !DICompositeType(tag: DW_TAG_array_type, baseType: !41, size: 32, elements: !47)
!47 = !{!48}
!48 = !DISubrange(count: 1)
!49 = !DIDerivedType(tag: DW_TAG_member, name: "key", scope: !36, file: !3, line: 10, baseType: !50, size: 64, offset: 128)
!50 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !51, size: 64)
!51 = !DIDerivedType(tag: DW_TAG_typedef, name: "__u32", file: !18, line: 27, baseType: !7)
!52 = !DIDerivedType(tag: DW_TAG_member, name: "value", scope: !36, file: !3, line: 11, baseType: !53, size: 64, offset: 192)
!53 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !54, size: 64)
!54 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "packet_stats", file: !55, line: 10, size: 128, elements: !56)
!55 = !DIFile(filename: "./bridge_xdp.h", directory: "/root/xdp_filter")
!56 = !{!57, !60}
!57 = !DIDerivedType(tag: DW_TAG_member, name: "packets", scope: !54, file: !55, line: 11, baseType: !58, size: 64)
!58 = !DIDerivedType(tag: DW_TAG_typedef, name: "__u64", file: !18, line: 31, baseType: !59)
!59 = !DIBasicType(name: "unsigned long long", size: 64, encoding: DW_ATE_unsigned)
!60 = !DIDerivedType(tag: DW_TAG_member, name: "bytes", scope: !54, file: !55, line: 12, baseType: !58, size: 64, offset: 64)
!61 = !{i32 7, !"Dwarf Version", i32 4}
!62 = !{i32 2, !"Debug Info Version", i32 3}
!63 = !{i32 1, !"wchar_size", i32 4}
!64 = !{i32 7, !"frame-pointer", i32 2}
!65 = !{i32 7, !"debug-info-assignment-tracking", i1 true}
!66 = !{!"clang version 18.1.8 ( 18.1.8-3.0.1.el9)"}
!67 = distinct !DISubprogram(name: "bridge_filter", scope: !3, file: !3, line: 15, type: !68, scopeLine: 16, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !79)
!68 = !DISubroutineType(types: !69)
!69 = !{!41, !70}
!70 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !71, size: 64)
!71 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "xdp_md", file: !6, line: 6324, size: 192, elements: !72)
!72 = !{!73, !74, !75, !76, !77, !78}
!73 = !DIDerivedType(tag: DW_TAG_member, name: "data", scope: !71, file: !6, line: 6325, baseType: !51, size: 32)
!74 = !DIDerivedType(tag: DW_TAG_member, name: "data_end", scope: !71, file: !6, line: 6326, baseType: !51, size: 32, offset: 32)
!75 = !DIDerivedType(tag: DW_TAG_member, name: "data_meta", scope: !71, file: !6, line: 6327, baseType: !51, size: 32, offset: 64)
!76 = !DIDerivedType(tag: DW_TAG_member, name: "ingress_ifindex", scope: !71, file: !6, line: 6329, baseType: !51, size: 32, offset: 96)
!77 = !DIDerivedType(tag: DW_TAG_member, name: "rx_queue_index", scope: !71, file: !6, line: 6330, baseType: !51, size: 32, offset: 128)
!78 = !DIDerivedType(tag: DW_TAG_member, name: "egress_ifindex", scope: !71, file: !6, line: 6332, baseType: !51, size: 32, offset: 160)
!79 = !{!80, !81, !82, !83, !95, !96, !97}
!80 = !DILocalVariable(name: "ctx", arg: 1, scope: !67, file: !3, line: 15, type: !70)
!81 = !DILocalVariable(name: "data_end", scope: !67, file: !3, line: 17, type: !15)
!82 = !DILocalVariable(name: "data", scope: !67, file: !3, line: 18, type: !15)
!83 = !DILocalVariable(name: "eth", scope: !67, file: !3, line: 19, type: !84)
!84 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !85, size: 64)
!85 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "ethhdr", file: !86, line: 166, size: 112, elements: !87)
!86 = !DIFile(filename: "/usr/include/linux/if_ether.h", directory: "")
!87 = !{!88, !91, !92}
!88 = !DIDerivedType(tag: DW_TAG_member, name: "h_dest", scope: !85, file: !86, line: 167, baseType: !89, size: 48)
!89 = !DICompositeType(tag: DW_TAG_array_type, baseType: !90, size: 48, elements: !42)
!90 = !DIBasicType(name: "unsigned char", size: 8, encoding: DW_ATE_unsigned_char)
!91 = !DIDerivedType(tag: DW_TAG_member, name: "h_source", scope: !85, file: !86, line: 168, baseType: !89, size: 48, offset: 48)
!92 = !DIDerivedType(tag: DW_TAG_member, name: "h_proto", scope: !85, file: !86, line: 169, baseType: !93, size: 16, offset: 96)
!93 = !DIDerivedType(tag: DW_TAG_typedef, name: "__be16", file: !94, line: 29, baseType: !17)
!94 = !DIFile(filename: "/usr/include/linux/types.h", directory: "")
!95 = !DILocalVariable(name: "key", scope: !67, file: !3, line: 26, type: !51)
!96 = !DILocalVariable(name: "stats", scope: !67, file: !3, line: 27, type: !53)
!97 = !DILocalVariable(name: "iph", scope: !98, file: !3, line: 39, type: !100)
!98 = distinct !DILexicalBlock(scope: !99, file: !3, line: 38, column: 46)
!99 = distinct !DILexicalBlock(scope: !67, file: !3, line: 38, column: 9)
!100 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !101, size: 64)
!101 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "iphdr", file: !102, line: 87, size: 160, elements: !103)
!102 = !DIFile(filename: "/usr/include/linux/ip.h", directory: "")
!103 = !{!104, !106, !107, !108, !109, !110, !111, !112, !113, !115}
!104 = !DIDerivedType(tag: DW_TAG_member, name: "ihl", scope: !101, file: !102, line: 89, baseType: !105, size: 4, flags: DIFlagBitField, extraData: i64 0)
!105 = !DIDerivedType(tag: DW_TAG_typedef, name: "__u8", file: !18, line: 21, baseType: !90)
!106 = !DIDerivedType(tag: DW_TAG_member, name: "version", scope: !101, file: !102, line: 90, baseType: !105, size: 4, offset: 4, flags: DIFlagBitField, extraData: i64 0)
!107 = !DIDerivedType(tag: DW_TAG_member, name: "tos", scope: !101, file: !102, line: 97, baseType: !105, size: 8, offset: 8)
!108 = !DIDerivedType(tag: DW_TAG_member, name: "tot_len", scope: !101, file: !102, line: 98, baseType: !93, size: 16, offset: 16)
!109 = !DIDerivedType(tag: DW_TAG_member, name: "id", scope: !101, file: !102, line: 99, baseType: !93, size: 16, offset: 32)
!110 = !DIDerivedType(tag: DW_TAG_member, name: "frag_off", scope: !101, file: !102, line: 100, baseType: !93, size: 16, offset: 48)
!111 = !DIDerivedType(tag: DW_TAG_member, name: "ttl", scope: !101, file: !102, line: 101, baseType: !105, size: 8, offset: 64)
!112 = !DIDerivedType(tag: DW_TAG_member, name: "protocol", scope: !101, file: !102, line: 102, baseType: !105, size: 8, offset: 72)
!113 = !DIDerivedType(tag: DW_TAG_member, name: "check", scope: !101, file: !102, line: 103, baseType: !114, size: 16, offset: 80)
!114 = !DIDerivedType(tag: DW_TAG_typedef, name: "__sum16", file: !94, line: 35, baseType: !17)
!115 = !DIDerivedType(tag: DW_TAG_member, scope: !101, file: !102, line: 104, baseType: !116, size: 64, offset: 96)
!116 = distinct !DICompositeType(tag: DW_TAG_union_type, scope: !101, file: !102, line: 104, size: 64, elements: !117)
!117 = !{!118, !124}
!118 = !DIDerivedType(tag: DW_TAG_member, scope: !116, file: !102, line: 104, baseType: !119, size: 64)
!119 = distinct !DICompositeType(tag: DW_TAG_structure_type, scope: !116, file: !102, line: 104, size: 64, elements: !120)
!120 = !{!121, !123}
!121 = !DIDerivedType(tag: DW_TAG_member, name: "saddr", scope: !119, file: !102, line: 104, baseType: !122, size: 32)
!122 = !DIDerivedType(tag: DW_TAG_typedef, name: "__be32", file: !94, line: 31, baseType: !51)
!123 = !DIDerivedType(tag: DW_TAG_member, name: "daddr", scope: !119, file: !102, line: 104, baseType: !122, size: 32, offset: 32)
!124 = !DIDerivedType(tag: DW_TAG_member, name: "addrs", scope: !116, file: !102, line: 104, baseType: !125, size: 64)
!125 = distinct !DICompositeType(tag: DW_TAG_structure_type, scope: !116, file: !102, line: 104, size: 64, elements: !126)
!126 = !{!127, !128}
!127 = !DIDerivedType(tag: DW_TAG_member, name: "saddr", scope: !125, file: !102, line: 104, baseType: !122, size: 32)
!128 = !DIDerivedType(tag: DW_TAG_member, name: "daddr", scope: !125, file: !102, line: 104, baseType: !122, size: 32, offset: 32)
!129 = distinct !DIAssignID()
!130 = !DILocation(line: 0, scope: !67)
!131 = !DILocation(line: 17, column: 41, scope: !67)
!132 = !{!133, !134, i64 4}
!133 = !{!"xdp_md", !134, i64 0, !134, i64 4, !134, i64 8, !134, i64 12, !134, i64 16, !134, i64 20}
!134 = !{!"int", !135, i64 0}
!135 = !{!"omnipotent char", !136, i64 0}
!136 = !{!"Simple C/C++ TBAA"}
!137 = !DILocation(line: 17, column: 30, scope: !67)
!138 = !DILocation(line: 17, column: 22, scope: !67)
!139 = !DILocation(line: 18, column: 37, scope: !67)
!140 = !{!133, !134, i64 0}
!141 = !DILocation(line: 18, column: 26, scope: !67)
!142 = !DILocation(line: 18, column: 18, scope: !67)
!143 = !DILocation(line: 22, column: 21, scope: !144)
!144 = distinct !DILexicalBlock(scope: !67, file: !3, line: 22, column: 9)
!145 = !DILocation(line: 22, column: 26, scope: !144)
!146 = !DILocation(line: 22, column: 9, scope: !67)
!147 = !DILocation(line: 26, column: 5, scope: !67)
!148 = !DILocation(line: 26, column: 11, scope: !67)
!149 = !{!134, !134, i64 0}
!150 = distinct !DIAssignID()
!151 = !DILocation(line: 27, column: 34, scope: !67)
!152 = !DILocation(line: 28, column: 9, scope: !153)
!153 = distinct !DILexicalBlock(scope: !67, file: !3, line: 28, column: 9)
!154 = !DILocation(line: 28, column: 9, scope: !67)
!155 = !DILocation(line: 29, column: 23, scope: !156)
!156 = distinct !DILexicalBlock(scope: !153, file: !3, line: 28, column: 16)
!157 = !{!158, !159, i64 0}
!158 = !{!"packet_stats", !159, i64 0, !159, i64 8}
!159 = !{!"long long", !135, i64 0}
!160 = !DILocation(line: 30, column: 35, scope: !156)
!161 = !DILocation(line: 30, column: 16, scope: !156)
!162 = !DILocation(line: 30, column: 22, scope: !156)
!163 = !{!158, !159, i64 8}
!164 = !DILocation(line: 31, column: 5, scope: !156)
!165 = !DILocation(line: 50, column: 1, scope: !67)
BIN
View File
Binary file not shown.
+33
View File
@@ -0,0 +1,33 @@
#include <stdio.h>
#include <bpf/libbpf.h>
#include "bridge_xdp.h"
int main(int argc, char **argv)
{
if (argc != 2) {
printf("Uso: %s <interface>\n", argv[0]);
return 1;
}
// Carregar programa XDP
struct bpf_object *obj = bpf_object__open("bridge_xdp.o");
if (!obj) {
printf("Erro ao abrir objeto BPF\n");
return 1;
}
if (bpf_object__load(obj)) {
printf("Erro ao carregar objeto BPF\n");
return 1;
}
// Anexar à interface
int prog_fd = bpf_program__fd(bpf_object__find_program_by_name(obj, "bridge_filter"));
if (bpf_xdp_attach(if_nametoindex(argv[1]), prog_fd, XDP_FLAGS_SKB_MODE, NULL) != 0) {
printf("Erro ao anexar XDP\n");
return 1;
}
printf("XDP anexado com sucesso à interface %s\n", argv[1]);
return 0;
}
+102
View File
@@ -0,0 +1,102 @@
#!/bin/bash
# THIS FILE IS ADDED FOR COMPATIBILITY PURPOSES
#
# It is highly advisable to create own systemd services or udev rules
# to run scripts during boot instead of using this file.
#
# In contrast to previous versions due to parallel execution during boot
# this script will NOT be run after all other services.
#
# Please note that you must run 'chmod +x /etc/rc.d/rc.local' to ensure
# that this script will be executed during boot.
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X
iptables -P INPUT DROP
iptables -P FORWARD ACCEPT
iptables -P OUTPUT ACCEPT
iptables -A INPUT -i enp0s6 -p udp --dport 32101 -j ACCEPT
iptables -A INPUT -i enp0s6 -p udp --dport 32110 -j ACCEPT
iptables -A INPUT -i enp0s6 -p udp --dport 32100 -j ACCEPT
iptables -A INPUT -i enp0s6 -p udp --dport 32400 -j ACCEPT
iptables -A INPUT -i enp0s6 -p tcp --dport 8117 -j ACCEPT
iptables -A INPUT -i enp0s6 -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -i enp0s6 -p tcp --dport 443 -j ACCEPT
iptables -A INPUT -i enp0s6 -j ACCEPT -m state --state ESTABLISHED,RELATED
iptables -A INPUT -i wg0 -j ACCEPT
# Permitir SIP (UDP/TCP 5060/5061), IAX2 (UDP 4569), RTP (UDP 18000:19000) para FreePBX
iptables -A INPUT -i enp0s6 -p udp --dport 5060 -j ACCEPT
iptables -A INPUT -i enp0s6 -p tcp --dport 5060 -j ACCEPT
iptables -A INPUT -i enp0s6 -p udp --dport 5061 -j ACCEPT
iptables -A INPUT -i enp0s6 -p tcp --dport 5061 -j ACCEPT
iptables -A INPUT -i enp0s6 -p udp --dport 5160 -j ACCEPT
iptables -A INPUT -i enp0s6 -p tcp --dport 5161 -j ACCEPT
iptables -A INPUT -i enp0s6 -p udp --dport 4569 -j ACCEPT
iptables -A INPUT -i enp0s6 -p udp --dport 18000:19000 -j ACCEPT
# Permitir tráfego de retorno (deve estar antes do DROP final)
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -t nat -A POSTROUTING -s 10.15.0.0/16 -j MASQUERADE
ip6tables -t nat -A POSTROUTING -s 2001:db8:15::/48 -j MASQUERADE
iptables -t nat -A POSTROUTING -s 172.17.0.0/16 -j MASQUERADE
# Habilitar redirecionamento de pacotes no kernel
echo 1 > /proc/sys/net/ipv4/ip_forward
# 1. DNAT APENAS para tráfego entrando na interface enp0s6 (WAN) com destino ao IP público
iptables -t nat -A PREROUTING -i enp0s6 -p tcp --dport 80 -j DNAT --to-destination 10.15.0.45:80
iptables -t nat -A PREROUTING -i enp0s6 -p tcp --dport 443 -j DNAT --to-destination 10.15.0.45:443
# 2. SNAT (Hairpinning) para clientes locais acessarem via IP público
iptables -t nat -A POSTROUTING -o enp0s6 -p tcp -d 10.15.0.45 --dport 80 -j MASQUERADE
iptables -t nat -A POSTROUTING -o enp0s6 -p tcp -d 10.15.0.45 --dport 443 -j MASQUERADE
# Permitir o tráfego na cadeia FORWARD
iptables -A FORWARD -p tcp -d 10.15.0.45 --dport 80 -j ACCEPT
iptables -A FORWARD -p tcp -d 10.15.0.45 --dport 443 -j ACCEPT
sh /root/test.sh
echo deadline > /sys/block/sda/queue/scheduler
touch /var/lock/subsys/local
#echo "bfq" > /sys/block/sda/queue/scheduler
#echo "1500" > /sys/block/sda/queue/iosched/read_expire
echo 64 > /sys/block/sda/queue/max_sectors_kb # Melhor para SSD virtual
echo deadline > /sys/block/sda/queue/scheduler # Alternativa ao BFQ
echo 0 > /proc/sys/vm/zone_reclaim_mode
tc qdisc add dev enp0s6 root cake bandwidth 1000mbit besteffort
tc qdisc add dev wg0 root cake bandwidth 500mbit besteffort dual-dsthost
echo f > /sys/class/net/enp0s6/queues/tx-0/xps_cpus
echo f > /sys/class/net/enp0s6/queues/rx-0/rps_cpus
# XPS para núcleos 4-7 (máscara 0xf0)
# Aumentar buffer de entrada/saída fixo
echo 4096 > /sys/class/net/enp0s6/queues/rx-0/rps_flow_cnt
echo 4096 > /proc/sys/net/core/rps_sock_flow_entries
sh /root/qos.sh
#cd /root/xdp_new && \
cd /home/rodrigo/xdp && \
ip link set enp0s6 mtu 1500
ip link set dev enp0s6 xdp obj xdp_ddos_filter.o sec xdp
sleep 20
ip link set dev br-02f7820020b4 xdp obj bridge_xdp.o sec xdp
ip link delete br-8b341d0f5c20
iptables -t nat -A POSTROUTING -o wg3 -j MASQUERADE
+451
View File
@@ -0,0 +1,451 @@
#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 <linux/icmp.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
// Otimização: constantes ajustadas para seu ambiente específico
#define MAX_HIGH_PRIORITY_PACKETS_PER_SECOND 3000 // Aumentado para serviços críticos (HTTP/HTTPS)
#define MAX_LOW_PRIORITY_PACKETS_PER_SECOND 500
#define MAX_PACKETS_PER_IP 300000
#define MAX_ICMP_PACKETS_PER_IP 30 // Reduzido conforme política ICMP restritiva
#define MAX_SYN_PACKETS_PER_IP 80 // Reduzido para proteção preventiva
#define TIMEOUT_SEC 1
#define PACKET_COST 1
#define HIGH_PRIO_TOKEN_RATE 3000 // Aumentado para combinar com QoS HTB
#define LOW_PRIO_TOKEN_RATE 500
#define MAX_TOKENS 3000
#define DOCKER_NETWORK_PREFIX 0x0A0F0000 // 10.15.0.0
#define DOCKER_NETWORK_MASK 0xFFFF0000 // /16
#define WG_NETWORK_PREFIX 0x0A630100 // 10.99.1.0
#define WG_NETWORK_MASK 0xFFFFFF00 // /24
#define NGINX_SERVER_IP 0x0A0F002D // 10.15.0.45
// Estrutura de dados otimizada com tipos consistentes
struct ip_stats {
__u32 pkt_count;
__u32 last_seen;
__u32 icmp_count;
__u32 syn_count;
__u32 high_prio_tokens;
__u32 low_prio_tokens;
__u16 flags; // Expandido para mais flags
__u16 padding; // Mantém alinhamento 4-bytes
} __attribute__((packed));
// Estrutura para configuração de redes internas
struct network_config {
__u32 prefix;
__u32 mask;
};
// Estrutura para configuração de portas
struct port_config {
__u8 priority; // 0=baixa, 1=média, 2=alta
__u8 is_filtered; // 0=não, 1=sim
};
// Adicionar flags para comportamento de tráfego
#define FLAG_POTENTIAL_SCANNER 0x01
#define FLAG_SYN_FLOOD 0x02
#define FLAG_ICMP_FLOOD 0x04
#define FLAG_MARKED_SUSPICIOUS 0x08
struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, 100000);
__type(key, __u32);
__type(value, struct ip_stats);
} ddos_map SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1000);
__type(key, __u32);
__type(value, __u32);
} whitelist_map SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, 100000);
__type(key, __u32);
__type(value, __u32);
} blacklist_map SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 100);
__type(key, __u32);
__type(value, struct network_config);
} internal_networks SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 65536);
__type(key, __u16);
__type(value, struct port_config);
} port_config_map SEC(".maps");
// Mapa para tempos de expiração
struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, 100000);
__type(key, __u32);
__type(value, __u32);
} expiration_map SEC(".maps");
// Mapa para estatísticas de tráfego para integração com userspace
struct traffic_stats {
__u64 total_packets;
__u64 dropped_packets;
__u64 passed_packets;
__u64 blacklisted_count;
__u64 syn_flood_count;
__u64 icmp_flood_count;
};
struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__uint(max_entries, 1);
__type(key, __u32);
__type(value, struct traffic_stats);
} stats_map SEC(".maps");
// Fast-path para serviços críticos - pré-verifica IPs confiáveis
struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, 1000);
__type(key, __u32);
__type(value, __u8); // 1 = confiável
} trusted_hosts_map SEC(".maps");
// Mapa para interface de redirecionamento
struct {
__uint(type, BPF_MAP_TYPE_XSKMAP);
__uint(max_entries, 1);
__type(key, int);
__type(value, int);
} xsks_map SEC(".maps");
// Função otimizada para timestamp
static __always_inline __u32 get_time_sec(void) {
return bpf_ktime_get_ns() / 1000000000;
}
static __always_inline int is_whitelisted(__u32 ip) {
return bpf_map_lookup_elem(&whitelist_map, &ip) != NULL;
}
static __always_inline int is_blacklisted(__u32 ip) {
__u32 now = get_time_sec();
__u32 *block_time = bpf_map_lookup_elem(&blacklist_map, &ip);
if (block_time) {
// Se passou mais de 1 hora, remove da blacklist
if (now - *block_time > 3600) {
bpf_map_delete_elem(&blacklist_map, &ip);
return 0;
}
return 1;
}
return 0;
}
static __always_inline int is_internal_network(__u32 ip) {
struct network_config *net;
__u32 key = 0;
// Iteração limitada pelo verificador
#pragma unroll
for (int i = 0; i < 8; i++) {
key = i;
net = bpf_map_lookup_elem(&internal_networks, &key);
if (!net)
break;
if ((ip & net->mask) == net->prefix)
return 1;
}
return 0;
}
static __always_inline int get_port_priority(__u16 port) {
struct port_config *cfg = bpf_map_lookup_elem(&port_config_map, &port);
if (cfg)
return cfg->priority;
return 0;
}
static __always_inline int is_filtered_port(__u16 port) {
struct port_config *cfg = bpf_map_lookup_elem(&port_config_map, &port);
if (cfg)
return cfg->is_filtered;
return (port <= 1024);
}
// Função para atualizar estatísticas de tráfego
static __always_inline void update_stats(__u32 action, __u8 flags) {
__u32 key = 0;
struct traffic_stats *stats = bpf_map_lookup_elem(&stats_map, &key);
if (stats) {
stats->total_packets++;
if (action == XDP_DROP) {
stats->dropped_packets++;
if (flags & FLAG_SYN_FLOOD)
stats->syn_flood_count++;
if (flags & FLAG_ICMP_FLOOD)
stats->icmp_flood_count++;
} else if (action == XDP_PASS) {
stats->passed_packets++;
}
if (flags & FLAG_MARKED_SUSPICIOUS)
stats->blacklisted_count++;
}
}
// Macro para conversão de porta e IP
#define ntohs(x) __builtin_bswap16(x)
#define ntohl(x) __builtin_bswap32(x)
// Versão otimizada do programa principal
SEC("xdp")
int xdp_ddos_filter(struct xdp_md *ctx) {
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
__u8 flags = 0;
// Verificação rápida de tamanho mínimo do pacote
if (data + sizeof(struct ethhdr) + sizeof(struct iphdr) > data_end)
return XDP_PASS;
struct ethhdr *eth = data;
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;
__u32 dst_ip = ip->daddr;
// Fast path: redes internas, whitelist e trusted hosts
if (is_internal_network(src_ip) || is_whitelisted(src_ip)) return XDP_PASS;
__u8 *trusted = bpf_map_lookup_elem(&trusted_hosts_map, &src_ip);
if (trusted && *trusted == 1) return XDP_PASS;
// Fast path: blacklist
if (is_blacklisted(src_ip)) {
update_stats(XDP_DROP, FLAG_MARKED_SUSPICIOUS);
return XDP_DROP;
}
// Fast path: WireGuard UDP/32100
if (ip->protocol == IPPROTO_UDP) {
struct udphdr *udp = (void *)(ip + 1);
if ((void *)(udp + 1) <= data_end) {
__u16 dest_port = ntohs(udp->dest);
if (dest_port == 32100 || dest_port == 32400 || dest_port == 32101 || dest_port == 32110) {
update_stats(XDP_PASS, 0);
return XDP_PASS;
}
}
}
// Variáveis locais para portas e flags
__u16 dest_port = 0;
int is_syn = 0, is_icmp = 0;
// Processamento por protocolo
if (ip->protocol == IPPROTO_TCP) {
struct tcphdr *tcp = (void *)(ip + 1);
if ((void *)(tcp + 1) > data_end) return XDP_PASS;
dest_port = ntohs(tcp->dest);
is_syn = tcp->syn && !tcp->ack;
// Detecção de port scan
if ((tcp->syn && tcp->rst) || (tcp->fin && tcp->rst) || (tcp->fin && tcp->syn))
flags |= FLAG_POTENTIAL_SCANNER;
} else if (ip->protocol == IPPROTO_UDP) {
struct udphdr *udp = (void *)(ip + 1);
if ((void *)(udp + 1) > data_end) return XDP_PASS;
dest_port = ntohs(udp->dest);
} else if (ip->protocol == IPPROTO_ICMP) {
is_icmp = 1;
struct icmphdr *icmp = (void *)(ip + 1);
if ((void *)(icmp + 1) > data_end) return XDP_PASS;
// Permitir apenas echo-reply (0) e limitar echo-request (8)
if (icmp->type != 0 && icmp->type != 8) {
update_stats(XDP_DROP, 0);
return XDP_DROP;
}
} else {
return XDP_PASS;
}
// Se não for porta filtrada e não for ICMP, libera rápido
if (!is_filtered_port(dest_port) && !is_icmp) {
struct ip_stats *entry = bpf_map_lookup_elem(&ddos_map, &src_ip);
if (entry && entry->pkt_count > 5 && entry->flags == 0) {
__u8 mark_trusted = 1;
bpf_map_update_elem(&trusted_hosts_map, &src_ip, &mark_trusted, BPF_ANY);
}
return XDP_PASS;
}
// Rate limiting e contadores
int priority = get_port_priority(dest_port);
struct ip_stats *entry = bpf_map_lookup_elem(&ddos_map, &src_ip);
__u32 now = get_time_sec();
// Use variáveis locais para tokens e contadores
__u32 pkt_count = 1, icmp_count = is_icmp ? 1 : 0, syn_count = is_syn ? 1 : 0;
__u32 high_tokens = MAX_TOKENS - PACKET_COST, low_tokens = MAX_TOKENS - PACKET_COST;
__u16 entry_flags = flags;
if (entry) {
__u32 elapsed = now > entry->last_seen ? now - entry->last_seen : 0;
pkt_count = entry->pkt_count + 1;
icmp_count = is_icmp ? entry->icmp_count + 1 : entry->icmp_count;
syn_count = is_syn ? entry->syn_count + 1 : entry->syn_count;
high_tokens = entry->high_prio_tokens;
low_tokens = entry->low_prio_tokens;
entry_flags = entry->flags | flags;
if (elapsed > 0) {
__u32 add_high = HIGH_PRIO_TOKEN_RATE * elapsed;
__u32 add_low = LOW_PRIO_TOKEN_RATE * elapsed;
high_tokens = (high_tokens + add_high > MAX_TOKENS) ? MAX_TOKENS : high_tokens + add_high;
low_tokens = (low_tokens + add_low > MAX_TOKENS) ? MAX_TOKENS : low_tokens + add_low;
if (elapsed >= TIMEOUT_SEC) {
icmp_count = is_icmp ? 1 : 0;
syn_count = is_syn ? 1 : 0;
entry_flags &= ~(FLAG_SYN_FLOOD | FLAG_ICMP_FLOOD);
}
}
// Flood ICMP
if (is_icmp && icmp_count > MAX_ICMP_PACKETS_PER_IP) {
entry_flags |= FLAG_ICMP_FLOOD;
__u32 one = 1;
bpf_map_update_elem(&blacklist_map, &src_ip, &one, BPF_ANY);
update_stats(XDP_DROP, FLAG_ICMP_FLOOD);
goto update_entry_and_drop;
}
// Flood SYN
if (is_syn && syn_count > MAX_SYN_PACKETS_PER_IP) {
entry_flags |= FLAG_SYN_FLOOD;
__u32 one = 1;
bpf_map_update_elem(&blacklist_map, &src_ip, &one, BPF_ANY);
update_stats(XDP_DROP, FLAG_SYN_FLOOD);
goto update_entry_and_drop;
}
// Flood total
if (pkt_count > MAX_PACKETS_PER_IP) {
entry_flags |= FLAG_MARKED_SUSPICIOUS;
__u32 one = 1;
bpf_map_update_elem(&blacklist_map, &src_ip, &one, BPF_ANY);
update_stats(XDP_DROP, FLAG_MARKED_SUSPICIOUS);
goto update_entry_and_drop;
}
// Token bucket
if (priority == 2) {
if (high_tokens >= PACKET_COST) {
high_tokens -= PACKET_COST;
goto update_entry_and_pass;
}
} else if (priority == 1) {
if (high_tokens >= PACKET_COST/2 && low_tokens >= PACKET_COST/2) {
high_tokens -= PACKET_COST/2;
low_tokens -= PACKET_COST/2;
goto update_entry_and_pass;
}
} else {
if (low_tokens >= PACKET_COST) {
low_tokens -= PACKET_COST;
goto update_entry_and_pass;
}
}
update_stats(XDP_DROP, 0);
goto update_entry_and_drop;
update_entry_and_pass:
{
struct ip_stats new_entry = {
.pkt_count = pkt_count,
.last_seen = now,
.icmp_count = icmp_count,
.syn_count = syn_count,
.high_prio_tokens = high_tokens,
.low_prio_tokens = low_tokens,
.flags = entry_flags
};
bpf_map_update_elem(&ddos_map, &src_ip, &new_entry, BPF_ANY);
update_stats(XDP_PASS, 0);
return XDP_PASS;
}
update_entry_and_drop:
{
struct ip_stats new_entry = {
.pkt_count = pkt_count,
.last_seen = now,
.icmp_count = icmp_count,
.syn_count = syn_count,
.high_prio_tokens = high_tokens,
.low_prio_tokens = low_tokens,
.flags = entry_flags
};
bpf_map_update_elem(&ddos_map, &src_ip, &new_entry, BPF_ANY);
return XDP_DROP;
}
} else {
// Primeira vez que vemos este IP
struct ip_stats new_entry = {
.pkt_count = pkt_count,
.last_seen = now,
.icmp_count = icmp_count,
.syn_count = syn_count,
.high_prio_tokens = high_tokens,
.low_prio_tokens = low_tokens,
.flags = entry_flags
};
bpf_map_update_elem(&ddos_map, &src_ip, &new_entry, BPF_ANY);
update_stats(XDP_PASS, 0);
return XDP_PASS;
}
// Redirecionamento AF_XDP para portas 80, 443 (TCP) e 32100 (UDP)
int xsk_map_key = ctx->rx_queue_index;
// TCP: 80 ou 443
if (ip->protocol == IPPROTO_TCP) {
struct tcphdr *tcp = (void *)(ip + 1);
if ((void *)(tcp + 1) > data_end) return XDP_PASS;
__u16 dest_port = ntohs(tcp->dest);
if (dest_port == 80 || dest_port == 443) {
if (bpf_map_lookup_elem(&xsks_map, &xsk_map_key))
return XDP_REDIRECT;
}
}
// UDP: 32100 (WireGuard)
if (ip->protocol == IPPROTO_UDP) {
struct udphdr *udp = (void *)(ip + 1);
if ((void *)(udp + 1) > data_end) return XDP_PASS;
__u16 dest_port = ntohs(udp->dest);
if (dest_port == 32100 || dest_port == 32110 || dest_port == 32101|| dest_port == 32400|| dest_port == 5060) {
if (bpf_map_lookup_elem(&xsks_map, &xsk_map_key))
return XDP_REDIRECT;
}
}
return XDP_PASS;
}
char __license[] SEC("license") = "GPL";
+614
View File
@@ -0,0 +1,614 @@
#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 <linux/icmp.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
// Otimização: constantes ajustadas para seu ambiente específico
#define MAX_HIGH_PRIORITY_PACKETS_PER_SECOND 3000 // Aumentado para serviços críticos (HTTP/HTTPS)
#define MAX_LOW_PRIORITY_PACKETS_PER_SECOND 500
#define MAX_PACKETS_PER_IP 300000
#define MAX_ICMP_PACKETS_PER_IP 30 // Reduzido conforme política ICMP restritiva
#define MAX_SYN_PACKETS_PER_IP 15 // Reduzido drasticamente para proteção contra SYN flood
#define MAX_SYN_PACKETS_PER_SECOND 8 // Limite de SYN por segundo por IP
#define TIMEOUT_SEC 1
#define SUSPICIOUS_TTL_THRESHOLD 240 // TTLs acima de 240 são suspeitos
#define SUSPICIOUS_WINDOW_SIZE 53270 // Window size específico de ataques conhecidos
#define PACKET_COST 1
#define HIGH_PRIO_TOKEN_RATE 3000 // Aumentado para combinar com QoS HTB
#define LOW_PRIO_TOKEN_RATE 500
#define MAX_TOKENS 3000
#define DOCKER_NETWORK_PREFIX 0x0A0F0000 // 10.15.0.0
#define DOCKER_NETWORK_MASK 0xFFFF0000 // /16
#define WG_NETWORK_PREFIX 0x0A630100 // 10.99.1.0
#define WG_NETWORK_MASK 0xFFFFFF00 // /24
#define NGINX_SERVER_IP 0x0A0F002D // 10.15.0.45
// Estrutura de dados otimizada com tipos consistentes
struct ip_stats {
__u32 pkt_count;
__u32 last_seen;
__u32 icmp_count;
__u32 syn_count;
__u32 high_prio_tokens;
__u32 low_prio_tokens;
__u32 syn_last_sec; // Timestamp do último segundo para contagem de SYN
__u32 syn_count_per_sec; // Contador de SYN no último segundo
__u16 flags; // Expandido para mais flags
__u16 padding; // Mantém alinhamento 4-bytes
} __attribute__((packed));
// Estrutura para configuração de redes internas
struct network_config {
__u32 prefix;
__u32 mask;
};
// Estrutura para configuração de portas
struct port_config {
__u8 priority; // 0=baixa, 1=média, 2=alta
__u8 is_filtered; // 0=não, 1=sim
};
// Adicionar flags para comportamento de tráfego
#define FLAG_POTENTIAL_SCANNER 0x01
#define FLAG_SYN_FLOOD 0x02
#define FLAG_ICMP_FLOOD 0x04
#define FLAG_MARKED_SUSPICIOUS 0x08
#define FLAG_SUSPICIOUS_TTL 0x10
#define FLAG_SUSPICIOUS_WINDOW 0x20
#define FLAG_SSH_ATTACKER 0x40
#define FLAG_HTTP_ATTACKER 0x80
struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, 100000);
__type(key, __u32);
__type(value, struct ip_stats);
} ddos_map SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1000);
__type(key, __u32);
__type(value, __u32);
} whitelist_map SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, 100000);
__type(key, __u32);
__type(value, __u32);
} blacklist_map SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 100);
__type(key, __u32);
__type(value, struct network_config);
} internal_networks SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 65536);
__type(key, __u16);
__type(value, struct port_config);
} port_config_map SEC(".maps");
// Mapa para tempos de expiração
struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, 100000);
__type(key, __u32);
__type(value, __u32);
} expiration_map SEC(".maps");
// Mapa para estatísticas de tráfego para integração com userspace
struct traffic_stats {
__u64 total_packets;
__u64 dropped_packets;
__u64 passed_packets;
__u64 blacklisted_count;
__u64 syn_flood_count;
__u64 icmp_flood_count;
};
struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__uint(max_entries, 1);
__type(key, __u32);
__type(value, struct traffic_stats);
} stats_map SEC(".maps");
// Fast-path para serviços críticos - pré-verifica IPs confiáveis
struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, 1000);
__type(key, __u32);
__type(value, __u8); // 1 = confiável
} trusted_hosts_map SEC(".maps");
// Mapa para interface de redirecionamento
struct {
__uint(type, BPF_MAP_TYPE_XSKMAP);
__uint(max_entries, 1);
__type(key, int);
__type(value, int);
} xsks_map SEC(".maps");
// Mapa para rastrear taxa global de SYN por porta
struct syn_rate_stats {
__u32 last_sec;
__u32 count;
};
struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, 1000);
__type(key, __u16); // porta de destino
__type(value, struct syn_rate_stats);
} syn_rate_map SEC(".maps");
// Função otimizada para timestamp
static __always_inline __u32 get_time_sec(void) {
return bpf_ktime_get_ns() / 1000000000;
}
static __always_inline int is_whitelisted(__u32 ip) {
return bpf_map_lookup_elem(&whitelist_map, &ip) != NULL;
}
static __always_inline int is_blacklisted(__u32 ip) {
__u32 now = get_time_sec();
__u32 *block_time = bpf_map_lookup_elem(&blacklist_map, &ip);
if (block_time) {
// Se passou mais de 1 hora, remove da blacklist
if (now - *block_time > 3600) {
bpf_map_delete_elem(&blacklist_map, &ip);
return 0;
}
return 1;
}
return 0;
}
static __always_inline int is_internal_network(__u32 ip) {
struct network_config *net;
__u32 key = 0;
// Iteração limitada pelo verificador
#pragma unroll
for (int i = 0; i < 8; i++) {
key = i;
net = bpf_map_lookup_elem(&internal_networks, &key);
if (!net)
break;
if ((ip & net->mask) == net->prefix)
return 1;
}
return 0;
}
static __always_inline int get_port_priority(__u16 port) {
struct port_config *cfg = bpf_map_lookup_elem(&port_config_map, &port);
if (cfg)
return cfg->priority;
return 0;
}
static __always_inline int is_filtered_port(__u16 port) {
struct port_config *cfg = bpf_map_lookup_elem(&port_config_map, &port);
if (cfg)
return cfg->is_filtered;
return (port <= 1024);
}
// Função para atualizar estatísticas de tráfego
static __always_inline void update_stats(__u32 action, __u8 flags) {
__u32 key = 0;
struct traffic_stats *stats = bpf_map_lookup_elem(&stats_map, &key);
if (stats) {
stats->total_packets++;
if (action == XDP_DROP) {
stats->dropped_packets++;
if (flags & FLAG_SYN_FLOOD)
stats->syn_flood_count++;
if (flags & FLAG_ICMP_FLOOD)
stats->icmp_flood_count++;
} else if (action == XDP_PASS) {
stats->passed_packets++;
}
if (flags & FLAG_MARKED_SUSPICIOUS)
stats->blacklisted_count++;
}
}
// Macro para conversão de porta e IP
#define ntohs(x) __builtin_bswap16(x)
#define ntohl(x) __builtin_bswap32(x)
// Versão otimizada do programa principal
SEC("xdp")
int xdp_ddos_filter(struct xdp_md *ctx) {
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
__u8 flags = 0;
// Verificação rápida de tamanho mínimo do pacote
if (data + sizeof(struct ethhdr) + sizeof(struct iphdr) > data_end)
return XDP_PASS;
struct ethhdr *eth = data;
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;
__u32 dst_ip = ip->daddr;
// Fast path: redes internas, whitelist e trusted hosts
if (is_internal_network(src_ip) || is_whitelisted(src_ip)) return XDP_PASS;
__u8 *trusted = bpf_map_lookup_elem(&trusted_hosts_map, &src_ip);
if (trusted && *trusted == 1) return XDP_PASS;
// Fast path: blacklist
if (is_blacklisted(src_ip)) {
update_stats(XDP_DROP, FLAG_MARKED_SUSPICIOUS);
return XDP_DROP;
}
// Fast path: WireGuard UDP/32100
if (ip->protocol == IPPROTO_UDP) {
struct udphdr *udp = (void *)(ip + 1);
if ((void *)(udp + 1) <= data_end) {
__u16 dest_port = ntohs(udp->dest);
// Permitir UDP 32100, 32400, 32101, 32110, 5060, 5160, 18000-19000
if (dest_port == 32100 || dest_port == 32400 || dest_port == 32101 || dest_port == 32110 ||
dest_port == 5060 || dest_port == 5160 ||
(dest_port >= 18000 && dest_port <= 19000)) {
update_stats(XDP_PASS, 0);
return XDP_PASS;
}
}
}
// Variáveis locais para portas e flags
__u16 dest_port = 0;
__u16 tcp_window = 0;
int is_syn = 0, is_icmp = 0;
// Processamento por protocolo
if (ip->protocol == IPPROTO_TCP) {
struct tcphdr *tcp = (void *)(ip + 1);
if ((void *)(tcp + 1) > data_end) return XDP_PASS;
dest_port = ntohs(tcp->dest);
tcp_window = ntohs(tcp->window);
is_syn = tcp->syn && !tcp->ack;
// Detecção de port scan
if ((tcp->syn && tcp->rst) || (tcp->fin && tcp->rst) || (tcp->fin && tcp->syn))
flags |= FLAG_POTENTIAL_SCANNER;
// Detecção de padrões de SYN flood conhecidos
if (is_syn) {
// TTL alto (> 240) é suspeito de spoofing/ataque
if (ip->ttl >= SUSPICIOUS_TTL_THRESHOLD)
flags |= FLAG_SUSPICIOUS_TTL;
// Window size específico de ataques conhecidos
if (tcp_window == SUSPICIOUS_WINDOW_SIZE)
flags |= FLAG_SUSPICIOUS_WINDOW;
// Padrões de SYN flood na porta SSH (22)
if (dest_port == 22) {
// TTL suspeito: muito alto (>240) ou específico de bots (56, 245)
if (ip->ttl >= SUSPICIOUS_TTL_THRESHOLD || ip->ttl == 56 || ip->ttl == 245)
flags |= FLAG_SUSPICIOUS_TTL;
// Window sizes pequenos típicos de ataques (512, 1024, 2048)
if (tcp_window == 512 || tcp_window == 1024 || tcp_window == 2048 ||
tcp_window == SUSPICIOUS_WINDOW_SIZE)
flags |= FLAG_SUSPICIOUS_WINDOW;
// Bloquear SSH com qualquer padrão suspeito
if (flags & (FLAG_SUSPICIOUS_TTL | FLAG_SUSPICIOUS_WINDOW))
flags |= FLAG_SSH_ATTACKER;
}
// Padrões de SYN flood nas portas HTTP/HTTPS (80, 443, 8117)
if (dest_port == 80 || dest_port == 443 || dest_port == 8117) {
// TTL suspeito: muito alto (>240) ou específico de bots (56, 245)
if (ip->ttl >= SUSPICIOUS_TTL_THRESHOLD || ip->ttl == 56 || ip->ttl == 245)
flags |= FLAG_SUSPICIOUS_TTL;
// Window sizes pequenos típicos de ataques
if (tcp_window == 512 || tcp_window == 1024 || tcp_window == 2048 ||
tcp_window == SUSPICIOUS_WINDOW_SIZE)
flags |= FLAG_SUSPICIOUS_WINDOW;
// Bloquear HTTP/HTTPS com padrão suspeito
if (flags & (FLAG_SUSPICIOUS_TTL | FLAG_SUSPICIOUS_WINDOW))
flags |= FLAG_HTTP_ATTACKER;
}
}
} else if (ip->protocol == IPPROTO_UDP) {
struct udphdr *udp = (void *)(ip + 1);
if ((void *)(udp + 1) > data_end) return XDP_PASS;
dest_port = ntohs(udp->dest);
} else if (ip->protocol == IPPROTO_ICMP) {
is_icmp = 1;
struct icmphdr *icmp = (void *)(ip + 1);
if ((void *)(icmp + 1) > data_end) return XDP_PASS;
// Permitir apenas echo-reply (0) e limitar echo-request (8)
if (icmp->type != 0 && icmp->type != 8) {
update_stats(XDP_DROP, 0);
return XDP_DROP;
}
} else {
return XDP_PASS;
}
// Se não for porta filtrada e não for ICMP, libera rápido
if (!is_filtered_port(dest_port) && !is_icmp) {
// Fast path para TCP 5061, 5161
if (ip->protocol == IPPROTO_TCP && (dest_port == 5061 || dest_port == 5161)) {
struct ip_stats *entry = bpf_map_lookup_elem(&ddos_map, &src_ip);
if (entry && entry->pkt_count > 5 && entry->flags == 0) {
__u8 mark_trusted = 1;
bpf_map_update_elem(&trusted_hosts_map, &src_ip, &mark_trusted, BPF_ANY);
}
return XDP_PASS;
}
struct ip_stats *entry = bpf_map_lookup_elem(&ddos_map, &src_ip);
if (entry && entry->pkt_count > 5 && entry->flags == 0) {
__u8 mark_trusted = 1;
bpf_map_update_elem(&trusted_hosts_map, &src_ip, &mark_trusted, BPF_ANY);
}
return XDP_PASS;
}
// Rate limiting e contadores
int priority = get_port_priority(dest_port);
struct ip_stats *entry = bpf_map_lookup_elem(&ddos_map, &src_ip);
__u32 now = get_time_sec();
// Verificar taxa global de SYN para portas críticas (proteção contra distributed SYN flood)
if (is_syn && (dest_port == 22 || dest_port == 80 || dest_port == 443 || dest_port == 8117)) {
struct syn_rate_stats *syn_rate = bpf_map_lookup_elem(&syn_rate_map, &dest_port);
__u32 max_syn_per_sec = 50; // Padrão
// Ajustar limite por porta
if (dest_port == 80 || dest_port == 443 || dest_port == 8117)
max_syn_per_sec = 200; // HTTP/HTTPS pode ter tráfego legítimo maior
if (syn_rate) {
if (now == syn_rate->last_sec) {
syn_rate->count++;
// Se exceder limite de SYN/segundo para esta porta, bloqueia este IP
if (syn_rate->count > max_syn_per_sec) {
__u32 block_time = now;
bpf_map_update_elem(&blacklist_map, &src_ip, &block_time, BPF_ANY);
update_stats(XDP_DROP, FLAG_SYN_FLOOD);
return XDP_DROP;
}
} else {
syn_rate->last_sec = now;
syn_rate->count = 1;
}
} else {
struct syn_rate_stats new_rate = {.last_sec = now, .count = 1};
bpf_map_update_elem(&syn_rate_map, &dest_port, &new_rate, BPF_ANY);
}
}
// Bloquear imediatamente IPs com padrões de ataque conhecidos
if ((flags & FLAG_SSH_ATTACKER) || (flags & FLAG_HTTP_ATTACKER)) {
__u32 block_time = now;
bpf_map_update_elem(&blacklist_map, &src_ip, &block_time, BPF_ANY);
update_stats(XDP_DROP, FLAG_SYN_FLOOD | flags);
return XDP_DROP;
}
// Use variáveis locais para tokens e contadores
__u32 pkt_count = 1, icmp_count = is_icmp ? 1 : 0, syn_count = is_syn ? 1 : 0;
__u32 high_tokens = MAX_TOKENS - PACKET_COST, low_tokens = MAX_TOKENS - PACKET_COST;
__u32 syn_count_per_sec = is_syn ? 1 : 0;
__u32 syn_last_sec = now;
__u16 entry_flags = flags;
if (entry) {
__u32 elapsed = now > entry->last_seen ? now - entry->last_seen : 0;
pkt_count = entry->pkt_count + 1;
icmp_count = is_icmp ? entry->icmp_count + 1 : entry->icmp_count;
syn_count = is_syn ? entry->syn_count + 1 : entry->syn_count;
high_tokens = entry->high_prio_tokens;
low_tokens = entry->low_prio_tokens;
entry_flags = entry->flags | flags;
// Contagem de SYN por segundo
if (is_syn) {
if (now == entry->syn_last_sec) {
syn_count_per_sec = entry->syn_count_per_sec + 1;
} else {
syn_count_per_sec = 1;
}
syn_last_sec = now;
} else {
syn_count_per_sec = entry->syn_count_per_sec;
syn_last_sec = entry->syn_last_sec;
}
if (elapsed > 0) {
__u32 add_high = HIGH_PRIO_TOKEN_RATE * elapsed;
__u32 add_low = LOW_PRIO_TOKEN_RATE * elapsed;
high_tokens = (high_tokens + add_high > MAX_TOKENS) ? MAX_TOKENS : high_tokens + add_high;
low_tokens = (low_tokens + add_low > MAX_TOKENS) ? MAX_TOKENS : low_tokens + add_low;
if (elapsed >= TIMEOUT_SEC) {
icmp_count = is_icmp ? 1 : 0;
syn_count = is_syn ? 1 : 0;
entry_flags &= ~(FLAG_SYN_FLOOD | FLAG_ICMP_FLOOD);
}
}
// Flood ICMP
if (is_icmp && icmp_count > MAX_ICMP_PACKETS_PER_IP) {
entry_flags |= FLAG_ICMP_FLOOD;
__u32 one = 1;
bpf_map_update_elem(&blacklist_map, &src_ip, &one, BPF_ANY);
update_stats(XDP_DROP, FLAG_ICMP_FLOOD);
goto update_entry_and_drop;
}
// Flood SYN - verificação de taxa por segundo E total
if (is_syn) {
// Bloquear se exceder SYN por segundo
if (syn_count_per_sec > MAX_SYN_PACKETS_PER_SECOND) {
entry_flags |= FLAG_SYN_FLOOD;
__u32 block_time = now;
bpf_map_update_elem(&blacklist_map, &src_ip, &block_time, BPF_ANY);
update_stats(XDP_DROP, FLAG_SYN_FLOOD);
goto update_entry_and_drop;
}
// Bloquear se exceder total de SYN
if (syn_count > MAX_SYN_PACKETS_PER_IP) {
entry_flags |= FLAG_SYN_FLOOD;
__u32 block_time = now;
bpf_map_update_elem(&blacklist_map, &src_ip, &block_time, BPF_ANY);
update_stats(XDP_DROP, FLAG_SYN_FLOOD);
goto update_entry_and_drop;
}
// Bloquear IPs com padrões suspeitos (TTL alto ou window suspeito)
if ((flags & FLAG_SUSPICIOUS_TTL) || (flags & FLAG_SUSPICIOUS_WINDOW)) {
if (syn_count > 5) { // Tolerância muito baixa para padrões suspeitos
entry_flags |= FLAG_SYN_FLOOD;
__u32 block_time = now;
bpf_map_update_elem(&blacklist_map, &src_ip, &block_time, BPF_ANY);
update_stats(XDP_DROP, FLAG_SYN_FLOOD);
goto update_entry_and_drop;
}
}
}
// Flood total
if (pkt_count > MAX_PACKETS_PER_IP) {
entry_flags |= FLAG_MARKED_SUSPICIOUS;
__u32 one = 1;
bpf_map_update_elem(&blacklist_map, &src_ip, &one, BPF_ANY);
update_stats(XDP_DROP, FLAG_MARKED_SUSPICIOUS);
goto update_entry_and_drop;
}
// Token bucket
if (priority == 2) {
if (high_tokens >= PACKET_COST) {
high_tokens -= PACKET_COST;
goto update_entry_and_pass;
}
} else if (priority == 1) {
if (high_tokens >= PACKET_COST/2 && low_tokens >= PACKET_COST/2) {
high_tokens -= PACKET_COST/2;
low_tokens -= PACKET_COST/2;
goto update_entry_and_pass;
}
} else {
if (low_tokens >= PACKET_COST) {
low_tokens -= PACKET_COST;
goto update_entry_and_pass;
}
}
update_stats(XDP_DROP, 0);
goto update_entry_and_drop;
update_entry_and_pass:
{
struct ip_stats new_entry = {
.pkt_count = pkt_count,
.last_seen = now,
.icmp_count = icmp_count,
.syn_count = syn_count,
.high_prio_tokens = high_tokens,
.low_prio_tokens = low_tokens,
.syn_last_sec = syn_last_sec,
.syn_count_per_sec = syn_count_per_sec,
.flags = entry_flags
};
bpf_map_update_elem(&ddos_map, &src_ip, &new_entry, BPF_ANY);
update_stats(XDP_PASS, 0);
return XDP_PASS;
}
update_entry_and_drop:
{
struct ip_stats new_entry = {
.pkt_count = pkt_count,
.last_seen = now,
.icmp_count = icmp_count,
.syn_count = syn_count,
.high_prio_tokens = high_tokens,
.low_prio_tokens = low_tokens,
.syn_last_sec = syn_last_sec,
.syn_count_per_sec = syn_count_per_sec,
.flags = entry_flags
};
bpf_map_update_elem(&ddos_map, &src_ip, &new_entry, BPF_ANY);
return XDP_DROP;
}
} else {
// Primeira vez que vemos este IP
struct ip_stats new_entry = {
.pkt_count = pkt_count,
.last_seen = now,
.icmp_count = icmp_count,
.syn_count = syn_count,
.high_prio_tokens = high_tokens,
.low_prio_tokens = low_tokens,
.syn_last_sec = syn_last_sec,
.syn_count_per_sec = syn_count_per_sec,
.flags = entry_flags
};
bpf_map_update_elem(&ddos_map, &src_ip, &new_entry, BPF_ANY);
update_stats(XDP_PASS, 0);
return XDP_PASS;
}
// Redirecionamento AF_XDP para portas 80, 443 (TCP) e 32100 (UDP)
int xsk_map_key = ctx->rx_queue_index;
// TCP: 80 ou 443
if (ip->protocol == IPPROTO_TCP) {
struct tcphdr *tcp = (void *)(ip + 1);
if ((void *)(tcp + 1) > data_end) return XDP_PASS;
__u16 dest_port = ntohs(tcp->dest);
// Redirecionar também 5061, 5161
if (dest_port == 80 || dest_port == 443 || dest_port == 5061 || dest_port == 5161) {
if (bpf_map_lookup_elem(&xsks_map, &xsk_map_key))
return XDP_REDIRECT;
}
}
// UDP: 32100 (WireGuard)
if (ip->protocol == IPPROTO_UDP) {
struct udphdr *udp = (void *)(ip + 1);
if ((void *)(udp + 1) > data_end) return XDP_PASS;
__u16 dest_port = ntohs(udp->dest);
// Redirecionar também 5060, 5160, 18000-19000
if (dest_port == 32100 || dest_port == 32110 || dest_port == 32101 || dest_port == 32400 ||
dest_port == 5060 || dest_port == 5160 ||
(dest_port >= 18000 && dest_port <= 19000)) {
if (bpf_map_lookup_elem(&xsks_map, &xsk_map_key))
return XDP_REDIRECT;
}
}
return XDP_PASS;
}
char __license[] SEC("license") = "GPL";
Binary file not shown.
+158
View File
@@ -0,0 +1,158 @@
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/udp.h>
#include <linux/icmp.h>
#include <linux/in.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
#include <stdint.h>
// Configurações de MTU para WireGuard
#define WG_OVERHEAD 60 // Overhead do WireGuard (cabeçalhos + criptografia)
#define MAX_WG_MTU 1420 // MTU máximo recomendado para WireGuard (1480 - overhead)
#define ETH_DEFAULT_MTU 1500 // MTU padrão da ethernet
#define MSS_CLAMPING_VALUE (MAX_WG_MTU - 40) // MSS = MTU - IP(20) - TCP(20)
#define MIN_MSS_VALUE 536 // Valor mínimo de MSS conforme RFC
#define ICMP_NEEDED_HEADROOM 8 // Espaço extra para cabeçalho ICMP
#define IPV4_HEADER_DF_OFFSET 6 // Offset do campo Flags no cabeçalho IP
// Estatísticas de pacotes processados
struct mtu_stats {
__u64 total_packets;
__u64 oversized_packets;
__u64 mss_adjusted;
__u64 fragmented;
__u64 pmtu_triggered;
};
// Mapa para armazenar estatísticas
struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__uint(max_entries, 1);
__type(key, __u32);
__type(value, struct mtu_stats);
} mtu_stats_map SEC(".maps");
// Mapa para MTUs conhecidos por destino
struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, 1024);
__type(key, __u32); // IP de destino
__type(value, __u16); // MTU conhecido
} path_mtu_map SEC(".maps");
// Função para ajustar TCP MSS - totalmente reescrita e simplificada para maior compatibilidade
static __always_inline int adjust_tcp_mss(struct tcphdr *tcp, void *data_end, __u16 max_mss) {
// Verificação básica do cabeçalho TCP
if ((void *)(tcp + 1) > data_end)
return 0;
// Ajustamos MSS apenas em pacotes SYN
if (!tcp->syn || tcp->ack)
return 0;
// Obter comprimento do cabeçalho e verificar se há opções
__u8 tcp_hdr_len = tcp->doff * 4;
if (tcp_hdr_len <= sizeof(struct tcphdr))
return 0; // Sem opções TCP
// Simplificado: remover a complexidade de analisar as opções
// Em vez disso, vamos assumir um MSS conservador para todos os pacotes SYN
__u32 key = 0;
struct mtu_stats *stats = bpf_map_lookup_elem(&mtu_stats_map, &key);
if (stats)
stats->mss_adjusted++;
return 1; // Reportamos ajuste sem tentar modificar o pacote
}
// Programa XDP principal - simplificado
SEC("xdp")
int xdp_wg_optimizer(struct xdp_md *ctx) {
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
__u32 key = 0;
// Incrementar estatística de pacotes totais
struct mtu_stats *stats = bpf_map_lookup_elem(&mtu_stats_map, &key);
if (stats)
stats->total_packets++;
// Verificação básica de limites
if (data + sizeof(struct ethhdr) > data_end)
return XDP_PASS;
struct ethhdr *eth = data;
// Filtrar apenas pacotes IPv4
if (eth->h_proto != bpf_htons(ETH_P_IP))
return XDP_PASS;
// Verificar se o pacote contém um cabeçalho IP completo
if ((void *)(eth + 1) + sizeof(struct iphdr) > data_end)
return XDP_PASS;
struct iphdr *iph = (void *)(eth + 1);
// Verificar cabeçalho IP
if ((void *)(iph + 1) > data_end || iph->version != 4)
return XDP_PASS;
// Verificação de MTU
__u16 tot_len = bpf_ntohs(iph->tot_len);
if (tot_len > MAX_WG_MTU) {
// Atualizar estatísticas
if (stats)
stats->oversized_packets++;
// Verificar flag DF
__u16 frag_off = bpf_ntohs(iph->frag_off);
if (frag_off & 0x4000) {
// Don't Fragment está ativo, registrar o MTU para este destino
__u32 dest_ip = iph->daddr;
__u16 mtu = MAX_WG_MTU;
bpf_map_update_elem(&path_mtu_map, &dest_ip, &mtu, BPF_ANY);
// Atualizar estatísticas
if (stats)
stats->pmtu_triggered++;
return XDP_ABORTED; // Sinalizar para enviar ICMP Fragmentation Needed
} else {
// Pacote pode ser fragmentado
if (stats)
stats->fragmented++;
}
}
// Ajustar MSS para pacotes TCP
if (iph->protocol == IPPROTO_TCP) {
__u8 ihl = iph->ihl * 4; // IP header length
void *tcp_ptr = (void *)iph + ihl;
// Verificar se temos espaço para o cabeçalho TCP
if (tcp_ptr + sizeof(struct tcphdr) <= data_end) {
struct tcphdr *tcp = tcp_ptr;
adjust_tcp_mss(tcp, data_end, MSS_CLAMPING_VALUE);
}
}
// Verificar MTUs conhecidos
__u16 *known_mtu = bpf_map_lookup_elem(&path_mtu_map, &iph->daddr);
if (known_mtu && tot_len > *known_mtu) {
__u16 frag_off = bpf_ntohs(iph->frag_off);
if (frag_off & 0x4000) {
// Don't Fragment está ativo, não pode passar pelo tunnel
if (stats)
stats->pmtu_triggered++;
return XDP_ABORTED;
}
}
// Permitir o pacote
return XDP_PASS;
}
char __license[] SEC("license") = "GPL";
Binary file not shown.