Packages
hackney
3.0.2
4.7.2
4.7.1
4.7.0
4.6.1
4.6.0
4.5.2
4.5.1
4.5.0
4.4.5
4.4.3
4.4.2
4.4.1
4.4.0
4.3.0
4.2.3
4.2.2
4.2.1
4.2.0
4.1.0
4.0.3
4.0.2
4.0.1
4.0.0
3.2.1
3.2.0
3.1.2
3.1.1
3.1.0
3.0.3
3.0.2
3.0.1
3.0.0
retired
2.0.1
2.0.0
2.0.0-beta.1
1.25.0
1.24.1
1.24.0
1.23.0
1.22.0
1.21.0
1.20.1
1.20.0
1.19.1
1.19.0
1.18.2
1.18.1
1.18.0
1.17.4
1.17.3
1.17.2
1.17.1
1.17.0
1.16.0
1.15.2
1.15.1
1.15.0
1.14.3
1.14.2
1.14.0
1.13.0
1.12.1
1.12.0
1.11.0
1.10.1
1.10.0
1.9.0
1.8.6
1.8.5
1.8.4
1.8.3
1.8.2
1.8.0
1.7.1
1.7.0
1.6.6
retired
1.6.5
1.6.4
retired
1.6.3
1.6.2
1.6.1
1.6.0
1.5.7
1.5.6
1.5.5
1.5.4
1.5.3
1.5.2
1.5.1
1.5.0
1.4.10
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.0
1.1.0
1.0.6
1.0.5
1.0.2
1.0.1
0.15.2
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.13.1
Simple HTTP client with HTTP/1.1, HTTP/2, and HTTP/3 support
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
c_src/lsquic/src/liblsquic/lsquic_mini_conn_ietf.c
/* Copyright (c) 2017 - 2022 LiteSpeed Technologies Inc. See LICENSE. */
/*
* lsquic_mini_conn_ietf.c -- Mini connection used by the IETF QUIC
*/
#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <limits.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <sys/queue.h>
#include <stdlib.h>
#include "lsquic.h"
#include "lsquic_int_types.h"
#include "lsquic_sizes.h"
#include "lsquic_hash.h"
#include "lsquic_conn.h"
#include "lsquic_mm.h"
#include "lsquic_malo.h"
#include "lsquic_engine_public.h"
#include "lsquic_packet_common.h"
#include "lsquic_packet_in.h"
#include "lsquic_packet_out.h"
#include "lsquic_parse.h"
#include "lsquic_rtt.h"
#include "lsquic_util.h"
#include "lsquic_enc_sess.h"
#include "lsquic_trechist.h"
#include "lsquic_mini_conn_ietf.h"
#include "lsquic_ev_log.h"
#include "lsquic_trans_params.h"
#include "lsquic_ietf.h"
#include "lsquic_packet_ietf.h"
#include "lsquic_attq.h"
#include "lsquic_alarmset.h"
#include "lsquic_crand.h"
#define LSQUIC_LOGGER_MODULE LSQLM_MINI_CONN
#define LSQUIC_LOG_CONN_ID lsquic_conn_log_cid(&conn->imc_conn)
#include "lsquic_logger.h"
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
static const struct conn_iface mini_conn_ietf_iface;
static unsigned highest_bit_set (unsigned long long);
static int
imico_can_send (const struct ietf_mini_conn *, size_t);
static void
ietf_mini_conn_ci_abort_error (struct lsquic_conn *lconn, int is_app,
unsigned error_code, const char *fmt, ...);
static const enum header_type el2hety[] =
{
[ENC_LEV_HSK] = HETY_HANDSHAKE,
[ENC_LEV_INIT] = HETY_INITIAL,
[ENC_LEV_APP] = HETY_SHORT,
[ENC_LEV_0RTT] = 0, /* Invalid */
};
static void
imico_destroy_packet (struct ietf_mini_conn *conn,
struct lsquic_packet_out *packet_out)
{
lsquic_packet_out_destroy(packet_out, conn->imc_enpub,
conn->imc_path.np_peer_ctx);
}
int
lsquic_mini_conn_ietf_ecn_ok (const struct ietf_mini_conn *conn)
{
packno_set_t acked;
/* First flight has only Initial and Handshake packets */
acked = conn->imc_acked_packnos[PNS_INIT]
| conn->imc_acked_packnos[PNS_HSK]
;
return 0 != (conn->imc_ecn_packnos & acked);
}
static enum ecn
imico_get_ecn (struct ietf_mini_conn *conn)
{
if (!conn->imc_enpub->enp_settings.es_ecn)
return ECN_NOT_ECT;
else
return ECN_ECT0;
}
static struct lsquic_packet_out *
imico_get_packet_out (struct ietf_mini_conn *conn,
enum header_type header_type, size_t need)
{
struct lsquic_packet_out *packet_out;
enum ecn ecn;
if (need)
TAILQ_FOREACH(packet_out, &conn->imc_packets_out, po_next)
if (!(packet_out->po_flags & PO_SENT)
&& packet_out->po_header_type == header_type
&& lsquic_packet_out_avail(packet_out) >= need)
return packet_out;
if (conn->imc_next_packno >= MAX_PACKETS)
{
LSQ_DEBUG("ran out of outgoing packet numbers, won't allocate packet");
return NULL;
}
packet_out = lsquic_packet_out_new(&conn->imc_enpub->enp_mm, NULL, 1,
&conn->imc_conn, IQUIC_PACKNO_LEN_1, NULL, NULL, &conn->imc_path,
header_type);
if (!packet_out)
{
LSQ_WARN("could not allocate packet: %s", strerror(errno));
return NULL;
}
packet_out->po_header_type = header_type;
packet_out->po_packno = conn->imc_next_packno++;
packet_out->po_flags |= PO_MINI;
lsquic_packet_out_set_pns(packet_out, lsquic_hety2pns[header_type]);
ecn = imico_get_ecn(conn);
packet_out->po_lflags |= ecn << POECN_SHIFT;
TAILQ_INSERT_TAIL(&conn->imc_packets_out, packet_out, po_next);
packet_out->po_loss_chain = packet_out;
return packet_out;
}
static struct ietf_mini_conn *
cryst_get_conn (const struct mini_crypto_stream *cryst)
{
return (void *)
((unsigned char *) (cryst - cryst->mcs_enc_level)
- offsetof(struct ietf_mini_conn, imc_streams));
}
struct msg_ctx
{
const unsigned char *buf;
const unsigned char *const end;
};
static size_t
read_from_msg_ctx (void *ctx, void *buf, size_t len, int *fin)
{
struct msg_ctx *msg_ctx = ctx;
if (len > (uintptr_t) (msg_ctx->end - msg_ctx->buf))
len = msg_ctx->end - msg_ctx->buf;
memcpy(buf, msg_ctx->buf, len);
msg_ctx->buf += len;
return len;
}
static int
imico_chlo_has_been_consumed (const struct ietf_mini_conn *conn)
{
return conn->imc_streams[ENC_LEV_INIT].mcs_read_off > 3
&& conn->imc_streams[ENC_LEV_INIT].mcs_read_off >= conn->imc_ch_len;
}
static int
imico_process_version_negociation (struct ietf_mini_conn *conn,
const struct transport_params *params)
{
unsigned common_versions;
unsigned ver;
int i;
if (LSQ_LOG_ENABLED(LSQ_LOG_DEBUG))
{
LSQ_DEBUG("client chosen version %s",
lsquic_ver2str[params->tp_chosen_version]);
for (i = 1; i < params->tp_version_cnt; ++i)
{
LSQ_DEBUG("client available version %s",
lsquic_ver2str[params->tp_version_info[i]]);
//EV_LOG_VER_NEG(LSQUIC_LOG_CONN_ID, "supports",
// lsquic_ver2str[params->tp_version_info[i]]);
}
}
if (params->tp_chosen_version != conn->imc_conn.cn_version)
{
LSQ_DEBUG("client chosen version does not match the version in use %s",
lsquic_ver2str[conn->imc_conn.cn_version]);
ietf_mini_conn_ci_abort_error(&conn->imc_conn, 0,
TEC_VERSION_NEGOTIATION_ERROR,
"chosen version mismatch");
return -1;
}
common_versions = conn->imc_enpub->enp_settings.es_versions
& params->tp_versions;
ver = highest_bit_set(common_versions);
if (conn->imc_conn.cn_version != (enum lsquic_version)ver)
{
LSQ_DEBUG("version negociation: switch version from %s to %s",
lsquic_ver2str[conn->imc_conn.cn_version],
lsquic_ver2str[ver]);
conn->imc_conn.cn_version = ver;
conn->imc_conn.cn_flags |= LSCONN_VER_UPDATED;
iquic_esfi_switch_version(conn->imc_conn.cn_enc_session,
&conn->imc_cces[0].cce_cid, 1);
}
return 0;
}
static int
imico_maybe_process_params (struct ietf_mini_conn *conn)
{
const struct transport_params *params;
if (imico_chlo_has_been_consumed(conn)
&& (conn->imc_flags & (IMC_ENC_SESS_INITED|IMC_HAVE_TP))
== IMC_ENC_SESS_INITED)
{
params = conn->imc_conn.cn_esf.i->esfi_get_peer_transport_params(
conn->imc_conn.cn_enc_session);
if (params)
{
conn->imc_flags |= IMC_HAVE_TP;
conn->imc_ack_exp = params->tp_ack_delay_exponent;
if (params->tp_set & (1 << TPI_MAX_UDP_PAYLOAD_SIZE))
{
if (params->tp_numerics[TPI_MAX_UDP_PAYLOAD_SIZE]
< conn->imc_path.np_pack_size)
conn->imc_path.np_pack_size =
params->tp_numerics[TPI_MAX_UDP_PAYLOAD_SIZE];
}
LSQ_DEBUG("read transport params, packet size is set to %hu bytes",
conn->imc_path.np_pack_size);
if (params->tp_set & (1 << TPI_VERSION_INFORMATION))
{
if (imico_process_version_negociation(conn, params) == -1)
{
conn->imc_flags |= IMC_VER_NEG_FAILED;
return -1;
}
}
iquic_esfi_init_server_tp(conn->imc_conn.cn_enc_session);
}
else
{
conn->imc_flags |= IMC_BAD_TRANS_PARAMS;
return -1;
}
}
return 0;
}
static void
imico_zero_pad (struct lsquic_packet_out *packet_out, size_t pad_size)
{
memset(packet_out->po_data + packet_out->po_data_sz, 0, pad_size);
packet_out->po_padding_sz = pad_size;
packet_out->po_data_sz += pad_size;
packet_out->po_frame_types |= QUIC_FTBIT_PADDING;
}
static int
imico_generate_ack (struct ietf_mini_conn *conn, enum packnum_space pns,
lsquic_time_t now);
static ssize_t
imico_stream_write (void *stream, const void *bufp, size_t bufsz)
{
struct mini_crypto_stream *const cryst = stream;
struct ietf_mini_conn *const conn = cryst_get_conn(cryst);
struct lsquic_conn *const lconn = &conn->imc_conn;
const struct parse_funcs *const pf = lconn->cn_pf;
struct msg_ctx msg_ctx = { bufp, (unsigned char *) bufp + bufsz, };
struct lsquic_packet_out *packet_out;
size_t header_sz, need;
const unsigned char *p;
int len;
if (0 != imico_maybe_process_params(conn))
return -1;
if (PNS_INIT == lsquic_enclev2pns[ cryst->mcs_enc_level ]
&& (conn->imc_flags & IMC_IGNORE_INIT))
{
LSQ_WARN("trying to write at the ignored Initial level");
return bufsz;
}
if (cryst->mcs_enc_level == ENC_LEV_INIT
&& (conn->imc_flags & IMC_QUEUED_ACK_INIT))
{
imico_generate_ack(conn, PNS_INIT, lsquic_time_now());
}
while (msg_ctx.buf < msg_ctx.end)
{
header_sz = lconn->cn_pf->pf_calc_crypto_frame_header_sz(
cryst->mcs_write_off, msg_ctx.end - msg_ctx.buf);
need = header_sz + 500;
packet_out = imico_get_packet_out(conn,
el2hety[ cryst->mcs_enc_level ], need);
if (!packet_out)
return -1;
// NOTE: reduce the size of first crypto frame to combine packets
int avail = lsquic_packet_out_avail(packet_out);
int coalescing = 0;
if (cryst->mcs_enc_level == ENC_LEV_HSK
&& cryst->mcs_write_off == 0
&& avail > (int)conn->imc_hello_pkt_remain - conn->imc_long_header_sz)
{
avail = conn->imc_hello_pkt_remain - conn->imc_long_header_sz;
conn->imc_hello_pkt_remain = 0;
coalescing = 1;
}
p = msg_ctx.buf;
len = pf->pf_gen_crypto_frame(packet_out->po_data + packet_out->po_data_sz,
avail, 0, cryst->mcs_write_off, 0,
msg_ctx.end - msg_ctx.buf, read_from_msg_ctx, &msg_ctx);
if (len < 0)
return len;
EV_LOG_GENERATED_CRYPTO_FRAME(LSQUIC_LOG_CONN_ID, pf,
packet_out->po_data + packet_out->po_data_sz, len);
packet_out->po_data_sz += len;
packet_out->po_frame_types |= 1 << QUIC_FRAME_CRYPTO;
packet_out->po_flags |= PO_HELLO;
if (coalescing && len < avail)
{
LSQ_DEBUG("generated PADDING frame: %d bytes for packet %"PRIu64,
avail - len, packet_out->po_packno);
imico_zero_pad(packet_out, avail - len);
}
cryst->mcs_write_off += msg_ctx.buf - p;
if (cryst->mcs_enc_level == ENC_LEV_INIT)
conn->imc_hello_pkt_remain = lsquic_packet_out_avail(packet_out);
}
assert(msg_ctx.buf == msg_ctx.end);
return bufsz;
}
static int
imico_stream_flush (void *stream)
{
return 0;
}
static struct stream_frame *
imico_find_stream_frame (const struct ietf_mini_conn *conn,
enum enc_level enc_level, unsigned read_off)
{
struct stream_frame *frame;
if (conn->imc_last_in.frame && enc_level == conn->imc_last_in.enc_level
&& read_off == DF_ROFF(conn->imc_last_in.frame))
return conn->imc_last_in.frame;
TAILQ_FOREACH(frame, &conn->imc_crypto_frames, next_frame)
if (enc_level == frame->stream_id && read_off == DF_ROFF(frame))
return frame;
return NULL;
}
static void
imico_read_chlo_size (struct ietf_mini_conn *conn, const unsigned char *buf,
size_t sz)
{
const unsigned char *const end = buf + sz;
assert(conn->imc_streams[ENC_LEV_INIT].mcs_read_off < 4);
switch (conn->imc_streams[ENC_LEV_INIT].mcs_read_off)
{
case 0:
if (buf == end)
return;
if (*buf != 1)
{
LSQ_DEBUG("Does not begin with ClientHello");
conn->imc_flags |= IMC_ERROR;
return;
}
++buf;
/* fall-through */
case 1:
if (buf == end)
return;
if (*buf != 0)
{
LSQ_DEBUG("ClientHello larger than 16K");
conn->imc_flags |= IMC_ERROR;
return;
}
++buf;
/* fall-through */
case 2:
if (buf == end)
return;
conn->imc_ch_len = *buf << 8;
++buf;
/* fall-through */
default:
if (buf == end)
return;
conn->imc_ch_len |= *buf;
}
}
static ssize_t
imico_stream_readf (void *stream,
size_t (*readf)(void *, const unsigned char *, size_t, int), void *ctx)
{
struct mini_crypto_stream *const cryst = stream;
struct ietf_mini_conn *const conn = cryst_get_conn(cryst);
struct stream_frame *frame;
const unsigned char *buf;
size_t nread, total_read;
unsigned avail;
total_read = 0;
while ((frame = imico_find_stream_frame(conn, cryst->mcs_enc_level,
cryst->mcs_read_off)))
{
avail = DF_SIZE(frame) - frame->data_frame.df_read_off;
buf = frame->data_frame.df_data + frame->data_frame.df_read_off;
nread = readf(ctx, buf, avail, DF_FIN(frame));
if (cryst->mcs_enc_level == ENC_LEV_INIT && cryst->mcs_read_off < 4)
imico_read_chlo_size(conn, buf, nread);
total_read += nread;
cryst->mcs_read_off += nread;
frame->data_frame.df_read_off += nread;
LSQ_DEBUG("read %zu bytes at offset %"PRIu64" on enc level %u", nread,
DF_ROFF(frame), cryst->mcs_enc_level);
if (DF_END(frame) == DF_ROFF(frame))
{
if (frame == conn->imc_last_in.frame)
conn->imc_last_in.frame = NULL;
else
{
TAILQ_REMOVE(&conn->imc_crypto_frames, frame, next_frame);
--conn->imc_n_crypto_frames;
conn->imc_crypto_frames_sz -= DF_SIZE(frame);
lsquic_packet_in_put(&conn->imc_enpub->enp_mm,
frame->packet_in);
lsquic_malo_put(frame);
}
}
if (nread < avail)
break;
}
if (total_read > 0)
return total_read;
else
{
/* CRYPTO streams never end, so zero bytes read always means
* EWOULDBLOCK
*/
errno = EWOULDBLOCK;
return -1;
}
}
static int
imico_stream_wantX (struct mini_crypto_stream *cryst, int bit, int is_want)
{
int old;
old = (cryst->mcs_flags & (1 << bit)) > 0;
cryst->mcs_flags &= ~(1 << bit);
cryst->mcs_flags |= !!is_want << bit;
return old;
}
static int
imico_stream_wantwrite (void *stream, int is_want)
{
return imico_stream_wantX(stream, MCSBIT_WANTWRITE, is_want);
}
static int
imico_stream_wantread (void *stream, int is_want)
{
return imico_stream_wantX(stream, MCSBIT_WANTREAD, is_want);
}
static enum enc_level
imico_stream_enc_level (void *stream)
{
struct mini_crypto_stream *const cryst = stream;
return cryst->mcs_enc_level;
}
static const struct crypto_stream_if crypto_stream_if =
{
.csi_write = imico_stream_write,
.csi_flush = imico_stream_flush,
.csi_readf = imico_stream_readf,
.csi_wantwrite = imico_stream_wantwrite,
.csi_wantread = imico_stream_wantread,
.csi_enc_level = imico_stream_enc_level,
};
static int
is_first_packet_ok (const struct lsquic_packet_in *packet_in,
size_t udp_payload_size)
{
if (udp_payload_size < IQUIC_MIN_INIT_PACKET_SZ)
{
/* [draft-ietf-quic-transport-24] Section 14 */
LSQ_LOG1(LSQ_LOG_DEBUG, "incoming UDP payload too small: %zu bytes",
udp_payload_size);
return 0;
}
/* TODO: Move decryption of the first packet into this function? */
return 1; /* TODO */
}
static void
imico_peer_addr_validated (struct ietf_mini_conn *conn, const char *how)
{
if (!(conn->imc_flags & IMC_ADDR_VALIDATED))
{
conn->imc_flags |= IMC_ADDR_VALIDATED;
LSQ_DEBUG("peer address validated (%s)", how);
}
}
struct lsquic_conn *
lsquic_mini_conn_ietf_new (struct lsquic_engine_public *enpub,
struct lsquic_packet_in *packet_in,
enum lsquic_version version, int is_ipv4, const lsquic_cid_t *odcid,
size_t udp_payload_size)
{
struct ietf_mini_conn *conn;
enc_session_t *enc_sess;
enum enc_level i;
const struct enc_session_funcs_iquic *esfi;
unsigned char rand_nybble;
if (!is_first_packet_ok(packet_in, udp_payload_size))
return NULL;
packet_in->pi_flags |= PI_FIRST_INIT;
conn = lsquic_malo_get(enpub->enp_mm.malo.mini_conn_ietf);
if (!conn)
{
LSQ_LOG1(LSQ_LOG_WARN, "cannot allocate mini connection: %s",
strerror(errno));
return NULL;
}
memset(conn, 0, sizeof(*conn));
conn->imc_conn.cn_if = &mini_conn_ietf_iface;
conn->imc_conn.cn_cces = conn->imc_cces;
conn->imc_conn.cn_n_cces = sizeof(conn->imc_cces)
/ sizeof(conn->imc_cces[0]);
conn->imc_cces[0].cce_cid = packet_in->pi_dcid;
conn->imc_cces[0].cce_flags = CCE_USED;
conn->imc_conn.cn_cces_mask = 1;
lsquic_scid_from_packet_in(packet_in, &conn->imc_path.np_dcid);
/* 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(enpub->enp_gen_scid_ctx, &conn->imc_conn,
&conn->imc_conn.cn_cces[1].cce_cid, enpub->enp_settings.es_scid_len);
if (conn->imc_path.np_dcid.len)
conn->imc_conn.cn_logid = conn->imc_path.np_dcid;
else
conn->imc_conn.cn_logid = conn->imc_conn.cn_cces[1].cce_cid;
LSQ_DEBUGC("recv SCID from client %"CID_FMT, CID_BITS(&conn->imc_cces[0].cce_cid));
LSQ_DEBUGC("recv DCID from client %"CID_FMT, CID_BITS(&conn->imc_path.np_dcid));
LSQ_DEBUGC("generated SCID %"CID_FMT" at index %u, switching to it",
CID_BITS(&conn->imc_conn.cn_cces[1].cce_cid), 1);
conn->imc_conn.cn_cces[1].cce_flags = CCE_SEQNO | CCE_USED;
conn->imc_conn.cn_cces_mask |= 1u << 1;
conn->imc_conn.cn_cur_cce_idx = 1;
conn->imc_conn.cn_flags = LSCONN_MINI|LSCONN_IETF|LSCONN_SERVER;
conn->imc_conn.cn_version = version;
for (i = 0; i < N_ENC_LEVS; ++i)
{
conn->imc_streams[i].mcs_enc_level = i;
conn->imc_stream_ps[i] = &conn->imc_streams[i];
}
rand_nybble = lsquic_crand_get_nybble(enpub->enp_crand);
if (rand_nybble == 0)
{
/* Use trechist for about one out of every sixteen connections so
* that the code does not grow stale.
*/
LSQ_DEBUG("using trechist");
conn->imc_flags |= IMC_TRECHIST;
conn->imc_recvd_packnos.trechist.hist_elems
= malloc(TRECHIST_SIZE * IMICO_N_PNS);
if (!conn->imc_recvd_packnos.trechist.hist_elems)
{
LSQ_WARN("cannot allocate trechist elems");
return NULL;
}
}
esfi = select_esf_iquic_by_ver(version);
if (version > LSQVER_ID27)
enc_sess = esfi->esfi_create_server(enpub, &conn->imc_conn,
&packet_in->pi_dcid, conn->imc_stream_ps, &crypto_stream_if,
odcid ? odcid : &conn->imc_cces[0].cce_cid,
&conn->imc_path.np_dcid,
odcid ? &conn->imc_cces[0].cce_cid : NULL);
else
enc_sess = esfi->esfi_create_server(enpub, &conn->imc_conn,
&packet_in->pi_dcid, conn->imc_stream_ps, &crypto_stream_if,
odcid, &conn->imc_path.np_dcid, NULL);
if (!enc_sess)
{
lsquic_malo_put(conn);
return NULL;
}
conn->imc_enpub = enpub;
conn->imc_expire = packet_in->pi_received +
enpub->enp_settings.es_handshake_to;
if (enpub->enp_settings.es_base_plpmtu)
conn->imc_path.np_pack_size = enpub->enp_settings.es_base_plpmtu;
else if (is_ipv4)
conn->imc_path.np_pack_size = IQUIC_MAX_IPv4_PACKET_SZ;
else
conn->imc_path.np_pack_size = IQUIC_MAX_IPv6_PACKET_SZ;
conn->imc_conn.cn_pf = select_pf_by_ver(version);
conn->imc_conn.cn_esf.i = esfi;
conn->imc_conn.cn_enc_session = enc_sess;
conn->imc_conn.cn_esf_c = select_esf_common_by_ver(version);
TAILQ_INIT(&conn->imc_packets_out);
TAILQ_INIT(&conn->imc_app_packets);
TAILQ_INIT(&conn->imc_crypto_frames);
if (odcid)
imico_peer_addr_validated(conn, "odcid");
#if LSQUIC_DEVEL
{
const char *const s = getenv("LSQUIC_LOSE_0RTT");
if (s && atoi(s))
{
LSQ_DEBUG("will lose 0-RTT packets (via env variable)");
conn->imc_delayed_packets_count = UCHAR_MAX;
}
}
#endif
conn->imc_long_header_sz = 1 /* Type */
+ 4 /* Version */
+ 1 /* DCIL */
+ conn->imc_path.np_dcid.len
+ 1 /* SCIL */
+ CN_SCID(&conn->imc_conn)->len
+ 0 /* HSK packet by server has no token */
+ 2 /* Always use two bytes to encode payload length */
+ 1 /* mini conn packet number < 256 */
+ IQUIC_TAG_LEN
;
conn->imc_hello_pkt_remain = conn->imc_path.np_pack_size
- conn->imc_long_header_sz - 1 /* token len */;
LSQ_DEBUG("created mini connection object %p; max packet size=%hu",
conn, conn->imc_path.np_pack_size);
return &conn->imc_conn;
}
static void
ietf_mini_conn_ci_client_call_on_new (struct lsquic_conn *lconn)
{
assert(0);
}
static void
ietf_mini_conn_ci_destroy (struct lsquic_conn *lconn)
{
struct ietf_mini_conn *conn = (struct ietf_mini_conn *) lconn;
struct lsquic_packet_out *packet_out;
struct lsquic_packet_in *packet_in;
struct stream_frame *frame;
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);
}
while ((packet_in = TAILQ_FIRST(&conn->imc_app_packets)))
{
TAILQ_REMOVE(&conn->imc_app_packets, packet_in, pi_next);
lsquic_packet_in_put(&conn->imc_enpub->enp_mm, packet_in);
}
while ((frame = TAILQ_FIRST(&conn->imc_crypto_frames)))
{
TAILQ_REMOVE(&conn->imc_crypto_frames, frame, next_frame);
lsquic_packet_in_put(&conn->imc_enpub->enp_mm, frame->packet_in);
lsquic_malo_put(frame);
}
if (lconn->cn_enc_session)
lconn->cn_esf.i->esfi_destroy(lconn->cn_enc_session);
LSQ_DEBUG("ietf_mini_conn_ci_destroyed");
if (conn->imc_flags & IMC_TRECHIST)
free(conn->imc_recvd_packnos.trechist.hist_elems);
lsquic_malo_put(conn);
}
static struct lsquic_engine *
ietf_mini_conn_ci_get_engine (struct lsquic_conn *lconn)
{
struct ietf_mini_conn *conn = (struct ietf_mini_conn *) lconn;
return conn->imc_enpub->enp_engine;
}
static void
ietf_mini_conn_ci_hsk_done (struct lsquic_conn *lconn,
enum lsquic_hsk_status status)
{
struct ietf_mini_conn *conn = (struct ietf_mini_conn *) lconn;
switch (status)
{
case LSQ_HSK_OK:
case LSQ_HSK_RESUMED_OK:
conn->imc_flags |= IMC_HSK_OK;
conn->imc_conn.cn_flags |= LSCONN_HANDSHAKE_DONE;
LSQ_DEBUG("handshake OK");
break;
default:
assert(0);
/* fall-through */
case LSQ_HSK_FAIL:
conn->imc_flags |= IMC_HSK_FAILED|IMC_ERROR;
LSQ_INFO("handshake failed");
break;
}
}
static void
ietf_mini_conn_ci_tls_alert (struct lsquic_conn *lconn, uint8_t alert)
{
struct ietf_mini_conn *conn = (struct ietf_mini_conn *) lconn;
LSQ_DEBUG("got TLS alert %"PRIu8, alert);
conn->imc_flags |= IMC_ERROR|IMC_TLS_ALERT;
conn->imc_tls_alert = alert;
}
/* A mini connection is only tickable if it has unsent packets. This can
* occur when packet sending is delayed.
*
* Otherwise, a mini connection is not tickable: Either there are incoming
* packets, in which case, the connection is going to be ticked, or there is
* an alarm pending, in which case it will be handled via the attq.
*/
static int
ietf_mini_conn_ci_is_tickable (struct lsquic_conn *lconn)
{
struct ietf_mini_conn *const conn = (struct ietf_mini_conn *) lconn;
const struct lsquic_packet_out *packet_out;
size_t packet_size;
if ((conn->imc_enpub->enp_flags & ENPUB_CAN_SEND)
&& !(conn->imc_flags & IMC_AMP_CAPPED))
TAILQ_FOREACH(packet_out, &conn->imc_packets_out, po_next)
if (!(packet_out->po_flags & PO_SENT))
{
packet_size = lsquic_packet_out_total_sz(lconn, packet_out);
return imico_can_send(conn, packet_size);
}
return 0;
}
static int
imico_can_send (const struct ietf_mini_conn *conn, size_t size)
{
return (conn->imc_flags & IMC_ADDR_VALIDATED)
|| conn->imc_bytes_in * 3 >= conn->imc_bytes_out + size
;
}
static lsquic_time_t
imico_rechist_largest_recv (void *rechist_ctx);
static int
imico_build_ack_frame (struct ietf_mini_conn *conn, enum packnum_space pns,
lsquic_time_t now, unsigned char *outbuf,
size_t outbuf_sz, lsquic_packno_t *ack2ed)
{
struct ietf_mini_rechist rechist;
uint64_t ecn_counts_buf[4];
const uint64_t *ecn_counts;
int not_used_has_missing, len;
if (conn->imc_incoming_ecn)
{
ecn_counts_buf[0] = conn->imc_ecn_counts_in[pns][0];
ecn_counts_buf[1] = conn->imc_ecn_counts_in[pns][1];
ecn_counts_buf[2] = conn->imc_ecn_counts_in[pns][2];
ecn_counts_buf[3] = conn->imc_ecn_counts_in[pns][3];
ecn_counts = ecn_counts_buf;
}
else
ecn_counts = NULL;
lsquic_imico_rechist_init(&rechist, conn, pns);
len = conn->imc_conn.cn_pf->pf_gen_ack_frame(outbuf, outbuf_sz,
lsquic_imico_rechist_first,
lsquic_imico_rechist_next, imico_rechist_largest_recv, &rechist,
now, ¬_used_has_missing, ack2ed, ecn_counts);
if (len < 0)
{
LSQ_WARN("could not generate ACK frame");
return -1;
}
EV_LOG_GENERATED_ACK_FRAME(LSQUIC_LOG_CONN_ID, conn->imc_conn.cn_pf,
outbuf, len);
return len;
}
static void
remove_ack_frame (struct ietf_mini_conn *conn,
struct lsquic_packet_out *packet_out)
{
if (packet_out->po_regen_sz == 0)
return;
int l = packet_out->po_data_sz - packet_out->po_regen_sz
- packet_out->po_padding_sz;
memmove(packet_out->po_data ,
packet_out->po_data + packet_out->po_regen_sz, l);
memset(packet_out->po_data + l, 0, packet_out->po_regen_sz);
packet_out->po_padding_sz += packet_out->po_regen_sz;
packet_out->po_regen_sz = 0;
}
// static void
// update_packet_ack (struct ietf_mini_conn *conn, enum packnum_space pns,
// struct lsquic_packet_out *packet_out)
// {
// int len, need;
// unsigned char outbuf[1024];
// size_t outbuf_sz = sizeof(outbuf);
// lsquic_packno_t ack2ed;
//
// len = imico_build_ack_frame(conn, pns, lsquic_time_now(), outbuf,
// outbuf_sz, &ack2ed);
// if (len > packet_out->po_regen_sz + packet_out->po_padding_sz)
// return;
// need = len + !!(packet_out->po_frame_types & (1 << QUIC_FRAME_PING));
// LSQ_DEBUG("update leading ACK frame for packet %"PRIu64
// ", regen_sz: %hd, padding_sz: %hd, need: %d, len: %d",
// packet_out->po_packno, packet_out->po_regen_sz,
// packet_out->po_padding_sz, need, len);
//
// if (need != packet_out->po_regen_sz)
// {
// int l = packet_out->po_data_sz - packet_out->po_regen_sz
// - packet_out->po_padding_sz;
// memmove(packet_out->po_data + need,
// packet_out->po_data + packet_out->po_regen_sz, l);
// if (packet_out->po_padding_sz)
// {
// if (need < packet_out->po_regen_sz)
// memset(packet_out->po_data + need + l, 0,
// packet_out->po_regen_sz - need);
// packet_out->po_padding_sz += packet_out->po_regen_sz - need;
// }
// else
// packet_out->po_data_sz -= packet_out->po_regen_sz - need;
// packet_out->po_regen_sz = need;
// }
// memmove(packet_out->po_data, outbuf, len);
// packet_out->po_frame_types |= 1 << QUIC_FRAME_ACK;
// packet_out->po_ack2ed = ack2ed;
// }
static struct lsquic_packet_out *
ietf_mini_conn_ci_next_packet_to_send (struct lsquic_conn *lconn,
const struct to_coal *to_coal)
{
struct ietf_mini_conn *conn = (struct ietf_mini_conn *) lconn;
struct lsquic_packet_out *packet_out;
size_t packet_size;
TAILQ_FOREACH(packet_out, &conn->imc_packets_out, po_next)
{
if (packet_out->po_flags & PO_SENT)
continue;
/* [draft-ietf-quic-transport-32] Section 14.1:
" a server MUST expand the payload of all UDP datagrams carrying
" ack-eliciting Initial packets to at least the smallest allowed
" maximum datagram size of 1200 bytes.
*/
if (packet_out->po_header_type == HETY_INITIAL)
{
if (packet_out->po_frame_types & (1 << QUIC_FRAME_ACK)
&& packet_out->po_retx_cnt > 0)
{
remove_ack_frame(conn, packet_out);
}
if (packet_out->po_retx_cnt > 0
&& !imico_can_send(conn, IQUIC_MAX_IPv4_PACKET_SZ))
{
conn->imc_flags |= IMC_AMP_CAPPED;
LSQ_DEBUG("cannot send INIT packet #%"PRIu64" without "
"enough quota", packet_out->po_packno);
return NULL;
}
// NOTE: do not padd INIT packet only, here, instead pad the coalesced
// later, can save one packet, more efficient.
// if (!(packet_out->po_frame_types & (1 << QUIC_FRAME_PADDING))
// && (packet_out->po_frame_types & IQUIC_FRAME_ACKABLE_MASK)
// && IQUIC_MIN_INIT_PACKET_SZ > packet_out->po_data_sz)
// {
// size_t pad_size;
//
// pad_size = IQUIC_MIN_INIT_PACKET_SZ - packet_out->po_data_sz;
// LSQ_DEBUG("generated PADDING frame: %zd bytes for packet %"PRIu64,
// pad_size, packet_out->po_packno);
// imico_zero_pad(packet_out, pad_size);
// }
}
packet_size = lsquic_packet_out_total_sz(lconn, packet_out);
if (!(to_coal
&& (packet_size + to_coal->prev_sz_sum
> conn->imc_path.np_pack_size
|| !lsquic_packet_out_equal_dcids(to_coal->prev_packet, packet_out))
))
{
if (!imico_can_send(conn, packet_size))
{
conn->imc_flags |= IMC_AMP_CAPPED;
LSQ_DEBUG("cannot send packet %"PRIu64" of size %zu: client "
"address has not been validated", packet_out->po_packno,
packet_size);
return NULL;
}
packet_out->po_flags |= PO_SENT;
conn->imc_bytes_out += packet_size;
if (!to_coal)
LSQ_DEBUG("packet_to_send: %"PRIu64, packet_out->po_packno);
else
LSQ_DEBUG("packet_to_send: %"PRIu64" (coalesced)",
packet_out->po_packno);
return packet_out;
}
else
return NULL;
}
return NULL;
}
static int
imico_calc_retx_timeout (const struct ietf_mini_conn *conn)
{
lsquic_time_t to;
to = lsquic_rtt_stats_get_srtt(&conn->imc_rtt_stats);
if (to)
{
to += to / 2;
if (to < 10000)
to = 10000;
}
else
to = 300000;
return to;
}
static lsquic_time_t
ietf_mini_conn_ci_next_tick_time (struct lsquic_conn *lconn, unsigned *why)
{
struct ietf_mini_conn *conn = (struct ietf_mini_conn *) lconn;
const struct lsquic_packet_out *packet_out;
lsquic_time_t exp_time, retx_time;
exp_time = conn->imc_expire;
TAILQ_FOREACH(packet_out, &conn->imc_packets_out, po_next)
if (packet_out->po_flags & PO_SENT)
{
retx_time = packet_out->po_sent + (imico_calc_retx_timeout(conn)
<< (packet_out->po_retx_cnt >> 1));
if (retx_time < exp_time)
{
*why = N_AEWS + AL_RETX_HSK;
return retx_time;
}
else
break;
}
*why = AEW_MINI_EXPIRE;
return exp_time;
}
#define IMICO_PROC_FRAME_ARGS \
struct ietf_mini_conn *conn, struct lsquic_packet_in *packet_in, \
const unsigned char *p, size_t len
static void
imico_dispatch_stream_events (struct ietf_mini_conn *conn)
{
enum enc_level i;
for (i = 0; i < N_ENC_LEVS; ++i)
if ((conn->imc_streams[i].mcs_flags & (MCS_CREATED|MCS_WANTREAD))
== (MCS_CREATED|MCS_WANTREAD))
{
LSQ_DEBUG("dispatch read events on level #%u", i);
lsquic_mini_cry_sm_if.on_read((void *) &conn->imc_streams[i],
conn->imc_conn.cn_enc_session);
}
for (i = 0; i < N_ENC_LEVS; ++i)
if ((conn->imc_streams[i].mcs_flags & (MCS_CREATED|MCS_WANTWRITE))
== (MCS_CREATED|MCS_WANTWRITE))
{
LSQ_DEBUG("dispatch write events on level #%u", i);
lsquic_mini_cry_sm_if.on_write((void *) &conn->imc_streams[i],
conn->imc_conn.cn_enc_session);
}
}
static int
imico_stash_stream_frame (struct ietf_mini_conn *conn,
enum enc_level enc_level, struct lsquic_packet_in *packet_in,
const struct stream_frame *frame)
{
struct stream_frame *copy;
if (conn->imc_n_crypto_frames >= IMICO_MAX_STASHED_FRAMES)
{
LSQ_INFO("cannot stash more CRYPTO frames, at %hhu already, while max "
"is %u", conn->imc_n_crypto_frames, IMICO_MAX_STASHED_FRAMES);
return -1;
}
if (conn->imc_crypto_frames_sz + DF_SIZE(frame) > IMICO_MAX_BUFFERED_CRYPTO)
{
LSQ_INFO("cannot stash more than %u bytes of CRYPTO frames",
IMICO_MAX_BUFFERED_CRYPTO);
return -1;
}
copy = lsquic_malo_get(conn->imc_enpub->enp_mm.malo.stream_frame);
if (!copy)
{
LSQ_INFO("could not allocate stream frame for stashing");
return -1;
}
*copy = *frame;
copy->packet_in = lsquic_packet_in_get(packet_in);
copy->stream_id = enc_level;
TAILQ_INSERT_TAIL(&conn->imc_crypto_frames, copy, next_frame);
++conn->imc_n_crypto_frames;
conn->imc_crypto_frames_sz += DF_SIZE(frame);
return 0;
}
static unsigned
imico_process_crypto_frame (IMICO_PROC_FRAME_ARGS)
{
int parsed_len;
enum enc_level enc_level, i;
struct stream_frame stream_frame;
parsed_len = conn->imc_conn.cn_pf->pf_parse_crypto_frame(p, len,
&stream_frame);
if (parsed_len < 0)
{
conn->imc_flags |= IMC_PARSE_FAILED;
return 0;
}
enc_level = lsquic_packet_in_enc_level(packet_in);
EV_LOG_CRYPTO_FRAME_IN(LSQUIC_LOG_CONN_ID, &stream_frame, enc_level);
if (conn->imc_streams[enc_level].mcs_read_off >= DF_OFF(&stream_frame)
&& conn->imc_streams[enc_level].mcs_read_off < DF_END(&stream_frame))
LSQ_DEBUG("Got CRYPTO frame for enc level #%u", enc_level);
else if (conn->imc_streams[enc_level].mcs_read_off < DF_OFF(&stream_frame))
{
LSQ_DEBUG("Can't read CRYPTO frame on enc level #%u at offset %"PRIu64
" yet -- stash", enc_level, DF_OFF(&stream_frame));
if (0 == imico_stash_stream_frame(conn, enc_level, packet_in,
&stream_frame))
return parsed_len;
else
return 0;
}
else
{
LSQ_DEBUG("Got duplicate CRYPTO frame for enc level #%u -- ignore",
enc_level);
return parsed_len;
}
if (!(conn->imc_flags & IMC_ENC_SESS_INITED))
{
if (0 != conn->imc_conn.cn_esf.i->esfi_init_server(
conn->imc_conn.cn_enc_session))
return 0;
conn->imc_flags |= IMC_ENC_SESS_INITED;
}
if (!(conn->imc_streams[enc_level].mcs_flags & MCS_CREATED))
{
LSQ_DEBUG("creating stream on level #%u", enc_level);
conn->imc_streams[enc_level].mcs_flags |= MCS_CREATED;
lsquic_mini_cry_sm_if.on_new_stream(conn->imc_conn.cn_enc_session,
(void *) &conn->imc_streams[enc_level]);
}
/* Assume that receiving a CRYPTO frame at a higher level means that we
* no longer want to read from a lower level.
*/
for (i = 0; i < enc_level; ++i)
conn->imc_streams[i].mcs_flags &= ~MCS_WANTREAD;
conn->imc_last_in.frame = &stream_frame;
conn->imc_last_in.enc_level = enc_level;
imico_dispatch_stream_events(conn);
conn->imc_last_in.frame = NULL;
if (DF_ROFF(&stream_frame) < DF_END(&stream_frame))
{
/* This is an odd condition, but let's handle it just in case */
LSQ_DEBUG("New CRYPTO frame on enc level #%u not fully read -- stash",
enc_level);
if (0 != imico_stash_stream_frame(conn, enc_level, packet_in,
&stream_frame))
return 0;
}
return parsed_len;
}
static ptrdiff_t
imico_count_zero_bytes (const unsigned char *p, size_t len)
{
const unsigned char *const end = p + len;
while (p < end && 0 == *p)
++p;
return len - (end - p);
}
static unsigned
imico_process_padding_frame (IMICO_PROC_FRAME_ARGS)
{
len = (size_t) imico_count_zero_bytes(p, len);
EV_LOG_PADDING_FRAME_IN(LSQUIC_LOG_CONN_ID, len);
return len;
}
static void
imico_take_rtt_sample (struct ietf_mini_conn *conn,
const struct lsquic_packet_out *packet_out,
lsquic_time_t now, lsquic_time_t lack_delta)
{
assert(packet_out->po_sent);
lsquic_time_t measured_rtt = now - packet_out->po_sent;
if (lack_delta < measured_rtt)
{
lsquic_rtt_stats_update(&conn->imc_rtt_stats, measured_rtt, lack_delta);
LSQ_DEBUG("srtt: %"PRIu64" usec, var: %"PRIu64,
lsquic_rtt_stats_get_srtt(&conn->imc_rtt_stats),
lsquic_rtt_stats_get_rttvar(&conn->imc_rtt_stats));
}
}
static unsigned
imico_process_ack_frame (IMICO_PROC_FRAME_ARGS)
{
int parsed_len;
unsigned n;
lsquic_packet_out_t *packet_out, *next;
struct ack_info *acki;
lsquic_packno_t packno;
lsquic_time_t warn_time;
packno_set_t acked;
enum packnum_space pns;
uint8_t ack_exp;
if (conn->imc_flags & IMC_HAVE_TP)
ack_exp = conn->imc_ack_exp;
else
ack_exp = TP_DEF_ACK_DELAY_EXP; /* Odd: no transport params yet? */
acki = conn->imc_enpub->enp_mm.acki;
parsed_len = conn->imc_conn.cn_pf->pf_parse_ack_frame(p, len, acki,
ack_exp);
if (parsed_len < 0)
{
conn->imc_flags |= IMC_PARSE_FAILED;
return 0;
}
pns = lsquic_hety2pns[ packet_in->pi_header_type ];
acked = 0;
for (n = 0; n < acki->n_ranges; ++n)
{
if (acki->ranges[n].high <= MAX_PACKETS)
{
acked |= (1ULL << acki->ranges[n].high)
| ((1ULL << acki->ranges[n].high) - 1);
acked &= ~((1ULL << acki->ranges[n].low) - 1);
}
else
{
packno = acki->ranges[n].high;
goto err_never_sent;
}
}
if (acked & ~conn->imc_sent_packnos)
{
packno = highest_bit_set(acked & ~conn->imc_sent_packnos);
goto err_never_sent;
}
EV_LOG_ACK_FRAME_IN(LSQUIC_LOG_CONN_ID, acki);
for (packet_out = TAILQ_FIRST(&conn->imc_packets_out); packet_out;
packet_out = next)
{
next = TAILQ_NEXT(packet_out, po_next);
if ((1ULL << packet_out->po_packno) & acked)
{
assert(lsquic_packet_out_pns(packet_out) == pns);
LSQ_DEBUG("Got ACK for packet %"PRIu64, packet_out->po_packno);
if (packet_out->po_packno == largest_acked(acki))
imico_take_rtt_sample(conn, packet_out,
packet_in->pi_received, acki->lack_delta);
TAILQ_REMOVE(&conn->imc_packets_out, packet_out, po_next);
imico_destroy_packet(conn, packet_out);
}
}
if (conn->imc_sent_packnos & ~conn->imc_acked_packnos[pns] & acked)
{
LSQ_DEBUG("Newly acked packets, reset handshake count");
conn->imc_hsk_count = 0;
}
conn->imc_acked_packnos[pns] |= acked;
return parsed_len;
err_never_sent:
warn_time = lsquic_time_now();
if (0 == conn->imc_enpub->enp_last_warning[WT_ACKPARSE_MINI]
|| conn->imc_enpub->enp_last_warning[WT_ACKPARSE_MINI]
+ WARNING_INTERVAL < warn_time)
{
conn->imc_enpub->enp_last_warning[WT_ACKPARSE_MINI] = warn_time;
LSQ_WARN("packet %"PRIu64" (pns: %u) was never sent", packno, pns);
}
else
LSQ_DEBUG("packet %"PRIu64" (pns: %u) was never sent", packno, pns);
return 0;
}
static unsigned
imico_process_ping_frame (IMICO_PROC_FRAME_ARGS)
{
LSQ_DEBUG("got a PING frame, do nothing");
EV_LOG_PING_FRAME_IN(LSQUIC_LOG_CONN_ID);
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)
{
conn->imc_flags |= IMC_PARSE_FAILED;
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)
{
LSQ_DEBUG("invalid frame %u (%s)", p[0],
frame_type_2_str[ conn->imc_conn.cn_pf->pf_parse_frame_type(p, len) ]);
return 0;
}
static unsigned
imico_process_invalid_frame_pv (IMICO_PROC_FRAME_ARGS)
{
LSQ_DEBUG("invalid frame %u (%s)", p[0],
frame_type_2_str[ conn->imc_conn.cn_pf->pf_parse_frame_type(p, len) ]);
ietf_mini_conn_ci_abort_error(&conn->imc_conn, 0, TEC_PROTOCOL_VIOLATION,
"protocol violation detected while processing frame");
return 0;
}
static unsigned (*const imico_process_frames[N_QUIC_FRAMES])
(IMICO_PROC_FRAME_ARGS) =
{
[QUIC_FRAME_PADDING] = imico_process_padding_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,
/* 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_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_BLOCKED] = imico_process_invalid_frame,
[QUIC_FRAME_STREAM_BLOCKED] = imico_process_invalid_frame,
[QUIC_FRAME_STREAMS_BLOCKED] = imico_process_invalid_frame,
[QUIC_FRAME_NEW_CONNECTION_ID] = imico_process_invalid_frame_pv,
[QUIC_FRAME_STOP_SENDING] = imico_process_invalid_frame,
[QUIC_FRAME_PATH_CHALLENGE] = imico_process_invalid_frame,
[QUIC_FRAME_PATH_RESPONSE] = imico_process_invalid_frame,
/* STREAM frame can only come in the App PNS and we delay those packets: */
[QUIC_FRAME_STREAM] = imico_process_invalid_frame,
[QUIC_FRAME_RETIRE_CONNECTION_ID] = imico_process_invalid_frame_pv,
[QUIC_FRAME_NEW_TOKEN] = imico_process_invalid_frame_pv,
[QUIC_FRAME_HANDSHAKE_DONE] = imico_process_invalid_frame_pv,
[QUIC_FRAME_ACK_FREQUENCY] = imico_process_invalid_frame,
[QUIC_FRAME_TIMESTAMP] = imico_process_invalid_frame,
};
static unsigned
imico_process_packet_frame (struct ietf_mini_conn *conn,
struct lsquic_packet_in *packet_in, const unsigned char *p, size_t len)
{
enum enc_level enc_level;
enum quic_frame_type type;
enc_level = lsquic_packet_in_enc_level(packet_in);
type = conn->imc_conn.cn_pf->pf_parse_frame_type(p, len);
if (lsquic_legal_frames_by_level[conn->imc_conn.cn_version][enc_level]
& (1 << type))
{
packet_in->pi_frame_types |= 1 << type;
return imico_process_frames[type](conn, packet_in, p, len);
}
else
{
LSQ_DEBUG("invalid frame %u at encryption level %s", type,
lsquic_enclev2str[enc_level]);
if (type == QUIC_FRAME_INVALID)
ietf_mini_conn_ci_abort_error(&conn->imc_conn, 0,
TEC_FRAME_ENCODING_ERROR,
"invalid frame %u at encryption level %s",
type, lsquic_enclev2str[enc_level]);
else
ietf_mini_conn_ci_abort_error(&conn->imc_conn, 0,
TEC_PROTOCOL_VIOLATION,
"protcol violation for invalid frame %u at encryption level %s",
type, lsquic_enclev2str[enc_level]);
return 0;
}
}
static int
imico_parse_regular_packet (struct ietf_mini_conn *conn,
struct lsquic_packet_in *packet_in)
{
const unsigned char *p, *pend;
unsigned len;
p = packet_in->pi_data + packet_in->pi_header_sz;
pend = packet_in->pi_data + packet_in->pi_data_sz;
if (p >= pend)
{
ietf_mini_conn_ci_abort_error(&conn->imc_conn, 0,
TEC_PROTOCOL_VIOLATION,
"packet %"PRIu64" has no frames",
packet_in->pi_packno);
return -1;
}
while (p < pend)
{
len = imico_process_packet_frame(conn, packet_in, p, pend - p);
if (len > 0)
p += len;
else
return -1;
}
return 0;
}
static unsigned
highest_bit_set (unsigned long long sz)
{
#if __GNUC__
unsigned clz = __builtin_clzll(sz);
return 63 - clz;
#else
unsigned long y;
unsigned n;
n = 64;
y = sz >> 32; if (y) { n -= 32; sz = y; }
y = sz >> 16; if (y) { n -= 16; sz = y; }
y = sz >> 8; if (y) { n -= 8; sz = y; }
y = sz >> 4; if (y) { n -= 4; sz = y; }
y = sz >> 2; if (y) { n -= 2; sz = y; }
y = sz >> 1; if (y) return 63 - n + 2;
return 63 - n + sz;
#endif
}
static void
ignore_init (struct ietf_mini_conn *conn)
{
struct lsquic_packet_out *packet_out, *next;
unsigned count;
conn->imc_flags |= IMC_IGNORE_INIT;
conn->imc_flags &= ~(IMC_QUEUED_ACK_INIT << PNS_INIT);
count = 0;
for (packet_out = TAILQ_FIRST(&conn->imc_packets_out); packet_out;
packet_out = next)
{
next = TAILQ_NEXT(packet_out, po_next);
if (PNS_INIT == lsquic_packet_out_pns(packet_out))
{
TAILQ_REMOVE(&conn->imc_packets_out, packet_out, po_next);
imico_destroy_packet(conn, packet_out);
++count;
}
}
LSQ_DEBUG("henceforth, no Initial packets shall be sent or received; "
"destroyed %u packet%.*s", count, count != 1, "s");
int ext_to = conn->imc_enpub->enp_settings.es_idle_timeout * 1000000
- conn->imc_enpub->enp_settings.es_handshake_to;
if (ext_to > 0)
{
conn->imc_expire += ext_to;
LSQ_DEBUG("extend mini-conn expire time to %d seconds",
conn->imc_enpub->enp_settings.es_idle_timeout);
}
}
static void
imico_maybe_delay_processing (struct ietf_mini_conn *conn,
struct lsquic_packet_in *packet_in)
{
unsigned max_delayed;
if (conn->imc_flags & IMC_ADDR_VALIDATED)
max_delayed = IMICO_MAX_DELAYED_PACKETS_VALIDATED;
else
max_delayed = IMICO_MAX_DELAYED_PACKETS_UNVALIDATED;
if (conn->imc_delayed_packets_count < max_delayed)
{
++conn->imc_delayed_packets_count;
lsquic_packet_in_upref(packet_in);
TAILQ_INSERT_TAIL(&conn->imc_app_packets, packet_in, pi_next);
LSQ_DEBUG("delay processing of packet (now delayed %hhu)",
conn->imc_delayed_packets_count);
}
else
LSQ_DEBUG("drop packet, already delayed %hhu packets",
conn->imc_delayed_packets_count);
}
/* [draft-ietf-quic-transport-30] Section 8.1:
" Additionally, a server MAY consider the client address validated if
" the client uses a connection ID chosen by the server and the
" connection ID contains at least 64 bits of entropy.
*
* We use RAND_bytes() to generate SCIDs, so it's all entropy.
*/
static void
imico_maybe_validate_by_dcid (struct ietf_mini_conn *conn,
const lsquic_cid_t *dcid)
{
unsigned i;
if (dcid->len >= 8)
/* Generic code with unnecessary loop as future-proofing */
for (i = 0; i < conn->imc_conn.cn_n_cces; ++i)
if ((conn->imc_conn.cn_cces_mask & (1 << i))
&& (conn->imc_conn.cn_cces[i].cce_flags & CCE_SEQNO)
&& LSQUIC_CIDS_EQ(&conn->imc_conn.cn_cces[i].cce_cid, dcid))
{
imico_peer_addr_validated(conn, "dcid/scid + entropy");
return;
}
}
static int
imico_received_packet_is_dup (struct ietf_mini_conn *conn,
enum packnum_space pns, lsquic_packno_t packno)
{
if (conn->imc_flags & IMC_TRECHIST)
return lsquic_trechist_contains(
conn->imc_recvd_packnos.trechist.hist_masks[pns],
conn->imc_recvd_packnos.trechist.hist_elems
+ TRECHIST_MAX_RANGES * pns, packno);
else
return !!(conn->imc_recvd_packnos.bitmasks[pns] & (1ULL << packno));
}
static int
imico_packno_is_largest (struct ietf_mini_conn *conn,
enum packnum_space pns, lsquic_packno_t packno)
{
if (conn->imc_flags & IMC_TRECHIST)
return 0 == conn->imc_recvd_packnos.trechist.hist_masks[pns]
|| packno > lsquic_trechist_max(
conn->imc_recvd_packnos.trechist.hist_masks[pns],
conn->imc_recvd_packnos.trechist.hist_elems
+ TRECHIST_MAX_RANGES * pns);
else
return 0 == conn->imc_recvd_packnos.bitmasks[pns]
|| packno > highest_bit_set(conn->imc_recvd_packnos.bitmasks[pns]);
}
static void
imico_record_recvd_packno (struct ietf_mini_conn *conn,
enum packnum_space pns, lsquic_packno_t packno)
{
if (conn->imc_flags & IMC_TRECHIST)
{
if (0 != lsquic_trechist_insert(
&conn->imc_recvd_packnos.trechist.hist_masks[pns],
conn->imc_recvd_packnos.trechist.hist_elems
+ TRECHIST_MAX_RANGES * pns, packno))
{
LSQ_INFO("too many ranges for trechist to hold or range too wide");
conn->imc_flags |= IMC_ERROR;
}
}
else
conn->imc_recvd_packnos.bitmasks[pns] |= 1ULL << packno;
}
static int
imico_switch_to_trechist (struct ietf_mini_conn *conn)
{
uint32_t masks[IMICO_N_PNS];
enum packnum_space pns;
struct trechist_elem *elems;
struct ietf_mini_rechist iter;
elems = malloc(TRECHIST_SIZE * N_PNS);
if (!elems)
{
LSQ_WARN("cannot allocate trechist elems");
return -1;
}
for (pns = 0; pns < IMICO_N_PNS; ++pns)
if (conn->imc_recvd_packnos.bitmasks[pns])
{
lsquic_imico_rechist_init(&iter, conn, pns);
lsquic_trechist_copy_ranges(&masks[pns],
elems + TRECHIST_MAX_RANGES * pns, &iter,
lsquic_imico_rechist_first,
lsquic_imico_rechist_next);
}
else
masks[pns] = 0;
memcpy(conn->imc_recvd_packnos.trechist.hist_masks, masks, sizeof(masks));
conn->imc_recvd_packnos.trechist.hist_elems = elems;
conn->imc_flags |= IMC_TRECHIST;
LSQ_DEBUG("switched to trechist");
return 0;
}
static int
imico_generate_handshake_done (struct ietf_mini_conn *conn);
/* Only a single packet is supported */
static void
ietf_mini_conn_ci_packet_in (struct lsquic_conn *lconn,
struct lsquic_packet_in *packet_in)
{
struct ietf_mini_conn *conn = (struct ietf_mini_conn *) lconn;
enum dec_packin dec_packin;
enum packnum_space pns;
/* Update "bytes in" count as early as possible. From
* [draft-ietf-quic-transport-28] Section 8.1:
" For the purposes of
" avoiding amplification prior to address validation, servers MUST
" count all of the payload bytes received in datagrams that are
" uniquely attributed to a single connection. This includes datagrams
" that contain packets that are successfully processed and datagrams
" that contain packets that are all discarded.
*/
conn->imc_bytes_in += packet_in->pi_data_sz;
conn->imc_flags &= ~IMC_AMP_CAPPED;
if (conn->imc_flags & IMC_ERROR)
{
LSQ_DEBUG("ignore incoming packet: connection is in error state");
return;
}
if (!(conn->imc_flags & IMC_ADDR_VALIDATED))
imico_maybe_validate_by_dcid(conn, &packet_in->pi_dcid);
pns = lsquic_hety2pns[ packet_in->pi_header_type ];
if (pns == PNS_INIT)
{
if (conn->imc_flags & IMC_IGNORE_INIT)
{
LSQ_DEBUG("ignore init packet"); /* Don't bother decrypting */
return;
}
if (packet_in->pi_pkt_size
&& packet_in->pi_pkt_size < IQUIC_MIN_INIT_PACKET_SZ)
{
LSQ_DEBUG("ignore init packet smaller than minimum size required");
/* Don't bother decrypting */
return;
}
}
dec_packin = lconn->cn_esf_c->esf_decrypt_packet(lconn->cn_enc_session,
conn->imc_enpub, &conn->imc_conn, packet_in);
switch (dec_packin)
{
case DECPI_OK:
break;
case DECPI_VIOLATION:
ietf_mini_conn_ci_abort_error(lconn, 0, TEC_PROTOCOL_VIOLATION,
"protocol violation detected while decrypting packet");
return;
case DECPI_NOT_YET:
imico_maybe_delay_processing(conn, packet_in);
return;
case DECPI_BADCRYPT:
if (packet_in->pi_flags & PI_FIRST_INIT)
{
LSQ_DEBUG("possible packet corruption, destroy mini-conn.");
conn->imc_flags |= IMC_ERROR | IMC_CLOSE_RECVD;
/* avoid adding CID to black list */
conn->imc_conn.cn_flags |= LSCONN_NO_BL;
}
//fall through
default:
LSQ_DEBUG("could not decrypt packet");
return;
}
EV_LOG_PACKET_IN(LSQUIC_LOG_CONN_ID, packet_in);
if (pns == PNS_APP)
{
imico_maybe_delay_processing(conn, packet_in);
return;
}
else if (pns == PNS_HSK)
{
if (!LSQUIC_CIDS_EQ(CN_SCID(&conn->imc_conn), &packet_in->pi_dcid))
{
ietf_mini_conn_ci_abort_error(lconn, 0, TEC_PROTOCOL_VIOLATION,
"protocol violation detected bad dcid for HSK pns");
return;
}
imico_peer_addr_validated(conn, "handshake PNS");
}
if (((conn->imc_flags >> IMCBIT_PNS_BIT_SHIFT) & 3) < pns)
{
conn->imc_flags &= ~(3 << IMCBIT_PNS_BIT_SHIFT);
conn->imc_flags |= pns << IMCBIT_PNS_BIT_SHIFT;
}
if (pns == PNS_HSK && !(conn->imc_flags & IMC_IGNORE_INIT))
ignore_init(conn);
if (packet_in->pi_packno > MAX_PACKETS
&& !(conn->imc_flags & IMC_TRECHIST))
{
if (0 != imico_switch_to_trechist(conn))
return;
}
if (imico_received_packet_is_dup(conn, pns, packet_in->pi_packno))
{
LSQ_DEBUG("duplicate packet %"PRIu64, packet_in->pi_packno);
return;
}
/* Update receive history before processing the packet: if there is an
* error, the connection is terminated and recording this packet number
* is helpful when it is printed along with other diagnostics in dtor.
*/
if (imico_packno_is_largest(conn, pns, packet_in->pi_packno))
conn->imc_largest_recvd[pns] = packet_in->pi_received;
imico_record_recvd_packno(conn, pns, packet_in->pi_packno);
if (!(conn->imc_flags & (IMC_QUEUED_ACK_INIT << pns)))
{
LSQ_DEBUG("queued ACK in %s", lsquic_pns2str[pns]);
conn->imc_flags |= IMC_QUEUED_ACK_INIT << pns;
}
++conn->imc_ecn_counts_in[pns][ lsquic_packet_in_ecn(packet_in) ];
conn->imc_incoming_ecn <<= 1;
conn->imc_incoming_ecn |= lsquic_packet_in_ecn(packet_in) != ECN_NOT_ECT;
if (0 != imico_parse_regular_packet(conn, packet_in))
{
LSQ_DEBUG("connection is now in error state");
conn->imc_flags |= IMC_ERROR;
return;
}
if (conn->imc_flags & IMC_HSK_OK)
{
if (lconn->cn_esf.i->esfi_in_init(lconn->cn_enc_session))
LSQ_DEBUG("still in init, defer HANDSHAKE_DONE");
else if (0 != imico_generate_handshake_done(conn))
conn->imc_flags |= IMC_ERROR;
}
}
static void
ietf_mini_conn_ci_packet_sent (struct lsquic_conn *lconn,
struct lsquic_packet_out *packet_out)
{
struct ietf_mini_conn *conn = (struct ietf_mini_conn *) lconn;
conn->imc_sent_packnos |= 1ULL << packet_out->po_packno;
conn->imc_ecn_packnos |= !!lsquic_packet_out_ecn(packet_out)
<< packet_out->po_packno;
#if 0
if (packet_out->po_frame_types & (1 << QUIC_FRAME_ACK))
{
assert(mc->mc_flags & MC_UNSENT_ACK);
mc->mc_flags &= ~MC_UNSENT_ACK;
}
#endif
if (packet_out->po_header_type == HETY_HANDSHAKE)
conn->imc_flags |= IMC_HSK_PACKET_SENT;
LSQ_DEBUG("%s: packet %"PRIu64" sent", __func__, packet_out->po_packno);
}
static void
ietf_mini_conn_ci_packet_not_sent (struct lsquic_conn *lconn,
struct lsquic_packet_out *packet_out)
{
struct ietf_mini_conn *conn = (struct ietf_mini_conn *) lconn;
size_t packet_size;
packet_out->po_flags &= ~PO_SENT;
packet_size = lsquic_packet_out_total_sz(lconn, packet_out);
conn->imc_bytes_out -= packet_size;
LSQ_DEBUG("%s: packet %"PRIu64" not sent", __func__, packet_out->po_packno);
}
static void
imico_return_enc_data (struct ietf_mini_conn *conn,
struct lsquic_packet_out *packet_out)
{
conn->imc_enpub->enp_pmi->pmi_return(conn->imc_enpub->enp_pmi_ctx,
conn->imc_path.np_peer_ctx, packet_out->po_enc_data,
lsquic_packet_out_ipv6(packet_out));
packet_out->po_flags &= ~PO_ENCRYPTED;
packet_out->po_enc_data = NULL;
}
static int
imico_repackage_packet (struct ietf_mini_conn *conn,
struct lsquic_packet_out *packet_out)
{
const lsquic_packno_t oldno = packet_out->po_packno;
const lsquic_packno_t packno = conn->imc_next_packno++;
if (packno > MAX_PACKETS)
return -1;
EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "schedule resend, repackage packet "
"#%"PRIu64" -> #%"PRIu64, oldno, packno);
packet_out->po_packno = packno;
packet_out->po_flags &= ~PO_SENT;
++packet_out->po_retx_cnt;
lsquic_packet_out_set_ecn(packet_out, imico_get_ecn(conn));
if (packet_out->po_flags & PO_ENCRYPTED)
imico_return_enc_data(conn, packet_out);
TAILQ_INSERT_TAIL(&conn->imc_packets_out, packet_out, po_next);
return 0;
}
static int
imico_handle_losses_and_have_unsent (struct ietf_mini_conn *conn,
lsquic_time_t now)
{
TAILQ_HEAD(, lsquic_packet_out) lost_packets =
TAILQ_HEAD_INITIALIZER(lost_packets);
const struct lsquic_conn *const lconn = &conn->imc_conn;
lsquic_packet_out_t *packet_out, *next;
lsquic_time_t retx_to = 0;
unsigned n_to_send = 0;
size_t packet_size;
for (packet_out = TAILQ_FIRST(&conn->imc_packets_out); packet_out;
packet_out = next)
{
next = TAILQ_NEXT(packet_out, po_next);
if (packet_out->po_flags & PO_SENT)
{
if (packet_out->po_frame_types & IQUIC_FRAME_RETX_MASK)
{
if (0 == retx_to)
retx_to = imico_calc_retx_timeout(conn);
if (conn->imc_hsk_count == 0)
packet_out->po_retx_cnt = 0;
if (packet_out->po_sent
+ (retx_to << (packet_out->po_retx_cnt >> 1)) < now)
{
LSQ_DEBUG("packet %"PRIu64" has been lost (rto: %"PRIu64")",
packet_out->po_packno,
retx_to << (packet_out->po_retx_cnt >> 1));
TAILQ_REMOVE(&conn->imc_packets_out, packet_out, po_next);
TAILQ_INSERT_TAIL(&lost_packets, packet_out, po_next);
}
}
else
{
TAILQ_REMOVE(&conn->imc_packets_out, packet_out, po_next);
imico_destroy_packet(conn, packet_out);
}
}
else if (packet_size = lsquic_packet_out_total_sz(lconn, packet_out),
imico_can_send(conn, packet_size))
++n_to_send;
else
break;
}
conn->imc_hsk_count += !TAILQ_EMPTY(&lost_packets);
while ((packet_out = TAILQ_FIRST(&lost_packets)))
{
TAILQ_REMOVE(&lost_packets, packet_out, po_next);
if (0 == imico_repackage_packet(conn, packet_out))
{
packet_size = lsquic_packet_out_total_sz(lconn, packet_out);
if (imico_can_send(conn, packet_size))
++n_to_send;
}
else
imico_destroy_packet(conn, packet_out);
}
return n_to_send > 0;
}
static int
imico_have_packets_to_send (struct ietf_mini_conn *conn, lsquic_time_t now)
{
return imico_handle_losses_and_have_unsent(conn, now);
}
void
lsquic_imico_rechist_init (struct ietf_mini_rechist *rechist,
const struct ietf_mini_conn *conn, enum packnum_space pns)
{
assert(pns < IMICO_N_PNS);
rechist->conn = conn;
rechist->pns = pns;
if (conn->imc_flags & IMC_TRECHIST)
lsquic_trechist_iter(&rechist->u.trechist_iter,
conn->imc_recvd_packnos.trechist.hist_masks[pns],
conn->imc_recvd_packnos.trechist.hist_elems + TRECHIST_MAX_RANGES * pns);
else
{
rechist->u.bitmask.cur_set = 0;
rechist->u.bitmask.cur_idx = 0;
}
}
static lsquic_time_t
imico_rechist_largest_recv (void *rechist_ctx)
{
struct ietf_mini_rechist *rechist = rechist_ctx;
return rechist->conn->imc_largest_recvd[ rechist->pns ];
}
static const struct lsquic_packno_range *
imico_bitmask_rechist_next (struct ietf_mini_rechist *rechist)
{
const struct ietf_mini_conn *conn = rechist->conn;
packno_set_t packnos;
int i;
packnos = rechist->u.bitmask.cur_set;
if (0 == packnos)
return NULL;
/* There may be a faster way to do this, but for now, we just want
* correctness.
*/
for (i = rechist->u.bitmask.cur_idx; i >= 0; --i)
if (packnos & (1ULL << i))
{
rechist->u.bitmask.range.low = i;
rechist->u.bitmask.range.high = i;
break;
}
assert(i >= 0); /* We must have hit at least one bit */
--i;
for ( ; i >= 0 && (packnos & (1ULL << i)); --i)
rechist->u.bitmask.range.low = i;
if (i >= 0)
{
rechist->u.bitmask.cur_set = packnos & ((1ULL << i) - 1);
rechist->u.bitmask.cur_idx = i;
}
else
rechist->u.bitmask.cur_set = 0;
LSQ_DEBUG("%s: return [%"PRIu64", %"PRIu64"]", __func__,
rechist->u.bitmask.range.low, rechist->u.bitmask.range.high);
return &rechist->u.bitmask.range;
}
const struct lsquic_packno_range *
lsquic_imico_rechist_next (void *rechist_ctx)
{
struct ietf_mini_rechist *rechist = rechist_ctx;
if (rechist->conn->imc_flags & IMC_TRECHIST)
return lsquic_trechist_next(&rechist->u.trechist_iter);
else
return imico_bitmask_rechist_next(rechist);
}
const struct lsquic_packno_range *
lsquic_imico_rechist_first (void *rechist_ctx)
{
struct ietf_mini_rechist *rechist = rechist_ctx;
if (rechist->conn->imc_flags & IMC_TRECHIST)
return lsquic_trechist_first(&rechist->u.trechist_iter);
else
{
rechist->u.bitmask.cur_set
= rechist->conn->imc_recvd_packnos.bitmasks[ rechist->pns ];
rechist->u.bitmask.cur_idx
= highest_bit_set(rechist->u.bitmask.cur_set);
return lsquic_imico_rechist_next(rechist_ctx);
}
}
static const enum header_type pns2hety[] =
{
[PNS_INIT] = HETY_INITIAL,
[PNS_HSK] = HETY_HANDSHAKE,
[PNS_APP] = HETY_SHORT,
};
static int
imico_generate_ping (struct ietf_mini_conn *conn,
struct lsquic_packet_out *packet_out)
{
int len;
len = conn->imc_conn.cn_pf->pf_gen_ping_frame(
packet_out->po_data + packet_out->po_data_sz,
lsquic_packet_out_avail(packet_out));
if (len > 0)
{
packet_out->po_frame_types |= 1 << QUIC_FRAME_PING;
packet_out->po_data_sz += len;
packet_out->po_regen_sz += len;
LSQ_DEBUG("wrote PING frame of size %d to packet %" PRIu64,
len, packet_out->po_packno);
}
return 0;
}
static int
imico_generate_ack (struct ietf_mini_conn *conn, enum packnum_space pns,
lsquic_time_t now)
{
struct lsquic_packet_out *packet_out;
enum header_type header_type;
int len;
header_type = pns2hety[pns];
packet_out = imico_get_packet_out(conn, header_type, 0);
if (!packet_out)
return -1;
len = imico_build_ack_frame(conn, pns, now,
packet_out->po_data + packet_out->po_data_sz,
lsquic_packet_out_avail(packet_out),
&packet_out->po_ack2ed);
if (len < 0)
return -1;
packet_out->po_frame_types |= 1 << QUIC_FRAME_ACK;
packet_out->po_data_sz += len;
packet_out->po_regen_sz += len;
conn->imc_flags &= ~(IMC_QUEUED_ACK_INIT << pns);
LSQ_DEBUG("wrote ACK frame of size %d in %s to packet %" PRIu64,
len, lsquic_pns2str[pns], packet_out->po_packno);
if (pns == PNS_INIT && conn->imc_hsk_count > 0)
imico_generate_ping(conn, packet_out);
return 0;
}
static int
imico_generate_acks (struct ietf_mini_conn *conn, lsquic_time_t now)
{
enum packnum_space pns;
for (pns = PNS_INIT; pns < IMICO_N_PNS; ++pns)
if (conn->imc_flags & (IMC_QUEUED_ACK_INIT << pns)
&& !(pns == PNS_INIT && (conn->imc_flags & IMC_IGNORE_INIT)))
{
if (0 != imico_generate_ack(conn, pns, now))
return -1;
}
return 0;
}
int
lsquic_mini_conn_ietf_pre_promote(struct ietf_mini_conn *conn,
lsquic_time_t now)
{
if (conn->imc_flags & (IMC_QUEUED_ACK_INIT|IMC_QUEUED_ACK_HSK))
{
if (0 != imico_generate_acks(conn, now))
{
conn->imc_flags |= IMC_ERROR;
return -1;
}
}
return 0;
}
static void
imico_generate_conn_close (struct ietf_mini_conn *conn)
{
struct lsquic_packet_out *packet_out;
enum header_type header_type;
enum packnum_space pns, pns_max;
unsigned error_code;
const char *reason;
size_t need;
int sz, rlen, is_app;
char reason_buf[0x20];
if (conn->imc_flags & IMC_ABORT_ERROR)
{
is_app = !!(conn->imc_flags & IMC_ABORT_ISAPP);
error_code = conn->imc_error_code;
reason = NULL;
rlen = 0;
}
else if (conn->imc_flags & IMC_TLS_ALERT)
{
is_app = 0;
error_code = 0x100 + conn->imc_tls_alert;
if (ALERT_NO_APPLICATION_PROTOCOL == conn->imc_tls_alert)
reason = "no suitable application protocol";
else
{
snprintf(reason_buf, sizeof(reason_buf), "TLS alert %"PRIu8,
conn->imc_tls_alert);
reason = reason_buf;
}
rlen = strlen(reason);
}
else if (conn->imc_flags & IMC_BAD_TRANS_PARAMS)
{
is_app = 0;
error_code = TEC_TRANSPORT_PARAMETER_ERROR;
reason = "bad transport parameters";
rlen = 24;
}
else if (conn->imc_flags & IMC_VER_NEG_FAILED)
{
is_app = 0;
error_code = TEC_VERSION_NEGOTIATION_ERROR;
reason = "version negociation failed";
rlen = 26;
}
else if (conn->imc_flags & IMC_HSK_FAILED)
{
is_app = 0;
error_code = TEC_NO_ERROR;
reason = "handshake failed";
rlen = 16;
}
else if (conn->imc_flags & IMC_PARSE_FAILED)
{
is_app = 0;
error_code = TEC_FRAME_ENCODING_ERROR;
reason = "cannot decode frame";
rlen = 19;
}
else
{
is_app = 0;
error_code = TEC_INTERNAL_ERROR;
reason = NULL;
rlen = 0;
}
/* [draft-ietf-quic-transport-28] Section 10.3.1:
*
" A client will always know whether the server has Handshake keys (see
" Section 17.2.2.1), but it is possible that a server does not know
" whether the client has Handshake keys. Under these circumstances, a
" server SHOULD send a CONNECTION_CLOSE frame in both Handshake and
" Initial packets to ensure that at least one of them is processable by
" the client.
--- 8< ---
" Sending a CONNECTION_CLOSE of type 0x1d in an Initial or Handshake
" packet could expose application state or be used to alter application
" state. A CONNECTION_CLOSE of type 0x1d MUST be replaced by a
" CONNECTION_CLOSE of type 0x1c when sending the frame in Initial or
" Handshake packets. Otherwise, information about the application
" state might be revealed. Endpoints MUST clear the value of the
" Reason Phrase field and SHOULD use the APPLICATION_ERROR code when
" converting to a CONNECTION_CLOSE of type 0x1c.
*/
LSQ_DEBUG("sending CONNECTION_CLOSE, is_app: %d, error code: %u, "
"reason: %.*s", is_app, error_code, rlen, reason);
if (is_app && conn->imc_conn.cn_version > LSQVER_ID27)
{
LSQ_DEBUG("convert to 0x1C, replace code and reason");
is_app = 0;
error_code = TEC_APPLICATION_ERROR;
rlen = 0;
}
pns = (conn->imc_flags >> IMCBIT_PNS_BIT_SHIFT) & 3;
switch ((!!(conn->imc_flags & IMC_HSK_PACKET_SENT) << 1)
| (pns == PNS_HSK) /* Handshake packet received */)
{
case (0 << 1) | 0:
pns = PNS_INIT;
pns_max = PNS_INIT;
break;
case (1 << 1) | 0:
pns = PNS_INIT;
pns_max = PNS_HSK;
break;
default:
pns = PNS_HSK;
pns_max = PNS_HSK;
break;
}
need = conn->imc_conn.cn_pf->pf_connect_close_frame_size(is_app,
error_code, 0, rlen);
LSQ_DEBUG("will generate %u CONNECTION_CLOSE frame%.*s",
pns_max - pns + 1, pns_max > pns, "s");
do
{
header_type = pns2hety[pns];
packet_out = imico_get_packet_out(conn, header_type, need);
if (!packet_out)
return;
sz = conn->imc_conn.cn_pf->pf_gen_connect_close_frame(
packet_out->po_data + packet_out->po_data_sz,
lsquic_packet_out_avail(packet_out), is_app, error_code, reason,
rlen);
if (sz >= 0)
{
packet_out->po_frame_types |= 1 << QUIC_FRAME_CONNECTION_CLOSE;
packet_out->po_data_sz += sz;
LSQ_DEBUG("generated CONNECTION_CLOSE frame");
}
else
LSQ_WARN("could not generate CONNECTION_CLOSE frame");
++pns;
}
while (pns <= pns_max);
}
static int
imico_generate_handshake_done (struct ietf_mini_conn *conn)
{
struct lsquic_packet_out *packet_out;
unsigned need;
int sz;
need = conn->imc_conn.cn_pf->pf_handshake_done_frame_size();
packet_out = imico_get_packet_out(conn, HETY_SHORT, need);
if (!packet_out)
return -1;
sz = conn->imc_conn.cn_pf->pf_gen_handshake_done_frame(
packet_out->po_data + packet_out->po_data_sz,
lsquic_packet_out_avail(packet_out));
if (sz < 0)
{
LSQ_WARN("could not generate HANDSHAKE_DONE frame");
return -1;
}
packet_out->po_frame_types |= 1 << QUIC_FRAME_HANDSHAKE_DONE;
packet_out->po_data_sz += sz;
LSQ_DEBUG("generated HANDSHAKE_DONE frame");
conn->imc_flags |= IMC_HSK_DONE_SENT;
return 0;
}
static enum tick_st
ietf_mini_conn_ci_tick (struct lsquic_conn *lconn, lsquic_time_t now)
{
struct ietf_mini_conn *conn = (struct ietf_mini_conn *) lconn;
enum tick_st tick;
if (conn->imc_expire < now)
{
LSQ_DEBUG("connection expired: closing");
return TICK_CLOSE;
}
if (!(conn->imc_flags &
(IMC_HAVE_TP|IMC_ADDR_VALIDATED|IMC_BAD_TRANS_PARAMS))
&& conn->imc_enpub->enp_settings.es_support_srej)
{
LSQ_DEBUG("Peer not validated and do not have transport parameters "
"on the first tick: retry");
return TICK_RETRY|TICK_CLOSE;
}
if (conn->imc_flags & (IMC_QUEUED_ACK_INIT|IMC_QUEUED_ACK_HSK))
{
if (0 != imico_generate_acks(conn, now))
{
conn->imc_flags |= IMC_ERROR;
return TICK_CLOSE;
}
}
if (lconn->cn_flags & (LSCONN_PROMOTED|LSCONN_PROMOTE_FAIL))
return TICK_CLOSE;
tick = 0;
if (conn->imc_flags & IMC_ERROR)
{
close_on_error:
if (!(conn->imc_flags & IMC_CLOSE_RECVD))
imico_generate_conn_close(conn);
tick |= TICK_CLOSE;
}
else if (conn->imc_flags & IMC_HSK_OK)
{
if (lconn->cn_esf.i->esfi_in_init(lconn->cn_enc_session))
LSQ_DEBUG("still in init, defer HANDSHAKE_DONE");
else if (0 != imico_generate_handshake_done(conn))
goto close_on_error;
tick |= TICK_PROMOTE;
}
if (imico_have_packets_to_send(conn, now))
tick |= TICK_SEND;
else
tick |= TICK_QUIET;
LSQ_DEBUG("Return TICK %d", tick);
return tick;
}
static void
ietf_mini_conn_ci_internal_error (struct lsquic_conn *lconn,
const char *format, ...)
{
struct ietf_mini_conn *conn = (struct ietf_mini_conn *) lconn;
LSQ_INFO("internal error reported");
conn->imc_flags |= IMC_ERROR;
}
static void
ietf_mini_conn_ci_abort_error (struct lsquic_conn *lconn, int is_app,
unsigned error_code, const char *fmt, ...)
{
struct ietf_mini_conn *conn = (struct ietf_mini_conn *) lconn;
va_list ap;
const char *err_str, *percent;
char err_buf[0x100];
percent = strchr(fmt, '%');
if (percent)
{
va_start(ap, fmt);
vsnprintf(err_buf, sizeof(err_buf), fmt, ap);
va_end(ap);
err_str = err_buf;
}
else
err_str = fmt;
LSQ_INFO("abort error: is_app: %d; error code: %u; error str: %s",
is_app, error_code, err_str);
conn->imc_flags |= IMC_ERROR|IMC_ABORT_ERROR;
if (is_app)
conn->imc_flags |= IMC_ABORT_ISAPP;
conn->imc_error_code = error_code;
}
static struct network_path *
ietf_mini_conn_ci_get_path (struct lsquic_conn *lconn,
const struct sockaddr *sa)
{
struct ietf_mini_conn *conn = (struct ietf_mini_conn *) lconn;
return &conn->imc_path;
}
static unsigned char
ietf_mini_conn_ci_record_addrs (struct lsquic_conn *lconn, void *peer_ctx,
const struct sockaddr *local_sa, const struct sockaddr *peer_sa)
{
struct ietf_mini_conn *conn = (struct ietf_mini_conn *) lconn;
const struct sockaddr *orig_peer_sa;
struct lsquic_packet_out *packet_out;
size_t len;
char path_str[4][INET6_ADDRSTRLEN + sizeof(":65535")];
if (NP_IS_IPv6(&conn->imc_path) != (AF_INET6 == peer_sa->sa_family))
TAILQ_FOREACH(packet_out, &conn->imc_packets_out, po_next)
if ((packet_out->po_flags & (PO_SENT|PO_ENCRYPTED)) == PO_ENCRYPTED)
imico_return_enc_data(conn, packet_out);
orig_peer_sa = NP_PEER_SA(&conn->imc_path);
if (orig_peer_sa->sa_family == 0)
LSQ_DEBUG("connection to %s from %s", SA2STR(local_sa, path_str[0]),
SA2STR(peer_sa, path_str[1]));
else if (!(lsquic_sockaddr_eq(NP_PEER_SA(&conn->imc_path), peer_sa)
&& lsquic_sockaddr_eq(NP_LOCAL_SA(&conn->imc_path), local_sa)))
{
LSQ_DEBUG("path changed from (%s - %s) to (%s - %s)",
SA2STR(NP_LOCAL_SA(&conn->imc_path), path_str[0]),
SA2STR(NP_PEER_SA(&conn->imc_path), path_str[1]),
SA2STR(local_sa, path_str[2]),
SA2STR(peer_sa, path_str[3]));
conn->imc_flags |= IMC_PATH_CHANGED;
}
len = local_sa->sa_family == AF_INET ? sizeof(struct sockaddr_in)
: sizeof(struct sockaddr_in6);
memcpy(conn->imc_path.np_peer_addr, peer_sa, len);
memcpy(conn->imc_path.np_local_addr, local_sa, len);
conn->imc_path.np_peer_ctx = peer_ctx;
return 0;
}
void
ietf_mini_conn_ci_count_garbage (struct lsquic_conn *lconn, size_t garbage_sz)
{
struct ietf_mini_conn *conn = (struct ietf_mini_conn *) lconn;
conn->imc_bytes_in += garbage_sz;
conn->imc_flags &= ~IMC_AMP_CAPPED;
LSQ_DEBUG("count %zd bytes of garbage, new value: %u bytes", garbage_sz,
conn->imc_bytes_in);
}
static const struct conn_iface mini_conn_ietf_iface = {
.ci_abort_error = ietf_mini_conn_ci_abort_error,
.ci_client_call_on_new = ietf_mini_conn_ci_client_call_on_new,
.ci_count_garbage = ietf_mini_conn_ci_count_garbage,
.ci_destroy = ietf_mini_conn_ci_destroy,
.ci_get_engine = ietf_mini_conn_ci_get_engine,
.ci_get_path = ietf_mini_conn_ci_get_path,
.ci_hsk_done = ietf_mini_conn_ci_hsk_done,
.ci_internal_error = ietf_mini_conn_ci_internal_error,
.ci_is_tickable = ietf_mini_conn_ci_is_tickable,
.ci_next_packet_to_send = ietf_mini_conn_ci_next_packet_to_send,
.ci_next_tick_time = ietf_mini_conn_ci_next_tick_time,
.ci_packet_in = ietf_mini_conn_ci_packet_in,
.ci_packet_not_sent = ietf_mini_conn_ci_packet_not_sent,
.ci_packet_sent = ietf_mini_conn_ci_packet_sent,
.ci_record_addrs = ietf_mini_conn_ci_record_addrs,
.ci_tick = ietf_mini_conn_ci_tick,
.ci_tls_alert = ietf_mini_conn_ci_tls_alert,
};