/* ============================================================================ * tc_capture.c — Programme XDP ingress : capture des TCP SYN et TLS ClientHello * * Remplace l'ancienne version TC (SCHED_CLS + TCX) par un hook XDP compatible * depuis le kernel 4.8. Utilisé en mode XDP_GENERIC sur Rocky Linux 9 (5.14). * * Conventions vérificateur eBPF : * - Tous les accès mémoire paquet utilisent de l'arithmétique de pointeur * directe avec bornes explicites (data / data_end). * - Les copies de longueur variable utilisent des boucles bornées (sans * #pragma unroll) : le vérificateur kernel ≥ 5.3 les accepte nativement. * - Les options TCP sont copiées brutes ; MSS et Window Scale sont extraits * côté Go (userspace) depuis le tableau tcp_options_raw. * ============================================================================ */ #include "vmlinux.h" #include #include #include #include "bpf_types.h" /* Constantes Ethernet */ #define ETH_P_IP 0x0800 #define ETH_HLEN 14 /* Constantes IP */ #define IPPROTO_TCP 6 #define IP_DF 0x4000 /* Constantes TCP */ #define TH_SYN 0x02 #define TH_ACK 0x10 #define TH_FIN 0x01 #define TH_RST 0x04 /* Ports */ #define HTTPS_PORT 443 #define HTTP_PORT 80 #define HTTP_ALT_PORT 8080 /* TLS */ #define TLS_CONTENT_HANDSHAKE 0x16 #define TLS_MSG_CLIENT_HELLO 0x01 /* Tailles maximales des payloads copiés */ #define MAX_TLS_PAYLOAD 2048 #define MAX_HTTP_PAYLOAD 1024 #define MAX_TCP_OPTIONS 40 /* Structure Ethernet locale (évite d'inclure linux/if_ether.h) */ struct ethhdr_local { __u8 h_dest[6]; __u8 h_source[6]; __be16 h_proto; } __attribute__((packed)); /* --------------------------------------------------------------------------- * capture_xdp — Point d'entrée XDP ingress * * Observe chaque paquet ingress en lecture seule (retourne toujours XDP_PASS). * Émet des événements vers les ring buffers pour TCP SYN, TLS ClientHello * et les payloads HTTP en clair. * ---------------------------------------------------------------------------*/ SEC("xdp") int capture_xdp(struct xdp_md *ctx) { void *data = (void *)(long)ctx->data; void *data_end = (void *)(long)ctx->data_end; /* --- Ethernet --- */ struct ethhdr_local *eth = data; if ((void *)(eth + 1) > data_end) return XDP_PASS; if (bpf_ntohs(eth->h_proto) != ETH_P_IP) return XDP_PASS; /* --- IPv4 --- */ struct iphdr *ip = data + ETH_HLEN; if ((void *)(ip + 1) > data_end) return XDP_PASS; if (ip->protocol != IPPROTO_TCP) return XDP_PASS; __u32 ihl = ip->ihl & 0x0F; if (ihl < 5) return XDP_PASS; __u32 ip_hlen = ihl << 2; /* ∈ [20, 60] */ __u32 src_ip = ip->saddr; __u32 dst_ip = ip->daddr; __u8 ttl = ip->ttl; __u16 ip_id = bpf_ntohs(ip->id); __u16 frag_off = bpf_ntohs(ip->frag_off); __u8 df_bit = (frag_off & IP_DF) ? 1 : 0; /* --- TCP à offset variable --- */ struct tcphdr *tcp = (void *)ip + ip_hlen; if ((void *)(tcp + 1) > data_end) /* valide tcp[0..19] */ return XDP_PASS; __u16 src_port = bpf_ntohs(tcp->source); __u16 dst_port = bpf_ntohs(tcp->dest); __u16 window = bpf_ntohs(tcp->window); /* Flags via les champs de bits du struct (sûr pour le vérificateur) */ __u8 tcp_flags = 0; if (tcp->syn) tcp_flags |= TH_SYN; if (tcp->ack) tcp_flags |= TH_ACK; if (tcp->fin) tcp_flags |= TH_FIN; if (tcp->rst) tcp_flags |= TH_RST; __u32 doff = tcp->doff; if (doff < 5) return XDP_PASS; __u32 tcp_hlen = doff << 2; /* ∈ [20, 60] */ /* Offset du payload applicatif */ void *payload = (void *)tcp + tcp_hlen; /* =================================================================== * TCP SYN : extraction des paramètres L3/L4 * ===================================================================*/ if ((tcp_flags & TH_SYN) && !(tcp_flags & TH_ACK)) { struct tcp_syn_event *evt = bpf_ringbuf_reserve(&rb_tcp_syn, sizeof(*evt), 0); if (!evt) return XDP_PASS; evt->src_ip = bpf_ntohl(src_ip); evt->dst_ip = bpf_ntohl(dst_ip); evt->src_port = src_port; evt->dst_port = dst_port; evt->ttl = ttl; evt->df_bit = df_bit; evt->ip_id = ip_id; evt->window_size = window; evt->window_scale = 0xFF; /* défaut = absent */ evt->mss = 0; evt->timestamp_ns = bpf_ktime_get_ns(); evt->tcp_options_len = 0; /* Copie brute des options TCP (MSS/WS extraits en userspace Go). * Boucle bornée à MAX_TCP_OPTIONS = 40 itérations : triviale pour * le vérificateur kernel ≥ 5.3, sans #pragma unroll. */ __u8 *opts_start = (__u8 *)(tcp + 1); /* après les 20 octets fixes */ __u32 opts_len = tcp_hlen - 20; /* ∈ [0, 40] */ if (opts_len > MAX_TCP_OPTIONS) opts_len = MAX_TCP_OPTIONS; if (opts_len > 0) { #pragma clang loop unroll(disable) for (__u32 i = 0; i < MAX_TCP_OPTIONS; i++) { if (i >= opts_len) break; if (opts_start + i + 1 > (__u8 *)data_end) break; evt->tcp_options_raw[i] = opts_start[i]; } evt->tcp_options_len = (__u8)opts_len; } bpf_ringbuf_submit(evt, 0); } /* =================================================================== * TLS ClientHello (port 443) * ===================================================================*/ if (dst_port == HTTPS_PORT) { /* Au moins 6 octets pour l'en-tête TLS record + type message */ if (payload + 6 > data_end) return XDP_PASS; __u8 tls_type = ((__u8 *)payload)[0]; __u8 tls_msg_type = ((__u8 *)payload)[5]; if (tls_type != TLS_CONTENT_HANDSHAKE || tls_msg_type != TLS_MSG_CLIENT_HELLO) return XDP_PASS; __u32 avail = (__u8 *)data_end - (__u8 *)payload; /* avail ≥ 6 (vérifié ci-dessus), on plafonne à MAX_TLS_PAYLOAD */ if (avail > MAX_TLS_PAYLOAD) avail = MAX_TLS_PAYLOAD; /* Barrière compilateur : coupe le lien CSE entre avail et (data_end - payload). * Sans cette barrière, clang génère un test "PTR_TO_PACKET <<= 32" (compare * data_end == payload pour l'entrée de boucle) que le vérificateur eBPF rejette. * La barrière force une comparaison scalaire (avail == 0) à la place. */ asm volatile("" : "+r"(avail)); struct tls_hello_event *tls_evt = bpf_ringbuf_reserve(&rb_tls_hello, sizeof(*tls_evt), 0); if (!tls_evt) return XDP_PASS; tls_evt->src_ip = bpf_ntohl(src_ip); tls_evt->src_port = src_port; tls_evt->timestamp_ns = bpf_ktime_get_ns(); tls_evt->payload_len = (__u16)avail; /* Copie bornée du payload TLS. * Pour tout i < avail : payload + i < payload + avail ≤ data_end. * Le vérificateur kernel ≥ 5.3 peut vérifier cette boucle sans unroll. */ __u8 *src = (__u8 *)payload; #pragma clang loop unroll(disable) for (__u32 i = 0; i < MAX_TLS_PAYLOAD; i++) { if (i >= avail) break; if (src + i + 1 > (__u8 *)data_end) break; tls_evt->payload[i] = src[i]; } bpf_ringbuf_submit(tls_evt, 0); return XDP_PASS; } /* =================================================================== * HTTP en clair (port 80 / 8080) * ===================================================================*/ if (dst_port == HTTP_PORT || dst_port == HTTP_ALT_PORT) { /* Ignorer SYN, FIN, RST : seuls les segments de données */ if (tcp_flags & (TH_SYN | TH_FIN | TH_RST)) return XDP_PASS; if (payload >= data_end) return XDP_PASS; __u32 avail = (__u8 *)data_end - (__u8 *)payload; if (avail > MAX_HTTP_PAYLOAD) avail = MAX_HTTP_PAYLOAD; /* Même barrière que pour la section TLS : force comparaison scalaire. */ asm volatile("" : "+r"(avail)); struct http_plain_event *h_evt = bpf_ringbuf_reserve(&rb_http_plain, sizeof(*h_evt), 0); if (!h_evt) return XDP_PASS; h_evt->src_ip = bpf_ntohl(src_ip); h_evt->dst_ip = bpf_ntohl(dst_ip); h_evt->src_port = src_port; h_evt->dst_port = dst_port; h_evt->timestamp_ns = bpf_ktime_get_ns(); h_evt->payload_len = (__u16)avail; __u8 *src = (__u8 *)payload; #pragma clang loop unroll(disable) for (__u32 i = 0; i < MAX_HTTP_PAYLOAD; i++) { if (i >= avail) break; if (src + i + 1 > (__u8 *)data_end) break; h_evt->payload[i] = src[i]; } bpf_ringbuf_submit(h_evt, 0); } return XDP_PASS; } char LICENSE[] SEC("license") = "GPL";