fix(ja4ebpf): fix TLS capture, SYN offsets, TCP option parsing

- Increase MAX_TLS_PAYLOAD from 512 to 2048 bytes to capture full
  TLS ClientHellos (modern browsers/curl send 1000-1543 byte ClientHellos)
- Fix ParseClientHello to tolerate XDP-truncated payloads: clamp
  recordLength and chLen to available data instead of returning error
- Fix cipher suites, compression, extensions truncation to use clamping
- Fix consumeSynEvents struct field offsets: dst_ip (4 bytes at offset 4)
  was not accounted for, causing all L3/L4 metadata to be read from
  wrong positions (TTL was actually dst_ip[0], windowSize was dst_port, etc.)
- Add parseTCPOptions() to extract MSS and Window Scale from raw TCP options
  (C code sets defaults of mss=0, window_scale=0xFF, expects Go to parse)
- Fix consumeAcceptEvents: skip zero-IP events to avoid phantom sessions
- Fix consumeSSLEvents: filter zero-IP/port events when proc fallback fails
- Add missing consumeHTTPPlainEvents goroutine (was defined but never called)
- Fix race condition: SYN consumer sets Correlated=true if TLS already present
- Update tls_hello_event struct offsets in Go consumer (payload_len now at
  offset 2054, was 518, due to payload array growing from 512 to 2048 bytes)
- Remove debug logging from consumers and GC

E2E verified: HTTP plain (port 80) and HTTPS (port 443) both produce
fully correlated sessions in ClickHouse with correct:
  - ip_meta_ttl=64, ip_meta_df=true, ip_meta_id
  - tcp_meta_window_size=64240, tcp_meta_window_scale=10, tcp_meta_mss=1460
  - ja4=t13i3010_1d37bd780c83_95d2a80e6515
  - tls_alpn=http/1.1
  - method=GET, path=/, header_order_signature=Host;User-Agent;Accept
  - correlated=1

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
toto
2026-04-12 04:16:44 +02:00
parent f85a10b012
commit b1218a2367
12 changed files with 715 additions and 248 deletions

View File

