From d151cc9e3c1b197ad5c359e97bf5db8d7139a4b9 Mon Sep 17 00:00:00 2001 From: Josiah VanderZee Date: Thu, 25 May 2023 14:22:28 +0000 Subject: [PATCH 01/24] Add optional port range to YAML SNI config --- iocore/net/Makefile.am | 4 +- iocore/net/YamlSNIConfig.cc | 31 ++++- iocore/net/YamlSNIConfig.h | 3 + iocore/net/unit_tests/sni_conf_test.yaml | 5 + .../sni_conf_test_bad_port_0-1.yaml | 2 + .../sni_conf_test_bad_port_1-yowzers2.yaml | 2 + .../sni_conf_test_bad_port_65535-65536.yaml | 2 + .../sni_conf_test_bad_port_8080-433.yaml | 2 + .../sni_conf_test_bad_port_yowzers-1.yaml | 2 + iocore/net/unit_tests/test_YamlSNIConfig.cc | 109 ++++++++++++++++++ 10 files changed, 160 insertions(+), 2 deletions(-) create mode 100644 iocore/net/unit_tests/sni_conf_test.yaml create mode 100644 iocore/net/unit_tests/sni_conf_test_bad_port_0-1.yaml create mode 100644 iocore/net/unit_tests/sni_conf_test_bad_port_1-yowzers2.yaml create mode 100644 iocore/net/unit_tests/sni_conf_test_bad_port_65535-65536.yaml create mode 100644 iocore/net/unit_tests/sni_conf_test_bad_port_8080-433.yaml create mode 100644 iocore/net/unit_tests/sni_conf_test_bad_port_yowzers-1.yaml create mode 100644 iocore/net/unit_tests/test_YamlSNIConfig.cc diff --git a/iocore/net/Makefile.am b/iocore/net/Makefile.am index b567a359c62..974b9852280 100644 --- a/iocore/net/Makefile.am +++ b/iocore/net/Makefile.am @@ -97,7 +97,8 @@ test_UDPNet_SOURCES = \ test_libinknet_SOURCES = \ libinknet_stub.cc \ - unit_tests/test_ProxyProtocol.cc + unit_tests/test_ProxyProtocol.cc \ + unit_tests/test_YamlSNIConfig.cc test_libinknet_CPPFLAGS = \ $(AM_CPPFLAGS) \ @@ -108,6 +109,7 @@ test_libinknet_CPPFLAGS = \ -I$(abs_top_srcdir)/proxy/http \ -I$(abs_top_srcdir)/mgmt \ -I$(abs_top_srcdir)/mgmt/utils \ + -DLIBINKNET_UNIT_TEST_DIR="$(abs_top_srcdir)/iocore/net/unit_tests" \ @OPENSSL_INCLUDES@ test_libinknet_LDFLAGS = \ diff --git a/iocore/net/YamlSNIConfig.cc b/iocore/net/YamlSNIConfig.cc index 314adecc851..ca3ca4d0056 100644 --- a/iocore/net/YamlSNIConfig.cc +++ b/iocore/net/YamlSNIConfig.cc @@ -21,13 +21,20 @@ #include "YamlSNIConfig.h" +#include #include #include #include +#include +#include +#include #include #include +#include "swoc/TextView.h" +#include "swoc/bwf_base.h" + #include "P_SNIActionPerformer.h" #include "tscore/Diags.h" @@ -58,6 +65,7 @@ load_tunnel_alpn(std::vector &dst, const YAML::Node &node) } } } + } // namespace ts::Errata @@ -175,7 +183,28 @@ template <> struct convert { } if (node[TS_fqdn]) { - item.fqdn = node[TS_fqdn].as(); + swoc::TextView fqdn{node[TS_fqdn].Scalar()}; + auto port_view{fqdn.take_suffix_at(':')}; + if (port_view && fqdn) { + auto min{port_view.split_prefix_at('-')}; + if (!min) { + min = port_view; + } + const auto &max{port_view}; + + swoc::TextView parsed_min; + long min_port{swoc::svtoi(min, &parsed_min)}; + swoc::TextView parsed_max; + long max_port{swoc::svtoi(max, &parsed_max)}; + if (parsed_min != min || min_port < 1 || parsed_max != max || max_port > 65535 || max_port < min_port) { + std::string out; + swoc::bwprint(out, "bad port range: {}-{}", min, max); + throw YAML::ParserException(node[TS_fqdn].Mark(), out); + } + + item.port_ranges.emplace_back(std::make_pair(min_port, max_port)); + } + item.fqdn = fqdn; } else { return false; // servername must be present } diff --git a/iocore/net/YamlSNIConfig.h b/iocore/net/YamlSNIConfig.h index 63d9aacd1ba..020e9bdaa80 100644 --- a/iocore/net/YamlSNIConfig.h +++ b/iocore/net/YamlSNIConfig.h @@ -22,7 +22,9 @@ #pragma once #include +#include #include +#include #include #include @@ -72,6 +74,7 @@ struct YamlSNIConfig { struct Item { std::string fqdn; + std::vector> port_ranges; std::optional offer_h2; // Has no value by default, so do not initialize! std::optional offer_quic; // Has no value by default, so do not initialize! uint8_t verify_client_level = 255; diff --git a/iocore/net/unit_tests/sni_conf_test.yaml b/iocore/net/unit_tests/sni_conf_test.yaml new file mode 100644 index 00000000000..c968dbb3af8 --- /dev/null +++ b/iocore/net/unit_tests/sni_conf_test.yaml @@ -0,0 +1,5 @@ +sni: +- fqdn: allports.com +- fqdn: someport.com:1-433 +- fqdn: someport.com:8080-65535 +- fqdn: oneport.com:433-433 diff --git a/iocore/net/unit_tests/sni_conf_test_bad_port_0-1.yaml b/iocore/net/unit_tests/sni_conf_test_bad_port_0-1.yaml new file mode 100644 index 00000000000..0c1aed96ce7 --- /dev/null +++ b/iocore/net/unit_tests/sni_conf_test_bad_port_0-1.yaml @@ -0,0 +1,2 @@ +sni: +- fqdn: badport.com:0-1 diff --git a/iocore/net/unit_tests/sni_conf_test_bad_port_1-yowzers2.yaml b/iocore/net/unit_tests/sni_conf_test_bad_port_1-yowzers2.yaml new file mode 100644 index 00000000000..82efa27c2b5 --- /dev/null +++ b/iocore/net/unit_tests/sni_conf_test_bad_port_1-yowzers2.yaml @@ -0,0 +1,2 @@ +sni: +- fqdn: badport.com:1-yowzers2 diff --git a/iocore/net/unit_tests/sni_conf_test_bad_port_65535-65536.yaml b/iocore/net/unit_tests/sni_conf_test_bad_port_65535-65536.yaml new file mode 100644 index 00000000000..79480927393 --- /dev/null +++ b/iocore/net/unit_tests/sni_conf_test_bad_port_65535-65536.yaml @@ -0,0 +1,2 @@ +sni: +- fqdn: badport.com:65535-65536 diff --git a/iocore/net/unit_tests/sni_conf_test_bad_port_8080-433.yaml b/iocore/net/unit_tests/sni_conf_test_bad_port_8080-433.yaml new file mode 100644 index 00000000000..d88e705eaee --- /dev/null +++ b/iocore/net/unit_tests/sni_conf_test_bad_port_8080-433.yaml @@ -0,0 +1,2 @@ +sni: +- fqdn: badport.com:8080-433 diff --git a/iocore/net/unit_tests/sni_conf_test_bad_port_yowzers-1.yaml b/iocore/net/unit_tests/sni_conf_test_bad_port_yowzers-1.yaml new file mode 100644 index 00000000000..8c97208abba --- /dev/null +++ b/iocore/net/unit_tests/sni_conf_test_bad_port_yowzers-1.yaml @@ -0,0 +1,2 @@ +sni: +- fqdn: notaport.com:yowzers-1 diff --git a/iocore/net/unit_tests/test_YamlSNIConfig.cc b/iocore/net/unit_tests/test_YamlSNIConfig.cc new file mode 100644 index 00000000000..a12efc8dc9c --- /dev/null +++ b/iocore/net/unit_tests/test_YamlSNIConfig.cc @@ -0,0 +1,109 @@ +/** @file + + Catch based unit tests for YamlSNIConfig + + @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. + */ + +#ifndef LIBINKNET_UNIT_TEST_DIR +#error please set LIBINKNET_UNIT_TEST_DIR +#endif + +#define _STR(s) #s +#define _XSTR(s) _STR(s) + +#include +#include +#include + +#include "swoc/bwf_base.h" +#include "catch.hpp" + +#include "YamlSNIConfig.h" + +TEST_CASE("YamlSNIConfig sets port ranges appropriately") +{ + YamlSNIConfig conf{}; + ts::Errata zret{conf.loader(_XSTR(LIBINKNET_UNIT_TEST_DIR) "/sni_conf_test.yaml")}; + if (!zret.isOK()) { + std::stringstream errorstream; + errorstream << zret; + FAIL(errorstream.str()); + } + REQUIRE(zret.isOK()); + REQUIRE(conf.items.size() == 4); + + SECTION("If no ports were specified, ports should be empty.") + { + const auto &item{conf.items[0]}; + CHECK(item.port_ranges.size() == 0); + } + + SECTION("If one port range was specified, ports should contain that port range.") + { + SECTION("Ports 1-433.") + { + const auto &item{conf.items[1]}; + REQUIRE(item.port_ranges.size() == 1); + const auto [min, max]{item.port_ranges[0]}; + CHECK(min == 1); + CHECK(max == 433); + } + SECTION("Ports 8080-65535.") + { + const auto &item{conf.items[2]}; + REQUIRE(item.port_ranges.size() == 1); + const auto [min, max]{item.port_ranges[0]}; + CHECK(min == 8080); + CHECK(max == 65535); + } + SECTION("Ports 433-433.") + { + const auto &item{conf.items[3]}; + REQUIRE(item.port_ranges.size() == 1); + const auto [min, max]{item.port_ranges[0]}; + CHECK(min == 433); + CHECK(max == 433); + } + } + + SECTION("If a port was specified, it should not interfere with the fqdn.") + { + const auto &item{conf.items[1]}; + CHECK(item.fqdn == "someport.com"); + } +} + +TEST_CASE("YamlConfig handles bad ports appropriately.") +{ + YamlSNIConfig conf{}; + + std::string port_str{GENERATE("0-1", "65535-65536", "8080-433", "yowzers-1", "1-yowzers2")}; + + std::string filepath; + swoc::bwprint(filepath, "{}/sni_conf_test_bad_port_{}.yaml", _XSTR(LIBINKNET_UNIT_TEST_DIR), port_str); + + ts::Errata zret{conf.loader(filepath)}; + std::stringstream errorstream; + errorstream << zret; + + std::string expected; + swoc::bwprint(expected, "1 [1]: yaml-cpp: error at line 2, column 9: bad port range: {}\n", port_str); + CHECK(errorstream.str() == expected); +} From 783c5e37d541accbc3d26e9303dd84ee1017b8fb Mon Sep 17 00:00:00 2001 From: Josiah VanderZee Date: Wed, 31 May 2023 20:22:09 +0000 Subject: [PATCH 02/24] Fix fqdn being cleared in absence of port range --- iocore/net/YamlSNIConfig.cc | 4 +++- iocore/net/unit_tests/test_YamlSNIConfig.cc | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/iocore/net/YamlSNIConfig.cc b/iocore/net/YamlSNIConfig.cc index ca3ca4d0056..a5ee0d42813 100644 --- a/iocore/net/YamlSNIConfig.cc +++ b/iocore/net/YamlSNIConfig.cc @@ -186,6 +186,7 @@ template <> struct convert { swoc::TextView fqdn{node[TS_fqdn].Scalar()}; auto port_view{fqdn.take_suffix_at(':')}; if (port_view && fqdn) { + item.fqdn = fqdn; auto min{port_view.split_prefix_at('-')}; if (!min) { min = port_view; @@ -203,8 +204,9 @@ template <> struct convert { } item.port_ranges.emplace_back(std::make_pair(min_port, max_port)); + } else { + item.fqdn = port_view; } - item.fqdn = fqdn; } else { return false; // servername must be present } diff --git a/iocore/net/unit_tests/test_YamlSNIConfig.cc b/iocore/net/unit_tests/test_YamlSNIConfig.cc index a12efc8dc9c..f7603be0c22 100644 --- a/iocore/net/unit_tests/test_YamlSNIConfig.cc +++ b/iocore/net/unit_tests/test_YamlSNIConfig.cc @@ -88,6 +88,12 @@ TEST_CASE("YamlSNIConfig sets port ranges appropriately") const auto &item{conf.items[1]}; CHECK(item.fqdn == "someport.com"); } + + SECTION("If no port was specified, it should not interfere with the fqdn.") + { + const auto &item{conf.items[0]}; + CHECK(item.fqdn == "allports.com"); + } } TEST_CASE("YamlConfig handles bad ports appropriately.") From 6980d86e5dd460cad4181f3931449c70dc264381 Mon Sep 17 00:00:00 2001 From: Josiah VanderZee Date: Wed, 31 May 2023 20:29:51 +0000 Subject: [PATCH 03/24] Consider port when matching in SSLSNIConfig --- iocore/net/Makefile.am | 2 + iocore/net/SSLSNIConfig.cc | 79 ++++++++++++++++--- iocore/net/SSLSNIConfig.h | 11 +++ iocore/net/unit_tests/test_ProxyProtocol.cc | 1 - iocore/net/unit_tests/test_SSLSNIConfig.cc | 87 +++++++++++++++++++++ iocore/net/unit_tests/unit_test_main.cc | 67 ++++++++++++++++ 6 files changed, 234 insertions(+), 13 deletions(-) create mode 100644 iocore/net/unit_tests/test_SSLSNIConfig.cc create mode 100644 iocore/net/unit_tests/unit_test_main.cc diff --git a/iocore/net/Makefile.am b/iocore/net/Makefile.am index 974b9852280..7bfa944007d 100644 --- a/iocore/net/Makefile.am +++ b/iocore/net/Makefile.am @@ -97,7 +97,9 @@ test_UDPNet_SOURCES = \ test_libinknet_SOURCES = \ libinknet_stub.cc \ + unit_tests/unit_test_main.cc \ unit_tests/test_ProxyProtocol.cc \ + unit_tests/test_SSLSNIConfig.cc \ unit_tests/test_YamlSNIConfig.cc test_libinknet_CPPFLAGS = \ diff --git a/iocore/net/SSLSNIConfig.cc b/iocore/net/SSLSNIConfig.cc index e7749fa4692..8471b561977 100644 --- a/iocore/net/SSLSNIConfig.cc +++ b/iocore/net/SSLSNIConfig.cc @@ -60,6 +60,7 @@ NamedElement::operator=(NamedElement &&other) { if (this != &other) { match = std::move(other.match); + ports = std::move(other.ports); } return *this; } @@ -111,45 +112,51 @@ int SNIConfigParams::load_sni_config() { for (auto &item : yaml_sni.items) { - auto ai = sni_action_list.emplace(sni_action_list.end()); - ai->set_glob_name(item.fqdn); + auto &ai = sni_action_list.emplace_back(); + ai.set_glob_name(item.fqdn); + if (!item.port_ranges.empty()) { + const auto [min, max]{item.port_ranges[0]}; + ai.ports = {min, max}; + } else { + ai.ports = {1, 65535}; + } Debug("ssl", "name: %s", item.fqdn.data()); // set SNI based actions to be called in the ssl_servername_only callback if (item.offer_h2.has_value()) { - ai->actions.push_back(std::make_unique(item.offer_h2.value())); + ai.actions.push_back(std::make_unique(item.offer_h2.value())); } if (item.offer_quic.has_value()) { ai->actions.push_back(std::make_unique(item.offer_quic.value())); } if (item.verify_client_level != 255) { - ai->actions.push_back( + ai.actions.push_back( std::make_unique(item.verify_client_level, item.verify_client_ca_file, item.verify_client_ca_dir)); } if (item.host_sni_policy != 255) { - ai->actions.push_back(std::make_unique(item.host_sni_policy)); + ai.actions.push_back(std::make_unique(item.host_sni_policy)); } if (item.valid_tls_version_min_in >= 0 || item.valid_tls_version_max_in >= 0) { - ai->actions.push_back(std::make_unique(item.valid_tls_version_min_in, item.valid_tls_version_max_in)); + ai.actions.push_back(std::make_unique(item.valid_tls_version_min_in, item.valid_tls_version_max_in)); } else { if (!item.protocol_unset) { - ai->actions.push_back(std::make_unique(item.protocol_mask)); + ai.actions.push_back(std::make_unique(item.protocol_mask)); } } if (item.tunnel_destination.length() > 0) { - ai->actions.push_back( + ai.actions.push_back( std::make_unique(item.tunnel_destination, item.tunnel_type, item.tunnel_prewarm, item.tunnel_alpn)); } if (!item.client_sni_policy.empty()) { - ai->actions.push_back(std::make_unique(item.client_sni_policy)); + ai.actions.push_back(std::make_unique(item.client_sni_policy)); } if (item.http2_buffer_water_mark.has_value()) { - ai->actions.push_back(std::make_unique(item.http2_buffer_water_mark.value())); + ai.actions.push_back(std::make_unique(item.http2_buffer_water_mark.value())); } - ai->actions.push_back(std::make_unique(item.server_max_early_data)); + ai.actions.push_back(std::make_unique(item.server_max_early_data)); - ai->actions.push_back(std::make_unique(item.ip_allow, item.fqdn)); + ai.actions.push_back(std::make_unique(item.ip_allow, item.fqdn)); // set the next hop properties auto nps = next_hop_list.emplace(next_hop_list.end()); @@ -177,6 +184,49 @@ SNIConfigParams::load_sni_config() return 0; } +std::pair +SNIConfigParams::get(std::string_view servername, long conn_port) const +{ + int ovector[OVECSIZE]; + + for (const auto &retval : sni_action_list) { + int length = servername.length(); + if (retval.match == nullptr && length == 0) { + return {&retval.actions, {}}; + } else if (auto offset = pcre_exec(retval.match.get(), nullptr, servername.data(), length, 0, 0, ovector, OVECSIZE); + offset >= 0) { + if (!retval.ports.contains(conn_port)) { + continue; + } + if (offset == 1) { + // first pair identify the portion of the subject string matched by the entire pattern + if (ovector[0] == 0 && ovector[1] == length) { + // full match + return {&retval.actions, {}}; + } else { + continue; + } + } + // If contains groups + if (offset == 0) { + // reset to max if too many. + offset = OVECSIZE / 3; + } + + ActionItem::Context::CapturedGroupViewVec groups; + groups.reserve(offset); + for (int strnum = 1; strnum < offset; strnum++) { + const std::size_t start = ovector[2 * strnum]; + const std::size_t length = ovector[2 * strnum + 1] - start; + + groups.emplace_back(servername.data() + start, length); + } + return {&retval.actions, {std::move(groups)}}; + } + } + return {nullptr, {}}; +} + std::pair SNIConfigParams::get(std::string_view servername) const { @@ -221,7 +271,12 @@ int SNIConfigParams::initialize() { std::string sni_filename = RecConfigReadConfigPath("proxy.config.ssl.servername.filename"); + return initialize(sni_filename); +} +int +SNIConfigParams::initialize(const std::string &sni_filename) +{ Note("%s loading ...", sni_filename.c_str()); struct stat sbuf; diff --git a/iocore/net/SSLSNIConfig.h b/iocore/net/SSLSNIConfig.h index b00dc68357a..bec0b37a4b4 100644 --- a/iocore/net/SSLSNIConfig.h +++ b/iocore/net/SSLSNIConfig.h @@ -30,6 +30,8 @@ ****************************************************************************/ #pragma once +#include "swoc/DiscreteRange.h" + #include #include #include @@ -60,12 +62,18 @@ struct PcreFreer { struct NamedElement { NamedElement() {} + NamedElement(const NamedElement &other) = delete; + NamedElement &operator=(const NamedElement &other) = delete; NamedElement(NamedElement &&other); NamedElement &operator=(NamedElement &&other); + ~NamedElement() = default; void set_glob_name(std::string name); void set_regex_name(const std::string ®ex_name); + using port_range_t = swoc::DiscreteRange; + port_range_t ports{1, 65535}; + std::unique_ptr match; }; @@ -84,8 +92,11 @@ struct SNIConfigParams : public ConfigInfo { SNIConfigParams() = default; ~SNIConfigParams() override; + std::pair get(std::string_view servername, long conn_port) const; + const NextHopProperty *get_property_config(const std::string &servername) const; int initialize(); + int initialize(const std::string &sni_filename); /** Walk sni.yaml config and populate sni_action_list @return 0 for success, 1 is failure */ diff --git a/iocore/net/unit_tests/test_ProxyProtocol.cc b/iocore/net/unit_tests/test_ProxyProtocol.cc index 9f8b7c4c0f4..9ff76cc628d 100644 --- a/iocore/net/unit_tests/test_ProxyProtocol.cc +++ b/iocore/net/unit_tests/test_ProxyProtocol.cc @@ -21,7 +21,6 @@ limitations under the License. */ -#define CATCH_CONFIG_MAIN #include "catch.hpp" #include "ProxyProtocol.h" diff --git a/iocore/net/unit_tests/test_SSLSNIConfig.cc b/iocore/net/unit_tests/test_SSLSNIConfig.cc new file mode 100644 index 00000000000..4f0ed5bdea4 --- /dev/null +++ b/iocore/net/unit_tests/test_SSLSNIConfig.cc @@ -0,0 +1,87 @@ +/** @file + + Catch based unit tests for YamlSNIConfig + + @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. + */ + +#ifndef LIBINKNET_UNIT_TEST_DIR +#error please set LIBINKNET_UNIT_TEST_DIR +#endif + +#define _STR(s) #s +#define _XSTR(s) _STR(s) + +#include "SSLSNIConfig.h" + +#include "catch.hpp" + +#include + +TEST_CASE("Test SSLSNIConfig") +{ + SNIConfigParams params; + params.initialize(_XSTR(LIBINKNET_UNIT_TEST_DIR) "/sni_conf_test.yaml"); + + SECTION("The config does not match any SNIs for someport.com:577") + { + const auto &actions{params.get({"someport.com", std::strlen("someport.com")}, 577)}; + CHECK(!actions.first); + } + + SECTION("The config does not match any SNIs for someport.com:808") + { + const auto &actions{params.get({"someport.com", std::strlen("someport.com")}, 808)}; + CHECK(!actions.first); + } + + SECTION("The config does not match any SNIs for oneport.com:1") + { + const auto &actions{params.get({"oneport.com", std::strlen("oneport.com")}, 1)}; + CHECK(!actions.first); + } + + SECTION("The config matches an SNI for allports.com") + { + const auto &actions{params.get({"allports.com", std::strlen("allports.com")}, 1)}; + REQUIRE(actions.first); + REQUIRE(actions.first->size() == 1); + } + + SECTION("The config matches an SNI for someport.com:1") + { + const auto &actions{params.get({"someport.com", std::strlen("someport.com")}, 1)}; + REQUIRE(actions.first); + REQUIRE(actions.first->size() == 1); + } + + SECTION("The config matches an SNI for someport.com:433") + { + const auto &actions{params.get({"someport.com", std::strlen("someport.com")}, 433)}; + REQUIRE(actions.first); + REQUIRE(actions.first->size() == 1); + } + + SECTION("The config matches an SNI for someport:8080") + { + const auto &actions{params.get({"someport.com", std::strlen("someport.com")}, 8080)}; + REQUIRE(actions.first); + REQUIRE(actions.first->size() == 1); + } +} diff --git a/iocore/net/unit_tests/unit_test_main.cc b/iocore/net/unit_tests/unit_test_main.cc new file mode 100644 index 00000000000..7db696f061f --- /dev/null +++ b/iocore/net/unit_tests/unit_test_main.cc @@ -0,0 +1,67 @@ +/** @file + + Catch based unit tests for YamlSNIConfig + + @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 "I_EventSystem.h" // must be included before I_EThread.h +#include "I_EThread.h" +#include "I_Thread.h" +#include "P_SSLConfig.h" +#include "records/I_RecordsConfig.h" +#include "tscore/BaseLogFile.h" +#include "tscore/Diags.h" +#include "tscore/I_Layout.h" + +#define CATCH_CONFIG_MAIN +#include "catch.hpp" + +inline static constexpr int test_threads{1}; + +class EventProcessorListener final : public Catch::TestEventListenerBase +{ +public: + using TestEventListenerBase::TestEventListenerBase; + + void + testRunStarting(Catch::TestRunInfo const &testRunInfo) override + { + Layout::create(); + BaseLogFile *base_log_file = new BaseLogFile("stderr"); + DiagsPtr::set(new Diags(testRunInfo.name, "" /* tags */, "" /* actions */, base_log_file)); + RecProcessInit(); + LibRecordsConfigInit(); + + ink_event_system_init(EVENT_SYSTEM_MODULE_PUBLIC_VERSION); + eventProcessor.start(test_threads); + + EThread *main_thread = new EThread; + main_thread->set_specific(); + + SSLConfig::startup(); + } + + void + testRunEnded(Catch::TestRunStats const & /* testRunStats */) override + { + } +}; + +CATCH_REGISTER_LISTENER(EventProcessorListener); From 9abd9663859603cc65aae6a5a7e45057aa2cf8ef Mon Sep 17 00:00:00 2001 From: Josiah VanderZee Date: Thu, 1 Jun 2023 16:50:08 +0000 Subject: [PATCH 04/24] Route SNI based on port --- iocore/net/SSLSNIConfig.cc | 46 +--- iocore/net/SSLSNIConfig.h | 4 +- iocore/net/TLSSNISupport.cc | 13 +- iocore/net/YamlSNIConfig.cc | 2 +- iocore/net/unit_tests/test_SSLSNIConfig.cc | 14 +- iocore/net/unit_tests/test_YamlSNIConfig.cc | 18 +- .../tls/tls_sni_with_port.replay.yaml | 70 +++++++ .../gold_tests/tls/tls_sni_with_port.test.py | 198 ++++++++++++++++++ 8 files changed, 301 insertions(+), 64 deletions(-) create mode 100644 tests/gold_tests/tls/tls_sni_with_port.replay.yaml create mode 100644 tests/gold_tests/tls/tls_sni_with_port.test.py diff --git a/iocore/net/SSLSNIConfig.cc b/iocore/net/SSLSNIConfig.cc index 8471b561977..f8aff9676e3 100644 --- a/iocore/net/SSLSNIConfig.cc +++ b/iocore/net/SSLSNIConfig.cc @@ -115,7 +115,7 @@ SNIConfigParams::load_sni_config() auto &ai = sni_action_list.emplace_back(); ai.set_glob_name(item.fqdn); if (!item.port_ranges.empty()) { - const auto [min, max]{item.port_ranges[0]}; + auto const [min, max]{item.port_ranges[0]}; ai.ports = {min, max}; } else { ai.ports = {1, 65535}; @@ -189,7 +189,7 @@ SNIConfigParams::get(std::string_view servername, long conn_port) const { int ovector[OVECSIZE]; - for (const auto &retval : sni_action_list) { + for (auto const &retval : sni_action_list) { int length = servername.length(); if (retval.match == nullptr && length == 0) { return {&retval.actions, {}}; @@ -227,46 +227,6 @@ SNIConfigParams::get(std::string_view servername, long conn_port) const return {nullptr, {}}; } -std::pair -SNIConfigParams::get(std::string_view servername) const -{ - int ovector[OVECSIZE]; - - for (const auto &retval : sni_action_list) { - int length = servername.length(); - if (retval.match == nullptr && length == 0) { - return {&retval.actions, {}}; - } else if (auto offset = pcre_exec(retval.match.get(), nullptr, servername.data(), length, 0, 0, ovector, OVECSIZE); - offset >= 0) { - if (offset == 1) { - // first pair identify the portion of the subject string matched by the entire pattern - if (ovector[0] == 0 && ovector[1] == length) { - // full match - return {&retval.actions, {}}; - } else { - continue; - } - } - // If contains groups - if (offset == 0) { - // reset to max if too many. - offset = OVECSIZE / 3; - } - - ActionItem::Context::CapturedGroupViewVec groups; - groups.reserve(offset); - for (int strnum = 1; strnum < offset; strnum++) { - const std::size_t start = ovector[2 * strnum]; - const std::size_t length = ovector[2 * strnum + 1] - start; - - groups.emplace_back(servername.data() + start, length); - } - return {&retval.actions, {std::move(groups)}}; - } - } - return {nullptr, {}}; -} - int SNIConfigParams::initialize() { @@ -369,7 +329,7 @@ SNIConfig::test_client_action(const char *servername, const IpEndpoint &ep, int bool retval = false; SNIConfig::scoped_config params; - const auto &actions = params->get(servername); + auto const &actions = params->get(servername, 8080); if (actions.first) { for (auto &&item : *actions.first) { retval |= item->TestClientSNIAction(servername, ep, host_sni_policy); diff --git a/iocore/net/SSLSNIConfig.h b/iocore/net/SSLSNIConfig.h index bec0b37a4b4..d80c7efd2c0 100644 --- a/iocore/net/SSLSNIConfig.h +++ b/iocore/net/SSLSNIConfig.h @@ -92,8 +92,6 @@ struct SNIConfigParams : public ConfigInfo { SNIConfigParams() = default; ~SNIConfigParams() override; - std::pair get(std::string_view servername, long conn_port) const; - const NextHopProperty *get_property_config(const std::string &servername) const; int initialize(); int initialize(const std::string &sni_filename); @@ -101,7 +99,7 @@ struct SNIConfigParams : public ConfigInfo { @return 0 for success, 1 is failure */ int load_sni_config(); - std::pair get(std::string_view servername) const; + std::pair get(std::string_view servername, long conn_port) const; SNIList sni_action_list; NextHopPropertyList next_hop_list; diff --git a/iocore/net/TLSSNISupport.cc b/iocore/net/TLSSNISupport.cc index b0afe78b976..39c24cc83b5 100644 --- a/iocore/net/TLSSNISupport.cc +++ b/iocore/net/TLSSNISupport.cc @@ -22,9 +22,16 @@ */ #include "TLSSNISupport.h" #include "tscore/ink_assert.h" +#include "tscore/ink_inet.h" #include "tscore/Diags.h" #include "SSLSNIConfig.h" +#include "I_EventSystem.h" +#include "P_SSLNextProtocolAccept.h" +#include "P_SSLNetVConnection.h" +#include "SNIActionPerformer.h" +#include "SSLTypes.h" + int TLSSNISupport::_ex_data_index = -1; void @@ -64,7 +71,11 @@ TLSSNISupport::perform_sni_action() } SNIConfig::scoped_config params; - if (const auto &actions = params->get({servername, std::strlen(servername)}); !actions.first) { + SSLNetVConnection *ssl_vc{dynamic_cast(this)}; + ink_assert(ssl_vc != nullptr); + auto const port{ssl_vc->get_local_port()}; + Debug("ssl_sni", "local port %d", port); + if (auto const &actions = params->get({servername, std::strlen(servername)}, port); !actions.first) { Debug("ssl_sni", "%s not available in the map", servername); } else { for (auto &&item : *actions.first) { diff --git a/iocore/net/YamlSNIConfig.cc b/iocore/net/YamlSNIConfig.cc index a5ee0d42813..460a48422c2 100644 --- a/iocore/net/YamlSNIConfig.cc +++ b/iocore/net/YamlSNIConfig.cc @@ -191,7 +191,7 @@ template <> struct convert { if (!min) { min = port_view; } - const auto &max{port_view}; + auto const &max{port_view}; swoc::TextView parsed_min; long min_port{swoc::svtoi(min, &parsed_min)}; diff --git a/iocore/net/unit_tests/test_SSLSNIConfig.cc b/iocore/net/unit_tests/test_SSLSNIConfig.cc index 4f0ed5bdea4..49919aee410 100644 --- a/iocore/net/unit_tests/test_SSLSNIConfig.cc +++ b/iocore/net/unit_tests/test_SSLSNIConfig.cc @@ -41,46 +41,46 @@ TEST_CASE("Test SSLSNIConfig") SECTION("The config does not match any SNIs for someport.com:577") { - const auto &actions{params.get({"someport.com", std::strlen("someport.com")}, 577)}; + auto const &actions{params.get({"someport.com", std::strlen("someport.com")}, 577)}; CHECK(!actions.first); } SECTION("The config does not match any SNIs for someport.com:808") { - const auto &actions{params.get({"someport.com", std::strlen("someport.com")}, 808)}; + auto const &actions{params.get({"someport.com", std::strlen("someport.com")}, 808)}; CHECK(!actions.first); } SECTION("The config does not match any SNIs for oneport.com:1") { - const auto &actions{params.get({"oneport.com", std::strlen("oneport.com")}, 1)}; + auto const &actions{params.get({"oneport.com", std::strlen("oneport.com")}, 1)}; CHECK(!actions.first); } SECTION("The config matches an SNI for allports.com") { - const auto &actions{params.get({"allports.com", std::strlen("allports.com")}, 1)}; + auto const &actions{params.get({"allports.com", std::strlen("allports.com")}, 1)}; REQUIRE(actions.first); REQUIRE(actions.first->size() == 1); } SECTION("The config matches an SNI for someport.com:1") { - const auto &actions{params.get({"someport.com", std::strlen("someport.com")}, 1)}; + auto const &actions{params.get({"someport.com", std::strlen("someport.com")}, 1)}; REQUIRE(actions.first); REQUIRE(actions.first->size() == 1); } SECTION("The config matches an SNI for someport.com:433") { - const auto &actions{params.get({"someport.com", std::strlen("someport.com")}, 433)}; + auto const &actions{params.get({"someport.com", std::strlen("someport.com")}, 433)}; REQUIRE(actions.first); REQUIRE(actions.first->size() == 1); } SECTION("The config matches an SNI for someport:8080") { - const auto &actions{params.get({"someport.com", std::strlen("someport.com")}, 8080)}; + auto const &actions{params.get({"someport.com", std::strlen("someport.com")}, 8080)}; REQUIRE(actions.first); REQUIRE(actions.first->size() == 1); } diff --git a/iocore/net/unit_tests/test_YamlSNIConfig.cc b/iocore/net/unit_tests/test_YamlSNIConfig.cc index f7603be0c22..4b54f0ee66d 100644 --- a/iocore/net/unit_tests/test_YamlSNIConfig.cc +++ b/iocore/net/unit_tests/test_YamlSNIConfig.cc @@ -51,7 +51,7 @@ TEST_CASE("YamlSNIConfig sets port ranges appropriately") SECTION("If no ports were specified, ports should be empty.") { - const auto &item{conf.items[0]}; + auto const &item{conf.items[0]}; CHECK(item.port_ranges.size() == 0); } @@ -59,25 +59,25 @@ TEST_CASE("YamlSNIConfig sets port ranges appropriately") { SECTION("Ports 1-433.") { - const auto &item{conf.items[1]}; + auto const item{conf.items[1]}; REQUIRE(item.port_ranges.size() == 1); - const auto [min, max]{item.port_ranges[0]}; + auto const [min, max]{item.port_ranges[0]}; CHECK(min == 1); CHECK(max == 433); } SECTION("Ports 8080-65535.") { - const auto &item{conf.items[2]}; + auto const &item{conf.items[2]}; REQUIRE(item.port_ranges.size() == 1); - const auto [min, max]{item.port_ranges[0]}; + auto const [min, max]{item.port_ranges[0]}; CHECK(min == 8080); CHECK(max == 65535); } SECTION("Ports 433-433.") { - const auto &item{conf.items[3]}; + auto const &item{conf.items[3]}; REQUIRE(item.port_ranges.size() == 1); - const auto [min, max]{item.port_ranges[0]}; + auto const [min, max]{item.port_ranges[0]}; CHECK(min == 433); CHECK(max == 433); } @@ -85,13 +85,13 @@ TEST_CASE("YamlSNIConfig sets port ranges appropriately") SECTION("If a port was specified, it should not interfere with the fqdn.") { - const auto &item{conf.items[1]}; + auto const &item{conf.items[1]}; CHECK(item.fqdn == "someport.com"); } SECTION("If no port was specified, it should not interfere with the fqdn.") { - const auto &item{conf.items[0]}; + auto const &item{conf.items[0]}; CHECK(item.fqdn == "allports.com"); } } diff --git a/tests/gold_tests/tls/tls_sni_with_port.replay.yaml b/tests/gold_tests/tls/tls_sni_with_port.replay.yaml new file mode 100644 index 00000000000..2457d564a08 --- /dev/null +++ b/tests/gold_tests/tls/tls_sni_with_port.replay.yaml @@ -0,0 +1,70 @@ +# 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. + +meta: + version: "1.0" + +sessions: +- protocol: + - name: http + version: 1 + - name: tls + sni: yay.example.com + - name: tcp + - name: ip + + transactions: + - client-request: + method: "GET" + version: "1.1" + url: example.com + headers: + fields: + - [ Host, yay.example.com ] + - [ Connection, keep-alive ] + - [ Content-Length, 16 ] + - [ uuid, "conn_refused" ] + + server-response: + status: 200 + reason: OK + headers: + fields: + - [ Content-Length, 32 ] + + proxy-response: + status: 400 + + - client-request: + method: "GET" + version: "1.1" + url: example.com + headers: + fields: + - [ Host, yay.example.com ] + - [ Connection, keep-alive ] + - [ Content-Length, 16 ] + - [ uuid, "conn_accepted" ] + + server-response: + status: 200 + reason: OK + headers: + fields: + - [ Content-Length, 32 ] + + proxy-response: + status: 200 diff --git a/tests/gold_tests/tls/tls_sni_with_port.test.py b/tests/gold_tests/tls/tls_sni_with_port.test.py new file mode 100644 index 00000000000..0bf3ae237f1 --- /dev/null +++ b/tests/gold_tests/tls/tls_sni_with_port.test.py @@ -0,0 +1,198 @@ +# 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. + +import functools +from typing import Any, Callable, Optional + +from ports import get_port + +Test.Summary = 'Tests SNI port-based routing' + + +class TestSNIWithPort: + """Configure a test for SNI port-based routing .""" + + replay_filepath: str = "tls_sni_with_port.replay.yaml" + client_counter: int = 0 + server_counter: int = 0 + ts_counter: int = 0 + + def __init__(self, name: str, /, autorun) -> None: + """Initialize the test. + + :param name: The name of the test. + """ + self.name = name + self.autorun = autorun + + def _init_run(self) -> "TestRun": + """Initialize processes for the test run.""" + + tr = Test.AddTestRun(self.name) + server_one = TestSNIWithPort.configure_server(tr, "yay.com") + server_two = TestSNIWithPort.configure_server(tr, "oof.com") + self._configure_traffic_server(tr, server_one, server_two) + + tr.Processes.Default.StartBefore(server_one) + tr.Processes.Default.StartBefore(server_two) + tr.Processes.Default.StartBefore(self._ts) + + return tr, self._ts, server_one, server_two, self._port_one, self._port_two, self._unspecified_port + + @classmethod + def runner(cls, name: str, autorun: bool = True) -> Optional[Callable]: + """Create a runner for a test case. + + :param autorun: Run the test case once it's set up. Default is True. + """ + test = cls(name, autorun=autorun)._prepare_test_case + return test + + def _prepare_test_case(self, func: Callable) -> Callable: + """Set up a test case and possibly run it. + + :param func: The test case to set up. + """ + functools.wraps(func) + tr, *test_run_args = self._init_run() + + def wrapper(*args, **kwargs) -> Any: + return func(tr, *test_run_args, *args, **kwargs) + + if self.autorun: + wrapper() + return wrapper + + @staticmethod + def configure_server(tr: "TestRun", domain: str): + server = tr.AddVerifierServerProcess( + f"server{TestSNIWithPort.server_counter}.{domain}", + TestSNIWithPort.replay_filepath + ) + TestSNIWithPort.server_counter += 1 + + return server + + def _configure_traffic_server(self, tr: "TestRun", server_one: "Process", server_two: "Process"): + """Configure Traffic Server. + + :param tr: The TestRun object to associate the ts process with. + """ + ts = tr.MakeATSProcess(f"ts-{TestSNIWithPort.ts_counter}", select_ports=False, enable_tls=True) + TestSNIWithPort.ts_counter += 1 + + ts.addSSLfile("ssl/server.pem") + ts.addSSLfile("ssl/server.key") + ts.addSSLfile("ssl/signed-foo.pem") + ts.addSSLfile("ssl/signed-foo.key") + self._port_one = get_port(ts, "PortOne") + self._port_two = get_port(ts, "PortTwo") + self._unspecified_port = get_port(ts, "UnspecifiedPort") + ts.Disk.records_config.update({ + 'proxy.config.ssl.server.cert.path': f"{ts.Variables.SSLDir}", + 'proxy.config.ssl.server.private_key.path': f"{ts.Variables.SSLDir}", + 'proxy.config.http.server_ports': f"{self._port_one}:ssl {self._port_two}:ssl {self._unspecified_port}:ssl", + 'proxy.config.diags.debug.enabled': 1, + 'proxy.config.diags.debug.tags': 'dns|http|ssl|sni', + }) + + ts.Disk.sni_yaml.AddLines([ + "sni:", + f"- fqdn: yay.example.com:{self._port_one}-{self._port_one}", + f" tunnel_route: localhost:{server_one.Variables.https_port}", + f"- fqdn: yay.example.com:{self._port_two}-{self._port_two}", + f" tunnel_route: localhost:{server_two.Variables.https_port}" + ]) + + ts.Disk.ssl_multicert_config.AddLine( + f"dest_ip=* ssl_cert_name=signed-foo.pem ssl_key_name=signed-foo.key" + ) + + self._ts = ts + + +# Tests start. + +@TestSNIWithPort.runner("Test that a request to a port not in the SNI does not get through.") +def test0( + tr: "TestRun", + ts: "Process", + server_one: "Process", + server_two: "Process", + port_one: int, + port_two: int, + unspecified_port: int): + client = tr.AddVerifierClientProcess( + f"client0", + TestSNIWithPort.replay_filepath, + https_ports=[unspecified_port], + keys="conn_refused" + ) + + tr.Processes.Default.ReturnCode = 0 + ts.Disk.traffic_out.Content += Testers.IncludesExpression( + "not available in the map", "The SNI should not be found in the map." + ) + + +@TestSNIWithPort.runner("Test that a request to a port one goes to server one.") +def test1( + tr: "TestRun", + ts: "Process", + server_one: "Process", + server_two: "Process", + port_one: int, + port_two: int, + unspecified_port: int): + client = tr.AddVerifierClientProcess( + f"client1", + TestSNIWithPort.replay_filepath, + https_ports=[port_one], + keys="conn_accepted" + ) + + tr.Processes.Default.ReturnCode = 0 + server_one.Streams.All.Content += Testers.IncludesExpression( + "Received an HTTP/1 Content-Length body of 16 bytes for key conn_accepted", "the request should go to server one" + ) + server_two.Streams.All.Content += Testers.ExcludesExpression( + "Received an HTTP/1 Content-Length body of 16 bytes for key conn_accepted", "the request should not go to server two" + ) + + +@TestSNIWithPort.runner("Test that a request to port two goes to server two.") +def test2( + tr: "TestRun", + ts: "Process", + server_one: "Process", + server_two: "Process", + port_one: int, + port_two: int, + unspecified_port: int): + client = tr.AddVerifierClientProcess( + f"client2", + TestSNIWithPort.replay_filepath, + https_ports=[port_two], + keys="conn_accepted" + ) + + tr.Processes.Default.ReturnCode = 0 + server_two.Streams.All.Content += Testers.IncludesExpression( + "Received an HTTP/1 Content-Length body of 16 bytes for key conn_accepted", "the request should go to server one" + ) + server_one.Streams.All.Content += Testers.ExcludesExpression( + "Received an HTTP/1 Content-Length body of 16 bytes for key conn_accepted", "the request should not go to server two" + ) From 8486a80de490914cf17b094c7f2d1fb1392db514 Mon Sep 17 00:00:00 2001 From: Josiah VanderZee Date: Thu, 1 Jun 2023 17:46:07 +0000 Subject: [PATCH 05/24] Remove unnecessary headers from TLSSNiSupport.cc --- iocore/net/TLSSNISupport.cc | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/iocore/net/TLSSNISupport.cc b/iocore/net/TLSSNISupport.cc index 39c24cc83b5..2039d2b388e 100644 --- a/iocore/net/TLSSNISupport.cc +++ b/iocore/net/TLSSNISupport.cc @@ -20,17 +20,13 @@ See the License for the specific language governing permissions and limitations under the License. */ +#include "P_SSLNextProtocolAccept.h" +#include "P_SSLNetVConnection.h" +#include "SSLSNIConfig.h" #include "TLSSNISupport.h" #include "tscore/ink_assert.h" #include "tscore/ink_inet.h" #include "tscore/Diags.h" -#include "SSLSNIConfig.h" - -#include "I_EventSystem.h" -#include "P_SSLNextProtocolAccept.h" -#include "P_SSLNetVConnection.h" -#include "SNIActionPerformer.h" -#include "SSLTypes.h" int TLSSNISupport::_ex_data_index = -1; From f2cfe78faae0e2673a812023c80a10fbb7ba8630 Mon Sep 17 00:00:00 2001 From: Josiah VanderZee Date: Thu, 1 Jun 2023 18:56:46 +0000 Subject: [PATCH 06/24] Add missing licenses and clean up code --- iocore/net/SSLSNIConfig.cc | 15 +++++++-------- iocore/net/SSLSNIConfig.h | 15 +++++++++------ iocore/net/TLSSNISupport.cc | 4 ++-- iocore/net/YamlSNIConfig.cc | 6 +++++- iocore/net/unit_tests/sni_conf_test.yaml | 17 +++++++++++++++++ .../unit_tests/sni_conf_test_bad_port_0-1.yaml | 16 ++++++++++++++++ .../sni_conf_test_bad_port_1-yowzers2.yaml | 16 ++++++++++++++++ .../sni_conf_test_bad_port_65535-65536.yaml | 16 ++++++++++++++++ .../sni_conf_test_bad_port_8080-433.yaml | 16 ++++++++++++++++ .../sni_conf_test_bad_port_yowzers-1.yaml | 16 ++++++++++++++++ iocore/net/unit_tests/test_SSLSNIConfig.cc | 13 ++++++++++--- iocore/net/unit_tests/test_YamlSNIConfig.cc | 2 +- iocore/net/unit_tests/unit_test_main.cc | 2 +- proxy/http/HttpSM.cc | 3 ++- .../tls/tls_sni_with_port.replay.yaml | 4 ++-- 15 files changed, 136 insertions(+), 25 deletions(-) diff --git a/iocore/net/SSLSNIConfig.cc b/iocore/net/SSLSNIConfig.cc index f8aff9676e3..9eb32311e68 100644 --- a/iocore/net/SSLSNIConfig.cc +++ b/iocore/net/SSLSNIConfig.cc @@ -41,6 +41,7 @@ #include "tscpp/util/TextView.h" +#include #include #include #include @@ -116,9 +117,7 @@ SNIConfigParams::load_sni_config() ai.set_glob_name(item.fqdn); if (!item.port_ranges.empty()) { auto const [min, max]{item.port_ranges[0]}; - ai.ports = {min, max}; - } else { - ai.ports = {1, 65535}; + ai.ports = {static_cast(min), static_cast(max)}; } Debug("ssl", "name: %s", item.fqdn.data()); @@ -185,7 +184,7 @@ SNIConfigParams::load_sni_config() } std::pair -SNIConfigParams::get(std::string_view servername, long conn_port) const +SNIConfigParams::get(std::string_view servername, uint16_t dest_incoming_port) const { int ovector[OVECSIZE]; @@ -195,7 +194,7 @@ SNIConfigParams::get(std::string_view servername, long conn_port) const return {&retval.actions, {}}; } else if (auto offset = pcre_exec(retval.match.get(), nullptr, servername.data(), length, 0, 0, ovector, OVECSIZE); offset >= 0) { - if (!retval.ports.contains(conn_port)) { + if (!retval.ports.contains(dest_incoming_port)) { continue; } if (offset == 1) { @@ -235,7 +234,7 @@ SNIConfigParams::initialize() } int -SNIConfigParams::initialize(const std::string &sni_filename) +SNIConfigParams::initialize(std::string const &sni_filename) { Note("%s loading ...", sni_filename.c_str()); @@ -324,12 +323,12 @@ SNIConfig::release(SNIConfigParams *params) // setting proxy.config.http.host_sni_policy and is possibly overridden if the sni policy // contains a host_sni_policy entry bool -SNIConfig::test_client_action(const char *servername, const IpEndpoint &ep, int &host_sni_policy) +SNIConfig::test_client_action(const char *servername, uint16_t dest_incoming_port, const IpEndpoint &ep, int &host_sni_policy) { bool retval = false; SNIConfig::scoped_config params; - auto const &actions = params->get(servername, 8080); + auto const &actions = params->get(servername, dest_incoming_port); if (actions.first) { for (auto &&item : *actions.first) { retval |= item->TestClientSNIAction(servername, ep, host_sni_policy); diff --git a/iocore/net/SSLSNIConfig.h b/iocore/net/SSLSNIConfig.h index d80c7efd2c0..73c0cfad1fc 100644 --- a/iocore/net/SSLSNIConfig.h +++ b/iocore/net/SSLSNIConfig.h @@ -35,6 +35,7 @@ #include #include #include +#include #include #include "ConfigProcessor.h" @@ -62,8 +63,8 @@ struct PcreFreer { struct NamedElement { NamedElement() {} - NamedElement(const NamedElement &other) = delete; - NamedElement &operator=(const NamedElement &other) = delete; + NamedElement(NamedElement const &other) = delete; + NamedElement &operator=(NamedElement const &other) = delete; NamedElement(NamedElement &&other); NamedElement &operator=(NamedElement &&other); ~NamedElement() = default; @@ -71,8 +72,9 @@ struct NamedElement { void set_glob_name(std::string name); void set_regex_name(const std::string ®ex_name); - using port_range_t = swoc::DiscreteRange; - port_range_t ports{1, 65535}; + inline static constexpr uint16_t MAX_PORT_VALUE{std::numeric_limits::max()}; + using port_range_t = swoc::DiscreteRange; + port_range_t ports{1, MAX_PORT_VALUE}; std::unique_ptr match; }; @@ -99,7 +101,7 @@ struct SNIConfigParams : public ConfigInfo { @return 0 for success, 1 is failure */ int load_sni_config(); - std::pair get(std::string_view servername, long conn_port) const; + std::pair get(std::string_view servername, uint16_t dest_incoming_port) const; SNIList sni_action_list; NextHopPropertyList next_hop_list; @@ -119,7 +121,8 @@ class SNIConfig static SNIConfigParams *acquire(); static void release(SNIConfigParams *params); - static bool test_client_action(const char *servername, const IpEndpoint &ep, int &enforcement_policy); + static bool test_client_action(const char *servername, uint16_t dest_incoming_port, const IpEndpoint &ep, + int &enforcement_policy); private: static int _configid; diff --git a/iocore/net/TLSSNISupport.cc b/iocore/net/TLSSNISupport.cc index 2039d2b388e..99852da4fd3 100644 --- a/iocore/net/TLSSNISupport.cc +++ b/iocore/net/TLSSNISupport.cc @@ -67,12 +67,12 @@ TLSSNISupport::perform_sni_action() } SNIConfig::scoped_config params; + // should always work in this context of SSL action callbacks SSLNetVConnection *ssl_vc{dynamic_cast(this)}; ink_assert(ssl_vc != nullptr); auto const port{ssl_vc->get_local_port()}; - Debug("ssl_sni", "local port %d", port); if (auto const &actions = params->get({servername, std::strlen(servername)}, port); !actions.first) { - Debug("ssl_sni", "%s not available in the map", servername); + Debug("ssl_sni", "%s:%i not available in the map", servername, port); } else { for (auto &&item : *actions.first) { auto ret = item->SNIAction(this, actions.second); diff --git a/iocore/net/YamlSNIConfig.cc b/iocore/net/YamlSNIConfig.cc index 460a48422c2..a8ff669ddcf 100644 --- a/iocore/net/YamlSNIConfig.cc +++ b/iocore/net/YamlSNIConfig.cc @@ -26,7 +26,9 @@ #include #include #include +#include #include +#include #include #include @@ -197,7 +199,8 @@ template <> struct convert { long min_port{swoc::svtoi(min, &parsed_min)}; swoc::TextView parsed_max; long max_port{swoc::svtoi(max, &parsed_max)}; - if (parsed_min != min || min_port < 1 || parsed_max != max || max_port > 65535 || max_port < min_port) { + if (parsed_min != min || min_port < 1 || parsed_max != max || max_port > std::numeric_limits::max() || + max_port < min_port) { std::string out; swoc::bwprint(out, "bad port range: {}-{}", min, max); throw YAML::ParserException(node[TS_fqdn].Mark(), out); @@ -205,6 +208,7 @@ template <> struct convert { item.port_ranges.emplace_back(std::make_pair(min_port, max_port)); } else { + // no port found; port_view contains entire fqdn and fqdn was emptied item.fqdn = port_view; } } else { diff --git a/iocore/net/unit_tests/sni_conf_test.yaml b/iocore/net/unit_tests/sni_conf_test.yaml index c968dbb3af8..fd9a6bc43dd 100644 --- a/iocore/net/unit_tests/sni_conf_test.yaml +++ b/iocore/net/unit_tests/sni_conf_test.yaml @@ -1,5 +1,22 @@ +# 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. + sni: - fqdn: allports.com - fqdn: someport.com:1-433 + http2: true - fqdn: someport.com:8080-65535 - fqdn: oneport.com:433-433 diff --git a/iocore/net/unit_tests/sni_conf_test_bad_port_0-1.yaml b/iocore/net/unit_tests/sni_conf_test_bad_port_0-1.yaml index 0c1aed96ce7..18a28ccfa3d 100644 --- a/iocore/net/unit_tests/sni_conf_test_bad_port_0-1.yaml +++ b/iocore/net/unit_tests/sni_conf_test_bad_port_0-1.yaml @@ -1,2 +1,18 @@ +# 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. + sni: - fqdn: badport.com:0-1 diff --git a/iocore/net/unit_tests/sni_conf_test_bad_port_1-yowzers2.yaml b/iocore/net/unit_tests/sni_conf_test_bad_port_1-yowzers2.yaml index 82efa27c2b5..3d9efb01f98 100644 --- a/iocore/net/unit_tests/sni_conf_test_bad_port_1-yowzers2.yaml +++ b/iocore/net/unit_tests/sni_conf_test_bad_port_1-yowzers2.yaml @@ -1,2 +1,18 @@ +# 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. + sni: - fqdn: badport.com:1-yowzers2 diff --git a/iocore/net/unit_tests/sni_conf_test_bad_port_65535-65536.yaml b/iocore/net/unit_tests/sni_conf_test_bad_port_65535-65536.yaml index 79480927393..e4e0d2fa40c 100644 --- a/iocore/net/unit_tests/sni_conf_test_bad_port_65535-65536.yaml +++ b/iocore/net/unit_tests/sni_conf_test_bad_port_65535-65536.yaml @@ -1,2 +1,18 @@ +# 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. + sni: - fqdn: badport.com:65535-65536 diff --git a/iocore/net/unit_tests/sni_conf_test_bad_port_8080-433.yaml b/iocore/net/unit_tests/sni_conf_test_bad_port_8080-433.yaml index d88e705eaee..9517b297ecd 100644 --- a/iocore/net/unit_tests/sni_conf_test_bad_port_8080-433.yaml +++ b/iocore/net/unit_tests/sni_conf_test_bad_port_8080-433.yaml @@ -1,2 +1,18 @@ +# 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. + sni: - fqdn: badport.com:8080-433 diff --git a/iocore/net/unit_tests/sni_conf_test_bad_port_yowzers-1.yaml b/iocore/net/unit_tests/sni_conf_test_bad_port_yowzers-1.yaml index 8c97208abba..413743e4961 100644 --- a/iocore/net/unit_tests/sni_conf_test_bad_port_yowzers-1.yaml +++ b/iocore/net/unit_tests/sni_conf_test_bad_port_yowzers-1.yaml @@ -1,2 +1,18 @@ +# 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. + sni: - fqdn: notaport.com:yowzers-1 diff --git a/iocore/net/unit_tests/test_SSLSNIConfig.cc b/iocore/net/unit_tests/test_SSLSNIConfig.cc index 49919aee410..6a53ddab638 100644 --- a/iocore/net/unit_tests/test_SSLSNIConfig.cc +++ b/iocore/net/unit_tests/test_SSLSNIConfig.cc @@ -1,6 +1,6 @@ /** @file - Catch based unit tests for YamlSNIConfig + Catch based unit tests for SSLSNIConfig @section license License @@ -68,14 +68,14 @@ TEST_CASE("Test SSLSNIConfig") { auto const &actions{params.get({"someport.com", std::strlen("someport.com")}, 1)}; REQUIRE(actions.first); - REQUIRE(actions.first->size() == 1); + REQUIRE(actions.first->size() == 2); } SECTION("The config matches an SNI for someport.com:433") { auto const &actions{params.get({"someport.com", std::strlen("someport.com")}, 433)}; REQUIRE(actions.first); - REQUIRE(actions.first->size() == 1); + REQUIRE(actions.first->size() == 2); } SECTION("The config matches an SNI for someport:8080") @@ -84,4 +84,11 @@ TEST_CASE("Test SSLSNIConfig") REQUIRE(actions.first); REQUIRE(actions.first->size() == 1); } + + SECTION("The config matches an SNI for someport:65535") + { + auto const &actions{params.get({"someport.com", std::strlen("someport.com")}, 65535)}; + REQUIRE(actions.first); + REQUIRE(actions.first->size() == 1); + } } diff --git a/iocore/net/unit_tests/test_YamlSNIConfig.cc b/iocore/net/unit_tests/test_YamlSNIConfig.cc index 4b54f0ee66d..19ebc971b47 100644 --- a/iocore/net/unit_tests/test_YamlSNIConfig.cc +++ b/iocore/net/unit_tests/test_YamlSNIConfig.cc @@ -110,6 +110,6 @@ TEST_CASE("YamlConfig handles bad ports appropriately.") errorstream << zret; std::string expected; - swoc::bwprint(expected, "1 [1]: yaml-cpp: error at line 2, column 9: bad port range: {}\n", port_str); + swoc::bwprint(expected, "1 [1]: yaml-cpp: error at line 18, column 9: bad port range: {}\n", port_str); CHECK(errorstream.str() == expected); } diff --git a/iocore/net/unit_tests/unit_test_main.cc b/iocore/net/unit_tests/unit_test_main.cc index 7db696f061f..701b818c432 100644 --- a/iocore/net/unit_tests/unit_test_main.cc +++ b/iocore/net/unit_tests/unit_test_main.cc @@ -1,6 +1,6 @@ /** @file - Catch based unit tests for YamlSNIConfig + Catch based unit tests for libinknet @section license License diff --git a/proxy/http/HttpSM.cc b/proxy/http/HttpSM.cc index 9b8896fd7ed..ae5a710bb47 100644 --- a/proxy/http/HttpSM.cc +++ b/proxy/http/HttpSM.cc @@ -4447,7 +4447,8 @@ HttpSM::check_sni_host() NetVConnection *netvc = ua_txn->get_netvc(); if (netvc) { IpEndpoint ip = netvc->get_remote_endpoint(); - if (SNIConfig::test_client_action(std::string{host_name, static_cast(host_len)}.c_str(), ip, host_sni_policy) && + if (SNIConfig::test_client_action(std::string{host_name, static_cast(host_len)}.c_str(), netvc->get_local_port(), + ip, host_sni_policy) && host_sni_policy > 0) { // In a SNI/Host mismatch where the Host would have triggered SNI policy, mark the transaction // to be considered for rejection after the remap phase passes. Gives the opportunity to conf_remap diff --git a/tests/gold_tests/tls/tls_sni_with_port.replay.yaml b/tests/gold_tests/tls/tls_sni_with_port.replay.yaml index 2457d564a08..4d6500c8b13 100644 --- a/tests/gold_tests/tls/tls_sni_with_port.replay.yaml +++ b/tests/gold_tests/tls/tls_sni_with_port.replay.yaml @@ -30,7 +30,7 @@ sessions: - client-request: method: "GET" version: "1.1" - url: example.com + url: a/path/conn_refused headers: fields: - [ Host, yay.example.com ] @@ -51,7 +51,7 @@ sessions: - client-request: method: "GET" version: "1.1" - url: example.com + url: /a/path/conn_accepted headers: fields: - [ Host, yay.example.com ] From 0d4dffe6b338ae31f70c7c46f0fd7802b5aac96e Mon Sep 17 00:00:00 2001 From: Josiah VanderZee Date: Thu, 1 Jun 2023 19:49:37 +0000 Subject: [PATCH 07/24] Add more unit tests for SNI port filtering --- iocore/net/YamlSNIConfig.h | 3 +- iocore/net/unit_tests/test_SSLSNIConfig.cc | 8 +++++ .../tls/tls_sni_with_port.replay.yaml | 6 ++-- .../gold_tests/tls/tls_sni_with_port.test.py | 36 ++++++++++++------- 4 files changed, 37 insertions(+), 16 deletions(-) diff --git a/iocore/net/YamlSNIConfig.h b/iocore/net/YamlSNIConfig.h index 020e9bdaa80..d681d20a782 100644 --- a/iocore/net/YamlSNIConfig.h +++ b/iocore/net/YamlSNIConfig.h @@ -27,6 +27,7 @@ #include #include #include +#include #include "SSLTypes.h" @@ -74,7 +75,7 @@ struct YamlSNIConfig { struct Item { std::string fqdn; - std::vector> port_ranges; + std::vector> port_ranges; std::optional offer_h2; // Has no value by default, so do not initialize! std::optional offer_quic; // Has no value by default, so do not initialize! uint8_t verify_client_level = 255; diff --git a/iocore/net/unit_tests/test_SSLSNIConfig.cc b/iocore/net/unit_tests/test_SSLSNIConfig.cc index 6a53ddab638..59651c6423c 100644 --- a/iocore/net/unit_tests/test_SSLSNIConfig.cc +++ b/iocore/net/unit_tests/test_SSLSNIConfig.cc @@ -57,6 +57,13 @@ TEST_CASE("Test SSLSNIConfig") CHECK(!actions.first); } + SECTION("The config does match an SNI for oneport.com:433") + { + auto const &actions{params.get({"oneport.com", std::strlen("oneport.com")}, 433)}; + REQUIRE(actions.first); + REQUIRE(actions.first->size() == 1); + } + SECTION("The config matches an SNI for allports.com") { auto const &actions{params.get({"allports.com", std::strlen("allports.com")}, 1)}; @@ -71,6 +78,7 @@ TEST_CASE("Test SSLSNIConfig") REQUIRE(actions.first->size() == 2); } + SECTION("The config matches an SNI for someport.com:433") { auto const &actions{params.get({"someport.com", std::strlen("someport.com")}, 433)}; diff --git a/tests/gold_tests/tls/tls_sni_with_port.replay.yaml b/tests/gold_tests/tls/tls_sni_with_port.replay.yaml index 4d6500c8b13..a86a4de652c 100644 --- a/tests/gold_tests/tls/tls_sni_with_port.replay.yaml +++ b/tests/gold_tests/tls/tls_sni_with_port.replay.yaml @@ -30,13 +30,13 @@ sessions: - client-request: method: "GET" version: "1.1" - url: a/path/conn_refused + url: /a/path/conn_remapped headers: fields: - [ Host, yay.example.com ] - [ Connection, keep-alive ] - [ Content-Length, 16 ] - - [ uuid, "conn_refused" ] + - [ uuid, "conn_remapped" ] server-response: status: 200 @@ -46,7 +46,7 @@ sessions: - [ Content-Length, 32 ] proxy-response: - status: 400 + status: 200 - client-request: method: "GET" diff --git a/tests/gold_tests/tls/tls_sni_with_port.test.py b/tests/gold_tests/tls/tls_sni_with_port.test.py index 0bf3ae237f1..b127e69a8e8 100644 --- a/tests/gold_tests/tls/tls_sni_with_port.test.py +++ b/tests/gold_tests/tls/tls_sni_with_port.test.py @@ -44,13 +44,14 @@ def _init_run(self) -> "TestRun": tr = Test.AddTestRun(self.name) server_one = TestSNIWithPort.configure_server(tr, "yay.com") server_two = TestSNIWithPort.configure_server(tr, "oof.com") - self._configure_traffic_server(tr, server_one, server_two) + server_three = TestSNIWithPort.configure_server(tr, "wow.com") + self._configure_traffic_server(tr, server_one, server_two, server_three) tr.Processes.Default.StartBefore(server_one) tr.Processes.Default.StartBefore(server_two) tr.Processes.Default.StartBefore(self._ts) - return tr, self._ts, server_one, server_two, self._port_one, self._port_two, self._unspecified_port + return tr, self._ts, server_one, server_two, server_three, self._port_one, self._port_two, self._unspecified_port @classmethod def runner(cls, name: str, autorun: bool = True) -> Optional[Callable]: @@ -86,7 +87,7 @@ def configure_server(tr: "TestRun", domain: str): return server - def _configure_traffic_server(self, tr: "TestRun", server_one: "Process", server_two: "Process"): + def _configure_traffic_server(self, tr: "TestRun", server_one: "Process", server_two: "Process", server_three: "Process"): """Configure Traffic Server. :param tr: The TestRun object to associate the ts process with. @@ -94,10 +95,7 @@ def _configure_traffic_server(self, tr: "TestRun", server_one: "Process", server ts = tr.MakeATSProcess(f"ts-{TestSNIWithPort.ts_counter}", select_ports=False, enable_tls=True) TestSNIWithPort.ts_counter += 1 - ts.addSSLfile("ssl/server.pem") - ts.addSSLfile("ssl/server.key") - ts.addSSLfile("ssl/signed-foo.pem") - ts.addSSLfile("ssl/signed-foo.key") + ts.addDefaultSSLFiles() self._port_one = get_port(ts, "PortOne") self._port_two = get_port(ts, "PortTwo") self._unspecified_port = get_port(ts, "UnspecifiedPort") @@ -109,6 +107,8 @@ def _configure_traffic_server(self, tr: "TestRun", server_one: "Process", server 'proxy.config.diags.debug.tags': 'dns|http|ssl|sni', }) + ts.Disk.remap_config.AddLine(f"map / http://127.0.0.1:{server_three.Variables.http_port}") + ts.Disk.sni_yaml.AddLines([ "sni:", f"- fqdn: yay.example.com:{self._port_one}-{self._port_one}", @@ -118,7 +118,7 @@ def _configure_traffic_server(self, tr: "TestRun", server_one: "Process", server ]) ts.Disk.ssl_multicert_config.AddLine( - f"dest_ip=* ssl_cert_name=signed-foo.pem ssl_key_name=signed-foo.key" + f"dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key" ) self._ts = ts @@ -132,6 +132,7 @@ def test0( ts: "Process", server_one: "Process", server_two: "Process", + server_three: "Process", port_one: int, port_two: int, unspecified_port: int): @@ -139,12 +140,21 @@ def test0( f"client0", TestSNIWithPort.replay_filepath, https_ports=[unspecified_port], - keys="conn_refused" + keys="conn_remapped" ) tr.Processes.Default.ReturnCode = 0 ts.Disk.traffic_out.Content += Testers.IncludesExpression( - "not available in the map", "The SNI should not be found in the map." + "not available in the map", "the request should not match an SNI" + ) + server_one.Streams.All.Content += Testers.ExcludesExpression( + "Received an HTTP/1 Content-Length body of 16 bytes for key conn_remapped", "the request should not go to server one" + ) + server_two.Streams.All.Content += Testers.ExcludesExpression( + "Received an HTTP/1 Content-Length body of 16 bytes for key conn_remapped", "the request should not go to server two" + ) + server_three.Streams.All.Content += Testers.IncludesExpression( + "Received an HTTP/1 Content-Length body of 16 bytes for key conn_remapped", "request was remaped to server three" ) @@ -154,6 +164,7 @@ def test1( ts: "Process", server_one: "Process", server_two: "Process", + server_three: "Process", port_one: int, port_two: int, unspecified_port: int): @@ -179,6 +190,7 @@ def test2( ts: "Process", server_one: "Process", server_two: "Process", + server_three: "Process", port_one: int, port_two: int, unspecified_port: int): @@ -191,8 +203,8 @@ def test2( tr.Processes.Default.ReturnCode = 0 server_two.Streams.All.Content += Testers.IncludesExpression( - "Received an HTTP/1 Content-Length body of 16 bytes for key conn_accepted", "the request should go to server one" + "Received an HTTP/1 Content-Length body of 16 bytes for key conn_accepted", "the request should go to server two" ) server_one.Streams.All.Content += Testers.ExcludesExpression( - "Received an HTTP/1 Content-Length body of 16 bytes for key conn_accepted", "the request should not go to server two" + "Received an HTTP/1 Content-Length body of 16 bytes for key conn_accepted", "the request should not go to server one" ) From ce7cc581b54cb65d2a8e36bc0876704c16aa9c79 Mon Sep 17 00:00:00 2001 From: Josiah VanderZee Date: Thu, 1 Jun 2023 19:58:44 +0000 Subject: [PATCH 08/24] Add tests for single port --- iocore/net/unit_tests/sni_conf_test.yaml | 2 +- iocore/net/unit_tests/test_SSLSNIConfig.cc | 1 - iocore/net/unit_tests/test_YamlSNIConfig.cc | 2 +- tests/gold_tests/tls/tls_sni_with_port.test.py | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/iocore/net/unit_tests/sni_conf_test.yaml b/iocore/net/unit_tests/sni_conf_test.yaml index fd9a6bc43dd..d09c13e2ed3 100644 --- a/iocore/net/unit_tests/sni_conf_test.yaml +++ b/iocore/net/unit_tests/sni_conf_test.yaml @@ -19,4 +19,4 @@ sni: - fqdn: someport.com:1-433 http2: true - fqdn: someport.com:8080-65535 -- fqdn: oneport.com:433-433 +- fqdn: oneport.com:433 diff --git a/iocore/net/unit_tests/test_SSLSNIConfig.cc b/iocore/net/unit_tests/test_SSLSNIConfig.cc index 59651c6423c..a910bd9f5f1 100644 --- a/iocore/net/unit_tests/test_SSLSNIConfig.cc +++ b/iocore/net/unit_tests/test_SSLSNIConfig.cc @@ -78,7 +78,6 @@ TEST_CASE("Test SSLSNIConfig") REQUIRE(actions.first->size() == 2); } - SECTION("The config matches an SNI for someport.com:433") { auto const &actions{params.get({"someport.com", std::strlen("someport.com")}, 433)}; diff --git a/iocore/net/unit_tests/test_YamlSNIConfig.cc b/iocore/net/unit_tests/test_YamlSNIConfig.cc index 19ebc971b47..f09ea1e2ab2 100644 --- a/iocore/net/unit_tests/test_YamlSNIConfig.cc +++ b/iocore/net/unit_tests/test_YamlSNIConfig.cc @@ -73,7 +73,7 @@ TEST_CASE("YamlSNIConfig sets port ranges appropriately") CHECK(min == 8080); CHECK(max == 65535); } - SECTION("Ports 433-433.") + SECTION("Port 433.") { auto const &item{conf.items[3]}; REQUIRE(item.port_ranges.size() == 1); diff --git a/tests/gold_tests/tls/tls_sni_with_port.test.py b/tests/gold_tests/tls/tls_sni_with_port.test.py index b127e69a8e8..62f1388feee 100644 --- a/tests/gold_tests/tls/tls_sni_with_port.test.py +++ b/tests/gold_tests/tls/tls_sni_with_port.test.py @@ -113,7 +113,7 @@ def _configure_traffic_server(self, tr: "TestRun", server_one: "Process", server "sni:", f"- fqdn: yay.example.com:{self._port_one}-{self._port_one}", f" tunnel_route: localhost:{server_one.Variables.https_port}", - f"- fqdn: yay.example.com:{self._port_two}-{self._port_two}", + f"- fqdn: yay.example.com:{self._port_two}", f" tunnel_route: localhost:{server_two.Variables.https_port}" ]) From 324a168ff3b6248499078a09e26049c13767185e Mon Sep 17 00:00:00 2001 From: Josiah VanderZee Date: Thu, 1 Jun 2023 20:22:04 +0000 Subject: [PATCH 09/24] Document new port filtering feature --- doc/admin-guide/files/sni.yaml.en.rst | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/doc/admin-guide/files/sni.yaml.en.rst b/doc/admin-guide/files/sni.yaml.en.rst index f7f90089df6..c486d937f59 100644 --- a/doc/admin-guide/files/sni.yaml.en.rst +++ b/doc/admin-guide/files/sni.yaml.en.rst @@ -27,7 +27,7 @@ Description This file is used to configure aspects of TLS connection handling for both inbound and outbound connections. With the exception of ``host_sni_policy`` (see the description below), the configuration is driven by the SNI values provided by the inbound connection. The -file consists of a set of configuration items, each identified by an SNI value (``fqdn``). +file consists of a set of configuration items, each identified by an SNI value and optionally a port range (``fqdn``). When an inbound TLS connection is made, the SNI value from the TLS negotiation is matched against the items specified by this file and if there is a match, the values specified in that item override the defaults. This is done during the inbound connection processing; some outbound properties @@ -55,7 +55,22 @@ for a more detailed description of HTTP/2 connection coalescing. A similar thing ========================= ========= ======================================================================================== Key Direction Meaning ========================= ========= ======================================================================================== -fqdn Both Fully Qualified Domain Name. This item is used if the SNI value matches this. +fqdn[:min[-max]] Both Fully Qualified Domain Name. This is the key for the items in this configuration file. + The incoming destination port will also be matched against the port range if present. + The max end of the range defaults to min if it is not specified. + For example: + + ``example.com`` + + would match all requests with an SNI for example.com + + ``example.com:443`` + + would only match requests with an SNI for example.com on port 443, and + + ``example.com:443-446`` + + would match requests with an SNI for example.com on ports 443 to 446, inclusive. ip_allow Inbound Specify a list of client IP address, subnets, or ranges what are allowed to complete the connection. This list is comma separated. IPv4 and IPv6 addresses can be specified. From deb5dbfb73baa8df2de3f7eb7f786f12332ab8ae Mon Sep 17 00:00:00 2001 From: Josiah VanderZee Date: Thu, 1 Jun 2023 20:27:20 +0000 Subject: [PATCH 10/24] Remove trailing whitespace --- tests/gold_tests/tls/tls_sni_with_port.replay.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/gold_tests/tls/tls_sni_with_port.replay.yaml b/tests/gold_tests/tls/tls_sni_with_port.replay.yaml index a86a4de652c..d114d07b345 100644 --- a/tests/gold_tests/tls/tls_sni_with_port.replay.yaml +++ b/tests/gold_tests/tls/tls_sni_with_port.replay.yaml @@ -47,7 +47,7 @@ sessions: proxy-response: status: 200 - + - client-request: method: "GET" version: "1.1" From 72a17302ec41d80dead98c18f856a7e219a01d49 Mon Sep 17 00:00:00 2001 From: Josiah VanderZee Date: Fri, 2 Jun 2023 14:13:23 +0000 Subject: [PATCH 11/24] Move ActionItem creation to YamlSNIConfig::Item --- iocore/net/SSLSNIConfig.cc | 36 +----------------------------------- iocore/net/YamlSNIConfig.cc | 34 ++++++++++++++++++++++++++++++++++ iocore/net/YamlSNIConfig.h | 4 ++++ 3 files changed, 39 insertions(+), 35 deletions(-) diff --git a/iocore/net/SSLSNIConfig.cc b/iocore/net/SSLSNIConfig.cc index 9eb32311e68..c329b31fbc1 100644 --- a/iocore/net/SSLSNIConfig.cc +++ b/iocore/net/SSLSNIConfig.cc @@ -121,41 +121,7 @@ SNIConfigParams::load_sni_config() } Debug("ssl", "name: %s", item.fqdn.data()); - // set SNI based actions to be called in the ssl_servername_only callback - if (item.offer_h2.has_value()) { - ai.actions.push_back(std::make_unique(item.offer_h2.value())); - } - if (item.offer_quic.has_value()) { - ai->actions.push_back(std::make_unique(item.offer_quic.value())); - } - if (item.verify_client_level != 255) { - ai.actions.push_back( - std::make_unique(item.verify_client_level, item.verify_client_ca_file, item.verify_client_ca_dir)); - } - if (item.host_sni_policy != 255) { - ai.actions.push_back(std::make_unique(item.host_sni_policy)); - } - if (item.valid_tls_version_min_in >= 0 || item.valid_tls_version_max_in >= 0) { - ai.actions.push_back(std::make_unique(item.valid_tls_version_min_in, item.valid_tls_version_max_in)); - } else { - if (!item.protocol_unset) { - ai.actions.push_back(std::make_unique(item.protocol_mask)); - } - } - if (item.tunnel_destination.length() > 0) { - ai.actions.push_back( - std::make_unique(item.tunnel_destination, item.tunnel_type, item.tunnel_prewarm, item.tunnel_alpn)); - } - if (!item.client_sni_policy.empty()) { - ai.actions.push_back(std::make_unique(item.client_sni_policy)); - } - if (item.http2_buffer_water_mark.has_value()) { - ai.actions.push_back(std::make_unique(item.http2_buffer_water_mark.value())); - } - - ai.actions.push_back(std::make_unique(item.server_max_early_data)); - - ai.actions.push_back(std::make_unique(item.ip_allow, item.fqdn)); + item.populate_sni_actions(ai.actions); // set the next hop properties auto nps = next_hop_list.emplace(next_hop_list.end()); diff --git a/iocore/net/YamlSNIConfig.cc b/iocore/net/YamlSNIConfig.cc index a8ff669ddcf..d25a03bf321 100644 --- a/iocore/net/YamlSNIConfig.cc +++ b/iocore/net/YamlSNIConfig.cc @@ -125,6 +125,40 @@ YamlSNIConfig::Item::EnableProtocol(YamlSNIConfig::TLSProtocol proto) } } +void +YamlSNIConfig::Item::populate_sni_actions(action_vector_t &actions) +{ + if (offer_h2.has_value()) { + actions.push_back(std::make_unique(offer_h2.value())); + } + if (item.offer_quic.has_value()) { + actions.push_back(std::make_unique(offer_quic.value())); + } + if (verify_client_level != 255) { + actions.push_back(std::make_unique(verify_client_level, verify_client_ca_file, verify_client_ca_dir)); + } + if (host_sni_policy != 255) { + actions.push_back(std::make_unique(host_sni_policy)); + } + if (valid_tls_version_min_in >= 0 || valid_tls_version_max_in >= 0) { + actions.push_back(std::make_unique(valid_tls_version_min_in, valid_tls_version_max_in)); + } else if (!protocol_unset) { + actions.push_back(std::make_unique(protocol_mask)); + } + if (tunnel_destination.length() > 0) { + actions.push_back(std::make_unique(tunnel_destination, tunnel_type, tunnel_prewarm, tunnel_alpn)); + } + if (!client_sni_policy.empty()) { + actions.push_back(std::make_unique(client_sni_policy)); + } + if (http2_buffer_water_mark.has_value()) { + actions.push_back(std::make_unique(http2_buffer_water_mark.value())); + } + + actions.push_back(std::make_unique(server_max_early_data)); + actions.push_back(std::make_unique(ip_allow, fqdn)); +} + VerifyClient::~VerifyClient() {} TsEnumDescriptor LEVEL_DESCRIPTOR = { diff --git a/iocore/net/YamlSNIConfig.h b/iocore/net/YamlSNIConfig.h index d681d20a782..086089701af 100644 --- a/iocore/net/YamlSNIConfig.h +++ b/iocore/net/YamlSNIConfig.h @@ -29,6 +29,7 @@ #include #include +#include "SNIActionPerformer.h" #include "SSLTypes.h" #include "tscore/Errata.h" @@ -106,7 +107,10 @@ struct YamlSNIConfig { uint32_t tunnel_prewarm_inactive_timeout = 0; TunnelPreWarm tunnel_prewarm = TunnelPreWarm::UNSET; + using action_vector_t = std::vector>; + void EnableProtocol(YamlSNIConfig::TLSProtocol proto); + void populate_sni_actions(action_vector_t &actions); }; ts::Errata loader(const std::string &cfgFilename); From 3f3709ee9dcdc2c5d67374f2c3d7244389d43ba9 Mon Sep 17 00:00:00 2001 From: Josiah VanderZee Date: Fri, 2 Jun 2023 14:45:13 +0000 Subject: [PATCH 12/24] Extract methods from SSLSNIConfig::load_sni_config --- iocore/net/SSLSNIConfig.cc | 52 ++++++++++++++-------- iocore/net/SSLSNIConfig.h | 8 +++- iocore/net/unit_tests/test_SSLSNIConfig.cc | 12 ++--- proxy/http/PreWarmManager.h | 2 +- 4 files changed, 48 insertions(+), 26 deletions(-) diff --git a/iocore/net/SSLSNIConfig.cc b/iocore/net/SSLSNIConfig.cc index c329b31fbc1..bfb5a7167b3 100644 --- a/iocore/net/SSLSNIConfig.cc +++ b/iocore/net/SSLSNIConfig.cc @@ -122,29 +122,45 @@ SNIConfigParams::load_sni_config() Debug("ssl", "name: %s", item.fqdn.data()); item.populate_sni_actions(ai.actions); + if (set_next_hop_properties(item) == 1) { + return 1; + } + } - // set the next hop properties - auto nps = next_hop_list.emplace(next_hop_list.end()); + return 0; +} - SSLConfig::scoped_config params; - // Load if we have at least specified the client certificate - if (!item.client_cert.empty()) { - nps->prop.client_cert_file = Layout::get()->relative_to(params->clientCertPathOnly, item.client_cert.data()); - if (!item.client_key.empty()) { - nps->prop.client_key_file = Layout::get()->relative_to(params->clientKeyPathOnly, item.client_key.data()); - } +int +SNIConfigParams::set_next_hop_properties(YamlSNIConfig::Item const &item) +{ + auto &nps = next_hop_list.emplace_back(); + if (load_certs_if_client_cert_specified(item, nps) == 1) { + return 1; + }; - auto ctx = params->getCTX(nps->prop.client_cert_file, nps->prop.client_key_file, params->clientCACertFilename, - params->clientCACertPath); - if (ctx.get() == nullptr) { - return 1; - } + nps.set_glob_name(item.fqdn); + nps.prop.verify_server_policy = item.verify_server_policy; + nps.prop.verify_server_properties = item.verify_server_properties; + + return 0; +} + +int +SNIConfigParams::load_certs_if_client_cert_specified(YamlSNIConfig::Item const &item, NextHopItem &nps) +{ + if (!item.client_cert.empty()) { + SSLConfig::scoped_config params; + nps.prop.client_cert_file = Layout::get()->relative_to(params->clientCertPathOnly, item.client_cert.data()); + if (!item.client_key.empty()) { + nps.prop.client_key_file = Layout::get()->relative_to(params->clientKeyPathOnly, item.client_key.data()); } - nps->set_glob_name(item.fqdn); - nps->prop.verify_server_policy = item.verify_server_policy; - nps->prop.verify_server_properties = item.verify_server_properties; - } // end for + auto ctx = + params->getCTX(nps.prop.client_cert_file, nps.prop.client_key_file, params->clientCACertFilename, params->clientCACertPath); + if (ctx.get() == nullptr) { + return 1; + } + } return 0; } diff --git a/iocore/net/SSLSNIConfig.h b/iocore/net/SSLSNIConfig.h index 73c0cfad1fc..b21c0879381 100644 --- a/iocore/net/SSLSNIConfig.h +++ b/iocore/net/SSLSNIConfig.h @@ -90,7 +90,9 @@ struct NextHopItem : public NamedElement { using SNIList = std::vector; using NextHopPropertyList = std::vector; -struct SNIConfigParams : public ConfigInfo { +class SNIConfigParams : public ConfigInfo +{ +public: SNIConfigParams() = default; ~SNIConfigParams() override; @@ -106,6 +108,10 @@ struct SNIConfigParams : public ConfigInfo { SNIList sni_action_list; NextHopPropertyList next_hop_list; YamlSNIConfig yaml_sni; + +private: + int set_next_hop_properties(YamlSNIConfig::Item const &item); + int load_certs_if_client_cert_specified(YamlSNIConfig::Item const &item, NextHopItem &nps); }; class SNIConfig diff --git a/iocore/net/unit_tests/test_SSLSNIConfig.cc b/iocore/net/unit_tests/test_SSLSNIConfig.cc index a910bd9f5f1..c1f5672f829 100644 --- a/iocore/net/unit_tests/test_SSLSNIConfig.cc +++ b/iocore/net/unit_tests/test_SSLSNIConfig.cc @@ -61,41 +61,41 @@ TEST_CASE("Test SSLSNIConfig") { auto const &actions{params.get({"oneport.com", std::strlen("oneport.com")}, 433)}; REQUIRE(actions.first); - REQUIRE(actions.first->size() == 1); + REQUIRE(actions.first->size() == 2); } SECTION("The config matches an SNI for allports.com") { auto const &actions{params.get({"allports.com", std::strlen("allports.com")}, 1)}; REQUIRE(actions.first); - REQUIRE(actions.first->size() == 1); + REQUIRE(actions.first->size() == 2); } SECTION("The config matches an SNI for someport.com:1") { auto const &actions{params.get({"someport.com", std::strlen("someport.com")}, 1)}; REQUIRE(actions.first); - REQUIRE(actions.first->size() == 2); + REQUIRE(actions.first->size() == 3); } SECTION("The config matches an SNI for someport.com:433") { auto const &actions{params.get({"someport.com", std::strlen("someport.com")}, 433)}; REQUIRE(actions.first); - REQUIRE(actions.first->size() == 2); + REQUIRE(actions.first->size() == 3); } SECTION("The config matches an SNI for someport:8080") { auto const &actions{params.get({"someport.com", std::strlen("someport.com")}, 8080)}; REQUIRE(actions.first); - REQUIRE(actions.first->size() == 1); + REQUIRE(actions.first->size() == 2); } SECTION("The config matches an SNI for someport:65535") { auto const &actions{params.get({"someport.com", std::strlen("someport.com")}, 65535)}; REQUIRE(actions.first); - REQUIRE(actions.first->size() == 1); + REQUIRE(actions.first->size() == 2); } } diff --git a/proxy/http/PreWarmManager.h b/proxy/http/PreWarmManager.h index adc91080b76..b660194b895 100644 --- a/proxy/http/PreWarmManager.h +++ b/proxy/http/PreWarmManager.h @@ -43,7 +43,7 @@ class PreWarmSM; class PreWarmManager; -struct SNIConfigParams; +class SNIConfigParams; extern ClassAllocator preWarmSMAllocator; extern PreWarmManager prewarmManager; From f103fb78a379fb3335d74ae907a078e7812dd098 Mon Sep 17 00:00:00 2001 From: Josiah VanderZee Date: Mon, 5 Jun 2023 17:23:40 +0000 Subject: [PATCH 13/24] Make inbound_port_range its own field in sni.yaml --- doc/admin-guide/files/sni.yaml.en.rst | 28 +++++++---- iocore/net/YamlSNIConfig.cc | 50 +++++++++---------- iocore/net/YamlSNIConfig.h | 1 + iocore/net/unit_tests/sni_conf_test.yaml | 9 ++-- .../sni_conf_test_bad_port_0-1.yaml | 3 +- .../sni_conf_test_bad_port_1-yowzers2.yaml | 3 +- .../sni_conf_test_bad_port_65535-65536.yaml | 3 +- .../sni_conf_test_bad_port_8080-433.yaml | 3 +- .../sni_conf_test_bad_port_yowzers-1.yaml | 3 +- .../gold_tests/tls/tls_sni_with_port.test.py | 9 +++- 10 files changed, 66 insertions(+), 46 deletions(-) diff --git a/doc/admin-guide/files/sni.yaml.en.rst b/doc/admin-guide/files/sni.yaml.en.rst index c486d937f59..c60aac5542b 100644 --- a/doc/admin-guide/files/sni.yaml.en.rst +++ b/doc/admin-guide/files/sni.yaml.en.rst @@ -27,7 +27,7 @@ Description This file is used to configure aspects of TLS connection handling for both inbound and outbound connections. With the exception of ``host_sni_policy`` (see the description below), the configuration is driven by the SNI values provided by the inbound connection. The -file consists of a set of configuration items, each identified by an SNI value and optionally a port range (``fqdn``). +file consists of a set of configuration items, each identified by an SNI value and optionally a port range (``fqdn``, ``inbound_port_range``). When an inbound TLS connection is made, the SNI value from the TLS negotiation is matched against the items specified by this file and if there is a match, the values specified in that item override the defaults. This is done during the inbound connection processing; some outbound properties @@ -52,26 +52,34 @@ for a more detailed description of HTTP/2 connection coalescing. A similar thing .. _override-host-sni-policy: .. _override-h2-properties: +The following fields make up the key for each item in the configuration file. + ========================= ========= ======================================================================================== Key Direction Meaning ========================= ========= ======================================================================================== -fqdn[:min[-max]] Both Fully Qualified Domain Name. This is the key for the items in this configuration file. - The incoming destination port will also be matched against the port range if present. - The max end of the range defaults to min if it is not specified. - For example: +fqdn Both Fully Qualified Domain Name. - ``example.com`` +inbound_port_range Inbound The port range for the inbound connection in the form ``port`` or + ``min-max``. - would match all requests with an SNI for example.com + For example: - ``example.com:443`` + ``443`` - would only match requests with an SNI for example.com on port 443, and + would match all requests with an SNI for example.com on port 443, and - ``example.com:443-446`` + ``443-446`` would match requests with an SNI for example.com on ports 443 to 446, inclusive. + By default this is all ports. + +========================= ========= ======================================================================================== +The following fields are the directives that determine the behavior of connections matching the key. + +========================= ========= ======================================================================================== +Key Direction Meaning +========================= ========= ======================================================================================== ip_allow Inbound Specify a list of client IP address, subnets, or ranges what are allowed to complete the connection. This list is comma separated. IPv4 and IPv6 addresses can be specified. Here is an example list :: diff --git a/iocore/net/YamlSNIConfig.cc b/iocore/net/YamlSNIConfig.cc index d25a03bf321..54bb56c3f8c 100644 --- a/iocore/net/YamlSNIConfig.cc +++ b/iocore/net/YamlSNIConfig.cc @@ -175,6 +175,7 @@ TsEnumDescriptor TLS_PROTOCOLS_DESCRIPTOR = { }; std::set valid_sni_config_keys = {TS_fqdn, + TS_inbound_port_range, TS_verify_client, TS_verify_client_ca_certs, TS_tunnel_route, @@ -219,35 +220,32 @@ template <> struct convert { } if (node[TS_fqdn]) { - swoc::TextView fqdn{node[TS_fqdn].Scalar()}; - auto port_view{fqdn.take_suffix_at(':')}; - if (port_view && fqdn) { - item.fqdn = fqdn; - auto min{port_view.split_prefix_at('-')}; - if (!min) { - min = port_view; - } - auto const &max{port_view}; - - swoc::TextView parsed_min; - long min_port{swoc::svtoi(min, &parsed_min)}; - swoc::TextView parsed_max; - long max_port{swoc::svtoi(max, &parsed_max)}; - if (parsed_min != min || min_port < 1 || parsed_max != max || max_port > std::numeric_limits::max() || - max_port < min_port) { - std::string out; - swoc::bwprint(out, "bad port range: {}-{}", min, max); - throw YAML::ParserException(node[TS_fqdn].Mark(), out); - } - - item.port_ranges.emplace_back(std::make_pair(min_port, max_port)); - } else { - // no port found; port_view contains entire fqdn and fqdn was emptied - item.fqdn = port_view; - } + item.fqdn = node[TS_fqdn].as(); } else { return false; // servername must be present } + + if (node[TS_inbound_port_range]) { + swoc::TextView port_view{node[TS_inbound_port_range].Scalar()}; + auto min{port_view.split_prefix_at('-')}; + if (!min) { + min = port_view; + } + auto const &max{port_view}; + + swoc::TextView parsed_min; + long min_port{swoc::svtoi(min, &parsed_min)}; + swoc::TextView parsed_max; + long max_port{swoc::svtoi(max, &parsed_max)}; + if (parsed_min != min || min_port < 1 || parsed_max != max || max_port > std::numeric_limits::max() || + max_port < min_port) { + std::string out; + swoc::bwprint(out, "bad port range: {}-{}", min, max); + throw YAML::ParserException(node[TS_fqdn].Mark(), out); + } + + item.port_ranges.emplace_back(std::make_pair(min_port, max_port)); + } if (node[TS_http2]) { item.offer_h2 = node[TS_http2].as(); } diff --git a/iocore/net/YamlSNIConfig.h b/iocore/net/YamlSNIConfig.h index 086089701af..2b18b91060f 100644 --- a/iocore/net/YamlSNIConfig.h +++ b/iocore/net/YamlSNIConfig.h @@ -36,6 +36,7 @@ #define TSDECL(id) constexpr char TS_##id[] = #id TSDECL(fqdn); +TSDECL(inbound_port_range); TSDECL(verify_client); TSDECL(verify_client_ca_certs); TSDECL(tunnel_route); diff --git a/iocore/net/unit_tests/sni_conf_test.yaml b/iocore/net/unit_tests/sni_conf_test.yaml index d09c13e2ed3..82bc3e278df 100644 --- a/iocore/net/unit_tests/sni_conf_test.yaml +++ b/iocore/net/unit_tests/sni_conf_test.yaml @@ -16,7 +16,10 @@ sni: - fqdn: allports.com -- fqdn: someport.com:1-433 +- fqdn: someport.com + inbound_port_range: 1-433 http2: true -- fqdn: someport.com:8080-65535 -- fqdn: oneport.com:433 +- fqdn: someport.com + inbound_port_range: 8080-65535 +- fqdn: oneport.com + inbound_port_range: 433 diff --git a/iocore/net/unit_tests/sni_conf_test_bad_port_0-1.yaml b/iocore/net/unit_tests/sni_conf_test_bad_port_0-1.yaml index 18a28ccfa3d..6cc3b07cb79 100644 --- a/iocore/net/unit_tests/sni_conf_test_bad_port_0-1.yaml +++ b/iocore/net/unit_tests/sni_conf_test_bad_port_0-1.yaml @@ -15,4 +15,5 @@ # limitations under the License. sni: -- fqdn: badport.com:0-1 +- fqdn: badport.com + inbound_port_range: 0-1 diff --git a/iocore/net/unit_tests/sni_conf_test_bad_port_1-yowzers2.yaml b/iocore/net/unit_tests/sni_conf_test_bad_port_1-yowzers2.yaml index 3d9efb01f98..eed8159566d 100644 --- a/iocore/net/unit_tests/sni_conf_test_bad_port_1-yowzers2.yaml +++ b/iocore/net/unit_tests/sni_conf_test_bad_port_1-yowzers2.yaml @@ -15,4 +15,5 @@ # limitations under the License. sni: -- fqdn: badport.com:1-yowzers2 +- fqdn: badport.com + inbound_port_range: 1-yowzers2 diff --git a/iocore/net/unit_tests/sni_conf_test_bad_port_65535-65536.yaml b/iocore/net/unit_tests/sni_conf_test_bad_port_65535-65536.yaml index e4e0d2fa40c..64aef61d966 100644 --- a/iocore/net/unit_tests/sni_conf_test_bad_port_65535-65536.yaml +++ b/iocore/net/unit_tests/sni_conf_test_bad_port_65535-65536.yaml @@ -15,4 +15,5 @@ # limitations under the License. sni: -- fqdn: badport.com:65535-65536 +- fqdn: badport.com + inbound_port_range: 65535-65536 diff --git a/iocore/net/unit_tests/sni_conf_test_bad_port_8080-433.yaml b/iocore/net/unit_tests/sni_conf_test_bad_port_8080-433.yaml index 9517b297ecd..118b46c6b8d 100644 --- a/iocore/net/unit_tests/sni_conf_test_bad_port_8080-433.yaml +++ b/iocore/net/unit_tests/sni_conf_test_bad_port_8080-433.yaml @@ -15,4 +15,5 @@ # limitations under the License. sni: -- fqdn: badport.com:8080-433 +- fqdn: badport.com + inbound_port_range: 8080-433 diff --git a/iocore/net/unit_tests/sni_conf_test_bad_port_yowzers-1.yaml b/iocore/net/unit_tests/sni_conf_test_bad_port_yowzers-1.yaml index 413743e4961..c783cfee3dc 100644 --- a/iocore/net/unit_tests/sni_conf_test_bad_port_yowzers-1.yaml +++ b/iocore/net/unit_tests/sni_conf_test_bad_port_yowzers-1.yaml @@ -15,4 +15,5 @@ # limitations under the License. sni: -- fqdn: notaport.com:yowzers-1 +- fqdn: notaport.com + inbound_port_range: yowzers-1 diff --git a/tests/gold_tests/tls/tls_sni_with_port.test.py b/tests/gold_tests/tls/tls_sni_with_port.test.py index 62f1388feee..68e2709bae6 100644 --- a/tests/gold_tests/tls/tls_sni_with_port.test.py +++ b/tests/gold_tests/tls/tls_sni_with_port.test.py @@ -111,9 +111,11 @@ def _configure_traffic_server(self, tr: "TestRun", server_one: "Process", server ts.Disk.sni_yaml.AddLines([ "sni:", - f"- fqdn: yay.example.com:{self._port_one}-{self._port_one}", + "- fqdn: yay.example.com", + f" inbound_port_range: {self._port_one}-{self._port_one}", f" tunnel_route: localhost:{server_one.Variables.https_port}", - f"- fqdn: yay.example.com:{self._port_two}", + "- fqdn: yay.example.com", + f" inbound_port_range: {self._port_two}", f" tunnel_route: localhost:{server_two.Variables.https_port}" ]) @@ -144,6 +146,9 @@ def test0( ) tr.Processes.Default.ReturnCode = 0 + ts.Disk.diags_log.Content += Testers.ExcludesExpression( + "unsupported key 'inbound_port_range'", "we should not warn about the key" + ) ts.Disk.traffic_out.Content += Testers.IncludesExpression( "not available in the map", "the request should not match an SNI" ) From 1d92b89db1af16fe18cfa94acad09e6897bf2010 Mon Sep 17 00:00:00 2001 From: Josiah VanderZee Date: Thu, 8 Jun 2023 09:03:10 -0500 Subject: [PATCH 14/24] Use bool type to indicate success in SNIConfigParams methods --- iocore/net/SSLSNIConfig.cc | 38 +++++++++++++++++++------------------- iocore/net/SSLSNIConfig.h | 10 +++++----- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/iocore/net/SSLSNIConfig.cc b/iocore/net/SSLSNIConfig.cc index bfb5a7167b3..300399d39bd 100644 --- a/iocore/net/SSLSNIConfig.cc +++ b/iocore/net/SSLSNIConfig.cc @@ -109,7 +109,7 @@ SNIConfigParams::get_property_config(const std::string &servername) const return nps; } -int +bool SNIConfigParams::load_sni_config() { for (auto &item : yaml_sni.items) { @@ -122,30 +122,30 @@ SNIConfigParams::load_sni_config() Debug("ssl", "name: %s", item.fqdn.data()); item.populate_sni_actions(ai.actions); - if (set_next_hop_properties(item) == 1) { - return 1; + if (!set_next_hop_properties(item)) { + return false; } } - return 0; + return true; } -int +bool SNIConfigParams::set_next_hop_properties(YamlSNIConfig::Item const &item) { auto &nps = next_hop_list.emplace_back(); - if (load_certs_if_client_cert_specified(item, nps) == 1) { - return 1; + if (!load_certs_if_client_cert_specified(item, nps)) { + return false; }; nps.set_glob_name(item.fqdn); nps.prop.verify_server_policy = item.verify_server_policy; nps.prop.verify_server_properties = item.verify_server_properties; - return 0; + return true; } -int +bool SNIConfigParams::load_certs_if_client_cert_specified(YamlSNIConfig::Item const &item, NextHopItem &nps) { if (!item.client_cert.empty()) { @@ -158,11 +158,11 @@ SNIConfigParams::load_certs_if_client_cert_specified(YamlSNIConfig::Item const & auto ctx = params->getCTX(nps.prop.client_cert_file, nps.prop.client_key_file, params->clientCACertFilename, params->clientCACertPath); if (ctx.get() == nullptr) { - return 1; + return false; } } - return 0; + return true; } std::pair @@ -208,14 +208,14 @@ SNIConfigParams::get(std::string_view servername, uint16_t dest_incoming_port) c return {nullptr, {}}; } -int +bool SNIConfigParams::initialize() { std::string sni_filename = RecConfigReadConfigPath("proxy.config.ssl.servername.filename"); return initialize(sni_filename); } -int +bool SNIConfigParams::initialize(std::string const &sni_filename) { Note("%s loading ...", sni_filename.c_str()); @@ -225,7 +225,7 @@ SNIConfigParams::initialize(std::string const &sni_filename) Note("%s failed to load", sni_filename.c_str()); Warning("Loading SNI configuration - filename: %s doesn't exist", sni_filename.c_str()); - return 0; + return true; } YamlSNIConfig yaml_sni_tmp; @@ -238,7 +238,7 @@ SNIConfigParams::initialize(std::string const &sni_filename) } else { Error("%s failed to load: %s", sni_filename.c_str(), errMsg.str().c_str()); } - return 1; + return false; } yaml_sni = std::move(yaml_sni_tmp); @@ -270,8 +270,8 @@ SNIConfig::reconfigure() Debug("ssl", "Reload SNI file"); SNIConfigParams *params = new SNIConfigParams; - int retStatus = params->initialize(); - if (!retStatus) { + bool retStatus = params->initialize(); + if (retStatus) { _configid = configProcessor.set(_configid, params); prewarmManager.reconfigure(); } else { @@ -279,13 +279,13 @@ SNIConfig::reconfigure() } std::string sni_filename = RecConfigReadConfigPath("proxy.config.ssl.servername.filename"); - if (!retStatus || TSSystemState::is_initializing()) { + if (retStatus || TSSystemState::is_initializing()) { Note("%s finished loading", sni_filename.c_str()); } else { Error("%s failed to load", sni_filename.c_str()); } - return !retStatus; + return retStatus ? 1 : 0; } SNIConfigParams * diff --git a/iocore/net/SSLSNIConfig.h b/iocore/net/SSLSNIConfig.h index b21c0879381..3f5b40fdb99 100644 --- a/iocore/net/SSLSNIConfig.h +++ b/iocore/net/SSLSNIConfig.h @@ -97,12 +97,12 @@ class SNIConfigParams : public ConfigInfo ~SNIConfigParams() override; const NextHopProperty *get_property_config(const std::string &servername) const; - int initialize(); - int initialize(const std::string &sni_filename); + bool initialize(); + bool initialize(const std::string &sni_filename); /** Walk sni.yaml config and populate sni_action_list @return 0 for success, 1 is failure */ - int load_sni_config(); + bool load_sni_config(); std::pair get(std::string_view servername, uint16_t dest_incoming_port) const; SNIList sni_action_list; @@ -110,8 +110,8 @@ class SNIConfigParams : public ConfigInfo YamlSNIConfig yaml_sni; private: - int set_next_hop_properties(YamlSNIConfig::Item const &item); - int load_certs_if_client_cert_specified(YamlSNIConfig::Item const &item, NextHopItem &nps); + bool set_next_hop_properties(YamlSNIConfig::Item const &item); + bool load_certs_if_client_cert_specified(YamlSNIConfig::Item const &item, NextHopItem &nps); }; class SNIConfig From 39b6b482434b8b5743aaf68659497d91c0890aab Mon Sep 17 00:00:00 2001 From: Josiah VanderZee Date: Thu, 8 Jun 2023 09:23:44 -0500 Subject: [PATCH 15/24] Use ts::bw_dbg instead of out in YamlSNIConfig --- iocore/net/YamlSNIConfig.cc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/iocore/net/YamlSNIConfig.cc b/iocore/net/YamlSNIConfig.cc index 54bb56c3f8c..edcd363db49 100644 --- a/iocore/net/YamlSNIConfig.cc +++ b/iocore/net/YamlSNIConfig.cc @@ -39,6 +39,7 @@ #include "P_SNIActionPerformer.h" +#include "tscore/BufferWriterForward.h" #include "tscore/Diags.h" #include "tscore/EnumDescriptor.h" #include "tscore/Errata.h" @@ -239,9 +240,7 @@ template <> struct convert { long max_port{swoc::svtoi(max, &parsed_max)}; if (parsed_min != min || min_port < 1 || parsed_max != max || max_port > std::numeric_limits::max() || max_port < min_port) { - std::string out; - swoc::bwprint(out, "bad port range: {}-{}", min, max); - throw YAML::ParserException(node[TS_fqdn].Mark(), out); + throw YAML::ParserException(node[TS_fqdn].Mark(), swoc::bwprint(ts::bw_dbg, "bad port range: {}-{}", min, max)); } item.port_ranges.emplace_back(std::make_pair(min_port, max_port)); From 7f32381cf90acbc6a03c3299000061a412f43fbf Mon Sep 17 00:00:00 2001 From: Josiah VanderZee Date: Thu, 8 Jun 2023 10:01:26 -0500 Subject: [PATCH 16/24] Extract check_port_range in test_YamlSNIConfig --- iocore/net/unit_tests/test_YamlSNIConfig.cc | 28 ++++++++++----------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/iocore/net/unit_tests/test_YamlSNIConfig.cc b/iocore/net/unit_tests/test_YamlSNIConfig.cc index f09ea1e2ab2..b4d264e80d7 100644 --- a/iocore/net/unit_tests/test_YamlSNIConfig.cc +++ b/iocore/net/unit_tests/test_YamlSNIConfig.cc @@ -33,10 +33,20 @@ #include #include "swoc/bwf_base.h" +#include #include "catch.hpp" #include "YamlSNIConfig.h" +static void +check_port_range(const YamlSNIConfig::Item &item, in_port_t min_expected, in_port_t max_expected) +{ + REQUIRE(item.port_ranges.size() == 1); + auto const [min, max]{item.port_ranges[0]}; + CHECK(min == min_expected); + CHECK(max == max_expected); +} + TEST_CASE("YamlSNIConfig sets port ranges appropriately") { YamlSNIConfig conf{}; @@ -59,27 +69,15 @@ TEST_CASE("YamlSNIConfig sets port ranges appropriately") { SECTION("Ports 1-433.") { - auto const item{conf.items[1]}; - REQUIRE(item.port_ranges.size() == 1); - auto const [min, max]{item.port_ranges[0]}; - CHECK(min == 1); - CHECK(max == 433); + check_port_range(conf.items[1], 1, 433); } SECTION("Ports 8080-65535.") { - auto const &item{conf.items[2]}; - REQUIRE(item.port_ranges.size() == 1); - auto const [min, max]{item.port_ranges[0]}; - CHECK(min == 8080); - CHECK(max == 65535); + check_port_range(conf.items[2], 8080, 65535); } SECTION("Port 433.") { - auto const &item{conf.items[3]}; - REQUIRE(item.port_ranges.size() == 1); - auto const [min, max]{item.port_ranges[0]}; - CHECK(min == 433); - CHECK(max == 433); + check_port_range(conf.items[3], 433, 433); } } From 0cb9c738ff42a59e465215e3d861e4371b6c86d8 Mon Sep 17 00:00:00 2001 From: Josiah VanderZee Date: Thu, 8 Jun 2023 10:33:32 -0500 Subject: [PATCH 17/24] Change vector of pairs to DiscreteRange --- iocore/net/SSLSNIConfig.cc | 5 +---- iocore/net/SSLSNIConfig.h | 6 ++++-- iocore/net/YamlSNIConfig.cc | 2 +- iocore/net/YamlSNIConfig.h | 10 +++++++++- iocore/net/unit_tests/test_YamlSNIConfig.cc | 17 +++++++---------- 5 files changed, 22 insertions(+), 18 deletions(-) diff --git a/iocore/net/SSLSNIConfig.cc b/iocore/net/SSLSNIConfig.cc index 300399d39bd..3cbf2faee44 100644 --- a/iocore/net/SSLSNIConfig.cc +++ b/iocore/net/SSLSNIConfig.cc @@ -115,10 +115,7 @@ SNIConfigParams::load_sni_config() for (auto &item : yaml_sni.items) { auto &ai = sni_action_list.emplace_back(); ai.set_glob_name(item.fqdn); - if (!item.port_ranges.empty()) { - auto const [min, max]{item.port_ranges[0]}; - ai.ports = {static_cast(min), static_cast(max)}; - } + ai.ports = item.port_range; Debug("ssl", "name: %s", item.fqdn.data()); item.populate_sni_actions(ai.actions); diff --git a/iocore/net/SSLSNIConfig.h b/iocore/net/SSLSNIConfig.h index 3f5b40fdb99..9ecab7c47d8 100644 --- a/iocore/net/SSLSNIConfig.h +++ b/iocore/net/SSLSNIConfig.h @@ -38,6 +38,8 @@ #include #include +#include + #include "ConfigProcessor.h" #include "SNIActionPerformer.h" #include "YamlSNIConfig.h" @@ -72,8 +74,8 @@ struct NamedElement { void set_glob_name(std::string name); void set_regex_name(const std::string ®ex_name); - inline static constexpr uint16_t MAX_PORT_VALUE{std::numeric_limits::max()}; - using port_range_t = swoc::DiscreteRange; + inline static constexpr in_port_t MAX_PORT_VALUE{std::numeric_limits::max()}; + using port_range_t = swoc::DiscreteRange; port_range_t ports{1, MAX_PORT_VALUE}; std::unique_ptr match; diff --git a/iocore/net/YamlSNIConfig.cc b/iocore/net/YamlSNIConfig.cc index edcd363db49..7b611be2dc4 100644 --- a/iocore/net/YamlSNIConfig.cc +++ b/iocore/net/YamlSNIConfig.cc @@ -243,7 +243,7 @@ template <> struct convert { throw YAML::ParserException(node[TS_fqdn].Mark(), swoc::bwprint(ts::bw_dbg, "bad port range: {}-{}", min, max)); } - item.port_ranges.emplace_back(std::make_pair(min_port, max_port)); + item.port_range = YamlSNIConfig::Item::port_range_t{static_cast(min_port), static_cast(max_port)}; } if (node[TS_http2]) { item.offer_h2 = node[TS_http2].as(); diff --git a/iocore/net/YamlSNIConfig.h b/iocore/net/YamlSNIConfig.h index 2b18b91060f..21eb8d4cdfb 100644 --- a/iocore/net/YamlSNIConfig.h +++ b/iocore/net/YamlSNIConfig.h @@ -27,8 +27,12 @@ #include #include #include +#include #include +#include "swoc/DiscreteRange.h" +#include + #include "SNIActionPerformer.h" #include "SSLTypes.h" @@ -77,7 +81,11 @@ struct YamlSNIConfig { struct Item { std::string fqdn; - std::vector> port_ranges; + + inline static constexpr in_port_t MAX_PORT_VALUE{std::numeric_limits::max()}; + using port_range_t = swoc::DiscreteRange; + port_range_t port_range{1, MAX_PORT_VALUE}; + std::optional offer_h2; // Has no value by default, so do not initialize! std::optional offer_quic; // Has no value by default, so do not initialize! uint8_t verify_client_level = 255; diff --git a/iocore/net/unit_tests/test_YamlSNIConfig.cc b/iocore/net/unit_tests/test_YamlSNIConfig.cc index b4d264e80d7..4d9b878203c 100644 --- a/iocore/net/unit_tests/test_YamlSNIConfig.cc +++ b/iocore/net/unit_tests/test_YamlSNIConfig.cc @@ -41,10 +41,8 @@ static void check_port_range(const YamlSNIConfig::Item &item, in_port_t min_expected, in_port_t max_expected) { - REQUIRE(item.port_ranges.size() == 1); - auto const [min, max]{item.port_ranges[0]}; - CHECK(min == min_expected); - CHECK(max == max_expected); + CHECK(item.port_range.min() == min_expected); + CHECK(item.port_range.max() == max_expected); } TEST_CASE("YamlSNIConfig sets port ranges appropriately") @@ -59,13 +57,12 @@ TEST_CASE("YamlSNIConfig sets port ranges appropriately") REQUIRE(zret.isOK()); REQUIRE(conf.items.size() == 4); - SECTION("If no ports were specified, ports should be empty.") + SECTION("If no ports were specified, port range should contain all ports.") { - auto const &item{conf.items[0]}; - CHECK(item.port_ranges.size() == 0); + check_port_range(conf.items[0], 1, 65535); } - SECTION("If one port range was specified, ports should contain that port range.") + SECTION("If one port range was specified, port range should match.") { SECTION("Ports 1-433.") { @@ -81,13 +78,13 @@ TEST_CASE("YamlSNIConfig sets port ranges appropriately") } } - SECTION("If a port was specified, it should not interfere with the fqdn.") + SECTION("If a port range was specified, it should not interfere with the fqdn.") { auto const &item{conf.items[1]}; CHECK(item.fqdn == "someport.com"); } - SECTION("If no port was specified, it should not interfere with the fqdn.") + SECTION("If no port range was specified, it should not interfere with the fqdn.") { auto const &item{conf.items[0]}; CHECK(item.fqdn == "allports.com"); From 0f75eef6d495b53f609ead52a01d2e10959ee24f Mon Sep 17 00:00:00 2001 From: Josiah VanderZee Date: Thu, 8 Jun 2023 10:51:55 -0500 Subject: [PATCH 18/24] Update test_net in src/tests/CMakeLists.txt --- src/tests/CMakeLists.txt | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 8504b333b3f..cb076ddb460 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -100,7 +100,22 @@ add_cache_test(Update_S_to_L ${CMAKE_SOURCE_DIR}/iocore/cache/test/test_Update_S add_executable(test_AIO ${CMAKE_SOURCE_DIR}/iocore/aio/test_AIO.cc) add_test(NAME test_AIO COMMAND $ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/iocore/aio) -add_net_test(test_net ${CMAKE_SOURCE_DIR}/iocore/net/unit_tests/test_ProxyProtocol.cc) +add_net_test(test_net + "${CMAKE_SOURCE_DIR}/iocore/net/unit_tests/test_ProxyProtocol.cc" + "${CMAKE_SOURCE_DIR}/iocore/net/unit_tests/test_SSLSNIConfig.cc" + "${CMAKE_SOURCE_DIR}/iocore/net/unit_tests/test_YamlSNIConfig.cc" + "${CMAKE_SOURCE_DIR}/iocore/net/unit_tests/unit_test_main.cc" +) +set(LIBINKNET_UNIT_TEST_DIR "${CMAKE_SOURCE_DIR}/iocore/net/unit_tests") +target_compile_definitions(test_net + PRIVATE + LIBINKNET_UNIT_TEST_DIR=${LIBINKNET_UNIT_TEST_DIR} +) +target_link_libraries(test_net + PRIVATE + hdrs + proxy +) add_stubbed_test(EventSystem ${CMAKE_SOURCE_DIR}/iocore/eventsystem/unit_tests/test_EventSystem.cc) add_stubbed_test(IOBuffer ${CMAKE_SOURCE_DIR}/iocore/eventsystem/unit_tests/test_IOBuffer.cc) From 38a64de395f9d1506491f32f3e1e0e0edd61df26 Mon Sep 17 00:00:00 2001 From: Josiah VanderZee Date: Fri, 9 Jun 2023 17:15:02 -0500 Subject: [PATCH 19/24] Replace uint16_t with in_port_t --- iocore/net/SSLSNIConfig.cc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/iocore/net/SSLSNIConfig.cc b/iocore/net/SSLSNIConfig.cc index 3cbf2faee44..e2224a820fd 100644 --- a/iocore/net/SSLSNIConfig.cc +++ b/iocore/net/SSLSNIConfig.cc @@ -41,7 +41,8 @@ #include "tscpp/util/TextView.h" -#include +#include + #include #include #include @@ -163,7 +164,7 @@ SNIConfigParams::load_certs_if_client_cert_specified(YamlSNIConfig::Item const & } std::pair -SNIConfigParams::get(std::string_view servername, uint16_t dest_incoming_port) const +SNIConfigParams::get(std::string_view servername, in_port_t dest_incoming_port) const { int ovector[OVECSIZE]; @@ -302,7 +303,7 @@ SNIConfig::release(SNIConfigParams *params) // setting proxy.config.http.host_sni_policy and is possibly overridden if the sni policy // contains a host_sni_policy entry bool -SNIConfig::test_client_action(const char *servername, uint16_t dest_incoming_port, const IpEndpoint &ep, int &host_sni_policy) +SNIConfig::test_client_action(const char *servername, in_port_t dest_incoming_port, const IpEndpoint &ep, int &host_sni_policy) { bool retval = false; SNIConfig::scoped_config params; From 57bd86f4fcf813e059142187bfa188d09ce1e538 Mon Sep 17 00:00:00 2001 From: Josiah VanderZee Date: Mon, 12 Jun 2023 05:52:57 -0500 Subject: [PATCH 20/24] Move port_range_t to tscpp/utils/ts_ip.h --- include/tscpp/util/ts_ip.h | 5 +++++ iocore/net/SSLSNIConfig.h | 9 ++------- iocore/net/YamlSNIConfig.cc | 7 +++++-- iocore/net/YamlSNIConfig.h | 10 +++------- 4 files changed, 15 insertions(+), 16 deletions(-) diff --git a/include/tscpp/util/ts_ip.h b/include/tscpp/util/ts_ip.h index 9d2c276f00b..40f577bcccb 100644 --- a/include/tscpp/util/ts_ip.h +++ b/include/tscpp/util/ts_ip.h @@ -22,12 +22,17 @@ #pragma once +#include #include +#include "swoc/DiscreteRange.h" #include "swoc/swoc_ip.h" namespace ts { +inline constexpr in_port_t MAX_PORT_VALUE{std::numeric_limits::max()}; +using port_range_t = swoc::DiscreteRange; + /// Pair of addresses, each optional. /// Used in situations where both an IPv4 and IPv6 may be needed. class IPAddrPair diff --git a/iocore/net/SSLSNIConfig.h b/iocore/net/SSLSNIConfig.h index 9ecab7c47d8..46890ae351c 100644 --- a/iocore/net/SSLSNIConfig.h +++ b/iocore/net/SSLSNIConfig.h @@ -30,15 +30,12 @@ ****************************************************************************/ #pragma once -#include "swoc/DiscreteRange.h" - #include #include #include -#include #include -#include +#include "tscpp/util/ts_ip.h" #include "ConfigProcessor.h" #include "SNIActionPerformer.h" @@ -74,9 +71,7 @@ struct NamedElement { void set_glob_name(std::string name); void set_regex_name(const std::string ®ex_name); - inline static constexpr in_port_t MAX_PORT_VALUE{std::numeric_limits::max()}; - using port_range_t = swoc::DiscreteRange; - port_range_t ports{1, MAX_PORT_VALUE}; + ts::port_range_t ports{1, ts::MAX_PORT_VALUE}; std::unique_ptr match; }; diff --git a/iocore/net/YamlSNIConfig.cc b/iocore/net/YamlSNIConfig.cc index 7b611be2dc4..93765746e5e 100644 --- a/iocore/net/YamlSNIConfig.cc +++ b/iocore/net/YamlSNIConfig.cc @@ -33,12 +33,15 @@ #include #include +#include #include "swoc/TextView.h" #include "swoc/bwf_base.h" #include "P_SNIActionPerformer.h" +#include "tscpp/util/ts_ip.h" + #include "tscore/BufferWriterForward.h" #include "tscore/Diags.h" #include "tscore/EnumDescriptor.h" @@ -238,12 +241,12 @@ template <> struct convert { long min_port{swoc::svtoi(min, &parsed_min)}; swoc::TextView parsed_max; long max_port{swoc::svtoi(max, &parsed_max)}; - if (parsed_min != min || min_port < 1 || parsed_max != max || max_port > std::numeric_limits::max() || + if (parsed_min != min || min_port < 1 || parsed_max != max || max_port > std::numeric_limits::max() || max_port < min_port) { throw YAML::ParserException(node[TS_fqdn].Mark(), swoc::bwprint(ts::bw_dbg, "bad port range: {}-{}", min, max)); } - item.port_range = YamlSNIConfig::Item::port_range_t{static_cast(min_port), static_cast(max_port)}; + item.port_range = ts::port_range_t{static_cast(min_port), static_cast(max_port)}; } if (node[TS_http2]) { item.offer_h2 = node[TS_http2].as(); diff --git a/iocore/net/YamlSNIConfig.h b/iocore/net/YamlSNIConfig.h index 21eb8d4cdfb..73839b26e8e 100644 --- a/iocore/net/YamlSNIConfig.h +++ b/iocore/net/YamlSNIConfig.h @@ -27,15 +27,13 @@ #include #include #include -#include #include -#include "swoc/DiscreteRange.h" -#include - #include "SNIActionPerformer.h" #include "SSLTypes.h" +#include "tscpp/util/ts_ip.h" + #include "tscore/Errata.h" #define TSDECL(id) constexpr char TS_##id[] = #id @@ -82,9 +80,7 @@ struct YamlSNIConfig { struct Item { std::string fqdn; - inline static constexpr in_port_t MAX_PORT_VALUE{std::numeric_limits::max()}; - using port_range_t = swoc::DiscreteRange; - port_range_t port_range{1, MAX_PORT_VALUE}; + ts::port_range_t port_range{1, ts::MAX_PORT_VALUE}; std::optional offer_h2; // Has no value by default, so do not initialize! std::optional offer_quic; // Has no value by default, so do not initialize! From 2c9d56a95dd62cb0330603baa6e12f2a7c871ed5 Mon Sep 17 00:00:00 2001 From: Josiah VanderZee Date: Fri, 23 Jun 2023 06:06:11 -0500 Subject: [PATCH 21/24] Fix compile error --- iocore/net/YamlSNIConfig.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iocore/net/YamlSNIConfig.cc b/iocore/net/YamlSNIConfig.cc index 93765746e5e..6d5c1cf0669 100644 --- a/iocore/net/YamlSNIConfig.cc +++ b/iocore/net/YamlSNIConfig.cc @@ -135,7 +135,7 @@ YamlSNIConfig::Item::populate_sni_actions(action_vector_t &actions) if (offer_h2.has_value()) { actions.push_back(std::make_unique(offer_h2.value())); } - if (item.offer_quic.has_value()) { + if (offer_quic.has_value()) { actions.push_back(std::make_unique(offer_quic.value())); } if (verify_client_level != 255) { From 4f8130b95ca50f39eea5ebfffcdf7ed3822b8c00 Mon Sep 17 00:00:00 2001 From: Josiah VanderZee Date: Fri, 23 Jun 2023 07:01:10 -0500 Subject: [PATCH 22/24] Add UnixNetVConnectionWithSNI class This groups together all the classes with SNI support under a common class, so that we can downcast TLSSNISupport objects to that base class. --- iocore/net/CMakeLists.txt | 4 ++++ iocore/net/P_QUICNetVConnection_quiche.h | 3 +-- iocore/net/P_SSLNetVConnection.h | 3 +-- iocore/net/P_UnixNetVConnection.h | 4 ++++ iocore/net/TLSSNISupport.cc | 4 ++-- iocore/net/unit_tests/CMakeLists.txt | 22 ++++++++++++++++++++++ 6 files changed, 34 insertions(+), 6 deletions(-) create mode 100644 iocore/net/unit_tests/CMakeLists.txt diff --git a/iocore/net/CMakeLists.txt b/iocore/net/CMakeLists.txt index 7aed806c984..4f1e9f05781 100644 --- a/iocore/net/CMakeLists.txt +++ b/iocore/net/CMakeLists.txt @@ -117,6 +117,10 @@ if(TS_USE_POSIX_CAP) target_link_libraries(inknet PUBLIC cap::cap) endif() +if(BUILD_TESTING) + add_subdirectory(unit_tests) +endif() + # Fails to link because of circular dep with proxy (ParentSelection) # add_executable(test_net unit_tests/test_ProxyProtocol.cc) # target_link_libraries(test_net records_p inknet inkevent tscore yaml-cpp libswoc) diff --git a/iocore/net/P_QUICNetVConnection_quiche.h b/iocore/net/P_QUICNetVConnection_quiche.h index d20e9188b75..de099f06cb3 100644 --- a/iocore/net/P_QUICNetVConnection_quiche.h +++ b/iocore/net/P_QUICNetVConnection_quiche.h @@ -56,11 +56,10 @@ class QUICPacketHandler; class QUICResetTokenTable; class QUICConnectionTable; -class QUICNetVConnection : public UnixNetVConnection, +class QUICNetVConnection : public UnixNetVConnectionWithSNI, public QUICConnection, public RefCountObj, public ALPNSupport, - public TLSSNISupport, public TLSSessionResumptionSupport, public TLSCertSwitchSupport, public TLSBasicSupport diff --git a/iocore/net/P_SSLNetVConnection.h b/iocore/net/P_SSLNetVConnection.h index cb41b9a2fee..b811ad820be 100644 --- a/iocore/net/P_SSLNetVConnection.h +++ b/iocore/net/P_SSLNetVConnection.h @@ -98,10 +98,9 @@ enum SSLHandshakeStatus { SSL_HANDSHAKE_ONGOING, SSL_HANDSHAKE_DONE, SSL_HANDSHA // A VConnection for a network socket. // ////////////////////////////////////////////////////////////////// -class SSLNetVConnection : public UnixNetVConnection, +class SSLNetVConnection : public UnixNetVConnectionWithSNI, public ALPNSupport, public TLSSessionResumptionSupport, - public TLSSNISupport, public TLSEarlyDataSupport, public TLSTunnelSupport, public TLSCertSwitchSupport, diff --git a/iocore/net/P_UnixNetVConnection.h b/iocore/net/P_UnixNetVConnection.h index fbc9747a855..50c3e7caa95 100644 --- a/iocore/net/P_UnixNetVConnection.h +++ b/iocore/net/P_UnixNetVConnection.h @@ -336,3 +336,7 @@ UnixNetVConnection::get_action() const void write_to_net(NetHandler *nh, UnixNetVConnection *vc, EThread *thread); void write_to_net_io(NetHandler *nh, UnixNetVConnection *vc, EThread *thread); void net_activity(UnixNetVConnection *vc, EThread *thread); + +class UnixNetVConnectionWithSNI : public UnixNetVConnection, public TLSSNISupport +{ +}; diff --git a/iocore/net/TLSSNISupport.cc b/iocore/net/TLSSNISupport.cc index 99852da4fd3..5f926a1d21a 100644 --- a/iocore/net/TLSSNISupport.cc +++ b/iocore/net/TLSSNISupport.cc @@ -21,7 +21,7 @@ limitations under the License. */ #include "P_SSLNextProtocolAccept.h" -#include "P_SSLNetVConnection.h" +#include "P_UnixNetVConnection.h" #include "SSLSNIConfig.h" #include "TLSSNISupport.h" #include "tscore/ink_assert.h" @@ -68,7 +68,7 @@ TLSSNISupport::perform_sni_action() SNIConfig::scoped_config params; // should always work in this context of SSL action callbacks - SSLNetVConnection *ssl_vc{dynamic_cast(this)}; + UnixNetVConnectionWithSNI *ssl_vc{dynamic_cast(this)}; ink_assert(ssl_vc != nullptr); auto const port{ssl_vc->get_local_port()}; if (auto const &actions = params->get({servername, std::strlen(servername)}, port); !actions.first) { diff --git a/iocore/net/unit_tests/CMakeLists.txt b/iocore/net/unit_tests/CMakeLists.txt new file mode 100644 index 00000000000..694c7ad8aa1 --- /dev/null +++ b/iocore/net/unit_tests/CMakeLists.txt @@ -0,0 +1,22 @@ +add_executable(test_inknet + ../libinknet_stub.cc + unit_test_main.cc + test_ProxyProtocol.cc + test_SSLSNIConfig.cc + test_YamlSNIConfig.cc +) + +target_link_libraries(test_inknet + PRIVATE + catch2::catch2 + ts::inknet # transitive + ts::hdrs # transitive + ts::proxy # transitive +) + +target_compile_definitions(test_inknet + PRIVATE + -DLIBINKNET_UNIT_TEST_DIR=${CMAKE_CURRENT_SOURCE_DIR} +) + +add_test(NAME test_inknet COMMAND $) From 3dc7500655c27a57e32ae6934947dc9cf57b390b Mon Sep 17 00:00:00 2001 From: Josiah VanderZee Date: Mon, 26 Jun 2023 05:31:44 -0500 Subject: [PATCH 23/24] Revert "Add UnixNetVConnectionWithSNI class" This reverts commit 4f8130b95ca50f39eea5ebfffcdf7ed3822b8c00. --- iocore/net/CMakeLists.txt | 4 ---- iocore/net/P_QUICNetVConnection_quiche.h | 3 ++- iocore/net/P_SSLNetVConnection.h | 3 ++- iocore/net/P_UnixNetVConnection.h | 4 ---- iocore/net/TLSSNISupport.cc | 4 ++-- iocore/net/unit_tests/CMakeLists.txt | 22 ---------------------- 6 files changed, 6 insertions(+), 34 deletions(-) delete mode 100644 iocore/net/unit_tests/CMakeLists.txt diff --git a/iocore/net/CMakeLists.txt b/iocore/net/CMakeLists.txt index 4f1e9f05781..7aed806c984 100644 --- a/iocore/net/CMakeLists.txt +++ b/iocore/net/CMakeLists.txt @@ -117,10 +117,6 @@ if(TS_USE_POSIX_CAP) target_link_libraries(inknet PUBLIC cap::cap) endif() -if(BUILD_TESTING) - add_subdirectory(unit_tests) -endif() - # Fails to link because of circular dep with proxy (ParentSelection) # add_executable(test_net unit_tests/test_ProxyProtocol.cc) # target_link_libraries(test_net records_p inknet inkevent tscore yaml-cpp libswoc) diff --git a/iocore/net/P_QUICNetVConnection_quiche.h b/iocore/net/P_QUICNetVConnection_quiche.h index de099f06cb3..d20e9188b75 100644 --- a/iocore/net/P_QUICNetVConnection_quiche.h +++ b/iocore/net/P_QUICNetVConnection_quiche.h @@ -56,10 +56,11 @@ class QUICPacketHandler; class QUICResetTokenTable; class QUICConnectionTable; -class QUICNetVConnection : public UnixNetVConnectionWithSNI, +class QUICNetVConnection : public UnixNetVConnection, public QUICConnection, public RefCountObj, public ALPNSupport, + public TLSSNISupport, public TLSSessionResumptionSupport, public TLSCertSwitchSupport, public TLSBasicSupport diff --git a/iocore/net/P_SSLNetVConnection.h b/iocore/net/P_SSLNetVConnection.h index b811ad820be..cb41b9a2fee 100644 --- a/iocore/net/P_SSLNetVConnection.h +++ b/iocore/net/P_SSLNetVConnection.h @@ -98,9 +98,10 @@ enum SSLHandshakeStatus { SSL_HANDSHAKE_ONGOING, SSL_HANDSHAKE_DONE, SSL_HANDSHA // A VConnection for a network socket. // ////////////////////////////////////////////////////////////////// -class SSLNetVConnection : public UnixNetVConnectionWithSNI, +class SSLNetVConnection : public UnixNetVConnection, public ALPNSupport, public TLSSessionResumptionSupport, + public TLSSNISupport, public TLSEarlyDataSupport, public TLSTunnelSupport, public TLSCertSwitchSupport, diff --git a/iocore/net/P_UnixNetVConnection.h b/iocore/net/P_UnixNetVConnection.h index 50c3e7caa95..fbc9747a855 100644 --- a/iocore/net/P_UnixNetVConnection.h +++ b/iocore/net/P_UnixNetVConnection.h @@ -336,7 +336,3 @@ UnixNetVConnection::get_action() const void write_to_net(NetHandler *nh, UnixNetVConnection *vc, EThread *thread); void write_to_net_io(NetHandler *nh, UnixNetVConnection *vc, EThread *thread); void net_activity(UnixNetVConnection *vc, EThread *thread); - -class UnixNetVConnectionWithSNI : public UnixNetVConnection, public TLSSNISupport -{ -}; diff --git a/iocore/net/TLSSNISupport.cc b/iocore/net/TLSSNISupport.cc index 5f926a1d21a..99852da4fd3 100644 --- a/iocore/net/TLSSNISupport.cc +++ b/iocore/net/TLSSNISupport.cc @@ -21,7 +21,7 @@ limitations under the License. */ #include "P_SSLNextProtocolAccept.h" -#include "P_UnixNetVConnection.h" +#include "P_SSLNetVConnection.h" #include "SSLSNIConfig.h" #include "TLSSNISupport.h" #include "tscore/ink_assert.h" @@ -68,7 +68,7 @@ TLSSNISupport::perform_sni_action() SNIConfig::scoped_config params; // should always work in this context of SSL action callbacks - UnixNetVConnectionWithSNI *ssl_vc{dynamic_cast(this)}; + SSLNetVConnection *ssl_vc{dynamic_cast(this)}; ink_assert(ssl_vc != nullptr); auto const port{ssl_vc->get_local_port()}; if (auto const &actions = params->get({servername, std::strlen(servername)}, port); !actions.first) { diff --git a/iocore/net/unit_tests/CMakeLists.txt b/iocore/net/unit_tests/CMakeLists.txt deleted file mode 100644 index 694c7ad8aa1..00000000000 --- a/iocore/net/unit_tests/CMakeLists.txt +++ /dev/null @@ -1,22 +0,0 @@ -add_executable(test_inknet - ../libinknet_stub.cc - unit_test_main.cc - test_ProxyProtocol.cc - test_SSLSNIConfig.cc - test_YamlSNIConfig.cc -) - -target_link_libraries(test_inknet - PRIVATE - catch2::catch2 - ts::inknet # transitive - ts::hdrs # transitive - ts::proxy # transitive -) - -target_compile_definitions(test_inknet - PRIVATE - -DLIBINKNET_UNIT_TEST_DIR=${CMAKE_CURRENT_SOURCE_DIR} -) - -add_test(NAME test_inknet COMMAND $) From 76dcee72de9ec8570134136a7581d1b385c55dc2 Mon Sep 17 00:00:00 2001 From: Josiah VanderZee Date: Mon, 26 Jun 2023 05:37:18 -0500 Subject: [PATCH 24/24] Add virtual _get_local_port to TLSSNISupport This is to allow TLSSNISupport to get the local inbound port for matching SNIs. --- iocore/net/P_QUICNetVConnection_quiche.h | 3 +++ iocore/net/P_SSLNetVConnection.h | 20 +++++++++++--------- iocore/net/QUICNetVConnection_quiche.cc | 8 ++++++++ iocore/net/SSLNetVConnection.cc | 8 ++++++++ iocore/net/TLSSNISupport.cc | 6 +----- iocore/net/TLSSNISupport.h | 11 ++++++++--- 6 files changed, 39 insertions(+), 17 deletions(-) diff --git a/iocore/net/P_QUICNetVConnection_quiche.h b/iocore/net/P_QUICNetVConnection_quiche.h index d20e9188b75..a8832401bd7 100644 --- a/iocore/net/P_QUICNetVConnection_quiche.h +++ b/iocore/net/P_QUICNetVConnection_quiche.h @@ -50,6 +50,8 @@ #include "quic/QUICContext.h" #include "quic/QUICStreamManager.h" #include "quic/QUICStreamManager_quiche.h" + +#include #include class QUICPacketHandler; @@ -155,6 +157,7 @@ class QUICNetVConnection : public UnixNetVConnection, // TLSSNISupport void _fire_ssl_servername_event() override; + in_port_t _get_local_port() override; // TLSSessionResumptionSupport const IpEndpoint &_getLocalEndpoint() override; diff --git a/iocore/net/P_SSLNetVConnection.h b/iocore/net/P_SSLNetVConnection.h index cb41b9a2fee..9c138da4a05 100644 --- a/iocore/net/P_SSLNetVConnection.h +++ b/iocore/net/P_SSLNetVConnection.h @@ -31,17 +31,8 @@ ****************************************************************************/ #pragma once -#include - #include "tscore/ink_platform.h" #include "ts/apidefs.h" -#include -#include -#include - -#include -#include -#include #include "P_EventSystem.h" #include "P_UnixNetVConnection.h" @@ -56,6 +47,15 @@ #include "P_SSLUtils.h" #include "P_SSLConfig.h" +#include +#include +#include +#include + +#include +#include +#include + // These are included here because older OpenSSL libraries don't have them. // Don't copy these defines, or use their values directly, they are merely // here to avoid compiler errors. @@ -408,7 +408,9 @@ class SSLNetVConnection : public UnixNetVConnection, return local_addr; } + // TLSSNISupport void _fire_ssl_servername_event() override; + in_port_t _get_local_port() override; bool _isTryingRenegotiation() const override; shared_SSL_CTX _lookupContextByName(const std::string &servername, SSLCertContextType ctxType) override; diff --git a/iocore/net/QUICNetVConnection_quiche.cc b/iocore/net/QUICNetVConnection_quiche.cc index 3c5795876d3..2d638f99289 100644 --- a/iocore/net/QUICNetVConnection_quiche.cc +++ b/iocore/net/QUICNetVConnection_quiche.cc @@ -26,6 +26,8 @@ #include "QUICMultiCertConfigLoader.h" #include "quic/QUICStream_quiche.h" #include "quic/QUICGlobals.h" + +#include #include static constexpr ink_hrtime WRITE_READY_INTERVAL = HRTIME_MSECONDS(2); @@ -745,6 +747,12 @@ QUICNetVConnection::_fire_ssl_servername_event() { } +in_port_t +QUICNetVConnection::_get_local_port() +{ + return this->get_local_port(); +} + const IpEndpoint & QUICNetVConnection::_getLocalEndpoint() { diff --git a/iocore/net/SSLNetVConnection.cc b/iocore/net/SSLNetVConnection.cc index 615324335b5..c40fad61f8a 100644 --- a/iocore/net/SSLNetVConnection.cc +++ b/iocore/net/SSLNetVConnection.cc @@ -42,6 +42,8 @@ #include "SSLStats.h" #include "P_ALPNSupport.h" +#include + #include #include @@ -1966,6 +1968,12 @@ SSLNetVConnection::_fire_ssl_servername_event() this->callHooks(TS_EVENT_SSL_SERVERNAME); } +in_port_t +SSLNetVConnection::_get_local_port() +{ + return this->get_local_port(); +} + bool SSLNetVConnection::_isTryingRenegotiation() const { diff --git a/iocore/net/TLSSNISupport.cc b/iocore/net/TLSSNISupport.cc index 99852da4fd3..88378a81418 100644 --- a/iocore/net/TLSSNISupport.cc +++ b/iocore/net/TLSSNISupport.cc @@ -21,7 +21,6 @@ limitations under the License. */ #include "P_SSLNextProtocolAccept.h" -#include "P_SSLNetVConnection.h" #include "SSLSNIConfig.h" #include "TLSSNISupport.h" #include "tscore/ink_assert.h" @@ -67,10 +66,7 @@ TLSSNISupport::perform_sni_action() } SNIConfig::scoped_config params; - // should always work in this context of SSL action callbacks - SSLNetVConnection *ssl_vc{dynamic_cast(this)}; - ink_assert(ssl_vc != nullptr); - auto const port{ssl_vc->get_local_port()}; + auto const port{this->_get_local_port()}; if (auto const &actions = params->get({servername, std::strlen(servername)}, port); !actions.first) { Debug("ssl_sni", "%s:%i not available in the map", servername, port); } else { diff --git a/iocore/net/TLSSNISupport.h b/iocore/net/TLSSNISupport.h index 7276fd9a691..baee3ae3bf2 100644 --- a/iocore/net/TLSSNISupport.h +++ b/iocore/net/TLSSNISupport.h @@ -23,11 +23,14 @@ */ #pragma once -#include +#include "tscore/ink_config.h" + +#include +#include + #include #include -#include -#include "tscore/ink_config.h" +#include class TLSSNISupport { @@ -60,6 +63,8 @@ class TLSSNISupport protected: virtual void _fire_ssl_servername_event() = 0; + virtual in_port_t _get_local_port() = 0; + void _clear(); private: