/* ============================================================================ * tc_capture.c — Programme TC ingress : capture des TCP SYN et TLS ClientHello * * Attaché sur l'interface réseau en ingress via TC (Traffic Control). * Émet des événements vers les ring buffers rb_tcp_syn et rb_tls_hello. * * Conventions vérificateur eBPF : * - Tous les offsets variables (ihl, doff) sont stockés en __u32 et bornés * explicitement avant tout usage en arithmétique de pointeur. * - Les lectures de longueur variable (options TCP, payload TLS/HTTP) sont * effectuées via bpf_skb_load_bytes() dans des tampons de pile locaux, * évitant ainsi toute arithmétique de pointeur sur des données paquet. * ============================================================================ */ #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 /* bit Don't Fragment */ /* Constantes TCP */ #define TH_SYN 0x02 #define TH_ACK 0x10 #define TH_FIN 0x01 #define TH_RST 0x04 /* Port HTTPS standard */ #define HTTPS_PORT 443 /* Ports HTTP en clair */ #define HTTP_PORT 80 #define HTTP_ALT_PORT 8080 /* Type de contenu TLS : Handshake */ #define TLS_CONTENT_HANDSHAKE 0x16 /* Type de message TLS : ClientHello */ #define TLS_MSG_CLIENT_HELLO 0x01 /* Taille maximale du payload TLS à copier (puissance de 2) */ #define MAX_TLS_PAYLOAD 512 /* Longueur maximale des options TCP en octets */ #define MAX_TCP_OPTIONS 40 /* --------------------------------------------------------------------------- * Structure interne pour le parsing de l'en-tête Ethernet * ---------------------------------------------------------------------------*/ struct ethhdr_local { __u8 h_dest[6]; __u8 h_source[6]; __be16 h_proto; } __attribute__((packed)); /* --------------------------------------------------------------------------- * capture_tc_ingress — Point d'entrée TC ingress * * Inspecte chaque paquet entrant, détecte les TCP SYN et les ClientHello TLS, * et soumet les événements correspondants aux ring buffers. * ---------------------------------------------------------------------------*/ SEC("tc/ingress") int capture_tc_ingress(struct __sk_buff *skb) { void *data = (void *)(long)skb->data; void *data_end = (void *)(long)skb->data_end; /* --- Parsing Ethernet --- */ struct ethhdr_local *eth = data; if ((void *)(eth + 1) > data_end) return TC_ACT_OK; if (bpf_ntohs(eth->h_proto) != ETH_P_IP) return TC_ACT_OK; /* --- Parsing IPv4 --- */ struct iphdr *ip = data + ETH_HLEN; if ((void *)(ip + 1) > data_end) return TC_ACT_OK; if (ip->protocol != IPPROTO_TCP) return TC_ACT_OK; /* ihl stocké en u32 et borné explicitement : le vérificateur peut ainsi * prouver que ip_hlen ∈ [20, 60] sans risque d'overflow signé. */ __u32 ihl = ip->ihl & 0x0F; if (ihl < 5) return TC_ACT_OK; __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; /* --- Parsing TCP --- */ struct tcphdr *tcp = data + ETH_HLEN + ip_hlen; if ((void *)(tcp + 1) > data_end) return TC_ACT_OK; __u16 src_port = bpf_ntohs(tcp->source); __u16 dst_port = bpf_ntohs(tcp->dest); __u16 window = bpf_ntohs(tcp->window); /* Lecture des flags via offset constant (octet 13 de l'en-tête TCP) */ __u8 tcp_flags = ((__u8 *)tcp)[13]; /* doff stocké en u32 et borné : tcp_hlen ∈ [20, 60] */ __u32 doff = tcp->doff; if (doff < 5) return TC_ACT_OK; __u32 tcp_hlen = doff << 2; /* ∈ [20, 60] */ /* Offset absolu du début du payload applicatif dans le paquet */ __u32 payload_off = ETH_HLEN + ip_hlen + tcp_hlen; /* --- Détection TCP SYN (SYN set, ACK clear) --- */ 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 TC_ACT_OK; 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; /* absent par défaut */ evt->mss = 0; /* absent par défaut */ evt->timestamp_ns = bpf_ktime_get_ns(); evt->tcp_options_len = 0; /* Lecture des options TCP dans un tampon de pile local (copie brute). * Le scan MSS/WS utilise bpf_skb_load_bytes avec offset variable plutôt * que opts_buf[j] : l'accès pile à index variable génère une erreur * vérificateur ("invalid variable-offset read from stack") car le tnum de * j accumule des bits carries au fil des incréments j += len (u8). */ __u32 opts_off = ETH_HLEN + ip_hlen + 20; __u32 opts_bytes = tcp_hlen - 20; /* tcp_hlen >= 20, donc >= 0 */ if (opts_bytes > MAX_TCP_OPTIONS) opts_bytes = MAX_TCP_OPTIONS; if (opts_bytes > 0) { __u8 opts_buf[MAX_TCP_OPTIONS] = {0}; /* Lecture à taille constante : le vérificateur connaît la borne. */ if (bpf_skb_load_bytes(skb, opts_off, opts_buf, MAX_TCP_OPTIONS) == 0) { /* Copie brute dans l'événement */ __builtin_memcpy(evt->tcp_options_raw, opts_buf, MAX_TCP_OPTIONS); evt->tcp_options_len = (__u8)opts_bytes; /* Scan MSS et Window Scale via bpf_skb_load_bytes (offset variable * dans le paquet = autorisé ; index variable dans la pile = refusé). */ __u32 j = 0; __u8 hdr2[2] = {0}; __u8 one[1] = {0}; #pragma unroll for (int iter = 0; iter < MAX_TCP_OPTIONS; iter++) { if (j + 1 >= opts_bytes) break; /* Lire kind et len d'un coup depuis le paquet */ if (bpf_skb_load_bytes(skb, opts_off + j, hdr2, 2) < 0) break; __u8 kind = hdr2[0]; if (kind == 0) break; /* EOL */ if (kind == 1) { j++; continue; /* NOP : 1 octet */ } __u8 len = hdr2[1]; if (len < 2 || j + len > opts_bytes) break; /* MSS (option 2) : 4 octets */ if (kind == 2 && len == 4) { __u8 mss_buf[2] = {0}; if (bpf_skb_load_bytes(skb, opts_off + j + 2, mss_buf, 2) == 0) { __u16 mss_val; __builtin_memcpy(&mss_val, mss_buf, 2); evt->mss = bpf_ntohs(mss_val); } } /* Window Scale (option 3) : 3 octets */ if (kind == 3 && len == 3) { if (bpf_skb_load_bytes(skb, opts_off + j + 2, one, 1) == 0) evt->window_scale = one[0]; } j += len; } } } bpf_ringbuf_submit(evt, 0); } /* --- Détection TLS ClientHello (port 443) --- */ if (dst_port == HTTPS_PORT) { /* Vérifier qu'il y a au moins 6 octets pour l'en-tête TLS record */ if (payload_off + 6 > skb->len) return TC_ACT_OK; __u8 tls_hdr[6]; if (bpf_skb_load_bytes(skb, payload_off, tls_hdr, sizeof(tls_hdr)) < 0) return TC_ACT_OK; /* Handshake (0x16) + ClientHello (0x01 au byte 5) */ if (tls_hdr[0] != TLS_CONTENT_HANDSHAKE || tls_hdr[5] != TLS_MSG_CLIENT_HELLO) return TC_ACT_OK; struct tls_hello_event *tls_evt = bpf_ringbuf_reserve(&rb_tls_hello, sizeof(*tls_evt), 0); if (!tls_evt) return TC_ACT_OK; tls_evt->src_ip = bpf_ntohl(src_ip); tls_evt->src_port = src_port; tls_evt->timestamp_ns = bpf_ktime_get_ns(); /* Calcul de la longueur disponible. * IMPORTANT : appliquer le masque SANS cap préalable. Si un cap * `if (avail > N) avail = N` précède le masque, le compilateur * supprime l'AND (semantically redundant). Sans cap, le compilateur * conserve l'AND et le vérificateur en déduit avail ∈ [0, 511]. * Cas edge : avail exactement multiple de 512 → avail & 511 = 0. */ __u32 avail = skb->len - payload_off; avail &= (MAX_TLS_PAYLOAD - 1); /* verifier : avail ∈ [0, 511] */ if (avail == 0) { bpf_ringbuf_discard(tls_evt, 0); return TC_ACT_OK; } tls_evt->payload_len = (__u16)avail; if (bpf_skb_load_bytes(skb, payload_off, tls_evt->payload, avail) < 0) { bpf_ringbuf_discard(tls_evt, 0); return TC_ACT_OK; } bpf_ringbuf_submit(tls_evt, 0); return TC_ACT_OK; } /* --- Détection payload 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 nous intéressent */ if (tcp_flags & (TH_SYN | TH_FIN | TH_RST)) return TC_ACT_OK; if (payload_off >= skb->len) return TC_ACT_OK; __u32 avail = skb->len - payload_off; /* Même stratégie que pour TLS : masque SANS cap préalable. * Le compilateur conserve l'AND, le vérificateur déduit [0, 4095]. */ avail &= 0xFFF; /* verifier : avail ∈ [0, 4095], smin ≥ 0 */ if (avail == 0) return TC_ACT_OK; struct http_plain_event *h_evt = bpf_ringbuf_reserve(&rb_http_plain, sizeof(*h_evt), 0); if (!h_evt) return TC_ACT_OK; 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->payload_len = (__u16)avail; h_evt->timestamp_ns = bpf_ktime_get_ns(); if (bpf_skb_load_bytes(skb, payload_off, h_evt->payload, avail) < 0) { bpf_ringbuf_discard(h_evt, 0); return TC_ACT_OK; } bpf_ringbuf_submit(h_evt, 0); } return TC_ACT_OK; } char LICENSE[] SEC("license") = "GPL";