maskit commented on code in PR #12995:
URL: https://github.com/apache/trafficserver/pull/12995#discussion_r2963015153


##########
plugins/experimental/jax_fingerprint/ja4/ja4.h:
##########
@@ -0,0 +1,171 @@
+/** @file
+
+  @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.
+
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <iterator>
+#include <string>
+#include <vector>
+
+namespace JA4
+{
+
+constexpr char PORTION_DELIMITER{'_'};
+
+enum class Protocol {
+  DTLS = 'd',
+  QUIC = 'q',
+  TLS  = 't',
+};
+
+enum class SNI {
+  to_domain = 'd',
+  to_IP     = 'i',
+};
+
+/**
+ * Represents the data sent in a TLS Client Hello needed for JA4 fingerprints.
+ */
+class TLSClientHelloSummary
+{
+public:
+  using difference_type = 
std::iterator_traits<std::vector<std::uint16_t>::iterator>::difference_type;
+
+  Protocol      protocol;
+  std::uint16_t TLS_version{0}; // 0 is not the default, this is only to not 
have it un-initialized.
+  std::string   ALPN;
+
+  std::vector<std::uint16_t> const &get_ciphers() const;
+  void                              add_cipher(std::uint16_t cipher);
+
+  std::vector<std::uint16_t> const &get_extensions() const;
+  void                              add_extension(std::uint16_t extension);
+
+  /**
+   * Get the number of ciphers excluding GREASE values.
+   *
+   * @return Returns the count of non-GREASE ciphers.
+   */
+  difference_type get_cipher_count() const;
+
+  /**
+   * Get the number of extensions excluding GREASE values.
+   *
+   * @return Returns the count of non-GREASE extensions.
+   */
+  difference_type get_extension_count() const;
+
+  /** Get the SNI type, domain or IP.
+   *
+   * @return Returns SNI::to_domain or SNI::to_IP.
+   */
+  SNI get_SNI_type() const;
+
+private:
+  std::vector<std::uint16_t> _ciphers;
+  std::vector<std::uint16_t> _extensions;
+  int                        _extension_count_including_sni_and_alpn{0};
+  SNI                        _SNI_type{SNI::to_IP};
+};
+
+/**
+ * Calculate the a portion of the JA4 fingerprint for the given client hello.
+ *
+ * The a portion of the fingerprint encodes the protocol, TLS version, SNI
+ * type, number of cipher suites, number of extensions, and first ALPN value.
+ *
+ * For more information see:
+ * https://github.com/FoxIO-LLC/ja4/blob/main/technical_details/JA4.md.
+ *
+ * @param TLS_summary The TLS client hello.
+ * @return Returns a string containing the a portion of the JA4 fingerprint.
+ */
+std::string make_JA4_a_raw(TLSClientHelloSummary const &TLS_summary);
+
+/**
+ * Calculate the b portion of the JA4 fingerprint for the given client hello.
+ *
+ * The b portion of the fingerprint is a comma-delimited list of lowercase hex
+ * numbers representing the cipher suites in sorted order. GREASE values are
+ * ignored.
+ *
+ * For more information see:
+ * https://github.com/FoxIO-LLC/ja4/blob/main/technical_details/JA4.md.
+ *
+ * @param TLS_summary The TLS client hello.
+ * @return Returns a string containing the b portion of the JA4 fingerprint.
+ */
+std::string make_JA4_b_raw(TLSClientHelloSummary const &TLS_summary);
+
+/**
+ * Calculate the c portion of the JA4 fingerprint for the given client hello.
+ *
+ * The b portion of the fingerprint is a comma-delimited list of lowercase hex
+ * numbers representing the extensions in sorted order. GREASE values and the
+ * SNI and ALPN extensions are ignored.
+ *
+ * For more information see:
+ * https://github.com/FoxIO-LLC/ja4/blob/main/technical_details/JA4.md.
+ *
+ * @param TLS_summary The TLS client hello.
+ * @return Returns a string containing the c portion of the JA4 fingerprint.
+ */
+std::string make_JA4_c_raw(TLSClientHelloSummary const &TLS_summary);
+
+/**
+ * Calculate the JA4 fingerprint for the given TLS client hello.
+ *
+ * @param TLS_summary The TLS client hello. If there was no ALPN in the
+ * Client Hello, TLS_summary.ALPN should either be empty or set to "00".
+ * Behavior when the number of digits in TLS_summary.TLS_version is greater
+ * than 2, the number of digits in TLS_summary.ALPN is greater than 2
+ * (except when TLS_summary.ALPN is empty) is unspecified.
+ * @param UnaryOp hasher A hash function. For a specification-compliant
+ * JA4 fingerprint, this should be a sha256 hash.
+ * @return Returns a string containing the JA4 fingerprint.
+ */
+template <typename UnaryOp>
+std::string
+make_JA4_fingerprint(TLSClientHelloSummary const &TLS_summary, UnaryOp hasher)
+{
+  std::string result;
+  result.append(make_JA4_a_raw(TLS_summary));
+  result.push_back(JA4::PORTION_DELIMITER);
+  result.append(hasher(make_JA4_b_raw(TLS_summary)).substr(0, 12));
+  result.push_back(JA4::PORTION_DELIMITER);
+  result.append(hasher(make_JA4_c_raw(TLS_summary)).substr(0, 12));
+  return result;
+}
+
+/**
+ * Check whether @a value is a GREASE value.
+ *
+ * These are reserved extensions randomly advertised to keep implementations
+ * well lubricated. They are ignored in all parts of JA4 because of their
+ * random nature.
+ *
+ * @return Returns true if the value is a GREASE value, fales otherwise.

Review Comment:
   fixed



##########
plugins/experimental/jax_fingerprint/header.cc:
##########
@@ -0,0 +1,160 @@
+/** @file
+
+  @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 "plugin.h"
+#include "header.h"
+
+#include "ts/ts.h"
+
+#include <string>
+#include <string_view>
+
+static void
+put_header(TSHttpTxn txnp, const std::string &name, const std::string &value, 
bool overwrite)
+{
+  TSMBuffer bufp;
+  TSMLoc    hdr_loc;
+  if (TS_SUCCESS != TSHttpTxnClientReqGet(txnp, &bufp, &hdr_loc)) {
+    Dbg(dbg_ctl, "Failed to get headers.");
+    return;
+  }
+
+  TSMLoc target = TSMimeHdrFieldFind(bufp, hdr_loc, name.c_str(), 
name.length());
+  if (target == TS_NULL_MLOC) {
+    // Add - Create a new field with the value
+    Dbg(dbg_ctl, "Add %s: %s", name.c_str(), value.c_str());
+    TSMimeHdrFieldCreateNamed(bufp, hdr_loc, name.c_str(), name.length(), 
&target);
+    TSMimeHdrFieldValueStringSet(bufp, hdr_loc, target, -1, value.c_str(), 
value.length());
+    TSMimeHdrFieldAppend(bufp, hdr_loc, target);
+    TSHandleMLocRelease(bufp, hdr_loc, target);
+  } else if (overwrite) {
+    // Replace - Set the value to the first field and remove all duplicate 
fields
+    Dbg(dbg_ctl, "Replace %s field value with %s", name.c_str(), 
value.c_str());
+    TSMLoc tmp   = nullptr;
+    bool   first = true;
+    while (target) {
+      tmp = TSMimeHdrFieldNextDup(bufp, hdr_loc, target);
+      if (first) {
+        first = false;
+        TSMimeHdrFieldValueStringSet(bufp, hdr_loc, target, -1, value.c_str(), 
value.size());
+      } else {
+        TSMimeHdrFieldDestroy(bufp, hdr_loc, target);
+      }
+      TSHandleMLocRelease(bufp, hdr_loc, target);
+      target = tmp;
+    }
+  } else {
+    // Append - Find the last duplicate field and set the value to it
+    Dbg(dbg_ctl, "Append %s to %s field value", value.c_str(), name.c_str());
+    TSMLoc dup = TSMimeHdrFieldNextDup(bufp, hdr_loc, target);
+    while (dup != TS_NULL_MLOC) {
+      TSHandleMLocRelease(bufp, hdr_loc, target);
+      target = dup;
+      dup    = TSMimeHdrFieldNextDup(bufp, hdr_loc, target);
+    }
+    TSMimeHdrFieldValueStringInsert(bufp, hdr_loc, target, -1, value.c_str(), 
value.length());
+    TSHandleMLocRelease(bufp, hdr_loc, target);
+  }
+
+  TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
+}
+
+static void
+put_via_header(TSHttpTxn txnp, const std::string &via_header, bool overwrite)
+{
+  TSMgmtString proxy_name = nullptr;
+  if (TS_SUCCESS == TSMgmtStringGet("proxy.config.proxy_name", &proxy_name)) {
+    put_header(txnp, via_header, proxy_name, overwrite);
+    TSfree(proxy_name);
+  } else {
+    TSError("[%s] Failed to get proxy name for %s, set 
'proxy.config.proxy_name' in records.config", PLUGIN_NAME,
+            via_header.c_str());
+    put_header(txnp, via_header, "unknown", overwrite);
+  }
+}
+
+void
+set_header(TSHttpTxn txnp, const std::string &header, const std::string 
&fingerprint)
+{
+  put_header(txnp, header, fingerprint, true);
+}
+
+void
+append_header(TSHttpTxn txnp, const std::string &header, const std::string 
&fingerprint)
+{
+  put_header(txnp, header, fingerprint, false);
+}
+
+void
+set_via_header(TSHttpTxn txnp, const std::string &via_header)
+{
+  put_via_header(txnp, via_header, true);
+}
+
+void
+append_via_header(TSHttpTxn txnp, const std::string &via_header)
+{
+  put_via_header(txnp, via_header, false);
+}
+
+void
+remove_header(TSHttpTxn txnp, const std::string &header)
+{
+  TSMBuffer bufp;
+  TSMLoc    hdr_loc;
+  if (TS_SUCCESS != TSHttpTxnClientReqGet(txnp, &bufp, &hdr_loc)) {
+    Dbg(dbg_ctl, "Failed to get headers.");
+    return;
+  }
+
+  TSMLoc target = TSMimeHdrFieldFind(bufp, hdr_loc, header.c_str(), 
header.length());
+  if (target != TS_NULL_MLOC) {
+    // Remove all
+    Dbg(dbg_ctl, "Remove all %s field", header.c_str());
+    TSMLoc tmp = nullptr;
+    while (target) {
+      tmp = TSMimeHdrFieldNextDup(bufp, hdr_loc, target);
+      TSMimeHdrFieldDestroy(bufp, hdr_loc, target);
+      TSHandleMLocRelease(bufp, hdr_loc, target);
+      target = tmp;
+    }
+  }
+  TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
+}
+
+bool
+has_header(TSHttpTxn txnp, const std::string &header)
+{
+  TSMBuffer bufp;
+  TSMLoc    hdr_loc;
+  if (TS_SUCCESS != TSHttpTxnClientReqGet(txnp, &bufp, &hdr_loc)) {
+    Dbg(dbg_ctl, "Failed to get headers.");
+    return false;
+  }
+
+  TSMLoc target = TSMimeHdrFieldFind(bufp, hdr_loc, header.c_str(), 
header.length());
+  if (target == TS_NULL_MLOC) {
+    return false;
+  } else {
+    TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
+    return true;
+  }

Review Comment:
   fixed



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to