When running against GENEVE-encapsulated traffic, ja4t and ja4ts in conn.log are empty for the inner connections. JA4 (TLS) fingerprints work fine for the same traffic.
This was reported by a user in the Zeek forum here. Below reproducer was generated with Claude Opus 4.6.
Reproducer
gen_pcap.py creates a PCAP with a plain and a GENEVE-wrapped TCP
handshake carrying identical TCP options:
uv run gen_pcap.py
zeek -r ja4t-geneve.pcap Zeek::Geneve
zeek-cut id.orig_h id.resp_h id.resp_p ja4t ja4ts < conn.log
10.0.0.1 10.0.0.2 443 65535_2-3-4-1-1_1460_7 65535_2-3-4-1-1_1460_7
172.16.0.1 172.16.0.2 443 (empty) (empty)
Expected: both connections produce the same ja4t/ja4ts values.
Likely cause
parse_tcp_options in zeek/src/ja4t.cc walks raw packet bytes from the outer Ethernet header, but uses ip_hl/tcp_hl from the inner (decapsulated) headers, so the computed offset is wrong when any tunnel encapsulation beyond VLAN is present. The LINK_ETHERNET check in scripts/fingerprints/ja4t/main.zeek:49 may also reject some tunnel types early.
When running against GENEVE-encapsulated traffic,
ja4tandja4tsinconn.logare empty for the inner connections. JA4 (TLS) fingerprints work fine for the same traffic.This was reported by a user in the Zeek forum here. Below reproducer was generated with Claude Opus 4.6.
Reproducer
gen_pcap.pycreates a PCAP with a plain and a GENEVE-wrapped TCPhandshake carrying identical TCP options:
uv run gen_pcap.py zeek -r ja4t-geneve.pcap Zeek::Geneve zeek-cut id.orig_h id.resp_h id.resp_p ja4t ja4ts < conn.logExpected: both connections produce the same
ja4t/ja4tsvalues.Likely cause
parse_tcp_optionsinzeek/src/ja4t.ccwalks raw packet bytes from the outer Ethernet header, but usesip_hl/tcp_hlfrom the inner (decapsulated) headers, so the computed offset is wrong when any tunnel encapsulation beyond VLAN is present. TheLINK_ETHERNETcheck inscripts/fingerprints/ja4t/main.zeek:49may also reject some tunnel types early.