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

@ -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 2021-01-27
- 2.27.6 - 2.27.6
- [BUGFIX] Replace dispatch read/write events assertion with a check. - [BUGFIX] Replace dispatch read/write events assertion with a check.

View file

@ -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 static void
http_client_on_sess_resume_info (lsquic_conn_t *conn, const unsigned char *buf, http_client_on_sess_resume_info (lsquic_conn_t *conn, const unsigned char *buf,
size_t bufsz) size_t bufsz)
@ -1011,7 +1012,6 @@ usage (const char *prog)
" -I Abort on incomplete reponse from server\n" " -I Abort on incomplete reponse from server\n"
" -4 Prefer IPv4 when resolving hostname\n" " -4 Prefer IPv4 when resolving hostname\n"
" -6 Prefer IPv6 when resolving hostname\n" " -6 Prefer IPv6 when resolving hostname\n"
" -0 FILE Provide RTT info file (reading or writing)\n"
#ifndef WIN32 #ifndef WIN32
" -C DIR Certificate store. If specified, server certificate will\n" " -C DIR Certificate store. If specified, server certificate will\n"
" be verified.\n" " be verified.\n"
@ -1736,7 +1736,7 @@ main (int argc, char **argv)
case '0': case '0':
http_client_if.on_sess_resume_info = http_client_on_sess_resume_info; http_client_if.on_sess_resume_info = http_client_on_sess_resume_info;
client_ctx.hcc_sess_resume_file_name = optarg; client_ctx.hcc_sess_resume_file_name = optarg;
break; goto common_opts;
case '3': case '3':
s_abandon_early = strtol(optarg, NULL, 10); s_abandon_early = strtol(optarg, NULL, 10);
break; break;
@ -1788,6 +1788,7 @@ main (int argc, char **argv)
prog.prog_api.ea_alpn = optarg; prog.prog_api.ea_alpn = optarg;
prog.prog_api.ea_stream_if = &hq_client_if; prog.prog_api.ea_stream_if = &hq_client_if;
break; break;
common_opts:
default: default:
if (0 != prog_set_opt(&prog, opt, optarg)) if (0 != prog_set_opt(&prog, opt, optarg))
exit(1); exit(1);

View file

