Skip to content

JA4D6

JA4D6 fingerprints one DHCPv6 message. It takes the shape of a JA4D value, and three of its fields read different sources.

One FoxIO implementation builds it

The Wireshark dissector is the one FoxIO implementation of JA4D6. docs/specs/foxio/JA4D6.md records the reading, and it states the consequence:

No reference split can exist on this page, because two implementations cannot disagree.

The Zeek package states that it awaits DHCPv6 support. The reference Rust and the reference Python each build nothing for this method.

The value

One JA4D6 value holds three parts, and one underscore separates each pair of parts. R4 to R6 hold the reading, and the image example is JA4D6=solct0014nn_8-1-3-39-16-6_17-23-24-39.

Part What it holds Rules
a Four fields, concatenated with no delimiter. R7 to R18
b The DHCPv6 option list. R19 to R22
c The option request list. R23 to R25

Part a holds four fields

Field Width What it holds Rules
1 5 The DHCPv6 message type, as a code from a table of 37 entries. R7 to R10
2 4 The length of the client identifier. R11 to R16
3 1 i when the message carries an identity association for a temporary address. R17
4 1 d when the message carries a client domain, and n otherwise. R18

Field 2 is the one field whose meaning differs from JA4D. JA4D holds the maximum message size there, and JA4D6 holds the client identifier length. The field reads DHCPv6 option 1 alone, and the first occurrence decides it, under R11 to R16. It caps at 9999, and an absent value writes 0000.

Part b differs from JA4D

JA4D names four options to ignore, and JA4D6 names none. R20 holds that sentence. A hyphen separates each pair of values in part b and in part c, and an empty list writes 00.

What this library emits

JA4D6Fingerprinter produces the value. NewJA4D6 builds one, and ProcessPacket reads one packet.

The fingerprinter holds no state. Reset and CleanupConnection each do nothing.

One DHCPv6 message produces one result. The message reaches the fingerprinter on UDP port 546 or port 547.

The Type field holds ja4d6.

The nested option walk

A DHCPv6 message nests options inside containers, and this library descends into each one. walkDHCPv6Options in ja4d6.go reads these containers.

Container Option number
Identity association for non-temporary addresses 3
Identity association for temporary addresses 4
Address 5
Relay message 9
Identity association for prefix delegation 25
Prefix 26

A bound of 32 levels stops the descent. A crafted message can nest a container without a bound, so the walk needs a limit that the packet does not choose.

Issue #370 widened the descent on 2026-08-14. Before that change the walk descended into the relay message alone. Field 3, field 4, part b and part c now read every container above. TestJA4D6ReadsASubfieldOptionOfANestedContainer holds the behavior.

Field 1 still writes the message type of the outer message. ProcessPacket in ja4d6.go reads it, and no inner message changes it.

How the conformance suite compares this method

The Wireshark dissector publishes JA4D and JA4D6 under one field name, ja4.ja4d. R26 and R27 record it. So the FoxIO corpus holds no value under a JA4D6 name, and the conformance suite compares a JA4D6 value under the method JA4D. FR-conformance-25 states that rule.

Where the register records a difference

testdata/deviations.json holds no entry whose key names JA4D6, measured on this branch. So this method carries no accepted difference from a FoxIO value.