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 {
|
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));
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user