feat: multi-distro VM tests, ja4ebpf eBPF improvements, bot-detector scoring
ja4ebpf: - Refactor BPF TC capture with improved SYN offset handling and TCP option parsing - Enhance TLS uprobe SSL hooking for better key extraction - Add ClickHouse writer improvements for HTTP log materialized views - Update RPM spec for Rocky Linux 8/9/10, fix systemd service - Simplify loader with cleaner bpf2go integration bot-detector: - Add H2 SETTINGS per-parameter comparison in browser_matcher - Enhance browser signatures and scoring pipeline - Improve preprocessing and cycle detection infra: - Multi-distro Vagrantfile (centos8, rocky9, rocky10) with per-distro provisioning - New Makefile targets: vm-up-all, test-vm-matrix, test-vm-centos8/rocky10 - Add debug helpers and run-test-from-host.sh for host-driven VM testing - Update run-tests-vm.sh for cross-distro compatibility - Remove accidental binary blob (\004) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@ -1,48 +1,90 @@
|
||||
// Package loader initialise les programmes eBPF via cilium/ebpf,
|
||||
// attache les hooks TC ingress et les uprobes SSL, et expose
|
||||
// les readers RingBuffer aux consommateurs Go.
|
||||
// attache le hook TC ingress et les uprobes SSL, et expose
|
||||
// les readers PerfEvent aux consommateurs Go.
|
||||
package loader
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/cilium/ebpf"
|
||||
"github.com/cilium/ebpf/link"
|
||||
"github.com/cilium/ebpf/ringbuf"
|
||||
"github.com/cilium/ebpf/perf"
|
||||
"github.com/cilium/ebpf/rlimit"
|
||||
"github.com/vishvananda/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc clang -target amd64 -cflags "-O2 -g -Wall -D__TARGET_ARCH_x86 -Wno-pass-failed" Ja4Tc ../../bpf/tc_capture.c -- -I../../bpf/headers
|
||||
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc clang -target amd64 -cflags "-O2 -g -Wall -D__TARGET_ARCH_x86 -Wno-pass-failed" Ja4Ssl ../../bpf/uprobe_ssl.c -- -I../../bpf/headers
|
||||
|
||||
// perCPUBufferSize est la taille du buffer perf per-CPU en octets (256 KB).
|
||||
const perCPUBufferSize = 256 * 1024
|
||||
|
||||
// Loader encapsule les objets eBPF compilés, les liens vers les hooks,
|
||||
// et les readers RingBuffer exposés au pipeline de traitement.
|
||||
// et les readers PerfEvent exposés au pipeline de traitement.
|
||||
type Loader struct {
|
||||
tcObjs *Ja4TcObjects // généré par bpf2go (tc_capture.c)
|
||||
sslObjs *Ja4SslObjects // généré par bpf2go (uprobe_ssl.c)
|
||||
tcLink link.Link
|
||||
tcNlLink netlink.Link // interface netlink pour cleanup TC
|
||||
uprobeLinks []link.Link
|
||||
statsMap *ebpf.Map // map tc_stats pour lecture des compteurs BPF (mode debug)
|
||||
|
||||
// SynReader lit les événements TCP SYN depuis rb_tcp_syn.
|
||||
SynReader *ringbuf.Reader
|
||||
// TLSReader lit les événements TLS ClientHello depuis rb_tls_hello.
|
||||
TLSReader *ringbuf.Reader
|
||||
// SSLReader lit les données SSL déchiffrées depuis rb_ssl_data.
|
||||
SSLReader *ringbuf.Reader
|
||||
// AcceptReader lit les événements accept4 depuis rb_accept.
|
||||
AcceptReader *ringbuf.Reader
|
||||
// HTTPPlainReader lit les payloads HTTP en clair depuis rb_http_plain.
|
||||
HTTPPlainReader *ringbuf.Reader
|
||||
// SynReader lit les événements TCP SYN depuis pb_tcp_syn.
|
||||
SynReader *perf.Reader
|
||||
// TLSReader lit les événements TLS ClientHello depuis pb_tls_hello.
|
||||
TLSReader *perf.Reader
|
||||
// SSLReader lit les données SSL déchiffrées depuis pb_ssl_data.
|
||||
SSLReader *perf.Reader
|
||||
// AcceptReader lit les événements accept4 depuis pb_accept.
|
||||
AcceptReader *perf.Reader
|
||||
// HTTPPlainReader lit les payloads HTTP en clair depuis pb_http_plain.
|
||||
HTTPPlainReader *perf.Reader
|
||||
}
|
||||
|
||||
// StatNames associe chaque index de compteur BPF à un nom lisible.
|
||||
var StatNames = map[uint32]string{
|
||||
0: "TOTAL",
|
||||
1: "IPV4",
|
||||
2: "TCP",
|
||||
3: "SYN",
|
||||
4: "SYN_SUBMIT",
|
||||
5: "TLS_SUBMIT",
|
||||
6: "HTTP_SUBMIT",
|
||||
}
|
||||
|
||||
// ReadStats lit les compteurs de la map tc_stats (PERCPU_ARRAY).
|
||||
// Retourne une map[index] → somme de toutes les valeurs CPU.
|
||||
// Si la map n'est pas disponible, retourne une map vide.
|
||||
func (l *Loader) ReadStats() (map[uint32]uint64, error) {
|
||||
result := make(map[uint32]uint64)
|
||||
if l.statsMap == nil {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
for key := uint32(0); key < 7; key++ {
|
||||
var values []uint64
|
||||
if err := l.statsMap.Lookup(key, &values); err != nil {
|
||||
continue
|
||||
}
|
||||
var sum uint64
|
||||
for _, v := range values {
|
||||
sum += v
|
||||
}
|
||||
result[key] = sum
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// New charge le bytecode eBPF embarqué, supprime la limite mémoire
|
||||
// RLIMIT_MEMLOCK (requise pour les ring buffers et les maps eBPF),
|
||||
// RLIMIT_MEMLOCK (requise pour les maps eBPF),
|
||||
// et retourne un Loader prêt à être attaché aux hooks.
|
||||
//
|
||||
// Cible : CentOS 8 / RHEL 8 et supérieur (kernel ≥ 4.18 avec BTF backporté).
|
||||
// Cible : kernel 4.18+ avec BTF. Les perf event arrays sont supportés depuis
|
||||
// kernel 4.4, bpf_skb_load_bytes depuis kernel 4.5, assurant une compatibilité
|
||||
// maximale via le hook TC ingress.
|
||||
// Le BTF natif est détecté automatiquement par cilium/ebpf via
|
||||
// /sys/kernel/btf/vmlinux — aucun fallback manuel n'est requis.
|
||||
func New() (*Loader, error) {
|
||||
@ -57,6 +99,28 @@ func New() (*Loader, error) {
|
||||
return nil, fmt.Errorf("chargement objets TC eBPF: %w", err)
|
||||
}
|
||||
|
||||
// Trouver la map tc_stats par iteration des maps kernel
|
||||
var statsMap *ebpf.Map
|
||||
var mapID ebpf.MapID = 0
|
||||
for {
|
||||
nextID, err := ebpf.MapGetNextID(mapID)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
m, err := ebpf.NewMapFromID(nextID)
|
||||
if err != nil {
|
||||
mapID = nextID
|
||||
continue
|
||||
}
|
||||
info, err := m.Info()
|
||||
if err == nil && info.Name == "tc_stats" {
|
||||
statsMap = m
|
||||
break
|
||||
}
|
||||
m.Close()
|
||||
mapID = nextID
|
||||
}
|
||||
|
||||
// Charger les objets SSL/uprobe (uprobe_ssl.c)
|
||||
sslObjs := &Ja4SslObjects{}
|
||||
if err := LoadJa4SslObjects(sslObjs, nil); err != nil {
|
||||
@ -64,42 +128,42 @@ func New() (*Loader, error) {
|
||||
return nil, fmt.Errorf("chargement objets SSL eBPF: %w", err)
|
||||
}
|
||||
|
||||
// Initialiser les readers pour chaque ring buffer
|
||||
synReader, err := ringbuf.NewReader(tcObjs.RbTcpSyn)
|
||||
// Initialiser les readers pour chaque perf event array
|
||||
synReader, err := perf.NewReader(tcObjs.PbTcpSyn, perCPUBufferSize)
|
||||
if err != nil {
|
||||
sslObjs.Close()
|
||||
tcObjs.Close()
|
||||
return nil, fmt.Errorf("création reader rb_tcp_syn: %w", err)
|
||||
return nil, fmt.Errorf("création reader pb_tcp_syn: %w", err)
|
||||
}
|
||||
|
||||
tlsReader, err := ringbuf.NewReader(tcObjs.RbTlsHello)
|
||||
tlsReader, err := perf.NewReader(tcObjs.PbTlsHello, perCPUBufferSize)
|
||||
if err != nil {
|
||||
synReader.Close()
|
||||
sslObjs.Close()
|
||||
tcObjs.Close()
|
||||
return nil, fmt.Errorf("création reader rb_tls_hello: %w", err)
|
||||
return nil, fmt.Errorf("création reader pb_tls_hello: %w", err)
|
||||
}
|
||||
|
||||
httpPlainReader, err := ringbuf.NewReader(tcObjs.RbHttpPlain)
|
||||
httpPlainReader, err := perf.NewReader(tcObjs.PbHttpPlain, perCPUBufferSize)
|
||||
if err != nil {
|
||||
tlsReader.Close()
|
||||
synReader.Close()
|
||||
sslObjs.Close()
|
||||
tcObjs.Close()
|
||||
return nil, fmt.Errorf("création reader rb_http_plain: %w", err)
|
||||
return nil, fmt.Errorf("création reader pb_http_plain: %w", err)
|
||||
}
|
||||
|
||||
sslReader, err := ringbuf.NewReader(sslObjs.RbSslData)
|
||||
sslReader, err := perf.NewReader(sslObjs.PbSslData, perCPUBufferSize)
|
||||
if err != nil {
|
||||
httpPlainReader.Close()
|
||||
tlsReader.Close()
|
||||
synReader.Close()
|
||||
sslObjs.Close()
|
||||
tcObjs.Close()
|
||||
return nil, fmt.Errorf("création reader rb_ssl_data: %w", err)
|
||||
return nil, fmt.Errorf("création reader pb_ssl_data: %w", err)
|
||||
}
|
||||
|
||||
acceptReader, err := ringbuf.NewReader(sslObjs.RbAccept)
|
||||
acceptReader, err := perf.NewReader(sslObjs.PbAccept, perCPUBufferSize)
|
||||
if err != nil {
|
||||
sslReader.Close()
|
||||
httpPlainReader.Close()
|
||||
@ -107,12 +171,13 @@ func New() (*Loader, error) {
|
||||
synReader.Close()
|
||||
sslObjs.Close()
|
||||
tcObjs.Close()
|
||||
return nil, fmt.Errorf("création reader rb_accept: %w", err)
|
||||
return nil, fmt.Errorf("création reader pb_accept: %w", err)
|
||||
}
|
||||
|
||||
return &Loader{
|
||||
tcObjs: tcObjs,
|
||||
sslObjs: sslObjs,
|
||||
statsMap: statsMap,
|
||||
SynReader: synReader,
|
||||
TLSReader: tlsReader,
|
||||
SSLReader: sslReader,
|
||||
@ -121,66 +186,79 @@ func New() (*Loader, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// AttachTC attache le programme XDP sur l'interface réseau spécifiée.
|
||||
// Essaie le mode natif XDP (driver support) puis se replie sur le mode générique
|
||||
// (SKB_MODE, compatible kernel ≥ 4.8, fonctionne dans les VMs).
|
||||
// AttachTC attache le programme TC ingress (clsact qdisc) sur l'interface
|
||||
// réseau spécifiée. Crée le qdisc clsact (idempotent) et attache le filtre BPF
|
||||
// en mode direct-action. Compatible kernel 4.1+.
|
||||
func (l *Loader) AttachTC(iface string) error {
|
||||
// Trouver l'interface par nom (standard Go net package)
|
||||
netIface, err := net.InterfaceByName(iface)
|
||||
if err != nil {
|
||||
return fmt.Errorf("interface réseau %q introuvable: %w", iface, err)
|
||||
}
|
||||
|
||||
// Mode natif (meilleure performance sur serveurs avec NIC compatible XDP)
|
||||
lnk, err := link.AttachXDP(link.XDPOptions{
|
||||
Interface: netIface.Index,
|
||||
Program: l.tcObjs.CaptureXdp,
|
||||
Flags: link.XDPDriverMode,
|
||||
})
|
||||
// Obtenir le link netlink par index (plus fiable que par nom)
|
||||
nlLink, err := netlink.LinkByIndex(netIface.Index)
|
||||
if err != nil {
|
||||
// Repli sur le mode générique (VMs, NICs sans driver XDP natif)
|
||||
lnk, err = link.AttachXDP(link.XDPOptions{
|
||||
Interface: netIface.Index,
|
||||
Program: l.tcObjs.CaptureXdp,
|
||||
Flags: link.XDPGenericMode,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("attachement XDP sur %q (natif et générique): %w", iface, err)
|
||||
}
|
||||
return fmt.Errorf("netlink link index %d introuvable: %w", netIface.Index, err)
|
||||
}
|
||||
|
||||
l.tcLink = lnk
|
||||
// Créer le qdisc clsact (idempotent via QdiscReplace)
|
||||
qdisc := &netlink.Clsact{
|
||||
QdiscAttrs: netlink.QdiscAttrs{
|
||||
LinkIndex: nlLink.Attrs().Index,
|
||||
Handle: netlink.MakeHandle(0xffff, 0),
|
||||
Parent: netlink.HANDLE_CLSACT,
|
||||
},
|
||||
}
|
||||
if err := netlink.QdiscReplace(qdisc); err != nil {
|
||||
return fmt.Errorf("clsact qdisc sur %q: %w", iface, err)
|
||||
}
|
||||
|
||||
// Attacher le programme BPF comme filtre ingress
|
||||
filter := &netlink.BpfFilter{
|
||||
FilterAttrs: netlink.FilterAttrs{
|
||||
LinkIndex: nlLink.Attrs().Index,
|
||||
Parent: netlink.HANDLE_MIN_INGRESS,
|
||||
Handle: 1,
|
||||
Protocol: unix.ETH_P_ALL,
|
||||
Priority: 1,
|
||||
},
|
||||
ClassId: netlink.MakeHandle(1, 1),
|
||||
Fd: l.tcObjs.CaptureTc.FD(),
|
||||
DirectAction: true,
|
||||
}
|
||||
if err := netlink.FilterReplace(filter); err != nil {
|
||||
return fmt.Errorf("TC filter ingress sur %q: %w", iface, err)
|
||||
}
|
||||
|
||||
l.tcNlLink = nlLink
|
||||
return nil
|
||||
}
|
||||
|
||||
// AttachUprobes attache les uprobes SSL_read et SSL_set_fd
|
||||
// sur le binaire libssl spécifié (ex: "/usr/lib64/libssl.so.3").
|
||||
func (l *Loader) AttachUprobes(sslLibPath string) error {
|
||||
// Vérifier que le fichier existe
|
||||
if _, err := os.Stat(sslLibPath); err != nil {
|
||||
return fmt.Errorf("bibliothèque SSL %q: %w", sslLibPath, err)
|
||||
}
|
||||
|
||||
// Ouvrir le binaire exécutable pour les uprobes
|
||||
ex, err := link.OpenExecutable(sslLibPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ouverture exécutable %q pour uprobe: %w", sslLibPath, err)
|
||||
}
|
||||
|
||||
// Uprobe sur SSL_set_fd (entry)
|
||||
setFdLink, err := ex.Uprobe("SSL_set_fd", l.sslObjs.UprobeSslSetFd, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("attachement uprobe SSL_set_fd: %w", err)
|
||||
}
|
||||
l.uprobeLinks = append(l.uprobeLinks, setFdLink)
|
||||
|
||||
// Uprobe sur SSL_read (entry)
|
||||
readEntryLink, err := ex.Uprobe("SSL_read", l.sslObjs.UprobeSslReadEntry, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("attachement uprobe SSL_read (entry): %w", err)
|
||||
}
|
||||
l.uprobeLinks = append(l.uprobeLinks, readEntryLink)
|
||||
|
||||
// Uretprobe sur SSL_read (exit)
|
||||
readExitLink, err := ex.Uretprobe("SSL_read", l.sslObjs.UretprobeSslReadExit, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("attachement uretprobe SSL_read (exit): %w", err)
|
||||
@ -191,10 +269,7 @@ func (l *Loader) AttachUprobes(sslLibPath string) error {
|
||||
}
|
||||
|
||||
// AttachAcceptProbe attache les tracepoints syscalls/sys_{enter,exit}_accept4.
|
||||
// Les tracepoints sont préférés aux kprobes car ils ne dépendent pas du nom
|
||||
// manglé __x64_sys_accept4 qui varie entre les versions du kernel (5.1+).
|
||||
func (l *Loader) AttachAcceptProbe() error {
|
||||
// Tracepoint à l'entrée de accept4
|
||||
kpEntry, err := link.Tracepoint("syscalls", "sys_enter_accept4",
|
||||
l.sslObjs.KprobeAccept4Entry, nil)
|
||||
if err != nil {
|
||||
@ -202,7 +277,6 @@ func (l *Loader) AttachAcceptProbe() error {
|
||||
}
|
||||
l.uprobeLinks = append(l.uprobeLinks, kpEntry)
|
||||
|
||||
// Tracepoint à la sortie de accept4
|
||||
kpExit, err := link.Tracepoint("syscalls", "sys_exit_accept4",
|
||||
l.sslObjs.KretprobeAccept4Exit, nil)
|
||||
if err != nil {
|
||||
@ -215,7 +289,6 @@ func (l *Loader) AttachAcceptProbe() error {
|
||||
|
||||
// Close détache tous les hooks eBPF et libère toutes les ressources associées.
|
||||
func (l *Loader) Close() error {
|
||||
// Fermer les readers RingBuffer
|
||||
if l.HTTPPlainReader != nil {
|
||||
l.HTTPPlainReader.Close()
|
||||
}
|
||||
@ -232,19 +305,26 @@ func (l *Loader) Close() error {
|
||||
l.SynReader.Close()
|
||||
}
|
||||
|
||||
// Détacher les uprobes et kprobes
|
||||
// Détacher le filtre TC ingress
|
||||
if l.tcNlLink != nil {
|
||||
filter := &netlink.BpfFilter{
|
||||
FilterAttrs: netlink.FilterAttrs{
|
||||
LinkIndex: l.tcNlLink.Attrs().Index,
|
||||
Parent: netlink.HANDLE_MIN_INGRESS,
|
||||
Handle: 1,
|
||||
Priority: 1,
|
||||
},
|
||||
}
|
||||
// Ignorer l'erreur — le filtre peut déjà être supprimé
|
||||
netlink.FilterDel(filter)
|
||||
}
|
||||
|
||||
for _, lnk := range l.uprobeLinks {
|
||||
if lnk != nil {
|
||||
lnk.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// Détacher le hook TC
|
||||
if l.tcLink != nil {
|
||||
l.tcLink.Close()
|
||||
}
|
||||
|
||||
// Libérer les objets eBPF (maps, programmes)
|
||||
if l.sslObjs != nil {
|
||||
l.sslObjs.Close()
|
||||
}
|
||||
@ -255,259 +335,10 @@ func (l *Loader) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Types d'événements : représentations Go des structures C eBPF
|
||||
// =============================================================================
|
||||
|
||||
// TCPSynEvent représente un événement TCP SYN capturé par TC ingress.
|
||||
type TCPSynEvent struct {
|
||||
SrcIP uint32
|
||||
DstIP uint32
|
||||
SrcPort uint16
|
||||
DstPort uint16
|
||||
TTL uint8
|
||||
DFBit uint8
|
||||
IPID uint16
|
||||
WindowSize uint16
|
||||
WindowScale uint8
|
||||
MSS uint16
|
||||
TCPOptions [40]byte
|
||||
TCPOptionsLen uint8
|
||||
Timestamp uint64
|
||||
}
|
||||
|
||||
// TLSHelloEvent représente un événement TLS ClientHello.
|
||||
type TLSHelloEvent struct {
|
||||
SrcIP uint32
|
||||
SrcPort uint16
|
||||
Payload []byte
|
||||
PayloadLen uint16
|
||||
Timestamp uint64
|
||||
}
|
||||
|
||||
// SSLDataEvent représente un bloc de données SSL déchiffré par uprobe.
|
||||
type SSLDataEvent struct {
|
||||
PID uint32
|
||||
TGID uint32
|
||||
FD uint32
|
||||
SrcIP uint32
|
||||
SrcPort uint16
|
||||
Data []byte
|
||||
DataLen uint32
|
||||
Timestamp uint64
|
||||
Direction uint8
|
||||
EOF bool
|
||||
}
|
||||
|
||||
// HTTPPlainEvent représente un payload TCP HTTP en clair capturé par TC ingress.
|
||||
type HTTPPlainEvent struct {
|
||||
SrcIP uint32
|
||||
DstIP uint32
|
||||
SrcPort uint16
|
||||
DstPort uint16
|
||||
Payload []byte
|
||||
PayloadLen uint16
|
||||
Timestamp uint64
|
||||
}
|
||||
|
||||
// AcceptEvent représente une acceptation de connexion TCP (accept4).
|
||||
type AcceptEvent struct {
|
||||
PID uint32
|
||||
TGID uint32
|
||||
FD uint32
|
||||
SrcIP uint32
|
||||
SrcPort uint16
|
||||
Timestamp uint64
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Méthodes de lecture des RingBuffers
|
||||
// =============================================================================
|
||||
|
||||
// ReadTCPSynEvent lit un événement TCP SYN depuis le RingBuffer.
|
||||
// Bloque jusqu'à ce qu'un événement soit disponible ou que ctx soit annulé.
|
||||
func (l *Loader) ReadTCPSynEvent(ctx context.Context) (*TCPSynEvent, error) {
|
||||
rec, err := readRecord(ctx, l.SynReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data := rec.RawSample
|
||||
// struct tcp_syn_event packed: src_ip(4)+dst_ip(4)+src_port(2)+dst_port(2)+
|
||||
// ttl(1)+df(1)+ip_id(2)+window(2)+wscale(1)+mss(2)+opts(40)+opts_len(1)+_pad(1)+ts(8) = 71
|
||||
if len(data) < 64 {
|
||||
return nil, fmt.Errorf("tcp_syn_event trop court: %d octets", len(data))
|
||||
}
|
||||
|
||||
ev := &TCPSynEvent{
|
||||
SrcIP: binary.LittleEndian.Uint32(data[0:4]),
|
||||
DstIP: binary.LittleEndian.Uint32(data[4:8]),
|
||||
SrcPort: binary.LittleEndian.Uint16(data[8:10]),
|
||||
DstPort: binary.LittleEndian.Uint16(data[10:12]),
|
||||
TTL: data[12],
|
||||
DFBit: data[13],
|
||||
IPID: binary.LittleEndian.Uint16(data[14:16]),
|
||||
WindowSize: binary.LittleEndian.Uint16(data[16:18]),
|
||||
WindowScale: data[18],
|
||||
MSS: binary.LittleEndian.Uint16(data[19:21]),
|
||||
}
|
||||
copy(ev.TCPOptions[:], data[21:61])
|
||||
ev.TCPOptionsLen = data[61]
|
||||
if len(data) >= 70 {
|
||||
ev.Timestamp = binary.LittleEndian.Uint64(data[62:70])
|
||||
}
|
||||
return ev, nil
|
||||
}
|
||||
|
||||
// ReadTLSHelloEvent lit un événement TLS ClientHello depuis le RingBuffer.
|
||||
func (l *Loader) ReadTLSHelloEvent(ctx context.Context) (*TLSHelloEvent, error) {
|
||||
rec, err := readRecord(ctx, l.TLSReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data := rec.RawSample
|
||||
// struct tls_hello_event: src_ip(4)+src_port(2)+payload(512)+payload_len(2)+ts(8) = 528
|
||||
if len(data) < 8 {
|
||||
return nil, fmt.Errorf("tls_hello_event trop court: %d octets", len(data))
|
||||
}
|
||||
|
||||
plen := uint16(0)
|
||||
if len(data) >= 520 {
|
||||
plen = binary.LittleEndian.Uint16(data[518:520])
|
||||
}
|
||||
payload := make([]byte, plen)
|
||||
if int(plen) <= 512 && len(data) >= 6+int(plen) {
|
||||
copy(payload, data[6:6+plen])
|
||||
}
|
||||
|
||||
ts := uint64(0)
|
||||
if len(data) >= 528 {
|
||||
ts = binary.LittleEndian.Uint64(data[520:528])
|
||||
}
|
||||
|
||||
return &TLSHelloEvent{
|
||||
SrcIP: binary.LittleEndian.Uint32(data[0:4]),
|
||||
SrcPort: binary.LittleEndian.Uint16(data[4:6]),
|
||||
Payload: payload,
|
||||
PayloadLen: plen,
|
||||
Timestamp: ts,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ReadSSLDataEvent lit un bloc de données SSL déchiffrées depuis le RingBuffer.
|
||||
func (l *Loader) ReadSSLDataEvent(ctx context.Context) (*SSLDataEvent, error) {
|
||||
rec, err := readRecord(ctx, l.SSLReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data := rec.RawSample
|
||||
// struct ssl_data_event: pid_tgid(8)+fd(4)+src_ip(4)+src_port(2)+data(4096)+data_len(4)+ts(8)+direction(1)
|
||||
if len(data) < 27 {
|
||||
return nil, fmt.Errorf("ssl_data_event trop court: %d octets", len(data))
|
||||
}
|
||||
|
||||
pidTGID := binary.LittleEndian.Uint64(data[0:8])
|
||||
dlen := uint32(0)
|
||||
if len(data) >= 4118 {
|
||||
dlen = binary.LittleEndian.Uint32(data[4114:4118])
|
||||
}
|
||||
payload := make([]byte, dlen)
|
||||
if int(dlen) <= 4096 && len(data) >= 18+int(dlen) {
|
||||
copy(payload, data[18:18+dlen])
|
||||
}
|
||||
|
||||
ts := uint64(0)
|
||||
if len(data) >= 4126 {
|
||||
ts = binary.LittleEndian.Uint64(data[4118:4126])
|
||||
}
|
||||
dir := uint8(0)
|
||||
if len(data) >= 4127 {
|
||||
dir = data[4126]
|
||||
}
|
||||
|
||||
return &SSLDataEvent{
|
||||
PID: uint32(pidTGID & 0xFFFFFFFF),
|
||||
TGID: uint32(pidTGID >> 32),
|
||||
FD: binary.LittleEndian.Uint32(data[8:12]),
|
||||
SrcIP: binary.LittleEndian.Uint32(data[12:16]),
|
||||
SrcPort: binary.LittleEndian.Uint16(data[16:18]),
|
||||
Data: payload,
|
||||
DataLen: dlen,
|
||||
Timestamp: ts,
|
||||
Direction: dir,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ReadHTTPPlainEvent lit un événement HTTP en clair depuis le RingBuffer TC.
|
||||
// struct http_plain_event: src_ip(4)+dst_ip(4)+src_port(2)+dst_port(2)+
|
||||
//
|
||||
// payload(4096)+payload_len(2)+ts(8) = 4118
|
||||
func (l *Loader) ReadHTTPPlainEvent(ctx context.Context) (*HTTPPlainEvent, error) {
|
||||
rec, err := readRecord(ctx, l.HTTPPlainReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data := rec.RawSample
|
||||
if len(data) < 12 {
|
||||
return nil, fmt.Errorf("http_plain_event trop court: %d octets", len(data))
|
||||
}
|
||||
|
||||
plen := uint16(0)
|
||||
if len(data) >= 4110 {
|
||||
plen = binary.LittleEndian.Uint16(data[4108:4110])
|
||||
}
|
||||
payload := make([]byte, plen)
|
||||
if int(plen) <= 4096 && len(data) >= 12+int(plen) {
|
||||
copy(payload, data[12:12+plen])
|
||||
}
|
||||
|
||||
ts := uint64(0)
|
||||
if len(data) >= 4118 {
|
||||
ts = binary.LittleEndian.Uint64(data[4110:4118])
|
||||
}
|
||||
|
||||
return &HTTPPlainEvent{
|
||||
SrcIP: binary.LittleEndian.Uint32(data[0:4]),
|
||||
DstIP: binary.LittleEndian.Uint32(data[4:8]),
|
||||
SrcPort: binary.LittleEndian.Uint16(data[8:10]),
|
||||
DstPort: binary.LittleEndian.Uint16(data[10:12]),
|
||||
Payload: payload,
|
||||
PayloadLen: plen,
|
||||
Timestamp: ts,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ReadAcceptEvent lit un événement accept4 depuis le RingBuffer.
|
||||
func (l *Loader) ReadAcceptEvent(ctx context.Context) (*AcceptEvent, error) {
|
||||
rec, err := readRecord(ctx, l.AcceptReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data := rec.RawSample
|
||||
// struct accept_event: pid_tgid(8)+fd(4)+src_ip(4)+src_port(2)+ts(8) = 26
|
||||
if len(data) < 26 {
|
||||
return nil, fmt.Errorf("accept_event trop court: %d octets", len(data))
|
||||
}
|
||||
|
||||
pidTGID := binary.LittleEndian.Uint64(data[0:8])
|
||||
return &AcceptEvent{
|
||||
PID: uint32(pidTGID & 0xFFFFFFFF),
|
||||
TGID: uint32(pidTGID >> 32),
|
||||
FD: binary.LittleEndian.Uint32(data[8:12]),
|
||||
SrcIP: binary.LittleEndian.Uint32(data[12:16]),
|
||||
SrcPort: binary.LittleEndian.Uint16(data[16:18]),
|
||||
Timestamp: binary.LittleEndian.Uint64(data[18:26]),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// readRecord lit un record brut depuis un RingBuffer avec annulation via context.
|
||||
func readRecord(ctx context.Context, rd *ringbuf.Reader) (ringbuf.Record, error) {
|
||||
// readRecord lit un record brut depuis un PerfReader avec annulation via context.
|
||||
func readRecord(ctx context.Context, rd *perf.Reader) (perf.Record, error) {
|
||||
type result struct {
|
||||
rec ringbuf.Record
|
||||
rec perf.Record
|
||||
err error
|
||||
}
|
||||
ch := make(chan result, 1)
|
||||
@ -517,8 +348,8 @@ func readRecord(ctx context.Context, rd *ringbuf.Reader) (ringbuf.Record, error)
|
||||
}()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
rd.Close() // débloque le Read() bloquant
|
||||
return ringbuf.Record{}, ctx.Err()
|
||||
rd.Close()
|
||||
return perf.Record{}, ctx.Err()
|
||||
case r := <-ch:
|
||||
return r.rec, r.err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user