@ -0,0 +1,183 @@
// Code generated by bpf2go; DO NOT EDIT.
//go:build 386 || amd64
package loader
import (
"bytes"
_ "embed"
"fmt"
"io"
"github.com/cilium/ebpf"
)
type Ja4SslAcceptEvent struct {
PidTgid uint64
Fd uint32
SrcIp uint32
SrcPort uint16
TimestampNs uint64
}
type Ja4SslAcceptKey struct {
PidTgid uint64
Fd uint32
}
type Ja4SslSslConnInfo struct {
Fd uint32
SrcIp uint32
SrcPort uint16
}
type Ja4SslSslReadArgs struct {
SslPtr uint64
BufPtr uint64
Num uint32
}
// LoadJa4Ssl returns the embedded CollectionSpec for Ja4Ssl.
func LoadJa4Ssl() (*ebpf.CollectionSpec, error) {
reader := bytes.NewReader(_Ja4SslBytes)
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
if err != nil {
return nil, fmt.Errorf("can't load Ja4Ssl: %w", err)
}
return spec, err
}
// LoadJa4SslObjects loads Ja4Ssl and converts it into a struct.
//
// The following types are suitable as obj argument:
//
// *Ja4SslObjects
// *Ja4SslPrograms
// *Ja4SslMaps
//
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
func LoadJa4SslObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
spec, err := LoadJa4Ssl()
if err != nil {
return err
}
return spec.LoadAndAssign(obj, opts)
}
// Ja4SslSpecs contains maps and programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type Ja4SslSpecs struct {
Ja4SslProgramSpecs
Ja4SslMapSpecs
}
// Ja4SslSpecs contains programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type Ja4SslProgramSpecs struct {
KprobeAccept4Entry *ebpf.ProgramSpec `ebpf:"kprobe_accept4_entry"`
KretprobeAccept4Exit *ebpf.ProgramSpec `ebpf:"kretprobe_accept4_exit"`
UprobeSslReadEntry *ebpf.ProgramSpec `ebpf:"uprobe_ssl_read_entry"`
UprobeSslSetFd *ebpf.ProgramSpec `ebpf:"uprobe_ssl_set_fd"`
UretprobeSslReadExit *ebpf.ProgramSpec `ebpf:"uretprobe_ssl_read_exit"`
}
// Ja4SslMapSpecs contains maps before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type Ja4SslMapSpecs struct {
AcceptArgsMap *ebpf.MapSpec `ebpf:"accept_args_map"`
AcceptMap *ebpf.MapSpec `ebpf:"accept_map"`
FdConnMap *ebpf.MapSpec `ebpf:"fd_conn_map"`
RbAccept *ebpf.MapSpec `ebpf:"rb_accept"`
RbHttpPlain *ebpf.MapSpec `ebpf:"rb_http_plain"`
RbSslData *ebpf.MapSpec `ebpf:"rb_ssl_data"`
RbTcpSyn *ebpf.MapSpec `ebpf:"rb_tcp_syn"`
RbTlsHello *ebpf.MapSpec `ebpf:"rb_tls_hello"`
SslArgsMap *ebpf.MapSpec `ebpf:"ssl_args_map"`
SslConnMap *ebpf.MapSpec `ebpf:"ssl_conn_map"`
}
// Ja4SslObjects contains all objects after they have been loaded into the kernel.
//
// It can be passed to LoadJa4SslObjects or ebpf.CollectionSpec.LoadAndAssign.
type Ja4SslObjects struct {
Ja4SslPrograms
Ja4SslMaps
}
func (o *Ja4SslObjects) Close() error {
return _Ja4SslClose(
&o.Ja4SslPrograms,
&o.Ja4SslMaps,
)
}
// Ja4SslMaps contains all maps after they have been loaded into the kernel.
//
// It can be passed to LoadJa4SslObjects or ebpf.CollectionSpec.LoadAndAssign.
type Ja4SslMaps struct {
AcceptArgsMap *ebpf.Map `ebpf:"accept_args_map"`
AcceptMap *ebpf.Map `ebpf:"accept_map"`
FdConnMap *ebpf.Map `ebpf:"fd_conn_map"`
RbAccept *ebpf.Map `ebpf:"rb_accept"`
RbHttpPlain *ebpf.Map `ebpf:"rb_http_plain"`
RbSslData *ebpf.Map `ebpf:"rb_ssl_data"`
RbTcpSyn *ebpf.Map `ebpf:"rb_tcp_syn"`
RbTlsHello *ebpf.Map `ebpf:"rb_tls_hello"`
SslArgsMap *ebpf.Map `ebpf:"ssl_args_map"`
SslConnMap *ebpf.Map `ebpf:"ssl_conn_map"`
}
func (m *Ja4SslMaps) Close() error {
return _Ja4SslClose(
m.AcceptArgsMap,
m.AcceptMap,
m.FdConnMap,
m.RbAccept,
m.RbHttpPlain,
m.RbSslData,
m.RbTcpSyn,
m.RbTlsHello,
m.SslArgsMap,
m.SslConnMap,
)
}
// Ja4SslPrograms contains all programs after they have been loaded into the kernel.
//
// It can be passed to LoadJa4SslObjects or ebpf.CollectionSpec.LoadAndAssign.
type Ja4SslPrograms struct {
KprobeAccept4Entry *ebpf.Program `ebpf:"kprobe_accept4_entry"`
KretprobeAccept4Exit *ebpf.Program `ebpf:"kretprobe_accept4_exit"`
UprobeSslReadEntry *ebpf.Program `ebpf:"uprobe_ssl_read_entry"`
UprobeSslSetFd *ebpf.Program `ebpf:"uprobe_ssl_set_fd"`
UretprobeSslReadExit *ebpf.Program `ebpf:"uretprobe_ssl_read_exit"`
}
func (p *Ja4SslPrograms) Close() error {
return _Ja4SslClose(
p.KprobeAccept4Entry,
p.KretprobeAccept4Exit,
p.UprobeSslReadEntry,
p.UprobeSslSetFd,
p.UretprobeSslReadExit,
)
}
func _Ja4SslClose(closers ...io.Closer) error {
for _, closer := range closers {
if err := closer.Close(); err != nil {
return err
}
}
return nil
}
// Do not access this directly.
//
//go:embed ja4ssl_x86_bpfel.o
var _Ja4SslBytes []byte