# ja4ebpf — Agent eBPF CO-RE

## Description

`ja4ebpf` est l'agent de collecte de données de la plateforme ja4-platform. C'est un binaire Go unique qui utilise eBPF CO-RE (Compile Once — Run Everywhere) pour observer passivement le trafic réseau d'un serveur Linux, sans modifier ni interrompre le serveur web cible (Apache, Nginx, Varnish, HAProxy, ou tout processus utilisant OpenSSL/BoringSSL).

Il capture simultanément les métadonnées réseau L3/L4 (TCP SYN), les paramètres TLS L5 (ClientHello), et le contenu applicatif L7 (requêtes HTTP déchiffrées), corr le tout en mémoire par la clé `src_ip:src_port`, et envoie les données vers ClickHouse en batch.

## Architecture interne

```
  +-----------------+    TC ingress (XDP/TC)     +--------------------+
  |  réseau entrant  |-------------------------->|  bpf/tc_capture.c  |
  |                 |                            |                    |
  |  SYN packet     |  --> rb_tcp_syn  (16 MB)  |  Programme eBPF    |
  |  TLS ClientHello|  --> rb_tls_hello (16 MB) |  CO-RE             |
  |  HTTP port 80   |  --> rb_http_plain (32 MB)|                    |
  +-----------------+                            +--------------------+
                                                           |
  +-----------------+    uprobe SSL_read          +--------------------+
  |  serveur web    |-------------------------->  |  bpf/uprobe_ssl.c  |
  |  (OpenSSL)      |                            |                    |
  |  flux déchiffré |  --> rb_ssl_data  (64 MB) |  Programme eBPF    |
  +-----------------+                            |  CO-RE             |
                                                 +--------------------+
                                                           |
                                               +-----------v-----------+
                                               |     Go userspace       |
                                               |                        |
                                               |  internal/loader/      |
                                               |  - RingBuffer readers  |
                                               |  - 5 goroutines        |
                                               |                        |
                                               |  internal/parser/      |
                                               |  - JA4 calculator      |
                                               |  - H2 preface parser   |
                                               |  - HTTP/1.1 parser     |
                                               |                        |
                                               |  internal/dispatcher/  |
                                               |  - Magic Bytes router  |
                                               |                        |
                                               |  internal/correlation/ |
                                               |  - 256-shard manager   |
                                               |  - GC 100ms            |
                                               |  - timeout 500ms       |
                                               |                        |
                                               |  internal/writer/      |
                                               |  - ClickHouse batch    |
                                               +----------+-------------+
                                                          |
                                               INSERT batch TCP :9000
                                                          |
                                                          v
                                               +----------------------+
                                               |  ja4_logs.           |
                                               |  http_logs_raw       |
                                               +----------------------+
```

## Hooks eBPF

### TC ingress — Couches L3/L4/L5

Le programme `bpf/tc_capture.c` est attaché à l'interface réseau via **TC (Traffic Control)** en ingress. Il s'exécute pour chaque paquet entrant :

**Paquets TCP SYN** : extraction des options TCP et métadonnées IP depuis les headers du paquet.
- `bpf_skb_load_bytes()` pour lire les options TCP depuis le skb
- Envoyé dans le RingBuffer `rb_tcp_syn` (16 MB)

**ClientHello TLS** : détection du type 0x16 (Handshake) et sous-type 0x01 (ClientHello).
- `bpf_skb_load_bytes()` pour capturer 512 octets du payload
- Envoyé dans le RingBuffer `rb_tls_hello` (16 MB)

**HTTP en clair (port 80/8080)** : pour les connexions non chiffrées.
- SYN/FIN/RST exclus (uniquement les segments porteurs de données)
- Jusqu'à 4096 octets via `bpf_skb_load_bytes()`
- Envoyé dans le RingBuffer `rb_http_plain` (32 MB)

### Uprobe SSL_read — Couche L7

Les **uprobes** s'attachent dynamiquement à `SSL_read` (ou `SSL_read_ex`) dans le processus du serveur web. Elles s'exécutent *après* le déchiffrement TLS, capturant le flux HTTP en clair dans `rb_ssl_data` (64 MB).

La clé de corrélation `src_ip:src_port` est extraite depuis la structure `SSL*` → file descriptor → socket noyau via `bpf_probe_read_kernel()`.

### Kprobe accept4

Un kprobe sur `accept4` peuple `rb_accept` (4 MB) avec le tuple `(src_ip, src_port, fd, pid_tgid)`, permettant d'associer chaque fd SSL à une connexion TCP.

