diff --git a/doc/admin-guide/files/records.config.en.rst b/doc/admin-guide/files/records.config.en.rst index f5b11e4e180..2ddd6551f3f 100644 --- a/doc/admin-guide/files/records.config.en.rst +++ b/doc/admin-guide/files/records.config.en.rst @@ -929,32 +929,29 @@ mptcp :overridable: Enable and set the ability to re-use server connections across client - connections. The valid values are: - - ======== =================================================================== - Value Description - ======== =================================================================== - ``none`` Do not match and do not re-use server sessions. If using this in - :ref:`ts-overridable-config` (like the :ref:`admin-plugins-conf-remap`), - use the integer ``0`` instead. - ``both`` Re-use server sessions, if *both* the IP address and fully qualified - domain name match. If using this in :ref:`ts-overridable-config` (like - the :ref:`admin-plugins-conf-remap`), use the integer ``1`` instead. - ``ip`` Re-use server sessions, checking only that the IP address and port - of the origin server matches. If using this in - :ref:`ts-overridable-config` (like the :ref:`admin-plugins-conf-remap`), - use the integer ``2`` instead. - ``host`` Re-use server sessions, checking only that the fully qualified - domain name matches. If using this in :ref:`ts-overridable-config` - (like the :ref:`admin-plugins-conf-remap`), use the integer ``3`` instead. - ======== =================================================================== - - It is strongly recommended to use either ``none`` or ``both`` for this value - unless you have a specific need for the other settings. The most common - reason is virtual hosts that share an IP address in which case performance - can be enhanced if those sessions can be re-used. However, not all web - servers support requests for different virtual hosts on the same connection - so use with caution. + connections. Multiple values can be specified when separated by commas with no white spaces. Valid values are: + + ============= =================================================================== + Value Description + ============= =================================================================== + ``none`` Do not match and do not re-use server sessions. + ``ip`` Re-use server sessions, checking only that the IP address and port + of the origin server matches. + ``host`` Re-use server sessions, checking that the fully qualified + domain name matches. In addition, if the session uses TLS, it also + checks that the current transaction's host header value matchs the session's SNI. + ``both`` Equivalent to ``host,ip``. + ``hostonly`` Check that the fully qualified domain name matches. + ``sni`` Check that the SNI of the session matches the SNI that would be used to + create a new session. Only applicable for TLS sessions. + ``cert`` Check that the certificate file name used for the server session matches the + certificate file name that would be used for the new server session. Only + applicable for TLS sessions. + ============= =================================================================== + + The setting must contain at least one of ``ip``, ``host``, ``hostonly`` or ``both`` + for session reuse to operate. The other values may be used for greater control + with TLS sessoin reuse. .. note:: diff --git a/doc/developer-guide/api/types/TSServerSessionSharingMatchType.en.rst b/doc/developer-guide/api/types/TSServerSessionSharingMatchType.en.rst deleted file mode 100644 index 4e52c8235b2..00000000000 --- a/doc/developer-guide/api/types/TSServerSessionSharingMatchType.en.rst +++ /dev/null @@ -1,46 +0,0 @@ -.. 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:: ../../../common.defs - -TSServerSessionSharingMatchType -******************************* - -Synopsis -======== - -.. code-block:: cpp - - #include - -.. c:type:: TSServerSessionSharingMatchType - -Enum typedef. - -Enumeration Members -=================== - -.. c:member:: TSServerSessionSharingMatchType TS_SERVER_SESSION_SHARING_MATCH_NONE - -.. c:member:: TSServerSessionSharingMatchType TS_SERVER_SESSION_SHARING_MATCH_BOTH - -.. c:member:: TSServerSessionSharingMatchType TS_SERVER_SESSION_SHARING_MATCH_IP - -.. c:member:: TSServerSessionSharingMatchType TS_SERVER_SESSION_SHARING_MATCH_HOST - -Description -=========== - diff --git a/doc/developer-guide/api/types/TSServerSessionSharingPoolType.en.rst b/doc/developer-guide/api/types/TSServerSessionSharingPoolType.en.rst deleted file mode 100644 index da6e9c4474c..00000000000 --- a/doc/developer-guide/api/types/TSServerSessionSharingPoolType.en.rst +++ /dev/null @@ -1,42 +0,0 @@ -.. 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:: ../../../common.defs - -TSServerSessionSharingPoolType -****************************** - -Synopsis -======== - -.. code-block:: cpp - - #include - -.. c:type:: TSServerSessionSharingPoolType - -Enum typedef. - -Enumeration Members -=================== - -.. c:member:: TSServerSessionSharingPoolType TS_SERVER_SESSION_SHARING_POOL_GLOBAL - -.. c:member:: TSServerSessionSharingPoolType TS_SERVER_SESSION_SHARING_POOL_THREAD - -Description -=========== - diff --git a/include/ts/apidefs.h.in b/include/ts/apidefs.h.in index c874121cfe6..49f5a83cd0b 100644 --- a/include/ts/apidefs.h.in +++ b/include/ts/apidefs.h.in @@ -599,22 +599,6 @@ typedef enum { #ifndef _HTTP_PROXY_API_ENUMS_H_ #define _HTTP_PROXY_API_ENUMS_H_ -/// Server session sharing values - match -/// Must be identical to definition in HttpProxyAPIEnums.h -typedef enum { - TS_SERVER_SESSION_SHARING_MATCH_NONE, - TS_SERVER_SESSION_SHARING_MATCH_BOTH, - TS_SERVER_SESSION_SHARING_MATCH_IP, - TS_SERVER_SESSION_SHARING_MATCH_HOST -} TSServerSessionSharingMatchType; - -/// Server session sharing values - pool -/// Must be identical to definition in HttpProxyAPIEnums.h -typedef enum { - TS_SERVER_SESSION_SHARING_POOL_GLOBAL, - TS_SERVER_SESSION_SHARING_POOL_THREAD, -} TSServerSessionSharingPoolType; - /// Values for per server outbound connection tracking group definition. /// See proxy.config.http.per_server.match typedef enum { diff --git a/proxy/hdrs/HTTP.h b/proxy/hdrs/HTTP.h index 5acbaefac01..0ae1077b585 100644 --- a/proxy/hdrs/HTTP.h +++ b/proxy/hdrs/HTTP.h @@ -586,7 +586,7 @@ class HTTPHdr : public MIMEHdr @note The results are cached so this is fast after the first call. @return A pointer to the host name. */ - const char *host_get(int *length = nullptr); + const char *host_get(int *length = nullptr) const; /** Get the target port. If the target port is not found then it is adjusted to the @@ -857,7 +857,7 @@ HTTPHdr::_test_and_fill_target_cache() const -------------------------------------------------------------------------*/ inline const char * -HTTPHdr::host_get(int *length) +HTTPHdr::host_get(int *length) const { this->_test_and_fill_target_cache(); if (m_target_in_url) { diff --git a/proxy/http/Http1ServerSession.cc b/proxy/http/Http1ServerSession.cc index 50b9e5e38c3..88c8f55ba5a 100644 --- a/proxy/http/Http1ServerSession.cc +++ b/proxy/http/Http1ServerSession.cc @@ -180,7 +180,7 @@ Http1ServerSession::release() server_vc->control_flags.set_flags(0); // Private sessions are never released back to the shared pool - if (private_session || TS_SERVER_SESSION_SHARING_MATCH_NONE == sharing_match) { + if (private_session || sharing_match == 0) { this->do_io_close(); return; } diff --git a/proxy/http/Http1ServerSession.h b/proxy/http/Http1ServerSession.h index c909b3c8a9f..a46c6dd6d08 100644 --- a/proxy/http/Http1ServerSession.h +++ b/proxy/http/Http1ServerSession.h @@ -111,7 +111,7 @@ class Http1ServerSession : public VConnection bool private_session = false; // Copy of the owning SM's server session sharing settings - TSServerSessionSharingMatchType sharing_match = TS_SERVER_SESSION_SHARING_MATCH_BOTH; + TSServerSessionSharingMatchMask sharing_match = TS_SERVER_SESSION_SHARING_MATCH_MASK_NONE; TSServerSessionSharingPoolType sharing_pool = TS_SERVER_SESSION_SHARING_POOL_GLOBAL; /// Hash map descriptor class for IP map. diff --git a/proxy/http/HttpConfig.cc b/proxy/http/HttpConfig.cc index a8f82796958..0ec5251f968 100644 --- a/proxy/http/HttpConfig.cc +++ b/proxy/http/HttpConfig.cc @@ -76,11 +76,12 @@ template struct ConfigEnumPair { /// If found @a value is set to the corresponding value in @a list. template static bool -http_config_enum_search(const char *key, const ConfigEnumPair (&list)[N], MgmtByte &value) +http_config_enum_search(std::string_view key, const ConfigEnumPair (&list)[N], MgmtByte &value) { + Debug("http_config", "enum element %.*s", static_cast(key.size()), key.data()); // We don't expect any of these lists to be more than 10 long, so a linear search is the best choice. for (unsigned i = 0; i < N; ++i) { - if (0 == strcasecmp(list[i]._key, key)) { + if (key.compare(list[i]._key) == 0) { value = list[i]._value; return true; } @@ -110,10 +111,56 @@ http_config_enum_read(const char *name, const ConfigEnumPair (&list)[N], Mgmt //////////////////////////////////////////////////////////////// /// Session sharing match types. static const ConfigEnumPair SessionSharingMatchStrings[] = { - {TS_SERVER_SESSION_SHARING_MATCH_NONE, "none"}, - {TS_SERVER_SESSION_SHARING_MATCH_IP, "ip"}, - {TS_SERVER_SESSION_SHARING_MATCH_HOST, "host"}, - {TS_SERVER_SESSION_SHARING_MATCH_BOTH, "both"}}; + {TS_SERVER_SESSION_SHARING_MATCH_NONE, "none"}, {TS_SERVER_SESSION_SHARING_MATCH_IP, "ip"}, + {TS_SERVER_SESSION_SHARING_MATCH_HOST, "host"}, {TS_SERVER_SESSION_SHARING_MATCH_HOST, "hostsni"}, + {TS_SERVER_SESSION_SHARING_MATCH_BOTH, "both"}, {TS_SERVER_SESSION_SHARING_MATCH_HOSTONLY, "hostonly"}, + {TS_SERVER_SESSION_SHARING_MATCH_SNI, "sni"}, {TS_SERVER_SESSION_SHARING_MATCH_CERT, "cert"}}; + +bool +HttpConfig::load_server_session_sharing_match(const char *key, MgmtByte &mask) +{ + MgmtByte value; + mask = 0; + // Parse through and build up mask + std::string_view key_list(key); + size_t start = 0; + size_t offset = 0; + Debug("http_config", "enum mask value %s", key); + do { + offset = key_list.find(',', start); + if (offset == std::string_view::npos) { + std::string_view one_key = key_list.substr(start); + if (!http_config_enum_search(one_key, SessionSharingMatchStrings, value)) { + return false; + } + } else { + std::string_view one_key = key_list.substr(start, offset - start); + if (!http_config_enum_search(one_key, SessionSharingMatchStrings, value)) { + return false; + } + start = offset + 1; + } + if (value < TS_SERVER_SESSION_SHARING_MATCH_NONE) { + mask |= (1 << value); + } else if (value == TS_SERVER_SESSION_SHARING_MATCH_BOTH) { + mask |= TS_SERVER_SESSION_SHARING_MATCH_MASK_IP | TS_SERVER_SESSION_SHARING_MATCH_MASK_HOSTONLY | + TS_SERVER_SESSION_SHARING_MATCH_MASK_HOSTSNISYNC; + } else if (value == TS_SERVER_SESSION_SHARING_MATCH_HOST) { + mask |= TS_SERVER_SESSION_SHARING_MATCH_MASK_HOSTONLY | TS_SERVER_SESSION_SHARING_MATCH_MASK_HOSTSNISYNC; + } + } while (offset != std::string_view::npos); + return true; +} + +static bool +http_config_enum_mask_read(const char *name, MgmtByte &value) +{ + char key[512]; // it's just one key - painful UI if keys are longer than this + if (REC_ERR_OKAY == RecGetRecordString(name, key, sizeof(key))) { + return HttpConfig::load_server_session_sharing_match(key, value); + } + return false; +} static const ConfigEnumPair SessionSharingPoolStrings[] = { {TS_SERVER_SESSION_SHARING_POOL_GLOBAL, "global"}, @@ -200,7 +247,7 @@ http_server_session_sharing_cb(const char *name, RecDataT dtype, RecData data, v MgmtByte &match = c->oride.server_session_sharing_match; if (RECD_INT == dtype) { match = static_cast(data.rec_int); - } else if (RECD_STRING == dtype && http_config_enum_search(data.rec_string, SessionSharingMatchStrings, match)) { + } else if (RECD_STRING == dtype && HttpConfig::load_server_session_sharing_match(data.rec_string, match)) { // empty } else { valid_p = false; @@ -1059,8 +1106,7 @@ HttpConfig::startup() // [amc] This is a bit of a mess, need to figure out to make this cleaner. RecRegisterConfigUpdateCb("proxy.config.http.server_session_sharing.match", &http_server_session_sharing_cb, &c); - http_config_enum_read("proxy.config.http.server_session_sharing.match", SessionSharingMatchStrings, - c.oride.server_session_sharing_match); + http_config_enum_mask_read("proxy.config.http.server_session_sharing.match", c.oride.server_session_sharing_match); http_config_enum_read("proxy.config.http.server_session_sharing.pool", SessionSharingPoolStrings, c.server_session_sharing_pool); RecRegisterConfigUpdateCb("proxy.config.http.insert_forwarded", &http_insert_forwarded_cb, &c); diff --git a/proxy/http/HttpConfig.h b/proxy/http/HttpConfig.h index bf567bd4df6..978775dbfe9 100644 --- a/proxy/http/HttpConfig.h +++ b/proxy/http/HttpConfig.h @@ -465,7 +465,7 @@ struct OverridableHttpConfigParams { MgmtByte keep_alive_post_out = 1; // share server sessions for post MgmtInt server_min_keep_alive_conns = 0; - MgmtByte server_session_sharing_match = TS_SERVER_SESSION_SHARING_MATCH_BOTH; + MgmtByte server_session_sharing_match = 0; MgmtByte auth_server_session_private = 1; MgmtByte fwd_proxy_auth_to_parent = 0; MgmtByte uncacheable_requests_bypass_parent = 1; @@ -821,6 +821,8 @@ class HttpConfig inkcoreapi static HttpConfigParams *acquire(); inkcoreapi static void release(HttpConfigParams *params); + static bool load_server_session_sharing_match(const char *key, MgmtByte &mask); + // parse ssl ports configuration string static HttpConfigPortRange *parse_ports_list(char *ports_str); diff --git a/proxy/http/HttpProxyAPIEnums.h b/proxy/http/HttpProxyAPIEnums.h index 5a94a10bd9f..f0d84557210 100644 --- a/proxy/http/HttpProxyAPIEnums.h +++ b/proxy/http/HttpProxyAPIEnums.h @@ -29,24 +29,37 @@ #pragma once -// This is use to signal apidefs.h to not define these again. -#ifndef _HTTP_PROXY_API_ENUMS_H_ -#define _HTTP_PROXY_API_ENUMS_H_ - /// Server session sharing values - match typedef enum { + TS_SERVER_SESSION_SHARING_MATCH_IP, + TS_SERVER_SESSION_SHARING_MATCH_HOSTONLY, + TS_SERVER_SESSION_SHARING_MATCH_HOSTSNISYNC, + TS_SERVER_SESSION_SHARING_MATCH_SNI, + TS_SERVER_SESSION_SHARING_MATCH_CERT, TS_SERVER_SESSION_SHARING_MATCH_NONE, TS_SERVER_SESSION_SHARING_MATCH_BOTH, - TS_SERVER_SESSION_SHARING_MATCH_IP, - TS_SERVER_SESSION_SHARING_MATCH_HOST + TS_SERVER_SESSION_SHARING_MATCH_HOST, } TSServerSessionSharingMatchType; +typedef enum { + TS_SERVER_SESSION_SHARING_MATCH_MASK_NONE = 0, + TS_SERVER_SESSION_SHARING_MATCH_MASK_IP = 0x1, + TS_SERVER_SESSION_SHARING_MATCH_MASK_HOSTONLY = 0x2, + TS_SERVER_SESSION_SHARING_MATCH_MASK_HOSTSNISYNC = 0x4, + TS_SERVER_SESSION_SHARING_MATCH_MASK_SNI = 0x8, + TS_SERVER_SESSION_SHARING_MATCH_MASK_CERT = 0x10 +} TSServerSessionSharingMatchMask; + /// Server session sharing values - pool typedef enum { TS_SERVER_SESSION_SHARING_POOL_GLOBAL, TS_SERVER_SESSION_SHARING_POOL_THREAD, } TSServerSessionSharingPoolType; +// This is use to signal apidefs.h to not define these again. +#ifndef _HTTP_PROXY_API_ENUMS_H_ +#define _HTTP_PROXY_API_ENUMS_H_ + /// Values for per server outbound connection tracking group definition. /// See proxy.config.http.per_server.match typedef enum { @@ -55,4 +68,5 @@ typedef enum { TS_SERVER_OUTBOUND_MATCH_HOST, TS_SERVER_OUTBOUND_MATCH_BOTH } TSOutboundConnectionMatchType; + #endif diff --git a/proxy/http/HttpSM.cc b/proxy/http/HttpSM.cc index 6cb81625f3a..f3ea3e02d7d 100644 --- a/proxy/http/HttpSM.cc +++ b/proxy/http/HttpSM.cc @@ -1785,7 +1785,7 @@ HttpSM::state_http_server_open(int event, void *data) THREAD_ALLOC_INIT(httpServerSessionAllocator, mutex->thread_holding) : httpServerSessionAllocator.alloc(); session->sharing_pool = static_cast(t_state.http_config_param->server_session_sharing_pool); - session->sharing_match = static_cast(t_state.txn_conf->server_session_sharing_match); + session->sharing_match = static_cast(t_state.txn_conf->server_session_sharing_match); netvc = static_cast(data); session->attach_hostname(t_state.current.server->name); @@ -4779,6 +4779,27 @@ set_tls_options(NetVCOptions &opt, const OverridableHttpConfigParams *txn_conf) } } +std::string_view +HttpSM::get_outbound_cert() const +{ + const char *cert_name = t_state.txn_conf->ssl_client_cert_filename; + return std::string_view(cert_name); +} + +std::string_view +HttpSM::get_outbound_sni() const +{ + const char *sni_name = nullptr; + size_t len = 0; + if (t_state.txn_conf->ssl_client_sni_policy != nullptr && !strcmp(t_state.txn_conf->ssl_client_sni_policy, "remap")) { + len = strlen(t_state.server_info.name); + sni_name = t_state.server_info.name; + } else { // Do the default of host header for SNI + sni_name = t_state.hdr_info.server_request.host_get(reinterpret_cast(&len)); + } + return std::string_view(sni_name, len); +} + ////////////////////////////////////////////////////////////////////////// // // HttpSM::do_http_server_open() @@ -5128,26 +5149,18 @@ HttpSM::do_http_server_open(bool raw) if (tls_upstream) { SMDebug("http", "calling sslNetProcessor.connect_re"); + std::string_view sni_name = this->get_outbound_sni(); + if (sni_name.length() > 0) { + opt.set_sni_servername(sni_name.data(), sni_name.length()); + } int len = 0; - if (t_state.txn_conf->ssl_client_sni_policy != nullptr && !strcmp(t_state.txn_conf->ssl_client_sni_policy, "remap")) { - len = strlen(t_state.server_info.name); - opt.set_sni_servername(t_state.server_info.name, len); - } else if (t_state.txn_conf->ssl_client_sni_policy != nullptr && - !strcmp(t_state.txn_conf->ssl_client_sni_policy, "verify_with_name_source")) { - // the same with "remap" policy to set sni_servername - len = strlen(t_state.server_info.name); - opt.set_sni_servername(t_state.server_info.name, len); - + if (t_state.txn_conf->ssl_client_sni_policy != nullptr && + !strcmp(t_state.txn_conf->ssl_client_sni_policy, "verify_with_name_source")) { // also set sni_hostname with host header from server request in this policy const char *host = t_state.hdr_info.server_request.host_get(&len); if (host && len > 0) { opt.set_sni_hostname(host, len); } - } else { // Do the default of host header for SNI - const char *host = t_state.hdr_info.server_request.host_get(&len); - if (host && len > 0) { - opt.set_sni_servername(host, len); - } } if (t_state.server_info.name) { opt.set_ssl_servername(t_state.server_info.name); diff --git a/proxy/http/HttpSM.h b/proxy/http/HttpSM.h index 6f1663bbfce..0a6b7d2e95c 100644 --- a/proxy/http/HttpSM.h +++ b/proxy/http/HttpSM.h @@ -211,6 +211,8 @@ class HttpSM : public Continuation, public PluginUserArgs static HttpSM *allocate(); HttpCacheSM &get_cache_sm(); // Added to get the object of CacheSM YTS Team, yamsat + std::string_view get_outbound_sni() const; + std::string_view get_outbound_cert() const; void init(bool from_early_data = false); diff --git a/proxy/http/HttpSessionManager.cc b/proxy/http/HttpSessionManager.cc index 3667e6f717b..968e6a5272c 100644 --- a/proxy/http/HttpSessionManager.cc +++ b/proxy/http/HttpSessionManager.cc @@ -64,41 +64,89 @@ ServerSessionPool::purge() bool ServerSessionPool::match(Http1ServerSession *ss, sockaddr const *addr, CryptoHash const &hostname_hash, - TSServerSessionSharingMatchType match_style) + TSServerSessionSharingMatchMask match_style) { - return TS_SERVER_SESSION_SHARING_MATCH_NONE != - match_style && // if no matching allowed, fail immediately. - // The hostname matches if we're not checking it or it (and the port!) is a match. - (TS_SERVER_SESSION_SHARING_MATCH_IP == match_style || - (ats_ip_port_cast(addr) == ats_ip_port_cast(ss->get_server_ip()) && ss->hostname_hash == hostname_hash)) && - // The IP address matches if we're not checking it or it is a match. - (TS_SERVER_SESSION_SHARING_MATCH_HOST == match_style || ats_ip_addr_port_eq(ss->get_server_ip(), addr)); + bool retval = match_style != 0; + if (retval && (TS_SERVER_SESSION_SHARING_MATCH_MASK_IP & match_style)) { + retval = ats_ip_addr_port_eq(ss->get_server_ip(), addr); + } + if (retval && (TS_SERVER_SESSION_SHARING_MATCH_MASK_HOSTONLY & match_style)) { + retval = (ats_ip_port_cast(addr) == ats_ip_port_cast(ss->get_server_ip()) && ss->hostname_hash == hostname_hash); + } + return retval; +} + +bool +ServerSessionPool::validate_host_sni(HttpSM *sm, NetVConnection *netvc) +{ + bool retval = true; + if (sm->t_state.scheme == URL_WKSIDX_HTTPS) { + // The sni_servername of the connection was set on HttpSM::do_http_server_open + // by fetching the hostname from the server request. So the connection should only + // be reused if the hostname in the new request is the same as the host name in the + // original request + const char *session_sni = netvc->options.sni_servername; + if (session_sni) { + // TS-4468: If the connection matches, make sure the SNI server + // name (if present) matches the request hostname + int len = 0; + const char *req_host = sm->t_state.hdr_info.server_request.host_get(&len); + retval = strncasecmp(session_sni, req_host, len) == 0; + Debug("http_ss", "validate_host_sni host=%*.s, sni=%s", len, req_host, session_sni); + } + } + return retval; } bool ServerSessionPool::validate_sni(HttpSM *sm, NetVConnection *netvc) { - // TS-4468: If the connection matches, make sure the SNI server - // name (if present) matches the request hostname - int len = 0; - const char *req_host = sm->t_state.hdr_info.server_request.host_get(&len); - // The sni_servername of the connection was set on HttpSM::do_http_server_open - // by fetching the hostname from the server request. So the connection should only - // be reused if the hostname in the new request is the same as the host name in the - // original request - const char *session_sni = netvc->options.sni_servername; - - return ((sm->t_state.scheme != URL_WKSIDX_HTTPS) || !session_sni || strncasecmp(session_sni, req_host, len) == 0); + bool retval = true; + // Verify that the sni name on this connection would match the sni we would have use to create + // a new connection. + // + if (sm->t_state.scheme == URL_WKSIDX_HTTPS) { + const char *session_sni = netvc->options.sni_servername; + std::string_view proposed_sni = sm->get_outbound_sni(); + Debug("http_ss", "validate_sni proposed_sni=%s, sni=%s", proposed_sni.data(), session_sni); + if (!session_sni || proposed_sni.length() == 0) { + retval = session_sni == nullptr && proposed_sni.length() == 0; + } else { + retval = proposed_sni.compare(session_sni) == 0; + } + } + return retval; +} + +bool +ServerSessionPool::validate_cert(HttpSM *sm, NetVConnection *netvc) +{ + bool retval = true; + // Verify that the cert file associated this connection would match the cert file we would have use to create + // a new connection. + // + if (sm->t_state.scheme == URL_WKSIDX_HTTPS) { + const char *session_cert = netvc->options.ssl_client_cert_name; + std::string_view proposed_cert = sm->get_outbound_cert(); + Debug("http_ss", "validate_cert proposed_cert=%.*s, cert=%s", static_cast(proposed_cert.size()), proposed_cert.data(), + session_cert); + if (!session_cert || proposed_cert.length() == 0) { + retval = session_cert == nullptr && proposed_cert.length() == 0; + } else { + retval = proposed_cert.compare(session_cert) == 0; + } + } + return retval; } HSMresult_t ServerSessionPool::acquireSession(sockaddr const *addr, CryptoHash const &hostname_hash, - TSServerSessionSharingMatchType match_style, HttpSM *sm, Http1ServerSession *&to_return) + TSServerSessionSharingMatchMask match_style, HttpSM *sm, Http1ServerSession *&to_return) { HSMresult_t zret = HSM_NOT_FOUND; to_return = nullptr; - if (TS_SERVER_SESSION_SHARING_MATCH_HOST == match_style) { + if ((TS_SERVER_SESSION_SHARING_MATCH_MASK_HOSTONLY & match_style) && !(TS_SERVER_SESSION_SHARING_MATCH_MASK_IP & match_style)) { // This is broken out because only in this case do we check the host hash first. The range must be checked // to verify an upstream that matches port and SNI name is selected. Walk backwards to select oldest. in_port_t port = ats_ip_port_cast(addr); @@ -108,7 +156,10 @@ ServerSessionPool::acquireSession(sockaddr const *addr, CryptoHash const &hostna std::tie(first, last) = static_cast(m_fqdn_pool.equal_range(hostname_hash)); while (last != first) { --last; - if (port == ats_ip_port_cast(last->get_server_ip()) && validate_sni(sm, last->get_netvc())) { + if (port == ats_ip_port_cast(last->get_server_ip()) && + (!(match_style & TS_SERVER_SESSION_SHARING_MATCH_MASK_SNI) || validate_sni(sm, last->get_netvc())) && + (!(match_style & TS_SERVER_SESSION_SHARING_MATCH_MASK_HOSTSNISYNC) || validate_host_sni(sm, last->get_netvc())) && + (!(match_style & TS_SERVER_SESSION_SHARING_MATCH_MASK_CERT) || validate_cert(sm, last->get_netvc()))) { zret = HSM_DONE; break; } @@ -118,17 +169,21 @@ ServerSessionPool::acquireSession(sockaddr const *addr, CryptoHash const &hostna m_fqdn_pool.erase(last); m_ip_pool.erase(to_return); } - } else if (TS_SERVER_SESSION_SHARING_MATCH_NONE != match_style) { // matching is not disabled. + } else if (TS_SERVER_SESSION_SHARING_MATCH_MASK_IP & match_style) { // matching is not disabled. IPTable::iterator first, last; // FreeBSD/clang++ bug workaround: explicit cast to super type to make overload work. Not needed on Fedora27 nor gcc. // Not fixed on FreeBSD as of llvm 6.0.1. std::tie(first, last) = static_cast(m_ip_pool.equal_range(addr)); - // The range is all that is needed in the match IP case, otherwise need to scan for matching fqdn. + // The range is all that is needed in the match IP case, otherwise need to scan for matching fqdn + // And matches the other constraints as well // Note the port is matched as part of the address key so it doesn't need to be checked again. - if (TS_SERVER_SESSION_SHARING_MATCH_IP != match_style) { + if (match_style & (~TS_SERVER_SESSION_SHARING_MATCH_MASK_IP)) { while (last != first) { --last; - if (last->hostname_hash == hostname_hash && validate_sni(sm, last->get_netvc())) { + if ((!(match_style & TS_SERVER_SESSION_SHARING_MATCH_MASK_HOSTONLY) || last->hostname_hash == hostname_hash) && + (!(match_style & TS_SERVER_SESSION_SHARING_MATCH_MASK_SNI) || validate_sni(sm, last->get_netvc())) && + (!(match_style & TS_SERVER_SESSION_SHARING_MATCH_MASK_HOSTSNISYNC) || validate_host_sni(sm, last->get_netvc())) && + (!(match_style & TS_SERVER_SESSION_SHARING_MATCH_MASK_CERT) || validate_cert(sm, last->get_netvc()))) { zret = HSM_DONE; break; } @@ -282,8 +337,8 @@ HttpSessionManager::acquire_session(Continuation * /* cont ATS_UNUSED */, sockad ProxyTransaction *ua_txn, HttpSM *sm) { Http1ServerSession *to_return = nullptr; - TSServerSessionSharingMatchType match_style = - static_cast(sm->t_state.txn_conf->server_session_sharing_match); + TSServerSessionSharingMatchMask match_style = + static_cast(sm->t_state.txn_conf->server_session_sharing_match); CryptoHash hostname_hash; HSMresult_t retval = HSM_NOT_FOUND; @@ -300,7 +355,12 @@ HttpSessionManager::acquire_session(Continuation * /* cont ATS_UNUSED */, sockad // the IP/hostname here seems a bit redundant too // if (ServerSessionPool::match(to_return, ip, hostname_hash, match_style) && - ServerSessionPool::validate_sni(sm, to_return->get_netvc())) { + (!(match_style & TS_SERVER_SESSION_SHARING_MATCH_MASK_SNI) || + ServerSessionPool::validate_sni(sm, to_return->get_netvc())) && + (!(match_style & TS_SERVER_SESSION_SHARING_MATCH_MASK_HOSTSNISYNC) || + ServerSessionPool::validate_host_sni(sm, to_return->get_netvc())) && + (!(match_style & TS_SERVER_SESSION_SHARING_MATCH_MASK_CERT) || + ServerSessionPool::validate_cert(sm, to_return->get_netvc()))) { Debug("http_ss", "[%" PRId64 "] [acquire session] returning attached session ", to_return->con_id); to_return->state = HSS_ACTIVE; sm->attach_server_session(to_return); diff --git a/proxy/http/HttpSessionManager.h b/proxy/http/HttpSessionManager.h index 3a6ff020722..dcb80c9bac4 100644 --- a/proxy/http/HttpSessionManager.h +++ b/proxy/http/HttpSessionManager.h @@ -64,7 +64,9 @@ class ServerSessionPool : public Continuation ServerSessionPool(); /// Handle events from server sessions. int eventHandler(int event, void *data); + static bool validate_host_sni(HttpSM *sm, NetVConnection *netvc); static bool validate_sni(HttpSM *sm, NetVConnection *netvc); + static bool validate_cert(HttpSM *sm, NetVConnection *netvc); protected: using IPTable = IntrusiveHashMap; @@ -74,7 +76,7 @@ class ServerSessionPool : public Continuation /** Check if a session matches address and host name. */ static bool match(Http1ServerSession *ss, sockaddr const *addr, CryptoHash const &host_hash, - TSServerSessionSharingMatchType match_style); + TSServerSessionSharingMatchMask match_style); /** Get a session from the pool. @@ -83,7 +85,7 @@ class ServerSessionPool : public Continuation @return A pointer to the session or @c NULL if not matching session was found. */ - HSMresult_t acquireSession(sockaddr const *addr, CryptoHash const &host_hash, TSServerSessionSharingMatchType match_style, + HSMresult_t acquireSession(sockaddr const *addr, CryptoHash const &host_hash, TSServerSessionSharingMatchMask match_style, HttpSM *sm, Http1ServerSession *&server_session); /** Release a session to to pool. */ diff --git a/src/traffic_server/InkAPI.cc b/src/traffic_server/InkAPI.cc index d9fef700e1d..2f75640824f 100644 --- a/src/traffic_server/InkAPI.cc +++ b/src/traffic_server/InkAPI.cc @@ -8758,6 +8758,11 @@ TSHttpTxnConfigStringSet(TSHttpTxn txnp, TSOverridableConfigKey conf, const char } } break; + case TS_CONFIG_HTTP_SERVER_SESSION_SHARING_MATCH: + if (value && length > 0) { + HttpConfig::load_server_session_sharing_match(value, s->t_state.my_txn_conf().server_session_sharing_match); + } + break; case TS_CONFIG_SSL_CLIENT_VERIFY_SERVER_POLICY: if (value && length > 0) { s->t_state.my_txn_conf().ssl_client_verify_server_policy = const_cast(value);