This is an automated email from the ASF dual-hosted git repository.
bnolsen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
The following commit(s) were added to refs/heads/master by this push:
new 801e5bebad add host_override to parent.config and other sni name fixes
(#12868)
801e5bebad is described below
commit 801e5bebad78b6546ebd1b6658bdafc33cd8feba
Author: Brian Olsen <[email protected]>
AuthorDate: Tue Mar 17 12:43:15 2026 -0600
add host_override to parent.config and other sni name fixes (#12868)
* add host_override to parent.config and other sni name fixes
* add documentation for host_override in parent.config, update
strategies.yaml docs
* add test using pristine_host_hdr setting
---------
Co-authored-by: Brian Olsen <[email protected]>
---
doc/admin-guide/files/parent.config.en.rst | 8 +
doc/admin-guide/files/strategies.yaml.en.rst | 2 +-
include/proxy/ParentSelection.h | 15 ++
.../proxy/http/remap/NextHopSelectionStrategy.h | 3 -
src/proxy/ParentSelection.cc | 7 +
src/proxy/http/HttpSM.cc | 6 +-
src/proxy/http/HttpTransact.cc | 73 ++++++++-
src/proxy/http/remap/NextHopConsistentHash.cc | 4 -
src/proxy/http/remap/NextHopRoundRobin.cc | 1 -
src/proxy/http/remap/NextHopSelectionStrategy.cc | 10 --
tests/gold_tests/tls/ssl/gen_foobar_certs.sh | 29 ++++
tests/gold_tests/tls/ssl/server-bar.key | 28 ++++
tests/gold_tests/tls/ssl/server-bar.pem | 22 +++
tests/gold_tests/tls/ssl/server-foo.key | 28 ++++
tests/gold_tests/tls/ssl/server-foo.pem | 22 +++
.../gold_tests/tls/tls_sni_parent_failover.test.py | 174 +++++++++++++++++++++
16 files changed, 402 insertions(+), 30 deletions(-)
diff --git a/doc/admin-guide/files/parent.config.en.rst
b/doc/admin-guide/files/parent.config.en.rst
index 7b8fa4a56d..64fa4d8974 100644
--- a/doc/admin-guide/files/parent.config.en.rst
+++ b/doc/admin-guide/files/parent.config.en.rst
@@ -324,6 +324,14 @@ The following list shows the possible actions and their
allowed values.
- ``false`` - The default. Do not ignore the host status.
+.. _parent-config-format-host_override:
+
+``host_override``
+ One of the following values:
+
+ - ``true`` - Sets the host header of the request using the selected
parent, including the SNI name to be used for any upstream TLS connection.
Useful when a parent is another CDN that requires a correct SNI name.
+ - ``false`` - The default. Does not change the host header.
+
.. _parent-config-format-hash-algorithm:
``hash_algorithm``
diff --git a/doc/admin-guide/files/strategies.yaml.en.rst
b/doc/admin-guide/files/strategies.yaml.en.rst
index ec7c223c8a..7e9852f830 100644
--- a/doc/admin-guide/files/strategies.yaml.en.rst
+++ b/doc/admin-guide/files/strategies.yaml.en.rst
@@ -286,7 +286,7 @@ Each **strategy** in the list may using the following
parameters:
(**self** should only be necessary when the local hostname can only be
translated to an IP address
with a DNS lookup.)
-- **host_override**: A boolean value that will set the host header to the
selected parent rather than the original host. **true** sets host header to
selected parent. **false** (default) leaves the host header untouched.
+- **host_override**: A boolean value that will set the host header to the
selected parent rather than the original host, including the SNI name for any
attempted upstream TLS connection. **true** sets host header to selected
parent. **false** (default) leaves the host header untouched.
Example:
::
diff --git a/include/proxy/ParentSelection.h b/include/proxy/ParentSelection.h
index 5960cfda18..8bfd556fcb 100644
--- a/include/proxy/ParentSelection.h
+++ b/include/proxy/ParentSelection.h
@@ -167,6 +167,7 @@ public:
int max_unavailable_server_retries = 1;
int secondary_mode = 1;
bool ignore_self_detect = false;
+ bool host_override = false;
ParentHashAlgorithm consistent_hash_algorithm =
ParentHashAlgorithm::SIPHASH24;
uint64_t consistent_hash_seed0 = 0;
uint64_t consistent_hash_seed1 = 0;
@@ -242,6 +243,20 @@ struct ParentResult {
return is_api_result() ? ParentRetry_t::NONE : rec->parent_retry;
}
+ bool
+ host_override() const
+ {
+ if (is_api_result()) {
+ return false;
+ }
+
+ if (!is_some()) {
+ return false;
+ }
+
+ return rec->host_override;
+ }
+
unsigned
max_retries(ParentRetry_t method) const
{
diff --git a/include/proxy/http/remap/NextHopSelectionStrategy.h
b/include/proxy/http/remap/NextHopSelectionStrategy.h
index 25e6267615..0623a9b440 100644
--- a/include/proxy/http/remap/NextHopSelectionStrategy.h
+++ b/include/proxy/http/remap/NextHopSelectionStrategy.h
@@ -27,7 +27,6 @@
#include <utility>
#include "ts/apidefs.h"
-#include "proxy/ParentSelection.h"
#include "proxy/http/HttpTransact.h"
#ifndef _NH_UNIT_TESTS_
@@ -199,8 +198,6 @@ public:
const time_t now = 0);
bool nextHopExists(TSHttpTxn txnp, void *ih = nullptr);
- void setHostHeader(TSHttpTxn txnp, const char *hostname);
-
virtual ParentRetry_t responseIsRetryable(int64_t sm_id,
HttpTransact::CurrentInfo ¤t_info, HTTPStatus response_code);
void retryComplete(TSHttpTxn txn, const char *hostname, const int port);
diff --git a/src/proxy/ParentSelection.cc b/src/proxy/ParentSelection.cc
index 0870bcb9bf..b282fba7b3 100644
--- a/src/proxy/ParentSelection.cc
+++ b/src/proxy/ParentSelection.cc
@@ -797,6 +797,13 @@ ParentRecord::Init(matcher_line *line_info)
ignore_self_detect = false;
}
used = true;
+ } else if (strcasecmp(label, "host_override") == 0) {
+ if (strcasecmp(val, "true") == 0) {
+ host_override = true;
+ } else {
+ host_override = false;
+ }
+ used = true;
} else if (strcasecmp(label, "hash_algorithm") == 0) {
consistent_hash_algorithm = parseHashAlgorithm(val);
used = true;
diff --git a/src/proxy/http/HttpSM.cc b/src/proxy/http/HttpSM.cc
index 0aa731a54f..1f312ad1e2 100644
--- a/src/proxy/http/HttpSM.cc
+++ b/src/proxy/http/HttpSM.cc
@@ -5337,11 +5337,11 @@ HttpSM::get_outbound_sni() const
// By default the host header field value is used for the SNI.
zret = t_state.hdr_info.server_request.host_get();
} else if (_ua.get_txn() && policy == "server_name"_tv) {
- const char *server_name = snis->get_sni_server_name();
- if (server_name[0] == '\0') {
+ const char *const server_name = snis->get_sni_server_name();
+ if (nullptr == server_name || server_name[0] == '\0') {
zret.assign(nullptr, swoc::TextView::npos);
} else {
- zret.assign(snis->get_sni_server_name(), swoc::TextView::npos);
+ zret.assign(server_name, swoc::TextView::npos);
}
} else if (policy.front() == '@') { // guaranteed non-empty from previous
clause
zret = policy.remove_prefix(1);
diff --git a/src/proxy/http/HttpTransact.cc b/src/proxy/http/HttpTransact.cc
index 4996a07cce..46562e5ed7 100644
--- a/src/proxy/http/HttpTransact.cc
+++ b/src/proxy/http/HttpTransact.cc
@@ -139,19 +139,51 @@ bypass_ok(HttpTransact::State *s)
return false;
}
+// wrapper to choose between a remap next hop strategy or use parent.config
+// remap next hop strategy is preferred
+inline static bool
+host_override(HttpTransact::State *s)
+{
+ if (s->response_action.handled) { // should be handled by the plugin
+ return false;
+ } else if (nullptr != s->next_hop_strategy) {
+ // remap strategies do not support the TSHttpTxnParentProxySet API.
+ return s->next_hop_strategy->host_override;
+ } else if (nullptr != s->parent_params) {
+ return s->parent_result.host_override();
+ }
+ return false;
+}
+
+// wrapper to choose between a remap next hop strategy or use parent.config
+// remap next hop strategy is preferred
+inline static bool
+is_some(HttpTransact::State *s)
+{
+ if (s->response_action.handled) {
+ return true;
+ } else if (nullptr != s->next_hop_strategy) {
+ // remap strategies do not support the TSHttpTxnParentProxySet API.
+ return s->parent_result.result == ParentResultType::SPECIFIED;
+ } else if (nullptr != s->parent_params) {
+ return s->parent_result.is_some();
+ }
+ return false;
+}
+
// wrapper to choose between a remap next hop strategy or use parent.config
// remap next hop strategy is preferred
inline static bool
is_api_result(HttpTransact::State *s)
{
- bool r = false;
+ bool res = false;
if (nullptr != s->next_hop_strategy) {
// remap strategies do not support the TSHttpTxnParentProxySet API.
- r = false;
+ res = false;
} else if (nullptr != s->parent_params) {
- r = s->parent_result.is_api_result();
+ res = s->parent_result.is_api_result();
}
- return r;
+ return res;
}
// wrapper to get the max_retries.
@@ -207,6 +239,8 @@ retry_type(HttpTransact::State *s)
inline static void
findParent(HttpTransact::State *s)
{
+ TxnDbg(dbg_ctl_http_trans, "findParent");
+
Metrics::Counter::increment(http_rsb.parent_count);
if (s->response_action.handled) {
s->parent_result.hostname = s->response_action.action.hostname;
@@ -295,6 +329,8 @@ parentExists(HttpTransact::State *s)
inline static void
nextParent(HttpTransact::State *s)
{
+ TxnDbg(dbg_ctl_http_trans, "nextParent");
+
TxnDbg(dbg_ctl_parent_down, "connection to parent %s failed, conn_state: %s,
request to origin: %s", s->parent_result.hostname,
HttpDebugNames::get_server_state_name(s->current.state),
s->request_data.get_host());
Metrics::Counter::increment(http_rsb.parent_count);
@@ -599,10 +635,11 @@ find_server_and_update_current_info(HttpTransact::State
*s)
} else {
findParent(s);
}
- if (!s->parent_result.is_some() || is_api_result(s) || parent_is_proxy(s))
{
+ if (!is_some(s) || is_api_result(s) || parent_is_proxy(s)) {
TxnDbg(dbg_ctl_http_trans, "request not cacheable, so bypass parent");
s->parent_result.result = ParentResultType::DIRECT;
}
+
} else if (s->txn_conf->uncacheable_requests_bypass_parent &&
s->txn_conf->no_dns_forward_to_parent == 0 &&
!HttpTransact::is_request_cache_lookupable(s)) {
// request not lookupable and cacheable, so bypass parent if the parent is
not an origin server.
@@ -616,10 +653,12 @@ find_server_and_update_current_info(HttpTransact::State
*s)
} else {
findParent(s);
}
- if (!s->parent_result.is_some() || is_api_result(s) || parent_is_proxy(s))
{
+
+ if (!is_some(s) || is_api_result(s) || parent_is_proxy(s)) {
TxnDbg(dbg_ctl_http_trans, "request not cacheable, so bypass parent");
s->parent_result.result = ParentResultType::DIRECT;
}
+
} else {
switch (s->parent_result.result) {
case ParentResultType::UNDEFINED:
@@ -660,14 +699,32 @@ find_server_and_update_current_info(HttpTransact::State
*s)
}
switch (s->parent_result.result) {
- case ParentResultType::SPECIFIED:
- s->parent_info.name = s->arena.str_store(s->parent_result.hostname,
strlen(s->parent_result.hostname));
+ case ParentResultType::SPECIFIED: {
+ char const *const hostname = s->parent_result.hostname;
+
+ if (nullptr != hostname) {
+ s->parent_info.name = s->arena.str_store(hostname, strlen(hostname));
+
+ // if host header override option enabled
+ if (host_override(s)) {
+ TxnDbg(dbg_ctl_http_trans, "overriding host header with parent %s",
hostname);
+ if (!s->hdr_info.server_request.valid()) {
+
s->hdr_info.client_request.value_set(static_cast<std::string_view>(MIME_FIELD_HOST),
hostname);
+ s->hdr_info.client_request.mark_target_dirty();
+ } else {
+
s->hdr_info.server_request.value_set(static_cast<std::string_view>(MIME_FIELD_HOST),
hostname);
+ s->hdr_info.server_request.mark_target_dirty();
+ }
+ }
+ }
+
update_current_info(&s->current, &s->parent_info,
ResolveInfo::PARENT_PROXY, false);
update_dns_info(&s->dns_info, &s->current);
ink_assert(s->dns_info.looking_up == ResolveInfo::PARENT_PROXY);
s->next_hop_scheme = URL_WKSIDX_HTTP;
return ResolveInfo::PARENT_PROXY;
+ }
case ParentResultType::FAIL:
// No more parents - need to return an error message
s->current.request_to = ResolveInfo::HOST_NONE;
diff --git a/src/proxy/http/remap/NextHopConsistentHash.cc
b/src/proxy/http/remap/NextHopConsistentHash.cc
index 848e78a751..635e153c1e 100644
--- a/src/proxy/http/remap/NextHopConsistentHash.cc
+++ b/src/proxy/http/remap/NextHopConsistentHash.cc
@@ -547,8 +547,4 @@ NextHopConsistentHash::findNextHop(TSHttpTxn txnp, void *
/* ih ATS_UNUSED */, t
NH_Dbg(NH_DBG_CTL, "[%" PRIu64 "] result.result: %s set hostname null port
0 retry false", sm_id,
ParentResultStr[static_cast<int>(result.result)]);
}
-
- setHostHeader(txnp, result.hostname);
-
- return;
}
diff --git a/src/proxy/http/remap/NextHopRoundRobin.cc
b/src/proxy/http/remap/NextHopRoundRobin.cc
index 3acb8411c3..c04acbdaa5 100644
--- a/src/proxy/http/remap/NextHopRoundRobin.cc
+++ b/src/proxy/http/remap/NextHopRoundRobin.cc
@@ -170,7 +170,6 @@ NextHopRoundRobin::findNextHop(TSHttpTxn txnp, void * /* ih
ATS_UNUSED */, time_
result->last_parent = cur_hst_index;
result->last_group = cur_grp_index;
result->retry = parentRetry;
- setHostHeader(txnp, result->hostname);
ink_assert(result->hostname != nullptr);
ink_assert(result->port != 0);
NH_Dbg(NH_DBG_CTL, "[%" PRIu64 "] Chosen parent = %s.%d", sm_id,
result->hostname, result->port);
diff --git a/src/proxy/http/remap/NextHopSelectionStrategy.cc
b/src/proxy/http/remap/NextHopSelectionStrategy.cc
index e308e73a4b..6ca4cf78ef 100644
--- a/src/proxy/http/remap/NextHopSelectionStrategy.cc
+++ b/src/proxy/http/remap/NextHopSelectionStrategy.cc
@@ -265,16 +265,6 @@ NextHopSelectionStrategy::markNextHop(TSHttpTxn txnp,
const char *hostname, cons
return passive_health.markNextHop(txnp, hostname, port, status, ih, now);
}
-void
-NextHopSelectionStrategy::setHostHeader(TSHttpTxn txnp, const char *hostname)
-{
- if (host_override && nullptr != hostname) {
- HttpSM *sm = reinterpret_cast<HttpSM *>(txnp);
-
sm->t_state.hdr_info.client_request.value_set(static_cast<std::string_view>(MIME_FIELD_HOST),
hostname);
- NH_Dbg(NH_DBG_CTL, "[%" PRIu64 "] overriding host header with parent %s",
sm->sm_id, hostname);
- }
-}
-
bool
NextHopSelectionStrategy::nextHopExists(TSHttpTxn txnp, void * /* ih
ATS_UNUSED */)
{
diff --git a/tests/gold_tests/tls/ssl/gen_foobar_certs.sh
b/tests/gold_tests/tls/ssl/gen_foobar_certs.sh
new file mode 100755
index 0000000000..ad45e8d836
--- /dev/null
+++ b/tests/gold_tests/tls/ssl/gen_foobar_certs.sh
@@ -0,0 +1,29 @@
+# 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.
+
+# bash script used to generate sni parent test certs
+
+for name in foo bar
+do
+ openssl req -x509 -newkey rsa:2048 \
+ -keyout server-${name}.key -out server-${name}.pem \
+ -days 3650 -nodes \
+ -subj "/C=US/ST=CO/L=Denver/O=Comcast/OU=Edge/CN=${name}.com" \
+ -addext
"subjectAltName=DNS:${name}.com,DNS:www.${name}.com,DNS:*.${name}.com" \
+ -addext "basicConstraints=critical,CA:TRUE" \
+ -addext "keyUsage=critical,keyCertSign,cRLSign" \
+ -addext "subjectKeyIdentifier=hash"
+done
diff --git a/tests/gold_tests/tls/ssl/server-bar.key
b/tests/gold_tests/tls/ssl/server-bar.key
new file mode 100644
index 0000000000..afe8e73254
--- /dev/null
+++ b/tests/gold_tests/tls/ssl/server-bar.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC7hSSKR+PDyaK9
+Qf5Wi2DoBedwTaIiHRVUOmjGMhb9bHH/xQ1xXbIlXhl9LB5z6KWZTLN/6tDbJMlk
+SF5CEPLXt0+XSwpx1ZZpBaxInKCZoiUbfazwb95y8evPYfOOmMR8tP/BKtygo0+U
+MbzjpIGZEdOkyWF6U4BtMJopC41JOxkrwvGG3DDBDNMui3qAgpmedylGhz7Lilpu
+XLc2cprtIBbo8jcQj4G0My5flZhvHmFe29L2YboLG2JtS8e39zOe+pfLb7T0JmDm
+UgxeJB7KH/d7BI90J1tYctVH2ky20oXQ2R1IC2bhsfuDEIo5OhPu56rINSmvXYDD
+RHL1nJH9AgMBAAECggEAJYzSVdxyeBzS+UoTR7pOU/gGsd7h5injwQOXQoT6RJIo
+O9r2R6rHByOSQBYLHRPLwWhyE13up8t0hp/VPC6PqnG7PuUYeNYX8fzgVIQExu8I
+tjoS3OzR4zudiGApePcPdZd7FW/jUUmSoG7bT8x75S6ELp3i5q986qCOZkS5bJ3Q
+Tr6LdgwYsdRMuwTBmHFym3obd8rVe1oQ4QY2M0mYAraGnFxiCNV+S+hDNsJ7na3F
+hbeBk+YpFKZ/dGDrKClhNbOmpSZmazihHaYoKrBc+fhJSzxVU5YfN+Ds7jsOptpa
+m0pr8Di3QnibkIUrjNiB3ewkIi+tyo0YO5Zzq406oQKBgQDxt4r/6hni0mBY3VrR
+UT74gzGihqxfXTLjq7Hiju9BC5qK9MaVRl+s5I6+phfSg4kyJSFsoWxcoKYFE/Km
+4xpk3sJlVIBwRBmx76h5B3eEpLayhoBR2TT0Cl5M8yhvz2p8CQO/MmGLP3qe/PqH
+32rBNLAs65e4ts2uKUJMC+AkrQKBgQDGmcM6vgRtq/DASFMMqpimSX+8fj9AGqLf
+mVcItXHx7pS1Aiq1G3PSA4efSryMjoleez3mfqFoPvTSP0ASIaQua9rAerfWfYaG
+/P8yAG7oddBb0SDe7f05VjY5jv3dS7sSDHpfaIa06xtRxd6HqUo7iPOir0FR6UzA
+iNkxKfR8kQKBgQCnhxxxjmDukfxw2soM9RB90P/fsxNY7RFONjuN2J7+J/qugEP6
+RdId1DMS867jGoNGG/H0hlTCRh2Ku26cOB6c9r8o185FAQ4GAyJy77foWPi+9vWM
+xMwsr9r33jeSduFIoj7UjyiICDEGbDN/ZFtrGQdZutdnEFuxb6shZcGt6QKBgFJj
+r65K7iNhVTsvxeRXUYSKsUdNSIgbhL4mKwkd3Ot1ApQlFfqULPRPKpBWvOnCqBJe
+Jkvc9LD+jSo7uyTKeAYaEGIRhvqgkJKnmmbv7xLY7Vtp4q0ZJhgHP++Y9pA7vpu6
+OXojLt8XOfoukCbPgFA6fHhdJEgK9SBapV/T++1BAoGBAMzO2jugm25fBzqWQpSe
+JV/CoQIbw2SUyXunsReIMqnYBETUDvzPrvQ0aRWUPtkzwf+vlbU7vauX4CRogB8e
+PWakmkCuSjPlxqycYUk/xIYEuaXn7Kk7IeF3UEbeSJLq9+cXeDun8OSE9lmD2Phn
+ITIVjf8kn1qEPgfr9GlGnaSN
+-----END PRIVATE KEY-----
diff --git a/tests/gold_tests/tls/ssl/server-bar.pem
b/tests/gold_tests/tls/ssl/server-bar.pem
new file mode 100644
index 0000000000..1a22b5935a
--- /dev/null
+++ b/tests/gold_tests/tls/ssl/server-bar.pem
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDnTCCAoWgAwIBAgIUS33d4I/Zq7YeC+bDnGr/e1BJkdwwDQYJKoZIhvcNAQEL
+BQAwXjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNPMQ8wDQYDVQQHDAZEZW52ZXIx
+EDAOBgNVBAoMB0NvbWNhc3QxDTALBgNVBAsMBEVkZ2UxEDAOBgNVBAMMB2Jhci5j
+b20wHhcNMjUxMjE3MTcxMDA0WhcNMzUxMjE1MTcxMDA0WjBeMQswCQYDVQQGEwJV
+UzELMAkGA1UECAwCQ08xDzANBgNVBAcMBkRlbnZlcjEQMA4GA1UECgwHQ29tY2Fz
+dDENMAsGA1UECwwERWRnZTEQMA4GA1UEAwwHYmFyLmNvbTCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBALuFJIpH48PJor1B/laLYOgF53BNoiIdFVQ6aMYy
+Fv1scf/FDXFdsiVeGX0sHnPopZlMs3/q0NskyWRIXkIQ8te3T5dLCnHVlmkFrEic
+oJmiJRt9rPBv3nLx689h846YxHy0/8Eq3KCjT5QxvOOkgZkR06TJYXpTgG0wmikL
+jUk7GSvC8YbcMMEM0y6LeoCCmZ53KUaHPsuKWm5ctzZymu0gFujyNxCPgbQzLl+V
+mG8eYV7b0vZhugsbYm1Lx7f3M576l8tvtPQmYOZSDF4kHsof93sEj3QnW1hy1Ufa
+TLbShdDZHUgLZuGx+4MQijk6E+7nqsg1Ka9dgMNEcvWckf0CAwEAAaNTMFEwHQYD
+VR0OBBYEFPSlsbODTwZv1B8DmEyvQSzKWKQTMB8GA1UdIwQYMBaAFPSlsbODTwZv
+1B8DmEyvQSzKWKQTMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB
+ACOxraNGdHERXHVCW3ICKd14ASA+Xewc2pQrjc/VEAuOH10G1yyDzHCNZrmiJFYN
+IAje3JTfagKK6dFXgeC8tbJhHoNlC+Y/tLxdReuRshJdEraD/2ih2NgUqsI4soJT
+EhB+pUIeni5hF+ymWzewx3axrnblDEt5nl50zOnKKdHuDO6JlovnI50JaFdduWAS
+oOrornY+r6WO9CKt9uLmf/y4Epn6yFX7AL9aDzQQsS62+4o/nkQLf/u5eFRSLJK2
+Jj+QAWNP37is8q+2Jgmbt3DWybOq2znN1edNtUYjRCNafiK9qrhGeNp+yI/qB8dm
+W5pOyGNb+j9iY39KbNkSmTM=
+-----END CERTIFICATE-----
diff --git a/tests/gold_tests/tls/ssl/server-foo.key
b/tests/gold_tests/tls/ssl/server-foo.key
new file mode 100644
index 0000000000..11d104b6e9
--- /dev/null
+++ b/tests/gold_tests/tls/ssl/server-foo.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEugIBADANBgkqhkiG9w0BAQEFAASCBKQwggSgAgEAAoIBAQCHQOoMuJlA4tSU
+DVSDcrb/ggBNvN4DIh3d4briYf2QFB1Gx7pnOwTG3Mt6jc6i/cBmOUhFWnYpe6An
+0on3ARlDzdXj3BiAd1Iw+BXCjxSGW6fVEWwalg09FkgOhRasYwOxggI01fGuY0uH
+CgRMi++4Xgr6umFFDh5hjvX1dLcn5gedseGLK+goiYI8ZPQIXWfIoKDAVb8aDFVu
++gIaZwexkQsSB/gr9uTqLNCZNlaI13fCuOFGLztvERDLwoQ88y1u9v9SpWnOB35l
+bbXf9jdcnv67ug0N04gtAYIUgyOOB0THDsjH2v8gKY7mL5UEOjzM0ZIKHGVDRSAp
+hgakHRVbAgMBAAECgf8h6GgJcWSImLm1168RQeQku9mcPYN75+izDBxMZvr67ZDX
+0p8Rrv+Oc6Wen5J/d2mJU5ol6mWVa90gG1IOeFBOHpvTn8Ki4Lc4oFVuJtobtSoE
+wKIwcy/r2YSFtEZRETZb1BZxkfNlS3cRdvMVbi6AVJrYzwkqTKHs01mgrf7zRs1G
+dpqSj32j4q6H/C6jF+3hBGfWX2HphNzEknZCjrtTqFDIdZ+v9XIAmthfAysKWLII
+oDcDj2NZbBgs32Woy90BTakxpE5r07spplXBdrEboF4ZikmZTd9GwMtY2pfzEMQ0
+IZzPyGyRyU8knr50mDeFNfSTcDdASUj5sBnYeQUCgYEAvQoDkGKm+DZNvI/ltTIF
+1cgcnb62Lrp+GHfSGNV4V0n3uQxyWLpFItARom5nfwrWoyAo+hskbqmAl2OcJj9J
+p3WIaudYb5NTzXYi4eY4gTOuyiXXsXR+PO1RvZEQnCPJAXH+Y+GxPVII5oxYcWeN
+mAnl5UrD17efMB3f2E8wDd8CgYEAtymmbkrxHMTry0JqR+r47i4eRS4LvXyPpVDa
+MMx8nHPqeIfYEgMjgmkBDZIHainKCoFsHseevLwTPz7UN4AXOqqYND4csZsjqAiI
+2/PEFiPczWiJkWN1JoUVBHgTdvrSaIEZ4xYsBfthfzgqan8Tkt/tDI9ysrWdC/QP
+EkbNMAUCgYA93bEc56xNPzhhpZY/nodaV4tF9Mwart3llBEmH3aq2oJABVrGvu8x
+XXbn+cnVQe7MpBHFCGz76m0zHl5UBhJMw6JJmjzPByA99ZOk6ntDjW5+qbPVV5KY
+zfIaYAdXkj6OQohGl+4xa0+OZA/tzT4Bq/uY9sbxKh7m2GARopjPcQKBgG1jboZI
+M5+e68PEsnypFGoLGQDv8wsDrTZqByVFutYlVE8PsjdvsHFeDMbyA4Of2Y5UpSHs
+zhyhpk0LVOqgkT70S1pIDhL0OGNOVY4nE2C1olT6rc4qu/h5WogEvns8aRUIpVE+
+GKSp8Rxtisd3hVUebxAlS7b3SAevhqC/sCORAoGAGy4yXxWrsNBu+OPpLDTiN3WN
+oou1MfVWx/5VmW71w5EUW1tZ1Umva6OSRszO8niRMARYvrqXMoOYRjHEaB1LoTiz
+berNI55iVxlfiwnl7AzigxU+EaDpf5wLPLi7yq5PJZSXW3TC5HZrqvVOEc4CAb1T
+7p7zNQCKEL3QA2jUymw=
+-----END PRIVATE KEY-----
diff --git a/tests/gold_tests/tls/ssl/server-foo.pem
b/tests/gold_tests/tls/ssl/server-foo.pem
new file mode 100644
index 0000000000..ff9366f21b
--- /dev/null
+++ b/tests/gold_tests/tls/ssl/server-foo.pem
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDnTCCAoWgAwIBAgIUKNL6X8hvp7XV9J3DJg/IXI0XsawwDQYJKoZIhvcNAQEL
+BQAwXjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNPMQ8wDQYDVQQHDAZEZW52ZXIx
+EDAOBgNVBAoMB0NvbWNhc3QxDTALBgNVBAsMBEVkZ2UxEDAOBgNVBAMMB2Zvby5j
+b20wHhcNMjUxMjE3MTcwOTU1WhcNMzUxMjE1MTcwOTU1WjBeMQswCQYDVQQGEwJV
+UzELMAkGA1UECAwCQ08xDzANBgNVBAcMBkRlbnZlcjEQMA4GA1UECgwHQ29tY2Fz
+dDENMAsGA1UECwwERWRnZTEQMA4GA1UEAwwHZm9vLmNvbTCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBAIdA6gy4mUDi1JQNVINytv+CAE283gMiHd3huuJh
+/ZAUHUbHumc7BMbcy3qNzqL9wGY5SEVadil7oCfSifcBGUPN1ePcGIB3UjD4FcKP
+FIZbp9URbBqWDT0WSA6FFqxjA7GCAjTV8a5jS4cKBEyL77heCvq6YUUOHmGO9fV0
+tyfmB52x4Ysr6CiJgjxk9AhdZ8igoMBVvxoMVW76AhpnB7GRCxIH+Cv25Oos0Jk2
+VojXd8K44UYvO28REMvChDzzLW72/1Klac4HfmVttd/2N1ye/ru6DQ3TiC0BghSD
+I44HRMcOyMfa/yApjuYvlQQ6PMzRkgocZUNFICmGBqQdFVsCAwEAAaNTMFEwHQYD
+VR0OBBYEFGKhV4jaRQ0wg1AWGgJleyIxP/UzMB8GA1UdIwQYMBaAFGKhV4jaRQ0w
+g1AWGgJleyIxP/UzMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB
+AEQ6IHWosM7rA0OTjTT8Cuovg1hO+EgFyXzzNYRUSNjlidXYPRfgXO1fRAK9Xzca
+dO+meaMgLxWK5AOcrtOLkorJom1tHzMsJmneRGiONGmGSNN84Fk0RwVWKz9Aumoj
+WVz+z/DEV+Myw8BQNP2BtZBX5MsDGWvLcZsZY9YNofIPvAVJONpiAuowc4/IQHH9
+156X3GYgaFkFEV9g6QqFpitR470DKihky8zpspJoeHD/pnZyY+3afKyU4i0YVPpa
+ZHri+ITdWjbqaRMewbcckIB6I+wWXTImHNibcL8sd1yLpw5+IDaV7AdMQmLj5Tmw
+6eiYbN3uwTKx79XwGOas1T0=
+-----END CERTIFICATE-----
diff --git a/tests/gold_tests/tls/tls_sni_parent_failover.test.py
b/tests/gold_tests/tls/tls_sni_parent_failover.test.py
new file mode 100644
index 0000000000..ff64e168f5
--- /dev/null
+++ b/tests/gold_tests/tls/tls_sni_parent_failover.test.py
@@ -0,0 +1,174 @@
+# 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.
+
+Test.Summary = '''
+Test parent failover with SNI name handling, validating that Traffic Server
+correctly fails over between HTTPS parents (including strategy-based parents)
+while enforcing TLS server name verification.
+'''
+
+# Define default ATS
+ts = Test.MakeATSProcess(
+ "ts",
+ enable_tls=True,
+ enable_cache=False,
+)
+
+server_foo = Test.MakeOriginServer(
+ "server_foo",
+ ssl=True,
+ options={
+ "--key": "{0}/server-foo.key".format(Test.RunDirectory),
+ "--cert": "{0}/server-foo.pem".format(Test.RunDirectory),
+ },
+)
+server_bar = Test.MakeOriginServer(
+ "server_bar",
+ ssl=True,
+ options={
+ "--key": "{0}/server-bar.key".format(Test.RunDirectory),
+ "--cert": "{0}/server-bar.pem".format(Test.RunDirectory),
+ },
+)
+
+# default check request/response
+request_foo_header = {"headers": "GET / HTTP/1.1\r\nHost: foo.com\r\n\r\n",
"timestamp": "1469733493.993", "body": ""}
+response_foo_header = {"headers": "HTTP/1.1 200 OK\r\nConnection:
close\r\n\r\n", "timestamp": "1469733493.993", "body": "foo ok"}
+request_bar_header = {"headers": "GET / HTTP/1.1\r\nHost: bar.com\r\n\r\n",
"timestamp": "1469733493.993", "body": ""}
+response_bar_header = {"headers": "HTTP/1.1 200 OK\r\nConnection:
close\r\n\r\n", "timestamp": "1469733493.993", "body": "bar ok"}
+
+server_foo.addResponse("sessionlog.json", request_foo_header,
response_foo_header)
+server_bar.addResponse("sessionlog.json", request_bar_header,
response_bar_header)
+
+# successful request to be served by bar.com
+request_bar_header = {"headers": "GET /path HTTP/1.1\r\nHost:
bar.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
+response_bar_header = {
+ "headers": "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n",
+ "timestamp": "1469733493.993",
+ "body": "path bar ok"
+}
+
+server_bar.addResponse("sessionlog.json", request_bar_header,
response_bar_header)
+
+ts.addSSLfile("ssl/server-foo.pem")
+ts.addSSLfile("ssl/server-foo.key")
+ts.addSSLfile("ssl/server-bar.pem")
+ts.addSSLfile("ssl/server-bar.key")
+
+dns = Test.MakeDNServer("dns")
+
+ts.Disk.records_config.update(
+ {
+ 'proxy.config.diags.debug.enabled': 1,
+ 'proxy.config.diags.debug.tags': 'http|ssl|parent_select|next_hop',
+ 'proxy.config.ssl.client.verify.server.policy': 'ENFORCED',
+ 'proxy.config.ssl.client.verify.server.properties': 'NAME',
+ 'proxy.config.url_remap.pristine_host_hdr': 0,
+ 'proxy.config.dns.nameservers':
'127.0.0.1:{0}'.format(dns.Variables.Port),
+ 'proxy.config.dns.resolv_conf': 'NULL',
+ 'proxy.config.exec_thread.autoconfig.scale': 1.0,
+ 'proxy.config.http.connect.down.policy': 1, # tls failures don't mark
down
+ })
+
+dns.addRecords(records={"foo.com.": ["127.0.0.1"]})
+dns.addRecords(records={"bar.com.": ["127.0.0.1"]})
+dns.addRecords(records={"parent.": ["127.0.0.1"]})
+dns.addRecords(records={"strategy.": ["127.0.0.1"]})
+
+ts.Disk.remap_config.AddLines(
+ [
+ "map http://parent https://parent",
+ "map http://strategy https://strategy @strategy=strat",
+ "map http://parent_prist https://parent @plugin=conf_remap.so
@pparam=proxy.config.url_remap.pristine_host_hdr=1",
+ "map http://strategy_prist https://strategy @strategy=strat
@plugin=conf_remap.so @pparam=proxy.config.url_remap.pristine_host_hdr=1",
+ ])
+
+ts.Disk.parent_config.AddLine(
+ 'dest_domain=. port=443 parent="foo.com:{0}|1;bar.com:{1}|1"
parent_retry=simple_retry parent_is_proxy=false go_direct=false
simple_server_retry_responses="404" host_override=true'
+ .format(server_foo.Variables.SSL_Port, server_bar.Variables.SSL_Port))
+
+# build strategies.yaml file
+ts.Disk.File(ts.Variables.CONFIGDIR + "/strategies.yaml", id="strategies",
typename="ats:config")
+
+s = ts.Disk.strategies
+s.AddLine("groups:")
+s.AddLines(
+ [
+ f" - &gstrat",
+ f" - host: foo.com",
+ f" protocol:",
+ f" - scheme: https",
+ f" port: {server_foo.Variables.SSL_Port}",
+ f" weight: 1.0",
+ f" - host: bar.com",
+ f" protocol:",
+ f" - scheme: https",
+ f" port: {server_bar.Variables.SSL_Port}",
+ f" weight: 1.0",
+ ])
+
+s.AddLine("strategies:")
+
+s.AddLines(
+ [
+ f" - strategy: strat",
+ f" policy: first_live",
+ f" go_direct: false",
+ f" parent_is_proxy: false",
+ f" ignore_self_detect: true",
+ f" host_override: true",
+ f" groups:",
+ f" - *gstrat",
+ f" scheme: https",
+ f" failover:",
+ f" ring_mode: exhaust_ring",
+ f" response_codes:",
+ f" - 404",
+ ])
+
+curl_args = f"-s -L -o /dev/stdout -D /dev/stderr -x
localhost:{ts.Variables.port} "
+
+tr = Test.AddTestRun("request with failover, parent.config")
+tr.Setup.Copy("ssl/server-foo.key")
+tr.Setup.Copy("ssl/server-foo.pem")
+tr.Setup.Copy("ssl/server-bar.key")
+tr.Setup.Copy("ssl/server-bar.pem")
+tr.MakeCurlCommand(curl_args + "http://parent/path", ts=ts)
+tr.StillRunningAfter = ts
+ps = tr.Processes.Default
+ps.StartBefore(server_foo)
+ps.StartBefore(server_bar)
+ps.StartBefore(dns)
+ps.StartBefore(Test.Processes.ts)
+ps.Streams.stdout = Testers.ContainsExpression("path bar ok", "Expected 200
response from bar.com")
+
+tr = Test.AddTestRun("request with failover, strategies.yaml")
+tr.MakeCurlCommand(curl_args + "http://strategy/path", ts=ts)
+tr.StillRunningAfter = ts
+ps = tr.Processes.Default
+ps.Streams.stdout = Testers.ContainsExpression("path bar ok", "Expected 200
response from bar.com")
+
+tr = Test.AddTestRun("request with failover, parent.config pristine_host_hdr")
+tr.MakeCurlCommand(curl_args + "http://parent_prist/path", ts=ts)
+tr.StillRunningAfter = ts
+ps = tr.Processes.Default
+ps.Streams.stdout = Testers.ContainsExpression("path bar ok", "Expected 200
response from bar.com")
+
+tr = Test.AddTestRun("request with failover, strategies.yaml")
+tr.MakeCurlCommand(curl_args + "http://strategy_prist/path", ts=ts)
+tr.StillRunningAfter = ts
+ps = tr.Processes.Default
+ps.Streams.stdout = Testers.ContainsExpression("path bar ok", "Expected 200
response from bar.com")