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

+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 ""