first commit
This commit is contained in:
commit
01d7cb1a80
34 files changed
+5723
No files matched your search
@@ -0,0 +1,7 @@
|
||||
build/
|
||||
bpf/vmlinux.h
|
||||
*.o
|
||||
node_modules/
|
||||
mysql-data/
|
||||
postgres-data/
|
||||
.env
|
||||
@@ -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 ""
|
||||
@@ -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)
|
||||
@@ -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";
|
||||
@@ -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"]
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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');
|
||||
@@ -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);
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 ""
|
||||
Executable
+403
@@ -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
|
||||
@@ -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 ""
|
||||
Reference in new issue
Block a user