feat(ja4ebpf): add dst_ip/dst_port to TLS and HTTP plain events for complete L3/L4

Add dst_ip and dst_port fields to tls_hello_event BPF struct and populate
them in tc_capture.c. Update Go TLS event handler with new byte offsets
(payload[2048]+src_ip(4)+dst_ip(4)+src_port(2)+dst_port(2)+payload_len(2)+
timestamp_ns(8) = 2070 bytes). Read dst_ip/dst_port from HTTP plain events
and use them to populate L3L4 when SYN was not captured, ensuring dst_ip
and dst_port are always available in ClickHouse for both TLS and HTTP sessions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jacquin Antoine
2026-04-15 14:31:46 +02:00
parent 65d833bb18
commit 0975d40609
5 changed files with 44 additions and 10 deletions

View File

@ -54,7 +54,9 @@ struct tcp_syn_event {
struct tls_hello_event { struct tls_hello_event {
__u8 payload[2048]; /* payload ClientHello brut (offset 0) */ __u8 payload[2048]; /* payload ClientHello brut (offset 0) */
__u32 src_ip; /* adresse source (host byte order) */ __u32 src_ip; /* adresse source (host byte order) */
__u32 dst_ip; /* adresse destination (host byte order) */
__u16 src_port; /* port source (host byte order) */ __u16 src_port; /* port source (host byte order) */
__u16 dst_port; /* port destination (host byte order) */
__u16 payload_len; /* longueur effective du payload */ __u16 payload_len; /* longueur effective du payload */
__u64 timestamp_ns; /* horodatage kernel */ __u64 timestamp_ns; /* horodatage kernel */
} __attribute__((packed)); } __attribute__((packed));

View File

@ -209,12 +209,16 @@ int capture_tc(struct __sk_buff *ctx)
return TC_ACT_OK; return TC_ACT_OK;
tls_evt->src_ip = 0; tls_evt->src_ip = 0;
tls_evt->dst_ip = 0;
tls_evt->src_port = 0; tls_evt->src_port = 0;
tls_evt->dst_port = 0;
tls_evt->payload_len = 0; tls_evt->payload_len = 0;
tls_evt->timestamp_ns = 0; tls_evt->timestamp_ns = 0;
tls_evt->src_ip = bpf_ntohl(src_ip); tls_evt->src_ip = bpf_ntohl(src_ip);
tls_evt->dst_ip = bpf_ntohl(dst_ip);
tls_evt->src_port = src_port; tls_evt->src_port = src_port;
tls_evt->dst_port = dst_port;
tls_evt->timestamp_ns = bpf_ktime_get_ns(); tls_evt->timestamp_ns = bpf_ktime_get_ns();
/* Copie via bpf_skb_load_bytes avec tailles constantes en cascade. /* Copie via bpf_skb_load_bytes avec tailles constantes en cascade.

View File

@ -394,16 +394,18 @@ func consumeTLSEvents(ctx context.Context, rd *perf.Reader, mgr *correlation.Man
} }
// struct tls_hello_event (packed): // struct tls_hello_event (packed):
// src_ip(4) + src_port(2) + payload[2048] + payload_len(2) + timestamp_ns(8) // payload[2048] + src_ip(4) + dst_ip(4) + src_port(2) + dst_port(2) + payload_len(2) + timestamp_ns(8)
// offsets: 0 4 6 2054 2056 // offsets: 0 2048 2052 2056 2058 2060 2062
if len(record.RawSample) < 2064 { if len(record.RawSample) < 2070 {
continue continue
} }
data := record.RawSample data := record.RawSample
srcIPRaw := binary.LittleEndian.Uint32(data[2048:2052]) srcIPRaw := binary.LittleEndian.Uint32(data[2048:2052])
srcPort := binary.LittleEndian.Uint16(data[2052:2054]) dstIPRaw := binary.LittleEndian.Uint32(data[2052:2056])
payloadLen := binary.LittleEndian.Uint16(data[2054:2056]) srcPort := binary.LittleEndian.Uint16(data[2056:2058])
dstPort := binary.LittleEndian.Uint16(data[2058:2060])
payloadLen := binary.LittleEndian.Uint16(data[2060:2062])
if int(payloadLen) > 2048 { if int(payloadLen) > 2048 {
payloadLen = 2048 payloadLen = 2048
@ -418,6 +420,12 @@ func consumeTLSEvents(ctx context.Context, rd *perf.Reader, mgr *correlation.Man
key.SrcIP[3] = byte(srcIPRaw) key.SrcIP[3] = byte(srcIPRaw)
key.SrcPort = srcPort key.SrcPort = srcPort
var tlsDstIP [4]byte
tlsDstIP[0] = byte(dstIPRaw >> 24)
tlsDstIP[1] = byte(dstIPRaw >> 16)
tlsDstIP[2] = byte(dstIPRaw >> 8)
tlsDstIP[3] = byte(dstIPRaw)
// Parser le ClientHello et calculer JA4 // Parser le ClientHello et calculer JA4
ch, err := parser.ParseClientHello(payload) ch, err := parser.ParseClientHello(payload)
if err != nil { if err != nil {
@ -460,9 +468,12 @@ func consumeTLSEvents(ctx context.Context, rd *perf.Reader, mgr *correlation.Man
TLSVersion: tlsVer, TLSVersion: tlsVer,
Timestamp: time.Now(), Timestamp: time.Now(),
} }
// Corréler si L3/L4 est déjà présent // Peupler L3/L4 si absent (SYN non capturé, TLS arrivé en premier)
if s.L3L4 != nil { if s.L3L4 == nil && dstIPRaw != 0 {
_ = s.L3L4 // corrélation implicite par présence des deux champs s.L3L4 = &correlation.L3L4{
DstIP: tlsDstIP,
DstPort: dstPort,
}
} }
}) })
counter.Add(1) counter.Add(1)
@ -825,7 +836,9 @@ func consumeHTTPPlainEvents(ctx context.Context, rd *perf.Reader, mgr *correlati
// src_ip et src_port en host byte order (bpf_ntohl appliqué dans tc_capture.c) // src_ip et src_port en host byte order (bpf_ntohl appliqué dans tc_capture.c)
srcIPRaw := binary.LittleEndian.Uint32(data[4096:4100]) srcIPRaw := binary.LittleEndian.Uint32(data[4096:4100])
dstIPRaw := binary.LittleEndian.Uint32(data[4100:4104])
srcPort := binary.LittleEndian.Uint16(data[4104:4106]) srcPort := binary.LittleEndian.Uint16(data[4104:4106])
dstPort := binary.LittleEndian.Uint16(data[4106:4108])
if srcIPRaw == 0 && srcPort == 0 { if srcIPRaw == 0 && srcPort == 0 {
continue continue
@ -838,6 +851,12 @@ func consumeHTTPPlainEvents(ctx context.Context, rd *perf.Reader, mgr *correlati
key.SrcIP[3] = byte(srcIPRaw) key.SrcIP[3] = byte(srcIPRaw)
key.SrcPort = srcPort key.SrcPort = srcPort
var httpDstIP [4]byte
httpDstIP[0] = byte(dstIPRaw >> 24)
httpDstIP[1] = byte(dstIPRaw >> 16)
httpDstIP[2] = byte(dstIPRaw >> 8)
httpDstIP[3] = byte(dstIPRaw)
// Extraire le payload HTTP // Extraire le payload HTTP
if len(data) < 4110 { if len(data) < 4110 {
continue continue
@ -872,8 +891,13 @@ func consumeHTTPPlainEvents(ctx context.Context, rd *perf.Reader, mgr *correlati
HeaderKV: req.HeaderKV, HeaderKV: req.HeaderKV,
HTTPVersion: req.Protocol, HTTPVersion: req.Protocol,
}) })
// Corréler si L3/L4 est déjà présent (TCP SYN capturé) // Peupler L3/L4 si absent (SYN non capturé)
_ = s.L3L4 // corrélation implicite if s.L3L4 == nil && dstIPRaw != 0 {
s.L3L4 = &correlation.L3L4{
DstIP: httpDstIP,
DstPort: dstPort,
}
}
}) })
counter.Add(1) counter.Add(1)
} }

View File

@ -61,7 +61,9 @@ type Ja4SslSslReadArgs struct {
type Ja4SslTlsHelloEvent struct { type Ja4SslTlsHelloEvent struct {
Payload [2048]uint8 Payload [2048]uint8
SrcIp uint32 SrcIp uint32
DstIp uint32
SrcPort uint16 SrcPort uint16
DstPort uint16
PayloadLen uint16 PayloadLen uint16
TimestampNs uint64 TimestampNs uint64
} }

View File

@ -61,7 +61,9 @@ type Ja4TcSslReadArgs struct {
type Ja4TcTlsHelloEvent struct { type Ja4TcTlsHelloEvent struct {
Payload [2048]uint8 Payload [2048]uint8
SrcIp uint32 SrcIp uint32
DstIp uint32
SrcPort uint16 SrcPort uint16
DstPort uint16
PayloadLen uint16 PayloadLen uint16
TimestampNs uint64 TimestampNs uint64
} }