Release 2.30.0

- [FEATURE] Added support for sending/receiving multiple headers to address the
  case related to "100 continue" header handling.
- [BUGFIX] Addressed high CPU usage for a GOAWAY connection before sending
  CONNECTION_CLOSE.
- [BUGFIX] Addressed SIGFPE due to zero pacing rate. (ISSUE #254).
- [BUGFIX] Fixed a minor issue related to multi-paths.
This commit is contained in:
George Wang 2021-04-12 09:52:42 -04:00
parent ab69788e51
commit 293df8d66b
16 changed files with 815 additions and 125 deletions

View file

@ -15,7 +15,7 @@ struct adaptive_cc
struct lsquic_cubic acc_cubic;
struct lsquic_bbr acc_bbr;
enum {
ACC_CUBIC, /* If set, use Cubic; otherwise, use BBR */
ACC_CUBIC = 1, /* If set, use Cubic; otherwise, use BBR */
} acc_flags;
};

View file

@ -45,6 +45,8 @@ 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_PATH_CHAL_2] = "PATH_CHAL_2",
[AL_PATH_CHAL_3] = "PATH_CHAL_3",
[AL_SESS_TICKET] = "SESS_TICKET",
[AL_BLOCKED_KA] = "BLOCKED_KA",
[AL_MTU_PROBE] = "MTU_PROBE",

View file

