From 7e385880d41fa0a410709933f774c482a8b34858 Mon Sep 17 00:00:00 2001 From: bneradt Date: Fri, 7 Aug 2026 13:53:01 -0500 Subject: [PATCH 1/2] Fix overridable configuration testing The overridable configuration regression test writes arbitrary values to each setting and expects exact round trips. That forced the server match converter to retain out-of-range enum values, which can break connection-group hashing and equality in production. This patch replaces the regression test with Catch coverage that checks the full name/key/type map and representative public setter and getter paths with valid values. It also adds focused converter coverage and clamps server match values to its documented enum range, removing the production workaround for the flawed test. Fixes: #4210 --- src/api/CMakeLists.txt | 33 ++++ src/api/InkAPITest.cc | 139 +---------------- .../unit_tests/test_HttpOverridableConfig.cc | 144 ++++++++++++++++++ src/iocore/net/CMakeLists.txt | 1 + src/iocore/net/ConnectionTracker.cc | 8 +- .../net/unit_tests/test_ConnectionTracker.cc | 63 ++++++++ 6 files changed, 246 insertions(+), 142 deletions(-) create mode 100644 src/api/unit_tests/test_HttpOverridableConfig.cc create mode 100644 src/iocore/net/unit_tests/test_ConnectionTracker.cc diff --git a/src/api/CMakeLists.txt b/src/api/CMakeLists.txt index ca3ea344862..fcfc12b67dc 100644 --- a/src/api/CMakeLists.txt +++ b/src/api/CMakeLists.txt @@ -63,4 +63,37 @@ if(APPLE) target_link_options(tsapi PRIVATE -undefined dynamic_lookup) endif() +if(BUILD_TESTING) + add_executable(test_api unit_tests/test_HttpOverridableConfig.cc) + target_link_libraries( + test_api + PRIVATE Catch2::Catch2WithMain + ts::tscore + ts::tsapi + ts::overridable_txn_vars + ts::tsutil + ts::http + ts::http_remap + ts::http2 + ts::logging + ts::hdrs + ts::diagsconfig + ts::inkutils + ts::inkdns + ts::inkhostdb + ts::inkcache + ts::aio + ts::proxy + ts::inknet + ts::records + ts::inkevent + libswoc::libswoc + ts::jsonrpc_protocol + ts::jsonrpc_server + ts::rpcpublichandlers + ts::configmanager + ) + add_catch2_test(NAME test_api COMMAND test_api) +endif() + clang_tidy_check(tsapi) diff --git a/src/api/InkAPITest.cc b/src/api/InkAPITest.cc index 881f0d91ebb..3ff7426e200 100644 --- a/src/api/InkAPITest.cc +++ b/src/api/InkAPITest.cc @@ -54,8 +54,6 @@ #include "records/RecHttp.h" #include "proxy/http/HttpSM.h" -#include "proxy/http/OverridableConfigDefs.h" -#include "iocore/net/ConnectionTracker.h" #include "tscore/TestBox.h" namespace @@ -94,7 +92,8 @@ DbgCtl dbg_ctl_SockClient{"SockClient"}; #define ERROR_BODY "TESTING ERROR PAGE" #define TRANSFORM_APPEND_STRING "This is a transformed response" -extern int dns_failover_period; +extern int dns_failover_period; +extern ClassAllocator httpSMAllocator; ////////////////////////////////////////////////////////////////////////////// // STRUCTURES @@ -8707,140 +8706,6 @@ EXCLUSIVE_REGRESSION_TEST(SDK_API_TSHttpConnectServerIntercept)(RegressionTest * return; } -//////////////////////////////////////////////// -// SDK_API_OVERRIDABLE_CONFIGS -// -// Unit Test for API: TSHttpTxnConfigFind -// TSHttpTxnConfigIntSet -// TSHttpTxnConfigIntGet -// TSHttpTxnConfigFloatSet -// TSHttpTxnConfigFloatGet -// TSHttpTxnConfigStringSet -// TSHttpTxnConfigStringGet -//////////////////////////////////////////////// - -// Generate the SDK_Overridable_Configs array from the X-macro. -// The order MUST match TSOverridableConfigKey enum order (enforced by static_assert). -// clang-format off -#define X_SDK_CONFIG(CONFIG_KEY, MEMBER, RECORD_NAME, DATA_TYPE, CONV) RECORD_NAME, -std::array SDK_Overridable_Configs = {{ - OVERRIDABLE_CONFIGS(X_SDK_CONFIG) -}}; -#undef X_SDK_CONFIG -// clang-format on - -static_assert(SDK_Overridable_Configs.size() == TS_CONFIG_LAST_ENTRY, - "SDK_Overridable_Configs size must match TS_CONFIG_LAST_ENTRY"); - -extern ClassAllocator httpSMAllocator; - -REGRESSION_TEST(SDK_API_OVERRIDABLE_CONFIGS)(RegressionTest *test, int /* atype ATS_UNUSED */, int *pstatus) -{ - TSOverridableConfigKey key; - TSRecordDataType type; - HttpSM *s = THREAD_ALLOC(httpSMAllocator, this_thread()); - bool success = true; - TSHttpTxn txnp = reinterpret_cast(s); - InkRand generator(17); - TSMgmtInt ival_read, ival_rand; - TSMgmtFloat fval_read, fval_rand; - const char *sval_read; - const char *test_string = "The Apache Traffic Server"; - int len; - - s->init(); - s->mutex = new_ProxyMutex(); - SCOPED_MUTEX_LOCK(lock, s->mutex, this_ethread()); - - HttpCacheSM *c_sm = &(s->get_cache_sm()); - c_sm->init(s, s->mutex); - - *pstatus = REGRESSION_TEST_INPROGRESS; - for (int i = 0; i < static_cast(SDK_Overridable_Configs.size()); ++i) { - std::string_view conf{SDK_Overridable_Configs[i]}; - - if (TS_SUCCESS == TSHttpTxnConfigFind(conf.data(), -1, &key, &type)) { - if (key != i) { - SDK_RPRINT(test, "TSHttpTxnConfigFind", "TestCase1", TC_FAIL, "Failed on %s, expected %d, got %d", conf.data(), i, key); - success = false; - continue; - } - } else { - SDK_RPRINT(test, "TSHttpTxnConfigFind", "TestCase1", TC_FAIL, "Call returned unexpected TS_ERROR for %s", conf.data()); - success = false; - continue; - } - - if (TS_SUCCESS == TSHttpTxnConfigFind(conf.data(), conf.size(), &key, &type)) { - if (key != i) { - SDK_RPRINT(test, "TSHttpTxnConfigFind", "TestCase1", TC_FAIL, "Failed on %s, expected %d, got %d", conf.data(), i, key); - success = false; - continue; - } - } else { - SDK_RPRINT(test, "TSHttpTxnConfigFind", "TestCase1", TC_FAIL, "Call returned unexpected TS_ERROR for %s", conf.data()); - success = false; - continue; - } - - // Now check the getters / setters - switch (type) { - case TS_RECORDDATATYPE_INT: - ival_rand = generator.random() % 126; // to fit in a signed byte - TSHttpTxnConfigIntSet(txnp, key, ival_rand); - TSHttpTxnConfigIntGet(txnp, key, &ival_read); - if (ival_rand != ival_read) { - SDK_RPRINT(test, "TSHttpTxnConfigIntSet", "TestCase1", TC_FAIL, "Failed on %s, %d != %d", conf.data(), ival_read, - ival_rand); - success = false; - continue; - } - break; - - case TS_RECORDDATATYPE_FLOAT: - fval_rand = generator.random(); - TSHttpTxnConfigFloatSet(txnp, key, fval_rand); - TSHttpTxnConfigFloatGet(txnp, key, &fval_read); - if (fval_rand != fval_read) { - SDK_RPRINT(test, "TSHttpTxnConfigFloatSet", "TestCase1", TC_FAIL, "Failed on %s, %f != %f", conf.data(), fval_read, - fval_rand); - success = false; - continue; - } - break; - - case TS_RECORDDATATYPE_STRING: - TSHttpTxnConfigStringSet(txnp, key, test_string, -1); - TSHttpTxnConfigStringGet(txnp, key, &sval_read, &len); - // Compare string content, not pointers - the implementation may store - // a copy of the string (e.g., in ParsedConfigCache for efficiency). - if (sval_read == nullptr || std::string_view(test_string) != std::string_view(sval_read, len)) { - SDK_RPRINT(test, "TSHttpTxnConfigStringSet", "TestCase1", TC_FAIL, "Failed on %s, %s != %s", conf.data(), - sval_read ? sval_read : "(null)", test_string); - success = false; - continue; - } - break; - - default: - break; - } - } - - s->destroy(); - if (success) { - *pstatus = REGRESSION_TEST_PASSED; - SDK_RPRINT(test, "TSHttpTxnConfigFind", "TestCase1", TC_PASS, "ok"); - SDK_RPRINT(test, "TSHttpTxnConfigIntSet", "TestCase1", TC_PASS, "ok"); - SDK_RPRINT(test, "TSHttpTxnConfigFloatSet", "TestCase1", TC_PASS, "ok"); - SDK_RPRINT(test, "TSHttpTxnConfigStringSet", "TestCase1", TC_PASS, "ok"); - } else { - *pstatus = REGRESSION_TEST_FAILED; - } - - return; -} - //////////////////////////////////////////////// // SDK_API_TXN_HTTP_INFO_INFO_GET // diff --git a/src/api/unit_tests/test_HttpOverridableConfig.cc b/src/api/unit_tests/test_HttpOverridableConfig.cc new file mode 100644 index 00000000000..4166ee38eee --- /dev/null +++ b/src/api/unit_tests/test_HttpOverridableConfig.cc @@ -0,0 +1,144 @@ +/** @file + + Catch based unit tests for HTTP overridable configuration APIs. + + @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 "iocore/eventsystem/ConfigProcessor.h" +#include "iocore/net/ConnectionTracker.h" +#include "proxy/http/HttpConfig.h" +#include "proxy/http/HttpSM.h" +#include "proxy/http/OverridableConfigDefs.h" +#include "ts/ts.h" + +#include + +#include +#include + +using namespace std::literals; + +namespace +{ +struct ConfigDescriptor { + std::string_view name; + TSOverridableConfigKey key; + TSRecordDataType type; +}; + +// clang-format off +static constexpr std::array CONFIG_DESCRIPTORS{{ +#define X_CONFIG_DESCRIPTOR(CONFIG_KEY, MEMBER, RECORD_NAME, DATA_TYPE, CONV) \ + {RECORD_NAME, TS_CONFIG_##CONFIG_KEY, TS_RECORDDATATYPE_##DATA_TYPE}, + OVERRIDABLE_CONFIGS(X_CONFIG_DESCRIPTOR) +#undef X_CONFIG_DESCRIPTOR +}}; +// clang-format on + +class TestHttpTxn +{ +public: + TestHttpTxn() + { + if (HttpConfig::m_id == 0) { + HttpConfig::m_id = configProcessor.set(HttpConfig::m_id, new HttpConfigParams); + } + + _sm.magic = HttpSmMagic_t::ALIVE; + _sm.t_state.http_config_param = HttpConfig::acquire(); + REQUIRE(_sm.t_state.http_config_param != nullptr); + _sm.t_state.txn_conf = &_sm.t_state.http_config_param->oride; + } + + operator TSHttpTxn() { return reinterpret_cast(&_sm); } + +private: + HttpSM _sm; +}; +} // namespace + +TEST_CASE("Find HTTP overridable configurations", "[api][overridable-config]") +{ + for (auto const &descriptor : CONFIG_DESCRIPTORS) { + TSOverridableConfigKey key; + TSRecordDataType type; + + INFO(descriptor.name); + + REQUIRE(TSHttpTxnConfigFind(descriptor.name.data(), -1, &key, &type) == TS_SUCCESS); + CHECK(key == descriptor.key); + CHECK(type == descriptor.type); + + REQUIRE(TSHttpTxnConfigFind(descriptor.name.data(), descriptor.name.size(), &key, &type) == TS_SUCCESS); + CHECK(key == descriptor.key); + CHECK(type == descriptor.type); + } + + TSOverridableConfigKey key; + TSRecordDataType type; + + CHECK(TSHttpTxnConfigFind("proxy.config.invalid", -1, &key, &type) == TS_ERROR); +} + +TEST_CASE("Set and get HTTP overridable configurations", "[api][overridable-config]") +{ + TestHttpTxn txn; + + SECTION("integer") + { + static constexpr TSMgmtInt expected = 0; + TSMgmtInt actual; + + REQUIRE(TSHttpTxnConfigIntSet(txn, TS_CONFIG_HTTP_CACHE_HTTP, expected) == TS_SUCCESS); + REQUIRE(TSHttpTxnConfigIntGet(txn, TS_CONFIG_HTTP_CACHE_HTTP, &actual) == TS_SUCCESS); + CHECK(actual == expected); + } + + SECTION("float") + { + static constexpr TSMgmtFloat expected = 0.25; + TSMgmtFloat actual; + + REQUIRE(TSHttpTxnConfigFloatSet(txn, TS_CONFIG_HTTP_CACHE_HEURISTIC_LM_FACTOR, expected) == TS_SUCCESS); + REQUIRE(TSHttpTxnConfigFloatGet(txn, TS_CONFIG_HTTP_CACHE_HEURISTIC_LM_FACTOR, &actual) == TS_SUCCESS); + CHECK(actual == expected); + } + + SECTION("constrained integer") + { + TSMgmtInt actual; + + REQUIRE(TSHttpTxnConfigIntSet(txn, TS_CONFIG_HTTP_PER_SERVER_CONNECTION_MATCH, 95) == TS_SUCCESS); + REQUIRE(TSHttpTxnConfigIntGet(txn, TS_CONFIG_HTTP_PER_SERVER_CONNECTION_MATCH, &actual) == TS_SUCCESS); + CHECK(actual == TS_SERVER_OUTBOUND_MATCH_BOTH); + } + + SECTION("string") + { + static constexpr auto expected = "Catch test"sv; + const char *actual; + int length; + + REQUIRE(TSHttpTxnConfigStringSet(txn, TS_CONFIG_HTTP_RESPONSE_SERVER_STR, expected.data(), expected.size()) == TS_SUCCESS); + REQUIRE(TSHttpTxnConfigStringGet(txn, TS_CONFIG_HTTP_RESPONSE_SERVER_STR, &actual, &length) == TS_SUCCESS); + REQUIRE(actual != nullptr); + CHECK(std::string_view(actual, length) == expected); + } +} diff --git a/src/iocore/net/CMakeLists.txt b/src/iocore/net/CMakeLists.txt index 68fb031ad77..7342d5c18ba 100644 --- a/src/iocore/net/CMakeLists.txt +++ b/src/iocore/net/CMakeLists.txt @@ -144,6 +144,7 @@ if(BUILD_TESTING) test_net libinknet_stub.cc NetVCTest.cc + unit_tests/test_ConnectionTracker.cc unit_tests/test_NetHandler.cc unit_tests/test_ProxyProtocol.cc unit_tests/test_SSLCertLookup.cc diff --git a/src/iocore/net/ConnectionTracker.cc b/src/iocore/net/ConnectionTracker.cc index 85519616328..92826d64eeb 100644 --- a/src/iocore/net/ConnectionTracker.cc +++ b/src/iocore/net/ConnectionTracker.cc @@ -51,11 +51,9 @@ const MgmtConverter ConnectionTracker::MIN_SERVER_CONV( const MgmtConverter ConnectionTracker::SERVER_MATCH_CONV{ [](const void *data) -> MgmtInt { return static_cast(*static_cast(data)); }, [](void *data, MgmtInt i) -> void { - // Problem - the InkAPITest requires being able to set an arbitrary value, so this can either - // correctly clamp or pass the regression tests. Currently it passes the tests. - // *static_cast(data) = std::clamp(static_cast(i), MATCH_IP, - // MATCH_BOTH); - *static_cast(data) = static_cast(i); + auto const value = std::clamp(i, static_cast(MATCH_IP), static_cast(MATCH_BOTH)); + + *static_cast(data) = static_cast(value); }, nullptr, nullptr, diff --git a/src/iocore/net/unit_tests/test_ConnectionTracker.cc b/src/iocore/net/unit_tests/test_ConnectionTracker.cc new file mode 100644 index 00000000000..ccd9d169d6f --- /dev/null +++ b/src/iocore/net/unit_tests/test_ConnectionTracker.cc @@ -0,0 +1,63 @@ +/** @file + + Catch based unit tests for connection tracking configuration. + + @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 "iocore/net/ConnectionTracker.h" + +#include + +#include + +TEST_CASE("Connection tracker server match conversion", "[libinknet][ConnectionTracker]") +{ + auto const &converter = ConnectionTracker::SERVER_MATCH_CONV; + auto match = ConnectionTracker::MATCH_IP; + + REQUIRE(converter.store_int != nullptr); + REQUIRE(converter.load_int != nullptr); + + SECTION("valid values round trip") + { + static constexpr std::array valid_values{ + ConnectionTracker::MATCH_IP, + ConnectionTracker::MATCH_PORT, + ConnectionTracker::MATCH_HOST, + ConnectionTracker::MATCH_BOTH, + }; + + for (auto const expected : valid_values) { + converter.store_int(&match, static_cast(expected)); + + CHECK(match == expected); + CHECK(converter.load_int(&match) == static_cast(expected)); + } + } + + SECTION("invalid values are clamped") + { + converter.store_int(&match, -1); + CHECK(match == ConnectionTracker::MATCH_IP); + + converter.store_int(&match, 95); + CHECK(match == ConnectionTracker::MATCH_BOTH); + } +} From 6758d6cce1506456099e106befaedb239c09830b Mon Sep 17 00:00:00 2001 From: bneradt Date: Mon, 10 Aug 2026 19:59:11 -0500 Subject: [PATCH 2/2] Complete overridable configuration coverage The standalone API Catch test could not resolve core symbols on macOS and only exercised representative configuration paths. Several string getters also rejected supported settings or lost explicit SSL string lengths, leaving default safety and bounded values unchecked. Link production API objects into the test, round-trip every overridable setting, and retain transaction-owned SSL strings with their API lengths. Cover constrained values, embedded NULs, and clears. --- include/proxy/http/HttpConfig.h | 24 ++- include/proxy/http/OverridableConfigDefs.h | 2 +- src/api/CMakeLists.txt | 14 +- src/api/InkAPI.cc | 75 ++++++---- .../unit_tests/test_HttpOverridableConfig.cc | 138 +++++++++++++----- src/proxy/http/HttpConfig.cc | 5 +- src/proxy/http/HttpTransact.cc | 2 +- 7 files changed, 187 insertions(+), 73 deletions(-) diff --git a/include/proxy/http/HttpConfig.h b/include/proxy/http/HttpConfig.h index 887ac1cb642..621f75523a9 100644 --- a/include/proxy/http/HttpConfig.h +++ b/include/proxy/http/HttpConfig.h @@ -669,9 +669,12 @@ struct OverridableHttpConfigParams { ////////////////////////////// // server verification mode // ////////////////////////////// - char *ssl_client_verify_server_policy = nullptr; - char *ssl_client_verify_server_properties = nullptr; - char *ssl_client_sni_policy = nullptr; + char *ssl_client_verify_server_policy = nullptr; + size_t ssl_client_verify_server_policy_len = 0; + char *ssl_client_verify_server_properties = nullptr; + size_t ssl_client_verify_server_properties_len = 0; + char *ssl_client_sni_policy = nullptr; + size_t ssl_client_sni_policy_len = 0; MgmtInt proxy_response_hsts_max_age = -1; @@ -786,11 +789,16 @@ struct OverridableHttpConfigParams { MgmtFloat background_fill_threshold = 0.5; // Various strings, good place for them here ... - char *ssl_client_cert_filename = nullptr; - char *ssl_client_private_key_filename = nullptr; - char *ssl_client_ca_cert_filename = nullptr; - char *ssl_client_ca_cert_path = nullptr; - char *ssl_client_alpn_protocols = nullptr; + char *ssl_client_cert_filename = nullptr; + size_t ssl_client_cert_filename_len = 0; + char *ssl_client_private_key_filename = nullptr; + size_t ssl_client_private_key_filename_len = 0; + char *ssl_client_ca_cert_filename = nullptr; + size_t ssl_client_ca_cert_filename_len = 0; + char *ssl_client_ca_cert_path = nullptr; + size_t ssl_client_ca_cert_path_len = 0; + char *ssl_client_alpn_protocols = nullptr; + size_t ssl_client_alpn_protocols_len = 0; // Host Resolution order HostResData host_res_data; diff --git a/include/proxy/http/OverridableConfigDefs.h b/include/proxy/http/OverridableConfigDefs.h index 8b18797494f..395b9af47e8 100644 --- a/include/proxy/http/OverridableConfigDefs.h +++ b/include/proxy/http/OverridableConfigDefs.h @@ -31,7 +31,7 @@ - Lua plugin enum and variable array (ts_lua_http_config.cc) - String-to-enum mapping (overridable_txn_vars.cc) - The _conf_to_memberp switch statement (InkAPI.cc) - - SDK_Overridable_Configs test array (InkAPITest.cc) + - Config descriptor test array (test_HttpOverridableConfig.cc) @section xmacro_format X-Macro Format diff --git a/src/api/CMakeLists.txt b/src/api/CMakeLists.txt index fcfc12b67dc..8ef4f8d44f6 100644 --- a/src/api/CMakeLists.txt +++ b/src/api/CMakeLists.txt @@ -16,7 +16,15 @@ ####################### # plugin api *only* -add_library(tsapi SHARED InkAPI.cc InkAPIInternal.cc InkIOCoreAPI.cc) +add_library(tsapi_objects OBJECT InkAPI.cc InkAPIInternal.cc InkIOCoreAPI.cc) +set_target_properties(tsapi_objects PROPERTIES POSITION_INDEPENDENT_CODE ON) +target_link_libraries(tsapi_objects PRIVATE libswoc::libswoc yaml-cpp::yaml-cpp OpenSSL::SSL) +if(ENABLE_PROBES) + target_link_libraries(tsapi_objects PRIVATE systemtap::systemtap) +endif() + +add_library(tsapi SHARED) +target_link_libraries(tsapi PRIVATE tsapi_objects) if(BUILD_REGRESSION_TESTING) target_sources(tsapi PRIVATE InkAPITest.cc) endif() @@ -69,7 +77,7 @@ if(BUILD_TESTING) test_api PRIVATE Catch2::Catch2WithMain ts::tscore - ts::tsapi + tsapi_objects ts::overridable_txn_vars ts::tsutil ts::http @@ -96,4 +104,4 @@ if(BUILD_TESTING) add_catch2_test(NAME test_api COMMAND test_api) endif() -clang_tidy_check(tsapi) +clang_tidy_check(tsapi_objects) diff --git a/src/api/InkAPI.cc b/src/api/InkAPI.cc index 4784314e8fd..8cf64847913 100644 --- a/src/api/InkAPI.cc +++ b/src/api/InkAPI.cc @@ -7521,6 +7521,16 @@ TSHttpTxnConfigStringSet(TSHttpTxn txnp, TSOverridableConfigKey conf, const char s->t_state.setup_per_txn_configs(); + auto set_txn_string = [s, value, length](char *&destination, size_t &destination_length) { + if (value && length > 0) { + destination = s->t_state.arena.str_store(value, length); + destination_length = length; + } else { + destination = nullptr; + destination_length = 0; + } + }; + switch (conf) { case TS_CONFIG_HTTP_RESPONSE_SERVER_STR: if (value && length > 0) { @@ -7563,44 +7573,31 @@ TSHttpTxnConfigStringSet(TSHttpTxn txnp, TSOverridableConfigKey conf, const char } break; case TS_CONFIG_SSL_CLIENT_VERIFY_SERVER_POLICY: - if (value && length > 0) { - s->t_state.my_txn_conf().ssl_client_verify_server_policy = const_cast(value); - } + set_txn_string(s->t_state.my_txn_conf().ssl_client_verify_server_policy, + s->t_state.my_txn_conf().ssl_client_verify_server_policy_len); break; case TS_CONFIG_SSL_CLIENT_VERIFY_SERVER_PROPERTIES: - if (value && length > 0) { - s->t_state.my_txn_conf().ssl_client_verify_server_properties = const_cast(value); - } + set_txn_string(s->t_state.my_txn_conf().ssl_client_verify_server_properties, + s->t_state.my_txn_conf().ssl_client_verify_server_properties_len); break; case TS_CONFIG_SSL_CLIENT_SNI_POLICY: - if (value && length > 0) { - s->t_state.my_txn_conf().ssl_client_sni_policy = const_cast(value); - } + set_txn_string(s->t_state.my_txn_conf().ssl_client_sni_policy, s->t_state.my_txn_conf().ssl_client_sni_policy_len); break; case TS_CONFIG_SSL_CLIENT_CERT_FILENAME: - if (value && length > 0) { - s->t_state.my_txn_conf().ssl_client_cert_filename = const_cast(value); - } + set_txn_string(s->t_state.my_txn_conf().ssl_client_cert_filename, s->t_state.my_txn_conf().ssl_client_cert_filename_len); break; case TS_CONFIG_SSL_CLIENT_PRIVATE_KEY_FILENAME: - if (value && length > 0) { - s->t_state.my_txn_conf().ssl_client_private_key_filename = const_cast(value); - } + set_txn_string(s->t_state.my_txn_conf().ssl_client_private_key_filename, + s->t_state.my_txn_conf().ssl_client_private_key_filename_len); break; case TS_CONFIG_SSL_CLIENT_CA_CERT_FILENAME: - if (value && length > 0) { - s->t_state.my_txn_conf().ssl_client_ca_cert_filename = const_cast(value); - } + set_txn_string(s->t_state.my_txn_conf().ssl_client_ca_cert_filename, s->t_state.my_txn_conf().ssl_client_ca_cert_filename_len); break; case TS_CONFIG_SSL_CLIENT_CA_CERT_PATH: - if (value && length > 0) { - s->t_state.my_txn_conf().ssl_client_ca_cert_path = const_cast(value); - } + set_txn_string(s->t_state.my_txn_conf().ssl_client_ca_cert_path, s->t_state.my_txn_conf().ssl_client_ca_cert_path_len); break; case TS_CONFIG_SSL_CLIENT_ALPN_PROTOCOLS: - if (value && length > 0) { - s->t_state.my_txn_conf().ssl_client_alpn_protocols = const_cast(value); - } + set_txn_string(s->t_state.my_txn_conf().ssl_client_alpn_protocols, s->t_state.my_txn_conf().ssl_client_alpn_protocols_len); break; case TS_CONFIG_SSL_CERT_FILEPATH: /* noop */ @@ -7676,9 +7673,37 @@ TSHttpTxnConfigStringGet(TSHttpTxn txnp, TSOverridableConfigKey conf, const char *value = sm->t_state.txn_conf->server_session_sharing_match_str; *length = *value ? strlen(*value) : 0; break; + case TS_CONFIG_SSL_CLIENT_VERIFY_SERVER_POLICY: + *value = sm->t_state.txn_conf->ssl_client_verify_server_policy; + *length = sm->t_state.txn_conf->ssl_client_verify_server_policy_len; + break; + case TS_CONFIG_SSL_CLIENT_VERIFY_SERVER_PROPERTIES: + *value = sm->t_state.txn_conf->ssl_client_verify_server_properties; + *length = sm->t_state.txn_conf->ssl_client_verify_server_properties_len; + break; + case TS_CONFIG_SSL_CLIENT_SNI_POLICY: + *value = sm->t_state.txn_conf->ssl_client_sni_policy; + *length = sm->t_state.txn_conf->ssl_client_sni_policy_len; + break; + case TS_CONFIG_SSL_CLIENT_CERT_FILENAME: + *value = sm->t_state.txn_conf->ssl_client_cert_filename; + *length = sm->t_state.txn_conf->ssl_client_cert_filename_len; + break; + case TS_CONFIG_SSL_CLIENT_PRIVATE_KEY_FILENAME: + *value = sm->t_state.txn_conf->ssl_client_private_key_filename; + *length = sm->t_state.txn_conf->ssl_client_private_key_filename_len; + break; + case TS_CONFIG_SSL_CLIENT_CA_CERT_FILENAME: + *value = sm->t_state.txn_conf->ssl_client_ca_cert_filename; + *length = sm->t_state.txn_conf->ssl_client_ca_cert_filename_len; + break; case TS_CONFIG_SSL_CLIENT_CA_CERT_PATH: *value = sm->t_state.txn_conf->ssl_client_ca_cert_path; - *length = *value ? strlen(*value) : 0; + *length = sm->t_state.txn_conf->ssl_client_ca_cert_path_len; + break; + case TS_CONFIG_SSL_CLIENT_ALPN_PROTOCOLS: + *value = sm->t_state.txn_conf->ssl_client_alpn_protocols; + *length = sm->t_state.txn_conf->ssl_client_alpn_protocols_len; break; default: { MgmtConverter const *conv; diff --git a/src/api/unit_tests/test_HttpOverridableConfig.cc b/src/api/unit_tests/test_HttpOverridableConfig.cc index 4166ee38eee..d43d3c585e2 100644 --- a/src/api/unit_tests/test_HttpOverridableConfig.cc +++ b/src/api/unit_tests/test_HttpOverridableConfig.cc @@ -31,10 +31,9 @@ #include #include +#include #include -using namespace std::literals; - namespace { struct ConfigDescriptor { @@ -69,6 +68,12 @@ class TestHttpTxn operator TSHttpTxn() { return reinterpret_cast(&_sm); } + const HttpForwarded::OptionBitSet & + forwarded_options() const + { + return _sm.t_state.txn_conf->insert_forwarded; + } + private: HttpSM _sm; }; @@ -86,7 +91,7 @@ TEST_CASE("Find HTTP overridable configurations", "[api][overridable-config]") CHECK(key == descriptor.key); CHECK(type == descriptor.type); - REQUIRE(TSHttpTxnConfigFind(descriptor.name.data(), descriptor.name.size(), &key, &type) == TS_SUCCESS); + REQUIRE(TSHttpTxnConfigFind(descriptor.name.data(), static_cast(descriptor.name.size()), &key, &type) == TS_SUCCESS); CHECK(key == descriptor.key); CHECK(type == descriptor.type); } @@ -97,48 +102,113 @@ TEST_CASE("Find HTTP overridable configurations", "[api][overridable-config]") CHECK(TSHttpTxnConfigFind("proxy.config.invalid", -1, &key, &type) == TS_ERROR); } -TEST_CASE("Set and get HTTP overridable configurations", "[api][overridable-config]") +TEST_CASE("Round trip HTTP overridable configurations", "[api][overridable-config]") { TestHttpTxn txn; - SECTION("integer") - { - static constexpr TSMgmtInt expected = 0; - TSMgmtInt actual; + for (auto const &descriptor : CONFIG_DESCRIPTORS) { + INFO(descriptor.name); - REQUIRE(TSHttpTxnConfigIntSet(txn, TS_CONFIG_HTTP_CACHE_HTTP, expected) == TS_SUCCESS); - REQUIRE(TSHttpTxnConfigIntGet(txn, TS_CONFIG_HTTP_CACHE_HTTP, &actual) == TS_SUCCESS); - CHECK(actual == expected); - } + switch (descriptor.type) { + case TS_RECORDDATATYPE_INT: { + TSMgmtInt expected; + TSMgmtInt actual; - SECTION("float") - { - static constexpr TSMgmtFloat expected = 0.25; - TSMgmtFloat actual; + REQUIRE(TSHttpTxnConfigIntGet(txn, descriptor.key, &expected) == TS_SUCCESS); + REQUIRE(TSHttpTxnConfigIntSet(txn, descriptor.key, expected) == TS_SUCCESS); + REQUIRE(TSHttpTxnConfigIntGet(txn, descriptor.key, &actual) == TS_SUCCESS); + CHECK(actual == expected); + break; + } + case TS_RECORDDATATYPE_FLOAT: { + TSMgmtFloat expected; + TSMgmtFloat actual; + + REQUIRE(TSHttpTxnConfigFloatGet(txn, descriptor.key, &expected) == TS_SUCCESS); + REQUIRE(TSHttpTxnConfigFloatSet(txn, descriptor.key, expected) == TS_SUCCESS); + REQUIRE(TSHttpTxnConfigFloatGet(txn, descriptor.key, &actual) == TS_SUCCESS); + CHECK(actual == expected); + break; + } + case TS_RECORDDATATYPE_STRING: { + if (descriptor.key == TS_CONFIG_SSL_CERT_FILEPATH) { + REQUIRE(TSHttpTxnConfigStringSet(txn, descriptor.key, "", 0) == TS_SUCCESS); + break; + } - REQUIRE(TSHttpTxnConfigFloatSet(txn, TS_CONFIG_HTTP_CACHE_HEURISTIC_LM_FACTOR, expected) == TS_SUCCESS); - REQUIRE(TSHttpTxnConfigFloatGet(txn, TS_CONFIG_HTTP_CACHE_HEURISTIC_LM_FACTOR, &actual) == TS_SUCCESS); - CHECK(actual == expected); - } + if (descriptor.key == TS_CONFIG_HTTP_INSERT_FORWARDED) { + static constexpr std::string_view expected{"none"}; - SECTION("constrained integer") - { - TSMgmtInt actual; + REQUIRE(TSHttpTxnConfigStringSet(txn, descriptor.key, expected.data(), static_cast(expected.size())) == TS_SUCCESS); + CHECK(txn.forwarded_options().none()); + break; + } + + const char *value; + int length; + + REQUIRE(TSHttpTxnConfigStringGet(txn, descriptor.key, &value, &length) == TS_SUCCESS); + REQUIRE(length >= 0); + REQUIRE((value != nullptr || length == 0)); + + std::string expected; + + if (value != nullptr) { + expected.assign(value, length); + } - REQUIRE(TSHttpTxnConfigIntSet(txn, TS_CONFIG_HTTP_PER_SERVER_CONNECTION_MATCH, 95) == TS_SUCCESS); - REQUIRE(TSHttpTxnConfigIntGet(txn, TS_CONFIG_HTTP_PER_SERVER_CONNECTION_MATCH, &actual) == TS_SUCCESS); - CHECK(actual == TS_SERVER_OUTBOUND_MATCH_BOTH); + REQUIRE(TSHttpTxnConfigStringSet(txn, descriptor.key, expected.data(), static_cast(expected.size())) == TS_SUCCESS); + + const char *actual; + int actual_length; + + REQUIRE(TSHttpTxnConfigStringGet(txn, descriptor.key, &actual, &actual_length) == TS_SUCCESS); + REQUIRE(actual_length >= 0); + REQUIRE((actual != nullptr || actual_length == 0)); + CHECK(std::string_view(actual != nullptr ? actual : "", actual_length) == expected); + break; + } + default: + FAIL("Unexpected overridable configuration data type"); + break; + } } +} - SECTION("string") - { - static constexpr auto expected = "Catch test"sv; - const char *actual; - int length; +TEST_CASE("Clamp constrained HTTP overridable configuration", "[api][overridable-config]") +{ + TestHttpTxn txn; + TSMgmtInt actual; - REQUIRE(TSHttpTxnConfigStringSet(txn, TS_CONFIG_HTTP_RESPONSE_SERVER_STR, expected.data(), expected.size()) == TS_SUCCESS); - REQUIRE(TSHttpTxnConfigStringGet(txn, TS_CONFIG_HTTP_RESPONSE_SERVER_STR, &actual, &length) == TS_SUCCESS); + REQUIRE(TSHttpTxnConfigIntSet(txn, TS_CONFIG_HTTP_PER_SERVER_CONNECTION_MATCH, 95) == TS_SUCCESS); + REQUIRE(TSHttpTxnConfigIntGet(txn, TS_CONFIG_HTTP_PER_SERVER_CONNECTION_MATCH, &actual) == TS_SUCCESS); + CHECK(actual == TS_SERVER_OUTBOUND_MATCH_BOTH); +} + +TEST_CASE("Round trip bounded SSL string overrides", "[api][overridable-config]") +{ + static constexpr std::array SSL_STRING_CONFIGS{ + TS_CONFIG_SSL_CLIENT_VERIFY_SERVER_POLICY, TS_CONFIG_SSL_CLIENT_VERIFY_SERVER_PROPERTIES, TS_CONFIG_SSL_CLIENT_SNI_POLICY, + TS_CONFIG_SSL_CLIENT_CERT_FILENAME, TS_CONFIG_SSL_CLIENT_PRIVATE_KEY_FILENAME, TS_CONFIG_SSL_CLIENT_CA_CERT_FILENAME, + TS_CONFIG_SSL_CLIENT_CA_CERT_PATH, TS_CONFIG_SSL_CLIENT_ALPN_PROTOCOLS, + }; + static constexpr std::array EXPECTED{'a', '\0', 'b'}; + + TestHttpTxn txn; + + for (auto const key : SSL_STRING_CONFIGS) { + const char *actual; + int actual_length; + + REQUIRE(TSHttpTxnConfigStringSet(txn, key, EXPECTED.data(), EXPECTED.size()) == TS_SUCCESS); + REQUIRE(TSHttpTxnConfigStringGet(txn, key, &actual, &actual_length) == TS_SUCCESS); REQUIRE(actual != nullptr); - CHECK(std::string_view(actual, length) == expected); + CHECK(actual_length == EXPECTED.size()); + CHECK(std::string_view(actual, actual_length) == std::string_view(EXPECTED.data(), EXPECTED.size())); + + REQUIRE(TSHttpTxnConfigStringSet(txn, key, nullptr, 0) == TS_SUCCESS); + REQUIRE(TSHttpTxnConfigStringGet(txn, key, &actual, &actual_length) == TS_SUCCESS); + CHECK(actual == nullptr); + CHECK(actual_length == 0); } } diff --git a/src/proxy/http/HttpConfig.cc b/src/proxy/http/HttpConfig.cc index 08a3ed23633..792f3e26db6 100644 --- a/src/proxy/http/HttpConfig.cc +++ b/src/proxy/http/HttpConfig.cc @@ -694,7 +694,7 @@ parse_http_status_code_list(HttpStatusBitset &set, swoc::TextView status_list) const MgmtConverter HttpStatusCodeList::Conv{ [](const void *data) -> std::string_view { const HttpStatusCodeList *list = static_cast(data); - return list->conf_value; + return list->conf_value ? list->conf_value : ""; }, [](void *data, std::string_view src) -> void { HttpStatusCodeList *list = static_cast(data); @@ -1569,6 +1569,9 @@ HttpConfig::reconfigure() params->oride.ssl_client_sni_policy = ats_strdup(m_master.oride.ssl_client_sni_policy); params->oride.ssl_client_alpn_protocols = ats_strdup(m_master.oride.ssl_client_alpn_protocols); + params->oride.ssl_client_sni_policy_len = params->oride.ssl_client_sni_policy ? strlen(params->oride.ssl_client_sni_policy) : 0; + params->oride.ssl_client_alpn_protocols_len = + params->oride.ssl_client_alpn_protocols ? strlen(params->oride.ssl_client_alpn_protocols) : 0; params->oride.negative_caching_list.set(m_master.oride.negative_caching_list.conf_value); params->oride.negative_revalidating_list.set(m_master.oride.negative_revalidating_list.conf_value); diff --git a/src/proxy/http/HttpTransact.cc b/src/proxy/http/HttpTransact.cc index 65b9e99b38a..5dd60c22c2e 100644 --- a/src/proxy/http/HttpTransact.cc +++ b/src/proxy/http/HttpTransact.cc @@ -144,7 +144,7 @@ HttpTransact::strip_at_headers(HTTPHdr &header, AtHeaderSource source, std::int6 // Support ip_resolve override. const MgmtConverter HttpTransact::HOST_RES_CONV{[](const void *data) -> std::string_view { const HostResData *host_res_data = static_cast(data); - return host_res_data->conf_value; + return host_res_data->conf_value ? host_res_data->conf_value : ""; }, [](void *data, std::string_view src) -> void { HostResData *res_data = static_cast(data);