From b1025ceee1940aefa3f0ed8966c8b6f95a3897fb Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Tue, 30 Jul 2019 11:00:10 +0900 Subject: [PATCH] Add QUIC draft-20 support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Squashed commit of the following: commit 4b8151ad413419ad94b6a7c1327eb7b95ee4d13c Merge: 5944d5593 78995bf4e Author: Masakazu Kitajo Date: Tue Jul 23 15:16:59 2019 +0900 Merge branch 'master' into quic-20 * master: (46 commits) Add dest addr information to UDPPacket class Update UDPNet for QUIC Add HKDF wrapper Use un-deprecated records for SSL server verification Removes proxy.config.http.cache.allow_empty_doc Deprecate the mysql_remap plugin. See #5395 Removes the stale_while_revalidate plugin. See #5395 Removes the memcached_remap plugin. See #5395 Removes the hipes plugin. See #5395 Removes the header_normalize plugin. See #5395 Removes the buffer_upload plugin. See #5395 Removes the balancer plugin. See #5395 Fixes spelling in lib/records Fixes memory leak in traffic_crashlog Promotes certifier to stable, see #5394 Promotes remap_purge to stable, see #5394 Promotes prefetch to stable, see #5394 Promotes multiplexer to stable, see #5394 Promotes cache_range_requests to stable, see #5394 fix If-Match and If-Unmodified-Since priority problem,about rfc https://tools.ietf.org/html/rfc7232#section-3.3 ... Conflicts: build/crypto.m4 configure.ac include/tscore/HKDF.h iocore/net/P_UDPNet.h iocore/net/P_UnixNet.h iocore/net/UnixUDPNet.cc src/tscore/HKDF_boringssl.cc src/tscore/HKDF_openssl.cc commit 5944d559380fb1cb17e4fbe99d06570a8b7ce4f0 Merge: 5f39e7f27 8510a1c24 Author: Masakazu Kitajo Date: Wed Jul 10 11:48:26 2019 +0900 Merge branch 'master' into quic-20 * master: Log H2 errors with the codes Separate out common header compression logic to hdrs directory TSIOBufferReaderCopy: copy data from a reader to a fixed buffer. Remove unused header file ink_inout.h Remove unused LibBulkIO Reverse internal order of HPACK Dynamic Table Entries More Autest cleanup commit 5f39e7f270ce211e274391357ced328560500ad4 Author: Masakazu Kitajo Date: Thu Jul 4 14:08:35 2019 +0900 Fix build issues on macOS (cherry picked from commit ec333e7e62ce61d87774b3801dc37cba6fff0354) commit 319a46fabc1b0dae3651548b11d3ebd752acce3a Author: Masakazu Kitajo Date: Thu Jul 4 12:52:45 2019 +0900 Fix a build issue on CentOS (cherry picked from commit 96293892f9306948c45f4adbd6e99e47d72cc5e3) commit f428cc465941ba242d1d01e146e7196fb83cb5e5 Author: Masakazu Kitajo Date: Thu Jul 4 12:48:28 2019 +0900 Remove dependency for libinknet and etc (cherry picked from commit 91f1726b39403890158cdf1a308ef190e0f7b5c8) commit b4f20d9d7ba21024b99aebf23567a1889e1ed0ff Author: Masakazu Kitajo Date: Thu Jul 4 11:39:01 2019 +0900 Fix a build issue on CentOS (cherry picked from commit d88d887d8d33656f044e0076b56c4678a62c85b0) commit cf7b61e8d320e8fee909c4dec35359495d525172 Author: Masakazu Kitajo Date: Wed Jul 3 18:00:22 2019 +0900 Add dependency for libUglyLogStubs.a for now to pass the CI job (cherry picked from commit 065983974c9e8fd0d71240f7e3b420860a7f8d13) commit 08b898b5945149ee26f37ebec7c1b85e6c43ec22 Author: Masakazu Kitajo Date: Wed Jul 3 17:28:29 2019 +0900 Remove many dependency for libinknet from QUIC module (cherry picked from commit 39c2c16d908887b079e5ad8dcad19a96ca5439c6) commit e5d74233a5486448ac7d15bd8178d46eb7997c9f Merge: 749f9090c a4d913a35 Author: Masakazu Kitajo Date: Wed Jul 3 11:01:11 2019 +0900 Merge branch 'master' into quic-20 * master: Combohandler: Set response as private if one of the origin responses is private Added end pointer to ink_atoi64 and used when parsing cache size Add ats_unique_buf Convert HdrHeap regression test into unit test using Catch This fixes issue #5642. When the number of connections to a parent proxy exceeds proxy.config.http.per_server.connection.max a state machine loop will occur when using parent selection to select a parent for redundancy and/or load balancing. Fix options processing for ja3_fingerprint plugin Conflicts: proxy/hdrs/Makefile.am commit 749f9090c8ffae7d5a20ec0c4551c83e348faf30 Author: Masakazu Kitajo Date: Thu Jun 27 11:31:57 2019 +0900 Remove libtsconfig from Makefiles (cherry picked from commit d034bd6bfd6613adb9dddd86affa9c71ebee5689) commit e6a546700dd9317efd49744723e82e43f668b6e6 Author: Masakazu Kitajo Date: Tue Jun 25 16:37:40 2019 +0900 Reduce trivial diff from master commit 05bd80b8fecf29cee8a2086e653d397f3740362a Merge: 867b0f627 9d859ea47 Author: Masakazu Kitajo Date: Tue Jun 25 16:25:10 2019 +0900 Merge branch 'master' into quic-20 * master: Update MT_Hashtable interface to use smart pointer Allows for resizing librecords via command line option Program to test if multiple URLs can be cached and generates a report on the cache headers. Doc: Improve TSHttpTxnUrlEffectiveStringGet docs, cleanup other doc build errors. emergency shutdown update example directory structure and add examples for lua plugin Fix build error for pre openssl-1.1.1 Use SSL_version() directly instead of SSL_get_version() which returns a string (Thanks @maskit for the pointer). Add metrics to track SSLv3 and TLS versions Auto port selection for more autests commit 867b0f627bc555c0208e1208380945650d328d95 Merge: 9d561a5a3 75e7fc340 Author: Masakazu Kitajo Date: Fri Jun 21 10:51:50 2019 +0900 Merge branch 'master' into quic-20 * master: Reenable redirect_actions Au test as it is working now. Fix the number of net_connections_currently_open_stat error increase Fix and reenable url_sig Au test. TS autest extension now auto selects both ssl and nonssl port cookie_remap plugin Au test case changes for compatability with PR 4964. Replaces Emergency() with Error() when parsing these records.config values use type info to assign an affinity thread HTTP/2: ignore unknown settings HTTP/2: cancel reading buffer when ATS received GOAWAY HTTP/2: increment write_vio.ndone by consumed size Cleanup debug log in mime_hdr_describe Fix default logging.yaml with new format. Update lua.en.rst commit 9d561a5a3945354e55be6edac6eeecca416b1842 Author: Masakazu Kitajo Date: Mon Jun 17 15:40:16 2019 +0900 Catch up changes on master commit 030cadb7a34b9f988478eefa261c2a49cdff1f8a Merge: 2805b749e 9d90a5114 Author: Masakazu Kitajo Date: Mon Jun 17 10:49:09 2019 +0900 Merge branch 'master' into quic-20 * master: (54 commits) TextView: Fix bug in rtrim_if when entire view is cleared. Step 23: Renames Rollback object -> FileManager consistently Step 22: Renames Rollback.cc/.h -> ConfigManager.cc/.h Step 21: Removes the UNVERSIONED option flag completely Step 20: Removes incVersion/inc_version from Filemanager etc. Step 19: Removes the now unused Rollback member variables Step 18: Removes setLastModifiedTime, and simplifies code Step 17: Removes versionInfo, as well as currentVersion and highestSeen Step 16: Removes createPathStr() and cleans up statFile() Step 15: Removes getBaseName() and various unused members Step 14: Removes numberOfVersions() and getCurrentVersion() Step 13: Removes openFile() and closeFile() Step 12: Removes extractVersionInfo() Step 11: Removes statVersion() Step 10: Removes versionTimeStamp() and versionTimeStamp_ml() Step 9: Removes getVersion() and getVersion_ml() Step 8: Removes internalUpdate(), functionality was moved in 6 Step 7: Removes updateVersion() and updateVersion_ml() Step 6: Simplifies checkForUserUpdate(), moving critical code here Step 5: Remove removeVersion() and removeVersion_ml() ... Conflicts: iocore/net/UnixNet.cc iocore/net/UnixUDPNet.cc iocore/net/libinknet_stub.cc commit 2805b749e1444e54373491584b2955a47e6cca29 Author: Masaori Koshiba Date: Fri May 31 15:44:53 2019 +0900 Skip running test_QPACK when qif dir is not found commit fe277104391e7d64938e50b2c5fac6a81cbbe13c Author: Masaori Koshiba Date: Fri May 31 15:17:40 2019 +0900 Rename Http3ClientSession to Http3Session commit 7d8d7df3b286ea3dcbfec86d0d6baa83729dd00c Author: Masaori Koshiba Date: Fri May 31 15:03:23 2019 +0900 Rename Http3ClientTransaction to Http3Transaction commit d23fe12e3d327f1761f065bf97e643f4e39cb9c8 Author: Masaori Koshiba Date: Fri May 31 09:25:07 2019 +0900 Follow proxy ssn/txn cleanups on master commit eabfc106b129d0de8979363930e7ef62f11a39d4 Merge: 5b0e099a0 3b865f63d Author: Masaori Koshiba Date: Fri May 31 08:48:21 2019 +0900 Merge branch 'asf/master' into quic-latest * asf/master: Ignore Pipfile.lock file Rename class: ProxyTransaction Rename class: ProxySession Rename class: Http1ServerSession Renamed class: Http1Transaction renamed ProxyTransaction::parent to proxy_ssn removed ProxyTransaction::get_transaction_count() ProxySession Cleanup Rename files: ProxyTransaction & ProxySession Rename files: Http1Transaction & Http1ServerSession Added new API TSSslClientContextsNamesGet and TSSslClientContextFindByName. Added an example plugin that records all loaded context information into a log file. Added an autest for the example plugin. Added documentation for the APIs. Fix ja3 hooks for openssl 1.0.1 Fix order a little bit, based on F30 availability commit 5b0e099a0922728fefcad9ee339d32c40e71b427 Merge: 0afaec693 58e7e8675 Author: Masakazu Kitajo Date: Wed May 29 08:14:51 2019 +0900 Merge branch 'master' into quic-latest * master: (31 commits) Ran clang-tidy with google-readability-casting Removes the abort() from header_rewrite, and try to deal with errors Set HTTP version on server side header conversion Fix ParentSelection regression/unit tests. Add an ignore_self_detect flag to parent.config so that the local cache host may be a member of a peering cache group. Clean up: Remove redundant proxy.config.http.parent_proxy_routing_enable variable Removes empty config load warning for YAML-based configuration files Doc: Fix typo in TSMimeHdrFieldCreate Minor wording changes in ssl_server_name.yaml docs Added loop detection via code and squid logging code Doc: Add documentation for a few C API functions, clean up doc build errors. Add cstdio in TextBuffer for vsnprintf Added pipenv config script for Autest. Doc: Remove origin KA note from removed setting Check DH_get_2048_256() error More fixes and cleanup of CI scripts Updates Dockerfile, and adds a comment to autest bootstrap Removed headers that don't exist in the dir to fix clang-tidy Add options to sort to reduce os differences in all_headers test Fix test to not break on custom layouts ... Conflicts: doc/admin-guide/files/records.config.en.rst iocore/net/UnixNet.cc iocore/net/UnixUDPNet.cc commit 0afaec6934c4b2039a32b2817acded4bc1a4875d Author: Masakazu Kitajo Date: Tue May 21 17:55:05 2019 +0100 Allow number of settings per H3 session to be configurable commit 4dda42c62d1ab1390a4138f96e9713a84602fa2b Author: Masaori Koshiba Date: Tue May 21 16:29:32 2019 +0100 Fix test_quic command --q-decoded-dir option commit b3701a4691524eb3e5a43f7bf57d837f789ae786 Author: Masakazu Kitajo Date: Tue May 21 15:13:42 2019 +0100 Print source port number commit 1e8a3a65dfaf1cc0ca15b6bfb3203e1478ed0cdb Author: Masakazu Kitajo Date: Tue May 21 15:01:39 2019 +0100 Use preferred address once a client initiated a migration to the address commit 396a0fced5bcb1c6d97951c91805f596032143df Author: Masaori Koshiba Date: Tue May 21 14:35:02 2019 +0100 More fix calculation of end of buf Same issue to d99371e341b797d7166aa7e392847802d9e48c59. commit 2eb96736f939e4538464ebfb1eae36627993cc45 Author: Masaori Koshiba Date: Mon May 20 14:25:03 2019 +0100 Fix local/remote TP for checking stream id commit 8aac785b6aaa08d9dbaa6dad0210761bf5bba52e Author: Masakazu Kitajo Date: Tue May 21 12:43:21 2019 +0100 Fix a nullptr dereference commit d99371e341b797d7166aa7e392847802d9e48c59 Author: Masaori Koshiba Date: Tue May 21 12:30:08 2019 +0100 Fix calculation of end of buf in QPACK::_decode_literal_header_field_without_name_ref() commit 14a4f67d838f421e88f0d7be79b26795573aceb8 Author: Masakazu Kitajo Date: Tue May 21 10:30:44 2019 +0100 Share a QUIC connection table among QUIC ports commit 4a7bc099c4f0ca75d68bfa68112f2a3651ec5f34 Author: Masakazu Kitajo Date: Tue May 21 09:35:51 2019 +0100 Fix a test for Http3Frame commit 142dd26851659214316d50bb76b29879affe609e Author: Masaori Koshiba Date: Mon May 20 16:36:14 2019 +0100 Fix handling 0 length HTTP/3 frame commit a8c14edbc062409079f41cacb4887a8af812b31a Author: Masakazu Kitajo Date: Mon May 20 15:53:53 2019 +0100 idle_timeout TP is milisecond since -19 commit 955d380371c3b9fa6aeb7cac10331d2b84ef19e1 Author: Masakazu Kitajo Date: Mon May 20 14:21:38 2019 +0100 Fix a bug in ConnectionId::_hashcode It should use the upper 64 bits but it wasn't because of signed and unsigned commit 39ddacee594fb8f2b19d5e63369e916bb00a69a8 Author: Masakazu Kitajo Date: Mon May 20 10:46:49 2019 +0100 Fix asan issues commit df42ccf6906b0c3d893f7a932d04a2d96a3cd7a8 Author: Masakazu Kitajo Date: Sun May 19 11:06:42 2019 +0900 Remove an unnecessary header include commit d0a0dc0247929eda6df9de7b2d751ccb9f1ca651 Author: Masakazu Kitajo Date: Thu May 16 22:33:42 2019 +0900 Make QUIC code compilable with BoringSSL commit f43cb6a04cf2757e5dee75dfec45f3c82c1bf452 Merge: 37f963fa3 6c51b258f Author: Masakazu Kitajo Date: Fri May 17 10:35:15 2019 +0900 Merge branch 'master' into quic-latest * master: Check for OpenSSSL v1.1.1, for now, for this test Cleanup of autest prog checks, and indentation Adds a missing dependency, bump autest version Doc: Clean up JA3 plugin docs. TextView: clean up on strcasecmp, strcmp, and memcmp. Make code compilable with BoringSSL Renumbers all the alarm codes / defines Removes the email feature of the Alarms subsystem Removes alarms that no longer are supported commit 37f963fa3b98e9cf4003bdc4f2f831c1e9e7c1cf Author: Masakazu Kitajo Date: Thu May 16 15:51:06 2019 +0900 cppcheck: fix peformance issues commit f2f49b3333844a3218454e7b461930b11181e63b Author: Masakazu Kitajo Date: Thu May 16 09:37:50 2019 +0900 Use QUICStreamFrame::to_io_buffer_block to avoid memcpy commit c517e0e42969ccadaa9b59ff0f8bc7441d0df760 Author: scw00 Date: Thu May 16 10:18:51 2019 +0800 Http3: close connection when buffer release commit 4cf8850ecd8eb4883f14bd170f34c921763ebb31 Merge: 0c80e6b11 554c828ae Author: Masakazu Kitajo Date: Thu May 16 09:26:29 2019 +0900 Merge branch 'master' into quic-latest * master: clang analzyer - fix false positive in Vol::handle_recover_from_data. AcidPtr: Tweaks tests to be a bit more robust. This is already documented in the proper traffic_ctl page grab lock before invoke Updates the Docker image with latest dependencies Fixed clang-analyzer issue with calling schedule_imm with the continuation possibly being a nullptr Fixes some cache-tests build issues Corret the clear range when cache restart Added Docker support for Fedora 29 and Fedora 30 gcc9: sprintf and strncat fixes in plugins clang-analyzer: Fix uninitialized variable in make_to_lower_case. clang-analyzer: fix bogus use after free with Ptr in LogFieldInt constructor. Document proxy.config.ssl.server.honor_cipher_order commit 0c80e6b11414152636d2253d0ab2474e7a4750b1 Author: Masaori Koshiba Date: Wed May 15 14:58:01 2019 +0900 Performance: Optimize QUICFrameGenerator::_is_level_matched() Prior this change, std::vector was copied everytime. QUICAckFrameManager and QUICHandshake don't need this check, because these should work with all encryptio level. commit 4aa5ba259e5d70fa0d1e420513435df5bbc83039 Author: Masaori Koshiba Date: Wed May 15 09:52:54 2019 +0900 Performance: Initialize QUICConnectionId with ZERO to avoid calling randomize() commit 0cb563424ae49d9e11c3087d900a386d55387418 Merge: 85fbeccff 5f246c33d Author: Masakazu Kitajo Date: Tue May 14 17:38:31 2019 +0900 Merge branch 'master' into quic-latest * master: gcc9: set default value for printing debug message Fix FREELIST macros for AArch64 commit 85fbeccff0c1ae7c6ab6a002fb24012e549c435e Author: Masakazu Kitajo Date: Tue May 14 17:37:50 2019 +0900 clang-tidy commit 8e4517ecb7538a20df1a779d6f276bc6f42c6531 Author: Masakazu Kitajo Date: Tue May 14 17:19:07 2019 +0900 Prevent event-processing-after-free on a test commit d39392d077162983c0b4aa6b23e306cc6deb6aea Author: Masakazu Kitajo Date: Tue May 14 14:51:34 2019 +0900 Satisfy gcc9 commit 9fc03ee395ebe56b8b2c2d555ef52f693b8e3f86 Author: Masakazu Kitajo Date: Tue May 14 14:09:07 2019 +0900 Fix tests for QUICStreamManager add_total_offset_sent was used in the tests but it only checks the function increment offset, and doesn't check check Stream Manager's expected behavior. commit 08042e622c34c3d2658ace4f16a540a6465c3d08 Author: Masaori Koshiba Date: Tue May 14 11:58:11 2019 +0900 Avoid read_avail() calls for performance commit 35cacaa26046bfb8e0e6fa0492762b43e15af645 Author: Masakazu Kitajo Date: Tue May 14 11:54:58 2019 +0900 Fix tests for h3 commit 919183e6f235e0904a30d9828dbb889053683bae Author: scw00 Date: Tue May 14 02:17:58 2019 +0000 QUIC: Fix test for pervious changes commit 9e593ff209c324917ed9c2bb0f33d5df9b44ed36 Author: Masakazu Kitajo Date: Wed May 1 17:19:45 2019 +0900 Update code for NUM_PLACEHOLDERS commit 2d883e02b3a0e402581070030b739b468977a36c Author: Masakazu Kitajo Date: Wed May 1 17:09:49 2019 +0900 Update QUIC draft nubmers to 20 commit ee689f178522a488741a772c3729fced905c916a Author: Masaori Koshiba Date: Mon May 13 15:19:18 2019 +0900 Fix autological-pointer-compare warning > comparison of array 'this->_loss_time' equal to a null pointer is always false [-Werror,-Wtautological-pointer-compare] commit b7bf6582cb0cdb502b0b256228619e2d4435dcc2 Author: scw00 Date: Fri May 10 07:43:11 2019 +0000 QUIC: Make congestion period in QUICRTTProvider commit 6562700146c8736b67fe5e20b490f1d08f1653a5 Author: scw00 Date: Fri May 10 05:44:54 2019 +0000 QUIC: Fix test due to pre commits commit 41ae20a5d00551385a81c294346ac80a1604ca0e Author: scw00 Date: Thu May 9 06:10:56 2019 +0000 QUIC: remove pn_space and remove the dependency with ld commit f51845b7eb1448cb8077771fadc026cbe1092e4c Author: scw00 Date: Mon Apr 29 01:33:19 2019 +0000 QUIC: Fixed test changed by loss_detector commit 97c9898c90cb1153fe015fc04cfc574f4c9b6281 Author: scw00 Date: Mon Apr 29 01:04:44 2019 +0000 QUIC: Update CC to draft 19 commit 2432f39f1535c420b05bc4f91152dc090733104b Author: scw00 Date: Sun Apr 28 02:42:48 2019 +0000 Fixed coredump in Loss Detector commit 509eef06bcd204b62cd35ab43cd4b767ea483b3e Author: scw00 Date: Thu Apr 25 09:27:24 2019 +0000 QUIC: recovery-19 combine loss detector into one commit a9e0b8bc18263f56fa41dd0e1a0fea7ef4b83e32 Author: Masaori Koshiba Date: Mon May 13 12:31:35 2019 +0900 Fix checking progress of write vio commit e59350c04de87623faf67082dbddd219796ab687 Merge: 56484a481 88b5a13ec Author: Masakazu Kitajo Date: Mon May 13 12:07:14 2019 +0900 Merge branch 'master' into quic-latest * master: Removes proxy.config.http.server_tcp_init_cwnd gcc9: Added default assignment operator clang-analyzer: Fixed err value being uninitialized Convert ssl_preaccept plugin to use command-line arguments Add be32toh and htobe32 for macOS clang-analyzer: Fix API test logic and fixed clang-analyzer issue calling strncmp with null argument commit 56484a48117fff092ceed19522aa21e4cd3f1d19 Author: Masakazu Kitajo Date: Fri May 10 17:25:14 2019 +0900 Remove an unused variable commit 5963187459f99879914dfa05077cb57fd9eab1d5 Merge: 478b7f8c8 fa10c20b8 Author: Masakazu Kitajo Date: Fri May 10 15:31:13 2019 +0900 Merge branch 'master' into quic-latest * master: (27 commits) Fixes spelling in doc Fixes typos in various documentation files gcc9: fixed issue with always printing a null char* Clang Analyzer: Fix IpMap.cc false positives. #2 Doc: Clean up some errors and formatting in traffic_ctl documentation. MemSpan: Update to templated style, fix gcc9 compile error. Rewrite SocksProxy based on states Fixes use-after-free in PVCTestDriver::start_tests Fixed syntax issue with clang Add API and fix logic for TS_SSL_VERIFY_*_HOOK. Improve test resilience by waiting for TS ports to be ready. Change HostStatus to use only one stat per host. The host stat is now a string value containing all time and reason data so that it may be restored from persistent store when ATS is restarted. Fixed nullptr check in cookie remap Address intermittent failures of all_headers Au test (see Issue # 5437). Removes unused TSConfig usage in verify_cert example Additional places to propagate the per-client-ip debug tags. Removes unused TSConfig usage in ssl_sni example Removes unused TSConfig usage in ssl_sni_whitelist example Add wait_for_cache to make all_headers test more resilient gcc9 - Fix I_Store.h ... commit 478b7f8c8005756150db26afa73299476ff3ed5a Author: Masakazu Kitajo Date: Fri May 10 15:20:13 2019 +0900 Fix some of compile warnings commit 8251867b18de6a0c472f4b25e5f999e7d3edd95f Author: Masakazu Kitajo Date: Wed May 1 22:56:04 2019 +0900 Add docs for QUIC configuration commit 709a9973f4f6e601ff7a2e25b9cd4c206c92a710 Author: Masaori Koshiba Date: Thu May 9 10:35:53 2019 +0900 Revert wrong conflicts fix in HPACK.cc and HTTP2.cc Merging master branch (8deca1c320f89f4a9109e9f87d3d99b5e7e17157) had some conflicts and they were fixed wrongly. - HTTP2.cc status_len and status were moved to narrow down their scope. - HPACK.cc encode_integer() and encode_string() were moved to XPACK.cc. commit f879f87155af0a2f1f81568cfe80f4757787252a Author: Masaori Koshiba Date: Tue May 7 14:05:36 2019 +0900 Follow changes of master branch Signatures changes of ssl_stapling_init_cert() Delete copy constructor of IpAllow::ACL commit 8deca1c320f89f4a9109e9f87d3d99b5e7e17157 Merge: 3cf90685a f32fc32c8 Author: Masaori Koshiba Date: Tue May 7 12:28:20 2019 +0900 Merge branch 'asf/master' into quic-latest * asf/master: First pass of documentation for URL rewrite. gcc9 - fix CryptoHash.h gcc9 - fix class Connection. Fix ContFlags for gcc 9. Doc: Update TSConfig... functions. Load the Socks configuration after the host state persistence data is loaded. Rewrite the assert statement to avoid Socks Proxy triggering the assertions. Move setsockopt from UnixNetProcessor to Server::setup_fd_for_listen Implement prefetched OCSP stapling responses Fixed clang-analyzer issues in cookie_remap TS C++ API: add member function TSSslConnection InterceptPlugin::getSslConnection() Update README.md Removes code related to removed configuration ssl_session_resuse: operator for redis endpoint compare functor must be const for STL compatability. Tries to make builds on older clang happy Update jtest.cc Added user defined conversion operator in ConstBuffer for string_view. Clang Analyzer: Fix IpMap.cc false positives. MIMEScanner: only clear m_line buffer if at MIME_PARSE_BEFORE state Off by 1 error in memcpy of redirect url correctly handle return value 0 from recv() cppcheck: Fix various issues found in iocore/eventsystem Fixes spelling in mgmt Fixes spelling in code in plugins Fixes spelling in plugins Fixes spelling in miscellaneous files Remove commented out includes cppcheck: Fix issues found in I_IOBuffer.h Fixes spelling in iocore fixes spelling in include Fixes spelling in src Fixes logging after collation removal De-tabifies REVIEWERS Doc: add prefetch to plugin index page Doc: Add links to Slicer plugin Cache:ttl-in-cache should always override never-cache cppcheck: Fix various issues in proxy/http2/ Purges log collation feature Add slice plugin config options to either pace or disable block stitch error logs. cppcheck: fix comparison issues found in plugins cppcheck: Changed from C casting to C++ casting Move the delete to please clang-analyzer Don't read frames after sending GOAWAY with an error code Adds assert, albeit not needed, makes CA happy cppcheck: Removed problematic move operators for FixedBufferWriter. cppcheck: Minimize a variable scope [iocore/net/P_UDPPacket.h:192]: (style) The scope of the variable 'body' can be reduced. cppcheck: Declare a member variable with an initial value Password can not be nullptr here, so don't check cppcheck: fixes issues found for plugins/authproxy cppcheck: fixes issues in cppcheck Revert RS-374 to avoid deadlocks Don't assign if this and other are the same object cppcheck: Fixes various issues under proxy/http/remap cppcheck: Fixes issue found in DiagsConfig.cc cppcheck: Reduce the scope of the variable 'out_buf' cppcheck: change to C++ style pointer casting cppcheck: Fix various issues of Http2DependencyTree cppcheck: Fix various issues of Http2ConnectionState cppcheck: fixes issues found in example/protocol cppcheck: fixes issues fround in example/remap cppcheck: fixes issue found in proxy/IPAllow.cc cppcheck: Remove an unused private function cppcheck: minimize variable scopes Fixes clang-analyzer error dereferencing nullptr in parent correct config name for proxy.config.dns.connection_mode Removes priorities for AIOs, thanks oknet cppcheck fix for iocore/dns cppcheck: fixes issues found in example/thread_pool cppcheck: fixes issues found for plugins/background_fetch cppcheck: fixes issues found for tests in proxy/http Removes non-existent include dir reference cppcheck: Reduce the scope of the variable 'netvc' cppcheck: Reduces variable scope for files in mgmt/... cppcheck: Minimize variable scopes and use different names to not shadow others cppcheck: Fixes issues found in async_http_fetch_streaming cppcheck: (portability) %zd in format string (no. 2) requires 'ssize_t' but the argument type is 'size_t {aka unsigned long}' cppcheck: (style) The scope of the variable can be reduced. cppcheck: syntax error and memory leak fix cppcheck: Function parameters should be passed by reference cppcheck C-style pointer casting cppcheck: fixes issues found in proxy/logging cppcheck: Change to C++ style pointer casting in Thread.cc cppcheck: fixes issues found for plugins/compress fix http2.max_header_list_size default fix ssl.server.ticket_key.filename default cppcheck: fixes issues in ink_uuid.h (BROKEN) Change to C++ style pointer casting cppcheck: Fixed various issues with SSL files cppcheck: Use initialization list instead of assigning in constructor body cppcheck: fix issue found in BufferWriterFormat.cc Throttling results in tight loop if there are no new connections Fix a build error in xdebug on macos fix proxy.config.cache.ram_cache.algorithm desc Add more information to diags.log error message for block stitching errors. traffic_via.pl: Fixed bugs, added tests, and make the output match more like traffic_via. Update README.md -H "xdebug: probe" injects trace of headers into response body A recipient MUST ignore If-Modified-Since if the request contains an If-None-Match header field, about rfc https://tools.ietf.org/html/rfc7232#section-3.3 Fixes the Brotli build issues fix proxy.config.hostdb.timeout default Ran CPP check on a few files while prodding around Adds basic version feature for traffic_layout info Cleanup: Use internal linkage for functions which are only needed in SSLUtils.cc Fix false collapsing of reverse DNS requests. Fix HostDBReverseTest unitilization sa_family, remove LRAND48 and SRAND48 for C++11 random. docs for schedule api Strip token from upstream if conifigured and dynamically allocate string buffers Ignore unsupported HTTP/2 settings parameters Update plugin API type conversions to support enums automatically. Fix IntrusiveHashMap active bucket list corruption issue during expansion. Corrected usage of OpenSSL API for ec and ecpf list retrieval. Now the signature is valid without additional length bytes accounted for signature. Fix reason tag of traffic_ctl host Conflicts: proxy/http2/HPACK.cc proxy/http2/HTTP2.cc commit 3cf90685a87494b47a6d4d56383f8735e27f6129 Author: Masakazu Kitajo Date: Wed May 1 18:37:47 2019 +0900 Remove unused defines commit c9e367a5f3cef855f64f047d61447d04300fe13d Author: Masaori Koshiba Date: Wed May 1 16:53:56 2019 +0900 Add workaround fix for mixed response header and body commit fd33307a3b4d6bff9577641ae9b20fd8f4ef6b4a Author: Masaori Koshiba Date: Wed May 1 16:50:17 2019 +0900 Print size of HTTP/3 frame on debug log commit 42b5fdfe2fc786e4a14f13319f690b07523923bb Author: Masaori Koshiba Date: Wed May 1 16:49:21 2019 +0900 Fix BAD_ACCESS in Http3FrameFactory::create_data_frame() commit d4d3fc02ef5a95f16ecefd2d4107db8cacdb43bb Author: Masakazu Kitajo Date: Thu Apr 25 11:55:38 2019 +0800 Check QUIC availability with SSL_MODE_QUIC_HACK commit 5a77a807942e8ef5eb4f0b844bcc881e0367375d Author: Masaori Koshiba Date: Wed May 1 14:31:44 2019 +0900 Fix stack-buffer-overflow on storing data frame commit d067d3eb321fb0660384a7e9435539583a25c673 Author: Masakazu Kitajo Date: Tue Apr 30 22:43:08 2019 +0900 Add missing files for BoringSSL commit 2a2107c11af28726bc568512c185b26b2b782ef6 Author: Masakazu Kitajo Date: Tue Apr 30 21:59:52 2019 +0900 Update clang-tidy target commit 897f6922c304e15c9eea31d7b52c0b986b0f5fd7 Author: Masakazu Kitajo Date: Wed Apr 17 16:03:24 2019 +0900 Print destination addresses and ports of received packets commit a41605abef9b88cdd06aded881921434bd258c80 Author: Masaori Koshiba Date: Tue Apr 16 12:44:32 2019 +0900 Add stateless reset reasons on debug log commit 22b77e9f04d2f6033661eb98a6739ca249ba45c4 Author: Masakazu Kitajo Date: Mon Apr 15 16:25:39 2019 +0900 Fix a wrong path for libhttp2.a commit c0d60aa4b387410152e15f07b37453c49c96270d Author: Masakazu Kitajo Date: Mon Apr 15 16:16:25 2019 +0900 Add stubs for http3 unit tests commit 4738bef618c38015b25ac1f74a805fb6f989ed52 Author: Masakazu Kitajo Date: Mon Apr 15 11:49:04 2019 +0900 Fix a strange link issue in unit tests on release build commit 4180a956490350c9fb4ab2a9f07224b989cc1a59 Author: scw00 Date: Wed Apr 10 06:50:29 2019 +0000 QUIC: Uses unidirectional stream to send HTTP3 SETTINGS frames commit 033ec0e42cb1ba5cef670c0f2ebe384efa3180c3 Author: Masaori Koshiba Date: Mon Apr 15 09:29:17 2019 +0900 Add @HWLOC_LIBS@ to LDADD for HTTP/3 check programs commit d57c17c83cb5e1de59d61d73221c6a223cae385b Author: Masaori Koshiba Date: Thu Feb 14 16:18:09 2019 +0900 Add keylog support on QUIC client commit 2380776b74fc118487340c1cb5fdb69520cc78cf Merge: 31f60ade6 076e1f3d8 Author: Masakazu Kitajo Date: Fri Apr 12 16:27:52 2019 +0900 Merge branch 'master' into quic-latest * master: (64 commits) Fixed cache RWW test crash Adds cache alterante update tests Fixed pthread mutex init issue with cache test Remove unused variable in cache test Adds a HostStatus::loadStats() function used to load persisted HostStatus stats after a restart of trafficserver. DRAFT: This PR fixes #5248 - parent host status is not persistent accross restarts. Changed how current age is determined to age out documents. Guaranteed freshness was being used innapropriately when the docs age was already beyond that value Adds a missing header field for the tcpinfo log Fixed cache test, using updated shutdown url_sig: fixed unit-test for remapped url. Setting the correct directory for test_Cache Some tidying up of the global namespace. Fix mysql-remap compilation error url_sig debug fix for when url is missing the signature query string Adds Cache test suits Doc: Add IPv6 CIDR Mask example Normalize on negative value for milsetone metrics Fix compilation error in wccp Add nullptr check on ConfigProcessor::release() Fix the hiredis autoconf for the default case ... Conflicts: .gitignore iocore/net/P_UDPNet.h iocore/net/P_UnixNetVConnection.h commit 31f60ade6cb8293e1b943b0b4afdddc7422890fb Author: Masakazu Kitajo Date: Fri Apr 12 16:05:04 2019 +0900 Update tests for H3 commit 6395120efa06258b8009d319a66f18d307e007ce Author: Masakazu Kitajo Date: Fri Apr 12 15:33:39 2019 +0900 Update H3 SETTINGS frame format to draft-19 commit 3459e0822a67fdeabda76aacfc289fa8bde0e4ed Author: Masakazu Kitajo Date: Fri Apr 12 15:07:41 2019 +0900 Update H3 Frame header format to draft-19 commit c3fcccb6bfb016f06aaddd731734d9e37f2437ea Author: Masakazu Kitajo Date: Tue Apr 9 17:35:03 2019 +0900 Remove version related fields from TP and version validation logic commit 7af17709e049f782f102633495f2b2255c9f7257 Author: Masakazu Kitajo Date: Tue Apr 9 14:30:04 2019 +0900 Update TransportParameter format to draft-19 commit f607cac2ac67bff68af9d7babfdf9b965ffe2119 Author: Masakazu Kitajo Date: Mon Apr 8 16:55:31 2019 +0900 Update QUIC draft numbers to 19 commit 3c27f603b86a1d8ddf851a767b5b30762b86ec12 Author: Masaori Koshiba Date: Wed Apr 10 13:59:18 2019 +0900 Ran clang-format commit 2fe722da117beb99a1f22e918a3ef1c3060f1450 Author: Masaori Koshiba Date: Wed Apr 10 13:52:24 2019 +0900 Fix unit tests using QUICTLS commit 039e4a0e850f0bbcf17a98505455486cf10c4810 Author: scw00 Date: Tue Apr 9 09:11:43 2019 +0000 Fixed compilation with ubuntu 18.04 commit 41c06df698844c80d2dd146b8ce5fb7cc5853c91 Author: Masaori Koshiba Date: Fri Apr 5 10:12:57 2019 +0900 Add HTTP/3 support to traffic_quic cmd commit 2d2b4a32cbbc53c209cb29b105bd6dd02bf6feb8 Author: Masaori Koshiba Date: Mon Apr 8 14:58:47 2019 +0900 Fix test_QUICPacketFactory & test_QUICVersionNegotiator commit 83bc8289e3de15bc3bb036d2a103ddc21b4f7d6a Author: Masaori Koshiba Date: Mon Apr 8 14:36:22 2019 +0900 Fix test_QUICLossDetector commit 70397c521d33ffcaa52b70f11ee56907d74b22d2 Author: Masaori Koshiba Date: Mon Apr 8 11:58:17 2019 +0900 Cleanup frame debug msg commit 22ef4573bfca24cf17459e5364d901b1cc91ed51 Author: Masaori Koshiba Date: Thu Apr 4 14:04:08 2019 +0900 Fix QUICDebugNames::key_phase() commit 42349cfd459dc57b236180c153abb2351c0e1a59 Author: Masaori Koshiba Date: Thu Apr 4 10:53:30 2019 +0900 Send NEW_TOKEN frame from server side only commit 51c65a85cc290db14e79034be0b83b26c7ea0407 Author: Masaori Koshiba Date: Wed Apr 3 15:35:52 2019 +0900 Fix setting NUM_PLACEHOLDERS commit 788e27e4389261bca6a2deb4af4ac1f15aedfff9 Author: Masaori Koshiba Date: Mon Apr 1 15:35:45 2019 +0900 Add debug_msg on QUICRstStreamFrame commit 2df6d5c7dca88649ab3c999b138e145441f0c457 Author: Masaori Koshiba Date: Tue Mar 26 11:15:45 2019 +0900 Fix QPACK::_encode_prefix() Prior this change, the allocated IOBufferData for Header Block Prefix was not initialized. This made random value in S and Delta Base (7+) fields in Header Block Prefix. commit f53f6e52f99f0032dfc972b74d3b5c530f66e3d5 Author: Masaori Koshiba Date: Mon Mar 25 10:50:19 2019 +0900 Fix adding Padding Frame commit c9b1db22c01da93d258b38b434246d9c40dc7fb3 Author: Masaori Koshiba Date: Mon Mar 25 10:49:47 2019 +0900 Check fist byte to make sure key is derived commit 150adcce36456a3f0f8feaf8708fdb55e839f0b9 Author: Masakazu Kitajo Date: Mon Mar 18 11:00:43 2019 +0900 Don't create packets on encryption levels already done commit 6f409c2ef37ae8cec5b3049387c12da610b8b7ac Author: Masakazu Kitajo Date: Fri Mar 15 13:52:41 2019 +0900 Use const references instead of moving QUICPacketUPtr commit d64938e0c6ca46757aeb3e8ff2e67cb8c09cae6f Author: Masakazu Kitajo Date: Fri Mar 15 12:11:06 2019 +0900 Add const qualifiers commit 98e1ec33f4dcbc70553e1536296b82dd63f4be3b Author: Masakazu Kitajo Date: Thu Mar 14 12:31:29 2019 +0900 Update tests to catch up interface changes commit 3059b853f424ff813834c700f3a5d328fc68dd9a Author: Masakazu Kitajo Date: Thu Mar 14 10:16:15 2019 +0900 Add QUICFrame::to_io_buffer_block() and use it instead of store() commit f04325d63d067b9d3fbdf43f5b3d58ce939abee3 Author: Masakazu Kitajo Date: Wed Mar 13 14:31:38 2019 +0900 Change interface of QUICPacketPayloadProtector to IOBufferBlock from uint8_t array commit 3effcc011fa0ab7c4f3fbb17b0822009226fb905 Author: Masakazu Kitajo Date: Thu Mar 14 12:29:53 2019 +0900 Fix test_QUICStream commit eb2a9ff588f948d97da06dc2f9f59e198454d5e2 Author: scw00 Date: Wed Mar 13 10:03:48 2019 +0800 QUIC: Fixed building and test_QUICStream commit fd6b9c91c4ccc769acb313ab02d2da26c3443407 Author: scw00 Date: Wed Mar 13 09:52:54 2019 +0800 QUIC Rename unbidirectional to unidirectional and fixed test commit 4aaae87ca6b86e88ba48191c1bbe45cbadd0c8bd Author: scw00 Date: Wed Mar 13 09:21:13 2019 +0800 QUIC: Move max_crypto_frame into QUICCryptoStream.cc commit 2bf10ce6deb2b66dfb995b1f57ced985ffb21684 Author: scw00 Date: Tue Mar 12 13:54:54 2019 +0800 QUIC: Adds unbidirectional stream test commit 2db36132a039e69418fbefb440fb6a0e0c6a013d Author: scw00 Date: Tue Mar 12 10:32:37 2019 +0800 QUIC: Add receive only stream commit 4adefcc846268814e375e219cadbb3bac3aa4c6d Author: scw00 Date: Tue Mar 12 09:37:37 2019 +0800 QUIC: Add Send only stream commit cb279cfc646763f92e66070ae088cac9669a0074 Author: scw00 Date: Tue Mar 12 08:44:33 2019 +0800 QUIC: Moves some functions to base QUICStream structure commit dc9485e2d9e11e4b422ebe206b26ee464545d91a Author: scw00 Date: Tue Mar 12 08:41:22 2019 +0800 QUIC: Sink records_xxx_frame function to QUICStream commit f93d4920c4cb851237d5abdd491517dbc5b7c9ee Author: scw00 Date: Mon Mar 11 15:49:43 2019 +0800 QUIC: Split out QUICCryptoStream commit 0f10526fa786bf11c24c585e44d399c504ea669d Author: scw00 Date: Mon Mar 11 15:38:23 2019 +0800 QUIC: split out QUICBidiredirectionalStream commit 08bcbdc5f8656df6d36119f13ed862c8444b07c9 Author: Masaori Koshiba Date: Mon Mar 11 16:11:34 2019 +0900 Add virtual destructor to HQClientSession / HQClientTransaction commit c18dcdb412a3f6e1efdcb4efc6fd0c1769a92099 Merge: 96f0032ed cb2382dbc Author: Masaori Koshiba Date: Mon Mar 11 15:39:13 2019 +0900 Merge branch 'asf/master' into quic-latest * asf/master: Fixed the compatibility with previous cache verison cdniuc is not a manditory claim Run clang-format against the same files on both make clang-foramt and git pre-hook UrlRewrite: separate constructor and configuration loading for testability. Fix #5108: Remove OS dependency in test_BufferWriterFormat.cc. Add Perltidy configuration and build target pop messages Fix a self detection issue where parents are not marked down when proxy.config.http.parent_proxy.self_detect is set to 2 because of multiple calls to creatHostStat() which was marking parents back up. Also added a new HostStatus Reason Code SELF_DETECT used when self detection marks a parent down. Add virtual destructor to SSLMultiCertConfigLoader. Doc: open_read_retry_time is overridable Fixes some places where refactoring was not complete Do not run clang-format for @default_stack_size@ Change url_mapping::toUrl to url_mapping::toURL for consistency with url_mapping::fromURL. Fix for() loop, correctly calculate the value of seg_in_use within Vol::dir_check() commit 96f0032edfd255033a448b9aa7175bf234a6a8ab Author: Masakazu Kitajo Date: Mon Mar 11 14:16:36 2019 +0900 Remove redundant QUICConfig::scoped_config commit a311a79516e41a70a72d5bcb757b5ac9f46a5edb Author: Masakazu Kitajo Date: Mon Mar 11 14:10:35 2019 +0900 Get session filename from QUICTLS commit f69fb7e42bc8815aff032b5018c84133b8c29fef Author: Masakazu Kitajo Date: Mon Mar 11 13:56:28 2019 +0900 Have a QUICConfigParams as a member of QNetVC commit 11e1159a3470615e5db4077315a30ca4727f48fb Author: Masakazu Kitajo Date: Thu Mar 7 16:31:00 2019 +0900 Reduce use of QUICConfig::scoped_config commit 24df75e9748f73a4cb8858f6c552909e0406da01 Author: Masaori Koshiba Date: Mon Mar 11 11:20:14 2019 +0900 Decouple HTTP/0.9 and HTTP/3 Add Http09ClientSession and Http09ClientTransaction to clarify HTTP/0.9 and HTTP/3 code. HTTP/0.9 and HTTP/3 is completely switched by ALPN (hq-* vs h3-*). commit 2f0858e06d40d8315daad4b556eb5727295060fa Author: Masaori Koshiba Date: Mon Mar 11 13:27:57 2019 +0900 Fix unit tests for HTTP/3 commit d0c9aed68f83519b8b9c9a385f031089bdbf8eed Author: scw00 Date: Mon Mar 11 11:16:15 2019 +0800 QUIC: Bring back retransmittion test case commit 9533aede1b808ba16d3b31d4af78a0cf465adff2 Author: scw00 Date: Mon Mar 11 10:43:36 2019 +0800 QUIC: Fixed test_QUICLossDetector crash commit e8eb00b0ce9987f4538542edfad111240853400d Author: scw00 Date: Mon Mar 11 10:23:04 2019 +0800 QUIC: Fixed compiler error in test_QUICStream commit c5c1378ce20cd7b092dcd51ba31509e086445bb5 Author: Masakazu Kitajo Date: Fri Mar 8 11:28:20 2019 +0900 Fix a nullptr dereference commit 49251b9f4a40326af4974348b929c310c957a992 Author: Masakazu Kitajo Date: Fri Mar 8 10:45:06 2019 +0900 Pass QUICPacketInfoUPtr to on_packet_sent instead of QUICPacketUPtr commit 0b492facbb9dcc9b14ee3cae63d741ab5628e45f Author: Masakazu Kitajo Date: Wed Mar 6 17:30:31 2019 +0900 Don't keep packets sent commit f9b26d2f52aae1aa50ecac68826733afc8f4ecae Author: scw00 Date: Fri Mar 8 08:16:49 2019 +0800 QUIC: Moving log in appropriate position commit 7532912c61b038cb45c8feddf6a5b6cb118ee196 Author: scw00 Date: Thu Mar 7 16:09:24 2019 +0800 QUIC: recovery draft-18 commit 2c538479077a23a1e5c2d2cd54fee02e76c2ea16 Author: Masaori Koshiba Date: Thu Mar 7 15:22:30 2019 +0900 Rename QUICSimpleApp to Http09App commit 0843eba371c019f74da7baea034bb2181ceeb052 Merge: ecc8f02e3 d91ca9ee4 Author: Masaori Koshiba Date: Thu Mar 7 12:38:26 2019 +0900 Merge branch 'asf/master' into quic-latest * asf/master: fix crash in CacheVC::openReadFromWriter commit ecc8f02e3ad988de7051c73d19a70892d55c81a1 Author: Masaori Koshiba Date: Thu Mar 7 12:26:14 2019 +0900 Fix QUICStreamManager to use QUICStreamVConnection commit e5a65d77da2d2f3b50ca5be7930488352ae896bb Author: Masaori Koshiba Date: Thu Mar 7 11:16:54 2019 +0900 Fix building unit tests for QUIC Prior this change, there're many undefined symbols comes from SSLMultiCertConfigLoader. As workaround fix, link unit tests to libinknet.a. commit 13e12de1a4e9ad1df22557e9e1bf187cd275a688 Author: Masaori Koshiba Date: Thu Mar 7 10:46:31 2019 +0900 Fix unit tests using QUICBidirectionalStream commit 8c157d759b5db530d15a342e7b234c33e961d5fb Author: Masaori Koshiba Date: Thu Mar 7 10:38:28 2019 +0900 Remove test_QUICHandshake commit 40882451978c2074837440135366637a6cb1fccc Author: scw00 Date: Mon Mar 4 15:01:10 2019 +0800 QUIC: Rename quicStreamAllocator to quicBidiStreamAllocator commit f6972d47db80a7e1a4b71f8b449abca43709d4b9 Author: scw00 Date: Mon Mar 4 14:49:45 2019 +0800 QUIC: Introduce QUICStreamVConnection to process VIO commit b0cd800ef0ac1a67c464b6ac88fcb8cc5718daf5 Author: scw00 Date: Mon Mar 4 09:54:18 2019 +0800 QUIC: Use QUICStream as base class commit aa107296e9f49d9c12b87845364df637e3f2f79f Author: scw00 Date: Fri Mar 1 17:27:25 2019 +0800 QUIC: Introduce base stream class commit 5e3ca198bc73f493c1df3b97aae6ecb058adf06f Author: Masaori Koshiba Date: Wed Feb 20 12:16:14 2019 +0900 QUIC: Load multiple certs commit c7e43ea08366c78887f3a01cc5d6a70d9db1099e Merge: e0cc477c9 814ccc5ea Author: Masaori Koshiba Date: Thu Mar 7 08:58:29 2019 +0900 Merge branch 'asf/master' into quic-latest * asf/master: Move minimum OpenSSL version to 1.0.2 Fix SessionProtocolNameRegistry lookup Implement aud claim in Uri Signing Plugin Fix #5093: new/delete mismatch in test_IntrusivePtr.cc. Fix #5094: Fix use after free in test_IntrusiveHashMap.cc Override delete in Extendible Fixing a previous fix to fully allocate strings when heap mismatch detected. Remove duplicate calls to TSHttpTxnReenable in xdebug traffic_layout/engine: add missing stat import tscore/ink_hrtime: add missing cstdint import tscore/eventnotify: change fcntl include path Add support for the old lua formatted ease of use conventions and the numeric log rolling values set thread affinity to current thread if the current thread type is the same as the target thread MIME: Fix line_is_real false positive. Merge url_scheme_get into only calling class method Remove extra vars to bwprint in SSLConfigParams::getCTX Conflicts: build/crypto.m4 include/tscore/ink_config.h.in src/traffic_layout/info.cc commit e0cc477c91851cfc095ebf84f2ee8aff62f7693f Author: Masakazu Kitajo Date: Wed Mar 6 15:55:08 2019 +0900 Fix tests commit b9928bf5f6d8a7551917c2c70482dc0d861dd318 Author: Masakazu Kitajo Date: Wed Mar 6 15:47:32 2019 +0900 Remove an unneccessary function commit ac81d910e6d6aaf2bf00db81de44abf0c6a15d43 Author: Masaori Koshiba Date: Tue Mar 5 16:07:42 2019 +0900 Walk through loss detectors to get correct RTO commit 991a990549153ea4c62c38ee46ab26f33a000c49 Author: Masaori Koshiba Date: Tue Mar 5 13:59:23 2019 +0900 Drop Initial packets with short DICL commit e9ad3d9c69020dde5e5e26d6cf9e451c7aeda101 Author: Masaori Koshiba Date: Tue Mar 5 11:12:37 2019 +0900 Include a reserved version to Version Negotiation packet commit 73454c9be6bd9735ef787ab7227c3cdb590ad122 Author: Masaori Koshiba Date: Mon Mar 4 11:08:17 2019 +0900 Print stream id of MAX_STREAM_DATA and STREAM_DATA_BLOCKED on debug log commit 620c1fd73f0d4079c345da2555c2d90a2a66800c Author: Masaori Koshiba Date: Mon Mar 4 10:55:45 2019 +0900 Check local flow controller to send MAX_STREAM_DATA frame commit 7a20a2117dc73aaf2804d7180e3784b97278fa56 Author: Masakazu Kitajo Date: Mon Mar 4 10:27:35 2019 +0900 Reduce dup code commit 01ed38c1ac7cfaba5acc38b6066b534d6a80e38b Author: scw00 Date: Fri Mar 1 15:26:20 2019 +0800 QUIC: Fixed build error under ubuntu 16.04 commit a52ad8d59321536c380641039676fe97f0317108 Author: scw00 Date: Fri Mar 1 11:35:35 2019 +0800 QUIC: Fixed warning in Mock.h commit 5da8f67a9b11b859b537daed30e9eed624dec054 Merge: 61f1631d4 69dd33dd7 Author: Masakazu Kitajo Date: Thu Feb 28 14:56:12 2019 +0900 Merge branch 'master' into quic-latest commit 61f1631d42ccdf748f404b55d8c68a518b11dcef Author: Masakazu Kitajo Date: Wed Feb 27 16:24:08 2019 +0900 Removed unused code commit 7a3af4e1c3251dbd5467810b0d22ba75808e4024 Author: Masakazu Kitajo Date: Wed Feb 27 16:05:54 2019 +0900 Remove QUICPacketTransmitter It's not used now. We might need it later but a fresh design would be better. commit fc88686b10f94b33a4cdc5976f81b417889d2020 Author: Masakazu Kitajo Date: Wed Feb 27 15:21:47 2019 +0900 Reduce duplicate code commit 0636a4a6ffb51729354ea329a882b86eb4bd10e7 Author: Masakazu Kitajo Date: Wed Feb 27 14:13:28 2019 +0900 Drop keys for INITIAL on client side after sending the first HANDSHAKE packet commit 22364902841f999e933c2439baa3491e24f507dd Author: Masakazu Kitajo Date: Wed Feb 27 14:02:29 2019 +0900 Drop keys for INITIAL on server side after processing the first HANDSHAKE packet commit 503320547c873db3bc79c8b00eb9028379a91b24 Author: Masakazu Kitajo Date: Wed Feb 27 13:47:20 2019 +0900 Removed a unused variable commit ba137ce1355a88e332db6e89b37d545fd861a708 Author: Masakazu Kitajo Date: Wed Feb 27 11:42:55 2019 +0900 Fix a use of wrong key_len commit 10ebad1bab212ed4ecff77074c45030ad0d6e43d Author: Masakazu Kitajo Date: Wed Feb 27 10:44:58 2019 +0900 Small cleanups commit 28617fe794ade3d0e3272cb22d228ba47898f1b1 Author: Masakazu Kitajo Date: Mon Feb 25 11:07:20 2019 +0900 Revert unnecessary changes commit 2c6156c42220c1ceed347f719ecb87fa0034d42c Author: Masakazu Kitajo Date: Mon Feb 25 10:54:54 2019 +0900 Update tests commit dac64b3b59707d983d5fd5982d27b51f70d570e0 Author: Masakazu Kitajo Date: Fri Feb 22 15:43:45 2019 +0900 Make tests buildable commit d741a0dcc291bc34e5e73dd194799b8a2cf0f7d4 Author: Masakazu Kitajo Date: Thu Feb 21 17:02:25 2019 +0900 Remove unused code commit 5b5a16fdfb38096933239a0a4c66d80323524397 Author: Masakazu Kitajo Date: Thu Feb 21 15:54:51 2019 +0900 Remove KeyingMaterial commit 3968cf7b588449ef6cc1bf16ca3042c27067203a Author: Masakazu Kitajo Date: Thu Feb 21 12:34:47 2019 +0900 Remove QUICPacketProtection commit 14871688b56d9a9043d79db5bfd617b8310173b5 Author: Masakazu Kitajo Date: Thu Feb 21 12:21:30 2019 +0900 Remove set_hs_protocol from QUICPacketFactory commit d030ade1e6c0868414e8dacbe82c02205a9a225a Author: Masakazu Kitajo Date: Wed Feb 20 12:04:50 2019 +0900 Remove set_hs_protocol from QUICPacketHeaderProtector commit fba23d972c9f551960de0e61ca1512c22fd9522c Author: Masakazu Kitajo Date: Thu Feb 14 18:15:29 2019 +0900 Introduce QUICPacketProtectionKeyInfo commit e6a9def4e848f9314029b7ab3ee917a42daf2d92 Author: Masakazu Kitajo Date: Tue Feb 26 15:07:03 2019 +0900 Don't send multiple PATH_CHALLENGE on the same packet This closes #5064 commit 4462afc9ff7cf62006db56d0093a4bc8b5c9eeae Author: Masakazu Kitajo Date: Mon Feb 25 14:54:31 2019 +0900 Fix memory leaks in QUICIncomingFrameBuffer commit f574354d0035d0094e3939a942a5e08c8bbc5d97 Author: Masakazu Kitajo Date: Mon Feb 25 11:11:14 2019 +0900 Fix compile warning on Fedora29 commit 6b10433908147e337f36ba2cc2cea95c9da18c55 Author: Masakazu Kitajo Date: Thu Feb 21 17:22:28 2019 +0900 Update for draft-18 commit f4b4625d4cc0c3b281de2bb85d00fad0dd65b402 Author: scw00 Date: Tue Feb 19 11:52:06 2019 +0800 QUIC: Fixed QUICIncomingFrameBuffer test commit 914ffb3618c7c13eae4341952351777342bdb347 Author: scw00 Date: Tue Feb 19 11:46:59 2019 +0800 QUIC: Fixed test cases commit c9552adbb67db43e2996042ed3728093e7e266e2 Author: scw00 Date: Tue Feb 19 11:08:42 2019 +0800 QUIC: Bring back QUICTransferProgressProvider commit 8535ce79ad887b806d519439b56b0e9cc7d17672 Author: scw00 Date: Tue Feb 19 08:31:09 2019 +0800 Clang-Format and signal user event only on consuming all data commit 1ccd18dcb8c087224b4f14e634e779a161f1bdfe Author: scw00 Date: Sun Feb 17 15:19:30 2019 +0800 QUIC: Do not discard frame in StreamState commit 38600d49cebfe1eb53ef69561d57737b7bd9fc48 Author: scw00 Date: Sun Feb 17 11:34:53 2019 +0800 QUIC: Refactor QUICStream State commit 6b2773c82f4ab05b4bd2983fe9ca52321c3bb798 Author: scw00 Date: Fri Feb 15 13:48:07 2019 +0800 QUIC: Fixed crash when records connection id frame commit d06806f97669e402d53cb7c42299956fbfcd9cf8 Author: Masaori Koshiba Date: Fri Feb 15 14:26:02 2019 +0900 Fix compiler warnings - unused parameter commit 0b84cf33d14accb558fa9e9b695d13a0f394868a Author: scw00 Date: Fri Feb 15 11:29:28 2019 +0800 QUIC: AckCreator only response to non-ack-only packet commit da5385f76a6f1de6e2ecab8792889f33757cbf5b Author: Masaori Koshiba Date: Fri Feb 15 11:00:53 2019 +0900 Make _has_new_data flag true only if received packet is not ack only commit 32aaa2520fda1b44dd6b4ec9ce1d59ef4983fa27 Author: Masaori Koshiba Date: Fri Feb 15 09:56:32 2019 +0900 Cleanup: remove unused counter QUICNetVConnection::_handshake_packets_sent is deprecated in favor of 2bebf6c7338e28c6ef0f8c1cdb3fb6c4e57fcc26. commit 3e6f177a1bd8f6fd58f71664d4bee18639f4bd63 Author: Masaori Koshiba Date: Fri Feb 15 09:36:57 2019 +0900 Fix compiler warnings - wrong format for ink_hrtime commit a2b365707c5b3350129e8eb96002bfeb04df85b5 Author: Masaori Koshiba Date: Fri Feb 15 09:35:42 2019 +0900 Fix compiler warnings - missing override keyword commit 3eac14ffd020cd40917e84506269a5f33e70d946 Author: scw00 Date: Thu Feb 14 16:05:09 2019 +0800 QUIC: Adds log for lost packet and add comment to the changes with spec commit 8fb23d69452471d2cc08ec8d50d5ddc292a4b9f4 Author: scw00 Date: Thu Feb 14 15:51:50 2019 +0800 QUIC: Fixed test cases commit 3d3cde2d96fc68ae9f15d72f93e95b4e52ea4852 Author: scw00 Date: Thu Feb 14 13:34:53 2019 +0800 QUIC: uncomment the congestion controller config commit 732c2b4d93d066426b892fda1c724934666f01fc Author: scw00 Date: Thu Feb 14 13:30:43 2019 +0800 QUIC: Fixed Initial const vars commit 93b24bbf2c67f07bd7089fd28ecfb65dc4135655 Author: scw00 Date: Thu Feb 14 12:58:02 2019 +0800 QUIC: process ecn section commit fe61dc5e73bbf8a55b4c0e41b090a5a806657ace Author: scw00 Date: Thu Feb 14 12:38:07 2019 +0800 QUIC: Adds draft-17 congestion controller commit cdf771bd0c9d526322fe35ce1c7a8b88c512bc39 Author: scw00 Date: Wed Feb 13 15:27:29 2019 +0800 QUIC: Remove unneccessary log with unvverified path commit 9979ff48bf86207c908ccdefa8b8bb0ca2c302c3 Author: scw00 Date: Wed Feb 13 12:36:36 2019 +0800 QUIC: Send Ping frame correctly commit 9352e40ca6ddcaac44a6d084398600ddff0ce180 Author: scw00 Date: Wed Feb 13 11:16:30 2019 +0800 QUIC: LossDetector draft-17 commit 48a89c4712b0e9adb808744ba67bdd88ab3305e3 Author: scw00 Date: Tue Feb 12 16:23:49 2019 +0800 QUIC: Adds crypto flags commit adcfe785b5d4f514870aaef04e7f0207921a189c Author: scw00 Date: Tue Feb 12 15:49:19 2019 +0800 QUIC: Rename QUICPacket::retransmittable to ack_eliciting commit 1a160b4c586b5d5c5f8c6444d9c789169ed11bb7 Author: Masakazu Kitajo Date: Thu Feb 14 16:54:20 2019 +0900 Make QUICNetVC::largest_acked_packet_number() private commit 9184dbf153d7df8f992306358c7f21f67ee15743 Author: Masakazu Kitajo Date: Thu Feb 14 16:31:19 2019 +0900 Make maximum/minimum_quic_packet_size private Since nobody use it. commit 682f1d8f7d80714d803c60618dc9147d08520c06 Author: Masakazu Kitajo Date: Thu Feb 14 16:20:06 2019 +0900 Fix typo commit 8ff58c73ae440dc1505eef52fc36ef490edb9cb5 Author: Masakazu Kitajo Date: Thu Feb 14 12:09:54 2019 +0900 Remove unused aliases commit 37082d54ccc9ccf9bf2026612eac2ec731068117 Author: Masakazu Kitajo Date: Wed Feb 13 15:31:26 2019 +0900 Create and delete QUICStreamFrame on QUICStream side commit b4507fb3451ec11596835547ac126ebe171dcc33 Author: Masakazu Kitajo Date: Wed Feb 13 11:33:28 2019 +0900 Update tests that create QUICFrame instances commit 9afd8c997cb88c0390685c41c17cfd7d7081c7dc Author: Masakazu Kitajo Date: Fri Feb 8 16:15:18 2019 +0900 Use QUICFrame::MAX_INSTANCE_FRAME commit 6e164ab1600542cd8d0b1d7890fa2543f29bf011 Author: Masakazu Kitajo Date: Fri Feb 8 16:10:37 2019 +0900 Use pre-allocated buffer for QUICFrameFactory::fast_create commit 9a34336e4bcf77a096049b1e8db3da1110ce6cd5 Author: Masakazu Kitajo Date: Fri Feb 8 11:45:36 2019 +0900 Remove QUICFrame allocators commit e319305aa7bbfeded37fe5ad7cf244505c266e83 Author: Masakazu Kitajo Date: Fri Feb 8 11:43:03 2019 +0900 Fix eternal PING bug commit 57ce5d89416e07174516f4775053ff8cad01114a Author: Masakazu Kitajo Date: Thu Feb 7 12:09:55 2019 +0900 Remove a mutex for frame transmitter We had frame transmitter but it was removed at some point. commit 6a057e7d813ce0d2e17dc40abf5d3395f9faa7e7 Author: Masakazu Kitajo Date: Wed Feb 6 16:38:51 2019 +0900 Use QUICFrameGenerators interface instead of using individual classes commit 10e1056e29710562edb0f8d25b9350ceefff6766 Author: scw00 Date: Fri Feb 1 10:26:33 2019 +0800 QUIC: Remove useless QUICConfig and rename resfresh_frame commit 96f46c5ac6a039089ee1f62da3f7e536a11e9291 Author: scw00 Date: Wed Jan 30 16:15:22 2019 +0800 QUIC: Only generate ack frame in QUICFrameGenerator::generate_frame commit 59eab471bd3cf75248d88a7064074e294ec8e2f6 Author: scw00 Date: Mon Jan 28 17:50:36 2019 +0800 QUIC: Refresh ack frame when previous ack_frame lost commit 08843763757513761262500030a44f5fc1c3caf1 Author: Masakazu Kitajo Date: Wed Feb 6 17:36:36 2019 +0900 Remove unused code commit dbbd4683d6c425946a0bbfa2b52312755fc3b297 Author: Masakazu Kitajo Date: Wed Feb 6 17:22:31 2019 +0900 Update .gitignore commit 8decc8350d2a1ee01a628e38a40e5f13ddefcd06 Author: Masakazu Kitajo Date: Wed Feb 6 17:20:36 2019 +0900 Separate out QUICPacketFactory commit b6e9a7bdc6b6ecb3237222582d24ae200d7a228f Author: Masakazu Kitajo Date: Wed Feb 6 16:37:33 2019 +0900 Remove tests for features removed commit a62ea1c0e065815585ba2be5380eaa0c26abfcd0 Author: Masakazu Kitajo Date: Tue Feb 5 16:06:15 2019 +0900 Remove QUICFrame::split which was used from RetransmisionFrame commit 8c4f40f845d659f7f757754fef9e2cd4d0604818 Author: Masakazu Kitajo Date: Tue Feb 5 15:43:51 2019 +0900 Remove QUICRetransmissionFrame commit 666ee793da1c177bc5d4ee785422896888fb51fc Author: Masakazu Kitajo Date: Tue Feb 5 14:49:30 2019 +0900 Remove QUICPacketRetransmitter commit 9bbdb911853d9fdfa824338a91dcdbeece1ee843 Author: Masaori Koshiba Date: Tue Feb 5 14:39:17 2019 +0900 Fix unit test of QPACK commit 982c9baf4b7c9ca86735fc0b28581039bc3f154b Author: Masaori Koshiba Date: Tue Feb 5 14:37:35 2019 +0900 Remove references to HTTP/3 from QPACK commit 90e1bca570574ef1d2179b8669e5fe90ce29a777 Author: Masaori Koshiba Date: Tue Feb 5 14:13:47 2019 +0900 Check decoded header list size on QPACK commit e0f07c411965567ee476d7fbe792c98e2d6cc128 Author: Masakazu Kitajo Date: Tue Feb 5 14:13:11 2019 +0900 Fix tests for QUICPacketHeaderProtector commit 2bebf6c7338e28c6ef0f8c1cdb3fb6c4e57fcc26 Author: scw00 Date: Sat Feb 2 09:59:30 2019 +0800 QUIC: Limit the sending after 3 packets with unavlidated path commit bdf4de605bfad412ea0301132d5632bb447d3c9f Author: Masakazu Kitajo Date: Tue Feb 5 12:15:42 2019 +0900 Fix tests for QUICPacket commit 59014a46f0f07c0d60a16fa8bb1144e40c61f882 Author: Masaori Koshiba Date: Tue Feb 5 10:30:07 2019 +0900 HTTP/3: load settings from records.config Below configs are added - proxy.config.http3.header_table_size - proxy.config.http3.max_header_list_size - proxy.config.http3.qpack_blocked_streams - proxy.config.http3.num_placeholders commit 1f17391f3364e0df1e3054ddd03c09cfec02459b Author: Masakazu Kitajo Date: Mon Feb 4 16:43:04 2019 +0900 Fix tests for QUICStreamManager commit f6810b0abcca5e42c321187b4b736e517ce8f07f Author: Masakazu Kitajo Date: Mon Feb 4 16:05:42 2019 +0900 Fix tests for QUICFrame This removes tests for RetransmissionFrame commit c1501512cb80969b8a3e466e7b86b7e0d647b5a6 Author: Masakazu Kitajo Date: Mon Feb 4 14:06:50 2019 +0900 Fix tests for QUICTransportParameters commit 7a83ea8c1a9a01ac40649c4114bc1dcbc8c79c03 Author: Masakazu Kitajo Date: Mon Feb 4 11:40:12 2019 +0900 Fix test_QUICFrameDispatcher commit c5f36deb48f418ffd79c26a857e5b68a8ada0074 Author: Masakazu Kitajo Date: Mon Feb 4 11:12:48 2019 +0900 Add missing override keyword commit 92707337a66e256899026ecc45605d14b64055a0 Author: scw00 Date: Fri Feb 1 10:41:31 2019 +0800 QUIC: Cast reference directly commit c8a5727e45463b686b789acc75dd4938f03b255f Author: scw00 Date: Thu Jan 31 09:22:35 2019 +0800 QUIC: Retransmit NEW/RETIRE_CONNECTION_ID frame commit 8a9dfd51beae9d53bfd1e4668cffbae2297b644f Author: Masakazu Kitajo Date: Sun Feb 3 15:35:25 2019 +0900 Catch up changes on master commit e85c101251a1dad5490aa9de0c1879fc39fd4d26 Merge: d537cf281 7eb4b2478 Author: Masakazu Kitajo Date: Sun Feb 3 15:13:11 2019 +0900 Merge branch 'master' into quic-latest commit d537cf28193a564bcfa5328757be54d63dd97e99 Author: Masakazu Kitajo Date: Fri Feb 1 17:08:36 2019 +0900 Add http3/ and quic/ to the derectory structure on README commit 4d1e6fd9ab66a40f2624d4e486fa084a844be879 Author: Masakazu Kitajo Date: Fri Feb 1 16:28:29 2019 +0900 Revert debug log format change for interop commit e87727f84a95ae6b3075455e2c5a3b90f9c14d71 Author: Masakazu Kitajo Date: Fri Feb 1 16:25:45 2019 +0900 More rename commit e0df8254057543ddbe0cb3904e9b9aa7a7cb9160 Author: Masakazu Kitajo Date: Fri Feb 1 16:17:14 2019 +0900 Rename BLOCKED frame to DATA_BLOCKED frame commit c83350134c8e4fd3629292e378f7bb365c1cf860 Author: Masaori Koshiba Date: Fri Feb 1 15:20:11 2019 +0900 Bump MAX_PACKET_HEADER_LEN for an endpoint which sending long retry token commit dc684f5a75c294880672d196727c58e33681bf43 Author: Masaori Koshiba Date: Fri Feb 1 15:17:50 2019 +0900 Print length of TLS message on debug log commit 0cac20c26ed7edf44d05c568b8b4aced9e8ff3ad Author: Masakazu Kitajo Date: Fri Feb 1 12:02:41 2019 +0900 Rename StreamBlocked frame to StreamDataBlocked frame commit 33dfc098c1b13f5d34939eb83cae925854f49ff3 Author: Masakazu Kitajo Date: Fri Feb 1 12:02:01 2019 +0900 Fix compile errors in tests commit c5048fb42c090b5219cd9b213aa690eafb33c848 Author: Masakazu Kitajo Date: Fri Feb 1 10:50:02 2019 +0900 Print offset in BLOCKED and STREAM_BLOCKED commit aa6224028eff21f9bcb3f148060bef0d1a74009f Author: Masakazu Kitajo Date: Fri Feb 1 09:29:16 2019 +0900 Fix compile warnings commit 8c0574a1dae84f6923c101f89469aa93f9fb2715 Author: Masakazu Kitajo Date: Thu Jan 31 21:35:57 2019 +0900 Don't respond with RETRY if received packet has short header commit 306ec3059440b493b9ed0aeafb6d0e538e0ed2d6 Author: Masakazu Kitajo Date: Thu Jan 31 20:58:24 2019 +0900 Fix frame size calculation on splitting STREAM commit 119062ff2bcc3dc0ae3ce911b2e97d4eb0bb82c9 Author: Masakazu Kitajo Date: Thu Jan 31 15:21:51 2019 +0900 Remove a wrong assert Packet number can be 3, and the following switch statement takes care the error commit 857b2451072e037a1d745859012e9553957e800c Author: Masaori Koshiba Date: Thu Jan 31 11:28:16 2019 +0900 Revert "Bump initial value of max_stream_data_bidi_local_in to allow sending request" This reverts commit a2cb6851aeba26ea54c86b351d0b17e67e237242. commit a2cb6851aeba26ea54c86b351d0b17e67e237242 Author: Masaori Koshiba Date: Thu Jan 31 11:13:38 2019 +0900 Bump initial value of max_stream_data_bidi_local_in to allow sending request commit 31b690ca332023bf12cc3d790cbd448b8696f7b7 Author: Masaori Koshiba Date: Thu Jan 31 11:08:11 2019 +0900 Print header and payload size of received packet commit 393b90b4ae8911fac6ddfa5cbb71c56bd2abb82a Author: Masaori Koshiba Date: Tue Jan 29 12:39:41 2019 +0900 HTTP/3: Handle error on creating uni stream commit f8e1e14101044b0e4c2c8f2d24c68bb20549a8f5 Author: Masaori Koshiba Date: Tue Jan 29 12:13:36 2019 +0900 Fix buffer-over-flow commit 22176bbe62b8ff9ad3bd4934beff032e78e9244e Author: Masaori Koshiba Date: Tue Jan 29 10:58:26 2019 +0900 Revert ALPN list for client commit b5ad80fda358266773eee6ddac28e8198aedfa6a Author: Masaori Koshiba Date: Tue Jan 29 10:57:36 2019 +0900 Fix build error commit d21e1a82af16f0db20b4c7742a3e8b569ab773d1 Author: Masaori Koshiba Date: Tue Jan 29 10:19:26 2019 +0900 Send NUM_PLACEHOLDERS commit 4ba478f426773c897a13ca666bc7df2e3ae737d9 Author: Masaori Koshiba Date: Tue Jan 29 10:06:06 2019 +0900 Switch HTTP/0.9 over QUIC (hq-*) and HTTP/3 (h3-*) by ALPN commit 7ae64a43e9214c70dfa7b8cb059525b2ea6ad61e Author: Masaori Koshiba Date: Tue Jan 29 10:05:04 2019 +0900 Add a interface to QUICConnection to get negotiated app name commit dc0198f742a0a76bdca8ab840aeb1981a7eb708a Author: Masaori Koshiba Date: Tue Jan 29 09:44:24 2019 +0900 Add IP_PROTO_TAG for HTTP/3 commit 524d0029fb2b4296a75a065c801d64dcdb50c3b3 Author: Masaori Koshiba Date: Mon Jan 28 16:20:44 2019 +0900 Add scheme & authority only if not exists commit 9672461941d7dee41ebaf983b160f50ee220d231 Author: Masaori Koshiba Date: Mon Jan 28 16:01:13 2019 +0900 Create QPACK ENCODER/DECODER stream commit d165267ddeea8f1de2a43ba93ecb12e903884bba Author: Masaori Koshiba Date: Wed Jan 23 14:06:07 2019 +0900 Add prototype of HTTP/3 support with ugly hacks Worked with lsquic-client with HEADER_TABLE_SIZE=0 commit 46da77d330379bab623a373dc65851d855eb06fc Author: scw00 Date: Mon Jan 28 18:07:18 2019 +0800 QUIC: Add retransmit fin stream frame test and fixed test_QUICFrameRetransmitter commit 2638c4fc9ab1b4a96955170aa5f54b1b9331b966 Author: scw00 Date: Mon Jan 28 17:07:15 2019 +0800 QUIC: Fixed traffic_quic compiler error commit fa9deaaf044b2d36fdb3132de6738fb1b92b9162 Author: Masakazu Kitajo Date: Mon Jan 28 14:02:35 2019 +0900 Set fin flag correctly when retransmitter splits STREAM frame commit 2ad2237cd2d30bba0cb24711125700e71dd0969c Author: Masakazu Kitajo Date: Mon Jan 28 11:02:27 2019 +0900 Record stream id of STREAM frames for retransmission commit 6dc267a9f5dfe17bfc5b022ae442892d181b3620 Author: Masakazu Kitajo Date: Thu Jan 24 17:55:41 2019 +0900 Send PING after path validation This is a workaround for connection migration. commit ba58765f6cbdd6e037f5d63cf462d9cb21830bc9 Author: Masakazu Kitajo Date: Thu Jan 24 17:49:34 2019 +0900 Record encryption level of lost frames commit 42653598059f89bdc286d58417267331a96ea73d Author: Masakazu Kitajo Date: Thu Jan 24 17:18:51 2019 +0900 Send PING frame on an appropriate encryption level commit 67673b15edcf91dff08f94cb62c81059f6bf1b25 Author: Masakazu Kitajo Date: Thu Jan 24 17:14:42 2019 +0900 Add PADDING frames if packet is too small To gather enough sample for header protection. commit eb25b14d99d50e5759d76557baa582f0850141b3 Author: Masakazu Kitajo Date: Thu Jan 24 14:39:24 2019 +0900 Update remote addr commit 866fe9298e02ee8f644133d1cea119c4d69c6ae4 Author: Masaori Koshiba Date: Thu Jan 24 11:54:31 2019 +0900 Update QPACK Static Table commit 0648132e9ba3399106ede3115ff57d9397127288 Author: Masakazu Kitajo Date: Thu Jan 24 11:17:58 2019 +0900 Fix stream type check commit c9b280286848f0b3965a9a3711d3028e547acc08 Author: Masakazu Kitajo Date: Wed Jan 23 15:10:39 2019 +0900 Fix compile warnings on CentOS commit ed680fbdd8aae074dfd6fd7f14fa1932c3e51b5f Author: Masaori Koshiba Date: Wed Jan 23 14:39:43 2019 +0900 Fix reading StreamIO buffer to create HTTP/3 frame commit 988ff5c0d365f66c8c85088f0bdc551771d220d8 Author: Masakazu Kitajo Date: Tue Jan 22 14:53:44 2019 +0900 Advertise hq-17 for now instead of h3-17 commit 6d8da83264fd3a3ce510ad0d3e7476a1661f5a82 Author: Masakazu Kitajo Date: Tue Jan 22 12:10:49 2019 +0900 Support ChaCha20 commit a6ee9e258d157af8266cc83ea89a2408ec6715f4 Author: Masaori Koshiba Date: Tue Jan 22 09:23:30 2019 +0900 HTTP/3: Add debug names functions for stream type, settings id, and error code commit 3bd51f16ed893687eec4827f8f5e3757f4b8de5b Author: Masaori Koshiba Date: Tue Jan 22 09:18:01 2019 +0900 HTTP/3: Add error codes commit d18ea13a1bab7eb9935ce9f7cba29b544f793a73 Author: Masaori Koshiba Date: Tue Jan 22 09:13:46 2019 +0900 HTTP/3: Add settings id and stream type for QPACK commit 3f8d224220516d1570ab6acc5a205bc0dcf43057 Author: Masakazu Kitajo Date: Mon Jan 21 16:43:05 2019 +0900 Fix a length check in packet protection commit 17736660076f686f6870520bcaf9dae10d5714f9 Author: Masakazu Kitajo Date: Mon Jan 21 14:29:27 2019 +0900 Log TPs correctly commit 0bec142021f0731fb3193f4099af9714d9b4d80a Author: Masakazu Kitajo Date: Mon Jan 21 14:14:08 2019 +0900 Don't unprotect RETRY packet's header commit ed906430e2c127ad5576377d28d47f472de66780 Author: Masakazu Kitajo Date: Mon Jan 21 10:19:59 2019 +0900 Update Retry packet format commit 92bf49d1e0794c2558dc683b66058dc6640bf194 Author: Masaori Koshiba Date: Fri Jan 18 13:57:01 2019 +0900 HTTP/3: Add SETTINGS frame commit 3be7a1566a2a2633683713cfbbbe01bf89604754 Author: Masaori Koshiba Date: Mon Jan 21 10:03:18 2019 +0900 clang-format proxy/http3/ commit 38439c9064a942905fa210a8c2ddf630eb5a203a Author: Masakazu Kitajo Date: Fri Jan 18 17:32:10 2019 +0900 Support NAT rebinding scenario on connection migration commit f846f919ed5472a36735face4e41375524227695 Author: Masaori Koshiba Date: Fri Jan 18 14:16:35 2019 +0900 draft-13: Remove flags from HTTP/3 frames commit 0608ab3c642874b62d81d0dc72eaeda36f115fc4 Author: Masaori Koshiba Date: Fri Jan 18 13:19:16 2019 +0900 Fix test_Http3Frame - checking frame type commit c27906e5806e79e96d59ba0caebcf9889f553458 Author: Masaori Koshiba Date: Fri Jan 18 13:11:13 2019 +0900 Fix building unit tests of HTTP/3 and QPACK - Remove test for libhttp3 from test_qpack - Disabled test_Http3FrameDispatcher temporally due to Http3FrameDispatcher::on_read_ready() is changed commit ccf5a07c2eefa8ad03b188497e2aa42eca89aacf Author: Masakazu Kitajo Date: Thu Jan 17 17:24:02 2019 +0900 Revmoed useless debug print commit ef7a22835860c0a0393790b50d1c8d55da95c32f Author: Masakazu Kitajo Date: Thu Jan 17 17:22:57 2019 +0900 Print H3 frame types received commit 2afb62121d27f96b65e23f34539221170376c8a4 Author: Masakazu Kitajo Date: Thu Jan 17 16:57:13 2019 +0900 Ignore decryption failure This closes #4134. commit 43f8c7d3694f57b466f38b4711066ee3cb5643f4 Author: Masakazu Kitajo Date: Thu Jan 17 16:29:47 2019 +0900 Acquire a lock before touching NetHandler commit a693553e129fe412f3904d9ed4a107bc35992ef3 Author: Masakazu Kitajo Date: Thu Jan 17 16:29:08 2019 +0900 Remove an unreasonable ink_assert commit 8697d161d6e9ec18c9ce8b9f68ba52834968b431 Author: Masakazu Kitajo Date: Wed Jan 16 17:33:06 2019 +0900 Send INITIAL_MAX_STREAM_DATA_UNI commit 4f7f8586bea2e88fa651dd59b584071eba58f32b Author: Masakazu Kitajo Date: Wed Jan 16 16:17:16 2019 +0900 Don't crash when H3 requests come commit bfda9e0f62dcd9e4504aa60e6527e2e35e303c5c Author: Masakazu Kitajo Date: Wed Jan 16 14:30:30 2019 +0900 Rename HQ to HTTP3 commit e1438d0d72431ce12a6566ad8dad72e63a9ea7cc Author: Masaori Koshiba Date: Wed Jan 16 08:12:02 2019 +0900 clang-formant commit cb13e7dddce53f4224c69ef5a2359f0cc1b424fe Author: Masaori Koshiba Date: Wed Jan 16 08:08:17 2019 +0900 Fix sample offset boundary check commit 3a993f9a7c8799577e4fa4f712041ba9e2726734 Author: Masakazu Kitajo Date: Tue Jan 15 15:58:49 2019 +0900 Change set_max_stream_id to set_max_streams_(bidi|uni) commit 0a685ab2b04a9b811d6f9b5562a624501cd34b88 Author: Masakazu Kitajo Date: Tue Jan 15 15:25:32 2019 +0900 Update names around max_streams commit f88932a0f9847f8a8e306d47115a5e7f2b57d9f5 Author: Masakazu Kitajo Date: Tue Jan 15 12:49:22 2019 +0900 Fix a packet type check in QUICPollCont commit b8089ccd8ec623598426094882d94bd920f06e68 Author: Masakazu Kitajo Date: Fri Jan 11 17:27:52 2019 +0900 Make unit tests compilable commit dca4f5a84c03706e0e0b08f65d2199b4bbaa94f0 Author: Masakazu Kitajo Date: Wed Jan 9 16:04:37 2019 +0900 Update CONNECTION_CLOSE frame commit c4dda17c6e4076a39d361d7cb29263be2a20f591 Author: Masakazu Kitajo Date: Wed Jan 9 15:28:49 2019 +0900 Probably fix packet number decoding issue commit e9c299f63d19fc5638acf197c194fb67c2e526ab Author: Masakazu Kitajo Date: Wed Jan 9 15:04:40 2019 +0900 Fix a compile error on ACK frame commit 56d0e1d80ccc3e5a27dcfad257898cb52ee5c019 Author: Masakazu Kitajo Date: Tue Jan 8 16:36:17 2019 +0900 Update ACK frame commit 0fbdd31904d50a90b053d4197ba6b5a284467add Author: Masakazu Kitajo Date: Tue Jan 8 16:14:45 2019 +0900 Update NEW_CONNECTION_ID frame commit 702e3e8f26e9eb7c147cfaae8dfa22a9e3d385f5 Author: Masakazu Kitajo Date: Tue Jan 8 16:12:00 2019 +0900 Update short header and header protection commit 585c1e8b26f4af6fb43aeab93738c0f46a5a1a61 Author: Masakazu Kitajo Date: Mon Jan 7 18:07:15 2019 +0900 Protect packet headers commit 3761592bac1de213a1d5567400742bb91595819f Author: Masakazu Kitajo Date: Mon Jan 7 12:39:38 2019 +0900 Read and write integer TPs as variable integers but not fixed size integers commit b022e04949a3abe7f9ef498d8df5ed5562eecf05 Author: Masakazu Kitajo Date: Mon Jan 7 12:07:36 2019 +0900 Update TP IDs and names commit 2f3b3c213b52b5d4a890540461fec5d3bbf44149 Author: Masakazu Kitajo Date: Mon Jan 7 11:33:59 2019 +0900 Update frame types and names commit bc7fe3cb2c18925c8ce3e3393caf33c80c9cb7ef Author: Masakazu Kitajo Date: Thu Dec 27 17:30:55 2018 +0900 Unprotect packet headers commit cd8347a7d2202733ad1644aa384e725cd2d941b5 Author: Masakazu Kitajo Date: Wed Dec 26 14:35:32 2018 +0900 Update version numbers to draft-17 and h3-17 commit d1f3ca0d324ca42e15138b0d35a70a03ce860221 Author: Masakazu Kitajo Date: Wed Dec 26 12:03:30 2018 +0900 Make it compilable with OpenSSL for draft-17 (probably broken) commit 447c7f6ca88df2873762b4df20eb14e15ef186bb Merge: 7c2b65a43 04bfb458c Author: Masakazu Kitajo Date: Thu Jan 17 13:48:16 2019 +0900 Merge branch 'master' into quic-latest Conflicts: iocore/net/P_UnixNetVConnection.h lib/records/I_RecHttp.h commit 7c2b65a43c1e51366ffcbeeff736b94023df63ef Author: scw00 Date: Sun Jan 13 17:19:16 2019 +0800 QUIC: Refactor crypto and stream frame retrasmission commit 06b3b2d0d0131ff21ad27dabcd794961f58f6713 Author: Masaori Koshiba Date: Tue Jan 15 12:00:58 2019 +0900 Assign thread for QUICNetVC at QUICPacketHandlerIn::_recv_packet Partically, revert 2c81f2b647bc4b24ae0459388ede28f826fed93b. On incoming side, vc->thread have to be assigned before enqueue. commit 72d2a70ee2f8a00c4eed95280b11cc5482c0fe41 Author: Masaori Koshiba Date: Tue Jan 15 09:33:06 2019 +0900 More fixes test cases broken due to (ats_unique_buf -> IOBufferBlock) commit f2cb7b6335f281bd5816da13acfc2c058665ec0e Author: Masaori Koshiba Date: Tue Jan 15 09:32:19 2019 +0900 Add test_QUICFrameRetransmitter to .gitignore commit b223484721089ca00566cedfe1323e984bd5e60c Author: scw00 Date: Fri Jan 11 15:23:03 2019 +0800 QUIC: Fixes test cases broken due to (ats_unique_buf -> IOBufferBlock) commit 850f2762c3c587c4727ca0ed80cf0f5e41f45fbc Author: Masaori Koshiba Date: Fri Jan 11 15:49:30 2019 +0900 Increment _next_stream_id_(uni|bidi) correctly commit 79ff56862137051abcb77853dfb318daa90269ae Author: Masaori Koshiba Date: Fri Jan 11 15:33:58 2019 +0900 Abort traffic_quic if it could not create stream commit b61748cfb9de25751a19d17599f5649e2711eb31 Author: scw00 Date: Fri Jan 11 13:44:12 2019 +0800 Uses IOBufferBlock::consume instead of split_frame commit 6c2e2f17e8d649eb25c02d906637cc918f5507f7 Author: scw00 Date: Fri Jan 11 10:14:45 2019 +0800 QUIC: Remove useless MAX_XX_DATA, STOP_SENDING, RESET retransmition commit 3e4ac8eff5531a8b212eb92f604c3a248d8127b1 Author: scw00 Date: Fri Jan 11 10:07:24 2019 +0800 QUIC: Uses Allocator to alloc QUICFrameInformation commit feccf53db09466acdba8fd7f4c82dd88466d0a7a Author: scw00 Date: Thu Jan 10 17:17:49 2019 +0800 QUIC: Introduces QUICFrameRetranmsitter to retransmit frame commit f848582a3d16c2bef08f11f8a557da0ddc47c07f Author: Masaori Koshiba Date: Fri Jan 11 14:49:46 2019 +0900 Set vc hander startEvent on out going QUICNetVC initialization commit 2c81f2b647bc4b24ae0459388ede28f826fed93b Author: Masaori Koshiba Date: Fri Jan 11 11:58:52 2019 +0900 Start QUICNetVConnection after thread is assigned commit 285d910a5344b1935e65bcd166542cc2888b0eb2 Author: scw00 Date: Tue Jan 8 16:26:31 2019 +0800 QUIC: Missed QUICNetVConnection _on_frame_lost commit 0d463a6f69367231fb7b5dd0729f11232e45b905 Author: scw00 Date: Tue Jan 8 16:20:26 2019 +0800 QUIC: Uses reference to the callback QUICFrameInformation commit 63e24156283048d1bb837f5971eb0511a9c65d06 Author: scw00 Date: Fri Dec 28 15:13:04 2018 +0800 QUIC: Records STREAM frame, RST frame, STOP_SENDING frame and MAX_STREAM_DATA frame commit 6e3d31a3a9e4909f5c392fcb5f243fd4fd36b842 Author: scw00 Date: Fri Dec 28 15:33:36 2018 +0800 QUIC: Fixes Stream test case commit f7fcbb5dbe82c82720e78eb7ce354fd7c879b3b5 Author: scw00 Date: Thu Dec 27 10:34:28 2018 +0800 QUIC: update crypto data buffer with IOBufferBlock commit c832698624e69e8e3d1d577b3234f70e616b3c89 Author: scw00 Date: Wed Dec 26 16:17:09 2018 +0800 QUIC Uses IOBufferBlock instead of ats_unique_malloc commit 80210bca24641f45374d4f28171bb4c6ddefd5c1 Author: Masakazu Kitajo Date: Wed Dec 26 14:30:24 2018 +0900 Fix tests for QUICStreamManager initial_max_stream_data_bidi_remote and initial_max_stream_data_bidi_local are always 32 bits values. commit 433b0abceea365dd3abcc54a3a8f4f64c75cc778 Author: Masakazu Kitajo Date: Wed Dec 26 09:33:36 2018 +0900 Fix compile errrors because of type difference commit 589eded4360cf4c8ce970e3a34d6aa64327e238b Author: scw00 Date: Tue Dec 25 11:36:21 2018 +0800 QUIC: make reset internal and fix typo commit 49c7ac1c5796edbe7fb2a6e55125094cf8a0b05a Author: scw00 Date: Sat Dec 22 15:35:22 2018 +0800 QUIC: reset a frame when fast_create commit d6ccabc7e1e212bba73ddaec7ce57586fe462a67 Author: scw00 Date: Sat Dec 22 14:25:32 2018 +0800 QUIC: add left frames commit a2f7fc15623e19fa6dc494a440d420ba1570a52b Author: scw00 Date: Fri Dec 21 21:46:43 2018 +0800 QUIC: Rework some frames parsing commit 9843be70033583cdb5c15c8cf9773629c2b6554f Author: scw00 Date: Fri Dec 21 20:07:56 2018 +0800 QUIC: Rework CryptoFrame parsing commit 7c1a1b046dac90bd241cd7d8125aece2f567329a Author: scw00 Date: Fri Dec 21 17:06:06 2018 +0800 QUIC: Rework QUICStreamFrame parsing commit d05b37a695525a7d912753a9963b490573c00bc5 Author: Masaori Koshiba Date: Fri Dec 21 11:47:47 2018 +0900 Prohibit create streams when max stream id is zero commit c97d8c04e0756a91d7c2fb0b530eef7a477dcc9e Author: Masaori Koshiba Date: Fri Dec 21 11:45:58 2018 +0900 Fix unit test of QUICStreamManager Prior this change, the test was broken by 0854da0dbb12b125c18f7e5b75fefe2977f024b4. commit 0854da0dbb12b125c18f7e5b75fefe2977f024b4 Author: Masaori Koshiba Date: Tue Dec 18 11:28:34 2018 +0900 Fix error of "HTTP GET on stream 2" scenario of QUICTracker QUICStreamManager::_find_or_create_stream() treated stream 0 special for handshake. But it's not required any more, because stream 0 is replaced by crypto stream. commit 205345828a78638f30488829f36f93bc348e42f3 Author: Masaori Koshiba Date: Tue Dec 18 12:00:37 2018 +0900 clang-format commit 1041f95901afaf1f57bdc996da8a3ba064f212b5 Author: Masakazu Kitajo Date: Tue Dec 18 11:30:47 2018 +0900 Add Pinger back with its source files commit 84cb675fe8db5527ff9fee8f214891136e3957ac Author: Masaori Koshiba Date: Fri Dec 14 15:30:21 2018 +0900 Check TLS version only if content type is HANDSHAKE on msg_cb For "Unsupported TLS version" scenario of QUICTracker commit 272d9513550eb1e4f10ad77456783916e21e6538 Author: Masaori Koshiba Date: Fri Dec 14 15:23:39 2018 +0900 Fix debug log to print error code in hex commit 6c433f5e6336eed64024b22bf5e93d901910127e Author: Masaori Koshiba Date: Thu Dec 13 14:22:20 2018 +0900 Fix unit tests commit 4532046d7fe1a5e811682220dc6ab69667efa572 Author: Masaori Koshiba Date: Thu Dec 13 13:30:37 2018 +0900 Revert "Provide an inteface for sending PING" This reverts commit c2b548104980d619fd90c5b5b186a5ed55ed1c94. commit c2af0e5f7870a9bb91b9c8c24a2387015f08fa97 Author: Masaori Koshiba Date: Thu Dec 13 13:30:34 2018 +0900 Revert "Add Pinger" This reverts commit 2197e2935c2ced586f6e7eef144903e0b3337683. commit 61a729bc97496a13560eb70d7a1b70d2f3837231 Author: Masakazu Kitajo Date: Tue Dec 11 16:12:44 2018 +0900 Don't retransmit CONNECTION_CLOSE and APPLICATION_CLOSE with QUICPacketRetransmitter commit c149a0f5985bb59fded4c4874c4f06cc5852e7e9 Author: Masakazu Kitajo Date: Tue Dec 11 16:01:00 2018 +0900 Pass connection error as a reference to keep it in QNetVC like QUICStream does commit cea7468245fd0a10fd03f0c8e1b38cac1704c66b Author: Masakazu Kitajo Date: Tue Dec 11 14:31:57 2018 +0900 Add QUICStream::stop_sending() commit 98f0ca4c627852ad02d3d7213e468ae5d0e89cad Author: Masakazu Kitajo Date: Tue Dec 11 11:58:54 2018 +0900 Don't retransmit PING frame commit c2b548104980d619fd90c5b5b186a5ed55ed1c94 Author: Masakazu Kitajo Date: Tue Dec 11 11:54:40 2018 +0900 Provide an inteface for sending PING commit 2197e2935c2ced586f6e7eef144903e0b3337683 Author: Masakazu Kitajo Date: Tue Dec 11 11:47:52 2018 +0900 Add Pinger commit eb122f585843f655230f513ffe2a267ecb9ceecb Author: Masakazu Kitajo Date: Tue Dec 11 10:15:37 2018 +0900 Add link openssl for test_tscore commit 8f4c036a53c27743bb5aca569409199359f90874 Author: Masakazu Kitajo Date: Tue Dec 11 09:55:17 2018 +0900 Fix include line in test_HKDF commit a1467183ccfa2107ed41a4ebd33bfce36a6b6df2 Author: Masakazu Kitajo Date: Mon Dec 10 17:16:06 2018 +0900 Fix a file path in Makefile.am commit 7979f0c6f11052c47861a621fec6917023c0337f Author: Masakazu Kitajo Date: Mon Dec 10 16:14:24 2018 +0900 Send NEW_TOKEN frame commit 5d1082ce96dcd4cc9149e55a185763cc6436148d Author: Masakazu Kitajo Date: Mon Dec 3 18:00:23 2018 +0900 Add QUICAddressVaridationToken and QUICResumptionToken commit bc3e89b05ca7f178a55a50826d17b4d7ceac96e1 Author: Masakazu Kitajo Date: Mon Dec 10 16:38:33 2018 +0900 Fix tests for AckFrameManager commit 4eb3fbf2280ed5c0d7a5ba9acfecdcb58a8d6944 Author: scw00 Date: Thu Dec 6 10:56:28 2018 +0800 QUIC: Send ping frame to force peer send ack commit d0d35fdb93fbe38716cd0784d7519b8a6b8b8d6f Author: scw00 Date: Tue Dec 4 11:59:59 2018 +0800 QUIC: Remove force_to_send api commit 1bc29d2627d010f55e9d01bf3b729038bd716077 Author: scw00 Date: Mon Dec 3 12:21:06 2018 +0800 QUIC: Add assertion on QUICNetVConnection::thread and fix tests commit f313cb52fc6ba1d6ea77ee0ae6091affa9a70a4c Author: scw00 Date: Mon Dec 3 09:51:44 2018 +0800 QUIC: Schedule PACKET_READY_EVENT if ack_frame availiable commit afd9c89872f52a637dde4d23361c57bbb1aea134 Author: scw00 Date: Sat Dec 1 13:26:33 2018 +0800 QUIC: Make a periodic event to send ack_frame commit dd49e1d69fd14652c17deec590717b2eb4812926 Author: scw00 Date: Thu Nov 29 11:42:12 2018 +0800 QUIC: add test for delay ack commit a732c62a1a84d27ee3a1fc644fa7aa67d0785ce0 Author: scw00 Date: Thu Nov 29 10:57:14 2018 +0800 QUIC test_QUICAckFrameCreator commit 61693dd6c5a442757f7214d6c09df9cda4e54d3f Author: scw00 Date: Thu Nov 29 10:45:32 2018 +0800 QUIC Sink common_send_packet to QUICConnection commit 26820f2789eae743f041bafd757b607e4ee9780e Author: scw00 Date: Thu Nov 29 09:47:54 2018 +0800 QUIC: Delay ack support commit 70ef6ad5043693150ea205c282ded0491659101e Author: Masakazu Kitajo Date: Fri Nov 30 16:21:12 2018 +0900 Add unit tests for QUICPreferredAddress commit 1105ae453757cab50eb7930c18e42fad252d39ee Author: Masakazu Kitajo Date: Fri Nov 30 15:40:46 2018 +0900 Update tests for QUICFlowController commit 1e94f755fd4ea617435cb406c8ac67e9b7811f5d Author: Masakazu Kitajo Date: Fri Nov 30 14:55:29 2018 +0900 Fix tests for QUICFlowController I accidentally pushed WIP branch. This fixes the unit tests but test cases MAX_DATA and MAX_STREAM_DATA are not added. commit 5f59304a750104bfd8778a6d0c227556a0a84563 Author: Masakazu Kitajo Date: Fri Nov 30 14:00:59 2018 +0900 Remove ../../ from inlcude lines commit 7a953a5767fc326ae8e5736eb7ec7c7f5b620758 Author: Masakazu Kitajo Date: Fri Nov 30 12:10:23 2018 +0900 Retransmit freames genererated by QUICFlowController by itself BLOCKED, STREAM_BLOCKED, MAX_DATA, MAX_STREAM_DATA commit 1a2ceb4b3aaa9f0d2e3cc4ba843c9955d4a2da94 Author: Masakazu Kitajo Date: Thu Nov 29 10:55:14 2018 +0900 Fixup for the previous commit It worked only for the unit test because FrameId was not issued and frame generator was not set. commit 9dc8b9346c61e0a4baa6fae7d11eae65654a12cb Author: Masakazu Kitajo Date: Thu Nov 29 10:16:19 2018 +0900 Regenerate RST_STREAM with the generateor instead of QUICPacketRetransmitter commit 4171c2754683e703f72f5f74366c13bc649b4819 Author: Masakazu Kitajo Date: Wed Nov 28 16:09:20 2018 +0900 Update stream state machine All stream states and transitions are covered, but not sure QUICStream uses it correctly. commit 6fa045be7449d55a5c6befe2add64d4ae601b4e9 Author: Masakazu Kitajo Date: Tue Nov 27 15:40:15 2018 +0900 PATH_RESPONSE doesn't need to be retransmitted commit 827548879007c8fc81dcd3aef8506fcdeada9885 Author: Masakazu Kitajo Date: Tue Nov 27 14:40:02 2018 +0900 Remove dependency for TCL and tscore/Map.h commit d640a733610d776e9daa2abc1c5391dc2d3b840c Merge: 0ef9bce32 d41bbedc5 Author: Masakazu Kitajo Date: Tue Nov 27 14:22:45 2018 +0900 Merge branch 'master' into quic-latest commit 0ef9bce3206ac4bc949003c3a7dc78aacc7780e6 Author: Masakazu Kitajo Date: Tue Nov 27 13:59:23 2018 +0900 Send original_connection_id TP commit c625b48a78ddb86cd3b5768e37f000295dbb8a31 Author: Masakazu Kitajo Date: Mon Nov 26 16:13:46 2018 +0900 Refacotr retry token and stateless reset token commit b17f0f47e4af1d022fcc2b0a9cea3b1b3884a948 Author: scw00 Date: Tue Nov 27 09:31:21 2018 +0800 QUIC: Removes unused the memcpy commit ab21f0cc4866035150204acdd6deb04865735b7c Author: scw00 Date: Tue Nov 27 09:21:42 2018 +0800 QUIC: Add issue_frame_id to get latest frame id commit 10ab66a95241ec6fd3ba56417c7d833fd0b90b75 Author: scw00 Date: Mon Nov 26 20:47:01 2018 +0800 run clang-format commit 8d9822a74a77b2a5d80b4379e99e7de3f2144a20 Author: scw00 Date: Mon Nov 26 20:46:09 2018 +0800 QUIC: Implement QUIC ack tracing commit b8692528b0c9edeb559d16eb2be1fa2726db7008 Author: scw00 Date: Mon Nov 26 11:18:56 2018 +0800 QUIC: Use uint8_t buffer instead of union commit 253117f65497f52064b13ab50bec5d08fd29fcce Author: scw00 Date: Mon Nov 26 10:19:25 2018 +0800 QUIC Removes frame type checks, user should only call records_frame when you need to track this frame commit 99c968e20cc9dcbcbce9786008a0862ae3ee22d4 Author: scw00 Date: Sun Nov 25 11:09:35 2018 +0800 QUIC: Records frame information which needs to be tracked commit d36bc1190631e0879961284e4c86188511caa7fb Author: Masakazu Kitajo Date: Thu Nov 22 16:48:48 2018 +0900 Send Preferred Address if it is set This adds proxy.config.quic.preferred_address . commit a88e26fbe8444905a44261b985ecc9bb1157959a Author: Masakazu Kitajo Date: Mon Nov 19 16:54:44 2018 +0900 Keep frame id and frame generator instead of actual frame instance commit 7d2c1a2409a7c7800ea99725d64ffe1b87387015 Author: Masakazu Kitajo Date: Thu Nov 22 10:56:49 2018 +0900 Fix CID randomization The last byte was not initialized correctly commit 13064f8f4e8989166e8edd3e631a93e3f5b6ac0e Author: Masakazu Kitajo Date: Wed Nov 21 18:11:06 2018 +0900 Fix build issues commit f3d3bbd92c01c3973cd3b2a03718464a52f6feac Author: Walter Karas Date: Tue Nov 20 20:32:10 2018 -0600 Avoid doubling the size of ats_unique_buf instances. It is not necessary to have a pointer to the ats_free() function in each instance. http://coliru.stacked-crooked.com/a/bf9a683da11820c2 commit 880e9f6732e2ed09706b0d504684924647b3287c Author: Masakazu Kitajo Date: Wed Nov 21 10:08:08 2018 +0900 Signal UDP activity commit a9c6242f7c368fae5b1d36ab0d68ff9aa74a13f4 Merge: 5d20eee5e a5f145d9a Author: Masakazu Kitajo Date: Wed Nov 21 10:02:35 2018 +0900 Merge branch 'master' into quic-latest Conflicts: iocore/net/UnixUDPNet.cc commit 5d20eee5ed58c08237bece79ec546a712bfa375d Author: Bryan Call Date: Tue Nov 13 16:03:24 2018 +0900 Changed the random number generator for QUICPathValidator commit d64f8c6e96961915de13a3e197ae50bba90aa4f7 Author: Masakazu Kitajo Date: Tue Nov 20 14:29:16 2018 +0900 Improve performance commit fe54897e3ee9fac3473af1da0c67ff0270217369 Author: scw00 Date: Tue Nov 20 09:30:48 2018 +0800 QUIC: Fixes receive side delay commit 0baa9ce6ccfe754f7091e33367521fe266f4a2ea Author: Masakazu Kitajo Date: Tue Nov 20 00:33:44 2018 +0900 Fix build error in unit tests commit 8b3effaf622c05b8fec96f3df32d6ad78f3fe4c3 Author: Masakazu Kitajo Date: Tue Nov 20 00:33:24 2018 +0900 Reduce use of QUICConfig commit b3e8671d6a11eadf58cff4d8e16b95a6c92eefd7 Author: Masakazu Kitajo Date: Mon Nov 19 22:30:26 2018 +0900 Rename server id to instance id commit 88cbdc7f4c2da45683bbf795b87eeb2f06453840 Author: Masakazu Kitajo Date: Mon Nov 19 15:59:22 2018 +0800 QUIC: Fixed the signalActivity is not work on QUIC branch Co-Authored-By: scw00 <616955249@qq.com> commit e17ac35a7c7469418341901b3be6a61848e9df86 Author: scw00 Date: Mon Nov 19 15:13:58 2018 +0800 QUIC: Fixed the signalActivity is not work on QUIC branch commit b23d1c57c03b689a3b06b2b7719e9674bd9dfb8b Author: Masakazu Kitajo Date: Tue Nov 13 13:02:26 2018 +0900 Fix a minor build error commit 40960ab2f397ea6e9d842b9356905cf0268d6ae5 Author: Masakazu Kitajo Date: Fri Nov 9 17:26:20 2018 +0900 clang-format commit d8a5b919d171c3cb373925a789f856cd59eb1347 Author: Masakazu Kitajo Date: Fri Nov 9 17:13:08 2018 +0900 Fix compile error because of incomplete merge commit 3d038e48d9c15625d84d84df19581635ffcdd13b Author: Masakazu Kitajo Date: Fri Nov 9 17:06:50 2018 +0900 Remove conflict marker commit 3407ed3f0db440c5e9444ebc8566e3004d397fc3 Author: Masakazu Kitajo Date: Fri Nov 9 17:00:15 2018 +0900 Fix unit tests commit bba430f0336baa4b5fa91182fd56faa4aa9facb5 Author: scw00 Date: Tue Nov 6 11:59:31 2018 +0800 QUIC: run clang-format commit 1c718c319e7a59b9bb75093ad9663004aa22b0a1 Author: scw00 Date: Tue Nov 6 11:58:51 2018 +0800 QUIC: Move set_owner to QUICFrame default constructor commit 3bb2ebee9a5707c5679cf1d63fc1555925fe870b Author: scw00 Date: Sat Nov 3 11:14:33 2018 +0800 QUIC: introduce QUICFrameReactor to react the frame sent event and lost event commit 64560de7ca84d6436b0c0a4ff63ebea44f7c3591 Merge: 18e992079 f6e3db952 Author: Masakazu Kitajo Date: Fri Nov 9 16:58:00 2018 +0900 Merge branch 'master' into quic-latest Conflicts: .gitignore iocore/net/P_Net.h commit 18e9920793909fc7036165766ce95456f1ce68d8 Author: Masakazu Kitajo Date: Tue Nov 6 16:10:30 2018 +0900 Fix a unit test for QUICFrame commit 6eb6b99821089309837f5d12af66b93dc6be15e3 Author: Masakazu Kitajo Date: Fri Nov 2 17:38:09 2018 +0900 Fix a bug in connection migration commit e8a450606c19a08cc2ed5d1b0125777b2eabd48e Author: Masakazu Kitajo Date: Fri Nov 2 17:37:03 2018 +0900 Improve a debug log commit fbd453272279cd59f18039d08612d4bfcf5f93f4 Author: Masakazu Kitajo Date: Fri Nov 2 11:00:20 2018 +0900 clang-format commit 10cce9ed3ff726c369a070c32a65312fb6668d0c Author: Masakazu Kitajo Date: Fri Nov 2 10:58:41 2018 +0900 Fix build issues caused by merging master commit 420d5afc1b9251c794e5b151f1b5b3e13c78d6e7 Merge: 11f8b0611 95b70f05d Author: Masakazu Kitajo Date: Fri Nov 2 09:28:18 2018 +0900 Merge branch 'master' into quic-latest commit 11f8b06119aef6e300e22c22db1deda7454a85e7 Author: Masakazu Kitajo Date: Wed Oct 24 11:31:54 2018 +0900 Add proxy.config.quic.max_ack_delay_in/out commit 992aa470edd1af361e5993ab66b9820c1b6201e5 Author: Masakazu Kitajo Date: Fri Oct 19 14:30:51 2018 +0900 Update connection migration commit 7350bc218752f45e754ccc90b14f8e1fd38dafca Author: Masakazu Kitajo Date: Thu Oct 18 11:35:19 2018 +0900 An endpoint should provide and maintain at least 8 CIDs commit 156c6827f8093f75e977057b6437e6d539eec811 Author: Masakazu Kitajo Date: Wed Oct 17 16:52:36 2018 +0900 Add partial support for ECN Section It can read ECN Section but cannot write ECN Section commit 1f1f22cf6e00a3508ded9d4f836005ee78b7e5df Author: Masakazu Kitajo Date: Wed Oct 17 12:04:11 2018 +0900 Fix a test for VN packet commit 50a0c033b99f63e242d7b5282192f52fb6b9bfbe Author: Masakazu Kitajo Date: Wed Oct 17 11:55:38 2018 +0900 Fix frame type detection commit 236323f61d36eb77b29006b66cad004e2e1e32fa Author: Masakazu Kitajo Date: Wed Oct 17 11:39:35 2018 +0900 Add RETIRE_CONNECTION_ID commit 73576459916de7cd1662559fff96b6c7c7ecdac9 Author: Masakazu Kitajo Date: Wed Oct 17 11:36:35 2018 +0900 Fix NEW_CONNECTION_ID commit 4c2b886301523949294a76e32baa0dba55bc9bc5 Author: Masakazu Kitajo Date: Wed Oct 17 11:24:46 2018 +0900 Fix tests for ACK frame commit 738f94a0c596940c8933c2db01ae2c5fd6803875 Author: Masakazu Kitajo Date: Mon Oct 15 15:58:50 2018 +0900 Apply minimum changes for draf-15 - Update Version - Update validation rule for Transport Parameters - Update frame type values - Update NEW_CONNECTION_ID frame format commit e89bed2d2243fd511dff7141acbe1095a4f44bee Author: Masaori Koshiba Date: Thu Oct 25 11:45:20 2018 +0900 Cleanup test_QUICHandshakeProtocol commit 49c53dac3da49bc327b56237b26a8341a99331fe Author: Masaori Koshiba Date: Wed Oct 24 14:28:12 2018 +0900 Start server stateless retry only if QUICNetVConnection doesn't exist commit 29bba883ce44ddbfa329b957a36e8509f2af4432 Author: Masaori Koshiba Date: Wed Oct 24 10:32:38 2018 +0900 Create 0-RTT packet if needed commit 1ed85fa00005bab727aa1730000fcdea9e55ee05 Author: Masakazu Kitajo Date: Tue Oct 23 17:37:37 2018 +0900 Add support for ack_delay_exponent commit 4c31a88af57dfef66ec7dd859345ba56bcfee467 Author: Masaori Koshiba Date: Tue Oct 23 16:00:45 2018 +0900 Handle RETRY packet on quic client commit 359ac53182da0a1ee386aa16a5e4a7f8bd6c196d Author: Masaori Koshiba Date: Tue Oct 23 15:58:39 2018 +0900 Rename config for server stateless retry commit 75f95ee6e834a03dd21eb142ac47dcc7f11b54f0 Author: Masaori Koshiba Date: Tue Oct 23 15:57:26 2018 +0900 Add Server Stateless Retry Support (take 2) commit 28832e086be5640d1990eef40c6b7a3373320a84 Author: Masaori Koshiba Date: Tue Oct 23 15:48:55 2018 +0900 Expand buffer size for serialized packet header commit 7e2d0ab498a109d0b3e06e4150d14e4f1284eafe Author: Masaori Koshiba Date: Tue Oct 23 15:47:19 2018 +0900 Add reset API to QUICPacketNumberGenerator and QUICPacketFactory commit 9b76a39cc4da6adf39ca49563796cf80092ba1f9 Author: Masaori Koshiba Date: Tue Oct 23 15:46:28 2018 +0900 Cleanup QUICStatelessRetry commit ed96c690290c2690fdc355f26510d8a9d4d4b617 Author: Masaori Koshiba Date: Tue Oct 23 15:45:48 2018 +0900 Build INITIAL packet with token commit e1b945981bb4a18aa2ea3c724b13b41cbe357eb8 Author: Masakazu Kitajo Date: Tue Oct 23 13:58:58 2018 +0900 Copy ACK block section in QUICAckFrame::clone() commit 65c88702f08ff59d96f7ff47115d9f24c355581f Author: Masakazu Kitajo Date: Tue Oct 23 13:47:08 2018 +0900 clang-format commit 3517a33e590dafe5c07b802b765f79e82c89f811 Author: Masakazu Kitajo Date: Tue Oct 23 13:46:17 2018 +0900 Fix compile errors in tests commit 0b56b02629ccf46b497920c0c7d51157cea3ee62 Author: Masakazu Kitajo Date: Mon Oct 22 15:59:32 2018 +0900 Fix log output commit 30c2af2ff40b35bfab49f1fa03140d787f0b13b5 Author: Masakazu Kitajo Date: Mon Oct 22 14:54:15 2018 +0900 Add check for probing packet commit 5ce7ab64e1201649f240d64511cc1debb3abddc7 Author: Masaori Koshiba Date: Wed Oct 3 14:41:28 2018 +0900 Load/Store RETRY Packet commit c26e6937767b2f17de66fd70df019b6d2fbe3efc Author: Masakazu Kitajo Date: Mon Oct 22 10:01:36 2018 +0900 Update debug log output commit a78b908f5a56fc1832cb686c7300f3bde7183779 Author: scw00 Date: Fri Oct 19 14:39:57 2018 +0800 QUIC: Add write_early_data to generate 0rtt km commit 6b6a75bf6c52cfb0925620dd36c8741d1aad1fd4 Author: Masakazu Kitajo Date: Fri Oct 19 16:19:38 2018 +0900 clang-format commit 742632709717d042d210cf3f03335f79812b188e Author: Masakazu Kitajo Date: Fri Oct 19 14:28:43 2018 +0900 Cleanup path validation event handlers commit 372e9c0104a3ea1bd1c9e8ab9bb7da891da05a03 Author: Masakazu Kitajo Date: Fri Oct 19 11:19:54 2018 +0900 Add is_probing_frame() and is_probing_packet() commit f2db48fb4147fa03b6ca6f4fea67c9f4db24a9af Author: scw00 Date: Tue Oct 16 16:58:57 2018 +0800 QUIC: Ignore empty msg when handshake success commit b68c249ca3816192affab52db8ecfb7ed4bc1f1b Merge: bfa09998a 9ec54c7ea Author: Masaori Koshiba Date: Tue Oct 16 14:47:23 2018 +0900 Merge branch 'asf/master' into quic-latest * asf/master: (52 commits) Fix gzip plugin not linked correctly with zlib ts_file: Add 'view' method to get a view of the path. Do not follow redirects after cache hit Fix logging log file roll issue (#2544). Remove deprecated HTTP/2 metrics TCL: remove TCL dependency from RecCore TCL: Remove TCL dependency from iocore Fix traffic_via problem with string length New ATS Magick experimental plug-in Doc: Repair various warnings, mostly from header_rewrite and ArgParser. Update the nitpick list. Meta: Add conditional compilation case meta structures. Removes errant mid-sentence extra space from message Allows unknown configuration variables when specified on the commandline Replace the overridable lookup with an unordered_map Rename stats for HTTP/2 current client connections Add some feeble amount of encapsulation to the Thread class. Cleanup: Remove MgmtHashTable.h Removes reference to non-existent example Convert traffic_cache_tool to use ArgParser client_bytes should be initialized as 0, it is exclusevely used by get_info_from_buffer to count the number of copied bytes and set the last byte to 0 ... Conflicts: lib/records/RecHttp.cc commit bfa09998a69508c120fc93e8846f7788c06aa478 Author: Masaori Koshiba Date: Tue Oct 16 14:26:31 2018 +0900 Fix unit tests commit 5d6bd76131a828a0b1993a5eba7d51c94ce8476f Author: Masaori Koshiba Date: Tue Oct 16 14:11:24 2018 +0900 Remove unused variable commit 73bddb3bb4c093ad073d9cafff17b10e5e242a74 Author: scw00 Date: Sun Oct 14 09:48:34 2018 +0800 QUIC: Changes QUICErrorUPtr to QUICConnectionUPtr to terminate the connection commit 3d6b0f5212aed5c21cdbe764a3f8fc6353384374 Author: scw00 Date: Sat Oct 13 11:46:22 2018 +0800 QUIC Client session reused by new session ticket commit 5e1c9166359a7f2afd66549a38701152ca9d3d04 Author: scw00 Date: Mon Oct 15 12:11:28 2018 +0800 QUIC Read new session ticket msg and send it back commit 30ed26a920b2995f6eca5f941d2d3ed964f08368 Author: Masakazu Kitajo Date: Mon Oct 15 10:13:37 2018 +0900 clang-format commit 84f168f433f33583467827945a6b8df41239cb34 Author: Masakazu Kitajo Date: Sat Oct 13 19:09:41 2018 +0900 Remove deprecated functions in QUICStreamIO commit a47a34a7b3117461afba38fa62468dc944f25637 Author: Masakazu Kitajo Date: Sat Oct 13 19:04:49 2018 +0900 Don't use deprecated functions of QUICStreamIO commit 51534c72c12d081324d34df93cbce9fc834c875b Author: Masakazu Kitajo Date: Sat Oct 13 18:59:43 2018 +0900 Cleanup QPACK commit 411094535d1d241b576cb60208e2fae67520d14d Author: Masakazu Kitajo Date: Sat Oct 13 18:12:44 2018 +0900 Fix a memory leak in QUICApplication commit f446a02d2c8a4e0a37f8e65d4af9c8cdffa79318 Author: Masakazu Kitajo Date: Sat Oct 13 18:06:16 2018 +0900 Cleanup tests for QUIC transport Fix memory leaks Remove unused variables commit 0f208a33ef0acf0038cf7887e7be185503921ad3 Author: Masakazu Kitajo Date: Thu Oct 4 17:44:21 2018 -0700 Fix a build issue This should fix #4353 commit 6f4dc5f5390838c384cd7baac2472474eabf524e Author: Masakazu Kitajo Date: Fri Sep 28 14:52:54 2018 -0600 Remove key phase changing logic completely for now It was destroyed when draft-13 was supported, and is causing a compile warning. commit 304ca803270207dbd47f238d7ddb73996e7936be Author: Masaori Koshiba Date: Fri Sep 28 17:56:11 2018 +0900 Restore unit test for full handshake with HRR commit 43ce284fed15d455be127c008261444cb51b8382 Author: Masaori Koshiba Date: Fri Sep 28 15:09:36 2018 +0900 Do not initiate connection migration before advertising new connection ids commit db38d2f865f0c8cb54eabdb3d1bc16edc29a9091 Author: Masaori Koshiba Date: Thu Sep 27 16:22:41 2018 +0900 Stop sending STREAM frame while proving a new path commit d054c42cc8bfda86ca465cb6caed13e9374e098f Author: Masaori Koshiba Date: Thu Sep 27 12:35:15 2018 +0900 Improve connection migration implementation - Store advertised Connection IDs - Initiate Connection Migration from client side To start connection migration exercise, set `proxy.config.quic.client.cm_exercise_enabled` 1. ``` PROXY_CONFIG_QUIC_CLIENT_CM_EXERCISE_ENABLED=1 /opt/ats/bin/traffic_quic ``` commit 1a5e471d5b60946216ab132150665e5cc7f8f77c Author: Masaori Koshiba Date: Thu Sep 27 11:53:48 2018 +0900 Ran clang-format commit 1c9fa8b03b8834dd8e46ff331936d3d1d7ab6ff5 Author: Masaori Koshiba Date: Thu Sep 27 11:47:29 2018 +0900 Print cid field of NEW_CONNECTION_ID on debug log commit 207946c4927d0419b035a6de227cf6302cf009fb Merge: 904d2c869 2e7344428 Author: Masaori Koshiba Date: Thu Sep 27 09:22:42 2018 +0900 Merge branch 'asf/master' into quic-latest * asf/master: Adds Robots exclusion file Removed headers that are not used Remove Bitops - apparently not used anywhere and should be replaced by std::bitset anyway. Rewrite traffic_layout with ArgParser Fix typo of the nofiles ulimit to a sane number Runroot: make runroot accept defective yaml file Generally random code cleanup Add a search path for Tcl Add warning of body_factory templates are not loaded due to missing dot file. Expand the post chunked tests. Removes secret field from log statement Fix libjansson & libcjose static or dynamic detection Fix doc building warnings for recent plugin docs commit 904d2c8698b5d0e9df97d085eb75d7dacd7ed7db Author: Masaori Koshiba Date: Wed Sep 26 14:31:44 2018 +0900 Cleanup: rename _state_common_receive_packet and _state_connection_established_process_packet - _state_common_receive_packet() -> _state_connection_established_receive_packet() - _state_connection_established_process_packet(QUICPacketUPtr packet) -> _state_connection_established_process_protected_packet(QUICPacketUPtr packet) commit 4b2821ad4d40436bfa00f903a946e75ecc644264 Author: Masaori Koshiba Date: Tue Sep 25 13:59:58 2018 +0900 Replace libtsutil.la with libtscore.la commit 3cf8517c00d6bbd364671a267145a2e40c53999a Merge: 5f167189a 52f515196 Author: Masakazu Kitajo Date: Mon Sep 24 16:03:34 2018 -0600 Merge branch 'master' into quic-latest Conflicts: iocore/net/P_UnixNetVConnection.h lib/records/RecHttp.cc proxy/http2/HTTP2.cc proxy/http2/Makefile.am src/Makefile.am src/traffic_server/traffic_server.cc src/tscore/Makefile.am commit 5f167189ae44847c912dcd5dcc7d55180219e3cc Author: Masakazu Kitajo Date: Thu Sep 20 10:33:22 2018 -0400 Fix several bugs in QPACK commit 1698fbac42297356fb20fd177ff423e60f553e7b Author: Masaori Koshiba Date: Wed Sep 19 22:17:39 2018 +0900 Omit zero-length DCID/SCID on sending packets commit 1676ab5962c99beea51a8c34ea5823f27226a12e Author: Masaori Koshiba Date: Wed Sep 19 20:58:21 2018 +0900 Fix zero-length DCIL & SCIL commit 31527a02cd52ab871319a2769fe8ca9c658dc424 Author: Masaori Koshiba Date: Wed Sep 19 20:14:32 2018 +0900 [draft-14] Fix test_QUICTransportParameters commit e6f705db3b5f0ee8c00aaff615fda0a21b394c1b Author: Masaori Koshiba Date: Wed Sep 19 00:23:45 2018 +0900 Print stream id in decimal in debug logs commit e81a8c350690e5c1854f421d5f14acf14ba75fa7 Author: Masaori Koshiba Date: Tue Sep 18 15:20:29 2018 +0900 Add debug logs on sending/receiving QUIC packet commit 9f1a5456a0e5e1c3f19e239b17ccc183b4425537 Author: Masaori Koshiba Date: Tue Sep 18 00:38:27 2018 +0900 Add QUICApplicationCloseFrame::debug_msg() commit cb549c7ba5da402eb815489f103db8043e42da32 Merge: e8118051b 4405411fe Author: Masaori Koshiba Date: Tue Sep 18 00:54:06 2018 +0900 Merge branch 'asf/master' into quic-latest * asf/master: (43 commits) HttpSessionManager: Fix potential infinite loop problem. IntrusiveHashMap: Inserts preserve order for equal keys, as with std::multimap. Update documentation, more testing. IntrusiveDList: Add ptr_ref_cast to make inheritance easier and avoid compiler aliasing complaints. TextView: Better support for std::string assignment. Correct interpretation of proxy.config.ssl.client.verify.server Disable the HttpSM half open logic if the underlying transport is TLS Fix inconsistent links in docs. Disables the double test for inconsistent execution GCC 8.1.1: Fix complaint about lack of storage definitiona for class static const. Fix a regression in the traffic_ctl host status subcommand. Host statuses were not being found as the reason tag was left off. Plugins: Cleanup up dependencies on core headers - authproxy Runroot: Update doc Fixes a segfault that may occur when the debug and a lookup fails to find an entry in the hoststatus hash table. Remove the ignore_keep_alive method entirely Lock continuation before calling event handler. Revert "Fixes MacOS linker issue with release build" Remove ssl_cert_loader. Certifier is more complete version. Cleanup, and adds support for new luajit option Remove unneeded aio header file Update to changelog generation tool to not require milestone to be closed ... commit e8118051b7f93516f18a54c8c0c5aa84759b8bd3 Author: Masakazu Kitajo Date: Fri Sep 14 21:26:30 2018 +0900 Support non-default table size of QPACK commit b3383588c1d207415dbdee088a63412147e82950 Author: Masaori Koshiba Date: Fri Sep 14 20:41:42 2018 +0900 Make QUICNetVC handle APPLICATION_CLOSE frame commit 66798aa2a9c9c745d68161eaab58e4988c218e30 Author: Masaori Koshiba Date: Fri Sep 14 12:29:45 2018 +0900 Update peer cid on server side when connection migration is initiated commit 1e5bacae50d6c16d23c710c2d5590c903ecf3f74 Author: Masakazu Kitajo Date: Fri Sep 14 11:07:22 2018 +0900 Support QPACK instruction Insert With Name Ref commit aede93e79ab51393c53e40ad382cf9b6e2150514 Author: Masakazu Kitajo Date: Fri Sep 14 11:04:19 2018 +0900 Update test_QPACK to decode speciied encoded files commit 8df035b43b9bc7b5065fde918b7f3ef5585b66d2 Author: Masaori Koshiba Date: Fri Sep 14 10:09:47 2018 +0900 Print generated alt quic connection ids for debug commit 035d09aa74353f3b75762a1d7a9e8ee1822d73df Author: Masaori Koshiba Date: Fri Sep 14 08:19:25 2018 +0900 Ignore invalid packets which failed to migrate connection commit d6515064c024c3f71cd1e1cdbd5f8fe14f916247 Author: Masakazu Kitajo Date: Thu Sep 13 17:55:44 2018 +0900 Fix bugs around static table reference commit d673c783ac7c4469ed174f0a2fb5d99df02f8179 Author: Masaori Koshiba Date: Thu Sep 13 15:14:12 2018 +0900 Ignore VN packets on closing state commit 730a57dda5a197427090c950d192d057582a9849 Author: Masaori Koshiba Date: Thu Sep 13 10:39:54 2018 +0900 Print error reason on debug log only if it is provided commit f1da5b0872f67a587ed81c57e9f9c9a912715c88 Author: Masaori Koshiba Date: Thu Sep 13 10:32:44 2018 +0900 Print derived secret on key_cb for debug commit 00397dfa42846b68f9e4b6345e656e3e35151ce9 Author: Masakazu Kitajo Date: Wed Sep 12 17:21:40 2018 +0900 Fix dup header name bug in QPACK test driver commit bfd04179ba77d1ba3e1ea0b619b84510137c6705 Author: Masakazu Kitajo Date: Wed Sep 12 17:20:39 2018 +0900 Increase maximum number of test sequences for QPACK test commit b5de7df0b6d8cac355f212b50f63aa72c653e388 Author: Masakazu Kitajo Date: Wed Sep 12 17:19:14 2018 +0900 Fix a sizeof/countof bug in QPACK commit da89f6932ec8ef2c49d67c3340685710737e73d9 Author: Masakazu Kitajo Date: Wed Sep 12 17:16:10 2018 +0900 Convert heaeder names to lowercase befor encoding and after decoding commit 0af26d2decd6b60c135eafc968e2a36b66b35429 Author: Masakazu Kitajo Date: Wed Sep 12 17:14:01 2018 +0900 Update QPACK static table commit 54be99c2bc1fcbdca1f586f491432654a4067d6e Author: Masaori Koshiba Date: Wed Sep 12 16:10:28 2018 +0900 Simplify content type strings and handshake type strings commit 0c3470265ca930b9a98c78cfb05d740d4a964f95 Author: Masaori Koshiba Date: Wed Sep 12 15:51:47 2018 +0900 Add unit tests using draft-14 test vectors for clear text AEAD key derivation commit 0a4524d825279ee8289ea673b46bd9e300279836 Author: Masaori Koshiba Date: Tue Sep 11 11:27:05 2018 +0900 [draft-14] Remove QUICTransErrorCode::UNSOLICITED_PATH_RESPONSE commit 67854c45d848efcd83399600c2b1d5d26704d8c1 Author: Masaori Koshiba Date: Mon Sep 10 14:49:38 2018 +0900 Add configs for Transport Parameters (initial_max_*) Split proxy.config.quic.initial_max_data - proxy.config.quic.initial_max_data_in : 65536 - proxy.config.quic.initial_max_data_out : 65536 Split proxy.config.quic.initial_max_stream_data - proxy.config.quic.initial_max_stream_data_bidi_local_in :0 - proxy.config.quic.initial_max_stream_data_bidi_local_out : 4096 - proxy.config.quic.initial_max_stream_data_bidi_remote_in : 4096 - proxy.config.quic.initial_max_stream_data_bidi_remote_out : 0 - proxy.config.quic.initial_max_stream_data_uni_in : 0 - proxy.config.quic.initial_max_stream_data_uni_out : 0 Add configs for initial_max_(bidi|uni)_streams - proxy.config.quic.initial_max_bidi_streams_in : 100 - proxy.config.quic.initial_max_bidi_streams_out : 0 - proxy.config.quic.initial_max_uni_streams_in : 0 - proxy.config.quic.initial_max_uni_streams_out : 0 commit 786ec670eb2336d3354b1276ad2c53ec34791bcd Author: Masaori Koshiba Date: Mon Sep 10 13:47:36 2018 +0900 [draft-14] Split INITIAL_MAX_STREAM_DATA into 3 transport parameters commit d497bfa4bf2b50f651908dbfe966e582363dbfb7 Author: Masaori Koshiba Date: Mon Sep 10 09:54:47 2018 +0900 [draft-14] Bump version commit 00953882b5f29686bdcec69216413bdcd86e177e Author: Masakazu Kitajo Date: Tue Sep 11 17:36:44 2018 +0900 Add const qualifier to _hs_protocol commit 0bd40e8ac1d6490469520fcec68973c438eb8a32 Author: Masakazu Kitajo Date: Tue Sep 11 17:01:04 2018 +0900 Move encrypt_pn/decrypt_pn impls to QUICPacketNumberEncryptor commit 9867db720f5473287e240cec6f9bc7e1055e0597 Author: Masaori Koshiba Date: Tue Sep 11 15:59:59 2018 +0900 Cleanup: Remove local_max_stream_data for stream 0 commit ad7f44d83823bf75127fbcbda63172178ce0b0e6 Author: Masaori Koshiba Date: Tue Sep 11 15:31:46 2018 +0900 Fix setting Transport Parameters on client side commit 4fa51aabea268a2d39e6e6dd992e0b8fff52e4b9 Author: Masakazu Kitajo Date: Tue Sep 11 15:36:00 2018 +0900 Remove unused function declarations from QUICStream commit fdd6f7e9ddd78d6a9e3ccb71330123d7124bef17 Author: Masakazu Kitajo Date: Tue Sep 11 15:29:39 2018 +0900 Rename QUICStream::_info to QUICStream::_connection_info commit 244bf34d95ea01c0a5d303a09c2274e08ae469ee Author: Masakazu Kitajo Date: Tue Sep 11 15:22:46 2018 +0900 Remove unused header inclusions commit 57207119b1cf51b3118462a42402149b1fa8ce4b Author: Masakazu Kitajo Date: Tue Sep 11 15:18:13 2018 +0900 Remove unused declarations commit 18b507739d251f3319352c9c16a5cab58d91fad7 Author: Masakazu Kitajo Date: Tue Sep 11 15:09:27 2018 +0900 Remove dependency for QUICTLS from QUICHandshake commit 7c089c1906827eae2b331267e48380098922456c Author: Masakazu Kitajo Date: Tue Sep 11 15:08:53 2018 +0900 Update mock class for QUIC commit b2a9a9b057ced8c7fdb5d7900dc088e6fdf04677 Author: Masakazu Kitajo Date: Tue Sep 11 12:38:52 2018 +0900 Fix a compile error on test_qpack commit 38a177115e38e83b25c03274025487a2ac47dcec Author: Masaori Koshiba Date: Tue Sep 11 12:20:49 2018 +0900 [draft-13] Add QUICTransportParameterId::DISABLE_MIGRATION commit 41f043e53d02d782cd24d3dbc74b21369c58f1c6 Author: Masakazu Kitajo Date: Thu Aug 30 14:00:45 2018 +0900 Reduce dependency for SSL library commit 17159040c6eab84b93186e59d53b6c6cbe5fd16a Author: Masaori Koshiba Date: Fri Sep 7 16:09:55 2018 +0900 Fix sending only one NEW_CONNECTION_ID frame The bug is introduced by 49f8be8763e60c2ee4c13c5d10956e2059cc712c commit 83dfc5a11be6b795b7715ce4189f849cb0893223 Author: Masaori Koshiba Date: Fri Sep 7 14:57:23 2018 +0900 Cleanup: Remove QUICFrame::_protection commit 0014b3664c83d33b0fa1aabb6bfeacbc083698d0 Author: Masaori Koshiba Date: Fri Sep 7 11:41:54 2018 +0900 Return QUICConnectionError if QUICHandshakeMsgs has error_code commit 656dffaa349db242141ba88deb3a03147d527325 Author: Masaori Koshiba Date: Thu Sep 6 16:28:22 2018 +0900 Make type of error code uint16_t for CRYPTO_ERROR (0x100 - 0x1FF) commit e7c493ae72eef75958409ccade0b1b8313ace494 Author: Masaori Koshiba Date: Tue Sep 4 16:20:30 2018 +0900 Handle TLS Alerts on msg_cb commit 6c7cef9c8d1c927d6f856e8eaa514ae9d532113f Author: Masaori Koshiba Date: Tue Sep 4 14:20:47 2018 +0900 [draft-13] Update Transport Error Codes commit dafca89d479ab5ad411b11ee4338648e34865a55 Author: Masaori Koshiba Date: Tue Sep 4 11:37:31 2018 +0900 Forward limit of local flow controller with the largest reordered stream frame To cover reordering cases. (e.g. QUICStream Unit Test Section "QUICStream_flow_control_local") commit 5e65a99094e4a921466236dd0de90768b5b6d361 Author: Masaori Koshiba Date: Tue Sep 4 11:34:45 2018 +0900 Cleanup: Remove _advertized_limit from QUICLocalFlowController commit abc0835abf2fc1df7dd0c9d413455067e5a7ed46 Author: Masaori Koshiba Date: Tue Sep 4 11:32:13 2018 +0900 Cleanup: Remove QUICStream::init_flow_control_params() commit 75a00c564f11e59a5145e1f9a0c1332770fd11bf Author: Masaori Koshiba Date: Mon Sep 3 15:52:32 2018 +0900 Fix QUICLocalFlowContoroller commit d74d6369a13f3154523cb722adc6a5bfe9bbe2f7 Author: Masaori Koshiba Date: Mon Sep 3 11:42:16 2018 +0900 Rename create_server_protected_packet to create_protected_packet commit 01030555a4cfe8e421886673e8dfdc592e13c53a Author: Masaori Koshiba Date: Fri Aug 31 14:24:26 2018 +0900 Update connection level FC (local) only if received packet include STREAM frame commit 4d48f656b0e088d10c717a25478909d516b1d3bd Author: Masaori Koshiba Date: Fri Aug 31 12:08:48 2018 +0900 Send MAX_DATA frame commit 4094f6b7bbe2fc8242b879444aa2773fb9f6eed4 Author: Masaori Koshiba Date: Fri Aug 31 11:44:07 2018 +0900 Acquire mutex lock before handleEvent() call in unit test of QUICStream commit 68b9428e7e99f8b6c58694a153ec8edc3a66e6e9 Author: Masaori Koshiba Date: Fri Aug 31 11:33:59 2018 +0900 Const Correctness commit 5b9a089f1261fb8d6558dcd589310e5dc6b1477c Author: Masaori Koshiba Date: Thu Aug 30 15:03:32 2018 +0900 The smallest bidi stream id is 0 commit 2621a0716f0e76f3a403383a87a154132960ac4e Author: Masaori Koshiba Date: Wed Aug 29 14:51:04 2018 +0900 Acquire mutex lock before handleEvent() call commit 4067503ebb037bfe1deb880162b2f65e3b647054 Author: Masaori Koshiba Date: Wed Aug 29 14:49:16 2018 +0900 Remove HQClientSession::release_netvc() commit cfa1d0da9a00b61b3fa75c984e59f76439ce517e Author: Masaori Koshiba Date: Wed Aug 29 14:48:46 2018 +0900 Fix HQ to replace AclRecord with IpAllow::ACL commit 3697b2335ee4bd210bc760f0d3a5469a3c5f59af Merge: f42905385 61d977844 Author: Masaori Koshiba Date: Wed Aug 29 12:26:24 2018 +0900 Merge branch 'asf/master' into quic-latest * asf/master: (86 commits) IPAllow: Refresh for C++11, fix potential use after free. PR #4129. Doc: Remove deprecated configs from examples of traffic_ctl Fix crash in Http2ClientSession::release_netvc Revert "Ensure continuation lock on read and write signal" Move handleEvent back into the header file. Corrects IPv4 multicast ip address check Cleanup: Remove #define INT_TO_BOOL, use local lambda instead. Clang-format: Redo clang-format error that slipped through. Cleanup: remove unused and unimplemented functions from ink_string.h HttpConnectionCount: Replace TSHashTable with IntrusiveHashMap. Outbound server session management - Replace TSHashTable with IntrusiveHashMap. IntrusiveHashMap: Add overloads to apply method to support functor taking reference or pointer. IntrusiveHashMap: Update erase methods. Add additional iterator_for methods for erase. IntrusiveHashMap: Change range to be std::pair based for better compatibility. IntrusiveHashMap: remove from "ts" namespace. The predecessor class, TSHashTable, wasn't in "ts" and having worked with IntrusiveHashMap for a while, I think it's best this isn't either. IntrusiveDList: Update erase methods. IntrusiveDList: Add user conversion from iterator to pointer to value_type. Completes documentation regarding redirects Completes & deduplicates code comment for redirect IntrusiveHashMap: Refresh TSHashTable for C++ eleventy. ... commit f429053851d5a9b340f1cc6348443ed3094a889c Author: Masaori Koshiba Date: Wed Aug 29 08:31:23 2018 +0900 [draft-13] Bump HQ version in ALPN commit e1f2f3c79e9619d666a9b7305cfac0e071ab4b97 Author: Masaori Koshiba Date: Tue Aug 28 16:14:23 2018 +0900 Refacoring QUICStream::generate_frame() commit 4e8f6b216a17dc24ef231d208b99b03d0cfa3457 Author: Masaori Koshiba Date: Tue Aug 28 15:34:20 2018 +0900 Add APIs to check Stream State by QUICFrameType commit fd582251820be0444cd477eccc512b8eb2c7a5cb Author: Masakazu Kitajo Date: Tue Aug 28 15:12:40 2018 +0900 Remove QUICTransportParametersInNewSessionTicket commit 33278e7efb767f3067938c0b8cb7a2b4981a0ce7 Author: Masaori Koshiba Date: Tue Aug 28 14:43:50 2018 +0900 Fix QUICSendStreamState::is_allowed_to_send() commit 4fef001479f58a1fdeac8b5f106dcf40f98d99cf Author: Masaori Koshiba Date: Tue Aug 28 13:14:26 2018 +0900 Fix build error commit 3d84318fbeb7ef1025aa12d4c608bb74d34494cd Author: Masakazu Kitajo Date: Tue Aug 28 13:12:12 2018 +0900 Remove QUICHandshake::protocol() commit 40187a3a6943d807b6f18c56b990a0b73f16d706 Author: Masakazu Kitajo Date: Tue Aug 28 12:51:02 2018 +0900 Add test_qpack to .gitignore commit f694413937f4a380b71c677a07f76a7595d3918f Author: Masakazu Kitajo Date: Tue Aug 28 12:35:47 2018 +0900 Reduce dependency for SSL from QUICHandshake commit deaa9d0cd1b4b21760a53b35304c6afcb8a71a12 Author: Masakazu Kitajo Date: Tue Aug 28 12:05:19 2018 +0900 Remove an unused member QUICTLS::_stateless commit cb79e0257d04b2110dd310d3a1378c99c651e9c2 Author: Masakazu Kitajo Date: Tue Aug 28 11:28:36 2018 +0900 Remove ssl_handle() from QUICHandshake commit 171d0d0a403b2a6f9fb2c8e6e0c9edcf1194b742 Author: Masakazu Kitajo Date: Tue Aug 28 10:56:45 2018 +0900 Cast to appropriate frame type commit 346940d7e15ccd69c20a948476c4386b6b6433b7 Author: Masakazu Kitajo Date: Tue Aug 28 10:32:29 2018 +0900 QUICFrameHandler accepts frame as reference but not as smart pointer commit 6e20b57400f0e48f936185cbdf1833ea5939bea6 Author: Masakazu Kitajo Date: Tue Aug 28 10:31:19 2018 +0900 Remove an unnecessary header inclusion commit d9e1e1a86d83ef6fa50ce1a7b76c96df9e4dbc13 Author: Masaori Koshiba Date: Mon Aug 27 12:57:55 2018 +0900 Remove handshake stream check commit 45dd5aecb93d3a6d809a2ed0ae3f6e7badb0bbec Author: Masaori Koshiba Date: Mon Aug 27 15:34:12 2018 +0900 Fix sending generated STREAM frame commit ea29ae141cc5891b92f87835cc00f5feaf35a790 Author: Masaori Koshiba Date: Mon Aug 27 14:59:27 2018 +0900 Cleanup: Suppress nbytes on debug log if it is INT64_MAX commit 4c0dc178fa412e48dc0a043d0e97df88986a0e4a Author: Masakazu Kitajo Date: Fri Jul 27 12:23:49 2018 +0900 Add tests for QPACK commit e4cbf6b1a984fdd1dfdd1ef3fcf0f90166209bd2 Author: Masakazu Kitajo Date: Tue Jul 24 11:39:36 2018 +0900 ADD QPACK module commit e755cc8500624689612ceccc49ee7ff228b6f834 Author: Masakazu Kitajo Date: Fri Jul 27 12:19:32 2018 +0900 Preserve the prefix bits when encoding integer with XPACK commit 062b8f481c5fe176f3fc73f986a275da7e3a4318 Author: Masakazu Kitajo Date: Mon Jul 23 10:17:58 2018 +0900 Separate out some of HPACK logic into hdrs/ Primitive representations and Huffman encoding can be shared with HPACK and QPACK. commit 8b8d9a043c651d0e5bcd97658b007e6dc02ccb74 Author: Masakazu Kitajo Date: Fri Jul 20 17:20:45 2018 +0900 Add QUICStreamIO::peek commit ea32638a1a0e2f8d52dc0876cace1c929ba67597 Author: Masaori Koshiba Date: Mon Aug 27 12:18:11 2018 +0900 Return STREAM_BLOCKED frame when it is generated commit 125fc326423d1275b1d975cf4484ca22c89968b9 Author: Masaori Koshiba Date: Fri Aug 24 16:29:22 2018 +0900 Generate ACK frame in the end of packetizing frames To set "ack_only" flag correctly. Because any generate_frame() could fail. Also add a counter to interrupt sending STREAM frames to send ACK frame periodically. commit 96c19776113aed9b9017ea4ee228912243087a76 Author: Masaori Koshiba Date: Mon Aug 27 12:05:58 2018 +0900 Make destructor of QUICTransportParameters virtual commit 05417b553944fd5b66f87d709a4a85acb4cae28b Author: Masaori Koshiba Date: Mon Aug 27 11:56:34 2018 +0900 Revert "Fixes memleaking in handshake" This reverts commit 0d5f86e310dcb8cb983dada02d9848b2de78a231. commit 0d5f86e310dcb8cb983dada02d9848b2de78a231 Author: scw00 Date: Fri Aug 24 15:19:25 2018 +0800 Fixes memleaking in handshake commit 9e7e83fbd9c1456e780042113aeb5f85c001d0e6 Author: scw00 Date: Thu Aug 23 15:16:54 2018 +0800 QUIC: fix memleaking in test_QUICFrame commit 7a195464eca6c51c491dfb14d6343e06f863ac46 Author: Masaori Koshiba Date: Thu Aug 23 16:05:16 2018 +0900 Send BLOCKED frame QUICRemoteFlowController::update() create BLOCKED(_STREAM) frame. It will be sent when STREAMs have anything to send. commit 253ee352ede1edd115d59fd1bdd433a94273e1c9 Author: Masaori Koshiba Date: Thu Aug 23 15:47:39 2018 +0900 Cleanup debug logs of Loss Detector commit 0a2fd364d8e3826fa5530760a8eaf03e1008e478 Author: Masaori Koshiba Date: Thu Aug 23 14:58:09 2018 +0900 Cleanup: Remove QUICPacketTransmitter::transmit_packet() commit 486bdb507ac801666534e382e4a2eeebea5188d3 Author: Masaori Koshiba Date: Thu Aug 23 12:13:45 2018 +0900 Do not change state of QUICAckFrameCreator::_should_send flag in QUICAckFrameCreator::_create_ack_frame() It's changed outside of QUICAckFrameCreator::_create_ack_frame() commit 33772c879267bab570eb89d183d24f23028b80ee Author: Masaori Koshiba Date: Thu Aug 23 11:28:02 2018 +0900 Cleanup: Remove tricky QUICNetVConnection::_store_frame() Which is used before draft-13. commit f422468a9b4288c8264803f0327c096e06ca6994 Author: Masaori Koshiba Date: Thu Aug 23 11:02:17 2018 +0900 Fix typo commit 7dadd68544e117f80ea09ab98c15179011880528 Author: Masaori Koshiba Date: Thu Aug 23 11:01:56 2018 +0900 Make QUICFlowController derived class from QUICFrameGenerator commit 49f8be8763e60c2ee4c13c5d10956e2059cc712c Author: Masaori Koshiba Date: Wed Aug 22 16:02:20 2018 +0900 Check frame size when AckFrameCreator/AltConnectionManager/PathValidator generate frame commit cfecee8a4e42b3b7d3d8afaf31970791dd0523cc Author: Masaori Koshiba Date: Wed Aug 22 16:00:23 2018 +0900 Fix MAX_PACKET_OVERHEAD (INITIAL packet has token field from draft-13) commit 45a5cdc92d4733141eeaf258df46c1c362890bb0 Author: Masaori Koshiba Date: Wed Aug 22 12:16:13 2018 +0900 Cleanup: Remove QUICAckFrameCreator::_create_frame() commit 516db1abea0671c6ce18437dd48e1c6016d7c7a8 Author: Masaori Koshiba Date: Wed Aug 22 10:37:24 2018 +0900 Fix unit tests commit 4b13b8c5d2f7d60b44522d43476fe4e77970f54a Author: Masaori Koshiba Date: Tue Aug 21 16:24:29 2018 +0900 Fix retransmittable flag on packetizing frames commit 90e21a372a1a1620b1e53f82be3719662a56916a Author: Masaori Koshiba Date: Mon Aug 20 14:32:19 2018 +0900 Add sageguard on sending packet commit 969ac3dc255b463dd79bd75f3a0eade7b099418f Author: sunwei Date: Sat Aug 4 16:42:26 2018 +0800 process early protected data during handshake commit a9a689061d75b1024bba6eff25170aa1705b8a0e Author: Masaori Koshiba Date: Tue Aug 14 14:47:09 2018 +0900 Check state of handshake before change encryption level To avoid sending CONNECTION_CLOSE (TRANSPORT_PARAMETER_ERROR) on 1-RTT packet when handshake is aborted by TP validation. commit cbc81bcc4aae47bd4c37a28bb4ec74b39c0648e7 Author: scw00 Date: Thu Aug 9 08:45:14 2018 +0800 Release ssl in QUICTLS destructor commit fad804a84d1a45f7ac4f24baea0d5838e15476db Author: Masaori Koshiba Date: Wed Aug 8 16:48:51 2018 +0900 Erase "ack_only" lost packet correctly To avoid heap-use-after-free caused by erasing elements in the loop commit 1fdeea385abbe66f2e2ed051a46a54716e08e038 Author: Masaori Koshiba Date: Wed Aug 8 11:53:36 2018 +0900 Process 0-RTT packet on state_connection_established Some client send STREAM frame on 0-RTT packet after handshake is completed. Need to clarify, but it looks not prohibited in draft-13. commit da7f40841303b9b36617f0f8cc2decd0a2eefee9 Author: Masaori Koshiba Date: Tue Aug 7 15:50:43 2018 +0900 Ignore ZERO_RTT_PROTECTED packet on state_connection_established commit bc126992c7d95d7a28b34e32e9a47f8a87d33984 Author: Masaori Koshiba Date: Tue Aug 7 15:25:00 2018 +0900 Fix test_QUICHandshakeProtocol for OpenSSL with SSL_MODE_QUIC_HACK It looks like OpenSSL with SSL_MODE_QUIC_HACK process only one encryption level on one SSL_do_handshake call. This behaivior might be different for each TLS stack. commit d924cd877b731fcfd5fa283b3efa9f627ce7d35a Author: Masaori Koshiba Date: Tue Aug 7 10:50:19 2018 +0900 Fix test_QUICHandshakeProtocol commit c0f4400280d8ac033c497ab1f598a7526f1aefec Author: Masaori Koshiba Date: Fri Aug 3 16:23:48 2018 +0900 Remove unnecessary checks before generate_frame() calls commit 795ba9b89e788858e470138dfc9958a4f4c37205 Author: Masaori Koshiba Date: Fri Aug 3 14:00:09 2018 +0900 Print maximum_(stream)_data on debug log commit 17e6ab5be1704105eef4a8a721b81cec00d326e0 Author: Masaori Koshiba Date: Fri Aug 3 10:33:18 2018 +0900 Check value of Reason Phrase Length of CONNECTION_CLOSE frame commit 34545229747c748d2b79d44353f1ced3e2a7a769 Author: Masaori Koshiba Date: Fri Aug 3 09:34:15 2018 +0900 [draft-13] Add Frame Type field in CONNECTION_CLOSE frame commit 87526c0d9889f7660a2a416078f7b902607bd568 Author: Masaori Koshiba Date: Wed Aug 1 10:19:03 2018 +0900 [draft-13] Add NEW_TOKEN frame commit 8dcf5b193c3841e75dcf567f685843f1cea1af52 Author: Masaori Koshiba Date: Wed Aug 1 15:59:44 2018 +0900 Fix unit tests except handshake commit 3692c63376d3997a41cb2240a330b0fac413868e Author: Masaori Koshiba Date: Wed Aug 1 15:44:13 2018 +0900 Fix build errors of unit tests commit a1a942be2d650f9d4b5ada00b51dafcf9fd92be9 Author: Masaori Koshiba Date: Wed Aug 1 11:29:11 2018 +0900 Set SSL_MODE_QUIC_HACK To use tatsuhiro-t's custom OpenSSL for QUIC draft-13. https://github.com/tatsuhiro-t/openssl/tree/quic-draft-13 commit ad51d3c286a67cff5a39986e10a5428e6de36f61 Author: Masaori Koshiba Date: Wed Aug 1 11:24:20 2018 +0900 Fix errors caused by rebase quic-d13 branch commit 35cfdca9cf41419aac45e8beeb0de359059222cd Author: Masaori Koshiba Date: Fri Jul 27 16:11:08 2018 +0900 Source addresses verification by receiving messages in HANDSHAKE packet commit 48129f0443e0db9289bfe06dc57d994adb4f1d60 Author: Masaori Koshiba Date: Fri Jul 27 12:33:27 2018 +0900 Print negotiated cipher suite on complete handshake (take 2) commit 45bcdb711391256d8ebafb3801a67ef0c9ca82fc Author: Masaori Koshiba Date: Fri Jul 27 12:32:38 2018 +0900 [draft-13] Add Loss Detections for each Packet Number Space commit 0a1c183e649ecf7b398067c68b8fbdd3815748e9 Author: Masaori Koshiba Date: Thu Jul 26 10:10:07 2018 +0900 Do padding only client INITIAL commit 800e03be68c4016c6dea25c648a6cbe2326ac0da Author: Masaori Koshiba Date: Wed Jul 25 13:32:16 2018 +0900 Fix maximum_frame_size check The maximum_frame_size is maximum size of whole frame include header from draft-13 changes. commit 303879530e324125c6bcb27899953d4e2dac59de Author: Masaori Koshiba Date: Wed Jul 25 11:29:11 2018 +0900 Print negotiated cipher suite on complete handshake commit 9cc24dbfe7fcf8c7b98ee3a2b79447eb3edb3572 Author: Masaori Koshiba Date: Tue Jul 24 16:18:28 2018 +0900 Simplify KeyMaterials in QUICPacketProtection commit f9923621031c9a8fa30082982b62f7719c5cf206 Author: Masaori Koshiba Date: Tue Jul 24 15:56:33 2018 +0900 Cleanup: rename "cleartext" to "initial" No logical changes - QUICKeyPhase::CLEARTEXT -> QUICKeyPhase::INITIAL - QUICKeyPhase::ZERORTT -> QUICKeyPhase::ZERO_RTT - _cleartext_key -> _initial_key - cleartext_secret -> initial_secret commit 9a3606851ed1a66d1506c9ef40ca87416d82301b Author: Masaori Koshiba Date: Tue Jul 24 15:28:35 2018 +0900 Cleanup: remove dead code around key generation commit 8e7a9c13470f1462ae073d317862cf4c7e1196a7 Author: Masaori Koshiba Date: Tue Jul 24 14:41:50 2018 +0900 Cleanup: fix debug messages commit 52024bae302d7e52f138908a86ab03e4630905cd Author: Masaori Koshiba Date: Tue Jul 24 14:28:04 2018 +0900 Fix key_callback for custom OpenSSL commit 321380994940d513252b918f74a6f5dbb72e82e5 Author: Masaori Koshiba Date: Tue Jul 24 14:26:55 2018 +0900 Continue handshake when server reject 0-RTT commit e252c749ae263fef6f2dfa650c08b19dae9b6e0f Author: Masaori Koshiba Date: Tue Jul 24 14:25:30 2018 +0900 Divide _retransmission_frames for each encryption level commit 82d3d6b44d9f63fdec3315ecbe02e39fb021c83d Author: Masaori Koshiba Date: Tue Jul 24 14:19:06 2018 +0900 Support 0-RTT with draft-13 commit 930a6393c71121ae2e923e6dad1720b8455a6e4d Author: Masaori Koshiba Date: Mon Jul 23 15:50:26 2018 +0900 Add debug prints on receiving and transmiting packets commit 120216a41ab37b21f891d8cc64cece75435bf190 Author: Masaori Koshiba Date: Mon Jul 23 15:46:26 2018 +0900 Cleanup: remove QUICHandshakeMsgType commit 85924fdc2d21a1a9569e8995357b951c5b5262bd Author: Masaori Koshiba Date: Mon Jul 23 15:46:05 2018 +0900 Cleanup: remove QUICStreamType::HANDSHAKE commit e523c076588e7632869f6493dbde37e8467d8194 Author: Masaori Koshiba Date: Mon Jul 23 15:25:54 2018 +0900 Fix dcid of INITIAL packet from server commit 6c17894c9db2e1882e1a9cfc9d12e024bc0d0085 Author: Masaori Koshiba Date: Mon Jul 23 12:19:08 2018 +0900 Check buffer length before reading QUICVariableInt commit a75c3eb59feff87f9f70d0fa43fa970484f11ec0 Author: Masaori Koshiba Date: Mon Jul 23 11:01:31 2018 +0900 Check remot transport parameters existence commit 3247788fe7a34f15f4f45c3842fd71c0d5bcde1a Author: Masaori Koshiba Date: Mon Jul 23 10:49:41 2018 +0900 Cleanup debug logs around quic_crypto commit 6f52da485ef6eb995f41035d89848a8ef807d64b Author: Masaori Koshiba Date: Mon Jul 23 10:38:28 2018 +0900 [draft-13] Update QUICTransportParametersHandler::TRANSPORT_PARAMETER_ID commit 5e4e2340ceb056b6eabbb92f538c89ffca9c874f Author: Masaori Koshiba Date: Mon Jul 23 08:44:19 2018 +0900 Set key phase depends on packet type commit 553c45945e627853ff5bbfdf6137b6d82709470e Author: Masaori Koshiba Date: Fri Jul 20 15:10:44 2018 +0900 Change Encryption Level on key callback commit 0b9031939d973ab397560af3dc69dec37aa9508e Author: Masaori Koshiba Date: Fri Jul 20 11:45:45 2018 +0900 Add debug prints of secrets with vv_quic_crypto tag commit 5cb3191e4fb30e32c5b4445e63b4f97a032ed252 Author: Masaori Koshiba Date: Fri Jul 20 11:17:49 2018 +0900 [draft-13] adjust QUICHKDF::expand to use HkdfLabel structure of TLS 1.3 commit 70fcaaed4807ed06a393e2bf4b44c404c5c19874 Author: Masaori Koshiba Date: Thu Jul 19 15:25:54 2018 +0900 [draft-13] Add Token Length and Token fields in INITIAL packet This change follows draft-13 + PR1498. commit c3d9bf142af23d1d2b28719afe397e0b0366f6f7 Author: Masaori Koshiba Date: Thu Jul 19 10:52:30 2018 +0900 Derive traffic keying material from TLS stack This change requires a API to derive traffic keying material on TLS stack. commit 9c30ce7adb2afff33ec20de846a632fdf3844a55 Author: Masaori Koshiba Date: Wed Jul 18 11:13:17 2018 +0900 [draft-13] Bump version commit 812e0b2b392a252d8a6fc6ce5a511003440d9d35 Author: Masaori Koshiba Date: Wed Jul 18 11:10:10 2018 +0900 Add empty check in on_packets_lost to avoid crash commit 451f4ed174c2f3003c99ef0bdcce007864f49fcd Author: Masaori Koshiba Date: Wed Jul 18 11:08:48 2018 +0900 Process INITIAL packet on client side commit 40a36c5711cdc84bff9c771a5ce98317dedd2a4b Author: Masaori Koshiba Date: Wed Jul 18 11:07:50 2018 +0900 Check frame size restrictively on frame generation commit 0ba665bf4cdb5dc36aa108e68f80425fe999d843 Author: Masaori Koshiba Date: Tue Jul 17 12:11:09 2018 +0900 Separate packet number space commit 4229faf002966b631785bf085544656d26309747 Author: Masaori Koshiba Date: Tue Jul 17 12:05:37 2018 +0900 Fix storing frame commit 7ad40959cc37959dd939c4d52c17715157d6ee33 Author: Masaori Koshiba Date: Tue Jul 17 12:04:30 2018 +0900 Add CRYPTO frame in QUICDebugNames commit 7c7223eabdc0e915ac2d54e87180530ad05ea971 Author: Masaori Koshiba Date: Tue Jul 17 11:19:36 2018 +0900 Fix allocating buffer for packet commit ca9b7ffbe561219805861c5cdb5199e64f8cffcd Author: Masaori Koshiba Date: Tue Jul 17 11:17:32 2018 +0900 Call do_handshake() on client side commit 0f2bf0089e67a5c9391a131f72ed71e6ba936f4b Author: Masaori Koshiba Date: Tue Jul 17 11:16:19 2018 +0900 Return empty frame if no bytes to sent commit c243e8c00a75c44673f0e4bff27abf01b224391b Author: Masaori Koshiba Date: Tue Jul 17 10:27:11 2018 +0900 Cleanup: remove QUICHandshakeMsgType commit f90658d97cfb47ca3421a8987a1bd41b222bf92f Author: Masaori Koshiba Date: Tue Jul 10 16:20:54 2018 +0900 Coalescing Packets on sending commit cee6ef4c883af35b286b0f1a170f047dadeef4f0 Author: Masaori Koshiba Date: Tue Jul 10 14:37:32 2018 +0900 Add null check of application commit a80d8f0ae005318a35e5abeb90344aee3abe5483 Author: Masaori Koshiba Date: Tue Jul 10 14:37:03 2018 +0900 Cleanup: remove STREAM_ID_FOR_HANDSHAKE commit 0f6c8e235cfea8c2f7eedc3702271cc51e4b3016 Author: Masaori Koshiba Date: Tue Jul 10 14:28:13 2018 +0900 Make QUICHandshake QUICFrameHandler/Generator instead of QUICApplication commit 1b99a5fbd3c6f938707ee2f4924331206ed4f7b3 Author: Masaori Koshiba Date: Tue Jul 10 14:19:36 2018 +0900 [draft-13] Add QUICCryptoStream commit 2f0757d582b2dd723b74a35ad6ba5bfb1eb84fd6 Author: Masaori Koshiba Date: Tue Jul 10 14:17:12 2018 +0900 [draft-13] Generate ACK frame for each encryption level commit 0712fe5f5b2e845aea7baad8e4a25b2573028374 Author: Masaori Koshiba Date: Tue Jul 10 12:26:58 2018 +0900 [draft-13] Add encryption level to QUICFrameDispatcher/Generator/Handler interfaces commit c9d2f5936b0f3df129257152953fff9fe391e4fd Author: Masaori Koshiba Date: Tue Jul 10 11:34:35 2018 +0900 [draft-13] Add QUICIncomingStreamFrameBuffer and QUICIncomingCryptoFrameBuffer QUICIncomingFrameBuffer is parent class of QUICIncomingStreamFrameBuffer and QUICIncomingCryptoFrameBuffer now. commit 6710bec5e67459e17f8f3b97aa3b3662a6bf0eb1 Author: Masaori Koshiba Date: Tue Jul 10 10:25:20 2018 +0900 [draft-13] Add QUICEncryptionLevel commit 3c6e8bf67556d14bda7158686d7da7fca630be19 Author: Masaori Koshiba Date: Tue Jul 10 10:17:39 2018 +0900 [draft-13] Handshake with raw TLS messages This requires quick hacked custom OpenSSL. When OpenSSL add new APIs to support QUIC (draft-13+), move it. commit 50b85989c9c94cfc1292f86b00846af601f88884 Author: Masaori Koshiba Date: Tue Jul 3 15:49:54 2018 +0900 [draft-13] Add CRYPTO frame commit 38104e981d592bc95271f8fb3eca95cb29b16443 Merge: 636548084 2ded01373 Author: Masaori Koshiba Date: Wed Aug 1 09:58:19 2018 +0900 Merge branch 'asf/master' into quic-latest * asf/master: (44 commits) Updates the default ciphers to avoid weak ciphers(non FS) Fixed Spelling. Removes remnants of dprintf support Change the defauilt connect_ports docs to reflect the code Fixing copy paste error in SNI yaml parsing Removes more references to traffic_cop and cop related functionality Fixes ticket loading from filesystems without a mtime Add support for 'fwd' value to X-Debug header, and move to later hook any deletion of X-Debug header from client request. For PostScript class, remove problematic parameter forwarding to functor (rely on lambda capturing instead). Modifies init script to add start/reload hooks fix another crash on shutdown and add unlikely Revert "Do not do DNS to origin if the object is HIT-STALE and parent exists" Dockerfile for CentOS/Fedora, i.e. yum dependencies Revert "Fix post process to propagate early server response." Revert "Fix post_error test by initializating the address length of the accept call." Revert "Fix crashes from early post return fix." Removes old commented-out code Test: Convert test_Ptr.cc to Catch. TextView: Unit tests for token handling on single characters, and with trim. TS-4765: Removes previously deprecated cqbl and pqbl log tags ... commit 636548084557512da0a7bff9e0f59d85ece9a9a7 Author: Masaori Koshiba Date: Wed Aug 1 09:51:57 2018 +0900 Fix unit test of QUICPakcet commit 45a351251275357365c67fc4bf6c3bf390e03738 Author: Masakazu Kitajo Date: Fri Jul 27 17:26:23 2018 +0900 Add be32toh and htobe32 commit 73c14aa10e29d6161de021c526d6dfd1ef76d00e Author: Masakazu Kitajo Date: Fri Jul 27 12:16:36 2018 +0900 Use refernce instead of shared_ptr while passing around stream frames commit 2e68c1b21d28e0608321ed8ba6bbfdd1f8f0c191 Author: Masakazu Kitajo Date: Fri Jul 27 12:00:17 2018 +0900 fix unit tests commit 97737e6b183ad1f97abdb3e7adb2766c31dbad1d Author: Masakazu Kitajo Date: Sun Jul 15 11:13:42 2018 +0900 Add debug prints commit 4503964ea557ffb9e9a9b7d24a070d1abcb76ff8 Author: Masakazu Kitajo Date: Sun Jul 15 11:12:25 2018 +0900 Check the buffer size before reading a packet number commit 9e23ea5a69bd00ab136ac31a12667c5b219b9a61 Author: Masakazu Kitajo Date: Fri Jul 13 16:21:14 2018 +0900 Fix build issues commit 7b335ee0c8835ee170c172ba5cc0be1b9f2f853b Merge: 747c7419f 46acdc85f Author: Leif Hedstrom Date: Thu Jul 12 19:38:34 2018 -0600 Merge remote-tracking branch 'origin/master' into quic-latest * origin/master: (69 commits) Refresh upstream connection throttling. Reduce lock contention, add maximum count, rate limit alerts. Runroot: add new option to specify layout during creating ASAN: stack-use-after-scope Add flag to enable the reload feature. Disable by default Add support for reloading the lua script in global plugin mode Fix bug on loading of lua script add a reason tag to traffic_control host subcommand. traffic_manager: Cleanup handling of proxy args. traffic_manager: fix --tsArgs to work. Plugin, makefile, readme. Fix RecConfigReadPluginDir and clean up RecCore Remove proxy.config.config_dir from records.config Handle response parsing case where EOF happens before any data arrives. Enforce sphinx>=1.7.5 when building docs IntrusiveDList: Refreshed for C++ eleventy, added const_iterator. Doc: Fix build error in Lua plugin documentation. Fix crashes from early post return fix. Avoid cert callback if no verification is requested. make sure the index stays positive Plugin, makefile, readme and schema ... commit 747c7419f1f5a8b704ef904297f5f1c7532bb18b Author: Masakazu Kitajo Date: Thu Jul 12 10:31:34 2018 +0900 Check buffer length before reading packet length commit 79a5e0708b3b326c5f1cf671b84eaf598c216a2f Author: Masakazu Kitajo Date: Mon Jul 2 13:44:48 2018 +0900 Remove redundant(and wrong) CID output on connection migration commit a1ecf66e6bd971ddf9861a654145cdd05883952f Author: Masakazu Kitajo Date: Thu Jun 28 16:56:59 2018 +0900 Make 0-RTT available again commit 75db12328189875c861eb1f4f54eca265a485f7a Author: Masakazu Kitajo Date: Thu Jun 28 16:56:29 2018 +0900 Add debug prints and assertions commit c11c12e3020e2bf4328c6a6df9a58c93ae7f6390 Author: Masaori Koshiba Date: Tue Jun 26 15:14:53 2018 +0900 Ignore empty stream frames except pure fin stream frame commit 4e2b166c4ae3e20f704d214567017798cf6e5c0d Author: Masaori Koshiba Date: Tue Jun 26 11:37:34 2018 +0900 Cleanup: break down test_libquic into small check programs commit 28970d79c990dc7c0274ff35269ce1fec412cc45 Author: Masaori Koshiba Date: Tue Jun 26 10:59:41 2018 +0900 Add a option to enable connection close excercise commit a114d463a69f664868bc0171e26994ddf9f394d9 Author: Masakazu Kitajo Date: Tue Jun 26 10:39:43 2018 +0900 Fix another bug around PN calculation commit d7f1602163b748295cb33af917ab90d2bd4b1e47 Author: Masakazu Kitajo Date: Mon Jun 25 15:20:50 2018 +0900 Fix bugs around PN encoding commit ef3334b73486bf7409ac73dadd2d465c2a7999c6 Author: Masaori Koshiba Date: Mon Jun 25 12:16:38 2018 +0900 Cleanup: simplify check program of QUIC Before this change, `make distclean` didn't work. Because many files in upper directories are specified in *_SOURCES. commit 6e85c9c8518526f158484f99e34c671a4e1afaa8 Author: Masakazu Kitajo Date: Fri Jun 22 16:10:28 2018 +0900 Fix a bug in the previous commit commit c6db432e5f2a6e5fe003028d1f5325f9974a6f85 Author: Masakazu Kitajo Date: Fri Jun 22 14:41:23 2018 +0900 Small refactoring commit 7034f0d6d35fb72c3eccea6a8d1d2deb560da2e6 Author: Masakazu Kitajo Date: Fri Jun 22 12:06:57 2018 +0900 Fix a build issue on gcc environment commit 30b4edd4c43a2dfdd47455b5f0ea8dd8e1f4c2a0 Author: Masakazu Kitajo Date: Fri Jun 22 10:43:52 2018 +0900 Remove unused variables commit b6e885823118ee95abfaf58c56247ba591ffbe1d Author: Masaori Koshiba Date: Fri Jun 22 10:17:32 2018 +0900 [draft-11] Relax restrictions of QUICTransportParameterId::STATELESS_RESET_TOKEN commit 8fc244e8e30cb4f9ea27865bd571f6f75dd64175 Author: Masaori Koshiba Date: Fri Jun 22 10:05:23 2018 +0900 Make pn_protector const reference commit 64db8f9505b55757726536bd185fa396b55040f6 Author: Masaori Koshiba Date: Fri Jun 22 09:43:28 2018 +0900 Add PNE support to QUICPacketHandlerOut commit f57ac7ce13f259f3231b75e0452ce543ec6a4258 Author: Masaori Koshiba Date: Fri Jun 22 09:32:13 2018 +0900 Fix Version Negotiation Packet handling commit ec666b4f8572af73fbc8f0a3fd9c9739bd85a8d9 Author: Masaori Koshiba Date: Fri Jun 22 09:08:31 2018 +0900 Ignore traffic_quic commit 1d1384a03a937274172a1d11794dffd099691ba4 Author: Masakazu Kitajo Date: Thu Jun 21 15:58:01 2018 +0900 Fix QUICPacketLongHeader::size() and store() commit 75dc251e0bff94b44eb131221c35d85b171c0125 Author: Masakazu Kitajo Date: Thu Jun 21 14:56:20 2018 +0900 clang-format commit c9d5e5404c4af7fa832c226bb9b5a1800424b5c4 Merge: 0e2934c7b 032920ffb Author: Masakazu Kitajo Date: Thu Jun 21 14:55:28 2018 +0900 Merge branch 'master' into quic-latest commit 0e2934c7b9f7219e4462eb4e6490130371b3a8b3 Author: Masakazu Kitajo Date: Thu Jun 21 12:22:21 2018 +0900 Fix a bug in unprotecting PN in short header Incorrect PN offset was used due to CID length difference. commit 609e3e7a416e0f38689363afe05b42ba3e85f29f Author: Masakazu Kitajo Date: Thu Jun 21 11:07:36 2018 +0900 Length of keys for PNE is not a fixed length commit 6751e97d47d4bef227c35ba206ae4466292b1cdc Author: Masakazu Kitajo Date: Thu Jun 21 11:07:08 2018 +0900 Protect sending packet numbers commit 32d43b09212e1de4f2b4f699bf6f0f2c02bb00e6 Author: Masakazu Kitajo Date: Thu Jun 21 11:04:09 2018 +0900 Change payload length field to length field (apart of draft 13) commit 3c73a156a0df9ebf68c43eba5d181500da817eb9 Author: Masakazu Kitajo Date: Thu Jun 21 10:57:08 2018 +0900 Print keys for PNE when keys are updated commit 7b03ca1281046962d086c1b7bd35d4b2f78029e3 Author: Masaori Koshiba Date: Wed Jun 20 15:26:01 2018 +0900 Fix test_QUICFrameDispatcher commit 51fecc1cc714f7a6d34556dd0d3b9c5399d592e5 Author: Masaori Koshiba Date: Wed Jun 20 15:21:15 2018 +0900 Fix unit tests commit 162d6cf735e4d9e97e2f3eb3b11a96cad0978943 Author: Masaori Koshiba Date: Wed Jun 20 15:05:13 2018 +0900 Const Correctness commit 5b0c618f300b277dfbc37de80d17da6bf73a87dc Author: Masaori Koshiba Date: Wed Jun 20 14:54:22 2018 +0900 Print "ack_only" when sending ack only packet commit f7473556c7cf541fff398d1b13197531fb65e715 Author: Masaori Koshiba Date: Wed Jun 20 14:50:50 2018 +0900 [draft-11] Follow pseudocode updates of draft-ietf-quic-recovery commit 41a18db2392dbec17d7cf7a3e8c77e2b47f2c7e2 Author: Masaori Koshiba Date: Wed Jun 20 12:22:25 2018 +0900 Reset CongestionController when QUICNetVC need to discard all transport state commit c753466bb394ea4b25b91b8792020e8fc07c8d6c Author: Masakazu Kitajo Date: Tue Jun 19 17:30:17 2018 +0900 Add QUICPacketNumberProtector commit 9a0f6635e8c251ad7fdb44f7f4b6d90204afeaff Author: Masakazu Kitajo Date: Tue Jun 19 17:27:51 2018 +0900 Add static accessor methods for QUICPacket header fields commit 3ffa6b252cb5d60a2a738648d13ae61676e16673 Author: Masakazu Kitajo Date: Tue Jun 19 17:23:16 2018 +0900 Print keys for packet number encryption commit 499aa73bab749ba1007e4849b1b2b789ebda5138 Author: Masakazu Kitajo Date: Tue Jun 19 17:21:16 2018 +0900 Chaange type of packet number length parameters commit f48b47bdbf22ce37958bf7b23a7df5be6e5357c6 Author: Masaori Koshiba Date: Tue Jun 19 14:59:28 2018 +0900 [draft-11] Add time_of_last_sent_handshake_packet commit 9ba6ff7af72a68b470d361092825dfa196fa64f9 Author: Masaori Koshiba Date: Mon Jun 18 16:17:57 2018 +0900 [draft-11] Initialize min_rtt with INT64_MAX commit c64b47636d9aa4b42b38c56c4dd5fe75fb925d98 Author: Masakazu Kitajo Date: Mon Jun 18 14:12:10 2018 +0900 Fix key pahses for PNE tests commit 4eb37f2589138b8c41c3fbca776c8373d75b75b1 Author: Masakazu Kitajo Date: Mon Jun 18 14:04:29 2018 +0900 Add encrypt_pn() and decrypt_pn() commit 8b852ab59c9a5bb2b74e349f2d0d575248a84d8f Author: Masaori Koshiba Date: Mon Jun 18 11:47:39 2018 +0900 Cleanup: Change QUICTypeUtil::has_long_header(const uint8_t *) to QUICInvariants::is_long_header(const uint8_t *) commit 4e0cbe4324975aaf26fabccd71c613293836ce6a Author: Masaori Koshiba Date: Mon Jun 18 11:40:42 2018 +0900 Cleanup: Remove QUICTypeUtil::has_connection_id(const uint8_t *) The omit_connection_id flag is removed by draft-10, This function doesn't work any more. commit 3c53ce8fb9125361983cbcfb26b5374cc68d22de Author: Masaori Koshiba Date: Mon Jun 18 11:08:31 2018 +0900 Cleanup: remove unused function When ATS support omitting SCID, parsing Short Header packet and QUICConnectionTable structure should be revisited. commit ba07bd2001fd645bdc39ce7f314c34c61c96a2f9 Author: Masaori Koshiba Date: Fri Jun 15 15:35:38 2018 +0900 Cleanup: remove deprecated apis of QUICPacket commit 25fc9182111ddddefa55a750d0514d58b4639919 Author: Masaori Koshiba Date: Fri Jun 15 15:10:30 2018 +0900 Cleanup: Remove QUICPacketFactory::_dcil and QUICPacketShortHeader::_dcil We don't need to track dcil for Short Header packet, becasue it's same to scil in local. In other words, when we need dcil while reading Short Header packet, we should refer it via `QUICConfigParams::scid_len()`; commit ac60e738390cb19871955a3017c1f14bd7cf5297 Author: Masaori Koshiba Date: Fri Jun 15 14:00:04 2018 +0900 Make sure length of scid is 18 bytes commit dbdd3d29115d11d474c3d18c2474c0cd530b5a24 Author: Masaori Koshiba Date: Fri Jun 15 10:25:30 2018 +0900 Check version in QUICPacketHandlerIn commit 648b7cd1073e8a0556d1f2dd98ce5650befde1dd Author: Masaori Koshiba Date: Fri Jun 8 16:12:52 2018 +0900 Fix unit tests commit 43908f1799b291224f2410005f93384b71c91209 Author: Masaori Koshiba Date: Fri Jun 8 15:44:27 2018 +0900 More format debug log commit 02e9022fbd41df5ac365a3e10528b87c0bd7b50a Author: Masaori Koshiba Date: Fri Jun 8 14:28:21 2018 +0900 Fix unit tests commit 3f549f468bff0906230b47395c05eb7a2d8b8beb Author: Masaori Koshiba Date: Fri Jun 8 11:57:04 2018 +0900 Add aead_tag_len in long packet header size calculation commit 8d6359a243896c6d60cfdc512cfbdc3d15b45ae6 Author: Masaori Koshiba Date: Fri Jun 8 11:55:52 2018 +0900 Fix build error commit c345ed74d0bca99a60ae5820103d6095047750b5 Author: Masakazu Kitajo Date: Fri May 25 10:05:02 2018 +0900 Generate a key for packet number protection commit 42b69b76558631b2b344c28b4a0730dcdaec3a16 Author: Masakazu Kitajo Date: Wed May 23 16:59:48 2018 +0900 Packet number gap for connection migration is gone commit 538e78f99d5ab61018f069feb4d4433084949338 Author: Masakazu Kitajo Date: Wed May 23 16:27:30 2018 +0900 Packet number is encoded into the first 1-2 bits of the packet number itself Short Header doesn't have bits anymore. commit bb89b2367a805507ae6060aaf34b229c87211c85 Author: Masakazu Kitajo Date: Wed May 23 14:17:18 2018 +0900 Packet number for Retry packets is always 0 commit f4d4a10ed3708aa4e89a3bb9b32146e24d319cb1 Author: Masakazu Kitajo Date: Wed May 23 12:29:26 2018 +0900 Initial packet number is 0 on draft-12 commit f6139ab26b2cb1531f24723ff9a48cb5636edf5c Author: Masakazu Kitajo Date: Wed May 23 11:41:52 2018 +0900 Check existence of PreferredAddress transsport parameter commit 14643af079b0309d5a283958e4ad556dc7206160 Author: Masakazu Kitajo Date: Tue May 22 17:32:58 2018 +0900 Rename INITIAL_MAX_STREAM_ID_{BIDI,UNI} parameters to INITIAL_MAX_{BIDI,UNI}_STREAMS commit 455c8a920003b425b99e1a5f827e1ccdf7384d0d Author: Masakazu Kitajo Date: Tue May 22 16:50:52 2018 +0900 Update QUIC/HTTP versions commit 5809e30ea8547c2fec2ac4c27bf35e482e03ca88 Author: Masakazu Kitajo Date: Wed Jun 6 11:19:34 2018 +0200 Initialize bidirectional stream state appropriately commit a111b9c585d5fcfb9bdb616fd1206a60d7dc63a7 Author: Masaori Koshiba Date: Wed Jun 6 16:00:47 2018 +0900 Reset QUICPacketRetransmitter when QUICNetVC discards all transport state commit cd041706743783a52144e0bc367e1b75680301aa Author: Masaori Koshiba Date: Wed Jun 6 15:15:30 2018 +0900 Check debug tag before print debug msg of frame commit 0a9fe522731ca820901781b08c6f9f892870e757 Author: Masaori Koshiba Date: Tue Jun 5 21:35:11 2018 +0900 Fix unsupported version packet handling - Do not copy payload, because payload size is unknown - Do not set largest received packet number, because packet number is unknown commit 1c8264b5fd92ab8b446dc2fb1824ddd6a0f86f5e Author: Masakazu Kitajo Date: Tue Jun 5 11:16:20 2018 +0200 Remove redundant remove_connection_ids() call commit e06bd43bcae99fe28a0f484f617d8ee1c0678f24 Author: Masakazu Kitajo Date: Tue Jun 5 11:14:59 2018 +0200 Fix a bug in ConnectionId comparation operators commit 25ab32e5c81ece13de55294776b6521ecea59ad5 Author: Masaori Koshiba Date: Tue Jun 5 16:57:47 2018 +0900 Move SendStreamState Ready when it is created commit 7f2038314bf54739ad498b38afd9f7a29515f4d4 Author: Masaori Koshiba Date: Tue Jun 5 16:02:43 2018 +0900 Check Version Negotiation packet in QUICPacketReceiveQueue::dequeue() To avoid heap-buffer-overflow in long_hdr_pkt_len() commit 7b8f86e28457b96d2aa61e70be0b92bd99a03546 Author: Masakazu Kitajo Date: Mon Jun 4 14:30:31 2018 +0200 Use this->thread instead of this_ethread() On some pathes, this_ethread() can be UDP thread, and it shouldn't be used. commit aea857dc20a372e72c769b8381dbb764bf233e15 Author: Masaori Koshiba Date: Mon Jun 4 21:02:13 2018 +0900 Add a validation of payload length of Long Header Packet commit e77ff1790167cb6dde479b4e81bc974a1e88729d Merge: c3d89e4e5 1c402972c Author: Leif Hedstrom Date: Mon Jun 4 09:50:24 2018 +0200 Merge branch 'master' into quic-latest * master: (25 commits) Removes obsolete ja traffic_line translations Removes more references to clustering Correct the number of available disk shows in fatal message Updates documentation for gzip/compress renaming Removes the documentation for the epic plugin Clang-Analyzer: Fix false positive in IntrusivePtr. Related to 40f01aa0c4fb2ab72459dcc16d63989f40bb8b9b. tsconfig/IntrusivePtr refresh - updated for eleventy and better conformance with std::shared_ptr. Update memory efficient string_view to traffic_layout related codes Update libexecdir of Layout GNU from config.layout Allow disable throttling. Log the errno from the connect failure. Only increment currently_open if socket connects (take 2) header age not critical to HSTS header test result. doc: fixes some example configurations in the plugins docs In the non-transparent case handle EADDRNOTAVAIL as a normal connect fail. doc: Fixes some broken macro usages traffic_crashlog can now terminate itself when using custom startup script Only increment currently_open count if FD is really open Make ATS buildable with BoringSSL again Adds log field for "reason phrase" returned with status codes ... commit c3d89e4e5299d29be1049ed9a3725f998ac8077a Author: Masakazu Kitajo Date: Fri Jun 1 22:34:42 2018 +0900 maximum_frame_size is for STREAM data size commit e8bb31603624a47a24b43bab45194c9dfe6f413e Author: Masakazu Kitajo Date: Fri Jun 1 22:05:59 2018 +0900 Fix a bug that bytes_in_flight is not decreased when retransmit HANDSHAKE packets commit d4e34a5bdb66934ba4ad7f1cfc488499e9687609 Author: Masakazu Kitajo Date: Fri Jun 1 21:20:22 2018 +0900 Fixs QUICNewConnectionIdFrame::clone() A wrong frame was created. commit 06bdc448840c08e611e776408c1488cd4276c919 Author: Masakazu Kitajo Date: Fri Jun 1 20:02:41 2018 +0900 Improve log output from LossDetector commit 79ee060bd1cd7178944208884fff26e89ba01031 Author: Masakazu Kitajo Date: Fri Jun 1 19:59:05 2018 +0900 Fix QUICFrameFactory::split_fram The new frame was not used. commit 94d86f4f899ad3a34179b920ae350cf821e6add5 Author: Masakazu Kitajo Date: Fri Jun 1 16:55:42 2018 +0900 Maybe we should not retransmit PATH_CHALLENGE frames commit 8d56b73aad6d58c164aa93f8131f9032ceb32377 Author: Masaori Koshiba Date: Fri Jun 1 16:26:05 2018 +0900 Print "[dcid-scid]" in debug log commit 2ccd112e536e346aac9c52efd78dd198c834d39c Author: Masaori Koshiba Date: Fri Jun 1 14:40:21 2018 +0900 Print full connection id only if QUIC_DEBUG_TAG is set commit 939cdfe2169633f7bd4f3a3aad17f710eb2508c1 Author: Masakazu Kitajo Date: Fri Jun 1 14:10:57 2018 +0900 Add QUICTransferProgressProvider.h commit 047a258b8c93e940110b1aab6cddc2bde45417a3 Author: Masakazu Kitajo Date: Fri Jun 1 14:09:25 2018 +0900 Added TransferProgressProvider commit cc2060720722482d44e812475f65e56d85f4d21e Author: Masaori Koshiba Date: Fri Jun 1 12:40:08 2018 +0900 Format debug log on changing connection id commit 97fa0b3ac1c9052333cc6f21331f8aae69920af0 Author: Masaori Koshiba Date: Fri Jun 1 08:31:28 2018 +0900 Print full connection id commit 38f6a321bc70e7ba98874150f98d168e3c29b659 Author: Masakazu Kitajo Date: Thu May 31 16:37:59 2018 +0900 add some const commit f6d2922fe074d14fff7eb57e249367974e8cd782 Author: Masakazu Kitajo Date: Thu May 31 16:09:04 2018 +0900 Print bidirectional stream state appropriately commit 27a2f3069ada506d473d65753c9decedba47c103 Author: Masakazu Kitajo Date: Thu May 31 15:15:37 2018 +0900 Update stream state machine commit 93edda2ab4d5e99780d812550a6fed0e320a1a50 Author: Masaori Koshiba Date: Thu May 31 14:41:46 2018 +0900 Cleanup: remove NetVConnectionContext_t if it could be referred by QUICConnectionInfoProvider::direction() commit 4712f8a260aefb3e2c0a2c21af98074660d18650 Author: Masaori Koshiba Date: Thu May 31 10:34:14 2018 +0900 Refer QUICConnectionInfoProviter when print debug log commit 976cec2ac7fc7932a0890ab8decdd532a7c1f3b5 Author: Masaori Koshiba Date: Tue May 29 14:05:41 2018 +0900 Print dcid and scid in debug log to identify quic connection Only change logs in QUICNetVConnection. Other components will be fixed. commit e2c5a0fdf4762592fec406eff9cc89ec79567596 Author: Masaori Koshiba Date: Thu May 31 10:59:58 2018 +0900 Rename QUICConnectionInfo QUICConnectionInfoProvider commit 997729b69d43de6bf3d39ff28ae672161a562a0c Author: Masaori Koshiba Date: Thu May 31 11:35:37 2018 +0900 Fix deleter of QUICPathResponseFrame unique pointer commit 4c2db196bb1ff9f24bab5c2921b5c98a7ffe4d27 Author: Masakazu Kitajo Date: Wed May 30 12:17:48 2018 +0900 Prevent logging "not ready" when no packets are in queue commit 7a605d51ecc39926774ed3ec215b38e58ed5c6a0 Author: Masakazu Kitajo Date: Wed May 30 12:03:04 2018 +0900 Clone frames when retransmit them Actual buffers were released right after scheduling retransmission commit 8fe569ada69e6f39838982815b163e4535cb6fa9 Author: Masakazu Kitajo Date: Wed May 30 11:58:51 2018 +0900 Fix a condition for turining fin flag on commit ab0e46da98251e7ee35c4ef407746153ea072505 Author: Masaori Koshiba Date: Tue May 29 16:12:54 2018 +0900 Cleanup: move read-only QUICConnection APIs into QUICConnectionInfo commit 43d0c00505a766860392f0a58737f5a93c29de4a Author: Masaori Koshiba Date: Tue May 29 15:16:00 2018 +0900 Start new path validation on only server side commit 90d5b413337f67dfe88dde3839fdefbb118cb986 Author: Masakazu Kitajo Date: Tue May 29 15:06:35 2018 +0900 Connection credit can be bigger than 16bit commit ffc2e7eeac820f2cf0e1c28f687b3c5188094f42 Author: Masakazu Kitajo Date: Tue May 29 15:04:34 2018 +0900 RetransmissionFrame should return a type of frame that have commit 50bab2eb5288f496b5fabd7c58be9ff34227d5f1 Author: Masakazu Kitajo Date: Tue May 29 12:24:35 2018 +0900 Do nothing if retransmission_frames is empty commit a32e30d333d87560f06c753755f95427d23a0b62 Author: Masakazu Kitajo Date: Tue May 29 12:02:37 2018 +0900 Add debug string for PATH_VALIDATION_TIMEOUT commit ff45ebfc370e11fa498224f1c6eb1c6d67cdf336 Author: Masakazu Kitajo Date: Tue May 29 11:32:15 2018 +0900 Send 3 Handshake packets at maximum without path validation commit c941afc59e562eb1c60c311167513a3d345b3f60 Author: scw00 Date: Mon May 28 15:36:19 2018 +0800 [QUIC] Splits Retransmit frame which exceed limit commit 0fe2c9d808580a66eeaa07b29cf8ca9fad547df8 Author: scw00 Date: Mon May 28 15:33:01 2018 +0800 [QUIC] split retransmit frame commit fd837f81d9ab969b4202b805d370288e0ef75143 Author: Masakazu Kitajo Date: Mon May 28 12:07:54 2018 +0900 Send PATH_CHALLENGE at the beginning on server side commit a881476ddd08d321f7f5e758c9117dd5fe1bddf4 Author: Masakazu Kitajo Date: Mon May 28 10:39:23 2018 +0900 Update code for boringssl Still cannot compile. Don't see API for setting max_early_data commit 4748395981d4171f2ed7af247aa095354ef557a6 Author: Masakazu Kitajo Date: Fri May 25 16:34:52 2018 +0900 3733 commit 26a4682b8028deaf663c38b3e5c854aebd961cfd Author: Masaori Koshiba Date: Fri May 25 15:07:22 2018 +0900 Add debug message of QUICRetransmissionFrame commit 73e3f9cc0eb4e866717d8c0319647cfc2305adbf Author: Masaori Koshiba Date: Fri May 25 12:31:56 2018 +0900 Ignore VN when version is already negotiated commit f045cc7797ea8846b5e376832267f5fc8bdd7b81 Author: Masaori Koshiba Date: Fri May 25 09:21:15 2018 +0900 Remove prepended "_" from public member of VIO in QUIC commit 6a067ec09ed860732bfc7e46ced9e80bd65ece03 Merge: 7b3a66fda de8a4ad76 Author: Masaori Koshiba Date: Fri May 25 09:02:51 2018 +0900 Merge branch 'asf/master' into quic-latest * asf/master: Fix dicarding active lock in CPPAPI Async Timer. Two more places to check whether attempting half_closed connection logic is feasible. TS-2365: Fix crash in tls dynamic record size Fixed clang-analyzer issue with dead increment in wccp Remove prepended "_" from public member of VIO Fixed clang-analyzer issue with memory leak in LogObject Fixed clang-analyzer issue with memory leak in LogHostList Fixes compiler flags used for compress plugin Removes the remaining vestiges of log_hosts.config support TSHttpSsnIdGet() in lua plugin Adding delay to fix race conditions Fixed clang-analyzer issue with null pointer in BufferWriter Fixed clang-analyzer issue with null pointer in URL Doc: remove cluster references in traffic_ctl Support for clang-analyzer to use Fedora 29 packages CacheTool: Fix Unmarshal code and check for loops while scanning Log the full ATS version to traffic.out on startup. Adding missing string_view include, hopefully makes Coverity happy Promote ts_lua plugin to stable Replaces the old metrics.config with a simple C++ class commit 7b3a66fda73336af6fa47b3c46080bd8756e8510 Author: Masaori Koshiba Date: Wed May 23 16:14:59 2018 +0900 Set nbytes when total stream data length become clear To indicate FIN flag beween QUICStream and QUICApplication. This is similar to processing chunked transfer encoding. Mark VIO wrapper apis in QUICStreamIO as deprecated. QUICApplications should use read/write/reenable apis. commit 433fa5b6a9e2ebab48dd71de5560fe988013ebae Author: Masakazu Kitajo Date: Tue May 22 16:48:58 2018 +0900 Fix tests commit a0f579e882224e8945b29f898b83fe900250512a Author: Masaori Koshiba Date: Mon May 21 14:29:18 2018 +0900 Fix debug message when storing frame commit cc560cf271f0f6f0cadd56f6b992ee24d48c0807 Author: Masaori Koshiba Date: Mon May 21 14:03:47 2018 +0900 Set ALPN ext for quic client commit e1cc18fbc445d36d27df39acc1adf7e76b904713 Author: scw00 Date: Wed May 16 15:09:41 2018 +0800 [QUIC] Fixs the compiler error with c++17 commit 2391a97e2ffa7f815d71e0e614cb4fd9ee1c8201 Author: scw00 Date: Wed May 16 08:12:32 2018 +0800 [QUIC] Limits the size when store frame into buffer commit 87370f98ecdaec37a53dcb15ee669c83e7922a72 Author: Masaori Koshiba Date: Mon May 21 10:13:16 2018 +0900 clang-format commit b42f7d27467c6e56be9ae8f9664a55e733e7f38d Author: Masaori Koshiba Date: Mon May 21 09:53:09 2018 +0900 Replace ts::string_view with std::string_view in QUIC commit 6d1a8eeb03eb3f4d77d903f2593d1fc4c896cf59 Merge: 57cf4157f 6932805c5 Author: Masaori Koshiba Date: Mon May 21 09:35:41 2018 +0900 Merge branch 'asf/master' into quic-latest * asf/master: (40 commits) WCCP: Fix issues with gcc8. For TS remap API functions, elaborate on mutual exclusion in the documentation. dump... add -F option Fix linkage of traffic_manager with WCCP due to removal of metric library. Editor: Fix CMakeLists.txt to handle escalate plugin promotion. Doc: Minor tweaks to clear up doc build errors. Promotes cache_promote plugin to stable This adds a new callback to remap APIs: TSRemapConfigReload Restores the expected non-tab indentations Promote the Escalate plugin to stable Uses known port, not rfc6890 loopback ip Fix documentation regarding string_view Replace ts::string_view with std::string_view Fixed memory leak in transform add test, found with clang-analyzer Removes all the metrics.config related stuff Adds a couple of more include dirs to vscode Remove TSHttpSsnConnectionGet which seems redundant. IPv6 for microserver and microDNS Allow better logging by using TSHttpConnectWithPluginId ... Conflicts: .gitignore lib/ts/Makefile.am lib/ts/ink_inet.cc lib/ts/ink_inet.h lib/tsconfig/TsConfigLua.h commit 57cf4157f3504ee9264786e52bd05d262cdc833b Author: Masaori Koshiba Date: Fri May 18 14:37:55 2018 +0900 Make PATH_CHALLENGE/RESPONSE frame unprotected PATH_CHALLENGE/RESPONSE frame could be in HANDSHAKE packet. commit ac6a84d294f6177ca9ebb882475017183ee056da Author: Masakazu Kitajo Date: Fri May 18 11:33:21 2018 +0900 draft-11 uses 2 bits but not 3 for short header type field commit 6d8574a764f2f668483956bffedf033321389c9c Author: Masakazu Kitajo Date: Fri May 18 09:00:53 2018 +0900 Doesn't count frame header size Offset was calculated with header size when update conection level flow control credit. commit bec0f225b593fe6c499307b626460e3758091229 Author: Masakazu Kitajo Date: Thu May 17 22:22:59 2018 +0900 Fix a buffer overflow commit 0904ef1abe0950700a68bcfc08ed742d1109ecf8 Author: Masaori Koshiba Date: Thu May 17 21:28:42 2018 +0900 Add Coaslescing Packets Support commit 77a09fbda3d836a0df8770371a00462174b965fc Author: Masaori Koshiba Date: Thu May 17 15:03:56 2018 +0900 Do not update peer connection id when src cid is zero commit 7268ee4916a7508f9ced4a81185ae1326ca79739 Author: Masaori Koshiba Date: Thu May 17 14:12:24 2018 +0900 Add nullptr check in QUICPacketReceiveQueue::dequeue() commit 5624d2365f2d2b301086826d6f84c0db815a7e16 Author: Masaori Koshiba Date: Thu May 17 14:10:50 2018 +0900 QUIC Client: Set peer connection id commit 4b3d12790c5866abe2eba0772bb9441a0aec8e6f Author: Masaori Koshiba Date: Thu May 17 11:29:16 2018 +0900 Fix nullptr dereference commit ba77c64514e9d585ed68c7c3251fe131af8c56c8 Author: Masakazu Kitajo Date: Thu May 17 10:28:14 2018 +0900 Add AL header commit df23d49602dbef944a22a4a67147648b8ba3dff9 Author: Masakazu Kitajo Date: Thu May 17 10:25:20 2018 +0900 Update largest received packet number The update logic was accidentally lost with 7cb18b38d88fd6af13eed65867701117cbc890ae commit 041f420e53290181e841fbbb61e09610d8f40c44 Author: Masakazu Kitajo Date: Wed May 16 17:35:13 2018 +0900 Print lower 64 bits of source CID at maximum commit 7cb18b38d88fd6af13eed65867701117cbc890ae Author: Masakazu Kitajo Date: Tue May 15 16:40:57 2018 +0900 Capsulate how to read QUIC packets from UDP packets To prepare for coalescing packets commit 7e738b00efd8e3ac73cd87a6461c2560c237d681 Author: Masakazu Kitajo Date: Tue May 15 16:38:04 2018 +0900 Fix a compile issue commit b64517ca7a70dbb53413975851b8e957f7a14bf1 Author: scw00 Date: Tue May 15 08:35:39 2018 +0800 Fix assertion in quic_client commit 2cb99d218a84453a488a02c9f7406a4d87d1523c Author: Masakazu Kitajo Date: Mon May 14 14:07:48 2018 +0900 Fix a heap-use-after-free commit 7770308bad1314cb6b49a5f137ee729d5dc89162 Author: Masakazu Kitajo Date: Mon May 14 10:30:25 2018 +0900 clang-format commit df98d80f2816ab2883af59dc4534fae89d45ef5e Merge: 85fb9f6b4 d4557f160 Author: Masakazu Kitajo Date: Mon May 14 10:23:01 2018 +0900 Merge branch 'master' into quic-latest commit 85fb9f6b44feca16395c77d88d84051c86454835 Author: Masakazu Kitajo Date: Fri Apr 27 10:33:33 2018 +0900 Add a configuration for QUIC connection migration quic.max_connection_ids commit adeaf3c131a39ddc29e383cd3d721589a7901fa7 Author: Masakazu Kitajo Date: Thu Apr 26 16:46:52 2018 +0900 Long header now has payload length and it will be 16 bytes larger when the packet is encrypted commit 89a5830c6979906819d5a0fdee93201d9ece9cc6 Author: Masakazu Kitajo Date: Thu Apr 26 15:40:56 2018 +0900 Fix bugs around packet headers commit f4401f04d2c04cd89af01593248eff16e08346cc Author: Masakazu Kitajo Date: Wed Apr 25 16:44:44 2018 +0900 INITIAL_MAX_STREAM_ID_* are 16 bits commit 2cbe172c948986408a5b7cf68b21f80839c74834 Author: Masakazu Kitajo Date: Wed Apr 25 15:53:45 2018 +0900 Keep payload size under PMTU - overheads commit 2feed67598e11a9b73b182f2abb30721a0a220cb Author: Masaori Koshiba Date: Wed Apr 25 14:46:39 2018 +0900 Fix storing Version Negotiation Packet and unit tests commit 8a04c3b583acf7a3b2c9925f411ffe0bd26c8e8d Author: Masaori Koshiba Date: Wed Apr 25 14:45:35 2018 +0900 Fix unit test build commit 910e021064be3d0559ae0f9f39244b353e4fdd0f Author: Masaori Koshiba Date: Wed Apr 25 11:06:59 2018 +0900 Move vc event logs to verbose debug logs commit 912ecb21979ddb585c21cd77ddac9c6b0e90e59d Author: Masakazu Kitajo Date: Tue Apr 24 15:55:51 2018 +0900 Update Transport Parameters for draft-11 commit 0509f141fe02183ed2e97603902aa35ea834ae85 Author: Masakazu Kitajo Date: Mon Apr 23 16:31:11 2018 +0900 Fix bugs around Connection ID commit 0efaaa386e893cf302debe845154ba6cbd7fae56 Author: Masakazu Kitajo Date: Mon Apr 23 14:44:09 2018 +0900 Update wire formats and tests commit adf54ea540029f6e87c6be3c2af3f428c7491b54 Author: Masakazu Kitajo Date: Fri Apr 20 10:36:54 2018 +0900 Make tests compilable commit 11e0bae268895e7739fcdeff2c9f8d9b495a3355 Author: Masakazu Kitajo Date: Wed Apr 18 13:25:01 2018 +0900 Update QUIC version literals to 11 commit cbc48bcae3f6be1febde6b38b9723233bfe596aa Author: Masakazu Kitajo Date: Fri Apr 6 10:45:59 2018 +0900 Make ConnectionId width flexible commit 84c5fd35027ab50478b9ffe5ac8118b312f3d343 Merge: 4589908ee 26da8b976 Author: Leif Hedstrom Date: Wed May 2 20:55:24 2018 -0600 Merge branch 'master' into quic-latest * master: downgrade 'previously indexed' cert warning to debug Ran clang-tidy with modernize-deprecated-headers Ignore temporary clang files Convert 'gzip' Au test case to 'compress'. Test: autest exercising cache-control directives Ran clang-tidy with modernize-use-override Only check for final C++ standards Conflicts: iocore/net/P_UnixUDPConnection.h commit 4589908eee4863c0b33339283d3856024ab12e1f Author: Leif Hedstrom Date: Sun Apr 29 13:28:10 2018 -0600 Fioxes build issues due to extraneous tab in Makefile commit d8feb83a9d39d269a06717e708b419ebd190c2b9 Author: Leif Hedstrom Date: Sun Apr 29 13:18:29 2018 -0600 Updated to new clang-format commit 47b1d724279e4453a4a3d7c79c35ad55bcb96ea7 Merge: 98ebf16b3 20e83321f Author: Leif Hedstrom Date: Sun Apr 29 13:17:35 2018 -0600 Merge remote-tracking branch 'origin/master' into quic-latest * origin/master: (76 commits) traffic_layout fix option in verify added Add ssn and txn tests for HTTP2 don't need negative events now API for setting OCSP Callback Removes remnants of streams cache log actual SSL error string on failure opening connection preserve whitespace when reading/writing records.config Renames gzip plugin to compress Updates editorconfig files & normalizes white-space Update traffic_layout runroot commands and switches fixes build on linunx/clang/libc++ combo Remove unused TSEvent parameters from C++ API Continuation class. doc/admin-guide: minor fixes Move inline functions into class Doc: various fixes Doc: Unify ":unit:" to ":units". Fix TSVConnArg error. Adds a basic .editconfig, should follow clang-format rules Updated to new version of clang-format Cleans up some pylint issues in the python code Correct parameter for certificate verification ... Conflicts: iocore/net/I_NetProcessor.h iocore/net/P_UnixNetVConnection.h lib/ts/ink_endian.h mgmt/RecordsConfig.cc proxy/Makefile.am proxy/http/Makefile.am commit 98ebf16b32524dea30b042d9b823c3cb98bd5044 Author: Masaori Koshiba Date: Mon Apr 23 16:20:27 2018 +0900 Add "output" option to quic client STDOUT is default destination. commit 36b9f22cb5d0d97f6f73a9a86f857bb80c8a8b76 Author: Masaori Koshiba Date: Mon Apr 23 16:19:27 2018 +0900 Add QUICClientConfig commit 5beddd6d1e29ceaff9c488f00949dab17f28ab48 Author: Masaori Koshiba Date: Fri Apr 20 15:29:17 2018 +0900 Print appropriate frame info in debug log commit 10540fb9a42b6426835ebdb26ee468ac4df9ad95 Author: Masaori Koshiba Date: Fri Apr 20 14:16:17 2018 +0900 Set FIN bit to request from quic client commit 59543aacaf5a9235dfce88256a68f5992d2e5e60 Author: Masakazu Kitajo Date: Fri Apr 20 12:24:43 2018 +0900 Fix unit tests commit 6212927aa508e8c365a405e7e0e2db47547f54a5 Author: Masaori Koshiba Date: Fri Apr 13 15:55:58 2018 +0900 Add HTTP/0.9 exchange support on quic client Out of scope: starting session or transaction of HTTP/QUIC commit 643f7420aeedea9602c5f941df0ca443c30721d6 Author: Masaori Koshiba Date: Fri Apr 13 15:40:55 2018 +0900 Add create_uni/bidi_stream to QUICStreamManager commit e1e5e86f896bae893a6507dc5f1df61d8e37e8b3 Author: Masaori Koshiba Date: Fri Apr 13 15:39:40 2018 +0900 Rename QUICConnection in QUICApplication commit fadeb545dff56497db8cfb7e3a1448ada881698b Author: scw00 Date: Fri Apr 13 08:31:27 2018 +0800 QUIC: Adding total offset in Stream Manager commit 13fd1491205d3d3f00f59c59feab7e593dfccee6 Author: scw00 Date: Thu Apr 12 19:17:07 2018 +0800 QUIC: make ack creator derive from QUICFrameGenerator commit 3dddd89f1be2c032774c67e9e1efb3cfd1cc5918 Author: Masakazu Kitajo Date: Thu Apr 12 16:10:39 2018 +0900 Make QUICClient::start an event handler commit b118034bd4da5bbe0338578ee14b02bcfb1add8a Author: scw00 Date: Wed Apr 11 11:08:41 2018 +0800 QUIC: Send MAX_XXX_FRAME 2 rtt before sender to get blocked commit d75edbd523c096472ce8867c79d34121e0899976 Author: Masakazu Kitajo Date: Wed Apr 11 14:51:38 2018 +0900 Explicitly specify max_stream_data in tests The default value was changed to 0 from INT64_MAX commit 49c465c225c5d8a74e1a81b49f980d4bb021b171 Author: Masakazu Kitajo Date: Wed Apr 11 12:18:02 2018 +0900 Fix flow control on 0-RTT scenario commit 0961c2af562a5fdc501890cda07cdb21dad7a61a Author: Masakazu Kitajo Date: Tue Apr 10 16:05:41 2018 +0900 Include memory in ink_memory.h commit cf3c8e54af3a7a88c6f4abc11ff67235bbf96687 Author: Masakazu Kitajo Date: Tue Apr 10 15:41:59 2018 +0900 Send data from stream 0 first commit 084ad245364f9137e667ad7baf6a2d3116204397 Author: Masaori Koshiba Date: Tue Apr 10 15:31:25 2018 +0900 Send cross thread event to the right thread on HQTransaction commit ef8f9768d26f40534354c1d9d22b3b11d3bb44e4 Author: Masaori Koshiba Date: Tue Apr 10 14:56:19 2018 +0900 Limit the number of sending packets on closing state commit 71a69310413b5137f4d54d97cb0f5061447ba056 Author: Masaori Koshiba Date: Tue Apr 10 12:09:24 2018 +0900 Send CONNECTION/APPLICATION_CLOSE frame commit 6e1e41bd83461d00f379a49bc860c6c787485f5a Author: Masakazu Kitajo Date: Tue Apr 10 11:58:32 2018 +0900 Range of initial packet number is between 0 and 2^32 - 1025 (inclusive) commit 9e0712024c3b5611007f084f8b5e2ef58be6be02 Author: Masakazu Kitajo Date: Tue Apr 10 11:11:04 2018 +0900 Respond to a STOP_SENDING with a RST_STREAM commit 55343ae365217077090580c20e9a272b481ba654 Merge: 5fa569877 7d91d1bbb Author: Masakazu Kitajo Date: Mon Apr 9 20:34:23 2018 +0900 Merge branch 'master' into quic-latest Conflicts: proxy/Main.cc commit 5fa569877e56cdddb0bae36effd61140b3218809 Author: Masakazu Kitajo Date: Mon Apr 9 17:02:57 2018 +0900 Send PATH_CHALLENGE when receives a packet with an alternative CID commit 20e97be78d145efbf8eca927add0684332767a81 Author: Masakazu Kitajo Date: Mon Apr 9 12:11:58 2018 +0900 Set new endpoint to QNetVC when connection migration happens commit 80763750fcfedf5ad2e8257549a67c2373e14ea4 Author: Masakazu Kitajo Date: Thu Apr 5 14:18:35 2018 +0900 Add Path Validator commit 4a3ea0e945557f3934f23d683bb586074dc511d3 Author: Masakazu Kitajo Date: Wed Apr 4 14:14:03 2018 +0900 Catch up draft-10 commit a34cac14b4bd89e83efd9fbcee1401a85a865e11 Author: Masaori Koshiba Date: Thu Apr 5 16:06:49 2018 +0900 Set inactivity timeout in/out appropriately commit 308d5794a8f6f225c02eac52e9fb11e1b047045b Author: Masaori Koshiba Date: Thu Apr 5 15:31:43 2018 +0900 Randomize Connection ID only if needed - take 2 Connection ID should not be changed if INITIAL packet is retransmitted commit 375fe44b33f207e1ae6c24df8fe4656ee3c53cb6 Author: Masaori Koshiba Date: Thu Apr 5 14:45:32 2018 +0900 Randomize Connection ID only if needed Connection id was rondomized every time when state_handshake reveiced QUIC_EVENT_PACKET_WRITE_READY event. commit 864da3bde6765bc17943d8654010dd02635bc70c Author: Masaori Koshiba Date: Thu Apr 5 12:19:06 2018 +0900 Fix QUICHandshake::is_version_negotiated() To do not send ACK frame if VRESION NEGOTIATION packet was sent. commit 5c7db9f3f6a9b68954798d07acb7a90eaff2dca5 Author: Masaori Koshiba Date: Thu Apr 5 11:08:26 2018 +0900 Start handshake over when qvc received VERSION NEGOTIATION packet commit 6ff1a3f373f7af9339285c6f4b96d5636ca8912c Author: Masaori Koshiba Date: Wed Apr 4 15:15:47 2018 +0900 Add Version Negotiation support on QUIC client To enforce version negotiation exercise, set below config 1. ``` proxy.config.quic.client.vn_exercise_enabled ``` commit 528f1ee56b7857c716fd74352b4ba04f19772027 Author: Masaori Koshiba Date: Wed Apr 4 15:06:13 2018 +0900 Add VERSION_NEGOTIATION packet support to QUICVersionNegotiator commit b778c96d2cf62791168f9ac885761e79e380c090 Author: Masaori Koshiba Date: Wed Apr 4 14:58:13 2018 +0900 Remove version args from create_initial_packet To use PacketFactory's _version like other create_X_packet functions commit fc1bc3600758cd0c712788896271db946dec669a Author: Masaori Koshiba Date: Wed Apr 4 14:28:43 2018 +0900 Fix QUICPacketFactory for Version Negotiation Packet commit 8185c20aad939cfe0187e1b7b711bd973e4b60e1 Author: Masaori Koshiba Date: Mon Apr 2 15:18:56 2018 +0900 Add nullptr check before calling QUICConnectionTable::erase() QUICNetVConnection::_ctable is nullptr when QVC is out going connection commit eb6b247130413a91ac816a4fa0b6174953c064b9 Author: Masaori Koshiba Date: Mon Apr 2 14:52:04 2018 +0900 Make constans of Congestion Control configurable Add below configs ``` proxy.config.quic.congestion_control.default_mss proxy.config.quic.congestion_control.initial_window_scale proxy.config.quic.congestion_control.minimum_window_scale proxy.config.quic.congestion_control.loss_reduction_factor ``` commit 90d86680169a72c94235e3591a9d72d8eb1ff467 Author: scw00 Date: Sat Mar 31 10:10:02 2018 +0800 QUIC: Check congestion window when sending packet commit de061414396063f85aa09a31b7a10fd60104546b Author: scw00 Date: Mon Apr 2 11:34:41 2018 +0800 QUIC: Fix clang warning commit b49f6c8784458d5b60762ffe6a2d113495126c32 Author: scw00 Date: Sat Mar 31 10:55:42 2018 +0800 QUIC: Change QUIC closing period dynamically commit 108c33a2919cc0a010df11ccbd8cbd60b3750eaf Author: Masaori Koshiba Date: Mon Apr 2 11:57:09 2018 +0900 Minor cleanup: rename "Client Initial" to "Initial" commit 6726567e60c83efb1605bbf6a3ddb200a3ca09de Author: Masaori Koshiba Date: Mon Apr 2 11:29:24 2018 +0900 Check every stream in QUICStreamManager::will_generate_frame() QUICStream might be in the stream_list, if it doesn't have any data to send. commit 5b6864516badc0b60b8d89737ccc809c72439e51 Author: Masaori Koshiba Date: Mon Apr 2 11:08:30 2018 +0900 Schedule QUIC_EVENT_PACKET_WRITE_READY in connectUp commit cef711994b9b676f1713bf4e79419d8f1c3a40fc Author: scw00 Date: Tue Feb 20 11:26:38 2018 +0800 add refcount to protect qvc commit bb333b9379f8579f918bf940542b8e290615b34b Author: scw00 Date: Sat Mar 31 08:32:52 2018 +0800 QUIC: Fix compiler warning commit 567f567ee4a33aadeb6156d6e8d890203b7f13f9 Author: Masaori Koshiba Date: Fri Mar 30 15:56:04 2018 +0900 Cleanup Makefile.am of unit test for QUIC commit e7babfd06a1db965415110b2df0052cc1f039b88 Author: Masaori Koshiba Date: Fri Mar 30 14:44:03 2018 +0900 Initialize RecordsConfig and QUICConfig on unit test - Load configs written in mgmt/RecordsConfig.cc - Start QUICConfig up commit 5c2a52c0b4323e4e4d4c5731cd5ce40caff95c58 Author: Masaori Koshiba Date: Fri Mar 30 14:26:32 2018 +0900 Make constants of Loss Detector configurable Add below configs ``` proxy.config.quic.loss_detection.max_tlps proxy.config.quic.loss_detection.reordering_threshold proxy.config.quic.loss_detection.time_reordering_fraction proxy.config.quic.loss_detection.using_time_loss_detection proxy.config.quic.loss_detection.min_tlp_timeout proxy.config.quic.loss_detection.min_rto_timeout proxy.config.quic.loss_detection.delayed_ack_timeout proxy.config.quic.loss_detection.default_initial_rtt ``` commit 830ed665bb245b0c83aa9eb985559b55c87dc548 Author: Masakazu Kitajo Date: Fri Mar 30 11:49:46 2018 +0900 Check if alt_con_manager is available alt_con_manager is not available until handshake completion commit b7c07e5b45c7ec0cc2eb6f2a8e0f47ccdb1401bb Author: Masakazu Kitajo Date: Fri Mar 30 10:52:37 2018 +0900 Update packet recovery logic to draft-10 commit 6e5b664a31c54d939867469520f5609130193d8a Author: Masakazu Kitajo Date: Mon Mar 26 17:24:21 2018 +0900 Create frames on demand commit 919da9dd2fcb6411d71187fdb4de45400ec05d4c Author: Masaori Koshiba Date: Thu Mar 29 09:48:05 2018 +0900 Set nullptr when _loss_detection_alarm is canceled commit c7a31b9bdf92e692183116b1e96da3937b535e60 Author: Masaori Koshiba Date: Thu Mar 29 09:40:20 2018 +0900 Print First ACK Block when ack received Also rename first_ack_block_length to first_ack_block because it's renamed by draft-08 commit 2ca74d875c4bb2dce21e7973cf1205a7b4dab8f9 Author: Masaori Koshiba Date: Thu Mar 29 09:32:55 2018 +0900 Cancel scheduled loss_detection_alarm on destructor Fix test_QUICLossDetector crash. commit c341432576f220dfe01dccf8d727f517567ccc9d Author: Masakazu Kitajo Date: Thu Mar 29 09:12:00 2018 +0900 Remove unused header inclusion commit d11c84838637d5d08c37d45c19229fcd3c2d4367 Author: Masaori Koshiba Date: Wed Mar 28 15:50:26 2018 +0900 Send ack for discarded packets Pass discarded packet to QUICNetVConnection::_recv_and_ack() to send ack to the packet. Probably this is fine, because if the packet has some stream data to ignore, it will be discarded by offset mismatch. commit d59e78bb0f093a9d54497fb9228721e72e19bdd7 Author: Masaori Koshiba Date: Wed Mar 28 14:22:03 2018 +0900 Ignore PROTECTED packet on state_handshake commit 133e1082a5bb6e2b15ffee6a53ad572ef481987e Author: Masaori Koshiba Date: Wed Mar 28 14:21:17 2018 +0900 Fix packet type selection logic on client side commit 152108180a294c91e5d31ec7b7e2918e90701faa Author: Masaori Koshiba Date: Wed Mar 28 11:24:33 2018 +0900 Print ACK frame info in debug log commit 4cb3a5bf56446d8b2dec7a0890af1cba09e3fc5d Author: Masaori Koshiba Date: Wed Mar 28 11:13:58 2018 +0900 Ignore INITIAL packet on state_connection_established Client might retransmit INITLA packet even if connection is established. This made crash on a assert in connection migration logic. Just ignore it for now. commit ae3c68d463fa9f8e4985ab999580f00f8cc6bad2 Author: Masaori Koshiba Date: Thu Mar 22 11:20:09 2018 +0900 Refactoring QUICHandshake commit 6183b8ef8f8ca5f502acffc834ac437db0b0568e Author: Masaori Koshiba Date: Tue Mar 27 14:04:41 2018 +0900 Re-randomize Connection ID only if TS received RETRY packet commit b8cae7d470eb5cc0fc23017ed73f34112021cc4a Author: scw00 Date: Sun Mar 25 18:03:04 2018 +0800 QUIC: Set packet_info's bytes to zero if it is the ack-only packet commit d774873ebc8ed4edc51b822f78c4d7e1b1c87b4a Author: Masaori Koshiba Date: Mon Mar 26 16:15:41 2018 +0900 Fix unit tests commit 2389503983f52fc0914d473489e768596eb51321 Author: Masaori Koshiba Date: Mon Mar 26 14:45:11 2018 +0900 Add suported_group configs commit bd09c194af798c32306923bb637e4a0def946002 Author: Masaori Koshiba Date: Mon Mar 26 12:46:03 2018 +0900 Separate SSL_CTX for client and server As for client, add SSL_CTX* to QUICConfig, just like SSLConfig has SSL_CTX *client_ctx. As for server, add SSL_CTX* to QUICConfig too for now. Probably this should be integrated with SSLCertLookup or SNIConfigParams. commit 9946719bddddd3be87f78740fbb03a993817fad6 Author: Masaori Koshiba Date: Mon Mar 26 15:00:42 2018 +0900 Fix QUICAckFrameCreator::update() calling in unit tests commit 7cb0796781a075c9585fed9355cda9b92f61c69e Author: Masaori Koshiba Date: Mon Mar 26 11:04:46 2018 +0900 Handle RETRY packet on client side commit d268ad432abff4379c158afd5db1b761ae688ee3 Author: Masaori Koshiba Date: Fri Mar 23 14:01:12 2018 +0900 Add reset api to QUICLossDetector, QUICStreamManager and QUICStream for handling RETRY packet commit dc7577a4151361ddfa4b3db595712ad2100b866b Author: Masakazu Kitajo Date: Fri Mar 23 12:22:57 2018 +0900 clang-format commit bef6fbad1085423d4b1a1b4228fea4f2e62a1a2e Author: Masakazu Kitajo Date: Fri Mar 23 09:29:43 2018 +0900 Don't ack protected packets with a handshake packet commit f17041180f4452031cb85cfd10958f9752ed1c7a Author: scw00 Date: Fri Mar 23 10:07:45 2018 +0800 QUIC: Memory pollution in building packet commit 20bc8b3aab51f8b2e089bee7d6085339f75dbb6d Merge: f3cceb579 60b2d9982 Author: Masakazu Kitajo Date: Thu Mar 22 20:18:39 2018 +0900 Merge branch 'master' into quic-latest commit f3cceb579ca3049da4a47efb7e551d38f8444824 Author: Masakazu Kitajo Date: Thu Mar 22 16:27:49 2018 +0900 Handle ACK block legnth and gap as ranges This fixes #3314 commit 72c9c4abad2b0b9a7ce6b4173390ca129da2f34c Author: Masaori Koshiba Date: Thu Mar 22 10:20:51 2018 +0900 Rename handshake msg types commit 17a22bf0d8452b5be926b0888496571c45e6baf6 Author: Masaori Koshiba Date: Thu Mar 22 09:35:45 2018 +0900 Do not allow early data when stateless retry is enabled The operator must dicide calling SSLStateless() or SSL_read_early_data(), because these are incompatible. For now, if `proxy.config.quic.stateless_retry` is enabled, 0-RTT is disabled. This could be changed if SSL_accept() support cookie ext. commit 63bb874a887c99340951111302f2d1db6ef60d1f Author: scw00 Date: Wed Mar 21 17:06:38 2018 +0800 QUIC: Fix complier error commit 976e1ecf82f82659d5145435ffc1cb6ebe52544a Author: Masakazu Kitajo Date: Tue Mar 20 16:08:24 2018 +0900 Add five_tuple() to QUICConnection interface commit 0bf98811bcb980b88c2e430e0c2f9accfba910c0 Author: Masaori Koshiba Date: Tue Mar 20 15:06:07 2018 +0900 Echo packet number of INITIAL packet in RETRY packet Following below in section 5.4.2 "Retry Packet" (draft-09) > The packet number field echoes the packet number field from the triggering client packet. commit ef7df31604ac843e736dd758c7debdafe68fc595 Author: Masakazu Kitajo Date: Tue Mar 20 15:23:26 2018 +0900 Add tests for HQ commit 4c6a3e851bbaf11c29660730fab0e3c051dd9db3 Author: Masakazu Kitajo Date: Mon Mar 19 16:56:27 2018 +0900 Add HQ frame support commit 05f30d5785544ca2d3168a8cc65cfaf62d501311 Author: Masakazu Kitajo Date: Mon Mar 19 23:17:36 2018 +0900 Fix Version Negotiation Fixes #3305 commit e7fda2c95512d6151860b68e0b3776360069bed1 Author: Masaori Koshiba Date: Mon Mar 19 16:33:03 2018 +0900 Derive key after handshake completed on client side commit 3e379b176101a87bbfb257cab47f837010937ebb Author: Masaori Koshiba Date: Mon Mar 19 11:37:08 2018 +0900 Format debug logs of sending/receiving packets/frames commit 09f62d9ffc55e6d18bbad8df76aa93bf37945a00 Author: Masaori Koshiba Date: Mon Mar 19 10:14:26 2018 +0900 Separate stateless cookie callbacks from QUICGlobal QUICNetVconnection is used from these callbacks for getting client address. This made link issues in Unit Tests of QUIC. commit 8388bc206fb86a824e26d1d1a778132f0f261ebd Author: Masaori Koshiba Date: Mon Mar 19 09:26:59 2018 +0900 Fix compile errors of unit tests commit 5f04fd1bc14d79e920c36ceb87d1615b855aacc8 Author: Masakazu Kitajo Date: Sun Mar 18 21:13:46 2018 +0900 Use negotiated cipher suite It was broken when we add 0rtt support because the logic checked whether handshake is complete. commit 147b19b16d760e692df7e5b267f6324f390478ef Author: Masaori Koshiba Date: Fri Mar 16 11:00:03 2018 +0900 clang-format commit 89f9562844399648d28184662b60fe1072ca000f Author: Masaori Koshiba Date: Fri Mar 16 10:57:44 2018 +0900 Generate client address validation token commit e10160e6b505b9a285ee85d641f15158409357f9 Author: Masaori Koshiba Date: Wed Mar 14 12:08:57 2018 +0900 Add Stateless Retry Support To enable this feature set `proxy.config.quic.stateless_retry` 1. Currently Address Validation Token in cookie ext is dummy. commit e86ea3860f2ca5ca74f7c8322e61abba2e0ad635 Author: Masakazu Kitajo Date: Thu Mar 15 15:24:05 2018 +0900 Move ats_unique_buf to ink_memory commit a9565325408e3aa15c22560c95cd7336b21a652d Author: scw00 Date: Thu Mar 15 11:36:59 2018 +0800 QUIC: Fix compiler error of memcpy commit b12c4d49582afcb943c67557efc443a2aa4ed29d Author: scw00 Date: Tue Mar 13 17:47:44 2018 +0800 QUIC: Fix the QUICHandshakeProtocol test commit 3d549767e6379eb6fb1e415b1ffdf6a508cf8549 Author: Masakazu Kitajo Date: Wed Mar 14 16:26:18 2018 +0900 Separate out QUICVariableInt from QUICUtil so that HQ can use it commit c9845e0ed9d68ad2dd6abecd40f0e609459aea37 Author: Masaori Koshiba Date: Wed Mar 14 09:31:32 2018 +0900 Create RETRY packet commit 94263e8967288676c1d3f16910a8cad9c9f2a16b Author: Masakazu Kitajo Date: Tue Mar 13 17:03:11 2018 +0900 Delay key driviation until it's ready to do that commit f9651e356af77b531a03ab982345199db1d34a93 Author: Masaori Koshiba Date: Tue Mar 13 16:08:42 2018 +0900 Add SSL_stateless() support in QUICTLS Also add test cases for key_share mismatch, stateless retry and both. This is a piece of Client Address Validation support. This requires latest OpenSSL (1.1.1-rc3+) for SSL_CTX_set_stateless_cookie_{generate,verify}_cb - https://github.com/openssl/openssl/commit/3fa2812f32bdb922d47b84ab7b5a98a807d838c0 commit 943dfdb66eef9aec56e417bcbe49165429331365 Author: Masakazu Kitajo Date: Tue Mar 13 14:31:11 2018 +0900 Make tests compilable again commit ca73ea98ac4618c464c6c27d478dfc2a52388783 Author: Masakazu Kitajo Date: Tue Mar 13 10:26:03 2018 +0900 Add logs around encrypting and decrypting commit 6f515f1c1b33b930b1acd023c722c97eef501ada Author: Masaori Koshiba Date: Tue Mar 13 10:08:31 2018 +0900 Fix signature of QUICHandshakeProtocol::is_key_derived() in Mock.h commit 95474563255c9169ca71d1d3740ae3b38fc3e1d7 Author: Masakazu Kitajo Date: Mon Mar 12 17:26:22 2018 +0900 Fix build warnings commit 3f85b668c732baf2b354f95351ca5d374df243e7 Author: Masakazu Kitajo Date: Mon Mar 12 16:24:32 2018 +0900 Fix memory leaks under iocore/net/quic commit 47289d2a581b9800d630d3425b789975bceaf6b7 Author: Masakazu Kitajo Date: Wed Mar 7 11:45:21 2018 +0900 Support QUIC 0-RTT commit ad869730cb5e3e24ca4e2b4f87542e13e1649130 Author: Masakazu Kitajo Date: Mon Mar 12 14:26:25 2018 +0900 Fix a buffer overflow on creating a stateless reset packet commit 429f0ac1e08fb6f9b1c05903aec1e95d8c279a73 Author: Masakazu Kitajo Date: Mon Mar 12 14:25:53 2018 +0900 Fix a build issue commit f8aa0dfc4c5077a101bbba928cf21278df14a09d Author: scw00 Date: Sun Mar 11 08:47:52 2018 +0800 QUIC: Make sure only schedule one event when close connection commit 2da95412be31c7cc43b4c0fbde18ab856b995d3c Author: scw00 Date: Fri Mar 9 09:01:20 2018 +0800 QUIC: free the retransmision initial packet commit fe1335fa69998e8798e3279ab528a2d32e16994a Author: scw00 Date: Fri Mar 9 08:25:21 2018 +0800 QUIC: Add default TCL path commit 3467144cfef30841cc10785ebbbabf6d5a4aa253 Merge: 387323abc 20db74bc0 Author: Leif Hedstrom Date: Wed Mar 7 14:10:36 2018 -0800 Merge branch 'master' into quic-latest * master: (49 commits) s3_auth: check if previous config (re)load failed Fix string_view hash function all or nothing for readIntoBuffer Diagnostic message when openning hostdb file fails Fixing #3232, crash when making a server push Add option to always add Client-ip header disable fips when TS_ENABLE_FIPS == 0 Select the current cert when looking for issuer Respond with 400 code when Content-Length headers mismatch, remove duplicate copies of the Content-Length header with exactly same values, and remove Content-Length headers if Transfer-Encoding header exists. Fix default include path ordering. remove HAVE_POSIX_MEMALIGN related code Acquire lock before calling Http2ClientSession::free() from Http2ConnectionState Combine mutex of Http2ClientSession and Http2ConnectionState Removed MD5 signature values based on ASF updated release policy New policy : -- MUST provide a SHA- or MD5-file -- SHOULD provide a SHA-file -- SHOULD NOT provide a MD5-file Add declaration of ssl_verify_client_callback Optimize: define AIO_Reqs::aio_temp_list by ASLL macro fix unpredictable diskok within CacheProcessor::start_internal Create a new AIOCallback to call back aio_err_callbck if there is an error from Linux Native AIO Remove unused SHARED_* build variables. Client verify callback with client verify hook ... Conflicts: proxy/http/Makefile.am commit 387323abc755f79317576fc717a64591f78640f3 Author: Masakazu Kitajo Date: Wed Mar 7 17:34:07 2018 +0900 Fix a buffer overflow commit aea566de628622436347fbcf7080b0585ab7e7a9 Author: Masakazu Kitajo Date: Wed Mar 7 15:42:52 2018 +0900 Fix warnings on compile with gcc commit 499a39bc5f9764801f6f68f79d93a5b38ad2af04 Author: Masakazu Kitajo Date: Tue Mar 6 14:16:36 2018 +0900 Read and discard early data commit 8004b3768733a9dba99b59871e6fa681e026d516 Author: Masakazu Kitajo Date: Mon Mar 5 14:43:09 2018 +0900 Retransmit all handshake packets Because of this block, handshake packets might not be retransmitted actually if protected packets are already in the list. commit 50476f0ccab5bcbfff5efa56ca3a1232cbb7977a Author: Masakazu Kitajo Date: Mon Mar 5 11:58:56 2018 +0900 Don't use raw pointer for QUICPacketHeader commit 9fa21b09e8f643b8b8f6cfbcdbaaef77b0db00ce Author: Masakazu Kitajo Date: Mon Mar 5 11:58:42 2018 +0900 Don't use std::move when return commit c3af882434fd279e82e5534a55f2faded535e957 Author: scw00 Date: Mon Mar 5 08:31:34 2018 +0800 QUIC: Mem leaking with QUICPacketHeader commit b3759a9addf8709c0552cbc282f048f8bbda8614 Author: Jordy Liu Date: Sat Mar 3 17:39:14 2018 +0800 Using MT_hashtable for VC lookup. commit 1d7d681cd5a6da686d88f066ce3897ad1a0b8c53 Author: scw00 Date: Fri Mar 2 14:13:07 2018 +0800 QUIC: Ensure switch to closed state once commit e6b552872ba68acf235577b656752af7b2278713 Author: Masaori Koshiba Date: Thu Mar 1 20:57:54 2018 +0900 Clear SSL_OP_ENABLE_MIDDLEBOX_COMPAT option of OpenSSL commit 358718497de8dcd6ded3a9d1e0f9206fd7bbef83 Author: Masaori Koshiba Date: Fri Mar 2 14:45:27 2018 +0900 Add debug log in QUICLossDetector::_detect_lost_packets() commit 1eba7ea33c0a710bb27828329dac55393525fd93 Author: scw00 Date: Thu Feb 22 17:19:16 2018 +0800 QUIC: Insert client selected connection id into ctable commit 4f68ac7e5f293e2bd1afa9af3bad79b22028e817 Author: scw00 Date: Tue Feb 27 13:53:01 2018 +0800 QUIC: Ensure only first initial packet could enter QVC::acceptEvent commit 8a7696cacff20c68b3f04345b88239e03e2e135b Author: Masakazu Kitajo Date: Tue Feb 27 15:40:00 2018 +0900 Ignore 0-RTT Protected packets for now commit 4925d9af45ee240e73f725acad384e966a4e5720 Author: Masakazu Kitajo Date: Tue Feb 27 14:29:54 2018 +0900 Notify packet arrival with QUIC_EVENT_PACKET_READ_READY Calling schedule_imm without specifying event implicitly use EVENT_IMMEDIATE, and it confuses QUICNetVC because the event is also used for inactive timeout. commit 29cb75274ebb23ed37d1c370c70a9468a6231e24 Author: scw00 Date: Fri Feb 23 13:40:18 2018 +0800 QUIC: clean the inactive timeout in closing and draining state commit 532e9dd14d1c3c44b58d4853afaed2d2dd1af045 Author: scw00 Date: Fri Feb 23 10:22:47 2018 +0800 QUIC: Send close frame packet when we are in close period commit 73d0ad369de49adf1480c08255594f1a9cdaeb19 Author: Masakazu Kitajo Date: Fri Feb 23 11:13:29 2018 +0900 Don't decrypt packets that have unsupported versions commit 4f0255b9c6ec46c1c775a25da452f70e25465a89 Author: scw00 Date: Fri Feb 23 09:25:33 2018 +0800 QUIC: remove useless scheduling event commit 779b69ce89d436bc64802a72fccfeacb49b8356d Merge: 6706e60b8 b2705d6a7 Author: Leif Hedstrom Date: Wed Feb 21 21:36:31 2018 -0700 Merge branch 'master' into quic-latest * master: Doc: Update documentation building documentation. Adds tcpi_total_retrans to tcpinfo Doc: Fix more documentation build warnings. Call mark_body_done() when response body is empty commit 6706e60b8c49c64df3c42d4ff415d2986bee02af Author: Masakazu Kitajo Date: Thu Feb 22 12:34:28 2018 +0900 Return CreationResult::IGNOER when keys for decryption are not ready commit 1f2013e10574380c1c2880d4e2bcf4f46c9730b1 Author: scw00 Date: Thu Feb 22 10:02:51 2018 +0800 QUIC: Fix Unexpected event on state_connection_closing commit 54853e1453af9f4a3dec67a7cf829835093427e6 Author: Masakazu Kitajo Date: Thu Feb 22 12:21:53 2018 +0900 clang-format commit f8a968bc780fc698ae9b909f61f8c52fdc459a00 Author: Masakazu Kitajo Date: Thu Feb 22 10:20:45 2018 +0900 Set max_early_data to 0xFFFFFFFF commit aea4c6c2c9109745554733ceb17c0b754ead3ab0 Author: Masakazu Kitajo Date: Wed Feb 21 11:26:15 2018 +0900 Prevent logs for unexpected events on EVENT_IMMEDIATE commit 235252651b7935c928bc13432c87813942e870ba Author: Masakazu Kitajo Date: Wed Feb 21 17:27:12 2018 +0900 Rename QUICCryptoTls to QUICTLS commit 4d58fcd16ff3d0ee5d23710d33dee45b8106f7bc Author: Masakazu Kitajo Date: Wed Feb 21 17:04:00 2018 +0900 Rename QUICCrypto to QUICHandshakeProtocol commit a60303a647bec7c718e66f351cd877c7e9f68488 Author: Masakazu Kitajo Date: Wed Feb 21 16:13:42 2018 +0900 Update tests commit 72dc4b76f0951ec5163b1be635d5b2445d53778f Author: Masakazu Kitajo Date: Wed Feb 21 15:41:44 2018 +0900 Remove QUICStreamManager::init() commit 11a3c6f99bd3ef97ce406c0af62c1f23e59401cc Author: scw00 Date: Wed Feb 21 12:19:57 2018 +0800 QUIC: Sends stateless reset packet if connection is closed commit e4effa3a228d7e016d95a97f414402b47508fc8f Author: Masakazu Kitajo Date: Wed Feb 21 14:31:53 2018 +0900 Use pragma once commit 3405854015c379bb61250d6736c459288e4aaa4f Merge: 39c800963 e4aa18e33 Author: Masakazu Kitajo Date: Tue Feb 20 21:12:24 2018 +0900 Merge branch 'master' into quic-latest Conflicts: iocore/net/Makefile.am proxy/Main.cc proxy/http/HttpProxyServerMain.cc commit 39c80096354acf44ed47647bb3f5c8b63bbecd02 Author: Masakazu Kitajo Date: Mon Feb 19 17:38:25 2018 +0900 Add QUICHKDF.h commit 999dde94f5e67d19f5a4c3d8e289a62962606871 Author: Masakazu Kitajo Date: Mon Feb 19 15:41:06 2018 +0900 Don't free QUICNetVConnections to avoid crash #3139 commit 970ca9b52ea07170076408faa8117771f9532cb9 Author: Masakazu Kitajo Date: Mon Feb 19 15:26:53 2018 +0900 Update HQ version number commit f3eaaaa195f628596eb331844692fc546598f663 Author: Masakazu Kitajo Date: Mon Feb 19 14:25:06 2018 +0900 Update loss detectin logic to draft-09 commit e957bbf555146d5f9f9aa8785f1443d1195c3b11 Author: Masakazu Kitajo Date: Mon Feb 19 13:55:20 2018 +0900 Separate out QUIC specific function from ts/HDKF commit b6098434a09a1ad8a86ac6125092a6c8c1827b59 Author: Masakazu Kitajo Date: Mon Feb 19 11:45:58 2018 +0900 Update labels for key generation commit 9e3d6220f37eb5e225aeb4c6f796dfe0549e9fb6 Author: Masakazu Kitajo Date: Mon Feb 19 11:42:31 2018 +0900 Don't send transport parameters on NEW_SESSION_TIKECT messages commit 365581b4857a16d4153cf984167b564e8599691b Author: Masakazu Kitajo Date: Mon Feb 19 11:38:37 2018 +0900 Update QUIC version to draft-09 commit e4c3201015f3d62438b7843ad20072b12219f200 Author: scw00 Date: Fri Feb 16 09:25:05 2018 +0800 QUIC: Push closed conn into closed queue commit fa5100060a43d95ec8df9c3b67ca9fa3388b141a Author: scw00 Date: Tue Feb 13 18:14:13 2018 +0800 drain all of udp packets and make sure PollCont run before UDPNetHandler commit 0b78de52fc25f8d4cd79348f894d6104faa26d17 Author: scw00 Date: Tue Feb 13 18:46:37 2018 +0800 introduce init function to initailize QUICPollEvent commit de29fa6c686ea9bfd0be1419de240517e9e01fcb Merge: ba5ff998a ec3ed32c9 Author: Leif Hedstrom Date: Sat Feb 10 20:49:39 2018 -0700 Merge branch 'master' into quic-latest * master: to only fix the SELCT_ALT hook Correct clear and assign of FIPS cache keys in HTTPInfo. Remove client read timeout Added logcode and stats Send VC_EVENT_WRITE_READY/COMPLETE when write_vio is consumed X-Debug: log-headers Make throttling feature more useful. psutil can not find python.h withouth python3-dev in Ubuntu Fix a bug that chunked resopnses aren't logged properly if the client side is H2 Documentation: correct inku16 to unsigned int:16 correct negative event's timeout_at value Fixes the escalate plugin such that redirects are followed Use CRYPTO_HEX_SIZE instead of hardcoded fixed sizes. fix OCSP under OpenSSL 1.1.x Note the stripe meta while clearing the stripe coverity 1385717: Out-of-bounds write heap use after free Adding custom method support and bug fixes Fix X-Id logging for slow log commit ba5ff998aef294820ea59c1d4363c5d9a423a56f Author: scw00 Date: Tue Feb 6 14:01:38 2018 +0800 add QUICPollEvent to packet UDPPacket and QVC commit 35abc38261e68d3d3ba7ffc9730f43f071387ac7 Author: scw00 Date: Sat Feb 3 11:04:14 2018 +0800 connect_re processed by nethandler commit 04dfc12f56e034d041789a3d2ebbbdf069a10f44 Author: scw00 Date: Sat Feb 3 08:59:27 2018 +0800 Add syscall into EventIO for qvc to avoid system call commit 5c219098a1ebc2d66b3e627b532079ccb3e976f1 Author: scw00 Date: Wed Jan 31 14:35:20 2018 +0800 [QUIC Client] fix the quic client commit c85f903a11d12b4e1505bd68603f2c157805c4a8 Author: scw00 Date: Sun Jan 28 21:22:41 2018 +0800 complete pollcont commit d9a5b22299a8d9dbcc1d7231ae806ab692669166 Author: Oknet Xu Date: Sun Jan 28 17:55:16 2018 +0800 Add QUICPollCont commit a0beeed6a9a7cf52e737191b75728c81ed05beae Author: scw00 Date: Fri Feb 2 08:13:46 2018 +0800 initialize `opts` to create udp connection with correct local addr commit 4fc50043fd4a40e80b1585db9dbd8d036b2fdd4e Merge: 56bf2d39e 424c70a44 Author: Masaori Koshiba Date: Fri Feb 2 10:59:36 2018 +0900 Merge branch 'master' into quic-latest * master: Bring back MT_hashtable.h under lib/ts/ Fixes a warning from clang v6.x Fixed crash in microserver help message Use NetVCOptions to create UDPConnection Add support to chunk post body's that come from H2 client without content length. Changes to allow for possible stale ua_session pointer in HttpSM object. Add an example of testing http2 with httpbin commit 56bf2d39e085a183a751c1e50a654b6c48108643 Author: Masaori Koshiba Date: Thu Feb 1 11:01:19 2018 +0900 Add QUICCrypto::is_key_derived() commit 7452dc21dfd2410ef30e510551134ed45d8fd09d Author: Masaori Koshiba Date: Thu Feb 1 10:14:29 2018 +0900 Cleanup test_QUICCrypto commit 14dc16cc951b99f312f209e546ea4ce139c5b4f5 Author: Masaori Koshiba Date: Thu Feb 1 10:13:41 2018 +0900 Write debug logs in stderr when unit tests run commit 3f4fe3d4d28d33e6fc67df14d1600d89ff32e65f Author: Masaori Koshiba Date: Tue Jan 30 16:54:00 2018 +0900 Fix tests for QUICHandshake commit 150c1e0cfb1ab8dcefa3a24c1085fb6b270eef4e Author: Masaori Koshiba Date: Tue Jan 30 16:49:01 2018 +0900 Fix tests for QUICCrypto commit d313bbaee52e2d7b26c5d8fd8906a6836744fd7a Author: Masaori Koshiba Date: Tue Jan 30 16:48:18 2018 +0900 Print TLS Version on debug log commit 4e7658ba62614bc982362820a06c69f7d0675862 Author: Masaori Koshiba Date: Tue Jan 30 12:27:27 2018 +0900 [QUIC Client] Load params from records.config commit e71980164966b320a13718d5eb903bc35b0a2a1a Merge: ce3d7c302 752899c76 Author: Leif Hedstrom Date: Mon Jan 29 11:47:48 2018 -0700 Merge branch 'master' into quic-latest * master: moves certain ocsp error messages to Debug commit ce3d7c30263edb588c8fc77b165733d5d0aab856 Author: Masakazu Kitajo Date: Sun Jan 28 23:48:24 2018 +0900 Manage connection table from QUICNetVConnection side PacketHandler doesn't have enough information to manage CID-Connection mapping. QUICNetVConnections take care of the mapping by themselves, and PacketHandler only lookups the mapping. commit 8be2071db8fec2d9e2f03ce7828e0e99afd14dd0 Author: scw00 Date: Sat Jan 27 12:14:57 2018 +0800 Replace INK_MD5 since c93790a merged commit 3420f3eb702c7eb7cbb3e57e4988b2e8bcded1d6 Author: Masakazu Kitajo Date: Sun Jan 28 00:33:57 2018 +0900 Fix tests for TransportParameters commit 90ea93d5e7cc0a2f79075067ea155118a0380c90 Author: Masakazu Kitajo Date: Sun Jan 28 00:33:36 2018 +0900 Make tests compilable commit e3ad67a5094a768705c473eefd3b5afddfc69792 Author: Masakazu Kitajo Date: Sat Jan 27 23:48:59 2018 +0900 Remove QUICConnection::reset_connection_id() commit 186ddfbc19c4dfb4e6a0fdadc35857a2c85f5f86 Author: Masakazu Kitajo Date: Sat Jan 27 21:54:30 2018 +0900 clang-format commit 7a1dc73d3c4e9718c9286a96b41fa509e3b4af87 Merge: 9d47df294 c93790aa7 Author: Masakazu Kitajo Date: Sat Jan 27 21:51:22 2018 +0900 Merge branch 'master' into quic-latest commit 9d47df294e2ea29a33dc91b89be936c6df1b8c27 Author: Masaori Koshiba Date: Thu Jan 25 14:45:15 2018 +1100 Allow empty Stream Data with FIN bit commit b62739606baf7508531270ec16c09fc599370700 Author: Masaori Koshiba Date: Wed Jan 24 09:23:27 2018 +1100 Check SSL_is_init_finished() before complete handshake commit 4f55b160ae66f6ecb7fe34b9b6c060fc4ccb3c5b Author: Masakazu Kitajo Date: Tue Jan 23 14:04:23 2018 +1100 Add a log for binidng client generated CID and server generated CID commit 66f1fc25205bd02f666bc151871004ee73dc0f5a Author: Masakazu Kitajo Date: Tue Jan 23 13:43:36 2018 +1100 Don't decrypt packets that have unsupported version commit 5a363861f812bfa4b2a0ed3775a011d2334739ca Author: Masakazu Kitajo Date: Tue Jan 23 01:52:30 2018 +1100 Print server generated CID on LossDetecotr logs commit 75812607fa50f5b165159cc0f9662e65d52a0311 Author: Masakazu Kitajo Date: Tue Jan 23 01:51:56 2018 +1100 Add QUIC CID to quic_app logs commit 163e3a7d518bd0e71d4010636d1ec7f492b13b94 Author: Masakazu Kitajo Date: Tue Jan 23 01:21:56 2018 +1100 Add QUIC CID to HQ logs commit 77badcf9df125dd2c3129dbabc02b773a2f26195 Author: Masakazu Kitajo Date: Mon Jan 22 16:00:39 2018 +1100 Fix a bug that retransmission timer wasn't reset correctly commit 9549f57290fb9d44ba7f175ba18834d3bdd248cd Author: Masaori Koshiba Date: Mon Jan 22 15:43:14 2018 +1100 Deactivate QUICNetVC on closing state commit 2c7ebfb7282ba0064c33af5eddc2fda77a76ce19 Author: Masakazu Kitajo Date: Mon Jan 22 14:55:13 2018 +1100 Print retransmission alarm in millisecond commit 9a8b3155dbe9d1a0bd4b97411c52925f2f5e3121 Merge: b0c77cd5a 129f59db0 Author: Leif Hedstrom Date: Mon Jan 22 11:38:24 2018 +1100 Merge branch 'master' into quic-latest * master: Do not send HSTS header when remap has failed commit b0c77cd5a5831d0601434db2e4f8044a09b08fab Author: Masakazu Kitajo Date: Mon Jan 22 10:53:04 2018 +1100 Fill the unused field of Version Negotiation Packet with random value commit f5b039539d13debe891b821afeb1dbeb709caa74 Author: Masaori Koshiba Date: Mon Jan 22 09:59:12 2018 +1100 Print event number commit 56ab5ab706d0542f8924f082667e45d2499f88f3 Author: Masaori Koshiba Date: Mon Jan 22 09:31:36 2018 +1100 Print QUIC_EVENT_HANDSHAKE_PACKET_WRITE_COMPLETE event commit e9619720ce62bf20ed3c4ce3d271626a61fd2828 Author: Masaori Koshiba Date: Fri Jan 19 16:33:24 2018 +0900 Use UDPNetProcessor::CreateUDPSocket() commit ea129d3ff4476c9f2509ccc32e6ae52817e515f9 Author: Masakazu Kitajo Date: Fri Jan 19 16:21:48 2018 +0900 Disable TLP for now TLP causes infinite retransmission & acknowledge loop somehow. Disable this as a workaround for next interop. commit c041e55ae46f785df472922745083e04e2318f52 Author: Leif Hedstrom Date: Fri Jan 19 10:33:43 2018 +0900 Fix a compiler warning for now commit 6d75bb178ffc16cfe9ad78c41d1687d4123f52d2 Author: Masaori Koshiba Date: Fri Jan 19 10:19:49 2018 +0900 Allocate QUICNetVConnection by ClassAllocator commit d133aae32768bed32c96b14a54da3d99874236d6 Merge: 2b002aabe f14df9e43 Author: Leif Hedstrom Date: Fri Jan 19 09:41:39 2018 +0900 Merge branch 'master' into quic-latest * master: Coverity ID 1241990: Fixes memory leak Makes it actually create a Config object Avoid copying over data in the write_vio into response_buffer Fix UDPNetProcessor::CreateUDPSocket() Reset water_mark in new_empty_MIOBuffer_internal Remove Congestion Control Feature appends missing dot to udns domain Conflicts: proxy/Makefile.am commit 2b002aabe93ecc6eaa1cd71b06d02b5243d34f2d Author: Masaori Koshiba Date: Wed Jan 17 17:03:07 2018 +0900 Complete handshake after sending finished commit 997c57b47d78cdddbf2e30fc37e05b98786950a2 Author: Masaori Koshiba Date: Wed Jan 17 14:06:22 2018 +0900 Add null packet check before sending packet commit 871fa49fe1b91a0f62740163aed26e558cead417 Author: Masaori Koshiba Date: Wed Jan 17 14:04:45 2018 +0900 Ignore Initial and Handshake packet after handshake completed commit 1afd5a880a4acca0460d69f0b07c291b4dc70901 Author: Masaori Koshiba Date: Wed Jan 17 09:49:57 2018 +0900 Add in/out checks after handshake commit f884b3f05751e4e6c257ab53ac94f2938543b7da Author: Masaori Koshiba Date: Wed Jan 17 09:47:22 2018 +0900 Fix error handling of handshake commit 4e3fa10bb1feb9ed63ff0246ffc67ef602053d3d Author: Masakazu Kitajo Date: Wed Jan 17 10:43:44 2018 +0900 Remove an unused variable commit 1a8b894ecc0abdb760c745c631ecd44d484713c0 Author: Masakazu Kitajo Date: Wed Jan 17 10:20:47 2018 +0900 Include headers at an apropriate place commit 2f954c6eaa6c0ec07025830af8160ffd5269ded3 Author: Masaori Koshiba Date: Wed Jan 17 10:18:23 2018 +0900 Add QUICStats.h commit 24b104f01261c6ac8dd5b7c5d1885fe066a4d315 Author: Masakazu Kitajo Date: Wed Jan 17 01:11:36 2018 +0900 Prepare for adding stats Currently proxy.process.quic.total_packets_sent is the only stat. commit abab45200c76b5afb7b4d01e811c95011a5284ef Author: Masakazu Kitajo Date: Tue Jan 16 15:20:03 2018 +0900 Implement PONG commit aa724876b275ade448747980ae8291dc07d13baa Author: Masakazu Kitajo Date: Tue Jan 16 14:33:20 2018 +0900 Respond with a closing frame on closing state commit af7e1b460fe5f00e5939cc291cd2f2beafdc1a62 Author: Masaori Koshiba Date: Tue Jan 16 14:29:58 2018 +0900 Fix QUICTransportParameters validations of initial_max_stream_id_bidi/uni commit 124c4d7af5ac0c21f6574a3ba5e4ba5b06cfd66d Author: Masaori Koshiba Date: Tue Jan 16 14:03:33 2018 +0900 Rename QUICStreamManager::create_client_stream to QUICStreamManager::create_stream commit 7972edd073ffe90a5c647d88f7fe8cfab8ca9403 Author: Masaori Koshiba Date: Tue Jan 16 12:32:50 2018 +0900 Check handshake status before connection migration commit 010f95eb9b103b99f55a9d8eb5b28e68b43f6309 Author: Masaori Koshiba Date: Tue Jan 16 11:53:42 2018 +0900 Fix Transport Parameter validations and default value of _initial_max_stream_* commit c8728dbb2f8afdf4974964c257ee6c85c2da72c8 Author: Masaori Koshiba Date: Tue Jan 16 10:33:24 2018 +0900 Create handshake stream via QUICStreamManager on connect commit bdd4f7a0a71f42f89b079002ceab092f02634152 Merge: 54a24da4a 995b6ef9c Author: Leif Hedstrom Date: Mon Jan 15 10:22:25 2018 -0700 Merge branch 'master' into quic-latest * master: Fix an enum element mismatch between TSServerState and ServerState_t Traffic-replay updated new_MIOBuffer: uninitialized water_mark fix. change history buffer from linear buffer to circulating buffer fixes parse of redirect uris missing leading / commit 54a24da4aaee498acb955a4a54409fb5d859489e Author: Masakazu Kitajo Date: Mon Jan 15 17:34:23 2018 +0900 Switch to close state on receiving a closing frame on draining state or closing state commit c147874b6c051944845be9931ef13f9a304f8968 Author: Masakazu Kitajo Date: Mon Jan 15 17:07:00 2018 +0900 Draining period and closing period can share the same timer commit e2dc349fc37276fbd166028cc3000cd92e4d1194 Author: Masaori Koshiba Date: Mon Jan 15 16:58:42 2018 +0900 Divide QUICPacketHandler into QUICPacketHandlerIn (incoming conn) and QUICPacketHandlerOut (outgoing conn) commit dbb740f141d97b882ca8e9512586a41fecc7b231 Author: Masaori Koshiba Date: Mon Jan 15 16:56:04 2018 +0900 Fix condition of ssl extention context on adding Transport Parameters commit 56fa13fdd531561ecec4e2e643dc3bc7f79d2a63 Author: Masakazu Kitajo Date: Mon Jan 15 16:21:01 2018 +0900 Switch to draining state on idle timeout commit 06986e42ebb5600ff14b25d7db08a99edf301e74 Author: Masakazu Kitajo Date: Mon Jan 15 14:42:39 2018 +0900 use this_ethread() instead of eventProcessor commit e9c5e429189e3b325f017dd2d3ae329e3d5d7939 Author: Masakazu Kitajo Date: Mon Jan 15 14:42:04 2018 +0900 Fix a bug that draining state can cause BAD_ACCESS commit 1f37f7ba8a3813dd553539a0d71d996acf9501f9 Author: Masakazu Kitajo Date: Mon Jan 15 13:04:15 2018 +0900 Add draining state to QUICNetVConnection commit bf8cf5f1fdfd695f46191e9d97ebbbb00724302f Author: Masaori Koshiba Date: Mon Jan 15 12:53:14 2018 +0900 Call default constructor of QUICStatelessResetToken commit a149a1f251e54a0904e38906459bacf8dae38278 Author: Masaori Koshiba Date: Mon Jan 15 12:00:04 2018 +0900 Support taking address and port on traffic_quic cmd commit 365b4cc97c307050f00805b6834d668cdb8f9558 Author: Masaori Koshiba Date: Mon Jan 15 11:03:36 2018 +0900 Divide traffic_quic.cc commit fa2c6df6dbfd0c4a03e6de6eb6c55b8995d709aa Author: Masakazu Kitajo Date: Mon Jan 15 09:47:12 2018 +0900 Cleanup QUICHandshake constructor commit a897358589476006deddf729f5b71ec0d340b284 Author: Masakazu Kitajo Date: Fri Jan 12 18:23:54 2018 +0900 Send transport parameters on NewSessionTicket messages too commit b7885e3d75dd2640a56f9a0a2492abf96403c8b1 Author: Masakazu Kitajo Date: Fri Jan 12 17:52:57 2018 +0900 Refactor QUICHandshake constructor & start() commit 23fe5043a1e361ed4b62648175882d469c4d1768 Author: Masaori Koshiba Date: Fri Jan 12 16:57:15 2018 +0900 Set net_config_poll_timeout on quic client commit 4576fc763dab539f4dbe278fc1a92c4f465acfc2 Author: Masaori Koshiba Date: Wed Jan 10 15:05:15 2018 +0900 Cleanup QUICTransportParametersHandler::parse commit cb4ea64f415c6e5030e74792c680d4adeeb6c2c2 Author: Masakazu Kitajo Date: Wed Jan 10 14:27:35 2018 +0900 Resolve a deadlock commit 24c99bcdb1d988d6aed3b131e4e2dafc3ff1531d Author: Masaori Koshiba Date: Wed Jan 10 14:15:49 2018 +0900 Add some command line arguments on quic client commit 56b8ac248f8c45cf63154db896041794a224c67a Author: Masakazu Kitajo Date: Wed Jan 10 12:31:35 2018 +0900 Retransmit all lost packets without congestion control commit e6f5c12642537c6d5d5b6a8a9d95d91a48ca9f86 Author: Masaori Koshiba Date: Wed Jan 10 11:50:43 2018 +0900 Format QUICStreamType commit 6cf85f5621715c6b79c1d035dce3a1f4681759e6 Author: Masaori Koshiba Date: Wed Jan 10 11:50:00 2018 +0900 Fix QUICTransportParameters for client commit b43897362755392e179d1a403d5055bc71583039 Author: Masakazu Kitajo Date: Wed Jan 10 10:26:40 2018 +0900 Make sure that loss detection alarm duration is positive value commit 11c27699e97f8d0ad806f7be190772e4e698dd1c Author: Masakazu Kitajo Date: Tue Jan 9 17:06:48 2018 +0900 Fix a testcase for LossDetector commit 40c752228dc97ce0ccadfd3a70506b6b586a9a9a Author: Masakazu Kitajo Date: Tue Jan 9 17:05:04 2018 +0900 Fix a bug that floating values are actually calculated as integers commit 666e7d6223a8a225d158b750caeb7abb813834db Author: Masaori Koshiba Date: Tue Jan 9 15:29:23 2018 +0900 Replace QUICDebugNames::vc_event(int) with get_vc_event_name(int) commit c68dd224475302476276616c1fe430662b635e4c Author: Masakazu Kitajo Date: Tue Jan 9 14:18:42 2018 +0900 Use the latest supported version number before version negotiation completes instead of 0 Version number 0 is reserved to represent version negotiation. commit 1cb8e693b1341b7b9da260523627dcac15c048c9 Author: Masakazu Kitajo Date: Tue Jan 9 12:14:27 2018 +0900 Move some code for BoringSSL to not lost them commit 1415df3e5e4522c8b6a66f251d93ffe2fe5809c3 Author: Masakazu Kitajo Date: Tue Jan 9 12:00:34 2018 +0900 Remove unused functions commit a851522a2c8ba57d942c4fcb486099de58ce623e Author: Masakazu Kitajo Date: Tue Jan 9 11:42:26 2018 +0900 Conver keys and IVs to hex only if the debug tag is set commit fe29839e094550d2493177f66d666adeece1b904 Author: Masaori Koshiba Date: Tue Jan 9 11:40:55 2018 +0900 Print negotiated cipher suite commit e653a773eb3efda2732b893f2b4f8c392808cd7b Merge: 509723575 00d35d77f Author: Leif Hedstrom Date: Sat Jan 6 00:10:08 2018 -0700 Merge branch 'master' into quic-latest * master: clang-format cleanup Fix compiler issue with ubuntu Remove use of netstat in tests Add fix to select ports better on the Mac based on PR 2832 MADV_DODUMP Restructured traffic_layout and command line handle transport of empty body response fix ts_lua core dump issue during config reload Modify regex_revalidate so that traffic_ctl may be used to trigger config file updates and add a config parameter to disable timed updates if desired. Cleans up the build, excludes ccache where it fails Conflicts: cmd/traffic_layout/traffic_layout.cc commit 509723575212cbd8c2eb401dfd6d818fd996140d Author: Masaori Koshiba Date: Thu Dec 28 22:21:47 2017 +0900 clang-format commit c33d80e362819450c5eeaccd5ab5a26e210ed75d Author: Masaori Koshiba Date: Thu Dec 28 22:13:46 2017 +0900 Start QUIC handshake from client commit 9ac1e8d777a7bd2ba789c6e67c74d8f81822f3e3 Author: Masaori Koshiba Date: Tue Dec 26 16:06:36 2017 +0900 Add initial code of QUIC client commit d82803897529df721f38ac054aa01467e9ef9404 Author: Masaori Koshiba Date: Thu Dec 28 11:56:03 2017 +0900 Add QUICHandshake test & refactoring commit cba3193ceadadde8612966b657fd54138a8bb5ee Author: Masaori Koshiba Date: Tue Dec 26 09:52:44 2017 +0900 Fix build errors of unit tests (tests for QUICLossDetector is FAIL) commit f584c8b37a5f08c37c6135ecb5564e1104026ff2 Author: Masaori Koshiba Date: Sun Dec 24 22:07:27 2017 +0900 Fix build errors commit 661204572b3cecd3f0362843c773cfa5b87f421a Merge: 24d344d4d 68dd4e3cf Author: Leif Hedstrom Date: Fri Dec 22 17:35:46 2017 -0700 Merge branch 'master' into quic-latest * master: (33 commits) coverity 1374999: Resource leak Fix race condition during testing rc2. Fix naming of average aggregate function Make sure that push requests use the custom port, if specified. Fixed uri_signing plugin to use pristine request header. Allow for spaces in list of algorithms in configuration file for gzip plugin. Change normalize_ae handling to normalize request from client not request to server (for benefit of gzip plugin). Add pparam to url_sig plugin to make it authenticate pristine URL, eliminate sheme check, other minor changes. coverity 1367517: Destroy held lock option for using the host field for response lookup Delete space after semi-colon when deleting cookies with a test to ensure not overflowing the cookies char buffer. Ensure idx + 1 is less than len before checking for space. Delete space after semi colon when deleting cookies with test to ensure not overflowing end of cookies char buffer. remove code to set hard limit in ink_max_out_rlimit(), hard limit should be set in the system outside of ats Update the docs to be a bit more clear about config behavior Issue #2883: Split TXN and SSN user arg allocation. Fix sdk_sanity_check_null_ptr to take a Make some of build options configurable logging: fix incorrect use of LogAccess::strlen() Do not try to load empty client_cert path. TransformVC may be double closed because there is a issue within HttpTunnel::chain_abort_all ... commit 24d344d4dd3847224458c8f51885d485c84c15b2 Author: Masakazu Kitajo Date: Fri Dec 22 17:02:43 2017 +0900 Ack delay field can be 64bit value commit 74061b16162994b2518053d7281a6b5e9c5882c4 Author: Masakazu Kitajo Date: Fri Dec 22 16:17:09 2017 +0900 Add getUInt8 and getUInt16 to QUICTransportParameter commit 0dc38c4ac27c8751681292a1a635cbca0a72bbfe Author: Masakazu Kitajo Date: Fri Dec 22 11:34:36 2017 +0900 Don't retransmit version negotiation packets This fixes #2977 commit c644c6c1c46f7c5dd17ca89c7312260f9ef632f6 Author: Masakazu Kitajo Date: Fri Dec 22 11:04:58 2017 +0900 Translate the pseudo code for congestion control to C++ commit c61bbf1231c54c6654f98ab3a3a4b306d2686e9a Author: Masakazu Kitajo Date: Thu Dec 21 16:30:49 2017 +0900 Send ACK frames actively commit ac2e13dcd1c59fb98eeefd3f69505680ebeb0dd2 Author: Masaori Koshiba Date: Fri Dec 22 11:00:38 2017 +0900 Wait whole client HTTP/0.9 request on read commit ad7d9eb2859974acb1d3add8c7a6fc3816a42c17 Author: Masakazu Kitajo Date: Thu Dec 21 15:02:15 2017 +0900 Update loss detection logic to draft-08 commit d1ef4a49c61a9628f2359cbae3ce385f76128bb0 Author: Masakazu Kitajo Date: Tue Dec 19 16:28:40 2017 +0900 Send ACK frames during handshake This fixes #2955 commit 703471b24d6e29057b7a6b52754fa7f4ec9d46a9 Author: Masakazu Kitajo Date: Tue Dec 19 11:18:40 2017 +0900 Fix a check for INITIAL_MAX_STREAM_ID_UNI commit 6616b3798fd4e9cbcd5327304b766628c093e74d Author: Masakazu Kitajo Date: Tue Dec 19 11:16:26 2017 +0900 Print generated keys and IVs with extra care Keys and IVs will be logged if you specify vv_quic_crypto (double v). commit 48f5acb0488b47aff70d72de2e9a319598493d82 Author: Masakazu Kitajo Date: Tue Dec 19 09:09:41 2017 +0900 Update tests for version negotiation commit 9266834ec31f5fc83e5e0458a3b3361b281858a7 Author: Masakazu Kitajo Date: Tue Dec 19 02:43:22 2017 +0900 Validate negotiated version in proper way commit 32225be3b3f084e91ac78141e36ace6986c8ba38 Author: Masakazu Kitajo Date: Tue Dec 19 01:31:43 2017 +0900 Fix a bug that receiving the first stream frame with fin flag causes RST_STREAM This fixes #2951 commit 72056a67d53bf232ae56391f6ca75d9c8d080c8c Author: Masaori Koshiba Date: Mon Dec 18 14:21:39 2017 +0900 draft-08: Support new PING/PONG frame format commit bbd681820940e62944052bc33884434b358db359 Author: Masakazu Kitajo Date: Mon Dec 18 14:11:03 2017 +0900 Resolve an deadlock issue on retrasnmission commit 6c80bfb792659c7a8ed9eabb1c124557f7b65921 Author: Masakazu Kitajo Date: Mon Dec 18 12:33:05 2017 +0900 Print the reason when switch to closing state commit 3cd8b506bee93f58518d747c817cf0e70836837a Author: Masakazu Kitajo Date: Mon Dec 18 11:51:20 2017 +0900 Shortest timeout period is 25ms commit e317a528e028c0fdf3076d2b75d7e02de6efdbbc Author: Masakazu Kitajo Date: Mon Dec 18 11:48:45 2017 +0900 Implement TLP and RTO commit af623f8864a7567fe0cbb4dfb8ac65b5663e35ce Author: Masaori Koshiba Date: Mon Dec 18 11:43:09 2017 +0900 Add tests for retransmission and fix QUIC*Frame::store() commit 0fa3586002d78f6e19329098c593a58f4514169b Author: Masakazu Kitajo Date: Mon Dec 18 11:12:34 2017 +0900 Fix retransmittable packet count commit 2411e7e9c4e8296b4c3ce82cd13ed814e62cca8a Author: Masakazu Kitajo Date: Mon Dec 18 11:10:48 2017 +0900 Unify log output from LossDetector commit 559ed2c19e0a99707beb55be9f2429c366797afa Author: Masaori Koshiba Date: Mon Dec 18 10:59:54 2017 +0900 Fix storing STREAM frame on retransmission commit d7ebd15ffe97105f105642812bcaaadf0dea9672 Author: Masaori Koshiba Date: Mon Dec 18 10:13:47 2017 +0900 Add more tests of QUIC*Frame::size() and bugfix commit 9ec0a4a2742522d00c2f93e6c625d10b359292b9 Author: Masaori Koshiba Date: Mon Dec 18 09:32:31 2017 +0900 Fix offset field length of STREAM frame when offset is 0 commit 076fc93bafe34f9146739af6bc34733c9367cc31 Author: Masaori Koshiba Date: Fri Dec 15 15:21:22 2017 +0900 Reenable quic stream when hq transaction is reenabled commit 937019af98332ee9f4dd978fde62e837c1035d07 Author: Masakazu Kitajo Date: Fri Dec 15 14:57:40 2017 +0900 MAX_DATA is now in units of octets commit 9707a54e3d1a363635e7d941308fd2f3c320cb32 Author: Masaori Koshiba Date: Fri Dec 15 14:41:54 2017 +0900 Add tests for QUICPacketHeader::store() commit 1a37dc6cad043a66af512de27a7161efb5cde1f6 Author: Masakazu Kitajo Date: Fri Dec 15 14:07:05 2017 +0900 Set omit-connection-id flag correctly commit b0520cd27bbe8b653d08e9791b703f90601bba04 Author: Masaori Koshiba Date: Fri Dec 15 12:10:34 2017 +0900 Fix STREAM frame loading commit 17cdcbf5bbcfa42f55c799f2c6dfc4df04b48888 Author: Masakazu Kitajo Date: Fri Dec 15 12:10:55 2017 +0900 Update version number for ALPN (hq-07 to hq-08) commit bb65d674656b2ca1172251753f3272d6a2c02590 Author: Masakazu Kitajo Date: Fri Dec 15 11:41:04 2017 +0900 Validate transport parameters commit 5b9acb9618f5c54e6d34a531374edee19d702e6f Author: Masaori Koshiba Date: Fri Dec 15 11:20:34 2017 +0900 draft-08: Support new ACK frame format commit 7b398cd194cf6be63d81d7c9eec473fe9dde4d29 Author: Masakazu Kitajo Date: Thu Dec 14 16:42:44 2017 +0900 Update tests for KeyGenerator since secrets are updated commit 30bb94126f0e87e5b184307c4cf45cf983d59f54 Author: Masakazu Kitajo Date: Thu Dec 14 16:30:40 2017 +0900 Fix the last fix commit 32b5f3d1254a8d0989113a5290c634ec0408385d Author: Masakazu Kitajo Date: Thu Dec 14 16:16:38 2017 +0900 Fix a check for connection id presence commit 3a77c75c4cdc613d287669a5cfd56cacd65f705f Author: Masakazu Kitajo Date: Thu Dec 14 15:43:53 2017 +0900 Fix a default transport parameter value for INITIAL_MAX_STREAM_ID_UNI commit b5f3490db7566330f36c49991ded136841593994 Author: Masaori Koshiba Date: Thu Dec 14 15:37:05 2017 +0900 draft-08: Support new frame format of NEW_CONNECTION_ID and STOP_SENDING commit cf90c5fc28dee4019db42bb29dead2bc0d5e068b Author: Masakazu Kitajo Date: Thu Dec 14 15:31:31 2017 +0900 Load local max_stream_data before starting handshake commit 66b937f6b31b79c34682a4a84ff28a2f78830680 Author: Masaori Koshiba Date: Thu Dec 14 15:03:37 2017 +0900 draft-08: Support new frame format of BLOCKED, STREAM_BLOCKED, and STREAM_ID_BLOCKED commit 9859e54c495023d591d437d90330fb05efd7d541 Author: Masaori Koshiba Date: Thu Dec 14 12:50:41 2017 +0900 draft-08: Support new frame format of CONNECTION_CLOSE, APPLICATION_CLOSE, MAX_DATA, MAX_STREAM_DATA and MAX_STREAM_ID commit d62dcb52c0ceceb90d824ba967e0b2f563273c58 Author: Masakazu Kitajo Date: Thu Dec 14 12:34:11 2017 +0900 draft-08: Update transport parameters & partially support unidirectional streams commit c7f4e0a6f5a46962d80a5d1a666f3573e34dd674 Author: Masakazu Kitajo Date: Thu Dec 14 12:15:20 2017 +0900 Fix a wrong packet type check commit 13819f30931b7855ca8fc1f5b5f55d651e7de715 Author: Masakazu Kitajo Date: Thu Dec 14 12:10:17 2017 +0900 draft-08: Update labels for handshake secrets commit fe481dfae4c1e3105d2f729dfdf80bf2fb690c10 Author: Masaori Koshiba Date: Thu Dec 14 11:32:41 2017 +0900 draft-08: Support new RST_STREAM frame format commit 25fc8ab030b5af02283fe120a294eff0b58a4f7b Author: Masaori Koshiba Date: Wed Dec 13 16:07:35 2017 +0900 draft-08: Support new STREAM frame format commit 3d3b174fd8579feb7d5eaa12d927d16861458b85 Author: Masaori Koshiba Date: Wed Dec 13 10:56:10 2017 +0900 draft-08: Adjust frame types commit 7e7e3fcf0d2990170772cb7dd86b3ee79a60cfc2 Author: Masaori Koshiba Date: Wed Dec 13 10:22:00 2017 +0900 draft-08: Support Variable-Length Integer Encoding commit af62ebda49bc8511b61209f59d3fb3f6f6364615 Author: Masakazu Kitajo Date: Wed Dec 13 12:10:50 2017 +0900 Parse and create ack block in draft-08 way commit c90274a75db135e2d420e457ed262cadd69954f2 Author: Masaori Koshiba Date: Tue Dec 12 16:28:27 2017 +0900 draft-08: Bump version commit ab3a06b8a88880441539457f4fd314f1a0b55ea1 Author: Masaori Koshiba Date: Tue Dec 12 16:28:11 2017 +0900 Fix QUICPacketLongHeader::store for new long header format commit 624c5c7e3274717667db4faf796867e8bc89d1ce Author: Masaori Koshiba Date: Tue Dec 12 16:08:08 2017 +0900 draft-08: Support short header packet type & format commit 387788783bf1013b982474ee59ec095345c964dd Author: Masaori Koshiba Date: Tue Dec 12 15:55:34 2017 +0900 draft-08: Support long header packet type & format commit 069f3e8c2ab9abcb8b3798e4276fd0a6daee1c12 Merge: 9786e2b85 8581d6b10 Author: Leif Hedstrom Date: Tue Dec 12 13:09:57 2017 -0700 Merge branch 'master' into quic-latest * master: uDNS no longer redirects NXDOMAIN to lo fix post-redirect test removes unused public ip option from microserver code Changing all *Debug* macros to *Debug Cleanup of bool conversion in Ptr.h for C++11. Update the autest.sh script that runs from Jenkins Add httpbin extension in Autest Fix typo remove hack for traffic_top update base version to 1.4.1 for autest Add closedir for dangling dir pointer Fix wildcard entry for SNI config Create system_stats plugin. This will insert system information in to the stats list on a five second interval. Currently it pulls load averages, number of processes, and most of the information from sysfs for each available network device. Removes DES-CBC3-SHA from the default cipher list (again...) commit 9786e2b8507c0eb76c6f874b5bcf66e4f7012565 Author: Masakazu Kitajo Date: Tue Dec 12 14:00:12 2017 +0900 Send NEW_CONNECTION_ID frames when connection establish commit fd308e09a529500daada004efaffc5bd94f710e4 Author: Masaori Koshiba Date: Tue Dec 12 11:35:16 2017 +0900 Stop increasing nbytes on write commit 77e901243ebc3a5c24e9eed88c2a6d3676c91d81 Author: Masaori Koshiba Date: Tue Dec 12 11:20:02 2017 +0900 Cleanup QUICSimpleApp commit 535134be1c2b83a4bb3fff0b2d6cfbb0d2288ec2 Author: Masaori Koshiba Date: Fri Dec 8 16:51:35 2017 +0900 Refactoring QUICStream & HQClientTransaction commit 53b5663462bbfb30904ce8c3ae7a19c203e7a4cf Author: Masakazu Kitajo Date: Mon Dec 11 16:03:46 2017 +0900 Revert "Unify how to create null frames and packets (cont.)" This reverts commit eb005d09dbcf00ff574efe69e6225f0d8e01eb4c. commit eb005d09dbcf00ff574efe69e6225f0d8e01eb4c Author: Masakazu Kitajo Date: Mon Dec 11 15:07:19 2017 +0900 Unify how to create null frames and packets (cont.) commit f0feed2037674255ab9d9bdc4fe8b0ab098e65ac Author: Masakazu Kitajo Date: Mon Dec 11 15:03:22 2017 +0900 Unify how to create null frames and packets commit 328b1e7c3d3896f11750191906940ab25ef94454 Author: Masakazu Kitajo Date: Mon Dec 11 11:41:41 2017 +0900 Fix a transport parameter name stateless RESET token is the right name. commit ee1e415b4a3599381b8532cdeff1169edd6f159a Author: Masakazu Kitajo Date: Mon Dec 11 09:14:12 2017 +0900 Close connection if transport parameters are invalid commit 98b258efe2733145a8fe3985b62b7b7dda4d19a1 Author: Masakazu Kitajo Date: Fri Dec 8 16:15:59 2017 +0900 Prohibit duplicate transport parameters commit 3a1d008ac2d4fb1b19ee4f3aea7a55f0cdf86971 Author: Masakazu Kitajo Date: Wed Dec 6 17:04:21 2017 +0900 Fix debug log format commit f0c333cec0aefb1421cfa5e24ba8e1a4f8a703eb Author: Masakazu Kitajo Date: Wed Dec 6 16:54:47 2017 +0900 Fix a bug introduced with a300d3a828916ce1ec8b6bf5ee818ce4a327bc28 commit 76083d8bf0acdffe2faaacbd429a5712e970919e Author: Masakazu Kitajo Date: Wed Dec 6 16:17:50 2017 +0900 Fix loss detection timer commit 8e8e57de7ce508ac6f53ee3178e7c0be713b4fda Author: Masakazu Kitajo Date: Wed Dec 6 15:56:59 2017 +0900 Use std::atomic for packet counts in LossDetector commit a300d3a828916ce1ec8b6bf5ee818ce4a327bc28 Author: Masakazu Kitajo Date: Wed Dec 6 15:37:00 2017 +0900 Use ink_hrtime internally for RTT calculation commit e86b83ac32771808d9f4d8a4f7de937621615625 Author: Masakazu Kitajo Date: Wed Dec 6 15:07:41 2017 +0900 Implement ack block section parser commit 331bfb36ec4bb3f91c93d4f1c858568bb26f50c8 Author: Masaori Koshiba Date: Wed Dec 6 15:03:54 2017 +0900 Check _write_vio is initialized commit 40c8e02cbceb10257ba7246bcc33084da564812e Author: Masakazu Kitajo Date: Wed Dec 6 14:52:43 2017 +0900 Fix a build issue commit c327d2a40434a1a925a01cbb68de8a43bdfcb597 Author: Masaori Koshiba Date: Wed Dec 6 10:13:59 2017 +0900 Transfer huge response using HttpTunnel flow control mechanism commit 9576d280286361a61832eb0e9ff1a2e86947aebf Author: scw00 Date: Fri Nov 17 10:59:46 2017 +0800 fix #2793 rework quic ack creator commit 67e1eeb39bdbe177656251cfa3d7492494569bc9 Author: Masakazu Kitajo Date: Tue Dec 5 11:50:10 2017 +0900 Rename DebugQUIC*() to QUIC*Debug() commit d7cce37efb9cb68361793bf62e965fba263c2fe3 Author: Masakazu Kitajo Date: Tue Dec 5 10:57:33 2017 +0900 Fix a bug that QUICPacket doesn't return packet size correctly commit 7fcb88e2330696d357da60ea7698ea6c1f7b8914 Author: Masakazu Kitajo Date: Tue Dec 5 10:50:00 2017 +0900 Update tests for Flow Controller commit f9d95ff8ed16041160d45e6f6c80874af20bfe64 Merge: d48163476 eee39a7c4 Author: Leif Hedstrom Date: Mon Dec 4 09:07:54 2017 -0700 Merge branch 'master' into quic-latest * master: coverity 1382722: Unchecked return value Deprecates the coallapsed_connection plugin, use collapsed_forwarding instead speedup the script load process when multiple remap rules load the same lua script file Created URI Signing Plugin. Fix crash from HttpSM::setup_100_continue_transfer Limit the amount of memory that an HTTP/2 stream will allocate commit d481634760331f8206ef8aa09fe9046fb04134f0 Author: Masakazu Kitajo Date: Mon Dec 4 17:38:13 2017 +0900 Reduce number of events for loss detection commit 16eca8226501a1292d934d35e8cf3e618eebd910 Author: Masakazu Kitajo Date: Mon Dec 4 15:33:11 2017 +0900 Fix a bug that QUICStream can consume window without sending frames commit b9c256cda794194ec6d25cc55f7983b52f4335f9 Author: Masakazu Kitajo Date: Mon Dec 4 15:28:09 2017 +0900 Don't return QUICError from FlowController commit 3f0a35bc4ccf44e5fabeac40c1cd6ce4c8c6897f Author: Masaori Koshiba Date: Mon Dec 4 14:42:41 2017 +0900 Fix big file transfer commit a648bb52fba52c2176e3151fe1968b1808f3745f Author: Masakazu Kitajo Date: Mon Dec 4 12:58:59 2017 +0900 Use std::atomic to generate packet numbers commit 3e3938fc972276cb82dffd84bda421fc4bf7185c Author: Masaori Koshiba Date: Mon Dec 4 12:20:54 2017 +0900 Add error logs of quic commit 35512a6c852a2f2bfd66336d9b273b311471bcfd Author: Masakazu Kitajo Date: Mon Dec 4 11:38:21 2017 +0900 Fix packet retransmission commit 877d9ff1f2f867ca184e83fae4fde41ec1d8c910 Author: Masakazu Kitajo Date: Fri Dec 1 16:29:10 2017 +0900 Fix a crash on idle timeout commit 437bb7c5574037ef52d912e649933ae90fdc2dab Author: Masakazu Kitajo Date: Fri Dec 1 15:28:43 2017 +0900 Fix a bug that data on streams can be corrupted commit f1782a09965733c3d9e3fba2d69d44a7d91405ea Author: Masakazu Kitajo Date: Fri Dec 1 11:27:09 2017 +0900 Fix a test for flow control commit a0ec809922cf7b1f1736d6eb71dbc5bf6f6da462 Author: Masakazu Kitajo Date: Fri Dec 1 11:12:05 2017 +0900 Make state transition solid commit 387b032b8763ad75ec773b45b10ea5d6c74a56f3 Author: Masakazu Kitajo Date: Fri Dec 1 09:48:58 2017 +0900 Wait for handshake completion with patient commit 45624e8e2b7c2aaa6c215f76ad7aad69d525e5c6 Merge: de1ea8c2f 531ec4ea1 Author: Leif Hedstrom Date: Thu Nov 30 09:18:16 2017 -0700 Merge branch 'master' into quic-latest * master: move TRANSACT_RETURN to Transaction.cc Revert "bugfix for malformed chunked response will be cached under some terrible network circumstances," header_rewrite - Delete cookies if they are empty in rm-cookie operation. overload ats_stringdup for string_view Fix potential crash from returning string_view from nullptr instead of default constructor. Suppress udpnet-service debug log bugfix for malformed chunked response will be cached under some terrible network circumstances, clang-format code indent adjusted too! Reverts the changes from TS-3054 commit de1ea8c2f98e6f62d5436bfd1e5fb9fc9ab46b10 Author: Masakazu Kitajo Date: Thu Nov 30 18:05:19 2017 +0900 Stabilize handshake commit 86a0e52f73737e08d8f70ef22380561a91e7b047 Author: Masakazu Kitajo Date: Thu Nov 30 17:55:02 2017 +0900 Fix connection level flow control calculation commit 99bf22ad4d7fe09c7966005956f1894b91646133 Author: Masakazu Kitajo Date: Thu Nov 30 16:26:03 2017 +0900 Remove an unused type QUICMaximumData commit 463b30577acb800eb80463ca7885eced4285e9a9 Author: Masakazu Kitajo Date: Thu Nov 30 15:31:25 2017 +0900 Increase buffer size for SSL error messages commit 2d3a1421c603bdb76e074c615f1bb6f2e5308e9a Author: Masakazu Kitajo Date: Wed Nov 29 14:07:06 2017 +0900 Update application name for ALPN to hq-07 commit 5f3df783483b11c0b599b73300d920aa8ae44e29 Author: Masakazu Kitajo Date: Wed Nov 29 12:49:34 2017 +0900 Remove 1-RTT Protected packet types commit ae2835c859a4295da7615c30a87c8fb3ef80df16 Author: Masakazu Kitajo Date: Wed Nov 29 12:01:45 2017 +0900 Update stateless reset packet format commit aac32c9aa000b8c2fbd0aef7bdbb0801de51617b Author: Masakazu Kitajo Date: Wed Nov 29 10:16:55 2017 +0900 Use unprotected packet for Version Negotiation Packets commit 837b02218eef2ec8fb3ca48b382efc8422b89dfe Merge: 7db88767d edbb96927 Author: Leif Hedstrom Date: Tue Nov 28 16:49:18 2017 -0700 Merge branch 'master' into quic-latest * master: (49 commits) Cleans up some build issue and unecessary casts Fix a number of tests Enable WCCP building on the Github builds Add protocol metrics to traffic_logstats Adds some basic documentation for traffic_logstats Move NetHandler initialization to be static and not per thread. Remove update race conditions. Related to issue #2761 Doc: Add Layer 4 / SNI Routing documentation. coverity 1382722: Unchecked return value Delete ssl_SNI.config from tests/min_cfg coverity 1382799, 1382796: Unchecked return value coverity 1382795: Pointer to local outside scope Fixed another build issue on Ubuntu, errors ignoring return value Rename prepare_plugin to PreparePlugin to follow naming convention Coverity: removing null pointer dereference Doc: Fix build warnings - SSL Session, Overridable Config. Fixed build on Ubuntu, errors ignoring return value Clean up tls test make it python3 compatible add autest for post redirection Fixed broken build on OSX where st_mtim is not defined ... Conflicts: proxy/Main.cc commit 7db88767ddcabfd3e03f5ff6944309f94869eea0 Author: Masakazu Kitajo Date: Tue Nov 28 21:09:22 2017 +0900 Remove timestamp section from ACK frame commit 092d089e043e8b22385a1468227b606f976386f5 Author: Masakazu Kitajo Date: Tue Nov 28 17:45:29 2017 +0900 clang-format commit 51a84aa2ae0b424ccfd46b2b71ed534d83fe17a6 Author: Masakazu Kitajo Date: Tue Nov 28 17:24:48 2017 +0900 Remove duplicate code commit 66044e3f4e99372fbe33d2b2f4e0b095164ff4c1 Author: Masakazu Kitajo Date: Tue Nov 28 17:22:04 2017 +0900 Resolve a race commit ff03253e49394fa8323018a9a4ba93b39e2bd351 Author: Masakazu Kitajo Date: Tue Nov 28 14:33:26 2017 +0900 Remove a wrong assert commit 668757f0ac9f291323fd05d086dc9e54abcbe34a Author: Masakazu Kitajo Date: Tue Nov 28 14:22:14 2017 +0900 Rename QUICPacketHeader::length() to size() commit 9b2da8fe85ae9b82ca7d12db7917217426b5a25f Author: Masakazu Kitajo Date: Tue Nov 28 14:18:45 2017 +0900 Fix packet size calculation commit 7acc7881ed4a001b89ea3eed8181e22ace4a7fd8 Author: Bryan Call Date: Wed Nov 22 10:48:19 2017 -0800 Fixed build issue with Fedora 26, may be used uninitialized in this function commit f91516c61c74d25f30516a1e372d376ea4c48ba4 Author: Masakazu Kitajo Date: Tue Nov 21 17:46:12 2017 +0900 Fix 1-rtt packet protection commit 0173ca9d97b49f61c87a17b9bf57d40500798074 Author: Masakazu Kitajo Date: Tue Nov 21 15:17:30 2017 +0900 Fix cleartext packet protection commit e9c499b297487f96e9526e5e3ebced8c1bb93fd2 Author: Masakazu Kitajo Date: Tue Nov 21 00:36:21 2017 +0900 Make the check for HKDF support more strict commit 3fac60a83568d2ba00c2b71b1cde7a4790b05442 Author: Masakazu Kitajo Date: Mon Nov 20 22:57:22 2017 +0900 Add a check for kdf.h and hkdf.h commit 9964e57b378fa1472a677c868292a683f73b0720 Author: Masakazu Kitajo Date: Mon Nov 20 17:20:10 2017 +0900 Fix bugs and make all tests pass (but still cannnot handshake) commit 0e83437af5abf82a64da3de326cd82114b8fd95e Author: Masakazu Kitajo Date: Fri Nov 17 15:54:42 2017 +0900 Make tests compilable commit e5af6be2b46d31768f29f8387febb0c3bd9b8b74 Author: scw00 Date: Fri Nov 17 11:22:40 2017 +0800 return something to make g++ happy commit 4232178f07465b8fc7d59d7c42ed58757d64713d Author: Masakazu Kitajo Date: Wed Nov 15 10:29:34 2017 +0900 Use AEAD instead of FNV-1a commit f04b6b5939cd3f59e0a0de4bbd7302112bdcf8cc Author: Masaori Koshiba Date: Wed Nov 15 14:15:40 2017 +0900 Add APPLICATION_CLOSE frame and change Error Code commit d6cac6a7024077486a3408be10392fa2716e3bc2 Author: Masakazu Kitajo Date: Mon Nov 13 17:54:10 2017 +0900 Extract HKDF logic to use it for cleartext packet validation commit 41e5504da198ab7486ca50232abd7647ed612e8c Merge: 1bb9f2eb1 4e5f024eb Author: Leif Hedstrom Date: Mon Nov 13 10:43:32 2017 +0800 Merge branch 'master' into quic-latest * master: Fix crash in H2 priority tree cleanup. Issue 1597: refactor ParentSelection to remove duplicated markParentDown() and markParentUp() code. Optimize: update ink_thread_create with thread-safe support Send RST_STREAM frame when deleting a stream if it is not CLOSED state Correct the listen_backlog Optimize: Within udp_read_from_net, change the receive IOBufferBlock size to 2048 bytes README: Alpine build instructions Optimze: Use a positive number as the timeout of DNSEntry. move the postbuf to HttpSM Fix #2533: ATS doesn't have an active timeout for http2 connection commit 1bb9f2eb160b017c0e28bea661c45094c76df555 Author: Masakazu Kitajo Date: Wed Nov 8 14:21:55 2017 +0900 Update Makefile to build tests commit 53a5fd81b2940ffed9db2b49d0e5fbfd5491678e Merge: a2be49013 2bea8ce48 Author: Leif Hedstrom Date: Tue Nov 7 16:42:11 2017 -0700 Merge branch 'master' into quic-latest * master: coverity 1021700: Uninitialized pointer field lib/ts/ink_sys_control: work around a glibc'ism lib/ts/ink_{defs,error}: modify imports lib/ts/ts_error.h: define throw Move Brotli library check into the library check section CFLAGS is getting set the default -g -O2. When optimization is set on CFLAGS or CXXFLAGS, optimization will not be added to the flags. Single nameserver failover in DNSHandler Delete H2 stream before destroy TS-5057: Add 103 Early Hints support Add flag to ignore out of bounds compile error for test iocore/dns: switch data-type to unsigned bug fix: timeout argument not being used Format some test file with autopep8 remove annoying print message that was added by mistake to the microserver extension fix syntax issue with double and txn that cause it to fail on different OS linux systems add some conditions for netstat existance Fix #2761. NetHandler needs to be initialized API for retrieving TSVConn from TSHttpSsn Fix logic for loading config file for url_sig plugin. Remove MIOBufferWriter unit tests that throw exceptions. Strict checks of RFC7540 8.1.2.2. commit a2be490138f5bb8ec9d90c6825b96c42de37f89e Merge: 586982227 6dcc822ba Author: Leif Hedstrom Date: Sun Nov 5 17:36:13 2017 -0700 Merge branch 'master' into quic-latest * master: Send data fairly on H2 streams even if stream priority is disabled Add traffic_server.memory.rss as a gauge to Epic Fixed chunked_encoding gold file Doc: Cleaning wording in proxy.config.http2.accept_no_activity_timeout information Doc: Updating proxy.config.https.accept_no_activity_timeout information commit 5869822270e857da897a95a0efd99c4b623f71dc Merge: 7e1cd27be af92bb6b1 Author: Leif Hedstrom Date: Thu Nov 2 11:53:52 2017 -0600 Merge branch 'master' into quic-latest * master: (27 commits) This removes all configuration Element ctor/dtor code Removes the rest of the CfgContext files and objects Removes the Configuration Context Manager list packages for building in Ubuntu Correct comment about TS_HTTP_SSN_CLOSE_HOOK. More header_rewrite examples Treat SSL_ERROR_SSL as EPIPE within SSLNetVC::load_buffer_and_write() Doc: Minor fixes - some MimeHdr functions, traffic_layout, event loop metrics. Implement zero-copy within UDPNetProcessorInternal::udp_read_from_net updated abbreviations in README include cstring to make the compiler stop complaining about strlen added tests for different thread configurations, thread information is verified using gdb More NULL to nullptr conversion fix 2707, assign server_session to nullptr after do `chain_abort_all` Getting rid of ts::Vec implementation and replacing it with std::vector Rework the new udp connection coverity 1376260: Uninitialized scalar field Prevent releasing streams simultaneously Within UDPConnection::Release(), it should calls the ep.stop() only if the UDPConnection will be closed Optimize: define UDPQueue::atomicQueue by ASLL macro and rename to outQueue ... Conflicts: iocore/net/P_UnixUDPConnection.h lib/records/I_RecHttp.h lib/records/RecHttp.cc commit 7e1cd27bec7631de9be8ef8a686ca8a99fc6fc56 Author: IvanStarodubtsev Date: Fri Oct 27 11:15:39 2017 +0300 QUIC_TLS* defines are extra to IETF QUIC draft commit 4449401f5938bddb0f7a74a64189d324a1c7b83b Merge: 91294bf2c 3ab974252 Author: Leif Hedstrom Date: Tue Oct 24 09:46:41 2017 -0700 Merge branch 'master' into quic-latest * master: event loop metrics collection TS-5088: Fix missing initial buffer from server, when combined with 101 response cachekey: ability to base the key on pristine URI. add string_view hash operator commit 91294bf2cbdfe1bdd01f9daa8e01c8b061bc1389 Merge: 22a9c65cf bc3395439 Author: Leif Hedstrom Date: Fri Oct 20 08:36:28 2017 -0600 Merge branch 'master' into quic-latest * master: Skip Link Local address to mitigate slow getnameinfo(3) problem on macOS coverity 1021743: Uninitialized members commit 22a9c65cf6f0556d9229b70dd325b21fe9961fdd Author: Leif Hedstrom Date: Thu Oct 19 15:15:58 2017 -0600 Change to use string_view, which is needed after merging current master commit 32106e9394162fcddb3665229144d038417c31f0 Merge: d48631f3d ff8b8a1f0 Author: Leif Hedstrom Date: Thu Oct 19 10:58:57 2017 -0600 Merge branch 'master' into quic-latest * master: (32 commits) add http 0.9 in find_proto_string Add MIOBufferWriter class which writes to an MIOBuffer instance. event loop changes use os.path.join added plugin exists condition Added skip condition on http2 test for lack of curl http2 CID 1196423: Error handling issues (CHECKED_RETURN) UDPBind with EventIO of UnixUDPConnection. Optimize: rename close_UnixNetVConnection to NetHandler::free_netvc. Allow binding value to non-nil variable in LuaVM for configuration files evaluations. This allows us to fix issue #2511. Tests for chunked POST over HTTP1.1 and HTTP2 and GET chunked over HTTP1.1 fix the missing lock in TSVConnFdCreate api Checking the retval of safe_getsockname() and safe_getpeername() optimze: move http 408 response logic into transaction log when ssl_multicert.config is successfully reloaded Optimize: If failed on migrateToCurrentThread, put the server session back to global server session pool This removes the FILE_WRITE mechanism from the core remove unnecessary assertion for "alternate" we have set the NO_FD in close already Fix prepare_plugin ... Conflicts: lib/records/RecHttp.cc lib/ts/ink_inet.cc lib/ts/ink_inet.h commit d48631f3d0c0bedf43655ab56b698341f931fb42 Author: Masakazu Kitajo Date: Thu Oct 19 16:27:52 2017 +0900 Fix stream level flow control It didn't use available credit if sending data don't fit in. commit 843a7ca5f2369d474d114e8d0feefc572125374f Author: Masakazu Kitajo Date: Thu Oct 19 12:26:19 2017 +0900 Add missed changes for the last commit commit ba7974de6e5b577f98945de8136130b6b43982bc Author: Masakazu Kitajo Date: Thu Oct 19 10:51:51 2017 +0900 Don't free QUICNetVConnection for now to prevent crashes Since we don't have QUICNetHandler, all IO events are handled by QUICNetVC directly, which causes crashes when the events are processed after freeing QUICNetVC. It is going to be addressed by introducing QUICNetHandler. commit 8ff12813d3147acb73333f55bd953a5dd5a78ee1 Author: Masakazu Kitajo Date: Wed Oct 18 17:27:30 2017 +0900 Fix the last wrong fix commit 5e713636eca3c1ef870a043b71b3935ff10d579b Author: Masakazu Kitajo Date: Wed Oct 18 10:55:41 2017 +0900 Fix max stream id limitation commit 461b7c80e578e51054a76c48bb319ddde259259e Author: Masakazu Kitajo Date: Tue Oct 17 15:19:45 2017 +0900 Remove temporal debug logs commit 266d5cb0ea2a57142975f8816f01481f50941e4e Author: Masakazu Kitajo Date: Tue Oct 17 15:04:18 2017 +0900 Add null checks commit 0a9919e60538d162d9d28767f6ab43b4d2059c49 Author: Masakazu Kitajo Date: Tue Oct 17 14:26:02 2017 +0900 Fix a bug that packet number can be decoded with an uninitialized value commit a739464caef585a11ca82d927429371d61187b49 Author: Masakazu Kitajo Date: Mon Oct 16 17:28:32 2017 +0900 Fix packet number decoding It was not decoded correctly when packets were not sequential. commit 03b95cdd897a245fc1544a60bc30e6b50ddf03e6 Author: Masakazu Kitajo Date: Mon Oct 16 15:19:26 2017 +0900 Change handshake state after handshake has completely completed commit e72cfd9c10b940971ff2643d46417ab4098656fa Author: Masaori Koshiba Date: Fri Oct 13 12:02:30 2017 +0900 Stop Version Negotiation Packet retransmission commit 2250a414d8818b5f6b781a9ee9811521d6022e20 Author: Masaori Koshiba Date: Thu Oct 12 16:34:14 2017 +0900 Fix heap-use-after-free in QUICStreamFrame::store Make data of QUICStramFrame ats_unique_buf. Copy data of _write_vio to the buffer, when QUICStream sends frame. Ideally this malloc and copy should be avoided. commit d46ff1251c628b642f1822591d1ac5126e916933 Author: Masaori Koshiba Date: Thu Oct 12 14:11:32 2017 +0900 Fix building unit tests of quic commit 2f5caad25df450b16f9458c7119a421652f6ddb5 Author: scw00 Date: Tue Oct 10 08:19:00 2017 +0800 add QUICInBuffer feature commit 6df84c9fb9a4ba936bd747e8509e56ea32d4f903 Author: Bryan Call Date: Thu Oct 5 13:21:29 2017 -0700 Call the QUICFrame destructor before freeing into the ClassAllocator commit e07806283172cd91256fc12235738e07607e0742 Author: Bryan Call Date: Thu Oct 5 12:00:14 2017 -0700 Moved the set handler in QUICNetVConnection constructor to init method commit f322f09d56ff4c9e956807f7a5754fe2274ad6be Merge: 05a419ec6 f556ec631 Author: Leif Hedstrom Date: Thu Oct 5 11:41:40 2017 -0700 Merge branch 'master' into quic-latest * master: Change the include from .cc to .h, as Odin intended Fix mem leak related to do_os_response() function Runroot for traffic_layout Fixed typo in cachekey plugin documentation commit 05a419ec6dfa3d58ca4b16ea00c768089b639076 Author: Masakazu Kitajo Date: Thu Oct 5 06:31:07 2017 -0700 Add connection id to debug logs from QUICHandshake commit 239ffeee50c3514dbc6f957100f6601e6c7db592 Author: Masakazu Kitajo Date: Thu Oct 5 06:09:05 2017 -0700 Add connection id to debug logs from QUICPacketHandler commit 6e223b7fe1e19f5df7d75152f89a571e29e355c2 Author: Masakazu Kitajo Date: Thu Oct 5 05:42:51 2017 -0700 Add connection id to debug logs from QUICStream commit 99338bd3a4d9bfe4aff3eb7b3a29c4acb208c428 Author: Masakazu Kitajo Date: Thu Oct 5 04:57:17 2017 -0700 Add connection id to debug logs from QUICLossDetector commit 51825180400fbc25a756ed45854ab3f20740cfa5 Author: Masakazu Kitajo Date: Thu Oct 5 00:33:53 2017 -0700 Postpone processing protected packets if handshake is not completed There's no guarantee that protected packets arrive after handshake completion because actual stream data is not processed on PACKET_READ_READY events. commit 3362d2b2597073979cde2ebd3ae18c31bd154287 Merge: b0df673a6 ed6dda01e Author: Leif Hedstrom Date: Wed Oct 4 14:23:09 2017 -0700 Merge branch 'master' into quic-latest * master: Added std::make_unique for C++11 Fix runtime undefined symbol error in multiplexer plugin. commit b0df673a64bcd9df284f48886ed388272679a1ca Author: Masakazu Kitajo Date: Wed Oct 4 13:00:08 2017 -0700 Print hq and quic to Via header commit f90c3197dc48a7c34023bfa459873d79016e8585 Author: Leif Hedstrom Date: Wed Oct 4 12:47:40 2017 -0700 Prefix the verbose handshake dump with v_ commit 72dc8c3a1c758cef3dec3869c889c281dc867cad Author: Bryan Call Date: Wed Oct 4 12:06:31 2017 -0700 Implemented HQClientTransaction::get_transaction_id commit e47001634c272861187f033537e7f321e8dfa9d0 Author: Bryan Call Date: Wed Oct 4 11:13:37 2017 -0700 Stubbed out get_transaction_id for now to get QUIC to build commit 57d68dc00c9aad4f70cb9596c6d5b750d23242da Author: Bryan Call Date: Wed Oct 4 10:52:31 2017 -0700 Use std:max instead of internal max macro that was removed commit b44224fd97b289a97a1bdb09aa6bed80f2d9a360 Merge: 8cc22df13 22af8981f Author: Leif Hedstrom Date: Wed Oct 4 10:27:50 2017 -0700 Merge branch 'master' into quic-latest * master: (149 commits) ESI: Make maximum file count runtime configurable. Add support for Forwarded HTTP header tag (RFC7239). UDPNet: Yet another timing fix for test_UDPnet. Support dynamic registration to StatPages. Cleanup: fix rollback to use elevating file operations. Update s3auth test, removed cpp file and added the Catch main define in the unit test file BufferWriter: Add data() method for access to the internal buffer for API consistency with std:string and ts::string_view. Ignoring unit tests Make sure all files in the sandbox are readable Test: Fix timing issues with txn and double tests. Also tweak test_UDPNet to report errors better and use a less common port. Preserve Accept-Encoding header for H2 Server Push promise. Tests: Clean up file extensions in lib/ts unit tests. Update document add new TLS milestones Doc: Clean up comments in null_transform example plugin. Add string literal support to ts::string_view. Replace proxy.config.http.normalize_ae_gzip with normalize_ae w/ Brotli support Issue # 2100 Cleanup: Fix traffic_manager so its checks run if WCCP is enabled. Doc: Minor fixups for duplicated milestones and stat typo. YTSATS-1464: Support set-redirect with READ_REQUEST_PRE_REMAP_HOOK ... commit 8cc22df1355e37b46c6665ecd7d2970aee464215 Author: Zizhong Zhang Date: Tue Oct 3 17:38:13 2017 -0700 Fix #2494 Generate Stateless Reset Token with a configurable value commit 36afa3a1708420b4232a63ab43a791e6fef83995 Author: Bryan Call Date: Tue Oct 3 18:05:06 2017 -0700 Have Quic use make_unique commit c4c3bd9c213b63d03108653761be90f52ac55c33 Author: Bryan Call Date: Tue Oct 3 17:17:41 2017 -0700 Added std::make_unique for C++11 commit 4abd02447216d925f0aaf3f26c5a6581eed4b7b8 Author: Masakazu Kitajo Date: Tue Oct 3 16:20:46 2017 -0700 Discard packets instead of crash This just avoids crashes. The packets need to be acked. (#2609) commit 4617336ff08ad85c1e33ebad83aea45abf2af3ef Author: Masaori Koshiba Date: Tue Oct 3 15:55:23 2017 -0700 Set default app name if client send no ALPN commit 155d69971f4cfb656c97c7583ea975643e168840 Author: Bryan Call Date: Tue Oct 3 15:33:09 2017 -0700 Initialize all members in QUICLossDetector and clean up to use C++11 assignment initialization commit 6fb7f874fba599efe602c35901d1df68da8d72e2 Author: Bryan Call Date: Tue Oct 3 13:57:46 2017 -0700 Initialize the SSL_CTX in QUICNetProcessor and clean up to use C++11 assignment initialization commit 49ce8f11234a11639b9ee69829193fbb8845d2f3 Author: Masaori Koshiba Date: Tue Oct 3 13:07:17 2017 -0700 Add debug tag for handshake packet dump commit d74253e17cd7237b744e8cfc9a262e7fc8e98b2b Author: Bryan Call Date: Tue Oct 3 11:03:07 2017 -0700 clang-format commit cbf265cd6b0b06ce9c353f911ef109026fbe412b Author: Bryan Call Date: Tue Oct 3 10:45:15 2017 -0700 Initialize variable to fix gcc 7.2.1 error error: ‘gap’ may be used uninitialized in this function commit 0551bfdb9673b71cbfd64c5fb59c502c82ee60b4 Author: Masakazu Kitajo Date: Sat Sep 30 22:01:18 2017 +0900 Remove QUICNetVConnection from the map when the connection has been freed commit 110d21d5b8482915bfc4ad1cea64d127af95b4c9 Author: Masakazu Kitajo Date: Sat Sep 30 18:48:22 2017 +0900 Add nullptr check for error reason phrease commit ebe0a758b53b53e026db31c7c7b58d435de3abbd Author: Masakazu Kitajo Date: Sat Sep 30 18:01:20 2017 +0900 Fix tests commit 4774200a265a59889b240fc1995ae03343cefcbd Author: scw00 Date: Sat Sep 30 09:37:07 2017 +0800 fix mock complie error commit ceea56b92a8e70f9939746fd6061d6138b2a3a1f Author: scw00 Date: Fri Sep 29 10:49:05 2017 +0800 fix the calc of rtt in LossDetector commit 57fe4b2a4981b7ec3126e2094f854c4e589b0c4a Author: Masakazu Kitajo Date: Thu Sep 28 17:24:35 2017 +0900 Send CLOSE_CONNECTION or RST_STREAM when error occurs commit 8b47c2e9e95e0ac1b03a58606bf893c70bd77d89 Author: Masaori Koshiba Date: Thu Sep 28 13:58:09 2017 +0900 Cleanup: unify debug tag of QUICLossDetector commit 9f8001a96c9882b232c2e633ca1d4efb34505e35 Author: Masaori Koshiba Date: Thu Sep 28 13:53:35 2017 +0900 Cleanup: use type alias of unique_ptr of QUICPacket commit f446bb6802d19a55a6afc2a16d8c575a178c172e Author: Masaori Koshiba Date: Thu Sep 28 13:40:11 2017 +0900 Cleanup: use type alias of unique_ptr of QUICFrame and QUICStreamFrame commit 9b4f995d10c98e7c58784248f327b02031f91df6 Author: Masaori Koshiba Date: Thu Sep 28 11:22:01 2017 +0900 Move QUICSimpleApp under proxy/hq/ commit 294d36f83ca407c84a136e0ab777fff336fd24b3 Author: Masakazu Kitajo Date: Thu Sep 28 11:10:23 2017 +0900 Fix Makefile.am QUIC related sources were build even if QUIC is not enabled commit 230216c7cd4fbbc971271cd04d47098a29a3a7c0 Author: Masaori Koshiba Date: Thu Sep 28 10:36:24 2017 +0900 Call set_parent() to set host_res_style when start HQClientTransaction commit 1418c4f569f87fff9e938fc8554c93286c3b25cb Author: Masaori Koshiba Date: Wed Sep 27 17:00:21 2017 +0900 Restart sending after received MAX_STREAM_DATA commit f0548d651a252600eabeea49ecba99dca6883efc Author: Masaori Koshiba Date: Wed Sep 27 16:53:48 2017 +0900 Add _stream_frame_send_queue for remote flow control `_frame_send_queue` of QUICNetVConnection is the queue for any type of frame except STREAM frame. The flow contorl doesn't blcok frames in this queue. `_stream_frame_send_queue` of QUICNetVConnection is the queue for STREAM frame except Stream 0. commit 5575369ad6f09f3b3f24a4b9c422f77d37341302 Author: Masakazu Kitajo Date: Tue Sep 26 11:53:42 2017 +0900 Delegate creating QUIC application to SessionAccept commit e601615b20dc73978acc2452d38d9b4c8169e1eb Author: Masaori Koshiba Date: Fri Sep 22 16:24:34 2017 +0900 Rename QUICStream::set_fin() to QUICStream::shutdown() commit 57011b159de89cb5aca24c09a3428c0a66c8cf24 Author: Masaori Koshiba Date: Fri Sep 22 15:29:09 2017 +0900 Set FIN flag on final STREAM frame commit 7d6097e360430a2494e154b55c9e9f77b949f6ce Author: Masaori Koshiba Date: Fri Sep 22 14:51:37 2017 +0900 Skip HTTP/1.1 response headers commit 53afc1ece0adfaff27a30e8b6189b0ce9ec08fa9 Author: Masaori Koshiba Date: Fri Sep 22 10:47:12 2017 +0900 Add simple multi-streamed application Also add HQClientSession and HQClientTransaction to connect to HttpSM from QUICSimpleApp. HQClientTransaction convert HTTP/0.9 GETs to HTTP/1.1. This will be changed to HTTP over QUIC eventually. commit 0d3bd625dd84405fb0ac42ebd5e3d37715f59b33 Author: scw00 Date: Mon Sep 18 15:50:23 2017 +0800 move state change in _write_to_read_vio commit 62bae4c01dd345f2b018d7dada932d3fbfc11b4c Author: scw00 Date: Mon Sep 18 12:04:02 2017 +0800 fix the complie error with test_QUICStreamManager commit 1eb074fa2a96ded10371cee3e7ad991b17659887 Author: Masaori Koshiba Date: Fri Sep 15 12:28:53 2017 +0900 Clear tracked event in the begnning of the event procedure - Clear tracked event in the begnning of the event procedure - Add interal version of transmit_frame() and transmit_packet() The methods from QUICPacketTransmitter and QUICFrameTransmitter could be called from QUICApplication. In that cases, PACKET_WRITE_READY event should be scheduled (if there're no tracked event). OTOH, internally called enqueue and dequeue frames or packets should not schedule event. So this change separate the methods for internal and external, and clear tracked event in the beggining. commit 47078f9aa1c62e9b7e80fb2fe2d95aeb5b1fa139 Author: Masaori Koshiba Date: Fri Sep 15 11:25:11 2017 +0900 Acquire a lock before _frame_send_queue operation commit f973bccd4b5b55f61104ada6e231495672756acb Author: Masaori Koshiba Date: Fri Sep 15 10:56:46 2017 +0900 Rename _frame_buffer of QUICNetVConnection to _frame_send_queue commit 0ee430e9666ac63b6595d5952a129e3046ee892c Author: scw00 Date: Tue Sep 12 20:32:22 2017 +0800 correct the calc of the forward_limit commit 0a56b5af456a7f0c8ed15b38fa21c87345291699 Author: scw00 Date: Fri Sep 15 08:57:46 2017 +0800 remove the useless frame since we already written it in vio commit 2110b5ba4c611520b9f1a735088de11a493c2675 Author: Masakazu Kitajo Date: Wed Sep 13 14:39:07 2017 +0900 Use the shortest offset length commit 581151aaf5c55e9c47fcfc3f99ed0cd9b458aa84 Author: Masakazu Kitajo Date: Wed Sep 13 11:12:25 2017 +0900 Use shortest stream id length commit 27899d856fef2cfedf62eb9deec0e7b8a41348df Author: Masakazu Kitajo Date: Tue Sep 12 17:36:08 2017 +0900 Add support for MAX_STREAM_ID frame and initial_max_stream_id transport parameter commit 0952de777ffd7b54b62c364dc0be5c65518aeb20 Author: Masaori Koshiba Date: Tue Sep 12 16:52:44 2017 +0900 Add QUICDebugNames::quic_event(int) commit 3bfc675a3b530903b4d75647a5c2cb32b2368471 Author: Masaori Koshiba Date: Tue Sep 12 16:47:24 2017 +0900 Schedule QUIC_EVENT_SHUTDOWN when change state to state_connection_closed commit b3c97064c70832f7c59f63c984266c14dd235a6f Author: Masaori Koshiba Date: Tue Sep 12 16:42:35 2017 +0900 Change default error_code to QUICErrorCode::NO_ERROR commit 2786a2c23a3250df3be41f16b92ac0fdce4420ac Author: Masakazu Kitajo Date: Tue Sep 12 16:11:53 2017 +0900 Remove send_frame from QUICStreamManager Since QUICStreamManager::send_frame does nothing, QUICStreams pass frames to QUICFrameTransmitter directly commit a46bcab6c6949a7d4685f03dbff912b603574ecf Author: Masakazu Kitajo Date: Tue Sep 12 14:50:33 2017 +0900 Send stateless reset packet if the client id doesn't seem valid anymore commit 34c9253dc403af84ed3938414f26bfaa05cc5d0b Author: Masakazu Kitajo Date: Tue Sep 12 11:21:35 2017 +0900 Add create_stateless_reset_packet commit 5442415d17b4062a9cf5b64db62a103d6ff559a9 Author: Masaori Koshiba Date: Tue Sep 12 09:56:37 2017 +0900 [draft-05] Fix QUICLossDetector to follow pseudocode of draft-05 commit 5241e33e39f690f0a7e42acdaf55f2372ae7337b Author: Masaori Koshiba Date: Mon Sep 11 16:55:46 2017 +0900 [draft-05] Fix error codes commit fd62dfc9f51d708ddb4e901057ee2c0e405ec1b2 Author: Masaori Koshiba Date: Mon Sep 11 15:47:21 2017 +0900 Cleanup QUICFrame - Replace hard-coded QUICFrameType - Reorder arguments of QUICRstFrame constructor - Use write_QUICStreamId() commit 58bd89f66df87e359816085a12a49cce4150bba9 Author: Masaori Koshiba Date: Mon Sep 11 15:11:15 2017 +0900 [draft-05] Add STOP_SENDING frame commit 533f8bbe94884e46521e5af45d6da7b6dc4f2de5 Author: scw00 Date: Sat Sep 9 20:38:46 2017 +0800 initialize the quic stream after allocator.alloc commit c445c53e159472ae894825d7f1628b877d534612 Author: Masaori Koshiba Date: Mon Sep 11 11:51:37 2017 +0900 Fix RST_STREAM frame format commit e98590b5d7934360ffef3c6f9b5918969e746fa4 Author: Masaori Koshiba Date: Mon Sep 11 11:43:12 2017 +0900 [draft-05] Remove GOAWAY frame from QUICFrameType commit 6160455655b2c2aef934fe081cedb7f3f1a8d057 Author: Masaori Koshiba Date: Mon Sep 11 11:29:28 2017 +0900 [draft-05] Remove GOAWAY frame commit 2f2ca3b2620e1672b21bdd102acf28c5b1a92948 Author: Masakazu Kitajo Date: Mon Sep 11 11:13:53 2017 +0900 Rename truncate_connection_id to omit_connection_id commit c1a88370301c7174a3f7fbcb25fccb1376a2921a Author: Masaori Koshiba Date: Mon Sep 11 11:08:50 2017 +0900 [draft-05] Increase the maximum lenght of ACK Block Length to 64 bits commit d62137c4ba9b715484dd7c0284440b9510e30474 Author: Masakazu Kitajo Date: Mon Sep 11 11:06:58 2017 +0900 Append PADDING frame randomly Minimum QUIC packet size applies only Cilent Initial Packet commit 27fca42498de9d55d54dd53ca07d6f5858f5fca4 Author: scw00 Date: Wed Sep 6 10:59:41 2017 +0800 support stateless retry token in transport parameters commit 6d8eee58fb8abdff2e796c0f1a9f12ba64ec37f6 Author: Masaori Koshiba Date: Wed Sep 6 16:35:15 2017 +0900 Rename buffer for received STREAM frame commit c193d323b0dc8e883a0f5d182319bf5e831b7f3c Author: Masakazu Kitajo Date: Wed Sep 6 10:46:40 2017 +0900 Prevent triggering READ_READY event on every frame commit 913767ae488c7f2414545adc22bcc5fd003edba4 Author: Masaori Koshiba Date: Tue Sep 5 16:32:32 2017 +0900 Randomize initial value for packet number commit 368e54b8df5acd4c5eff21a7d29a7fec643bc04f Author: Masakazu Kitajo Date: Tue Sep 5 16:24:08 2017 +0900 Schedule QUIC_EVENT_PACKET_WRITE_READY only if it's not scheduled commit 4e471619c2b0d8a505beb1d7aaac148bc90da87d Author: Masaori Koshiba Date: Tue Sep 5 15:52:46 2017 +0900 Encode/Decode packet number commit 9af58ef1f308ea6481a2878770c4b9c5b210e50e Author: Masaori Koshiba Date: Tue Sep 5 09:27:03 2017 +0900 Add packet number encoder and decoder commit de466e93b69b0be8d2439d7b4313a05460692e65 Author: Masakazu Kitajo Date: Mon Sep 4 17:43:09 2017 +0900 clang-format commit 22dfdfb3cf243377bf0ed864eee88ccb16b19a5e Author: Masakazu Kitajo Date: Mon Sep 4 17:40:32 2017 +0900 Add tests for QUICStreamManager (total_offset_sent/received) commit c20192b930196249719fc0922254ecd88bc8457c Author: Masakazu Kitajo Date: Mon Sep 4 11:22:20 2017 +0900 Add tests for QUICStreamManager commit f0f998cc16f6e4e4368ff8244430e906fc0b81f4 Author: Masakazu Kitajo Date: Fri Sep 1 16:45:19 2017 +0900 Reimplement flow control with QUICFlowController commit 05de7b0180622c53527e5656a601ee3c1bd4e4d5 Author: Masakazu Kitajo Date: Wed Aug 30 14:16:15 2017 +0900 Fix trivial things commit d1cbe218accf9647e018c8a5a86ff3a93861c88e Author: Masakazu Kitajo Date: Wed Aug 30 12:41:47 2017 +0900 Improve debug logs commit 08c398d158c6877f98fa0c981cc955536800dfd4 Author: Masakazu Kitajo Date: Wed Aug 30 11:41:22 2017 +0900 Propagate errors commit 5f46f5c50a8093536fc242da03b82f46c3035fc6 Author: Masakazu Kitajo Date: Wed Aug 30 11:38:47 2017 +0900 Update stream state after sending / receiving frames commit b8a2a62bc83e17ec4721e50e65fdfb84c179755a Author: Oknet Xu Date: Mon Aug 28 10:56:16 2017 +0800 Resign server.pem for autests to 10 years commit c90703cc4c3be3c5b7790265cd09c584c30b1be0 Author: scw00 Date: Fri Sep 1 09:49:23 2017 +0800 fix the compiler error with gcc 4.9 commit 407dd1b98156af1010893aa75d428cd70e6b27a8 Author: Masakazu Kitajo Date: Tue Aug 29 09:59:19 2017 +0900 Fix a runtime error on Fedora 26 commit ca6ab0c896348e65d9a8c8c2422312a3c023be0a Author: Masakazu Kitajo Date: Mon Aug 28 16:26:37 2017 +0900 Allocate QUICPacketHeader with class allocators commit b9570de8dabe46f8a0f7b8fa8eae5e219d1827a2 Author: Masakazu Kitajo Date: Mon Aug 28 15:49:24 2017 +0900 Allocate QUICPacket with a class allocator commit 6f1f8ae58ec06c64b674be0e84025c122ac2847e Author: Masakazu Kitajo Date: Fri Aug 25 17:32:34 2017 +0900 Print transport parameters commit 0472c9dccae114c68e7642a7390eaceabec46cc1 Author: Masakazu Kitajo Date: Fri Aug 25 17:32:06 2017 +0900 Fix transport parameter parser commit 30f32d1735719592382e358f4215b8dce785fb51 Author: Masaori Koshiba Date: Fri Aug 25 15:08:54 2017 +0900 Use constant expressions commit a281282714390a0367b34786aca2b78b49c52471 Author: Masaori Koshiba Date: Fri Aug 25 15:06:39 2017 +0900 Ran clang-format commit 13e1e43c4c2ae8f70878fbbc64fc65188b8981f6 Author: Masaori Koshiba Date: Fri Aug 25 14:10:34 2017 +0900 Ran clan-tody with performance-unnecessary-value-param commit 8e231ee8dc6a4ff04e88c4b19156c2c82042e4b1 Author: Masaori Koshiba Date: Fri Aug 25 13:52:21 2017 +0900 Modernize typedef and remove unused aliases commit 996ab43b7632823249fd38ead7d401f0dfc7080b Author: Masakazu Kitajo Date: Fri Aug 25 12:08:42 2017 +0900 Fix tests commit 1acd290179f3ad0f963ba6410e6cf8d50b271f62 Author: Masakazu Kitajo Date: Fri Aug 25 11:14:30 2017 +0900 Refactor handshake As per 7.2 Cryptographic and Transport Handshake, crypto module, transport parameter, negotiated version, negotiated application are provided from Handshake. commit c6da89c43342b91e30617982907faf248c6da2e6 Author: Masaori Koshiba Date: Fri Aug 25 11:06:03 2017 +0900 Fix handshake_outstanding count commit f52efea24b10c8fbbf641cd5784eccbacf14107c Author: Masaori Koshiba Date: Thu Aug 24 16:01:12 2017 +0900 Fix alarm duration of QUICLossDetector - Fix cut & paste code from pseudocode for SetLossDetectionAlarm - Use ink_hrtime type commit ed4ff1b7ab89b0176d7559519d493377ecb5f949 Author: Masaori Koshiba Date: Thu Aug 24 15:09:43 2017 +0900 Set inactivity timeout before handshake start commit 1f8eeaee5364da3db3fe3e7cb130827321690d88 Author: Masaori Koshiba Date: Thu Aug 24 14:39:47 2017 +0900 Prevent retransmitting Handshake packets many many times commit 075d829b405800c70061797725a5584d378f48ab Author: Masaori Koshiba Date: Wed Aug 23 16:35:10 2017 +0900 Cleanup QUICStreamManager commit 0b589903df5f433fc44df2c408eb30428c13de99 Author: Masaori Koshiba Date: Wed Aug 23 16:25:45 2017 +0900 Remvoe duplicated mutex allocation commit 6d538d4e4cb385325999150e67c0b1848aca6997 Author: Masaori Koshiba Date: Wed Aug 23 16:05:01 2017 +0900 Return QUICError from QUICFrameHandler::handle_frame() commit 5a5d53f384fe5ed5526676678e2bd44d9b4e0610 Author: Masakazu Kitajo Date: Wed Aug 23 16:00:40 2017 +0900 Add support for ALPN on QUIC Since there's only one application layer protocol now, NextProtocolAccept is not introduced yet. commit 970d63786d0711ecf283f7d719a9b5f725c42caf Author: Masaori Koshiba Date: Wed Aug 23 14:58:04 2017 +0900 Delete QUICFlowController class commit 08f2639c08f722660317b95f0c6d9116cb01f5ac Author: Masaori Koshiba Date: Wed Aug 23 11:47:56 2017 +0900 Add Flow Control support commit 1d28d8c41ca04d8e97e8c49d5710dd1ee240ddba Author: Masaori Koshiba Date: Wed Aug 23 12:10:56 2017 +0900 Rename _request/_response_buffer_offset to _recv/_send_offset commit a86fca03b73533a4bcdfc2f58e9bd17413791a9f Author: Masaori Koshiba Date: Wed Aug 23 10:37:54 2017 +0900 Cleanup QUICTransportParameterValue commit 28dd6829690dd5ee39c6bffbe9c92afd8fa594cc Author: Masakazu Kitajo Date: Wed Aug 23 09:57:34 2017 +0900 Cleanup FrameDispatcher commit a4657c59fb7d2c1315ca09ecb2b3145c7168c5f2 Author: Masaori Koshiba Date: Wed Aug 23 09:11:26 2017 +0900 Load TransportParameters from QUICConfig commit f98e16d6606509782ddb11dcf25797404aaccebd Author: Masaori Koshiba Date: Wed Aug 23 08:40:53 2017 +0900 Fix QUICTransportParameter - Make data of QUICTransportParameterValue std::unique_ptr - Change _parameters table to std::map - Fix binary format of TransportParameter commit 6115406298e96219ab1a20e671094c52f5dea760 Author: Masaori Koshiba Date: Wed Aug 23 08:34:07 2017 +0900 Fix build error of test_QUICStream commit cd36a9eb52c220cd9d38947408d8d9c43fd43d87 Author: Masakazu Kitajo Date: Tue Aug 22 15:48:45 2017 +0900 Add QUICApplicatioMap commit c485128a5ca56b2f4621b8511218a6d11b4751c1 Author: Masakazu Kitajo Date: Tue Aug 22 12:00:10 2017 +0900 Introduce QUICApplicaionMap class To keep QUICNetVConnection focused on connection, delegate streams and applications mapping to QUICApplicationMap. commit 8562c59545fd70e0c5ad5e6c57d7f073fac9ce03 Author: Masakazu Kitajo Date: Tue Aug 22 09:29:28 2017 +0900 Use pragma once commit 0f3003b2f0b524b53f382c41593021fd54f0895b Author: Masakazu Kitajo Date: Tue Aug 22 09:28:31 2017 +0900 Create mutexes in QUICApplication constructor There's no need to create and pass it from QUICNetVConnection commit 51e8f5a08df023ad2e1f5d96046045ff0d679ce8 Author: Masakazu Kitajo Date: Tue Aug 22 09:07:32 2017 +0900 Remove get_crypto from QUICNetVConnection Crypto module should not be exported because it's too powerful to touch it from QUICApplications except QUICHandshake. commit 78330c7f962d47c4a9483098d2e804779f4d56f0 Author: Masakazu Kitajo Date: Mon Aug 21 11:51:00 2017 +0900 Fix a compile error commit 82a41a975057018651b588d4f05b5356004d9887 Author: Masakazu Kitajo Date: Mon Aug 21 11:46:49 2017 +0900 Revalidate negotiated version (server side) commit e27a5b83238a9b3a2ae2d6849d7981349ab12216 Author: Masakazu Kitajo Date: Fri Aug 18 15:24:17 2017 +0900 Fix build issue on Fedora 26 commit 8790f3df35de521add33e18021b45337a5603647 Author: Masakazu Kitajo Date: Fri Aug 18 15:24:02 2017 +0900 Add AL header commit 5adb4a1d0c9ec1c803353f5d2bd868390463aa7a Author: Masakazu Kitajo Date: Fri Aug 18 15:23:10 2017 +0900 clang-format commit 71f40486164fdc29a86bd80f0a730e1d6ccbda6c Author: Masakazu Kitajo Date: Fri Aug 18 14:59:41 2017 +0900 Exchange Transport Parameters This commit just enable to exhcange the parameters, and the parameters are not loded from configuration nor processed. commit 6ea3b7d6c2bd0dc32c88f90e841c6d7092ff12b7 Author: Masaori Koshiba Date: Fri Aug 18 14:40:32 2017 +0900 Fix max data size of STREAM frame commit 2ec9b809a898c083bad84abfff87e3dd99416c36 Author: Masaori Koshiba Date: Fri Aug 18 14:38:11 2017 +0900 Set sa_family when initialize QUICNetVConnection commit 5fe89243e0d21d4cf0a1c268cb85ec7840f0198a Author: Masaori Koshiba Date: Fri Aug 18 14:35:57 2017 +0900 Fix "Invalid argument" error on sending UDP packet commit 8eaad3da7e604b97fe3dc9c34d3094aad3bed3b1 Author: Masaori Koshiba Date: Fri Aug 18 14:32:45 2017 +0900 Set IPV6_V6ONLY socket option commit 8f7f0a8300e20b55c61f76acf355439710e1add4 Author: Masaori Koshiba Date: Fri Aug 18 14:30:55 2017 +0900 Add QUICDebugNames::error_class and QUICDebugNames::error_code commit 6a633e6d13363316c4f8fdcd0467e8f91b0d74c4 Author: Masakazu Kitajo Date: Fri Aug 18 14:45:02 2017 +0900 Fix TLS 1.3 check Check for TLS1_3_VERSION was still needed for OpenSSL < 1.1.1 commit 985169147a952dfd67c5e790f2d39c00cae8459b Author: Masakazu Kitajo Date: Thu Aug 17 11:25:34 2017 +0900 Make a check for TLS 1.3 support strict commit 31ca4fa14c9d007f5a16645da4a01fb1040cb4b0 Author: Masaori Koshiba Date: Wed Aug 16 15:56:00 2017 +0900 Add MAX_DATA, MAX_STREAM_DATA, BLOCKED, and STREAM_BLOCKED frame factory commit e44f61e268b0af2921e96d398150e06001a3b9e4 Author: Masakazu Kitajo Date: Wed Aug 16 11:05:56 2017 +0900 Fix build issues on FreeBSD 11 commit 39356833845830c0f6901af61dbacd3885cd5cc9 Author: Masakazu Kitajo Date: Tue Aug 15 16:36:23 2017 +0900 clang-format commit af8743532fc8f4429caffd75e8e887f1785f7e54 Author: Masakazu Kitajo Date: Tue Aug 15 16:34:55 2017 +0900 Add AL header commit df6c99e3c0a86f512ed39764b580ae5acb878e70 Author: Masakazu Kitajo Date: Tue Aug 15 16:14:55 2017 +0900 Add one more TS_USE_QUIC check commit 49e6fc74cfe533e33ac39b4f7af44f30907cf464 Author: Masakazu Kitajo Date: Tue Aug 15 15:43:57 2017 +0900 Build QUIC modules only if TLS 1.3 is available commit b82fd38bb5408eefc6351780a88ed86940e28685 Author: Masakazu Kitajo Date: Tue Aug 15 12:27:10 2017 +0900 Make TLS 1.3 support optional commit 58478550894828671231bb94b7584db4cbe26991 Author: Masakazu Kitajo Date: Tue Aug 15 10:55:55 2017 +0900 Increase buffer sizes for UDP socket commit c6b05347e0616fe666dc6412a76f05291bce68a1 Author: Masakazu Kitajo Date: Tue Aug 15 10:50:15 2017 +0900 Allocate buffer for UDP packets with a size of PMTU commit 92741d4334cc3a8b4d87ae310f51c192597b4838 Author: Masaori Koshiba Date: Tue Aug 15 09:52:55 2017 +0900 Add draft-05(-pre) support - Change label prefix of Key Expansion - Change largest length of the Largest Acknowledged field of Ack frame - Change QUIC_SUPPORTED_VERSIONS commit 85155fa607828380f9b55d2a7ebfbb1b32b1976c Author: Masakazu Kitajo Date: Tue Aug 15 09:23:32 2017 +0900 Change QUICNetProcessor from struct to class commit 08ef8e4d398e2084b28ac4e766dc34379b1b0720 Author: Masakazu Kitajo Date: Mon Aug 14 17:45:42 2017 +0900 Add a test case for handling CONNECTION_CLOSE frames on FrameDispatcher commit e4d34d77238292453ee3358e7d4f0bd8f61c3dcb Author: Masakazu Kitajo Date: Mon Aug 14 17:30:18 2017 +0900 Remove dependency for NetVConnection To make it possible to run test without real network layer, QUIC related classes depend on QUICConnection abstraction layer instead of QUICNetVConnection. commit 52ba71ad38ed36af6a7290f0fa8f9a41adc256f4 Author: Masakazu Kitajo Date: Mon Aug 14 16:59:25 2017 +0900 Remove QUICConnectionState commit c4a8ea6adaf59b234f299f38935aa68362dc8617 Author: Masakazu Kitajo Date: Mon Aug 14 16:55:05 2017 +0900 Handle CONNECTION_CLOSE frame on QUICNetVConnection Integrate QUICConnectionManager into QUICNetVConnection. commit e95c8f38ca1d029a2218d16d4295a4e10200e2ac Author: Masaori Koshiba Date: Mon Aug 14 10:43:48 2017 +0900 Print Connection ID on debug logs in QUICNetVConnection commit e8c6b978ed02ea96b80901f662a47a07d89c34ef Author: Masaori Koshiba Date: Thu Aug 10 15:34:56 2017 +0900 Add QUICConfig and proxy.config.quic.no_activity_timeout_in Add QUICConfig class to load configs related to QUIC. "proxy.config.quic.no_activity_timeout_in" is also added. Default value is 30 seconds. commit fc797a0838dd8d6c62a9a068b1e8c730d98ac780 Author: Masakazu Kitajo Date: Thu Aug 10 16:11:46 2017 +0900 Fix a runtime error on Fedora 26 operator[] of std::map try to create a value if the key doesn't exist, which we don't want to because it would be a shared_ptr. Use find() instead. commit 43a9f9ec1f639ef7dc9d9de6e37daf252d617bbd Author: Masakazu Kitajo Date: Thu Aug 10 15:58:46 2017 +0900 Fix an runtime error on Fedora 26 Make sure that all information are extracted from a packet before std::move release it when passing the infomation and the packet at the same time. commit b51be170deb0cec505233b912e9b80c72d33913b Author: Masakazu Kitajo Date: Thu Aug 10 12:15:21 2017 +0900 Fix a build error on Fedora 26 swap() in Vec.h was actually a global function, and it seems like the function confuses gcc 7.1.1 if we use unique_ptr in some situation. commit 7ee8f3966b75092a5045fe0b9413368d20d17278 Author: Masakazu Kitajo Date: Thu Aug 10 11:57:18 2017 +0900 Fix build warnings on Fedora 26 commit 7052a17f4e884e013d6efc78167375a6e2787093 Author: Masaori Koshiba Date: Wed Aug 9 15:20:35 2017 +0900 Close QUIC Connection by inactivity timeout commit a080d20eff91423956bed23f4f57879cb1d8e1a4 Author: Masakazu Kitajo Date: Wed Aug 9 10:26:37 2017 +0900 Use cross-platform print format commit 94f42e73042c77891bf1dabbd083d59978626c05 Author: Bryan Call Date: Tue Aug 8 15:51:05 2017 -0700 Updates to build on Fedora 26 and clang-format commit 14ebf42f3f408c784a73f476587f9e39facfa97a Author: Masaori Koshiba Date: Tue Aug 8 10:26:35 2017 +0900 Add basic support of IETF QUIC (draft-04) Some features listed on the First Implementation is supported. - Version negotiation - Basic packetization and reliability - Basic STREAM sending and receiving - Integration with TLS 1.3 handshake (1-RTT) - Authentication for cleartext - PADDING frames - Connection ID - Packet protection - Frame parsing for all frames This commit also has - Simple echo QUIC application But this commit doesn't support - Connection Close Many features which are out of scope of First Implementation are also not supported. - The entire HTTP mapping - Congestion control ...etc Co-authored-by: Masakazu Kitajo --- .gitignore | 27 + README | 2 + build/crypto.m4 | 31 + configure.ac | 26 + doc/admin-guide/files/records.config.en.rst | 253 +- include/ts/apidefs.h.in | 6 + include/tscore/ink_config.h.in | 2 + include/tscore/ink_inet.h | 3 + iocore/eventsystem/I_Event.h | 3 + iocore/eventsystem/I_Thread.h | 7 + iocore/net/I_NetProcessor.h | 1 + iocore/net/I_NetVConnection.h | 6 + iocore/net/Makefile.am | 21 + iocore/net/P_Net.h | 7 + iocore/net/P_QUICClosedConCollector.h | 36 + iocore/net/P_QUICNet.h | 77 + iocore/net/P_QUICNetProcessor.h | 78 + iocore/net/P_QUICNetVConnection.h | 371 +++ iocore/net/P_QUICNextProtocolAccept.h | 61 + iocore/net/P_QUICPacketHandler.h | 112 + iocore/net/P_UDPNet.h | 3 +- iocore/net/P_UnixNetVConnection.h | 19 +- iocore/net/P_UnixUDPConnection.h | 10 +- iocore/net/QUICClosedConCollector.cc | 63 + iocore/net/QUICMultiCertConfigLoader.cc | 350 +++ iocore/net/QUICMultiCertConfigLoader.h | 58 + iocore/net/QUICNet.cc | 171 + iocore/net/QUICNetProcessor.cc | 221 ++ iocore/net/QUICNetVConnection.cc | 2393 ++++++++++++++ iocore/net/QUICNextProtocolAccept.cc | 101 + iocore/net/QUICPacketHandler.cc | 483 +++ iocore/net/UnixNet.cc | 3 +- iocore/net/UnixUDPNet.cc | 8 +- iocore/net/libinknet_stub.cc | 7 + iocore/net/quic/Makefile.am | 305 ++ iocore/net/quic/Mock.h | 730 +++++ iocore/net/quic/QUICAckFrameCreator.cc | 392 +++ iocore/net/quic/QUICAckFrameCreator.h | 135 + iocore/net/quic/QUICAddrVerifyState.cc | 66 + iocore/net/quic/QUICAddrVerifyState.h | 43 + iocore/net/quic/QUICAltConnectionManager.cc | 371 +++ iocore/net/quic/QUICAltConnectionManager.h | 124 + iocore/net/quic/QUICApplication.cc | 281 ++ iocore/net/quic/QUICApplication.h | 100 + iocore/net/quic/QUICApplicationMap.cc | 47 + iocore/net/quic/QUICApplicationMap.h | 40 + iocore/net/quic/QUICBidirectionalStream.cc | 569 ++++ iocore/net/quic/QUICBidirectionalStream.h | 107 + iocore/net/quic/QUICConfig.cc | 474 +++ iocore/net/quic/QUICConfig.h | 160 + iocore/net/quic/QUICCongestionController.cc | 242 ++ iocore/net/quic/QUICConnection.h | 59 + iocore/net/quic/QUICConnectionTable.cc | 66 + iocore/net/quic/QUICConnectionTable.h | 58 + iocore/net/quic/QUICCryptoStream.cc | 167 + iocore/net/quic/QUICCryptoStream.h | 76 + iocore/net/quic/QUICDebugNames.cc | 329 ++ iocore/net/quic/QUICDebugNames.h | 62 + iocore/net/quic/QUICEchoApp.cc | 85 + iocore/net/quic/QUICEchoApp.h | 39 + iocore/net/quic/QUICEvents.h | 38 + iocore/net/quic/QUICFlowController.cc | 270 ++ iocore/net/quic/QUICFlowController.h | 153 + iocore/net/quic/QUICFrame.cc | 2760 +++++++++++++++++ iocore/net/quic/QUICFrame.h | 869 ++++++ iocore/net/quic/QUICFrameDispatcher.cc | 91 + iocore/net/quic/QUICFrameDispatcher.h | 46 + iocore/net/quic/QUICFrameGenerator.cc | 60 + iocore/net/quic/QUICFrameGenerator.h | 69 + iocore/net/quic/QUICFrameHandler.h | 37 + iocore/net/quic/QUICFrameRetransmitter.cc | 183 ++ iocore/net/quic/QUICFrameRetransmitter.h | 96 + iocore/net/quic/QUICGlobals.cc | 99 + iocore/net/quic/QUICGlobals.h | 42 + iocore/net/quic/QUICHKDF.cc | 60 + iocore/net/quic/QUICHKDF.h | 34 + iocore/net/quic/QUICHandshake.cc | 510 +++ iocore/net/quic/QUICHandshake.h | 105 + iocore/net/quic/QUICHandshakeProtocol.h | 64 + iocore/net/quic/QUICIncomingFrameBuffer.cc | 249 ++ iocore/net/quic/QUICIncomingFrameBuffer.h | 86 + iocore/net/quic/QUICIntUtil.cc | 133 + iocore/net/quic/QUICIntUtil.h | 45 + iocore/net/quic/QUICKeyGenerator.cc | 148 + iocore/net/quic/QUICKeyGenerator.h | 69 + iocore/net/quic/QUICKeyGenerator_boringssl.cc | 77 + iocore/net/quic/QUICKeyGenerator_openssl.cc | 63 + iocore/net/quic/QUICLossDetector.cc | 673 ++++ iocore/net/quic/QUICLossDetector.h | 237 ++ iocore/net/quic/QUICPacket.cc | 985 ++++++ iocore/net/quic/QUICPacket.h | 419 +++ iocore/net/quic/QUICPacketFactory.cc | 361 +++ iocore/net/quic/QUICPacketFactory.h | 85 + iocore/net/quic/QUICPacketHeaderProtector.cc | 222 ++ iocore/net/quic/QUICPacketHeaderProtector.h | 48 + .../QUICPacketHeaderProtector_boringssl.cc | 31 + .../quic/QUICPacketHeaderProtector_openssl.cc | 53 + iocore/net/quic/QUICPacketPayloadProtector.cc | 124 + iocore/net/quic/QUICPacketPayloadProtector.h | 53 + .../QUICPacketPayloadProtector_boringssl.cc | 48 + .../QUICPacketPayloadProtector_openssl.cc | 138 + .../net/quic/QUICPacketProtectionKeyInfo.cc | 368 +++ iocore/net/quic/QUICPacketProtectionKeyInfo.h | 123 + iocore/net/quic/QUICPacketReceiveQueue.cc | 225 ++ iocore/net/quic/QUICPacketReceiveQueue.h | 54 + iocore/net/quic/QUICPathValidator.cc | 169 + iocore/net/quic/QUICPathValidator.h | 64 + iocore/net/quic/QUICPinger.cc | 77 + iocore/net/quic/QUICPinger.h | 47 + iocore/net/quic/QUICStats.h | 43 + iocore/net/quic/QUICStream.cc | 323 ++ iocore/net/quic/QUICStream.h | 134 + iocore/net/quic/QUICStreamFactory.cc | 84 + iocore/net/quic/QUICStreamFactory.h | 46 + iocore/net/quic/QUICStreamManager.cc | 470 +++ iocore/net/quic/QUICStreamManager.h | 101 + iocore/net/quic/QUICStreamState.cc | 438 +++ iocore/net/quic/QUICStreamState.h | 172 + iocore/net/quic/QUICTLS.cc | 229 ++ iocore/net/quic/QUICTLS.h | 108 + iocore/net/quic/QUICTLS_boringssl.cc | 180 ++ iocore/net/quic/QUICTLS_openssl.cc | 698 +++++ .../net/quic/QUICTransferProgressProvider.h | 74 + iocore/net/quic/QUICTransportParameters.cc | 471 +++ iocore/net/quic/QUICTransportParameters.h | 157 + iocore/net/quic/QUICTypes.cc | 757 +++++ iocore/net/quic/QUICTypes.h | 556 ++++ iocore/net/quic/QUICUnidirectionalStream.cc | 760 +++++ iocore/net/quic/QUICUnidirectionalStream.h | 137 + iocore/net/quic/QUICVersionNegotiator.cc | 81 + iocore/net/quic/QUICVersionNegotiator.h | 45 + iocore/net/quic/test/event_processor_main.cc | 64 + iocore/net/quic/test/main.cc | 58 + iocore/net/quic/test/server_cert.h | 73 + .../net/quic/test/test_QUICAckFrameCreator.cc | 409 +++ .../net/quic/test/test_QUICAddrVerifyState.cc | 61 + .../test/test_QUICAltConnectionManager.cc | 97 + .../net/quic/test/test_QUICFlowController.cc | 491 +++ iocore/net/quic/test/test_QUICFrame.cc | 1564 ++++++++++ .../net/quic/test/test_QUICFrameDispatcher.cc | 77 + .../quic/test/test_QUICFrameRetransmitter.cc | 296 ++ .../quic/test/test_QUICHandshakeProtocol.cc | 519 ++++ .../quic/test/test_QUICIncomingFrameBuffer.cc | 263 ++ iocore/net/quic/test/test_QUICInvariants.cc | 224 ++ iocore/net/quic/test/test_QUICKeyGenerator.cc | 100 + iocore/net/quic/test/test_QUICLossDetector.cc | 285 ++ iocore/net/quic/test/test_QUICPacket.cc | 293 ++ .../net/quic/test/test_QUICPacketFactory.cc | 123 + .../test/test_QUICPacketHeaderProtector.cc | 210 ++ iocore/net/quic/test/test_QUICStream.cc | 841 +++++ .../net/quic/test/test_QUICStreamManager.cc | 260 ++ iocore/net/quic/test/test_QUICStreamState.cc | 532 ++++ .../quic/test/test_QUICTransportParameters.cc | 292 ++ iocore/net/quic/test/test_QUICType.cc | 78 + iocore/net/quic/test/test_QUICTypeUtil.cc | 177 ++ .../quic/test/test_QUICVersionNegotiator.cc | 125 + iocore/net/test_I_UDPNet.cc | 2 +- lib/records/I_RecHttp.h | 27 +- lib/records/RecHttp.cc | 89 +- mgmt/RecordsConfig.cc | 105 + proxy/Makefile.am | 5 + proxy/http/HttpProxyServerMain.cc | 27 + proxy/http/Makefile.am | 1 + proxy/http3/Http09App.cc | 115 + proxy/http3/Http09App.h | 51 + proxy/http3/Http3.cc | 38 + proxy/http3/Http3.h | 46 + proxy/http3/Http3App.cc | 400 +++ proxy/http3/Http3App.h | 111 + proxy/http3/Http3Config.cc | 100 + proxy/http3/Http3Config.h | 62 + proxy/http3/Http3DataFramer.cc | 61 + proxy/http3/Http3DataFramer.h | 44 + proxy/http3/Http3DebugNames.cc | 152 + proxy/http3/Http3DebugNames.h | 36 + proxy/http3/Http3Frame.cc | 494 +++ proxy/http3/Http3Frame.h | 244 ++ proxy/http3/Http3FrameCollector.cc | 67 + proxy/http3/Http3FrameCollector.h | 40 + proxy/http3/Http3FrameDispatcher.cc | 115 + proxy/http3/Http3FrameDispatcher.h | 50 + proxy/http3/Http3FrameGenerator.h | 34 + proxy/http3/Http3FrameHandler.h | 36 + proxy/http3/Http3HeaderFramer.cc | 112 + proxy/http3/Http3HeaderFramer.h | 60 + proxy/http3/Http3HeaderVIOAdaptor.cc | 61 + proxy/http3/Http3HeaderVIOAdaptor.h | 44 + proxy/http3/Http3Session.cc | 231 ++ proxy/http3/Http3Session.h | 105 + proxy/http3/Http3SessionAccept.cc | 100 + proxy/http3/Http3SessionAccept.h | 57 + proxy/http3/Http3StreamDataVIOAdaptor.cc | 47 + proxy/http3/Http3StreamDataVIOAdaptor.h | 41 + proxy/http3/Http3Transaction.cc | 851 +++++ proxy/http3/Http3Transaction.h | 148 + proxy/http3/Http3Types.cc | 41 + proxy/http3/Http3Types.h | 152 + proxy/http3/Makefile.am | 108 + proxy/http3/QPACK.cc | 1830 +++++++++++ proxy/http3/QPACK.h | 332 ++ proxy/http3/test/Mock.h | 47 + proxy/http3/test/main.cc | 54 + proxy/http3/test/main_qpack.cc | 107 + proxy/http3/test/test_Http3Frame.cc | 252 ++ proxy/http3/test/test_Http3FrameDispatcher.cc | 50 + proxy/http3/test/test_QPACK.cc | 466 +++ src/Makefile.am | 4 + src/traffic_layout/info.cc | 1 + src/traffic_quic/Makefile.inc | 63 + src/traffic_quic/diags.h | 96 + src/traffic_quic/quic_client.cc | 384 +++ src/traffic_quic/quic_client.h | 110 + src/traffic_quic/traffic_quic.cc | 334 ++ src/traffic_server/Makefile.inc | 7 + src/traffic_server/traffic_server.cc | 21 +- src/tscore/ink_inet.cc | 5 +- 216 files changed, 43608 insertions(+), 48 deletions(-) create mode 100644 iocore/net/P_QUICClosedConCollector.h create mode 100644 iocore/net/P_QUICNet.h create mode 100644 iocore/net/P_QUICNetProcessor.h create mode 100644 iocore/net/P_QUICNetVConnection.h create mode 100644 iocore/net/P_QUICNextProtocolAccept.h create mode 100644 iocore/net/P_QUICPacketHandler.h create mode 100644 iocore/net/QUICClosedConCollector.cc create mode 100644 iocore/net/QUICMultiCertConfigLoader.cc create mode 100644 iocore/net/QUICMultiCertConfigLoader.h create mode 100644 iocore/net/QUICNet.cc create mode 100644 iocore/net/QUICNetProcessor.cc create mode 100644 iocore/net/QUICNetVConnection.cc create mode 100644 iocore/net/QUICNextProtocolAccept.cc create mode 100644 iocore/net/QUICPacketHandler.cc create mode 100644 iocore/net/quic/Makefile.am create mode 100644 iocore/net/quic/Mock.h create mode 100644 iocore/net/quic/QUICAckFrameCreator.cc create mode 100644 iocore/net/quic/QUICAckFrameCreator.h create mode 100644 iocore/net/quic/QUICAddrVerifyState.cc create mode 100644 iocore/net/quic/QUICAddrVerifyState.h create mode 100644 iocore/net/quic/QUICAltConnectionManager.cc create mode 100644 iocore/net/quic/QUICAltConnectionManager.h create mode 100644 iocore/net/quic/QUICApplication.cc create mode 100644 iocore/net/quic/QUICApplication.h create mode 100644 iocore/net/quic/QUICApplicationMap.cc create mode 100644 iocore/net/quic/QUICApplicationMap.h create mode 100644 iocore/net/quic/QUICBidirectionalStream.cc create mode 100644 iocore/net/quic/QUICBidirectionalStream.h create mode 100644 iocore/net/quic/QUICConfig.cc create mode 100644 iocore/net/quic/QUICConfig.h create mode 100644 iocore/net/quic/QUICCongestionController.cc create mode 100644 iocore/net/quic/QUICConnection.h create mode 100644 iocore/net/quic/QUICConnectionTable.cc create mode 100644 iocore/net/quic/QUICConnectionTable.h create mode 100644 iocore/net/quic/QUICCryptoStream.cc create mode 100644 iocore/net/quic/QUICCryptoStream.h create mode 100644 iocore/net/quic/QUICDebugNames.cc create mode 100644 iocore/net/quic/QUICDebugNames.h create mode 100644 iocore/net/quic/QUICEchoApp.cc create mode 100644 iocore/net/quic/QUICEchoApp.h create mode 100644 iocore/net/quic/QUICEvents.h create mode 100644 iocore/net/quic/QUICFlowController.cc create mode 100644 iocore/net/quic/QUICFlowController.h create mode 100644 iocore/net/quic/QUICFrame.cc create mode 100644 iocore/net/quic/QUICFrame.h create mode 100644 iocore/net/quic/QUICFrameDispatcher.cc create mode 100644 iocore/net/quic/QUICFrameDispatcher.h create mode 100644 iocore/net/quic/QUICFrameGenerator.cc create mode 100644 iocore/net/quic/QUICFrameGenerator.h create mode 100644 iocore/net/quic/QUICFrameHandler.h create mode 100644 iocore/net/quic/QUICFrameRetransmitter.cc create mode 100644 iocore/net/quic/QUICFrameRetransmitter.h create mode 100644 iocore/net/quic/QUICGlobals.cc create mode 100644 iocore/net/quic/QUICGlobals.h create mode 100644 iocore/net/quic/QUICHKDF.cc create mode 100644 iocore/net/quic/QUICHKDF.h create mode 100644 iocore/net/quic/QUICHandshake.cc create mode 100644 iocore/net/quic/QUICHandshake.h create mode 100644 iocore/net/quic/QUICHandshakeProtocol.h create mode 100644 iocore/net/quic/QUICIncomingFrameBuffer.cc create mode 100644 iocore/net/quic/QUICIncomingFrameBuffer.h create mode 100644 iocore/net/quic/QUICIntUtil.cc create mode 100644 iocore/net/quic/QUICIntUtil.h create mode 100644 iocore/net/quic/QUICKeyGenerator.cc create mode 100644 iocore/net/quic/QUICKeyGenerator.h create mode 100644 iocore/net/quic/QUICKeyGenerator_boringssl.cc create mode 100644 iocore/net/quic/QUICKeyGenerator_openssl.cc create mode 100644 iocore/net/quic/QUICLossDetector.cc create mode 100644 iocore/net/quic/QUICLossDetector.h create mode 100644 iocore/net/quic/QUICPacket.cc create mode 100644 iocore/net/quic/QUICPacket.h create mode 100644 iocore/net/quic/QUICPacketFactory.cc create mode 100644 iocore/net/quic/QUICPacketFactory.h create mode 100644 iocore/net/quic/QUICPacketHeaderProtector.cc create mode 100644 iocore/net/quic/QUICPacketHeaderProtector.h create mode 100644 iocore/net/quic/QUICPacketHeaderProtector_boringssl.cc create mode 100644 iocore/net/quic/QUICPacketHeaderProtector_openssl.cc create mode 100644 iocore/net/quic/QUICPacketPayloadProtector.cc create mode 100644 iocore/net/quic/QUICPacketPayloadProtector.h create mode 100644 iocore/net/quic/QUICPacketPayloadProtector_boringssl.cc create mode 100644 iocore/net/quic/QUICPacketPayloadProtector_openssl.cc create mode 100644 iocore/net/quic/QUICPacketProtectionKeyInfo.cc create mode 100644 iocore/net/quic/QUICPacketProtectionKeyInfo.h create mode 100644 iocore/net/quic/QUICPacketReceiveQueue.cc create mode 100644 iocore/net/quic/QUICPacketReceiveQueue.h create mode 100644 iocore/net/quic/QUICPathValidator.cc create mode 100644 iocore/net/quic/QUICPathValidator.h create mode 100644 iocore/net/quic/QUICPinger.cc create mode 100644 iocore/net/quic/QUICPinger.h create mode 100644 iocore/net/quic/QUICStats.h create mode 100644 iocore/net/quic/QUICStream.cc create mode 100644 iocore/net/quic/QUICStream.h create mode 100644 iocore/net/quic/QUICStreamFactory.cc create mode 100644 iocore/net/quic/QUICStreamFactory.h create mode 100644 iocore/net/quic/QUICStreamManager.cc create mode 100644 iocore/net/quic/QUICStreamManager.h create mode 100644 iocore/net/quic/QUICStreamState.cc create mode 100644 iocore/net/quic/QUICStreamState.h create mode 100644 iocore/net/quic/QUICTLS.cc create mode 100644 iocore/net/quic/QUICTLS.h create mode 100644 iocore/net/quic/QUICTLS_boringssl.cc create mode 100644 iocore/net/quic/QUICTLS_openssl.cc create mode 100644 iocore/net/quic/QUICTransferProgressProvider.h create mode 100644 iocore/net/quic/QUICTransportParameters.cc create mode 100644 iocore/net/quic/QUICTransportParameters.h create mode 100644 iocore/net/quic/QUICTypes.cc create mode 100644 iocore/net/quic/QUICTypes.h create mode 100644 iocore/net/quic/QUICUnidirectionalStream.cc create mode 100644 iocore/net/quic/QUICUnidirectionalStream.h create mode 100644 iocore/net/quic/QUICVersionNegotiator.cc create mode 100644 iocore/net/quic/QUICVersionNegotiator.h create mode 100644 iocore/net/quic/test/event_processor_main.cc create mode 100644 iocore/net/quic/test/main.cc create mode 100644 iocore/net/quic/test/server_cert.h create mode 100644 iocore/net/quic/test/test_QUICAckFrameCreator.cc create mode 100644 iocore/net/quic/test/test_QUICAddrVerifyState.cc create mode 100644 iocore/net/quic/test/test_QUICAltConnectionManager.cc create mode 100644 iocore/net/quic/test/test_QUICFlowController.cc create mode 100644 iocore/net/quic/test/test_QUICFrame.cc create mode 100644 iocore/net/quic/test/test_QUICFrameDispatcher.cc create mode 100644 iocore/net/quic/test/test_QUICFrameRetransmitter.cc create mode 100644 iocore/net/quic/test/test_QUICHandshakeProtocol.cc create mode 100644 iocore/net/quic/test/test_QUICIncomingFrameBuffer.cc create mode 100644 iocore/net/quic/test/test_QUICInvariants.cc create mode 100644 iocore/net/quic/test/test_QUICKeyGenerator.cc create mode 100644 iocore/net/quic/test/test_QUICLossDetector.cc create mode 100644 iocore/net/quic/test/test_QUICPacket.cc create mode 100644 iocore/net/quic/test/test_QUICPacketFactory.cc create mode 100644 iocore/net/quic/test/test_QUICPacketHeaderProtector.cc create mode 100644 iocore/net/quic/test/test_QUICStream.cc create mode 100644 iocore/net/quic/test/test_QUICStreamManager.cc create mode 100644 iocore/net/quic/test/test_QUICStreamState.cc create mode 100644 iocore/net/quic/test/test_QUICTransportParameters.cc create mode 100644 iocore/net/quic/test/test_QUICType.cc create mode 100644 iocore/net/quic/test/test_QUICTypeUtil.cc create mode 100644 iocore/net/quic/test/test_QUICVersionNegotiator.cc create mode 100644 proxy/http3/Http09App.cc create mode 100644 proxy/http3/Http09App.h create mode 100644 proxy/http3/Http3.cc create mode 100644 proxy/http3/Http3.h create mode 100644 proxy/http3/Http3App.cc create mode 100644 proxy/http3/Http3App.h create mode 100644 proxy/http3/Http3Config.cc create mode 100644 proxy/http3/Http3Config.h create mode 100644 proxy/http3/Http3DataFramer.cc create mode 100644 proxy/http3/Http3DataFramer.h create mode 100644 proxy/http3/Http3DebugNames.cc create mode 100644 proxy/http3/Http3DebugNames.h create mode 100644 proxy/http3/Http3Frame.cc create mode 100644 proxy/http3/Http3Frame.h create mode 100644 proxy/http3/Http3FrameCollector.cc create mode 100644 proxy/http3/Http3FrameCollector.h create mode 100644 proxy/http3/Http3FrameDispatcher.cc create mode 100644 proxy/http3/Http3FrameDispatcher.h create mode 100644 proxy/http3/Http3FrameGenerator.h create mode 100644 proxy/http3/Http3FrameHandler.h create mode 100644 proxy/http3/Http3HeaderFramer.cc create mode 100644 proxy/http3/Http3HeaderFramer.h create mode 100644 proxy/http3/Http3HeaderVIOAdaptor.cc create mode 100644 proxy/http3/Http3HeaderVIOAdaptor.h create mode 100644 proxy/http3/Http3Session.cc create mode 100644 proxy/http3/Http3Session.h create mode 100644 proxy/http3/Http3SessionAccept.cc create mode 100644 proxy/http3/Http3SessionAccept.h create mode 100644 proxy/http3/Http3StreamDataVIOAdaptor.cc create mode 100644 proxy/http3/Http3StreamDataVIOAdaptor.h create mode 100644 proxy/http3/Http3Transaction.cc create mode 100644 proxy/http3/Http3Transaction.h create mode 100644 proxy/http3/Http3Types.cc create mode 100644 proxy/http3/Http3Types.h create mode 100644 proxy/http3/Makefile.am create mode 100644 proxy/http3/QPACK.cc create mode 100644 proxy/http3/QPACK.h create mode 100644 proxy/http3/test/Mock.h create mode 100644 proxy/http3/test/main.cc create mode 100644 proxy/http3/test/main_qpack.cc create mode 100644 proxy/http3/test/test_Http3Frame.cc create mode 100644 proxy/http3/test/test_Http3FrameDispatcher.cc create mode 100644 proxy/http3/test/test_QPACK.cc create mode 100644 src/traffic_quic/Makefile.inc create mode 100644 src/traffic_quic/diags.h create mode 100644 src/traffic_quic/quic_client.cc create mode 100644 src/traffic_quic/quic_client.h create mode 100644 src/traffic_quic/traffic_quic.cc 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};