Release 2.4.4

- [API] Add lsquic_alpn2ver() to aid parsing Alt-Svc header.
- [BUGFIX] NULL dereference when H3 frame header would be split.
- [BUGFIX] Do not close fixed-size H3 frame prematurely.
- [BUGFIX] Allow PING frames in IETF mini conn.
- [BUGFIX] Mini conns: don't send any packets after receiving
  CONNECTION_CLOSE.
- [BUGFIX] Client migration: reserve slot for DCID from transport params.
- [BUGFIX] Allow max_early_data_size=0 -- early_data might not be there.
- [BUGFIX] Use an invalid stream number to reset BPT cache (zero is now a
  valid stream number).
- [SPEC] Use FINAL_SIZE_ERROR when FIN mismatch is detected.
- [OPTIMIZATION] Closed connection only gets one chance to send packets.
- [OPTIMIZATION] Flush headers stream before packetizing stream data.
- [OPTIMIZATION] process QPACK encoder STREAM frames immediately.
- Update ls-qpack to v0.10.1.
This commit is contained in:
Dmitri Tikhonov 2019-10-08 08:36:49 -04:00
parent 1245d2e023
commit 662de5e197
15 changed files with 388 additions and 57 deletions

View file

@ -37,6 +37,7 @@ print OUT <<C_CODE;
*/
#include <assert.h>
#include <string.h>
#include "lsquic.h"
@ -127,4 +128,37 @@ lsquic_get_h3_alpns (unsigned versions)
C_CODE
print OUT <<'C_CODE';
enum lsquic_version
lsquic_alpn2ver (const char *alpn, size_t len)
{
static const struct el {
size_t len;
char alpn[10];
enum lsquic_version version;
} map[] = {
C_CODE
for ($i = 0; $i < @versions; ++$i) {
print OUT " {sizeof(\"h3-Q0$versions[$i]\")-1,\"h3-Q0$versions[$i]\", $enums[$i]},\n";
}
for ($i = 0; $i < @draft_versions; ++$i) {
print OUT " {sizeof(\"h3-$draft_versions[$i]\")-1,\"h3-$draft_versions[$i]\", LSQVER_ID$draft_versions[$i]},\n";
}
print OUT <<'C_CODE';
};
const struct el *el;
if (alpn)
for (el = map; el < map + sizeof(map) / sizeof(map[0]); ++el)
if (el->len == len && 0 == strncmp(el->alpn, alpn, len))
return el->version;
return -1;
}
C_CODE
close OUT;

View file

