diff --git a/CHANGELOG b/CHANGELOG index 46a7d3011..71389511e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,13 @@ +2019-09-23 + - 2.4.2 + - [BUGFIX] H3 framing: fix zero-byte write when space is available + - [BUGFIX] Don't send STREAM frame when incoming unidirectgional stream + is closed + - [BUGFIX] Cancel all pending writes by stream reset by a GOAWAY + - [BUGFIX] Fix use-after-free in IETF full conn + - [OPTIMIZATION] Wait for session tickets for two seconds and then drop + SSL object and crypto streams. + 2019-09-18 - 2.4.0 - [FEATURE] QUIC and HTTP/3 Internet Draft 23 support diff --git a/include/lsquic.h b/include/lsquic.h index 8bc23684e..9c7d65525 100644 --- a/include/lsquic.h +++ b/include/lsquic.h @@ -25,7 +25,7 @@ extern "C" { #define LSQUIC_MAJOR_VERSION 2 #define LSQUIC_MINOR_VERSION 4 -#define LSQUIC_PATCH_VERSION 0 +#define LSQUIC_PATCH_VERSION 2 /** * Engine flags: diff --git a/src/liblsquic/lsquic_alarmset.c b/src/liblsquic/lsquic_alarmset.c index 1c3c8ba9b..2724c55cc 100644 --- a/src/liblsquic/lsquic_alarmset.c +++ b/src/liblsquic/lsquic_alarmset.c @@ -47,6 +47,7 @@ static const char *const lsquic_alid2str[] = [AL_CID_THROT] = "CID_THROT", [AL_PATH_CHAL_0] = "PATH_CHAL_0", [AL_PATH_CHAL_1] = "PATH_CHAL_1", + [AL_SESS_TICKET] = "SESS_TICKET", }; diff --git a/src/liblsquic/lsquic_alarmset.h b/src/liblsquic/lsquic_alarmset.h index 9ca46a293..d550717ac 100644 --- a/src/liblsquic/lsquic_alarmset.h +++ b/src/liblsquic/lsquic_alarmset.h @@ -35,6 +35,7 @@ enum alarm_id { AL_PATH_CHAL, AL_PATH_CHAL_0 = AL_PATH_CHAL, AL_PATH_CHAL_1, + AL_SESS_TICKET, MAX_LSQUIC_ALARMS }; @@ -54,6 +55,7 @@ enum alarm_id_bit { ALBIT_PATH_CHAL = 1 << AL_PATH_CHAL, ALBIT_PATH_CHAL_0 = 1 << AL_PATH_CHAL_0, ALBIT_PATH_CHAL_1 = 1 << AL_PATH_CHAL_1, + ALBIT_SESS_TICKET = 1 << AL_SESS_TICKET, }; diff --git a/src/liblsquic/lsquic_conn.h b/src/liblsquic/lsquic_conn.h index a1c2b91d2..c7e2efdf8 100644 --- a/src/liblsquic/lsquic_conn.h +++ b/src/liblsquic/lsquic_conn.h @@ -240,6 +240,10 @@ struct conn_iface const lsquic_cid_t * (*ci_get_log_cid) (const struct lsquic_conn *); + + /* Optional method. Only used by the IETF client code. */ + void + (*ci_drop_crypto_streams) (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 627dd3989..266dd683e 100644 --- a/src/liblsquic/lsquic_enc_sess.h +++ b/src/liblsquic/lsquic_enc_sess.h @@ -2,6 +2,7 @@ #ifndef LSQUIC_ENC_SESS_H #define LSQUIC_ENC_SESS_H 1 +struct lsquic_alarmset; struct lsquic_engine_public; struct lsquic_packet_out; struct lsquic_packet_in; @@ -262,7 +263,8 @@ struct enc_session_funcs_iquic struct lsquic_conn *, const struct lsquic_cid *, const struct ver_neg *, void *(crypto_streams)[4], const struct crypto_stream_if *, - const unsigned char *, size_t); + const unsigned char *, size_t, + struct lsquic_alarmset *); void (*esfi_destroy) (enc_session_t *); diff --git a/src/liblsquic/lsquic_enc_sess_ietf.c b/src/liblsquic/lsquic_enc_sess_ietf.c index e5e56133c..162bfc438 100644 --- a/src/liblsquic/lsquic_enc_sess_ietf.c +++ b/src/liblsquic/lsquic_enc_sess_ietf.c @@ -44,6 +44,7 @@ #include "lsquic_frab_list.h" #include "lsquic_tokgen.h" #include "lsquic_ietf.h" +#include "lsquic_alarmset.h" #if __GNUC__ # define UNLIKELY(cond) __builtin_expect(cond, 0) @@ -106,6 +107,10 @@ iquic_esf_get_server_cert_chain (enc_session_t *); static void maybe_drop_SSL (struct enc_sess_iquic *); +static void +no_sess_ticket (enum alarm_id alarm_id, void *ctx, + lsquic_time_t expiry, lsquic_time_t now); + typedef void (*gen_hp_mask_f)(struct enc_sess_iquic *, const struct header_prot *, unsigned cliser, @@ -236,6 +241,7 @@ struct enc_sess_iquic ESI_ALPN_CHECKED = 1 << 8, ESI_CACHED_INFO = 1 << 9, ESI_1RTT_ACKED = 1 << 10, + ESI_WANT_TICKET = 1 << 11, } esi_flags; enum evp_aead_direction_t esi_dir[2]; /* client, server */ @@ -264,6 +270,8 @@ struct enc_sess_iquic struct frab_list esi_frals[N_ENC_LEVS]; struct transport_params esi_peer_tp; + struct lsquic_alarmset + *esi_alset; }; @@ -651,7 +659,8 @@ iquic_esfi_create_client (const char *hostname, struct lsquic_engine_public *enpub, struct lsquic_conn *lconn, const lsquic_cid_t *dcid, const struct ver_neg *ver_neg, void *crypto_streams[4], const struct crypto_stream_if *cryst_if, - const unsigned char *zero_rtt, size_t zero_rtt_sz) + const unsigned char *zero_rtt, size_t zero_rtt_sz, + struct lsquic_alarmset *alset) { struct enc_sess_iquic *enc_sess; @@ -720,6 +729,12 @@ iquic_esfi_create_client (const char *hostname, enc_sess->esi_zero_rtt_sz = 0; } + if (enc_sess->esi_enpub->enp_stream_if->on_zero_rtt_info) + enc_sess->esi_flags |= ESI_WANT_TICKET; + enc_sess->esi_alset = alset; + lsquic_alarmset_init_alarm(enc_sess->esi_alset, AL_SESS_TICKET, + no_sess_ticket, enc_sess); + return enc_sess; } @@ -1192,6 +1207,8 @@ iquic_new_session_cb (SSL *ssl, SSL_SESSION *session) enc_sess->esi_enpub->enp_stream_if->on_zero_rtt_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); return 0; } @@ -2202,6 +2219,20 @@ cache_info (struct enc_sess_iquic *enc_sess) } +static void +drop_SSL (struct enc_sess_iquic *enc_sess) +{ + LSQ_DEBUG("drop SSL object"); + if (enc_sess->esi_conn->cn_if->ci_drop_crypto_streams) + enc_sess->esi_conn->cn_if->ci_drop_crypto_streams( + enc_sess->esi_conn); + cache_info(enc_sess); + SSL_free(enc_sess->esi_ssl); + enc_sess->esi_ssl = NULL; + free_handshake_keys(enc_sess); +} + + static void maybe_drop_SSL (struct enc_sess_iquic *enc_sess) { @@ -2216,15 +2247,32 @@ maybe_drop_SSL (struct enc_sess_iquic *enc_sess) && enc_sess->esi_ssl && lsquic_frab_list_empty(&enc_sess->esi_frals[ENC_LEV_FORW])) { - LSQ_DEBUG("drop SSL object"); - cache_info(enc_sess); - SSL_free(enc_sess->esi_ssl); - enc_sess->esi_ssl = NULL; - free_handshake_keys(enc_sess); + if ((enc_sess->esi_flags & (ESI_SERVER|ESI_WANT_TICKET)) + != ESI_WANT_TICKET) + drop_SSL(enc_sess); + else if (enc_sess->esi_alset + && !lsquic_alarmset_is_set(enc_sess->esi_alset, AL_SESS_TICKET)) + { + LSQ_DEBUG("no session ticket: delay dropping SSL object"); + lsquic_alarmset_set(enc_sess->esi_alset, AL_SESS_TICKET, + /* Wait up to two seconds for session tickets */ + lsquic_time_now() + 2000000); + } } } +static void +no_sess_ticket (enum alarm_id alarm_id, void *ctx, + lsquic_time_t expiry, lsquic_time_t now) +{ + struct enc_sess_iquic *enc_sess = ctx; + + LSQ_DEBUG("no session tickets forthcoming -- drop SSL"); + drop_SSL(enc_sess); +} + + typedef char enums_have_the_same_value[ (int) ssl_encryption_initial == (int) ENC_LEV_CLEAR && (int) ssl_encryption_early_data == (int) ENC_LEV_EARLY && @@ -2583,23 +2631,43 @@ readf_cb (void *ctx, const unsigned char *buf, size_t len, int fin) } +static size_t +discard_cb (void *ctx, const unsigned char *buf, size_t len, int fin) +{ + return len; +} + + static void chsk_ietf_on_read (struct lsquic_stream *stream, lsquic_stream_ctx_t *ctx) { struct enc_sess_iquic *const enc_sess = (void *) ctx; enum enc_level enc_level = enc_sess->esi_cryst_if->csi_enc_level(stream); struct readf_ctx readf_ctx = { enc_sess, enc_level, 0, }; + ssize_t nread; - ssize_t nread = enc_sess->esi_cryst_if->csi_readf(stream, - readf_cb, &readf_ctx); - - if (!(nread < 0 || readf_ctx.err)) - iquic_esfi_shake_stream((enc_session_t *)enc_sess, stream, "on_read"); + if (enc_sess->esi_ssl) + { + nread = enc_sess->esi_cryst_if->csi_readf(stream, readf_cb, &readf_ctx); + if (!(nread < 0 || readf_ctx.err)) + iquic_esfi_shake_stream((enc_session_t *)enc_sess, stream, + "on_read"); + else + enc_sess->esi_conn->cn_if->ci_internal_error(enc_sess->esi_conn, + "shaking stream failed: nread: %zd, err: %d, SSL err: %"PRIu32, + nread, readf_ctx.err, ERR_get_error()); + } else - enc_sess->esi_conn->cn_if->ci_internal_error(enc_sess->esi_conn, - "shaking stream failed: nread: %zd, err: %d, SSL err: %"PRIu32, - nread, readf_ctx.err, ERR_get_error()); + { + /* This branch is reached when we don't want TLS ticket and drop + * the SSL object before we process TLS tickets that have been + * already received and waiting in the incoming stream buffer. + */ + nread = enc_sess->esi_cryst_if->csi_readf(stream, discard_cb, NULL); + lsquic_stream_wantread(stream, 0); + LSQ_DEBUG("no SSL object: discard %zd bytes of SSL data", nread); + } } diff --git a/src/liblsquic/lsquic_full_conn_ietf.c b/src/liblsquic/lsquic_full_conn_ietf.c index bca1a2a6f..99e36045d 100644 --- a/src/liblsquic/lsquic_full_conn_ietf.c +++ b/src/liblsquic/lsquic_full_conn_ietf.c @@ -123,6 +123,7 @@ enum ifull_conn_flags IFC_GOAWAY_CLOSE = 1 << 23, IFC_FIRST_TICK = 1 << 24, IFC_IGNORE_HSK = 1 << 25, + IFC_PROC_CRYPTO = 1 << 26, }; @@ -359,9 +360,9 @@ struct ietf_full_conn unsigned ifc_last_retire_prior_to; lsquic_time_t ifc_last_live_update; struct conn_path ifc_paths[N_PATHS]; - struct lsquic_stream *ifc_crypto_streams[N_ENC_LEVS]; union { struct { + struct lsquic_stream *crypto_streams[N_ENC_LEVS]; struct ver_neg ifcli_ver_neg; uint64_t ifcli_max_push_id; @@ -1035,8 +1036,8 @@ lsquic_ietf_full_conn_client_new (struct lsquic_engine_public *enpub, conn->ifc_conn.cn_esf.i->esfi_create_client(hostname, conn->ifc_enpub, &conn->ifc_conn, CUR_DCID(conn), &conn->ifc_u.cli.ifcli_ver_neg, - (void **) conn->ifc_crypto_streams, &crypto_stream_if, - zero_rtt, zero_rtt_sz); + (void **) conn->ifc_u.cli.crypto_streams, &crypto_stream_if, + zero_rtt, zero_rtt_sz, &conn->ifc_alset); if (!conn->ifc_conn.cn_enc_session) { /* TODO: free other stuff */ @@ -1044,17 +1045,17 @@ lsquic_ietf_full_conn_client_new (struct lsquic_engine_public *enpub, return NULL; } - conn->ifc_crypto_streams[ENC_LEV_CLEAR] = lsquic_stream_new_crypto( + conn->ifc_u.cli.crypto_streams[ENC_LEV_CLEAR] = lsquic_stream_new_crypto( ENC_LEV_CLEAR, &conn->ifc_pub, &lsquic_cry_sm_if, conn->ifc_conn.cn_enc_session, SCF_IETF|SCF_DI_AUTOSWITCH|SCF_CALL_ON_NEW|SCF_CRITICAL); - if (!conn->ifc_crypto_streams[ENC_LEV_CLEAR]) + if (!conn->ifc_u.cli.crypto_streams[ENC_LEV_CLEAR]) { /* TODO: free other stuff */ free(conn); return NULL; } - if (!lsquic_stream_get_ctx(conn->ifc_crypto_streams[ENC_LEV_CLEAR])) + if (!lsquic_stream_get_ctx(conn->ifc_u.cli.crypto_streams[ENC_LEV_CLEAR])) { /* TODO: free other stuff */ free(conn); @@ -1065,9 +1066,10 @@ lsquic_ietf_full_conn_client_new (struct lsquic_engine_public *enpub, if (!conn->ifc_pub.packet_out_malo) { free(conn); - lsquic_stream_destroy(conn->ifc_crypto_streams[ENC_LEV_CLEAR]); + lsquic_stream_destroy(conn->ifc_u.cli.crypto_streams[ENC_LEV_CLEAR]); return NULL; } + conn->ifc_flags |= IFC_PROC_CRYPTO; LSQ_DEBUG("negotiating version %s", lsquic_ver2str[conn->ifc_u.cli.ifcli_ver_neg.vn_ver]); @@ -2322,6 +2324,32 @@ ietf_full_conn_ci_retire_cid (struct lsquic_conn *lconn) } +static void +drop_crypto_streams (struct ietf_full_conn *conn) +{ + struct lsquic_stream **streamp; + unsigned count; + + if (!(conn->ifc_flags & IFC_PROC_CRYPTO)) + return; + + conn->ifc_flags &= ~IFC_PROC_CRYPTO; + + count = 0; + for (streamp = conn->ifc_u.cli.crypto_streams; streamp < + conn->ifc_u.cli.crypto_streams + sizeof(conn->ifc_u.cli.crypto_streams) + / sizeof(conn->ifc_u.cli.crypto_streams[0]); ++streamp) + if (*streamp) + { + lsquic_stream_force_finish(*streamp); + *streamp = NULL; + ++count; + } + + LSQ_DEBUG("dropped %u crypto stream%.*s", count, count != 1, "s"); +} + + static void ietf_full_conn_ci_destroy (struct lsquic_conn *lconn) { @@ -2332,11 +2360,15 @@ ietf_full_conn_ci_destroy (struct lsquic_conn *lconn) struct lsquic_hash_elem *el; unsigned i; - for (streamp = conn->ifc_crypto_streams; streamp < - conn->ifc_crypto_streams + sizeof(conn->ifc_crypto_streams) - / sizeof(conn->ifc_crypto_streams[0]); ++streamp) - if (*streamp) - lsquic_stream_destroy(*streamp); + if (!(conn->ifc_flags & IFC_SERVER)) + { + for (streamp = conn->ifc_u.cli.crypto_streams; streamp < + conn->ifc_u.cli.crypto_streams + + sizeof(conn->ifc_u.cli.crypto_streams) + / sizeof(conn->ifc_u.cli.crypto_streams[0]); ++streamp) + if (*streamp) + lsquic_stream_destroy(*streamp); + } while ((el = lsquic_hash_first(conn->ifc_pub.all_streams))) { stream = lsquic_hashelem_getdata(el); @@ -3366,9 +3398,10 @@ process_crypto_stream_read_events (struct ietf_full_conn *conn) { struct lsquic_stream **stream; - for (stream = conn->ifc_crypto_streams; stream < - conn->ifc_crypto_streams + sizeof(conn->ifc_crypto_streams) - / sizeof(conn->ifc_crypto_streams[0]); ++stream) + assert(!(conn->ifc_flags & IFC_SERVER)); + for (stream = conn->ifc_u.cli.crypto_streams; stream < + conn->ifc_u.cli.crypto_streams + sizeof(conn->ifc_u.cli.crypto_streams) + / sizeof(conn->ifc_u.cli.crypto_streams[0]); ++stream) if (*stream && (*stream)->sm_qflags & SMQF_WANT_READ) lsquic_stream_dispatch_read_events(*stream); } @@ -3379,9 +3412,10 @@ process_crypto_stream_write_events (struct ietf_full_conn *conn) { struct lsquic_stream **stream; - for (stream = conn->ifc_crypto_streams; stream < - conn->ifc_crypto_streams + sizeof(conn->ifc_crypto_streams) - / sizeof(conn->ifc_crypto_streams[0]); ++stream) + assert(!(conn->ifc_flags & IFC_SERVER)); + for (stream = conn->ifc_u.cli.crypto_streams; stream < + conn->ifc_u.cli.crypto_streams + sizeof(conn->ifc_u.cli.crypto_streams) + / sizeof(conn->ifc_u.cli.crypto_streams[0]); ++stream) if (*stream && (*stream)->sm_qflags & SMQF_WRITE_Q_FLAGS) lsquic_stream_dispatch_write_events(*stream); } @@ -4260,9 +4294,8 @@ process_stop_sending_frame (struct ietf_full_conn *conn, } -/* Ignore CRYPTO frames in server mode */ static unsigned -process_crypto_frame_server (struct ietf_full_conn *conn, +discard_crypto_frame (struct ietf_full_conn *conn, struct lsquic_packet_in *packet_in, const unsigned char *p, size_t len) { struct stream_frame stream_frame; @@ -4271,14 +4304,17 @@ process_crypto_frame_server (struct ietf_full_conn *conn, parsed_len = conn->ifc_conn.cn_pf->pf_parse_crypto_frame(p, len, &stream_frame); if (parsed_len > 0) + { + LSQ_DEBUG("discard %d-byte CRYPTO frame", parsed_len); return (unsigned) parsed_len; + } else return 0; } static unsigned -process_crypto_frame_client (struct ietf_full_conn *conn, +process_crypto_frame (struct ietf_full_conn *conn, struct lsquic_packet_in *packet_in, const unsigned char *p, size_t len) { struct stream_frame *stream_frame; @@ -4286,6 +4322,12 @@ process_crypto_frame_client (struct ietf_full_conn *conn, enum enc_level enc_level; int parsed_len; + /* Ignore CRYPTO frames in server mode and in client mode after SSL object + * is gone. + */ + if (!(conn->ifc_flags & IFC_PROC_CRYPTO)) + return discard_crypto_frame(conn, packet_in, p, len); + stream_frame = lsquic_malo_get(conn->ifc_pub.mm->malo.stream_frame); if (!stream_frame) { @@ -4317,8 +4359,9 @@ process_crypto_frame_client (struct ietf_full_conn *conn, return parsed_len; } - if (conn->ifc_crypto_streams[enc_level]) - stream = conn->ifc_crypto_streams[enc_level]; + assert(!(conn->ifc_flags & IFC_SERVER)); + if (conn->ifc_u.cli.crypto_streams[enc_level]) + stream = conn->ifc_u.cli.crypto_streams[enc_level]; else { stream = lsquic_stream_new_crypto(enc_level, &conn->ifc_pub, @@ -4330,7 +4373,7 @@ process_crypto_frame_client (struct ietf_full_conn *conn, ABORT_WARN("cannot create crypto stream for level %u", enc_level); return 0; } - conn->ifc_crypto_streams[enc_level] = stream; + conn->ifc_u.cli.crypto_streams[enc_level] = stream; (void) lsquic_stream_wantread(stream, 1); } @@ -4357,17 +4400,6 @@ process_crypto_frame_client (struct ietf_full_conn *conn, } -static unsigned -process_crypto_frame (struct ietf_full_conn *conn, - struct lsquic_packet_in *packet_in, const unsigned char *p, size_t len) -{ - if (conn->ifc_flags & IFC_SERVER) - return process_crypto_frame_server(conn, packet_in, p, len); - else - return process_crypto_frame_client(conn, packet_in, p, len); -} - - static unsigned process_stream_frame (struct ietf_full_conn *conn, struct lsquic_packet_in *packet_in, const unsigned char *p, size_t len) @@ -5419,11 +5451,12 @@ ignore_init (struct ietf_full_conn *conn) lsquic_alarmset_unset(&conn->ifc_alset, AL_ACK_INIT + PNS_INIT); lsquic_send_ctl_empty_pns(&conn->ifc_send_ctl, PNS_INIT); lsquic_rechist_cleanup(&conn->ifc_rechist[PNS_INIT]); - if (conn->ifc_crypto_streams[ENC_LEV_CLEAR]) - { - lsquic_stream_destroy(conn->ifc_crypto_streams[ENC_LEV_CLEAR]); - conn->ifc_crypto_streams[ENC_LEV_CLEAR] = NULL; - } + if (!(conn->ifc_flags & IFC_SERVER)) + if (conn->ifc_u.cli.crypto_streams[ENC_LEV_CLEAR]) + { + lsquic_stream_destroy(conn->ifc_u.cli.crypto_streams[ENC_LEV_CLEAR]); + conn->ifc_u.cli.crypto_streams[ENC_LEV_CLEAR] = NULL; + } } @@ -5436,11 +5469,12 @@ ignore_hsk (struct ietf_full_conn *conn) lsquic_alarmset_unset(&conn->ifc_alset, AL_ACK_HSK); lsquic_send_ctl_empty_pns(&conn->ifc_send_ctl, PNS_HSK); lsquic_rechist_cleanup(&conn->ifc_rechist[PNS_HSK]); - if (conn->ifc_crypto_streams[ENC_LEV_INIT]) - { - lsquic_stream_destroy(conn->ifc_crypto_streams[ENC_LEV_INIT]); - conn->ifc_crypto_streams[ENC_LEV_INIT] = NULL; - } + if (!(conn->ifc_flags & IFC_SERVER)) + if (conn->ifc_u.cli.crypto_streams[ENC_LEV_INIT]) + { + lsquic_stream_destroy(conn->ifc_u.cli.crypto_streams[ENC_LEV_INIT]); + conn->ifc_u.cli.crypto_streams[ENC_LEV_INIT] = NULL; + } } @@ -6385,6 +6419,14 @@ ietf_full_conn_ci_record_addrs (struct lsquic_conn *lconn, void *peer_ctx, } +static void +ietf_full_conn_ci_drop_crypto_streams (struct lsquic_conn *lconn) +{ + struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn; + drop_crypto_streams(conn); +} + + static const struct conn_iface ietf_full_conn_iface = { .ci_abort = ietf_full_conn_ci_abort, .ci_abort_error = ietf_full_conn_ci_abort_error, @@ -6395,6 +6437,7 @@ static const struct conn_iface ietf_full_conn_iface = { .ci_close = ietf_full_conn_ci_close, .ci_destroy = ietf_full_conn_ci_destroy, .ci_drain_time = ietf_full_conn_ci_drain_time, + .ci_drop_crypto_streams = ietf_full_conn_ci_drop_crypto_streams, .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, @@ -6763,10 +6806,7 @@ apply_uni_stream_class (struct ietf_full_conn *conn, maybe_schedule_ss_for_stream(conn, stream->id, HEC_REQUEST_CANCELLED); } - if (stream->sm_hash_el.qhe_flags & QHE_HASHED) - lsquic_hash_erase(conn->ifc_pub.all_streams, &stream->sm_hash_el); - assert((stream->sm_qflags & SMQF_WANT_READ) == SMQF_WANT_READ); - lsquic_stream_destroy(stream); + lsquic_stream_close(stream); break; default: LSQ_DEBUG("unknown unidirectional stream %"PRIu64 " of type %"PRIu64 @@ -6779,10 +6819,7 @@ apply_uni_stream_class (struct ietf_full_conn *conn, */ maybe_schedule_ss_for_stream(conn, stream->id, HEC_STREAM_CREATION_ERROR); - if (stream->sm_hash_el.qhe_flags & QHE_HASHED) - lsquic_hash_erase(conn->ifc_pub.all_streams, &stream->sm_hash_el); - assert((stream->sm_qflags & SMQF_WANT_READ) == SMQF_WANT_READ); - lsquic_stream_destroy(stream); + lsquic_stream_close(stream); break; } } diff --git a/src/liblsquic/lsquic_stream.c b/src/liblsquic/lsquic_stream.c index deb5c147a..253eae030 100644 --- a/src/liblsquic/lsquic_stream.c +++ b/src/liblsquic/lsquic_stream.c @@ -113,7 +113,10 @@ static enum swtp_status stream_write_to_packet_crypto (struct frame_gen_ctx *fg_ctx, const size_t size); static size_t -stream_write_avail (struct lsquic_stream *); +stream_write_avail_no_frames (struct lsquic_stream *); + +static size_t +stream_write_avail_with_frames (struct lsquic_stream *); static size_t stream_write_avail_with_headers (struct lsquic_stream *); @@ -151,6 +154,9 @@ on_write_pp_wrapper (struct lsquic_stream *, lsquic_stream_ctx_t *); static void stream_hq_frame_put (struct lsquic_stream *, struct stream_hq_frame *); +static size_t +stream_hq_frame_size (const struct stream_hq_frame *); + const struct stream_filter_if hq_stream_filter_if = { .sfi_readable = hq_filter_readable, @@ -352,7 +358,7 @@ stream_new_common (lsquic_stream_id_t id, struct lsquic_conn_public *conn_pub, stream->stream_if = stream_if; stream->conn_pub = conn_pub; stream->sm_onnew_arg = stream_if_ctx; - stream->sm_write_avail = stream_write_avail; + stream->sm_write_avail = stream_write_avail_no_frames; STAILQ_INIT(&stream->sm_hq_frames); @@ -606,20 +612,26 @@ stream_is_finished (const lsquic_stream_t *stream) } +/* This is an internal function */ +void +lsquic_stream_force_finish (struct lsquic_stream *stream) +{ + LSQ_DEBUG("stream is now finished"); + SM_HISTORY_APPEND(stream, SHE_FINISHED); + if (0 == (stream->sm_qflags & SMQF_SERVICE_FLAGS)) + TAILQ_INSERT_TAIL(&stream->conn_pub->service_streams, stream, + next_service_stream); + stream->sm_qflags |= SMQF_FREE_STREAM; + stream->stream_flags |= STREAM_FINISHED; +} + + static void maybe_finish_stream (lsquic_stream_t *stream) { if (0 == (stream->stream_flags & STREAM_FINISHED) && stream_is_finished(stream)) - { - LSQ_DEBUG("stream is now finished"); - SM_HISTORY_APPEND(stream, SHE_FINISHED); - if (0 == (stream->sm_qflags & SMQF_SERVICE_FLAGS)) - TAILQ_INSERT_TAIL(&stream->conn_pub->service_streams, stream, - next_service_stream); - stream->sm_qflags |= SMQF_FREE_STREAM; - stream->stream_flags |= STREAM_FINISHED; - } + lsquic_stream_force_finish(stream); } @@ -718,10 +730,9 @@ lsquic_stream_readable (struct lsquic_stream *stream) static size_t -stream_write_avail (struct lsquic_stream *stream) +stream_write_avail_no_frames (struct lsquic_stream *stream) { uint64_t stream_avail, conn_avail; - size_t hq_frames_sz; stream_avail = stream->max_send_off - stream->tosend_off - stream->sm_n_buffered; @@ -733,20 +744,47 @@ stream_write_avail (struct lsquic_stream *stream) stream_avail = conn_avail; } - if ((stream->sm_bflags & (SMBF_IETF|SMBF_USE_HEADERS)) - == (SMBF_IETF|SMBF_USE_HEADERS)) - { - hq_frames_sz = active_hq_frame_sizes(stream); - if (hq_frames_sz == 0) - hq_frames_sz = 3; /* Smallest new frame */ + return stream_avail; +} - if (stream_avail > hq_frames_sz) - stream_avail -= hq_frames_sz; - else - stream_avail = 0; + +static size_t +stream_write_avail_with_frames (struct lsquic_stream *stream) +{ + uint64_t stream_avail, conn_avail; + const struct stream_hq_frame *shf; + size_t size; + + stream_avail = stream->max_send_off - stream->tosend_off + - stream->sm_n_buffered; + STAILQ_FOREACH(shf, &stream->sm_hq_frames, shf_next) + if (!(shf->shf_flags & SHF_WRITTEN)) + { + size = stream_hq_frame_size(shf); + assert(size <= stream_avail); + stream_avail -= size; + } + + if (stream->sm_bflags & SMBF_CONN_LIMITED) + { + conn_avail = lsquic_conn_cap_avail(&stream->conn_pub->conn_cap); + STAILQ_FOREACH(shf, &stream->sm_hq_frames, shf_next) + if (!(shf->shf_flags & SHF_CC_PAID)) + { + size = stream_hq_frame_size(shf); + if (size < conn_avail) + conn_avail -= size; + else + return 0; + } + if (conn_avail < stream_avail) + stream_avail = conn_avail; } - return stream_avail; + if (stream_avail >= 3 /* Smallest new frame */) + return stream_avail; + else + return 0; } @@ -772,7 +810,7 @@ static size_t stream_write_avail_with_headers (struct lsquic_stream *stream) { if (stream->stream_flags & STREAM_PUSHING) - return stream_write_avail(stream); + return stream_write_avail_with_frames(stream); switch (stream->sm_send_headers_state) { @@ -787,7 +825,7 @@ stream_write_avail_with_headers (struct lsquic_stream *stream) /* fall-through */ default: assert(SSHS_HBLOCK_SENDING == stream->sm_send_headers_state); - return stream_write_avail(stream); + return stream_write_avail_with_frames(stream); } } @@ -1432,6 +1470,24 @@ stream_shutdown_read (lsquic_stream_t *stream) } +static int +stream_is_incoming_unidir (const struct lsquic_stream *stream) +{ + enum stream_id_type sit; + + if (stream->sm_bflags & SMBF_IETF) + { + sit = stream->id & SIT_MASK; + if (stream->sm_bflags & SMBF_SERVER) + return sit == SIT_UNI_CLIENT; + else + return sit == SIT_UNI_SERVER; + } + else + return 0; +} + + static void stream_shutdown_write (lsquic_stream_t *stream) { @@ -1447,6 +1503,7 @@ stream_shutdown_write (lsquic_stream_t *stream) */ if (!(stream->sm_bflags & SMBF_CRYPTO) && !(stream->stream_flags & (STREAM_FIN_SENT|STREAM_RST_SENT)) + && !stream_is_incoming_unidir(stream) && !(stream->sm_qflags & SMQF_SEND_RST)) { if (stream->sm_n_buffered == 0) @@ -1544,7 +1601,7 @@ fake_reset_unused_stream (lsquic_stream_t *stream) TAILQ_REMOVE(&stream->conn_pub->sending_streams, stream, next_send_stream); stream->sm_qflags &= ~SMQF_SENDING_FLAGS; - + drop_buffered_data(stream); LSQ_DEBUG("fake-reset stream%s", stream_stalled(stream) ? " (stalled)" : ""); maybe_finish_stream(stream); @@ -1771,7 +1828,7 @@ stream_hblock_sent (struct lsquic_stream *stream) LSQ_DEBUG("header block has been sent: restore default behavior"); stream->sm_send_headers_state = SSHS_BEGIN; - stream->sm_write_avail = stream_write_avail; + stream->sm_write_avail = stream_write_avail_with_frames; want_write = !!(stream->sm_qflags & SMQF_WANT_WRITE); if (want_write != stream->sm_saved_want_write) @@ -2944,7 +3001,7 @@ update_buffered_hq_frames (struct lsquic_stream *stream, size_t len, struct stream_hq_frame *shf; uint64_t cur_off, end; size_t frame_sz; - int extendable; + unsigned extendable; cur_off = stream->sm_payload + stream->sm_n_buffered; STAILQ_FOREACH(shf, &stream->sm_hq_frames, shf_next) @@ -2958,12 +3015,13 @@ update_buffered_hq_frames (struct lsquic_stream *stream, size_t len, if (shf) { - if (len > end - cur_off) - len = end - cur_off; + if (len > end + extendable - cur_off) + len = end + extendable - cur_off; frame_sz = stream_hq_frame_size(shf); } - else if (avail >= 3) + else { + assert(avail >= 3); shf = stream_activate_hq_frame(stream, cur_off, HQFT_DATA, 0, len); /* XXX malloc can fail */ if (len > stream_hq_frame_end(shf) - cur_off) @@ -2974,8 +3032,6 @@ update_buffered_hq_frames (struct lsquic_stream *stream, size_t len, return 0; avail -= frame_sz; } - else - return 0; if (!(shf->shf_flags & SHF_CC_PAID)) { @@ -3006,6 +3062,15 @@ save_to_buffer (lsquic_stream_t *stream, struct lsquic_reader *reader, { size_t avail, n_written, n_allowed; + avail = lsquic_stream_write_avail(stream); + if (avail < len) + len = avail; + if (len == 0) + { + LSQ_DEBUG("zero-byte write (avail: %zu)", avail); + return 0; + } + n_allowed = stream_get_n_allowed(stream); assert(stream->sm_n_buffered + len <= n_allowed); @@ -3017,10 +3082,6 @@ save_to_buffer (lsquic_stream_t *stream, struct lsquic_reader *reader, stream->sm_n_allocated = n_allowed; } - avail = lsquic_stream_write_avail(stream); - if (avail < len) - len = avail; - if ((stream->sm_bflags & (SMBF_IETF|SMBF_USE_HEADERS)) == (SMBF_IETF|SMBF_USE_HEADERS)) len = update_buffered_hq_frames(stream, len, avail); diff --git a/src/liblsquic/lsquic_stream.h b/src/liblsquic/lsquic_stream.h index 54ac69e7a..935c28674 100644 --- a/src/liblsquic/lsquic_stream.h +++ b/src/liblsquic/lsquic_stream.h @@ -584,4 +584,7 @@ lsquic_stream_duplicate_push (struct lsquic_stream *, uint64_t push_id); int lsquic_stream_push_promise (struct lsquic_stream *, struct push_promise *); +void +lsquic_stream_force_finish (struct lsquic_stream *); + #endif diff --git a/test/unittests/test_h3_framing.c b/test/unittests/test_h3_framing.c index 0205d398e..797bcee0a 100644 --- a/test/unittests/test_h3_framing.c +++ b/test/unittests/test_h3_framing.c @@ -393,19 +393,31 @@ packetization_write_as_much_as_you_can (lsquic_stream_t *stream, lsquic_stream_ctx_t *ctx) { struct packetization_test_stream_ctx *const pack_ctx = (void *) ctx; - unsigned n_to_write; + unsigned n_to_write, n_sched; ssize_t n_written; + size_t avail; int s; while (pack_ctx->off < pack_ctx->len) { n_to_write = calc_n_to_write(pack_ctx->write_size); + n_sched = lsquic_send_ctl_n_scheduled(stream->conn_pub->send_ctl); if (n_to_write > pack_ctx->len - pack_ctx->off) n_to_write = pack_ctx->len - pack_ctx->off; n_written = lsquic_stream_write(stream, pack_ctx->buf + pack_ctx->off, n_to_write); if (n_written == 0) + { + if (n_to_write && SSHS_BEGIN == stream->sm_send_headers_state + && lsquic_send_ctl_can_send(stream->conn_pub->send_ctl)) + { + avail = lsquic_stream_write_avail(stream); + assert(avail == 0 + || lsquic_send_ctl_n_scheduled( + stream->conn_pub->send_ctl) > n_sched); + } break; + } pack_ctx->off += n_written; if (pack_ctx->flush_after_each_write) { @@ -425,16 +437,27 @@ packetization_perform_one_write (lsquic_stream_t *stream, lsquic_stream_ctx_t *ctx) { struct packetization_test_stream_ctx *const pack_ctx = (void *) ctx; - unsigned n_to_write; + unsigned n_to_write, n_sched; ssize_t n_written; + size_t avail; int s; n_to_write = calc_n_to_write(pack_ctx->write_size); if (n_to_write > pack_ctx->len - pack_ctx->off) n_to_write = pack_ctx->len - pack_ctx->off; + n_sched = lsquic_send_ctl_n_scheduled(stream->conn_pub->send_ctl); n_written = lsquic_stream_write(stream, pack_ctx->buf + pack_ctx->off, n_to_write); assert(n_written >= 0); + if (n_written == 0 && SSHS_BEGIN == stream->sm_send_headers_state + && n_to_write + && lsquic_send_ctl_can_send(stream->conn_pub->send_ctl)) + { + avail = lsquic_stream_write_avail(stream); + assert(avail == 0 + || lsquic_send_ctl_n_scheduled( + stream->conn_pub->send_ctl) > n_sched); + } pack_ctx->off += n_written; if (pack_ctx->flush_after_each_write) {