This is an automated email from the ASF dual-hosted git repository.
shinrich pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
The following commit(s) were added to refs/heads/master by this push:
new e3ee3e4 JA3 fingerprint and documentation
e3ee3e4 is described below
commit e3ee3e4aedac2e7af77a3f6af0fc2eb7091e310b
Author: dyrock <[email protected]>
AuthorDate: Mon Jan 14 16:40:08 2019 -0600
JA3 fingerprint and documentation
---
configure.ac | 20 +
doc/admin-guide/plugins/index.en.rst | 4 +
doc/admin-guide/plugins/ja3_fingerprint.en.rst | 49 ++
plugins/Makefile.am | 4 +
plugins/experimental/ja3_fingerprint/Makefile.inc | 19 +
plugins/experimental/ja3_fingerprint/README | 26 ++
.../ja3_fingerprint/ja3_fingerprint.cc | 493 +++++++++++++++++++++
7 files changed, 615 insertions(+)
diff --git a/configure.ac b/configure.ac
index bec8ef5..5ecc6b6 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1247,6 +1247,26 @@ AC_CHECK_FUNC([EVP_MD_CTX_free], [],
LIBS="$saved_LIBS"
+#
+# Check OpenSSL version for JA3 Fingerprint
+#
+AC_MSG_CHECKING([for JA3 compatible OpenSSL version])
+AC_EGREP_CPP(yes, [
+ #include <openssl/opensslv.h>
+ #if (OPENSSL_VERSION_NUMBER < 0x010100000L)
+ yes
+ #elif (OPENSSL_VERSION_NUMBER >= 0x010101000L)
+ yes
+ #endif
+ ], [
+ AC_MSG_RESULT(yes)
+ AS_IF([test "x${enable_experimental_plugins}" = "xyes"], [
+ enable_ja3_plugin=yes
+ ])
+ ], [AC_MSG_RESULT(no)])
+
+AM_CONDITIONAL([BUILD_JA3_PLUGIN], [test "x${enable_ja3_plugin}" = "xyes"])
+
#
# Check for zlib presence and usability
TS_CHECK_ZLIB
diff --git a/doc/admin-guide/plugins/index.en.rst
b/doc/admin-guide/plugins/index.en.rst
index d27e931..1df7cad 100644
--- a/doc/admin-guide/plugins/index.en.rst
+++ b/doc/admin-guide/plugins/index.en.rst
@@ -153,6 +153,7 @@ directory of the |TS| source tree. Experimental plugins can
be compiled by passi
Header Frequency <header_freq.en>
HIPES <hipes.en>
Hook Trace <hook-trace.en>
+ JA3 Fingerprint <ja3_fingerprint.en>
Memcache <memcache.en>
Metalink <metalink.en>
Money Trace <money_trace.en>
@@ -195,6 +196,9 @@ directory of the |TS| source tree. Experimental plugins can
be compiled by passi
:doc:`HIPES <hipes.en>`
Adds support for HTTP Pipes.
+:doc:`JA3 Fingerprint <ja3_fingerprint.en>`
+ Calculates JA3 Fingerprints for incoming SSL traffic.
+
:doc:`Memcache <memcache.en>`
Implements the memcache protocol for cache contents.
diff --git a/doc/admin-guide/plugins/ja3_fingerprint.en.rst
b/doc/admin-guide/plugins/ja3_fingerprint.en.rst
new file mode 100644
index 0000000..9be99f5
--- /dev/null
+++ b/doc/admin-guide/plugins/ja3_fingerprint.en.rst
@@ -0,0 +1,49 @@
+.. Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+
+
+.. include:: ../../common.defs
+
+.. _admin-plugins-ja3_fingerprint:
+
+
+JA3 Fingerprint Plugin
+*******************
+
+Description
+===========
+
+``JA3 Fingerprint`` calculates JA3 fingerprints for incoming SSL traffic. "JA3
is a method for creating SSL/TLS client fingerprints" by concatenating values
in ClientHello packet and MD5 hash the result to produce a 32 character
fingerprint. Malwares tend to use the same encryption code/client, which makes
it an effective way to detect malicious clients. More info about ja3 is
available: https://github.com/salesforce/ja3.
+
+The calculated JA3 fingerprints are then appended to upstream request (to be
processed at upstream) and/or logged locally (depending on the config).
+
+Plugin Configuration
+====================
+.. program:: ja3_fingerprint.so
+
+* ``ja3_fingerprint`` can be used as a global/remap plugin and is configured
via :file:`plugin.config` or :file:`remap.config`.
+ .. option:: --ja3raw
+
+ (`optional`, default:unused) - enables raw fingerprints header. With this
option, the plugin will append additional header `X-JA3-Raw` to proxy request.
+
+ .. option:: --ja3log
+
+ (`optional`, default:unused) - enables local logging. With this option, the
plugin will log JA3 info to :file:`ja3_fingerprint.log` in the standard logging
directory. The format is: [time] [client IP] [JA3 string] [JA3 hash]
+
+Requirement
+=============
+Won't compile against OpenSSL 1.1.0 due to API changes and opaque structures.
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index 9eee0dd..c00a4fb 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -84,6 +84,10 @@ include experimental/tls_bridge/Makefile.inc
include experimental/url_sig/Makefile.inc
include experimental/prefetch/Makefile.inc
+if BUILD_JA3_PLUGIN
+include experimental/ja3_fingerprint/Makefile.inc
+endif
+
if BUILD_URI_SIGNING_PLUGIN
include experimental/uri_signing/Makefile.inc
endif
diff --git a/plugins/experimental/ja3_fingerprint/Makefile.inc
b/plugins/experimental/ja3_fingerprint/Makefile.inc
new file mode 100644
index 0000000..b29be9f
--- /dev/null
+++ b/plugins/experimental/ja3_fingerprint/Makefile.inc
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+pkglib_LTLIBRARIES += experimental/ja3_fingerprint/ja3_fingerprint.la
+
+experimental_ja3_fingerprint_ja3_fingerprint_la_SOURCES =
experimental/ja3_fingerprint/ja3_fingerprint.cc
diff --git a/plugins/experimental/ja3_fingerprint/README
b/plugins/experimental/ja3_fingerprint/README
new file mode 100644
index 0000000..fdb85c7
--- /dev/null
+++ b/plugins/experimental/ja3_fingerprint/README
@@ -0,0 +1,26 @@
+ATS (Apache Traffic Server) JA3 Fingerprint Plugin
+
+General description
+--------------------
+1. JA3
+This plugin looks at all incoming SSL/TLS clientHello and calculates JA3
fingerprint for each client.
+It then performs
+1) logging JA3 string and its MD5 hash to `ja3_fingerprint.log` in the
standard logging directory;
+2) appending `X-JA3-Sig` and/or `X-JA3-Raw` headers to upstream request
(depending on the config)
+
+The log file format is as follows:
+
+[time] [client IP] [JA3 string] [MD5 Hash]
+
+2. plugin.config
+In plugin.config, supply name of the plugin and options
+Example: ja3_fingerprint.so --ja3raw --ja3log
+Add flag --ja3raw if `X-JA3-Raw` is desired other than only `X-JA3-Sig`.
+Add flag --ja3log if local logging in standard logging directory is desired.
+
+3. remap.config
+This plugin can also be used as a remap plugin. For each remap rule, add
plugin and parameter field. For example:
+map http://from.com http://to.com @plugin=ja3_fingerprint.so
[@pparam=--ja3raw] [@pparam=--ja3log]
+
+4. Requirement
+Won't compile against OpenSSL 1.1.0 due to APIs and opaque structures.
diff --git a/plugins/experimental/ja3_fingerprint/ja3_fingerprint.cc
b/plugins/experimental/ja3_fingerprint/ja3_fingerprint.cc
new file mode 100644
index 0000000..16589c4
--- /dev/null
+++ b/plugins/experimental/ja3_fingerprint/ja3_fingerprint.cc
@@ -0,0 +1,493 @@
+/** @ja3_fingerprint.cc
+ Plugin JA3 Fingerprint calculates JA3 signatures for incoming SSL traffic.
+ @section license License
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+#include <inttypes.h>
+#include <cstdlib>
+#include <cstdio>
+#include <cstring>
+#include <cerrno>
+#include <stdlib.h>
+#include <getopt.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <algorithm>
+#include <fstream>
+#include <sstream>
+#include <string>
+#include <unordered_set>
+#include <unordered_map>
+#include <memory>
+#include <regex>
+
+#include "ts/ts.h"
+#include "ts/remap.h"
+
+#ifdef OPENSSL_NO_SSL_INTERN
+#undef OPENSSL_NO_SSL_INTERN
+#endif
+
+#include <openssl/ssl.h>
+#include <openssl/md5.h>
+#include <openssl/opensslv.h>
+
+// Get 16bit big endian order and update pointer
+#define n2s(c, s) ((s = (((unsigned int)(c[0])) << 8) | (((unsigned
int)(c[1])))), c += 2)
+
+const char *PLUGIN_NAME = "ja3_fingerprint";
+static TSTextLogObject pluginlog;
+static int ja3_idx = -1;
+static int enable_raw = 0;
+static int enable_log = 0;
+
+// GREASE table as in ja3
+static const std::unordered_set<uint16_t> GREASE_table = {0x0a0a, 0x1a1a,
0x2a2a, 0x3a3a, 0x4a4a, 0x5a5a, 0x6a6a, 0x7a7a,
+ 0x8a8a, 0x9a9a,
0xaaaa, 0xbaba, 0xcaca, 0xdada, 0xeaea, 0xfafa};
+
+struct ja3_data {
+ std::string ja3_string;
+ char md5String[33];
+ char ip_addr[INET6_ADDRSTRLEN];
+};
+
+struct ja3_remap_info {
+ int raw = false;
+ int log = false;
+ TSCont handler = nullptr;
+
+ ~ja3_remap_info()
+ {
+ if (handler) {
+ TSContDestroy(handler);
+ handler = nullptr;
+ }
+ }
+};
+
+static int
+custom_get_ja3_prefixed(int unit, const unsigned char *&data, int len,
std::string &result)
+{
+ int cnt, tmp;
+ bool first = true;
+ // Extract each entry and append to result string
+ for (cnt = 0; cnt < len; cnt += unit) {
+ if (unit == 1) {
+ tmp = *(data++);
+ } else {
+ n2s(data, tmp);
+ }
+
+ // Check for GREASE for 16-bit values, append only if non-GREASE
+ if (unit != 2 || GREASE_table.find(tmp) == GREASE_table.end()) {
+ if (!first) {
+ result += '-';
+ }
+ first = false;
+ result += std::to_string(tmp);
+ }
+ }
+ return 0;
+}
+
+char *
+getIP(sockaddr const *s_sockaddr, char res[INET6_ADDRSTRLEN])
+{
+ res[0] = '\0';
+
+ if (s_sockaddr == nullptr) {
+ return nullptr;
+ }
+
+ switch (s_sockaddr->sa_family) {
+ case AF_INET: {
+ const struct sockaddr_in *s_sockaddr_in = reinterpret_cast<const struct
sockaddr_in *>(s_sockaddr);
+ inet_ntop(AF_INET, &s_sockaddr_in->sin_addr, res, INET_ADDRSTRLEN);
+ } break;
+ case AF_INET6: {
+ const struct sockaddr_in6 *s_sockaddr_in6 = reinterpret_cast<const struct
sockaddr_in6 *>(s_sockaddr);
+ inet_ntop(AF_INET6, &s_sockaddr_in6->sin6_addr, res, INET6_ADDRSTRLEN);
+ } break;
+ default:
+ return nullptr;
+ }
+
+ return res[0] ? res : nullptr;
+}
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+// Parsing clientHello to get ja3 string
+// No error checking or handling because this should be called after openSSL
has done all checks and
+// returned successfully
+static std::string
+custom_get_ja3(SSL *s)
+{
+ TSDebug(PLUGIN_NAME, "Entering custom_get_ja3()...");
+ std::string ja3;
+ const unsigned char *p, *d;
+ int i, j, len;
+
+ // ClientHello buf and len
+ d = p = (unsigned char *)s->init_msg;
+ long n = s->init_num;
+
+ // Get version
+ int version = (((int)p[0]) << 8) | (int)p[1];
+ ja3 += std::to_string(version) + ',';
+ p += 2;
+
+ // Skip client random
+ p += SSL3_RANDOM_SIZE;
+
+ // Skip session id
+ j = *(p++);
+ p += j;
+
+ // No DTLS handling
+
+ // Get cipher suites
+ n2s(p, len);
+ custom_get_ja3_prefixed(2, p, len, ja3);
+ ja3 += ',';
+
+ // Skip compression
+ i = *(p++);
+ p += i;
+
+ // Get extensions
+ uint16_t type;
+ int size;
+ std::string eclist, ecpflist;
+
+ // Skip length blob
+ p += 2;
+ bool first = true;
+ while (p < d + n) {
+ // Each extension blob is comprised of [2bytes] type + [2bytes] size +
[size bytes] data
+ n2s(p, type);
+ n2s(p, size);
+
+ // Elliptic curve points
+ if (type == 0x0a) {
+ const unsigned char *sdata = p;
+ n2s(sdata, len);
+ custom_get_ja3_prefixed(2, sdata, len, eclist);
+ }
+ // Elliptic curve point formats
+ else if (type == 0x0b) {
+ const unsigned char *sdata = p;
+ len = *(sdata++);
+ custom_get_ja3_prefixed(1, sdata, len, ecpflist);
+ }
+
+ // Update pointer
+ p += size;
+
+ // Update ja3 string with valid extension type
+ if (GREASE_table.find(type) == GREASE_table.end()) {
+ if (!first) {
+ ja3 += '-';
+ }
+ first = false;
+ ja3 += std::to_string(type);
+ }
+ }
+
+ // Append eclist and ecpflist
+ ja3 += "," + eclist + "," + ecpflist;
+ TSDebug(PLUGIN_NAME, "ja3 string: %s", ja3.c_str());
+ return ja3;
+}
+#elif OPENSSL_VERSION_NUMBER >= 0x10101000L
+static std::string
+custom_get_ja3(SSL *s)
+{
+ std::string ja3;
+ size_t len;
+ const unsigned char *p;
+
+ // Get version
+ unsigned int version = SSL_client_hello_get0_legacy_version(s);
+ ja3 += std::to_string(version) + ',';
+
+ // Get cipher suites
+ len = SSL_client_hello_get0_ciphers(s, &p);
+ custom_get_ja3_prefixed(2, p, len, ja3);
+
+ // Get extenstions
+ int *o;
+ std::string eclist, ecpflist;
+ if (SSL_client_hello_get0_ext(s, 0x0a, &p, &len) == 1) {
+ custom_get_ja3_prefixed(2, p, len, eclist);
+ }
+ if (SSL_client_hello_get0_ext(s, 0x0b, &p, &len) == 1) {
+ custom_get_ja3_prefixed(1, p, len, ecpflist);
+ }
+ if (SSL_client_hello_get1_extensions_present(s, &o, &len) == 1) {
+ bool first = true;
+ for (size_t i = 0; i < len; i++) {
+ int type = o[i];
+ if (GREASE_table.find(type) == GREASE_table.end()) {
+ if (!first) {
+ ja3 += '-';
+ }
+ first = false;
+ ja3 += std::to_string(type);
+ }
+ }
+ }
+ ja3 += "," + eclist + "," + ecpflist;
+ return ja3;
+}
+#else
+#error OpenSSL cannot be 1.1.0
+#endif
+
+static int
+client_hello_ja3_handler(TSCont contp, TSEvent event, void *edata)
+{
+ TSVConn ssl_vc = reinterpret_cast<TSVConn>(edata);
+ switch (event) {
+ case TS_EVENT_SSL_CLIENT_HELLO: {
+ TSSslConnection sslobj = TSVConnSSLConnectionGet(ssl_vc);
+
+ // OpenSSL handle
+ SSL *ssl = reinterpret_cast<SSL *>(sslobj);
+
+ ja3_data *data = new ja3_data;
+ data->ja3_string.append(custom_get_ja3(ssl));
+ getIP(TSNetVConnRemoteAddrGet(ssl_vc), data->ip_addr);
+
+ TSVConnArgSet(ssl_vc, ja3_idx, static_cast<void *>(data));
+ TSDebug(PLUGIN_NAME, "client_hello_ja3_handler(): JA3: %s",
data->ja3_string.c_str());
+
+ // MD5 hash
+ unsigned char digest[MD5_DIGEST_LENGTH];
+ MD5((unsigned char *)data->ja3_string.c_str(), data->ja3_string.length(),
digest);
+
+ for (int i = 0; i < 16; i++) {
+ sprintf(&(data->md5String[i * 2]), "%02x", (unsigned int)digest[i]);
+ }
+ TSDebug(PLUGIN_NAME, "Fingerprint: %s", data->md5String);
+ break;
+ }
+ case TS_EVENT_VCONN_CLOSE: {
+ // Clean up
+ ja3_data *data = static_cast<ja3_data *>(TSVConnArgGet(ssl_vc, ja3_idx));
+
+ if (data == nullptr) {
+ TSDebug(PLUGIN_NAME, "client_hello_ja3_handler(): Failed to retrieve ja3
data at VCONN_CLOSE.");
+ return TS_ERROR;
+ }
+
+ TSVConnArgSet(ssl_vc, ja3_idx, nullptr);
+
+ delete data;
+ break;
+ }
+ default: {
+ TSDebug(PLUGIN_NAME, "client_hello_ja3_handler(): Unexpected event.");
+ break;
+ }
+ }
+ TSVConnReenable(ssl_vc);
+ return TS_SUCCESS;
+}
+
+static int
+req_hdr_ja3_handler(TSCont contp, TSEvent event, void *edata)
+{
+ TSHttpTxn txnp = nullptr;
+ TSHttpSsn ssnp = nullptr;
+ TSVConn vconn = nullptr;
+ if ((txnp = static_cast<TSHttpTxn>(edata)) == nullptr || (ssnp =
TSHttpTxnSsnGet(txnp)) == nullptr ||
+ (vconn = TSHttpSsnClientVConnGet(ssnp)) == nullptr) {
+ TSDebug(PLUGIN_NAME, "req_hdr_ja3_handler(): Failure to retrieve
txn/ssn/vconn object.");
+ TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
+ return TS_SUCCESS;
+ }
+
+ // Retrieve ja3_data from vconn args
+ ja3_data *data = static_cast<ja3_data *>(TSVConnArgGet(vconn, ja3_idx));
+ if (data) {
+ // Decide global or remap
+ ja3_remap_info *info = static_cast<ja3_remap_info *>(TSContDataGet(contp));
+ bool raw_flag = info ? info->raw : enable_raw;
+ bool log_flag = info ? info->log : enable_log;
+ TSDebug(PLUGIN_NAME, "req_hdr_ja3_handler(): Found ja3 string.");
+
+ // Get handle to headers
+ TSMBuffer bufp;
+ TSMLoc hdr_loc, field_loc;
+ TSAssert(TS_SUCCESS == TSHttpTxnServerReqGet(txnp, &bufp, &hdr_loc));
+
+ // Add JA3 md5 fingerprints
+ TSMimeHdrFieldCreateNamed(bufp, hdr_loc, "X-JA3-Sig", 9, &field_loc);
+ TSMimeHdrFieldValueStringSet(bufp, hdr_loc, field_loc, -1,
data->md5String, 32);
+ TSMimeHdrFieldAppend(bufp, hdr_loc, field_loc);
+ TSHandleMLocRelease(bufp, hdr_loc, field_loc);
+
+ // If raw string is configured, added JA3 raw string to header as well
+ if (raw_flag) {
+ TSMimeHdrFieldCreateNamed(bufp, hdr_loc, "X-JA3-Raw", 9, &field_loc);
+ TSMimeHdrFieldValueStringSet(bufp, hdr_loc, field_loc, -1,
data->ja3_string.data(), data->ja3_string.size());
+ TSMimeHdrFieldAppend(bufp, hdr_loc, field_loc);
+ TSHandleMLocRelease(bufp, hdr_loc, field_loc);
+ }
+ TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
+
+ // Write to logfile
+ if (log_flag) {
+ TSTextLogObjectWrite(pluginlog, "Client IP: %s\tJA3: %.*s\tMD5: %.*s",
data->ip_addr,
+ static_cast<int>(data->ja3_string.size()),
data->ja3_string.data(), 32, data->md5String);
+ }
+ } else {
+ TSDebug(PLUGIN_NAME, "req_hdr_ja3_handler(): ja3 data not set. Not SSL
vconn. Abort.");
+ }
+ TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
+ return TS_SUCCESS;
+}
+
+static bool
+read_config_option(int argc, const char *argv[], int &raw, int &log)
+{
+ static const struct option longopts[] = {{"ja3raw", no_argument, &raw, 1},
{"ja3log", no_argument, &log, 1}, {0, 0, 0, 0}};
+
+ int opt = 0;
+ while ((opt = getopt_long(argc, (char *const *)argv, "", longopts, nullptr))
>= 0) {
+ switch (opt) {
+ case '?':
+ TSDebug(PLUGIN_NAME, "read_config_option(): Unrecognized command
arguments.");
+ case 0:
+ case -1:
+ break;
+ default:
+ TSDebug(PLUGIN_NAME, "read_config_option(): Unexpected options error.");
+ return false;
+ }
+ }
+
+ TSDebug(PLUGIN_NAME, "read_config_option(): ja3 raw is %s", (raw == 1) ?
"enabled" : "disabled");
+ TSDebug(PLUGIN_NAME, "read_config_option(): ja3 logging is %s", (log == 1) ?
"enabled" : "disabled");
+ return true;
+}
+
+void
+TSPluginInit(int argc, const char *argv[])
+{
+ TSDebug(PLUGIN_NAME, "Initializing plugin");
+
+ TSPluginRegistrationInfo info;
+
+ info.plugin_name = PLUGIN_NAME;
+ info.vendor_name = "Oath";
+ info.support_email = "[email protected]";
+
+ // Options
+ if (!read_config_option(argc, argv, enable_raw, enable_log)) {
+ return;
+ }
+
+ if (TSPluginRegister(&info) != TS_SUCCESS) {
+ TSError("[%s] Unable to initialize plugin. Failed to register.",
PLUGIN_NAME);
+ } else {
+ if (enable_log && !pluginlog) {
+ TSAssert(TS_SUCCESS == TSTextLogObjectCreate(PLUGIN_NAME,
TS_LOG_MODE_ADD_TIMESTAMP, &pluginlog));
+ TSDebug(PLUGIN_NAME, "log object created successfully");
+ }
+ // SNI handler
+ TSCont ja3_cont = TSContCreate(client_hello_ja3_handler, nullptr);
+ TSVConnArgIndexReserve(PLUGIN_NAME, "used to pass ja3", &ja3_idx);
+ TSHttpHookAdd(TS_SSL_CLIENT_HELLO_HOOK, ja3_cont);
+ TSHttpHookAdd(TS_VCONN_CLOSE_HOOK, ja3_cont);
+ TSHttpHookAdd(TS_HTTP_SEND_REQUEST_HDR_HOOK,
TSContCreate(req_hdr_ja3_handler, nullptr));
+ }
+
+ return;
+}
+
+// Remap Part
+TSReturnCode
+TSRemapInit(TSRemapInterface *api_info, char *errbuf, int errbuf_size)
+{
+ TSDebug(PLUGIN_NAME, "JA3 Remap Plugin initializing..");
+
+ // Check if there is config conflict as both global and remap plugin
+ if (ja3_idx >= 0) {
+ TSError(PLUGIN_NAME, "TSRemapInit(): JA3 configured as both global and
remap. Check plugin.config.");
+ return TS_ERROR;
+ }
+
+ // Set up SNI handler for all TLS connections
+ TSCont ja3_cont = TSContCreate(client_hello_ja3_handler, nullptr);
+ TSVConnArgIndexReserve(PLUGIN_NAME, "Used to pass ja3", &ja3_idx);
+ TSHttpHookAdd(TS_SSL_CLIENT_HELLO_HOOK, ja3_cont);
+ TSHttpHookAdd(TS_VCONN_CLOSE_HOOK, ja3_cont);
+
+ return TS_SUCCESS;
+}
+
+TSReturnCode
+TSRemapNewInstance(int argc, char *argv[], void **ih, char * /* errbuf
ATS_UNUSED */, int /* errbuf_size ATS_UNUSED */)
+{
+ TSDebug(PLUGIN_NAME, "New instance for client matching %s to %s", argv[0],
argv[1]);
+ ja3_remap_info *pri = new ja3_remap_info;
+
+ // Parse parameters
+ if (!read_config_option(argc - 1, const_cast<const char **>(argv + 1),
pri->raw, pri->log)) {
+ TSDebug(PLUGIN_NAME, "TSRemapNewInstance(): Bad arguments");
+ return TS_ERROR;
+ }
+
+ if (pri->log && !pluginlog) {
+ TSAssert(TS_SUCCESS == TSTextLogObjectCreate(PLUGIN_NAME,
TS_LOG_MODE_ADD_TIMESTAMP, &pluginlog));
+ TSDebug(PLUGIN_NAME, "log object created successfully");
+ }
+
+ // Create continuation
+ pri->handler = TSContCreate(req_hdr_ja3_handler, nullptr);
+ TSContDataSet(pri->handler, pri);
+
+ // Pass to other remap plugin functions
+ *ih = static_cast<void *>(pri);
+ return TS_SUCCESS;
+}
+
+TSRemapStatus
+TSRemapDoRemap(void *ih, TSHttpTxn rh, TSRemapRequestInfo *rri)
+{
+ auto pri = static_cast<ja3_remap_info *>(ih);
+
+ // On remap, set up handler at send req hook to send JA3 data as header
+ if (!pri || !rri || !(pri->handler)) {
+ TSError("[%s] TSRemapDoRemap(): Invalid private data or RRI or handler.",
PLUGIN_NAME);
+ } else {
+ TSHttpTxnHookAdd(rh, TS_HTTP_SEND_REQUEST_HDR_HOOK, pri->handler);
+ }
+
+ return TSREMAP_NO_REMAP;
+}
+
+void
+TSRemapDeleteInstance(void *ih)
+{
+ auto pri = static_cast<ja3_remap_info *>(ih);
+ if (pri) {
+ delete pri;
+ }
+ ih = nullptr;
+}