@ -1154,8 +1154,13 @@ iquic_new_session_cb (SSL *ssl, SSL_SESSION *session)
assert(enc_sess->esi_enpub->enp_stream_if->on_zero_rtt_info);
max_early_data_size = SSL_SESSION_get_max_early_data_size(session);
if (0xFFFFFFFFu != max_early_data_size)
if (max_early_data_size && 0xFFFFFFFFu != max_early_data_size)
{
/* XXX We do not catch the case when early_data extension is present
* and max_early_data_size is set to zero, which is an invalid value.
* This is because there is no way to check this using existing
* BoringSSL APIs.
*/
/* See [draft-ietf-quic-tls-23], Section 4.5 */
LSQ_INFO("max_early_data_size=0x%X, protocol violation",
max_early_data_size);

View file

@ -1922,7 +1922,10 @@ coi_reheap (struct conns_out_iter *iter, lsquic_engine_t *engine)
{
TAILQ_REMOVE(&iter->coi_active_list, conn, cn_next_out);
conn->cn_flags &= ~LSCONN_COI_ACTIVE;
lsquic_mh_insert(iter->coi_heap, conn, conn->cn_last_sent);
if ((conn->cn_flags & CONN_REF_FLAGS) != LSCONN_HAS_OUTGOING)
lsquic_mh_insert(iter->coi_heap, conn, conn->cn_last_sent);
else /* Closed connection gets one shot at sending packets */
(void) engine_decref_conn(engine, conn, LSCONN_HAS_OUTGOING);
}
while ((conn = TAILQ_FIRST(&iter->coi_inactive_list)))
{

View file

@ -4507,6 +4507,12 @@ process_stream_frame (struct ietf_full_conn *conn,
return 0;
}
/* Don't wait for the regular on_read dispatch in order to save an
* unnecessary blocked/unblocked sequence.
*/
if ((conn->ifc_flags & IFC_HTTP) && conn->ifc_qdh.qdh_enc_sm_in == stream)
lsquic_stream_dispatch_read_events(conn->ifc_qdh.qdh_enc_sm_in);
return parsed_len;
}
@ -4809,6 +4815,30 @@ retire_dcids_prior_to (struct ietf_full_conn *conn, unsigned retire_prior_to)
}
/* We need to be able to allocate a DCE slot to begin migration or to retire
* the DCID in transport parameters.
*/
static int
must_reserve_one_dce_slot (struct ietf_full_conn *conn)
{
struct lsquic_conn *const lconn = &conn->ifc_conn;
const struct transport_params *params;
if (conn->ifc_flags & IFC_SERVER)
return 0;
if (lsquic_send_ctl_1rtt_acked(&conn->ifc_send_ctl))
return 0;
params = lconn->cn_esf.i->esfi_get_peer_transport_params(
lconn->cn_enc_session);
if (params) /* Just in case */
return !!(params->tp_flags & (TRAPA_PREFADDR_IPv4|TRAPA_PREFADDR_IPv6));
else
return 0;
}
static unsigned
process_new_connection_id_frame (struct ietf_full_conn *conn,
struct lsquic_packet_in *packet_in, const unsigned char *p, size_t len)
@ -4883,6 +4913,16 @@ process_new_connection_id_frame (struct ietf_full_conn *conn,
if (dce)
{
if (must_reserve_one_dce_slot(conn))
{
for (el = dce + 1; el < DCES_END(conn) && *el; ++el)
;
if (el == DCES_END(conn))
{
action_str = "Ignored (last slot reserved for migration)";
goto end;
}
}
*dce = lsquic_malo_get(conn->ifc_pub.mm->malo.dcid_elem);
if (*dce)
{
@ -4901,6 +4941,7 @@ process_new_connection_id_frame (struct ietf_full_conn *conn,
else
action_str = "Ignored (no slots available)";
end:
LSQ_DEBUGC("Got new connection ID from peer: seq=%"PRIu64"; "
"cid: %"CID_FMT". %s.", seqno, CID_BITS(&cid), action_str);
return parsed_len;

View file

@ -342,6 +342,24 @@ process_blocked_frame (struct mini_conn *mc, lsquic_packet_in_t *packet_in,
}
static mconn_packno_set_t
drop_packets_out (struct mini_conn *mc)
{
struct lsquic_packet_out *packet_out;
mconn_packno_set_t in_flight = 0;
while ((packet_out = TAILQ_FIRST(&mc->mc_packets_out)))
{
TAILQ_REMOVE(&mc->mc_packets_out, packet_out, po_next);
if (packet_out->po_flags & PO_SENT)
in_flight |= MCONN_PACKET_MASK(packet_out->po_packno);
mini_destroy_packet(mc, packet_out);
}
return in_flight;
}
static unsigned
process_connection_close_frame (struct mini_conn *mc,
lsquic_packet_in_t *packet_in, const unsigned char *p, size_t len)
@ -350,6 +368,8 @@ process_connection_close_frame (struct mini_conn *mc,
uint16_t reason_len;
uint8_t reason_off;
int parsed_len;
(void) drop_packets_out(mc);
parsed_len = mc->mc_conn.cn_pf->pf_parse_connect_close_frame(p, len,
NULL, &error_code, &reason_len, &reason_off);
if (parsed_len < 0)
@ -1700,8 +1720,7 @@ mini_conn_ci_destroy (struct lsquic_conn *lconn)
assert(!(lconn->cn_flags & LSCONN_HASHED));
struct mini_conn *mc = (struct mini_conn *) lconn;
lsquic_packet_in_t *packet_in;
lsquic_packet_out_t *packet_out;
mconn_packno_set_t still_deferred = 0, in_flight = 0;
mconn_packno_set_t still_deferred = 0, in_flight;
enum lsq_log_level log_level;
#if LSQUIC_RECORD_INORD_HIST
char inord_str[0x100];
@ -1717,13 +1736,10 @@ mini_conn_ci_destroy (struct lsquic_conn *lconn)
still_deferred |= MCONN_PACKET_MASK(packet_in->pi_packno);
lsquic_packet_in_put(&mc->mc_enpub->enp_mm, packet_in);
}
while ((packet_out = TAILQ_FIRST(&mc->mc_packets_out)))
{
TAILQ_REMOVE(&mc->mc_packets_out, packet_out, po_next);
if (packet_out->po_flags & PO_SENT)
in_flight |= MCONN_PACKET_MASK(packet_out->po_packno);
mini_destroy_packet(mc, packet_out);
}
if (TAILQ_EMPTY(&mc->mc_packets_out))
in_flight = ~0ull; /* Indicates that packets were dropped before */
else
in_flight = drop_packets_out(mc);
if (mc->mc_conn.cn_enc_session)
mc->mc_conn.cn_esf.g->esf_destroy(mc->mc_conn.cn_enc_session);
log_level = warning_is_warranted(mc) ? LSQ_LOG_WARN : LSQ_LOG_DEBUG;

View file

@ -798,6 +798,42 @@ imico_process_ack_frame (IMICO_PROC_FRAME_ARGS)
}
static unsigned
imico_process_ping_frame (IMICO_PROC_FRAME_ARGS)
{
LSQ_DEBUG("got a PING frame, do nothing");
return 1;
}
static unsigned
imico_process_connection_close_frame (IMICO_PROC_FRAME_ARGS)
{
struct lsquic_packet_out *packet_out;
uint64_t error_code;
uint16_t reason_len;
uint8_t reason_off;
int parsed_len, app_error;
while ((packet_out = TAILQ_FIRST(&conn->imc_packets_out)))
{
TAILQ_REMOVE(&conn->imc_packets_out, packet_out, po_next);
imico_destroy_packet(conn, packet_out);
}
conn->imc_flags |= IMC_CLOSE_RECVD;
parsed_len = conn->imc_conn.cn_pf->pf_parse_connect_close_frame(p, len,
&app_error, &error_code, &reason_len, &reason_off);
if (parsed_len < 0)
return 0;
EV_LOG_CONNECTION_CLOSE_FRAME_IN(LSQUIC_LOG_CONN_ID, error_code,
(int) reason_len, (const char *) p + reason_off);
LSQ_INFO("Received CONNECTION_CLOSE frame (%s-level code: %"PRIu64"; "
"reason: %.*s)", app_error ? "application" : "transport",
error_code, (int) reason_len, (const char *) p + reason_off);
return 0; /* This shuts down the connection */
}
static unsigned
imico_process_invalid_frame (IMICO_PROC_FRAME_ARGS)
{
@ -814,15 +850,15 @@ static unsigned (*const imico_process_frames[N_QUIC_FRAMES])
[QUIC_FRAME_STREAM] = imico_process_stream_frame,
[QUIC_FRAME_CRYPTO] = imico_process_crypto_frame,
[QUIC_FRAME_ACK] = imico_process_ack_frame,
[QUIC_FRAME_PING] = imico_process_ping_frame,
[QUIC_FRAME_CONNECTION_CLOSE] = imico_process_connection_close_frame,
/* XXX: Some of them are invalid, while others are unexpected. We treat
* them the same: handshake cannot proceed.
*/
[QUIC_FRAME_RST_STREAM] = imico_process_invalid_frame,
[QUIC_FRAME_CONNECTION_CLOSE] = imico_process_invalid_frame,
[QUIC_FRAME_MAX_DATA] = imico_process_invalid_frame,
[QUIC_FRAME_MAX_STREAM_DATA] = imico_process_invalid_frame,
[QUIC_FRAME_MAX_STREAMS] = imico_process_invalid_frame,
[QUIC_FRAME_PING] = imico_process_invalid_frame,
[QUIC_FRAME_BLOCKED] = imico_process_invalid_frame,
[QUIC_FRAME_STREAM_BLOCKED] = imico_process_invalid_frame,
[QUIC_FRAME_STREAMS_BLOCKED] = imico_process_invalid_frame,
@ -1422,7 +1458,8 @@ ietf_mini_conn_ci_tick (struct lsquic_conn *lconn, lsquic_time_t now)
if (conn->imc_flags & IMC_ERROR)
{
imico_generate_conn_close(conn);
if (!(conn->imc_flags & IMC_CLOSE_RECVD))
imico_generate_conn_close(conn);
tick |= TICK_CLOSE;
}
else if (conn->imc_flags & IMC_HSK_OK)

View file

@ -54,6 +54,7 @@ struct ietf_mini_conn
IMC_BAD_TRANS_PARAMS = 1 << 16,
IMC_ADDR_VALIDATED = 1 << 17,
IMC_HSK_PACKET_SENT = 1 << 18,
IMC_CLOSE_RECVD = 1 << 19,
} imc_flags;
struct mini_crypto_stream imc_streams[N_ENC_LEVS];
void *imc_stream_ps[N_ENC_LEVS];

View file

@ -345,6 +345,7 @@ lsquic_send_ctl_init (lsquic_send_ctl_t *ctl, struct lsquic_alarmset *alset,
sizeof(ctl->sc_buffered_packets[0]); ++i)
TAILQ_INIT(&ctl->sc_buffered_packets[i].bpq_packets);
ctl->sc_max_packno_bits = PACKNO_BITS_2; /* Safe value before verneg */
ctl->sc_cached_bpt.stream_id = UINT64_MAX;
}
@ -2347,16 +2348,6 @@ lsquic_send_ctl_get_packet_for_stream (lsquic_send_ctl_t *ctl,
}
int
lsquic_send_ctl_buffered_and_same_prio_as_headers (struct lsquic_send_ctl *ctl,
const struct lsquic_stream *stream)
{
return !lsquic_send_ctl_schedule_stream_packets_immediately(ctl)
&& BPT_HIGHEST_PRIO == send_ctl_lookup_bpt(ctl, stream);
}
#ifdef NDEBUG
static
#elif __GNUC__

View file

@ -282,7 +282,7 @@ lsquic_send_ctl_schedule_buffered (lsquic_send_ctl_t *, enum buf_packet_type);
|| TAILQ_FIRST(&(ctl)->sc_buffered_packets[BPT_OTHER_PRIO].bpq_packets ))
#define lsquic_send_ctl_invalidate_bpt_cache(ctl) do { \
(ctl)->sc_cached_bpt.stream_id = 0; \
(ctl)->sc_cached_bpt.stream_id = UINT64_MAX; \
} while (0)
#ifndef NDEBUG
@ -328,11 +328,6 @@ lsquic_send_ctl_pacer_blocked (struct lsquic_send_ctl *);
int
lsquic_send_ctl_sched_is_blocked (struct lsquic_send_ctl *);
int
lsquic_send_ctl_buffered_and_same_prio_as_headers (struct lsquic_send_ctl *,
const struct lsquic_stream *);
void
lsquic_send_ctl_verneg_done (struct lsquic_send_ctl *);

