/* ============================================================================ * tc_capture.c — Programme TC ingress : capture des TCP SYN, TLS ClientHello * et HTTP en clair * * Hook TC ingress (clsact qdisc) compatible kernel 4.1+. * Émet via bpf_perf_event_output() (kernel 4.4+) pour compatibilité maximale. * * IMPORTANT : Ce programme n'utilise AUCUN accès direct au paquet (data/data_end). * Toutes les lectures se font via bpf_skb_load_bytes() (kernel 4.5+) avec des * tailles constantes, pour compatibilité avec le vérificateur kernel 4.18 qui * rejette "math between pkt pointer and register with unbounded min value". * * Les copies de payload utilisent bpf_skb_load_bytes() avec &= (2^n - 1) * pour borner la taille per le vérificateur. * Les structs > 512o utilisent un PERCPU_ARRAY temporaire (stack limit eBPF). * ============================================================================ */ #include "vmlinux.h" #include #include #include #include "bpf_types.h" /* Constantes */ #define ETH_P_IP 0x0800 #define ETH_HLEN 14 #define IPPROTO_TCP 6 #define IP_DF 0x4000 #define TH_SYN 0x02 #define TH_ACK 0x10 #define TH_FIN 0x01 #define TH_RST 0x04 #define HTTPS_PORT 443 #define HTTP_PORT 80 #define HTTP_ALT_PORT 8080 #define TLS_CONTENT_HANDSHAKE 0x16 #define TLS_MSG_CLIENT_HELLO 0x01 #define MAX_TLS_PAYLOAD 2048 #define MAX_HTTP_PAYLOAD 1024 #define MAX_TCP_OPTIONS 40 /* Counter map for debug */ struct { __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); __uint(max_entries, 7); __type(key, __u32); __type(value, __u64); } tc_stats SEC(".maps"); #define STAT_TOTAL 0 #define STAT_IPV4 1 #define STAT_TCP 2 #define STAT_SYN 3 #define STAT_SYN_SUBMIT 4 #define STAT_TLS_SUBMIT 5 #define STAT_HTTP_SUBMIT 6 /* --------------------------------------------------------------------------- * capture_tc — Point d'entrée TC ingress (clsact) * * AUCUN accès direct au paquet. Tout via bpf_skb_load_bytes() + tailles constantes. * Compatible vérificateur kernel 4.18. * ---------------------------------------------------------------------------*/ SEC("tc") int capture_tc(struct __sk_buff *ctx) { __u32 key; __u64 *cnt; __u32 pkt_len = ctx->len; key = STAT_TOTAL; cnt = bpf_map_lookup_elem(&tc_stats, &key); if (cnt) (*cnt)++; /* --- Ethernet : vérifier type IPv4 --- */ if (pkt_len < ETH_HLEN + 20 + 20) return TC_ACT_OK; __be16 h_proto; bpf_skb_load_bytes(ctx, 12, &h_proto, 2); if (h_proto != bpf_htons(ETH_P_IP)) return TC_ACT_OK; /* --- IPv4 : lire le header (20 octets min) --- */ key = STAT_IPV4; cnt = bpf_map_lookup_elem(&tc_stats, &key); if (cnt) (*cnt)++; struct iphdr iph; bpf_skb_load_bytes(ctx, ETH_HLEN, &iph, sizeof(iph)); if (iph.protocol != IPPROTO_TCP) return TC_ACT_OK; __u32 ihl = iph.ihl & 0x0F; if (ihl < 5 || ihl > 15) return TC_ACT_OK; __u32 ip_hlen = ihl << 2; if (ip_hlen < 20 || ip_hlen > 60) return TC_ACT_OK; __u32 src_ip = iph.saddr; __u32 dst_ip = iph.daddr; __u8 ttl = iph.ttl; __u16 ip_id = bpf_ntohs(iph.id); __u16 frag_off = bpf_ntohs(iph.frag_off); __u8 df_bit = (frag_off & IP_DF) ? 1 : 0; /* --- TCP : lire le header (20 octets) --- */ __u32 tcp_off = ETH_HLEN + ip_hlen; if (pkt_len < tcp_off + 20) return TC_ACT_OK; key = STAT_TCP; cnt = bpf_map_lookup_elem(&tc_stats, &key); if (cnt) (*cnt)++; struct tcphdr tcph; bpf_skb_load_bytes(ctx, tcp_off, &tcph, sizeof(tcph)); __u16 src_port = bpf_ntohs(tcph.source); __u16 dst_port = bpf_ntohs(tcph.dest); __u16 window = bpf_ntohs(tcph.window); __u8 tcp_flags = 0; if (tcph.syn) tcp_flags |= TH_SYN; if (tcph.ack) tcp_flags |= TH_ACK; if (tcph.fin) tcp_flags |= TH_FIN; if (tcph.rst) tcp_flags |= TH_RST; __u32 doff = tcph.doff; if (doff < 5 || doff > 15) return TC_ACT_OK; __u32 tcp_hlen = doff << 2; if (tcp_hlen < 20 || tcp_hlen > 60) return TC_ACT_OK; __u32 payload_off = ETH_HLEN + ip_hlen + tcp_hlen; /* =================================================================== * TCP SYN * ===================================================================*/ if ((tcp_flags & TH_SYN) && !(tcp_flags & TH_ACK)) { key = STAT_SYN; cnt = bpf_map_lookup_elem(&tc_stats, &key); if (cnt) (*cnt)++; struct tcp_syn_event evt = {}; 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; evt.mss = 0; evt.timestamp_ns = bpf_ktime_get_ns(); evt.tcp_options_len = 0; /* Copie des options TCP via bpf_skb_load_bytes avec taille constante. * On lit MAX_TCP_OPTIONS=40 octets depuis le début des options. * Si le paquet est trop court, l'appel échoue → options absentes. */ __u32 opts_off = tcp_off + 20; __u32 opts_len = tcp_hlen - 20; if (opts_len > 0 && opts_len <= MAX_TCP_OPTIONS && opts_off + MAX_TCP_OPTIONS <= pkt_len) { bpf_skb_load_bytes(ctx, opts_off, evt.tcp_options_raw, MAX_TCP_OPTIONS); evt.tcp_options_len = (__u8)opts_len; } bpf_perf_event_output(ctx, &pb_tcp_syn, BPF_F_CURRENT_CPU, &evt, sizeof(evt)); key = STAT_SYN_SUBMIT; cnt = bpf_map_lookup_elem(&tc_stats, &key); if (cnt) (*cnt)++; } /* =================================================================== * TLS ClientHello (port 443) * ===================================================================*/ if (dst_port == HTTPS_PORT) { /* Lire les 6 premiers octets du payload pour vérifier le type TLS */ if (payload_off + 6 > pkt_len) return TC_ACT_OK; __u8 tls_hdr[6]; bpf_skb_load_bytes(ctx, payload_off, tls_hdr, 6); if (tls_hdr[0] != TLS_CONTENT_HANDSHAKE || tls_hdr[5] != TLS_MSG_CLIENT_HELLO) return TC_ACT_OK; /* Avail via pkt_len (scalaire pur) */ __u32 avail = 0; if (pkt_len > payload_off) { avail = pkt_len - payload_off; if (avail > MAX_TLS_PAYLOAD) avail = MAX_TLS_PAYLOAD; } if (avail == 0) return TC_ACT_OK; __u32 zero = 0; struct tls_hello_event *tls_evt = bpf_map_lookup_elem(&__tls_buf, &zero); if (!tls_evt) return TC_ACT_OK; tls_evt->src_ip = 0; tls_evt->src_port = 0; tls_evt->payload_len = 0; tls_evt->timestamp_ns = 0; 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 via bpf_skb_load_bytes avec taille constante 256. * Kernel 4.18 ne supporte pas les tailles variables vers map values. * 256 octets capture le ClientHello dans la majorité des cas. */ if (bpf_skb_load_bytes(ctx, payload_off, tls_evt, 256)) return TC_ACT_OK; bpf_perf_event_output(ctx, &pb_tls_hello, BPF_F_CURRENT_CPU, tls_evt, sizeof(*tls_evt)); key = STAT_TLS_SUBMIT; cnt = bpf_map_lookup_elem(&tc_stats, &key); if (cnt) (*cnt)++; return TC_ACT_OK; } /* =================================================================== * HTTP en clair (port 80 / 8080) * ===================================================================*/ if (dst_port == HTTP_PORT || dst_port == HTTP_ALT_PORT) { if (tcp_flags & (TH_SYN | TH_FIN | TH_RST)) return TC_ACT_OK; if (payload_off >= pkt_len) return TC_ACT_OK; /* Avail via pkt_len (scalaire pur) */ __u32 avail = 0; if (pkt_len > payload_off) { avail = pkt_len - payload_off; if (avail > MAX_HTTP_PAYLOAD) avail = MAX_HTTP_PAYLOAD; } if (avail == 0) return TC_ACT_OK; __u32 zero = 0; struct http_plain_event *h_evt = bpf_map_lookup_elem(&__http_buf, &zero); if (!h_evt) return TC_ACT_OK; h_evt->src_ip = 0; h_evt->dst_ip = 0; h_evt->src_port = 0; h_evt->dst_port = 0; h_evt->payload_len = 0; h_evt->timestamp_ns = 0; 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; /* Taille constante 256 pour compatibilité vérificateur kernel 4.18 */ if (bpf_skb_load_bytes(ctx, payload_off, h_evt, 256)) return TC_ACT_OK; bpf_perf_event_output(ctx, &pb_http_plain, BPF_F_CURRENT_CPU, h_evt, sizeof(*h_evt)); key = STAT_HTTP_SUBMIT; cnt = bpf_map_lookup_elem(&tc_stats, &key); if (cnt) (*cnt)++; } return TC_ACT_OK; } char LICENSE[] SEC("license") = "GPL";