diff --git a/CHANGELOG b/CHANGELOG index 36d1c39e5..caf64799d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,27 @@ +2020-09-15 + - 2.20.0 + - [FEATURE] QUIC and HTTP/3 Internet Draft 30 support. + - [FEATURE] Unreliable Datagram Extension support. + - [FEATURE] Adaptive congestion controller. + - [BUGFIX] Do not send MAX_STREAM_DATA frames on crypto streams. + - [BUGFIX] Fail with CRYPTO_BUFFER_EXCEEDED when too much CRYPTO + data comes in. + - [BUFFIX] Spin bit is now strictly per path; value is reset on + DCID change. + - [BUGFIX] Check that max value of max_streams_uni and + max_streams_bidi TPs is 2^60. + - [BUGFIX] Close IETF mini conn immediately if crypto session + cannot be initialized. + - Deprecate ID-28 (no browser uses it): it's no longer in the + default versions list. + - New programs duck_server and duck_client that implement the + experimental siduck-00 protocol. They quack! + - IETF crypto streams: don't limit ourselves from sending. + - Command-line programs: turn off QL loss bits if -G is used, as + Wireshark cannot decrypt QUIC packets when this extension is used. + - Turn all h3 framing unit tests back on. + - Fix malo initialization when compiled in no-pool mode. + 2020-09-08 - 2.19.10 - [FEATURE] Add lsquic_stream_pwritev(). This function allows one to diff --git a/CMakeLists.txt b/CMakeLists.txt index bde1f5124..980222d5c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -265,6 +265,8 @@ add_executable(md5_client bin/md5_client.c bin/prog.c bin/test_common.c bin/test ENDIF() add_executable(echo_server bin/echo_server.c bin/prog.c bin/test_common.c bin/test_cert.c ${GETOPT_C}) add_executable(echo_client bin/echo_client.c bin/prog.c bin/test_common.c bin/test_cert.c ${GETOPT_C}) +add_executable(duck_server bin/duck_server.c bin/prog.c bin/test_common.c bin/test_cert.c ${GETOPT_C}) +add_executable(duck_client bin/duck_client.c bin/prog.c bin/test_common.c bin/test_cert.c ${GETOPT_C}) IF (NOT MSVC) @@ -300,6 +302,8 @@ TARGET_LINK_LIBRARIES(md5_client ${LIBS}) ENDIF() TARGET_LINK_LIBRARIES(echo_server ${LIBS}) TARGET_LINK_LIBRARIES(echo_client ${LIBS}) +TARGET_LINK_LIBRARIES(duck_server ${LIBS}) +TARGET_LINK_LIBRARIES(duck_client ${LIBS}) add_subdirectory(bin) ENDIF() # LSQUIC_BIN diff --git a/EXAMPLES.txt b/EXAMPLES.txt index c9cb2601b..d1b258415 100644 --- a/EXAMPLES.txt +++ b/EXAMPLES.txt @@ -32,6 +32,14 @@ See bin/http_{client,server}.c This pair of programs is to demonstrate how to use HTTP features of QUIC. HTTP server is interoperable with proto-quic's quic_client. +Duck client and server +---------------------- + +See bin/duck_{client,server}.c + +This pair of programs implement the siduck-00 protocol. They provide an +illustration of using the datagram API. + Usage Examples -------------- diff --git a/README.md b/README.md index 89e516af8..2813b2d63 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,9 @@ and OpenLiteSpeed. We think it is free of major problems. Nevertheless, do not hesitate to report bugs back to us. Even better, send us fixes and improvements! -Currently supported QUIC versions are Q043, Q046, Q050, ID-27, ID-28, and ID-29. -Support for newer versions will be added soon after they are released. +Currently supported QUIC versions are Q043, Q046, Q050, ID-27, ID-28, ID-29, +and ID-30. Support for newer versions will be added soon after they are +released. Documentation ------------- diff --git a/bin/duck_client.c b/bin/duck_client.c new file mode 100644 index 000000000..2d8efcf02 --- /dev/null +++ b/bin/duck_client.c @@ -0,0 +1,189 @@ +/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. */ +/* + * duck_client.c -- The siduck client. See + * https://tools.ietf.org/html/draft-pardue-quic-siduck-00 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef WIN32 +#include +#include +#define Read read +#else +#include "vc_compat.h" +#include "getopt.h" +#include +#define Read _read +#define STDIN_FILENO 0 +#endif + +#include + +#include "lsquic.h" +#include "test_common.h" +#include "prog.h" + +#include "../src/liblsquic/lsquic_logger.h" + +/* Expected request and response of the siduck protocol */ +#define REQUEST "quack" +#define RESPONSE "quack-ack" + +static lsquic_conn_ctx_t * +duck_client_on_new_conn (void *stream_if_ctx, lsquic_conn_t *conn) +{ + LSQ_NOTICE("created a new connection"); + return stream_if_ctx; +} + + +static void +duck_client_on_hsk_done (lsquic_conn_t *conn, enum lsquic_hsk_status s) +{ + if (s == LSQ_HSK_OK || s == LSQ_HSK_RESUMED_OK) + { + if (lsquic_conn_want_datagram_write(conn, 1) < 0) + LSQ_ERROR("want_datagram_write failed"); + } +} + + +static void +duck_client_on_conn_closed (lsquic_conn_t *conn) +{ + lsquic_conn_ctx_t *ctx = lsquic_conn_get_ctx(conn); + LSQ_NOTICE("Connection closed, stop client"); + prog_stop((struct prog *) ctx); +} + + +static ssize_t +duck_client_on_dg_write (lsquic_conn_t *conn, void *buf, size_t sz) +{ + int s; + + /* We only write one request */ + s = lsquic_conn_want_datagram_write(conn, 0); + assert(s == 1); /* Old value was "yes, we want to write a datagram" */ + + if (sz >= sizeof(REQUEST) - 1) + { + LSQ_INFO("wrote `%s' in request", REQUEST); + memcpy(buf, REQUEST, sizeof(REQUEST) - 1); + return sizeof(REQUEST) - 1; + } + else + return -1; +} + + +static void +duck_client_on_datagram (lsquic_conn_t *conn, const void *buf, size_t bufsz) +{ + if (bufsz == sizeof(RESPONSE) - 1 + && 0 == memcmp(buf, RESPONSE, sizeof(RESPONSE) - 1)) + { + LSQ_DEBUG("received the expected `%s' response", RESPONSE); + lsquic_conn_close(conn); + } + else + { + LSQ_NOTICE("unexpected request received, abort connection"); + lsquic_conn_abort(conn); + } +} + + +const struct lsquic_stream_if duck_client_stream_if = { + .on_new_conn = duck_client_on_new_conn, + .on_hsk_done = duck_client_on_hsk_done, + .on_conn_closed = duck_client_on_conn_closed, + .on_dg_write = duck_client_on_dg_write, + .on_datagram = duck_client_on_datagram, +}; + + +static void +usage (const char *prog) +{ + const char *const slash = strrchr(prog, '/'); + if (slash) + prog = slash + 1; + LSQ_NOTICE( +"Usage: %s [opts]\n" +"\n" +"Options:\n" + , prog); +} + + +int +main (int argc, char **argv) +{ + int opt, s; + struct sport_head sports; + struct prog prog; + + TAILQ_INIT(&sports); + prog_init(&prog, 0, &sports, &duck_client_stream_if, &prog); + prog.prog_settings.es_datagrams = 1; + prog.prog_settings.es_init_max_data = 0; + prog.prog_settings.es_init_max_streams_bidi = 0; + prog.prog_settings.es_init_max_streams_uni = 0; + prog.prog_settings.es_max_streams_in = 0; + prog.prog_api.ea_alpn = "siduck-00"; + + while (-1 != (opt = getopt(argc, argv, PROG_OPTS "h"))) + { + switch (opt) { + case 'h': + usage(argv[0]); + prog_print_common_options(&prog, stdout); + exit(0); + default: + if (0 != prog_set_opt(&prog, opt, optarg)) + exit(1); + } + } + +#ifndef WIN32 + int flags = fcntl(STDIN_FILENO, F_GETFL); + flags |= O_NONBLOCK; + if (0 != fcntl(STDIN_FILENO, F_SETFL, flags)) + { + perror("fcntl"); + exit(1); + } +#else + { + u_long on = 1; + ioctlsocket(STDIN_FILENO, FIONBIO, &on); + } +#endif + + if (0 != prog_prep(&prog)) + { + LSQ_ERROR("could not prep"); + exit(EXIT_FAILURE); + } + if (0 != prog_connect(&prog, NULL, 0)) + { + LSQ_ERROR("could not connect"); + exit(EXIT_FAILURE); + } + + LSQ_DEBUG("entering event loop"); + + s = prog_run(&prog); + prog_cleanup(&prog); + + exit(0 == s ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/bin/duck_server.c b/bin/duck_server.c new file mode 100644 index 000000000..ffa2c2515 --- /dev/null +++ b/bin/duck_server.c @@ -0,0 +1,160 @@ +/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. */ +/* + * A duck quacks! The server for the siduck protocol: + * https://tools.ietf.org/html/draft-pardue-quic-siduck-00 + */ + +#include +#include +#include +#include +#include +#include +#include +#ifndef WIN32 +#include +#include +#else +#include "vc_compat.h" +#include "getopt.h" +#endif + +#include "lsquic.h" +#include "../src/liblsquic/lsquic_hash.h" +#include "test_common.h" +#include "test_cert.h" +#include "prog.h" + +#include "../src/liblsquic/lsquic_logger.h" + + +static lsquic_conn_ctx_t * +duck_server_on_new_conn (void *stream_if_ctx, lsquic_conn_t *conn) +{ + LSQ_NOTICE("New siduck connection established!"); + return NULL; +} + + +static void +duck_server_on_conn_closed (lsquic_conn_t *conn) +{ + LSQ_NOTICE("siduck connection closed"); +} + + +/* Expected request and response of the siduck protocol */ +#define REQUEST "quack" +#define RESPONSE "quack-ack" + + +static ssize_t +duck_on_dg_write (lsquic_conn_t *conn, void *buf, size_t sz) +{ + int s; + + /* We only write one response */ + s = lsquic_conn_want_datagram_write(conn, 0); + assert(s == 1); /* Old value was "yes" */ + + if (sz >= sizeof(RESPONSE) - 1) + { + LSQ_INFO("wrote `%s' in response", RESPONSE); + memcpy(buf, RESPONSE, sizeof(RESPONSE) - 1); + lsquic_conn_close(conn); /* Close connection right away */ + return sizeof(RESPONSE) - 1; + } + else + return -1; +} + + +static void +duck_on_datagram (lsquic_conn_t *conn, const void *buf, size_t bufsz) +{ + int s; + + if (bufsz == sizeof(REQUEST) - 1 + && 0 == memcmp(buf, REQUEST, sizeof(REQUEST) - 1)) + { + LSQ_DEBUG("received the expected `%s' request", REQUEST); + s = lsquic_conn_want_datagram_write(conn, 1); + assert(s == 0); + } + else + { + LSQ_NOTICE("unexpected request received, abort connection"); + lsquic_conn_abort(conn); + } +} + + +const struct lsquic_stream_if duck_server_stream_if = { + .on_new_conn = duck_server_on_new_conn, + .on_conn_closed = duck_server_on_conn_closed, + .on_dg_write = duck_on_dg_write, + .on_datagram = duck_on_datagram, +}; + + +static void +usage (const char *prog) +{ + const char *const slash = strrchr(prog, '/'); + if (slash) + prog = slash + 1; + printf( +"Usage: %s [opts]\n" +"\n" +"Options:\n" + , prog); +} + + +int +main (int argc, char **argv) +{ + int opt, s; + struct prog prog; + struct sport_head sports; + + TAILQ_INIT(&sports); + prog_init(&prog, LSENG_SERVER, &sports, &duck_server_stream_if, NULL); + prog.prog_settings.es_datagrams = 1; + prog.prog_settings.es_init_max_data = 0; + prog.prog_settings.es_init_max_streams_bidi = 0; + prog.prog_settings.es_init_max_streams_uni = 0; + prog.prog_settings.es_max_streams_in = 0; + + while (-1 != (opt = getopt(argc, argv, PROG_OPTS "h"))) + { + switch (opt) { + case 'h': + usage(argv[0]); + prog_print_common_options(&prog, stdout); + exit(0); + default: + if (0 != prog_set_opt(&prog, opt, optarg)) + exit(1); + } + } + + if (0 != add_alpn("siduck-00")) + { + LSQ_ERROR("could not add ALPN"); + exit(EXIT_FAILURE); + } + + if (0 != prog_prep(&prog)) + { + LSQ_ERROR("could not prep"); + exit(EXIT_FAILURE); + } + + LSQ_DEBUG("entering event loop"); + + s = prog_run(&prog); + prog_cleanup(&prog); + + exit(0 == s ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/bin/http_server.c b/bin/http_server.c index 12d7678b5..bc8a94cc5 100644 --- a/bin/http_server.c +++ b/bin/http_server.c @@ -29,9 +29,11 @@ #include #include "lsquic.h" +#include "../src/liblsquic/lsquic_hash.h" #include "lsxpack_header.h" #include "test_config.h" #include "test_common.h" +#include "test_cert.h" #include "prog.h" #if HAVE_REGEX @@ -1764,6 +1766,7 @@ main (int argc, char **argv) struct stat st; struct server_ctx server_ctx; struct prog prog; + const char *const *alpn; #if !(HAVE_OPEN_MEMSTREAM || HAVE_REGEX) fprintf(stderr, "cannot run server without regex or open_memstream\n"); @@ -1846,6 +1849,18 @@ main (int argc, char **argv) exit(EXIT_FAILURE); } + alpn = lsquic_get_h3_alpns(prog.prog_settings.es_versions); + while (*alpn) + { + if (0 == add_alpn(*alpn)) + ++alpn; + else + { + LSQ_ERROR("cannot add ALPN %s", *alpn); + exit(EXIT_FAILURE); + } + } + if (0 != prog_prep(&prog)) { LSQ_ERROR("could not prep"); diff --git a/bin/prog.c b/bin/prog.c index c42cb29be..0ad79a775 100644 --- a/bin/prog.c +++ b/bin/prog.c @@ -357,6 +357,12 @@ prog_set_opt (struct prog *prog, int opt, const char *arg) } } prog->prog_keylog_dir = optarg; + if (prog->prog_settings.es_ql_bits) + { + LSQ_NOTICE("QL loss bits turned off because of -G. If you want " + "to turn it on, just override: -G dir -o ql_bits=2"); + prog->prog_settings.es_ql_bits = 0; + } return 0; #else LSQ_ERROR("key logging is not supported on Windows"); diff --git a/bin/test_cert.c b/bin/test_cert.c index 40bd15ad9..8a05070a5 100644 --- a/bin/test_cert.c +++ b/bin/test_cert.c @@ -17,15 +17,36 @@ #include "test_cert.h" +static char s_alpn[0x100]; + +int +add_alpn (const char *alpn) +{ + size_t alpn_len, all_len; + + alpn_len = strlen(alpn); + if (alpn_len > 255) + return -1; + + all_len = strlen(s_alpn); + if (all_len + 1 + alpn_len + 1 > sizeof(s_alpn)) + return -1; + + s_alpn[all_len] = alpn_len; + memcpy(&s_alpn[all_len + 1], alpn, alpn_len); + s_alpn[all_len + 1 + alpn_len] = '\0'; + return 0; +} + + static int select_alpn (SSL *ssl, const unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg) { - const unsigned char alpn[] = "\x5h3-27\x5h3-28\x5h3-29"; int r; r = SSL_select_next_proto((unsigned char **) out, outlen, in, inlen, - alpn, sizeof(alpn)); + (unsigned char *) s_alpn, strlen(s_alpn)); if (r == OPENSSL_NPN_NEGOTIATED) return SSL_TLSEXT_ERR_OK; else diff --git a/bin/test_cert.h b/bin/test_cert.h index b4ce9655c..60898a5f3 100644 --- a/bin/test_cert.h +++ b/bin/test_cert.h @@ -24,5 +24,7 @@ lookup_cert (void *cert_lu_ctx, const struct sockaddr * /*unused */, void delete_certs (struct lsquic_hash *); +int +add_alpn (const char *alpn); #endif diff --git a/docs/apiref.rst b/docs/apiref.rst index 2e3ec69b2..7113af26f 100644 --- a/docs/apiref.rst +++ b/docs/apiref.rst @@ -58,6 +58,10 @@ developed by the IETF. Both types are included in a single enum: IETF QUIC version ID 29 + .. member:: LSQVER_ID30 + + IETF QUIC version ID 30 + .. member:: N_LSQVER Special value indicating the number of versions in the enum. It @@ -695,11 +699,25 @@ settings structure: Congestion control algorithm to use. - - 0: Use default (:macro:`LSQUIC_DF_CC_ALGO)` + - 0: Use default (:macro:`LSQUIC_DF_CC_ALGO`) - 1: Cubic - - 2: BBR + - 2: BBRv1 + - 3: Adaptive congestion control. - IETF QUIC only. + Adaptive congestion control adapts to the environment. It figures + out whether to use Cubic or BBRv1 based on the RTT. + + .. member:: unsigned es_cc_rtt_thresh + + Congestion controller RTT threshold in microseconds. + + Adaptive congestion control uses BBRv1 until RTT is determined. At + that point a permanent choice of congestion controller is made. If + RTT is smaller than or equal to + :member:`lsquic_engine_settings.es_cc_rtt_thresh`, congestion + controller is switched to Cubic; otherwise, BBRv1 is picked. + + The default value is :macro:`LSQUIC_DF_CC_RTT_THRESH` .. member:: int es_ql_bits @@ -802,6 +820,20 @@ settings structure: Default value is :macro:`LSQUIC_DF_GREASE_QUIC_BIT` + .. member:: int es_datagrams + + Enable datagrams extension. Allowed values are 0 and 1. + + Default value is :macro:`LSQUIC_DF_DATAGRAMS` + + .. member:: int es_optimistic_nat + + If set to true, changes in peer port are assumed to be due to a + benign NAT rebinding and path characteristics -- MTU, RTT, and + CC state -- are not reset. + + Default value is :macro:`LSQUIC_DF_OPTIMISTIC_NAT` + To initialize the settings structure to library defaults, use the following convenience function: @@ -971,7 +1003,11 @@ out of date. Please check your :file:`lsquic.h` for actual values.* .. macro:: LSQUIC_DF_CC_ALGO - Use Cubic by default. + Use Adaptive Congestion Controller by default. + +.. macro:: LSQUIC_DF_CC_RTT_THRESH + + Default value of the CC RTT threshold is 1500 microseconds .. macro:: LSQUIC_DF_DELAYED_ACKS @@ -1010,6 +1046,18 @@ out of date. Please check your :file:`lsquic.h` for actual values.* By default, greasing the QUIC bit is enabled (if peer sent the "grease_quic_bit" transport parameter). +.. macro:: LSQUIC_DF_TIMESTAMPS + + Timestamps are on by default. + +.. macro:: LSQUIC_DF_DATAGRAMS + + Datagrams are off by default. + +.. macro:: LSQUIC_DF_OPTIMISTIC_NAT + + Assume optimistic NAT by default. + Receiving Packets ----------------- @@ -1220,6 +1268,19 @@ the engine to communicate with the user code: This callback is optional. + .. member:: ssize_t (*on_dg_write)(lsquic_conn_t *c, void *buf, size_t buf_sz) + + Called when datagram is ready to be written. Write at most + ``buf_sz`` bytes to ``buf`` and return number of bytes + written. + + .. member:: void (*on_datagram)(lsquic_conn_t *c, const void *buf, size_t sz) + + Called when datagram is read from a packet. This callback is + required when :member:`lsquic_engine_settings.es_datagrams` is true. + Take care to process it quickly, as this is called during + :func:`lsquic_engine_packet_in()`. + Creating Connections -------------------- @@ -2077,7 +2138,7 @@ List of Log Modules The following log modules are defined: - *alarmset*: Alarm processing. -- *bbr*: BBR congestion controller. +- *bbr*: BBRv1 congestion controller. - *bw-sampler*: Bandwidth sampler (used by BBR). - *cfcw*: Connection flow control window. - *conn*: Connection. @@ -2115,3 +2176,36 @@ The following log modules are defined: - *stream*: Stream operation. - *tokgen*: Token generation and validation. - *trapa*: Transport parameter processing. + +.. _apiref-datagrams: + +Datagrams +--------- + +lsquic supports the +`Unreliable Datagram Extension `_. +To enable datagrams, set :member:`lsquic_engine_settings.es_datagrams` to +true and specify +:member:`lsquic_stream_if.on_datagram` +and +:member:`lsquic_stream_if.on_dg_write` callbacks. + +.. function:: int lsquic_conn_want_datagram_write (lsquic_conn_t *conn, int want) + + Indicate desire (or lack thereof) to write a datagram. + + :param conn: Connection on which to send a datagram. + :param want: Boolean value indicating whether the caller wants to write + a datagram. + :return: Previous value of ``want`` or ``-1`` if the datagrams cannot be + written. + +.. function:: size_t lsquic_conn_get_min_datagram_size (lsquic_conn_t *conn) + + Get minimum datagram size. By default, this value is zero. + +.. function:: int lsquic_conn_set_min_datagram_size (lsquic_conn_t *conn, size_t sz) + + Set minimum datagram size. This is the minumum value of the buffer + passed to the :member:`lsquic_stream_if.on_dg_write` callback. + Returns 0 on success and -1 on error. diff --git a/docs/conf.py b/docs/conf.py index 03de61e22..131098d5f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -24,9 +24,9 @@ author = u'LiteSpeed Technologies' # The short X.Y version -version = u'2.19' +version = u'2.20' # The full version, including alpha/beta/rc tags -release = u'2.19.10' +release = u'2.20.0' # -- General configuration --------------------------------------------------- diff --git a/docs/index.rst b/docs/index.rst index c1563acec..b6542ca2e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -17,7 +17,7 @@ Most of the code in this distribution has been used in our own products since 2017. Currently supported QUIC versions are Q043, Q046, Q050, ID-27, ID-28, -and ID-29. +ID-29, and ID-30. Support for newer versions will be added soon after they are released. LSQUIC is licensed under the `MIT License`_; see LICENSE in the source @@ -37,6 +37,7 @@ LSQUIC supports nearly all QUIC and HTTP/3 features, including - TLS Key updates - Extensions: + - :ref:`apiref-datagrams` - Loss bits extension (allowing network observer to locate source of packet loss) - Timestamps extension (allowing for one-way delay calculation, improving performance of some congestion controllers) - Delayed ACKs (this reduces number of ACK frames sent and processed, improving throughput) diff --git a/include/lsquic.h b/include/lsquic.h index e0463c530..f80f58069 100644 --- a/include/lsquic.h +++ b/include/lsquic.h @@ -24,8 +24,8 @@ extern "C" { #endif #define LSQUIC_MAJOR_VERSION 2 -#define LSQUIC_MINOR_VERSION 19 -#define LSQUIC_PATCH_VERSION 10 +#define LSQUIC_MINOR_VERSION 20 +#define LSQUIC_PATCH_VERSION 0 /** * Engine flags: @@ -91,6 +91,11 @@ enum lsquic_version */ LSQVER_ID29, + /** + * IETF QUIC Draft-30 + */ + LSQVER_ID30, + /** * Special version to trigger version negotiation. * [draft-ietf-quic-transport-11], Section 3. @@ -101,7 +106,8 @@ enum lsquic_version }; /** - * We currently support versions 43, 46, 50, Draft-27, Draft-28, and Draft-29. + * We currently support versions 43, 46, 50, Draft-27, Draft-28, Draft-29, + * and Draft-30. * @see lsquic_version */ #define LSQUIC_SUPPORTED_VERSIONS ((1 << N_LSQVER) - 1) @@ -114,15 +120,15 @@ enum lsquic_version #define LSQUIC_EXPERIMENTAL_VERSIONS ( \ (1 << LSQVER_VERNEG) | LSQUIC_EXPERIMENTAL_Q098) -#define LSQUIC_DEPRECATED_VERSIONS 0 +#define LSQUIC_DEPRECATED_VERSIONS (1 << LSQVER_ID28) #define LSQUIC_GQUIC_HEADER_VERSIONS (1 << LSQVER_043) #define LSQUIC_IETF_VERSIONS ((1 << LSQVER_ID27) | (1 << LSQVER_ID28) \ - | (1 << LSQVER_ID29) | (1 << LSQVER_VERNEG)) + | (1 << LSQVER_ID29) | (1 << LSQVER_ID30) | (1 << LSQVER_VERNEG)) #define LSQUIC_IETF_DRAFT_VERSIONS ((1 << LSQVER_ID27) | (1 << LSQVER_ID28) \ - | (1 << LSQVER_ID29) | (1 << LSQVER_VERNEG)) + | (1 << LSQVER_ID29) | (1 << LSQVER_ID30) | (1 << LSQVER_VERNEG)) enum lsquic_hsk_status { @@ -179,6 +185,13 @@ struct lsquic_stream_if { void (*on_read) (lsquic_stream_t *s, lsquic_stream_ctx_t *h); void (*on_write) (lsquic_stream_t *s, lsquic_stream_ctx_t *h); void (*on_close) (lsquic_stream_t *s, lsquic_stream_ctx_t *h); + /* Called when datagram is ready to be written */ + ssize_t (*on_dg_write)(lsquic_conn_t *c, void *, size_t); + /* Called when datagram is read from a packet. This callback is required + * when es_datagrams is true. Take care to process it quickly, as this + * is called during lsquic_engine_packet_in(). + */ + void (*on_datagram)(lsquic_conn_t *, const void *buf, size_t); /* This callback in only called in client mode */ /** * When handshake is completed, this optional callback is called. @@ -338,8 +351,17 @@ typedef struct ssl_ctx_st * (*lsquic_lookup_cert_f)( /** Turn on timestamp extension by default */ #define LSQUIC_DF_TIMESTAMPS 1 -/* 1: Cubic; 2: BBR */ -#define LSQUIC_DF_CC_ALGO 1 +/* Use Adaptive CC by default */ +#define LSQUIC_DF_CC_ALGO 3 + +/* Default value of the CC RTT threshold is 1.5 ms */ +#define LSQUIC_DF_CC_RTT_THRESH 1500 + +/** Turn off datagram extension by default */ +#define LSQUIC_DF_DATAGRAMS 0 + +/** Assume optimistic NAT by default. */ +#define LSQUIC_DF_OPTIMISTIC_NAT 1 /** By default, incoming packet size is not limited. */ #define LSQUIC_DF_MAX_UDP_PAYLOAD_SIZE_RX 0 @@ -565,6 +587,8 @@ struct lsquic_engine_settings { * @ref lsquic_stream_wantread() or @ref lsquic_stream_wantwrite() * or shuts down the stream. * + * This also applies to the on_dg_write() callback. + * * The default value is @ref LSQUIC_DF_RW_ONCE. */ int es_rw_once; @@ -605,10 +629,23 @@ struct lsquic_engine_settings { * * 0: Use default (@ref LSQUIC_DF_CC_ALGO) * 1: Cubic - * 2: BBR + * 2: BBRv1 + * 3: Adaptive (Cubic or BBRv1) */ unsigned es_cc_algo; + /** + * Congestion controller RTT threshold in microseconds. + * + * Adaptive congestion control uses BBRv1 until RTT is determined. At + * that point a permanent choice of congestion controller is made. If + * RTT is smaller than or equal to es_cc_rtt_thresh, congestion + * controller is switched to Cubic; otherwise, BBRv1 is picked. + * + * The default value is @ref LSQUIC_DF_CC_RTT_THRESH. + */ + unsigned es_cc_rtt_thresh; + /** * No progress timeout. * @@ -878,6 +915,22 @@ struct lsquic_engine_settings { * Default value is @ref LSQUIC_DF_MTU_PROBE_TIMER. */ unsigned es_mtu_probe_timer; + + /** + * Enable datagram extension. Allowed values are 0 and 1. + * + * Default value is @ref LSQUIC_DF_DATAGRAMS + */ + int es_datagrams; + + /** + * If set to true, changes in peer port are assumed to be due to a + * benign NAT rebinding and path characteristics -- MTU, RTT, and + * CC state -- are not reset. + * + * Default value is @ref LSQUIC_DF_OPTIMISTIC_NAT. + */ + int es_optimistic_nat; }; /* Initialize `settings' to default values */ @@ -1587,6 +1640,20 @@ int lsquic_conn_get_sockaddr(lsquic_conn_t *c, const struct sockaddr **local, const struct sockaddr **peer); +/* Returns previous value */ +int +lsquic_conn_want_datagram_write (lsquic_conn_t *, int is_want); + +/* Get minimum datagram size. By default, this value is zero. */ +size_t +lsquic_conn_get_min_datagram_size (lsquic_conn_t *); + +/* Set minimum datagram size. This is the minumum value of the buffer passed + * to the on_dg_write() callback. + */ +int +lsquic_conn_set_min_datagram_size (lsquic_conn_t *, size_t sz); + struct lsquic_logger_if { int (*log_buf)(void *logger_ctx, const char *buf, size_t len); }; diff --git a/src/liblsquic/CMakeLists.txt b/src/liblsquic/CMakeLists.txt index dd5395110..998192cb5 100644 --- a/src/liblsquic/CMakeLists.txt +++ b/src/liblsquic/CMakeLists.txt @@ -1,6 +1,7 @@ # Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. SET(lsquic_STAT_SRCS ls-qpack/lsqpack.c + lsquic_adaptive_cc.c lsquic_alarmset.c lsquic_arr.c lsquic_attq.c diff --git a/src/liblsquic/lsquic_adaptive_cc.c b/src/liblsquic/lsquic_adaptive_cc.c new file mode 100644 index 000000000..8f6caa5a5 --- /dev/null +++ b/src/liblsquic/lsquic_adaptive_cc.c @@ -0,0 +1,212 @@ +/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. */ +/* lsquic_adaptive_cc.c -- adaptive congestion controller */ + +#include +#include +#include +#include +#include +#include +#ifdef WIN32 +#include +#endif + +#include "lsquic_int_types.h" +#include "lsquic_types.h" +#include "lsquic_hash.h" +#include "lsquic_util.h" +#include "lsquic_cong_ctl.h" +#include "lsquic_sfcw.h" +#include "lsquic_conn_flow.h" +#include "lsquic_varint.h" +#include "lsquic_hq.h" +#include "lsquic_stream.h" +#include "lsquic_rtt.h" +#include "lsquic_conn_public.h" +#include "lsquic_packet_common.h" +#include "lsquic_packet_out.h" +#include "lsquic_bw_sampler.h" +#include "lsquic_minmax.h" +#include "lsquic_bbr.h" +#include "lsquic_cubic.h" +#include "lsquic_adaptive_cc.h" + +#define LSQUIC_LOGGER_MODULE LSQLM_ADAPTIVE_CC +#define LSQUIC_LOG_CONN_ID lsquic_conn_log_cid(acc->acc_cubic.cu_conn) +#include "lsquic_logger.h" + + +#define CALL_BOTH(method, ...) do { \ + lsquic_cong_bbr_if.method(&acc->acc_bbr, __VA_ARGS__); \ + lsquic_cong_cubic_if.method(&acc->acc_cubic, __VA_ARGS__); \ +} while (0) + + +#define CALL_BOTH_MAYBE(method, ...) do { \ + if (lsquic_cong_bbr_if.method) \ + lsquic_cong_bbr_if.method(&acc->acc_bbr, __VA_ARGS__); \ + if (lsquic_cong_cubic_if.method) \ + lsquic_cong_cubic_if.method(&acc->acc_cubic, __VA_ARGS__); \ +} while (0) + + +#define CALL_BOTH0(method) do { \ + lsquic_cong_bbr_if.method(&acc->acc_bbr); \ + lsquic_cong_cubic_if.method(&acc->acc_cubic); \ +} while (0) + + +static void +adaptive_cc_init (void *cong_ctl, const struct lsquic_conn_public *conn_pub, + enum quic_ft_bit retx_frames) +{ + struct adaptive_cc *const acc = cong_ctl; + + CALL_BOTH(cci_init, conn_pub, retx_frames); + LSQ_DEBUG("initialized"); +} + + +static void +adaptive_cc_reinit (void *cong_ctl) +{ + struct adaptive_cc *const acc = cong_ctl; + + CALL_BOTH0(cci_reinit); +} + + +static void +adaptive_cc_ack (void *cong_ctl, struct lsquic_packet_out *packet_out, + unsigned packet_sz, lsquic_time_t now, int app_limited) +{ + struct adaptive_cc *const acc = cong_ctl; + + CALL_BOTH(cci_ack, packet_out, packet_sz, now, app_limited); +} + + +static void +adaptive_cc_loss (void *cong_ctl) +{ + struct adaptive_cc *const acc = cong_ctl; + + CALL_BOTH0(cci_loss); +} + + +static void +adaptive_cc_begin_ack (void *cong_ctl, lsquic_time_t ack_time, + uint64_t in_flight) +{ + struct adaptive_cc *const acc = cong_ctl; + + CALL_BOTH_MAYBE(cci_begin_ack, ack_time, in_flight); +} + + +static void +adaptive_cc_end_ack (void *cong_ctl, uint64_t in_flight) +{ + struct adaptive_cc *const acc = cong_ctl; + + CALL_BOTH_MAYBE(cci_end_ack, in_flight); +} + + +static void +adaptive_cc_sent (void *cong_ctl, struct lsquic_packet_out *packet_out, + uint64_t in_flight, int app_limited) +{ + struct adaptive_cc *const acc = cong_ctl; + + CALL_BOTH_MAYBE(cci_sent, packet_out, in_flight, app_limited); +} + + +static void +adaptive_cc_lost (void *cong_ctl, struct lsquic_packet_out *packet_out, + unsigned packet_sz) +{ + struct adaptive_cc *const acc = cong_ctl; + + CALL_BOTH_MAYBE(cci_lost, packet_out, packet_sz); +} + + +static void +adaptive_cc_timeout (void *cong_ctl) +{ + struct adaptive_cc *const acc = cong_ctl; + + CALL_BOTH0(cci_timeout); +} + + +static void +adaptive_cc_was_quiet (void *cong_ctl, lsquic_time_t now, uint64_t in_flight) +{ + struct adaptive_cc *const acc = cong_ctl; + + CALL_BOTH(cci_was_quiet, now, in_flight); +} + + +static uint64_t +adaptive_cc_get_cwnd (void *cong_ctl) +{ + struct adaptive_cc *const acc = cong_ctl; + uint64_t rv[2]; + + rv[0] = lsquic_cong_cubic_if.cci_get_cwnd(&acc->acc_cubic); + rv[1] = lsquic_cong_bbr_if.cci_get_cwnd(&acc->acc_bbr); + + if (acc->acc_flags & ACC_CUBIC) + return rv[0]; + else + return rv[1]; +} + + +static uint64_t +adaptive_cc_pacing_rate (void *cong_ctl, int in_recovery) +{ + struct adaptive_cc *const acc = cong_ctl; + uint64_t rv[2]; + + rv[0] = lsquic_cong_cubic_if.cci_pacing_rate(&acc->acc_cubic, in_recovery); + rv[1] = lsquic_cong_bbr_if.cci_pacing_rate(&acc->acc_bbr, in_recovery); + + if (acc->acc_flags & ACC_CUBIC) + return rv[0]; + else + return rv[1]; +} + + +static void +adaptive_cc_cleanup (void *cong_ctl) +{ + struct adaptive_cc *const acc = cong_ctl; + + CALL_BOTH0(cci_cleanup); + LSQ_DEBUG("cleanup"); +} + + +const struct cong_ctl_if lsquic_cong_adaptive_if = +{ + .cci_ack = adaptive_cc_ack, + .cci_begin_ack = adaptive_cc_begin_ack, + .cci_end_ack = adaptive_cc_end_ack, + .cci_cleanup = adaptive_cc_cleanup, + .cci_get_cwnd = adaptive_cc_get_cwnd, + .cci_init = adaptive_cc_init, + .cci_pacing_rate = adaptive_cc_pacing_rate, + .cci_loss = adaptive_cc_loss, + .cci_lost = adaptive_cc_lost, + .cci_reinit = adaptive_cc_reinit, + .cci_timeout = adaptive_cc_timeout, + .cci_sent = adaptive_cc_sent, + .cci_was_quiet = adaptive_cc_was_quiet, +}; diff --git a/src/liblsquic/lsquic_adaptive_cc.h b/src/liblsquic/lsquic_adaptive_cc.h new file mode 100644 index 000000000..64a298a34 --- /dev/null +++ b/src/liblsquic/lsquic_adaptive_cc.h @@ -0,0 +1,24 @@ +/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. */ +/* + * lsquic_adaptive_cc.h -- Adaptive congestion controller + * + * The controller begins using BBRv1, but keeps Cubic state as well. + * When RTT is known, we pick either Cubic (small RTT) or BBRv1 (large + * RTT). + */ + +#ifndef LSQUIC_ADAPTIVE_CC_H +#define LSQUIC_ADAPTIVE_CC_H 1 + +struct adaptive_cc +{ + struct lsquic_cubic acc_cubic; + struct lsquic_bbr acc_bbr; + enum { + ACC_CUBIC, /* If set, use Cubic; otherwise, use BBR */ + } acc_flags; +}; + +extern const struct cong_ctl_if lsquic_cong_adaptive_if; + +#endif diff --git a/src/liblsquic/lsquic_bw_sampler.c b/src/liblsquic/lsquic_bw_sampler.c index d76348b4f..0885b4a00 100644 --- a/src/liblsquic/lsquic_bw_sampler.c +++ b/src/liblsquic/lsquic_bw_sampler.c @@ -58,7 +58,10 @@ lsquic_bw_sampler_cleanup (struct bw_sampler *sampler) if (sampler->bws_conn) LSQ_DEBUG("cleanup"); if (sampler->bws_malo) + { lsquic_malo_destroy(sampler->bws_malo); + sampler->bws_malo = NULL; + } } diff --git a/src/liblsquic/lsquic_conn.c b/src/liblsquic/lsquic_conn.c index dd42a8300..80f576e1a 100644 --- a/src/liblsquic/lsquic_conn.c +++ b/src/liblsquic/lsquic_conn.c @@ -267,3 +267,33 @@ lsquic_conn_log_cid (const struct lsquic_conn *lconn) return lconn->cn_if->ci_get_log_cid(lconn); return CN_SCID(lconn); } + + +int +lsquic_conn_want_datagram_write (struct lsquic_conn *lconn, int is_want) +{ + if (lconn->cn_if && lconn->cn_if->ci_want_datagram_write) + return lconn->cn_if->ci_want_datagram_write(lconn, is_want); + else + return -1; +} + + +int +lsquic_conn_set_min_datagram_size (struct lsquic_conn *lconn, size_t sz) +{ + if (lconn->cn_if && lconn->cn_if->ci_set_min_datagram_size) + return lconn->cn_if->ci_set_min_datagram_size(lconn, sz); + else + return -1; +} + + +size_t +lsquic_conn_get_min_datagram_size (struct lsquic_conn *lconn) +{ + if (lconn->cn_if && lconn->cn_if->ci_get_min_datagram_size) + return lconn->cn_if->ci_get_min_datagram_size(lconn); + else + return 0; +} diff --git a/src/liblsquic/lsquic_conn.h b/src/liblsquic/lsquic_conn.h index b4d323329..b7f9374fa 100644 --- a/src/liblsquic/lsquic_conn.h +++ b/src/liblsquic/lsquic_conn.h @@ -91,6 +91,12 @@ struct ack_state uint32_t arr[6]; }; +struct to_coal +{ + const struct lsquic_packet_out *prev_packet; + size_t prev_sz_sum; +}; + struct conn_iface { enum tick_st @@ -108,7 +114,7 @@ struct conn_iface * for by the congestion controller. */ struct lsquic_packet_out * - (*ci_next_packet_to_send) (struct lsquic_conn *, size_t); + (*ci_next_packet_to_send) (struct lsquic_conn *, const struct to_coal *); void (*ci_packet_sent) (struct lsquic_conn *, struct lsquic_packet_out *); @@ -270,6 +276,18 @@ struct conn_iface void (*ci_ack_rollback) (struct lsquic_conn *, struct ack_state *); + + /* Optional method. */ + int + (*ci_want_datagram_write) (struct lsquic_conn *, int); + + /* Optional method */ + int + (*ci_set_min_datagram_size) (struct lsquic_conn *, size_t); + + /* Optional method */ + size_t + (*ci_get_min_datagram_size) (struct lsquic_conn *); }; #define LSCONN_CCE_BITS 3 diff --git a/src/liblsquic/lsquic_enc_sess.h b/src/liblsquic/lsquic_enc_sess.h index c824d2645..200eba438 100644 --- a/src/liblsquic/lsquic_enc_sess.h +++ b/src/liblsquic/lsquic_enc_sess.h @@ -342,6 +342,7 @@ extern const struct enc_session_funcs_iquic lsquic_enc_session_iquic_ietf_v1; ver == LSQVER_ID27 ? &lsquic_enc_session_common_ietf_v1 : \ ver == LSQVER_ID28 ? &lsquic_enc_session_common_ietf_v1 : \ ver == LSQVER_ID29 ? &lsquic_enc_session_common_ietf_v1 : \ + ver == LSQVER_ID30 ? &lsquic_enc_session_common_ietf_v1 : \ ver == LSQVER_VERNEG ? &lsquic_enc_session_common_ietf_v1 : \ ver == LSQVER_050 ? &lsquic_enc_session_common_gquic_2 : \ &lsquic_enc_session_common_gquic_1 ) diff --git a/src/liblsquic/lsquic_enc_sess_ietf.c b/src/liblsquic/lsquic_enc_sess_ietf.c index df5e3cf36..1025dbeb2 100644 --- a/src/liblsquic/lsquic_enc_sess_ietf.c +++ b/src/liblsquic/lsquic_enc_sess_ietf.c @@ -74,7 +74,8 @@ static const struct alpn_map { { LSQVER_ID27, (unsigned char *) "\x05h3-27", }, { LSQVER_ID28, (unsigned char *) "\x05h3-28", }, { LSQVER_ID29, (unsigned char *) "\x05h3-29", }, - { LSQVER_VERNEG, (unsigned char *) "\x05h3-29", }, + { LSQVER_ID30, (unsigned char *) "\x05h3-30", }, + { LSQVER_VERNEG, (unsigned char *) "\x05h3-30", }, }; struct enc_sess_iquic; @@ -571,7 +572,6 @@ gen_trans_params (struct enc_sess_iquic *enc_sess, unsigned char *buf, #endif } #if LSQUIC_TEST_QUANTUM_READINESS - else { const char *s = getenv("LSQUIC_TEST_QUANTUM_READINESS"); if (s && atoi(s)) @@ -627,6 +627,16 @@ gen_trans_params (struct enc_sess_iquic *enc_sess, unsigned char *buf, params.tp_numerics[TPI_TIMESTAMPS] = TS_GENERATE_THEM; params.tp_set |= 1 << TPI_TIMESTAMPS; } + if (settings->es_datagrams) + { + if (params.tp_set & (1 << TPI_MAX_UDP_PAYLOAD_SIZE)) + params.tp_numerics[TPI_MAX_DATAGRAM_FRAME_SIZE] + = params.tp_max_udp_payload_size; + else + params.tp_numerics[TPI_MAX_DATAGRAM_FRAME_SIZE] + = TP_DEF_MAX_UDP_PAYLOAD_SIZE; + params.tp_set |= 1 << TPI_MAX_DATAGRAM_FRAME_SIZE; + } len = (version == LSQVER_ID27 ? lsquic_tp_encode_27 : lsquic_tp_encode)( ¶ms, enc_sess->esi_flags & ESI_SERVER, buf, bufsz); @@ -1220,7 +1230,11 @@ iquic_esfi_init_server (enc_session_t *enc_session_p) SSL_CTX *ssl_ctx = NULL; union { char errbuf[ERR_ERROR_STRING_BUF_LEN]; - unsigned char trans_params[sizeof(struct transport_params)]; + unsigned char trans_params[sizeof(struct transport_params) +#if LSQUIC_TEST_QUANTUM_READINESS + + 4 + lsquic_tp_get_quantum_sz() +#endif + ]; } u; if (enc_sess->esi_enpub->enp_alpn) @@ -1396,7 +1410,7 @@ init_client (struct enc_sess_iquic *const enc_sess) #define hexbuf errbuf /* This is a dual-purpose buffer */ unsigned char trans_params[0x80 #if LSQUIC_TEST_QUANTUM_READINESS - + 4 + QUANTUM_READY_SZ + + 4 + lsquic_tp_get_quantum_sz() #endif ]; @@ -2018,6 +2032,7 @@ iquic_esf_encrypt_packet (enc_session_t *enc_session_p, packet_out->po_sent_sz = dst_sz; packet_out->po_flags &= ~PO_IPv6; packet_out->po_flags |= PO_ENCRYPTED|PO_SENT_SZ|(ipv6 << POIPv6_SHIFT); + packet_out->po_dcid_len = packet_out->po_path->np_dcid.len; lsquic_packet_out_set_enc_level(packet_out, enc_level); lsquic_packet_out_set_kp(packet_out, enc_sess->esi_key_phase); diff --git a/src/liblsquic/lsquic_engine.c b/src/liblsquic/lsquic_engine.c index be047587e..02c710bc3 100644 --- a/src/liblsquic/lsquic_engine.c +++ b/src/liblsquic/lsquic_engine.c @@ -58,6 +58,7 @@ #include "lsquic_bw_sampler.h" #include "lsquic_minmax.h" #include "lsquic_bbr.h" +#include "lsquic_adaptive_cc.h" #include "lsquic_set.h" #include "lsquic_conn_flow.h" #include "lsquic_sfcw.h" @@ -359,6 +360,9 @@ lsquic_engine_init_settings (struct lsquic_engine_settings *settings, settings->es_grease_quic_bit = LSQUIC_DF_GREASE_QUIC_BIT; settings->es_mtu_probe_timer = LSQUIC_DF_MTU_PROBE_TIMER; settings->es_dplpmtud = LSQUIC_DF_DPLPMTUD; + settings->es_cc_algo = LSQUIC_DF_CC_ALGO; + settings->es_cc_rtt_thresh = LSQUIC_DF_CC_RTT_THRESH; + settings->es_optimistic_nat = LSQUIC_DF_OPTIMISTIC_NAT; } @@ -418,7 +422,7 @@ lsquic_engine_check_settings (const struct lsquic_engine_settings *settings, return -1; } - if (settings->es_cc_algo > 2) + if (settings->es_cc_algo > 3) { if (err_buf) snprintf(err_buf, err_buf_sz, "Invalid congestion control " @@ -2472,8 +2476,11 @@ send_packets_out (struct lsquic_engine *engine, #endif && iov < batch->iov + sizeof(batch->iov) / sizeof(batch->iov[0])) { - const size_t size = iov_size(packet_iov, iov); - packet_out = conn->cn_if->ci_next_packet_to_send(conn, size); + const struct to_coal to_coal = { + .prev_packet = packet_out, + .prev_sz_sum = iov_size(packet_iov, iov), + }; + packet_out = conn->cn_if->ci_next_packet_to_send(conn, &to_coal); if (packet_out) goto next_coa; } @@ -2829,7 +2836,7 @@ lsquic_engine_packet_in (lsquic_engine_t *engine, break; } - /* [draft-ietf-quic-transport-27] Section 12.2: + /* [draft-ietf-quic-transport-30] Section 12.2: * " Receivers SHOULD ignore any subsequent packets with a different * " Destination Connection ID than the first packet in the datagram. */ diff --git a/src/liblsquic/lsquic_full_conn.c b/src/liblsquic/lsquic_full_conn.c index 14bd1c368..75f9bdfcf 100644 --- a/src/liblsquic/lsquic_full_conn.c +++ b/src/liblsquic/lsquic_full_conn.c @@ -42,6 +42,7 @@ #include "lsquic_bw_sampler.h" #include "lsquic_minmax.h" #include "lsquic_bbr.h" +#include "lsquic_adaptive_cc.h" #include "lsquic_set.h" #include "lsquic_malo.h" #include "lsquic_chsk_stream.h" @@ -3670,10 +3671,11 @@ full_conn_ci_packet_in (lsquic_conn_t *lconn, lsquic_packet_in_t *packet_in) static lsquic_packet_out_t * -full_conn_ci_next_packet_to_send (struct lsquic_conn *lconn, size_t size) +full_conn_ci_next_packet_to_send (struct lsquic_conn *lconn, + const struct to_coal *unused) { struct full_conn *conn = (struct full_conn *) lconn; - return lsquic_send_ctl_next_packet_to_send(&conn->fc_send_ctl, 0); + return lsquic_send_ctl_next_packet_to_send(&conn->fc_send_ctl, NULL); } diff --git a/src/liblsquic/lsquic_full_conn_ietf.c b/src/liblsquic/lsquic_full_conn_ietf.c index b3974a320..cee9c512b 100644 --- a/src/liblsquic/lsquic_full_conn_ietf.c +++ b/src/liblsquic/lsquic_full_conn_ietf.c @@ -42,6 +42,7 @@ #include "lsquic_bw_sampler.h" #include "lsquic_minmax.h" #include "lsquic_bbr.h" +#include "lsquic_adaptive_cc.h" #include "lsquic_send_ctl.h" #include "lsquic_alarmset.h" #include "lsquic_ver_neg.h" @@ -132,9 +133,10 @@ enum ifull_conn_flags IFC_IGNORE_HSK = 1 << 25, IFC_PROC_CRYPTO = 1 << 26, IFC_MIGRA = 1 << 27, - IFC_SPIN = 1 << 28, /* Spin bits are enabled */ + IFC_UNUSED28 = 1 << 28, /* Unused */ IFC_DELAYED_ACKS = 1 << 29, /* Delayed ACKs are enabled */ IFC_TIMESTAMPS = 1 << 30, /* Timestamps are enabled */ + IFC_DATAGRAMS = 1u<< 31, /* Datagrams are enabled */ }; @@ -146,6 +148,7 @@ enum more_flags MF_IGNORE_MISSING = 1 << 3, MF_CONN_CLOSE_PACK = 1 << 4, /* CONNECTION_CLOSE has been packetized */ MF_SEND_WRONG_COUNTS= 1 << 5, /* Send wrong ECN counts to peer */ + MF_WANT_DATAGRAM_WRITE = 1 << 6, }; @@ -305,6 +308,7 @@ struct conn_path struct network_path cop_path; uint64_t cop_path_chals[8]; /* Arbitrary number */ uint64_t cop_inc_chal; /* Incoming challenge */ + lsquic_packno_t cop_max_packno; enum { /* Initialized covers cop_path.np_pack_size and cop_path.np_dcid */ COP_INITIALIZED = 1 << 0, @@ -316,9 +320,12 @@ struct conn_path * original path. */ COP_GOT_NONPROB = 1 << 2, + /* Spin bit is enabled on this path. */ + COP_SPIN_BIT = 1 << 3, } cop_flags; unsigned char cop_n_chals; unsigned char cop_cce_idx; + unsigned char cop_spin_bit; struct dplpmtud_state cop_dplpmtud; }; @@ -336,6 +343,8 @@ struct ietf_full_conn struct lsquic_conn ifc_conn; struct conn_cid_elem ifc_cces[MAX_SCID]; struct lsquic_rechist ifc_rechist[N_PNS]; + /* App PNS only, used to calculate was_missing: */ + lsquic_packno_t ifc_max_ackable_packno_in; struct lsquic_send_ctl ifc_send_ctl; struct lsquic_stream *ifc_stream_hcsi; /* HTTP Control Stream Incoming */ struct lsquic_stream *ifc_stream_hcso; /* HTTP Control Stream Outgoing */ @@ -359,7 +368,6 @@ struct ietf_full_conn struct conn_err ifc_error; unsigned ifc_n_delayed_streams; unsigned ifc_n_cons_unretx; - int ifc_spin_bit; const struct lsquic_stream_if *ifc_stream_if; void *ifc_stream_ctx; @@ -448,6 +456,8 @@ struct ietf_full_conn lsquic_time_t ifc_idle_to; lsquic_time_t ifc_ping_period; uint64_t ifc_last_max_data_off_sent; + unsigned short ifc_min_dg_sz, + ifc_max_dg_sz; struct inc_ack_stats ifc_ias; struct ack_info ifc_ack; }; @@ -459,6 +469,9 @@ struct ietf_full_conn #define DCES_END(conn_) ((conn_)->ifc_dces + (sizeof((conn_)->ifc_dces) \ / sizeof((conn_)->ifc_dces[0]))) +#define NPATH2CPATH(npath_) ((struct conn_path *) \ + ((char *) (npath_) - offsetof(struct conn_path, cop_path))) + static const struct ver_neg server_ver_neg; static const struct conn_iface *ietf_full_conn_iface_ptr; @@ -1098,20 +1111,16 @@ ietf_full_conn_add_scid (struct ietf_full_conn *conn, * " connection IDs. */ static void -maybe_enable_spin (struct ietf_full_conn *conn) +maybe_enable_spin (struct ietf_full_conn *conn, struct conn_path *cpath) { uint8_t nyb; - if (!conn->ifc_settings->es_spin) - { - conn->ifc_flags &= ~IFC_SPIN; - LSQ_DEBUG("spin bit disabled via settings"); - } - else if (lsquic_crand_get_nybble(conn->ifc_enpub->enp_crand)) + if (conn->ifc_settings->es_spin + && lsquic_crand_get_nybble(conn->ifc_enpub->enp_crand)) { - conn->ifc_flags |= IFC_SPIN; - conn->ifc_spin_bit = 0; - LSQ_DEBUG("spin bit enabled"); + cpath->cop_flags |= COP_SPIN_BIT; + cpath->cop_spin_bit = 0; + LSQ_DEBUG("spin bit enabled on path %hhu", cpath->cop_path.np_path_id); } else { @@ -1120,11 +1129,13 @@ maybe_enable_spin (struct ietf_full_conn *conn) * " independently for each connection ID. * (ibid.) */ - conn->ifc_flags &= ~IFC_SPIN; + cpath->cop_flags &= ~COP_SPIN_BIT; nyb = lsquic_crand_get_nybble(conn->ifc_enpub->enp_crand); - conn->ifc_spin_bit = nyb & 1; - LSQ_DEBUG("spin bit randomly disabled; random spin bit value is %d", - conn->ifc_spin_bit); + cpath->cop_spin_bit = nyb & 1; + LSQ_DEBUG("spin bit disabled %s on path %hhu; random spin bit " + "value is %hhu", + !conn->ifc_settings->es_spin ? "via settings" : "randomly", + cpath->cop_path.np_path_id, cpath->cop_spin_bit); } } @@ -1186,6 +1197,7 @@ ietf_full_conn_init (struct ietf_full_conn *conn, conn->ifc_max_ack_packno[PNS_INIT] = IQUIC_INVALID_PACKNO; conn->ifc_max_ack_packno[PNS_HSK] = IQUIC_INVALID_PACKNO; conn->ifc_max_ack_packno[PNS_APP] = IQUIC_INVALID_PACKNO; + conn->ifc_max_ackable_packno_in = 0; conn->ifc_paths[0].cop_path.np_path_id = 0; conn->ifc_paths[1].cop_path.np_path_id = 1; conn->ifc_paths[2].cop_path.np_path_id = 2; @@ -1194,7 +1206,6 @@ ietf_full_conn_init (struct ietf_full_conn *conn, conn->ifc_max_req_id = VINT_MAX_VALUE + 1; conn->ifc_ping_unretx_thresh = 20; conn->ifc_max_retx_since_last_ack = MAX_RETR_PACKETS_SINCE_LAST_ACK; - maybe_enable_spin(conn); if (conn->ifc_settings->es_noprogress_timeout) conn->ifc_mflags |= MF_NOPROG_TIMEOUT; return 0; @@ -1403,6 +1414,7 @@ lsquic_ietf_full_conn_server_new (struct lsquic_engine_public *enpub, conn->ifc_paths[0].cop_path = imc->imc_path; conn->ifc_paths[0].cop_flags = COP_VALIDATED|COP_INITIALIZED; conn->ifc_used_paths = 1 << 0; + maybe_enable_spin(conn, &conn->ifc_paths[0]); if (imc->imc_flags & IMC_ADDR_VALIDATED) lsquic_send_ctl_path_validated(&conn->ifc_send_ctl); else @@ -2720,6 +2732,28 @@ ietf_full_conn_ci_write_ack (struct lsquic_conn *lconn, } +static int +ietf_full_conn_ci_want_datagram_write (struct lsquic_conn *lconn, int is_want) +{ + struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn; + int old; + + if (conn->ifc_flags & IFC_DATAGRAMS) + { + old = !!(conn->ifc_mflags & MF_WANT_DATAGRAM_WRITE); + if (is_want) + conn->ifc_mflags |= MF_WANT_DATAGRAM_WRITE; + else + conn->ifc_mflags &= ~MF_WANT_DATAGRAM_WRITE; + LSQ_DEBUG("turn %s \"want datagram write\" flag", + is_want ? "on" : "off"); + return old; + } + else + return -1; +} + + static void ietf_full_conn_ci_client_call_on_new (struct lsquic_conn *lconn) { @@ -2840,6 +2874,8 @@ ietf_full_conn_ci_retire_cid (struct lsquic_conn *lconn) * Switch DCID. */ *CUR_DCID(conn) = (*dces[0])->de_cid; + if (CUR_CPATH(conn)->cop_flags & COP_SPIN_BIT) + CUR_CPATH(conn)->cop_spin_bit = 0; LSQ_INFOC("switched DCID to %"CID_FMT, CID_BITS(CUR_DCID(conn))); /* * Mark old DCID for retirement. @@ -3348,6 +3384,15 @@ handshake_ok (struct lsquic_conn *lconn) LSQ_DEBUG("timestamps enabled: will send TIMESTAMP frames"); conn->ifc_flags |= IFC_TIMESTAMPS; } + if (conn->ifc_settings->es_datagrams + && (params->tp_set & (1 << TPI_MAX_DATAGRAM_FRAME_SIZE))) + { + LSQ_DEBUG("datagrams enabled"); + conn->ifc_flags |= IFC_DATAGRAMS; + conn->ifc_max_dg_sz = + params->tp_numerics[TPI_MAX_DATAGRAM_FRAME_SIZE] > USHRT_MAX + ? USHRT_MAX : params->tp_numerics[TPI_MAX_DATAGRAM_FRAME_SIZE]; + } conn->ifc_max_peer_ack_usec = params->tp_max_ack_delay * 1000; @@ -3766,6 +3811,11 @@ ietf_full_conn_ci_is_tickable (struct lsquic_conn *lconn) LSQ_DEBUG("tickable: send DATA_BLOCKED frame"); goto check_can_send; } + if (conn->ifc_mflags & MF_WANT_DATAGRAM_WRITE) + { + LSQ_DEBUG("tickable: want to write DATAGRAM frame"); + goto check_can_send; + } if (conn->ifc_conn.cn_flags & LSCONN_HANDSHAKE_DONE ? lsquic_send_ctl_has_buffered(&conn->ifc_send_ctl) : lsquic_send_ctl_has_buffered_high(&conn->ifc_send_ctl)) @@ -4375,26 +4425,32 @@ generate_path_resp_3 (struct ietf_full_conn *conn, lsquic_time_t now) static struct lsquic_packet_out * -ietf_full_conn_ci_next_packet_to_send (struct lsquic_conn *lconn, size_t size) +ietf_full_conn_ci_next_packet_to_send (struct lsquic_conn *lconn, + const struct to_coal *to_coal) { struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn; struct lsquic_packet_out *packet_out; + const struct conn_path *cpath; - packet_out = lsquic_send_ctl_next_packet_to_send(&conn->ifc_send_ctl, size); + packet_out = lsquic_send_ctl_next_packet_to_send(&conn->ifc_send_ctl, + to_coal); if (packet_out) - lsquic_packet_out_set_spin_bit(packet_out, conn->ifc_spin_bit); + { + cpath = NPATH2CPATH(packet_out->po_path); + lsquic_packet_out_set_spin_bit(packet_out, cpath->cop_spin_bit); + } return packet_out; } static struct lsquic_packet_out * ietf_full_conn_ci_next_packet_to_send_pre_hsk (struct lsquic_conn *lconn, - size_t size) + const struct to_coal *to_coal) { struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn; struct lsquic_packet_out *packet_out; - packet_out = ietf_full_conn_ci_next_packet_to_send(lconn, size); + packet_out = ietf_full_conn_ci_next_packet_to_send(lconn, to_coal); if (packet_out) ++conn->ifc_u.cli.ifcli_packets_out; return packet_out; @@ -4618,16 +4674,55 @@ maybe_retire_dcid (struct ietf_full_conn *conn, const lsquic_cid_t *dcid) } +/* Return true if the two paths differ only in peer port */ +static int +only_peer_port_changed (const struct network_path *old, + struct network_path *new) +{ + const struct sockaddr *old_sa, *new_sa; + + if (!lsquic_sockaddr_eq(NP_LOCAL_SA(old), NP_LOCAL_SA(new))) + return 0; + + old_sa = NP_PEER_SA(old); + new_sa = NP_PEER_SA(new); + if (old_sa->sa_family == AF_INET) + return old_sa->sa_family == new_sa->sa_family + && ((struct sockaddr_in *) old_sa)->sin_addr.s_addr + == ((struct sockaddr_in *) new_sa)->sin_addr.s_addr + && ((struct sockaddr_in *) old_sa)->sin_port + != /* NE! */((struct sockaddr_in *) new_sa)->sin_port; + else + return old_sa->sa_family == new_sa->sa_family + && ((struct sockaddr_in6 *) old_sa)->sin6_port != /* NE! */ + ((struct sockaddr_in6 *) new_sa)->sin6_port + && 0 == memcmp(&((struct sockaddr_in6 *) old_sa)->sin6_addr, + &((struct sockaddr_in6 *) new_sa)->sin6_addr, + sizeof(((struct sockaddr_in6 *) new_sa)->sin6_addr)); +} + + static void switch_path_to (struct ietf_full_conn *conn, unsigned char path_id) { const unsigned char old_path_id = conn->ifc_cur_path_id; + const int keep_path_properties = conn->ifc_settings->es_optimistic_nat + && only_peer_port_changed(CUR_NPATH(conn), + &conn->ifc_paths[path_id].cop_path); assert(conn->ifc_cur_path_id != path_id); EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "switched paths"); + if (keep_path_properties) + { + conn->ifc_paths[path_id].cop_path.np_pack_size + = CUR_NPATH(conn)->np_pack_size; + LSQ_DEBUG("keep path properties: set MTU to %hu", + conn->ifc_paths[path_id].cop_path.np_pack_size); + } lsquic_send_ctl_repath(&conn->ifc_send_ctl, - CUR_NPATH(conn), &conn->ifc_paths[path_id].cop_path); + CUR_NPATH(conn), &conn->ifc_paths[path_id].cop_path, + keep_path_properties); maybe_retire_dcid(conn, &CUR_NPATH(conn)->np_dcid); conn->ifc_cur_path_id = path_id; conn->ifc_pub.path = CUR_NPATH(conn); @@ -5600,7 +5695,11 @@ insert_new_dcid (struct ietf_full_conn *conn, uint64_t seqno, memcpy((*dce)->de_srst, token, sizeof((*dce)->de_srst)); (*dce)->de_flags |= DE_SRST; if (update_cur_dcid) + { *CUR_DCID(conn) = *cid; + if (CUR_CPATH(conn)->cop_flags & COP_SPIN_BIT) + CUR_CPATH(conn)->cop_spin_bit = 0; + } } else LSQ_WARN("cannot allocate dce to insert DCID seqno %"PRIu64, seqno); @@ -5742,7 +5841,7 @@ process_retire_connection_id_frame (struct ietf_full_conn *conn, { if (LSQUIC_CIDS_EQ(&cce->cce_cid, &packet_in->pi_dcid)) { - ABORT_QUIETLY(0, TEC_FRAME_ENCODING_ERROR, "cannot retire CID " + ABORT_QUIETLY(0, TEC_PROTOCOL_VIOLATION, "cannot retire CID " "seqno=%"PRIu64", for it is used as DCID in the packet", seqno); return 0; } @@ -6012,6 +6111,35 @@ process_timestamp_frame (struct ietf_full_conn *conn, } +static unsigned +process_datagram_frame (struct ietf_full_conn *conn, + struct lsquic_packet_in *packet_in, const unsigned char *p, size_t len) +{ + const void *data; + size_t data_sz; + int parsed_len; + + if (!(conn->ifc_flags & IFC_DATAGRAMS)) + { + ABORT_QUIETLY(0, TEC_PROTOCOL_VIOLATION, + "Received unexpected DATAGRAM frame (not negotiated)"); + return 0; + } + + parsed_len = conn->ifc_conn.cn_pf->pf_parse_datagram_frame(p, len, + &data, &data_sz); + if (parsed_len < 0) + return 0; + + EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "%zd-byte DATAGRAM", data_sz); + LSQ_DEBUG("%zd-byte DATAGRAM", data_sz); + + conn->ifc_enpub->enp_stream_if->on_datagram(&conn->ifc_conn, data, data_sz); + + return parsed_len; +} + + typedef unsigned (*process_frame_f)( struct ietf_full_conn *, struct lsquic_packet_in *, const unsigned char *p, size_t); @@ -6041,6 +6169,7 @@ static process_frame_f const process_frames[N_QUIC_FRAMES] = [QUIC_FRAME_HANDSHAKE_DONE] = process_handshake_done_frame, [QUIC_FRAME_ACK_FREQUENCY] = process_ack_frequency_frame, [QUIC_FRAME_TIMESTAMP] = process_timestamp_frame, + [QUIC_FRAME_DATAGRAM] = process_datagram_frame, }; @@ -6254,9 +6383,22 @@ force_queueing_ack_app (struct ietf_full_conn *conn) } +enum was_missing { + /* Note that particular enum values matter for speed */ + WM_NONE = 0, + WM_MAX_GAP = 1, /* Newly arrived ackable packet introduced a gap in incoming + * packet number sequence. + */ + WM_SMALLER = 2, /* Newly arrived ackable packet is smaller than previously + * seen maximum number. + */ + +}; + + static void try_queueing_ack_app (struct ietf_full_conn *conn, - int was_missing, int ecn, lsquic_time_t now) + enum was_missing was_missing, int ecn, lsquic_time_t now) { lsquic_time_t srtt, ack_timeout; @@ -6268,8 +6410,10 @@ try_queueing_ack_app (struct ietf_full_conn *conn, */ || (ecn == ECN_CE && lsquic_send_ctl_ecn_turned_on(&conn->ifc_send_ctl)) + || (was_missing == WM_MAX_GAP) || ((conn->ifc_flags & IFC_ACK_HAD_MISS) - && was_missing && conn->ifc_n_slack_akbl[PNS_APP] > 0) + && was_missing == WM_SMALLER + && conn->ifc_n_slack_akbl[PNS_APP] > 0) || many_in_and_will_write(conn)) { lsquic_alarmset_unset(&conn->ifc_alset, AL_ACK_APP); @@ -6279,7 +6423,7 @@ try_queueing_ack_app (struct ietf_full_conn *conn, "was_missing: %d", lsquic_pns2str[PNS_APP], conn->ifc_n_slack_akbl[PNS_APP], conn->ifc_n_slack_all, - !!(conn->ifc_flags & IFC_ACK_HAD_MISS), was_missing); + !!(conn->ifc_flags & IFC_ACK_HAD_MISS), (int) was_missing); } else if (conn->ifc_n_slack_akbl[PNS_APP] > 0) { @@ -6310,17 +6454,6 @@ try_queueing_ack_init_or_hsk (struct ietf_full_conn *conn, } -static void -try_queueing_ack (struct ietf_full_conn *conn, enum packnum_space pns, - int was_missing, int ecn, lsquic_time_t now) -{ - if (PNS_APP == pns) - try_queueing_ack_app(conn, was_missing, ecn, now); - else - try_queueing_ack_init_or_hsk(conn, pns); -} - - static int maybe_queue_opp_ack (struct ietf_full_conn *conn) { @@ -6437,6 +6570,8 @@ process_retry_packet (struct ietf_full_conn *conn, return -1; *CUR_DCID(conn) = scid; + if (CUR_CPATH(conn)->cop_flags & COP_SPIN_BIT) + CUR_CPATH(conn)->cop_spin_bit = 0; lsquic_alarmset_unset(&conn->ifc_alset, AL_RETX_INIT); lsquic_alarmset_unset(&conn->ifc_alset, AL_RETX_HSK); lsquic_alarmset_unset(&conn->ifc_alset, AL_RETX_APP); @@ -6509,9 +6644,6 @@ on_dcid_change (struct ietf_full_conn *conn, const lsquic_cid_t *dcid_in) LSQ_DEBUGC("%s: set SCID to %"CID_FMT, __func__, CID_BITS(CN_SCID(lconn))); LOG_SCIDS(conn); - /* Reset spin bit, see [draft-ietf-quic-transport-20] Section 17.3.1 */ - maybe_enable_spin(conn); - return 0; } @@ -6568,15 +6700,33 @@ record_dcid (struct ietf_full_conn *conn, } +static int +holes_after (struct lsquic_rechist *rechist, lsquic_packno_t packno) +{ + const struct lsquic_packno_range *first_range; + + first_range = lsquic_rechist_peek(rechist); + /* If it's not in the very first range, there is obviously a gap + * between it and the maximum packet number. If the packet number + * in question preceeds the cutoff, we assume that there are no + * holes (as we simply have no information). + */ + return first_range + && packno < first_range->low + && packno > lsquic_rechist_cutoff(rechist); +} + + static int process_regular_packet (struct ietf_full_conn *conn, struct lsquic_packet_in *packet_in) { + struct conn_path *cpath; enum packnum_space pns; enum received_st st; enum dec_packin dec_packin; - enum quic_ft_bit frame_types; - int was_missing, packno_increased; + enum was_missing was_missing; + unsigned n_rechist_packets; unsigned char saved_path_id; if (HETY_RETRY == packet_in->pi_header_type) @@ -6679,8 +6829,7 @@ process_regular_packet (struct ietf_full_conn *conn, EV_LOG_PACKET_IN(LSQUIC_LOG_CONN_ID, packet_in); - packno_increased = packet_in->pi_packno - > lsquic_rechist_largest_packno(&conn->ifc_rechist[pns]); + n_rechist_packets = lsquic_rechist_n_packets(&conn->ifc_rechist[pns]); st = lsquic_rechist_received(&conn->ifc_rechist[pns], packet_in->pi_packno, packet_in->pi_received); switch (st) { @@ -6704,31 +6853,81 @@ process_regular_packet (struct ietf_full_conn *conn, if (lsquic_packet_in_non_probing(packet_in) && packet_in->pi_packno > conn->ifc_max_non_probing) conn->ifc_max_non_probing = packet_in->pi_packno; - if (0 == (conn->ifc_flags & (IFC_ACK_QUED_INIT << pns))) + /* From [draft-ietf-quic-transport-30] Section 13.2.1: + * + " In order to assist loss detection at the sender, an endpoint SHOULD + " generate and send an ACK frame without delay when it receives an ack- + " eliciting packet either: + " + " * when the received packet has a packet number less than another + " ack-eliciting packet that has been received, or + " + " * when the packet has a packet number larger than the highest- + " numbered ack-eliciting packet that has been received and there are + " missing packets between that packet and this packet. + * + */ + if (packet_in->pi_frame_types & IQUIC_FRAME_ACKABLE_MASK) { - frame_types = packet_in->pi_frame_types; - if (frame_types & IQUIC_FRAME_ACKABLE_MASK) + if (PNS_APP == pns /* was_missing is only used in PNS_APP */) { - was_missing = packet_in->pi_packno != - lsquic_rechist_largest_packno(&conn->ifc_rechist[pns]); - ++conn->ifc_n_slack_akbl[pns]; + if (packet_in->pi_packno > conn->ifc_max_ackable_packno_in) + { + was_missing = (enum was_missing) /* WM_MAX_GAP is 1 */ + n_rechist_packets /* Don't count very first packno */ + && conn->ifc_max_ackable_packno_in + 1 + < packet_in->pi_packno + && holes_after(&conn->ifc_rechist[PNS_APP], + conn->ifc_max_ackable_packno_in); + conn->ifc_max_ackable_packno_in = packet_in->pi_packno; + } + else + was_missing = (enum was_missing) /* WM_SMALLER is 2 */ + /* The check is necessary (rather setting was_missing to + * WM_SMALLER) because we cannot guarantee that peer does + * not have bugs. + */ + ((packet_in->pi_packno + < conn->ifc_max_ackable_packno_in) << 1); } else - was_missing = 0; - conn->ifc_n_slack_all += PNS_APP == pns; - try_queueing_ack(conn, pns, was_missing, + was_missing = WM_NONE; + ++conn->ifc_n_slack_akbl[pns]; + } + else + was_missing = WM_NONE; + conn->ifc_n_slack_all += PNS_APP == pns; + if (0 == (conn->ifc_flags & (IFC_ACK_QUED_INIT << pns))) + { + if (PNS_APP == pns) + try_queueing_ack_app(conn, was_missing, lsquic_packet_in_ecn(packet_in), packet_in->pi_received); + else + try_queueing_ack_init_or_hsk(conn, pns); } conn->ifc_incoming_ecn <<= 1; conn->ifc_incoming_ecn |= lsquic_packet_in_ecn(packet_in) != ECN_NOT_ECT; ++conn->ifc_ecn_counts_in[pns][ lsquic_packet_in_ecn(packet_in) ]; - if (packno_increased && PNS_APP == pns && (conn->ifc_flags & IFC_SPIN)) + if (PNS_APP == pns + && (cpath = &conn->ifc_paths[packet_in->pi_path_id], + cpath->cop_flags & COP_SPIN_BIT) + /* [draft-ietf-quic-transport-30] Section 17.3.1 talks about + * how spin bit value is set. + */ + && (packet_in->pi_packno > cpath->cop_max_packno + /* Zero means "unset", in which case any incoming packet + * number will do. On receipt of second packet numbered + * zero, the rechist module will dup it and this code path + * won't hit. + */ + || cpath->cop_max_packno == 0)) { + cpath->cop_max_packno = packet_in->pi_packno; if (conn->ifc_flags & IFC_SERVER) - conn->ifc_spin_bit = lsquic_packet_in_spin_bit(packet_in); + cpath->cop_spin_bit = lsquic_packet_in_spin_bit(packet_in); else - conn->ifc_spin_bit = !lsquic_packet_in_spin_bit(packet_in); + cpath->cop_spin_bit = !lsquic_packet_in_spin_bit(packet_in); } conn->ifc_pub.bytes_in += packet_in->pi_data_sz; if ((conn->ifc_mflags & MF_VALIDATE_PATH) && @@ -7290,6 +7489,88 @@ ietf_full_conn_ci_retx_timeout (struct lsquic_conn *lconn) } +static size_t +ietf_full_conn_ci_get_min_datagram_size (struct lsquic_conn *lconn) +{ + struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn; + return (size_t) conn->ifc_min_dg_sz; +} + + +static int +ietf_full_conn_ci_set_min_datagram_size (struct lsquic_conn *lconn, + size_t new_size) +{ + struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn; + const struct transport_params *const params = + lconn->cn_esf.i->esfi_get_peer_transport_params(lconn->cn_enc_session); + + if (!(conn->ifc_flags & IFC_DATAGRAMS)) + { + LSQ_WARN("datagrams are not enabled: cannot set minimum size"); + return -1; + } + + if (new_size > USHRT_MAX) + { + LSQ_DEBUG("min datagram size cannot be larger than %hu", USHRT_MAX); + return -1; + } + + if (new_size > params->tp_numerics[TPI_MAX_DATAGRAM_FRAME_SIZE]) + { + LSQ_DEBUG("maximum datagram frame size is %"PRIu64", cannot change it " + "to %zd", params->tp_numerics[TPI_MAX_DATAGRAM_FRAME_SIZE], + new_size); + return -1; + } + + conn->ifc_min_dg_sz = new_size; + LSQ_DEBUG("set minimum datagram size to %zd bytes", new_size); + return 0; +} + + +/* Return true if datagram was written, false otherwise */ +static int +write_datagram (struct ietf_full_conn *conn) +{ + struct lsquic_packet_out *packet_out; + size_t need; + int w; + + need = conn->ifc_conn.cn_pf->pf_datagram_frame_size(conn->ifc_min_dg_sz); + packet_out = get_writeable_packet(conn, need); + if (!packet_out) + return 0; + + w = conn->ifc_conn.cn_pf->pf_gen_datagram_frame( + packet_out->po_data + packet_out->po_data_sz, + lsquic_packet_out_avail(packet_out), conn->ifc_min_dg_sz, + conn->ifc_max_dg_sz, + conn->ifc_enpub->enp_stream_if->on_dg_write, &conn->ifc_conn); + if (w < 0) + { + LSQ_DEBUG("could not generate DATAGRAM frame"); + return 0; + } + if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0, + QUIC_FRAME_DATAGRAM, packet_out->po_data_sz, w)) + { + ABORT_ERROR("adding DATAGRAME frame to packet failed: %d", errno); + return 0; + } + packet_out->po_frame_types |= QUIC_FTBIT_DATAGRAM; + lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, w); + /* XXX The DATAGRAM frame should really be a regen. Do it when we + * no longer require these frame types to be at the beginning of the + * packet. + */ + + return 1; +} + + static enum tick_st ietf_full_conn_ci_tick (struct lsquic_conn *lconn, lsquic_time_t now) { @@ -7454,6 +7735,10 @@ ietf_full_conn_ci_tick (struct lsquic_conn *lconn, lsquic_time_t now) if (!write_is_possible(conn)) goto end_write; + while ((conn->ifc_mflags & MF_WANT_DATAGRAM_WRITE) && write_datagram(conn)) + if (!write_is_possible(conn)) + goto end_write; + if (!TAILQ_EMPTY(&conn->ifc_pub.write_streams)) { process_streams_write_events(conn, 1); @@ -7850,8 +8135,11 @@ ietf_full_conn_ci_record_addrs (struct lsquic_conn *lconn, void *peer_ctx, { record_to_path(conn, first_unused, peer_ctx, local_sa, peer_sa); if (0 == conn->ifc_used_paths && !(conn->ifc_flags & IFC_SERVER)) + { /* First path is considered valid immediately */ first_unused->cop_flags |= COP_VALIDATED; + maybe_enable_spin(conn, first_unused); + } LSQ_DEBUG("record new path ID %d", (int) (first_unused - conn->ifc_paths)); conn->ifc_used_paths |= 1 << (first_unused - conn->ifc_paths); @@ -7905,6 +8193,7 @@ ietf_full_conn_ci_count_garbage (struct lsquic_conn *lconn, size_t garbage_sz) .ci_get_ctx = ietf_full_conn_ci_get_ctx, \ .ci_get_engine = ietf_full_conn_ci_get_engine, \ .ci_get_log_cid = ietf_full_conn_ci_get_log_cid, \ + .ci_get_min_datagram_size= ietf_full_conn_ci_get_min_datagram_size, \ .ci_get_path = ietf_full_conn_ci_get_path, \ .ci_going_away = ietf_full_conn_ci_going_away, \ .ci_hsk_done = ietf_full_conn_ci_hsk_done, \ @@ -7922,10 +8211,12 @@ ietf_full_conn_ci_count_garbage (struct lsquic_conn *lconn, size_t garbage_sz) .ci_report_live = ietf_full_conn_ci_report_live, \ .ci_retx_timeout = ietf_full_conn_ci_retx_timeout, \ .ci_set_ctx = ietf_full_conn_ci_set_ctx, \ + .ci_set_min_datagram_size= ietf_full_conn_ci_set_min_datagram_size, \ .ci_status = ietf_full_conn_ci_status, \ .ci_stateless_reset = ietf_full_conn_ci_stateless_reset, \ .ci_tick = ietf_full_conn_ci_tick, \ .ci_tls_alert = ietf_full_conn_ci_tls_alert, \ + .ci_want_datagram_write = ietf_full_conn_ci_want_datagram_write, \ .ci_write_ack = ietf_full_conn_ci_write_ack static const struct conn_iface ietf_full_conn_iface = { @@ -8111,6 +8402,14 @@ on_setting (void *ctx, uint64_t setting_id, uint64_t value) LSQ_DEBUG("received unknown SETTING 0x%"PRIX64"=0x%"PRIX64 "; ignore it", setting_id, value); break; + case 2: /* HTTP/2 SETTINGS_ENABLE_PUSH */ + case 3: /* HTTP/2 SETTINGS_MAX_CONCURRENT_STREAMS */ + case 4: /* HTTP/2 SETTINGS_INITIAL_WINDOW_SIZE */ + case 5: /* HTTP/2 SETTINGS_MAX_FRAME_SIZE */ + /* [draft-ietf-quic-http-30] Section 7.2.4.1 */ + ABORT_QUIETLY(1, HEC_SETTINGS_ERROR, "unexpected HTTP/2 setting " + "%"PRIu64, setting_id); + break; } } @@ -8333,12 +8632,14 @@ hcsi_on_new (void *stream_if_ctx, struct lsquic_stream *stream) callbacks = &hcsi_callbacks_server_28; break; case (0 << 8) | LSQVER_ID29: + case (0 << 8) | LSQVER_ID30: callbacks = &hcsi_callbacks_client_29; break; default: assert(0); /* fallthru */ case (1 << 8) | LSQVER_ID29: + case (1 << 8) | LSQVER_ID30: callbacks = &hcsi_callbacks_server_29; break; } diff --git a/src/liblsquic/lsquic_handshake.c b/src/liblsquic/lsquic_handshake.c index ec52371f4..a25fd39d1 100644 --- a/src/liblsquic/lsquic_handshake.c +++ b/src/liblsquic/lsquic_handshake.c @@ -3768,6 +3768,7 @@ gquic_encrypt_packet (enc_session_t *enc_session_p, packet_out->po_sent_sz = enc_sz; packet_out->po_flags &= ~PO_IPv6; packet_out->po_flags |= PO_ENCRYPTED|PO_SENT_SZ|(ipv6 << POIPv6_SHIFT); + packet_out->po_dcid_len = GQUIC_CID_LEN; return ENCPA_OK; } diff --git a/src/liblsquic/lsquic_logger.c b/src/liblsquic/lsquic_logger.c index 58a0d97eb..fcec7a2e6 100644 --- a/src/liblsquic/lsquic_logger.c +++ b/src/liblsquic/lsquic_logger.c @@ -72,6 +72,7 @@ enum lsq_log_level lsq_log_levels[N_LSQUIC_LOGGER_MODULES] = { [LSQLM_HSK_ADAPTER] = LSQ_LOG_WARN, [LSQLM_BBR] = LSQ_LOG_WARN, [LSQLM_CUBIC] = LSQ_LOG_WARN, + [LSQLM_ADAPTIVE_CC] = LSQ_LOG_WARN, [LSQLM_HEADERS] = LSQ_LOG_WARN, [LSQLM_FRAME_READER]= LSQ_LOG_WARN, [LSQLM_FRAME_WRITER]= LSQ_LOG_WARN, @@ -115,6 +116,7 @@ const char *const lsqlm_to_str[N_LSQUIC_LOGGER_MODULES] = { [LSQLM_HSK_ADAPTER] = "hsk-adapter", [LSQLM_BBR] = "bbr", [LSQLM_CUBIC] = "cubic", + [LSQLM_ADAPTIVE_CC] = "adaptive-cc", [LSQLM_HEADERS] = "headers", [LSQLM_FRAME_READER]= "frame-reader", [LSQLM_FRAME_WRITER]= "frame-writer", diff --git a/src/liblsquic/lsquic_logger.h b/src/liblsquic/lsquic_logger.h index 0473bb2f0..5186b1843 100644 --- a/src/liblsquic/lsquic_logger.h +++ b/src/liblsquic/lsquic_logger.h @@ -63,6 +63,7 @@ enum lsquic_logger_module { LSQLM_HSK_ADAPTER, LSQLM_BBR, LSQLM_CUBIC, + LSQLM_ADAPTIVE_CC, LSQLM_HEADERS, LSQLM_FRAME_WRITER, LSQLM_FRAME_READER, diff --git a/src/liblsquic/lsquic_malo.c b/src/liblsquic/lsquic_malo.c index adbf06fef..fd3598db6 100644 --- a/src/liblsquic/lsquic_malo.c +++ b/src/liblsquic/lsquic_malo.c @@ -182,6 +182,7 @@ lsquic_malo_create (size_t obj_size) { TAILQ_INIT(&malo->elems); malo->obj_size = obj_size; + malo->next_iter_elem = NULL; return malo; } else diff --git a/src/liblsquic/lsquic_mini_conn.c b/src/liblsquic/lsquic_mini_conn.c index a45f39656..99da0dfd9 100644 --- a/src/liblsquic/lsquic_mini_conn.c +++ b/src/liblsquic/lsquic_mini_conn.c @@ -1924,12 +1924,13 @@ mini_conn_ci_Q050_packet_in (struct lsquic_conn *lconn, static struct lsquic_packet_out * -mini_conn_ci_next_packet_to_send (struct lsquic_conn *lconn, size_t size) +mini_conn_ci_next_packet_to_send (struct lsquic_conn *lconn, + const struct to_coal *to_coal_UNUSED) { struct mini_conn *mc = (struct mini_conn *) lconn; lsquic_packet_out_t *packet_out; - assert(0 == size); + assert(NULL == to_coal_UNUSED); TAILQ_FOREACH(packet_out, &mc->mc_packets_out, po_next) { if (packet_out->po_flags & PO_SENT) diff --git a/src/liblsquic/lsquic_mini_conn_ietf.c b/src/liblsquic/lsquic_mini_conn_ietf.c index 98aabdb5e..ab793a9ac 100644 --- a/src/liblsquic/lsquic_mini_conn_ietf.c +++ b/src/liblsquic/lsquic_mini_conn_ietf.c @@ -448,6 +448,17 @@ is_first_packet_ok (const struct lsquic_packet_in *packet_in, } +static void +imico_peer_addr_validated (struct ietf_mini_conn *conn, const char *how) +{ + if (!(conn->imc_flags & IMC_ADDR_VALIDATED)) + { + conn->imc_flags |= IMC_ADDR_VALIDATED; + LSQ_DEBUG("peer address validated (%s)", how); + } +} + + struct lsquic_conn * lsquic_mini_conn_ietf_new (struct lsquic_engine_public *enpub, const struct lsquic_packet_in *packet_in, @@ -527,7 +538,7 @@ lsquic_mini_conn_ietf_new (struct lsquic_engine_public *enpub, TAILQ_INIT(&conn->imc_app_packets); TAILQ_INIT(&conn->imc_crypto_frames); if (odcid) - conn->imc_flags |= IMC_ADDR_VALIDATED; + imico_peer_addr_validated(conn, "odcid"); LSQ_DEBUG("created mini connection object %p; max packet size=%hu", conn, conn->imc_path.np_pack_size); @@ -652,7 +663,8 @@ imico_can_send (const struct ietf_mini_conn *conn, size_t size) static struct lsquic_packet_out * -ietf_mini_conn_ci_next_packet_to_send (struct lsquic_conn *lconn, size_t size) +ietf_mini_conn_ci_next_packet_to_send (struct lsquic_conn *lconn, + const struct to_coal *to_coal) { struct ietf_mini_conn *conn = (struct ietf_mini_conn *) lconn; struct lsquic_packet_out *packet_out; @@ -663,7 +675,11 @@ ietf_mini_conn_ci_next_packet_to_send (struct lsquic_conn *lconn, size_t size) if (packet_out->po_flags & PO_SENT) continue; packet_size = lsquic_packet_out_total_sz(lconn, packet_out); - if (size == 0 || packet_size + size <= conn->imc_path.np_pack_size) + if (!(to_coal + && (packet_size + to_coal->prev_sz_sum + > conn->imc_path.np_pack_size + || !lsquic_packet_out_equal_dcids(to_coal->prev_packet, packet_out)) + )) { if (!imico_can_send(conn, packet_size)) { @@ -674,7 +690,7 @@ ietf_mini_conn_ci_next_packet_to_send (struct lsquic_conn *lconn, size_t size) } packet_out->po_flags |= PO_SENT; conn->imc_bytes_out += packet_size; - if (size == 0) + if (!to_coal) LSQ_DEBUG("packet_to_send: %"PRIu64, packet_out->po_packno); else LSQ_DEBUG("packet_to_send: %"PRIu64" (coalesced)", @@ -847,7 +863,7 @@ imico_process_crypto_frame (IMICO_PROC_FRAME_ARGS) { if (0 != conn->imc_conn.cn_esf.i->esfi_init_server( conn->imc_conn.cn_enc_session)) - return -1; + return 0; conn->imc_flags |= IMC_ENC_SESS_INITED; } @@ -1210,6 +1226,32 @@ imico_maybe_delay_processing (struct ietf_mini_conn *conn, } +/* [draft-ietf-quic-transport-30] Section 8.1: + " Additionally, a server MAY consider the client address validated if + " the client uses a connection ID chosen by the server and the + " connection ID contains at least 64 bits of entropy. + * + * We use RAND_bytes() to generate SCIDs, so it's all entropy. + */ +static void +imico_maybe_validate_by_dcid (struct ietf_mini_conn *conn, + const lsquic_cid_t *dcid) +{ + unsigned i; + + if (dcid->len >= 8) + /* Generic code with unnecessary loop as future-proofing */ + for (i = 0; i < conn->imc_conn.cn_n_cces; ++i) + if ((conn->imc_conn.cn_cces_mask & (i << 1)) + && (conn->imc_conn.cn_cces[i].cce_flags & CCE_SEQNO) + && LSQUIC_CIDS_EQ(&conn->imc_conn.cn_cces[i].cce_cid, dcid)) + { + imico_peer_addr_validated(conn, "dcid/scid + entropy"); + return; + } +} + + /* Only a single packet is supported */ static void ietf_mini_conn_ci_packet_in (struct lsquic_conn *lconn, @@ -1236,6 +1278,9 @@ ietf_mini_conn_ci_packet_in (struct lsquic_conn *lconn, return; } + if (!(conn->imc_flags & IMC_ADDR_VALIDATED)) + imico_maybe_validate_by_dcid(conn, &packet_in->pi_dcid); + pns = lsquic_hety2pns[ packet_in->pi_header_type ]; if (pns == PNS_INIT && (conn->imc_flags & IMC_IGNORE_INIT)) { @@ -1261,7 +1306,7 @@ ietf_mini_conn_ci_packet_in (struct lsquic_conn *lconn, return; } else if (pns == PNS_HSK) - conn->imc_flags |= IMC_ADDR_VALIDATED; + imico_peer_addr_validated(conn, "handshake PNS"); if (((conn->imc_flags >> IMCBIT_PNS_BIT_SHIFT) & 3) < pns) { diff --git a/src/liblsquic/lsquic_packet_common.h b/src/liblsquic/lsquic_packet_common.h index 837fbbc36..3148093d2 100644 --- a/src/liblsquic/lsquic_packet_common.h +++ b/src/liblsquic/lsquic_packet_common.h @@ -36,6 +36,7 @@ enum quic_frame_type QUIC_FRAME_HANDSHAKE_DONE, /* I */ QUIC_FRAME_ACK_FREQUENCY, /* I */ QUIC_FRAME_TIMESTAMP, /* I */ + QUIC_FRAME_DATAGRAM, /* I */ N_QUIC_FRAMES }; @@ -66,6 +67,7 @@ enum quic_ft_bit { QUIC_FTBIT_HANDSHAKE_DONE = 1 << QUIC_FRAME_HANDSHAKE_DONE, QUIC_FTBIT_ACK_FREQUENCY = 1 << QUIC_FRAME_ACK_FREQUENCY, QUIC_FTBIT_TIMESTAMP = 1 << QUIC_FRAME_TIMESTAMP, + QUIC_FTBIT_DATAGRAM = 1 << QUIC_FRAME_DATAGRAM, }; static const char * const frame_type_2_str[N_QUIC_FRAMES] = { @@ -95,6 +97,7 @@ static const char * const frame_type_2_str[N_QUIC_FRAMES] = { [QUIC_FRAME_HANDSHAKE_DONE] = "QUIC_FRAME_HANDSHAKE_DONE", [QUIC_FRAME_ACK_FREQUENCY] = "QUIC_FRAME_ACK_FREQUENCY", [QUIC_FRAME_TIMESTAMP] = "QUIC_FRAME_TIMESTAMP", + [QUIC_FRAME_DATAGRAM] = "QUIC_FRAME_DATAGRAM", }; #define QUIC_FRAME_PRELEN (sizeof("QUIC_FRAME_")) @@ -132,6 +135,7 @@ static const char * const frame_type_2_str[N_QUIC_FRAMES] = { QUIC_FRAME_SLEN(QUIC_FRAME_HANDSHAKE_DONE) + 1 + \ QUIC_FRAME_SLEN(QUIC_FRAME_ACK_FREQUENCY) + 1 + \ QUIC_FRAME_SLEN(QUIC_FRAME_TIMESTAMP) + 1 + \ + QUIC_FRAME_SLEN(QUIC_FRAME_DATAGRAM) + 1 + \ 0 diff --git a/src/liblsquic/lsquic_packet_out.c b/src/liblsquic/lsquic_packet_out.c index 0043428c8..1e70e6d73 100644 --- a/src/liblsquic/lsquic_packet_out.c +++ b/src/liblsquic/lsquic_packet_out.c @@ -475,3 +475,55 @@ lsquic_packet_out_turn_on_fin (struct lsquic_packet_out *packet_out, return -1; } + + +static unsigned +offset_to_dcid (const struct lsquic_packet_out *packet_out) +{ + if (packet_out->po_header_type == HETY_NOT_SET) + return 1; + else + { + assert(!(packet_out->po_lflags & POL_GQUIC)); + return 6; + } +} + + +/* Return true if DCIDs of the two packets are equal, false otherwise. */ +int +lsquic_packet_out_equal_dcids (const struct lsquic_packet_out *a, + const struct lsquic_packet_out *b) +{ + const int a_encrypted = !!(a->po_flags & PO_ENCRYPTED); + const int b_encrypted = !!(b->po_flags & PO_ENCRYPTED); + const unsigned char *dcids[2]; + size_t sizes[2]; + + switch ((a_encrypted << 1) | b_encrypted) + { + case (0 << 1) | 0: + return a->po_path == b->po_path; + case (0 << 1) | 1: + dcids[0] = a->po_path->np_dcid.idbuf; + sizes[0] = a->po_path->np_dcid.len; + dcids[1] = b->po_enc_data + offset_to_dcid(b); + sizes[1] = b->po_dcid_len; + break; + case (1 << 1) | 0: + dcids[0] = a->po_enc_data + offset_to_dcid(a); + sizes[0] = a->po_dcid_len; + dcids[1] = b->po_path->np_dcid.idbuf; + sizes[1] = b->po_path->np_dcid.len; + break; + default: + dcids[0] = a->po_enc_data + offset_to_dcid(a); + sizes[0] = a->po_dcid_len; + dcids[1] = b->po_enc_data + offset_to_dcid(b); + sizes[1] = b->po_dcid_len; + break; + } + + return sizes[0] == sizes[1] + && 0 == memcmp(dcids[0], dcids[1], sizes[0]); +} diff --git a/src/liblsquic/lsquic_packet_out.h b/src/liblsquic/lsquic_packet_out.h index 0478cc794..56f86e922 100644 --- a/src/liblsquic/lsquic_packet_out.h +++ b/src/liblsquic/lsquic_packet_out.h @@ -148,6 +148,7 @@ typedef struct lsquic_packet_out unsigned short po_n_alloc; /* Total number of bytes allocated in po_data */ unsigned short po_token_len; enum header_type po_header_type:8; + unsigned char po_dcid_len; /* If PO_ENCRYPTED is set */ enum { POL_GQUIC = 1 << 0, /* Used for logging */ #define POLEV_SHIFT 1 @@ -345,4 +346,7 @@ int lsquic_packet_out_turn_on_fin (struct lsquic_packet_out *, const struct parse_funcs *, const struct lsquic_stream *); +int +lsquic_packet_out_equal_dcids (const struct lsquic_packet_out *, + const struct lsquic_packet_out *); #endif diff --git a/src/liblsquic/lsquic_parse.h b/src/liblsquic/lsquic_parse.h index 8f4def780..9707e3580 100644 --- a/src/liblsquic/lsquic_parse.h +++ b/src/liblsquic/lsquic_parse.h @@ -323,6 +323,15 @@ struct parse_funcs (*pf_gen_timestamp_frame) (unsigned char *buf, size_t buf_len, uint64_t); int (*pf_parse_timestamp_frame) (const unsigned char *buf, size_t, uint64_t *); + int + (*pf_parse_datagram_frame) (const unsigned char *buf, size_t, const void **, + size_t *); + int + (*pf_gen_datagram_frame) (unsigned char *, size_t bufsz, size_t min_sz, + size_t max_sz, ssize_t (*)(struct lsquic_conn *, void *, size_t), + struct lsquic_conn *); + unsigned + (*pf_datagram_frame_size) (size_t); }; diff --git a/src/liblsquic/lsquic_parse_common.c b/src/liblsquic/lsquic_parse_common.c index 80f7f2f6b..18e2530e6 100644 --- a/src/liblsquic/lsquic_parse_common.c +++ b/src/liblsquic/lsquic_parse_common.c @@ -220,6 +220,36 @@ lsquic_cid_from_packet (const unsigned char *buf, size_t bufsz, /* See [draft-ietf-quic-transport-28], Section 12.4 (Table 3) */ const enum quic_ft_bit lsquic_legal_frames_by_level[N_LSQVER][N_ENC_LEVS] = { + [LSQVER_ID30] = { + [ENC_LEV_CLEAR] = QUIC_FTBIT_CRYPTO | QUIC_FTBIT_PADDING | QUIC_FTBIT_PING + | QUIC_FTBIT_ACK | QUIC_FTBIT_CONNECTION_CLOSE, + [ENC_LEV_EARLY] = QUIC_FTBIT_PADDING | QUIC_FTBIT_PING + | QUIC_FTBIT_STREAM | QUIC_FTBIT_RST_STREAM + | QUIC_FTBIT_BLOCKED | QUIC_FTBIT_CONNECTION_CLOSE + | QUIC_FTBIT_MAX_DATA | QUIC_FTBIT_MAX_STREAM_DATA + | QUIC_FTBIT_MAX_STREAMS | QUIC_FTBIT_STREAM_BLOCKED + | QUIC_FTBIT_STREAMS_BLOCKED + | QUIC_FTBIT_NEW_CONNECTION_ID | QUIC_FTBIT_STOP_SENDING + | QUIC_FTBIT_PATH_CHALLENGE | QUIC_FTBIT_PATH_RESPONSE + | QUIC_FTBIT_DATAGRAM + | QUIC_FTBIT_RETIRE_CONNECTION_ID, + [ENC_LEV_INIT] = QUIC_FTBIT_CRYPTO | QUIC_FTBIT_PADDING | QUIC_FTBIT_PING + | QUIC_FTBIT_ACK| QUIC_FTBIT_CONNECTION_CLOSE, + [ENC_LEV_FORW] = QUIC_FTBIT_CRYPTO | QUIC_FTBIT_PADDING | QUIC_FTBIT_PING + | QUIC_FTBIT_ACK | QUIC_FTBIT_CONNECTION_CLOSE + | QUIC_FTBIT_STREAM | QUIC_FTBIT_RST_STREAM + | QUIC_FTBIT_BLOCKED + | QUIC_FTBIT_MAX_DATA | QUIC_FTBIT_MAX_STREAM_DATA + | QUIC_FTBIT_MAX_STREAMS | QUIC_FTBIT_STREAM_BLOCKED + | QUIC_FTBIT_STREAMS_BLOCKED + | QUIC_FTBIT_NEW_CONNECTION_ID | QUIC_FTBIT_STOP_SENDING + | QUIC_FTBIT_PATH_CHALLENGE | QUIC_FTBIT_PATH_RESPONSE + | QUIC_FTBIT_HANDSHAKE_DONE | QUIC_FTBIT_ACK_FREQUENCY + | QUIC_FTBIT_RETIRE_CONNECTION_ID | QUIC_FTBIT_NEW_TOKEN + | QUIC_FTBIT_TIMESTAMP + | QUIC_FTBIT_DATAGRAM + , + }, [LSQVER_ID29] = { [ENC_LEV_CLEAR] = QUIC_FTBIT_CRYPTO | QUIC_FTBIT_PADDING | QUIC_FTBIT_PING | QUIC_FTBIT_ACK | QUIC_FTBIT_CONNECTION_CLOSE, @@ -231,6 +261,7 @@ const enum quic_ft_bit lsquic_legal_frames_by_level[N_LSQVER][N_ENC_LEVS] = | QUIC_FTBIT_STREAMS_BLOCKED | QUIC_FTBIT_NEW_CONNECTION_ID | QUIC_FTBIT_STOP_SENDING | QUIC_FTBIT_PATH_CHALLENGE | QUIC_FTBIT_PATH_RESPONSE + | QUIC_FTBIT_DATAGRAM | QUIC_FTBIT_RETIRE_CONNECTION_ID, [ENC_LEV_INIT] = QUIC_FTBIT_CRYPTO | QUIC_FTBIT_PADDING | QUIC_FTBIT_PING | QUIC_FTBIT_ACK| QUIC_FTBIT_CONNECTION_CLOSE, @@ -246,6 +277,7 @@ const enum quic_ft_bit lsquic_legal_frames_by_level[N_LSQVER][N_ENC_LEVS] = | QUIC_FTBIT_HANDSHAKE_DONE | QUIC_FTBIT_ACK_FREQUENCY | QUIC_FTBIT_RETIRE_CONNECTION_ID | QUIC_FTBIT_NEW_TOKEN | QUIC_FTBIT_TIMESTAMP + | QUIC_FTBIT_DATAGRAM , }, [LSQVER_ID28] = { @@ -287,7 +319,9 @@ const enum quic_ft_bit lsquic_legal_frames_by_level[N_LSQVER][N_ENC_LEVS] = | QUIC_FTBIT_STREAMS_BLOCKED | QUIC_FTBIT_NEW_CONNECTION_ID | QUIC_FTBIT_STOP_SENDING | QUIC_FTBIT_PATH_CHALLENGE | QUIC_FTBIT_PATH_RESPONSE - | QUIC_FTBIT_RETIRE_CONNECTION_ID, + | QUIC_FTBIT_RETIRE_CONNECTION_ID + | QUIC_FTBIT_DATAGRAM + , [ENC_LEV_INIT] = QUIC_FTBIT_CRYPTO | QUIC_FTBIT_PADDING | QUIC_FTBIT_PING | QUIC_FTBIT_ACK| QUIC_FTBIT_CONNECTION_CLOSE, [ENC_LEV_FORW] = QUIC_FTBIT_CRYPTO | QUIC_FTBIT_PADDING | QUIC_FTBIT_PING @@ -302,6 +336,7 @@ const enum quic_ft_bit lsquic_legal_frames_by_level[N_LSQVER][N_ENC_LEVS] = | QUIC_FTBIT_HANDSHAKE_DONE | QUIC_FTBIT_ACK_FREQUENCY | QUIC_FTBIT_RETIRE_CONNECTION_ID | QUIC_FTBIT_NEW_TOKEN | QUIC_FTBIT_TIMESTAMP + | QUIC_FTBIT_DATAGRAM , }, }; diff --git a/src/liblsquic/lsquic_parse_ietf_v1.c b/src/liblsquic/lsquic_parse_ietf_v1.c index 90ae5999c..ca4218d44 100644 --- a/src/liblsquic/lsquic_parse_ietf_v1.c +++ b/src/liblsquic/lsquic_parse_ietf_v1.c @@ -49,6 +49,8 @@ #define FRAME_TYPE_ACK_FREQUENCY 0xAF #define FRAME_TYPE_TIMESTAMP 0x2F5 +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + static int ietf_v1_gen_one_varint (unsigned char *, size_t, unsigned char, uint64_t); @@ -2147,6 +2149,82 @@ ietf_v1_parse_timestamp_frame (const unsigned char *buf, size_t buf_len, } +static int +ietf_v1_parse_datagram_frame (const unsigned char *buf, size_t buf_len, + const void **data, size_t *data_len) +{ + uint64_t len; + int s; + + /* Length and frame type have been checked already */ + assert(buf_len > 0); + assert(buf[0] == 0x30 || buf[0] == 0x31); + + if (buf[0] & 1) + { + s = vint_read(buf + 1, buf + buf_len, &len); + if (s > 0 && 1 + s + len >= buf_len) + { + *data = buf + 1 + s; + *data_len = len; + return 1 + buf_len + len; + } + else + return -1; + } + else + { + *data = buf + 1; + *data_len = buf_len - 1; + return buf_len; + } +} + + +static unsigned +ietf_v1_datagram_frame_size (size_t sz) +{ + return 1u + vint_size(sz) + sz; +} + + +static int +ietf_v1_gen_datagram_frame (unsigned char *buf, size_t bufsz, size_t min_sz, + size_t max_sz, + ssize_t (*user_callback)(struct lsquic_conn *, void *, size_t), + struct lsquic_conn *lconn) +{ + unsigned bits, len_sz; + ssize_t nw; + + /* We always generate length. A more efficient implementation would + * complicate the API. + */ + if (min_sz) + bits = vint_val2bits(min_sz); + else + bits = vint_val2bits(bufsz); + len_sz = 1u << bits; + + if (1 + len_sz + min_sz > bufsz) + { + errno = ENOBUFS; + return -1; + } + + nw = user_callback(lconn, buf + 1 + len_sz, min_sz ? min_sz + : MIN(bufsz - 1 - len_sz, max_sz)); + if (nw >= 0) + { + buf[0] = 0x31; + vint_write(&buf[1], (uint64_t) nw, bits, len_sz); + return 1 + len_sz + nw; + } + else + return -1; +} + + const struct parse_funcs lsquic_parse_funcs_ietf_v1 = { .pf_gen_reg_pkt_header = ietf_v1_gen_reg_pkt_header, @@ -2217,4 +2295,7 @@ const struct parse_funcs lsquic_parse_funcs_ietf_v1 = .pf_ack_frequency_frame_size = ietf_v1_ack_frequency_frame_size, .pf_gen_timestamp_frame = ietf_v1_gen_timestamp_frame, .pf_parse_timestamp_frame = ietf_v1_parse_timestamp_frame, + .pf_parse_datagram_frame = ietf_v1_parse_datagram_frame, + .pf_gen_datagram_frame = ietf_v1_gen_datagram_frame, + .pf_datagram_frame_size = ietf_v1_datagram_frame_size, }; diff --git a/src/liblsquic/lsquic_parse_iquic_common.c b/src/liblsquic/lsquic_parse_iquic_common.c index 102e772a7..ad149af10 100644 --- a/src/liblsquic/lsquic_parse_iquic_common.c +++ b/src/liblsquic/lsquic_parse_iquic_common.c @@ -289,8 +289,8 @@ const enum quic_frame_type lsquic_iquic_byte2type[0x40] = [0x2D] = QUIC_FRAME_INVALID, [0x2E] = QUIC_FRAME_INVALID, [0x2F] = QUIC_FRAME_INVALID, - [0x30] = QUIC_FRAME_INVALID, - [0x31] = QUIC_FRAME_INVALID, + [0x30] = QUIC_FRAME_DATAGRAM, + [0x31] = QUIC_FRAME_DATAGRAM, [0x32] = QUIC_FRAME_INVALID, [0x33] = QUIC_FRAME_INVALID, [0x34] = QUIC_FRAME_INVALID, diff --git a/src/liblsquic/lsquic_pr_queue.c b/src/liblsquic/lsquic_pr_queue.c index 6b0062168..501315238 100644 --- a/src/liblsquic/lsquic_pr_queue.c +++ b/src/liblsquic/lsquic_pr_queue.c @@ -515,10 +515,11 @@ lsquic_prq_have_pending (const struct pr_queue *prq) static struct lsquic_packet_out * -evanescent_conn_ci_next_packet_to_send (struct lsquic_conn *lconn, size_t size) +evanescent_conn_ci_next_packet_to_send (struct lsquic_conn *lconn, + const struct to_coal *to_coal_UNUSED) { struct evanescent_conn *const evconn = (struct evanescent_conn *) lconn; - assert(size == 0); + assert(!to_coal_UNUSED); return &evconn->evc_packet_out; } diff --git a/src/liblsquic/lsquic_rechist.c b/src/liblsquic/lsquic_rechist.c index 4d67fe887..0281be7f3 100644 --- a/src/liblsquic/lsquic_rechist.c +++ b/src/liblsquic/lsquic_rechist.c @@ -188,3 +188,16 @@ lsquic_rechist_mem_used (const struct lsquic_rechist *rechist) - sizeof(rechist->rh_pints) + lsquic_packints_mem_used(&rechist->rh_pints); } + + +const struct lsquic_packno_range * +lsquic_rechist_peek (const struct lsquic_rechist *rechist) +{ + const struct packet_interval *pint; + + pint = TAILQ_FIRST(&rechist->rh_pints.pk_intervals); + if (pint) + return &pint->range; + else + return NULL; +} diff --git a/src/liblsquic/lsquic_rechist.h b/src/liblsquic/lsquic_rechist.h index f2d98344a..96e8dbf88 100644 --- a/src/liblsquic/lsquic_rechist.h +++ b/src/liblsquic/lsquic_rechist.h @@ -77,4 +77,9 @@ lsquic_rechist_largest_recv (const lsquic_rechist_t *); size_t lsquic_rechist_mem_used (const struct lsquic_rechist *); +const struct lsquic_packno_range * +lsquic_rechist_peek (const struct lsquic_rechist *); + +#define lsquic_rechist_n_packets(rechist_) (+(rechist_)->rh_n_packets) + #endif diff --git a/src/liblsquic/lsquic_send_ctl.c b/src/liblsquic/lsquic_send_ctl.c index 7276dae31..9c815edaf 100644 --- a/src/liblsquic/lsquic_send_ctl.c +++ b/src/liblsquic/lsquic_send_ctl.c @@ -30,6 +30,7 @@ #include "lsquic_bw_sampler.h" #include "lsquic_minmax.h" #include "lsquic_bbr.h" +#include "lsquic_adaptive_cc.h" #include "lsquic_util.h" #include "lsquic_sfcw.h" #include "lsquic_varint.h" @@ -63,7 +64,7 @@ #define MIN_RTO_DELAY 1000000 /* Microseconds */ #define N_NACKS_BEFORE_RETX 3 -#define CGP(ctl) ((struct cong_ctl *) &(ctl)->sc_cong_u) +#define CGP(ctl) ((struct cong_ctl *) (ctl)->sc_cong_ctl) #define packet_out_total_sz(p) \ lsquic_packet_out_total_sz(ctl->sc_conn_pub->lconn, p) @@ -323,7 +324,7 @@ lsquic_send_ctl_init (lsquic_send_ctl_t *ctl, struct lsquic_alarmset *alset, struct lsquic_engine_public *enpub, const struct ver_neg *ver_neg, struct lsquic_conn_public *conn_pub, enum send_ctl_flags flags) { - unsigned i, algo; + unsigned i; memset(ctl, 0, sizeof(*ctl)); TAILQ_INIT(&ctl->sc_scheduled_packets); TAILQ_INIT(&ctl->sc_unacked_packets[PNS_INIT]); @@ -351,14 +352,22 @@ lsquic_send_ctl_init (lsquic_send_ctl_t *ctl, struct lsquic_alarmset *alset, lsquic_alarmset_init_alarm(alset, AL_RETX_HSK, retx_alarm_rings, ctl); lsquic_alarmset_init_alarm(alset, AL_RETX_APP, retx_alarm_rings, ctl); lsquic_senhist_init(&ctl->sc_senhist, ctl->sc_flags & SC_IETF); - if (0 == enpub->enp_settings.es_cc_algo) - algo = LSQUIC_DF_CC_ALGO; - else - algo = enpub->enp_settings.es_cc_algo; - if (algo == 2) - ctl->sc_ci = &lsquic_cong_bbr_if; - else + switch (enpub->enp_settings.es_cc_algo) + { + case 1: ctl->sc_ci = &lsquic_cong_cubic_if; + ctl->sc_cong_ctl = &ctl->sc_adaptive_cc.acc_cubic; + break; + case 2: + ctl->sc_ci = &lsquic_cong_bbr_if; + ctl->sc_cong_ctl = &ctl->sc_adaptive_cc.acc_bbr; + break; + case 3: + default: + ctl->sc_ci = &lsquic_cong_adaptive_if; + ctl->sc_cong_ctl = &ctl->sc_adaptive_cc; + break; + } ctl->sc_ci->cci_init(CGP(ctl), conn_pub, ctl->sc_retx_frames); if (ctl->sc_flags & SC_PACE) lsquic_pacer_init(&ctl->sc_pacer, conn_pub->lconn, @@ -682,6 +691,33 @@ lsquic_send_ctl_sent_packet (lsquic_send_ctl_t *ctl, } +static void +send_ctl_select_cc (struct lsquic_send_ctl *ctl) +{ + lsquic_time_t srtt; + + srtt = lsquic_rtt_stats_get_srtt(&ctl->sc_conn_pub->rtt_stats); + + if (srtt <= ctl->sc_enpub->enp_settings.es_cc_rtt_thresh) + { + LSQ_INFO("srtt is %"PRIu64" usec, which is smaller than or equal to " + "the threshold of %u usec: select Cubic congestion controller", + srtt, ctl->sc_enpub->enp_settings.es_cc_rtt_thresh); + ctl->sc_ci = &lsquic_cong_cubic_if; + ctl->sc_cong_ctl = &ctl->sc_adaptive_cc.acc_cubic; + ctl->sc_flags |= SC_CLEANUP_BBR; + } + else + { + LSQ_INFO("srtt is %"PRIu64" usec, which is greater than the threshold " + "of %u usec: select BBRv1 congestion controller", srtt, + ctl->sc_enpub->enp_settings.es_cc_rtt_thresh); + ctl->sc_ci = &lsquic_cong_bbr_if; + ctl->sc_cong_ctl = &ctl->sc_adaptive_cc.acc_bbr; + } +} + + static void take_rtt_sample (lsquic_send_ctl_t *ctl, lsquic_time_t now, lsquic_time_t lack_delta) @@ -696,6 +732,8 @@ take_rtt_sample (lsquic_send_ctl_t *ctl, LSQ_DEBUG("packno %"PRIu64"; rtt: %"PRIu64"; delta: %"PRIu64"; " "new srtt: %"PRIu64, packno, measured_rtt, lack_delta, lsquic_rtt_stats_get_srtt(&ctl->sc_conn_pub->rtt_stats)); + if (ctl->sc_ci == &lsquic_cong_adaptive_if) + send_ctl_select_cc(ctl); } } @@ -1423,6 +1461,11 @@ lsquic_send_ctl_cleanup (lsquic_send_ctl_t *ctl) if (ctl->sc_flags & SC_PACE) lsquic_pacer_cleanup(&ctl->sc_pacer); ctl->sc_ci->cci_cleanup(CGP(ctl)); + if (ctl->sc_flags & SC_CLEANUP_BBR) + { + assert(ctl->sc_ci == &lsquic_cong_cubic_if); + lsquic_cong_bbr_if.cci_cleanup(&ctl->sc_adaptive_cc.acc_bbr); + } #if LSQUIC_SEND_STATS LSQ_NOTICE("stats: n_total_sent: %u; n_resent: %u; n_delayed: %u", ctl->sc_stats.n_total_sent, ctl->sc_stats.n_resent, @@ -1801,9 +1844,11 @@ lsquic_send_ctl_next_packet_to_send_predict (struct lsquic_send_ctl *ctl) lsquic_packet_out_t * -lsquic_send_ctl_next_packet_to_send (struct lsquic_send_ctl *ctl, size_t size) +lsquic_send_ctl_next_packet_to_send (struct lsquic_send_ctl *ctl, + const struct to_coal *to_coal) { lsquic_packet_out_t *packet_out; + size_t size; int dec_limit; get_packet: @@ -1843,14 +1888,24 @@ lsquic_send_ctl_next_packet_to_send (struct lsquic_send_ctl *ctl, size_t size) } } - if (UNLIKELY(size)) + if (UNLIKELY(to_coal != NULL)) { - if (packet_out_total_sz(packet_out) + size > SC_PACK_SIZE(ctl)) + /* From [draft-ietf-quic-transport-30], Section-12.2: + " Senders MUST NOT coalesce QUIC packets with different connection + " IDs into a single UDP datagram. + */ + if (packet_out_total_sz(packet_out) + to_coal->prev_sz_sum + > SC_PACK_SIZE(ctl) + || !lsquic_packet_out_equal_dcids(to_coal->prev_packet, packet_out)) return NULL; LSQ_DEBUG("packet %"PRIu64" (%zu bytes) will be tacked on to " "previous packet(s) (%zu bytes) (coalescing)", - packet_out->po_packno, packet_out_total_sz(packet_out), size); + packet_out->po_packno, packet_out_total_sz(packet_out), + to_coal->prev_sz_sum); + size = to_coal->prev_sz_sum; } + else + size = 0; send_ctl_sched_remove(ctl, packet_out); if (dec_limit) @@ -3336,8 +3391,9 @@ send_ctl_resize_q (struct lsquic_send_ctl *ctl, struct lsquic_packets_tailq *q, void -lsquic_send_ctl_repath (struct lsquic_send_ctl *ctl, struct network_path *old, - struct network_path *new) +lsquic_send_ctl_repath (struct lsquic_send_ctl *ctl, + const struct network_path *old, const struct network_path *new, + int keep_path_properties) { struct lsquic_packet_out *packet_out; unsigned count; @@ -3367,11 +3423,15 @@ lsquic_send_ctl_repath (struct lsquic_send_ctl *ctl, struct network_path *old, LSQ_DEBUG("repathed %u packet%.*s", count, count != 1, "s"); - lsquic_send_ctl_resize(ctl); - - memset(&ctl->sc_conn_pub->rtt_stats, 0, - sizeof(ctl->sc_conn_pub->rtt_stats)); - ctl->sc_ci->cci_reinit(CGP(ctl)); + if (keep_path_properties) + LSQ_DEBUG("keeping path properties: MTU, RTT, and CC state"); + else + { + lsquic_send_ctl_resize(ctl); + memset(&ctl->sc_conn_pub->rtt_stats, 0, + sizeof(ctl->sc_conn_pub->rtt_stats)); + ctl->sc_ci->cci_reinit(CGP(ctl)); + } } diff --git a/src/liblsquic/lsquic_send_ctl.h b/src/liblsquic/lsquic_send_ctl.h index b19c55411..c0418641d 100644 --- a/src/liblsquic/lsquic_send_ctl.h +++ b/src/liblsquic/lsquic_send_ctl.h @@ -20,6 +20,7 @@ struct lsquic_conn_public; struct network_path; struct ver_neg; enum pns; +struct to_coal; enum buf_packet_type { BPT_HIGHEST_PRIO, BPT_OTHER_PRIO, }; @@ -48,6 +49,7 @@ enum send_ctl_flags { SC_SANITY_CHECK = 1 << 15, SC_CIDLEN = 1 << 16, /* sc_cidlen is set */ SC_POISON = 1 << 17, /* poisoned packet exists */ + SC_CLEANUP_BBR = 1 << 18, }; typedef struct lsquic_send_ctl { @@ -67,11 +69,9 @@ typedef struct lsquic_send_ctl { int (*sc_can_send)(struct lsquic_send_ctl *); unsigned sc_bytes_unacked_retx; unsigned sc_bytes_scheduled; - union { - struct lsquic_cubic cubic; - struct lsquic_bbr bbr; - } sc_cong_u; + struct adaptive_cc sc_adaptive_cc; const struct cong_ctl_if *sc_ci; + void *sc_cong_ctl; struct lsquic_engine_public *sc_enpub; unsigned sc_bytes_unacked_all; unsigned sc_n_in_flight_all; @@ -166,7 +166,8 @@ void lsquic_send_ctl_delayed_one (lsquic_send_ctl_t *, struct lsquic_packet_out *); struct lsquic_packet_out * -lsquic_send_ctl_next_packet_to_send (struct lsquic_send_ctl *, size_t); +lsquic_send_ctl_next_packet_to_send (struct lsquic_send_ctl *, + const struct to_coal *); int lsquic_send_ctl_next_packet_to_send_predict (struct lsquic_send_ctl *); @@ -362,8 +363,9 @@ void lsquic_send_ctl_empty_pns (struct lsquic_send_ctl *, enum packnum_space); void -lsquic_send_ctl_repath (struct lsquic_send_ctl *, struct network_path *old, - struct network_path *new); +lsquic_send_ctl_repath (struct lsquic_send_ctl *ctl, + const struct network_path *old, const struct network_path *new, + int keep_path_properties); void lsquic_send_ctl_resize (struct lsquic_send_ctl *); diff --git a/src/liblsquic/lsquic_stream.c b/src/liblsquic/lsquic_stream.c index ea03b81af..f1b514a8e 100644 --- a/src/liblsquic/lsquic_stream.c +++ b/src/liblsquic/lsquic_stream.c @@ -61,6 +61,7 @@ #include "lsquic_bw_sampler.h" #include "lsquic_minmax.h" #include "lsquic_bbr.h" +#include "lsquic_adaptive_cc.h" #include "lsquic_send_ctl.h" #include "lsquic_headers.h" #include "lsquic_ev_log.h" @@ -504,9 +505,16 @@ lsquic_stream_new_crypto (enum enc_level enc_level, stream->sm_bflags |= SMBF_CRYPTO|SMBF_IETF; stream->sm_enc_level = enc_level; - /* TODO: why have limit in crypto stream? Set it to UINT64_MAX? */ + /* We allow buffering of up to 16 KB of CRYPTO data (I guess we could + * make this configurable?). The window is opened (without sending + * MAX_STREAM_DATA) as CRYPTO data is consumed. If too much comes in + * at a time, we abort with TEC_CRYPTO_BUFFER_EXCEEDED. + */ lsquic_sfcw_init(&stream->fc, 16 * 1024, NULL, conn_pub, stream_id); - stream->max_send_off = 16 * 1024; + /* Don't limit ourselves from sending CRYPTO data. We assume that + * the underlying crypto library behaves in a sane manner. + */ + stream->max_send_off = UINT64_MAX; LSQ_DEBUG("created crypto stream"); SM_HISTORY_APPEND(stream, SHE_CREATED); stream->sm_frame_header_sz = stream_crypto_frame_header_sz; @@ -970,8 +978,14 @@ lsquic_stream_update_sfcw (lsquic_stream_t *stream, uint64_t max_off) if (stream->sm_bflags & SMBF_IETF) { lconn = stream->conn_pub->lconn; - lconn->cn_if->ci_abort_error(lconn, 0, TEC_FLOW_CONTROL_ERROR, - "flow control violation on stream %"PRIu64, stream->id); + if (lsquic_stream_is_crypto(stream)) + lconn->cn_if->ci_abort_error(lconn, 0, + TEC_CRYPTO_BUFFER_EXCEEDED, + "crypto buffer exceeded on in crypto level %"PRIu64, + crypto_level(stream)); + else + lconn->cn_if->ci_abort_error(lconn, 0, TEC_FLOW_CONTROL_ERROR, + "flow control violation on stream %"PRIu64, stream->id); } return -1; } @@ -1369,7 +1383,12 @@ static void stream_consumed_bytes (struct lsquic_stream *stream) { lsquic_sfcw_set_read_off(&stream->fc, stream->read_offset); - if (lsquic_sfcw_fc_offsets_changed(&stream->fc)) + if (lsquic_sfcw_fc_offsets_changed(&stream->fc) + /* We advance crypto streams' offsets (to control amount of + * buffering we allow), but do not send MAX_STREAM_DATA frames. + */ + && !((stream->sm_bflags & (SMBF_IETF|SMBF_CRYPTO)) + == (SMBF_IETF|SMBF_CRYPTO))) { if (!(stream->sm_qflags & SMQF_SENDING_FLAGS)) TAILQ_INSERT_TAIL(&stream->conn_pub->sending_streams, stream, @@ -4534,6 +4553,12 @@ update_type_hist_and_check (const struct lsquic_stream *stream, case HQFT_MAX_PUSH_ID: /* [draft-ietf-quic-http-24], Section 7 */ return -1; + case 2: /* HTTP/2 PRIORITY */ + case 6: /* HTTP/2 PING */ + case 8: /* HTTP/2 WINDOW_UPDATE */ + case 9: /* HTTP/2 CONTINUATION */ + /* [draft-ietf-quic-http-30], Section 7.2.8 */ + return -1; default: /* Ignore unknown frames */ return 0; diff --git a/src/liblsquic/lsquic_stream.h b/src/liblsquic/lsquic_stream.h index f4abcf056..47bc0093b 100644 --- a/src/liblsquic/lsquic_stream.h +++ b/src/liblsquic/lsquic_stream.h @@ -536,7 +536,7 @@ size_t lsquic_stream_flush_threshold (const struct lsquic_stream *, unsigned); #endif -#define crypto_level(stream) (~0ULL - (stream)->id) +#define crypto_level(stream) (UINT64_MAX - (stream)->id) void lsquic_stream_set_stream_if (struct lsquic_stream *, diff --git a/src/liblsquic/lsquic_tokgen.c b/src/liblsquic/lsquic_tokgen.c index ad81bd3e3..251428bd6 100644 --- a/src/liblsquic/lsquic_tokgen.c +++ b/src/liblsquic/lsquic_tokgen.c @@ -49,7 +49,7 @@ struct tokgen_shm_state { uint8_t tgss_version; uint8_t tgss_magic_top[sizeof(TOKGEN_SHM_MAGIC_TOP) - 1]; - uint8_t tgss_crypter_key[N_TOKEN_TYPES][CRYPTER_KEY_SIZE]; + uint8_t tgss_padding[2 * CRYPTER_KEY_SIZE]; uint8_t tgss_srst_prk_size; uint8_t tgss_srst_prk[SRST_MAX_PRK_SIZE]; uint8_t tgss_magic_bottom[sizeof(TOKGEN_SHM_MAGIC_BOTTOM) - 1]; @@ -72,8 +72,6 @@ struct crypter struct token_generator { - /* We encrypt different token types using different keys. */ - struct crypter tg_crypters[N_TOKEN_TYPES]; /* Stateless reset token is generated using HKDF with CID as the * `info' parameter to HKDF-Expand. diff --git a/src/liblsquic/lsquic_tokgen.h b/src/liblsquic/lsquic_tokgen.h index 85bf49025..878ce2f42 100644 --- a/src/liblsquic/lsquic_tokgen.h +++ b/src/liblsquic/lsquic_tokgen.h @@ -7,8 +7,6 @@ struct sockaddr; struct lsquic_packet_in; struct lsquic_cid; -enum token_type { TOKEN_RETRY, TOKEN_RESUME, N_TOKEN_TYPES, }; - struct token_generator; struct token_generator * diff --git a/src/liblsquic/lsquic_trans_params.c b/src/liblsquic/lsquic_trans_params.c index 0e0e03f83..a080091b9 100644 --- a/src/liblsquic/lsquic_trans_params.c +++ b/src/liblsquic/lsquic_trans_params.c @@ -54,6 +54,7 @@ tpi_val_2_enum (uint64_t tpi_val) case 14: return TPI_ACTIVE_CONNECTION_ID_LIMIT; case 15: return TPI_INITIAL_SOURCE_CID; case 16: return TPI_RETRY_SOURCE_CID; + case 0x20: return TPI_MAX_DATAGRAM_FRAME_SIZE; #if LSQUIC_TEST_QUANTUM_READINESS case 0xC37: return TPI_QUANTUM_READINESS; #endif @@ -85,6 +86,7 @@ static const unsigned enum_2_tpi_val[LAST_TPI + 1] = [TPI_ACTIVE_CONNECTION_ID_LIMIT] = 0xE, [TPI_INITIAL_SOURCE_CID] = 0xF, [TPI_RETRY_SOURCE_CID] = 0x10, + [TPI_MAX_DATAGRAM_FRAME_SIZE] = 0x20, #if LSQUIC_TEST_QUANTUM_READINESS [TPI_QUANTUM_READINESS] = 0xC37, #endif @@ -114,6 +116,7 @@ const char * const lsquic_tpi2str[LAST_TPI + 1] = [TPI_ACTIVE_CONNECTION_ID_LIMIT] = "active_connection_id_limit", [TPI_INITIAL_SOURCE_CID] = "initial_source_connection_id", [TPI_RETRY_SOURCE_CID] = "retry_source_connection_id", + [TPI_MAX_DATAGRAM_FRAME_SIZE] = "max_datagram_frame_size", #if LSQUIC_TEST_QUANTUM_READINESS [TPI_QUANTUM_READINESS] = "quantum_readiness", #endif @@ -148,8 +151,8 @@ static const uint64_t max_vals[MAX_NUMERIC_TPI + 1] = */ [TPI_MAX_UDP_PAYLOAD_SIZE] = VINT_MAX_VALUE, [TPI_ACK_DELAY_EXPONENT] = VINT_MAX_VALUE, - [TPI_INIT_MAX_STREAMS_UNI] = VINT_MAX_VALUE, - [TPI_INIT_MAX_STREAMS_BIDI] = VINT_MAX_VALUE, + [TPI_INIT_MAX_STREAMS_UNI] = 1ull << 60, + [TPI_INIT_MAX_STREAMS_BIDI] = 1ull << 60, [TPI_INIT_MAX_DATA] = VINT_MAX_VALUE, [TPI_INIT_MAX_STREAM_DATA_BIDI_LOCAL] = VINT_MAX_VALUE, [TPI_INIT_MAX_STREAM_DATA_BIDI_REMOTE] = VINT_MAX_VALUE, @@ -160,6 +163,7 @@ static const uint64_t max_vals[MAX_NUMERIC_TPI + 1] = [TPI_LOSS_BITS] = 1, [TPI_MIN_ACK_DELAY] = (1u << 24) - 1u, [TPI_TIMESTAMPS] = TS_WANT_THEM|TS_GENERATE_THEM, + [TPI_MAX_DATAGRAM_FRAME_SIZE] = VINT_MAX_VALUE, }; @@ -206,6 +210,23 @@ lsquic_tp_has_pref_ipv6 (const struct transport_params *params) } +#if LSQUIC_TEST_QUANTUM_READINESS +#include +size_t +lsquic_tp_get_quantum_sz (void) +{ + const char *str; + + str = getenv("LSQUIC_QUANTUM_SZ"); + if (str) + return atoi(str); + else + /* https://github.com/quicwg/base-drafts/wiki/Quantum-Readiness-test */ + return 1200; +} +#endif + + static size_t update_cid_bits (unsigned bits[][3], enum transport_param_id tpi, const lsquic_cid_t *cid) @@ -238,6 +259,9 @@ lsquic_tp_encode (const struct transport_params *params, int is_server, enum transport_param_id tpi; unsigned set; unsigned bits[LAST_TPI + 1][3 /* ID, length, value */]; +#if LSQUIC_TEST_QUANTUM_READINESS + const size_t quantum_sz = lsquic_tp_get_quantum_sz(); +#endif need = 0; set = params->tp_set; /* Will turn bits off for default values */ @@ -275,14 +299,14 @@ lsquic_tp_encode (const struct transport_params *params, int is_server, } } #if LSQUIC_TEST_QUANTUM_READINESS - else if (set & (1 << TPI_QUANTUM_READINESS)) + if (set & (1 << TPI_QUANTUM_READINESS)) { bits[TPI_QUANTUM_READINESS][0] = vint_val2bits(enum_2_tpi_val[TPI_QUANTUM_READINESS]); - bits[TPI_QUANTUM_READINESS][1] = vint_val2bits(QUANTUM_READY_SZ); + bits[TPI_QUANTUM_READINESS][1] = vint_val2bits(quantum_sz); need += (1 << bits[TPI_QUANTUM_READINESS][0]) + (1 << bits[TPI_QUANTUM_READINESS][1]) - + QUANTUM_READY_SZ; + + quantum_sz; } #endif @@ -375,6 +399,7 @@ lsquic_tp_encode (const struct transport_params *params, int is_server, case TPI_LOSS_BITS: case TPI_MIN_ACK_DELAY: case TPI_TIMESTAMPS: + case TPI_MAX_DATAGRAM_FRAME_SIZE: vint_write(p, 1 << bits[tpi][2], bits[tpi][1], 1 << bits[tpi][1]); p += 1 << bits[tpi][1]; @@ -420,11 +445,11 @@ lsquic_tp_encode (const struct transport_params *params, int is_server, break; #if LSQUIC_TEST_QUANTUM_READINESS case TPI_QUANTUM_READINESS: - vint_write(p, QUANTUM_READY_SZ, - bits[tpi][1], 1 << bits[tpi][1]); + LSQ_DEBUG("encoded %zd bytes of quantum readiness", quantum_sz); + vint_write(p, quantum_sz, bits[tpi][1], 1 << bits[tpi][1]); p += 1 << bits[tpi][1]; - memset(p, 'Q', QUANTUM_READY_SZ); - p += QUANTUM_READY_SZ; + memset(p, 'Q', quantum_sz); + p += quantum_sz; break; #endif } @@ -501,6 +526,7 @@ lsquic_tp_decode (const unsigned char *const buf, size_t bufsz, case TPI_LOSS_BITS: case TPI_MIN_ACK_DELAY: case TPI_TIMESTAMPS: + case TPI_MAX_DATAGRAM_FRAME_SIZE: switch (len) { case 1: @@ -734,6 +760,9 @@ lsquic_tp_encode_27 (const struct transport_params *params, int is_server, enum transport_param_id tpi; unsigned set; unsigned bits[LAST_TPI + 1][3 /* ID, length, value */]; +#if LSQUIC_TEST_QUANTUM_READINESS + const size_t quantum_sz = lsquic_tp_get_quantum_sz(); +#endif need = 0; set = params->tp_set; /* Will turn bits off for default values */ @@ -788,10 +817,10 @@ lsquic_tp_encode_27 (const struct transport_params *params, int is_server, { bits[TPI_QUANTUM_READINESS][0] = vint_val2bits(enum_2_tpi_val[TPI_QUANTUM_READINESS]); - bits[TPI_QUANTUM_READINESS][1] = vint_val2bits(QUANTUM_READY_SZ); + bits[TPI_QUANTUM_READINESS][1] = vint_val2bits(quantum_sz); need += (1 << bits[TPI_QUANTUM_READINESS][0]) + (1 << bits[TPI_QUANTUM_READINESS][1]) - + QUANTUM_READY_SZ; + + quantum_sz; } #endif @@ -884,6 +913,7 @@ lsquic_tp_encode_27 (const struct transport_params *params, int is_server, case TPI_LOSS_BITS: case TPI_MIN_ACK_DELAY: case TPI_TIMESTAMPS: + case TPI_MAX_DATAGRAM_FRAME_SIZE: vint_write(p, 1 << bits[tpi][2], bits[tpi][1], 1 << bits[tpi][1]); p += 1 << bits[tpi][1]; @@ -931,11 +961,11 @@ lsquic_tp_encode_27 (const struct transport_params *params, int is_server, break; #if LSQUIC_TEST_QUANTUM_READINESS case TPI_QUANTUM_READINESS: - vint_write(p, QUANTUM_READY_SZ, - bits[tpi][1], 1 << bits[tpi][1]); + LSQ_DEBUG("encoded %zd bytes of quantum readiness", quantum_sz); + vint_write(p, quantum_sz, bits[tpi][1], 1 << bits[tpi][1]); p += 1 << bits[tpi][1]; - memset(p, 'Q', QUANTUM_READY_SZ); - p += QUANTUM_READY_SZ; + memset(p, 'Q', quantum_sz); + p += quantum_sz; break; #endif } @@ -1012,6 +1042,7 @@ lsquic_tp_decode_27 (const unsigned char *const buf, size_t bufsz, case TPI_LOSS_BITS: case TPI_MIN_ACK_DELAY: case TPI_TIMESTAMPS: + case TPI_MAX_DATAGRAM_FRAME_SIZE: switch (len) { case 1: diff --git a/src/liblsquic/lsquic_trans_params.h b/src/liblsquic/lsquic_trans_params.h index aab9edb98..66bddeb2a 100644 --- a/src/liblsquic/lsquic_trans_params.h +++ b/src/liblsquic/lsquic_trans_params.h @@ -34,6 +34,7 @@ enum transport_param_id */ TPI_MIN_ACK_DELAY, TPI_TIMESTAMPS, + TPI_MAX_DATAGRAM_FRAME_SIZE, TPI_LOSS_BITS, MAX_NUMERIC_TPI = TPI_LOSS_BITS, /* @@ -53,8 +54,6 @@ enum transport_param_id #define LAST_TP_CID TPI_RETRY_SOURCE_CID TPI_RETRY_SOURCE_CID, #if LSQUIC_TEST_QUANTUM_READINESS - /* https://github.com/quicwg/base-drafts/wiki/Quantum-Readiness-test */ -#define QUANTUM_READY_SZ 1200 TPI_QUANTUM_READINESS, #endif TPI_STATELESS_RESET_TOKEN, LAST_TPI = TPI_STATELESS_RESET_TOKEN @@ -180,4 +179,9 @@ extern const char * const lsquic_tpi2str[LAST_TPI + 1]; #define TS_WANT_THEM 1 #define TS_GENERATE_THEM 2 +#if LSQUIC_TEST_QUANTUM_READINESS +size_t +lsquic_tp_get_quantum_sz (void); +#endif + #endif diff --git a/src/liblsquic/lsquic_version.c b/src/liblsquic/lsquic_version.c index 43185216c..b81a20248 100644 --- a/src/liblsquic/lsquic_version.c +++ b/src/liblsquic/lsquic_version.c @@ -21,6 +21,7 @@ static const unsigned char version_tags[N_LSQVER][4] = [LSQVER_ID27] = { 0xFF, 0, 0, 27, }, [LSQVER_ID28] = { 0xFF, 0, 0, 28, }, [LSQVER_ID29] = { 0xFF, 0, 0, 29, }, + [LSQVER_ID30] = { 0xFF, 0, 0, 30, }, [LSQVER_VERNEG] = { 0xFA, 0xFA, 0xFA, 0xFA, }, }; @@ -60,6 +61,7 @@ const char *const lsquic_ver2str[N_LSQVER] = { [LSQVER_ID27] = "FF00001B", [LSQVER_ID28] = "FF00001C", [LSQVER_ID29] = "FF00001D", + [LSQVER_ID30] = "FF00001E", [LSQVER_VERNEG] = "FAFAFAFA", }; diff --git a/tests/test_h3_framing.c b/tests/test_h3_framing.c index fa00b9513..4804bd7df 100644 --- a/tests/test_h3_framing.c +++ b/tests/test_h3_framing.c @@ -45,6 +45,7 @@ #include "lsquic_bw_sampler.h" #include "lsquic_minmax.h" #include "lsquic_bbr.h" +#include "lsquic_adaptive_cc.h" #include "lsquic_send_ctl.h" #include "lsquic_ver_neg.h" #include "lsquic_packet_out.h" @@ -363,7 +364,8 @@ init_test_objs (struct test_objs *tobjs, unsigned initial_conn_window, tobjs->eng_pub.enp_hsi_if = &tobjs->hsi_if; lsquic_send_ctl_init(&tobjs->send_ctl, &tobjs->alset, &tobjs->eng_pub, &tobjs->ver_neg, &tobjs->conn_pub, 0); - tobjs->send_ctl.sc_cong_u.cubic.cu_cwnd = ~0ull; + tobjs->send_ctl.sc_adaptive_cc.acc_cubic.cu_cwnd = ~0ull; + tobjs->send_ctl.sc_cong_ctl = &tobjs->send_ctl.sc_adaptive_cc.acc_cubic; tobjs->stream_if = &stream_if; tobjs->stream_if_ctx = &test_ctx; tobjs->ctor_flags = stream_ctor_flags; @@ -1626,7 +1628,6 @@ main (int argc, char **argv) else { main_test_pwritev(); - return 0; main_test_hq_framing(); for (n_packets = 1; n_packets <= 2; ++n_packets) for (extra_sz = 0; extra_sz <= 2; ++extra_sz) diff --git a/tests/test_send_headers.c b/tests/test_send_headers.c index 9fc938f88..cd22d6168 100644 --- a/tests/test_send_headers.c +++ b/tests/test_send_headers.c @@ -46,6 +46,7 @@ #include "lsquic_bw_sampler.h" #include "lsquic_minmax.h" #include "lsquic_bbr.h" +#include "lsquic_adaptive_cc.h" #include "lsquic_send_ctl.h" #include "lsquic_ver_neg.h" #include "lsquic_packet_out.h" diff --git a/tests/test_stream.c b/tests/test_stream.c index 445932094..05d08f191 100644 --- a/tests/test_stream.c +++ b/tests/test_stream.c @@ -40,6 +40,7 @@ #include "lsquic_bw_sampler.h" #include "lsquic_minmax.h" #include "lsquic_bbr.h" +#include "lsquic_adaptive_cc.h" #include "lsquic_send_ctl.h" #include "lsquic_ver_neg.h" #include "lsquic_packet_out.h" @@ -3005,7 +3006,7 @@ test_packetization (int schedule_stream_packets_immediately, int dispatch_once, } else { - assert(0x4000 == nw); + assert(sizeof(buf) == nw); assert(0 == memcmp(buf, buf_out, nw)); }