View file

@ -870,6 +870,7 @@ lsquic_stream_frame_in (lsquic_stream_t *stream, stream_frame_t *frame)
uint64_t max_off;
int got_next_offset, rv, free_frame;
enum ins_frame ins_frame;
struct lsquic_conn *lconn;
assert(frame->packet_in);
@ -885,6 +886,18 @@ lsquic_stream_frame_in (lsquic_stream_t *stream, stream_frame_t *frame)
return -1;
}
if (frame->data_frame.df_fin && (stream->sm_bflags & SMBF_IETF)
&& (stream->stream_flags & STREAM_FIN_RECVD)
&& stream->sm_fin_off != DF_END(frame))
{
lconn = stream->conn_pub->lconn;
lconn->cn_if->ci_abort_error(lconn, 0, TEC_FINAL_SIZE_ERROR,
"new final size %"PRIu64" from STREAM frame (id: %"PRIu64") does "
"not match previous final size %"PRIu64, DF_END(frame),
stream->id, stream->sm_fin_off);
return -1;
}
got_next_offset = frame->data_frame.df_offset == stream->read_offset;
insert_frame:
ins_frame = stream->data_in->di_if->di_insert_frame(stream->data_in, frame, stream->read_offset);
@ -981,6 +994,19 @@ int
lsquic_stream_rst_in (lsquic_stream_t *stream, uint64_t offset,
uint64_t error_code)
{
struct lsquic_conn *lconn;
if ((stream->sm_bflags & SMBF_IETF)
&& (stream->stream_flags & STREAM_FIN_RECVD)
&& stream->sm_fin_off != offset)
{
lconn = stream->conn_pub->lconn;
lconn->cn_if->ci_abort_error(lconn, 0, TEC_FINAL_SIZE_ERROR,
"final size %"PRIu64" from RESET_STREAM frame (id: %"PRIu64") "
"does not match previous final size %"PRIu64, offset,
stream->id, stream->sm_fin_off);
return -1;
}
if (stream->stream_flags & STREAM_RST_RECVD)
{
@ -2502,12 +2528,18 @@ stream_activate_hq_frame (struct lsquic_stream *stream, uint64_t off,
shf->shf_flags |= flags;
shf->shf_frame_type = frame_type;
if (shf->shf_flags & SHF_FIXED_SIZE)
{
shf->shf_frame_size = size;
LSQ_DEBUG("activated fixed-size HQ frame of type 0x%X at offset "
"%"PRIu64", size %zu", shf->shf_frame_type, shf->shf_off, size);
}
else
{
shf->shf_frame_ptr = NULL;
if (size >= (1 << 6))
shf->shf_flags |= SHF_TWO_BYTES;
LSQ_DEBUG("activated variable-size HQ frame of type 0x%X at offset "
"%"PRIu64, shf->shf_frame_type, shf->shf_off);
}
return shf;
@ -2528,7 +2560,10 @@ frame_hq_gen_read (void *ctx, void *begin_buf, size_t len, int *fin)
while (p < end)
{
shf = find_cur_hq_frame(stream);
if (!shf)
if (shf)
LSQ_DEBUG("found current HQ frame of type 0x%X at offset %"PRIu64,
shf->shf_frame_type, shf->shf_off);
else
{
rem = frame_std_gen_size(ctx);
if (rem)
@ -2537,7 +2572,11 @@ frame_hq_gen_read (void *ctx, void *begin_buf, size_t len, int *fin)
rem = (1 << 14) - 1;
shf = stream_activate_hq_frame(stream,
stream->sm_payload, HQFT_DATA, 0, rem);
/* XXX malloc can fail */
if (!shf)
{
/* TODO: abort connection? Handle failure somehow */
break;
}
}
else
break;
@ -2548,7 +2587,10 @@ frame_hq_gen_read (void *ctx, void *begin_buf, size_t len, int *fin)
{
frame_sz = stream_hq_frame_size(shf);
if (frame_sz > (uintptr_t) (end - p))
{
stream_hq_frame_put(stream, shf);
break;
}
LSQ_DEBUG("insert %zu-byte HQ frame of type 0x%X at payload "
"offset %"PRIu64" (actual offset %"PRIu64")", frame_sz,
shf->shf_frame_type, stream->sm_payload, stream->tosend_off);
@ -2705,32 +2747,43 @@ stream_write_to_packet_std (struct frame_gen_ctx *fg_ctx, const size_t size)
struct lsquic_send_ctl *const send_ctl = stream->conn_pub->send_ctl;
unsigned stream_header_sz, need_at_least;
struct lsquic_packet_out *packet_out;
struct lsquic_stream *headers_stream;
int len;
if (!(stream->sm_bflags & SMBF_IETF)
&& (stream->stream_flags &
(STREAM_HEADERS_SENT|STREAM_HDRS_FLUSHED))
== STREAM_HEADERS_SENT
&& lsquic_send_ctl_buffered_and_same_prio_as_headers(send_ctl, stream))
if ((stream->stream_flags & (STREAM_HEADERS_SENT|STREAM_HDRS_FLUSHED))
== STREAM_HEADERS_SENT)
{
/* TODO: make this logic work for IETF streams as well XXX */
struct lsquic_stream *const headers_stream
= lsquic_headers_stream_get_stream(stream->conn_pub->u.gquic.hs);
if (lsquic_stream_has_data_to_flush(headers_stream))
/* Optimization idea: the QPACK encoder stream needs only be flushed
* if the headers in this stream are dependent on the buffered encoder
* stream bytes. Knowing this would require changes to ls-qpack. For
* this reason, we don't perform this check and just flush it.
*/
if (stream->sm_bflags & SMBF_IETF)
headers_stream = stream->conn_pub->u.ietf.qeh->qeh_enc_sm_out;
else
headers_stream =
lsquic_headers_stream_get_stream(stream->conn_pub->u.gquic.hs);
if (headers_stream && lsquic_stream_has_data_to_flush(headers_stream))
{
LSQ_DEBUG("flushing headers stream before potential write to a "
"buffered packet");
LSQ_DEBUG("flushing headers stream before packetizing stream data");
(void) lsquic_stream_flush(headers_stream);
}
else
/* Some other stream must have flushed it: this means our headers
* are flushed.
*/
stream->stream_flags |= STREAM_HDRS_FLUSHED;
/* If there is nothing to flush, some other stream must have flushed it:
* this means our headers are flushed. Either way, only do this once.
*/
stream->stream_flags |= STREAM_HDRS_FLUSHED;
}
stream_header_sz = stream->sm_frame_header_sz(stream, size);
need_at_least = stream_header_sz + (size > 0);
need_at_least = stream_header_sz;
if ((stream->sm_bflags & (SMBF_IETF|SMBF_USE_HEADERS))
== (SMBF_IETF|SMBF_USE_HEADERS))
{
if (size > 0)
need_at_least += 3; /* Enough room for HTTP/3 frame */
}
else
need_at_least += size > 0;
get_packet:
packet_out = lsquic_send_ctl_get_packet_for_stream(send_ctl,
need_at_least, stream->conn_pub->path, stream);
@ -2826,13 +2879,14 @@ maybe_close_varsize_hq_frame (struct lsquic_stream *stream)
if (shf->shf_flags & SHF_FIXED_SIZE)
{
stream_hq_frame_put(stream, shf);
if (shf->shf_off + shf->shf_frame_size <= stream->sm_payload)
stream_hq_frame_put(stream, shf);
return;
}
bits = (shf->shf_flags & SHF_TWO_BYTES) > 0;
size = stream->sm_payload + stream->sm_n_buffered - shf->shf_off;
if (size && size <= VINT_MAX_B(bits))
if (size && size <= VINT_MAX_B(bits) && shf->shf_frame_ptr)
{
if (0 == stream->sm_n_buffered)
LSQ_DEBUG("close HQ frame type 0x%X of size %"PRIu64,
@ -2850,6 +2904,14 @@ maybe_close_varsize_hq_frame (struct lsquic_stream *stream)
shf->shf_flags |= SHF_FIXED_SIZE;
}
}
else if (!shf->shf_frame_ptr)
{
LSQ_WARN("dangling HTTP/3 frame");
stream->conn_pub->lconn->cn_if->ci_internal_error(
stream->conn_pub->lconn, "dangling HTTP/3 frame on stream %"PRIu64,
stream->id);
stream_hq_frame_put(stream, shf);
}
else if (!size)
{
assert(!shf->shf_frame_ptr);