## Corrélation in-memory

Le gestionnaire `internal/correlation` maintient un état par connexion :

| Mécanisme | Valeur |
|-----------|--------|
| Sharding | 256 buckets (hash src_ip:src_port) |
| Timeout orphelin | 500 ms (→ `correlated=0`) |
| Détection Slowloris | 10 s (export partiel) |
| GC interval | 100 ms |
| Keep-Alive | max_keepalives incrémenté par requête |

### Routeur Magic Bytes (dispatcher)

```
Buffer reçu (SSL data ou HTTP plain)
    |
    +-- starts with "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" --> ProtoHTTP2
    |
    +-- starts with "GET " / "POST " / "PUT " / ...      --> ProtoHTTP1
    |
    +-- partial H2 preface prefix                         --> ProtoHTTP2 (buffer)
    |
    +-- autre                                              --> ProtoUnknown (ignoré)
```

**Fingerprinting HTTP/2 passif** : après détection du preface `PRI * HTTP/2.0...`, le parser itère les frames :
- Frame `SETTINGS` (type `0x04`) : extraction des 7 paramètres, valeur `-1` si absent
- Frame `WINDOW_UPDATE` (type `0x08`, stream 0) : incrément de fenêtre connexion
- Frame `HEADERS` (type `0x01`) : ordre des pseudo-headers (`m,a,s,p`)

## Données collectées

### L3/L4 (TCP SYN)

| Champ | Description |
|-------|-------------|
| `src_ip`, `src_port` | Clé de corrélation |
| `ttl` | Time To Live initial |
| `df_bit` | Don't Fragment bit |
| `ip_id` | IP Identification (0 = Linux/VPN/spoofé) |
| `window_size` | Taille fenêtre TCP SYN |
| `window_scale` | Option Window Scale (RFC 1323) |
| `mss` | Maximum Segment Size |
| `tcp_options` | Options TCP brutes (40 octets max) |
| `tcp_jitter_variance` | Variance jitter inter-SYN |
| `syn_timing_cv` | Délai SYN→ClientHello (ns) |

### L5 (TLS ClientHello)

| Champ | Description |
|-------|-------------|
| `tls_version` | Version TLS |
| `ciphers` | Liste suites cryptographiques |
| `extensions` | Liste extensions TLS |
| `elliptic_curves` | Courbes elliptiques supportées |
| `point_formats` | Formats de points EC |
| `alpn` | ALPN list (h2, http/1.1, ...) |
| `sni` | Server Name Indication |
| `ja4` | Empreinte JA4 calculée Go-side |
| `ja4t` | Empreinte JA4T (TCP) calculée Go-side |

### L7 HTTP/1.1

| Champ | Description |
|-------|-------------|
| `method` | Méthode HTTP |
| `path` | Chemin |
| `query_string` | Paramètres query |
| `http_version` | HTTP/1.0 ou HTTP/1.1 |
| `headers_raw` | En-têtes dans leur ordre d'émission |
| `header_order_signature` | Hash de l'ordre |
| `status_code` | Code de statut |
| `response_size` | Taille réponse (octets) |
| `duration_ms` | Durée requête |
| `timestamp_ns` | Horodatage ns absolu |

### L7 HTTP/2 (preface client)

| Champ | Description |
|-------|-------------|
| `h2_header_table_size` | SETTINGS ID 1 (-1 si absent) |
| `h2_enable_push` | SETTINGS ID 2 |
| `h2_max_concurrent_streams` | SETTINGS ID 3 |
| `h2_initial_window_size` | SETTINGS ID 4 |
| `h2_max_frame_size` | SETTINGS ID 5 |
| `h2_max_header_list_size` | SETTINGS ID 6 |
| `h2_enable_connect_protocol` | SETTINGS ID 8 (RFC 8441) |
| `h2_window_update` | Incrément WINDOW_UPDATE connexion |
| `h2_has_priority` | Flag PRIORITY dans HEADERS frame |
| `h2_pseudo_order` | Ordre pseudo-headers (ex: `m,a,s,p`) |

## eBPF CO-RE

| Aspect | Détail |
|--------|--------|
| Compilateur | `clang` + `llvm` |
| Target | `bpf` (architecture BPF 64 bits) |
| BTF source | `/sys/kernel/btf/vmlinux` (disponible RHEL 8+) |
| Relocations | `cilium/ebpf` résout automatiquement les offsets struct |
| Embed | `go:generate` génère `bpf_bpfel.go` avec bytecode embarqué |
| Compatibilité | Rocky/RHEL Linux 8, 9, 10 (kernel 4.18+) |