@ -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 #if HAVE_OPEN_MEMSTREAM
* good assumption to make in a real product.
*/
static void static void
hq_server_on_read (struct lsquic_stream *stream, lsquic_stream_ctx_t *st_h) hq_server_on_read (struct lsquic_stream *stream, lsquic_stream_ctx_t *st_h)
{ {
char buf[0x400]; char tbuf[0x100], *buf;
ssize_t nread; ssize_t nread;
char *path, *end, *filename; char *path, *end, *filename;
nread = lsquic_stream_read(stream, buf, sizeof(buf)); if (!st_h->req_fh)
if (nread >= (ssize_t) sizeof(buf)) 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)); fwrite(tbuf, 1, nread, st_h->req_fh);
lsquic_stream_close(stream);
return; return;
} }
else if (nread < 0)
if (nread < 0)
{ {
LSQ_WARN("error reading request from stream: %s", strerror(errno)); LSQ_WARN("error reading request from stream: %s", strerror(errno));
lsquic_stream_close(stream); lsquic_stream_close(stream);
return; 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, ' '); path = strchr(buf, ' ');
if (!path) if (!path)
{ {
@ -1104,8 +1110,8 @@ hq_server_on_read (struct lsquic_stream *stream, lsquic_stream_ctx_t *st_h)
return; return;
} }
++path; ++path;
for (end = path + nread - 5; end > path for (end = buf + st_h->req_sz - 1; end > path
&& (*end == '\r' || *end == '\n'); --end) && (*end == '\0' || *end == '\r' || *end == '\n'); --end)
*end = '\0'; *end = '\0';
LSQ_NOTICE("parsed out request path: %s", path); 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_write = hq_server_on_write,
.on_close = http_server_on_close, .on_close = http_server_on_close,
}; };
#endif
#if HAVE_REGEX #if HAVE_REGEX
@ -1922,7 +1929,11 @@ main (int argc, char **argv)
prog_init(&prog, LSENG_SERVER|LSENG_HTTP, &server_ctx.sports, prog_init(&prog, LSENG_SERVER|LSENG_HTTP, &server_ctx.sports,
&http_server_if, &server_ctx); &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) { switch (opt) {
case 'n': case 'n':
@ -1965,12 +1976,14 @@ main (int argc, char **argv)
usage(argv[0]); usage(argv[0]);
prog_print_common_options(&prog, stdout); prog_print_common_options(&prog, stdout);
exit(0); exit(0);
#if HAVE_OPEN_MEMSTREAM
case 'Q': case 'Q':
/* XXX A bit hacky, as `prog' has already been initialized... */ /* XXX A bit hacky, as `prog' has already been initialized... */
prog.prog_engine_flags &= ~LSENG_HTTP; prog.prog_engine_flags &= ~LSENG_HTTP;
prog.prog_api.ea_stream_if = &hq_server_if; prog.prog_api.ea_stream_if = &hq_server_if;
add_alpn(optarg); add_alpn(optarg);
break; break;
#endif
default: default:
if (0 != prog_set_opt(&prog, opt, optarg)) if (0 != prog_set_opt(&prog, opt, optarg))
exit(1); exit(1);

View file

@ -39,6 +39,7 @@
static int prog_stopped; static int prog_stopped;
static const char *s_keylog_dir; static const char *s_keylog_dir;
static const char *s_sess_resume_file;
static SSL_CTX * get_ssl_ctx (void *, const struct sockaddr *); static SSL_CTX * get_ssl_ctx (void *, const struct sockaddr *);
static void keylog_log_line (const SSL *, const char *); static void keylog_log_line (const SSL *, const char *);
@ -118,6 +119,7 @@ void
prog_print_common_options (const struct prog *prog, FILE *out) prog_print_common_options (const struct prog *prog, FILE *out)
{ {
fprintf(out, fprintf(out,
" -0 FILE Provide session resumption file (reading or writing)\n"
#if HAVE_REGEX #if HAVE_REGEX
" -s SVCPORT Service port. Takes on the form of host:port, host,\n" " -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" " 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; sport->sp_flags |= SPORT_CONNECT;
} }
return 0; return 0;
case '0':
s_sess_resume_file = optarg;
return 0;
case 'G': case 'G':
#ifndef WIN32 #ifndef WIN32
if (0 == stat(optarg, &st)) 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 static int
prog_init_ssl_ctx (struct prog *prog) prog_init_ssl_ctx (struct prog *prog)
{ {
@ -454,6 +506,14 @@ prog_init_ssl_ctx (struct prog *prog)
if (s_keylog_dir) if (s_keylog_dir)
SSL_CTX_set_keylog_callback(prog->prog_ssl_ctx, keylog_log_line); 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; return 0;
} }

View file

@ -158,6 +158,8 @@ static void getExtensionPtrs()
#endif #endif
static struct packets_in * static struct packets_in *
allocate_packets_in (SOCKET_TYPE fd) allocate_packets_in (SOCKET_TYPE fd)
{ {

View file

@ -874,6 +874,14 @@ settings structure:
Default value is :macro:`LSQUIC_DF_MAX_BATCH_SIZE` 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 To initialize the settings structure to library defaults, use the following
convenience function: 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 By default, maximum batch size is not specified, leaving it up to the
library. library.
.. macro:: LSQUIC_DF_CHECK_TP_SANITY
Transport parameter sanity checks are performed by default.
Receiving Packets Receiving Packets
----------------- -----------------
@ -1337,6 +1349,11 @@ the engine to communicate with the user code:
This callback lets client record information needed to This callback lets client record information needed to
perform session resumption next time around. 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. This callback is optional.
.. member:: ssize_t (*on_dg_write)(lsquic_conn_t *c, void *buf, size_t buf_sz) .. member:: ssize_t (*on_dg_write)(lsquic_conn_t *c, void *buf, size_t buf_sz)

View file

@ -24,9 +24,9 @@ copyright = u'2021, LiteSpeed Technologies'
author = u'LiteSpeed Technologies' author = u'LiteSpeed Technologies'
# The short X.Y version # The short X.Y version
version = u'2.27' version = u'2.28'
# The full version, including alpha/beta/rc tags # The full version, including alpha/beta/rc tags
release = u'2.27.6' release = u'2.28.0'
# -- General configuration --------------------------------------------------- # -- General configuration ---------------------------------------------------

View file

@ -24,8 +24,8 @@ extern "C" {
#endif #endif
#define LSQUIC_MAJOR_VERSION 2 #define LSQUIC_MAJOR_VERSION 2
#define LSQUIC_MINOR_VERSION 27 #define LSQUIC_MINOR_VERSION 28
#define LSQUIC_PATCH_VERSION 6 #define LSQUIC_PATCH_VERSION 0
/** /**
* Engine flags: * Engine flags:
@ -208,6 +208,11 @@ struct lsquic_stream_if {
/** /**
* This optional callback lets client record information needed to * This optional callback lets client record information needed to
* perform a session resumption next time around. * 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); 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_ctx_st;
struct ssl_st; struct ssl_st;
struct ssl_session_st;
struct lsxpack_header; struct lsxpack_header;
/** /**
@ -440,6 +446,9 @@ typedef struct ssl_ctx_st * (*lsquic_lookup_cert_f)(
*/ */
#define LSQUIC_DF_MAX_BATCH_SIZE 0 #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 { struct lsquic_engine_settings {
/** /**
* This is a bit mask wherein each bit corresponds to a value in * 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; 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 * batch of packets passed to @ref ea_packets_out() callback). Must
* be no larger than 1024. * be no larger than 1024.
* *
* Default value is @ref LSQUIC_DF_MAX_BATCH_SIZE * Default value is @ref LSQUIC_DF_MAX_BATCH_SIZE
*/ */
unsigned es_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 */ /* Initialize `settings' to default values */
@ -1337,8 +1355,10 @@ struct lsquic_engine_api
/** /**
* Optional interface to control the creation of connection IDs * Optional interface to control the creation of connection IDs
*/ */
void (*ea_generate_scid)(lsquic_conn_t *, void (*ea_generate_scid)(void *ctx,
lsquic_cid_t *, unsigned); 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 int
lsquic_cid_from_packet (const unsigned char *, size_t bufsz, lsquic_cid_t *cid); 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. * Returns true if there are connections to be processed, false otherwise.
* If true, `diff' is set to the difference between the earliest advisory * If true, `diff' is set to the difference between the earliest advisory
@ -2065,6 +2097,18 @@ lsquic_ver2str[N_LSQVER];
lsquic_conn_t * lsquic_conn_t *
lsquic_ssl_to_conn (const struct ssl_st *); 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 #ifdef __cplusplus
} }
#endif #endif

View file

@ -239,7 +239,7 @@ lsquic_generate_cid (lsquic_cid_t *cid, size_t len)
void 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) unsigned len)
{ {
lsquic_generate_cid(scid, len); lsquic_generate_cid(scid, len);

View file

@ -393,7 +393,7 @@ void
lsquic_generate_cid_gquic (lsquic_cid_t *cid); lsquic_generate_cid_gquic (lsquic_cid_t *cid);
void 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); unsigned len);
void 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_ex_data(enc_sess->esi_ssl, s_idx, enc_sess);
SSL_set_connect_state(enc_sess->esi_ssl); 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_flags |= ESI_WANT_TICKET;
enc_sess->esi_alset = alset; enc_sess->esi_alset = alset;
lsquic_alarmset_init_alarm(enc_sess->esi_alset, AL_SESS_TICKET, 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) } while (0)
#endif #endif
/* Return 0 on success, in which case *buf is newly allocated memory and should
* be freed by the caller.
*/
static int 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; uint32_t num;
unsigned char *p, *buf; unsigned char *p, *buf;
uint8_t *ticket_buf; uint8_t *ticket_buf;
@ -1446,33 +1450,29 @@ iquic_new_session_cb (SSL *ssl, SSL_SESSION *session)
const uint8_t *trapa_buf; const uint8_t *trapa_buf;
size_t trapa_sz, buf_sz; size_t trapa_sz, buf_sz;
enc_sess = SSL_get_ex_data(ssl, s_idx); SSL_get_peer_quic_transport_params(ssl, &trapa_buf, &trapa_sz);
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);
if (!(trapa_buf + trapa_sz)) if (!(trapa_buf + trapa_sz))
{ {
LSQ_WARN("no transport parameters: cannot generate session " LSQ_WARN("no transport parameters: cannot generate session "
"resumption info"); "resumption info");
return 0; return -1;
} }
if (trapa_sz > UINT32_MAX) if (trapa_sz > UINT32_MAX)
{ {
LSQ_WARN("trapa size too large: %zu", trapa_sz); LSQ_WARN("trapa size too large: %zu", trapa_sz);
return 0; return -1;
} }
if (!SSL_SESSION_to_bytes(session, &ticket_buf, &ticket_sz)) if (!SSL_SESSION_to_bytes(session, &ticket_buf, &ticket_sz))
{ {
LSQ_INFO("could not serialize new session"); LSQ_INFO("could not serialize new session");
return 0; return -1;
} }
if (ticket_sz > UINT32_MAX) if (ticket_sz > UINT32_MAX)
{ {
LSQ_WARN("ticket size too large: %zu", ticket_sz); LSQ_WARN("ticket size too large: %zu", ticket_sz);
OPENSSL_free(ticket_buf); OPENSSL_free(ticket_buf);
return 0; return -1;
} }
buf_sz = sizeof(tag) + sizeof(uint32_t) + sizeof(uint32_t) 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) if (!buf)
{ {
OPENSSL_free(ticket_buf); OPENSSL_free(ticket_buf);
LSQ_WARN("%s: malloc failed", __func__); LSQ_INFO("%s: malloc failed", __func__);
return 0; return -1;
} }
p = buf; 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); 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, *bufp = buf;
buf, buf_sz); *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); free(buf);
enc_sess->esi_flags &= ~ESI_WANT_TICKET; enc_sess->esi_flags &= ~ESI_WANT_TICKET;
lsquic_alarmset_unset(enc_sess->esi_alset, AL_SESS_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"); 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; return 0;
} }
@ -3400,3 +3448,28 @@ lsquic_ssl_to_conn (const struct ssl_st *ssl)
return enc_sess->esi_conn; 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_thresh = LSQUIC_DF_PTPC_ERR_THRESH;
settings->es_ptpc_err_divisor= LSQUIC_DF_PTPC_ERR_DIVISOR; settings->es_ptpc_err_divisor= LSQUIC_DF_PTPC_ERR_DIVISOR;
settings->es_delay_onclose = LSQUIC_DF_DELAY_ONCLOSE; 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; engine->pub.enp_get_ssl_ctx = api->ea_get_ssl_ctx;
if (api->ea_generate_scid) if (api->ea_generate_scid)
{
engine->pub.enp_generate_scid = 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 else
engine->pub.enp_generate_scid = lsquic_generate_scid; engine->pub.enp_generate_scid = lsquic_generate_scid;

View file

@ -48,8 +48,9 @@ struct lsquic_engine_public {
void *enp_stream_if_ctx; void *enp_stream_if_ctx;
const struct lsquic_hset_if *enp_hsi_if; const struct lsquic_hset_if *enp_hsi_if;
void *enp_hsi_ctx; void *enp_hsi_ctx;
void (*enp_generate_scid)(struct lsquic_conn *, void (*enp_generate_scid)(void *,
struct lsquic_cid *, unsigned); struct lsquic_conn *, struct lsquic_cid *, unsigned);
void *enp_gen_scid_ctx;
int (*enp_verify_cert)(void *verify_ctx, int (*enp_verify_cert)(void *verify_ctx,
struct stack_st_X509 *chain); struct stack_st_X509 *chain);
void *enp_verify_ctx; 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; conn->fc_recent_packets[out].els[idx].time = time;
} }
static void static void
recent_packet_hist_frames (struct full_conn *conn, unsigned out, recent_packet_hist_frames (struct full_conn *conn, unsigned out,
enum quic_ft_bit frame_types) 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; idx = (conn->fc_recent_packets[out].idx - 1) % KEEP_PACKET_HISTORY;
conn->fc_recent_packets[out].els[idx].frame_types |= frame_types; conn->fc_recent_packets[out].els[idx].frame_types |= frame_types;
} }
#else #else
#define recent_packet_hist_new(conn, out, time) #define recent_packet_hist_new(conn, out, time)
#define recent_packet_hist_frames(conn, out, frames) #define recent_packet_hist_frames(conn, out, frames)
@ -543,6 +546,7 @@ apply_peer_settings (struct full_conn *conn)
return 0; return 0;
} }
static const struct conn_iface *full_conn_iface_ptr; 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); assert(i == ack_info->n_ranges);
LSQ_DEBUG("Sent ACK frame %s", ack_buf); LSQ_DEBUG("Sent ACK frame %s", ack_buf);
} }
#endif #endif
@ -4059,7 +4065,6 @@ headers_stream_on_priority (void *ctx, lsquic_stream_id_t stream_id,
} }
#define STRLEN(s) (sizeof(s) - 1) #define STRLEN(s) (sizeof(s) - 1)
static struct uncompressed_headers * static struct uncompressed_headers *
@ -4456,6 +4461,7 @@ full_conn_ci_get_stats (struct lsquic_conn *lconn)
return &conn->fc_stats; return &conn->fc_stats;
} }
#include "lsquic_cong_ctl.h" #include "lsquic_cong_ctl.h"
static void static void
@ -4493,6 +4499,8 @@ full_conn_ci_log_stats (struct lsquic_conn *lconn)
*conn->fc_last_stats = conn->fc_stats; *conn->fc_last_stats = conn->fc_stats;
memset(bs, 0, sizeof(*bs)); memset(bs, 0, sizeof(*bs));
} }
#endif #endif

