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 <lzx404...@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

Reply via email to