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
56 changes: 35 additions & 21 deletions plugins/experimental/jax_fingerprint/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,44 +15,58 @@
#
#######################

set(ENABLE_JAX_METHODS
"ja3;ja4;ja4h"
CACHE STRING "Semicolon-separated list of fingerprint methods to compile into jax_fingerprint"
)

list(LENGTH ENABLE_JAX_METHODS _jax_method_count)
if(_jax_method_count EQUAL 0)
message(FATAL_ERROR "ENABLE_JAX_METHODS must list at least one method (e.g. ja3;ja4;ja4h)")
endif()

set(_jax_plugin_method_sources "")
set(_jax_test_method_sources "")
set(_jax_method_defs "")
foreach(_m IN LISTS ENABLE_JAX_METHODS)
if(NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${_m}")
message(FATAL_ERROR "ENABLE_JAX_METHODS references unknown method '${_m}' (no ${_m}/ directory)")
endif()
file(GLOB _srcs CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${_m}/*.cc")
set(_plugin_srcs ${_srcs})
set(_test_srcs ${_srcs})
list(FILTER _plugin_srcs EXCLUDE REGEX "/test\\.cc$")
# method.cc and tls_client_hello_summary.cc bind algorithm logic to ATS APIs (TSClientHello
# etc.) that the test binary does not link; everything else is pure algorithm/data and is
# safe to include in test_jax.
list(FILTER _test_srcs EXCLUDE REGEX "/(method|tls_client_hello_summary)\\.cc$")
list(APPEND _jax_plugin_method_sources ${_plugin_srcs})
list(APPEND _jax_test_method_sources ${_test_srcs})
string(TOUPPER ${_m} _m_upper)
list(APPEND _jax_method_defs "ENABLE_JAX_METHOD_${_m_upper}")
endforeach()

add_atsplugin(
jax_fingerprint
plugin.cc
context.cc
userarg.cc
header.cc
log.cc
ja3/method.cc
ja3/utils.cc
ja4/method.cc
ja4/ja4.cc
ja4/datasource.cc
ja4/tls_client_hello_summary.cc
ja4h/method.cc
ja4h/ja4h.cc
ja4h/datasource.cc
common/utils.cc
${_jax_plugin_method_sources}
)
target_link_libraries(jax_fingerprint PRIVATE OpenSSL::Crypto OpenSSL::SSL)
target_include_directories(jax_fingerprint BEFORE PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_definitions(jax_fingerprint PRIVATE ${_jax_method_defs} JAX_FINGERPRINT_MAX_METHODS=${_jax_method_count})
verify_global_plugin(jax_fingerprint)
verify_remap_plugin(jax_fingerprint)

if(BUILD_TESTING)
add_executable(
test_jax
ja3/test.cc
ja3/utils.cc
ja4/test.cc
ja4/ja4.cc
ja4/datasource.cc
ja4h/test.cc
ja4h/ja4h.cc
ja4h/datasource.cc
common/utils.cc
)
add_executable(test_jax common/utils.cc ${_jax_test_method_sources})
target_link_libraries(test_jax PRIVATE Catch2::Catch2WithMain OpenSSL::Crypto OpenSSL::SSL)
target_include_directories(test_jax BEFORE PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_definitions(test_jax PRIVATE ${_jax_method_defs})

add_catch2_test(NAME test_jax COMMAND test_jax)
endif()
144 changes: 144 additions & 0 deletions plugins/experimental/jax_fingerprint/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
ATS (Apache Traffic Server) JAx Fingerprint Plugin

User-facing documentation lives in
`doc/admin-guide/plugins/jax_fingerprint.en.rst`. This README covers the
developer side: how the plugin is organised, and what it takes to add a new
fingerprinting method.


Plugin layout
-------------

```
jax_fingerprint/
plugin.cc / plugin.h Plugin entry points, hook handlers, method dispatch.
context.cc / .h JAxContext: per-connection (or per-txn) fingerprint state.
context_map.h Inline fixed-size table that multiplexes JAxContexts
by method name on a single user-arg slot.
userarg.cc / userarg.h Shared user-arg slot reservation and accessors.
header.cc / .h Set/append/remove the fingerprint and via headers.
log.cc / .h Optional log file output for fingerprints.
method.h Abstract Method struct (name, type, callbacks).
Unaware of any concrete method.
config.h PluginConfig, parsing helpers.
common/ Shared utilities (hash stringification, etc.).
ja3/ ja4/ ja4h/ One subdirectory per fingerprinting method.
```

Each method subdirectory follows the conventions described below.


Adding a new method
-------------------

Suppose you want to add a method called `mymethod`. The steps are:

1. Create a directory `mymethod/` under this plugin.

2. Drop in the source files (see *File naming* below for what each name
means to the build):

mymethod/method.h Declares `extern struct Method method;` in
your namespace (e.g. `namespace mymethod`).
mymethod/method.cc Defines `Method method = { "MYMETHOD", ... }`
and the on_client_hello / on_request callback.
This is the file allowed to use TS APIs
(TSVConn, TSClientHello, ...).
mymethod/mymethod.cc/.h Pure algorithm. No TS API.
mymethod/datasource.cc/.h Abstract data source the algorithm consumes
(mirror ja4/datasource.h).
mymethod/test.cc Catch2 unit tests for the algorithm.

3. Register the method in plugin.cc. The dispatcher is a `constexpr` table
gated by `ENABLE_JAX_METHOD_*` macros. Add two entries:

#ifdef ENABLE_JAX_METHOD_MYMETHOD
#include "mymethod/method.h"
#endif

...

constexpr Method const *METHODS[] = {
...
#ifdef ENABLE_JAX_METHOD_MYMETHOD
&mymethod::method,
#endif
};

4. Enable the method at build time:

cmake -B build -DENABLE_JAX_METHODS="ja3;ja4;ja4h;mymethod"

CMake automatically picks up `mymethod/*.cc`, defines
`ENABLE_JAX_METHOD_MYMETHOD`, and bumps `JAX_FINGERPRINT_MAX_METHODS` to
match the list length.

5. Use the method:

jax_fingerprint.so --method MYMETHOD --header x-my-fingerprint


File naming
-----------

The top-level CMakeLists.txt globs `*.cc` from each enabled method directory
and applies these filters:

test.cc Built only into the test_jax binary (Catch2 unit tests).

method.cc Built only into jax_fingerprint.so. This is the one file
in each method directory that is allowed to call TS APIs
(TSVConn, TSClientHello, ...). Keep all TS glue here.

every other Built into BOTH jax_fingerprint.so and test_jax. Keep these
.cc files free of TS API calls so the unit-test binary, which
does not link the TS plugin SDK, still builds.

If you genuinely need to spread TS-API code across more than `method.cc` in
a method directory, you have to extend the exclusion regex in
CMakeLists.txt. There is one such historical exception today --
`ja4/tls_client_hello_summary.cc` -- and the regex carves it out by name.
Prefer not to add new files in that category: split the algorithm so the
TS-facing piece stays in `method.cc` and the pure-data piece is its own
file. If a future case really does justify extending the regex, name the
file something that documents the constraint (for instance `*_ts.cc`) and
update the regex accordingly.

The method directory name (lower case) maps to the macro
`ENABLE_JAX_METHOD_<UPPER>` (so `mymethod` -> `ENABLE_JAX_METHOD_MYMETHOD`).
`Method::name` should be the user-visible spelling (e.g. "MYMETHOD"),
matched against `--method` on the command line.

`Method::name` must reference a string with static storage duration (a
string literal is the natural fit). ContextMap stores `std::string_view`
slot keys that alias `Method::name`; if `name` ever pointed at temporary
storage, the keys would dangle.


Build-time configuration
------------------------

ENABLE_JAX_METHODS Semicolon-separated list of methods to
compile in. Default: "ja3;ja4;ja4h".
Empty list or unknown directory name causes
a FATAL_ERROR at configure time.

JAX_FINGERPRINT_MAX_METHODS Auto-derived from the length of
ENABLE_JAX_METHODS, passed to the plugin as
a compile definition. Bounds the inline
ContextMap slot array. The header has a
fallback `#define JAX_FINGERPRINT_MAX_METHODS 8`
so it is also valid standalone (e.g. for
IDE indexing).


Unit tests vs. AuTest
---------------------

* Catch2 unit tests (`test_jax`) live in each `*/test.cc` and cover pure
algorithm logic. Built when BUILD_TESTING is on; run via ctest or the
test_jax binary directly.

* End-to-end AuTests live under `tests/gold_tests/pluginTest/jax_fingerprint/`
and exercise the plugin via Proxy Verifier replay yamls. They require a
full install (traffic_server, traffic_layout, the plugin .so).
2 changes: 1 addition & 1 deletion plugins/experimental/jax_fingerprint/context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ JAxContext::get_fingerprint() const
}

void
JAxContext::set_fingerprint(const std::string &fingerprint)
JAxContext::set_fingerprint(std::string_view fingerprint)
{
this->_fingerprint = fingerprint;
Dbg(dbg_ctl, "Fingerprint: %s", this->_fingerprint.c_str());
Expand Down
3 changes: 2 additions & 1 deletion plugins/experimental/jax_fingerprint/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include <limits.h>

#include <string>
#include <string_view>

class JAxContext
{
Expand All @@ -36,7 +37,7 @@ class JAxContext
~JAxContext();

const std::string &get_fingerprint() const;
void set_fingerprint(const std::string &fingerprint);
void set_fingerprint(std::string_view fingerprint);

const char *get_addr() const;
const char *get_method_name() const;
Expand Down
Loading