You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by ma...@apache.org on 2023/08/14 22:18:28 UTC
[trafficserver] branch master updated: Check SNI in h3 (#10184)
This is an automated email from the ASF dual-hosted git repository.
maskit 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 335054e739 Check SNI in h3 (#10184)
335054e739 is described below
commit 335054e739cb2402b65211342c9a5d1ef7aa3330
Author: Zhengxi Li <lz...@hotmail.com>
AuthorDate: Mon Aug 14 18:18:22 2023 -0400
Check SNI in h3 (#10184)
---
proxy/http3/Http3SessionAccept.cc | 7 +-
tests/gold_tests/h3/h3_sni_check.test.py | 129 +++++++++++++++++++++++++
tests/gold_tests/h3/replays/h3_sni.replay.yaml | 72 ++++++++++++++
3 files changed, 207 insertions(+), 1 deletion(-)
diff --git a/proxy/http3/Http3SessionAccept.cc b/proxy/http3/Http3SessionAccept.cc
index ab00d0aa20..be3f2b90d2 100644
--- a/proxy/http3/Http3SessionAccept.cc
+++ b/proxy/http3/Http3SessionAccept.cc
@@ -48,6 +48,12 @@ Http3SessionAccept::accept(NetVConnection *netvc, MIOBuffer *iobuf, IOBufferRead
Warning("QUIC client '%s' prohibited by ip-allow policy", ats_ip_ntop(client_ip, ipb, sizeof(ipb)));
return false;
}
+ // RFC9114, section 3.2-2: Client must send the SNI extension.
+ if (auto sni = netvc->get_service<TLSSNISupport>(); !sni || sni->get_sni_server_name()[0] == '\0') {
+ ip_port_text_buffer ipb;
+ Debug("http3", "SNI not found in connection from %s.", ats_ip_nptop(client_ip, ipb, sizeof(ipb)));
+ return false;
+ }
netvc->attributes = this->options.transport_type;
@@ -59,7 +65,6 @@ Http3SessionAccept::accept(NetVConnection *netvc, MIOBuffer *iobuf, IOBufferRead
Debug("http3", "[%s] accepted connection from %s transport type = %d", qvc->cids().data(),
ats_ip_nptop(client_ip, ipb, sizeof(ipb)), netvc->attributes);
}
-
std::string_view alpn = qvc->negotiated_application_name();
if (IP_PROTO_TAG_HTTP_QUIC.compare(alpn) == 0 || IP_PROTO_TAG_HTTP_QUIC_D29.compare(alpn) == 0) {
diff --git a/tests/gold_tests/h3/h3_sni_check.test.py b/tests/gold_tests/h3/h3_sni_check.test.py
new file mode 100644
index 0000000000..7dea1a8ba7
--- /dev/null
+++ b/tests/gold_tests/h3/h3_sni_check.test.py
@@ -0,0 +1,129 @@
+'''
+Verify h3 SNI checking behavior.
+'''
+# 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 = '''
+Verify h3 SNI checking behavior.
+'''
+
+Test.SkipUnless(
+ Condition.HasATSFeature('TS_HAS_QUICHE'),
+ Condition.HasCurlFeature('http3')
+)
+
+Test.ContinueOnFail = True
+
+
+class Test_sni_check:
+ """Configure a test to verify SNI checking behavior for h3 connections."""
+
+ replay_file = "replays/h3_sni.replay.yaml"
+ client_counter: int = 0
+ ts_counter: int = 0
+ server_counter: int = 0
+
+ def __init__(self, name: str, gold_file="", replay_keys="", expect_request_rejected=False):
+ """Initialize the test.
+
+ :param name: The name of the test.
+ :param gold_file: (Optional) Gold file to be checked.
+ :param replay_keys: (Optional) Keys to be used by pv.
+ :param expect_request_rejected: (Optional) Whether or not the client request is expected to be rejected.
+ """
+ self.name = name
+ self.gold_file = gold_file
+ self.replay_keys = replay_keys
+ self.expect_request_rejected = expect_request_rejected
+
+ def _configure_server(self, tr: 'TestRun'):
+ """Configure the server.
+
+ :param tr: The TestRun object to associate the server process with.
+ """
+ server = tr.AddVerifierServerProcess(
+ f"server_{Test_sni_check.server_counter}",
+ self.replay_file)
+ Test_sni_check.server_counter += 1
+ self._server = server
+
+ def _configure_traffic_server(self, tr: 'TestRun'):
+ """Configure Traffic Server.
+
+ :param tr: The TestRun object to associate the ts process with.
+ """
+ ts = tr.MakeATSProcess(f"ts-{Test_sni_check.ts_counter}", enable_quic=True, enable_tls=True)
+
+ Test_sni_check.ts_counter += 1
+ self._ts = ts
+ # Configure TLS for Traffic Server.
+ self._ts.addDefaultSSLFiles()
+ self._ts.Disk.ssl_multicert_config.AddLine(
+ 'dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key'
+ )
+ self._ts.Disk.records_config.update({
+ 'proxy.config.diags.debug.enabled': 1,
+ 'proxy.config.diags.debug.tags': 'http',
+ 'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir),
+ 'proxy.config.quic.no_activity_timeout_in': 0,
+ 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir),
+ 'proxy.config.ssl.client.verify.server.policy': 'PERMISSIVE',
+ })
+
+ self._ts.Disk.remap_config.AddLine(f'map / http://127.0.0.1:{self._server.Variables.http_port}')
+
+ def run(self):
+ """Run the test."""
+ tr = Test.AddTestRun(self.name)
+ self._configure_server(tr)
+ self._configure_traffic_server(tr)
+
+ tr.Processes.Default.StartBefore(self._server)
+ tr.Processes.Default.StartBefore(self._ts)
+
+ tr.AddVerifierClientProcess(
+ f'client-{Test_sni_check.client_counter}',
+ self.replay_file,
+ http3_ports=[self._ts.Variables.ssl_port],
+ keys=self.replay_keys)
+ Test_sni_check.client_counter += 1
+
+ if self.expect_request_rejected:
+ # The client request should time out because ATS rejects it and does
+ # not send a response.
+ tr.Processes.Default.ReturnCode = 1
+ self._ts.Disk.traffic_out.Content += Testers.ContainsExpression(
+ "SNI not found", "ATS should detect the missing SNI.")
+ else:
+ # Verify the client request is successful.
+ tr.Processes.Default.ReturnCode = 0
+ self._ts.Disk.traffic_out.Content += Testers.ExcludesExpression(
+ "SNI not found", "ATS should see the SNI presented by client.")
+
+ if self.gold_file:
+ tr.Processes.Default.Streams.all = self.gold_file
+
+
+# TEST 1: Client request with SNI.
+test0 = Test_sni_check(
+ "", replay_keys="has_sni", expect_request_rejected=False)
+test0.run()
+
+# TEST 2: Client request without SNI.
+test1 = Test_sni_check(
+ "", replay_keys="no_sni", expect_request_rejected=True)
+test1.run()
diff --git a/tests/gold_tests/h3/replays/h3_sni.replay.yaml b/tests/gold_tests/h3/replays/h3_sni.replay.yaml
new file mode 100644
index 0000000000..135d375dfc
--- /dev/null
+++ b/tests/gold_tests/h3/replays/h3_sni.replay.yaml
@@ -0,0 +1,72 @@
+# 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: 3
+ - name: tls
+ sni: test_sni
+ transactions:
+
+ - client-request:
+ version: "3"
+ headers:
+ fields:
+ - [ Content-Length, 0 ]
+ - [:method, GET]
+ - [:scheme, https]
+ - [:authority, example.com]
+ - [:path, /path/test1]
+ - [ uuid, has_sni ]
+ server-response:
+ status: 200
+ reason: "OK"
+ headers:
+ fields:
+ - [ Content-Length, 20 ]
+
+ proxy-response:
+ status: 200
+
+- protocol:
+ - name: http
+ version: 3
+ # Note that the SNI is not specified for this connection.
+ - name: tls
+ transactions:
+ - client-request:
+ version: "3"
+ headers:
+ fields:
+ - [ Content-Length, 0 ]
+ - [:method, GET]
+ - [:scheme, https]
+ - [:authority, example.com]
+ - [:path, /path/test1]
+ - [ uuid, no_sni ]
+ server-response:
+ status: 200
+ reason: "OK"
+ headers:
+ fields:
+ - [ Content-Length, 20 ]
+
+ proxy-response:
+ status: 200