From 1dbb5f407c8645ebd1cbf2ef97b8ed1ed3ecffee Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Mon, 9 Feb 2026 16:10:16 -0700 Subject: [PATCH 1/9] Add support for custom logging field --- example/plugins/c-api/CMakeLists.txt | 1 + .../c-api/custom_logfield/custom_logfield.cc | 71 +++++++++++++++++++ include/proxy/logging/Log.h | 1 + include/proxy/logging/LogAccess.h | 5 ++ include/proxy/logging/LogField.h | 6 ++ include/ts/apidefs.h.in | 17 ++++- include/ts/ts.h | 3 + src/api/InkAPI.cc | 12 ++++ src/proxy/logging/Log.cc | 14 ++++ src/proxy/logging/LogAccess.cc | 13 ++++ src/proxy/logging/LogField.cc | 34 ++++++++- src/traffic_server/traffic_server.cc | 6 +- 12 files changed, 176 insertions(+), 7 deletions(-) create mode 100644 example/plugins/c-api/custom_logfield/custom_logfield.cc diff --git a/example/plugins/c-api/CMakeLists.txt b/example/plugins/c-api/CMakeLists.txt index 678fe416dde..65594cd391e 100644 --- a/example/plugins/c-api/CMakeLists.txt +++ b/example/plugins/c-api/CMakeLists.txt @@ -65,3 +65,4 @@ add_atsplugin(statistic ./statistic/statistic.cc) add_atsplugin(protocol_stack ./protocol_stack/protocol_stack.cc) add_atsplugin(client_context_dump ./client_context_dump/client_context_dump.cc) target_link_libraries(client_context_dump PRIVATE OpenSSL::SSL libswoc::libswoc) +add_atsplugin(custom_logfield ./custom_logfield/custom_logfield.cc) diff --git a/example/plugins/c-api/custom_logfield/custom_logfield.cc b/example/plugins/c-api/custom_logfield/custom_logfield.cc new file mode 100644 index 00000000000..539a128619e --- /dev/null +++ b/example/plugins/c-api/custom_logfield/custom_logfield.cc @@ -0,0 +1,71 @@ +/** @file + + This plugin counts the number of times every header has appeared. + Maintains separate counts for client and origin headers. + + @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 + +DbgCtl dbg_ctl{"custom_logfield"}; + +char PLUGIN_NAME[] = "header_freq"; +char VENDOR_NAME[] = "Apache Software Foundation"; +char SUPPORT_EMAIL[] = "dev@trafficserver.apache.org"; + +int +marshal_function(TSHttpTxn txnp, char *) +{ + Dbg(dbg_ctl, "Marshaling a custom field"); + TSAssert(txnp); + return 0; +} + +int +unmarshal_function(char **, char *, int) +{ + Dbg(dbg_ctl, "Unarshaling a custom field"); + return 0; +} + +int +lifecycle_event_handler(TSCont /* contp ATS_UNUSED */, TSEvent event, void * /* edata ATS_UNUSED */) +{ + TSAssert(event == TS_EVENT_LIFECYCLE_LOG_INITIAZLIED); + + Dbg(dbg_ctl, "Registering a custom field"); + TSLogFieldRegister("custom log field", "cstm", TS_LOG_TYPE_STRING, marshal_function, unmarshal_function); + + return TS_SUCCESS; +} + +void +TSPluginInit(int /* argc ATS_UNUSED */, const char ** /* argv ATS_UNUSED */) +{ + Dbg(dbg_ctl, "Initializing plugin"); + + TSPluginRegistrationInfo info = {PLUGIN_NAME, VENDOR_NAME, SUPPORT_EMAIL}; + if (TSPluginRegister(&info) != TS_SUCCESS) { + TSError("[%s](%s) Plugin registration failed. \n", PLUGIN_NAME, __FUNCTION__); + } + + TSCont cont = TSContCreate(lifecycle_event_handler, nullptr); + TSLifecycleHookAdd(TS_LIFECYCLE_LOG_INITIAZLIED_HOOK, cont); +} diff --git a/include/proxy/logging/Log.h b/include/proxy/logging/Log.h index 4cf40db0969..e9af78fadce 100644 --- a/include/proxy/logging/Log.h +++ b/include/proxy/logging/Log.h @@ -155,6 +155,7 @@ class Log // main interface static void init(int configFlags = 0); static void init_fields(); + static void init_plugin_fields(); static bool transaction_logging_enabled() diff --git a/include/proxy/logging/LogAccess.h b/include/proxy/logging/LogAccess.h index 0cdd6e1098c..90a8802b6d3 100644 --- a/include/proxy/logging/LogAccess.h +++ b/include/proxy/logging/LogAccess.h @@ -303,6 +303,11 @@ class LogAccess int marshal_milestone_fmt_ms(TSMilestonesType ms, char *buf); int marshal_milestone_diff(TSMilestonesType ms1, TSMilestonesType ms2, char *buf); void set_http_header_field(LogField::Container container, char *field, char *buf, int len); + + // Plugin + int marshal_custom_field(char *buf, LogField::CustomMarshalFunc plugin_marshal_func); + static int unmarshal_custom_field(char **buf, char *dest, int len, LogField::CustomUnmarshalFunc plugin_unmarshal_func); + // // unmarshalling routines // diff --git a/include/proxy/logging/LogField.h b/include/proxy/logging/LogField.h index dcc5b23bb0c..dd44b3d2bf8 100644 --- a/include/proxy/logging/LogField.h +++ b/include/proxy/logging/LogField.h @@ -84,6 +84,8 @@ class LogField using UnmarshalFuncWithSlice = int (*)(char **, char *, int, LogSlice *, LogEscapeType); using UnmarshalFuncWithMap = int (*)(char **, char *, int, const Ptr &); using SetFunc = void (LogAccess::*)(char *, int); + using CustomMarshalFunc = int (*)(void *, char *); + using CustomUnmarshalFunc = int (*)(char **, char *, int); using VarUnmarshalFuncSliceOnly = std::variant; using VarUnmarshalFunc = std::variant; @@ -132,6 +134,8 @@ class LogField LogField(const char *name, const char *symbol, Type type, MarshalFunc marshal, UnmarshalFuncWithMap unmarshal, const Ptr &map, SetFunc _setFunc = nullptr); + LogField(const char *name, const char *symbol, Type type, CustomMarshalFunc custom_marshal, CustomUnmarshalFunc custom_unmarshal); + LogField(const char *field, Container container); LogField(const LogField &rhs); ~LogField(); @@ -207,6 +211,8 @@ class LogField SetFunc m_set_func; TSMilestonesType milestone_from_m_name(); int milestones_from_m_name(TSMilestonesType *m1, TSMilestonesType *m2); + CustomMarshalFunc m_custom_martial_func; + CustomUnmarshalFunc m_custom_unmartial_func; public: LINK(LogField, link); diff --git a/include/ts/apidefs.h.in b/include/ts/apidefs.h.in index 078ce0eb69c..de23be16de6 100644 --- a/include/ts/apidefs.h.in +++ b/include/ts/apidefs.h.in @@ -361,6 +361,7 @@ enum TSEvent { TS_EVENT_LIFECYCLE_MSG = 60105, TS_EVENT_LIFECYCLE_TASK_THREADS_READY = 60106, TS_EVENT_LIFECYCLE_SHUTDOWN = 60107, + TS_EVENT_LIFECYCLE_LOG_INITIAZLIED = 60108, TS_EVENT_INTERNAL_60200 = 60200, TS_EVENT_INTERNAL_60201 = 60201, @@ -578,6 +579,7 @@ enum TSLifecycleHookID { TS_LIFECYCLE_TASK_THREADS_READY_HOOK, TS_LIFECYCLE_SHUTDOWN_HOOK, TS_LIFECYCLE_SSL_SECRET_HOOK, + TS_LIFECYCLE_LOG_INITIAZLIED_HOOK, TS_LIFECYCLE_LAST_HOOK, }; @@ -1083,9 +1085,11 @@ using TSRemapPluginInfo = struct tsapi_remap_plugin_info *; using TSFetchSM = struct tsapi_fetchsm *; -using TSThreadFunc = void *(*)(void *data); -using TSEventFunc = int (*)(TSCont contp, TSEvent event, void *edata); -using TSConfigDestroyFunc = void (*)(void *data); +using TSThreadFunc = void *(*)(void *data); +using TSEventFunc = int (*)(TSCont contp, TSEvent event, void *edata); +using TSConfigDestroyFunc = void (*)(void *data); +using TSLogMarshalCallback = int (*)(TSHttpTxn, char *); +using TSLogUnmarshalCallback = int (*)(char **, char *, int); struct TSFetchEvent { int success_event_id; @@ -1570,6 +1574,13 @@ struct TSResponseAction { bool no_cache; }; +enum TSLogType { + TS_LOG_TYPE_S_INT, + TS_LOG_TYPE_D_INT, + TS_LOG_TYPE_STRING, + TS_LOG_TYPE_IP, +}; + /* -------------------------------------------------------------------------- Init */ diff --git a/include/ts/ts.h b/include/ts/ts.h index d62d5d81561..3bd6aeff8e7 100644 --- a/include/ts/ts.h +++ b/include/ts/ts.h @@ -3224,3 +3224,6 @@ TSReturnCode TSVConnPPInfoGet(TSVConn vconn, uint16_t key, const char **value, i */ TSReturnCode TSVConnPPInfoIntGet(TSVConn vconn, uint16_t key, TSMgmtInt *value); + +TSReturnCode TSLogFieldRegister(std::string_view name, std::string_view symbol, TSLogType type, TSLogMarshalCallback marshal_cb, + TSLogUnmarshalCallback unmarshal_cb); diff --git a/src/api/InkAPI.cc b/src/api/InkAPI.cc index 1c832eb9dd6..042f09fbd23 100644 --- a/src/api/InkAPI.cc +++ b/src/api/InkAPI.cc @@ -8996,3 +8996,15 @@ TSConnectionLimitExemptListClear() { ConnectionTracker::clear_client_exempt_list(); } + +TSReturnCode +TSLogFieldRegister(std::string_view name, std::string_view symbol, TSLogType type, TSLogMarshalCallback marshal_cb, + TSLogUnmarshalCallback unmarshal_cb) +{ + LogField *field = new LogField(name.data(), symbol.data(), static_cast(type), + reinterpret_cast(marshal_cb), unmarshal_cb); + Log::global_field_list.add(field, false); + Log::field_symbol_hash.emplace(symbol.data(), field); + + return TS_SUCCESS; +} diff --git a/src/proxy/logging/Log.cc b/src/proxy/logging/Log.cc index 35d2c871874..9a40d4b6de1 100644 --- a/src/proxy/logging/Log.cc +++ b/src/proxy/logging/Log.cc @@ -54,6 +54,8 @@ #include "tscore/MgmtDefs.h" +#include + #define PERIODIC_TASKS_INTERVAL_FALLBACK 5 // Log global objects @@ -1043,9 +1045,21 @@ Log::init_fields() global_field_list.add(field, false); field_symbol_hash.emplace("vs", field); + Log::init_plugin_fields(); + init_status |= FIELDS_INITIALIZED; } +void +Log::init_plugin_fields() +{ + APIHook *hook = g_lifecycle_hooks->get(TS_LIFECYCLE_LOG_INITIAZLIED_HOOK); + while (hook) { + hook->invoke(TS_EVENT_LIFECYCLE_LOG_INITIAZLIED, nullptr); + hook = hook->next(); + } +} + /*------------------------------------------------------------------------- Initialization functions diff --git a/src/proxy/logging/LogAccess.cc b/src/proxy/logging/LogAccess.cc index 9c0ce48bd48..634db06a3ce 100644 --- a/src/proxy/logging/LogAccess.cc +++ b/src/proxy/logging/LogAccess.cc @@ -473,6 +473,19 @@ LogAccess::marshal_ip(char *dest, sockaddr const *ip) return INK_ALIGN_DEFAULT(len); } +int +LogAccess::marshal_custom_field(char *buf, LogField::CustomMarshalFunc plugin_marshal_func) +{ + int len = plugin_marshal_func(m_http_sm, buf); + return INK_ALIGN_DEFAULT(len); +} + +int +LogAccess::unmarshal_custom_field(char **buf, char *dest, int len, LogField::CustomUnmarshalFunc plugin_unmarshal_func) +{ + return plugin_unmarshal_func(buf, dest, len); +} + inline int LogAccess::unmarshal_with_map(int64_t code, char *dest, int len, const Ptr &map, const char *msg) { diff --git a/src/proxy/logging/LogField.cc b/src/proxy/logging/LogField.cc index f7dcbbd067a..28b9c925c9a 100644 --- a/src/proxy/logging/LogField.cc +++ b/src/proxy/logging/LogField.cc @@ -287,6 +287,34 @@ LogField::LogField(const char *name, const char *symbol, Type type, MarshalFunc strcmp(m_symbol, "cqtn") == 0 || strcmp(m_symbol, "cqtd") == 0 || strcmp(m_symbol, "cqtt") == 0); } +LogField::LogField(const char *name, const char *symbol, Type type, CustomMarshalFunc custom_marshal, + CustomUnmarshalFunc custom_unmarshal) + : m_name(ats_strdup(name)), + m_symbol(ats_strdup(symbol)), + m_type(type), + m_container(NO_CONTAINER), + m_marshal_func(nullptr), + m_unmarshal_func(VarUnmarshalFunc(nullptr)), + m_agg_op(NO_AGGREGATE), + m_agg_cnt(0), + m_agg_val(0), + m_milestone1(TS_MILESTONE_LAST_ENTRY), + m_milestone2(TS_MILESTONE_LAST_ENTRY), + m_time_field(false), + m_alias_map(nullptr), + m_set_func(nullptr), + m_custom_martial_func(custom_marshal), + m_custom_unmartial_func(custom_unmarshal) +{ + ink_assert(m_name != nullptr); + ink_assert(m_symbol != nullptr); + ink_assert(m_type >= 0 && m_type < N_TYPES); + ink_assert(m_marshal_func != (MarshalFunc) nullptr); + + m_time_field = (strcmp(m_symbol, "cqts") == 0 || strcmp(m_symbol, "cqth") == 0 || strcmp(m_symbol, "cqtq") == 0 || + strcmp(m_symbol, "cqtn") == 0 || strcmp(m_symbol, "cqtd") == 0 || strcmp(m_symbol, "cqtt") == 0); +} + TSMilestonesType LogField::milestone_from_m_name() { @@ -523,7 +551,11 @@ unsigned LogField::marshal(LogAccess *lad, char *buf) { if (m_container == NO_CONTAINER) { - return (lad->*m_marshal_func)(buf); + if (m_custom_martial_func == nullptr) { + return (lad->*m_marshal_func)(buf); + } else { + return lad->marshal_custom_field(buf, m_custom_martial_func); + } } switch (m_container) { diff --git a/src/traffic_server/traffic_server.cc b/src/traffic_server/traffic_server.cc index 811539f1433..acbf531f522 100644 --- a/src/traffic_server/traffic_server.cc +++ b/src/traffic_server/traffic_server.cc @@ -2212,9 +2212,6 @@ main(int /* argc ATS_UNUSED */, const char **argv) hostDBProcessor.start(); - // initialize logging (after event and net processor) - Log::init(); - (void)parsePluginConfig(); // Init plugins as soon as logging is ready. @@ -2228,6 +2225,9 @@ main(int /* argc ATS_UNUSED */, const char **argv) pluginInitCheck.notify_one(); } + // initialize logging (after event and net processor) + Log::init(); + if (IpAllow::has_no_rules()) { Error("No ip_allow.yaml entries found. All requests will be denied!"); } From e74d9123f83b0a88fe5cb5f2478f88873e95fdb4 Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Thu, 5 Feb 2026 21:53:03 -0700 Subject: [PATCH 2/9] Add support for PP2_SUBTYPE_SSL_CIPHER --- include/iocore/net/ProxyProtocol.h | 1 + src/iocore/net/ProxyProtocol.cc | 60 +++++++++++++++++++ .../net/unit_tests/test_ProxyProtocol.cc | 8 ++- 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/include/iocore/net/ProxyProtocol.h b/include/iocore/net/ProxyProtocol.h index 2cc4848dfb2..a5f589225ee 100644 --- a/include/iocore/net/ProxyProtocol.h +++ b/include/iocore/net/ProxyProtocol.h @@ -86,6 +86,7 @@ class ProxyProtocol void set_ipv6_addrs(const in6_addr &src_addr, uint16_t src_port, const in6_addr &dst_addr, uint16_t dst_port); std::optional get_tlv(const uint8_t tlvCode) const; + std::optional get_tlv_ssl_cipher() const; ProxyProtocolVersion version = ProxyProtocolVersion::UNDEFINED; uint16_t ip_family = AF_UNSPEC; diff --git a/src/iocore/net/ProxyProtocol.cc b/src/iocore/net/ProxyProtocol.cc index 0422e707717..38d4ae99087 100644 --- a/src/iocore/net/ProxyProtocol.cc +++ b/src/iocore/net/ProxyProtocol.cc @@ -547,6 +547,66 @@ ProxyProtocol::get_tlv(const uint8_t tlvCode) const return std::nullopt; } +/* + * PP2_TYPE_SSL + * struct pp2_tlv_ssl { + * uint8_t client; + * uint32_t verify; + * struct pp2_tlv sub_tlv[0]; + * }; + */ + +std::optional +ProxyProtocol::get_tlv_ssl_cipher() const +{ + if (auto v = tlv.find(PP2_TYPE_SSL); v != tlv.end() && v->second.length() != 0) { + auto ssl = v->second; + + // Is the client connected over TLS + if ((ssl.data()[0] & 0x01) == 0) { + // Not over TLS + return std::nullopt; + } + + // Find PP2_SUBTYPE_SSL_CIPHER + uint16_t len = ssl.length(); + const char *p = ssl.data() + 5; // Skip client (uint8_t) + verify (uint32_t) + const char *end = p + len; + while (p != end) { + if (end - p < 3) { + // The size of a sub TLV entry must be 3 bytes or more + Dbg(dbg_ctl_proxyprotocol_v2, "Remaining data (%ld bytes) is not enough for a sub TLV field", end - p); + return std::nullopt; + } + + // Type + uint8_t type = *p; + p += 1; + + // Length + uint16_t length = ntohs(*reinterpret_cast(p)); + p += 2; + + // Value + if (end - p < length) { + // Does not have enough data + Dbg(dbg_ctl_proxyprotocol_v2, "Remaining data (%ld bytes) is not enough for a TLV field (ID:%u LEN:%hu)", end - p, type, + length); + return std::nullopt; + } + + // Found it? + if (type == PP2_SUBTYPE_SSL_CIPHER) { + Dbg(dbg_ctl_proxyprotocol, "TLV: ID=%u LEN=%hu", type, length); + return std::string_view(p, length); + } + + p += length; + } + } + return std::nullopt; +} + int ProxyProtocol::set_additional_data(std::string_view data) { diff --git a/src/iocore/net/unit_tests/test_ProxyProtocol.cc b/src/iocore/net/unit_tests/test_ProxyProtocol.cc index 70ead688be0..3f7fb526a6c 100644 --- a/src/iocore/net/unit_tests/test_ProxyProtocol.cc +++ b/src/iocore/net/unit_tests/test_ProxyProtocol.cc @@ -303,13 +303,15 @@ TEST_CASE("PROXY Protocol v2 Parser", "[ProxyProtocol][ProxyProtocolv2]") 0x55, 0x49, 0x54, 0x0A, ///< 0x21, ///< version & command 0x11, ///< protocol & family - 0x00, 0x17, ///< len + 0x00, 0x25, ///< len 0xC0, 0x00, 0x02, 0x01, ///< src_addr 0xC6, 0x33, 0x64, 0x01, ///< dst_addr 0xC3, 0x50, ///< src_port 0x01, 0xBB, ///< dst_port 0x01, 0x00, 0x02, 0x68, 0x32, /// PP2_TYPE_ALPN (h2) - 0x02, 0x00, 0x03, 0x61, 0x62, 0x63 /// PP2_TYPE_AUTHORITY (abc) + 0x02, 0x00, 0x03, 0x61, 0x62, 0x63, /// PP2_TYPE_AUTHORITY (abc) + 0x20, 0x00, 0x0B, 0x01, 0x00, 0x00, 0x00, 0x00, /// PP2_TYPE_SSL (client=0x01, verify=0) + 0x23, 0x00, 0x03, 0x58, 0x59, 0x5A, /// PP2_SUBTYPE_SSL_CIPHER (XYZ) }; swoc::TextView tv(reinterpret_cast(raw_data), sizeof(raw_data)); @@ -327,6 +329,8 @@ TEST_CASE("PROXY Protocol v2 Parser", "[ProxyProtocol][ProxyProtocolv2]") CHECK(pp_info.tlv[PP2_TYPE_ALPN] == "h2"); CHECK(pp_info.tlv[PP2_TYPE_AUTHORITY] == "abc"); + + CHECK(pp_info.get_tlv_ssl_cipher() == "XYZ"); } SECTION("TLVs with extra data") From ab3f1d464d9bfc2d27670057f6fc24e70c41cc37 Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Thu, 5 Feb 2026 22:12:27 -0700 Subject: [PATCH 3/9] Add pptc log field for PP2_SUBTYPE_SSL_CIPHER --- doc/admin-guide/logging/formatting.en.rst | 2 ++ include/proxy/logging/LogAccess.h | 1 + src/proxy/logging/Log.cc | 5 +++++ src/proxy/logging/LogAccess.cc | 17 +++++++++++++++++ 4 files changed, 25 insertions(+) diff --git a/doc/admin-guide/logging/formatting.en.rst b/doc/admin-guide/logging/formatting.en.rst index bac1fbeeb68..9b34fe312e0 100644 --- a/doc/admin-guide/logging/formatting.en.rst +++ b/doc/admin-guide/logging/formatting.en.rst @@ -532,6 +532,8 @@ ppd Proxy Protocol Destination IP received via Proxy Protocol context from the Dest IP to the |TS| ppa Proxy Protocol The Authority TLV from Proxy Protocol context from the LB Authority to the |TS| +pptc Proxy Protocol The TLS cipher from Proxy Protocol context from the LB + Authority to the |TS| ===== ============== ========================================================== .. note:: diff --git a/include/proxy/logging/LogAccess.h b/include/proxy/logging/LogAccess.h index 90a8802b6d3..f0845e0a544 100644 --- a/include/proxy/logging/LogAccess.h +++ b/include/proxy/logging/LogAccess.h @@ -270,6 +270,7 @@ class LogAccess int marshal_proxy_protocol_src_ip(char *); // STR int marshal_proxy_protocol_dst_ip(char *); // STR int marshal_proxy_protocol_authority(char *); // STR + int marshal_proxy_protocol_tls_cipher(char *); // STR // named fields from within a http header // diff --git a/src/proxy/logging/Log.cc b/src/proxy/logging/Log.cc index 9a40d4b6de1..9e6b46e7db7 100644 --- a/src/proxy/logging/Log.cc +++ b/src/proxy/logging/Log.cc @@ -1036,6 +1036,11 @@ Log::init_fields() global_field_list.add(field, false); field_symbol_hash.emplace("ppa", field); + field = new LogField("proxy_protocol_tls_cipher", "pptc", LogField::STRING, &LogAccess::marshal_proxy_protocol_tls_cipher, + &LogAccess::unmarshal_str); + global_field_list.add(field, false); + field_symbol_hash.emplace("pptc", field); + field = new LogField("version_build_number", "vbn", LogField::STRING, &LogAccess::marshal_version_build_number, &LogAccess::unmarshal_str); global_field_list.add(field, false); diff --git a/src/proxy/logging/LogAccess.cc b/src/proxy/logging/LogAccess.cc index 634db06a3ce..e472cc8aeb4 100644 --- a/src/proxy/logging/LogAccess.cc +++ b/src/proxy/logging/LogAccess.cc @@ -1677,6 +1677,23 @@ LogAccess::marshal_proxy_protocol_authority(char *buf) return len; } +int +LogAccess::marshal_proxy_protocol_tls_cipher(char *buf) +{ + int len = INK_MIN_ALIGN; + + if (m_http_sm) { + if (auto cipher = m_http_sm->t_state.pp_info.get_tlv_ssl_cipher(); cipher) { + len = padded_length(cipher->size() + 1); + if (buf) { + marshal_str(buf, cipher->data(), len); + buf[cipher->size()] = '\0'; + } + } + } + return len; +} + /*------------------------------------------------------------------------- -------------------------------------------------------------------------*/ int From 7afd3c5f50b873938ced7ddee723e258da2b9fa8 Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Fri, 6 Feb 2026 10:43:10 -0700 Subject: [PATCH 4/9] Add support for PP2_SUBTYPE_SSL_VERSION --- include/iocore/net/ProxyProtocol.h | 3 +++ src/iocore/net/ProxyProtocol.cc | 22 +++++++++++++++---- .../net/unit_tests/test_ProxyProtocol.cc | 6 +++-- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/include/iocore/net/ProxyProtocol.h b/include/iocore/net/ProxyProtocol.h index a5f589225ee..c8504f24b0f 100644 --- a/include/iocore/net/ProxyProtocol.h +++ b/include/iocore/net/ProxyProtocol.h @@ -86,6 +86,7 @@ class ProxyProtocol void set_ipv6_addrs(const in6_addr &src_addr, uint16_t src_port, const in6_addr &dst_addr, uint16_t dst_port); std::optional get_tlv(const uint8_t tlvCode) const; + std::optional get_tlv_ssl_version() const; std::optional get_tlv_ssl_cipher() const; ProxyProtocolVersion version = ProxyProtocolVersion::UNDEFINED; @@ -135,6 +136,8 @@ class ProxyProtocol private: std::string additional_data; + + std::optional _get_tlv_ssl_subtype(int subtype) const; }; const size_t PPv1_CONNECTION_HEADER_LEN_MAX = 108; diff --git a/src/iocore/net/ProxyProtocol.cc b/src/iocore/net/ProxyProtocol.cc index 38d4ae99087..afad67a7b1c 100644 --- a/src/iocore/net/ProxyProtocol.cc +++ b/src/iocore/net/ProxyProtocol.cc @@ -557,7 +557,7 @@ ProxyProtocol::get_tlv(const uint8_t tlvCode) const */ std::optional -ProxyProtocol::get_tlv_ssl_cipher() const +ProxyProtocol::_get_tlv_ssl_subtype(int subtype) const { if (auto v = tlv.find(PP2_TYPE_SSL); v != tlv.end() && v->second.length() != 0) { auto ssl = v->second; @@ -568,10 +568,10 @@ ProxyProtocol::get_tlv_ssl_cipher() const return std::nullopt; } - // Find PP2_SUBTYPE_SSL_CIPHER + // Find the given subtype uint16_t len = ssl.length(); const char *p = ssl.data() + 5; // Skip client (uint8_t) + verify (uint32_t) - const char *end = p + len; + const char *end = p + len + 1; while (p != end) { if (end - p < 3) { // The size of a sub TLV entry must be 3 bytes or more @@ -596,7 +596,7 @@ ProxyProtocol::get_tlv_ssl_cipher() const } // Found it? - if (type == PP2_SUBTYPE_SSL_CIPHER) { + if (type == subtype) { Dbg(dbg_ctl_proxyprotocol, "TLV: ID=%u LEN=%hu", type, length); return std::string_view(p, length); } @@ -607,6 +607,20 @@ ProxyProtocol::get_tlv_ssl_cipher() const return std::nullopt; } +std::optional +ProxyProtocol::get_tlv_ssl_version() const +{ + // The specification only says "the US-ASCII string representation of the TLS version". + // HAProxy sends a string returned by SSL_get_version. + return this->_get_tlv_ssl_subtype(PP2_SUBTYPE_SSL_VERSION); +} + +std::optional +ProxyProtocol::get_tlv_ssl_cipher() const +{ + return this->_get_tlv_ssl_subtype(PP2_SUBTYPE_SSL_CIPHER); +} + int ProxyProtocol::set_additional_data(std::string_view data) { diff --git a/src/iocore/net/unit_tests/test_ProxyProtocol.cc b/src/iocore/net/unit_tests/test_ProxyProtocol.cc index 3f7fb526a6c..f50f6839c4b 100644 --- a/src/iocore/net/unit_tests/test_ProxyProtocol.cc +++ b/src/iocore/net/unit_tests/test_ProxyProtocol.cc @@ -303,15 +303,16 @@ TEST_CASE("PROXY Protocol v2 Parser", "[ProxyProtocol][ProxyProtocolv2]") 0x55, 0x49, 0x54, 0x0A, ///< 0x21, ///< version & command 0x11, ///< protocol & family - 0x00, 0x25, ///< len + 0x00, 0x2B, ///< len 0xC0, 0x00, 0x02, 0x01, ///< src_addr 0xC6, 0x33, 0x64, 0x01, ///< dst_addr 0xC3, 0x50, ///< src_port 0x01, 0xBB, ///< dst_port 0x01, 0x00, 0x02, 0x68, 0x32, /// PP2_TYPE_ALPN (h2) 0x02, 0x00, 0x03, 0x61, 0x62, 0x63, /// PP2_TYPE_AUTHORITY (abc) - 0x20, 0x00, 0x0B, 0x01, 0x00, 0x00, 0x00, 0x00, /// PP2_TYPE_SSL (client=0x01, verify=0) + 0x20, 0x00, 0x11, 0x01, 0x00, 0x00, 0x00, 0x00, /// PP2_TYPE_SSL (client=0x01, verify=0) 0x23, 0x00, 0x03, 0x58, 0x59, 0x5A, /// PP2_SUBTYPE_SSL_CIPHER (XYZ) + 0x21, 0x00, 0x03, 0x54, 0x4C, 0x53, /// PP2_SUBTYPE_SSL_VERSION (TLS) }; swoc::TextView tv(reinterpret_cast(raw_data), sizeof(raw_data)); @@ -331,6 +332,7 @@ TEST_CASE("PROXY Protocol v2 Parser", "[ProxyProtocol][ProxyProtocolv2]") CHECK(pp_info.tlv[PP2_TYPE_AUTHORITY] == "abc"); CHECK(pp_info.get_tlv_ssl_cipher() == "XYZ"); + CHECK(pp_info.get_tlv_ssl_version() == "TLS"); } SECTION("TLVs with extra data") From 71f2fd516e2e5a98621c5019722d10f228d45db4 Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Fri, 6 Feb 2026 10:51:39 -0700 Subject: [PATCH 5/9] Add pptv log field for PP2_SUBTYPE_SSL_VERSION --- doc/admin-guide/logging/formatting.en.rst | 2 ++ include/proxy/logging/LogAccess.h | 1 + src/proxy/logging/Log.cc | 5 +++++ src/proxy/logging/LogAccess.cc | 17 +++++++++++++++++ 4 files changed, 25 insertions(+) diff --git a/doc/admin-guide/logging/formatting.en.rst b/doc/admin-guide/logging/formatting.en.rst index 9b34fe312e0..ca9543971e7 100644 --- a/doc/admin-guide/logging/formatting.en.rst +++ b/doc/admin-guide/logging/formatting.en.rst @@ -534,6 +534,8 @@ ppa Proxy Protocol The Authority TLV from Proxy Protocol context from the LB Authority to the |TS| pptc Proxy Protocol The TLS cipher from Proxy Protocol context from the LB Authority to the |TS| +pptv Proxy Protocol The TLS version from Proxy Protocol context from the LB + Authority to the |TS| ===== ============== ========================================================== .. note:: diff --git a/include/proxy/logging/LogAccess.h b/include/proxy/logging/LogAccess.h index f0845e0a544..ad2545de9ca 100644 --- a/include/proxy/logging/LogAccess.h +++ b/include/proxy/logging/LogAccess.h @@ -271,6 +271,7 @@ class LogAccess int marshal_proxy_protocol_dst_ip(char *); // STR int marshal_proxy_protocol_authority(char *); // STR int marshal_proxy_protocol_tls_cipher(char *); // STR + int marshal_proxy_protocol_tls_version(char *); // STR // named fields from within a http header // diff --git a/src/proxy/logging/Log.cc b/src/proxy/logging/Log.cc index 9e6b46e7db7..b44d76d2799 100644 --- a/src/proxy/logging/Log.cc +++ b/src/proxy/logging/Log.cc @@ -1041,6 +1041,11 @@ Log::init_fields() global_field_list.add(field, false); field_symbol_hash.emplace("pptc", field); + field = new LogField("proxy_protocol_tls_version", "pptv", LogField::STRING, &LogAccess::marshal_proxy_protocol_tls_version, + &LogAccess::unmarshal_str); + global_field_list.add(field, false); + field_symbol_hash.emplace("pptv", field); + field = new LogField("version_build_number", "vbn", LogField::STRING, &LogAccess::marshal_version_build_number, &LogAccess::unmarshal_str); global_field_list.add(field, false); diff --git a/src/proxy/logging/LogAccess.cc b/src/proxy/logging/LogAccess.cc index e472cc8aeb4..477e0868873 100644 --- a/src/proxy/logging/LogAccess.cc +++ b/src/proxy/logging/LogAccess.cc @@ -1694,6 +1694,23 @@ LogAccess::marshal_proxy_protocol_tls_cipher(char *buf) return len; } +int +LogAccess::marshal_proxy_protocol_tls_version(char *buf) +{ + int len = INK_MIN_ALIGN; + + if (m_http_sm) { + if (auto version = m_http_sm->t_state.pp_info.get_tlv_ssl_version(); version) { + len = padded_length(version->size() + 1); + if (buf) { + marshal_str(buf, version->data(), len); + buf[version->size()] = '\0'; + } + } + } + return len; +} + /*------------------------------------------------------------------------- -------------------------------------------------------------------------*/ int From 78eeeeff1cc72849de953364c03e4d071d1bf033 Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Mon, 9 Feb 2026 16:11:59 -0700 Subject: [PATCH 6/9] Fix copy pasta in documentation --- doc/admin-guide/logging/formatting.en.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/admin-guide/logging/formatting.en.rst b/doc/admin-guide/logging/formatting.en.rst index ca9543971e7..b85e6407ada 100644 --- a/doc/admin-guide/logging/formatting.en.rst +++ b/doc/admin-guide/logging/formatting.en.rst @@ -533,9 +533,9 @@ ppd Proxy Protocol Destination IP received via Proxy Protocol context from the ppa Proxy Protocol The Authority TLV from Proxy Protocol context from the LB Authority to the |TS| pptc Proxy Protocol The TLS cipher from Proxy Protocol context from the LB - Authority to the |TS| + TLS Cipher to the |TS| pptv Proxy Protocol The TLS version from Proxy Protocol context from the LB - Authority to the |TS| + TLS version to the |TS| ===== ============== ========================================================== .. note:: From 214207a1acf3c1451f1982b07f8380a0e6183ccb Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Mon, 9 Feb 2026 16:17:25 -0700 Subject: [PATCH 7/9] Change parameter type --- include/iocore/net/ProxyProtocol.h | 2 +- src/iocore/net/ProxyProtocol.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/iocore/net/ProxyProtocol.h b/include/iocore/net/ProxyProtocol.h index c8504f24b0f..5c73019144d 100644 --- a/include/iocore/net/ProxyProtocol.h +++ b/include/iocore/net/ProxyProtocol.h @@ -137,7 +137,7 @@ class ProxyProtocol private: std::string additional_data; - std::optional _get_tlv_ssl_subtype(int subtype) const; + std::optional _get_tlv_ssl_subtype(uint8_t subtype) const; }; const size_t PPv1_CONNECTION_HEADER_LEN_MAX = 108; diff --git a/src/iocore/net/ProxyProtocol.cc b/src/iocore/net/ProxyProtocol.cc index afad67a7b1c..eae3b1aebbd 100644 --- a/src/iocore/net/ProxyProtocol.cc +++ b/src/iocore/net/ProxyProtocol.cc @@ -557,7 +557,7 @@ ProxyProtocol::get_tlv(const uint8_t tlvCode) const */ std::optional -ProxyProtocol::_get_tlv_ssl_subtype(int subtype) const +ProxyProtocol::_get_tlv_ssl_subtype(uint8_t subtype) const { if (auto v = tlv.find(PP2_TYPE_SSL); v != tlv.end() && v->second.length() != 0) { auto ssl = v->second; From 8296706c77339cb00f50d5398032334aff07bfe5 Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Mon, 9 Feb 2026 16:46:49 -0700 Subject: [PATCH 8/9] Fix off by one error --- src/iocore/net/ProxyProtocol.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/iocore/net/ProxyProtocol.cc b/src/iocore/net/ProxyProtocol.cc index eae3b1aebbd..c81bd80cff8 100644 --- a/src/iocore/net/ProxyProtocol.cc +++ b/src/iocore/net/ProxyProtocol.cc @@ -571,7 +571,7 @@ ProxyProtocol::_get_tlv_ssl_subtype(uint8_t subtype) const // Find the given subtype uint16_t len = ssl.length(); const char *p = ssl.data() + 5; // Skip client (uint8_t) + verify (uint32_t) - const char *end = p + len + 1; + const char *end = p + len; while (p != end) { if (end - p < 3) { // The size of a sub TLV entry must be 3 bytes or more From 2aa259f63297253f9c3da930e89b8c10077be43f Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Mon, 9 Feb 2026 17:34:27 -0700 Subject: [PATCH 9/9] Add a length check --- src/iocore/net/ProxyProtocol.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/iocore/net/ProxyProtocol.cc b/src/iocore/net/ProxyProtocol.cc index c81bd80cff8..96939412c8c 100644 --- a/src/iocore/net/ProxyProtocol.cc +++ b/src/iocore/net/ProxyProtocol.cc @@ -568,10 +568,14 @@ ProxyProtocol::_get_tlv_ssl_subtype(uint8_t subtype) const return std::nullopt; } + if (ssl.length() < 5) { + return std::nullopt; + } + // Find the given subtype uint16_t len = ssl.length(); const char *p = ssl.data() + 5; // Skip client (uint8_t) + verify (uint32_t) - const char *end = p + len; + const char *end = ssl.data() + len; while (p != end) { if (end - p < 3) { // The size of a sub TLV entry must be 3 bytes or more