diff --git a/.gitignore b/.gitignore index 6d2f706e0e8..6ac08e2d387 100644 --- a/.gitignore +++ b/.gitignore @@ -62,6 +62,7 @@ src/traffic_layout/traffic_layout src/traffic_logcat/traffic_logcat src/traffic_logstats/traffic_logstats src/traffic_manager/traffic_manager +src/traffic_quic/traffic_quic src/traffic_server/traffic_server src/traffic_top/traffic_top src/traffic_via/traffic_via @@ -92,6 +93,29 @@ lib/perl/lib/Apache/TS.pm iocore/net/test_certlookup iocore/net/test_UDPNet +iocore/net/quic/test_QUICAckFrameCreator +iocore/net/quic/test_QUICAddrVerifyState +iocore/net/quic/test_QUICAltConnectionManager +iocore/net/quic/test_QUICFlowController +iocore/net/quic/test_QUICFrame +iocore/net/quic/test_QUICFrameDispatcher +iocore/net/quic/test_QUICFrameRetransmitter +iocore/net/quic/test_QUICHandshake +iocore/net/quic/test_QUICHandshakeProtocol +iocore/net/quic/test_QUICIncomingFrameBuffer +iocore/net/quic/test_QUICInvariants +iocore/net/quic/test_QUICKeyGenerator +iocore/net/quic/test_QUICLossDetector +iocore/net/quic/test_QUICPacket +iocore/net/quic/test_QUICPacketHeaderProtector +iocore/net/quic/test_QUICPacketFactory +iocore/net/quic/test_QUICStream +iocore/net/quic/test_QUICStreamManager +iocore/net/quic/test_QUICStreamState +iocore/net/quic/test_QUICTransportParameters +iocore/net/quic/test_QUICType +iocore/net/quic/test_QUICTypeUtil +iocore/net/quic/test_QUICVersionNegotiator iocore/aio/test_AIO iocore/eventsystem/test_Buffer iocore/eventsystem/test_Event @@ -99,6 +123,7 @@ iocore/eventsystem/test_MIOBufferWriter iocore/hostdb/test_RefCountCache proxy/hdrs/test_mime +proxy/hdrs/test_Huffmancode proxy/hdrs/test_proxy_hdrs proxy/hdrs/test_hdr_heap proxy/hdrs/test_Huffmancode @@ -107,6 +132,8 @@ proxy/http/test_proxy_http proxy/http2/test_Http2DependencyTree proxy/http2/test_HPACK proxy/http2/hpack-tests/results +proxy/http3/test_libhttp3 +proxy/http3/test_qpack proxy/logging/test_LogUtils proxy/logging/test_LogUtils2 diff --git a/README b/README index fbadc41254c..29e96b68645 100644 --- a/README +++ b/README @@ -26,6 +26,7 @@ plugins to build large scale web applications. |-- eventsystem/ ...... Event Driven Engine |-- hostdb/ ........... Internal DNS cache |-- net/ .............. Network + |-- quic/ ......... QUIC implementation |-- utils/ ............ Utilities |-- lib/ .................. |-- perl/ ............. Perl libraries for e.g. mgmt access and configurations @@ -38,6 +39,7 @@ plugins to build large scale web applications. |-- hdrs/ ............. Headers parsing and management |-- http/ ............. The actual HTTP protocol implementation |---http2/ ............ HTTP/2 implementation + |---http3/ ............ HTTP/3 implementation |-- logging/ .......... Flexible logging |-- shared/ ........... Shared files |-- rc/ ................... Installation programs and scripts diff --git a/build/crypto.m4 b/build/crypto.m4 index 818e292def6..6361955abbe 100644 --- a/build/crypto.m4 +++ b/build/crypto.m4 @@ -221,6 +221,37 @@ AC_DEFUN([TS_CHECK_CRYPTO_HKDF], [ AC_SUBST(use_hkdf) ]) +AC_DEFUN([TS_CHECK_CRYPTO_TLS13], [ + enable_tls13=yes + _tls13_saved_LIBS=$LIBS + TS_ADDTO(LIBS, [$OPENSSL_LIBS]) + AC_MSG_CHECKING([whether TLS 1.3 is supported]) + AC_LINK_IFELSE( + [ + AC_LANG_PROGRAM([[ +#include + ]], + [[ +#ifndef TLS1_3_VERSION +# error no TLS1_3 support +#endif +#ifdef OPENSSL_NO_TLS1_3 +# error no TLS1_3 support +#endif + ]]) + ], + [ + AC_MSG_RESULT([yes]) + ], + [ + AC_MSG_RESULT([no]) + enable_tls13=no + ]) + LIBS=$_tls13_saved_LIBS + TS_ARG_ENABLE_VAR([use], [tls13]) + AC_SUBST(use_tls13) +]) + dnl dnl Since OpenSSL 1.1.0 dnl diff --git a/configure.ac b/configure.ac index e6a74d8a233..79aa4bc7598 100644 --- a/configure.ac +++ b/configure.ac @@ -1177,6 +1177,29 @@ TS_CHECK_CRYPTO_DH_GET_2048_256 TS_CHECK_CRYPTO_HKDF AM_CONDITIONAL([HAS_HKDF], [test "x$enable_hkdf" = "xyes"]) +# Check for TLS 1.3 support +TS_CHECK_CRYPTO_TLS13 + +# Check for QUIC support +enable_quic=no +AC_MSG_CHECKING([whether APIs for QUIC are available]) +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], + [[ + #ifdef OPENSSL_IS_BORINGSSL + SSL_QUIC_METHOD var; + #else + #ifndef SSL_MODE_QUIC_HACK + # error no hack for quic + #endif + #endif + ]]) + ], + [AC_MSG_RESULT([yes]); enable_quic=yes], + [AC_MSG_RESULT([no])]) +AM_CONDITIONAL([ENABLE_QUIC], [test "x$enable_quic" = "xyes"]) +TS_ARG_ENABLE_VAR([use], [quic]) +AC_SUBST(use_quic) + # Check for OCSP TS_CHECK_CRYPTO_OCSP @@ -2006,6 +2029,7 @@ AC_SUBST([default_stack_size], [$with_default_stack_size]) iocore_include_dirs="\ -I\$(abs_top_srcdir)/iocore/eventsystem \ -I\$(abs_top_srcdir)/iocore/net \ +-I\$(abs_top_srcdir)/iocore/net/quic \ -I\$(abs_top_srcdir)/iocore/aio \ -I\$(abs_top_srcdir)/iocore/hostdb \ -I\$(abs_top_srcdir)/iocore/cache \ @@ -2058,6 +2082,7 @@ AC_CONFIG_FILES([ iocore/eventsystem/Makefile iocore/hostdb/Makefile iocore/net/Makefile + iocore/net/quic/Makefile iocore/utils/Makefile lib/Makefile src/tscpp/api/Makefile @@ -2078,6 +2103,7 @@ AC_CONFIG_FILES([ proxy/http/Makefile proxy/http/remap/Makefile proxy/http2/Makefile + proxy/http3/Makefile proxy/logging/Makefile proxy/shared/Makefile rc/Makefile diff --git a/doc/admin-guide/files/records.config.en.rst b/doc/admin-guide/files/records.config.en.rst index 6384543b34f..394d7334ad0 100644 --- a/doc/admin-guide/files/records.config.en.rst +++ b/doc/admin-guide/files/records.config.en.rst @@ -603,6 +603,7 @@ HTTP Engine proto Value List of supported session protocols. pp Enable Proxy Protocol. ssl SSL terminated. + quic QUIC terminated. tr-full Fully transparent (inbound and outbound) tr-in Inbound transparent. tr-out Outbound transparent. @@ -616,7 +617,7 @@ HTTP Engine blind Accept only the ``CONNECT`` method on this port. - Not compatible with: ``tr-in``, ``ssl``. + Not compatible with: ``tr-in``, ``ssl`` and ``quic``. compress Compress the connection. Retained only by inertia, should be considered "not implemented". @@ -630,13 +631,19 @@ ipv6 ssl Require SSL termination for inbound connections. SSL :ref:`must be configured ` for this option to provide a functional server port. - Not compatible with: ``blind``. + Not compatible with: ``blind`` and ``quic``. + +quic + Require QUIC termination for inbound connections. SSL :ref:`must be configured ` for this option to provide a functional server port. + **THIS IS EXPERIMENTAL SUPPORT AND NOT READY FOR PRODUCTION USE.** + + Not compatible with: ``blind`` and ``ssl``. proto Specify the :ref:`session level protocols ` supported. These should be separated by semi-colons. For TLS proxy ports the default value is all available protocols. For non-TLS proxy ports the default is HTTP - only. + only. HTTP/3 is only available on QUIC ports. pp Enables Proxy Protocol on the port. If Proxy Protocol is enabled on the @@ -716,6 +723,12 @@ mptcp 9090:proto=http:ssl +.. topic:: Example + + Listen on port 4433 for QUIC connections.:: + + 4433:quic + .. ts:cv:: CONFIG proxy.config.http.connect_ports STRING 443 The range of origin server ports that can be used for tunneling via ``CONNECT``. @@ -3584,6 +3597,240 @@ HTTP/2 Configuration Clients exceeded this limit will be immediately disconnected with an error code of ENHANCE_YOUR_CALM. +HTTP/3 Configuration +==================== + +There is no configuration available yet on this release. + +QUIC Configuration +==================== + +All configurations for QUIC are still experimental and may be changed or +removed in the future without prior notice. + +.. ts:cv:: CONFIG proxy.config.quic.instance_id INT 0 + :reloadable: + + A static key used for calculating Stateless Reset Token. All instances in a + cluster need to share the same value. + +.. ts:cv:: CONFIG proxy.config.quic.connection_table.size INT 65521 + + A size of hash table that stores connection information. + +.. ts:cv:: CONFIG proxy.config.quic.proxy.config.quic.num_alt_connection_ids INT 65521 + :reloadable: + + A number of alternate Connection IDs that |TS| provides to a peer. It has to + be at least 8. + +.. ts:cv:: CONFIG proxy.config.quic.stateless_retry_enabled INT 0 + :reloadable: + + Enables Stateless Retry. + +.. ts:cv:: CONFIG proxy.config.quic.client.vn_exercise_enabled INT 0 + :reloadable: + + Enables version negotiation exercise on origin server connections. + +.. ts:cv:: CONFIG proxy.config.quic.client.cm_exercise_enabled INT 0 + :reloadable: + + Enables connection migration exercise on origin server connections. + +.. ts:cv:: CONFIG proxy.config.quic.server.supported_groups STRING "P-256:X25519:P-384:P-521" + :reloadable: + + Configures the list of supported groups provided by OpenSSL which will be + used to determine the set of shared groups on QUIC origin server connections. + +.. ts:cv:: CONFIG proxy.config.quic.client.supported_groups STRING "P-256:X25519:P-384:P-521" + :reloadable: + + Configures the list of supported groups provided by OpenSSL which will be + used to determine the set of shared groups on QUIC client connections. + +.. ts:cv:: CONFIG proxy.config.quic.client.session_file STRING "" + :reloadable: + + Only available for :program:`traffic_quic`. + If specified, TLS session data will be stored to the file, and will be used + for resuming a session. + +.. ts:cv:: CONFIG proxy.config.quic.client.keylog_file STRING "" + :reloadable: + + Only available for :program:`traffic_quic`. + If specified, key information will be stored to the file. + +.. ts:cv:: CONFIG proxy.config.quic.no_activity_timeout_in INT 30000 + :reloadable: + + This value will be advertised as ``idle_timeout`` Transport Parameter. + +.. ts:cv:: CONFIG proxy.config.quic.no_activity_timeout_out INT 30000 + :reloadable: + + This value will be advertised as ``idle_timeout`` Transport Parameter. + +.. ts:cv:: CONFIG proxy.config.quic.preferred_address_ipv4 STRING "" + :reloadable: + + This value will be advertised as a part of ``preferred_address`` + Transport Parameter. + +.. ts:cv:: CONFIG proxy.config.quic.preferred_address_ipv6 STRING "" + :reloadable: + + This value will be advertised as a part of ``preferred_address`` + Transport Parameter. + +.. ts:cv:: CONFIG proxy.config.quic.initial_max_data_in INT 65536 + :reloadable: + + This value will be advertised as ``initial_max_data`` Transport Parameter. + +.. ts:cv:: CONFIG proxy.config.quic.initial_max_data_out INT 65536 + :reloadable: + + This value will be advertised as ``initial_max_data`` Transport Parameter. + +.. ts:cv:: CONFIG proxy.config.quic.max_stream_data_bidi_local_in INT 0 + :reloadable: + + This value will be advertised as ``initial_max_stream_data_bidi_local`` + Transport Parameter. + +.. ts:cv:: CONFIG proxy.config.quic.max_stream_data_bidi_local_out INT 4096 + :reloadable: + + This value will be advertised as ``initial_max_stream_data_bidi_local`` + Transport Parameter. + +.. ts:cv:: CONFIG proxy.config.quic.max_stream_data_bidi_remote_in INT 4096 + :reloadable: + + This value will be advertised as ``initial_max_stream_data_bidi_remote`` + Transport Parameter. + +.. ts:cv:: CONFIG proxy.config.quic.max_stream_data_bidi_remote_out INT 0 + :reloadable: + + This value will be advertised as ``initial_max_stream_data_bidi_remote`` + Transport Parameter. + +.. ts:cv:: CONFIG proxy.config.quic.max_stream_data_uni_in INT 4096 + :reloadable: + + This value will be advertised as ``initial_max_stream_data_uni`` + Transport Parameter. + +.. ts:cv:: CONFIG proxy.config.quic.max_stream_data_uni_out INT 0 + :reloadable: + + This value will be advertised as ``initial_max_stream_data_uni`` + Transport Parameter. + +.. ts:cv:: CONFIG proxy.config.quic.max_streams_bidi_in INT 100 + :reloadable: + + This value will be advertised as ``initial_max_streams_bidi`` + Transport Parameter. + +.. ts:cv:: CONFIG proxy.config.quic.max_streams_bidi_out INT 100 + :reloadable: + + This value will be advertised as ``initial_max_streams_bidi`` + Transport Parameter. + +.. ts:cv:: CONFIG proxy.config.quic.max_streams_uni_in INT 100 + :reloadable: + + This value will be advertised as ``initial_max_streams_uni`` + Transport Parameter. + +.. ts:cv:: CONFIG proxy.config.quic.max_streams_uni_out INT 100 + :reloadable: + + This value will be advertised as ``initial_max_streams_uni`` + Transport Parameter. + +.. ts:cv:: CONFIG proxy.config.quic.ack_delay_exponent_in INT 3 + :reloadable: + + This value will be advertised as ``ack_delay_exponent`` Transport Parameter. + +.. ts:cv:: CONFIG proxy.config.quic.ack_delay_exponent_out INT 3 + :reloadable: + + This value will be advertised as ``ack_delay_exponent`` Transport Parameter. + +.. ts:cv:: CONFIG proxy.config.quic.max_ack_delay_in INT 25 + :reloadable: + + This value will be advertised as ``max_ack_delay`` Transport Parameter. + +.. ts:cv:: CONFIG proxy.config.quic.max_ack_delay_out INT 25 + :reloadable: + + This value will be advertised as ``max_ack_delay`` Transport Parameter. + + +.. ts:cv:: CONFIG proxy.config.quic.loss_detection.packet_threshold INT 3 + :reloadable: + + This is just for debugging. Do not change it from the default value unless + you really understand what this is. + +.. ts:cv:: CONFIG proxy.config.quic.loss_detection.time_threshold FLOAT 1.25 + :reloadable: + + This is just for debugging. Do not change it from the default value unless + you really understand what this is. + +.. ts:cv:: CONFIG proxy.config.quic.loss_detection.granularity INT 1 + :reloadable: + + This is just for debugging. Do not change it from the default value unless + you really understand what this is. + +.. ts:cv:: CONFIG proxy.config.quic.loss_detection.initial_rtt INT 1 + :reloadable: + + This is just for debugging. Do not change it from the default value unless + you really understand what this is. + +.. ts:cv:: CONFIG proxy.config.quic.congestion_control.max_datagram_size INT 1200 + :reloadable: + + This is just for debugging. Do not change it from the default value unless + you really understand what this is. + +.. ts:cv:: CONFIG proxy.config.quic.congestion_control.initial_window_scale INT 10 + :reloadable: + + This is just for debugging. Do not change it from the default value unless + you really understand what this is. + +.. ts:cv:: CONFIG proxy.config.quic.congestion_control.minimum_window_scale INT 2 + :reloadable: + + This is just for debugging. Do not change it from the default value unless + you really understand what this is. + +.. ts:cv:: CONFIG proxy.config.quic.congestion_control.loss_reduction_factor FLOAT 0.5 + :reloadable: + + This is just for debugging. Do not change it from the default value unless + you really understand what this is. + +.. ts:cv:: CONFIG proxy.config.quic.congestion_control.persistent_congestion_threshold INT 2 + :reloadable: + + This is just for debugging. Do not change it from the default value unless + you really understand what this is. + Plug-in Configuration ===================== diff --git a/include/ts/apidefs.h.in b/include/ts/apidefs.h.in index c6f1c9bacfc..9364adf8996 100644 --- a/include/ts/apidefs.h.in +++ b/include/ts/apidefs.h.in @@ -1236,11 +1236,15 @@ extern tsapi const char *const TS_ALPN_PROTOCOL_HTTP_0_9; extern tsapi const char *const TS_ALPN_PROTOCOL_HTTP_1_0; extern tsapi const char *const TS_ALPN_PROTOCOL_HTTP_1_1; extern tsapi const char *const TS_ALPN_PROTOCOL_HTTP_2_0; +extern tsapi const char *const TS_ALPN_PROTOCOL_HTTP_3; +extern tsapi const char *const TS_ALPN_PROTOCOL_HTTP_QUIC; extern tsapi int TS_ALPN_PROTOCOL_INDEX_HTTP_0_9; extern tsapi int TS_ALPN_PROTOCOL_INDEX_HTTP_1_0; extern tsapi int TS_ALPN_PROTOCOL_INDEX_HTTP_1_1; extern tsapi int TS_ALPN_PROTOCOL_INDEX_HTTP_2_0; +extern tsapi int TS_ALPN_PROTOCOL_INDEX_HTTP_3; +extern tsapi int TS_ALPN_PROTOCOL_INDEX_HTTP_QUIC; extern tsapi const char *const TS_ALPN_PROTOCOL_GROUP_HTTP; extern tsapi const char *const TS_ALPN_PROTOCOL_GROUP_HTTP2; @@ -1248,6 +1252,8 @@ extern tsapi const char *const TS_ALPN_PROTOCOL_GROUP_HTTP2; extern tsapi const char *const TS_PROTO_TAG_HTTP_1_0; extern tsapi const char *const TS_PROTO_TAG_HTTP_1_1; extern tsapi const char *const TS_PROTO_TAG_HTTP_2_0; +extern tsapi const char *const TS_PROTO_TAG_HTTP_3; +extern tsapi const char *const TS_PROTO_TAG_HTTP_QUIC; extern tsapi const char *const TS_PROTO_TAG_TLS_1_3; extern tsapi const char *const TS_PROTO_TAG_TLS_1_2; extern tsapi const char *const TS_PROTO_TAG_TLS_1_1; diff --git a/include/tscore/ink_config.h.in b/include/tscore/ink_config.h.in index 8c920f93300..e994ea0a38c 100644 --- a/include/tscore/ink_config.h.in +++ b/include/tscore/ink_config.h.in @@ -72,6 +72,8 @@ #define TS_USE_HELLO_CB @use_hello_cb@ #define TS_USE_SET_RBIO @use_set_rbio@ #define TS_USE_GET_DH_2048_256 @use_dh_get_2048_256@ +#define TS_USE_TLS13 @use_tls13@ +#define TS_USE_QUIC @use_quic@ #define TS_USE_TLS_SET_CIPHERSUITES @use_tls_set_ciphersuites@ #define TS_USE_LINUX_NATIVE_AIO @use_linux_native_aio@ #define TS_USE_REMOTE_UNWINDING @use_remote_unwinding@ diff --git a/include/tscore/ink_inet.h b/include/tscore/ink_inet.h index ff6c10406e5..70cd47ca78e 100644 --- a/include/tscore/ink_inet.h +++ b/include/tscore/ink_inet.h @@ -50,6 +50,7 @@ extern const std::string_view IP_PROTO_TAG_IPV4; extern const std::string_view IP_PROTO_TAG_IPV6; extern const std::string_view IP_PROTO_TAG_UDP; extern const std::string_view IP_PROTO_TAG_TCP; +extern const std::string_view IP_PROTO_TAG_QUIC; extern const std::string_view IP_PROTO_TAG_TLS_1_0; extern const std::string_view IP_PROTO_TAG_TLS_1_1; extern const std::string_view IP_PROTO_TAG_TLS_1_2; @@ -58,6 +59,8 @@ extern const std::string_view IP_PROTO_TAG_HTTP_0_9; extern const std::string_view IP_PROTO_TAG_HTTP_1_0; extern const std::string_view IP_PROTO_TAG_HTTP_1_1; extern const std::string_view IP_PROTO_TAG_HTTP_2_0; +extern const std::string_view IP_PROTO_TAG_HTTP_QUIC; +extern const std::string_view IP_PROTO_TAG_HTTP_3; struct IpAddr; // forward declare. diff --git a/iocore/eventsystem/I_Event.h b/iocore/eventsystem/I_Event.h index e598adda288..022f0adc7ec 100644 --- a/iocore/eventsystem/I_Event.h +++ b/iocore/eventsystem/I_Event.h @@ -73,6 +73,9 @@ #define HTTP2_SESSION_EVENTS_START 2250 #define HTTP_TUNNEL_EVENTS_START 2300 #define HTTP_SCH_UPDATE_EVENTS_START 2400 +#define QUIC_EVENT_EVENTS_START 2500 +#define HTTP3_SESSION_EVENTS_START 2600 +#define QPACK_EVENT_EVENTS_START 2700 #define NT_ASYNC_CONNECT_EVENT_EVENTS_START 3000 #define NT_ASYNC_IO_EVENT_EVENTS_START 3100 #define RAFT_EVENT_EVENTS_START 3200 diff --git a/iocore/eventsystem/I_Thread.h b/iocore/eventsystem/I_Thread.h index 54cb9b0ee0b..116463fbc18 100644 --- a/iocore/eventsystem/I_Thread.h +++ b/iocore/eventsystem/I_Thread.h @@ -118,9 +118,16 @@ class Thread ProxyAllocator eventAllocator; ProxyAllocator netVCAllocator; ProxyAllocator sslNetVCAllocator; + ProxyAllocator quicNetVCAllocator; ProxyAllocator http1ClientSessionAllocator; ProxyAllocator http2ClientSessionAllocator; ProxyAllocator http2StreamAllocator; + ProxyAllocator quicClientSessionAllocator; + ProxyAllocator quicHandshakeAllocator; + ProxyAllocator quicBidiStreamAllocator; + ProxyAllocator quicSendStreamAllocator; + ProxyAllocator quicReceiveStreamAllocator; + ProxyAllocator quicStreamManagerAllocator; ProxyAllocator httpServerSessionAllocator; ProxyAllocator hdrHeapAllocator; ProxyAllocator strHeapAllocator; diff --git a/iocore/net/I_NetProcessor.h b/iocore/net/I_NetProcessor.h index bf97639c671..58ce935ee63 100644 --- a/iocore/net/I_NetProcessor.h +++ b/iocore/net/I_NetProcessor.h @@ -257,3 +257,4 @@ extern inkcoreapi NetProcessor &netProcessor; */ extern inkcoreapi NetProcessor &sslNetProcessor; +extern inkcoreapi NetProcessor &quicNetProcessor; diff --git a/iocore/net/I_NetVConnection.h b/iocore/net/I_NetVConnection.h index 4adf7947844..df0667f2abc 100644 --- a/iocore/net/I_NetVConnection.h +++ b/iocore/net/I_NetVConnection.h @@ -180,6 +180,10 @@ struct NetVCOptions { EventType etype; + /** ALPN protocol-lists. The format is OpenSSL protocol-lists format (vector of 8-bit length-prefixed, byte strings) + https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set_alpn_protos.html + */ + std::string_view alpn_protos; /** Server name to use for SNI data on an outbound connection. */ ats_scoped_str sni_servername; @@ -225,6 +229,7 @@ struct NetVCOptions { NetVCOptions() { reset(); } ~NetVCOptions() {} + /** Set the SNI server name. A local copy is made of @a name. */ @@ -241,6 +246,7 @@ struct NetVCOptions { } return *this; } + self & set_ssl_servername(const char *name) { diff --git a/iocore/net/Makefile.am b/iocore/net/Makefile.am index cbd512f68bc..898eb9b0eea 100644 --- a/iocore/net/Makefile.am +++ b/iocore/net/Makefile.am @@ -16,6 +16,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +if ENABLE_QUIC +SUBDIRS = quic +endif + AM_CPPFLAGS += \ $(iocore_include_dirs) \ -I$(abs_top_srcdir)/include \ @@ -159,6 +163,23 @@ libinknet_a_SOURCES = \ UnixUDPNet.cc \ SSLDynlock.cc +if ENABLE_QUIC +libinknet_a_SOURCES += \ + P_QUICClosedConCollector.h \ + P_QUICPacketHandler.h \ + P_QUICNet.h \ + P_QUICNetProcessor.h \ + P_QUICNetVConnection.h \ + P_QUICNextProtocolAccept.h \ + QUICClosedConCollector.cc \ + QUICPacketHandler.cc \ + QUICMultiCertConfigLoader.cc \ + QUICNet.cc \ + QUICNetProcessor.cc \ + QUICNetVConnection.cc \ + QUICNextProtocolAccept.cc +endif + if BUILD_TESTS libinknet_a_SOURCES += \ NetVCTest.cc \ diff --git a/iocore/net/P_Net.h b/iocore/net/P_Net.h index 602ea38c8e1..15a55bb8bb6 100644 --- a/iocore/net/P_Net.h +++ b/iocore/net/P_Net.h @@ -109,6 +109,13 @@ extern RecRawStatBlock *net_rsb; #include "P_SSLNetAccept.h" #include "P_SSLCertLookup.h" +#if TS_USE_QUIC == 1 +#include "P_QUICNetVConnection.h" +#include "P_QUICNetProcessor.h" +#include "P_QUICPacketHandler.h" +#include "P_QUICNet.h" +#endif + static constexpr ts::ModuleVersion NET_SYSTEM_MODULE_INTERNAL_VERSION(NET_SYSTEM_MODULE_PUBLIC_VERSION, ts::ModuleVersion::PRIVATE); // For very verbose iocore debugging. diff --git a/iocore/net/P_QUICClosedConCollector.h b/iocore/net/P_QUICClosedConCollector.h new file mode 100644 index 00000000000..c8abb6eb949 --- /dev/null +++ b/iocore/net/P_QUICClosedConCollector.h @@ -0,0 +1,36 @@ +/** @file + This file implements an I/O Processor for network I/O + @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. + */ + +#pragma once + +#include "P_QUICNetVConnection.h" + +class QUICClosedConCollector : public Continuation +{ +public: + QUICClosedConCollector(); + + int mainEvent(int event, Event *e); + + ASLL(QUICNetVConnection, closed_alink) closedQueue; + +private: + Que(QUICNetVConnection, closed_link) _localClosedQueue; + + void _process_closed_connection(EThread *t); +}; diff --git a/iocore/net/P_QUICNet.h b/iocore/net/P_QUICNet.h new file mode 100644 index 00000000000..802bf6ed6e6 --- /dev/null +++ b/iocore/net/P_QUICNet.h @@ -0,0 +1,77 @@ +/** @file + + A brief file description + + @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. + */ + +#ifndef __P_QUICNET_H__ +#define __P_QUICNET_H__ + +#include + +#include "tscore/ink_platform.h" + +#include "P_Net.h" + +class NetHandler; +typedef int (NetHandler::*NetContHandler)(int, void *); + +void initialize_thread_for_quic_net(EThread *thread); + +struct QUICPollEvent { + QUICConnection *con; + UDPPacketInternal *packet; + void init(QUICConnection *con, UDPPacketInternal *packet); + void free(); + + SLINK(QUICPollEvent, alink); + LINK(QUICPollEvent, link); +}; + +struct QUICPollCont : public Continuation { + NetHandler *net_handler; + PollDescriptor *pollDescriptor; + + QUICPollCont(Ptr &m); + QUICPollCont(Ptr &m, NetHandler *nh); + ~QUICPollCont(); + int pollEvent(int, Event *); + +public: + // Atomic Queue to save incoming packets + ASLL(QUICPollEvent, alink) inQueue; + +private: + // Internal Queue to save Long Header Packet + Que(UDPPacketInternal, link) _longInQueue; + +private: + void _process_short_header_packet(QUICPollEvent *e, NetHandler *nh); + void _process_long_header_packet(QUICPollEvent *e, NetHandler *nh); +}; + +static inline QUICPollCont * +get_QUICPollCont(EThread *t) +{ + return (QUICPollCont *)ETHREAD_GET_PTR(t, quic_NetProcessor.quicPollCont_offset); +} + +extern ClassAllocator quicPollEventAllocator; +#endif diff --git a/iocore/net/P_QUICNetProcessor.h b/iocore/net/P_QUICNetProcessor.h new file mode 100644 index 00000000000..bb3e8576b74 --- /dev/null +++ b/iocore/net/P_QUICNetProcessor.h @@ -0,0 +1,78 @@ +/** @file + + A brief file description + + @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 + + */ + +/**************************************************************************** + + P_QUICNetProcessor.h + + The QUIC version of the UnixNetProcessor class. The majority of the logic + is in UnixNetProcessor. The QUICNetProcessor provides the following: + + * QUIC library initialization through the start() method. + * Allocation of a QUICNetVConnection through the allocate_vc virtual method. + + Possibly another pass through could simplify the allocate_vc logic too, but + I think I will stop here for now. + + ****************************************************************************/ +#pragma once + +#include "tscore/ink_platform.h" +#include "P_Net.h" +#include "quic/QUICConnectionTable.h" + +class UnixNetVConnection; +struct NetAccept; + +////////////////////////////////////////////////////////////////// +// +// class QUICNetProcessor +// +////////////////////////////////////////////////////////////////// +class QUICNetProcessor : public UnixNetProcessor +{ +public: + QUICNetProcessor(); + virtual ~QUICNetProcessor(); + + void init() override; + virtual int start(int, size_t stacksize) override; + // TODO: refactoring NetProcessor::connect_re and UnixNetProcessor::connect_re_internal + // Action *connect_re(Continuation *cont, sockaddr const *addr, NetVCOptions *opts) override; + Action *connect_re(Continuation *cont, sockaddr const *addr, NetVCOptions *opts); + + virtual NetAccept *createNetAccept(const NetProcessor::AcceptOptions &opt) override; + virtual NetVConnection *allocate_vc(EThread *t) override; + + Action *main_accept(Continuation *cont, SOCKET fd, AcceptOptions const &opt) override; + + off_t quicPollCont_offset; + +private: + QUICNetProcessor(const QUICNetProcessor &); + QUICNetProcessor &operator=(const QUICNetProcessor &); + + QUICConnectionTable *_ctable = nullptr; +}; + +extern QUICNetProcessor quic_NetProcessor; diff --git a/iocore/net/P_QUICNetVConnection.h b/iocore/net/P_QUICNetVConnection.h new file mode 100644 index 00000000000..8ca25880df5 --- /dev/null +++ b/iocore/net/P_QUICNetVConnection.h @@ -0,0 +1,371 @@ +/** @file + + A brief file description + + @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. + */ + +/**************************************************************************** + + QUICNetVConnection.h + + This file implements an I/O Processor for network I/O. + + + ****************************************************************************/ +#pragma once + +#include "tscore/ink_platform.h" +#include "P_Net.h" +#include "P_EventSystem.h" +#include "P_UnixNetVConnection.h" +#include "P_UnixNet.h" +#include "P_UDPNet.h" +#include "tscore/ink_apidefs.h" +#include "tscore/List.h" + +#include "quic/QUICConfig.h" +#include "quic/QUICConnection.h" +#include "quic/QUICConnectionTable.h" +#include "quic/QUICVersionNegotiator.h" +#include "quic/QUICPacket.h" +#include "quic/QUICPacketFactory.h" +#include "quic/QUICFrame.h" +#include "quic/QUICFrameDispatcher.h" +#include "quic/QUICHandshake.h" +#include "quic/QUICApplication.h" +#include "quic/QUICStream.h" +#include "quic/QUICHandshakeProtocol.h" +#include "quic/QUICAckFrameCreator.h" +#include "quic/QUICPinger.h" +#include "quic/QUICLossDetector.h" +#include "quic/QUICStreamManager.h" +#include "quic/QUICAltConnectionManager.h" +#include "quic/QUICPathValidator.h" +#include "quic/QUICApplicationMap.h" +#include "quic/QUICPacketReceiveQueue.h" +#include "quic/QUICAddrVerifyState.h" +#include "quic/QUICPacketProtectionKeyInfo.h" + +// Size of connection ids for debug log : e.g. aaaaaaaa-bbbbbbbb\0 +static constexpr size_t MAX_CIDS_SIZE = 8 + 1 + 8 + 1; + +////////////////////////////////////////////////////////////////// +// +// class NetVConnection +// +// A VConnection for a network socket. +// +////////////////////////////////////////////////////////////////// + +class QUICPacketHandler; +class QUICLossDetector; + +class SSLNextProtocolSet; + +/** + * @class QUICNetVConnection + * @brief A NetVConnection for a QUIC network socket + * @detail + * + * state_pre_handshake() + * | READ: + * | Do nothing + * | WRITE: + * | _state_common_send_packet() + * v + * state_handshake() + * | READ: + * | _state_handshake_process_packet() + * | _state_handshake_process_initial_packet() + * | _state_handshake_process_retry_packet() + * | _state_handshake_process_handshake_packet() + * | _state_handshake_process_zero_rtt_protected_packet() + * | WRITE: + * | _state_common_send_packet() + * | or + * | _state_handshake_send_retry_packet() + * v + * state_connection_established() + * | READ: + * | _state_connection_established_receive_packet() + * | _state_connection_established_process_protected_packet() + * | WRITE: + * | _state_common_send_packet() + * v + * state_connection_closing() (If closing actively) + * | READ: + * | _state_closing_receive_packet() + * | WRITE: + * | _state_closing_send_packet() + * v + * state_connection_draining() (If closing passively) + * | READ: + * | _state_draining_receive_packet() + * | WRITE: + * | Do nothing + * v + * state_connection_close() + * READ: + * Do nothing + * WRITE: + * Do nothing + **/ +class QUICNetVConnection : public UnixNetVConnection, public QUICConnection, public QUICFrameGenerator, public RefCountObj +{ + using super = UnixNetVConnection; ///< Parent type. + +public: + QUICNetVConnection(); + ~QUICNetVConnection(); + void init(QUICConnectionId peer_cid, QUICConnectionId original_cid, UDPConnection *, QUICPacketHandler *); + void init(QUICConnectionId peer_cid, QUICConnectionId original_cid, QUICConnectionId first_cid, UDPConnection *, + QUICPacketHandler *, QUICConnectionTable *ctable); + + // accept new conn_id + int acceptEvent(int event, Event *e); + + // UnixNetVConnection + void reenable(VIO *vio) override; + VIO *do_io_read(Continuation *c, int64_t nbytes, MIOBuffer *buf) override; + VIO *do_io_write(Continuation *c, int64_t nbytes, IOBufferReader *buf, bool owner = false) override; + int connectUp(EThread *t, int fd) override; + + // QUICNetVConnection + int startEvent(int event, Event *e); + int state_pre_handshake(int event, Event *data); + int state_handshake(int event, Event *data); + int state_connection_established(int event, Event *data); + int state_connection_closing(int event, Event *data); + int state_connection_draining(int event, Event *data); + int state_connection_closed(int event, Event *data); + void start(); + void remove_connection_ids(); + void free(EThread *t) override; + void free() override; + void destroy(EThread *t); + + UDPConnection *get_udp_con(); + virtual void net_read_io(NetHandler *nh, EThread *lthread) override; + virtual int64_t load_buffer_and_write(int64_t towrite, MIOBufferAccessor &buf, int64_t &total_written, int &needs) override; + + int populate_protocol(std::string_view *results, int n) const override; + const char *protocol_contains(std::string_view tag) const override; + + // QUICNetVConnection + void registerNextProtocolSet(SSLNextProtocolSet *s); + + // QUICConnection + QUICStreamManager *stream_manager() override; + void close(QUICConnectionErrorUPtr error) override; + void handle_received_packet(UDPPacket *packet) override; + void ping() override; + + // QUICConnection (QUICConnectionInfoProvider) + QUICConnectionId peer_connection_id() const override; + QUICConnectionId original_connection_id() const override; + QUICConnectionId first_connection_id() const override; + QUICConnectionId connection_id() const override; + std::string_view cids() const override; + const QUICFiveTuple five_tuple() const override; + uint32_t pmtu() const override; + NetVConnectionContext_t direction() const override; + SSLNextProtocolSet *next_protocol_set() const override; + std::string_view negotiated_application_name() const override; + bool is_closed() const override; + + // QUICConnection (QUICFrameHandler) + std::vector interests() override; + QUICConnectionErrorUPtr handle_frame(QUICEncryptionLevel level, const QUICFrame &frame) override; + + // QUICFrameGenerator + bool will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) override; + QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size, + ink_hrtime timestamp) override; + + int in_closed_queue = 0; + + bool shouldDestroy(); + + LINK(QUICNetVConnection, closed_link); + SLINK(QUICNetVConnection, closed_alink); + +private: + std::random_device _rnd; + + QUICConfig::scoped_config _quic_config; + + QUICConnectionId _peer_quic_connection_id; // dst cid in local + QUICConnectionId _peer_old_quic_connection_id; // dst previous cid in local + QUICConnectionId _original_quic_connection_id; // dst cid of initial packet from client + QUICConnectionId _first_quic_connection_id; // dst cid of initial packet from client that doesn't have retry token + QUICConnectionId _quic_connection_id; // src cid in local + QUICFiveTuple _five_tuple; + bool _connection_migration_initiated = false; + + char _cids_data[MAX_CIDS_SIZE] = {0}; + std::string_view _cids; + + UDPConnection *_udp_con = nullptr; + QUICPacketProtectionKeyInfo _pp_key_info; + QUICPacketHandler *_packet_handler = nullptr; + QUICPacketFactory _packet_factory; + QUICFrameFactory _frame_factory; + QUICAckFrameManager _ack_frame_manager; + QUICPinger _pinger; + QUICPacketHeaderProtector _ph_protector; + QUICRTTMeasure _rtt_measure; + QUICApplicationMap *_application_map = nullptr; + + uint32_t _pmtu = 1280; + + SSLNextProtocolSet *_next_protocol_set = nullptr; + + // TODO: use custom allocator and make them std::unique_ptr or std::shared_ptr + // or make them just member variables. + QUICHandshake *_handshake_handler = nullptr; + QUICHandshakeProtocol *_hs_protocol = nullptr; + QUICLossDetector *_loss_detector = nullptr; + QUICFrameDispatcher *_frame_dispatcher = nullptr; + QUICStreamManager *_stream_manager = nullptr; + QUICCongestionController *_congestion_controller = nullptr; + QUICRemoteFlowController *_remote_flow_controller = nullptr; + QUICLocalFlowController *_local_flow_controller = nullptr; + QUICConnectionTable *_ctable = nullptr; + QUICAltConnectionManager *_alt_con_manager = nullptr; + QUICPathValidator *_path_validator = nullptr; + + std::vector _frame_generators; + + QUICPacketReceiveQueue _packet_recv_queue = {this->_packet_factory, this->_ph_protector}; + + QUICConnectionErrorUPtr _connection_error = nullptr; + uint32_t _state_closing_recv_packet_count = 0; + uint32_t _state_closing_recv_packet_window = 1; + uint64_t _flow_control_buffer_size = 1024; + + void _schedule_packet_write_ready(bool delay = false); + void _unschedule_packet_write_ready(); + void _close_packet_write_ready(Event *data); + Event *_packet_write_ready = nullptr; + + void _schedule_closing_timeout(ink_hrtime interval); + void _unschedule_closing_timeout(); + void _close_closing_timeout(Event *data); + Event *_closing_timeout = nullptr; + + void _schedule_closed_event(); + void _unschedule_closed_event(); + void _close_closed_event(Event *data); + Event *_closed_event = nullptr; + + void _schedule_path_validation_timeout(ink_hrtime interval); + void _unschedule_path_validation_timeout(); + void _close_path_validation_timeout(Event *data); + Event *_path_validation_timeout = nullptr; + + void _schedule_ack_manager_periodic(ink_hrtime interval); + void _unschedule_ack_manager_periodic(); + Event *_ack_manager_periodic = nullptr; + + QUICEncryptionLevel _minimum_encryption_level = QUICEncryptionLevel::INITIAL; + + QUICPacketNumber _largest_acked_packet_number(QUICEncryptionLevel level) const; + uint32_t _maximum_quic_packet_size() const; + uint32_t _minimum_quic_packet_size(); + uint64_t _maximum_stream_frame_data_size(); + + Ptr _store_frame(Ptr parent_block, size_t &size_added, uint64_t &max_frame_size, QUICFrame &frame, + std::vector &frames); + QUICPacketUPtr _packetize_frames(QUICEncryptionLevel level, uint64_t max_packet_size, std::vector &frames); + void _packetize_closing_frame(); + Ptr _generate_padding_frame(size_t frame_size); + QUICPacketUPtr _build_packet(QUICEncryptionLevel level, Ptr parent_block, bool retransmittable, bool probing, + bool crypto); + + QUICConnectionErrorUPtr _recv_and_ack(const QUICPacket &packet, bool *has_non_probing_frame = nullptr); + + QUICConnectionErrorUPtr _state_handshake_process_packet(const QUICPacket &packet); + QUICConnectionErrorUPtr _state_handshake_process_version_negotiation_packet(const QUICPacket &packet); + QUICConnectionErrorUPtr _state_handshake_process_initial_packet(const QUICPacket &packet); + QUICConnectionErrorUPtr _state_handshake_process_retry_packet(const QUICPacket &packet); + QUICConnectionErrorUPtr _state_handshake_process_handshake_packet(const QUICPacket &packet); + QUICConnectionErrorUPtr _state_handshake_process_zero_rtt_protected_packet(const QUICPacket &packet); + QUICConnectionErrorUPtr _state_connection_established_receive_packet(); + QUICConnectionErrorUPtr _state_connection_established_process_protected_packet(const QUICPacket &packet); + QUICConnectionErrorUPtr _state_connection_established_migrate_connection(const QUICPacket &packet); + QUICConnectionErrorUPtr _state_connection_established_initiate_connection_migration(); + QUICConnectionErrorUPtr _state_closing_receive_packet(); + QUICConnectionErrorUPtr _state_draining_receive_packet(); + QUICConnectionErrorUPtr _state_common_send_packet(); + QUICConnectionErrorUPtr _state_handshake_send_retry_packet(); + QUICConnectionErrorUPtr _state_closing_send_packet(); + + Ptr _packet_transmitter_mutex; + + void _init_flow_control_params(const std::shared_ptr &local_tp, + const std::shared_ptr &remote_tp); + void _handle_error(QUICConnectionErrorUPtr error); + QUICPacketUPtr _dequeue_recv_packet(QUICPacketCreationResult &result); + void _validate_new_path(); + + int _complete_handshake_if_possible(); + void _switch_to_handshake_state(); + void _switch_to_established_state(); + void _switch_to_closing_state(QUICConnectionErrorUPtr error); + void _switch_to_draining_state(QUICConnectionErrorUPtr error); + void _switch_to_close_state(); + + bool _application_started = false; + void _start_application(); + + void _handle_periodic_ack_event(); + void _handle_path_validation_timeout(Event *data); + void _handle_idle_timeout(); + + QUICConnectionErrorUPtr _handle_frame(const QUICNewConnectionIdFrame &frame); + + void _update_cids(); + void _update_peer_cid(const QUICConnectionId &new_cid); + void _update_local_cid(const QUICConnectionId &new_cid); + void _rerandomize_original_cid(); + + QUICHandshakeProtocol *_setup_handshake_protocol(shared_SSL_CTX ctx); + + QUICPacketUPtr _the_final_packet = QUICPacketFactory::create_null_packet(); + QUICStatelessResetToken _reset_token; + + ats_unique_buf _av_token = {nullptr}; + size_t _av_token_len = 0; + bool _is_resumption_token_sent = false; + + uint64_t _stream_frames_sent = 0; + + // TODO: Source addresses verification through an address validation token + bool _has_ack_eliciting_packet_out = true; + + QUICAddrVerifyState _verfied_state; + + // QUICFrameGenerator + void _on_frame_lost(QUICFrameInformationUPtr &info) override; +}; + +typedef int (QUICNetVConnection::*QUICNetVConnHandler)(int, void *); + +extern ClassAllocator quicNetVCAllocator; diff --git a/iocore/net/P_QUICNextProtocolAccept.h b/iocore/net/P_QUICNextProtocolAccept.h new file mode 100644 index 00000000000..5b03652b0ae --- /dev/null +++ b/iocore/net/P_QUICNextProtocolAccept.h @@ -0,0 +1,61 @@ +/** @file + + QUICNextProtocolAccept + + @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. + */ + +#pragma once + +#include "P_QUICNetVConnection.h" +#include "P_SSLNextProtocolSet.h" +#include "I_IOBuffer.h" + +class QUICNextProtocolAccept : public SessionAccept +{ +public: + QUICNextProtocolAccept(); + ~QUICNextProtocolAccept(); + + bool accept(NetVConnection *, MIOBuffer *, IOBufferReader *); + + // Register handler as an endpoint for the specified protocol. Neither + // handler nor protocol are copied, so the caller must guarantee their + // lifetime is at least as long as that of the acceptor. + bool registerEndpoint(const char *protocol, Continuation *handler); + + // Unregister the handler. Returns false if this protocol is not registered + // or if it is not registered for the specified handler. + bool unregisterEndpoint(const char *protocol, Continuation *handler); + + SLINK(QUICNextProtocolAccept, link); + SSLNextProtocolSet *getProtoSet(); + SSLNextProtocolSet *cloneProtoSet(); + + // noncopyable + QUICNextProtocolAccept(const QUICNextProtocolAccept &) = delete; // disabled + QUICNextProtocolAccept &operator=(const QUICNextProtocolAccept &) = delete; // disabled + +private: + int mainEvent(int event, void *netvc); + + SSLNextProtocolSet protoset; + + friend struct QUICNextProtocolTrampoline; +}; diff --git a/iocore/net/P_QUICPacketHandler.h b/iocore/net/P_QUICPacketHandler.h new file mode 100644 index 00000000000..27a5235d911 --- /dev/null +++ b/iocore/net/P_QUICPacketHandler.h @@ -0,0 +1,112 @@ +/** @file + + A brief file description + + @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. + */ + +#pragma once + +#include "tscore/ink_platform.h" +#include "P_Connection.h" +#include "P_NetAccept.h" +#include "quic/QUICTypes.h" +#include "quic/QUICConnectionTable.h" + +class QUICClosedConCollector; +class QUICNetVConnection; +class QUICPacket; +class QUICPacketHeaderProtector; + +class QUICPacketHandler +{ +public: + QUICPacketHandler(); + ~QUICPacketHandler(); + + void send_packet(const QUICPacket &packet, QUICNetVConnection *vc, const QUICPacketHeaderProtector &pn_protector); + void send_packet(QUICNetVConnection *vc, Ptr udp_payload); + + void close_connection(QUICNetVConnection *conn); + +protected: + void _send_packet(const QUICPacket &packet, UDPConnection *udp_con, IpEndpoint &addr, uint32_t pmtu, + const QUICPacketHeaderProtector *ph_protector, int dcil); + void _send_packet(UDPConnection *udp_con, IpEndpoint &addr, Ptr udp_payload); + + // FIXME Remove this + // QUICPacketHandler could be a continuation, but NetAccept is a contination too. + virtual Continuation *_get_continuation() = 0; + + Event *_collector_event = nullptr; + QUICClosedConCollector *_closed_con_collector = nullptr; + + virtual void _recv_packet(int event, UDPPacket *udpPacket) = 0; +}; + +/* + * @class QUICPacketHanderIn + * @brief QUIC Packet Handler for incoming connections + */ +class QUICPacketHandlerIn : public NetAccept, public QUICPacketHandler +{ +public: + QUICPacketHandlerIn(const NetProcessor::AcceptOptions &opt, QUICConnectionTable &ctable); + ~QUICPacketHandlerIn(); + + // NetAccept + virtual NetProcessor *getNetProcessor() const override; + virtual NetAccept *clone() const override; + virtual int acceptEvent(int event, void *e) override; + void init_accept(EThread *t) override; + +protected: + // QUICPacketHandler + Continuation *_get_continuation() override; + +private: + void _recv_packet(int event, UDPPacket *udp_packet) override; + int _stateless_retry(const uint8_t *buf, uint64_t buf_len, UDPConnection *connection, IpEndpoint from, QUICConnectionId dcid, + QUICConnectionId scid, QUICConnectionId *original_cid); + + QUICConnectionTable &_ctable; +}; + +/* + * @class QUICPacketHanderOut + * @brief QUIC Packet Handler for outgoing connections + */ +class QUICPacketHandlerOut : public Continuation, public QUICPacketHandler +{ +public: + QUICPacketHandlerOut(); + ~QUICPacketHandlerOut(){}; + + void init(QUICNetVConnection *vc); + int event_handler(int event, Event *data); + +protected: + // QUICPacketHandler + Continuation *_get_continuation() override; + +private: + void _recv_packet(int event, UDPPacket *udp_packet) override; + + QUICNetVConnection *_vc = nullptr; +}; diff --git a/iocore/net/P_UDPNet.h b/iocore/net/P_UDPNet.h index f53c8231195..a7de9647b1b 100644 --- a/iocore/net/P_UDPNet.h +++ b/iocore/net/P_UDPNet.h @@ -58,7 +58,8 @@ extern UDPNetProcessorInternal udpNetInternal; #define SLOT_TIME HRTIME_MSECONDS(SLOT_TIME_MSEC) #define N_SLOTS 2048 -constexpr int UDP_PERIOD = 9; +constexpr int UDP_PERIOD = 9; +constexpr int UDP_NH_PERIOD = UDP_PERIOD + 1; class PacketQueue { diff --git a/iocore/net/P_UnixNetVConnection.h b/iocore/net/P_UnixNetVConnection.h index a5620cc233b..7b961443b89 100644 --- a/iocore/net/P_UnixNetVConnection.h +++ b/iocore/net/P_UnixNetVConnection.h @@ -152,15 +152,15 @@ class UnixNetVConnection : public NetVConnection // called when handing an event from this NetVConnection,// // or the NetVConnection creation callback. // //////////////////////////////////////////////////////////// - void set_active_timeout(ink_hrtime timeout_in) override; - void set_inactivity_timeout(ink_hrtime timeout_in) override; - void cancel_active_timeout() override; - void cancel_inactivity_timeout() override; + virtual void set_active_timeout(ink_hrtime timeout_in) override; + virtual void set_inactivity_timeout(ink_hrtime timeout_in) override; + virtual void cancel_active_timeout() override; + virtual void cancel_inactivity_timeout() override; void set_action(Continuation *c) override; const Action *get_action() const; - void add_to_keep_alive_queue() override; - void remove_from_keep_alive_queue() override; - bool add_to_active_queue() override; + virtual void add_to_keep_alive_queue() override; + virtual void remove_from_keep_alive_queue() override; + virtual bool add_to_active_queue() override; virtual void remove_from_active_queue(); // The public interface is VIO::reenable() @@ -291,9 +291,9 @@ class UnixNetVConnection : public NetVConnection ink_hrtime get_inactivity_timeout() override; ink_hrtime get_active_timeout() override; - void set_local_addr() override; + virtual void set_local_addr() override; void set_mptcp_state() override; - void set_remote_addr() override; + virtual void set_remote_addr() override; void set_remote_addr(const sockaddr *) override; int set_tcp_congestion_control(int side) override; void apply_options() override; @@ -422,3 +422,4 @@ UnixNetVConnection::get_action() const void write_to_net(NetHandler *nh, UnixNetVConnection *vc, EThread *thread); void write_to_net_io(NetHandler *nh, UnixNetVConnection *vc, EThread *thread); +void net_activity(UnixNetVConnection *vc, EThread *thread); diff --git a/iocore/net/P_UnixUDPConnection.h b/iocore/net/P_UnixUDPConnection.h index 668f9f3939f..9e3c2f2a804 100644 --- a/iocore/net/P_UnixUDPConnection.h +++ b/iocore/net/P_UnixUDPConnection.h @@ -46,21 +46,21 @@ class UnixUDPConnection : public UDPConnectionInternal // Incoming UDP Packet Queue ASLL(UDPPacketInternal, alink) inQueue; - int onCallbackQueue; - Action *callbackAction; - EThread *ethread; + int onCallbackQueue = 0; + Action *callbackAction = nullptr; + EThread *ethread = nullptr; EventIO ep; UnixUDPConnection(int the_fd); ~UnixUDPConnection() override; private: - int m_errno; + int m_errno = 0; void UDPConnection_is_abstract() override{}; }; TS_INLINE -UnixUDPConnection::UnixUDPConnection(int the_fd) : onCallbackQueue(0), callbackAction(nullptr), ethread(nullptr), m_errno(0) +UnixUDPConnection::UnixUDPConnection(int the_fd) { fd = the_fd; SET_HANDLER(&UnixUDPConnection::callbackHandler); diff --git a/iocore/net/QUICClosedConCollector.cc b/iocore/net/QUICClosedConCollector.cc new file mode 100644 index 00000000000..647468e1cf3 --- /dev/null +++ b/iocore/net/QUICClosedConCollector.cc @@ -0,0 +1,63 @@ +/** @file + This file implements an I/O Processor for network I/O + @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_QUICClosedConCollector.h" + +QUICClosedConCollector::QUICClosedConCollector() +{ + SET_HANDLER(&QUICClosedConCollector::mainEvent); +} + +int +QUICClosedConCollector::mainEvent(int event, Event *e) +{ + EThread *t = this->mutex->thread_holding; + ink_assert(t == this_thread()); + + this->_process_closed_connection(t); + return 0; +} + +void +QUICClosedConCollector::_process_closed_connection(EThread *t) +{ + ink_release_assert(t != nullptr); + + QUICNetVConnection *qvc; + Que(QUICNetVConnection, closed_link) local_queue; + + while ((qvc = this->_localClosedQueue.pop())) { + if (qvc->shouldDestroy()) { + qvc->destroy(t); + } else { + local_queue.push(qvc); + } + } + + SList(QUICNetVConnection, closed_alink) aq(this->closedQueue.popall()); + while ((qvc = aq.pop())) { + qvc->remove_connection_ids(); + if (qvc->shouldDestroy()) { + qvc->destroy(t); + } else { + local_queue.push(qvc); + } + } + + this->_localClosedQueue.append(local_queue); +} diff --git a/iocore/net/QUICMultiCertConfigLoader.cc b/iocore/net/QUICMultiCertConfigLoader.cc new file mode 100644 index 00000000000..a948a6f8f2e --- /dev/null +++ b/iocore/net/QUICMultiCertConfigLoader.cc @@ -0,0 +1,350 @@ +/** @file + * + * A brief file description + * + * @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 "QUICMultiCertConfigLoader.h" +#include "P_SSLConfig.h" +#include "P_SSLNextProtocolSet.h" +#include "P_OCSPStapling.h" +#include "QUICGlobals.h" +#include "QUICConfig.h" +#include "QUICConnection.h" +#include "QUICTypes.h" +// #include "QUICGlobals.h" + +#define QUICConfDebug(fmt, ...) Debug("quic_conf", fmt, ##__VA_ARGS__) +#define QUICGlobalQCDebug(qc, fmt, ...) Debug("quic_global", "[%s] " fmt, qc->cids().data(), ##__VA_ARGS__) + +int QUICCertConfig::_config_id = 0; + +// +// QUICCertConfig +// +void +QUICCertConfig::startup() +{ + reconfigure(); +} + +void +QUICCertConfig::reconfigure() +{ + SSLConfig::scoped_config params; + SSLCertLookup *lookup = new SSLCertLookup(); + + QUICMultiCertConfigLoader loader(params); + loader.load(lookup); + + _config_id = configProcessor.set(_config_id, lookup); +} + +SSLCertLookup * +QUICCertConfig::acquire() +{ + return static_cast(configProcessor.get(_config_id)); +} + +void +QUICCertConfig::release(SSLCertLookup *lookup) +{ + configProcessor.release(_config_id, lookup); +} + +// +// QUICMultiCertConfigLoader +// +SSL_CTX * +QUICMultiCertConfigLoader::default_server_ssl_ctx() +{ + return quic_new_ssl_ctx(); +} + +SSL_CTX * +QUICMultiCertConfigLoader::init_server_ssl_ctx(std::vector &cert_list, const SSLMultiCertConfigParams *multi_cert_params) +{ + const SSLConfigParams *params = this->_params; + + SSL_CTX *ctx = this->default_server_ssl_ctx(); + + if (multi_cert_params) { + if (multi_cert_params->dialog) { + // TODO: dialog support + } + + if (multi_cert_params->cert) { + if (!SSLMultiCertConfigLoader::load_certs(ctx, cert_list, params, multi_cert_params)) { + goto fail; + } + } + + // SSL_CTX_load_verify_locations() builds the cert chain from the + // serverCACertFilename if that is not nullptr. Otherwise, it uses the hashed + // symlinks in serverCACertPath. + // + // if ssl_ca_name is NOT configured for this cert in ssl_multicert.config + // AND + // if proxy.config.ssl.CA.cert.filename and proxy.config.ssl.CA.cert.path + // are configured + // pass that file as the chain (include all certs in that file) + // else if proxy.config.ssl.CA.cert.path is configured (and + // proxy.config.ssl.CA.cert.filename is nullptr) + // use the hashed symlinks in that directory to build the chain + if (!multi_cert_params->ca && params->serverCACertPath != nullptr) { + if ((!SSL_CTX_load_verify_locations(ctx, params->serverCACertFilename, params->serverCACertPath)) || + (!SSL_CTX_set_default_verify_paths(ctx))) { + Error("invalid CA Certificate file or CA Certificate path"); + goto fail; + } + } + } + + if (params->clientCertLevel != 0) { + // TODO: client cert support + } + + if (!SSLMultiCertConfigLoader::set_session_id_context(ctx, params, multi_cert_params)) { + goto fail; + } + +#if TS_USE_TLS_SET_CIPHERSUITES + if (params->server_tls13_cipher_suites != nullptr) { + if (!SSL_CTX_set_ciphersuites(ctx, params->server_tls13_cipher_suites)) { + Error("invalid tls server cipher suites in records.config"); + goto fail; + } + } +#endif + +#if defined(SSL_CTX_set1_groups_list) || defined(SSL_CTX_set1_curves_list) + if (params->server_groups_list != nullptr) { +#ifdef SSL_CTX_set1_groups_list + if (!SSL_CTX_set1_groups_list(ctx, params->server_groups_list)) { +#else + if (!SSL_CTX_set1_curves_list(ctx, params->server_groups_list)) { +#endif + Error("invalid groups list for server in records.config"); + goto fail; + } + } +#endif + + // SSL_CTX_set_info_callback(ctx, ssl_callback_info); + + SSL_CTX_set_alpn_select_cb(ctx, QUICMultiCertConfigLoader::ssl_select_next_protocol, nullptr); + +#if TS_USE_TLS_OCSP + if (SSLConfigParams::ssl_ocsp_enabled) { + QUICConfDebug("SSL OCSP Stapling is enabled"); + SSL_CTX_set_tlsext_status_cb(ctx, ssl_callback_ocsp_stapling); + const char *cert_name = multi_cert_params ? multi_cert_params->cert.get() : nullptr; + + for (auto cert : cert_list) { + if (!ssl_stapling_init_cert(ctx, cert, cert_name, nullptr)) { + Warning("failed to configure SSL_CTX for OCSP Stapling info for certificate at %s", cert_name); + } + } + } else { + QUICConfDebug("SSL OCSP Stapling is disabled"); + } +#else + if (SSLConfigParams::ssl_ocsp_enabled) { + Warning("failed to enable SSL OCSP Stapling; this version of OpenSSL does not support it"); + } +#endif /* TS_USE_TLS_OCSP */ + + if (SSLConfigParams::init_ssl_ctx_cb) { + SSLConfigParams::init_ssl_ctx_cb(ctx, true); + } + + return ctx; + +fail: + SSLReleaseContext(ctx); + for (auto cert : cert_list) { + X509_free(cert); + } + + return nullptr; +} + +SSL_CTX * +QUICMultiCertConfigLoader::_store_ssl_ctx(SSLCertLookup *lookup, const shared_SSLMultiCertConfigParams multi_cert_params) +{ + std::vector cert_list; + shared_SSL_CTX ctx(this->init_server_ssl_ctx(cert_list, multi_cert_params.get()), SSL_CTX_free); + shared_ssl_ticket_key_block keyblock = nullptr; + bool inserted = false; + + if (!ctx || !multi_cert_params) { + lookup->is_valid = false; + return nullptr; + } + + const char *certname = multi_cert_params->cert.get(); + for (auto cert : cert_list) { + if (0 > SSLMultiCertConfigLoader::check_server_cert_now(cert, certname)) { + /* At this point, we know cert is bad, and we've already printed a + descriptive reason as to why cert is bad to the log file */ + QUICConfDebug("Marking certificate as NOT VALID: %s", certname); + lookup->is_valid = false; + } + } + + // Index this certificate by the specified IP(v6) address. If the address is "*", make it the default context. + if (multi_cert_params->addr) { + if (strcmp(multi_cert_params->addr, "*") == 0) { + if (lookup->insert(multi_cert_params->addr, SSLCertContext(ctx, multi_cert_params, keyblock)) >= 0) { + inserted = true; + lookup->ssl_default = ctx; + this->_set_handshake_callbacks(ctx.get()); + } + } else { + IpEndpoint ep; + + if (ats_ip_pton(multi_cert_params->addr, &ep) == 0) { + QUICConfDebug("mapping '%s' to certificate %s", (const char *)multi_cert_params->addr, (const char *)certname); + if (lookup->insert(ep, SSLCertContext(ctx, multi_cert_params, keyblock)) >= 0) { + inserted = true; + } + } else { + Error("'%s' is not a valid IPv4 or IPv6 address", (const char *)multi_cert_params->addr); + lookup->is_valid = false; + } + } + } + + // Insert additional mappings. Note that this maps multiple keys to the same value, so when + // this code is updated to reconfigure the SSL certificates, it will need some sort of + // refcounting or alternate way of avoiding double frees. + QUICConfDebug("importing SNI names from %s", (const char *)certname); + for (auto cert : cert_list) { + if (SSLMultiCertConfigLoader::index_certificate(lookup, SSLCertContext(ctx, multi_cert_params), cert, certname)) { + inserted = true; + } + } + + if (inserted) { + if (SSLConfigParams::init_ssl_ctx_cb) { + SSLConfigParams::init_ssl_ctx_cb(ctx.get(), true); + } + } + + if (!inserted) { + SSLReleaseContext(ctx.get()); + ctx = nullptr; + } + + for (auto &i : cert_list) { + X509_free(i); + } + + return ctx.get(); +} + +void +QUICMultiCertConfigLoader::_set_handshake_callbacks(SSL_CTX *ssl_ctx) +{ + SSL_CTX_set_cert_cb(ssl_ctx, QUICMultiCertConfigLoader::ssl_cert_cb, nullptr); + SSL_CTX_set_tlsext_servername_callback(ssl_ctx, QUICMultiCertConfigLoader::ssl_sni_cb); + + // Set client hello callback if needed + // SSL_CTX_set_client_hello_cb(ctx, QUIC::ssl_client_hello_cb, nullptr); +} + +int +QUICMultiCertConfigLoader::ssl_select_next_protocol(SSL *ssl, const unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned inlen, void *) +{ + const unsigned char *npn; + unsigned npnsz = 0; + QUICConnection *qc = static_cast(SSL_get_ex_data(ssl, QUIC::ssl_quic_qc_index)); + + qc->next_protocol_set()->advertiseProtocols(&npn, &npnsz); + if (SSL_select_next_proto((unsigned char **)out, outlen, npn, npnsz, in, inlen) == OPENSSL_NPN_NEGOTIATED) { + return SSL_TLSEXT_ERR_OK; + } + + *out = nullptr; + *outlen = 0; + return SSL_TLSEXT_ERR_NOACK; +} + +int +QUICMultiCertConfigLoader::ssl_sni_cb(SSL *ssl, int * /*ad*/, void * /*arg*/) +{ + // XXX: add SNIConfig support ? + // XXX: add TRANSPORT_BLIND_TUNNEL support ? + return 1; +} + +int +QUICMultiCertConfigLoader::ssl_cert_cb(SSL *ssl, void * /*arg*/) +{ + shared_SSL_CTX ctx = nullptr; + SSLCertContext *cc = nullptr; + QUICCertConfig::scoped_config lookup; + const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + QUICConnection *qc = static_cast(SSL_get_ex_data(ssl, QUIC::ssl_quic_qc_index)); + + if (servername == nullptr) { + servername = ""; + } + QUICGlobalQCDebug(qc, "SNI=%s", servername); + + // The incoming SSL_CTX is either the one mapped from the inbound IP address or the default one. If we + // don't find a name-based match at this point, we *do not* want to mess with the context because we've + // already made a best effort to find the best match. + if (likely(servername)) { + cc = lookup->find((char *)servername); + if (cc && cc->getCtx()) { + ctx = cc->getCtx(); + } + } + + // If there's no match on the server name, try to match on the peer address. + if (ctx == nullptr) { + QUICFiveTuple five_tuple = qc->five_tuple(); + IpEndpoint ip = five_tuple.destination(); + cc = lookup->find(ip); + + if (cc && cc->getCtx()) { + ctx = cc->getCtx(); + } + } + + bool found = true; + if (ctx != nullptr) { + SSL_set_SSL_CTX(ssl, ctx.get()); + } else { + found = false; + } + + SSL_CTX *verify_ctx = nullptr; + verify_ctx = SSL_get_SSL_CTX(ssl); + QUICGlobalQCDebug(qc, "%s SSL_CTX %p for requested name '%s'", found ? "found" : "using", verify_ctx, servername); + + if (verify_ctx == nullptr) { + return 0; + } + + return 1; +} diff --git a/iocore/net/QUICMultiCertConfigLoader.h b/iocore/net/QUICMultiCertConfigLoader.h new file mode 100644 index 00000000000..f29bda6ff25 --- /dev/null +++ b/iocore/net/QUICMultiCertConfigLoader.h @@ -0,0 +1,58 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include "P_SSLCertLookup.h" +#include "P_SSLUtils.h" + +class QUICCertConfig +{ +public: + static void startup(); + static void reconfigure(); + static SSLCertLookup *acquire(); + static void release(SSLCertLookup *lookup); + + using scoped_config = ConfigProcessor::scoped_config; + +private: + static int _config_id; +}; + +class QUICMultiCertConfigLoader : public SSLMultiCertConfigLoader +{ +public: + QUICMultiCertConfigLoader(const SSLConfigParams *p) : SSLMultiCertConfigLoader(p) {} + + virtual SSL_CTX *default_server_ssl_ctx() override; + virtual SSL_CTX *init_server_ssl_ctx(std::vector &cert_list, const SSLMultiCertConfigParams *multi_cert_params) override; + +private: + virtual SSL_CTX *_store_ssl_ctx(SSLCertLookup *lookup, const shared_SSLMultiCertConfigParams multi_cert_params) override; + virtual void _set_handshake_callbacks(SSL_CTX *ssl_ctx) override; + static int ssl_select_next_protocol(SSL *ssl, const unsigned char **out, unsigned char *outlen, const unsigned char *in, + unsigned inlen, void *); + static int ssl_cert_cb(SSL *ssl, void *arg); + static int ssl_sni_cb(SSL *ssl, int *ad, void *arg); +}; diff --git a/iocore/net/QUICNet.cc b/iocore/net/QUICNet.cc new file mode 100644 index 00000000000..d3df166be11 --- /dev/null +++ b/iocore/net/QUICNet.cc @@ -0,0 +1,171 @@ +/** @file + + A brief file description + + @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 "quic/QUICEvents.h" + +ClassAllocator quicPollEventAllocator("quicPollEvent"); + +void +QUICPollEvent::init(QUICConnection *con, UDPPacketInternal *packet) +{ + this->con = con; + this->packet = packet; + if (con != nullptr) { + static_cast(con)->refcount_inc(); + } +} + +void +QUICPollEvent::free() +{ + if (this->con != nullptr) { + ink_assert(static_cast(this->con)->refcount_dec() >= 0); + this->con = nullptr; + } + + quicPollEventAllocator.free(this); +} + +QUICPollCont::QUICPollCont(Ptr &m) : Continuation(m.get()), net_handler(nullptr) +{ + SET_HANDLER(&QUICPollCont::pollEvent); +} + +QUICPollCont::QUICPollCont(Ptr &m, NetHandler *nh) : Continuation(m.get()), net_handler(nh) +{ + SET_HANDLER(&QUICPollCont::pollEvent); +} + +QUICPollCont::~QUICPollCont() {} + +void +QUICPollCont::_process_long_header_packet(QUICPollEvent *e, NetHandler *nh) +{ + UDPPacketInternal *p = e->packet; + // FIXME: VC is nullptr ? + QUICNetVConnection *vc = static_cast(e->con); + uint8_t *buf = (uint8_t *)p->getIOBlockChain()->buf(); + + QUICPacketType ptype; + QUICPacketLongHeader::type(ptype, buf, 1); + if (ptype == QUICPacketType::INITIAL && !vc->read.triggered) { + SCOPED_MUTEX_LOCK(lock, vc->mutex, this_ethread()); + vc->read.triggered = 1; + vc->handle_received_packet(p); + vc->handleEvent(QUIC_EVENT_PACKET_READ_READY, nullptr); + e->free(); + + return; + } + + if (vc) { + SCOPED_MUTEX_LOCK(lock, vc->mutex, this_ethread()); + vc->read.triggered = 1; + vc->handle_received_packet(p); + } else { + this->_longInQueue.push(p); + } + + // Push QUICNetVC into nethandler's enabled list + if (vc != nullptr) { + int isin = ink_atomic_swap(&vc->read.in_enabled_list, 1); + if (!isin) { + nh->read_enable_list.push(vc); + } + } + + // Note: We should free QUICPollEvent here since vc could be freed from other thread. + e->free(); +} + +void +QUICPollCont::_process_short_header_packet(QUICPollEvent *e, NetHandler *nh) +{ + UDPPacketInternal *p = e->packet; + QUICNetVConnection *vc = static_cast(e->con); + + vc->read.triggered = 1; + vc->handle_received_packet(p); + + // Push QUICNetVC into nethandler's enabled list + int isin = ink_atomic_swap(&vc->read.in_enabled_list, 1); + if (!isin) { + nh->read_enable_list.push(vc); + } + + // Note: We should free QUICPollEvent here since vc could be freed from other thread. + e->free(); +} + +// +// QUICPollCont continuation which traverse the inQueue(ASLL) +// and create new QUICNetVC for Initial Packet, +// and push the triggered QUICNetVC into enable list. +// +int +QUICPollCont::pollEvent(int, Event *) +{ + ink_assert(this->mutex->thread_holding == this_thread()); + uint8_t *buf; + QUICPollEvent *e; + NetHandler *nh = get_NetHandler(this->mutex->thread_holding); + + // Process the ASLL + SList(QUICPollEvent, alink) aq(inQueue.popall()); + Queue result; + while ((e = aq.pop())) { + QUICNetVConnection *qvc = static_cast(e->con); + UDPPacketInternal *p = e->packet; + if (qvc != nullptr && qvc->in_closed_queue) { + p->free(); + e->free(); + continue; + } + result.push(e); + } + + while ((e = result.pop())) { + buf = (uint8_t *)e->packet->getIOBlockChain()->buf(); + if (QUICInvariants::is_long_header(buf)) { + // Long Header Packet with Connection ID, has a valid type value. + this->_process_long_header_packet(e, nh); + } else { + // Short Header Packet with Connection ID, has a valid type value. + this->_process_short_header_packet(e, nh); + } + } + + return EVENT_CONT; +} + +void +initialize_thread_for_quic_net(EThread *thread) +{ + NetHandler *nh = get_NetHandler(thread); + QUICPollCont *quicpc = get_QUICPollCont(thread); + + new ((ink_dummy_for_new *)quicpc) QUICPollCont(thread->mutex, nh); + + thread->schedule_every(quicpc, -HRTIME_MSECONDS(UDP_PERIOD)); +} diff --git a/iocore/net/QUICNetProcessor.cc b/iocore/net/QUICNetProcessor.cc new file mode 100644 index 00000000000..c14e000230c --- /dev/null +++ b/iocore/net/QUICNetProcessor.cc @@ -0,0 +1,221 @@ +/** @file + + @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 "tscore/ink_config.h" +#include "tscore/I_Layout.h" + +#include "P_Net.h" +#include "records/I_RecHttp.h" + +#include "QUICGlobals.h" +#include "QUICConfig.h" +#include "QUICMultiCertConfigLoader.h" + +// +// Global Data +// + +QUICNetProcessor quic_NetProcessor; + +QUICNetProcessor::QUICNetProcessor() {} + +QUICNetProcessor::~QUICNetProcessor() +{ + // TODO: clear all values before destory the table. + delete this->_ctable; +} + +void +QUICNetProcessor::init() +{ + // first we allocate a QUICPollCont. + this->quicPollCont_offset = eventProcessor.allocate(sizeof(QUICPollCont)); + + // schedule event + eventProcessor.schedule_spawn(&initialize_thread_for_quic_net, ET_NET); +} + +int +QUICNetProcessor::start(int, size_t stacksize) +{ + QUIC::init(); + // This initialization order matters ... + // QUICInitializeLibrary(); + QUICConfig::startup(); + QUICCertConfig::startup(); + +#ifdef TLS1_3_VERSION_DRAFT_TXT + // FIXME: remove this when TLS1_3_VERSION_DRAFT_TXT is removed + Debug("quic_ps", "%s", TLS1_3_VERSION_DRAFT_TXT); +#endif + + return 0; +} + +NetAccept * +QUICNetProcessor::createNetAccept(const NetProcessor::AcceptOptions &opt) +{ + if (this->_ctable == nullptr) { + QUICConfig::scoped_config params; + this->_ctable = new QUICConnectionTable(params->connection_table_size()); + } + return (NetAccept *)new QUICPacketHandlerIn(opt, *this->_ctable); +} + +NetVConnection * +QUICNetProcessor::allocate_vc(EThread *t) +{ + QUICNetVConnection *vc; + + if (t) { + vc = THREAD_ALLOC(quicNetVCAllocator, t); + new (vc) QUICNetVConnection(); + } else { + if (likely(vc = quicNetVCAllocator.alloc())) { + new (vc) QUICNetVConnection(); + vc->from_accept_thread = true; + } + } + + vc->ep.syscall = false; + return vc; +} + +Action * +QUICNetProcessor::connect_re(Continuation *cont, sockaddr const *remote_addr, NetVCOptions *opt) +{ + Debug("quic_ps", "connect to server"); + EThread *t = cont->mutex->thread_holding; + ink_assert(t); + QUICNetVConnection *vc = static_cast(this->allocate_vc(t)); + + if (opt) { + vc->options = *opt; + } else { + opt = &vc->options; + } + + int fd; + Action *status; + bool result = udpNet.CreateUDPSocket(&fd, remote_addr, &status, *opt); + if (!result) { + vc->free(t); + return status; + } + + // Setup UDPConnection + UnixUDPConnection *con = new UnixUDPConnection(fd); + Debug("quic_ps", "con=%p fd=%d", con, fd); + + QUICPacketHandlerOut *packet_handler = new QUICPacketHandlerOut(); + if (opt->local_ip.isValid()) { + con->setBinding(opt->local_ip, opt->local_port); + } + con->bindToThread(packet_handler); + + PollCont *pc = get_UDPPollCont(con->ethread); + PollDescriptor *pd = pc->pollDescriptor; + + errno = 0; + int res = con->ep.start(pd, con, EVENTIO_READ); + if (res < 0) { + Debug("udpnet", "Error: %s (%d)", strerror(errno), errno); + } + + // Setup QUICNetVConnection + QUICConnectionId client_dst_cid; + client_dst_cid.randomize(); + // vc->init set handler of vc `QUICNetVConnection::startEvent` + vc->init(client_dst_cid, client_dst_cid, con, packet_handler); + packet_handler->init(vc); + + // Connection ID will be changed + vc->id = net_next_connection_number(); + vc->set_context(NET_VCONNECTION_OUT); + vc->con.setRemote(remote_addr); + vc->submit_time = Thread::get_hrtime(); + vc->mutex = cont->mutex; + vc->action_ = cont; + + if (t->is_event_type(opt->etype)) { + MUTEX_TRY_LOCK(lock, cont->mutex, t); + if (lock.is_locked()) { + MUTEX_TRY_LOCK(lock2, get_NetHandler(t)->mutex, t); + if (lock2.is_locked()) { + vc->connectUp(t, NO_FD); + return ACTION_RESULT_DONE; + } + } + } + + // Try to stay on the current thread if it is the right type + if (t->is_event_type(opt->etype)) { + t->schedule_imm(vc); + } else { // Otherwise, pass along to another thread of the right type + eventProcessor.schedule_imm(vc, opt->etype); + } + + return ACTION_RESULT_DONE; +} + +Action * +QUICNetProcessor::main_accept(Continuation *cont, SOCKET fd, AcceptOptions const &opt) +{ + // UnixNetProcessor *this_unp = static_cast(this); + Debug("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); + + ProxyMutex *mutex = this_ethread()->mutex.get(); + int accept_threads = opt.accept_threads; // might be changed. + IpEndpoint accept_ip; // local binding address. + // char thr_name[MAX_THREAD_NAME_LENGTH]; + + NetAccept *na = createNetAccept(opt); + + if (accept_threads < 0) { + REC_ReadConfigInteger(accept_threads, "proxy.config.accept_threads"); + } + NET_INCREMENT_DYN_STAT(net_accepts_currently_open_stat); + + 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.port() = htons(opt.local_port); + + na->accept_fn = net_accept; + na->server.fd = fd; + ats_ip_copy(&na->server.accept_addr, &accept_ip); + + na->action_ = new NetAcceptAction(); + *na->action_ = cont; + na->action_->server = &na->server; + na->init_accept(); + + SCOPED_MUTEX_LOCK(lock, na->mutex, this_ethread()); + udpNet.UDPBind((Continuation *)na, &na->server.accept_addr.sa, 1048576, 1048576); + + return na->action_.get(); +} diff --git a/iocore/net/QUICNetVConnection.cc b/iocore/net/QUICNetVConnection.cc new file mode 100644 index 00000000000..5bbd25b8781 --- /dev/null +++ b/iocore/net/QUICNetVConnection.cc @@ -0,0 +1,2393 @@ +/** @file + + A brief file description + + @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 +#include + +#include "tscore/ink_config.h" +#include "records/I_RecHttp.h" +#include "tscore/Diags.h" + +#include "P_Net.h" +#include "InkAPIInternal.h" // Added to include the quic_hook definitions +#include "Log.h" + +#include "P_SSLNextProtocolSet.h" +#include "QUICMultiCertConfigLoader.h" +#include "QUICTLS.h" + +#include "QUICStats.h" +#include "QUICGlobals.h" +#include "QUICDebugNames.h" +#include "QUICEvents.h" +#include "QUICConfig.h" +#include "QUICIntUtil.h" + +using namespace std::literals; +static constexpr std::string_view QUIC_DEBUG_TAG = "quic_net"sv; + +#define QUICConDebug(fmt, ...) Debug(QUIC_DEBUG_TAG.data(), "[%s] " fmt, this->cids().data(), ##__VA_ARGS__) + +#define QUICConVDebug(fmt, ...) Debug("v_quic_net", "[%s] " fmt, this->cids().data(), ##__VA_ARGS__) +#define QUICConVVVDebug(fmt, ...) Debug("vvv_quic_net", "[%s] " fmt, this->cids().data(), ##__VA_ARGS__) + +#define QUICFCDebug(fmt, ...) Debug("quic_flow_ctrl", "[%s] " fmt, this->cids().data(), ##__VA_ARGS__) + +#define QUICError(fmt, ...) \ + Debug("quic_net", "[%s] " fmt, this->cids().data(), ##__VA_ARGS__); \ + Error("quic_net [%s] " fmt, this->cids().data(), ##__VA_ARGS__) + +static constexpr uint32_t IPV4_HEADER_SIZE = 20; +static constexpr uint32_t IPV6_HEADER_SIZE = 40; +static constexpr uint32_t UDP_HEADER_SIZE = 8; +static constexpr uint32_t MAX_PACKET_OVERHEAD = 62; ///< Max long header len without length of token field of Initial packet +static constexpr uint32_t MINIMUM_INITIAL_PACKET_SIZE = 1200; +static constexpr ink_hrtime WRITE_READY_INTERVAL = HRTIME_MSECONDS(2); +static constexpr uint32_t PACKET_PER_EVENT = 256; +static constexpr uint32_t MAX_CONSECUTIVE_STREAMS = 8; ///< Interrupt sending STREAM frames to send ACK frame +static constexpr uint32_t MIN_PKT_PAYLOAD_LEN = 3; ///< Minimum payload length for sampling for header protection + +static constexpr uint32_t STATE_CLOSING_MAX_SEND_PKT_NUM = 8; ///< Max number of sending packets which contain a closing frame. +static constexpr uint32_t STATE_CLOSING_MAX_RECV_PKT_WIND = 1 << STATE_CLOSING_MAX_SEND_PKT_NUM; + +ClassAllocator quicNetVCAllocator("quicNetVCAllocator"); + +class QUICTPConfigQCP : public QUICTPConfig +{ +public: + QUICTPConfigQCP(const QUICConfigParams *params, NetVConnectionContext_t ctx) : _params(params), _ctx(ctx) {} + + uint32_t + no_activity_timeout() const override + { + if (this->_ctx == NET_VCONNECTION_IN) { + return this->_params->no_activity_timeout_in(); + } else { + return this->_params->no_activity_timeout_out(); + } + } + + const IpEndpoint * + preferred_address_ipv4() const override + { + return this->_params->preferred_address_ipv4(); + } + + const IpEndpoint * + preferred_address_ipv6() const override + { + return this->_params->preferred_address_ipv6(); + } + + uint32_t + initial_max_data() const override + { + if (this->_ctx == NET_VCONNECTION_IN) { + return this->_params->initial_max_data_in(); + } else { + return this->_params->initial_max_data_out(); + } + } + + uint32_t + initial_max_stream_data_bidi_local() const override + { + if (this->_ctx == NET_VCONNECTION_IN) { + return this->_params->initial_max_stream_data_bidi_local_in(); + } else { + return this->_params->initial_max_stream_data_bidi_local_out(); + } + } + + uint32_t + initial_max_stream_data_bidi_remote() const override + { + if (this->_ctx == NET_VCONNECTION_IN) { + return this->_params->initial_max_stream_data_bidi_remote_in(); + } else { + return this->_params->initial_max_stream_data_bidi_remote_out(); + } + } + + uint32_t + initial_max_stream_data_uni() const override + { + if (this->_ctx == NET_VCONNECTION_IN) { + return this->_params->initial_max_stream_data_uni_in(); + } else { + return this->_params->initial_max_stream_data_uni_out(); + } + } + + uint64_t + initial_max_streams_bidi() const override + { + if (this->_ctx == NET_VCONNECTION_IN) { + return this->_params->initial_max_streams_bidi_in(); + } else { + return this->_params->initial_max_streams_bidi_out(); + } + } + + uint64_t + initial_max_streams_uni() const override + { + if (this->_ctx == NET_VCONNECTION_IN) { + return this->_params->initial_max_streams_uni_in(); + } else { + return this->_params->initial_max_streams_uni_out(); + } + } + + uint8_t + ack_delay_exponent() const override + { + if (this->_ctx == NET_VCONNECTION_IN) { + return this->_params->ack_delay_exponent_in(); + } else { + return this->_params->ack_delay_exponent_out(); + } + } + + uint8_t + max_ack_delay() const override + { + if (this->_ctx == NET_VCONNECTION_IN) { + return this->_params->max_ack_delay_in(); + } else { + return this->_params->max_ack_delay_out(); + } + } + +private: + const QUICConfigParams *_params; + NetVConnectionContext_t _ctx; +}; + +class QUICCCConfigQCP : public QUICCCConfig +{ +public: + QUICCCConfigQCP(const QUICConfigParams *params) : _params(params) {} + + uint32_t + max_datagram_size() const override + { + return this->_params->cc_max_datagram_size(); + } + + uint32_t + initial_window() const override + { + return this->_params->cc_initial_window(); + } + + uint32_t + minimum_window() const override + { + return this->_params->cc_minimum_window(); + } + + float + loss_reduction_factor() const override + { + return this->_params->cc_loss_reduction_factor(); + } + + uint32_t + persistent_congestion_threshold() const override + { + return this->_params->cc_persistent_congestion_threshold(); + } + +private: + const QUICConfigParams *_params; +}; + +class QUICLDConfigQCP : public QUICLDConfig +{ +public: + QUICLDConfigQCP(const QUICConfigParams *params) : _params(params) {} + + uint32_t + packet_threshold() const override + { + return this->_params->ld_packet_threshold(); + } + + float + time_threshold() const override + { + return this->_params->ld_time_threshold(); + } + + ink_hrtime + granularity() const override + { + return this->_params->ld_granularity(); + } + + ink_hrtime + initial_rtt() const override + { + return this->_params->ld_initial_rtt(); + } + +private: + const QUICConfigParams *_params; +}; + +QUICNetVConnection::QUICNetVConnection() : _packet_factory(this->_pp_key_info), _ph_protector(this->_pp_key_info) {} + +QUICNetVConnection::~QUICNetVConnection() +{ + this->_unschedule_ack_manager_periodic(); + this->_unschedule_packet_write_ready(); + this->_unschedule_closing_timeout(); + this->_unschedule_closed_event(); + this->_unschedule_path_validation_timeout(); +} + +// XXX This might be called on ET_UDP thread +// Initialize QUICNetVC for out going connection (NET_VCONNECTION_OUT) +void +QUICNetVConnection::init(QUICConnectionId peer_cid, QUICConnectionId original_cid, UDPConnection *udp_con, + QUICPacketHandler *packet_handler) +{ + SET_HANDLER((NetVConnHandler)&QUICNetVConnection::startEvent); + this->_udp_con = udp_con; + this->_packet_handler = packet_handler; + this->_peer_quic_connection_id = peer_cid; + this->_original_quic_connection_id = original_cid; + this->_quic_connection_id.randomize(); + + this->_update_cids(); + + if (is_debug_tag_set(QUIC_DEBUG_TAG.data())) { + char dcid_hex_str[QUICConnectionId::MAX_HEX_STR_LENGTH]; + char scid_hex_str[QUICConnectionId::MAX_HEX_STR_LENGTH]; + this->_peer_quic_connection_id.hex(dcid_hex_str, QUICConnectionId::MAX_HEX_STR_LENGTH); + this->_quic_connection_id.hex(scid_hex_str, QUICConnectionId::MAX_HEX_STR_LENGTH); + + QUICConDebug("dcid=%s scid=%s", dcid_hex_str, scid_hex_str); + } +} + +// Initialize QUICNetVC for in coming connection (NET_VCONNECTION_IN) +void +QUICNetVConnection::init(QUICConnectionId peer_cid, QUICConnectionId original_cid, QUICConnectionId first_cid, + UDPConnection *udp_con, QUICPacketHandler *packet_handler, QUICConnectionTable *ctable) +{ + SET_HANDLER((NetVConnHandler)&QUICNetVConnection::acceptEvent); + this->_udp_con = udp_con; + this->_packet_handler = packet_handler; + this->_peer_quic_connection_id = peer_cid; + this->_original_quic_connection_id = original_cid; + this->_first_quic_connection_id = first_cid; + this->_quic_connection_id.randomize(); + + if (ctable) { + this->_ctable = ctable; + this->_ctable->insert(this->_quic_connection_id, this); + this->_ctable->insert(this->_original_quic_connection_id, this); + } + + this->_update_cids(); + + if (is_debug_tag_set(QUIC_DEBUG_TAG.data())) { + char dcid_hex_str[QUICConnectionId::MAX_HEX_STR_LENGTH]; + char scid_hex_str[QUICConnectionId::MAX_HEX_STR_LENGTH]; + this->_peer_quic_connection_id.hex(dcid_hex_str, QUICConnectionId::MAX_HEX_STR_LENGTH); + this->_quic_connection_id.hex(scid_hex_str, QUICConnectionId::MAX_HEX_STR_LENGTH); + + QUICConDebug("dcid=%s scid=%s", dcid_hex_str, scid_hex_str); + } +} + +bool +QUICNetVConnection::shouldDestroy() +{ + return this->refcount() == 0; +} + +VIO * +QUICNetVConnection::do_io_read(Continuation *c, int64_t nbytes, MIOBuffer *buf) +{ + ink_assert(false); + return nullptr; +} + +VIO * +QUICNetVConnection::do_io_write(Continuation *c, int64_t nbytes, IOBufferReader *buf, bool owner) +{ + ink_assert(false); + return nullptr; +} + +int +QUICNetVConnection::acceptEvent(int event, Event *e) +{ + EThread *t = (e == nullptr) ? this_ethread() : e->ethread; + NetHandler *h = get_NetHandler(t); + + MUTEX_TRY_LOCK(lock, h->mutex, t); + if (!lock.is_locked()) { + if (event == EVENT_NONE) { + t->schedule_in(this, HRTIME_MSECONDS(net_retry_delay)); + return EVENT_DONE; + } else { + e->schedule_in(HRTIME_MSECONDS(net_retry_delay)); + return EVENT_CONT; + } + } + + // 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) { + free(t); + return EVENT_DONE; + } + + // FIXME: complete do_io_xxxx instead + this->read.enabled = 1; + + // Handshake callback handler. + SET_HANDLER((NetVConnHandler)&QUICNetVConnection::state_pre_handshake); + + // Send this netvc to InactivityCop. + nh->startCop(this); + + if (inactivity_timeout_in) { + set_inactivity_timeout(inactivity_timeout_in); + } else { + set_inactivity_timeout(0); + } + + if (active_timeout_in) { + set_active_timeout(active_timeout_in); + } + + this->start(); + + action_.continuation->handleEvent(NET_EVENT_ACCEPT, this); + this->_schedule_packet_write_ready(); + + return EVENT_DONE; +} + +int +QUICNetVConnection::startEvent(int event, Event *e) +{ + ink_assert(event == EVENT_IMMEDIATE); + MUTEX_TRY_LOCK(lock, get_NetHandler(e->ethread)->mutex, e->ethread); + if (!lock.is_locked()) { + e->schedule_in(HRTIME_MSECONDS(net_retry_delay)); + return EVENT_CONT; + } + + if (!action_.cancelled) { + this->connectUp(e->ethread, NO_FD); + } else { + this->free(e->ethread); + } + + return EVENT_DONE; +} + +// XXX This might be called on ET_UDP thread +void +QUICNetVConnection::start() +{ + ink_release_assert(this->thread != nullptr); + + this->_five_tuple.update(this->local_addr, this->remote_addr, SOCK_DGRAM); + // Version 0x00000001 uses stream 0 for cryptographic handshake with TLS 1.3, but newer version may not + if (this->direction() == NET_VCONNECTION_IN) { + QUICCertConfig::scoped_config server_cert; + + this->_pp_key_info.set_context(QUICPacketProtectionKeyInfo::Context::SERVER); + this->_ack_frame_manager.set_ack_delay_exponent(this->_quic_config->ack_delay_exponent_in()); + this->_reset_token = QUICStatelessResetToken(this->_quic_connection_id, this->_quic_config->instance_id()); + this->_hs_protocol = this->_setup_handshake_protocol(server_cert->ssl_default); + this->_handshake_handler = + new QUICHandshake(this, this->_hs_protocol, this->_reset_token, this->_quic_config->stateless_retry()); + this->_ack_frame_manager.set_max_ack_delay(this->_quic_config->max_ack_delay_in()); + this->_schedule_ack_manager_periodic(this->_quic_config->max_ack_delay_in()); + } else { + QUICTPConfigQCP tp_config(this->_quic_config, NET_VCONNECTION_OUT); + this->_pp_key_info.set_context(QUICPacketProtectionKeyInfo::Context::CLIENT); + this->_ack_frame_manager.set_ack_delay_exponent(this->_quic_config->ack_delay_exponent_out()); + this->_hs_protocol = this->_setup_handshake_protocol(this->_quic_config->client_ssl_ctx()); + this->_handshake_handler = new QUICHandshake(this, this->_hs_protocol); + this->_handshake_handler->start(tp_config, &this->_packet_factory, this->_quic_config->vn_exercise_enabled()); + this->_handshake_handler->do_handshake(); + this->_ack_frame_manager.set_max_ack_delay(this->_quic_config->max_ack_delay_out()); + this->_schedule_ack_manager_periodic(this->_quic_config->max_ack_delay_out()); + } + + this->_application_map = new QUICApplicationMap(); + + this->_frame_dispatcher = new QUICFrameDispatcher(this); + + // Create frame handlers + QUICCCConfigQCP cc_config(this->_quic_config); + QUICLDConfigQCP ld_config(this->_quic_config); + this->_rtt_measure.init(ld_config); + this->_congestion_controller = new QUICCongestionController(this->_rtt_measure, this, cc_config); + this->_loss_detector = new QUICLossDetector(this, this->_congestion_controller, &this->_rtt_measure, ld_config); + this->_frame_dispatcher->add_handler(this->_loss_detector); + + this->_remote_flow_controller = new QUICRemoteConnectionFlowController(UINT64_MAX); + this->_local_flow_controller = new QUICLocalConnectionFlowController(&this->_rtt_measure, UINT64_MAX); + this->_path_validator = new QUICPathValidator(); + this->_stream_manager = new QUICStreamManager(this, &this->_rtt_measure, this->_application_map); + + // Register frame generators + this->_frame_generators.push_back(this->_handshake_handler); // CRYPTO + this->_frame_generators.push_back(this->_path_validator); // PATH_CHALLENGE, PATH_RESPOSNE + this->_frame_generators.push_back(this->_local_flow_controller); // MAX_DATA + this->_frame_generators.push_back(this->_remote_flow_controller); // DATA_BLOCKED + this->_frame_generators.push_back(this); // NEW_TOKEN + this->_frame_generators.push_back(this->_stream_manager); // STREAM, MAX_STREAM_DATA, STREAM_DATA_BLOCKED + this->_frame_generators.push_back(&this->_ack_frame_manager); // ACK + this->_frame_generators.push_back(&this->_pinger); // PING + + // Register frame handlers + this->_frame_dispatcher->add_handler(this); + this->_frame_dispatcher->add_handler(this->_stream_manager); + this->_frame_dispatcher->add_handler(this->_path_validator); + this->_frame_dispatcher->add_handler(this->_handshake_handler); +} + +void +QUICNetVConnection::free(EThread *t) +{ + QUICConDebug("Free connection"); + + /* TODO: Uncmment these blocks after refactoring read / write process + this->_udp_con = nullptr; + this->_packet_handler = nullptr; + + _unschedule_packet_write_ready(); + + delete this->_handshake_handler; + delete this->_application_map; + delete this->_hs_protocol; + delete this->_loss_detector; + delete this->_frame_dispatcher; + delete this->_stream_manager; + delete this->_congestion_controller; + if (this->_alt_con_manager) { + delete this->_alt_con_manager; + } + + super::clear(); + */ + this->_packet_handler->close_connection(this); +} + +void +QUICNetVConnection::free() +{ + this->free(this_ethread()); +} + +// called by ET_UDP +void +QUICNetVConnection::remove_connection_ids() +{ + if (this->_ctable) { + this->_ctable->erase(this->_original_quic_connection_id, this); + this->_ctable->erase(this->_quic_connection_id, this); + } + + if (this->_alt_con_manager) { + this->_alt_con_manager->invalidate_alt_connections(); + } +} + +// called by ET_UDP +void +QUICNetVConnection::destroy(EThread *t) +{ + QUICConDebug("Destroy connection"); + /* TODO: Uncmment these blocks after refactoring read / write process + if (from_accept_thread) { + quicNetVCAllocator.free(this); + } else { + THREAD_FREE(this, quicNetVCAllocator, t); + } + */ +} + +void +QUICNetVConnection::reenable(VIO *vio) +{ + return; +} + +int +QUICNetVConnection::connectUp(EThread *t, int fd) +{ + int res = 0; + NetHandler *nh = get_NetHandler(t); + this->thread = this_ethread(); + ink_assert(nh->mutex->thread_holding == this->thread); + + SET_HANDLER((NetVConnHandler)&QUICNetVConnection::state_pre_handshake); + + if ((res = nh->startIO(this)) < 0) { + // FIXME: startIO only return 0 now! what should we do if it failed ? + } + + nh->startCop(this); + + // FIXME: complete do_io_xxxx instead + this->read.enabled = 1; + + this->start(); + + // start QUIC handshake + this->_schedule_packet_write_ready(); + + return CONNECT_SUCCESS; +} + +QUICConnectionId +QUICNetVConnection::peer_connection_id() const +{ + return this->_peer_quic_connection_id; +} + +QUICConnectionId +QUICNetVConnection::original_connection_id() const +{ + return this->_original_quic_connection_id; +} + +QUICConnectionId +QUICNetVConnection::first_connection_id() const +{ + return this->_first_quic_connection_id; +} + +QUICConnectionId +QUICNetVConnection::connection_id() const +{ + return this->_quic_connection_id; +} + +/* + Return combination of dst connection id and src connection id for debug log + e.g. "aaaaaaaa-bbbbbbbb" + - "aaaaaaaa" : high 32 bit of dst connection id + - "bbbbbbbb" : high 32 bit of src connection id + */ +std::string_view +QUICNetVConnection::cids() const +{ + return this->_cids; +} + +const QUICFiveTuple +QUICNetVConnection::five_tuple() const +{ + return this->_five_tuple; +} + +uint32_t +QUICNetVConnection::pmtu() const +{ + return this->_pmtu; +} + +NetVConnectionContext_t +QUICNetVConnection::direction() const +{ + return this->netvc_context; +} + +uint32_t +QUICNetVConnection::_minimum_quic_packet_size() +{ + if (netvc_context == NET_VCONNECTION_OUT) { + // FIXME Only the first packet need to be 1200 bytes at least + return MINIMUM_INITIAL_PACKET_SIZE; + } else { + // FIXME This size should be configurable and should have some randomness + // This is just for providing protection against packet analysis for protected packets + return 32 + (this->_rnd() & 0x3f); // 32 to 96 + } +} + +uint32_t +QUICNetVConnection::_maximum_quic_packet_size() const +{ + if (this->options.ip_family == PF_INET6) { + return this->_pmtu - UDP_HEADER_SIZE - IPV6_HEADER_SIZE; + } else { + return this->_pmtu - UDP_HEADER_SIZE - IPV4_HEADER_SIZE; + } +} + +uint64_t +QUICNetVConnection::_maximum_stream_frame_data_size() +{ + return this->_maximum_quic_packet_size() - MAX_STREAM_FRAME_OVERHEAD - MAX_PACKET_OVERHEAD; +} + +QUICStreamManager * +QUICNetVConnection::stream_manager() +{ + return this->_stream_manager; +} + +void +QUICNetVConnection::handle_received_packet(UDPPacket *packet) +{ + this->_packet_recv_queue.enqueue(packet); +} + +void +QUICNetVConnection::ping() +{ + this->_pinger.request(QUICEncryptionLevel::ONE_RTT); +} + +void +QUICNetVConnection::close(QUICConnectionErrorUPtr error) +{ + if (this->handler == reinterpret_cast(&QUICNetVConnection::state_connection_closed) || + this->handler == reinterpret_cast(&QUICNetVConnection::state_connection_closing)) { + // do nothing + } else { + this->_switch_to_closing_state(std::move(error)); + } +} + +std::vector +QUICNetVConnection::interests() +{ + return {QUICFrameType::CONNECTION_CLOSE, QUICFrameType::DATA_BLOCKED, QUICFrameType::MAX_DATA}; +} + +QUICConnectionErrorUPtr +QUICNetVConnection::handle_frame(QUICEncryptionLevel level, const QUICFrame &frame) +{ + QUICConnectionErrorUPtr error = nullptr; + + switch (frame.type()) { + case QUICFrameType::MAX_DATA: + this->_remote_flow_controller->forward_limit(static_cast(frame).maximum_data()); + QUICFCDebug("[REMOTE] %" PRIu64 "/%" PRIu64, this->_remote_flow_controller->current_offset(), + this->_remote_flow_controller->current_limit()); + this->_schedule_packet_write_ready(); + break; + case QUICFrameType::DATA_BLOCKED: + // DATA_BLOCKED frame is for debugging. Nothing to do here. + break; + case QUICFrameType::CONNECTION_CLOSE: + if (this->handler == reinterpret_cast(&QUICNetVConnection::state_connection_closed) || + this->handler == reinterpret_cast(&QUICNetVConnection::state_connection_draining)) { + return error; + } + + // 7.9.1. Closing and Draining Connection States + // An endpoint MAY transition from the closing period to the draining period if it can confirm that its peer is also closing or + // draining. Receiving a closing frame is sufficient confirmation, as is receiving a stateless reset. + { + uint16_t error_code = static_cast(frame).error_code(); + this->_switch_to_draining_state( + QUICConnectionErrorUPtr(std::make_unique(static_cast(error_code)))); + } + break; + default: + QUICConDebug("Unexpected frame type: %02x", static_cast(frame.type())); + ink_assert(false); + break; + } + + return error; +} + +// XXX Setup QUICNetVConnection on regular EThread. +// QUICNetVConnection::init() might be called on ET_UDP EThread. +int +QUICNetVConnection::state_pre_handshake(int event, Event *data) +{ + SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread()); + + // this->thread should be assigned on any direction + ink_assert(this->thread == this_ethread()); + + if (!this->nh) { + this->nh = get_NetHandler(this_ethread()); + } + + // FIXME: Should be accept_no_activity_timeout? + if (this->get_context() == NET_VCONNECTION_IN) { + this->set_inactivity_timeout(HRTIME_MSECONDS(this->_quic_config->no_activity_timeout_in())); + } else { + this->set_inactivity_timeout(HRTIME_MSECONDS(this->_quic_config->no_activity_timeout_out())); + } + + this->add_to_active_queue(); + + this->_switch_to_handshake_state(); + return this->handleEvent(event, data); +} + +// TODO: Timeout by active_timeout +int +QUICNetVConnection::state_handshake(int event, Event *data) +{ + SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread()); + + if (this->_handshake_handler && this->_handshake_handler->is_completed()) { + this->_switch_to_established_state(); + return this->handleEvent(event, data); + } + + QUICConnectionErrorUPtr error = nullptr; + + switch (event) { + case QUIC_EVENT_PACKET_READ_READY: { + QUICPacketCreationResult result; + net_activity(this, this_ethread()); + do { + QUICPacketUPtr packet = this->_dequeue_recv_packet(result); + if (result == QUICPacketCreationResult::NOT_READY) { + error = nullptr; + } else if (result == QUICPacketCreationResult::FAILED) { + // Don't make this error, and discard the packet. + // Because: + // - Attacker can terminate connections + // - It could be just an errora on lower layer + error = nullptr; + } else if (result == QUICPacketCreationResult::SUCCESS || result == QUICPacketCreationResult::UNSUPPORTED) { + error = this->_state_handshake_process_packet(*packet); + } + + // if we complete handshake, switch to establish state + if (this->_handshake_handler && this->_handshake_handler->is_completed()) { + this->_switch_to_established_state(); + return this->handleEvent(event, data); + } + + } while (error == nullptr && (result == QUICPacketCreationResult::SUCCESS || result == QUICPacketCreationResult::IGNORED)); + break; + } + case QUIC_EVENT_ACK_PERIODIC: + this->_handle_periodic_ack_event(); + break; + case QUIC_EVENT_PACKET_WRITE_READY: + this->_close_packet_write_ready(data); + // TODO: support RETRY packet + error = this->_state_common_send_packet(); + // Reschedule WRITE_READY + this->_schedule_packet_write_ready(true); + break; + case QUIC_EVENT_PATH_VALIDATION_TIMEOUT: + this->_handle_path_validation_timeout(data); + break; + case EVENT_IMMEDIATE: + // Start Immediate Close because of Idle Timeout + this->_handle_idle_timeout(); + break; + default: + QUICConDebug("Unexpected event: %s (%d)", QUICDebugNames::quic_event(event), event); + } + + if (error != nullptr) { + this->_handle_error(std::move(error)); + } + + return EVENT_CONT; +} + +int +QUICNetVConnection::state_connection_established(int event, Event *data) +{ + SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread()); + QUICConnectionErrorUPtr error = nullptr; + switch (event) { + case QUIC_EVENT_PACKET_READ_READY: + error = this->_state_connection_established_receive_packet(); + break; + case QUIC_EVENT_ACK_PERIODIC: + this->_handle_periodic_ack_event(); + break; + case QUIC_EVENT_PACKET_WRITE_READY: + this->_close_packet_write_ready(data); + error = this->_state_common_send_packet(); + // Reschedule WRITE_READY + this->_schedule_packet_write_ready(true); + break; + case QUIC_EVENT_PATH_VALIDATION_TIMEOUT: + this->_handle_path_validation_timeout(data); + break; + case EVENT_IMMEDIATE: + // Start Immediate Close because of Idle Timeout + this->_handle_idle_timeout(); + break; + default: + QUICConDebug("Unexpected event: %s (%d)", QUICDebugNames::quic_event(event), event); + } + + if (error != nullptr) { + QUICConDebug("QUICError: cls=%u, code=0x%" PRIx16, static_cast(error->cls), error->code); + this->_handle_error(std::move(error)); + } + + return EVENT_CONT; +} + +int +QUICNetVConnection::state_connection_closing(int event, Event *data) +{ + SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread()); + + QUICConnectionErrorUPtr error = nullptr; + switch (event) { + case QUIC_EVENT_PACKET_READ_READY: + error = this->_state_closing_receive_packet(); + break; + case QUIC_EVENT_PACKET_WRITE_READY: + this->_close_packet_write_ready(data); + this->_state_closing_send_packet(); + break; + case QUIC_EVENT_PATH_VALIDATION_TIMEOUT: + this->_handle_path_validation_timeout(data); + break; + case QUIC_EVENT_CLOSING_TIMEOUT: + this->_close_closing_timeout(data); + this->_switch_to_close_state(); + break; + case QUIC_EVENT_ACK_PERIODIC: + default: + QUICConDebug("Unexpected event: %s (%d)", QUICDebugNames::quic_event(event), event); + ink_assert(false); + } + + return EVENT_DONE; +} + +int +QUICNetVConnection::state_connection_draining(int event, Event *data) +{ + SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread()); + + QUICConnectionErrorUPtr error = nullptr; + switch (event) { + case QUIC_EVENT_PACKET_READ_READY: + error = this->_state_draining_receive_packet(); + break; + case QUIC_EVENT_PACKET_WRITE_READY: + // Do not send any packets in this state. + // This should be the only difference between this and closing_state. + this->_close_packet_write_ready(data); + break; + case QUIC_EVENT_PATH_VALIDATION_TIMEOUT: + this->_handle_path_validation_timeout(data); + break; + case QUIC_EVENT_CLOSING_TIMEOUT: + this->_close_closing_timeout(data); + this->_switch_to_close_state(); + break; + case QUIC_EVENT_ACK_PERIODIC: + default: + QUICConDebug("Unexpected event: %s (%d)", QUICDebugNames::quic_event(event), event); + ink_assert(false); + } + + return EVENT_DONE; +} + +int +QUICNetVConnection::state_connection_closed(int event, Event *data) +{ + SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread()); + switch (event) { + case QUIC_EVENT_SHUTDOWN: { + this->_unschedule_ack_manager_periodic(); + this->_unschedule_packet_write_ready(); + this->_unschedule_closing_timeout(); + this->_unschedule_path_validation_timeout(); + this->_close_closed_event(data); + this->next_inactivity_timeout_at = 0; + this->next_activity_timeout_at = 0; + + this->inactivity_timeout_in = 0; + this->active_timeout_in = 0; + + // TODO: Drop record from Connection-ID - QUICNetVConnection table in QUICPacketHandler + // Shutdown loss detector + SCOPED_MUTEX_LOCK(lock2, this->_loss_detector->mutex, this_ethread()); + this->_loss_detector->handleEvent(QUIC_EVENT_LD_SHUTDOWN, nullptr); + + // FIXME I'm not sure whether we can block here, but it's needed to not crash. + SCOPED_MUTEX_LOCK(lock, this->nh->mutex, this_ethread()); + if (this->nh) { + this->nh->free_netvc(this); + } else { + this->free(this->mutex->thread_holding); + } + break; + } + case QUIC_EVENT_PACKET_WRITE_READY: { + this->_close_packet_write_ready(data); + break; + } + default: + QUICConDebug("Unexpected event: %s (%d)", QUICDebugNames::quic_event(event), event); + } + + return EVENT_DONE; +} + +UDPConnection * +QUICNetVConnection::get_udp_con() +{ + return this->_udp_con; +} + +void +QUICNetVConnection::net_read_io(NetHandler *nh, EThread *lthread) +{ + SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread()); + this->handleEvent(QUIC_EVENT_PACKET_READ_READY, nullptr); + + return; +} + +int64_t +QUICNetVConnection::load_buffer_and_write(int64_t towrite, MIOBufferAccessor &buf, int64_t &total_written, int &needs) +{ + ink_assert(false); + + return 0; +} + +int +QUICNetVConnection::populate_protocol(std::string_view *results, int n) const +{ + int retval = 0; + if (n > retval) { + results[retval++] = IP_PROTO_TAG_QUIC; + if (n > retval) { + retval += super::populate_protocol(results + retval, n - retval); + } + } + return retval; +} + +const char * +QUICNetVConnection::protocol_contains(std::string_view prefix) const +{ + const char *retval = nullptr; + std::string_view tag = IP_PROTO_TAG_QUIC; + if (prefix.size() <= tag.size() && strncmp(tag.data(), prefix.data(), prefix.size()) == 0) { + retval = tag.data(); + } else { + retval = super::protocol_contains(prefix); + } + return retval; +} + +void +QUICNetVConnection::registerNextProtocolSet(SSLNextProtocolSet *s) +{ + this->_next_protocol_set = s; +} + +bool +QUICNetVConnection::is_closed() const +{ + return this->handler == reinterpret_cast(&QUICNetVConnection::state_connection_closed); +} + +SSLNextProtocolSet * +QUICNetVConnection::next_protocol_set() const +{ + return this->_next_protocol_set; +} + +QUICPacketNumber +QUICNetVConnection::_largest_acked_packet_number(QUICEncryptionLevel level) const +{ + auto index = QUICTypeUtil::pn_space(level); + + return this->_loss_detector->largest_acked_packet_number(index); +} + +std::string_view +QUICNetVConnection::negotiated_application_name() const +{ + const uint8_t *name; + unsigned int name_len = 0; + + this->_hs_protocol->negotiated_application_name(&name, &name_len); + + return std::string_view(reinterpret_cast(name), name_len); +} + +QUICConnectionErrorUPtr +QUICNetVConnection::_state_handshake_process_packet(const QUICPacket &packet) +{ + QUICConnectionErrorUPtr error = nullptr; + switch (packet.type()) { + case QUICPacketType::VERSION_NEGOTIATION: + error = this->_state_handshake_process_version_negotiation_packet(packet); + break; + case QUICPacketType::INITIAL: + error = this->_state_handshake_process_initial_packet(packet); + break; + case QUICPacketType::RETRY: + error = this->_state_handshake_process_retry_packet(packet); + break; + case QUICPacketType::HANDSHAKE: + error = this->_state_handshake_process_handshake_packet(packet); + if (this->_pp_key_info.is_decryption_key_available(QUICKeyPhase::INITIAL) && this->netvc_context == NET_VCONNECTION_IN) { + this->_pp_key_info.drop_keys(QUICKeyPhase::INITIAL); + this->_minimum_encryption_level = QUICEncryptionLevel::HANDSHAKE; + } + break; + case QUICPacketType::ZERO_RTT_PROTECTED: + error = this->_state_handshake_process_zero_rtt_protected_packet(packet); + break; + case QUICPacketType::PROTECTED: + default: + QUICConDebug("Ignore %s(%" PRIu8 ") packet", QUICDebugNames::packet_type(packet.type()), static_cast(packet.type())); + + error = std::make_unique(QUICTransErrorCode::INTERNAL_ERROR); + break; + } + return error; +} + +QUICConnectionErrorUPtr +QUICNetVConnection::_state_handshake_process_version_negotiation_packet(const QUICPacket &packet) +{ + QUICConnectionErrorUPtr error = nullptr; + + if (packet.destination_cid() != this->connection_id()) { + QUICConDebug("Ignore Version Negotiation packet"); + return error; + } + + if (this->_handshake_handler->is_version_negotiated()) { + QUICConDebug("ignore VN - already negotiated"); + } else { + error = this->_handshake_handler->negotiate_version(packet, &this->_packet_factory); + + // discard all transport state except packet number + this->_loss_detector->reset(); + + this->_congestion_controller->reset(); + + // start handshake over + this->_handshake_handler->reset(); + this->_handshake_handler->do_handshake(); + this->_schedule_packet_write_ready(); + } + + return error; +} + +QUICConnectionErrorUPtr +QUICNetVConnection::_state_handshake_process_initial_packet(const QUICPacket &packet) +{ + // QUIC packet could be smaller than MINIMUM_INITIAL_PACKET_SIZE when coalescing packets + // if (packet->size() < MINIMUM_INITIAL_PACKET_SIZE) { + // QUICConDebug("Packet size is smaller than the minimum initial packet size"); + // // Ignore the packet + // return QUICErrorUPtr(new QUICNoError()); + // } + + QUICConnectionErrorUPtr error = nullptr; + + // Start handshake + if (this->netvc_context == NET_VCONNECTION_IN) { + if (!this->_alt_con_manager) { + this->_alt_con_manager = + new QUICAltConnectionManager(this, *this->_ctable, this->_peer_quic_connection_id, this->_quic_config->instance_id(), + this->_quic_config->num_alt_connection_ids(), this->_quic_config->preferred_address_ipv4(), + this->_quic_config->preferred_address_ipv6()); + this->_frame_generators.push_back(this->_alt_con_manager); + this->_frame_dispatcher->add_handler(this->_alt_con_manager); + } + QUICTPConfigQCP tp_config(this->_quic_config, NET_VCONNECTION_IN); + error = this->_handshake_handler->start(tp_config, packet, &this->_packet_factory, this->_alt_con_manager->preferred_address()); + + // If version negotiation was failed and VERSION NEGOTIATION packet was sent, nothing to do. + if (this->_handshake_handler->is_version_negotiated()) { + error = this->_recv_and_ack(packet); + + if (error == nullptr && !this->_handshake_handler->has_remote_tp()) { + error = std::make_unique(QUICTransErrorCode::TRANSPORT_PARAMETER_ERROR); + } + } + } else { + // on client side, _handshake_handler is already started. Just process packet like _state_handshake_process_handshake_packet() + error = this->_recv_and_ack(packet); + } + + return error; +} + +/** + This doesn't call this->_recv_and_ack(), because RETRY packet doesn't have any frames. + */ +QUICConnectionErrorUPtr +QUICNetVConnection::_state_handshake_process_retry_packet(const QUICPacket &packet) +{ + ink_assert(this->netvc_context == NET_VCONNECTION_OUT); + + if (this->_av_token) { + QUICConDebug("Ignore RETRY packet - already processed before"); + return nullptr; + } + + // TODO: move packet->payload to _av_token + this->_av_token_len = packet.payload_length(); + this->_av_token = ats_unique_malloc(this->_av_token_len); + memcpy(this->_av_token.get(), packet.payload(), this->_av_token_len); + + // discard all transport state + this->_handshake_handler->reset(); + this->_packet_factory.reset(); + this->_loss_detector->reset(); + + this->_congestion_controller->reset(); + this->_packet_recv_queue.reset(); + + // Initialize Key Materials with peer CID. Because peer CID is DCID of (second) INITIAL packet from client which reply to RETRY + // packet from server + this->_hs_protocol->initialize_key_materials(this->_peer_quic_connection_id); + + // start handshake over + this->_handshake_handler->do_handshake(); + this->_schedule_packet_write_ready(); + + return nullptr; +} + +QUICConnectionErrorUPtr +QUICNetVConnection::_state_handshake_process_handshake_packet(const QUICPacket &packet) +{ + // Source address is verified by receiving any message from the client encrypted using the + // Handshake keys. + if (this->netvc_context == NET_VCONNECTION_IN && !this->_verfied_state.is_verified()) { + this->_verfied_state.set_addr_verifed(); + } + return this->_recv_and_ack(packet); +} + +QUICConnectionErrorUPtr +QUICNetVConnection::_state_handshake_process_zero_rtt_protected_packet(const QUICPacket &packet) +{ + this->_stream_manager->init_flow_control_params(this->_handshake_handler->local_transport_parameters(), + this->_handshake_handler->remote_transport_parameters()); + this->_start_application(); + return this->_recv_and_ack(packet); +} + +QUICConnectionErrorUPtr +QUICNetVConnection::_state_connection_established_process_protected_packet(const QUICPacket &packet) +{ + QUICConnectionErrorUPtr error = nullptr; + bool has_non_probing_frame = false; + + error = this->_recv_and_ack(packet, &has_non_probing_frame); + if (error != nullptr) { + return error; + } + + // Migrate connection if required + // FIXME Connection migration will be initiated when a peer sent non-probing frames. + // We need to two or more paths because we need to respond to probing packets on a new path and also need to send other frames + // on the old path until they initiate migration. + // if (packet.destination_cid() == this->_quic_connection_id && has_non_probing_frame) { + if (this->_alt_con_manager != nullptr) { + if (packet.destination_cid() != this->_quic_connection_id || !ats_ip_addr_port_eq(packet.from(), this->remote_addr)) { + if (!has_non_probing_frame) { + QUICConDebug("FIXME: Connection migration has been initiated without non-probing frames"); + } + error = this->_state_connection_established_migrate_connection(packet); + if (error != nullptr) { + return error; + } + } + } + + // For Connection Migration excercise + if (this->netvc_context == NET_VCONNECTION_OUT && this->_quic_config->cm_exercise_enabled()) { + this->_state_connection_established_initiate_connection_migration(); + } + + return error; +} + +QUICConnectionErrorUPtr +QUICNetVConnection::_state_connection_established_receive_packet() +{ + QUICConnectionErrorUPtr error = nullptr; + QUICPacketCreationResult result; + + // Receive a QUIC packet + net_activity(this, this_ethread()); + do { + QUICPacketUPtr packet = this->_dequeue_recv_packet(result); + if (result == QUICPacketCreationResult::FAILED) { + // Don't make this error, and discard the packet. + // Because: + // - Attacker can terminate connections + // - It could be just an errora on lower layer + continue; + } else if (result == QUICPacketCreationResult::NO_PACKET) { + return error; + } else if (result == QUICPacketCreationResult::NOT_READY) { + return error; + } else if (result == QUICPacketCreationResult::IGNORED) { + continue; + } + + // Process the packet + switch (packet->type()) { + case QUICPacketType::PROTECTED: + error = this->_state_connection_established_process_protected_packet(*packet); + break; + case QUICPacketType::INITIAL: + case QUICPacketType::HANDSHAKE: + case QUICPacketType::ZERO_RTT_PROTECTED: + // Pass packet to _recv_and_ack to send ack to the packet. Stream data will be discarded by offset mismatch. + error = this->_recv_and_ack(*packet); + break; + default: + QUICConDebug("Unknown packet type: %s(%" PRIu8 ")", QUICDebugNames::packet_type(packet->type()), + static_cast(packet->type())); + + error = std::make_unique(QUICTransErrorCode::INTERNAL_ERROR); + break; + } + + } while (error == nullptr && (result == QUICPacketCreationResult::SUCCESS || result == QUICPacketCreationResult::IGNORED)); + return error; +} + +QUICConnectionErrorUPtr +QUICNetVConnection::_state_closing_receive_packet() +{ + while (this->_packet_recv_queue.size() > 0) { + QUICPacketCreationResult result; + QUICPacketUPtr packet = this->_dequeue_recv_packet(result); + if (result == QUICPacketCreationResult::SUCCESS) { + switch (packet->type()) { + case QUICPacketType::VERSION_NEGOTIATION: + // Ignore VN packets on closing state + break; + default: + this->_recv_and_ack(*packet); + break; + } + } + ++this->_state_closing_recv_packet_count; + + if (this->_state_closing_recv_packet_window < STATE_CLOSING_MAX_RECV_PKT_WIND && + this->_state_closing_recv_packet_count >= this->_state_closing_recv_packet_window) { + this->_state_closing_recv_packet_count = 0; + this->_state_closing_recv_packet_window <<= 1; + + this->_schedule_packet_write_ready(true); + break; + } + } + + return nullptr; +} + +QUICConnectionErrorUPtr +QUICNetVConnection::_state_draining_receive_packet() +{ + while (this->_packet_recv_queue.size() > 0) { + QUICPacketCreationResult result; + QUICPacketUPtr packet = this->_dequeue_recv_packet(result); + if (result == QUICPacketCreationResult::SUCCESS) { + this->_recv_and_ack(*packet); + // Do NOT schedule WRITE_READY event from this point. + // An endpoint in the draining state MUST NOT send any packets. + } + } + + return nullptr; +} + +/** + * 1. Check congestion window + * 2. Allocate buffer for UDP Payload + * 3. Generate QUIC Packet + * 4. Store data to the paylaod + * 5. Send UDP Packet + */ +QUICConnectionErrorUPtr +QUICNetVConnection::_state_common_send_packet() +{ + uint32_t packet_count = 0; + uint32_t error = 0; + while (error == 0 && packet_count < PACKET_PER_EVENT) { + uint32_t window = this->_congestion_controller->open_window(); + + if (window == 0) { + break; + } + + Ptr udp_payload(new_IOBufferBlock()); + uint32_t udp_payload_len = std::min(window, this->_pmtu); + udp_payload->alloc(iobuffer_size_to_index(udp_payload_len)); + + uint32_t written = 0; + for (int i = static_cast(this->_minimum_encryption_level); i <= static_cast(QUICEncryptionLevel::ONE_RTT); ++i) { + auto level = QUIC_ENCRYPTION_LEVELS[i]; + if (level == QUICEncryptionLevel::ONE_RTT && !this->_handshake_handler->is_completed()) { + continue; + } + + uint32_t max_packet_size = udp_payload_len - written; + if (this->netvc_context == NET_VCONNECTION_IN && !this->_verfied_state.is_verified()) { + max_packet_size = std::min(max_packet_size, this->_verfied_state.windows()); + } + + QUICPacketInfoUPtr packet_info = std::make_unique(); + QUICPacketUPtr packet = this->_packetize_frames(level, max_packet_size, packet_info->frames); + + if (packet) { + packet_info->packet_number = packet->packet_number(); + packet_info->time_sent = Thread::get_hrtime(); + packet_info->ack_eliciting = packet->is_ack_eliciting(); + packet_info->is_crypto_packet = packet->is_crypto_packet(); + packet_info->in_flight = true; + if (packet_info->ack_eliciting) { + packet_info->sent_bytes = packet->size(); + } else { + packet_info->sent_bytes = 0; + } + packet_info->type = packet->type(); + packet_info->pn_space = QUICTypeUtil::pn_space(level); + + if (this->netvc_context == NET_VCONNECTION_IN && !this->_verfied_state.is_verified()) { + QUICConDebug("send to unverified window: %u", this->_verfied_state.windows()); + this->_verfied_state.consume(packet->size()); + } + + // TODO: do not write two QUIC Short Header Packets + uint8_t *buf = reinterpret_cast(udp_payload->end()); + size_t len = 0; + packet->store(buf, &len); + udp_payload->fill(len); + written += len; + + int dcil = (this->_peer_quic_connection_id == QUICConnectionId::ZERO()) ? 0 : this->_peer_quic_connection_id.length(); + this->_ph_protector.protect(buf, len, dcil); + + QUICConDebug("[TX] %s packet #%" PRIu64 " size=%zu", QUICDebugNames::packet_type(packet->type()), packet->packet_number(), + len); + + if (this->_pp_key_info.is_encryption_key_available(QUICKeyPhase::INITIAL) && packet->type() == QUICPacketType::HANDSHAKE && + this->netvc_context == NET_VCONNECTION_OUT) { + this->_pp_key_info.drop_keys(QUICKeyPhase::INITIAL); + this->_minimum_encryption_level = QUICEncryptionLevel::HANDSHAKE; + } + + this->_loss_detector->on_packet_sent(std::move(packet_info)); + packet_count++; + } + } + + if (written) { + this->_packet_handler->send_packet(this, udp_payload); + } else { + udp_payload->dealloc(); + break; + } + } + + if (packet_count) { + QUIC_INCREMENT_DYN_STAT_EX(QUICStats::total_packets_sent_stat, packet_count); + net_activity(this, this_ethread()); + } + + return nullptr; +} + +QUICConnectionErrorUPtr +QUICNetVConnection::_state_closing_send_packet() +{ + this->_packetize_closing_frame(); + + // TODO: should credit of congestion controller be checked? + + // During the closing period, an endpoint that sends a + // closing frame SHOULD respond to any packet that it receives with + // another packet containing a closing frame. To minimize the state + // that an endpoint maintains for a closing connection, endpoints MAY + // send the exact same packet. + if (this->_the_final_packet) { + this->_packet_handler->send_packet(*this->_the_final_packet, this, this->_ph_protector); + } + + return nullptr; +} + +Ptr +QUICNetVConnection::_store_frame(Ptr parent_block, size_t &size_added, uint64_t &max_frame_size, QUICFrame &frame, + std::vector &frames) +{ + Ptr new_block = frame.to_io_buffer_block(max_frame_size); + + size_added = 0; + Ptr tmp = new_block; + while (tmp) { + size_added += tmp->size(); + tmp = tmp->next; + } + + if (parent_block == nullptr) { + parent_block = new_block; + } else { + parent_block->next = new_block; + } + + // frame should be stored because it's created with max_frame_size + ink_assert(size_added != 0); + + max_frame_size -= size_added; + + if (is_debug_tag_set(QUIC_DEBUG_TAG.data())) { + char msg[1024]; + frame.debug_msg(msg, sizeof(msg)); + QUICConDebug("[TX] | %s", msg); + } + + frames.emplace_back(frame.id(), frame.generated_by()); + + while (parent_block->next) { + parent_block = parent_block->next; + } + return parent_block; +} + +// FIXME QUICNetVConnection should not know the actual type value of PADDING frame +Ptr +QUICNetVConnection::_generate_padding_frame(size_t frame_size) +{ + Ptr block = make_ptr(new_IOBufferBlock()); + block->alloc(iobuffer_size_to_index(frame_size)); + memset(block->start(), 0, frame_size); + block->fill(frame_size); + + ink_assert(frame_size == static_cast(block->size())); + + return block; +} + +QUICPacketUPtr +QUICNetVConnection::_packetize_frames(QUICEncryptionLevel level, uint64_t max_packet_size, std::vector &frames) +{ + ink_hrtime timestamp = Thread::get_hrtime(); + + QUICPacketUPtr packet = QUICPacketFactory::create_null_packet(); + if (max_packet_size <= MAX_PACKET_OVERHEAD) { + return packet; + } + + // TODO: adjust MAX_PACKET_OVERHEAD for each encryption level + uint64_t max_frame_size = max_packet_size - MAX_PACKET_OVERHEAD; + if (level == QUICEncryptionLevel::INITIAL && this->_av_token) { + max_frame_size = max_frame_size - (QUICVariableInt::size(this->_av_token_len) + this->_av_token_len); + } + max_frame_size = std::min(max_frame_size, this->_maximum_stream_frame_data_size()); + + bool probing = false; + int frame_count = 0; + size_t len = 0; + Ptr first_block = make_ptr(new_IOBufferBlock()); + Ptr last_block = first_block; + first_block->alloc(iobuffer_size_to_index(0)); + first_block->fill(0); + + if (!this->_has_ack_eliciting_packet_out) { + // Sent too much ack_only packet. At this moment we need to packetize a ping frame + // to force peer send ack frame. + this->_pinger.request(level); + } + + size_t size_added = 0; + bool ack_eliciting = false; + bool crypto = false; + uint8_t frame_instance_buffer[QUICFrame::MAX_INSTANCE_SIZE]; // This is for a frame instance but not serialized frame data + QUICFrame *frame = nullptr; + for (auto g : this->_frame_generators) { + while (g->will_generate_frame(level, timestamp)) { + // FIXME will_generate_frame should receive more parameters so we don't need extra checks + if (g == this->_remote_flow_controller && !this->_stream_manager->will_generate_frame(level, timestamp)) { + break; + } + if (g == this->_stream_manager && this->_path_validator->is_validating()) { + break; + } + + // Common block + frame = g->generate_frame(frame_instance_buffer, level, this->_remote_flow_controller->credit(), max_frame_size, timestamp); + if (frame) { + ++frame_count; + probing |= frame->is_probing_frame(); + if (frame->is_flow_controlled()) { + int ret = this->_remote_flow_controller->update(this->_stream_manager->total_offset_sent()); + QUICFCDebug("[REMOTE] %" PRIu64 "/%" PRIu64, this->_remote_flow_controller->current_offset(), + this->_remote_flow_controller->current_limit()); + ink_assert(ret == 0); + } + last_block = this->_store_frame(last_block, size_added, max_frame_size, *frame, frames); + len += size_added; + + // FIXME ACK frame should have priority + if (frame->type() == QUICFrameType::STREAM) { + if (++this->_stream_frames_sent % MAX_CONSECUTIVE_STREAMS == 0) { + break; + } + } + + if (!ack_eliciting && frame->type() != QUICFrameType::ACK) { + ack_eliciting = true; + this->_pinger.cancel(level); + } + + if (frame->type() == QUICFrameType::CRYPTO && + (level == QUICEncryptionLevel::INITIAL || level == QUICEncryptionLevel::HANDSHAKE)) { + crypto = true; + } + + frame->~QUICFrame(); + } else { + // Move to next generator + break; + } + } + } + + // Schedule a packet + if (len != 0) { + if (level == QUICEncryptionLevel::INITIAL && this->netvc_context == NET_VCONNECTION_OUT) { + // Pad with PADDING frames + uint64_t min_size = this->_minimum_quic_packet_size(); + if (this->_av_token) { + min_size = min_size - this->_av_token_len; + } + min_size = std::min(min_size, max_packet_size); + + if (min_size > len) { + Ptr pad_block = _generate_padding_frame(min_size - len); + last_block->next = pad_block; + len += pad_block->size(); + } + } else { + // Pad with PADDING frames to make sure payload length satisfy the minimum length for sampling for header protection + if (MIN_PKT_PAYLOAD_LEN > len) { + Ptr pad_block = _generate_padding_frame(MIN_PKT_PAYLOAD_LEN - len); + last_block->next = pad_block; + len += pad_block->size(); + } + } + + // Packet is retransmittable if it's not ack only packet + packet = this->_build_packet(level, first_block, ack_eliciting, probing, crypto); + this->_has_ack_eliciting_packet_out = ack_eliciting; + } + + return packet; +} + +void +QUICNetVConnection::_packetize_closing_frame() +{ + if (this->_connection_error == nullptr || this->_the_final_packet) { + return; + } + + QUICFrame *frame = nullptr; + + // CONNECTION_CLOSE + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + frame = QUICFrameFactory::create_connection_close_frame(frame_buf, *this->_connection_error); + + uint32_t max_size = this->_maximum_quic_packet_size(); + + size_t size_added = 0; + uint64_t max_frame_size = static_cast(max_size); + std::vector frames; + Ptr parent_block; + parent_block = nullptr; + parent_block = this->_store_frame(parent_block, size_added, max_frame_size, *frame, frames); + + QUICEncryptionLevel level = this->_hs_protocol->current_encryption_level(); + ink_assert(level != QUICEncryptionLevel::ZERO_RTT); + this->_the_final_packet = this->_build_packet(level, parent_block, true, false, false); +} + +QUICConnectionErrorUPtr +QUICNetVConnection::_recv_and_ack(const QUICPacket &packet, bool *has_non_probing_frame) +{ + ink_assert(packet.type() != QUICPacketType::RETRY); + + const uint8_t *payload = packet.payload(); + uint16_t size = packet.payload_length(); + QUICPacketNumber packet_num = packet.packet_number(); + QUICEncryptionLevel level = QUICTypeUtil::encryption_level(packet.type()); + + bool ack_only; + bool is_flow_controlled; + + QUICConnectionErrorUPtr error = nullptr; + if (has_non_probing_frame) { + *has_non_probing_frame = false; + } + + error = this->_frame_dispatcher->receive_frames(level, payload, size, ack_only, is_flow_controlled, has_non_probing_frame); + if (error != nullptr) { + return error; + } + + if (is_flow_controlled) { + int ret = this->_local_flow_controller->update(this->_stream_manager->total_offset_received()); + QUICFCDebug("[LOCAL] %" PRIu64 "/%" PRIu64, this->_local_flow_controller->current_offset(), + this->_local_flow_controller->current_limit()); + + if (ret != 0) { + return std::make_unique(QUICTransErrorCode::FLOW_CONTROL_ERROR); + } + + this->_local_flow_controller->forward_limit(this->_stream_manager->total_reordered_bytes() + this->_flow_control_buffer_size); + QUICFCDebug("[LOCAL] %" PRIu64 "/%" PRIu64, this->_local_flow_controller->current_offset(), + this->_local_flow_controller->current_limit()); + } + + this->_ack_frame_manager.update(level, packet_num, size, ack_only); + + return error; +} + +QUICPacketUPtr +QUICNetVConnection::_build_packet(QUICEncryptionLevel level, Ptr parent_block, bool ack_eliciting, bool probing, + bool crypto) +{ + QUICPacketType type = QUICTypeUtil::packet_type(level); + QUICPacketUPtr packet = QUICPacketFactory::create_null_packet(); + + // FIXME Pass parent_block to create_x_packet + // No need to make a unique buf here + ats_unique_buf buf = ats_unique_malloc(2048); + uint8_t *raw_buf = buf.get(); + size_t len = 0; + while (parent_block) { + memcpy(raw_buf + len, parent_block->start(), parent_block->size()); + len += parent_block->size(); + parent_block = parent_block->next; + } + + switch (type) { + case QUICPacketType::INITIAL: { + QUICConnectionId dcid = this->_peer_quic_connection_id; + ats_unique_buf token = {nullptr}; + size_t token_len = 0; + + if (this->netvc_context == NET_VCONNECTION_OUT) { + // TODO: Add a case of using token which is advertized by NEW_TOKEN frame + if (this->_av_token) { + token = ats_unique_malloc(this->_av_token_len); + token_len = this->_av_token_len; + memcpy(token.get(), this->_av_token.get(), token_len); + } else { + dcid = this->_original_quic_connection_id; + } + } + + packet = this->_packet_factory.create_initial_packet( + dcid, this->_quic_connection_id, this->_largest_acked_packet_number(QUICEncryptionLevel::INITIAL), std::move(buf), len, + ack_eliciting, probing, crypto, std::move(token), token_len); + break; + } + case QUICPacketType::HANDSHAKE: { + packet = this->_packet_factory.create_handshake_packet(this->_peer_quic_connection_id, this->_quic_connection_id, + this->_largest_acked_packet_number(QUICEncryptionLevel::HANDSHAKE), + std::move(buf), len, ack_eliciting, probing, crypto); + break; + } + case QUICPacketType::ZERO_RTT_PROTECTED: { + packet = this->_packet_factory.create_zero_rtt_packet(this->_original_quic_connection_id, this->_quic_connection_id, + this->_largest_acked_packet_number(QUICEncryptionLevel::ZERO_RTT), + std::move(buf), len, ack_eliciting, probing); + break; + } + case QUICPacketType::PROTECTED: { + packet = this->_packet_factory.create_protected_packet(this->_peer_quic_connection_id, + this->_largest_acked_packet_number(QUICEncryptionLevel::ONE_RTT), + std::move(buf), len, ack_eliciting, probing); + break; + } + default: + // should not be here + ink_assert(false); + break; + } + + return packet; +} + +void +QUICNetVConnection::_init_flow_control_params(const std::shared_ptr &local_tp, + const std::shared_ptr &remote_tp) +{ + this->_stream_manager->init_flow_control_params(local_tp, remote_tp); + + uint64_t local_initial_max_data = 0; + uint64_t remote_initial_max_data = 0; + if (local_tp) { + local_initial_max_data = local_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_DATA); + this->_flow_control_buffer_size = local_initial_max_data; + } + if (remote_tp) { + remote_initial_max_data = remote_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_DATA); + } + + this->_local_flow_controller->set_limit(local_initial_max_data); + this->_remote_flow_controller->set_limit(remote_initial_max_data); + QUICFCDebug("[LOCAL] %" PRIu64 "/%" PRIu64, this->_local_flow_controller->current_offset(), + this->_local_flow_controller->current_limit()); + QUICFCDebug("[REMOTE] %" PRIu64 "/%" PRIu64, this->_remote_flow_controller->current_offset(), + this->_remote_flow_controller->current_limit()); +} + +void +QUICNetVConnection::_handle_error(QUICConnectionErrorUPtr error) +{ + QUICError("QUICError: %s (%u), %s (0x%" PRIx16 ")", QUICDebugNames::error_class(error->cls), + static_cast(error->cls), QUICDebugNames::error_code(error->code), error->code); + + // Connection Error + this->close(std::move(error)); +} + +QUICPacketUPtr +QUICNetVConnection::_dequeue_recv_packet(QUICPacketCreationResult &result) +{ + QUICPacketUPtr packet = this->_packet_recv_queue.dequeue(result); + + if (result == QUICPacketCreationResult::SUCCESS) { + if (this->direction() == NET_VCONNECTION_OUT) { + // Reset CID if a server sent back a new CID + // FIXME This should happen only once + QUICConnectionId src_cid = packet->source_cid(); + // FIXME src connection id could be zero ? if so, check packet header type. + if (src_cid != QUICConnectionId::ZERO()) { + if (this->_peer_quic_connection_id != src_cid) { + this->_update_peer_cid(src_cid); + } + } + } + + if (!this->_verfied_state.is_verified()) { + this->_verfied_state.fill(packet->size()); + } + } + + // Debug prints + switch (result) { + case QUICPacketCreationResult::NO_PACKET: + break; + case QUICPacketCreationResult::NOT_READY: + QUICConDebug("Not ready to decrypt the packet"); + break; + case QUICPacketCreationResult::IGNORED: + QUICConDebug("Ignored"); + break; + case QUICPacketCreationResult::UNSUPPORTED: + QUICConDebug("Unsupported version"); + break; + case QUICPacketCreationResult::SUCCESS: + if (packet->type() == QUICPacketType::VERSION_NEGOTIATION) { + QUICConDebug("[RX] %s packet size=%u", QUICDebugNames::packet_type(packet->type()), packet->size()); + } else { + QUICConDebug("[RX] %s packet #%" PRIu64 " size=%u header_len=%u payload_len=%u", QUICDebugNames::packet_type(packet->type()), + packet->packet_number(), packet->size(), packet->header_size(), packet->payload_length()); + } + break; + default: + QUICConDebug("Failed to decrypt the packet"); + break; + } + + return packet; +} + +void +QUICNetVConnection::_schedule_packet_write_ready(bool delay) +{ + if (!this->_packet_write_ready) { + QUICConVVVDebug("Schedule %s event", QUICDebugNames::quic_event(QUIC_EVENT_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); + } + } +} + +void +QUICNetVConnection::_unschedule_packet_write_ready() +{ + if (this->_packet_write_ready) { + this->_packet_write_ready->cancel(); + this->_packet_write_ready = nullptr; + } +} + +void +QUICNetVConnection::_close_packet_write_ready(Event *data) +{ + ink_assert(this->_packet_write_ready == data); + this->_packet_write_ready = nullptr; +} + +void +QUICNetVConnection::_schedule_closing_timeout(ink_hrtime interval) +{ + if (!this->_closing_timeout) { + QUICConDebug("Schedule %s event in %" PRIu64 "ms", QUICDebugNames::quic_event(QUIC_EVENT_CLOSING_TIMEOUT), + interval / HRTIME_MSECOND); + this->_closing_timeout = this->thread->schedule_in_local(this, interval, QUIC_EVENT_CLOSING_TIMEOUT); + } +} + +void +QUICNetVConnection::_unschedule_closing_timeout() +{ + if (this->_closing_timeout) { + QUICConDebug("Unschedule %s event", QUICDebugNames::quic_event(QUIC_EVENT_CLOSING_TIMEOUT)); + this->_closing_timeout->cancel(); + this->_closing_timeout = nullptr; + } +} + +void +QUICNetVConnection::_schedule_ack_manager_periodic(ink_hrtime interval) +{ + this->_ack_manager_periodic = this->thread->schedule_every(this, interval, QUIC_EVENT_ACK_PERIODIC); +} + +void +QUICNetVConnection::_unschedule_ack_manager_periodic() +{ + if (this->_ack_manager_periodic) { + QUICConDebug("Unschedule %s event", QUICDebugNames::quic_event(QUIC_EVENT_ACK_PERIODIC)); + this->_ack_manager_periodic->cancel(); + this->_ack_manager_periodic = nullptr; + } +} + +void +QUICNetVConnection::_close_closing_timeout(Event *data) +{ + ink_assert(this->_closing_timeout == data); + this->_closing_timeout = nullptr; +} + +void +QUICNetVConnection::_schedule_closed_event() +{ + if (!this->_closed_event) { + QUICConDebug("Schedule %s event", QUICDebugNames::quic_event(QUIC_EVENT_SHUTDOWN)); + this->_closed_event = this->thread->schedule_imm(this, QUIC_EVENT_SHUTDOWN, nullptr); + } +} + +void +QUICNetVConnection::_unschedule_closed_event() +{ + if (!this->_closed_event) { + QUICConDebug("Unschedule %s event", QUICDebugNames::quic_event(QUIC_EVENT_SHUTDOWN)); + this->_closed_event->cancel(); + this->_closed_event = nullptr; + } +} + +void +QUICNetVConnection::_close_closed_event(Event *data) +{ + ink_assert(this->_closed_event == data); + this->_closed_event = nullptr; +} + +int +QUICNetVConnection::_complete_handshake_if_possible() +{ + if (this->handler != reinterpret_cast(&QUICNetVConnection::state_handshake)) { + return 0; + } + + if (!(this->_handshake_handler && this->_handshake_handler->is_completed())) { + return -1; + } + + if (this->netvc_context == NET_VCONNECTION_OUT && !this->_handshake_handler->has_remote_tp()) { + return -1; + } + + this->_init_flow_control_params(this->_handshake_handler->local_transport_parameters(), + this->_handshake_handler->remote_transport_parameters()); + + // PN space doesn't matter but seems like this is the way to pick the LossDetector for 0-RTT and Short packet + uint64_t ack_delay_exponent = + this->_handshake_handler->remote_transport_parameters()->getAsUInt(QUICTransportParameterId::ACK_DELAY_EXPONENT); + this->_loss_detector->update_ack_delay_exponent(ack_delay_exponent); + + this->_start_application(); + + return 0; +} + +void +QUICNetVConnection::_schedule_path_validation_timeout(ink_hrtime interval) +{ + if (!this->_path_validation_timeout) { + QUICConDebug("Schedule %s event in %" PRIu64 "ms", QUICDebugNames::quic_event(QUIC_EVENT_PATH_VALIDATION_TIMEOUT), + interval / HRTIME_MSECOND); + this->_path_validation_timeout = this->thread->schedule_in_local(this, interval, QUIC_EVENT_PATH_VALIDATION_TIMEOUT); + } +} + +void +QUICNetVConnection::_unschedule_path_validation_timeout() +{ + if (this->_path_validation_timeout) { + QUICConDebug("Unschedule %s event", QUICDebugNames::quic_event(QUIC_EVENT_PATH_VALIDATION_TIMEOUT)); + this->_path_validation_timeout->cancel(); + this->_path_validation_timeout = nullptr; + } +} + +void +QUICNetVConnection::_close_path_validation_timeout(Event *data) +{ + ink_assert(this->_path_validation_timeout == data); + this->_path_validation_timeout = nullptr; +} + +void +QUICNetVConnection::_start_application() +{ + if (!this->_application_started) { + this->_application_started = true; + + const uint8_t *app_name; + unsigned int app_name_len = 0; + this->_handshake_handler->negotiated_application_name(&app_name, &app_name_len); + if (app_name == nullptr) { + app_name = reinterpret_cast(IP_PROTO_TAG_HTTP_QUIC.data()); + app_name_len = IP_PROTO_TAG_HTTP_QUIC.size(); + } + + if (netvc_context == NET_VCONNECTION_IN) { + Continuation *endpoint = this->_next_protocol_set->findEndpoint(app_name, app_name_len); + if (endpoint == nullptr) { + this->_handle_error(std::make_unique(QUICTransErrorCode::VERSION_NEGOTIATION_ERROR)); + } else { + endpoint->handleEvent(NET_EVENT_ACCEPT, this); + } + } else { + this->action_.continuation->handleEvent(NET_EVENT_OPEN, this); + } + } +} + +void +QUICNetVConnection::_switch_to_handshake_state() +{ + QUICConDebug("Enter state_handshake"); + SET_HANDLER((NetVConnHandler)&QUICNetVConnection::state_handshake); +} + +void +QUICNetVConnection::_switch_to_established_state() +{ + if (this->_complete_handshake_if_possible() == 0) { + QUICConDebug("Enter state_connection_established"); + QUICConDebug("Negotiated cipher suite: %s", this->_handshake_handler->negotiated_cipher_suite()); + + SET_HANDLER((NetVConnHandler)&QUICNetVConnection::state_connection_established); + + if (this->direction() == NET_VCONNECTION_OUT) { + std::shared_ptr remote_tp = this->_handshake_handler->remote_transport_parameters(); + const uint8_t *pref_addr_buf; + uint16_t len; + pref_addr_buf = remote_tp->getAsBytes(QUICTransportParameterId::PREFERRED_ADDRESS, len); + this->_alt_con_manager = new QUICAltConnectionManager(this, *this->_ctable, this->_peer_quic_connection_id, + this->_quic_config->instance_id(), 1, {pref_addr_buf, len}); + this->_frame_generators.push_back(this->_alt_con_manager); + this->_frame_dispatcher->add_handler(this->_alt_con_manager); + } + } else { + // Illegal state change + ink_assert(!"Handshake has to be completed"); + } +} + +void +QUICNetVConnection::_switch_to_closing_state(QUICConnectionErrorUPtr error) +{ + if (this->_complete_handshake_if_possible() != 0) { + QUICConDebug("Switching state without handshake completion"); + } + if (error->msg) { + QUICConDebug("Reason: %.*s", static_cast(strlen(error->msg)), error->msg); + } + + // Once we are in closing or draining state, the ack_manager is not needed anymore. Because we don't send + // any frame other than close_frame. + this->_unschedule_ack_manager_periodic(); + + this->_connection_error = std::move(error); + this->_schedule_packet_write_ready(); + + this->remove_from_active_queue(); + this->set_inactivity_timeout(0); + + ink_hrtime rto = this->_rtt_measure.current_pto_period(); + + QUICConDebug("Enter state_connection_closing"); + SET_HANDLER((NetVConnHandler)&QUICNetVConnection::state_connection_closing); + + // This states SHOULD persist for three times the + // current Retransmission Timeout (RTO) interval as defined in + // [QUIC-RECOVERY]. + this->_schedule_closing_timeout(3 * rto); +} + +void +QUICNetVConnection::_switch_to_draining_state(QUICConnectionErrorUPtr error) +{ + if (this->_complete_handshake_if_possible() != 0) { + QUICConDebug("Switching state without handshake completion"); + } + if (error->msg) { + QUICConDebug("Reason: %.*s", static_cast(strlen(error->msg)), error->msg); + } + + // Once we are in closing or draining state, the ack_manager is not needed anymore. Because we don't send + // any frame other than close_frame. + this->_unschedule_ack_manager_periodic(); + + this->remove_from_active_queue(); + this->set_inactivity_timeout(0); + + ink_hrtime rto = this->_rtt_measure.current_pto_period(); + + QUICConDebug("Enter state_connection_draining"); + SET_HANDLER((NetVConnHandler)&QUICNetVConnection::state_connection_draining); + + // This states SHOULD persist for three times the + // current Retransmission Timeout (RTO) interval as defined in + // [QUIC-RECOVERY]. + + this->_schedule_closing_timeout(3 * rto); +} + +void +QUICNetVConnection::_switch_to_close_state() +{ + this->_unschedule_closing_timeout(); + this->_unschedule_path_validation_timeout(); + + if (this->_complete_handshake_if_possible() != 0) { + QUICConDebug("Switching state without handshake completion"); + } + QUICConDebug("Enter state_connection_closed"); + SET_HANDLER((NetVConnHandler)&QUICNetVConnection::state_connection_closed); + this->_schedule_closed_event(); +} + +void +QUICNetVConnection::_handle_idle_timeout() +{ + this->remove_from_active_queue(); + this->_switch_to_draining_state(std::make_unique(QUICTransErrorCode::NO_ERROR, "Idle Timeout")); + + // TODO: signal VC_EVENT_ACTIVE_TIMEOUT/VC_EVENT_INACTIVITY_TIMEOUT to application +} + +void +QUICNetVConnection::_validate_new_path() +{ + this->_path_validator->validate(); + // Not sure how long we should wait. The spec says just "enough time". + // Use the same time amount as the closing timeout. + ink_hrtime rto = this->_rtt_measure.current_pto_period(); + this->_schedule_path_validation_timeout(3 * rto); +} + +void +QUICNetVConnection::_update_cids() +{ + snprintf(this->_cids_data, sizeof(this->_cids_data), "%08" PRIx32 "-%08" PRIx32 "", this->_peer_quic_connection_id.h32(), + this->_quic_connection_id.h32()); + + this->_cids = {this->_cids_data, sizeof(this->_cids_data)}; +} + +void +QUICNetVConnection::_update_peer_cid(const QUICConnectionId &new_cid) +{ + if (is_debug_tag_set(QUIC_DEBUG_TAG.data())) { + char old_cid_str[QUICConnectionId::MAX_HEX_STR_LENGTH]; + char new_cid_str[QUICConnectionId::MAX_HEX_STR_LENGTH]; + this->_peer_quic_connection_id.hex(old_cid_str, QUICConnectionId::MAX_HEX_STR_LENGTH); + new_cid.hex(new_cid_str, QUICConnectionId::MAX_HEX_STR_LENGTH); + + QUICConDebug("dcid: %s -> %s", old_cid_str, new_cid_str); + } + + this->_peer_old_quic_connection_id = this->_peer_quic_connection_id; + this->_peer_quic_connection_id = new_cid; + this->_update_cids(); +} + +void +QUICNetVConnection::_update_local_cid(const QUICConnectionId &new_cid) +{ + if (is_debug_tag_set(QUIC_DEBUG_TAG.data())) { + char old_cid_str[QUICConnectionId::MAX_HEX_STR_LENGTH]; + char new_cid_str[QUICConnectionId::MAX_HEX_STR_LENGTH]; + this->_quic_connection_id.hex(old_cid_str, QUICConnectionId::MAX_HEX_STR_LENGTH); + new_cid.hex(new_cid_str, QUICConnectionId::MAX_HEX_STR_LENGTH); + + QUICConDebug("scid: %s -> %s", old_cid_str, new_cid_str); + } + + this->_quic_connection_id = new_cid; + this->_update_cids(); +} + +void +QUICNetVConnection::_rerandomize_original_cid() +{ + QUICConnectionId tmp = this->_original_quic_connection_id; + this->_original_quic_connection_id.randomize(); + + if (is_debug_tag_set(QUIC_DEBUG_TAG.data())) { + char old_cid_str[QUICConnectionId::MAX_HEX_STR_LENGTH]; + char new_cid_str[QUICConnectionId::MAX_HEX_STR_LENGTH]; + tmp.hex(old_cid_str, QUICConnectionId::MAX_HEX_STR_LENGTH); + this->_original_quic_connection_id.hex(new_cid_str, QUICConnectionId::MAX_HEX_STR_LENGTH); + + QUICConDebug("original cid: %s -> %s", old_cid_str, new_cid_str); + } +} + +QUICHandshakeProtocol * +QUICNetVConnection::_setup_handshake_protocol(shared_SSL_CTX ctx) +{ + // Initialize handshake protocol specific stuff + // For QUICv1 TLS is the only option + QUICTLS *tls = new QUICTLS(this->_pp_key_info, ctx.get(), this->direction(), this->options, + this->_quic_config->client_session_file(), this->_quic_config->client_keylog_file()); + SSL_set_ex_data(tls->ssl_handle(), QUIC::ssl_quic_qc_index, static_cast(this)); + + return tls; +} + +QUICConnectionErrorUPtr +QUICNetVConnection::_state_connection_established_migrate_connection(const QUICPacket &p) +{ + ink_assert(this->_handshake_handler->is_completed()); + + QUICConnectionErrorUPtr error = nullptr; + QUICConnectionId dcid = p.destination_cid(); + + if (this->netvc_context == NET_VCONNECTION_IN) { + if (!this->_alt_con_manager->is_ready_to_migrate()) { + // TODO: Should endpoint send connection error when remote endpoint doesn't send NEW_CONNECTION_ID frames before initiating + // connection migration ? + QUICConDebug("Ignore connection migration - remote endpoint initiated CM before sending NEW_CONNECTION_ID frames"); + return error; + } + QUICConDebug("Connection migration is initiated by remote"); + } + + if (this->connection_id() == dcid) { + // On client side (NET_VCONNECTION_OUT), nothing to do any more + if (this->netvc_context == NET_VCONNECTION_IN) { + Connection con; + con.setRemote(&(p.from().sa)); + this->con.move(con); + this->set_remote_addr(); + this->_udp_con = p.udp_con(); + this->_validate_new_path(); + } + } else { + if (this->_alt_con_manager->migrate_to(dcid, this->_reset_token)) { + // DCID of received packet is local cid + this->_update_local_cid(dcid); + + // On client side (NET_VCONNECTION_OUT), nothing to do any more + if (this->netvc_context == NET_VCONNECTION_IN) { + Connection con; + con.setRemote(&(p.from().sa)); + this->con.move(con); + this->set_remote_addr(); + this->_udp_con = p.udp_con(); + + this->_update_peer_cid(this->_alt_con_manager->migrate_to_alt_cid()); + this->_validate_new_path(); + } + } else { + char dcid_str[QUICConnectionId::MAX_HEX_STR_LENGTH]; + dcid.hex(dcid_str, QUICConnectionId::MAX_HEX_STR_LENGTH); + QUICConDebug("Connection migration failed cid=%s", dcid_str); + } + } + + return error; +} + +/** + * Connection Migration Excercise from client + */ +QUICConnectionErrorUPtr +QUICNetVConnection::_state_connection_established_initiate_connection_migration() +{ + ink_assert(this->_handshake_handler->is_completed()); + ink_assert(this->netvc_context == NET_VCONNECTION_OUT); + + QUICConnectionErrorUPtr error = nullptr; + ink_hrtime timestamp = Thread::get_hrtime(); + + std::shared_ptr remote_tp = this->_handshake_handler->remote_transport_parameters(); + + if (this->_connection_migration_initiated || remote_tp->contains(QUICTransportParameterId::DISABLE_MIGRATION) || + !this->_alt_con_manager->is_ready_to_migrate() || + this->_alt_con_manager->will_generate_frame(QUICEncryptionLevel::ONE_RTT, timestamp)) { + return error; + } + + QUICConDebug("Initiated connection migration"); + this->_connection_migration_initiated = true; + + this->_update_peer_cid(this->_alt_con_manager->migrate_to_alt_cid()); + + this->_validate_new_path(); + + return error; +} + +void +QUICNetVConnection::_handle_periodic_ack_event() +{ + ink_hrtime timestamp = Thread::get_hrtime(); + bool need_schedule = false; + for (int i = static_cast(this->_minimum_encryption_level); i <= static_cast(QUICEncryptionLevel::ONE_RTT); ++i) { + if (this->_ack_frame_manager.will_generate_frame(QUIC_ENCRYPTION_LEVELS[i], timestamp)) { + need_schedule = true; + break; + } + } + + if (need_schedule) { + // we have ack to send + // FIXME: should sent depend on socket event. + this->_schedule_packet_write_ready(); + } +} + +void +QUICNetVConnection::_handle_path_validation_timeout(Event *data) +{ + this->_close_path_validation_timeout(data); + if (this->_path_validator->is_validated()) { + QUICConDebug("Path validated"); + this->_alt_con_manager->drop_cid(this->_peer_old_quic_connection_id); + // FIXME This is a kind of workaround for connection migration. + // This PING make peer to send an ACK frame so that ATS can detect packet loss. + // It would be better if QUICLossDetector could detect the loss in another way. + this->ping(); + } else { + QUICConDebug("Path validation failed"); + this->_switch_to_close_state(); + } +} + +// QUICFrameGenerator +bool +QUICNetVConnection::will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) +{ + if (!this->_is_level_matched(level)) { + return false; + } + + return !this->_is_resumption_token_sent; +} + +QUICFrame * +QUICNetVConnection::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size, + ink_hrtime timestamp) +{ + QUICFrame *frame = nullptr; + + if (!this->_is_level_matched(level)) { + return frame; + } + + if (this->_is_resumption_token_sent) { + return frame; + } + + if (this->direction() == NET_VCONNECTION_IN) { + // TODO Make expiration period configurable + QUICResumptionToken token(this->get_remote_endpoint(), this->connection_id(), Thread::get_hrtime() + HRTIME_HOURS(24)); + frame = QUICFrameFactory::create_new_token_frame(buf, token, this->_issue_frame_id(), this); + if (frame) { + if (frame->size() < maximum_frame_size) { + this->_is_resumption_token_sent = true; + } else { + // Cancel generating frame + frame = nullptr; + } + } + } + + return frame; +} + +void +QUICNetVConnection::_on_frame_lost(QUICFrameInformationUPtr &info) +{ + this->_is_resumption_token_sent = false; +} diff --git a/iocore/net/QUICNextProtocolAccept.cc b/iocore/net/QUICNextProtocolAccept.cc new file mode 100644 index 00000000000..1f721fa5bfc --- /dev/null +++ b/iocore/net/QUICNextProtocolAccept.cc @@ -0,0 +1,101 @@ +/** @file + + QUICNextProtocolAccept + + @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_QUICNextProtocolAccept.h" + +static QUICNetVConnection * +quic_netvc_cast(int event, void *edata) +{ + union { + VIO *vio; + NetVConnection *vc; + } ptr; + + switch (event) { + case NET_EVENT_ACCEPT: + ptr.vc = static_cast(edata); + return dynamic_cast(ptr.vc); + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_READ_COMPLETE: + case VC_EVENT_ERROR: + ptr.vio = static_cast(edata); + return dynamic_cast(ptr.vio->vc_server); + default: + return nullptr; + } +} + +int +QUICNextProtocolAccept::mainEvent(int event, void *edata) +{ + QUICNetVConnection *netvc = quic_netvc_cast(event, edata); + + Debug("v_quic", "[%s] event %d netvc %p", netvc->cids().data(), event, netvc); + switch (event) { + case NET_EVENT_ACCEPT: + ink_release_assert(netvc != nullptr); + netvc->registerNextProtocolSet(&this->protoset); + return EVENT_CONT; + default: + netvc->do_io_close(); + return EVENT_DONE; + } +} + +bool +QUICNextProtocolAccept::accept(NetVConnection *, MIOBuffer *, IOBufferReader *) +{ + ink_release_assert(0); + return false; +} + +bool +QUICNextProtocolAccept::registerEndpoint(const char *protocol, Continuation *handler) +{ + return this->protoset.registerEndpoint(protocol, handler); +} + +bool +QUICNextProtocolAccept::unregisterEndpoint(const char *protocol, Continuation *handler) +{ + return this->protoset.unregisterEndpoint(protocol, handler); +} + +QUICNextProtocolAccept::QUICNextProtocolAccept() : SessionAccept(nullptr) +{ + SET_HANDLER(&QUICNextProtocolAccept::mainEvent); +} + +SSLNextProtocolSet * +QUICNextProtocolAccept::getProtoSet() +{ + return &this->protoset; +} + +SSLNextProtocolSet * +QUICNextProtocolAccept::cloneProtoSet() +{ + return this->protoset.clone(); +} + +QUICNextProtocolAccept::~QUICNextProtocolAccept() {} diff --git a/iocore/net/QUICPacketHandler.cc b/iocore/net/QUICPacketHandler.cc new file mode 100644 index 00000000000..fc906b2d366 --- /dev/null +++ b/iocore/net/QUICPacketHandler.cc @@ -0,0 +1,483 @@ +/** @file + + @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 "tscore/ink_config.h" +#include "P_Net.h" + +#include "P_QUICClosedConCollector.h" + +#include "QUICGlobals.h" +#include "QUICConfig.h" +#include "QUICPacket.h" +#include "QUICDebugNames.h" +#include "QUICEvents.h" + +static constexpr int LONG_HDR_OFFSET_CONNECTION_ID = 6; +static constexpr char debug_tag[] = "quic_sec"; + +#define QUICDebug(fmt, ...) Debug(debug_tag, fmt, ##__VA_ARGS__) +#define QUICDebugQC(qc, fmt, ...) Debug(debug_tag, "[%s] " fmt, qc->cids().data(), ##__VA_ARGS__) + +// ["local dcid" - "local scid"] +#define QUICDebugDS(dcid, scid, fmt, ...) \ + Debug(debug_tag, "[%08" PRIx32 "-%08" PRIx32 "] " fmt, dcid.h32(), scid.h32(), ##__VA_ARGS__) + +// +// QUICPacketHandler +// +QUICPacketHandler::QUICPacketHandler() +{ + this->_closed_con_collector = new QUICClosedConCollector; + this->_closed_con_collector->mutex = new_ProxyMutex(); +} + +QUICPacketHandler::~QUICPacketHandler() +{ + if (this->_collector_event != nullptr) { + this->_collector_event->cancel(); + this->_collector_event = nullptr; + } + + if (this->_closed_con_collector != nullptr) { + delete this->_closed_con_collector; + this->_closed_con_collector = 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(const QUICPacket &packet, UDPConnection *udp_con, IpEndpoint &addr, uint32_t pmtu, + const QUICPacketHeaderProtector *ph_protector, int dcil) +{ + size_t udp_len; + Ptr udp_payload(new_IOBufferBlock()); + udp_payload->alloc(iobuffer_size_to_index(pmtu)); + packet.store(reinterpret_cast(udp_payload->end()), &udp_len); + udp_payload->fill(udp_len); + + if (ph_protector) { + ph_protector->protect(reinterpret_cast(udp_payload->start()), udp_len, dcil); + } + + this->_send_packet(udp_con, addr, udp_payload); +} + +void +QUICPacketHandler::_send_packet(UDPConnection *udp_con, IpEndpoint &addr, Ptr udp_payload) +{ + UDPPacket *udp_packet = new_UDPPacket(addr, 0, udp_payload); + + if (is_debug_tag_set(debug_tag)) { + ip_port_text_buffer ipb; + QUICConnectionId dcid = QUICConnectionId::ZERO(); + QUICConnectionId scid = QUICConnectionId::ZERO(); + + const uint8_t *buf = reinterpret_cast(udp_payload->buf()); + uint64_t buf_len = udp_payload->size(); + + if (!QUICInvariants::dcid(dcid, buf, buf_len)) { + ink_assert(false); + } + + if (QUICInvariants::is_long_header(buf)) { + if (!QUICInvariants::scid(scid, buf, buf_len)) { + ink_assert(false); + } + } + + QUICDebugDS(dcid, scid, "send %s packet to %s from port %u size=%" PRId64, (QUICInvariants::is_long_header(buf) ? "LH" : "SH"), + ats_ip_nptop(&addr, ipb, sizeof(ipb)), udp_con->getPortNum(), buf_len); + } + + udp_con->send(this->_get_continuation(), udp_packet); + get_UDPNetHandler(static_cast(udp_con)->ethread)->signalActivity(); +} + +// +// QUICPacketHandlerIn +// +QUICPacketHandlerIn::QUICPacketHandlerIn(const NetProcessor::AcceptOptions &opt, QUICConnectionTable &ctable) + : NetAccept(opt), QUICPacketHandler(), _ctable(ctable) +{ + this->mutex = new_ProxyMutex(); + // create Connection Table + QUICConfig::scoped_config params; +} + +QUICPacketHandlerIn::~QUICPacketHandlerIn() {} + +NetProcessor * +QUICPacketHandlerIn::getNetProcessor() const +{ + return &quic_NetProcessor; +} + +NetAccept * +QUICPacketHandlerIn::clone() const +{ + NetAccept *na; + na = new QUICPacketHandlerIn(opt, this->_ctable); + *na = *this; + return na; +} + +int +QUICPacketHandlerIn::acceptEvent(int event, void *data) +{ + // NetVConnection *netvc; + ink_release_assert(event == NET_EVENT_DATAGRAM_OPEN || event == NET_EVENT_DATAGRAM_READ_READY || + event == NET_EVENT_DATAGRAM_ERROR); + ink_release_assert((event == NET_EVENT_DATAGRAM_OPEN) ? (data != nullptr) : (1)); + ink_release_assert((event == NET_EVENT_DATAGRAM_READ_READY) ? (data != nullptr) : (1)); + + if (event == NET_EVENT_DATAGRAM_OPEN) { + // Nothing to do. + return EVENT_CONT; + } else if (event == NET_EVENT_DATAGRAM_READ_READY) { + if (this->_collector_event == nullptr) { + this->_collector_event = this_ethread()->schedule_every(this->_closed_con_collector, HRTIME_MSECONDS(100)); + } + + Queue *queue = (Queue *)data; + UDPPacket *packet_r; + while ((packet_r = queue->dequeue())) { + this->_recv_packet(event, packet_r); + } + return EVENT_CONT; + } + + ///////////////// + // EVENT_ERROR // + ///////////////// + if (((long)data) == -ECONNABORTED) { + } + + ink_abort("QUIC accept received fatal error: errno = %d", -((int)(intptr_t)data)); + return EVENT_CONT; + return 0; +} + +void +QUICPacketHandlerIn::init_accept(EThread *t = nullptr) +{ + SET_HANDLER(&QUICPacketHandlerIn::acceptEvent); +} + +Continuation * +QUICPacketHandlerIn::_get_continuation() +{ + return static_cast(this); +} + +void +QUICPacketHandlerIn::_recv_packet(int event, UDPPacket *udp_packet) +{ + // Assumption: udp_packet has only one IOBufferBlock + IOBufferBlock *block = udp_packet->getIOBlockChain(); + const uint8_t *buf = reinterpret_cast(block->buf()); + uint64_t buf_len = block->size(); + + if (buf_len == 0) { + QUICDebug("Ignore packet - payload is too small"); + udp_packet->free(); + return; + } + + QUICConnectionId dcid = QUICConnectionId::ZERO(); + QUICConnectionId scid = QUICConnectionId::ZERO(); + + if (!QUICInvariants::dcid(dcid, buf, buf_len)) { + QUICDebug("Ignore packet - payload is too small"); + udp_packet->free(); + return; + } + + if (QUICInvariants::is_long_header(buf)) { + if (!QUICInvariants::scid(scid, buf, buf_len)) { + QUICDebug("Ignore packet - payload is too small"); + udp_packet->free(); + return; + } + + if (is_debug_tag_set(debug_tag)) { + ip_port_text_buffer ipb_from; + ip_port_text_buffer ipb_to; + QUICDebugDS(scid, dcid, "recv LH packet from %s to %s size=%" PRId64, + ats_ip_nptop(&udp_packet->from.sa, ipb_from, sizeof(ipb_from)), + ats_ip_nptop(&udp_packet->to.sa, ipb_to, sizeof(ipb_to)), udp_packet->getPktLength()); + } + + QUICVersion v; + if (unlikely(!QUICInvariants::version(v, buf, buf_len))) { + QUICDebug("Ignore packet - payload is too small"); + udp_packet->free(); + return; + } + + if (!QUICInvariants::is_version_negotiation(v) && !QUICTypeUtil::is_supported_version(v)) { + QUICDebugDS(scid, dcid, "Unsupported version: 0x%x", v); + + QUICPacketUPtr vn = QUICPacketFactory::create_version_negotiation_packet(scid, dcid); + this->_send_packet(*vn, udp_packet->getConnection(), udp_packet->from, 1200, nullptr, 0); + udp_packet->free(); + return; + } + + if (dcid == QUICConnectionId::ZERO()) { + // TODO: lookup DCID by 5-tuple when ATS omits SCID + return; + } + + QUICPacketType type = QUICPacketType::UNINITIALIZED; + QUICPacketLongHeader::type(type, buf, buf_len); + if (type == QUICPacketType::INITIAL) { + // [draft-18] 7.2. + // When an Initial packet is sent by a client which has not previously received a Retry packet from the server, it populates + // the Destination Connection ID field with an unpredictable value. This MUST be at least 8 bytes in length. + if (dcid != QUICConnectionId::ZERO() && dcid.length() < QUICConnectionId::MIN_LENGTH_FOR_INITIAL) { + QUICDebug("Ignore packet - DCIL is too small for Initial packet"); + udp_packet->free(); + return; + } + } + } else { + // TODO: lookup DCID by 5-tuple when ATS omits SCID + if (is_debug_tag_set(debug_tag)) { + ip_port_text_buffer ipb_from; + ip_port_text_buffer ipb_to; + QUICDebugDS(scid, dcid, "recv SH packet from %s to %s size=%" PRId64, + ats_ip_nptop(&udp_packet->from.sa, ipb_from, sizeof(ipb_from)), + ats_ip_nptop(&udp_packet->to.sa, ipb_to, sizeof(ipb_to)), udp_packet->getPktLength()); + } + } + + QUICConnection *qc = this->_ctable.lookup(dcid); + QUICNetVConnection *vc = static_cast(qc); + + // Server Stateless Retry + QUICConfig::scoped_config params; + QUICConnectionId cid_in_retry_token = QUICConnectionId::ZERO(); + if (!vc && params->stateless_retry() && QUICInvariants::is_long_header(buf)) { + int ret = this->_stateless_retry(buf, buf_len, udp_packet->getConnection(), udp_packet->from, dcid, scid, &cid_in_retry_token); + if (ret < 0) { + udp_packet->free(); + return; + } + } + + // [draft-12] 6.1.2. Server Packet Handling + // Servers MUST drop incoming packets under all other circumstances. They SHOULD send a Stateless Reset (Section 6.10.4) if a + // connection ID is present in the header. + if ((!vc && !QUICInvariants::is_long_header(buf)) || (vc && vc->in_closed_queue)) { + if (is_debug_tag_set(debug_tag)) { + char dcid_str[QUICConnectionId::MAX_HEX_STR_LENGTH]; + dcid.hex(dcid_str, QUICConnectionId::MAX_HEX_STR_LENGTH); + + if (!vc && !QUICInvariants::is_long_header(buf)) { + QUICDebugDS(scid, dcid, "sent Stateless Reset : connection not found, dcid=%s", dcid_str); + } else if (vc && vc->in_closed_queue) { + QUICDebugDS(scid, dcid, "sent Stateless Reset : connection is already closed, dcid=%s", dcid_str); + } + } + + QUICStatelessResetToken token(dcid, params->instance_id()); + auto packet = QUICPacketFactory::create_stateless_reset_packet(dcid, token); + this->_send_packet(*packet, udp_packet->getConnection(), udp_packet->from, 1200, nullptr, 0); + udp_packet->free(); + return; + } + + EThread *eth = nullptr; + if (!vc) { + // Create a new NetVConnection + Connection con; + con.setRemote(&udp_packet->from.sa); + + eth = eventProcessor.assign_thread(ET_NET); + QUICConnectionId original_cid = dcid; + QUICConnectionId peer_cid = scid; + + if (is_debug_tag_set("quic_sec")) { + char client_dcid_hex_str[QUICConnectionId::MAX_HEX_STR_LENGTH]; + original_cid.hex(client_dcid_hex_str, QUICConnectionId::MAX_HEX_STR_LENGTH); + QUICDebugDS(peer_cid, original_cid, "client initial dcid=%s", client_dcid_hex_str); + } + + vc = static_cast(getNetProcessor()->allocate_vc(nullptr)); + vc->init(peer_cid, original_cid, cid_in_retry_token, udp_packet->getConnection(), this, &this->_ctable); + vc->id = net_next_connection_number(); + vc->con.move(con); + vc->submit_time = Thread::get_hrtime(); + vc->thread = eth; + vc->mutex = new_ProxyMutex(); + 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 = udp_packet->from.sa.sa_family; + + qc = vc; + } else { + eth = vc->thread; + } + + QUICPollEvent *qe = quicPollEventAllocator.alloc(); + qe->init(qc, static_cast(udp_packet)); + // Push the packet into QUICPollCont + get_QUICPollCont(eth)->inQueue.push(qe); + get_NetHandler(eth)->signalActivity(); + + return; +} + +// TODO: Should be called via eventProcessor? +void +QUICPacketHandler::send_packet(const QUICPacket &packet, QUICNetVConnection *vc, const QUICPacketHeaderProtector &ph_protector) +{ + this->_send_packet(packet, vc->get_udp_con(), vc->con.addr, vc->pmtu(), &ph_protector, vc->peer_connection_id().length()); +} + +void +QUICPacketHandler::send_packet(QUICNetVConnection *vc, Ptr udp_payload) +{ + this->_send_packet(vc->get_udp_con(), vc->con.addr, udp_payload); +} + +int +QUICPacketHandlerIn::_stateless_retry(const uint8_t *buf, uint64_t buf_len, UDPConnection *connection, IpEndpoint from, + QUICConnectionId dcid, QUICConnectionId scid, QUICConnectionId *original_cid) +{ + QUICPacketType type = QUICPacketType::UNINITIALIZED; + QUICPacketLongHeader::type(type, buf, buf_len); + + if (type != QUICPacketType::INITIAL) { + return 1; + } + + // TODO: refine packet parsers in here, QUICPacketLongHeader, and QUICPacketReceiveQueue + size_t token_length = 0; + uint8_t token_length_field_len = 0; + if (!QUICPacketLongHeader::token_length(token_length, &token_length_field_len, buf, buf_len)) { + return -1; + } + + if (token_length == 0) { + QUICRetryToken token(from, dcid); + QUICConnectionId local_cid; + local_cid.randomize(); + QUICPacketUPtr retry_packet = QUICPacketFactory::create_retry_packet(scid, local_cid, dcid, token); + + this->_send_packet(*retry_packet, connection, from, 1200, nullptr, 0); + + return -2; + } else { + uint8_t dcil, scil; + QUICPacketLongHeader::dcil(dcil, buf, buf_len); + QUICPacketLongHeader::scil(scil, buf, buf_len); + const uint8_t *token = buf + LONG_HDR_OFFSET_CONNECTION_ID + dcil + scil + token_length_field_len; + + if (QUICAddressValidationToken::type(token) == QUICAddressValidationToken::Type::RETRY) { + QUICRetryToken token1(token, token_length); + if (token1.is_valid(from)) { + *original_cid = token1.original_dcid(); + return 0; + } else { + return -3; + } + } else { + // TODO Handle ResumptionToken + return -4; + } + } + + return 0; +} + +// +// QUICPacketHandlerOut +// +QUICPacketHandlerOut::QUICPacketHandlerOut() : Continuation(new_ProxyMutex()), QUICPacketHandler() +{ + SET_HANDLER(&QUICPacketHandlerOut::event_handler); +} + +void +QUICPacketHandlerOut::init(QUICNetVConnection *vc) +{ + this->_vc = vc; +} + +int +QUICPacketHandlerOut::event_handler(int event, Event *data) +{ + switch (event) { + case NET_EVENT_DATAGRAM_OPEN: { + // Nothing to do. + return EVENT_CONT; + } + case NET_EVENT_DATAGRAM_READ_READY: { + Queue *queue = (Queue *)data; + UDPPacket *packet_r; + while ((packet_r = queue->dequeue())) { + this->_recv_packet(event, packet_r); + } + return EVENT_CONT; + } + default: + Debug("quic_ph", "Unknown Event (%d)", event); + + break; + } + + return EVENT_DONE; +} + +Continuation * +QUICPacketHandlerOut::_get_continuation() +{ + return this; +} + +void +QUICPacketHandlerOut::_recv_packet(int event, UDPPacket *udp_packet) +{ + if (is_debug_tag_set(debug_tag)) { + IOBufferBlock *block = udp_packet->getIOBlockChain(); + const uint8_t *buf = reinterpret_cast(block->buf()); + + ip_port_text_buffer ipb_from; + ip_port_text_buffer ipb_to; + QUICDebugQC(this->_vc, "recv %s packet from %s to %s size=%" PRId64, (QUICInvariants::is_long_header(buf) ? "LH" : "SH"), + ats_ip_nptop(&udp_packet->from.sa, ipb_from, sizeof(ipb_from)), + ats_ip_nptop(&udp_packet->to.sa, ipb_to, sizeof(ipb_to)), udp_packet->getPktLength()); + } + + this->_vc->handle_received_packet(udp_packet); + eventProcessor.schedule_imm(this->_vc, ET_CALL, QUIC_EVENT_PACKET_READ_READY, nullptr); +} diff --git a/iocore/net/UnixNet.cc b/iocore/net/UnixNet.cc index 63721b96154..2efc69e4b29 100644 --- a/iocore/net/UnixNet.cc +++ b/iocore/net/UnixNet.cc @@ -234,7 +234,8 @@ initialize_thread_for_net(EThread *thread) thread->schedule_every(inactivityCop, HRTIME_SECONDS(cop_freq)); thread->set_tail_handler(nh); - thread->ep = (EventIO *)ats_malloc(sizeof(EventIO)); + thread->ep = (EventIO *)ats_malloc(sizeof(EventIO)); + new (thread->ep) EventIO(); thread->ep->type = EVENTIO_ASYNC_SIGNAL; #if HAVE_EVENTFD thread->ep->start(pd, thread->evfd, nullptr, EVENTIO_READ); diff --git a/iocore/net/UnixUDPNet.cc b/iocore/net/UnixUDPNet.cc index cd223c2579f..f4c22e1b516 100644 --- a/iocore/net/UnixUDPNet.cc +++ b/iocore/net/UnixUDPNet.cc @@ -840,6 +840,8 @@ UDPNetProcessor::UDPBind(Continuation *cont, sockaddr const *addr, int send_bufs if (fd != NO_FD) { socketManager.close(fd); } + Debug("udpnet", "Error: %s (%d)", strerror(errno), errno); + cont->handleEvent(NET_EVENT_DATAGRAM_ERROR, nullptr); return ACTION_IO_ERROR; } @@ -990,6 +992,10 @@ UDPQueue::SendUDPPacket(UDPPacketInternal *p, int32_t /* pktLen ATS_UNUSED */) n = ::sendmsg(p->conn->getFd(), &msg, 0); if ((n >= 0) || ((n < 0) && (errno != EAGAIN))) { // send succeeded or some random error happened. + if (n < 0) { + Debug("udp-send", "Error: %s (%d)", strerror(errno), errno); + } + break; } if (errno == EAGAIN) { @@ -1039,7 +1045,7 @@ UDPNetHandler::startNetEvent(int event, Event *e) (void)event; SET_HANDLER((UDPNetContHandler)&UDPNetHandler::mainNetEvent); trigger_event = e; - e->schedule_every(-HRTIME_MSECONDS(UDP_PERIOD)); + e->schedule_every(-HRTIME_MSECONDS(UDP_NH_PERIOD)); return EVENT_CONT; } diff --git a/iocore/net/libinknet_stub.cc b/iocore/net/libinknet_stub.cc index 1dbc971690f..000eb3811c9 100644 --- a/iocore/net/libinknet_stub.cc +++ b/iocore/net/libinknet_stub.cc @@ -160,3 +160,10 @@ ProcessManager::signalManager(int, char const *, int) ink_assert(false); return; } + +void +ProcessManager::signalManager(int, char const *) +{ + ink_assert(false); + return; +} diff --git a/iocore/net/quic/Makefile.am b/iocore/net/quic/Makefile.am new file mode 100644 index 00000000000..e13d1de2a93 --- /dev/null +++ b/iocore/net/quic/Makefile.am @@ -0,0 +1,305 @@ +# Makefile.am for the traffic/iocore/net hierarchy +# +# 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. + +AM_CPPFLAGS += \ + $(iocore_include_dirs) \ + -I$(abs_top_srcdir)/lib \ + -I$(abs_top_srcdir)/lib/records \ + -I$(abs_top_srcdir)/proxy \ + -I$(abs_top_srcdir)/proxy/hdrs \ + -I$(abs_top_srcdir)/proxy/shared \ + -I$(abs_top_srcdir)/proxy/logging \ + -I$(abs_top_srcdir)/proxy/http \ + -I$(abs_top_srcdir)/proxy/http3 \ + -I$(abs_top_srcdir)/mgmt \ + -I$(abs_top_srcdir)/mgmt/utils \ + $(TS_INCLUDES) \ + @OPENSSL_INCLUDES@ + +noinst_LIBRARIES = libquic.a + +if OPENSSL_IS_BORINGSSL +QUICPHProtector_impl = QUICPacketHeaderProtector_boringssl.cc +QUICPPProtector_impl = QUICPacketPayloadProtector_boringssl.cc +QUICTLS_impl = QUICTLS_boringssl.cc +QUICKeyGenerator_impl = QUICKeyGenerator_boringssl.cc +else +QUICPHProtector_impl = QUICPacketHeaderProtector_openssl.cc +QUICPPProtector_impl = QUICPacketPayloadProtector_openssl.cc +QUICTLS_impl = QUICTLS_openssl.cc +QUICKeyGenerator_impl = QUICKeyGenerator_openssl.cc +endif + +libquic_a_SOURCES = \ + QUICGlobals.cc \ + QUICTypes.cc \ + QUICIntUtil.cc \ + QUICPacket.cc \ + QUICPacketFactory.cc \ + QUICFrame.cc \ + QUICFrameDispatcher.cc \ + QUICVersionNegotiator.cc \ + QUICLossDetector.cc \ + QUICStreamManager.cc \ + QUICCongestionController.cc \ + QUICFlowController.cc \ + QUICStreamState.cc \ + QUICStream.cc \ + QUICHandshake.cc \ + QUICPacketHeaderProtector.cc \ + $(QUICPHProtector_impl) \ + QUICPacketPayloadProtector.cc \ + $(QUICPPProtector_impl) \ + QUICPacketProtectionKeyInfo.cc \ + QUICTLS.cc \ + $(QUICTLS_impl) \ + QUICKeyGenerator.cc \ + $(QUICKeyGenerator_impl) \ + QUICKeyGenerator.cc \ + QUICHKDF.cc \ + QUICTransportParameters.cc \ + QUICConnectionTable.cc \ + QUICAltConnectionManager.cc \ + QUICAckFrameCreator.cc \ + QUICConfig.cc \ + QUICDebugNames.cc \ + QUICApplication.cc \ + QUICApplicationMap.cc \ + QUICIncomingFrameBuffer.cc \ + QUICPacketReceiveQueue.cc \ + QUICPathValidator.cc \ + QUICPinger.cc \ + QUICFrameGenerator.cc \ + QUICFrameRetransmitter.cc \ + QUICAddrVerifyState.cc \ + QUICBidirectionalStream.cc \ + QUICCryptoStream.cc \ + QUICUnidirectionalStream.cc \ + QUICStreamFactory.cc + +# +# Check Programs +# +check_PROGRAMS = \ + test_QUICAckFrameCreator \ + test_QUICAltConnectionManager \ + test_QUICFlowController \ + test_QUICFrame \ + test_QUICFrameDispatcher \ + test_QUICLossDetector \ + test_QUICHandshakeProtocol \ + test_QUICIncomingFrameBuffer \ + test_QUICInvariants \ + test_QUICKeyGenerator \ + test_QUICPacket \ + test_QUICPacketHeaderProtector \ + test_QUICPacketFactory \ + test_QUICStream \ + test_QUICStreamManager \ + test_QUICStreamState \ + test_QUICTransportParameters \ + test_QUICType \ + test_QUICTypeUtil \ + test_QUICVersionNegotiator \ + test_QUICFrameRetransmitter \ + test_QUICAddrVerifyState + +TESTS = $(check_PROGRAMS) + +test_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -I$(abs_top_srcdir)/tests/include + +test_LDADD = \ + libquic.a \ + $(top_builddir)/iocore/eventsystem/libinkevent.a \ + $(top_builddir)/lib/records/librecords_p.a \ + $(top_builddir)/mgmt/libmgmt_p.la \ + $(top_builddir)/proxy/shared/libUglyLogStubs.a \ + $(top_builddir)/src/tscore/libtscore.la \ + $(top_builddir)/src/tscpp/util/libtscpputil.la \ + $(top_builddir)/proxy/ParentSelectionStrategy.o \ + @HWLOC_LIBS@ @OPENSSL_LIBS@ @LIBPCRE@ @YAMLCPP_LIBS@ + +test_event_main_SOURCES = \ + ./test/event_processor_main.cc + +test_main_SOURCES = \ + ./test/main.cc + +test_QUICAckFrameCreator_CPPFLAGS = $(test_CPPFLAGS) +test_QUICAckFrameCreator_LDFLAGS = @AM_LDFLAGS@ +test_QUICAckFrameCreator_LDADD = $(test_LDADD) +test_QUICAckFrameCreator_SOURCES = \ + $(test_main_SOURCES) \ + ./test/test_QUICAckFrameCreator.cc + +test_QUICAltConnectionManager_CPPFLAGS = $(test_CPPFLAGS) +test_QUICAltConnectionManager_LDFLAGS = @AM_LDFLAGS@ +test_QUICAltConnectionManager_LDADD = $(test_LDADD) +test_QUICAltConnectionManager_SOURCES = \ + $(test_main_SOURCES) \ + ./test/test_QUICAltConnectionManager.cc + +test_QUICFlowController_CPPFLAGS = $(test_CPPFLAGS) +test_QUICFlowController_LDFLAGS = @AM_LDFLAGS@ +test_QUICFlowController_LDADD = $(test_LDADD) +test_QUICFlowController_SOURCES = \ + $(test_main_SOURCES) \ + ./test/test_QUICFlowController.cc + +test_QUICFrame_CPPFLAGS = $(test_CPPFLAGS) +test_QUICFrame_LDFLAGS = @AM_LDFLAGS@ +test_QUICFrame_LDADD = $(test_LDADD) +test_QUICFrame_SOURCES = \ + $(test_main_SOURCES) \ + ./test/test_QUICFrame.cc + +test_QUICFrameDispatcher_CPPFLAGS = $(test_CPPFLAGS) +test_QUICFrameDispatcher_LDFLAGS = @AM_LDFLAGS@ +test_QUICFrameDispatcher_LDADD = $(test_LDADD) +test_QUICFrameDispatcher_SOURCES = \ + $(test_event_main_SOURCES) \ + ./test/test_QUICFrameDispatcher.cc + +test_QUICLossDetector_CPPFLAGS = $(test_CPPFLAGS) +test_QUICLossDetector_LDFLAGS = @AM_LDFLAGS@ +test_QUICLossDetector_LDADD = $(test_LDADD) +test_QUICLossDetector_SOURCES = \ + $(test_event_main_SOURCES) \ + ./test/test_QUICLossDetector.cc + +test_QUICHandshakeProtocol_CPPFLAGS = $(test_CPPFLAGS) +test_QUICHandshakeProtocol_LDFLAGS = @AM_LDFLAGS@ +test_QUICHandshakeProtocol_LDADD = $(test_LDADD) +test_QUICHandshakeProtocol_SOURCES = \ + $(test_main_SOURCES) \ + ./test/test_QUICHandshakeProtocol.cc + +test_QUICIncomingFrameBuffer_CPPFLAGS = $(test_CPPFLAGS) +test_QUICIncomingFrameBuffer_LDFLAGS = @AM_LDFLAGS@ +test_QUICIncomingFrameBuffer_LDADD = $(test_LDADD) +test_QUICIncomingFrameBuffer_SOURCES = \ + $(test_event_main_SOURCES) \ + ./test/test_QUICIncomingFrameBuffer.cc + +test_QUICInvariants_CPPFLAGS = $(test_CPPFLAGS) +test_QUICInvariants_LDFLAGS = @AM_LDFLAGS@ +test_QUICInvariants_LDADD = $(test_LDADD) +test_QUICInvariants_SOURCES = \ + $(test_main_SOURCES) \ + ./test/test_QUICInvariants.cc + +test_QUICKeyGenerator_CPPFLAGS = $(test_CPPFLAGS) +test_QUICKeyGenerator_LDFLAGS = @AM_LDFLAGS@ +test_QUICKeyGenerator_LDADD = $(test_LDADD) +test_QUICKeyGenerator_SOURCES = \ + $(test_main_SOURCES) \ + ./test/test_QUICKeyGenerator.cc + +test_QUICPacket_CPPFLAGS = $(test_CPPFLAGS) +test_QUICPacket_LDFLAGS = @AM_LDFLAGS@ +test_QUICPacket_LDADD = $(test_LDADD) +test_QUICPacket_SOURCES = \ + $(test_main_SOURCES) \ + ./test/test_QUICPacket.cc + +test_QUICPacketFactory_CPPFLAGS = $(test_CPPFLAGS) +test_QUICPacketFactory_LDFLAGS = @AM_LDFLAGS@ +test_QUICPacketFactory_LDADD = $(test_LDADD) +test_QUICPacketFactory_SOURCES = \ + $(test_main_SOURCES) \ + ./test/test_QUICPacketFactory.cc + +test_QUICPacketHeaderProtector_CPPFLAGS = $(test_CPPFLAGS) +test_QUICPacketHeaderProtector_LDFLAGS = @AM_LDFLAGS@ +test_QUICPacketHeaderProtector_LDADD = $(test_LDADD) +test_QUICPacketHeaderProtector_SOURCES = \ + $(test_main_SOURCES) \ + ./test/test_QUICPacketHeaderProtector.cc + +test_QUICStream_CPPFLAGS = $(test_CPPFLAGS) +test_QUICStream_LDFLAGS = @AM_LDFLAGS@ +test_QUICStream_LDADD = $(test_LDADD) +test_QUICStream_SOURCES = \ + $(test_event_main_SOURCES) \ + ./test/test_QUICStream.cc + +test_QUICStreamManager_CPPFLAGS = $(test_CPPFLAGS) +test_QUICStreamManager_LDFLAGS = @AM_LDFLAGS@ +test_QUICStreamManager_LDADD = $(test_LDADD) +test_QUICStreamManager_SOURCES = \ + $(test_event_main_SOURCES) \ + ./test/test_QUICStreamManager.cc + +test_QUICStreamState_CPPFLAGS = $(test_CPPFLAGS) +test_QUICStreamState_LDFLAGS = @AM_LDFLAGS@ +test_QUICStreamState_LDADD = $(test_LDADD) +test_QUICStreamState_SOURCES = \ + $(test_main_SOURCES) \ + ./test/test_QUICStreamState.cc + +test_QUICTransportParameters_CPPFLAGS = $(test_CPPFLAGS) +test_QUICTransportParameters_LDFLAGS = @AM_LDFLAGS@ +test_QUICTransportParameters_LDADD = $(test_LDADD) +test_QUICTransportParameters_SOURCES = \ + $(test_main_SOURCES) \ + ./test/test_QUICTransportParameters.cc + +test_QUICType_CPPFLAGS = $(test_CPPFLAGS) +test_QUICType_LDFLAGS = @AM_LDFLAGS@ +test_QUICType_LDADD = $(test_LDADD) +test_QUICType_SOURCES = \ + $(test_main_SOURCES) \ + ./test/test_QUICType.cc + +test_QUICTypeUtil_CPPFLAGS = $(test_CPPFLAGS) +test_QUICTypeUtil_LDFLAGS = @AM_LDFLAGS@ +test_QUICTypeUtil_LDADD = $(test_LDADD) +test_QUICTypeUtil_SOURCES = \ + $(test_main_SOURCES) \ + ./test/test_QUICTypeUtil.cc + +test_QUICVersionNegotiator_CPPFLAGS = $(test_CPPFLAGS) +test_QUICVersionNegotiator_LDFLAGS = @AM_LDFLAGS@ +test_QUICVersionNegotiator_LDADD = $(test_LDADD) +test_QUICVersionNegotiator_SOURCES = \ + $(test_main_SOURCES) \ + ./test/test_QUICVersionNegotiator.cc + +test_QUICFrameRetransmitter_CPPFLAGS = $(test_CPPFLAGS) +test_QUICFrameRetransmitter_LDFLAGS = @AM_LDFLAGS@ +test_QUICFrameRetransmitter_LDADD = $(test_LDADD) +test_QUICFrameRetransmitter_SOURCES = \ + $(test_main_SOURCES) \ + ./test/test_QUICFrameRetransmitter.cc + +test_QUICAddrVerifyState_CPPFLAGS = $(test_CPPFLAGS) +test_QUICAddrVerifyState_LDFLAGS = @AM_LDFLAGS@ +test_QUICAddrVerifyState_LDADD = $(test_LDADD) +test_QUICAddrVerifyState_SOURCES = \ + $(test_main_SOURCES) \ + ./test/test_QUICAddrVerifyState.cc + +# +# clang-tidy +# +include $(top_srcdir)/build/tidy.mk + +clang-tidy-local: $(DIST_SOURCES) + $(CXX_Clang_Tidy) diff --git a/iocore/net/quic/Mock.h b/iocore/net/quic/Mock.h new file mode 100644 index 00000000000..45f5e8f49b4 --- /dev/null +++ b/iocore/net/quic/Mock.h @@ -0,0 +1,730 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include "P_Net.h" + +#include "QUICApplication.h" +#include "QUICStreamManager.h" +#include "QUICLossDetector.h" +#include "QUICEvents.h" + +using namespace std::literals; +std::string_view negotiated_application_name_sv = "h3-20"sv; + +class MockQUICStreamManager : public QUICStreamManager +{ +public: + MockQUICStreamManager() : QUICStreamManager() {} + // Override + virtual QUICConnectionErrorUPtr + handle_frame(QUICEncryptionLevel level, const QUICFrame &f) override + { + ++_frameCount[static_cast(f.type())]; + ++_totalFrameCount; + + return nullptr; + } + + // for Test + int + getStreamFrameCount() + { + return _frameCount[static_cast(QUICFrameType::STREAM)]; + } + + int + getAckFrameCount() + { + return _frameCount[static_cast(QUICFrameType::ACK)]; + } + + int + getPingFrameCount() + { + return _frameCount[static_cast(QUICFrameType::PING)]; + } + + int + getTotalFrameCount() + { + return _totalFrameCount; + } + +private: + int _totalFrameCount = 0; + int _frameCount[256] = {0}; +}; + +class MockNetVConnection : public NetVConnection +{ +public: + MockNetVConnection(NetVConnectionContext_t context = NET_VCONNECTION_OUT) : NetVConnection() { netvc_context = context; } + VIO * + do_io_read(Continuation *c, int64_t nbytes, MIOBuffer *buf) + { + return nullptr; + }; + VIO * + do_io_write(Continuation *c, int64_t nbytes, IOBufferReader *buf, bool owner = false) + { + return nullptr; + }; + void do_io_close(int lerrno = -1){}; + void do_io_shutdown(ShutdownHowTo_t howto){}; + void reenable(VIO *vio){}; + void reenable_re(VIO *vio){}; + void set_active_timeout(ink_hrtime timeout_in){}; + void set_inactivity_timeout(ink_hrtime timeout_in){}; + void cancel_active_timeout(){}; + void cancel_inactivity_timeout(){}; + void add_to_keep_alive_queue(){}; + void remove_from_keep_alive_queue(){}; + bool + add_to_active_queue() + { + return true; + }; + ink_hrtime + get_active_timeout() + { + return 0; + } + ink_hrtime + get_inactivity_timeout() + { + return 0; + } + void + apply_options() + { + } + SOCKET + get_socket() { return 0; } + int + set_tcp_init_cwnd(int init_cwnd) + { + return 0; + } + int + set_tcp_congestion_control(int side) + { + return 0; + } + void set_local_addr(){}; + void set_remote_addr(){}; + + NetVConnectionContext_t + get_context() const + { + return netvc_context; + } +}; + +class MockQUICConnection : public QUICConnection +{ +public: + MockQUICConnection(NetVConnectionContext_t context = NET_VCONNECTION_OUT) : QUICConnection(), _direction(context) + { + this->_mutex = new_ProxyMutex(); + }; + + QUICConnectionId + connection_id() const override + { + return {reinterpret_cast("\x00"), 1}; + } + + QUICConnectionId + peer_connection_id() const override + { + return {reinterpret_cast("\x00"), 1}; + } + + QUICConnectionId + original_connection_id() const override + { + return {reinterpret_cast("\x00"), 1}; + } + + QUICConnectionId + first_connection_id() const override + { + return {reinterpret_cast("\x00"), 1}; + } + + const QUICFiveTuple + five_tuple() const override + { + return QUICFiveTuple(); + } + + std::string_view + cids() const override + { + using namespace std::literals; + return std::string_view("00000000-00000000"sv); + } + + std::vector + interests() override + { + return {QUICFrameType::CONNECTION_CLOSE}; + } + + QUICConnectionErrorUPtr + handle_frame(QUICEncryptionLevel level, const QUICFrame &f) override + { + ++_frameCount[static_cast(f.type())]; + ++_totalFrameCount; + + return nullptr; + } + + uint32_t + pmtu() const override + { + return 1280; + } + + NetVConnectionContext_t + direction() const override + { + return _direction; + } + + SSLNextProtocolSet * + next_protocol_set() const override + { + return nullptr; + } + + void + close(QUICConnectionErrorUPtr error) override + { + } + + int + getTotalFrameCount() + { + return _totalFrameCount; + } + + QUICStreamManager * + stream_manager() override + { + return &_stream_manager; + } + + bool + is_closed() const override + { + return false; + } + + void + handle_received_packet(UDPPacket *) override + { + } + + void + ping() override + { + } + + std::string_view + negotiated_application_name() const override + { + return negotiated_application_name_sv; + } + + int _transmit_count = 0; + int _retransmit_count = 0; + Ptr _mutex; + int _totalFrameCount = 0; + int _frameCount[256] = {0}; + MockQUICStreamManager _stream_manager; + + QUICTransportParametersInEncryptedExtensions dummy_transport_parameters(); + NetVConnectionContext_t _direction; +}; + +class MockQUICConnectionInfoProvider : public QUICConnectionInfoProvider +{ + QUICConnectionId + connection_id() const override + { + return {reinterpret_cast("\x00"), 1}; + } + + QUICConnectionId + peer_connection_id() const override + { + return {reinterpret_cast("\x00"), 1}; + } + + QUICConnectionId + original_connection_id() const override + { + return {reinterpret_cast("\x00"), 1}; + } + + QUICConnectionId + first_connection_id() const override + { + return {reinterpret_cast("\x00"), 1}; + } + + const QUICFiveTuple + five_tuple() const override + { + return QUICFiveTuple(); + } + + std::string_view + cids() const override + { + using namespace std::literals; + return std::string_view("00000000-00000000"sv); + } + + uint32_t + pmtu() const override + { + return 1280; + } + + NetVConnectionContext_t + direction() const override + { + return NET_VCONNECTION_OUT; + } + + SSLNextProtocolSet * + next_protocol_set() const override + { + return nullptr; + } + + bool + is_closed() const override + { + return false; + } + + std::string_view + negotiated_application_name() const override + { + return negotiated_application_name_sv; + } +}; + +class MockQUICCongestionController : public QUICCongestionController +{ +public: + MockQUICCongestionController(QUICConnectionInfoProvider *info, const QUICCCConfig &cc_config) + : QUICCongestionController(this->_rtt_measure, info, cc_config) + { + } + // Override + virtual void + on_packets_lost(const std::map &packets) override + { + for (auto &p : packets) { + lost_packets.insert(p.first); + } + } + + // for Test + int + getStreamFrameCount() + { + return _frameCount[static_cast(QUICFrameType::STREAM)]; + } + + int + getAckFrameCount() + { + return _frameCount[static_cast(QUICFrameType::ACK)]; + } + + int + getPingFrameCount() + { + return _frameCount[static_cast(QUICFrameType::PING)]; + } + + int + getTotalFrameCount() + { + return _totalFrameCount; + } + + std::set lost_packets; + +private: + int _totalFrameCount = 0; + int _frameCount[256] = {0}; + + QUICRTTMeasure _rtt_measure; +}; + +class MockQUICLossDetector : public QUICLossDetector +{ +public: + MockQUICLossDetector(QUICConnectionInfoProvider *info, QUICCongestionController *cc, QUICRTTMeasure *rtt_measure, + const QUICLDConfig &ld_config) + : QUICLossDetector(info, cc, rtt_measure, ld_config) + { + } + void + rcv_frame(std::shared_ptr) + { + } + + void + on_packet_sent(QUICPacketUPtr packet) + { + } +}; + +class MockQUICApplication : public QUICApplication +{ +public: + MockQUICApplication(QUICConnection *c) : QUICApplication(c) { SET_HANDLER(&MockQUICApplication::main_event_handler); } + + int + main_event_handler(int event, Event *data) + { + if (event == 12345) { + QUICStreamIO *stream_io = static_cast(data->cookie); + stream_io->write_reenable(); + } + return EVENT_CONT; + } + + void + send(const uint8_t *data, size_t size, QUICStreamId stream_id) + { + QUICStreamIO *stream_io = this->_find_stream_io(stream_id); + stream_io->write(data, size); + + eventProcessor.schedule_imm(this, ET_CALL, 12345, stream_io); + } +}; + +class MockQUICPacketProtectionKeyInfo : public QUICPacketProtectionKeyInfo +{ +public: + const EVP_CIPHER * + get_cipher(QUICKeyPhase phase) const override + { + return EVP_aes_128_gcm(); + } + + size_t + get_tag_len(QUICKeyPhase phase) const override + { + return EVP_GCM_TLS_TAG_LEN; + } + + const size_t *encryption_iv_len(QUICKeyPhase) const override + { + static size_t dummy = 12; + return &dummy; + } +}; + +class MockQUICHandshakeProtocol : public QUICHandshakeProtocol +{ +public: + MockQUICHandshakeProtocol(QUICPacketProtectionKeyInfo &pp_key_info) : QUICHandshakeProtocol(pp_key_info) {} + + int + handshake(QUICHandshakeMsgs *out, const QUICHandshakeMsgs *in) override + { + return true; + } + + void + reset() override + { + } + + bool + is_handshake_finished() const override + { + return true; + } + + bool + is_ready_to_derive() const override + { + return true; + } + + int + initialize_key_materials(QUICConnectionId cid) override + { + return 0; + } + + const char * + negotiated_cipher_suite() const override + { + return nullptr; + } + + void + negotiated_application_name(const uint8_t **name, unsigned int *len) const override + { + *name = reinterpret_cast("h3"); + *len = 2; + } + + QUICEncryptionLevel + current_encryption_level() const override + { + return QUICEncryptionLevel::INITIAL; + } + + void + abort_handshake() override + { + return; + } + + std::shared_ptr + local_transport_parameters() override + { + return nullptr; + } + + std::shared_ptr + remote_transport_parameters() override + { + return nullptr; + } + + void + set_local_transport_parameters(std::shared_ptr tp) override + { + } + + void + set_remote_transport_parameters(std::shared_ptr tp) override + { + } +}; + +class MockContinuation : public Continuation +{ +public: + MockContinuation(Ptr m) : Continuation(m) { SET_HANDLER(&MockContinuation::event_handler); } + int + event_handler(int event, Event *data) + { + return EVENT_CONT; + } +}; + +class MockQUICRTTProvider : public QUICRTTProvider +{ + ink_hrtime + latest_rtt() const override + { + return HRTIME_MSECONDS(1); + } + + ink_hrtime + rttvar() const override + { + return HRTIME_MSECONDS(1); + } + + ink_hrtime + smoothed_rtt() const override + { + return HRTIME_MSECONDS(1); + } + + ink_hrtime + congestion_period(uint32_t threshold) const override + { + return HRTIME_MSECONDS(1); + } +}; + +class MockQUICTransferProgressProvider : public QUICTransferProgressProvider +{ +public: + bool + is_transfer_goal_set() const override + { + return false; + } + + bool + is_transfer_complete() const override + { + return this->_is_transfer_complete || this->_transfer_progress >= this->_transfer_goal; + } + + bool + is_cancelled() const override + { + return this->_is_reset_complete; + } + + uint64_t + transfer_progress() const override + { + return this->_transfer_progress; + } + + uint64_t + transfer_goal() const override + { + return this->_transfer_goal; + } + + void + set_transfer_complete(bool b) + { + this->_is_transfer_complete = b; + } + + void + set_cancelled(bool b) + { + this->_is_reset_complete = b; + } + + void + set_transfer_progress(uint64_t v) + { + this->_transfer_progress = v; + } + + void + set_transfer_goal(uint64_t v) + { + this->_transfer_goal = v; + } + +private: + bool _is_transfer_complete = false; + bool _is_reset_complete = false; + uint64_t _transfer_progress = 0; + uint64_t _transfer_goal = UINT64_MAX; +}; + +class MockQUICFrameGenerator : public QUICFrameGenerator +{ +public: + bool + will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) override + { + return true; + } + + QUICFrame * + generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size, + ink_hrtime timestamp) override + { + QUICFrame *frame = QUICFrameFactory::create_ping_frame(buf, 0, this); + QUICFrameInformationUPtr info = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc()); + this->_records_frame(0, std::move(info)); + return frame; + } + + int lost_frame_count = 0; + +private: + void + _on_frame_lost(QUICFrameInformationUPtr &info) override + { + lost_frame_count++; + } +}; + +class MockQUICLDConfig : public QUICLDConfig +{ + uint32_t + packet_threshold() const + { + return 3; + } + + float + time_threshold() const + { + return 1.25; + } + + ink_hrtime + granularity() const + { + return HRTIME_MSECONDS(1); + } + + ink_hrtime + initial_rtt() const + { + return HRTIME_MSECONDS(100); + } +}; + +class MockQUICCCConfig : public QUICCCConfig +{ + uint32_t + max_datagram_size() const + { + return 1200; + } + + uint32_t + initial_window() const + { + return 10; + } + + uint32_t + minimum_window() const + { + return 2; + } + + float + loss_reduction_factor() const + { + return 0.5; + } + + uint32_t + persistent_congestion_threshold() const + { + return 2; + } +}; diff --git a/iocore/net/quic/QUICAckFrameCreator.cc b/iocore/net/quic/QUICAckFrameCreator.cc new file mode 100644 index 00000000000..27923b0f499 --- /dev/null +++ b/iocore/net/quic/QUICAckFrameCreator.cc @@ -0,0 +1,392 @@ +/** @file + * + * A brief file description + * + * @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 "I_EventSystem.h" +#include "QUICAckFrameCreator.h" +#include "QUICConfig.h" +#include + +QUICAckFrameManager::QUICAckFrameManager() +{ + for (auto i = 0; i < kPacketNumberSpace; i++) { + this->_ack_creator[i] = std::make_unique(static_cast(i), this); + } +} + +QUICAckFrameManager::~QUICAckFrameManager() {} + +void +QUICAckFrameManager::set_ack_delay_exponent(uint8_t ack_delay_exponent) +{ + // This function should be called only once + ink_assert(this->_ack_delay_exponent == 0); + this->_ack_delay_exponent = ack_delay_exponent; +} + +int +QUICAckFrameManager::update(QUICEncryptionLevel level, QUICPacketNumber packet_number, size_t size, bool ack_only) +{ + if (!this->_is_level_matched(level)) { + return 0; + } + + auto index = QUICTypeUtil::pn_space(level); + auto &ack_creator = this->_ack_creator[static_cast(index)]; + ack_creator->push_back(packet_number, size, ack_only); + return 0; +} + +/** + * @param connection_credit This is not used. Because ACK frame is not flow-controlled + */ +QUICFrame * +QUICAckFrameManager::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t /* connection_credit */, + uint16_t maximum_frame_size, ink_hrtime timestamp) +{ + QUICAckFrame *ack_frame = nullptr; + + if (!this->_is_level_matched(level) || level == QUICEncryptionLevel::ZERO_RTT) { + return ack_frame; + } + + auto index = QUICTypeUtil::pn_space(level); + auto &ack_creator = this->_ack_creator[static_cast(index)]; + ack_frame = ack_creator->generate_ack_frame(buf, maximum_frame_size); + + if (ack_frame != nullptr) { + QUICFrameInformationUPtr info = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc()); + AckFrameInfo *ack_info = reinterpret_cast(info->data); + ack_info->largest_acknowledged = ack_frame->largest_acknowledged(); + + info->level = level; + info->type = ack_frame->type(); + this->_records_frame(ack_frame->id(), std::move(info)); + } + + return ack_frame; +} + +bool +QUICAckFrameManager::will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) +{ + // No ACK frame on ZERO_RTT level + if (!this->_is_level_matched(level) || level == QUICEncryptionLevel::ZERO_RTT) { + return false; + } + + auto index = QUICTypeUtil::pn_space(level); + return this->_ack_creator[static_cast(index)]->is_ack_frame_ready(); +} + +void +QUICAckFrameManager::_on_frame_acked(QUICFrameInformationUPtr &info) +{ + ink_assert(info->type == QUICFrameType::ACK); + AckFrameInfo *ack_info = reinterpret_cast(info->data); + auto index = QUICTypeUtil::pn_space(info->level); + this->_ack_creator[static_cast(index)]->forget(ack_info->largest_acknowledged); +} + +void +QUICAckFrameManager::_on_frame_lost(QUICFrameInformationUPtr &info) +{ + ink_assert(info->type == QUICFrameType::ACK); + auto index = QUICTypeUtil::pn_space(info->level); + // when ack frame lost. Force to refresh the frame. + this->_ack_creator[static_cast(index)]->refresh_state(); +} + +QUICFrameId +QUICAckFrameManager::issue_frame_id() +{ + return this->_issue_frame_id(); +} + +uint8_t +QUICAckFrameManager::ack_delay_exponent() const +{ + return this->_ack_delay_exponent; +} + +void +QUICAckFrameManager::set_max_ack_delay(uint16_t delay) +{ + for (auto i = 0; i < kPacketNumberSpace; i++) { + this->_ack_creator[i]->set_max_ack_delay(delay); + } +} + +// +// QUICAckFrameManager::QUICAckFrameCreator +// +void +QUICAckFrameManager::QUICAckFrameCreator::refresh_state() +{ + if (this->_packet_numbers.empty() || !this->_available) { + return; + } + + // we have something to send + this->_should_send = true; +} + +void +QUICAckFrameManager::QUICAckFrameCreator::forget(QUICPacketNumber largest_acknowledged) +{ + this->_available = false; + this->sort(); + std::list remove_list; + for (auto it = this->_packet_numbers.begin(); it != this->_packet_numbers.end(); it++) { + if ((*it).packet_number == largest_acknowledged) { + remove_list.splice(remove_list.begin(), this->_packet_numbers, it, this->_packet_numbers.end()); + break; + } + this->_available |= !(*it).ack_only; + } + + if (this->_packet_numbers.empty() || !this->_available) { + this->_should_send = false; + } +} + +void +QUICAckFrameManager::QUICAckFrameCreator::push_back(QUICPacketNumber packet_number, size_t size, bool ack_only) +{ + if (packet_number == 0 || packet_number > this->_largest_ack_number) { + this->_largest_ack_received_time = Thread::get_hrtime(); + this->_largest_ack_number = packet_number; + } + + if (!this->_latest_packet_received_time) { + this->_latest_packet_received_time = Thread::get_hrtime(); + } + + // unorder packet should send ack immediately to accellerate the recovery + if (this->_expect_next != packet_number) { + this->_should_send = true; + } + + // every 2 full-packet should send a ack frame like tcp + this->_size_unsend += size; + // FIXME: this size should be fixed with PMTU + if (this->_size_unsend > 2 * 1480) { + this->_size_unsend = 0; + this->_should_send = true; + } + + // can not delay handshake packet + if ((this->_pn_space == QUICPacketNumberSpace::Initial || this->_pn_space == QUICPacketNumberSpace::Handshake) && !ack_only) { + this->_should_send = true; + } + + if (!ack_only) { + this->_available = true; + this->_has_new_data = true; + } else { + this->_should_send = this->_available ? this->_should_send : false; + } + + this->_expect_next = packet_number + 1; + this->_packet_numbers.push_back({ack_only, packet_number}); +} + +size_t +QUICAckFrameManager::QUICAckFrameCreator::size() +{ + return this->_packet_numbers.size(); +} + +void +QUICAckFrameManager::QUICAckFrameCreator::clear() +{ + this->_packet_numbers.clear(); + this->_largest_ack_number = 0; + this->_largest_ack_received_time = 0; + this->_latest_packet_received_time = 0; + this->_size_unsend = 0; + this->_should_send = false; + this->_available = false; +} + +QUICPacketNumber +QUICAckFrameManager::QUICAckFrameCreator::largest_ack_number() +{ + return this->_largest_ack_number; +} + +ink_hrtime +QUICAckFrameManager::QUICAckFrameCreator::largest_ack_received_time() +{ + return this->_largest_ack_received_time; +} + +void +QUICAckFrameManager::QUICAckFrameCreator::sort() +{ + // TODO Find more smart way + this->_packet_numbers.sort([](const RecvdPacket &a, const RecvdPacket &b) -> bool { return a.packet_number > b.packet_number; }); +} + +QUICAckFrame * +QUICAckFrameManager::QUICAckFrameCreator::generate_ack_frame(uint8_t *buf, uint16_t maximum_frame_size) +{ + QUICAckFrame *ack_frame = nullptr; + if (!this->_available) { + this->_should_send = false; + return ack_frame; + } + + ack_frame = this->_create_ack_frame(buf); + if (ack_frame == nullptr || ack_frame->size() < maximum_frame_size) { + this->_should_send = false; + this->_latest_packet_received_time = 0; + } else { + return nullptr; + } + + return ack_frame; +} + +QUICAckFrame * +QUICAckFrameManager::QUICAckFrameCreator::_create_ack_frame(uint8_t *buf) +{ + ink_assert(!this->_packet_numbers.empty()); + QUICAckFrame *ack_frame = nullptr; + this->sort(); + std::list &list = this->_packet_numbers; + + this->_has_new_data = false; + + uint8_t gap = 0; + uint64_t length = 0; + auto it = list.begin(); + + // skip ack_only packets + for (; it != list.end(); it++) { + if (!(*it).ack_only) { + break; + } + } + + if (it == list.end()) { + return ack_frame; + } + + QUICPacketNumber largest_ack_number = (*it).packet_number; + QUICPacketNumber last_ack_number = largest_ack_number; + + while (it != list.end()) { + QUICPacketNumber pn = (*it).packet_number; + if (pn == last_ack_number) { + last_ack_number--; + length++; + it++; + continue; + } + + ink_assert(length > 0); + + if (ack_frame) { + ack_frame->ack_block_section()->add_ack_block({static_cast(gap - 1), length - 1}); + } else { + uint64_t delay = this->_calculate_delay(); + ack_frame = QUICFrameFactory::create_ack_frame(buf, largest_ack_number, delay, length - 1, + this->_ack_manager->issue_frame_id(), this->_ack_manager); + } + + gap = last_ack_number - pn; + last_ack_number = pn; + length = 0; + } + + if (ack_frame) { + ack_frame->ack_block_section()->add_ack_block({static_cast(gap - 1), length - 1}); + } else { + uint64_t delay = this->_calculate_delay(); + ack_frame = QUICFrameFactory::create_ack_frame(buf, largest_ack_number, delay, length - 1, this->_ack_manager->issue_frame_id(), + this->_ack_manager); + } + + return ack_frame; +} + +uint64_t +QUICAckFrameManager::QUICAckFrameCreator::_calculate_delay() +{ + // Ack delay is in microseconds and scaled + ink_hrtime now = Thread::get_hrtime(); + uint64_t delay = (now - this->_largest_ack_received_time) / 1000; + uint8_t ack_delay_exponent = 3; + if (this->_pn_space != QUICPacketNumberSpace::Initial && this->_pn_space != QUICPacketNumberSpace::Handshake) { + ack_delay_exponent = this->_ack_manager->ack_delay_exponent(); + } + return delay >> ack_delay_exponent; +} + +bool +QUICAckFrameManager::QUICAckFrameCreator::available() const +{ + return this->_available; +} + +bool +QUICAckFrameManager::QUICAckFrameCreator::is_ack_frame_ready() +{ + if (this->_available && this->_has_new_data && !this->_packet_numbers.empty() && + this->_latest_packet_received_time + this->_max_ack_delay * HRTIME_MSECOND <= Thread::get_hrtime()) { + // when we has new data and the data is available to send (not ack only). and we delay for too much time. Send it out + this->_should_send = true; + } + + return this->_should_send && this->_available && !this->_packet_numbers.empty(); +} + +void +QUICAckFrameManager::QUICAckFrameCreator::set_max_ack_delay(uint16_t delay) +{ + this->_max_ack_delay = delay; +} + +QUICAckFrameManager::QUICAckFrameCreator::QUICAckFrameCreator(QUICPacketNumberSpace pn_space, QUICAckFrameManager *ack_manager) + : _ack_manager(ack_manager), _pn_space(pn_space) +{ +} + +QUICAckFrameManager::QUICAckFrameCreator::~QUICAckFrameCreator() {} + +/* + No limit of encryption level. + ``` + std::array _encryption_level_filter = { + QUICEncryptionLevel::INITIAL, + QUICEncryptionLevel::ZERO_RTT, + QUICEncryptionLevel::HANDSHAKE, + QUICEncryptionLevel::ONE_RTT, + }; + ``` +*/ +bool +QUICAckFrameManager::_is_level_matched(QUICEncryptionLevel level) +{ + return true; +} diff --git a/iocore/net/quic/QUICAckFrameCreator.h b/iocore/net/quic/QUICAckFrameCreator.h new file mode 100644 index 00000000000..08b50d470bc --- /dev/null +++ b/iocore/net/quic/QUICAckFrameCreator.h @@ -0,0 +1,135 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include "QUICFrameGenerator.h" +#include "QUICTypes.h" +#include "QUICFrame.h" +#include + +class QUICConnection; + +class QUICAckFrameManager : public QUICFrameGenerator +{ +public: + class QUICAckFrameCreator + { + public: + struct RecvdPacket { + bool ack_only = false; + QUICPacketNumber packet_number = 0; + }; + QUICAckFrameCreator(QUICPacketNumberSpace pn_space, QUICAckFrameManager *ack_manager); + ~QUICAckFrameCreator(); + + void push_back(QUICPacketNumber packet_number, size_t size, bool ack_only); + size_t size(); + void clear(); + void sort(); + void forget(QUICPacketNumber largest_acknowledged); + bool available() const; + bool is_ack_frame_ready(); + void set_max_ack_delay(uint16_t delay); + + // Checks maximum_frame_size and return _ack_frame + QUICAckFrame *generate_ack_frame(uint8_t *buf, uint16_t maximum_frame_size); + + // refresh state when frame lost + void refresh_state(); + + QUICPacketNumber largest_ack_number(); + ink_hrtime largest_ack_received_time(); + + private: + uint64_t _calculate_delay(); + QUICAckFrame *_create_ack_frame(uint8_t *buf); + + std::list _packet_numbers; + bool _available = false; // packet_number has data to sent + bool _should_send = false; // ack frame should be sent immediately + bool _has_new_data = false; // new data after last sent + size_t _size_unsend = 0; + uint16_t _max_ack_delay = 25; + QUICPacketNumber _largest_ack_number = 0; + QUICPacketNumber _expect_next = 0; + ink_hrtime _largest_ack_received_time = 0; + ink_hrtime _latest_packet_received_time = 0; + + QUICAckFrameManager *_ack_manager = nullptr; + + QUICPacketNumberSpace _pn_space = QUICPacketNumberSpace::Initial; + }; + + static constexpr int MAXIMUM_PACKET_COUNT = 256; + + QUICAckFrameManager(); + ~QUICAckFrameManager(); + + void set_ack_delay_exponent(uint8_t ack_delay_exponent); + void set_max_ack_delay(uint16_t delay); + void set_force_to_send(bool on = true); + bool force_to_send() const; + + /* + * All packet numbers ATS received need to be passed to this method. + * Returns 0 if updated successfully. + */ + int update(QUICEncryptionLevel level, QUICPacketNumber packet_number, size_t size, bool akc_only); + + /* + * Returns true only if should send ack. + */ + bool will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) override; + + /* + * Calls create directly. + */ + QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size, + ink_hrtime timestamp) override; + + QUICFrameId issue_frame_id(); + uint8_t ack_delay_exponent() const; + +protected: + virtual bool _is_level_matched(QUICEncryptionLevel level) override; + +private: + virtual void _on_frame_acked(QUICFrameInformationUPtr &info) override; + virtual void _on_frame_lost(QUICFrameInformationUPtr &info) override; + + /* + * Returns QUICAckFrame only if ACK frame is able to be sent. + * Caller must send the ACK frame to the peer if it was returned. + */ + QUICAckFrame *_create_ack_frame(uint8_t *buf, QUICEncryptionLevel level); + uint64_t _calculate_delay(QUICEncryptionLevel level); + + bool _available[4] = {false}; + bool _should_send[4] = {false}; + + // Initial, 0/1-RTT, and Handshake + std::unique_ptr _ack_creator[3]; + + uint8_t _ack_delay_exponent = 0; +}; diff --git a/iocore/net/quic/QUICAddrVerifyState.cc b/iocore/net/quic/QUICAddrVerifyState.cc new file mode 100644 index 00000000000..67281b592d6 --- /dev/null +++ b/iocore/net/quic/QUICAddrVerifyState.cc @@ -0,0 +1,66 @@ +/** @file + + A brief file description + + @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 "QUICAddrVerifyState.h" + +void +QUICAddrVerifyState::fill(uint32_t windows) +{ + uint64_t tmp = this->_windows; + if (tmp + windows * 3 > UINT32_MAX) { + // overflow + this->_windows = UINT32_MAX; + return; + } + + this->_windows += windows * 3; +} + +void +QUICAddrVerifyState::consume(uint32_t windows) +{ + if (this->_windows <= windows) { + this->_windows = 0; + return; + } + + this->_windows -= windows; +} + +uint32_t +QUICAddrVerifyState::windows() +{ + return this->_windows; +} + +void +QUICAddrVerifyState::set_addr_verifed() +{ + this->_src_addr_verified = true; +} + +bool +QUICAddrVerifyState::is_verified() const +{ + return this->_src_addr_verified; +} diff --git a/iocore/net/quic/QUICAddrVerifyState.h b/iocore/net/quic/QUICAddrVerifyState.h new file mode 100644 index 00000000000..d6d6c7c4e42 --- /dev/null +++ b/iocore/net/quic/QUICAddrVerifyState.h @@ -0,0 +1,43 @@ +/** @file + + A brief file description + + @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. + */ + +#pragma once + +#include + +class QUICAddrVerifyState +{ +public: + QUICAddrVerifyState(uint8_t packets, uint32_t windows) : _windows(windows) {} + QUICAddrVerifyState() = default; + + void fill(uint32_t windows); + void consume(uint32_t windows); + void set_addr_verifed(); + uint32_t windows(); + bool is_verified() const; + +private: + bool _src_addr_verified = false; + uint32_t _windows = 0; +}; diff --git a/iocore/net/quic/QUICAltConnectionManager.cc b/iocore/net/quic/QUICAltConnectionManager.cc new file mode 100644 index 00000000000..faca19e8743 --- /dev/null +++ b/iocore/net/quic/QUICAltConnectionManager.cc @@ -0,0 +1,371 @@ +/** @file + + A brief file description + + @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 "QUICAltConnectionManager.h" +#include "QUICConnectionTable.h" + +static constexpr char V_DEBUG_TAG[] = "v_quic_alt_con"; + +#define QUICACMVDebug(fmt, ...) Debug(V_DEBUG_TAG, "[%s] " fmt, this->_qc->cids().data(), ##__VA_ARGS__) + +QUICAltConnectionManager::QUICAltConnectionManager(QUICConnection *qc, QUICConnectionTable &ctable, + const QUICConnectionId &peer_initial_cid, uint32_t instance_id, + uint8_t num_alt_con, const QUICPreferredAddress &preferred_address) + : _qc(qc), _ctable(ctable), _instance_id(instance_id), _nids(num_alt_con) +{ + // Sequence number of the initial CID is 0 + this->_alt_quic_connection_ids_remote.push_back({0, peer_initial_cid, {}, {true}}); + + // Sequence number of the preferred address is 1 if available + if (preferred_address.is_available()) { + this->_alt_quic_connection_ids_remote.push_back({1, preferred_address.cid(), preferred_address.token(), {false}}); + } + + this->_alt_quic_connection_ids_local = static_cast(ats_malloc(sizeof(AltConnectionInfo) * this->_nids)); +} + +QUICAltConnectionManager::QUICAltConnectionManager(QUICConnection *qc, QUICConnectionTable &ctable, + const QUICConnectionId &peer_initial_cid, uint32_t instance_id, + uint8_t num_alt_con, const IpEndpoint *preferred_endpoint_ipv4, + const IpEndpoint *preferred_endpoint_ipv6) + : _qc(qc), _ctable(ctable), _instance_id(instance_id), _nids(num_alt_con) +{ + // Sequence number of the initial CID is 0 + this->_alt_quic_connection_ids_remote.push_back({0, peer_initial_cid, {}, {true}}); + + this->_alt_quic_connection_ids_local = static_cast(ats_malloc(sizeof(AltConnectionInfo) * this->_nids)); + this->_init_alt_connection_ids(preferred_endpoint_ipv4, preferred_endpoint_ipv6); +} + +QUICAltConnectionManager::~QUICAltConnectionManager() +{ + ats_free(this->_alt_quic_connection_ids_local); + delete this->_preferred_address; +} + +const QUICPreferredAddress * +QUICAltConnectionManager::preferred_address() const +{ + return this->_preferred_address; +} + +std::vector +QUICAltConnectionManager::interests() +{ + return {QUICFrameType::NEW_CONNECTION_ID, QUICFrameType::RETIRE_CONNECTION_ID}; +} + +QUICConnectionErrorUPtr +QUICAltConnectionManager::handle_frame(QUICEncryptionLevel level, const QUICFrame &frame) +{ + QUICConnectionErrorUPtr error = nullptr; + + switch (frame.type()) { + case QUICFrameType::NEW_CONNECTION_ID: + error = this->_register_remote_connection_id(static_cast(frame)); + break; + case QUICFrameType::RETIRE_CONNECTION_ID: + error = this->_retire_remote_connection_id(static_cast(frame)); + break; + default: + QUICACMVDebug("Unexpected frame type: %02x", static_cast(frame.type())); + ink_assert(false); + break; + } + + return error; +} + +QUICAltConnectionManager::AltConnectionInfo +QUICAltConnectionManager::_generate_next_alt_con_info() +{ + QUICConnectionId conn_id; + conn_id.randomize(); + QUICStatelessResetToken token(conn_id, this->_instance_id); + AltConnectionInfo aci = {++this->_alt_quic_connection_id_seq_num, conn_id, token, {false}}; + + if (this->_qc->direction() == NET_VCONNECTION_IN) { + this->_ctable.insert(conn_id, this->_qc); + } + + if (is_debug_tag_set(V_DEBUG_TAG)) { + char new_cid_str[QUICConnectionId::MAX_HEX_STR_LENGTH]; + conn_id.hex(new_cid_str, QUICConnectionId::MAX_HEX_STR_LENGTH); + QUICACMVDebug("alt-cid=%s", new_cid_str); + } + + return aci; +} + +void +QUICAltConnectionManager::_init_alt_connection_ids(const IpEndpoint *preferred_endpoint_ipv4, + const IpEndpoint *preferred_endpoint_ipv6) +{ + if (preferred_endpoint_ipv4 || preferred_endpoint_ipv6) { + this->_alt_quic_connection_ids_local[0] = this->_generate_next_alt_con_info(); + // This alt cid will be advertised via Transport Parameter + this->_alt_quic_connection_ids_local[0].advertised = true; + + IpEndpoint empty_endpoint_ipv4; + IpEndpoint empty_endpoint_ipv6; + empty_endpoint_ipv4.sa.sa_family = AF_UNSPEC; + empty_endpoint_ipv6.sa.sa_family = AF_UNSPEC; + if (preferred_endpoint_ipv4 == nullptr) { + preferred_endpoint_ipv4 = &empty_endpoint_ipv4; + } + if (preferred_endpoint_ipv6 == nullptr) { + preferred_endpoint_ipv6 = &empty_endpoint_ipv6; + } + + // FIXME Check nullptr dereference + this->_preferred_address = + new QUICPreferredAddress(*preferred_endpoint_ipv4, *preferred_endpoint_ipv6, this->_alt_quic_connection_ids_local[0].id, + this->_alt_quic_connection_ids_local[0].token); + } + + for (int i = (preferred_endpoint_ipv4 || preferred_endpoint_ipv6 ? 1 : 0); i < this->_nids; ++i) { + this->_alt_quic_connection_ids_local[i] = this->_generate_next_alt_con_info(); + } + this->_need_advertise = true; +} + +bool +QUICAltConnectionManager::_update_alt_connection_id(uint64_t chosen_seq_num) +{ + for (int i = 0; i < this->_nids; ++i) { + if (_alt_quic_connection_ids_local[i].seq_num == chosen_seq_num) { + _alt_quic_connection_ids_local[i] = this->_generate_next_alt_con_info(); + this->_need_advertise = true; + return true; + } + } + + // Seq 0 is special so it's not in the array + if (chosen_seq_num == 0) { + return true; + } + + return false; +} + +QUICConnectionErrorUPtr +QUICAltConnectionManager::_register_remote_connection_id(const QUICNewConnectionIdFrame &frame) +{ + QUICConnectionErrorUPtr error = nullptr; + + if (frame.connection_id() == QUICConnectionId::ZERO()) { + error = std::make_unique(QUICTransErrorCode::PROTOCOL_VIOLATION, "received zero-length cid", + QUICFrameType::NEW_CONNECTION_ID); + } else { + this->_alt_quic_connection_ids_remote.push_back( + {frame.sequence(), frame.connection_id(), frame.stateless_reset_token(), {false}}); + } + + return error; +} + +QUICConnectionErrorUPtr +QUICAltConnectionManager::_retire_remote_connection_id(const QUICRetireConnectionIdFrame &frame) +{ + QUICConnectionErrorUPtr error = nullptr; + + if (!this->_update_alt_connection_id(frame.seq_num())) { + error = std::make_unique(QUICTransErrorCode::PROTOCOL_VIOLATION, "received unused sequence number", + QUICFrameType::RETIRE_CONNECTION_ID); + } + return error; +} + +bool +QUICAltConnectionManager::is_ready_to_migrate() const +{ + if (this->_alt_quic_connection_ids_remote.empty()) { + return false; + } + + for (auto &info : this->_alt_quic_connection_ids_remote) { + if (!info.used) { + return true; + } + } + return false; +} + +QUICConnectionId +QUICAltConnectionManager::migrate_to_alt_cid() +{ + if (this->_qc->direction() == NET_VCONNECTION_OUT) { + this->_init_alt_connection_ids(); + } + + for (auto &info : this->_alt_quic_connection_ids_remote) { + if (info.used) { + continue; + } + info.used = true; + return info.id; + } + + ink_assert(!"Could not find CID available"); + return QUICConnectionId::ZERO(); +} + +bool +QUICAltConnectionManager::migrate_to(const QUICConnectionId &cid, QUICStatelessResetToken &new_reset_token) +{ + for (unsigned int i = 0; i < this->_nids; ++i) { + AltConnectionInfo &info = this->_alt_quic_connection_ids_local[i]; + if (info.id == cid) { + // Migrate connection + new_reset_token = info.token; + return true; + } + } + return false; +} + +void +QUICAltConnectionManager::drop_cid(const QUICConnectionId &cid) +{ + for (auto it = this->_alt_quic_connection_ids_remote.begin(); it != this->_alt_quic_connection_ids_remote.end(); ++it) { + if (it->id == cid) { + QUICACMVDebug("Dropping advertized CID %" PRIx32 " seq# %" PRIu64, it->id.h32(), it->seq_num); + this->_retired_seq_nums.push(it->seq_num); + this->_alt_quic_connection_ids_remote.erase(it); + return; + } + } +} + +void +QUICAltConnectionManager::invalidate_alt_connections() +{ + for (unsigned int i = 0; i < this->_nids; ++i) { + this->_ctable.erase(this->_alt_quic_connection_ids_local[i].id, this->_qc); + } +} + +bool +QUICAltConnectionManager::will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) +{ + if (!this->_is_level_matched(level)) { + return false; + } + + return this->_need_advertise || !this->_retired_seq_nums.empty(); +} + +/** + * @param connection_credit This is not used. Because NEW_CONNECTION_ID frame is not flow-controlled + */ +QUICFrame * +QUICAltConnectionManager::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t /* connection_credit */, + uint16_t maximum_frame_size, ink_hrtime timestamp) +{ + QUICFrame *frame = nullptr; + if (!this->_is_level_matched(level)) { + return frame; + } + + if (this->_need_advertise) { + int count = this->_nids; + for (int i = 0; i < count; ++i) { + if (!this->_alt_quic_connection_ids_local[i].advertised) { + frame = QUICFrameFactory::create_new_connection_id_frame(buf, this->_alt_quic_connection_ids_local[i].seq_num, + this->_alt_quic_connection_ids_local[i].id, + this->_alt_quic_connection_ids_local[i].token); + + if (frame && frame->size() > maximum_frame_size) { + // Cancel generating frame + frame = nullptr; + } else if (frame != nullptr) { + this->_records_new_connection_id_frame(level, static_cast(*frame)); + this->_alt_quic_connection_ids_local[i].advertised = true; + } + + return frame; + } + } + this->_need_advertise = false; + } + + if (!this->_retired_seq_nums.empty()) { + if (auto s = this->_retired_seq_nums.front()) { + frame = QUICFrameFactory::create_retire_connection_id_frame(buf, s); + this->_records_retire_connection_id_frame(level, static_cast(*frame)); + this->_retired_seq_nums.pop(); + return frame; + } + } + + return frame; +} + +void +QUICAltConnectionManager::_records_new_connection_id_frame(QUICEncryptionLevel level, const QUICNewConnectionIdFrame &frame) +{ + QUICFrameInformationUPtr info = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc()); + info->type = frame.type(); + info->level = level; + + AltConnectionInfo *frame_info = reinterpret_cast(info->data); + frame_info->seq_num = frame.sequence(); + frame_info->token = frame.stateless_reset_token(); + frame_info->id = frame.connection_id(); + this->_records_frame(frame.id(), std::move(info)); +} + +void +QUICAltConnectionManager::_records_retire_connection_id_frame(QUICEncryptionLevel level, const QUICRetireConnectionIdFrame &frame) +{ + QUICFrameInformationUPtr info = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc()); + info->type = frame.type(); + info->level = level; + *reinterpret_cast(info->data) = frame.seq_num(); + this->_records_frame(frame.id(), std::move(info)); +} + +void +QUICAltConnectionManager::_on_frame_lost(QUICFrameInformationUPtr &info) +{ + switch (info->type) { + case QUICFrameType::NEW_CONNECTION_ID: { + AltConnectionInfo *frame_info = reinterpret_cast(info->data); + for (int i = 0; i < this->_nids; ++i) { + if (this->_alt_quic_connection_ids_local[i].seq_num == frame_info->seq_num) { + ink_assert(this->_alt_quic_connection_ids_local[i].advertised); + this->_alt_quic_connection_ids_local[i].advertised = false; + this->_need_advertise = true; + return; + } + } + break; + } + case QUICFrameType::RETIRE_CONNECTION_ID: { + this->_retired_seq_nums.push(*reinterpret_cast(info->data)); + break; + } + default: + ink_assert(0); + } +} diff --git a/iocore/net/quic/QUICAltConnectionManager.h b/iocore/net/quic/QUICAltConnectionManager.h new file mode 100644 index 00000000000..501ab99a28c --- /dev/null +++ b/iocore/net/quic/QUICAltConnectionManager.h @@ -0,0 +1,124 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include +#include + +#include "QUICFrameGenerator.h" +#include "QUICTypes.h" +#include "QUICConnection.h" + +class QUICConnectionTable; + +class QUICAltConnectionManager : public QUICFrameHandler, public QUICFrameGenerator +{ +public: + /** + * Constructor for clients + */ + QUICAltConnectionManager(QUICConnection *qc, QUICConnectionTable &ctable, const QUICConnectionId &peer_initial_cid, + uint32_t instance_id, uint8_t num_alt_con, const QUICPreferredAddress &preferred_address); + /** + * Constructor for servers + */ + QUICAltConnectionManager(QUICConnection *qc, QUICConnectionTable &ctable, const QUICConnectionId &peer_initial_cid, + uint32_t instance_id, uint8_t num_alt_con, const IpEndpoint *preferred_endpoint_ipv4 = nullptr, + const IpEndpoint *preferred_endpoint_ipv6 = nullptr); + ~QUICAltConnectionManager(); + + /** + * Check if AltConnectionManager has at least one CID advertised by the peer. + */ + bool is_ready_to_migrate() const; + + /** + * Prepare for new CID for the peer, and return one of CIDs advertised by the peer. + * New CID for the peer will be sent on next call for generate_frame() + */ + QUICConnectionId migrate_to_alt_cid(); + + /** + * Migrate to new CID + * + * cid need to match with one of alt CID that AltConnnectionManager prepared. + */ + bool migrate_to(const QUICConnectionId &cid, QUICStatelessResetToken &new_reset_token); + + void drop_cid(const QUICConnectionId &cid); + + /** + * Invalidate all CIDs prepared + */ + void invalidate_alt_connections(); + + /** + * Returns server preferred address if available + */ + const QUICPreferredAddress *preferred_address() const; + + // QUICFrameHandler + virtual std::vector interests() override; + virtual QUICConnectionErrorUPtr handle_frame(QUICEncryptionLevel level, const QUICFrame &frame) override; + + // QUICFrameGenerator + bool will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) override; + QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size, + ink_hrtime timestamp) override; + +private: + struct AltConnectionInfo { + uint64_t seq_num; + QUICConnectionId id; + QUICStatelessResetToken token; + union { + bool advertised; // For local info + bool used; // For remote info + }; + }; + + QUICConnection *_qc = nullptr; + QUICConnectionTable &_ctable; + AltConnectionInfo *_alt_quic_connection_ids_local; + std::vector _alt_quic_connection_ids_remote; + std::queue _retired_seq_nums; + uint32_t _instance_id = 0; + uint8_t _nids = 1; + uint64_t _alt_quic_connection_id_seq_num = 0; + bool _need_advertise = false; + QUICPreferredAddress *_preferred_address = nullptr; + + AltConnectionInfo _generate_next_alt_con_info(); + void _init_alt_connection_ids(const IpEndpoint *preferred_endpoint_ipv4 = nullptr, + const IpEndpoint *preferred_endpoint_ipv6 = nullptr); + bool _update_alt_connection_id(uint64_t chosen_seq_num); + + void _records_new_connection_id_frame(QUICEncryptionLevel level, const QUICNewConnectionIdFrame &frame); + void _records_retire_connection_id_frame(QUICEncryptionLevel, const QUICRetireConnectionIdFrame &frame); + + void _on_frame_lost(QUICFrameInformationUPtr &info) override; + + QUICConnectionErrorUPtr _register_remote_connection_id(const QUICNewConnectionIdFrame &frame); + QUICConnectionErrorUPtr _retire_remote_connection_id(const QUICRetireConnectionIdFrame &frame); +}; diff --git a/iocore/net/quic/QUICApplication.cc b/iocore/net/quic/QUICApplication.cc new file mode 100644 index 00000000000..54ad68e1137 --- /dev/null +++ b/iocore/net/quic/QUICApplication.cc @@ -0,0 +1,281 @@ +/** @file + * + * A brief file description + * + * @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 "QUICApplication.h" +#include "QUICStream.h" + +static constexpr char tag_stream_io[] = "quic_stream_io"; +static constexpr char tag_app[] = "quic_app"; + +#define QUICStreamIODebug(fmt, ...) \ + Debug(tag_stream_io, "[%s] [%" PRIu64 "] " fmt, this->_stream_vc->connection_info()->cids().data(), this->_stream_vc->id(), \ + ##__VA_ARGS__) + +// +// QUICStreamIO +// +QUICStreamIO::QUICStreamIO(QUICApplication *app, QUICStreamVConnection *stream_vc) : _stream_vc(stream_vc) +{ + this->_read_buffer = new_MIOBuffer(BUFFER_SIZE_INDEX_8K); + this->_write_buffer = new_MIOBuffer(BUFFER_SIZE_INDEX_8K); + + this->_read_buffer_reader = this->_read_buffer->alloc_reader(); + this->_write_buffer_reader = this->_write_buffer->alloc_reader(); + + switch (stream_vc->direction()) { + case QUICStreamDirection::BIDIRECTIONAL: + this->_read_vio = stream_vc->do_io_read(app, INT64_MAX, this->_read_buffer); + this->_write_vio = stream_vc->do_io_write(app, INT64_MAX, this->_write_buffer_reader); + break; + case QUICStreamDirection::SEND: + this->_write_vio = stream_vc->do_io_write(app, INT64_MAX, this->_write_buffer_reader); + break; + case QUICStreamDirection::RECEIVE: + this->_read_vio = stream_vc->do_io_read(app, INT64_MAX, this->_read_buffer); + break; + default: + ink_assert(false); + break; + } +} + +QUICStreamIO::~QUICStreamIO() +{ + // All readers will be deallocated + free_MIOBuffer(this->_read_buffer); + free_MIOBuffer(this->_write_buffer); +}; + +uint32_t +QUICStreamIO::stream_id() const +{ + return this->_stream_vc->id(); +} + +bool +QUICStreamIO::is_bidirectional() const +{ + return this->_stream_vc->is_bidirectional(); +} + +int64_t +QUICStreamIO::read(uint8_t *buf, int64_t len) +{ + if (is_debug_tag_set(tag_stream_io)) { + if (this->_read_vio->nbytes == INT64_MAX) { + QUICStreamIODebug("nbytes=- ndone=%" PRId64 " read_avail=%" PRId64 " read_len=%" PRId64, this->_read_vio->ndone, + this->_read_buffer_reader->read_avail(), len); + } else { + QUICStreamIODebug("nbytes=%" PRId64 " ndone=%" PRId64 " read_avail=%" PRId64 " read_len=%" PRId64, this->_read_vio->nbytes, + this->_read_vio->ndone, this->_read_buffer_reader->read_avail(), len); + } + } + + int64_t nread = this->_read_buffer_reader->read(buf, len); + if (nread > 0) { + this->_read_vio->ndone += nread; + } + + this->_stream_vc->on_read(); + + return nread; +} + +int64_t +QUICStreamIO::peek(uint8_t *buf, int64_t len) +{ + return this->_read_buffer_reader->memcpy(buf, len) - reinterpret_cast(buf); +} + +void +QUICStreamIO::consume(int64_t len) +{ + this->_read_buffer_reader->consume(len); + this->_stream_vc->on_read(); +} + +bool +QUICStreamIO::is_read_done() +{ + return this->_read_vio->ntodo() == 0; +} + +int64_t +QUICStreamIO::write(const uint8_t *buf, int64_t len) +{ + SCOPED_MUTEX_LOCK(lock, this->_write_vio->mutex, this_ethread()); + + int64_t nwritten = this->_write_buffer->write(buf, len); + if (nwritten > 0) { + this->_nwritten += nwritten; + } + + return len; +} + +int64_t +QUICStreamIO::write(IOBufferReader *r, int64_t len) +{ + SCOPED_MUTEX_LOCK(lock, this->_write_vio->mutex, this_ethread()); + + int64_t bytes_avail = this->_write_buffer->write_avail(); + + if (bytes_avail > 0) { + if (is_debug_tag_set(tag_stream_io)) { + if (this->_write_vio->nbytes == INT64_MAX) { + QUICStreamIODebug("nbytes=- ndone=%" PRId64 " write_avail=%" PRId64 " write_len=%" PRId64, this->_write_vio->ndone, + bytes_avail, len); + } else { + QUICStreamIODebug("nbytes=%" PRId64 " ndone=%" PRId64 " write_avail=%" PRId64 " write_len=%" PRId64, + this->_write_vio->nbytes, this->_write_vio->ndone, bytes_avail, len); + } + } + + int64_t bytes_len = std::min(bytes_avail, len); + int64_t nwritten = this->_write_buffer->write(r, bytes_len); + + if (nwritten > 0) { + this->_nwritten += nwritten; + } + + return nwritten; + } else { + return 0; + } +} + +// TODO: Similar to other "write" apis, but do not copy. +int64_t +QUICStreamIO::write(IOBufferBlock *b) +{ + ink_assert(!"not implemented yet"); + return 0; +} + +void +QUICStreamIO::write_done() +{ + this->_write_vio->nbytes = this->_nwritten; +} + +void +QUICStreamIO::read_reenable() +{ + return this->_read_vio->reenable(); +} + +void +QUICStreamIO::write_reenable() +{ + return this->_write_vio->reenable(); +} + +// +// QUICApplication +// +QUICApplication::QUICApplication(QUICConnection *qc) : Continuation(new_ProxyMutex()) +{ + this->_qc = qc; +} + +QUICApplication::~QUICApplication() +{ + for (auto const &kv : this->_stream_map) { + delete kv.second; + } +} + +// @brief Bind stream and application +void +QUICApplication::set_stream(QUICStreamVConnection *stream_vc, QUICStreamIO *stream_io) +{ + if (stream_io == nullptr) { + stream_io = new QUICStreamIO(this, stream_vc); + } + this->_stream_map.insert(std::make_pair(stream_vc->id(), stream_io)); +} + +// @brief Bind stream and application +void +QUICApplication::set_stream(QUICStreamIO *stream_io) +{ + this->_stream_map.insert(std::make_pair(stream_io->stream_id(), stream_io)); +} + +bool +QUICApplication::is_stream_set(QUICStreamVConnection *stream) +{ + auto result = this->_stream_map.find(stream->id()); + + return result != this->_stream_map.end(); +} + +void +QUICApplication::reenable(QUICStreamVConnection *stream) +{ + QUICStreamIO *stream_io = this->_find_stream_io(stream->id()); + if (stream_io) { + stream_io->read_reenable(); + stream_io->write_reenable(); + } else { + Debug(tag_app, "[%s] Unknown Stream id=%" PRIx64, this->_qc->cids().data(), stream->id()); + } + + return; +} + +void +QUICApplication::unset_stream(QUICStreamVConnection *stream) +{ + QUICStreamIO *stream_io = this->_find_stream_io(stream->id()); + if (stream_io) { + this->_stream_map.erase(stream->id()); + } +} + +QUICStreamIO * +QUICApplication::_find_stream_io(QUICStreamId id) +{ + auto result = this->_stream_map.find(id); + + if (result == this->_stream_map.end()) { + return nullptr; + } else { + return result->second; + } +} + +QUICStreamIO * +QUICApplication::_find_stream_io(VIO *vio) +{ + if (vio == nullptr) { + return nullptr; + } + + QUICStream *stream = dynamic_cast(vio->vc_server); + if (stream == nullptr) { + return nullptr; + } + + return this->_find_stream_io(stream->id()); +} diff --git a/iocore/net/quic/QUICApplication.h b/iocore/net/quic/QUICApplication.h new file mode 100644 index 00000000000..dd6d52489bd --- /dev/null +++ b/iocore/net/quic/QUICApplication.h @@ -0,0 +1,100 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include "I_EventSystem.h" +#include "I_IOBuffer.h" +#include "QUICTypes.h" +#include "QUICConnection.h" +#include "QUICStream.h" + +class QUICApplication; + +/** + @brief QUICStream I/O Interface for QUICApplication + */ +class QUICStreamIO +{ +public: + QUICStreamIO(QUICApplication *app, QUICStreamVConnection *stream); + virtual ~QUICStreamIO(); + + uint32_t stream_id() const; + bool is_bidirectional() const; + + int64_t read(uint8_t *buf, int64_t len); + int64_t peek(uint8_t *buf, int64_t len); + void consume(int64_t len); + bool is_read_done(); + virtual void read_reenable(); + + int64_t write(const uint8_t *buf, int64_t len); + int64_t write(IOBufferReader *r, int64_t len); + int64_t write(IOBufferBlock *b); + void write_done(); + virtual void write_reenable(); + +protected: + MIOBuffer *_read_buffer = nullptr; + MIOBuffer *_write_buffer = nullptr; + + IOBufferReader *_read_buffer_reader = nullptr; + IOBufferReader *_write_buffer_reader = nullptr; + +private: + QUICStreamVConnection *_stream_vc = nullptr; + + VIO *_read_vio = nullptr; + VIO *_write_vio = nullptr; + + // Track how much data is written to _write_vio. When total size of data become clear, + // set it to _write_vio.nbytes. + uint64_t _nwritten = 0; +}; + +/** + * @brief Abstract QUIC Application Class + * @detail Every quic application must inherits this class + */ +class QUICApplication : public Continuation +{ +public: + QUICApplication(QUICConnection *qc); + virtual ~QUICApplication(); + + void set_stream(QUICStreamVConnection *stream_vc, QUICStreamIO *stream_io = nullptr); + void set_stream(QUICStreamIO *stream_io); + bool is_stream_set(QUICStreamVConnection *stream_vc); + void reenable(QUICStreamVConnection *stream_vc); + void unset_stream(QUICStreamVConnection *stream_vc); + +protected: + QUICStreamIO *_find_stream_io(QUICStreamId id); + QUICStreamIO *_find_stream_io(VIO *vio); + + QUICConnection *_qc = nullptr; + +private: + std::map _stream_map; +}; diff --git a/iocore/net/quic/QUICApplicationMap.cc b/iocore/net/quic/QUICApplicationMap.cc new file mode 100644 index 00000000000..ff95226930c --- /dev/null +++ b/iocore/net/quic/QUICApplicationMap.cc @@ -0,0 +1,47 @@ +/** @file + * + * A brief file description + * + * @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 + +QUICApplication * +QUICApplicationMap::get(QUICStreamId id) +{ + auto it = this->_map.find(id); + if (it == this->_map.end()) { + return this->_default_app; + } else { + return it->second; + } +} + +void +QUICApplicationMap::set(QUICStreamId id, QUICApplication *app) +{ + this->_map[id] = app; +} + +void +QUICApplicationMap::set_default(QUICApplication *app) +{ + this->_default_app = app; +} diff --git a/iocore/net/quic/QUICApplicationMap.h b/iocore/net/quic/QUICApplicationMap.h new file mode 100644 index 00000000000..a657adf6e41 --- /dev/null +++ b/iocore/net/quic/QUICApplicationMap.h @@ -0,0 +1,40 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include "QUICTypes.h" +#include "QUICApplication.h" +#include + +class QUICApplicationMap +{ +public: + void set(QUICStreamId id, QUICApplication *app); + void set_default(QUICApplication *app); + QUICApplication *get(QUICStreamId id); + +private: + std::map _map; + QUICApplication *_default_app = nullptr; +}; diff --git a/iocore/net/quic/QUICBidirectionalStream.cc b/iocore/net/quic/QUICBidirectionalStream.cc new file mode 100644 index 00000000000..bbd98d9652a --- /dev/null +++ b/iocore/net/quic/QUICBidirectionalStream.cc @@ -0,0 +1,569 @@ +/** @file + * + * A brief file description + * + * @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 "QUICBidirectionalStream.h" + +// +// QUICBidirectionalStream +// +QUICBidirectionalStream::QUICBidirectionalStream(QUICRTTProvider *rtt_provider, QUICConnectionInfoProvider *cinfo, QUICStreamId sid, + uint64_t recv_max_stream_data, uint64_t send_max_stream_data) + : QUICStreamVConnection(cinfo, sid), + _remote_flow_controller(send_max_stream_data, _id), + _local_flow_controller(rtt_provider, recv_max_stream_data, _id), + _flow_control_buffer_size(recv_max_stream_data), + _state(nullptr, &this->_progress_vio, this, nullptr) +{ + SET_HANDLER(&QUICBidirectionalStream::state_stream_open); + + QUICStreamFCDebug("[LOCAL] %" PRIu64 "/%" PRIu64, this->_local_flow_controller.current_offset(), + this->_local_flow_controller.current_limit()); + QUICStreamFCDebug("[REMOTE] %" PRIu64 "/%" PRIu64, this->_remote_flow_controller.current_offset(), + this->_remote_flow_controller.current_limit()); +} + +int +QUICBidirectionalStream::state_stream_open(int event, void *data) +{ + QUICVStreamDebug("%s (%d)", get_vc_event_name(event), event); + QUICErrorUPtr error = nullptr; + + switch (event) { + case VC_EVENT_READ_READY: + case VC_EVENT_READ_COMPLETE: { + int64_t len = this->_process_read_vio(); + if (len > 0) { + this->_signal_read_event(); + } + + break; + } + case VC_EVENT_WRITE_READY: + case VC_EVENT_WRITE_COMPLETE: { + int64_t len = this->_process_write_vio(); + if (len > 0) { + this->_signal_write_event(); + } + + break; + } + case VC_EVENT_EOS: + case VC_EVENT_ERROR: + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: { + // TODO + ink_assert(false); + break; + } + default: + QUICStreamDebug("unknown event"); + ink_assert(false); + } + + // FIXME error is always nullptr + if (error != nullptr) { + if (error->cls == QUICErrorClass::TRANSPORT) { + QUICStreamDebug("QUICError: %s (%u), %s (0x%x)", QUICDebugNames::error_class(error->cls), + static_cast(error->cls), QUICDebugNames::error_code(error->code), + static_cast(error->code)); + } else { + QUICStreamDebug("QUICError: %s (%u), APPLICATION ERROR (0x%x)", QUICDebugNames::error_class(error->cls), + static_cast(error->cls), static_cast(error->code)); + } + if (dynamic_cast(error.get()) != nullptr) { + // Stream Error + QUICStreamErrorUPtr serror = QUICStreamErrorUPtr(static_cast(error.get())); + this->reset(std::move(serror)); + } else { + // Connection Error + // TODO Close connection (Does this really happen?) + } + } + + return EVENT_DONE; +} + +int +QUICBidirectionalStream::state_stream_closed(int event, void *data) +{ + QUICVStreamDebug("%s (%d)", get_vc_event_name(event), event); + + switch (event) { + case VC_EVENT_READ_READY: + case VC_EVENT_READ_COMPLETE: { + // ignore + break; + } + case VC_EVENT_WRITE_READY: + case VC_EVENT_WRITE_COMPLETE: { + // ignore + break; + } + case VC_EVENT_EOS: + case VC_EVENT_ERROR: + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: { + // TODO + ink_assert(false); + break; + } + default: + ink_assert(false); + } + + return EVENT_DONE; +} + +bool +QUICBidirectionalStream::is_transfer_goal_set() const +{ + return this->_received_stream_frame_buffer.is_transfer_goal_set(); +} + +uint64_t +QUICBidirectionalStream::transfer_progress() const +{ + return this->_received_stream_frame_buffer.transfer_progress(); +} + +uint64_t +QUICBidirectionalStream::transfer_goal() const +{ + return this->_received_stream_frame_buffer.transfer_goal(); +} + +bool +QUICBidirectionalStream::is_cancelled() const +{ + return this->_is_reset_complete; +} + +/** + * @brief Receive STREAM frame + * @detail When receive STREAM frame, reorder frames and write to buffer of read_vio. + * If the reordering or writting operation is heavy, split out them to read function, + * which is called by application via do_io_read() or reenable(). + */ +QUICConnectionErrorUPtr +QUICBidirectionalStream::recv(const QUICStreamFrame &frame) +{ + ink_assert(_id == frame.stream_id()); + ink_assert(this->_read_vio.op == VIO::READ); + + // Check stream state - Do this first before accept the frame + if (!this->_state.is_allowed_to_receive(frame)) { + QUICStreamDebug("Canceled receiving %s frame due to the stream state", QUICDebugNames::frame_type(frame.type())); + return std::make_unique(QUICTransErrorCode::STREAM_STATE_ERROR); + } + + // Flow Control - Even if it's allowed to receive on the state, it may exceed the limit + int ret = this->_local_flow_controller.update(frame.offset() + frame.data_length()); + QUICStreamFCDebug("[LOCAL] %" PRIu64 "/%" PRIu64, this->_local_flow_controller.current_offset(), + this->_local_flow_controller.current_limit()); + if (ret != 0) { + return std::make_unique(QUICTransErrorCode::FLOW_CONTROL_ERROR); + } + + // Make a copy and insert it into the receive buffer because the frame passed is temporal + QUICFrame *cloned = new QUICStreamFrame(frame); + QUICConnectionErrorUPtr error = this->_received_stream_frame_buffer.insert(cloned); + if (error != nullptr) { + this->_received_stream_frame_buffer.clear(); + return error; + } + + auto new_frame = this->_received_stream_frame_buffer.pop(); + const QUICStreamFrame *stream_frame = nullptr; + uint64_t last_offset = 0; + uint64_t last_length = 0; + + while (new_frame != nullptr) { + stream_frame = static_cast(new_frame); + last_offset = stream_frame->offset(); + last_length = stream_frame->data_length(); + + this->_write_to_read_vio(stream_frame->offset(), reinterpret_cast(stream_frame->data()->start()), + stream_frame->data_length(), stream_frame->has_fin_flag()); + this->_state.update_with_receiving_frame(*new_frame); + + delete new_frame; + new_frame = this->_received_stream_frame_buffer.pop(); + } + + // Forward limit of local flow controller with the largest reordered stream frame + if (stream_frame) { + this->_reordered_bytes = last_offset + last_length; + this->_local_flow_controller.forward_limit(this->_reordered_bytes + this->_flow_control_buffer_size); + QUICStreamFCDebug("[LOCAL] %" PRIu64 "/%" PRIu64, this->_local_flow_controller.current_offset(), + this->_local_flow_controller.current_limit()); + } + + this->_signal_read_event(); + + return nullptr; +} + +QUICConnectionErrorUPtr +QUICBidirectionalStream::recv(const QUICMaxStreamDataFrame &frame) +{ + this->_remote_flow_controller.forward_limit(frame.maximum_stream_data()); + QUICStreamFCDebug("[REMOTE] %" PRIu64 "/%" PRIu64, this->_remote_flow_controller.current_offset(), + this->_remote_flow_controller.current_limit()); + + int64_t len = this->_process_write_vio(); + if (len > 0) { + this->_signal_write_event(); + } + + return nullptr; +} + +QUICConnectionErrorUPtr +QUICBidirectionalStream::recv(const QUICStreamDataBlockedFrame &frame) +{ + // STREAM_DATA_BLOCKED frames are for debugging. Nothing to do here. + QUICStreamFCDebug("[REMOTE] blocked %" PRIu64, frame.offset()); + return nullptr; +} + +QUICConnectionErrorUPtr +QUICBidirectionalStream::recv(const QUICStopSendingFrame &frame) +{ + this->_state.update_with_receiving_frame(frame); + this->_reset_reason = QUICStreamErrorUPtr(new QUICStreamError(this, QUIC_APP_ERROR_CODE_STOPPING)); + // We received and processed STOP_SENDING frame, so return NO_ERROR here + return nullptr; +} + +QUICConnectionErrorUPtr +QUICBidirectionalStream::recv(const QUICRstStreamFrame &frame) +{ + this->_state.update_with_receiving_frame(frame); + this->_signal_read_eos_event(); + return nullptr; +} + +// this->_read_vio.nbytes should be INT64_MAX until receive FIN flag +VIO * +QUICBidirectionalStream::do_io_read(Continuation *c, int64_t nbytes, MIOBuffer *buf) +{ + if (buf) { + this->_read_vio.buffer.writer_for(buf); + } else { + this->_read_vio.buffer.clear(); + } + + this->_read_vio.mutex = c ? c->mutex : this->mutex; + this->_read_vio.cont = c; + this->_read_vio.nbytes = nbytes; + this->_read_vio.ndone = 0; + this->_read_vio.vc_server = this; + this->_read_vio.op = VIO::READ; + + this->_process_read_vio(); + this->_send_tracked_event(this->_read_event, VC_EVENT_READ_READY, &this->_read_vio); + + return &this->_read_vio; +} + +VIO * +QUICBidirectionalStream::do_io_write(Continuation *c, int64_t nbytes, IOBufferReader *buf, bool owner) +{ + if (buf) { + this->_write_vio.buffer.reader_for(buf); + } else { + this->_write_vio.buffer.clear(); + } + + this->_write_vio.mutex = c ? c->mutex : this->mutex; + this->_write_vio.cont = c; + this->_write_vio.nbytes = nbytes; + this->_write_vio.ndone = 0; + this->_write_vio.vc_server = this; + this->_write_vio.op = VIO::WRITE; + + this->_process_write_vio(); + this->_send_tracked_event(this->_write_event, VC_EVENT_WRITE_READY, &this->_write_vio); + + return &this->_write_vio; +} + +void +QUICBidirectionalStream::do_io_close(int lerrno) +{ + SET_HANDLER(&QUICBidirectionalStream::state_stream_closed); + + this->_read_vio.buffer.clear(); + this->_read_vio.nbytes = 0; + this->_read_vio.op = VIO::NONE; + this->_read_vio.cont = nullptr; + + this->_write_vio.buffer.clear(); + this->_write_vio.nbytes = 0; + this->_write_vio.op = VIO::NONE; + this->_write_vio.cont = nullptr; +} + +void +QUICBidirectionalStream::do_io_shutdown(ShutdownHowTo_t howto) +{ + ink_assert(false); // unimplemented yet + return; +} + +void +QUICBidirectionalStream::reenable(VIO *vio) +{ + if (vio->op == VIO::READ) { + QUICVStreamDebug("read_vio reenabled"); + + int64_t len = this->_process_read_vio(); + if (len > 0) { + this->_signal_read_event(); + } + } else if (vio->op == VIO::WRITE) { + QUICVStreamDebug("write_vio reenabled"); + + int64_t len = this->_process_write_vio(); + if (len > 0) { + this->_signal_write_event(); + } + } +} + +bool +QUICBidirectionalStream::will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) +{ + return this->_local_flow_controller.will_generate_frame(level, timestamp) || !this->is_retransmited_frame_queue_empty() || + this->_write_vio.get_reader()->is_read_avail_more_than(0); +} + +QUICFrame * +QUICBidirectionalStream::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, + uint16_t maximum_frame_size, ink_hrtime timestamp) +{ + SCOPED_MUTEX_LOCK(lock, this->_write_vio.mutex, this_ethread()); + + QUICFrame *frame = this->create_retransmitted_frame(buf, level, maximum_frame_size, this->_issue_frame_id(), this); + if (frame != nullptr) { + ink_assert(frame->type() == QUICFrameType::STREAM); + this->_records_stream_frame(level, *static_cast(frame)); + return frame; + } + + // RESET_STREAM + if (this->_reset_reason && !this->_is_reset_sent) { + frame = QUICFrameFactory::create_rst_stream_frame(buf, *this->_reset_reason, this->_issue_frame_id(), this); + this->_records_rst_stream_frame(level, *static_cast(frame)); + this->_state.update_with_sending_frame(*frame); + this->_is_reset_sent = true; + return frame; + } + + // STOP_SENDING + if (this->_stop_sending_reason && !this->_is_stop_sending_sent) { + frame = + QUICFrameFactory::create_stop_sending_frame(buf, this->id(), this->_stop_sending_reason->code, this->_issue_frame_id(), this); + this->_records_stop_sending_frame(level, *static_cast(frame)); + this->_state.update_with_sending_frame(*frame); + this->_is_stop_sending_sent = true; + return frame; + } + + // MAX_STREAM_DATA + frame = this->_local_flow_controller.generate_frame(buf, level, UINT16_MAX, maximum_frame_size, timestamp); + if (frame) { + return frame; + } + + if (!this->_state.is_allowed_to_send(QUICFrameType::STREAM)) { + return frame; + } + + uint64_t maximum_data_size = 0; + if (maximum_frame_size <= MAX_STREAM_FRAME_OVERHEAD) { + return frame; + } + maximum_data_size = maximum_frame_size - MAX_STREAM_FRAME_OVERHEAD; + + bool pure_fin = false; + bool fin = false; + if ((this->_write_vio.nbytes != 0 || this->_write_vio.nbytes != INT64_MAX) && + this->_write_vio.nbytes == static_cast(this->_send_offset)) { + // Pure FIN stream should be sent regardless status of remote flow controller, because the length is zero. + pure_fin = true; + fin = true; + } + + uint64_t len = 0; + IOBufferReader *reader = this->_write_vio.get_reader(); + if (!pure_fin) { + uint64_t data_len = reader->block_read_avail(); + if (data_len == 0) { + return frame; + } + + // Check Connection/Stream level credit only if the generating STREAM frame is not pure fin + uint64_t stream_credit = this->_remote_flow_controller.credit(); + if (stream_credit == 0) { + // STREAM_DATA_BLOCKED + frame = this->_remote_flow_controller.generate_frame(buf, level, UINT16_MAX, maximum_frame_size, timestamp); + return frame; + } + + if (connection_credit == 0) { + // BLOCKED - BLOCKED frame will be sent by connection level remote flow controller + return frame; + } + + len = std::min(data_len, std::min(maximum_data_size, std::min(stream_credit, connection_credit))); + + // data_len, maximum_data_size, stream_credit and connection_credit are already checked they're larger than 0 + ink_assert(len != 0); + + if (this->_write_vio.nbytes == static_cast(this->_send_offset + len)) { + fin = true; + } + } + + Ptr block = make_ptr(reader->get_current_block()->clone()); + block->consume(reader->start_offset); + block->_end = std::min(block->start() + len, block->_buf_end); + ink_assert(static_cast(block->read_avail()) == len); + + // STREAM - Pure FIN or data length is lager than 0 + // FIXME has_length_flag and has_offset_flag should be configurable + frame = QUICFrameFactory::create_stream_frame(buf, block, this->_id, this->_send_offset, fin, true, true, this->_issue_frame_id(), + this); + if (!this->_state.is_allowed_to_send(*frame)) { + QUICStreamDebug("Canceled sending %s frame due to the stream state", QUICDebugNames::frame_type(frame->type())); + return frame; + } + + if (!pure_fin) { + int ret = this->_remote_flow_controller.update(this->_send_offset + len); + // We cannot cancel sending the frame after updating the flow controller + + // Calling update always success, because len is always less than stream_credit + ink_assert(ret == 0); + + QUICStreamFCDebug("[REMOTE] %" PRIu64 "/%" PRIu64, this->_remote_flow_controller.current_offset(), + this->_remote_flow_controller.current_limit()); + if (this->_remote_flow_controller.current_offset() == this->_remote_flow_controller.current_limit()) { + QUICStreamDebug("Flow Controller will block sending a STREAM frame"); + } + + reader->consume(len); + this->_send_offset += len; + this->_write_vio.ndone += len; + } + this->_records_stream_frame(level, *static_cast(frame)); + + this->_signal_write_event(); + this->_state.update_with_sending_frame(*frame); + + return frame; +} + +void +QUICBidirectionalStream::_on_frame_acked(QUICFrameInformationUPtr &info) +{ + StreamFrameInfo *frame_info = nullptr; + switch (info->type) { + case QUICFrameType::RESET_STREAM: + this->_is_reset_complete = true; + break; + case QUICFrameType::STREAM: + frame_info = reinterpret_cast(info->data); + frame_info->block = nullptr; + if (false) { + this->_is_transfer_complete = true; + } + break; + case QUICFrameType::STOP_SENDING: + default: + break; + } + + this->_state.update_on_ack(); +} + +void +QUICBidirectionalStream::_on_frame_lost(QUICFrameInformationUPtr &info) +{ + switch (info->type) { + case QUICFrameType::RESET_STREAM: + // [draft-16] 13.2. Retransmission of Information + // Cancellation of stream transmission, as carried in a RESET_STREAM + // frame, is sent until acknowledged or until all stream data is + // acknowledged by the peer (that is, either the "Reset Recvd" or + // "Data Recvd" state is reached on the send stream). The content of + // a RESET_STREAM frame MUST NOT change when it is sent again. + this->_is_reset_sent = false; + break; + case QUICFrameType::STREAM: + this->save_frame_info(std::move(info)); + break; + case QUICFrameType::STOP_SENDING: + this->_is_stop_sending_sent = false; + break; + default: + break; + } +} + +void +QUICBidirectionalStream::stop_sending(QUICStreamErrorUPtr error) +{ + this->_stop_sending_reason = std::move(error); +} + +void +QUICBidirectionalStream::reset(QUICStreamErrorUPtr error) +{ + this->_reset_reason = std::move(error); +} + +void +QUICBidirectionalStream::on_read() +{ + this->_state.update_on_read(); +} + +void +QUICBidirectionalStream::on_eos() +{ + this->_state.update_on_eos(); +} + +QUICOffset +QUICBidirectionalStream::largest_offset_received() const +{ + return this->_local_flow_controller.current_offset(); +} + +QUICOffset +QUICBidirectionalStream::largest_offset_sent() const +{ + return this->_remote_flow_controller.current_offset(); +} diff --git a/iocore/net/quic/QUICBidirectionalStream.h b/iocore/net/quic/QUICBidirectionalStream.h new file mode 100644 index 00000000000..fcbd5d7140b --- /dev/null +++ b/iocore/net/quic/QUICBidirectionalStream.h @@ -0,0 +1,107 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include "QUICStream.h" + +class QUICBidirectionalStream : public QUICStreamVConnection, public QUICTransferProgressProvider +{ +public: + QUICBidirectionalStream(QUICRTTProvider *rtt_provider, QUICConnectionInfoProvider *cinfo, QUICStreamId sid, + uint64_t recv_max_stream_data, uint64_t send_max_stream_data); + QUICBidirectionalStream() + : QUICStreamVConnection(), + _remote_flow_controller(0, 0), + _local_flow_controller(nullptr, 0, 0), + _state(nullptr, nullptr, nullptr, nullptr) + { + } + + ~QUICBidirectionalStream() {} + + int state_stream_open(int event, void *data); + int state_stream_closed(int event, void *data); + + // QUICFrameGenerator + bool will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) override; + QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size, + ink_hrtime timestamp) override; + + virtual QUICConnectionErrorUPtr recv(const QUICStreamFrame &frame) override; + virtual QUICConnectionErrorUPtr recv(const QUICMaxStreamDataFrame &frame) override; + virtual QUICConnectionErrorUPtr recv(const QUICStreamDataBlockedFrame &frame) override; + virtual QUICConnectionErrorUPtr recv(const QUICStopSendingFrame &frame) override; + virtual QUICConnectionErrorUPtr recv(const QUICRstStreamFrame &frame) override; + + // Implement VConnection Interface. + VIO *do_io_read(Continuation *c, int64_t nbytes = INT64_MAX, MIOBuffer *buf = 0) override; + VIO *do_io_write(Continuation *c = nullptr, int64_t nbytes = INT64_MAX, IOBufferReader *buf = 0, bool owner = false) override; + void do_io_close(int lerrno = -1) override; + void do_io_shutdown(ShutdownHowTo_t howto) override; + void reenable(VIO *vio) override; + + void stop_sending(QUICStreamErrorUPtr error) override; + void reset(QUICStreamErrorUPtr error) override; + + // QUICTransferProgressProvider + bool is_transfer_goal_set() const override; + uint64_t transfer_progress() const override; + uint64_t transfer_goal() const override; + bool is_cancelled() const override; + + /* + * QUICApplication need to call one of these functions when it process VC_EVENT_* + */ + virtual void on_read() override; + virtual void on_eos() override; + + QUICOffset largest_offset_received() const override; + QUICOffset largest_offset_sent() const override; + +private: + QUICStreamErrorUPtr _reset_reason = nullptr; + bool _is_reset_sent = false; + QUICStreamErrorUPtr _stop_sending_reason = nullptr; + bool _is_stop_sending_sent = false; + + bool _is_transfer_complete = false; + bool _is_reset_complete = false; + + QUICTransferProgressProviderVIO _progress_vio = {this->_write_vio}; + + QUICRemoteStreamFlowController _remote_flow_controller; + QUICLocalStreamFlowController _local_flow_controller; + uint64_t _flow_control_buffer_size = 1024; + + // Fragments of received STREAM frame (offset is unmatched) + // TODO: Consider to replace with ts/RbTree.h or other data structure + QUICIncomingStreamFrameBuffer _received_stream_frame_buffer; + + // FIXME Unidirectional streams should use either ReceiveStreamState or SendStreamState + QUICBidirectionalStreamStateMachine _state; + + // QUICFrameGenerator + void _on_frame_acked(QUICFrameInformationUPtr &info) override; + void _on_frame_lost(QUICFrameInformationUPtr &info) override; +}; diff --git a/iocore/net/quic/QUICConfig.cc b/iocore/net/quic/QUICConfig.cc new file mode 100644 index 00000000000..600cef8e75f --- /dev/null +++ b/iocore/net/quic/QUICConfig.cc @@ -0,0 +1,474 @@ +/** @file + * + * A brief file description + * + * @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 "QUICConfig.h" + +#include + +#include + +#include "P_SSLConfig.h" + +#include "QUICGlobals.h" +#include "QUICTransportParameters.h" + +int QUICConfig::_config_id = 0; +int QUICConfigParams::_connection_table_size = 65521; + +SSL_CTX * +quic_new_ssl_ctx() +{ + SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_method()); + + SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_3_VERSION); + SSL_CTX_set_max_proto_version(ssl_ctx, TLS1_3_VERSION); + +#ifndef OPENSSL_IS_BORINGSSL + // FIXME: OpenSSL (1.1.1-alpha) enable this option by default. But this shoule be removed when OpenSSL disable this by default. + SSL_CTX_clear_options(ssl_ctx, SSL_OP_ENABLE_MIDDLEBOX_COMPAT); + + SSL_CTX_set_max_early_data(ssl_ctx, UINT32_C(0xFFFFFFFF)); + + SSL_CTX_add_custom_ext(ssl_ctx, QUICTransportParametersHandler::TRANSPORT_PARAMETER_ID, + SSL_EXT_TLS_ONLY | SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_3_ENCRYPTED_EXTENSIONS, + &QUICTransportParametersHandler::add, &QUICTransportParametersHandler::free, nullptr, + &QUICTransportParametersHandler::parse, nullptr); +#else + // QUIC Transport Parameters are accesible with SSL_set_quic_transport_params and SSL_get_peer_quic_transport_params +#endif + +#ifdef SSL_MODE_QUIC_HACK + // tatsuhiro-t's custom OpenSSL for QUIC draft-13 + // https://github.com/tatsuhiro-t/openssl/tree/quic-draft-13 + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_QUIC_HACK); +#endif + + return ssl_ctx; +} + +/** + ALPN and SNI should be set to SSL object with NETVC_OPTIONS + **/ +static shared_SSL_CTX +quic_init_client_ssl_ctx(const QUICConfigParams *params) +{ + std::unique_ptr ssl_ctx(nullptr, &SSL_CTX_free); + ssl_ctx.reset(quic_new_ssl_ctx()); + +#if defined(SSL_CTX_set1_groups_list) || defined(SSL_CTX_set1_curves_list) + if (params->client_supported_groups() != nullptr) { +#ifdef SSL_CTX_set1_groups_list + if (SSL_CTX_set1_groups_list(ssl_ctx.get(), params->client_supported_groups()) != 1) { +#else + if (SSL_CTX_set1_curves_list(ssl_ctx.get(), params->client_supported_groups()) != 1) { +#endif + Error("SSL_CTX_set1_groups_list failed"); + } + } +#endif + + if (params->client_session_file() != nullptr) { + SSL_CTX_set_session_cache_mode(ssl_ctx.get(), SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL_STORE); + SSL_CTX_sess_set_new_cb(ssl_ctx.get(), QUIC::ssl_client_new_session); + } + +#ifdef SSL_MODE_QUIC_HACK + if (params->client_keylog_file() != nullptr) { + SSL_CTX_set_keylog_callback(ssl_ctx.get(), QUIC::ssl_client_keylog_cb); + } +#endif + + return ssl_ctx; +} + +// +// QUICConfigParams +// +QUICConfigParams::~QUICConfigParams() +{ + this->_server_supported_groups = (char *)ats_free_null(this->_server_supported_groups); + this->_client_supported_groups = (char *)ats_free_null(this->_client_supported_groups); + + SSL_CTX_free(this->_client_ssl_ctx.get()); +}; + +void +QUICConfigParams::initialize() +{ + REC_EstablishStaticConfigInt32U(this->_instance_id, "proxy.config.quic.instance_id"); + REC_EstablishStaticConfigInt32(this->_connection_table_size, "proxy.config.quic.connection_table.size"); + REC_EstablishStaticConfigInt32U(this->_num_alt_connection_ids, "proxy.config.quic.num_alt_connection_ids"); + REC_EstablishStaticConfigInt32U(this->_stateless_retry, "proxy.config.quic.server.stateless_retry_enabled"); + REC_EstablishStaticConfigInt32U(this->_vn_exercise_enabled, "proxy.config.quic.client.vn_exercise_enabled"); + REC_EstablishStaticConfigInt32U(this->_cm_exercise_enabled, "proxy.config.quic.client.cm_exercise_enabled"); + + REC_ReadConfigStringAlloc(this->_server_supported_groups, "proxy.config.quic.server.supported_groups"); + REC_ReadConfigStringAlloc(this->_client_supported_groups, "proxy.config.quic.client.supported_groups"); + REC_ReadConfigStringAlloc(this->_client_session_file, "proxy.config.quic.client.session_file"); + REC_ReadConfigStringAlloc(this->_client_keylog_file, "proxy.config.quic.client.keylog_file"); + + // Transport Parameters + REC_EstablishStaticConfigInt32U(this->_no_activity_timeout_in, "proxy.config.quic.no_activity_timeout_in"); + REC_EstablishStaticConfigInt32U(this->_no_activity_timeout_out, "proxy.config.quic.no_activity_timeout_out"); + REC_ReadConfigStringAlloc(this->_preferred_address_ipv4, "proxy.config.quic.preferred_address_ipv4"); + if (this->_preferred_address_ipv4) { + ats_ip_pton(this->_preferred_address_ipv4, &this->_preferred_endpoint_ipv4); + } + REC_ReadConfigStringAlloc(this->_preferred_address_ipv6, "proxy.config.quic.preferred_address_ipv6"); + if (this->_preferred_address_ipv6) { + ats_ip_pton(this->_preferred_address_ipv6, &this->_preferred_endpoint_ipv6); + } + REC_EstablishStaticConfigInt32U(this->_initial_max_data_in, "proxy.config.quic.initial_max_data_in"); + REC_EstablishStaticConfigInt32U(this->_initial_max_data_out, "proxy.config.quic.initial_max_data_out"); + REC_EstablishStaticConfigInt32U(this->_initial_max_stream_data_bidi_local_in, + "proxy.config.quic.initial_max_stream_data_bidi_local_in"); + REC_EstablishStaticConfigInt32U(this->_initial_max_stream_data_bidi_local_out, + "proxy.config.quic.initial_max_stream_data_bidi_local_out"); + REC_EstablishStaticConfigInt32U(this->_initial_max_stream_data_bidi_remote_in, + "proxy.config.quic.initial_max_stream_data_bidi_remote_in"); + REC_EstablishStaticConfigInt32U(this->_initial_max_stream_data_bidi_remote_out, + "proxy.config.quic.initial_max_stream_data_bidi_remote_out"); + REC_EstablishStaticConfigInt32U(this->_initial_max_stream_data_uni_in, "proxy.config.quic.initial_max_stream_data_uni_in"); + REC_EstablishStaticConfigInt32U(this->_initial_max_stream_data_uni_out, "proxy.config.quic.initial_max_stream_data_uni_out"); + REC_EstablishStaticConfigInt32U(this->_initial_max_streams_bidi_in, "proxy.config.quic.initial_max_streams_bidi_in"); + REC_EstablishStaticConfigInt32U(this->_initial_max_streams_bidi_out, "proxy.config.quic.initial_max_streams_bidi_out"); + REC_EstablishStaticConfigInt32U(this->_initial_max_streams_uni_in, "proxy.config.quic.initial_max_streams_uni_in"); + REC_EstablishStaticConfigInt32U(this->_initial_max_streams_uni_out, "proxy.config.quic.initial_max_streams_uni_out"); + REC_EstablishStaticConfigInt32U(this->_ack_delay_exponent_in, "proxy.config.quic.ack_delay_exponent_in"); + REC_EstablishStaticConfigInt32U(this->_ack_delay_exponent_out, "proxy.config.quic.ack_delay_exponent_out"); + REC_EstablishStaticConfigInt32U(this->_max_ack_delay_in, "proxy.config.quic.max_ack_delay_in"); + REC_EstablishStaticConfigInt32U(this->_max_ack_delay_out, "proxy.config.quic.max_ack_delay_out"); + + // Loss Detection + REC_EstablishStaticConfigInt32U(this->_ld_packet_threshold, "proxy.config.quic.loss_detection.packet_threshold"); + REC_EstablishStaticConfigFloat(this->_ld_time_threshold, "proxy.config.quic.loss_detection.time_threshold"); + + uint32_t timeout = 0; + REC_EstablishStaticConfigInt32U(timeout, "proxy.config.quic.loss_detection.granularity"); + this->_ld_granularity = HRTIME_MSECONDS(timeout); + + REC_EstablishStaticConfigInt32U(timeout, "proxy.config.quic.loss_detection.initial_rtt"); + this->_ld_initial_rtt = HRTIME_MSECONDS(timeout); + + // Congestion Control + REC_EstablishStaticConfigInt32U(this->_cc_max_datagram_size, "proxy.config.quic.congestion_control.max_datagram_size"); + REC_EstablishStaticConfigInt32U(this->_cc_initial_window_scale, "proxy.config.quic.congestion_control.initial_window_scale"); + REC_EstablishStaticConfigInt32U(this->_cc_minimum_window_scale, "proxy.config.quic.congestion_control.minimum_window_scale"); + REC_EstablishStaticConfigFloat(this->_cc_loss_reduction_factor, "proxy.config.quic.congestion_control.loss_reduction_factor"); + REC_EstablishStaticConfigInt32U(this->_cc_persistent_congestion_threshold, + "proxy.config.quic.congestion_control.persistent_congestion_threshold"); + + this->_client_ssl_ctx = quic_init_client_ssl_ctx(this); +} + +uint32_t +QUICConfigParams::no_activity_timeout_in() const +{ + return this->_no_activity_timeout_in; +} + +uint32_t +QUICConfigParams::no_activity_timeout_out() const +{ + return this->_no_activity_timeout_out; +} + +const IpEndpoint * +QUICConfigParams::preferred_address_ipv4() const +{ + if (!this->_preferred_address_ipv4) { + return nullptr; + } + + return &this->_preferred_endpoint_ipv4; +} + +const IpEndpoint * +QUICConfigParams::preferred_address_ipv6() const +{ + if (!this->_preferred_address_ipv6) { + return nullptr; + } + + return &this->_preferred_endpoint_ipv6; +} + +uint32_t +QUICConfigParams::instance_id() const +{ + return this->_instance_id; +} + +int +QUICConfigParams::connection_table_size() +{ + return _connection_table_size; +} + +uint32_t +QUICConfigParams::num_alt_connection_ids() const +{ + return this->_num_alt_connection_ids; +} + +uint32_t +QUICConfigParams::stateless_retry() const +{ + return this->_stateless_retry; +} + +uint32_t +QUICConfigParams::vn_exercise_enabled() const +{ + return this->_vn_exercise_enabled; +} + +uint32_t +QUICConfigParams::cm_exercise_enabled() const +{ + return this->_cm_exercise_enabled; +} + +uint32_t +QUICConfigParams::initial_max_data_in() const +{ + return this->_initial_max_data_in; +} + +uint32_t +QUICConfigParams::initial_max_data_out() const +{ + return this->_initial_max_data_out; +} + +uint32_t +QUICConfigParams::initial_max_stream_data_bidi_local_in() const +{ + return this->_initial_max_stream_data_bidi_local_in; +} + +uint32_t +QUICConfigParams::initial_max_stream_data_bidi_local_out() const +{ + return this->_initial_max_stream_data_bidi_local_out; +} + +uint32_t +QUICConfigParams::initial_max_stream_data_bidi_remote_in() const +{ + return this->_initial_max_stream_data_bidi_remote_in; +} + +uint32_t +QUICConfigParams::initial_max_stream_data_bidi_remote_out() const +{ + return this->_initial_max_stream_data_bidi_remote_out; +} + +uint32_t +QUICConfigParams::initial_max_stream_data_uni_in() const +{ + return this->_initial_max_stream_data_uni_in; +} + +uint32_t +QUICConfigParams::initial_max_stream_data_uni_out() const +{ + return this->_initial_max_stream_data_uni_out; +} + +uint64_t +QUICConfigParams::initial_max_streams_bidi_in() const +{ + return this->_initial_max_streams_bidi_in; +} + +uint64_t +QUICConfigParams::initial_max_streams_bidi_out() const +{ + return this->_initial_max_streams_bidi_out; +} + +uint64_t +QUICConfigParams::initial_max_streams_uni_in() const +{ + return this->_initial_max_streams_uni_in; +} + +uint64_t +QUICConfigParams::initial_max_streams_uni_out() const +{ + return this->_initial_max_streams_uni_out; +} + +uint8_t +QUICConfigParams::ack_delay_exponent_in() const +{ + return this->_ack_delay_exponent_in; +} + +uint8_t +QUICConfigParams::ack_delay_exponent_out() const +{ + return this->_ack_delay_exponent_out; +} + +uint8_t +QUICConfigParams::max_ack_delay_in() const +{ + return this->_max_ack_delay_in; +} + +uint8_t +QUICConfigParams::max_ack_delay_out() const +{ + return this->_max_ack_delay_out; +} + +const char * +QUICConfigParams::server_supported_groups() const +{ + return this->_server_supported_groups; +} + +const char * +QUICConfigParams::client_supported_groups() const +{ + return this->_client_supported_groups; +} + +shared_SSL_CTX +QUICConfigParams::client_ssl_ctx() const +{ + return this->_client_ssl_ctx; +} + +uint32_t +QUICConfigParams::ld_packet_threshold() const +{ + return _ld_packet_threshold; +} + +float +QUICConfigParams::ld_time_threshold() const +{ + return _ld_time_threshold; +} + +ink_hrtime +QUICConfigParams::ld_granularity() const +{ + return _ld_granularity; +} + +ink_hrtime +QUICConfigParams::ld_initial_rtt() const +{ + return _ld_initial_rtt; +} + +uint32_t +QUICConfigParams::cc_max_datagram_size() const +{ + return _cc_max_datagram_size; +} + +uint32_t +QUICConfigParams::cc_initial_window() const +{ + // kInitialWindow: Default limit on the initial amount of data in + // flight, in bytes. Taken from [RFC6928]. The RECOMMENDED value is + // the minimum of 10 * kMaxDatagramSize and max(2* kMaxDatagramSize, + // 14600)). + return std::min(_cc_initial_window_scale * _cc_max_datagram_size, + std::max(2 * _cc_max_datagram_size, static_cast(14600))); +} + +uint32_t +QUICConfigParams::cc_minimum_window() const +{ + return _cc_minimum_window_scale * _cc_max_datagram_size; +} + +float +QUICConfigParams::cc_loss_reduction_factor() const +{ + return _cc_loss_reduction_factor; +} + +uint32_t +QUICConfigParams::cc_persistent_congestion_threshold() const +{ + return _cc_persistent_congestion_threshold; +} + +uint8_t +QUICConfigParams::scid_len() +{ + return QUICConfigParams::_scid_len; +} + +const char * +QUICConfigParams::client_session_file() const +{ + return this->_client_session_file; +} + +const char * +QUICConfigParams::client_keylog_file() const +{ + return this->_client_keylog_file; +} + +// +// QUICConfig +// +void +QUICConfig::startup() +{ + reconfigure(); +} + +void +QUICConfig::reconfigure() +{ + QUICConfigParams *params; + params = new QUICConfigParams; + // re-read configuration + params->initialize(); + _config_id = configProcessor.set(_config_id, params); + + QUICConnectionId::SCID_LEN = params->scid_len(); +} + +QUICConfigParams * +QUICConfig::acquire() +{ + return static_cast(configProcessor.get(_config_id)); +} + +void +QUICConfig::release(QUICConfigParams *params) +{ + configProcessor.release(_config_id, params); +} diff --git a/iocore/net/quic/QUICConfig.h b/iocore/net/quic/QUICConfig.h new file mode 100644 index 00000000000..36ff7af9554 --- /dev/null +++ b/iocore/net/quic/QUICConfig.h @@ -0,0 +1,160 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include + +#include "ProxyConfig.h" +#include "P_SSLCertLookup.h" + +class QUICConfigParams : public ConfigInfo +{ +public: + QUICConfigParams(){}; + ~QUICConfigParams(); + + void initialize(); + + uint32_t instance_id() const; + uint32_t num_alt_connection_ids() const; + uint32_t stateless_retry() const; + uint32_t vn_exercise_enabled() const; + uint32_t cm_exercise_enabled() const; + + const char *server_supported_groups() const; + const char *client_supported_groups() const; + const char *client_session_file() const; + const char *client_keylog_file() const; + + shared_SSL_CTX client_ssl_ctx() const; + + // Transport Parameters + uint32_t no_activity_timeout_in() const; + uint32_t no_activity_timeout_out() const; + const IpEndpoint *preferred_address_ipv4() const; + const IpEndpoint *preferred_address_ipv6() const; + uint32_t initial_max_data_in() const; + uint32_t initial_max_data_out() const; + uint32_t initial_max_stream_data_bidi_local_in() const; + uint32_t initial_max_stream_data_bidi_local_out() const; + uint32_t initial_max_stream_data_bidi_remote_in() const; + uint32_t initial_max_stream_data_bidi_remote_out() const; + uint32_t initial_max_stream_data_uni_in() const; + uint32_t initial_max_stream_data_uni_out() const; + uint64_t initial_max_streams_bidi_in() const; + uint64_t initial_max_streams_bidi_out() const; + uint64_t initial_max_streams_uni_in() const; + uint64_t initial_max_streams_uni_out() const; + uint8_t ack_delay_exponent_in() const; + uint8_t ack_delay_exponent_out() const; + uint8_t max_ack_delay_in() const; + uint8_t max_ack_delay_out() const; + + // Loss Detection + uint32_t ld_packet_threshold() const; + float ld_time_threshold() const; + ink_hrtime ld_granularity() const; + ink_hrtime ld_initial_rtt() const; + + // Congestion Control + uint32_t cc_max_datagram_size() const; + uint32_t cc_initial_window() const; + uint32_t cc_minimum_window() const; + float cc_loss_reduction_factor() const; + uint32_t cc_persistent_congestion_threshold() const; + + static int connection_table_size(); + static uint8_t scid_len(); + +private: + static int _connection_table_size; + // TODO: make configurable + static const uint8_t _scid_len = 18; //< Length of Source Connection ID + + uint32_t _instance_id = 0; + uint32_t _num_alt_connection_ids = 0; + uint32_t _stateless_retry = 0; + uint32_t _vn_exercise_enabled = 0; + uint32_t _cm_exercise_enabled = 0; + + char *_server_supported_groups = nullptr; + char *_client_supported_groups = nullptr; + char *_client_session_file = nullptr; + char *_client_keylog_file = nullptr; + + shared_SSL_CTX _client_ssl_ctx = nullptr; + + // Transport Parameters + uint32_t _no_activity_timeout_in = 0; + uint32_t _no_activity_timeout_out = 0; + const char *_preferred_address_ipv4 = nullptr; + const char *_preferred_address_ipv6 = nullptr; + IpEndpoint _preferred_endpoint_ipv4; + IpEndpoint _preferred_endpoint_ipv6; + uint32_t _initial_max_data_in = 0; + uint32_t _initial_max_data_out = 0; + uint32_t _initial_max_stream_data_bidi_local_in = 0; + uint32_t _initial_max_stream_data_bidi_local_out = 0; + uint32_t _initial_max_stream_data_bidi_remote_in = 0; + uint32_t _initial_max_stream_data_bidi_remote_out = 0; + uint32_t _initial_max_stream_data_uni_in = 0; + uint32_t _initial_max_stream_data_uni_out = 0; + uint32_t _initial_max_streams_bidi_in = 0; + uint32_t _initial_max_streams_bidi_out = 0; + uint32_t _initial_max_streams_uni_in = 0; + uint32_t _initial_max_streams_uni_out = 0; + uint32_t _ack_delay_exponent_in = 0; + uint32_t _ack_delay_exponent_out = 0; + uint32_t _max_ack_delay_in = 0; + uint32_t _max_ack_delay_out = 0; + + // [draft-17 recovery] 6.4.1. Constants of interest + uint32_t _ld_packet_threshold = 3; + float _ld_time_threshold = 1.25; + ink_hrtime _ld_granularity = HRTIME_MSECONDS(1); + ink_hrtime _ld_initial_rtt = HRTIME_MSECONDS(100); + + // [draft-11 recovery] 4.7.1. Constants of interest + uint32_t _cc_max_datagram_size = 1200; + uint32_t _cc_initial_window_scale = 10; // Actual initial window size is this value multiplied by the _cc_default_mss + uint32_t _cc_minimum_window_scale = 2; // Actual minimum window size is this value multiplied by the _cc_default_mss + float _cc_loss_reduction_factor = 0.5; + uint32_t _cc_persistent_congestion_threshold = 2; +}; + +class QUICConfig +{ +public: + static void startup(); + static void reconfigure(); + static QUICConfigParams *acquire(); + static void release(QUICConfigParams *params); + + using scoped_config = ConfigProcessor::scoped_config; + +private: + static int _config_id; +}; + +SSL_CTX *quic_new_ssl_ctx(); diff --git a/iocore/net/quic/QUICCongestionController.cc b/iocore/net/quic/QUICCongestionController.cc new file mode 100644 index 00000000000..79d8fd8df18 --- /dev/null +++ b/iocore/net/quic/QUICCongestionController.cc @@ -0,0 +1,242 @@ +/** @file + * + * A brief file description + * + * @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 +#include + +#define QUICCCDebug(fmt, ...) \ + Debug("quic_cc", \ + "[%s] " \ + "window: %" PRIu32 " bytes: %" PRIu32 " ssthresh: %" PRIu32 " " fmt, \ + this->_info->cids().data(), this->_congestion_window, this->_bytes_in_flight, this->_ssthresh, ##__VA_ARGS__) + +#define QUICCCError(fmt, ...) \ + Error("quic_cc", \ + "[%s] " \ + "window: %" PRIu32 " bytes: %" PRIu32 " ssthresh: %" PRIu32 " " fmt, \ + this->_info->cids().data(), this->_congestion_window, this->_bytes_in_flight, this->_ssthresh, ##__VA_ARGS__) + +QUICCongestionController::QUICCongestionController(const QUICRTTProvider &rtt_provider, QUICConnectionInfoProvider *info, + const QUICCCConfig &cc_config) + : _cc_mutex(new_ProxyMutex()), _info(info), _rtt_provider(rtt_provider) +{ + this->_k_max_datagram_size = cc_config.max_datagram_size(); + this->_k_initial_window = cc_config.initial_window(); + this->_k_minimum_window = cc_config.minimum_window(); + this->_k_loss_reduction_factor = cc_config.loss_reduction_factor(); + this->_k_persistent_congestion_threshold = cc_config.persistent_congestion_threshold(); + + this->reset(); +} + +void +QUICCongestionController::on_packet_sent(size_t bytes_sent) +{ + SCOPED_MUTEX_LOCK(lock, this->_cc_mutex, this_ethread()); + this->_bytes_in_flight += bytes_sent; +} + +bool +QUICCongestionController::_in_recovery(ink_hrtime sent_time) +{ + return sent_time <= this->_recovery_start_time; +} + +bool +QUICCongestionController::is_app_limited() +{ + // FIXME : don't known how does app worked here + return false; +} + +void +QUICCongestionController::on_packet_acked(const QUICPacketInfo &acked_packet) +{ + // Remove from bytes_in_flight. + SCOPED_MUTEX_LOCK(lock, this->_cc_mutex, this_ethread()); + this->_bytes_in_flight -= acked_packet.sent_bytes; + if (this->_in_recovery(acked_packet.time_sent)) { + // Do not increase congestion window in recovery period. + return; + } + + if (this->is_app_limited()) { + // Do not increase congestion_window if application + // limited. + return; + } + + if (this->_congestion_window < this->_ssthresh) { + // Slow start. + this->_congestion_window += acked_packet.sent_bytes; + QUICCCDebug("slow start window chaged"); + } else { + // Congestion avoidance. + this->_congestion_window += this->_k_max_datagram_size * acked_packet.sent_bytes / this->_congestion_window; + QUICCCDebug("Congestion avoidance window changed"); + } +} + +// addtional code +// the original one is: +// CongestionEvent(sent_time): +void +QUICCongestionController::_congestion_event(ink_hrtime sent_time) +{ + // Start a new congestion event if the sent time is larger + // than the start time of the previous recovery epoch. + if (!this->_in_recovery(sent_time)) { + this->_recovery_start_time = Thread::get_hrtime(); + this->_congestion_window *= this->_k_loss_reduction_factor; + this->_congestion_window = std::max(this->_congestion_window, this->_k_minimum_window); + this->_ssthresh = this->_congestion_window; + } +} + +// additional code +// the original one is: +// ProcessECN(ack): +void +QUICCongestionController::process_ecn(const QUICPacketInfo &acked_largest_packet, const QUICAckFrame::EcnSection *ecn_section) +{ + // If the ECN-CE counter reported by the peer has increased, + // this could be a new congestion event. + if (ecn_section->ecn_ce_count() > this->_ecn_ce_counter) { + this->_ecn_ce_counter = ecn_section->ecn_ce_count(); + // Start a new congestion event if the last acknowledged + // packet was sent after the start of the previous + // recovery epoch. + this->_congestion_event(acked_largest_packet.time_sent); + } +} + +bool +QUICCongestionController::_in_persistent_congestion(const std::map &lost_packets, + QUICPacketInfo *largest_lost_packet) +{ + ink_hrtime period = this->_rtt_provider.congestion_period(this->_k_persistent_congestion_threshold); + // Determine if all packets in the window before the + // newest lost packet, including the edges, are marked + // lost + return this->_in_window_lost(lost_packets, largest_lost_packet, period); +} + +// additional code +// the original one is: +// OnPacketsLost(lost_packets): +void +QUICCongestionController::on_packets_lost(const std::map &lost_packets) +{ + if (lost_packets.empty()) { + return; + } + + SCOPED_MUTEX_LOCK(lock, this->_cc_mutex, this_ethread()); + // Remove lost packets from bytes_in_flight. + for (auto &lost_packet : lost_packets) { + this->_bytes_in_flight -= lost_packet.second->sent_bytes; + } + QUICPacketInfo *largest_lost_packet = lost_packets.rbegin()->second; + // Start a new recovery epoch if the lost packet is larger + // than the end of the previous recovery epoch. + this->_congestion_event(largest_lost_packet->time_sent); + + // Collapse congestion window if persistent congestion + if (this->_in_persistent_congestion(lost_packets, largest_lost_packet)) { + this->_congestion_window = this->_k_minimum_window; + } +} + +bool +QUICCongestionController::check_credit() const +{ + if (this->_bytes_in_flight >= this->_congestion_window) { + QUICCCDebug("Congestion control pending"); + } + + return this->_bytes_in_flight < this->_congestion_window; +} + +uint32_t +QUICCongestionController::open_window() const +{ + if (this->check_credit()) { + return this->_congestion_window - this->_bytes_in_flight; + } else { + return 0; + } +} + +uint32_t +QUICCongestionController::bytes_in_flight() const +{ + return this->_bytes_in_flight; +} + +uint32_t +QUICCongestionController::congestion_window() const +{ + return this->_congestion_window; +} + +uint32_t +QUICCongestionController::current_ssthresh() const +{ + return this->_ssthresh; +} + +// [draft-17 recovery] 7.9.3. Initialization +void +QUICCongestionController::reset() +{ + SCOPED_MUTEX_LOCK(lock, this->_cc_mutex, this_ethread()); + + this->_bytes_in_flight = 0; + this->_congestion_window = this->_k_initial_window; + this->_recovery_start_time = 0; + this->_ssthresh = UINT32_MAX; +} + +bool +QUICCongestionController::_in_window_lost(const std::map &lost_packets, + QUICPacketInfo *largest_lost_packet, ink_hrtime period) const +{ + // check whether packets are continuous. return true if all continuous packets are in period + QUICPacketNumber next_expected = UINT64_MAX; + for (auto &it : lost_packets) { + if (it.second->time_sent >= largest_lost_packet->time_sent - period) { + if (next_expected == UINT64_MAX) { + next_expected = it.second->packet_number + 1; + continue; + } + + if (next_expected != it.second->packet_number) { + return false; + } + + next_expected = it.second->packet_number + 1; + } + } + + return next_expected == UINT64_MAX ? false : true; +} diff --git a/iocore/net/quic/QUICConnection.h b/iocore/net/quic/QUICConnection.h new file mode 100644 index 00000000000..36cc5fb987e --- /dev/null +++ b/iocore/net/quic/QUICConnection.h @@ -0,0 +1,59 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include "I_EventSystem.h" +#include "I_NetVConnection.h" +#include "QUICFrameHandler.h" + +class QUICApplication; +class QUICStreamManager; +class UDPPacket; +class SSLNextProtocolSet; + +class QUICConnectionInfoProvider +{ +public: + virtual QUICConnectionId peer_connection_id() const = 0; + virtual QUICConnectionId original_connection_id() const = 0; + virtual QUICConnectionId first_connection_id() const = 0; + virtual QUICConnectionId connection_id() const = 0; + virtual std::string_view cids() const = 0; + virtual const QUICFiveTuple five_tuple() const = 0; + + virtual uint32_t pmtu() const = 0; + virtual NetVConnectionContext_t direction() const = 0; + virtual SSLNextProtocolSet *next_protocol_set() const = 0; + virtual bool is_closed() const = 0; + virtual std::string_view negotiated_application_name() const = 0; +}; + +class QUICConnection : public QUICFrameHandler, public QUICConnectionInfoProvider +{ +public: + virtual QUICStreamManager *stream_manager() = 0; + virtual void close(QUICConnectionErrorUPtr error) = 0; + virtual void handle_received_packet(UDPPacket *packeet) = 0; + virtual void ping() = 0; +}; diff --git a/iocore/net/quic/QUICConnectionTable.cc b/iocore/net/quic/QUICConnectionTable.cc new file mode 100644 index 00000000000..b400beb4f06 --- /dev/null +++ b/iocore/net/quic/QUICConnectionTable.cc @@ -0,0 +1,66 @@ +/** @file + + QUICConnectionTable + + @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 "QUICConnectionTable.h" + +QUICConnectionTable::~QUICConnectionTable() +{ + // TODO: clear all values. +} + +QUICConnection * +QUICConnectionTable::insert(QUICConnectionId cid, QUICConnection *connection) +{ + Ptr m = _connections.lock_for_key(cid); + SCOPED_MUTEX_LOCK(lock, m, this_ethread()); + // To check whether the return value is nullptr by caller in case memory leak. + // The return value isn't nullptr, the new value will take up the slot and return old value. + return _connections.insert_entry(cid, connection); +} + +void +QUICConnectionTable::erase(QUICConnectionId cid, QUICConnection *connection) +{ + Ptr m = _connections.lock_for_key(cid); + SCOPED_MUTEX_LOCK(lock, m, this_ethread()); + QUICConnection *ret_connection = _connections.remove_entry(cid); + if (ret_connection) { + ink_assert(ret_connection == connection); + } +} + +QUICConnection * +QUICConnectionTable::erase(QUICConnectionId cid) +{ + Ptr m = _connections.lock_for_key(cid); + SCOPED_MUTEX_LOCK(lock, m, this_ethread()); + return _connections.remove_entry(cid); +} + +QUICConnection * +QUICConnectionTable::lookup(QUICConnectionId cid) +{ + Ptr m = _connections.lock_for_key(cid); + SCOPED_MUTEX_LOCK(lock, m, this_ethread()); + return _connections.lookup_entry(cid); +} diff --git a/iocore/net/quic/QUICConnectionTable.h b/iocore/net/quic/QUICConnectionTable.h new file mode 100644 index 00000000000..4be207a492e --- /dev/null +++ b/iocore/net/quic/QUICConnectionTable.h @@ -0,0 +1,58 @@ +/** @file + * + * QUICConnectionTable + * + * @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. + */ + +#pragma once + +#include "QUICTypes.h" +#include "QUICConnection.h" +#include "tscore/MT_hashtable.h" + +class QUICConnectionTable +{ +public: + QUICConnectionTable(int hash_table_size = 65521) : _connections(hash_table_size) {} + ~QUICConnectionTable(); + /* + * Insert an entry + * + * Return zero if it is the only connection or the first connection from the endpoint. + * Caller is responsible for memory management. + */ + QUICConnection *insert(QUICConnectionId cid, QUICConnection *connection); + + /* + * Remove an entry + * + * Fails if CID is not associated to a specified connection + */ + void erase(QUICConnectionId cid, QUICConnection *connection); + QUICConnection *erase(QUICConnectionId cid); + + /* + * Lookup QUICConnection by cid + */ + QUICConnection *lookup(QUICConnectionId cid); + +private: + MTHashTable _connections; +}; diff --git a/iocore/net/quic/QUICCryptoStream.cc b/iocore/net/quic/QUICCryptoStream.cc new file mode 100644 index 00000000000..f3b702cc487 --- /dev/null +++ b/iocore/net/quic/QUICCryptoStream.cc @@ -0,0 +1,167 @@ +/** @file + * + * A brief file description + * + * @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 "QUICCryptoStream.h" + +constexpr uint32_t MAX_CRYPTO_FRAME_OVERHEAD = 16; + +// +// QUICCryptoStream +// +QUICCryptoStream::QUICCryptoStream() : _received_stream_frame_buffer() +{ + this->_read_buffer = new_MIOBuffer(BUFFER_SIZE_INDEX_8K); + this->_write_buffer = new_MIOBuffer(BUFFER_SIZE_INDEX_8K); + + this->_read_buffer_reader = this->_read_buffer->alloc_reader(); + this->_write_buffer_reader = this->_write_buffer->alloc_reader(); +} + +QUICCryptoStream::~QUICCryptoStream() +{ + // All readers will be deallocated + free_MIOBuffer(this->_read_buffer); + free_MIOBuffer(this->_write_buffer); +} + +/** + * Reset send/recv offset of stream + */ +void +QUICCryptoStream::reset_send_offset() +{ + this->_send_offset = 0; +} + +void +QUICCryptoStream::reset_recv_offset() +{ + this->_received_stream_frame_buffer.clear(); +} + +QUICConnectionErrorUPtr +QUICCryptoStream::recv(const QUICCryptoFrame &frame) +{ + // Make a copy and insert it into the receive buffer because the frame passed is temporal + QUICFrame *cloned = new QUICCryptoFrame(frame); + QUICConnectionErrorUPtr error = this->_received_stream_frame_buffer.insert(cloned); + if (error != nullptr) { + this->_received_stream_frame_buffer.clear(); + return error; + } + + auto new_frame = this->_received_stream_frame_buffer.pop(); + while (new_frame != nullptr) { + const QUICCryptoFrame *crypto_frame = static_cast(new_frame); + + this->_read_buffer->write(reinterpret_cast(crypto_frame->data()->start()), crypto_frame->data_length()); + + delete new_frame; + new_frame = this->_received_stream_frame_buffer.pop(); + } + + return nullptr; +} + +int64_t +QUICCryptoStream::read_avail() +{ + return this->_read_buffer_reader->read_avail(); +} + +int64_t +QUICCryptoStream::read(uint8_t *buf, int64_t len) +{ + return this->_read_buffer_reader->read(buf, len); +} + +int64_t +QUICCryptoStream::write(const uint8_t *buf, int64_t len) +{ + return this->_write_buffer->write(buf, len); +} + +bool +QUICCryptoStream::will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) +{ + return this->_write_buffer_reader->is_read_avail_more_than(0) || !this->is_retransmited_frame_queue_empty(); +} + +/** + * @param connection_credit This is not used. Because CRYPTO frame is not flow-controlled + */ +QUICFrame * +QUICCryptoStream::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t /* connection_credit */, + uint16_t maximum_frame_size, ink_hrtime timestamp) +{ + QUICConnectionErrorUPtr error = nullptr; + + if (this->_reset_reason) { + return QUICFrameFactory::create_rst_stream_frame(buf, *this->_reset_reason); + } + + QUICFrame *frame = this->create_retransmitted_frame(buf, level, maximum_frame_size, this->_issue_frame_id(), this); + if (frame != nullptr) { + ink_assert(frame->type() == QUICFrameType::CRYPTO); + this->_records_crypto_frame(level, *static_cast(frame)); + return frame; + } + + if (maximum_frame_size <= MAX_CRYPTO_FRAME_OVERHEAD) { + return frame; + } + + uint64_t frame_payload_size = maximum_frame_size - MAX_CRYPTO_FRAME_OVERHEAD; + uint64_t bytes_avail = this->_write_buffer_reader->read_avail(); + frame_payload_size = std::min(bytes_avail, frame_payload_size); + if (frame_payload_size == 0) { + return frame; + } + + Ptr block = make_ptr(this->_write_buffer_reader->get_current_block()->clone()); + block->consume(this->_write_buffer_reader->start_offset); + block->_end = std::min(block->start() + frame_payload_size, block->_buf_end); + ink_assert(static_cast(block->read_avail()) == frame_payload_size); + + frame = QUICFrameFactory::create_crypto_frame(buf, block, this->_send_offset, this->_issue_frame_id(), this); + this->_send_offset += frame_payload_size; + this->_write_buffer_reader->consume(frame_payload_size); + this->_records_crypto_frame(level, *static_cast(frame)); + + return frame; +} + +void +QUICCryptoStream::_on_frame_acked(QUICFrameInformationUPtr &info) +{ + ink_assert(info->type == QUICFrameType::CRYPTO); + CryptoFrameInfo *crypto_frame_info = reinterpret_cast(info->data); + crypto_frame_info->block = nullptr; +} + +void +QUICCryptoStream::_on_frame_lost(QUICFrameInformationUPtr &info) +{ + ink_assert(info->type == QUICFrameType::CRYPTO); + this->save_frame_info(std::move(info)); +} diff --git a/iocore/net/quic/QUICCryptoStream.h b/iocore/net/quic/QUICCryptoStream.h new file mode 100644 index 00000000000..6da3b2fcd2b --- /dev/null +++ b/iocore/net/quic/QUICCryptoStream.h @@ -0,0 +1,76 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include "QUICStream.h" + +/** + * @brief QUIC Crypto stream + * Differences from QUICStream are below + * - this doesn't have VConnection interface + * - no stream id + * - no flow control + * - no state (never closed) + */ +class QUICCryptoStream : public QUICStream +{ +public: + QUICCryptoStream(); + ~QUICCryptoStream(); + + int state_stream_open(int event, void *data); + + const QUICConnectionInfoProvider *info() const; + QUICOffset final_offset() const; + void reset_send_offset(); + void reset_recv_offset(); + + QUICConnectionErrorUPtr recv(const QUICCryptoFrame &frame) override; + + int64_t read_avail(); + int64_t read(uint8_t *buf, int64_t len); + int64_t write(const uint8_t *buf, int64_t len); + + // QUICFrameGenerator + bool will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) override; + QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size, + ink_hrtime timestamp) override; + +private: + void _on_frame_acked(QUICFrameInformationUPtr &info) override; + void _on_frame_lost(QUICFrameInformationUPtr &info) override; + + QUICStreamErrorUPtr _reset_reason = nullptr; + QUICOffset _send_offset = 0; + + // Fragments of received STREAM frame (offset is unmatched) + // TODO: Consider to replace with ts/RbTree.h or other data structure + QUICIncomingCryptoFrameBuffer _received_stream_frame_buffer; + + MIOBuffer *_read_buffer = nullptr; + MIOBuffer *_write_buffer = nullptr; + + IOBufferReader *_read_buffer_reader = nullptr; + IOBufferReader *_write_buffer_reader = nullptr; +}; diff --git a/iocore/net/quic/QUICDebugNames.cc b/iocore/net/quic/QUICDebugNames.cc new file mode 100644 index 00000000000..b70d95975bf --- /dev/null +++ b/iocore/net/quic/QUICDebugNames.cc @@ -0,0 +1,329 @@ +/** @file + * + * A brief file description + * + * @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 "QUICDebugNames.h" +#include "I_VConnection.h" + +const char * +QUICDebugNames::packet_type(QUICPacketType type) +{ + switch (type) { + case QUICPacketType::VERSION_NEGOTIATION: + return "VERSION_NEGOTIATION"; + case QUICPacketType::INITIAL: + return "INITIAL"; + case QUICPacketType::RETRY: + return "RETRY"; + case QUICPacketType::HANDSHAKE: + return "HANDSHAKE"; + case QUICPacketType::ZERO_RTT_PROTECTED: + return "ZERO_RTT_PROTECTED"; + case QUICPacketType::PROTECTED: + return "PROTECTED"; + case QUICPacketType::STATELESS_RESET: + return "STATELESS_RESET"; + case QUICPacketType::UNINITIALIZED: + default: + return "UNKNOWN"; + } +} + +const char * +QUICDebugNames::frame_type(QUICFrameType type) +{ + switch (type) { + case QUICFrameType::PADDING: + return "PADDING"; + case QUICFrameType::RESET_STREAM: + return "RESET_STREAM"; + case QUICFrameType::CONNECTION_CLOSE: + return "CONNECTION_CLOSE"; + case QUICFrameType::MAX_DATA: + return "MAX_DATA"; + case QUICFrameType::MAX_STREAM_DATA: + return "MAX_STREAM_DATA"; + case QUICFrameType::MAX_STREAMS: + return "MAX_STREAMS"; + case QUICFrameType::PING: + return "PING"; + case QUICFrameType::DATA_BLOCKED: + return "DATA_BLOCKED"; + case QUICFrameType::STREAM_DATA_BLOCKED: + return "STREAM_DATA_BLOCKED"; + case QUICFrameType::STREAMS_BLOCKED: + return "STREAMS_BLOCKED"; + case QUICFrameType::NEW_CONNECTION_ID: + return "NEW_CONNECTION_ID"; + case QUICFrameType::STOP_SENDING: + return "STOP_SENDING"; + case QUICFrameType::ACK: + return "ACK"; + case QUICFrameType::PATH_CHALLENGE: + return "PATH_CHALLENGE"; + case QUICFrameType::PATH_RESPONSE: + return "PATH_RESPONSE"; + case QUICFrameType::STREAM: + return "STREAM"; + case QUICFrameType::CRYPTO: + return "CRYPTO"; + case QUICFrameType::RETIRE_CONNECTION_ID: + return "RETIRE_CONNECTION_ID"; + case QUICFrameType::NEW_TOKEN: + return "NEW_TOKEN"; + case QUICFrameType::UNKNOWN: + default: + return "UNKNOWN"; + } +} + +const char * +QUICDebugNames::error_class(QUICErrorClass cls) +{ + switch (cls) { + case QUICErrorClass::UNDEFINED: + return "UNDEFINED"; + case QUICErrorClass::TRANSPORT: + return "TRANSPORT"; + case QUICErrorClass::APPLICATION: + return "APPLICATION"; + default: + return "UNKNOWN"; + } +} + +const char * +QUICDebugNames::error_code(uint16_t code) +{ + switch (code) { + case static_cast(QUICTransErrorCode::NO_ERROR): + return "NO_ERROR"; + case static_cast(QUICTransErrorCode::INTERNAL_ERROR): + return "INTERNAL_ERROR"; + case static_cast(QUICTransErrorCode::FLOW_CONTROL_ERROR): + return "FLOW_CONTROL_ERROR"; + case static_cast(QUICTransErrorCode::STREAM_ID_ERROR): + return "STREAM_ID_ERROR"; + case static_cast(QUICTransErrorCode::STREAM_STATE_ERROR): + return "STREAM_STATE_ERROR"; + case static_cast(QUICTransErrorCode::FINAL_OFFSET_ERROR): + return "FINAL_OFFSET_ERROR"; + case static_cast(QUICTransErrorCode::FRAME_ENCODING_ERROR): + return "FRAME_ENCODING_ERROR"; + case static_cast(QUICTransErrorCode::TRANSPORT_PARAMETER_ERROR): + return "TRANSPORT_PARAMETER_ERROR"; + case static_cast(QUICTransErrorCode::VERSION_NEGOTIATION_ERROR): + return "VERSION_NEGOTIATION_ERROR"; + case static_cast(QUICTransErrorCode::PROTOCOL_VIOLATION): + return "PROTOCOL_VIOLATION"; + case static_cast(QUICTransErrorCode::INVALID_MIGRATION): + return "INVALID_MIGRATION"; + default: + if (0x0100 <= code && code <= 0x01FF) { + return "CRYPTO_ERROR"; + } + + return "UNKNOWN"; + } +} + +const char * +QUICDebugNames::quic_event(int event) +{ + switch (event) { + case QUIC_EVENT_PACKET_READ_READY: + return "QUIC_EVENT_PACKET_READ_READY"; + case QUIC_EVENT_PACKET_WRITE_READY: + return "QUIC_EVENT_PACKET_WRITE_READY"; + case QUIC_EVENT_HANDSHAKE_PACKET_WRITE_COMPLETE: + return "QUIC_EVENT_HANDSHAKE_PACKET_WRITE_COMPLETE"; + case QUIC_EVENT_CLOSING_TIMEOUT: + return "QUIC_EVENT_CLOSING_TIMEOUT"; + case QUIC_EVENT_PATH_VALIDATION_TIMEOUT: + return "QUIC_EVENT_PATH_VALIDATION_TIMEOUT"; + case QUIC_EVENT_SHUTDOWN: + return "QUIC_EVENT_SHUTDOWN"; + case QUIC_EVENT_LD_SHUTDOWN: + return "QUIC_EVENT_LD_SHUTDOWN"; + case QUIC_EVENT_ACK_PERIODIC: + return "QUIC_EVENT_ACK_PERIODIC"; + default: + return "UNKNOWN"; + } +} + +const char * +QUICDebugNames::transport_parameter_id(QUICTransportParameterId id) +{ + switch (id) { + case QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_LOCAL: + return "INITIAL_MAX_STREAM_DATA_BIDI_LOCAL"; + case QUICTransportParameterId::INITIAL_MAX_DATA: + return "INITIAL_MAX_DATA"; + case QUICTransportParameterId::INITIAL_MAX_STREAMS_BIDI: + return "INITIAL_MAX_STREAMS_BIDI"; + case QUICTransportParameterId::IDLE_TIMEOUT: + return "IDLE_TIMEOUT"; + case QUICTransportParameterId::PREFERRED_ADDRESS: + return "PREFERRED_ADDRESS"; + case QUICTransportParameterId::MAX_PACKET_SIZE: + return "MAX_PACKET_SIZE"; + case QUICTransportParameterId::STATELESS_RESET_TOKEN: + return "STATELESS_RESET_TOKEN"; + case QUICTransportParameterId::ACK_DELAY_EXPONENT: + return "ACK_DELAY_EXPONENT"; + case QUICTransportParameterId::INITIAL_MAX_STREAMS_UNI: + return "INITIAL_MAX_STREAMS_UNI"; + case QUICTransportParameterId::DISABLE_MIGRATION: + return "DISABLE_MIGRATION"; + case QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_REMOTE: + return "INITIAL_MAX_STREAM_DATA_BIDI_REMOTE"; + case QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_UNI: + return "INITIAL_MAX_STREAM_DATA_UNI"; + case QUICTransportParameterId::MAX_ACK_DELAY: + return "INITIAL_MAX_ACK_DELAY"; + case QUICTransportParameterId::ORIGINAL_CONNECTION_ID: + return "INITIAL_ORIGINAL_CONNECTION_ID"; + default: + return "UNKNOWN"; + } +} + +const char * +QUICDebugNames::stream_state(const QUICSendStreamState state) +{ + switch (state) { + case QUICSendStreamState::Init: + return "QUICSendStreamState::Init"; + case QUICSendStreamState::Ready: + return "QUICSendStreamState::Ready"; + case QUICSendStreamState::Send: + return "QUICSendStreamState::Send"; + case QUICSendStreamState::DataSent: + return "QUICSendStreamState::DataSent"; + case QUICSendStreamState::DataRecvd: + return "QUICSendStreamState::DataRecvd"; + case QUICSendStreamState::ResetSent: + return "QUICSendStreamState::ResetSent"; + case QUICSendStreamState::ResetRecvd: + return "QUICSendStreamState::ResetRecvd"; + default: + return "UNKNOWN"; + } +} + +const char * +QUICDebugNames::stream_state(const QUICReceiveStreamState state) +{ + switch (state) { + case QUICReceiveStreamState::Init: + return "QUICReceiveStreamState::Init"; + case QUICReceiveStreamState::Recv: + return "QUICReceiveStreamState::Recv"; + case QUICReceiveStreamState::SizeKnown: + return "QUICReceiveStreamState::SizeKnown"; + case QUICReceiveStreamState::DataRecvd: + return "QUICReceiveStreamState::DataRecvd"; + case QUICReceiveStreamState::ResetRecvd: + return "QUICReceiveStreamState::ResetRecvd"; + case QUICReceiveStreamState::DataRead: + return "QUICReceiveStreamState::DataRead"; + case QUICReceiveStreamState::ResetRead: + return "QUICReceiveStreamState::ResetRead"; + default: + return "UNKNOWN"; + } +} + +const char * +QUICDebugNames::stream_state(const QUICBidirectionalStreamState state) +{ + switch (state) { + case QUICBidirectionalStreamState::Init: + return "QUICBidirectionalStreamState::Init"; + case QUICBidirectionalStreamState::Idle: + return "QUICBidirectionalStreamState::Idle"; + case QUICBidirectionalStreamState::Open: + return "QUICBidirectionalStreamState::Open"; + case QUICBidirectionalStreamState::HC_R: + return "QUICBidirectionalStreamState::HC_R"; + case QUICBidirectionalStreamState::HC_L: + return "QUICBidirectionalStreamState::HC_L"; + case QUICBidirectionalStreamState::Closed: + return "QUICBidirectionalStreamState::Closed"; + case QUICBidirectionalStreamState::Invalid: + return "QUICBidirectionalStreamState::Invalid"; + default: + return "UNKNOWN"; + } +} + +const char * +QUICDebugNames::key_phase(QUICKeyPhase phase) +{ + switch (phase) { + case QUICKeyPhase::PHASE_0: + return "PHASE_0"; + case QUICKeyPhase::PHASE_1: + return "PHASE_1"; + case QUICKeyPhase::INITIAL: + return "INITIAL"; + case QUICKeyPhase::ZERO_RTT: + return "ZERO_RTT"; + case QUICKeyPhase::HANDSHAKE: + return "HANDSHAKE"; + default: + return "UNKNOWN"; + } +} + +const char * +QUICDebugNames::encryption_level(QUICEncryptionLevel level) +{ + switch (level) { + case QUICEncryptionLevel::INITIAL: + return "INITIAL"; + case QUICEncryptionLevel::ZERO_RTT: + return "ZERO_RTT"; + case QUICEncryptionLevel::HANDSHAKE: + return "HANDSHAKE"; + case QUICEncryptionLevel::ONE_RTT: + return "ONE_RTT"; + default: + return "UNKNOWN"; + } +} + +const char * +QUICDebugNames::pn_space(QUICPacketNumberSpace pn_space) +{ + switch (pn_space) { + case QUICPacketNumberSpace::Initial: + return "QUICPacketNumberSpace::Initial"; + case QUICPacketNumberSpace::Handshake: + return "QUICPacketNumberSpace::Handshake"; + case QUICPacketNumberSpace::ApplicationData: + return "QUICPacketNumberSpace::ApplicationData"; + default: + return "UNKNOWN"; + } +} diff --git a/iocore/net/quic/QUICDebugNames.h b/iocore/net/quic/QUICDebugNames.h new file mode 100644 index 00000000000..f80c8ae6202 --- /dev/null +++ b/iocore/net/quic/QUICDebugNames.h @@ -0,0 +1,62 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include "QUICTypes.h" +#include "QUICEvents.h" +#include "QUICTransportParameters.h" +#include "QUICStreamState.h" + +class QUICDebugNames +{ +public: + static const char *packet_type(QUICPacketType type); + static const char *frame_type(QUICFrameType type); + static const char *error_class(QUICErrorClass cls); + static const char *error_code(uint16_t code); + static const char *transport_parameter_id(QUICTransportParameterId id); + static const char *stream_state(const QUICSendStreamState state); + static const char *stream_state(const QUICReceiveStreamState state); + static const char *stream_state(const QUICBidirectionalStreamState state); + static const char *quic_event(int event); + static const char *key_phase(QUICKeyPhase phase); + static const char *encryption_level(QUICEncryptionLevel level); + static const char *pn_space(QUICPacketNumberSpace pn_space); +}; + +class QUICDebug +{ +public: + static void + to_hex(uint8_t *out, const uint8_t *in, int in_len) + { + for (int i = 0; i < in_len; ++i) { + int u4 = in[i] / 16; + int l4 = in[i] % 16; + out[i * 2] = (u4 < 10) ? ('0' + u4) : ('a' + u4 - 10); + out[i * 2 + 1] = (l4 < 10) ? ('0' + l4) : ('a' + l4 - 10); + } + out[in_len * 2] = 0; + } +}; diff --git a/iocore/net/quic/QUICEchoApp.cc b/iocore/net/quic/QUICEchoApp.cc new file mode 100644 index 00000000000..35ea791f724 --- /dev/null +++ b/iocore/net/quic/QUICEchoApp.cc @@ -0,0 +1,85 @@ +/** @file + * + * A brief file description + * + * @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 "QUICEchoApp.h" + +#include "P_Net.h" +#include "P_VConnection.h" +#include "QUICDebugNames.h" + +static constexpr char tag[] = "quic_echo_app"; + +QUICEchoApp::QUICEchoApp(QUICConnection *qc) : QUICApplication(qc) +{ + SET_HANDLER(&QUICEchoApp::main_event_handler); +} + +int +QUICEchoApp::main_event_handler(int event, Event *data) +{ + Debug(tag, "%s", get_vc_event_name(event)); + + QUICStream *stream = reinterpret_cast(data->cookie); + QUICStreamIO *stream_io = this->_find_stream_io(stream->id()); + if (stream_io == nullptr) { + Debug(tag, "Unknown Stream, id: %" PRIx64, stream->id()); + return -1; + } + + switch (event) { + case VC_EVENT_READ_READY: + case VC_EVENT_READ_COMPLETE: { + uint8_t msg[1024] = {0}; + int64_t msg_len = 1024; + + int64_t read_len = stream_io->read(msg, msg_len); + + if (read_len) { + Debug(tag, "msg: %s, len: %" PRId64, msg, read_len); + + stream_io->write(msg, read_len); + stream_io->write_reenable(); + stream_io->read_reenable(); + } else { + Debug(tag, "No MSG"); + } + break; + } + case VC_EVENT_WRITE_READY: + case VC_EVENT_WRITE_COMPLETE: { + // do nothing + break; + } + case VC_EVENT_EOS: + case VC_EVENT_ERROR: + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: { + ink_assert(false); + break; + } + default: + break; + } + + return EVENT_CONT; +} diff --git a/iocore/net/quic/QUICEchoApp.h b/iocore/net/quic/QUICEchoApp.h new file mode 100644 index 00000000000..8a61f908508 --- /dev/null +++ b/iocore/net/quic/QUICEchoApp.h @@ -0,0 +1,39 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include "QUICApplication.h" + +/** + * @brief Echo over QUIC + * @detail An example application over QUIC. + * Receive DATA of STREAM Frame and echo it. + */ +class QUICEchoApp : public QUICApplication +{ +public: + QUICEchoApp(QUICConnection *qc); + + int main_event_handler(int event, Event *data); +}; diff --git a/iocore/net/quic/QUICEvents.h b/iocore/net/quic/QUICEvents.h new file mode 100644 index 00000000000..c758249ee93 --- /dev/null +++ b/iocore/net/quic/QUICEvents.h @@ -0,0 +1,38 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include "I_EventSystem.h" +#include "I_Event.h" + +enum { + QUIC_EVENT_PACKET_READ_READY = QUIC_EVENT_EVENTS_START, + QUIC_EVENT_PACKET_WRITE_READY, + QUIC_EVENT_HANDSHAKE_PACKET_WRITE_COMPLETE, + QUIC_EVENT_CLOSING_TIMEOUT, + QUIC_EVENT_PATH_VALIDATION_TIMEOUT, + QUIC_EVENT_ACK_PERIODIC, + QUIC_EVENT_SHUTDOWN, + QUIC_EVENT_LD_SHUTDOWN, +}; diff --git a/iocore/net/quic/QUICFlowController.cc b/iocore/net/quic/QUICFlowController.cc new file mode 100644 index 00000000000..7c2e3db9e0b --- /dev/null +++ b/iocore/net/quic/QUICFlowController.cc @@ -0,0 +1,270 @@ +/** @file + * + * A brief file description + * + * @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 "QUICFlowController.h" +#include "QUICFrame.h" + +// +// QUICRateAnalyzer +// +void +QUICRateAnalyzer::update(QUICOffset offset) +{ + ink_hrtime now = Thread::get_hrtime(); + if (offset > 0 && now > this->_start_time) { + this->_rate = static_cast(offset) / (now - this->_start_time); + } +} + +uint64_t +QUICRateAnalyzer::expect_recv_bytes(ink_hrtime time) +{ + return static_cast(time * this->_rate); +} + +// +// QUICFlowController +// +uint64_t +QUICFlowController::credit() const +{ + return this->current_limit() - this->current_offset(); +} + +QUICOffset +QUICFlowController::current_offset() const +{ + return this->_offset; +} + +QUICOffset +QUICFlowController::current_limit() const +{ + return this->_limit; +} + +int +QUICFlowController::update(QUICOffset offset) +{ + if (this->_offset <= offset) { + if (offset > this->_limit) { + return -1; + } + this->_offset = offset; + } + + return 0; +} + +void +QUICFlowController::forward_limit(QUICOffset limit) +{ + // MAX_(STREAM_)DATA might be unorderd due to delay + // Just ignore if the size was smaller than the last one + if (this->_limit > limit) { + return; + } + this->_limit = limit; +} + +void +QUICFlowController::set_limit(QUICOffset limit) +{ + ink_assert(this->_limit == UINT64_MAX || this->_limit == limit); + this->_limit = limit; +} + +// For RemoteFlowController, caller of this function should also check QUICStreamManager::will_generate_frame() +bool +QUICFlowController::will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) +{ + if (!this->_is_level_matched(level)) { + return false; + } + + return this->_should_create_frame; +} + +/** + * @param connection_credit This is not used. Because MAX_(STREAM_)DATA frame are not flow-controlled + */ +QUICFrame * +QUICFlowController::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t /* connection_credit */, + uint16_t maximum_frame_size, ink_hrtime timestamp) +{ + QUICFrame *frame = nullptr; + + if (!this->_is_level_matched(level)) { + return frame; + } + + if (this->_should_create_frame) { + frame = this->_create_frame(buf); + if (frame && frame->size() <= maximum_frame_size) { + this->_should_create_frame = false; + } + } + + return frame; +} + +// +// QUICRemoteFlowController +// +void +QUICRemoteFlowController::forward_limit(QUICOffset new_limit) +{ + QUICFlowController::forward_limit(new_limit); + this->_blocked = false; + this->_should_create_frame = false; +} + +int +QUICRemoteFlowController::update(QUICOffset offset) +{ + int ret = QUICFlowController::update(offset); + + // Create BLOCKED(_STREAM) frame + // The frame will be sent if stream has something to send. + if (offset >= this->_limit) { + this->_should_create_frame = true; + this->_blocked = true; + } + + return ret; +} + +void +QUICRemoteFlowController::_on_frame_lost(QUICFrameInformationUPtr &info) +{ + ink_assert(info->type == QUICFrameType::DATA_BLOCKED || info->type == QUICFrameType::STREAM_DATA_BLOCKED); + if (this->_offset == *reinterpret_cast(info->data)) { + this->_should_create_frame = true; + } +} + +// +// QUICLocalFlowController +// +QUICOffset +QUICLocalFlowController::current_limit() const +{ + return this->_limit; +} + +void +QUICLocalFlowController::forward_limit(QUICOffset new_limit) +{ + // Create MAX_(STREAM_)DATA frame. The frame will be sent on next WRITE_READY event on QUICNetVC + if (this->_need_to_forward_limit()) { + QUICFlowController::forward_limit(new_limit); + this->_should_create_frame = true; + } +} + +int +QUICLocalFlowController::update(QUICOffset offset) +{ + if (this->_offset <= offset) { + this->_analyzer.update(offset); + } + return QUICFlowController::update(offset); +} + +void +QUICLocalFlowController::set_limit(QUICOffset limit) +{ + QUICFlowController::set_limit(limit); +} + +void +QUICLocalFlowController::_on_frame_lost(QUICFrameInformationUPtr &info) +{ + ink_assert(info->type == QUICFrameType::MAX_DATA || info->type == QUICFrameType::MAX_STREAM_DATA); + if (this->_limit == *reinterpret_cast(info->data)) { + this->_should_create_frame = true; + } +} + +bool +QUICLocalFlowController::_need_to_forward_limit() +{ + QUICOffset threshold = this->_analyzer.expect_recv_bytes(2 * this->_rtt_provider->smoothed_rtt()); + if (this->_offset + threshold >= this->_limit) { + return true; + } + + return false; +} + +// +// QUIC[Remote|Local][Connection|Stream]FlowController +// +QUICFrame * +QUICRemoteConnectionFlowController::_create_frame(uint8_t *buf) +{ + auto frame = QUICFrameFactory::create_data_blocked_frame(buf, this->_offset, this->_issue_frame_id(), this); + QUICFrameInformationUPtr info = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc()); + info->type = frame->type(); + info->level = QUICEncryptionLevel::NONE; + *(reinterpret_cast(info->data)) = this->_offset; + this->_records_frame(frame->id(), std::move(info)); + return frame; +} + +QUICFrame * +QUICLocalConnectionFlowController::_create_frame(uint8_t *buf) +{ + auto frame = QUICFrameFactory::create_max_data_frame(buf, this->_limit, this->_issue_frame_id(), this); + QUICFrameInformationUPtr info = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc()); + info->type = frame->type(); + info->level = QUICEncryptionLevel::NONE; + *(reinterpret_cast(info->data)) = this->_limit; + this->_records_frame(frame->id(), std::move(info)); + return frame; +} + +QUICFrame * +QUICRemoteStreamFlowController::_create_frame(uint8_t *buf) +{ + auto frame = + QUICFrameFactory::create_stream_data_blocked_frame(buf, this->_stream_id, this->_offset, this->_issue_frame_id(), this); + QUICFrameInformationUPtr info = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc()); + info->type = frame->type(); + info->level = QUICEncryptionLevel::NONE; + *(reinterpret_cast(info->data)) = this->_offset; + this->_records_frame(frame->id(), std::move(info)); + return frame; +} + +QUICFrame * +QUICLocalStreamFlowController::_create_frame(uint8_t *buf) +{ + auto frame = QUICFrameFactory::create_max_stream_data_frame(buf, this->_stream_id, this->_limit, this->_issue_frame_id(), this); + QUICFrameInformationUPtr info = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc()); + info->type = frame->type(); + info->level = QUICEncryptionLevel::NONE; + *(reinterpret_cast(info->data)) = this->_limit; + this->_records_frame(frame->id(), std::move(info)); + return frame; +} diff --git a/iocore/net/quic/QUICFlowController.h b/iocore/net/quic/QUICFlowController.h new file mode 100644 index 00000000000..c3cb6371139 --- /dev/null +++ b/iocore/net/quic/QUICFlowController.h @@ -0,0 +1,153 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include "I_EventSystem.h" +#include "QUICTypes.h" +#include "QUICFrame.h" +#include "QUICFrameGenerator.h" +#include "QUICLossDetector.h" + +class QUICRateAnalyzer +{ +public: + void update(QUICOffset offset); + uint64_t expect_recv_bytes(ink_hrtime time); + +private: + double _rate = 0.0; + ink_hrtime _start_time = Thread::get_hrtime(); +}; + +class QUICFlowController : public QUICFrameGenerator +{ +public: + uint64_t credit() const; + QUICOffset current_offset() const; + virtual QUICOffset current_limit() const; + + /* + * Returns 0 if succeed + */ + virtual int update(QUICOffset offset); + virtual void forward_limit(QUICOffset limit); + + /** + * This is only for flow controllers initialized without a limit (== UINT64_MAX). + * Once a limit is set, it should be updated with forward_limit(). + */ + virtual void set_limit(QUICOffset limit); + + // QUICFrameGenerator + bool will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) override; + QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size, + ink_hrtime timestamp) override; + +protected: + QUICFlowController(uint64_t initial_limit) : _limit(initial_limit) {} + virtual QUICFrame *_create_frame(uint8_t *buf) = 0; + + QUICOffset _offset = 0; //< Largest sent/received offset + QUICOffset _limit = 0; //< Maximum amount of data to send/receive + bool _should_create_frame = false; +}; + +class QUICRemoteFlowController : public QUICFlowController +{ +public: + QUICRemoteFlowController(uint64_t initial_limit) : QUICFlowController(initial_limit) {} + int update(QUICOffset offset) override; + void forward_limit(QUICOffset new_limit) override; + +private: + void _on_frame_lost(QUICFrameInformationUPtr &info) override; + bool _blocked = false; +}; + +class QUICLocalFlowController : public QUICFlowController +{ +public: + QUICLocalFlowController(QUICRTTProvider *rtt_provider, uint64_t initial_limit) + : QUICFlowController(initial_limit), _rtt_provider(rtt_provider) + { + } + QUICOffset current_limit() const override; + + /** + * Unlike QUICRemoteFlowController::forward_limit(), this function forwards limit if needed. + */ + void forward_limit(QUICOffset new_limit) override; + int update(QUICOffset offset) override; + void set_limit(QUICOffset limit) override; + +private: + void _on_frame_lost(QUICFrameInformationUPtr &info) override; + bool _need_to_forward_limit(); + + QUICRateAnalyzer _analyzer; + QUICRTTProvider *_rtt_provider = nullptr; +}; + +class QUICRemoteConnectionFlowController : public QUICRemoteFlowController +{ +public: + QUICRemoteConnectionFlowController(uint64_t initial_limit) : QUICRemoteFlowController(initial_limit) {} + QUICFrame *_create_frame(uint8_t *buf) override; +}; + +class QUICLocalConnectionFlowController : public QUICLocalFlowController +{ +public: + QUICLocalConnectionFlowController(QUICRTTProvider *rtt_provider, uint64_t initial_limit) + : QUICLocalFlowController(rtt_provider, initial_limit) + { + } + QUICFrame *_create_frame(uint8_t *buf) override; +}; + +class QUICRemoteStreamFlowController : public QUICRemoteFlowController +{ +public: + QUICRemoteStreamFlowController(uint64_t initial_limit, QUICStreamId stream_id) + : QUICRemoteFlowController(initial_limit), _stream_id(stream_id) + { + } + QUICFrame *_create_frame(uint8_t *buf) override; + +private: + QUICStreamId _stream_id = 0; +}; + +class QUICLocalStreamFlowController : public QUICLocalFlowController +{ +public: + QUICLocalStreamFlowController(QUICRTTProvider *rtt_provider, uint64_t initial_limit, QUICStreamId stream_id) + : QUICLocalFlowController(rtt_provider, initial_limit), _stream_id(stream_id) + { + } + QUICFrame *_create_frame(uint8_t *buf) override; + +private: + QUICStreamId _stream_id = 0; +}; diff --git a/iocore/net/quic/QUICFrame.cc b/iocore/net/quic/QUICFrame.cc new file mode 100644 index 00000000000..903c4e96679 --- /dev/null +++ b/iocore/net/quic/QUICFrame.cc @@ -0,0 +1,2760 @@ +/** @file + * + * A brief file description + * + * @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 "QUICFrame.h" + +#include + +#include "QUICStream.h" +#include "QUICIntUtil.h" +#include "QUICDebugNames.h" +#include "QUICPacket.h" + +#define LEFT_SPACE(pos) ((size_t)(buf + len - pos)) +#define FRAME_SIZE(pos) (pos - buf) + +// the pos will auto move forward . return true if the data vaild +static bool +read_varint(uint8_t *&pos, size_t len, uint64_t &field, size_t &field_len) +{ + if (len < 1) { + return false; + } + + field_len = QUICVariableInt::size(pos); + if (len < field_len) { + return false; + } + + field = QUICIntUtil::read_QUICVariableInt(pos); + pos += field_len; + return true; +} + +QUICFrameType +QUICFrame::type() const +{ + ink_assert("should not be called"); + return QUICFrameType::UNKNOWN; +} + +bool +QUICFrame::is_probing_frame() const +{ + return false; +} + +bool +QUICFrame::is_flow_controlled() const +{ + return false; +} + +QUICFrameId +QUICFrame::id() const +{ + return this->_id; +} + +QUICFrameGenerator * +QUICFrame::generated_by() +{ + return this->_owner; +} + +QUICFrameType +QUICFrame::type(const uint8_t *buf) +{ + if (buf[0] >= static_cast(QUICFrameType::UNKNOWN)) { + return QUICFrameType::UNKNOWN; + } else if (static_cast(QUICFrameType::ACK) <= buf[0] && buf[0] < static_cast(QUICFrameType::RESET_STREAM)) { + return QUICFrameType::ACK; + } else if (static_cast(QUICFrameType::STREAM) <= buf[0] && buf[0] < static_cast(QUICFrameType::MAX_DATA)) { + return QUICFrameType::STREAM; + } else if (static_cast(QUICFrameType::MAX_STREAMS) <= buf[0] && + buf[0] < static_cast(QUICFrameType::DATA_BLOCKED)) { + return QUICFrameType::MAX_STREAMS; + } else if (static_cast(QUICFrameType::STREAMS_BLOCKED) <= buf[0] && + buf[0] < static_cast(QUICFrameType::NEW_CONNECTION_ID)) { + return QUICFrameType::STREAMS_BLOCKED; + } else if (static_cast(QUICFrameType::CONNECTION_CLOSE) <= buf[0] && + buf[0] < static_cast(QUICFrameType::UNKNOWN)) { + return QUICFrameType::CONNECTION_CLOSE; + } else { + return static_cast(buf[0]); + } +} + +Ptr +QUICFrame::to_io_buffer_block(size_t limit) const +{ + // FIXME Each classes should override this and drop store(). + // This just wraps store() for now. + + Ptr block = make_ptr(new_IOBufferBlock()); + block->alloc(iobuffer_size_to_index(limit)); + + size_t written_len = 0; + this->store(reinterpret_cast(block->start()), &written_len, limit); + block->fill(written_len); + + return block; +} + +int +QUICFrame::debug_msg(char *msg, size_t msg_len) const +{ + return snprintf(msg, msg_len, "%s size=%zu", QUICDebugNames::frame_type(this->type()), this->size()); +} + +bool +QUICFrame::valid() const +{ + return this->_valid; +} + +// +// STREAM Frame +// + +QUICStreamFrame::QUICStreamFrame(Ptr &block, QUICStreamId stream_id, QUICOffset offset, bool last, + bool has_offset_field, bool has_length_field, QUICFrameId id, QUICFrameGenerator *owner) + : QUICFrame(id, owner), + _block(block), + _stream_id(stream_id), + _offset(offset), + _fin(last), + _has_offset_field(has_offset_field), + _has_length_field(has_length_field) +{ +} + +QUICStreamFrame::QUICStreamFrame(const uint8_t *buf, size_t len) +{ + this->parse(buf, len); +} + +QUICStreamFrame::QUICStreamFrame(const QUICStreamFrame &o) + : QUICFrame(o), + _block(make_ptr(o._block->clone())), + _stream_id(o._stream_id), + _offset(o._offset), + _fin(o._fin), + _has_offset_field(o._has_offset_field), + _has_length_field(o._has_length_field) +{ +} + +void +QUICStreamFrame::parse(const uint8_t *buf, size_t len) +{ + ink_assert(len >= 1); + this->_reset(); + + uint8_t *pos = const_cast(buf); + this->_has_offset_field = (buf[0] & 0x04) != 0; // "O" of "0b00010OLF" + this->_has_length_field = (buf[0] & 0x02) != 0; // "L" of "0b00010OLF" + this->_fin = (buf[0] & 0x01) != 0; // "F" of "0b00010OLF" + pos += 1; + + size_t field_len = 0; + if (!read_varint(pos, LEFT_SPACE(pos), this->_stream_id, field_len)) { + return; + } + + if (this->_has_offset_field && !read_varint(pos, LEFT_SPACE(pos), this->_offset, field_len)) { + return; + } + + uint64_t data_len = 0; + if (this->_has_length_field && !read_varint(pos, LEFT_SPACE(pos), data_len, field_len)) { + return; + } + + if (!this->_has_length_field) { + data_len = LEFT_SPACE(pos); + } + if (LEFT_SPACE(pos) < data_len) { + return; + } + + this->_valid = true; + this->_block = make_ptr(new_IOBufferBlock()); + this->_block->alloc(); + ink_assert(static_cast(this->_block->write_avail()) > data_len); + memcpy(this->_block->start(), pos, data_len); + this->_block->fill(data_len); + pos += data_len; + this->_size = FRAME_SIZE(pos); +} + +void +QUICStreamFrame::_reset() +{ + this->_block = nullptr; + this->_fin = false; + this->_has_length_field = true; + this->_has_offset_field = true; + this->_offset = 0; + this->_stream_id = 0; + this->_owner = nullptr; + this->_id = 0; + this->_valid = false; + this->_size = 0; +} + +QUICFrameType +QUICStreamFrame::type() const +{ + return QUICFrameType::STREAM; +} + +size_t +QUICStreamFrame::size() const +{ + if (this->_size) { + return this->_size; + } + + size_t size = 1; + size_t data_len = 0; + if (this->_block.get() != nullptr) { + data_len = this->_block->read_avail(); + } + + size += QUICVariableInt::size(this->_stream_id); + if (this->_has_offset_field) { + size += QUICVariableInt::size(this->_offset); + } + + if (this->_has_length_field) { + size += QUICVariableInt::size(data_len); + size += data_len; + } + + return size; +} + +bool +QUICStreamFrame::is_flow_controlled() const +{ + return true; +} + +size_t +QUICStreamFrame::store(uint8_t *buf, size_t *len, size_t limit) const +{ + return this->store(buf, len, limit, true); +} + +int +QUICStreamFrame::debug_msg(char *msg, size_t msg_len) const +{ + return snprintf(msg, msg_len, "STREAM size=%zu id=%" PRIu64 " offset=%" PRIu64 " data_len=%" PRIu64 " fin=%d", this->size(), + this->stream_id(), this->offset(), this->data_length(), this->has_fin_flag()); +} + +Ptr +QUICStreamFrame::to_io_buffer_block(size_t limit) const +{ + Ptr header; + + if (limit < this->size()) { + return header; + } + + // Create header block + size_t written_len = 0; + header = make_ptr(new_IOBufferBlock()); + header->alloc(iobuffer_size_to_index(MAX_HEADER_SIZE)); + this->_store_header(reinterpret_cast(header->start()), &written_len, true); + header->fill(written_len); + + // Append payload block to a chain + ink_assert(written_len + this->data_length() <= limit); + header->next = this->data(); + + // Return the chain + return header; +} + +size_t +QUICStreamFrame::_store_header(uint8_t *buf, size_t *len, bool include_length_field) const +{ + // Build Frame Type: "0b0010OLF" + buf[0] = static_cast(QUICFrameType::STREAM); + *len = 1; + + size_t n; + + // Stream ID (i) + QUICTypeUtil::write_QUICStreamId(this->stream_id(), buf + *len, &n); + *len += n; + + // [Offset (i)] "O" of "0b0010OLF" + if (this->has_offset_field()) { + QUICTypeUtil::write_QUICOffset(this->offset(), buf + *len, &n); + *len += n; + buf[0] += 0x04; + } + + // [Length (i)] "L of "0b0010OLF" + if (include_length_field) { + QUICIntUtil::write_QUICVariableInt(this->data_length(), buf + *len, &n); + *len += n; + buf[0] += 0x02; + } + + // "F" of "0b0010OLF" + if (this->has_fin_flag()) { + buf[0] += 0x01; + } + + return *len; +} + +size_t +QUICStreamFrame::store(uint8_t *buf, size_t *len, size_t limit, bool include_length_field) const +{ + ink_assert(!"Call to_io_buffer_block() instead"); + return 0; +} + +QUICStreamId +QUICStreamFrame::stream_id() const +{ + return this->_stream_id; +} + +QUICOffset +QUICStreamFrame::offset() const +{ + if (this->has_offset_field()) { + return this->_offset; + } + + return 0; +} + +uint64_t +QUICStreamFrame::data_length() const +{ + return this->_block->read_avail(); +} + +IOBufferBlock * +QUICStreamFrame::data() const +{ + return this->_block.get(); +} + +/** + * "O" of "0b00010OLF" + */ +bool +QUICStreamFrame::has_offset_field() const +{ + return this->_has_offset_field; +} + +/** + * "L" of "0b00010OLF" + */ +bool +QUICStreamFrame::has_length_field() const +{ + // This depends on `include_length_field` arg of QUICStreamFrame::store. + // Returning true for just in case. + return this->_has_length_field; +} + +/** + * "F" of "0b00010OLF" + */ +bool +QUICStreamFrame::has_fin_flag() const +{ + return this->_fin; +} + +// +// CRYPTO frame +// + +QUICCryptoFrame::QUICCryptoFrame(Ptr &block, QUICOffset offset, QUICFrameId id, QUICFrameGenerator *owner) + : QUICFrame(id, owner), _offset(offset), _block(block) +{ +} + +QUICCryptoFrame::QUICCryptoFrame(const uint8_t *buf, size_t len) +{ + this->parse(buf, len); +} + +QUICCryptoFrame::QUICCryptoFrame(const QUICCryptoFrame &o) + : QUICFrame(o), _offset(o._offset), _block(make_ptr(o._block->clone())) +{ +} + +void +QUICCryptoFrame::parse(const uint8_t *buf, size_t len) +{ + ink_assert(len >= 1); + this->_reset(); + uint8_t *pos = const_cast(buf) + 1; + + size_t field_len = 0; + if (!read_varint(pos, LEFT_SPACE(pos), this->_offset, field_len)) { + return; + } + + uint64_t data_len = 0; + if (!read_varint(pos, LEFT_SPACE(pos), data_len, field_len)) { + return; + } + + if (LEFT_SPACE(pos) < data_len) { + return; + } + + this->_valid = true; + this->_block = make_ptr(new_IOBufferBlock()); + this->_block->alloc(); + ink_assert(static_cast(this->_block->write_avail()) > data_len); + memcpy(this->_block->start(), pos, data_len); + this->_block->fill(data_len); + pos += data_len; + this->_size = FRAME_SIZE(pos); +} + +void +QUICCryptoFrame::_reset() +{ + this->_block = nullptr; + this->_offset = 0; + this->_owner = nullptr; + this->_id = 0; + this->_valid = false; + this->_size = 0; +} + +// QUICFrame * +// QUICCryptoFrame::clone(uint8_t *buf) const +// { +// Ptr block = make_ptr(this->_block->clone()); +// return QUICFrameFactory::create_crypto_frame(buf, block, this->offset(), this->_id, this->_owner); +// } + +QUICFrameType +QUICCryptoFrame::type() const +{ + return QUICFrameType::CRYPTO; +} + +size_t +QUICCryptoFrame::size() const +{ + if (this->_size) { + return this->_size; + } + + return 1 + this->_block->read_avail() + QUICVariableInt::size(this->_offset) + QUICVariableInt::size(this->_block->read_avail()); +} + +int +QUICCryptoFrame::debug_msg(char *msg, size_t msg_len) const +{ + return snprintf(msg, msg_len, "CRYPTO size=%zu offset=%" PRIu64 " data_len=%" PRIu64, this->size(), this->offset(), + this->data_length()); +} + +size_t +QUICCryptoFrame::store(uint8_t *buf, size_t *len, size_t limit) const +{ + if (limit < this->size()) { + return 0; + } + + // Frame Type + buf[0] = static_cast(QUICFrameType::CRYPTO); + *len = 1; + + size_t n; + + // Offset (i) + QUICTypeUtil::write_QUICOffset(this->offset(), buf + *len, &n); + *len += n; + + // Length (i) + QUICIntUtil::write_QUICVariableInt(this->data_length(), buf + *len, &n); + *len += n; + + // Crypto Data (*) + memcpy(buf + *len, this->data()->start(), this->data_length()); + *len += this->data_length(); + + return *len; +} + +QUICOffset +QUICCryptoFrame::offset() const +{ + return this->_offset; +} + +uint64_t +QUICCryptoFrame::data_length() const +{ + return this->_block->read_avail(); +} + +IOBufferBlock * +QUICCryptoFrame::data() const +{ + return this->_block.get(); +} + +// +// ACK frame +// + +QUICAckFrame::QUICAckFrame(const uint8_t *buf, size_t len) +{ + this->parse(buf, len); +} + +void +QUICAckFrame::parse(const uint8_t *buf, size_t len) +{ + ink_assert(len >= 1); + this->_reset(); + uint8_t *pos = const_cast(buf) + 1; + bool has_ecn = (buf[0] == static_cast(QUICFrameType::ACK_WITH_ECN)); + + size_t field_len = 0; + if (!read_varint(pos, LEFT_SPACE(pos), this->_largest_acknowledged, field_len)) { + return; + } + + if (!read_varint(pos, LEFT_SPACE(pos), this->_ack_delay, field_len)) { + return; + } + + uint64_t ack_block_count = 0; + if (!read_varint(pos, LEFT_SPACE(pos), ack_block_count, field_len)) { + return; + } + + uint64_t first_ack_block = 0; + if (!read_varint(pos, LEFT_SPACE(pos), first_ack_block, field_len)) { + return; + } + + this->_ack_block_section = new AckBlockSection(first_ack_block); + for (size_t i = 0; i < ack_block_count; i++) { + uint64_t gap = 0; + uint64_t add_ack_block = 0; + + if (!read_varint(pos, LEFT_SPACE(pos), gap, field_len)) { + return; + } + + if (!read_varint(pos, LEFT_SPACE(pos), add_ack_block, field_len)) { + return; + } + + this->_ack_block_section->add_ack_block({gap, add_ack_block}); + } + + if (has_ecn) { + this->_ecn_section = new EcnSection(pos, LEFT_SPACE(pos)); + if (!this->_ecn_section->valid()) { + return; + } + pos += this->_ecn_section->size(); + } + + this->_valid = true; + this->_size = FRAME_SIZE(pos); +} + +QUICAckFrame::QUICAckFrame(QUICPacketNumber largest_acknowledged, uint64_t ack_delay, uint64_t first_ack_block, QUICFrameId id, + QUICFrameGenerator *owner) + : QUICFrame(id, owner) +{ + this->_largest_acknowledged = largest_acknowledged; + this->_ack_delay = ack_delay; + this->_ack_block_section = new AckBlockSection(first_ack_block); +} + +void +QUICAckFrame::_reset() +{ + if (this->_ack_block_section) { + delete this->_ack_block_section; + this->_ack_block_section = nullptr; + } + if (this->_ecn_section) { + delete this->_ecn_section; + this->_ecn_section = nullptr; + } + + this->_largest_acknowledged = 0; + this->_ack_delay = 0; + this->_owner = nullptr; + this->_id = 0; + this->_valid = false; + this->_size = 0; +} + +QUICAckFrame::~QUICAckFrame() +{ + if (this->_ack_block_section) { + delete this->_ack_block_section; + this->_ack_block_section = nullptr; + } + if (this->_ecn_section) { + delete this->_ecn_section; + this->_ecn_section = nullptr; + } +} + +QUICFrameType +QUICAckFrame::type() const +{ + // TODO ECN + return QUICFrameType::ACK; +} + +size_t +QUICAckFrame::size() const +{ + if (this->_size) { + return this->_size; + } + + size_t pre_len = 1 + QUICVariableInt::size(this->_largest_acknowledged) + QUICVariableInt::size(this->_ack_delay) + + QUICVariableInt::size(this->_ack_block_section->count()); + if (this->_ack_block_section) { + pre_len += this->_ack_block_section->size(); + } + + if (this->_ecn_section) { + return pre_len + this->_ecn_section->size(); + } + + return pre_len; +} + +size_t +QUICAckFrame::store(uint8_t *buf, size_t *len, size_t limit) const +{ + if (limit < this->size()) { + return 0; + } + + uint8_t *p = buf; + size_t n; + *p = static_cast(QUICFrameType::ACK); + ++p; + + QUICIntUtil::write_QUICVariableInt(this->_largest_acknowledged, p, &n); + p += n; + QUICIntUtil::write_QUICVariableInt(this->_ack_delay, p, &n); + p += n; + QUICIntUtil::write_QUICVariableInt(this->ack_block_count(), p, &n); + p += n; + + ink_assert(limit >= static_cast(p - buf)); + limit -= (p - buf); + this->_ack_block_section->store(p, &n, limit); + p += n; + + *len = p - buf; + + return *len; +} + +int +QUICAckFrame::debug_msg(char *msg, size_t msg_len) const +{ + int len = snprintf(msg, msg_len, "ACK size=%zu largest_acked=%" PRIu64 " delay=%" PRIu64 " block_count=%" PRIu64, this->size(), + this->largest_acknowledged(), this->ack_delay(), this->ack_block_count()); + msg_len -= len; + + if (this->ack_block_section()) { + len += snprintf(msg + len, msg_len, " first_ack_block=%" PRIu64, this->ack_block_section()->first_ack_block()); + } + + return len; +} + +QUICPacketNumber +QUICAckFrame::largest_acknowledged() const +{ + return this->_largest_acknowledged; +} + +uint64_t +QUICAckFrame::ack_delay() const +{ + return this->_ack_delay; +} + +uint64_t +QUICAckFrame::ack_block_count() const +{ + return this->_ack_block_section->count(); +} + +QUICAckFrame::AckBlockSection * +QUICAckFrame::ack_block_section() +{ + return this->_ack_block_section; +} + +const QUICAckFrame::AckBlockSection * +QUICAckFrame::ack_block_section() const +{ + return this->_ack_block_section; +} + +QUICAckFrame::EcnSection * +QUICAckFrame::ecn_section() +{ + return this->_ecn_section; +} + +const QUICAckFrame::EcnSection * +QUICAckFrame::ecn_section() const +{ + return this->_ecn_section; +} + +// +// QUICAckFrame::PacketNumberRange +// +QUICAckFrame::PacketNumberRange::PacketNumberRange(PacketNumberRange &&a) noexcept +{ + this->_first = a._first; + this->_last = a._last; +} + +uint64_t +QUICAckFrame::PacketNumberRange::first() const +{ + return this->_first; +} + +uint64_t +QUICAckFrame::PacketNumberRange::last() const +{ + return this->_last; +} + +uint64_t +QUICAckFrame::PacketNumberRange::size() const +{ + return this->_first - this->_last; +} + +bool +QUICAckFrame::PacketNumberRange::contains(QUICPacketNumber x) const +{ + return static_cast(this->_last) <= static_cast(x) && + static_cast(x) <= static_cast(this->_first); +} + +// +// QUICAckFrame::AckBlock +// +uint64_t +QUICAckFrame::AckBlock::gap() const +{ + return this->_gap; +} + +uint64_t +QUICAckFrame::AckBlock::length() const +{ + return this->_length; +} + +size_t +QUICAckFrame::AckBlock::size() const +{ + return QUICVariableInt::size(this->_gap) + QUICVariableInt::size(this->_length); +} + +// +// QUICAckFrame::AckBlockSection +// +uint8_t +QUICAckFrame::AckBlockSection::count() const +{ + return this->_ack_blocks.size(); +} + +size_t +QUICAckFrame::AckBlockSection::size() const +{ + size_t n = 0; + + n += QUICVariableInt::size(this->_first_ack_block); + + for (auto &&block : *this) { + n += block.size(); + } + + return n; +} + +size_t +QUICAckFrame::AckBlockSection::store(uint8_t *buf, size_t *len, size_t limit) const +{ + if (limit < this->size()) { + return 0; + } + + size_t n; + uint8_t *p = buf; + + QUICIntUtil::write_QUICVariableInt(this->_first_ack_block, p, &n); + p += n; + + for (auto &&block : *this) { + QUICIntUtil::write_QUICVariableInt(block.gap(), p, &n); + p += n; + QUICIntUtil::write_QUICVariableInt(block.length(), p, &n); + p += n; + } + + *len = p - buf; + + return *len; +} + +uint64_t +QUICAckFrame::AckBlockSection::first_ack_block() const +{ + return this->_first_ack_block; +} + +void +QUICAckFrame::AckBlockSection::add_ack_block(AckBlock block) +{ + this->_ack_blocks.push_back(block); +} + +QUICAckFrame::AckBlockSection::const_iterator +QUICAckFrame::AckBlockSection::begin() const +{ + return const_iterator(0, &this->_ack_blocks); +} + +QUICAckFrame::AckBlockSection::const_iterator +QUICAckFrame::AckBlockSection::end() const +{ + return const_iterator(this->_ack_blocks.size(), &this->_ack_blocks); +} + +QUICAckFrame::AckBlockSection::const_iterator::const_iterator(uint8_t index, const std::vector *ack_blocks) + : _index(index), _ack_blocks(ack_blocks) +{ + if (this->_ack_blocks->size()) { + if (this->_ack_blocks->size() == this->_index) { + this->_current_block = {UINT64_C(0), UINT64_C(0)}; + } else { + this->_current_block = this->_ack_blocks->at(this->_index); + } + } +} + +// FIXME: something wrong with clang-format? +const QUICAckFrame::AckBlock & +QUICAckFrame::AckBlockSection::const_iterator::operator++() +{ + ++(this->_index); + + if (this->_ack_blocks->size() == this->_index) { + this->_current_block = {UINT64_C(0), UINT64_C(0)}; + } else { + this->_current_block = this->_ack_blocks->at(this->_index); + } + + return this->_current_block; +} + +const bool +QUICAckFrame::AckBlockSection::const_iterator::operator!=(const const_iterator &ite) const +{ + return this->_index != ite._index; +} + +const bool +QUICAckFrame::AckBlockSection::const_iterator::operator==(const const_iterator &ite) const +{ + return this->_index == ite._index; +} + +QUICAckFrame::EcnSection::EcnSection(const uint8_t *buf, size_t len) +{ + uint8_t *pos = const_cast(buf); + + size_t field_len = 0; + if (!read_varint(pos, LEFT_SPACE(pos), this->_ect0_count, field_len)) { + return; + } + + if (!read_varint(pos, LEFT_SPACE(pos), this->_ect1_count, field_len)) { + return; + } + + if (!read_varint(pos, LEFT_SPACE(pos), this->_ecn_ce_count, field_len)) { + return; + } + + this->_valid = true; + this->_size = FRAME_SIZE(pos); +} + +bool +QUICAckFrame::EcnSection::valid() const +{ + return this->_valid; +} + +size_t +QUICAckFrame::EcnSection::size() const +{ + return QUICVariableInt::size(this->_ect0_count) + QUICVariableInt::size(this->_ect1_count) + + QUICVariableInt::size(this->_ecn_ce_count); +} + +uint64_t +QUICAckFrame::EcnSection::ect0_count() const +{ + return this->_ect0_count; +} + +uint64_t +QUICAckFrame::EcnSection::ect1_count() const +{ + return this->_ect1_count; +} + +uint64_t +QUICAckFrame::EcnSection::ecn_ce_count() const +{ + return this->_ecn_ce_count; +} + +// +// RESET_STREAM frame +// + +QUICRstStreamFrame::QUICRstStreamFrame(QUICStreamId stream_id, QUICAppErrorCode error_code, QUICOffset final_offset, QUICFrameId id, + QUICFrameGenerator *owner) + : QUICFrame(id, owner), _stream_id(stream_id), _error_code(error_code), _final_offset(final_offset) +{ +} + +QUICRstStreamFrame::QUICRstStreamFrame(const uint8_t *buf, size_t len) +{ + this->parse(buf, len); +} + +void +QUICRstStreamFrame::parse(const uint8_t *buf, size_t len) +{ + ink_assert(len >= 1); + this->_reset(); + uint8_t *pos = 1 + const_cast(buf); + + size_t field_len = 0; + if (!read_varint(pos, LEFT_SPACE(pos), this->_stream_id, field_len)) { + return; + } + + if (LEFT_SPACE(pos) < 2) { + return; + } + + this->_error_code = QUICIntUtil::read_nbytes_as_uint(pos, 2); + pos += 2; + + if (!read_varint(pos, LEFT_SPACE(pos), this->_final_offset, field_len)) { + return; + } + + this->_valid = true; + this->_size = FRAME_SIZE(pos); +} + +void +QUICRstStreamFrame::_reset() +{ + this->_stream_id = 0; + this->_error_code = 0; + this->_final_offset = 0; + + this->_owner = nullptr; + this->_id = 0; + this->_valid = false; + this->_size = 0; +} + +QUICFrameType +QUICRstStreamFrame::type() const +{ + return QUICFrameType::RESET_STREAM; +} + +size_t +QUICRstStreamFrame::size() const +{ + if (this->_size) { + return this->_size; + } + + return 1 + QUICVariableInt::size(this->_stream_id) + sizeof(QUICAppErrorCode) + QUICVariableInt::size(this->_final_offset); +} + +size_t +QUICRstStreamFrame::store(uint8_t *buf, size_t *len, size_t limit) const +{ + if (limit < this->size()) { + return 0; + } + + size_t n; + uint8_t *p = buf; + *p = static_cast(QUICFrameType::RESET_STREAM); + ++p; + QUICTypeUtil::write_QUICStreamId(this->_stream_id, p, &n); + p += n; + QUICTypeUtil::write_QUICAppErrorCode(this->_error_code, p, &n); + p += n; + QUICTypeUtil::write_QUICOffset(this->_final_offset, p, &n); + p += n; + + *len = p - buf; + + return *len; +} + +int +QUICRstStreamFrame::debug_msg(char *msg, size_t msg_len) const +{ + return snprintf(msg, msg_len, "RESET_STREAM size=%zu stream_id=%" PRIu64 " code=0x%" PRIx16, this->size(), this->stream_id(), + this->error_code()); +} + +QUICStreamId +QUICRstStreamFrame::stream_id() const +{ + return this->_stream_id; +} + +QUICAppErrorCode +QUICRstStreamFrame::error_code() const +{ + return this->_error_code; +} + +QUICOffset +QUICRstStreamFrame::final_offset() const +{ + return this->_final_offset; +} + +// +// PING frame +// + +QUICPingFrame::QUICPingFrame(const uint8_t *buf, size_t len) +{ + this->parse(buf, len); +} + +void +QUICPingFrame::parse(const uint8_t *buf, size_t len) +{ + this->_reset(); + this->_valid = true; + this->_size = 1; +} + +QUICFrameType +QUICPingFrame::type() const +{ + return QUICFrameType::PING; +} + +size_t +QUICPingFrame::size() const +{ + return 1; +} + +size_t +QUICPingFrame::store(uint8_t *buf, size_t *len, size_t limit) const +{ + if (limit < this->size()) { + return 0; + } + + *len = this->size(); + buf[0] = static_cast(QUICFrameType::PING); + return *len; +} + +// +// PADDING frame +// +QUICPaddingFrame::QUICPaddingFrame(const uint8_t *buf, size_t len) +{ + this->parse(buf, len); +} + +void +QUICPaddingFrame::parse(const uint8_t *buf, size_t len) +{ + ink_assert(len >= 1); + this->_reset(); + this->_valid = true; + this->_size = 1; +} + +QUICFrameType +QUICPaddingFrame::type() const +{ + return QUICFrameType::PADDING; +} + +size_t +QUICPaddingFrame::size() const +{ + return 1; +} + +bool +QUICPaddingFrame::is_probing_frame() const +{ + return true; +} + +size_t +QUICPaddingFrame::store(uint8_t *buf, size_t *len, size_t limit) const +{ + if (limit < this->size()) { + return 0; + } + + buf[0] = static_cast(QUICFrameType::PADDING); + *len = 1; + return *len; +} + +// +// CONNECTION_CLOSE frame +// +QUICConnectionCloseFrame::QUICConnectionCloseFrame(uint16_t error_code, QUICFrameType frame_type, uint64_t reason_phrase_length, + const char *reason_phrase, QUICFrameId id, QUICFrameGenerator *owner) + : QUICFrame(id, owner), + _type(0x1c), + _error_code(error_code), + _frame_type(frame_type), + _reason_phrase_length(reason_phrase_length), + _reason_phrase(reason_phrase) +{ +} + +QUICConnectionCloseFrame::QUICConnectionCloseFrame(uint16_t error_code, uint64_t reason_phrase_length, const char *reason_phrase, + QUICFrameId id, QUICFrameGenerator *owner) + : QUICFrame(id, owner), + _type(0x1d), + _error_code(error_code), + _reason_phrase_length(reason_phrase_length), + _reason_phrase(reason_phrase) +{ +} + +QUICConnectionCloseFrame::QUICConnectionCloseFrame(const uint8_t *buf, size_t len) +{ + this->parse(buf, len); +} + +void +QUICConnectionCloseFrame::_reset() +{ + this->_error_code = 0; + this->_reason_phrase_length = 0; + this->_reason_phrase = nullptr; + this->_frame_type = QUICFrameType::UNKNOWN; + + this->_owner = nullptr; + this->_id = 0; + this->_size = 0; + this->_valid = false; +} + +void +QUICConnectionCloseFrame::parse(const uint8_t *buf, size_t len) +{ + ink_assert(len >= 1); + this->_reset(); + this->_type = buf[0]; + uint8_t *pos = const_cast(buf) + 1; + + if (LEFT_SPACE(pos) < 2) { + return; + } + + this->_error_code = QUICIntUtil::read_nbytes_as_uint(pos, 2); + pos += 2; + + size_t field_len = 0; + uint64_t field = 0; + + if (this->_type == 0x1c) { + if (!read_varint(pos, LEFT_SPACE(pos), field, field_len)) { + return; + } + + this->_frame_type = static_cast(field); + + /** + Frame Type Field Accessor + + PADDING frame in Frame Type field means frame type that triggered the error is unknown. + Return QUICFrameType::UNKNOWN when Frame Type field is PADDING (0x0). + */ + if (this->_frame_type == QUICFrameType::PADDING) { + this->_frame_type = QUICFrameType::UNKNOWN; + } + } + + if (!read_varint(pos, LEFT_SPACE(pos), this->_reason_phrase_length, field_len)) { + return; + } + + if (LEFT_SPACE(pos) < this->_reason_phrase_length) { + return; + } + + this->_valid = true; + this->_reason_phrase = reinterpret_cast(pos); + pos += this->_reason_phrase_length; + this->_size = FRAME_SIZE(pos); +} + +QUICFrameType +QUICConnectionCloseFrame::type() const +{ + return QUICFrameType::CONNECTION_CLOSE; +} + +size_t +QUICConnectionCloseFrame::size() const +{ + if (this->_size) { + return this->_size; + } + + return 1 + sizeof(QUICTransErrorCode) + QUICVariableInt::size(sizeof(QUICFrameType)) + + QUICVariableInt::size(this->_reason_phrase_length) + this->_reason_phrase_length; +} + +/** + Store CONNECTION_CLOSE frame in buffer. + + PADDING frame in Frame Type field means frame type that triggered the error is unknown. + When `_frame_type` is QUICFrameType::UNKNOWN, it's converted to QUICFrameType::PADDING (0x0). + */ +size_t +QUICConnectionCloseFrame::store(uint8_t *buf, size_t *len, size_t limit) const +{ + if (limit < this->size()) { + return 0; + } + + size_t n; + uint8_t *p = buf; + *p = this->_type; + ++p; + + // Error Code (16) + QUICTypeUtil::write_QUICTransErrorCode(this->_error_code, p, &n); + p += n; + + // Frame Type (i) + QUICFrameType frame_type = this->_frame_type; + if (frame_type == QUICFrameType::UNKNOWN) { + frame_type = QUICFrameType::PADDING; + } + *p = static_cast(frame_type); + ++p; + + // Reason Phrase Length (i) + QUICIntUtil::write_QUICVariableInt(this->_reason_phrase_length, p, &n); + p += n; + + // Reason Phrase (*) + if (this->_reason_phrase_length > 0) { + memcpy(p, this->_reason_phrase, this->_reason_phrase_length); + p += this->_reason_phrase_length; + } + + *len = p - buf; + return *len; +} + +int +QUICConnectionCloseFrame::debug_msg(char *msg, size_t msg_len) const +{ + int len; + if (this->_type == 0x1c) { + len = + snprintf(msg, msg_len, "CONNECTION_CLOSE size=%zu code=%s (0x%" PRIx16 ") frame=%s", this->size(), + QUICDebugNames::error_code(this->error_code()), this->error_code(), QUICDebugNames::frame_type(this->frame_type())); + } else { + // Application-specific error. It doesn't have a frame type and we don't know string representations of error codes. + len = snprintf(msg, msg_len, "CONNECTION_CLOSE size=%zu code=0x%" PRIx16 " ", this->size(), this->error_code()); + } + + if (this->reason_phrase_length() != 0 && this->reason_phrase() != nullptr) { + memcpy(msg + len, " reason=", 8); + len += 8; + + int phrase_len = std::min(msg_len - len, static_cast(this->reason_phrase_length())); + memcpy(msg + len, this->reason_phrase(), phrase_len); + len += phrase_len; + msg[len] = '\0'; + ++len; + } + + return len; +} + +uint16_t +QUICConnectionCloseFrame::error_code() const +{ + return this->_error_code; +} + +QUICFrameType +QUICConnectionCloseFrame::frame_type() const +{ + return this->_frame_type; +} + +uint64_t +QUICConnectionCloseFrame::reason_phrase_length() const +{ + return this->_reason_phrase_length; +} + +const char * +QUICConnectionCloseFrame::reason_phrase() const +{ + return this->_reason_phrase; +} + +// +// MAX_DATA frame +// +QUICMaxDataFrame::QUICMaxDataFrame(uint64_t maximum_data, QUICFrameId id, QUICFrameGenerator *owner) : QUICFrame(id, owner) +{ + this->_maximum_data = maximum_data; +} + +void +QUICMaxDataFrame::_reset() +{ + this->_maximum_data = 0; + + this->_owner = nullptr; + this->_id = 0; + this->_valid = false; + this->_size = 0; +} + +QUICMaxDataFrame::QUICMaxDataFrame(const uint8_t *buf, size_t len) +{ + this->parse(buf, len); +} + +void +QUICMaxDataFrame::parse(const uint8_t *buf, size_t len) +{ + ink_assert(len >= 1); + this->_reset(); + uint8_t *pos = 1 + const_cast(buf); + + size_t field_len = 0; + if (!read_varint(pos, LEFT_SPACE(pos), this->_maximum_data, field_len)) { + return; + } + + this->_valid = true; + this->_size = FRAME_SIZE(pos); +} + +QUICFrameType +QUICMaxDataFrame::type() const +{ + return QUICFrameType::MAX_DATA; +} + +size_t +QUICMaxDataFrame::size() const +{ + if (this->_size) { + return this->_size; + } + + return sizeof(QUICFrameType) + QUICVariableInt::size(this->_maximum_data); +} + +size_t +QUICMaxDataFrame::store(uint8_t *buf, size_t *len, size_t limit) const +{ + if (limit < this->size()) { + return 0; + } + + size_t n; + uint8_t *p = buf; + *p = static_cast(QUICFrameType::MAX_DATA); + ++p; + QUICTypeUtil::write_QUICMaxData(this->_maximum_data, p, &n); + p += n; + + *len = p - buf; + return *len; +} + +int +QUICMaxDataFrame::debug_msg(char *msg, size_t msg_len) const +{ + return snprintf(msg, msg_len, "MAX_DATA size=%zu maximum=%" PRIu64, this->size(), this->maximum_data()); +} + +uint64_t +QUICMaxDataFrame::maximum_data() const +{ + return this->_maximum_data; +} + +// +// MAX_STREAM_DATA +// +QUICMaxStreamDataFrame::QUICMaxStreamDataFrame(QUICStreamId stream_id, uint64_t maximum_stream_data, QUICFrameId id, + QUICFrameGenerator *owner) + : QUICFrame(id, owner) +{ + this->_stream_id = stream_id; + this->_maximum_stream_data = maximum_stream_data; +} + +void +QUICMaxStreamDataFrame::_reset() +{ + this->_stream_id = 0; + this->_maximum_stream_data = 0; + + this->_owner = nullptr; + this->_id = 0; + this->_valid = false; + this->_size = 0; +} + +QUICMaxStreamDataFrame::QUICMaxStreamDataFrame(const uint8_t *buf, size_t len) +{ + this->parse(buf, len); +} + +void +QUICMaxStreamDataFrame::parse(const uint8_t *buf, size_t len) +{ + ink_assert(len >= 1); + this->_reset(); + uint8_t *pos = const_cast(buf) + 1; + + size_t field_len = 0; + if (!read_varint(pos, LEFT_SPACE(pos), this->_stream_id, field_len)) { + return; + } + + if (!read_varint(pos, LEFT_SPACE(pos), this->_maximum_stream_data, field_len)) { + return; + } + + this->_valid = true; + this->_size = FRAME_SIZE(pos); +} + +QUICFrameType +QUICMaxStreamDataFrame::type() const +{ + return QUICFrameType::MAX_STREAM_DATA; +} + +size_t +QUICMaxStreamDataFrame::size() const +{ + if (this->_size) { + return this->_size; + } + + return sizeof(QUICFrameType) + QUICVariableInt::size(this->_maximum_stream_data) + QUICVariableInt::size(this->_stream_id); +} + +size_t +QUICMaxStreamDataFrame::store(uint8_t *buf, size_t *len, size_t limit) const +{ + if (limit < this->size()) { + return 0; + } + size_t n; + uint8_t *p = buf; + *p = static_cast(QUICFrameType::MAX_STREAM_DATA); + ++p; + QUICTypeUtil::write_QUICStreamId(this->_stream_id, p, &n); + p += n; + QUICTypeUtil::write_QUICMaxData(this->_maximum_stream_data, p, &n); + p += n; + + *len = p - buf; + return *len; +} + +int +QUICMaxStreamDataFrame::debug_msg(char *msg, size_t msg_len) const +{ + return snprintf(msg, msg_len, "MAX_STREAM_DATA size=%zu id=%" PRIu64 " maximum=%" PRIu64, this->size(), this->stream_id(), + this->maximum_stream_data()); +} + +QUICStreamId +QUICMaxStreamDataFrame::stream_id() const +{ + return this->_stream_id; +} + +uint64_t +QUICMaxStreamDataFrame::maximum_stream_data() const +{ + return this->_maximum_stream_data; +} + +// +// MAX_STREAMS +// +QUICMaxStreamsFrame::QUICMaxStreamsFrame(QUICStreamId maximum_streams, QUICFrameId id, QUICFrameGenerator *owner) + : QUICFrame(id, owner) +{ + this->_maximum_streams = maximum_streams; +} + +void +QUICMaxStreamsFrame::_reset() +{ + this->_maximum_streams = 0; + + this->_owner = nullptr; + this->_id = 0; + this->_valid = false; + this->_size = 0; +} + +QUICMaxStreamsFrame::QUICMaxStreamsFrame(const uint8_t *buf, size_t len) +{ + this->parse(buf, len); +} + +void +QUICMaxStreamsFrame::parse(const uint8_t *buf, size_t len) +{ + ink_assert(len >= 1); + this->_reset(); + uint8_t *pos = const_cast(buf) + 1; + + size_t field_len = 0; + if (!read_varint(pos, LEFT_SPACE(pos), this->_maximum_streams, field_len)) { + return; + } + + this->_valid = true; + this->_size = FRAME_SIZE(pos); +} + +QUICFrameType +QUICMaxStreamsFrame::type() const +{ + return QUICFrameType::MAX_STREAMS; +} + +size_t +QUICMaxStreamsFrame::size() const +{ + if (this->_size) { + return this->_size; + } + + return sizeof(QUICFrameType) + QUICVariableInt::size(this->_maximum_streams); +} + +size_t +QUICMaxStreamsFrame::store(uint8_t *buf, size_t *len, size_t limit) const +{ + if (limit < this->size()) { + return 0; + } + + size_t n; + uint8_t *p = buf; + *p = static_cast(QUICFrameType::MAX_STREAMS); + ++p; + QUICTypeUtil::write_QUICStreamId(this->_maximum_streams, p, &n); + p += n; + + *len = p - buf; + return *len; +} + +uint64_t +QUICMaxStreamsFrame::maximum_streams() const +{ + return this->_maximum_streams; +} + +// +// DATA_BLOCKED frame +// +QUICDataBlockedFrame::QUICDataBlockedFrame(const uint8_t *buf, size_t len) +{ + this->parse(buf, len); +} + +void +QUICDataBlockedFrame::_reset() +{ + this->_offset = 0; + + this->_owner = nullptr; + this->_id = 0; + this->_valid = false; + this->_size = 0; +} + +void +QUICDataBlockedFrame::parse(const uint8_t *buf, size_t len) +{ + ink_assert(len >= 1); + this->_reset(); + uint8_t *pos = const_cast(buf) + 1; + + size_t field_len = 0; + if (!read_varint(pos, LEFT_SPACE(pos), this->_offset, field_len)) { + return; + } + + this->_valid = true; + this->_size = FRAME_SIZE(pos); +} + +int +QUICDataBlockedFrame::debug_msg(char *msg, size_t msg_len) const +{ + return snprintf(msg, msg_len, "DATA_BLOCKED size=%zu offset=%" PRIu64, this->size(), this->offset()); +} + +QUICFrameType +QUICDataBlockedFrame::type() const +{ + return QUICFrameType::DATA_BLOCKED; +} + +size_t +QUICDataBlockedFrame::size() const +{ + if (this->_size) { + return this->_size; + } + + return sizeof(QUICFrameType) + QUICVariableInt::size(this->offset()); +} + +size_t +QUICDataBlockedFrame::store(uint8_t *buf, size_t *len, size_t limit) const +{ + if (limit < this->size()) { + return 0; + } + + size_t n; + uint8_t *p = buf; + + *p = static_cast(QUICFrameType::DATA_BLOCKED); + ++p; + QUICTypeUtil::write_QUICOffset(this->_offset, p, &n); + p += n; + + *len = p - buf; + + return *len; +} + +QUICOffset +QUICDataBlockedFrame::offset() const +{ + return this->_offset; +} + +// +// STREAM_DATA_BLOCKED frame +// +QUICStreamDataBlockedFrame::QUICStreamDataBlockedFrame(const uint8_t *buf, size_t len) +{ + this->parse(buf, len); +} + +void +QUICStreamDataBlockedFrame::_reset() +{ + this->_stream_id = 0; + this->_offset = 0; + + this->_owner = nullptr; + this->_id = 0; + this->_valid = false; + this->_size = 0; +} + +void +QUICStreamDataBlockedFrame::parse(const uint8_t *buf, size_t len) +{ + ink_assert(len >= 1); + this->_reset(); + uint8_t *pos = const_cast(buf) + 1; + + size_t field_len = 0; + if (!read_varint(pos, LEFT_SPACE(pos), this->_stream_id, field_len)) { + return; + } + + if (!read_varint(pos, LEFT_SPACE(pos), this->_offset, field_len)) { + return; + } + + this->_valid = true; + this->_size = FRAME_SIZE(pos); +} + +int +QUICStreamDataBlockedFrame::debug_msg(char *msg, size_t msg_len) const +{ + return snprintf(msg, msg_len, "STREAM_DATA_BLOCKED size=%zu id=%" PRIu64 " offset=%" PRIu64, this->size(), this->stream_id(), + this->offset()); +} + +QUICFrameType +QUICStreamDataBlockedFrame::type() const +{ + return QUICFrameType::STREAM_DATA_BLOCKED; +} + +size_t +QUICStreamDataBlockedFrame::size() const +{ + if (this->_size) { + return this->_size; + } + + return sizeof(QUICFrameType) + QUICVariableInt::size(this->_offset) + QUICVariableInt::size(this->_stream_id); +} + +size_t +QUICStreamDataBlockedFrame::store(uint8_t *buf, size_t *len, size_t limit) const +{ + if (limit < this->size()) { + return 0; + } + + size_t n; + uint8_t *p = buf; + *p = static_cast(QUICFrameType::STREAM_DATA_BLOCKED); + ++p; + QUICTypeUtil::write_QUICStreamId(this->_stream_id, p, &n); + p += n; + QUICTypeUtil::write_QUICOffset(this->_offset, p, &n); + p += n; + + *len = p - buf; + + return *len; +} + +QUICStreamId +QUICStreamDataBlockedFrame::stream_id() const +{ + return this->_stream_id; +} + +QUICOffset +QUICStreamDataBlockedFrame::offset() const +{ + return this->_offset; +} + +// +// STREAMS_BLOCKED frame +// +QUICStreamIdBlockedFrame::QUICStreamIdBlockedFrame(const uint8_t *buf, size_t len) +{ + this->parse(buf, len); +} + +void +QUICStreamIdBlockedFrame::_reset() +{ + this->_stream_id = 0; + + this->_owner = nullptr; + this->_id = 0; + this->_size = 0; + this->_valid = false; +} + +void +QUICStreamIdBlockedFrame::parse(const uint8_t *buf, size_t len) +{ + ink_assert(len >= 1); + this->_reset(); + uint8_t *pos = const_cast(buf) + 1; + + size_t field_len = 0; + if (!read_varint(pos, LEFT_SPACE(pos), this->_stream_id, field_len)) { + return; + } + + this->_valid = true; + this->_size = FRAME_SIZE(pos); +} + +QUICFrameType +QUICStreamIdBlockedFrame::type() const +{ + return QUICFrameType::STREAMS_BLOCKED; +} + +size_t +QUICStreamIdBlockedFrame::size() const +{ + if (this->_size) { + return this->_size; + } + + return sizeof(QUICFrameType) + QUICVariableInt::size(this->_stream_id); +} + +size_t +QUICStreamIdBlockedFrame::store(uint8_t *buf, size_t *len, size_t limit) const +{ + if (limit < this->size()) { + return 0; + } + + size_t n; + uint8_t *p = buf; + + *p = static_cast(QUICFrameType::STREAMS_BLOCKED); + ++p; + QUICTypeUtil::write_QUICStreamId(this->_stream_id, p, &n); + p += n; + + *len = p - buf; + return *len; +} + +QUICStreamId +QUICStreamIdBlockedFrame::stream_id() const +{ + return this->_stream_id; +} + +// +// NEW_CONNECTION_ID frame +// +QUICNewConnectionIdFrame::QUICNewConnectionIdFrame(const uint8_t *buf, size_t len) +{ + this->parse(buf, len); +} + +void +QUICNewConnectionIdFrame::_reset() +{ + this->_sequence = 0; + this->_connection_id = QUICConnectionId::ZERO(); + + this->_owner = nullptr; + this->_id = 0; + this->_valid = false; + this->_size = 0; +} + +void +QUICNewConnectionIdFrame::parse(const uint8_t *buf, size_t len) +{ + ink_assert(len >= 1); + this->_reset(); + uint8_t *pos = const_cast(buf) + 1; + + size_t field_len = 0; + if (!read_varint(pos, LEFT_SPACE(pos), this->_sequence, field_len)) { + return; + } + + if (LEFT_SPACE(pos) < 1) { + return; + } + + size_t cid_len = *pos; + pos += 1; + + if (LEFT_SPACE(pos) < cid_len) { + return; + } + + this->_connection_id = QUICTypeUtil::read_QUICConnectionId(pos, cid_len); + pos += cid_len; + + if (LEFT_SPACE(pos) < 16) { + return; + } + + this->_stateless_reset_token = QUICStatelessResetToken(pos); + this->_valid = true; + this->_size = FRAME_SIZE(pos) + 16; +} + +QUICFrameType +QUICNewConnectionIdFrame::type() const +{ + return QUICFrameType::NEW_CONNECTION_ID; +} + +size_t +QUICNewConnectionIdFrame::size() const +{ + if (this->_size) { + return this->_size; + } + + return sizeof(QUICFrameType) + QUICVariableInt::size(this->_sequence) + 1 + this->_connection_id.length() + 16; +} + +size_t +QUICNewConnectionIdFrame::store(uint8_t *buf, size_t *len, size_t limit) const +{ + if (limit < this->size()) { + return 0; + } + + size_t n; + uint8_t *p = buf; + *p = static_cast(QUICFrameType::NEW_CONNECTION_ID); + ++p; + QUICIntUtil::write_QUICVariableInt(this->_sequence, p, &n); + p += n; + *p = this->_connection_id.length(); + p += 1; + QUICTypeUtil::write_QUICConnectionId(this->_connection_id, p, &n); + p += n; + memcpy(p, this->_stateless_reset_token.buf(), QUICStatelessResetToken::LEN); + p += QUICStatelessResetToken::LEN; + + *len = p - buf; + return *len; +} + +int +QUICNewConnectionIdFrame::debug_msg(char *msg, size_t msg_len) const +{ + char cid_str[QUICConnectionId::MAX_HEX_STR_LENGTH]; + this->connection_id().hex(cid_str, QUICConnectionId::MAX_HEX_STR_LENGTH); + + return snprintf(msg, msg_len, "NEW_CONNECTION_ID size=%zu seq=%" PRIu64 " cid=0x%s", this->size(), this->sequence(), cid_str); +} + +uint64_t +QUICNewConnectionIdFrame::sequence() const +{ + return this->_sequence; +} + +QUICConnectionId +QUICNewConnectionIdFrame::connection_id() const +{ + return this->_connection_id; +} + +QUICStatelessResetToken +QUICNewConnectionIdFrame::stateless_reset_token() const +{ + return this->_stateless_reset_token; +} + +// +// STOP_SENDING frame +// + +QUICStopSendingFrame::QUICStopSendingFrame(QUICStreamId stream_id, QUICAppErrorCode error_code, QUICFrameId id, + QUICFrameGenerator *owner) + : QUICFrame(id, owner), _stream_id(stream_id), _error_code(error_code) +{ +} + +void +QUICStopSendingFrame::_reset() +{ + this->_stream_id = 0; + this->_error_code = 0; + + this->_owner = nullptr; + this->_id = 0; + this->_valid = false; + this->_size = 0; +} + +QUICStopSendingFrame::QUICStopSendingFrame(const uint8_t *buf, size_t len) +{ + this->parse(buf, len); +} + +void +QUICStopSendingFrame::parse(const uint8_t *buf, size_t len) +{ + ink_assert(len >= 1); + this->_reset(); + uint8_t *pos = const_cast(buf) + 1; + + size_t field_len = 0; + if (!read_varint(pos, LEFT_SPACE(pos), this->_stream_id, field_len)) { + return; + } + + if (LEFT_SPACE(pos) < 2) { + return; + } + + this->_error_code = static_cast(QUICIntUtil::read_nbytes_as_uint(pos, 2)); + this->_valid = true; + this->_size = FRAME_SIZE(pos) + 2; +} + +QUICFrameType +QUICStopSendingFrame::type() const +{ + return QUICFrameType::STOP_SENDING; +} + +size_t +QUICStopSendingFrame::size() const +{ + if (this->_size) { + return this->_size; + } + + return sizeof(QUICFrameType) + QUICVariableInt::size(this->_stream_id) + sizeof(QUICAppErrorCode); +} + +size_t +QUICStopSendingFrame::store(uint8_t *buf, size_t *len, size_t limit) const +{ + if (limit < this->size()) { + return 0; + } + + size_t n; + uint8_t *p = buf; + *p = static_cast(QUICFrameType::STOP_SENDING); + ++p; + QUICTypeUtil::write_QUICStreamId(this->_stream_id, p, &n); + p += n; + QUICTypeUtil::write_QUICAppErrorCode(this->_error_code, p, &n); + p += n; + + *len = p - buf; + return *len; +} + +QUICAppErrorCode +QUICStopSendingFrame::error_code() const +{ + return this->_error_code; +} + +QUICStreamId +QUICStopSendingFrame::stream_id() const +{ + return this->_stream_id; +} + +// +// PATH_CHALLENGE frame +// +QUICPathChallengeFrame::QUICPathChallengeFrame(const uint8_t *buf, size_t len) +{ + this->parse(buf, len); +} + +void +QUICPathChallengeFrame::_reset() +{ + this->_data = nullptr; + this->_owner = nullptr; + this->_id = 0; + this->_valid = false; + this->_size = 0; +} + +void +QUICPathChallengeFrame::parse(const uint8_t *buf, size_t len) +{ + ink_assert(len >= 1); + this->_reset(); + uint8_t *pos = const_cast(buf) + 1; + + if (LEFT_SPACE(pos) < QUICPathChallengeFrame::DATA_LEN) { + return; + } + + this->_data = ats_unique_malloc(QUICPathChallengeFrame::DATA_LEN); + memcpy(this->_data.get(), pos, QUICPathChallengeFrame::DATA_LEN); + this->_valid = true; + this->_size = FRAME_SIZE(pos) + QUICPathChallengeFrame::DATA_LEN; +} + +QUICFrameType +QUICPathChallengeFrame::type() const +{ + return QUICFrameType::PATH_CHALLENGE; +} + +size_t +QUICPathChallengeFrame::size() const +{ + if (this->_size) { + return this->_size; + } + + return 1 + QUICPathChallengeFrame::DATA_LEN; +} + +bool +QUICPathChallengeFrame::is_probing_frame() const +{ + return true; +} + +size_t +QUICPathChallengeFrame::store(uint8_t *buf, size_t *len, size_t limit) const +{ + if (limit < this->size()) { + return 0; + } + + *len = this->size(); + + buf[0] = static_cast(QUICFrameType::PATH_CHALLENGE); + memcpy(buf + 1, this->data(), QUICPathChallengeFrame::DATA_LEN); + + return *len; +} + +const uint8_t * +QUICPathChallengeFrame::data() const +{ + return this->_data.get(); +} + +// +// PATH_RESPONSE frame +// +QUICPathResponseFrame::QUICPathResponseFrame(const uint8_t *buf, size_t len) +{ + this->parse(buf, len); +} + +void +QUICPathResponseFrame::_reset() +{ + this->_data = nullptr; + this->_owner = nullptr; + this->_id = 0; + this->_valid = false; + this->_size = 0; +} + +void +QUICPathResponseFrame::parse(const uint8_t *buf, size_t len) +{ + ink_assert(len >= 1); + this->_reset(); + uint8_t *pos = const_cast(buf) + 1; + + if (LEFT_SPACE(pos) < QUICPathChallengeFrame::DATA_LEN) { + return; + } + + this->_data = ats_unique_malloc(QUICPathChallengeFrame::DATA_LEN); + memcpy(this->_data.get(), pos, QUICPathChallengeFrame::DATA_LEN); + this->_valid = true; + this->_size = FRAME_SIZE(pos) + QUICPathChallengeFrame::DATA_LEN; +} + +QUICFrameType +QUICPathResponseFrame::type() const +{ + return QUICFrameType::PATH_RESPONSE; +} + +size_t +QUICPathResponseFrame::size() const +{ + return 1 + 8; +} + +bool +QUICPathResponseFrame::is_probing_frame() const +{ + return true; +} + +size_t +QUICPathResponseFrame::store(uint8_t *buf, size_t *len, size_t limit) const +{ + if (limit < this->size()) { + return 0; + } + + *len = this->size(); + + buf[0] = static_cast(QUICFrameType::PATH_RESPONSE); + memcpy(buf + 1, this->data(), QUICPathResponseFrame::DATA_LEN); + + return *len; +} + +const uint8_t * +QUICPathResponseFrame::data() const +{ + return this->_data.get(); +} + +// +// QUICNewTokenFrame +// +QUICNewTokenFrame::QUICNewTokenFrame(const uint8_t *buf, size_t len) +{ + this->parse(buf, len); +} + +void +QUICNewTokenFrame::_reset() +{ + this->_token = nullptr; + this->_token_length = 0; + + this->_owner = nullptr; + this->_id = 0; + this->_valid = false; + this->_size = 0; +} + +void +QUICNewTokenFrame::parse(const uint8_t *buf, size_t len) +{ + ink_assert(len >= 1); + this->_reset(); + uint8_t *pos = const_cast(buf) + 1; + + size_t field_len = 0; + if (!read_varint(pos, LEFT_SPACE(pos), this->_token_length, field_len)) { + return; + } + + if (LEFT_SPACE(pos) < this->_token_length) { + return; + } + + this->_token = ats_unique_malloc(this->_token_length); + memcpy(this->_token.get(), pos, this->_token_length); + this->_valid = true; + this->_size = FRAME_SIZE(pos) + this->_token_length; +} + +QUICFrameType +QUICNewTokenFrame::type() const +{ + return QUICFrameType::NEW_TOKEN; +} + +size_t +QUICNewTokenFrame::size() const +{ + if (this->_size) { + return this->_size; + } + + return 1 + QUICVariableInt::size(this->_token_length) + this->token_length(); +} + +size_t +QUICNewTokenFrame::store(uint8_t *buf, size_t *len, size_t limit) const +{ + if (limit < this->size()) { + return 0; + } + + uint8_t *p = buf; + + // Type (i) + *p = static_cast(QUICFrameType::NEW_TOKEN); + ++p; + + // Token Length (i) + size_t n; + QUICIntUtil::write_QUICVariableInt(this->_token_length, p, &n); + p += n; + + // Token (*) + memcpy(p, this->token(), this->token_length()); + p += this->token_length(); + + *len = p - buf; + return *len; +} + +uint64_t +QUICNewTokenFrame::token_length() const +{ + return this->_token_length; +} + +const uint8_t * +QUICNewTokenFrame::token() const +{ + return this->_token.get(); +} + +// +// RETIRE_CONNECTION_ID frame +// +QUICRetireConnectionIdFrame::QUICRetireConnectionIdFrame(const uint8_t *buf, size_t len) +{ + this->parse(buf, len); +} + +void +QUICRetireConnectionIdFrame::_reset() +{ + this->_seq_num = 0; + + this->_owner = nullptr; + this->_id = 0; + this->_valid = false; + this->_size = 0; + this->_size = 0; +} + +void +QUICRetireConnectionIdFrame::parse(const uint8_t *buf, size_t len) +{ + ink_assert(len >= 1); + this->_reset(); + uint8_t *pos = const_cast(buf) + 1; + + size_t field_len = 0; + if (!read_varint(pos, LEFT_SPACE(pos), this->_seq_num, field_len)) { + return; + } + + this->_valid = true; + this->_size = FRAME_SIZE(pos); +} + +QUICFrameType +QUICRetireConnectionIdFrame::type() const +{ + return QUICFrameType::RETIRE_CONNECTION_ID; +} + +size_t +QUICRetireConnectionIdFrame::size() const +{ + if (this->_size) { + return this->_size; + } + + return sizeof(QUICFrameType) + QUICVariableInt::size(this->_seq_num); +} + +size_t +QUICRetireConnectionIdFrame::store(uint8_t *buf, size_t *len, size_t limit) const +{ + if (limit < this->size()) { + return 0; + } + + size_t n; + uint8_t *p = buf; + *p = static_cast(QUICFrameType::RETIRE_CONNECTION_ID); + ++p; + QUICIntUtil::write_QUICVariableInt(this->_seq_num, p, &n); + p += n; + + *len = p - buf; + + return *len; +} + +int +QUICRetireConnectionIdFrame::debug_msg(char *msg, size_t msg_len) const +{ + return snprintf(msg, msg_len, "RETIRE_CONNECTION_ID size=%zu seq_num=%" PRIu64, this->size(), this->seq_num()); +} + +uint64_t +QUICRetireConnectionIdFrame::seq_num() const +{ + return this->_seq_num; +} + +// +// UNKNOWN +// +QUICFrameType +QUICUnknownFrame::type() const +{ + return QUICFrameType::UNKNOWN; +} + +size_t +QUICUnknownFrame::size() const +{ + // FIXME size should be readable + return 0; +} + +size_t +QUICUnknownFrame::store(uint8_t *buf, size_t *len, size_t limit) const +{ + return 0; +} + +void +QUICUnknownFrame::parse(const uint8_t *buf, size_t len) +{ +} + +int +QUICUnknownFrame::debug_msg(char *msg, size_t msg_len) const +{ + return 0; +} + +// +// QUICFrameFactory +// + +QUICFrame * +QUICFrameFactory::create(uint8_t *buf, const uint8_t *src, size_t len) +{ + switch (QUICFrame::type(src)) { + case QUICFrameType::STREAM: + new (buf) QUICStreamFrame(src, len); + return reinterpret_cast(buf); + case QUICFrameType::CRYPTO: + new (buf) QUICCryptoFrame(src, len); + return reinterpret_cast(buf); + case QUICFrameType::ACK: + new (buf) QUICAckFrame(src, len); + return reinterpret_cast(buf); + case QUICFrameType::PADDING: + new (buf) QUICPaddingFrame(src, len); + return reinterpret_cast(buf); + case QUICFrameType::RESET_STREAM: + new (buf) QUICRstStreamFrame(src, len); + return reinterpret_cast(buf); + case QUICFrameType::CONNECTION_CLOSE: + new (buf) QUICConnectionCloseFrame(src, len); + return reinterpret_cast(buf); + case QUICFrameType::MAX_DATA: + new (buf) QUICMaxDataFrame(src, len); + return reinterpret_cast(buf); + case QUICFrameType::MAX_STREAM_DATA: + new (buf) QUICMaxStreamDataFrame(src, len); + return reinterpret_cast(buf); + case QUICFrameType::MAX_STREAMS: + new (buf) QUICMaxStreamsFrame(src, len); + return reinterpret_cast(buf); + case QUICFrameType::PING: + new (buf) QUICPingFrame(src, len); + return reinterpret_cast(buf); + case QUICFrameType::DATA_BLOCKED: + new (buf) QUICDataBlockedFrame(src, len); + return reinterpret_cast(buf); + case QUICFrameType::STREAM_DATA_BLOCKED: + new (buf) QUICStreamDataBlockedFrame(src, len); + return reinterpret_cast(buf); + case QUICFrameType::STREAMS_BLOCKED: + new (buf) QUICStreamIdBlockedFrame(src, len); + return reinterpret_cast(buf); + case QUICFrameType::NEW_CONNECTION_ID: + new (buf) QUICNewConnectionIdFrame(src, len); + return reinterpret_cast(buf); + case QUICFrameType::STOP_SENDING: + new (buf) QUICStopSendingFrame(src, len); + return reinterpret_cast(buf); + case QUICFrameType::PATH_CHALLENGE: + new (buf) QUICPathChallengeFrame(src, len); + return reinterpret_cast(buf); + case QUICFrameType::PATH_RESPONSE: + new (buf) QUICPathResponseFrame(src, len); + return reinterpret_cast(buf); + case QUICFrameType::NEW_TOKEN: + new (buf) QUICNewTokenFrame(src, len); + return reinterpret_cast(buf); + case QUICFrameType::RETIRE_CONNECTION_ID: + new (buf) QUICRetireConnectionIdFrame(src, len); + return reinterpret_cast(buf); + default: + // Unknown frame + Debug("quic_frame_factory", "Unknown frame type %x", src[0]); + return nullptr; + } +} + +const QUICFrame & +QUICFrameFactory::fast_create(const uint8_t *buf, size_t len) +{ + if (QUICFrame::type(buf) == QUICFrameType::UNKNOWN) { + return this->_unknown_frame; + } + + ptrdiff_t type_index = static_cast(QUICFrame::type(buf)); + QUICFrame *frame = this->_reusable_frames[type_index]; + + if (frame == nullptr) { + frame = QUICFrameFactory::create(this->_buf_for_fast_create + (type_index * QUICFrame::MAX_INSTANCE_SIZE), buf, len); + if (frame != nullptr) { + this->_reusable_frames[static_cast(QUICFrame::type(buf))] = frame; + } + } else { + frame->parse(buf, len); + } + + return *frame; +} + +QUICStreamFrame * +QUICFrameFactory::create_stream_frame(uint8_t *buf, Ptr &block, QUICStreamId stream_id, QUICOffset offset, bool last, + bool has_offset_field, bool has_length_field, QUICFrameId id, QUICFrameGenerator *owner) +{ + Ptr new_block = make_ptr(block->clone()); + new (buf) QUICStreamFrame(new_block, stream_id, offset, last, has_offset_field, has_length_field, id, owner); + return reinterpret_cast(buf); +} + +QUICCryptoFrame * +QUICFrameFactory::create_crypto_frame(uint8_t *buf, Ptr &block, QUICOffset offset, QUICFrameId id, + QUICFrameGenerator *owner) +{ + Ptr new_block = make_ptr(block->clone()); + new (buf) QUICCryptoFrame(new_block, offset, id, owner); + return reinterpret_cast(buf); +} + +QUICAckFrame * +QUICFrameFactory::create_ack_frame(uint8_t *buf, QUICPacketNumber largest_acknowledged, uint64_t ack_delay, + uint64_t first_ack_block, QUICFrameId id, QUICFrameGenerator *owner) +{ + new (buf) QUICAckFrame(largest_acknowledged, ack_delay, first_ack_block, id, owner); + return reinterpret_cast(buf); +} + +QUICConnectionCloseFrame * +QUICFrameFactory::create_connection_close_frame(uint8_t *buf, uint16_t error_code, QUICFrameType frame_type, + uint16_t reason_phrase_length, const char *reason_phrase, QUICFrameId id, + QUICFrameGenerator *owner) +{ + new (buf) QUICConnectionCloseFrame(error_code, frame_type, reason_phrase_length, reason_phrase, id, owner); + return reinterpret_cast(buf); +} + +QUICConnectionCloseFrame * +QUICFrameFactory::create_connection_close_frame(uint8_t *buf, QUICConnectionError &error, QUICFrameId id, QUICFrameGenerator *owner) +{ + ink_assert(error.cls == QUICErrorClass::TRANSPORT); + if (error.msg) { + return QUICFrameFactory::create_connection_close_frame(buf, error.code, error.frame_type(), strlen(error.msg), error.msg, id, + owner); + } else { + return QUICFrameFactory::create_connection_close_frame(buf, error.code, error.frame_type(), 0, nullptr, id, owner); + } +} + +QUICMaxDataFrame * +QUICFrameFactory::create_max_data_frame(uint8_t *buf, uint64_t maximum_data, QUICFrameId id, QUICFrameGenerator *owner) +{ + new (buf) QUICMaxDataFrame(maximum_data, id, owner); + return reinterpret_cast(buf); +} +QUICMaxStreamDataFrame * +QUICFrameFactory::create_max_stream_data_frame(uint8_t *buf, QUICStreamId stream_id, uint64_t maximum_data, QUICFrameId id, + QUICFrameGenerator *owner) +{ + new (buf) QUICMaxStreamDataFrame(stream_id, maximum_data, id, owner); + return reinterpret_cast(buf); +} + +QUICMaxStreamsFrame * +QUICFrameFactory::create_max_streams_frame(uint8_t *buf, QUICStreamId maximum_streams, QUICFrameId id, QUICFrameGenerator *owner) +{ + new (buf) QUICMaxStreamsFrame(maximum_streams, id, owner); + return reinterpret_cast(buf); +} + +QUICPingFrame * +QUICFrameFactory::create_ping_frame(uint8_t *buf, QUICFrameId id, QUICFrameGenerator *owner) +{ + new (buf) QUICPingFrame(id, owner); + return reinterpret_cast(buf); +} + +QUICPathChallengeFrame * +QUICFrameFactory::create_path_challenge_frame(uint8_t *buf, const uint8_t *data, QUICFrameId id, QUICFrameGenerator *owner) +{ + ats_unique_buf challenge_data = ats_unique_malloc(QUICPathChallengeFrame::DATA_LEN); + memcpy(challenge_data.get(), data, QUICPathChallengeFrame::DATA_LEN); + + new (buf) QUICPathChallengeFrame(std::move(challenge_data), id, owner); + return reinterpret_cast(buf); +} + +QUICPathResponseFrame * +QUICFrameFactory::create_path_response_frame(uint8_t *buf, const uint8_t *data, QUICFrameId id, QUICFrameGenerator *owner) +{ + ats_unique_buf response_data = ats_unique_malloc(QUICPathResponseFrame::DATA_LEN); + memcpy(response_data.get(), data, QUICPathResponseFrame::DATA_LEN); + + new (buf) QUICPathResponseFrame(std::move(response_data), id, owner); + return reinterpret_cast(buf); +} + +QUICDataBlockedFrame * +QUICFrameFactory::create_data_blocked_frame(uint8_t *buf, QUICOffset offset, QUICFrameId id, QUICFrameGenerator *owner) +{ + new (buf) QUICDataBlockedFrame(offset, id, owner); + return reinterpret_cast(buf); +} + +QUICStreamDataBlockedFrame * +QUICFrameFactory::create_stream_data_blocked_frame(uint8_t *buf, QUICStreamId stream_id, QUICOffset offset, QUICFrameId id, + QUICFrameGenerator *owner) +{ + new (buf) QUICStreamDataBlockedFrame(stream_id, offset, id, owner); + return reinterpret_cast(buf); +} + +QUICStreamIdBlockedFrame * +QUICFrameFactory::create_stream_id_blocked_frame(uint8_t *buf, QUICStreamId stream_id, QUICFrameId id, QUICFrameGenerator *owner) +{ + new (buf) QUICStreamIdBlockedFrame(stream_id, id, owner); + return reinterpret_cast(buf); +} + +QUICRstStreamFrame * +QUICFrameFactory::create_rst_stream_frame(uint8_t *buf, QUICStreamId stream_id, QUICAppErrorCode error_code, + QUICOffset final_offset, QUICFrameId id, QUICFrameGenerator *owner) +{ + new (buf) QUICRstStreamFrame(stream_id, error_code, final_offset, id, owner); + return reinterpret_cast(buf); +} + +QUICRstStreamFrame * +QUICFrameFactory::create_rst_stream_frame(uint8_t *buf, QUICStreamError &error, QUICFrameId id, QUICFrameGenerator *owner) +{ + return QUICFrameFactory::create_rst_stream_frame(buf, error.stream->id(), error.code, error.stream->final_offset(), id, owner); +} + +QUICStopSendingFrame * +QUICFrameFactory::create_stop_sending_frame(uint8_t *buf, QUICStreamId stream_id, QUICAppErrorCode error_code, QUICFrameId id, + QUICFrameGenerator *owner) +{ + new (buf) QUICStopSendingFrame(stream_id, error_code, id, owner); + return reinterpret_cast(buf); +} + +QUICNewConnectionIdFrame * +QUICFrameFactory::create_new_connection_id_frame(uint8_t *buf, uint32_t sequence, QUICConnectionId connectoin_id, + QUICStatelessResetToken stateless_reset_token, QUICFrameId id, + QUICFrameGenerator *owner) +{ + new (buf) QUICNewConnectionIdFrame(sequence, connectoin_id, stateless_reset_token, id, owner); + return reinterpret_cast(buf); +} + +QUICNewTokenFrame * +QUICFrameFactory::create_new_token_frame(uint8_t *buf, const QUICResumptionToken &token, QUICFrameId id, QUICFrameGenerator *owner) +{ + uint64_t token_len = token.length(); + ats_unique_buf token_buf = ats_unique_malloc(token_len); + memcpy(token_buf.get(), token.buf(), token_len); + + new (buf) QUICNewTokenFrame(std::move(token_buf), token_len, id, owner); + return reinterpret_cast(buf); +} + +QUICRetireConnectionIdFrame * +QUICFrameFactory::create_retire_connection_id_frame(uint8_t *buf, uint64_t seq_num, QUICFrameId id, QUICFrameGenerator *owner) +{ + new (buf) QUICRetireConnectionIdFrame(seq_num, id, owner); + return reinterpret_cast(buf); +} + +QUICFrameId +QUICFrameInfo::id() const +{ + return this->_id; +} + +QUICFrameGenerator * +QUICFrameInfo::generated_by() const +{ + return this->_generator; +} diff --git a/iocore/net/quic/QUICFrame.h b/iocore/net/quic/QUICFrame.h new file mode 100644 index 00000000000..20f1bfe035d --- /dev/null +++ b/iocore/net/quic/QUICFrame.h @@ -0,0 +1,869 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include +#include "tscore/Allocator.h" +#include "tscore/List.h" +#include "tscore/Ptr.h" +#include "I_IOBuffer.h" +#include +#include + +#include "QUICTypes.h" + +class QUICFrame; +class QUICStreamFrame; +class QUICCryptoFrame; +class QUICPacket; +class QUICFrameGenerator; + +using QUICFrameId = uint64_t; + +class QUICFrame +{ +public: + constexpr static int MAX_INSTANCE_SIZE = 256; + + virtual ~QUICFrame() {} + static QUICFrameType type(const uint8_t *buf); + + QUICFrameId id() const; + + virtual QUICFrameType type() const; + virtual size_t size() const = 0; + virtual bool is_probing_frame() const; + virtual bool is_flow_controlled() const; + virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const = 0; + virtual Ptr to_io_buffer_block(size_t limit) const; + virtual int debug_msg(char *msg, size_t msg_len) const; + virtual void parse(const uint8_t *buf, size_t len){}; + virtual QUICFrameGenerator *generated_by(); + bool valid() const; + LINK(QUICFrame, link); + +protected: + virtual void _reset(){}; + QUICFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : _id(id), _owner(owner) {} + size_t _size = 0; + bool _valid = false; + QUICFrameId _id = 0; + QUICFrameGenerator *_owner = nullptr; +}; + +// +// STREAM Frame +// + +class QUICStreamFrame : public QUICFrame +{ +public: + QUICStreamFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {} + QUICStreamFrame(const uint8_t *buf, size_t len); + QUICStreamFrame(Ptr &block, QUICStreamId streamid, QUICOffset offset, bool last = false, + bool has_offset_field = true, bool has_length_field = true, QUICFrameId id = 0, + QUICFrameGenerator *owner = nullptr); + QUICStreamFrame(const QUICStreamFrame &o); + + virtual QUICFrameType type() const override; + virtual size_t size() const override; + virtual bool is_flow_controlled() const override; + virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override; + virtual Ptr to_io_buffer_block(size_t limit) const override; + virtual int debug_msg(char *msg, size_t msg_len) const override; + virtual void parse(const uint8_t *buf, size_t len) override; + + size_t store(uint8_t *buf, size_t *len, size_t limit, bool include_length_field) const; + QUICStreamId stream_id() const; + QUICOffset offset() const; + IOBufferBlock *data() const; + uint64_t data_length() const; + bool has_offset_field() const; + bool has_length_field() const; + bool has_fin_flag() const; + + LINK(QUICStreamFrame, link); + +private: + static constexpr uint8_t MAX_HEADER_SIZE = 32; + + virtual void _reset() override; + + size_t _store_header(uint8_t *buf, size_t *len, bool include_length_field) const; + + Ptr _block; + QUICStreamId _stream_id = 0; + QUICOffset _offset = 0; + bool _fin = false; + bool _has_offset_field = true; + bool _has_length_field = true; +}; + +// +// CRYPTO Frame +// + +class QUICCryptoFrame : public QUICFrame +{ +public: + QUICCryptoFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {} + QUICCryptoFrame(const uint8_t *buf, size_t len); + QUICCryptoFrame(Ptr &block, QUICOffset offset, QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr); + QUICCryptoFrame(const QUICCryptoFrame &o); + + virtual QUICFrameType type() const override; + virtual size_t size() const override; + virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override; + virtual int debug_msg(char *msg, size_t msg_len) const override; + virtual void parse(const uint8_t *buf, size_t len) override; + + QUICOffset offset() const; + uint64_t data_length() const; + IOBufferBlock *data() const; + + LINK(QUICCryptoFrame, link); + +private: + virtual void _reset() override; + + QUICOffset _offset = 0; + Ptr _block; +}; + +// +// ACK Frame +// + +class QUICAckFrame : public QUICFrame +{ +public: + class PacketNumberRange + { + public: + PacketNumberRange(QUICPacketNumber first, QUICPacketNumber last) : _first(first), _last(last) {} + PacketNumberRange(PacketNumberRange &&a) noexcept; + QUICPacketNumber first() const; + QUICPacketNumber last() const; + uint64_t size() const; + bool contains(QUICPacketNumber x) const; + bool + operator<(const PacketNumberRange &b) const + { + return static_cast(this->first()) < static_cast(b.first()); + } + + private: + QUICPacketNumber _first; + QUICPacketNumber _last; + }; + + class AckBlock + { + public: + AckBlock(uint64_t g, uint64_t l) : _gap(g), _length(l) {} + uint64_t gap() const; + uint64_t length() const; + size_t size() const; + LINK(QUICAckFrame::AckBlock, link); + + private: + size_t _get_gap_size() const; + size_t _get_length_size() const; + + uint64_t _gap = 0; + uint64_t _length = 0; + }; + + class AckBlockSection + { + public: + class const_iterator : public std::iterator + { + public: + const_iterator(uint8_t index, const std::vector *ack_blocks); + + const QUICAckFrame::AckBlock &operator*() const { return this->_current_block; }; + const QUICAckFrame::AckBlock *operator->() const { return &this->_current_block; }; + const QUICAckFrame::AckBlock &operator++(); + const bool operator!=(const const_iterator &ite) const; + const bool operator==(const const_iterator &ite) const; + + private: + uint8_t _index = 0; + QUICAckFrame::AckBlock _current_block = {UINT64_C(0), UINT64_C(0)}; + const std::vector *_ack_blocks = nullptr; + }; + + AckBlockSection(uint64_t first_ack_block) : _first_ack_block(first_ack_block) {} + uint8_t count() const; + size_t size() const; + size_t store(uint8_t *buf, size_t *len, size_t limit) const; + uint64_t first_ack_block() const; + void add_ack_block(const AckBlock block); + const_iterator begin() const; + const_iterator end() const; + bool has_protected() const; + + private: + uint64_t _first_ack_block = 0; + uint8_t _ack_block_count = 0; + std::vector _ack_blocks; + }; + + class EcnSection + { + public: + EcnSection(const uint8_t *buf, size_t len); + size_t size() const; + bool valid() const; + uint64_t ect0_count() const; + uint64_t ect1_count() const; + uint64_t ecn_ce_count() const; + + private: + bool _valid = false; + size_t _size = 0; + uint64_t _ect0_count = 0; + uint64_t _ect1_count = 0; + uint64_t _ecn_ce_count = 0; + }; + + QUICAckFrame(QUICFrameId id = 0) : QUICFrame(id) {} + QUICAckFrame(const uint8_t *buf, size_t len); + QUICAckFrame(QUICPacketNumber largest_acknowledged, uint64_t ack_delay, uint64_t first_ack_block, QUICFrameId id = 0, + QUICFrameGenerator *owner = nullptr); + + // There's no reasont restrict copy, but we need to write the copy constructor. Otherwise it will crash on destruct. + QUICAckFrame(const QUICAckFrame &) = delete; + + virtual ~QUICAckFrame(); + virtual QUICFrameType type() const override; + virtual size_t size() const override; + virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override; + virtual void parse(const uint8_t *buf, size_t len) override; + virtual int debug_msg(char *msg, size_t msg_len) const override; + + QUICPacketNumber largest_acknowledged() const; + uint64_t ack_delay() const; + uint64_t ack_block_count() const; + const AckBlockSection *ack_block_section() const; + AckBlockSection *ack_block_section(); + const EcnSection *ecn_section() const; + EcnSection *ecn_section(); + +private: + virtual void _reset() override; + + QUICPacketNumber _largest_acknowledged = 0; + uint64_t _ack_delay = 0; + AckBlockSection *_ack_block_section = nullptr; + EcnSection *_ecn_section = nullptr; +}; + +// +// RESET_STREAM +// + +class QUICRstStreamFrame : public QUICFrame +{ +public: + QUICRstStreamFrame(QUICFrameId id = 0) : QUICFrame(id) {} + QUICRstStreamFrame(const uint8_t *buf, size_t len); + QUICRstStreamFrame(QUICStreamId stream_id, QUICAppErrorCode error_code, QUICOffset final_offset, QUICFrameId id = 0, + QUICFrameGenerator *owner = nullptr); + + virtual QUICFrameType type() const override; + virtual size_t size() const override; + virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override; + virtual int debug_msg(char *msg, size_t msg_len) const override; + virtual void parse(const uint8_t *buf, size_t len) override; + + QUICStreamId stream_id() const; + QUICAppErrorCode error_code() const; + QUICOffset final_offset() const; + +private: + virtual void _reset() override; + + QUICStreamId _stream_id = 0; + QUICAppErrorCode _error_code = 0; + QUICOffset _final_offset = 0; +}; + +// +// PING +// + +class QUICPingFrame : public QUICFrame +{ +public: + QUICPingFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {} + QUICPingFrame(const uint8_t *buf, size_t len); + virtual QUICFrameType type() const override; + virtual size_t size() const override; + virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override; + virtual void parse(const uint8_t *buf, size_t len) override; + +private: +}; + +// +// PADDING +// + +class QUICPaddingFrame : public QUICFrame +{ +public: + QUICPaddingFrame() {} + QUICPaddingFrame(const uint8_t *buf, size_t len); + virtual QUICFrameType type() const override; + virtual size_t size() const override; + virtual bool is_probing_frame() const override; + virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override; + virtual void parse(const uint8_t *buf, size_t len) override; +}; + +// +// CONNECTION_CLOSE +// + +class QUICConnectionCloseFrame : public QUICFrame +{ +public: + QUICConnectionCloseFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {} + QUICConnectionCloseFrame(const uint8_t *buf, size_t len); + // Constructor for transport error codes + QUICConnectionCloseFrame(uint16_t error_code, QUICFrameType frame_type, uint64_t reason_phrase_length, const char *reason_phrase, + QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr); + // Constructor for application protocol error codes + QUICConnectionCloseFrame(uint16_t error_code, uint64_t reason_phrase_length, const char *reason_phrase, QUICFrameId id = 0, + QUICFrameGenerator *owner = nullptr); + virtual QUICFrameType type() const override; + virtual size_t size() const override; + virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override; + virtual int debug_msg(char *msg, size_t msg_len) const override; + virtual void parse(const uint8_t *buf, size_t len) override; + + uint16_t error_code() const; + QUICFrameType frame_type() const; + uint64_t reason_phrase_length() const; + const char *reason_phrase() const; + +private: + virtual void _reset() override; + + uint8_t _type = 0; + uint16_t _error_code; + QUICFrameType _frame_type = QUICFrameType::UNKNOWN; + uint64_t _reason_phrase_length = 0; + const char *_reason_phrase = nullptr; +}; + +// +// MAX_DATA +// + +class QUICMaxDataFrame : public QUICFrame +{ +public: + QUICMaxDataFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {} + QUICMaxDataFrame(const uint8_t *buf, size_t len); + QUICMaxDataFrame(uint64_t maximum_data, QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr); + virtual QUICFrameType type() const override; + virtual size_t size() const override; + virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override; + virtual int debug_msg(char *msg, size_t msg_len) const override; + virtual void parse(const uint8_t *buf, size_t len) override; + + uint64_t maximum_data() const; + +private: + virtual void _reset() override; + + uint64_t _maximum_data = 0; +}; + +// +// MAX_STREAM_DATA +// + +class QUICMaxStreamDataFrame : public QUICFrame +{ +public: + QUICMaxStreamDataFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {} + QUICMaxStreamDataFrame(const uint8_t *buf, size_t len); + QUICMaxStreamDataFrame(QUICStreamId stream_id, uint64_t maximum_stream_data, QUICFrameId id = 0, + QUICFrameGenerator *owner = nullptr); + virtual QUICFrameType type() const override; + virtual size_t size() const override; + virtual void parse(const uint8_t *buf, size_t len) override; + virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override; + virtual int debug_msg(char *msg, size_t msg_len) const override; + + QUICStreamId stream_id() const; + uint64_t maximum_stream_data() const; + +private: + virtual void _reset() override; + + QUICStreamId _stream_id = 0; + uint64_t _maximum_stream_data = 0; +}; + +// +// MAX_STREAMS +// + +class QUICMaxStreamsFrame : public QUICFrame +{ +public: + QUICMaxStreamsFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {} + QUICMaxStreamsFrame(const uint8_t *buf, size_t len); + QUICMaxStreamsFrame(QUICStreamId maximum_streams, QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr); + virtual QUICFrameType type() const override; + virtual size_t size() const override; + virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override; + virtual void parse(const uint8_t *buf, size_t len) override; + uint64_t maximum_streams() const; + +private: + virtual void _reset() override; + + uint64_t _maximum_streams = 0; +}; + +// +// BLOCKED +// +class QUICDataBlockedFrame : public QUICFrame +{ +public: + QUICDataBlockedFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {} + QUICDataBlockedFrame(const uint8_t *buf, size_t len); + QUICDataBlockedFrame(QUICOffset offset, QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) + : QUICFrame(id, owner), _offset(offset){}; + + virtual QUICFrameType type() const override; + virtual size_t size() const override; + virtual void parse(const uint8_t *buf, size_t len) override; + virtual int debug_msg(char *msg, size_t msg_len) const override; + virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override; + + QUICOffset offset() const; + +private: + virtual void _reset() override; + + QUICOffset _offset = 0; +}; + +// +// STREAM_DATA_BLOCKED +// + +class QUICStreamDataBlockedFrame : public QUICFrame +{ +public: + QUICStreamDataBlockedFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {} + QUICStreamDataBlockedFrame(const uint8_t *buf, size_t len); + QUICStreamDataBlockedFrame(QUICStreamId s, QUICOffset o, QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) + : QUICFrame(id, owner), _stream_id(s), _offset(o){}; + + virtual QUICFrameType type() const override; + virtual size_t size() const override; + virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override; + virtual void parse(const uint8_t *buf, size_t len) override; + virtual int debug_msg(char *msg, size_t msg_len) const override; + + QUICStreamId stream_id() const; + QUICOffset offset() const; + +private: + virtual void _reset() override; + + QUICStreamId _stream_id = 0; + QUICOffset _offset = 0; +}; + +// +// STREAMS_BLOCKED +// +class QUICStreamIdBlockedFrame : public QUICFrame +{ +public: + QUICStreamIdBlockedFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {} + QUICStreamIdBlockedFrame(const uint8_t *buf, size_t len); + QUICStreamIdBlockedFrame(QUICStreamId s, QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) + : QUICFrame(id, owner), _stream_id(s) + { + } + virtual QUICFrameType type() const override; + virtual size_t size() const override; + virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override; + virtual void parse(const uint8_t *buf, size_t len) override; + + QUICStreamId stream_id() const; + +private: + virtual void _reset() override; + + QUICStreamId _stream_id = 0; +}; + +// +// NEW_CONNECTION_ID +// + +class QUICNewConnectionIdFrame : public QUICFrame +{ +public: + QUICNewConnectionIdFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {} + QUICNewConnectionIdFrame(const uint8_t *buf, size_t len); + QUICNewConnectionIdFrame(uint64_t seq, const QUICConnectionId &cid, QUICStatelessResetToken token, QUICFrameId id = 0, + QUICFrameGenerator *owner = nullptr) + : QUICFrame(id, owner), _sequence(seq), _connection_id(cid), _stateless_reset_token(token){}; + + virtual QUICFrameType type() const override; + virtual size_t size() const override; + virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override; + virtual void parse(const uint8_t *buf, size_t len) override; + virtual int debug_msg(char *msg, size_t msg_len) const override; + + uint64_t sequence() const; + QUICConnectionId connection_id() const; + QUICStatelessResetToken stateless_reset_token() const; + +private: + virtual void _reset() override; + + uint64_t _sequence = 0; + QUICConnectionId _connection_id = QUICConnectionId::ZERO(); + QUICStatelessResetToken _stateless_reset_token; +}; + +// +// STOP_SENDING +// + +class QUICStopSendingFrame : public QUICFrame +{ +public: + QUICStopSendingFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {} + QUICStopSendingFrame(const uint8_t *buf, size_t len); + QUICStopSendingFrame(QUICStreamId stream_id, QUICAppErrorCode error_code, QUICFrameId id = 0, + QUICFrameGenerator *owner = nullptr); + + virtual QUICFrameType type() const override; + virtual size_t size() const override; + virtual void parse(const uint8_t *buf, size_t len) override; + virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override; + + QUICStreamId stream_id() const; + QUICAppErrorCode error_code() const; + +private: + virtual void _reset() override; + + QUICStreamId _stream_id = 0; + QUICAppErrorCode _error_code = 0; +}; + +// +// PATH_CHALLENGE +// + +class QUICPathChallengeFrame : public QUICFrame +{ +public: + static constexpr uint8_t DATA_LEN = 8; + QUICPathChallengeFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {} + QUICPathChallengeFrame(const uint8_t *buf, size_t len); + QUICPathChallengeFrame(ats_unique_buf data, QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) + : QUICFrame(id, owner), _data(std::move(data)) + { + } + virtual QUICFrameType type() const override; + virtual size_t size() const override; + virtual bool is_probing_frame() const override; + virtual void parse(const uint8_t *buf, size_t len) override; + virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override; + + const uint8_t *data() const; + +private: + virtual void _reset() override; + + ats_unique_buf _data = {nullptr}; +}; + +// +// PATH_RESPONSE +// + +class QUICPathResponseFrame : public QUICFrame +{ +public: + static constexpr uint8_t DATA_LEN = 8; + QUICPathResponseFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {} + QUICPathResponseFrame(const uint8_t *buf, size_t len); + QUICPathResponseFrame(ats_unique_buf data, QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) + : QUICFrame(id, owner), _data(std::move(data)) + { + } + virtual QUICFrameType type() const override; + virtual size_t size() const override; + virtual bool is_probing_frame() const override; + virtual void parse(const uint8_t *buf, size_t len) override; + virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override; + + const uint8_t *data() const; + +private: + virtual void _reset() override; + + ats_unique_buf _data = {nullptr}; +}; + +// +// NEW_TOKEN +// + +class QUICNewTokenFrame : public QUICFrame +{ +public: + QUICNewTokenFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {} + QUICNewTokenFrame(const uint8_t *buf, size_t len); + QUICNewTokenFrame(ats_unique_buf token, size_t token_length, QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) + : QUICFrame(id, owner), _token_length(token_length), _token(std::move(token)) + { + } + virtual QUICFrameType type() const override; + virtual size_t size() const override; + virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override; + virtual void parse(const uint8_t *buf, size_t len) override; + + uint64_t token_length() const; + const uint8_t *token() const; + +private: + virtual void _reset() override; + + uint64_t _token_length = 0; + ats_unique_buf _token = {nullptr}; +}; + +// +// RETIRE_CONNECTION_ID +// + +class QUICRetireConnectionIdFrame : public QUICFrame +{ +public: + QUICRetireConnectionIdFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {} + QUICRetireConnectionIdFrame(const uint8_t *buf, size_t len); + QUICRetireConnectionIdFrame(uint64_t seq_num, QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) + : QUICFrame(id, owner), _seq_num(seq_num) + { + } + virtual QUICFrameType type() const override; + virtual size_t size() const override; + virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override; + virtual void parse(const uint8_t *buf, size_t len) override; + virtual int debug_msg(char *msg, size_t msg_len) const override; + + uint64_t seq_num() const; + +private: + virtual void _reset() override; + + uint64_t _seq_num = 0; +}; + +// +// UNKNOWN +// + +class QUICUnknownFrame : public QUICFrame +{ + QUICFrameType type() const override; + size_t size() const override; + size_t store(uint8_t *buf, size_t *len, size_t limit) const override; + void parse(const uint8_t *buf, size_t len) override; + int debug_msg(char *msg, size_t msg_len) const override; +}; + +// +// QUICFrameFactory +// +class QUICFrameFactory +{ +public: + /* + * This is used for creating a QUICFrame object based on received data. + */ + static QUICFrame *create(uint8_t *buf, const uint8_t *src, size_t len); + + /* + * This works almost the same as create() but it reuses created objects for performance. + * If you create a frame object which has the same frame type that you created before, the object will be reset by new data. + */ + const QUICFrame &fast_create(const uint8_t *buf, size_t len); + + /* + * Creates a STREAM frame. + * You have to make sure that the data size won't exceed the maximum size of QUIC packet. + */ + static QUICStreamFrame *create_stream_frame(uint8_t *buf, Ptr &block, QUICStreamId stream_id, QUICOffset offset, + bool last = false, bool has_offset_field = true, bool has_length_field = true, + QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr); + + /* + * Creates a CRYPTO frame. + * You have to make sure that the data size won't exceed the maximum size of QUIC packet. + */ + static QUICCryptoFrame *create_crypto_frame(uint8_t *buf, Ptr &block, QUICOffset offset, QUICFrameId id = 0, + QUICFrameGenerator *owner = nullptr); + + /* + * Creates a ACK frame. + * You shouldn't call this directly but through QUICAckFrameCreator because QUICAckFrameCreator manages packet numbers that we + * need to ack. + */ + static QUICAckFrame *create_ack_frame(uint8_t *buf, QUICPacketNumber largest_acknowledged, uint64_t ack_delay, + uint64_t first_ack_block, QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr); + /* + * Creates a CONNECTION_CLOSE frame. + */ + static QUICConnectionCloseFrame *create_connection_close_frame(uint8_t *buf, uint16_t error_code, QUICFrameType frame_type, + uint16_t reason_phrase_length = 0, + const char *reason_phrase = nullptr, QUICFrameId id = 0, + QUICFrameGenerator *owner = nullptr); + + static QUICConnectionCloseFrame *create_connection_close_frame(uint8_t *buf, QUICConnectionError &error, QUICFrameId id = 0, + QUICFrameGenerator *owner = nullptr); + + /* + * Creates a MAX_DATA frame. + */ + static QUICMaxDataFrame *create_max_data_frame(uint8_t *buf, uint64_t maximum_data, QUICFrameId id = 0, + QUICFrameGenerator *owner = nullptr); + + /* + / * Creates a MAX_STREAM_DATA frame. + */ + static QUICMaxStreamDataFrame *create_max_stream_data_frame(uint8_t *buf, QUICStreamId stream_id, uint64_t maximum_stream_data, + QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr); + /* + * Creates a MAX_STREAMS frame. + */ + static QUICMaxStreamsFrame *create_max_streams_frame(uint8_t *buf, QUICStreamId maximum_streams, QUICFrameId id = 0, + QUICFrameGenerator *owner = nullptr); + + /* + * Creates a PING frame + */ + static QUICPingFrame *create_ping_frame(uint8_t *buf, QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr); + + /* + * Creates a PATH_CHALLENGE frame + */ + static QUICPathChallengeFrame *create_path_challenge_frame(uint8_t *buf, const uint8_t *data, QUICFrameId id = 0, + QUICFrameGenerator *owner = nullptr); + + /* + * Creates a PATH_RESPONSE frame + */ + static QUICPathResponseFrame *create_path_response_frame(uint8_t *buf, const uint8_t *data, QUICFrameId id = 0, + QUICFrameGenerator *owner = nullptr); + + /* + * Creates a BLOCKED frame. + */ + static QUICDataBlockedFrame *create_data_blocked_frame(uint8_t *buf, QUICOffset offset, QUICFrameId id = 0, + QUICFrameGenerator *owner = nullptr); + + /* + * Creates a STREAM_DATA_BLOCKED frame. + */ + static QUICStreamDataBlockedFrame *create_stream_data_blocked_frame(uint8_t *buf, QUICStreamId stream_id, QUICOffset offset, + QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr); + + /* + * Creates a STREAMS_BLOCKED frame. + */ + static QUICStreamIdBlockedFrame *create_stream_id_blocked_frame(uint8_t *buf, QUICStreamId stream_id, QUICFrameId id = 0, + QUICFrameGenerator *owner = nullptr); + + /* + * Creates a RESET_STREAM frame. + */ + static QUICRstStreamFrame *create_rst_stream_frame(uint8_t *buf, QUICStreamId stream_id, QUICAppErrorCode error_code, + QUICOffset final_offset, QUICFrameId id = 0, + QUICFrameGenerator *owner = nullptr); + static QUICRstStreamFrame *create_rst_stream_frame(uint8_t *buf, QUICStreamError &error, QUICFrameId id = 0, + QUICFrameGenerator *owner = nullptr); + + /* + * Creates a STOP_SENDING frame. + */ + static QUICStopSendingFrame *create_stop_sending_frame(uint8_t *buf, QUICStreamId stream_id, QUICAppErrorCode error_code, + QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr); + + /* + * Creates a NEW_CONNECTION_ID frame. + */ + static QUICNewConnectionIdFrame *create_new_connection_id_frame(uint8_t *buf, uint32_t sequence, QUICConnectionId connectoin_id, + QUICStatelessResetToken stateless_reset_token, QUICFrameId id = 0, + QUICFrameGenerator *owner = nullptr); + + /* + * Creates a NEW_TOKEN frame + */ + static QUICNewTokenFrame *create_new_token_frame(uint8_t *buf, const QUICResumptionToken &token, QUICFrameId id = 0, + QUICFrameGenerator *owner = nullptr); + + /* + * Creates a RETIRE_CONNECTION_ID frame + */ + static QUICRetireConnectionIdFrame *create_retire_connection_id_frame(uint8_t *buf, uint64_t seq_num, QUICFrameId id = 0, + QUICFrameGenerator *owner = nullptr); + +private: + // FIXME Actual number of frame types is several but some of the values are not sequential. + QUICFrame *_reusable_frames[256] = {nullptr}; + uint8_t _buf_for_fast_create[256 * QUICFrame::MAX_INSTANCE_SIZE]; + QUICUnknownFrame _unknown_frame; +}; + +class QUICFrameInfo +{ +public: + QUICFrameInfo(QUICFrameId id, QUICFrameGenerator *generator) : _id(id), _generator(generator) {} + QUICFrameId id() const; + QUICFrameGenerator *generated_by() const; + +private: + QUICFrameId _id = 0; + QUICFrameGenerator *_generator; +}; diff --git a/iocore/net/quic/QUICFrameDispatcher.cc b/iocore/net/quic/QUICFrameDispatcher.cc new file mode 100644 index 00000000000..c2f3d1f7757 --- /dev/null +++ b/iocore/net/quic/QUICFrameDispatcher.cc @@ -0,0 +1,91 @@ +/** @file + * + * A brief file description + * + * @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 "QUICFrameDispatcher.h" +#include "QUICDebugNames.h" + +static constexpr char tag[] = "quic_net"; + +#define QUICDebug(fmt, ...) Debug(tag, "[%s] " fmt, this->_info->cids().data(), ##__VA_ARGS__) + +// +// Frame Dispatcher +// +QUICFrameDispatcher::QUICFrameDispatcher(QUICConnectionInfoProvider *info) : _info(info) {} + +void +QUICFrameDispatcher::add_handler(QUICFrameHandler *handler) +{ + for (QUICFrameType t : handler->interests()) { + this->_handlers[static_cast(t)].push_back(handler); + } +} + +QUICConnectionErrorUPtr +QUICFrameDispatcher::receive_frames(QUICEncryptionLevel level, const uint8_t *payload, uint16_t size, bool &ack_only, + bool &is_flow_controlled, bool *has_non_probing_frame) +{ + uint16_t cursor = 0; + ack_only = true; + is_flow_controlled = false; + QUICConnectionErrorUPtr error = nullptr; + + while (cursor < size) { + const QUICFrame &frame = this->_frame_factory.fast_create(payload + cursor, size - cursor); + if (frame.type() == QUICFrameType::UNKNOWN) { + QUICDebug("Failed to create a frame (%u bytes skipped)", size - cursor); + break; + } + if (has_non_probing_frame) { + *has_non_probing_frame |= !frame.is_probing_frame(); + } + cursor += frame.size(); + + QUICFrameType type = frame.type(); + + if (type == QUICFrameType::STREAM) { + is_flow_controlled = true; + } + + if (is_debug_tag_set(tag) && type != QUICFrameType::PADDING) { + char msg[1024]; + frame.debug_msg(msg, sizeof(msg)); + QUICDebug("[RX] | %s", msg); + } + + if (type != QUICFrameType::PADDING && type != QUICFrameType::ACK) { + ack_only = false; + } + + std::vector handlers = this->_handlers[static_cast(type)]; + for (auto h : handlers) { + error = h->handle_frame(level, frame); + // TODO: is there any case to continue this loop even if error? + if (error != nullptr) { + return error; + } + } + } + + return error; +} diff --git a/iocore/net/quic/QUICFrameDispatcher.h b/iocore/net/quic/QUICFrameDispatcher.h new file mode 100644 index 00000000000..79d71316c41 --- /dev/null +++ b/iocore/net/quic/QUICFrameDispatcher.h @@ -0,0 +1,46 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include + +#include "QUICConnection.h" +#include "QUICFrame.h" +#include "QUICFrameHandler.h" + +class QUICFrameDispatcher +{ +public: + QUICFrameDispatcher(QUICConnectionInfoProvider *info); + + QUICConnectionErrorUPtr receive_frames(QUICEncryptionLevel level, const uint8_t *payload, uint16_t size, + bool &should_send_ackbool, bool &is_flow_controlled, bool *has_non_probing_frame); + + void add_handler(QUICFrameHandler *handler); + +private: + QUICConnectionInfoProvider *_info = nullptr; + QUICFrameFactory _frame_factory; + std::vector _handlers[256]; +}; diff --git a/iocore/net/quic/QUICFrameGenerator.cc b/iocore/net/quic/QUICFrameGenerator.cc new file mode 100644 index 00000000000..3dce9665d4a --- /dev/null +++ b/iocore/net/quic/QUICFrameGenerator.cc @@ -0,0 +1,60 @@ +/** @file + * + * A brief file description + * + * @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 "QUICFrameGenerator.h" + +void +QUICFrameGenerator::_records_frame(QUICFrameId id, QUICFrameInformationUPtr info) +{ + this->_info.insert(std::make_pair(id, std::move(info))); +} + +bool +QUICFrameGenerator::_is_level_matched(QUICEncryptionLevel level) +{ + if (level == this->_encryption_level_filter) { + return true; + } else { + return false; + } +} + +void +QUICFrameGenerator::on_frame_acked(QUICFrameId id) +{ + auto it = this->_info.find(id); + if (it != this->_info.end()) { + this->_on_frame_acked(it->second); + this->_info.erase(it); + } +} + +void +QUICFrameGenerator::on_frame_lost(QUICFrameId id) +{ + auto it = this->_info.find(id); + if (it != this->_info.end()) { + this->_on_frame_lost(it->second); + this->_info.erase(it); + } +} diff --git a/iocore/net/quic/QUICFrameGenerator.h b/iocore/net/quic/QUICFrameGenerator.h new file mode 100644 index 00000000000..2298de597d0 --- /dev/null +++ b/iocore/net/quic/QUICFrameGenerator.h @@ -0,0 +1,69 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include "QUICFrame.h" +#include "QUICFrameRetransmitter.h" + +class QUICFrameGenerator +{ +public: + virtual ~QUICFrameGenerator(){}; + virtual bool will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) = 0; + + /* + * This function constructs an instance of QUICFrame on buf. + * It returns a pointer for the frame if it succeeded, and returns nullptr if it failed. + */ + virtual QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, + uint16_t maximum_frame_size, ink_hrtime timestamp) = 0; + + void on_frame_acked(QUICFrameId id); + void on_frame_lost(QUICFrameId id); + +protected: + QUICFrameId + _issue_frame_id() + { + return this->_latest_frame_Id++; + } + + virtual void + _on_frame_acked(QUICFrameInformationUPtr &info) + { + } + + virtual void + _on_frame_lost(QUICFrameInformationUPtr &info) + { + } + + virtual bool _is_level_matched(QUICEncryptionLevel level); + void _records_frame(QUICFrameId id, QUICFrameInformationUPtr info); + +private: + QUICFrameId _latest_frame_Id = 0; + QUICEncryptionLevel _encryption_level_filter = QUICEncryptionLevel::ONE_RTT; + std::map _info; +}; diff --git a/iocore/net/quic/QUICFrameHandler.h b/iocore/net/quic/QUICFrameHandler.h new file mode 100644 index 00000000000..5a2870f9869 --- /dev/null +++ b/iocore/net/quic/QUICFrameHandler.h @@ -0,0 +1,37 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include +#include "QUICTypes.h" + +class QUICFrame; + +class QUICFrameHandler +{ +public: + virtual ~QUICFrameHandler(){}; + virtual std::vector interests() = 0; + virtual QUICConnectionErrorUPtr handle_frame(QUICEncryptionLevel level, const QUICFrame &frame) = 0; +}; diff --git a/iocore/net/quic/QUICFrameRetransmitter.cc b/iocore/net/quic/QUICFrameRetransmitter.cc new file mode 100644 index 00000000000..bedc6548bcb --- /dev/null +++ b/iocore/net/quic/QUICFrameRetransmitter.cc @@ -0,0 +1,183 @@ +/** @file + * + * A brief file description + * + * @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 "tscore/Diags.h" + +#include "QUICFrameRetransmitter.h" +#include "QUICFrameGenerator.h" +#include "QUICDebugNames.h" + +ClassAllocator quicFrameInformationAllocator("quicFrameInformationAllocator"); + +QUICFrame * +QUICFrameRetransmitter::create_retransmitted_frame(uint8_t *buf, QUICEncryptionLevel level, uint16_t maximum_frame_size, + QUICFrameId id, QUICFrameGenerator *owner) +{ + QUICFrame *frame = nullptr; + if (this->_lost_frame_info_queue.empty()) { + return frame; + } + + std::deque tmp_queue; + for (auto it = this->_lost_frame_info_queue.begin(); it != this->_lost_frame_info_queue.end(); + it = this->_lost_frame_info_queue.begin()) { + QUICFrameInformationUPtr &info = *it; + + if (info->level != QUICEncryptionLevel::NONE && info->level != level) { + // skip unmapped info. + tmp_queue.push_back(std::move(info)); + this->_lost_frame_info_queue.pop_front(); + continue; + } + + switch (info->type) { + case QUICFrameType::STREAM: + frame = this->_create_stream_frame(buf, info, maximum_frame_size, tmp_queue, id, owner); + break; + case QUICFrameType::CRYPTO: + frame = this->_create_crypto_frame(buf, info, maximum_frame_size, tmp_queue, id, owner); + break; + default: + ink_assert("unknown frame type"); + Error("unknown frame type: %s", QUICDebugNames::frame_type(info->type)); + } + + this->_lost_frame_info_queue.pop_front(); + if (frame != nullptr) { + break; + } + } + + this->_append_info_queue(tmp_queue); + return frame; +} + +void +QUICFrameRetransmitter::save_frame_info(QUICFrameInformationUPtr info) +{ + for (auto type : RETRANSMITTED_FRAME_TYPE) { + if (type == info->type) { + this->_lost_frame_info_queue.push_back(std::move(info)); + break; + } + } +} + +void +QUICFrameRetransmitter::_append_info_queue(std::deque &tmp_queue) +{ + auto it = tmp_queue.begin(); + while (it != tmp_queue.end()) { + this->_lost_frame_info_queue.push_back(std::move(*it)); + tmp_queue.pop_front(); + it = tmp_queue.begin(); + } +} + +QUICFrame * +QUICFrameRetransmitter::_create_stream_frame(uint8_t *buf, QUICFrameInformationUPtr &info, uint16_t maximum_frame_size, + std::deque &tmp_queue, QUICFrameId id, + QUICFrameGenerator *owner) +{ + QUICFrame *frame = nullptr; + StreamFrameInfo *stream_info = reinterpret_cast(info->data); + + static constexpr uint32_t MAX_STREAM_FRAME_OVERHEAD = 24; + if (maximum_frame_size <= MAX_STREAM_FRAME_OVERHEAD) { + tmp_queue.push_back(std::move(info)); + return frame; + } + + // FIXME MAX_STREAM_FRAME_OVERHEAD is here and there + // These size calculation should not exist multiple places + uint64_t maximum_data_size = maximum_frame_size - MAX_STREAM_FRAME_OVERHEAD; + if (maximum_data_size >= static_cast(stream_info->block->size())) { + frame = QUICFrameFactory::create_stream_frame(buf, stream_info->block, stream_info->stream_id, stream_info->offset, + stream_info->has_fin, true, true, id, owner); + ink_assert(frame->size() <= maximum_frame_size); + stream_info->block = nullptr; + } else { + frame = QUICFrameFactory::create_stream_frame(buf, stream_info->block, stream_info->stream_id, stream_info->offset, false, true, + true, id, owner); + QUICStreamFrame *stream_frame = static_cast(frame); + IOBufferBlock *block = stream_frame->data(); + size_t over_length = stream_frame->data_length() - maximum_data_size; + block->_end = std::max(block->start(), block->_end - over_length); + if (block->read_avail() == 0) { + // no payload + tmp_queue.push_back(std::move(info)); + return nullptr; + } + stream_info->block->consume(stream_frame->data_length()); + stream_info->offset += stream_frame->data_length(); + ink_assert(frame->size() <= maximum_frame_size); + tmp_queue.push_back(std::move(info)); + return frame; + } + + ink_assert(frame != nullptr); + return frame; +} + +QUICFrame * +QUICFrameRetransmitter::_create_crypto_frame(uint8_t *buf, QUICFrameInformationUPtr &info, uint16_t maximum_frame_size, + std::deque &tmp_queue, QUICFrameId id, + QUICFrameGenerator *owner) +{ + CryptoFrameInfo *crypto_info = reinterpret_cast(info->data); + // FIXME: has_offset and has_length should be configurable. + auto frame = QUICFrameFactory::create_crypto_frame(buf, crypto_info->block, crypto_info->offset, id, owner); + if (frame->size() > maximum_frame_size) { + QUICCryptoFrame *crypto_frame = static_cast(frame); + if (crypto_frame->size() - crypto_frame->data_length() > maximum_frame_size) { + // header length is larger than maximum_frame_size. + tmp_queue.push_back(std::move(info)); + return nullptr; + } + + IOBufferBlock *block = crypto_frame->data(); + size_t over_length = crypto_frame->size() - maximum_frame_size; + block->_end = std::max(block->start(), block->_end - over_length); + if (block->read_avail() == 0) { + // no payload + tmp_queue.push_back(std::move(info)); + return nullptr; + } + + crypto_info->block->consume(crypto_frame->data_length()); + crypto_info->offset += crypto_frame->data_length(); + ink_assert(frame->size() <= maximum_frame_size); + tmp_queue.push_back(std::move(info)); + return frame; + } + + crypto_info->block = nullptr; + ink_assert(frame != nullptr); + return frame; +} + +bool +QUICFrameRetransmitter::is_retransmited_frame_queue_empty() const +{ + return this->_lost_frame_info_queue.empty(); +} diff --git a/iocore/net/quic/QUICFrameRetransmitter.h b/iocore/net/quic/QUICFrameRetransmitter.h new file mode 100644 index 00000000000..9f45dd5b959 --- /dev/null +++ b/iocore/net/quic/QUICFrameRetransmitter.h @@ -0,0 +1,96 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include +#include "QUICFrame.h" + +class QUICFrameGenerator; + +struct QUICFrameInformation { + QUICFrameType type; + QUICEncryptionLevel level; + + uint8_t data[128] = {}; +}; + +struct QUICFrameInformationDeleter { + void operator()(QUICFrameInformation *p); +}; + +using QUICFrameInformationUPtr = std::unique_ptr; + +constexpr QUICFrameType RETRANSMITTED_FRAME_TYPE[] = {QUICFrameType::STREAM, QUICFrameType::CRYPTO}; + +struct StreamFrameInfo { + QUICStreamId stream_id; + QUICOffset offset; + bool has_fin; + Ptr block; +}; + +struct CryptoFrameInfo { + QUICOffset offset; + Ptr block; +}; + +struct RstStreamFrameInfo { + QUICAppErrorCode error_code; + QUICOffset final_offset; +}; + +struct StopSendingFrameInfo { + QUICAppErrorCode error_code; +}; + +struct AckFrameInfo { + QUICPacketNumber largest_acknowledged; +}; + +class QUICFrameRetransmitter +{ +public: + virtual QUICFrame *create_retransmitted_frame(uint8_t *buf, QUICEncryptionLevel level, uint16_t maximum_frame_size, + QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr); + virtual void save_frame_info(QUICFrameInformationUPtr info); + bool is_retransmited_frame_queue_empty() const; + +private: + QUICFrame *_create_stream_frame(uint8_t *buf, QUICFrameInformationUPtr &info, uint16_t maximum_frame_size, + std::deque &tmp_queue, QUICFrameId id, QUICFrameGenerator *owner); + QUICFrame *_create_crypto_frame(uint8_t *buf, QUICFrameInformationUPtr &info, uint16_t maximum_frame_size, + std::deque &tmp_queue, QUICFrameId id, QUICFrameGenerator *owner); + + void _append_info_queue(std::deque &tmp_queue); + + std::deque _lost_frame_info_queue; +}; + +extern ClassAllocator quicFrameInformationAllocator; + +inline void +QUICFrameInformationDeleter::operator()(QUICFrameInformation *p) +{ + quicFrameInformationAllocator.free(p); +} diff --git a/iocore/net/quic/QUICGlobals.cc b/iocore/net/quic/QUICGlobals.cc new file mode 100644 index 00000000000..14761aa5d8d --- /dev/null +++ b/iocore/net/quic/QUICGlobals.cc @@ -0,0 +1,99 @@ +/** @file + * + * A brief file description + * + * @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 "QUICGlobals.h" + +#include +#include + +#include "QUICMultiCertConfigLoader.h" + +#include "QUICStats.h" +#include "QUICConfig.h" +#include "QUICConnection.h" + +#include "QUICTLS.h" +#include + +#define QUICGlobalDebug(fmt, ...) Debug("quic_global", fmt, ##__VA_ARGS__) + +RecRawStatBlock *quic_rsb; + +int QUIC::ssl_quic_qc_index = -1; +int QUIC::ssl_quic_tls_index = -1; + +void +QUIC::init() +{ + QUIC::_register_stats(); + ssl_quic_qc_index = SSL_get_ex_new_index(0, (void *)"QUICConnection index", nullptr, nullptr, nullptr); + ssl_quic_tls_index = SSL_get_ex_new_index(0, (void *)"QUICTLS index", nullptr, nullptr, nullptr); +} + +int +QUIC::ssl_client_new_session(SSL *ssl, SSL_SESSION *session) +{ + QUICTLS *qtls = static_cast(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"); + + if (file == nullptr) { + QUICGlobalDebug("Could not write TLS session in %s", session_file); + return 0; + } + + PEM_write_bio_SSL_SESSION(file, session); + BIO_free(file); + return 0; +} + +void +QUIC::ssl_client_keylog_cb(const SSL *ssl, const char *line) +{ + QUICTLS *qtls = static_cast(SSL_get_ex_data(ssl, QUIC::ssl_quic_tls_index)); + const char *keylog_file = qtls->keylog_file(); + std::ofstream file(keylog_file, std::ios_base::app); + + if (!file.is_open()) { + QUICGlobalDebug("could not open keylog file: %s", keylog_file); + return; + } + + file.write(line, strlen(line)); + file.put('\n'); + file.flush(); +} + +void +QUIC::_register_stats() +{ + quic_rsb = RecAllocateRawStatBlock(static_cast(QUICStats::count)); + + // Transfered packet counts + RecRegisterRawStat(quic_rsb, RECT_PROCESS, "proxy.process.quic.total_packets_sent", RECD_INT, RECP_PERSISTENT, + static_cast(QUICStats::total_packets_sent_stat), RecRawStatSyncSum); + // RecRegisterRawStat(quic_rsb, RECT_PROCESS, "proxy.process.quic.total_packets_retransmitted", RECD_INT, RECP_PERSISTENT, + // static_cast(quic_total_packets_retransmitted_stat), RecRawStatSyncSum); + // RecRegisterRawStat(quic_rsb, RECT_PROCESS, "proxy.process.quic.total_packets_received", RECD_INT, RECP_PERSISTENT, + // static_cast(quic_total_packets_received_stat), RecRawStatSyncSum); +} diff --git a/iocore/net/quic/QUICGlobals.h b/iocore/net/quic/QUICGlobals.h new file mode 100644 index 00000000000..41b29afbf12 --- /dev/null +++ b/iocore/net/quic/QUICGlobals.h @@ -0,0 +1,42 @@ +/** @file + * + * QUIC Globals + * + * @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. + */ + +#pragma once + +#include + +class QUIC +{ +public: + static void init(); + + // SSL callbacks + static int ssl_client_new_session(SSL *ssl, SSL_SESSION *session); + static void ssl_client_keylog_cb(const SSL *ssl, const char *line); + + static int ssl_quic_qc_index; + static int ssl_quic_tls_index; + +private: + static void _register_stats(); +}; diff --git a/iocore/net/quic/QUICHKDF.cc b/iocore/net/quic/QUICHKDF.cc new file mode 100644 index 00000000000..778eac50b85 --- /dev/null +++ b/iocore/net/quic/QUICHKDF.cc @@ -0,0 +1,60 @@ +/** @file + * + * HKDF utility (common part) + * + * @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 "QUICHKDF.h" + +#include +#include + +#include + +/** + * HKDF-Expand-Label function of QUIC + * The HKDF-Expand-Label function and HkdfLabel structure is defined in TLS 1.3 Section 7.1. + */ +int +QUICHKDF::expand(uint8_t *dst, size_t *dst_len, const uint8_t *secret, size_t secret_len, const char *label, size_t label_len, + uint16_t length) +{ + // Create HkdfLabel + uint8_t hkdf_label[512]; // 2 + 255 + 255 + int hkdf_label_len = 0; + + // length field + hkdf_label[0] = (length >> 8) & 0xFF; + hkdf_label[1] = length & 0xFF; + hkdf_label_len += 2; + + // label (prefix + Label) field + hkdf_label_len += sprintf(reinterpret_cast(hkdf_label + hkdf_label_len), "%ctls13 %.*s", static_cast(6 + label_len), + static_cast(label_len), label); + + // context field + // XXX: Assuming Context is zero-length character (indicated by "") + // HkdfLabel requires "0" (length) in context field, because `context<0..255>` is valiable-integer. + hkdf_label[hkdf_label_len] = 0; + ++hkdf_label_len; + + HKDF::expand(dst, dst_len, secret, secret_len, hkdf_label, hkdf_label_len, length); + + return 1; +} diff --git a/iocore/net/quic/QUICHKDF.h b/iocore/net/quic/QUICHKDF.h new file mode 100644 index 00000000000..4bb0ffb003a --- /dev/null +++ b/iocore/net/quic/QUICHKDF.h @@ -0,0 +1,34 @@ +/** @file + + HKDF utility + + @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. + */ + +#pragma once + +#include "tscore/HKDF.h" + +class QUICHKDF : public HKDF +{ +public: + QUICHKDF(const EVP_MD *digest) : HKDF(digest) {} + int expand(uint8_t *dst, size_t *dst_len, const uint8_t *secret, size_t secret_len, const char *label, size_t label_len, + uint16_t length); +}; diff --git a/iocore/net/quic/QUICHandshake.cc b/iocore/net/quic/QUICHandshake.cc new file mode 100644 index 00000000000..6042d26a67c --- /dev/null +++ b/iocore/net/quic/QUICHandshake.cc @@ -0,0 +1,510 @@ +/** @file + * + * A brief file description + * + * @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 "QUICHandshake.h" + +#include + +#include "QUICEvents.h" +#include "QUICGlobals.h" +#include "QUICPacketFactory.h" +#include "QUICVersionNegotiator.h" +#include "QUICConfig.h" + +#define QUICHSDebug(fmt, ...) Debug("quic_handshake", "[%s] " fmt, this->_qc->cids().data(), ##__VA_ARGS__) + +#define QUICVHSDebug(fmt, ...) Debug("v_quic_handshake", "[%s] " fmt, this->_qc->cids().data(), ##__VA_ARGS__) + +#define I_WANNA_DUMP_THIS_BUF(buf, len) \ + { \ + static constexpr char dump_tag[] = "v_quic_handshake_dump_pkt"; \ + int i; \ + Debug(dump_tag, "len=%" PRId64 "\n", len); \ + for (i = 0; i < len / 8; i++) { \ + Debug(dump_tag, "%02x %02x %02x %02x %02x %02x %02x %02x ", buf[i * 8 + 0], buf[i * 8 + 1], buf[i * 8 + 2], buf[i * 8 + 3], \ + buf[i * 8 + 4], buf[i * 8 + 5], buf[i * 8 + 6], buf[i * 8 + 7]); \ + } \ + switch (len % 8) { \ + case 1: \ + Debug(dump_tag, "%02x", buf[i * 8 + 0]); \ + break; \ + case 2: \ + Debug(dump_tag, "%02x %02x", buf[i * 8 + 0], buf[i * 8 + 1]); \ + \ + break; \ + case 3: \ + Debug(dump_tag, "%02x %02x %02x", buf[i * 8 + 0], buf[i * 8 + 1], buf[i * 8 + 2]); \ + \ + break; \ + case 4: \ + Debug(dump_tag, "%02x %02x %02x %02x", buf[i * 8 + 0], buf[i * 8 + 1], buf[i * 8 + 2], buf[i * 8 + 3]); \ + \ + break; \ + case 5: \ + Debug(dump_tag, "%02x %02x %02x %02x %02x", buf[i * 8 + 0], buf[i * 8 + 1], buf[i * 8 + 2], buf[i * 8 + 3], buf[i * 8 + 4]); \ + \ + break; \ + case 6: \ + Debug(dump_tag, "%02x %02x %02x %02x %02x %02x", buf[i * 8 + 0], buf[i * 8 + 1], buf[i * 8 + 2], buf[i * 8 + 3], \ + buf[i * 8 + 4], buf[i * 8 + 5]); \ + \ + break; \ + case 7: \ + Debug(dump_tag, "%02x %02x %02x %02x %02x %02x %02x", buf[i * 8 + 0], buf[i * 8 + 1], buf[i * 8 + 2], buf[i * 8 + 3], \ + buf[i * 8 + 4], buf[i * 8 + 5], buf[i * 8 + 6]); \ + \ + break; \ + default: \ + break; \ + } \ + } + +static constexpr int UDP_MAXIMUM_PAYLOAD_SIZE = 65527; +// TODO: fix size +static constexpr int MAX_HANDSHAKE_MSG_LEN = 65527; + +QUICHandshake::QUICHandshake(QUICConnection *qc, QUICHandshakeProtocol *hsp) : QUICHandshake(qc, hsp, {}, false) {} + +QUICHandshake::QUICHandshake(QUICConnection *qc, QUICHandshakeProtocol *hsp, QUICStatelessResetToken token, bool stateless_retry) + : _qc(qc), + _hs_protocol(hsp), + _version_negotiator(new QUICVersionNegotiator()), + _reset_token(token), + _stateless_retry(stateless_retry) +{ + this->_hs_protocol->initialize_key_materials(this->_qc->original_connection_id()); + + if (this->_qc->direction() == NET_VCONNECTION_OUT) { + this->_client_initial = true; + } +} + +QUICHandshake::~QUICHandshake() +{ + delete this->_hs_protocol; +} + +QUICConnectionErrorUPtr +QUICHandshake::start(const QUICTPConfig &tp_config, QUICPacketFactory *packet_factory, bool vn_exercise_enabled) +{ + QUICVersion initital_version = QUIC_SUPPORTED_VERSIONS[0]; + if (vn_exercise_enabled) { + initital_version = QUIC_EXERCISE_VERSION; + } + + this->_load_local_client_transport_parameters(tp_config); + packet_factory->set_version(initital_version); + + return nullptr; +} + +QUICConnectionErrorUPtr +QUICHandshake::start(const QUICTPConfig &tp_config, const QUICPacket &initial_packet, QUICPacketFactory *packet_factory, + const QUICPreferredAddress *pref_addr) +{ + // Negotiate version + if (this->_version_negotiator->status() == QUICVersionNegotiationStatus::NOT_NEGOTIATED) { + if (initial_packet.type() != QUICPacketType::INITIAL) { + return std::make_unique(QUICTransErrorCode::PROTOCOL_VIOLATION); + } + if (initial_packet.version()) { + if (this->_version_negotiator->negotiate(initial_packet) == QUICVersionNegotiationStatus::NEGOTIATED) { + QUICHSDebug("Version negotiation succeeded: %x", initial_packet.version()); + this->_load_local_server_transport_parameters(tp_config, pref_addr); + packet_factory->set_version(this->_version_negotiator->negotiated_version()); + } else { + ink_assert(!"Unsupported version initial packet should be droped QUICPakcetHandler"); + } + } else { + return std::make_unique(QUICTransErrorCode::PROTOCOL_VIOLATION); + } + } + return nullptr; +} + +QUICConnectionErrorUPtr +QUICHandshake::negotiate_version(const QUICPacket &vn, QUICPacketFactory *packet_factory) +{ + // Client side only + ink_assert(this->_qc->direction() == NET_VCONNECTION_OUT); + + // If already negotiated, just ignore it + if (this->_version_negotiator->status() == QUICVersionNegotiationStatus::NEGOTIATED || + this->_version_negotiator->status() == QUICVersionNegotiationStatus::VALIDATED) { + QUICHSDebug("Ignore Version Negotiation packet"); + return nullptr; + } + + if (vn.version() != 0x00) { + QUICHSDebug("Version field must be 0x00000000"); + return std::make_unique(QUICTransErrorCode::PROTOCOL_VIOLATION); + } + + if (this->_version_negotiator->negotiate(vn) == QUICVersionNegotiationStatus::NEGOTIATED) { + QUICVersion version = this->_version_negotiator->negotiated_version(); + QUICHSDebug("Version negotiation succeeded: 0x%x", version); + packet_factory->set_version(version); + } else { + QUICHSDebug("Version negotiation failed"); + return std::make_unique(QUICTransErrorCode::VERSION_NEGOTIATION_ERROR); + } + + return nullptr; +} + +bool +QUICHandshake::is_version_negotiated() const +{ + return (this->_version_negotiator->status() == QUICVersionNegotiationStatus::NEGOTIATED || + this->_version_negotiator->status() == QUICVersionNegotiationStatus::VALIDATED); +} + +bool +QUICHandshake::is_completed() const +{ + return this->_hs_protocol->is_handshake_finished(); +} + +bool +QUICHandshake::is_stateless_retry_enabled() const +{ + return this->_stateless_retry; +} + +bool +QUICHandshake::has_remote_tp() const +{ + return this->_remote_transport_parameters != nullptr; +} + +QUICVersion +QUICHandshake::negotiated_version() +{ + return this->_version_negotiator->negotiated_version(); +} + +// Similar to SSLNetVConnection::getSSLCipherSuite() +const char * +QUICHandshake::negotiated_cipher_suite() +{ + return this->_hs_protocol->negotiated_cipher_suite(); +} + +void +QUICHandshake::negotiated_application_name(const uint8_t **name, unsigned int *len) +{ + this->_hs_protocol->negotiated_application_name(name, len); +} + +bool +QUICHandshake::check_remote_transport_parameters() +{ + auto tp = this->_hs_protocol->remote_transport_parameters(); + + if (tp == nullptr) { + // nothing to check + return true; + } + + if (std::dynamic_pointer_cast(tp)) { + return this->_check_remote_transport_parameters(std::static_pointer_cast(tp)); + } else { + return this->_check_remote_transport_parameters( + std::static_pointer_cast(tp)); + } +} + +bool +QUICHandshake::_check_remote_transport_parameters(std::shared_ptr tp) +{ + // An endpoint MUST treat receipt of duplicate transport parameters as a connection error of type TRANSPORT_PARAMETER_ERROR. + if (!tp->is_valid()) { + QUICHSDebug("Transport parameter is not valid"); + this->_abort_handshake(QUICTransErrorCode::TRANSPORT_PARAMETER_ERROR); + return false; + } + + this->_remote_transport_parameters = tp; + + return true; +} + +bool +QUICHandshake::_check_remote_transport_parameters(std::shared_ptr tp) +{ + // An endpoint MUST treat receipt of duplicate transport parameters as a connection error of type TRANSPORT_PARAMETER_ERROR. + if (!tp->is_valid()) { + QUICHSDebug("Transport parameter is not valid"); + this->_abort_handshake(QUICTransErrorCode::TRANSPORT_PARAMETER_ERROR); + return false; + } + + this->_remote_transport_parameters = tp; + + return true; +} + +std::shared_ptr +QUICHandshake::local_transport_parameters() +{ + return this->_local_transport_parameters; +} + +std::shared_ptr +QUICHandshake::remote_transport_parameters() +{ + return this->_remote_transport_parameters; +} + +/** + * reset states for starting over + */ +void +QUICHandshake::reset() +{ + this->_client_initial = true; + this->_hs_protocol->reset(); + + for (auto level : QUIC_ENCRYPTION_LEVELS) { + int index = static_cast(level); + QUICCryptoStream *stream = &this->_crypto_streams[index]; + stream->reset_send_offset(); + stream->reset_recv_offset(); + } +} + +std::vector +QUICHandshake::interests() +{ + return { + QUICFrameType::CRYPTO, + }; +} + +QUICConnectionErrorUPtr +QUICHandshake::handle_frame(QUICEncryptionLevel level, const QUICFrame &frame) +{ + QUICConnectionErrorUPtr error = nullptr; + switch (frame.type()) { + case QUICFrameType::CRYPTO: + error = this->_crypto_streams[static_cast(level)].recv(static_cast(frame)); + if (error != nullptr) { + return error; + } + break; + default: + QUICHSDebug("Unexpected frame type: %02x", static_cast(frame.type())); + ink_assert(false); + break; + } + + return this->do_handshake(); +} + +bool +QUICHandshake::will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) +{ + if (!this->_is_level_matched(level)) { + return false; + } + + return this->_crypto_streams[static_cast(level)].will_generate_frame(level, timestamp); +} + +QUICFrame * +QUICHandshake::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size, + ink_hrtime timestamp) +{ + QUICFrame *frame = nullptr; + + if (this->_is_level_matched(level)) { + frame = + this->_crypto_streams[static_cast(level)].generate_frame(buf, level, connection_credit, maximum_frame_size, timestamp); + } + + return frame; +} + +void +QUICHandshake::_load_local_server_transport_parameters(const QUICTPConfig &tp_config, const QUICPreferredAddress *pref_addr) +{ + QUICTransportParametersInEncryptedExtensions *tp = new QUICTransportParametersInEncryptedExtensions(); + + // MUSTs + tp->set(QUICTransportParameterId::IDLE_TIMEOUT, static_cast(tp_config.no_activity_timeout())); + if (this->_stateless_retry) { + tp->set(QUICTransportParameterId::ORIGINAL_CONNECTION_ID, this->_qc->first_connection_id(), + this->_qc->first_connection_id().length()); + } + + // MAYs + if (tp_config.initial_max_data() != 0) { + tp->set(QUICTransportParameterId::INITIAL_MAX_DATA, tp_config.initial_max_data()); + } + if (tp_config.initial_max_streams_bidi() != 0) { + tp->set(QUICTransportParameterId::INITIAL_MAX_STREAMS_BIDI, tp_config.initial_max_streams_bidi()); + } + if (tp_config.initial_max_streams_uni() != 0) { + tp->set(QUICTransportParameterId::INITIAL_MAX_STREAMS_UNI, tp_config.initial_max_streams_uni()); + } + if (tp_config.initial_max_stream_data_bidi_local() != 0) { + tp->set(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_LOCAL, tp_config.initial_max_stream_data_bidi_local()); + } + if (tp_config.initial_max_stream_data_bidi_remote() != 0) { + tp->set(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_REMOTE, tp_config.initial_max_stream_data_bidi_remote()); + } + if (tp_config.initial_max_stream_data_uni() != 0) { + tp->set(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_UNI, tp_config.initial_max_stream_data_uni()); + } + if (pref_addr != nullptr) { + uint8_t pref_addr_buf[QUICPreferredAddress::MAX_LEN]; + uint16_t len; + pref_addr->store(pref_addr_buf, len); + tp->set(QUICTransportParameterId::PREFERRED_ADDRESS, pref_addr_buf, len); + } + + // MAYs (server) + tp->set(QUICTransportParameterId::STATELESS_RESET_TOKEN, this->_reset_token.buf(), QUICStatelessResetToken::LEN); + tp->set(QUICTransportParameterId::ACK_DELAY_EXPONENT, tp_config.ack_delay_exponent()); + + tp->add_version(QUIC_SUPPORTED_VERSIONS[0]); + + this->_local_transport_parameters = std::shared_ptr(tp); + this->_hs_protocol->set_local_transport_parameters(this->_local_transport_parameters); +} + +void +QUICHandshake::_load_local_client_transport_parameters(const QUICTPConfig &tp_config) +{ + QUICTransportParametersInClientHello *tp = new QUICTransportParametersInClientHello(); + + // MUSTs + tp->set(QUICTransportParameterId::IDLE_TIMEOUT, static_cast(tp_config.no_activity_timeout())); + + // MAYs + if (tp_config.initial_max_data() != 0) { + tp->set(QUICTransportParameterId::INITIAL_MAX_DATA, tp_config.initial_max_data()); + } + if (tp_config.initial_max_streams_bidi() != 0) { + tp->set(QUICTransportParameterId::INITIAL_MAX_STREAMS_BIDI, tp_config.initial_max_streams_bidi()); + } + if (tp_config.initial_max_streams_uni() != 0) { + tp->set(QUICTransportParameterId::INITIAL_MAX_STREAMS_UNI, tp_config.initial_max_streams_uni()); + } + if (tp_config.initial_max_stream_data_bidi_local() != 0) { + tp->set(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_LOCAL, tp_config.initial_max_stream_data_bidi_local()); + } + if (tp_config.initial_max_stream_data_bidi_remote() != 0) { + tp->set(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_REMOTE, tp_config.initial_max_stream_data_bidi_remote()); + } + if (tp_config.initial_max_stream_data_uni() != 0) { + tp->set(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_UNI, tp_config.initial_max_stream_data_uni()); + } + tp->set(QUICTransportParameterId::ACK_DELAY_EXPONENT, tp_config.ack_delay_exponent()); + + this->_local_transport_parameters = std::shared_ptr(tp); + this->_hs_protocol->set_local_transport_parameters(std::unique_ptr(tp)); +} + +QUICConnectionErrorUPtr +QUICHandshake::do_handshake() +{ + QUICConnectionErrorUPtr error = nullptr; + + QUICHandshakeMsgs in; + uint8_t in_buf[UDP_MAXIMUM_PAYLOAD_SIZE] = {0}; + in.buf = in_buf; + in.max_buf_len = UDP_MAXIMUM_PAYLOAD_SIZE; + + if (this->_client_initial) { + this->_client_initial = false; + } else { + for (auto level : QUIC_ENCRYPTION_LEVELS) { + int index = static_cast(level); + QUICCryptoStream *stream = &this->_crypto_streams[index]; + int64_t bytes_avail = stream->read_avail(); + // TODO: check size + if (bytes_avail > 0) { + stream->read(in.buf + in.offsets[index], bytes_avail); + in.offsets[index] = bytes_avail; + in.offsets[4] += bytes_avail; + } + } + } + + QUICHandshakeMsgs out; + uint8_t out_buf[MAX_HANDSHAKE_MSG_LEN] = {0}; + out.buf = out_buf; + out.max_buf_len = MAX_HANDSHAKE_MSG_LEN; + + int result = this->_hs_protocol->handshake(&out, &in); + if (this->_remote_transport_parameters == nullptr) { + if (!this->check_remote_transport_parameters()) { + result = 0; + } + } + + if (result == 1) { + for (auto level : QUIC_ENCRYPTION_LEVELS) { + int index = static_cast(level); + QUICCryptoStream *stream = &this->_crypto_streams[index]; + size_t len = out.offsets[index + 1] - out.offsets[index]; + // TODO: check size + if (len > 0) { + stream->write(out.buf + out.offsets[index], len); + } + } + } else if (out.error_code != 0) { + this->_hs_protocol->abort_handshake(); + error = std::make_unique(QUICErrorClass::TRANSPORT, out.error_code); + } + + return error; +} + +void +QUICHandshake::_abort_handshake(QUICTransErrorCode code) +{ + QUICHSDebug("Abort Handshake"); + + this->_hs_protocol->abort_handshake(); + + this->_qc->close(QUICConnectionErrorUPtr(new QUICConnectionError(code))); +} + +/* + No limit of encryption level. + ``` + std::array _encryption_level_filter = { + QUICEncryptionLevel::INITIAL, + QUICEncryptionLevel::ZERO_RTT, + QUICEncryptionLevel::HANDSHAKE, + QUICEncryptionLevel::ONE_RTT, + }; + ``` +*/ +bool +QUICHandshake::_is_level_matched(QUICEncryptionLevel level) +{ + return true; +} diff --git a/iocore/net/quic/QUICHandshake.h b/iocore/net/quic/QUICHandshake.h new file mode 100644 index 00000000000..4dda103616a --- /dev/null +++ b/iocore/net/quic/QUICHandshake.h @@ -0,0 +1,105 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include "QUICConnection.h" +#include "QUICTransportParameters.h" +#include "QUICFrameHandler.h" +#include "QUICFrameGenerator.h" +#include "QUICCryptoStream.h" + +/** + * @class QUICHandshake + * @brief Send/Receive CRYPTO frame and do Cryptographic Handshake + */ +class QUICVersionNegotiator; +class QUICPacketFactory; +class SSLNextProtocolSet; + +class QUICHandshake : public QUICFrameHandler, public QUICFrameGenerator +{ +public: + // Constructor for client side + QUICHandshake(QUICConnection *qc, QUICHandshakeProtocol *hsp); + // Constructor for server side + QUICHandshake(QUICConnection *qc, QUICHandshakeProtocol *hsp, QUICStatelessResetToken token, bool stateless_retry); + ~QUICHandshake(); + + // QUICFrameHandler + virtual std::vector interests() override; + virtual QUICConnectionErrorUPtr handle_frame(QUICEncryptionLevel level, const QUICFrame &frame) override; + + // QUICFrameGenerator + bool will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) override; + QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size, + ink_hrtime timestamp) override; + + // for client side + QUICConnectionErrorUPtr start(const QUICTPConfig &tp_config, QUICPacketFactory *packet_factory, bool vn_exercise_enabled); + QUICConnectionErrorUPtr negotiate_version(const QUICPacket &packet, QUICPacketFactory *packet_factory); + void reset(); + + // for server side + QUICConnectionErrorUPtr start(const QUICTPConfig &tp_config, const QUICPacket &initial_packet, QUICPacketFactory *packet_factory, + const QUICPreferredAddress *pref_addr); + + QUICConnectionErrorUPtr do_handshake(); + + bool check_remote_transport_parameters(); + + // Getters + QUICVersion negotiated_version(); + const char *negotiated_cipher_suite(); + void negotiated_application_name(const uint8_t **name, unsigned int *len); + std::shared_ptr local_transport_parameters(); + std::shared_ptr remote_transport_parameters(); + + bool is_version_negotiated() const; + bool is_completed() const; + bool is_stateless_retry_enabled() const; + bool has_remote_tp() const; + +protected: + virtual bool _is_level_matched(QUICEncryptionLevel level) override; + +private: + QUICConnection *_qc = nullptr; + QUICHandshakeProtocol *_hs_protocol = nullptr; + + QUICVersionNegotiator *_version_negotiator = nullptr; + QUICStatelessResetToken _reset_token; + bool _client_initial = false; + bool _stateless_retry = false; + + QUICCryptoStream _crypto_streams[4]; + + void _load_local_server_transport_parameters(const QUICTPConfig &tp_config, const QUICPreferredAddress *pref_addr); + void _load_local_client_transport_parameters(const QUICTPConfig &tp_config); + bool _check_remote_transport_parameters(std::shared_ptr tp); + bool _check_remote_transport_parameters(std::shared_ptr tp); + std::shared_ptr _local_transport_parameters = nullptr; + std::shared_ptr _remote_transport_parameters = nullptr; + + void _abort_handshake(QUICTransErrorCode code); +}; diff --git a/iocore/net/quic/QUICHandshakeProtocol.h b/iocore/net/quic/QUICHandshakeProtocol.h new file mode 100644 index 00000000000..fd4c68b65c1 --- /dev/null +++ b/iocore/net/quic/QUICHandshakeProtocol.h @@ -0,0 +1,64 @@ +/** @file + * + * QUIC TLS + * + * @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. + */ + +#pragma once + +#include "QUICKeyGenerator.h" +#include "QUICTypes.h" +#include "QUICTransportParameters.h" + +class QUICHandshakeProtocol; +class QUICPacketProtectionKeyInfo; + +struct QUICHandshakeMsgs { + uint8_t *buf = nullptr; //< pointer to the buffer + size_t max_buf_len = 0; //< size of buffer + size_t offsets[5] = {0}; //< offset to the each encryption level - {initial, zero_rtt, handshake, one_rtt, total length} + uint16_t error_code = 0; //< CRYPTO_ERROR - TLS Alert Desciption + 0x100 +}; + +class QUICHandshakeProtocol +{ +public: + QUICHandshakeProtocol(QUICPacketProtectionKeyInfo &pp_key_info) : _pp_key_info(pp_key_info) {} + virtual ~QUICHandshakeProtocol(){}; + + virtual int handshake(QUICHandshakeMsgs *out, const QUICHandshakeMsgs *in) = 0; + virtual void reset() = 0; + virtual bool is_handshake_finished() const = 0; + virtual bool is_ready_to_derive() const = 0; + virtual int initialize_key_materials(QUICConnectionId cid) = 0; + virtual const char *negotiated_cipher_suite() const = 0; + virtual void negotiated_application_name(const uint8_t **name, unsigned int *len) const = 0; + + virtual std::shared_ptr local_transport_parameters() = 0; + virtual std::shared_ptr remote_transport_parameters() = 0; + virtual void set_local_transport_parameters(std::shared_ptr tp) = 0; + virtual void set_remote_transport_parameters(std::shared_ptr tp) = 0; + + virtual QUICEncryptionLevel current_encryption_level() const = 0; + virtual void abort_handshake() = 0; + +protected: + QUICPacketProtectionKeyInfo &_pp_key_info; +}; diff --git a/iocore/net/quic/QUICIncomingFrameBuffer.cc b/iocore/net/quic/QUICIncomingFrameBuffer.cc new file mode 100644 index 00000000000..154563f4278 --- /dev/null +++ b/iocore/net/quic/QUICIncomingFrameBuffer.cc @@ -0,0 +1,249 @@ +/** @file + * + * A brief file description + * + * @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 "QUICIncomingFrameBuffer.h" + +// +// QUICIncomingFrameBuffer +// + +QUICIncomingFrameBuffer::~QUICIncomingFrameBuffer() +{ + this->clear(); +} + +void +QUICIncomingFrameBuffer::clear() +{ + for (auto ite : this->_out_of_order_queue) { + delete ite.second; + } + this->_out_of_order_queue.clear(); + + while (!this->_recv_buffer.empty()) { + delete this->_recv_buffer.front(); + this->_recv_buffer.pop(); + } + + this->_recv_offset = 0; +} + +bool +QUICIncomingFrameBuffer::empty() +{ + return this->_out_of_order_queue.empty() && this->_recv_buffer.empty(); +} + +// +// QUICIncomingStreamFrameBuffer +// +QUICIncomingStreamFrameBuffer::~QUICIncomingStreamFrameBuffer() +{ + this->clear(); +} + +const QUICFrame * +QUICIncomingStreamFrameBuffer::pop() +{ + if (this->_recv_buffer.empty()) { + auto elem = this->_out_of_order_queue.find(this->_recv_offset); + while (elem != this->_out_of_order_queue.end()) { + const QUICStreamFrame *frame = static_cast(elem->second); + + this->_recv_buffer.push(frame); + this->_recv_offset += frame->data_length(); + this->_out_of_order_queue.erase(elem); + elem = this->_out_of_order_queue.find(this->_recv_offset); + } + } + + if (!this->_recv_buffer.empty()) { + auto frame = this->_recv_buffer.front(); + this->_recv_buffer.pop(); + return frame; + } + return nullptr; +} + +QUICConnectionErrorUPtr +QUICIncomingStreamFrameBuffer::insert(const QUICFrame *frame) +{ + const QUICStreamFrame *stream_frame = static_cast(frame); + + QUICOffset offset = stream_frame->offset(); + size_t len = stream_frame->data_length(); + + QUICConnectionErrorUPtr err = this->_check_and_set_fin_flag(offset, len, stream_frame->has_fin_flag()); + if (err != nullptr) { + delete frame; + return err; + } + + // Ignore empty stream frame except pure fin stream frame + if (len == 0 && !stream_frame->has_fin_flag()) { + delete frame; + return nullptr; + } + + if (this->_recv_offset > offset) { + // dup frame; + delete frame; + return nullptr; + } else if (this->_recv_offset == offset) { + this->_recv_offset = offset + len; + this->_recv_buffer.push(stream_frame); + } else { + this->_out_of_order_queue.insert(std::make_pair(offset, stream_frame)); + } + + return nullptr; +} + +void +QUICIncomingStreamFrameBuffer::clear() +{ + this->_fin_offset = UINT64_MAX; + this->_max_offset = 0; + + super::clear(); +} + +QUICConnectionErrorUPtr +QUICIncomingStreamFrameBuffer::_check_and_set_fin_flag(QUICOffset offset, size_t len, bool fin_flag) +{ + // stream with fin flag {11.3. Stream Final Offset} + // Once a final offset for a stream is known, it cannot change. + // If a RESET_STREAM or STREAM frame causes the final offset to change for a stream, + // an endpoint SHOULD respond with a FINAL_OFFSET_ERROR error (see Section 12). + // A receiver SHOULD treat receipt of data at or beyond the final offset as a + // FINAL_OFFSET_ERROR error, even after a stream is closed. + + // {11.3. Stream Final Offset} + // A receiver SHOULD treat receipt of data at or beyond the final offset as a + // FINAL_OFFSET_ERROR error, even after a stream is closed. + if (fin_flag) { + if (this->_fin_offset != UINT64_MAX) { + if (this->_fin_offset == offset + len) { + // dup fin frame + return nullptr; + } + return std::make_unique(QUICTransErrorCode::FINAL_OFFSET_ERROR); + } + + this->_fin_offset = offset + len; + + if (this->_max_offset > this->_fin_offset) { + return std::make_unique(QUICTransErrorCode::FINAL_OFFSET_ERROR); + } + + } else if (this->_fin_offset != UINT64_MAX && this->_fin_offset <= offset) { + return std::make_unique(QUICTransErrorCode::FINAL_OFFSET_ERROR); + } + this->_max_offset = std::max(offset + len, this->_max_offset); + + return nullptr; +} + +bool +QUICIncomingStreamFrameBuffer::is_transfer_goal_set() const +{ + return this->_fin_offset != UINT64_MAX; +} + +uint64_t +QUICIncomingStreamFrameBuffer::transfer_progress() const +{ + return this->_max_offset; +} + +uint64_t +QUICIncomingStreamFrameBuffer::transfer_goal() const +{ + return this->_fin_offset; +} + +bool +QUICIncomingStreamFrameBuffer::is_cancelled() const +{ + return false; +} + +// +// QUICIncomingCryptoFrameBuffer +// +QUICIncomingCryptoFrameBuffer::~QUICIncomingCryptoFrameBuffer() +{ + super::clear(); +} + +const QUICFrame * +QUICIncomingCryptoFrameBuffer::pop() +{ + if (this->_recv_buffer.empty()) { + auto elem = this->_out_of_order_queue.find(this->_recv_offset); + while (elem != this->_out_of_order_queue.end()) { + const QUICCryptoFrame *frame = static_cast(elem->second); + + this->_recv_buffer.push(frame); + this->_recv_offset += frame->data_length(); + this->_out_of_order_queue.erase(elem); + elem = this->_out_of_order_queue.find(this->_recv_offset); + } + } + + if (!this->_recv_buffer.empty()) { + auto frame = this->_recv_buffer.front(); + this->_recv_buffer.pop(); + return frame; + } + + return nullptr; +} + +QUICConnectionErrorUPtr +QUICIncomingCryptoFrameBuffer::insert(const QUICFrame *frame) +{ + const QUICCryptoFrame *crypto_frame = static_cast(frame); + + QUICOffset offset = crypto_frame->offset(); + size_t len = crypto_frame->data_length(); + + // Ignore empty stream frame + if (len == 0) { + delete frame; + return nullptr; + } + + if (this->_recv_offset > offset) { + // dup frame; + delete frame; + return nullptr; + } else if (this->_recv_offset == offset) { + this->_recv_offset = offset + len; + this->_recv_buffer.push(crypto_frame); + } else { + this->_out_of_order_queue.insert(std::make_pair(offset, crypto_frame)); + } + + return nullptr; +} diff --git a/iocore/net/quic/QUICIncomingFrameBuffer.h b/iocore/net/quic/QUICIncomingFrameBuffer.h new file mode 100644 index 00000000000..6251908b6d6 --- /dev/null +++ b/iocore/net/quic/QUICIncomingFrameBuffer.h @@ -0,0 +1,86 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include +#include + +#include "QUICTypes.h" +#include "QUICFrame.h" +#include "QUICTransferProgressProvider.h" + +class QUICIncomingFrameBuffer +{ +public: + ~QUICIncomingFrameBuffer(); + virtual const QUICFrame *pop() = 0; + virtual QUICConnectionErrorUPtr insert(const QUICFrame *frame) = 0; + virtual void clear(); + virtual bool empty(); + +protected: + QUICOffset _recv_offset = 0; + + std::queue _recv_buffer; + std::map _out_of_order_queue; +}; + +class QUICIncomingStreamFrameBuffer : public QUICIncomingFrameBuffer, public QUICTransferProgressProvider +{ +public: + using super = QUICIncomingFrameBuffer; ///< Parent type. + + QUICIncomingStreamFrameBuffer() {} + ~QUICIncomingStreamFrameBuffer(); + + const QUICFrame *pop() override; + QUICConnectionErrorUPtr insert(const QUICFrame *frame) override; + void clear() override; + + // QUICTransferProgressProvider + bool is_transfer_goal_set() const override; + uint64_t transfer_progress() const override; + uint64_t transfer_goal() const override; + bool is_cancelled() const override; + +private: + QUICConnectionErrorUPtr _check_and_set_fin_flag(QUICOffset offset, size_t len = 0, bool fin_flag = false); + + QUICOffset _max_offset = 0; + QUICOffset _fin_offset = UINT64_MAX; +}; + +class QUICIncomingCryptoFrameBuffer : public QUICIncomingFrameBuffer +{ +public: + using super = QUICIncomingFrameBuffer; ///< Parent type. + + QUICIncomingCryptoFrameBuffer() {} + ~QUICIncomingCryptoFrameBuffer(); + + const QUICFrame *pop() override; + QUICConnectionErrorUPtr insert(const QUICFrame *frame) override; + +private: +}; diff --git a/iocore/net/quic/QUICIntUtil.cc b/iocore/net/quic/QUICIntUtil.cc new file mode 100644 index 00000000000..e9840ca2d61 --- /dev/null +++ b/iocore/net/quic/QUICIntUtil.cc @@ -0,0 +1,133 @@ +/** @file + * + * A brief file description + * + * @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 "QUICIntUtil.h" +#include "tscore/ink_endian.h" +#include +#include + +size_t +QUICVariableInt::size(const uint8_t *src) +{ + return 1 << (src[0] >> 6); +} + +size_t +QUICVariableInt::size(uint64_t src) +{ + uint8_t flag = 0; + if (src > 4611686018427387903) { + // max usable bits is 62 + return 0; + } else if (src > 1073741823) { + flag = 0x03; + } else if (src > 16383) { + flag = 0x02; + } else if (src > 63) { + flag = 0x01; + } else { + flag = 0x00; + } + + return 1 << flag; +} + +int +QUICVariableInt::encode(uint8_t *dst, size_t dst_len, size_t &len, uint64_t src) +{ + uint8_t flag = 0; + if (src > 4611686018427387903) { + // max usable bits is 62 + return 1; + } else if (src > 1073741823) { + flag = 0x03; + } else if (src > 16383) { + flag = 0x02; + } else if (src > 63) { + flag = 0x01; + } else { + flag = 0x00; + } + + len = 1 << flag; + if (len > dst_len) { + return 1; + } + + size_t dummy = 0; + QUICIntUtil::write_uint_as_nbytes(src, len, dst, &dummy); + dst[0] |= (flag << 6); + + return 0; +} + +int +QUICVariableInt::decode(uint64_t &dst, size_t &len, const uint8_t *src, size_t src_len) +{ + if (src_len < 1) { + return -1; + } + len = 1 << (src[0] >> 6); + if (src_len < len) { + return 1; + } + + uint8_t buf[8] = {0}; + memcpy(buf, src, len); + buf[0] &= 0x3f; + + dst = QUICIntUtil::read_nbytes_as_uint(buf, len); + + return 0; +} + +uint64_t +QUICIntUtil::read_QUICVariableInt(const uint8_t *buf) +{ + uint64_t dst = 0; + size_t len = 0; + QUICVariableInt::decode(dst, len, buf, 8); + return dst; +} + +void +QUICIntUtil::write_QUICVariableInt(uint64_t data, uint8_t *buf, size_t *len) +{ + QUICVariableInt::encode(buf, 8, *len, data); +} + +uint64_t +QUICIntUtil::read_nbytes_as_uint(const uint8_t *buf, uint8_t n) +{ + uint64_t value = 0; + memcpy(&value, buf, n); + return be64toh(value << (64 - n * 8)); +} + +void +QUICIntUtil::write_uint_as_nbytes(uint64_t value, uint8_t n, uint8_t *buf, size_t *len) +{ + value = htobe64(value) >> (64 - n * 8); + memcpy(buf, reinterpret_cast(&value), n); + *len = n; +} diff --git a/iocore/net/quic/QUICIntUtil.h b/iocore/net/quic/QUICIntUtil.h new file mode 100644 index 00000000000..c259bca682a --- /dev/null +++ b/iocore/net/quic/QUICIntUtil.h @@ -0,0 +1,45 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include +#include + +class QUICVariableInt +{ +public: + static size_t size(const uint8_t *src); + static size_t size(uint64_t src); + static int encode(uint8_t *dst, size_t dst_len, size_t &len, uint64_t src); + static int decode(uint64_t &dst, size_t &len, const uint8_t *src, size_t src_len = 8); +}; + +class QUICIntUtil +{ +public: + static uint64_t read_QUICVariableInt(const uint8_t *buf); + static void write_QUICVariableInt(uint64_t data, uint8_t *buf, size_t *len); + static uint64_t read_nbytes_as_uint(const uint8_t *buf, uint8_t n); + static void write_uint_as_nbytes(uint64_t value, uint8_t n, uint8_t *buf, size_t *len); +}; diff --git a/iocore/net/quic/QUICKeyGenerator.cc b/iocore/net/quic/QUICKeyGenerator.cc new file mode 100644 index 00000000000..05c1796f950 --- /dev/null +++ b/iocore/net/quic/QUICKeyGenerator.cc @@ -0,0 +1,148 @@ +/** @file + * + * A key generator for QUIC connection + * + * @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 "QUICKeyGenerator.h" + +#include + +#include "tscore/ink_assert.h" +#include "tscore/Diags.h" + +#include "QUICHKDF.h" +#include "QUICDebugNames.h" + +using namespace std::literals; + +constexpr static uint8_t QUIC_VERSION_1_SALT[] = { + 0xef, 0x4f, 0xb0, 0xab, 0xb4, 0x74, 0x70, 0xc4, 0x1b, 0xef, 0xcf, 0x80, 0x31, 0x33, 0x4f, 0xae, 0x48, 0x5e, 0x09, 0xa0, +}; +constexpr static std::string_view LABEL_FOR_CLIENT_INITIAL_SECRET("client in"sv); +constexpr static std::string_view LABEL_FOR_SERVER_INITIAL_SECRET("server in"sv); +constexpr static std::string_view LABEL_FOR_KEY("quic key"sv); +constexpr static std::string_view LABEL_FOR_IV("quic iv"sv); +constexpr static std::string_view LABEL_FOR_HP("quic hp"sv); + +void +QUICKeyGenerator::generate(uint8_t *hp_key, uint8_t *pp_key, uint8_t *iv, size_t *iv_len, QUICConnectionId cid) +{ + const QUIC_EVP_CIPHER *cipher = this->_get_cipher_for_initial(); + const EVP_MD *md = EVP_sha256(); + uint8_t secret[512]; + size_t secret_len = sizeof(secret); + QUICHKDF hkdf(md); + + switch (this->_ctx) { + case Context::CLIENT: + this->_generate_initial_secret(secret, &secret_len, hkdf, cid, LABEL_FOR_CLIENT_INITIAL_SECRET.data(), + LABEL_FOR_CLIENT_INITIAL_SECRET.length(), EVP_MD_size(md)); + if (is_debug_tag_set("vv_quic_crypto")) { + uint8_t print_buf[1024 + 1]; + QUICDebug::to_hex(print_buf, secret, secret_len); + Debug("vv_quic_crypto", "client_in_secret=%s", print_buf); + } + + break; + case Context::SERVER: + this->_generate_initial_secret(secret, &secret_len, hkdf, cid, LABEL_FOR_SERVER_INITIAL_SECRET.data(), + LABEL_FOR_SERVER_INITIAL_SECRET.length(), EVP_MD_size(md)); + if (is_debug_tag_set("vv_quic_crypto")) { + uint8_t print_buf[1024 + 1]; + QUICDebug::to_hex(print_buf, secret, secret_len); + Debug("vv_quic_crypto", "server_in_secret=%s", print_buf); + } + + break; + } + + this->_generate(hp_key, pp_key, iv, iv_len, hkdf, secret, secret_len, cipher); +} + +void +QUICKeyGenerator::regenerate(uint8_t *hp_key, uint8_t *pp_key, uint8_t *iv, size_t *iv_len, const uint8_t *secret, + size_t secret_len, const QUIC_EVP_CIPHER *cipher, QUICHKDF &hkdf) +{ + this->_generate(hp_key, pp_key, iv, iv_len, hkdf, secret, secret_len, cipher); +} + +int +QUICKeyGenerator::_generate(uint8_t *hp_key, uint8_t *pp_key, uint8_t *iv, size_t *iv_len, QUICHKDF &hkdf, const uint8_t *secret, + size_t secret_len, const QUIC_EVP_CIPHER *cipher) +{ + // Generate key, iv, and hp_key + // key = HKDF-Expand-Label(S, "quic key", "", key_length) + // iv = HKDF-Expand-Label(S, "quic iv", "", iv_length) + // hp_key = HKDF-Expand-Label(S, "quic hp", "", hp_key_length) + size_t dummy; + this->_generate_key(pp_key, &dummy, hkdf, secret, secret_len, this->_get_key_len(cipher)); + this->_generate_iv(iv, iv_len, hkdf, secret, secret_len, this->_get_iv_len(cipher)); + this->_generate_hp(hp_key, &dummy, hkdf, secret, secret_len, this->_get_key_len(cipher)); + + return 0; +} + +int +QUICKeyGenerator::_generate_initial_secret(uint8_t *out, size_t *out_len, QUICHKDF &hkdf, QUICConnectionId cid, const char *label, + size_t label_len, size_t length) +{ + uint8_t client_connection_id[QUICConnectionId::MAX_LENGTH]; + size_t cid_len = 0; + uint8_t initial_secret[512]; + size_t initial_secret_len = sizeof(initial_secret); + + // TODO: do not extract initial secret twice + QUICTypeUtil::write_QUICConnectionId(cid, client_connection_id, &cid_len); + if (hkdf.extract(initial_secret, &initial_secret_len, QUIC_VERSION_1_SALT, sizeof(QUIC_VERSION_1_SALT), client_connection_id, + cid.length()) != 1) { + return -1; + } + + if (is_debug_tag_set("vv_quic_crypto")) { + uint8_t print_buf[1024 + 1]; + QUICDebug::to_hex(print_buf, initial_secret, initial_secret_len); + Debug("vv_quic_crypto", "initial_secret=%s", print_buf); + } + + hkdf.expand(out, out_len, initial_secret, initial_secret_len, reinterpret_cast(label), label_len, length); + return 0; +} + +int +QUICKeyGenerator::_generate_key(uint8_t *out, size_t *out_len, QUICHKDF &hkdf, const uint8_t *secret, size_t secret_len, + size_t key_length) const +{ + return hkdf.expand(out, out_len, secret, secret_len, LABEL_FOR_KEY.data(), LABEL_FOR_KEY.length(), key_length); +} + +int +QUICKeyGenerator::_generate_iv(uint8_t *out, size_t *out_len, QUICHKDF &hkdf, const uint8_t *secret, size_t secret_len, + size_t iv_length) const +{ + return hkdf.expand(out, out_len, secret, secret_len, LABEL_FOR_IV.data(), LABEL_FOR_IV.length(), iv_length); +} + +int +QUICKeyGenerator::_generate_hp(uint8_t *out, size_t *out_len, QUICHKDF &hkdf, const uint8_t *secret, size_t secret_len, + size_t hp_length) const +{ + return hkdf.expand(out, out_len, secret, secret_len, LABEL_FOR_HP.data(), LABEL_FOR_HP.length(), hp_length); +} diff --git a/iocore/net/quic/QUICKeyGenerator.h b/iocore/net/quic/QUICKeyGenerator.h new file mode 100644 index 00000000000..a82ad43a2a3 --- /dev/null +++ b/iocore/net/quic/QUICKeyGenerator.h @@ -0,0 +1,69 @@ +/** @file + * + * A key generator for QUIC connection + * + * @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. + */ +#pragma once + +#include +#include "QUICTypes.h" +#include "QUICHKDF.h" + +#ifdef OPENSSL_IS_BORINGSSL +typedef EVP_AEAD QUIC_EVP_CIPHER; +#else +typedef EVP_CIPHER QUIC_EVP_CIPHER; +#endif // OPENSSL_IS_BORINGSSL + +class QUICKeyGenerator +{ +public: + enum class Context { SERVER, CLIENT }; + + QUICKeyGenerator(Context ctx) : _ctx(ctx) {} + + /** + * Generate keys for Initial encryption level + * The keys for the remaining encryption level are derived by TLS stack with "quic " prefix + */ + void generate(uint8_t *hp_key, uint8_t *pp_key, uint8_t *iv, size_t *iv_len, QUICConnectionId cid); + + void regenerate(uint8_t *hp_key, uint8_t *pp_key, uint8_t *iv, size_t *iv_len, const uint8_t *secret, size_t secret_len, + const QUIC_EVP_CIPHER *cipher, QUICHKDF &hkdf); + +private: + Context _ctx = Context::SERVER; + + uint8_t _last_secret[256]; + size_t _last_secret_len = 0; + + int _generate(uint8_t *hp_key, uint8_t *pp_key, uint8_t *iv, size_t *iv_len, QUICHKDF &hkdf, const uint8_t *secret, + size_t secret_len, const QUIC_EVP_CIPHER *cipher); + int _generate_initial_secret(uint8_t *out, size_t *out_len, QUICHKDF &hkdf, QUICConnectionId cid, const char *label, + size_t label_len, size_t length); + int _generate_key(uint8_t *out, size_t *out_len, QUICHKDF &hkdf, const uint8_t *secret, size_t secret_len, + size_t key_length) const; + int _generate_iv(uint8_t *out, size_t *out_len, QUICHKDF &hkdf, const uint8_t *secret, size_t secret_len, size_t iv_length) const; + int _generate_hp(uint8_t *out, size_t *out_len, QUICHKDF &hkdf, const uint8_t *secret, size_t secret_len, size_t hp_length) const; + size_t _get_key_len(const QUIC_EVP_CIPHER *cipher) const; + size_t _get_iv_len(const QUIC_EVP_CIPHER *cipher) const; + const QUIC_EVP_CIPHER *_get_cipher_for_initial() const; + const QUIC_EVP_CIPHER *_get_cipher_for_protected_packet(const SSL *ssl) const; +}; diff --git a/iocore/net/quic/QUICKeyGenerator_boringssl.cc b/iocore/net/quic/QUICKeyGenerator_boringssl.cc new file mode 100644 index 00000000000..e2204bbd3b0 --- /dev/null +++ b/iocore/net/quic/QUICKeyGenerator_boringssl.cc @@ -0,0 +1,77 @@ +/** @file + * + * A key generator for QUIC connection (BoringSSL specific parts) + * + * @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 "tscore/ink_assert.h" +#include "QUICKeyGenerator.h" + +#include +size_t +QUICKeyGenerator::_get_key_len(const QUIC_EVP_CIPHER *cipher) const +{ + return EVP_AEAD_key_length(cipher); +} + +size_t +QUICKeyGenerator::_get_iv_len(const QUIC_EVP_CIPHER *cipher) const +{ + return EVP_AEAD_nonce_length(cipher); +} + +const QUIC_EVP_CIPHER * +QUICKeyGenerator::_get_cipher_for_initial() const +{ + return EVP_aead_aes_128_gcm(); +} + +const QUIC_EVP_CIPHER * +QUICKeyGenerator::_get_cipher_for_protected_packet(const SSL *ssl) const +{ + switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl))) { + case TLS1_CK_AES_128_GCM_SHA256: + return EVP_aead_aes_128_gcm(); + case TLS1_CK_AES_256_GCM_SHA384: + return EVP_aead_aes_256_gcm(); + case TLS1_CK_CHACHA20_POLY1305_SHA256: + return EVP_aead_chacha20_poly1305(); + default: + ink_assert(false); + return nullptr; + } +} + +// SSL_HANDSHAKE_MAC_SHA256, SSL_HANDSHAKE_MAC_SHA384 are defind in `ssl/internal.h` of BoringSSL +/* +const EVP_MD * +QUICKeyGenerator::get_handshake_digest(const SSL *ssl) +{ + switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl))) { + case TLS1_CK_AES_128_GCM_SHA256: + case TLS1_CK_CHACHA20_POLY1305_SHA256: + return EVP_sha256(); + case TLS1_CK_AES_256_GCM_SHA384: + return EVP_sha384(); + default: + ink_assert(false); + return nullptr; + } +} +*/ diff --git a/iocore/net/quic/QUICKeyGenerator_openssl.cc b/iocore/net/quic/QUICKeyGenerator_openssl.cc new file mode 100644 index 00000000000..5d9d0294e0f --- /dev/null +++ b/iocore/net/quic/QUICKeyGenerator_openssl.cc @@ -0,0 +1,63 @@ +/** @file + * + * A key generator for QUIC connection (OpenSSL specific parts) + * + * @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 "tscore/ink_assert.h" +#include "QUICKeyGenerator.h" + +#include + +size_t +QUICKeyGenerator::_get_key_len(const QUIC_EVP_CIPHER *cipher) const +{ + return EVP_CIPHER_key_length(cipher); +} + +size_t +QUICKeyGenerator::_get_iv_len(const QUIC_EVP_CIPHER *cipher) const +{ + return EVP_CIPHER_iv_length(cipher); +} + +const QUIC_EVP_CIPHER * +QUICKeyGenerator::_get_cipher_for_initial() const +{ + return EVP_aes_128_gcm(); +} + +const QUIC_EVP_CIPHER * +QUICKeyGenerator::_get_cipher_for_protected_packet(const SSL *ssl) const +{ + switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl))) { + case TLS1_3_CK_AES_128_GCM_SHA256: + return EVP_aes_128_gcm(); + case TLS1_3_CK_AES_256_GCM_SHA384: + return EVP_aes_256_gcm(); + case TLS1_3_CK_CHACHA20_POLY1305_SHA256: + return EVP_chacha20_poly1305(); + case TLS1_3_CK_AES_128_CCM_SHA256: + case TLS1_3_CK_AES_128_CCM_8_SHA256: + return EVP_aes_128_ccm(); + default: + ink_assert(false); + return nullptr; + } +} diff --git a/iocore/net/quic/QUICLossDetector.cc b/iocore/net/quic/QUICLossDetector.cc new file mode 100644 index 00000000000..7cd94c0f394 --- /dev/null +++ b/iocore/net/quic/QUICLossDetector.cc @@ -0,0 +1,673 @@ +/** @file + * + * A brief file description + * + * @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 "QUICLossDetector.h" + +#include "tscore/ink_assert.h" + +#include "QUICConfig.h" +#include "QUICEvents.h" +#include "QUICDebugNames.h" +#include "QUICFrameGenerator.h" + +#define QUICLDDebug(fmt, ...) Debug("quic_loss_detector", "[%s] " fmt, this->_info->cids().data(), ##__VA_ARGS__) +#define QUICLDVDebug(fmt, ...) Debug("v_quic_loss_detector", "[%s] " fmt, this->_info->cids().data(), ##__VA_ARGS__) + +QUICLossDetector::QUICLossDetector(QUICConnectionInfoProvider *info, QUICCongestionController *cc, QUICRTTMeasure *rtt_measure, + const QUICLDConfig &ld_config) + : _info(info), _rtt_measure(rtt_measure), _cc(cc) +{ + this->mutex = new_ProxyMutex(); + this->_loss_detection_mutex = new_ProxyMutex(); + + this->_k_packet_threshold = ld_config.packet_threshold(); + this->_k_time_threshold = ld_config.time_threshold(); + + this->reset(); + + SET_HANDLER(&QUICLossDetector::event_handler); +} + +QUICLossDetector::~QUICLossDetector() +{ + if (this->_loss_detection_timer) { + this->_loss_detection_timer->cancel(); + this->_loss_detection_timer = nullptr; + } + + for (auto i = 0; i < kPacketNumberSpace; i++) { + this->_sent_packets[i].clear(); + } +} + +int +QUICLossDetector::event_handler(int event, Event *edata) +{ + switch (event) { + case EVENT_INTERVAL: { + if (this->_loss_detection_alarm_at <= Thread::get_hrtime()) { + this->_loss_detection_alarm_at = 0; + this->_on_loss_detection_timeout(); + } + break; + } + case QUIC_EVENT_LD_SHUTDOWN: { + SCOPED_MUTEX_LOCK(lock, this->_loss_detection_mutex, this_ethread()); + QUICLDDebug("Shutdown"); + + if (this->_loss_detection_timer) { + this->_loss_detection_timer->cancel(); + this->_loss_detection_timer = nullptr; + } + break; + } + default: + break; + } + return EVENT_CONT; +} + +std::vector +QUICLossDetector::interests() +{ + return {QUICFrameType::ACK}; +} + +QUICConnectionErrorUPtr +QUICLossDetector::handle_frame(QUICEncryptionLevel level, const QUICFrame &frame) +{ + QUICConnectionErrorUPtr error = nullptr; + + switch (frame.type()) { + case QUICFrameType::ACK: + this->_on_ack_received(static_cast(frame), QUICTypeUtil::pn_space(level)); + break; + default: + QUICLDDebug("Unexpected frame type: %02x", static_cast(frame.type())); + ink_assert(false); + break; + } + + return error; +} + +QUICPacketNumber +QUICLossDetector::largest_acked_packet_number(QUICPacketNumberSpace pn_space) +{ + int index = static_cast(pn_space); + return this->_largest_acked_packet[index]; +} + +void +QUICLossDetector::on_packet_sent(QUICPacketInfoUPtr packet_info, bool in_flight) +{ + if (packet_info->type == QUICPacketType::VERSION_NEGOTIATION) { + return; + } + + SCOPED_MUTEX_LOCK(lock, this->_loss_detection_mutex, this_ethread()); + + QUICPacketNumber packet_number = packet_info->packet_number; + bool ack_eliciting = packet_info->ack_eliciting; + bool is_crypto_packet = packet_info->is_crypto_packet; + ink_hrtime now = packet_info->time_sent; + size_t sent_bytes = packet_info->sent_bytes; + + this->_add_to_sent_packet_list(packet_number, std::move(packet_info)); + + if (in_flight) { + if (is_crypto_packet) { + this->_time_of_last_sent_crypto_packet = now; + } + + if (ack_eliciting) { + this->_time_of_last_sent_ack_eliciting_packet = now; + } + this->_cc->on_packet_sent(sent_bytes); + this->_set_loss_detection_timer(); + } +} + +void +QUICLossDetector::reset() +{ + SCOPED_MUTEX_LOCK(lock, this->_loss_detection_mutex, this_ethread()); + if (this->_loss_detection_timer) { + this->_loss_detection_timer->cancel(); + this->_loss_detection_timer = nullptr; + } + + this->_ack_eliciting_outstanding = 0; + this->_crypto_outstanding = 0; + + // [draft-17 recovery] 6.4.3. Initialization + this->_time_of_last_sent_ack_eliciting_packet = 0; + this->_time_of_last_sent_crypto_packet = 0; + for (auto i = 0; i < kPacketNumberSpace; i++) { + this->_largest_acked_packet[i] = 0; + this->_loss_time[i] = 0; + this->_sent_packets[i].clear(); + } + + this->_rtt_measure->reset(); +} + +void +QUICLossDetector::update_ack_delay_exponent(uint8_t ack_delay_exponent) +{ + this->_ack_delay_exponent = ack_delay_exponent; +} + +void +QUICLossDetector::_on_ack_received(const QUICAckFrame &ack_frame, QUICPacketNumberSpace pn_space) +{ + SCOPED_MUTEX_LOCK(lock, this->_loss_detection_mutex, this_ethread()); + + int index = static_cast(pn_space); + this->_largest_acked_packet[index] = std::max(this->_largest_acked_packet[index], ack_frame.largest_acknowledged()); + // If the largest acknowledged is newly acked and + // ack-eliciting, update the RTT. + auto pi = this->_sent_packets[index].find(ack_frame.largest_acknowledged()); + if (pi != this->_sent_packets[index].end() && pi->second->ack_eliciting) { + ink_hrtime latest_rtt = Thread::get_hrtime() - pi->second->time_sent; + // _latest_rtt is nanosecond but ack_frame.ack_delay is microsecond and scaled + ink_hrtime delay = HRTIME_USECONDS(ack_frame.ack_delay() << this->_ack_delay_exponent); + this->_rtt_measure->update_rtt(latest_rtt, delay); + } + + QUICLDVDebug("[%s] Unacked packets %lu (retransmittable %u, includes %u handshake packets)", QUICDebugNames::pn_space(pn_space), + this->_sent_packets[index].size(), this->_ack_eliciting_outstanding.load(), this->_crypto_outstanding.load()); + + // if (ACK frame contains ECN information): + // ProcessECN(ack) + if (ack_frame.ecn_section() != nullptr && pi != this->_sent_packets[index].end()) { + this->_cc->process_ecn(*pi->second, ack_frame.ecn_section()); + } + + // Find all newly acked packets. + bool newly_acked_packets = false; + for (auto &&range : this->_determine_newly_acked_packets(ack_frame)) { + for (auto ite = this->_sent_packets[index].begin(); ite != this->_sent_packets[index].end(); /* no increment here*/) { + auto tmp_ite = ite; + tmp_ite++; + if (range.contains(ite->first)) { + newly_acked_packets = true; + this->_on_packet_acked(*(ite->second)); + } + ite = tmp_ite; + } + } + + if (!newly_acked_packets) { + return; + } + + QUICLDVDebug("[%s] Unacked packets %lu (retransmittable %u, includes %u handshake packets)", QUICDebugNames::pn_space(pn_space), + this->_sent_packets[index].size(), this->_ack_eliciting_outstanding.load(), this->_crypto_outstanding.load()); + + this->_detect_lost_packets(pn_space); + + this->_rtt_measure->set_crypto_count(0); + this->_rtt_measure->set_pto_count(0); + + QUICLDDebug("[%s] Unacked packets %lu (retransmittable %u, includes %u handshake packets)", QUICDebugNames::pn_space(pn_space), + this->_sent_packets[index].size(), this->_ack_eliciting_outstanding.load(), this->_crypto_outstanding.load()); + + this->_set_loss_detection_timer(); +} + +void +QUICLossDetector::_on_packet_acked(const QUICPacketInfo &acked_packet) +{ + SCOPED_MUTEX_LOCK(lock, this->_loss_detection_mutex, this_ethread()); + // QUICLDDebug("Packet number %" PRIu64 " has been acked", acked_packet_number); + + if (acked_packet.ack_eliciting) { + this->_cc->on_packet_acked(acked_packet); + } + + for (const QUICFrameInfo &frame_info : acked_packet.frames) { + auto reactor = frame_info.generated_by(); + if (reactor == nullptr) { + continue; + } + + reactor->on_frame_acked(frame_info.id()); + } + + this->_remove_from_sent_packet_list(acked_packet.packet_number, acked_packet.pn_space); +} + +ink_hrtime +QUICLossDetector::_get_earliest_loss_time(QUICPacketNumberSpace &pn_space) +{ + ink_hrtime time = this->_loss_time[static_cast(QUICPacketNumberSpace::Initial)]; + pn_space = QUICPacketNumberSpace::Initial; + for (auto i = 1; i < kPacketNumberSpace; i++) { + if (this->_loss_time[i] != 0 && (time != 0 || this->_loss_time[i] < time)) { + time = this->_loss_time[i]; + pn_space = static_cast(i); + } + } + + return time; +} + +void +QUICLossDetector::_set_loss_detection_timer() +{ + // Don't arm the alarm if there are no packets with retransmittable data in flight. + // -- MODIFIED CODE -- + // In psuedocode, `bytes_in_flight` is used, but we're tracking "retransmittable data in flight" by `_ack_eliciting_outstanding` + if (this->_ack_eliciting_outstanding == 0) { + if (this->_loss_detection_timer) { + this->_loss_detection_alarm_at = 0; + this->_loss_detection_timer->cancel(); + this->_loss_detection_timer = nullptr; + QUICLDDebug("Loss detection alarm has been unset"); + } + + return; + } + // -- END OF MODIFIED CODE -- + + QUICPacketNumberSpace pn_space; + ink_hrtime loss_time = this->_get_earliest_loss_time(pn_space); + if (loss_time != 0) { + // Time threshold loss detection. + this->_loss_detection_alarm_at = loss_time; + QUICLDDebug("[%s] time threshold loss detection timer: %" PRId64, QUICDebugNames::pn_space(pn_space), + this->_loss_detection_alarm_at); + } else if (this->_crypto_outstanding) { + // Handshake retransmission alarm. + this->_loss_detection_alarm_at = this->_time_of_last_sent_crypto_packet + this->_rtt_measure->handshake_retransmit_timeout(); + QUICLDDebug("%s crypto packet alarm will be set: %" PRId64, QUICDebugNames::pn_space(pn_space), this->_loss_detection_alarm_at); + // -- ADDITIONAL CODE -- + // In psudocode returning here, but we don't do for scheduling _loss_detection_alarm event. + // -- END OF ADDITIONAL CODE -- + } else { + // PTO Duration + this->_loss_detection_alarm_at = this->_time_of_last_sent_ack_eliciting_packet + this->_rtt_measure->current_pto_period(); + QUICLDDebug("[%s] PTO timeout will be set: %" PRId64, QUICDebugNames::pn_space(pn_space), this->_loss_detection_alarm_at); + } + + if (!this->_loss_detection_timer) { + this->_loss_detection_timer = eventProcessor.schedule_every(this, HRTIME_MSECONDS(25)); + } +} + +void +QUICLossDetector::_on_loss_detection_timeout() +{ + QUICPacketNumberSpace pn_space; + ink_hrtime loss_time = this->_get_earliest_loss_time(pn_space); + if (loss_time != 0) { + // Time threshold loss Detection + this->_detect_lost_packets(pn_space); + } else if (this->_crypto_outstanding) { + // Handshake retransmission alarm. + this->_retransmit_all_unacked_crypto_data(); + this->_rtt_measure->set_crypto_count(this->_rtt_measure->crypto_count() + 1); + } else { + QUICLDVDebug("PTO"); + this->_send_two_packets(); + this->_rtt_measure->set_pto_count(this->_rtt_measure->pto_count() + 1); + } + + QUICLDDebug("[%s] Unacked packets %lu (retransmittable %u, includes %u handshake packets)", QUICDebugNames::pn_space(pn_space), + this->_sent_packets[static_cast(pn_space)].size(), this->_ack_eliciting_outstanding.load(), + this->_crypto_outstanding.load()); + + if (is_debug_tag_set("v_quic_loss_detector")) { + for (auto i = 0; i < 3; i++) { + for (auto &unacked : this->_sent_packets[i]) { + QUICLDVDebug("[%s] #%" PRIu64 " is_crypto=%i ack_eliciting=%i size=%zu %u %u", + QUICDebugNames::pn_space(static_cast(i)), unacked.first, + unacked.second->is_crypto_packet, unacked.second->ack_eliciting, unacked.second->sent_bytes, + this->_ack_eliciting_outstanding.load(), this->_crypto_outstanding.load()); + } + } + } + + this->_set_loss_detection_timer(); +} + +void +QUICLossDetector::_detect_lost_packets(QUICPacketNumberSpace pn_space) +{ + SCOPED_MUTEX_LOCK(lock, this->_loss_detection_mutex, this_ethread()); + this->_loss_time[static_cast(pn_space)] = 0; + ink_hrtime loss_delay = this->_k_time_threshold * std::max(this->_rtt_measure->latest_rtt(), this->_rtt_measure->smoothed_rtt()); + std::map lost_packets; + + // Packets sent before this time are deemed lost. + ink_hrtime lost_send_time = Thread::get_hrtime() - loss_delay; + + // Packets with packet numbers before this are deemed lost. + QUICPacketNumber lost_pn = this->_largest_acked_packet[static_cast(pn_space)] - this->_k_packet_threshold; + + for (auto it = this->_sent_packets[static_cast(pn_space)].begin(); + it != this->_sent_packets[static_cast(pn_space)].end(); ++it) { + if (it->first > this->_largest_acked_packet[static_cast(pn_space)]) { + // the spec uses continue but we can break here because the _sent_packets is sorted by packet_number. + break; + } + + auto &unacked = it->second; + + // Mark packet as lost, or set time when it should be marked. + if (unacked->time_sent < lost_send_time || unacked->packet_number < lost_pn) { + if (unacked->time_sent < lost_send_time) { + QUICLDDebug("[%s] Lost: time since sent is too long (#%" PRId64 " sent=%" PRId64 ", delay=%" PRId64 + ", fraction=%lf, lrtt=%" PRId64 ", srtt=%" PRId64 ")", + QUICDebugNames::pn_space(pn_space), it->first, unacked->time_sent, lost_send_time, this->_k_time_threshold, + this->_rtt_measure->latest_rtt(), this->_rtt_measure->smoothed_rtt()); + } else { + QUICLDDebug("[%s] Lost: packet delta is too large (#%" PRId64 " largest=%" PRId64 " threshold=%" PRId32 ")", + QUICDebugNames::pn_space(pn_space), it->first, this->_largest_acked_packet[static_cast(pn_space)], + this->_k_packet_threshold); + } + + if (unacked->in_flight) { + lost_packets.insert({it->first, it->second.get()}); + } else if (this->_loss_time[static_cast(pn_space)] == 0) { + this->_loss_time[static_cast(pn_space)] = unacked->time_sent + loss_delay; + } else { + this->_loss_time[static_cast(pn_space)] = + std::min(this->_loss_time[static_cast(pn_space)], unacked->time_sent + loss_delay); + } + } + } + + // Inform the congestion controller of lost packets and + // lets it decide whether to retransmit immediately. + if (!lost_packets.empty()) { + this->_cc->on_packets_lost(lost_packets); + for (auto lost_packet : lost_packets) { + // -- ADDITIONAL CODE -- + // Not sure how we can get feedback from congestion control and when we should retransmit the lost packets but we need to send + // them somewhere. + // I couldn't find the place so just send them here for now. + this->_retransmit_lost_packet(*lost_packet.second); + // -- END OF ADDITIONAL CODE -- + // -- ADDITIONAL CODE -- + this->_remove_from_sent_packet_list(lost_packet.first, pn_space); + // -- END OF ADDITIONAL CODE -- + } + } +} + +// ===== Functions below are used on the spec but there're no pseudo code ===== + +void +QUICLossDetector::_retransmit_all_unacked_crypto_data() +{ + SCOPED_MUTEX_LOCK(lock, this->_loss_detection_mutex, this_ethread()); + for (auto i = 0; i < kPacketNumberSpace; i++) { + std::set retransmitted_crypto_packets; + std::map lost_packets; + for (auto &info : this->_sent_packets[i]) { + if (info.second->is_crypto_packet) { + retransmitted_crypto_packets.insert(info.first); + this->_retransmit_lost_packet(*info.second); + lost_packets.insert({info.first, info.second.get()}); + } + } + + this->_cc->on_packets_lost(lost_packets); + for (auto packet_number : retransmitted_crypto_packets) { + this->_remove_from_sent_packet_list(packet_number, static_cast(i)); + } + } +} + +void +QUICLossDetector::_send_two_packets() +{ + SCOPED_MUTEX_LOCK(lock, this->_loss_detection_mutex, this_ethread()); + // TODO sent ping +} + +// ===== Functions below are helper functions ===== + +void +QUICLossDetector::_retransmit_lost_packet(QUICPacketInfo &packet_info) +{ + SCOPED_MUTEX_LOCK(lock, this->_loss_detection_mutex, this_ethread()); + + QUICLDDebug("Retransmit %s packet #%" PRIu64, QUICDebugNames::packet_type(packet_info.type), packet_info.packet_number); + for (QUICFrameInfo &frame_info : packet_info.frames) { + auto reactor = frame_info.generated_by(); + if (reactor == nullptr) { + continue; + } + + reactor->on_frame_lost(frame_info.id()); + } +} + +std::set +QUICLossDetector::_determine_newly_acked_packets(const QUICAckFrame &ack_frame) +{ + std::set numbers; + QUICPacketNumber x = ack_frame.largest_acknowledged(); + numbers.insert({x, static_cast(x) - ack_frame.ack_block_section()->first_ack_block()}); + x -= ack_frame.ack_block_section()->first_ack_block() + 1; + for (auto &&block : *(ack_frame.ack_block_section())) { + x -= block.gap() + 1; + numbers.insert({x, static_cast(x) - block.length()}); + x -= block.length() + 1; + } + + return numbers; +} + +void +QUICLossDetector::_add_to_sent_packet_list(QUICPacketNumber packet_number, QUICPacketInfoUPtr packet_info) +{ + SCOPED_MUTEX_LOCK(lock, this->_loss_detection_mutex, this_ethread()); + + // Add to the list + int index = static_cast(packet_info->pn_space); + this->_sent_packets[index].insert(std::pair(packet_number, std::move(packet_info))); + + // Increment counters + auto ite = this->_sent_packets[index].find(packet_number); + if (ite != this->_sent_packets[index].end()) { + if (ite->second->is_crypto_packet) { + ++this->_crypto_outstanding; + ink_assert(this->_crypto_outstanding.load() > 0); + } + if (ite->second->ack_eliciting) { + ++this->_ack_eliciting_outstanding; + ink_assert(this->_ack_eliciting_outstanding.load() > 0); + } + } +} + +void +QUICLossDetector::_remove_from_sent_packet_list(QUICPacketNumber packet_number, QUICPacketNumberSpace pn_space) +{ + SCOPED_MUTEX_LOCK(lock, this->_loss_detection_mutex, this_ethread()); + + auto ite = this->_sent_packets[static_cast(pn_space)].find(packet_number); + this->_decrement_outstanding_counters(ite, pn_space); + this->_sent_packets[static_cast(pn_space)].erase(packet_number); +} + +std::map::iterator +QUICLossDetector::_remove_from_sent_packet_list(std::map::iterator it, + QUICPacketNumberSpace pn_space) +{ + SCOPED_MUTEX_LOCK(lock, this->_loss_detection_mutex, this_ethread()); + + this->_decrement_outstanding_counters(it, pn_space); + return this->_sent_packets[static_cast(pn_space)].erase(it); +} + +void +QUICLossDetector::_decrement_outstanding_counters(std::map::iterator it, + QUICPacketNumberSpace pn_space) +{ + if (it != this->_sent_packets[static_cast(pn_space)].end()) { + // Decrement counters + if (it->second->is_crypto_packet) { + ink_assert(this->_crypto_outstanding.load() > 0); + --this->_crypto_outstanding; + } + if (it->second->ack_eliciting) { + ink_assert(this->_ack_eliciting_outstanding.load() > 0); + --this->_ack_eliciting_outstanding; + } + } +} + +// +// QUICRTTMeasure +// +QUICRTTMeasure::QUICRTTMeasure(const QUICLDConfig &ld_config) + : _k_granularity(ld_config.granularity()), _k_initial_rtt(ld_config.initial_rtt()) +{ +} + +void +QUICRTTMeasure::init(const QUICLDConfig &ld_config) +{ + this->_k_granularity = ld_config.granularity(); + this->_k_initial_rtt = ld_config.initial_rtt(); +} + +ink_hrtime +QUICRTTMeasure::smoothed_rtt() const +{ + return this->_smoothed_rtt; +} + +void +QUICRTTMeasure::update_rtt(ink_hrtime latest_rtt, ink_hrtime ack_delay) +{ + // additional code + this->_latest_rtt = latest_rtt; + + // min_rtt ignores ack delay. + this->_min_rtt = std::min(this->_min_rtt, latest_rtt); + // Limit ack_delay by max_ack_delay + ack_delay = std::min(ack_delay, this->_max_ack_delay); + // Adjust for ack delay if it's plausible. + if (this->_latest_rtt - this->_min_rtt > ack_delay) { + this->_latest_rtt -= ack_delay; + + // the newest spec has removed the max_ack_delay assignment. but we need to assign it in somewhere + // this code is from draft-19 + this->_max_ack_delay = std::max(ack_delay, this->_max_ack_delay); + } + // Based on {{RFC6298}}. + if (this->_smoothed_rtt == 0) { + this->_smoothed_rtt = latest_rtt; + this->_rttvar = latest_rtt / 2.0; + } else { + double rttvar_sample = ABS(this->_smoothed_rtt - latest_rtt); + this->_rttvar = 3.0 / 4.0 * this->_rttvar + 1.0 / 4.0 * rttvar_sample; + this->_smoothed_rtt = 7.0 / 8.0 * this->_smoothed_rtt + 1.0 / 8.0 * latest_rtt; + } +} + +ink_hrtime +QUICRTTMeasure::current_pto_period() const +{ + // PTO timeout + ink_hrtime alarm_duration; + alarm_duration = this->_smoothed_rtt + 4 * this->_rttvar + this->_max_ack_delay; + alarm_duration = std::max(alarm_duration, this->_k_granularity); + alarm_duration = alarm_duration * (1 << this->_pto_count); + return alarm_duration; +} + +ink_hrtime +QUICRTTMeasure::congestion_period(uint32_t threshold) const +{ + ink_hrtime pto = this->_smoothed_rtt + std::max(this->_rttvar * 4, this->_k_granularity); + return pto * (1 << (threshold - 1)); +} + +ink_hrtime +QUICRTTMeasure::handshake_retransmit_timeout() const +{ + // Handshake retransmission alarm. + ink_hrtime timeout = 0; + if (this->_smoothed_rtt == 0) { + timeout = 2 * this->_k_initial_rtt; + } else { + timeout = 2 * this->_smoothed_rtt; + } + timeout = std::max(timeout, this->_k_granularity); + timeout = timeout * (1 << this->_crypto_count); + + return timeout; +} + +void +QUICRTTMeasure::set_crypto_count(uint32_t count) +{ + this->_crypto_count = count; +} + +void +QUICRTTMeasure::set_pto_count(uint32_t count) +{ + this->_pto_count = count; +} + +ink_hrtime +QUICRTTMeasure::rttvar() const +{ + return this->_rttvar; +} + +ink_hrtime +QUICRTTMeasure::latest_rtt() const +{ + return this->_latest_rtt; +} + +uint32_t +QUICRTTMeasure::crypto_count() const +{ + return this->_crypto_count; +} + +uint32_t +QUICRTTMeasure::pto_count() const +{ + return this->_pto_count; +} + +void +QUICRTTMeasure::reset() +{ + this->_crypto_count = 0; + this->_pto_count = 0; + this->_smoothed_rtt = 0; + this->_rttvar = 0; + this->_min_rtt = INT64_MAX; +} diff --git a/iocore/net/quic/QUICLossDetector.h b/iocore/net/quic/QUICLossDetector.h new file mode 100644 index 00000000000..1b7a536a7c6 --- /dev/null +++ b/iocore/net/quic/QUICLossDetector.h @@ -0,0 +1,237 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +// TODO Using STL Map because ts/Map lacks remove method +#include +#include + +#include "I_EventSystem.h" +#include "I_Action.h" +#include "tscore/ink_hrtime.h" +#include "I_VConnection.h" +#include "QUICTypes.h" +#include "QUICPacket.h" +#include "QUICFrame.h" +#include "QUICFrameHandler.h" +#include "QUICConnection.h" + +class QUICLossDetector; +class QUICRTTMeasure; + +struct QUICPacketInfo { + // 6.3.1. Sent Packet Fields + QUICPacketNumber packet_number; + ink_hrtime time_sent; + bool ack_eliciting; + bool is_crypto_packet; + bool in_flight; + size_t sent_bytes; + + // addition + QUICPacketType type; + std::vector frames; + QUICPacketNumberSpace pn_space; + // end +}; + +using QUICPacketInfoUPtr = std::unique_ptr; + +class QUICRTTProvider +{ +public: + virtual ink_hrtime smoothed_rtt() const = 0; + virtual ink_hrtime rttvar() const = 0; + virtual ink_hrtime latest_rtt() const = 0; + + virtual ink_hrtime congestion_period(uint32_t threshold) const = 0; +}; + +class QUICCongestionController +{ +public: + QUICCongestionController(const QUICRTTProvider &rtt_provider, QUICConnectionInfoProvider *info, const QUICCCConfig &cc_config); + virtual ~QUICCongestionController() {} + void on_packet_sent(size_t bytes_sent); + void on_packet_acked(const QUICPacketInfo &acked_packet); + virtual void on_packets_lost(const std::map &packets); + void on_retransmission_timeout_verified(); + void process_ecn(const QUICPacketInfo &acked_largest_packet, const QUICAckFrame::EcnSection *ecn_section); + bool check_credit() const; + uint32_t open_window() const; + void reset(); + bool is_app_limited(); + + // Debug + uint32_t bytes_in_flight() const; + uint32_t congestion_window() const; + uint32_t current_ssthresh() const; + +private: + Ptr _cc_mutex; + + void _congestion_event(ink_hrtime sent_time); + bool _in_persistent_congestion(const std::map &lost_packets, + QUICPacketInfo *largest_lost_packet); + bool _in_window_lost(const std::map &lost_packets, QUICPacketInfo *largest_lost_packet, + ink_hrtime period) const; + + // [draft-17 recovery] 7.9.1. Constants of interest + // Values will be loaded from records.config via QUICConfig at constructor + uint32_t _k_max_datagram_size = 0; + uint32_t _k_initial_window = 0; + uint32_t _k_minimum_window = 0; + float _k_loss_reduction_factor = 0.0; + uint32_t _k_persistent_congestion_threshold = 0; + + // [draft-17 recovery] 7.9.2. Variables of interest + uint32_t _ecn_ce_counter = 0; + uint32_t _bytes_in_flight = 0; + uint32_t _congestion_window = 0; + ink_hrtime _recovery_start_time = 0; + uint32_t _ssthresh = UINT32_MAX; + + QUICConnectionInfoProvider *_info = nullptr; + const QUICRTTProvider &_rtt_provider; + + bool _in_recovery(ink_hrtime sent_time); +}; + +class QUICLossDetector : public Continuation, public QUICFrameHandler +{ +public: + QUICLossDetector(QUICConnectionInfoProvider *info, QUICCongestionController *cc, QUICRTTMeasure *rtt_measure, + const QUICLDConfig &ld_config); + ~QUICLossDetector(); + + int event_handler(int event, Event *edata); + + std::vector interests() override; + virtual QUICConnectionErrorUPtr handle_frame(QUICEncryptionLevel level, const QUICFrame &frame) override; + void on_packet_sent(QUICPacketInfoUPtr packet_info, bool in_flight = true); + QUICPacketNumber largest_acked_packet_number(QUICPacketNumberSpace pn_space); + void update_ack_delay_exponent(uint8_t ack_delay_exponent); + void reset(); + +private: + Ptr _loss_detection_mutex; + + uint8_t _ack_delay_exponent = 3; + + // [draft-17 recovery] 6.4.1. Constants of interest + // Values will be loaded from records.config via QUICConfig at constructor + uint32_t _k_packet_threshold = 0; + float _k_time_threshold = 0.0; + + // [draft-11 recovery] 3.5.2. Variables of interest + // Keep the order as the same as the spec so that we can see the difference easily. + Action *_loss_detection_timer = nullptr; + ink_hrtime _time_of_last_sent_ack_eliciting_packet = 0; + ink_hrtime _time_of_last_sent_crypto_packet = 0; + ink_hrtime _loss_time[kPacketNumberSpace] = {0}; + QUICPacketNumber _largest_acked_packet[kPacketNumberSpace] = {0}; + std::map _sent_packets[kPacketNumberSpace]; + + // These are not defined on the spec but expected to be count + // These counter have to be updated when inserting / erasing packets from _sent_packets with following functions. + std::atomic _crypto_outstanding; + std::atomic _ack_eliciting_outstanding; + void _add_to_sent_packet_list(QUICPacketNumber packet_number, std::unique_ptr packet_info); + void _remove_from_sent_packet_list(QUICPacketNumber packet_number, QUICPacketNumberSpace pn_space); + std::map::iterator _remove_from_sent_packet_list( + std::map::iterator it, QUICPacketNumberSpace pn_space); + void _decrement_outstanding_counters(std::map::iterator it, QUICPacketNumberSpace pn_space); + + /* + * Because this alarm will be reset on every packet transmission, to reduce number of events, + * Loss Detector uses schedule_every() and checks if it has to be triggered. + */ + ink_hrtime _loss_detection_alarm_at = 0; + + void _on_ack_received(const QUICAckFrame &ack_frame, QUICPacketNumberSpace pn_space); + void _on_packet_acked(const QUICPacketInfo &acked_packet); + void _detect_lost_packets(QUICPacketNumberSpace pn_space); + void _set_loss_detection_timer(); + void _on_loss_detection_timeout(); + void _retransmit_lost_packet(QUICPacketInfo &packet_info); + + ink_hrtime _get_earliest_loss_time(QUICPacketNumberSpace &pn_space); + + std::set _determine_newly_acked_packets(const QUICAckFrame &ack_frame); + + void _retransmit_all_unacked_crypto_data(); + void _send_one_packet(); + void _send_two_packets(); + + QUICConnectionInfoProvider *_info = nullptr; + QUICRTTMeasure *_rtt_measure = nullptr; + QUICCongestionController *_cc = nullptr; +}; + +class QUICRTTMeasure : public QUICRTTProvider +{ +public: + // use `friend` so ld can acesss RTTMeasure. + // friend QUICLossDetector; + + QUICRTTMeasure(const QUICLDConfig &ld_config); + QUICRTTMeasure() = default; + + void init(const QUICLDConfig &ld_config); + + // period + ink_hrtime handshake_retransmit_timeout() const; + ink_hrtime current_pto_period() const; + ink_hrtime congestion_period(uint32_t threshold) const override; + + // get members + ink_hrtime smoothed_rtt() const override; + ink_hrtime rttvar() const override; + ink_hrtime latest_rtt() const override; + + uint32_t pto_count() const; + uint32_t crypto_count() const; + + void set_crypto_count(uint32_t count); + void set_pto_count(uint32_t count); + + void update_rtt(ink_hrtime latest_rtt, ink_hrtime ack_delay); + void reset(); + +private: + // related to rtt calculate + uint32_t _crypto_count = 0; + uint32_t _pto_count = 0; + ink_hrtime _max_ack_delay = 0; + + // rtt vars + ink_hrtime _latest_rtt = 0; + ink_hrtime _smoothed_rtt = 0; + ink_hrtime _rttvar = 0; + ink_hrtime _min_rtt = INT64_MAX; + + // config + ink_hrtime _k_granularity = 0; + ink_hrtime _k_initial_rtt = 0; +}; diff --git a/iocore/net/quic/QUICPacket.cc b/iocore/net/quic/QUICPacket.cc new file mode 100644 index 00000000000..ec8ac37c6ba --- /dev/null +++ b/iocore/net/quic/QUICPacket.cc @@ -0,0 +1,985 @@ +/** @file + * + * A brief file description + * + * @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 "QUICPacket.h" + +#include +#include + +#include "QUICIntUtil.h" +#include "QUICDebugNames.h" + +using namespace std::literals; +static constexpr std::string_view tag = "quic_packet"sv; +static constexpr uint64_t aead_tag_len = 16; + +#define QUICDebug(dcid, scid, fmt, ...) \ + Debug(tag.data(), "[%08" PRIx32 "-%08" PRIx32 "] " fmt, dcid.h32(), scid.h32(), ##__VA_ARGS__); + +ClassAllocator quicPacketAllocator("quicPacketAllocator"); +ClassAllocator quicPacketLongHeaderAllocator("quicPacketLongHeaderAllocator"); +ClassAllocator quicPacketShortHeaderAllocator("quicPacketShortHeaderAllocator"); + +static constexpr int LONG_HDR_OFFSET_CONNECTION_ID = 6; +static constexpr int LONG_HDR_OFFSET_VERSION = 1; + +// +// QUICPacketHeader +// +const uint8_t * +QUICPacketHeader::buf() +{ + if (this->_buf) { + return this->_buf.get(); + } else { + // TODO Reuse serialzied data if nothing has changed + this->store(this->_serialized, &this->_buf_len); + if (this->_buf_len > MAX_PACKET_HEADER_LEN) { + ink_assert(!"Serialized packet header is too long"); + } + + this->_buf_len += this->_payload_length; + return this->_serialized; + } +} + +const IpEndpoint & +QUICPacketHeader::from() const +{ + return this->_from; +} + +bool +QUICPacketHeader::is_crypto_packet() const +{ + return false; +} + +uint16_t +QUICPacketHeader::packet_size() const +{ + return this->_buf_len; +} + +QUICPacketHeaderUPtr +QUICPacketHeader::load(const IpEndpoint from, ats_unique_buf buf, size_t len, QUICPacketNumber base) +{ + QUICPacketHeaderUPtr header = QUICPacketHeaderUPtr(nullptr, &QUICPacketHeaderDeleter::delete_null_header); + if (QUICInvariants::is_long_header(buf.get())) { + QUICPacketLongHeader *long_header = quicPacketLongHeaderAllocator.alloc(); + new (long_header) QUICPacketLongHeader(from, std::move(buf), len, base); + header = QUICPacketHeaderUPtr(long_header, &QUICPacketHeaderDeleter::delete_long_header); + } else { + QUICPacketShortHeader *short_header = quicPacketShortHeaderAllocator.alloc(); + new (short_header) QUICPacketShortHeader(from, std::move(buf), len, base); + header = QUICPacketHeaderUPtr(short_header, &QUICPacketHeaderDeleter::delete_short_header); + } + return header; +} + +QUICPacketHeaderUPtr +QUICPacketHeader::build(QUICPacketType type, QUICKeyPhase key_phase, QUICConnectionId destination_cid, QUICConnectionId source_cid, + QUICPacketNumber packet_number, QUICPacketNumber base_packet_number, QUICVersion version, bool crypto, + ats_unique_buf payload, size_t len) +{ + QUICPacketLongHeader *long_header = quicPacketLongHeaderAllocator.alloc(); + new (long_header) QUICPacketLongHeader(type, key_phase, destination_cid, source_cid, packet_number, base_packet_number, version, + crypto, std::move(payload), len); + return QUICPacketHeaderUPtr(long_header, &QUICPacketHeaderDeleter::delete_long_header); +} + +QUICPacketHeaderUPtr +QUICPacketHeader::build(QUICPacketType type, QUICKeyPhase key_phase, QUICConnectionId destination_cid, QUICConnectionId source_cid, + QUICPacketNumber packet_number, QUICPacketNumber base_packet_number, QUICVersion version, bool crypto, + ats_unique_buf payload, size_t len, ats_unique_buf token, size_t token_len) +{ + QUICPacketLongHeader *long_header = quicPacketLongHeaderAllocator.alloc(); + new (long_header) QUICPacketLongHeader(type, key_phase, destination_cid, source_cid, packet_number, base_packet_number, version, + crypto, std::move(payload), len, std::move(token), token_len); + return QUICPacketHeaderUPtr(long_header, &QUICPacketHeaderDeleter::delete_long_header); +} + +QUICPacketHeaderUPtr +QUICPacketHeader::build(QUICPacketType type, QUICKeyPhase key_phase, QUICVersion version, QUICConnectionId destination_cid, + QUICConnectionId source_cid, QUICConnectionId original_dcid, ats_unique_buf retry_token, + size_t retry_token_len) +{ + QUICPacketLongHeader *long_header = quicPacketLongHeaderAllocator.alloc(); + new (long_header) QUICPacketLongHeader(type, key_phase, version, destination_cid, source_cid, original_dcid, + std::move(retry_token), retry_token_len); + return QUICPacketHeaderUPtr(long_header, &QUICPacketHeaderDeleter::delete_long_header); +} + +QUICPacketHeaderUPtr +QUICPacketHeader::build(QUICPacketType type, QUICKeyPhase key_phase, QUICPacketNumber packet_number, + QUICPacketNumber base_packet_number, ats_unique_buf payload, size_t len) +{ + QUICPacketShortHeader *short_header = quicPacketShortHeaderAllocator.alloc(); + new (short_header) QUICPacketShortHeader(type, key_phase, packet_number, base_packet_number, std::move(payload), len); + return QUICPacketHeaderUPtr(short_header, &QUICPacketHeaderDeleter::delete_short_header); +} + +QUICPacketHeaderUPtr +QUICPacketHeader::build(QUICPacketType type, QUICKeyPhase key_phase, QUICConnectionId connection_id, QUICPacketNumber packet_number, + QUICPacketNumber base_packet_number, ats_unique_buf payload, size_t len) +{ + QUICPacketShortHeader *short_header = quicPacketShortHeaderAllocator.alloc(); + new (short_header) + QUICPacketShortHeader(type, key_phase, connection_id, packet_number, base_packet_number, std::move(payload), len); + return QUICPacketHeaderUPtr(short_header, &QUICPacketHeaderDeleter::delete_short_header); +} + +QUICPacketHeaderUPtr +QUICPacketHeader::clone() const +{ + return QUICPacketHeaderUPtr(nullptr, &QUICPacketHeaderDeleter::delete_null_header); +} + +// +// QUICPacketLongHeader +// + +QUICPacketLongHeader::QUICPacketLongHeader(const IpEndpoint from, ats_unique_buf buf, size_t len, QUICPacketNumber base) + : QUICPacketHeader(from, std::move(buf), len, base) +{ + this->_key_phase = QUICTypeUtil::key_phase(this->type()); + uint8_t *raw_buf = this->_buf.get(); + + uint8_t dcil = 0; + uint8_t scil = 0; + QUICPacketLongHeader::dcil(dcil, raw_buf, len); + QUICPacketLongHeader::scil(scil, raw_buf, len); + + size_t offset = LONG_HDR_OFFSET_CONNECTION_ID; + this->_destination_cid = {raw_buf + offset, dcil}; + offset += dcil; + this->_source_cid = {raw_buf + offset, scil}; + offset += scil; + + if (this->type() != QUICPacketType::VERSION_NEGOTIATION) { + if (this->type() == QUICPacketType::RETRY) { + uint8_t odcil = (raw_buf[0] & 0x0f) + 3; + this->_original_dcid = {raw_buf + offset, odcil}; + offset += odcil; + } else { + if (this->type() == QUICPacketType::INITIAL) { + // Token Length Field + this->_token_len = QUICIntUtil::read_QUICVariableInt(raw_buf + offset); + offset += QUICVariableInt::size(raw_buf + offset); + // Token Field + this->_token_offset = offset; + offset += this->_token_len; + } + + // Length Field + offset += QUICVariableInt::size(raw_buf + offset); + + // PN Field + int pn_len = QUICTypeUtil::read_QUICPacketNumberLen(raw_buf); + QUICPacket::decode_packet_number(this->_packet_number, QUICTypeUtil::read_QUICPacketNumber(raw_buf + offset, pn_len), pn_len, + this->_base_packet_number); + offset += pn_len; + } + } + + this->_payload_offset = offset; + this->_payload_length = len - this->_payload_offset; +} + +QUICPacketLongHeader::QUICPacketLongHeader(QUICPacketType type, QUICKeyPhase key_phase, const QUICConnectionId &destination_cid, + const QUICConnectionId &source_cid, QUICPacketNumber packet_number, + QUICPacketNumber base_packet_number, QUICVersion version, bool crypto, + ats_unique_buf buf, size_t len, ats_unique_buf token, size_t token_len) + : QUICPacketHeader(type, packet_number, base_packet_number, true, version, std::move(buf), len, key_phase), + _destination_cid(destination_cid), + _source_cid(source_cid), + _token_len(token_len), + _token(std::move(token)), + _is_crypto_packet(crypto) +{ + if (this->_type == QUICPacketType::VERSION_NEGOTIATION) { + this->_buf_len = + LONG_HDR_OFFSET_CONNECTION_ID + this->_destination_cid.length() + this->_source_cid.length() + this->_payload_length; + } else { + this->buf(); + } +} + +QUICPacketLongHeader::QUICPacketLongHeader(QUICPacketType type, QUICKeyPhase key_phase, QUICVersion version, + const QUICConnectionId &destination_cid, const QUICConnectionId &source_cid, + const QUICConnectionId &original_dcid, ats_unique_buf retry_token, + size_t retry_token_len) + : QUICPacketHeader(type, 0, 0, true, version, std::move(retry_token), retry_token_len, key_phase), + _destination_cid(destination_cid), + _source_cid(source_cid), + _original_dcid(original_dcid) + +{ + // this->_buf_len will be set + this->buf(); +} + +QUICPacketType +QUICPacketLongHeader::type() const +{ + if (this->_buf) { + QUICPacketType type = QUICPacketType::UNINITIALIZED; + QUICPacketLongHeader::type(type, this->_buf.get(), this->_buf_len); + return type; + } else { + return this->_type; + } +} + +bool +QUICPacketLongHeader::is_crypto_packet() const +{ + return this->_is_crypto_packet; +} + +bool +QUICPacketLongHeader::type(QUICPacketType &type, const uint8_t *packet, size_t packet_len) +{ + if (packet_len < 1) { + return false; + } + + QUICVersion version; + if (QUICPacketLongHeader::version(version, packet, packet_len) && version == 0x00) { + type = QUICPacketType::VERSION_NEGOTIATION; + } else { + uint8_t raw_type = (packet[0] & 0x30) >> 4; + type = static_cast(raw_type); + } + return true; +} + +bool +QUICPacketLongHeader::version(QUICVersion &version, const uint8_t *packet, size_t packet_len) +{ + if (packet_len < 5) { + return false; + } + + version = QUICTypeUtil::read_QUICVersion(packet + LONG_HDR_OFFSET_VERSION); + return true; +} + +bool +QUICPacketLongHeader::dcil(uint8_t &dcil, const uint8_t *packet, size_t packet_len) +{ + if (QUICInvariants::dcil(dcil, packet, packet_len)) { + if (dcil != 0) { + dcil += 3; + } + return true; + } else { + return false; + } +} + +bool +QUICPacketLongHeader::scil(uint8_t &scil, const uint8_t *packet, size_t packet_len) +{ + if (QUICInvariants::scil(scil, packet, packet_len)) { + if (scil != 0) { + scil += 3; + } + return true; + } else { + return false; + } +} + +bool +QUICPacketLongHeader::token_length(size_t &token_length, uint8_t *field_len, const uint8_t *packet, size_t packet_len) +{ + QUICPacketType type = QUICPacketType::UNINITIALIZED; + QUICPacketLongHeader::type(type, packet, packet_len); + + if (type != QUICPacketType::INITIAL) { + token_length = 0; + if (field_len) { + *field_len = 0; + } + return true; + } + + uint8_t dcil, scil; + QUICPacketLongHeader::dcil(dcil, packet, packet_len); + QUICPacketLongHeader::scil(scil, packet, packet_len); + + size_t offset = LONG_HDR_OFFSET_CONNECTION_ID + dcil + scil; + if (offset >= packet_len) { + return false; + } + + if (offset > packet_len) { + return false; + } + + token_length = QUICIntUtil::read_QUICVariableInt(packet + offset); + if (field_len) { + *field_len = QUICVariableInt::size(packet + offset); + } + + return true; +} + +bool +QUICPacketLongHeader::length(size_t &length, uint8_t *field_len, const uint8_t *packet, size_t packet_len) +{ + uint8_t dcil, scil; + QUICPacketLongHeader::dcil(dcil, packet, packet_len); + QUICPacketLongHeader::scil(scil, packet, packet_len); + + // Token Length (i) + Token (*) (for INITIAL packet) + size_t token_length = 0; + uint8_t token_length_field_len = 0; + if (!QUICPacketLongHeader::token_length(token_length, &token_length_field_len, packet, packet_len)) { + return false; + } + + // Length (i) + size_t length_offset = LONG_HDR_OFFSET_CONNECTION_ID + dcil + scil + token_length_field_len + token_length; + if (length_offset >= packet_len) { + return false; + } + length = QUICIntUtil::read_QUICVariableInt(packet + length_offset); + if (field_len) { + *field_len = QUICVariableInt::size(packet + length_offset); + } + return true; +} + +bool +QUICPacketLongHeader::packet_number_offset(uint8_t &pn_offset, const uint8_t *packet, size_t packet_len) +{ + QUICPacketType type; + QUICPacketLongHeader::type(type, packet, packet_len); + + uint8_t dcil, scil; + size_t token_length; + uint8_t token_length_field_len; + size_t length; + uint8_t length_field_len; + if (!QUICPacketLongHeader::dcil(dcil, packet, packet_len) || !QUICPacketLongHeader::scil(scil, packet, packet_len) || + !QUICPacketLongHeader::token_length(token_length, &token_length_field_len, packet, packet_len) || + !QUICPacketLongHeader::length(length, &length_field_len, packet, packet_len)) { + return false; + } + pn_offset = 6 + dcil + scil + token_length_field_len + token_length + length_field_len; + + return true; +} + +bool +QUICPacketLongHeader::key_phase(QUICKeyPhase &phase, const uint8_t *packet, size_t packet_len) +{ + QUICPacketType type = QUICPacketType::UNINITIALIZED; + QUICPacketLongHeader::type(type, packet, packet_len); + phase = QUICTypeUtil::key_phase(type); + return true; +} + +QUICConnectionId +QUICPacketLongHeader::destination_cid() const +{ + return this->_destination_cid; +} + +QUICConnectionId +QUICPacketLongHeader::source_cid() const +{ + return this->_source_cid; +} + +QUICConnectionId +QUICPacketLongHeader::original_dcid() const +{ + return this->_original_dcid; +} + +QUICPacketNumber +QUICPacketLongHeader::packet_number() const +{ + return this->_packet_number; +} + +bool +QUICPacketLongHeader::has_version() const +{ + return true; +} + +bool +QUICPacketLongHeader::is_valid() const +{ + if (this->_buf && this->_buf_len != this->_payload_offset + this->_payload_length) { + QUICDebug(this->_source_cid, this->_destination_cid, + "Invalid packet: packet_size(%zu) should be header_size(%zu) + payload_size(%zu)", this->_buf_len, + this->_payload_offset, this->_payload_length); + Warning("Invalid packet: packet_size(%zu) should be header_size(%zu) + payload_size(%zu)", this->_buf_len, + this->_payload_offset, this->_payload_length); + + return false; + } + + return true; +} + +QUICVersion +QUICPacketLongHeader::version() const +{ + if (this->_buf) { + QUICVersion version = 0; + QUICPacketLongHeader::version(version, this->_buf.get(), this->_buf_len); + return version; + } else { + return this->_version; + } +} + +const uint8_t * +QUICPacketLongHeader::payload() const +{ + if (this->_buf) { + uint8_t *raw = this->_buf.get(); + return raw + this->_payload_offset; + } else { + return this->_payload.get(); + } +} + +uint16_t +QUICPacketHeader::payload_size() const +{ + return this->_payload_length; +} + +const uint8_t * +QUICPacketLongHeader::token() const +{ + if (this->_buf) { + uint8_t *raw = this->_buf.get(); + return raw + this->_token_offset; + } else { + return this->_token.get(); + } +} + +size_t +QUICPacketLongHeader::token_len() const +{ + return this->_token_len; +} + +QUICKeyPhase +QUICPacketLongHeader::key_phase() const +{ + return this->_key_phase; +} + +uint16_t +QUICPacketLongHeader::size() const +{ + return this->_buf_len - this->_payload_length; +} + +void +QUICPacketLongHeader::store(uint8_t *buf, size_t *len) const +{ + size_t n; + *len = 0; + buf[0] = 0xC0; + buf[0] += static_cast(this->_type) << 4; + if (this->_type == QUICPacketType::VERSION_NEGOTIATION) { + buf[0] |= rand(); + } + *len += 1; + + QUICTypeUtil::write_QUICVersion(this->_version, buf + *len, &n); + *len += n; + + buf[*len] = this->_destination_cid == QUICConnectionId::ZERO() ? 0 : (this->_destination_cid.length() - 3) << 4; + buf[*len] += this->_source_cid == QUICConnectionId::ZERO() ? 0 : this->_source_cid.length() - 3; + *len += 1; + + if (this->_destination_cid != QUICConnectionId::ZERO()) { + QUICTypeUtil::write_QUICConnectionId(this->_destination_cid, buf + *len, &n); + *len += n; + } + if (this->_source_cid != QUICConnectionId::ZERO()) { + QUICTypeUtil::write_QUICConnectionId(this->_source_cid, buf + *len, &n); + *len += n; + } + + if (this->_type != QUICPacketType::VERSION_NEGOTIATION) { + if (this->_type == QUICPacketType::RETRY) { + // Original Destination Connection ID + if (this->_original_dcid != QUICConnectionId::ZERO()) { + QUICTypeUtil::write_QUICConnectionId(this->_original_dcid, buf + *len, &n); + *len += n; + } + // ODCIL + buf[0] |= this->_original_dcid.length() - 3; + } else { + if (this->_type == QUICPacketType::INITIAL) { + // Token Length Field + QUICIntUtil::write_QUICVariableInt(this->_token_len, buf + *len, &n); + *len += n; + + // Token Field + memcpy(buf + *len, this->token(), this->token_len()); + *len += this->token_len(); + } + + QUICPacketNumber pn = 0; + size_t pn_len = 4; + QUICPacket::encode_packet_number(pn, this->_packet_number, pn_len); + + if (pn > 0x7FFFFF) { + pn_len = 4; + } else if (pn > 0x7FFF) { + pn_len = 3; + } else if (pn > 0x7F) { + pn_len = 2; + } else { + pn_len = 1; + } + + if (this->_type != QUICPacketType::RETRY) { + // PN Len field + QUICTypeUtil::write_QUICPacketNumberLen(pn_len, buf); + } + + // Length Field + QUICIntUtil::write_QUICVariableInt(pn_len + this->_payload_length + aead_tag_len, buf + *len, &n); + *len += n; + + // PN Field + QUICTypeUtil::write_QUICPacketNumber(pn, pn_len, buf + *len, &n); + *len += n; + } + + // Payload will be stored + } +} + +// +// QUICPacketShortHeader +// + +QUICPacketShortHeader::QUICPacketShortHeader(const IpEndpoint from, ats_unique_buf buf, size_t len, QUICPacketNumber base) + : QUICPacketHeader(from, std::move(buf), len, base) +{ + QUICInvariants::dcid(this->_connection_id, this->_buf.get(), len); + + int offset = 1 + this->_connection_id.length(); + this->_packet_number_len = QUICTypeUtil::read_QUICPacketNumberLen(this->_buf.get()); + QUICPacketNumber src = QUICTypeUtil::read_QUICPacketNumber(this->_buf.get() + offset, this->_packet_number_len); + QUICPacket::decode_packet_number(this->_packet_number, src, this->_packet_number_len, this->_base_packet_number); + this->_payload_length = len - (1 + QUICConnectionId::SCID_LEN + this->_packet_number_len); +} + +QUICPacketShortHeader::QUICPacketShortHeader(QUICPacketType type, QUICKeyPhase key_phase, QUICPacketNumber packet_number, + QUICPacketNumber base_packet_number, ats_unique_buf buf, size_t len) +{ + this->_type = type; + this->_key_phase = key_phase; + this->_packet_number = packet_number; + this->_base_packet_number = base_packet_number; + this->_packet_number_len = QUICPacket::calc_packet_number_len(packet_number, base_packet_number); + this->_payload = std::move(buf); + this->_payload_length = len; +} + +QUICPacketShortHeader::QUICPacketShortHeader(QUICPacketType type, QUICKeyPhase key_phase, const QUICConnectionId &connection_id, + QUICPacketNumber packet_number, QUICPacketNumber base_packet_number, + ats_unique_buf buf, size_t len) +{ + this->_type = type; + this->_key_phase = key_phase; + this->_connection_id = connection_id; + this->_packet_number = packet_number; + this->_base_packet_number = base_packet_number; + this->_packet_number_len = QUICPacket::calc_packet_number_len(packet_number, base_packet_number); + this->_payload = std::move(buf); + this->_payload_length = len; +} + +QUICPacketType +QUICPacketShortHeader::type() const +{ + QUICKeyPhase key_phase = this->key_phase(); + + switch (key_phase) { + case QUICKeyPhase::PHASE_0: { + return QUICPacketType::PROTECTED; + } + case QUICKeyPhase::PHASE_1: { + return QUICPacketType::PROTECTED; + } + default: + return QUICPacketType::STATELESS_RESET; + } +} + +QUICConnectionId +QUICPacketShortHeader::destination_cid() const +{ + if (this->_buf) { + QUICConnectionId dcid = QUICConnectionId::ZERO(); + QUICInvariants::dcid(dcid, this->_buf.get(), this->_buf_len); + return dcid; + } else { + return _connection_id; + } +} + +QUICPacketNumber +QUICPacketShortHeader::packet_number() const +{ + return this->_packet_number; +} + +bool +QUICPacketShortHeader::has_version() const +{ + return false; +} + +bool +QUICPacketShortHeader::is_valid() const +{ + return true; +} + +QUICVersion +QUICPacketShortHeader::version() const +{ + return 0; +} + +const uint8_t * +QUICPacketShortHeader::payload() const +{ + if (this->_buf) { + return this->_buf.get() + this->size(); + } else { + return this->_payload.get(); + } +} + +QUICKeyPhase +QUICPacketShortHeader::key_phase() const +{ + if (this->_buf) { + QUICKeyPhase phase = QUICKeyPhase::INITIAL; + QUICPacketShortHeader::key_phase(phase, this->_buf.get(), this->_buf_len); + return phase; + } else { + return this->_key_phase; + } +} + +bool +QUICPacketShortHeader::key_phase(QUICKeyPhase &phase, const uint8_t *packet, size_t packet_len) +{ + if (packet_len < 1) { + return false; + } + if (packet[0] & 0x04) { + phase = QUICKeyPhase::PHASE_1; + } else { + phase = QUICKeyPhase::PHASE_0; + } + return true; +} + +bool +QUICPacketShortHeader::packet_number_offset(uint8_t &pn_offset, const uint8_t *packet, size_t packet_len, int dcil) +{ + pn_offset = 1 + dcil; + return true; +} + +/** + * Header Length (doesn't include payload length) + */ +uint16_t +QUICPacketShortHeader::size() const +{ + uint16_t len = 1; + if (this->_connection_id != QUICConnectionId::ZERO()) { + len += this->_connection_id.length(); + } + len += this->_packet_number_len; + + return len; +} + +void +QUICPacketShortHeader::store(uint8_t *buf, size_t *len) const +{ + size_t n; + *len = 0; + buf[0] = 0x40; + if (this->_key_phase == QUICKeyPhase::PHASE_1) { + buf[0] |= 0x04; + } + *len += 1; + + if (this->_connection_id != QUICConnectionId::ZERO()) { + QUICTypeUtil::write_QUICConnectionId(this->_connection_id, buf + *len, &n); + *len += n; + } + + QUICPacketNumber dst = 0; + size_t dst_len = this->_packet_number_len; + QUICPacket::encode_packet_number(dst, this->_packet_number, dst_len); + QUICTypeUtil::write_QUICPacketNumber(dst, dst_len, buf + *len, &n); + *len += n; + + QUICTypeUtil::write_QUICPacketNumberLen(n, buf); +} + +// +// QUICPacket +// + +QUICPacket::QUICPacket() {} + +QUICPacket::QUICPacket(UDPConnection *udp_con, QUICPacketHeaderUPtr header, ats_unique_buf payload, size_t payload_len) + : _udp_con(udp_con), _header(std::move(header)), _payload(std::move(payload)), _payload_size(payload_len) +{ +} + +QUICPacket::QUICPacket(QUICPacketHeaderUPtr header, ats_unique_buf payload, size_t payload_len, bool ack_eliciting, bool probing) + : _header(std::move(header)), + _payload(std::move(payload)), + _payload_size(payload_len), + _is_ack_eliciting(ack_eliciting), + _is_probing_packet(probing) +{ +} + +QUICPacket::~QUICPacket() +{ + this->_header = nullptr; +} + +const IpEndpoint & +QUICPacket::from() const +{ + return this->_header->from(); +} + +UDPConnection * +QUICPacket::udp_con() const +{ + return this->_udp_con; +} + +/** + * When packet is "Short Header Packet", QUICPacket::type() will return 1-RTT Protected (key phase 0) + * or 1-RTT Protected (key phase 1) + */ +QUICPacketType +QUICPacket::type() const +{ + return this->_header->type(); +} + +QUICConnectionId +QUICPacket::destination_cid() const +{ + return this->_header->destination_cid(); +} + +QUICConnectionId +QUICPacket::source_cid() const +{ + return this->_header->source_cid(); +} + +QUICPacketNumber +QUICPacket::packet_number() const +{ + return this->_header->packet_number(); +} + +bool +QUICPacket::is_crypto_packet() const +{ + return this->_header->is_crypto_packet(); +} + +const QUICPacketHeader & +QUICPacket::header() const +{ + return *this->_header; +} + +const uint8_t * +QUICPacket::payload() const +{ + return this->_payload.get(); +} + +QUICVersion +QUICPacket::version() const +{ + return this->_header->version(); +} + +bool +QUICPacket::is_ack_eliciting() const +{ + return this->_is_ack_eliciting; +} + +bool +QUICPacket::is_probing_packet() const +{ + return this->_is_probing_packet; +} + +uint16_t +QUICPacket::size() const +{ + // This includes not only header size and payload size but also AEAD tag length + uint16_t size = this->_header->packet_size(); + if (size == 0) { + size = this->header_size() + this->payload_length(); + } + return size; +} + +uint16_t +QUICPacket::header_size() const +{ + return this->_header->size(); +} + +uint16_t +QUICPacket::payload_length() const +{ + return this->_payload_size; +} + +QUICKeyPhase +QUICPacket::key_phase() const +{ + return this->_header->key_phase(); +} + +void +QUICPacket::store(uint8_t *buf, size_t *len) const +{ + memcpy(buf, this->_header->buf(), this->_header->size()); + memcpy(buf + this->_header->size(), this->payload(), this->payload_length()); + *len = this->_header->size() + this->payload_length(); +} + +uint8_t +QUICPacket::calc_packet_number_len(QUICPacketNumber num, QUICPacketNumber base) +{ + uint64_t d = (num - base) * 2; + uint8_t len = 0; + + if (d > 0xFFFFFF) { + len = 4; + } else if (d > 0xFFFF) { + len = 3; + } else if (d > 0xFF) { + len = 2; + } else { + len = 1; + } + + return len; +} + +bool +QUICPacket::encode_packet_number(QUICPacketNumber &dst, QUICPacketNumber src, size_t len) +{ + uint64_t mask = 0; + switch (len) { + case 1: + mask = 0xFF; + break; + case 2: + mask = 0xFFFF; + break; + case 3: + mask = 0xFFFFFF; + break; + case 4: + mask = 0xFFFFFFFF; + break; + default: + ink_assert(!"len must be 1, 2, or 4"); + return false; + } + dst = src & mask; + + return true; +} + +bool +QUICPacket::decode_packet_number(QUICPacketNumber &dst, QUICPacketNumber src, size_t len, QUICPacketNumber largest_acked) +{ + ink_assert(len == 1 || len == 2 || len == 3 || len == 4); + + uint64_t maximum_diff = 0; + switch (len) { + case 1: + maximum_diff = 0x100; + break; + case 2: + maximum_diff = 0x10000; + break; + case 3: + maximum_diff = 0x1000000; + break; + case 4: + maximum_diff = 0x100000000; + break; + default: + ink_assert(!"len must be 1, 2, 3 or 4"); + } + QUICPacketNumber base = largest_acked & (~(maximum_diff - 1)); + QUICPacketNumber candidate1 = base + src; + QUICPacketNumber candidate2 = base + src + maximum_diff; + QUICPacketNumber expected = largest_acked + 1; + + if (((candidate1 > expected) ? (candidate1 - expected) : (expected - candidate1)) < + ((candidate2 > expected) ? (candidate2 - expected) : (expected - candidate2))) { + dst = candidate1; + } else { + dst = candidate2; + } + + return true; +} diff --git a/iocore/net/quic/QUICPacket.h b/iocore/net/quic/QUICPacket.h new file mode 100644 index 00000000000..dd9c9bcdb90 --- /dev/null +++ b/iocore/net/quic/QUICPacket.h @@ -0,0 +1,419 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include +#include +#include + +#include "tscore/List.h" +#include "I_IOBuffer.h" + +#include "QUICTypes.h" +#include "QUICHandshakeProtocol.h" +#include "QUICPacketHeaderProtector.h" +#include "QUICFrame.h" + +#define QUIC_FIELD_OFFSET_CONNECTION_ID 1 +#define QUIC_FIELD_OFFSET_PACKET_NUMBER 4 +#define QUIC_FIELD_OFFSET_PAYLOAD 5 + +class UDPConnection; +class QUICPacketHeader; +class QUICPacket; +class QUICPacketLongHeader; +class QUICPacketShortHeader; + +extern ClassAllocator quicPacketAllocator; +extern ClassAllocator quicPacketLongHeaderAllocator; +extern ClassAllocator quicPacketShortHeaderAllocator; + +using QUICPacketHeaderDeleterFunc = void (*)(QUICPacketHeader *p); +using QUICPacketHeaderUPtr = std::unique_ptr; + +class QUICPacketHeader +{ +public: + QUICPacketHeader(const IpEndpoint from, ats_unique_buf buf, size_t len, QUICPacketNumber base) + : _from(from), _buf(std::move(buf)), _buf_len(len), _base_packet_number(base) + { + } + ~QUICPacketHeader() {} + const uint8_t *buf(); + + virtual bool is_crypto_packet() const; + + const IpEndpoint &from() const; + + virtual QUICPacketType type() const = 0; + + /* + * Returns a connection id + */ + virtual QUICConnectionId destination_cid() const = 0; + virtual QUICConnectionId source_cid() const = 0; + + virtual QUICPacketNumber packet_number() const = 0; + virtual QUICVersion version() const = 0; + + /* + * Returns a pointer for the payload + */ + virtual const uint8_t *payload() const = 0; + + /* + * Returns its payload size based on header length and buffer size that is specified to the constructo. + */ + uint16_t payload_size() const; + + /* + * Returns its header size + */ + virtual uint16_t size() const = 0; + + /* + * Returns its packet size + */ + uint16_t packet_size() const; + + /* + * Returns a key phase + */ + virtual QUICKeyPhase key_phase() const = 0; + + /* + * Stores serialized header + * + * The serialized data doesn't contain a payload part even if it was created with a buffer that contains payload data. + */ + virtual void store(uint8_t *buf, size_t *len) const = 0; + + QUICPacketHeaderUPtr clone() const; + + virtual bool has_version() const = 0; + virtual bool is_valid() const = 0; + + /***** STATIC members *****/ + + /* + * Load data from a buffer and create a QUICPacketHeader + * + * This creates either a QUICPacketShortHeader or a QUICPacketLongHeader. + */ + static QUICPacketHeaderUPtr load(const IpEndpoint from, ats_unique_buf buf, size_t len, QUICPacketNumber base); + + /* + * Build a QUICPacketHeader + * + * This creates a QUICPacketLongHeader. + */ + static QUICPacketHeaderUPtr build(QUICPacketType type, QUICKeyPhase key_phase, QUICConnectionId destination_cid, + QUICConnectionId source_cid, QUICPacketNumber packet_number, + QUICPacketNumber base_packet_number, QUICVersion version, bool crypto, ats_unique_buf payload, + size_t len); + + /* + * Build a QUICPacketHeader + * + * This creates a QUICPacketLongHeader for INITIAL packet + */ + static QUICPacketHeaderUPtr build(QUICPacketType type, QUICKeyPhase key_phase, QUICConnectionId destination_cid, + QUICConnectionId source_cid, QUICPacketNumber packet_number, + QUICPacketNumber base_packet_number, QUICVersion version, bool crypto, ats_unique_buf payload, + size_t len, ats_unique_buf token, size_t token_len); + + /* + * Build a QUICPacketHeader + * + * This creates a QUICPacketLongHeader for RETRY packet + */ + static QUICPacketHeaderUPtr build(QUICPacketType type, QUICKeyPhase key_phase, QUICVersion version, + QUICConnectionId destination_cid, QUICConnectionId source_cid, QUICConnectionId original_dcid, + ats_unique_buf retry_token, size_t retry_token_len); + + /* + * Build a QUICPacketHeader + * + * This creates a QUICPacketShortHeader that contains a ConnectionID. + */ + static QUICPacketHeaderUPtr build(QUICPacketType type, QUICKeyPhase key_phase, QUICPacketNumber packet_number, + QUICPacketNumber base_packet_number, ats_unique_buf payload, size_t len); + + /* + * Build a QUICPacketHeader + * + * This creates a QUICPacketShortHeader that doesn't contain a ConnectionID (Stateless Reset Packet). + */ + static QUICPacketHeaderUPtr build(QUICPacketType type, QUICKeyPhase key_phase, QUICConnectionId connection_id, + QUICPacketNumber packet_number, QUICPacketNumber base_packet_number, ats_unique_buf payload, + size_t len); + +protected: + QUICPacketHeader(){}; + QUICPacketHeader(QUICPacketType type, QUICPacketNumber packet_number, QUICPacketNumber base_packet_number, bool has_version, + QUICVersion version, ats_unique_buf payload, size_t payload_length, QUICKeyPhase key_phase) + : _payload(std::move(payload)), + _type(type), + _key_phase(key_phase), + _packet_number(packet_number), + _base_packet_number(base_packet_number), + _version(version), + _payload_length(payload_length), + _has_version(has_version){}; + // Token field in Initial packet could be very long. + static constexpr size_t MAX_PACKET_HEADER_LEN = 256; + + const IpEndpoint _from = {}; + + // These two are used only if the instance was created with a buffer + ats_unique_buf _buf = {nullptr}; + size_t _buf_len = 0; + + // These are used only if the instance was created without a buffer + uint8_t _serialized[MAX_PACKET_HEADER_LEN]; + ats_unique_buf _payload = ats_unique_buf(nullptr); + QUICPacketType _type = QUICPacketType::UNINITIALIZED; + QUICKeyPhase _key_phase = QUICKeyPhase::INITIAL; + QUICConnectionId _connection_id = QUICConnectionId::ZERO(); + QUICPacketNumber _packet_number = 0; + QUICPacketNumber _base_packet_number = 0; + QUICVersion _version = 0; + size_t _payload_length = 0; + bool _has_version = false; +}; + +class QUICPacketLongHeader : public QUICPacketHeader +{ +public: + QUICPacketLongHeader() : QUICPacketHeader(){}; + virtual ~QUICPacketLongHeader(){}; + QUICPacketLongHeader(const IpEndpoint from, ats_unique_buf buf, size_t len, QUICPacketNumber base); + QUICPacketLongHeader(QUICPacketType type, QUICKeyPhase key_phase, const QUICConnectionId &destination_cid, + const QUICConnectionId &source_cid, QUICPacketNumber packet_number, QUICPacketNumber base_packet_number, + QUICVersion version, bool crypto, ats_unique_buf buf, size_t len, + ats_unique_buf token = ats_unique_buf(nullptr), size_t token_len = 0); + QUICPacketLongHeader(QUICPacketType type, QUICKeyPhase key_phase, QUICVersion version, const QUICConnectionId &destination_cid, + const QUICConnectionId &source_cid, const QUICConnectionId &original_dcid, ats_unique_buf retry_token, + size_t retry_token_len); + + QUICPacketType type() const override; + QUICConnectionId destination_cid() const override; + QUICConnectionId source_cid() const override; + QUICConnectionId original_dcid() const; + QUICPacketNumber packet_number() const override; + bool has_version() const override; + bool is_valid() const override; + bool is_crypto_packet() const override; + QUICVersion version() const override; + const uint8_t *payload() const override; + const uint8_t *token() const; + size_t token_len() const; + QUICKeyPhase key_phase() const override; + uint16_t size() const override; + void store(uint8_t *buf, size_t *len) const override; + + static bool type(QUICPacketType &type, const uint8_t *packet, size_t packet_len); + static bool version(QUICVersion &version, const uint8_t *packet, size_t packet_len); + /** + * Unlike QUICInvariants::dcil(), this returns actual connection id length + */ + static bool dcil(uint8_t &dcil, const uint8_t *packet, size_t packet_len); + /** + * Unlike QUICInvariants::scil(), this returns actual connection id length + */ + static bool scil(uint8_t &scil, const uint8_t *packet, size_t packet_len); + static bool token_length(size_t &token_length, uint8_t *field_len, const uint8_t *packet, size_t packet_len); + static bool length(size_t &length, uint8_t *field_len, const uint8_t *packet, size_t packet_len); + static bool key_phase(QUICKeyPhase &key_phase, const uint8_t *packet, size_t packet_len); + static bool packet_number_offset(uint8_t &pn_offset, const uint8_t *packet, size_t packet_len); + +private: + QUICConnectionId _destination_cid = QUICConnectionId::ZERO(); + QUICConnectionId _source_cid = QUICConnectionId::ZERO(); + QUICConnectionId _original_dcid = QUICConnectionId::ZERO(); //< RETRY packet only + size_t _token_len = 0; //< INITIAL packet only + size_t _token_offset = 0; //< INITIAL packet only + ats_unique_buf _token = ats_unique_buf(nullptr); //< INITIAL packet only + size_t _payload_offset = 0; + bool _is_crypto_packet = false; +}; + +class QUICPacketShortHeader : public QUICPacketHeader +{ +public: + QUICPacketShortHeader() : QUICPacketHeader(){}; + virtual ~QUICPacketShortHeader(){}; + QUICPacketShortHeader(const IpEndpoint from, ats_unique_buf buf, size_t len, QUICPacketNumber base); + QUICPacketShortHeader(QUICPacketType type, QUICKeyPhase key_phase, QUICPacketNumber packet_number, + QUICPacketNumber base_packet_number, ats_unique_buf buf, size_t len); + QUICPacketShortHeader(QUICPacketType type, QUICKeyPhase key_phase, const QUICConnectionId &connection_id, + QUICPacketNumber packet_number, QUICPacketNumber base_packet_number, ats_unique_buf buf, size_t len); + QUICPacketType type() const override; + QUICConnectionId destination_cid() const override; + QUICConnectionId + source_cid() const override + { + return QUICConnectionId::ZERO(); + } + QUICPacketNumber packet_number() const override; + bool has_version() const override; + bool is_valid() const override; + QUICVersion version() const override; + const uint8_t *payload() const override; + QUICKeyPhase key_phase() const override; + uint16_t size() const override; + void store(uint8_t *buf, size_t *len) const override; + + static bool key_phase(QUICKeyPhase &key_phase, const uint8_t *packet, size_t packet_len); + static bool packet_number_offset(uint8_t &pn_offset, const uint8_t *packet, size_t packet_len, int dcil); + +private: + int _packet_number_len; +}; + +class QUICPacketHeaderDeleter +{ +public: + static void + delete_null_header(QUICPacketHeader *header) + { + ink_assert(header == nullptr); + } + + static void + delete_long_header(QUICPacketHeader *header) + { + QUICPacketLongHeader *long_header = dynamic_cast(header); + ink_assert(long_header != nullptr); + long_header->~QUICPacketLongHeader(); + quicPacketLongHeaderAllocator.free(long_header); + } + + static void + delete_short_header(QUICPacketHeader *header) + { + QUICPacketShortHeader *short_header = dynamic_cast(header); + ink_assert(short_header != nullptr); + short_header->~QUICPacketShortHeader(); + quicPacketShortHeaderAllocator.free(short_header); + } +}; + +class QUICPacket +{ +public: + QUICPacket(); + + /* + * Creates a QUICPacket with a QUICPacketHeader and a buffer that contains payload + * + * This will be used for receiving packets. Therefore, it is expected that payload is already decrypted. + * However, QUICPacket class itself doesn't care about whether the payload is protected (encrypted) or not. + */ + QUICPacket(UDPConnection *udp_con, QUICPacketHeaderUPtr header, ats_unique_buf payload, size_t payload_len); + + QUICPacket(QUICPacketHeaderUPtr header, ats_unique_buf payload, size_t payload_len, std::vector &frames); + + /* + * Creates a QUICPacket with a QUICPacketHeader, a buffer that contains payload and a flag that indicates whether the packet is + * ack_eliciting + * + * This will be used for sending packets. Therefore, it is expected that payload is already encrypted. + * However, QUICPacket class itself doesn't care about whether the payload is protected (encrypted) or not. + */ + QUICPacket(QUICPacketHeaderUPtr header, ats_unique_buf payload, size_t payload_len, bool ack_eliciting, bool probing); + + QUICPacket(QUICPacketHeaderUPtr header, ats_unique_buf payload, size_t payload_len, bool ack_eliciting, bool probing, + std::vector &frames); + + ~QUICPacket(); + + UDPConnection *udp_con() const; + const IpEndpoint &from() const; + QUICPacketType type() const; + QUICConnectionId destination_cid() const; + QUICConnectionId source_cid() const; + QUICPacketNumber packet_number() const; + QUICVersion version() const; + const QUICPacketHeader &header() const; + const uint8_t *payload() const; + bool is_ack_eliciting() const; + bool is_crypto_packet() const; + bool is_probing_packet() const; + + /* + * Size of whole QUIC packet (header + payload + integrity check) + */ + uint16_t size() const; + + /* + * Size of header + */ + uint16_t header_size() const; + + /* + * Length of payload + */ + uint16_t payload_length() const; + + void store(uint8_t *buf, size_t *len) const; + QUICKeyPhase key_phase() const; + + /***** STATIC MEMBERS *****/ + + static uint8_t calc_packet_number_len(QUICPacketNumber num, QUICPacketNumber base); + static bool encode_packet_number(QUICPacketNumber &dst, QUICPacketNumber src, size_t len); + static bool decode_packet_number(QUICPacketNumber &dst, QUICPacketNumber src, size_t len, QUICPacketNumber largest_acked); + + LINK(QUICPacket, link); + +private: + UDPConnection *_udp_con = nullptr; + QUICPacketHeaderUPtr _header = QUICPacketHeaderUPtr(nullptr, &QUICPacketHeaderDeleter::delete_null_header); + ats_unique_buf _payload = ats_unique_buf(nullptr); + size_t _payload_size = 0; + bool _is_ack_eliciting = false; + bool _is_probing_packet = false; +}; + +using QUICPacketDeleterFunc = void (*)(QUICPacket *p); +using QUICPacketUPtr = std::unique_ptr; + +class QUICPacketDeleter +{ +public: + // TODO Probably these methods should call destructor + static void + delete_null_packet(QUICPacket *packet) + { + ink_assert(packet == nullptr); + } + + static void + delete_packet(QUICPacket *packet) + { + packet->~QUICPacket(); + quicPacketAllocator.free(packet); + } +}; diff --git a/iocore/net/quic/QUICPacketFactory.cc b/iocore/net/quic/QUICPacketFactory.cc new file mode 100644 index 00000000000..4c88c1b3151 --- /dev/null +++ b/iocore/net/quic/QUICPacketFactory.cc @@ -0,0 +1,361 @@ +/** @file + * + * A brief file description + * + * @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 "QUICPacketFactory.h" +#include "QUICPacketProtectionKeyInfo.h" +#include "QUICDebugNames.h" + +using namespace std::literals; +static constexpr std::string_view tag = "quic_packet"sv; +static constexpr std::string_view tag_v = "v_quic_packet"sv; + +#define QUICDebug(dcid, scid, fmt, ...) \ + Debug(tag.data(), "[%08" PRIx32 "-%08" PRIx32 "] " fmt, dcid.h32(), scid.h32(), ##__VA_ARGS__); +#define QUICVDebug(dcid, scid, fmt, ...) \ + Debug(tag_v.data(), "[%08" PRIx32 "-%08" PRIx32 "] " fmt, dcid.h32(), scid.h32(), ##__VA_ARGS__); + +// +// QUICPacketNumberGenerator +// +QUICPacketNumberGenerator::QUICPacketNumberGenerator() {} + +QUICPacketNumber +QUICPacketNumberGenerator::next() +{ + // TODO Increment the number at least one but not only always one + return this->_current++; +} + +void +QUICPacketNumberGenerator::reset() +{ + this->_current = 0; +} + +// +// QUICPacketFactory +// +QUICPacketUPtr +QUICPacketFactory::create_null_packet() +{ + return {nullptr, &QUICPacketDeleter::delete_null_packet}; +} + +QUICPacketUPtr +QUICPacketFactory::create(UDPConnection *udp_con, IpEndpoint from, ats_unique_buf buf, size_t len, + QUICPacketNumber base_packet_number, QUICPacketCreationResult &result) +{ + size_t max_plain_txt_len = 2048; + ats_unique_buf plain_txt = ats_unique_malloc(max_plain_txt_len); + size_t plain_txt_len = 0; + + QUICPacketHeaderUPtr header = QUICPacketHeader::load(from, std::move(buf), len, base_packet_number); + + QUICConnectionId dcid = header->destination_cid(); + QUICConnectionId scid = header->source_cid(); + QUICVDebug(scid, dcid, "Decrypting %s packet #%" PRIu64 " using %s", QUICDebugNames::packet_type(header->type()), + header->packet_number(), QUICDebugNames::key_phase(header->key_phase())); + + if (header->has_version() && !QUICTypeUtil::is_supported_version(header->version())) { + if (header->type() == QUICPacketType::VERSION_NEGOTIATION) { + // version of VN packet is 0x00000000 + // This packet is unprotected. Just copy the payload + result = QUICPacketCreationResult::SUCCESS; + memcpy(plain_txt.get(), header->payload(), header->payload_size()); + plain_txt_len = header->payload_size(); + } else { + // We can't decrypt packets that have unknown versions + // What we can use is invariant field of Long Header - version, dcid, and scid + result = QUICPacketCreationResult::UNSUPPORTED; + } + } else { + Ptr plain = make_ptr(new_IOBufferBlock()); + Ptr protected_ibb = make_ptr(new_IOBufferBlock()); + protected_ibb->set_internal(reinterpret_cast(const_cast(header->payload())), header->payload_size(), + BUFFER_SIZE_NOT_ALLOCATED); + Ptr header_ibb = make_ptr(new_IOBufferBlock()); + header_ibb->set_internal(reinterpret_cast(const_cast(header->buf())), header->size(), + BUFFER_SIZE_NOT_ALLOCATED); + + switch (header->type()) { + case QUICPacketType::STATELESS_RESET: + case QUICPacketType::RETRY: + // These packets are unprotected. Just copy the payload + memcpy(plain_txt.get(), header->payload(), header->payload_size()); + plain_txt_len = header->payload_size(); + result = QUICPacketCreationResult::SUCCESS; + break; + case QUICPacketType::PROTECTED: + if (this->_pp_key_info.is_decryption_key_available(header->key_phase())) { + plain = this->_pp_protector.unprotect(header_ibb, protected_ibb, header->packet_number(), header->key_phase()); + if (plain != nullptr) { + memcpy(plain_txt.get(), plain->buf(), plain->size()); + plain_txt_len = plain->size(); + result = QUICPacketCreationResult::SUCCESS; + } else { + result = QUICPacketCreationResult::FAILED; + } + } else { + result = QUICPacketCreationResult::NOT_READY; + } + break; + case QUICPacketType::INITIAL: + if (this->_pp_key_info.is_decryption_key_available(QUICKeyPhase::INITIAL)) { + if (QUICTypeUtil::is_supported_version(header->version())) { + plain = this->_pp_protector.unprotect(header_ibb, protected_ibb, header->packet_number(), header->key_phase()); + if (plain != nullptr) { + memcpy(plain_txt.get(), plain->buf(), plain->size()); + plain_txt_len = plain->size(); + result = QUICPacketCreationResult::SUCCESS; + } else { + result = QUICPacketCreationResult::FAILED; + } + } else { + result = QUICPacketCreationResult::SUCCESS; + } + } else { + result = QUICPacketCreationResult::IGNORED; + } + break; + case QUICPacketType::HANDSHAKE: + if (this->_pp_key_info.is_decryption_key_available(QUICKeyPhase::HANDSHAKE)) { + plain = this->_pp_protector.unprotect(header_ibb, protected_ibb, header->packet_number(), header->key_phase()); + if (plain != nullptr) { + memcpy(plain_txt.get(), plain->buf(), plain->size()); + plain_txt_len = plain->size(); + result = QUICPacketCreationResult::SUCCESS; + } else { + result = QUICPacketCreationResult::FAILED; + } + } else { + result = QUICPacketCreationResult::IGNORED; + } + break; + case QUICPacketType::ZERO_RTT_PROTECTED: + if (this->_pp_key_info.is_decryption_key_available(QUICKeyPhase::ZERO_RTT)) { + plain = this->_pp_protector.unprotect(header_ibb, protected_ibb, header->packet_number(), header->key_phase()); + if (plain != nullptr) { + memcpy(plain_txt.get(), plain->buf(), plain->size()); + plain_txt_len = plain->size(); + result = QUICPacketCreationResult::SUCCESS; + } else { + result = QUICPacketCreationResult::IGNORED; + } + } else { + result = QUICPacketCreationResult::NOT_READY; + } + break; + default: + result = QUICPacketCreationResult::FAILED; + break; + } + } + + QUICPacket *packet = nullptr; + if (result == QUICPacketCreationResult::SUCCESS || result == QUICPacketCreationResult::UNSUPPORTED) { + packet = quicPacketAllocator.alloc(); + new (packet) QUICPacket(udp_con, std::move(header), std::move(plain_txt), plain_txt_len); + } + + return QUICPacketUPtr(packet, &QUICPacketDeleter::delete_packet); +} + +QUICPacketUPtr +QUICPacketFactory::create_version_negotiation_packet(QUICConnectionId dcid, QUICConnectionId scid) +{ + size_t len = sizeof(QUICVersion) * (countof(QUIC_SUPPORTED_VERSIONS) + 1); + ats_unique_buf versions(reinterpret_cast(ats_malloc(len))); + uint8_t *p = versions.get(); + + size_t n; + for (auto v : QUIC_SUPPORTED_VERSIONS) { + QUICTypeUtil::write_QUICVersion(v, p, &n); + p += n; + } + + // [draft-18] 6.3. Using Reserved Versions + // To help ensure this, a server SHOULD include a reserved version (see Section 15) while generating a + // Version Negotiation packet. + QUICTypeUtil::write_QUICVersion(QUIC_EXERCISE_VERSION, p, &n); + p += n; + + ink_assert(len == static_cast(p - versions.get())); + // VN packet dosen't have packet number field and version field is always 0x00000000 + QUICPacketHeaderUPtr header = QUICPacketHeader::build(QUICPacketType::VERSION_NEGOTIATION, QUICKeyPhase::INITIAL, dcid, scid, + 0x00, 0x00, 0x00, false, std::move(versions), len); + + return QUICPacketFactory::_create_unprotected_packet(std::move(header)); +} + +QUICPacketUPtr +QUICPacketFactory::create_initial_packet(QUICConnectionId destination_cid, QUICConnectionId source_cid, + QUICPacketNumber base_packet_number, ats_unique_buf payload, size_t len, + bool retransmittable, bool probing, bool crypto, ats_unique_buf token, size_t token_len) +{ + QUICPacketNumberSpace index = QUICTypeUtil::pn_space(QUICEncryptionLevel::INITIAL); + QUICPacketNumber pn = this->_packet_number_generator[static_cast(index)].next(); + QUICPacketHeaderUPtr header = + QUICPacketHeader::build(QUICPacketType::INITIAL, QUICKeyPhase::INITIAL, destination_cid, source_cid, pn, base_packet_number, + this->_version, crypto, std::move(payload), len, std::move(token), token_len); + return this->_create_encrypted_packet(std::move(header), retransmittable, probing); +} + +QUICPacketUPtr +QUICPacketFactory::create_retry_packet(QUICConnectionId destination_cid, QUICConnectionId source_cid, + QUICConnectionId original_dcid, QUICRetryToken &token) +{ + ats_unique_buf payload = ats_unique_malloc(token.length()); + memcpy(payload.get(), token.buf(), token.length()); + + QUICPacketHeaderUPtr header = + QUICPacketHeader::build(QUICPacketType::RETRY, QUICKeyPhase::INITIAL, QUIC_SUPPORTED_VERSIONS[0], destination_cid, source_cid, + original_dcid, std::move(payload), token.length()); + return QUICPacketFactory::_create_unprotected_packet(std::move(header)); +} + +QUICPacketUPtr +QUICPacketFactory::create_handshake_packet(QUICConnectionId destination_cid, QUICConnectionId source_cid, + QUICPacketNumber base_packet_number, ats_unique_buf payload, size_t len, + bool retransmittable, bool probing, bool crypto) +{ + QUICPacketNumberSpace index = QUICTypeUtil::pn_space(QUICEncryptionLevel::INITIAL); + QUICPacketNumber pn = this->_packet_number_generator[static_cast(index)].next(); + QUICPacketHeaderUPtr header = + QUICPacketHeader::build(QUICPacketType::HANDSHAKE, QUICKeyPhase::HANDSHAKE, destination_cid, source_cid, pn, base_packet_number, + this->_version, crypto, std::move(payload), len); + return this->_create_encrypted_packet(std::move(header), retransmittable, probing); +} + +QUICPacketUPtr +QUICPacketFactory::create_zero_rtt_packet(QUICConnectionId destination_cid, QUICConnectionId source_cid, + QUICPacketNumber base_packet_number, ats_unique_buf payload, size_t len, + bool retransmittable, bool probing) +{ + QUICPacketNumberSpace index = QUICTypeUtil::pn_space(QUICEncryptionLevel::INITIAL); + QUICPacketNumber pn = this->_packet_number_generator[static_cast(index)].next(); + QUICPacketHeaderUPtr header = + QUICPacketHeader::build(QUICPacketType::ZERO_RTT_PROTECTED, QUICKeyPhase::ZERO_RTT, destination_cid, source_cid, pn, + base_packet_number, this->_version, false, std::move(payload), len); + return this->_create_encrypted_packet(std::move(header), retransmittable, probing); +} + +QUICPacketUPtr +QUICPacketFactory::create_protected_packet(QUICConnectionId connection_id, QUICPacketNumber base_packet_number, + ats_unique_buf payload, size_t len, bool retransmittable, bool probing) +{ + QUICPacketNumberSpace index = QUICTypeUtil::pn_space(QUICEncryptionLevel::INITIAL); + QUICPacketNumber pn = this->_packet_number_generator[static_cast(index)].next(); + // TODO Key phase should be picked up from QUICHandshakeProtocol, probably + QUICPacketHeaderUPtr header = QUICPacketHeader::build(QUICPacketType::PROTECTED, QUICKeyPhase::PHASE_0, connection_id, pn, + base_packet_number, std::move(payload), len); + return this->_create_encrypted_packet(std::move(header), retransmittable, probing); +} + +QUICPacketUPtr +QUICPacketFactory::create_stateless_reset_packet(QUICConnectionId connection_id, QUICStatelessResetToken stateless_reset_token) +{ + std::random_device rnd; + + uint8_t random_packet_number = static_cast(rnd() & 0xFF); + size_t payload_len = static_cast((rnd() & 0xFF) | 16); // Mimimum length has to be 16 + ats_unique_buf payload = ats_unique_malloc(payload_len + 16); + uint8_t *naked_payload = payload.get(); + + // Generate random octets + for (int i = payload_len - 1; i >= 0; --i) { + naked_payload[i] = static_cast(rnd() & 0xFF); + } + // Copy stateless reset token into payload + memcpy(naked_payload + payload_len - 16, stateless_reset_token.buf(), 16); + + // KeyPhase won't be used + QUICPacketHeaderUPtr header = QUICPacketHeader::build(QUICPacketType::STATELESS_RESET, QUICKeyPhase::INITIAL, connection_id, + random_packet_number, 0, std::move(payload), payload_len); + return QUICPacketFactory::_create_unprotected_packet(std::move(header)); +} + +QUICPacketUPtr +QUICPacketFactory::_create_unprotected_packet(QUICPacketHeaderUPtr header) +{ + ats_unique_buf cleartext = ats_unique_malloc(2048); + size_t cleartext_len = header->payload_size(); + + memcpy(cleartext.get(), header->payload(), cleartext_len); + QUICPacket *packet = quicPacketAllocator.alloc(); + new (packet) QUICPacket(std::move(header), std::move(cleartext), cleartext_len, false, false); + + return QUICPacketUPtr(packet, &QUICPacketDeleter::delete_packet); +} + +QUICPacketUPtr +QUICPacketFactory::_create_encrypted_packet(QUICPacketHeaderUPtr header, bool retransmittable, bool probing) +{ + QUICConnectionId dcid = header->destination_cid(); + QUICConnectionId scid = header->source_cid(); + QUICVDebug(dcid, scid, "Encrypting %s packet #%" PRIu64 " using %s", QUICDebugNames::packet_type(header->type()), + header->packet_number(), QUICDebugNames::key_phase(header->key_phase())); + + QUICPacket *packet = nullptr; + + Ptr payload_ibb = make_ptr(new_IOBufferBlock()); + payload_ibb->set_internal(reinterpret_cast(const_cast(header->payload())), header->payload_size(), + BUFFER_SIZE_NOT_ALLOCATED); + + Ptr header_ibb = make_ptr(new_IOBufferBlock()); + header_ibb->set_internal(reinterpret_cast(const_cast(header->buf())), header->size(), + BUFFER_SIZE_NOT_ALLOCATED); + + Ptr protected_payload = + this->_pp_protector.protect(header_ibb, payload_ibb, header->packet_number(), header->key_phase()); + if (protected_payload != nullptr) { + ats_unique_buf cipher_txt = ats_unique_malloc(protected_payload->size()); + memcpy(cipher_txt.get(), protected_payload->buf(), protected_payload->size()); + packet = quicPacketAllocator.alloc(); + new (packet) QUICPacket(std::move(header), std::move(cipher_txt), protected_payload->size(), retransmittable, probing); + } else { + QUICDebug(dcid, scid, "Failed to encrypt a packet"); + } + + return QUICPacketUPtr(packet, &QUICPacketDeleter::delete_packet); +} + +void +QUICPacketFactory::set_version(QUICVersion negotiated_version) +{ + this->_version = negotiated_version; +} + +bool +QUICPacketFactory::is_ready_to_create_protected_packet() +{ + return this->_pp_key_info.is_encryption_key_available(QUICKeyPhase::PHASE_0) || + this->_pp_key_info.is_encryption_key_available(QUICKeyPhase::PHASE_1); +} + +void +QUICPacketFactory::reset() +{ + for (auto i = 0; i < kPacketNumberSpace; i++) { + this->_packet_number_generator[i].reset(); + } +} diff --git a/iocore/net/quic/QUICPacketFactory.h b/iocore/net/quic/QUICPacketFactory.h new file mode 100644 index 00000000000..a00639dfda4 --- /dev/null +++ b/iocore/net/quic/QUICPacketFactory.h @@ -0,0 +1,85 @@ +/** @file + * + * QUIC packet factory + * + * @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. + */ + +#pragma once + +#include "QUICTypes.h" +#include "QUICPacket.h" +#include "QUICPacketPayloadProtector.h" + +class QUICPacketProtectionKeyInfo; + +class QUICPacketNumberGenerator +{ +public: + QUICPacketNumberGenerator(); + QUICPacketNumber next(); + void reset(); + +private: + std::atomic _current = 0; +}; + +class QUICPacketFactory +{ +public: + static QUICPacketUPtr create_null_packet(); + static QUICPacketUPtr create_version_negotiation_packet(QUICConnectionId dcid, QUICConnectionId scid); + static QUICPacketUPtr create_stateless_reset_packet(QUICConnectionId connection_id, + QUICStatelessResetToken stateless_reset_token); + static QUICPacketUPtr create_retry_packet(QUICConnectionId destination_cid, QUICConnectionId source_cid, + QUICConnectionId original_dcid, QUICRetryToken &token); + + QUICPacketFactory(const QUICPacketProtectionKeyInfo &pp_key_info) : _pp_key_info(pp_key_info), _pp_protector(pp_key_info) {} + + QUICPacketUPtr create(UDPConnection *udp_con, IpEndpoint from, ats_unique_buf buf, size_t len, + QUICPacketNumber base_packet_number, QUICPacketCreationResult &result); + QUICPacketUPtr create_initial_packet(QUICConnectionId destination_cid, QUICConnectionId source_cid, + QUICPacketNumber base_packet_number, ats_unique_buf payload, size_t len, bool ack_eliciting, + bool probing, bool crypto, ats_unique_buf token = ats_unique_buf(nullptr), + size_t token_len = 0); + QUICPacketUPtr create_handshake_packet(QUICConnectionId destination_cid, QUICConnectionId source_cid, + QUICPacketNumber base_packet_number, ats_unique_buf payload, size_t len, + bool ack_eliciting, bool probing, bool crypto); + QUICPacketUPtr create_zero_rtt_packet(QUICConnectionId destination_cid, QUICConnectionId source_cid, + QUICPacketNumber base_packet_number, ats_unique_buf payload, size_t len, bool ack_eliciting, + bool probing); + QUICPacketUPtr create_protected_packet(QUICConnectionId connection_id, QUICPacketNumber base_packet_number, + ats_unique_buf payload, size_t len, bool ack_eliciting, bool probing); + void set_version(QUICVersion negotiated_version); + + bool is_ready_to_create_protected_packet(); + void reset(); + +private: + QUICVersion _version = QUIC_SUPPORTED_VERSIONS[0]; + + const QUICPacketProtectionKeyInfo &_pp_key_info; + QUICPacketPayloadProtector _pp_protector; + + // Initial, 0/1-RTT, and Handshake + QUICPacketNumberGenerator _packet_number_generator[3]; + + static QUICPacketUPtr _create_unprotected_packet(QUICPacketHeaderUPtr header); + QUICPacketUPtr _create_encrypted_packet(QUICPacketHeaderUPtr header, bool ack_eliciting, bool probing); +}; diff --git a/iocore/net/quic/QUICPacketHeaderProtector.cc b/iocore/net/quic/QUICPacketHeaderProtector.cc new file mode 100644 index 00000000000..49a29adb3ba --- /dev/null +++ b/iocore/net/quic/QUICPacketHeaderProtector.cc @@ -0,0 +1,222 @@ +/** @file + * + * QUIC Packet Header Protector + * + * @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 "QUICPacketProtectionKeyInfo.h" +#include "QUICPacketHeaderProtector.h" +#include "QUICDebugNames.h" +#include "QUICPacket.h" + +#include "tscore/Diags.h" + +bool +QUICPacketHeaderProtector::protect(uint8_t *unprotected_packet, size_t unprotected_packet_len, int dcil) const +{ + // Do nothing if the packet is VN + if (QUICInvariants::is_long_header(unprotected_packet)) { + QUICVersion version; + QUICPacketLongHeader::version(version, unprotected_packet, unprotected_packet_len); + if (version == 0x0) { + return true; + } + } + + QUICKeyPhase phase; + QUICPacketType type; + if (QUICInvariants::is_long_header(unprotected_packet)) { + QUICPacketLongHeader::key_phase(phase, unprotected_packet, unprotected_packet_len); + QUICPacketLongHeader::type(type, unprotected_packet, unprotected_packet_len); + } else { + // This is a kind of hack. For short header we need to use the same key for header protection regardless of the key phase. + phase = QUICKeyPhase::PHASE_0; + type = QUICPacketType::PROTECTED; + } + + Debug("v_quic_pne", "Protecting a packet number of %s packet using %s", QUICDebugNames::packet_type(type), + QUICDebugNames::key_phase(phase)); + + const EVP_CIPHER *aead = this->_pp_key_info.get_cipher_for_hp(phase); + if (!aead) { + Debug("quic_pne", "Failed to encrypt a packet number: keys for %s is not ready", QUICDebugNames::key_phase(phase)); + return false; + } + + const uint8_t *key = this->_pp_key_info.encryption_key_for_hp(phase); + if (!key) { + Debug("quic_pne", "Failed to encrypt a packet number: keys for %s is not ready", QUICDebugNames::key_phase(phase)); + return false; + } + + uint8_t sample_offset; + if (!this->_calc_sample_offset(&sample_offset, unprotected_packet, unprotected_packet_len, dcil)) { + Debug("v_quic_pne", "Failed to calculate a sample offset"); + return false; + } + + uint8_t mask[EVP_MAX_BLOCK_LENGTH]; + if (!this->_generate_mask(mask, unprotected_packet + sample_offset, key, aead)) { + Debug("v_quic_pne", "Failed to generate a mask"); + return false; + } + + if (!this->_protect(unprotected_packet, unprotected_packet_len, mask, dcil)) { + Debug("quic_pne", "Failed to encrypt a packet number"); + } + + return true; +} + +bool +QUICPacketHeaderProtector::unprotect(uint8_t *protected_packet, size_t protected_packet_len) const +{ + // Do nothing if the packet is VN or RETRY + if (QUICInvariants::is_long_header(protected_packet)) { + QUICVersion version; + QUICPacketLongHeader::version(version, protected_packet, protected_packet_len); + if (version == 0x0) { + return true; + } + QUICPacketType type; + QUICPacketLongHeader::type(type, protected_packet, protected_packet_len); + if (type == QUICPacketType::RETRY) { + return true; + } + } + + QUICKeyPhase phase; + QUICPacketType type; + if (QUICInvariants::is_long_header(protected_packet)) { + QUICPacketLongHeader::key_phase(phase, protected_packet, protected_packet_len); + QUICPacketLongHeader::type(type, protected_packet, protected_packet_len); + } else { + // This is a kind of hack. For short header we need to use the same key for header protection regardless of the key phase. + phase = QUICKeyPhase::PHASE_0; + type = QUICPacketType::PROTECTED; + } + + Debug("v_quic_pne", "Unprotecting a packet number of %s packet using %s", QUICDebugNames::packet_type(type), + QUICDebugNames::key_phase(phase)); + + const EVP_CIPHER *aead = this->_pp_key_info.get_cipher_for_hp(phase); + if (!aead) { + Debug("quic_pne", "Failed to decrypt a packet number: keys for %s is not ready", QUICDebugNames::key_phase(phase)); + return false; + } + + const uint8_t *key = this->_pp_key_info.decryption_key_for_hp(phase); + if (!key) { + Debug("quic_pne", "Failed to decrypt a packet number: keys for %s is not ready", QUICDebugNames::key_phase(phase)); + return false; + } + + uint8_t sample_offset; + if (!this->_calc_sample_offset(&sample_offset, protected_packet, protected_packet_len, QUICConnectionId::SCID_LEN)) { + Debug("v_quic_pne", "Failed to calculate a sample offset"); + return false; + } + + uint8_t mask[EVP_MAX_BLOCK_LENGTH]; + if (!this->_generate_mask(mask, protected_packet + sample_offset, key, aead)) { + Debug("v_quic_pne", "Failed to generate a mask"); + return false; + } + + if (!this->_unprotect(protected_packet, protected_packet_len, mask)) { + Debug("quic_pne", "Failed to decrypt a packet number"); + } + + return true; +} + +bool +QUICPacketHeaderProtector::_calc_sample_offset(uint8_t *sample_offset, const uint8_t *protected_packet, size_t protected_packet_len, + int dcil) const +{ + if (QUICInvariants::is_long_header(protected_packet)) { + uint8_t dcil; + uint8_t scil; + size_t dummy; + uint8_t length_len; + QUICPacketLongHeader::dcil(dcil, protected_packet, protected_packet_len); + QUICPacketLongHeader::scil(scil, protected_packet, protected_packet_len); + QUICPacketLongHeader::length(dummy, &length_len, protected_packet, protected_packet_len); + *sample_offset = 6 + dcil + scil + length_len + 4; + + QUICPacketType type; + QUICPacketLongHeader::type(type, protected_packet, protected_packet_len); + if (type == QUICPacketType::INITIAL) { + size_t token_len; + uint8_t token_length_len; + QUICPacketLongHeader::token_length(token_len, &token_length_len, protected_packet, protected_packet_len); + *sample_offset += token_len + token_length_len; + } + } else { + *sample_offset = 1 + dcil + 4; + } + + return static_cast(*sample_offset + 16) <= protected_packet_len; +} + +bool +QUICPacketHeaderProtector::_unprotect(uint8_t *protected_packet, size_t protected_packet_len, const uint8_t *mask) const +{ + uint8_t pn_offset; + + // Unprotect packet number + if (QUICInvariants::is_long_header(protected_packet)) { + protected_packet[0] ^= mask[0] & 0x0f; + QUICPacketLongHeader::packet_number_offset(pn_offset, protected_packet, protected_packet_len); + } else { + protected_packet[0] ^= mask[0] & 0x1f; + QUICPacketShortHeader::packet_number_offset(pn_offset, protected_packet, protected_packet_len, QUICConnectionId::SCID_LEN); + } + uint8_t pn_length = QUICTypeUtil::read_QUICPacketNumberLen(protected_packet); + + for (int i = 0; i < pn_length; ++i) { + protected_packet[pn_offset + i] ^= mask[1 + i]; + } + + return true; +} + +bool +QUICPacketHeaderProtector::_protect(uint8_t *protected_packet, size_t protected_packet_len, const uint8_t *mask, int dcil) const +{ + uint8_t pn_offset; + + uint8_t pn_length = QUICTypeUtil::read_QUICPacketNumberLen(protected_packet); + + // Protect packet number + if (QUICInvariants::is_long_header(protected_packet)) { + protected_packet[0] ^= mask[0] & 0x0f; + QUICPacketLongHeader::packet_number_offset(pn_offset, protected_packet, protected_packet_len); + } else { + protected_packet[0] ^= mask[0] & 0x1f; + QUICPacketShortHeader::packet_number_offset(pn_offset, protected_packet, protected_packet_len, dcil); + } + + for (int i = 0; i < pn_length; ++i) { + protected_packet[pn_offset + i] ^= mask[1 + i]; + } + + return true; +} diff --git a/iocore/net/quic/QUICPacketHeaderProtector.h b/iocore/net/quic/QUICPacketHeaderProtector.h new file mode 100644 index 00000000000..215b3ed0d43 --- /dev/null +++ b/iocore/net/quic/QUICPacketHeaderProtector.h @@ -0,0 +1,48 @@ +/** @file + * + * QUIC Packet Header Protector + * + * @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. + */ + +#pragma once + +#include "QUICTypes.h" +#include "QUICKeyGenerator.h" + +class QUICPacketProtectionKeyInfo; + +class QUICPacketHeaderProtector +{ +public: + QUICPacketHeaderProtector(const QUICPacketProtectionKeyInfo &pp_key_info) : _pp_key_info(pp_key_info) {} + + bool unprotect(uint8_t *protected_packet, size_t protected_packet_len) const; + bool protect(uint8_t *unprotected_packet, size_t unprotected_packet_len, int dcil) const; + +private: + const QUICPacketProtectionKeyInfo &_pp_key_info; + + bool _calc_sample_offset(uint8_t *sample_offset, const uint8_t *protected_packet, size_t protected_packet_len, int dcil) const; + + bool _generate_mask(uint8_t *mask, const uint8_t *sample, const uint8_t *key, const EVP_CIPHER *cipher) const; + + bool _unprotect(uint8_t *packet, size_t packet_len, const uint8_t *mask) const; + bool _protect(uint8_t *packet, size_t packet_len, const uint8_t *mask, int dcil) const; +}; diff --git a/iocore/net/quic/QUICPacketHeaderProtector_boringssl.cc b/iocore/net/quic/QUICPacketHeaderProtector_boringssl.cc new file mode 100644 index 00000000000..54c539e6c59 --- /dev/null +++ b/iocore/net/quic/QUICPacketHeaderProtector_boringssl.cc @@ -0,0 +1,31 @@ +/** @file + * + * QUIC Packet Header Protector (BoringSSL specific code) + * + * @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 "QUICPacketHeaderProtector.h" + +bool +QUICPacketHeaderProtector::_generate_mask(uint8_t *mask, const uint8_t *sample, const uint8_t *key, const EVP_CIPHER *cipher) const +{ + ink_assert(!"not implemented"); + return false; +} diff --git a/iocore/net/quic/QUICPacketHeaderProtector_openssl.cc b/iocore/net/quic/QUICPacketHeaderProtector_openssl.cc new file mode 100644 index 00000000000..43bbba8e901 --- /dev/null +++ b/iocore/net/quic/QUICPacketHeaderProtector_openssl.cc @@ -0,0 +1,53 @@ +/** @file + * + * QUIC Packet Header Protector (OpenSSL specific code) + * + * @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 "QUICPacketHeaderProtector.h" + +bool +QUICPacketHeaderProtector::_generate_mask(uint8_t *mask, const uint8_t *sample, const uint8_t *key, const EVP_CIPHER *cipher) const +{ + static constexpr unsigned char FIVE_ZEROS[] = {0x00, 0x00, 0x00, 0x00, 0x00}; + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + + if (!ctx || !EVP_EncryptInit_ex(ctx, cipher, nullptr, key, sample)) { + return false; + } + + int len = 0; + if (cipher == EVP_chacha20()) { + if (!EVP_EncryptUpdate(ctx, mask, &len, FIVE_ZEROS, sizeof(FIVE_ZEROS))) { + return false; + } + } else { + if (!EVP_EncryptUpdate(ctx, mask, &len, sample, 16)) { + return false; + } + } + if (!EVP_EncryptFinal_ex(ctx, mask + len, &len)) { + return false; + } + + EVP_CIPHER_CTX_free(ctx); + + return true; +} diff --git a/iocore/net/quic/QUICPacketPayloadProtector.cc b/iocore/net/quic/QUICPacketPayloadProtector.cc new file mode 100644 index 00000000000..ca690577c2d --- /dev/null +++ b/iocore/net/quic/QUICPacketPayloadProtector.cc @@ -0,0 +1,124 @@ +/** @file + * + * QUIC Packet Header Protector + * + * @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 "QUICPacketProtectionKeyInfo.h" +#include "QUICPacketPayloadProtector.h" +#include "QUICDebugNames.h" + +static constexpr char tag[] = "quic_ppp"; + +Ptr +QUICPacketPayloadProtector::protect(const Ptr unprotected_header, const Ptr unprotected_payload, + uint64_t pkt_num, QUICKeyPhase phase) const +{ + Ptr protected_payload; + protected_payload = nullptr; + + if (!this->_pp_key_info.is_encryption_key_available(phase)) { + Debug(tag, "Failed to encrypt a packet: keys for %s is not ready", QUICDebugNames::key_phase(phase)); + return protected_payload; + } + + size_t tag_len = this->_pp_key_info.get_tag_len(phase); + const uint8_t *key = this->_pp_key_info.encryption_key(phase); + const uint8_t *iv = this->_pp_key_info.encryption_iv(phase); + size_t iv_len = *this->_pp_key_info.encryption_iv_len(phase); + + const EVP_CIPHER *cipher = this->_pp_key_info.get_cipher(phase); + + protected_payload = make_ptr(new_IOBufferBlock()); + protected_payload->alloc(iobuffer_size_to_index(unprotected_payload->size() + tag_len)); + + size_t written_len = 0; + if (!this->_protect(reinterpret_cast(protected_payload->start()), written_len, protected_payload->write_avail(), + unprotected_payload, pkt_num, reinterpret_cast(unprotected_header->buf()), + unprotected_header->size(), key, iv, iv_len, cipher, tag_len)) { + Debug(tag, "Failed to encrypt a packet #%" PRIu64 " with keys for %s", pkt_num, QUICDebugNames::key_phase(phase)); + protected_payload = nullptr; + } else { + protected_payload->fill(written_len); + } + + return protected_payload; +} + +Ptr +QUICPacketPayloadProtector::unprotect(const Ptr unprotected_header, const Ptr protected_payload, + uint64_t pkt_num, QUICKeyPhase phase) const +{ + Ptr unprotected_payload; + unprotected_payload = nullptr; + + size_t tag_len = this->_pp_key_info.get_tag_len(phase); + const uint8_t *key = this->_pp_key_info.decryption_key(phase); + const uint8_t *iv = this->_pp_key_info.decryption_iv(phase); + size_t iv_len = *this->_pp_key_info.decryption_iv_len(phase); + if (!key) { + Debug(tag, "Failed to decrypt a packet: keys for %s is not ready", QUICDebugNames::key_phase(phase)); + return unprotected_payload; + } + const EVP_CIPHER *cipher = this->_pp_key_info.get_cipher(phase); + + unprotected_payload = make_ptr(new_IOBufferBlock()); + unprotected_payload->alloc(iobuffer_size_to_index(protected_payload->size())); + + size_t written_len = 0; + if (!this->_unprotect(reinterpret_cast(unprotected_payload->start()), written_len, unprotected_payload->write_avail(), + reinterpret_cast(protected_payload->buf()), protected_payload->size(), pkt_num, + reinterpret_cast(unprotected_header->buf()), unprotected_header->size(), key, iv, iv_len, cipher, + tag_len)) { + Debug(tag, "Failed to decrypt a packet #%" PRIu64, pkt_num); + unprotected_payload = nullptr; + } else { + unprotected_payload->fill(written_len); + } + return unprotected_payload; +} + +/** + * Example iv_len = 12 + * + * 0 1 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 (byte) + * +-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | iv | // IV + * +-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |0|0|0|0| pkt num | // network byte order & left-padded with zeros + * +-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | nonce | // nonce = iv xor pkt_num + * +-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + */ +void +QUICPacketPayloadProtector::_gen_nonce(uint8_t *nonce, size_t &nonce_len, uint64_t pkt_num, const uint8_t *iv, size_t iv_len) const +{ + nonce_len = iv_len; + memcpy(nonce, iv, iv_len); + + pkt_num = htobe64(pkt_num); + uint8_t *p = reinterpret_cast(&pkt_num); + + for (size_t i = 0; i < 8; ++i) { + nonce[iv_len - 8 + i] ^= p[i]; + } +} diff --git a/iocore/net/quic/QUICPacketPayloadProtector.h b/iocore/net/quic/QUICPacketPayloadProtector.h new file mode 100644 index 00000000000..a11197d83ea --- /dev/null +++ b/iocore/net/quic/QUICPacketPayloadProtector.h @@ -0,0 +1,53 @@ +/** @file + * + * QUIC Packet Payload Protector + * + * @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. + */ + +#pragma once + +#include "I_IOBuffer.h" +#include "QUICTypes.h" +#include "QUICKeyGenerator.h" + +class QUICPacketProtectionKeyInfo; + +class QUICPacketPayloadProtector +{ +public: + QUICPacketPayloadProtector(const QUICPacketProtectionKeyInfo &pp_key_info) : _pp_key_info(pp_key_info) {} + + Ptr protect(const Ptr protected_payload, const Ptr unprotected_payload, + uint64_t pkt_num, QUICKeyPhase phase) const; + Ptr unprotect(const Ptr unprotected_payload, const Ptr protected_payload, + uint64_t pkt_num, QUICKeyPhase phase) const; + +private: + const QUICPacketProtectionKeyInfo &_pp_key_info; + + bool _unprotect(uint8_t *plain, size_t &plain_len, size_t max_plain_len, const uint8_t *protected_payload, + size_t protected_payload_len, uint64_t pkt_num, const uint8_t *ad, size_t ad_len, const uint8_t *key, + const uint8_t *iv, size_t iv_len, const EVP_CIPHER *cipher, size_t tag_len) const; + bool _protect(uint8_t *protected_payload, size_t &protected_payload_len, size_t max_protected_payload_len, + const Ptr plain, uint64_t pkt_num, const uint8_t *ad, size_t ad_len, const uint8_t *key, + const uint8_t *iv, size_t iv_len, const EVP_CIPHER *cipher, size_t tag_len) const; + + void _gen_nonce(uint8_t *nonce, size_t &nonce_len, uint64_t pkt_num, const uint8_t *iv, size_t iv_len) const; +}; diff --git a/iocore/net/quic/QUICPacketPayloadProtector_boringssl.cc b/iocore/net/quic/QUICPacketPayloadProtector_boringssl.cc new file mode 100644 index 00000000000..56e88dd45fd --- /dev/null +++ b/iocore/net/quic/QUICPacketPayloadProtector_boringssl.cc @@ -0,0 +1,48 @@ +/** @file + * + * QUIC Packet Payload Protector (BoringSSL specific code) + * + * @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 "QUICPacketProtectionKeyInfo.h" +#include "QUICPacketPayloadProtector.h" +#include "tscore/Diags.h" + +// static constexpr char tag[] = "quic_ppp"; + +bool +QUICPacketPayloadProtector::_protect(uint8_t *protected_payload, size_t &protected_payload_len, size_t max_protecgted_payload_len, + const Ptr plain, uint64_t pkt_num, const uint8_t *ad, size_t ad_len, + const uint8_t *key, const uint8_t *iv, size_t iv_len, const EVP_CIPHER *cipher, + size_t tag_len) const +{ + ink_assert(!"not implemented"); + return false; +} + +bool +QUICPacketPayloadProtector::_unprotect(uint8_t *plain, size_t &plain_len, size_t max_plain_len, const uint8_t *protected_payload, + size_t protected_payload_len, uint64_t pkt_num, const uint8_t *ad, size_t ad_len, + const uint8_t *key, const uint8_t *iv, size_t iv_len, const EVP_CIPHER *cipher, + size_t tag_len) const +{ + ink_assert(!"not implemented"); + return false; +} diff --git a/iocore/net/quic/QUICPacketPayloadProtector_openssl.cc b/iocore/net/quic/QUICPacketPayloadProtector_openssl.cc new file mode 100644 index 00000000000..b25f099051e --- /dev/null +++ b/iocore/net/quic/QUICPacketPayloadProtector_openssl.cc @@ -0,0 +1,138 @@ +/** @file + * + * QUIC Packet Payload Protector (OpenSSL specific code) + * + * @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 "QUICPacketProtectionKeyInfo.h" +#include "QUICPacketPayloadProtector.h" +#include "tscore/Diags.h" + +static constexpr char tag[] = "quic_ppp"; + +bool +QUICPacketPayloadProtector::_protect(uint8_t *cipher, size_t &cipher_len, size_t max_cipher_len, const Ptr plain, + uint64_t pkt_num, const uint8_t *ad, size_t ad_len, const uint8_t *key, const uint8_t *iv, + size_t iv_len, const EVP_CIPHER *aead, size_t tag_len) const +{ + EVP_CIPHER_CTX *aead_ctx; + int len; + uint8_t nonce[EVP_MAX_IV_LENGTH] = {0}; + size_t nonce_len = 0; + + this->_gen_nonce(nonce, nonce_len, pkt_num, iv, iv_len); + + if (!(aead_ctx = EVP_CIPHER_CTX_new())) { + return false; + } + if (!EVP_EncryptInit_ex(aead_ctx, aead, nullptr, nullptr, nullptr)) { + return false; + } + if (!EVP_CIPHER_CTX_ctrl(aead_ctx, EVP_CTRL_AEAD_SET_IVLEN, nonce_len, nullptr)) { + return false; + } + if (!EVP_EncryptInit_ex(aead_ctx, nullptr, nullptr, key, nonce)) { + return false; + } + if (!EVP_EncryptUpdate(aead_ctx, nullptr, &len, ad, ad_len)) { + return false; + } + + cipher_len = 0; + Ptr b = plain; + while (b) { + if (!EVP_EncryptUpdate(aead_ctx, cipher + cipher_len, &len, reinterpret_cast(b->buf()), b->size())) { + return false; + } + cipher_len += len; + b = b->next; + } + + if (!EVP_EncryptFinal_ex(aead_ctx, cipher + cipher_len, &len)) { + return false; + } + cipher_len += len; + + if (max_cipher_len < cipher_len + tag_len) { + return false; + } + if (!EVP_CIPHER_CTX_ctrl(aead_ctx, EVP_CTRL_AEAD_GET_TAG, tag_len, cipher + cipher_len)) { + return false; + } + cipher_len += tag_len; + + EVP_CIPHER_CTX_free(aead_ctx); + + return true; +} + +bool +QUICPacketPayloadProtector::_unprotect(uint8_t *plain, size_t &plain_len, size_t max_plain_len, const uint8_t *cipher, + size_t cipher_len, uint64_t pkt_num, const uint8_t *ad, size_t ad_len, const uint8_t *key, + const uint8_t *iv, size_t iv_len, const EVP_CIPHER *aead, size_t tag_len) const +{ + EVP_CIPHER_CTX *aead_ctx; + int len; + uint8_t nonce[EVP_MAX_IV_LENGTH] = {0}; + size_t nonce_len = 0; + + this->_gen_nonce(nonce, nonce_len, pkt_num, iv, iv_len); + + if (!(aead_ctx = EVP_CIPHER_CTX_new())) { + return false; + } + if (!EVP_DecryptInit_ex(aead_ctx, aead, nullptr, nullptr, nullptr)) { + return false; + } + if (!EVP_CIPHER_CTX_ctrl(aead_ctx, EVP_CTRL_AEAD_SET_IVLEN, nonce_len, nullptr)) { + return false; + } + if (!EVP_DecryptInit_ex(aead_ctx, nullptr, nullptr, key, nonce)) { + return false; + } + if (!EVP_DecryptUpdate(aead_ctx, nullptr, &len, ad, ad_len)) { + return false; + } + + if (cipher_len < tag_len) { + return false; + } + cipher_len -= tag_len; + if (!EVP_DecryptUpdate(aead_ctx, plain, &len, cipher, cipher_len)) { + return false; + } + plain_len = len; + + if (!EVP_CIPHER_CTX_ctrl(aead_ctx, EVP_CTRL_AEAD_SET_TAG, tag_len, const_cast(cipher + cipher_len))) { + return false; + } + + int ret = EVP_DecryptFinal_ex(aead_ctx, plain + len, &len); + + EVP_CIPHER_CTX_free(aead_ctx); + + if (ret > 0) { + plain_len += len; + return true; + } else { + Debug(tag, "Failed to decrypt -- the first 4 bytes decrypted are %0x %0x %0x %0x", plain[0], plain[1], plain[2], plain[3]); + return false; + } +} diff --git a/iocore/net/quic/QUICPacketProtectionKeyInfo.cc b/iocore/net/quic/QUICPacketProtectionKeyInfo.cc new file mode 100644 index 00000000000..55c2e2ee5bd --- /dev/null +++ b/iocore/net/quic/QUICPacketProtectionKeyInfo.cc @@ -0,0 +1,368 @@ +/** @file + * + * QUIC Packet Protection Key Info + * + * @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 "QUICPacketProtectionKeyInfo.h" + +void +QUICPacketProtectionKeyInfo::set_context(Context ctx) +{ + this->_ctx = ctx; +} + +void +QUICPacketProtectionKeyInfo::drop_keys(QUICKeyPhase phase) +{ + int index = static_cast(phase); + + this->_is_client_key_available[index] = false; + this->_is_server_key_available[index] = false; + + memset(this->_client_key[index], 0x00, sizeof(this->_client_key[index])); + memset(this->_server_key[index], 0x00, sizeof(this->_server_key[index])); + + memset(this->_client_iv[index], 0x00, sizeof(this->_client_iv[index])); + memset(this->_server_iv[index], 0x00, sizeof(this->_server_iv[index])); + + this->_client_iv_len[index] = 0; + this->_server_iv_len[index] = 0; + + memset(this->_client_key_for_hp[index], 0x00, sizeof(this->_client_key_for_hp[index])); + memset(this->_server_key_for_hp[index], 0x00, sizeof(this->_server_key_for_hp[index])); +} + +const EVP_CIPHER * +QUICPacketProtectionKeyInfo::get_cipher(QUICKeyPhase phase) const +{ + switch (phase) { + case QUICKeyPhase::INITIAL: + return this->_cipher_initial; + default: + return this->_cipher; + } +} + +size_t +QUICPacketProtectionKeyInfo::get_tag_len(QUICKeyPhase phase) const +{ + switch (phase) { + case QUICKeyPhase::INITIAL: + return EVP_GCM_TLS_TAG_LEN; + default: + return this->_tag_len; + } +} + +bool +QUICPacketProtectionKeyInfo::is_encryption_key_available(QUICKeyPhase phase) const +{ + int index = static_cast(phase); + if (this->_ctx == Context::SERVER) { + return this->_is_server_key_available[index]; + } else { + return this->_is_client_key_available[index]; + } +} + +void +QUICPacketProtectionKeyInfo::set_encryption_key_available(QUICKeyPhase phase) +{ + int index = static_cast(phase); + if (this->_ctx == Context::SERVER) { + this->_is_server_key_available[index] = true; + } else { + this->_is_client_key_available[index] = true; + } +} + +const uint8_t * +QUICPacketProtectionKeyInfo::encryption_key(QUICKeyPhase phase) const +{ + int index = static_cast(phase); + if (this->_ctx == Context::SERVER) { + return this->_server_key[index]; + } else { + return this->_client_key[index]; + } +} + +uint8_t * +QUICPacketProtectionKeyInfo::encryption_key(QUICKeyPhase phase) +{ + return const_cast(const_cast(this)->encryption_key(phase)); +} + +size_t +QUICPacketProtectionKeyInfo::encryption_key_len(QUICKeyPhase phase) const +{ + const EVP_CIPHER *cipher; + + switch (phase) { + case QUICKeyPhase::INITIAL: + cipher = this->_cipher_initial; + break; + default: + cipher = this->_cipher; + break; + } + + return EVP_CIPHER_key_length(cipher); +} + +const uint8_t * +QUICPacketProtectionKeyInfo::encryption_iv(QUICKeyPhase phase) const +{ + int index = static_cast(phase); + if (this->_ctx == Context::SERVER) { + return this->_server_iv[index]; + } else { + return this->_client_iv[index]; + } +} + +uint8_t * +QUICPacketProtectionKeyInfo::encryption_iv(QUICKeyPhase phase) +{ + return const_cast(const_cast(this)->encryption_iv(phase)); +} + +const size_t * +QUICPacketProtectionKeyInfo::encryption_iv_len(QUICKeyPhase phase) const +{ + int index = static_cast(phase); + if (this->_ctx == Context::SERVER) { + return &this->_server_iv_len[index]; + } else { + return &this->_client_iv_len[index]; + } +} + +size_t * +QUICPacketProtectionKeyInfo::encryption_iv_len(QUICKeyPhase phase) +{ + return const_cast(const_cast(this)->encryption_iv_len(phase)); +} + +bool +QUICPacketProtectionKeyInfo::is_decryption_key_available(QUICKeyPhase phase) const +{ + int index = static_cast(phase); + if (this->_ctx == Context::SERVER) { + return this->_is_client_key_available[index]; + } else { + return this->_is_server_key_available[index]; + } +} + +void +QUICPacketProtectionKeyInfo::set_decryption_key_available(QUICKeyPhase phase) +{ + int index = static_cast(phase); + if (this->_ctx == Context::SERVER) { + this->_is_client_key_available[index] = true; + } else { + this->_is_server_key_available[index] = true; + ; + } +} + +const uint8_t * +QUICPacketProtectionKeyInfo::decryption_key(QUICKeyPhase phase) const +{ + int index = static_cast(phase); + if (this->_ctx == Context::SERVER) { + return this->_client_key[index]; + } else { + return this->_server_key[index]; + } +} + +uint8_t * +QUICPacketProtectionKeyInfo::decryption_key(QUICKeyPhase phase) +{ + return const_cast(const_cast(this)->decryption_key(phase)); +} + +size_t +QUICPacketProtectionKeyInfo::decryption_key_len(QUICKeyPhase phase) const +{ + const EVP_CIPHER *cipher; + + switch (phase) { + case QUICKeyPhase::INITIAL: + cipher = this->_cipher_initial; + break; + default: + cipher = this->_cipher; + break; + } + + return EVP_CIPHER_key_length(cipher); +} + +const uint8_t * +QUICPacketProtectionKeyInfo::decryption_iv(QUICKeyPhase phase) const +{ + int index = static_cast(phase); + if (this->_ctx == Context::SERVER) { + return this->_client_iv[index]; + } else { + return this->_server_iv[index]; + } +} + +uint8_t * +QUICPacketProtectionKeyInfo::decryption_iv(QUICKeyPhase phase) +{ + return const_cast(const_cast(this)->decryption_iv(phase)); +} + +const size_t * +QUICPacketProtectionKeyInfo::decryption_iv_len(QUICKeyPhase phase) const +{ + int index = static_cast(phase); + if (this->_ctx == Context::SERVER) { + return &this->_client_iv_len[index]; + } else { + return &this->_server_iv_len[index]; + } +} + +size_t * +QUICPacketProtectionKeyInfo::decryption_iv_len(QUICKeyPhase phase) +{ + return const_cast(const_cast(this)->decryption_iv_len(phase)); +} + +void +QUICPacketProtectionKeyInfo::set_cipher_initial(const EVP_CIPHER *cipher) +{ + this->_cipher_initial = cipher; +} + +void +QUICPacketProtectionKeyInfo::set_cipher(const EVP_CIPHER *cipher, size_t tag_len) +{ + this->_cipher = cipher; + this->_tag_len = tag_len; +} + +const EVP_CIPHER * +QUICPacketProtectionKeyInfo::get_cipher_for_hp(QUICKeyPhase phase) const +{ + switch (phase) { + case QUICKeyPhase::INITIAL: + return this->_cipher_for_hp_initial; + default: + return this->_cipher_for_hp; + } +} + +void +QUICPacketProtectionKeyInfo::set_cipher_for_hp_initial(const EVP_CIPHER *cipher) +{ + this->_cipher_for_hp_initial = cipher; +} + +void +QUICPacketProtectionKeyInfo::set_cipher_for_hp(const EVP_CIPHER *cipher) +{ + this->_cipher_for_hp = cipher; +} + +const uint8_t * +QUICPacketProtectionKeyInfo::encryption_key_for_hp(QUICKeyPhase phase) const +{ + int index = static_cast(phase); + if (this->_ctx == Context::SERVER) { + return this->_server_key_for_hp[index]; + } else { + return this->_client_key_for_hp[index]; + } +} + +uint8_t * +QUICPacketProtectionKeyInfo::encryption_key_for_hp(QUICKeyPhase phase) +{ + int index = static_cast(phase); + if (this->_ctx == Context::SERVER) { + return this->_server_key_for_hp[index]; + } else { + return this->_client_key_for_hp[index]; + } +} + +size_t +QUICPacketProtectionKeyInfo::encryption_key_for_hp_len(QUICKeyPhase phase) const +{ + const EVP_CIPHER *cipher; + + switch (phase) { + case QUICKeyPhase::INITIAL: + cipher = this->_cipher_for_hp_initial; + break; + default: + cipher = this->_cipher_for_hp; + break; + } + + return EVP_CIPHER_key_length(cipher); +} + +const uint8_t * +QUICPacketProtectionKeyInfo::decryption_key_for_hp(QUICKeyPhase phase) const +{ + int index = static_cast(phase); + if (this->_ctx == Context::SERVER) { + return this->_client_key_for_hp[index]; + } else { + return this->_server_key_for_hp[index]; + } +} + +uint8_t * +QUICPacketProtectionKeyInfo::decryption_key_for_hp(QUICKeyPhase phase) +{ + int index = static_cast(phase); + if (this->_ctx == Context::SERVER) { + return this->_client_key_for_hp[index]; + } else { + return this->_server_key_for_hp[index]; + } +} + +size_t +QUICPacketProtectionKeyInfo::decryption_key_for_hp_len(QUICKeyPhase phase) const +{ + const EVP_CIPHER *cipher; + + switch (phase) { + case QUICKeyPhase::INITIAL: + cipher = this->_cipher_for_hp_initial; + break; + default: + cipher = this->_cipher_for_hp; + break; + } + + return EVP_CIPHER_key_length(cipher); +} diff --git a/iocore/net/quic/QUICPacketProtectionKeyInfo.h b/iocore/net/quic/QUICPacketProtectionKeyInfo.h new file mode 100644 index 00000000000..32a38d41383 --- /dev/null +++ b/iocore/net/quic/QUICPacketProtectionKeyInfo.h @@ -0,0 +1,123 @@ +/** @file + * + * QUIC Packet Protection Key Info + * + * @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. + */ + +#pragma once + +#include "QUICTypes.h" +#include "QUICKeyGenerator.h" + +class QUICPacketProtectionKeyInfo +{ +public: + enum class Context { SERVER, CLIENT }; + + // FIXME This should be passed to the constructor but NetVC cannot pass it because it has set_context too. + void set_context(Context ctx); + + void drop_keys(QUICKeyPhase phase); + + // Payload Protection (common) + + virtual const EVP_CIPHER *get_cipher(QUICKeyPhase phase) const; + virtual size_t get_tag_len(QUICKeyPhase phase) const; + virtual void set_cipher_initial(const EVP_CIPHER *cipher); + virtual void set_cipher(const EVP_CIPHER *cipher, size_t tag_len); + + // Payload Protection (encryption) + + virtual bool is_encryption_key_available(QUICKeyPhase phase) const; + virtual void set_encryption_key_available(QUICKeyPhase phase); + + virtual const uint8_t *encryption_key(QUICKeyPhase phase) const; + virtual uint8_t *encryption_key(QUICKeyPhase phase); + + virtual size_t encryption_key_len(QUICKeyPhase phase) const; + + virtual const uint8_t *encryption_iv(QUICKeyPhase phase) const; + virtual uint8_t *encryption_iv(QUICKeyPhase phase); + + virtual const size_t *encryption_iv_len(QUICKeyPhase phase) const; + virtual size_t *encryption_iv_len(QUICKeyPhase phase); + + // Payload Protection (decryption) + + virtual bool is_decryption_key_available(QUICKeyPhase phase) const; + virtual void set_decryption_key_available(QUICKeyPhase phase); + + virtual const uint8_t *decryption_key(QUICKeyPhase phase) const; + virtual uint8_t *decryption_key(QUICKeyPhase phase); + + virtual size_t decryption_key_len(QUICKeyPhase phase) const; + + virtual const uint8_t *decryption_iv(QUICKeyPhase phase) const; + virtual uint8_t *decryption_iv(QUICKeyPhase phase); + + virtual const size_t *decryption_iv_len(QUICKeyPhase phase) const; + virtual size_t *decryption_iv_len(QUICKeyPhase phase); + + // Header Protection + + virtual const EVP_CIPHER *get_cipher_for_hp(QUICKeyPhase phase) const; + virtual void set_cipher_for_hp_initial(const EVP_CIPHER *cipher); + virtual void set_cipher_for_hp(const EVP_CIPHER *cipher); + + virtual const uint8_t *encryption_key_for_hp(QUICKeyPhase phase) const; + virtual uint8_t *encryption_key_for_hp(QUICKeyPhase phase); + + virtual size_t encryption_key_for_hp_len(QUICKeyPhase phase) const; + + virtual const uint8_t *decryption_key_for_hp(QUICKeyPhase phase) const; + virtual uint8_t *decryption_key_for_hp(QUICKeyPhase phase); + + virtual size_t decryption_key_for_hp_len(QUICKeyPhase phase) const; + +private: + Context _ctx = Context::SERVER; + + // Payload Protection + + const EVP_CIPHER *_cipher_initial = nullptr; + const EVP_CIPHER *_cipher = nullptr; + size_t _tag_len = 0; + + bool _is_client_key_available[5] = {false}; + bool _is_server_key_available[5] = {false}; + + // FIXME EVP_MAX_KEY_LENGTH and EVP_MAX_IV_LENGTH are not enough somehow + uint8_t _client_key[5][512]; + uint8_t _server_key[5][512]; + + uint8_t _client_iv[5][512]; + uint8_t _server_iv[5][512]; + + size_t _client_iv_len[5]; + size_t _server_iv_len[5]; + + // Header Protection + + const EVP_CIPHER *_cipher_for_hp_initial = nullptr; + const EVP_CIPHER *_cipher_for_hp = nullptr; + + uint8_t _client_key_for_hp[5][512]; + uint8_t _server_key_for_hp[5][512]; +}; diff --git a/iocore/net/quic/QUICPacketReceiveQueue.cc b/iocore/net/quic/QUICPacketReceiveQueue.cc new file mode 100644 index 00000000000..405c29533de --- /dev/null +++ b/iocore/net/quic/QUICPacketReceiveQueue.cc @@ -0,0 +1,225 @@ +/** @file + * + * A brief file description + * + * @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 "QUICPacketReceiveQueue.h" +#include "QUICPacketFactory.h" + +#include "QUICIntUtil.h" + +// FIXME: workaround for coalescing packets +static constexpr int LONG_HDR_OFFSET_CONNECTION_ID = 6; + +static bool +is_vn(QUICVersion v) +{ + return v == 0x0; +} + +static bool +long_hdr_pkt_len(size_t &pkt_len, uint8_t *buf, size_t len) +{ + uint8_t dcil, scil; + QUICPacketLongHeader::dcil(dcil, buf, len); + QUICPacketLongHeader::scil(scil, buf, len); + + size_t offset = LONG_HDR_OFFSET_CONNECTION_ID + dcil + scil; + + // token_length and token_length_field_len should be 0 except INITIAL packet + size_t token_length = 0; + uint8_t token_length_field_len = 0; + if (!QUICPacketLongHeader::token_length(token_length, &token_length_field_len, buf, len)) { + return false; + } + + size_t length = 0; + uint8_t length_field_len = 0; + if (!QUICPacketLongHeader::length(length, &length_field_len, buf, len)) { + return false; + } + + pkt_len = offset + token_length + token_length_field_len + length_field_len + length; + + return true; +} + +QUICPacketReceiveQueue::QUICPacketReceiveQueue(QUICPacketFactory &packet_factory, QUICPacketHeaderProtector &ph_protector) + : _packet_factory(packet_factory), _ph_protector(ph_protector) +{ +} + +void +QUICPacketReceiveQueue::enqueue(UDPPacket *packet) +{ + this->_queue.enqueue(packet); +} + +QUICPacketUPtr +QUICPacketReceiveQueue::dequeue(QUICPacketCreationResult &result) +{ + QUICPacketUPtr quic_packet = QUICPacketFactory::create_null_packet(); + UDPPacket *udp_packet = nullptr; + + // FIXME: avoid this copy + // Copy payload of UDP packet to this->_payload once + if (!this->_payload) { + udp_packet = this->_queue.dequeue(); + if (!udp_packet) { + result = QUICPacketCreationResult::NO_PACKET; + return quic_packet; + } + + // Create a QUIC packet + this->_udp_con = udp_packet->getConnection(); + this->_from = udp_packet->from; + this->_payload_len = udp_packet->getPktLength(); + this->_payload = ats_unique_malloc(this->_payload_len); + IOBufferBlock *b = udp_packet->getIOBlockChain(); + size_t written = 0; + while (b) { + memcpy(this->_payload.get() + written, b->buf(), b->read_avail()); + written += b->read_avail(); + b = b->next.get(); + } + } + + ats_unique_buf pkt = {nullptr}; + size_t pkt_len = 0; + QUICPacketType type = QUICPacketType::UNINITIALIZED; + + if (QUICInvariants::is_long_header(this->_payload.get())) { + uint8_t *buf = this->_payload.get() + this->_offset; + size_t remaining_len = this->_payload_len - this->_offset; + + if (QUICInvariants::is_long_header(buf)) { + QUICVersion version; + QUICPacketLongHeader::version(version, buf, remaining_len); + if (is_vn(version)) { + pkt_len = remaining_len; + type = QUICPacketType::VERSION_NEGOTIATION; + } else if (!QUICTypeUtil::is_supported_version(version)) { + result = QUICPacketCreationResult::UNSUPPORTED; + pkt_len = remaining_len; + } else { + QUICPacketLongHeader::type(type, this->_payload.get() + this->_offset, remaining_len); + if (type == QUICPacketType::RETRY) { + pkt_len = remaining_len; + } else { + if (!long_hdr_pkt_len(pkt_len, this->_payload.get() + this->_offset, remaining_len)) { + this->_payload.release(); + this->_payload = nullptr; + this->_payload_len = 0; + this->_offset = 0; + + result = QUICPacketCreationResult::IGNORED; + + return quic_packet; + } + } + } + } else { + pkt_len = remaining_len; + } + + if (pkt_len < this->_payload_len) { + pkt = ats_unique_malloc(pkt_len); + memcpy(pkt.get(), this->_payload.get() + this->_offset, pkt_len); + this->_offset += pkt_len; + + if (this->_offset >= this->_payload_len) { + this->_payload.release(); + this->_payload = nullptr; + this->_payload_len = 0; + this->_offset = 0; + } + } else { + pkt = std::move(this->_payload); + pkt_len = this->_payload_len; + this->_payload = nullptr; + this->_payload_len = 0; + this->_offset = 0; + } + } else { + if (!this->_packet_factory.is_ready_to_create_protected_packet() && udp_packet) { + this->enqueue(udp_packet); + this->_payload.release(); + this->_payload = nullptr; + this->_payload_len = 0; + this->_offset = 0; + result = QUICPacketCreationResult::NOT_READY; + return quic_packet; + } + pkt = std::move(this->_payload); + pkt_len = this->_payload_len; + this->_payload = nullptr; + this->_payload_len = 0; + this->_offset = 0; + type = QUICPacketType::PROTECTED; + } + + if (this->_ph_protector.unprotect(pkt.get(), pkt_len)) { + quic_packet = this->_packet_factory.create(this->_udp_con, this->_from, std::move(pkt), pkt_len, + this->_largest_received_packet_number, result); + } else { + // ZERO_RTT might be rejected + if (type == QUICPacketType::ZERO_RTT_PROTECTED) { + result = QUICPacketCreationResult::IGNORED; + } else { + result = QUICPacketCreationResult::FAILED; + } + } + + if (udp_packet) { + udp_packet->free(); + } + + switch (result) { + case QUICPacketCreationResult::NOT_READY: + // FIXME: unordered packet should be buffered and retried + if (this->_queue.size > 0) { + result = QUICPacketCreationResult::IGNORED; + } + + break; + case QUICPacketCreationResult::UNSUPPORTED: + // do nothing - if the packet is unsupported version, we don't know packet number + break; + default: + if (quic_packet && quic_packet->packet_number() > this->_largest_received_packet_number) { + this->_largest_received_packet_number = quic_packet->packet_number(); + } + } + + return quic_packet; +} + +uint32_t +QUICPacketReceiveQueue::size() +{ + return this->_queue.size; +} + +void +QUICPacketReceiveQueue::reset() +{ + this->_largest_received_packet_number = 0; +} diff --git a/iocore/net/quic/QUICPacketReceiveQueue.h b/iocore/net/quic/QUICPacketReceiveQueue.h new file mode 100644 index 00000000000..5027df37b9a --- /dev/null +++ b/iocore/net/quic/QUICPacketReceiveQueue.h @@ -0,0 +1,54 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include "I_UDPPacket.h" +#include "tscore/List.h" + +#include "QUICPacket.h" + +class QUICPacketFactory; + +class QUICPacketReceiveQueue +{ +public: + QUICPacketReceiveQueue(QUICPacketFactory &packet_factory, QUICPacketHeaderProtector &ph_protector); + + void enqueue(UDPPacket *packet); + QUICPacketUPtr dequeue(QUICPacketCreationResult &result); + uint32_t size(); + void reset(); + +private: + CountQueue _queue; + QUICPacketFactory &_packet_factory; + QUICPacketHeaderProtector &_ph_protector; + QUICPacketNumber _largest_received_packet_number = 0; + // FIXME: workaround code for coalescing packets + ats_unique_buf _payload = {nullptr}; + size_t _payload_len = 0; + size_t _offset = 0; + UDPConnection *_udp_con; + IpEndpoint _from; +}; diff --git a/iocore/net/quic/QUICPathValidator.cc b/iocore/net/quic/QUICPathValidator.cc new file mode 100644 index 00000000000..40a23904886 --- /dev/null +++ b/iocore/net/quic/QUICPathValidator.cc @@ -0,0 +1,169 @@ +/** @file + + A brief file description + + @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 +#include "QUICPathValidator.h" + +bool +QUICPathValidator::is_validating() +{ + return this->_state == ValidationState::VALIDATING; +} + +bool +QUICPathValidator::is_validated() +{ + return this->_state == ValidationState::VALIDATED; +} + +void +QUICPathValidator::validate() +{ + if (this->_state == ValidationState::VALIDATING) { + // Do nothing + } else { + this->_state = ValidationState::VALIDATING; + this->_generate_challenge(); + } +} + +void +QUICPathValidator::_generate_challenge() +{ + size_t seed = + std::chrono::duration_cast(std::chrono::high_resolution_clock::now().time_since_epoch()).count(); + std::minstd_rand random(seed); + + for (auto &i : this->_outgoing_challenge) { + i = random(); + } + this->_has_outgoing_challenge = 3; +} + +void +QUICPathValidator::_generate_response(const QUICPathChallengeFrame &frame) +{ + memcpy(this->_incoming_challenge, frame.data(), QUICPathChallengeFrame::DATA_LEN); + this->_has_outgoing_response = true; +} + +QUICConnectionErrorUPtr +QUICPathValidator::_validate_response(const QUICPathResponseFrame &frame) +{ + QUICConnectionErrorUPtr error = std::make_unique(QUICTransErrorCode::PROTOCOL_VIOLATION); + + for (int i = 0; i < 3; ++i) { + if (memcmp(this->_outgoing_challenge + (QUICPathChallengeFrame::DATA_LEN * i), frame.data(), + QUICPathChallengeFrame::DATA_LEN) == 0) { + this->_state = ValidationState::VALIDATED; + this->_has_outgoing_challenge = 0; + error = nullptr; + break; + } + } + + return error; +} + +// +// QUICFrameHandler +// +std::vector +QUICPathValidator::interests() +{ + return {QUICFrameType::PATH_CHALLENGE, QUICFrameType::PATH_RESPONSE}; +} + +QUICConnectionErrorUPtr +QUICPathValidator::handle_frame(QUICEncryptionLevel level, const QUICFrame &frame) +{ + QUICConnectionErrorUPtr error = nullptr; + + switch (frame.type()) { + case QUICFrameType::PATH_CHALLENGE: + this->_generate_response(static_cast(frame)); + break; + case QUICFrameType::PATH_RESPONSE: + error = this->_validate_response(static_cast(frame)); + break; + default: + ink_assert(!"Can't happen"); + } + + return error; +} + +// +// QUICFrameGenerator +// +bool +QUICPathValidator::will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) +{ + if (!this->_is_level_matched(level)) { + return false; + } + + if (this->_last_sent_at == timestamp) { + return false; + } + + return (this->_has_outgoing_challenge || this->_has_outgoing_response); +} + +/** + * @param connection_credit This is not used. Because PATH_CHALLENGE and PATH_RESPONSE frame are not flow-controlled + */ +QUICFrame * +QUICPathValidator::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t /* connection_credit */, + uint16_t maximum_frame_size, ink_hrtime timestamp) +{ + QUICFrame *frame = nullptr; + + if (!this->_is_level_matched(level)) { + return frame; + } + + if (this->_has_outgoing_response) { + frame = QUICFrameFactory::create_path_response_frame(buf, this->_incoming_challenge); + if (frame && frame->size() > maximum_frame_size) { + // Cancel generating frame + frame = nullptr; + } else { + this->_has_outgoing_response = false; + } + } else if (this->_has_outgoing_challenge) { + frame = QUICFrameFactory::create_path_challenge_frame( + buf, this->_outgoing_challenge + (QUICPathChallengeFrame::DATA_LEN * (this->_has_outgoing_challenge - 1))); + if (frame && frame->size() > maximum_frame_size) { + // Cancel generating frame + frame = nullptr; + } else { + --this->_has_outgoing_challenge; + ink_assert(this->_has_outgoing_challenge >= 0); + } + } + + this->_last_sent_at = timestamp; + + return frame; +} diff --git a/iocore/net/quic/QUICPathValidator.h b/iocore/net/quic/QUICPathValidator.h new file mode 100644 index 00000000000..47a2975d108 --- /dev/null +++ b/iocore/net/quic/QUICPathValidator.h @@ -0,0 +1,64 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include +#include "QUICTypes.h" +#include "QUICFrameHandler.h" +#include "QUICFrameGenerator.h" + +class QUICPathValidator : public QUICFrameHandler, public QUICFrameGenerator +{ +public: + QUICPathValidator() {} + bool is_validating(); + bool is_validated(); + void validate(); + + // QUICFrameHandler + std::vector interests() override; + QUICConnectionErrorUPtr handle_frame(QUICEncryptionLevel level, const QUICFrame &frame) override; + + // QUICFrameGeneratro + bool will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) override; + QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size, + ink_hrtime timestamp) override; + +private: + enum class ValidationState : int { + NOT_VALIDATED, + VALIDATING, + VALIDATED, + }; + ValidationState _state = ValidationState::NOT_VALIDATED; + int _has_outgoing_challenge = 0; + bool _has_outgoing_response = false; + ink_hrtime _last_sent_at = 0; + uint8_t _incoming_challenge[QUICPathChallengeFrame::DATA_LEN]; + uint8_t _outgoing_challenge[QUICPathChallengeFrame::DATA_LEN * 3]; + + void _generate_challenge(); + void _generate_response(const QUICPathChallengeFrame &frame); + QUICConnectionErrorUPtr _validate_response(const QUICPathResponseFrame &frame); +}; diff --git a/iocore/net/quic/QUICPinger.cc b/iocore/net/quic/QUICPinger.cc new file mode 100644 index 00000000000..e6f69c69d5d --- /dev/null +++ b/iocore/net/quic/QUICPinger.cc @@ -0,0 +1,77 @@ +/** @file + + A brief file description + + @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 "QUICPinger.h" + +void +QUICPinger::request(QUICEncryptionLevel level) +{ + if (!this->_is_level_matched(level)) { + return; + } + ++this->_need_to_fire[static_cast(level)]; +} + +void +QUICPinger::cancel(QUICEncryptionLevel level) +{ + if (!this->_is_level_matched(level)) { + return; + } + + if (this->_need_to_fire[static_cast(level)] > 0) { + --this->_need_to_fire[static_cast(level)]; + } +} + +bool +QUICPinger::will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) +{ + if (!this->_is_level_matched(level)) { + return false; + } + + return this->_need_to_fire[static_cast(QUICTypeUtil::pn_space(level))] > 0; +} + +/** + * @param connection_credit This is not used. Because PING frame is not flow-controlled + */ +QUICFrame * +QUICPinger::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t /* connection_credit */, uint16_t maximum_frame_size, + ink_hrtime timestamp) +{ + QUICFrame *frame = nullptr; + + if (!this->_is_level_matched(level)) { + return frame; + } + + if (this->_need_to_fire[static_cast(level)] > 0 && maximum_frame_size > 0) { + // don't care ping frame lost or acked + frame = QUICFrameFactory::create_ping_frame(buf, 0, nullptr); + this->_need_to_fire[static_cast(level)] = 0; + } + + return frame; +} diff --git a/iocore/net/quic/QUICPinger.h b/iocore/net/quic/QUICPinger.h new file mode 100644 index 00000000000..efa59a375d9 --- /dev/null +++ b/iocore/net/quic/QUICPinger.h @@ -0,0 +1,47 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include +#include "QUICTypes.h" +#include "QUICFrameHandler.h" +#include "QUICFrameGenerator.h" + +class QUICPinger : public QUICFrameGenerator +{ +public: + QUICPinger() {} + + void request(QUICEncryptionLevel level); + void cancel(QUICEncryptionLevel level); + + // QUICFrameGenerator + bool will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) override; + QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size, + ink_hrtime timestamp) override; + +private: + // Initial, 0/1-RTT, and Handshake + uint64_t _need_to_fire[4] = {0}; +}; diff --git a/iocore/net/quic/QUICStats.h b/iocore/net/quic/QUICStats.h new file mode 100644 index 00000000000..51dcff2623c --- /dev/null +++ b/iocore/net/quic/QUICStats.h @@ -0,0 +1,43 @@ +/** @file + * + * QUIC Stats + * + * @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. + */ + +#pragma once + +#include "records/I_RecProcess.h" + +extern RecRawStatBlock *quic_rsb; + +enum class QUICStats { + total_packets_sent_stat, + count, +}; + +#define QUIC_INCREMENT_DYN_STAT(x) RecIncrRawStat(quic_rsb, nullptr, (int)x, 1) +#define QUIC_DECREMENT_DYN_STAT(x) RecIncrRawStat(quic_rsb, nullptr, (int)x, -1) +#define QUIC_SET_COUNT_DYN_STAT(x, count) RecSetRawStatCount(quic_rsb, x, count) +#define QUIC_INCREMENT_DYN_STAT_EX(x, y) RecIncrRawStat(quic_rsb, nullptr, (int)x, y) +#define QUIC_CLEAR_DYN_STAT(x) \ + do { \ + RecSetRawStatSum(quic_rsb, (x), 0); \ + RecSetRawStatCount(quic_rsb, (x), 0); \ + } while (0) diff --git a/iocore/net/quic/QUICStream.cc b/iocore/net/quic/QUICStream.cc new file mode 100644 index 00000000000..334b9f41bca --- /dev/null +++ b/iocore/net/quic/QUICStream.cc @@ -0,0 +1,323 @@ +/** @file + * + * A brief file description + * + * @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 "QUICStream.h" + +#include "QUICStreamManager.h" + +constexpr uint32_t MAX_STREAM_FRAME_OVERHEAD = 24; + +QUICStream::QUICStream(QUICConnectionInfoProvider *cinfo, QUICStreamId sid) : _connection_info(cinfo), _id(sid) {} + +QUICStream::~QUICStream() {} + +QUICStreamId +QUICStream::id() const +{ + return this->_id; +} + +QUICStreamDirection +QUICStream::direction() const +{ + return QUICTypeUtil::detect_stream_direction(this->_id, this->_connection_info->direction()); +} + +const QUICConnectionInfoProvider * +QUICStream::connection_info() const +{ + return this->_connection_info; +} + +bool +QUICStream::is_bidirectional() const +{ + return ((this->_id & 0x03) < 0x02); +} + +QUICOffset +QUICStream::final_offset() const +{ + // TODO Return final offset + return 0; +} + +QUICOffset +QUICStream::reordered_bytes() const +{ + return this->_reordered_bytes; +} + +QUICConnectionErrorUPtr +QUICStream::recv(const QUICStreamFrame &frame) +{ + return nullptr; +} + +QUICConnectionErrorUPtr +QUICStream::recv(const QUICMaxStreamDataFrame &frame) +{ + return nullptr; +} + +QUICConnectionErrorUPtr +QUICStream::recv(const QUICStreamDataBlockedFrame &frame) +{ + return nullptr; +} + +QUICConnectionErrorUPtr +QUICStream::recv(const QUICStopSendingFrame &frame) +{ + return nullptr; +} + +QUICConnectionErrorUPtr +QUICStream::recv(const QUICRstStreamFrame &frame) +{ + return nullptr; +} + +QUICConnectionErrorUPtr +QUICStream::recv(const QUICCryptoFrame &frame) +{ + return nullptr; +} + +void +QUICStream::_records_stream_frame(QUICEncryptionLevel level, const QUICStreamFrame &frame) +{ + QUICFrameInformationUPtr info = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc()); + info->type = frame.type(); + info->level = level; + StreamFrameInfo *frame_info = reinterpret_cast(info->data); + frame_info->stream_id = frame.stream_id(); + frame_info->offset = frame.offset(); + frame_info->has_fin = frame.has_fin_flag(); + frame_info->block = frame.data(); + this->_records_frame(frame.id(), std::move(info)); +} + +void +QUICStream::_records_rst_stream_frame(QUICEncryptionLevel level, const QUICRstStreamFrame &frame) +{ + QUICFrameInformationUPtr info = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc()); + info->type = frame.type(); + info->level = level; + RstStreamFrameInfo *frame_info = reinterpret_cast(info->data); + frame_info->error_code = frame.error_code(); + frame_info->final_offset = frame.final_offset(); + this->_records_frame(frame.id(), std::move(info)); +} + +void +QUICStream::_records_stop_sending_frame(QUICEncryptionLevel level, const QUICStopSendingFrame &frame) +{ + QUICFrameInformationUPtr info = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc()); + info->type = frame.type(); + info->level = level; + StopSendingFrameInfo *frame_info = reinterpret_cast(info->data); + frame_info->error_code = frame.error_code(); + this->_records_frame(frame.id(), std::move(info)); +} + +void +QUICStream::_records_crypto_frame(QUICEncryptionLevel level, const QUICCryptoFrame &frame) +{ + QUICFrameInformationUPtr info = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc()); + info->type = QUICFrameType::CRYPTO; + info->level = level; + CryptoFrameInfo *crypto_frame_info = reinterpret_cast(info->data); + crypto_frame_info->offset = frame.offset(); + crypto_frame_info->block = frame.data(); + this->_records_frame(frame.id(), std::move(info)); +} + +void +QUICStream::reset(QUICStreamErrorUPtr error) +{ +} + +void +QUICStream::stop_sending(QUICStreamErrorUPtr error) +{ +} + +QUICOffset +QUICStream::largest_offset_received() const +{ + return 0; +} + +QUICOffset +QUICStream::largest_offset_sent() const +{ + return 0; +} + +void +QUICStream::on_eos() +{ +} + +void +QUICStream::on_read() +{ +} + +// +// QUICStreamVConnection +// +QUICStreamVConnection::~QUICStreamVConnection() +{ + if (this->_read_event) { + this->_read_event->cancel(); + this->_read_event = nullptr; + } + + if (this->_write_event) { + this->_write_event->cancel(); + this->_write_event = nullptr; + } +} + +void +QUICStreamVConnection::_write_to_read_vio(QUICOffset offset, const uint8_t *data, uint64_t data_length, bool fin) +{ + SCOPED_MUTEX_LOCK(lock, this->_read_vio.mutex, this_ethread()); + + uint64_t bytes_added = this->_read_vio.buffer.writer()->write(data, data_length); + + // Until receive FIN flag, keep nbytes INT64_MAX + if (fin && bytes_added == data_length) { + this->_read_vio.nbytes = offset + data_length; + } +} + +/** + * Replace existing event only if the new event is different than the inprogress event + */ +Event * +QUICStreamVConnection::_send_tracked_event(Event *event, int send_event, VIO *vio) +{ + if (event != nullptr) { + if (event->callback_event != send_event) { + event->cancel(); + event = nullptr; + } + } + + if (event == nullptr) { + event = this_ethread()->schedule_imm(this, send_event, vio); + } + + return event; +} + +/** + * @brief Signal event to this->_read_vio.cont + */ +void +QUICStreamVConnection::_signal_read_event() +{ + if (this->_read_vio.cont == nullptr || this->_read_vio.op == VIO::NONE) { + return; + } + MUTEX_TRY_LOCK(lock, this->_read_vio.mutex, this_ethread()); + + int event = this->_read_vio.ntodo() ? VC_EVENT_READ_READY : VC_EVENT_READ_COMPLETE; + + if (lock.is_locked()) { + this->_read_vio.cont->handleEvent(event, &this->_read_vio); + } else { + this_ethread()->schedule_imm(this->_read_vio.cont, event, &this->_read_vio); + } +} + +/** + * @brief Signal event to this->_write_vio.cont + */ +void +QUICStreamVConnection::_signal_write_event() +{ + if (this->_write_vio.cont == nullptr || this->_write_vio.op == VIO::NONE) { + return; + } + MUTEX_TRY_LOCK(lock, this->_write_vio.mutex, this_ethread()); + + int event = this->_write_vio.ntodo() ? VC_EVENT_WRITE_READY : VC_EVENT_WRITE_COMPLETE; + + if (lock.is_locked()) { + this->_write_vio.cont->handleEvent(event, &this->_write_vio); + } else { + this_ethread()->schedule_imm(this->_write_vio.cont, event, &this->_write_vio); + } +} + +/** + * @brief Signal event to this->_write_vio.cont + */ +void +QUICStreamVConnection::_signal_read_eos_event() +{ + if (this->_read_vio.cont == nullptr || this->_read_vio.op == VIO::NONE) { + return; + } + MUTEX_TRY_LOCK(lock, this->_read_vio.mutex, this_ethread()); + + int event = VC_EVENT_EOS; + + if (lock.is_locked()) { + this->_write_vio.cont->handleEvent(event, &this->_write_vio); + } else { + this_ethread()->schedule_imm(this->_read_vio.cont, event, &this->_read_vio); + } +} + +int64_t +QUICStreamVConnection::_process_read_vio() +{ + if (this->_read_vio.cont == nullptr || this->_read_vio.op == VIO::NONE) { + return 0; + } + + // Pass through. Read operation is done by QUICStream::recv(const std::shared_ptr frame) + // TODO: 1. pop frame from _received_stream_frame_buffer + // 2. write data to _read_vio + + return 0; +} + +/** + * @brief Send STREAM DATA from _response_buffer + * @detail Call _signal_write_event() to indicate event upper layer + */ +int64_t +QUICStreamVConnection::_process_write_vio() +{ + if (this->_write_vio.cont == nullptr || this->_write_vio.op == VIO::NONE) { + return 0; + } + + return 0; +} diff --git a/iocore/net/quic/QUICStream.h b/iocore/net/quic/QUICStream.h new file mode 100644 index 00000000000..bff660a0b6d --- /dev/null +++ b/iocore/net/quic/QUICStream.h @@ -0,0 +1,134 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include "tscore/List.h" + +#include "P_VConnection.h" +#include "I_Event.h" + +#include "QUICFrame.h" +#include "QUICStreamState.h" +#include "QUICFlowController.h" +#include "QUICIncomingFrameBuffer.h" +#include "QUICFrameGenerator.h" +#include "QUICConnection.h" +#include "QUICFrameRetransmitter.h" +#include "QUICDebugNames.h" + +/** + * @brief QUIC Stream + * TODO: This is similar to Http2Stream. Need to think some integration. + */ +class QUICStream : public QUICFrameGenerator, public QUICFrameRetransmitter +{ +public: + QUICStream() {} + QUICStream(QUICConnectionInfoProvider *cinfo, QUICStreamId sid); + virtual ~QUICStream(); + + QUICStreamId id() const; + QUICStreamDirection direction() const; + const QUICConnectionInfoProvider *connection_info() const; + bool is_bidirectional() const; + QUICOffset final_offset() const; + + /* + * QUICApplication need to call one of these functions when it process VC_EVENT_* + */ + virtual void on_read(); + virtual void on_eos(); + + virtual QUICConnectionErrorUPtr recv(const QUICStreamFrame &frame); + virtual QUICConnectionErrorUPtr recv(const QUICMaxStreamDataFrame &frame); + virtual QUICConnectionErrorUPtr recv(const QUICStreamDataBlockedFrame &frame); + virtual QUICConnectionErrorUPtr recv(const QUICStopSendingFrame &frame); + virtual QUICConnectionErrorUPtr recv(const QUICRstStreamFrame &frame); + virtual QUICConnectionErrorUPtr recv(const QUICCryptoFrame &frame); + + QUICOffset reordered_bytes() const; + virtual QUICOffset largest_offset_received() const; + virtual QUICOffset largest_offset_sent() const; + + virtual void stop_sending(QUICStreamErrorUPtr error); + virtual void reset(QUICStreamErrorUPtr error); + + LINK(QUICStream, link); + +protected: + QUICConnectionInfoProvider *_connection_info = nullptr; + QUICStreamId _id = 0; + QUICOffset _send_offset = 0; + QUICOffset _reordered_bytes = 0; + + void _records_rst_stream_frame(QUICEncryptionLevel level, const QUICRstStreamFrame &frame); + void _records_stream_frame(QUICEncryptionLevel level, const QUICStreamFrame &frame); + void _records_stop_sending_frame(QUICEncryptionLevel level, const QUICStopSendingFrame &frame); + void _records_crypto_frame(QUICEncryptionLevel level, const QUICCryptoFrame &frame); +}; + +// This is VConnection class for VIO operation. +class QUICStreamVConnection : public VConnection, public QUICStream +{ +public: + QUICStreamVConnection(QUICConnectionInfoProvider *cinfo, QUICStreamId sid) : VConnection(nullptr), QUICStream(cinfo, sid) + { + mutex = new_ProxyMutex(); + } + + QUICStreamVConnection() : VConnection(nullptr) {} + virtual ~QUICStreamVConnection(); + + LINK(QUICStreamVConnection, link); + +protected: + virtual int64_t _process_read_vio(); + virtual int64_t _process_write_vio(); + void _signal_read_event(); + void _signal_write_event(); + void _signal_read_eos_event(); + Event *_send_tracked_event(Event *, int, VIO *); + + void _write_to_read_vio(QUICOffset offset, const uint8_t *data, uint64_t data_length, bool fin); + + VIO _read_vio; + VIO _write_vio; + + Event *_read_event = nullptr; + Event *_write_event = nullptr; +}; + +#define QUICStreamDebug(fmt, ...) \ + Debug("quic_stream", "[%s] [%" PRIu64 "] [%s] " fmt, this->_connection_info->cids().data(), this->_id, \ + QUICDebugNames::stream_state(this->_state.get()), ##__VA_ARGS__) + +#define QUICVStreamDebug(fmt, ...) \ + Debug("v_quic_stream", "[%s] [%" PRIu64 "] [%s] " fmt, this->_connection_info->cids().data(), this->_id, \ + QUICDebugNames::stream_state(this->_state.get()), ##__VA_ARGS__) + +#define QUICStreamFCDebug(fmt, ...) \ + Debug("quic_flow_ctrl", "[%s] [%" PRIu64 "] [%s] " fmt, this->_connection_info->cids().data(), this->_id, \ + QUICDebugNames::stream_state(this->_state.get()), ##__VA_ARGS__) + +extern const uint32_t MAX_STREAM_FRAME_OVERHEAD; diff --git a/iocore/net/quic/QUICStreamFactory.cc b/iocore/net/quic/QUICStreamFactory.cc new file mode 100644 index 00000000000..9762895eb78 --- /dev/null +++ b/iocore/net/quic/QUICStreamFactory.cc @@ -0,0 +1,84 @@ +/** @file + * + * A brief file description + * + * @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 "QUICStream.h" +#include "QUICBidirectionalStream.h" +#include "QUICUnidirectionalStream.h" +#include "QUICStreamFactory.h" + +ClassAllocator quicBidiStreamAllocator("quicBidiStreamAllocator"); +ClassAllocator quicSendStreamAllocator("quicSendStreamAllocator"); +ClassAllocator quicReceiveStreamAllocator("quicReceiveStreamAllocator"); + +QUICStreamVConnection * +QUICStreamFactory::create(QUICStreamId sid, uint64_t local_max_stream_data, uint64_t remote_max_stream_data) +{ + QUICStreamVConnection *stream = nullptr; + switch (QUICTypeUtil::detect_stream_direction(sid, this->_info->direction())) { + case QUICStreamDirection::BIDIRECTIONAL: + // TODO Free the stream somewhere + stream = THREAD_ALLOC(quicBidiStreamAllocator, this_ethread()); + new (stream) QUICBidirectionalStream(this->_rtt_provider, this->_info, sid, local_max_stream_data, remote_max_stream_data); + break; + case QUICStreamDirection::SEND: + // TODO Free the stream somewhere + stream = THREAD_ALLOC(quicSendStreamAllocator, this_ethread()); + new (stream) QUICSendStream(this->_info, sid, remote_max_stream_data); + break; + case QUICStreamDirection::RECEIVE: + // server side + // TODO Free the stream somewhere + stream = THREAD_ALLOC(quicReceiveStreamAllocator, this_ethread()); + new (stream) QUICReceiveStream(this->_rtt_provider, this->_info, sid, local_max_stream_data); + break; + default: + ink_assert(false); + break; + } + + return stream; +} + +void +QUICStreamFactory::delete_stream(QUICStreamVConnection *stream) +{ + if (!stream) { + return; + } + + stream->~QUICStreamVConnection(); + switch (stream->direction()) { + case QUICStreamDirection::BIDIRECTIONAL: + THREAD_FREE(static_cast(stream), quicBidiStreamAllocator, this_thread()); + break; + case QUICStreamDirection::SEND: + THREAD_FREE(static_cast(stream), quicSendStreamAllocator, this_thread()); + break; + case QUICStreamDirection::RECEIVE: + THREAD_FREE(static_cast(stream), quicReceiveStreamAllocator, this_thread()); + break; + default: + ink_assert(false); + break; + } +} diff --git a/iocore/net/quic/QUICStreamFactory.h b/iocore/net/quic/QUICStreamFactory.h new file mode 100644 index 00000000000..fd497bf2b1a --- /dev/null +++ b/iocore/net/quic/QUICStreamFactory.h @@ -0,0 +1,46 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include "QUICTypes.h" + +class QUICStreamVConnection; + +// PS: this class function should not static because of THREAD_ALLOC and THREAD_FREE +class QUICStreamFactory +{ +public: + QUICStreamFactory(QUICRTTProvider *rtt_provider, QUICConnectionInfoProvider *info) : _rtt_provider(rtt_provider), _info(info) {} + ~QUICStreamFactory() {} + + // create a bidistream, send only stream or receive only stream + QUICStreamVConnection *create(QUICStreamId sid, uint64_t recv_max_stream_data, uint64_t send_max_stream_data); + + // delete stream by stream type + void delete_stream(QUICStreamVConnection *stream); + +private: + QUICRTTProvider *_rtt_provider = nullptr; + QUICConnectionInfoProvider *_info = nullptr; +}; diff --git a/iocore/net/quic/QUICStreamManager.cc b/iocore/net/quic/QUICStreamManager.cc new file mode 100644 index 00000000000..91edd1b7c8b --- /dev/null +++ b/iocore/net/quic/QUICStreamManager.cc @@ -0,0 +1,470 @@ +/** @file + * + * A brief file description + * + * @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 "QUICStreamManager.h" + +#include "QUICApplication.h" +#include "QUICTransportParameters.h" + +static constexpr char tag[] = "quic_stream_manager"; +static constexpr QUICStreamId QUIC_STREAM_TYPES = 4; + +ClassAllocator quicStreamManagerAllocator("quicStreamManagerAllocator"); + +QUICStreamManager::QUICStreamManager(QUICConnectionInfoProvider *info, QUICRTTProvider *rtt_provider, QUICApplicationMap *app_map) + : _stream_factory(rtt_provider, info), _info(info), _app_map(app_map) +{ + if (this->_info->direction() == NET_VCONNECTION_OUT) { + this->_next_stream_id_bidi = static_cast(QUICStreamType::CLIENT_BIDI); + this->_next_stream_id_uni = static_cast(QUICStreamType::CLIENT_UNI); + } else { + this->_next_stream_id_bidi = static_cast(QUICStreamType::SERVER_BIDI); + this->_next_stream_id_uni = static_cast(QUICStreamType::SERVER_UNI); + } +} + +std::vector +QUICStreamManager::interests() +{ + return { + QUICFrameType::STREAM, QUICFrameType::RESET_STREAM, QUICFrameType::STOP_SENDING, + QUICFrameType::MAX_STREAM_DATA, QUICFrameType::MAX_STREAMS, + }; +} + +void +QUICStreamManager::init_flow_control_params(const std::shared_ptr &local_tp, + const std::shared_ptr &remote_tp) +{ + this->_local_tp = local_tp; + this->_remote_tp = remote_tp; + + if (this->_local_tp) { + this->_local_max_streams_bidi = this->_local_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAMS_BIDI); + this->_local_max_streams_uni = this->_local_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAMS_UNI); + } + if (this->_remote_tp) { + this->_remote_max_streams_bidi = this->_remote_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAMS_BIDI); + this->_remote_max_streams_uni = this->_remote_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAMS_UNI); + } +} + +void +QUICStreamManager::set_max_streams_bidi(uint64_t max_streams) +{ + if (this->_local_max_streams_bidi <= max_streams) { + this->_local_max_streams_bidi = max_streams; + } +} + +void +QUICStreamManager::set_max_streams_uni(uint64_t max_streams) +{ + if (this->_local_max_streams_uni <= max_streams) { + this->_local_max_streams_uni = max_streams; + } +} + +QUICConnectionErrorUPtr +QUICStreamManager::create_stream(QUICStreamId stream_id) +{ + // TODO: check stream_id + QUICConnectionErrorUPtr error = nullptr; + QUICStreamVConnection *stream_vc = this->_find_or_create_stream_vc(stream_id); + if (!stream_vc) { + return std::make_unique(QUICTransErrorCode::STREAM_ID_ERROR); + } + + QUICApplication *application = this->_app_map->get(stream_id); + + if (!application->is_stream_set(stream_vc)) { + application->set_stream(stream_vc); + } + + return error; +} + +QUICConnectionErrorUPtr +QUICStreamManager::create_uni_stream(QUICStreamId &new_stream_id) +{ + QUICConnectionErrorUPtr error = this->create_stream(this->_next_stream_id_uni); + if (error == nullptr) { + new_stream_id = this->_next_stream_id_uni; + this->_next_stream_id_uni += QUIC_STREAM_TYPES; + } + + return error; +} + +QUICConnectionErrorUPtr +QUICStreamManager::create_bidi_stream(QUICStreamId &new_stream_id) +{ + QUICConnectionErrorUPtr error = this->create_stream(this->_next_stream_id_bidi); + if (error == nullptr) { + new_stream_id = this->_next_stream_id_bidi; + this->_next_stream_id_bidi += QUIC_STREAM_TYPES; + } + + return error; +} + +void +QUICStreamManager::reset_stream(QUICStreamId stream_id, QUICStreamErrorUPtr error) +{ + auto stream = this->_find_stream_vc(stream_id); + stream->reset(std::move(error)); +} + +QUICConnectionErrorUPtr +QUICStreamManager::handle_frame(QUICEncryptionLevel level, const QUICFrame &frame) +{ + QUICConnectionErrorUPtr error = nullptr; + + switch (frame.type()) { + case QUICFrameType::MAX_STREAM_DATA: + error = this->_handle_frame(static_cast(frame)); + break; + case QUICFrameType::STREAM_DATA_BLOCKED: + // STREAM_DATA_BLOCKED frame is for debugging. Just propagate to streams + error = this->_handle_frame(static_cast(frame)); + break; + case QUICFrameType::STREAM: + error = this->_handle_frame(static_cast(frame)); + break; + case QUICFrameType::STOP_SENDING: + error = this->_handle_frame(static_cast(frame)); + break; + case QUICFrameType::RESET_STREAM: + error = this->_handle_frame(static_cast(frame)); + break; + case QUICFrameType::MAX_STREAMS: + error = this->_handle_frame(static_cast(frame)); + break; + default: + Debug(tag, "Unexpected frame type: %02x", static_cast(frame.type())); + ink_assert(false); + break; + } + + return error; +} + +QUICConnectionErrorUPtr +QUICStreamManager::_handle_frame(const QUICMaxStreamDataFrame &frame) +{ + QUICStreamVConnection *stream = this->_find_or_create_stream_vc(frame.stream_id()); + if (stream) { + return stream->recv(frame); + } else { + return std::make_unique(QUICTransErrorCode::STREAM_ID_ERROR); + } +} + +QUICConnectionErrorUPtr +QUICStreamManager::_handle_frame(const QUICStreamDataBlockedFrame &frame) +{ + QUICStreamVConnection *stream = this->_find_or_create_stream_vc(frame.stream_id()); + if (stream) { + return stream->recv(frame); + } else { + return std::make_unique(QUICTransErrorCode::STREAM_ID_ERROR); + } +} + +QUICConnectionErrorUPtr +QUICStreamManager::_handle_frame(const QUICStreamFrame &frame) +{ + QUICStreamVConnection *stream = this->_find_or_create_stream_vc(frame.stream_id()); + if (!stream) { + return std::make_unique(QUICTransErrorCode::STREAM_ID_ERROR); + } + + QUICApplication *application = this->_app_map->get(frame.stream_id()); + + if (application && !application->is_stream_set(stream)) { + application->set_stream(stream); + } + + return stream->recv(frame); +} + +QUICConnectionErrorUPtr +QUICStreamManager::_handle_frame(const QUICRstStreamFrame &frame) +{ + QUICStream *stream = this->_find_or_create_stream_vc(frame.stream_id()); + if (stream) { + return stream->recv(frame); + } else { + return std::make_unique(QUICTransErrorCode::STREAM_ID_ERROR); + } +} + +QUICConnectionErrorUPtr +QUICStreamManager::_handle_frame(const QUICStopSendingFrame &frame) +{ + QUICStream *stream = this->_find_or_create_stream_vc(frame.stream_id()); + if (stream) { + return stream->recv(frame); + } else { + return std::make_unique(QUICTransErrorCode::STREAM_ID_ERROR); + } +} + +QUICConnectionErrorUPtr +QUICStreamManager::_handle_frame(const QUICMaxStreamsFrame &frame) +{ + QUICStreamType type = QUICTypeUtil::detect_stream_type(frame.maximum_streams()); + if (type == QUICStreamType::SERVER_BIDI || type == QUICStreamType::CLIENT_BIDI) { + this->_remote_max_streams_bidi = frame.maximum_streams(); + } else { + this->_remote_max_streams_uni = frame.maximum_streams(); + } + return nullptr; +} + +QUICStreamVConnection * +QUICStreamManager::_find_stream_vc(QUICStreamId id) +{ + for (QUICStreamVConnection *s = this->stream_list.head; s; s = s->link.next) { + if (s->id() == id) { + return s; + } + } + return nullptr; +} + +QUICStreamVConnection * +QUICStreamManager::_find_or_create_stream_vc(QUICStreamId stream_id) +{ + QUICStreamVConnection *stream = this->_find_stream_vc(stream_id); + if (!stream) { + if (!this->_local_tp) { + return nullptr; + } + + ink_assert(this->_local_tp); + ink_assert(this->_remote_tp); + + uint64_t local_max_stream_data = 0; + uint64_t remote_max_stream_data = 0; + + switch (QUICTypeUtil::detect_stream_type(stream_id)) { + case QUICStreamType::CLIENT_BIDI: + if (this->_info->direction() == NET_VCONNECTION_OUT) { + // client + if (this->_remote_max_streams_bidi == 0 || stream_id > this->_remote_max_streams_bidi) { + return nullptr; + } + + local_max_stream_data = this->_local_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_LOCAL); + remote_max_stream_data = this->_remote_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_REMOTE); + } else { + // server + if (this->_local_max_streams_bidi == 0 || stream_id > this->_local_max_streams_bidi) { + return nullptr; + } + + local_max_stream_data = this->_local_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_REMOTE); + remote_max_stream_data = this->_remote_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_LOCAL); + } + + break; + case QUICStreamType::CLIENT_UNI: + if (this->_info->direction() == NET_VCONNECTION_OUT) { + // client + if (this->_remote_max_streams_uni == 0 || stream_id > this->_remote_max_streams_uni) { + return nullptr; + } + } else { + // server + if (this->_local_max_streams_uni == 0 || stream_id > this->_local_max_streams_uni) { + return nullptr; + } + } + + local_max_stream_data = this->_local_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_UNI); + remote_max_stream_data = this->_remote_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_UNI); + + break; + case QUICStreamType::SERVER_BIDI: + if (this->_info->direction() == NET_VCONNECTION_OUT) { + // client + if (this->_local_max_streams_bidi == 0 || stream_id > this->_local_max_streams_bidi) { + return nullptr; + } + + local_max_stream_data = this->_local_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_REMOTE); + remote_max_stream_data = this->_remote_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_LOCAL); + } else { + // server + if (this->_remote_max_streams_bidi == 0 || stream_id > this->_remote_max_streams_bidi) { + return nullptr; + } + + local_max_stream_data = this->_local_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_LOCAL); + remote_max_stream_data = this->_remote_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_REMOTE); + } + break; + case QUICStreamType::SERVER_UNI: + if (this->_info->direction() == NET_VCONNECTION_OUT) { + if (this->_local_max_streams_uni == 0 || stream_id > this->_local_max_streams_uni) { + return nullptr; + } + } else { + if (this->_remote_max_streams_uni == 0 || stream_id > this->_remote_max_streams_uni) { + return nullptr; + } + } + + local_max_stream_data = this->_local_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_UNI); + remote_max_stream_data = this->_remote_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_UNI); + + break; + default: + ink_release_assert(false); + break; + } + + stream = this->_stream_factory.create(stream_id, local_max_stream_data, remote_max_stream_data); + ink_assert(stream != nullptr); + this->stream_list.push(stream); + } + + return stream; +} + +uint64_t +QUICStreamManager::total_reordered_bytes() const +{ + uint64_t total_bytes = 0; + + // FIXME Iterating all (open + closed) streams is expensive + for (QUICStreamVConnection *s = this->stream_list.head; s; s = s->link.next) { + total_bytes += s->reordered_bytes(); + } + return total_bytes; +} + +uint64_t +QUICStreamManager::total_offset_received() const +{ + uint64_t total_offset_received = 0; + + // FIXME Iterating all (open + closed) streams is expensive + for (QUICStreamVConnection *s = this->stream_list.head; s; s = s->link.next) { + total_offset_received += s->largest_offset_received(); + } + return total_offset_received; +} + +uint64_t +QUICStreamManager::total_offset_sent() const +{ + return this->_total_offset_sent; +} + +void +QUICStreamManager::_add_total_offset_sent(uint32_t sent_byte) +{ + // FIXME: use atomic increment + this->_total_offset_sent += sent_byte; +} + +uint32_t +QUICStreamManager::stream_count() const +{ + uint32_t count = 0; + for (QUICStreamVConnection *s = this->stream_list.head; s; s = s->link.next) { + ++count; + } + return count; +} + +void +QUICStreamManager::set_default_application(QUICApplication *app) +{ + this->_app_map->set_default(app); +} + +bool +QUICStreamManager::will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) +{ + if (!this->_is_level_matched(level)) { + return false; + } + + // workaround fix until support 0-RTT on client + if (level == QUICEncryptionLevel::ZERO_RTT) { + return false; + } + + for (QUICStreamVConnection *s = this->stream_list.head; s; s = s->link.next) { + if (s->will_generate_frame(level, timestamp)) { + return true; + } + } + + return false; +} + +QUICFrame * +QUICStreamManager::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size, + ink_hrtime timestamp) +{ + QUICFrame *frame = nullptr; + + if (!this->_is_level_matched(level)) { + return frame; + } + + // workaround fix until support 0-RTT on client + if (level == QUICEncryptionLevel::ZERO_RTT) { + return frame; + } + + // FIXME We should pick a stream based on priority + for (QUICStreamVConnection *s = this->stream_list.head; s; s = s->link.next) { + frame = s->generate_frame(buf, level, connection_credit, maximum_frame_size, timestamp); + if (frame) { + break; + } + } + + if (frame != nullptr && frame->type() == QUICFrameType::STREAM) { + this->_add_total_offset_sent(static_cast(frame)->data_length()); + } + + return frame; +} + +bool +QUICStreamManager::_is_level_matched(QUICEncryptionLevel level) +{ + for (auto l : this->_encryption_level_filter) { + if (l == level) { + return true; + } + } + + return false; +} diff --git a/iocore/net/quic/QUICStreamManager.h b/iocore/net/quic/QUICStreamManager.h new file mode 100644 index 00000000000..fbdc49d78fd --- /dev/null +++ b/iocore/net/quic/QUICStreamManager.h @@ -0,0 +1,101 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include "QUICTypes.h" +#include "QUICBidirectionalStream.h" +#include "QUICUnidirectionalStream.h" +#include "QUICApplicationMap.h" +#include "QUICFrameHandler.h" +#include "QUICFrame.h" +#include "QUICStreamFactory.h" +#include "QUICLossDetector.h" + +class QUICTransportParameters; + +class QUICStreamManager : public QUICFrameHandler, public QUICFrameGenerator +{ +public: + QUICStreamManager() : _stream_factory(nullptr, nullptr){}; + QUICStreamManager(QUICConnectionInfoProvider *info, QUICRTTProvider *rtt_provider, QUICApplicationMap *app_map); + + void init_flow_control_params(const std::shared_ptr &local_tp, + const std::shared_ptr &remote_tp); + void set_max_streams_bidi(uint64_t max_streams); + void set_max_streams_uni(uint64_t max_streams); + uint64_t total_reordered_bytes() const; + uint64_t total_offset_received() const; + uint64_t total_offset_sent() const; + + uint32_t stream_count() const; + QUICConnectionErrorUPtr create_stream(QUICStreamId stream_id); + QUICConnectionErrorUPtr create_uni_stream(QUICStreamId &new_stream_id); + QUICConnectionErrorUPtr create_bidi_stream(QUICStreamId &new_stream_id); + void reset_stream(QUICStreamId stream_id, QUICStreamErrorUPtr error); + + void set_default_application(QUICApplication *app); + + DLL stream_list; + + // QUICFrameHandler + virtual std::vector interests() override; + virtual QUICConnectionErrorUPtr handle_frame(QUICEncryptionLevel level, const QUICFrame &frame) override; + + // QUICFrameGenerator + bool will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) override; + QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size, + ink_hrtime timestamp) override; + +protected: + virtual bool _is_level_matched(QUICEncryptionLevel level) override; + +private: + QUICStreamVConnection *_find_stream_vc(QUICStreamId id); + QUICStreamVConnection *_find_or_create_stream_vc(QUICStreamId stream_id); + void _add_total_offset_sent(uint32_t sent_byte); + QUICConnectionErrorUPtr _handle_frame(const QUICStreamFrame &frame); + QUICConnectionErrorUPtr _handle_frame(const QUICRstStreamFrame &frame); + QUICConnectionErrorUPtr _handle_frame(const QUICStopSendingFrame &frame); + QUICConnectionErrorUPtr _handle_frame(const QUICMaxStreamDataFrame &frame); + QUICConnectionErrorUPtr _handle_frame(const QUICStreamDataBlockedFrame &frame); + QUICConnectionErrorUPtr _handle_frame(const QUICMaxStreamsFrame &frame); + + QUICStreamFactory _stream_factory; + + QUICConnectionInfoProvider *_info = nullptr; + QUICApplicationMap *_app_map = nullptr; + std::shared_ptr _local_tp = nullptr; + std::shared_ptr _remote_tp = nullptr; + QUICStreamId _local_max_streams_bidi = 0; + QUICStreamId _local_max_streams_uni = 0; + QUICStreamId _remote_max_streams_bidi = 0; + QUICStreamId _remote_max_streams_uni = 0; + QUICStreamId _next_stream_id_uni = 0; + QUICStreamId _next_stream_id_bidi = 0; + uint64_t _total_offset_sent = 0; + std::array _encryption_level_filter = { + QUICEncryptionLevel::ZERO_RTT, + QUICEncryptionLevel::ONE_RTT, + }; +}; diff --git a/iocore/net/quic/QUICStreamState.cc b/iocore/net/quic/QUICStreamState.cc new file mode 100644 index 00000000000..d88348dcc17 --- /dev/null +++ b/iocore/net/quic/QUICStreamState.cc @@ -0,0 +1,438 @@ +/** @file + * + * A brief file description + * + * @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 "QUICStreamState.h" +#include "tscore/ink_assert.h" + +// ---------QUICReceiveStreamState ----------- + +bool +QUICReceiveStreamStateMachine::is_allowed_to_send(const QUICFrame &frame) const +{ + return this->is_allowed_to_send(frame.type()); +} + +bool +QUICReceiveStreamStateMachine::is_allowed_to_send(QUICFrameType type) const +{ + if (type != QUICFrameType::STOP_SENDING && type != QUICFrameType::MAX_STREAM_DATA) { + return false; + } + + QUICReceiveStreamState state = this->get(); + // The receiver only sends MAX_STREAM_DATA in the “Recv” state. + if (type == QUICFrameType::MAX_STREAM_DATA && state == QUICReceiveStreamState::Recv) { + return true; + } + + // A receiver can send STOP_SENDING in any state where it has not received a RESET_STREAM frame; that is states other than “Reset + // Recvd” or “Reset Read”. + if (type == QUICFrameType::STOP_SENDING && state != QUICReceiveStreamState::ResetRecvd && + state != QUICReceiveStreamState::ResetRead) { + return true; + } + + return false; +} + +bool +QUICReceiveStreamStateMachine::is_allowed_to_receive(const QUICFrame &frame) const +{ + return this->is_allowed_to_receive(frame.type()); +} + +bool +QUICReceiveStreamStateMachine::is_allowed_to_receive(QUICFrameType type) const +{ + // always allow receive these frames. + if (type == QUICFrameType::STREAM || type == QUICFrameType::STREAM_DATA_BLOCKED || type == QUICFrameType::RESET_STREAM) { + return true; + } + + return false; +} + +void +QUICReceiveStreamStateMachine::update_with_sending_frame(const QUICFrame &frame) +{ +} + +void +QUICReceiveStreamStateMachine::update_with_receiving_frame(const QUICFrame &frame) +{ + // The receiving part of a stream initiated by a peer (types 1 and 3 for a client, or 0 and 2 for a server) is created when the + // first STREAM, STREAM_DATA_BLOCKED, or RESET_STREAM is received for that stream. + QUICReceiveStreamState state = this->get(); + QUICFrameType type = frame.type(); + + if (state == QUICReceiveStreamState::Init && + (type == QUICFrameType::STREAM || type == QUICFrameType::STREAM_DATA_BLOCKED || type == QUICFrameType::RESET_STREAM)) { + this->_set_state(QUICReceiveStreamState::Recv); + } + + switch (this->get()) { + case QUICReceiveStreamState::Recv: + if (type == QUICFrameType::STREAM) { + if (static_cast(frame).has_fin_flag()) { + this->_set_state(QUICReceiveStreamState::SizeKnown); + if (this->_in_progress->is_transfer_complete()) { + this->_set_state(QUICReceiveStreamState::DataRecvd); + } + } + } else if (type == QUICFrameType::RESET_STREAM) { + this->_set_state(QUICReceiveStreamState::ResetRecvd); + } + break; + case QUICReceiveStreamState::SizeKnown: + if (type == QUICFrameType::STREAM && this->_in_progress->is_transfer_complete()) { + this->_set_state(QUICReceiveStreamState::DataRecvd); + } else if (type == QUICFrameType::RESET_STREAM) { + this->_set_state(QUICReceiveStreamState::ResetRecvd); + } + break; + case QUICReceiveStreamState::DataRecvd: + if (type == QUICFrameType::STREAM && this->_in_progress->is_transfer_complete()) { + this->_set_state(QUICReceiveStreamState::ResetRecvd); + } + break; + case QUICReceiveStreamState::Init: + case QUICReceiveStreamState::ResetRecvd: + case QUICReceiveStreamState::DataRead: + case QUICReceiveStreamState::ResetRead: + break; + default: + ink_assert(!"Unknown state"); + break; + } +} + +void +QUICReceiveStreamStateMachine::update_on_read() +{ + if (this->_in_progress->is_transfer_complete()) { + this->_set_state(QUICReceiveStreamState::DataRead); + } +} + +void +QUICReceiveStreamStateMachine::update_on_eos() +{ + this->_set_state(QUICReceiveStreamState::ResetRead); +} + +void +QUICReceiveStreamStateMachine::update(const QUICSendStreamState state) +{ + // The receiving part of a stream enters the “Recv” state when the sending part of a bidirectional stream initiated by the + // endpoint (type 0 for a client, type 1 for a server) enters the “Ready” state. + switch (this->get()) { + case QUICReceiveStreamState::Init: + if (state == QUICSendStreamState::Ready) { + this->_set_state(QUICReceiveStreamState::Recv); + } + break; + default: + break; + } +} + +// ---------- QUICSendStreamState ------------- + +bool +QUICSendStreamStateMachine::is_allowed_to_send(const QUICFrame &frame) const +{ + return this->is_allowed_to_send(frame.type()); +} + +bool +QUICSendStreamStateMachine::is_allowed_to_send(QUICFrameType type) const +{ + if (type != QUICFrameType::STREAM && type != QUICFrameType::STREAM_DATA_BLOCKED && type != QUICFrameType::RESET_STREAM) { + return false; + } + + switch (this->get()) { + case QUICSendStreamState::Ready: + if (type == QUICFrameType::STREAM || type == QUICFrameType::STREAM_DATA_BLOCKED || type == QUICFrameType::RESET_STREAM) { + return true; + } + break; + case QUICSendStreamState::Send: + if (type == QUICFrameType::STREAM || type == QUICFrameType::STREAM_DATA_BLOCKED || type == QUICFrameType::RESET_STREAM) { + return true; + } + break; + case QUICSendStreamState::DataSent: + if (type == QUICFrameType::RESET_STREAM) { + return true; + } + break; + // A sender MUST NOT send any of these frames from a terminal state (“Data Recvd” or “Reset Recvd”). + case QUICSendStreamState::DataRecvd: + case QUICSendStreamState::ResetRecvd: + break; + // A sender MUST NOT send STREAM or STREAM_DATA_BLOCKED after sending a RESET_STREAM; that is, in the terminal states and in the + // “Reset Sent” state. + case QUICSendStreamState::ResetSent: + if (type == QUICFrameType::RESET_STREAM) { + return true; + } + break; + default: + ink_assert("This shouuldn't be happen"); + break; + } + + return false; +} + +bool +QUICSendStreamStateMachine::is_allowed_to_receive(const QUICFrame &frame) const +{ + return this->is_allowed_to_receive(frame.type()); +} + +bool +QUICSendStreamStateMachine::is_allowed_to_receive(QUICFrameType type) const +{ + if (type != QUICFrameType::STOP_SENDING && type != QUICFrameType::MAX_STREAM_DATA) { + return false; + } + + // A sender could receive either of these two frames(MAX_STREAM_DATA and STOP_SENDING) in any state as a result of delayed + // delivery of packets. + // PS: Because we need to reply a RESET_STREAM frame. STOP_SENDING frame is accpeted in all states. But we + // don't need to do anything for MAX_STREAM_DATA frame when we are in terminal state. + if (type == QUICFrameType::STOP_SENDING) { + return true; + } + + switch (this->get()) { + case QUICSendStreamState::Ready: + case QUICSendStreamState::Send: + if (type == QUICFrameType::MAX_STREAM_DATA) { + return true; + } + break; + // "MAX_STREAM_DATA frames might be received until the peer receives the final stream offset. The endpoint can safely ignore + // any MAX_STREAM_DATA frames it receives from its peer for a stream in this state." + case QUICSendStreamState::DataSent: + case QUICSendStreamState::ResetSent: + case QUICSendStreamState::DataRecvd: + case QUICSendStreamState::ResetRecvd: + if (type == QUICFrameType::MAX_STREAM_DATA) { + return true; + } + break; + default: + break; + } + + return false; +} + +void +QUICSendStreamStateMachine::update_with_sending_frame(const QUICFrame &frame) +{ + QUICSendStreamState state = this->get(); + QUICFrameType type = frame.type(); + if (state == QUICSendStreamState::Ready && + (type == QUICFrameType::STREAM || type == QUICFrameType::STREAM_DATA_BLOCKED || type == QUICFrameType::RESET_STREAM)) { + this->_set_state(QUICSendStreamState::Send); + } + + switch (this->get()) { + case QUICSendStreamState::Send: + if (type == QUICFrameType::STREAM) { + if (static_cast(frame).has_fin_flag()) { + this->_set_state(QUICSendStreamState::DataSent); + } + } else if (type == QUICFrameType::RESET_STREAM) { + this->_set_state(QUICSendStreamState::ResetSent); + } + break; + case QUICSendStreamState::DataSent: + if (type == QUICFrameType::RESET_STREAM) { + this->_set_state(QUICSendStreamState::ResetSent); + } + break; + case QUICSendStreamState::Init: + case QUICSendStreamState::Ready: + case QUICSendStreamState::DataRecvd: + case QUICSendStreamState::ResetSent: + case QUICSendStreamState::ResetRecvd: + break; + default: + ink_assert(!"Unknown state"); + break; + } +} + +void +QUICSendStreamStateMachine::update_with_receiving_frame(const QUICFrame &frame) +{ +} + +void +QUICSendStreamStateMachine::update_on_ack() +{ + if (this->_out_progress->is_transfer_complete()) { + this->_set_state(QUICSendStreamState::DataRecvd); + } else if (this->_out_progress->is_cancelled()) { + this->_set_state(QUICSendStreamState::ResetRecvd); + } +} + +void +QUICSendStreamStateMachine::update(const QUICReceiveStreamState state) +{ + // The sending part of a bidirectional stream initiated by a peer (type 0 for a server, type 1 for a client) enters the “Ready” + // state then immediately transitions to the “Send” state if the receiving part enters the “Recv” state (Section 3.2). + switch (this->get()) { + case QUICSendStreamState::Ready: + if (state == QUICReceiveStreamState::Recv) { + this->_set_state(QUICSendStreamState::Send); + } + break; + default: + break; + } +} + +// ---------QUICBidirectionalStreamState ----------- + +QUICBidirectionalStreamState +QUICBidirectionalStreamStateMachine::get() const +{ + QUICSendStreamState s_state = this->_send_stream_state.get(); + QUICReceiveStreamState r_state = this->_recv_stream_state.get(); + + if (s_state == QUICSendStreamState::Ready || r_state == QUICReceiveStreamState::Init) { + return QUICBidirectionalStreamState::Idle; + } else if (s_state == QUICSendStreamState::Ready || s_state == QUICSendStreamState::Send || + s_state == QUICSendStreamState::DataSent) { + if (r_state == QUICReceiveStreamState::Recv || r_state == QUICReceiveStreamState::SizeKnown) { + return QUICBidirectionalStreamState::Open; + } else if (r_state == QUICReceiveStreamState::DataRecvd || r_state == QUICReceiveStreamState::DataRead) { + return QUICBidirectionalStreamState::HC_R; + } else if (r_state == QUICReceiveStreamState::ResetRecvd || r_state == QUICReceiveStreamState::ResetRead) { + return QUICBidirectionalStreamState::HC_R; + } else { + ink_assert(false); + return QUICBidirectionalStreamState::Invalid; + } + } else if (s_state == QUICSendStreamState::DataRecvd) { + if (r_state == QUICReceiveStreamState::Recv || r_state == QUICReceiveStreamState::SizeKnown) { + return QUICBidirectionalStreamState::HC_L; + } else if (r_state == QUICReceiveStreamState::DataRecvd || r_state == QUICReceiveStreamState::DataRead) { + return QUICBidirectionalStreamState::Closed; + } else if (r_state == QUICReceiveStreamState::ResetRecvd || r_state == QUICReceiveStreamState::ResetRead) { + return QUICBidirectionalStreamState::Closed; + } else { + ink_assert(false); + return QUICBidirectionalStreamState::Invalid; + } + } else if (s_state == QUICSendStreamState::ResetSent || s_state == QUICSendStreamState::ResetRecvd) { + if (r_state == QUICReceiveStreamState::Recv || r_state == QUICReceiveStreamState::SizeKnown) { + return QUICBidirectionalStreamState::HC_L; + } else if (r_state == QUICReceiveStreamState::DataRecvd || r_state == QUICReceiveStreamState::DataRead) { + return QUICBidirectionalStreamState::Closed; + } else if (r_state == QUICReceiveStreamState::ResetRecvd || r_state == QUICReceiveStreamState::ResetRead) { + return QUICBidirectionalStreamState::Closed; + } else { + ink_assert(false); + return QUICBidirectionalStreamState::Invalid; + } + } else { + ink_assert(false); + return QUICBidirectionalStreamState::Invalid; + } +} + +void +QUICBidirectionalStreamStateMachine::update_with_sending_frame(const QUICFrame &frame) +{ + // The receiving part of a stream enters the “Recv” state when the sending part of a bidirectional stream initiated by the + // endpoint (type 0 for a client, type 1 for a server) enters the “Ready” state. + this->_send_stream_state.update_with_sending_frame(frame); + // PS: It should not happen because we initialize the send side and read side together. And the SendState has the default state + // "Ready". But to obey the specs, we do this as follow. + if (this->_send_stream_state.get() == QUICSendStreamState::Ready && + this->_recv_stream_state.get() == QUICReceiveStreamState::Init) { + this->_recv_stream_state.update(this->_send_stream_state.get()); + } +} + +void +QUICBidirectionalStreamStateMachine::update_with_receiving_frame(const QUICFrame &frame) +{ + // The sending part of a bidirectional stream initiated by a peer (type 0 for a server, type 1 for a client) enters the “Ready” + // state then immediately transitions to the “Send” state if the receiving part enters the “Recv” state (Section 3.2). + this->_recv_stream_state.update_with_receiving_frame(frame); + if (this->_send_stream_state.get() == QUICSendStreamState::Ready && + this->_recv_stream_state.get() == QUICReceiveStreamState::Recv) { + this->_send_stream_state.update(this->_recv_stream_state.get()); + } +} + +void +QUICBidirectionalStreamStateMachine::update_on_ack() +{ + this->_send_stream_state.update_on_ack(); +} + +void +QUICBidirectionalStreamStateMachine::update_on_read() +{ + this->_recv_stream_state.update_on_read(); +} + +void +QUICBidirectionalStreamStateMachine::update_on_eos() +{ + this->_recv_stream_state.update_on_eos(); +} + +bool +QUICBidirectionalStreamStateMachine::is_allowed_to_send(const QUICFrame &frame) const +{ + return this->is_allowed_to_send(frame.type()); +} + +bool +QUICBidirectionalStreamStateMachine::is_allowed_to_send(QUICFrameType type) const +{ + return this->_send_stream_state.is_allowed_to_send(type) || this->_recv_stream_state.is_allowed_to_send(type); +} + +bool +QUICBidirectionalStreamStateMachine::is_allowed_to_receive(const QUICFrame &frame) const +{ + return this->is_allowed_to_receive(frame.type()); +} + +bool +QUICBidirectionalStreamStateMachine::is_allowed_to_receive(QUICFrameType type) const +{ + return this->_send_stream_state.is_allowed_to_receive(type) || this->_recv_stream_state.is_allowed_to_receive(type); +} diff --git a/iocore/net/quic/QUICStreamState.h b/iocore/net/quic/QUICStreamState.h new file mode 100644 index 00000000000..f95bfc04712 --- /dev/null +++ b/iocore/net/quic/QUICStreamState.h @@ -0,0 +1,172 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include "QUICFrame.h" +#include "QUICTransferProgressProvider.h" + +enum class QUICSendStreamState { + Init, + Ready, + Send, + DataSent, + DataRecvd, + ResetSent, + ResetRecvd, +}; + +enum class QUICReceiveStreamState { + Init, + Recv, + SizeKnown, + DataRecvd, + ResetRecvd, + DataRead, + ResetRead, +}; + +enum class QUICBidirectionalStreamState { + Init, + Idle, + Open, + HC_R, + HC_L, + Closed, + Invalid, +}; + +template class QUICStreamStateMachine +{ +public: + virtual ~QUICStreamStateMachine() {} + + virtual T + get() const + { + return this->_state; + } + + virtual void update_with_sending_frame(const QUICFrame &frame) = 0; + virtual void update_with_receiving_frame(const QUICFrame &frame) = 0; + + virtual bool is_allowed_to_send(QUICFrameType type) const = 0; + virtual bool is_allowed_to_send(const QUICFrame &frame) const = 0; + virtual bool is_allowed_to_receive(QUICFrameType type) const = 0; + virtual bool is_allowed_to_receive(const QUICFrame &frame) const = 0; + +protected: + void + _set_state(T s) + { + ink_assert(s != T::Init); + this->_state = s; + } + +private: + T _state = T::Init; +}; + +class QUICUnidirectionalStreamStateMachine +{ +public: + QUICUnidirectionalStreamStateMachine(QUICTransferProgressProvider *in, QUICTransferProgressProvider *out) + : _in_progress(in), _out_progress(out) + { + } + +protected: + QUICTransferProgressProvider *_in_progress = nullptr; + QUICTransferProgressProvider *_out_progress = nullptr; +}; + +class QUICSendStreamStateMachine : public QUICUnidirectionalStreamStateMachine, public QUICStreamStateMachine +{ +public: + QUICSendStreamStateMachine(QUICTransferProgressProvider *in, QUICTransferProgressProvider *out) + : QUICUnidirectionalStreamStateMachine(in, out) + { + this->_set_state(QUICSendStreamState::Ready); + } + + void update_with_sending_frame(const QUICFrame &frame) override; + void update_with_receiving_frame(const QUICFrame &frame) override; + void update_on_ack(); + + bool is_allowed_to_send(QUICFrameType type) const override; + bool is_allowed_to_send(const QUICFrame &frame) const override; + bool is_allowed_to_receive(QUICFrameType type) const override; + bool is_allowed_to_receive(const QUICFrame &frame) const override; + + void update(const QUICReceiveStreamState opposite_side); +}; + +class QUICReceiveStreamStateMachine : public QUICUnidirectionalStreamStateMachine, + public QUICStreamStateMachine +{ +public: + QUICReceiveStreamStateMachine(QUICTransferProgressProvider *in, QUICTransferProgressProvider *out) + : QUICUnidirectionalStreamStateMachine(in, out) + { + } + + void update_with_sending_frame(const QUICFrame &frame) override; + void update_with_receiving_frame(const QUICFrame &frame) override; + void update_on_read(); + void update_on_eos(); + + bool is_allowed_to_send(QUICFrameType type) const override; + bool is_allowed_to_send(const QUICFrame &frame) const override; + bool is_allowed_to_receive(QUICFrameType type) const override; + bool is_allowed_to_receive(const QUICFrame &frame) const override; + + void update(const QUICSendStreamState opposite_side); +}; + +class QUICBidirectionalStreamStateMachine : public QUICStreamStateMachine +{ +public: + QUICBidirectionalStreamStateMachine(QUICTransferProgressProvider *send_in, QUICTransferProgressProvider *send_out, + QUICTransferProgressProvider *recv_in, QUICTransferProgressProvider *recv_out) + : _send_stream_state(send_in, send_out), _recv_stream_state(recv_in, recv_out) + { + this->_recv_stream_state.update(this->_send_stream_state.get()); + }; + + QUICBidirectionalStreamState get() const override; + + void update_with_sending_frame(const QUICFrame &frame) override; + void update_with_receiving_frame(const QUICFrame &frame) override; + void update_on_ack(); + void update_on_read(); + void update_on_eos(); + + bool is_allowed_to_send(QUICFrameType type) const override; + bool is_allowed_to_send(const QUICFrame &frame) const override; + bool is_allowed_to_receive(QUICFrameType type) const override; + bool is_allowed_to_receive(const QUICFrame &frame) const override; + +private: + QUICSendStreamStateMachine _send_stream_state; + QUICReceiveStreamStateMachine _recv_stream_state; +}; diff --git a/iocore/net/quic/QUICTLS.cc b/iocore/net/quic/QUICTLS.cc new file mode 100644 index 00000000000..5736ef46191 --- /dev/null +++ b/iocore/net/quic/QUICTLS.cc @@ -0,0 +1,229 @@ +/** @file + * + * QUIC Handshake Protocol (TLS to Secure QUIC) + * + * @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 "QUICTLS.h" + +#include +#include +#include + +#include "QUICPacketProtectionKeyInfo.h" +#include "QUICDebugNames.h" + +constexpr static char tag[] = "quic_tls"; + +SSL * +QUICTLS::ssl_handle() +{ + return this->_ssl; +} + +std::shared_ptr +QUICTLS::local_transport_parameters() +{ + return this->_local_transport_parameters; +} + +std::shared_ptr +QUICTLS::remote_transport_parameters() +{ + return this->_remote_transport_parameters; +} + +void +QUICTLS::set_local_transport_parameters(std::shared_ptr tp) +{ + this->_local_transport_parameters = tp; +} + +void +QUICTLS::set_remote_transport_parameters(std::shared_ptr tp) +{ + this->_remote_transport_parameters = tp; +} + +const char * +QUICTLS::session_file() const +{ + return this->_session_file; +} + +const char * +QUICTLS::keylog_file() const +{ + return this->_keylog_file; +} + +QUICTLS::~QUICTLS() +{ + SSL_free(this->_ssl); +} + +void +QUICTLS::reset() +{ + SSL_clear(this->_ssl); +} + +uint16_t +QUICTLS::convert_to_quic_trans_error_code(uint8_t alert) +{ + return 0x100 | alert; +} + +bool +QUICTLS::is_handshake_finished() const +{ + return SSL_is_init_finished(this->_ssl); +} + +bool +QUICTLS::is_ready_to_derive() const +{ + if (this->_netvc_context == NET_VCONNECTION_IN) { + return SSL_get_current_cipher(this->_ssl) != nullptr; + } else { + return this->is_handshake_finished(); + } +} + +int +QUICTLS::initialize_key_materials(QUICConnectionId cid) +{ + this->_pp_key_info.set_cipher_initial(EVP_aes_128_gcm()); + this->_pp_key_info.set_cipher_for_hp_initial(EVP_aes_128_ecb()); + + // Generate keys + Debug(tag, "Generating %s keys", QUICDebugNames::key_phase(QUICKeyPhase::INITIAL)); + + uint8_t *client_key_for_hp; + uint8_t *client_key; + uint8_t *client_iv; + size_t client_key_for_hp_len; + size_t client_key_len; + size_t *client_iv_len; + + uint8_t *server_key_for_hp; + uint8_t *server_key; + uint8_t *server_iv; + size_t server_key_for_hp_len; + size_t server_key_len; + size_t *server_iv_len; + + if (this->_netvc_context == NET_VCONNECTION_IN) { + client_key_for_hp = this->_pp_key_info.decryption_key_for_hp(QUICKeyPhase::INITIAL); + client_key_for_hp_len = this->_pp_key_info.decryption_key_for_hp_len(QUICKeyPhase::INITIAL); + client_key = this->_pp_key_info.decryption_key(QUICKeyPhase::INITIAL); + client_key_len = this->_pp_key_info.decryption_key_len(QUICKeyPhase::INITIAL); + client_iv = this->_pp_key_info.decryption_iv(QUICKeyPhase::INITIAL); + client_iv_len = this->_pp_key_info.decryption_iv_len(QUICKeyPhase::INITIAL); + server_key_for_hp = this->_pp_key_info.encryption_key_for_hp(QUICKeyPhase::INITIAL); + server_key_for_hp_len = this->_pp_key_info.encryption_key_for_hp_len(QUICKeyPhase::INITIAL); + server_key = this->_pp_key_info.encryption_key(QUICKeyPhase::INITIAL); + server_key_len = this->_pp_key_info.encryption_key_len(QUICKeyPhase::INITIAL); + server_iv = this->_pp_key_info.encryption_iv(QUICKeyPhase::INITIAL); + server_iv_len = this->_pp_key_info.encryption_iv_len(QUICKeyPhase::INITIAL); + } else { + client_key_for_hp = this->_pp_key_info.encryption_key_for_hp(QUICKeyPhase::INITIAL); + client_key_for_hp_len = this->_pp_key_info.encryption_key_for_hp_len(QUICKeyPhase::INITIAL); + client_key = this->_pp_key_info.encryption_key(QUICKeyPhase::INITIAL); + client_key_len = this->_pp_key_info.encryption_key_len(QUICKeyPhase::INITIAL); + client_iv = this->_pp_key_info.encryption_iv(QUICKeyPhase::INITIAL); + client_iv_len = this->_pp_key_info.encryption_iv_len(QUICKeyPhase::INITIAL); + server_key_for_hp = this->_pp_key_info.decryption_key_for_hp(QUICKeyPhase::INITIAL); + server_key_for_hp_len = this->_pp_key_info.decryption_key_for_hp_len(QUICKeyPhase::INITIAL); + server_key = this->_pp_key_info.decryption_key(QUICKeyPhase::INITIAL); + server_key_len = this->_pp_key_info.decryption_key_len(QUICKeyPhase::INITIAL); + server_iv = this->_pp_key_info.decryption_iv(QUICKeyPhase::INITIAL); + server_iv_len = this->_pp_key_info.decryption_iv_len(QUICKeyPhase::INITIAL); + } + + this->_keygen_for_client.generate(client_key_for_hp, client_key, client_iv, client_iv_len, cid); + this->_keygen_for_server.generate(server_key_for_hp, server_key, server_iv, server_iv_len, cid); + + this->_pp_key_info.set_decryption_key_available(QUICKeyPhase::INITIAL); + this->_pp_key_info.set_encryption_key_available(QUICKeyPhase::INITIAL); + + this->_print_km("initial - server", server_key_for_hp, server_key_for_hp_len, server_key, server_key_len, server_iv, + *server_iv_len); + this->_print_km("initial - client", client_key_for_hp, client_key_for_hp_len, client_key, client_key_len, client_iv, + *client_iv_len); + + return 1; +} + +const char * +QUICTLS::negotiated_cipher_suite() const +{ + return SSL_get_cipher_name(this->_ssl); +} + +void +QUICTLS::negotiated_application_name(const uint8_t **name, unsigned int *len) const +{ + SSL_get0_alpn_selected(this->_ssl, name, len); +} + +QUICEncryptionLevel +QUICTLS::current_encryption_level() const +{ + return this->_current_level; +} + +void +QUICTLS::abort_handshake() +{ + this->_state = HandshakeState::ABORTED; + + return; +} + +void +QUICTLS::_update_encryption_level(QUICEncryptionLevel level) +{ + if (this->_current_level < level) { + this->_current_level = level; + } + + return; +} + +void +QUICTLS::_print_km(const char *header, const uint8_t *key_for_hp, size_t key_for_hp_len, const uint8_t *key, size_t key_len, + const uint8_t *iv, size_t iv_len, const uint8_t *secret, size_t secret_len) +{ + if (is_debug_tag_set("vv_quic_crypto")) { + Debug("vv_quic_crypto", "%s", header); + uint8_t print_buf[128]; + if (secret) { + QUICDebug::to_hex(print_buf, static_cast(secret), secret_len); + Debug("vv_quic_crypto", "secret=%s", print_buf); + } + QUICDebug::to_hex(print_buf, key, key_len); + Debug("vv_quic_crypto", "key=%s", print_buf); + QUICDebug::to_hex(print_buf, iv, iv_len); + Debug("vv_quic_crypto", "iv=%s", print_buf); + QUICDebug::to_hex(print_buf, key_for_hp, key_for_hp_len); + Debug("vv_quic_crypto", "hp=%s", print_buf); + } +} diff --git a/iocore/net/quic/QUICTLS.h b/iocore/net/quic/QUICTLS.h new file mode 100644 index 00000000000..7bba45aff38 --- /dev/null +++ b/iocore/net/quic/QUICTLS.h @@ -0,0 +1,108 @@ +/** @file + * + * QUIC TLS + * + * @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. + */ + +#pragma once + +#include + +#ifdef OPENSSL_IS_BORINGSSL +#include +#include +#else +#include +#endif + +#include "I_EventSystem.h" +#include "I_NetVConnection.h" +#include "QUICHandshakeProtocol.h" + +class QUICTLS : public QUICHandshakeProtocol +{ +public: + QUICTLS(QUICPacketProtectionKeyInfo &pp_key_info, SSL_CTX *ssl_ctx, NetVConnectionContext_t nvc_ctx, + const NetVCOptions &netvc_options, const char *session_file = nullptr, const char *keylog_file = nullptr); + ~QUICTLS(); + + // TODO: integrate with _early_data_processed + enum class HandshakeState { + PROCESSING, + ABORTED, + }; + + static QUICEncryptionLevel get_encryption_level(int msg_type); + static uint16_t convert_to_quic_trans_error_code(uint8_t alert); + + std::shared_ptr local_transport_parameters() override; + std::shared_ptr remote_transport_parameters() override; + void set_local_transport_parameters(std::shared_ptr tp) override; + void set_remote_transport_parameters(std::shared_ptr tp) override; + + const char *session_file() const; + const char *keylog_file() const; + + // FIXME Should not exist + SSL *ssl_handle(); + + // QUICHandshakeProtocol + int handshake(QUICHandshakeMsgs *out, const QUICHandshakeMsgs *in) override; + void reset() override; + bool is_handshake_finished() const override; + bool is_ready_to_derive() const override; + int initialize_key_materials(QUICConnectionId cid) override; + void update_key_materials_on_key_cb(int name, const uint8_t *secret, size_t secret_len); + const char *negotiated_cipher_suite() const override; + void negotiated_application_name(const uint8_t **name, unsigned int *len) const override; + QUICEncryptionLevel current_encryption_level() const override; + void abort_handshake() override; + +private: + QUICKeyGenerator _keygen_for_client = QUICKeyGenerator(QUICKeyGenerator::Context::CLIENT); + QUICKeyGenerator _keygen_for_server = QUICKeyGenerator(QUICKeyGenerator::Context::SERVER); + const EVP_MD *_get_handshake_digest() const; + + int _read_early_data(); + int _write_early_data(); + int _handshake(QUICHandshakeMsgs *out, const QUICHandshakeMsgs *in); + int _process_post_handshake_messages(QUICHandshakeMsgs *out, const QUICHandshakeMsgs *in); + void _generate_0rtt_key(); + void _update_encryption_level(QUICEncryptionLevel level); + + void _store_negotiated_cipher(); + void _store_negotiated_cipher_for_hp(); + + void _print_km(const char *header, const uint8_t *key_for_hp, size_t key_for_hp_len, const uint8_t *key, size_t key_len, + const uint8_t *iv, size_t iv_len, const uint8_t *secret = nullptr, size_t secret_len = 0); + + const char *_session_file = nullptr; + const char *_keylog_file = nullptr; + SSL *_ssl = nullptr; + NetVConnectionContext_t _netvc_context = NET_VCONNECTION_UNSET; + bool _early_data_processed = false; + bool _is_session_reused = false; + bool _early_data = true; + QUICEncryptionLevel _current_level = QUICEncryptionLevel::INITIAL; + HandshakeState _state = HandshakeState::PROCESSING; + + std::shared_ptr _local_transport_parameters = nullptr; + std::shared_ptr _remote_transport_parameters = nullptr; +}; diff --git a/iocore/net/quic/QUICTLS_boringssl.cc b/iocore/net/quic/QUICTLS_boringssl.cc new file mode 100644 index 00000000000..a535e79bccb --- /dev/null +++ b/iocore/net/quic/QUICTLS_boringssl.cc @@ -0,0 +1,180 @@ +/** @file + * + * QUIC Crypto (TLS to Secure QUIC) using BoringSSL + * + * @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 "QUICTLS.h" + +#include +#include +#include +#include +#include +#include + +// static constexpr char tag[] = "quic_tls"; + +QUICTLS::QUICTLS(QUICPacketProtectionKeyInfo &pp_key_info, SSL_CTX *ssl_ctx, NetVConnectionContext_t nvc_ctx, + const NetVCOptions &netvc_options, const char *session_file, const char *keylog_file) + : QUICHandshakeProtocol(pp_key_info), + _session_file(session_file), + _keylog_file(keylog_file), + _ssl(SSL_new(ssl_ctx)), + _netvc_context(nvc_ctx) +{ + ink_assert(this->_netvc_context != NET_VCONNECTION_UNSET); +} + +int +QUICTLS::handshake(QUICHandshakeMsgs *out, const QUICHandshakeMsgs *in) +{ + ink_assert(false); + return 0; +} + +int +QUICTLS::_process_post_handshake_messages(QUICHandshakeMsgs *out, const QUICHandshakeMsgs *in) +{ + ink_assert(false); + return 0; +} + +int +QUICTLS::_read_early_data() +{ + uint8_t early_data[8]; + size_t early_data_len = 0; + do { + ERR_clear_error(); + early_data_len = SSL_read(this->_ssl, early_data, sizeof(early_data)); + } while (SSL_in_early_data(this->_ssl)); + + return 1; +} + +/* +const EVP_AEAD * +QUICTLS::_get_evp_aead(QUICKeyPhase phase) const +{ + if (phase == QUICKeyPhase::INITIAL) { + return EVP_aead_aes_128_gcm(); + } else { + const SSL_CIPHER *cipher = SSL_get_current_cipher(this->_ssl); + if (cipher) { + switch (SSL_CIPHER_get_id(cipher)) { + case TLS1_CK_AES_128_GCM_SHA256: + return EVP_aead_aes_128_gcm(); + case TLS1_CK_AES_256_GCM_SHA384: + return EVP_aead_aes_256_gcm(); + case TLS1_CK_CHACHA20_POLY1305_SHA256: + return EVP_aead_chacha20_poly1305(); + default: + ink_assert(false); + return nullptr; + } + } else { + ink_assert(false); + return nullptr; + } + } +} + +size_t +QUICTLS::_get_aead_tag_len(QUICKeyPhase phase) const +{ + if (phase == QUICKeyPhase::INITIAL) { + return EVP_GCM_TLS_TAG_LEN; + } else { + const SSL_CIPHER *cipher = SSL_get_current_cipher(this->_ssl); + if (cipher) { + switch (SSL_CIPHER_get_id(cipher)) { + case TLS1_CK_AES_128_GCM_SHA256: + case TLS1_CK_AES_256_GCM_SHA384: + return EVP_GCM_TLS_TAG_LEN; + case TLS1_CK_CHACHA20_POLY1305_SHA256: + return 16; + default: + ink_assert(false); + return -1; + } + } else { + ink_assert(false); + return -1; + } + } +} + +const EVP_MD * +QUICKeyGenerator::_get_handshake_digest() +{ + // TODO not implemented + return nullptr; +} + +bool +QUICTLS::_encrypt(uint8_t *cipher, size_t &cipher_len, size_t max_cipher_len, const uint8_t *plain, size_t plain_len, + uint64_t pkt_num, const uint8_t *ad, size_t ad_len, const KeyMaterial &km, const EVP_AEAD *aead, + size_t tag_len) const +{ + uint8_t nonce[EVP_MAX_IV_LENGTH] = {0}; + size_t nonce_len = 0; + _gen_nonce(nonce, nonce_len, pkt_num, km.iv, km.iv_len); + + EVP_AEAD_CTX *aead_ctx = EVP_AEAD_CTX_new(aead, km.key, km.key_len, tag_len); + if (!aead_ctx) { + Debug(tag, "Failed to create EVP_AEAD_CTX"); + return false; + } + + if (!EVP_AEAD_CTX_seal(aead_ctx, cipher, &cipher_len, max_cipher_len, nonce, nonce_len, plain, plain_len, ad, ad_len)) { + Debug(tag, "Failed to encrypt"); + return false; + } + + EVP_AEAD_CTX_free(aead_ctx); + + return true; +} + +bool +QUICTLS::_decrypt(uint8_t *plain, size_t &plain_len, size_t max_plain_len, const uint8_t *cipher, size_t cipher_len, + uint64_t pkt_num, const uint8_t *ad, size_t ad_len, const KeyMaterial &km, const EVP_AEAD *aead, + size_t tag_len) const +{ + uint8_t nonce[EVP_MAX_IV_LENGTH] = {0}; + size_t nonce_len = 0; + _gen_nonce(nonce, nonce_len, pkt_num, km.iv, km.iv_len); + + EVP_AEAD_CTX *aead_ctx = EVP_AEAD_CTX_new(aead, km.key, km.key_len, tag_len); + if (!aead_ctx) { + Debug(tag, "Failed to create EVP_AEAD_CTX"); + return false; + } + + if (!EVP_AEAD_CTX_open(aead_ctx, plain, &plain_len, max_plain_len, nonce, nonce_len, cipher, cipher_len, ad, ad_len)) { + Debug(tag, "Failed to decrypt"); + return false; + } + + EVP_AEAD_CTX_free(aead_ctx); + + return true; +} +*/ diff --git a/iocore/net/quic/QUICTLS_openssl.cc b/iocore/net/quic/QUICTLS_openssl.cc new file mode 100644 index 00000000000..0eabec13a23 --- /dev/null +++ b/iocore/net/quic/QUICTLS_openssl.cc @@ -0,0 +1,698 @@ +/** @file + * + * QUIC Crypto (TLS to Secure QUIC) using OpenSSL + * + * @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 "QUICTLS.h" + +#include +#include +#include +#include +#include + +#include "QUICConfig.h" +#include "QUICGlobals.h" +#include "QUICDebugNames.h" +#include "QUICPacketProtectionKeyInfo.h" + +static constexpr char tag[] = "quic_tls"; + +using namespace std::literals; + +static constexpr std::string_view QUIC_CLIENT_EARLY_TRAFFIC_SECRET_LABEL("QUIC_CLIENT_EARLY_TRAFFIC_SECRET"sv); +static constexpr std::string_view QUIC_CLIENT_HANDSHAKE_TRAFFIC_SECRET_LABEL("QUIC_CLIENT_HANDSHAKE_TRAFFIC_SECRET"sv); +static constexpr std::string_view QUIC_SERVER_HANDSHAKE_TRAFFIC_SECRET_LABEL("QUIC_SERVER_HANDSHAKE_TRAFFIC_SECRET"sv); +// TODO: support key update +static constexpr std::string_view QUIC_CLIENT_TRAFFIC_SECRET_LABEL("QUIC_CLIENT_TRAFFIC_SECRET_0"sv); +static constexpr std::string_view QUIC_SERVER_TRAFFIC_SECRET_LABEL("QUIC_SERVER_TRAFFIC_SECRET_0"sv); + +static const char * +content_type_str(int type) +{ + switch (type) { + case SSL3_RT_CHANGE_CIPHER_SPEC: + return "CHANGE_CIPHER_SPEC"; + case SSL3_RT_ALERT: + return "ALERT"; + case SSL3_RT_HANDSHAKE: + return "HANDSHAKE"; + case SSL3_RT_APPLICATION_DATA: + return "APPLICATION_DATA"; + case SSL3_RT_HEADER: + // The buf contains the record header bytes only + return "HEADER"; + case SSL3_RT_INNER_CONTENT_TYPE: + // Used when an encrypted TLSv1.3 record is sent or received. In encrypted TLSv1.3 records the content type in the record header + // is always SSL3_RT_APPLICATION_DATA. The real content type for the record is contained in an "inner" content type. buf + // contains the encoded "inner" content type byte. + return "INNER_CONTENT_TYPE"; + default: + return "UNKNOWN"; + } +} + +static const char * +hs_type_str(int type) +{ + switch (type) { + case SSL3_MT_CLIENT_HELLO: + return "CLIENT_HELLO"; + case SSL3_MT_SERVER_HELLO: + return "SERVER_HELLO"; + case SSL3_MT_NEWSESSION_TICKET: + return "NEWSESSION_TICKET"; + case SSL3_MT_END_OF_EARLY_DATA: + return "END_OF_EARLY_DATA"; + case SSL3_MT_ENCRYPTED_EXTENSIONS: + return "ENCRYPTED_EXTENSIONS"; + case SSL3_MT_CERTIFICATE: + return "CERTIFICATE"; + case SSL3_MT_CERTIFICATE_VERIFY: + return "CERTIFICATE_VERIFY"; + case SSL3_MT_FINISHED: + return "FINISHED"; + case SSL3_MT_KEY_UPDATE: + return "KEY_UPDATE"; + case SSL3_MT_MESSAGE_HASH: + return "MESSAGE_HASH"; + default: + return "UNKNOWN"; + } +} + +static void +msg_cb(int write_p, int version, int content_type, const void *buf, size_t len, SSL *ssl, void *arg) +{ + // Debug for reading + if (write_p == 0 && (content_type == SSL3_RT_HANDSHAKE || content_type == SSL3_RT_ALERT)) { + const uint8_t *tmp = reinterpret_cast(buf); + int msg_type = tmp[0]; + + Debug(tag, "%s (%d), %s (%d) len=%zu", content_type_str(content_type), content_type, hs_type_str(msg_type), msg_type, len); + return; + } + + if (!write_p || !arg || (content_type != SSL3_RT_HANDSHAKE && content_type != SSL3_RT_ALERT)) { + return; + } + + QUICHandshakeMsgs *msg = reinterpret_cast(arg); + if (msg == nullptr) { + return; + } + + const uint8_t *msg_buf = reinterpret_cast(buf); + + if (content_type == SSL3_RT_HANDSHAKE) { + if (version != TLS1_3_VERSION) { + return; + } + + int msg_type = msg_buf[0]; + + QUICEncryptionLevel level = QUICTLS::get_encryption_level(msg_type); + int index = static_cast(level); + int next_index = index + 1; + + size_t offset = msg->offsets[next_index]; + size_t next_level_offset = offset + len; + + memcpy(msg->buf + offset, buf, len); + + for (int i = next_index; i < 5; ++i) { + msg->offsets[i] = next_level_offset; + } + } else if (content_type == SSL3_RT_ALERT && msg_buf[0] == SSL3_AL_FATAL && len == 2) { + msg->error_code = QUICTLS::convert_to_quic_trans_error_code(msg_buf[1]); + } + + return; +} + +/** + This is very inspired from writting keylog format of ngtcp2's examples + https://github.com/ngtcp2/ngtcp2/blob/894ed23c970d61eede74f69d9178090af63fdf70/examples/keylog.cc + */ +static void +log_secret(SSL *ssl, int name, const unsigned char *secret, size_t secretlen) +{ + if (auto keylog_cb = SSL_CTX_get_keylog_callback(SSL_get_SSL_CTX(ssl))) { + unsigned char crandom[32]; + if (SSL_get_client_random(ssl, crandom, sizeof(crandom)) != sizeof(crandom)) { + return; + } + uint8_t line[256] = {0}; + size_t len = 0; + switch (name) { + case SSL_KEY_CLIENT_EARLY_TRAFFIC: + memcpy(line, QUIC_CLIENT_EARLY_TRAFFIC_SECRET_LABEL.data(), QUIC_CLIENT_EARLY_TRAFFIC_SECRET_LABEL.size()); + len += QUIC_CLIENT_EARLY_TRAFFIC_SECRET_LABEL.size(); + break; + case SSL_KEY_CLIENT_HANDSHAKE_TRAFFIC: + memcpy(line, QUIC_CLIENT_HANDSHAKE_TRAFFIC_SECRET_LABEL.data(), QUIC_CLIENT_HANDSHAKE_TRAFFIC_SECRET_LABEL.size()); + len += QUIC_CLIENT_HANDSHAKE_TRAFFIC_SECRET_LABEL.size(); + break; + case SSL_KEY_SERVER_HANDSHAKE_TRAFFIC: + memcpy(line, QUIC_SERVER_HANDSHAKE_TRAFFIC_SECRET_LABEL.data(), QUIC_SERVER_HANDSHAKE_TRAFFIC_SECRET_LABEL.size()); + len += QUIC_SERVER_HANDSHAKE_TRAFFIC_SECRET_LABEL.size(); + break; + case SSL_KEY_CLIENT_APPLICATION_TRAFFIC: + memcpy(line, QUIC_CLIENT_TRAFFIC_SECRET_LABEL.data(), QUIC_CLIENT_TRAFFIC_SECRET_LABEL.size()); + len += QUIC_CLIENT_TRAFFIC_SECRET_LABEL.size(); + break; + case SSL_KEY_SERVER_APPLICATION_TRAFFIC: + memcpy(line, QUIC_SERVER_TRAFFIC_SECRET_LABEL.data(), QUIC_SERVER_TRAFFIC_SECRET_LABEL.size()); + len += QUIC_SERVER_TRAFFIC_SECRET_LABEL.size(); + break; + + default: + return; + } + + line[len] = ' '; + ++len; + QUICDebug::to_hex(line + len, crandom, sizeof(crandom)); + len += sizeof(crandom) * 2; + line[len] = ' '; + ++len; + QUICDebug::to_hex(line + len, secret, secretlen); + + keylog_cb(ssl, reinterpret_cast(line)); + } +} + +static int +key_cb(SSL *ssl, int name, const unsigned char *secret, size_t secret_len, void *arg) +{ + if (arg == nullptr) { + return 0; + } + + QUICTLS *qtls = reinterpret_cast(arg); + qtls->update_key_materials_on_key_cb(name, secret, secret_len); + + log_secret(ssl, name, secret, secret_len); + + return 1; +} + +void +QUICTLS::update_key_materials_on_key_cb(int name, const uint8_t *secret, size_t secret_len) +{ + if (is_debug_tag_set("vv_quic_crypto")) { + switch (name) { + case SSL_KEY_CLIENT_EARLY_TRAFFIC: + Debug("vv_quic_crypto", "%s", QUIC_CLIENT_EARLY_TRAFFIC_SECRET_LABEL.data()); + break; + case SSL_KEY_CLIENT_HANDSHAKE_TRAFFIC: + Debug("vv_quic_crypto", "%s", QUIC_CLIENT_HANDSHAKE_TRAFFIC_SECRET_LABEL.data()); + break; + case SSL_KEY_SERVER_HANDSHAKE_TRAFFIC: + Debug("vv_quic_crypto", "%s", QUIC_SERVER_HANDSHAKE_TRAFFIC_SECRET_LABEL.data()); + break; + case SSL_KEY_CLIENT_APPLICATION_TRAFFIC: + Debug("vv_quic_crypto", "%s", QUIC_CLIENT_TRAFFIC_SECRET_LABEL.data()); + break; + case SSL_KEY_SERVER_APPLICATION_TRAFFIC: + Debug("vv_quic_crypto", "%s", QUIC_SERVER_TRAFFIC_SECRET_LABEL.data()); + break; + default: + break; + } + } + + if (this->_state == HandshakeState::ABORTED) { + return; + } + + QUICKeyPhase phase; + const QUIC_EVP_CIPHER *cipher; + QUICHKDF hkdf(this->_get_handshake_digest()); + + this->_store_negotiated_cipher(); + this->_store_negotiated_cipher_for_hp(); + + uint8_t *key_for_hp; + uint8_t *key; + uint8_t *iv; + size_t key_for_hp_len; + size_t key_len; + size_t *iv_len; + + switch (name) { + case SSL_KEY_CLIENT_EARLY_TRAFFIC: + // this->_update_encryption_level(QUICEncryptionLevel::ZERO_RTT); + phase = QUICKeyPhase::ZERO_RTT; + cipher = this->_pp_key_info.get_cipher(phase); + if (this->_netvc_context == NET_VCONNECTION_IN) { + key_for_hp = this->_pp_key_info.decryption_key_for_hp(phase); + key_for_hp_len = this->_pp_key_info.decryption_key_for_hp_len(phase); + key = this->_pp_key_info.decryption_key(phase); + key_len = this->_pp_key_info.decryption_key_len(phase); + iv = this->_pp_key_info.decryption_iv(phase); + iv_len = this->_pp_key_info.decryption_iv_len(phase); + this->_keygen_for_client.regenerate(key_for_hp, key, iv, iv_len, secret, secret_len, cipher, hkdf); + this->_pp_key_info.set_decryption_key_available(phase); + } else { + key_for_hp = this->_pp_key_info.encryption_key_for_hp(phase); + key_for_hp_len = this->_pp_key_info.encryption_key_for_hp_len(phase); + key = this->_pp_key_info.encryption_key(phase); + key_len = this->_pp_key_info.encryption_key_len(phase); + iv = this->_pp_key_info.encryption_iv(phase); + iv_len = this->_pp_key_info.encryption_iv_len(phase); + this->_keygen_for_client.regenerate(key_for_hp, key, iv, iv_len, secret, secret_len, cipher, hkdf); + this->_pp_key_info.set_encryption_key_available(phase); + } + this->_print_km("update - client - 0rtt", key_for_hp, key_for_hp_len, key, key_len, iv, *iv_len, secret, secret_len); + break; + case SSL_KEY_CLIENT_HANDSHAKE_TRAFFIC: + this->_update_encryption_level(QUICEncryptionLevel::HANDSHAKE); + phase = QUICKeyPhase::HANDSHAKE; + cipher = this->_pp_key_info.get_cipher(phase); + if (this->_netvc_context == NET_VCONNECTION_IN) { + key_for_hp = this->_pp_key_info.decryption_key_for_hp(phase); + key_for_hp_len = this->_pp_key_info.decryption_key_for_hp_len(phase); + key = this->_pp_key_info.decryption_key(phase); + key_len = this->_pp_key_info.decryption_key_len(phase); + iv = this->_pp_key_info.decryption_iv(phase); + iv_len = this->_pp_key_info.decryption_iv_len(phase); + this->_keygen_for_client.regenerate(key_for_hp, key, iv, iv_len, secret, secret_len, cipher, hkdf); + this->_pp_key_info.set_decryption_key_available(phase); + } else { + key_for_hp = this->_pp_key_info.encryption_key_for_hp(phase); + key_for_hp_len = this->_pp_key_info.encryption_key_for_hp_len(phase); + key = this->_pp_key_info.encryption_key(phase); + key_len = this->_pp_key_info.encryption_key_len(phase); + iv = this->_pp_key_info.encryption_iv(phase); + iv_len = this->_pp_key_info.encryption_iv_len(phase); + this->_keygen_for_client.regenerate(key_for_hp, key, iv, iv_len, secret, secret_len, cipher, hkdf); + this->_pp_key_info.set_encryption_key_available(phase); + } + this->_print_km("update - client - handshake", key_for_hp, key_for_hp_len, key, key_len, iv, *iv_len, secret, secret_len); + break; + case SSL_KEY_CLIENT_APPLICATION_TRAFFIC: + this->_update_encryption_level(QUICEncryptionLevel::ONE_RTT); + phase = QUICKeyPhase::PHASE_0; + cipher = this->_pp_key_info.get_cipher(phase); + if (this->_netvc_context == NET_VCONNECTION_IN) { + key_for_hp = this->_pp_key_info.decryption_key_for_hp(phase); + key_for_hp_len = this->_pp_key_info.decryption_key_for_hp_len(phase); + key = this->_pp_key_info.decryption_key(phase); + key_len = this->_pp_key_info.decryption_key_len(phase); + iv = this->_pp_key_info.decryption_iv(phase); + iv_len = this->_pp_key_info.decryption_iv_len(phase); + this->_keygen_for_client.regenerate(key_for_hp, key, iv, iv_len, secret, secret_len, cipher, hkdf); + this->_pp_key_info.set_decryption_key_available(phase); + } else { + key_for_hp = this->_pp_key_info.encryption_key_for_hp(phase); + key_for_hp_len = this->_pp_key_info.encryption_key_for_hp_len(phase); + key = this->_pp_key_info.encryption_key(phase); + key_len = this->_pp_key_info.encryption_key_len(phase); + iv = this->_pp_key_info.encryption_iv(phase); + iv_len = this->_pp_key_info.encryption_iv_len(phase); + this->_keygen_for_client.regenerate(key_for_hp, key, iv, iv_len, secret, secret_len, cipher, hkdf); + this->_pp_key_info.set_encryption_key_available(phase); + } + this->_print_km("update - client - 1rtt", key_for_hp, key_for_hp_len, key, key_len, iv, *iv_len, secret, secret_len); + break; + case SSL_KEY_SERVER_HANDSHAKE_TRAFFIC: + this->_update_encryption_level(QUICEncryptionLevel::HANDSHAKE); + phase = QUICKeyPhase::HANDSHAKE; + cipher = this->_pp_key_info.get_cipher(phase); + if (this->_netvc_context == NET_VCONNECTION_IN) { + key_for_hp = this->_pp_key_info.encryption_key_for_hp(phase); + key_for_hp_len = this->_pp_key_info.encryption_key_for_hp_len(phase); + key = this->_pp_key_info.encryption_key(phase); + key_len = this->_pp_key_info.encryption_key_len(phase); + iv = this->_pp_key_info.encryption_iv(phase); + iv_len = this->_pp_key_info.encryption_iv_len(phase); + this->_keygen_for_server.regenerate(key_for_hp, key, iv, iv_len, secret, secret_len, cipher, hkdf); + this->_pp_key_info.set_encryption_key_available(phase); + } else { + key_for_hp = this->_pp_key_info.decryption_key_for_hp(phase); + key_for_hp_len = this->_pp_key_info.decryption_key_for_hp_len(phase); + key = this->_pp_key_info.decryption_key(phase); + key_len = this->_pp_key_info.decryption_key_len(phase); + iv = this->_pp_key_info.decryption_iv(phase); + iv_len = this->_pp_key_info.decryption_iv_len(phase); + this->_keygen_for_server.regenerate(key_for_hp, key, iv, iv_len, secret, secret_len, cipher, hkdf); + this->_pp_key_info.set_decryption_key_available(phase); + } + this->_print_km("update - server - handshake", key_for_hp, key_for_hp_len, key, key_len, iv, *iv_len, secret, secret_len); + break; + case SSL_KEY_SERVER_APPLICATION_TRAFFIC: + this->_update_encryption_level(QUICEncryptionLevel::ONE_RTT); + phase = QUICKeyPhase::PHASE_0; + cipher = this->_pp_key_info.get_cipher(phase); + if (this->_netvc_context == NET_VCONNECTION_IN) { + key_for_hp = this->_pp_key_info.encryption_key_for_hp(phase); + key_for_hp_len = this->_pp_key_info.encryption_key_for_hp_len(phase); + key = this->_pp_key_info.encryption_key(phase); + key_len = this->_pp_key_info.encryption_key_len(phase); + iv = this->_pp_key_info.encryption_iv(phase); + iv_len = this->_pp_key_info.encryption_iv_len(phase); + this->_keygen_for_server.regenerate(key_for_hp, key, iv, iv_len, secret, secret_len, cipher, hkdf); + this->_pp_key_info.set_encryption_key_available(phase); + } else { + key_for_hp = this->_pp_key_info.decryption_key_for_hp(phase); + key_for_hp_len = this->_pp_key_info.decryption_key_for_hp_len(phase); + key = this->_pp_key_info.decryption_key(phase); + key_len = this->_pp_key_info.decryption_key_len(phase); + iv = this->_pp_key_info.decryption_iv(phase); + iv_len = this->_pp_key_info.decryption_iv_len(phase); + this->_keygen_for_server.regenerate(key_for_hp, key, iv, iv_len, secret, secret_len, cipher, hkdf); + this->_pp_key_info.set_decryption_key_available(phase); + } + this->_print_km("update - server - 1rtt", key_for_hp, key_for_hp_len, key, key_len, iv, *iv_len, secret, secret_len); + break; + default: + break; + } + + return; +} + +QUICTLS::QUICTLS(QUICPacketProtectionKeyInfo &pp_key_info, SSL_CTX *ssl_ctx, NetVConnectionContext_t nvc_ctx, + const NetVCOptions &netvc_options, const char *session_file, const char *keylog_file) + : QUICHandshakeProtocol(pp_key_info), + _session_file(session_file), + _keylog_file(keylog_file), + _ssl(SSL_new(ssl_ctx)), + _netvc_context(nvc_ctx) +{ + ink_assert(this->_netvc_context != NET_VCONNECTION_UNSET); + + if (this->_netvc_context == NET_VCONNECTION_OUT) { + SSL_set_connect_state(this->_ssl); + + SSL_set_alpn_protos(this->_ssl, reinterpret_cast(netvc_options.alpn_protos.data()), + netvc_options.alpn_protos.size()); + SSL_set_tlsext_host_name(this->_ssl, netvc_options.sni_servername.get()); + } else { + SSL_set_accept_state(this->_ssl); + } + + SSL_set_ex_data(this->_ssl, QUIC::ssl_quic_tls_index, this); + SSL_set_key_callback(this->_ssl, key_cb, this); + + if (session_file && this->_netvc_context == NET_VCONNECTION_OUT) { + auto file = BIO_new_file(session_file, "r"); + if (file == nullptr) { + Debug(tag, "Could not read tls session file %s", session_file); + return; + } + + auto session = PEM_read_bio_SSL_SESSION(file, nullptr, nullptr, nullptr); + if (session == nullptr) { + Debug(tag, "Could not read tls session file %s", session_file); + } else { + if (!SSL_set_session(this->_ssl, session)) { + Debug(tag, "Session resumption failed : %s", session_file); + } else { + Debug(tag, "Session resumption success : %s", session_file); + this->_is_session_reused = true; + } + SSL_SESSION_free(session); + } + + BIO_free(file); + } +} + +QUICEncryptionLevel +QUICTLS::get_encryption_level(int msg_type) +{ + switch (msg_type) { + case SSL3_MT_CLIENT_HELLO: + case SSL3_MT_SERVER_HELLO: + return QUICEncryptionLevel::INITIAL; + case SSL3_MT_END_OF_EARLY_DATA: + return QUICEncryptionLevel::ZERO_RTT; + case SSL3_MT_ENCRYPTED_EXTENSIONS: + case SSL3_MT_CERTIFICATE_REQUEST: + case SSL3_MT_CERTIFICATE: + case SSL3_MT_CERTIFICATE_VERIFY: + case SSL3_MT_FINISHED: + return QUICEncryptionLevel::HANDSHAKE; + case SSL3_MT_KEY_UPDATE: + case SSL3_MT_NEWSESSION_TICKET: + return QUICEncryptionLevel::ONE_RTT; + default: + return QUICEncryptionLevel::NONE; + } +} + +int +QUICTLS::handshake(QUICHandshakeMsgs *out, const QUICHandshakeMsgs *in) +{ + if (this->is_handshake_finished()) { + if (in != nullptr && in->offsets[4] != 0) { + return this->_process_post_handshake_messages(out, in); + } + + return 0; + } + + return this->_handshake(out, in); +} + +int +QUICTLS::_handshake(QUICHandshakeMsgs *out, const QUICHandshakeMsgs *in) +{ + ink_assert(this->_ssl != nullptr); + if (this->_state == HandshakeState::ABORTED) { + return 0; + } + + int err = SSL_ERROR_NONE; + ERR_clear_error(); + int ret = 0; + + SSL_set_msg_callback(this->_ssl, msg_cb); + SSL_set_msg_callback_arg(this->_ssl, out); + + // TODO: set BIO_METHOD which read from QUICHandshakeMsgs directly + BIO *rbio = BIO_new(BIO_s_mem()); + // TODO: set dummy BIO_METHOD which do nothing + BIO *wbio = BIO_new(BIO_s_mem()); + if (in != nullptr && in->offsets[4] != 0) { + BIO_write(rbio, in->buf, in->offsets[4]); + } + SSL_set_bio(this->_ssl, rbio, wbio); + + if (this->_netvc_context == NET_VCONNECTION_IN) { + if (!this->_early_data_processed) { + if (this->_read_early_data() != 1) { + out->error_code = static_cast(QUICTransErrorCode::PROTOCOL_VIOLATION); + return 0; + } else { + this->_early_data_processed = true; + } + } + + ret = SSL_accept(this->_ssl); + } else { + if (!this->_early_data_processed) { + if (this->_write_early_data()) { + this->_early_data_processed = true; + } + } + + ret = SSL_connect(this->_ssl); + } + + if (ret <= 0) { + err = SSL_get_error(this->_ssl, ret); + + switch (err) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + break; + default: + char err_buf[256] = {0}; + ERR_error_string_n(ERR_get_error(), err_buf, sizeof(err_buf)); + Debug(tag, "Handshake: %s", err_buf); + return ret; + } + } + + return 1; +} + +int +QUICTLS::_process_post_handshake_messages(QUICHandshakeMsgs *out, const QUICHandshakeMsgs *in) +{ + ink_assert(this->_ssl != nullptr); + + int err = SSL_ERROR_NONE; + ERR_clear_error(); + int ret = 0; + + SSL_set_msg_callback(this->_ssl, msg_cb); + SSL_set_msg_callback_arg(this->_ssl, out); + + // TODO: set BIO_METHOD which read from QUICHandshakeMsgs directly + BIO *rbio = BIO_new(BIO_s_mem()); + // TODO: set dummy BIO_METHOD which do nothing + BIO *wbio = BIO_new(BIO_s_mem()); + if (in != nullptr && in->offsets[4] != 0) { + BIO_write(rbio, in->buf, in->offsets[4]); + } + SSL_set_bio(this->_ssl, rbio, wbio); + + uint8_t data[2048]; + size_t l = 0; + ret = SSL_read_ex(this->_ssl, data, 2048, &l); + + if (ret <= 0) { + err = SSL_get_error(this->_ssl, ret); + + switch (err) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + break; + default: + char err_buf[256] = {0}; + ERR_error_string_n(ERR_get_error(), err_buf, sizeof(err_buf)); + Debug(tag, "Handshake: %s", err_buf); + return ret; + } + } + + return 1; +} + +void +QUICTLS::_store_negotiated_cipher() +{ + ink_assert(this->_ssl); + + const QUIC_EVP_CIPHER *cipher = nullptr; + size_t tag_len = 0; + const SSL_CIPHER *ssl_cipher = SSL_get_current_cipher(this->_ssl); + + if (ssl_cipher) { + switch (SSL_CIPHER_get_id(ssl_cipher)) { + case TLS1_3_CK_AES_128_GCM_SHA256: + cipher = EVP_aes_128_gcm(); + tag_len = EVP_GCM_TLS_TAG_LEN; + break; + case TLS1_3_CK_AES_256_GCM_SHA384: + cipher = EVP_aes_256_gcm(); + tag_len = EVP_GCM_TLS_TAG_LEN; + break; + case TLS1_3_CK_CHACHA20_POLY1305_SHA256: + cipher = EVP_chacha20_poly1305(); + tag_len = EVP_CHACHAPOLY_TLS_TAG_LEN; + break; + case TLS1_3_CK_AES_128_CCM_SHA256: + cipher = EVP_aes_128_ccm(); + tag_len = EVP_GCM_TLS_TAG_LEN; + break; + case TLS1_3_CK_AES_128_CCM_8_SHA256: + cipher = EVP_aes_128_ccm(); + tag_len = EVP_CCM8_TLS_TAG_LEN; + break; + default: + ink_assert(false); + } + } else { + ink_assert(false); + } + + this->_pp_key_info.set_cipher(cipher, tag_len); +} + +void +QUICTLS::_store_negotiated_cipher_for_hp() +{ + ink_assert(this->_ssl); + + const QUIC_EVP_CIPHER *cipher_for_hp = nullptr; + const SSL_CIPHER *ssl_cipher = SSL_get_current_cipher(this->_ssl); + + if (ssl_cipher) { + switch (SSL_CIPHER_get_id(ssl_cipher)) { + case TLS1_3_CK_AES_128_GCM_SHA256: + cipher_for_hp = EVP_aes_128_ecb(); + break; + case TLS1_3_CK_AES_256_GCM_SHA384: + cipher_for_hp = EVP_aes_256_ecb(); + break; + case TLS1_3_CK_CHACHA20_POLY1305_SHA256: + cipher_for_hp = EVP_chacha20(); + break; + case TLS1_3_CK_AES_128_CCM_SHA256: + case TLS1_3_CK_AES_128_CCM_8_SHA256: + cipher_for_hp = EVP_aes_128_ecb(); + break; + default: + ink_assert(false); + break; + } + } else { + ink_assert(false); + } + + this->_pp_key_info.set_cipher_for_hp(cipher_for_hp); +} + +int +QUICTLS::_read_early_data() +{ + uint8_t early_data[8]; + size_t early_data_len = 0; + + // Early data within the TLS connection MUST NOT be used. As it is for other TLS application data, a server MUST treat receiving + // early data on the TLS connection as a connection error of type PROTOCOL_VIOLATION. + SSL_read_early_data(this->_ssl, early_data, sizeof(early_data), &early_data_len); + // error or reading empty data return 1, otherwise return 0. + return early_data_len != 0 ? 0 : 1; +} + +int +QUICTLS::_write_early_data() +{ + size_t early_data_len = 0; + + // Early data within the TLS connection MUST NOT be used. As it is for other TLS application data, a server MUST treat receiving + // early data on the TLS connection as a connection error of type PROTOCOL_VIOLATION. + SSL_write_early_data(this->_ssl, "", 0, &early_data_len); + // always return 1 + return 1; +} + +const EVP_MD * +QUICTLS::_get_handshake_digest() const +{ + switch (SSL_CIPHER_get_id(SSL_get_current_cipher(this->_ssl))) { + case TLS1_3_CK_AES_128_GCM_SHA256: + case TLS1_3_CK_CHACHA20_POLY1305_SHA256: + case TLS1_3_CK_AES_128_CCM_SHA256: + case TLS1_3_CK_AES_128_CCM_8_SHA256: + return EVP_sha256(); + case TLS1_3_CK_AES_256_GCM_SHA384: + return EVP_sha384(); + default: + ink_assert(false); + return nullptr; + } +} diff --git a/iocore/net/quic/QUICTransferProgressProvider.h b/iocore/net/quic/QUICTransferProgressProvider.h new file mode 100644 index 00000000000..ce798fec954 --- /dev/null +++ b/iocore/net/quic/QUICTransferProgressProvider.h @@ -0,0 +1,74 @@ +/** @file + * + * Interface for providing transfer progress + * + * @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 "I_VIO.h" + +#pragma once + +class QUICTransferProgressProvider +{ +public: + virtual bool is_transfer_goal_set() const = 0; + virtual uint64_t transfer_progress() const = 0; + virtual uint64_t transfer_goal() const = 0; + virtual bool is_cancelled() const = 0; + + virtual bool + is_transfer_complete() const + { + return this->transfer_progress() == this->transfer_goal(); + } +}; + +class QUICTransferProgressProviderVIO : public QUICTransferProgressProvider +{ +public: + QUICTransferProgressProviderVIO(VIO &vio) : _vio(vio) {} + + bool + is_transfer_goal_set() const + { + return this->_vio.nbytes != INT64_MAX; + } + + uint64_t + transfer_progress() const + { + return this->_vio.ndone; + } + + uint64_t + transfer_goal() const + { + return this->_vio.nbytes; + } + + bool + is_cancelled() const + { + return false; + } + +private: + VIO &_vio; +}; diff --git a/iocore/net/quic/QUICTransportParameters.cc b/iocore/net/quic/QUICTransportParameters.cc new file mode 100644 index 00000000000..04f0009930b --- /dev/null +++ b/iocore/net/quic/QUICTransportParameters.cc @@ -0,0 +1,471 @@ +/** @file + * + * A brief file description + * + * @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 +#include "tscore/Diags.h" +#include "QUICGlobals.h" +#include "QUICIntUtil.h" +#include "QUICTransportParameters.h" +#include "QUICConnection.h" +#include "QUICHandshake.h" +#include "QUICDebugNames.h" +#include "QUICTLS.h" +#include "QUICTypes.h" + +static constexpr char tag[] = "quic_handshake"; + +static constexpr uint32_t TP_ERROR_LENGTH = 0x010000; +static constexpr uint32_t TP_ERROR_VALUE = 0x020000; +// static constexpr uint32_t TP_ERROR_MUST_EXIST = 0x030000; +static constexpr uint32_t TP_ERROR_MUST_NOT_EXIST = 0x040000; + +QUICTransportParameters::Value::Value(const uint8_t *data, uint16_t len) : _len(len) +{ + this->_data = static_cast(ats_malloc(len)); + memcpy(this->_data, data, len); +} + +QUICTransportParameters::Value::~Value() +{ + ats_free(this->_data); + this->_data = nullptr; +} + +bool +QUICTransportParameters::is_valid() const +{ + return this->_valid; +} + +const uint8_t * +QUICTransportParameters::Value::data() const +{ + return this->_data; +} + +uint16_t +QUICTransportParameters::Value::len() const +{ + return this->_len; +} + +QUICTransportParameters::~QUICTransportParameters() +{ + for (auto p : this->_parameters) { + delete p.second; + } +} + +void +QUICTransportParameters::_load(const uint8_t *buf, size_t len) +{ + bool has_error = false; + const uint8_t *p = buf; + + // Read size of parameters field + uint16_t nbytes = (p[0] << 8) + p[1]; + p += 2; + + // Read parameters + const uint8_t *end = p + nbytes; + while (p < end) { + // Read ID + uint16_t id = 0; + if (end - p >= 2) { + id = (p[0] << 8) + p[1]; + p += 2; + } else { + has_error = true; + break; + } + + // Check duplication + // An endpoint MUST treat receipt of duplicate transport parameters as a connection error of type TRANSPORT_PARAMETER_ERROR + if (this->_parameters.find(id) != this->_parameters.end()) { + has_error = true; + break; + } + + // Read length of value + uint16_t len = 0; + if (end - p >= 2) { + len = (p[0] << 8) + p[1]; + p += 2; + } else { + has_error = true; + break; + } + + // Store parameter + if (end - p >= len) { + this->_parameters.insert(std::make_pair(id, new Value(p, len))); + p += len; + } else { + has_error = true; + break; + } + } + + if (has_error) { + this->_valid = false; + return; + } + + // Validate parameters + int res = this->_validate_parameters(); + if (res < 0) { + Debug(tag, "Transport parameter is not valid (err=%d)", res); + this->_valid = false; + } else { + this->_valid = true; + } +} + +int +QUICTransportParameters::_validate_parameters() const +{ + decltype(this->_parameters)::const_iterator ite; + + // MUSTs + + // MAYs + if ((ite = this->_parameters.find(QUICTransportParameterId::INITIAL_MAX_DATA)) != this->_parameters.end()) { + } + + if ((ite = this->_parameters.find(QUICTransportParameterId::INITIAL_MAX_STREAMS_BIDI)) != this->_parameters.end()) { + } + + if ((ite = this->_parameters.find(QUICTransportParameterId::INITIAL_MAX_STREAMS_UNI)) != this->_parameters.end()) { + } + + if ((ite = this->_parameters.find(QUICTransportParameterId::IDLE_TIMEOUT)) != this->_parameters.end()) { + } + + if ((ite = this->_parameters.find(QUICTransportParameterId::MAX_PACKET_SIZE)) != this->_parameters.end()) { + if (QUICIntUtil::read_nbytes_as_uint(ite->second->data(), ite->second->len()) < 1200) { + return -(TP_ERROR_VALUE | QUICTransportParameterId::MAX_PACKET_SIZE); + } + } + + if ((ite = this->_parameters.find(QUICTransportParameterId::ACK_DELAY_EXPONENT)) != this->_parameters.end()) { + if (QUICIntUtil::read_nbytes_as_uint(ite->second->data(), ite->second->len()) > 20) { + return -(TP_ERROR_VALUE | QUICTransportParameterId::ACK_DELAY_EXPONENT); + } + } + + // MAYs (initial values for the flow control on each type of stream) + if ((ite = this->_parameters.find(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_LOCAL)) != this->_parameters.end()) { + } + + if ((ite = this->_parameters.find(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_REMOTE)) != this->_parameters.end()) { + } + + if ((ite = this->_parameters.find(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_UNI)) != this->_parameters.end()) { + } + + if ((ite = this->_parameters.find(QUICTransportParameterId::DISABLE_MIGRATION)) != this->_parameters.end()) { + } + + if ((ite = this->_parameters.find(QUICTransportParameterId::MAX_ACK_DELAY)) != this->_parameters.end()) { + } + + return 0; +} + +const uint8_t * +QUICTransportParameters::getAsBytes(QUICTransportParameterId tpid, uint16_t &len) const +{ + auto p = this->_parameters.find(tpid); + if (p != this->_parameters.end()) { + len = p->second->len(); + return p->second->data(); + } + + len = 0; + return nullptr; +} + +uint64_t +QUICTransportParameters::getAsUInt(QUICTransportParameterId tpid) const +{ + uint64_t int_value = 0; + size_t int_value_len = 0; + uint16_t raw_value_len = 0; + const uint8_t *raw_value = this->getAsBytes(tpid, raw_value_len); + if (raw_value) { + QUICVariableInt::decode(int_value, int_value_len, raw_value, raw_value_len); + return int_value; + } else { + return 0; + } +} + +bool +QUICTransportParameters::contains(QUICTransportParameterId id) const +{ + // Use std::map::contains when C++20 is supported + auto p = this->_parameters.find(id); + return (p != this->_parameters.end()); +} + +void +QUICTransportParameters::set(QUICTransportParameterId id, const uint8_t *value, uint16_t value_len) +{ + if (this->_parameters.find(id) != this->_parameters.end()) { + this->_parameters.erase(id); + } + this->_parameters.insert(std::make_pair(id, new Value(value, value_len))); +} + +void +QUICTransportParameters::set(QUICTransportParameterId id, uint64_t value) +{ + uint8_t v[8]; + size_t n; + QUICIntUtil::write_QUICVariableInt(value, v, &n); + this->set(id, v, n); +} + +void +QUICTransportParameters::store(uint8_t *buf, uint16_t *len) const +{ + uint8_t *p = buf; + + // Write QUIC versions + this->_store(p, len); + p += *len; + + // Write parameters + // XXX parameters_size will be written later + uint8_t *parameters_size = p; + p += sizeof(uint16_t); + + for (auto &it : this->_parameters) { + // TODO Skip non-MUST parameters that have their default values + p[0] = (it.first & 0xff00) >> 8; + p[1] = it.first & 0xff; + p += 2; + p[0] = (it.second->len() & 0xff00) >> 8; + p[1] = it.second->len() & 0xff; + p += 2; + memcpy(p, it.second->data(), it.second->len()); + p += it.second->len(); + } + + ptrdiff_t n = p - parameters_size - sizeof(uint16_t); + + parameters_size[0] = (n & 0xff00) >> 8; + parameters_size[1] = n & 0xff; + + *len = (p - buf); +} + +void +QUICTransportParameters::_print() const +{ + for (auto &p : this->_parameters) { + if (p.second->len() == 0) { + Debug(tag, "%s: (no value)", QUICDebugNames::transport_parameter_id(p.first)); + } else if (p.second->len() <= 8) { + uint64_t int_value; + size_t int_value_len; + QUICVariableInt::decode(int_value, int_value_len, p.second->data(), p.second->len()); + Debug(tag, "%s: 0x%" PRIx64 " (%" PRIu64 ")", QUICDebugNames::transport_parameter_id(p.first), int_value, int_value); + } else if (p.second->len() <= 24) { + char hex_str[65]; + to_hex_str(hex_str, sizeof(hex_str), p.second->data(), p.second->len()); + Debug(tag, "%s: %s", QUICDebugNames::transport_parameter_id(p.first), hex_str); + } else if (QUICTransportParameterId::PREFERRED_ADDRESS == p.first) { + QUICPreferredAddress pref_addr(p.second->data(), p.second->len()); + char cid_hex_str[QUICConnectionId::MAX_HEX_STR_LENGTH]; + char token_hex_str[QUICStatelessResetToken::LEN * 2 + 1]; + char ep_ipv4_hex_str[512]; + char ep_ipv6_hex_str[512]; + pref_addr.cid().hex(cid_hex_str, sizeof(cid_hex_str)); + to_hex_str(token_hex_str, sizeof(token_hex_str), pref_addr.token().buf(), QUICStatelessResetToken::LEN); + ats_ip_nptop(pref_addr.endpoint_ipv4(), ep_ipv4_hex_str, sizeof(ep_ipv4_hex_str)); + ats_ip_nptop(pref_addr.endpoint_ipv6(), ep_ipv6_hex_str, sizeof(ep_ipv6_hex_str)); + Debug(tag, "%s: Endpoint(IPv4)=%s, Endpoint(IPv6)=%s, CID=%s, Token=%s", QUICDebugNames::transport_parameter_id(p.first), + ep_ipv4_hex_str, ep_ipv6_hex_str, cid_hex_str, token_hex_str); + } else { + Debug(tag, "%s: (long data)", QUICDebugNames::transport_parameter_id(p.first)); + } + } +} + +// +// QUICTransportParametersInClientHello +// + +QUICTransportParametersInClientHello::QUICTransportParametersInClientHello(const uint8_t *buf, size_t len) +{ + this->_load(buf, len); + if (is_debug_tag_set(tag)) { + this->_print(); + } +} + +void +QUICTransportParametersInClientHello::_store(uint8_t *buf, uint16_t *len) const +{ + *len = 0; +} + +std::ptrdiff_t +QUICTransportParametersInClientHello::_parameters_offset(const uint8_t *) const +{ + return 4; // sizeof(QUICVersion) +} + +int +QUICTransportParametersInClientHello::_validate_parameters() const +{ + int res = QUICTransportParameters::_validate_parameters(); + if (res < 0) { + return res; + } + + decltype(this->_parameters)::const_iterator ite; + + // MUST NOTs + if ((ite = this->_parameters.find(QUICTransportParameterId::STATELESS_RESET_TOKEN)) != this->_parameters.end()) { + return -(TP_ERROR_MUST_NOT_EXIST | QUICTransportParameterId::STATELESS_RESET_TOKEN); + } + + if ((ite = this->_parameters.find(QUICTransportParameterId::PREFERRED_ADDRESS)) != this->_parameters.end()) { + return -(TP_ERROR_MUST_NOT_EXIST | QUICTransportParameterId::PREFERRED_ADDRESS); + } + + return 0; +} + +// +// QUICTransportParametersInEncryptedExtensions +// + +QUICTransportParametersInEncryptedExtensions::QUICTransportParametersInEncryptedExtensions(const uint8_t *buf, size_t len) +{ + this->_load(buf, len); + if (is_debug_tag_set(tag)) { + this->_print(); + } +} + +void +QUICTransportParametersInEncryptedExtensions::_store(uint8_t *buf, uint16_t *len) const +{ + *len = 0; +} + +void +QUICTransportParametersInEncryptedExtensions::add_version(QUICVersion version) +{ + this->_versions[this->_n_versions++] = version; +} + +std::ptrdiff_t +QUICTransportParametersInEncryptedExtensions::_parameters_offset(const uint8_t *buf) const +{ + return 4 + 1 + buf[4]; +} + +int +QUICTransportParametersInEncryptedExtensions::_validate_parameters() const +{ + int res = QUICTransportParameters::_validate_parameters(); + if (res < 0) { + return res; + } + + decltype(this->_parameters)::const_iterator ite; + + // MUSTs if the server sent a Retry packet + if ((ite = this->_parameters.find(QUICTransportParameterId::ORIGINAL_CONNECTION_ID)) != this->_parameters.end()) { + // We cannot check the length because it's not a fixed length. + } else { + // TODO Need a way that checks if we received a Retry from the server + // return -(TP_ERROR_MUST_EXIST | QUICTransportParameterId::ORIGINAL_CONNECTION_ID); + } + + // MAYs + if ((ite = this->_parameters.find(QUICTransportParameterId::STATELESS_RESET_TOKEN)) != this->_parameters.end()) { + if (ite->second->len() != 16) { + return -(TP_ERROR_LENGTH | QUICTransportParameterId::STATELESS_RESET_TOKEN); + } + } + + if ((ite = this->_parameters.find(QUICTransportParameterId::PREFERRED_ADDRESS)) != this->_parameters.end()) { + if (ite->second->len() < QUICPreferredAddress::MIN_LEN || QUICPreferredAddress::MAX_LEN < ite->second->len()) { + return -(TP_ERROR_LENGTH | QUICTransportParameterId::PREFERRED_ADDRESS); + } + } + + return 0; +} + +#ifndef OPENSSL_IS_BORINGSSL + +static constexpr int TRANSPORT_PARAMETERS_MAXIMUM_SIZE = 65535; + +// +// QUICTransportParametersHandler +// + +int +QUICTransportParametersHandler::add(SSL *s, unsigned int ext_type, unsigned int context, const unsigned char **out, size_t *outlen, + X509 *x, size_t chainidx, int *al, void *add_arg) +{ + QUICTLS *qtls = static_cast(SSL_get_ex_data(s, QUIC::ssl_quic_tls_index)); + *out = reinterpret_cast(ats_malloc(TRANSPORT_PARAMETERS_MAXIMUM_SIZE)); + qtls->local_transport_parameters()->store(const_cast(*out), reinterpret_cast(outlen)); + + return 1; +} + +void +QUICTransportParametersHandler::free(SSL *s, unsigned int ext_type, unsigned int context, const unsigned char *out, void *add_arg) +{ + ats_free(const_cast(out)); +} + +int +QUICTransportParametersHandler::parse(SSL *s, unsigned int ext_type, unsigned int context, const unsigned char *in, size_t inlen, + X509 *x, size_t chainidx, int *al, void *parse_arg) +{ + QUICTLS *qtls = static_cast(SSL_get_ex_data(s, QUIC::ssl_quic_tls_index)); + + switch (context) { + case SSL_EXT_CLIENT_HELLO: + qtls->set_remote_transport_parameters(std::make_shared(in, inlen)); + break; + case SSL_EXT_TLS1_3_ENCRYPTED_EXTENSIONS: + qtls->set_remote_transport_parameters(std::make_shared(in, inlen)); + break; + default: + // Do nothing + break; + } + + return 1; +} +#endif diff --git a/iocore/net/quic/QUICTransportParameters.h b/iocore/net/quic/QUICTransportParameters.h new file mode 100644 index 00000000000..6886bd09af0 --- /dev/null +++ b/iocore/net/quic/QUICTransportParameters.h @@ -0,0 +1,157 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include +#include + +#include +#include "QUICTypes.h" +#include + +class QUICTransportParameterId +{ +public: + enum { + ORIGINAL_CONNECTION_ID, + IDLE_TIMEOUT, + STATELESS_RESET_TOKEN, + MAX_PACKET_SIZE, + INITIAL_MAX_DATA, + INITIAL_MAX_STREAM_DATA_BIDI_LOCAL, + INITIAL_MAX_STREAM_DATA_BIDI_REMOTE, + INITIAL_MAX_STREAM_DATA_UNI, + INITIAL_MAX_STREAMS_BIDI, + INITIAL_MAX_STREAMS_UNI, + ACK_DELAY_EXPONENT, + MAX_ACK_DELAY, + DISABLE_MIGRATION, + PREFERRED_ADDRESS, + }; + + explicit operator bool() const { return true; } + bool + operator==(const QUICTransportParameterId &x) const + { + return this->_id == x._id; + } + + bool + operator==(uint16_t &x) const + { + return this->_id == x; + } + + operator uint16_t() const { return _id; }; + QUICTransportParameterId() : _id(0){}; + QUICTransportParameterId(uint16_t id) : _id(id){}; + +private: + uint16_t _id = 0; +}; + +class QUICTransportParameters +{ +public: + QUICTransportParameters(const uint8_t *buf, size_t len); + virtual ~QUICTransportParameters(); + + bool is_valid() const; + + const uint8_t *getAsBytes(QUICTransportParameterId id, uint16_t &len) const; + uint64_t getAsUInt(QUICTransportParameterId id) const; + bool contains(QUICTransportParameterId id) const; + + void set(QUICTransportParameterId id, const uint8_t *value, uint16_t value_len); + void set(QUICTransportParameterId id, uint64_t value); + + void store(uint8_t *buf, uint16_t *len) const; + +protected: + class Value + { + public: + Value(const uint8_t *data, uint16_t len); + ~Value(); + const uint8_t *data() const; + uint16_t len() const; + + private: + uint8_t *_data = nullptr; + uint16_t _len = 0; + }; + + QUICTransportParameters(){}; + void _load(const uint8_t *buf, size_t len); + bool _valid = false; + + virtual std::ptrdiff_t _parameters_offset(const uint8_t *buf) const = 0; + virtual int _validate_parameters() const; + virtual void _store(uint8_t *buf, uint16_t *len) const = 0; + void _print() const; + + std::map _parameters; +}; + +class QUICTransportParametersInClientHello : public QUICTransportParameters +{ +public: + QUICTransportParametersInClientHello() : QUICTransportParameters(){}; + QUICTransportParametersInClientHello(const uint8_t *buf, size_t len); + +protected: + std::ptrdiff_t _parameters_offset(const uint8_t *buf) const override; + int _validate_parameters() const override; + void _store(uint8_t *buf, uint16_t *len) const override; + +private: +}; + +class QUICTransportParametersInEncryptedExtensions : public QUICTransportParameters +{ +public: + QUICTransportParametersInEncryptedExtensions() : QUICTransportParameters(){}; + QUICTransportParametersInEncryptedExtensions(const uint8_t *buf, size_t len); + void add_version(QUICVersion version); + +protected: + std::ptrdiff_t _parameters_offset(const uint8_t *buf) const override; + int _validate_parameters() const override; + void _store(uint8_t *buf, uint16_t *len) const override; + + uint8_t _n_versions = 0; + QUICVersion _versions[256] = {}; +}; + +class QUICTransportParametersHandler +{ +public: + static constexpr int TRANSPORT_PARAMETER_ID = 0xffa5; + + static int add(SSL *s, unsigned int ext_type, unsigned int context, const unsigned char **out, size_t *outlen, X509 *x, + size_t chainidx, int *al, void *add_arg); + static void free(SSL *s, unsigned int ext_type, unsigned int context, const unsigned char *out, void *add_arg); + static int parse(SSL *s, unsigned int ext_type, unsigned int context, const unsigned char *in, size_t inlen, X509 *x, + size_t chainidx, int *al, void *parse_arg); +}; diff --git a/iocore/net/quic/QUICTypes.cc b/iocore/net/quic/QUICTypes.cc new file mode 100644 index 00000000000..29e09d2de00 --- /dev/null +++ b/iocore/net/quic/QUICTypes.cc @@ -0,0 +1,757 @@ +/** @file + * + * A brief file description + * + * @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 +#include "QUICTypes.h" +#include "QUICIntUtil.h" +#include "tscore/CryptoHash.h" +#include "I_EventSystem.h" +#include + +uint8_t QUICConnectionId::SCID_LEN = 0; + +// TODO: move to somewhere in lib/ts/ +int +to_hex_str(char *dst, size_t dst_len, const uint8_t *src, size_t src_len) +{ + if (dst_len < src_len * 2 + 1) { + return -1; + } + + static char hex_digits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + + for (size_t i = 0; i < src_len; ++i) { + *dst = hex_digits[src[i] >> 4]; + *(dst + 1) = hex_digits[src[i] & 0xf]; + dst += 2; + } + *dst = '\0'; + + return 0; +} + +bool +QUICTypeUtil::is_supported_version(QUICVersion version) +{ + for (auto v : QUIC_SUPPORTED_VERSIONS) { + if (v == version) { + return true; + } + } + return false; +} + +QUICStreamType +QUICTypeUtil::detect_stream_type(QUICStreamId id) +{ + uint8_t type = (id & 0x03); + return static_cast(type); +} + +QUICStreamDirection +QUICTypeUtil::detect_stream_direction(QUICStreamId id, NetVConnectionContext_t context) +{ + switch (QUICTypeUtil::detect_stream_type(id)) { + case QUICStreamType::CLIENT_BIDI: + case QUICStreamType::SERVER_BIDI: + return QUICStreamDirection::BIDIRECTIONAL; + case QUICStreamType::CLIENT_UNI: + if (context == NET_VCONNECTION_OUT) { + return QUICStreamDirection::SEND; + } + return QUICStreamDirection::RECEIVE; + case QUICStreamType::SERVER_UNI: + if (context == NET_VCONNECTION_IN) { + return QUICStreamDirection::SEND; + } + return QUICStreamDirection::RECEIVE; + default: + return QUICStreamDirection::UNKNOWN; + } +} + +QUICEncryptionLevel +QUICTypeUtil::encryption_level(QUICPacketType type) +{ + switch (type) { + case QUICPacketType::INITIAL: + return QUICEncryptionLevel::INITIAL; + case QUICPacketType::ZERO_RTT_PROTECTED: + return QUICEncryptionLevel::ZERO_RTT; + case QUICPacketType::HANDSHAKE: + return QUICEncryptionLevel::HANDSHAKE; + case QUICPacketType::PROTECTED: + return QUICEncryptionLevel::ONE_RTT; + default: + ink_assert(false); + return QUICEncryptionLevel::NONE; + } +} + +QUICPacketType +QUICTypeUtil::packet_type(QUICEncryptionLevel level) +{ + switch (level) { + case QUICEncryptionLevel::INITIAL: + return QUICPacketType::INITIAL; + case QUICEncryptionLevel::ZERO_RTT: + return QUICPacketType::ZERO_RTT_PROTECTED; + case QUICEncryptionLevel::HANDSHAKE: + return QUICPacketType::HANDSHAKE; + case QUICEncryptionLevel::ONE_RTT: + return QUICPacketType::PROTECTED; + default: + ink_assert(false); + return QUICPacketType::UNINITIALIZED; + } +} + +// TODO: clarify QUICKeyPhase and QUICEncryptionlevel mapping +QUICKeyPhase +QUICTypeUtil::key_phase(QUICPacketType type) +{ + switch (type) { + case QUICPacketType::INITIAL: + return QUICKeyPhase::INITIAL; + case QUICPacketType::ZERO_RTT_PROTECTED: + return QUICKeyPhase::ZERO_RTT; + case QUICPacketType::HANDSHAKE: + return QUICKeyPhase::HANDSHAKE; + case QUICPacketType::PROTECTED: + // XXX assuming Long Header Packet + return QUICKeyPhase::PHASE_0; + default: + return QUICKeyPhase::INITIAL; + } +} + +// 0-RTT and 1-RTT use same Packet Number Space +QUICPacketNumberSpace +QUICTypeUtil::pn_space(QUICEncryptionLevel level) +{ + switch (level) { + case QUICEncryptionLevel::HANDSHAKE: + return QUICPacketNumberSpace::Handshake; + case QUICEncryptionLevel::INITIAL: + return QUICPacketNumberSpace::Initial; + default: + return QUICPacketNumberSpace::ApplicationData; + } +} + +QUICConnectionId +QUICTypeUtil::read_QUICConnectionId(const uint8_t *buf, uint8_t len) +{ + return {buf, len}; +} + +int +QUICTypeUtil::read_QUICPacketNumberLen(const uint8_t *buf) +{ + return (buf[0] & 0x03) + 1; +} + +void +QUICTypeUtil::write_QUICPacketNumberLen(int len, uint8_t *buf) +{ + buf[0] |= len - 1; +} + +QUICPacketNumber +QUICTypeUtil::read_QUICPacketNumber(const uint8_t *buf, int encoded_length) +{ + return QUICIntUtil::read_nbytes_as_uint(buf, encoded_length); +} + +QUICVersion +QUICTypeUtil::read_QUICVersion(const uint8_t *buf) +{ + return static_cast(QUICIntUtil::read_nbytes_as_uint(buf, 4)); +} + +QUICStreamId +QUICTypeUtil::read_QUICStreamId(const uint8_t *buf) +{ + return static_cast(QUICIntUtil::read_QUICVariableInt(buf)); +} + +QUICOffset +QUICTypeUtil::read_QUICOffset(const uint8_t *buf) +{ + return static_cast(QUICIntUtil::read_QUICVariableInt(buf)); +} + +uint16_t +QUICTypeUtil::read_QUICTransErrorCode(const uint8_t *buf) +{ + return QUICIntUtil::read_nbytes_as_uint(buf, 2); +} + +QUICAppErrorCode +QUICTypeUtil::read_QUICAppErrorCode(const uint8_t *buf) +{ + return static_cast(QUICIntUtil::read_nbytes_as_uint(buf, 2)); +} + +uint64_t +QUICTypeUtil::read_QUICMaxData(const uint8_t *buf) +{ + return QUICIntUtil::read_QUICVariableInt(buf); +} + +void +QUICTypeUtil::write_QUICConnectionId(QUICConnectionId connection_id, uint8_t *buf, size_t *len) +{ + memcpy(buf, connection_id, connection_id.length()); + *len = connection_id.length(); +} + +void +QUICTypeUtil::write_QUICPacketNumber(QUICPacketNumber packet_number, uint8_t n, uint8_t *buf, size_t *len) +{ + uint64_t pn = static_cast(packet_number); + QUICIntUtil::write_uint_as_nbytes(static_cast(pn), n, buf, len); +} + +void +QUICTypeUtil::write_QUICVersion(QUICVersion version, uint8_t *buf, size_t *len) +{ + QUICIntUtil::write_uint_as_nbytes(static_cast(version), 4, buf, len); +} + +void +QUICTypeUtil::write_QUICStreamId(QUICStreamId stream_id, uint8_t *buf, size_t *len) +{ + QUICIntUtil::write_QUICVariableInt(stream_id, buf, len); +} + +void +QUICTypeUtil::write_QUICOffset(QUICOffset offset, uint8_t *buf, size_t *len) +{ + QUICIntUtil::write_QUICVariableInt(offset, buf, len); +} + +void +QUICTypeUtil::write_QUICTransErrorCode(uint16_t error_code, uint8_t *buf, size_t *len) +{ + QUICIntUtil::write_uint_as_nbytes(static_cast(error_code), 2, buf, len); +} + +void +QUICTypeUtil::write_QUICAppErrorCode(QUICAppErrorCode error_code, uint8_t *buf, size_t *len) +{ + QUICIntUtil::write_uint_as_nbytes(static_cast(error_code), 2, buf, len); +} + +void +QUICTypeUtil::write_QUICMaxData(uint64_t max_data, uint8_t *buf, size_t *len) +{ + QUICIntUtil::write_QUICVariableInt(max_data, buf, len); +} + +QUICStatelessResetToken::QUICStatelessResetToken(const QUICConnectionId &conn_id, uint32_t instance_id) +{ + uint64_t data = conn_id ^ instance_id; + CryptoHash _hash; + static constexpr char STATELESS_RESET_TOKEN_KEY[] = "stateless_token_reset_key"; + CryptoContext ctx; + ctx.update(STATELESS_RESET_TOKEN_KEY, strlen(STATELESS_RESET_TOKEN_KEY)); + ctx.update(reinterpret_cast(&data), 8); + ctx.finalize(_hash); + + size_t dummy; + QUICIntUtil::write_uint_as_nbytes(_hash.u64[0], 8, _token, &dummy); + QUICIntUtil::write_uint_as_nbytes(_hash.u64[1], 8, _token + 8, &dummy); +} + +QUICResumptionToken::QUICResumptionToken(const IpEndpoint &src, QUICConnectionId cid, ink_hrtime expire_time) +{ + // TODO: read cookie secret from file like SSLTicketKeyConfig + static constexpr char stateless_retry_token_secret[] = "stateless_cookie_secret"; + size_t dummy; + + uint8_t data[1 + INET6_ADDRPORTSTRLEN + QUICConnectionId::MAX_LENGTH + 4] = {0}; + size_t data_len = 0; + ats_ip_nptop(src, reinterpret_cast(data), sizeof(data)); + data_len = strlen(reinterpret_cast(data)); + + size_t cid_len; + QUICTypeUtil::write_QUICConnectionId(cid, data + data_len, &cid_len); + data_len += cid_len; + + QUICIntUtil::write_uint_as_nbytes(expire_time >> 30, 4, data + data_len, &dummy); + data_len += 4; + + this->_token[0] = static_cast(Type::RESUMPTION); + HMAC(EVP_sha1(), stateless_retry_token_secret, sizeof(stateless_retry_token_secret), data, data_len, this->_token + 1, + &this->_token_len); + ink_assert(this->_token_len == 20); + this->_token_len += 1; + + QUICIntUtil::write_uint_as_nbytes(expire_time >> 30, 4, this->_token + this->_token_len, &dummy); + this->_token_len += 4; + + QUICTypeUtil::write_QUICConnectionId(cid, this->_token + this->_token_len, &cid_len); + this->_token_len += cid_len; +} + +bool +QUICResumptionToken::is_valid(const IpEndpoint &src) const +{ + QUICResumptionToken x(src, this->cid(), this->expire_time() << 30); + return *this == x && this->expire_time() >= (Thread::get_hrtime() >> 30); +} + +const QUICConnectionId +QUICResumptionToken::cid() const +{ + // Type uses 1 byte and output of EVP_sha1() should be 160 bits + return QUICTypeUtil::read_QUICConnectionId(this->_token + (1 + 20 + 4), this->_token_len - (1 + 20 + 4)); +} + +const ink_hrtime +QUICResumptionToken::expire_time() const +{ + return QUICIntUtil::read_nbytes_as_uint(this->_token + (1 + 20), 4); +} + +QUICRetryToken::QUICRetryToken(const IpEndpoint &src, QUICConnectionId original_dcid) +{ + // TODO: read cookie secret from file like SSLTicketKeyConfig + static constexpr char stateless_retry_token_secret[] = "stateless_cookie_secret"; + + uint8_t data[1 + INET6_ADDRPORTSTRLEN + QUICConnectionId::MAX_LENGTH] = {0}; + size_t data_len = 0; + ats_ip_nptop(src, reinterpret_cast(data), sizeof(data)); + data_len = strlen(reinterpret_cast(data)); + + size_t cid_len; + QUICTypeUtil::write_QUICConnectionId(original_dcid, data + data_len, &cid_len); + data_len += cid_len; + + this->_token[0] = static_cast(Type::RETRY); + HMAC(EVP_sha1(), stateless_retry_token_secret, sizeof(stateless_retry_token_secret), data, data_len, this->_token + 1, + &this->_token_len); + ink_assert(this->_token_len == 20); + this->_token_len += 1; + + QUICTypeUtil::write_QUICConnectionId(original_dcid, this->_token + this->_token_len, &cid_len); + this->_token_len += cid_len; +} + +bool +QUICRetryToken::is_valid(const IpEndpoint &src) const +{ + return *this == QUICRetryToken(src, this->original_dcid()); +} + +const QUICConnectionId +QUICRetryToken::original_dcid() const +{ + // Type uses 1 byte and output of EVP_sha1() should be 160 bits + return QUICTypeUtil::read_QUICConnectionId(this->_token + (1 + 20), this->_token_len - (1 + 20)); +} + +QUICFrameType +QUICConnectionError::frame_type() const +{ + ink_assert(this->cls != QUICErrorClass::APPLICATION); + return _frame_type; +} + +// +// QUICPreferredAddress +// + +QUICPreferredAddress::QUICPreferredAddress(const uint8_t *buf, uint16_t len) +{ + if (len < QUICPreferredAddress::MIN_LEN || buf == nullptr) { + return; + } + + const uint8_t *p = buf; + + // ipv4Address + in_addr_t addr_ipv4; + memcpy(&addr_ipv4, p, 4); + p += 4; + + // ipv4Port + in_port_t port_ipv4; + memcpy(&port_ipv4, p, 2); + p += 2; + + ats_ip4_set(&this->_endpoint_ipv4, addr_ipv4, port_ipv4); + + // ipv6Address + in6_addr addr_ipv6; + memcpy(&addr_ipv6, p, 16); + p += TS_IP6_SIZE; + + // ipv6Port + in_port_t port_ipv6; + memcpy(&port_ipv6, p, 2); + p += 2; + + ats_ip6_set(&this->_endpoint_ipv6, addr_ipv6, port_ipv6); + + // CID + uint16_t cid_len = QUICIntUtil::read_nbytes_as_uint(p, 1); + p += 1; + this->_cid = QUICTypeUtil::read_QUICConnectionId(p, cid_len); + p += cid_len; + + // Token + this->_token = {p}; + + this->_valid = true; +} + +bool +QUICPreferredAddress::is_available() const +{ + return this->_valid; +} + +bool +QUICPreferredAddress::has_ipv4() const +{ + return this->_endpoint_ipv4.isValid(); +} + +bool +QUICPreferredAddress::has_ipv6() const +{ + return this->_endpoint_ipv6.isValid(); +} + +const IpEndpoint +QUICPreferredAddress::endpoint_ipv4() const +{ + return this->_endpoint_ipv4; +} + +const IpEndpoint +QUICPreferredAddress::endpoint_ipv6() const +{ + return this->_endpoint_ipv6; +} + +const QUICConnectionId +QUICPreferredAddress::cid() const +{ + return this->_cid; +} + +const QUICStatelessResetToken +QUICPreferredAddress::token() const +{ + return this->_token; +} + +void +QUICPreferredAddress::store(uint8_t *buf, uint16_t &len) const +{ + size_t dummy; + uint8_t *p = buf; + + if (this->_endpoint_ipv4.isValid()) { + // ipv4Address + memcpy(p, &ats_ip4_addr_cast(this->_endpoint_ipv4), 4); + p += 4; + + // ipv4Port + memcpy(p, &ats_ip_port_cast(this->_endpoint_ipv4), 2); + p += 2; + } else { + memset(p, 0, 6); + p += 6; + } + + if (this->_endpoint_ipv6.isValid()) { + // ipv6Address + memcpy(p, &ats_ip6_addr_cast(this->_endpoint_ipv6), 16); + p += 16; + + // ipv6Port + memcpy(p, &ats_ip_port_cast(this->_endpoint_ipv6), 2); + p += 2; + } else { + memset(p, 0, 18); + p += 18; + } + + // CID + uint8_t cid_len = this->_cid.length(); + p[0] = cid_len; + p += 1; + QUICTypeUtil::write_QUICConnectionId(this->_cid, p, &dummy); + p += cid_len; + + // Token + memcpy(p, this->_token.buf(), 16); + p += 16; + + len = p - buf; +} + +// +// QUICFiveTuple +// +QUICFiveTuple::QUICFiveTuple(IpEndpoint src, IpEndpoint dst, int protocol) : _source(src), _destination(dst), _protocol(protocol) +{ + // FIXME Generate a hash code + this->_hash_code = src.port() + dst.port() + protocol; +} +void +QUICFiveTuple::update(IpEndpoint src, IpEndpoint dst, int protocol) +{ + this->_source = src; + this->_destination = dst; + this->_protocol = protocol; + + // FIXME Generate a hash code + this->_hash_code = src.port() + dst.port() + protocol; +} + +IpEndpoint +QUICFiveTuple::source() const +{ + return this->_source; +} + +IpEndpoint +QUICFiveTuple::destination() const +{ + return this->_destination; +} + +int +QUICFiveTuple::protocol() const +{ + return this->_protocol; +} + +// +// QUICConnectionId +// +QUICConnectionId +QUICConnectionId::ZERO() +{ + uint8_t zero[MAX_LENGTH] = {0}; + return QUICConnectionId(zero, sizeof(zero)); +} + +QUICConnectionId::QUICConnectionId() +{ + this->randomize(); +} + +QUICConnectionId::QUICConnectionId(const uint8_t *buf, uint8_t len) : _len(len) +{ + memcpy(this->_id, buf, len); +} + +uint8_t +QUICConnectionId::length() const +{ + return this->_len; +} + +bool +QUICConnectionId::is_zero() const +{ + for (int i = sizeof(this->_id) - 1; i >= 0; --i) { + if (this->_id[i]) { + return false; + } + } + return true; +} + +void +QUICConnectionId::randomize() +{ + std::random_device rnd; + uint32_t x = rnd(); + for (int i = QUICConnectionId::SCID_LEN - 1; i >= 0; --i) { + if (i % 4 == 0) { + x = rnd(); + } + this->_id[i] = (x >> (8 * (i % 4))) & 0xFF; + } + this->_len = QUICConnectionId::SCID_LEN; +} + +uint64_t +QUICConnectionId::_hashcode() const +{ + return (static_cast(this->_id[0]) << 56) + (static_cast(this->_id[1]) << 48) + + (static_cast(this->_id[2]) << 40) + (static_cast(this->_id[3]) << 32) + + (static_cast(this->_id[4]) << 24) + (static_cast(this->_id[5]) << 16) + + (static_cast(this->_id[6]) << 8) + (static_cast(this->_id[7])); +} + +uint32_t +QUICConnectionId::h32() const +{ + return static_cast(QUICIntUtil::read_nbytes_as_uint(this->_id, 4)); +} + +int +QUICConnectionId::hex(char *buf, size_t len) const +{ + return to_hex_str(buf, len, this->_id, this->_len); +} + +// +// QUICInvariants +// +bool +QUICInvariants::is_long_header(const uint8_t *buf) +{ + return (buf[0] & 0x80) != 0; +} + +bool +QUICInvariants::is_version_negotiation(QUICVersion v) +{ + return v == 0x0; +} + +bool +QUICInvariants::version(QUICVersion &dst, const uint8_t *buf, uint64_t buf_len) +{ + if (!QUICInvariants::is_long_header(buf) || buf_len < QUICInvariants::LH_CIL_OFFSET) { + return false; + } + + dst = QUICTypeUtil::read_QUICVersion(buf + QUICInvariants::LH_VERSION_OFFSET); + + return true; +} + +bool +QUICInvariants::dcil(uint8_t &dst, const uint8_t *buf, uint64_t buf_len) +{ + ink_assert(QUICInvariants::is_long_header(buf)); + + if (buf_len < QUICInvariants::LH_CIL_OFFSET) { + return false; + } + + dst = buf[QUICInvariants::LH_CIL_OFFSET] >> 4; + + return true; +} + +bool +QUICInvariants::scil(uint8_t &dst, const uint8_t *buf, uint64_t buf_len) +{ + ink_assert(QUICInvariants::is_long_header(buf)); + + if (buf_len < QUICInvariants::LH_CIL_OFFSET) { + return false; + } + + dst = buf[QUICInvariants::LH_CIL_OFFSET] & 0x0F; + + return true; +} + +bool +QUICInvariants::dcid(QUICConnectionId &dst, const uint8_t *buf, uint64_t buf_len) +{ + uint8_t dcid_offset = 0; + uint8_t dcid_len = 0; + + if (QUICInvariants::is_long_header(buf)) { + uint8_t dcil = 0; + if (!QUICInvariants::dcil(dcil, buf, buf_len)) { + return false; + } + + if (dcil) { + dcid_len = dcil + QUICInvariants::CIL_BASE; + } else { + dst = QUICConnectionId::ZERO(); + return true; + } + + dcid_offset = QUICInvariants::LH_DCID_OFFSET; + } else { + // remote dcil is local scil + dcid_len = QUICConnectionId::SCID_LEN; + dcid_offset = QUICInvariants::SH_DCID_OFFSET; + } + + if (dcid_offset + dcid_len > buf_len) { + return false; + } + + dst = QUICTypeUtil::read_QUICConnectionId(buf + dcid_offset, dcid_len); + + return true; +} + +bool +QUICInvariants::scid(QUICConnectionId &dst, const uint8_t *buf, uint64_t buf_len) +{ + ink_assert(QUICInvariants::is_long_header(buf)); + + if (buf_len < QUICInvariants::LH_CIL_OFFSET) { + return false; + } + + uint8_t scid_offset = QUICInvariants::LH_DCID_OFFSET; + uint8_t scid_len = 0; + + uint8_t dcil = 0; + if (!QUICInvariants::dcil(dcil, buf, buf_len)) { + return false; + } + + if (dcil) { + scid_offset += (dcil + QUICInvariants::CIL_BASE); + } + + uint8_t scil = 0; + if (!QUICInvariants::scil(scil, buf, buf_len)) { + return false; + } + + if (scil) { + scid_len = scil + QUICInvariants::CIL_BASE; + } else { + dst = QUICConnectionId::ZERO(); + return true; + } + + if (scid_offset + scid_len > buf_len) { + return false; + } + + dst = QUICTypeUtil::read_QUICConnectionId(buf + scid_offset, scid_len); + + return true; +} diff --git a/iocore/net/quic/QUICTypes.h b/iocore/net/quic/QUICTypes.h new file mode 100644 index 00000000000..ee19ab05d90 --- /dev/null +++ b/iocore/net/quic/QUICTypes.h @@ -0,0 +1,556 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include +#include "tscore/ink_endian.h" +#include "tscore/ink_hrtime.h" +#include "tscore/Ptr.h" +#include "I_EventSystem.h" + +#include "I_NetVConnection.h" + +#include +#include +#include +#include "tscore/ink_memory.h" +#include "tscore/ink_inet.h" +#include "openssl/evp.h" + +using QUICPacketNumber = uint64_t; +using QUICVersion = uint32_t; +using QUICStreamId = uint64_t; +using QUICOffset = uint64_t; + +static constexpr uint8_t kPacketNumberSpace = 3; + +// TODO: Update version number +// Note: Prefix for drafts (0xff000000) + draft number +// Note: Fix "Supported Version" field in test case of QUICPacketFactory_Create_VersionNegotiationPacket +// Note: Fix QUIC_ALPN_PROTO_LIST in QUICConfig.cc +// Note: Change ExtensionType (QUICTransportParametersHandler::TRANSPORT_PARAMETER_ID) if it's changed +constexpr QUICVersion QUIC_SUPPORTED_VERSIONS[] = { + 0xff000014, +}; +constexpr QUICVersion QUIC_EXERCISE_VERSION = 0x1a2a3a4a; + +enum class QUICEncryptionLevel { + NONE = -1, + INITIAL = 0, + ZERO_RTT = 1, + HANDSHAKE = 2, + ONE_RTT = 3, +}; + +// For range-based for loop. This starts from INITIAL to ONE_RTT. It doesn't include NONE. +// Defining begin, end, operator*, operator++ doen't work for duplicate symbol issue with libmgmt_p.a :( +// TODO: support ZERO_RTT +constexpr QUICEncryptionLevel QUIC_ENCRYPTION_LEVELS[] = { + QUICEncryptionLevel::INITIAL, + QUICEncryptionLevel::ZERO_RTT, + QUICEncryptionLevel::HANDSHAKE, + QUICEncryptionLevel::ONE_RTT, +}; + +// introduce by draft-19 kPacketNumberSpace +enum class QUICPacketNumberSpace { + Initial, + Handshake, + ApplicationData, +}; + +// Devide to QUICPacketType and QUICPacketLongHeaderType ? +enum class QUICPacketType : uint8_t { + INITIAL = 0x00, // draft-17 version-specific type + ZERO_RTT_PROTECTED = 0x01, // draft-17 version-specific type + HANDSHAKE = 0x02, // draft-17 version-specific type + RETRY = 0x03, // draft-17 version-specific type + VERSION_NEGOTIATION = 0xF0, // Not on the spec. but just for convenience + PROTECTED, // Not on the spec. but just for convenience + STATELESS_RESET, // Not on the spec. but just for convenience + UNINITIALIZED = 0xFF, // Not on the spec. but just for convenience +}; + +// XXX If you add or remove QUICFrameType, you might also need to change QUICFrame::type(const uint8_t *) +enum class QUICFrameType : uint8_t { + PADDING = 0x00, + PING, + ACK, + ACK_WITH_ECN, + RESET_STREAM = 0x04, + STOP_SENDING, + CRYPTO, + NEW_TOKEN, + STREAM, // 0x08 - 0x0f + MAX_DATA = 0x10, + MAX_STREAM_DATA, + MAX_STREAMS, // 0x12 - 0x13 + DATA_BLOCKED = 0x14, + STREAM_DATA_BLOCKED, + STREAMS_BLOCKED, // 0x16 - 0x17 + NEW_CONNECTION_ID = 0x18, + RETIRE_CONNECTION_ID, + PATH_CHALLENGE, + PATH_RESPONSE, + CONNECTION_CLOSE, // 0x1c - 0x1d + UNKNOWN = 0x1e, +}; + +enum class QUICVersionNegotiationStatus { + NOT_NEGOTIATED, // Haven't negotiated yet + NEGOTIATED, // Negotiated + VALIDATED, // Validated with a one in transport parameters + FAILED, // Negotiation failed +}; + +enum class QUICKeyPhase : int { + PHASE_0 = 0, + PHASE_1, + INITIAL, + ZERO_RTT, + HANDSHAKE, +}; + +enum class QUICPacketCreationResult { + SUCCESS, + FAILED, + NO_PACKET, + NOT_READY, + IGNORED, + UNSUPPORTED, +}; + +enum class QUICErrorClass { + UNDEFINED, + TRANSPORT, + APPLICATION, +}; + +enum class QUICTransErrorCode : uint16_t { + NO_ERROR = 0x00, + INTERNAL_ERROR, + SERVER_BUSY, + FLOW_CONTROL_ERROR, + STREAM_ID_ERROR, + STREAM_STATE_ERROR, + FINAL_OFFSET_ERROR, + FRAME_ENCODING_ERROR, + TRANSPORT_PARAMETER_ERROR, + VERSION_NEGOTIATION_ERROR, + PROTOCOL_VIOLATION, + INVALID_MIGRATION = 0x0C, + CRYPTO_ERROR = 0x0100, // 0x100 - 0x1FF +}; + +// Application Protocol Error Codes defined in application +using QUICAppErrorCode = uint16_t; +constexpr uint16_t QUIC_APP_ERROR_CODE_STOPPING = 0; + +class QUICError +{ +public: + virtual ~QUICError() {} + + QUICErrorClass cls = QUICErrorClass::UNDEFINED; + uint16_t code = 0; + const char *msg = nullptr; + +protected: + QUICError(){}; + QUICError(QUICErrorClass error_class, uint16_t error_code, const char *error_msg = nullptr) + : cls(error_class), code(error_code), msg(error_msg) + { + } +}; + +class QUICConnectionError : public QUICError +{ +public: + QUICConnectionError() : QUICError() {} + QUICConnectionError(QUICTransErrorCode error_code, const char *error_msg = nullptr, + QUICFrameType frame_type = QUICFrameType::UNKNOWN) + : QUICError(QUICErrorClass::TRANSPORT, static_cast(error_code), error_msg), _frame_type(frame_type){}; + QUICConnectionError(QUICErrorClass error_class, uint16_t error_code, const char *error_msg = nullptr, + QUICFrameType frame_type = QUICFrameType::UNKNOWN) + : QUICError(error_class, error_code, error_msg), _frame_type(frame_type){}; + + QUICFrameType frame_type() const; + +private: + QUICFrameType _frame_type = QUICFrameType::UNKNOWN; +}; + +class QUICStream; + +class QUICStreamError : public QUICError +{ +public: + QUICStreamError() : QUICError() {} + QUICStreamError(const QUICStream *s, const QUICTransErrorCode error_code, const char *error_msg = nullptr) + : QUICError(QUICErrorClass::TRANSPORT, static_cast(error_code), error_msg), stream(s){}; + QUICStreamError(const QUICStream *s, const QUICAppErrorCode error_code, const char *error_msg = nullptr) + : QUICError(QUICErrorClass::APPLICATION, static_cast(error_code), error_msg), stream(s){}; + + const QUICStream *stream; +}; + +using QUICErrorUPtr = std::unique_ptr; +using QUICConnectionErrorUPtr = std::unique_ptr; +using QUICStreamErrorUPtr = std::unique_ptr; + +class QUICConnectionId +{ +public: + static uint8_t SCID_LEN; + + static const int MIN_LENGTH_FOR_INITIAL = 8; + static const int MAX_LENGTH = 18; + static const size_t MAX_HEX_STR_LENGTH = MAX_LENGTH * 2 + 1; + static QUICConnectionId ZERO(); + QUICConnectionId(); + QUICConnectionId(const uint8_t *buf, uint8_t len); + + explicit operator bool() const { return true; } + /** + * Note that this returns a kind of hash code so we can use a ConnectionId as a key for a hashtable. + */ + operator uint64_t() const { return this->_hashcode(); } + operator const uint8_t *() const { return this->_id; } + bool + operator==(const QUICConnectionId &x) const + { + if (this->_len != x._len) { + return false; + } + return memcmp(this->_id, x._id, this->_len) == 0; + } + + bool + operator!=(const QUICConnectionId &x) const + { + if (this->_len != x._len) { + return true; + } + return memcmp(this->_id, x._id, this->_len) != 0; + } + + /* + * This is just for debugging. + */ + uint32_t h32() const; + int hex(char *buf, size_t len) const; + + uint8_t length() const; + bool is_zero() const; + void randomize(); + +private: + uint64_t _hashcode() const; + uint8_t _id[MAX_LENGTH]; + uint8_t _len = 0; +}; + +class QUICStatelessResetToken +{ +public: + constexpr static int8_t LEN = 16; + + QUICStatelessResetToken() {} + QUICStatelessResetToken(const QUICConnectionId &conn_id, uint32_t instance_id); + QUICStatelessResetToken(const uint8_t *buf) { memcpy(this->_token, buf, QUICStatelessResetToken::LEN); } + + bool + operator==(const QUICStatelessResetToken &x) const + { + return memcmp(this->_token, x._token, QUICStatelessResetToken::LEN) == 0; + } + + const uint8_t * + buf() const + { + return _token; + } + +private: + uint8_t _token[LEN] = {0}; + + void _generate(uint64_t data); +}; + +class QUICAddressValidationToken +{ +public: + enum class Type : uint8_t { + RESUMPTION, + RETRY, + }; + + virtual ~QUICAddressValidationToken(){}; + + static Type + type(const uint8_t *buf) + { + ink_assert(static_cast(buf[0]) == Type::RESUMPTION || static_cast(buf[0]) == Type::RETRY); + return static_cast(buf[0]) == Type::RESUMPTION ? Type::RESUMPTION : Type::RETRY; + } + + virtual const uint8_t *buf() const = 0; + virtual uint8_t length() const = 0; +}; + +class QUICResumptionToken : public QUICAddressValidationToken +{ +public: + QUICResumptionToken() {} + QUICResumptionToken(const uint8_t *buf, uint8_t len) : _token_len(len) { memcpy(this->_token, buf, len); } + QUICResumptionToken(const IpEndpoint &src, QUICConnectionId cid, ink_hrtime expire_time); + + bool + operator==(const QUICResumptionToken &x) const + { + if (this->_token_len != x._token_len) { + return false; + } + return memcmp(this->_token, x._token, this->_token_len) == 0; + } + + bool is_valid(const IpEndpoint &src) const; + + const QUICConnectionId cid() const; + const ink_hrtime expire_time() const; + + const uint8_t * + buf() const override + { + return this->_token; + } + + uint8_t + length() const override + { + return this->_token_len; + } + +private: + uint8_t _token[1 + EVP_MAX_MD_SIZE + QUICConnectionId::MAX_LENGTH + 4]; + unsigned int _token_len; +}; + +class QUICRetryToken : public QUICAddressValidationToken +{ +public: + QUICRetryToken() {} + QUICRetryToken(const uint8_t *buf, uint8_t len) : _token_len(len) { memcpy(this->_token, buf, len); } + QUICRetryToken(const IpEndpoint &src, QUICConnectionId original_dcid); + + bool + operator==(const QUICRetryToken &x) const + { + if (this->_token_len != x._token_len) { + return false; + } + return memcmp(this->_token, x._token, this->_token_len) == 0; + } + + bool is_valid(const IpEndpoint &src) const; + + const QUICConnectionId original_dcid() const; + + const uint8_t * + buf() const override + { + return this->_token; + } + + uint8_t + length() const override + { + return this->_token_len; + } + +private: + uint8_t _token[1 + EVP_MAX_MD_SIZE + QUICConnectionId::MAX_LENGTH] = {}; + unsigned int _token_len = 0; + QUICConnectionId _original_dcid; +}; + +class QUICPreferredAddress +{ +public: + constexpr static int16_t MIN_LEN = 26; + constexpr static int16_t MAX_LEN = 295; + + QUICPreferredAddress(IpEndpoint endpoint_ipv4, IpEndpoint endpoint_ipv6, const QUICConnectionId &cid, + QUICStatelessResetToken token) + : _endpoint_ipv4(endpoint_ipv4), _endpoint_ipv6(endpoint_ipv6), _cid(cid), _token(token), _valid(true) + { + } + QUICPreferredAddress(const uint8_t *buf, uint16_t len); + + bool is_available() const; + bool has_ipv4() const; + bool has_ipv6() const; + const IpEndpoint endpoint_ipv4() const; + const IpEndpoint endpoint_ipv6() const; + const QUICConnectionId cid() const; + const QUICStatelessResetToken token() const; + + void store(uint8_t *buf, uint16_t &len) const; + +private: + IpEndpoint _endpoint_ipv4; + IpEndpoint _endpoint_ipv6; + QUICConnectionId _cid; + QUICStatelessResetToken _token; + bool _valid = false; +}; + +enum class QUICStreamType : uint8_t { + CLIENT_BIDI = 0x00, + SERVER_BIDI, + CLIENT_UNI, + SERVER_UNI, +}; + +enum class QUICStreamDirection : uint8_t { + UNKNOWN = 0, + SEND, + RECEIVE, + BIDIRECTIONAL, +}; + +class QUICFiveTuple +{ +public: + QUICFiveTuple(){}; + QUICFiveTuple(IpEndpoint src, IpEndpoint dst, int protocol); + void update(IpEndpoint src, IpEndpoint dst, int protocol); + IpEndpoint source() const; + IpEndpoint destination() const; + int protocol() const; + +private: + IpEndpoint _source; + IpEndpoint _destination; + int _protocol; + uint64_t _hash_code = 0; +}; + +class QUICTPConfig +{ +public: + virtual uint32_t no_activity_timeout() const = 0; + virtual const IpEndpoint *preferred_address_ipv4() const = 0; + virtual const IpEndpoint *preferred_address_ipv6() const = 0; + virtual uint32_t initial_max_data() const = 0; + virtual uint32_t initial_max_stream_data_bidi_local() const = 0; + virtual uint32_t initial_max_stream_data_bidi_remote() const = 0; + virtual uint32_t initial_max_stream_data_uni() const = 0; + virtual uint64_t initial_max_streams_bidi() const = 0; + virtual uint64_t initial_max_streams_uni() const = 0; + virtual uint8_t ack_delay_exponent() const = 0; + virtual uint8_t max_ack_delay() const = 0; +}; + +class QUICLDConfig +{ +public: + virtual uint32_t packet_threshold() const = 0; + virtual float time_threshold() const = 0; + virtual ink_hrtime granularity() const = 0; + virtual ink_hrtime initial_rtt() const = 0; +}; + +class QUICCCConfig +{ +public: + virtual uint32_t max_datagram_size() const = 0; + virtual uint32_t initial_window() const = 0; + virtual uint32_t minimum_window() const = 0; + virtual float loss_reduction_factor() const = 0; + virtual uint32_t persistent_congestion_threshold() const = 0; +}; + +// TODO: move version independent functions to QUICInvariants +class QUICTypeUtil +{ +public: + static bool is_supported_version(QUICVersion version); + static QUICStreamType detect_stream_type(QUICStreamId id); + static QUICStreamDirection detect_stream_direction(QUICStreamId id, NetVConnectionContext_t context); + static QUICEncryptionLevel encryption_level(QUICPacketType type); + static QUICPacketType packet_type(QUICEncryptionLevel level); + static QUICKeyPhase key_phase(QUICPacketType type); + static QUICPacketNumberSpace pn_space(QUICEncryptionLevel level); + + static QUICConnectionId read_QUICConnectionId(const uint8_t *buf, uint8_t n); + static int read_QUICPacketNumberLen(const uint8_t *buf); + static QUICPacketNumber read_QUICPacketNumber(const uint8_t *buf, int len); + static QUICVersion read_QUICVersion(const uint8_t *buf); + static QUICStreamId read_QUICStreamId(const uint8_t *buf); + static QUICOffset read_QUICOffset(const uint8_t *buf); + static uint16_t read_QUICTransErrorCode(const uint8_t *buf); + static QUICAppErrorCode read_QUICAppErrorCode(const uint8_t *buf); + static uint64_t read_QUICMaxData(const uint8_t *buf); + + static void write_QUICConnectionId(QUICConnectionId connection_id, uint8_t *buf, size_t *len); + static void write_QUICPacketNumberLen(int len, uint8_t *buf); + static void write_QUICPacketNumber(QUICPacketNumber packet_number, uint8_t n, uint8_t *buf, size_t *len); + static void write_QUICVersion(QUICVersion version, uint8_t *buf, size_t *len); + static void write_QUICStreamId(QUICStreamId stream_id, uint8_t *buf, size_t *len); + static void write_QUICOffset(QUICOffset offset, uint8_t *buf, size_t *len); + static void write_QUICTransErrorCode(uint16_t error_code, uint8_t *buf, size_t *len); + static void write_QUICAppErrorCode(QUICAppErrorCode error_code, uint8_t *buf, size_t *len); + static void write_QUICMaxData(uint64_t max_data, uint8_t *buf, size_t *len); + +private: +}; + +class QUICInvariants +{ +public: + static bool is_long_header(const uint8_t *buf); + static bool is_version_negotiation(QUICVersion v); + static bool version(QUICVersion &dst, const uint8_t *buf, uint64_t buf_len); + /** + * This function returns the raw value. You'll need to add 3 to the returned value to get the actual connection id length. + */ + static bool dcil(uint8_t &dst, const uint8_t *buf, uint64_t buf_len); + /** + * This function returns the raw value. You'll need to add 3 to the returned value to get the actual connection id length. + */ + static bool scil(uint8_t &dst, const uint8_t *buf, uint64_t buf_len); + static bool dcid(QUICConnectionId &dst, const uint8_t *buf, uint64_t buf_len); + static bool scid(QUICConnectionId &dst, const uint8_t *buf, uint64_t buf_len); + + static const size_t CIL_BASE = 3; + static const size_t LH_VERSION_OFFSET = 1; + static const size_t LH_CIL_OFFSET = 5; + static const size_t LH_DCID_OFFSET = 6; + static const size_t SH_DCID_OFFSET = 1; + static const size_t LH_MIN_LEN = 6; + static const size_t SH_MIN_LEN = 1; +}; + +int to_hex_str(char *dst, size_t dst_len, const uint8_t *src, size_t src_len); diff --git a/iocore/net/quic/QUICUnidirectionalStream.cc b/iocore/net/quic/QUICUnidirectionalStream.cc new file mode 100644 index 00000000000..b6dae8f6f0a --- /dev/null +++ b/iocore/net/quic/QUICUnidirectionalStream.cc @@ -0,0 +1,760 @@ +/** @file + * + * A brief file description + * + * @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 "QUICUnidirectionalStream.h" + +// +// QUICSendStream +// +QUICSendStream::QUICSendStream(QUICConnectionInfoProvider *cinfo, QUICStreamId sid, uint64_t send_max_stream_data) + : QUICStreamVConnection(cinfo, sid), _remote_flow_controller(send_max_stream_data, _id), _state(nullptr, &this->_progress_vio) +{ + SET_HANDLER(&QUICSendStream::state_stream_open); + + QUICStreamFCDebug("[REMOTE] %" PRIu64 "/%" PRIu64, this->_remote_flow_controller.current_offset(), + this->_remote_flow_controller.current_limit()); +} + +int +QUICSendStream::state_stream_open(int event, void *data) +{ + QUICVStreamDebug("%s (%d)", get_vc_event_name(event), event); + QUICErrorUPtr error = nullptr; + + switch (event) { + case VC_EVENT_READ_READY: + case VC_EVENT_READ_COMPLETE: { + // should not schedule read event. + ink_assert(0); + break; + } + case VC_EVENT_WRITE_READY: + case VC_EVENT_WRITE_COMPLETE: { + int64_t len = this->_process_write_vio(); + if (len > 0) { + this->_signal_write_event(); + } + + break; + } + case VC_EVENT_EOS: + case VC_EVENT_ERROR: + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: { + // TODO + ink_assert(false); + break; + } + default: + QUICStreamDebug("unknown event"); + ink_assert(false); + } + + // FIXME error is always nullptr + if (error != nullptr) { + if (error->cls == QUICErrorClass::TRANSPORT) { + QUICStreamDebug("QUICError: %s (%u), %s (0x%x)", QUICDebugNames::error_class(error->cls), + static_cast(error->cls), QUICDebugNames::error_code(error->code), + static_cast(error->code)); + } else { + QUICStreamDebug("QUICError: %s (%u), APPLICATION ERROR (0x%x)", QUICDebugNames::error_class(error->cls), + static_cast(error->cls), static_cast(error->code)); + } + if (dynamic_cast(error.get()) != nullptr) { + // Stream Error + QUICStreamErrorUPtr serror = QUICStreamErrorUPtr(static_cast(error.get())); + this->reset(std::move(serror)); + } else { + // Connection Error + // TODO Close connection (Does this really happen?) + } + } + + return EVENT_DONE; +} + +int +QUICSendStream::state_stream_closed(int event, void *data) +{ + QUICVStreamDebug("%s (%d)", get_vc_event_name(event), event); + + switch (event) { + case VC_EVENT_READ_READY: + case VC_EVENT_READ_COMPLETE: { + // ignore + break; + } + case VC_EVENT_WRITE_READY: + case VC_EVENT_WRITE_COMPLETE: { + // ignore + break; + } + case VC_EVENT_EOS: + case VC_EVENT_ERROR: + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: { + // TODO + ink_assert(false); + break; + } + default: + ink_assert(false); + } + + return EVENT_DONE; +} + +bool +QUICSendStream::will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) +{ + return !this->is_retransmited_frame_queue_empty() || this->_write_vio.get_reader()->is_read_avail_more_than(0); +} + +QUICFrame * +QUICSendStream::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size, + ink_hrtime timestamp) +{ + SCOPED_MUTEX_LOCK(lock, this->_write_vio.mutex, this_ethread()); + + QUICFrame *frame = this->create_retransmitted_frame(buf, level, maximum_frame_size, this->_issue_frame_id(), this); + if (frame != nullptr) { + ink_assert(frame->type() == QUICFrameType::STREAM); + this->_records_stream_frame(level, *static_cast(frame)); + return frame; + } + + // RESET_STREAM + if (this->_reset_reason && !this->_is_reset_sent) { + frame = QUICFrameFactory::create_rst_stream_frame(buf, *this->_reset_reason, this->_issue_frame_id(), this); + this->_records_rst_stream_frame(level, *static_cast(frame)); + this->_state.update_with_sending_frame(*frame); + this->_is_reset_sent = true; + return frame; + } + + if (!this->_state.is_allowed_to_send(QUICFrameType::STREAM)) { + return frame; + } + + uint64_t maximum_data_size = 0; + if (maximum_frame_size <= MAX_STREAM_FRAME_OVERHEAD) { + return frame; + } + maximum_data_size = maximum_frame_size - MAX_STREAM_FRAME_OVERHEAD; + + bool pure_fin = false; + bool fin = false; + if ((this->_write_vio.nbytes != 0 || this->_write_vio.nbytes != INT64_MAX) && + this->_write_vio.nbytes == static_cast(this->_send_offset)) { + // Pure FIN stream should be sent regardless status of remote flow controller, because the length is zero. + pure_fin = true; + fin = true; + } + + uint64_t len = 0; + IOBufferReader *reader = this->_write_vio.get_reader(); + if (!pure_fin) { + uint64_t data_len = reader->block_read_avail(); + if (data_len == 0) { + return frame; + } + + // Check Connection/Stream level credit only if the generating STREAM frame is not pure fin + uint64_t stream_credit = this->_remote_flow_controller.credit(); + if (stream_credit == 0) { + // STREAM_DATA_BLOCKED + frame = this->_remote_flow_controller.generate_frame(buf, level, UINT16_MAX, maximum_frame_size, timestamp); + return frame; + } + + if (connection_credit == 0) { + // BLOCKED - BLOCKED frame will be sent by connection level remote flow controller + return frame; + } + + len = std::min(data_len, std::min(maximum_data_size, std::min(stream_credit, connection_credit))); + + // data_len, maximum_data_size, stream_credit and connection_credit are already checked they're larger than 0 + ink_assert(len != 0); + + if (this->_write_vio.nbytes == static_cast(this->_send_offset + len)) { + fin = true; + } + } + + Ptr block = make_ptr(reader->get_current_block()->clone()); + block->consume(reader->start_offset); + block->_end = std::min(block->start() + len, block->_buf_end); + ink_assert(static_cast(block->read_avail()) == len); + + // STREAM - Pure FIN or data length is lager than 0 + // FIXME has_length_flag and has_offset_flag should be configurable + frame = QUICFrameFactory::create_stream_frame(buf, block, this->_id, this->_send_offset, fin, true, true, this->_issue_frame_id(), + this); + if (!this->_state.is_allowed_to_send(*frame)) { + QUICStreamDebug("Canceled sending %s frame due to the stream state", QUICDebugNames::frame_type(frame->type())); + return frame; + } + + if (!pure_fin) { + int ret = this->_remote_flow_controller.update(this->_send_offset + len); + // We cannot cancel sending the frame after updating the flow controller + + // Calling update always success, because len is always less than stream_credit + ink_assert(ret == 0); + + QUICStreamFCDebug("[REMOTE] %" PRIu64 "/%" PRIu64, this->_remote_flow_controller.current_offset(), + this->_remote_flow_controller.current_limit()); + if (this->_remote_flow_controller.current_offset() == this->_remote_flow_controller.current_limit()) { + QUICStreamDebug("Flow Controller will block sending a STREAM frame"); + } + + reader->consume(len); + this->_send_offset += len; + this->_write_vio.ndone += len; + } + this->_records_stream_frame(level, *static_cast(frame)); + + this->_signal_write_event(); + this->_state.update_with_sending_frame(*frame); + + return frame; +} + +QUICConnectionErrorUPtr +QUICSendStream::recv(const QUICStopSendingFrame &frame) +{ + this->_state.update_with_receiving_frame(frame); + this->reset(QUICStreamErrorUPtr(new QUICStreamError(this, QUIC_APP_ERROR_CODE_STOPPING))); + // We received and processed STOP_SENDING frame, so return NO_ERROR here + return nullptr; +} + +QUICConnectionErrorUPtr +QUICSendStream::recv(const QUICMaxStreamDataFrame &frame) +{ + this->_remote_flow_controller.forward_limit(frame.maximum_stream_data()); + QUICStreamFCDebug("[REMOTE] %" PRIu64 "/%" PRIu64, this->_remote_flow_controller.current_offset(), + this->_remote_flow_controller.current_limit()); + + int64_t len = this->_process_write_vio(); + if (len > 0) { + this->_signal_write_event(); + } + + return nullptr; +} + +VIO * +QUICSendStream::do_io_read(Continuation *c, int64_t nbytes, MIOBuffer *buf) +{ + QUICStreamDebug("Warning wants to read from send only stream ignore"); + // FIXME: should not assert here + ink_assert(!"read from send only stream"); + return nullptr; +} + +VIO * +QUICSendStream::do_io_write(Continuation *c, int64_t nbytes, IOBufferReader *buf, bool owner) +{ + if (buf) { + this->_write_vio.buffer.reader_for(buf); + } else { + this->_write_vio.buffer.clear(); + } + + this->_write_vio.mutex = c ? c->mutex : this->mutex; + this->_write_vio.cont = c; + this->_write_vio.nbytes = nbytes; + this->_write_vio.ndone = 0; + this->_write_vio.vc_server = this; + this->_write_vio.op = VIO::WRITE; + + this->_process_write_vio(); + this->_send_tracked_event(this->_write_event, VC_EVENT_WRITE_READY, &this->_write_vio); + + return &this->_write_vio; +} + +void +QUICSendStream::do_io_close(int lerrno) +{ + SET_HANDLER(&QUICSendStream::state_stream_closed); + + ink_assert(this->_read_vio.nbytes == 0); + ink_assert(this->_read_vio.op == VIO::NONE); + ink_assert(this->_read_vio.cont == nullptr); + this->_read_vio.buffer.clear(); + + this->_write_vio.buffer.clear(); + this->_write_vio.nbytes = 0; + this->_write_vio.op = VIO::NONE; + this->_write_vio.cont = nullptr; +} + +void +QUICSendStream::do_io_shutdown(ShutdownHowTo_t howto) +{ + switch (howto) { + case IO_SHUTDOWN_READ: + // ignore + break; + case IO_SHUTDOWN_WRITE: + case IO_SHUTDOWN_READWRITE: + this->do_io_close(); + break; + default: + ink_assert(0); + break; + } +} + +void +QUICSendStream::reenable(VIO *vio) +{ + ink_assert(vio == &this->_write_vio); + ink_assert(vio->op == VIO::WRITE); + + int64_t len = this->_process_write_vio(); + if (len > 0) { + this->_signal_write_event(); + } +} + +void +QUICSendStream::reset(QUICStreamErrorUPtr error) +{ + this->_reset_reason = std::move(error); +} + +void +QUICSendStream::_on_frame_acked(QUICFrameInformationUPtr &info) +{ + StreamFrameInfo *frame_info = nullptr; + switch (info->type) { + case QUICFrameType::RESET_STREAM: + this->_is_reset_complete = true; + break; + case QUICFrameType::STREAM: + frame_info = reinterpret_cast(info->data); + frame_info->block = nullptr; + if (false) { + this->_is_transfer_complete = true; + } + break; + default: + ink_assert(!"unexpected frame type"); + break; + } +} + +void +QUICSendStream::_on_frame_lost(QUICFrameInformationUPtr &info) +{ + switch (info->type) { + case QUICFrameType::RESET_STREAM: + // [draft-16] 13.2. Retransmission of Information + // Cancellation of stream transmission, as carried in a RESET_STREAM + // frame, is sent until acknowledged or until all stream data is + // acknowledged by the peer (that is, either the "Reset Recvd" or + // "Data Recvd" state is reached on the send stream). The content of + // a RESET_STREAM frame MUST NOT change when it is sent again. + this->_is_reset_sent = false; + break; + case QUICFrameType::STREAM: + this->save_frame_info(std::move(info)); + break; + default: + ink_assert(!"unexpected frame type"); + break; + } +} + +QUICOffset +QUICSendStream::largest_offset_sent() const +{ + return this->_remote_flow_controller.current_offset(); +} + +// +// QUICReceiveStream +// +QUICReceiveStream::QUICReceiveStream(QUICRTTProvider *rtt_provider, QUICConnectionInfoProvider *cinfo, QUICStreamId sid, + uint64_t recv_max_stream_data) + : QUICStreamVConnection(cinfo, sid), + _local_flow_controller(rtt_provider, recv_max_stream_data, _id), + _flow_control_buffer_size(recv_max_stream_data), + _state(this, nullptr) +{ + SET_HANDLER(&QUICReceiveStream::state_stream_open); + + QUICStreamFCDebug("[LOCAL] %" PRIu64 "/%" PRIu64, this->_local_flow_controller.current_offset(), + this->_local_flow_controller.current_limit()); +} + +int +QUICReceiveStream::state_stream_open(int event, void *data) +{ + QUICVStreamDebug("%s (%d)", get_vc_event_name(event), event); + QUICErrorUPtr error = nullptr; + + switch (event) { + case VC_EVENT_READ_READY: + case VC_EVENT_READ_COMPLETE: { + int64_t len = this->_process_read_vio(); + if (len > 0) { + this->_signal_read_event(); + } + + break; + } + case VC_EVENT_WRITE_READY: + case VC_EVENT_WRITE_COMPLETE: { + // should not schedule write event + ink_assert(!"should not schedule write even"); + break; + } + case VC_EVENT_EOS: + case VC_EVENT_ERROR: + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: { + // TODO + ink_assert(false); + break; + } + default: + QUICStreamDebug("unknown event"); + ink_assert(false); + } + + // FIXME error is always nullptr + if (error != nullptr) { + if (error->cls == QUICErrorClass::TRANSPORT) { + QUICStreamDebug("QUICError: %s (%u), %s (0x%x)", QUICDebugNames::error_class(error->cls), + static_cast(error->cls), QUICDebugNames::error_code(error->code), + static_cast(error->code)); + } else { + QUICStreamDebug("QUICError: %s (%u), APPLICATION ERROR (0x%x)", QUICDebugNames::error_class(error->cls), + static_cast(error->cls), static_cast(error->code)); + } + if (dynamic_cast(error.get()) != nullptr) { + // Stream Error + QUICStreamErrorUPtr serror = QUICStreamErrorUPtr(static_cast(error.get())); + this->reset(std::move(serror)); + } else { + // Connection Error + // TODO Close connection (Does this really happen?) + } + } + + return EVENT_DONE; +} + +int +QUICReceiveStream::state_stream_closed(int event, void *data) +{ + QUICVStreamDebug("%s (%d)", get_vc_event_name(event), event); + + switch (event) { + case VC_EVENT_READ_READY: + case VC_EVENT_READ_COMPLETE: { + // ignore + break; + } + case VC_EVENT_WRITE_READY: + case VC_EVENT_WRITE_COMPLETE: { + // ignore + break; + } + case VC_EVENT_EOS: + case VC_EVENT_ERROR: + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: { + // TODO + ink_assert(false); + break; + } + default: + ink_assert(false); + } + + return EVENT_DONE; +} + +bool +QUICReceiveStream::is_transfer_goal_set() const +{ + return this->_received_stream_frame_buffer.is_transfer_goal_set(); +} + +uint64_t +QUICReceiveStream::transfer_progress() const +{ + return this->_received_stream_frame_buffer.transfer_progress(); +} + +uint64_t +QUICReceiveStream::transfer_goal() const +{ + return this->_received_stream_frame_buffer.transfer_goal(); +} + +bool +QUICReceiveStream::is_cancelled() const +{ + return this->_is_stop_sending_complete; +} + +bool +QUICReceiveStream::will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) +{ + return this->_local_flow_controller.will_generate_frame(level, timestamp) || + (this->_stop_sending_reason != nullptr && this->_is_stop_sending_sent == false); +} + +QUICFrame * +QUICReceiveStream::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size, + ink_hrtime timestamp) +{ + QUICFrame *frame = nullptr; + // STOP_SENDING + if (this->_stop_sending_reason && !this->_is_stop_sending_sent) { + frame = + QUICFrameFactory::create_stop_sending_frame(buf, this->id(), this->_stop_sending_reason->code, this->_issue_frame_id(), this); + this->_records_stop_sending_frame(level, *static_cast(frame)); + this->_state.update_with_sending_frame(*frame); + this->_is_stop_sending_sent = true; + return frame; + } + + // MAX_STREAM_DATA + frame = this->_local_flow_controller.generate_frame(buf, level, UINT16_MAX, maximum_frame_size, timestamp); + return frame; +} + +QUICConnectionErrorUPtr +QUICReceiveStream::recv(const QUICRstStreamFrame &frame) +{ + this->_state.update_with_receiving_frame(frame); + this->_signal_read_eos_event(); + return nullptr; +} + +QUICConnectionErrorUPtr +QUICReceiveStream::recv(const QUICStreamDataBlockedFrame &frame) +{ + // STREAM_DATA_BLOCKED frames are for debugging. Nothing to do here. + QUICStreamFCDebug("[REMOTE] blocked %" PRIu64, frame.offset()); + return nullptr; +} + +/** + * @brief Receive STREAM frame + * @detail When receive STREAM frame, reorder frames and write to buffer of read_vio. + * If the reordering or writting operation is heavy, split out them to read function, + * which is called by application via do_io_read() or reenable(). + */ +QUICConnectionErrorUPtr +QUICReceiveStream::recv(const QUICStreamFrame &frame) +{ + ink_assert(_id == frame.stream_id()); + ink_assert(this->_read_vio.op == VIO::READ); + + // Check stream state - Do this first before accept the frame + if (!this->_state.is_allowed_to_receive(frame)) { + QUICStreamDebug("Canceled receiving %s frame due to the stream state", QUICDebugNames::frame_type(frame.type())); + return std::make_unique(QUICTransErrorCode::STREAM_STATE_ERROR); + } + + // Flow Control - Even if it's allowed to receive on the state, it may exceed the limit + int ret = this->_local_flow_controller.update(frame.offset() + frame.data_length()); + QUICStreamFCDebug("[LOCAL] %" PRIu64 "/%" PRIu64, this->_local_flow_controller.current_offset(), + this->_local_flow_controller.current_limit()); + if (ret != 0) { + return std::make_unique(QUICTransErrorCode::FLOW_CONTROL_ERROR); + } + + // Make a copy and insert it into the receive buffer because the frame passed is temporal + QUICFrame *cloned = new QUICStreamFrame(frame); + QUICConnectionErrorUPtr error = this->_received_stream_frame_buffer.insert(cloned); + if (error != nullptr) { + this->_received_stream_frame_buffer.clear(); + return error; + } + + auto new_frame = this->_received_stream_frame_buffer.pop(); + const QUICStreamFrame *stream_frame = nullptr; + uint64_t last_offset = 0; + uint64_t last_length = 0; + + while (new_frame != nullptr) { + stream_frame = static_cast(new_frame); + last_offset = stream_frame->offset(); + last_length = stream_frame->data_length(); + + this->_write_to_read_vio(stream_frame->offset(), reinterpret_cast(stream_frame->data()->start()), + stream_frame->data_length(), stream_frame->has_fin_flag()); + this->_state.update_with_receiving_frame(*new_frame); + + delete new_frame; + new_frame = this->_received_stream_frame_buffer.pop(); + } + + // Forward limit of local flow controller with the largest reordered stream frame + if (stream_frame) { + this->_reordered_bytes = last_offset + last_length; + this->_local_flow_controller.forward_limit(this->_reordered_bytes + this->_flow_control_buffer_size); + QUICStreamFCDebug("[LOCAL] %" PRIu64 "/%" PRIu64, this->_local_flow_controller.current_offset(), + this->_local_flow_controller.current_limit()); + } + + this->_signal_read_event(); + + return nullptr; +} + +VIO * +QUICReceiveStream::do_io_read(Continuation *c, int64_t nbytes, MIOBuffer *buf) +{ + if (buf) { + this->_read_vio.buffer.writer_for(buf); + } else { + this->_read_vio.buffer.clear(); + } + + this->_read_vio.mutex = c ? c->mutex : this->mutex; + this->_read_vio.cont = c; + this->_read_vio.nbytes = nbytes; + this->_read_vio.ndone = 0; + this->_read_vio.vc_server = this; + this->_read_vio.op = VIO::READ; + + this->_process_read_vio(); + this->_send_tracked_event(this->_read_event, VC_EVENT_READ_READY, &this->_read_vio); + + return &this->_read_vio; +} + +VIO * +QUICReceiveStream::do_io_write(Continuation *c, int64_t nbytes, IOBufferReader *buf, bool owner) +{ + QUICStreamDebug("Warning wants to write to send only stream ignore"); + // FIXME: should not assert here + ink_assert(!"write to send only stream"); + return nullptr; +} + +void +QUICReceiveStream::do_io_close(int lerrno) +{ + SET_HANDLER(&QUICReceiveStream::state_stream_closed); + + ink_assert(this->_write_vio.nbytes == 0); + ink_assert(this->_write_vio.op == VIO::NONE); + ink_assert(this->_write_vio.cont == nullptr); + this->_write_vio.buffer.clear(); + + this->_read_vio.buffer.clear(); + this->_read_vio.nbytes = 0; + this->_read_vio.op = VIO::NONE; + this->_read_vio.cont = nullptr; +} + +void +QUICReceiveStream::do_io_shutdown(ShutdownHowTo_t howto) +{ + switch (howto) { + case IO_SHUTDOWN_WRITE: + // ignore + break; + case IO_SHUTDOWN_READ: + case IO_SHUTDOWN_READWRITE: + this->do_io_close(); + break; + default: + ink_assert(0); + break; + } +} + +void +QUICReceiveStream::reenable(VIO *vio) +{ + ink_assert(vio == &this->_read_vio); + ink_assert(vio->op == VIO::READ); + + int64_t len = this->_process_read_vio(); + if (len > 0) { + this->_signal_read_event(); + } +} + +void +QUICReceiveStream::on_read() +{ + this->_state.update_on_read(); +} + +void +QUICReceiveStream::on_eos() +{ + this->_state.update_on_eos(); +} + +QUICOffset +QUICReceiveStream::largest_offset_received() const +{ + return this->_local_flow_controller.current_offset(); +} + +void +QUICReceiveStream::_on_frame_lost(QUICFrameInformationUPtr &info) +{ + switch (info->type) { + case QUICFrameType::STOP_SENDING: + this->_is_stop_sending_sent = false; + break; + default: + ink_assert(!"unknown frame type"); + break; + } +} + +void +QUICReceiveStream::_on_frame_acked(QUICFrameInformationUPtr &info) +{ + switch (info->type) { + case QUICFrameType::STOP_SENDING: + this->_is_stop_sending_complete = true; + break; + default: + ink_assert(!"unknown frame type"); + break; + } +} + +void +QUICReceiveStream::stop_sending(QUICStreamErrorUPtr error) +{ + this->_stop_sending_reason = std::move(error); +} diff --git a/iocore/net/quic/QUICUnidirectionalStream.h b/iocore/net/quic/QUICUnidirectionalStream.h new file mode 100644 index 00000000000..0c7162742a6 --- /dev/null +++ b/iocore/net/quic/QUICUnidirectionalStream.h @@ -0,0 +1,137 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include "QUICStream.h" + +class QUICSendStream : public QUICStreamVConnection +{ +public: + QUICSendStream(QUICConnectionInfoProvider *cinfo, QUICStreamId sid, uint64_t send_max_stream_data); + QUICSendStream() : _remote_flow_controller(0, 0), _state(nullptr, nullptr) {} + + ~QUICSendStream() {} + + int state_stream_open(int event, void *data); + int state_stream_closed(int event, void *data); + + // QUICFrameGenerator + bool will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) override; + QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size, + ink_hrtime timestamp) override; + + virtual QUICConnectionErrorUPtr recv(const QUICMaxStreamDataFrame &frame) override; + virtual QUICConnectionErrorUPtr recv(const QUICStopSendingFrame &frame) override; + + // Implement VConnection Interface. + VIO *do_io_read(Continuation *c, int64_t nbytes = INT64_MAX, MIOBuffer *buf = 0) override; + VIO *do_io_write(Continuation *c = nullptr, int64_t nbytes = INT64_MAX, IOBufferReader *buf = 0, bool owner = false) override; + void do_io_close(int lerrno = -1) override; + void do_io_shutdown(ShutdownHowTo_t howto) override; + void reenable(VIO *vio) override; + + void reset(QUICStreamErrorUPtr error) override; + + QUICOffset largest_offset_sent() const override; + +private: + QUICStreamErrorUPtr _reset_reason = nullptr; + bool _is_reset_sent = false; + + bool _is_transfer_complete = false; + bool _is_reset_complete = false; + + QUICTransferProgressProviderVIO _progress_vio = {this->_read_vio}; + + QUICRemoteStreamFlowController _remote_flow_controller; + + QUICSendStreamStateMachine _state; + + // QUICFrameGenerator + void _on_frame_acked(QUICFrameInformationUPtr &info) override; + void _on_frame_lost(QUICFrameInformationUPtr &info) override; +}; + +class QUICReceiveStream : public QUICStreamVConnection, public QUICTransferProgressProvider +{ +public: + QUICReceiveStream(QUICRTTProvider *rtt_provider, QUICConnectionInfoProvider *cinfo, QUICStreamId sid, + uint64_t recv_max_stream_data); + QUICReceiveStream() : _local_flow_controller(nullptr, 0, 0), _state(nullptr, nullptr) {} + + ~QUICReceiveStream() {} + + int state_stream_open(int event, void *data); + int state_stream_closed(int event, void *data); + + // QUICFrameGenerator + bool will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) override; + QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size, + ink_hrtime timestamp) override; + + virtual QUICConnectionErrorUPtr recv(const QUICStreamFrame &frame) override; + virtual QUICConnectionErrorUPtr recv(const QUICStreamDataBlockedFrame &frame) override; + virtual QUICConnectionErrorUPtr recv(const QUICRstStreamFrame &frame) override; + + // Implement VConnection Interface. + VIO *do_io_read(Continuation *c, int64_t nbytes = INT64_MAX, MIOBuffer *buf = 0) override; + VIO *do_io_write(Continuation *c = nullptr, int64_t nbytes = INT64_MAX, IOBufferReader *buf = 0, bool owner = false) override; + void do_io_close(int lerrno = -1) override; + void do_io_shutdown(ShutdownHowTo_t howto) override; + void reenable(VIO *vio) override; + + // QUICTransferProgressProvider + bool is_transfer_goal_set() const override; + uint64_t transfer_progress() const override; + uint64_t transfer_goal() const override; + bool is_cancelled() const override; + + /* + * QUICApplication need to call one of these functions when it process VC_EVENT_* + */ + virtual void on_read() override; + virtual void on_eos() override; + + QUICOffset largest_offset_received() const override; + + void stop_sending(QUICStreamErrorUPtr error) override; + +private: + QUICStreamErrorUPtr _stop_sending_reason = nullptr; + bool _is_stop_sending_sent = false; + bool _is_stop_sending_complete = false; + + QUICLocalStreamFlowController _local_flow_controller; + uint64_t _flow_control_buffer_size = 1024; + + // Fragments of received STREAM frame (offset is unmatched) + // TODO: Consider to replace with ts/RbTree.h or other data structure + QUICIncomingStreamFrameBuffer _received_stream_frame_buffer; + + QUICReceiveStreamStateMachine _state; + + // QUICFrameGenerator + void _on_frame_acked(QUICFrameInformationUPtr &info) override; + void _on_frame_lost(QUICFrameInformationUPtr &info) override; +}; diff --git a/iocore/net/quic/QUICVersionNegotiator.cc b/iocore/net/quic/QUICVersionNegotiator.cc new file mode 100644 index 00000000000..ec1dc7938d6 --- /dev/null +++ b/iocore/net/quic/QUICVersionNegotiator.cc @@ -0,0 +1,81 @@ +/** @file + * + * A brief file description + * + * @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 "QUICVersionNegotiator.h" +#include "QUICTransportParameters.h" + +QUICVersionNegotiationStatus +QUICVersionNegotiator::status() +{ + return this->_status; +} + +QUICVersionNegotiationStatus +QUICVersionNegotiator::negotiate(const QUICPacket &packet) +{ + switch (packet.type()) { + case QUICPacketType::INITIAL: { + if (QUICTypeUtil::is_supported_version(packet.version())) { + this->_status = QUICVersionNegotiationStatus::NEGOTIATED; + this->_negotiated_version = packet.version(); + } + + break; + } + case QUICPacketType::VERSION_NEGOTIATION: { + const uint8_t *supported_versions = packet.payload(); + uint16_t supported_versions_len = packet.payload_length(); + uint16_t len = 0; + + while (len < supported_versions_len) { + QUICVersion version = QUICTypeUtil::read_QUICVersion(supported_versions + len); + len += sizeof(QUICVersion); + + if (QUICTypeUtil::is_supported_version(version)) { + this->_status = QUICVersionNegotiationStatus::NEGOTIATED; + this->_negotiated_version = version; + break; + } + } + + break; + } + default: + ink_assert(false); + break; + } + + return this->_status; +} + +QUICVersionNegotiationStatus +QUICVersionNegotiator::validate() +{ + return this->_status; +} + +QUICVersion +QUICVersionNegotiator::negotiated_version() +{ + return this->_negotiated_version; +} diff --git a/iocore/net/quic/QUICVersionNegotiator.h b/iocore/net/quic/QUICVersionNegotiator.h new file mode 100644 index 00000000000..6b86d7f73a5 --- /dev/null +++ b/iocore/net/quic/QUICVersionNegotiator.h @@ -0,0 +1,45 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include "QUICTypes.h" +#include "QUICPacket.h" +#include "QUICTransportParameters.h" + +/** + * @brief Abstruct QUIC Application Class + * @detail Every quic application must inherits this class + */ +class QUICVersionNegotiator +{ +public: + QUICVersionNegotiationStatus status(); + QUICVersionNegotiationStatus negotiate(const QUICPacket &initial_packet); + QUICVersionNegotiationStatus validate(); + QUICVersion negotiated_version(); + +private: + QUICVersion _negotiated_version = 0; + QUICVersionNegotiationStatus _status = QUICVersionNegotiationStatus::NOT_NEGOTIATED; +}; diff --git a/iocore/net/quic/test/event_processor_main.cc b/iocore/net/quic/test/event_processor_main.cc new file mode 100644 index 00000000000..4c1d2dd5b3c --- /dev/null +++ b/iocore/net/quic/test/event_processor_main.cc @@ -0,0 +1,64 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +// To make compile faster +// https://github.com/philsquared/Catch/blob/master/docs/slow-compiles.md +#define CATCH_CONFIG_MAIN +#include "catch.hpp" + +#include "tscore/I_Layout.h" +#include "tscore/Diags.h" + +#include "I_EventSystem.h" +#include "RecordsConfig.h" + +#include "QUICConfig.h" + +#define TEST_THREADS 1 + +struct EventProcessorListener : Catch::TestEventListenerBase { + using TestEventListenerBase::TestEventListenerBase; // inherit constructor + + void + testRunStarting(Catch::TestRunInfo const &testRunInfo) override + { + BaseLogFile *base_log_file = new BaseLogFile("stderr"); + diags = new Diags(testRunInfo.name.c_str(), "" /* tags */, "" /* actions */, base_log_file); + diags->activate_taglist("vv_quic|quic", DiagsTagType_Debug); + diags->config.enabled[DiagsTagType_Debug] = true; + diags->show_location = SHOW_LOCATION_DEBUG; + + Layout::create(); + RecProcessInit(RECM_STAND_ALONE); + LibRecordsConfigInit(); + + QUICConfig::startup(); + + ink_event_system_init(EVENT_SYSTEM_MODULE_PUBLIC_VERSION); + eventProcessor.start(TEST_THREADS); + + Thread *main_thread = new EThread; + main_thread->set_specific(); + } +}; +CATCH_REGISTER_LISTENER(EventProcessorListener); diff --git a/iocore/net/quic/test/main.cc b/iocore/net/quic/test/main.cc new file mode 100644 index 00000000000..96767a2f97b --- /dev/null +++ b/iocore/net/quic/test/main.cc @@ -0,0 +1,58 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +// To make compile faster +// https://github.com/philsquared/Catch/blob/master/docs/slow-compiles.md +#define CATCH_CONFIG_MAIN +#include "catch.hpp" + +#include "tscore/I_Layout.h" +#include "tscore/Diags.h" + +#include "RecordsConfig.h" +#include "QUICConfig.h" + +struct EventProcessorListener : Catch::TestEventListenerBase { + using TestEventListenerBase::TestEventListenerBase; // inherit constructor + + void + testRunStarting(Catch::TestRunInfo const &testRunInfo) override + { + BaseLogFile *base_log_file = new BaseLogFile("stderr"); + diags = new Diags(testRunInfo.name.c_str(), "" /* tags */, "" /* actions */, base_log_file); + diags->activate_taglist("vv_quic|quic", DiagsTagType_Debug); + diags->config.enabled[DiagsTagType_Debug] = true; + diags->show_location = SHOW_LOCATION_DEBUG; + + Layout::create(); + RecProcessInit(RECM_STAND_ALONE); + LibRecordsConfigInit(); + + QUICConfig::startup(); + + EThread *thread = new EThread(); + thread->set_specific(); + init_buffer_allocators(0); + } +}; +CATCH_REGISTER_LISTENER(EventProcessorListener); diff --git a/iocore/net/quic/test/server_cert.h b/iocore/net/quic/test/server_cert.h new file mode 100644 index 00000000000..b19377c81de --- /dev/null +++ b/iocore/net/quic/test/server_cert.h @@ -0,0 +1,73 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +static constexpr char server_crt[] = "-----BEGIN CERTIFICATE-----\n" + "MIIDRjCCAi4CCQDoLSBwQxmcJTANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQGEwJK\n" + "UDEOMAwGA1UECBMFVG9reW8xDzANBgNVBAcTBk1pbmF0bzEhMB8GA1UEChMYSW50\n" + "ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRIwEAYDVQQDEwlsb2NhbGhvc3QwHhcNMTcw\n" + "MTE4MDEyMzA3WhcNMjcwMTE2MDEyMzA3WjBlMQswCQYDVQQGEwJKUDEOMAwGA1UE\n" + "CBMFVG9reW8xDzANBgNVBAcTBk1pbmF0bzEhMB8GA1UEChMYSW50ZXJuZXQgV2lk\n" + "Z2l0cyBQdHkgTHRkMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB\n" + "AQUAA4IBDwAwggEKAoIBAQC70j62KOWkuqNsDhl+7uqKFS6TMcJYLdYrH1YInwlY\n" + "htOMSMWx2hPSYYBKzVQpLvhe2LPbhLwcVJdq4aqQNjNpxrpxW/YIY5zxCRVgQsgf\n" + "KXiKgUR0G+F3MQHsm1YIqxQU2OeJldIZUBM2YMDp8h1CXTAvGaAZaXsqO9UvR2Zw\n" + "JZJ+GElYNlNwhdStqIM8v1JNFjfO3gWkVqTv+QM4fmpror2pp8CaDrueg4PrSY3Y\n" + "D/WG75rkmlrW26t0Q8fjkn+s/UiQ3V/IkP1+MfrJWH6RL2DGjBv2KfNAik42xWUi\n" + "KXzaNcDFN4hjqVG59O9bPnUDn1wPypY/TXB4iqSAlxupAgMBAAEwDQYJKoZIhvcN\n" + "AQELBQADggEBAKLc+P5YfusNYIkX3YE+gHBVpo95xnoVUcsGr/h1zanCkmsyKkNU\n" + "e2w9xsVnRLgpRfwrnwiaNP/k6cPYt5ePPCJjUfkO7Ql7DCcjLgEp8lrvxMmRIdSg\n" + "LPq+NdityxXYhfaZdGdXjnLLiq3zYL/8aYjjZ8YAZTuu6pBgfGvjcqYLV1ohimrP\n" + "8BW0BbnvedqTyL7tdKjdiWnHE5ObrxnphL2evoStskBr5CLYR4vX7+qp0oVSz2Ol\n" + "nBMV3wXyhHBY1tuT1SK7ajC/ZHrciZosACRV5PC6nKXi3shWOxt76SZV3HcMmFwX\n" + "NQYYTBOlb5U080adFSmP5/6NRzrKwZ3mD2s=\n" + "-----END CERTIFICATE-----\n"; + +static constexpr char server_key[] = "-----BEGIN RSA PRIVATE KEY-----\n" + "MIIEpAIBAAKCAQEAu9I+tijlpLqjbA4Zfu7qihUukzHCWC3WKx9WCJ8JWIbTjEjF\n" + "sdoT0mGASs1UKS74Xtiz24S8HFSXauGqkDYzaca6cVv2CGOc8QkVYELIHyl4ioFE\n" + "dBvhdzEB7JtWCKsUFNjniZXSGVATNmDA6fIdQl0wLxmgGWl7KjvVL0dmcCWSfhhJ\n" + "WDZTcIXUraiDPL9STRY3zt4FpFak7/kDOH5qa6K9qafAmg67noOD60mN2A/1hu+a\n" + "5Jpa1turdEPH45J/rP1IkN1fyJD9fjH6yVh+kS9gxowb9inzQIpONsVlIil82jXA\n" + "xTeIY6lRufTvWz51A59cD8qWP01weIqkgJcbqQIDAQABAoIBADI3ShEF6jAavmq7\n" + "clGfqxF0DFnKaf2Nc79fx27SpnsGwTS2mDSu67HJ47UcJK5GIp2pLp04ZdrlOv6W\n" + "izW3aBOV0G9SePtRNrqzBQYRlNPQEKxnV1f7xFJLxgnulhgHNX1FaNI+PkgKQri9\n" + "MZba5rvBkoplPYrNyuJF0P+tBVRiISWDY00PlZ57pQDyOvXzUckAkxmjNzo+86ld\n" + "/NyO+nR45vVKSeIBT5tT67D8wRisZgO/7QKP5sbKYwa7AR4sTEYFwBaFi4Mr6v1T\n" + "kp0KxOFBI+MioFwyK7ZjkoKClrY/K0IPsKfn2vmi6jLpfkA+qCl1JsVhrfVO3KJc\n" + "PXXF4QECgYEA9339GQS2AWSuA/9ZgHFqTTOEEHujCkh9D4mKO4LRi5hKPN9NQKUU\n" + "KgaBXWTbr8FwOTXw6HMl0SaIOdc6VxdzViNvPCpu2Wn8hyTC5Mjs/BtXkXNcBQqs\n" + "tPm0JxgC6fpQAb+gU+zZ+QQNlUWH/CEiQFxxGNzBn9E3Xq2j0StdhPECgYEAwkci\n" + "GiQuM4KMDdwbs4RDlEZyvXxWwgHKPoXv/Uq7HXtuT1FGb/+Rf3BGimMf2Qqmppp8\n" + "MAZ+xk+eXhtqKZHsV2ifhUfuVZ6NPhT2WRyn6MozuHh3MK4l2KtOhxulcoX/2sDk\n" + "dLYclxhXZFuXvbLz2KpgMmPMGyzEQNHQaoTkojkCgYEAxb/wVGY0OybD+EO2su9s\n" + "PaVU94qielvzOU/vmJ9taTnUz5Co/Gcqlm2+Pe6RrnxEfCICjOk8pUJBhN3ZKq99\n" + "I62Keqt5CNUrxpvz8bQtzz7VmE1xkEG4P55pePcxlNzBwrPnmkdc3yCC7euxvR6I\n" + "bJ6wa2owd89Gi6r4gvBAeDECgYBpdiPU/P73h05v16RR9uKYgwWWRwDxn/chqaN1\n" + "ZDPe9ToUZJJQCfP5sgEY7mZDc7yzg/kWOPBoxp+5hjhDCKu7Z1fxCfMfF0qlAMwZ\n" + "46xieiFJaluJWX/B9nxSa3eMi6EwJrXdhV5Pxy7pk67zk0k7vIEr2XDa75o5dawl\n" + "pq5WQQKBgQC9xsRLtQjnDEdNEgCicTupa7BXmvc9tRb1mA5SeqjwzYuulrTyvn5Y\n" + "QOXYdz8aeZ+ZQ/cDeGA3jA6lekWnExkp9enHeqadyDWM7rvXi800E6gB/vrO7r/c\n" + "iE+fpXud6cwNw2XYsk6RBSQ8qhJoCpa+koPXfSJOZ9Y89NMbtq0w3Q==\n" + "-----END RSA PRIVATE KEY-----\n"; diff --git a/iocore/net/quic/test/test_QUICAckFrameCreator.cc b/iocore/net/quic/test/test_QUICAckFrameCreator.cc new file mode 100644 index 00000000000..99b40e73555 --- /dev/null +++ b/iocore/net/quic/test/test_QUICAckFrameCreator.cc @@ -0,0 +1,409 @@ +/** @file + * + * A brief file description + * + * @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 "catch.hpp" + +#include "quic/QUICAckFrameCreator.h" + +TEST_CASE("QUICAckFrameManager", "[quic]") +{ + QUICAckFrameManager ack_manager; + QUICEncryptionLevel level = QUICEncryptionLevel::INITIAL; + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + + // Initial state + QUICFrame *ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0); + QUICAckFrame *frame = static_cast(ack_frame); + CHECK(frame == nullptr); + + // One packet + ack_manager.update(level, 1, 1, false); + ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0); + frame = static_cast(ack_frame); + CHECK(frame != nullptr); + CHECK(frame->ack_block_count() == 0); + CHECK(frame->largest_acknowledged() == 1); + CHECK(frame->ack_block_section()->first_ack_block() == 0); + + // retry + CHECK(ack_manager.will_generate_frame(level, 0) == false); + + // Not sequential + ack_manager.update(level, 2, 1, false); + ack_manager.update(level, 5, 1, false); + ack_manager.update(level, 3, 1, false); + ack_manager.update(level, 4, 1, false); + ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0); + frame = static_cast(ack_frame); + CHECK(frame != nullptr); + CHECK(frame->ack_block_count() == 0); + CHECK(frame->largest_acknowledged() == 5); + CHECK(frame->ack_block_section()->first_ack_block() == 4); + + // Loss + ack_manager.update(level, 6, 1, false); + ack_manager.update(level, 7, 1, false); + ack_manager.update(level, 10, 1, false); + ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0); + frame = static_cast(ack_frame); + CHECK(frame != nullptr); + CHECK(frame->ack_block_count() == 1); + CHECK(frame->largest_acknowledged() == 10); + CHECK(frame->ack_block_section()->first_ack_block() == 0); + CHECK(frame->ack_block_section()->begin()->gap() == 1); + + // on frame acked + ack_manager.on_frame_acked(frame->id()); + + CHECK(ack_manager.will_generate_frame(level, 0) == false); + ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0); + CHECK(ack_frame == nullptr); + + ack_manager.update(level, 11, 1, false); + ack_manager.update(level, 12, 1, false); + ack_manager.update(level, 13, 1, false); + ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0); + frame = static_cast(ack_frame); + CHECK(frame != nullptr); + CHECK(frame->ack_block_count() == 0); + CHECK(frame->largest_acknowledged() == 13); + CHECK(frame->ack_block_section()->first_ack_block() == 2); + CHECK(frame->ack_block_section()->begin()->gap() == 0); + + ack_manager.on_frame_acked(frame->id()); + + // ack-only + ack_manager.update(level, 14, 1, true); + ack_manager.update(level, 15, 1, true); + ack_manager.update(level, 16, 1, true); + CHECK(ack_manager.will_generate_frame(level, 0) == false); + ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0); + + ack_manager.update(level, 17, 1, false); + ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0); + frame = static_cast(ack_frame); + CHECK(frame != nullptr); + CHECK(frame->ack_block_count() == 0); + CHECK(frame->largest_acknowledged() == 17); + CHECK(frame->ack_block_section()->first_ack_block() == 3); + CHECK(frame->ack_block_section()->begin()->gap() == 0); +} + +TEST_CASE("QUICAckFrameManager should send", "[quic]") +{ + SECTION("QUIC unorder packet", "[quic]") + { + QUICAckFrameManager ack_manager; + + QUICEncryptionLevel level = QUICEncryptionLevel::ONE_RTT; + ack_manager.update(level, 2, 1, false); + CHECK(ack_manager.will_generate_frame(level, 0) == true); + } + + SECTION("QUIC delay ack and unorder packet", "[quic]") + { + QUICAckFrameManager ack_manager; + + QUICEncryptionLevel level = QUICEncryptionLevel::ONE_RTT; + ack_manager.update(level, 0, 1, false); + CHECK(ack_manager.will_generate_frame(level, 0) == false); + + ack_manager.update(level, 1, 1, false); + CHECK(ack_manager.will_generate_frame(level, 0) == false); + + ack_manager.update(level, 3, 1, false); + CHECK(ack_manager.will_generate_frame(level, 0) == true); + } + + SECTION("QUIC delay too much time", "[quic]") + { + Thread::get_hrtime_updated(); + QUICAckFrameManager ack_manager; + + QUICEncryptionLevel level = QUICEncryptionLevel::ONE_RTT; + ack_manager.update(level, 0, 1, false); + CHECK(ack_manager.will_generate_frame(level, 0) == false); + + sleep(1); + Thread::get_hrtime_updated(); + ack_manager.update(level, 1, 1, false); + CHECK(ack_manager.will_generate_frame(level, 0) == true); + } + + SECTION("QUIC intial packet", "[quic]") + { + QUICAckFrameManager ack_manager; + + QUICEncryptionLevel level = QUICEncryptionLevel::INITIAL; + ack_manager.update(level, 0, 1, false); + CHECK(ack_manager.will_generate_frame(level, 0) == true); + } + + SECTION("QUIC handshake packet", "[quic]") + { + QUICAckFrameManager ack_manager; + + QUICEncryptionLevel level = QUICEncryptionLevel::HANDSHAKE; + ack_manager.update(level, 0, 1, false); + CHECK(ack_manager.will_generate_frame(level, 0) == true); + } + + SECTION("QUIC frame fired", "[quic]") + { + QUICAckFrameManager ack_manager; + QUICEncryptionLevel level = QUICEncryptionLevel::ONE_RTT; + + ack_manager.update(level, 0, 1, false); + CHECK(ack_manager.will_generate_frame(level, 0) == false); + + sleep(1); + Thread::get_hrtime_updated(); + CHECK(ack_manager.will_generate_frame(level, 0) == true); + } + + SECTION("QUIC refresh frame", "[quic]") + { + QUICAckFrameManager ack_manager; + QUICEncryptionLevel level = QUICEncryptionLevel::ONE_RTT; + + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + QUICFrame *ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0); + QUICAckFrame *frame = static_cast(ack_frame); + CHECK(frame == nullptr); + + // unorder frame should sent immediately + ack_manager.update(level, 1, 1, false); + CHECK(ack_manager.will_generate_frame(level, 0) == true); + + ack_manager.update(level, 2, 1, false); + + // Delay due to some reason, the frame is not valued any more, but still valued + sleep(1); + Thread::get_hrtime_updated(); + CHECK(ack_manager.will_generate_frame(level, 0) == true); + ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0); + frame = static_cast(ack_frame); + + CHECK(frame->ack_block_count() == 0); + CHECK(frame->largest_acknowledged() == 2); + CHECK(frame->ack_block_section()->first_ack_block() == 1); + CHECK(frame->ack_block_section()->begin()->gap() == 0); + } +} + +TEST_CASE("QUICAckFrameManager_loss_recover", "[quic]") +{ + QUICAckFrameManager ack_manager; + QUICEncryptionLevel level = QUICEncryptionLevel::INITIAL; + + // Initial state + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + QUICFrame *ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0); + QUICAckFrame *frame = static_cast(ack_frame); + CHECK(frame == nullptr); + + ack_manager.update(level, 2, 1, false); + ack_manager.update(level, 5, 1, false); + ack_manager.update(level, 6, 1, false); + ack_manager.update(level, 8, 1, false); + ack_manager.update(level, 9, 1, false); + + ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0); + frame = static_cast(ack_frame); + CHECK(frame != nullptr); + CHECK(frame->ack_block_count() == 2); + CHECK(frame->largest_acknowledged() == 9); + CHECK(frame->ack_block_section()->first_ack_block() == 1); + CHECK(frame->ack_block_section()->begin()->gap() == 0); + + CHECK(ack_manager.will_generate_frame(level, 0) == false); + + ack_manager.update(level, 7, 1, false); + ack_manager.update(level, 4, 1, false); + ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0); + frame = static_cast(ack_frame); + CHECK(frame != nullptr); + CHECK(frame->ack_block_count() == 1); + CHECK(frame->largest_acknowledged() == 9); + CHECK(frame->ack_block_section()->first_ack_block() == 5); + CHECK(frame->ack_block_section()->begin()->gap() == 0); +} + +TEST_CASE("QUICAckFrameManager_QUICAckFrameCreator", "[quic]") +{ + QUICAckFrameManager ack_manager; + QUICAckFrameManager::QUICAckFrameCreator packet_numbers(QUICPacketNumberSpace::Initial, &ack_manager); + + CHECK(packet_numbers.size() == 0); + CHECK(packet_numbers.largest_ack_number() == 0); + CHECK(packet_numbers.largest_ack_received_time() == 0); + + Thread::get_hrtime_updated(); + + packet_numbers.push_back(3, 2, false); + CHECK(packet_numbers.size() == 1); + CHECK(packet_numbers.largest_ack_number() == 3); + + ink_hrtime ti = packet_numbers.largest_ack_received_time(); + CHECK(packet_numbers.largest_ack_received_time() != 0); + + Thread::get_hrtime_updated(); + + packet_numbers.push_back(2, 2, false); + CHECK(packet_numbers.size() == 2); + CHECK(packet_numbers.largest_ack_number() == 3); + CHECK(packet_numbers.largest_ack_received_time() == ti); + + Thread::get_hrtime_updated(); + + packet_numbers.push_back(10, 2, false); + CHECK(packet_numbers.size() == 3); + CHECK(packet_numbers.largest_ack_number() == 10); + CHECK(packet_numbers.largest_ack_received_time() > ti); + + Thread::get_hrtime_updated(); + + packet_numbers.clear(); + CHECK(packet_numbers.size() == 0); + CHECK(packet_numbers.largest_ack_number() == 0); + CHECK(packet_numbers.largest_ack_received_time() == 0); +} + +TEST_CASE("QUICAckFrameManager lost_frame", "[quic]") +{ + QUICAckFrameManager ack_manager; + QUICEncryptionLevel level = QUICEncryptionLevel::INITIAL; + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + + // Initial state + QUICFrame *ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0); + QUICAckFrame *frame = static_cast(ack_frame); + CHECK(frame == nullptr); + + ack_manager.update(level, 2, 1, false); + ack_manager.update(level, 5, 1, false); + ack_manager.update(level, 6, 1, false); + ack_manager.update(level, 8, 1, false); + ack_manager.update(level, 9, 1, false); + + ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0); + frame = static_cast(ack_frame); + CHECK(frame != nullptr); + CHECK(frame->ack_block_count() == 2); + CHECK(frame->largest_acknowledged() == 9); + CHECK(frame->ack_block_section()->first_ack_block() == 1); + CHECK(frame->ack_block_section()->begin()->gap() == 0); + + ack_manager.on_frame_lost(frame->id()); + CHECK(ack_manager.will_generate_frame(level, 0) == true); + ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0); + frame = static_cast(ack_frame); + CHECK(frame != nullptr); + CHECK(frame->ack_block_count() == 2); + CHECK(frame->largest_acknowledged() == 9); + CHECK(frame->ack_block_section()->first_ack_block() == 1); + CHECK(frame->ack_block_section()->begin()->gap() == 0); + + CHECK(ack_manager.will_generate_frame(level, 0) == false); + ack_manager.on_frame_lost(frame->id()); + CHECK(ack_manager.will_generate_frame(level, 0) == true); + ack_manager.update(level, 7, 1, false); + ack_manager.update(level, 4, 1, false); + + ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0); + frame = static_cast(ack_frame); + CHECK(frame != nullptr); + CHECK(frame->ack_block_count() == 1); + CHECK(frame->largest_acknowledged() == 9); + CHECK(frame->ack_block_section()->first_ack_block() == 5); + CHECK(frame->ack_block_section()->begin()->gap() == 0); + + CHECK(ack_manager.will_generate_frame(level, 0) == false); +} + +TEST_CASE("QUICAckFrameManager ack only packet", "[quic]") +{ + SECTION("INITIAL") + { + QUICAckFrameManager ack_manager; + QUICEncryptionLevel level = QUICEncryptionLevel::INITIAL; + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + + // Initial state + QUICFrame *ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0); + QUICAckFrame *frame = static_cast(ack_frame); + CHECK(frame == nullptr); + + ack_manager.update(level, 1, 1, false); + ack_manager.update(level, 2, 1, false); + ack_manager.update(level, 3, 1, false); + ack_manager.update(level, 4, 1, false); + ack_manager.update(level, 5, 1, false); + + CHECK(ack_manager.will_generate_frame(level, 0) == true); + + ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0); + frame = static_cast(ack_frame); + CHECK(frame != nullptr); + CHECK(frame->ack_block_count() == 0); + CHECK(frame->largest_acknowledged() == 5); + CHECK(frame->ack_block_section()->first_ack_block() == 4); + CHECK(frame->ack_block_section()->begin()->gap() == 0); + + ack_manager.update(level, 6, 1, true); + ack_manager.update(level, 7, 1, true); + CHECK(ack_manager.will_generate_frame(level, 0) == false); + } + + SECTION("ONE_RTT") + { + QUICAckFrameManager ack_manager; + QUICEncryptionLevel level = QUICEncryptionLevel::ONE_RTT; + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + + // Initial state + QUICFrame *ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0); + QUICAckFrame *frame = static_cast(ack_frame); + CHECK(frame == nullptr); + + ack_manager.update(level, 1, 1, false); + ack_manager.update(level, 2, 1, false); + ack_manager.update(level, 3, 1, false); + ack_manager.update(level, 4, 1, false); + ack_manager.update(level, 5, 1, false); + + CHECK(ack_manager.will_generate_frame(level, 0) == true); + + ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0); + frame = static_cast(ack_frame); + CHECK(frame != nullptr); + CHECK(frame->ack_block_count() == 0); + CHECK(frame->largest_acknowledged() == 5); + CHECK(frame->ack_block_section()->first_ack_block() == 4); + CHECK(frame->ack_block_section()->begin()->gap() == 0); + + ack_manager.update(level, 6, 1, true); + ack_manager.update(level, 7, 1, true); + CHECK(ack_manager.will_generate_frame(level, 0) == false); + } +} diff --git a/iocore/net/quic/test/test_QUICAddrVerifyState.cc b/iocore/net/quic/test/test_QUICAddrVerifyState.cc new file mode 100644 index 00000000000..a90135c0bd6 --- /dev/null +++ b/iocore/net/quic/test/test_QUICAddrVerifyState.cc @@ -0,0 +1,61 @@ +/** @file + * + * A brief file description + * + * @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 "catch.hpp" + +#include "P_QUICNetVConnection.h" +#include + +TEST_CASE("QUICAddrVerifyState", "[quic]") +{ + QUICAddrVerifyState state; + + // without consuming + CHECK(state.windows() == 0); + state.fill(10240); + CHECK(state.windows() == 10240 * 3); + + // consume + CHECK(state.windows() == 10240 * 3); + state.consume(10240); + CHECK(state.windows() == 10240 * 2); + state.consume(10240); + CHECK(state.windows() == 10240); + state.consume(10240); + CHECK(state.windows() == 0); + + // fill + state.fill(1); + CHECK(state.windows() == 3); + state.consume(1); + CHECK(state.windows() == 2); + state.consume(1); + CHECK(state.windows() == 1); + state.consume(1); + CHECK(state.windows() == 0); + + // fill overflow + state.fill(UINT32_MAX); + state.fill(2); + CHECK(state.windows() == UINT32_MAX); +} diff --git a/iocore/net/quic/test/test_QUICAltConnectionManager.cc b/iocore/net/quic/test/test_QUICAltConnectionManager.cc new file mode 100644 index 00000000000..d30da7b390a --- /dev/null +++ b/iocore/net/quic/test/test_QUICAltConnectionManager.cc @@ -0,0 +1,97 @@ +/** @file + * + * A brief file description + * + * @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 "catch.hpp" + +#include "quic/QUICAltConnectionManager.h" +#include "quic/QUICIntUtil.h" +#include + +TEST_CASE("QUICPreferredAddress", "[quic]") +{ + uint8_t buf[] = { + 0x12, 0x34, 0x56, 0x78, // IPv4 address + 0x23, 0x45, // IPv4 port + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, // IPv6 address + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x34, 0x56, // IPv6 port + 0x01, // ConnectionId length + 0x55, // ConnectionId + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, // Stateless Reset Token + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, + }; + uint8_t cid_buf[] = {0x55}; + QUICConnectionId cid55(cid_buf, sizeof(cid_buf)); + in6_addr ipv6_addr; + memcpy(ipv6_addr.s6_addr, "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", 16); + + SECTION("load") + { + auto pref_addr = new QUICPreferredAddress(buf, sizeof(buf)); + CHECK(pref_addr->is_available()); + CHECK(pref_addr->has_ipv4()); + CHECK(pref_addr->endpoint_ipv4().isIp4()); + CHECK(pref_addr->endpoint_ipv4().host_order_port() == 0x2345); + CHECK(pref_addr->endpoint_ipv4().sin.sin_addr.s_addr == 0x78563412); + CHECK(pref_addr->has_ipv6()); + CHECK(pref_addr->endpoint_ipv6().isIp6()); + CHECK(pref_addr->endpoint_ipv6().host_order_port() == 0x3456); + CHECK(memcmp(pref_addr->endpoint_ipv6().sin6.sin6_addr.s6_addr, ipv6_addr.s6_addr, 16) == 0); + CHECK(pref_addr->cid() == cid55); + CHECK(memcmp(pref_addr->token().buf(), buf + 26, 16) == 0); + } + + SECTION("store") + { + IpEndpoint ep_ipv4; + ats_ip4_set(&ep_ipv4, 0x78563412, 0x4523); + + IpEndpoint ep_ipv6; + ats_ip6_set(&ep_ipv6, ipv6_addr, 0x5634); + + auto pref_addr = new QUICPreferredAddress(ep_ipv4, ep_ipv6, cid55, {buf + 26}); + CHECK(pref_addr->is_available()); + CHECK(pref_addr->has_ipv4()); + CHECK(pref_addr->endpoint_ipv4().isIp4()); + CHECK(pref_addr->endpoint_ipv4().host_order_port() == 0x2345); + CHECK(pref_addr->endpoint_ipv4().sin.sin_addr.s_addr == 0x78563412); + CHECK(pref_addr->has_ipv6()); + CHECK(pref_addr->endpoint_ipv6().isIp6()); + CHECK(pref_addr->endpoint_ipv6().host_order_port() == 0x3456); + CHECK(memcmp(pref_addr->endpoint_ipv6().sin6.sin6_addr.s6_addr, ipv6_addr.s6_addr, 16) == 0); + CHECK(pref_addr->cid() == cid55); + + uint8_t actual[QUICPreferredAddress::MAX_LEN]; + uint16_t len; + pref_addr->store(actual, len); + CHECK(sizeof(buf) == len); + CHECK(memcmp(buf, actual, sizeof(buf)) == 0); + } + + SECTION("unavailable") + { + auto pref_addr = new QUICPreferredAddress(nullptr, 0); + CHECK(!pref_addr->is_available()); + CHECK(!pref_addr->has_ipv4()); + CHECK(!pref_addr->has_ipv6()); + } +} diff --git a/iocore/net/quic/test/test_QUICFlowController.cc b/iocore/net/quic/test/test_QUICFlowController.cc new file mode 100644 index 00000000000..d58a31172a7 --- /dev/null +++ b/iocore/net/quic/test/test_QUICFlowController.cc @@ -0,0 +1,491 @@ +/** @file + * + * A brief file description + * + * @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 "catch.hpp" + +#include "quic/QUICFlowController.h" +#include "quic/Mock.h" +#include + +static constexpr int DEFAULT_RTT = 1 * HRTIME_SECOND; + +class MockRTTProvider : public QUICRTTProvider +{ +public: + ink_hrtime + smoothed_rtt() const override + { + return this->_smoothed_rtt; + } + + MockRTTProvider(ink_hrtime rtt) : _smoothed_rtt(rtt) {} + void + set_smoothed_rtt(ink_hrtime rtt) + { + this->_smoothed_rtt = rtt; + } + + ink_hrtime + latest_rtt() const override + { + return HRTIME_MSECONDS(1); + } + + ink_hrtime + rttvar() const override + { + return HRTIME_MSECONDS(1); + } + + ink_hrtime + congestion_period(uint32_t period) const override + { + return HRTIME_MSECONDS(1); + } + +private: + ink_hrtime _smoothed_rtt = 0; +}; + +TEST_CASE("QUICFlowController_Local_Connection", "[quic]") +{ + int ret = 0; + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + MockRTTProvider rp(DEFAULT_RTT); + QUICLocalConnectionFlowController fc(&rp, 1024); + + // Check initial state + CHECK(fc.current_offset() == 0); + CHECK(fc.current_limit() == 1024); + + ret = fc.update(256); + CHECK(fc.current_offset() == 256); + CHECK(fc.current_limit() == 1024); + CHECK(ret == 0); + + ret = fc.update(512); + CHECK(fc.current_offset() == 512); + CHECK(fc.current_limit() == 1024); + CHECK(ret == 0); + + // Retransmit + ret = fc.update(512); + CHECK(fc.current_offset() == 512); + CHECK(fc.current_limit() == 1024); + CHECK(ret == 0); + + ret = fc.update(1024); + CHECK(fc.current_offset() == 1024); + CHECK(fc.current_limit() == 1024); + CHECK(ret == 0); + + // Delay + ret = fc.update(512); + CHECK(fc.current_offset() == 1024); + CHECK(fc.current_limit() == 1024); + CHECK(ret == 0); + Thread::get_hrtime_updated(); + + // Exceed limit + ret = fc.update(1280); + CHECK(fc.current_offset() == 1024); + CHECK(fc.current_limit() == 1024); + CHECK(ret != 0); + + // MAX_STREAM_DATA + fc.forward_limit(2048); + CHECK(fc.current_offset() == 1024); + CHECK(fc.current_limit() == 2048); + QUICFrame *frame = fc.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 0, 1024, 0); + CHECK(frame); + CHECK(frame->type() == QUICFrameType::MAX_DATA); + + ret = fc.update(1280); + CHECK(fc.current_offset() == 1280); + CHECK(fc.current_limit() == 2048); + CHECK(ret == 0); +} + +TEST_CASE("QUICFlowController_Remote_Connection", "[quic]") +{ + int ret = 0; + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + QUICRemoteConnectionFlowController fc(1024); + + // Check initial state + CHECK(fc.current_offset() == 0); + CHECK(fc.current_limit() == 1024); + + ret = fc.update(256); + CHECK(fc.current_offset() == 256); + CHECK(fc.current_limit() == 1024); + CHECK(ret == 0); + + ret = fc.update(512); + CHECK(fc.current_offset() == 512); + CHECK(fc.current_limit() == 1024); + CHECK(ret == 0); + + // Retransmit + ret = fc.update(512); + CHECK(fc.current_offset() == 512); + CHECK(fc.current_limit() == 1024); + CHECK(ret == 0); + + ret = fc.update(1000); + CHECK(fc.current_offset() == 1000); + CHECK(fc.current_limit() == 1024); + CHECK(ret == 0); + + // Delay + ret = fc.update(512); + CHECK(fc.current_offset() == 1000); + CHECK(fc.current_limit() == 1024); + CHECK(ret == 0); + + // Exceed limit + ret = fc.update(1280); + CHECK(fc.current_offset() == 1000); + CHECK(fc.current_limit() == 1024); + CHECK(ret != 0); + QUICFrame *frame = fc.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 0, 1024, 0); + CHECK(frame); + CHECK(frame->type() == QUICFrameType::DATA_BLOCKED); + + // MAX_STREAM_DATA + fc.forward_limit(2048); + CHECK(fc.current_offset() == 1000); + CHECK(fc.current_limit() == 2048); + + ret = fc.update(1280); + CHECK(fc.current_offset() == 1280); + CHECK(fc.current_limit() == 2048); + CHECK(ret == 0); +} + +TEST_CASE("QUICFlowController_Remote_Connection_ZERO_Credit", "[quic]") +{ + int ret = 0; + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + QUICRemoteConnectionFlowController fc(1024); + + // Check initial state + CHECK(fc.current_offset() == 0); + CHECK(fc.current_limit() == 1024); + + // Zero credit + ret = fc.update(1024); + CHECK(fc.current_offset() == 1024); + CHECK(fc.current_limit() == 1024); + CHECK(ret == 0); + + CHECK(fc.will_generate_frame(QUICEncryptionLevel::ONE_RTT, 0)); + // if there're anything to send + QUICFrame *frame = fc.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 0, 1024, 0); + CHECK(frame); + CHECK(frame->type() == QUICFrameType::DATA_BLOCKED); + + // MAX_STREAM_DATA + fc.forward_limit(2048); + CHECK(fc.current_offset() == 1024); + CHECK(fc.current_limit() == 2048); + + ret = fc.update(1280); + CHECK(fc.current_offset() == 1280); + CHECK(fc.current_limit() == 2048); + CHECK(ret == 0); +} + +TEST_CASE("QUICFlowController_Local_Stream", "[quic]") +{ + int ret = 0; + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + MockRTTProvider rp(DEFAULT_RTT); + QUICLocalStreamFlowController fc(&rp, 1024, 0); + + // Check initial state + CHECK(fc.current_offset() == 0); + CHECK(fc.current_limit() == 1024); + + ret = fc.update(256); + CHECK(fc.current_offset() == 256); + CHECK(fc.current_limit() == 1024); + CHECK(ret == 0); + + ret = fc.update(512); + CHECK(fc.current_offset() == 512); + CHECK(fc.current_limit() == 1024); + CHECK(ret == 0); + + // Retransmit + ret = fc.update(512); + CHECK(fc.current_offset() == 512); + CHECK(fc.current_limit() == 1024); + CHECK(ret == 0); + + ret = fc.update(1024); + CHECK(fc.current_offset() == 1024); + CHECK(fc.current_limit() == 1024); + CHECK(ret == 0); + + // Delay + ret = fc.update(512); + CHECK(fc.current_offset() == 1024); + CHECK(fc.current_limit() == 1024); + CHECK(ret == 0); + Thread::get_hrtime_updated(); + + // Exceed limit + ret = fc.update(1280); + CHECK(fc.current_offset() == 1024); + CHECK(fc.current_limit() == 1024); + CHECK(ret != 0); + + // MAX_STREAM_DATA + fc.forward_limit(2048); + CHECK(fc.current_offset() == 1024); + CHECK(fc.current_limit() == 2048); + QUICFrame *frame = fc.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 0, 1024, 0); + CHECK(frame); + CHECK(frame->type() == QUICFrameType::MAX_STREAM_DATA); + + ret = fc.update(1280); + CHECK(fc.current_offset() == 1280); + CHECK(fc.current_limit() == 2048); + CHECK(ret == 0); +} + +TEST_CASE("QUICFlowController_Remote_Stream", "[quic]") +{ + int ret = 0; + QUICRemoteStreamFlowController fc(1024, 0); + + // Check initial state + CHECK(fc.current_offset() == 0); + CHECK(fc.current_limit() == 1024); + + ret = fc.update(256); + CHECK(fc.current_offset() == 256); + CHECK(fc.current_limit() == 1024); + CHECK(ret == 0); + + ret = fc.update(512); + CHECK(fc.current_offset() == 512); + CHECK(fc.current_limit() == 1024); + CHECK(ret == 0); + + // Retransmit + ret = fc.update(512); + CHECK(fc.current_offset() == 512); + CHECK(fc.current_limit() == 1024); + CHECK(ret == 0); + + ret = fc.update(1024); + CHECK(fc.current_offset() == 1024); + CHECK(fc.current_limit() == 1024); + CHECK(ret == 0); + + CHECK(fc.credit() == 0); + CHECK(fc.will_generate_frame(QUICEncryptionLevel::ONE_RTT, 0)); + + // Delay + ret = fc.update(512); + CHECK(fc.current_offset() == 1024); + CHECK(fc.current_limit() == 1024); + CHECK(ret == 0); + + // Exceed limit + ret = fc.update(1280); + CHECK(fc.current_offset() == 1024); + CHECK(fc.current_limit() == 1024); + CHECK(ret != 0); + + // MAX_STREAM_DATA + fc.forward_limit(2048); + CHECK(fc.current_offset() == 1024); + CHECK(fc.current_limit() == 2048); + + ret = fc.update(1280); + CHECK(fc.current_offset() == 1280); + CHECK(fc.current_limit() == 2048); + CHECK(ret == 0); +} + +TEST_CASE("Frame retransmission", "[quic]") +{ + QUICEncryptionLevel level = QUICEncryptionLevel::ONE_RTT; + + SECTION("BLOCKED frame") + { + int ret = 0; + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + QUICRemoteConnectionFlowController fc(1024); + + // Check initial state + auto frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0); + CHECK(!frame); + + ret = fc.update(1024); + CHECK(ret == 0); + frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0); + REQUIRE(frame); + CHECK(static_cast(frame)->offset() == 1024); + QUICFrameId id = frame->id(); + + // Don't retransmit unless the frame is lost + frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0); + REQUIRE(!frame); + + // Retransmit + fc.on_frame_lost(id); + frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0); + REQUIRE(frame); + CHECK(static_cast(frame)->offset() == 1024); + + // Don't send if it was not blocked + fc.on_frame_lost(frame->id()); + fc.forward_limit(2048); + ret = fc.update(1536); + frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0); + CHECK(!frame); + + // This should not be retransmition + ret = fc.update(2048); + frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0); + REQUIRE(frame); + CHECK(static_cast(frame)->offset() == 2048); + } + + SECTION("STREAM_DATA_BLOCKED frame") + { + int ret = 0; + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + QUICRemoteStreamFlowController fc(1024, 0); + + // Check initial state + auto frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0); + CHECK(!frame); + + ret = fc.update(1024); + CHECK(ret == 0); + frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0); + REQUIRE(frame); + CHECK(static_cast(frame)->offset() == 1024); + QUICFrameId id = frame->id(); + + // Don't retransmit unless the frame is lost + frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0); + REQUIRE(!frame); + + // Retransmit + fc.on_frame_lost(id); + frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0); + REQUIRE(frame); + CHECK(static_cast(frame)->offset() == 1024); + + // Don't send if it was not blocked + fc.on_frame_lost(frame->id()); + fc.forward_limit(2048); + ret = fc.update(1536); + frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0); + CHECK(!frame); + + // This should not be retransmition + ret = fc.update(2048); + frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0); + REQUIRE(frame); + CHECK(static_cast(frame)->offset() == 2048); + } + + SECTION("MAX_DATA frame") + { + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + MockRTTProvider rp(DEFAULT_RTT); + QUICLocalConnectionFlowController fc(&rp, 1024); + + // Check initial state + auto frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0); + CHECK(!frame); + + fc.update(1024); + fc.forward_limit(1024); + frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0); + REQUIRE(frame); + CHECK(static_cast(frame)->maximum_data() == 1024); + QUICFrameId id = frame->id(); + + // Don't retransmit unless the frame is lost + frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0); + REQUIRE(!frame); + + // Retransmit + fc.on_frame_lost(id); + frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0); + REQUIRE(frame); + CHECK(static_cast(frame)->maximum_data() == 1024); + + // Send new one if it was updated + fc.on_frame_lost(id); + fc.forward_limit(2048); + fc.update(2048); + frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0); + REQUIRE(frame); + CHECK(static_cast(frame)->maximum_data() == 2048); + } + + SECTION("MAX_STREAM_DATA frame") + { + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + MockRTTProvider rp(DEFAULT_RTT); + QUICLocalStreamFlowController fc(&rp, 1024, 0); + + // Check initial state + auto frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0); + CHECK(!frame); + + fc.update(1024); + fc.forward_limit(1024); + frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0); + REQUIRE(frame); + CHECK(static_cast(frame)->maximum_stream_data() == 1024); + QUICFrameId id = frame->id(); + + // Don't retransmit unless the frame is lost + frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0); + REQUIRE(!frame); + + // Retransmit + fc.on_frame_lost(id); + frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0); + REQUIRE(frame); + CHECK(static_cast(frame)->maximum_stream_data() == 1024); + + // Send new one if it was updated + fc.on_frame_lost(id); + fc.forward_limit(2048); + fc.update(2048); + frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0); + REQUIRE(frame); + CHECK(static_cast(frame)->maximum_stream_data() == 2048); + } +} diff --git a/iocore/net/quic/test/test_QUICFrame.cc b/iocore/net/quic/test/test_QUICFrame.cc new file mode 100644 index 00000000000..a8f81fd5538 --- /dev/null +++ b/iocore/net/quic/test/test_QUICFrame.cc @@ -0,0 +1,1564 @@ +/** @file + * + * A brief file description + * + * @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 "catch.hpp" + +#include "quic/Mock.h" +#include "quic/QUICFrame.h" +#include "quic/QUICStream.h" + +extern const ink_freelist_ops *freelist_global_ops; +extern const ink_freelist_ops *freelist_class_ops; + +TEST_CASE("QUICFrame Type", "[quic]") +{ + CHECK(QUICFrame::type(reinterpret_cast("\x00")) == QUICFrameType::PADDING); + CHECK(QUICFrame::type(reinterpret_cast("\x01")) == QUICFrameType::PING); + + // Range of ACK + CHECK(QUICFrame::type(reinterpret_cast("\x02")) == QUICFrameType::ACK); + CHECK(QUICFrame::type(reinterpret_cast("\x03")) == QUICFrameType::ACK); + + CHECK(QUICFrame::type(reinterpret_cast("\x04")) == QUICFrameType::RESET_STREAM); + CHECK(QUICFrame::type(reinterpret_cast("\x05")) == QUICFrameType::STOP_SENDING); + CHECK(QUICFrame::type(reinterpret_cast("\x06")) == QUICFrameType::CRYPTO); + CHECK(QUICFrame::type(reinterpret_cast("\x07")) == QUICFrameType::NEW_TOKEN); + + // Range of STREAM + CHECK(QUICFrame::type(reinterpret_cast("\x08")) == QUICFrameType::STREAM); + CHECK(QUICFrame::type(reinterpret_cast("\x0f")) == QUICFrameType::STREAM); + + CHECK(QUICFrame::type(reinterpret_cast("\x10")) == QUICFrameType::MAX_DATA); + CHECK(QUICFrame::type(reinterpret_cast("\x11")) == QUICFrameType::MAX_STREAM_DATA); + + CHECK(QUICFrame::type(reinterpret_cast("\x12")) == QUICFrameType::MAX_STREAMS); + CHECK(QUICFrame::type(reinterpret_cast("\x13")) == QUICFrameType::MAX_STREAMS); + + CHECK(QUICFrame::type(reinterpret_cast("\x14")) == QUICFrameType::DATA_BLOCKED); + CHECK(QUICFrame::type(reinterpret_cast("\x15")) == QUICFrameType::STREAM_DATA_BLOCKED); + + CHECK(QUICFrame::type(reinterpret_cast("\x16")) == QUICFrameType::STREAMS_BLOCKED); + CHECK(QUICFrame::type(reinterpret_cast("\x17")) == QUICFrameType::STREAMS_BLOCKED); + + CHECK(QUICFrame::type(reinterpret_cast("\x18")) == QUICFrameType::NEW_CONNECTION_ID); + CHECK(QUICFrame::type(reinterpret_cast("\x19")) == QUICFrameType::RETIRE_CONNECTION_ID); + CHECK(QUICFrame::type(reinterpret_cast("\x1a")) == QUICFrameType::PATH_CHALLENGE); + CHECK(QUICFrame::type(reinterpret_cast("\x1b")) == QUICFrameType::PATH_RESPONSE); + + CHECK(QUICFrame::type(reinterpret_cast("\x1c")) == QUICFrameType::CONNECTION_CLOSE); + CHECK(QUICFrame::type(reinterpret_cast("\x1d")) == QUICFrameType::CONNECTION_CLOSE); + + // Undefined ragne + CHECK(QUICFrame::type(reinterpret_cast("\x1e")) == QUICFrameType::UNKNOWN); + CHECK(QUICFrame::type(reinterpret_cast("\xff")) == QUICFrameType::UNKNOWN); +} + +TEST_CASE("Load STREAM Frame", "[quic]") +{ + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + + SECTION("OLF=000") + { + uint8_t buf1[] = { + 0x08, // 0b00001OLF (OLF=000) + 0x01, // Stream ID + 0x01, 0x02, 0x03, 0x04, // Stream Data + }; + const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1)); + CHECK(frame1->type() == QUICFrameType::STREAM); + CHECK(frame1->size() == 6); + const QUICStreamFrame *stream_frame = static_cast(frame1); + CHECK(stream_frame->stream_id() == 0x01); + CHECK(stream_frame->offset() == 0x00); + CHECK(stream_frame->data_length() == 4); + CHECK(memcmp(stream_frame->data()->start(), "\x01\x02\x03\x04", 4) == 0); + CHECK(stream_frame->has_fin_flag() == false); + } + + SECTION("OLF=010") + { + uint8_t buf1[] = { + 0x0a, // 0b00001OLF (OLF=010) + 0x01, // Stream ID + 0x05, // Data Length + 0x01, 0x02, 0x03, 0x04, 0x05, // Stream Data + }; + const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1)); + CHECK(frame1->type() == QUICFrameType::STREAM); + CHECK(frame1->size() == 8); + const QUICStreamFrame *stream_frame = static_cast(frame1); + CHECK(stream_frame->stream_id() == 0x01); + CHECK(stream_frame->offset() == 0x00); + CHECK(stream_frame->data_length() == 5); + CHECK(memcmp(stream_frame->data()->start(), "\x01\x02\x03\x04\x05", 5) == 0); + CHECK(stream_frame->has_fin_flag() == false); + } + + SECTION("OLF=110") + { + uint8_t buf1[] = { + 0x0e, // 0b00001OLF (OLF=110) + 0x01, // Stream ID + 0x02, // Data Length + 0x05, // Data Length + 0x01, 0x02, 0x03, 0x04, 0x05, // Stream Data + }; + const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1)); + CHECK(frame1->type() == QUICFrameType::STREAM); + CHECK(frame1->size() == 9); + + const QUICStreamFrame *stream_frame = static_cast(frame1); + CHECK(stream_frame->stream_id() == 0x01); + CHECK(stream_frame->offset() == 0x02); + CHECK(stream_frame->data_length() == 5); + CHECK(memcmp(stream_frame->data()->start(), "\x01\x02\x03\x04\x05", 5) == 0); + CHECK(stream_frame->has_fin_flag() == false); + } + + SECTION("OLF=111") + { + uint8_t buf1[] = { + 0x0f, // 0b00001OLF (OLF=111) + 0x01, // Stream ID + 0x02, // Data Length + 0x05, // Data Length + 0x01, 0x02, 0x03, 0x04, 0x05, // Stream Data + }; + const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1)); + CHECK(frame1->type() == QUICFrameType::STREAM); + CHECK(frame1->size() == 9); + + const QUICStreamFrame *stream_frame = static_cast(frame1); + CHECK(stream_frame->stream_id() == 0x01); + CHECK(stream_frame->offset() == 0x02); + CHECK(stream_frame->data_length() == 5); + CHECK(memcmp(stream_frame->data()->start(), "\x01\x02\x03\x04\x05", 5) == 0); + CHECK(stream_frame->has_fin_flag() == true); + } + + SECTION("BAD DATA") + { + uint8_t buf1[] = { + 0x0e, // 0b00001OLF (OLF=110) + 0x01, // Stream ID + 0x02, // Data Length + 0x05, // Data Length + 0x01, 0x02, 0x03, 0x04, // BAD Stream Data + }; + const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1)); + CHECK(frame1->type() == QUICFrameType::STREAM); + CHECK(frame1->valid() == false); + } + + SECTION("BAD DATA") + { + uint8_t buf1[] = { + 0x0e, // 0b00001OLF (OLF=110) + 0x01, // Stream ID + }; + const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1)); + CHECK(frame1->type() == QUICFrameType::STREAM); + CHECK(frame1->valid() == false); + } +} + +TEST_CASE("Store STREAM Frame", "[quic]") +{ + SECTION("8bit stream id, 0bit offset") + { + uint8_t buf[32] = {0}; + size_t len = 0; + uint8_t expected1[] = { + 0x0a, // 0b00001OLF (OLF=010) + 0x01, // Stream ID + 0x05, // Data Length + 0x01, 0x02, 0x03, 0x04, 0x05, // Stream Data + }; + + uint8_t raw1[] = "\x01\x02\x03\x04\x05"; + Ptr block = make_ptr(new_IOBufferBlock()); + block->alloc(); + memcpy(block->start(), raw1, 5); + block->fill(5); + + QUICStreamFrame stream_frame(block, 0x01, 0x00, false, false, true); + CHECK(stream_frame.size() == 8); + + Ptr ibb = stream_frame.to_io_buffer_block(sizeof(buf)); + for (auto b = ibb; b; b = b->next) { + memcpy(buf + len, b->start(), b->size()); + len += b->size(); + } + CHECK(len == 8); + CHECK(memcmp(buf, expected1, len) == 0); + } + + SECTION("8bit stream id, 16bit offset") + { + uint8_t buf[32] = {0}; + size_t len = 0; + uint8_t expected2[] = { + 0x0e, // 0b00001OLF (OLF=110) + 0x01, // Stream ID + 0x01, // Offset + 0x05, // Data Length + 0x01, 0x02, 0x03, 0x04, 0x05, // Stream Data + }; + uint8_t raw2[] = "\x01\x02\x03\x04\x05"; + Ptr block = make_ptr(new_IOBufferBlock()); + block->alloc(); + memcpy(block->start(), raw2, 5); + block->fill(5); + + QUICStreamFrame stream_frame(block, 0x01, 0x01); + CHECK(stream_frame.size() == 9); + + Ptr ibb = stream_frame.to_io_buffer_block(sizeof(buf)); + for (auto b = ibb; b; b = b->next) { + memcpy(buf + len, b->start(), b->size()); + len += b->size(); + } + CHECK(len == 9); + CHECK(memcmp(buf, expected2, len) == 0); + } + + SECTION("8bit stream id, 32bit offset") + { + uint8_t buf[32] = {0}; + size_t len = 0; + uint8_t expected3[] = { + 0x0e, // 0b00001OLF (OLF=110) + 0x01, // Stream ID + 0x80, 0x01, 0x00, 0x00, // Offset + 0x05, // Data Length + 0x01, 0x02, 0x03, 0x04, 0x05, // Stream Data + }; + uint8_t raw3[] = "\x01\x02\x03\x04\x05"; + Ptr block = make_ptr(new_IOBufferBlock()); + block->alloc(); + memcpy(block->start(), raw3, 5); + block->fill(5); + + QUICStreamFrame stream_frame(block, 0x01, 0x010000); + CHECK(stream_frame.size() == 12); + + Ptr ibb = stream_frame.to_io_buffer_block(sizeof(buf)); + for (auto b = ibb; b; b = b->next) { + memcpy(buf + len, b->start(), b->size()); + len += b->size(); + } + CHECK(len == 12); + CHECK(memcmp(buf, expected3, len) == 0); + } + + SECTION("8bit stream id, 64bit offset") + { + uint8_t buf[32] = {0}; + size_t len = 0; + uint8_t expected4[] = { + 0x0e, // 0b00001OLF (OLF=110) + 0x01, // Stream ID + 0xc0, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, // Offset + 0x05, // Data Length + 0x01, 0x02, 0x03, 0x04, 0x05, // Stream Data + }; + uint8_t raw4[] = "\x01\x02\x03\x04\x05"; + Ptr block = make_ptr(new_IOBufferBlock()); + block->alloc(); + memcpy(block->start(), raw4, 5); + block->fill(5); + + QUICStreamFrame stream_frame(block, 0x01, 0x0100000000); + CHECK(stream_frame.size() == 16); + + Ptr ibb = stream_frame.to_io_buffer_block(sizeof(buf)); + for (auto b = ibb; b; b = b->next) { + memcpy(buf + len, b->start(), b->size()); + len += b->size(); + } + CHECK(len == 16); + CHECK(memcmp(buf, expected4, len) == 0); + } + + SECTION("16bit stream id, 64bit offset") + { + uint8_t buf[32] = {0}; + size_t len = 0; + uint8_t expected5[] = { + 0x0e, // 0b00001OLF (OLF=110) + 0x41, 0x00, // Stream ID + 0xc0, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, // Offset + 0x05, // Data Length + 0x01, 0x02, 0x03, 0x04, 0x05, // Stream Data + }; + uint8_t raw5[] = "\x01\x02\x03\x04\x05"; + Ptr block = make_ptr(new_IOBufferBlock()); + block->alloc(); + memcpy(block->start(), raw5, 5); + block->fill(5); + + QUICStreamFrame stream_frame(block, 0x0100, 0x0100000000); + CHECK(stream_frame.size() == 17); + + Ptr ibb = stream_frame.to_io_buffer_block(sizeof(buf)); + for (auto b = ibb; b; b = b->next) { + memcpy(buf + len, b->start(), b->size()); + len += b->size(); + } + CHECK(len == 17); + CHECK(memcmp(buf, expected5, len) == 0); + } + + SECTION("24bit stream id, 64bit offset") + { + uint8_t buf[32] = {0}; + size_t len = 0; + uint8_t expected6[] = { + 0x0e, // 0b00001OLF (OLF=110) + 0x80, 0x01, 0x00, 0x00, // Stream ID + 0xc0, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, // Offset + 0x05, // Data Length + 0x01, 0x02, 0x03, 0x04, 0x05, // Stream Data + }; + uint8_t raw6[] = "\x01\x02\x03\x04\x05"; + Ptr block = make_ptr(new_IOBufferBlock()); + block->alloc(); + memcpy(block->start(), raw6, 5); + block->fill(5); + + QUICStreamFrame stream_frame(block, 0x010000, 0x0100000000); + CHECK(stream_frame.size() == 19); + + Ptr ibb = stream_frame.to_io_buffer_block(sizeof(buf)); + for (auto b = ibb; b; b = b->next) { + memcpy(buf + len, b->start(), b->size()); + len += b->size(); + } + CHECK(len == 19); + CHECK(memcmp(buf, expected6, len) == 0); + } + + SECTION("32bit stream id, 64bit offset") + { + uint8_t buf[32] = {0}; + size_t len = 0; + uint8_t expected7[] = { + 0x0e, // 0b00001OLF (OLF=110) + 0x81, 0x00, 0x00, 0x00, // Stream ID + 0xc0, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, // Offset + 0x05, // Data Length + 0x01, 0x02, 0x03, 0x04, 0x05, // Stream Data + }; + uint8_t raw7[] = "\x01\x02\x03\x04\x05"; + Ptr block = make_ptr(new_IOBufferBlock()); + block->alloc(); + memcpy(block->start(), raw7, 5); + block->fill(5); + + QUICStreamFrame stream_frame(block, 0x01000000, 0x0100000000); + CHECK(stream_frame.size() == 19); + + Ptr ibb = stream_frame.to_io_buffer_block(sizeof(buf)); + for (auto b = ibb; b; b = b->next) { + memcpy(buf + len, b->start(), b->size()); + len += b->size(); + } + CHECK(len == 19); + CHECK(memcmp(buf, expected7, len) == 0); + } + + SECTION("32bit stream id, 64bit offset, FIN bit") + { + uint8_t buf[32] = {0}; + size_t len = 0; + uint8_t expected[] = { + 0x0f, // 0b00001OLF (OLF=111) + 0x81, 0x00, 0x00, 0x00, // Stream ID + 0xc0, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, // Offset + 0x05, // Data Length + 0x01, 0x02, 0x03, 0x04, 0x05, // Stream Data + }; + uint8_t raw[] = "\x01\x02\x03\x04\x05"; + Ptr block = make_ptr(new_IOBufferBlock()); + block->alloc(); + memcpy(block->start(), raw, 5); + block->fill(5); + + QUICStreamFrame stream_frame(block, 0x01000000, 0x0100000000, true); + CHECK(stream_frame.size() == 19); + + Ptr ibb = stream_frame.to_io_buffer_block(sizeof(buf)); + for (auto b = ibb; b; b = b->next) { + memcpy(buf + len, b->start(), b->size()); + len += b->size(); + } + CHECK(len == 19); + CHECK(memcmp(buf, expected, len) == 0); + } +} + +TEST_CASE("CRYPTO Frame", "[quic]") +{ + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + SECTION("Loading") + { + uint8_t buf[] = { + 0x06, // Type + 0x80, 0x01, 0x00, 0x00, // Offset + 0x05, // Length + 0x01, 0x02, 0x03, 0x04, 0x05, // Crypto Data + }; + const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf)); + CHECK(frame->type() == QUICFrameType::CRYPTO); + CHECK(frame->size() == sizeof(buf)); + + const QUICCryptoFrame *crypto_frame = static_cast(frame); + CHECK(crypto_frame->offset() == 0x010000); + CHECK(crypto_frame->data_length() == 5); + CHECK(memcmp(crypto_frame->data()->start(), "\x01\x02\x03\x04\x05", 5) == 0); + } + + SECTION("BAD Loading") + { + uint8_t buf[] = { + 0x06, // Type + 0x80, 0x01, 0x00, 0x00, // Offset + }; + const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf)); + CHECK(frame->type() == QUICFrameType::CRYPTO); + CHECK(frame->valid() == false); + } + + SECTION("Storing") + { + uint8_t buf[32] = {0}; + size_t len; + uint8_t expected[] = { + 0x06, // Typr + 0x80, 0x01, 0x00, 0x00, // Offset + 0x05, // Length + 0x01, 0x02, 0x03, 0x04, 0x05, // Crypto Data + }; + uint8_t raw_data[] = "\x01\x02\x03\x04\x05"; + Ptr block = make_ptr(new_IOBufferBlock()); + block->alloc(); + memcpy(block->start(), raw_data, 5); + block->fill(5); + + QUICCryptoFrame crypto_frame(block, 0x010000); + CHECK(crypto_frame.size() == sizeof(expected)); + + crypto_frame.store(buf, &len, 32); + CHECK(len == sizeof(expected)); + CHECK(memcmp(buf, expected, sizeof(expected)) == 0); + } +} + +TEST_CASE("Load Ack Frame 1", "[quic]") +{ + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + SECTION("0 Ack Block, 8 bit packet number length, 8 bit block length") + { + uint8_t buf1[] = { + 0x02, // Type + 0x12, // Largest Acknowledged + 0x74, 0x56, // Ack Delay + 0x00, // Ack Block Count + 0x00, // Ack Block Section + }; + const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1)); + CHECK(frame1->type() == QUICFrameType::ACK); + CHECK(frame1->size() == 6); + const QUICAckFrame *ack_frame1 = static_cast(frame1); + CHECK(ack_frame1 != nullptr); + CHECK(ack_frame1->ack_block_count() == 0); + CHECK(ack_frame1->largest_acknowledged() == 0x12); + CHECK(ack_frame1->ack_delay() == 0x3456); + } + + SECTION("0 Ack Block, 8 bit packet number length, 8 bit block length") + { + uint8_t buf1[] = { + 0x02, // Type + 0x80, 0x00, 0x00, 0x01, // Largest Acknowledged + 0x41, 0x71, // Ack Delay + 0x00, // Ack Block Count + 0x80, 0x00, 0x00, 0x01, // Ack Block Section (First ACK Block Length) + }; + const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1)); + CHECK(frame1->type() == QUICFrameType::ACK); + CHECK(frame1->size() == 12); + + const QUICAckFrame *ack_frame1 = static_cast(frame1); + CHECK(ack_frame1 != nullptr); + CHECK(ack_frame1->largest_acknowledged() == 0x01); + CHECK(ack_frame1->ack_delay() == 0x0171); + CHECK(ack_frame1->ack_block_count() == 0); + CHECK(ack_frame1->ecn_section() == nullptr); + + const QUICAckFrame::AckBlockSection *section = ack_frame1->ack_block_section(); + CHECK(section->first_ack_block() == 0x01); + } + + SECTION("2 Ack Block, 8 bit packet number length, 8 bit block length") + { + uint8_t buf1[] = { + 0x02, // Type + 0x12, // Largest Acknowledged + 0x74, 0x56, // Ack Delay + 0x02, // Ack Block Count + 0x01, // Ack Block Section (First ACK Block Length) + 0x02, // Ack Block Section (Gap 1) + 0x43, 0x04, // Ack Block Section (ACK Block 1 Length) + 0x85, 0x06, 0x07, 0x08, // Ack Block Section (Gap 2) + 0xc9, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, // Ack Block Section (ACK Block 2 Length) + }; + + const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1)); + CHECK(frame1->type() == QUICFrameType::ACK); + CHECK(frame1->size() == 21); + const QUICAckFrame *ack_frame1 = static_cast(frame1); + CHECK(ack_frame1 != nullptr); + CHECK(ack_frame1->largest_acknowledged() == 0x12); + CHECK(ack_frame1->ack_delay() == 0x3456); + CHECK(ack_frame1->ack_block_count() == 2); + CHECK(ack_frame1->ecn_section() == nullptr); + + const QUICAckFrame::AckBlockSection *section = ack_frame1->ack_block_section(); + CHECK(section->first_ack_block() == 0x01); + auto ite = section->begin(); + CHECK(ite != section->end()); + CHECK(ite->gap() == 0x02); + CHECK(ite->length() == 0x0304); + ++ite; + CHECK(ite != section->end()); + CHECK(ite->gap() == 0x05060708); + CHECK(ite->length() == 0x090a0b0c0d0e0f10); + ++ite; + CHECK(ite == section->end()); + } + + SECTION("load bad frame") + { + uint8_t buf1[] = { + 0x02, // Type + 0x12, // Largest Acknowledged + }; + + const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1)); + CHECK(frame1->type() == QUICFrameType::ACK); + CHECK(frame1->valid() == false); + } + + SECTION("load bad block") + { + uint8_t buf1[] = { + 0x02, // Type + 0x12, // Largest Acknowledged + 0x74, 0x56, // Ack Delay + 0x02, // Ack Block Count + 0x01, // Ack Block Section (First ACK Block Length) + 0x02, // Ack Block Section (Gap 1) + 0x43, 0x04, // Ack Block Section (ACK Block 1 Length) + 0x85, 0x06, 0x07, 0x08, // Ack Block Section (Gap 2) + }; + + const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1)); + CHECK(frame1->type() == QUICFrameType::ACK); + CHECK(frame1->valid() == false); + } + + SECTION("0 Ack Block, 8 bit packet number length, 8 bit block length with ECN section") + { + uint8_t buf1[] = { + 0x03, // Type + 0x12, // Largest Acknowledged + 0x74, 0x56, // Ack Delay + 0x00, // Ack Block Count + 0x00, // Ack Block Section + // ECN + 0x01, // ECT0 + 0x02, // ECT1 + 0x03, // ECN-CE + }; + const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1)); + CHECK(frame1->type() == QUICFrameType::ACK); + CHECK(frame1->size() == 9); + const QUICAckFrame *ack_frame1 = static_cast(frame1); + CHECK(ack_frame1 != nullptr); + CHECK(ack_frame1->ack_block_count() == 0); + CHECK(ack_frame1->largest_acknowledged() == 0x12); + CHECK(ack_frame1->ack_delay() == 0x3456); + CHECK(ack_frame1->ecn_section()); + CHECK(ack_frame1->ecn_section()->ect0_count() == 1); + CHECK(ack_frame1->ecn_section()->ect1_count() == 2); + CHECK(ack_frame1->ecn_section()->ecn_ce_count() == 3); + } + + SECTION("0 Ack Block, 8 bit packet number length, 8 bit block length with ECN section") + { + uint8_t buf1[] = { + 0x03, // Type + 0x12, // Largest Acknowledged + 0x74, 0x56, // Ack Delay + 0x00, // Ack Block Count + 0x00, // Ack Block Section + // ECN + 0x01, // ECT0 + 0x02, // ECT1 + }; + const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1)); + CHECK(frame1->type() == QUICFrameType::ACK); + CHECK(frame1->valid() == false); + } +} + +TEST_CASE("Store Ack Frame", "[quic]") +{ + SECTION("0 Ack Block, 8 bit packet number length, 8 bit block length") + { + uint8_t buf[32] = {0}; + size_t len; + + uint8_t expected[] = { + 0x02, // Type + 0x12, // Largest Acknowledged + 0x74, 0x56, // Ack Delay + 0x00, // Ack Block Count + 0x00, // Ack Block Section + }; + + QUICAckFrame ack_frame(0x12, 0x3456, 0); + CHECK(ack_frame.size() == 6); + + ack_frame.store(buf, &len, 32); + CHECK(len == 6); + CHECK(memcmp(buf, expected, len) == 0); + } + + SECTION("2 Ack Block, 8 bit packet number length, 8 bit block length") + { + uint8_t buf[32] = {0}; + size_t len; + + uint8_t expected[] = { + 0x02, // Type + 0x12, // Largest Acknowledged + 0x74, 0x56, // Ack Delay + 0x02, // Ack Block Count + 0x01, // Ack Block Section (First ACK Block Length) + 0x02, // Ack Block Section (Gap 1) + 0x43, 0x04, // Ack Block Section (ACK Block 1 Length) + 0x85, 0x06, 0x07, 0x08, // Ack Block Section (Gap 2) + 0xc9, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, // Ack Block Section (ACK Block 2 Length) + }; + QUICAckFrame ack_frame(0x12, 0x3456, 0x01); + QUICAckFrame::AckBlockSection *section = ack_frame.ack_block_section(); + section->add_ack_block({0x02, 0x0304}); + section->add_ack_block({0x05060708, 0x090a0b0c0d0e0f10}); + CHECK(ack_frame.size() == 21); + + ack_frame.store(buf, &len, 32); + CHECK(len == 21); + CHECK(memcmp(buf, expected, len) == 0); + } +} + +TEST_CASE("Load RESET_STREAM Frame", "[quic]") +{ + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + SECTION("Load") + { + uint8_t buf1[] = { + 0x04, // Type + 0x92, 0x34, 0x56, 0x78, // Stream ID + 0x00, 0x01, // Error Code + 0xd1, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 // Final Offset + }; + const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1)); + CHECK(frame1->type() == QUICFrameType::RESET_STREAM); + CHECK(frame1->size() == 15); + const QUICRstStreamFrame *rst_stream_frame1 = static_cast(frame1); + CHECK(rst_stream_frame1 != nullptr); + CHECK(rst_stream_frame1->error_code() == 0x0001); + CHECK(rst_stream_frame1->stream_id() == 0x12345678); + CHECK(rst_stream_frame1->final_offset() == 0x1122334455667788); + } + + SECTION("BAD Load") + { + uint8_t buf1[] = { + 0x04, // Type + 0x92, 0x34, 0x56, 0x78, // Stream ID + 0x00, 0x01, // Error Code + }; + const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1)); + CHECK(frame1->type() == QUICFrameType::RESET_STREAM); + CHECK(frame1->valid() == false); + } +} + +TEST_CASE("Store RESET_STREAM Frame", "[quic]") +{ + uint8_t buf[65535]; + size_t len; + + uint8_t expected[] = { + 0x04, // Type + 0x92, 0x34, 0x56, 0x78, // Stream ID + 0x00, 0x01, // Error Code + 0xd1, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 // Final Offset + }; + QUICRstStreamFrame rst_stream_frame(0x12345678, 0x0001, 0x1122334455667788); + CHECK(rst_stream_frame.size() == 15); + + rst_stream_frame.store(buf, &len, 65535); + CHECK(len == 15); + CHECK(memcmp(buf, expected, len) == 0); +} + +TEST_CASE("Load Ping Frame", "[quic]") +{ + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + uint8_t buf[] = { + 0x01, // Type + }; + const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf)); + CHECK(frame->type() == QUICFrameType::PING); + CHECK(frame->size() == 1); + + const QUICPingFrame *ping_frame = static_cast(frame); + CHECK(ping_frame != nullptr); +} + +TEST_CASE("Store Ping Frame", "[quic]") +{ + uint8_t buf[16]; + size_t len; + + uint8_t expected[] = { + 0x01, // Type + }; + + QUICPingFrame frame; + CHECK(frame.size() == 1); + + frame.store(buf, &len, 16); + CHECK(len == 1); + CHECK(memcmp(buf, expected, len) == 0); +} + +TEST_CASE("Load Padding Frame", "[quic]") +{ + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + uint8_t buf1[] = { + 0x00, // Type + }; + const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1)); + CHECK(frame1->type() == QUICFrameType::PADDING); + CHECK(frame1->size() == 1); + const QUICPaddingFrame *paddingFrame1 = static_cast(frame1); + CHECK(paddingFrame1 != nullptr); +} + +TEST_CASE("Store Padding Frame", "[quic]") +{ + uint8_t buf[65535]; + size_t len; + + uint8_t expected[] = { + 0x00, // Type + }; + QUICPaddingFrame padding_frame; + padding_frame.store(buf, &len, 65535); + CHECK(len == 1); + CHECK(memcmp(buf, expected, len) == 0); +} + +TEST_CASE("ConnectionClose Frame", "[quic]") +{ + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + uint8_t reason_phrase[] = {0x41, 0x42, 0x43, 0x44, 0x45}; + size_t reason_phrase_len = sizeof(reason_phrase); + + SECTION("loading w/ reason phrase") + { + uint8_t buf[] = { + 0x1c, // Type + 0x00, 0x0A, // Error Code + 0x00, // Frame Type + 0x05, // Reason Phrase Length + 0x41, 0x42, 0x43, 0x44, 0x45 // Reason Phrase ("ABCDE"); + }; + + const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf)); + CHECK(frame->type() == QUICFrameType::CONNECTION_CLOSE); + CHECK(frame->size() == sizeof(buf)); + + const QUICConnectionCloseFrame *conn_close_frame = static_cast(frame); + CHECK(conn_close_frame != nullptr); + CHECK(conn_close_frame->error_code() == static_cast(QUICTransErrorCode::PROTOCOL_VIOLATION)); + CHECK(conn_close_frame->frame_type() == QUICFrameType::UNKNOWN); + CHECK(conn_close_frame->reason_phrase_length() == reason_phrase_len); + CHECK(memcmp(conn_close_frame->reason_phrase(), reason_phrase, reason_phrase_len) == 0); + } + + SECTION("Bad loading") + { + uint8_t buf[] = { + 0x1c, // Type + 0x00, 0x0A, // Error Code + 0x00, // Frame Type + 0x05, // Reason Phrase Length + }; + + const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf)); + CHECK(frame->type() == QUICFrameType::CONNECTION_CLOSE); + CHECK(frame->valid() == false); + } + + SECTION("loading w/o reason phrase") + { + uint8_t buf[] = { + 0x1c, // Type + 0x00, 0x0A, // Error Code + 0x04, // Frame Type + 0x00, // Reason Phrase Length + }; + const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf)); + CHECK(frame->type() == QUICFrameType::CONNECTION_CLOSE); + CHECK(frame->size() == sizeof(buf)); + + const QUICConnectionCloseFrame *conn_close_frame = static_cast(frame); + CHECK(conn_close_frame != nullptr); + CHECK(conn_close_frame->error_code() == static_cast(QUICTransErrorCode::PROTOCOL_VIOLATION)); + CHECK(conn_close_frame->frame_type() == QUICFrameType::RESET_STREAM); + CHECK(conn_close_frame->reason_phrase_length() == 0); + } + + SECTION("storing w/ reason phrase") + { + uint8_t buf[32]; + size_t len; + + uint8_t expected[] = { + 0x1c, // Type + 0x00, 0x0A, // Error Code + 0x08, // Frame Type + 0x05, // Reason Phrase Length + 0x41, 0x42, 0x43, 0x44, 0x45 // Reason Phrase ("ABCDE"); + }; + QUICConnectionCloseFrame connection_close_frame(static_cast(QUICTransErrorCode::PROTOCOL_VIOLATION), + QUICFrameType::STREAM, 5, "ABCDE"); + CHECK(connection_close_frame.size() == sizeof(expected)); + + connection_close_frame.store(buf, &len, 32); + CHECK(len == sizeof(expected)); + CHECK(memcmp(buf, expected, len) == 0); + } + + SECTION("storing w/o reason phrase") + { + uint8_t buf[32]; + size_t len; + + uint8_t expected[] = { + 0x1c, // Type + 0x00, 0x0A, // Error Code + 0x00, // Frame Type + 0x00, // Reason Phrase Length + }; + QUICConnectionCloseFrame connection_close_frame(static_cast(QUICTransErrorCode::PROTOCOL_VIOLATION), + QUICFrameType::UNKNOWN, 0, nullptr); + connection_close_frame.store(buf, &len, 32); + CHECK(len == sizeof(expected)); + CHECK(memcmp(buf, expected, len) == 0); + } +} + +TEST_CASE("Load MaxData Frame", "[quic]") +{ + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + SECTION("Load") + { + uint8_t buf1[] = { + 0x10, // Type + 0xd1, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 // Maximum Data + }; + const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1)); + CHECK(frame1->type() == QUICFrameType::MAX_DATA); + CHECK(frame1->size() == 9); + const QUICMaxDataFrame *max_data_frame = static_cast(frame1); + CHECK(max_data_frame != nullptr); + CHECK(max_data_frame->maximum_data() == 0x1122334455667788ULL); + } + + SECTION("Bad Load") + { + uint8_t buf1[] = { + 0x10, // Type + }; + const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1)); + CHECK(frame1->type() == QUICFrameType::MAX_DATA); + CHECK(frame1->valid() == false); + } +} + +TEST_CASE("Store MaxData Frame", "[quic]") +{ + uint8_t buf[65535]; + size_t len; + + uint8_t expected[] = { + 0x10, // Type + 0xd1, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 // Maximum Data + }; + QUICMaxDataFrame max_data_frame(0x1122334455667788, 0, nullptr); + CHECK(max_data_frame.size() == 9); + + max_data_frame.store(buf, &len, 65535); + CHECK(len == 9); + CHECK(memcmp(buf, expected, len) == 0); +} + +TEST_CASE("Load MaxStreamData Frame", "[quic]") +{ + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + SECTION("Load") + { + uint8_t buf1[] = { + 0x11, // Type + 0x81, 0x02, 0x03, 0x04, // Stream ID + 0xd1, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 // Maximum Stream Data + }; + const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1)); + CHECK(frame1->type() == QUICFrameType::MAX_STREAM_DATA); + CHECK(frame1->size() == 13); + const QUICMaxStreamDataFrame *maxStreamDataFrame1 = static_cast(frame1); + CHECK(maxStreamDataFrame1 != nullptr); + CHECK(maxStreamDataFrame1->stream_id() == 0x01020304); + CHECK(maxStreamDataFrame1->maximum_stream_data() == 0x1122334455667788ULL); + } + + SECTION("Load") + { + uint8_t buf1[] = { + 0x11, // Type + 0x81, 0x02, 0x03, 0x04, // Stream ID + }; + const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1)); + CHECK(frame1->type() == QUICFrameType::MAX_STREAM_DATA); + CHECK(frame1->valid() == false); + } +} + +TEST_CASE("Store MaxStreamData Frame", "[quic]") +{ + uint8_t buf[65535]; + size_t len; + + uint8_t expected[] = { + 0x11, // Type + 0x81, 0x02, 0x03, 0x04, // Stream ID + 0xd1, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 // Maximum Stream Data + }; + QUICMaxStreamDataFrame max_stream_data_frame(0x01020304, 0x1122334455667788ULL); + CHECK(max_stream_data_frame.size() == 13); + + max_stream_data_frame.store(buf, &len, 65535); + CHECK(len == 13); + CHECK(memcmp(buf, expected, len) == 0); +} + +TEST_CASE("Load MaxStreams Frame", "[quic]") +{ + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + SECTION("load") + { + uint8_t buf1[] = { + 0x12, // Type + 0x81, 0x02, 0x03, 0x04, // Stream ID + }; + const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1)); + CHECK(frame1->type() == QUICFrameType::MAX_STREAMS); + CHECK(frame1->size() == 5); + const QUICMaxStreamsFrame *max_streams_frame = static_cast(frame1); + CHECK(max_streams_frame != nullptr); + CHECK(max_streams_frame->maximum_streams() == 0x01020304); + } + SECTION("bad load") + { + uint8_t buf1[] = { + 0x12, // Type + }; + const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1)); + CHECK(frame1->type() == QUICFrameType::MAX_STREAMS); + CHECK(frame1->valid() == false); + } +} + +TEST_CASE("Store MaxStreams Frame", "[quic]") +{ + uint8_t buf[65535]; + size_t len; + + uint8_t expected[] = { + 0x12, // Type + 0x81, 0x02, 0x03, 0x04, // Stream ID + }; + QUICMaxStreamsFrame max_streams_frame(0x01020304, 0, nullptr); + CHECK(max_streams_frame.size() == 5); + + max_streams_frame.store(buf, &len, 65535); + CHECK(len == 5); + CHECK(memcmp(buf, expected, len) == 0); +} + +TEST_CASE("Load DataBlocked Frame", "[quic]") +{ + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + SECTION("load") + { + uint8_t buf1[] = { + 0x14, // Type + 0x07, // Offset + }; + const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1)); + CHECK(frame1->type() == QUICFrameType::DATA_BLOCKED); + CHECK(frame1->size() == 2); + const QUICDataBlockedFrame *blocked_stream_frame = static_cast(frame1); + CHECK(blocked_stream_frame != nullptr); + CHECK(blocked_stream_frame->offset() == 0x07); + } + + SECTION("bad load") + { + uint8_t buf1[] = { + 0x14, // Type + }; + const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1)); + CHECK(frame1->type() == QUICFrameType::DATA_BLOCKED); + CHECK(frame1->valid() == false); + } +} + +TEST_CASE("Store DataBlocked Frame", "[quic]") +{ + uint8_t buf[65535]; + size_t len; + + uint8_t expected[] = { + 0x14, // Type + 0x07, // Offset + }; + QUICDataBlockedFrame blocked_stream_frame(0x07, 0, nullptr); + CHECK(blocked_stream_frame.size() == 2); + + blocked_stream_frame.store(buf, &len, 65535); + CHECK(len == 2); + CHECK(memcmp(buf, expected, len) == 0); +} + +TEST_CASE("Load StreamDataBlocked Frame", "[quic]") +{ + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + SECTION("Load") + { + uint8_t buf1[] = { + 0x15, // Type + 0x81, 0x02, 0x03, 0x04, // Stream ID + 0x07, // Offset + }; + const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1)); + CHECK(frame1->type() == QUICFrameType::STREAM_DATA_BLOCKED); + CHECK(frame1->size() == 6); + const QUICStreamDataBlockedFrame *stream_blocked_frame = static_cast(frame1); + CHECK(stream_blocked_frame != nullptr); + CHECK(stream_blocked_frame->stream_id() == 0x01020304); + CHECK(stream_blocked_frame->offset() == 0x07); + } + + SECTION("Load") + { + uint8_t buf1[] = { + 0x15, // Type + 0x81, 0x02, 0x03, 0x04, // Stream ID + }; + const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1)); + CHECK(frame1->type() == QUICFrameType::STREAM_DATA_BLOCKED); + CHECK(frame1->valid() == false); + } +} + +TEST_CASE("Store StreamDataBlocked Frame", "[quic]") +{ + uint8_t buf[65535]; + size_t len; + + uint8_t expected[] = { + 0x15, // Type + 0x81, 0x02, 0x03, 0x04, // Stream ID + 0x07, // Offset + }; + QUICStreamDataBlockedFrame stream_blocked_frame(0x01020304, 0x07); + CHECK(stream_blocked_frame.size() == 6); + + stream_blocked_frame.store(buf, &len, 65535); + CHECK(len == 6); + CHECK(memcmp(buf, expected, len) == 0); +} + +TEST_CASE("Load StreamsBlocked Frame", "[quic]") +{ + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + SECTION("Load") + { + uint8_t buf1[] = { + 0x16, // Type + 0x41, 0x02, // Stream ID + }; + const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1)); + CHECK(frame1->type() == QUICFrameType::STREAMS_BLOCKED); + CHECK(frame1->size() == 3); + const QUICStreamIdBlockedFrame *stream_id_blocked_frame = static_cast(frame1); + CHECK(stream_id_blocked_frame != nullptr); + CHECK(stream_id_blocked_frame->stream_id() == 0x0102); + } + + SECTION("Load") + { + uint8_t buf1[] = { + 0x16, // Type + }; + const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1)); + CHECK(frame1->type() == QUICFrameType::STREAMS_BLOCKED); + CHECK(frame1->valid() == false); + } +} + +TEST_CASE("Store StreamsBlocked Frame", "[quic]") +{ + uint8_t buf[65535]; + size_t len; + + uint8_t expected[] = { + 0x16, // Type + 0x41, 0x02, // Stream ID + }; + QUICStreamIdBlockedFrame stream_id_blocked_frame(0x0102, 0, nullptr); + CHECK(stream_id_blocked_frame.size() == 3); + + stream_id_blocked_frame.store(buf, &len, 65535); + CHECK(len == 3); + CHECK(memcmp(buf, expected, len) == 0); +} + +TEST_CASE("Load NewConnectionId Frame", "[quic]") +{ + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + SECTION("load") + { + uint8_t buf1[] = { + 0x18, // Type + 0x41, 0x02, // Sequence + 0x08, // Length + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, // Connection ID + 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, // Stateless Reset Token + 0x80, 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0, + }; + const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1)); + CHECK(frame1->type() == QUICFrameType::NEW_CONNECTION_ID); + CHECK(frame1->size() == 28); + const QUICNewConnectionIdFrame *new_con_id_frame = static_cast(frame1); + CHECK(new_con_id_frame != nullptr); + CHECK(new_con_id_frame->sequence() == 0x0102); + CHECK((new_con_id_frame->connection_id() == + QUICConnectionId(reinterpret_cast("\x11\x22\x33\x44\x55\x66\x77\x88"), 8))); + CHECK(memcmp(new_con_id_frame->stateless_reset_token().buf(), buf1 + 12, 16) == 0); + } + + SECTION("Bad Load") + { + uint8_t buf1[] = { + 0x18, // Type + 0x41, 0x02, // Sequence + 0x08, // Length + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, // Connection ID + 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, // Stateless Reset Token + }; + const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1)); + CHECK(frame1->type() == QUICFrameType::NEW_CONNECTION_ID); + CHECK(frame1->valid() == false); + } +} + +TEST_CASE("Store NewConnectionId Frame", "[quic]") +{ + uint8_t buf[32]; + size_t len; + + uint8_t expected[] = { + 0x18, // Type + 0x41, 0x02, // Sequence + 0x08, // Length + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, // Connection ID + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // Stateless Reset Token + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + }; + QUICNewConnectionIdFrame new_con_id_frame(0x0102, {reinterpret_cast("\x11\x22\x33\x44\x55\x66\x77\x88"), 8}, + {expected + 12}); + CHECK(new_con_id_frame.size() == 28); + + new_con_id_frame.store(buf, &len, 32); + CHECK(len == 28); + CHECK(memcmp(buf, expected, len) == 0); +} + +TEST_CASE("Load STOP_SENDING Frame", "[quic]") +{ + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + SECTION("LOAD") + { + uint8_t buf[] = { + 0x05, // Type + 0x92, 0x34, 0x56, 0x78, // Stream ID + 0x00, 0x01, // Error Code + }; + const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf)); + CHECK(frame->type() == QUICFrameType::STOP_SENDING); + CHECK(frame->size() == 7); + + const QUICStopSendingFrame *stop_sending_frame = static_cast(frame); + CHECK(stop_sending_frame != nullptr); + CHECK(stop_sending_frame->stream_id() == 0x12345678); + CHECK(stop_sending_frame->error_code() == 0x0001); + } + + SECTION("Bad LOAD") + { + uint8_t buf[] = { + 0x05, // Type + 0x92, 0x34, 0x56, 0x78, // Stream ID + }; + const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf)); + CHECK(frame->type() == QUICFrameType::STOP_SENDING); + CHECK(frame->valid() == false); + } +} + +TEST_CASE("Store STOP_SENDING Frame", "[quic]") +{ + uint8_t buf[65535]; + size_t len; + + uint8_t expected[] = { + 0x05, // Type + 0x92, 0x34, 0x56, 0x78, // Stream ID + 0x00, 0x01, // Error Code + }; + QUICStopSendingFrame stop_sending_frame(0x12345678, static_cast(0x01)); + CHECK(stop_sending_frame.size() == 7); + + stop_sending_frame.store(buf, &len, 65535); + CHECK(len == 7); + CHECK(memcmp(buf, expected, len) == 0); +} + +TEST_CASE("Load PATH_CHALLENGE Frame", "[quic]") +{ + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + SECTION("Load") + { + uint8_t buf[] = { + 0x1a, // Type + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, // Data + }; + const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf)); + CHECK(frame->type() == QUICFrameType::PATH_CHALLENGE); + CHECK(frame->size() == 9); + + const QUICPathChallengeFrame *path_challenge_frame = static_cast(frame); + CHECK(path_challenge_frame != nullptr); + CHECK(memcmp(path_challenge_frame->data(), "\x01\x23\x45\x67\x89\xab\xcd\xef", QUICPathChallengeFrame::DATA_LEN) == 0); + } + + SECTION("Load") + { + uint8_t buf[] = { + 0x1a, // Type + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xef, // Data + }; + const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf)); + CHECK(frame->type() == QUICFrameType::PATH_CHALLENGE); + CHECK(frame->valid() == false); + } +} + +TEST_CASE("Store PATH_CHALLENGE Frame", "[quic]") +{ + uint8_t buf[16]; + size_t len; + + uint8_t expected[] = { + 0x1a, // Type + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, // Data + }; + + uint8_t raw[] = "\x01\x23\x45\x67\x89\xab\xcd\xef"; + size_t raw_len = sizeof(raw) - 1; + ats_unique_buf data = ats_unique_malloc(raw_len); + memcpy(data.get(), raw, raw_len); + + QUICPathChallengeFrame frame(std::move(data)); + CHECK(frame.size() == 9); + + frame.store(buf, &len, 16); + CHECK(len == 9); + CHECK(memcmp(buf, expected, len) == 0); +} + +TEST_CASE("Load PATH_RESPONSE Frame", "[quic]") +{ + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + SECTION("Load") + { + uint8_t buf[] = { + 0x1b, // Type + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, // Data + }; + const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf)); + CHECK(frame->type() == QUICFrameType::PATH_RESPONSE); + CHECK(frame->size() == 9); + + const QUICPathResponseFrame *path_response_frame = static_cast(frame); + CHECK(path_response_frame != nullptr); + CHECK(memcmp(path_response_frame->data(), "\x01\x23\x45\x67\x89\xab\xcd\xef", QUICPathResponseFrame::DATA_LEN) == 0); + } + + SECTION("Load") + { + uint8_t buf[] = { + 0x1b, // Type + 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, // Data + }; + const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf)); + CHECK(frame->type() == QUICFrameType::PATH_RESPONSE); + CHECK(frame->valid() == false); + } +} + +TEST_CASE("Store PATH_RESPONSE Frame", "[quic]") +{ + uint8_t buf[16]; + size_t len; + + uint8_t expected[] = { + 0x1b, // Type + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, // Data + }; + + uint8_t raw[] = "\x01\x23\x45\x67\x89\xab\xcd\xef"; + size_t raw_len = sizeof(raw) - 1; + ats_unique_buf data = ats_unique_malloc(raw_len); + memcpy(data.get(), raw, raw_len); + + QUICPathResponseFrame frame(std::move(data)); + CHECK(frame.size() == 9); + + frame.store(buf, &len, 16); + CHECK(len == 9); + CHECK(memcmp(buf, expected, len) == 0); +} + +TEST_CASE("NEW_TOKEN Frame", "[quic]") +{ + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + uint8_t raw_new_token_frame[] = { + 0x07, // Type + 0x08, // Token Length (i) + 0xbe, 0xef, 0xbe, 0xef, 0xbe, 0xef, 0xbe, 0xef, // Token (*) + }; + size_t raw_new_token_frame_len = sizeof(raw_new_token_frame); + + uint8_t raw_token[] = {0xbe, 0xef, 0xbe, 0xef, 0xbe, 0xef, 0xbe, 0xef}; + size_t raw_token_len = sizeof(raw_token); + + SECTION("load") + { + const QUICFrame *frame = QUICFrameFactory::create(frame_buf, raw_new_token_frame, raw_new_token_frame_len); + CHECK(frame->type() == QUICFrameType::NEW_TOKEN); + CHECK(frame->size() == raw_new_token_frame_len); + + const QUICNewTokenFrame *new_token_frame = static_cast(frame); + CHECK(new_token_frame != nullptr); + CHECK(new_token_frame->token_length() == raw_token_len); + CHECK(memcmp(new_token_frame->token(), raw_token, raw_token_len) == 0); + } + + SECTION("bad load") + { + const QUICFrame *frame = QUICFrameFactory::create(frame_buf, raw_new_token_frame, raw_new_token_frame_len - 5); + CHECK(frame->type() == QUICFrameType::NEW_TOKEN); + CHECK(frame->valid() == false); + } + + SECTION("store") + { + uint8_t buf[32]; + size_t len; + + ats_unique_buf token = ats_unique_malloc(raw_token_len); + memcpy(token.get(), raw_token, raw_token_len); + + QUICNewTokenFrame frame(std::move(token), raw_token_len); + CHECK(frame.size() == raw_new_token_frame_len); + + frame.store(buf, &len, 16); + CHECK(len == raw_new_token_frame_len); + CHECK(memcmp(buf, raw_new_token_frame, len) == 0); + } +} + +TEST_CASE("RETIRE_CONNECTION_ID Frame", "[quic]") +{ + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + uint8_t raw_retire_connection_id_frame[] = { + 0x19, // Type + 0x08, // Sequence Number (i) + }; + size_t raw_retire_connection_id_frame_len = sizeof(raw_retire_connection_id_frame); + uint64_t seq_num = 8; + + SECTION("load") + { + const QUICFrame *frame = + QUICFrameFactory::create(frame_buf, raw_retire_connection_id_frame, raw_retire_connection_id_frame_len); + CHECK(frame->type() == QUICFrameType::RETIRE_CONNECTION_ID); + CHECK(frame->size() == raw_retire_connection_id_frame_len); + + const QUICRetireConnectionIdFrame *retire_connection_id_frame = static_cast(frame); + CHECK(retire_connection_id_frame != nullptr); + CHECK(retire_connection_id_frame->seq_num() == seq_num); + } + + SECTION("bad load") + { + const QUICFrame *frame = + QUICFrameFactory::create(frame_buf, raw_retire_connection_id_frame, raw_retire_connection_id_frame_len - 1); + CHECK(frame->type() == QUICFrameType::RETIRE_CONNECTION_ID); + CHECK(frame->valid() == false); + } + + SECTION("store") + { + uint8_t buf[32]; + size_t len; + + QUICRetireConnectionIdFrame frame(seq_num, 0, nullptr); + CHECK(frame.size() == raw_retire_connection_id_frame_len); + + frame.store(buf, &len, 16); + CHECK(len == raw_retire_connection_id_frame_len); + CHECK(memcmp(buf, raw_retire_connection_id_frame, len) == 0); + } +} + +TEST_CASE("QUICFrameFactory Create Unknown Frame", "[quic]") +{ + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + uint8_t buf1[] = { + 0x20, // Type + }; + const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1)); + CHECK(frame1 == nullptr); +} + +TEST_CASE("QUICFrameFactory Fast Create Frame", "[quic]") +{ + QUICFrameFactory factory; + + uint8_t buf1[] = { + 0x12, // Type + 0x81, 0x02, 0x03, 0x04, // Stream Data + }; + uint8_t buf2[] = { + 0x12, // Type + 0x85, 0x06, 0x07, 0x08, // Stream Data + }; + const QUICFrame &frame1 = factory.fast_create(buf1, sizeof(buf1)); + CHECK(frame1.type() == QUICFrameType::MAX_STREAMS); + + const QUICMaxStreamsFrame &max_streams_frame1 = static_cast(frame1); + CHECK(max_streams_frame1.maximum_streams() == 0x01020304); + + const QUICFrame &frame2 = factory.fast_create(buf2, sizeof(buf2)); + CHECK(frame2.type() == QUICFrameType::MAX_STREAMS); + + const QUICMaxStreamsFrame &max_streams_frame2 = static_cast(frame2); + CHECK(max_streams_frame2.maximum_streams() == 0x05060708); + + CHECK(&frame1 == &frame2); +} + +TEST_CASE("QUICFrameFactory Fast Create Unknown Frame", "[quic]") +{ + QUICFrameFactory factory; + + uint8_t buf1[] = { + 0x20, // Type + }; + const QUICFrame &frame1 = factory.fast_create(buf1, sizeof(buf1)); + CHECK(frame1.type() == QUICFrameType::UNKNOWN); +} + +TEST_CASE("QUICFrameFactory Create CONNECTION_CLOSE with a QUICConnectionError", "[quic]") +{ + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + + std::unique_ptr error = + std::unique_ptr(new QUICConnectionError(QUICTransErrorCode::INTERNAL_ERROR)); + const QUICConnectionCloseFrame *connection_close_frame1 = QUICFrameFactory::create_connection_close_frame(frame_buf, *error); + CHECK(connection_close_frame1->error_code() == static_cast(QUICTransErrorCode::INTERNAL_ERROR)); + CHECK(connection_close_frame1->reason_phrase_length() == 0); + CHECK(connection_close_frame1->reason_phrase() == nullptr); + + error = std::unique_ptr(new QUICConnectionError(QUICTransErrorCode::INTERNAL_ERROR, "test")); + const QUICConnectionCloseFrame *connection_close_frame2 = QUICFrameFactory::create_connection_close_frame(frame_buf, *error); + CHECK(connection_close_frame2->error_code() == static_cast(QUICTransErrorCode::INTERNAL_ERROR)); + CHECK(connection_close_frame2->reason_phrase_length() == 4); + CHECK(memcmp(connection_close_frame2->reason_phrase(), "test", 4) == 0); +} + +TEST_CASE("QUICFrameFactory Create RESET_STREAM with a QUICStreamError", "[quic]") +{ + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + QUICRTTMeasure mock_rtt; + MockQUICConnection mock_connection; + QUICBidirectionalStream stream(&mock_rtt, &mock_connection, 0x1234, 0, 0); + std::unique_ptr error = + std::unique_ptr(new QUICStreamError(&stream, static_cast(0x01))); + const QUICRstStreamFrame *rst_stream_frame1 = QUICFrameFactory::create_rst_stream_frame(frame_buf, *error); + CHECK(rst_stream_frame1->error_code() == 0x01); + CHECK(rst_stream_frame1->stream_id() == 0x1234); + CHECK(rst_stream_frame1->final_offset() == 0); +} diff --git a/iocore/net/quic/test/test_QUICFrameDispatcher.cc b/iocore/net/quic/test/test_QUICFrameDispatcher.cc new file mode 100644 index 00000000000..be81c2e37fe --- /dev/null +++ b/iocore/net/quic/test/test_QUICFrameDispatcher.cc @@ -0,0 +1,77 @@ +/** @file + * + * A brief file description + * + * @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 "catch.hpp" + +#include "quic/QUICFrameDispatcher.h" +#include "quic/Mock.h" +#include + +TEST_CASE("QUICFrameHandler", "[quic]") +{ + Ptr block = make_ptr(new_IOBufferBlock()); + block->alloc(); + block->fill(1); + CHECK(block->read_avail() == 1); + + QUICStreamFrame streamFrame(block, 0x03, 0); + + MockQUICLDConfig ld_config; + MockQUICCCConfig cc_config; + MockQUICConnection connection; + MockQUICStreamManager streamManager; + MockQUICConnectionInfoProvider info; + MockQUICCongestionController cc(&info, cc_config); + QUICRTTMeasure rtt_measure; + MockQUICLossDetector lossDetector(&info, &cc, &rtt_measure, ld_config); + + QUICFrameDispatcher quicFrameDispatcher(&info); + quicFrameDispatcher.add_handler(&connection); + quicFrameDispatcher.add_handler(&streamManager); + quicFrameDispatcher.add_handler(&lossDetector); + + // Initial state + CHECK(connection.getTotalFrameCount() == 0); + CHECK(streamManager.getTotalFrameCount() == 0); + + // STREAM frame + uint8_t buf[4096] = {0}; + size_t len = 0; + Ptr ibb = streamFrame.to_io_buffer_block(sizeof(buf)); + for (auto b = ibb; b; b = b->next) { + memcpy(buf + len, b->start(), b->size()); + len += b->size(); + } + bool should_send_ack; + bool is_flow_controlled; + quicFrameDispatcher.receive_frames(QUICEncryptionLevel::INITIAL, buf, len, should_send_ack, is_flow_controlled, nullptr); + CHECK(connection.getTotalFrameCount() == 0); + CHECK(streamManager.getTotalFrameCount() == 1); + + // CONNECTION_CLOSE frame + QUICConnectionCloseFrame connectionCloseFrame(0, 0, "", 0, nullptr); + connectionCloseFrame.store(buf, &len, 4096); + quicFrameDispatcher.receive_frames(QUICEncryptionLevel::INITIAL, buf, len, should_send_ack, is_flow_controlled, nullptr); + CHECK(connection.getTotalFrameCount() == 1); + CHECK(streamManager.getTotalFrameCount() == 1); +} diff --git a/iocore/net/quic/test/test_QUICFrameRetransmitter.cc b/iocore/net/quic/test/test_QUICFrameRetransmitter.cc new file mode 100644 index 00000000000..39d50b7a18c --- /dev/null +++ b/iocore/net/quic/test/test_QUICFrameRetransmitter.cc @@ -0,0 +1,296 @@ +/** @file + * + * A brief file description + * + * @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 "catch.hpp" + +#include "QUICFrameRetransmitter.h" + +constexpr static uint8_t data[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x01, 0x02, 0x03, 0x04, 0x05, + 0x06, 0x07, 0x08, 0x09, 0x10, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10}; + +TEST_CASE("QUICFrameRetransmitter ignore frame which can not be retranmistted", "[quic]") +{ + QUICFrameRetransmitter retransmitter; + QUICFrameInformationUPtr info = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc()); + info->type = QUICFrameType::PING; + info->level = QUICEncryptionLevel::NONE; + + retransmitter.save_frame_info(std::move(info)); + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + CHECK(retransmitter.create_retransmitted_frame(frame_buf, QUICEncryptionLevel::INITIAL, UINT16_MAX) == nullptr); +} + +// TEST_CASE("QUICFrameRetransmitter ignore frame which can not be split", "[quic]") +// { +// QUICFrameRetransmitter retransmitter; +// QUICFrameInformationUPtr info = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc()); +// info->type = QUICFrameType::STOP_SENDING; +// info->level = QUICEncryptionLevel::NONE; +// +// retransmitter.save_frame_info(info); +// CHECK(retransmitter.create_retransmitted_frame(QUICEncryptionLevel::INITIAL, 0) == nullptr); +// } + +TEST_CASE("QUICFrameRetransmitter ignore frame which has wrong level", "[quic]") +{ + QUICFrameRetransmitter retransmitter; + QUICFrameInformationUPtr info = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc()); + info->type = QUICFrameType::STREAM; + info->level = QUICEncryptionLevel::HANDSHAKE; + + retransmitter.save_frame_info(std::move(info)); + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + CHECK(retransmitter.create_retransmitted_frame(frame_buf, QUICEncryptionLevel::INITIAL, UINT16_MAX) == nullptr); +} + +TEST_CASE("QUICFrameRetransmitter successfully create retransmitted frame", "[quic]") +{ + QUICFrameRetransmitter retransmitter; + QUICFrameInformationUPtr info = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc()); + info->type = QUICFrameType::STREAM; + info->level = QUICEncryptionLevel::INITIAL; + + Ptr block = make_ptr(new_IOBufferBlock()); + block->alloc(); + memcpy(block->start(), data, sizeof(data)); + block->fill(sizeof(data)); + + StreamFrameInfo *frame_info = reinterpret_cast(info->data); + frame_info->stream_id = 0x12345; + frame_info->offset = 0x67890; + frame_info->block = block; + + retransmitter.save_frame_info(std::move(info)); + + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + auto frame = retransmitter.create_retransmitted_frame(frame_buf, QUICEncryptionLevel::INITIAL, UINT16_MAX); + CHECK(frame != nullptr); + CHECK(frame->type() == QUICFrameType::STREAM); +} + +TEST_CASE("QUICFrameRetransmitter successfully create stream frame", "[quic]") +{ + QUICFrameRetransmitter retransmitter; + QUICFrameInformationUPtr info = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc()); + info->type = QUICFrameType::STREAM; + info->level = QUICEncryptionLevel::INITIAL; + + Ptr block = make_ptr(new_IOBufferBlock()); + block->alloc(); + memcpy(block->start(), data, sizeof(data)); + block->fill(sizeof(data)); + + StreamFrameInfo *frame_info = reinterpret_cast(info->data); + frame_info->stream_id = 0x12345; + frame_info->offset = 0x67890; + frame_info->block = block; + + CHECK(block->refcount() == 2); + retransmitter.save_frame_info(std::move(info)); + CHECK(block->refcount() == 2); // block's refcount doesn't change + + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + auto frame = retransmitter.create_retransmitted_frame(frame_buf, QUICEncryptionLevel::INITIAL, UINT16_MAX); + CHECK(frame != nullptr); + CHECK(frame->type() == QUICFrameType::STREAM); + auto stream_frame = static_cast(frame); + CHECK(stream_frame->stream_id() == 0x12345); + CHECK(stream_frame->offset() == 0x67890); + CHECK(stream_frame->data_length() == sizeof(data)); + CHECK(memcmp(stream_frame->data()->start(), data, sizeof(data)) == 0); + std::destroy_at(frame); + frame = nullptr; + // Becasue the info has been released, the refcount should be 1 (var block). + CHECK(block->refcount() == 1); +} + +TEST_CASE("QUICFrameRetransmitter successfully split stream frame", "[quic]") +{ + QUICFrameRetransmitter retransmitter; + QUICFrameInformationUPtr info = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc()); + info->type = QUICFrameType::STREAM; + info->level = QUICEncryptionLevel::INITIAL; + + Ptr block = make_ptr(new_IOBufferBlock()); + block->alloc(); + memcpy(block->start(), data, sizeof(data)); + block->fill(sizeof(data)); + + StreamFrameInfo *frame_info = reinterpret_cast(info->data); + frame_info->stream_id = 0x12345; + frame_info->offset = 0x67890; + frame_info->block = block; + CHECK(block->refcount() == 2); + + retransmitter.save_frame_info(std::move(info)); + + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + auto frame = retransmitter.create_retransmitted_frame(frame_buf, QUICEncryptionLevel::INITIAL, 25); + CHECK(frame != nullptr); + CHECK(frame->type() == QUICFrameType::STREAM); + auto stream_frame = static_cast(frame); + CHECK(stream_frame->stream_id() == 0x12345); + CHECK(stream_frame->offset() == 0x67890); + CHECK(stream_frame->size() <= 25); + + auto size = stream_frame->data_length(); + CHECK(memcmp(stream_frame->data()->start(), data, stream_frame->data_length()) == 0); + // one for var block, one for the left data which saved in retransmitter + CHECK(block->data->refcount() == 2); + // one for var block, one for the left data which saved in retransmitter, one for var frame + CHECK(block->refcount() == 2); + std::destroy_at(frame); + frame = nullptr; + // one for var block, one for var info + CHECK(block->refcount() == 2); + CHECK(block->data->refcount() == 1); + + frame = retransmitter.create_retransmitted_frame(frame_buf, QUICEncryptionLevel::INITIAL, UINT16_MAX); + CHECK(frame != nullptr); + CHECK(frame->type() == QUICFrameType::STREAM); + stream_frame = static_cast(frame); + CHECK(stream_frame->stream_id() == 0x12345); + CHECK(stream_frame->offset() == 0x67890 + size); + CHECK(stream_frame->data_length() == sizeof(data) - size); + CHECK(memcmp(stream_frame->data()->start(), data + size, stream_frame->data_length()) == 0); + CHECK(block->refcount() == 1); // one for var block + + std::destroy_at(frame); + frame = nullptr; + CHECK(block->refcount() == 1); + CHECK(block->data->refcount() == 1); +} + +TEST_CASE("QUICFrameRetransmitter successfully split crypto frame", "[quic]") +{ + QUICFrameRetransmitter retransmitter; + QUICFrameInformationUPtr info = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc()); + info->type = QUICFrameType::CRYPTO; + info->level = QUICEncryptionLevel::INITIAL; + + Ptr block = make_ptr(new_IOBufferBlock()); + block->alloc(); + memcpy(block->start(), data, sizeof(data)); + block->fill(sizeof(data)); + + CryptoFrameInfo *frame_info = reinterpret_cast(info->data); + frame_info->offset = 0x67890; + frame_info->block = block; + CHECK(block->refcount() == 2); + + retransmitter.save_frame_info(std::move(info)); + + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + auto frame = retransmitter.create_retransmitted_frame(frame_buf, QUICEncryptionLevel::INITIAL, 25); + CHECK(frame != nullptr); + CHECK(frame->type() == QUICFrameType::CRYPTO); + auto crypto_frame = static_cast(frame); + CHECK(crypto_frame->offset() == 0x67890); + CHECK(crypto_frame->size() <= 25); + + auto size = crypto_frame->data_length(); + CHECK(memcmp(crypto_frame->data()->start(), data, crypto_frame->data_length()) == 0); + // one for var block, one for the left data which saved in retransmitter + CHECK(block->data->refcount() == 2); + // one for var block, one for the left data which saved in retransmitter, one for var frame + CHECK(block->refcount() == 2); + std::destroy_at(frame); + frame = nullptr; + // one for var block, one for var info + CHECK(block->refcount() == 2); + CHECK(block->data->refcount() == 1); + + frame = retransmitter.create_retransmitted_frame(frame_buf, QUICEncryptionLevel::INITIAL, UINT16_MAX); + CHECK(frame != nullptr); + CHECK(frame->type() == QUICFrameType::CRYPTO); + crypto_frame = static_cast(frame); + CHECK(crypto_frame->offset() == 0x67890 + size); + CHECK(crypto_frame->data_length() == sizeof(data) - size); + CHECK(memcmp(crypto_frame->data()->start(), data + size, crypto_frame->data_length()) == 0); + CHECK(block->refcount() == 1); // one for var block + + std::destroy_at(frame); + frame = nullptr; + CHECK(block->refcount() == 1); + CHECK(block->data->refcount() == 1); +} + +TEST_CASE("QUICFrameRetransmitter successfully split stream frame with fin flag", "[quic]") +{ + QUICFrameRetransmitter retransmitter; + QUICFrameInformationUPtr info = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc()); + info->type = QUICFrameType::STREAM; + info->level = QUICEncryptionLevel::INITIAL; + + Ptr block = make_ptr(new_IOBufferBlock()); + block->alloc(); + memcpy(block->start(), data, sizeof(data)); + block->fill(sizeof(data)); + + StreamFrameInfo *frame_info = reinterpret_cast(info->data); + frame_info->stream_id = 0x12345; + frame_info->offset = 0x67890; + frame_info->block = block; + frame_info->has_fin = true; + CHECK(block->refcount() == 2); + + retransmitter.save_frame_info(std::move(info)); + + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + auto frame = retransmitter.create_retransmitted_frame(frame_buf, QUICEncryptionLevel::INITIAL, 25); + CHECK(frame != nullptr); + CHECK(frame->type() == QUICFrameType::STREAM); + auto stream_frame = static_cast(frame); + CHECK(stream_frame->stream_id() == 0x12345); + CHECK(stream_frame->offset() == 0x67890); + CHECK(stream_frame->size() <= 25); + CHECK(stream_frame->has_fin_flag() == false); + + auto size = stream_frame->data_length(); + CHECK(memcmp(stream_frame->data()->start(), data, stream_frame->data_length()) == 0); + // one for var block, one for the left data which saved in retransmitter + CHECK(block->data->refcount() == 2); + // one for var block, one for the left data which saved in retransmitter, one for var frame + CHECK(block->refcount() == 2); + std::destroy_at(frame); + frame = nullptr; + // one for var block, one for var info + CHECK(block->refcount() == 2); + CHECK(block->data->refcount() == 1); + + frame = retransmitter.create_retransmitted_frame(frame_buf, QUICEncryptionLevel::INITIAL, UINT16_MAX); + CHECK(frame != nullptr); + CHECK(frame->type() == QUICFrameType::STREAM); + stream_frame = static_cast(frame); + CHECK(stream_frame->stream_id() == 0x12345); + CHECK(stream_frame->offset() == 0x67890 + size); + CHECK(stream_frame->data_length() == sizeof(data) - size); + CHECK(memcmp(stream_frame->data()->start(), data + size, stream_frame->data_length()) == 0); + CHECK(block->refcount() == 1); // one for var block + CHECK(stream_frame->has_fin_flag() == true); + + std::destroy_at(frame); + frame = nullptr; + CHECK(block->refcount() == 1); + CHECK(block->data->refcount() == 1); +} diff --git a/iocore/net/quic/test/test_QUICHandshakeProtocol.cc b/iocore/net/quic/test/test_QUICHandshakeProtocol.cc new file mode 100644 index 00000000000..f90b10af3fa --- /dev/null +++ b/iocore/net/quic/test/test_QUICHandshakeProtocol.cc @@ -0,0 +1,519 @@ +/** @file + * + * A brief file description + * + * @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 "catch.hpp" + +#include +#include +#include + +#ifdef OPENSSL_IS_BORINGSSL +#include +#endif + +#include + +// #include "Mock.h" +#include "QUICPacketHeaderProtector.h" +#include "QUICPacketPayloadProtector.h" +#include "QUICPacketProtectionKeyInfo.h" +#include "QUICTLS.h" + +// XXX For NetVCOptions::reset +struct PollCont; +#include "P_UDPConnection.h" +#include "P_UnixNet.h" +#include "P_UnixNetVConnection.h" + +// depends on size of cert +static constexpr uint32_t MAX_HANDSHAKE_MSG_LEN = 8192; + +#include "./server_cert.h" + +static void +print_hex(const uint8_t *v, size_t len) +{ + for (size_t i = 0; i < len; i++) { + std::cout << std::setw(2) << std::setfill('0') << std::hex << static_cast(v[i]) << " "; + + if (i != 0 && (i + 1) % 32 == 0 && i != len - 1) { + std::cout << std::endl; + } + } + + std::cout << std::endl; + + return; +} + +static const uint8_t original[] = { + 0x41, 0x70, 0x61, 0x63, 0x68, 0x65, 0x20, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x20, 0x53, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; +static const uint64_t pkt_num = 0x123456789; +static const uint8_t ad[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}; + +TEST_CASE("QUICHandshakeProtocol") +{ + // Client + SSL_CTX *client_ssl_ctx = SSL_CTX_new(TLS_method()); + SSL_CTX_set_min_proto_version(client_ssl_ctx, TLS1_3_VERSION); + SSL_CTX_set_max_proto_version(client_ssl_ctx, TLS1_3_VERSION); +#ifndef OPENSSL_IS_BORINGSSL + SSL_CTX_clear_options(client_ssl_ctx, SSL_OP_ENABLE_MIDDLEBOX_COMPAT); +#endif +#ifdef SSL_MODE_QUIC_HACK + SSL_CTX_set_mode(client_ssl_ctx, SSL_MODE_QUIC_HACK); +#endif + + // Server + SSL_CTX *server_ssl_ctx = SSL_CTX_new(TLS_method()); + SSL_CTX_set_min_proto_version(server_ssl_ctx, TLS1_3_VERSION); + SSL_CTX_set_max_proto_version(server_ssl_ctx, TLS1_3_VERSION); +#ifndef OPENSSL_IS_BORINGSSL + SSL_CTX_clear_options(server_ssl_ctx, SSL_OP_ENABLE_MIDDLEBOX_COMPAT); +#endif +#ifdef SSL_MODE_QUIC_HACK + SSL_CTX_set_mode(server_ssl_ctx, SSL_MODE_QUIC_HACK); +#endif + BIO *crt_bio(BIO_new_mem_buf(server_crt, sizeof(server_crt))); + X509 *x509 = PEM_read_bio_X509(crt_bio, nullptr, nullptr, nullptr); + SSL_CTX_use_certificate(server_ssl_ctx, x509); + BIO *key_bio(BIO_new_mem_buf(server_key, sizeof(server_key))); + EVP_PKEY *pkey = PEM_read_bio_PrivateKey(key_bio, nullptr, nullptr, nullptr); + SSL_CTX_use_PrivateKey(server_ssl_ctx, pkey); + + SECTION("Full Handshake", "[quic]") + { + QUICPacketProtectionKeyInfo pp_key_info_client; + QUICPacketProtectionKeyInfo pp_key_info_server; + NetVCOptions netvc_options; + QUICHandshakeProtocol *client = new QUICTLS(pp_key_info_client, client_ssl_ctx, NET_VCONNECTION_OUT, netvc_options); + QUICHandshakeProtocol *server = new QUICTLS(pp_key_info_server, server_ssl_ctx, NET_VCONNECTION_IN, netvc_options); + QUICPacketPayloadProtector ppp_client(pp_key_info_client); + QUICPacketPayloadProtector ppp_server(pp_key_info_server); + + CHECK(client->initialize_key_materials({reinterpret_cast("\x83\x94\xc8\xf0\x3e\x51\x57\x00"), 8})); + CHECK(server->initialize_key_materials({reinterpret_cast("\x83\x94\xc8\xf0\x3e\x51\x57\x00"), 8})); + + // CH + QUICHandshakeMsgs msg1; + uint8_t msg1_buf[MAX_HANDSHAKE_MSG_LEN] = {0}; + msg1.buf = msg1_buf; + msg1.max_buf_len = MAX_HANDSHAKE_MSG_LEN; + + REQUIRE(client->handshake(&msg1, nullptr) == 1); + std::cout << "### Messages from client" << std::endl; + print_hex(msg1.buf, msg1.offsets[4]); + + // SH, EE, CERT, CV, FIN + QUICHandshakeMsgs msg2; + uint8_t msg2_buf[MAX_HANDSHAKE_MSG_LEN] = {0}; + msg2.buf = msg2_buf; + msg2.max_buf_len = MAX_HANDSHAKE_MSG_LEN; + + REQUIRE(server->handshake(&msg2, &msg1) == 1); + std::cout << "### Messages from server" << std::endl; + print_hex(msg2.buf, msg2.offsets[4]); + + // FIN + QUICHandshakeMsgs msg3; + uint8_t msg3_buf[MAX_HANDSHAKE_MSG_LEN] = {0}; + msg3.buf = msg3_buf; + msg3.max_buf_len = MAX_HANDSHAKE_MSG_LEN; + +#ifdef SSL_MODE_QUIC_HACK + // -- Hacks for OpenSSL with SSL_MODE_QUIC_HACK -- + // SH + QUICHandshakeMsgs msg2_1; + uint8_t msg2_1_buf[MAX_HANDSHAKE_MSG_LEN] = {0}; + msg2_1.buf = msg2_1_buf; + msg2_1.max_buf_len = MAX_HANDSHAKE_MSG_LEN; + + memcpy(msg2_1.buf, msg2.buf, msg2.offsets[1]); + msg2_1.offsets[0] = 0; + msg2_1.offsets[1] = msg2.offsets[1]; + msg2_1.offsets[2] = msg2.offsets[1]; + msg2_1.offsets[3] = msg2.offsets[1]; + msg2_1.offsets[4] = msg2.offsets[1]; + + // EE - FIN + QUICHandshakeMsgs msg2_2; + uint8_t msg2_2_buf[MAX_HANDSHAKE_MSG_LEN] = {0}; + msg2_2.buf = msg2_2_buf; + msg2_2.max_buf_len = MAX_HANDSHAKE_MSG_LEN; + + size_t len = msg2.offsets[3] - msg2.offsets[2]; + memcpy(msg2_2.buf, msg2.buf + msg2.offsets[1], len); + msg2_2.offsets[0] = 0; + msg2_2.offsets[1] = 0; + msg2_2.offsets[2] = 0; + msg2_2.offsets[3] = len; + msg2_2.offsets[4] = len; + + REQUIRE(client->handshake(&msg3, &msg2_1) == 1); + REQUIRE(client->handshake(&msg3, &msg2_2) == 1); +#else + REQUIRE(client->handshake(&msg3, &msg2) == 1); +#endif + std::cout << "### Messages from client" << std::endl; + print_hex(msg3.buf, msg3.offsets[4]); + + // NS + QUICHandshakeMsgs msg4; + uint8_t msg4_buf[MAX_HANDSHAKE_MSG_LEN] = {0}; + msg4.buf = msg4_buf; + msg4.max_buf_len = MAX_HANDSHAKE_MSG_LEN; + + REQUIRE(server->handshake(&msg4, &msg3) == 1); + std::cout << "### Messages from server" << std::endl; + print_hex(msg4.buf, msg4.offsets[4]); + + QUICHandshakeMsgs msg5; + uint8_t msg5_buf[MAX_HANDSHAKE_MSG_LEN] = {0}; + msg5.buf = msg5_buf; + msg5.max_buf_len = MAX_HANDSHAKE_MSG_LEN; + REQUIRE(client->handshake(&msg5, &msg4) == 1); + std::cout << "### Messages from server" << std::endl; + print_hex(msg4.buf, msg4.offsets[4]); + + // encrypt - decrypt + // client (encrypt) - server (decrypt) + std::cout << "### Original Text" << std::endl; + print_hex(original, sizeof(original)); + + Ptr original_ibb = make_ptr(new_IOBufferBlock()); + original_ibb->set_internal(const_cast(original), sizeof(original), BUFFER_SIZE_NOT_ALLOCATED); + Ptr header_ibb = make_ptr(new_IOBufferBlock()); + header_ibb->set_internal(const_cast(ad), sizeof(ad), BUFFER_SIZE_NOT_ALLOCATED); + Ptr cipher = ppp_client.protect(header_ibb, original_ibb, pkt_num, QUICKeyPhase::PHASE_0); + CHECK(cipher); + + std::cout << "### Encrypted Text" << std::endl; + print_hex(reinterpret_cast(cipher->buf()), cipher->size()); + + Ptr plain = ppp_server.unprotect(header_ibb, cipher, pkt_num, QUICKeyPhase::PHASE_0); + CHECK(plain); + + std::cout << "### Decrypted Text" << std::endl; + print_hex(reinterpret_cast(plain->buf()), plain->size()); + + CHECK(sizeof(original) == (plain->size())); + CHECK(memcmp(original, plain->buf(), plain->size()) == 0); + + // Teardown + delete client; + delete server; + } + + SECTION("Full Handshake with HRR", "[quic]") + { + // client key_share will be X25519 (default of OpenSSL) +#ifdef SSL_CTX_set1_groups_list + if (SSL_CTX_set1_groups_list(server_ssl_ctx, "P-521:P-384:P-256") != 1) { +#else + if (SSL_CTX_set1_curves_list(server_ssl_ctx, "P-521:P-384:P-256") != 1) { +#endif + REQUIRE(false); + } + + QUICPacketProtectionKeyInfo pp_key_info_client; + QUICPacketProtectionKeyInfo pp_key_info_server; + NetVCOptions netvc_options; + QUICHandshakeProtocol *client = new QUICTLS(pp_key_info_client, client_ssl_ctx, NET_VCONNECTION_OUT, netvc_options); + QUICHandshakeProtocol *server = new QUICTLS(pp_key_info_server, server_ssl_ctx, NET_VCONNECTION_IN, netvc_options); + QUICPacketPayloadProtector ppp_client(pp_key_info_client); + QUICPacketPayloadProtector ppp_server(pp_key_info_server); + + CHECK(client->initialize_key_materials({reinterpret_cast("\x83\x94\xc8\xf0\x3e\x51\x57\x00"), 8})); + CHECK(server->initialize_key_materials({reinterpret_cast("\x83\x94\xc8\xf0\x3e\x51\x57\x00"), 8})); + + // CH + QUICHandshakeMsgs msg1; + uint8_t msg1_buf[MAX_HANDSHAKE_MSG_LEN] = {0}; + msg1.buf = msg1_buf; + msg1.max_buf_len = MAX_HANDSHAKE_MSG_LEN; + + REQUIRE(client->handshake(&msg1, nullptr) == 1); + std::cout << "### Messages from client" << std::endl; + print_hex(msg1.buf, msg1.offsets[4]); + + // HRR + QUICHandshakeMsgs msg2; + uint8_t msg2_buf[MAX_HANDSHAKE_MSG_LEN] = {0}; + msg2.buf = msg2_buf; + msg2.max_buf_len = MAX_HANDSHAKE_MSG_LEN; + + REQUIRE(server->handshake(&msg2, &msg1) == 1); + std::cout << "### Messages from server" << std::endl; + print_hex(msg2.buf, msg2.offsets[4]); + + // CH + QUICHandshakeMsgs msg3; + uint8_t msg3_buf[MAX_HANDSHAKE_MSG_LEN] = {0}; + msg3.buf = msg3_buf; + msg3.max_buf_len = MAX_HANDSHAKE_MSG_LEN; + + REQUIRE(client->handshake(&msg3, &msg2) == 1); + std::cout << "### Messages from client" << std::endl; + print_hex(msg3.buf, msg3.offsets[4]); + + // SH, EE, CERT, CV, FIN + QUICHandshakeMsgs msg4; + uint8_t msg4_buf[MAX_HANDSHAKE_MSG_LEN] = {0}; + msg4.buf = msg4_buf; + msg4.max_buf_len = MAX_HANDSHAKE_MSG_LEN; + + REQUIRE(server->handshake(&msg4, &msg3) == 1); + std::cout << "### Messages from server" << std::endl; + print_hex(msg4.buf, msg4.offsets[4]); + + // FIN + QUICHandshakeMsgs msg5; + uint8_t msg5_buf[MAX_HANDSHAKE_MSG_LEN] = {0}; + msg5.buf = msg5_buf; + msg5.max_buf_len = MAX_HANDSHAKE_MSG_LEN; + +#ifdef SSL_MODE_QUIC_HACK + // -- Hacks for OpenSSL with SSL_MODE_QUIC_HACK -- + // SH + QUICHandshakeMsgs msg4_1; + uint8_t msg4_1_buf[MAX_HANDSHAKE_MSG_LEN] = {0}; + msg4_1.buf = msg4_1_buf; + msg4_1.max_buf_len = MAX_HANDSHAKE_MSG_LEN; + + memcpy(msg4_1.buf, msg4.buf, msg4.offsets[1]); + msg4_1.offsets[0] = 0; + msg4_1.offsets[1] = msg4.offsets[1]; + msg4_1.offsets[2] = msg4.offsets[1]; + msg4_1.offsets[3] = msg4.offsets[1]; + msg4_1.offsets[4] = msg4.offsets[1]; + + // EE - FIN + QUICHandshakeMsgs msg4_2; + uint8_t msg4_2_buf[MAX_HANDSHAKE_MSG_LEN] = {0}; + msg4_2.buf = msg4_2_buf; + msg4_2.max_buf_len = MAX_HANDSHAKE_MSG_LEN; + + size_t len = msg4.offsets[3] - msg4.offsets[2]; + memcpy(msg4_2.buf, msg4.buf + msg4.offsets[1], len); + msg4_2.offsets[0] = 0; + msg4_2.offsets[1] = 0; + msg4_2.offsets[2] = 0; + msg4_2.offsets[3] = len; + msg4_2.offsets[4] = len; + + REQUIRE(client->handshake(&msg5, &msg4_1) == 1); + REQUIRE(client->handshake(&msg5, &msg4_2) == 1); +#else + REQUIRE(client->handshake(&msg5, &msg4) == 1); +#endif + std::cout << "### Messages from client" << std::endl; + print_hex(msg5.buf, msg5.offsets[4]); + + // NS + QUICHandshakeMsgs msg6; + uint8_t msg6_buf[MAX_HANDSHAKE_MSG_LEN] = {0}; + msg6.buf = msg6_buf; + msg6.max_buf_len = MAX_HANDSHAKE_MSG_LEN; + + REQUIRE(server->handshake(&msg6, &msg5) == 1); + std::cout << "### Messages from server" << std::endl; + print_hex(msg6.buf, msg6.offsets[4]); + + Ptr original_ibb = make_ptr(new_IOBufferBlock()); + original_ibb->set_internal(const_cast(original), sizeof(original), BUFFER_SIZE_NOT_ALLOCATED); + Ptr header_ibb = make_ptr(new_IOBufferBlock()); + header_ibb->set_internal(const_cast(ad), sizeof(ad), BUFFER_SIZE_NOT_ALLOCATED); + + // encrypt - decrypt + // client (encrypt) - server (decrypt) + std::cout << "### Original Text" << std::endl; + print_hex(original, sizeof(original)); + + Ptr cipher = ppp_client.protect(header_ibb, original_ibb, pkt_num, QUICKeyPhase::PHASE_0); + CHECK(cipher); + + std::cout << "### Encrypted Text" << std::endl; + print_hex(reinterpret_cast(cipher->buf()), cipher->size()); + + Ptr plain = ppp_server.unprotect(header_ibb, cipher, pkt_num, QUICKeyPhase::PHASE_0); + CHECK(plain); + + std::cout << "### Decrypted Text" << std::endl; + print_hex(reinterpret_cast(plain->buf()), plain->size()); + + CHECK(sizeof(original) == plain->size()); + CHECK(memcmp(original, plain->buf(), plain->size()) == 0); + + // Teardown + // Make it back to the default settings +#ifdef SSL_CTX_set1_groups_list + if (SSL_CTX_set1_groups_list(server_ssl_ctx, "X25519:P-521:P-384:P-256") != 1) { +#else + if (SSL_CTX_set1_curves_list(server_ssl_ctx, "X25519:P-521:P-384:P-256") != 1) { +#endif + REQUIRE(false); + } + + delete client; + delete server; + } + + SECTION("Alert", "[quic]") + { + QUICPacketProtectionKeyInfo pp_key_info_server; + NetVCOptions netvc_options; + QUICHandshakeProtocol *server = new QUICTLS(pp_key_info_server, server_ssl_ctx, NET_VCONNECTION_IN, netvc_options); + CHECK(server->initialize_key_materials({reinterpret_cast("\x83\x94\xc8\xf0\x3e\x51\x57\x00"), 8})); + + // Malformed CH (finished) + uint8_t msg1_buf[] = {0x14, 0x00, 0x00, 0x30, 0x35, 0xb9, 0x82, 0x9d, 0xb9, 0x14, 0x70, 0x03, 0x60, + 0xd2, 0x5a, 0x03, 0x12, 0x12, 0x3d, 0x17, 0xc2, 0x13, 0x8c, 0xd7, 0x8b, 0x6e, + 0xc5, 0x4e, 0x50, 0x0a, 0x78, 0x6e, 0xa8, 0x54, 0x5f, 0x74, 0xfb, 0xf5, 0x6e, + 0x09, 0x90, 0x07, 0x58, 0x5a, 0x30, 0x5a, 0xe9, 0xcb, 0x1b, 0xa0, 0x69, 0x35}; + size_t msg1_len = sizeof(msg1_buf); + + QUICHandshakeMsgs msg1; + msg1.buf = msg1_buf; + msg1.max_buf_len = MAX_HANDSHAKE_MSG_LEN; + msg1.offsets[0] = 0; + msg1.offsets[1] = msg1_len; + msg1.offsets[2] = msg1_len; + msg1.offsets[3] = msg1_len; + msg1.offsets[4] = msg1_len; + + QUICHandshakeMsgs msg2; + uint8_t msg2_buf[MAX_HANDSHAKE_MSG_LEN] = {0}; + msg2.buf = msg2_buf; + msg2.max_buf_len = MAX_HANDSHAKE_MSG_LEN; + + CHECK(server->handshake(&msg2, &msg1) != 1); + CHECK(msg2.error_code == 0x10a); //< 0x100 + unexpected_message(10) + + // Teardown + delete server; + } + + SECTION("Full Handshake + Packet Number Protection", "[quic]") + { + QUICPacketProtectionKeyInfo pp_key_info_client; + QUICPacketProtectionKeyInfo pp_key_info_server; + NetVCOptions netvc_options; + QUICHandshakeProtocol *client = new QUICTLS(pp_key_info_client, client_ssl_ctx, NET_VCONNECTION_OUT, netvc_options); + QUICHandshakeProtocol *server = new QUICTLS(pp_key_info_server, server_ssl_ctx, NET_VCONNECTION_IN, netvc_options); + + CHECK(client->initialize_key_materials({reinterpret_cast("\x83\x94\xc8\xf0\x3e\x51\x57\x00"), 8})); + CHECK(server->initialize_key_materials({reinterpret_cast("\x83\x94\xc8\xf0\x3e\x51\x57\x00"), 8})); + + // # Start Handshake + + // CH + QUICHandshakeMsgs msg1; + uint8_t msg1_buf[MAX_HANDSHAKE_MSG_LEN] = {0}; + msg1.buf = msg1_buf; + msg1.max_buf_len = MAX_HANDSHAKE_MSG_LEN; + + REQUIRE(client->handshake(&msg1, nullptr) == 1); + + // SH, EE, CERT, CV, FIN + QUICHandshakeMsgs msg2; + uint8_t msg2_buf[MAX_HANDSHAKE_MSG_LEN] = {0}; + msg2.buf = msg2_buf; + msg2.max_buf_len = MAX_HANDSHAKE_MSG_LEN; + + REQUIRE(server->handshake(&msg2, &msg1) == 1); + + // FIN + QUICHandshakeMsgs msg3; + uint8_t msg3_buf[MAX_HANDSHAKE_MSG_LEN] = {0}; + msg3.buf = msg3_buf; + msg3.max_buf_len = MAX_HANDSHAKE_MSG_LEN; + +#ifdef SSL_MODE_QUIC_HACK + // -- Hacks for OpenSSL with SSL_MODE_QUIC_HACK -- + // SH + QUICHandshakeMsgs msg2_1; + uint8_t msg2_1_buf[MAX_HANDSHAKE_MSG_LEN] = {0}; + msg2_1.buf = msg2_1_buf; + msg2_1.max_buf_len = MAX_HANDSHAKE_MSG_LEN; + + memcpy(msg2_1.buf, msg2.buf, msg2.offsets[1]); + msg2_1.offsets[0] = 0; + msg2_1.offsets[1] = msg2.offsets[1]; + msg2_1.offsets[2] = msg2.offsets[1]; + msg2_1.offsets[3] = msg2.offsets[1]; + msg2_1.offsets[4] = msg2.offsets[1]; + + // EE - FIN + QUICHandshakeMsgs msg2_2; + uint8_t msg2_2_buf[MAX_HANDSHAKE_MSG_LEN] = {0}; + msg2_2.buf = msg2_2_buf; + msg2_2.max_buf_len = MAX_HANDSHAKE_MSG_LEN; + + size_t len = msg2.offsets[3] - msg2.offsets[2]; + memcpy(msg2_2.buf, msg2.buf + msg2.offsets[1], len); + msg2_2.offsets[0] = 0; + msg2_2.offsets[1] = 0; + msg2_2.offsets[2] = 0; + msg2_2.offsets[3] = len; + msg2_2.offsets[4] = len; + + REQUIRE(client->handshake(&msg3, &msg2_1) == 1); + REQUIRE(client->handshake(&msg3, &msg2_2) == 1); +#else + REQUIRE(client->handshake(&msg3, &msg2) == 1); +#endif + + // NS + QUICHandshakeMsgs msg4; + uint8_t msg4_buf[MAX_HANDSHAKE_MSG_LEN] = {0}; + msg4.buf = msg4_buf; + msg4.max_buf_len = MAX_HANDSHAKE_MSG_LEN; + + REQUIRE(server->handshake(&msg4, &msg3) == 1); + + QUICHandshakeMsgs msg5; + uint8_t msg5_buf[MAX_HANDSHAKE_MSG_LEN] = {0}; + msg5.buf = msg5_buf; + msg5.max_buf_len = MAX_HANDSHAKE_MSG_LEN; + REQUIRE(client->handshake(&msg5, &msg4) == 1); + + // # End Handshake + + // Teardown + delete client; + delete server; + } + + BIO_free(crt_bio); + BIO_free(key_bio); + + X509_free(x509); + EVP_PKEY_free(pkey); + + SSL_CTX_free(server_ssl_ctx); + SSL_CTX_free(client_ssl_ctx); +} diff --git a/iocore/net/quic/test/test_QUICIncomingFrameBuffer.cc b/iocore/net/quic/test/test_QUICIncomingFrameBuffer.cc new file mode 100644 index 00000000000..50a3dfaf364 --- /dev/null +++ b/iocore/net/quic/test/test_QUICIncomingFrameBuffer.cc @@ -0,0 +1,263 @@ +/** @file + * + * A brief file description + * + * @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 "catch.hpp" + +#include "quic/QUICIncomingFrameBuffer.h" +#include "quic/QUICBidirectionalStream.h" +#include + +TEST_CASE("QUICIncomingStreamFrameBuffer_fin_offset", "[quic]") +{ + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + QUICBidirectionalStream *stream = new QUICBidirectionalStream(); + QUICIncomingStreamFrameBuffer buffer; + QUICErrorUPtr err = nullptr; + + Ptr block_1024 = make_ptr(new_IOBufferBlock()); + block_1024->alloc(); + block_1024->fill(1024); + CHECK(block_1024->read_avail() == 1024); + + Ptr block_0 = make_ptr(new_IOBufferBlock()); + block_0->alloc(); + CHECK(block_0->read_avail() == 0); + + SECTION("single frame") + { + QUICStreamFrame *stream1_frame_0_r = QUICFrameFactory::create_stream_frame(frame_buf, block_1024, 1, 0, true); + + err = buffer.insert(new QUICStreamFrame(*stream1_frame_0_r)); + CHECK(err == nullptr); + + buffer.clear(); + } + + SECTION("multiple frames") + { + uint8_t frame_buf0[QUICFrame::MAX_INSTANCE_SIZE]; + uint8_t frame_buf1[QUICFrame::MAX_INSTANCE_SIZE]; + uint8_t frame_buf2[QUICFrame::MAX_INSTANCE_SIZE]; + uint8_t frame_buf3[QUICFrame::MAX_INSTANCE_SIZE]; + uint8_t frame_buf4[QUICFrame::MAX_INSTANCE_SIZE]; + QUICStreamFrame *stream1_frame_0_r = QUICFrameFactory::create_stream_frame(frame_buf0, block_1024, 1, 0); + QUICStreamFrame *stream1_frame_1_r = QUICFrameFactory::create_stream_frame(frame_buf1, block_1024, 1, 1024); + QUICStreamFrame *stream1_frame_2_r = QUICFrameFactory::create_stream_frame(frame_buf2, block_1024, 1, 2048, true); + QUICStreamFrame *stream1_frame_3_r = QUICFrameFactory::create_stream_frame(frame_buf3, block_1024, 1, 3072, true); + QUICStreamFrame *stream1_frame_4_r = QUICFrameFactory::create_stream_frame(frame_buf4, block_1024, 1, 4096); + + buffer.insert(new QUICStreamFrame(*stream1_frame_0_r)); + buffer.insert(new QUICStreamFrame(*stream1_frame_1_r)); + buffer.insert(new QUICStreamFrame(*stream1_frame_2_r)); + err = buffer.insert(new QUICStreamFrame(*stream1_frame_3_r)); + CHECK(err->cls == QUICErrorClass::TRANSPORT); + CHECK(err->code == static_cast(QUICTransErrorCode::FINAL_OFFSET_ERROR)); + + buffer.clear(); + + QUICIncomingStreamFrameBuffer buffer2; + + buffer2.insert(new QUICStreamFrame(*stream1_frame_3_r)); + buffer2.insert(new QUICStreamFrame(*stream1_frame_0_r)); + buffer2.insert(new QUICStreamFrame(*stream1_frame_1_r)); + err = buffer2.insert(new QUICStreamFrame(*stream1_frame_2_r)); + CHECK(err->cls == QUICErrorClass::TRANSPORT); + CHECK(err->code == static_cast(QUICTransErrorCode::FINAL_OFFSET_ERROR)); + + buffer2.clear(); + + QUICIncomingStreamFrameBuffer buffer3; + + buffer3.insert(new QUICStreamFrame(*stream1_frame_4_r)); + err = buffer3.insert(new QUICStreamFrame(*stream1_frame_3_r)); + CHECK(err->cls == QUICErrorClass::TRANSPORT); + CHECK(err->code == static_cast(QUICTransErrorCode::FINAL_OFFSET_ERROR)); + + buffer3.clear(); + } + + SECTION("Pure FIN") + { + uint8_t frame_buf0[QUICFrame::MAX_INSTANCE_SIZE]; + uint8_t frame_buf1[QUICFrame::MAX_INSTANCE_SIZE]; + uint8_t frame_buf2[QUICFrame::MAX_INSTANCE_SIZE]; + QUICStreamFrame *stream1_frame_0_r = QUICFrameFactory::create_stream_frame(frame_buf0, block_1024, 1, 0); + QUICStreamFrame *stream1_frame_empty = QUICFrameFactory::create_stream_frame(frame_buf1, block_0, 1, 1024); + QUICStreamFrame *stream1_frame_pure_fin = QUICFrameFactory::create_stream_frame(frame_buf2, block_0, 1, 1024, true); + + err = buffer.insert(new QUICStreamFrame(*stream1_frame_0_r)); + CHECK(err == nullptr); + + err = buffer.insert(new QUICStreamFrame(*stream1_frame_empty)); + CHECK(err == nullptr); + + err = buffer.insert(new QUICStreamFrame(*stream1_frame_pure_fin)); + CHECK(err == nullptr); + + buffer.clear(); + } + + delete stream; +} + +TEST_CASE("QUICIncomingStreamFrameBuffer_pop", "[quic]") +{ + QUICBidirectionalStream *stream = new QUICBidirectionalStream(); + QUICIncomingStreamFrameBuffer buffer; + QUICErrorUPtr err = nullptr; + + Ptr block_1024 = make_ptr(new_IOBufferBlock()); + block_1024->alloc(); + block_1024->fill(1024); + CHECK(block_1024->read_avail() == 1024); + + Ptr block_0 = make_ptr(new_IOBufferBlock()); + block_0->alloc(); + CHECK(block_0->read_avail() == 0); + + uint8_t frame_buf0[QUICFrame::MAX_INSTANCE_SIZE]; + uint8_t frame_buf1[QUICFrame::MAX_INSTANCE_SIZE]; + uint8_t frame_buf2[QUICFrame::MAX_INSTANCE_SIZE]; + uint8_t frame_buf3[QUICFrame::MAX_INSTANCE_SIZE]; + uint8_t frame_buf4[QUICFrame::MAX_INSTANCE_SIZE]; + uint8_t frame_buf5[QUICFrame::MAX_INSTANCE_SIZE]; + QUICStreamFrame *stream1_frame_0_r = QUICFrameFactory::create_stream_frame(frame_buf0, block_1024, 1, 0); + QUICStreamFrame *stream1_frame_1_r = QUICFrameFactory::create_stream_frame(frame_buf1, block_1024, 1, 1024); + QUICStreamFrame *stream1_frame_empty = QUICFrameFactory::create_stream_frame(frame_buf2, block_0, 1, 2048); + QUICStreamFrame *stream1_frame_2_r = QUICFrameFactory::create_stream_frame(frame_buf3, block_1024, 1, 2048); + QUICStreamFrame *stream1_frame_3_r = QUICFrameFactory::create_stream_frame(frame_buf4, block_1024, 1, 3072); + QUICStreamFrame *stream1_frame_4_r = QUICFrameFactory::create_stream_frame(frame_buf5, block_1024, 1, 4096, true); + + buffer.insert(new QUICStreamFrame(*stream1_frame_0_r)); + buffer.insert(new QUICStreamFrame(*stream1_frame_1_r)); + buffer.insert(new QUICStreamFrame(*stream1_frame_empty)); + buffer.insert(new QUICStreamFrame(*stream1_frame_2_r)); + buffer.insert(new QUICStreamFrame(*stream1_frame_3_r)); + buffer.insert(new QUICStreamFrame(*stream1_frame_4_r)); + CHECK(!buffer.empty()); + + auto frame = static_cast(buffer.pop()); + CHECK(frame->offset() == 0); + frame = static_cast(buffer.pop()); + CHECK(frame->offset() == 1024); + frame = static_cast(buffer.pop()); + CHECK(frame->offset() == 2048); + frame = static_cast(buffer.pop()); + CHECK(frame->offset() == 3072); + frame = static_cast(buffer.pop()); + CHECK(frame->offset() == 4096); + CHECK(buffer.empty()); + + buffer.clear(); + + buffer.insert(new QUICStreamFrame(*stream1_frame_4_r)); + buffer.insert(new QUICStreamFrame(*stream1_frame_3_r)); + buffer.insert(new QUICStreamFrame(*stream1_frame_2_r)); + buffer.insert(new QUICStreamFrame(*stream1_frame_1_r)); + buffer.insert(new QUICStreamFrame(*stream1_frame_0_r)); + CHECK(!buffer.empty()); + + frame = static_cast(buffer.pop()); + CHECK(frame->offset() == 0); + frame = static_cast(buffer.pop()); + CHECK(frame->offset() == 1024); + frame = static_cast(buffer.pop()); + CHECK(frame->offset() == 2048); + frame = static_cast(buffer.pop()); + CHECK(frame->offset() == 3072); + frame = static_cast(buffer.pop()); + CHECK(frame->offset() == 4096); + CHECK(buffer.empty()); + + delete stream; +} + +TEST_CASE("QUICIncomingStreamFrameBuffer_dup_frame", "[quic]") +{ + QUICBidirectionalStream *stream = new QUICBidirectionalStream(); + QUICIncomingStreamFrameBuffer buffer; + QUICErrorUPtr err = nullptr; + + Ptr block_1024 = make_ptr(new_IOBufferBlock()); + block_1024->alloc(); + block_1024->fill(1024); + CHECK(block_1024->read_avail() == 1024); + + Ptr block_0 = make_ptr(new_IOBufferBlock()); + block_0->alloc(); + CHECK(block_0->read_avail() == 0); + + uint8_t frame_buf0[QUICFrame::MAX_INSTANCE_SIZE]; + uint8_t frame_buf1[QUICFrame::MAX_INSTANCE_SIZE]; + uint8_t frame_buf2[QUICFrame::MAX_INSTANCE_SIZE]; + uint8_t frame_buf3[QUICFrame::MAX_INSTANCE_SIZE]; + uint8_t frame_buf4[QUICFrame::MAX_INSTANCE_SIZE]; + uint8_t frame_buf5[QUICFrame::MAX_INSTANCE_SIZE]; + uint8_t frame_buf6[QUICFrame::MAX_INSTANCE_SIZE]; + uint8_t frame_buf7[QUICFrame::MAX_INSTANCE_SIZE]; + QUICStreamFrame *stream1_frame_0_r = QUICFrameFactory::create_stream_frame(frame_buf0, block_1024, 1, 0); + QUICStreamFrame *stream1_frame_1_r = QUICFrameFactory::create_stream_frame(frame_buf1, block_1024, 1, 1024); + QUICStreamFrame *stream1_frame_2_r = QUICFrameFactory::create_stream_frame(frame_buf2, block_1024, 1, 2048, true); + QUICStreamFrame *stream1_frame_3_r = QUICFrameFactory::create_stream_frame(frame_buf3, block_1024, 1, 2048, true); + + buffer.insert(new QUICStreamFrame(*stream1_frame_0_r)); + buffer.insert(new QUICStreamFrame(*stream1_frame_1_r)); + buffer.insert(new QUICStreamFrame(*stream1_frame_2_r)); + err = buffer.insert(new QUICStreamFrame(*stream1_frame_3_r)); + CHECK(err == nullptr); + + auto frame = static_cast(buffer.pop()); + CHECK(frame->offset() == 0); + frame = static_cast(buffer.pop()); + CHECK(frame->offset() == 1024); + frame = static_cast(buffer.pop()); + CHECK(frame->offset() == 2048); + frame = static_cast(buffer.pop()); + CHECK(frame == nullptr); + CHECK(buffer.empty()); + + buffer.clear(); + + QUICStreamFrame *stream2_frame_0_r = QUICFrameFactory::create_stream_frame(frame_buf4, block_1024, 1, 0); + QUICStreamFrame *stream2_frame_1_r = QUICFrameFactory::create_stream_frame(frame_buf5, block_1024, 1, 1024); + QUICStreamFrame *stream2_frame_2_r = QUICFrameFactory::create_stream_frame(frame_buf6, block_1024, 1, 1024); + QUICStreamFrame *stream2_frame_3_r = QUICFrameFactory::create_stream_frame(frame_buf7, block_1024, 1, 2048, true); + + buffer.insert(new QUICStreamFrame(*stream2_frame_0_r)); + buffer.insert(new QUICStreamFrame(*stream2_frame_1_r)); + buffer.insert(new QUICStreamFrame(*stream2_frame_2_r)); + err = buffer.insert(new QUICStreamFrame(*stream2_frame_3_r)); + CHECK(err == nullptr); + + frame = static_cast(buffer.pop()); + CHECK(frame->offset() == 0); + frame = static_cast(buffer.pop()); + CHECK(frame->offset() == 1024); + frame = static_cast(buffer.pop()); + CHECK(frame->offset() == 2048); + frame = static_cast(buffer.pop()); + CHECK(frame == nullptr); + CHECK(buffer.empty()); + + delete stream; +} diff --git a/iocore/net/quic/test/test_QUICInvariants.cc b/iocore/net/quic/test/test_QUICInvariants.cc new file mode 100644 index 00000000000..5cdbc28b632 --- /dev/null +++ b/iocore/net/quic/test/test_QUICInvariants.cc @@ -0,0 +1,224 @@ +/** @file + * + * A brief file description + * + * @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 "catch.hpp" + +#include "quic/QUICTypes.h" + +TEST_CASE("Long Header - regular case", "[quic]") +{ + const uint8_t raw_dcid[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; + const uint8_t raw_scid[] = {0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18}; + QUICConnectionId expected_dcid(raw_dcid, 8); + QUICConnectionId expected_scid(raw_scid, 8); + + SECTION("dcid & scid") + { + const uint8_t buf[] = { + 0x80, // Long header, Type: NONE + 0x11, 0x22, 0x33, 0x44, // Version + 0x55, // DCIL/SCIL + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // Destination Connection ID + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, // Source Connection ID + }; + uint64_t buf_len = sizeof(buf); + + QUICVersion version = 0; + uint8_t dcil = 0; + uint8_t scil = 0; + QUICConnectionId dcid = QUICConnectionId::ZERO(); + QUICConnectionId scid = QUICConnectionId::ZERO(); + + CHECK(QUICInvariants::version(version, buf, buf_len)); + CHECK(version == 0x11223344); + + CHECK(QUICInvariants::dcil(dcil, buf, buf_len)); + CHECK(dcil == 5); + CHECK(QUICInvariants::dcid(dcid, buf, buf_len)); + CHECK(dcid == expected_dcid); + + CHECK(QUICInvariants::scil(scil, buf, buf_len)); + CHECK(scil == 5); + CHECK(QUICInvariants::scid(scid, buf, buf_len)); + CHECK(scid == expected_scid); + } + + SECTION("omitted dcid") + { + const uint8_t buf[] = { + 0x80, // Long header, Type: NONE + 0x11, 0x22, 0x33, 0x44, // Version + 0x05, // DCIL/SCIL + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, // Source Connection ID + }; + uint64_t buf_len = sizeof(buf); + + QUICVersion version = 0; + uint8_t dcil = 0; + uint8_t scil = 0; + QUICConnectionId dcid = QUICConnectionId::ZERO(); + QUICConnectionId scid = QUICConnectionId::ZERO(); + + CHECK(QUICInvariants::version(version, buf, buf_len)); + CHECK(version == 0x11223344); + + CHECK(QUICInvariants::dcil(dcil, buf, buf_len)); + CHECK(dcil == 0); + CHECK(QUICInvariants::dcid(dcid, buf, buf_len)); + CHECK(dcid == QUICConnectionId::ZERO()); + + CHECK(QUICInvariants::scil(scil, buf, buf_len)); + CHECK(scil == 5); + CHECK(QUICInvariants::scid(scid, buf, buf_len)); + CHECK(scid == expected_scid); + } + + SECTION("omitted scid") + { + const uint8_t buf[] = { + 0x80, // Long header, Type: NONE + 0x11, 0x22, 0x33, 0x44, // Version + 0x50, // DCIL/SCIL + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // Destination Connection ID + }; + uint64_t buf_len = sizeof(buf); + + QUICVersion version = 0; + uint8_t dcil = 0; + uint8_t scil = 0; + QUICConnectionId dcid = QUICConnectionId::ZERO(); + QUICConnectionId scid = QUICConnectionId::ZERO(); + + CHECK(QUICInvariants::version(version, buf, buf_len)); + CHECK(version == 0x11223344); + + CHECK(QUICInvariants::dcil(dcil, buf, buf_len)); + CHECK(dcil == 5); + CHECK(QUICInvariants::dcid(dcid, buf, buf_len)); + CHECK(dcid == expected_dcid); + + CHECK(QUICInvariants::scil(scil, buf, buf_len)); + CHECK(scil == 0); + CHECK(QUICInvariants::scid(scid, buf, buf_len)); + CHECK(scid == QUICConnectionId::ZERO()); + } +} + +TEST_CASE("Long Header - error cases", "[quic]") +{ + SECTION("version") + { + const uint8_t buf[] = { + 0x80, // Long header, Type: NONE + }; + uint64_t buf_len = sizeof(buf); + + QUICVersion version = 0; + + CHECK(QUICInvariants::version(version, buf, buf_len) == false); + } + + SECTION("dcid") + { + const uint8_t buf[] = { + 0x80, // Long header, Type: NONE + 0x11, 0x22, 0x33, 0x44, // Version + 0x55, // DCIL/SCIL + 0x01, 0x02, 0x03, 0x04, // Invalid Destination Connection ID + }; + uint64_t buf_len = sizeof(buf); + + const uint8_t raw_dcid[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; + QUICConnectionId expected_dcid(raw_dcid, 8); + + QUICVersion version = 0; + QUICConnectionId dcid = QUICConnectionId::ZERO(); + + CHECK(QUICInvariants::version(version, buf, buf_len)); + CHECK(version == 0x11223344); + CHECK(QUICInvariants::dcid(dcid, buf, buf_len) == false); + } + + SECTION("scid") + { + const uint8_t buf[] = { + 0x80, // Long header, Type: NONE + 0x11, 0x22, 0x33, 0x44, // Version + 0x55, // DCIL/SCIL + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // Destination Connection ID + 0x11, 0x12, 0x13, 0x14, // Invalid Source Connection ID + }; + uint64_t buf_len = sizeof(buf); + + const uint8_t raw_dcid[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; + const uint8_t raw_scid[] = {0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18}; + QUICConnectionId expected_dcid(raw_dcid, 8); + QUICConnectionId expected_scid(raw_scid, 8); + + QUICVersion version = 0; + QUICConnectionId dcid = QUICConnectionId::ZERO(); + QUICConnectionId scid = QUICConnectionId::ZERO(); + + CHECK(QUICInvariants::version(version, buf, buf_len)); + CHECK(version == 0x11223344); + CHECK(QUICInvariants::dcid(dcid, buf, buf_len)); + CHECK(dcid == expected_dcid); + CHECK(QUICInvariants::scid(scid, buf, buf_len) == false); + } +} + +// When ATS change QUICConfigParams::_scid_len shorter, this test should be failed +TEST_CASE("Short Header - regular case", "[quic]") +{ + const uint8_t buf[] = { + 0x00, // Long header, Type: NONE + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // Destination Connection ID (144) + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, // + 0x10, 0x11, // + }; + uint64_t buf_len = sizeof(buf); + + const uint8_t raw_dcid[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, // + 0x10, 0x11, // + }; + QUICConnectionId expected_dcid(raw_dcid, sizeof(raw_dcid)); + + QUICConnectionId dcid = QUICConnectionId::ZERO(); + + CHECK(QUICInvariants::dcid(dcid, buf, buf_len)); + CHECK(dcid == expected_dcid); +} + +TEST_CASE("Short Header - error case", "[quic]") +{ + const uint8_t buf[] = { + 0x00, // Long header, Type: NONE + 0x01, 0x02, 0x03, 0x04, // Invalid Destination Connection ID + }; + uint64_t buf_len = sizeof(buf); + + QUICConnectionId dcid = QUICConnectionId::ZERO(); + CHECK(QUICInvariants::dcid(dcid, buf, buf_len) == false); +} diff --git a/iocore/net/quic/test/test_QUICKeyGenerator.cc b/iocore/net/quic/test/test_QUICKeyGenerator.cc new file mode 100644 index 00000000000..e24d3dbb711 --- /dev/null +++ b/iocore/net/quic/test/test_QUICKeyGenerator.cc @@ -0,0 +1,100 @@ +/** @file + * + * A brief file description + * + * @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 "catch.hpp" + +#include +#include + +#ifdef OPENSSL_IS_BORINGSSL +#include +#endif + +#include + +#include "QUICKeyGenerator.h" +#include "QUICPacketProtectionKeyInfo.h" + +// https://github.com/quicwg/base-drafts/wiki/Test-Vector-for-the-Clear-Text-AEAD-key-derivation +TEST_CASE("draft-17 Test Vectors", "[quic]") +{ + SECTION("CLIENT Initial") + { + QUICKeyGenerator keygen(QUICKeyGenerator::Context::CLIENT); + + QUICConnectionId cid = {reinterpret_cast("\xc6\x54\xef\xd8\xa3\x1b\x47\x92"), 8}; + + uint8_t expected_client_key[] = { + 0x86, 0xd1, 0x83, 0x04, 0x80, 0xb4, 0x0f, 0x86, 0xcf, 0x9d, 0x68, 0xdc, 0xad, 0xf3, 0x5d, 0xfe, + }; + uint8_t expected_client_iv[] = { + 0x12, 0xf3, 0x93, 0x8a, 0xca, 0x34, 0xaa, 0x02, 0x54, 0x31, 0x63, 0xd4, + }; + uint8_t expected_client_hp[] = { + 0xcd, 0x25, 0x3a, 0x36, 0xff, 0x93, 0x93, 0x7c, 0x46, 0x93, 0x84, 0xa8, 0x23, 0xaf, 0x6c, 0x56, + }; + + QUICPacketProtectionKeyInfo pp_key_info; + pp_key_info.set_cipher_initial(EVP_aes_128_gcm()); + pp_key_info.set_cipher_for_hp_initial(EVP_aes_128_ecb()); + keygen.generate(pp_key_info.encryption_key_for_hp(QUICKeyPhase::INITIAL), pp_key_info.encryption_key(QUICKeyPhase::INITIAL), + pp_key_info.encryption_iv(QUICKeyPhase::INITIAL), pp_key_info.encryption_iv_len(QUICKeyPhase::INITIAL), cid); + + CHECK(pp_key_info.encryption_key_len(QUICKeyPhase::INITIAL) == sizeof(expected_client_key)); + CHECK(memcmp(pp_key_info.encryption_key(QUICKeyPhase::INITIAL), expected_client_key, sizeof(expected_client_key)) == 0); + CHECK(*pp_key_info.encryption_iv_len(QUICKeyPhase::INITIAL) == sizeof(expected_client_iv)); + CHECK(memcmp(pp_key_info.encryption_iv(QUICKeyPhase::INITIAL), expected_client_iv, sizeof(expected_client_iv)) == 0); + CHECK(pp_key_info.encryption_key_for_hp_len(QUICKeyPhase::INITIAL) == sizeof(expected_client_hp)); + CHECK(memcmp(pp_key_info.encryption_key_for_hp(QUICKeyPhase::INITIAL), expected_client_hp, sizeof(expected_client_hp)) == 0); + } + + SECTION("SERVER Initial") + { + QUICKeyGenerator keygen(QUICKeyGenerator::Context::SERVER); + + QUICConnectionId cid = {reinterpret_cast("\xc6\x54\xef\xd8\xa3\x1b\x47\x92"), 8}; + + uint8_t expected_server_key[] = { + 0x2c, 0x78, 0x63, 0x3e, 0x20, 0x6e, 0x99, 0xad, 0x25, 0x19, 0x64, 0xf1, 0x9f, 0x6d, 0xcd, 0x6d, + }; + uint8_t expected_server_iv[] = { + 0x7b, 0x50, 0xbf, 0x36, 0x98, 0xa0, 0x6d, 0xfa, 0xbf, 0x75, 0xf2, 0x87, + }; + uint8_t expected_server_hp[] = { + 0x25, 0x79, 0xd8, 0x69, 0x6f, 0x85, 0xed, 0xa6, 0x8d, 0x35, 0x02, 0xb6, 0x55, 0x96, 0x58, 0x6b, + }; + + QUICPacketProtectionKeyInfo pp_key_info; + pp_key_info.set_cipher_initial(EVP_aes_128_gcm()); + pp_key_info.set_cipher_for_hp_initial(EVP_aes_128_ecb()); + keygen.generate(pp_key_info.encryption_key_for_hp(QUICKeyPhase::INITIAL), pp_key_info.encryption_key(QUICKeyPhase::INITIAL), + pp_key_info.encryption_iv(QUICKeyPhase::INITIAL), pp_key_info.encryption_iv_len(QUICKeyPhase::INITIAL), cid); + + CHECK(pp_key_info.encryption_key_len(QUICKeyPhase::INITIAL) == sizeof(expected_server_key)); + CHECK(memcmp(pp_key_info.encryption_key(QUICKeyPhase::INITIAL), expected_server_key, sizeof(expected_server_key)) == 0); + CHECK(*pp_key_info.encryption_iv_len(QUICKeyPhase::INITIAL) == sizeof(expected_server_iv)); + CHECK(memcmp(pp_key_info.encryption_iv(QUICKeyPhase::INITIAL), expected_server_iv, sizeof(expected_server_iv)) == 0); + CHECK(pp_key_info.encryption_key_for_hp_len(QUICKeyPhase::INITIAL) == sizeof(expected_server_hp)); + CHECK(memcmp(pp_key_info.encryption_key_for_hp(QUICKeyPhase::INITIAL), expected_server_hp, sizeof(expected_server_hp)) == 0); + } +} diff --git a/iocore/net/quic/test/test_QUICLossDetector.cc b/iocore/net/quic/test/test_QUICLossDetector.cc new file mode 100644 index 00000000000..00167247299 --- /dev/null +++ b/iocore/net/quic/test/test_QUICLossDetector.cc @@ -0,0 +1,285 @@ +/** @file + * + * A brief file description + * + * @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 "catch.hpp" + +#include "QUICLossDetector.h" +#include "QUICEvents.h" +#include "Mock.h" +#include "tscore/ink_hrtime.h" + +TEST_CASE("QUICLossDetector_Loss", "[quic]") +{ + MockQUICPacketProtectionKeyInfo pp_key_info; + pp_key_info.set_encryption_key_available(QUICKeyPhase::PHASE_0); + + QUICPacketFactory pf(pp_key_info); + QUICRTTMeasure rtt_measure; + + QUICAckFrameManager afm; + QUICConnectionId connection_id = {reinterpret_cast("\x01"), 1}; + MockQUICCCConfig cc_config; + MockQUICLDConfig ld_config; + MockQUICConnectionInfoProvider info; + MockQUICCongestionController cc(&info, cc_config); + QUICLossDetector detector(&info, &cc, &rtt_measure, ld_config); + ats_unique_buf payload = ats_unique_malloc(512); + size_t payload_len = 512; + QUICPacketUPtr packet = QUICPacketFactory::create_null_packet(); + QUICAckFrame *frame = nullptr; + + SECTION("Handshake") + { + MockQUICFrameGenerator g; + // Check initial state + uint8_t frame_buffer[1024] = {0}; + CHECK(g.lost_frame_count == 0); + QUICFrame *ping_frame = g.generate_frame(frame_buffer, QUICEncryptionLevel::HANDSHAKE, 4, UINT16_MAX, 0); + + uint8_t raw[4]; + size_t len; + CHECK(ping_frame->store(raw, &len, 10240) < 4); + + // Send SERVER_CLEARTEXT (Handshake message) + ats_unique_buf payload = ats_unique_malloc(sizeof(raw)); + memcpy(payload.get(), raw, sizeof(raw)); + + QUICPacketHeaderUPtr header = + QUICPacketHeader::build(QUICPacketType::HANDSHAKE, QUICKeyPhase::HANDSHAKE, + {reinterpret_cast("\xff\xdd\xbb\x99\x77\x55\x33\x11"), 8}, + {reinterpret_cast("\x11\x12\x13\x14\x15\x16\x17\x18"), 8}, 0x00000001, 0, 0x00112233, + false, std::move(payload), sizeof(raw)); + QUICPacketUPtr packet = QUICPacketUPtr(new QUICPacket(std::move(header), std::move(payload), sizeof(raw), true, false), + [](QUICPacket *p) { delete p; }); + detector.on_packet_sent(QUICPacketInfoUPtr(new QUICPacketInfo{ + packet->packet_number(), + Thread::get_hrtime(), + packet->is_ack_eliciting(), + packet->is_crypto_packet(), + true, + packet->size(), + packet->type(), + {}, + QUICPacketNumberSpace::Handshake, + })); + ink_hrtime_sleep(HRTIME_MSECONDS(1000)); + CHECK(g.lost_frame_count >= 0); + + // Receive ACK + QUICAckFrame frame(0x01, 20, 0); + frame.ack_block_section()->add_ack_block({0, 1ULL}); + detector.handle_frame(QUICEncryptionLevel::INITIAL, frame); + ink_hrtime_sleep(HRTIME_MSECONDS(1500)); + int retransmit_count = g.lost_frame_count; + ink_hrtime_sleep(HRTIME_MSECONDS(1500)); + CHECK(g.lost_frame_count == retransmit_count); + } + + SECTION("1-RTT") + { + // Send packet (1) to (7) + QUICPacketNumberSpace pn_space = QUICPacketNumberSpace::ApplicationData; + QUICEncryptionLevel level = QUICEncryptionLevel::ONE_RTT; + payload = ats_unique_malloc(payload_len); + QUICPacketUPtr packet1 = pf.create_protected_packet(connection_id, detector.largest_acked_packet_number(pn_space), + std::move(payload), payload_len, true, false); + REQUIRE(packet1 != nullptr); + payload = ats_unique_malloc(payload_len); + QUICPacketUPtr packet2 = pf.create_protected_packet(connection_id, detector.largest_acked_packet_number(pn_space), + std::move(payload), payload_len, true, false); + payload = ats_unique_malloc(payload_len); + QUICPacketUPtr packet3 = pf.create_protected_packet(connection_id, detector.largest_acked_packet_number(pn_space), + std::move(payload), payload_len, true, false); + payload = ats_unique_malloc(payload_len); + QUICPacketUPtr packet4 = pf.create_protected_packet(connection_id, detector.largest_acked_packet_number(pn_space), + std::move(payload), payload_len, true, false); + payload = ats_unique_malloc(payload_len); + QUICPacketUPtr packet5 = pf.create_protected_packet(connection_id, detector.largest_acked_packet_number(pn_space), + std::move(payload), payload_len, true, false); + payload = ats_unique_malloc(payload_len); + QUICPacketUPtr packet6 = pf.create_protected_packet(connection_id, detector.largest_acked_packet_number(pn_space), + std::move(payload), payload_len, true, false); + payload = ats_unique_malloc(payload_len); + QUICPacketUPtr packet7 = pf.create_protected_packet(connection_id, detector.largest_acked_packet_number(pn_space), + std::move(payload), payload_len, true, false); + payload = ats_unique_malloc(payload_len); + QUICPacketUPtr packet8 = pf.create_protected_packet(connection_id, detector.largest_acked_packet_number(pn_space), + std::move(payload), payload_len, true, false); + payload = ats_unique_malloc(payload_len); + QUICPacketUPtr packet9 = pf.create_protected_packet(connection_id, detector.largest_acked_packet_number(pn_space), + std::move(payload), payload_len, true, false); + payload = ats_unique_malloc(payload_len); + QUICPacketUPtr packet10 = pf.create_protected_packet(connection_id, detector.largest_acked_packet_number(pn_space), + std::move(payload), payload_len, true, false); + + QUICPacketNumber pn1 = packet1->packet_number(); + QUICPacketNumber pn2 = packet2->packet_number(); + QUICPacketNumber pn3 = packet3->packet_number(); + QUICPacketNumber pn4 = packet4->packet_number(); + QUICPacketNumber pn5 = packet5->packet_number(); + QUICPacketNumber pn6 = packet6->packet_number(); + QUICPacketNumber pn7 = packet7->packet_number(); + QUICPacketNumber pn8 = packet8->packet_number(); + QUICPacketNumber pn9 = packet9->packet_number(); + QUICPacketNumber pn10 = packet10->packet_number(); + + QUICPacketInfoUPtr packet_info = nullptr; + detector.on_packet_sent(QUICPacketInfoUPtr(new QUICPacketInfo{packet1->packet_number(), + Thread::get_hrtime(), + packet1->is_ack_eliciting(), + packet1->is_crypto_packet(), + true, + packet1->size(), + packet1->type(), + {}, + pn_space})); + detector.on_packet_sent(QUICPacketInfoUPtr(new QUICPacketInfo{packet2->packet_number(), + Thread::get_hrtime(), + packet2->is_ack_eliciting(), + packet2->is_crypto_packet(), + true, + packet2->size(), + packet2->type(), + {}, + pn_space})); + detector.on_packet_sent(QUICPacketInfoUPtr(new QUICPacketInfo{packet3->packet_number(), + Thread::get_hrtime(), + packet3->is_ack_eliciting(), + packet3->is_crypto_packet(), + true, + packet3->size(), + packet3->type(), + {}, + pn_space})); + detector.on_packet_sent(QUICPacketInfoUPtr(new QUICPacketInfo{packet4->packet_number(), + Thread::get_hrtime(), + packet4->is_ack_eliciting(), + packet4->is_crypto_packet(), + true, + packet4->size(), + packet4->type(), + {}, + pn_space})); + detector.on_packet_sent(QUICPacketInfoUPtr(new QUICPacketInfo{packet5->packet_number(), + Thread::get_hrtime(), + packet5->is_ack_eliciting(), + packet5->is_crypto_packet(), + true, + packet5->size(), + packet5->type(), + {}, + pn_space})); + detector.on_packet_sent(QUICPacketInfoUPtr(new QUICPacketInfo{packet6->packet_number(), + Thread::get_hrtime(), + packet6->is_ack_eliciting(), + packet6->is_crypto_packet(), + true, + packet6->size(), + packet6->type(), + {}, + pn_space})); + detector.on_packet_sent(QUICPacketInfoUPtr(new QUICPacketInfo{packet7->packet_number(), + Thread::get_hrtime(), + packet6->is_ack_eliciting(), + packet7->is_crypto_packet(), + true, + packet7->size(), + packet7->type(), + {}, + pn_space})); + detector.on_packet_sent(QUICPacketInfoUPtr(new QUICPacketInfo{packet8->packet_number(), + Thread::get_hrtime(), + packet6->is_ack_eliciting(), + packet8->is_crypto_packet(), + true, + packet8->size(), + packet8->type(), + {}, + pn_space})); + detector.on_packet_sent(QUICPacketInfoUPtr(new QUICPacketInfo{packet9->packet_number(), + Thread::get_hrtime(), + packet6->is_ack_eliciting(), + packet9->is_crypto_packet(), + true, + packet9->size(), + packet9->type(), + {}, + pn_space})); + detector.on_packet_sent(QUICPacketInfoUPtr(new QUICPacketInfo{packet10->packet_number(), + Thread::get_hrtime(), + packet10->is_ack_eliciting(), + packet10->is_crypto_packet(), + true, + packet10->size(), + packet10->type(), + {}, + pn_space})); + + ink_hrtime_sleep(HRTIME_MSECONDS(2000)); + // Receive an ACK for (1) (4) (5) (7) (8) (9) + afm.update(level, pn1, payload_len, false); + afm.update(level, pn4, payload_len, false); + afm.update(level, pn5, payload_len, false); + afm.update(level, pn7, payload_len, false); + afm.update(level, pn8, payload_len, false); + afm.update(level, pn9, payload_len, false); + afm.update(level, pn10, payload_len, false); + uint8_t buf[QUICFrame::MAX_INSTANCE_SIZE]; + QUICFrame *x = afm.generate_frame(buf, level, 2048, 2048, 0); + frame = static_cast(x); + ink_hrtime_sleep(HRTIME_MSECONDS(1000)); + detector.handle_frame(level, *frame); + + // Lost because of packet_threshold. + CHECK(cc.lost_packets.size() == 3); + + CHECK(cc.lost_packets.find(pn1) == cc.lost_packets.end()); + CHECK(cc.lost_packets.find(pn2) != cc.lost_packets.end()); + CHECK(cc.lost_packets.find(pn3) != cc.lost_packets.end()); + CHECK(cc.lost_packets.find(pn4) == cc.lost_packets.end()); + CHECK(cc.lost_packets.find(pn5) == cc.lost_packets.end()); + CHECK(cc.lost_packets.find(pn6) != cc.lost_packets.end()); + CHECK(cc.lost_packets.find(pn7) == cc.lost_packets.end()); + CHECK(cc.lost_packets.find(pn8) == cc.lost_packets.end()); + CHECK(cc.lost_packets.find(pn9) == cc.lost_packets.end()); + CHECK(cc.lost_packets.find(pn9) == cc.lost_packets.end()); + } +} + +TEST_CASE("QUICLossDetector_HugeGap", "[quic]") +{ + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + MockQUICConnectionInfoProvider info; + MockQUICCCConfig cc_config; + MockQUICLDConfig ld_config; + MockQUICCongestionController cc(&info, cc_config); + QUICRTTMeasure rtt_measure; + QUICLossDetector detector(&info, &cc, &rtt_measure, ld_config); + + auto t1 = Thread::get_hrtime(); + QUICAckFrame *ack = QUICFrameFactory::create_ack_frame(frame_buf, 100000000, 100, 10000000); + ack->ack_block_section()->add_ack_block({20000000, 30000000}); + detector.handle_frame(QUICEncryptionLevel::INITIAL, *ack); + auto t2 = Thread::get_hrtime(); + CHECK(t2 - t1 < HRTIME_MSECONDS(100)); +} diff --git a/iocore/net/quic/test/test_QUICPacket.cc b/iocore/net/quic/test/test_QUICPacket.cc new file mode 100644 index 00000000000..f50d156af65 --- /dev/null +++ b/iocore/net/quic/test/test_QUICPacket.cc @@ -0,0 +1,293 @@ +/** @file + * + * A brief file description + * + * @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 "catch.hpp" + +#include "quic/QUICPacket.h" + +TEST_CASE("QUICPacketHeader - Long", "[quic]") +{ + SECTION("Long Header (load) Version Negotiation Packet") + { + const uint8_t input[] = { + 0xc0, // Long header, Type: NONE + 0x00, 0x00, 0x00, 0x00, // Version + 0x55, // DCIL/SCIL + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // Destination Connection ID + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, // Source Connection ID + 0x00, 0x00, 0x00, 0x08, // Supported Version 1 + 0x00, 0x00, 0x00, 0x09, // Supported Version 1 + }; + ats_unique_buf uinput = ats_unique_malloc(sizeof(input)); + memcpy(uinput.get(), input, sizeof(input)); + + QUICPacketHeaderUPtr header = QUICPacketHeader::load({}, std::move(uinput), sizeof(input), 0); + CHECK(header->size() == 22); + CHECK(header->packet_size() == 30); + CHECK(header->type() == QUICPacketType::VERSION_NEGOTIATION); + CHECK( + (header->destination_cid() == QUICConnectionId(reinterpret_cast("\x01\x02\x03\x04\x05\x06\x07\x08"), 8))); + CHECK((header->source_cid() == QUICConnectionId(reinterpret_cast("\x11\x12\x13\x14\x15\x16\x17\x18"), 8))); + CHECK(header->has_version() == true); + CHECK(header->version() == 0x00000000); + } + + SECTION("Long Header (load) INITIAL Packet") + { + const uint8_t input[] = { + 0xc3, // Long header, Type: INITIAL + 0x11, 0x22, 0x33, 0x44, // Version + 0x55, // DCIL/SCIL + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // Destination Connection ID + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, // Source Connection ID + 0x00, // Token Length (i), Token (*) + 0x02, // Payload length + 0x01, 0x23, 0x45, 0x67, // Packet number + 0xff, 0xff, // Payload (dummy) + }; + ats_unique_buf uinput = ats_unique_malloc(sizeof(input)); + memcpy(uinput.get(), input, sizeof(input)); + + QUICPacketHeaderUPtr header = QUICPacketHeader::load({}, std::move(uinput), sizeof(input), 0); + CHECK(header->size() == sizeof(input) - 2); // Packet Length - Payload Length + CHECK(header->packet_size() == sizeof(input)); + CHECK(header->type() == QUICPacketType::INITIAL); + CHECK( + (header->destination_cid() == QUICConnectionId(reinterpret_cast("\x01\x02\x03\x04\x05\x06\x07\x08"), 8))); + CHECK((header->source_cid() == QUICConnectionId(reinterpret_cast("\x11\x12\x13\x14\x15\x16\x17\x18"), 8))); + CHECK(header->packet_number() == 0x01234567); + CHECK(header->has_version() == true); + CHECK(header->version() == 0x11223344); + } + + SECTION("Long Header (load) RETRY Packet") + { + const uint8_t input[] = { + 0xf5, // Long header, Type: RETRY + 0x11, 0x22, 0x33, 0x44, // Version + 0x55, // DCIL/SCIL + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // Destination Connection ID + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, // Source Connection ID + 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, // Original Destination Connection ID + 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, // Retry Token + 0x80, 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0, + }; + ats_unique_buf uinput = ats_unique_malloc(sizeof(input)); + memcpy(uinput.get(), input, sizeof(input)); + + const uint8_t retry_token[] = {0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0}; + + QUICPacketHeaderUPtr header = QUICPacketHeader::load({}, std::move(uinput), sizeof(input), 0); + CHECK(header->size() == sizeof(input) - 16); // Packet Length - Payload Length (Retry Token) + CHECK(header->packet_size() == sizeof(input)); + CHECK(header->type() == QUICPacketType::RETRY); + CHECK( + (header->destination_cid() == QUICConnectionId(reinterpret_cast("\x01\x02\x03\x04\x05\x06\x07\x08"), 8))); + CHECK((header->source_cid() == QUICConnectionId(reinterpret_cast("\x11\x12\x13\x14\x15\x16\x17\x18"), 8))); + + QUICPacketLongHeader *retry_header = static_cast(header.get()); + CHECK((retry_header->original_dcid() == + QUICConnectionId(reinterpret_cast("\x08\x07\x06\x05\x04\x03\x02\x01"), 8))); + + CHECK(memcmp(header->payload(), retry_token, 16) == 0); + CHECK(header->has_version() == true); + CHECK(header->version() == 0x11223344); + } + + SECTION("Long Header (store) INITIAL Packet") + { + uint8_t buf[64] = {0}; + size_t len = 0; + + const uint8_t expected[] = { + 0xc3, // Long header, Type: INITIAL + 0x11, 0x22, 0x33, 0x44, // Version + 0x55, // DCIL/SCIL + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // Destination Connection ID + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, // Source Connection ID + 0x00, // Token Length (i), Token (*) + 0x19, // Length (Not 0x09 because it will have 16 bytes of AEAD tag) + 0x01, 0x23, 0x45, 0x67, // Packet number + 0x11, 0x22, 0x33, 0x44, 0x55, // Payload (dummy) + }; + ats_unique_buf payload = ats_unique_malloc(5); + memcpy(payload.get(), expected + 17, 5); + + QUICPacketHeaderUPtr header = QUICPacketHeader::build( + QUICPacketType::INITIAL, QUICKeyPhase::INITIAL, {reinterpret_cast("\x01\x02\x03\x04\x05\x06\x07\x08"), 8}, + {reinterpret_cast("\x11\x12\x13\x14\x15\x16\x17\x18"), 8}, 0x01234567, 0, 0x11223344, true, + std::move(payload), 5); + + CHECK(header->size() == sizeof(expected) - 5); + CHECK(header->packet_size() == sizeof(expected)); + CHECK(header->type() == QUICPacketType::INITIAL); + CHECK( + (header->destination_cid() == QUICConnectionId(reinterpret_cast("\x01\x02\x03\x04\x05\x06\x07\x08"), 8))); + CHECK((header->source_cid() == QUICConnectionId(reinterpret_cast("\x11\x12\x13\x14\x15\x16\x17\x18"), 8))); + CHECK(header->packet_number() == 0x01234567); + CHECK(header->has_version() == true); + CHECK(header->version() == 0x11223344); + CHECK(header->is_crypto_packet()); + + header->store(buf, &len); + CHECK(len == header->size()); + CHECK(memcmp(buf, expected, len) == 0); + } + + SECTION("Long Header (store) RETRY Packet") + { + uint8_t buf[64] = {0}; + size_t len = 0; + + const uint8_t expected[] = { + 0xf5, // Long header, Type: RETRY + 0x11, 0x22, 0x33, 0x44, // Version + 0x55, // DCIL/SCIL + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // Destination Connection ID + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, // Source Connection ID + 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, // Original Destination Connection ID + 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, // Retry Token + 0x80, 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0, + }; + ats_unique_buf payload = ats_unique_malloc(16); + memcpy(payload.get(), expected + 30, 16); + + QUICPacketHeaderUPtr header = + QUICPacketHeader::build(QUICPacketType::RETRY, QUICKeyPhase::INITIAL, 0x11223344, + {reinterpret_cast("\x01\x02\x03\x04\x05\x06\x07\x08"), 8}, + {reinterpret_cast("\x11\x12\x13\x14\x15\x16\x17\x18"), 8}, + {reinterpret_cast("\x08\x07\x06\x05\x04\x03\x02\x01"), 8}, std::move(payload), 16); + + CHECK(header->size() == sizeof(expected) - 16); + CHECK(header->packet_size() == sizeof(expected)); + CHECK(header->type() == QUICPacketType::RETRY); + CHECK( + (header->destination_cid() == QUICConnectionId(reinterpret_cast("\x01\x02\x03\x04\x05\x06\x07\x08"), 8))); + CHECK((header->source_cid() == QUICConnectionId(reinterpret_cast("\x11\x12\x13\x14\x15\x16\x17\x18"), 8))); + CHECK(header->has_version() == true); + CHECK(header->version() == 0x11223344); + + QUICPacketLongHeader *retry_header = static_cast(header.get()); + CHECK((retry_header->original_dcid() == + QUICConnectionId(reinterpret_cast("\x08\x07\x06\x05\x04\x03\x02\x01"), 8))); + + header->store(buf, &len); + CHECK(len == header->size()); + CHECK(memcmp(buf, expected, 22) == 0); + CHECK(memcmp(buf + 22, expected + 22, 8) == 0); + } +} + +TEST_CASE("QUICPacketHeader - Short", "[quic]") +{ + const uint8_t raw_dcid[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // Destination Connection ID (144) + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, // + 0x10, 0x11, // + }; + QUICConnectionId dcid(raw_dcid, sizeof(raw_dcid)); + + SECTION("Short Header (load)") + { + const uint8_t input[] = { + 0x43, // Short header with (K=0) + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // Destination Connection ID (144) + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, // + 0x10, 0x11, // + 0x01, 0x23, 0x45, 0x67, // Packet number + 0xff, 0xff, // Payload (dummy) + }; + ats_unique_buf uinput = ats_unique_malloc(sizeof(input)); + memcpy(uinput.get(), input, sizeof(input)); + + QUICPacketHeaderUPtr header = QUICPacketHeader::load({}, std::move(uinput), sizeof(input), 0); + CHECK(header->size() == 23); + CHECK(header->packet_size() == 25); + CHECK(header->key_phase() == QUICKeyPhase::PHASE_0); + CHECK(header->destination_cid() == dcid); + CHECK(header->packet_number() == 0x01234567); + CHECK(header->has_version() == false); + } + + SECTION("Short Header (store)") + { + uint8_t buf[32] = {0}; + size_t len = 0; + + const uint8_t expected[] = { + 0x43, // Short header with (K=0) + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // Destination Connection ID (144) + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, // + 0x10, 0x11, // + 0x01, 0x23, 0x45, 0x67, // Packet number + 0x11, 0x22, 0x33, 0x44, 0x55, // Protected Payload + }; + size_t payload_len = 5; + size_t header_len = sizeof(expected) - 5; + + ats_unique_buf payload = ats_unique_malloc(payload_len); + memcpy(payload.get(), expected + header_len, payload_len); + + QUICPacketHeaderUPtr header = + QUICPacketHeader::build(QUICPacketType::PROTECTED, QUICKeyPhase::PHASE_0, dcid, 0x01234567, 0, std::move(payload), 32); + + CHECK(header->size() == 23); + CHECK(header->packet_size() == 0); + CHECK(header->key_phase() == QUICKeyPhase::PHASE_0); + CHECK(header->type() == QUICPacketType::PROTECTED); + CHECK(header->destination_cid() == dcid); + CHECK(header->packet_number() == 0x01234567); + CHECK(header->has_version() == false); + + header->store(buf, &len); + CHECK(len == header_len); + CHECK(memcmp(buf, expected, header_len) == 0); + } +} + +TEST_CASE("Encoded Packet Number Length", "[quic]") +{ + QUICPacketNumber base = 0xabe8bc; + + CHECK(QUICPacket::calc_packet_number_len(0xace8fe, base) == 3); +} + +TEST_CASE("Encoding Packet Number", "[quic]") +{ + QUICPacketNumber dst = 0; + QUICPacketNumber src = 0xaa831f94; + + QUICPacket::encode_packet_number(dst, src, 2); + CHECK(dst == 0x1f94); +} + +TEST_CASE("Decoding Packet Number 1", "[quic]") +{ + QUICPacketNumber dst = 0; + QUICPacketNumber src = 0x9b3; + size_t len = 2; + QUICPacketNumber base = 0xaa82f30e; + + QUICPacket::decode_packet_number(dst, src, len, base); + CHECK(dst == 0xaa8309b3); +} diff --git a/iocore/net/quic/test/test_QUICPacketFactory.cc b/iocore/net/quic/test/test_QUICPacketFactory.cc new file mode 100644 index 00000000000..08d94b494ca --- /dev/null +++ b/iocore/net/quic/test/test_QUICPacketFactory.cc @@ -0,0 +1,123 @@ +/** @file + * + * A brief file description + * + * @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 "catch.hpp" + +#include "quic/QUICPacket.h" +#include "quic/Mock.h" + +TEST_CASE("QUICPacketFactory_Create_VersionNegotiationPacket", "[quic]") +{ + MockQUICPacketProtectionKeyInfo pp_key_info; + QUICPacketFactory factory(pp_key_info); + + const uint8_t raw_dcid[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; + const uint8_t raw_scid[] = {0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18}; + QUICConnectionId dcid(raw_dcid, 8); + QUICConnectionId scid(raw_scid, 8); + + QUICPacketUPtr vn_packet = factory.create_version_negotiation_packet(scid, dcid); + + REQUIRE(vn_packet != nullptr); + CHECK(vn_packet->type() == QUICPacketType::VERSION_NEGOTIATION); + CHECK(vn_packet->destination_cid() == scid); + CHECK(vn_packet->source_cid() == dcid); + CHECK(vn_packet->version() == 0x00); + + QUICVersion supported_version = QUICTypeUtil::read_QUICVersion(vn_packet->payload()); + CHECK(supported_version == QUIC_SUPPORTED_VERSIONS[0]); + + uint8_t expected[] = { + 0xa7, // Long header, Type: NONE + 0x00, 0x00, 0x00, 0x00, // Version + 0x55, // DCIL/SCIL + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, // Destination Connection ID + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // Source Connection ID + 0xff, 0x00, 0x00, 0x14, // Supported Version + 0x1a, 0x2a, 0x3a, 0x4a, // Excercise Version + }; + uint8_t buf[1024] = {0}; + size_t buf_len; + vn_packet->store(buf, &buf_len); + CHECK((buf[0] & 0x80) == 0x80); // Lower 7 bits of the first byte is random + CHECK(memcmp(buf + 1, expected + 1, buf_len - 1) == 0); +} + +TEST_CASE("QUICPacketFactory_Create_Retry", "[quic]") +{ + MockQUICPacketProtectionKeyInfo pp_key_info; + QUICPacketFactory factory(pp_key_info); + factory.set_version(0x11223344); + + uint8_t raw[] = {0xaa, 0xbb, 0xcc, 0xdd}; + QUICRetryToken token(raw, 4); + + QUICPacketUPtr packet = + factory.create_retry_packet(QUICConnectionId(reinterpret_cast("\x01\x02\x03\x04"), 4), + QUICConnectionId(reinterpret_cast("\x11\x12\x13\x14"), 4), + QUICConnectionId(reinterpret_cast("\x04\x03\x02\x01"), 4), token); + + REQUIRE(packet != nullptr); + CHECK(packet->type() == QUICPacketType::RETRY); + CHECK((packet->destination_cid() == QUICConnectionId(reinterpret_cast("\x01\x02\x03\x04"), 4))); + CHECK(memcmp(packet->payload(), raw, sizeof(raw)) == 0); + CHECK(packet->packet_number() == 0); + CHECK(packet->version() == QUIC_SUPPORTED_VERSIONS[0]); +} + +TEST_CASE("QUICPacketFactory_Create_Handshake", "[quic]") +{ + MockQUICPacketProtectionKeyInfo pp_key_info; + pp_key_info.set_encryption_key_available(QUICKeyPhase::HANDSHAKE); + QUICPacketFactory factory(pp_key_info); + factory.set_version(0x11223344); + + uint8_t raw[] = {0xaa, 0xbb, 0xcc, 0xdd}; + ats_unique_buf payload = ats_unique_malloc(sizeof(raw)); + memcpy(payload.get(), raw, sizeof(raw)); + + QUICPacketUPtr packet = + factory.create_handshake_packet(QUICConnectionId(reinterpret_cast("\x01\x02\x03\x04"), 4), + QUICConnectionId(reinterpret_cast("\x11\x12\x13\x14"), 4), 0, + std::move(payload), sizeof(raw), true, false, true); + REQUIRE(packet != nullptr); + CHECK(packet->type() == QUICPacketType::HANDSHAKE); + CHECK((packet->destination_cid() == QUICConnectionId(reinterpret_cast("\x01\x02\x03\x04"), 4))); + CHECK(memcmp(packet->payload(), raw, sizeof(raw)) != 0); + CHECK(packet->packet_number() <= 0xFFFFFBFF); + CHECK(packet->version() == 0x11223344); +} + +TEST_CASE("QUICPacketFactory_Create_StatelessResetPacket", "[quic]") +{ + MockQUICPacketProtectionKeyInfo pp_key_info; + QUICPacketFactory factory(pp_key_info); + QUICStatelessResetToken token({reinterpret_cast("\x30\x39"), 2}, 67890); + + QUICPacketUPtr packet = + factory.create_stateless_reset_packet(QUICConnectionId(reinterpret_cast("\x01\x02\x03\x04"), 4), token); + + REQUIRE(packet != nullptr); + CHECK(packet->type() == QUICPacketType::STATELESS_RESET); + CHECK((packet->destination_cid() == QUICConnectionId(reinterpret_cast("\x01\x02\x03\x04"), 4))); +} diff --git a/iocore/net/quic/test/test_QUICPacketHeaderProtector.cc b/iocore/net/quic/test/test_QUICPacketHeaderProtector.cc new file mode 100644 index 00000000000..dc754a4a2f6 --- /dev/null +++ b/iocore/net/quic/test/test_QUICPacketHeaderProtector.cc @@ -0,0 +1,210 @@ +/** @file + * + * A brief file description + * + * @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 "catch.hpp" + +#include "QUICPacketProtectionKeyInfo.h" +#include "QUICPacketHeaderProtector.h" +#include "QUICTLS.h" + +struct PollCont; +#include "P_UDPConnection.h" +#include "P_UnixNet.h" +#include "P_UnixNetVConnection.h" + +// depends on size of cert +static constexpr uint32_t MAX_HANDSHAKE_MSG_LEN = 8192; + +#include "./server_cert.h" + +TEST_CASE("QUICPacketHeaderProtector") +{ + // Client + SSL_CTX *client_ssl_ctx = SSL_CTX_new(TLS_method()); + SSL_CTX_set_min_proto_version(client_ssl_ctx, TLS1_3_VERSION); + SSL_CTX_set_max_proto_version(client_ssl_ctx, TLS1_3_VERSION); +#ifndef OPENSSL_IS_BORINGSSL + SSL_CTX_clear_options(client_ssl_ctx, SSL_OP_ENABLE_MIDDLEBOX_COMPAT); +#endif +#ifdef SSL_MODE_QUIC_HACK + SSL_CTX_set_mode(client_ssl_ctx, SSL_MODE_QUIC_HACK); +#endif + + // Server + SSL_CTX *server_ssl_ctx = SSL_CTX_new(TLS_method()); + SSL_CTX_set_min_proto_version(server_ssl_ctx, TLS1_3_VERSION); + SSL_CTX_set_max_proto_version(server_ssl_ctx, TLS1_3_VERSION); +#ifndef OPENSSL_IS_BORINGSSL + SSL_CTX_clear_options(server_ssl_ctx, SSL_OP_ENABLE_MIDDLEBOX_COMPAT); +#endif +#ifdef SSL_MODE_QUIC_HACK + SSL_CTX_set_mode(server_ssl_ctx, SSL_MODE_QUIC_HACK); +#endif + BIO *crt_bio(BIO_new_mem_buf(server_crt, sizeof(server_crt))); + X509 *x509 = PEM_read_bio_X509(crt_bio, nullptr, nullptr, nullptr); + SSL_CTX_use_certificate(server_ssl_ctx, x509); + BIO *key_bio(BIO_new_mem_buf(server_key, sizeof(server_key))); + EVP_PKEY *pkey = PEM_read_bio_PrivateKey(key_bio, nullptr, nullptr, nullptr); + SSL_CTX_use_PrivateKey(server_ssl_ctx, pkey); + + SECTION("Long header", "[quic]") + { + uint8_t original[] = { + 0xC3, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + }; + uint8_t tmp[64]; + memcpy(tmp, original, sizeof(tmp)); + + QUICPacketProtectionKeyInfo pp_key_info_client; + QUICPacketProtectionKeyInfo pp_key_info_server; + NetVCOptions netvc_options; + QUICHandshakeProtocol *client = new QUICTLS(pp_key_info_client, client_ssl_ctx, NET_VCONNECTION_OUT, netvc_options); + QUICHandshakeProtocol *server = new QUICTLS(pp_key_info_server, server_ssl_ctx, NET_VCONNECTION_IN, netvc_options); + + CHECK(client->initialize_key_materials({reinterpret_cast("\x83\x94\xc8\xf0\x3e\x51\x57\x00"), 8})); + CHECK(server->initialize_key_materials({reinterpret_cast("\x83\x94\xc8\xf0\x3e\x51\x57\x00"), 8})); + + QUICPacketHeaderProtector client_ph_protector(pp_key_info_client); + QUICPacketHeaderProtector server_ph_protector(pp_key_info_server); + + // ## Client -> Server + client_ph_protector.protect(tmp, sizeof(tmp), 18); + CHECK(memcmp(original, tmp, sizeof(original)) != 0); + server_ph_protector.unprotect(tmp, sizeof(tmp)); + CHECK(memcmp(original, tmp, sizeof(original)) == 0); + // ## Server -> Client + server_ph_protector.protect(tmp, sizeof(tmp), 18); + CHECK(memcmp(original, tmp, sizeof(original)) != 0); + client_ph_protector.unprotect(tmp, sizeof(tmp)); + CHECK(memcmp(original, tmp, sizeof(original)) == 0); + } + + SECTION("Short header", "[quic]") + { + uint8_t original[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + }; + uint8_t tmp[48]; + memcpy(tmp, original, sizeof(tmp)); + + QUICPacketProtectionKeyInfo pp_key_info_client; + QUICPacketProtectionKeyInfo pp_key_info_server; + NetVCOptions netvc_options; + QUICHandshakeProtocol *client = new QUICTLS(pp_key_info_client, client_ssl_ctx, NET_VCONNECTION_OUT, netvc_options); + QUICHandshakeProtocol *server = new QUICTLS(pp_key_info_server, server_ssl_ctx, NET_VCONNECTION_IN, netvc_options); + + CHECK(client->initialize_key_materials({reinterpret_cast("\x83\x94\xc8\xf0\x3e\x51\x57\x00"), 8})); + CHECK(server->initialize_key_materials({reinterpret_cast("\x83\x94\xc8\xf0\x3e\x51\x57\x00"), 8})); + + QUICPacketHeaderProtector client_ph_protector(pp_key_info_client); + QUICPacketHeaderProtector server_ph_protector(pp_key_info_server); + + // Handshake + // CH + QUICHandshakeMsgs msg1; + uint8_t msg1_buf[MAX_HANDSHAKE_MSG_LEN] = {0}; + msg1.buf = msg1_buf; + msg1.max_buf_len = MAX_HANDSHAKE_MSG_LEN; + + REQUIRE(client->handshake(&msg1, nullptr) == 1); + + // SH, EE, CERT, CV, FIN + QUICHandshakeMsgs msg2; + uint8_t msg2_buf[MAX_HANDSHAKE_MSG_LEN] = {0}; + msg2.buf = msg2_buf; + msg2.max_buf_len = MAX_HANDSHAKE_MSG_LEN; + + REQUIRE(server->handshake(&msg2, &msg1) == 1); + + // FIN + QUICHandshakeMsgs msg3; + uint8_t msg3_buf[MAX_HANDSHAKE_MSG_LEN] = {0}; + msg3.buf = msg3_buf; + msg3.max_buf_len = MAX_HANDSHAKE_MSG_LEN; + +#ifdef SSL_MODE_QUIC_HACK + // -- Hacks for OpenSSL with SSL_MODE_QUIC_HACK -- + // SH + QUICHandshakeMsgs msg2_1; + uint8_t msg2_1_buf[MAX_HANDSHAKE_MSG_LEN] = {0}; + msg2_1.buf = msg2_1_buf; + msg2_1.max_buf_len = MAX_HANDSHAKE_MSG_LEN; + + memcpy(msg2_1.buf, msg2.buf, msg2.offsets[1]); + msg2_1.offsets[0] = 0; + msg2_1.offsets[1] = msg2.offsets[1]; + msg2_1.offsets[2] = msg2.offsets[1]; + msg2_1.offsets[3] = msg2.offsets[1]; + msg2_1.offsets[4] = msg2.offsets[1]; + + // EE - FIN + QUICHandshakeMsgs msg2_2; + uint8_t msg2_2_buf[MAX_HANDSHAKE_MSG_LEN] = {0}; + msg2_2.buf = msg2_2_buf; + msg2_2.max_buf_len = MAX_HANDSHAKE_MSG_LEN; + + size_t len = msg2.offsets[3] - msg2.offsets[2]; + memcpy(msg2_2.buf, msg2.buf + msg2.offsets[1], len); + msg2_2.offsets[0] = 0; + msg2_2.offsets[1] = 0; + msg2_2.offsets[2] = 0; + msg2_2.offsets[3] = len; + msg2_2.offsets[4] = len; + + REQUIRE(client->handshake(&msg3, &msg2_1) == 1); + REQUIRE(client->handshake(&msg3, &msg2_2) == 1); +#else + REQUIRE(client->handshake(&msg3, &msg2) == 1); +#endif + + // NS + QUICHandshakeMsgs msg4; + uint8_t msg4_buf[MAX_HANDSHAKE_MSG_LEN] = {0}; + msg4.buf = msg4_buf; + msg4.max_buf_len = MAX_HANDSHAKE_MSG_LEN; + + REQUIRE(server->handshake(&msg4, &msg3) == 1); + + QUICHandshakeMsgs msg5; + uint8_t msg5_buf[MAX_HANDSHAKE_MSG_LEN] = {0}; + msg5.buf = msg5_buf; + msg5.max_buf_len = MAX_HANDSHAKE_MSG_LEN; + REQUIRE(client->handshake(&msg5, &msg4) == 1); + + // ## Client -> Server + client_ph_protector.protect(tmp, sizeof(tmp), 18); + CHECK(memcmp(original, tmp, sizeof(original)) != 0); + server_ph_protector.unprotect(tmp, sizeof(tmp)); + CHECK(memcmp(original, tmp, sizeof(original)) == 0); + // ## Server -> Client + server_ph_protector.protect(tmp, sizeof(tmp), 18); + CHECK(memcmp(original, tmp, sizeof(original)) != 0); + client_ph_protector.unprotect(tmp, sizeof(tmp)); + CHECK(memcmp(original, tmp, sizeof(original)) == 0); + } +} diff --git a/iocore/net/quic/test/test_QUICStream.cc b/iocore/net/quic/test/test_QUICStream.cc new file mode 100644 index 00000000000..aefc4883033 --- /dev/null +++ b/iocore/net/quic/test/test_QUICStream.cc @@ -0,0 +1,841 @@ +/** @file + * + * A brief file description + * + * @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 "catch.hpp" + +#include "quic/QUICBidirectionalStream.h" +#include "quic/QUICUnidirectionalStream.h" +#include "quic/Mock.h" + +TEST_CASE("QUICBidiStream", "[quic]") +{ + // Test Data + uint8_t payload[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}; + uint32_t stream_id = 0x03; + Ptr block = make_ptr(new_IOBufferBlock()); + block->alloc(); + memcpy(block->start(), payload, sizeof(payload)); + block->fill(sizeof(payload)); + + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + + Ptr new_block = make_ptr(block->clone()); + new_block->_end = new_block->_start + 2; + QUICStreamFrame frame_1(new_block, stream_id, 0); + block->consume(2); + + new_block = block->clone(); + new_block->_end = new_block->_start + 2; + QUICStreamFrame frame_2(new_block, stream_id, 2); + block->consume(2); + + new_block = block->clone(); + new_block->_end = new_block->_start + 2; + QUICStreamFrame frame_3(new_block, stream_id, 4); + block->consume(2); + + new_block = block->clone(); + new_block->_end = new_block->_start + 2; + QUICStreamFrame frame_4(new_block, stream_id, 6); + block->consume(2); + + new_block = block->clone(); + new_block->_end = new_block->_start + 2; + QUICStreamFrame frame_5(new_block, stream_id, 8); + block->consume(2); + + new_block = block->clone(); + new_block->_end = new_block->_start + 2; + QUICStreamFrame frame_6(new_block, stream_id, 10); + block->consume(2); + + new_block = block->clone(); + new_block->_end = new_block->_start + 2; + QUICStreamFrame frame_7(new_block, stream_id, 12); + block->consume(2); + + new_block = block->clone(); + new_block->_end = new_block->_start + 2; + QUICStreamFrame frame_8(new_block, stream_id, 14); + block->consume(2); + + SECTION("QUICStream_assembling_byte_stream_1") + { + MIOBuffer *read_buffer = new_MIOBuffer(BUFFER_SIZE_INDEX_4K); + IOBufferReader *reader = read_buffer->alloc_reader(); + + QUICRTTMeasure rtt_provider; + MockQUICConnectionInfoProvider cinfo_provider; + std::unique_ptr stream( + new QUICBidirectionalStream(&rtt_provider, &cinfo_provider, stream_id, 1024, 1024)); + stream->do_io_read(nullptr, INT64_MAX, read_buffer); + + stream->recv(frame_1); + stream->recv(frame_2); + stream->recv(frame_3); + stream->recv(frame_4); + stream->recv(frame_5); + stream->recv(frame_6); + stream->recv(frame_7); + stream->recv(frame_8); + + uint8_t buf[32]; + int64_t len = reader->read_avail(); + reader->read(buf, len); + + CHECK(len == 16); + CHECK(memcmp(buf, payload, len) == 0); + } + + SECTION("QUICStream_assembling_byte_stream_2") + { + MIOBuffer *read_buffer = new_MIOBuffer(BUFFER_SIZE_INDEX_4K); + IOBufferReader *reader = read_buffer->alloc_reader(); + + QUICRTTMeasure rtt_provider; + MockQUICConnectionInfoProvider cinfo_provider; + std::unique_ptr stream( + new QUICBidirectionalStream(&rtt_provider, &cinfo_provider, stream_id, UINT64_MAX, UINT64_MAX)); + stream->do_io_read(nullptr, INT64_MAX, read_buffer); + + stream->recv(frame_8); + stream->recv(frame_7); + stream->recv(frame_6); + stream->recv(frame_5); + stream->recv(frame_4); + stream->recv(frame_3); + stream->recv(frame_2); + stream->recv(frame_1); + + uint8_t buf[32]; + int64_t len = reader->read_avail(); + reader->read(buf, len); + + CHECK(len == 16); + CHECK(memcmp(buf, payload, len) == 0); + } + + SECTION("QUICStream_assembling_byte_stream_3") + { + MIOBuffer *read_buffer = new_MIOBuffer(BUFFER_SIZE_INDEX_4K); + IOBufferReader *reader = read_buffer->alloc_reader(); + + QUICRTTMeasure rtt_provider; + MockQUICConnectionInfoProvider cinfo_provider; + std::unique_ptr stream( + new QUICBidirectionalStream(&rtt_provider, &cinfo_provider, stream_id, UINT64_MAX, UINT64_MAX)); + stream->do_io_read(nullptr, INT64_MAX, read_buffer); + + stream->recv(frame_8); + stream->recv(frame_7); + stream->recv(frame_6); + stream->recv(frame_7); // duplicated frame + stream->recv(frame_5); + stream->recv(frame_3); + stream->recv(frame_1); + stream->recv(frame_2); + stream->recv(frame_4); + stream->recv(frame_5); // duplicated frame + + uint8_t buf[32]; + int64_t len = reader->read_avail(); + reader->read(buf, len); + + CHECK(len == 16); + CHECK(memcmp(buf, payload, len) == 0); + } + + SECTION("QUICStream_flow_control_local", "[quic]") + { + std::unique_ptr error = nullptr; + + MIOBuffer *read_buffer = new_MIOBuffer(BUFFER_SIZE_INDEX_4K); + + QUICRTTMeasure rtt_provider; + MockQUICConnectionInfoProvider cinfo_provider; + std::unique_ptr stream( + new QUICBidirectionalStream(&rtt_provider, &cinfo_provider, stream_id, 4096, 4096)); + stream->do_io_read(nullptr, INT64_MAX, read_buffer); + + Ptr block = make_ptr(new_IOBufferBlock()); + block->alloc(); + block->fill(1024); + + // Start with 1024 but not 0 so received frames won't be processed + error = stream->recv(*std::make_shared(block, stream_id, 1024)); + CHECK(error == nullptr); + // duplicate + error = stream->recv(*std::make_shared(block, stream_id, 1024)); + CHECK(error == nullptr); + error = stream->recv(*std::make_shared(block, stream_id, 3072)); + CHECK(error == nullptr); + // delay + error = stream->recv(*std::make_shared(block, stream_id, 2048)); + CHECK(error == nullptr); + // all frames should be processed + error = stream->recv(*std::make_shared(block, stream_id, 0)); + CHECK(error == nullptr); + // start again without the first block + error = stream->recv(*std::make_shared(block, stream_id, 5120)); + CHECK(error == nullptr); + // this should exceed the limit + error = stream->recv(*std::make_shared(block, stream_id, 8192)); + CHECK(error->cls == QUICErrorClass::TRANSPORT); + CHECK(error->code == static_cast(QUICTransErrorCode::FLOW_CONTROL_ERROR)); + } + + SECTION("QUICStream_flow_control_remote", "[quic]") + { + std::unique_ptr error = nullptr; + + MIOBuffer *read_buffer = new_MIOBuffer(BUFFER_SIZE_INDEX_4K); + MIOBuffer *write_buffer = new_MIOBuffer(BUFFER_SIZE_INDEX_4K); + IOBufferReader *write_buffer_reader = write_buffer->alloc_reader(); + + QUICRTTMeasure rtt_provider; + MockQUICConnectionInfoProvider cinfo_provider; + std::unique_ptr stream( + new QUICBidirectionalStream(&rtt_provider, &cinfo_provider, stream_id, 4096, 4096)); + SCOPED_MUTEX_LOCK(lock, stream->mutex, this_ethread()); + + MockContinuation mock_cont(stream->mutex); + stream->do_io_read(nullptr, INT64_MAX, read_buffer); + stream->do_io_write(&mock_cont, INT64_MAX, write_buffer_reader); + + QUICEncryptionLevel level = QUICEncryptionLevel::ONE_RTT; + + const char data[1024] = {0}; + QUICFrame *frame = nullptr; + + write_buffer->write(data, 1024); + stream->handleEvent(VC_EVENT_WRITE_READY, nullptr); + CHECK(stream->will_generate_frame(level, 0) == true); + frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0); + CHECK(frame->type() == QUICFrameType::STREAM); + CHECK(stream->will_generate_frame(level, 0) == false); + + write_buffer->write(data, 1024); + stream->handleEvent(VC_EVENT_WRITE_READY, nullptr); + CHECK(stream->will_generate_frame(level, 0) == true); + frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0); + CHECK(frame->type() == QUICFrameType::STREAM); + CHECK(stream->will_generate_frame(level, 0) == false); + + write_buffer->write(data, 1024); + stream->handleEvent(VC_EVENT_WRITE_READY, nullptr); + CHECK(stream->will_generate_frame(level, 0) == true); + frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0); + CHECK(frame->type() == QUICFrameType::STREAM); + CHECK(stream->will_generate_frame(level, 0) == false); + + write_buffer->write(data, 1024); + stream->handleEvent(VC_EVENT_WRITE_READY, nullptr); + CHECK(stream->will_generate_frame(level, 0) == true); + frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0); + CHECK(frame->type() == QUICFrameType::STREAM); + CHECK(stream->will_generate_frame(level, 0) == false); + + // This should not send a frame because of flow control + write_buffer->write(data, 1024); + stream->handleEvent(VC_EVENT_WRITE_READY, nullptr); + CHECK(stream->will_generate_frame(level, 0) == true); + frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0); + CHECK(frame); + CHECK(frame->type() == QUICFrameType::STREAM_DATA_BLOCKED); + CHECK(stream->will_generate_frame(level, 0) == true); + + // Update window + stream->recv(*std::make_shared(stream_id, 5120)); + + // This should send a frame + stream->handleEvent(VC_EVENT_WRITE_READY, nullptr); + CHECK(stream->will_generate_frame(level, 0) == true); + frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0); + CHECK(frame->type() == QUICFrameType::STREAM); + CHECK(stream->will_generate_frame(level, 0) == false); + + // Update window + stream->recv(*std::make_shared(stream_id, 5632)); + + // This should send a frame + write_buffer->write(data, 1024); + stream->handleEvent(VC_EVENT_WRITE_READY, nullptr); + CHECK(stream->will_generate_frame(level, 0) == true); + frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0); + CHECK(frame->type() == QUICFrameType::STREAM); + CHECK(stream->will_generate_frame(level, 0) == true); + + stream->handleEvent(VC_EVENT_WRITE_READY, nullptr); + CHECK(stream->will_generate_frame(level, 0) == true); + frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0); + CHECK(frame->type() == QUICFrameType::STREAM_DATA_BLOCKED); + + // Update window + stream->recv(*std::make_shared(stream_id, 6144)); + + stream->handleEvent(VC_EVENT_WRITE_READY, nullptr); + CHECK(stream->will_generate_frame(level, 0) == true); + frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0); + CHECK(frame->type() == QUICFrameType::STREAM); + CHECK(stream->will_generate_frame(level, 0) == false); + } + + /* + * This test does not pass now + */ + SECTION("Retransmit STREAM frame") + { + MIOBuffer *write_buffer = new_MIOBuffer(BUFFER_SIZE_INDEX_8K); + IOBufferReader *write_buffer_reader = write_buffer->alloc_reader(); + + QUICRTTMeasure rtt_provider; + MockQUICConnectionInfoProvider cinfo_provider; + std::unique_ptr stream( + new QUICBidirectionalStream(&rtt_provider, &cinfo_provider, stream_id, UINT64_MAX, UINT64_MAX)); + SCOPED_MUTEX_LOCK(lock, stream->mutex, this_ethread()); + + MockContinuation mock_cont(stream->mutex); + stream->do_io_write(&mock_cont, INT64_MAX, write_buffer_reader); + + QUICEncryptionLevel level = QUICEncryptionLevel::ONE_RTT; + const char data1[] = "this is a test data"; + const char data2[] = "THIS IS ANOTHER TEST DATA"; + QUICFrame *frame = nullptr; + QUICStreamFrame *frame1 = nullptr; + QUICStreamFrame *frame2 = nullptr; + uint8_t frame_buf2[QUICFrame::MAX_INSTANCE_SIZE]; + + // Write data1 + write_buffer->write(data1, sizeof(data1)); + stream->handleEvent(VC_EVENT_WRITE_READY, nullptr); + // Generate STREAM frame + frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0); + frame1 = static_cast(frame); + CHECK(frame->type() == QUICFrameType::STREAM); + CHECK(stream->generate_frame(frame_buf, level, 4096, 4096, 0) == nullptr); + CHECK(stream->will_generate_frame(level, 0) == false); + stream->on_frame_lost(frame->id()); + CHECK(stream->will_generate_frame(level, 0) == true); + + // Write data2 + write_buffer->write(data2, sizeof(data2)); + stream->handleEvent(VC_EVENT_WRITE_READY, nullptr); + // Lost the frame + stream->on_frame_lost(frame->id()); + // Regenerate a frame + frame = stream->generate_frame(frame_buf2, level, 4096, 4096, 0); + // Lost data should be resent first + frame2 = static_cast(frame); + CHECK(frame->type() == QUICFrameType::STREAM); + CHECK(frame1->offset() == frame2->offset()); + CHECK(frame1->data_length() == frame2->data_length()); + CHECK(memcmp(frame1->data()->buf(), frame2->data()->buf(), frame1->data_length()) == 0); + } + + SECTION("Retransmit RESET_STREAM frame") + { + MIOBuffer *write_buffer = new_MIOBuffer(BUFFER_SIZE_INDEX_8K); + IOBufferReader *write_buffer_reader = write_buffer->alloc_reader(); + + QUICRTTMeasure rtt_provider; + MockQUICConnectionInfoProvider cinfo_provider; + std::unique_ptr stream( + new QUICBidirectionalStream(&rtt_provider, &cinfo_provider, stream_id, UINT64_MAX, UINT64_MAX)); + SCOPED_MUTEX_LOCK(lock, stream->mutex, this_ethread()); + + MockContinuation mock_cont(stream->mutex); + stream->do_io_write(&mock_cont, INT64_MAX, write_buffer_reader); + + QUICEncryptionLevel level = QUICEncryptionLevel::ONE_RTT; + QUICFrame *frame = nullptr; + + stream->reset(QUICStreamErrorUPtr(new QUICStreamError(stream.get(), QUIC_APP_ERROR_CODE_STOPPING))); + frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0); + REQUIRE(frame); + CHECK(frame->type() == QUICFrameType::RESET_STREAM); + // Don't send it again untill it is considers as lost + CHECK(stream->generate_frame(frame_buf, level, 4096, 4096, 0) == nullptr); + // Loss the frame + stream->on_frame_lost(frame->id()); + // After the loss the frame should be regenerated + frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0); + REQUIRE(frame); + CHECK(frame->type() == QUICFrameType::RESET_STREAM); + } + + SECTION("Retransmit STOP_SENDING frame") + { + MIOBuffer *write_buffer = new_MIOBuffer(BUFFER_SIZE_INDEX_8K); + IOBufferReader *write_buffer_reader = write_buffer->alloc_reader(); + + QUICRTTMeasure rtt_provider; + MockQUICConnectionInfoProvider cinfo_provider; + std::unique_ptr stream( + new QUICBidirectionalStream(&rtt_provider, &cinfo_provider, stream_id, UINT64_MAX, UINT64_MAX)); + SCOPED_MUTEX_LOCK(lock, stream->mutex, this_ethread()); + + MockContinuation mock_cont(stream->mutex); + stream->do_io_write(&mock_cont, INT64_MAX, write_buffer_reader); + + QUICEncryptionLevel level = QUICEncryptionLevel::ONE_RTT; + QUICFrame *frame = nullptr; + + stream->stop_sending(QUICStreamErrorUPtr(new QUICStreamError(stream.get(), QUIC_APP_ERROR_CODE_STOPPING))); + frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0); + REQUIRE(frame); + CHECK(frame->type() == QUICFrameType::STOP_SENDING); + // Don't send it again untill it is considers as lost + CHECK(stream->generate_frame(frame_buf, level, 4096, 4096, 0) == nullptr); + // Loss the frame + stream->on_frame_lost(frame->id()); + // After the loss the frame should be regenerated + frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0); + REQUIRE(frame); + CHECK(frame->type() == QUICFrameType::STOP_SENDING); + } +} + +TEST_CASE("QUIC receive only stream", "[quic]") +{ + // Test Data + uint8_t payload[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}; + uint32_t stream_id = 0x03; + Ptr block = make_ptr(new_IOBufferBlock()); + block->alloc(); + memcpy(block->start(), payload, sizeof(payload)); + block->fill(sizeof(payload)); + + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + + Ptr new_block = make_ptr(block->clone()); + new_block->_end = new_block->_start + 2; + QUICStreamFrame frame_1(new_block, stream_id, 0); + block->consume(2); + + new_block = block->clone(); + new_block->_end = new_block->_start + 2; + QUICStreamFrame frame_2(new_block, stream_id, 2); + block->consume(2); + + new_block = block->clone(); + new_block->_end = new_block->_start + 2; + QUICStreamFrame frame_3(new_block, stream_id, 4); + block->consume(2); + + new_block = block->clone(); + new_block->_end = new_block->_start + 2; + QUICStreamFrame frame_4(new_block, stream_id, 6); + block->consume(2); + + new_block = block->clone(); + new_block->_end = new_block->_start + 2; + QUICStreamFrame frame_5(new_block, stream_id, 8); + block->consume(2); + + new_block = block->clone(); + new_block->_end = new_block->_start + 2; + QUICStreamFrame frame_6(new_block, stream_id, 10); + block->consume(2); + + new_block = block->clone(); + new_block->_end = new_block->_start + 2; + QUICStreamFrame frame_7(new_block, stream_id, 12); + block->consume(2); + + new_block = block->clone(); + new_block->_end = new_block->_start + 2; + QUICStreamFrame frame_8(new_block, stream_id, 14); + block->consume(2); + + SECTION("QUICStream_assembling_byte_stream_1") + { + MIOBuffer *read_buffer = new_MIOBuffer(BUFFER_SIZE_INDEX_4K); + IOBufferReader *reader = read_buffer->alloc_reader(); + + QUICRTTMeasure rtt_provider; + MockQUICConnectionInfoProvider cinfo_provider; + std::unique_ptr stream(new QUICReceiveStream(&rtt_provider, &cinfo_provider, stream_id, 1024)); + stream->do_io_read(nullptr, INT64_MAX, read_buffer); + + stream->recv(frame_1); + stream->recv(frame_2); + stream->recv(frame_3); + stream->recv(frame_4); + stream->recv(frame_5); + stream->recv(frame_6); + stream->recv(frame_7); + stream->recv(frame_8); + + uint8_t buf[32]; + int64_t len = reader->read_avail(); + reader->read(buf, len); + + CHECK(len == 16); + CHECK(memcmp(buf, payload, len) == 0); + } + + SECTION("QUICStream_assembling_byte_stream_2") + { + MIOBuffer *read_buffer = new_MIOBuffer(BUFFER_SIZE_INDEX_4K); + IOBufferReader *reader = read_buffer->alloc_reader(); + + QUICRTTMeasure rtt_provider; + MockQUICConnectionInfoProvider cinfo_provider; + std::unique_ptr stream(new QUICReceiveStream(&rtt_provider, &cinfo_provider, stream_id, UINT64_MAX)); + stream->do_io_read(nullptr, INT64_MAX, read_buffer); + + stream->recv(frame_8); + stream->recv(frame_7); + stream->recv(frame_6); + stream->recv(frame_5); + stream->recv(frame_4); + stream->recv(frame_3); + stream->recv(frame_2); + stream->recv(frame_1); + + uint8_t buf[32]; + int64_t len = reader->read_avail(); + reader->read(buf, len); + + CHECK(len == 16); + CHECK(memcmp(buf, payload, len) == 0); + } + + SECTION("QUICStream_assembling_byte_stream_3") + { + MIOBuffer *read_buffer = new_MIOBuffer(BUFFER_SIZE_INDEX_4K); + IOBufferReader *reader = read_buffer->alloc_reader(); + + QUICRTTMeasure rtt_provider; + MockQUICConnectionInfoProvider cinfo_provider; + std::unique_ptr stream(new QUICReceiveStream(&rtt_provider, &cinfo_provider, stream_id, UINT64_MAX)); + stream->do_io_read(nullptr, INT64_MAX, read_buffer); + + stream->recv(frame_8); + stream->recv(frame_7); + stream->recv(frame_6); + stream->recv(frame_7); // duplicated frame + stream->recv(frame_5); + stream->recv(frame_3); + stream->recv(frame_1); + stream->recv(frame_2); + stream->recv(frame_4); + stream->recv(frame_5); // duplicated frame + + uint8_t buf[32]; + int64_t len = reader->read_avail(); + reader->read(buf, len); + + CHECK(len == 16); + CHECK(memcmp(buf, payload, len) == 0); + } + + SECTION("QUICStream_flow_control_local", "[quic]") + { + std::unique_ptr error = nullptr; + + MIOBuffer *read_buffer = new_MIOBuffer(BUFFER_SIZE_INDEX_4K); + + QUICRTTMeasure rtt_provider; + MockQUICConnectionInfoProvider cinfo_provider; + std::unique_ptr stream(new QUICReceiveStream(&rtt_provider, &cinfo_provider, stream_id, 4096)); + stream->do_io_read(nullptr, INT64_MAX, read_buffer); + + Ptr block = make_ptr(new_IOBufferBlock()); + block->alloc(); + block->fill(1024); + + // Start with 1024 but not 0 so received frames won't be processed + error = stream->recv(*std::make_shared(block, stream_id, 1024)); + CHECK(error == nullptr); + // duplicate + error = stream->recv(*std::make_shared(block, stream_id, 1024)); + CHECK(error == nullptr); + error = stream->recv(*std::make_shared(block, stream_id, 3072)); + CHECK(error == nullptr); + // delay + error = stream->recv(*std::make_shared(block, stream_id, 2048)); + CHECK(error == nullptr); + // all frames should be processed + error = stream->recv(*std::make_shared(block, stream_id, 0)); + CHECK(error == nullptr); + // start again without the first block + error = stream->recv(*std::make_shared(block, stream_id, 5120)); + CHECK(error == nullptr); + // this should exceed the limit + error = stream->recv(*std::make_shared(block, stream_id, 8192)); + CHECK(error->cls == QUICErrorClass::TRANSPORT); + CHECK(error->code == static_cast(QUICTransErrorCode::FLOW_CONTROL_ERROR)); + } + + SECTION("Retransmit STOP_SENDING frame") + { + QUICRTTMeasure rtt_provider; + MockQUICConnectionInfoProvider cinfo_provider; + std::unique_ptr stream(new QUICReceiveStream(&rtt_provider, &cinfo_provider, stream_id, UINT64_MAX)); + SCOPED_MUTEX_LOCK(lock, stream->mutex, this_ethread()); + + QUICEncryptionLevel level = QUICEncryptionLevel::ONE_RTT; + QUICFrame *frame = nullptr; + + stream->stop_sending(QUICStreamErrorUPtr(new QUICStreamError(stream.get(), QUIC_APP_ERROR_CODE_STOPPING))); + frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0); + REQUIRE(frame); + CHECK(frame->type() == QUICFrameType::STOP_SENDING); + // Don't send it again untill it is considers as lost + CHECK(stream->generate_frame(frame_buf, level, 4096, 4096, 0) == nullptr); + // Loss the frame + stream->on_frame_lost(frame->id()); + // After the loss the frame should be regenerated + frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0); + REQUIRE(frame); + CHECK(frame->type() == QUICFrameType::STOP_SENDING); + } +} + +TEST_CASE("QUIC send only stream", "[quic]") +{ + // Test Data + uint8_t payload[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}; + uint32_t stream_id = 0x03; + Ptr block = make_ptr(new_IOBufferBlock()); + block->alloc(); + memcpy(block->start(), payload, sizeof(payload)); + block->fill(sizeof(payload)); + + uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + + Ptr new_block = make_ptr(block->clone()); + new_block->_end = new_block->_start + 2; + QUICStreamFrame frame_1(new_block, stream_id, 0); + block->consume(2); + + new_block = block->clone(); + new_block->_end = new_block->_start + 2; + QUICStreamFrame frame_2(new_block, stream_id, 2); + block->consume(2); + + new_block = block->clone(); + new_block->_end = new_block->_start + 2; + QUICStreamFrame frame_3(new_block, stream_id, 4); + block->consume(2); + + new_block = block->clone(); + new_block->_end = new_block->_start + 2; + QUICStreamFrame frame_4(new_block, stream_id, 6); + block->consume(2); + + new_block = block->clone(); + new_block->_end = new_block->_start + 2; + QUICStreamFrame frame_5(new_block, stream_id, 8); + block->consume(2); + + new_block = block->clone(); + new_block->_end = new_block->_start + 2; + QUICStreamFrame frame_6(new_block, stream_id, 10); + block->consume(2); + + new_block = block->clone(); + new_block->_end = new_block->_start + 2; + QUICStreamFrame frame_7(new_block, stream_id, 12); + block->consume(2); + + new_block = block->clone(); + new_block->_end = new_block->_start + 2; + QUICStreamFrame frame_8(new_block, stream_id, 14); + block->consume(2); + + SECTION("QUICStream_flow_control_remote", "[quic]") + { + std::unique_ptr error = nullptr; + + MIOBuffer *write_buffer = new_MIOBuffer(BUFFER_SIZE_INDEX_4K); + IOBufferReader *write_buffer_reader = write_buffer->alloc_reader(); + + QUICRTTMeasure rtt_provider; + MockQUICConnectionInfoProvider cinfo_provider; + std::unique_ptr stream(new QUICSendStream(&cinfo_provider, stream_id, 4096)); + SCOPED_MUTEX_LOCK(lock, stream->mutex, this_ethread()); + + MockContinuation mock_cont(stream->mutex); + stream->do_io_write(&mock_cont, INT64_MAX, write_buffer_reader); + + QUICEncryptionLevel level = QUICEncryptionLevel::ONE_RTT; + + const char data[1024] = {0}; + QUICFrame *frame = nullptr; + + write_buffer->write(data, 1024); + stream->handleEvent(VC_EVENT_WRITE_READY, nullptr); + CHECK(stream->will_generate_frame(level, 0) == true); + frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0); + CHECK(frame->type() == QUICFrameType::STREAM); + CHECK(stream->will_generate_frame(level, 0) == false); + + write_buffer->write(data, 1024); + stream->handleEvent(VC_EVENT_WRITE_READY, nullptr); + CHECK(stream->will_generate_frame(level, 0) == true); + frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0); + CHECK(frame->type() == QUICFrameType::STREAM); + CHECK(stream->will_generate_frame(level, 0) == false); + + write_buffer->write(data, 1024); + stream->handleEvent(VC_EVENT_WRITE_READY, nullptr); + CHECK(stream->will_generate_frame(level, 0) == true); + frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0); + CHECK(frame->type() == QUICFrameType::STREAM); + CHECK(stream->will_generate_frame(level, 0) == false); + + write_buffer->write(data, 1024); + stream->handleEvent(VC_EVENT_WRITE_READY, nullptr); + CHECK(stream->will_generate_frame(level, 0) == true); + frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0); + CHECK(frame->type() == QUICFrameType::STREAM); + CHECK(stream->will_generate_frame(level, 0) == false); + + // This should not send a frame because of flow control + write_buffer->write(data, 1024); + stream->handleEvent(VC_EVENT_WRITE_READY, nullptr); + CHECK(stream->will_generate_frame(level, 0) == true); + frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0); + CHECK(frame); + CHECK(frame->type() == QUICFrameType::STREAM_DATA_BLOCKED); + CHECK(stream->will_generate_frame(level, 0) == true); + + // Update window + stream->recv(*std::make_shared(stream_id, 5120)); + + // This should send a frame + stream->handleEvent(VC_EVENT_WRITE_READY, nullptr); + CHECK(stream->will_generate_frame(level, 0) == true); + frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0); + CHECK(frame->type() == QUICFrameType::STREAM); + CHECK(stream->will_generate_frame(level, 0) == false); + + // Update window + stream->recv(*std::make_shared(stream_id, 5632)); + + // This should send a frame + write_buffer->write(data, 1024); + stream->handleEvent(VC_EVENT_WRITE_READY, nullptr); + CHECK(stream->will_generate_frame(level, 0) == true); + frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0); + CHECK(frame->type() == QUICFrameType::STREAM); + CHECK(stream->will_generate_frame(level, 0) == true); + + stream->handleEvent(VC_EVENT_WRITE_READY, nullptr); + CHECK(stream->will_generate_frame(level, 0) == true); + frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0); + CHECK(frame->type() == QUICFrameType::STREAM_DATA_BLOCKED); + + // Update window + stream->recv(*std::make_shared(stream_id, 6144)); + + stream->handleEvent(VC_EVENT_WRITE_READY, nullptr); + CHECK(stream->will_generate_frame(level, 0) == true); + frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0); + CHECK(frame->type() == QUICFrameType::STREAM); + CHECK(stream->will_generate_frame(level, 0) == false); + } + + /* + * This test does not pass now + */ + SECTION("Retransmit STREAM frame") + { + MIOBuffer *write_buffer = new_MIOBuffer(BUFFER_SIZE_INDEX_8K); + IOBufferReader *write_buffer_reader = write_buffer->alloc_reader(); + + QUICRTTMeasure rtt_provider; + MockQUICConnectionInfoProvider cinfo_provider; + std::unique_ptr stream(new QUICSendStream(&cinfo_provider, stream_id, UINT64_MAX)); + SCOPED_MUTEX_LOCK(lock, stream->mutex, this_ethread()); + + MockContinuation mock_cont(stream->mutex); + stream->do_io_write(&mock_cont, INT64_MAX, write_buffer_reader); + + QUICEncryptionLevel level = QUICEncryptionLevel::ONE_RTT; + const char data1[] = "this is a test data"; + const char data2[] = "THIS IS ANOTHER TEST DATA"; + QUICFrame *frame = nullptr; + QUICStreamFrame *frame1 = nullptr; + QUICStreamFrame *frame2 = nullptr; + uint8_t frame_buf2[QUICFrame::MAX_INSTANCE_SIZE]; + + // Write data1 + write_buffer->write(data1, sizeof(data1)); + stream->handleEvent(VC_EVENT_WRITE_READY, nullptr); + // Generate STREAM frame + frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0); + frame1 = static_cast(frame); + CHECK(frame->type() == QUICFrameType::STREAM); + CHECK(stream->generate_frame(frame_buf, level, 4096, 4096, 0) == nullptr); + CHECK(stream->will_generate_frame(level, 0) == false); + stream->on_frame_lost(frame->id()); + CHECK(stream->will_generate_frame(level, 0) == true); + + // Write data2 + write_buffer->write(data2, sizeof(data2)); + stream->handleEvent(VC_EVENT_WRITE_READY, nullptr); + // Lost the frame + stream->on_frame_lost(frame->id()); + // Regenerate a frame + frame = stream->generate_frame(frame_buf2, level, 4096, 4096, 0); + // Lost data should be resent first + frame2 = static_cast(frame); + CHECK(frame->type() == QUICFrameType::STREAM); + CHECK(frame1->offset() == frame2->offset()); + CHECK(frame1->data_length() == frame2->data_length()); + CHECK(memcmp(frame1->data()->buf(), frame2->data()->buf(), frame1->data_length()) == 0); + } + + SECTION("Retransmit RESET_STREAM frame") + { + MIOBuffer *write_buffer = new_MIOBuffer(BUFFER_SIZE_INDEX_8K); + IOBufferReader *write_buffer_reader = write_buffer->alloc_reader(); + + QUICRTTMeasure rtt_provider; + MockQUICConnectionInfoProvider cinfo_provider; + std::unique_ptr stream(new QUICSendStream(&cinfo_provider, stream_id, UINT64_MAX)); + SCOPED_MUTEX_LOCK(lock, stream->mutex, this_ethread()); + + MockContinuation mock_cont(stream->mutex); + stream->do_io_write(&mock_cont, INT64_MAX, write_buffer_reader); + + QUICEncryptionLevel level = QUICEncryptionLevel::ONE_RTT; + QUICFrame *frame = nullptr; + + stream->reset(QUICStreamErrorUPtr(new QUICStreamError(stream.get(), QUIC_APP_ERROR_CODE_STOPPING))); + frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0); + REQUIRE(frame); + CHECK(frame->type() == QUICFrameType::RESET_STREAM); + // Don't send it again untill it is considers as lost + CHECK(stream->generate_frame(frame_buf, level, 4096, 4096, 0) == nullptr); + // Loss the frame + stream->on_frame_lost(frame->id()); + // After the loss the frame should be regenerated + frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0); + REQUIRE(frame); + CHECK(frame->type() == QUICFrameType::RESET_STREAM); + } +} diff --git a/iocore/net/quic/test/test_QUICStreamManager.cc b/iocore/net/quic/test/test_QUICStreamManager.cc new file mode 100644 index 00000000000..ced351aba7e --- /dev/null +++ b/iocore/net/quic/test/test_QUICStreamManager.cc @@ -0,0 +1,260 @@ +/** @file + * + * A brief file description + * + * @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 "catch.hpp" + +#include + +#include "quic/QUICStreamManager.h" +#include "quic/QUICFrame.h" +#include "quic/Mock.h" + +TEST_CASE("QUICStreamManager_NewStream", "[quic]") +{ + QUICEncryptionLevel level = QUICEncryptionLevel::ONE_RTT; + QUICApplicationMap app_map; + MockQUICConnection connection; + MockQUICApplication mock_app(&connection); + app_map.set_default(&mock_app); + MockQUICConnectionInfoProvider cinfo_provider; + QUICRTTMeasure rtt_provider; + QUICStreamManager sm(&cinfo_provider, &rtt_provider, &app_map); + + uint8_t local_tp_buf[] = { + 0x00, 0x06, // size of parameters + 0x00, 0x08, // parameter id - initial_max_streams_bidi + 0x00, 0x02, // length of value + 0x40, 0x10 // value + }; + std::shared_ptr local_tp = + std::make_shared(local_tp_buf, sizeof(local_tp_buf)); + + uint8_t remote_tp_buf[] = { + 0x00, 0x06, // size of parameters + 0x00, 0x08, // parameter id - initial_max_streams_bidi + 0x00, 0x02, // length of value + 0x40, 0x10 // value + }; + std::shared_ptr remote_tp = + std::make_shared(remote_tp_buf, sizeof(remote_tp_buf)); + + sm.init_flow_control_params(local_tp, remote_tp); + + // STREAM frames create new streams + Ptr block = make_ptr(new_IOBufferBlock()); + block->alloc(); + block->fill(4); + CHECK(block->read_avail() == 4); + + uint8_t stream_frame_0_buf[QUICFrame::MAX_INSTANCE_SIZE]; + uint8_t stream_frame_4_buf[QUICFrame::MAX_INSTANCE_SIZE]; + QUICFrame *stream_frame_0 = QUICFrameFactory::create_stream_frame(stream_frame_0_buf, block, 0, 0); + QUICFrame *stream_frame_4 = QUICFrameFactory::create_stream_frame(stream_frame_4_buf, block, 4, 0); + CHECK(sm.stream_count() == 0); + sm.handle_frame(level, *stream_frame_0); + CHECK(sm.stream_count() == 1); + sm.handle_frame(level, *stream_frame_4); + CHECK(sm.stream_count() == 2); + + // RESET_STREAM frames create new streams + uint8_t rst_stream_frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + QUICFrame *rst_stream_frame = + QUICFrameFactory::create_rst_stream_frame(rst_stream_frame_buf, 8, static_cast(0x01), 0); + sm.handle_frame(level, *rst_stream_frame); + CHECK(sm.stream_count() == 3); + + // MAX_STREAM_DATA frames create new streams + uint8_t max_stream_data_frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + QUICFrame *max_stream_data_frame = QUICFrameFactory::create_max_stream_data_frame(max_stream_data_frame_buf, 0x0c, 0); + sm.handle_frame(level, *max_stream_data_frame); + CHECK(sm.stream_count() == 4); + + // STREAM_DATA_BLOCKED frames create new streams + uint8_t stream_blocked_frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + QUICFrame *stream_blocked_frame = QUICFrameFactory::create_stream_data_blocked_frame(stream_blocked_frame_buf, 0x10, 0); + sm.handle_frame(level, *stream_blocked_frame); + CHECK(sm.stream_count() == 5); + + // Set local maximum stream id + sm.set_max_streams_bidi(5); + uint8_t stream_blocked_frame_x_buf[QUICFrame::MAX_INSTANCE_SIZE]; + QUICFrame *stream_blocked_frame_x = QUICFrameFactory::create_stream_data_blocked_frame(stream_blocked_frame_x_buf, 0x18, 0); + sm.handle_frame(level, *stream_blocked_frame_x); + CHECK(sm.stream_count() == 5); +} + +TEST_CASE("QUICStreamManager_first_initial_map", "[quic]") +{ + QUICEncryptionLevel level = QUICEncryptionLevel::ONE_RTT; + QUICApplicationMap app_map; + MockQUICConnection connection; + MockQUICApplication mock_app(&connection); + app_map.set_default(&mock_app); + MockQUICConnectionInfoProvider cinfo_provider; + QUICRTTMeasure rtt_provider; + QUICStreamManager sm(&cinfo_provider, &rtt_provider, &app_map); + std::shared_ptr local_tp = std::make_shared(); + std::shared_ptr remote_tp = std::make_shared(); + sm.init_flow_control_params(local_tp, remote_tp); + + // STREAM frames create new streams + Ptr block = make_ptr(new_IOBufferBlock()); + block->alloc(); + block->fill(4); + CHECK(block->read_avail() == 4); + + uint8_t stream_frame_0_buf[QUICFrame::MAX_INSTANCE_SIZE]; + QUICFrame *stream_frame_0 = QUICFrameFactory::create_stream_frame(stream_frame_0_buf, block, 0, 7); + + sm.handle_frame(level, *stream_frame_0); + CHECK("succeed"); +} + +TEST_CASE("QUICStreamManager_total_offset_received", "[quic]") +{ + QUICEncryptionLevel level = QUICEncryptionLevel::ONE_RTT; + QUICApplicationMap app_map; + MockQUICConnection connection; + MockQUICApplication mock_app(&connection); + app_map.set_default(&mock_app); + QUICRTTMeasure rtt_provider; + QUICStreamManager sm(new MockQUICConnectionInfoProvider(), &rtt_provider, &app_map); + + uint8_t local_tp_buf[] = { + 0x00, 0x0e, // size of parameters + 0x00, 0x08, // parameter id - initial_max_streams_bidi + 0x00, 0x02, // length of value + 0x40, 0x10, // value + 0x00, 0x05, // parameter id - initial_max_stream_data_bidi_local + 0x00, 0x04, // length of value + 0xbf, 0xff, 0xff, 0xff // value + }; + std::shared_ptr local_tp = + std::make_shared(local_tp_buf, sizeof(local_tp_buf)); + + uint8_t remote_tp_buf[] = { + 0x00, 0x0e, // size of parameters + 0x00, 0x08, // parameter id - initial_max_streams_bidi + 0x00, 0x02, // length of value + 0x40, 0x10, // value + 0x00, 0x06, // parameter id - initial_max_stream_data_bidi_remote + 0x00, 0x04, // length of value + 0xbf, 0xff, 0xff, 0xff // value + }; + std::shared_ptr remote_tp = + std::make_shared(remote_tp_buf, sizeof(remote_tp_buf)); + + sm.init_flow_control_params(local_tp, remote_tp); + + // Create a stream with STREAM_DATA_BLOCKED (== noop) + uint8_t stream_blocked_frame_0_buf[QUICFrame::MAX_INSTANCE_SIZE]; + uint8_t stream_blocked_frame_1_buf[QUICFrame::MAX_INSTANCE_SIZE]; + QUICFrame *stream_blocked_frame_0 = QUICFrameFactory::create_stream_data_blocked_frame(stream_blocked_frame_0_buf, 0, 0); + QUICFrame *stream_blocked_frame_1 = QUICFrameFactory::create_stream_data_blocked_frame(stream_blocked_frame_1_buf, 4, 0); + sm.handle_frame(level, *stream_blocked_frame_0); + sm.handle_frame(level, *stream_blocked_frame_1); + CHECK(sm.stream_count() == 2); + CHECK(sm.total_offset_received() == 0); + + // total_offset should be a integer in unit of 1024 octets + Ptr block = make_ptr(new_IOBufferBlock()); + block->alloc(); + block->fill(1024); + CHECK(block->read_avail() == 1024); + + uint8_t stream_frame_1_buf[QUICFrame::MAX_INSTANCE_SIZE]; + QUICFrame *stream_frame_1 = QUICFrameFactory::create_stream_frame(stream_frame_1_buf, block, 8, 0); + sm.handle_frame(level, *stream_frame_1); + CHECK(sm.total_offset_received() == 1024); +} + +TEST_CASE("QUICStreamManager_total_offset_sent", "[quic]") +{ + QUICEncryptionLevel level = QUICEncryptionLevel::ONE_RTT; + QUICApplicationMap app_map; + MockQUICConnection connection; + MockQUICApplication mock_app(&connection); + app_map.set_default(&mock_app); + QUICRTTMeasure rtt_provider; + QUICStreamManager sm(new MockQUICConnectionInfoProvider(), &rtt_provider, &app_map); + + uint8_t local_tp_buf[] = { + 0x00, 0x0e, // size of parameters + 0x00, 0x08, // parameter id - initial_max_streams_bidi + 0x00, 0x02, // length of value + 0x40, 0x10, // value + 0x00, 0x05, // parameter id - initial_max_stream_data_bidi_local + 0x00, 0x04, // length of value + 0xbf, 0xff, 0xff, 0xff // value + }; + std::shared_ptr local_tp = + std::make_shared(local_tp_buf, sizeof(local_tp_buf)); + + uint8_t remote_tp_buf[] = { + 0x00, 0x0e, // size of parameters + 0x00, 0x08, // parameter id - initial_max_streams_bidi + 0x00, 0x02, // length of value + 0x40, 0x10, // value + 0x00, 0x06, // parameter id - initial_max_stream_data_bidi_remote + 0x00, 0x04, // length of value + 0xbf, 0xff, 0xff, 0xff // value + }; + std::shared_ptr remote_tp = + std::make_shared(remote_tp_buf, sizeof(remote_tp_buf)); + + sm.init_flow_control_params(local_tp, remote_tp); + + // Create a stream with STREAM_DATA_BLOCKED (== noop) + Ptr block_3 = make_ptr(new_IOBufferBlock()); + block_3->alloc(); + block_3->fill(3); + CHECK(block_3->read_avail() == 3); + + uint8_t stream_frame0_buf_r[QUICFrame::MAX_INSTANCE_SIZE]; + uint8_t stream_frame4_buf_r[QUICFrame::MAX_INSTANCE_SIZE]; + QUICFrame *stream_frame_0_r = QUICFrameFactory::create_stream_frame(stream_frame0_buf_r, block_3, 0, 0); + QUICFrame *stream_frame_4_r = QUICFrameFactory::create_stream_frame(stream_frame4_buf_r, block_3, 4, 0); + sm.handle_frame(level, *stream_frame_0_r); + sm.handle_frame(level, *stream_frame_4_r); + CHECK(sm.stream_count() == 2); + CHECK(sm.total_offset_sent() == 0); + + Ptr block_1024 = make_ptr(new_IOBufferBlock()); + block_1024->alloc(); + block_1024->fill(1024); + CHECK(block_1024->read_avail() == 1024); + + // total_offset should be a integer in unit of octets + uint8_t frame_buf[4096]; + mock_app.send(reinterpret_cast(block_1024->buf()), 1024, 0); + sm.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 16384, 16384, 0); + CHECK(sm.total_offset_sent() == 1024); + + // total_offset should be a integer in unit of octets + mock_app.send(reinterpret_cast(block_1024->buf()), 1024, 4); + sm.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 16384, 16384, 0); + CHECK(sm.total_offset_sent() == 2048); + + // Wait for event processing + sleep(2); +} diff --git a/iocore/net/quic/test/test_QUICStreamState.cc b/iocore/net/quic/test/test_QUICStreamState.cc new file mode 100644 index 00000000000..b5538669d16 --- /dev/null +++ b/iocore/net/quic/test/test_QUICStreamState.cc @@ -0,0 +1,532 @@ +/** @file + * + * A brief file description + * + * @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 "catch.hpp" + +#include + +#include "quic/QUICFrame.h" +#include "quic/QUICStreamState.h" +#include "quic/Mock.h" + +// Unidirectional (sending) +TEST_CASE("QUICSendStreamState", "[quic]") +{ + Ptr block_4 = make_ptr(new_IOBufferBlock()); + block_4->alloc(); + block_4->fill(4); + CHECK(block_4->read_avail() == 4); + + uint8_t stream_frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + uint8_t stream_frame_with_fin_buf[QUICFrame::MAX_INSTANCE_SIZE]; + uint8_t rst_stream_frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + uint8_t stream_data_blocked_frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + + auto stream_frame = QUICFrameFactory::create_stream_frame(stream_frame_buf, block_4, 1, 0); + auto stream_frame_with_fin = QUICFrameFactory::create_stream_frame(stream_frame_with_fin_buf, block_4, 1, 0, true); + auto rst_stream_frame = + QUICFrameFactory::create_rst_stream_frame(rst_stream_frame_buf, 0, static_cast(0x01), 0); + auto stream_data_blocked_frame = QUICFrameFactory::create_stream_data_blocked_frame(stream_data_blocked_frame_buf, 0, 0); + MockQUICTransferProgressProvider pp; + + SECTION("Ready -> Send -> Data Sent -> Data Recvd") + { + // Case1. Create Stream (Sending) + QUICSendStreamStateMachine ss(nullptr, &pp); + CHECK(ss.get() == QUICSendStreamState::Ready); + + // Case2. Send STREAM + CHECK(ss.is_allowed_to_send(QUICFrameType::STREAM)); + ss.update_with_sending_frame(*stream_frame); + CHECK(ss.get() == QUICSendStreamState::Send); + + // Case3. Send STREAM_DATA_BLOCKED + CHECK(ss.is_allowed_to_send(QUICFrameType::STREAM_DATA_BLOCKED)); + ss.update_with_sending_frame(*stream_data_blocked_frame); + CHECK(ss.get() == QUICSendStreamState::Send); + + // Case3. Send FIN in a STREAM + CHECK(ss.is_allowed_to_send(QUICFrameType::STREAM)); + ss.update_with_sending_frame(*stream_frame_with_fin); + CHECK(ss.get() == QUICSendStreamState::DataSent); + + // Case4. STREAM is not allowed to send + CHECK(!ss.is_allowed_to_send(QUICFrameType::STREAM)); + + // Case5. Receive all ACKs + pp.set_transfer_complete(true); + ss.update_on_ack(); + CHECK(ss.get() == QUICSendStreamState::DataRecvd); + } + + SECTION("Ready -> Send") + { + // Case1. Create Stream (Sending) + QUICSendStreamStateMachine ss(nullptr, &pp); + CHECK(ss.get() == QUICSendStreamState::Ready); + + // Case2. Send STREAM_DATA_BLOCKED + CHECK(ss.is_allowed_to_send(QUICFrameType::STREAM_DATA_BLOCKED)); + ss.update_with_sending_frame(*stream_data_blocked_frame); + CHECK(ss.get() == QUICSendStreamState::Send); + } + + SECTION("Ready -> Reset Sent -> Reset Recvd") + { + MockQUICTransferProgressProvider pp; + + // Case1. Create Stream (Sending) + QUICSendStreamStateMachine ss(nullptr, &pp); + CHECK(ss.get() == QUICSendStreamState::Ready); + + // Case2. Send RESET_STREAM + CHECK(ss.is_allowed_to_send(QUICFrameType::RESET_STREAM)); + ss.update_with_sending_frame(*rst_stream_frame); + CHECK(ss.get() == QUICSendStreamState::ResetSent); + + // Case3. Receive ACK for STREAM + CHECK(ss.get() == QUICSendStreamState::ResetSent); + + // Case4. Receive ACK for RESET_STREAM + pp.set_cancelled(true); + ss.update_on_ack(); + CHECK(ss.get() == QUICSendStreamState::ResetRecvd); + } + + SECTION("Ready -> Send -> Reset Sent -> Reset Recvd") + { + // Case1. Create Stream (Sending) + QUICSendStreamStateMachine ss(nullptr, &pp); + CHECK(ss.get() == QUICSendStreamState::Ready); + + // Case2. Send STREAM + CHECK(ss.is_allowed_to_send(QUICFrameType::STREAM)); + ss.update_with_sending_frame(*stream_frame); + CHECK(ss.get() == QUICSendStreamState::Send); + + // Case3. Send RESET_STREAM + CHECK(ss.is_allowed_to_send(QUICFrameType::RESET_STREAM)); + ss.update_with_sending_frame(*rst_stream_frame); + CHECK(ss.get() == QUICSendStreamState::ResetSent); + + // Case4. Receive ACK for STREAM + ss.update_on_ack(); + CHECK(ss.get() == QUICSendStreamState::ResetSent); + + // Case5. Receive ACK for RESET_STREAM + pp.set_cancelled(true); + ss.update_on_ack(); + CHECK(ss.get() == QUICSendStreamState::ResetRecvd); + } + + SECTION("Ready -> Send -> Data Sent -> Reset Sent -> Reset Recvd") + { + // Case1. Create Stream (Sending) + QUICSendStreamStateMachine ss(nullptr, &pp); + CHECK(ss.get() == QUICSendStreamState::Ready); + + // Case2. Send STREAM + CHECK(ss.is_allowed_to_send(QUICFrameType::STREAM)); + ss.update_with_sending_frame(*stream_frame); + CHECK(ss.get() == QUICSendStreamState::Send); + + // Case3. Send STREAM_DATA_BLOCKED + CHECK(ss.is_allowed_to_send(QUICFrameType::STREAM_DATA_BLOCKED)); + ss.update_with_sending_frame(*stream_data_blocked_frame); + CHECK(ss.get() == QUICSendStreamState::Send); + + // Case3. Send FIN in a STREAM + CHECK(ss.is_allowed_to_send(QUICFrameType::STREAM)); + ss.update_with_sending_frame(*stream_frame_with_fin); + CHECK(ss.get() == QUICSendStreamState::DataSent); + + // Case4. STREAM is not allowed to send + CHECK(!ss.is_allowed_to_send(QUICFrameType::STREAM)); + + // Case4. Send RESET_STREAM + CHECK(ss.is_allowed_to_send(QUICFrameType::RESET_STREAM)); + ss.update_with_sending_frame(*rst_stream_frame); + CHECK(ss.get() == QUICSendStreamState::ResetSent); + + // Case5. Receive ACK for STREAM + ss.update_on_ack(); + CHECK(ss.get() == QUICSendStreamState::ResetSent); + + // Case6. Receive ACK for RESET_STREAM + pp.set_cancelled(true); + ss.update_on_ack(); + CHECK(ss.get() == QUICSendStreamState::ResetRecvd); + } +} + +// Unidirectional (receiving) +TEST_CASE("QUICReceiveStreamState", "[quic]") +{ + Ptr block_4 = make_ptr(new_IOBufferBlock()); + block_4->alloc(); + block_4->fill(4); + CHECK(block_4->read_avail() == 4); + + uint8_t stream_frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + uint8_t stream_frame_delayed_buf[QUICFrame::MAX_INSTANCE_SIZE]; + uint8_t stream_frame_with_fin_buf[QUICFrame::MAX_INSTANCE_SIZE]; + uint8_t rst_stream_frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + uint8_t stream_data_blocked_frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + + auto stream_frame = QUICFrameFactory::create_stream_frame(stream_frame_buf, block_4, 1, 0); + auto stream_frame_delayed = QUICFrameFactory::create_stream_frame(stream_frame_delayed_buf, block_4, 1, 1); + auto stream_frame_with_fin = QUICFrameFactory::create_stream_frame(stream_frame_with_fin_buf, block_4, 1, 2, true); + auto rst_stream_frame = + QUICFrameFactory::create_rst_stream_frame(rst_stream_frame_buf, 0, static_cast(0x01), 0); + auto stream_data_blocked_frame = QUICFrameFactory::create_stream_data_blocked_frame(stream_data_blocked_frame_buf, 0, 0); + + SECTION("Recv -> Size Known -> Data Recvd -> Data Read") + { + MockQUICTransferProgressProvider in_progress; + + // Case1. Recv STREAM + QUICReceiveStreamStateMachine ss(&in_progress, nullptr); + CHECK(ss.is_allowed_to_send(QUICFrameType::MAX_STREAM_DATA) == false); + CHECK(ss.is_allowed_to_receive(QUICFrameType::STREAM)); + in_progress.set_transfer_progress(1); + ss.update_with_receiving_frame(*stream_frame); + CHECK(ss.get() == QUICReceiveStreamState::Recv); + + // Case2. Recv STREAM_DATA_BLOCKED + CHECK(ss.is_allowed_to_receive(QUICFrameType::STREAM_DATA_BLOCKED)); + ss.update_with_receiving_frame(*stream_data_blocked_frame); + CHECK(ss.get() == QUICReceiveStreamState::Recv); + + // Case3. Recv FIN in a STREAM + CHECK(ss.is_allowed_to_receive(QUICFrameType::STREAM)); + in_progress.set_transfer_goal(3); + ss.update_with_receiving_frame(*stream_frame_with_fin); + CHECK(ss.get() == QUICReceiveStreamState::SizeKnown); + + // Case4. Recv ALL data + CHECK(ss.is_allowed_to_receive(QUICFrameType::STREAM)); + in_progress.set_transfer_progress(3); + ss.update_with_receiving_frame(*stream_frame_delayed); + CHECK(ss.get() == QUICReceiveStreamState::DataRecvd); + + // Case5. Read data + in_progress.set_transfer_complete(true); + ss.update_on_read(); + CHECK(ss.get() == QUICReceiveStreamState::DataRead); + } + + SECTION("Recv -> Reset Recvd -> Reset Read") + { + MockQUICTransferProgressProvider in_progress; + + // Case1. Recv STREAM + QUICReceiveStreamStateMachine ss(&in_progress, nullptr); + CHECK(ss.is_allowed_to_receive(QUICFrameType::STREAM)); + ss.update_with_receiving_frame(*stream_frame); + CHECK(ss.get() == QUICReceiveStreamState::Recv); + + // Case2. Recv RESET_STREAM + CHECK(ss.is_allowed_to_receive(QUICFrameType::RESET_STREAM)); + ss.update_with_receiving_frame(*rst_stream_frame); + CHECK(ss.get() == QUICReceiveStreamState::ResetRecvd); + + // Case3. Handle reset + ss.update_on_eos(); + CHECK(ss.get() == QUICReceiveStreamState::ResetRead); + } + + SECTION("Recv -> Size Known -> Reset Recvd") + { + MockQUICTransferProgressProvider in_progress; + + // Case1. Recv STREAM + QUICReceiveStreamStateMachine ss(&in_progress, nullptr); + CHECK(ss.is_allowed_to_receive(QUICFrameType::STREAM)); + ss.update_with_receiving_frame(*stream_frame); + CHECK(ss.get() == QUICReceiveStreamState::Recv); + + // Case2. Recv FIN in a STREAM + CHECK(ss.is_allowed_to_receive(QUICFrameType::STREAM)); + ss.update_with_receiving_frame(*stream_frame_with_fin); + CHECK(ss.get() == QUICReceiveStreamState::SizeKnown); + + // Case3. Recv RESET_STREAM + CHECK(ss.is_allowed_to_receive(QUICFrameType::RESET_STREAM)); + ss.update_with_receiving_frame(*rst_stream_frame); + CHECK(ss.get() == QUICReceiveStreamState::ResetRecvd); + } + + SECTION("Recv -> Size Known -> Data Recvd !-> Reset Recvd") + { + MockQUICTransferProgressProvider in_progress; + + // Case1. Recv STREAM + QUICReceiveStreamStateMachine ss(&in_progress, nullptr); + CHECK(ss.is_allowed_to_receive(QUICFrameType::STREAM)); + in_progress.set_transfer_progress(1); + ss.update_with_receiving_frame(*stream_frame); + CHECK(ss.get() == QUICReceiveStreamState::Recv); + + // Case2. Recv FIN in a STREAM + CHECK(ss.is_allowed_to_receive(QUICFrameType::STREAM)); + in_progress.set_transfer_goal(3); + ss.update_with_receiving_frame(*stream_frame_with_fin); + CHECK(ss.get() == QUICReceiveStreamState::SizeKnown); + + // Case3. Recv ALL data + CHECK(ss.is_allowed_to_receive(QUICFrameType::STREAM)); + in_progress.set_transfer_progress(3); + ss.update_with_receiving_frame(*stream_frame_delayed); + CHECK(ss.get() == QUICReceiveStreamState::DataRecvd); + + // Case4. Recv RESET_STREAM + CHECK(ss.is_allowed_to_receive(QUICFrameType::RESET_STREAM)); + ss.update_with_receiving_frame(*rst_stream_frame); + CHECK(ss.get() == QUICReceiveStreamState::DataRecvd); + } + + SECTION("Recv -> Size Known -> Reset Recvd !-> Data Recvd") + { + MockQUICTransferProgressProvider in_progress; + + // Case1. Recv STREAM + QUICReceiveStreamStateMachine ss(&in_progress, nullptr); + CHECK(ss.is_allowed_to_receive(QUICFrameType::STREAM)); + in_progress.set_transfer_progress(1); + ss.update_with_receiving_frame(*stream_frame); + CHECK(ss.get() == QUICReceiveStreamState::Recv); + + // Case2. Recv FIN in a STREAM + CHECK(ss.is_allowed_to_receive(QUICFrameType::STREAM)); + in_progress.set_transfer_goal(3); + ss.update_with_receiving_frame(*stream_frame_with_fin); + CHECK(ss.get() == QUICReceiveStreamState::SizeKnown); + + // Case3. Recv RESET_STREAM + CHECK(ss.is_allowed_to_receive(QUICFrameType::RESET_STREAM)); + ss.update_with_receiving_frame(*rst_stream_frame); + CHECK(ss.get() == QUICReceiveStreamState::ResetRecvd); + + // Case4. Recv ALL data + CHECK(ss.is_allowed_to_receive(QUICFrameType::STREAM)); + in_progress.set_transfer_progress(3); + ss.update_with_receiving_frame(*stream_frame_delayed); + CHECK(ss.get() == QUICReceiveStreamState::ResetRecvd); + CHECK(ss.is_allowed_to_send(QUICFrameType::STOP_SENDING) == false); + } + + SECTION("Do not discard STREAM and RESET_STREAM in DataRecvd") + { + MockQUICTransferProgressProvider in_progress; + + // Case1. Recv STREAM + QUICReceiveStreamStateMachine ss(&in_progress, nullptr); + CHECK(ss.is_allowed_to_receive(QUICFrameType::STREAM)); + ss.update_with_receiving_frame(*stream_frame); + CHECK(ss.get() == QUICReceiveStreamState::Recv); + + // Case2. Recv FIN in a STREAM + CHECK(ss.is_allowed_to_receive(QUICFrameType::STREAM)); + ss.update_with_receiving_frame(*stream_frame_with_fin); + CHECK(ss.get() == QUICReceiveStreamState::SizeKnown); + + // // Case3. Recv ALL data + CHECK(ss.is_allowed_to_receive(QUICFrameType::STREAM)); + in_progress.set_transfer_complete(true); + ss.update_with_receiving_frame(*stream_frame_delayed); + // ss.update_on_transport_recv_event(); + CHECK(ss.get() == QUICReceiveStreamState::DataRecvd); + + CHECK(ss.is_allowed_to_receive(QUICFrameType::RESET_STREAM)); + CHECK(ss.is_allowed_to_receive(QUICFrameType::STREAM)); + CHECK(ss.is_allowed_to_send(QUICFrameType::STOP_SENDING)); + } +} + +TEST_CASE("QUICBidiState", "[quic]") +{ + Ptr block_4 = make_ptr(new_IOBufferBlock()); + block_4->alloc(); + block_4->fill(4); + CHECK(block_4->read_avail() == 4); + + uint8_t stream_frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + uint8_t stream_frame_delayed_buf[QUICFrame::MAX_INSTANCE_SIZE]; + uint8_t stream_frame_with_fin_buf[QUICFrame::MAX_INSTANCE_SIZE]; + uint8_t rst_stream_frame_buf[QUICFrame::MAX_INSTANCE_SIZE]; + + auto stream_frame = QUICFrameFactory::create_stream_frame(stream_frame_buf, block_4, 1, 0); + auto stream_frame_delayed = QUICFrameFactory::create_stream_frame(stream_frame_delayed_buf, block_4, 1, 1); + auto stream_frame_with_fin = QUICFrameFactory::create_stream_frame(stream_frame_with_fin_buf, block_4, 1, 2, true); + auto rst_stream_frame = + QUICFrameFactory::create_rst_stream_frame(rst_stream_frame_buf, 0, static_cast(0x01), 0); + + SECTION("QUICBidiState idle -> open -> HC_R 1") + { + MockQUICTransferProgressProvider in_progress; + MockQUICTransferProgressProvider out_progress; + + QUICBidirectionalStreamStateMachine ss(nullptr, &out_progress, &in_progress, nullptr); + CHECK(ss.get() == QUICBidirectionalStreamState::Idle); + + CHECK(ss.is_allowed_to_receive(QUICFrameType::STREAM)); + ss.update_with_receiving_frame(*stream_frame); + + CHECK(ss.get() == QUICBidirectionalStreamState::Open); + in_progress.set_transfer_complete(true); + ss.update_with_receiving_frame(*stream_frame_with_fin); + CHECK(ss.get() == QUICBidirectionalStreamState::HC_R); + } + + SECTION("QUICBidiState idle -> open -> HC_R 2") + { + MockQUICTransferProgressProvider in_progress; + MockQUICTransferProgressProvider out_progress; + + QUICBidirectionalStreamStateMachine ss(nullptr, &out_progress, &in_progress, nullptr); + CHECK(ss.get() == QUICBidirectionalStreamState::Idle); + + CHECK(ss.is_allowed_to_receive(QUICFrameType::STREAM)); + ss.update_with_receiving_frame(*stream_frame); + + CHECK(ss.get() == QUICBidirectionalStreamState::Open); + ss.update_with_receiving_frame(*rst_stream_frame); + CHECK(ss.get() == QUICBidirectionalStreamState::HC_R); + } + + SECTION("QUICBidiState idle -> open -> HC_L 1") + { + MockQUICTransferProgressProvider in_progress; + MockQUICTransferProgressProvider out_progress; + + QUICBidirectionalStreamStateMachine ss(nullptr, &out_progress, &in_progress, nullptr); + CHECK(ss.get() == QUICBidirectionalStreamState::Idle); + + CHECK(ss.is_allowed_to_send(QUICFrameType::STREAM)); + ss.update_with_sending_frame(*stream_frame); + + CHECK(ss.get() == QUICBidirectionalStreamState::Open); + ss.update_with_sending_frame(*stream_frame_with_fin); + CHECK(ss.get() == QUICBidirectionalStreamState::Open); + + out_progress.set_transfer_complete(true); + ss.update_on_ack(); + CHECK(ss.get() == QUICBidirectionalStreamState::HC_L); + + ss.update_with_sending_frame(*stream_frame_delayed); + CHECK(ss.get() == QUICBidirectionalStreamState::HC_L); + } + + SECTION("QUICBidiState idle -> open -> HC_L 2") + { + MockQUICTransferProgressProvider in_progress; + MockQUICTransferProgressProvider out_progress; + + QUICBidirectionalStreamStateMachine ss(nullptr, &out_progress, &in_progress, nullptr); + CHECK(ss.get() == QUICBidirectionalStreamState::Idle); + + CHECK(ss.is_allowed_to_send(QUICFrameType::STREAM)); + ss.update_with_sending_frame(*stream_frame); + + CHECK(ss.get() == QUICBidirectionalStreamState::Open); + ss.update_with_sending_frame(*rst_stream_frame); + CHECK(ss.get() == QUICBidirectionalStreamState::HC_L); + } + + SECTION("QUICBidiState idle -> open -> closed 1") + { + MockQUICTransferProgressProvider in_progress; + MockQUICTransferProgressProvider out_progress; + + QUICBidirectionalStreamStateMachine ss(nullptr, &out_progress, &in_progress, nullptr); + CHECK(ss.get() == QUICBidirectionalStreamState::Idle); + + CHECK(ss.is_allowed_to_send(QUICFrameType::STREAM)); + ss.update_with_sending_frame(*stream_frame); + + CHECK(ss.get() == QUICBidirectionalStreamState::Open); + ss.update_with_sending_frame(*rst_stream_frame); + CHECK(ss.get() == QUICBidirectionalStreamState::HC_L); + + CHECK(ss.is_allowed_to_receive(QUICFrameType::STREAM)); + ss.update_with_receiving_frame(*stream_frame); + + ss.update_with_receiving_frame(*rst_stream_frame); + CHECK(ss.get() == QUICBidirectionalStreamState::Closed); + + ss.update_on_eos(); + CHECK(ss.get() == QUICBidirectionalStreamState::Closed); + } + + SECTION("QUICBidiState idle -> open -> closed 2") + { + MockQUICTransferProgressProvider in_progress; + MockQUICTransferProgressProvider out_progress; + + QUICBidirectionalStreamStateMachine ss(nullptr, &out_progress, &in_progress, nullptr); + CHECK(ss.get() == QUICBidirectionalStreamState::Idle); + + CHECK(ss.is_allowed_to_send(QUICFrameType::STREAM)); + ss.update_with_sending_frame(*stream_frame_with_fin); + CHECK(ss.get() == QUICBidirectionalStreamState::Open); + out_progress.set_transfer_complete(true); + ss.update_on_ack(); + CHECK(ss.get() == QUICBidirectionalStreamState::HC_L); + + CHECK(ss.is_allowed_to_receive(QUICFrameType::STREAM)); + ss.update_with_receiving_frame(*stream_frame); + + ss.update_with_receiving_frame(*rst_stream_frame); + CHECK(ss.get() == QUICBidirectionalStreamState::Closed); + + in_progress.set_transfer_complete(true); + ss.update_on_eos(); + CHECK(ss.get() == QUICBidirectionalStreamState::Closed); + } + + SECTION("QUICBidiState idle -> open -> closed 3") + { + MockQUICTransferProgressProvider in_progress; + MockQUICTransferProgressProvider out_progress; + + QUICBidirectionalStreamStateMachine ss(nullptr, &out_progress, &in_progress, nullptr); + CHECK(ss.get() == QUICBidirectionalStreamState::Idle); + + CHECK(ss.is_allowed_to_send(QUICFrameType::STREAM)); + ss.update_with_sending_frame(*stream_frame_with_fin); + CHECK(ss.get() == QUICBidirectionalStreamState::Open); + out_progress.set_transfer_complete(true); + ss.update_on_ack(); + CHECK(ss.get() == QUICBidirectionalStreamState::HC_L); + + CHECK(ss.is_allowed_to_receive(QUICFrameType::STREAM)); + ss.update_with_receiving_frame(*stream_frame_delayed); + + ss.update_with_receiving_frame(*stream_frame_with_fin); + CHECK(ss.get() == QUICBidirectionalStreamState::HC_L); + + in_progress.set_transfer_complete(true); + ss.update_on_read(); + CHECK(ss.get() == QUICBidirectionalStreamState::Closed); + } +} diff --git a/iocore/net/quic/test/test_QUICTransportParameters.cc b/iocore/net/quic/test/test_QUICTransportParameters.cc new file mode 100644 index 00000000000..d60e2606790 --- /dev/null +++ b/iocore/net/quic/test/test_QUICTransportParameters.cc @@ -0,0 +1,292 @@ +/** @file + * + * A brief file description + * + * @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 "catch.hpp" + +#include "QUICTransportParameters.h" + +TEST_CASE("QUICTransportParametersInClientHello_read", "[quic]") +{ + SECTION("OK") + { + uint8_t buf[] = { + 0x00, 0x1c, // size of parameters + 0x00, 0x00, // parameter id + 0x00, 0x04, // length of value + 0x11, 0x22, 0x33, 0x44, // value + 0x00, 0x01, // parameter id + 0x00, 0x04, // length of value + 0x12, 0x34, 0x56, 0x78, // value + 0x00, 0x05, // parameter id + 0x00, 0x02, // length of value + 0x0a, 0x0b, // value + 0x00, 0x03, // parameter id + 0x00, 0x02, // length of value + 0x05, 0x67, // value + }; + + QUICTransportParametersInClientHello params_in_ch(buf, sizeof(buf)); + CHECK(params_in_ch.is_valid()); + + uint16_t len = 0; + const uint8_t *data = nullptr; + + data = params_in_ch.getAsBytes(QUICTransportParameterId::ORIGINAL_CONNECTION_ID, len); + CHECK(len == 4); + CHECK(memcmp(data, "\x11\x22\x33\x44", 4) == 0); + + data = params_in_ch.getAsBytes(QUICTransportParameterId::IDLE_TIMEOUT, len); + CHECK(len == 4); + CHECK(memcmp(data, "\x12\x34\x56\x78", 4) == 0); + + data = params_in_ch.getAsBytes(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_LOCAL, len); + CHECK(len == 2); + CHECK(memcmp(data, "\x0a\x0b", 2) == 0); + + data = params_in_ch.getAsBytes(QUICTransportParameterId::MAX_PACKET_SIZE, len); + CHECK(len == 2); + CHECK(memcmp(data, "\x05\x67", 2) == 0); + + data = params_in_ch.getAsBytes(QUICTransportParameterId::ACK_DELAY_EXPONENT, len); + CHECK(len == 0); + CHECK(data == nullptr); + } + + SECTION("Duplicate parameters") + { + uint8_t buf[] = { + 0x00, 0x10, // size of parameters + 0x00, 0x00, // parameter id + 0x00, 0x04, // length of value + 0x11, 0x22, 0x33, 0x44, // value + 0x00, 0x00, // parameter id + 0x00, 0x04, // length of value + 0x12, 0x34, 0x56, 0x78, // value + }; + + QUICTransportParametersInClientHello params_in_ch(buf, sizeof(buf)); + CHECK(!params_in_ch.is_valid()); + } +} + +TEST_CASE("QUICTransportParametersInClientHello_write", "[quic]") +{ + uint8_t buf[65536]; + uint16_t len; + + uint8_t expected[] = { + 0x00, 0x22, // size of parameters + 0x00, 0x02, // parameter id + 0x00, 0x10, // length of value + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, // value + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, // value + 0x00, 0x03, // parameter id + 0x00, 0x02, // length of value + 0x5b, 0xcd, // value + 0x00, 0x05, // parameter id + 0x00, 0x04, // length of value + 0x91, 0x22, 0x33, 0x44, // value + }; + + QUICTransportParametersInClientHello params_in_ch; + + uint32_t max_stream_data = 0x11223344; + params_in_ch.set(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_LOCAL, max_stream_data); + + uint16_t max_packet_size = 0x1bcd; + params_in_ch.set(QUICTransportParameterId::MAX_PACKET_SIZE, max_packet_size); + + uint8_t stateless_reset_token[16] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77}; + params_in_ch.set(QUICTransportParameterId::STATELESS_RESET_TOKEN, stateless_reset_token, 16); + + params_in_ch.store(buf, &len); + CHECK(len == 36); + CHECK(memcmp(buf, expected, len) == 0); +} + +TEST_CASE("QUICTransportParametersInEncryptedExtensions_read", "[quic]") +{ + SECTION("OK case") + { + uint8_t buf[] = { + 0x00, 0x2a, // size of parameters + 0x00, 0x01, // parameter id + 0x00, 0x02, // length of value + 0x51, 0x23, // value + 0x00, 0x02, // parameter id + 0x00, 0x10, // length of value + 0x00, 0x10, 0x20, 0x30, // value + 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0, 0x00, 0x04, // parameter id + 0x00, 0x04, // length of value + 0x92, 0x34, 0x56, 0x78, // value + 0x00, 0x06, // parameter id + 0x00, 0x04, // length of value + 0x91, 0x22, 0x33, 0x44, // value + }; + + QUICTransportParametersInEncryptedExtensions params_in_ee(buf, sizeof(buf)); + CHECK(params_in_ee.is_valid()); + + uint16_t len = 0; + const uint8_t *data = nullptr; + + data = params_in_ee.getAsBytes(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_REMOTE, len); + CHECK(len == 4); + CHECK(memcmp(data, "\x91\x22\x33\x44", 4) == 0); + + data = params_in_ee.getAsBytes(QUICTransportParameterId::INITIAL_MAX_DATA, len); + CHECK(len == 4); + CHECK(memcmp(data, "\x92\x34\x56\x78", 4) == 0); + + data = params_in_ee.getAsBytes(QUICTransportParameterId::IDLE_TIMEOUT, len); + CHECK(len == 2); + CHECK(memcmp(data, "\x51\x23", 2) == 0); + + data = params_in_ee.getAsBytes(QUICTransportParameterId::STATELESS_RESET_TOKEN, len); + CHECK(len == 16); + CHECK(memcmp(data, buf + 12, 16) == 0); + + CHECK(!params_in_ee.contains(QUICTransportParameterId::DISABLE_MIGRATION)); + } + + SECTION("OK case - zero length value") + { + uint8_t buf[] = { + 0x00, 0x1a, // size of parameters + 0x00, 0x01, // parameter id + 0x00, 0x02, // length of value + 0x51, 0x23, // value + 0x00, 0x04, // parameter id + 0x00, 0x04, // length of value + 0xa2, 0x34, 0x56, 0x78, // value + 0x00, 0x06, // parameter id + 0x00, 0x04, // length of value + 0xa1, 0x22, 0x33, 0x44, // value + 0x00, 0x0c, // parameter id + 0x00, 0x00, // length of value + }; + + QUICTransportParametersInEncryptedExtensions params_in_ee(buf, sizeof(buf)); + CHECK(params_in_ee.is_valid()); + + uint16_t len = 0; + const uint8_t *data = nullptr; + + data = params_in_ee.getAsBytes(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_REMOTE, len); + CHECK(len == 4); + CHECK(memcmp(data, "\xa1\x22\x33\x44", 4) == 0); + + data = params_in_ee.getAsBytes(QUICTransportParameterId::INITIAL_MAX_DATA, len); + CHECK(len == 4); + CHECK(memcmp(data, "\xa2\x34\x56\x78", 4) == 0); + + data = params_in_ee.getAsBytes(QUICTransportParameterId::IDLE_TIMEOUT, len); + CHECK(len == 2); + CHECK(memcmp(data, "\x51\x23", 2) == 0); + + CHECK(params_in_ee.contains(QUICTransportParameterId::DISABLE_MIGRATION)); + } + + SECTION("Duplicate parameters") + { + uint8_t buf[] = { + 0x00, 0x1e, // size of parameters + 0x00, 0x00, // parameter id + 0x00, 0x04, // length of value + 0x01, 0x02, 0x03, 0x04, // value + 0x00, 0x00, // parameter id + 0x00, 0x04, // length of value + 0x12, 0x34, 0x56, 0x78, // value + }; + + QUICTransportParametersInEncryptedExtensions params_in_ee(buf, sizeof(buf)); + CHECK(!params_in_ee.is_valid()); + } +} + +TEST_CASE("QUICTransportParametersEncryptedExtensions_write", "[quic]") +{ + SECTION("OK cases") + { + uint8_t buf[65536]; + uint16_t len; + + uint8_t expected[] = { + 0x00, 0x0e, // size of parameters + 0x00, 0x03, // parameter id + 0x00, 0x02, // length of value + 0x5b, 0xcd, // value + 0x00, 0x06, // parameter id + 0x00, 0x04, // length of value + 0x91, 0x22, 0x33, 0x44, // value + }; + + QUICTransportParametersInEncryptedExtensions params_in_ee; + + uint32_t max_stream_data = 0x11223344; + params_in_ee.set(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_REMOTE, max_stream_data); + + uint16_t max_packet_size = 0x1bcd; + params_in_ee.set(QUICTransportParameterId::MAX_PACKET_SIZE, max_packet_size); + + params_in_ee.add_version(0x01020304); + params_in_ee.add_version(0x05060708); + params_in_ee.store(buf, &len); + CHECK(len == 16); + CHECK(memcmp(buf, expected, len) == 0); + } + + SECTION("OK cases - include zero length value") + { + uint8_t buf[65536]; + uint16_t len; + + uint8_t expected[] = { + 0x00, 0x12, // size of parameters + 0x00, 0x03, // parameter id + 0x00, 0x02, // length of value + 0x5b, 0xcd, // value + 0x00, 0x06, // parameter id + 0x00, 0x04, // length of value + 0x91, 0x22, 0x33, 0x44, // value + 0x00, 0x0c, // parameter id + 0x00, 0x00, // length of value + }; + + QUICTransportParametersInEncryptedExtensions params_in_ee; + + uint32_t max_stream_data = 0x11223344; + params_in_ee.set(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_REMOTE, max_stream_data); + + uint16_t max_packet_size = 0x1bcd; + params_in_ee.set(QUICTransportParameterId::MAX_PACKET_SIZE, max_packet_size); + params_in_ee.set(QUICTransportParameterId::DISABLE_MIGRATION, nullptr, 0); + + params_in_ee.add_version(0x01020304); + params_in_ee.add_version(0x05060708); + params_in_ee.store(buf, &len); + CHECK(len == 20); + CHECK(memcmp(buf, expected, len) == 0); + } +} diff --git a/iocore/net/quic/test/test_QUICType.cc b/iocore/net/quic/test/test_QUICType.cc new file mode 100644 index 00000000000..aff8247fb4c --- /dev/null +++ b/iocore/net/quic/test/test_QUICType.cc @@ -0,0 +1,78 @@ +/** @file + * + * A brief file description + * + * @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 "catch.hpp" + +#include "quic/QUICTypes.h" +#include "I_EventSystem.h" +#include "tscore/ink_hrtime.h" +#include + +TEST_CASE("QUICType", "[quic]") +{ + SECTION("QUICRetryToken") + { + IpEndpoint ep; + ats_ip4_set(&ep, 0x04030201, 0x2211); + + uint8_t cid_buf[] = {0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27}; + QUICConnectionId cid(cid_buf, sizeof(cid_buf)); + + QUICRetryToken token1(ep, cid); + QUICRetryToken token2(token1.buf(), token1.length()); + + CHECK(token1.is_valid(ep)); + CHECK(token2.is_valid(ep)); + CHECK(QUICAddressValidationToken::type(token1.buf()) == QUICAddressValidationToken::Type::RETRY); + CHECK(QUICAddressValidationToken::type(token2.buf()) == QUICAddressValidationToken::Type::RETRY); + CHECK(token1 == token2); + CHECK(token1.length() == token2.length()); + CHECK(memcmp(token1.buf(), token2.buf(), token1.length()) == 0); + CHECK(token1.original_dcid() == token2.original_dcid()); + } + + SECTION("QUICResumptionToken") + { + IpEndpoint ep; + ats_ip4_set(&ep, 0x04030201, 0x2211); + + uint8_t cid_buf[] = {0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27}; + QUICConnectionId cid(cid_buf, sizeof(cid_buf)); + + ink_hrtime expire_date = Thread::get_hrtime() + (3 * HRTIME_DAY); + + QUICResumptionToken token1(ep, cid, expire_date); + QUICResumptionToken token2(token1.buf(), token1.length()); + + CHECK(token1.is_valid(ep)); + CHECK(token2.is_valid(ep)); + CHECK(QUICAddressValidationToken::type(token1.buf()) == QUICAddressValidationToken::Type::RESUMPTION); + CHECK(QUICAddressValidationToken::type(token2.buf()) == QUICAddressValidationToken::Type::RESUMPTION); + CHECK(token1 == token2); + CHECK(token1.length() == token2.length()); + CHECK(memcmp(token1.buf(), token2.buf(), token1.length()) == 0); + CHECK(token1.cid() == token2.cid()); + } +} diff --git a/iocore/net/quic/test/test_QUICTypeUtil.cc b/iocore/net/quic/test/test_QUICTypeUtil.cc new file mode 100644 index 00000000000..b71ce4ab6ba --- /dev/null +++ b/iocore/net/quic/test/test_QUICTypeUtil.cc @@ -0,0 +1,177 @@ +/** @file + * + * A brief file description + * + * @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 "catch.hpp" + +#include "quic/QUICTypes.h" +#include "quic/QUICIntUtil.h" +#include + +TEST_CASE("QUICTypeUtil", "[quic]") +{ + uint8_t buf[8]; + size_t len; + + QUICIntUtil::write_uint_as_nbytes(0xff, 1, buf, &len); + INFO("1 byte to 1 byte"); + CHECK(memcmp(buf, "\xff\x00\x00\x00\x00\x00\x00\x00", 1) == 0); + + QUICIntUtil::write_uint_as_nbytes(0xff, 2, buf, &len); + INFO("1 byte to 2 byte"); + CHECK(memcmp(buf, "\x00\xff\x00\x00\x00\x00\x00\x00", 2) == 0); + + QUICIntUtil::write_uint_as_nbytes(0xff, 4, buf, &len); + INFO("1 byte to 4 byte"); + CHECK(memcmp(buf, "\x00\x00\x00\xff\x00\x00\x00\x00", 4) == 0); + + QUICIntUtil::write_uint_as_nbytes(0xff, 6, buf, &len); + INFO("1 byte to 6 byte"); + CHECK(memcmp(buf, "\x00\x00\x00\x00\x00\xff\x00\x00", 6) == 0); + + QUICIntUtil::write_uint_as_nbytes(0xff, 8, buf, &len); + INFO("1 byte to 8 byte"); + CHECK(memcmp(buf, "\x00\x00\x00\x00\x00\x00\x00\xff", 8) == 0); + + QUICIntUtil::write_uint_as_nbytes(0x11ff, 2, buf, &len); + INFO("2 byte to 2 byte"); + CHECK(memcmp(buf, "\x11\xff\x00\x00\x00\x00\x00\x00", 2) == 0); + + QUICIntUtil::write_uint_as_nbytes(0x11ff, 4, buf, &len); + INFO("2 byte to 4 byte"); + CHECK(memcmp(buf, "\x00\x00\x11\xff\x00\x00\x00\x00", 4) == 0); + + QUICIntUtil::write_uint_as_nbytes(0x11ff, 6, buf, &len); + INFO("2 byte to 6 byte"); + CHECK(memcmp(buf, "\x00\x00\x00\x00\x11\xff\x00\x00", 6) == 0); + + QUICIntUtil::write_uint_as_nbytes(0x11ff, 8, buf, &len); + INFO("2 byte to 8 byte"); + CHECK(memcmp(buf, "\x00\x00\x00\x00\x00\x00\x11\xff", 8) == 0); +} + +TEST_CASE("Variable Length - encoding 1", "[quic]") +{ + uint8_t dst[8] = {0}; + uint64_t src = 151288809941952652; + size_t len = 0; + uint8_t expect[] = {0xc2, 0x19, 0x7c, 0x5e, 0xff, 0x14, 0xe8, 0x8c}; + + QUICVariableInt::encode(dst, sizeof(dst), len, src); + + CHECK(len == 8); + CHECK(memcmp(dst, expect, 8) == 0); +} + +TEST_CASE("Variable Length - encoding 2", "[quic]") +{ + uint8_t dst[8] = {0}; + uint64_t src = 494878333; + size_t len = 0; + uint8_t expect[] = {0x9d, 0x7f, 0x3e, 0x7d}; + + QUICVariableInt::encode(dst, sizeof(dst), len, src); + + CHECK(len == 4); + CHECK(memcmp(dst, expect, 4) == 0); +} + +TEST_CASE("Variable Length - encoding 3", "[quic]") +{ + uint8_t dst[8] = {0}; + uint64_t src = 15293; + size_t len = 0; + uint8_t expect[] = {0x7b, 0xbd}; + + QUICVariableInt::encode(dst, sizeof(dst), len, src); + + CHECK(len == 2); + CHECK(memcmp(dst, expect, 2) == 0); +} + +TEST_CASE("Variable Length - encoding 4", "[quic]") +{ + uint8_t dst[8] = {0}; + uint64_t src = 37; + size_t len = 0; + uint8_t expect[] = {0x25}; + + QUICVariableInt::encode(dst, sizeof(dst), len, src); + + CHECK(len == 1); + CHECK(memcmp(dst, expect, 1) == 0); +} + +TEST_CASE("Variable Length - decoding 1", "[quic]") +{ + uint8_t src[] = {0xc2, 0x19, 0x7c, 0x5e, 0xff, 0x14, 0xe8, 0x8c}; + uint64_t dst = 0; + size_t len = 0; + QUICVariableInt::decode(dst, len, src, sizeof(src)); + + CHECK(dst == 151288809941952652); + CHECK(len == 8); +} + +TEST_CASE("Variable Length - decoding 2", "[quic]") +{ + uint8_t src[] = {0x9d, 0x7f, 0x3e, 0x7d, 0x00, 0x00, 0x00, 0x00}; + uint64_t dst = 0; + size_t len = 0; + QUICVariableInt::decode(dst, len, src, sizeof(src)); + + CHECK(dst == 494878333); + CHECK(len == 4); +} + +TEST_CASE("Variable Length - decoding 3", "[quic]") +{ + uint8_t src[] = {0x7b, 0xbd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint64_t dst = 0; + size_t len = 0; + QUICVariableInt::decode(dst, len, src, sizeof(src)); + + CHECK(dst == 15293); + CHECK(len == 2); +} + +TEST_CASE("Variable Length - decoding 4", "[quic]") +{ + uint8_t src[] = {0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint64_t dst = 0; + size_t len = 0; + QUICVariableInt::decode(dst, len, src, sizeof(src)); + + CHECK(dst == 37); + CHECK(len == 1); +} + +TEST_CASE("Variable Length - decoding 5", "[quic]") +{ + uint8_t src[] = {0x40, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint64_t dst = 0; + size_t len = 0; + QUICVariableInt::decode(dst, len, src, sizeof(src)); + + CHECK(dst == 37); + CHECK(len == 2); +} diff --git a/iocore/net/quic/test/test_QUICVersionNegotiator.cc b/iocore/net/quic/test/test_QUICVersionNegotiator.cc new file mode 100644 index 00000000000..be76949a89d --- /dev/null +++ b/iocore/net/quic/test/test_QUICVersionNegotiator.cc @@ -0,0 +1,125 @@ +/** @file + * + * A brief file description + * + * @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 "catch.hpp" + +#include "quic/QUICVersionNegotiator.h" +#include "quic/QUICPacketProtectionKeyInfo.h" +#include "quic/Mock.h" + +TEST_CASE("QUICVersionNegotiator - Server Side", "[quic]") +{ + MockQUICPacketProtectionKeyInfo pp_key_info; + pp_key_info.set_encryption_key_available(QUICKeyPhase::INITIAL); + + QUICPacketFactory packet_factory(pp_key_info); + QUICVersionNegotiator vn; + ats_unique_buf dummy_payload = ats_unique_malloc(128); + size_t dummy_payload_len = 128; + + SECTION("Normal case") + { + // Check initial state + CHECK(vn.status() == QUICVersionNegotiationStatus::NOT_NEGOTIATED); + + // Negotiate version + packet_factory.set_version(QUIC_SUPPORTED_VERSIONS[0]); + QUICPacketUPtr initial_packet = + packet_factory.create_initial_packet({}, {}, 0, std::move(dummy_payload), dummy_payload_len, true, false, true); + + REQUIRE(initial_packet != nullptr); + vn.negotiate(*initial_packet); + CHECK(vn.status() == QUICVersionNegotiationStatus::NEGOTIATED); + } + + SECTION("Negotiation case") + { + // Check initial state + CHECK(vn.status() == QUICVersionNegotiationStatus::NOT_NEGOTIATED); + + // Negotiate version + packet_factory.set_version(QUIC_SUPPORTED_VERSIONS[0]); + QUICPacketUPtr initial_packet = + packet_factory.create_initial_packet({}, {}, 0, std::move(dummy_payload), dummy_payload_len, true, false, true); + + REQUIRE(initial_packet != nullptr); + vn.negotiate(*initial_packet); + CHECK(vn.status() == QUICVersionNegotiationStatus::NEGOTIATED); + } + + SECTION("Downgrade case") + { + // Check initial state + CHECK(vn.status() == QUICVersionNegotiationStatus::NOT_NEGOTIATED); + + // Negotiate version + packet_factory.set_version(QUIC_EXERCISE_VERSION); + QUICPacketUPtr initial_packet = + packet_factory.create_initial_packet({}, {}, 0, std::move(dummy_payload), dummy_payload_len, true, false, true); + + REQUIRE(initial_packet != nullptr); + vn.negotiate(*initial_packet); + CHECK(vn.status() == QUICVersionNegotiationStatus::NOT_NEGOTIATED); + } +} + +TEST_CASE("QUICVersionNegotiator - Client Side", "[quic]") +{ + MockQUICPacketProtectionKeyInfo pp_key_info; + pp_key_info.set_encryption_key_available(QUICKeyPhase::INITIAL); + + QUICPacketFactory packet_factory(pp_key_info); + QUICVersionNegotiator vn; + ats_unique_buf dummy_payload = ats_unique_malloc(128); + size_t dummy_payload_len = 128; + + SECTION("Normal case") + { + // Check initial state + CHECK(vn.status() == QUICVersionNegotiationStatus::NOT_NEGOTIATED); + + // No Version Negotiation packet from server + } + + SECTION("Negotiation case") + { + // Check initial state + CHECK(vn.status() == QUICVersionNegotiationStatus::NOT_NEGOTIATED); + + // Negotiate version + packet_factory.set_version(QUIC_EXERCISE_VERSION); + QUICPacketUPtr initial_packet = + packet_factory.create_initial_packet({}, {}, 0, std::move(dummy_payload), dummy_payload_len, true, false, true); + REQUIRE(initial_packet != nullptr); + + // Server send VN packet based on Initial packet + QUICPacketUPtr vn_packet = + packet_factory.create_version_negotiation_packet(initial_packet->source_cid(), initial_packet->destination_cid()); + REQUIRE(vn_packet != nullptr); + + // Negotiate version + vn.negotiate(*vn_packet); + CHECK(vn.status() == QUICVersionNegotiationStatus::NEGOTIATED); + CHECK(vn.negotiated_version() == QUIC_SUPPORTED_VERSIONS[0]); + } +} diff --git a/iocore/net/test_I_UDPNet.cc b/iocore/net/test_I_UDPNet.cc index e74be1d9473..2f57cf6f8c6 100644 --- a/iocore/net/test_I_UDPNet.cc +++ b/iocore/net/test_I_UDPNet.cc @@ -61,7 +61,7 @@ EchoServer::start() addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); addr.sin_port = 0; - udpNet.UDPBind(static_cast(this), reinterpret_cast(&addr), 1024000, 1024000); + udpNet.UDPBind(static_cast(this), reinterpret_cast(&addr), 1048576, 1048576); return true; } diff --git a/lib/records/I_RecHttp.h b/lib/records/I_RecHttp.h index bb70597b542..0349ed1ce1f 100644 --- a/lib/records/I_RecHttp.h +++ b/lib/records/I_RecHttp.h @@ -143,6 +143,7 @@ extern SessionProtocolSet HTTP_PROTOCOL_SET; extern SessionProtocolSet HTTP2_PROTOCOL_SET; extern SessionProtocolSet DEFAULT_NON_TLS_SESSION_PROTOCOL_SET; extern SessionProtocolSet DEFAULT_TLS_SESSION_PROTOCOL_SET; +extern SessionProtocolSet DEFAULT_QUIC_SESSION_PROTOCOL_SET; const char *RecNormalizeProtoTag(const char *tag); @@ -236,7 +237,8 @@ struct HttpProxyPort { TRANSPORT_COMPRESSED, ///< Compressed HTTP. TRANSPORT_BLIND_TUNNEL, ///< Blind tunnel (no processing). TRANSPORT_SSL, ///< SSL connection. - TRANSPORT_PLUGIN /// < Protocol plugin connection + TRANSPORT_PLUGIN, /// < Protocol plugin connection + TRANSPORT_QUIC, ///< SSL connection. }; int m_fd; ///< Pre-opened file descriptor if present. @@ -281,6 +283,9 @@ struct HttpProxyPort { /// Check for SSL port. bool isSSL() const; + /// Check for QUIC port. + bool isQUIC() const; + /// Check for SSL port. bool isPlugin() const; @@ -307,6 +312,15 @@ struct HttpProxyPort { /// @return @c true if any global port is an SSL port. static bool hasSSL(); + /// Check for QUIC ports. + /// @return @c true if any port in @a ports is an QUIC port. + static bool hasQUIC(Group const &ports ///< Ports to check. + ); + + /// Check for QUIC ports. + /// @return @c true if any global port is an QUIC port. + static bool hasQUIC(); + /** Load all relevant configuration data. This is hardwired to look up the appropriate values in the @@ -398,6 +412,7 @@ struct HttpProxyPort { static const char *const OPT_TRANSPARENT_FULL; ///< Full transparency. static const char *const OPT_TRANSPARENT_PASSTHROUGH; ///< Pass-through non-HTTP. static const char *const OPT_SSL; ///< SSL (experimental) + static const char *const OPT_QUIC; ///< QUIC (experimental) static const char *const OPT_PROXY_PROTO; ///< Proxy Protocol static const char *const OPT_PLUGIN; ///< Protocol Plugin handle (experimental) static const char *const OPT_BLIND_TUNNEL; ///< Blind tunnel. @@ -432,6 +447,11 @@ HttpProxyPort::isSSL() const return TRANSPORT_SSL == m_type; } inline bool +HttpProxyPort::isQUIC() const +{ + return TRANSPORT_QUIC == m_type; +} +inline bool HttpProxyPort::isPlugin() const { return TRANSPORT_PLUGIN == m_type; @@ -474,6 +494,11 @@ HttpProxyPort::hasSSL() { return self::hasSSL(m_global); } +inline bool +HttpProxyPort::hasQUIC() +{ + return self::hasQUIC(m_global); +} inline const HttpProxyPort * HttpProxyPort::findHttp(uint16_t family) { diff --git a/lib/records/RecHttp.cc b/lib/records/RecHttp.cc index 02c5d19ad68..d8ce6f6fd2d 100644 --- a/lib/records/RecHttp.cc +++ b/lib/records/RecHttp.cc @@ -38,39 +38,46 @@ SessionProtocolNameRegistry globalSessionProtocolNameRegistry; These are also used for NPN setup. */ -const char *const TS_ALPN_PROTOCOL_HTTP_0_9 = IP_PROTO_TAG_HTTP_0_9.data(); -const char *const TS_ALPN_PROTOCOL_HTTP_1_0 = IP_PROTO_TAG_HTTP_1_0.data(); -const char *const TS_ALPN_PROTOCOL_HTTP_1_1 = IP_PROTO_TAG_HTTP_1_1.data(); -const char *const TS_ALPN_PROTOCOL_HTTP_2_0 = IP_PROTO_TAG_HTTP_2_0.data(); +const char *const TS_ALPN_PROTOCOL_HTTP_0_9 = IP_PROTO_TAG_HTTP_0_9.data(); +const char *const TS_ALPN_PROTOCOL_HTTP_1_0 = IP_PROTO_TAG_HTTP_1_0.data(); +const char *const TS_ALPN_PROTOCOL_HTTP_1_1 = IP_PROTO_TAG_HTTP_1_1.data(); +const char *const TS_ALPN_PROTOCOL_HTTP_2_0 = IP_PROTO_TAG_HTTP_2_0.data(); +const char *const TS_ALPN_PROTOCOL_HTTP_3 = IP_PROTO_TAG_HTTP_3.data(); +const char *const TS_ALPN_PROTOCOL_HTTP_QUIC = IP_PROTO_TAG_HTTP_QUIC.data(); const char *const TS_ALPN_PROTOCOL_GROUP_HTTP = "http"; const char *const TS_ALPN_PROTOCOL_GROUP_HTTP2 = "http2"; -const char *const TS_PROTO_TAG_HTTP_1_0 = TS_ALPN_PROTOCOL_HTTP_1_0; -const char *const TS_PROTO_TAG_HTTP_1_1 = TS_ALPN_PROTOCOL_HTTP_1_1; -const char *const TS_PROTO_TAG_HTTP_2_0 = TS_ALPN_PROTOCOL_HTTP_2_0; -const char *const TS_PROTO_TAG_TLS_1_3 = IP_PROTO_TAG_TLS_1_3.data(); -const char *const TS_PROTO_TAG_TLS_1_2 = IP_PROTO_TAG_TLS_1_2.data(); -const char *const TS_PROTO_TAG_TLS_1_1 = IP_PROTO_TAG_TLS_1_1.data(); -const char *const TS_PROTO_TAG_TLS_1_0 = IP_PROTO_TAG_TLS_1_0.data(); -const char *const TS_PROTO_TAG_TCP = IP_PROTO_TAG_TCP.data(); -const char *const TS_PROTO_TAG_UDP = IP_PROTO_TAG_UDP.data(); -const char *const TS_PROTO_TAG_IPV4 = IP_PROTO_TAG_IPV4.data(); -const char *const TS_PROTO_TAG_IPV6 = IP_PROTO_TAG_IPV6.data(); +const char *const TS_PROTO_TAG_HTTP_1_0 = TS_ALPN_PROTOCOL_HTTP_1_0; +const char *const TS_PROTO_TAG_HTTP_1_1 = TS_ALPN_PROTOCOL_HTTP_1_1; +const char *const TS_PROTO_TAG_HTTP_2_0 = TS_ALPN_PROTOCOL_HTTP_2_0; +const char *const TS_PROTO_TAG_HTTP_3 = TS_ALPN_PROTOCOL_HTTP_3; +const char *const TS_PROTO_TAG_HTTP_QUIC = TS_ALPN_PROTOCOL_HTTP_QUIC; +const char *const TS_PROTO_TAG_TLS_1_3 = IP_PROTO_TAG_TLS_1_3.data(); +const char *const TS_PROTO_TAG_TLS_1_2 = IP_PROTO_TAG_TLS_1_2.data(); +const char *const TS_PROTO_TAG_TLS_1_1 = IP_PROTO_TAG_TLS_1_1.data(); +const char *const TS_PROTO_TAG_TLS_1_0 = IP_PROTO_TAG_TLS_1_0.data(); +const char *const TS_PROTO_TAG_TCP = IP_PROTO_TAG_TCP.data(); +const char *const TS_PROTO_TAG_UDP = IP_PROTO_TAG_UDP.data(); +const char *const TS_PROTO_TAG_IPV4 = IP_PROTO_TAG_IPV4.data(); +const char *const TS_PROTO_TAG_IPV6 = IP_PROTO_TAG_IPV6.data(); std::unordered_set TSProtoTags; // Precomputed indices for ease of use. -int TS_ALPN_PROTOCOL_INDEX_HTTP_0_9 = SessionProtocolNameRegistry::INVALID; -int TS_ALPN_PROTOCOL_INDEX_HTTP_1_0 = SessionProtocolNameRegistry::INVALID; -int TS_ALPN_PROTOCOL_INDEX_HTTP_1_1 = SessionProtocolNameRegistry::INVALID; -int TS_ALPN_PROTOCOL_INDEX_HTTP_2_0 = SessionProtocolNameRegistry::INVALID; +int TS_ALPN_PROTOCOL_INDEX_HTTP_0_9 = SessionProtocolNameRegistry::INVALID; +int TS_ALPN_PROTOCOL_INDEX_HTTP_1_0 = SessionProtocolNameRegistry::INVALID; +int TS_ALPN_PROTOCOL_INDEX_HTTP_1_1 = SessionProtocolNameRegistry::INVALID; +int TS_ALPN_PROTOCOL_INDEX_HTTP_2_0 = SessionProtocolNameRegistry::INVALID; +int TS_ALPN_PROTOCOL_INDEX_HTTP_3 = SessionProtocolNameRegistry::INVALID; +int TS_ALPN_PROTOCOL_INDEX_HTTP_QUIC = SessionProtocolNameRegistry::INVALID; // Predefined protocol sets for ease of use. SessionProtocolSet HTTP_PROTOCOL_SET; SessionProtocolSet HTTP2_PROTOCOL_SET; SessionProtocolSet DEFAULT_NON_TLS_SESSION_PROTOCOL_SET; SessionProtocolSet DEFAULT_TLS_SESSION_PROTOCOL_SET; +SessionProtocolSet DEFAULT_QUIC_SESSION_PROTOCOL_SET; static bool mptcp_supported() @@ -174,6 +181,7 @@ const char *const HttpProxyPort::OPT_PLUGIN = "plugin"; const char *const HttpProxyPort::OPT_BLIND_TUNNEL = "blind"; const char *const HttpProxyPort::OPT_COMPRESSED = "compressed"; const char *const HttpProxyPort::OPT_MPTCP = "mptcp"; +const char *const HttpProxyPort::OPT_QUIC = "quic"; // File local constants. namespace @@ -209,6 +217,18 @@ HttpProxyPort::hasSSL(Group const &ports) return std::any_of(ports.begin(), ports.end(), [](HttpProxyPort const &port) { return port.isSSL(); }); } +bool +HttpProxyPort::hasQUIC(Group const &ports) +{ + bool zret = false; + for (int i = 0, n = ports.size(); i < n && !zret; ++i) { + if (ports[i].isQUIC()) { + zret = true; + } + } + return zret; +} + const HttpProxyPort * HttpProxyPort::findHttp(Group const &ports, uint16_t family) { @@ -375,6 +395,8 @@ HttpProxyPort::processOptions(const char *opts) af_set_p = true; } else if (0 == strcasecmp(OPT_SSL, item)) { m_type = TRANSPORT_SSL; + } else if (0 == strcasecmp(OPT_QUIC, item)) { + m_type = TRANSPORT_QUIC; } else if (0 == strcasecmp(OPT_PLUGIN, item)) { m_type = TRANSPORT_PLUGIN; } else if (0 == strcasecmp(OPT_PROXY_PROTO, item)) { @@ -456,7 +478,13 @@ HttpProxyPort::processOptions(const char *opts) // Set the default session protocols. if (!sp_set_p) { - m_session_protocol_preference = this->isSSL() ? DEFAULT_TLS_SESSION_PROTOCOL_SET : DEFAULT_NON_TLS_SESSION_PROTOCOL_SET; + if (this->isSSL()) { + m_session_protocol_preference = DEFAULT_TLS_SESSION_PROTOCOL_SET; + } else if (this->isQUIC()) { + m_session_protocol_preference = DEFAULT_QUIC_SESSION_PROTOCOL_SET; + } else { + m_session_protocol_preference = DEFAULT_NON_TLS_SESSION_PROTOCOL_SET; + } } return zret; @@ -570,6 +598,8 @@ HttpProxyPort::print(char *out, size_t n) zret += snprintf(out + zret, n - zret, ":%s", OPT_BLIND_TUNNEL); } else if (TRANSPORT_SSL == m_type) { zret += snprintf(out + zret, n - zret, ":%s", OPT_SSL); + } else if (TRANSPORT_QUIC == m_type) { + zret += snprintf(out + zret, n - zret, ":%s", OPT_QUIC); } else if (TRANSPORT_PLUGIN == m_type) { zret += snprintf(out + zret, n - zret, ":%s", OPT_PLUGIN); } else if (TRANSPORT_COMPRESSED == m_type) { @@ -617,6 +647,8 @@ HttpProxyPort::print(char *out, size_t n) sp_set.markOut(DEFAULT_NON_TLS_SESSION_PROTOCOL_SET); } else if (sp_set == DEFAULT_TLS_SESSION_PROTOCOL_SET && this->isSSL()) { sp_set.markOut(DEFAULT_TLS_SESSION_PROTOCOL_SET); + } else if (sp_set == DEFAULT_QUIC_SESSION_PROTOCOL_SET && this->isQUIC()) { + sp_set.markOut(DEFAULT_QUIC_SESSION_PROTOCOL_SET); } // pull out groups. @@ -671,10 +703,12 @@ void ts_session_protocol_well_known_name_indices_init() { // register all the well known protocols and get the indices set. - TS_ALPN_PROTOCOL_INDEX_HTTP_0_9 = globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_0_9}); - TS_ALPN_PROTOCOL_INDEX_HTTP_1_0 = globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_1_0}); - TS_ALPN_PROTOCOL_INDEX_HTTP_1_1 = globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_1_1}); - TS_ALPN_PROTOCOL_INDEX_HTTP_2_0 = globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_2_0}); + TS_ALPN_PROTOCOL_INDEX_HTTP_0_9 = globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_0_9}); + TS_ALPN_PROTOCOL_INDEX_HTTP_1_0 = globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_1_0}); + TS_ALPN_PROTOCOL_INDEX_HTTP_1_1 = globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_1_1}); + TS_ALPN_PROTOCOL_INDEX_HTTP_2_0 = globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_2_0}); + TS_ALPN_PROTOCOL_INDEX_HTTP_3 = globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_3}); + TS_ALPN_PROTOCOL_INDEX_HTTP_QUIC = globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_QUIC}); // Now do the predefined protocol sets. HTTP_PROTOCOL_SET.markIn(TS_ALPN_PROTOCOL_INDEX_HTTP_0_9); @@ -683,12 +717,19 @@ ts_session_protocol_well_known_name_indices_init() HTTP2_PROTOCOL_SET.markIn(TS_ALPN_PROTOCOL_INDEX_HTTP_2_0); DEFAULT_TLS_SESSION_PROTOCOL_SET.markAllIn(); + DEFAULT_TLS_SESSION_PROTOCOL_SET.markOut(TS_ALPN_PROTOCOL_INDEX_HTTP_3); + DEFAULT_TLS_SESSION_PROTOCOL_SET.markOut(TS_ALPN_PROTOCOL_INDEX_HTTP_QUIC); + + DEFAULT_QUIC_SESSION_PROTOCOL_SET.markIn(TS_ALPN_PROTOCOL_INDEX_HTTP_3); + DEFAULT_QUIC_SESSION_PROTOCOL_SET.markIn(TS_ALPN_PROTOCOL_INDEX_HTTP_QUIC); DEFAULT_NON_TLS_SESSION_PROTOCOL_SET = HTTP_PROTOCOL_SET; TSProtoTags.insert(TS_PROTO_TAG_HTTP_1_0); TSProtoTags.insert(TS_PROTO_TAG_HTTP_1_1); TSProtoTags.insert(TS_PROTO_TAG_HTTP_2_0); + TSProtoTags.insert(TS_PROTO_TAG_HTTP_3); + TSProtoTags.insert(TS_PROTO_TAG_HTTP_QUIC); TSProtoTags.insert(TS_PROTO_TAG_TLS_1_3); TSProtoTags.insert(TS_PROTO_TAG_TLS_1_2); TSProtoTags.insert(TS_PROTO_TAG_TLS_1_1); diff --git a/mgmt/RecordsConfig.cc b/mgmt/RecordsConfig.cc index 9491cddff2a..c52c800ea3c 100644 --- a/mgmt/RecordsConfig.cc +++ b/mgmt/RecordsConfig.cc @@ -1292,6 +1292,111 @@ static const RecordElement RecordsConfig[] = {RECT_CONFIG, "proxy.config.http2.max_settings_per_minute", RECD_INT, "14", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL} , + //############ + //# + //# HTTP/3 global configuration. + //# + //############ + {RECT_CONFIG, "proxy.config.http3.header_table_size", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.http3.max_header_list_size", RECD_INT, "4096", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.http3.qpack_blocked_streams", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.http3.num_placeholders", RECD_INT, "100", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.http3.max_settings", RECD_INT, "10", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL} + , + + + //############ + //# + //# QUIC global configuration. + //# + //############ + {RECT_CONFIG, "proxy.config.quic.instance_id", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.quic.connection_table.size", RECD_INT, "65521", RECU_RESTART_TS, RR_NULL, RECC_INT, "[1-536870909]", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.quic.num_alt_connection_ids", RECD_INT, "8", RECU_RESTART_TS, RR_NULL, RECC_INT, "[8-256]", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.quic.server.stateless_retry_enabled", RECD_INT, "0", RECU_RESTART_TS, RR_NULL, RECC_INT, "[0-1]", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.quic.client.vn_exercise_enabled", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_INT, "[0-1]", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.quic.client.cm_exercise_enabled", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_INT, "[0-1]", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.quic.server.supported_groups", RECD_STRING, "P-256:X25519:P-384:P-521" , RECU_RESTART_TS, RR_NULL, RECC_NULL, nullptr, RECA_NULL} + , + {RECT_CONFIG, "proxy.config.quic.client.supported_groups", RECD_STRING, "P-256:X25519:P-384:P-521" , RECU_RESTART_TS, RR_NULL, RECC_NULL, nullptr, RECA_NULL} + , + {RECT_CONFIG, "proxy.config.quic.client.session_file", RECD_STRING, nullptr , RECU_RESTART_TS, RR_NULL, RECC_NULL, nullptr, RECA_NULL} + , + {RECT_CONFIG, "proxy.config.quic.client.keylog_file", RECD_STRING, nullptr , RECU_RESTART_TS, RR_NULL, RECC_NULL, nullptr, RECA_NULL} + , + // Transport Parameters + {RECT_CONFIG, "proxy.config.quic.no_activity_timeout_in", RECD_INT, "30000", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.quic.no_activity_timeout_out", RECD_INT, "30000", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.quic.preferred_address_ipv4", RECD_STRING, nullptr , RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL} + , + {RECT_CONFIG, "proxy.config.quic.preferred_address_ipv6", RECD_STRING, nullptr , RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL} + , + {RECT_CONFIG, "proxy.config.quic.initial_max_data_in", RECD_INT, "65536", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.quic.initial_max_data_out", RECD_INT, "65536", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.quic.initial_max_stream_data_bidi_local_in", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.quic.initial_max_stream_data_bidi_local_out", RECD_INT, "4096", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.quic.initial_max_stream_data_bidi_remote_in", RECD_INT, "4096", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.quic.initial_max_stream_data_bidi_remote_out", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.quic.initial_max_stream_data_uni_in", RECD_INT, "4096", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.quic.initial_max_stream_data_uni_out", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.quic.initial_max_streams_bidi_in", RECD_INT, "100", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.quic.initial_max_streams_bidi_out", RECD_INT, "100", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.quic.initial_max_streams_uni_in", RECD_INT, "100", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.quic.initial_max_streams_uni_out", RECD_INT, "100", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.quic.ack_delay_exponent_in", RECD_INT, "3", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.quic.ack_delay_exponent_out", RECD_INT, "3", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.quic.max_ack_delay_in", RECD_INT, "25", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.quic.max_ack_delay_out", RECD_INT, "25", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL} + , + // Constants of Loss Detection + {RECT_CONFIG, "proxy.config.quic.loss_detection.packet_threshold", RECD_INT, "3", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.quic.loss_detection.time_threshold", RECD_FLOAT, "1.25", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.quic.loss_detection.granularity", RECD_INT, "1", RECU_DYNAMIC, RR_NULL, RECC_STR, "[0-1]", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.quic.loss_detection.initial_rtt", RECD_INT, "100", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL} + , + + // Constatns of Congestion Control + {RECT_CONFIG, "proxy.config.quic.congestion_control.max_datagram_size", RECD_INT, "1200", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.quic.congestion_control.initial_window_scale", RECD_INT, "10", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.quic.congestion_control.minimum_window_scale", RECD_INT, "2", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.quic.congestion_control.loss_reduction_factor", RECD_FLOAT, "0.5", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[\\.0-9]+$", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.quic.congestion_control.persistent_congestion_threshold", RECD_INT, "2", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[\\.0-9]+$", RECA_NULL} + , + //# Add LOCAL Records Here {RECT_LOCAL, "proxy.local.incoming_ip_to_bind", RECD_STRING, nullptr, RECU_NULL, RR_NULL, RECC_NULL, nullptr, RECA_NULL} , diff --git a/proxy/Makefile.am b/proxy/Makefile.am index 30afed79735..5f8d3b11f1a 100644 --- a/proxy/Makefile.am +++ b/proxy/Makefile.am @@ -19,6 +19,9 @@ include $(top_srcdir)/build/tidy.mk SUBDIRS = hdrs shared http http2 logging +if ENABLE_QUIC +SUBDIRS += http3 +endif noinst_LIBRARIES = libproxy.a @@ -27,6 +30,8 @@ AM_CPPFLAGS += \ -I$(abs_top_srcdir)/include \ -I$(abs_top_srcdir)/lib \ -I$(abs_srcdir)/http \ + -I$(abs_srcdir)/http2 \ + -I$(abs_srcdir)/http3 \ -I$(abs_srcdir)/logging \ -I$(abs_srcdir)/http/remap \ -I$(abs_srcdir)/hdrs \ diff --git a/proxy/http/HttpProxyServerMain.cc b/proxy/http/HttpProxyServerMain.cc index 3e4c1b870cf..70f17b77427 100644 --- a/proxy/http/HttpProxyServerMain.cc +++ b/proxy/http/HttpProxyServerMain.cc @@ -39,6 +39,10 @@ #include "http2/Http2SessionAccept.h" #include "HttpConnectionCount.h" #include "HttpProxyServerMain.h" +#if TS_USE_QUIC == 1 +#include "P_QUICNextProtocolAccept.h" +#include "http3/Http3SessionAccept.h" +#endif #include @@ -252,6 +256,23 @@ MakeHttpProxyAcceptor(HttpProxyAcceptor &acceptor, HttpProxyPort &port, unsigned ssl_plugin_acceptors.push(ssl); ssl->proxyPort = &port; acceptor._accept = ssl; +#if TS_USE_QUIC == 1 + } else if (port.isQUIC()) { + QUICNextProtocolAccept *quic = new QUICNextProtocolAccept(); + + // HTTP/3 + if (port.m_session_protocol_preference.contains(TS_ALPN_PROTOCOL_INDEX_HTTP_3)) { + quic->registerEndpoint(TS_ALPN_PROTOCOL_HTTP_3, new Http3SessionAccept(accept_opt)); + } + + // HTTP/0.9 over QUIC (for interop only, will be removed) + if (port.m_session_protocol_preference.contains(TS_ALPN_PROTOCOL_INDEX_HTTP_QUIC)) { + quic->registerEndpoint(TS_ALPN_PROTOCOL_HTTP_QUIC, new Http3SessionAccept(accept_opt)); + } + + quic->proxyPort = &port; + acceptor._accept = quic; +#endif } else { acceptor._accept = probe; } @@ -344,6 +365,12 @@ start_HttpProxyServer() if (nullptr == sslNetProcessor.main_accept(acceptor._accept, port.m_fd, acceptor._net_opt)) { return; } +#if TS_USE_QUIC == 1 + } else if (port.isQUIC()) { + if (nullptr == quic_NetProcessor.main_accept(acceptor._accept, port.m_fd, acceptor._net_opt)) { + return; + } +#endif } else if (!port.isPlugin()) { if (nullptr == netProcessor.main_accept(acceptor._accept, port.m_fd, acceptor._net_opt)) { return; diff --git a/proxy/http/Makefile.am b/proxy/http/Makefile.am index 7b5abd7707d..31a06c1b086 100644 --- a/proxy/http/Makefile.am +++ b/proxy/http/Makefile.am @@ -32,6 +32,7 @@ AM_CPPFLAGS += \ -I$(abs_top_srcdir)/proxy/http/remap \ -I$(abs_top_srcdir)/proxy/logging \ -I$(abs_top_srcdir)/proxy/http2 \ + -I$(abs_top_srcdir)/proxy/http3 \ $(TS_INCLUDES) noinst_HEADERS = HttpProxyServerMain.h diff --git a/proxy/http3/Http09App.cc b/proxy/http3/Http09App.cc new file mode 100644 index 00000000000..0b317ff0816 --- /dev/null +++ b/proxy/http3/Http09App.cc @@ -0,0 +1,115 @@ +/** @file + * + * A brief file description + * + * @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 "Http09App.h" + +#include "tscore/ink_resolver.h" + +#include "P_Net.h" +#include "P_VConnection.h" +#include "QUICDebugNames.h" + +#include "Http3Session.h" +#include "Http3Transaction.h" + +static constexpr char debug_tag[] = "quic_simple_app"; +static constexpr char debug_tag_v[] = "v_quic_simple_app"; + +Http09App::Http09App(QUICNetVConnection *client_vc, IpAllow::ACL &&session_acl, const HttpSessionAccept::Options &options) + : QUICApplication(client_vc) +{ + this->_ssn = new Http09Session(client_vc); + this->_ssn->acl = std::move(session_acl); + // TODO: avoid const cast + this->_ssn->host_res_style = + ats_host_res_from(client_vc->get_remote_addr()->sa_family, const_cast(options.host_res_preference)); + this->_ssn->outbound_ip4 = options.outbound_ip4; + this->_ssn->outbound_ip6 = options.outbound_ip6; + this->_ssn->outbound_port = options.outbound_port; + this->_ssn->new_connection(client_vc, nullptr, nullptr); + + this->_qc->stream_manager()->set_default_application(this); + + SET_HANDLER(&Http09App::main_event_handler); +} + +Http09App::~Http09App() +{ + delete this->_ssn; +} + +int +Http09App::main_event_handler(int event, Event *data) +{ + Debug(debug_tag_v, "[%s] %s (%d)", this->_qc->cids().data(), get_vc_event_name(event), event); + + VIO *vio = reinterpret_cast(data); + QUICStreamIO *stream_io = this->_find_stream_io(vio); + + if (stream_io == nullptr) { + Debug(debug_tag, "[%s] Unknown Stream", this->_qc->cids().data()); + return -1; + } + + QUICStreamId stream_id = stream_io->stream_id(); + Http09Transaction *txn = static_cast(this->_ssn->get_transaction(stream_id)); + + uint8_t dummy; + switch (event) { + case VC_EVENT_READ_READY: + case VC_EVENT_READ_COMPLETE: + if (!stream_io->is_bidirectional()) { + // FIXME Ignore unidirectional streams for now + break; + } + if (stream_io->peek(&dummy, 1)) { + if (txn == nullptr) { + txn = new Http09Transaction(this->_ssn, stream_io); + SCOPED_MUTEX_LOCK(lock, txn->mutex, this_ethread()); + + txn->new_transaction(); + } else { + SCOPED_MUTEX_LOCK(lock, txn->mutex, this_ethread()); + txn->handleEvent(event); + } + } + break; + case VC_EVENT_WRITE_READY: + case VC_EVENT_WRITE_COMPLETE: + if (txn != nullptr) { + SCOPED_MUTEX_LOCK(lock, txn->mutex, this_ethread()); + txn->handleEvent(event); + } + break; + case VC_EVENT_EOS: + case VC_EVENT_ERROR: + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: + ink_assert(false); + break; + default: + break; + } + + return EVENT_CONT; +} diff --git a/proxy/http3/Http09App.h b/proxy/http3/Http09App.h new file mode 100644 index 00000000000..ab35399e2c9 --- /dev/null +++ b/proxy/http3/Http09App.h @@ -0,0 +1,51 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include "IPAllow.h" + +#include "HttpSessionAccept.h" + +#include "QUICApplication.h" + +class QUICNetVConnection; +class Http09Session; + +/** + * @brief A simple multi-streamed application. + * @detail Response to simple HTTP/0.9 GETs + * This will be removed when HTTP/0.9 over QUIC support is dropped + * + */ +class Http09App : public QUICApplication +{ +public: + Http09App(QUICNetVConnection *client_vc, IpAllow::ACL &&session_acl, const HttpSessionAccept::Options &options); + ~Http09App(); + + int main_event_handler(int event, Event *data); + +private: + Http09Session *_ssn = nullptr; +}; diff --git a/proxy/http3/Http3.cc b/proxy/http3/Http3.cc new file mode 100644 index 00000000000..ce885585070 --- /dev/null +++ b/proxy/http3/Http3.cc @@ -0,0 +1,38 @@ +/** @file + * + * A brief file description + * + * @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 "Http3.h" + +// Default values of settings defined by specs (draft-17) +const uint32_t HTTP3_DEFAULT_HEADER_TABLE_SIZE = 0; +const uint32_t HTTP3_DEFAULT_MAX_HEADER_LIST_SIZE = UINT32_MAX; +const uint32_t HTTP3_DEFAULT_QPACK_BLOCKED_STREAMS = 0; +const uint32_t HTTP3_DEFAULT_NUM_PLACEHOLDERS = 0; + +RecRawStatBlock *http3_rsb; + +void +Http3::init() +{ + http3_rsb = RecAllocateRawStatBlock(static_cast(HTTP3_N_STATS)); +} diff --git a/proxy/http3/Http3.h b/proxy/http3/Http3.h new file mode 100644 index 00000000000..f390e06b4bd --- /dev/null +++ b/proxy/http3/Http3.h @@ -0,0 +1,46 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include "tscore/ink_defs.h" +#include "records/I_RecDefs.h" +#include "records/I_RecProcess.h" + +extern const uint32_t HTTP3_DEFAULT_HEADER_TABLE_SIZE; +extern const uint32_t HTTP3_DEFAULT_MAX_HEADER_LIST_SIZE; +extern const uint32_t HTTP3_DEFAULT_QPACK_BLOCKED_STREAMS; +extern const uint32_t HTTP3_DEFAULT_NUM_PLACEHOLDERS; + +extern RecRawStatBlock *http3_rsb; // Container for statistics. + +class Http3 +{ +public: + static void init(); +}; + +// Statistics +enum { + HTTP3_N_STATS // Terminal counter, NOT A STAT INDEX. +}; diff --git a/proxy/http3/Http3App.cc b/proxy/http3/Http3App.cc new file mode 100644 index 00000000000..946a73d7a59 --- /dev/null +++ b/proxy/http3/Http3App.cc @@ -0,0 +1,400 @@ +/** @file + * + * A brief file description + * + * @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 "Http3App.h" + +#include "tscore/ink_resolver.h" + +#include "P_Net.h" +#include "P_VConnection.h" + +#include "Http3.h" +#include "Http3Config.h" +#include "Http3DebugNames.h" +#include "Http3Session.h" +#include "Http3Transaction.h" + +static constexpr char debug_tag[] = "http3"; +static constexpr char debug_tag_v[] = "v_http3"; + +Http3App::Http3App(QUICNetVConnection *client_vc, IpAllow::ACL &&session_acl, const HttpSessionAccept::Options &options) + : QUICApplication(client_vc) +{ + this->_ssn = new Http3Session(client_vc); + this->_ssn->acl = std::move(session_acl); + // TODO: avoid const cast + this->_ssn->host_res_style = + ats_host_res_from(client_vc->get_remote_addr()->sa_family, const_cast(options.host_res_preference)); + this->_ssn->outbound_ip4 = options.outbound_ip4; + this->_ssn->outbound_ip6 = options.outbound_ip6; + this->_ssn->outbound_port = options.outbound_port; + + this->_ssn->new_connection(client_vc, nullptr, nullptr); + + this->_qc->stream_manager()->set_default_application(this); + + this->_settings_handler = new Http3SettingsHandler(this->_ssn); + this->_control_stream_dispatcher.add_handler(this->_settings_handler); + + this->_settings_framer = new Http3SettingsFramer(client_vc->get_context()); + this->_control_stream_collector.add_generator(this->_settings_framer); + + SET_HANDLER(&Http3App::main_event_handler); +} + +Http3App::~Http3App() +{ + delete this->_ssn; + delete this->_settings_handler; + delete this->_settings_framer; +} + +void +Http3App::start() +{ + QUICStreamId stream_id; + QUICConnectionErrorUPtr error; + + error = this->create_uni_stream(stream_id, Http3StreamType::CONTROL); + if (error == nullptr) { + this->_local_control_stream = this->_find_stream_io(stream_id); + this->_handle_uni_stream_on_write_ready(VC_EVENT_WRITE_READY, this->_local_control_stream); + } + + // TODO: Open uni streams for QPACK when dynamic table is used + // error = this->create_uni_stream(stream_id, Http3StreamType::QPACK_ENCODER); + // if (error == nullptr) { + // this->_handle_uni_stream_on_write_ready(VC_EVENT_WRITE_READY, this->_find_stream_io(stream_id)); + // } + + // error = this->create_uni_stream(stream_id, Http3StreamType::QPACK_DECODER); + // if (error == nullptr) { + // this->_handle_uni_stream_on_write_ready(VC_EVENT_WRITE_READY, this->_find_stream_io(stream_id)); + // } +} + +int +Http3App::main_event_handler(int event, Event *data) +{ + Debug(debug_tag_v, "[%s] %s (%d)", this->_qc->cids().data(), get_vc_event_name(event), event); + + VIO *vio = reinterpret_cast(data); + QUICStreamIO *stream_io = this->_find_stream_io(vio); + + if (stream_io == nullptr) { + Debug(debug_tag, "[%s] Unknown Stream", this->_qc->cids().data()); + return -1; + } + + switch (event) { + case VC_EVENT_READ_READY: + case VC_EVENT_READ_COMPLETE: + if (stream_io->is_bidirectional()) { + this->_handle_bidi_stream_on_read_ready(event, stream_io); + } else { + this->_handle_uni_stream_on_read_ready(event, stream_io); + } + break; + case VC_EVENT_WRITE_READY: + case VC_EVENT_WRITE_COMPLETE: + if (stream_io->is_bidirectional()) { + this->_handle_bidi_stream_on_write_ready(event, stream_io); + } else { + this->_handle_uni_stream_on_write_ready(event, stream_io); + } + break; + case VC_EVENT_EOS: + if (stream_io->is_bidirectional()) { + this->_handle_bidi_stream_on_eos(event, stream_io); + } else { + this->_handle_uni_stream_on_eos(event, stream_io); + } + break; + case VC_EVENT_ERROR: + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: + ink_assert(false); + break; + default: + break; + } + + return EVENT_CONT; +} + +QUICConnectionErrorUPtr +Http3App::create_uni_stream(QUICStreamId &new_stream_id, Http3StreamType type) +{ + QUICConnectionErrorUPtr error = this->_qc->stream_manager()->create_uni_stream(new_stream_id); + + if (error == nullptr) { + QUICStreamIO *stream_io = this->_find_stream_io(new_stream_id); + + uint8_t buf[] = {static_cast(type)}; + stream_io->write(buf, sizeof(uint8_t)); + + this->_local_uni_stream_map.insert(std::make_pair(new_stream_id, type)); + + Debug("http3", "[%" PRIu64 "] %s stream is created", new_stream_id, Http3DebugNames::stream_type(type)); + } else { + Debug("http3", "Could not creat %s stream", Http3DebugNames::stream_type(type)); + } + + return error; +} + +void +Http3App::_handle_uni_stream_on_read_ready(int /* event */, QUICStreamIO *stream_io) +{ + Http3StreamType type; + auto it = this->_remote_uni_stream_map.find(stream_io->stream_id()); + if (it == this->_remote_uni_stream_map.end()) { + // Set uni stream suitable app (HTTP/3 or QPACK) by stream type + uint8_t buf; + stream_io->read(&buf, 1); + type = Http3Stream::type(&buf); + + Debug("http3", "[%d] %s stream is opened", stream_io->stream_id(), Http3DebugNames::stream_type(type)); + + if (type == Http3StreamType::CONTROL) { + if (this->_remote_control_stream) { + // TODO: make error + } + this->_remote_control_stream = stream_io; + } + + this->_remote_uni_stream_map.insert(std::make_pair(stream_io->stream_id(), type)); + } else { + type = it->second; + } + + switch (type) { + case Http3StreamType::CONTROL: + case Http3StreamType::PUSH: { + uint64_t nread = 0; + this->_control_stream_dispatcher.on_read_ready(*stream_io, nread); + // TODO: when PUSH comes from client, send stream error with HTTP_WRONG_STREAM_DIRECTION + break; + } + case Http3StreamType::QPACK_ENCODER: + case Http3StreamType::QPACK_DECODER: { + this->_set_qpack_stream(type, stream_io); + } + case Http3StreamType::UNKOWN: + default: + // TODO: just ignore or trigger QUIC STOP_SENDING frame with HTTP_UNKNOWN_STREAM_TYPE + break; + } +} + +void +Http3App::_handle_bidi_stream_on_read_ready(int event, QUICStreamIO *stream_io) +{ + uint8_t dummy; + if (stream_io->peek(&dummy, 1)) { + QUICStreamId stream_id = stream_io->stream_id(); + Http3Transaction *txn = static_cast(this->_ssn->get_transaction(stream_id)); + + if (txn == nullptr) { + txn = new Http3Transaction(this->_ssn, stream_io); + SCOPED_MUTEX_LOCK(lock, txn->mutex, this_ethread()); + + txn->new_transaction(); + } else { + SCOPED_MUTEX_LOCK(lock, txn->mutex, this_ethread()); + txn->handleEvent(event); + } + } +} + +void +Http3App::_handle_uni_stream_on_write_ready(int /* event */, QUICStreamIO *stream_io) +{ + auto it = this->_local_uni_stream_map.find(stream_io->stream_id()); + if (it == this->_local_uni_stream_map.end()) { + ink_abort("stream not found"); + return; + } + + switch (it->second) { + case Http3StreamType::CONTROL: { + size_t nwritten = 0; + this->_control_stream_collector.on_write_ready(stream_io, nwritten); + break; + } + case Http3StreamType::QPACK_ENCODER: + case Http3StreamType::QPACK_DECODER: { + this->_set_qpack_stream(it->second, stream_io); + } + case Http3StreamType::UNKOWN: + case Http3StreamType::PUSH: + default: + break; + } +} + +void +Http3App::_handle_bidi_stream_on_eos(int /* event */, QUICStreamIO *stream_io) +{ + // TODO: handle eos +} + +void +Http3App::_handle_uni_stream_on_eos(int /* event */, QUICStreamIO *stream_io) +{ + // TODO: handle eos +} + +void +Http3App::_set_qpack_stream(Http3StreamType type, QUICStreamIO *stream_io) +{ + // Change app to QPACK from Http3 + if (type == Http3StreamType::QPACK_ENCODER) { + if (this->_qc->direction() == NET_VCONNECTION_IN) { + this->_ssn->remote_qpack()->set_encoder_stream(stream_io); + } else { + this->_ssn->local_qpack()->set_encoder_stream(stream_io); + } + } else if (type == Http3StreamType::QPACK_DECODER) { + if (this->_qc->direction() == NET_VCONNECTION_IN) { + this->_ssn->local_qpack()->set_decoder_stream(stream_io); + } else { + this->_ssn->remote_qpack()->set_decoder_stream(stream_io); + } + } else { + ink_abort("unkown stream type"); + } +} + +void +Http3App::_handle_bidi_stream_on_write_ready(int event, QUICStreamIO *stream_io) +{ + QUICStreamId stream_id = stream_io->stream_id(); + Http3Transaction *txn = static_cast(this->_ssn->get_transaction(stream_id)); + if (txn != nullptr) { + SCOPED_MUTEX_LOCK(lock, txn->mutex, this_ethread()); + txn->handleEvent(event); + } +} + +// +// SETTINGS frame handler +// +std::vector +Http3SettingsHandler::interests() +{ + return {Http3FrameType::SETTINGS}; +} + +Http3ErrorUPtr +Http3SettingsHandler::handle_frame(std::shared_ptr frame) +{ + ink_assert(frame->type() == Http3FrameType::SETTINGS); + + const Http3SettingsFrame *settings_frame = dynamic_cast(frame.get()); + + if (!settings_frame) { + // make error + return Http3ErrorUPtr(new Http3NoError()); + } + + if (settings_frame->is_valid()) { + return settings_frame->get_error(); + } + + // TODO: Add length check: the maximum number of values are 2^62 - 1, but some fields have shorter maximum than it. + if (settings_frame->contains(Http3SettingsId::HEADER_TABLE_SIZE)) { + uint64_t header_table_size = settings_frame->get(Http3SettingsId::HEADER_TABLE_SIZE); + this->_session->remote_qpack()->update_max_table_size(header_table_size); + + Debug("http3", "SETTINGS_HEADER_TABLE_SIZE: %" PRId64, header_table_size); + } + + if (settings_frame->contains(Http3SettingsId::MAX_HEADER_LIST_SIZE)) { + uint64_t max_header_list_size = settings_frame->get(Http3SettingsId::MAX_HEADER_LIST_SIZE); + this->_session->remote_qpack()->update_max_header_list_size(max_header_list_size); + + Debug("http3", "SETTINGS_MAX_HEADER_LIST_SIZE: %" PRId64, max_header_list_size); + } + + if (settings_frame->contains(Http3SettingsId::QPACK_BLOCKED_STREAMS)) { + uint64_t qpack_blocked_streams = settings_frame->get(Http3SettingsId::QPACK_BLOCKED_STREAMS); + this->_session->remote_qpack()->update_max_blocking_streams(qpack_blocked_streams); + + Debug("http3", "SETTINGS_QPACK_BLOCKED_STREAMS: %" PRId64, qpack_blocked_streams); + } + + if (settings_frame->contains(Http3SettingsId::NUM_PLACEHOLDERS)) { + uint64_t num_placeholders = settings_frame->get(Http3SettingsId::NUM_PLACEHOLDERS); + // TODO: update settings for priority tree + + Debug("http3", "SETTINGS_NUM_PLACEHOLDERS: %" PRId64, num_placeholders); + } + + return Http3ErrorUPtr(new Http3NoError()); +} + +// +// SETTINGS frame framer +// +Http3FrameUPtr +Http3SettingsFramer::generate_frame(uint16_t max_size) +{ + if (this->_is_sent) { + return Http3FrameFactory::create_null_frame(); + } + + this->_is_sent = true; + + Http3Config::scoped_config params; + + Http3SettingsFrame *frame = http3SettingsFrameAllocator.alloc(); + new (frame) Http3SettingsFrame(); + + if (params->header_table_size() != HTTP3_DEFAULT_HEADER_TABLE_SIZE) { + frame->set(Http3SettingsId::HEADER_TABLE_SIZE, params->header_table_size()); + } + + if (params->max_header_list_size() != HTTP3_DEFAULT_MAX_HEADER_LIST_SIZE) { + frame->set(Http3SettingsId::MAX_HEADER_LIST_SIZE, params->max_header_list_size()); + } + + if (params->qpack_blocked_streams() != HTTP3_DEFAULT_QPACK_BLOCKED_STREAMS) { + frame->set(Http3SettingsId::QPACK_BLOCKED_STREAMS, params->qpack_blocked_streams()); + } + + // Server side only + if (this->_context == NET_VCONNECTION_IN) { + if (params->num_placeholders() != HTTP3_DEFAULT_NUM_PLACEHOLDERS) { + frame->set(Http3SettingsId::NUM_PLACEHOLDERS, params->num_placeholders()); + } + } + + return Http3SettingsFrameUPtr(frame, &Http3FrameDeleter::delete_settings_frame); +} + +bool +Http3SettingsFramer::is_done() const +{ + return this->_is_done; +} diff --git a/proxy/http3/Http3App.h b/proxy/http3/Http3App.h new file mode 100644 index 00000000000..fd1893d9722 --- /dev/null +++ b/proxy/http3/Http3App.h @@ -0,0 +1,111 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include "IPAllow.h" + +#include "QUICApplication.h" + +#include "HttpSessionAccept.h" + +#include "Http3Types.h" +#include "Http3FrameDispatcher.h" +#include "Http3FrameCollector.h" +#include "Http3FrameGenerator.h" +#include "Http3FrameHandler.h" + +class QUICNetVConnection; +class Http3Session; + +/** + * @brief A HTTP/3 application + * @detail + */ +class Http3App : public QUICApplication +{ +public: + Http3App(QUICNetVConnection *client_vc, IpAllow::ACL &&session_acl, const HttpSessionAccept::Options &options); + virtual ~Http3App(); + + virtual void start(); + virtual int main_event_handler(int event, Event *data); + + // TODO: Return StreamIO. It looks bother that coller have to look up StreamIO by stream id. + // Why not create_bidi_stream ? + QUICConnectionErrorUPtr create_uni_stream(QUICStreamId &new_stream_id, Http3StreamType type); + +protected: + Http3Session *_ssn = nullptr; + +private: + void _handle_uni_stream_on_read_ready(int event, QUICStreamIO *stream_io); + void _handle_uni_stream_on_write_ready(int event, QUICStreamIO *stream_io); + void _handle_uni_stream_on_eos(int event, QUICStreamIO *stream_io); + void _handle_bidi_stream_on_read_ready(int event, QUICStreamIO *stream_io); + void _handle_bidi_stream_on_write_ready(int event, QUICStreamIO *stream_io); + void _handle_bidi_stream_on_eos(int event, QUICStreamIO *stream_io); + + void _set_qpack_stream(Http3StreamType type, QUICStreamIO *stream_io); + + Http3FrameHandler *_settings_handler = nullptr; + Http3FrameGenerator *_settings_framer = nullptr; + + Http3FrameDispatcher _control_stream_dispatcher; + Http3FrameCollector _control_stream_collector; + + QUICStreamIO *_remote_control_stream; + QUICStreamIO *_local_control_stream; + + std::map _remote_uni_stream_map; + std::map _local_uni_stream_map; +}; + +class Http3SettingsHandler : public Http3FrameHandler +{ +public: + Http3SettingsHandler(Http3Session *session) : _session(session){}; + + // Http3FrameHandler + std::vector interests() override; + Http3ErrorUPtr handle_frame(std::shared_ptr frame) override; + +private: + // TODO: clarify Http3Session I/F for Http3SettingsHandler and Http3App + Http3Session *_session = nullptr; +}; + +class Http3SettingsFramer : public Http3FrameGenerator +{ +public: + Http3SettingsFramer(NetVConnectionContext_t context) : _context(context){}; + + // Http3FrameGenerator + Http3FrameUPtr generate_frame(uint16_t max_size) override; + bool is_done() const override; + +private: + NetVConnectionContext_t _context; + bool _is_done = false; ///< Becarefull when set FIN flag on CONTROL stream. Maybe never? + bool _is_sent = false; ///< Send SETTINGS frame only once +}; diff --git a/proxy/http3/Http3Config.cc b/proxy/http3/Http3Config.cc new file mode 100644 index 00000000000..a152af5aa87 --- /dev/null +++ b/proxy/http3/Http3Config.cc @@ -0,0 +1,100 @@ +/** @file + * + * HTTP/3 Config + * + * @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 "Http3Config.h" + +int Http3Config::_config_id = 0; + +// +// Http3ConfigParams +// +void +Http3ConfigParams::initialize() +{ + REC_EstablishStaticConfigInt32U(this->_header_table_size, "proxy.config.http3.header_table_size"); + REC_EstablishStaticConfigInt32U(this->_max_header_list_size, "proxy.config.http3.max_header_list_size"); + REC_EstablishStaticConfigInt32U(this->_qpack_blocked_streams, "proxy.config.http3.qpack_blocked_streams"); + REC_EstablishStaticConfigInt32U(this->_num_placeholders, "proxy.config.http3.num_placeholders"); + REC_EstablishStaticConfigInt32U(this->_max_settings, "proxy.config.http3.max_settings"); +} + +uint32_t +Http3ConfigParams::header_table_size() const +{ + return this->_header_table_size; +} + +uint32_t +Http3ConfigParams::max_header_list_size() const +{ + return this->_max_header_list_size; +} + +uint32_t +Http3ConfigParams::qpack_blocked_streams() const +{ + return this->_qpack_blocked_streams; +} + +uint32_t +Http3ConfigParams::num_placeholders() const +{ + return this->_num_placeholders; +} + +uint32_t +Http3ConfigParams::max_settings() const +{ + return this->_max_settings; +} + +// +// Http3Config +// +void +Http3Config::startup() +{ + reconfigure(); +} + +void +Http3Config::reconfigure() +{ + Http3ConfigParams *params; + params = new Http3ConfigParams; + // re-read configuration + params->initialize(); + Http3Config::_config_id = configProcessor.set(Http3Config::_config_id, params); +} + +Http3ConfigParams * +Http3Config::acquire() +{ + return static_cast(configProcessor.get(Http3Config::_config_id)); +} + +void +Http3Config::release(Http3ConfigParams *params) +{ + configProcessor.release(Http3Config::_config_id, params); +} diff --git a/proxy/http3/Http3Config.h b/proxy/http3/Http3Config.h new file mode 100644 index 00000000000..27ae9a274b7 --- /dev/null +++ b/proxy/http3/Http3Config.h @@ -0,0 +1,62 @@ +/** @file + * + * HTTP/3 Config + * + * @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. + */ + +#pragma once + +#include "ProxyConfig.h" + +class Http3ConfigParams : public ConfigInfo +{ +public: + Http3ConfigParams(){}; + ~Http3ConfigParams(){}; + + void initialize(); + + uint32_t header_table_size() const; + uint32_t max_header_list_size() const; + uint32_t qpack_blocked_streams() const; + uint32_t num_placeholders() const; + uint32_t max_settings() const; + +private: + uint32_t _header_table_size = 0; + uint32_t _max_header_list_size = 0; + uint32_t _qpack_blocked_streams = 0; + uint32_t _num_placeholders = 0; + uint32_t _max_settings = 10; +}; + +class Http3Config +{ +public: + static void startup(); + static void reconfigure(); + static Http3ConfigParams *acquire(); + static void release(Http3ConfigParams *params); + + using scoped_config = ConfigProcessor::scoped_config; + +private: + static int _config_id; +}; diff --git a/proxy/http3/Http3DataFramer.cc b/proxy/http3/Http3DataFramer.cc new file mode 100644 index 00000000000..eaf58627573 --- /dev/null +++ b/proxy/http3/Http3DataFramer.cc @@ -0,0 +1,61 @@ +/** @file + * + * A brief file description + * + * @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 "Http3Frame.h" +#include "Http3DataFramer.h" +#include "Http3Transaction.h" + +Http3DataFramer::Http3DataFramer(Http3Transaction *transaction, VIO *source) : _transaction(transaction), _source_vio(source) {} + +Http3FrameUPtr +Http3DataFramer::generate_frame(uint16_t max_size) +{ + if (!this->_transaction->is_response_header_sent()) { + return Http3FrameFactory::create_null_frame(); + } + + Http3FrameUPtr frame = Http3FrameFactory::create_null_frame(); + IOBufferReader *reader = this->_source_vio->get_reader(); + + if (max_size <= Http3Frame::MAX_FRAM_HEADER_OVERHEAD) { + return frame; + } + + size_t payload_len = max_size - Http3Frame::MAX_FRAM_HEADER_OVERHEAD; + if (!reader->is_read_avail_more_than(payload_len)) { + payload_len = reader->read_avail(); + } + + if (payload_len) { + frame = Http3FrameFactory::create_data_frame(reader, payload_len); + this->_source_vio->ndone += payload_len; + } + + return frame; +} + +bool +Http3DataFramer::is_done() const +{ + return this->_source_vio->ntodo() == 0; +} diff --git a/proxy/http3/Http3DataFramer.h b/proxy/http3/Http3DataFramer.h new file mode 100644 index 00000000000..045e86c6b47 --- /dev/null +++ b/proxy/http3/Http3DataFramer.h @@ -0,0 +1,44 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include "Http3FrameGenerator.h" +#include "Http3Frame.h" + +class Http3Transaction; +class VIO; + +class Http3DataFramer : public Http3FrameGenerator +{ +public: + Http3DataFramer(Http3Transaction *transaction, VIO *source); + + // Http3FrameGenerator + Http3FrameUPtr generate_frame(uint16_t max_size) override; + bool is_done() const override; + +private: + Http3Transaction *_transaction = nullptr; + VIO *_source_vio = nullptr; +}; diff --git a/proxy/http3/Http3DebugNames.cc b/proxy/http3/Http3DebugNames.cc new file mode 100644 index 00000000000..2d1e87a605a --- /dev/null +++ b/proxy/http3/Http3DebugNames.cc @@ -0,0 +1,152 @@ +/** @file + * + * A brief file description + * + * @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 "Http3DebugNames.h" +#include "Http3Types.h" + +const char * +Http3DebugNames::frame_type(Http3FrameType type) +{ + switch (type) { + case Http3FrameType::DATA: + return "DATA"; + case Http3FrameType::HEADERS: + return "HEADERS"; + case Http3FrameType::PRIORITY: + return "PRIORITY"; + case Http3FrameType::CANCEL_PUSH: + return "CANCEL_PUSH"; + case Http3FrameType::SETTINGS: + return "SETTINGS"; + case Http3FrameType::PUSH_PROMISE: + return "PUSH_PROMISE"; + case Http3FrameType::GOAWAY: + return "GOAWAY"; + case Http3FrameType::DUPLICATE_PUSH_ID: + return "DUPLICATE_PUSH_ID"; + case Http3FrameType::UNKNOWN: + default: + return "UNKNOWN"; + } +} + +const char * +Http3DebugNames::stream_type(Http3StreamType type) +{ + return Http3DebugNames::stream_type(static_cast(type)); +} + +const char * +Http3DebugNames::stream_type(uint8_t type) +{ + switch (type) { + case static_cast(Http3StreamType::CONTROL): + return "CONTROL"; + case static_cast(Http3StreamType::QPACK_ENCODER): + return "QPACK_ENCODER"; + case static_cast(Http3StreamType::PUSH): + return "PUSH"; + case static_cast(Http3StreamType::QPACK_DECODER): + return "QPACK_DECODER"; + default: + return "UNKNOWN"; + } +} + +const char * +Http3DebugNames::settings_id(uint16_t id) +{ + switch (id) { + case static_cast(Http3SettingsId::HEADER_TABLE_SIZE): + return "HEADER_TABLE_SIZE"; + case static_cast(Http3SettingsId::MAX_HEADER_LIST_SIZE): + return "MAX_HEADER_LIST_SIZE"; + case static_cast(Http3SettingsId::QPACK_BLOCKED_STREAMS): + return "QPACK_BLOCKED_STREAMS"; + case static_cast(Http3SettingsId::NUM_PLACEHOLDERS): + return "NUM_PLACEHOLDERS"; + default: + return "UNKNOWN"; + } +} + +const char * +Http3DebugNames::error_code(uint16_t code) +{ + switch (code) { + case static_cast(Http3ErrorCode::NO_ERROR): + return "NO_ERROR"; + case static_cast(Http3ErrorCode::WRONG_SETTING_DIRECTION): + return "WRONG_SETTING_DIRECTION"; + case static_cast(Http3ErrorCode::PUSH_REFUSED): + return "PUSH_REFUSED"; + case static_cast(Http3ErrorCode::INTERNAL_ERROR): + return "INTERNAL_ERROR"; + case static_cast(Http3ErrorCode::PUSH_ALREADY_IN_CACHE): + return "PUSH_ALREADY_IN_CACHE"; + case static_cast(Http3ErrorCode::REQUEST_CANCELLED): + return "REQUEST_CANCELLED"; + case static_cast(Http3ErrorCode::INCOMPLETE_REQUEST): + return "INCOMPLETE_REQUEST"; + case static_cast(Http3ErrorCode::CONNECT_ERROR): + return "CONNECT_ERROR"; + case static_cast(Http3ErrorCode::EXCESSIVE_LOAD): + return "EXCESSIVE_LOAD"; + case static_cast(Http3ErrorCode::VERSION_FALLBACK): + return "VERSION_FALLBACK"; + case static_cast(Http3ErrorCode::WRONG_STREAM): + return "WRONG_STREAM"; + case static_cast(Http3ErrorCode::LIMIT_EXCEEDED): + return "LIMIT_EXCEEDED"; + case static_cast(Http3ErrorCode::DUPLICATE_PUSH): + return "DUPLICATE_PUSH"; + case static_cast(Http3ErrorCode::UNKNOWN_STREAM_TYPE): + return "UNKNOWN_STREAM_TYPE"; + case static_cast(Http3ErrorCode::WRONG_STREAM_COUNT): + return "WRONG_STREAM_COUNT"; + case static_cast(Http3ErrorCode::CLOSED_CRITICAL_STREAM): + return "CLOSED_CRITICAL_STREAM"; + case static_cast(Http3ErrorCode::WRONG_STREAM_DIRECTION): + return "WRONG_STREAM_DIRECTION"; + case static_cast(Http3ErrorCode::EARLY_RESPONSE): + return "EARLY_RESPONSE"; + case static_cast(Http3ErrorCode::MISSING_SETTINGS): + return "MISSING_SETTINGS"; + case static_cast(Http3ErrorCode::UNEXPECTED_FRAME): + return "UNEXPECTED_FRAME"; + case static_cast(Http3ErrorCode::REQUEST_REJECTED): + return "REQUEST_REJECTED"; + case static_cast(Http3ErrorCode::QPACK_DECOMPRESSION_FAILED): + return "QPACK_DECOMPRESSION_FAILED"; + case static_cast(Http3ErrorCode::QPACK_ENCODER_STREAM_ERROR): + return "QPACK_ENCODER_STREAM_ERROR"; + case static_cast(Http3ErrorCode::QPACK_DECODER_STREAM_ERROR): + return "QPACK_DECODER_STREAM_ERROR"; + default: + if (0x0100 <= code && code <= 0x01FF) { + return "MALFORMED_FRAME"; + } + + return "UNKNOWN"; + } +} diff --git a/proxy/http3/Http3DebugNames.h b/proxy/http3/Http3DebugNames.h new file mode 100644 index 00000000000..c8f34da693c --- /dev/null +++ b/proxy/http3/Http3DebugNames.h @@ -0,0 +1,36 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include "Http3Types.h" + +class Http3DebugNames +{ +public: + static const char *frame_type(Http3FrameType type); + static const char *stream_type(Http3StreamType type); + static const char *stream_type(uint8_t type); + static const char *settings_id(uint16_t id); + static const char *error_code(uint16_t code); +}; diff --git a/proxy/http3/Http3Frame.cc b/proxy/http3/Http3Frame.cc new file mode 100644 index 00000000000..b9102dc3afd --- /dev/null +++ b/proxy/http3/Http3Frame.cc @@ -0,0 +1,494 @@ +/** @file + * + * A brief file description + * + * @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 "tscore/Diags.h" +#include "quic/QUICIntUtil.h" +#include "Http3Frame.h" +#include "Http3Config.h" + +ClassAllocator http3FrameAllocator("http3FrameAllocator"); +ClassAllocator http3DataFrameAllocator("http3DataFrameAllocator"); +ClassAllocator http3HeadersFrameAllocator("http3HeadersFrameAllocator"); +ClassAllocator http3SettingsFrameAllocator("http3SettingsFrameAllocator"); + +// +// Static functions +// + +int +Http3Frame::length(const uint8_t *buf, size_t buf_len, uint64_t &length) +{ + size_t length_field_length = 0; + return QUICVariableInt::decode(length, length_field_length, buf, buf_len); +} + +Http3FrameType +Http3Frame::type(const uint8_t *buf, size_t buf_len) +{ + uint64_t type = 0; + size_t type_field_length = 0; + int ret = QUICVariableInt::decode(type, type_field_length, buf, buf_len); + ink_assert(ret != 1); + if (type <= static_cast(Http3FrameType::X_MAX_DEFINED)) { + return static_cast(type); + } else { + return Http3FrameType::UNKNOWN; + } +} + +// +// Generic Frame +// + +Http3Frame::Http3Frame(const uint8_t *buf, size_t buf_len) +{ + // Type + size_t type_field_length = 0; + int ret = QUICVariableInt::decode(reinterpret_cast(this->_type), type_field_length, buf, buf_len); + ink_assert(ret != 1); + + // Length + size_t length_field_length = 0; + ret = QUICVariableInt::decode(this->_length, length_field_length, buf + type_field_length, buf_len - type_field_length); + ink_assert(ret != 1); + + // Payload offset + this->_payload_offset = type_field_length + length_field_length; +} + +Http3Frame::Http3Frame(Http3FrameType type) : _type(type) {} + +uint64_t +Http3Frame::total_length() const +{ + return this->_payload_offset + this->length(); +} + +uint64_t +Http3Frame::length() const +{ + return this->_length; +} + +Http3FrameType +Http3Frame::type() const +{ + if (static_cast(this->_type) <= static_cast(Http3FrameType::X_MAX_DEFINED)) { + return this->_type; + } else { + return Http3FrameType::UNKNOWN; + } +} + +void +Http3Frame::store(uint8_t *buf, size_t *len) const +{ + // If you really need this, you should keep the data passed to its constructor + ink_assert(!"Not supported"); +} + +void +Http3Frame::reset(const uint8_t *buf, size_t len) +{ + this->~Http3Frame(); + new (this) Http3Frame(buf, len); +} + +// +// UNKNOWN Frame +// +Http3UnknownFrame::Http3UnknownFrame(const uint8_t *buf, size_t buf_len) : Http3Frame(buf, buf_len), _buf(buf), _buf_len(buf_len) {} + +void +Http3UnknownFrame::store(uint8_t *buf, size_t *len) const +{ + memcpy(buf, this->_buf, this->_buf_len); + *len = this->_buf_len; +} + +// +// DATA Frame +// +Http3DataFrame::Http3DataFrame(const uint8_t *buf, size_t buf_len) : Http3Frame(buf, buf_len) +{ + this->_payload = buf + this->_payload_offset; + this->_payload_len = buf_len - this->_payload_offset; +} + +Http3DataFrame::Http3DataFrame(ats_unique_buf payload, size_t payload_len) + : Http3Frame(Http3FrameType::DATA), _payload_uptr(std::move(payload)), _payload_len(payload_len) +{ + this->_length = this->_payload_len; + this->_payload = this->_payload_uptr.get(); +} + +void +Http3DataFrame::store(uint8_t *buf, size_t *len) const +{ + size_t written = 0; + size_t n; + QUICVariableInt::encode(buf, UINT64_MAX, n, static_cast(this->_type)); + written += n; + QUICVariableInt::encode(buf + written, UINT64_MAX, n, this->_length); + written += n; + memcpy(buf + written, this->_payload, this->_payload_len); + written += this->_payload_len; + *len = written; +} + +void +Http3DataFrame::reset(const uint8_t *buf, size_t len) +{ + this->~Http3DataFrame(); + new (this) Http3DataFrame(buf, len); +} + +const uint8_t * +Http3DataFrame::payload() const +{ + return this->_payload; +} + +uint64_t +Http3DataFrame::payload_length() const +{ + return this->_payload_len; +} + +// +// HEADERS Frame +// +Http3HeadersFrame::Http3HeadersFrame(const uint8_t *buf, size_t buf_len) : Http3Frame(buf, buf_len) +{ + this->_header_block = buf + this->_payload_offset; + this->_header_block_len = buf_len - this->_payload_offset; +} + +Http3HeadersFrame::Http3HeadersFrame(ats_unique_buf header_block, size_t header_block_len) + : Http3Frame(Http3FrameType::HEADERS), _header_block_uptr(std::move(header_block)), _header_block_len(header_block_len) +{ + this->_length = header_block_len; + this->_header_block = this->_header_block_uptr.get(); +} + +void +Http3HeadersFrame::store(uint8_t *buf, size_t *len) const +{ + size_t written = 0; + size_t n; + QUICVariableInt::encode(buf, UINT64_MAX, n, static_cast(this->_type)); + written += n; + QUICVariableInt::encode(buf + written, UINT64_MAX, n, this->_length); + written += n; + memcpy(buf + written, this->_header_block, this->_header_block_len); + written += this->_header_block_len; + *len = written; +} + +void +Http3HeadersFrame::reset(const uint8_t *buf, size_t len) +{ + this->~Http3HeadersFrame(); + new (this) Http3HeadersFrame(buf, len); +} + +const uint8_t * +Http3HeadersFrame::header_block() const +{ + return this->_header_block; +} + +uint64_t +Http3HeadersFrame::header_block_length() const +{ + return this->_header_block_len; +} + +// +// SETTINGS Frame +// + +Http3SettingsFrame::Http3SettingsFrame(const uint8_t *buf, size_t buf_len, uint32_t max_settings) : Http3Frame(buf, buf_len) +{ + size_t len = this->_payload_offset; + uint32_t nsettings = 0; + + while (len < buf_len) { + if (nsettings >= max_settings) { + this->_error_code = Http3ErrorCode::EXCESSIVE_LOAD; + this->_error_reason = reinterpret_cast("too many settings"); + break; + } + + size_t id_len = QUICVariableInt::size(buf + len); + uint16_t id = QUICIntUtil::read_QUICVariableInt(buf + len); + len += id_len; + + size_t value_len = QUICVariableInt::size(buf + len); + uint64_t value = QUICIntUtil::read_QUICVariableInt(buf + len); + len += value_len; + + // Ignore any SETTINGS identifier it does not understand. + bool ignore = true; + for (const auto &known_id : Http3SettingsFrame::VALID_SETTINGS_IDS) { + if (id == static_cast(known_id)) { + ignore = false; + break; + } + } + + if (ignore) { + continue; + } + + this->_settings.insert(std::make_pair(static_cast(id), value)); + ++nsettings; + } + + if (len == buf_len) { + this->_valid = true; + } +} + +void +Http3SettingsFrame::store(uint8_t *buf, size_t *len) const +{ + uint8_t payload[Http3SettingsFrame::MAX_PAYLOAD_SIZE] = {0}; + uint8_t *p = payload; + size_t l = 0; + + for (auto &it : this->_settings) { + QUICIntUtil::write_QUICVariableInt(static_cast(it.first), p, &l); + p += l; + QUICIntUtil::write_QUICVariableInt(it.second, p, &l); + p += l; + } + + // Exercise the requirement that unknown identifiers be ignored. - 4.2.5.1. + QUICIntUtil::write_QUICVariableInt(static_cast(Http3SettingsId::UNKNOWN), p, &l); + p += l; + QUICIntUtil::write_QUICVariableInt(0, p, &l); + p += l; + + size_t written = 0; + size_t payload_len = p - payload; + + size_t n; + QUICVariableInt::encode(buf, UINT64_MAX, n, static_cast(this->_type)); + written += n; + QUICVariableInt::encode(buf + written, UINT64_MAX, n, payload_len); + written += n; + + // Payload + memcpy(buf + written, payload, payload_len); + written += payload_len; + + *len = written; +} + +void +Http3SettingsFrame::reset(const uint8_t *buf, size_t len) +{ + this->~Http3SettingsFrame(); + new (this) Http3SettingsFrame(buf, len); +} + +bool +Http3SettingsFrame::is_valid() const +{ + return this->_valid; +} + +Http3ErrorUPtr +Http3SettingsFrame::get_error() const +{ + return std::make_unique(this->_error_code, this->_error_reason); +} + +bool +Http3SettingsFrame::contains(Http3SettingsId id) const +{ + auto p = this->_settings.find(id); + return (p != this->_settings.end()); +} + +uint64_t +Http3SettingsFrame::get(Http3SettingsId id) const +{ + auto p = this->_settings.find(id); + if (p != this->_settings.end()) { + return p->second; + } + + return 0; +} + +void +Http3SettingsFrame::set(Http3SettingsId id, uint64_t value) +{ + this->_settings[id] = value; +} + +// +// Http3FrameFactory +// +Http3FrameUPtr +Http3FrameFactory::create_null_frame() +{ + return {nullptr, &Http3FrameDeleter::delete_null_frame}; +} + +Http3FrameUPtr +Http3FrameFactory::create(const uint8_t *buf, size_t len) +{ + Http3Config::scoped_config params; + Http3Frame *frame = nullptr; + Http3FrameType type = Http3Frame::type(buf, len); + + switch (type) { + case Http3FrameType::HEADERS: + frame = http3HeadersFrameAllocator.alloc(); + new (frame) Http3HeadersFrame(buf, len); + return Http3FrameUPtr(frame, &Http3FrameDeleter::delete_headers_frame); + case Http3FrameType::DATA: + frame = http3DataFrameAllocator.alloc(); + new (frame) Http3DataFrame(buf, len); + return Http3FrameUPtr(frame, &Http3FrameDeleter::delete_data_frame); + case Http3FrameType::SETTINGS: + frame = http3SettingsFrameAllocator.alloc(); + new (frame) Http3SettingsFrame(buf, len, params->max_settings()); + return Http3FrameUPtr(frame, &Http3FrameDeleter::delete_settings_frame); + default: + // Unknown frame + Debug("http3_frame_factory", "Unknown frame type %hhx", static_cast(type)); + frame = http3FrameAllocator.alloc(); + new (frame) Http3Frame(buf, len); + return Http3FrameUPtr(frame, &Http3FrameDeleter::delete_frame); + } +} + +std::shared_ptr +Http3FrameFactory::fast_create(const uint8_t *buf, size_t len) +{ + Http3FrameType type = Http3Frame::type(buf, len); + if (type == Http3FrameType::UNKNOWN) { + if (!this->_unknown_frame) { + this->_unknown_frame = Http3FrameFactory::create(buf, len); + } else { + this->_unknown_frame->reset(buf, len); + } + return this->_unknown_frame; + } + + std::shared_ptr frame = this->_reusable_frames[static_cast(type)]; + + if (frame == nullptr) { + frame = Http3FrameFactory::create(buf, len); + if (frame != nullptr) { + this->_reusable_frames[static_cast(type)] = frame; + } + } else { + frame->reset(buf, len); + } + + return frame; +} + +std::shared_ptr +Http3FrameFactory::fast_create(QUICStreamIO &stream_io, size_t frame_len) +{ + uint8_t buf[65536]; + + // FIXME DATA frames can be giga bytes + ink_assert(sizeof(buf) > frame_len); + + if (stream_io.peek(buf, frame_len) < static_cast(frame_len)) { + // Return if whole frame data is not available + return nullptr; + } + return this->fast_create(buf, frame_len); +} + +Http3HeadersFrameUPtr +Http3FrameFactory::create_headers_frame(const uint8_t *header_block, size_t header_block_len) +{ + ats_unique_buf buf = ats_unique_malloc(header_block_len); + memcpy(buf.get(), header_block, header_block_len); + + Http3HeadersFrame *frame = http3HeadersFrameAllocator.alloc(); + new (frame) Http3HeadersFrame(std::move(buf), header_block_len); + return Http3HeadersFrameUPtr(frame, &Http3FrameDeleter::delete_headers_frame); +} + +Http3HeadersFrameUPtr +Http3FrameFactory::create_headers_frame(IOBufferReader *header_block_reader, size_t header_block_len) +{ + ats_unique_buf buf = ats_unique_malloc(header_block_len); + + int64_t nread; + while ((nread = header_block_reader->read(buf.get(), header_block_len)) > 0) { + ; + } + + Http3HeadersFrame *frame = http3HeadersFrameAllocator.alloc(); + new (frame) Http3HeadersFrame(std::move(buf), header_block_len); + return Http3HeadersFrameUPtr(frame, &Http3FrameDeleter::delete_headers_frame); +} + +Http3DataFrameUPtr +Http3FrameFactory::create_data_frame(const uint8_t *payload, size_t payload_len) +{ + ats_unique_buf buf = ats_unique_malloc(payload_len); + memcpy(buf.get(), payload, payload_len); + + Http3DataFrame *frame = http3DataFrameAllocator.alloc(); + new (frame) Http3DataFrame(std::move(buf), payload_len); + return Http3DataFrameUPtr(frame, &Http3FrameDeleter::delete_data_frame); +} + +// TODO: This should clone IOBufferBlock chain to avoid memcpy +Http3DataFrameUPtr +Http3FrameFactory::create_data_frame(IOBufferReader *reader, size_t payload_len) +{ + ats_unique_buf buf = ats_unique_malloc(payload_len); + size_t written = 0; + + while (written < payload_len) { + int64_t len = reader->block_read_avail(); + + if (written + len > payload_len) { + len = payload_len - written; + } + + memcpy(buf.get() + written, reinterpret_cast(reader->start()), len); + reader->consume(len); + written += len; + } + + ink_assert(written == payload_len); + + Http3DataFrame *frame = http3DataFrameAllocator.alloc(); + new (frame) Http3DataFrame(std::move(buf), payload_len); + + return Http3DataFrameUPtr(frame, &Http3FrameDeleter::delete_data_frame); +} diff --git a/proxy/http3/Http3Frame.h b/proxy/http3/Http3Frame.h new file mode 100644 index 00000000000..4ae00a9d33a --- /dev/null +++ b/proxy/http3/Http3Frame.h @@ -0,0 +1,244 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include "tscore/Allocator.h" +#include "tscore/ink_memory.h" +#include "tscore/ink_assert.h" +#include "QUICApplication.h" +#include "Http3Types.h" + +class Http3Frame +{ +public: + constexpr static size_t MAX_FRAM_HEADER_OVERHEAD = 128; ///< Type (i) + Length (i) + + Http3Frame() {} + Http3Frame(const uint8_t *buf, size_t len); + Http3Frame(Http3FrameType type); + virtual ~Http3Frame() {} + + uint64_t total_length() const; + uint64_t length() const; + Http3FrameType type() const; + virtual void store(uint8_t *buf, size_t *len) const; + virtual void reset(const uint8_t *buf, size_t len); + static int length(const uint8_t *buf, size_t buf_len, uint64_t &length); + static Http3FrameType type(const uint8_t *buf, size_t buf_len); + +protected: + uint64_t _length = 0; + Http3FrameType _type = Http3FrameType::UNKNOWN; + size_t _payload_offset = 0; +}; + +class Http3UnknownFrame : public Http3Frame +{ +public: + Http3UnknownFrame() : Http3Frame() {} + Http3UnknownFrame(const uint8_t *buf, size_t len); + + void store(uint8_t *buf, size_t *len) const override; + +protected: + const uint8_t *_buf = nullptr; + size_t _buf_len = 0; +}; + +// +// DATA Frame +// + +class Http3DataFrame : public Http3Frame +{ +public: + Http3DataFrame() : Http3Frame() {} + Http3DataFrame(const uint8_t *buf, size_t len); + Http3DataFrame(ats_unique_buf payload, size_t payload_len); + + void store(uint8_t *buf, size_t *len) const override; + void reset(const uint8_t *buf, size_t len) override; + + const uint8_t *payload() const; + uint64_t payload_length() const; + +private: + const uint8_t *_payload = nullptr; + ats_unique_buf _payload_uptr = {nullptr}; + size_t _payload_len = 0; +}; + +// +// HEADERS Frame +// + +class Http3HeadersFrame : public Http3Frame +{ +public: + Http3HeadersFrame() : Http3Frame() {} + Http3HeadersFrame(const uint8_t *buf, size_t len); + Http3HeadersFrame(ats_unique_buf header_block, size_t header_block_len); + + void store(uint8_t *buf, size_t *len) const override; + void reset(const uint8_t *buf, size_t len) override; + + const uint8_t *header_block() const; + uint64_t header_block_length() const; + +private: + const uint8_t *_header_block = nullptr; + ats_unique_buf _header_block_uptr = {nullptr}; + size_t _header_block_len = 0; +}; + +// +// SETTINGS Frame +// + +class Http3SettingsFrame : public Http3Frame +{ +public: + Http3SettingsFrame() : Http3Frame(Http3FrameType::SETTINGS) {} + Http3SettingsFrame(const uint8_t *buf, size_t len, uint32_t max_settings = 0); + + static constexpr size_t MAX_PAYLOAD_SIZE = 60; + static constexpr std::array VALID_SETTINGS_IDS{ + Http3SettingsId::HEADER_TABLE_SIZE, + Http3SettingsId::MAX_HEADER_LIST_SIZE, + Http3SettingsId::QPACK_BLOCKED_STREAMS, + Http3SettingsId::NUM_PLACEHOLDERS, + }; + + void store(uint8_t *buf, size_t *len) const override; + void reset(const uint8_t *buf, size_t len) override; + + bool is_valid() const; + Http3ErrorUPtr get_error() const; + + bool contains(Http3SettingsId id) const; + uint64_t get(Http3SettingsId id) const; + void set(Http3SettingsId id, uint64_t value); + +private: + std::map _settings; + // TODO: make connection error with HTTP_MALFORMED_FRAME + bool _valid = false; + Http3ErrorCode _error_code; + const char *_error_reason = nullptr; +}; + +using Http3FrameDeleterFunc = void (*)(Http3Frame *p); +using Http3FrameUPtr = std::unique_ptr; +using Http3DataFrameUPtr = std::unique_ptr; +using Http3HeadersFrameUPtr = std::unique_ptr; +using Http3SettingsFrameUPtr = std::unique_ptr; + +using Http3FrameDeleterFunc = void (*)(Http3Frame *p); +using Http3FrameUPtr = std::unique_ptr; +using Http3DataFrameUPtr = std::unique_ptr; +using Http3HeadersFrameUPtr = std::unique_ptr; + +extern ClassAllocator http3FrameAllocator; +extern ClassAllocator http3DataFrameAllocator; +extern ClassAllocator http3HeadersFrameAllocator; +extern ClassAllocator http3SettingsFrameAllocator; + +class Http3FrameDeleter +{ +public: + static void + delete_null_frame(Http3Frame *frame) + { + ink_assert(frame == nullptr); + } + + static void + delete_frame(Http3Frame *frame) + { + frame->~Http3Frame(); + http3FrameAllocator.free(static_cast(frame)); + } + + static void + delete_data_frame(Http3Frame *frame) + { + frame->~Http3Frame(); + http3DataFrameAllocator.free(static_cast(frame)); + } + + static void + delete_headers_frame(Http3Frame *frame) + { + frame->~Http3Frame(); + http3HeadersFrameAllocator.free(static_cast(frame)); + } + + static void + delete_settings_frame(Http3Frame *frame) + { + frame->~Http3Frame(); + http3SettingsFrameAllocator.free(static_cast(frame)); + } +}; + +// +// Http3FrameFactory +// +class Http3FrameFactory +{ +public: + /* + * This is for an empty Http3FrameUPtr. + * Empty frames are used for variable initialization and return value of frame creation failure + */ + static Http3FrameUPtr create_null_frame(); + + /* + * This is used for creating a Http3Frame object based on received data. + */ + static Http3FrameUPtr create(const uint8_t *buf, size_t len); + + /* + * This works almost the same as create() but it reuses created objects for performance. + * If you create a frame object which has the same frame type that you created before, the object will be reset by new data. + */ + std::shared_ptr fast_create(QUICStreamIO &stream_io, size_t frame_len); + std::shared_ptr fast_create(const uint8_t *buf, size_t len); + + /* + * Creates a HEADERS frame. + */ + static Http3HeadersFrameUPtr create_headers_frame(const uint8_t *header_block, size_t header_block_len); + static Http3HeadersFrameUPtr create_headers_frame(IOBufferReader *header_block_reader, size_t header_block_len); + + /* + * Creates a DATA frame. + */ + static Http3DataFrameUPtr create_data_frame(const uint8_t *data, size_t data_len); + static Http3DataFrameUPtr create_data_frame(IOBufferReader *reader, size_t data_len); + +private: + std::shared_ptr _unknown_frame = nullptr; + std::shared_ptr _reusable_frames[256] = {nullptr}; +}; diff --git a/proxy/http3/Http3FrameCollector.cc b/proxy/http3/Http3FrameCollector.cc new file mode 100644 index 00000000000..213a1e3e113 --- /dev/null +++ b/proxy/http3/Http3FrameCollector.cc @@ -0,0 +1,67 @@ +/** @file + * + * A brief file description + * + * @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 "Http3FrameCollector.h" + +#include "Http3DebugNames.h" + +Http3ErrorUPtr +Http3FrameCollector::on_write_ready(QUICStreamIO *stream_io, size_t &nwritten) +{ + bool all_done = true; + uint8_t tmp[32768]; + nwritten = 0; + + for (auto g : this->_generators) { + if (g->is_done()) { + continue; + } + size_t len = 0; + Http3FrameUPtr frame = g->generate_frame(sizeof(tmp) - nwritten); + if (frame) { + frame->store(tmp + nwritten, &len); + nwritten += len; + + Debug("http3", "[TX] [%d] | %s size=%zu", stream_io->stream_id(), Http3DebugNames::frame_type(frame->type()), len); + } + + all_done &= g->is_done(); + } + + if (nwritten) { + int64_t len = stream_io->write(tmp, nwritten); + ink_assert(len > 0 && (uint64_t)len == nwritten); + } + + if (all_done) { + stream_io->write_done(); + } + + return Http3ErrorUPtr(new Http3NoError()); +} + +void +Http3FrameCollector::add_generator(Http3FrameGenerator *generator) +{ + this->_generators.push_back(generator); +} diff --git a/proxy/http3/Http3FrameCollector.h b/proxy/http3/Http3FrameCollector.h new file mode 100644 index 00000000000..73b7c5c5ab2 --- /dev/null +++ b/proxy/http3/Http3FrameCollector.h @@ -0,0 +1,40 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include "QUICApplication.h" +#include "Http3Frame.h" +#include "Http3FrameGenerator.h" +#include + +class Http3FrameCollector +{ +public: + Http3ErrorUPtr on_write_ready(QUICStreamIO *stream_io, size_t &nread); + + void add_generator(Http3FrameGenerator *generator); + +private: + std::vector _generators; +}; diff --git a/proxy/http3/Http3FrameDispatcher.cc b/proxy/http3/Http3FrameDispatcher.cc new file mode 100644 index 00000000000..64df8d5a5ed --- /dev/null +++ b/proxy/http3/Http3FrameDispatcher.cc @@ -0,0 +1,115 @@ +/** @file + * + * A brief file description + * + * @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 "Http3FrameDispatcher.h" + +#include "tscore/Diags.h" +#include "QUICIntUtil.h" + +#include "Http3DebugNames.h" + +// +// Frame Dispatcher +// + +void +Http3FrameDispatcher::add_handler(Http3FrameHandler *handler) +{ + for (Http3FrameType t : handler->interests()) { + this->_handlers[static_cast(t)].push_back(handler); + } +} + +Http3ErrorUPtr +Http3FrameDispatcher::on_read_ready(QUICStreamIO &stream_io, uint64_t &nread) +{ + std::shared_ptr frame(nullptr); + Http3ErrorUPtr error = Http3ErrorUPtr(new Http3NoError()); + nread = 0; + + while (true) { + // Read a length of Type field and hopefully a length of Length field too + uint8_t head[16]; + int64_t read_len = stream_io.peek(head, 16); + Debug("v_http3", "reading H3 frame: state=%d read_len=%" PRId64, this->_reading_state, read_len); + + if (this->_reading_state == READING_TYPE_LEN) { + if (read_len >= 1) { + this->_reading_frame_type_len = QUICVariableInt::size(head); + this->_reading_state = READING_LENGTH_LEN; + Debug("v_http3", "type_len=%" PRId64, this->_reading_frame_type_len); + } else { + break; + } + } + + if (this->_reading_state == READING_LENGTH_LEN) { + if (read_len >= this->_reading_frame_type_len + 1) { + this->_reading_frame_length_len = QUICVariableInt::size(head + this->_reading_frame_type_len); + this->_reading_state = READING_PAYLOAD_LEN; + Debug("v_http3", "length_len=%" PRId64, this->_reading_frame_length_len); + } else { + break; + } + } + + if (this->_reading_state == READING_PAYLOAD_LEN) { + if (read_len >= this->_reading_frame_type_len + this->_reading_frame_length_len) { + size_t dummy; + QUICVariableInt::decode(this->_reading_frame_payload_len, dummy, head + this->_reading_frame_type_len); + Debug("v_http3", "payload_len=%" PRId64, this->_reading_frame_payload_len); + this->_reading_state = READING_PAYLOAD; + } else { + break; + } + } + + if (this->_reading_state == READING_PAYLOAD) { + // Create a frame + // Type field length + Length field length + Payload length + size_t frame_len = this->_reading_frame_type_len + this->_reading_frame_length_len + this->_reading_frame_payload_len; + frame = this->_frame_factory.fast_create(stream_io, frame_len); + if (frame == nullptr) { + break; + } + + // Consume buffer if frame is created + nread += frame_len; + stream_io.consume(frame_len); + + // Dispatch + Http3FrameType type = frame->type(); + Debug("http3", "[RX] [%d] | %s size=%zu", stream_io.stream_id(), Http3DebugNames::frame_type(type), frame_len); + std::vector handlers = this->_handlers[static_cast(type)]; + for (auto h : handlers) { + error = h->handle_frame(frame); + if (error->cls != Http3ErrorClass::NONE) { + return error; + } + } + this->_reading_state = READING_TYPE_LEN; + } + } + + return error; +} diff --git a/proxy/http3/Http3FrameDispatcher.h b/proxy/http3/Http3FrameDispatcher.h new file mode 100644 index 00000000000..fae5121755b --- /dev/null +++ b/proxy/http3/Http3FrameDispatcher.h @@ -0,0 +1,50 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include "QUICApplication.h" +#include "Http3Frame.h" +#include "Http3FrameHandler.h" +#include + +class Http3FrameDispatcher +{ +public: + Http3ErrorUPtr on_read_ready(QUICStreamIO &stream_io, uint64_t &nread); + + void add_handler(Http3FrameHandler *handler); + +private: + enum READING_STATE { + READING_TYPE_LEN, + READING_LENGTH_LEN, + READING_PAYLOAD_LEN, + READING_PAYLOAD, + } _reading_state = READING_TYPE_LEN; + int64_t _reading_frame_type_len; + int64_t _reading_frame_length_len; + uint64_t _reading_frame_payload_len; + Http3FrameFactory _frame_factory; + std::vector _handlers[256]; +}; diff --git a/proxy/http3/Http3FrameGenerator.h b/proxy/http3/Http3FrameGenerator.h new file mode 100644 index 00000000000..6da188f6070 --- /dev/null +++ b/proxy/http3/Http3FrameGenerator.h @@ -0,0 +1,34 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include "Http3Frame.h" + +class Http3FrameGenerator +{ +public: + virtual ~Http3FrameGenerator(){}; + virtual Http3FrameUPtr generate_frame(uint16_t max_size) = 0; + virtual bool is_done() const = 0; +}; diff --git a/proxy/http3/Http3FrameHandler.h b/proxy/http3/Http3FrameHandler.h new file mode 100644 index 00000000000..e3c1f97bf1a --- /dev/null +++ b/proxy/http3/Http3FrameHandler.h @@ -0,0 +1,36 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include +#include +#include + +class Http3FrameHandler +{ +public: + virtual ~Http3FrameHandler(){}; + virtual std::vector interests() = 0; + virtual Http3ErrorUPtr handle_frame(std::shared_ptr frame) = 0; +}; diff --git a/proxy/http3/Http3HeaderFramer.cc b/proxy/http3/Http3HeaderFramer.cc new file mode 100644 index 00000000000..0931acc36eb --- /dev/null +++ b/proxy/http3/Http3HeaderFramer.cc @@ -0,0 +1,112 @@ +/** @file + * + * A brief file description + * + * @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 "Http3HeaderFramer.h" + +#include "I_VIO.h" + +#include "HTTP.h" +#include "HTTP2.h" + +#include "Http3Frame.h" +#include "Http3Transaction.h" + +Http3HeaderFramer::Http3HeaderFramer(Http3Transaction *transaction, VIO *source, QPACK *qpack, uint64_t stream_id) + : _transaction(transaction), _source_vio(source), _qpack(qpack), _stream_id(stream_id) +{ + http_parser_init(&this->_http_parser); +} + +Http3FrameUPtr +Http3HeaderFramer::generate_frame(uint16_t max_size) +{ + ink_assert(!this->_transaction->is_response_header_sent()); + + if (!this->_header_block) { + // this->_header_block will be filled if it is ready + this->_generate_header_block(); + } + + if (this->_header_block) { + // Create frames on demand base on max_size since we don't know how much we can write now + uint64_t len = std::min(this->_header_block_len - this->_header_block_wrote, static_cast(max_size)); + + Http3FrameUPtr frame = Http3FrameFactory::create_headers_frame(this->_header_block_reader, len); + + this->_header_block_wrote += len; + + if (this->_header_block_len == this->_header_block_wrote) { + this->_sent_all_data = true; + } + return frame; + } else { + return Http3FrameFactory::create_null_frame(); + } +} + +bool +Http3HeaderFramer::is_done() const +{ + return this->_sent_all_data; +} + +void +Http3HeaderFramer::_convert_header_from_1_1_to_3(HTTPHdr *h3_hdrs, HTTPHdr *h1_hdrs) +{ + http2_generate_h2_header_from_1_1(h1_hdrs, h3_hdrs); +} + +void +Http3HeaderFramer::_generate_header_block() +{ + // Prase response header and generate header block + int bytes_used = 0; + ParseResult parse_result = PARSE_RESULT_ERROR; + + if (this->_transaction->direction() == NET_VCONNECTION_OUT) { + this->_header.create(HTTP_TYPE_REQUEST); + parse_result = this->_header.parse_req(&this->_http_parser, this->_source_vio->get_reader(), &bytes_used, false); + } else { + this->_header.create(HTTP_TYPE_RESPONSE); + parse_result = this->_header.parse_resp(&this->_http_parser, this->_source_vio->get_reader(), &bytes_used, false); + } + this->_source_vio->ndone += this->_header.length_get(); + + switch (parse_result) { + case PARSE_RESULT_DONE: { + HTTPHdr h3_hdr; + this->_convert_header_from_1_1_to_3(&h3_hdr, &this->_header); + + this->_header_block = new_MIOBuffer(); + this->_header_block_reader = this->_header_block->alloc_reader(); + + this->_qpack->encode(this->_stream_id, h3_hdr, this->_header_block, this->_header_block_len); + break; + } + case PARSE_RESULT_CONT: + break; + default: + Debug("http3_trans", "Ignore ivalid headers"); + break; + } +} diff --git a/proxy/http3/Http3HeaderFramer.h b/proxy/http3/Http3HeaderFramer.h new file mode 100644 index 00000000000..55ce58627a7 --- /dev/null +++ b/proxy/http3/Http3HeaderFramer.h @@ -0,0 +1,60 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include "hdrs/HTTP.h" + +#include "QPACK.h" + +#include "Http3FrameGenerator.h" +#include "Http3Frame.h" + +class Http3Transaction; +class VIO; + +class Http3HeaderFramer : public Http3FrameGenerator +{ +public: + Http3HeaderFramer(Http3Transaction *transaction, VIO *source, QPACK *qpack, uint64_t stream_id); + + // Http3FrameGenerator + Http3FrameUPtr generate_frame(uint16_t max_size) override; + bool is_done() const override; + +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; + HTTPParser _http_parser; + HTTPHdr _header; + + void _convert_header_from_1_1_to_3(HTTPHdr *h3_hdrs, HTTPHdr *h1_hdrs); + void _generate_header_block(); +}; diff --git a/proxy/http3/Http3HeaderVIOAdaptor.cc b/proxy/http3/Http3HeaderVIOAdaptor.cc new file mode 100644 index 00000000000..6a55ae7a6e3 --- /dev/null +++ b/proxy/http3/Http3HeaderVIOAdaptor.cc @@ -0,0 +1,61 @@ +/** @file + + A brief file description + + @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 "Http3HeaderVIOAdaptor.h" + +#include "I_VIO.h" +#include "HTTP.h" + +Http3HeaderVIOAdaptor::Http3HeaderVIOAdaptor(HTTPHdr *hdr, QPACK *qpack, Continuation *cont, uint64_t stream_id) + : _request_header(hdr), _qpack(qpack), _cont(cont), _stream_id(stream_id) +{ +} + +std::vector +Http3HeaderVIOAdaptor::interests() +{ + return {Http3FrameType::HEADERS}; +} + +Http3ErrorUPtr +Http3HeaderVIOAdaptor::handle_frame(std::shared_ptr frame) +{ + ink_assert(frame->type() == Http3FrameType::HEADERS); + const Http3HeadersFrame *hframe = dynamic_cast(frame.get()); + + int res = this->_qpack->decode(this->_stream_id, hframe->header_block(), hframe->header_block_length(), *this->_request_header, + this->_cont); + + if (res == 0) { + // When decoding is not blocked, continuation should be called directly? + } else if (res == 1) { + // Decoding is blocked. + Debug("http3", "Decoding is blocked. DecodeRequest is scheduled"); + } else if (res < 0) { + Debug("http3", "Error on decoding header (%d)", res); + } else { + ink_abort("should not be here"); + } + + return Http3ErrorUPtr(new Http3NoError()); +} diff --git a/proxy/http3/Http3HeaderVIOAdaptor.h b/proxy/http3/Http3HeaderVIOAdaptor.h new file mode 100644 index 00000000000..1df2b71a75f --- /dev/null +++ b/proxy/http3/Http3HeaderVIOAdaptor.h @@ -0,0 +1,44 @@ +/** @file + + A brief file description + + @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. + */ + +#pragma once + +#include "QPACK.h" + +#include "Http3FrameHandler.h" + +// TODO: rename, this is not VIOAdoptor anymore +class Http3HeaderVIOAdaptor : public Http3FrameHandler +{ +public: + Http3HeaderVIOAdaptor(HTTPHdr *hdr, QPACK *qpack, Continuation *cont, uint64_t stream_id); + // Http3FrameHandler + std::vector interests() override; + Http3ErrorUPtr handle_frame(std::shared_ptr frame) override; + +private: + HTTPHdr *_request_header = nullptr; + QPACK *_qpack = nullptr; + Continuation *_cont = nullptr; + uint64_t _stream_id = 0; +}; diff --git a/proxy/http3/Http3Session.cc b/proxy/http3/Http3Session.cc new file mode 100644 index 00000000000..e552f953f9d --- /dev/null +++ b/proxy/http3/Http3Session.cc @@ -0,0 +1,231 @@ +/** @file + + A brief file description + + @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 "Http3Session.h" + +#include "Http3.h" + +// +// HQSession +// +HQSession ::~HQSession() +{ + for (HQTransaction *t = this->_transaction_list.head; t; t = static_cast(t->link.next)) { + delete t; + } +} + +void +HQSession::add_transaction(HQTransaction *trans) +{ + this->_transaction_list.enqueue(trans); + + return; +} + +HQTransaction * +HQSession::get_transaction(QUICStreamId id) +{ + for (HQTransaction *t = this->_transaction_list.head; t; t = static_cast(t->link.next)) { + if (t->get_transaction_id() == static_cast(id)) { + return t; + } + } + + return nullptr; +} + +VIO * +HQSession::do_io_read(Continuation *c, int64_t nbytes, MIOBuffer *buf) +{ + ink_assert(false); + return nullptr; +} + +VIO * +HQSession::do_io_write(Continuation *c, int64_t nbytes, IOBufferReader *buf, bool owner) +{ + ink_assert(false); + return nullptr; +} + +void +HQSession::do_io_close(int lerrno) +{ + // TODO + return; +} + +void +HQSession::do_io_shutdown(ShutdownHowTo_t howto) +{ + ink_assert(false); + return; +} + +void +HQSession::reenable(VIO *vio) +{ + ink_assert(false); + return; +} + +void +HQSession::new_connection(NetVConnection *new_vc, MIOBuffer *iobuf, IOBufferReader *reade) +{ + this->con_id = static_cast(reinterpret_cast(new_vc))->connection_id(); + + return; +} + +void +HQSession::start() +{ + ink_assert(false); + return; +} + +void +HQSession::destroy() +{ + ink_assert(false); + return; +} + +void +HQSession::release(ProxyTransaction *trans) +{ + return; +} + +NetVConnection * +HQSession::get_netvc() const +{ + return this->_client_vc; +} + +int +HQSession::get_transact_count() const +{ + return 0; +} + +// +// Http3Session +// +Http3Session::Http3Session(NetVConnection *vc) : HQSession(vc) +{ + this->_local_qpack = new QPACK(static_cast(vc), HTTP3_DEFAULT_MAX_HEADER_LIST_SIZE, + HTTP3_DEFAULT_HEADER_TABLE_SIZE, HTTP3_DEFAULT_QPACK_BLOCKED_STREAMS); + this->_remote_qpack = new QPACK(static_cast(vc), HTTP3_DEFAULT_MAX_HEADER_LIST_SIZE, + HTTP3_DEFAULT_HEADER_TABLE_SIZE, HTTP3_DEFAULT_QPACK_BLOCKED_STREAMS); +} + +Http3Session::~Http3Session() +{ + this->_client_vc = nullptr; + delete this->_local_qpack; + delete this->_remote_qpack; +} + +const char * +Http3Session::get_protocol_string() const +{ + return IP_PROTO_TAG_HTTP_3.data(); +} + +int +Http3Session::populate_protocol(std::string_view *result, int size) const +{ + int retval = 0; + if (size > retval) { + result[retval++] = IP_PROTO_TAG_HTTP_3; + if (size > retval) { + retval += super::populate_protocol(result + retval, size - retval); + } + } + return retval; +} + +void +Http3Session::increment_current_active_client_connections_stat() +{ + // TODO Implement stats +} + +void +Http3Session::decrement_current_active_client_connections_stat() +{ + // TODO Implement stats +} + +QPACK * +Http3Session::local_qpack() +{ + return this->_local_qpack; +} + +QPACK * +Http3Session::remote_qpack() +{ + return this->_remote_qpack; +} + +// +// Http09Session +// +Http09Session::~Http09Session() +{ + this->_client_vc = nullptr; +} + +const char * +Http09Session::get_protocol_string() const +{ + return IP_PROTO_TAG_HTTP_QUIC.data(); +} + +int +Http09Session::populate_protocol(std::string_view *result, int size) const +{ + int retval = 0; + if (size > retval) { + result[retval++] = IP_PROTO_TAG_HTTP_QUIC; + if (size > retval) { + retval += super::populate_protocol(result + retval, size - retval); + } + } + return retval; +} + +void +Http09Session::increment_current_active_client_connections_stat() +{ + // TODO Implement stats +} + +void +Http09Session::decrement_current_active_client_connections_stat() +{ + // TODO Implement stats +} diff --git a/proxy/http3/Http3Session.h b/proxy/http3/Http3Session.h new file mode 100644 index 00000000000..49fb40c185f --- /dev/null +++ b/proxy/http3/Http3Session.h @@ -0,0 +1,105 @@ +/** @file + + A brief file description + + @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. + */ + +#pragma once + +#include "ProxySession.h" +#include "Http3Transaction.h" +#include "QPACK.h" + +class HQSession : public ProxySession +{ +public: + using super = ProxySession; ///< Parent type + + HQSession(NetVConnection *vc) : _client_vc(vc){}; + virtual ~HQSession(); + + // Implement VConnection interface + VIO *do_io_read(Continuation *c, int64_t nbytes = INT64_MAX, MIOBuffer *buf = nullptr) override; + VIO *do_io_write(Continuation *c = nullptr, int64_t nbytes = INT64_MAX, IOBufferReader *buf = 0, bool owner = false) override; + void do_io_close(int lerrno = -1) override; + void do_io_shutdown(ShutdownHowTo_t howto) override; + void reenable(VIO *vio) override; + + // Implement ProxySession interface + void new_connection(NetVConnection *new_vc, MIOBuffer *iobuf, IOBufferReader *reader) override; + void start() override; + void destroy() override; + void release(ProxyTransaction *trans) override; + NetVConnection *get_netvc() const override; + int get_transact_count() const override; + + // HQSession + void add_transaction(HQTransaction *); + HQTransaction *get_transaction(QUICStreamId); + +protected: + NetVConnection *_client_vc = nullptr; + +private: + // this should be unordered map? + Queue _transaction_list; +}; + +class Http3Session : public HQSession +{ +public: + using super = HQSession; ///< Parent type + + Http3Session(NetVConnection *vc); + ~Http3Session(); + + // ProxySession interface + const char *get_protocol_string() const override; + int populate_protocol(std::string_view *result, int size) const override; + void increment_current_active_client_connections_stat() override; + void decrement_current_active_client_connections_stat() override; + + QPACK *local_qpack(); + QPACK *remote_qpack(); + +private: + QPACK *_remote_qpack = nullptr; // QPACK for decoding + QPACK *_local_qpack = nullptr; // QPACK for encoding +}; + +/** + Only for interop. Will be removed. + */ +class Http09Session : public HQSession +{ +public: + using super = HQSession; ///< Parent type + + Http09Session(NetVConnection *vc) : HQSession(vc) {} + ~Http09Session(); + + // ProxySession interface + const char *get_protocol_string() const override; + int populate_protocol(std::string_view *result, int size) const override; + void increment_current_active_client_connections_stat() override; + void decrement_current_active_client_connections_stat() override; + +private: +}; diff --git a/proxy/http3/Http3SessionAccept.cc b/proxy/http3/Http3SessionAccept.cc new file mode 100644 index 00000000000..9763f95c067 --- /dev/null +++ b/proxy/http3/Http3SessionAccept.cc @@ -0,0 +1,100 @@ +/** @file + + A brief file description + + @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 "Http3SessionAccept.h" + +#include "P_Net.h" +#include "I_Machine.h" +#include "IPAllow.h" + +#include "Http09App.h" +#include "Http3App.h" + +Http3SessionAccept::Http3SessionAccept(const HttpSessionAccept::Options &_o) : SessionAccept(nullptr), options(_o) +{ + SET_HANDLER(&Http3SessionAccept::mainEvent); +} + +Http3SessionAccept::~Http3SessionAccept() {} + +bool +Http3SessionAccept::accept(NetVConnection *netvc, MIOBuffer *iobuf, IOBufferReader *reader) +{ + sockaddr const *client_ip = netvc->get_remote_addr(); + IpAllow::ACL session_acl = IpAllow::match(client_ip, IpAllow::SRC_ADDR); + if (!session_acl.isValid()) { + ip_port_text_buffer ipb; + Warning("QUIC client '%s' prohibited by ip-allow policy", ats_ip_ntop(client_ip, ipb, sizeof(ipb))); + return false; + } + + netvc->attributes = this->options.transport_type; + + QUICNetVConnection *qvc = static_cast(netvc); + + if (is_debug_tag_set("http3")) { + ip_port_text_buffer ipb; + + Debug("http3", "[%s] accepted connection from %s transport type = %d", qvc->cids().data(), + ats_ip_nptop(client_ip, ipb, sizeof(ipb)), netvc->attributes); + } + + std::string_view alpn = qvc->negotiated_application_name(); + + if (alpn.empty() || IP_PROTO_TAG_HTTP_QUIC.compare(alpn) == 0) { + if (alpn.empty()) { + Debug("http3", "[%s] start HTTP/0.9 app (ALPN=null)", qvc->cids().data()); + } else { + Debug("http3", "[%s] start HTTP/0.9 app (ALPN=%s)", qvc->cids().data(), IP_PROTO_TAG_HTTP_QUIC.data()); + } + + new Http09App(qvc, std::move(session_acl), this->options); + } else if (IP_PROTO_TAG_HTTP_3.compare(alpn) == 0) { + Debug("http3", "[%s] start HTTP/3 app (ALPN=%s)", qvc->cids().data(), IP_PROTO_TAG_HTTP_3.data()); + + Http3App *app = new Http3App(qvc, std::move(session_acl), this->options); + app->start(); + } else { + ink_abort("Negotiated App Name is unknown"); + } + + return true; +} + +int +Http3SessionAccept::mainEvent(int event, void *data) +{ + NetVConnection *netvc; + ink_release_assert(event == NET_EVENT_ACCEPT || event == EVENT_ERROR); + ink_release_assert((event == NET_EVENT_ACCEPT) ? (data != nullptr) : (1)); + + if (event == NET_EVENT_ACCEPT) { + netvc = static_cast(data); + if (!this->accept(netvc, nullptr, nullptr)) { + netvc->do_io_close(); + } + return EVENT_CONT; + } + + return EVENT_CONT; +} diff --git a/proxy/http3/Http3SessionAccept.h b/proxy/http3/Http3SessionAccept.h new file mode 100644 index 00000000000..8bb388e3daf --- /dev/null +++ b/proxy/http3/Http3SessionAccept.h @@ -0,0 +1,57 @@ +/** @file + + A brief file description + + @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. + */ + +#ifndef __HTTP_QUIC_SESSION_ACCEPT_H__ +#define __HTTP_QUIC_SESSION_ACCEPT_H__ + +#include "tscore/ink_platform.h" +#include "I_Net.h" + +// XXX HttpSessionAccept::Options needs to be refactored and separated from HttpSessionAccept so that +// it can generically apply to all protocol implementations. +#include "http/HttpSessionAccept.h" + +// HTTP/QUIC Session Accept. +// +// HTTP/QUIC needs to be explicitly enabled on a server port. The syntax is different for SSL and raw +// ports. The example below configures QUIC on port 443 (with TLS). +// +// CONFIG proxy.config.http.server_ports STRING 443:quic + +class Http3SessionAccept : public SessionAccept +{ +public: + explicit Http3SessionAccept(const HttpSessionAccept::Options &); + ~Http3SessionAccept(); + + bool accept(NetVConnection *, MIOBuffer *, IOBufferReader *); + int mainEvent(int event, void *netvc); + +private: + Http3SessionAccept(const Http3SessionAccept &); + Http3SessionAccept &operator=(const Http3SessionAccept &); + + HttpSessionAccept::Options options; +}; + +#endif // __HTTP_QUIC_SESSION_ACCEPT_H__ diff --git a/proxy/http3/Http3StreamDataVIOAdaptor.cc b/proxy/http3/Http3StreamDataVIOAdaptor.cc new file mode 100644 index 00000000000..e5b6875c209 --- /dev/null +++ b/proxy/http3/Http3StreamDataVIOAdaptor.cc @@ -0,0 +1,47 @@ +/** @file + + A brief file description + + @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 "Http3StreamDataVIOAdaptor.h" +#include "I_VIO.h" + +Http3StreamDataVIOAdaptor::Http3StreamDataVIOAdaptor(VIO *sink) : _sink_vio(sink) {} + +std::vector +Http3StreamDataVIOAdaptor::interests() +{ + return {Http3FrameType::DATA}; +} + +Http3ErrorUPtr +Http3StreamDataVIOAdaptor::handle_frame(std::shared_ptr frame) +{ + ink_assert(frame->type() == Http3FrameType::DATA); + const Http3DataFrame *dframe = dynamic_cast(frame.get()); + + SCOPED_MUTEX_LOCK(lock, this->_sink_vio->mutex, this_ethread()); + + MIOBuffer *writer = this->_sink_vio->get_writer(); + writer->write(dframe->payload(), dframe->payload_length()); + + return Http3ErrorUPtr(new Http3NoError()); +} diff --git a/proxy/http3/Http3StreamDataVIOAdaptor.h b/proxy/http3/Http3StreamDataVIOAdaptor.h new file mode 100644 index 00000000000..f62fd319c64 --- /dev/null +++ b/proxy/http3/Http3StreamDataVIOAdaptor.h @@ -0,0 +1,41 @@ +/** @file + + A brief file description + + @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. + */ + +#pragma once + +#include "Http3FrameHandler.h" + +class VIO; + +class Http3StreamDataVIOAdaptor : public Http3FrameHandler +{ +public: + Http3StreamDataVIOAdaptor(VIO *sink); + + // Http3FrameHandler + std::vector interests() override; + Http3ErrorUPtr handle_frame(std::shared_ptr frame) override; + +private: + VIO *_sink_vio = nullptr; +}; diff --git a/proxy/http3/Http3Transaction.cc b/proxy/http3/Http3Transaction.cc new file mode 100644 index 00000000000..b72f33a9b02 --- /dev/null +++ b/proxy/http3/Http3Transaction.cc @@ -0,0 +1,851 @@ +/** @file + + A brief file description + + @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 "Http3Transaction.h" + +#include "QUICDebugNames.h" + +#include "Http3Session.h" +#include "Http3StreamDataVIOAdaptor.h" +#include "Http3HeaderVIOAdaptor.h" +#include "Http3HeaderFramer.h" +#include "Http3DataFramer.h" +#include "HttpSM.h" +#include "HTTP2.h" + +#define Http3TransDebug(fmt, ...) \ + Debug("http3_trans", "[%s] [%" PRIx32 "] " fmt, \ + static_cast(reinterpret_cast(this->proxy_ssn->get_netvc()))->cids().data(), \ + this->get_transaction_id(), ##__VA_ARGS__) + +#define Http3TransVDebug(fmt, ...) \ + Debug("v_http3_trans", "[%s] [%" PRIx32 "] " fmt, \ + static_cast(reinterpret_cast(this->proxy_ssn->get_netvc()))->cids().data(), \ + this->get_transaction_id(), ##__VA_ARGS__) + +// static void +// dump_io_buffer(IOBufferReader *reader) +// { +// IOBufferReader *debug_reader = reader->clone(); +// uint8_t msg[1024] = {0}; +// int64_t msg_len = 1024; +// int64_t read_len = debug_reader->read(msg, msg_len); +// Debug("v_http3_trans", "len=%" PRId64 "\n%s\n", read_len, msg); +// } + +// +// HQTransaction +// +HQTransaction::HQTransaction(HQSession *session, QUICStreamIO *stream_io) : super(), _stream_io(stream_io) +{ + this->mutex = new_ProxyMutex(); + this->_thread = this_ethread(); + + this->set_proxy_ssn(session); + + this->sm_reader = this->_read_vio_buf.alloc_reader(); + + HTTPType http_type = HTTP_TYPE_UNKNOWN; + if (this->direction() == NET_VCONNECTION_OUT) { + http_type = HTTP_TYPE_RESPONSE; + } else { + http_type = HTTP_TYPE_REQUEST; + } + + this->_header.create(http_type); +} + +HQTransaction::~HQTransaction() +{ + this->_header.destroy(); +} + +void +HQTransaction::set_active_timeout(ink_hrtime timeout_in) +{ + if (this->proxy_ssn) { + this->proxy_ssn->set_active_timeout(timeout_in); + } +} + +void +HQTransaction::set_inactivity_timeout(ink_hrtime timeout_in) +{ + if (this->proxy_ssn) { + this->proxy_ssn->set_inactivity_timeout(timeout_in); + } +} + +void +HQTransaction::cancel_inactivity_timeout() +{ + if (this->proxy_ssn) { + this->proxy_ssn->cancel_inactivity_timeout(); + } +} + +void +HQTransaction::release(IOBufferReader *r) +{ + super::release(r); + this->do_io_close(); + this->current_reader = nullptr; +} + +bool +HQTransaction::allow_half_open() const +{ + return false; +} + +VIO * +HQTransaction::do_io_read(Continuation *c, int64_t nbytes, MIOBuffer *buf) +{ + if (buf) { + this->_read_vio.buffer.writer_for(buf); + } else { + this->_read_vio.buffer.clear(); + } + + this->_read_vio.mutex = c ? c->mutex : this->mutex; + this->_read_vio.cont = c; + this->_read_vio.nbytes = nbytes; + this->_read_vio.ndone = 0; + this->_read_vio.vc_server = this; + this->_read_vio.op = VIO::READ; + + this->_process_read_vio(); + this->_send_tracked_event(this->_read_event, VC_EVENT_READ_READY, &this->_read_vio); + + return &this->_read_vio; +} + +VIO * +HQTransaction::do_io_write(Continuation *c, int64_t nbytes, IOBufferReader *buf, bool owner) +{ + if (buf) { + this->_write_vio.buffer.reader_for(buf); + } else { + this->_write_vio.buffer.clear(); + } + + this->_write_vio.mutex = c ? c->mutex : this->mutex; + this->_write_vio.cont = c; + this->_write_vio.nbytes = nbytes; + this->_write_vio.ndone = 0; + this->_write_vio.vc_server = this; + this->_write_vio.op = VIO::WRITE; + + this->_process_write_vio(); + this->_send_tracked_event(this->_write_event, VC_EVENT_WRITE_READY, &this->_write_vio); + + return &this->_write_vio; +} + +void +HQTransaction::do_io_close(int lerrno) +{ + if (this->_read_event) { + this->_read_event->cancel(); + this->_read_event = nullptr; + } + + if (this->_write_event) { + this->_write_event->cancel(); + this->_write_event = nullptr; + } + + this->_read_vio.buffer.clear(); + this->_read_vio.nbytes = 0; + this->_read_vio.op = VIO::NONE; + this->_read_vio.cont = nullptr; + + this->_write_vio.buffer.clear(); + this->_write_vio.nbytes = 0; + this->_write_vio.op = VIO::NONE; + this->_write_vio.cont = nullptr; + + this->proxy_ssn->do_io_close(lerrno); +} + +void +HQTransaction::do_io_shutdown(ShutdownHowTo_t howto) +{ + return; +} + +void +HQTransaction::reenable(VIO *vio) +{ + if (vio->op == VIO::READ) { + int64_t len = this->_process_read_vio(); + this->_stream_io->read_reenable(); + + if (len > 0) { + this->_signal_read_event(); + } + } else if (vio->op == VIO::WRITE) { + int64_t len = this->_process_write_vio(); + this->_stream_io->write_reenable(); + + if (len > 0) { + this->_signal_write_event(); + } + } +} + +void +HQTransaction::destroy() +{ + current_reader = nullptr; +} + +void +HQTransaction::transaction_done() +{ + // TODO: start closing transaction + return; +} + +int +HQTransaction::get_transaction_id() const +{ + return this->_stream_io->stream_id(); +} + +void +HQTransaction::increment_client_transactions_stat() +{ + // TODO +} + +void +HQTransaction::decrement_client_transactions_stat() +{ + // TODO +} + +NetVConnectionContext_t +HQTransaction::direction() const +{ + return this->proxy_ssn->get_netvc()->get_context(); +} + +/** + * @brief Replace existing event only if the new event is different than the inprogress event + */ +Event * +HQTransaction::_send_tracked_event(Event *event, int send_event, VIO *vio) +{ + if (event != nullptr) { + if (event->callback_event != send_event) { + event->cancel(); + event = nullptr; + } + } + + if (event == nullptr) { + event = this_ethread()->schedule_imm(this, send_event, vio); + } + + return event; +} + +/** + * @brief Signal event to this->_read_vio.cont + */ +void +HQTransaction::_signal_read_event() +{ + if (this->_read_vio.cont == nullptr || this->_read_vio.op == VIO::NONE) { + return; + } + int event = this->_read_vio.ntodo() ? VC_EVENT_READ_READY : VC_EVENT_READ_COMPLETE; + + MUTEX_TRY_LOCK(lock, this->_read_vio.mutex, this_ethread()); + if (lock.is_locked()) { + this->_read_vio.cont->handleEvent(event, &this->_read_vio); + } else { + this_ethread()->schedule_imm(this->_read_vio.cont, event, &this->_read_vio); + } + + Http3TransVDebug("%s (%d)", get_vc_event_name(event), event); +} + +/** + * @brief Signal event to this->_write_vio.cont + */ +void +HQTransaction::_signal_write_event() +{ + if (this->_write_vio.cont == nullptr || this->_write_vio.op == VIO::NONE) { + return; + } + int event = this->_write_vio.ntodo() ? VC_EVENT_WRITE_READY : VC_EVENT_WRITE_COMPLETE; + + MUTEX_TRY_LOCK(lock, this->_write_vio.mutex, this_ethread()); + if (lock.is_locked()) { + this->_write_vio.cont->handleEvent(event, &this->_write_vio); + } else { + this_ethread()->schedule_imm(this->_write_vio.cont, event, &this->_write_vio); + } + + Http3TransVDebug("%s (%d)", get_vc_event_name(event), event); +} + +// +// Http3Transaction +// +Http3Transaction::Http3Transaction(Http3Session *session, QUICStreamIO *stream_io) : super(session, stream_io) +{ + static_cast(this->proxy_ssn)->add_transaction(static_cast(this)); + + this->_header_framer = new Http3HeaderFramer(this, &this->_write_vio, session->local_qpack(), stream_io->stream_id()); + this->_data_framer = new Http3DataFramer(this, &this->_write_vio); + this->_frame_collector.add_generator(this->_header_framer); + this->_frame_collector.add_generator(this->_data_framer); + // this->_frame_collector.add_generator(this->_push_controller); + + this->_header_handler = new Http3HeaderVIOAdaptor(&this->_header, session->remote_qpack(), this, stream_io->stream_id()); + this->_data_handler = new Http3StreamDataVIOAdaptor(&this->_read_vio); + + this->_frame_dispatcher.add_handler(this->_header_handler); + this->_frame_dispatcher.add_handler(this->_data_handler); + + SET_HANDLER(&Http3Transaction::state_stream_open); +} + +Http3Transaction::~Http3Transaction() +{ + delete this->_header_framer; + delete this->_data_framer; + delete this->_header_handler; + delete this->_data_handler; +} + +int +Http3Transaction::state_stream_open(int event, void *edata) +{ + // TODO: should check recursive call? + if (this->_thread != this_ethread()) { + // Send on to the owning thread + if (this->_cross_thread_event == nullptr) { + this->_cross_thread_event = this->_thread->schedule_imm(this, event, edata); + } + return 0; + } + + SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread()); + + Event *e = static_cast(edata); + if (e == this->_cross_thread_event) { + this->_cross_thread_event = nullptr; + } + + switch (event) { + case VC_EVENT_READ_READY: + case VC_EVENT_READ_COMPLETE: { + Http3TransVDebug("%s (%d)", get_vc_event_name(event), event); + int64_t len = this->_process_read_vio(); + // if no progress, don't need to signal + if (len > 0) { + this->_signal_read_event(); + } + this->_stream_io->read_reenable(); + + break; + } + case VC_EVENT_WRITE_READY: + case VC_EVENT_WRITE_COMPLETE: { + Http3TransVDebug("%s (%d)", get_vc_event_name(event), event); + int64_t len = this->_process_write_vio(); + // if no progress, don't need to signal + if (len > 0) { + this->_signal_write_event(); + } + this->_stream_io->write_reenable(); + + break; + } + case VC_EVENT_EOS: + case VC_EVENT_ERROR: + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: { + Http3TransVDebug("%s (%d)", get_vc_event_name(event), event); + break; + } + case QPACK_EVENT_DECODE_COMPLETE: { + Http3TransVDebug("%s (%d)", "QPACK_EVENT_DECODE_COMPLETE", event); + int res = this->_on_qpack_decode_complete(); + if (res) { + // If READ_READY event is scheduled, should it be canceled? + this->_signal_read_event(); + } + break; + } + case QPACK_EVENT_DECODE_FAILED: { + Http3TransVDebug("%s (%d)", "QPACK_EVENT_DECODE_FAILED", event); + // FIXME: handle error + break; + } + default: + Http3TransDebug("Unknown event %d", event); + } + + return EVENT_DONE; +} + +int +Http3Transaction::state_stream_closed(int event, void *data) +{ + Http3TransVDebug("%s (%d)", get_vc_event_name(event), event); + + switch (event) { + case VC_EVENT_READ_READY: + case VC_EVENT_READ_COMPLETE: { + // ignore + break; + } + case VC_EVENT_WRITE_READY: + case VC_EVENT_WRITE_COMPLETE: { + // ignore + break; + } + case VC_EVENT_EOS: + case VC_EVENT_ERROR: + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: { + // TODO + Http3TransVDebug("%s (%d)", get_vc_event_name(event), event); + break; + } + default: + Http3TransDebug("Unknown event %d", event); + } + + return EVENT_DONE; +} + +void +Http3Transaction::do_io_close(int lerrno) +{ + SET_HANDLER(&Http3Transaction::state_stream_closed); + super::do_io_close(lerrno); +} + +bool +Http3Transaction::is_response_header_sent() const +{ + return this->_header_framer->is_done(); +} + +bool +Http3Transaction::is_response_body_sent() const +{ + return this->_data_framer->is_done(); +} + +int64_t +Http3Transaction::_process_read_vio() +{ + if (this->_read_vio.cont == nullptr || this->_read_vio.op == VIO::NONE) { + return 0; + } + + if (this->_thread != this_ethread()) { + SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread()); + if (this->_cross_thread_event == nullptr) { + // Send to the right thread + this->_cross_thread_event = this->_thread->schedule_imm(this, VC_EVENT_READ_READY, nullptr); + } + return 0; + } + + SCOPED_MUTEX_LOCK(lock, this->_read_vio.mutex, this_ethread()); + + uint64_t nread = 0; + this->_frame_dispatcher.on_read_ready(*this->_stream_io, nread); + return nread; +} + +int64_t +Http3Transaction::_process_write_vio() +{ + if (this->_write_vio.cont == nullptr || this->_write_vio.op == VIO::NONE) { + return 0; + } + + if (this->_thread != this_ethread()) { + SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread()); + if (this->_cross_thread_event == nullptr) { + // Send to the right thread + this->_cross_thread_event = this->_thread->schedule_imm(this, VC_EVENT_WRITE_READY, nullptr); + } + return 0; + } + + SCOPED_MUTEX_LOCK(lock, this->_write_vio.mutex, this_ethread()); + + size_t nwritten = 0; + this->_frame_collector.on_write_ready(this->_stream_io, nwritten); + + return nwritten; +} + +// Constant strings for pseudo headers +const char *HTTP3_VALUE_SCHEME = ":scheme"; +const char *HTTP3_VALUE_AUTHORITY = ":authority"; + +const unsigned HTTP3_LEN_SCHEME = countof(":scheme") - 1; +const unsigned HTTP3_LEN_AUTHORITY = countof(":authority") - 1; + +ParseResult +Http3Transaction::_convert_header_from_3_to_1_1(HTTPHdr *hdrs) +{ + // TODO: do HTTP/3 specific convert, if there + + if (http_hdr_type_get(hdrs->m_http) == HTTP_TYPE_REQUEST) { + // Dirty hack to bypass checks + MIMEField *field; + if ((field = hdrs->field_find(HTTP3_VALUE_SCHEME, HTTP3_LEN_SCHEME)) == nullptr) { + char value_s[] = "https"; + MIMEField *scheme_field = hdrs->field_create(HTTP3_VALUE_SCHEME, HTTP3_LEN_SCHEME); + scheme_field->value_set(hdrs->m_heap, hdrs->m_mime, value_s, sizeof(value_s) - 1); + hdrs->field_attach(scheme_field); + } + + if ((field = hdrs->field_find(HTTP3_VALUE_AUTHORITY, HTTP3_LEN_AUTHORITY)) == nullptr) { + char value_a[] = "localhost"; + MIMEField *authority_field = hdrs->field_create(HTTP3_VALUE_AUTHORITY, HTTP3_LEN_AUTHORITY); + authority_field->value_set(hdrs->m_heap, hdrs->m_mime, value_a, sizeof(value_a) - 1); + hdrs->field_attach(authority_field); + } + } + + return http2_convert_header_from_2_to_1_1(hdrs); +} + +int +Http3Transaction::_on_qpack_decode_complete() +{ + ParseResult res = this->_convert_header_from_3_to_1_1(&this->_header); + if (res == PARSE_RESULT_ERROR) { + Http3TransDebug("PARSE_RESULT_ERROR"); + return -1; + } + + // FIXME: response header might be delayed from first response body because of callback from QPACK + // Workaround fix for mixed response header and body + if (http_hdr_type_get(this->_header.m_http) == HTTP_TYPE_RESPONSE) { + return 0; + } + + SCOPED_MUTEX_LOCK(lock, this->_read_vio.mutex, this_ethread()); + MIOBuffer *writer = this->_read_vio.get_writer(); + + // TODO: Http2Stream::send_request has same logic. It originally comes from HttpSM::write_header_into_buffer. + // a). Make HttpSM::write_header_into_buffer static + // or + // b). Add interface to HTTPHdr to dump data + // or + // c). Add interface to HttpSM to handle HTTPHdr directly + int bufindex; + int dumpoffset = 0; + int done, tmp; + IOBufferBlock *block; + do { + bufindex = 0; + tmp = dumpoffset; + block = writer->get_current_block(); + if (!block) { + writer->add_block(); + block = writer->get_current_block(); + } + done = this->_header.print(block->end(), block->write_avail(), &bufindex, &tmp); + dumpoffset += bufindex; + writer->fill(bufindex); + if (!done) { + writer->add_block(); + } + } while (!done); + + return 1; +} + +// +// Http09Transaction +// +Http09Transaction::Http09Transaction(Http09Session *session, QUICStreamIO *stream_io) : super(session, stream_io) +{ + static_cast(this->proxy_ssn)->add_transaction(static_cast(this)); + + SET_HANDLER(&Http09Transaction::state_stream_open); +} + +Http09Transaction::~Http09Transaction() {} + +int +Http09Transaction::state_stream_open(int event, void *edata) +{ + // TODO: should check recursive call? + Http3TransVDebug("%s (%d)", get_vc_event_name(event), event); + + if (this->_thread != this_ethread()) { + // Send on to the owning thread + if (this->_cross_thread_event == nullptr) { + this->_cross_thread_event = this->_thread->schedule_imm(this, event, edata); + } + return 0; + } + + SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread()); + + Event *e = static_cast(edata); + if (e == this->_cross_thread_event) { + this->_cross_thread_event = nullptr; + } + + switch (event) { + case VC_EVENT_READ_READY: + case VC_EVENT_READ_COMPLETE: { + int64_t len = this->_process_read_vio(); + // if no progress, don't need to signal + if (len > 0) { + this->_signal_read_event(); + } + this->_stream_io->read_reenable(); + + break; + } + case VC_EVENT_WRITE_READY: + case VC_EVENT_WRITE_COMPLETE: { + int64_t len = this->_process_write_vio(); + if (len > 0) { + this->_signal_write_event(); + } + this->_stream_io->write_reenable(); + + break; + } + case VC_EVENT_EOS: + case VC_EVENT_ERROR: + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: { + Http3TransDebug("%d", event); + break; + } + default: + Http3TransDebug("Unknown event %d", event); + } + + return EVENT_DONE; +} + +void +Http09Transaction::do_io_close(int lerrno) +{ + SET_HANDLER(&Http09Transaction::state_stream_closed); + super::do_io_close(lerrno); +} + +int +Http09Transaction::state_stream_closed(int event, void *data) +{ + Http3TransVDebug("%s (%d)", get_vc_event_name(event), event); + + switch (event) { + case VC_EVENT_READ_READY: + case VC_EVENT_READ_COMPLETE: { + // ignore + break; + } + case VC_EVENT_WRITE_READY: + case VC_EVENT_WRITE_COMPLETE: { + // ignore + break; + } + case VC_EVENT_EOS: + case VC_EVENT_ERROR: + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: { + // TODO + break; + } + default: + Http3TransDebug("Unknown event %d", event); + } + + return EVENT_DONE; +} + +// Convert HTTP/0.9 to HTTP/1.1 +int64_t +Http09Transaction::_process_read_vio() +{ + if (this->_read_vio.cont == nullptr || this->_read_vio.op == VIO::NONE) { + return 0; + } + + if (this->_thread != this_ethread()) { + SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread()); + if (this->_cross_thread_event == nullptr) { + // Send to the right thread + this->_cross_thread_event = this->_thread->schedule_imm(this, VC_EVENT_READ_READY, nullptr); + } + return 0; + } + + SCOPED_MUTEX_LOCK(lock, this->_read_vio.mutex, this_ethread()); + + // Nuke this block when we drop 0.9 support + if (!this->_protocol_detected) { + uint8_t start[3]; + if (this->_stream_io->peek(start, 3) < 3) { + return 0; + } + // If the first two bit are 0 and 1, the 3rd byte is type field. + // Because there is no type value larger than 0x20, we can assume that the + // request is HTTP/0.9 if the value is larger than 0x20. + if (0x40 <= start[0] && start[0] < 0x80 && start[2] > 0x20) { + this->_legacy_request = true; + } + this->_protocol_detected = true; + } + + if (this->_legacy_request) { + uint64_t nread = 0; + MIOBuffer *writer = this->_read_vio.get_writer(); + + // Nuke this branch when we drop 0.9 support + if (!this->_client_req_header_complete) { + uint8_t buf[4096]; + int len = this->_stream_io->peek(buf, 4096); + // Check client request is complete or not + if (len < 2 || buf[len - 1] != '\n') { + return 0; + } + this->_stream_io->consume(len); + nread += len; + this->_client_req_header_complete = true; + + // Check "CRLF" or "LF" + int n = 2; + if (buf[len - 2] != '\r') { + n = 1; + } + + writer->write(buf, len - n); + // FIXME: Get hostname from SNI? + const char version[] = " HTTP/1.1\r\nHost: localhost\r\n\r\n"; + writer->write(version, sizeof(version)); + } else { + uint8_t buf[4096]; + int len; + while ((len = this->_stream_io->read(buf, 4096)) > 0) { + nread += len; + writer->write(buf, len); + } + } + + return nread; + // End of code for HTTP/0.9 + } else { + // Ignore malformed data + uint8_t buf[4096]; + int len; + uint64_t nread = 0; + + while ((len = this->_stream_io->read(buf, 4096)) > 0) { + nread += len; + } + + return nread; + } +} + +// FIXME: already defined somewhere? +static constexpr char http_1_1_version[] = "HTTP/1.1"; + +// Convert HTTP/1.1 to HTTP/0.9 +int64_t +Http09Transaction::_process_write_vio() +{ + if (this->_write_vio.cont == nullptr || this->_write_vio.op == VIO::NONE) { + return 0; + } + + if (this->_thread != this_ethread()) { + SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread()); + if (this->_cross_thread_event == nullptr) { + // Send to the right thread + this->_cross_thread_event = this->_thread->schedule_imm(this, VC_EVENT_WRITE_READY, nullptr); + } + return 0; + } + + SCOPED_MUTEX_LOCK(lock, this->_write_vio.mutex, this_ethread()); + + IOBufferReader *reader = this->_write_vio.get_reader(); + + if (this->_legacy_request) { + // This branch is for HTTP/0.9 + int64_t http_1_1_version_len = sizeof(http_1_1_version) - 1; + + if (reader->is_read_avail_more_than(http_1_1_version_len) && + memcmp(reader->start(), http_1_1_version, http_1_1_version_len) == 0) { + // Skip HTTP/1.1 response headers + IOBufferBlock *headers = reader->get_current_block(); + int64_t headers_size = headers->read_avail(); + reader->consume(headers_size); + this->_write_vio.ndone += headers_size; + } + + // Write HTTP/1.1 response body + int64_t bytes_avail = reader->read_avail(); + int64_t total_written = 0; + + while (total_written < bytes_avail) { + int64_t data_len = reader->block_read_avail(); + int64_t bytes_written = this->_stream_io->write(reader, data_len); + if (bytes_written <= 0) { + break; + } + + reader->consume(bytes_written); + this->_write_vio.ndone += bytes_written; + total_written += bytes_written; + } + + // NOTE: When Chunked Transfer Coding is supported, check ChunkedState of ChunkedHandler + // is CHUNK_READ_DONE and set FIN flag + if (this->_write_vio.ntodo() == 0) { + // The size of respons to client + this->_stream_io->write_done(); + } + + return total_written; + } else { + // nothing to do + return 0; + } +} diff --git a/proxy/http3/Http3Transaction.h b/proxy/http3/Http3Transaction.h new file mode 100644 index 00000000000..1bed5b058fd --- /dev/null +++ b/proxy/http3/Http3Transaction.h @@ -0,0 +1,148 @@ +/** @file + + A brief file description + + @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. + */ + +#pragma once + +#include "I_VConnection.h" +#include "ProxyTransaction.h" +#include "Http3FrameDispatcher.h" +#include "Http3FrameCollector.h" + +class QUICStreamIO; +class HQSession; +class Http09Session; +class Http3Session; +class Http3HeaderFramer; +class Http3DataFramer; + +class HQTransaction : public ProxyTransaction +{ +public: + using super = ProxyTransaction; + + HQTransaction(HQSession *session, QUICStreamIO *stream_io); + virtual ~HQTransaction(); + + // Implement ProxyClienTransaction interface + void set_active_timeout(ink_hrtime timeout_in) override; + void set_inactivity_timeout(ink_hrtime timeout_in) override; + void cancel_inactivity_timeout() override; + void transaction_done() override; + bool allow_half_open() const override; + void destroy() override; + void release(IOBufferReader *r) override; + int get_transaction_id() const override; + void increment_client_transactions_stat() override; + void decrement_client_transactions_stat() override; + + // VConnection interface + virtual VIO *do_io_read(Continuation *c, int64_t nbytes = INT64_MAX, MIOBuffer *buf = 0) override; + virtual VIO *do_io_write(Continuation *c = nullptr, int64_t nbytes = INT64_MAX, IOBufferReader *buf = 0, + bool owner = false) override; + virtual void do_io_close(int lerrno = -1) override; + virtual void do_io_shutdown(ShutdownHowTo_t) override; + virtual void reenable(VIO *) override; + + // HQTransaction + virtual int state_stream_open(int, void *) = 0; + virtual int state_stream_closed(int event, void *data) = 0; + NetVConnectionContext_t direction() const; + +protected: + virtual int64_t _process_read_vio() = 0; + virtual int64_t _process_write_vio() = 0; + Event *_send_tracked_event(Event *, int, VIO *); + void _signal_read_event(); + void _signal_write_event(); + + EThread *_thread = nullptr; + Event *_cross_thread_event = nullptr; + + MIOBuffer _read_vio_buf = CLIENT_CONNECTION_FIRST_READ_BUFFER_SIZE_INDEX; + QUICStreamIO *_stream_io = nullptr; + + VIO _read_vio; + VIO _write_vio; + Event *_read_event = nullptr; + Event *_write_event = nullptr; + + HTTPHdr _header; ///< HTTP header buffer for decoding +}; + +class Http3Transaction : public HQTransaction +{ +public: + using super = HQTransaction; + + Http3Transaction(Http3Session *session, QUICStreamIO *stream_io); + ~Http3Transaction(); + + int state_stream_open(int event, void *data) override; + int state_stream_closed(int event, void *data) override; + + void do_io_close(int lerrno = -1) override; + + bool is_response_header_sent() const; + bool is_response_body_sent() const; + +private: + int64_t _process_read_vio() override; + int64_t _process_write_vio() override; + + ParseResult _convert_header_from_3_to_1_1(HTTPHdr *hdr); + int _on_qpack_decode_complete(); + + // These are for HTTP/3 + Http3FrameDispatcher _frame_dispatcher; + Http3FrameCollector _frame_collector; + Http3FrameGenerator *_header_framer = nullptr; + Http3FrameGenerator *_data_framer = nullptr; + Http3FrameHandler *_header_handler = nullptr; + Http3FrameHandler *_data_handler = nullptr; +}; + +/** + Only for interop. Will be removed. + */ +class Http09Transaction : public HQTransaction +{ +public: + using super = HQTransaction; + + Http09Transaction(Http09Session *session, QUICStreamIO *stream_io); + ~Http09Transaction(); + + int state_stream_open(int event, void *data) override; + int state_stream_closed(int event, void *data) override; + + void do_io_close(int lerrno = -1) override; + +private: + int64_t _process_read_vio() override; + int64_t _process_write_vio() override; + + // These are for HTTP/0.9 + bool _protocol_detected = false; + bool _legacy_request = false; + bool _client_req_header_complete = false; +}; diff --git a/proxy/http3/Http3Types.cc b/proxy/http3/Http3Types.cc new file mode 100644 index 00000000000..77eb2158fb8 --- /dev/null +++ b/proxy/http3/Http3Types.cc @@ -0,0 +1,41 @@ +/** @file + * + * A brief file description + * + * @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 "Http3Types.h" + +Http3StreamType +Http3Stream::type(const uint8_t *buf) +{ + switch (*buf) { + case static_cast(Http3StreamType::CONTROL): + return Http3StreamType::CONTROL; + case static_cast(Http3StreamType::PUSH): + return Http3StreamType::PUSH; + case static_cast(Http3StreamType::QPACK_ENCODER): + return Http3StreamType::QPACK_ENCODER; + case static_cast(Http3StreamType::QPACK_DECODER): + return Http3StreamType::QPACK_DECODER; + default: + return Http3StreamType::UNKOWN; + } +} diff --git a/proxy/http3/Http3Types.h b/proxy/http3/Http3Types.h new file mode 100644 index 00000000000..c6bba88483f --- /dev/null +++ b/proxy/http3/Http3Types.h @@ -0,0 +1,152 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include "tscore/ink_platform.h" + +#include + +enum class Http3StreamType : uint8_t { + CONTROL = 0x00, ///< HTTP/3 + PUSH = 0x01, ///< HTTP/3 + QPACK_ENCODER = 0x02, ///< QPACK : encoder -> decoder + QPACK_DECODER = 0x03, ///< QPACK : decoder -> encoder + RESERVED = 0x21, + UNKOWN = 0xFF, +}; + +enum class Http3SettingsId : uint64_t { + HEADER_TABLE_SIZE = 0x01, ///< QPACK Settings + RESERVED_1 = 0x02, ///< HTTP/3 Settings + RESERVED_2 = 0x03, ///< HTTP/3 Settings + RESERVED_3 = 0x04, ///< HTTP/3 Settings + RESERVED_4 = 0x05, ///< HTTP/3 Settings + MAX_HEADER_LIST_SIZE = 0x06, ///< HTTP/3 Settings + QPACK_BLOCKED_STREAMS = 0x07, ///< QPACK Settings + NUM_PLACEHOLDERS = 0x09, ///< HTTP/3 Settings + UNKNOWN = 0x0A0A, +}; + +// Update Http3Frame::type(const uint8_t *) too when you modify this list +enum class Http3FrameType : uint64_t { + DATA = 0x00, + HEADERS = 0x01, + PRIORITY = 0x02, + CANCEL_PUSH = 0x03, + SETTINGS = 0x04, + PUSH_PROMISE = 0x05, + X_RESERVED_1 = 0x06, + GOAWAY = 0x07, + X_RESERVED_2 = 0x08, + X_RESERVED_3 = 0x09, + MAX_PUSH_ID = 0x0D, + DUPLICATE_PUSH_ID = 0x0E, + X_MAX_DEFINED = 0x0E, + UNKNOWN = 0xFF, +}; + +enum class Http3ErrorClass { + NONE, + APPLICATION, +}; + +// Actual error code of QPACK is not decided yet on qpack-05. It will be changed. +enum class Http3ErrorCode : uint16_t { + NO_ERROR = 0x0000, + WRONG_SETTING_DIRECTION = 0x0001, + PUSH_REFUSED = 0x0002, + INTERNAL_ERROR = 0x0003, + PUSH_ALREADY_IN_CACHE = 0x0004, + REQUEST_CANCELLED = 0x0005, + INCOMPLETE_REQUEST = 0x0006, + CONNECT_ERROR = 0x0007, + EXCESSIVE_LOAD = 0x0008, + VERSION_FALLBACK = 0x0009, + WRONG_STREAM = 0x000A, + LIMIT_EXCEEDED = 0x000B, + DUPLICATE_PUSH = 0x000C, + UNKNOWN_STREAM_TYPE = 0x000D, + WRONG_STREAM_COUNT = 0x000E, + CLOSED_CRITICAL_STREAM = 0x000F, + WRONG_STREAM_DIRECTION = 0x0010, + EARLY_RESPONSE = 0x0011, + MISSING_SETTINGS = 0x0012, + UNEXPECTED_FRAME = 0x0013, + REQUEST_REJECTED = 0x0014, + MALFORMED_FRAME = 0x0100, + QPACK_DECOMPRESSION_FAILED = 0x200, + QPACK_ENCODER_STREAM_ERROR = 0x201, + QPACK_DECODER_STREAM_ERROR = 0x202, +}; + +class Http3Error +{ +public: + virtual ~Http3Error() {} + uint16_t code(); + + Http3ErrorClass cls = Http3ErrorClass::NONE; + union { + Http3ErrorCode app_error_code; + }; + const char *msg = nullptr; + +protected: + Http3Error(){}; + Http3Error(const Http3ErrorCode error_code, const char *error_msg = nullptr) + : cls(Http3ErrorClass::APPLICATION), app_error_code(error_code), msg(error_msg){}; +}; + +class Http3NoError : public Http3Error +{ +public: + Http3NoError() : Http3Error() {} +}; + +class Http3ConnectionError : public Http3Error +{ +public: + Http3ConnectionError() : Http3Error() {} + Http3ConnectionError(const Http3ErrorCode error_code, const char *error_msg = nullptr) : Http3Error(error_code, error_msg){}; +}; + +class Http3Stream +{ +public: + static Http3StreamType type(const uint8_t *buf); +}; + +class Http3StreamError : public Http3Error +{ +public: + Http3StreamError() : Http3Error() {} + Http3StreamError(Http3Stream *s, const Http3ErrorCode error_code, const char *error_msg = nullptr) + : Http3Error(error_code, error_msg), stream(s){}; + + Http3Stream *stream; +}; + +using Http3ErrorUPtr = std::unique_ptr; +using Http3ConnectionErrorUPtr = std::unique_ptr; +using Http3StreamErrorUPtr = std::unique_ptr; diff --git a/proxy/http3/Makefile.am b/proxy/http3/Makefile.am new file mode 100644 index 00000000000..f5d4de5906f --- /dev/null +++ b/proxy/http3/Makefile.am @@ -0,0 +1,108 @@ +# +# Makefile.am for HTTP/3 +# +# 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. + +AM_CPPFLAGS += \ + $(iocore_include_dirs) \ + -I$(abs_top_srcdir)/proxy/api/ts \ + -I$(abs_top_srcdir)/lib \ + -I$(abs_top_srcdir)/lib/records \ + -I$(abs_top_srcdir)/mgmt \ + -I$(abs_top_srcdir)/mgmt/utils \ + -I$(abs_top_srcdir)/proxy \ + -I$(abs_top_srcdir)/proxy/http \ + -I$(abs_top_srcdir)/proxy/http2 \ + -I$(abs_top_srcdir)/proxy/hdrs \ + -I$(abs_top_srcdir)/proxy/shared \ + -I$(abs_top_srcdir)/proxy/http/remap \ + $(TS_INCLUDES) + +noinst_LIBRARIES = libhttp3.a + +libhttp3_a_SOURCES = \ + Http09App.cc \ + Http3.cc \ + Http3Config.cc \ + Http3App.cc \ + Http3Types.cc \ + Http3SessionAccept.cc \ + Http3Session.cc \ + Http3Transaction.cc \ + Http3DebugNames.cc \ + Http3Frame.cc \ + Http3FrameCollector.cc \ + Http3FrameDispatcher.cc \ + Http3HeaderFramer.cc \ + Http3DataFramer.cc \ + Http3HeaderVIOAdaptor.cc \ + Http3StreamDataVIOAdaptor.cc \ + QPACK.cc + +# +# Check Programs +# +check_PROGRAMS = \ + test_libhttp3 \ + test_qpack + +TESTS = $(check_PROGRAMS) + +test_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -I$(abs_top_srcdir)/proxy/logging \ + -I$(abs_top_srcdir)/tests/include + +test_LDADD = \ + $(top_builddir)/iocore/net/quic/libquic.a \ + $(top_builddir)/iocore/eventsystem/libinkevent.a \ + $(top_builddir)/lib/records/librecords_p.a \ + $(top_builddir)/mgmt/libmgmt_p.la \ + $(top_builddir)/src/tscore/libtscore.la \ + $(top_builddir)/src/tscpp/util/libtscpputil.la \ + $(top_builddir)/proxy/http2/libhttp2.a \ + $(top_builddir)/proxy/hdrs/libhdrs.a \ + $(top_builddir)/proxy/shared/libUglyLogStubs.a \ + @LIBPCRE@ \ + @OPENSSL_LIBS@ \ + @HWLOC_LIBS@ + +test_libhttp3_CPPFLAGS = $(test_CPPFLAGS) +test_libhttp3_LDFLAGS = @AM_LDFLAGS@ +test_libhttp3_LDADD = $(test_LDADD) +test_libhttp3_SOURCES = \ + ./test/main.cc \ + ./test/test_Http3Frame.cc \ + ./Http3Config.cc \ + ./Http3Frame.cc + +test_qpack_CPPFLAGS = $(test_CPPFLAGS) +test_qpack_LDFLAGS = @AM_LDFLAGS@ +test_qpack_LDADD = $(test_LDADD) +test_qpack_SOURCES = \ + ./test/main_qpack.cc \ + ./test/test_QPACK.cc \ + ./QPACK.cc + + +# +# clang-tidy +# +include $(top_srcdir)/build/tidy.mk + +clang-tidy-local: $(libhttp3_a_SOURCES) \ + $(CXX_Clang_Tidy) diff --git a/proxy/http3/QPACK.cc b/proxy/http3/QPACK.cc new file mode 100644 index 00000000000..c331dce81fe --- /dev/null +++ b/proxy/http3/QPACK.cc @@ -0,0 +1,1830 @@ +/** @file + * + * A brief file description + * + * @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 "HTTP.h" +#include "XPACK.h" +#include "QPACK.h" +#include "tscore/ink_defs.h" +#include "tscore/ink_memory.h" + +#define QPACKDebug(fmt, ...) Debug("qpack", "[%s] " fmt, this->_qc->cids().data(), ##__VA_ARGS__) +#define QPACKDTDebug(fmt, ...) Debug("qpack", "" fmt, ##__VA_ARGS__) + +// qpack-05 Appendix A. +const QPACK::Header QPACK::StaticTable::STATIC_HEADER_FIELDS[] = { + {":authority", ""}, + {":path", "/"}, + {"age", "0"}, + {"content-disposition", ""}, + {"content-length", "0"}, + {"cookie", ""}, + {"date", ""}, + {"etag", ""}, + {"if-modified-since", ""}, + {"if-none-match", ""}, + {"last-modified", ""}, + {"link", ""}, + {"location", ""}, + {"referer", ""}, + {"set-cookie", ""}, + {":method", "CONNECT"}, + {":method", "DELETE"}, + {":method", "GET"}, + {":method", "HEAD"}, + {":method", "OPTIONS"}, + {":method", "POST"}, + {":method", "PUT"}, + {":scheme", "http"}, + {":scheme", "https"}, + {":status", "103"}, + {":status", "200"}, + {":status", "304"}, + {":status", "404"}, + {":status", "503"}, + {"accept", "*/*"}, + {"accept", "application/dns-message"}, + {"accept-encoding", "gzip, deflate, br"}, + {"accept-ranges", "bytes"}, + {"access-control-allow-headers", "cache-control"}, + {"access-control-allow-headers", "content-type"}, + {"access-control-allow-origin", "*"}, + {"cache-control", "max-age=0"}, + {"cache-control", "max-age=2592000"}, + {"cache-control", "max-age=604800"}, + {"cache-control", "no-cache"}, + {"cache-control", "no-store"}, + {"cache-control", "public, max-age=31536000"}, + {"content-encoding", "br"}, + {"content-encoding", "gzip"}, + {"content-type", "application/dns-message"}, + {"content-type", "application/javascript"}, + {"content-type", "application/json"}, + {"content-type", "application/x-www-form-urlencoded"}, + {"content-type", "image/gif"}, + {"content-type", "image/jpeg"}, + {"content-type", "image/png"}, + {"content-type", "text/css"}, + {"content-type", "text/html; charset=utf-8"}, + {"content-type", "text/plain"}, + {"content-type", "text/plain;charset=utf-8"}, + {"range", "bytes=0-"}, + {"strict-transport-security", "max-age=31536000"}, + {"strict-transport-security", "max-age=31536000; includesubdomains"}, + {"strict-transport-security", "max-age=31536000; includesubdomains; preload"}, + {"vary", "accept-encoding"}, + {"vary", "origin"}, + {"x-content-type-options", "nosniff"}, + {"x-xss-protection", "1; mode=block"}, + {":status", "100"}, + {":status", "204"}, + {":status", "206"}, + {":status", "302"}, + {":status", "400"}, + {":status", "403"}, + {":status", "421"}, + {":status", "425"}, + {":status", "500"}, + {"accept-language", ""}, + {"access-control-allow-credentials", "FALSE"}, + {"access-control-allow-credentials", "TRUE"}, + {"access-control-allow-headers", "*"}, + {"access-control-allow-methods", "get"}, + {"access-control-allow-methods", "get, post, options"}, + {"access-control-allow-methods", "options"}, + {"access-control-expose-headers", "content-length"}, + {"access-control-request-headers", "content-type"}, + {"access-control-request-method", "get"}, + {"access-control-request-method", "post"}, + {"alt-svc", "clear"}, + {"authorization", ""}, + {"content-security-policy", "script-src 'none'; object-src 'none'; base-uri 'none'"}, + {"early-data", "1"}, + {"expect-ct", ""}, + {"forwarded", ""}, + {"if-range", ""}, + {"origin", ""}, + {"purpose", "prefetch"}, + {"server", ""}, + {"timing-allow-origin", "*"}, + {"upgrade-insecure-requests", "1"}, + {"user-agent", ""}, + {"x-forwarded-for", ""}, + {"x-frame-options", "deny"}, + {"x-frame-options", "sameorigin"}}; + +QPACK::QPACK(QUICConnection *qc, uint32_t max_header_list_size, uint16_t max_table_size, uint16_t max_blocking_streams) + : QUICApplication(qc), + _dynamic_table(max_table_size), + _max_header_list_size(max_header_list_size), + _max_table_size(max_table_size), + _max_blocking_streams(max_blocking_streams) +{ + SET_HANDLER(&QPACK::event_handler); + + this->_encoder_stream_sending_instructions = new_MIOBuffer(BUFFER_SIZE_INDEX_1K); + this->_decoder_stream_sending_instructions = new_MIOBuffer(BUFFER_SIZE_INDEX_1K); + this->_encoder_stream_sending_instructions_reader = this->_encoder_stream_sending_instructions->alloc_reader(); + this->_decoder_stream_sending_instructions_reader = this->_decoder_stream_sending_instructions->alloc_reader(); +} + +QPACK::~QPACK() {} + +int +QPACK::event_handler(int event, Event *data) +{ + VIO *vio = reinterpret_cast(data); + QUICStreamIO *stream_io = this->_find_stream_io(vio); + int ret; + + switch (event) { + case VC_EVENT_READ_READY: + ret = this->_on_read_ready(*stream_io); + break; + case VC_EVENT_READ_COMPLETE: + ret = EVENT_DONE; + break; + case VC_EVENT_WRITE_READY: + ret = this->_on_write_ready(*stream_io); + break; + case VC_EVENT_WRITE_COMPLETE: + ret = EVENT_DONE; + break; + default: + ret = EVENT_DONE; + } + return ret; +} + +int +QPACK::encode(uint64_t stream_id, HTTPHdr &header_set, MIOBuffer *header_block, uint64_t &header_block_len) +{ + if (!header_block) { + return -1; + } + + uint16_t base_index = this->_largest_known_received_index; + + // Compress headers and record the largest reference + uint16_t referred_index = 0; + uint16_t largest_reference = 0; + uint16_t smallest_reference = 0; + IOBufferBlock *compressed_headers = new_IOBufferBlock(); + compressed_headers->alloc(BUFFER_SIZE_INDEX_2K); + + MIMEFieldIter field_iter; + for (MIMEField *field = header_set.iter_get_first(&field_iter); field != nullptr; field = header_set.iter_get_next(&field_iter)) { + int ret = this->_encode_header(*field, base_index, compressed_headers, referred_index); + largest_reference = std::max(largest_reference, referred_index); + smallest_reference = std::min(smallest_reference, referred_index); + if (ret < 0) { + compressed_headers->free(); + return ret; + } + } + struct EntryReference eref = {smallest_reference, largest_reference}; + this->_references.emplace(stream_id, eref); + + // Make an IOBufferBlock for Header Data Prefix + IOBufferBlock *header_data_prefix = new_IOBufferBlock(); + header_data_prefix->alloc(BUFFER_SIZE_INDEX_128); + this->_encode_prefix(largest_reference, base_index, header_data_prefix); + + header_block->append_block(header_data_prefix); + header_block_len += header_data_prefix->size(); + + header_block->append_block(compressed_headers); + header_block_len += compressed_headers->size(); + + return 0; +} + +int +QPACK::decode(uint64_t stream_id, const uint8_t *header_block, size_t header_block_len, HTTPHdr &hdr, Continuation *cont, + EThread *thread) +{ + if (!cont || !header_block) { + return -1; + } + + if (this->_invalid) { + thread->schedule_imm(cont, QPACK_EVENT_DECODE_FAILED, nullptr); + return -1; + } + + uint64_t tmp = 0; + int64_t ret = xpack_decode_integer(tmp, header_block, header_block + header_block_len, 8); + if (ret < 0 && tmp > 0xFFFF) { + return -1; + } + uint16_t largest_reference = tmp; + + if (this->_dynamic_table.largest_index() < largest_reference) { + // Blocked + if (this->_add_to_blocked_list( + new DecodeRequest(largest_reference, thread, cont, stream_id, header_block, header_block_len, hdr))) { + return 1; + } else { + // Number of blocked streams exceed the limit + return -2; + } + } + + this->_decode(thread, cont, stream_id, header_block, header_block_len, hdr); + + return 0; +} + +void +QPACK::set_encoder_stream(QUICStreamIO *stream_io) +{ + this->_encoder_stream_id = stream_io->stream_id(); + this->set_stream(stream_io); +} + +void +QPACK::set_decoder_stream(QUICStreamIO *stream_io) +{ + this->_decoder_stream_id = stream_io->stream_id(); + this->set_stream(stream_io); +} + +void +QPACK::update_max_header_list_size(uint32_t max_header_list_size) +{ + this->_max_header_list_size = max_header_list_size; +} + +void +QPACK::update_max_table_size(uint16_t max_table_size) +{ + this->_max_table_size = max_table_size; +} + +void +QPACK::update_max_blocking_streams(uint16_t max_blocking_streams) +{ + this->_max_blocking_streams = max_blocking_streams; +} + +int +QPACK::_encode_prefix(uint16_t largest_reference, uint16_t base_index, IOBufferBlock *prefix) +{ + int ret; + if ((ret = xpack_encode_integer(reinterpret_cast(prefix->end()), + reinterpret_cast(prefix->end() + prefix->write_avail()), largest_reference, 8)) < 0) { + return -1; + } + prefix->fill(ret); + + uint16_t delta; + prefix->end()[0] = 0x0; + if (base_index < largest_reference) { + prefix->end()[0] |= 0x80; + delta = largest_reference - base_index; + } else { + delta = base_index - largest_reference; + } + + if ((ret = xpack_encode_integer(reinterpret_cast(prefix->end()), + reinterpret_cast(prefix->end() + prefix->write_avail()), delta, 7)) < 0) { + return -2; + } + prefix->fill(ret); + + QPACKDebug("Encoded Header Data Prefix: largest_ref=%d, base_index=%d, delta=%d", largest_reference, base_index, delta); + + return 0; +} + +int +QPACK::_encode_header(const MIMEField &field, uint16_t base_index, IOBufferBlock *compressed_header, uint16_t &referred_index) +{ + Arena arena; + int name_len; + const char *name = field.name_get(&name_len); + char *lowered_name = arena.str_store(name, name_len); + for (int i = 0; i < name_len; i++) { + lowered_name[i] = ParseRules::ink_tolower(lowered_name[i]); + } + int value_len; + const char *value = field.value_get(&value_len); + + // TODO Set never_index flag on/off according to encoding headers + bool never_index = false; + + // Find from tables, and insert / duplicate a entry prior to encode it + LookupResult lookup_result_static; + LookupResult lookup_result_dynamic; + lookup_result_static = StaticTable::lookup(lowered_name, name_len, value, value_len); + if (lookup_result_static.match_type != LookupResult::MatchType::EXACT) { + lookup_result_dynamic = this->_dynamic_table.lookup(lowered_name, name_len, value, value_len); + if (lookup_result_dynamic.match_type == LookupResult::MatchType::EXACT) { + if (this->_dynamic_table.should_duplicate(lookup_result_dynamic.index)) { + // Duplicate an entry and use the new entry + uint16_t current_index = lookup_result_dynamic.index; + lookup_result_dynamic = this->_dynamic_table.duplicate_entry(current_index); + if (lookup_result_dynamic.match_type != LookupResult::MatchType::NONE) { + this->_write_duplicate(current_index); + QPACKDebug("Wrote Duplicate: current_index=%d", current_index); + this->_dynamic_table.ref_entry(current_index); + } + } + } else if (lookup_result_static.match_type == LookupResult::MatchType::NAME) { + if (never_index) { + // Name in static table is always available. Do nothing. + } else { + // Insert both the name and the value + lookup_result_dynamic = this->_dynamic_table.insert_entry(lowered_name, name_len, value, value_len); + if (lookup_result_dynamic.match_type != LookupResult::MatchType::NONE) { + this->_write_insert_with_name_ref(lookup_result_static.index, false, value, value_len); + QPACKDebug("Wrote Insert With Name Ref: index=%u, dynamic_table=%d value=%.*s", lookup_result_static.index, false, + value_len, value); + } + } + } else if (lookup_result_dynamic.match_type == LookupResult::MatchType::NAME) { + if (never_index) { + if (this->_dynamic_table.should_duplicate(lookup_result_dynamic.index)) { + // Duplicate an entry and use the new entry + uint16_t current_index = lookup_result_dynamic.index; + lookup_result_dynamic = this->_dynamic_table.duplicate_entry(current_index); + if (lookup_result_dynamic.match_type != LookupResult::MatchType::NONE) { + this->_write_duplicate(current_index); + QPACKDebug("Wrote Duplicate: current_index=%d", current_index); + this->_dynamic_table.ref_entry(current_index); + } + } + } else { + if (this->_dynamic_table.should_duplicate(lookup_result_dynamic.index)) { + // Duplicate an entry and use the new entry + uint16_t current_index = lookup_result_dynamic.index; + lookup_result_dynamic = this->_dynamic_table.duplicate_entry(current_index); + if (lookup_result_dynamic.match_type != LookupResult::MatchType::NONE) { + this->_write_duplicate(current_index); + QPACKDebug("Wrote Duplicate: current_index=%d", current_index); + this->_dynamic_table.ref_entry(current_index); + } + } else { + // Insert both the name and the value + uint16_t current_index = lookup_result_dynamic.index; + lookup_result_dynamic = this->_dynamic_table.insert_entry(lowered_name, name_len, value, value_len); + if (lookup_result_dynamic.match_type != LookupResult::MatchType::NONE) { + this->_write_insert_with_name_ref(current_index, true, value, value_len); + QPACKDebug("Wrote Insert With Name Ref: index=%u, dynamic_table=%d, value=%.*s", current_index, true, value_len, value); + } + } + } + } else { + if (never_index) { + // Insert only the name + lookup_result_dynamic = this->_dynamic_table.insert_entry(lowered_name, name_len, "", 0); + if (lookup_result_dynamic.match_type != LookupResult::MatchType::NONE) { + this->_write_insert_without_name_ref(lowered_name, name_len, "", 0); + QPACKDebug("Wrote Insert Without Name Ref: name=%.*s value=%.*s", name_len, lowered_name, 0, ""); + } + } else { + // Insert both the name and the value + lookup_result_dynamic = this->_dynamic_table.insert_entry(lowered_name, name_len, value, value_len); + if (lookup_result_dynamic.match_type != LookupResult::MatchType::NONE) { + this->_write_insert_without_name_ref(lowered_name, name_len, value, value_len); + QPACKDebug("Wrote Insert Without Name Ref: name=%.*s value=%.*s", name_len, lowered_name, value_len, value); + } + } + } + } + + // Encode + if (lookup_result_static.match_type == LookupResult::MatchType::EXACT) { + this->_encode_indexed_header_field(lookup_result_static.index, base_index, false, compressed_header); + QPACKDebug("Encoded Indexed Header Field: abs_index=%d, base_index=%d, dynamic_table=%d", lookup_result_static.index, + base_index, false); + referred_index = 0; + } else if (lookup_result_dynamic.match_type == LookupResult::MatchType::EXACT) { + if (lookup_result_dynamic.index < this->_largest_known_received_index) { + this->_encode_indexed_header_field(lookup_result_dynamic.index, base_index, true, compressed_header); + QPACKDebug("Encoded Indexed Header Field: abs_index=%d, base_index=%d, dynamic_table=%d", lookup_result_dynamic.index, + base_index, true); + } else { + this->_encode_indexed_header_field_with_postbase_index(lookup_result_dynamic.index, base_index, never_index, + compressed_header); + QPACKDebug("Encoded Indexed Header With Postbase Index: abs_index=%d, base_index=%d, never_index=%d", + lookup_result_dynamic.index, base_index, never_index); + } + this->_dynamic_table.ref_entry(lookup_result_dynamic.index); + referred_index = lookup_result_dynamic.index; + } else if (lookup_result_static.match_type == LookupResult::MatchType::NAME) { + this->_encode_literal_header_field_with_name_ref(lookup_result_static.index, false, base_index, value, value_len, never_index, + compressed_header); + QPACKDebug( + "Encoded Literal Header Field With Name Ref: abs_index=%d, base_index=%d, dynamic_table=%d, value=%.*s, never_index=%d", + lookup_result_static.index, base_index, false, value_len, value, never_index); + referred_index = 0; + } else if (lookup_result_dynamic.match_type == LookupResult::MatchType::NAME) { + if (lookup_result_dynamic.index <= this->_largest_known_received_index) { + this->_encode_literal_header_field_with_name_ref(lookup_result_dynamic.index, true, base_index, value, value_len, never_index, + compressed_header); + QPACKDebug( + "Encoded Literal Header Field With Name Ref: abs_index=%d, base_index=%d, dynamic_table=%d, value=%.*s, never_index=%d", + lookup_result_dynamic.index, base_index, true, value_len, value, never_index); + } else { + this->_encode_literal_header_field_with_postbase_name_ref(lookup_result_dynamic.index, base_index, value, value_len, + never_index, compressed_header); + QPACKDebug("Encoded Literal Header Field With Postbase Name Ref: abs_index=%d, base_index=%d, value=%.*s, never_index=%d", + lookup_result_dynamic.index, base_index, value_len, value, never_index); + } + this->_dynamic_table.ref_entry(lookup_result_dynamic.index); + referred_index = lookup_result_dynamic.index; + } else { + this->_encode_literal_header_field_without_name_ref(lowered_name, name_len, value, value_len, never_index, compressed_header); + QPACKDebug("Encoded Literal Header Field Without Name Ref: name=%.*s, value=%.*s, never_index=%d", name_len, lowered_name, + value_len, value, never_index); + } + + return 0; +} + +int +QPACK::_encode_indexed_header_field(uint16_t index, uint16_t base_index, bool dynamic_table, IOBufferBlock *compressed_header) +{ + char *buf = compressed_header->end(); + char *buf_end = buf + compressed_header->write_avail(); + int written = 0; + + // Indexed Header Field + buf[0] = 0x80; + + // References static table or not + if (dynamic_table) { + // Use relative index if we refer Dynamic Table + index = this->_calc_relative_index_from_absolute_index(base_index, index); + } else { + buf[0] |= 0x40; + } + + // Index + int ret; + if ((ret = xpack_encode_integer(reinterpret_cast(buf + written), reinterpret_cast(buf_end), index, 6)) < + 0) { + return ret; + } + written += ret; + + // Finalize and Schedule to send + compressed_header->fill(written); + + return 0; +} + +int +QPACK::_encode_indexed_header_field_with_postbase_index(uint16_t index, uint16_t base_index, bool never_index, + IOBufferBlock *compressed_header) +{ + char *buf = compressed_header->end(); + char *buf_end = buf + compressed_header->write_avail(); + int written = 0; + + // Indexed Header Field with Post-Base Index + buf[0] = 0x10; + + // Index + int ret; + if ((ret = xpack_encode_integer(reinterpret_cast(buf + written), reinterpret_cast(buf_end), + this->_calc_postbase_index_from_absolute_index(base_index, index), 4)) < 0) { + return ret; + } + written += ret; + + // Finalize and Schedule to send + compressed_header->fill(written); + + return 0; +} + +int +QPACK::_encode_literal_header_field_with_name_ref(uint16_t index, bool dynamic_table, uint16_t base_index, const char *value, + int value_len, bool never_index, IOBufferBlock *compressed_header) +{ + char *buf = compressed_header->end(); + char *buf_end = buf + compressed_header->write_avail(); + int written = 0; + + // Literal Header Field With Name Reference + buf[0] = 0x40; + + if (never_index) { + buf[0] |= 0x20; + } + + // References static table or not + if (dynamic_table) { + // Use relative index if we refer Dynamic Table + index = this->_calc_relative_index_from_absolute_index(base_index, index); + } else { + buf[0] |= 0x10; + } + + // Index + int ret; + if ((ret = xpack_encode_integer(reinterpret_cast(buf + written), reinterpret_cast(buf_end), index, 4)) < + 0) { + return ret; + } + written += ret; + + // Value + if ((ret = xpack_encode_string(reinterpret_cast(buf + written), reinterpret_cast(buf_end), value, + value_len)) < 0) { + return ret; + } + written += ret; + + // Finalize and Schedule to send + compressed_header->fill(written); + + return 0; +} + +int +QPACK::_encode_literal_header_field_without_name_ref(const char *name, int name_len, const char *value, int value_len, + bool never_index, IOBufferBlock *compressed_header) +{ + char *buf = compressed_header->end(); + char *buf_end = buf + compressed_header->write_avail(); + int written = 0; + + // Literal Header Field Without Name Reference + buf[0] = 0x20; + + if (never_index) { + buf[0] |= 0x10; + } + + // Name + int ret; + if ((ret = xpack_encode_string(reinterpret_cast(buf + written), reinterpret_cast(buf_end), name, name_len, + 3)) < 0) { + return ret; + } + written += ret; + + // Value + if ((ret = xpack_encode_string(reinterpret_cast(buf + written), reinterpret_cast(buf_end), value, value_len, + 7)) < 0) { + return ret; + } + written += ret; + + // Finalize and Schedule to send + compressed_header->fill(written); + + return 0; +} + +int +QPACK::_encode_literal_header_field_with_postbase_name_ref(uint16_t index, uint16_t base_index, const char *value, int value_len, + bool never_index, IOBufferBlock *compressed_header) +{ + char *buf = compressed_header->end(); + char *buf_end = buf + compressed_header->write_avail(); + int written = 0; + + // Literal Header Field With Post-Base Name Reference + buf[0] = 0x00; + + if (never_index) { + buf[0] |= 0x08; + } + + // Index + int ret; + if ((ret = xpack_encode_integer(reinterpret_cast(buf + written), reinterpret_cast(buf_end), + this->_calc_postbase_index_from_absolute_index(base_index, index), 3)) < 0) { + return ret; + } + written += ret; + + // Value + if ((ret = xpack_encode_string(reinterpret_cast(buf + written), reinterpret_cast(buf_end), value, value_len, + 7)) < 0) { + return ret; + } + written += ret; + + // Finalize and Schedule to send + compressed_header->fill(written); + + return 0; +} + +int +QPACK::_decode_indexed_header_field(int16_t base_index, const uint8_t *buf, size_t buf_len, HTTPHdr &hdr, uint32_t &header_len) +{ + // Read index field + int len = 0; + uint64_t index; + int ret = xpack_decode_integer(index, buf, buf + buf_len, 6); + if (ret < 0) { + return -1; + } + len += ret; + + // Lookup a table + const char *name = nullptr; + int name_len = 0; + const char *value = nullptr; + int value_len = 0; + QPACK::LookupResult result; + + if (buf[0] & 0x40) { // Static table + result = StaticTable::lookup(index, &name, &name_len, &value, &value_len); + } else { // Dynamic table + result = this->_dynamic_table.lookup(this->_calc_absolute_index_from_relative_index(base_index, index), &name, &name_len, + &value, &value_len); + } + if (result.match_type != QPACK::LookupResult::MatchType::EXACT) { + return -1; + } + + // Create and attach a header + this->_attach_header(hdr, name, name_len, value, value_len, false); + header_len = name_len + value_len; + + QPACKDebug("Decoded Indexed Header Field: base_index=%d, abs_index=%d, name=%.*s, value=%.*s", base_index, result.index, name_len, + name, value_len, value); + + return len; +} + +int +QPACK::_decode_literal_header_field_with_name_ref(int16_t base_index, const uint8_t *buf, size_t buf_len, HTTPHdr &hdr, + uint32_t &header_len) +{ + int read_len = 0; + + // Never index field + bool never_index = false; + if (buf[0] & 0x20) { + never_index = true; + } + + // Read name index field + uint64_t index; + int ret = xpack_decode_integer(index, buf, buf + buf_len, 4); + if (ret < 0) { + return -1; + } + read_len += ret; + + // Lookup the name + const char *name = nullptr; + int name_len = 0; + const char *dummy = nullptr; + int dummy_len = 0; + QPACK::LookupResult result; + + if (buf[0] & 0x10) { // Static table + result = StaticTable::lookup(index, &name, &name_len, &dummy, &dummy_len); + } else { // Dynamic table + result = this->_dynamic_table.lookup(this->_calc_absolute_index_from_relative_index(base_index, index), &name, &name_len, + &dummy, &dummy_len); + } + if (result.match_type != QPACK::LookupResult::MatchType::EXACT) { + return -1; + } + + // Read value + Arena arena; + char *value; + uint64_t value_len; + if ((ret = xpack_decode_string(arena, &value, value_len, buf + read_len, buf + buf_len, 7)) < 0) { + return -1; + } + read_len += ret; + + // Create and attach a header + this->_attach_header(hdr, name, name_len, value, value_len, never_index); + header_len = name_len + value_len; + + QPACKDebug("Decoded Literal Header Field With Name Ref: base_index=%d, abs_index=%d, name=%.*s, value=%.*s", base_index, + result.index, name_len, name, static_cast(value_len), value); + + return read_len; +} + +int +QPACK::_decode_literal_header_field_without_name_ref(const uint8_t *buf, size_t buf_len, HTTPHdr &hdr, uint32_t &header_len) +{ + int read_len = 0; + + // Never index field + bool never_index = false; + if (buf[0] & 0x10) { + never_index = true; + } + + // Read name and value + Arena arena; + int64_t ret; + char *name; + uint64_t name_len; + if ((ret = xpack_decode_string(arena, &name, name_len, buf, buf + buf_len, 3)) < 0) { + return -1; + } + read_len += ret; + + char *value; + uint64_t value_len; + if ((ret = xpack_decode_string(arena, &value, value_len, buf + read_len, buf + buf_len, 7)) < 0) { + return -1; + } + read_len += ret; + + // Create and attach a header + this->_attach_header(hdr, name, name_len, value, value_len, never_index); + header_len = name_len + value_len; + + QPACKDebug("Decoded Literal Header Field Without Name Ref: name=%.*s, value=%.*s", static_cast(name_len), name, + static_cast(value_len), value); + + return read_len; +} + +int +QPACK::_decode_indexed_header_field_with_postbase_index(int16_t base_index, const uint8_t *buf, size_t buf_len, HTTPHdr &hdr, + uint32_t &header_len) +{ + // Read index field + int len = 0; + uint64_t index; + int ret = xpack_decode_integer(index, buf, buf + buf_len, 4); + if (ret < 0) { + return -1; + } + len += ret; + + // Lookup a table + const char *name = nullptr; + int name_len = 0; + const char *value = nullptr; + int value_len = 0; + QPACK::LookupResult result; + + result = this->_dynamic_table.lookup(this->_calc_absolute_index_from_postbase_index(base_index, index), &name, &name_len, &value, + &value_len); + if (result.match_type != QPACK::LookupResult::MatchType::EXACT) { + return -1; + } + + // Create and attach a header + this->_attach_header(hdr, name, name_len, value, value_len, false); + header_len = name_len + value_len; + + QPACKDebug("Decoded Indexed Header Field With Postbase Index: base_index=%d, abs_index=%d, name=%.*s, value=%.*s", base_index, + result.index, name_len, name, value_len, value); + + return len; +} + +int +QPACK::_decode_literal_header_field_with_postbase_name_ref(int16_t base_index, const uint8_t *buf, size_t buf_len, HTTPHdr &hdr, + uint32_t &header_len) +{ + int read_len = 0; + + // Never index field + bool never_index = false; + if (buf[0] & 0x08) { + never_index = true; + } + + // Read name index field + uint64_t index; + int ret = xpack_decode_integer(index, buf, buf + buf_len, 3); + if (ret < 0) { + return -1; + } + read_len += ret; + + // Lookup the name + const char *name = nullptr; + int name_len = 0; + const char *dummy = nullptr; + int dummy_len = 0; + QPACK::LookupResult result; + + result = this->_dynamic_table.lookup(this->_calc_absolute_index_from_postbase_index(base_index, index), &name, &name_len, &dummy, + &dummy_len); + if (result.match_type != QPACK::LookupResult::MatchType::EXACT) { + return -1; + } + + // Read value + Arena arena; + char *value; + uint64_t value_len; + if ((ret = xpack_decode_string(arena, &value, value_len, buf + read_len, buf + buf_len, 7)) < 0) { + return -1; + } + read_len += ret; + + // Create and attach a header + this->_attach_header(hdr, name, name_len, value, value_len, never_index); + header_len = name_len + value_len; + + QPACKDebug("Decoded Literal Header Field With Postbase Name Ref: base_index=%d, abs_index=%d, name=%.*s, value=%.*s", base_index, + static_cast(index), name_len, name, static_cast(value_len), value); + + return read_len; +} + +int +QPACK::_decode_header(const uint8_t *header_block, size_t header_block_len, HTTPHdr &hdr) +{ + const uint8_t *pos = header_block; + size_t remain_len = header_block_len; + int64_t ret; + + // Decode Header Data Prefix + uint64_t tmp; + if ((ret = xpack_decode_integer(tmp, pos, pos + remain_len, 8)) < 0 && tmp > 0xFFFF) { + return -1; + } + pos += ret; + uint16_t largest_reference = tmp; + + uint64_t delta_base_index; + uint16_t base_index; + if ((ret = xpack_decode_integer(delta_base_index, pos, pos + remain_len, 7)) < 0 && delta_base_index < 0xFFFF) { + return -2; + } + + if (pos[0] & 0x80) { + if (delta_base_index == 0) { + return -3; + } + base_index = largest_reference - delta_base_index; + } else { + base_index = largest_reference + delta_base_index; + } + pos += ret; + + uint32_t decoded_header_list_size = 0; + + // Decode Instructions + while (pos < header_block + header_block_len) { + uint32_t header_len = 0; + + if (pos[0] & 0x80) { // Index Header Field + ret = this->_decode_indexed_header_field(base_index, pos, remain_len, hdr, header_len); + } else if (pos[0] & 0x40) { // Literal Header Field With Name Reference + ret = this->_decode_literal_header_field_with_name_ref(base_index, pos, remain_len, hdr, header_len); + } else if (pos[0] & 0x20) { // Literal Header Field Without Name Reference + ret = this->_decode_literal_header_field_without_name_ref(pos, remain_len, hdr, header_len); + } else if (pos[0] & 0x10) { // Indexed Header Field With Post-Base Index + ret = this->_decode_indexed_header_field_with_postbase_index(base_index, pos, remain_len, hdr, header_len); + } else { // Literal Header Field With Post-Base Name Reference + ret = this->_decode_literal_header_field_with_postbase_name_ref(base_index, pos, remain_len, hdr, header_len); + } + + if (ret < 0) { + break; + } + + decoded_header_list_size += header_len; + if (decoded_header_list_size > this->_max_header_list_size) { + ret = -2; + break; + } + + pos += ret; + } + + return ret; +} + +void +QPACK::_decode(EThread *ethread, Continuation *cont, uint64_t stream_id, const uint8_t *header_block, size_t header_block_len, + HTTPHdr &hdr) +{ + int event; + int res = this->_decode_header(header_block, header_block_len, hdr); + if (res < 0) { + event = QPACK_EVENT_DECODE_FAILED; + QPACKDebug("decoding header failed (%d)", res); + } else { + event = QPACK_EVENT_DECODE_COMPLETE; + this->_write_header_acknowledgement(stream_id); + } + ethread->schedule_imm(cont, event, &hdr); +} + +bool +QPACK::_add_to_blocked_list(DecodeRequest *decode_request) +{ + if (this->_blocked_list.count() >= this->_max_blocking_streams) { + return false; + } + + this->_blocked_list.append(decode_request); + return true; +} + +void +QPACK::_update_largest_known_received_index_by_insert_count(uint16_t insert_count) +{ + this->_largest_known_received_index += insert_count; +} + +void +QPACK::_update_largest_known_received_index_by_stream_id(uint64_t stream_id) +{ + uint16_t largest_ref_index = this->_references[stream_id].largest; + if (largest_ref_index > this->_largest_known_received_index) { + this->_largest_known_received_index = largest_ref_index; + } +} + +void +QPACK::_update_reference_counts(uint64_t stream_id) +{ + uint16_t smallest_ref_index = this->_references[stream_id].smallest; + if (smallest_ref_index) { + this->_dynamic_table.unref_entry(smallest_ref_index); + } +} + +void +QPACK::_resume_decode() +{ + DecodeRequest *r = this->_blocked_list.head(); + while (r) { + if (this->_largest_known_received_index >= r->largest_reference()) { + this->_decode(r->thread(), r->continuation(), r->stream_id(), r->header_block(), r->header_block_len(), r->hdr()); + DecodeRequest *tmp = r; + r = DecodeRequest::Linkage::next_ptr(r); + this->_blocked_list.erase(tmp); + delete tmp; + } else { + r = DecodeRequest::Linkage::next_ptr(r); + } + } +} + +void +QPACK::_abort_decode() +{ + this->_invalid = true; + + DecodeRequest *r = this->_blocked_list.head(); + while (r) { + if (this->_largest_known_received_index >= r->largest_reference()) { + r->thread()->schedule_imm(r->continuation(), QPACK_EVENT_DECODE_FAILED, nullptr); + DecodeRequest *tmp = r; + r = DecodeRequest::Linkage::next_ptr(r); + this->_blocked_list.erase(tmp); + delete tmp; + } else { + r = DecodeRequest::Linkage::next_ptr(r); + } + } +} + +int +QPACK::_on_read_ready(QUICStreamIO &stream_io) +{ + QUICStreamId stream_id = stream_io.stream_id(); + if (stream_id == this->_decoder_stream_id) { + return this->_on_decoder_stream_read_ready(stream_io); + } else if (stream_id == this->_encoder_stream_id) { + return this->_on_encoder_stream_read_ready(stream_io); + } else { + ink_assert(!"The stream ID must match either encoder stream id or decoder stream id"); + return EVENT_DONE; + } +} + +int +QPACK::_on_write_ready(QUICStreamIO &stream_io) +{ + QUICStreamId stream_id = stream_io.stream_id(); + if (stream_id == this->_decoder_stream_id) { + return this->_on_decoder_write_ready(stream_io); + } else if (stream_id == this->_encoder_stream_id) { + return this->_on_encoder_write_ready(stream_io); + } else { + ink_assert(!"The stream ID must match either decoder stream id or decoder stream id"); + return EVENT_DONE; + } +} + +int +QPACK::_on_decoder_stream_read_ready(QUICStreamIO &stream_io) +{ + uint8_t buf; + if (stream_io.peek(&buf, 1) > 0) { + if (buf & 0x80) { // Header Acknowledgement + uint64_t stream_id; + if (this->_read_header_acknowledgement(stream_io, stream_id) >= 0) { + QPACKDebug("Received Header Acknowledgement: stream_id=%" PRIu64, stream_id); + this->_update_largest_known_received_index_by_stream_id(stream_id); + this->_update_reference_counts(stream_id); + this->_references.erase(stream_id); + } + } else if (buf & 0x40) { // Stream Cancellation + uint64_t stream_id; + if (this->_read_stream_cancellation(stream_io, stream_id) >= 0) { + QPACKDebug("Received Stream Cancellation: stream_id=%" PRIu64, stream_id); + this->_update_reference_counts(stream_id); + this->_references.erase(stream_id); + } + } else { // Table State Synchronize + uint16_t insert_count; + if (this->_read_table_state_synchronize(stream_io, insert_count) >= 0) { + QPACKDebug("Received Table State Synchronize: inserted_count=%d", insert_count); + this->_update_largest_known_received_index_by_insert_count(insert_count); + } + } + } + + return EVENT_DONE; +} + +int +QPACK::_on_encoder_stream_read_ready(QUICStreamIO &stream_io) +{ + uint8_t buf; + + while (stream_io.peek(&buf, 1) > 0) { + if (buf & 0x80) { // Insert With Name Reference + bool is_static; + uint16_t index; + Arena arena; + char *value; + uint16_t value_len; + if (this->_read_insert_with_name_ref(stream_io, is_static, index, arena, &value, value_len) < 0) { + this->_abort_decode(); + return EVENT_DONE; + } + QPACKDebug("Received Insert With Name Ref: is_static=%d, index=%d, value=%.*s", is_static, index, value_len, value); + this->_dynamic_table.insert_entry(is_static, index, value, value_len); + } else if (buf & 0x40) { // Insert Without Name Reference + Arena arena; + char *name; + uint16_t name_len; + char *value; + uint16_t value_len; + if (this->_read_insert_without_name_ref(stream_io, arena, &name, name_len, &value, value_len) < 0) { + this->_abort_decode(); + return EVENT_DONE; + } + QPACKDebug("Received Insert Without Name Ref: name=%.*s, value=%.*s", name_len, name, value_len, value); + this->_dynamic_table.insert_entry(name, name_len, value, value_len); + } else if (buf & 0x20) { // Dynamic Table Size Update + uint16_t max_size; + if (this->_read_dynamic_table_size_update(stream_io, max_size) < 0) { + this->_abort_decode(); + return EVENT_DONE; + } + QPACKDebug("Received Dynamic Table Size Update: max_size=%d", max_size); + this->_dynamic_table.update_size(max_size); + } else { // Duplicatex + uint16_t index; + if (this->_read_duplicate(stream_io, index) < 0) { + this->_abort_decode(); + return EVENT_DONE; + } + QPACKDebug("Received Duplicate: index=%d", index); + this->_dynamic_table.duplicate_entry(index); + } + + this->_resume_decode(); + } + + return EVENT_DONE; +} + +int +QPACK::_on_decoder_write_ready(QUICStreamIO &stream_io) +{ + int64_t written_len = stream_io.write(this->_decoder_stream_sending_instructions_reader, INT64_MAX); + this->_decoder_stream_sending_instructions_reader->consume(written_len); + return written_len; +} + +int +QPACK::_on_encoder_write_ready(QUICStreamIO &stream_io) +{ + int64_t written_len = stream_io.write(this->_encoder_stream_sending_instructions_reader, INT64_MAX); + this->_encoder_stream_sending_instructions_reader->consume(written_len); + return written_len; +} + +size_t +QPACK::estimate_header_block_size(const HTTPHdr &hdr) +{ + // FIXME Estimate it + return 128 * 1024 * 1024; +} + +const QPACK::LookupResult +QPACK::StaticTable::lookup(uint16_t index, const char **name, int *name_len, const char **value, int *value_len) +{ + const Header &header = STATIC_HEADER_FIELDS[index]; + *name = header.name; + *name_len = header.name_len; + *value = header.value; + *value_len = header.value_len; + return {index, QPACK::LookupResult::MatchType::EXACT}; +} + +const QPACK::LookupResult +QPACK::StaticTable::lookup(const char *name, int name_len, const char *value, int value_len) +{ + QPACK::LookupResult::MatchType match_type = QPACK::LookupResult::MatchType::NONE; + uint16_t i = 0; + uint16_t candidate_index = 0; + int n = countof(STATIC_HEADER_FIELDS); + + for (; i < n; ++i) { + const Header &h = STATIC_HEADER_FIELDS[i]; + if (h.name_len == name_len) { + if (memcmp(name, h.name, name_len) == 0) { + candidate_index = i; + if (value_len == h.value_len && memcmp(value, h.value, value_len) == 0) { + // Exact match + match_type = QPACK::LookupResult::MatchType::EXACT; + break; + } else { + // Name match -- Keep it for no exact matchs + match_type = QPACK::LookupResult::MatchType::NAME; + } + } + } + } + return {candidate_index, match_type}; +} + +uint16_t +QPACK::_calc_absolute_index_from_relative_index(uint16_t base_index, uint16_t relative_index) +{ + return base_index - relative_index; +} + +uint16_t +QPACK::_calc_absolute_index_from_postbase_index(uint16_t base_index, uint16_t postbase_index) +{ + return base_index + postbase_index + 1; +} + +uint16_t +QPACK::_calc_relative_index_from_absolute_index(uint16_t base_index, uint16_t absolute_index) +{ + return base_index - absolute_index; +} + +uint16_t +QPACK::_calc_postbase_index_from_absolute_index(uint16_t base_index, uint16_t absolute_index) +{ + return absolute_index - base_index - 1; +} + +void +QPACK::_attach_header(HTTPHdr &hdr, const char *name, int name_len, const char *value, int value_len, bool never_index) +{ + // TODO If never_index is true, we need to mark this header as sensitive to not index the header when passing it to the other side + MIMEField *new_field = hdr.field_create(name, name_len); + new_field->value_set(hdr.m_heap, hdr.m_mime, value, value_len); + hdr.field_attach(new_field); +} + +// +// DynamicTable +// +QPACK::DynamicTable::DynamicTable(uint16_t size) : _available(size), _max_entries(size), _storage(new DynamicTableStorage(size)) +{ + QPACKDTDebug("Dynamic table size: %u", size); + this->_entries = static_cast(ats_malloc(sizeof(struct DynamicTableEntry) * size)); + this->_entries_head = size - 1; + this->_entries_tail = size - 1; +} + +QPACK::DynamicTable::~DynamicTable() +{ + if (this->_storage) { + delete this->_storage; + this->_storage = nullptr; + } + if (this->_entries) { + delete this->_entries; + this->_entries = nullptr; + } +} + +const QPACK::LookupResult +QPACK::DynamicTable::lookup(uint16_t index, const char **name, int *name_len, const char **value, int *value_len) +{ + // ink_assert(index >= this->_entries[(this->_entries_tail + 1) % this->_max_entries].index); + // ink_assert(index <= this->_entries[this->_entries_head].index); + uint16_t pos = (this->_entries_head + (index - this->_entries[this->_entries_head].index)) % this->_max_entries; + *name_len = this->_entries[pos].name_len; + *value_len = this->_entries[pos].value_len; + this->_storage->read(this->_entries[pos].offset, name, *name_len, value, *value_len); + return {index, QPACK::LookupResult::MatchType::EXACT}; +} + +const QPACK::LookupResult +QPACK::DynamicTable::lookup(const char *name, int name_len, const char *value, int value_len) +{ + QPACK::LookupResult::MatchType match_type = QPACK::LookupResult::MatchType::NONE; + uint16_t i = this->_entries_tail + 1; + int end = this->_entries_head; + uint16_t candidate_index = 0; + const char *tmp_name = nullptr; + const char *tmp_value = nullptr; + + // DynamicTable is empty + if (this->_entries_inserted == 0) { + return {candidate_index, match_type}; + } + + // TODO Use a tree for better perfomance + for (; i <= end; i = (i + 1) % this->_max_entries) { + if (name_len != 0 && this->_entries[i].name_len == name_len) { + this->_storage->read(this->_entries[i].offset, &tmp_name, this->_entries[i].name_len, &tmp_value, + this->_entries[i].value_len); + if (memcmp(name, tmp_name, name_len) == 0) { + candidate_index = this->_entries[i].index; + if (value_len == this->_entries[i].value_len && memcmp(value, tmp_value, value_len) == 0) { + // Exact match + match_type = QPACK::LookupResult::MatchType::EXACT; + break; + } else { + // Name match -- Keep it for no exact matchs + match_type = QPACK::LookupResult::MatchType::NAME; + } + } + } + } + + return {candidate_index, match_type}; +} + +const QPACK::LookupResult +QPACK::DynamicTable::insert_entry(bool is_static, uint16_t index, const char *value, uint16_t value_len) +{ + const char *name; + int name_len; + const char *dummy; + int dummy_len; + + if (is_static) { + StaticTable::lookup(index, &name, &name_len, &dummy, &dummy_len); + } else { + this->lookup(index, &name, &name_len, &dummy, &dummy_len); + } + return this->insert_entry(name, name_len, value, value_len); +} + +const QPACK::LookupResult +QPACK::DynamicTable::insert_entry(const char *name, uint16_t name_len, const char *value, uint16_t value_len) +{ + if (this->_max_entries == 0) { + return {UINT16_C(0), QPACK::LookupResult::MatchType::NONE}; + } + + // Check if we can make enough space to insert a new entry + uint16_t required_len = name_len + value_len; + uint16_t available = this->_available; + uint16_t tail = (this->_entries_tail + 1) % this->_max_entries; + while (available < required_len) { + if (this->_entries[tail].ref_count) { + break; + } + available += this->_entries[tail].name_len + this->_entries[tail].value_len; + tail = (tail + 1) % this->_max_entries; + } + if (available < required_len) { + // We can't insert a new entry because some stream(s) refer an entry that need to be evicted + return {UINT16_C(0), QPACK::LookupResult::MatchType::NONE}; + } + + // Evict + if (this->_available != available) { + QPACKDTDebug("Evict entries: from %u to %u", this->_entries[(this->_entries_tail + 1) % this->_max_entries].index, + this->_entries[tail - 1].index); + this->_available = available; + this->_entries_tail = tail - 1; + QPACKDTDebug("Available size: %u", this->_available); + } + + // Insert + this->_entries_head = (this->_entries_head + 1) % this->_max_entries; + this->_entries[this->_entries_head] = {++this->_entries_inserted, this->_storage->write(name, name_len, value, value_len), + name_len, value_len, 0}; + this->_available -= required_len; + + QPACKDTDebug("Insert Entry: entry=%u, index=%u, size=%u", this->_entries_head, this->_entries_inserted, name_len + value_len); + QPACKDTDebug("Available size: %u", this->_available); + return {this->_entries_inserted, value_len ? LookupResult::MatchType::EXACT : LookupResult::MatchType::NAME}; +} + +const QPACK::LookupResult +QPACK::DynamicTable::duplicate_entry(uint16_t current_index) +{ + const char *name; + int name_len; + const char *value; + int value_len; + char *duped_name; + char *duped_value; + + this->lookup(current_index, &name, &name_len, &value, &value_len); + // We need to dup name and value to avoid memcpy-param-overlap + duped_name = ats_strndup(name, name_len); + duped_value = ats_strndup(value, value_len); + const LookupResult result = this->insert_entry(duped_name, name_len, duped_value, value_len); + ats_free(duped_name); + ats_free(duped_value); + + return result; +} + +bool +QPACK::DynamicTable::should_duplicate(uint16_t index) +{ + // TODO: Check whether a specified entry should be duplicated + // Just return false for now + return false; +} + +void +QPACK::DynamicTable::update_size(uint16_t max_size) +{ + // TODO Implement it +} + +void +QPACK::DynamicTable::ref_entry(uint16_t index) +{ + uint16_t pos = (this->_entries_head + (index - this->_entries[this->_entries_head].index)) % this->_max_entries; + ++this->_entries[pos].ref_count; +} + +void +QPACK::DynamicTable::unref_entry(uint16_t index) +{ + uint16_t pos = (this->_entries_head + (index - this->_entries[this->_entries_head].index)) % this->_max_entries; + --this->_entries[pos].ref_count; +} + +uint16_t +QPACK::DynamicTable::largest_index() +{ + return this->_entries_inserted; +} + +int +QPACK::_write_insert_with_name_ref(uint16_t index, bool dynamic, const char *value, uint16_t value_len) +{ + IOBufferBlock *instruction = new_IOBufferBlock(); + instruction->alloc(TS_IOBUFFER_SIZE_INDEX_2K); + + char *buf = instruction->end(); + char *buf_end = buf + instruction->write_avail(); + int written = 0; + + // Insert With Name Reference + buf[0] = 0x80; + + // References static table or not + if (!dynamic) { + buf[0] |= 0x40; + } + + // Name Index + int ret; + if ((ret = xpack_encode_integer(reinterpret_cast(buf + written), reinterpret_cast(buf_end), index, 6)) < + 0) { + return ret; + } + written += ret; + + // Value + if ((ret = xpack_encode_string(reinterpret_cast(buf + written), reinterpret_cast(buf_end), value, value_len, + 7)) < 0) { + return ret; + } + written += ret; + + // Finalize and Schedule to send + instruction->fill(written); + this->_encoder_stream_sending_instructions->append_block(instruction); + + return 0; +} + +int +QPACK::_write_insert_without_name_ref(const char *name, int name_len, const char *value, uint16_t value_len) +{ + IOBufferBlock *instruction = new_IOBufferBlock(); + instruction->alloc(TS_IOBUFFER_SIZE_INDEX_2K); + + char *buf = instruction->end(); + char *buf_end = buf + instruction->write_avail(); + int written = 0; + + // Insert Without Name Reference + buf[0] = 0x40; + + // Name + int ret; + if ((ret = xpack_encode_string(reinterpret_cast(buf + written), reinterpret_cast(buf_end), name, name_len, + 5)) < 0) { + return ret; + } + written += ret; + + // Value + if ((ret = xpack_encode_string(reinterpret_cast(buf + written), reinterpret_cast(buf_end), value, value_len, + 7)) < 0) { + return ret; + } + written += ret; + + // Finalize and Schedule to send + instruction->fill(written); + this->_encoder_stream_sending_instructions->append_block(instruction); + + return 0; +} + +int +QPACK::_write_duplicate(uint16_t index) +{ + IOBufferBlock *instruction = new_IOBufferBlock(); + instruction->alloc(TS_IOBUFFER_SIZE_INDEX_2K); + + char *buf = instruction->end(); + char *buf_end = buf + instruction->write_avail(); + int written = 0; + + // Index + int ret; + if ((ret = xpack_encode_integer(reinterpret_cast(buf + written), reinterpret_cast(buf_end), index, 5)) < + 0) { + return ret; + } + written += ret; + + // Finalize and Schedule to send + instruction->fill(written); + this->_encoder_stream_sending_instructions->append_block(instruction); + + return 0; +} + +int +QPACK::_write_dynamic_table_size_update(uint16_t max_size) +{ + IOBufferBlock *instruction = new_IOBufferBlock(); + instruction->alloc(TS_IOBUFFER_SIZE_INDEX_128); + + char *buf = instruction->end(); + char *buf_end = buf + instruction->write_avail(); + int written = 0; + + // Dynamic Table Size Update + buf[0] = 0x20; + + // Max Size + int ret; + if ((ret = xpack_encode_integer(reinterpret_cast(buf + written), reinterpret_cast(buf_end), max_size, 5)) < + 0) { + return ret; + } + written += ret; + + // Finalize and Schedule to send + instruction->fill(written); + this->_encoder_stream_sending_instructions->append_block(instruction); + + return 0; +} + +int +QPACK::_write_table_state_syncrhonize(uint16_t insert_count) +{ + IOBufferBlock *instruction = new_IOBufferBlock(); + instruction->alloc(TS_IOBUFFER_SIZE_INDEX_128); + + char *buf = instruction->end(); + char *buf_end = buf + instruction->write_avail(); + int written = 0; + + // Insert Count + int ret; + if ((ret = xpack_encode_integer(reinterpret_cast(buf + written), reinterpret_cast(buf_end), insert_count, + 6)) < 0) { + return ret; + } + written += ret; + + // Finalize and Schedule to send + instruction->fill(written); + this->_encoder_stream_sending_instructions->append_block(instruction); + + return 0; +} + +int +QPACK::_write_header_acknowledgement(uint64_t stream_id) +{ + IOBufferBlock *instruction = new_IOBufferBlock(); + instruction->alloc(TS_IOBUFFER_SIZE_INDEX_128); + + char *buf = instruction->end(); + char *buf_end = buf + instruction->write_avail(); + int written = 0; + + // Header Acknowledgement + buf[0] = 0x80; + + // Stream ID + int ret; + if ((ret = xpack_encode_integer(reinterpret_cast(buf + written), reinterpret_cast(buf_end), stream_id, 7)) < + 0) { + return ret; + } + written += ret; + + // Finalize and Schedule to send + instruction->fill(written); + this->_encoder_stream_sending_instructions->append_block(instruction); + + return 0; +} + +int +QPACK::_write_stream_cancellation(uint64_t stream_id) +{ + IOBufferBlock *instruction = new_IOBufferBlock(); + instruction->alloc(TS_IOBUFFER_SIZE_INDEX_128); + + char *buf = instruction->end(); + char *buf_end = buf + instruction->write_avail(); + int written = 0; + + // Stream Cancellation + buf[0] = 0x40; + + // Stream ID + int ret; + if ((ret = xpack_encode_integer(reinterpret_cast(buf + written), reinterpret_cast(buf_end), stream_id, 7)) < + 0) { + return ret; + } + written += ret; + + // Finalize and Schedule to send + instruction->fill(written); + this->_encoder_stream_sending_instructions->append_block(instruction); + + return 0; +} + +int +QPACK::_read_insert_with_name_ref(QUICStreamIO &stream_io, bool &is_static, uint16_t &index, Arena &arena, char **value, + uint16_t &value_len) +{ + size_t read_len = 0; + int ret; + uint8_t input[16384]; + int input_len; + input_len = stream_io.peek(input, sizeof(input)); + + // S flag + is_static = input[0] & 0x40; + + // Name Index + uint64_t tmp; + if ((ret = xpack_decode_integer(tmp, input, input + input_len, 6)) < 0 && tmp > 0xFFFF) { + return -1; + } + index = tmp; + read_len += ret; + + // Value + if ((ret = xpack_decode_string(arena, value, tmp, input + read_len, input + input_len, 7)) < 0 && tmp > 0xFF) { + return -1; + } + value_len = tmp; + read_len += ret; + + stream_io.consume(read_len); + + return 0; +} + +int +QPACK::_read_insert_without_name_ref(QUICStreamIO &stream_io, Arena &arena, char **name, uint16_t &name_len, char **value, + uint16_t &value_len) +{ + size_t read_len = 0; + int ret; + uint8_t input[16384]; + int input_len; + input_len = stream_io.peek(input, sizeof(input)); + + // Name + uint64_t tmp; + if ((ret = xpack_decode_string(arena, name, tmp, input, input + input_len, 5)) < 0 && tmp > 0xFFFF) { + return -1; + } + name_len = tmp; + read_len += ret; + + // Value + if ((ret = xpack_decode_string(arena, value, tmp, input + read_len, input + input_len, 7)) < 0 && tmp > 0xFFFF) { + return -1; + } + value_len = tmp; + read_len += ret; + + stream_io.consume(read_len); + + return 0; +} + +int +QPACK::_read_duplicate(QUICStreamIO &stream_io, uint16_t &index) +{ + size_t read_len = 0; + int ret; + uint8_t input[16]; + int input_len; + input_len = stream_io.peek(input, sizeof(input)); + + // Index + uint64_t tmp; + if ((ret = xpack_decode_integer(tmp, input, input + input_len, 5)) < 0 && tmp > 0xFFFF) { + return -1; + } + index = tmp; + read_len += ret; + + stream_io.consume(read_len); + + return 0; +} + +int +QPACK::_read_dynamic_table_size_update(QUICStreamIO &stream_io, uint16_t &max_size) +{ + size_t read_len = 0; + int ret; + uint8_t input[16]; + int input_len; + input_len = stream_io.peek(input, sizeof(input)); + uint64_t tmp; + + // Max Size + if ((ret = xpack_decode_integer(tmp, input, input + input_len, 5)) < 0 && tmp > 0xFFFF) { + return -1; + } + max_size = tmp; + read_len += ret; + + stream_io.consume(read_len); + + return 0; +} + +int +QPACK::_read_table_state_synchronize(QUICStreamIO &stream_io, uint16_t &insert_count) +{ + size_t read_len = 0; + int ret; + uint8_t input[16]; + int input_len; + input_len = stream_io.peek(input, sizeof(input)); + uint64_t tmp; + + // Insert Count + if ((ret = xpack_decode_integer(tmp, input, input + input_len, 6)) < 0 && tmp > 0xFFFF) { + return -1; + } + insert_count = tmp; + read_len += ret; + + stream_io.consume(read_len); + + return 0; +} + +int +QPACK::_read_header_acknowledgement(QUICStreamIO &stream_io, uint64_t &stream_id) +{ + size_t read_len = 0; + int ret; + uint8_t input[16]; + int input_len; + input_len = stream_io.peek(input, sizeof(input)); + + // Stream ID + // FIXME xpack_decode_integer does not support uint64_t + if ((ret = xpack_decode_integer(stream_id, input, input + input_len, 7)) < 0) { + return -1; + } + read_len += ret; + + stream_io.consume(read_len); + + return 0; +} + +int +QPACK::_read_stream_cancellation(QUICStreamIO &stream_io, uint64_t &stream_id) +{ + size_t read_len = 0; + int ret; + uint8_t input[16]; + int input_len; + input_len = stream_io.peek(input, sizeof(input)); + + // Stream ID + // FIXME xpack_decode_integer does not support uint64_t + if ((ret = xpack_decode_integer(stream_id, input, input + input_len, 6)) < 0) { + return -1; + } + read_len += ret; + + stream_io.consume(read_len); + + return 0; +} + +// +// DynamicTableStorage +// + +QPACK::DynamicTableStorage::DynamicTableStorage(uint16_t size) : _head(size * 2 - 1), _tail(size * 2 - 1) +{ + this->_data_size = size * 2; + this->_data = reinterpret_cast(ats_malloc(this->_data_size)); + this->_overwrite_threshold = size; +} + +QPACK::DynamicTableStorage::~DynamicTableStorage() +{ + if (this->_data) { + ats_free(this->_data); + this->_data = nullptr; + } +} + +void +QPACK::DynamicTableStorage::read(uint16_t offset, const char **name, uint16_t name_len, const char **value, uint16_t value_len) +{ + *name = reinterpret_cast(this->_data + offset); + *value = reinterpret_cast(this->_data + offset + name_len); +} + +uint16_t +QPACK::DynamicTableStorage::write(const char *name, uint16_t name_len, const char *value, uint16_t value_len) +{ + uint16_t offset = (this->_head + 1) % this->_data_size; + memcpy(this->_data + offset, name, name_len); + memcpy(this->_data + offset + name_len, value, value_len); + + this->_head = (this->_head + (name_len + value_len)) % this->_data_size; + if (this->_head > this->_overwrite_threshold) { + this->_head = 0; + } + + return offset; +} + +void +QPACK::DynamicTableStorage::erase(uint16_t name_len, uint16_t value_len) +{ + this->_tail = (this->_tail + (name_len + value_len)) % this->_data_size; +} diff --git a/proxy/http3/QPACK.h b/proxy/http3/QPACK.h new file mode 100644 index 00000000000..85bd7106701 --- /dev/null +++ b/proxy/http3/QPACK.h @@ -0,0 +1,332 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include "I_EventSystem.h" +#include "I_Event.h" +#include "tscpp/util/IntrusiveDList.h" +#include "MIME.h" +#include "QUICApplication.h" + +class HTTPHdr; + +enum { + QPACK_EVENT_DECODE_COMPLETE = QPACK_EVENT_EVENTS_START, + QPACK_EVENT_DECODE_FAILED, +}; + +class QPACK : public QUICApplication +{ +public: + QPACK(QUICConnection *qc, uint32_t max_header_list_size, uint16_t max_table_size, uint16_t max_blocking_streams); + virtual ~QPACK(); + + int event_handler(int event, Event *data); + + /* + * header_block must have enough size to store all headers in header_set. + * The maximum size can be estimated with QPACK::estimate_header_block_size(). + */ + int encode(uint64_t stream_id, HTTPHdr &header_set, MIOBuffer *header_block, uint64_t &header_block_len); + + /* + * This will emit either of two events below: + * - QPACK_EVENT_DECODE_COMPLETE (Data: *HTTPHdr) + * - QPACK_EVENT_DECODE_FAILED (Data: nullptr) + */ + int decode(uint64_t stream_id, const uint8_t *header_block, size_t header_block_len, HTTPHdr &hdr, Continuation *cont, + EThread *thread = this_ethread()); + + int cancel(uint64_t stream_id); + + void set_encoder_stream(QUICStreamIO *stream_io); + void set_decoder_stream(QUICStreamIO *stream_io); + + void update_max_header_list_size(uint32_t max_header_list_size); + void update_max_table_size(uint16_t max_table_size); + void update_max_blocking_streams(uint16_t max_blocking_streams); + + static size_t estimate_header_block_size(const HTTPHdr &header_set); + +private: + struct LookupResult { + uint16_t index = 0; + enum MatchType { NONE, NAME, EXACT } match_type = MatchType::NONE; + }; + + struct Header { + Header(const char *n, const char *v) : name(n), value(v), name_len(strlen(name)), value_len(strlen(value)) {} + const char *name; + const char *value; + const int name_len; + const int value_len; + }; + + class StaticTable + { + public: + static const LookupResult lookup(uint16_t index, const char **name, int *name_len, const char **value, int *value_len); + static const LookupResult lookup(const char *name, int name_len, const char *value, int value_len); + + private: + static const Header STATIC_HEADER_FIELDS[]; + }; + + struct DynamicTableEntry { + uint16_t index = 0; + uint16_t offset = 0; + uint16_t name_len = 0; + uint16_t value_len = 0; + uint16_t ref_count = 0; + }; + + class DynamicTableStorage + { + public: + DynamicTableStorage(uint16_t size); + ~DynamicTableStorage(); + void read(uint16_t offset, const char **name, uint16_t name_len, const char **value, uint16_t value_len); + uint16_t write(const char *name, uint16_t name_len, const char *value, uint16_t value_len); + void erase(uint16_t name_len, uint16_t value_len); + + private: + uint16_t _overwrite_threshold = 0; + uint8_t *_data = nullptr; + uint16_t _data_size = 0; + uint16_t _head = 0; + uint16_t _tail = 0; + }; + + class DynamicTable + { + public: + DynamicTable(uint16_t size); + ~DynamicTable(); + + const LookupResult lookup(uint16_t index, const char **name, int *name_len, const char **value, int *value_len); + const LookupResult lookup(const char *name, int name_len, const char *value, int value_len); + const LookupResult insert_entry(bool is_static, uint16_t index, const char *value, uint16_t value_len); + const LookupResult insert_entry(const char *name, uint16_t name_len, const char *value, uint16_t value_len); + const LookupResult duplicate_entry(uint16_t current_index); + bool should_duplicate(uint16_t index); + void update_size(uint16_t max_size); + void ref_entry(uint16_t index); + void unref_entry(uint16_t index); + uint16_t largest_index(); + + private: + uint16_t _available = 0; + uint16_t _entries_inserted = 0; + + // FIXME It may be better to split this array into small arrays to reduce memory footprint + struct DynamicTableEntry *_entries = nullptr; + uint16_t _max_entries = 0; + uint16_t _entries_head = 0; + uint16_t _entries_tail = 0; + DynamicTableStorage *_storage = nullptr; + }; + + class DecodeRequest + { + public: + DecodeRequest(uint16_t largest_reference, EThread *thread, Continuation *continuation, uint64_t stream_id, + const uint8_t *header_blcok, size_t header_block_len, HTTPHdr &hdr) + : _largest_reference(largest_reference), + _thread(thread), + _continuation(continuation), + _stream_id(stream_id), + _header_block(header_blcok), + _header_block_len(header_block_len), + _hdr(hdr) + { + } + + uint16_t + largest_reference() + { + return this->_largest_reference; + } + + EThread * + thread() + { + return this->_thread; + } + + Continuation * + continuation() + { + return this->_continuation; + } + + uint64_t + stream_id() + { + return this->_stream_id; + } + + const uint8_t * + header_block() + { + return this->_header_block; + } + + size_t + header_block_len() + { + return this->_header_block_len; + } + + HTTPHdr & + hdr() + { + return this->_hdr; + } + + class Linkage + { + public: + static DecodeRequest *& + next_ptr(DecodeRequest *t) + { + return *reinterpret_cast(&t->_next); + } + static DecodeRequest *& + prev_ptr(DecodeRequest *t) + { + return *reinterpret_cast(&t->_prev); + } + }; + + private: + uint16_t _largest_reference; + EThread *_thread; + Continuation *_continuation; + uint64_t _stream_id; + const uint8_t *_header_block; + size_t _header_block_len; + HTTPHdr &_hdr; + + // For IntrusiveDList support + DecodeRequest *_next = nullptr; + DecodeRequest *_prev = nullptr; + }; + + struct EntryReference { + uint16_t smallest; + uint16_t largest; + }; + + DynamicTable _dynamic_table; + std::map _references; + uint32_t _max_header_list_size = 0; + uint16_t _max_table_size = 0; + uint16_t _max_blocking_streams = 0; + + Continuation *_event_handler = nullptr; + void _resume_decode(); + void _abort_decode(); + + bool _invalid = false; + + ts::IntrusiveDList _blocked_list; + bool _add_to_blocked_list(DecodeRequest *decode_request); + + uint16_t _largest_known_received_index = 0; + void _update_largest_known_received_index_by_insert_count(uint16_t insert_count); + void _update_largest_known_received_index_by_stream_id(uint64_t stream_id); + + void _update_reference_counts(uint64_t stream_id); + + // Encoder Stream + int _read_insert_with_name_ref(QUICStreamIO &stream_io, bool &is_static, uint16_t &index, Arena &arena, char **value, + uint16_t &value_len); + int _read_insert_without_name_ref(QUICStreamIO &stream_io, Arena &arena, char **name, uint16_t &name_len, char **value, + uint16_t &value_len); + int _read_duplicate(QUICStreamIO &stream_io, uint16_t &index); + int _read_dynamic_table_size_update(QUICStreamIO &stream_io, uint16_t &max_size); + int _write_insert_with_name_ref(uint16_t index, bool dynamic, const char *value, uint16_t value_len); + int _write_insert_without_name_ref(const char *name, int name_len, const char *value, uint16_t value_len); + int _write_duplicate(uint16_t index); + int _write_dynamic_table_size_update(uint16_t max_size); + + // Decoder Stream + int _read_table_state_synchronize(QUICStreamIO &stream_io, uint16_t &insert_count); + int _read_header_acknowledgement(QUICStreamIO &stream_io, uint64_t &stream_id); + int _read_stream_cancellation(QUICStreamIO &stream_io, uint64_t &stream_id); + int _write_table_state_syncrhonize(uint16_t insert_count); + int _write_header_acknowledgement(uint64_t stream_id); + int _write_stream_cancellation(uint64_t stream_id); + + // Request and Push Streams + int _encode_prefix(uint16_t largest_reference, uint16_t base_index, IOBufferBlock *prefix); + int _encode_header(const MIMEField &field, uint16_t base_index, IOBufferBlock *compressed_header, uint16_t &referred_index); + int _encode_indexed_header_field(uint16_t index, uint16_t base_index, bool dynamic_table, IOBufferBlock *compressed_header); + int _encode_indexed_header_field_with_postbase_index(uint16_t index, uint16_t base_index, bool never_index, + IOBufferBlock *compressed_header); + int _encode_literal_header_field_with_name_ref(uint16_t index, bool dynamic_table, uint16_t base_index, const char *value, + int value_len, bool never_index, IOBufferBlock *compressed_header); + int _encode_literal_header_field_without_name_ref(const char *name, int name_len, const char *value, int value_len, + bool never_index, IOBufferBlock *compressed_header); + int _encode_literal_header_field_with_postbase_name_ref(uint16_t index, uint16_t base_index, const char *value, int value_len, + bool never_index, IOBufferBlock *compressed_header); + + void _decode(EThread *ethread, Continuation *cont, uint64_t stream_id, const uint8_t *header_block, size_t header_block_len, + HTTPHdr &hdr); + int _decode_header(const uint8_t *header_block, size_t header_block_len, HTTPHdr &hdr); + int _decode_indexed_header_field(int16_t base_index, const uint8_t *buf, size_t buf_len, HTTPHdr &hdr, uint32_t &header_len); + int _decode_indexed_header_field_with_postbase_index(int16_t base_index, const uint8_t *buf, size_t buf_len, HTTPHdr &hdr, + uint32_t &header_len); + int _decode_literal_header_field_with_name_ref(int16_t base_index, const uint8_t *buf, size_t buf_len, HTTPHdr &hdr, + uint32_t &header_len); + int _decode_literal_header_field_without_name_ref(const uint8_t *buf, size_t buf_len, HTTPHdr &hdr, uint32_t &header_len); + int _decode_literal_header_field_with_postbase_name_ref(int16_t base_index, const uint8_t *buf, size_t buf_len, HTTPHdr &hdr, + uint32_t &header_len); + + // Utilities + uint16_t _calc_absolute_index_from_relative_index(uint16_t base_index, uint16_t relative_index); + uint16_t _calc_absolute_index_from_postbase_index(uint16_t base_index, uint16_t postbase_index); + uint16_t _calc_relative_index_from_absolute_index(uint16_t base_index, uint16_t absolute_index); + uint16_t _calc_postbase_index_from_absolute_index(uint16_t base_index, uint16_t absolute_index); + void _attach_header(HTTPHdr &hdr, const char *name, int name_len, const char *value, int value_len, bool never_index); + + int _on_read_ready(QUICStreamIO &stream_io); + int _on_decoder_stream_read_ready(QUICStreamIO &stream_io); + int _on_encoder_stream_read_ready(QUICStreamIO &stream_io); + + int _on_write_ready(QUICStreamIO &stream_io); + int _on_decoder_write_ready(QUICStreamIO &stream_io); + int _on_encoder_write_ready(QUICStreamIO &stream_io); + + // Stream numbers + // FIXME How are these stream ids negotiated? In interop, encoder stream id have to be 0 and decoder stream id must not be used. + uint64_t _encoder_stream_id = 0; + uint64_t _decoder_stream_id = 9999; + + // Chain of sending instructions + MIOBuffer *_encoder_stream_sending_instructions; + MIOBuffer *_decoder_stream_sending_instructions; + IOBufferReader *_encoder_stream_sending_instructions_reader; + IOBufferReader *_decoder_stream_sending_instructions_reader; +}; diff --git a/proxy/http3/test/Mock.h b/proxy/http3/test/Mock.h new file mode 100644 index 00000000000..2b02ed014c2 --- /dev/null +++ b/proxy/http3/test/Mock.h @@ -0,0 +1,47 @@ +/** @file + * + * A brief file description + * + * @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 "catch.hpp" + +#include "Http3FrameHandler.h" + +class Http3MockFrameHandler : public Http3FrameHandler +{ +public: + int total_frame_received = 0; + + // Http3FrameHandler + + std::vector + interests() override + { + return {Http3FrameType::DATA}; + } + + Http3ErrorUPtr + handle_frame(std::shared_ptr frame) override + { + this->total_frame_received++; + return Http3ErrorUPtr(new Http3NoError()); + } +}; diff --git a/proxy/http3/test/main.cc b/proxy/http3/test/main.cc new file mode 100644 index 00000000000..002e9d6dd8b --- /dev/null +++ b/proxy/http3/test/main.cc @@ -0,0 +1,54 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +// To make compile faster +// https://github.com/philsquared/Catch/blob/master/docs/slow-compiles.md +#define CATCH_CONFIG_MAIN +#include "catch.hpp" + +#include "tscore/I_Layout.h" +#include "tscore/Diags.h" + +#include "RecordsConfig.h" +#include "Http3Config.h" + +struct EventProcessorListener : Catch::TestEventListenerBase { + using TestEventListenerBase::TestEventListenerBase; // inherit constructor + + virtual void + testRunStarting(Catch::TestRunInfo const &testRunInfo) override + { + BaseLogFile *base_log_file = new BaseLogFile("stderr"); + diags = new Diags(testRunInfo.name.c_str(), "" /* tags */, "" /* actions */, base_log_file); + diags->activate_taglist("vv_quic|quic", DiagsTagType_Debug); + diags->config.enabled[DiagsTagType_Debug] = true; + diags->show_location = SHOW_LOCATION_DEBUG; + + Layout::create(); + RecProcessInit(RECM_STAND_ALONE); + LibRecordsConfigInit(); + + Http3Config::startup(); + } +}; +CATCH_REGISTER_LISTENER(EventProcessorListener); diff --git a/proxy/http3/test/main_qpack.cc b/proxy/http3/test/main_qpack.cc new file mode 100644 index 00000000000..d0626372708 --- /dev/null +++ b/proxy/http3/test/main_qpack.cc @@ -0,0 +1,107 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +// To make compile faster +// https://github.com/philsquared/Catch/blob/master/docs/slow-compiles.md +// #define CATCH_CONFIG_MAIN +#define CATCH_CONFIG_RUNNER +#include "catch.hpp" + +#include "tscore/I_Layout.h" +#include "tscore/Diags.h" + +#include "I_EventSystem.h" +#include "RecordsConfig.h" + +#include "QUICConfig.h" +#include "HuffmanCodec.h" +#include "QPACK.h" +#include "HTTP.h" + +#define TEST_THREADS 1 + +char qifdir[256] = "./qifs/qifs"; +char encdir[256] = "./qifs/encoded"; +char decdir[256] = "./qifs/decoded"; +int tablesize = 4096; +int streams = 100; +int ackmode = 0; +char appname[256] = "ats"; +char pattern[256] = ""; + +struct EventProcessorListener : Catch::TestEventListenerBase { + using TestEventListenerBase::TestEventListenerBase; // inherit constructor + + virtual void + testRunStarting(Catch::TestRunInfo const &testRunInfo) override + { + BaseLogFile *base_log_file = new BaseLogFile("stderr"); + diags = new Diags(testRunInfo.name.c_str(), "" /* tags */, "" /* actions */, base_log_file); + diags->activate_taglist("qpack", DiagsTagType_Debug); + diags->config.enabled[DiagsTagType_Debug] = true; + diags->show_location = SHOW_LOCATION_DEBUG; + + Layout::create(); + RecProcessInit(RECM_STAND_ALONE); + LibRecordsConfigInit(); + + QUICConfig::startup(); + + ink_event_system_init(EVENT_SYSTEM_MODULE_PUBLIC_VERSION); + eventProcessor.start(TEST_THREADS); + + Thread *main_thread = new EThread; + main_thread->set_specific(); + + url_init(); + mime_init(); + http_init(); + hpack_huffman_init(); + } +}; +CATCH_REGISTER_LISTENER(EventProcessorListener); + +int +main(int argc, char *argv[]) +{ + Catch::Session session; + using namespace Catch::clara; + auto cli = + session.cli() | Opt(qifdir, "qifdir")["--q-qif-dir"]("path for a directory that contains QIF files (default:qifs/qifs") | + Opt(encdir, "encdir")["--q-encoded-dir"]("path for a directory that encoded files will be stored (default:qifs/encoded)") | + Opt(decdir, "decdir")["--q-decoded-dir"]("path for a directory that decoded files will be stored (default:qifs/decoded)") | + Opt(tablesize, "size")["--q-dynamic-table-size"]("dynamic table size for encoding: 0-65535 (default:4096)") | + Opt(streams, "n")["--q-max-blocked-streams"]("max blocked streams for encoding: 0-65535 (default:100)") | + Opt(ackmode, "mode")["--q-ack-mode"]("acknowledgement modes for encoding: none(default:0) or immediate(1)") | + Opt(pattern, "pattern")["--q-pattern"]("filename pattern: file name pattern for decoding (default:)") | + Opt(appname, "app")["--q-app"]("app name: app name (default:ats)"); + + session.cli(cli); + + int returnCode = session.applyCommandLine(argc, argv); + if (returnCode != 0) { + return returnCode; + } + + return session.run(); +} diff --git a/proxy/http3/test/test_Http3Frame.cc b/proxy/http3/test/test_Http3Frame.cc new file mode 100644 index 00000000000..a7f90d8938d --- /dev/null +++ b/proxy/http3/test/test_Http3Frame.cc @@ -0,0 +1,252 @@ +/** @file + * + * A brief file description + * + * @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 "catch.hpp" +#include +#include "Http3Frame.h" +#include "Http3FrameDispatcher.h" + +TEST_CASE("Http3Frame Type", "[http3]") +{ + CHECK(Http3Frame::type(reinterpret_cast("\x00\x00"), 2) == Http3FrameType::DATA); + // Undefined ragne + CHECK(Http3Frame::type(reinterpret_cast("\x0f\x00"), 2) == Http3FrameType::UNKNOWN); + CHECK(Http3Frame::type(reinterpret_cast("\xff\xff\xff\xff\xff\xff\xff\x00"), 9) == Http3FrameType::UNKNOWN); +} + +TEST_CASE("Load DATA Frame", "[http3]") +{ + SECTION("No flags") + { + uint8_t buf1[] = { + 0x00, // Type + 0x04, // Length + 0x11, 0x22, 0x33, 0x44, // Payload + }; + std::shared_ptr frame1 = Http3FrameFactory::create(buf1, sizeof(buf1)); + CHECK(frame1->type() == Http3FrameType::DATA); + CHECK(frame1->length() == 4); + + std::shared_ptr data_frame = std::dynamic_pointer_cast(frame1); + CHECK(data_frame); + CHECK(data_frame->payload_length() == 4); + CHECK(memcmp(data_frame->payload(), "\x11\x22\x33\x44", 4) == 0); + } + + SECTION("Have flags (invalid)") + { + uint8_t buf1[] = { + 0x00, // Type + 0x04, // Length + 0x11, 0x22, 0x33, 0x44, // Payload + }; + std::shared_ptr frame1 = Http3FrameFactory::create(buf1, sizeof(buf1)); + CHECK(frame1->type() == Http3FrameType::DATA); + CHECK(frame1->length() == 4); + + std::shared_ptr data_frame = std::dynamic_pointer_cast(frame1); + CHECK(data_frame); + CHECK(data_frame->payload_length() == 4); + CHECK(memcmp(data_frame->payload(), "\x11\x22\x33\x44", 4) == 0); + } +} + +TEST_CASE("Store DATA Frame", "[http3]") +{ + SECTION("Normal") + { + uint8_t buf[32] = {0}; + size_t len; + uint8_t expected1[] = { + 0x00, // Type + 0x04, // Length + 0x11, 0x22, 0x33, 0x44, // Payload + }; + + uint8_t raw1[] = "\x11\x22\x33\x44"; + ats_unique_buf payload1 = ats_unique_malloc(4); + memcpy(payload1.get(), raw1, 4); + + Http3DataFrame data_frame(std::move(payload1), 4); + CHECK(data_frame.length() == 4); + + data_frame.store(buf, &len); + CHECK(len == 6); + CHECK(memcmp(buf, expected1, len) == 0); + } +} + +TEST_CASE("Store HEADERS Frame", "[http3]") +{ + SECTION("Normal") + { + uint8_t buf[32] = {0}; + size_t len; + uint8_t expected1[] = { + 0x01, // Type + 0x04, // Length + 0x11, 0x22, 0x33, 0x44, // Payload + }; + + uint8_t raw1[] = "\x11\x22\x33\x44"; + ats_unique_buf header_block = ats_unique_malloc(4); + memcpy(header_block.get(), raw1, 4); + + Http3HeadersFrame hdrs_frame(std::move(header_block), 4); + CHECK(hdrs_frame.length() == 4); + + hdrs_frame.store(buf, &len); + CHECK(len == 6); + CHECK(memcmp(buf, expected1, len) == 0); + } +} + +TEST_CASE("Load SETTINGS Frame", "[http3]") +{ + SECTION("Normal") + { + uint8_t buf[] = { + 0x04, // Type + 0x08, // Length + 0x06, // Identifier + 0x44, 0x00, // Value + 0x09, // Identifier + 0x0f, // Value + 0x4a, 0xba, // Identifier + 0x00, // Value + }; + + std::shared_ptr frame = Http3FrameFactory::create(buf, sizeof(buf)); + CHECK(frame->type() == Http3FrameType::SETTINGS); + CHECK(frame->length() == sizeof(buf) - 2); + + std::shared_ptr settings_frame = std::dynamic_pointer_cast(frame); + CHECK(settings_frame); + CHECK(settings_frame->is_valid()); + CHECK(settings_frame->get(Http3SettingsId::MAX_HEADER_LIST_SIZE) == 0x0400); + CHECK(settings_frame->get(Http3SettingsId::NUM_PLACEHOLDERS) == 0x0f); + } +} + +TEST_CASE("Store SETTINGS Frame", "[http3]") +{ + SECTION("Normal") + { + uint8_t expected[] = { + 0x04, // Type + 0x08, // Length + 0x06, // Identifier + 0x44, 0x00, // Value + 0x09, // Identifier + 0x0f, // Value + 0x4a, 0x0a, // Identifier + 0x00, // Value + }; + + Http3SettingsFrame settings_frame; + settings_frame.set(Http3SettingsId::MAX_HEADER_LIST_SIZE, 0x0400); + settings_frame.set(Http3SettingsId::NUM_PLACEHOLDERS, 0x0f); + + uint8_t buf[32] = {0}; + size_t len; + settings_frame.store(buf, &len); + CHECK(len == sizeof(expected)); + CHECK(memcmp(buf, expected, len) == 0); + } + + SECTION("Normal from Client") + { + uint8_t expected[] = { + 0x04, // Type + 0x06, // Length + 0x06, // Identifier + 0x44, 0x00, // Value + 0x4a, 0x0a, // Identifier + 0x00, // Value + }; + + Http3SettingsFrame settings_frame; + settings_frame.set(Http3SettingsId::MAX_HEADER_LIST_SIZE, 0x0400); + + uint8_t buf[32] = {0}; + size_t len; + settings_frame.store(buf, &len); + CHECK(len == sizeof(expected)); + CHECK(memcmp(buf, expected, len) == 0); + } +} + +TEST_CASE("Http3FrameFactory Create Unknown Frame", "[http3]") +{ + uint8_t buf1[] = { + 0x0f, // Type + 0x00, // Length + }; + std::shared_ptr frame1 = Http3FrameFactory::create(buf1, sizeof(buf1)); + CHECK(frame1); + CHECK(frame1->type() == Http3FrameType::UNKNOWN); + CHECK(frame1->length() == 0); +} + +TEST_CASE("Http3FrameFactory Fast Create Frame", "[http3]") +{ + Http3FrameFactory factory; + + uint8_t buf1[] = { + 0x00, // Type + 0x04, // Length + 0x11, 0x22, 0x33, 0x44, // Payload + }; + uint8_t buf2[] = { + 0x00, // Type + 0x04, // Length + 0xaa, 0xbb, 0xcc, 0xdd, // Payload + }; + std::shared_ptr frame1 = factory.fast_create(buf1, sizeof(buf1)); + CHECK(frame1 != nullptr); + + std::shared_ptr data_frame1 = std::dynamic_pointer_cast(frame1); + CHECK(data_frame1 != nullptr); + CHECK(memcmp(data_frame1->payload(), buf1 + 2, 4) == 0); + + std::shared_ptr frame2 = factory.fast_create(buf2, sizeof(buf2)); + CHECK(frame2 != nullptr); + + std::shared_ptr data_frame2 = std::dynamic_pointer_cast(frame2); + CHECK(data_frame2 != nullptr); + CHECK(memcmp(data_frame2->payload(), buf2 + 2, 4) == 0); + + CHECK(frame1 == frame2); +} + +TEST_CASE("Http3FrameFactory Fast Create Unknown Frame", "[http3]") +{ + Http3FrameFactory factory; + + uint8_t buf1[] = { + 0x0f, // Type + }; + std::shared_ptr frame1 = factory.fast_create(buf1, sizeof(buf1)); + CHECK(frame1); + CHECK(frame1->type() == Http3FrameType::UNKNOWN); +} diff --git a/proxy/http3/test/test_Http3FrameDispatcher.cc b/proxy/http3/test/test_Http3FrameDispatcher.cc new file mode 100644 index 00000000000..3e8a1ae4966 --- /dev/null +++ b/proxy/http3/test/test_Http3FrameDispatcher.cc @@ -0,0 +1,50 @@ +/** @file + * + * A brief file description + * + * @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 "catch.hpp" + +#include "Http3FrameDispatcher.h" +#include "Mock.h" + +TEST_CASE("Http3FrameHandler dispatch", "[http3]") +{ + uint8_t input[] = {// 1st frame (HEADERS) + 0x02, 0x01, 0x00, 0x01, 0x23, + // 2nd frame (DATA) + 0x04, 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, + // 3rd frame (incomplete) + 0xff}; + + Http3FrameDispatcher http3FrameDispatcher; + Http3MockFrameHandler handler; + http3FrameDispatcher.add_handler(&handler); + uint16_t nread = 0; + + // Initial state + CHECK(handler.total_frame_received == 0); + CHECK(nread == 0); + + http3FrameDispatcher.on_read_ready(input, sizeof(input), nread); + CHECK(handler.total_frame_received == 1); + CHECK(nread == 12); +} diff --git a/proxy/http3/test/test_QPACK.cc b/proxy/http3/test/test_QPACK.cc new file mode 100644 index 00000000000..f76626d457c --- /dev/null +++ b/proxy/http3/test/test_QPACK.cc @@ -0,0 +1,466 @@ +/** @file + * + * A brief file description + * + * @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 "catch.hpp" +#include +#include +#include +#include "XPACK.h" +#include "QPACK.h" +#include "HTTP.h" +#include "../../iocore/net/quic/Mock.h" + +// Declared in main_qpack.cc +extern char qifdir[256]; +extern char encdir[256]; +extern char decdir[256]; +extern int tablesize; +extern int streams; +extern int ackmode; +extern char appname[256]; +extern char pattern[256]; + +constexpr int ACK_MODE_IMMEDIATE = 1; +constexpr int ACK_MODE_NONE = 0; + +constexpr int MAX_SEQUENCE = 1024; + +class TestQUICConnection : public MockQUICConnection +{ +}; + +class QUICApplicationDriver +{ +public: + QUICApplicationDriver() {} + + QUICConnection * + get_connection() + { + return &this->_connection; + } + +private: + TestQUICConnection _connection; +}; + +// TODO: QUICUnidirectionalStream should be used if there +class TestQUICStream : public QUICBidirectionalStream +{ +public: + TestQUICStream(QUICStreamId sid) + : QUICBidirectionalStream(new MockQUICRTTProvider(), new MockQUICConnectionInfoProvider(), sid, 65536, 65536) + { + } + + void + write(const uint8_t *buf, size_t buf_len, QUICOffset offset, bool last) + { + this->_write_to_read_vio(offset, buf, buf_len, last); + this->_signal_read_event(); + } + + size_t + read(uint8_t *buf, size_t buf_len) + { + this->_signal_write_event(); + IOBufferReader *reader = this->_write_vio.get_reader(); + return reader->read(buf, buf_len); + } +}; + +class TestQPACKEventHandler : public Continuation +{ +public: + TestQPACKEventHandler() : Continuation() { SET_HANDLER(&TestQPACKEventHandler::event_handler); } + + int + event_handler(int event, Event *data) + { + this->_event = event; + return 0; + } + + int + last_event() + { + return this->_event; + } + +private: + int _event = 0; +}; + +static int +load_qif_file(const char *filename, HTTPHdr **headers) +{ + HTTPHdr *hdr = nullptr; + int n = 0; + std::ifstream ifs(filename); + std::string line; + + while (std::getline(ifs, line)) { + if (line.empty()) { + if (hdr) { + headers[n++] = hdr; + hdr = nullptr; + } else { + continue; + } + } else if (line.at(0) == '#') { + continue; + } else { + if (!hdr) { + hdr = new HTTPHdr(); + hdr->create(HTTP_TYPE_REQUEST); + } + auto tab = line.find_first_of('\t'); + auto name = line.substr(0, tab); + auto value = line.substr(tab + 1); + auto field = hdr->field_create(name.c_str(), tab); + hdr->field_attach(field); + hdr->field_value_set(field, value.c_str(), line.length() - tab - 1); + } + } + if (hdr) { + headers[n++] = hdr; + } + + return n; +} + +void +output_encoder_stream_data(FILE *fd, TestQUICStream *stream) +{ + uint8_t buf[1024]; + + // Write StreamId (0) + uint64_t stream_id = 0; + fwrite(reinterpret_cast(&stream_id), 8, 1, fd); + + // Skip 32 bits for Legnth + fseek(fd, 4, SEEK_CUR); + + // Write QPACKData + uint64_t total, nread; + total = 0; + while ((nread = stream->read(buf, sizeof(buf))) > 0) { + fwrite(buf, nread, 1, fd); + total += nread; + } + + // Back to the posistion for Length + fseek(fd, -(total + 4), SEEK_CUR); + + // Write Length + uint32_t len = htobe32(total); + fwrite(reinterpret_cast(&len), 4, 1, fd); + + // Back to the tail + fseek(fd, 0, SEEK_END); +} + +void +output_encoded_data(FILE *fd, uint64_t stream_id, IOBufferReader *header_block_reader) +{ + uint8_t buf[1024]; + + // Write StreamId + stream_id = htobe64(stream_id); + fwrite(reinterpret_cast(&stream_id), 8, 1, fd); + + // Skip 32 bits for Legnth + fseek(fd, 4, SEEK_CUR); + + // Write QPACKData + int64_t total, nread; + total = 0; + while ((nread = header_block_reader->read(buf, sizeof(buf))) > 0) { + fwrite(buf, nread, 1, fd); + total += nread; + } + + // Back to the posistion for Length + fseek(fd, -(total + 4), SEEK_CUR); + + // Write Length + uint32_t len = htobe32(total); + fwrite(reinterpret_cast(&len), 4, 1, fd); + + // Back to the tail + fseek(fd, 0, SEEK_END); +} + +void +output_decoded_headers(FILE *fd, HTTPHdr **headers, uint64_t n) +{ + for (uint64_t i = 0; i < n; ++i) { + HTTPHdr *header_set = headers[i]; + if (!header_set) { + continue; + } + fprintf(fd, "# stream %" PRIu64 "\n", i + 1); + MIMEFieldIter field_iter; + for (MIMEField *field = header_set->iter_get_first(&field_iter); field != nullptr; + field = header_set->iter_get_next(&field_iter)) { + int name_len; + int value_len; + Arena arena; + const char *name = field->name_get(&name_len); + char *lowered_name = arena.str_store(name, name_len); + for (int i = 0; i < name_len; i++) { + lowered_name[i] = ParseRules::ink_tolower(lowered_name[i]); + } + const char *value = field->value_get(&value_len); + fprintf(fd, "%.*s\t%.*s\n", name_len, lowered_name, value_len, value); + } + fprintf(fd, "\n"); + } +} + +static int +read_block(FILE *fd, uint64_t &stream_id, uint8_t **head, uint32_t &block_len) +{ + size_t len; + + // Read Stream ID + len = fread(&stream_id, 1, 8, fd); + if (len != 8) { + return -1; + } + stream_id = be64toh(stream_id); + + // Read Length + len = fread(&block_len, 1, 4, fd); + if (len != 4) { + return -1; + } + block_len = be32toh(block_len); + + // Set the head of block + *head = reinterpret_cast(ats_malloc(block_len)); + len = fread(*head, 1, block_len, fd); + if (len != block_len) { + ats_free(*head); + return -1; + } + + return 0; +} + +void +acknowledge_header_block(TestQUICStream *stream, uint64_t stream_id) +{ + uint8_t buf[128]; + + buf[0] = 0x80; + int ret = xpack_encode_integer(buf, buf + sizeof(buf), stream_id, 7); + stream->write(buf, ret, 0, stream_id); +} + +static int +test_encode(const char *qif_file, const char *out_file, int dts, int mbs, int am) +{ + int ret = 0; + + FILE *fd = fopen(out_file, "w"); + if (!fd) { + std::cerr << "couldn't open file: " << out_file << std::endl; + REQUIRE(false); + return -1; + } + + HTTPHdr *requests[MAX_SEQUENCE] = {nullptr}; + int n_requests = load_qif_file(qif_file, requests); + + QUICApplicationDriver driver; + QPACK *qpack = new QPACK(driver.get_connection(), UINT32_MAX, dts, mbs); + TestQUICStream *encoder_stream = new TestQUICStream(0); + TestQUICStream *decoder_stream = new TestQUICStream(9999); + qpack->set_stream(encoder_stream); + qpack->set_stream(decoder_stream); + + uint64_t stream_id = 1; + MIOBuffer *header_block = new_MIOBuffer(); + uint64_t header_block_len = 0; + IOBufferReader *header_block_reader = header_block->alloc_reader(); + for (int i = 0; i < n_requests; ++i) { + HTTPHdr *hdr = requests[i]; + ret = qpack->encode(stream_id, *hdr, header_block, header_block_len); + if (ret < 0) { + break; + } + + output_encoder_stream_data(fd, encoder_stream); + output_encoded_data(fd, stream_id, header_block_reader); + + if (am == ACK_MODE_IMMEDIATE) { + acknowledge_header_block(decoder_stream, stream_id); + } + + ++stream_id; + } + + fflush(fd); + fclose(fd); + + return ret; +} + +static int +test_decode(const char *enc_file, const char *out_file, int dts, int mbs, int am, const char *app_name) +{ + int ret = 0; + + FILE *fd_in = fopen(enc_file, "r"); + if (!fd_in) { + std::cerr << "couldn't open file: " << enc_file << std::endl; + REQUIRE(false); + return -1; + } + + FILE *fd_out = fopen(out_file, "w"); + if (!fd_out) { + std::cerr << "couldn't open file: " << out_file << std::endl; + REQUIRE(false); + return -1; + } + + // HTTPHdr *requests[MAX_SEQUENCE]; + // int n_requests = load_qif_file(qif_file, requests); + + TestQPACKEventHandler *event_handler = new TestQPACKEventHandler(); + + QUICApplicationDriver driver; + QPACK *qpack = new QPACK(driver.get_connection(), UINT32_MAX, dts, mbs); + TestQUICStream *encoder_stream = new TestQUICStream(0); + qpack->set_stream(encoder_stream); + + int offset = 0; + uint8_t *block = nullptr; + uint32_t block_len; + int read_len = 0; + + uint64_t stream_id = 1; + HTTPHdr *header_sets[MAX_SEQUENCE] = {nullptr}; + int n_headers = 0; + while ((read_len = read_block(fd_in, stream_id, &block, block_len)) >= 0) { + if (stream_id == encoder_stream->id()) { + encoder_stream->write(block, block_len, offset, false); + offset += block_len; + } else { + if (!header_sets[stream_id - 1]) { + header_sets[stream_id - 1] = new HTTPHdr(); + header_sets[stream_id - 1]->create(HTTP_TYPE_REQUEST); + ++n_headers; + } + qpack->decode(stream_id, block, block_len, *header_sets[stream_id - 1], event_handler, eventProcessor.all_ethreads[0]); + } + ats_free(block); + } + + if (!feof(fd_in)) { + return -1; + } + + sleep(1); + + CHECK(event_handler->last_event() == QPACK_EVENT_DECODE_COMPLETE); + + output_decoded_headers(fd_out, header_sets, n_headers); + + for (unsigned int i = 0; i < countof(header_sets); ++i) { + if (header_sets[i]) { + header_sets[i]->destroy(); + delete header_sets[i]; + } + } + + return ret; +} + +TEST_CASE("Encoding", "[qpack-encode]") +{ + struct dirent *d; + DIR *dir = opendir(qifdir); + + if (dir == nullptr) { + std::cerr << "couldn't open dir: " << qifdir << std::endl; + return; + } + + struct stat st; + char qif_file[PATH_MAX + 1] = ""; + char out_file[PATH_MAX + 1] = ""; + strcat(qif_file, qifdir); + strcat(out_file, encdir); + + while ((d = readdir(dir)) != nullptr) { + char section_name[1024]; + sprintf(section_name, "%s: DTS=%d, MBS=%d, AM=%d", d->d_name, tablesize, streams, ackmode); + SECTION(section_name) + { + qif_file[strlen(qifdir)] = '/'; + qif_file[strlen(qifdir) + 1] = '\0'; + ink_strlcat(qif_file, d->d_name, sizeof(qif_file)); + stat(qif_file, &st); + if (S_ISREG(st.st_mode) && strstr(d->d_name, ".qif") == (d->d_name + (strlen(d->d_name) - 4))) { + sprintf(out_file + strlen(encdir), "/ats/%s.ats.%d.%d.%d", d->d_name, tablesize, streams, ackmode); + CHECK(test_encode(qif_file, out_file, tablesize, streams, ackmode) == 0); + } + } + } +} + +TEST_CASE("Decoding", "[qpack-decode]") +{ + char app_dir[PATH_MAX + 1] = ""; + sprintf(app_dir, "%s/%s", encdir, appname); + struct dirent *d; + DIR *dir = opendir(app_dir); + + if (dir == nullptr) { + std::cerr << "couldn't open dir: " << app_dir << std::endl; + return; + } + + struct stat st; + char enc_file[PATH_MAX + 1] = ""; + char out_file[PATH_MAX + 1] = ""; + strcat(enc_file, encdir); + strcat(out_file, decdir); + + while ((d = readdir(dir)) != nullptr) { + char section_name[1024]; + sprintf(section_name, "%s: DTS=%d, MBS=%d, AM=%d, APP=%s", d->d_name, tablesize, streams, ackmode, appname); + SECTION(section_name) + { + sprintf(enc_file + strlen(encdir), "/%s/%s", appname, d->d_name); + stat(enc_file, &st); + if (S_ISREG(st.st_mode) && strstr(d->d_name, pattern)) { + sprintf(out_file + strlen(decdir), "/%s/%s.decoded", appname, d->d_name); + CHECK(test_decode(enc_file, out_file, tablesize, streams, ackmode, appname) == 0); + } + } + } +} diff --git a/src/Makefile.am b/src/Makefile.am index 0d7f07973ae..b6a1b98bd2c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -41,5 +41,9 @@ include traffic_ctl/Makefile.inc include traffic_layout/Makefile.inc include traffic_logcat/Makefile.inc +if ENABLE_QUIC +include traffic_quic/Makefile.inc +endif + clang-tidy-local: $(DIST_SOURCES) $(CXX_Clang_Tidy) diff --git a/src/traffic_layout/info.cc b/src/traffic_layout/info.cc index 22945244088..e275513754b 100644 --- a/src/traffic_layout/info.cc +++ b/src/traffic_layout/info.cc @@ -104,6 +104,7 @@ produce_features(bool json) print_feature("TS_HAS_IP_TOS", TS_HAS_IP_TOS, json); print_feature("TS_USE_HWLOC", TS_USE_HWLOC, json); print_feature("TS_USE_SET_RBIO", TS_USE_SET_RBIO, json); + print_feature("TS_USE_TLS13", TS_USE_TLS13, json); print_feature("TS_USE_LINUX_NATIVE_AIO", TS_USE_LINUX_NATIVE_AIO, 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_quic/Makefile.inc b/src/traffic_quic/Makefile.inc new file mode 100644 index 00000000000..963576f5c2d --- /dev/null +++ b/src/traffic_quic/Makefile.inc @@ -0,0 +1,63 @@ +# +# 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. + +bin_PROGRAMS += traffic_quic/traffic_quic + +traffic_quic_traffic_quic_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + $(iocore_include_dirs) \ + -I$(abs_top_srcdir)/lib \ + -I$(abs_top_srcdir)/lib/records \ + -I$(abs_top_srcdir)/mgmt \ + -I$(abs_top_srcdir)/mgmt/utils \ + -I$(abs_top_srcdir)/proxy \ + -I$(abs_top_srcdir)/proxy/hdrs \ + -I$(abs_top_srcdir)/proxy/http \ + -I$(abs_top_srcdir)/proxy/http/remap \ + -I$(abs_top_srcdir)/proxy/http3 \ + -I$(abs_top_srcdir)/proxy/logging \ + -I$(abs_top_srcdir)/proxy/shared \ + $(TS_INCLUDES) \ + @OPENSSL_INCLUDES@ + +traffic_quic_traffic_quic_LDFLAGS = \ + $(AM_LDFLAGS) \ + @OPENSSL_LDFLAGS@ + +traffic_quic_traffic_quic_SOURCES = \ + traffic_quic/quic_client.cc \ + traffic_quic/traffic_quic.cc + +traffic_quic_traffic_quic_LDADD = \ + $(top_builddir)/iocore/net/libinknet.a \ + $(top_builddir)/iocore/aio/libinkaio.a \ + $(top_builddir)/iocore/net/quic/libquic.a \ + $(top_builddir)/iocore/eventsystem/libinkevent.a \ + $(top_builddir)/lib/records/librecords_p.a \ + $(top_builddir)/mgmt/libmgmt_p.la \ + $(top_builddir)/src/tscore/libtscore.la \ + $(top_builddir)/src/tscpp/util/libtscpputil.la \ + $(top_builddir)/proxy/ParentSelectionStrategy.o \ + $(top_builddir)/proxy/http3/libhttp3.a \ + $(top_builddir)/proxy/http2/libhttp2.a \ + $(top_builddir)/proxy/libproxy.a \ + $(top_builddir)/proxy/hdrs/libhdrs.a \ + @HWLOC_LIBS@ \ + @YAMLCPP_LIBS@ \ + @OPENSSL_LIBS@ \ + @LIBPCRE@ + diff --git a/src/traffic_quic/diags.h b/src/traffic_quic/diags.h new file mode 100644 index 00000000000..031d6b54afc --- /dev/null +++ b/src/traffic_quic/diags.h @@ -0,0 +1,96 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +// copy from iocore/utils/diags.i +#pragma once + +#include "tscore/Diags.h" + +#define DIAGS_LOG_FILE "diags.log" + +static void +reconfigure_diags() +{ + int i; + DiagsConfigState c; + + // initial value set to 0 or 1 based on command line tags + c.enabled[DiagsTagType_Debug] = (diags->base_debug_tags != nullptr); + c.enabled[DiagsTagType_Action] = (diags->base_action_tags != nullptr); + + c.enabled[DiagsTagType_Debug] = 1; + c.enabled[DiagsTagType_Action] = 1; + diags->show_location = SHOW_LOCATION_ALL; + + // read output routing values + for (i = 0; i < DL_Status; i++) { + c.outputs[i].to_stdout = 0; + c.outputs[i].to_stderr = 1; + c.outputs[i].to_syslog = 0; + c.outputs[i].to_diagslog = 0; + } + + for (i = DL_Status; i < DiagsLevel_Count; i++) { + c.outputs[i].to_stdout = 0; + c.outputs[i].to_stderr = 0; + c.outputs[i].to_syslog = 0; + c.outputs[i].to_diagslog = 1; + } + + ////////////////////////////// + // clear out old tag tables // + ////////////////////////////// + + diags->deactivate_all(DiagsTagType_Debug); + diags->deactivate_all(DiagsTagType_Action); + + ////////////////////////////////////////////////////////////////////// + // add new tag tables + ////////////////////////////////////////////////////////////////////// + + if (diags->base_debug_tags) + diags->activate_taglist(diags->base_debug_tags, DiagsTagType_Debug); + if (diags->base_action_tags) + diags->activate_taglist(diags->base_action_tags, DiagsTagType_Action); + +//////////////////////////////////// +// change the diags config values // +//////////////////////////////////// +#if !defined(__GNUC__) && !defined(hpux) + diags->config = c; +#else + memcpy(((void *)&diags->config), ((void *)&c), sizeof(DiagsConfigState)); +#endif +} + +static void +init_diags(const char *bdt, const char *bat) +{ + char diags_logpath[500]; + strcpy(diags_logpath, DIAGS_LOG_FILE); + + diags = new Diags("Client", bdt, bat, new BaseLogFile(diags_logpath)); + Status("opened %s", diags_logpath); + + reconfigure_diags(); +} diff --git a/src/traffic_quic/quic_client.cc b/src/traffic_quic/quic_client.cc new file mode 100644 index 00000000000..007c20ae3e8 --- /dev/null +++ b/src/traffic_quic/quic_client.cc @@ -0,0 +1,384 @@ +/** @file + * + * A brief file description + * + * @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 "quic_client.h" + +#include +#include +#include + +#include "Http3Transaction.h" + +// OpenSSL protocol-lists format (vector of 8-bit length-prefixed, byte strings) +// https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set_alpn_protos.html +// Should be integrate with IP_PROTO_TAG_HTTP_QUIC in ts/ink_inet.h ? +using namespace std::literals; +static constexpr std::string_view HQ_ALPN_PROTO_LIST("\5hq-20"sv); +static constexpr std::string_view H3_ALPN_PROTO_LIST("\5h3-20"sv); + +QUICClient::QUICClient(const QUICClientConfig *config) : Continuation(new_ProxyMutex()), _config(config) +{ + SET_HANDLER(&QUICClient::start); +} + +QUICClient::~QUICClient() +{ + freeaddrinfo(this->_remote_addr_info); +} + +int +QUICClient::start(int, void *) +{ + SET_HANDLER(&QUICClient::state_http_server_open); + + struct addrinfo hints; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = 0; + hints.ai_protocol = 0; + + int res = getaddrinfo(this->_config->addr, this->_config->port, &hints, &this->_remote_addr_info); + if (res < 0) { + Debug("quic_client", "Error: %s (%d)", strerror(errno), errno); + return EVENT_DONE; + } + + std::string_view alpn_protos; + if (this->_config->http3) { + alpn_protos = H3_ALPN_PROTO_LIST; + } else { + alpn_protos = HQ_ALPN_PROTO_LIST; + } + + for (struct addrinfo *info = this->_remote_addr_info; info != nullptr; info = info->ai_next) { + NetVCOptions opt; + opt.ip_proto = NetVCOptions::USE_UDP; + opt.ip_family = info->ai_family; + opt.etype = ET_NET; + opt.socket_recv_bufsize = 1048576; + opt.socket_send_bufsize = 1048576; + opt.alpn_protos = alpn_protos; + opt.set_sni_servername(this->_config->addr, strnlen(this->_config->addr, 1023)); + + SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread()); + + Action *action = quic_NetProcessor.connect_re(this, info->ai_addr, &opt); + if (action == ACTION_RESULT_DONE) { + break; + } + } + return EVENT_CONT; +} + +// Similar to HttpSM::state_http_server_open(int event, void *data) +int +QUICClient::state_http_server_open(int event, void *data) +{ + switch (event) { + case NET_EVENT_OPEN: { + // TODO: create ProxyServerSession / ProxyServerTransaction + Debug("quic_client", "start proxy server ssn/txn"); + + QUICNetVConnection *conn = static_cast(data); + + if (this->_config->http0_9) { + Http09ClientApp *app = new Http09ClientApp(conn, this->_config); + app->start(); + } else if (this->_config->http3) { + // TODO: see what server session is doing with IpAllow::ACL + IpAllow::ACL session_acl; + Http3ClientApp *app = new Http3ClientApp(conn, std::move(session_acl), options, this->_config); + SCOPED_MUTEX_LOCK(lock, app->mutex, this_ethread()); + app->start(); + } else { + ink_abort("invalid config"); + } + + break; + } + case NET_EVENT_OPEN_FAILED: { + ink_assert(false); + break; + } + case NET_EVENT_ACCEPT: { + // do nothing + break; + } + default: + ink_assert(false); + } + + return 0; +} + +// +// Http09ClientApp +// +#define Http09ClientAppDebug(fmt, ...) Debug("quic_client_app", "[%s] " fmt, this->_qc->cids().data(), ##__VA_ARGS__) +#define Http09ClientAppVDebug(fmt, ...) Debug("v_quic_client_app", "[%s] " fmt, this->_qc->cids().data(), ##__VA_ARGS__) + +Http09ClientApp::Http09ClientApp(QUICNetVConnection *qvc, const QUICClientConfig *config) : QUICApplication(qvc), _config(config) +{ + this->_qc->stream_manager()->set_default_application(this); + + SET_HANDLER(&Http09ClientApp::main_event_handler); +} + +void +Http09ClientApp::start() +{ + if (this->_config->output[0] != 0x0) { + this->_filename = this->_config->output; + } + + if (this->_filename) { + // Destroy contents if file already exists + std::ofstream f_stream(this->_filename, std::ios::binary | std::ios::trunc); + } + + this->_do_http_request(); +} + +void +Http09ClientApp::_do_http_request() +{ + QUICStreamId stream_id; + QUICConnectionErrorUPtr error = this->_qc->stream_manager()->create_bidi_stream(stream_id); + + if (error != nullptr) { + Error("%s", error->msg); + ink_abort("Could not create bidi stream : %s", error->msg); + } + + // TODO: move to transaction + char request[1024] = {0}; + int request_len = snprintf(request, sizeof(request), "GET %s\r\n", this->_config->path); + + Http09ClientAppDebug("\n%s", request); + + QUICStreamIO *stream_io = this->_find_stream_io(stream_id); + + stream_io->write(reinterpret_cast(request), request_len); + stream_io->write_done(); + stream_io->write_reenable(); +} + +int +Http09ClientApp::main_event_handler(int event, Event *data) +{ + Http09ClientAppVDebug("%s (%d)", get_vc_event_name(event), event); + + VIO *vio = reinterpret_cast(data); + QUICStreamIO *stream_io = this->_find_stream_io(vio); + + if (stream_io == nullptr) { + Http09ClientAppDebug("Unknown Stream"); + return -1; + } + + switch (event) { + case VC_EVENT_READ_READY: + case VC_EVENT_READ_COMPLETE: { + std::streambuf *default_stream = nullptr; + std::ofstream f_stream; + + if (this->_filename) { + default_stream = std::cout.rdbuf(); + f_stream = std::ofstream(this->_filename, std::ios::binary | std::ios::app); + std::cout.rdbuf(f_stream.rdbuf()); + } + + uint8_t buf[8192] = {0}; + int64_t nread; + while ((nread = stream_io->read(buf, sizeof(buf))) > 0) { + std::cout.write(reinterpret_cast(buf), nread); + } + std::cout.flush(); + + if (this->_filename) { + f_stream.close(); + std::cout.rdbuf(default_stream); + } + + if (stream_io->is_read_done() && this->_config->close) { + // Connection Close Exercise + this->_qc->close(QUICConnectionErrorUPtr(new QUICConnectionError(QUICTransErrorCode::NO_ERROR, "Close Exercise"))); + } + + break; + } + case VC_EVENT_WRITE_READY: + case VC_EVENT_WRITE_COMPLETE: + break; + case VC_EVENT_EOS: + case VC_EVENT_ERROR: + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: + ink_assert(false); + break; + default: + break; + } + + return EVENT_CONT; +} + +// +// Http3ClientApp +// +Http3ClientApp::Http3ClientApp(QUICNetVConnection *qvc, IpAllow::ACL &&session_acl, const HttpSessionAccept::Options &options, + const QUICClientConfig *config) + : super(qvc, std::move(session_acl), options), _config(config) +{ +} + +Http3ClientApp::~Http3ClientApp() +{ + free_MIOBuffer(this->_req_buf); + this->_req_buf = nullptr; + + free_MIOBuffer(this->_resp_buf); + this->_resp_buf = nullptr; + + delete this->_resp_handler; +} + +void +Http3ClientApp::start() +{ + this->_req_buf = new_MIOBuffer(); + this->_resp_buf = new_MIOBuffer(); + IOBufferReader *resp_buf_reader = _resp_buf->alloc_reader(); + + this->_resp_handler = new RespHandler(this->_config, resp_buf_reader); + + super::start(); + this->_do_http_request(); +} + +void +Http3ClientApp::_do_http_request() +{ + QUICConnectionErrorUPtr error; + QUICStreamId stream_id; + error = this->_qc->stream_manager()->create_bidi_stream(stream_id); + if (error != nullptr) { + Error("%s", error->msg); + ink_abort("Could not create bidi stream : %s", error->msg); + } + + QUICStreamIO *stream_io = this->_find_stream_io(stream_id); + + // TODO: create Http3ServerTransaction + Http3Transaction *txn = new Http3Transaction(this->_ssn, stream_io); + SCOPED_MUTEX_LOCK(lock, txn->mutex, this_ethread()); + + // TODO: fix below issue with H2 origin conn stuff + // Do not call ProxyClientTransaction::new_transaction(), but need to setup txn - e.g. do_io_write / do_io_read + VIO *read_vio = txn->do_io_read(this->_resp_handler, INT64_MAX, this->_resp_buf); + this->_resp_handler->set_read_vio(read_vio); + + // Write HTTP Request to write_vio + char request[1024] = {0}; + std::string format; + if (this->_config->path[0] == '/') { + format = "GET https://%s%s HTTP/1.1\r\n\r\n"; + } else { + format = "GET https://%s/%s HTTP/1.1\r\n\r\n"; + } + + int request_len = snprintf(request, sizeof(request), format.c_str(), this->_config->addr, this->_config->path); + + Http09ClientAppDebug("\n%s", request); + + // TODO: check write avail size + int64_t nbytes = this->_req_buf->write(request, request_len); + IOBufferReader *buf_start = this->_req_buf->alloc_reader(); + txn->do_io_write(this, nbytes, buf_start); +} + +// +// Response Handler +// +RespHandler::RespHandler(const QUICClientConfig *config, IOBufferReader *reader) + : Continuation(new_ProxyMutex()), _config(config), _reader(reader) +{ + if (this->_config->output[0] != 0x0) { + this->_filename = this->_config->output; + } + + if (this->_filename) { + // Destroy contents if file already exists + std::ofstream f_stream(this->_filename, std::ios::binary | std::ios::trunc); + } + + SET_HANDLER(&RespHandler::main_event_handler); +} + +void +RespHandler::set_read_vio(VIO *vio) +{ + this->_read_vio = vio; +} + +int +RespHandler::main_event_handler(int event, Event *data) +{ + Debug("v_http3", "%s", get_vc_event_name(event)); + switch (event) { + case VC_EVENT_READ_READY: + case VC_EVENT_READ_COMPLETE: { + std::streambuf *default_stream = nullptr; + std::ofstream f_stream; + + if (this->_filename) { + default_stream = std::cout.rdbuf(); + f_stream = std::ofstream(this->_filename, std::ios::binary | std::ios::app); + std::cout.rdbuf(f_stream.rdbuf()); + } + + uint8_t buf[8192] = {0}; + int64_t nread; + while ((nread = this->_reader->read(buf, sizeof(buf))) > 0) { + std::cout.write(reinterpret_cast(buf), nread); + this->_read_vio->ndone += nread; + } + std::cout.flush(); + + if (this->_filename) { + f_stream.close(); + std::cout.rdbuf(default_stream); + } + + break; + } + case VC_EVENT_WRITE_READY: + case VC_EVENT_WRITE_COMPLETE: + default: + break; + } + + return EVENT_CONT; +} diff --git a/src/traffic_quic/quic_client.h b/src/traffic_quic/quic_client.h new file mode 100644 index 00000000000..9af9f0d3593 --- /dev/null +++ b/src/traffic_quic/quic_client.h @@ -0,0 +1,110 @@ +/** @file + * + * A brief file description + * + * @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. + */ + +#pragma once + +#include "P_Net.h" +#include "I_EventSystem.h" +#include "I_NetVConnection.h" +#include "P_QUICNetProcessor.h" + +#include "QUICApplication.h" +#include "Http3App.h" + +// TODO: add quic version option +// TODO: add host header option (also should be used for SNI) +struct QUICClientConfig { + char addr[1024] = "127.0.0.1"; + char output[1024] = {0}; + char port[16] = "4433"; + char path[1018] = "/"; + char debug_tags[1024] = "quic|vv_quic_crypto|http3|qpack"; + int close = false; + int http0_9 = true; + int http3 = false; +}; + +class RespHandler : public Continuation +{ +public: + RespHandler(const QUICClientConfig *config, IOBufferReader *reader); + int main_event_handler(int event, Event *data); + void set_read_vio(VIO *vio); + +private: + const QUICClientConfig *_config = nullptr; + const char *_filename = nullptr; + IOBufferReader *_reader = nullptr; + VIO *_read_vio = nullptr; +}; + +class QUICClient : public Continuation +{ +public: + QUICClient(const QUICClientConfig *config); + ~QUICClient(); + + int start(int, void *); + int state_http_server_open(int event, void *data); + +private: + const QUICClientConfig *_config = nullptr; + struct addrinfo *_remote_addr_info = nullptr; + HttpSessionAccept::Options options; +}; + +class Http09ClientApp : public QUICApplication +{ +public: + Http09ClientApp(QUICNetVConnection *qvc, const QUICClientConfig *config); + + void start(); + int main_event_handler(int event, Event *data); + +private: + void _do_http_request(); + + const QUICClientConfig *_config = nullptr; + const char *_filename = nullptr; +}; + +class Http3ClientApp : public Http3App +{ +public: + using super = Http3App; + + Http3ClientApp(QUICNetVConnection *qvc, IpAllow::ACL &&session_acl, const HttpSessionAccept::Options &options, + const QUICClientConfig *config); + ~Http3ClientApp(); + + void start() override; + +private: + void _do_http_request(); + + RespHandler *_resp_handler = nullptr; + const QUICClientConfig *_config = nullptr; + + MIOBuffer *_req_buf = nullptr; + MIOBuffer *_resp_buf = nullptr; +}; diff --git a/src/traffic_quic/traffic_quic.cc b/src/traffic_quic/traffic_quic.cc new file mode 100644 index 00000000000..a03c29a90a5 --- /dev/null +++ b/src/traffic_quic/traffic_quic.cc @@ -0,0 +1,334 @@ +/** @file + * + * A brief file description + * + * @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 "tscore/ink_string.h" +#include "tscore/ink_args.h" +#include "tscore/I_Layout.h" +#include "tscore/I_Version.h" + +#include "RecordsConfig.h" +#include "URL.h" +#include "MIME.h" +#include "HTTP.h" +#include "HuffmanCodec.h" +#include "Http3Config.h" + +#include "diags.h" +#include "quic_client.h" + +#define THREADS 1 + +constexpr size_t stacksize = 1048576; + +// TODO: Support QUIC version, cipher suite ...etc +// TODO: Support qdrive tests +// https://github.com/ekr/qdrive +// https://github.com/mcmanus/mozquic/tree/master/tests/qdrive +int +main(int argc, const char **argv) +{ + // Before accessing file system initialize Layout engine + Layout::create(); + + // Set up the application version info + AppVersionInfo appVersionInfo; + appVersionInfo.setup(PACKAGE_NAME, "traffic_quic", PACKAGE_VERSION, __DATE__, __TIME__, BUILD_MACHINE, BUILD_PERSON, ""); + + QUICClientConfig config; + + const ArgumentDescription argument_descriptions[] = { + {"addr", 'a', "Address", "S1023", config.addr, nullptr, nullptr}, + {"output", 'o', "Write to FILE instead of stdout", "S1023", config.output, nullptr, nullptr}, + {"port", 'p', "Port", "S15", config.port, nullptr, nullptr}, + {"path", 'P', "Path", "S1017", config.path, nullptr, nullptr}, + {"debug", 'T', "Vertical-bar-separated Debug Tags", "S1023", config.debug_tags, nullptr, nullptr}, + {"close", 'c', "Enable connection close excercise", "F", &config.close, nullptr, nullptr}, + {"http0_9", '-', "Enable HTTP/0.9", "T", &config.http0_9, nullptr, nullptr}, + {"http3", '-', "Enable HTTP/3", "F", &config.http3, nullptr, nullptr}, + + HELP_ARGUMENT_DESCRIPTION(), + VERSION_ARGUMENT_DESCRIPTION(), + RUNROOT_ARGUMENT_DESCRIPTION(), + }; + + // Process command line arguments and dump into variables + process_args(&appVersionInfo, argument_descriptions, countof(argument_descriptions), argv); + + if (config.http3) { + config.http0_9 = false; + } + + init_diags(config.debug_tags, nullptr); + RecProcessInit(RECM_STAND_ALONE); + LibRecordsConfigInit(); + + Debug("quic_client", "Load configs from %s", RecConfigReadConfigDir().c_str()); + + Thread *main_thread = new EThread; + main_thread->set_specific(); + net_config_poll_timeout = 10; + ink_net_init(ts::ModuleVersion(1, 0, ts::ModuleVersion::PRIVATE)); + + SSLInitializeLibrary(); + SSLConfig::startup(); + + netProcessor.init(); + quic_NetProcessor.init(); + + ink_event_system_init(EVENT_SYSTEM_MODULE_PUBLIC_VERSION); + eventProcessor.start(THREADS); + udpNet.start(1, stacksize); + quic_NetProcessor.start(-1, stacksize); + + // Same to init_http_header(); in traffic_server.cc + url_init(); + mime_init(); + http_init(); + hpack_huffman_init(); + + Http3Config::startup(); + + QUICClient client(&config); + eventProcessor.schedule_in(&client, 1, ET_NET); + + this_thread()->execute(); +} + +// FIXME: remove stub +// +// stub +// +void +initialize_thread_for_http_sessions(EThread *, int) +{ + ink_assert(false); +} + +#include "P_UnixNet.h" +#include "P_DNSConnection.h" +int +DNSConnection::close() +{ + ink_assert(false); + return 0; +} + +void +DNSConnection::trigger() +{ + ink_assert(false); +} + +#include "StatPages.h" +void +StatPagesManager::register_http(char const *, Action *(*)(Continuation *, HTTPHdr *)) +{ + // ink_assert(false); +} + +#include "ParentSelection.h" +void +SocksServerConfig::startup() +{ + ink_assert(false); +} + +int SocksServerConfig::m_id = 0; + +void +ParentConfigParams::findParent(HttpRequestData *, ParentResult *, unsigned int, unsigned int) +{ + ink_assert(false); +} + +void +ParentConfigParams::nextParent(HttpRequestData *, ParentResult *, unsigned int, unsigned int) +{ + ink_assert(false); +} + +#include "Log.h" +void +Log::trace_in(sockaddr const *, unsigned short, char const *, ...) +{ + ink_assert(false); +} + +void +Log::trace_out(sockaddr const *, unsigned short, char const *, ...) +{ + ink_assert(false); +} + +#include "InkAPIInternal.h" + +int +APIHook::invoke(int, void *) +{ + ink_assert(false); + return 0; +} + +APIHook * +APIHook::next() const +{ + ink_assert(false); + return nullptr; +} + +APIHook * +APIHooks::get() const +{ + ink_assert(false); + return nullptr; +} + +void +APIHooks::clear() +{ + ink_abort("do not call stub"); +} + +void +APIHooks::append(INKContInternal *) +{ + ink_abort("do not call stub"); +} + +void +APIHooks::prepend(INKContInternal *) +{ + ink_abort("do not call stub"); +} + +void +ConfigUpdateCbTable::invoke(const char * /* name ATS_UNUSED */) +{ + ink_release_assert(false); +} + +#include "ControlMatcher.h" +char * +HttpRequestData::get_string() +{ + ink_assert(false); + return nullptr; +} + +const char * +HttpRequestData::get_host() +{ + ink_assert(false); + return nullptr; +} + +sockaddr const * +HttpRequestData::get_ip() +{ + ink_assert(false); + return nullptr; +} + +sockaddr const * +HttpRequestData::get_client_ip() +{ + ink_assert(false); + return nullptr; +} + +SslAPIHooks *ssl_hooks = nullptr; +StatPagesManager statPagesManager; + +#include "HttpDebugNames.h" +const char * +HttpDebugNames::get_api_hook_name(TSHttpHookID t) +{ + return "dummy"; +} + +#include "HttpSM.h" +HttpSM::HttpSM() : Continuation(nullptr), vc_table(this) {} + +void +HttpSM::cleanup() +{ + ink_abort("do not call stub"); +} + +void +HttpSM::destroy() +{ + ink_abort("do not call stub"); +} + +void +HttpSM::set_next_state() +{ + ink_abort("do not call stub"); +} + +void +HttpSM::handle_api_return() +{ + ink_abort("do not call stub"); +} + +int +HttpSM::kill_this_async_hook(int /* event ATS_UNUSED */, void * /* data ATS_UNUSED */) +{ + return EVENT_DONE; +} + +void +HttpSM::attach_client_session(ProxyTransaction *, IOBufferReader *) +{ + ink_abort("do not call stub"); +} + +void +HttpSM::init() +{ + ink_abort("do not call stub"); +} + +ClassAllocator httpSMAllocator("httpSMAllocator"); +HttpAPIHooks *http_global_hooks; + +HttpVCTable::HttpVCTable(HttpSM *) {} + +PostDataBuffers::~PostDataBuffers() {} + +#include "HttpTunnel.h" +HttpTunnel::HttpTunnel() : Continuation(nullptr) {} +HttpTunnelConsumer::HttpTunnelConsumer() {} +HttpTunnelProducer::HttpTunnelProducer() {} +ChunkedHandler::ChunkedHandler() {} + +#include "HttpCacheSM.h" +HttpCacheSM::HttpCacheSM() {} + +HttpCacheAction::HttpCacheAction() : sm(nullptr) {} +void +HttpCacheAction::cancel(Continuation *c) +{ +} diff --git a/src/traffic_server/Makefile.inc b/src/traffic_server/Makefile.inc index 9d3d621a4a1..41d3839759c 100644 --- a/src/traffic_server/Makefile.inc +++ b/src/traffic_server/Makefile.inc @@ -27,6 +27,7 @@ traffic_server_traffic_server_CPPFLAGS = \ -I$(abs_top_srcdir)/proxy \ -I$(abs_top_srcdir)/proxy/http \ -I$(abs_top_srcdir)/proxy/http2 \ + -I$(abs_top_srcdir)/proxy/http3 \ -I$(abs_top_srcdir)/proxy/logging \ -I$(abs_top_srcdir)/proxy/http/remap \ -I$(abs_top_srcdir)/proxy/hdrs \ @@ -92,3 +93,9 @@ traffic_server_traffic_server_LDADD = \ if IS_DARWIN traffic_server_traffic_server_LDADD += $(LUAJIT_DARWIN_LDFLAGS) endif + +if ENABLE_QUIC +traffic_server_traffic_server_LDADD += \ + $(top_builddir)/proxy/http3/libhttp3.a \ + $(top_builddir)/iocore/net/quic/libquic.a +endif diff --git a/src/traffic_server/traffic_server.cc b/src/traffic_server/traffic_server.cc index e11b1ed4b17..464b52824d9 100644 --- a/src/traffic_server/traffic_server.cc +++ b/src/traffic_server/traffic_server.cc @@ -98,6 +98,11 @@ extern "C" int plock(int); #include "P_SSLSNI.h" #include "P_SSLClientUtils.h" +#if TS_USE_QUIC == 1 +#include "Http3.h" +#include "Http3Config.h" +#endif + #include "tscore/ink_cap.h" #if TS_HAS_PROFILER @@ -1725,6 +1730,9 @@ main(int /* argc ATS_UNUSED */, const char **argv) // We want to initialize Machine as early as possible because it // has other dependencies. Hopefully not in prep_HttpProxyServer(). HttpConfig::startup(); +#if TS_USE_QUIC == 1 + Http3Config::startup(); +#endif /* Set up the machine with the outbound address if that's set, or the inbound address if set, otherwise let it default. @@ -1809,6 +1817,11 @@ main(int /* argc ATS_UNUSED */, const char **argv) netProcessor.init(); prep_HttpProxyServer(); +#if TS_USE_QUIC == 1 + // OK, pushing a spawn scheduling here + quic_NetProcessor.init(); +#endif + // If num_accept_threads == 0, let the ET_NET threads to set the condition variable, // Else we set it here so when checking the condition variable later it returns immediately. if (num_accept_threads == 0) { @@ -1874,6 +1887,10 @@ main(int /* argc ATS_UNUSED */, const char **argv) // Initialize HTTP/2 Http2::init(); +#if TS_USE_QUIC == 1 + // Initialize HTTP/QUIC + Http3::init(); +#endif if (!HttpProxyPort::loadValue(http_accept_port_descriptor)) { HttpProxyPort::loadConfig(); @@ -1893,7 +1910,9 @@ main(int /* argc ATS_UNUSED */, const char **argv) SSLConfigParams::init_ssl_ctx_cb = init_ssl_ctx_callback; SSLConfigParams::load_ssl_file_cb = load_ssl_file_callback; sslNetProcessor.start(-1, stacksize); - +#if TS_USE_QUIC == 1 + quic_NetProcessor.start(-1, stacksize); +#endif pmgmt->registerPluginCallbacks(global_config_cbs); cacheProcessor.afterInitCallbackSet(&CB_After_Cache_Init); diff --git a/src/tscore/ink_inet.cc b/src/tscore/ink_inet.cc index 3f276c4e89f..092342f62db 100644 --- a/src/tscore/ink_inet.cc +++ b/src/tscore/ink_inet.cc @@ -41,6 +41,7 @@ const std::string_view IP_PROTO_TAG_IPV4("ipv4"sv); const std::string_view IP_PROTO_TAG_IPV6("ipv6"sv); const std::string_view IP_PROTO_TAG_UDP("udp"sv); const std::string_view IP_PROTO_TAG_TCP("tcp"sv); +const std::string_view IP_PROTO_TAG_QUIC("quic"sv); const std::string_view IP_PROTO_TAG_TLS_1_0("tls/1.0"sv); const std::string_view IP_PROTO_TAG_TLS_1_1("tls/1.1"sv); const std::string_view IP_PROTO_TAG_TLS_1_2("tls/1.2"sv); @@ -48,7 +49,9 @@ const std::string_view IP_PROTO_TAG_TLS_1_3("tls/1.3"sv); const std::string_view IP_PROTO_TAG_HTTP_0_9("http/0.9"sv); const std::string_view IP_PROTO_TAG_HTTP_1_0("http/1.0"sv); const std::string_view IP_PROTO_TAG_HTTP_1_1("http/1.1"sv); -const std::string_view IP_PROTO_TAG_HTTP_2_0("h2"sv); // HTTP/2 over TLS +const std::string_view IP_PROTO_TAG_HTTP_2_0("h2"sv); // HTTP/2 over TLS +const std::string_view IP_PROTO_TAG_HTTP_QUIC("hq-20"sv); // HTTP/0.9 over QUIC +const std::string_view IP_PROTO_TAG_HTTP_3("h3-20"sv); // HTTP/3 over QUIC const std::string_view UNIX_PROTO_TAG{"unix"sv};