Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions doc/admin-guide/plugins/ja4_fingerprint.en.rst
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,22 @@ Key Differences from JA3
Plugin Configuration
====================

The plugin operates as a global plugin and has no configuration options.
The plugin operates as a global plugin.

To enable the plugin, add the following line to :file:`plugin.config`::

ja4_fingerprint.so

No additional parameters are required or supported.

.. option:: --preserve

This option controls whether the plugin preserves any existing JA4 header. If the option is specified the plugin keep the header
intact. If the option is not speficied, the plugins appends a generated fingerprint to the existing header value.
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in documentation: "speficied" should be "specified".

Suggested change
intact. If the option is not speficied, the plugins appends a generated fingerprint to the existing header value.
intact. If the option is not specified, the plugins appends a generated fingerprint to the existing header value.

Copilot uses AI. Check for mistakes.
Comment on lines +90 to +91
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The --preserve option description has several grammar issues and is hard to read as a single long line (e.g., "the plugin keep", "the plugins appends"). Please fix the wording and wrap the text consistently with the rest of this RST file.

Suggested change
This option controls whether the plugin preserves any existing JA4 header. If the option is specified the plugin keep the header
intact. If the option is not speficied, the plugins appends a generated fingerprint to the existing header value.
This option controls whether the plugin preserves any existing JA4 header.
If the option is specified, the plugin keeps the header intact. If the
option is not specified, the plugin appends a generated fingerprint to the
existing header value.

Copilot uses AI. Check for mistakes.

.. option:: --nologging

This option disables log output.
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"This option disables log output" is ambiguous now that debug logging (Dbg) still emits fingerprints when debug tags are enabled. Please clarify that --nologging disables writing to ja4_fingerprint.log (text log), and whether debug output is still expected.

Suggested change
This option disables log output.
This option disables writing fingerprints to the :file:`ja4_fingerprint.log` text log file. It does not affect Traffic Server debug
logging (``Dbg``); if relevant debug tags are enabled, fingerprints may still appear in debug logs.

Copilot uses AI. Check for mistakes.


Plugin Behavior
===============
Expand All @@ -110,7 +119,7 @@ Log Output
==========

The plugin writes to ``ja4_fingerprint.log`` in the Traffic Server log
directory (typically ``/var/log/trafficserver/``).
directory (typically ``/var/log/trafficserver/``) if the feature is not disabled.
Comment on lines 121 to +122
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"if the feature is not disabled" is unclear—this appears to refer specifically to --nologging. Consider rephrasing to explicitly mention --nologging, and also update earlier sections (e.g., the "Plugin Behavior" bullet that says it always "Log to File") to reflect the conditional logging behavior.

Copilot uses AI. Check for mistakes.

**Log Format**::

Expand Down Expand Up @@ -175,7 +184,6 @@ Limitations
===========

* The plugin only operates in global mode (no per-remap configuration)
* Logging cannot be disabled
* Raw (unhashed) cipher and extension lists are not logged
* Non-TLS connections do not generate fingerprints

Expand Down
29 changes: 18 additions & 11 deletions plugins/experimental/ja4_fingerprint/plugin.cc
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,17 @@ constexpr unsigned int EXT_SUPPORTED_VERSIONS{0x2b};
DbgCtl dbg_ctl{PLUGIN_NAME};

int global_preserve_enabled{0};
int global_nologging_enabled{0};

} // end anonymous namespace

static bool
read_config_option(int argc, char const *argv[], int &preserve)
read_config_option(int argc, char const *argv[], int &preserve, int &no_logging)
{
const struct option longopts[] = {
{"preserve", no_argument, &preserve, 1},
{nullptr, 0, nullptr, 0}
{"preserve", no_argument, &preserve, 1},
{"nologging", no_argument, &no_logging, 1},
{nullptr, 0, nullptr, 0}
};

optind = 0;
Expand All @@ -108,6 +110,7 @@ read_config_option(int argc, char const *argv[], int &preserve)
}

Dbg(dbg_ctl, "JA4 preserve is %s", (preserve == 1) ? "enabled" : "disabled");
Dbg(dbg_ctl, "JA4 nologging is %s", (no_logging == 1) ? "enabled" : "disabled");
return true;
}

Expand Down Expand Up @@ -148,16 +151,18 @@ TSPluginInit(int argc, char const **argv)
TSError("[%s] Failed to register.", PLUGIN_NAME);
return;
}
if (!read_config_option(argc, argv, global_preserve_enabled)) {
if (!read_config_option(argc, argv, global_preserve_enabled, global_nologging_enabled)) {
TSError("[%s] Failed to parse options.", PLUGIN_NAME);
return;
}
reserve_user_arg();
if (!create_log_file()) {
TSError("[%s] Failed to create log.", PLUGIN_NAME);
return;
} else {
Dbg(dbg_ctl, "Created log file.");
if (!global_nologging_enabled) {
if (!create_log_file()) {
TSError("[%s] Failed to create log.", PLUGIN_NAME);
return;
} else {
Dbg(dbg_ctl, "Created log file.");
}
}
Comment on lines +159 to 166
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new --nologging behavior isn't covered by existing tests. Since there are already gold tests for this plugin, please add a test case that runs with ja4_fingerprint.so --nologging and asserts (1) no ja4_fingerprint.log is created/written and (2) JA4 headers are still injected.

Copilot generated this review using guidance from repository custom instructions.
register_hooks();
}
Expand Down Expand Up @@ -207,8 +212,11 @@ handle_client_hello(TSCont /* cont ATS_UNUSED */, TSEvent event, void *edata)
} else {
auto data{std::make_unique<JA4_data>()};
data->fingerprint = get_fingerprint(ch);
Dbg(dbg_ctl, "JA4 fingerprint: %s", data->fingerprint.c_str());
get_IP(TSNetVConnRemoteAddrGet(ssl_vc), data->IP_addr);
log_fingerprint(data.get());
if (!global_nologging_enabled) {
log_fingerprint(data.get());
}
// The VCONN_CLOSE handler is now responsible for freeing the resource.
TSUserArgSet(ssl_vc, *get_user_arg_index(), static_cast<void *>(data.release()));
}
Comment on lines 220 to 222
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TSUserArgSet() stores a JA4_data* on the vconn, but other hooks treat this user arg as a std::string* (see TSUserArgGet() usage in handle_read_request_hdr() and the delete in handle_vconn_close()). Deleting/using a JA4_data* as a std::string* is undefined behavior even if the std::string is the first member. Please make the stored type consistent (e.g., store JA4_data* and update the later casts/deletes, or store only a std::string* and adjust log_fingerprint() accordingly).

Copilot uses AI. Check for mistakes.
Expand Down Expand Up @@ -259,7 +267,6 @@ get_IP(sockaddr const *s_sockaddr, char res[INET6_ADDRSTRLEN])
void
log_fingerprint(JA4_data const *data)
{
Dbg(dbg_ctl, "JA4 fingerprint: %s", data->fingerprint.c_str());
if (TS_ERROR == TSTextLogObjectWrite(*get_log_handle(), "Client IP: %s\tJA4: %s", data->IP_addr, data->fingerprint.c_str())) {
Dbg(dbg_ctl, "Failed to write to log!");
}
Expand Down