## Configuration

```yaml
# /etc/ja4ebpf/config.yml
interface: eth0
target_binary: /usr/sbin/httpd

clickhouse:
  dsn: clickhouse://data_writer:pwd@localhost:9000/ja4_logs
  table: http_logs_raw
  batch_size: 500
  flush_interval_ms: 200

correlation:
  session_timeout_ms: 500
  slowloris_threshold_s: 10
  gc_interval_ms: 100
```

### Variables d'environnement

| Variable | Défaut | Description |
|----------|--------|-------------|
| `JA4EBPF_INTERFACE` | `eth0` | Interface réseau |
| `JA4EBPF_TARGET_BINARY` | `/usr/sbin/httpd` | Binaire à hooker (uprobe SSL_read) |
| `JA4EBPF_CLICKHOUSE_DSN` | — | DSN ClickHouse |
| `JA4EBPF_BATCH_SIZE` | `500` | Taille des batchs d'insertion |
| `JA4EBPF_FLUSH_INTERVAL_MS` | `200` | Intervalle de flush (ms) |
| `JA4EBPF_SESSION_TIMEOUT_MS` | `500` | Timeout session orpheline |
| `JA4EBPF_SLOWLORIS_THRESHOLD_S` | `10` | Seuil détection Slowloris (s) |

## Build

```bash
# Build complet (bytecode eBPF + binaire Go) — Docker Rocky Linux
make build-ja4ebpf

# Tests unitaires (nécessite NET_RAW/NET_ADMIN/BPF capabilities)
make test-ja4ebpf

# Build RPMs el8/el9/el10
make rpm-ja4ebpf
# → services/ja4ebpf/dist/rpm/el{8,9,10}/
```

## Structure du code

```
services/ja4ebpf/
├── bpf/
│   ├── bpf_types.h            # Structs C partagées + déclarations maps eBPF
│   ├── tc_capture.c           # Programme TC ingress (L3/L4/L5 + HTTP plain)
│   └── uprobe_ssl.c           # Programme uprobe SSL_read (L7 déchiffré)
├── cmd/ja4ebpf/
│   └── main.go                # Point d'entrée : 5 goroutines consumer
├── internal/
│   ├── loader/
│   │   └── loader.go          # Chargement eBPF + RingBuffer readers + désérialisation
│   ├── parser/
│   │   ├── ja4.go             # Calcul empreintes JA4 / JA4T
│   │   ├── http2.go           # Parser HTTP/2 preface (SETTINGS, WINDOW_UPDATE, HEADERS)
│   │   └── http1.go           # Parser HTTP/1.1
│   ├── dispatcher/
│   │   └── dispatcher.go      # Routeur Magic Bytes (ProtoHTTP1/2/Unknown)
│   ├── correlation/
│   │   └── manager.go         # Gestionnaire sessions 256-shard
│   └── writer/
│       └── writer.go          # Writer ClickHouse (batch + retry)
├── packaging/
│   ├── rpm/ja4ebpf.spec       # Spec RPM (el8/el9/el10)
│   └── systemd/ja4ebpf.service # Unit systemd
├── Dockerfile                 # Image de production
├── Dockerfile.tests           # Image de tests
├── Dockerfile.package         # Build RPM multi-distro (5 stages)
└── Makefile
```

## Capabilities Linux requises (SELinux Enforcing)

L'agent tourne sous l'utilisateur `ja4ebpf` (UID/GID 490 fixe). Les capabilities Linux accordées via `AmbientCapabilities` :

| Capability | Raison |
|------------|--------|
| `CAP_BPF` | Chargement des programmes eBPF (kernel 5.8+) |
| `CAP_SYS_ADMIN` | Uprobes + RHEL 8 (kernel 4.18, pré-CAP_BPF) |
| `CAP_NET_ADMIN` | Attachement hooks TC ingress |
| `CAP_PERFMON` | Accès perf events pour les uprobes |

`LimitMEMLOCK=infinity` est requis pour le `mlock()` des maps eBPF.

## Tests d'intégration

Stacks Docker Compose testant l'agent contre différents serveurs web :

```bash
make test-integration     # Apache httpd (référence)
make test-nginx           # Nginx
make test-nginx-varnish   # Nginx + Varnish (reverse proxy)
make test-hitch-varnish   # Hitch (TLS) + Varnish
make test-all-stacks      # Les 4 stacks en séquence
```
