This is an automated email from the ASF dual-hosted git repository.
bneradt 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 3df2bceb04 HTTP/3 support via OpenSSL 3.5 (#13186)
3df2bceb04 is described below
commit 3df2bceb04b4c022aaf5bef67d9986c90fa81fbe
Author: Brian Neradt <[email protected]>
AuthorDate: Thu Jul 30 17:56:00 2026 -0500
HTTP/3 support via OpenSSL 3.5 (#13186)
* HTTP/3 via OpenSSL 3.5 + quiche
Fedora now ships OpenSSL 3.5 with the third-party QUIC TLS
callback API, but quiche still links against the older
quictls/BoringSSL symbols. ATS therefore could not use the system
OpenSSL library for downstream HTTP/3 without dragging in a different
TLS stack.
This adds CMake detection for the OpenSSL callback API and provides a
private compatibility layer that maps quiche's legacy hooks onto
SSL_set_quic_tls_cbs. This requires static quiche in that mode so ATS
resolves the shim symbols locally and links the final binaries against
the system OpenSSL libraries.
This also relaxes verifier-only HTTP/3 AuTest gates that do not execute
curl, so those tests can run when ATS has QUIC support but the installed
curl lacks HTTP/3.
Unknown unidirectional HTTP/3 stream types and aliased frame-type
interests also exposed gaps in shared H3 handling. This discards ignored
stream data and de-duplicates handler registration so buffered input
does not grow indefinitely and MAX_PUSH_ID handlers run once.
* HTTP/3 via OpenSSL QUIC
OpenSSL 3.5 can terminate QUIC connections directly, but ATS only had a
quiche-backed HTTP/3 listener. Operators who want to use the system
OpenSSL QUIC stack needed a separate downstream backend without changing
the existing quiche path or origin HTTP/3 scope.
This adds an optional ENABLE_OPENSSL_QUIC backend that uses OpenSSL's
native QUIC listener and stream APIs for downstream HTTP/3. This keeps
the backend mutually exclusive with quiche, exposes TS_HAS_OPENSSL_QUIC,
and shares ATS's existing HTTP/3 stream handling above the transport.
This also installs native-QUIC TLS callbacks for ALPN and SNI
certificate selection before ATS has a QUIC NetVC to bind. OpenSSL
native QUIC does not make a selected SSL_CTX certificate active via
SSL_set_SSL_CTX alone, so this applies the selected cert, key, and chain
to the connection SSL.
This also broadens client-side HTTP/3 tests to run with either backend,
keeps H3 streams open across informational responses, and hardens
transaction cleanup when OpenSSL closes stream state before ATS finishes
teardown. This caches stream identifiers, declines listener-time QUIC
tickets until a NetVC is bound, and adds focused H3 lifecycle and
session-ticket coverage.
---
CMakeLists.txt | 48 +-
CMakePresets.json | 6 +-
cmake/CheckOpenSSLHasNativeQuic.cmake | 59 ++
cmake/CheckOpenSSLHasQuicTlsCbs.cmake | 55 ++
cmake/Findquiche.cmake | 27 +
include/iocore/net/QUICMultiCertConfigLoader.h | 14 +-
include/iocore/net/TLSSNISupport.h | 1 +
include/iocore/net/quic/QUICConfig.h | 7 +
include/iocore/net/quic/QUICStream.h | 20 +-
include/iocore/net/quic/QUICStreamManager.h | 10 +-
include/proxy/http3/Http3Frame.h | 3 +
include/proxy/http3/Http3HeaderFramer.h | 23 +-
include/proxy/http3/Http3HeaderVIOAdaptor.h | 1 -
include/proxy/http3/Http3Transaction.h | 10 +-
include/tscore/ink_config.h.cmake.in | 1 +
src/iocore/net/CMakeLists.txt | 22 +-
src/iocore/net/OpenSSLQUICNetProcessor.cc | 125 ++++
...VConnection.cc => OpenSSLQUICNetVConnection.cc} | 660 ++++++++++++---------
src/iocore/net/OpenSSLQUICPacketHandler.cc | 630 ++++++++++++++++++++
src/iocore/net/P_QUICNetProcessor.h | 10 +-
src/iocore/net/P_QUICNetVConnection.h | 56 +-
src/iocore/net/P_QUICPacketHandler.h | 22 +-
src/iocore/net/QUICMultiCertConfigLoader.cc | 243 +++++++-
src/iocore/net/QUICNetVConnection.cc | 29 +-
src/iocore/net/SSLSessionTicket.cc | 8 +
src/iocore/net/TLSSNISupport.cc | 10 +
src/iocore/net/quic/CMakeLists.txt | 18 +-
src/iocore/net/quic/OpenSSLQuicCompat.cc | 493 +++++++++++++++
src/iocore/net/quic/QUICConfig.cc | 23 +
src/iocore/net/quic/QUICGlobals.cc | 3 +-
src/iocore/net/quic/QUICStream.cc | 56 +-
src/iocore/net/quic/QUICStreamManager.cc | 28 +-
src/proxy/http3/Http3App.cc | 9 +-
src/proxy/http3/Http3DataFramer.cc | 2 +-
src/proxy/http3/Http3Frame.cc | 6 +
src/proxy/http3/Http3FrameCounter.cc | 3 +-
src/proxy/http3/Http3FrameDispatcher.cc | 8 +-
src/proxy/http3/Http3HeaderFramer.cc | 30 +
src/proxy/http3/Http3ProtocolEnforcer.cc | 4 +-
src/proxy/http3/Http3Transaction.cc | 15 +-
src/proxy/http3/test/test_Http3FrameDispatcher.cc | 49 +-
src/traffic_layout/info.cc | 1 +
src/traffic_server/traffic_server.cc | 2 +-
tests/gold_tests/h3/h3_go_client.test.py | 2 +-
tests/gold_tests/headers/via.test.py | 2 +-
45 files changed, 2492 insertions(+), 362 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ea333aa4dc..e926d8a899 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -215,6 +215,7 @@ set(ENABLE_TPROXY
'X' where X is a number to use as the IP_TRANSPARENT sockopt,
anything else to enable."
)
+option(ENABLE_OPENSSL_QUIC "Use OpenSSL native QUIC (default OFF)")
option(ENABLE_QUICHE "Use quiche (default OFF)")
option(ENABLE_EXAMPLE "Build example directory (default OFF)")
@@ -283,6 +284,8 @@ pkg_check_modules(PCRE2 REQUIRED IMPORTED_TARGET libpcre2-8)
include(CheckOpenSSLIsBoringSSL)
include(CheckOpenSSLIsQuictls)
include(CheckOpenSSLIsAwsLc)
+include(CheckOpenSSLHasQuicTlsCbs)
+include(CheckOpenSSLHasNativeQuic)
find_package(OpenSSL REQUIRED)
check_openssl_is_boringssl(SSLLIB_IS_BORINGSSL BORINGSSL_VERSION
"${OPENSSL_INCLUDE_DIR}")
check_openssl_is_awslc(SSLLIB_IS_AWSLC AWSLC_VERSION "${OPENSSL_INCLUDE_DIR}")
@@ -315,6 +318,18 @@ if(NOT SSLLIB_IS_BORINGSSL
add_compile_definitions(OPENSSL_API_COMPAT=10002 OPENSSL_IS_OPENSSL3)
endif()
+check_openssl_has_quic_tls_cbs(SSLLIB_HAS_QUIC_TLS_CBS
"${OPENSSL_INCLUDE_DIR}")
+if(SSLLIB_HAS_QUIC_TLS_CBS
+ AND NOT SSLLIB_IS_BORINGSSL
+ AND NOT SSLLIB_IS_AWSLC
+ AND NOT SSLLIB_IS_QUICTLS
+)
+ set(TS_OPENSSL_QUIC_TLS_CBS_COMPAT TRUE)
+ add_compile_definitions(TS_OPENSSL_QUIC_TLS_CBS_COMPAT)
+endif()
+
+check_openssl_has_native_quic(SSLLIB_HAS_NATIVE_QUIC "${OPENSSL_INCLUDE_DIR}")
+
if(ENABLE_PROFILER)
find_package(profiler REQUIRED)
set(TS_HAS_PROFILER ${profiler_FOUND})
@@ -330,13 +345,40 @@ elseif(TS_HAS_MIMALLOC)
link_libraries(mimalloc)
endif()
+if(ENABLE_OPENSSL_QUIC AND ENABLE_QUICHE)
+ message(FATAL_ERROR "ENABLE_OPENSSL_QUIC and ENABLE_QUICHE are mutually
exclusive QUIC backends")
+endif()
+
+if(ENABLE_OPENSSL_QUIC)
+ if(NOT SSLLIB_HAS_NATIVE_QUIC)
+ message(FATAL_ERROR "OpenSSL native QUIC support requires OpenSSL 3.5 or
newer with OSSL_QUIC_server_method")
+ endif()
+ if(SSLLIB_IS_BORINGSSL
+ OR SSLLIB_IS_AWSLC
+ OR SSLLIB_IS_QUICTLS
+ )
+ message(FATAL_ERROR "OpenSSL native QUIC support requires upstream OpenSSL
3.5 or newer")
+ endif()
+ set(TS_HAS_OPENSSL_QUIC TRUE)
+ set(TS_USE_QUIC TRUE)
+ message(STATUS "Using OpenSSL native QUIC")
+endif()
+
if(ENABLE_QUICHE)
+ if(TS_OPENSSL_QUIC_TLS_CBS_COMPAT)
+ set(quiche_USE_STATIC TRUE)
+ endif()
find_package(quiche REQUIRED)
set(TS_HAS_QUICHE ${quiche_FOUND})
set(TS_USE_QUIC ${TS_HAS_QUICHE})
- if(NOT SSLLIB_IS_BORINGSSL AND NOT SSLLIB_IS_QUICTLS)
- message(FATAL_ERROR "Use of BoringSSL or OPENSSL/QUICTLS is required if
quiche is used.")
+ if(NOT SSLLIB_IS_BORINGSSL
+ AND NOT SSLLIB_IS_QUICTLS
+ AND NOT TS_OPENSSL_QUIC_TLS_CBS_COMPAT
+ )
+ message(
+ FATAL_ERROR "Use of BoringSSL, OPENSSL/QUICTLS, or OpenSSL QUIC TLS
callbacks is required if quiche is used."
+ )
endif()
if(SSLLIB_IS_QUICTLS)
@@ -345,6 +387,8 @@ if(ENABLE_QUICHE)
message(
"WARNING - Using quictls requires using a special version of quiche.
Make sure quictls is supported in quiche."
)
+ elseif(TS_OPENSSL_QUIC_TLS_CBS_COMPAT)
+ message(STATUS "Using OpenSSL QUIC TLS callbacks compatibility for quiche")
endif()
endif()
diff --git a/CMakePresets.json b/CMakePresets.json
index bfa5cbb8de..a4eff644ea 100644
--- a/CMakePresets.json
+++ b/CMakePresets.json
@@ -191,7 +191,8 @@
"wamr_ROOT": "/opt",
"ENABLE_OTEL_TRACER": "ON",
"ENABLE_WASM_WAMR": "ON",
- "ENABLE_CRIPTS": "ON"
+ "ENABLE_CRIPTS": "ON",
+ "ENABLE_OPENSSL_QUIC": "ON"
}
},
{
@@ -207,7 +208,7 @@
"name": "ci-fedora-quiche",
"displayName": "CI Fedora Quiche",
"description": "CI Pipeline config for Fedora Linux (quiche build)",
- "inherits": ["ci"],
+ "inherits": ["ci-fedora"],
"cacheVariables": {
"OPENSSL_ROOT_DIR": "/opt/h3-tools-boringssl/boringssl",
"quiche_ROOT": "/opt/h3-tools-boringssl/quiche",
@@ -217,6 +218,7 @@
"ENABLE_OTEL_TRACER": "ON",
"ENABLE_WASM_WAMR": "ON",
"CMAKE_INSTALL_PREFIX": "/tmp/ats-quiche",
+ "ENABLE_OPENSSL_QUIC": "OFF",
"ENABLE_QUICHE": "ON"
}
},
diff --git a/cmake/CheckOpenSSLHasNativeQuic.cmake
b/cmake/CheckOpenSSLHasNativeQuic.cmake
new file mode 100644
index 0000000000..28c96acb71
--- /dev/null
+++ b/cmake/CheckOpenSSLHasNativeQuic.cmake
@@ -0,0 +1,59 @@
+#######################
+#
+# 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.
+#
+#######################
+
+function(CHECK_OPENSSL_HAS_NATIVE_QUIC OUT_VAR OPENSSL_INCLUDE_DIR)
+ set(CHECK_PROGRAM
+ "
+ #include <openssl/ssl.h>
+ #include <openssl/quic.h>
+ #include <cstdint>
+
+ int main() {
+ const SSL_METHOD *method = OSSL_QUIC_server_method();
+ SSL *ssl = nullptr;
+ uint64_t value = 0;
+ return method == nullptr ||
+ SSL_new_listener == nullptr ||
+ SSL_listen == nullptr ||
+ SSL_handle_events == nullptr ||
+ SSL_accept_connection == nullptr ||
+ SSL_accept_stream == nullptr ||
+ SSL_new_stream == nullptr ||
+ SSL_stream_conclude == nullptr ||
+ SSL_get_stream_id == nullptr ||
+ SSL_get_stream_type == nullptr ||
+ SSL_get_stream_read_state == nullptr ||
+ SSL_get_stream_write_buf_avail(ssl, &value) ||
+ SSL_get_conn_close_info == nullptr ||
+ SSL_shutdown_ex == nullptr ||
+ SSL_set_default_stream_mode == nullptr ||
+ SSL_set_blocking_mode == nullptr ||
+ SSL_set_event_handling_mode(ssl,
SSL_VALUE_EVENT_HANDLING_MODE_EXPLICIT) ||
+ SSL_set_feature_request_uint(ssl,
SSL_VALUE_QUIC_IDLE_TIMEOUT, value) ||
+ SSL_set_incoming_stream_policy == nullptr;
+ }
+ "
+ )
+ set(CMAKE_REQUIRED_INCLUDES "${OPENSSL_INCLUDE_DIR}")
+ set(CMAKE_REQUIRED_LIBRARIES OpenSSL::SSL OpenSSL::Crypto)
+ include(CheckCXXSourceCompiles)
+ check_cxx_source_compiles("${CHECK_PROGRAM}" ${OUT_VAR})
+ set(${OUT_VAR}
+ ${${OUT_VAR}}
+ PARENT_SCOPE
+ )
+endfunction()
diff --git a/cmake/CheckOpenSSLHasQuicTlsCbs.cmake
b/cmake/CheckOpenSSLHasQuicTlsCbs.cmake
new file mode 100644
index 0000000000..3aec5cf4ce
--- /dev/null
+++ b/cmake/CheckOpenSSLHasQuicTlsCbs.cmake
@@ -0,0 +1,55 @@
+#######################
+#
+# 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.
+#
+#######################
+
+function(CHECK_OPENSSL_HAS_QUIC_TLS_CBS OUT_VAR OPENSSL_INCLUDE_DIR)
+ set(CHECK_PROGRAM
+ "
+ #include <openssl/ssl.h>
+ #include <openssl/core_dispatch.h>
+
+ int main() {
+ OSSL_FUNC_SSL_QUIC_TLS_crypto_send_fn *crypto_send = nullptr;
+ OSSL_FUNC_SSL_QUIC_TLS_crypto_recv_rcd_fn *crypto_recv = nullptr;
+ OSSL_FUNC_SSL_QUIC_TLS_crypto_release_rcd_fn *crypto_release =
nullptr;
+ OSSL_FUNC_SSL_QUIC_TLS_yield_secret_fn *yield_secret = nullptr;
+ OSSL_FUNC_SSL_QUIC_TLS_got_transport_params_fn
*got_transport_params = nullptr;
+ OSSL_FUNC_SSL_QUIC_TLS_alert_fn *alert = nullptr;
+ const OSSL_DISPATCH callbacks[] = {
+ {OSSL_FUNC_SSL_QUIC_TLS_CRYPTO_SEND, reinterpret_cast<void
(*)(void)>(crypto_send)},
+ {OSSL_FUNC_SSL_QUIC_TLS_CRYPTO_RECV_RCD, reinterpret_cast<void
(*)(void)>(crypto_recv)},
+ {OSSL_FUNC_SSL_QUIC_TLS_CRYPTO_RELEASE_RCD,
reinterpret_cast<void (*)(void)>(crypto_release)},
+ {OSSL_FUNC_SSL_QUIC_TLS_YIELD_SECRET, reinterpret_cast<void
(*)(void)>(yield_secret)},
+ {OSSL_FUNC_SSL_QUIC_TLS_GOT_TRANSPORT_PARAMS,
reinterpret_cast<void (*)(void)>(got_transport_params)},
+ {OSSL_FUNC_SSL_QUIC_TLS_ALERT, reinterpret_cast<void
(*)(void)>(alert)},
+ {0, nullptr},
+ };
+
+ return callbacks[0].function_id == 0 ||
+ SSL_set_quic_tls_cbs == nullptr ||
+ SSL_set_quic_tls_transport_params == nullptr;
+ }
+ "
+ )
+ set(CMAKE_REQUIRED_INCLUDES "${OPENSSL_INCLUDE_DIR}")
+ set(CMAKE_REQUIRED_LIBRARIES OpenSSL::SSL OpenSSL::Crypto)
+ include(CheckCXXSourceCompiles)
+ check_cxx_source_compiles("${CHECK_PROGRAM}" ${OUT_VAR})
+ set(${OUT_VAR}
+ ${${OUT_VAR}}
+ PARENT_SCOPE
+ )
+endfunction()
diff --git a/cmake/Findquiche.cmake b/cmake/Findquiche.cmake
index fcb3c61f27..4cead8bbdf 100644
--- a/cmake/Findquiche.cmake
+++ b/cmake/Findquiche.cmake
@@ -28,7 +28,24 @@
# quiche::quiche
#
+if(quiche_USE_STATIC
+ AND quiche_LIBRARY
+ AND NOT quiche_LIBRARY MATCHES "\\${CMAKE_STATIC_LIBRARY_SUFFIX}$"
+)
+ unset(quiche_LIBRARY CACHE)
+endif()
+
+if(quiche_USE_STATIC)
+ set(_quiche_ORIGINAL_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES})
+ set(CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_STATIC_LIBRARY_SUFFIX})
+endif()
+
find_library(quiche_LIBRARY NAMES quiche)
+
+if(quiche_USE_STATIC)
+ set(CMAKE_FIND_LIBRARY_SUFFIXES ${_quiche_ORIGINAL_FIND_LIBRARY_SUFFIXES})
+endif()
+
find_path(
quiche_INCLUDE_DIR
NAMES quiche.h
@@ -40,8 +57,18 @@ mark_as_advanced(quiche_FOUND quiche_LIBRARY
quiche_INCLUDE_DIR)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(quiche REQUIRED_VARS quiche_LIBRARY
quiche_INCLUDE_DIR)
+if(quiche_USE_STATIC
+ AND quiche_FOUND
+ AND NOT quiche_LIBRARY MATCHES "\\${CMAKE_STATIC_LIBRARY_SUFFIX}$"
+)
+ message(FATAL_ERROR "Static quiche was requested, but ${quiche_LIBRARY} is
not a static library")
+endif()
+
if(quiche_FOUND)
set(quiche_INCLUDE_DIRS "${quiche_INCLUDE_DIR}")
+ if(quiche_USE_STATIC)
+ message(STATUS "Using static quiche library: ${quiche_LIBRARY}")
+ endif()
endif()
if(quiche_FOUND AND NOT TARGET quiche::quiche)
diff --git a/include/iocore/net/QUICMultiCertConfigLoader.h
b/include/iocore/net/QUICMultiCertConfigLoader.h
index 4dad3313a6..6ae299a510 100644
--- a/include/iocore/net/QUICMultiCertConfigLoader.h
+++ b/include/iocore/net/QUICMultiCertConfigLoader.h
@@ -25,6 +25,9 @@
#include "iocore/net/SSLMultiCertConfigLoader.h"
#include "iocore/eventsystem/ConfigProcessor.h"
+#include "tscore/ink_config.h"
+
+#include <string_view>
class QUICCertConfig
{
@@ -40,6 +43,10 @@ private:
static int _config_id;
};
+#if TS_HAS_OPENSSL_QUIC
+std::string_view quic_sni_server_name(SSL *ssl);
+#endif
+
class QUICMultiCertConfigLoader : public SSLMultiCertConfigLoader
{
public:
@@ -48,7 +55,12 @@ public:
virtual SSL_CTX *default_server_ssl_ctx() override;
private:
- const char *_debug_tag() const override;
+ const char *_debug_tag() const override;
+#if TS_HAS_OPENSSL_QUIC
+ virtual void _set_handshake_callbacks(SSL_CTX *ctx) override;
+ virtual bool _set_alpn_callback(SSL_CTX *ctx) override;
+ virtual bool _set_curves(SSL_CTX *ctx) override;
+#endif
virtual bool _setup_session_cache(SSL_CTX *ctx) override;
virtual bool _set_cipher_suites_for_legacy_versions(SSL_CTX *ctx) override;
virtual bool _set_info_callback(SSL_CTX *ctx) override;
diff --git a/include/iocore/net/TLSSNISupport.h
b/include/iocore/net/TLSSNISupport.h
index 3e30f7aaf2..17ff9ed28e 100644
--- a/include/iocore/net/TLSSNISupport.h
+++ b/include/iocore/net/TLSSNISupport.h
@@ -123,6 +123,7 @@ public:
* @return True if the servername was set successfully, false otherwise.
*/
bool set_sni_server_name(SSL *ssl, char const *name);
+ void set_sni_server_name(std::string_view name);
/**
* Get the server name in SNI
diff --git a/include/iocore/net/quic/QUICConfig.h
b/include/iocore/net/quic/QUICConfig.h
index 1ebec4f6aa..b0b1ea25b2 100644
--- a/include/iocore/net/quic/QUICConfig.h
+++ b/include/iocore/net/quic/QUICConfig.h
@@ -24,7 +24,11 @@
#pragma once
#include <openssl/ssl.h>
+#include "tscore/ink_config.h"
+
+#if TS_HAS_QUICHE
#include <quiche.h>
+#endif
#include "iocore/eventsystem/ConfigProcessor.h"
#include "iocore/net/SSLTypes.h"
@@ -89,7 +93,9 @@ public:
bool disable_http_0_9() const;
+#if TS_HAS_QUICHE
quiche_cc_algorithm get_cc_algorithm() const;
+#endif
private:
static int _connection_table_size;
@@ -163,3 +169,4 @@ private:
};
SSL_CTX *quic_new_ssl_ctx();
+SSL_CTX *quic_new_server_ssl_ctx();
diff --git a/include/iocore/net/quic/QUICStream.h
b/include/iocore/net/quic/QUICStream.h
index ef84c9cb6a..e57c49b6dc 100644
--- a/include/iocore/net/quic/QUICStream.h
+++ b/include/iocore/net/quic/QUICStream.h
@@ -31,11 +31,25 @@
#include "iocore/net/quic/QUICConnection.h"
#include "iocore/net/quic/QUICDebugNames.h"
-#include <quiche.h>
+#include <cstddef>
+#include <cstdint>
class QUICStreamAdapter;
class QUICStreamStateListener;
+class QUICStreamIO
+{
+public:
+ using ErrorCode = uint64_t;
+
+ virtual ~QUICStreamIO() = default;
+
+ virtual int64_t read_stream(QUICStreamId stream_id, uint8_t *buf, size_t
len, bool &fin, ErrorCode &error_code) = 0;
+ virtual bool stream_read_finished(QUICStreamId stream_id)
= 0;
+ virtual int64_t stream_write_capacity(QUICStreamId stream_id)
= 0;
+ virtual int64_t write_stream(QUICStreamId stream_id, uint8_t const *buf,
size_t len, bool fin, ErrorCode &error_code) = 0;
+};
+
/**
* @brief QUIC Stream
* TODO: This is similar to Http2Stream. Need to think some integration.
@@ -61,8 +75,8 @@ public:
void stop_sending(QUICStreamErrorUPtr error);
void reset(QUICStreamErrorUPtr error);
- void receive_data(quiche_conn *quiche_con);
- void send_data(quiche_conn *quiche_con);
+ void receive_data(QUICStreamIO &stream_io);
+ int64_t send_data(QUICStreamIO &stream_io);
/*
* QUICApplication need to call one of these functions when it process
VC_EVENT_*
diff --git a/include/iocore/net/quic/QUICStreamManager.h
b/include/iocore/net/quic/QUICStreamManager.h
index 78985a7d3c..c79704a9b7 100644
--- a/include/iocore/net/quic/QUICStreamManager.h
+++ b/include/iocore/net/quic/QUICStreamManager.h
@@ -48,10 +48,10 @@ public:
QUICStream *create_stream(QUICStreamId stream_id, QUICConnectionError &err);
- QUICConnectionErrorUPtr create_uni_stream(QUICStreamId new_stream_id);
- QUICConnectionErrorUPtr create_bidi_stream(QUICStreamId new_stream_id);
- QUICConnectionErrorUPtr delete_stream(QUICStreamId new_stream_id);
- void reset_stream(QUICStreamId stream_id,
QUICStreamErrorUPtr error);
+ virtual QUICConnectionErrorUPtr create_uni_stream(QUICStreamId
&new_stream_id);
+ virtual QUICConnectionErrorUPtr create_bidi_stream(QUICStreamId
&new_stream_id);
+ QUICConnectionErrorUPtr delete_stream(QUICStreamId new_stream_id);
+ void reset_stream(QUICStreamId stream_id,
QUICStreamErrorUPtr error);
void set_default_application(QUICApplication *app);
@@ -63,4 +63,6 @@ public:
protected:
QUICContext *_context = nullptr;
QUICApplicationMap *_app_map = nullptr;
+ QUICStreamId _next_local_bidi_stream_id{0};
+ QUICStreamId _next_local_uni_stream_id{0};
};
diff --git a/include/proxy/http3/Http3Frame.h b/include/proxy/http3/Http3Frame.h
index cd8d5e860e..a025549638 100644
--- a/include/proxy/http3/Http3Frame.h
+++ b/include/proxy/http3/Http3Frame.h
@@ -98,6 +98,9 @@ private:
// Head of IOBufferBlock chain to send
Ptr<IOBufferBlock> _whole_frame;
uint64_t _payload_len = 0;
+
+protected:
+ bool _parse() override;
};
//
diff --git a/include/proxy/http3/Http3HeaderFramer.h
b/include/proxy/http3/Http3HeaderFramer.h
index 548f88368d..6dcd4a7d3a 100644
--- a/include/proxy/http3/Http3HeaderFramer.h
+++ b/include/proxy/http3/Http3HeaderFramer.h
@@ -44,16 +44,21 @@ public:
Http3FrameUPtr generate_frame() override;
bool is_done() const override;
+ void reset();
+ bool is_final_header_sent() const;
+
private:
- Http3Transaction *_transaction = nullptr;
- VIO *_source_vio = nullptr;
- QPACK *_qpack = nullptr;
- MIOBuffer *_header_block = nullptr;
- IOBufferReader *_header_block_reader = nullptr;
- uint64_t _header_block_len = 0;
- uint64_t _header_block_wrote = 0;
- uint64_t _stream_id = 0;
- bool _sent_all_data = false;
+ Http3Transaction *_transaction = nullptr;
+ VIO *_source_vio = nullptr;
+ QPACK *_qpack = nullptr;
+ MIOBuffer *_header_block = nullptr;
+ IOBufferReader *_header_block_reader = nullptr;
+ uint64_t _header_block_len = 0;
+ uint64_t _header_block_wrote = 0;
+ uint64_t _stream_id = 0;
+ bool _sent_all_data = false;
+ bool _is_current_header_final = false;
+ bool _is_final_header_sent = false;
HTTPParser _http_parser;
HTTPHdr _header;
VersionConverter _hvc;
diff --git a/include/proxy/http3/Http3HeaderVIOAdaptor.h
b/include/proxy/http3/Http3HeaderVIOAdaptor.h
index 59a02b9a15..13b30f2314 100644
--- a/include/proxy/http3/Http3HeaderVIOAdaptor.h
+++ b/include/proxy/http3/Http3HeaderVIOAdaptor.h
@@ -28,7 +28,6 @@
#include "proxy/http3/Http3FrameHandler.h"
class HQTransaction;
-
class Http3HeaderVIOAdaptor : public Continuation, public Http3FrameHandler
{
public:
diff --git a/include/proxy/http3/Http3Transaction.h
b/include/proxy/http3/Http3Transaction.h
index b253ecbb8b..5adb9bda17 100644
--- a/include/proxy/http3/Http3Transaction.h
+++ b/include/proxy/http3/Http3Transaction.h
@@ -130,8 +130,10 @@ public:
Http3Transaction(Http3Session *session, QUICStreamVCAdapter::IOInfo &info);
virtual ~Http3Transaction();
- int state_stream_open(int event, Event *data) override;
- int state_stream_closed(int event, Event *data) override;
+ VIO *do_io_write(Continuation *c = nullptr, int64_t nbytes = INT64_MAX,
IOBufferReader *buf = nullptr,
+ bool owner = false) override;
+ int state_stream_open(int event, Event *data) override;
+ int state_stream_closed(int event, Event *data) override;
void do_io_close(int lerrno = -1) override;
void on_header_decode_complete();
@@ -152,8 +154,8 @@ private:
Http3FrameDispatcher _frame_dispatcher;
Http3FrameCollector _frame_collector;
Http3ProtocolEnforcer *_protocol_enforcer = nullptr;
- Http3FrameGenerator *_header_framer = nullptr;
- Http3FrameGenerator *_data_framer = nullptr;
+ Http3HeaderFramer *_header_framer = nullptr;
+ Http3DataFramer *_data_framer = nullptr;
Http3HeaderVIOAdaptor *_header_handler = nullptr;
Http3StreamDataVIOAdaptor *_data_handler = nullptr;
};
diff --git a/include/tscore/ink_config.h.cmake.in
b/include/tscore/ink_config.h.cmake.in
index 73c8b860fb..3898e3e7dc 100644
--- a/include/tscore/ink_config.h.cmake.in
+++ b/include/tscore/ink_config.h.cmake.in
@@ -145,6 +145,7 @@ const int DEFAULT_STACKSIZE = @DEFAULT_STACK_SIZE@;
#cmakedefine01 TS_HAS_JEMALLOC
#cmakedefine01 TS_HAS_MIMALLOC
#cmakedefine01 TS_HAS_PROFILER
+#cmakedefine01 TS_HAS_OPENSSL_QUIC
#cmakedefine01 TS_HAS_QUICHE
#cmakedefine01 TS_HAS_SO_MARK
#cmakedefine01 TS_HAS_SO_PEERCRED
diff --git a/src/iocore/net/CMakeLists.txt b/src/iocore/net/CMakeLists.txt
index d1d5875ab4..6ad1804a91 100644
--- a/src/iocore/net/CMakeLists.txt
+++ b/src/iocore/net/CMakeLists.txt
@@ -81,18 +81,17 @@ if(TS_USE_QUIC)
add_subdirectory(quic)
target_sources(
- inknet
- PRIVATE QUICClosedConCollector.cc
- QUICMultiCertConfigLoader.cc
- QUICNet.cc
- QUICNetProcessor.cc
- QUICNetVConnection.cc
- QUICNextProtocolAccept.cc
- QUICPacketHandler.cc
- QUICSupport.cc
+ inknet PRIVATE QUICClosedConCollector.cc QUICMultiCertConfigLoader.cc
QUICNextProtocolAccept.cc QUICSupport.cc
)
- target_link_libraries(inknet PUBLIC quiche::quiche ts::quic)
+ if(TS_HAS_OPENSSL_QUIC)
+ target_sources(inknet PRIVATE OpenSSLQUICNetProcessor.cc
OpenSSLQUICNetVConnection.cc OpenSSLQUICPacketHandler.cc)
+ elseif(TS_HAS_QUICHE)
+ target_sources(inknet PRIVATE QUICNet.cc QUICNetProcessor.cc
QUICNetVConnection.cc QUICPacketHandler.cc)
+ target_link_libraries(inknet PUBLIC quiche::quiche)
+ endif()
+
+ target_link_libraries(inknet PUBLIC ts::quic)
endif()
if(BUILD_REGRESSION_TESTING OR BUILD_TESTING)
@@ -168,6 +167,9 @@ if(BUILD_TESTING)
)
if(TS_USE_QUIC)
list(APPEND LINK_GROUP_LIBS quic http3)
+ if(TS_HAS_QUICHE)
+ list(APPEND LINK_GROUP_LIBS quiche::quiche)
+ endif()
endif()
if(CMAKE_LINK_GROUP_USING_RESCAN_SUPPORTED OR
CMAKE_CXX_LINK_GROUP_USING_RESCAN_SUPPORTED)
string(JOIN "," LINK_GROUP_LIBS_CSV ${LINK_GROUP_LIBS})
diff --git a/src/iocore/net/OpenSSLQUICNetProcessor.cc
b/src/iocore/net/OpenSSLQUICNetProcessor.cc
new file mode 100644
index 0000000000..bbaa562551
--- /dev/null
+++ b/src/iocore/net/OpenSSLQUICNetProcessor.cc
@@ -0,0 +1,125 @@
+/** @file
+
+ OpenSSL native QUIC NetProcessor support.
+
+ @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 "P_Net.h"
+#include "P_QUICNetProcessor.h"
+#include "P_QUICPacketHandler.h"
+#include "P_QUICNetVConnection.h"
+#include "P_UnixNet.h"
+#include "iocore/net/quic/QUICConfig.h"
+#include "iocore/net/quic/QUICGlobals.h"
+#include "iocore/net/QUICMultiCertConfigLoader.h"
+
+#include <cerrno>
+
+QUICNetProcessor quic_NetProcessor;
+
+namespace
+{
+DbgCtl dbg_ctl_quic_ps{"quic_ps"};
+DbgCtl dbg_ctl_iocore_net_processor{"iocore_net_processor"};
+} // end anonymous namespace
+
+QUICNetProcessor::QUICNetProcessor() {}
+
+QUICNetProcessor::~QUICNetProcessor() {}
+
+void
+QUICNetProcessor::init()
+{
+}
+
+int
+QUICNetProcessor::start(int, size_t /* stacksize ATS_UNUSED */)
+{
+ QUIC::init();
+ QUICConfig::startup();
+ QUICCertConfig::startup();
+
+ return 0;
+}
+
+NetAccept *
+QUICNetProcessor::createNetAccept(NetProcessor::AcceptOptions const &opt)
+{
+ return new QUICPacketHandlerIn(opt);
+}
+
+NetVConnection *
+QUICNetProcessor::allocate_vc(EThread *t)
+{
+ QUICNetVConnection *vc = nullptr;
+
+ if (t) {
+ vc = THREAD_ALLOC(quicNetVCAllocator, t);
+ new (vc) QUICNetVConnection();
+ } else if (likely(vc = quicNetVCAllocator.alloc())) {
+ new (vc) QUICNetVConnection();
+ vc->from_accept_thread = true;
+ }
+
+ if (vc != nullptr) {
+ vc->ep.syscall = false;
+ }
+
+ return vc;
+}
+
+Action *
+QUICNetProcessor::connect_re(Continuation *cont, sockaddr const * /*
remote_addr ATS_UNUSED */,
+ NetVCOptions const & /* opt ATS_UNUSED */)
+{
+ Dbg(dbg_ctl_quic_ps, "OpenSSL native QUIC origin connections are not
supported");
+ cont->handleEvent(NET_EVENT_OPEN_FAILED, reinterpret_cast<void *>(-ENOTSUP));
+ return ACTION_IO_ERROR;
+}
+
+Action *
+QUICNetProcessor::main_accept(Continuation *cont, SOCKET fd, AcceptOptions
const &opt)
+{
+ Dbg(dbg_ctl_iocore_net_processor, "NetProcessor::main_accept - port
%d,recv_bufsize %d, send_bufsize %d, sockopt 0x%0x",
+ opt.local_port, opt.recv_bufsize, opt.send_bufsize, opt.sockopt_flags);
+
+ IpEndpoint accept_ip;
+ NetAccept *na = createNetAccept(opt);
+
+ Metrics::Gauge::increment(net_rsb.accepts_currently_open);
+
+ if (opt.localhost_only) {
+ accept_ip.setToLoopback(opt.ip_family);
+ } else if (opt.local_ip.isValid()) {
+ accept_ip.assign(opt.local_ip);
+ } else {
+ accept_ip.setToAnyAddr(opt.ip_family);
+ }
+ ink_assert(0 < opt.local_port && opt.local_port < 65536);
+ accept_ip.network_order_port() = htons(opt.local_port);
+
+ na->server.sock = UnixSocket{fd};
+ ats_ip_copy(&na->server.accept_addr, &accept_ip);
+
+ na->action_ = new NetAcceptAction(cont, &na->server);
+ na->init_accept();
+
+ return na->action_.get();
+}
diff --git a/src/iocore/net/QUICNetVConnection.cc
b/src/iocore/net/OpenSSLQUICNetVConnection.cc
similarity index 53%
copy from src/iocore/net/QUICNetVConnection.cc
copy to src/iocore/net/OpenSSLQUICNetVConnection.cc
index 18a8591373..9a06fb94d2 100644
--- a/src/iocore/net/QUICNetVConnection.cc
+++ b/src/iocore/net/OpenSSLQUICNetVConnection.cc
@@ -1,6 +1,6 @@
/** @file
- A brief file description
+ OpenSSL native QUIC NetVConnection support.
@section license License
@@ -28,24 +28,54 @@
#include "api/APIHook.h"
#include "iocore/eventsystem/EThread.h"
#include "iocore/net/QUICMultiCertConfigLoader.h"
-#include "iocore/net/UDPPacket.h"
+#include "iocore/net/SSLAPIHooks.h"
+#include "iocore/net/quic/QUICApplicationMap.h"
#include "iocore/net/quic/QUICEvents.h"
-#include "iocore/net/quic/QUICStream.h"
#include "iocore/net/quic/QUICGlobals.h"
-#include "iocore/net/SSLAPIHooks.h"
+#include "iocore/net/quic/QUICStream.h"
+#include "iocore/net/quic/QUICStreamManager.h"
#include "tscore/ink_config.h"
-#include <netinet/in.h>
-#include <quiche.h>
+#include <openssl/err.h>
+#include <openssl/quic.h>
+#include <openssl/ssl.h>
+
+#include <algorithm>
+#include <cerrno>
#include <cstring>
+#include <vector>
namespace
{
-constexpr ink_hrtime WRITE_READY_INTERVAL = HRTIME_MSECONDS(2);
+constexpr ink_hrtime OPENSSL_QUIC_EVENT_INTERVAL = HRTIME_MSECONDS(2);
DbgCtl dbg_ctl_quic_net{"quic_net"};
DbgCtl dbg_ctl_v_quic_net{"v_quic_net"};
+class OpenSSLQUICStreamManager : public QUICStreamManager
+{
+public:
+ OpenSSLQUICStreamManager(QUICContext *context, QUICApplicationMap *app_map,
QUICNetVConnection *vc)
+ : QUICStreamManager(context, app_map), _vc(vc)
+ {
+ }
+
+ QUICConnectionErrorUPtr
+ create_uni_stream(QUICStreamId &new_stream_id) override
+ {
+ return this->_vc->create_openssl_stream(SSL_STREAM_FLAG_UNI |
SSL_STREAM_FLAG_NO_BLOCK, new_stream_id);
+ }
+
+ QUICConnectionErrorUPtr
+ create_bidi_stream(QUICStreamId &new_stream_id) override
+ {
+ return this->_vc->create_openssl_stream(SSL_STREAM_FLAG_NO_BLOCK,
new_stream_id);
+ }
+
+private:
+ QUICNetVConnection *_vc = nullptr;
+};
+
} // end anonymous namespace
#define QUICConDebug(fmt, ...) Dbg(dbg_ctl_quic_net, "[%s] " fmt,
this->cids().data(), ##__VA_ARGS__)
@@ -67,34 +97,65 @@ QUICNetVConnection::QUICNetVConnection()
QUICNetVConnection::~QUICNetVConnection() {}
void
-QUICNetVConnection::init(QUICVersion /* version ATS_UNUSED */,
QUICConnectionId /* peer_cid ATS_UNUSED */,
- QUICConnectionId /* original_cid ATS_UNUSED */,
UDPConnection *, QUICPacketHandler *)
+QUICNetVConnection::init(SSL *ssl, QUICPacketHandler *packet_handler)
{
+ SET_HANDLER((NetVConnHandler)&QUICNetVConnection::acceptEvent);
+
+ this->_ssl = ssl;
+ this->_packet_handler = packet_handler;
+ this->_quic_connection_id.randomize();
+ this->_initial_source_connection_id = this->_quic_connection_id;
+ this->_cid_text = this->_quic_connection_id.hex();
+
+ SSL_set_ex_data(ssl, QUIC::ssl_quic_qc_index, static_cast<QUICConnection
*>(this));
+ SSL_set_blocking_mode(ssl, 0);
+ SSL_set_event_handling_mode(ssl, SSL_VALUE_EVENT_HANDLING_MODE_IMPLICIT);
+ SSL_set_incoming_stream_policy(ssl, SSL_INCOMING_STREAM_POLICY_ACCEPT, 0);
+ SSL_set_default_stream_mode(ssl, SSL_DEFAULT_STREAM_MODE_NONE);
+
+ QUICConfig::scoped_config params;
+ SSL_set_feature_request_uint(ssl, SSL_VALUE_QUIC_IDLE_TIMEOUT,
params->no_activity_timeout_in());
+ SSL_set_feature_request_uint(ssl, SSL_VALUE_QUIC_STREAM_BIDI_LOCAL_AVAIL,
params->initial_max_streams_bidi_in());
+ SSL_set_feature_request_uint(ssl, SSL_VALUE_QUIC_STREAM_UNI_LOCAL_AVAIL,
params->initial_max_streams_uni_in());
+
+ this->_bindSSLObject();
+ if (auto const servername = quic_sni_server_name(ssl); !servername.empty()) {
+ this->set_sni_server_name(servername);
+ }
}
void
-QUICNetVConnection::init(QUICVersion /* version ATS_UNUSED */,
QUICConnectionId /* peer_cid ATS_UNUSED */,
- QUICConnectionId original_cid, QUICConnectionId /*
first_cid ATS_UNUSED */,
- QUICConnectionId /* retry_cid ATS_UNUSED */,
UDPConnection *udp_con, quiche_conn *quiche_con,
- QUICPacketHandler *packet_handler,
QUICConnectionTable *ctable, SSL *ssl)
+QUICNetVConnection::set_quic_endpoints(IpEndpoint const &local, IpEndpoint
const &remote)
{
- SET_HANDLER((NetVConnHandler)&QUICNetVConnection::acceptEvent);
- this->_udp_con = udp_con;
- this->_quiche_con = quiche_con;
- this->_packet_handler = packet_handler;
- this->_original_quic_connection_id = original_cid;
- this->_quic_connection_id.randomize();
- this->_initial_source_connection_id = this->_quic_connection_id;
+ this->local_addr = local;
+ this->remote_addr = remote;
+ this->got_local_addr = true;
+ this->got_remote_addr = true;
+}
- if (ctable) {
- this->_ctable = ctable;
- this->_ctable->insert(this->_quic_connection_id, this);
- this->_ctable->insert(this->_original_quic_connection_id, this);
+QUICConnectionErrorUPtr
+QUICNetVConnection::create_openssl_stream(uint64_t flags, QUICStreamId
&new_stream_id)
+{
+ if (this->_ssl == nullptr) {
+ return
std::make_unique<QUICConnectionError>(QUICTransErrorCode::INTERNAL_ERROR, "QUIC
connection is not initialized");
}
- this->_ssl = ssl;
- SSL_set_ex_data(ssl, QUIC::ssl_quic_qc_index, static_cast<QUICConnection
*>(this));
- this->_bindSSLObject();
+ SSL *stream_ssl = SSL_new_stream(this->_ssl, flags);
+ if (stream_ssl == nullptr) {
+ return
std::make_unique<QUICConnectionError>(QUICTransErrorCode::INTERNAL_ERROR,
"Failed to create QUIC stream");
+ }
+
+ SSL_set_blocking_mode(stream_ssl, 0);
+ SSL_set_event_handling_mode(stream_ssl,
SSL_VALUE_EVENT_HANDLING_MODE_IMPLICIT);
+ SSL_set_mode(stream_ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER |
SSL_MODE_ENABLE_PARTIAL_WRITE);
+
+ new_stream_id = SSL_get_stream_id(stream_ssl);
+ this->_openssl_streams.emplace(new_stream_id, stream_ssl);
+
+ [[maybe_unused]] QUICConnectionError err;
+ this->_stream_manager->create_stream(new_stream_id, err);
+
+ return nullptr;
}
void
@@ -103,17 +164,11 @@ QUICNetVConnection::free()
this->free_thread(this_ethread());
}
-// called by ET_UDP
void
QUICNetVConnection::remove_connection_ids()
{
- if (this->_ctable) {
- this->_ctable->erase(this->_quic_connection_id, this);
- this->_ctable->erase(this->_original_quic_connection_id, this);
- }
}
-// called by ET_UDP
void
QUICNetVConnection::destroy(EThread *t)
{
@@ -135,13 +190,18 @@ QUICNetVConnection::free_thread(EThread * /* t ATS_UNUSED
*/)
{
QUICConDebug("Free connection");
- this->_udp_con = nullptr;
+ this->_unschedule_openssl_event();
- quiche_conn_free(this->_quiche_con);
- this->_quiche_con = nullptr;
+ for (auto &[stream_id, stream_ssl] : this->_openssl_streams) {
+ SSL_free(stream_ssl);
+ }
+ this->_openssl_streams.clear();
- this->_unschedule_quiche_timeout();
- this->_unschedule_packet_write_ready();
+ if (this->_ssl != nullptr) {
+ this->_unbindSSLObject();
+ SSL_free(this->_ssl);
+ this->_ssl = nullptr;
+ }
this->_application_map.reset();
this->_stream_manager.reset();
@@ -152,8 +212,10 @@ QUICNetVConnection::free_thread(EThread * /* t ATS_UNUSED
*/)
TLSEventSupport::clear();
TLSCertSwitchSupport::_clear();
- this->_packet_handler->close_connection(this);
- this->_packet_handler = nullptr;
+ if (this->_packet_handler != nullptr) {
+ this->_packet_handler->close_connection(this);
+ this->_packet_handler = nullptr;
+ }
}
void
@@ -164,30 +226,22 @@ QUICNetVConnection::reenable(VIO * /* vio ATS_UNUSED */)
int
QUICNetVConnection::state_handshake(int event, Event *data)
{
- if (quiche_conn_is_established(this->_quiche_con)) {
- this->_switch_to_established_state();
- return this->handleEvent(event, data);
+ if (data == this->_packet_write_ready) {
+ this->_packet_write_ready = nullptr;
}
switch (event) {
+ case EVENT_IMMEDIATE:
+ case EVENT_INTERVAL:
case QUIC_EVENT_PACKET_READ_READY:
- this->_handle_read_ready();
- break;
case QUIC_EVENT_PACKET_WRITE_READY:
- this->_close_packet_write_ready(data);
- this->_handle_write_ready();
- // Reschedule WRITE_READY
- this->_schedule_packet_write_ready(true);
- break;
- case EVENT_INTERVAL:
- this->_close_quiche_timeout(data);
- this->_handle_interval();
+ this->_handle_openssl_events();
break;
case VC_EVENT_EOS:
case VC_EVENT_ERROR:
case VC_EVENT_ACTIVE_TIMEOUT:
case VC_EVENT_INACTIVITY_TIMEOUT:
- _unschedule_packet_write_ready();
+ this->_unschedule_openssl_event();
this->_propagate_event(event);
this->closed = 1;
break;
@@ -196,8 +250,15 @@ QUICNetVConnection::state_handshake(int event, Event *data)
break;
}
- if (this->closed != 1 && (quiche_conn_is_closed(this->_quiche_con) ||
quiche_conn_is_draining(this->_quiche_con))) {
+ if (this->closed != 1 && SSL_is_init_finished(this->_ssl)) {
+ this->_switch_to_established_state();
+ this->_handle_openssl_events();
+ }
+
+ if (this->closed != 1 && this->_openssl_connection_closed()) {
this->_schedule_closing_event();
+ } else if (this->closed != 1) {
+ this->_schedule_openssl_event();
}
return EVENT_DONE;
@@ -206,29 +267,26 @@ QUICNetVConnection::state_handshake(int event, Event
*data)
int
QUICNetVConnection::state_established(int event, Event *data)
{
- if (!this->_quiche_con) {
- // Connection has been closed.
+ if (this->_ssl == nullptr) {
return EVENT_DONE;
}
+
+ if (data == this->_packet_write_ready) {
+ this->_packet_write_ready = nullptr;
+ }
+
switch (event) {
+ case EVENT_IMMEDIATE:
+ case EVENT_INTERVAL:
case QUIC_EVENT_PACKET_READ_READY:
- this->_handle_read_ready();
- break;
case QUIC_EVENT_PACKET_WRITE_READY:
- this->_close_packet_write_ready(data);
- this->_handle_write_ready();
- // Reschedule WRITE_READY
- this->_schedule_packet_write_ready(true);
- break;
- case EVENT_INTERVAL:
- this->_close_quiche_timeout(data);
- this->_handle_interval();
+ this->_handle_openssl_events();
break;
case VC_EVENT_EOS:
case VC_EVENT_ERROR:
case VC_EVENT_ACTIVE_TIMEOUT:
case VC_EVENT_INACTIVITY_TIMEOUT:
- _unschedule_packet_write_ready();
+ this->_unschedule_openssl_event();
this->_propagate_event(event);
this->closed = 1;
break;
@@ -237,8 +295,10 @@ QUICNetVConnection::state_established(int event, Event
*data)
break;
}
- if (this->closed != 1 && (quiche_conn_is_closed(this->_quiche_con) ||
quiche_conn_is_draining(this->_quiche_con))) {
+ if (this->closed != 1 && this->_openssl_connection_closed()) {
this->_schedule_closing_event();
+ } else if (this->closed != 1) {
+ this->_schedule_openssl_event();
}
return EVENT_DONE;
@@ -251,35 +311,37 @@ QUICNetVConnection::_switch_to_established_state()
this->_record_tls_handshake_end_time();
this->_update_end_of_handshake_stats();
SET_HANDLER((NetVConnHandler)&QUICNetVConnection::state_established);
- this->_start_application();
this->_handshake_completed = true;
+ this->_start_application();
}
void
QUICNetVConnection::_start_application()
{
- if (!this->_application_started) {
- this->_application_started = true;
+ if (this->_application_started) {
+ return;
+ }
- const uint8_t *app_name;
- size_t app_name_len = 0;
- quiche_conn_application_proto(this->_quiche_con, &app_name, &app_name_len);
- if (app_name == nullptr) {
- app_name = reinterpret_cast<const uint8_t
*>(IP_PROTO_TAG_HTTP_QUIC.data());
- app_name_len = IP_PROTO_TAG_HTTP_QUIC.size();
- }
+ this->_application_started = true;
- this->set_negotiated_protocol_id({reinterpret_cast<const char
*>(app_name), static_cast<size_t>(app_name_len)});
+ unsigned char const *app_name = nullptr;
+ unsigned int app_name_len = 0;
+ SSL_get0_alpn_selected(this->_ssl, &app_name, &app_name_len);
- if (netvc_context == NET_VCONNECTION_IN) {
- if (!this->setSelectedProtocol(app_name, app_name_len)) {
- //
this->_handle_error(std::make_unique<QUICConnectionError>(QUICTransErrorCode::PROTOCOL_VIOLATION));
- } else {
- this->endpoint()->handleEvent(NET_EVENT_ACCEPT, this);
- }
- } else {
- this->action_.continuation->handleEvent(NET_EVENT_OPEN, this);
+ if (app_name == nullptr || app_name_len == 0) {
+ app_name = reinterpret_cast<unsigned char const
*>(IP_PROTO_TAG_HTTP_QUIC.data());
+ app_name_len = IP_PROTO_TAG_HTTP_QUIC.size();
+ }
+
+ this->_negotiated_alpn.assign(reinterpret_cast<char const *>(app_name),
app_name_len);
+ this->set_negotiated_protocol_id(this->_negotiated_alpn);
+
+ if (netvc_context == NET_VCONNECTION_IN) {
+ if (this->setSelectedProtocol(app_name, app_name_len)) {
+ this->endpoint()->handleEvent(NET_EVENT_ACCEPT, this);
}
+ } else {
+ this->action_.continuation->handleEvent(NET_EVENT_OPEN, this);
}
}
@@ -292,7 +354,6 @@ QUICNetVConnection::_propagate_event(int event)
} else if (this->write.vio.cont && this->write.vio.mutex ==
this->write.vio.cont->mutex) {
this->write.vio.cont->handleEvent(event, &this->write.vio);
} else {
- // Proxy Session does not exist
QUICConVDebug("Session does not exist");
}
}
@@ -316,6 +377,7 @@ QUICNetVConnection::do_io_write(Continuation *c, int64_t
nbytes, IOBufferReader
{
auto vio = super::do_io_write(c, nbytes, buf);
this->write.enabled = 1;
+ this->_schedule_openssl_event(false);
return vio;
}
@@ -338,28 +400,21 @@ QUICNetVConnection::acceptEvent(int event, Event *e)
this->_context = std::make_unique<QUICContext>(this);
this->_application_map = std::make_unique<QUICApplicationMap>();
- this->_stream_manager =
std::make_unique<QUICStreamManager>(this->_context.get(),
this->_application_map.get());
+ this->_stream_manager =
std::make_unique<OpenSSLQUICStreamManager>(this->_context.get(),
this->_application_map.get(), this);
- // this->thread is already assigned by QUICPacketHandlerIn::_recv_packet
ink_assert(this->thread == this_ethread());
- // Send this NetVC to NetHandler and start to polling read & write event.
if (h->startIO(this) < 0) {
this->free_thread(t);
return EVENT_DONE;
}
- // FIXME: complete do_io_xxxx instead
- this->read.enabled = 1;
+ this->read.enabled = 1;
+ this->write.enabled = 1;
- // Handshake callback handler.
SET_HANDLER((NetVConnHandler)&QUICNetVConnection::state_handshake);
- // Send this netvc to InactivityCop.
- // Note: even though we will set the timeouts to 0, we need this so we make
sure the one gets freed and the IO is properly ended.
nh->startCop(this);
-
- // We will take care of this by using `idle_timeout` configured by
`proxy.config.quic.no_activity_timeout_in`.
this->set_default_inactivity_timeout(0);
if (inactivity_timeout_in) {
@@ -371,9 +426,7 @@ QUICNetVConnection::acceptEvent(int event, Event *e)
}
action_.continuation->handleEvent(NET_EVENT_ACCEPT, this);
- this->_schedule_packet_write_ready();
-
- this->_schedule_quiche_timeout();
+ this->_schedule_openssl_event(false);
return EVENT_DONE;
}
@@ -393,51 +446,27 @@ QUICNetVConnection::stream_manager()
void
QUICNetVConnection::close_quic_connection(QUICConnectionErrorUPtr error)
{
- if (this->_quiche_con == nullptr || quiche_conn_is_closed(this->_quiche_con)
|| quiche_conn_is_draining(this->_quiche_con)) {
- return;
- }
-
- const bool is_app_error = error != nullptr && error->cls ==
QUICErrorClass::APPLICATION;
- const uint64_t code = error == nullptr ?
static_cast<uint64_t>(QUICTransErrorCode::NO_ERROR) : error->code;
- const uint8_t *reason = nullptr;
- size_t reason_len = 0;
-
- if (error != nullptr && error->msg != nullptr) {
- reason = reinterpret_cast<const uint8_t *>(error->msg);
- reason_len = strlen(error->msg);
- }
-
- if (quiche_conn_close(this->_quiche_con, is_app_error, code, reason,
reason_len) != 0) {
- QUICConDebug("failed to close QUIC connection with code %" PRIu64, code);
- return;
+ if (this->_ssl != nullptr) {
+ SSL_SHUTDOWN_EX_ARGS args = {};
+ if (error != nullptr) {
+ args.quic_error_code = error->code;
+ args.quic_reason = error->msg;
+ }
+ SSL_shutdown_ex(this->_ssl, SSL_SHUTDOWN_FLAG_NO_BLOCK, &args,
sizeof(args));
}
-
- this->_schedule_packet_write_ready(false);
}
void
QUICNetVConnection::reset_quic_connection()
{
+ if (this->_ssl != nullptr) {
+ SSL_shutdown_ex(this->_ssl, SSL_SHUTDOWN_FLAG_RAPID |
SSL_SHUTDOWN_FLAG_NO_BLOCK, nullptr, 0);
+ }
}
void
-QUICNetVConnection::handle_received_packet(UDPPacket *packet)
+QUICNetVConnection::handle_received_packet(UDPPacket * /* packet ATS_UNUSED */)
{
- size_t buf_len{0};
- uint8_t *buf = packet->get_entire_chain_buffer(&buf_len);
- this->netActivity();
- quiche_recv_info recv_info = {
- &packet->from.sa,
- static_cast<socklen_t>(packet->from.isIp4() ? sizeof(packet->from.sin) :
sizeof(packet->from.sin6)),
- &packet->to.sa,
- static_cast<socklen_t>(packet->to.isIp4() ? sizeof(packet->to.sin) :
sizeof(packet->to.sin6)),
- };
-
- ssize_t done = quiche_conn_recv(this->_quiche_con, buf, buf_len, &recv_info);
- if (done < 0) {
- QUICConVDebug("failed to process packet: %zd", done);
- return;
- }
}
void
@@ -472,25 +501,25 @@ QUICNetVConnection::retry_source_connection_id() const
QUICConnectionId
QUICNetVConnection::initial_source_connection_id() const
{
- return {};
+ return this->_initial_source_connection_id;
}
QUICConnectionId
QUICNetVConnection::connection_id() const
{
- return {};
+ return this->_quic_connection_id;
}
std::string_view
QUICNetVConnection::cids() const
{
- return "";
+ return this->_cid_text;
}
-const QUICFiveTuple
+QUICFiveTuple const
QUICNetVConnection::five_tuple() const
{
- return {};
+ return QUICFiveTuple(this->remote_addr, this->local_addr, IPPROTO_UDP);
}
uint32_t
@@ -508,29 +537,25 @@ QUICNetVConnection::direction() const
QUICVersion
QUICNetVConnection::negotiated_version() const
{
- return 0;
+ return QUIC_SUPPORTED_VERSIONS[0];
}
std::string_view
QUICNetVConnection::negotiated_application_name() const
{
- const uint8_t *name;
- size_t name_len = 0;
- quiche_conn_application_proto(this->_quiche_con, &name, &name_len);
-
- return std::string_view(reinterpret_cast<const char *>(name), name_len);
+ return this->_negotiated_alpn;
}
void
QUICNetVConnection::on_stream_updated()
{
- this->_schedule_packet_write_ready(false);
+ this->_schedule_openssl_event(false);
}
bool
QUICNetVConnection::is_closed() const
{
- return quiche_conn_is_closed(this->_quiche_con);
+ return this->_openssl_connection_closed();
}
bool
@@ -542,13 +567,13 @@ QUICNetVConnection::is_at_anti_amplification_limit() const
bool
QUICNetVConnection::is_address_validation_completed() const
{
- return false;
+ return true;
}
bool
QUICNetVConnection::is_handshake_completed() const
{
- return false;
+ return this->_handshake_completed;
}
void
@@ -592,166 +617,181 @@ QUICNetVConnection::_unbindSSLObject()
void
QUICNetVConnection::_schedule_packet_write_ready(bool delay)
{
- if (!this->_packet_write_ready) {
- if (delay) {
- this->_packet_write_ready = this->thread->schedule_in(this,
WRITE_READY_INTERVAL, QUIC_EVENT_PACKET_WRITE_READY, nullptr);
- } else {
- this->_packet_write_ready = this->thread->schedule_imm(this,
QUIC_EVENT_PACKET_WRITE_READY, nullptr);
- }
- }
+ this->_schedule_openssl_event(delay);
}
void
QUICNetVConnection::_unschedule_packet_write_ready()
{
- if (this->_packet_write_ready) {
- this->_packet_write_ready->cancel();
- this->_packet_write_ready = nullptr;
- }
+ this->_unschedule_openssl_event();
}
void
QUICNetVConnection::_close_packet_write_ready(Event *data)
{
- ink_assert(this->_packet_write_ready == data);
- this->_packet_write_ready = nullptr;
+ if (this->_packet_write_ready == data) {
+ this->_packet_write_ready = nullptr;
+ }
}
void
QUICNetVConnection::_schedule_quiche_timeout()
{
- if (!this->_quiche_timeout) {
- this->_quiche_timeout = this->thread->schedule_in(this,
HRTIME_MSECONDS(quiche_conn_timeout_as_millis(this->_quiche_con)));
- }
}
void
QUICNetVConnection::_unschedule_quiche_timeout()
{
- if (this->_quiche_timeout) {
- this->_quiche_timeout->cancel();
- this->_quiche_timeout = nullptr;
- }
}
void
-QUICNetVConnection::_close_quiche_timeout(Event *data)
+QUICNetVConnection::_close_quiche_timeout(Event * /* data ATS_UNUSED */)
{
- ink_assert(this->_quiche_timeout == data);
- this->_quiche_timeout = nullptr;
}
void
QUICNetVConnection::_schedule_closing_event()
{
QUICConDebug("Scheduling closing event");
- if (quiche_conn_is_timed_out(this->_quiche_con)) {
- QUICConDebug("QUIC Idle timeout detected");
- this->thread->schedule_imm(this, VC_EVENT_INACTIVITY_TIMEOUT);
- return;
+ SSL_CONN_CLOSE_INFO info = {};
+ if (this->_ssl != nullptr && SSL_get_conn_close_info(this->_ssl, &info,
sizeof(info)) == 1) {
+ QUICConDebug("QUIC close info: error_code=%" PRIu64 " frame_type=%" PRIu64
" flags=0x%x reason=\"%.*s\"", info.error_code,
+ info.frame_type, info.flags,
static_cast<int>(info.reason_len), info.reason == nullptr ? "" : info.reason);
+ if (info.error_code == OSSL_QUIC_LOCAL_ERR_IDLE_TIMEOUT) {
+ QUICConDebug("QUIC Idle timeout detected");
+ this->thread->schedule_imm(this, VC_EVENT_INACTIVITY_TIMEOUT);
+ return;
+ }
}
- bool is_app;
- uint64_t error_code;
- const uint8_t *reason;
- size_t reason_len;
- bool has_error = quiche_conn_peer_error(this->_quiche_con,
&is_app, &error_code, &reason, &reason_len) ||
- quiche_conn_local_error(this->_quiche_con, &is_app,
&error_code, &reason, &reason_len);
- if (has_error && error_code !=
static_cast<uint64_t>(QUICTransErrorCode::NO_ERROR)) {
- QUICConDebug("is_app=%d error_code=%" PRId64 " reason=%.*s", is_app,
error_code, static_cast<int>(reason_len), reason);
- this->thread->schedule_imm(this, VC_EVENT_ERROR);
+ this->thread->schedule_imm(this, VC_EVENT_EOS);
+}
+
+void
+QUICNetVConnection::_handle_read_ready()
+{
+ this->_handle_openssl_events();
+}
+
+void
+QUICNetVConnection::_handle_write_ready()
+{
+ this->_handle_openssl_events();
+}
+
+void
+QUICNetVConnection::_handle_interval()
+{
+ this->_handle_openssl_events();
+}
+
+void
+QUICNetVConnection::_handle_openssl_events()
+{
+ if (this->_ssl == nullptr) {
return;
}
- // If it's not timeout nor error, it's probably eos
- this->thread->schedule_imm(this, VC_EVENT_EOS);
+ if (SSL_handle_events(this->_ssl) != 1) {
+ QUICConDebug("SSL_handle_events failed: %s",
ERR_error_string(ERR_peek_error(), nullptr));
+ }
+
+ if (this->_stream_manager != nullptr && this->_application_started) {
+ this->_accept_openssl_streams();
+ this->_process_openssl_streams();
+ }
+
+ this->netActivity();
}
void
-QUICNetVConnection::_handle_read_ready()
+QUICNetVConnection::_accept_openssl_streams()
{
- quiche_stream_iter *readable = quiche_conn_readable(this->_quiche_con);
- uint64_t s = 0;
- while (quiche_stream_iter_next(readable, &s)) {
- QUICConVDebug("stream %" PRIu64 " is readable\n", s);
- QUICStream *stream = static_cast<QUICStream
*>(this->_stream_manager->find_stream(s));
- if (stream == nullptr) {
+ while (SSL *stream_ssl = SSL_accept_stream(this->_ssl,
SSL_ACCEPT_STREAM_NO_BLOCK)) {
+ SSL_set_blocking_mode(stream_ssl, 0);
+ SSL_set_event_handling_mode(stream_ssl,
SSL_VALUE_EVENT_HANDLING_MODE_IMPLICIT);
+ SSL_set_mode(stream_ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER |
SSL_MODE_ENABLE_PARTIAL_WRITE);
+
+ QUICStreamId stream_id = SSL_get_stream_id(stream_ssl);
+ this->_openssl_streams.emplace(stream_id, stream_ssl);
+
+ if (this->_stream_manager->find_stream(stream_id) == nullptr) {
[[maybe_unused]] QUICConnectionError err;
- stream = this->_stream_manager->create_stream(s, err);
+ this->_stream_manager->create_stream(stream_id, err);
}
- stream->receive_data(this->_quiche_con);
}
- quiche_stream_iter_free(readable);
}
void
-QUICNetVConnection::_handle_write_ready()
+QUICNetVConnection::_process_openssl_streams()
{
- if (quiche_conn_is_established(this->_quiche_con)) {
- quiche_stream_iter *writable = quiche_conn_writable(this->_quiche_con);
- uint64_t s = 0;
- while (quiche_stream_iter_next(writable, &s)) {
- QUICStream *stream = static_cast<QUICStream
*>(this->_stream_manager->find_stream(s));
- if (stream == nullptr) {
- [[maybe_unused]] QUICConnectionError err;
- stream = this->_stream_manager->create_stream(s, err);
- }
- stream->send_data(this->_quiche_con);
+ std::vector<QUICStreamId> stream_ids;
+ stream_ids.reserve(this->_openssl_streams.size());
+ for (auto const &entry : this->_openssl_streams) {
+ stream_ids.push_back(entry.first);
+ }
+
+ for (QUICStreamId stream_id : stream_ids) {
+ auto stream_it = this->_openssl_streams.find(stream_id);
+ if (stream_it == this->_openssl_streams.end()) {
+ continue;
}
- quiche_stream_iter_free(writable);
- }
-
- Ptr<IOBufferBlock> udp_payload;
- quiche_send_info send_info;
- struct timespec send_at_hint;
- ssize_t res;
- ssize_t written = 0;
-
- size_t quantum = quiche_conn_send_quantum(this->_quiche_con);
- size_t max_udp_payload_size =
quiche_conn_max_send_udp_payload_size(this->_quiche_con);
-
- // This buffer size must be less than 64KB because it can be used for UDP
GSO (UDP_SEGMENT)
- udp_payload = new_IOBufferBlock();
- udp_payload->alloc(buffer_size_to_index(quantum, BUFFER_SIZE_INDEX_32K));
- quantum = std::min(static_cast<int64_t>(quantum),
udp_payload->write_avail());
- while (written + max_udp_payload_size <= quantum) {
- res = quiche_conn_send(this->_quiche_con, reinterpret_cast<uint8_t
*>(udp_payload->end()) + written, max_udp_payload_size,
- &send_info);
-#ifdef HAVE_SO_TXTIME
- if (written == 0) {
- memcpy(&send_at_hint, &send_info.at, sizeof(struct timespec));
+
+ SSL *stream_ssl = stream_it->second;
+ QUICStream *stream = static_cast<QUICStream
*>(this->_stream_manager->find_stream(stream_id));
+ if (stream == nullptr) {
+ continue;
}
-#endif
- if (res > 0) {
- written += res;
+ int stream_type = SSL_get_stream_type(stream_ssl);
+ if ((stream_type & SSL_STREAM_TYPE_READ) != 0) {
+ stream->receive_data(*this);
}
- if (static_cast<size_t>(res) != max_udp_payload_size) {
- break;
+ if ((stream_type & SSL_STREAM_TYPE_WRITE) != 0 ||
stream->has_data_to_send()) {
+ if (stream->has_data_to_send()) {
+ while (stream->has_data_to_send() && stream->send_data(*this) > 0) {}
+ } else {
+ stream->send_data(*this);
+ }
}
}
- if (written > 0) {
- udp_payload->fill(written);
- int segment_size = 0;
- if (static_cast<size_t>(written) > max_udp_payload_size) {
- segment_size = max_udp_payload_size;
+}
+
+void
+QUICNetVConnection::_schedule_openssl_event(bool delay)
+{
+ if (!delay && this->_packet_write_ready != nullptr) {
+ this->_packet_write_ready->cancel();
+ this->_packet_write_ready = nullptr;
+ }
+
+ if (this->_packet_write_ready == nullptr && this->thread != nullptr) {
+ if (delay) {
+ this->_packet_write_ready = this->thread->schedule_in(this,
OPENSSL_QUIC_EVENT_INTERVAL);
+ } else {
+ this->_packet_write_ready = this->thread->schedule_imm(this,
QUIC_EVENT_PACKET_WRITE_READY);
}
- this->_packet_handler->send_packet(this->_udp_con, this->con.addr,
udp_payload, segment_size, &send_at_hint);
- this->netActivity();
}
}
void
-QUICNetVConnection::_handle_interval()
+QUICNetVConnection::_unschedule_openssl_event()
{
- quiche_conn_on_timeout(this->_quiche_con);
+ if (this->_packet_write_ready != nullptr) {
+ this->_packet_write_ready->cancel();
+ this->_packet_write_ready = nullptr;
+ }
+}
- if (quiche_conn_is_closed(this->_quiche_con)) {
- this->_schedule_closing_event();
- } else {
- // Just schedule timeout event again if the connection is still open
- this->_schedule_quiche_timeout();
+bool
+QUICNetVConnection::_openssl_connection_closed() const
+{
+ if (this->_ssl == nullptr) {
+ return true;
}
+
+ SSL_CONN_CLOSE_INFO info = {};
+ return SSL_get_conn_close_info(this->_ssl, &info, sizeof(info)) == 1;
}
int
@@ -770,10 +810,10 @@ QUICNetVConnection::populate_protocol(std::string_view
*results, int n) const
return retval;
}
-const char *
+char const *
QUICNetVConnection::protocol_contains(std::string_view prefix) const
{
- const char *retval = nullptr;
+ char const *retval = nullptr;
if (prefix.size() <= IP_PROTO_TAG_QUIC.size() &&
strncmp(IP_PROTO_TAG_QUIC.data(), prefix.data(), prefix.size()) == 0) {
retval = IP_PROTO_TAG_QUIC.data();
} else if (prefix.size() <= IP_PROTO_TAG_TLS_1_3.size() &&
@@ -791,6 +831,99 @@ QUICNetVConnection::get_quic_connection()
return static_cast<QUICConnection *>(this);
}
+int64_t
+QUICNetVConnection::read_stream(QUICStreamId stream_id, uint8_t *buf, size_t
len, bool &fin, QUICStreamIO::ErrorCode &error_code)
+{
+ auto it = this->_openssl_streams.find(stream_id);
+ if (it == this->_openssl_streams.end()) {
+ error_code = ENOENT;
+ return -1;
+ }
+
+ SSL_handle_events(it->second);
+
+ size_t read_len = 0;
+ fin = false;
+ if (SSL_read_ex(it->second, buf, len, &read_len) == 1) {
+ fin = this->stream_read_finished(stream_id);
+ return read_len;
+ }
+
+ int ssl_error = SSL_get_error(it->second, 0);
+ if (ssl_error == SSL_ERROR_WANT_READ || ssl_error == SSL_ERROR_WANT_WRITE) {
+ return 0;
+ }
+ if (ssl_error == SSL_ERROR_ZERO_RETURN) {
+ fin = true;
+ return 0;
+ }
+
+ error_code = ssl_error;
+ return -1;
+}
+
+bool
+QUICNetVConnection::stream_read_finished(QUICStreamId stream_id)
+{
+ auto it = this->_openssl_streams.find(stream_id);
+ if (it == this->_openssl_streams.end()) {
+ return true;
+ }
+
+ int state = SSL_get_stream_read_state(it->second);
+ return state == SSL_STREAM_STATE_FINISHED || state ==
SSL_STREAM_STATE_RESET_REMOTE || state == SSL_STREAM_STATE_CONN_CLOSED;
+}
+
+int64_t
+QUICNetVConnection::stream_write_capacity(QUICStreamId stream_id)
+{
+ auto it = this->_openssl_streams.find(stream_id);
+ if (it == this->_openssl_streams.end()) {
+ return -1;
+ }
+
+ uint64_t avail = 0;
+ if (SSL_get_stream_write_buf_avail(it->second, &avail) == 1) {
+ return std::min<uint64_t>(avail, 16 * 1024);
+ }
+
+ return 0;
+}
+
+int64_t
+QUICNetVConnection::write_stream(QUICStreamId stream_id, uint8_t const *buf,
size_t len, bool fin,
+ QUICStreamIO::ErrorCode &error_code)
+{
+ auto it = this->_openssl_streams.find(stream_id);
+ if (it == this->_openssl_streams.end()) {
+ error_code = ENOENT;
+ return -1;
+ }
+
+ SSL_handle_events(it->second);
+
+ if (len == 0 && fin) {
+ SSL_stream_conclude(it->second, 0);
+ return 0;
+ }
+
+ size_t written_len = 0;
+ const uint64_t flags = fin ? SSL_WRITE_FLAG_CONCLUDE : 0;
+ if (SSL_write_ex2(it->second, buf, len, flags, &written_len) == 1) {
+ return written_len;
+ }
+
+ int ssl_error = SSL_get_error(it->second, 0);
+ if (ssl_error == SSL_ERROR_WANT_READ || ssl_error == SSL_ERROR_WANT_WRITE) {
+ return 0;
+ }
+
+ error_code = ssl_error;
+ QUICConDebug("Failed to write %zu bytes on QUIC stream %" PRIu64 ":
ssl_error=%d, openssl_error=%s", len, stream_id, ssl_error,
+ ERR_error_string(ERR_peek_error(), nullptr));
+ return -1;
+}
+
void
QUICNetVConnection::reenable(int event)
{
@@ -822,7 +955,7 @@ QUICNetVConnection::getMutexForTLSEvents()
bool
QUICNetVConnection::_isReadyToTransferData() const
{
- return quiche_conn_is_established(this->_quiche_con);
+ return this->_handshake_completed;
}
SSL *
@@ -834,9 +967,6 @@ QUICNetVConnection::_get_ssl_object() const
ssl_curve_id
QUICNetVConnection::_get_tls_curve() const
{
- // For resumed server side session caching, we have to retrieve the
curve/group
- // from our stored data. For non-resumed sessions or from ticket based
resumption,
- // simply query the SSL object.
if (getIsResumedFromSessionCache()) {
return getSSLCurveNID();
} else {
@@ -847,9 +977,6 @@ QUICNetVConnection::_get_tls_curve() const
std::string_view
QUICNetVConnection::_get_tls_group() const
{
- // For resumed server side session caching, we have to retrieve the
curve/group
- // from our stored data. For non-resumed sessions or from ticket based
resumption,
- // simply query the SSL object.
if (getIsResumedFromSessionCache()) {
return getSSLGroupName();
} else {
@@ -864,7 +991,6 @@ QUICNetVConnection::_verify_certificate(X509_STORE_CTX * /*
ctx ATS_UNUSED */)
TSHttpHookID hookId;
APIHook *hook = nullptr;
- // TODO Simply call callHooks once QUICNetVC implements TLSEventSupport
if (get_context() == NET_VCONNECTION_IN) {
eventId = TS_EVENT_SSL_VERIFY_CLIENT;
hookId = TS_SSL_VERIFY_CLIENT_HOOK;
@@ -879,8 +1005,6 @@ QUICNetVConnection::_verify_certificate(X509_STORE_CTX *
/* ctx ATS_UNUSED */)
hook->invoke(eventId, this);
}
- // According to the implementation in SSLNetVC,
- // we can assume that reenable() is called during the event handling.
ink_assert(this->_is_verifying_cert == false);
if (this->_is_cert_verified) {
return 1;
@@ -895,7 +1019,7 @@ QUICNetVConnection::_get_local_port()
return this->get_local_port();
}
-const IpEndpoint &
+IpEndpoint const &
QUICNetVConnection::_getLocalEndpoint()
{
return this->local_addr;
@@ -904,13 +1028,11 @@ QUICNetVConnection::_getLocalEndpoint()
bool
QUICNetVConnection::_isTryingRenegotiation() const
{
- // Renegotiation is not allowed on QUIC (TLS 1.3) connections.
- // If handshake is completed when this function is called, that should be
unallowed attempt of renegotiation.
- return quiche_conn_is_established(this->_quiche_con);
+ return this->_handshake_completed;
}
shared_SSL_CTX
-QUICNetVConnection::_lookupContextByName(const std::string &servername,
SSLCertContextType ctxType)
+QUICNetVConnection::_lookupContextByName(std::string const &servername,
SSLCertContextType ctxType)
{
shared_SSL_CTX ctx = nullptr;
QUICCertConfig::scoped_config lookup;
diff --git a/src/iocore/net/OpenSSLQUICPacketHandler.cc
b/src/iocore/net/OpenSSLQUICPacketHandler.cc
new file mode 100644
index 0000000000..f62766b974
--- /dev/null
+++ b/src/iocore/net/OpenSSLQUICPacketHandler.cc
@@ -0,0 +1,630 @@
+/** @file
+
+ OpenSSL native QUIC listener support.
+
+ @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 "P_QUICPacketHandler.h"
+#include "P_QUICNetProcessor.h"
+#include "P_QUICNetVConnection.h"
+#include "P_SSLCertLookup.h"
+#include "P_UnixNet.h"
+#include "iocore/net/QUICMultiCertConfigLoader.h"
+#include "iocore/net/quic/QUICConfig.h"
+#include "tscore/ink_atomic.h"
+#include "tscore/ink_sock.h"
+
+#include <openssl/bio.h>
+#include <openssl/crypto.h>
+#include <openssl/err.h>
+#include <openssl/ssl.h>
+
+#include <cerrno>
+#include <algorithm>
+
+namespace
+{
+constexpr ink_hrtime OPENSSL_QUIC_EVENT_INTERVAL = HRTIME_MSECONDS(2);
+
+DbgCtl dbg_ctl_openssl_quic{"openssl_quic"};
+
+struct PeerCaptureBioState {
+ BIO *inner{nullptr};
+ IpEndpoint last_peer;
+ bool have_last_peer{false};
+};
+
+thread_local PeerCaptureBioState *active_peer_capture = nullptr;
+
+void
+free_quic_peer_ex_data(void *, void *ptr, CRYPTO_EX_DATA *, int, long, void *)
+{
+ delete static_cast<IpEndpoint *>(ptr);
+}
+
+int
+quic_peer_ex_data_index()
+{
+ static int index = SSL_get_ex_new_index(0, nullptr, nullptr, nullptr,
free_quic_peer_ex_data);
+
+ return index;
+}
+
+bool
+bio_addr_to_ip_endpoint(IpEndpoint &endpoint, BIO_ADDR const *bio_addr)
+{
+ if (bio_addr == nullptr) {
+ return false;
+ }
+
+ in_port_t const port = BIO_ADDR_rawport(bio_addr);
+ if (BIO_ADDR_family(bio_addr) == AF_INET) {
+ in_addr_t addr = 0;
+ size_t len = sizeof(addr);
+
+ if (BIO_ADDR_rawaddress(bio_addr, &addr, &len) != 1 || len !=
sizeof(addr)) {
+ return false;
+ }
+
+ ats_ip4_set(&endpoint, addr, port);
+ return true;
+ }
+
+ if (BIO_ADDR_family(bio_addr) == AF_INET6) {
+ in6_addr addr;
+ size_t len = sizeof(addr);
+
+ if (BIO_ADDR_rawaddress(bio_addr, &addr, &len) != 1 || len !=
sizeof(addr)) {
+ return false;
+ }
+
+ ats_ip6_set(&endpoint, addr, port);
+ return true;
+ }
+
+ return false;
+}
+
+PeerCaptureBioState *
+peer_capture_state(BIO *bio)
+{
+ return static_cast<PeerCaptureBioState *>(BIO_get_data(bio));
+}
+
+BIO_MSG *
+bio_msg_at(BIO_MSG *msg, size_t stride, size_t index)
+{
+ return reinterpret_cast<BIO_MSG *>(reinterpret_cast<char *>(msg) + (stride *
index));
+}
+
+void
+update_last_peer(PeerCaptureBioState &state, BIO_MSG const &msg)
+{
+ IpEndpoint peer;
+ if (msg.peer != nullptr && bio_addr_to_ip_endpoint(peer, msg.peer)) {
+ state.last_peer = peer;
+ state.have_last_peer = true;
+ active_peer_capture = &state;
+ }
+}
+
+void
+copy_retry_flags(BIO *bio, BIO *inner)
+{
+ BIO_clear_retry_flags(bio);
+ BIO_set_flags(bio, BIO_get_retry_flags(inner));
+ BIO_set_retry_reason(bio, BIO_get_retry_reason(inner));
+}
+
+int
+peer_capture_bio_create(BIO *bio)
+{
+ BIO_set_init(bio, 0);
+ BIO_set_data(bio, nullptr);
+ BIO_set_shutdown(bio, 1);
+
+ return 1;
+}
+
+int
+peer_capture_bio_destroy(BIO *bio)
+{
+ if (bio == nullptr) {
+ return 0;
+ }
+
+ auto *state = peer_capture_state(bio);
+ if (state != nullptr) {
+ if (active_peer_capture == state) {
+ active_peer_capture = nullptr;
+ }
+ BIO_free(state->inner);
+ delete state;
+ BIO_set_data(bio, nullptr);
+ }
+ BIO_set_init(bio, 0);
+
+ return 1;
+}
+
+int
+peer_capture_bio_write(BIO *bio, char const *data, int len)
+{
+ auto *state = peer_capture_state(bio);
+ if (state == nullptr || state->inner == nullptr) {
+ return 0;
+ }
+
+ int const result = BIO_write(state->inner, data, len);
+ copy_retry_flags(bio, state->inner);
+
+ return result;
+}
+
+int
+peer_capture_bio_write_ex(BIO *bio, char const *data, size_t len, size_t
*written)
+{
+ auto *state = peer_capture_state(bio);
+ if (state == nullptr || state->inner == nullptr) {
+ return 0;
+ }
+
+ int const result = BIO_write_ex(state->inner, data, len, written);
+ copy_retry_flags(bio, state->inner);
+
+ return result;
+}
+
+int
+peer_capture_bio_sendmmsg(BIO *bio, BIO_MSG *msg, size_t stride, size_t
num_msg, uint64_t flags, size_t *num_processed)
+{
+ auto *state = peer_capture_state(bio);
+ if (state == nullptr || state->inner == nullptr) {
+ return 0;
+ }
+
+ int const result = BIO_sendmmsg(state->inner, msg, stride, num_msg, flags,
num_processed);
+ copy_retry_flags(bio, state->inner);
+
+ return result;
+}
+
+int
+peer_capture_bio_read(BIO *bio, char *data, int len)
+{
+ auto *state = peer_capture_state(bio);
+ if (state == nullptr || state->inner == nullptr) {
+ return 0;
+ }
+
+ int const result = BIO_read(state->inner, data, len);
+ copy_retry_flags(bio, state->inner);
+
+ return result;
+}
+
+int
+peer_capture_bio_read_ex(BIO *bio, char *data, size_t len, size_t *read_bytes)
+{
+ auto *state = peer_capture_state(bio);
+ if (state == nullptr || state->inner == nullptr) {
+ return 0;
+ }
+
+ int const result = BIO_read_ex(state->inner, data, len, read_bytes);
+ copy_retry_flags(bio, state->inner);
+
+ return result;
+}
+
+int
+peer_capture_bio_recvmmsg(BIO *bio, BIO_MSG *msg, size_t stride, size_t
num_msg, uint64_t flags, size_t *num_processed)
+{
+ auto *state = peer_capture_state(bio);
+ if (state == nullptr || state->inner == nullptr) {
+ return 0;
+ }
+
+ size_t const read_limit = std::min<size_t>(num_msg, 1);
+
+ int const result = BIO_recvmmsg(state->inner, msg, stride, read_limit,
flags, num_processed);
+ copy_retry_flags(bio, state->inner);
+
+ if (result == 1 && num_processed != nullptr && *num_processed > 0) {
+ update_last_peer(*state, *bio_msg_at(msg, stride, 0));
+ }
+
+ return result;
+}
+
+long
+peer_capture_bio_ctrl(BIO *bio, int cmd, long larg, void *parg)
+{
+ auto *state = peer_capture_state(bio);
+ if (state == nullptr || state->inner == nullptr) {
+ return 0;
+ }
+
+ return BIO_ctrl(state->inner, cmd, larg, parg);
+}
+
+long
+peer_capture_bio_callback_ctrl(BIO *bio, int cmd, BIO_info_cb *cb)
+{
+ auto *state = peer_capture_state(bio);
+ if (state == nullptr || state->inner == nullptr) {
+ return 0;
+ }
+
+ return BIO_callback_ctrl(state->inner, cmd, cb);
+}
+
+BIO_METHOD *
+peer_capture_bio_method()
+{
+ static BIO_METHOD *method = []() -> BIO_METHOD * {
+ BIO_METHOD *m = BIO_meth_new(BIO_TYPE_SOURCE_SINK | BIO_get_new_index(),
"ATS OpenSSL QUIC peer capture");
+ if (m == nullptr) {
+ return nullptr;
+ }
+
+ BIO_meth_set_create(m, peer_capture_bio_create);
+ BIO_meth_set_destroy(m, peer_capture_bio_destroy);
+ BIO_meth_set_write(m, peer_capture_bio_write);
+ BIO_meth_set_write_ex(m, peer_capture_bio_write_ex);
+ BIO_meth_set_sendmmsg(m, peer_capture_bio_sendmmsg);
+ BIO_meth_set_read(m, peer_capture_bio_read);
+ BIO_meth_set_read_ex(m, peer_capture_bio_read_ex);
+ BIO_meth_set_recvmmsg(m, peer_capture_bio_recvmmsg);
+ BIO_meth_set_ctrl(m, peer_capture_bio_ctrl);
+ BIO_meth_set_callback_ctrl(m, peer_capture_bio_callback_ctrl);
+
+ return m;
+ }();
+
+ return method;
+}
+
+BIO *
+new_peer_capture_bio(int fd)
+{
+ BIO *inner = BIO_new_dgram(fd, BIO_NOCLOSE);
+ if (inner == nullptr) {
+ return nullptr;
+ }
+
+ BIO *bio = BIO_new(peer_capture_bio_method());
+ if (bio == nullptr) {
+ BIO_free(inner);
+ return nullptr;
+ }
+
+ auto *state = new PeerCaptureBioState;
+ state->inner = inner;
+ BIO_set_data(bio, state);
+ BIO_set_init(bio, 1);
+
+ return bio;
+}
+
+int
+new_pending_quic_connection_cb(SSL_CTX *, SSL *new_ssl, void *)
+{
+ auto *capture = active_peer_capture;
+ if (capture == nullptr || !capture->have_last_peer) {
+ return 1;
+ }
+
+ int const index = quic_peer_ex_data_index();
+ if (index < 0) {
+ return 0;
+ }
+
+ delete static_cast<IpEndpoint *>(SSL_get_ex_data(new_ssl, index));
+ auto *peer = new IpEndpoint(capture->last_peer);
+ if (SSL_set_ex_data(new_ssl, index, peer) != 1) {
+ delete peer;
+ return 0;
+ }
+
+ return 1;
+}
+
+bool
+get_bio_peer_addr(BIO *bio, IpEndpoint &remote_addr)
+{
+ if (bio == nullptr) {
+ return false;
+ }
+
+ BIO_ADDR *peer_addr = BIO_ADDR_new();
+ if (peer_addr == nullptr) {
+ return false;
+ }
+
+ bool const success = BIO_dgram_get_peer(bio, peer_addr) == 1 &&
bio_addr_to_ip_endpoint(remote_addr, peer_addr);
+ BIO_ADDR_free(peer_addr);
+
+ return success;
+}
+
+bool
+get_quic_peer_addr(SSL *ssl, IpEndpoint &remote_addr)
+{
+ int const index = quic_peer_ex_data_index();
+ if (index >= 0) {
+ auto *peer = static_cast<IpEndpoint *>(SSL_get_ex_data(ssl, index));
+ if (peer != nullptr) {
+ remote_addr = *peer;
+ return true;
+ }
+ }
+
+ return get_bio_peer_addr(SSL_get_rbio(ssl), remote_addr) ||
get_bio_peer_addr(SSL_get_wbio(ssl), remote_addr);
+}
+
+} // end anonymous namespace
+
+QUICPacketHandler::QUICPacketHandler()
+{
+ this->_closed_con_collector =
std::make_unique<QUICClosedConCollector>();
+ this->_closed_con_collector->mutex = new_ProxyMutex();
+}
+
+QUICPacketHandler::~QUICPacketHandler()
+{
+ if (this->_collector_event != nullptr) {
+ this->_collector_event->cancel();
+ this->_collector_event = nullptr;
+ }
+}
+
+void
+QUICPacketHandler::close_connection(QUICNetVConnection *conn)
+{
+ int isin = ink_atomic_swap(&conn->in_closed_queue, 1);
+ if (!isin) {
+ this->_closed_con_collector->closedQueue.push(conn);
+ }
+}
+
+void
+QUICPacketHandler::send_packet(UDPConnection * /* udp_con ATS_UNUSED */,
IpEndpoint & /* addr ATS_UNUSED */,
+ Ptr<IOBufferBlock> /* udp_payload ATS_UNUSED
*/, uint16_t /* segment_size ATS_UNUSED */,
+ struct timespec * /* send_at_hint ATS_UNUSED */)
+{
+}
+
+QUICPacketHandlerIn::QUICPacketHandlerIn(NetProcessor::AcceptOptions const
&opt) : NetAccept(opt), QUICPacketHandler()
+{
+ this->mutex = new_ProxyMutex();
+}
+
+QUICPacketHandlerIn::~QUICPacketHandlerIn()
+{
+ if (this->_event != nullptr) {
+ this->_event->cancel();
+ this->_event = nullptr;
+ }
+ if (this->_listener != nullptr) {
+ SSL_free(this->_listener);
+ this->_listener = nullptr;
+ }
+}
+
+NetProcessor *
+QUICPacketHandlerIn::getNetProcessor() const
+{
+ return &quic_NetProcessor;
+}
+
+NetAccept *
+QUICPacketHandlerIn::clone() const
+{
+ return new QUICPacketHandlerIn(this->opt);
+}
+
+int
+QUICPacketHandlerIn::acceptEvent(int /* event ATS_UNUSED */, void * /* data
ATS_UNUSED */)
+{
+ this->setThreadAffinity(this_ethread());
+
+ if (this->_collector_event == nullptr) {
+ this->_collector_event =
this_ethread()->schedule_every(this->_closed_con_collector.get(),
HRTIME_MSECONDS(100));
+ }
+
+ if (this->_listener == nullptr && !this->_start_listener()) {
+ return EVENT_DONE;
+ }
+
+ this->_event = nullptr;
+ this->_service_listener();
+ this->_schedule_event();
+
+ return EVENT_CONT;
+}
+
+void
+QUICPacketHandlerIn::init_accept(EThread *t)
+{
+ SET_HANDLER(&QUICPacketHandlerIn::acceptEvent);
+
+ if (t == nullptr) {
+ t = eventProcessor.assign_thread(ET_NET);
+ }
+
+ if (!this->action_->continuation->mutex) {
+ this->action_->continuation->mutex = t->mutex;
+ this->action_->mutex = t->mutex;
+ }
+
+ this->mutex = get_NetHandler(t)->mutex;
+ t->schedule_imm(this);
+}
+
+Continuation *
+QUICPacketHandlerIn::_get_continuation()
+{
+ return this;
+}
+
+void
+QUICPacketHandlerIn::_recv_packet(int /* event ATS_UNUSED */, UDPPacket * /*
udpPacket ATS_UNUSED */)
+{
+}
+
+bool
+QUICPacketHandlerIn::_start_listener()
+{
+ if (!this->server.sock.is_ok()) {
+ this->server.sock = UnixSocket{this->server.accept_addr.sa.sa_family,
SOCK_DGRAM, 0};
+ if (!this->server.sock.is_ok()) {
+ Error("failed to create OpenSSL QUIC UDP socket: %s", strerror(errno));
+ return false;
+ }
+
+ if (this->server.accept_addr.sa.sa_family == AF_INET6 &&
this->server.sock.enable_option(IPPROTO_IPV6, IPV6_V6ONLY) < 0) {
+ Error("failed to set IPV6_V6ONLY on OpenSSL QUIC socket: %s",
strerror(errno));
+ return false;
+ }
+ if (this->server.sock.enable_option(SOL_SOCKET, SO_REUSEADDR) < 0) {
+ Error("failed to set SO_REUSEADDR on OpenSSL QUIC socket: %s",
strerror(errno));
+ return false;
+ }
+ if (this->server.sock.bind(&this->server.accept_addr.sa,
ats_ip_size(&this->server.accept_addr.sa)) < 0) {
+ Error("failed to bind OpenSSL QUIC socket: %s", strerror(errno));
+ return false;
+ }
+ }
+
+ if (this->server.sock.set_nonblocking() < 0) {
+ Error("failed to make OpenSSL QUIC socket nonblocking: %s",
strerror(errno));
+ return false;
+ }
+ if (this->opt.recv_bufsize > 0) {
+ this->server.sock.set_rcvbuf_size(this->opt.recv_bufsize);
+ }
+ if (this->opt.send_bufsize > 0) {
+ this->server.sock.set_sndbuf_size(this->opt.send_bufsize);
+ }
+
+ QUICCertConfig::scoped_config server_cert;
+ QUICConfig::scoped_config params;
+ auto default_ctx = server_cert->defaultContext();
+ uint64_t listener_flags = 0;
+ if (!params->stateless_retry()) {
+ listener_flags |= SSL_LISTENER_FLAG_NO_VALIDATE;
+ }
+
+ SSL_CTX_set_new_pending_conn_cb(default_ctx.get(),
new_pending_quic_connection_cb, nullptr);
+
+ this->_listener = SSL_new_listener(default_ctx.get(), listener_flags);
+ if (this->_listener == nullptr) {
+ Error("failed to create OpenSSL QUIC listener");
+ return false;
+ }
+
+ BIO *bio = new_peer_capture_bio(this->server.sock.get_fd());
+ if (bio == nullptr) {
+ Error("failed to create OpenSSL QUIC peer capture BIO");
+ return false;
+ }
+ SSL_set_bio(this->_listener, bio, bio);
+ if (SSL_get_rbio(this->_listener) == nullptr ||
SSL_get_wbio(this->_listener) == nullptr) {
+ Error("failed to configure OpenSSL QUIC listener socket");
+ return false;
+ }
+ SSL_set_blocking_mode(this->_listener, 0);
+ SSL_set_event_handling_mode(this->_listener,
SSL_VALUE_EVENT_HANDLING_MODE_EXPLICIT);
+
+ SSL_set_feature_request_uint(this->_listener, SSL_VALUE_QUIC_IDLE_TIMEOUT,
params->no_activity_timeout_in());
+ SSL_set_feature_request_uint(this->_listener,
SSL_VALUE_QUIC_STREAM_BIDI_LOCAL_AVAIL, params->initial_max_streams_bidi_in());
+ SSL_set_feature_request_uint(this->_listener,
SSL_VALUE_QUIC_STREAM_UNI_LOCAL_AVAIL, params->initial_max_streams_uni_in());
+ SSL_set_incoming_stream_policy(this->_listener,
SSL_INCOMING_STREAM_POLICY_ACCEPT, 0);
+
+ if (SSL_listen(this->_listener) != 1) {
+ Error("failed to start OpenSSL QUIC listener");
+ return false;
+ }
+
+ Dbg(dbg_ctl_openssl_quic, "OpenSSL QUIC listener started on fd %d",
this->server.sock.get_fd());
+ return true;
+}
+
+void
+QUICPacketHandlerIn::_service_listener()
+{
+ SSL_handle_events(this->_listener);
+
+ while (SSL *ssl = SSL_accept_connection(this->_listener,
SSL_ACCEPT_CONNECTION_NO_BLOCK)) {
+ SSL_set_blocking_mode(ssl, 0);
+ SSL_set_event_handling_mode(ssl, SSL_VALUE_EVENT_HANDLING_MODE_IMPLICIT);
+ SSL_set_incoming_stream_policy(ssl, SSL_INCOMING_STREAM_POLICY_ACCEPT, 0);
+ SSL_set_default_stream_mode(ssl, SSL_DEFAULT_STREAM_MODE_NONE);
+
+ auto *vc = static_cast<QUICNetVConnection
*>(getNetProcessor()->allocate_vc(this_ethread()));
+ if (vc == nullptr) {
+ SSL_free(ssl);
+ continue;
+ }
+
+ IpEndpoint remote_addr;
+ if (!get_quic_peer_addr(ssl, remote_addr)) {
+ remote_addr.setToAnyAddr(this->server.accept_addr.sa.sa_family);
+ }
+
+ vc->init(ssl, this);
+ vc->id = net_next_connection_number();
+ vc->set_quic_endpoints(this->server.accept_addr, remote_addr);
+ vc->submit_time = ink_get_hrtime();
+ vc->thread = this_ethread();
+ vc->mutex = get_NetHandler(this_ethread())->mutex;
+ vc->action_ = *this->action_;
+ vc->set_is_transparent(this->opt.f_inbound_transparent);
+ vc->set_context(NET_VCONNECTION_IN);
+ vc->options.ip_proto = NetVCOptions::USE_UDP;
+ vc->options.ip_family = this->server.accept_addr.sa.sa_family;
+ this_ethread()->schedule_imm(vc, EVENT_NONE, nullptr);
+ }
+}
+
+void
+QUICPacketHandlerIn::_schedule_event()
+{
+ if (this->_event == nullptr) {
+ this->_event = this_ethread()->schedule_in(this,
OPENSSL_QUIC_EVENT_INTERVAL);
+ }
+}
+
+void
+QUICPacketHandlerOut::init(QUICNetVConnection * /* vc ATS_UNUSED */)
+{
+}
+
+Continuation *
+QUICPacketHandlerOut::_get_continuation()
+{
+ return this;
+}
+
+void
+QUICPacketHandlerOut::_recv_packet(int /* event ATS_UNUSED */, UDPPacket * /*
udp_packet ATS_UNUSED */)
+{
+}
diff --git a/src/iocore/net/P_QUICNetProcessor.h
b/src/iocore/net/P_QUICNetProcessor.h
index 677b1fecec..21d2e91dc8 100644
--- a/src/iocore/net/P_QUICNetProcessor.h
+++ b/src/iocore/net/P_QUICNetProcessor.h
@@ -43,7 +43,11 @@
#include "P_UnixNetProcessor.h"
#include "iocore/net/quic/QUICConnectionTable.h"
+#include "tscore/ink_config.h"
+
+#if TS_HAS_QUICHE
#include <quiche.h>
+#endif
class UnixNetVConnection;
struct NetAccept;
@@ -68,17 +72,21 @@ public:
Action *main_accept(Continuation *cont, SOCKET fd, AcceptOptions const &opt)
override;
+#if TS_HAS_QUICHE
off_t quicPollCont_offset;
+#endif
protected:
- NetAccept *createNetAccept(const NetProcessor::AcceptOptions &opt) override;
+ NetAccept *createNetAccept(NetProcessor::AcceptOptions const &opt) override;
private:
QUICNetProcessor(const QUICNetProcessor &);
QUICNetProcessor &operator=(const QUICNetProcessor &);
+#if TS_HAS_QUICHE
QUICConnectionTable *_ctable = nullptr;
quiche_config *_quiche_config = nullptr;
+#endif
};
extern QUICNetProcessor quic_NetProcessor;
diff --git a/src/iocore/net/P_QUICNetVConnection.h
b/src/iocore/net/P_QUICNetVConnection.h
index 89af3685e4..bb8064ecb4 100644
--- a/src/iocore/net/P_QUICNetVConnection.h
+++ b/src/iocore/net/P_QUICNetVConnection.h
@@ -46,11 +46,19 @@
#include "iocore/net/quic/QUICConnection.h"
#include "iocore/net/quic/QUICConnectionTable.h"
#include "iocore/net/quic/QUICContext.h"
+#include "iocore/net/quic/QUICStream.h"
#include "iocore/net/quic/QUICStreamManager.h"
+#include "tscore/ink_config.h"
#include "tscore/List.h"
#include <netinet/in.h>
+#include <openssl/ssl.h>
+#include <string>
+#include <unordered_map>
+
+#if TS_HAS_QUICHE
#include <quiche.h>
+#endif
class EThread;
class QUICPacketHandler;
@@ -66,16 +74,23 @@ class QUICNetVConnection : public UnixNetVConnection,
public TLSCertSwitchSupport,
public TLSEventSupport,
public TLSBasicSupport,
- public QUICSupport
+ public QUICSupport,
+ public QUICStreamIO
{
using super = UnixNetVConnection; ///< Parent type.
public:
QUICNetVConnection();
~QUICNetVConnection();
+#if TS_HAS_OPENSSL_QUIC
+ void init(SSL *ssl, QUICPacketHandler *packet_handler);
+ void set_quic_endpoints(IpEndpoint const &local,
IpEndpoint const &remote);
+ QUICConnectionErrorUPtr create_openssl_stream(uint64_t flags, QUICStreamId
&new_stream_id);
+#elif TS_HAS_QUICHE
void init(QUICVersion version, QUICConnectionId peer_cid, QUICConnectionId
original_cid, UDPConnection *, QUICPacketHandler *);
void init(QUICVersion version, QUICConnectionId peer_cid, QUICConnectionId
original_cid, QUICConnectionId first_cid,
QUICConnectionId retry_cid, UDPConnection *, quiche_conn *,
QUICPacketHandler *, QUICConnectionTable *ctable, SSL *);
+#endif
// Event handlers
int acceptEvent(int event, Event *e);
@@ -103,7 +118,7 @@ public:
// NetVConnection
int populate_protocol(std::string_view *results, int n) const
override;
- const char *protocol_contains(std::string_view tag) const override;
+ char const *protocol_contains(std::string_view tag) const override;
// QUICConnection
QUICStreamManager *stream_manager() override;
@@ -120,20 +135,27 @@ public:
QUICConnectionId initial_source_connection_id() const override;
QUICConnectionId connection_id() const override;
std::string_view cids() const override;
- const QUICFiveTuple five_tuple() const override;
+ QUICFiveTuple const five_tuple() const override;
uint32_t pmtu() const override;
NetVConnectionContext_t direction() const override;
QUICVersion negotiated_version() const override;
std::string_view negotiated_application_name() const override;
+ void on_stream_updated() override;
bool is_closed() const override;
bool is_at_anti_amplification_limit() const override;
bool is_address_validation_completed() const override;
bool is_handshake_completed() const override;
- void on_stream_updated() override;
// QUICSupport
QUICConnection *get_quic_connection() override;
+ // QUICStreamIO
+ int64_t read_stream(QUICStreamId stream_id, uint8_t *buf, size_t len, bool
&fin, QUICStreamIO::ErrorCode &error_code) override;
+ bool stream_read_finished(QUICStreamId stream_id) override;
+ int64_t stream_write_capacity(QUICStreamId stream_id) override;
+ int64_t write_stream(QUICStreamId stream_id, uint8_t const *buf, size_t len,
bool fin,
+ QUICStreamIO::ErrorCode &error_code) override;
+
// QUICNetVConnection
int in_closed_queue = 0;
@@ -167,11 +189,11 @@ protected:
in_port_t _get_local_port() override;
// TLSSessionResumptionSupport
- const IpEndpoint &_getLocalEndpoint() override;
+ IpEndpoint const &_getLocalEndpoint() override;
// TLSCertSwitchSupport
bool _isTryingRenegotiation() const override;
- shared_SSL_CTX _lookupContextByName(const std::string &servername,
SSLCertContextType ctxType) override;
+ shared_SSL_CTX _lookupContextByName(std::string const &servername,
SSLCertContextType ctxType) override;
shared_SSL_CTX _lookupContextByIP() override;
// TLSEventSupport
@@ -199,9 +221,16 @@ private:
QUICConnectionId _initial_source_connection_id; // src cid used for Initial
packet
QUICConnectionId _quic_connection_id; // src cid in local
- UDPConnection *_udp_con = nullptr;
- quiche_conn *_quiche_con = nullptr;
- QUICConnectionTable *_ctable = nullptr;
+ QUICConnectionTable *_ctable = nullptr;
+
+#if TS_HAS_OPENSSL_QUIC
+ std::unordered_map<QUICStreamId, SSL *> _openssl_streams;
+ std::string _cid_text;
+ std::string _negotiated_alpn;
+#elif TS_HAS_QUICHE
+ UDPConnection *_udp_con = nullptr;
+ quiche_conn *_quiche_con = nullptr;
+#endif
void _bindSSLObject();
void _unbindSSLObject();
@@ -222,6 +251,15 @@ private:
void _handle_write_ready();
void _handle_interval();
+#if TS_HAS_OPENSSL_QUIC
+ void _handle_openssl_events();
+ void _accept_openssl_streams();
+ void _process_openssl_streams();
+ void _schedule_openssl_event(bool delay = true);
+ void _unschedule_openssl_event();
+ bool _openssl_connection_closed() const;
+#endif
+
void _propagate_event(int event);
void _switch_to_established_state();
diff --git a/src/iocore/net/P_QUICPacketHandler.h
b/src/iocore/net/P_QUICPacketHandler.h
index f64cfc6310..47283c4058 100644
--- a/src/iocore/net/P_QUICPacketHandler.h
+++ b/src/iocore/net/P_QUICPacketHandler.h
@@ -26,8 +26,13 @@
#include "P_NetAccept.h"
#include "P_QUICClosedConCollector.h"
#include "iocore/net/UDPConnection.h"
+#include "tscore/ink_config.h"
+#if TS_HAS_OPENSSL_QUIC
+#include <openssl/ssl.h>
+#elif TS_HAS_QUICHE
#include <quiche.h>
+#endif
class QUICNetVConnection;
class QUICConnectionTable;
@@ -54,7 +59,11 @@ protected:
class QUICPacketHandlerIn : public NetAccept, public QUICPacketHandler
{
public:
- QUICPacketHandlerIn(const NetProcessor::AcceptOptions &opt,
QUICConnectionTable &ctable, quiche_config &config);
+#if TS_HAS_OPENSSL_QUIC
+ QUICPacketHandlerIn(NetProcessor::AcceptOptions const &opt);
+#elif TS_HAS_QUICHE
+ QUICPacketHandlerIn(NetProcessor::AcceptOptions const &opt,
QUICConnectionTable &ctable, quiche_config &config);
+#endif
~QUICPacketHandlerIn();
// NetAccept
@@ -68,10 +77,21 @@ protected:
Continuation *_get_continuation() override;
private:
+#if TS_HAS_OPENSSL_QUIC
+ SSL *_listener = nullptr;
+ Event *_event = nullptr;
+#elif TS_HAS_QUICHE
QUICConnectionTable &_ctable;
quiche_config &_quiche_config;
+#endif
void _recv_packet(int event, UDPPacket *udpPacket) override;
+
+#if TS_HAS_OPENSSL_QUIC
+ bool _start_listener();
+ void _service_listener();
+ void _schedule_event();
+#endif
};
class QUICPacketHandlerOut : public Continuation, public QUICPacketHandler
diff --git a/src/iocore/net/QUICMultiCertConfigLoader.cc
b/src/iocore/net/QUICMultiCertConfigLoader.cc
index 2e7049643e..556b581485 100644
--- a/src/iocore/net/QUICMultiCertConfigLoader.cc
+++ b/src/iocore/net/QUICMultiCertConfigLoader.cc
@@ -24,9 +24,13 @@
#include "P_SSLCertLookup.h"
#include "P_SSLConfig.h"
#include "iocore/net/QUICMultiCertConfigLoader.h"
+#include "iocore/net/TLSSNISupport.h"
#include "iocore/net/quic/QUICConfig.h"
#include "mgmt/config/ConfigContextDiags.h"
+#include <string>
+#include <string_view>
+
int QUICCertConfig::_config_id = 0;
//
@@ -88,8 +92,245 @@ QUICCertConfig::release(SSLCertLookup *lookup)
SSL_CTX *
QUICMultiCertConfigLoader::default_server_ssl_ctx()
{
- return quic_new_ssl_ctx();
+ return quic_new_server_ssl_ctx();
+}
+
+#if TS_HAS_OPENSSL_QUIC
+namespace
+{
+DbgCtl dbg_ctl_quic{"quic"};
+} // end anonymous namespace
+
+namespace
+{
+void
+free_quic_sni_ex_data(void *, void *ptr, CRYPTO_EX_DATA *, int, long, void *)
+{
+ delete static_cast<std::string *>(ptr);
+}
+
+int
+quic_sni_ex_data_index()
+{
+ static int index = SSL_get_ex_new_index(0, nullptr, nullptr, nullptr,
free_quic_sni_ex_data);
+
+ return index;
+}
+
+void
+remember_quic_sni(SSL *ssl, std::string_view servername)
+{
+ if (ssl == nullptr || servername.empty()) {
+ return;
+ }
+
+ auto const index = quic_sni_ex_data_index();
+ if (index < 0) {
+ return;
+ }
+
+ auto *stored = static_cast<std::string *>(SSL_get_ex_data(ssl, index));
+ if (stored == nullptr) {
+ stored = new std::string;
+ if (SSL_set_ex_data(ssl, index, stored) != 1) {
+ delete stored;
+ return;
+ }
+ }
+ stored->assign(servername);
+
+ if (auto *snis = TLSSNISupport::getInstance(ssl); snis != nullptr) {
+ snis->set_sni_server_name(servername);
+ }
+}
+
+bool
+apply_quic_sni_certificate(SSL *ssl, SSL_CTX *ctx)
+{
+ X509 *cert = SSL_CTX_get0_certificate(ctx);
+ EVP_PKEY *key = SSL_CTX_get0_privatekey(ctx);
+
+ if (cert == nullptr && key == nullptr) {
+ return true;
+ }
+ if (cert == nullptr || key == nullptr) {
+ return false;
+ }
+
+ if (SSL_use_certificate(ssl, cert) != 1 || SSL_use_PrivateKey(ssl, key) !=
1) {
+ return false;
+ }
+
+ STACK_OF(X509) *chain = nullptr;
+ if (SSL_CTX_get0_chain_certs(ctx, &chain) == 1 && chain != nullptr &&
SSL_set1_chain(ssl, chain) != 1) {
+ return false;
+ }
+
+ return true;
+}
+
+bool
+select_quic_sni_context(SSL *ssl, std::string_view servername)
+{
+ if (servername.empty()) {
+ return true;
+ }
+
+ QUICCertConfig::scoped_config lookup;
+ if (!lookup) {
+ Dbg(dbg_ctl_quic, "OpenSSL QUIC SNI context selection could not acquire
cert lookup");
+ return false;
+ }
+
+ SSLCertContext *cert_context = lookup->find(std::string(servername));
+ if (cert_context == nullptr) {
+ return true;
+ }
+
+ shared_SSL_CTX const selected_ctx = cert_context->getCtx();
+ if (selected_ctx == nullptr) {
+ Dbg(dbg_ctl_quic, "OpenSSL QUIC SNI context selection found null context
for SNI '%.*s'", static_cast<int>(servername.size()),
+ servername.data());
+ return false;
+ }
+
+ SSL_set_SSL_CTX(ssl, selected_ctx.get());
+ if (!apply_quic_sni_certificate(ssl, selected_ctx.get())) {
+ Dbg(dbg_ctl_quic, "OpenSSL QUIC SNI context selection failed to apply
SSL_CTX %p certificate to ssl=%p", selected_ctx.get(),
+ ssl);
+ return false;
+ }
+
+ return true;
+}
+
+std::string_view
+servername_from_client_hello(SSL *ssl)
+{
+ unsigned char const *p = nullptr;
+ size_t remaining = 0;
+ if (SSL_client_hello_get0_ext(ssl, TLSEXT_TYPE_server_name, &p, &remaining)
!= 1 || remaining <= 2) {
+ return {};
+ }
+
+ size_t len = (static_cast<size_t>(*p++) << 8);
+ len += *p++;
+ if (len + 2 != remaining) {
+ return {};
+ }
+ remaining = len;
+
+ if (remaining <= 3 || *p++ != TLSEXT_NAMETYPE_host_name) {
+ return {};
+ }
+ --remaining;
+
+ len = (static_cast<size_t>(*p++) << 8);
+ len += *p++;
+ if (len + 2 > remaining) {
+ return {};
+ }
+
+ return {reinterpret_cast<char const *>(p), len};
+}
+
+int
+quic_client_hello_callback(SSL *ssl, int *, void *)
+{
+ auto const servername = servername_from_client_hello(ssl);
+ remember_quic_sni(ssl, servername);
+
+ return select_quic_sni_context(ssl, servername) ? SSL_CLIENT_HELLO_SUCCESS :
SSL_CLIENT_HELLO_ERROR;
+}
+
+int
+quic_servername_callback(SSL *ssl, int *, void *)
+{
+ char const *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
+ auto const sni = servername == nullptr ? std::string_view{} :
std::string_view{servername};
+ remember_quic_sni(ssl, sni);
+
+ return select_quic_sni_context(ssl, sni) ? SSL_TLSEXT_ERR_OK :
SSL_TLSEXT_ERR_ALERT_FATAL;
+}
+} // end anonymous namespace
+
+std::string_view
+quic_sni_server_name(SSL *ssl)
+{
+ if (ssl == nullptr) {
+ return {};
+ }
+
+ auto const index = quic_sni_ex_data_index();
+ if (index < 0) {
+ return {};
+ }
+
+ auto *stored = static_cast<std::string *>(SSL_get_ex_data(ssl, index));
+
+ return stored == nullptr ? std::string_view{} : std::string_view{*stored};
+}
+
+void
+QUICMultiCertConfigLoader::_set_handshake_callbacks(SSL_CTX *ctx)
+{
+ Dbg(dbg_ctl_quic, "installing OpenSSL QUIC cert callback on SSL_CTX %p",
ctx);
+ SSL_CTX_set_client_hello_cb(ctx, quic_client_hello_callback, nullptr);
+ SSL_CTX_set_tlsext_servername_callback(ctx, quic_servername_callback);
+ SSL_CTX_set_cert_cb(
+ ctx,
+ [](SSL *ssl, void *) -> int {
+ char const *servername = SSL_get_servername(ssl,
TLSEXT_NAMETYPE_host_name);
+ auto const sni = servername == nullptr ? std::string_view{} :
std::string_view{servername};
+ remember_quic_sni(ssl, sni);
+
+ return select_quic_sni_context(ssl, sni) ? 1 : 0;
+ },
+ nullptr);
+}
+
+namespace
+{
+int
+quic_alpn_select_callback(SSL * /* ssl ATS_UNUSED */, const unsigned char
**out, unsigned char *outlen, const unsigned char *in,
+ unsigned int inlen, void * /* arg ATS_UNUSED */)
+{
+ static constexpr unsigned char server_protos[] = {2, 'h', '3', 5, 'h', '3',
'-', '2', '9', 5, 'h', '3', '-', '2', '7'};
+
+ if (SSL_select_next_proto(const_cast<unsigned char **>(out), outlen,
server_protos, sizeof(server_protos), in, inlen) ==
+ OPENSSL_NPN_NEGOTIATED) {
+ return SSL_TLSEXT_ERR_OK;
+ }
+
+ *out = nullptr;
+ *outlen = 0;
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+}
+} // end anonymous namespace
+
+bool
+QUICMultiCertConfigLoader::_set_alpn_callback(SSL_CTX *ctx)
+{
+ SSL_CTX_set_alpn_select_cb(ctx, quic_alpn_select_callback, nullptr);
+ return true;
+}
+
+bool
+QUICMultiCertConfigLoader::_set_curves(SSL_CTX *ctx)
+{
+ QUICConfig::scoped_config params;
+ if (params->server_supported_groups() == nullptr) {
+ return true;
+ }
+
+ if (!SSL_CTX_set1_groups_list(ctx, params->server_supported_groups())) {
+ Error("invalid QUIC groups list for server in records.config");
+ return false;
+ }
+
+ return true;
}
+#endif
bool
QUICMultiCertConfigLoader::_setup_session_cache(SSL_CTX * /* ctx ATS_UNUSED */)
diff --git a/src/iocore/net/QUICNetVConnection.cc
b/src/iocore/net/QUICNetVConnection.cc
index 18a8591373..451b1fc124 100644
--- a/src/iocore/net/QUICNetVConnection.cc
+++ b/src/iocore/net/QUICNetVConnection.cc
@@ -679,7 +679,7 @@ QUICNetVConnection::_handle_read_ready()
[[maybe_unused]] QUICConnectionError err;
stream = this->_stream_manager->create_stream(s, err);
}
- stream->receive_data(this->_quiche_con);
+ stream->receive_data(*this);
}
quiche_stream_iter_free(readable);
}
@@ -696,7 +696,7 @@ QUICNetVConnection::_handle_write_ready()
[[maybe_unused]] QUICConnectionError err;
stream = this->_stream_manager->create_stream(s, err);
}
- stream->send_data(this->_quiche_con);
+ stream->send_data(*this);
}
quiche_stream_iter_free(writable);
}
@@ -791,6 +791,31 @@ QUICNetVConnection::get_quic_connection()
return static_cast<QUICConnection *>(this);
}
+int64_t
+QUICNetVConnection::read_stream(QUICStreamId stream_id, uint8_t *buf, size_t
len, bool &fin, QUICStreamIO::ErrorCode &error_code)
+{
+ return quiche_conn_stream_recv(this->_quiche_con, stream_id, buf, len, &fin,
&error_code);
+}
+
+bool
+QUICNetVConnection::stream_read_finished(QUICStreamId stream_id)
+{
+ return quiche_conn_stream_finished(this->_quiche_con, stream_id);
+}
+
+int64_t
+QUICNetVConnection::stream_write_capacity(QUICStreamId stream_id)
+{
+ return quiche_conn_stream_capacity(this->_quiche_con, stream_id);
+}
+
+int64_t
+QUICNetVConnection::write_stream(QUICStreamId stream_id, const uint8_t *buf,
size_t len, bool fin,
+ QUICStreamIO::ErrorCode &error_code)
+{
+ return quiche_conn_stream_send(this->_quiche_con, stream_id,
const_cast<uint8_t *>(buf), len, fin, &error_code);
+}
+
void
QUICNetVConnection::reenable(int event)
{
diff --git a/src/iocore/net/SSLSessionTicket.cc
b/src/iocore/net/SSLSessionTicket.cc
index 7d6674bd64..58ff5fe46a 100644
--- a/src/iocore/net/SSLSessionTicket.cc
+++ b/src/iocore/net/SSLSessionTicket.cc
@@ -53,6 +53,14 @@ ssl_callback_session_ticket(SSL *ssl, unsigned char
*keyname, unsigned char *iv,
if (srs) {
return srs->processSessionTicket(ssl, keyname, iv, cipher_ctx, hctx, enc);
} else {
+#if TS_HAS_OPENSSL_QUIC
+ if (SSL_is_quic(ssl) == 1) {
+ // OpenSSL native QUIC can parse client-offered tickets while servicing
+ // the listener, before ATS has accepted and bound a QUIC NetVC.
+ return 0;
+ }
+#endif
+
// We could implement a default behavior that would have been done if this
callback was not registered, but it's not necessary
// at the moment because TLSSessionResumptionSupport is always available
when the callback is registered.
ink_assert(!"srs should be available");
diff --git a/src/iocore/net/TLSSNISupport.cc b/src/iocore/net/TLSSNISupport.cc
index b6acd202b2..55e0a673a4 100644
--- a/src/iocore/net/TLSSNISupport.cc
+++ b/src/iocore/net/TLSSNISupport.cc
@@ -162,6 +162,16 @@ TLSSNISupport::set_sni_server_name(SSL *ssl, char const
*name)
return true;
}
+void
+TLSSNISupport::set_sni_server_name(std::string_view name)
+{
+ if (name.empty()) {
+ this->_clear();
+ } else {
+ this->_set_sni_server_name_buffer(name);
+ }
+}
+
void
TLSSNISupport::_clear()
{
diff --git a/src/iocore/net/quic/CMakeLists.txt
b/src/iocore/net/quic/CMakeLists.txt
index 4151917c01..0d559c72dd 100644
--- a/src/iocore/net/quic/CMakeLists.txt
+++ b/src/iocore/net/quic/CMakeLists.txt
@@ -26,11 +26,25 @@ add_library(
QUICTypes.cc
QUICIntUtil.cc
QUICStream.cc
- QUICStream.cc
QUICStreamManager.cc
QUICStreamAdapter.cc
QUICStreamVCAdapter.cc
)
add_library(ts::quic ALIAS quic)
-target_link_libraries(quic PUBLIC ts::inkevent ts::inknet ts::tscore
OpenSSL::Crypto OpenSSL::SSL quiche::quiche)
+target_link_libraries(quic PUBLIC ts::inkevent ts::inknet ts::tscore
OpenSSL::Crypto OpenSSL::SSL)
+
+if(TS_HAS_QUICHE)
+ target_link_libraries(quic PUBLIC quiche::quiche)
+endif()
+
+if(TS_OPENSSL_QUIC_TLS_CBS_COMPAT AND TS_HAS_QUICHE)
+ add_library(openssl_quic_compat STATIC OpenSSLQuicCompat.cc)
+ add_library(ts::openssl_quic_compat ALIAS openssl_quic_compat)
+ target_link_libraries(openssl_quic_compat PUBLIC OpenSSL::SSL
OpenSSL::Crypto)
+ set_property(
+ TARGET quiche::quiche
+ APPEND
+ PROPERTY INTERFACE_LINK_LIBRARIES ts::openssl_quic_compat
+ )
+endif()
diff --git a/src/iocore/net/quic/OpenSSLQuicCompat.cc
b/src/iocore/net/quic/OpenSSLQuicCompat.cc
new file mode 100644
index 0000000000..7afafff280
--- /dev/null
+++ b/src/iocore/net/quic/OpenSSLQuicCompat.cc
@@ -0,0 +1,493 @@
+/** @file
+
+ Bridges quiche's legacy QUIC TLS callback API to OpenSSL 3.5's third-party
+ QUIC TLS callback API.
+
+ This compatibility layer exports the quictls/BoringSSL-style symbols that
+ quiche expects while ATS links against upstream OpenSSL 3.5. It stores
+ per-SSL callback state in OpenSSL ex-data and translates CRYPTO data,
+ encryption secrets, alerts, and transport parameters between the two APIs.
+
+ 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 <openssl/core_dispatch.h>
+#include <openssl/crypto.h>
+#include <openssl/ssl.h>
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <deque>
+#include <utility>
+#include <vector>
+
+enum ssl_encryption_level_t {
+ ssl_encryption_initial = 0,
+ ssl_encryption_early_data,
+ ssl_encryption_handshake,
+ ssl_encryption_application,
+};
+
+struct ssl_quic_method_st {
+ int (*set_encryption_secrets)(SSL *ssl, ssl_encryption_level_t level,
uint8_t const *read_secret, uint8_t const *write_secret,
+ size_t secret_len);
+ int (*add_handshake_data)(SSL *ssl, ssl_encryption_level_t level, uint8_t
const *data, size_t len);
+ int (*flush_flight)(SSL *ssl);
+ int (*send_alert)(SSL *ssl, ssl_encryption_level_t level, uint8_t alert);
+};
+
+using SSL_QUIC_METHOD = ssl_quic_method_st;
+
+namespace
+{
+
+constexpr auto read_secret_direction = 0;
+constexpr auto write_secret_direction = 1;
+constexpr auto quic_level_count = 4;
+
+struct PendingSecret {
+ std::vector<uint8_t> read;
+ std::vector<uint8_t> write;
+ bool have_read{false};
+ bool have_write{false};
+ bool delivered{false};
+};
+
+struct QuicCompatState {
+ SSL_QUIC_METHOD const
*method{nullptr};
+ std::array<std::deque<std::vector<uint8_t>>, quic_level_count> crypto_data;
+ std::array<PendingSecret, quic_level_count> secrets;
+ ssl_encryption_level_t
read_level{ssl_encryption_initial};
+ ssl_encryption_level_t
write_level{ssl_encryption_initial};
+ ssl_encryption_level_t
active_recv_level{ssl_encryption_initial};
+ bool
active_recv{false};
+ std::vector<uint8_t>
local_transport_params;
+ std::vector<uint8_t>
peer_transport_params;
+};
+
+void
+free_quic_ex_data(void *, void *ptr, CRYPTO_EX_DATA *, int, long, void *)
+{
+ delete static_cast<QuicCompatState *>(ptr);
+}
+
+int
+quic_ex_data_index()
+{
+ static int index = SSL_get_ex_new_index(0, nullptr, nullptr, nullptr,
free_quic_ex_data);
+
+ return index;
+}
+
+bool
+is_valid_level(ssl_encryption_level_t level)
+{
+ auto index = static_cast<int>(level);
+
+ return index >= 0 && index < quic_level_count;
+}
+
+size_t
+level_index(ssl_encryption_level_t level)
+{
+ return static_cast<size_t>(level);
+}
+
+uint8_t const *
+data_or_null(std::vector<uint8_t> const &data)
+{
+ return data.empty() ? nullptr : data.data();
+}
+
+bool
+assign_bytes(std::vector<uint8_t> &dst, uint8_t const *data, size_t len)
+{
+ if (len == 0) {
+ dst.clear();
+ return true;
+ }
+ if (data == nullptr) {
+ return false;
+ }
+
+ dst.assign(data, data + len);
+ return true;
+}
+
+QuicCompatState *
+get_state(SSL const *ssl)
+{
+ if (ssl == nullptr) {
+ return nullptr;
+ }
+
+ int const index = quic_ex_data_index();
+ return index < 0 ? nullptr : static_cast<QuicCompatState
*>(SSL_get_ex_data(ssl, index));
+}
+
+QuicCompatState *
+get_or_create_state(SSL *ssl)
+{
+ if (ssl == nullptr) {
+ return nullptr;
+ }
+
+ if (auto *state = get_state(ssl); state != nullptr) {
+ return state;
+ }
+
+ auto *state = new QuicCompatState;
+ int const index = quic_ex_data_index();
+ if (index < 0 || SSL_set_ex_data(ssl, index, state) != 1) {
+ delete state;
+ return nullptr;
+ }
+
+ return state;
+}
+
+bool
+compat_level_from_protection(uint32_t prot_level, ssl_encryption_level_t
&level)
+{
+ switch (prot_level) {
+ case OSSL_RECORD_PROTECTION_LEVEL_NONE:
+ level = ssl_encryption_initial;
+ return true;
+ case OSSL_RECORD_PROTECTION_LEVEL_EARLY:
+ level = ssl_encryption_early_data;
+ return true;
+ case OSSL_RECORD_PROTECTION_LEVEL_HANDSHAKE:
+ level = ssl_encryption_handshake;
+ return true;
+ case OSSL_RECORD_PROTECTION_LEVEL_APPLICATION:
+ level = ssl_encryption_application;
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool
+deliver_0rtt_secret(SSL *ssl, QuicCompatState &state, ssl_encryption_level_t
level, int direction,
+ std::vector<uint8_t> const &secret)
+{
+ if (state.method == nullptr || state.method->set_encryption_secrets ==
nullptr) {
+ return false;
+ }
+
+ bool const is_server = SSL_is_server(ssl) == 1;
+ if (direction == read_secret_direction && is_server) {
+ return state.method->set_encryption_secrets(ssl, level,
data_or_null(secret), nullptr, secret.size()) == 1;
+ }
+
+ if (direction == write_secret_direction && !is_server) {
+ return state.method->set_encryption_secrets(ssl, level, nullptr,
data_or_null(secret), secret.size()) == 1;
+ }
+
+ return true;
+}
+
+bool
+deliver_paired_secrets(SSL *ssl, QuicCompatState &state,
ssl_encryption_level_t level)
+{
+ if (state.method == nullptr || state.method->set_encryption_secrets ==
nullptr) {
+ return false;
+ }
+
+ auto &pending = state.secrets[level_index(level)];
+ if (!pending.have_read || !pending.have_write || pending.delivered) {
+ return true;
+ }
+ if (pending.read.size() != pending.write.size()) {
+ return false;
+ }
+
+ int const result =
+ state.method->set_encryption_secrets(ssl, level,
data_or_null(pending.read), data_or_null(pending.write), pending.read.size());
+ if (result == 1) {
+ pending.delivered = true;
+ }
+
+ return result == 1;
+}
+
+int
+crypto_send_cb(SSL *ssl, unsigned char const *buf, size_t buf_len, size_t
*consumed, void *)
+{
+ auto *state = get_state(ssl);
+ if (state == nullptr || state->method == nullptr ||
state->method->add_handshake_data == nullptr) {
+ return 0;
+ }
+
+ if (consumed != nullptr) {
+ *consumed = 0;
+ }
+
+ static constexpr uint8_t empty_data = 0;
+ if (buf == nullptr && buf_len > 0) {
+ return 0;
+ }
+
+ auto const *data = buf_len == 0 ? &empty_data : reinterpret_cast<uint8_t
const *>(buf);
+ if (state->method->add_handshake_data(ssl, state->write_level, data,
buf_len) != 1) {
+ return 0;
+ }
+ if (state->method->flush_flight != nullptr &&
state->method->flush_flight(ssl) != 1) {
+ return 0;
+ }
+
+ if (consumed != nullptr) {
+ *consumed = buf_len;
+ }
+
+ return 1;
+}
+
+int
+crypto_recv_rcd_cb(SSL *ssl, unsigned char const **buf, size_t *bytes_read,
void *)
+{
+ auto *state = get_state(ssl);
+ if (state == nullptr || buf == nullptr || bytes_read == nullptr) {
+ return 0;
+ }
+
+ *buf = nullptr;
+ *bytes_read = 0;
+
+ if (state->active_recv) {
+ auto &active_queue =
state->crypto_data[level_index(state->active_recv_level)];
+ if (!active_queue.empty()) {
+ *buf = active_queue.front().data();
+ *bytes_read = active_queue.front().size();
+ }
+ return 1;
+ }
+
+ auto &queue = state->crypto_data[level_index(state->read_level)];
+ if (queue.empty()) {
+ return 1;
+ }
+
+ state->active_recv = true;
+ state->active_recv_level = state->read_level;
+ *buf = queue.front().data();
+ *bytes_read = queue.front().size();
+
+ return 1;
+}
+
+int
+crypto_release_rcd_cb(SSL *ssl, size_t bytes_read, void *)
+{
+ auto *state = get_state(ssl);
+ if (state == nullptr || !state->active_recv) {
+ return 1;
+ }
+
+ auto &queue = state->crypto_data[level_index(state->active_recv_level)];
+ if (!queue.empty()) {
+ if (bytes_read >= queue.front().size()) {
+ queue.pop_front();
+ } else {
+ queue.front().erase(queue.front().begin(), queue.front().begin() +
bytes_read);
+ }
+ }
+ state->active_recv = false;
+
+ return 1;
+}
+
+int
+yield_secret_cb(SSL *ssl, uint32_t prot_level, int direction, unsigned char
const *secret, size_t secret_len, void *)
+{
+ auto *state = get_state(ssl);
+ if (state == nullptr) {
+ return 0;
+ }
+
+ ssl_encryption_level_t level = ssl_encryption_initial;
+ if (!compat_level_from_protection(prot_level, level)) {
+ return 0;
+ }
+
+ if (direction == read_secret_direction) {
+ state->read_level = level;
+ } else if (direction == write_secret_direction) {
+ state->write_level = level;
+ } else {
+ return 0;
+ }
+
+ std::vector<uint8_t> secret_copy;
+ if (!assign_bytes(secret_copy, reinterpret_cast<uint8_t const *>(secret),
secret_len)) {
+ return 0;
+ }
+
+ if (level == ssl_encryption_early_data) {
+ return deliver_0rtt_secret(ssl, *state, level, direction, secret_copy) ? 1
: 0;
+ }
+
+ auto &pending = state->secrets[level_index(level)];
+ if (direction == read_secret_direction) {
+ pending.read = std::move(secret_copy);
+ pending.have_read = true;
+ } else {
+ pending.write = std::move(secret_copy);
+ pending.have_write = true;
+ }
+ pending.delivered = false;
+
+ return deliver_paired_secrets(ssl, *state, level) ? 1 : 0;
+}
+
+int
+got_transport_params_cb(SSL *ssl, unsigned char const *params, size_t
params_len, void *)
+{
+ auto *state = get_state(ssl);
+ if (state == nullptr) {
+ return 0;
+ }
+
+ if (!assign_bytes(state->peer_transport_params, reinterpret_cast<uint8_t
const *>(params), params_len)) {
+ return 0;
+ }
+
+ return 1;
+}
+
+int
+alert_cb(SSL *ssl, unsigned char alert_code, void *)
+{
+ auto *state = get_state(ssl);
+ if (state == nullptr || state->method == nullptr ||
state->method->send_alert == nullptr) {
+ return 0;
+ }
+
+ return state->method->send_alert(ssl, state->write_level, alert_code);
+}
+
+OSSL_DISPATCH const quic_tls_callbacks[] = {
+ {OSSL_FUNC_SSL_QUIC_TLS_CRYPTO_SEND, reinterpret_cast<void
(*)(void)>(crypto_send_cb) },
+ {OSSL_FUNC_SSL_QUIC_TLS_CRYPTO_RECV_RCD, reinterpret_cast<void
(*)(void)>(crypto_recv_rcd_cb) },
+ {OSSL_FUNC_SSL_QUIC_TLS_CRYPTO_RELEASE_RCD, reinterpret_cast<void
(*)(void)>(crypto_release_rcd_cb) },
+ {OSSL_FUNC_SSL_QUIC_TLS_YIELD_SECRET, reinterpret_cast<void
(*)(void)>(yield_secret_cb) },
+ {OSSL_FUNC_SSL_QUIC_TLS_GOT_TRANSPORT_PARAMS, reinterpret_cast<void
(*)(void)>(got_transport_params_cb)},
+ {OSSL_FUNC_SSL_QUIC_TLS_ALERT, reinterpret_cast<void
(*)(void)>(alert_cb) },
+ {0, nullptr
},
+};
+
+} // namespace
+
+extern "C" int
+SSL_set_quic_method(SSL *ssl, SSL_QUIC_METHOD const *quic_method)
+{
+ auto *state = get_or_create_state(ssl);
+ if (state == nullptr) {
+ return 0;
+ }
+
+ state->method = quic_method;
+
+ return SSL_set_quic_tls_cbs(ssl, quic_tls_callbacks, nullptr);
+}
+
+extern "C" int
+SSL_set_quic_transport_params(SSL *ssl, uint8_t const *params, size_t
params_len)
+{
+ auto *state = get_or_create_state(ssl);
+ if (state == nullptr) {
+ return 0;
+ }
+
+ if (!assign_bytes(state->local_transport_params, params, params_len)) {
+ return 0;
+ }
+ return SSL_set_quic_tls_transport_params(ssl,
data_or_null(state->local_transport_params),
state->local_transport_params.size());
+}
+
+extern "C" void
+SSL_get_peer_quic_transport_params(SSL const *ssl, uint8_t const **out_params,
size_t *out_params_len)
+{
+ auto const *state = get_state(ssl);
+ if (out_params != nullptr) {
+ *out_params = state == nullptr ? nullptr :
data_or_null(state->peer_transport_params);
+ }
+ if (out_params_len != nullptr) {
+ *out_params_len = state == nullptr ? 0 :
state->peer_transport_params.size();
+ }
+}
+
+extern "C" ssl_encryption_level_t
+SSL_quic_write_level(SSL const *ssl)
+{
+ auto const *state = get_state(ssl);
+
+ return state == nullptr ? ssl_encryption_initial : state->write_level;
+}
+
+extern "C" int
+SSL_provide_quic_data(SSL *ssl, ssl_encryption_level_t level, uint8_t const
*data, size_t len)
+{
+ auto *state = get_or_create_state(ssl);
+ if (state == nullptr || !is_valid_level(level)) {
+ return 0;
+ }
+
+ auto &records = state->crypto_data[level_index(level)];
+ records.emplace_back();
+ if (!assign_bytes(records.back(), data, len)) {
+ records.pop_back();
+ return 0;
+ }
+
+ return 1;
+}
+
+extern "C" int
+SSL_process_quic_post_handshake(SSL *ssl)
+{
+ auto *state = get_state(ssl);
+ if (state == nullptr) {
+ return 0;
+ }
+
+ bool has_crypto_data = false;
+ for (auto const &queue : state->crypto_data) {
+ if (!queue.empty()) {
+ has_crypto_data = true;
+ break;
+ }
+ }
+ if (!has_crypto_data) {
+ return 1;
+ }
+
+ unsigned char data = 0;
+ size_t bytes_read = 0;
+ int const result = SSL_read_ex(ssl, &data, 0, &bytes_read);
+
+ if (result == 1) {
+ return 1;
+ }
+
+ int const error = SSL_get_error(ssl, result);
+
+ return error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE;
+}
+
+extern "C" void
+SSL_set_quic_use_legacy_codepoint(SSL *, int)
+{
+}
diff --git a/src/iocore/net/quic/QUICConfig.cc
b/src/iocore/net/quic/QUICConfig.cc
index 9905ae0688..7d9fe28927 100644
--- a/src/iocore/net/quic/QUICConfig.cc
+++ b/src/iocore/net/quic/QUICConfig.cc
@@ -23,6 +23,9 @@
#include "iocore/net/quic/QUICConfig.h"
+#if TS_HAS_OPENSSL_QUIC
+#include <openssl/quic.h>
+#endif
#include <openssl/ssl.h>
#include "mgmt/config/ConfigContextDiags.h"
@@ -54,6 +57,24 @@ quic_new_ssl_ctx()
return ssl_ctx;
}
+SSL_CTX *
+quic_new_server_ssl_ctx()
+{
+#if TS_HAS_OPENSSL_QUIC
+ SSL_CTX *ssl_ctx = SSL_CTX_new(OSSL_QUIC_server_method());
+ if (ssl_ctx == nullptr) {
+ return nullptr;
+ }
+
+ SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_3_VERSION);
+ SSL_CTX_set_max_proto_version(ssl_ctx, TLS1_3_VERSION);
+
+ return ssl_ctx;
+#else
+ return quic_new_ssl_ctx();
+#endif
+}
+
/**
ALPN and SNI should be set to SSL object with NETVC_OPTIONS
**/
@@ -429,6 +450,7 @@ QUICConfigParams::disable_http_0_9() const
return this->_disable_http_0_9;
}
+#if TS_HAS_QUICHE
quiche_cc_algorithm
QUICConfigParams::get_cc_algorithm() const
{
@@ -441,6 +463,7 @@ QUICConfigParams::get_cc_algorithm() const
return QUICHE_CC_RENO;
}
}
+#endif
//
// QUICConfig
diff --git a/src/iocore/net/quic/QUICGlobals.cc
b/src/iocore/net/quic/QUICGlobals.cc
index b44366f8d2..57d7b4d067 100644
--- a/src/iocore/net/quic/QUICGlobals.cc
+++ b/src/iocore/net/quic/QUICGlobals.cc
@@ -54,8 +54,7 @@ QUIC::init()
int
QUIC::ssl_client_new_session([[maybe_unused]] SSL *ssl, [[maybe_unused]]
SSL_SESSION *session)
{
-#if TS_HAS_QUICHE
-#else
+#if !TS_HAS_OPENSSL_QUIC && !TS_HAS_QUICHE
QUICTLS *qtls = static_cast<QUICTLS *>(SSL_get_ex_data(ssl,
QUIC::ssl_quic_tls_index));
const char *session_file = qtls->session_file();
auto file = BIO_new_file(session_file, "w");
diff --git a/src/iocore/net/quic/QUICStream.cc
b/src/iocore/net/quic/QUICStream.cc
index cdc427c9af..9f8de91fa0 100644
--- a/src/iocore/net/quic/QUICStream.cc
+++ b/src/iocore/net/quic/QUICStream.cc
@@ -118,36 +118,43 @@ QUICStream::on_eos()
}
void
-QUICStream::receive_data(quiche_conn *quiche_con)
+QUICStream::receive_data(QUICStreamIO &stream_io)
{
uint8_t buf[4096];
- bool fin;
- ssize_t read_len = 0;
- [[maybe_unused]] ErrorCode error_code{0}; // Only set if
QUICHE_ERR_STREAM_STOPPED(-15) or QUICHE_ERR_STREAM_RESET(-16) are
- // returned by
quiche_conn_stream_recv.
+ bool fin = false;
+ bool delivered = false;
+ ssize_t read_len = 0;
+ [[maybe_unused]] ErrorCode error_code{0};
- while ((read_len = quiche_conn_stream_recv(quiche_con, this->_id, buf,
sizeof(buf), &fin, &error_code)) > 0) {
+ while ((read_len = stream_io.read_stream(this->_id, buf, sizeof(buf), fin,
error_code)) > 0) {
this->_adapter->write(this->_received_bytes, buf, read_len, fin);
this->_received_bytes += read_len;
+ delivered = true;
}
- this->_has_no_more_data = quiche_conn_stream_finished(quiche_con, this->_id);
- this->_adapter->encourge_read();
+ if (read_len == 0 && fin && !this->_has_no_more_data) {
+ this->_adapter->write(this->_received_bytes, buf, 0, true);
+ delivered = true;
+ }
+ this->_has_no_more_data = stream_io.stream_read_finished(this->_id);
+
+ if (delivered) {
+ this->_adapter->encourge_read();
+ }
}
-void
-QUICStream::send_data(quiche_conn *quiche_con)
+int64_t
+QUICStream::send_data(QUICStreamIO &stream_io)
{
bool fin = false;
ssize_t len = 0;
- [[maybe_unused]] ErrorCode error_code{0}; // Only set if
QUICHE_ERR_STREAM_STOPPED(-15) or QUICHE_ERR_STREAM_RESET(-16) are
- // returned by
quiche_conn_stream_send.
- size_t written_this_event = 0;
+ [[maybe_unused]] ErrorCode error_code{0};
+ size_t written_this_event = 0;
while (written_this_event < MAX_STREAM_SEND_BYTES_PER_EVENT) {
- len = quiche_conn_stream_capacity(quiche_con, this->_id);
+ len = stream_io.stream_write_capacity(this->_id);
if (len <= 0) {
- return;
+ return written_this_event;
}
if (!this->_pending_send_block) {
@@ -156,13 +163,14 @@ QUICStream::send_data(quiche_conn *quiche_con)
if (!this->_pending_send_block) {
if (!this->_sent_fin && this->_adapter->is_eos() &&
this->_adapter->total_len() == this->_sent_bytes) {
static constexpr uint8_t empty_data = 0;
- ssize_t written_len =
quiche_conn_stream_send(quiche_con, this->_id, &empty_data, 0, true,
&error_code);
+ ssize_t written_len =
stream_io.write_stream(this->_id, &empty_data, 0, true, error_code);
if (written_len >= 0) {
this->_sent_fin = true;
+ return written_this_event + static_cast<size_t>(written_len);
}
}
this->_adapter->encourge_write();
- return;
+ return written_this_event;
}
this->_pending_send_fin = this->_adapter->total_len() ==
this->_sent_bytes + this->_pending_send_block->size();
}
@@ -177,28 +185,30 @@ QUICStream::send_data(quiche_conn *quiche_con)
}
if (block->size() > 0 || fin) {
- ssize_t written_len = quiche_conn_stream_send(quiche_con, this->_id,
reinterpret_cast<uint8_t *>(block->start()),
- block->size(), fin,
&error_code);
+ ssize_t written_len =
+ stream_io.write_stream(this->_id, reinterpret_cast<uint8_t
*>(block->start()), block->size(), fin, error_code);
if (written_len >= 0) {
this->_adapter->consume(written_len);
this->_sent_bytes += written_len;
- written_this_event += written_len;
+ written_this_event += static_cast<size_t>(written_len);
if (written_len >= block->size()) {
this->_pending_send_block = nullptr;
this->_pending_send_fin = false;
this->_sent_fin = fin;
} else {
block->consume(written_len);
- return;
+ return written_this_event;
}
if (!this->has_data_to_send()) {
this->_adapter->encourge_write();
- return;
+ return written_this_event;
}
continue;
}
}
this->_adapter->encourge_write();
- return;
+ return written_this_event;
}
+
+ return written_this_event;
}
diff --git a/src/iocore/net/quic/QUICStreamManager.cc
b/src/iocore/net/quic/QUICStreamManager.cc
index b3b82692fa..4fbeb7b870 100644
--- a/src/iocore/net/quic/QUICStreamManager.cc
+++ b/src/iocore/net/quic/QUICStreamManager.cc
@@ -113,14 +113,38 @@ QUICStreamManager::create_stream(QUICStreamId stream_id,
QUICConnectionError & /
}
QUICConnectionErrorUPtr
-QUICStreamManager::create_uni_stream(QUICStreamId /* new_stream_id ATS_UNUSED
*/)
+QUICStreamManager::create_uni_stream(QUICStreamId &new_stream_id)
{
+ if (this->_next_local_uni_stream_id == 0) {
+ this->_next_local_uni_stream_id =
+ static_cast<QUICStreamId>(this->_context->connection_info()->direction()
== NET_VCONNECTION_IN ? QUICStreamType::SERVER_UNI :
+
QUICStreamType::CLIENT_UNI);
+ }
+
+ new_stream_id = this->_next_local_uni_stream_id;
+ this->_next_local_uni_stream_id += 4;
+
+ [[maybe_unused]] QUICConnectionError err;
+ this->create_stream(new_stream_id, err);
+
return nullptr;
}
QUICConnectionErrorUPtr
-QUICStreamManager::create_bidi_stream(QUICStreamId /* new_stream_id ATS_UNUSED
*/)
+QUICStreamManager::create_bidi_stream(QUICStreamId &new_stream_id)
{
+ if (this->_next_local_bidi_stream_id == 0) {
+ this->_next_local_bidi_stream_id =
+ static_cast<QUICStreamId>(this->_context->connection_info()->direction()
== NET_VCONNECTION_IN ? QUICStreamType::SERVER_BIDI :
+
QUICStreamType::CLIENT_BIDI);
+ }
+
+ new_stream_id = this->_next_local_bidi_stream_id;
+ this->_next_local_bidi_stream_id += 4;
+
+ [[maybe_unused]] QUICConnectionError err;
+ this->create_stream(new_stream_id, err);
+
return nullptr;
}
diff --git a/src/proxy/http3/Http3App.cc b/src/proxy/http3/Http3App.cc
index 1f3d1a3172..cca7960303 100644
--- a/src/proxy/http3/Http3App.cc
+++ b/src/proxy/http3/Http3App.cc
@@ -296,8 +296,13 @@ Http3App::_handle_uni_stream_on_read_ready(int /* event
*/, VIO *vio)
// Recipients of unknown stream types MUST either abort reading of the
stream or discard incoming data without further
// processing. If reading is aborted, the recipient SHOULD use the
H3_STREAM_CREATION_ERROR error code or a reserved error code
// (Section 8.1). The recipient MUST NOT consider unknown stream types to
be a connection error of any kind.
- error = std::make_unique<Http3Error>(Http3ErrorClass::STREAM,
Http3ErrorCode::H3_STREAM_CREATION_ERROR, "Stream type unkown");
- Dbg(dbg_ctl, "UNKNOWN stream [%" PRIu64 "] error: %hu, %s",
adapter->stream().id(), error->get_code(), error->msg);
+ IOBufferReader *reader = vio->get_reader();
+ if (reader != nullptr) {
+ int64_t const bytes_to_discard = reader->read_avail();
+
+ reader->consume(bytes_to_discard);
+ vio->ndone += bytes_to_discard;
+ }
break;
}
default:
diff --git a/src/proxy/http3/Http3DataFramer.cc
b/src/proxy/http3/Http3DataFramer.cc
index f9e52be630..17d626b251 100644
--- a/src/proxy/http3/Http3DataFramer.cc
+++ b/src/proxy/http3/Http3DataFramer.cc
@@ -53,5 +53,5 @@ Http3DataFramer::generate_frame()
bool
Http3DataFramer::is_done() const
{
- return this->_source_vio->ntodo() == 0;
+ return this->_transaction->is_response_header_sent() &&
this->_source_vio->ntodo() == 0;
}
diff --git a/src/proxy/http3/Http3Frame.cc b/src/proxy/http3/Http3Frame.cc
index 6fc7d5b544..2c15ee03ef 100644
--- a/src/proxy/http3/Http3Frame.cc
+++ b/src/proxy/http3/Http3Frame.cc
@@ -266,6 +266,12 @@ Http3DataFrame::payload_length() const
return this->_length;
}
+bool
+Http3DataFrame::_parse()
+{
+ return this->_reader->read_avail() >= static_cast<int64_t>(this->_length);
+}
+
IOBufferReader *
Http3DataFrame::data() const
{
diff --git a/src/proxy/http3/Http3FrameCounter.cc
b/src/proxy/http3/Http3FrameCounter.cc
index 1ac83210d2..d9696735a8 100644
--- a/src/proxy/http3/Http3FrameCounter.cc
+++ b/src/proxy/http3/Http3FrameCounter.cc
@@ -30,8 +30,7 @@ Http3FrameCounter::interests()
{
return {Http3FrameType::DATA, Http3FrameType::HEADERS,
Http3FrameType::X_RESERVED_1, Http3FrameType::CANCEL_PUSH,
Http3FrameType::SETTINGS, Http3FrameType::PUSH_PROMISE,
Http3FrameType::X_RESERVED_2, Http3FrameType::GOAWAY,
- Http3FrameType::X_RESERVED_3, Http3FrameType::X_RESERVED_4,
Http3FrameType::MAX_PUSH_ID, Http3FrameType::X_MAX_DEFINED,
- Http3FrameType::UNKNOWN};
+ Http3FrameType::X_RESERVED_3, Http3FrameType::X_RESERVED_4,
Http3FrameType::MAX_PUSH_ID, Http3FrameType::UNKNOWN};
}
Http3ErrorUPtr
diff --git a/src/proxy/http3/Http3FrameDispatcher.cc
b/src/proxy/http3/Http3FrameDispatcher.cc
index fb0e797086..f697cb4ead 100644
--- a/src/proxy/http3/Http3FrameDispatcher.cc
+++ b/src/proxy/http3/Http3FrameDispatcher.cc
@@ -42,8 +42,14 @@ DbgCtl dbg_ctl_http3{"http3"};
void
Http3FrameDispatcher::add_handler(Http3FrameHandler *handler)
{
+ bool registered[256] = {};
+
for (Http3FrameType t : handler->interests()) {
- this->_handlers[static_cast<uint8_t>(t)].push_back(handler);
+ auto const type = static_cast<uint8_t>(t);
+ if (!registered[type]) {
+ this->_handlers[type].push_back(handler);
+ registered[type] = true;
+ }
}
}
diff --git a/src/proxy/http3/Http3HeaderFramer.cc
b/src/proxy/http3/Http3HeaderFramer.cc
index f514fda7b8..94410be022 100644
--- a/src/proxy/http3/Http3HeaderFramer.cc
+++ b/src/proxy/http3/Http3HeaderFramer.cc
@@ -73,6 +73,9 @@ Http3HeaderFramer::generate_frame()
if (this->_header_block_len == this->_header_block_wrote) {
this->_sent_all_data = true;
+ if (this->_is_current_header_final) {
+ this->_is_final_header_sent = true;
+ }
}
return frame;
} else {
@@ -86,6 +89,31 @@ Http3HeaderFramer::is_done() const
return this->_sent_all_data;
}
+void
+Http3HeaderFramer::reset()
+{
+ this->_header.destroy();
+ if (this->_header_block != nullptr) {
+ free_MIOBuffer(this->_header_block);
+ }
+
+ this->_header_block = nullptr;
+ this->_header_block_reader = nullptr;
+ this->_header_block_len = 0;
+ this->_header_block_wrote = 0;
+ this->_sent_all_data = false;
+ this->_is_current_header_final = false;
+
+ http_parser_clear(&this->_http_parser);
+ http_parser_init(&this->_http_parser);
+}
+
+bool
+Http3HeaderFramer::is_final_header_sent() const
+{
+ return this->_is_final_header_sent;
+}
+
void
Http3HeaderFramer::_generate_header_block()
{
@@ -104,6 +132,8 @@ Http3HeaderFramer::_generate_header_block()
switch (parse_result) {
case ParseResult::DONE: {
+ this->_is_current_header_final =
+ this->_transaction->direction() == NET_VCONNECTION_OUT ||
!this->_header.expect_final_response();
this->_hvc.convert(this->_header, 1, 3);
this->_header_block = new_MIOBuffer(BUFFER_SIZE_INDEX_32K);
diff --git a/src/proxy/http3/Http3ProtocolEnforcer.cc
b/src/proxy/http3/Http3ProtocolEnforcer.cc
index a37cb543ae..06121d3aa9 100644
--- a/src/proxy/http3/Http3ProtocolEnforcer.cc
+++ b/src/proxy/http3/Http3ProtocolEnforcer.cc
@@ -29,8 +29,8 @@ Http3ProtocolEnforcer::interests()
{
return {Http3FrameType::DATA, Http3FrameType::HEADERS,
Http3FrameType::X_RESERVED_1, Http3FrameType::CANCEL_PUSH,
Http3FrameType::SETTINGS, Http3FrameType::PUSH_PROMISE,
Http3FrameType::X_RESERVED_2, Http3FrameType::GOAWAY,
- Http3FrameType::X_RESERVED_3, Http3FrameType::X_RESERVED_4,
Http3FrameType::MAX_PUSH_ID, Http3FrameType::X_MAX_DEFINED,
- Http3FrameType::RESERVED, Http3FrameType::UNKNOWN};
+ Http3FrameType::X_RESERVED_3, Http3FrameType::X_RESERVED_4,
Http3FrameType::MAX_PUSH_ID, Http3FrameType::RESERVED,
+ Http3FrameType::UNKNOWN};
}
Http3ErrorUPtr
diff --git a/src/proxy/http3/Http3Transaction.cc
b/src/proxy/http3/Http3Transaction.cc
index e1f336f25c..084ea0d566 100644
--- a/src/proxy/http3/Http3Transaction.cc
+++ b/src/proxy/http3/Http3Transaction.cc
@@ -463,7 +463,8 @@ HQTransaction::_is_write_buffer_flushed()
SCOPED_MUTEX_LOCK(lock, this->_info.write_vio->mutex, this_ethread());
- return this->_info.write_vio->ntodo() == 0;
+ IOBufferReader *reader = this->_info.write_vio->get_reader();
+ return reader == nullptr || reader->read_avail() == 0;
}
bool
@@ -539,6 +540,16 @@ Http3Transaction::~Http3Transaction()
this->_data_handler = nullptr;
}
+VIO *
+Http3Transaction::do_io_write(Continuation *c, int64_t nbytes, IOBufferReader
*buf, bool owner)
+{
+ if (c != nullptr && nbytes > 0 && buf != nullptr) {
+ this->_header_framer->reset();
+ }
+
+ return super::do_io_write(c, nbytes, buf, owner);
+}
+
int
Http3Transaction::state_stream_open(int event, Event *edata)
{
@@ -675,7 +686,7 @@ Http3Transaction::on_header_decode_complete()
bool
Http3Transaction::is_response_header_sent() const
{
- return this->_header_framer->is_done();
+ return this->_header_framer->is_final_header_sent();
}
bool
diff --git a/src/proxy/http3/test/test_Http3FrameDispatcher.cc
b/src/proxy/http3/test/test_Http3FrameDispatcher.cc
index 37560e9a15..dec273aff4 100644
--- a/src/proxy/http3/test/test_Http3FrameDispatcher.cc
+++ b/src/proxy/http3/test/test_Http3FrameDispatcher.cc
@@ -27,6 +27,28 @@
#include "proxy/http3/Http3ProtocolEnforcer.h"
#include "Mock.h"
+namespace
+{
+class AliasedInterestsFrameHandler : public Http3FrameHandler
+{
+public:
+ std::vector<Http3FrameType>
+ interests() override
+ {
+ return {Http3FrameType::MAX_PUSH_ID, Http3FrameType::X_MAX_DEFINED};
+ }
+
+ Http3ErrorUPtr
+ handle_frame(std::shared_ptr<const Http3Frame> /* frame ATS_UNUSED */,
Http3StreamType /* s_type ATS_UNUSED */) override
+ {
+ ++this->total_frame_received;
+ return nullptr;
+ }
+
+ int total_frame_received = 0;
+};
+} // namespace
+
TEST_CASE("Http3FrameHandler dispatch", "[http3]")
{
Http3FrameDispatcher http3FrameDispatcher;
@@ -92,7 +114,7 @@ TEST_CASE("Http3FrameHandler dispatch", "[http3]")
total_nread += nread;
CHECK(!error);
}
- CHECK(handler.total_frame_received == 5);
+ CHECK(handler.total_frame_received == 1);
CHECK(total_nread == 19);
}
}
@@ -100,6 +122,31 @@ TEST_CASE("Http3FrameHandler dispatch", "[http3]")
free_MIOBuffer(buf);
}
+TEST_CASE("Http3FrameHandler aliased interests", "[http3]")
+{
+ Http3FrameDispatcher http3_frame_dispatcher;
+ AliasedInterestsFrameHandler handler;
+ http3_frame_dispatcher.add_handler(&handler);
+
+ MIOBuffer *buf = new_MIOBuffer(BUFFER_SIZE_INDEX_512);
+ IOBufferReader *reader = buf->alloc_reader();
+ uint64_t nread = 0;
+ uint8_t input[] = {
+ 0x0d, // Type: MAX_PUSH_ID
+ 0x01, // Length
+ 0x01, // Push ID
+ };
+
+ buf->write(input, sizeof(input));
+
+ auto error = http3_frame_dispatcher.on_read_ready(0,
Http3StreamType::CONTROL, *reader, nread);
+ CHECK(!error);
+ CHECK(handler.total_frame_received == 1);
+ CHECK(nread == sizeof(input));
+
+ free_MIOBuffer(buf);
+}
+
TEST_CASE("control stream tests", "[http3]")
{
Http3FrameDispatcher http3FrameDispatcher;
diff --git a/src/traffic_layout/info.cc b/src/traffic_layout/info.cc
index e77c2926d3..9b1e6c331c 100644
--- a/src/traffic_layout/info.cc
+++ b/src/traffic_layout/info.cc
@@ -160,6 +160,7 @@ produce_features(bool json)
print_feature("TS_USE_HWLOC", TS_USE_HWLOC, json);
print_feature("TS_USE_TLS13", TS_USE_TLS13, json);
print_feature("TS_USE_QUIC", TS_USE_QUIC, json);
+ print_feature("TS_HAS_OPENSSL_QUIC", TS_HAS_OPENSSL_QUIC, json);
print_feature("TS_HAS_QUICHE", TS_HAS_QUICHE, json);
print_feature("TS_HAS_SO_PEERCRED", TS_HAS_SO_PEERCRED, json);
print_feature("TS_USE_REMOTE_UNWINDING", TS_USE_REMOTE_UNWINDING, json);
diff --git a/src/traffic_server/traffic_server.cc
b/src/traffic_server/traffic_server.cc
index fd2f837a8a..5ee457b5be 100644
--- a/src/traffic_server/traffic_server.cc
+++ b/src/traffic_server/traffic_server.cc
@@ -77,7 +77,7 @@ extern "C" int plock(int);
#include "Crash.h"
#include "tscore/signals.h"
#include "../iocore/net/P_Net.h"
-#if TS_HAS_QUICHE
+#if TS_USE_QUIC == 1
#include "../iocore/net/P_QUICNetProcessor.h"
#endif
#include "../iocore/net/P_UDPNet.h"
diff --git a/tests/gold_tests/h3/h3_go_client.test.py
b/tests/gold_tests/h3/h3_go_client.test.py
index d99c1979e1..5feee43500 100644
--- a/tests/gold_tests/h3/h3_go_client.test.py
+++ b/tests/gold_tests/h3/h3_go_client.test.py
@@ -103,7 +103,7 @@ logging:
tr.Setup.Copy("go_h3_client")
tr.Processes.Default.StartBefore(self._server)
tr.Processes.Default.StartBefore(self._ts)
- tr.Processes.Default.Env['GOFLAGS'] = '-mod=readonly'
+ tr.Processes.Default.Env['GOFLAGS'] = '-mod=readonly -modcacherw'
tr.Processes.Default.Env['GOCACHE'] = os.path.join(tr.RunDirectory,
'gocache')
tr.Processes.Default.Env['GOMODCACHE'] = os.path.join(tr.RunDirectory,
'gomodcache')
tr.Processes.Default.Env['GOTOOLCHAIN'] = 'local'
diff --git a/tests/gold_tests/headers/via.test.py
b/tests/gold_tests/headers/via.test.py
index 841736340e..0a9f527cf0 100644
--- a/tests/gold_tests/headers/via.test.py
+++ b/tests/gold_tests/headers/via.test.py
@@ -115,7 +115,7 @@ if not Condition.CurlUsingUnixDomainSocket():
tr.StillRunningAfter = ts
# HTTP 3
- if Condition.HasATSFeature('TS_HAS_QUICHE') and
Condition.HasCurlFeature('http3'):
+ if Condition.HasATSFeature('TS_USE_QUIC') and
Condition.HasCurlFeature('http3'):
tr = Test.AddTestRun()
tr.MakeCurlCommand(
'--verbose --ipv4 --http3 --insecure --header "Host:
www.example.com" https://localhost:{}'.format(