/* ============================================================================ * 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. * ============================================================================ */ #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 /* Port HTTPS standard */ #define HTTPS_PORT 443 /* Ports HTTP en clair */ #define HTTP_PORT 80 #define HTTP_ALT_PORT 8080 /* Flags TCP */ #define TH_FIN 0x01 #define TH_RST 0x04 /* 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 */ #define MAX_TLS_PAYLOAD 512 /* Longueur maximale des options TCP */ #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) { /* Pointeurs de début et fin du buffer paquet */ 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; /* Vérifier que c'est un paquet IPv4 */ if (bpf_ntohs(eth->h_proto) != ETH_P_IP) return TC_ACT_OK; /* --- Parsing IPv4 --- */ struct iphdr *ip = (struct iphdr *)((void *)eth + ETH_HLEN); if ((void *)(ip + 1) > data_end) return TC_ACT_OK; /* Vérifier que c'est du TCP */ if (ip->protocol != IPPROTO_TCP) return TC_ACT_OK; __u8 ihl = ip->ihl & 0x0F; /* longueur en-tête IP en mots de 32 bits */ __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 = (struct tcphdr *)((void *)ip + (ihl * 4)); 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); __u8 tcp_flags = ((__u8 *)tcp)[13]; /* octet des flags TCP */ __u8 data_off = tcp->doff; /* longueur en-tête TCP en mots de 32 bits */ /* --- Détection TCP SYN (SYN set, ACK clear) --- */ if ((tcp_flags & TH_SYN) && !(tcp_flags & TH_ACK)) { /* Allouer un slot dans le ring buffer */ 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(); /* --- Parsing des options TCP --- */ __u8 *opts_start = (__u8 *)tcp + 20; /* options commencent après les 20 octets fixes */ __u8 opts_len = (data_off * 4) - 20; if (opts_len > MAX_TCP_OPTIONS) opts_len = MAX_TCP_OPTIONS; evt->tcp_options_len = 0; /* Copier les options brutes avec vérification de bornes */ __u8 *opts_ptr = opts_start; /* Boucle bornée sur les options TCP (max 40 octets) */ #pragma unroll for (int i = 0; i < MAX_TCP_OPTIONS; i++) { if (i >= opts_len) break; if ((void *)(opts_ptr + i + 1) > data_end) break; __u8 opt_kind; bpf_probe_read_kernel(&opt_kind, 1, opts_ptr + i); evt->tcp_options_raw[i] = opt_kind; evt->tcp_options_len = i + 1; /* NOP : 1 octet */ if (opt_kind == 1) continue; /* EOL : fin des options */ if (opt_kind == 0) break; /* Option avec longueur */ if ((void *)(opts_ptr + i + 2) > data_end) break; __u8 opt_len; bpf_probe_read_kernel(&opt_len, 1, opts_ptr + i + 1); if (opt_len < 2) break; /* MSS (option 2) : 4 octets au total */ if (opt_kind == 2 && opt_len == 4) { if ((void *)(opts_ptr + i + 4) > data_end) break; __u16 mss_val; bpf_probe_read_kernel(&mss_val, 2, opts_ptr + i + 2); evt->mss = bpf_ntohs(mss_val); } /* Window Scale (option 3) : 3 octets au total */ if (opt_kind == 3 && opt_len == 3) { if ((void *)(opts_ptr + i + 3) > data_end) break; __u8 wscale; bpf_probe_read_kernel(&wscale, 1, opts_ptr + i + 2); evt->window_scale = wscale; } /* Avancer au-delà de cette option */ i += opt_len - 1; } bpf_ringbuf_submit(evt, 0); } /* --- Détection TLS ClientHello (port 443) --- */ if (dst_port == HTTPS_PORT) { __u8 *tcp_payload = (__u8 *)tcp + (data_off * 4); /* Vérifier qu'il y a au moins 6 octets pour l'en-tête TLS record + type hello */ if ((void *)(tcp_payload + 6) > data_end) return TC_ACT_OK; __u8 content_type, msg_type; bpf_probe_read_kernel(&content_type, 1, tcp_payload); bpf_probe_read_kernel(&msg_type, 1, tcp_payload + 5); /* Vérifier : Handshake (0x16) + ClientHello (0x01) */ if (content_type == TLS_CONTENT_HANDSHAKE && msg_type == TLS_MSG_CLIENT_HELLO) { 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(); /* Calculer la longueur de payload disponible */ __u32 avail = (__u32)(data_end - (void *)tcp_payload); if (avail > MAX_TLS_PAYLOAD) avail = MAX_TLS_PAYLOAD; tls_evt->payload_len = (__u16)avail; /* Copier le payload TLS (borné à MAX_TLS_PAYLOAD) */ bpf_probe_read_kernel(tls_evt->payload, avail & (MAX_TLS_PAYLOAD - 1), tcp_payload); bpf_ringbuf_submit(tls_evt, 0); } } /* --- Détection payload HTTP en clair (port 80 / 8080) --- */ if (dst_port == HTTP_PORT || dst_port == HTTP_ALT_PORT) { /* Ignorer SYN, FIN, RST : on ne veut que les segments de données */ if (tcp_flags & (TH_SYN | TH_FIN | TH_RST)) return TC_ACT_OK; /* Calculer l'offset du payload TCP dans le paquet */ __u32 payload_off = ETH_HLEN + (ihl * 4) + (data_off * 4); /* Vérifier qu'il y a un payload non vide */ if (skb->len <= payload_off) return TC_ACT_OK; __u32 avail = skb->len - payload_off; if (avail > 4096) avail = 4096; /* Réserver une entrée dans le ring buffer HTTP en clair */ 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->timestamp_ns = bpf_ktime_get_ns(); /* Copier le payload depuis le skb linéaire via bpf_skb_load_bytes. * La longueur est masquée pour satisfaire le vérificateur eBPF. */ __u32 copy_len = avail & (4096 - 1); if (copy_len == 0) copy_len = 1; /* cas avail == 4096 exactement */ h_evt->payload_len = (__u16)avail; if (bpf_skb_load_bytes(skb, payload_off, h_evt->payload, copy_len) < 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";