View file

@ -1184,7 +1184,7 @@ ietf_full_conn_add_scid (struct ietf_full_conn *conn,
} }
if (enpub->enp_settings.es_scid_len) 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); enpub->enp_settings.es_scid_len);
cce->cce_seqno = conn->ifc_scid_seqno++; 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 /* Generate new SCID. Since is not the original SCID, it is given
* a sequence number (0) and therefore can be retired by the client. * 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); &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", 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) */ /* 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] = 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 /* Perform an implicit flush when we hit connection or stream flow control
* data. This is to prevent a (theoretical) stall: * 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 * 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. * 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 * None of them flushes, which means that data is not sent and connection
* WINDOW_UPDATE frame never arrives from peer. Stall. * 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 static int
maybe_flush_stream (struct lsquic_stream *stream) maybe_flush_stream (struct lsquic_stream *stream)
{ {
if (stream->sm_n_buffered > 0 if (stream->sm_n_buffered > 0 && stream->sm_write_avail(stream) == 0)
&& (stream->sm_bflags & SMBF_CONN_LIMITED) {
&& lsquic_conn_cap_avail(&stream->conn_pub->conn_cap) == 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); return stream_flush_nocheck(stream);
}
else else
return 0; return 0;
} }
@ -4033,7 +4042,12 @@ send_headers_ietf (struct lsquic_stream *stream,
return -1; return -1;
#endif #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; stream->stream_flags |= STREAM_NOPUSH;
/* TODO: Optimize for the common case: write directly to sm_buf and fall /* 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"); nw, promise->pp_write_state == PPWS_DONE ? "done" : "not done");
if (promise->pp_write_state == PPWS_DONE) if (promise->pp_write_state == PPWS_DONE)
{ {
stream->stream_flags &= ~STREAM_PUSHING;
/* Restore want_write flag */ /* Restore want_write flag */
want_write = !!(stream->sm_qflags & SMQF_WANT_WRITE); want_write = !!(stream->sm_qflags & SMQF_WANT_WRITE);
if (want_write != stream->sm_saved_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 push_promise *promise)
{ {
struct lsquic_reader pp_reader; struct lsquic_reader pp_reader;
struct stream_hq_frame *shf;
unsigned bits, len; unsigned bits, len;
ssize_t nw; 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, vint_write(promise->pp_encoded_push_id + 8 - len, promise->pp_id,
bits, 1 << bits); 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, 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; return -1;
stream->stream_flags |= STREAM_PUSHING; stream->stream_flags |= STREAM_PUSHING;
init_pp_reader(promise, &pp_reader); 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); nw = stream_write(stream, &pp_reader, SWO_BUFFER);
if (nw > 0) if (nw > 0)
{ {
SLIST_INSERT_HEAD(&stream->sm_promises, promise, pp_next); SLIST_INSERT_HEAD(&stream->sm_promises, promise, pp_next);
++promise->pp_refcnt; ++promise->pp_refcnt;
if (promise->pp_write_state == PPWS_DONE) if (promise->pp_write_state == PPWS_DONE)
{
LSQ_DEBUG("fully wrote promise %"PRIu64, promise->pp_id); LSQ_DEBUG("fully wrote promise %"PRIu64, promise->pp_id);
stream->stream_flags &= ~STREAM_PUSHING;
}
else else
{ {
LSQ_DEBUG("partially wrote promise %"PRIu64" (state: %d, off: %u)" 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->stream_flags |= STREAM_NOPUSH;
stream->sm_saved_want_write = stream->sm_saved_want_write =
!!(stream->sm_qflags & SMQF_WANT_WRITE); !!(stream->sm_qflags & SMQF_WANT_WRITE);
lsquic_stream_flush(stream);
stream_wantwrite(stream, 1); stream_wantwrite(stream, 1);
} }
return 0; return 0;
@ -5396,6 +5425,7 @@ lsquic_stream_push_promise (struct lsquic_stream *stream,
{ {
if (nw < 0) if (nw < 0)
LSQ_WARN("failure writing push promise"); LSQ_WARN("failure writing push promise");
stream_hq_frame_put(stream, shf);
stream->stream_flags |= STREAM_NOPUSH; stream->stream_flags |= STREAM_NOPUSH;
stream->stream_flags &= ~STREAM_PUSHING; stream->stream_flags &= ~STREAM_PUSHING;
return -1; return -1;

View file

@ -531,7 +531,8 @@ test_pp_wantwrite_restoration (const int want_write)
lsquic_stream_window_update(stream, 100); lsquic_stream_window_update(stream, 100);
lsquic_stream_dispatch_write_events(stream); lsquic_stream_dispatch_write_events(stream);
assert((stream->stream_flags & (STREAM_NOPUSH|STREAM_PUSHING)) 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(SLIST_FIRST(&stream->sm_promises)->pp_write_state == PPWS_DONE); /* Done! */
assert(want_write == s_onwrite_called); /* Restored: and on_write called */ assert(want_write == s_onwrite_called); /* Restored: and on_write called */