From c2faf0324458344d584c6f933db7bee65fcab166 Mon Sep 17 00:00:00 2001 From: Dmitri Tikhonov Date: Wed, 3 Feb 2021 11:05:50 -0500 Subject: [PATCH] Release 2.28.0 - [API] lsquic_ssl_sess_to_resume_info() is the new way to get session info. - [API] Add user pointer to ea_generate_scid callback. - [API] Add lsquic_dcid_from_packet() -- a fast function to parse out DCID. - [API] Add es_max_batch_size to control outgoing packet batch size. - [BUGFIX] Disallow sending of header while promise is being written. - [BUGFIX] Flush stream when buffered bytes exhaust stream cap. - [BUGFIX] Deactivate HQ frame if writing push promise fails. - Perform sanity check on peer transport parameters and fail the handshake if some flow control limits are too low. This can be turned off, see es_check_tp_sanity. - http_server: fix how requests are read in "hq" mode. --- CHANGELOG | 16 ++++ bin/http_client.c | 5 +- bin/http_server.c | 39 ++++++--- bin/prog.c | 60 +++++++++++++ bin/test_common.c | 2 + docs/apiref.rst | 17 ++++ docs/conf.py | 4 +- include/lsquic.h | 54 ++++++++++-- src/liblsquic/lsquic_conn.c | 2 +- src/liblsquic/lsquic_conn.h | 2 +- src/liblsquic/lsquic_enc_sess_ietf.c | 105 +++++++++++++++++++---- src/liblsquic/lsquic_engine.c | 4 + src/liblsquic/lsquic_engine_public.h | 5 +- src/liblsquic/lsquic_full_conn.c | 10 ++- src/liblsquic/lsquic_full_conn_ietf.c | 2 +- src/liblsquic/lsquic_mini_conn_ietf.c | 2 +- src/liblsquic/lsquic_parse_common.c | 116 ++++++++++++++++++++++++++ src/liblsquic/lsquic_stream.c | 46 ++++++++-- tests/test_send_headers.c | 3 +- 19 files changed, 440 insertions(+), 54 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index f0c3877..769f22d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,19 @@ +2021-02-03 + - 2.28.0 + - [API] lsquic_ssl_sess_to_resume_info() is the new way to get + session info. + - [API] Add user pointer to ea_generate_scid callback. + - [API] Add lsquic_dcid_from_packet() -- a fast function to parse + out DCID. + - [API] Add es_max_batch_size to control outgoing packet batch size. + - [BUGFIX] Disallow sending of header while promise is being written. + - [BUGFIX] Flush stream when buffered bytes exhaust stream cap. + - [BUGFIX] Deactivate HQ frame if writing push promise fails. + - Perform sanity check on peer transport parameters and fail the + handshake if some flow control limits are too low. This can be + turned off, see es_check_tp_sanity. + - http_server: fix how requests are read in "hq" mode. + 2021-01-27 - 2.27.6 - [BUGFIX] Replace dispatch read/write events assertion with a check. diff --git a/bin/http_client.c b/bin/http_client.c index ff2c7ec..db53014 100644 --- a/bin/http_client.c +++ b/bin/http_client.c @@ -426,6 +426,7 @@ http_client_on_hsk_done (lsquic_conn_t *conn, enum lsquic_hsk_status status) } +/* Now only used for gQUIC and will be going away after that */ static void http_client_on_sess_resume_info (lsquic_conn_t *conn, const unsigned char *buf, size_t bufsz) @@ -1011,7 +1012,6 @@ usage (const char *prog) " -I Abort on incomplete reponse from server\n" " -4 Prefer IPv4 when resolving hostname\n" " -6 Prefer IPv6 when resolving hostname\n" -" -0 FILE Provide RTT info file (reading or writing)\n" #ifndef WIN32 " -C DIR Certificate store. If specified, server certificate will\n" " be verified.\n" @@ -1736,7 +1736,7 @@ main (int argc, char **argv) case '0': http_client_if.on_sess_resume_info = http_client_on_sess_resume_info; client_ctx.hcc_sess_resume_file_name = optarg; - break; + goto common_opts; case '3': s_abandon_early = strtol(optarg, NULL, 10); break; @@ -1788,6 +1788,7 @@ main (int argc, char **argv) prog.prog_api.ea_alpn = optarg; prog.prog_api.ea_stream_if = &hq_client_if; break; + common_opts: default: if (0 != prog_set_opt(&prog, opt, optarg)) exit(1); diff --git a/bin/http_server.c b/bin/http_server.c index 0404c97..f839de3 100644 --- a/bin/http_server.c +++ b/bin/http_server.c @@ -1066,30 +1066,36 @@ const struct lsquic_stream_if http_server_if = { }; -/* XXX Assume we can always read the request in one shot. This is not a - * good assumption to make in a real product. - */ +#if HAVE_OPEN_MEMSTREAM static void hq_server_on_read (struct lsquic_stream *stream, lsquic_stream_ctx_t *st_h) { - char buf[0x400]; + char tbuf[0x100], *buf; ssize_t nread; char *path, *end, *filename; - nread = lsquic_stream_read(stream, buf, sizeof(buf)); - if (nread >= (ssize_t) sizeof(buf)) + if (!st_h->req_fh) + st_h->req_fh = open_memstream(&st_h->req_buf, &st_h->req_sz); + + nread = lsquic_stream_read(stream, tbuf, sizeof(tbuf)); + if (nread > 0) { - LSQ_WARN("request too large, at least %zd bytes", sizeof(buf)); - lsquic_stream_close(stream); + fwrite(tbuf, 1, nread, st_h->req_fh); return; } - else if (nread < 0) + + if (nread < 0) { LSQ_WARN("error reading request from stream: %s", strerror(errno)); lsquic_stream_close(stream); return; } - buf[nread] = '\0'; + + fwrite("", 1, 1, st_h->req_fh); + fclose(st_h->req_fh); + LSQ_INFO("got request: `%.*s'", (int) st_h->req_sz, st_h->req_buf); + + buf = st_h->req_buf; path = strchr(buf, ' '); if (!path) { @@ -1104,8 +1110,8 @@ hq_server_on_read (struct lsquic_stream *stream, lsquic_stream_ctx_t *st_h) return; } ++path; - for (end = path + nread - 5; end > path - && (*end == '\r' || *end == '\n'); --end) + for (end = buf + st_h->req_sz - 1; end > path + && (*end == '\0' || *end == '\r' || *end == '\n'); --end) *end = '\0'; LSQ_NOTICE("parsed out request path: %s", path); @@ -1174,6 +1180,7 @@ const struct lsquic_stream_if hq_server_if = { .on_write = hq_server_on_write, .on_close = http_server_on_close, }; +#endif #if HAVE_REGEX @@ -1922,7 +1929,11 @@ main (int argc, char **argv) prog_init(&prog, LSENG_SERVER|LSENG_HTTP, &server_ctx.sports, &http_server_if, &server_ctx); - while (-1 != (opt = getopt(argc, argv, PROG_OPTS "y:Y:n:p:r:w:P:hQ:"))) + while (-1 != (opt = getopt(argc, argv, PROG_OPTS "y:Y:n:p:r:w:P:h" +#if HAVE_OPEN_MEMSTREAM + "Q:" +#endif + ))) { switch (opt) { case 'n': @@ -1965,12 +1976,14 @@ main (int argc, char **argv) usage(argv[0]); prog_print_common_options(&prog, stdout); exit(0); +#if HAVE_OPEN_MEMSTREAM case 'Q': /* XXX A bit hacky, as `prog' has already been initialized... */ prog.prog_engine_flags &= ~LSENG_HTTP; prog.prog_api.ea_stream_if = &hq_server_if; add_alpn(optarg); break; +#endif default: if (0 != prog_set_opt(&prog, opt, optarg)) exit(1); diff --git a/bin/prog.c b/bin/prog.c index f1cee05..0affcb1 100644 --- a/bin/prog.c +++ b/bin/prog.c @@ -39,6 +39,7 @@ static int prog_stopped; static const char *s_keylog_dir; +static const char *s_sess_resume_file; static SSL_CTX * get_ssl_ctx (void *, const struct sockaddr *); static void keylog_log_line (const SSL *, const char *); @@ -118,6 +119,7 @@ void prog_print_common_options (const struct prog *prog, FILE *out) { fprintf(out, +" -0 FILE Provide session resumption file (reading or writing)\n" #if HAVE_REGEX " -s SVCPORT Service port. Takes on the form of host:port, host,\n" " or port. If host is not an IPv4 or IPv6 address, it is\n" @@ -338,6 +340,9 @@ prog_set_opt (struct prog *prog, int opt, const char *arg) sport->sp_flags |= SPORT_CONNECT; } return 0; + case '0': + s_sess_resume_file = optarg; + return 0; case 'G': #ifndef WIN32 if (0 == stat(optarg, &st)) @@ -426,6 +431,53 @@ get_ssl_ctx (void *peer_ctx, const struct sockaddr *unused) } +static int +prog_new_session_cb (SSL *ssl, SSL_SESSION *session) +{ + unsigned char *buf; + size_t bufsz, nw; + FILE *file; + + /* Our client is rather limited: only one file and only one ticket + * can be saved. A more flexible client implementation would call + * lsquic_ssl_to_conn() and maybe save more tickets based on its + * own configuration. + */ + if (!s_sess_resume_file) + return 0; + + if (0 != lsquic_ssl_sess_to_resume_info(ssl, session, &buf, &bufsz)) + { + LSQ_NOTICE("lsquic_ssl_sess_to_resume_info failed"); + return 0; + } + + file = fopen(s_sess_resume_file, "wb"); + if (!file) + { + LSQ_WARN("cannot open %s for writing: %s", + s_sess_resume_file, strerror(errno)); + free(buf); + return 0; + } + + nw = fwrite(buf, 1, bufsz, file); + if (nw == bufsz) + { + LSQ_INFO("wrote %zd bytes of session resumption information to %s", + nw, s_sess_resume_file); + s_sess_resume_file = NULL; /* Save just one ticket */ + } + else + LSQ_WARN("error: fwrite(%s) returns %zd instead of %zd: %s", + s_sess_resume_file, nw, bufsz, strerror(errno)); + + fclose(file); + free(buf); + return 0; +} + + static int prog_init_ssl_ctx (struct prog *prog) { @@ -454,6 +506,14 @@ prog_init_ssl_ctx (struct prog *prog) if (s_keylog_dir) SSL_CTX_set_keylog_callback(prog->prog_ssl_ctx, keylog_log_line); + if (s_sess_resume_file) + { + SSL_CTX_set_session_cache_mode(prog->prog_ssl_ctx, + SSL_SESS_CACHE_CLIENT); + SSL_CTX_set_early_data_enabled(prog->prog_ssl_ctx, 1); + SSL_CTX_sess_set_new_cb(prog->prog_ssl_ctx, prog_new_session_cb); + } + return 0; } diff --git a/bin/test_common.c b/bin/test_common.c index 8a595a1..174fde7 100644 --- a/bin/test_common.c +++ b/bin/test_common.c @@ -158,6 +158,8 @@ static void getExtensionPtrs() #endif + + static struct packets_in * allocate_packets_in (SOCKET_TYPE fd) { diff --git a/docs/apiref.rst b/docs/apiref.rst index 68f1921..02f3023 100644 --- a/docs/apiref.rst +++ b/docs/apiref.rst @@ -874,6 +874,14 @@ settings structure: Default value is :macro:`LSQUIC_DF_MAX_BATCH_SIZE` + .. member:: int es_check_tp_sanity + + When true, sanity checks are performed on peer's transport parameter + values. If some limits are set suspiciously low, the connection won't + be established. + + Default value is :macro:`LSQUIC_DF_CHECK_TP_SANITY` + To initialize the settings structure to library defaults, use the following convenience function: @@ -1115,6 +1123,10 @@ out of date. Please check your :file:`lsquic.h` for actual values.* By default, maximum batch size is not specified, leaving it up to the library. +.. macro:: LSQUIC_DF_CHECK_TP_SANITY + + Transport parameter sanity checks are performed by default. + Receiving Packets ----------------- @@ -1337,6 +1349,11 @@ the engine to communicate with the user code: This callback lets client record information needed to perform session resumption next time around. + For IETF QUIC, this is called only if :member:`lsquic_engine_api.ea_get_ssl_ctx_st` + is *not* set, in which case the library creates its own SSL_CTX. + + Note: this callback will be deprecated when gQUIC support is removed. + This callback is optional. .. member:: ssize_t (*on_dg_write)(lsquic_conn_t *c, void *buf, size_t buf_sz) diff --git a/docs/conf.py b/docs/conf.py index bb8850a..806b82f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -24,9 +24,9 @@ copyright = u'2021, LiteSpeed Technologies' author = u'LiteSpeed Technologies' # The short X.Y version -version = u'2.27' +version = u'2.28' # The full version, including alpha/beta/rc tags -release = u'2.27.6' +release = u'2.28.0' # -- General configuration --------------------------------------------------- diff --git a/include/lsquic.h b/include/lsquic.h index f2643b6..13897c5 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 27 -#define LSQUIC_PATCH_VERSION 6 +#define LSQUIC_MINOR_VERSION 28 +#define LSQUIC_PATCH_VERSION 0 /** * Engine flags: @@ -208,6 +208,11 @@ struct lsquic_stream_if { /** * This optional callback lets client record information needed to * perform a session resumption next time around. + * + * For IETF QUIC, this is called only if ea_get_ssl_ctx() is *not* set, + * in which case the library creates its own SSL_CTX. + * + * Note: this callback will be deprecated when gQUIC support is removed. */ void (*on_sess_resume_info)(lsquic_conn_t *c, const unsigned char *, size_t); /** @@ -234,6 +239,7 @@ struct lsquic_stream_if { struct ssl_ctx_st; struct ssl_st; +struct ssl_session_st; struct lsxpack_header; /** @@ -440,6 +446,9 @@ typedef struct ssl_ctx_st * (*lsquic_lookup_cert_f)( */ #define LSQUIC_DF_MAX_BATCH_SIZE 0 +/** Transport parameter sanity checks are performed by default. */ +#define LSQUIC_DF_CHECK_TP_SANITY 1 + struct lsquic_engine_settings { /** * This is a bit mask wherein each bit corresponds to a value in @@ -1046,13 +1055,22 @@ struct lsquic_engine_settings { int es_delay_onclose; /** - * If set to a non-zero value, specifies maximum batch size. (The + * If set to a non-zero value, specified maximum batch size. (The * batch of packets passed to @ref ea_packets_out() callback). Must * be no larger than 1024. * * Default value is @ref LSQUIC_DF_MAX_BATCH_SIZE */ unsigned es_max_batch_size; + + /** + * When true, sanity checks are performed on peer's transport parameter + * values. If some limits are set suspiciously low, the connection won't + * be established. + * + * Default value is @ref LSQUIC_DF_CHECK_TP_SANITY + */ + int es_check_tp_sanity; }; /* Initialize `settings' to default values */ @@ -1337,8 +1355,10 @@ struct lsquic_engine_api /** * Optional interface to control the creation of connection IDs */ - void (*ea_generate_scid)(lsquic_conn_t *, - lsquic_cid_t *, unsigned); + void (*ea_generate_scid)(void *ctx, + lsquic_conn_t *, lsquic_cid_t *, unsigned); + /** Passed to ea_generate_scid() */ + void *ea_gen_scid_ctx; }; /** @@ -2022,6 +2042,18 @@ lsquic_is_valid_hs_packet (lsquic_engine_t *, const unsigned char *, size_t); int lsquic_cid_from_packet (const unsigned char *, size_t bufsz, lsquic_cid_t *cid); +/** + * On success, offset to the CID is returned (a non-negative value). + * `cid_len' is set to the length of the CID. The server perspective + * is assumed. `server_cid_len' is set to the length of the CIDs that + * server generates. + * + * On failure, a negative value is returned. + */ +int +lsquic_dcid_from_packet (const unsigned char *, size_t bufsz, + unsigned server_cid_len, unsigned *cid_len); + /** * Returns true if there are connections to be processed, false otherwise. * If true, `diff' is set to the difference between the earliest advisory @@ -2065,6 +2097,18 @@ lsquic_ver2str[N_LSQVER]; lsquic_conn_t * lsquic_ssl_to_conn (const struct ssl_st *); +/* Return session resumption information that can be used on subsequenct + * connection as argument to lsquic_engine_connect(). Call from inside + * SSL's new session callback. + * + * Returns 0 on success. In this case, `buf' is made to point to newly + * allocated memory containing `buf_sz' bytes. It is the caller's + * responsibility to free the memory. + */ +int +lsquic_ssl_sess_to_resume_info (struct ssl_st *, struct ssl_session_st *, + unsigned char **buf, size_t *buf_sz); + #ifdef __cplusplus } #endif diff --git a/src/liblsquic/lsquic_conn.c b/src/liblsquic/lsquic_conn.c index ca21e93..cd48d4b 100644 --- a/src/liblsquic/lsquic_conn.c +++ b/src/liblsquic/lsquic_conn.c @@ -239,7 +239,7 @@ lsquic_generate_cid (lsquic_cid_t *cid, size_t len) void -lsquic_generate_scid (struct lsquic_conn *lconn, lsquic_cid_t *scid, +lsquic_generate_scid (void *ctx, struct lsquic_conn *lconn, lsquic_cid_t *scid, unsigned len) { lsquic_generate_cid(scid, len); diff --git a/src/liblsquic/lsquic_conn.h b/src/liblsquic/lsquic_conn.h index cd5f249..4979240 100644 --- a/src/liblsquic/lsquic_conn.h +++ b/src/liblsquic/lsquic_conn.h @@ -393,7 +393,7 @@ void lsquic_generate_cid_gquic (lsquic_cid_t *cid); void -lsquic_generate_scid (struct lsquic_conn *lconn, lsquic_cid_t *scid, +lsquic_generate_scid (void *, struct lsquic_conn *lconn, lsquic_cid_t *scid, unsigned len); void diff --git a/src/liblsquic/lsquic_enc_sess_ietf.c b/src/liblsquic/lsquic_enc_sess_ietf.c index 562dc5d..ea6190b 100644 --- a/src/liblsquic/lsquic_enc_sess_ietf.c +++ b/src/liblsquic/lsquic_enc_sess_ietf.c @@ -979,7 +979,7 @@ iquic_esfi_create_client (const char *hostname, SSL_set_ex_data(enc_sess->esi_ssl, s_idx, enc_sess); SSL_set_connect_state(enc_sess->esi_ssl); - if (enc_sess->esi_enpub->enp_stream_if->on_sess_resume_info) + if (SSL_CTX_sess_get_new_cb(ssl_ctx)) enc_sess->esi_flags |= ESI_WANT_TICKET; enc_sess->esi_alset = alset; lsquic_alarmset_init_alarm(enc_sess->esi_alset, AL_SESS_TICKET, @@ -1434,10 +1434,14 @@ iquic_esfi_init_server (enc_session_t *enc_session_p) } while (0) #endif + +/* Return 0 on success, in which case *buf is newly allocated memory and should + * be freed by the caller. + */ static int -iquic_new_session_cb (SSL *ssl, SSL_SESSION *session) +iquic_ssl_sess_to_resume_info (struct enc_sess_iquic *enc_sess, SSL *ssl, + SSL_SESSION *session, unsigned char **bufp, size_t *buf_szp) { - struct enc_sess_iquic *enc_sess; uint32_t num; unsigned char *p, *buf; uint8_t *ticket_buf; @@ -1446,33 +1450,29 @@ iquic_new_session_cb (SSL *ssl, SSL_SESSION *session) const uint8_t *trapa_buf; size_t trapa_sz, buf_sz; - enc_sess = SSL_get_ex_data(ssl, s_idx); - assert(enc_sess->esi_enpub->enp_stream_if->on_sess_resume_info); - - SSL_get_peer_quic_transport_params(enc_sess->esi_ssl, &trapa_buf, - &trapa_sz); + SSL_get_peer_quic_transport_params(ssl, &trapa_buf, &trapa_sz); if (!(trapa_buf + trapa_sz)) { LSQ_WARN("no transport parameters: cannot generate session " "resumption info"); - return 0; + return -1; } if (trapa_sz > UINT32_MAX) { LSQ_WARN("trapa size too large: %zu", trapa_sz); - return 0; + return -1; } if (!SSL_SESSION_to_bytes(session, &ticket_buf, &ticket_sz)) { LSQ_INFO("could not serialize new session"); - return 0; + return -1; } if (ticket_sz > UINT32_MAX) { LSQ_WARN("ticket size too large: %zu", ticket_sz); OPENSSL_free(ticket_buf); - return 0; + return -1; } buf_sz = sizeof(tag) + sizeof(uint32_t) + sizeof(uint32_t) @@ -1481,8 +1481,8 @@ iquic_new_session_cb (SSL *ssl, SSL_SESSION *session) if (!buf) { OPENSSL_free(ticket_buf); - LSQ_WARN("%s: malloc failed", __func__); - return 0; + LSQ_INFO("%s: malloc failed", __func__); + return -1; } p = buf; @@ -1503,8 +1503,26 @@ iquic_new_session_cb (SSL *ssl, SSL_SESSION *session) LSQ_DEBUG("generated %zu bytes of session resumption buffer", buf_sz); - enc_sess->esi_enpub->enp_stream_if->on_sess_resume_info(enc_sess->esi_conn, - buf, buf_sz); + *bufp = buf; + *buf_szp = buf_sz; + return 0; +} + + +static int +iquic_new_session_cb (SSL *ssl, SSL_SESSION *session) +{ + struct enc_sess_iquic *enc_sess; + unsigned char *buf; + size_t buf_sz; + + enc_sess = SSL_get_ex_data(ssl, s_idx); + assert(enc_sess->esi_enpub->enp_stream_if->on_sess_resume_info); + + if (0 == iquic_ssl_sess_to_resume_info(enc_sess, ssl, session, &buf, + &buf_sz)) + enc_sess->esi_enpub->enp_stream_if->on_sess_resume_info( + enc_sess->esi_conn, buf, buf_sz); free(buf); enc_sess->esi_flags &= ~ESI_WANT_TICKET; lsquic_alarmset_unset(enc_sess->esi_alset, AL_SESS_TICKET); @@ -1805,6 +1823,36 @@ get_peer_transport_params (struct enc_sess_iquic *enc_sess) LSQ_DEBUG("greasing turned off: won't grease the QUIC bit"); } + if (enc_sess->esi_enpub->enp_settings.es_check_tp_sanity + /* We only care (and know) about HTTP/3. Other protocols may have + * their own limitations. The most generic way to do this would be + * to factor out transport parameter sanity check into a callback. + */ + && enc_sess->esi_alpn && enc_sess->esi_alpn[0] >= 2 + && enc_sess->esi_alpn[1] == 'h' + && enc_sess->esi_alpn[2] == '3') + { + const enum transport_param_id stream_data = enc_sess->esi_flags + & ESI_SERVER ? TPI_INIT_MAX_STREAM_DATA_BIDI_LOCAL + : TPI_INIT_MAX_STREAM_DATA_BIDI_REMOTE; + if (!((trans_params->tp_set & (1 << stream_data)) + && trans_params->tp_numerics[stream_data] >= 0x1000)) + { + LSQ_INFO("peer transport parameters: %s=%"PRIu64" does not pass " + "sanity check", lsquic_tpi2str[stream_data], + trans_params->tp_numerics[stream_data]); + return -1; + } + if (!((trans_params->tp_set & (1 << TPI_INIT_MAX_DATA)) + && trans_params->tp_numerics[TPI_INIT_MAX_DATA] >= 0x1000)) + { + LSQ_INFO("peer transport parameters: %s=%"PRIu64" does not pass " + "sanity check", lsquic_tpi2str[TPI_INIT_MAX_DATA], + trans_params->tp_numerics[TPI_INIT_MAX_DATA]); + return -1; + } + } + return 0; } @@ -3400,3 +3448,28 @@ lsquic_ssl_to_conn (const struct ssl_st *ssl) return enc_sess->esi_conn; } + + +int +lsquic_ssl_sess_to_resume_info (SSL *ssl, SSL_SESSION *session, + unsigned char **buf, size_t *buf_sz) +{ + struct enc_sess_iquic *enc_sess; + int status; + + if (s_idx < 0) + return -1; + + enc_sess = SSL_get_ex_data(ssl, s_idx); + if (!enc_sess) + return -1; + + status = iquic_ssl_sess_to_resume_info(enc_sess, ssl, session, buf, buf_sz); + if (status == 0) + { + LSQ_DEBUG("%s called successfully, unset WANT_TICKET flag", __func__); + enc_sess->esi_flags &= ~ESI_WANT_TICKET; + lsquic_alarmset_unset(enc_sess->esi_alset, AL_SESS_TICKET); + } + return status; +} diff --git a/src/liblsquic/lsquic_engine.c b/src/liblsquic/lsquic_engine.c index d633767..39dac79 100644 --- a/src/liblsquic/lsquic_engine.c +++ b/src/liblsquic/lsquic_engine.c @@ -393,6 +393,7 @@ lsquic_engine_init_settings (struct lsquic_engine_settings *settings, settings->es_ptpc_err_thresh = LSQUIC_DF_PTPC_ERR_THRESH; settings->es_ptpc_err_divisor= LSQUIC_DF_PTPC_ERR_DIVISOR; settings->es_delay_onclose = LSQUIC_DF_DELAY_ONCLOSE; + settings->es_check_tp_sanity = LSQUIC_DF_CHECK_TP_SANITY; } @@ -616,7 +617,10 @@ lsquic_engine_new (unsigned flags, engine->pub.enp_get_ssl_ctx = api->ea_get_ssl_ctx; if (api->ea_generate_scid) + { engine->pub.enp_generate_scid = api->ea_generate_scid; + engine->pub.enp_gen_scid_ctx = api->ea_gen_scid_ctx; + } else engine->pub.enp_generate_scid = lsquic_generate_scid; diff --git a/src/liblsquic/lsquic_engine_public.h b/src/liblsquic/lsquic_engine_public.h index 337b79d..ff5f2b4 100644 --- a/src/liblsquic/lsquic_engine_public.h +++ b/src/liblsquic/lsquic_engine_public.h @@ -48,8 +48,9 @@ struct lsquic_engine_public { void *enp_stream_if_ctx; const struct lsquic_hset_if *enp_hsi_if; void *enp_hsi_ctx; - void (*enp_generate_scid)(struct lsquic_conn *, - struct lsquic_cid *, unsigned); + void (*enp_generate_scid)(void *, + struct lsquic_conn *, struct lsquic_cid *, unsigned); + void *enp_gen_scid_ctx; int (*enp_verify_cert)(void *verify_ctx, struct stack_st_X509 *chain); void *enp_verify_ctx; diff --git a/src/liblsquic/lsquic_full_conn.c b/src/liblsquic/lsquic_full_conn.c index caace14..b9e4165 100644 --- a/src/liblsquic/lsquic_full_conn.c +++ b/src/liblsquic/lsquic_full_conn.c @@ -336,6 +336,7 @@ recent_packet_hist_new (struct full_conn *conn, unsigned out, conn->fc_recent_packets[out].els[idx].time = time; } + static void recent_packet_hist_frames (struct full_conn *conn, unsigned out, enum quic_ft_bit frame_types) @@ -344,6 +345,8 @@ recent_packet_hist_frames (struct full_conn *conn, unsigned out, idx = (conn->fc_recent_packets[out].idx - 1) % KEEP_PACKET_HISTORY; conn->fc_recent_packets[out].els[idx].frame_types |= frame_types; } + + #else #define recent_packet_hist_new(conn, out, time) #define recent_packet_hist_frames(conn, out, frames) @@ -543,6 +546,7 @@ apply_peer_settings (struct full_conn *conn) return 0; } + static const struct conn_iface *full_conn_iface_ptr; @@ -1248,6 +1252,8 @@ verify_ack_frame (struct full_conn *conn, const unsigned char *buf, int bufsz) assert(i == ack_info->n_ranges); LSQ_DEBUG("Sent ACK frame %s", ack_buf); } + + #endif @@ -4059,7 +4065,6 @@ headers_stream_on_priority (void *ctx, lsquic_stream_id_t stream_id, } - #define STRLEN(s) (sizeof(s) - 1) static struct uncompressed_headers * @@ -4456,6 +4461,7 @@ full_conn_ci_get_stats (struct lsquic_conn *lconn) return &conn->fc_stats; } + #include "lsquic_cong_ctl.h" static void @@ -4493,6 +4499,8 @@ full_conn_ci_log_stats (struct lsquic_conn *lconn) *conn->fc_last_stats = conn->fc_stats; memset(bs, 0, sizeof(*bs)); } + + #endif diff --git a/src/liblsquic/lsquic_full_conn_ietf.c b/src/liblsquic/lsquic_full_conn_ietf.c index 1133011..af8b2a4 100644 --- a/src/liblsquic/lsquic_full_conn_ietf.c +++ b/src/liblsquic/lsquic_full_conn_ietf.c @@ -1184,7 +1184,7 @@ ietf_full_conn_add_scid (struct ietf_full_conn *conn, } if (enpub->enp_settings.es_scid_len) - enpub->enp_generate_scid(lconn, &cce->cce_cid, + enpub->enp_generate_scid(enpub->enp_gen_scid_ctx, lconn, &cce->cce_cid, enpub->enp_settings.es_scid_len); cce->cce_seqno = conn->ifc_scid_seqno++; diff --git a/src/liblsquic/lsquic_mini_conn_ietf.c b/src/liblsquic/lsquic_mini_conn_ietf.c index 1d8bcc5..933440a 100644 --- a/src/liblsquic/lsquic_mini_conn_ietf.c +++ b/src/liblsquic/lsquic_mini_conn_ietf.c @@ -500,7 +500,7 @@ lsquic_mini_conn_ietf_new (struct lsquic_engine_public *enpub, /* Generate new SCID. Since is not the original SCID, it is given * a sequence number (0) and therefore can be retired by the client. */ - enpub->enp_generate_scid(&conn->imc_conn, + enpub->enp_generate_scid(enpub->enp_gen_scid_ctx, &conn->imc_conn, &conn->imc_conn.cn_cces[1].cce_cid, enpub->enp_settings.es_scid_len); LSQ_DEBUGC("generated SCID %"CID_FMT" at index %u, switching to it", diff --git a/src/liblsquic/lsquic_parse_common.c b/src/liblsquic/lsquic_parse_common.c index 8fe48f3..c98cdea 100644 --- a/src/liblsquic/lsquic_parse_common.c +++ b/src/liblsquic/lsquic_parse_common.c @@ -217,6 +217,122 @@ lsquic_cid_from_packet (const unsigned char *buf, size_t bufsz, } +int +lsquic_dcid_from_packet (const unsigned char *buf, size_t bufsz, + unsigned server_cid_len, unsigned *cid_len) +{ + const unsigned char *p; + unsigned dcil, scil; + + if (bufsz < 9) + return -1; + + switch (buf[0] >> 3) + { + /* Xs vary, Gs are iGnored: */ + /* 1X11 XGGG: */ + case (0x80|0x40|0x20|0x10|0x08) >> 3: + case (0x80|0x00|0x20|0x10|0x08) >> 3: + case (0x80|0x40|0x20|0x10|0x00) >> 3: + case (0x80|0x00|0x20|0x10|0x00) >> 3: + Q046_long: + /* lsquic_Q046_parse_packet_in_long_begin */ + if (bufsz < 14) + return -1; + p = buf + 5; + dcil = p[0] >> 4; + if (dcil) + dcil += 3; + scil = p[0] & 0xF; + if (scil) + scil += 3; + ++p; + if (dcil == GQUIC_CID_LEN && scil == 0) + { + *cid_len = GQUIC_CID_LEN; + return (unsigned) (p - buf); + } + else + return -1; + /* 1X00 XGGG: */ + /* + case (0x80|0x40|0x00|0x00|0x08) >> 3: + case (0x80|0x00|0x00|0x00|0x08) >> 3: + case (0x80|0x40|0x00|0x00|0x00) >> 3: + case (0x80|0x00|0x00|0x00|0x00) >> 3: + case (0x80|0x40|0x00|0x10|0x08) >> 3: + case (0x80|0x00|0x00|0x10|0x08) >> 3: + case (0x80|0x40|0x00|0x10|0x00) >> 3: + case (0x80|0x00|0x00|0x10|0x00) >> 3: + case (0x80|0x40|0x20|0x00|0x08) >> 3: + case (0x80|0x00|0x20|0x00|0x08) >> 3: + case (0x80|0x40|0x20|0x00|0x00) >> 3: + case (0x80|0x00|0x20|0x00|0x00) >> 3: + */ + default: + /* parse_ietf_v1_or_Q046plus_long_begin */ + if (buf[4] == (unsigned) '6') + goto Q046_long; + /* lsquic_Q050_parse_packet_in_long_begin or + lsquic_ietf_v1_parse_packet_in_long_begin */ + if (bufsz < 14) + return -1; + dcil = buf[5]; + if (dcil <= MAX_CID_LEN && 6 + dcil < bufsz) + { + *cid_len = dcil; + return 6; + } + else + return -1; + /* 01XX XGGG */ + case (0x00|0x40|0x00|0x00|0x00) >> 3: + case (0x00|0x40|0x00|0x00|0x08) >> 3: + case (0x00|0x40|0x00|0x10|0x00) >> 3: + case (0x00|0x40|0x00|0x10|0x08) >> 3: + case (0x00|0x40|0x20|0x00|0x00) >> 3: + case (0x00|0x40|0x20|0x00|0x08) >> 3: + case (0x00|0x40|0x20|0x10|0x00) >> 3: + case (0x00|0x40|0x20|0x10|0x08) >> 3: + /* lsquic_ietf_v1_parse_packet_in_short_begin */ + if (1 + server_cid_len <= bufsz) + { + *cid_len = server_cid_len; + return 1; + } + else + return -1; + /* 00XX 0GGG */ + case (0x00|0x00|0x00|0x00|0x00) >> 3: + case (0x00|0x00|0x00|0x10|0x00) >> 3: + case (0x00|0x00|0x20|0x00|0x00) >> 3: + case (0x00|0x00|0x20|0x10|0x00) >> 3: + /* lsquic_Q046_parse_packet_in_short_begin */ + if (1 + server_cid_len <= bufsz && (buf[0] & 0x40)) + { + *cid_len = server_cid_len; + return 1; + } + else + return -1; + /* 00XX 1GGG */ + case (0x00|0x00|0x00|0x00|0x08) >> 3: + case (0x00|0x00|0x00|0x10|0x08) >> 3: + case (0x00|0x00|0x20|0x00|0x08) >> 3: + case (0x00|0x00|0x20|0x10|0x08) >> 3: + /* lsquic_gquic_parse_packet_in_begin */ + if (1 + GQUIC_CID_LEN <= bufsz + && (buf[0] & PACKET_PUBLIC_FLAGS_8BYTE_CONNECTION_ID)) + { + *cid_len = server_cid_len; + return 1; + } + else + return -1; + } +} + + /* 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] = { diff --git a/src/liblsquic/lsquic_stream.c b/src/liblsquic/lsquic_stream.c index 8a9580c..f062834 100644 --- a/src/liblsquic/lsquic_stream.c +++ b/src/liblsquic/lsquic_stream.c @@ -3469,21 +3469,30 @@ stream_write_to_packets (lsquic_stream_t *stream, struct lsquic_reader *reader, } -/* Perform an implicit flush when we hit connection limit while buffering - * data. This is to prevent a (theoretical) stall: +/* Perform an implicit flush when we hit connection or stream flow control + * limit while buffering data. + * + * This is to prevent a (theoretical) stall. Scenario 1: * * Imagine a number of streams, all of which buffered some data. The buffered * data is up to connection cap, which means no further writes are possible. * None of them flushes, which means that data is not sent and connection * WINDOW_UPDATE frame never arrives from peer. Stall. + * + * Scenario 2: + * + * Stream flow control window is smaller than the packetizing threshold. In + * this case, without a flush, the peer will never send a WINDOW_UPDATE. Stall. */ static int maybe_flush_stream (struct lsquic_stream *stream) { - if (stream->sm_n_buffered > 0 - && (stream->sm_bflags & SMBF_CONN_LIMITED) - && lsquic_conn_cap_avail(&stream->conn_pub->conn_cap) == 0) + if (stream->sm_n_buffered > 0 && stream->sm_write_avail(stream) == 0) + { + LSQ_DEBUG("out of flow control credits, flush %zu buffered bytes", + stream->sm_n_buffered + active_hq_frame_sizes(stream)); return stream_flush_nocheck(stream); + } else return 0; } @@ -4033,7 +4042,12 @@ send_headers_ietf (struct lsquic_stream *stream, return -1; #endif - stream->stream_flags &= ~STREAM_PUSHING; + if (stream->stream_flags & STREAM_PUSHING) + { + LSQ_DEBUG("push promise still being written, cannot send header now"); + errno = EBADMSG; + return -1; + } stream->stream_flags |= STREAM_NOPUSH; /* TODO: Optimize for the common case: write directly to sm_buf and fall @@ -5326,6 +5340,7 @@ on_write_pp_wrapper (struct lsquic_stream *stream, lsquic_stream_ctx_t *h) nw, promise->pp_write_state == PPWS_DONE ? "done" : "not done"); if (promise->pp_write_state == PPWS_DONE) { + stream->stream_flags &= ~STREAM_PUSHING; /* Restore want_write flag */ want_write = !!(stream->sm_qflags & SMQF_WANT_WRITE); if (want_write != stream->sm_saved_want_write) @@ -5353,6 +5368,7 @@ lsquic_stream_push_promise (struct lsquic_stream *stream, struct push_promise *promise) { struct lsquic_reader pp_reader; + struct stream_hq_frame *shf; unsigned bits, len; ssize_t nw; @@ -5365,21 +5381,33 @@ lsquic_stream_push_promise (struct lsquic_stream *stream, vint_write(promise->pp_encoded_push_id + 8 - len, promise->pp_id, bits, 1 << bits); - if (!stream_activate_hq_frame(stream, + shf = stream_activate_hq_frame(stream, stream->sm_payload + stream->sm_n_buffered, HQFT_PUSH_PROMISE, - SHF_FIXED_SIZE, pp_reader_size(promise))) + SHF_FIXED_SIZE, pp_reader_size(promise)); + if (!shf) return -1; stream->stream_flags |= STREAM_PUSHING; init_pp_reader(promise, &pp_reader); +#ifdef FIU_ENABLE + if (fiu_fail("stream/fail_initial_pp_write")) + { + LSQ_NOTICE("%s: failed to write push promise (fiu)", __func__); + nw = -1; + } + else +#endif nw = stream_write(stream, &pp_reader, SWO_BUFFER); if (nw > 0) { SLIST_INSERT_HEAD(&stream->sm_promises, promise, pp_next); ++promise->pp_refcnt; if (promise->pp_write_state == PPWS_DONE) + { LSQ_DEBUG("fully wrote promise %"PRIu64, promise->pp_id); + stream->stream_flags &= ~STREAM_PUSHING; + } else { LSQ_DEBUG("partially wrote promise %"PRIu64" (state: %d, off: %u)" @@ -5388,6 +5416,7 @@ lsquic_stream_push_promise (struct lsquic_stream *stream, stream->stream_flags |= STREAM_NOPUSH; stream->sm_saved_want_write = !!(stream->sm_qflags & SMQF_WANT_WRITE); + lsquic_stream_flush(stream); stream_wantwrite(stream, 1); } return 0; @@ -5396,6 +5425,7 @@ lsquic_stream_push_promise (struct lsquic_stream *stream, { if (nw < 0) LSQ_WARN("failure writing push promise"); + stream_hq_frame_put(stream, shf); stream->stream_flags |= STREAM_NOPUSH; stream->stream_flags &= ~STREAM_PUSHING; return -1; diff --git a/tests/test_send_headers.c b/tests/test_send_headers.c index 762a427..daa0d5d 100644 --- a/tests/test_send_headers.c +++ b/tests/test_send_headers.c @@ -531,7 +531,8 @@ test_pp_wantwrite_restoration (const int want_write) lsquic_stream_window_update(stream, 100); lsquic_stream_dispatch_write_events(stream); assert((stream->stream_flags & (STREAM_NOPUSH|STREAM_PUSHING)) - == (STREAM_NOPUSH|STREAM_PUSHING)); + /* After push promise was all written, STREAM_PUSHING is no longer set */ + == STREAM_NOPUSH); assert(SLIST_FIRST(&stream->sm_promises)->pp_write_state == PPWS_DONE); /* Done! */ assert(want_write == s_onwrite_called); /* Restored: and on_write called */