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.
This commit is contained in:
Dmitri Tikhonov 2021-02-03 11:05:50 -05:00
parent 9a7f663e1a
commit c2faf03244
19 changed files with 440 additions and 54 deletions

View file

@ -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);

View file

@ -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

View file

@ -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;
}

View file

@ -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;

View file

@ -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;

View file

@ -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

View file

@ -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++;

View file

@ -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",

View file

@ -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] =
{

View file

@ -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;