first commit
This commit is contained in:
commit
01d7cb1a80
34 files changed
+5723
No files matched your search
@@ -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";
|
||||
Reference in new issue
Block a user