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

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