fixed4339
This commit is contained in:
1 parent
26d0220ecb
commit
13d0584df9
2 files changed
+108
-37
No files matched your search
@@ -60,6 +60,39 @@ static __always_inline void stats_inc(__u32 idx)
|
||||
__sync_fetch_and_add(val, 1);
|
||||
}
|
||||
|
||||
#define SOCKMAP_AF_INET 2
|
||||
#define SOCKMAP_AF_INET6 10
|
||||
|
||||
// ─────────────────────────────────────────────────────────
|
||||
// BUG ENCONTRADO E CORRIGIDO: o filtro "só IPv4" (family != AF_INET)
|
||||
// descartava toda conexão aceita por um servidor que escuta em dual-stack
|
||||
// (bind em "::", padrão do Apache/muitos daemons quando não força IPv4).
|
||||
// O socket ACEITO aparece como AF_INET6 pro kernel/eBPF mesmo a conexão
|
||||
// sendo, na prática, IPv4 — o endereço vem mapeado como ::ffff:a.b.c.d.
|
||||
//
|
||||
// Evidência que confirmou isso: em produção, o Nextcloud (Apache
|
||||
// dual-stack) tinha 0 de 184 conexões aceitas registradas no sockhash —
|
||||
// o lado ATIVO dele (quando ELE disca pra MariaDB/Redis com IP literal,
|
||||
// então family=AF_INET) funcionava 375/375, mas o lado PASSIVO (aceitando
|
||||
// conexão do nginx) nunca registrava, porque a mesma conexão TCP aparece
|
||||
// como AF_INET6 do lado de quem aceitou. Isso deixava o socket do nginx
|
||||
// "meio-registrado" (só o lado dele no mapa), redirect sempre em
|
||||
// fallback, e é o cenário suspeito por trás dos travamentos em
|
||||
// transferências grandes.
|
||||
//
|
||||
// Verifica se um endereço IPv6 é, na verdade, um IPv4 mapeado
|
||||
// (::ffff:a.b.c.d, RFC 4291 §2.5.5.2: 80 bits zero + 16 bits 0xffff +
|
||||
// 32 bits do IPv4) e extrai o IPv4 equivalente.
|
||||
static __always_inline bool ipv4_mapped(const __u32 ip6[4], __u32 *out_ip4)
|
||||
{
|
||||
if (ip6[0] != 0 || ip6[1] != 0)
|
||||
return false;
|
||||
if (bpf_ntohl(ip6[2]) != 0x0000ffff)
|
||||
return false;
|
||||
*out_ip4 = ip6[3];
|
||||
return true;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────
|
||||
// Verifica se um IP (em network byte order / big-endian) é
|
||||
// endereço privado RFC-1918 ou loopback.
|
||||
@@ -114,14 +147,39 @@ static __always_inline bool is_local_ip(__u32 ip_be)
|
||||
// calculam exatamente o mesmo valor pro mesmo par de portas, não importa
|
||||
// qual seja a codificação exata do kernel — o que importa é que os dois
|
||||
// membros do par usem a MESMA transformação simétrica.
|
||||
static __always_inline void
|
||||
// Retorna false se a conexão não puder ser reduzida a um par IPv4 (IPv6
|
||||
// nativo de verdade, fora do escopo deste programa) — quem chama deve
|
||||
// tratar isso como "não é pra registrar", igual ao antigo filtro de
|
||||
// family, só que agora sem descartar dual-stack por engano.
|
||||
static __always_inline bool
|
||||
extract_key_from_ops(struct bpf_sock_ops *ops, struct sock_key *key)
|
||||
{
|
||||
key->sip4 = ops->local_ip4;
|
||||
key->dip4 = ops->remote_ip4;
|
||||
__u32 local_ip4, remote_ip4;
|
||||
|
||||
if (ops->family == SOCKMAP_AF_INET) {
|
||||
local_ip4 = ops->local_ip4;
|
||||
remote_ip4 = ops->remote_ip4;
|
||||
} else if (ops->family == SOCKMAP_AF_INET6) {
|
||||
if (!ipv4_mapped(ops->local_ip6, &local_ip4) ||
|
||||
!ipv4_mapped(ops->remote_ip6, &remote_ip4))
|
||||
return false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
key->sip4 = local_ip4;
|
||||
key->dip4 = remote_ip4;
|
||||
key->sport = bpf_htonl(ops->local_port) >> 16;
|
||||
key->dport = ops->remote_port >> 16;
|
||||
key->family = ops->family;
|
||||
// family SEMPRE normalizado pra AF_INET: os dois lados da MESMA
|
||||
// conexão TCP podem reportar families diferentes entre si (um vê
|
||||
// AF_INET literal, o outro vê AF_INET6 mapeado por causa de bind
|
||||
// dual-stack) — sem normalizar, as chaves dos dois lados nunca
|
||||
// bateriam e o par nunca seria encontrado no sockhash. Foi
|
||||
// exatamente isso que deixava o Nextcloud (Apache em dual-stack)
|
||||
// com 0 registros passivos em 184 conexões aceitas.
|
||||
key->family = SOCKMAP_AF_INET;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────
|
||||
@@ -136,8 +194,12 @@ int bpf_sockops_handler(struct bpf_sock_ops *skops)
|
||||
{
|
||||
__u32 op = skops->op;
|
||||
|
||||
// Só processamos conexões IPv4
|
||||
if (skops->family != 2) // AF_INET
|
||||
// Aceita AF_INET e AF_INET6 aqui (o filtro real de família acontece
|
||||
// dentro de extract_key_from_ops, que resolve IPv6 mapeado pra IPv4
|
||||
// ou retorna false se for IPv6 de verdade). Cortar por family==AF_INET
|
||||
// logo no topo, como a versão antiga fazia, foi o que descartava
|
||||
// silenciosamente toda conexão aceita por um listener dual-stack.
|
||||
if (skops->family != SOCKMAP_AF_INET && skops->family != SOCKMAP_AF_INET6)
|
||||
return 0;
|
||||
|
||||
switch (op) {
|
||||
@@ -145,25 +207,24 @@ int bpf_sockops_handler(struct bpf_sock_ops *skops)
|
||||
case BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB:
|
||||
// Conexão ativa: cliente conectou a um servidor
|
||||
case BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB: {
|
||||
struct sock_key key = {};
|
||||
if (!extract_key_from_ops(skops, &key)) {
|
||||
stats_inc(3); // IPv6 de verdade (não mapeado) — fora do escopo
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ── 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)) {
|
||||
if (!is_local_ip(key.sip4) || !is_local_ip(key.dip4)) {
|
||||
stats_inc(3); // conta conexões externas ignoradas
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct sock_key key = {};
|
||||
extract_key_from_ops(skops, &key);
|
||||
|
||||
// DEBUG TEMPORÁRIO — remover depois de confirmar o comportamento.
|
||||
bpf_printk("REG op=%u sip=%x dip=%x", op, key.sip4, key.dip4);
|
||||
bpf_printk("REG sport=%u dport=%u", key.sport, key.dport);
|
||||
if (skops->family == SOCKMAP_AF_INET6)
|
||||
bpf_printk("REG via IPv6 mapeado: op=%u sip=%x dip=%x", op, key.sip4, key.dip4);
|
||||
|
||||
// BPF_ANY: atualiza mesmo se já existir (cobre reconexões keepalive)
|
||||
int ret = bpf_sock_hash_update(skops, &sock_hash, &key, BPF_ANY);
|
||||
bpf_printk("REG update ret=%d", ret);
|
||||
if (ret == 0) {
|
||||
stats_inc(0);
|
||||
|
||||
@@ -190,9 +251,7 @@ int bpf_sockops_handler(struct bpf_sock_ops *skops)
|
||||
new_state == BPF_TCP_FIN_WAIT2 ||
|
||||
new_state == BPF_TCP_TIME_WAIT) {
|
||||
struct sock_key key = {};
|
||||
extract_key_from_ops(skops, &key);
|
||||
// DEBUG TEMPORÁRIO
|
||||
bpf_printk("DEL state=%u sport=%u dport=%u", new_state, key.sport, key.dport);
|
||||
if (extract_key_from_ops(skops, &key))
|
||||
bpf_map_delete_elem(&sock_hash, &key);
|
||||
}
|
||||
break;
|
||||
@@ -228,21 +287,33 @@ int bpf_msg_redirect(struct sk_msg_md *msg)
|
||||
//
|
||||
// Mesma normalização de porta usada em extract_key_from_ops (mesmo
|
||||
// domínio dos dois lados): remote_port só com ">> 16", local_port
|
||||
// convertido pra esse domínio via "bpf_htonl(...) >> 16".
|
||||
// convertido pra esse domínio via "bpf_htonl(...) >> 16". Mesma
|
||||
// resolução de IPv6-mapeado-pra-IPv4 também — precisa bater com o
|
||||
// que extract_key_from_ops grava no REGISTRO, senão o par nunca é
|
||||
// encontrado (era exatamente o bug do Nextcloud dual-stack).
|
||||
__u32 sip4, dip4;
|
||||
if (msg->family == SOCKMAP_AF_INET) {
|
||||
sip4 = msg->remote_ip4;
|
||||
dip4 = msg->local_ip4;
|
||||
} else if (msg->family == SOCKMAP_AF_INET6) {
|
||||
if (!ipv4_mapped(msg->remote_ip6, &sip4) || !ipv4_mapped(msg->local_ip6, &dip4)) {
|
||||
stats_inc(2); // IPv6 de verdade — nunca deveria estar no mapa, mas por segurança cai no fallback
|
||||
return SK_PASS;
|
||||
}
|
||||
} else {
|
||||
stats_inc(2);
|
||||
return SK_PASS;
|
||||
}
|
||||
|
||||
struct sock_key key = {};
|
||||
key.sip4 = msg->remote_ip4; // IP remoto = IP do par
|
||||
key.dip4 = msg->local_ip4; // IP local = nosso IP
|
||||
key.sip4 = sip4; // IP remoto = IP do par
|
||||
key.dip4 = dip4; // IP local = nosso IP
|
||||
key.sport = msg->remote_port >> 16;
|
||||
key.dport = bpf_htonl(msg->local_port) >> 16;
|
||||
key.family = msg->family;
|
||||
|
||||
// DEBUG TEMPORÁRIO
|
||||
bpf_printk("MSG sip=%x dip=%x", key.sip4, key.dip4);
|
||||
bpf_printk("MSG sport=%u dport=%u size=%u", key.sport, key.dport, msg->size);
|
||||
key.family = SOCKMAP_AF_INET; // normalizado, ver extract_key_from_ops
|
||||
|
||||
// Tenta redirecionar diretamente para o socket par.
|
||||
int ret = bpf_msg_redirect_hash(msg, &sock_hash, &key, BPF_F_INGRESS);
|
||||
bpf_printk("MSG redirect ret=%d", ret);
|
||||
if (ret == SK_PASS) {
|
||||
stats_inc(1); // Redirecionamento OK — caminho acelerado
|
||||
return SK_PASS;
|
||||
|
||||
@@ -52,12 +52,12 @@ ip link set dev wg0 xdp obj /home/rodrigo/server-mv/xdp/xdp_wg_mtu_optimizer.o s
|
||||
# então esse link precisa ser reaplicado se a rede "dockernet" for recriada.
|
||||
ip link set dev br-02f7820020b4 xdp obj /home/rodrigo/server-mv/xdp/bridge_xdp.o sec xdp
|
||||
|
||||
# eBPF sockmap: DESATIVADO DE PROPÓSITO. Causou timeout/connection reset
|
||||
# em conexões container-a-container (nginx->redis/mariadb/anatomia-web),
|
||||
# incluindo depois de corrigir o bug de NAT (masquerade sem -o enp0s6,
|
||||
# já corrigido em nftables.conf) — ou seja, o bug é na lógica de
|
||||
# match/redirect do sk_msg em bpf/sockmap.bpf.c, não só no NAT. Não
|
||||
# reativar em produção sem antes rastrear com bpf_printk num ambiente
|
||||
# de teste. Ganho é só performance (bypass de socket local entre
|
||||
# containers no mesmo host); o site funciona normalmente sem isso.
|
||||
# cd /home/docker/ebpf-sockmap/ && make load
|
||||
# eBPF sockmap: DESATIVADO DE PROPÓSITO. O bug de porta (byte-order
|
||||
# assimétrico entre local_port/remote_port em extract_key_from_ops e
|
||||
# bpf_msg_redirect) já foi corrigido e resolveu conexões pequenas/médias,
|
||||
# mas arquivos grandes (streaming sustentado) ainda travam — suspeita de
|
||||
# backpressure/sendfile no caminho de redirect do sk_msg, ainda em
|
||||
# investigação com bpf_printk. Não reativar em produção até confirmar.
|
||||
# Ganho é só performance (bypass de socket local entre containers no
|
||||
# mesmo host); o site funciona normalmente sem isso.
|
||||
# cd /home/rodrigo/server-mv/ebpf-sockmap/ && make load
|
||||
Reference in new issue
Block a user