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 ca3ea344862..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() @@ -63,4 +71,37 @@ if(APPLE) target_link_options(tsapi PRIVATE -undefined dynamic_lookup) endif() -clang_tidy_check(tsapi) +if(BUILD_TESTING) + add_executable(test_api unit_tests/test_HttpOverridableConfig.cc) + target_link_libraries( + test_api + PRIVATE Catch2::Catch2WithMain + ts::tscore + tsapi_objects + 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_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/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..d43d3c585e2 --- /dev/null +++ b/src/api/unit_tests/test_HttpOverridableConfig.cc @@ -0,0 +1,214 @@ +/** @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 +#include + +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); } + + const HttpForwarded::OptionBitSet & + forwarded_options() const + { + return _sm.t_state.txn_conf->insert_forwarded; + } + +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(), static_cast(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("Round trip HTTP overridable configurations", "[api][overridable-config]") +{ + TestHttpTxn txn; + + for (auto const &descriptor : CONFIG_DESCRIPTORS) { + INFO(descriptor.name); + + switch (descriptor.type) { + case TS_RECORDDATATYPE_INT: { + TSMgmtInt expected; + TSMgmtInt 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; + } + + if (descriptor.key == TS_CONFIG_HTTP_INSERT_FORWARDED) { + static constexpr std::string_view expected{"none"}; + + 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(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; + } + } +} + +TEST_CASE("Clamp constrained HTTP overridable configuration", "[api][overridable-config]") +{ + TestHttpTxn txn; + 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); +} + +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(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/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); + } +} 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);