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:
@ -54,7 +54,9 @@ struct tcp_syn_event {
|
||||
struct tls_hello_event {
|
||||
__u8 payload[2048]; /* payload ClientHello brut (offset 0) */
|
||||
__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 dst_port; /* port destination (host byte order) */
|
||||
__u16 payload_len; /* longueur effective du payload */
|
||||
__u64 timestamp_ns; /* horodatage kernel */
|
||||
} __attribute__((packed));
|
||||
|
||||
@ -209,12 +209,16 @@ int capture_tc(struct __sk_buff *ctx)
|
||||
return TC_ACT_OK;
|
||||
|
||||
tls_evt->src_ip = 0;
|
||||
tls_evt->dst_ip = 0;
|
||||
tls_evt->src_port = 0;
|
||||
tls_evt->dst_port = 0;
|
||||
tls_evt->payload_len = 0;
|
||||
tls_evt->timestamp_ns = 0;
|
||||
|
||||
tls_evt->src_ip = bpf_ntohl(src_ip);
|
||||
tls_evt->dst_ip = bpf_ntohl(dst_ip);
|
||||
tls_evt->src_port = src_port;
|
||||
tls_evt->dst_port = dst_port;
|
||||
tls_evt->timestamp_ns = bpf_ktime_get_ns();
|
||||
|
||||
/* Copie via bpf_skb_load_bytes avec tailles constantes en cascade.
|
||||
|
||||
@ -394,16 +394,18 @@ func consumeTLSEvents(ctx context.Context, rd *perf.Reader, mgr *correlation.Man
|
||||
}
|
||||
|
||||
// struct tls_hello_event (packed):
|
||||
// src_ip(4) + src_port(2) + payload[2048] + payload_len(2) + timestamp_ns(8)
|
||||
// offsets: 0 4 6 2054 2056
|
||||
if len(record.RawSample) < 2064 {
|
||||
// payload[2048] + src_ip(4) + dst_ip(4) + src_port(2) + dst_port(2) + payload_len(2) + timestamp_ns(8)
|
||||
// offsets: 0 2048 2052 2056 2058 2060 2062
|
||||
if len(record.RawSample) < 2070 {
|
||||
continue
|
||||
}
|
||||
data := record.RawSample
|
||||
|
||||
srcIPRaw := binary.LittleEndian.Uint32(data[2048:2052])
|
||||
srcPort := binary.LittleEndian.Uint16(data[2052:2054])
|
||||
payloadLen := binary.LittleEndian.Uint16(data[2054:2056])
|
||||
dstIPRaw := binary.LittleEndian.Uint32(data[2052: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 {
|
||||
payloadLen = 2048
|
||||
@ -418,6 +420,12 @@ func consumeTLSEvents(ctx context.Context, rd *perf.Reader, mgr *correlation.Man
|
||||
key.SrcIP[3] = byte(srcIPRaw)
|
||||
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
|
||||
ch, err := parser.ParseClientHello(payload)
|
||||
if err != nil {
|
||||
@ -460,9 +468,12 @@ func consumeTLSEvents(ctx context.Context, rd *perf.Reader, mgr *correlation.Man
|
||||
TLSVersion: tlsVer,
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
// Corréler si L3/L4 est déjà présent
|
||||
if s.L3L4 != nil {
|
||||
_ = s.L3L4 // corrélation implicite par présence des deux champs
|
||||
// Peupler L3/L4 si absent (SYN non capturé, TLS arrivé en premier)
|
||||
if s.L3L4 == nil && dstIPRaw != 0 {
|
||||
s.L3L4 = &correlation.L3L4{
|
||||
DstIP: tlsDstIP,
|
||||
DstPort: dstPort,
|
||||
}
|
||||
}
|
||||
})
|
||||
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)
|
||||
srcIPRaw := binary.LittleEndian.Uint32(data[4096:4100])
|
||||
dstIPRaw := binary.LittleEndian.Uint32(data[4100:4104])
|
||||
srcPort := binary.LittleEndian.Uint16(data[4104:4106])
|
||||
dstPort := binary.LittleEndian.Uint16(data[4106:4108])
|
||||
|
||||
if srcIPRaw == 0 && srcPort == 0 {
|
||||
continue
|
||||
@ -838,6 +851,12 @@ func consumeHTTPPlainEvents(ctx context.Context, rd *perf.Reader, mgr *correlati
|
||||
key.SrcIP[3] = byte(srcIPRaw)
|
||||
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
|
||||
if len(data) < 4110 {
|
||||
continue
|
||||
@ -872,8 +891,13 @@ func consumeHTTPPlainEvents(ctx context.Context, rd *perf.Reader, mgr *correlati
|
||||
HeaderKV: req.HeaderKV,
|
||||
HTTPVersion: req.Protocol,
|
||||
})
|
||||
// Corréler si L3/L4 est déjà présent (TCP SYN capturé)
|
||||
_ = s.L3L4 // corrélation implicite
|
||||
// Peupler L3/L4 si absent (SYN non capturé)
|
||||
if s.L3L4 == nil && dstIPRaw != 0 {
|
||||
s.L3L4 = &correlation.L3L4{
|
||||
DstIP: httpDstIP,
|
||||
DstPort: dstPort,
|
||||
}
|
||||
}
|
||||
})
|
||||
counter.Add(1)
|
||||
}
|
||||
|
||||
@ -61,7 +61,9 @@ type Ja4SslSslReadArgs struct {
|
||||
type Ja4SslTlsHelloEvent struct {
|
||||
Payload [2048]uint8
|
||||
SrcIp uint32
|
||||
DstIp uint32
|
||||
SrcPort uint16
|
||||
DstPort uint16
|
||||
PayloadLen uint16
|
||||
TimestampNs uint64
|
||||
}
|
||||
|
||||
@ -61,7 +61,9 @@ type Ja4TcSslReadArgs struct {
|
||||
type Ja4TcTlsHelloEvent struct {
|
||||
Payload [2048]uint8
|
||||
SrcIp uint32
|
||||
DstIp uint32
|
||||
SrcPort uint16
|
||||
DstPort uint16
|
||||
PayloadLen uint16
|
||||
TimestampNs uint64
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user