@ -704,6 +704,7 @@ wipe_path (struct ietf_full_conn *conn, unsigned path_id)
memset(&conn->ifc_paths[path_id], 0, sizeof(conn->ifc_paths[0]));
conn->ifc_paths[path_id].cop_path.np_path_id = path_id;
conn->ifc_paths[path_id].cop_path.np_peer_ctx = peer_ctx;
conn->ifc_used_paths &= ~(1 << path_id);
}
@ -2755,6 +2756,20 @@ have_bidi_streams (const struct ietf_full_conn *conn)
}
static int
conn_ok_to_close (const struct ietf_full_conn *conn)
{
assert(conn->ifc_flags & IFC_CLOSING);
return !(conn->ifc_flags & IFC_SERVER)
|| (conn->ifc_flags & IFC_RECV_CLOSE)
|| (
!lsquic_send_ctl_have_outgoing_stream_frames(&conn->ifc_send_ctl)
&& !have_bidi_streams(conn)
&& lsquic_send_ctl_have_unacked_stream_frames(
&conn->ifc_send_ctl) == 0);
}
static void
maybe_close_conn (struct ietf_full_conn *conn)
{
@ -2763,8 +2778,13 @@ maybe_close_conn (struct ietf_full_conn *conn)
&& !have_bidi_streams(conn))
{
conn->ifc_flags |= IFC_CLOSING|IFC_GOAWAY_CLOSE;
conn->ifc_send_flags |= SF_SEND_CONN_CLOSE;
LSQ_DEBUG("closing connection: GOAWAY sent and no responses remain");
LSQ_DEBUG("maybe_close_conn: GOAWAY sent and no responses remain");
if (conn_ok_to_close(conn))
{
conn->ifc_send_flags |= SF_SEND_CONN_CLOSE;
LSQ_DEBUG("maybe_close_conn: ok to close: "
"schedule to send CONNECTION_CLOSE");
}
}
}
@ -2920,7 +2940,12 @@ ietf_full_conn_ci_close (struct lsquic_conn *lconn)
lsquic_stream_maybe_reset(stream, 0, 1);
}
conn->ifc_flags |= IFC_CLOSING;
conn->ifc_send_flags |= SF_SEND_CONN_CLOSE;
if (conn_ok_to_close(conn))
{
conn->ifc_send_flags |= SF_SEND_CONN_CLOSE;
LSQ_DEBUG("ietf_full_conn_ci_close: ok to close: "
"schedule to send CONNECTION_CLOSE");
}
lsquic_engine_add_conn_to_tickable(conn->ifc_enpub, lconn);
}
}
@ -3204,7 +3229,8 @@ ietf_full_conn_ci_going_away (struct lsquic_conn *lconn)
{
if (!(conn->ifc_flags & (IFC_CLOSING|IFC_GOING_AWAY)))
{
LSQ_INFO("connection marked as going away");
LSQ_INFO("connection marked as going away, last stream: %ld",
conn->ifc_max_req_id);
conn->ifc_flags |= IFC_GOING_AWAY;
const lsquic_stream_id_t stream_id = conn->ifc_max_req_id + N_SITS;
if (valid_stream_id(stream_id))
@ -4340,20 +4366,6 @@ process_streams_write_events (struct ietf_full_conn *conn, int high_prio)
}
static int
conn_ok_to_close (const struct ietf_full_conn *conn)
{
assert(conn->ifc_flags & IFC_CLOSING);
return !(conn->ifc_flags & IFC_SERVER)
|| (conn->ifc_flags & IFC_RECV_CLOSE)
|| (
!lsquic_send_ctl_have_outgoing_stream_frames(&conn->ifc_send_ctl)
&& !have_bidi_streams(conn)
&& lsquic_send_ctl_have_unacked_stream_frames(
&conn->ifc_send_ctl) == 0);
}
static void
generate_connection_close_packet (struct ietf_full_conn *conn)
{
@ -8325,7 +8337,9 @@ ietf_full_conn_ci_tick (struct lsquic_conn *lconn, lsquic_time_t now)
lsquic_send_ctl_maybe_app_limited(&conn->ifc_send_ctl, CUR_NPATH(conn));
end_write:
if ((conn->ifc_flags & IFC_CLOSING) && conn_ok_to_close(conn))
if ((conn->ifc_flags & IFC_CLOSING)
&& ((conn->ifc_send_flags & SF_SEND_CONN_CLOSE)
|| conn_ok_to_close(conn)))
{
LSQ_DEBUG("connection is OK to close");
conn->ifc_flags |= IFC_TICK_CLOSE;

View file

@ -133,6 +133,7 @@ typedef struct hs_ctx_st
HSET_SCID = (1 << 2),
HSET_IRTT = (1 << 3),
HSET_SRST = (1 << 4),
HSET_XLCT = (1 << 5), /* xlct is set */
} set;
enum {
HOPT_NSTP = (1 << 0), /* NSTP option present in COPT */
@ -283,6 +284,7 @@ struct lsquic_enc_session
SSL_CTX * ssl_ctx;
struct lsquic_engine_public *enpub;
struct lsquic_str * cert_ptr; /* pointer to the leaf cert of the server, not real copy */
uint64_t cert_hash;
struct lsquic_str chlo; /* real copy of CHLO message */
struct lsquic_str sstk;
struct lsquic_str ssno;
@ -310,6 +312,7 @@ typedef struct compress_cert_hash_item_st
typedef struct cert_item_st
{
struct lsquic_str* crt;
uint64_t hash; /* Hash of `crt' */
struct lsquic_hash_elem hash_el;
unsigned char key[0];
} cert_item_t;
@ -535,6 +538,8 @@ insert_cert (struct lsquic_engine_public *enpub, const unsigned char *key,
item->crt = crt_copy;
memcpy(item->key, key, key_sz);
item->hash = lsquic_fnv1a_64((const uint8_t *)lsquic_str_buf(crt),
lsquic_str_len(crt));
el = lsquic_hash_insert(enpub->enp_server_certs, item->key, key_sz,
item, &item->hash_el);
if (el)
@ -1267,6 +1272,13 @@ static int parse_hs_data (struct lsquic_enc_session *enc_session, uint32_t tag,
break;
case QTAG_XLCT:
if (len != sizeof(hs_ctx->xlct))
{
LSQ_INFO("Unexpected size of XLCT: %u instead of %zu bytes",
len, sizeof(hs_ctx->xlct));
return -1;
}
hs_ctx->set |= HSET_XLCT;
hs_ctx->xlct = get_tag_value_i64(val, len);
break;
@ -1664,7 +1676,6 @@ determine_rtts (struct lsquic_enc_session *enc_session,
{
hs_ctx_t *const hs_ctx = &enc_session->hs_ctx;
enum hsk_failure_reason hfr;
uint64_t hash = 0;
if (!(hs_ctx->set & HSET_SCID))
{
@ -1701,12 +1712,9 @@ determine_rtts (struct lsquic_enc_session *enc_session,
goto fail_1rtt;
}
if (hs_ctx->xlct)
if (hs_ctx->set & HSET_XLCT)
{
hash = lsquic_fnv1a_64((const uint8_t *)lsquic_str_buf(enc_session->cert_ptr),
lsquic_str_len(enc_session->cert_ptr));
if (hash != hs_ctx->xlct)
if (enc_session->cert_hash != hs_ctx->xlct)
{
/* The expected leaf certificate hash could not be validated. */
hs_ctx->rrej = HFR_INVALID_EXPECTED_LEAF_CERTIFICATE;
@ -2297,6 +2305,7 @@ get_sni_SSL_CTX(struct lsquic_enc_session *enc_session, lsquic_lookup_cert_f cb,
}
}
enc_session->cert_ptr = item->crt;
enc_session->cert_hash = item->hash;
}
else
{
@ -2310,6 +2319,9 @@ get_sni_SSL_CTX(struct lsquic_enc_session *enc_session, lsquic_lookup_cert_f cb,
if (!enc_session->cert_ptr)
return GET_SNI_ERR;
enc_session->es_flags |= ES_FREE_CERT_PTR;
enc_session->cert_hash = lsquic_fnv1a_64(
(const uint8_t *) lsquic_str_buf(enc_session->cert_ptr),
lsquic_str_len(enc_session->cert_ptr));
}
}
return GET_SNI_OK;

View file

@ -36,6 +36,8 @@ struct uncompressed_headers
UH_H1H = (1 << 2), /* uh_hset points to http1x_headers */
} uh_flags:8;
void *uh_hset;
struct uncompressed_headers
*uh_next;
};
#endif

View file

@ -36,7 +36,6 @@ struct lsquic_mm {
struct malo *frame_rec_arr; /* For struct frame_rec_arr */
struct malo *mini_conn; /* For struct mini_conn */
struct malo *mini_conn_ietf;/* For struct ietf_mini_conn */
struct malo *retry_conn; /* For struct retry_conn */
struct malo *packet_in; /* For struct lsquic_packet_in */
struct malo *packet_out; /* For struct lsquic_packet_out */
struct malo *dcid_elem; /* For struct dcid_elem */

View file

@ -641,7 +641,7 @@ qdh_header_read_results (struct qpack_dec_hdl *qdh,
if (rhs == LQRHS_DONE)
{
if (!lsquic_stream_header_is_trailer(stream))
if (1) //!lsquic_stream_header_is_trailer(stream))
{
if (stream->sm_hblock_ctx->ctx.ppc_flags
& (PPC_INC_SET|PPC_URG_SET))

View file

@ -546,6 +546,8 @@ send_ctl_transfer_time (void *ctx)
in_recovery = send_ctl_in_recovery(ctl);
pacing_rate = ctl->sc_ci->cci_pacing_rate(CGP(ctl), in_recovery);
if (!pacing_rate)
pacing_rate = 1;
tx_time = (uint64_t) SC_PACK_SIZE(ctl) * 1000000 / pacing_rate;
return tx_time;
}
@ -3788,6 +3790,8 @@ lsquic_send_ctl_can_send_probe (const struct lsquic_send_ctl *ctl,
if (n_out + path->np_pack_size >= cwnd)
return 0;
pacing_rate = ctl->sc_ci->cci_pacing_rate(CGP(ctl), 0);
if (!pacing_rate)
pacing_rate = 1;
tx_time = (uint64_t) path->np_pack_size * 1000000 / pacing_rate;
return lsquic_pacer_can_schedule_probe(&ctl->sc_pacer,
ctl->sc_n_scheduled + ctl->sc_n_in_flight_all, tx_time);

View file

@ -623,15 +623,13 @@ lsquic_stream_drop_hset_ref (struct lsquic_stream *stream)
static void
destroy_uh (struct lsquic_stream *stream)
destroy_uh (struct uncompressed_headers *uh, const struct lsquic_hset_if *hsi_if)
{
if (stream->uh)
if (uh)
{
if (stream->uh->uh_hset)
stream->conn_pub->enpub->enp_hsi_if
->hsi_discard_header_set(stream->uh->uh_hset);
free(stream->uh);
stream->uh = NULL;
if (uh->uh_hset)
hsi_if->hsi_discard_header_set(uh->uh_hset);
free(uh);
}
}
@ -641,6 +639,7 @@ lsquic_stream_destroy (lsquic_stream_t *stream)
{
struct push_promise *promise;
struct stream_hq_frame *shf;
struct uncompressed_headers *uh;
stream->stream_flags |= STREAM_U_WRITE_DONE|STREAM_U_READ_DONE;
if ((stream->stream_flags & (STREAM_ONNEW_DONE|STREAM_ONCLOSE_DONE)) ==
@ -687,7 +686,12 @@ lsquic_stream_destroy (lsquic_stream_t *stream)
}
while ((shf = STAILQ_FIRST(&stream->sm_hq_frames)))
stream_hq_frame_put(stream, shf);
destroy_uh(stream);
while(stream->uh)
{
uh = stream->uh;
stream->uh = uh->uh_next;
destroy_uh(uh, stream->conn_pub->enpub->enp_hsi_if);
}
free(stream->sm_buf);
free(stream->sm_header_block);
LSQ_DEBUG("destroyed stream");
@ -1443,7 +1447,8 @@ static size_t
read_uh (struct lsquic_stream *stream,
size_t (*readf)(void *, const unsigned char *, size_t, int), void *ctx)
{
struct http1x_headers *const h1h = stream->uh->uh_hset;
struct uncompressed_headers *uh = stream->uh;
struct http1x_headers *const h1h = uh->uh_hset;
size_t nread;
nread = readf(ctx, (unsigned char *) h1h->h1h_buf + h1h->h1h_off,
@ -1452,8 +1457,10 @@ read_uh (struct lsquic_stream *stream,
h1h->h1h_off += nread;
if (h1h->h1h_off == h1h->h1h_size)
{
LSQ_DEBUG("read all uncompressed headers");
destroy_uh(stream);
stream->uh = uh->uh_next;
LSQ_DEBUG("read all uncompressed headers from uh: %p, next uh: %p",
uh, stream->uh);
destroy_uh(uh, stream->conn_pub->enpub->enp_hsi_if);
if (stream->stream_flags & STREAM_HEAD_IN_FIN)
{
stream->stream_flags |= STREAM_FIN_REACHED;
@ -4186,7 +4193,7 @@ lsquic_stream_send_headers (lsquic_stream_t *stream,
const lsquic_http_headers_t *headers, int eos)
{
if ((stream->sm_bflags & SMBF_USE_HEADERS)
&& !(stream->stream_flags & (STREAM_HEADERS_SENT|STREAM_U_WRITE_DONE)))
&& !(stream->stream_flags & (STREAM_U_WRITE_DONE)))
{
if (stream->sm_bflags & SMBF_IETF)
return send_headers_ietf(stream, headers, eos);
@ -4411,15 +4418,19 @@ static int
stream_uh_in_gquic (struct lsquic_stream *stream,
struct uncompressed_headers *uh)
{
if ((stream->sm_bflags & SMBF_USE_HEADERS)
&& !(stream->stream_flags & STREAM_HAVE_UH))
struct uncompressed_headers **next;
if ((stream->sm_bflags & SMBF_USE_HEADERS))
{
SM_HISTORY_APPEND(stream, SHE_HEADERS_IN);
LSQ_DEBUG("received uncompressed headers");
stream->stream_flags |= STREAM_HAVE_UH;
if (uh->uh_flags & UH_FIN)
stream->stream_flags |= STREAM_FIN_RECVD|STREAM_HEAD_IN_FIN;
stream->uh = uh;
next = &stream->uh;
while(*next)
next = &(*next)->uh_next;
*next = uh;
assert(uh->uh_next == NULL);
if (uh->uh_oth_stream_id == 0)
{
if (uh->uh_weight)
@ -4443,9 +4454,10 @@ stream_uh_in_ietf (struct lsquic_stream *stream,
struct uncompressed_headers *uh)
{
int push_promise;
struct uncompressed_headers **next;
push_promise = lsquic_stream_header_is_pp(stream);
if (!(stream->stream_flags & STREAM_HAVE_UH) && !push_promise)
if (!push_promise)
{
SM_HISTORY_APPEND(stream, SHE_HEADERS_IN);
LSQ_DEBUG("received uncompressed headers");
@ -4460,7 +4472,11 @@ stream_uh_in_ietf (struct lsquic_stream *stream,
&& lsquic_stream_is_pushed(stream));
stream->stream_flags |= STREAM_FIN_RECVD|STREAM_HEAD_IN_FIN;
}
stream->uh = uh;
next = &stream->uh;
while(*next)
next = &(*next)->uh_next;
*next = uh;
assert(uh->uh_next == NULL);
if (uh->uh_oth_stream_id == 0)
{
if (uh->uh_weight)
@ -4652,6 +4668,7 @@ void *
lsquic_stream_get_hset (struct lsquic_stream *stream)
{
void *hset;
struct uncompressed_headers *uh;
if (stream_is_read_reset(stream))
{
@ -4676,9 +4693,14 @@ lsquic_stream_get_hset (struct lsquic_stream *stream)
hset = stream->uh->uh_hset;
stream->uh->uh_hset = NULL;
destroy_uh(stream);
uh = stream->uh;
stream->uh = uh->uh_next;
free(uh);
if (stream->stream_flags & STREAM_HEAD_IN_FIN)
{
stream->stream_flags |= STREAM_FIN_REACHED;
SM_HISTORY_APPEND(stream, SHE_REACH_FIN);
}
@ -4706,31 +4728,21 @@ static int
update_type_hist_and_check (const struct lsquic_stream *stream,
struct hq_filter *filter)
{
/* 3-bit codes: */
enum {
CODE_UNSET,
CODE_HEADER, /* H Header */
CODE_DATA, /* D Data */
CODE_PLUS, /* + Plus: meaning previous frame repeats */
};
static const unsigned valid_seqs[] = {
/* Ordered by expected frequency */
0123, /* HD+ */
012, /* HD */
01, /* H */
013, /* H+ */ /* Really HH, but we don't record it like this */
01231, /* HD+H */
0121, /* HDH */
};
unsigned code, i;
switch (filter->hqfi_type)
{
case HQFT_HEADERS:
code = CODE_HEADER;
if (filter->hqfi_flags & HQFI_FLAG_TRAILER)
return -1;
if (filter->hqfi_flags & HQFI_FLAG_DATA)
filter->hqfi_flags |= HQFI_FLAG_TRAILER;
else
filter->hqfi_flags |= HQFI_FLAG_HEADER;
break;
case HQFT_DATA:
code = CODE_DATA;
if ((filter->hqfi_flags & (HQFI_FLAG_HEADER
| HQFI_FLAG_TRAILER)) != HQFI_FLAG_HEADER)
return -1;
filter->hqfi_flags |= HQFI_FLAG_DATA;
break;
case HQFT_PUSH_PROMISE:
/* [draft-ietf-quic-http-24], Section 7 */
@ -4768,31 +4780,7 @@ update_type_hist_and_check (const struct lsquic_stream *stream,
return 0;
}
if (filter->hqfi_hist_idx >= MAX_HQFI_ENTRIES)
return -1;
if (filter->hqfi_hist_idx && (filter->hqfi_hist_buf & 7) == code)
{
filter->hqfi_hist_buf <<= 3;
filter->hqfi_hist_buf |= CODE_PLUS;
filter->hqfi_hist_idx++;
}
else if (filter->hqfi_hist_idx > 1
&& ((filter->hqfi_hist_buf >> 3) & 7) == code
&& (filter->hqfi_hist_buf & 7) == CODE_PLUS)
/* Keep it at plus, do nothing */;
else
{
filter->hqfi_hist_buf <<= 3;
filter->hqfi_hist_buf |= code;
filter->hqfi_hist_idx++;
}
for (i = 0; i < sizeof(valid_seqs) / sizeof(valid_seqs[0]); ++i)
if (filter->hqfi_hist_buf == valid_seqs[i])
return 0;
return -1;
return 0;
}
@ -4860,8 +4848,7 @@ hq_read (void *ctx, const unsigned char *buf, size_t sz, int fin)
{
lconn = stream->conn_pub->lconn;
filter->hqfi_flags |= HQFI_FLAG_ERROR;
LSQ_INFO("unexpected HTTP/3 frame sequence: %o",
filter->hqfi_hist_buf);
LSQ_INFO("unexpected HTTP/3 frame sequence");
lconn->cn_if->ci_abort_error(lconn, 1, HEC_FRAME_UNEXPECTED,
"unexpected HTTP/3 frame sequence on stream %"PRIu64,
stream->id);

View file

@ -109,6 +109,9 @@ struct hq_filter
HQFI_FLAG_ERROR = 1 << 1,
HQFI_FLAG_BEGIN = 1 << 2,
HQFI_FLAG_BLOCKED = 1 << 3,
HQFI_FLAG_HEADER = 1 << 4,
HQFI_FLAG_DATA = 1 << 5,
HQFI_FLAG_TRAILER = 1 << 6,
} hqfi_flags:8;
enum {
HQFI_STATE_FRAME_HEADER_BEGIN,
@ -117,9 +120,6 @@ struct hq_filter
HQFI_STATE_PUSH_ID_BEGIN,
HQFI_STATE_PUSH_ID_CONTINUE,
} hqfi_state:8;
unsigned char hqfi_hist_idx;
#define MAX_HQFI_ENTRIES (sizeof(unsigned) * 8 / 3)
unsigned hqfi_hist_buf;
};