litespeed-quic/src/liblsquic/lsquic_full_conn_ietf.c

9705 lines
332 KiB
C

/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. */
/*
* lsquic_full_conn_ietf.c -- IETF QUIC connection.
*/
#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#define _USE_MATH_DEFINES /* Need this for M_E on Windows */
#include <math.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/queue.h>
#include <openssl/aead.h>
#include <openssl/rand.h>
#include "fiu-local.h"
#include "lsquic.h"
#include "lsxpack_header.h"
#include "lsquic_types.h"
#include "lsquic_int_types.h"
#include "lsquic_attq.h"
#include "lsquic_packet_common.h"
#include "lsquic_packet_ietf.h"
#include "lsquic_packet_in.h"
#include "lsquic_packet_out.h"
#include "lsquic_hash.h"
#include "lsquic_conn.h"
#include "lsquic_rechist.h"
#include "lsquic_senhist.h"
#include "lsquic_cubic.h"
#include "lsquic_pacer.h"
#include "lsquic_sfcw.h"
#include "lsquic_conn_flow.h"
#include "lsquic_varint.h"
#include "lsquic_hq.h"
#include "lsquic_stream.h"
#include "lsquic_rtt.h"
#include "lsquic_conn_public.h"
#include "lsquic_bw_sampler.h"
#include "lsquic_minmax.h"
#include "lsquic_bbr.h"
#include "lsquic_adaptive_cc.h"
#include "lsquic_send_ctl.h"
#include "lsquic_alarmset.h"
#include "lsquic_ver_neg.h"
#include "lsquic_mm.h"
#include "lsquic_engine_public.h"
#include "lsquic_set.h"
#include "lsquic_sizes.h"
#include "lsquic_trans_params.h"
#include "lsquic_version.h"
#include "lsquic_parse.h"
#include "lsquic_util.h"
#include "lsquic_enc_sess.h"
#include "lsquic_ev_log.h"
#include "lsquic_malo.h"
#include "lsquic_frab_list.h"
#include "lsquic_hcso_writer.h"
#include "lsquic_hcsi_reader.h"
#include "lsqpack.h"
#include "lsquic_http1x_if.h"
#include "lsquic_qenc_hdl.h"
#include "lsquic_qdec_hdl.h"
#include "lsquic_trechist.h"
#include "lsquic_mini_conn_ietf.h"
#include "lsquic_tokgen.h"
#include "lsquic_full_conn.h"
#include "lsquic_spi.h"
#include "lsquic_min_heap.h"
#include "lsquic_hpi.h"
#include "lsquic_ietf.h"
#include "lsquic_push_promise.h"
#include "lsquic_headers.h"
#include "lsquic_crand.h"
#include "ls-sfparser.h"
#include "lsquic_qpack_exp.h"
#define LSQUIC_LOGGER_MODULE LSQLM_CONN
#define LSQUIC_LOG_CONN_ID ietf_full_conn_ci_get_log_cid(&conn->ifc_conn)
#include "lsquic_logger.h"
#define MAX_RETR_PACKETS_SINCE_LAST_ACK 2
#define MAX_ANY_PACKETS_SINCE_LAST_ACK 20
#define ACK_TIMEOUT (TP_DEF_MAX_ACK_DELAY * 1000)
#define INITIAL_CHAL_TIMEOUT 25000
/* Retire original CID after this much time has elapsed: */
#define RET_CID_TIMEOUT 2000000
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
/* IETF QUIC push promise does not contain stream ID. This means that, unlike
* in GQUIC, one cannot create a stream immediately and pass it to the client.
* We may have to add a special API for IETF push promises. That's in the
* future: right now, we punt it.
*/
#define CLIENT_PUSH_SUPPORT 0
/* IMPORTANT: Keep values of IFC_SERVER and IFC_HTTP same as LSENG_SERVER
* and LSENG_HTTP.
*/
enum ifull_conn_flags
{
IFC_SERVER = LSENG_SERVER, /* Server mode */
IFC_HTTP = LSENG_HTTP, /* HTTP mode */
IFC_ACK_HAD_MISS = 1 << 2,
#define IFC_BIT_ERROR 3
IFC_ERROR = 1 << IFC_BIT_ERROR,
IFC_TIMED_OUT = 1 << 4,
IFC_ABORTED = 1 << 5,
IFC_HSK_FAILED = 1 << 6,
IFC_GOING_AWAY = 1 << 7,
IFC_CLOSING = 1 << 8, /* Closing */
IFC_RECV_CLOSE = 1 << 9, /* Received CONNECTION_CLOSE frame */
IFC_TICK_CLOSE = 1 << 10, /* We returned TICK_CLOSE */
IFC_CREATED_OK = 1 << 11,
IFC_HAVE_SAVED_ACK= 1 << 12,
IFC_ABORT_COMPLAINED
= 1 << 13,
IFC_DCID_SET = 1 << 14,
#define IFCBIT_ACK_QUED_SHIFT 15
IFC_ACK_QUED_INIT = 1 << 15,
IFC_ACK_QUED_HSK = IFC_ACK_QUED_INIT << PNS_HSK,
IFC_ACK_QUED_APP = IFC_ACK_QUED_INIT << PNS_APP,
#define IFC_ACK_QUEUED (IFC_ACK_QUED_INIT|IFC_ACK_QUED_HSK|IFC_ACK_QUED_APP)
IFC_HAVE_PEER_SET = 1 << 18,
IFC_GOT_PRST = 1 << 19,
IFC_IGNORE_INIT = 1 << 20,
IFC_RETRIED = 1 << 21,
IFC_SWITCH_DCID = 1 << 22, /* Perform DCID switch when a new CID becomes available */
IFC_GOAWAY_CLOSE = 1 << 23,
IFC_FIRST_TICK = 1 << 24,
IFC_IGNORE_HSK = 1 << 25,
IFC_PROC_CRYPTO = 1 << 26,
IFC_MIGRA = 1 << 27,
IFC_HTTP_INITED = 1 << 28, /* HTTP initialized */
IFC_DELAYED_ACKS = 1 << 29, /* Delayed ACKs are enabled */
IFC_TIMESTAMPS = 1 << 30, /* Timestamps are enabled */
IFC_DATAGRAMS = 1u<< 31, /* Datagrams are enabled */
};
enum more_flags
{
MF_VALIDATE_PATH = 1 << 0,
MF_NOPROG_TIMEOUT = 1 << 1,
MF_CHECK_MTU_PROBE = 1 << 2,
MF_IGNORE_MISSING = 1 << 3,
MF_CONN_CLOSE_PACK = 1 << 4, /* CONNECTION_CLOSE has been packetized */
MF_SEND_WRONG_COUNTS= 1 << 5, /* Send wrong ECN counts to peer */
MF_WANT_DATAGRAM_WRITE = 1 << 6,
MF_DOING_0RTT = 1 << 7,
};
#define N_PATHS 4
enum send
{
/* PATH_CHALLENGE and PATH_RESPONSE frames are not retransmittable. They
* are positioned first in the enum to optimize packetization.
*/
SEND_PATH_CHAL,
SEND_PATH_CHAL_PATH_0 = SEND_PATH_CHAL + 0,
SEND_PATH_CHAL_PATH_1 = SEND_PATH_CHAL + 1,
SEND_PATH_CHAL_PATH_2 = SEND_PATH_CHAL + 2,
SEND_PATH_CHAL_PATH_3 = SEND_PATH_CHAL + 3,
SEND_PATH_RESP,
SEND_PATH_RESP_PATH_0 = SEND_PATH_RESP + 0,
SEND_PATH_RESP_PATH_1 = SEND_PATH_RESP + 1,
SEND_PATH_RESP_PATH_2 = SEND_PATH_RESP + 2,
SEND_PATH_RESP_PATH_3 = SEND_PATH_RESP + 3,
SEND_MAX_DATA,
SEND_PING,
SEND_NEW_CID,
SEND_RETIRE_CID,
SEND_CONN_CLOSE,
SEND_STREAMS_BLOCKED,
SEND_STREAMS_BLOCKED_BIDI = SEND_STREAMS_BLOCKED + SD_BIDI,
SEND_STREAMS_BLOCKED_UNI = SEND_STREAMS_BLOCKED + SD_UNI,
SEND_MAX_STREAMS,
SEND_MAX_STREAMS_BIDI = SEND_MAX_STREAMS + SD_BIDI,
SEND_MAX_STREAMS_UNI = SEND_MAX_STREAMS + SD_UNI,
SEND_STOP_SENDING,
SEND_HANDSHAKE_DONE,
SEND_ACK_FREQUENCY,
N_SEND
};
enum send_flags
{
SF_SEND_MAX_DATA = 1 << SEND_MAX_DATA,
SF_SEND_PING = 1 << SEND_PING,
SF_SEND_PATH_CHAL = 1 << SEND_PATH_CHAL,
SF_SEND_PATH_CHAL_PATH_0 = 1 << SEND_PATH_CHAL_PATH_0,
SF_SEND_PATH_CHAL_PATH_1 = 1 << SEND_PATH_CHAL_PATH_1,
SF_SEND_PATH_CHAL_PATH_2 = 1 << SEND_PATH_CHAL_PATH_2,
SF_SEND_PATH_CHAL_PATH_3 = 1 << SEND_PATH_CHAL_PATH_3,
SF_SEND_PATH_RESP = 1 << SEND_PATH_RESP,
SF_SEND_PATH_RESP_PATH_0 = 1 << SEND_PATH_RESP_PATH_0,
SF_SEND_PATH_RESP_PATH_1 = 1 << SEND_PATH_RESP_PATH_1,
SF_SEND_PATH_RESP_PATH_2 = 1 << SEND_PATH_RESP_PATH_2,
SF_SEND_PATH_RESP_PATH_3 = 1 << SEND_PATH_RESP_PATH_3,
SF_SEND_NEW_CID = 1 << SEND_NEW_CID,
SF_SEND_RETIRE_CID = 1 << SEND_RETIRE_CID,
SF_SEND_CONN_CLOSE = 1 << SEND_CONN_CLOSE,
SF_SEND_STREAMS_BLOCKED = 1 << SEND_STREAMS_BLOCKED,
SF_SEND_STREAMS_BLOCKED_BIDI = 1 << SEND_STREAMS_BLOCKED_BIDI,
SF_SEND_STREAMS_BLOCKED_UNI = 1 << SEND_STREAMS_BLOCKED_UNI,
SF_SEND_MAX_STREAMS = 1 << SEND_MAX_STREAMS,
SF_SEND_MAX_STREAMS_BIDI = 1 << SEND_MAX_STREAMS_BIDI,
SF_SEND_MAX_STREAMS_UNI = 1 << SEND_MAX_STREAMS_UNI,
SF_SEND_STOP_SENDING = 1 << SEND_STOP_SENDING,
SF_SEND_HANDSHAKE_DONE = 1 << SEND_HANDSHAKE_DONE,
SF_SEND_ACK_FREQUENCY = 1 << SEND_ACK_FREQUENCY,
};
#define SF_SEND_PATH_CHAL_ALL \
(((SF_SEND_PATH_CHAL << N_PATHS) - 1) & ~(SF_SEND_PATH_CHAL - 1))
#define IFC_IMMEDIATE_CLOSE_FLAGS \
(IFC_TIMED_OUT|IFC_ERROR|IFC_ABORTED|IFC_HSK_FAILED|IFC_GOT_PRST)
#define MAX_ERRMSG 256
#define MAX_SCID 8
#define SET_ERRMSG(conn, ...) do { \
if (!(conn)->ifc_errmsg) \
{ \
(conn)->ifc_errmsg = malloc(MAX_ERRMSG); \
if ((conn)->ifc_errmsg) \
snprintf((conn)->ifc_errmsg, MAX_ERRMSG, __VA_ARGS__); \
} \
} while (0)
#define ABORT_WITH_FLAG(conn, log_level, flag, ...) do { \
SET_ERRMSG(conn, __VA_ARGS__); \
if (!((conn)->ifc_flags & IFC_ABORT_COMPLAINED)) \
LSQ_LOG(log_level, "Abort connection: " __VA_ARGS__); \
(conn)->ifc_flags |= flag|IFC_ABORT_COMPLAINED; \
} while (0)
#define ABORT_ERROR(...) \
ABORT_WITH_FLAG(conn, LSQ_LOG_ERROR, IFC_ERROR, __VA_ARGS__)
#define ABORT_WARN(...) \
ABORT_WITH_FLAG(conn, LSQ_LOG_WARN, IFC_ERROR, __VA_ARGS__)
#define CONN_ERR(app_error_, code_) (struct conn_err) { \
.app_error = (app_error_), .u.err = (code_), }
/* Use this for protocol errors; they do not need to be as loud as our own
* internal errors.
*/
#define ABORT_QUIETLY(app_error, code, ...) do { \
conn->ifc_error = CONN_ERR(app_error, code); \
ABORT_WITH_FLAG(conn, LSQ_LOG_INFO, IFC_ERROR, __VA_ARGS__); \
} while (0)
static enum stream_id_type
gen_sit (unsigned server, enum stream_dir sd)
{
return (server > 0) | ((sd > 0) << SD_SHIFT);
}
struct stream_id_to_ss
{
STAILQ_ENTRY(stream_id_to_ss) sits_next;
lsquic_stream_id_t sits_stream_id;
enum http_error_code sits_error_code;
};
struct http_ctl_stream_in
{
struct hcsi_reader reader;
};
struct conn_err
{
int app_error;
union
{
enum trans_error_code tec;
enum http_error_code hec;
unsigned err;
} u;
};
struct dplpmtud_state
{
lsquic_packno_t ds_probe_packno;
#ifndef NDEBUG
lsquic_time_t ds_probe_sent;
#endif
enum {
DS_PROBE_SENT = 1 << 0,
} ds_flags;
unsigned short ds_probed_size,
ds_failed_size; /* If non-zero, defines ceiling */
unsigned char ds_probe_count;
};
struct conn_path
{
struct network_path cop_path;
uint64_t cop_path_chals[8]; /* Arbitrary number */
uint64_t cop_inc_chal; /* Incoming challenge */
lsquic_packno_t cop_max_packno;
enum {
/* Initialized covers cop_path.np_pack_size and cop_path.np_dcid */
COP_INITIALIZED = 1 << 0,
/* This flag is set when we received a response to one of path
* challenges we sent on this path.
*/
COP_VALIDATED = 1 << 1,
/* Received non-probing frames. This flag is not set for the
* original path.
*/
COP_GOT_NONPROB = 1 << 2,
/* Spin bit is enabled on this path. */
COP_SPIN_BIT = 1 << 3,
} cop_flags;
unsigned char cop_n_chals;
unsigned char cop_cce_idx;
unsigned char cop_spin_bit;
struct dplpmtud_state cop_dplpmtud;
};
struct packet_tolerance_stats
{
unsigned n_acks; /* Number of ACKs between probes */
float integral_error;
lsquic_time_t last_sample;
};
union prio_iter
{
struct stream_prio_iter spi;
struct http_prio_iter hpi;
};
struct prio_iter_if
{
void (*pii_init) (void *, struct lsquic_stream *first,
struct lsquic_stream *last, uintptr_t next_ptr_offset,
struct lsquic_conn_public *, const char *name,
int (*filter)(void *filter_ctx, struct lsquic_stream *),
void *filter_ctx);
struct lsquic_stream * (*pii_first) (void *);
struct lsquic_stream * (*pii_next) (void *);
void (*pii_drop_non_high) (void *);
void (*pii_drop_high) (void *);
void (*pii_cleanup) (void *);
};
static const struct prio_iter_if orig_prio_iter_if = {
lsquic_spi_init,
lsquic_spi_first,
lsquic_spi_next,
lsquic_spi_drop_non_high,
lsquic_spi_drop_high,
lsquic_spi_cleanup,
};
static const struct prio_iter_if ext_prio_iter_if = {
lsquic_hpi_init,
lsquic_hpi_first,
lsquic_hpi_next,
lsquic_hpi_drop_non_high,
lsquic_hpi_drop_high,
lsquic_hpi_cleanup,
};
struct ietf_full_conn
{
struct lsquic_conn ifc_conn;
struct conn_cid_elem ifc_cces[MAX_SCID];
struct lsquic_rechist ifc_rechist[N_PNS];
/* App PNS only, used to calculate was_missing: */
lsquic_packno_t ifc_max_ackable_packno_in;
struct lsquic_send_ctl ifc_send_ctl;
struct lsquic_stream *ifc_stream_hcsi; /* HTTP Control Stream Incoming */
struct lsquic_stream *ifc_stream_hcso; /* HTTP Control Stream Outgoing */
struct lsquic_conn_public ifc_pub;
lsquic_alarmset_t ifc_alset;
struct lsquic_set64 ifc_closed_stream_ids[N_SITS];
lsquic_stream_id_t ifc_n_created_streams[N_SDS];
/* Not including the value stored in ifc_max_allowed_stream_id: */
lsquic_stream_id_t ifc_max_allowed_stream_id[N_SITS];
uint64_t ifc_closed_peer_streams[N_SDS];
/* Maximum number of open stream initiated by peer: */
unsigned ifc_max_streams_in[N_SDS];
uint64_t ifc_max_stream_data_uni;
enum ifull_conn_flags ifc_flags;
enum more_flags ifc_mflags;
enum send_flags ifc_send_flags;
enum send_flags ifc_delayed_send;
struct {
uint64_t streams_blocked[N_SDS];
} ifc_send;
struct conn_err ifc_error;
unsigned ifc_n_delayed_streams;
unsigned ifc_n_cons_unretx;
const struct lsquic_stream_if
*ifc_stream_if;
void *ifc_stream_ctx;
const struct prio_iter_if *ifc_pii;
char *ifc_errmsg;
struct lsquic_engine_public
*ifc_enpub;
const struct lsquic_engine_settings
*ifc_settings;
STAILQ_HEAD(, stream_id_to_ss)
ifc_stream_ids_to_ss;
lsquic_time_t ifc_created;
lsquic_time_t ifc_saved_ack_received;
lsquic_packno_t ifc_max_ack_packno[N_PNS];
lsquic_packno_t ifc_max_non_probing;
struct {
uint64_t max_stream_send;
uint8_t ack_exp;
} ifc_cfg;
int (*ifc_process_incoming_packet)(
struct ietf_full_conn *,
struct lsquic_packet_in *);
/* Number ackable packets received since last ACK was sent: */
unsigned ifc_n_slack_akbl[N_PNS];
unsigned ifc_n_slack_all; /* App PNS only */
unsigned ifc_max_retx_since_last_ack;
lsquic_time_t ifc_max_ack_delay;
uint64_t ifc_ecn_counts_in[N_PNS][4];
uint64_t ifc_ecn_counts_out[N_PNS][4];
lsquic_stream_id_t ifc_max_req_id;
struct hcso_writer ifc_hcso;
struct http_ctl_stream_in ifc_hcsi;
struct qpack_enc_hdl ifc_qeh;
struct qpack_dec_hdl ifc_qdh;
struct {
uint64_t header_table_size,
qpack_blocked_streams;
} ifc_peer_hq_settings;
struct dcid_elem *ifc_dces[MAX_IETF_CONN_DCIDS];
TAILQ_HEAD(, dcid_elem) ifc_to_retire;
unsigned ifc_scid_seqno;
lsquic_time_t ifc_scid_timestamp[MAX_SCID];
/* Last 8 packets had ECN markings? */
uint8_t ifc_incoming_ecn;
unsigned char ifc_cur_path_id; /* Indexes ifc_paths */
unsigned char ifc_used_paths; /* Bitmask */
unsigned char ifc_mig_path_id;
/* ifc_active_cids_limit is the maximum number of CIDs at any one time this
* endpoint is allowed to issue to peer. If the TP value exceeds cn_n_cces,
* it is reduced to it. ifc_active_cids_count tracks how many CIDs have
* been issued. It is decremented each time a CID is retired.
*/
unsigned char ifc_active_cids_limit;
unsigned char ifc_active_cids_count;
unsigned char ifc_first_active_cid_seqno;
unsigned char ifc_ping_unretx_thresh;
unsigned ifc_last_retire_prior_to;
unsigned ifc_ack_freq_seqno;
unsigned ifc_last_pack_tol;
unsigned ifc_last_calc_pack_tol;
#if LSQUIC_CONN_STATS
unsigned ifc_min_pack_tol_sent;
unsigned ifc_max_pack_tol_sent;
#endif
unsigned ifc_max_ack_freq_seqno; /* Incoming */
unsigned short ifc_max_udp_payload; /* Cached TP */
lsquic_time_t ifc_last_live_update;
struct conn_path ifc_paths[N_PATHS];
union {
struct {
struct lsquic_stream *crypto_streams[N_ENC_LEVS];
struct ver_neg
ifcli_ver_neg;
uint64_t ifcli_max_push_id;
uint64_t ifcli_min_goaway_stream_id;
enum {
IFCLI_PUSH_ENABLED = 1 << 0,
IFCLI_HSK_SENT_OR_DEL = 1 << 1,
} ifcli_flags;
unsigned ifcli_packets_out;
} cli;
struct {
uint64_t ifser_max_push_id;
uint64_t ifser_next_push_id;
enum {
IFSER_PUSH_ENABLED = 1 << 0,
IFSER_MAX_PUSH_ID = 1 << 1, /* ifser_max_push_id is set */
} ifser_flags;
} ser;
} ifc_u;
lsquic_time_t ifc_idle_to;
lsquic_time_t ifc_ping_period;
struct lsquic_hash *ifc_bpus;
uint64_t ifc_last_max_data_off_sent;
unsigned short ifc_min_dg_sz,
ifc_max_dg_sz;
struct packet_tolerance_stats
ifc_pts;
#if LSQUIC_CONN_STATS
struct conn_stats ifc_stats,
*ifc_last_stats;
#endif
struct ack_info ifc_ack;
};
#define CUR_CPATH(conn_) (&(conn_)->ifc_paths[(conn_)->ifc_cur_path_id])
#define CUR_NPATH(conn_) (&(CUR_CPATH(conn_)->cop_path))
#define CUR_DCID(conn_) (&(CUR_NPATH(conn_)->np_dcid))
#define DCES_END(conn_) ((conn_)->ifc_dces + (sizeof((conn_)->ifc_dces) \
/ sizeof((conn_)->ifc_dces[0])))
#define NPATH2CPATH(npath_) ((struct conn_path *) \
((char *) (npath_) - offsetof(struct conn_path, cop_path)))
#if LSQUIC_CONN_STATS
#define CONN_STATS(what_, count_) do { \
conn->ifc_stats.what_ += (count_); \
} while (0)
#else
#define CONN_STATS(what_, count_)
#endif
static const struct ver_neg server_ver_neg;
static const struct conn_iface *ietf_full_conn_iface_ptr;
static const struct conn_iface *ietf_full_conn_prehsk_iface_ptr;
static int
process_incoming_packet_verneg (struct ietf_full_conn *,
struct lsquic_packet_in *);
static int
process_incoming_packet_fast (struct ietf_full_conn *,
struct lsquic_packet_in *);
static void
ietf_full_conn_ci_packet_in (struct lsquic_conn *, struct lsquic_packet_in *);
static int
handshake_ok (struct lsquic_conn *);
static void
ignore_init (struct ietf_full_conn *);
static void
ignore_hsk (struct ietf_full_conn *);
static unsigned
ietf_full_conn_ci_n_avail_streams (const struct lsquic_conn *);
static const lsquic_cid_t *
ietf_full_conn_ci_get_log_cid (const struct lsquic_conn *);
static void
ietf_full_conn_ci_destroy (struct lsquic_conn *);
static int
insert_new_dcid (struct ietf_full_conn *, uint64_t seqno,
const lsquic_cid_t *, const unsigned char *token, int update_cur_dcid);
static struct conn_cid_elem *
find_cce_by_cid (struct ietf_full_conn *, const lsquic_cid_t *);
static void
mtu_probe_too_large (struct ietf_full_conn *, const struct lsquic_packet_out *);
static int
apply_trans_params (struct ietf_full_conn *, const struct transport_params *);
static void
packet_tolerance_alarm_expired (enum alarm_id al_id, void *ctx,
lsquic_time_t expiry, lsquic_time_t now);
static int
init_http (struct ietf_full_conn *);
static unsigned
highest_bit_set (unsigned sz)
{
#if __GNUC__
unsigned clz = __builtin_clz(sz);
return 31 - clz;
#else
unsigned n, y;
n = 32;
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 31 - n + 2;
return 31 - n + sz;
#endif
}
static void
set_versions (struct ietf_full_conn *conn, unsigned versions,
enum lsquic_version *ver)
{
conn->ifc_u.cli.ifcli_ver_neg.vn_supp = versions;
conn->ifc_u.cli.ifcli_ver_neg.vn_ver = (ver) ? *ver : highest_bit_set(versions);
conn->ifc_u.cli.ifcli_ver_neg.vn_buf = lsquic_ver2tag(conn->ifc_u.cli.ifcli_ver_neg.vn_ver);
conn->ifc_conn.cn_version = conn->ifc_u.cli.ifcli_ver_neg.vn_ver;
}
static void
init_ver_neg (struct ietf_full_conn *conn, unsigned versions,
enum lsquic_version *ver)
{
set_versions(conn, versions, ver);
conn->ifc_u.cli.ifcli_ver_neg.vn_tag = &conn->ifc_u.cli.ifcli_ver_neg.vn_buf;
conn->ifc_u.cli.ifcli_ver_neg.vn_state = VN_START;
}
static void
ack_alarm_expired (enum alarm_id al_id, void *ctx, lsquic_time_t expiry,
lsquic_time_t now)
{
struct ietf_full_conn *conn = ctx;
assert(al_id == AL_ACK_APP);
LSQ_DEBUG("%s ACK timer expired (%"PRIu64" < %"PRIu64"): ACK queued",
lsquic_pns2str[PNS_APP], expiry, now);
conn->ifc_flags |= IFC_ACK_QUED_APP;
}
static void
idle_alarm_expired (enum alarm_id al_id, void *ctx, lsquic_time_t expiry,
lsquic_time_t now)
{
struct ietf_full_conn *const conn = (struct ietf_full_conn *) ctx;
if ((conn->ifc_mflags & MF_NOPROG_TIMEOUT)
&& conn->ifc_pub.last_prog + conn->ifc_enpub->enp_noprog_timeout < now)
{
EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "connection timed out due to "
"lack of progress");
/* Different flag so that CONNECTION_CLOSE frame is sent */
ABORT_QUIETLY(0, TEC_APPLICATION_ERROR,
"connection timed out due to lack of progress");
}
else
{
LSQ_DEBUG("connection timed out");
EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "connection timed out");
conn->ifc_flags |= IFC_TIMED_OUT;
}
}
static void
handshake_alarm_expired (enum alarm_id al_id, void *ctx,
lsquic_time_t expiry, lsquic_time_t now)
{
struct ietf_full_conn *const conn = (struct ietf_full_conn *) ctx;
LSQ_DEBUG("connection timed out: handshake timed out");
conn->ifc_flags |= IFC_TIMED_OUT;
}
/*
* When this alarm expires, at least one SCID slot shoud be available
* for generation.
*/
static void
cid_throt_alarm_expired (enum alarm_id al_id, void *ctx,
lsquic_time_t expiry, lsquic_time_t now)
{
struct ietf_full_conn *const conn = (struct ietf_full_conn *) ctx;
LSQ_DEBUG("%s", __func__);
conn->ifc_send_flags |= SF_SEND_NEW_CID;
return;
}
static void
wipe_path (struct ietf_full_conn *conn, unsigned path_id)
{
memset(&conn->ifc_paths[path_id], 0, sizeof(conn->ifc_paths[0]));
conn->ifc_paths[path_id].cop_path.np_path_id = path_id;
}
static void
path_chal_alarm_expired (enum alarm_id al_id, void *ctx,
lsquic_time_t expiry, lsquic_time_t now)
{
struct ietf_full_conn *const conn = (struct ietf_full_conn *) ctx;
const unsigned path_id = al_id - AL_PATH_CHAL;
struct conn_path *const copath = &conn->ifc_paths[path_id];
if (copath->cop_n_chals < sizeof(copath->cop_path_chals)
/ sizeof(copath->cop_path_chals[0]))
{
LSQ_DEBUG("path #%u challenge expired, schedule another one", path_id);
conn->ifc_send_flags |= SF_SEND_PATH_CHAL << path_id;
}
else if (conn->ifc_cur_path_id != path_id)
{
LSQ_INFO("migration to path #%u failed after none of %u path "
"challenges received responses", path_id, copath->cop_n_chals);
/* There may be a lingering challenge if its generation is delayed */
lsquic_send_ctl_cancel_chals(&conn->ifc_send_ctl, &copath->cop_path);
wipe_path(conn, path_id);
}
else
LSQ_INFO("no path challenge responses on current path %u, stop "
"sending path challenges", path_id);
}
/* Sending DATA_BLOCKED and STREAM_DATA_BLOCKED frames is a way to elicit
* incoming packets from peer when it is too slow to read data. This is
* recommended by [draft-ietf-quic-transport-25] Section 4.1.
*
* If we are still in the blocked state, we schedule a blocked frame to
* be sent.
*/
static void
blocked_ka_alarm_expired (enum alarm_id al_id, void *ctx,
lsquic_time_t expiry, lsquic_time_t now)
{
struct ietf_full_conn *const conn = (struct ietf_full_conn *) ctx;
struct lsquic_stream *stream;
struct lsquic_hash_elem *el;
if (lsquic_conn_cap_avail(&conn->ifc_pub.conn_cap) == 0)
{
LSQ_DEBUG("set SEND_BLOCKED flag on connection");
conn->ifc_conn.cn_flags |= LSCONN_SEND_BLOCKED;
return;
}
for (el = lsquic_hash_first(conn->ifc_pub.all_streams); el;
el = lsquic_hash_next(conn->ifc_pub.all_streams))
{
stream = lsquic_hashelem_getdata(el);
if (lsquic_stream_is_blocked(stream))
{
if (!(stream->sm_qflags & SMQF_SENDING_FLAGS))
TAILQ_INSERT_TAIL(&conn->ifc_pub.sending_streams, stream,
next_send_stream);
stream->sm_qflags |= SMQF_SEND_BLOCKED;
LSQ_DEBUG("set SEND_BLOCKED flag on stream %"PRIu64, stream->id);
return;
}
}
}
static void
mtu_probe_alarm_expired (enum alarm_id al_id, void *ctx,
lsquic_time_t expiry, lsquic_time_t now)
{
struct ietf_full_conn *const conn = (struct ietf_full_conn *) ctx;
LSQ_DEBUG("MTU probe alarm expired: set `check MTU probe' flag");
assert(!(conn->ifc_mflags & MF_CHECK_MTU_PROBE));
conn->ifc_mflags |= MF_CHECK_MTU_PROBE;
}
static int
migra_is_on (const struct ietf_full_conn *conn, unsigned path_id)
{
return (conn->ifc_send_flags & (SF_SEND_PATH_CHAL << path_id))
|| lsquic_alarmset_is_set(&conn->ifc_alset, AL_PATH_CHAL + path_id);
}
#define TRANSPORT_OVERHEAD(is_ipv6) (((is_ipv6) ? 40 : 20) + 8 /* UDP */)
static unsigned short
calc_base_packet_size (const struct ietf_full_conn *conn, int is_ipv6)
{
unsigned short size;
if (conn->ifc_settings->es_base_plpmtu)
size = conn->ifc_settings->es_base_plpmtu;
else if (is_ipv6)
size = IQUIC_MAX_IPv6_PACKET_SZ;
else
size = IQUIC_MAX_IPv4_PACKET_SZ;
return size;
}
static void
migra_begin (struct ietf_full_conn *conn, struct conn_path *copath,
struct dcid_elem *dce, const struct sockaddr *dest_sa,
const struct transport_params *params)
{
assert(!(migra_is_on(conn, copath - conn->ifc_paths)));
dce->de_flags |= DE_ASSIGNED;
copath->cop_flags |= COP_INITIALIZED;
copath->cop_path.np_dcid = dce->de_cid;
copath->cop_path.np_peer_ctx = CUR_NPATH(conn)->np_peer_ctx;
copath->cop_path.np_pack_size
= calc_base_packet_size(conn, NP_IS_IPv6(CUR_NPATH(conn)));
if (conn->ifc_max_udp_payload < copath->cop_path.np_pack_size)
copath->cop_path.np_pack_size = conn->ifc_max_udp_payload;
memcpy(&copath->cop_path.np_local_addr, NP_LOCAL_SA(CUR_NPATH(conn)),
sizeof(copath->cop_path.np_local_addr));
memcpy(&copath->cop_path.np_peer_addr, dest_sa,
sizeof(copath->cop_path.np_peer_addr));
conn->ifc_mig_path_id = copath - conn->ifc_paths;
conn->ifc_used_paths |= 1 << conn->ifc_mig_path_id;
conn->ifc_send_flags |= SF_SEND_PATH_CHAL << conn->ifc_mig_path_id;
LSQ_DEBUG("Schedule migration to path %hhu: will send PATH_CHALLENGE",
conn->ifc_mig_path_id);
}
static void
ping_alarm_expired (enum alarm_id al_id, void *ctx, lsquic_time_t expiry,
lsquic_time_t now)
{
struct ietf_full_conn *const conn = (struct ietf_full_conn *) ctx;
LSQ_DEBUG("Ping alarm rang: schedule PING frame to be generated");
conn->ifc_send_flags |= SF_SEND_PING;
}
static void
retire_cid (struct ietf_full_conn *, struct conn_cid_elem *, lsquic_time_t);
static void
log_scids (const struct ietf_full_conn *conn)
{
const struct lsquic_conn *const lconn = &conn->ifc_conn;
const struct conn_cid_elem *cce;
char flags[5];
unsigned idx;
int fi;
LSQ_DEBUG("Log SCID array: (n_cces %hhu; mask: 0x%hhX; "
"active: %hhu; limit: %hhu)",
conn->ifc_conn.cn_n_cces, conn->ifc_conn.cn_cces_mask,
conn->ifc_active_cids_count, conn->ifc_active_cids_limit);
for (cce = lconn->cn_cces; cce < END_OF_CCES(lconn); ++cce)
{
idx = cce - lconn->cn_cces;
fi = 0;
if (cce->cce_flags & CCE_PORT) flags[fi++] = 'p';
if (cce->cce_flags & CCE_REG) flags[fi++] = 'r';
if (cce->cce_flags & CCE_SEQNO) flags[fi++] = 's';
if (cce->cce_flags & CCE_USED) flags[fi++] = 'u';
flags[fi] = '\0';
if (lconn->cn_cces_mask & (1 << idx))
{
if (cce->cce_flags & CCE_PORT)
LSQ_DEBUG( " %u: flags %-4s; port %hu", idx, flags,
cce->cce_port);
else if (cce->cce_flags & CCE_SEQNO)
LSQ_DEBUGC(" %u: flags %-4s; seqno: %u; %"CID_FMT, idx,
flags, cce->cce_seqno, CID_BITS(&cce->cce_cid));
else
LSQ_DEBUGC(" %u: flags %-4s; %"CID_FMT, idx, flags,
CID_BITS(&cce->cce_cid));
}
else
LSQ_DEBUG( " %u: flags %-4s; <empty>", idx, flags);
}
}
#define LOG_SCIDS(conn_) do { \
if (LSQ_LOG_ENABLED(LSQ_LOG_DEBUG)) \
log_scids(conn_); \
} while (0)
static void
ret_cids_alarm_expired (enum alarm_id al_id, void *ctx, lsquic_time_t expiry,
lsquic_time_t now)
{
struct ietf_full_conn *const conn = (struct ietf_full_conn *) ctx;
struct lsquic_conn *const lconn = &conn->ifc_conn;
struct conn_cid_elem *cce;
unsigned idx;
LSQ_DEBUG("The 'retire original CIDs' alarm rang");
for (cce = lconn->cn_cces; cce < END_OF_CCES(lconn); ++cce)
{
idx = cce - lconn->cn_cces;
if ((lconn->cn_cces_mask & (1 << idx))
&& (cce->cce_flags & (CCE_SEQNO|CCE_PORT)) == 0)
{
LSQ_DEBUG("retiring original CID at index %u", idx);
retire_cid(conn, cce, now);
}
}
LOG_SCIDS(conn);
}
static ssize_t
crypto_stream_write (void *stream, const void *buf, size_t len)
{
return lsquic_stream_write(stream, buf, len);
}
static int
crypto_stream_flush (void *stream)
{
return lsquic_stream_flush(stream);
}
static ssize_t
crypto_stream_readf (void *stream,
size_t (*readf)(void *, const unsigned char *, size_t, int), void *ctx)
{
return lsquic_stream_readf(stream, readf, ctx);
}
static int
crypto_stream_wantwrite (void *stream, int is_want)
{
return lsquic_stream_wantwrite(stream, is_want);
}
static int
crypto_stream_wantread (void *stream, int is_want)
{
return lsquic_stream_wantread(stream, is_want);
}
static enum enc_level
crypto_stream_enc_level (void *streamp)
{
const struct lsquic_stream *stream = streamp;
return crypto_level(stream);
}
static const struct crypto_stream_if crypto_stream_if =
{
.csi_write = crypto_stream_write,
.csi_flush = crypto_stream_flush,
.csi_readf = crypto_stream_readf,
.csi_wantwrite = crypto_stream_wantwrite,
.csi_wantread = crypto_stream_wantread,
.csi_enc_level = crypto_stream_enc_level,
};
static const struct lsquic_stream_if *unicla_if_ptr;
static lsquic_stream_id_t
generate_stream_id (struct ietf_full_conn *conn, enum stream_dir sd)
{
lsquic_stream_id_t id;
id = conn->ifc_n_created_streams[sd]++;
return id << SIT_SHIFT
| sd << SD_SHIFT
| !!(conn->ifc_flags & IFC_SERVER)
;
}
static lsquic_stream_id_t
avail_streams_count (const struct ietf_full_conn *conn, int server,
enum stream_dir sd)
{
enum stream_id_type sit;
lsquic_stream_id_t max_count;
sit = gen_sit(server, sd);
max_count = conn->ifc_max_allowed_stream_id[sit] >> SIT_SHIFT;
LSQ_DEBUG("sit-%u streams: max count: %"PRIu64"; created streams: %"PRIu64,
sit, max_count, conn->ifc_n_created_streams[sd]);
if (max_count >= conn->ifc_n_created_streams[sd])
return max_count - conn->ifc_n_created_streams[sd];
else
{
assert(0);
return 0;
}
}
/* If `priority' is negative, this means that the stream is critical */
static int
create_uni_stream_out (struct ietf_full_conn *conn, int priority,
const struct lsquic_stream_if *stream_if, void *stream_if_ctx)
{
struct lsquic_stream *stream;
lsquic_stream_id_t stream_id;
stream_id = generate_stream_id(conn, SD_UNI);
stream = lsquic_stream_new(stream_id, &conn->ifc_pub, stream_if,
stream_if_ctx, 0, conn->ifc_max_stream_data_uni,
SCF_IETF | (priority < 0 ? SCF_CRITICAL : 0));
if (!stream)
return -1;
if (!lsquic_hash_insert(conn->ifc_pub.all_streams, &stream->id,
sizeof(stream->id), stream, &stream->sm_hash_el))
{
lsquic_stream_destroy(stream);
return -1;
}
if (priority >= 0)
lsquic_stream_set_priority_internal(stream, priority);
lsquic_stream_call_on_new(stream);
return 0;
}
static int
create_ctl_stream_out (struct ietf_full_conn *conn)
{
return create_uni_stream_out(conn, -1,
lsquic_hcso_writer_if, &conn->ifc_hcso);
}
static int
create_qenc_stream_out (struct ietf_full_conn *conn)
{
return create_uni_stream_out(conn, -1,
lsquic_qeh_enc_sm_out_if, &conn->ifc_qeh);
}
static int
create_qdec_stream_out (struct ietf_full_conn *conn)
{
return create_uni_stream_out(conn, -1,
lsquic_qdh_dec_sm_out_if, &conn->ifc_qdh);
}
static int
create_bidi_stream_out (struct ietf_full_conn *conn)
{
struct lsquic_stream *stream;
lsquic_stream_id_t stream_id;
enum stream_ctor_flags flags;
flags = SCF_IETF|SCF_DI_AUTOSWITCH;
if (conn->ifc_enpub->enp_settings.es_rw_once)
flags |= SCF_DISP_RW_ONCE;
if (conn->ifc_enpub->enp_settings.es_delay_onclose)
flags |= SCF_DELAY_ONCLOSE;
if (conn->ifc_flags & IFC_HTTP)
{
flags |= SCF_HTTP;
if (conn->ifc_pii == &ext_prio_iter_if)
flags |= SCF_HTTP_PRIO;
}
stream_id = generate_stream_id(conn, SD_BIDI);
stream = lsquic_stream_new(stream_id, &conn->ifc_pub,
conn->ifc_enpub->enp_stream_if,
conn->ifc_enpub->enp_stream_if_ctx,
conn->ifc_settings->es_init_max_stream_data_bidi_local,
conn->ifc_cfg.max_stream_send, flags);
if (!stream)
return -1;
if (!lsquic_hash_insert(conn->ifc_pub.all_streams, &stream->id,
sizeof(stream->id), stream, &stream->sm_hash_el))
{
lsquic_stream_destroy(stream);
return -1;
}
lsquic_stream_call_on_new(stream);
return 0;
}
static struct lsquic_stream *
create_push_stream (struct ietf_full_conn *conn)
{
struct lsquic_stream *stream;
lsquic_stream_id_t stream_id;
enum stream_ctor_flags flags;
assert((conn->ifc_flags & (IFC_SERVER|IFC_HTTP)) == (IFC_SERVER|IFC_HTTP));
flags = SCF_IETF|SCF_HTTP;
if (conn->ifc_enpub->enp_settings.es_rw_once)
flags |= SCF_DISP_RW_ONCE;
if (conn->ifc_enpub->enp_settings.es_delay_onclose)
flags |= SCF_DELAY_ONCLOSE;
stream_id = generate_stream_id(conn, SD_UNI);
stream = lsquic_stream_new(stream_id, &conn->ifc_pub,
conn->ifc_enpub->enp_stream_if,
conn->ifc_enpub->enp_stream_if_ctx,
conn->ifc_settings->es_init_max_stream_data_bidi_local,
conn->ifc_cfg.max_stream_send, flags);
if (!stream)
return NULL;
if (!lsquic_hash_insert(conn->ifc_pub.all_streams, &stream->id,
sizeof(stream->id), stream, &stream->sm_hash_el))
{
lsquic_stream_destroy(stream);
return NULL;
}
return stream;
}
/* This function looks through the SCID array searching for an available
* slot. If it finds an available slot it will
* 1. generate an SCID,
* 2. mark with latest seqno,
* 3. increment seqno,
* 4. turn on CCE_SEQNO flag,
* 5. turn on flag given through flag paramter,
* 6. add cce to mask, and
* 7. add timestamp for when slot is new available for CID generation.
*/
static struct conn_cid_elem *
ietf_full_conn_add_scid (struct ietf_full_conn *conn,
struct lsquic_engine_public *enpub,
enum conn_cce_flags flags,
lsquic_time_t now)
{
struct conn_cid_elem *cce;
struct lsquic_conn *lconn = &conn->ifc_conn;
lsquic_time_t *min_timestamp;
int i;
if (enpub->enp_settings.es_scid_len)
{
for (cce = lconn->cn_cces; cce < END_OF_CCES(lconn); ++cce)
if (!(lconn->cn_cces_mask & (1 << (cce - lconn->cn_cces))))
break;
}
else if (0 == lconn->cn_cces_mask)
cce = lconn->cn_cces;
else
cce = END_OF_CCES(lconn);
if (cce >= END_OF_CCES(lconn))
{
LSQ_LOG1(LSQ_LOG_DEBUG, "cannot find slot for new SCID");
return NULL;
}
if (enpub->enp_settings.es_scid_len)
enpub->enp_generate_scid(lconn, &cce->cce_cid,
enpub->enp_settings.es_scid_len);
cce->cce_seqno = conn->ifc_scid_seqno++;
cce->cce_flags |= CCE_SEQNO | flags;
lconn->cn_cces_mask |= 1 << (cce - lconn->cn_cces);
++conn->ifc_active_cids_count;
if (enpub->enp_settings.es_scid_iss_rate)
{
min_timestamp = &conn->ifc_scid_timestamp[0];
for (i = 1; i < lconn->cn_n_cces; i++)
if (conn->ifc_scid_timestamp[i] < *min_timestamp)
min_timestamp = &conn->ifc_scid_timestamp[i];
*min_timestamp = now;
}
LSQ_LOG1C(LSQ_LOG_DEBUG, "generated and assigned SCID %"CID_FMT,
CID_BITS(&cce->cce_cid));
return cce;
}
/* From [draft-ietf-quic-transport-25] Section 17.3.1:
* " endpoints MUST disable their use of the spin bit for a random selection
* " of at least one in every 16 network paths, or for one in every 16
* " connection IDs.
*/
static void
maybe_enable_spin (struct ietf_full_conn *conn, struct conn_path *cpath)
{
uint8_t nyb;
if (conn->ifc_settings->es_spin
&& lsquic_crand_get_nybble(conn->ifc_enpub->enp_crand))
{
cpath->cop_flags |= COP_SPIN_BIT;
cpath->cop_spin_bit = 0;
LSQ_DEBUG("spin bit enabled on path %hhu", cpath->cop_path.np_path_id);
}
else
{
/* " It is RECOMMENDED that endpoints set the spin bit to a random
* " value either chosen independently for each packet or chosen
* " independently for each connection ID.
* (ibid.)
*/
cpath->cop_flags &= ~COP_SPIN_BIT;
nyb = lsquic_crand_get_nybble(conn->ifc_enpub->enp_crand);
cpath->cop_spin_bit = nyb & 1;
LSQ_DEBUG("spin bit disabled %s on path %hhu; random spin bit "
"value is %hhu",
!conn->ifc_settings->es_spin ? "via settings" : "randomly",
cpath->cop_path.np_path_id, cpath->cop_spin_bit);
}
}
static int
ietf_full_conn_init (struct ietf_full_conn *conn,
struct lsquic_engine_public *enpub, unsigned flags, int ecn)
{
if (flags & IFC_SERVER)
conn->ifc_conn.cn_if = ietf_full_conn_iface_ptr;
else
conn->ifc_conn.cn_if = ietf_full_conn_prehsk_iface_ptr;
if (enpub->enp_settings.es_scid_len)
assert(CN_SCID(&conn->ifc_conn)->len);
conn->ifc_enpub = enpub;
conn->ifc_settings = &enpub->enp_settings;
conn->ifc_pub.lconn = &conn->ifc_conn;
conn->ifc_pub.send_ctl = &conn->ifc_send_ctl;
conn->ifc_pub.enpub = enpub;
conn->ifc_pub.mm = &enpub->enp_mm;
#if LSQUIC_CONN_STATS
conn->ifc_pub.conn_stats = &conn->ifc_stats;
#endif
conn->ifc_pub.path = CUR_NPATH(conn);
TAILQ_INIT(&conn->ifc_pub.sending_streams);
TAILQ_INIT(&conn->ifc_pub.read_streams);
TAILQ_INIT(&conn->ifc_pub.write_streams);
TAILQ_INIT(&conn->ifc_pub.service_streams);
STAILQ_INIT(&conn->ifc_stream_ids_to_ss);
TAILQ_INIT(&conn->ifc_to_retire);
lsquic_alarmset_init(&conn->ifc_alset, &conn->ifc_conn);
lsquic_alarmset_init_alarm(&conn->ifc_alset, AL_IDLE, idle_alarm_expired, conn);
lsquic_alarmset_init_alarm(&conn->ifc_alset, AL_ACK_APP, ack_alarm_expired, conn);
lsquic_alarmset_init_alarm(&conn->ifc_alset, AL_PING, ping_alarm_expired, conn);
lsquic_alarmset_init_alarm(&conn->ifc_alset, AL_HANDSHAKE, handshake_alarm_expired, conn);
lsquic_alarmset_init_alarm(&conn->ifc_alset, AL_CID_THROT, cid_throt_alarm_expired, conn);
lsquic_alarmset_init_alarm(&conn->ifc_alset, AL_PATH_CHAL_0, path_chal_alarm_expired, conn);
lsquic_alarmset_init_alarm(&conn->ifc_alset, AL_PATH_CHAL_1, path_chal_alarm_expired, conn);
lsquic_alarmset_init_alarm(&conn->ifc_alset, AL_PATH_CHAL_2, path_chal_alarm_expired, conn);
lsquic_alarmset_init_alarm(&conn->ifc_alset, AL_PATH_CHAL_3, path_chal_alarm_expired, conn);
lsquic_alarmset_init_alarm(&conn->ifc_alset, AL_BLOCKED_KA, blocked_ka_alarm_expired, conn);
lsquic_alarmset_init_alarm(&conn->ifc_alset, AL_MTU_PROBE, mtu_probe_alarm_expired, conn);
/* For Init and Handshake, we don't expect many ranges at all. For
* the regular receive history, set limit to a value that would never
* be reached under normal circumstances, yet small enough that would
* use little memory when under attack and be robust (fast). The
* value 1000 limits receive history to about 16KB.
*/
lsquic_rechist_init(&conn->ifc_rechist[PNS_INIT], 1, 10);
lsquic_rechist_init(&conn->ifc_rechist[PNS_HSK], 1, 10);
lsquic_rechist_init(&conn->ifc_rechist[PNS_APP], 1, 1000);
lsquic_send_ctl_init(&conn->ifc_send_ctl, &conn->ifc_alset, enpub,
flags & IFC_SERVER ? &server_ver_neg : &conn->ifc_u.cli.ifcli_ver_neg,
&conn->ifc_pub, SC_IETF|SC_NSTP|(ecn ? SC_ECN : 0));
lsquic_cfcw_init(&conn->ifc_pub.cfcw, &conn->ifc_pub,
conn->ifc_settings->es_init_max_data);
conn->ifc_pub.all_streams = lsquic_hash_create();
if (!conn->ifc_pub.all_streams)
return -1;
conn->ifc_pub.u.ietf.qeh = &conn->ifc_qeh;
conn->ifc_pub.u.ietf.qdh = &conn->ifc_qdh;
conn->ifc_pub.u.ietf.hcso = &conn->ifc_hcso;
conn->ifc_peer_hq_settings.header_table_size = HQ_DF_QPACK_MAX_TABLE_CAPACITY;
conn->ifc_peer_hq_settings.qpack_blocked_streams = HQ_DF_QPACK_BLOCKED_STREAMS;
conn->ifc_flags = flags | IFC_FIRST_TICK;
conn->ifc_max_ack_packno[PNS_INIT] = IQUIC_INVALID_PACKNO;
conn->ifc_max_ack_packno[PNS_HSK] = IQUIC_INVALID_PACKNO;
conn->ifc_max_ack_packno[PNS_APP] = IQUIC_INVALID_PACKNO;
conn->ifc_max_ackable_packno_in = 0;
conn->ifc_paths[0].cop_path.np_path_id = 0;
conn->ifc_paths[1].cop_path.np_path_id = 1;
conn->ifc_paths[2].cop_path.np_path_id = 2;
conn->ifc_paths[3].cop_path.np_path_id = 3;
#define valid_stream_id(v) ((v) <= VINT_MAX_VALUE)
conn->ifc_max_req_id = VINT_MAX_VALUE + 1;
conn->ifc_ping_unretx_thresh = 20;
conn->ifc_max_retx_since_last_ack = MAX_RETR_PACKETS_SINCE_LAST_ACK;
conn->ifc_max_ack_delay = ACK_TIMEOUT;
if (conn->ifc_settings->es_noprogress_timeout)
conn->ifc_mflags |= MF_NOPROG_TIMEOUT;
if (conn->ifc_settings->es_ext_http_prio)
conn->ifc_pii = &ext_prio_iter_if;
else
conn->ifc_pii = &orig_prio_iter_if;
return 0;
}
struct lsquic_conn *
lsquic_ietf_full_conn_client_new (struct lsquic_engine_public *enpub,
unsigned versions, unsigned flags,
const char *hostname, unsigned short base_plpmtu, int is_ipv4,
const unsigned char *sess_resume, size_t sess_resume_sz,
const unsigned char *token, size_t token_sz, void* peer_ctx)
{
const struct transport_params *params;
const struct enc_session_funcs_iquic *esfi;
struct ietf_full_conn *conn;
enum lsquic_version ver, sess_resume_version;
lsquic_time_t now;
conn = calloc(1, sizeof(*conn));
if (!conn)
goto err0;
now = lsquic_time_now();
/* Set the flags early so that correct CID is used for logging */
conn->ifc_conn.cn_flags |= LSCONN_IETF;
conn->ifc_conn.cn_cces = conn->ifc_cces;
conn->ifc_conn.cn_n_cces = sizeof(conn->ifc_cces)
/ sizeof(conn->ifc_cces[0]);
if (!ietf_full_conn_add_scid(conn, enpub, CCE_USED, now))
goto err1;
assert(versions);
versions &= LSQUIC_IETF_VERSIONS;
ver = highest_bit_set(versions);
if (sess_resume)
{
sess_resume_version = lsquic_sess_resume_version(sess_resume, sess_resume_sz);
if (sess_resume_version < N_LSQVER && ((1 << sess_resume_version) & versions))
ver = sess_resume_version;
}
esfi = select_esf_iquic_by_ver(ver);
if (0 != ietf_full_conn_init(conn, enpub, flags,
enpub->enp_settings.es_ecn))
goto err2;
if (base_plpmtu)
conn->ifc_paths[0].cop_path.np_pack_size
= base_plpmtu - TRANSPORT_OVERHEAD(!is_ipv4);
else
conn->ifc_paths[0].cop_path.np_pack_size
= calc_base_packet_size(conn, !is_ipv4);
if (token)
{
if (0 != lsquic_send_ctl_set_token(&conn->ifc_send_ctl, token,
token_sz))
goto err2;
}
/* Do not infer anything about server limits before processing its
* transport parameters.
*/
conn->ifc_max_streams_in[SD_BIDI] = enpub->enp_settings.es_max_streams_in;
conn->ifc_max_allowed_stream_id[SIT_BIDI_SERVER] =
enpub->enp_settings.es_max_streams_in << SIT_SHIFT;
if (flags & IFC_HTTP)
{
if (enpub->enp_settings.es_support_push && CLIENT_PUSH_SUPPORT)
conn->ifc_max_streams_in[SD_UNI]
= MAX(3, enpub->enp_settings.es_max_streams_in);
else
conn->ifc_max_streams_in[SD_UNI] = 3;
}
else
conn->ifc_max_streams_in[SD_UNI] = enpub->enp_settings.es_max_streams_in;
conn->ifc_max_allowed_stream_id[SIT_UNI_SERVER]
= conn->ifc_max_streams_in[SD_UNI] << SIT_SHIFT;
init_ver_neg(conn, versions, &ver);
assert(ver == conn->ifc_u.cli.ifcli_ver_neg.vn_ver);
if (conn->ifc_settings->es_handshake_to)
lsquic_alarmset_set(&conn->ifc_alset, AL_HANDSHAKE,
lsquic_time_now() + conn->ifc_settings->es_handshake_to);
conn->ifc_idle_to = conn->ifc_settings->es_idle_timeout * 1000000;
if (conn->ifc_idle_to)
lsquic_alarmset_set(&conn->ifc_alset, AL_IDLE, now + conn->ifc_idle_to);
if (enpub->enp_settings.es_support_push && CLIENT_PUSH_SUPPORT)
{
conn->ifc_u.cli.ifcli_flags |= IFCLI_PUSH_ENABLED;
conn->ifc_u.cli.ifcli_max_push_id = 100;
LSQ_DEBUG("push enabled: set MAX_PUSH_ID to %"PRIu64,
conn->ifc_u.cli.ifcli_max_push_id);
}
conn->ifc_conn.cn_pf = select_pf_by_ver(ver);
conn->ifc_conn.cn_esf_c = select_esf_common_by_ver(ver);
conn->ifc_conn.cn_esf.i = esfi;
lsquic_generate_cid(CUR_DCID(conn), 0);
conn->ifc_conn.cn_enc_session =
conn->ifc_conn.cn_esf.i->esfi_create_client(hostname,
conn->ifc_enpub, &conn->ifc_conn, CUR_DCID(conn),
&conn->ifc_u.cli.ifcli_ver_neg,
(void **) conn->ifc_u.cli.crypto_streams, &crypto_stream_if,
sess_resume, sess_resume_sz, &conn->ifc_alset,
conn->ifc_max_streams_in[SD_UNI], peer_ctx);
if (!conn->ifc_conn.cn_enc_session)
goto err2;
conn->ifc_u.cli.crypto_streams[ENC_LEV_CLEAR] = lsquic_stream_new_crypto(
ENC_LEV_CLEAR, &conn->ifc_pub, &lsquic_cry_sm_if,
conn->ifc_conn.cn_enc_session,
SCF_IETF|SCF_DI_AUTOSWITCH|SCF_CALL_ON_NEW|SCF_CRITICAL);
if (!conn->ifc_u.cli.crypto_streams[ENC_LEV_CLEAR])
goto err3;
if (!lsquic_stream_get_ctx(conn->ifc_u.cli.crypto_streams[ENC_LEV_CLEAR]))
goto err4;
conn->ifc_pub.packet_out_malo =
lsquic_malo_create(sizeof(struct lsquic_packet_out));
if (!conn->ifc_pub.packet_out_malo)
goto err4;
conn->ifc_flags |= IFC_PROC_CRYPTO;
LSQ_DEBUG("negotiating version %s",
lsquic_ver2str[conn->ifc_u.cli.ifcli_ver_neg.vn_ver]);
conn->ifc_process_incoming_packet = process_incoming_packet_verneg;
conn->ifc_created = now;
LSQ_DEBUG("logging using %s SCID",
LSQUIC_LOG_CONN_ID == CN_SCID(&conn->ifc_conn) ? "client" : "server");
if (sess_resume && (params
= conn->ifc_conn.cn_esf.i->esfi_get_peer_transport_params(
conn->ifc_conn.cn_enc_session), params != NULL))
{
LSQ_DEBUG("initializing transport parameters for 0RTT");
if (0 != apply_trans_params(conn, params))
goto full_err;
if ((conn->ifc_flags & IFC_HTTP) && 0 != init_http(conn))
goto full_err;
conn->ifc_mflags |= MF_DOING_0RTT;
}
conn->ifc_flags |= IFC_CREATED_OK;
return &conn->ifc_conn;
err4:
lsquic_stream_destroy(conn->ifc_u.cli.crypto_streams[ENC_LEV_CLEAR]);
err3:
conn->ifc_conn.cn_esf.i->esfi_destroy(conn->ifc_conn.cn_enc_session);
err2:
lsquic_send_ctl_cleanup(&conn->ifc_send_ctl);
if (conn->ifc_pub.all_streams)
lsquic_hash_destroy(conn->ifc_pub.all_streams);
err1:
free(conn);
err0:
return NULL;
full_err:
ietf_full_conn_ci_destroy(&conn->ifc_conn);
return NULL;
}
typedef char mini_conn_does_not_have_more_cces[
sizeof(((struct ietf_mini_conn *)0)->imc_cces)
<= sizeof(((struct ietf_full_conn *)0)->ifc_cces) ? 1 : -1];
struct lsquic_conn *
lsquic_ietf_full_conn_server_new (struct lsquic_engine_public *enpub,
unsigned flags, struct lsquic_conn *mini_conn)
{
struct ietf_mini_conn *const imc = (void *) mini_conn;
struct ietf_full_conn *conn;
struct lsquic_packet_out *packet_out;
struct lsquic_packet_in *packet_in;
struct conn_cid_elem *cce;
int have_outgoing_ack;
lsquic_packno_t next_packno;
lsquic_time_t now;
enum packnum_space pns;
unsigned i;
struct ietf_mini_rechist mini_rechist;
conn = calloc(1, sizeof(*conn));
if (!conn)
goto err0;
now = lsquic_time_now();
conn->ifc_conn.cn_cces = conn->ifc_cces;
conn->ifc_conn.cn_n_cces = sizeof(conn->ifc_cces)
/ sizeof(conn->ifc_cces[0]);
assert(conn->ifc_conn.cn_n_cces >= mini_conn->cn_n_cces);
conn->ifc_conn.cn_cur_cce_idx = mini_conn->cn_cur_cce_idx;
conn->ifc_conn.cn_cces_mask = mini_conn->cn_cces_mask;
for (cce = mini_conn->cn_cces, i = 0; cce < END_OF_CCES(mini_conn);
++cce, ++i)
if ((1 << (cce - mini_conn->cn_cces)) & mini_conn->cn_cces_mask)
{
conn->ifc_conn.cn_cces[i].cce_cid = cce->cce_cid;
conn->ifc_conn.cn_cces[i].cce_flags = cce->cce_flags;
if (cce->cce_flags & CCE_SEQNO)
{
if (cce->cce_seqno > conn->ifc_scid_seqno)
conn->ifc_scid_seqno = cce->cce_seqno;
conn->ifc_conn.cn_cces[i].cce_seqno = cce->cce_seqno;
++conn->ifc_active_cids_count;
}
conn->ifc_scid_timestamp[i] = now;
}
++conn->ifc_scid_seqno;
/* Set the flags early so that correct CID is used for logging */
conn->ifc_conn.cn_flags |= LSCONN_IETF | LSCONN_SERVER;
if (0 != ietf_full_conn_init(conn, enpub, flags,
lsquic_mini_conn_ietf_ecn_ok(imc)))
goto err1;
conn->ifc_pub.packet_out_malo =
lsquic_malo_create(sizeof(struct lsquic_packet_out));
if (!conn->ifc_pub.packet_out_malo)
goto err1;
if (imc->imc_flags & IMC_IGNORE_INIT)
conn->ifc_flags |= IFC_IGNORE_INIT;
conn->ifc_paths[0].cop_path = imc->imc_path;
conn->ifc_paths[0].cop_flags = COP_VALIDATED|COP_INITIALIZED;
conn->ifc_used_paths = 1 << 0;
maybe_enable_spin(conn, &conn->ifc_paths[0]);
if (imc->imc_flags & IMC_ADDR_VALIDATED)
lsquic_send_ctl_path_validated(&conn->ifc_send_ctl);
else
conn->ifc_mflags |= MF_VALIDATE_PATH;
conn->ifc_pub.bytes_out = imc->imc_bytes_out;
conn->ifc_pub.bytes_in = imc->imc_bytes_in;
if (imc->imc_flags & IMC_PATH_CHANGED)
{
LSQ_DEBUG("path changed during mini conn: schedule PATH_CHALLENGE");
conn->ifc_send_flags |= SF_SEND_PATH_CHAL_PATH_0;
}
conn->ifc_max_streams_in[SD_BIDI]
= enpub->enp_settings.es_init_max_streams_bidi;
conn->ifc_max_allowed_stream_id[SIT_BIDI_CLIENT]
= conn->ifc_max_streams_in[SD_BIDI] << SIT_SHIFT;
conn->ifc_max_streams_in[SD_UNI]
= enpub->enp_settings.es_init_max_streams_uni;
conn->ifc_max_allowed_stream_id[SIT_UNI_CLIENT]
= conn->ifc_max_streams_in[SD_UNI] << SIT_SHIFT;
conn->ifc_conn.cn_version = mini_conn->cn_version;
conn->ifc_conn.cn_flags |= LSCONN_VER_SET;
conn->ifc_conn.cn_pf = mini_conn->cn_pf;
conn->ifc_conn.cn_esf_c = mini_conn->cn_esf_c;
conn->ifc_conn.cn_esf = mini_conn->cn_esf;
if (enpub->enp_settings.es_support_push)
conn->ifc_u.ser.ifser_flags |= IFSER_PUSH_ENABLED;
if (flags & IFC_HTTP)
{
fiu_do_on("full_conn_ietf/promise_hash", goto promise_alloc_failed);
conn->ifc_pub.u.ietf.promises = lsquic_hash_create();
#if FIU_ENABLE
promise_alloc_failed:
#endif
if (!conn->ifc_pub.u.ietf.promises)
goto err2;
}
assert(mini_conn->cn_flags & LSCONN_HANDSHAKE_DONE);
conn->ifc_conn.cn_flags |= LSCONN_HANDSHAKE_DONE;
if (!(imc->imc_flags & IMC_HSK_DONE_SENT))
{
LSQ_DEBUG("HANDSHAKE_DONE not yet sent, will process CRYPTO frames");
conn->ifc_flags |= IFC_PROC_CRYPTO;
}
conn->ifc_conn.cn_enc_session = mini_conn->cn_enc_session;
mini_conn->cn_enc_session = NULL;
conn->ifc_conn.cn_esf_c->esf_set_conn(conn->ifc_conn.cn_enc_session,
&conn->ifc_conn);
conn->ifc_process_incoming_packet = process_incoming_packet_fast;
conn->ifc_send_ctl.sc_cur_packno = imc->imc_next_packno - 1;
lsquic_send_ctl_begin_optack_detection(&conn->ifc_send_ctl);
for (pns = 0; pns < IMICO_N_PNS; ++pns)
{
lsquic_imico_rechist_init(&mini_rechist, imc, pns);
if (pns < IMICO_N_PNS)
{
if (0 != lsquic_rechist_copy_ranges(&conn->ifc_rechist[pns],
&mini_rechist, lsquic_imico_rechist_first,
lsquic_imico_rechist_next))
goto err2;
conn->ifc_rechist[pns].rh_largest_acked_received
= imc->imc_largest_recvd[pns];
}
}
/* Mini connection sends out packets 0, 1, 2... and so on. It deletes
* packets that have been successfully sent and acked or those that have
* been lost. We take ownership of all packets in mc_packets_out; those
* that are not on the list are recorded in fc_send_ctl.sc_senhist.
*/
have_outgoing_ack = 0;
next_packno = ~0ULL;
/* mini conn may drop Init packets, making gaps; don't warn about them: */
conn->ifc_send_ctl.sc_senhist.sh_flags |= SH_GAP_OK;
while ((packet_out = TAILQ_FIRST(&imc->imc_packets_out)))
{
TAILQ_REMOVE(&imc->imc_packets_out, packet_out, po_next);
/* Holes in the sequence signify no-longer-relevant Initial packets or
* ACKed or lost packets.
*/
++next_packno;
for ( ; next_packno < packet_out->po_packno; ++next_packno)
{
lsquic_senhist_add(&conn->ifc_send_ctl.sc_senhist, next_packno);
conn->ifc_send_ctl.sc_senhist.sh_warn_thresh = next_packno;
}
packet_out->po_path = CUR_NPATH(conn);
if (imc->imc_sent_packnos & (1ULL << packet_out->po_packno))
{
LSQ_DEBUG("got sent packet_out %"PRIu64" from mini",
packet_out->po_packno);
if (0 != lsquic_send_ctl_sent_packet(&conn->ifc_send_ctl,
packet_out))
{
LSQ_WARN("could not add packet %"PRIu64" to sent set: %s",
packet_out->po_packno, strerror(errno));
goto err2;
}
}
else
{
LSQ_DEBUG("got unsent packet_out %"PRIu64" from mini (will send)",
packet_out->po_packno);
lsquic_send_ctl_scheduled_one(&conn->ifc_send_ctl, packet_out);
have_outgoing_ack |= packet_out->po_frame_types &
(1 << QUIC_FRAME_ACK);
}
}
conn->ifc_send_ctl.sc_senhist.sh_flags &= ~SH_GAP_OK;
/* ...Yes, that's a bunch of little annoying steps to suppress the gap
* warnings, but it would have been even more annoying (and expensive)
* to add packet renumbering logic to the mini conn.
*/
for (pns = 0; pns < IMICO_N_PNS; ++pns)
for (i = 0; i < 4; ++i)
{
conn->ifc_ecn_counts_in[pns][i] = imc->imc_ecn_counts_in[pns][i];
conn->ifc_ecn_counts_out[pns][i] = imc->imc_ecn_counts_out[pns][i];
}
conn->ifc_incoming_ecn = imc->imc_incoming_ecn;
conn->ifc_pub.rtt_stats = imc->imc_rtt_stats;
lsquic_alarmset_init_alarm(&conn->ifc_alset, AL_RET_CIDS,
ret_cids_alarm_expired, conn);
lsquic_alarmset_set(&conn->ifc_alset, AL_RET_CIDS,
now + RET_CID_TIMEOUT);
conn->ifc_last_live_update = now;
LSQ_DEBUG("Calling on_new_conn callback");
conn->ifc_conn.cn_conn_ctx = conn->ifc_enpub->enp_stream_if->on_new_conn(
conn->ifc_enpub->enp_stream_if_ctx, &conn->ifc_conn);
if (0 != handshake_ok(&conn->ifc_conn))
goto err3;
conn->ifc_created = imc->imc_created;
conn->ifc_idle_to = conn->ifc_settings->es_idle_timeout * 1000000;
if (conn->ifc_idle_to)
lsquic_alarmset_set(&conn->ifc_alset, AL_IDLE,
imc->imc_created + conn->ifc_idle_to);
while ((packet_in = TAILQ_FIRST(&imc->imc_app_packets)))
{
TAILQ_REMOVE(&imc->imc_app_packets, packet_in, pi_next);
LSQ_DEBUG("inherit packet %"PRIu64" from mini conn",
packet_in->pi_packno);
ietf_full_conn_ci_packet_in(&conn->ifc_conn, packet_in);
lsquic_packet_in_put(conn->ifc_pub.mm, packet_in);
}
LSQ_DEBUG("logging using %s SCID",
LSQUIC_LOG_CONN_ID == CN_SCID(&conn->ifc_conn) ? "server" : "client");
conn->ifc_flags |= IFC_CREATED_OK;
return &conn->ifc_conn;
err3:
ietf_full_conn_ci_destroy(&conn->ifc_conn);
return NULL;
err2:
lsquic_malo_destroy(conn->ifc_pub.packet_out_malo);
err1:
lsquic_send_ctl_cleanup(&conn->ifc_send_ctl);
if (conn->ifc_pub.all_streams)
lsquic_hash_destroy(conn->ifc_pub.all_streams);
free(conn);
err0:
return NULL;
}
static int
should_generate_ack (struct ietf_full_conn *conn,
enum ifull_conn_flags ack_queued)
{
unsigned lost_acks;
/* Need to set which ACKs are queued because generate_ack_frame() does not
* generate ACKs unconditionally.
*/
lost_acks = lsquic_send_ctl_lost_ack(&conn->ifc_send_ctl);
if (lost_acks)
conn->ifc_flags |= lost_acks << IFCBIT_ACK_QUED_SHIFT;
return (conn->ifc_flags & ack_queued) != 0;
}
static int
ietf_full_conn_ci_can_write_ack (struct lsquic_conn *lconn)
{
struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn;
/* Follow opportunistic ACK logic. Because this method is only used by
* buffered packets code path, no need to check whether anything is
* writing: we know it is.
*/
return conn->ifc_n_slack_akbl[PNS_APP] > 0
&& lsquic_send_ctl_can_send(&conn->ifc_send_ctl);
}
static unsigned
ietf_full_conn_ci_cancel_pending_streams (struct lsquic_conn *lconn, unsigned n)
{
struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn;
if (n > conn->ifc_n_delayed_streams)
conn->ifc_n_delayed_streams = 0;
else
conn->ifc_n_delayed_streams -= n;
return conn->ifc_n_delayed_streams;
}
/* Best effort. If timestamp frame does not fit, oh well */
static void
generate_timestamp_frame (struct ietf_full_conn *conn,
struct lsquic_packet_out *packet_out, lsquic_time_t now)
{
uint64_t timestamp;
int w;
timestamp = (now - conn->ifc_created) >> TP_DEF_ACK_DELAY_EXP;
w = conn->ifc_conn.cn_pf->pf_gen_timestamp_frame(
packet_out->po_data + packet_out->po_data_sz,
lsquic_packet_out_avail(packet_out), timestamp);
if (w < 0)
{
LSQ_DEBUG("could not generate TIMESTAMP frame");
return;
}
LSQ_DEBUG("generated TIMESTAMP(%"PRIu64" us) frame",
timestamp << TP_DEF_ACK_DELAY_EXP);
EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "generated TIMESTAMP(%"
PRIu64" us) frame", timestamp << TP_DEF_ACK_DELAY_EXP);
if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0,
QUIC_FRAME_TIMESTAMP, packet_out->po_data_sz, w))
{
LSQ_DEBUG("%s: adding frame to packet failed: %d", __func__, errno);
return;
}
packet_out->po_frame_types |= 1 << QUIC_FRAME_TIMESTAMP;
lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, w);
packet_out->po_regen_sz += w;
}
struct ietf_ack_state
{
enum ifull_conn_flags conn_flags;
enum send_flags send_flags;
enum alarm_id_bit armed_set;
unsigned n_slack_akbl;
unsigned n_slack_all;
unsigned char unretx_thresh;
};
typedef char ack_state_size[sizeof(struct ietf_ack_state)
<= sizeof(struct ack_state) ? 1 : - 1];
static void
ietf_full_conn_ci_ack_snapshot (struct lsquic_conn *lconn,
struct ack_state *opaque)
{
struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn;
struct ietf_ack_state *const ack_state = (struct ietf_ack_state *) opaque;
ack_state->conn_flags = conn->ifc_flags;
ack_state->send_flags = conn->ifc_send_flags;
ack_state->armed_set = conn->ifc_alset.as_armed_set;
ack_state->n_slack_akbl = conn->ifc_n_slack_akbl[PNS_APP];
ack_state->n_slack_all = conn->ifc_n_slack_all;
ack_state->unretx_thresh= conn->ifc_ping_unretx_thresh;
LSQ_DEBUG("take ACK snapshot");
}
static void
ietf_full_conn_ci_ack_rollback (struct lsquic_conn *lconn,
struct ack_state *opaque)
{
struct ietf_ack_state *const ack_state = (struct ietf_ack_state *) opaque;
struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn;
conn->ifc_flags &= ~(IFC_ACK_HAD_MISS|IFC_ACK_QUED_APP);
conn->ifc_flags |= (IFC_ACK_HAD_MISS|IFC_ACK_QUED_APP)
& ack_state->conn_flags;
conn->ifc_send_flags &= ~SF_SEND_PING;
conn->ifc_send_flags |= SF_SEND_PING & ack_state->send_flags;
conn->ifc_alset.as_armed_set &= ~ALBIT_ACK_APP;
conn->ifc_alset.as_armed_set |= ALBIT_ACK_APP & ack_state->armed_set;
conn->ifc_n_slack_akbl[PNS_APP] = ack_state->n_slack_akbl;
conn->ifc_n_slack_all = ack_state->n_slack_all;
conn->ifc_ping_unretx_thresh = ack_state->unretx_thresh;
LSQ_DEBUG("roll back ACK state");
}
static int
generate_ack_frame_for_pns (struct ietf_full_conn *conn,
struct lsquic_packet_out *packet_out, enum packnum_space pns,
lsquic_time_t now)
{
const uint64_t *ecn_counts;
int has_missing, w;
if (conn->ifc_incoming_ecn
&& lsquic_send_ctl_ecn_turned_on(&conn->ifc_send_ctl))
ecn_counts = conn->ifc_ecn_counts_in[pns];
else if ((conn->ifc_mflags & MF_SEND_WRONG_COUNTS) && pns == PNS_APP)
{
/* We try once. A more advanced version would wait until we get a
* packet from peer and only then stop.
*/
conn->ifc_mflags &= ~MF_SEND_WRONG_COUNTS;
ecn_counts = conn->ifc_ecn_counts_in[pns];
}
else
ecn_counts = NULL;
w = conn->ifc_conn.cn_pf->pf_gen_ack_frame(
packet_out->po_data + packet_out->po_data_sz,
lsquic_packet_out_avail(packet_out),
(gaf_rechist_first_f) lsquic_rechist_first,
(gaf_rechist_next_f) lsquic_rechist_next,
(gaf_rechist_largest_recv_f) lsquic_rechist_largest_recv,
&conn->ifc_rechist[pns], now, &has_missing, &packet_out->po_ack2ed,
ecn_counts);
if (w < 0) {
ABORT_ERROR("generating ACK frame failed: %d", errno);
return -1;
}
CONN_STATS(out.acks, 1);
char buf[0x100];
lsquic_hexstr(packet_out->po_data + packet_out->po_data_sz, w, buf, sizeof(buf));
LSQ_DEBUG("ACK bytes: %s", buf);
EV_LOG_GENERATED_ACK_FRAME(LSQUIC_LOG_CONN_ID, conn->ifc_conn.cn_pf,
packet_out->po_data + packet_out->po_data_sz, w);
lsquic_send_ctl_scheduled_ack(&conn->ifc_send_ctl, pns,
packet_out->po_ack2ed);
packet_out->po_frame_types |= 1 << QUIC_FRAME_ACK;
if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0,
QUIC_FRAME_ACK, packet_out->po_data_sz, w))
{
ABORT_ERROR("adding frame to packet failed: %d", errno);
return -1;
}
lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, w);
packet_out->po_regen_sz += w;
if (has_missing && !(conn->ifc_mflags & MF_IGNORE_MISSING))
conn->ifc_flags |= IFC_ACK_HAD_MISS;
else
conn->ifc_flags &= ~IFC_ACK_HAD_MISS;
LSQ_DEBUG("Put %d bytes of ACK frame into packet on outgoing queue", w);
if (conn->ifc_n_cons_unretx >= conn->ifc_ping_unretx_thresh &&
!lsquic_send_ctl_have_outgoing_retx_frames(&conn->ifc_send_ctl))
{
LSQ_DEBUG("schedule PING frame after %u non-retx "
"packets sent", conn->ifc_n_cons_unretx);
conn->ifc_send_flags |= SF_SEND_PING;
/* This gives a range [12, 27]: */
conn->ifc_ping_unretx_thresh = 12
+ lsquic_crand_get_nybble(conn->ifc_enpub->enp_crand);
}
conn->ifc_n_slack_akbl[pns] = 0;
conn->ifc_flags &= ~(IFC_ACK_QUED_INIT << pns);
if (pns == PNS_APP)
{
conn->ifc_n_slack_all = 0;
lsquic_alarmset_unset(&conn->ifc_alset, AL_ACK_APP);
}
lsquic_send_ctl_sanity_check(&conn->ifc_send_ctl);
LSQ_DEBUG("%s ACK state reset", lsquic_pns2str[pns]);
if (pns == PNS_APP && (conn->ifc_flags & IFC_TIMESTAMPS))
generate_timestamp_frame(conn, packet_out, now);
return 0;
}
/* Return number of packets scheduled or 0 on error */
static unsigned
generate_ack_frame (struct ietf_full_conn *conn, lsquic_time_t now)
{
struct lsquic_packet_out *packet_out;
enum packnum_space pns;
unsigned count;
int s;
count = 0;
for (pns = 0; pns < N_PNS; ++pns)
if (conn->ifc_flags & (IFC_ACK_QUED_INIT << pns))
{
packet_out = lsquic_send_ctl_new_packet_out(&conn->ifc_send_ctl,
0, pns, CUR_NPATH(conn));
if (!packet_out)
{
ABORT_ERROR("cannot allocate packet: %s", strerror(errno));
return 0;
}
s = generate_ack_frame_for_pns(conn, packet_out, pns, now);
lsquic_send_ctl_scheduled_one(&conn->ifc_send_ctl, packet_out);
if (s != 0)
return 0;
++count;
}
return count;
}
static struct lsquic_packet_out *
get_writeable_packet_on_path (struct ietf_full_conn *conn,
unsigned need_at_least, const struct network_path *path,
int regen_match)
{
struct lsquic_packet_out *packet_out;
int is_err;
packet_out = lsquic_send_ctl_get_writeable_packet(&conn->ifc_send_ctl,
PNS_APP, need_at_least, path, regen_match, &is_err);
if (!packet_out && is_err)
ABORT_ERROR("cannot allocate packet: %s", strerror(errno));
return packet_out;
}
static struct lsquic_packet_out *
get_writeable_packet (struct ietf_full_conn *conn, unsigned need_at_least)
{
return get_writeable_packet_on_path(conn, need_at_least,
CUR_NPATH(conn), 0);
}
static void
generate_max_data_frame (struct ietf_full_conn *conn)
{
const uint64_t offset = lsquic_cfcw_get_fc_recv_off(&conn->ifc_pub.cfcw);
struct lsquic_packet_out *packet_out;
unsigned need;
int w;
need = conn->ifc_conn.cn_pf->pf_max_data_frame_size(offset);
packet_out = get_writeable_packet(conn, need);
if (!packet_out)
return;
w = conn->ifc_conn.cn_pf->pf_gen_max_data_frame(
packet_out->po_data + packet_out->po_data_sz,
lsquic_packet_out_avail(packet_out), offset);
if (w < 0)
{
ABORT_ERROR("Generating MAX_DATA frame failed");
return;
}
LSQ_DEBUG("generated %d-byte MAX_DATA frame (offset: %"PRIu64")", w, offset);
EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "generated MAX_DATA frame, offset=%"
PRIu64, offset);
if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0,
QUIC_FRAME_MAX_DATA, packet_out->po_data_sz, w))
{
ABORT_ERROR("adding frame to packet failed: %d", errno);
return;
}
lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, w);
packet_out->po_frame_types |= QUIC_FTBIT_MAX_DATA;
conn->ifc_send_flags &= ~SF_SEND_MAX_DATA;
conn->ifc_last_max_data_off_sent = offset;
}
static int
can_issue_cids (const struct ietf_full_conn *conn)
{
int can;
can = ((1 << conn->ifc_conn.cn_n_cces) - 1
!= conn->ifc_conn.cn_cces_mask)
&& conn->ifc_active_cids_count < conn->ifc_active_cids_limit;
LSQ_DEBUG("can issue CIDs: %d (n_cces %hhu; mask: 0x%hhX; "
"active: %hhu; limit: %hhu)",
can, conn->ifc_conn.cn_n_cces, conn->ifc_conn.cn_cces_mask,
conn->ifc_active_cids_count, conn->ifc_active_cids_limit);
return can;
}
static int
generate_new_cid_frame (struct ietf_full_conn *conn, lsquic_time_t now)
{
struct lsquic_packet_out *packet_out;
struct conn_cid_elem *cce;
size_t need;
int w;
unsigned char token_buf[IQUIC_SRESET_TOKEN_SZ];
assert(conn->ifc_enpub->enp_settings.es_scid_len);
need = conn->ifc_conn.cn_pf->pf_new_connection_id_frame_size(
conn->ifc_scid_seqno, conn->ifc_enpub->enp_settings.es_scid_len);
packet_out = get_writeable_packet(conn, need);
if (!packet_out)
return -1;
if (!(cce = ietf_full_conn_add_scid(conn, conn->ifc_enpub, 0, now)))
{
ABORT_WARN("cannot add a new SCID");
return -1;
}
lsquic_tg_generate_sreset(conn->ifc_enpub->enp_tokgen, &cce->cce_cid,
token_buf);
if (0 != lsquic_engine_add_cid(conn->ifc_enpub, &conn->ifc_conn,
cce - conn->ifc_cces))
{
ABORT_WARN("cannot track new SCID");
return -1;
}
w = conn->ifc_conn.cn_pf->pf_gen_new_connection_id_frame(
packet_out->po_data + packet_out->po_data_sz,
lsquic_packet_out_avail(packet_out), cce->cce_seqno,
&cce->cce_cid, token_buf, sizeof(token_buf));
if (w < 0)
{
ABORT_ERROR("generating NEW_CONNECTION_ID frame failed: %d", errno);
return -1;
}
LSQ_DEBUGC("generated %d-byte NEW_CONNECTION_ID frame (CID: %"CID_FMT")",
w, CID_BITS(&cce->cce_cid));
EV_LOG_GENERATED_NEW_CONNECTION_ID_FRAME(LSQUIC_LOG_CONN_ID,
conn->ifc_conn.cn_pf, packet_out->po_data + packet_out->po_data_sz, w);
if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0,
QUIC_FRAME_NEW_CONNECTION_ID, packet_out->po_data_sz, w))
{
ABORT_ERROR("adding frame to packet failed: %d", errno);
return -1;
}
packet_out->po_frame_types |= QUIC_FTBIT_NEW_CONNECTION_ID;
lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, w);
if (!can_issue_cids(conn))
{
conn->ifc_send_flags &= ~SF_SEND_NEW_CID;
LSQ_DEBUG("All %u SCID slots have been assigned",
conn->ifc_conn.cn_n_cces);
}
return 0;
}
static void
maybe_get_rate_available_scid_slot (struct ietf_full_conn *conn,
lsquic_time_t now)
{
const struct lsquic_conn *const lconn = &conn->ifc_conn;
const struct conn_cid_elem *cce;
unsigned active_cid;
lsquic_time_t total_elapsed, elapsed_thresh, period, wait_time;
if (!conn->ifc_enpub->enp_settings.es_scid_iss_rate)
{
conn->ifc_send_flags |= SF_SEND_NEW_CID;
return;
}
/* period: usec per cid */
period = (60 * 1000000) / conn->ifc_enpub->enp_settings.es_scid_iss_rate;
active_cid = 0;
total_elapsed = 0;
for (cce = lconn->cn_cces; cce < END_OF_CCES(lconn); ++cce)
{
if ((cce->cce_flags & (CCE_SEQNO|CCE_PORT)) == CCE_SEQNO)
{
active_cid += 1;
/* When server is promoted, the timestamp may be larger than the
* first tick time.
*/
if (now > conn->ifc_scid_timestamp[cce - lconn->cn_cces])
total_elapsed +=
now - conn->ifc_scid_timestamp[cce - lconn->cn_cces];
}
}
elapsed_thresh = ((active_cid * (active_cid + 1)) / 2) * period;
/* compare total elapsed usec to elapsed usec threshold */
if (total_elapsed < elapsed_thresh)
{
wait_time = (elapsed_thresh - total_elapsed) / active_cid;
LSQ_DEBUG("cid_throt no SCID slots available (rate-limited), "
"must wait %"PRIu64" usec", wait_time);
lsquic_alarmset_set(&conn->ifc_alset, AL_CID_THROT, now + wait_time);
conn->ifc_send_flags &= ~SF_SEND_NEW_CID;
}
else
conn->ifc_send_flags |= SF_SEND_NEW_CID;
}
static void
generate_new_cid_frames (struct ietf_full_conn *conn, lsquic_time_t now)
{
int s;
do
{
s = generate_new_cid_frame(conn, now);
if (s < 0)
break;
if (conn->ifc_send_flags & SF_SEND_NEW_CID)
maybe_get_rate_available_scid_slot(conn, now);
}
while (conn->ifc_send_flags & SF_SEND_NEW_CID);
LOG_SCIDS(conn);
}
static int
generate_retire_cid_frame (struct ietf_full_conn *conn)
{
struct lsquic_packet_out *packet_out;
struct dcid_elem *dce;
size_t need;
int w;
dce = TAILQ_FIRST(&conn->ifc_to_retire);
assert(dce);
need = conn->ifc_conn.cn_pf->pf_retire_cid_frame_size(dce->de_seqno);
packet_out = get_writeable_packet(conn, need);
if (!packet_out)
return -1;
w = conn->ifc_conn.cn_pf->pf_gen_retire_cid_frame(
packet_out->po_data + packet_out->po_data_sz,
lsquic_packet_out_avail(packet_out), dce->de_seqno);
if (w < 0)
{
ABORT_ERROR("generating RETIRE_CONNECTION_ID frame failed: %d", errno);
return -1;
}
LSQ_DEBUG("generated %d-byte RETIRE_CONNECTION_ID frame (seqno: %u)",
w, dce->de_seqno);
EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "generated RETIRE_CONNECTION_ID "
"frame, seqno=%u", dce->de_seqno);
if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0,
QUIC_FRAME_RETIRE_CONNECTION_ID, packet_out->po_data_sz, w))
{
ABORT_ERROR("adding frame to packet failed: %d", errno);
return -1;
}
packet_out->po_frame_types |= QUIC_FTBIT_RETIRE_CONNECTION_ID;
lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, w);
TAILQ_REMOVE(&conn->ifc_to_retire, dce, de_next_to_ret);
lsquic_malo_put(dce);
if (TAILQ_EMPTY(&conn->ifc_to_retire))
conn->ifc_send_flags &= ~SF_SEND_RETIRE_CID;
return 0;
}
static void
generate_retire_cid_frames (struct ietf_full_conn *conn, lsquic_time_t now)
{
int s;
do
s = generate_retire_cid_frame(conn);
while (0 == s && (conn->ifc_send_flags & SF_SEND_RETIRE_CID));
}
static void
generate_streams_blocked_frame (struct ietf_full_conn *conn, enum stream_dir sd)
{
struct lsquic_packet_out *packet_out;
uint64_t limit;
size_t need;
int w;
limit = conn->ifc_send.streams_blocked[sd];
need = conn->ifc_conn.cn_pf->pf_streams_blocked_frame_size(limit);
packet_out = get_writeable_packet(conn, need);
if (!packet_out)
return;
w = conn->ifc_conn.cn_pf->pf_gen_streams_blocked_frame(
packet_out->po_data + packet_out->po_data_sz,
lsquic_packet_out_avail(packet_out), sd == SD_UNI, limit);
if (w < 0)
{
ABORT_ERROR("generating STREAMS_BLOCKED frame failed: %d", errno);
return;
}
LSQ_DEBUG("generated %d-byte STREAMS_BLOCKED frame (uni: %d, "
"limit: %"PRIu64")", w, sd == SD_UNI, limit);
EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "generated %d-byte STREAMS_BLOCKED "
"frame (uni: %d, limit: %"PRIu64")", w, sd == SD_UNI, limit);
if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0,
QUIC_FRAME_STREAMS_BLOCKED, packet_out->po_data_sz, w))
{
ABORT_ERROR("adding frame to packet failed: %d", errno);
return;
}
packet_out->po_frame_types |= QUIC_FTBIT_STREAM_BLOCKED;
lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, w);
conn->ifc_send_flags &= ~(SF_SEND_STREAMS_BLOCKED << sd);
}
static void
generate_streams_blocked_uni_frame (struct ietf_full_conn *conn,
lsquic_time_t now)
{
generate_streams_blocked_frame(conn, SD_UNI);
}
static void
generate_streams_blocked_bidi_frame (struct ietf_full_conn *conn,
lsquic_time_t now)
{
generate_streams_blocked_frame(conn, SD_BIDI);
}
static void
generate_max_streams_frame (struct ietf_full_conn *conn, enum stream_dir sd)
{
struct lsquic_packet_out *packet_out;
enum stream_id_type sit;
uint64_t limit;
size_t need;
int w;
limit = conn->ifc_closed_peer_streams[sd] + conn->ifc_max_streams_in[sd];
need = conn->ifc_conn.cn_pf->pf_max_streams_frame_size(limit);
packet_out = get_writeable_packet(conn, need);
if (!packet_out)
return;
w = conn->ifc_conn.cn_pf->pf_gen_max_streams_frame(
packet_out->po_data + packet_out->po_data_sz,
lsquic_packet_out_avail(packet_out), sd, limit);
if (w < 0)
{
ABORT_ERROR("generating MAX_STREAMS frame failed: %d", errno);
return;
}
LSQ_DEBUG("generated %d-byte MAX_STREAMS frame (uni: %d, "
"limit: %"PRIu64")", w, sd == SD_UNI, limit);
EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "generated %d-byte MAX_STREAMS "
"frame (uni: %d, limit: %"PRIu64")", w, sd == SD_UNI, limit);
if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0,
QUIC_FRAME_MAX_STREAMS, packet_out->po_data_sz, w))
{
ABORT_ERROR("adding frame to packet failed: %d", errno);
return;
}
packet_out->po_frame_types |= QUIC_FTBIT_MAX_STREAMS;
lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, w);
conn->ifc_send_flags &= ~(SF_SEND_MAX_STREAMS << sd);
sit = gen_sit(!(conn->ifc_flags & IFC_SERVER), sd);
LSQ_DEBUG("max_allowed_stream_id[ %u ] goes from %"PRIu64" to %"PRIu64,
sit, conn->ifc_max_allowed_stream_id[ sit ], limit << SIT_SHIFT);
conn->ifc_max_allowed_stream_id[ sit ] = limit << SIT_SHIFT;
}
static void
generate_max_streams_uni_frame (struct ietf_full_conn *conn, lsquic_time_t now)
{
generate_max_streams_frame(conn, SD_UNI);
}
static void
generate_max_streams_bidi_frame (struct ietf_full_conn *conn, lsquic_time_t now)
{
generate_max_streams_frame(conn, SD_BIDI);
}
/* Return true if generated, false otherwise */
static int
generate_blocked_frame (struct ietf_full_conn *conn)
{
const uint64_t offset = conn->ifc_pub.conn_cap.cc_blocked;
struct lsquic_packet_out *packet_out;
size_t need;
int w;
need = conn->ifc_conn.cn_pf->pf_blocked_frame_size(offset);
packet_out = get_writeable_packet(conn, need);
if (!packet_out)
return 0;
w = conn->ifc_conn.cn_pf->pf_gen_blocked_frame(
packet_out->po_data + packet_out->po_data_sz,
lsquic_packet_out_avail(packet_out), offset);
if (w < 0)
{
ABORT_ERROR("generating BLOCKED frame failed: %d", errno);
return 0;
}
LSQ_DEBUG("generated %d-byte BLOCKED frame (offset: %"PRIu64")", w, offset);
EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "generated BLOCKED frame, offset=%"
PRIu64, offset);
if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0,
QUIC_FRAME_BLOCKED, packet_out->po_data_sz, w))
{
ABORT_ERROR("adding frame to packet failed: %d", errno);
return 0;
}
packet_out->po_frame_types |= QUIC_FTBIT_BLOCKED;
lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, w);
return 1;
}
/* Return true if generated, false otherwise */
static int
generate_max_stream_data_frame (struct ietf_full_conn *conn,
struct lsquic_stream *stream)
{
struct lsquic_packet_out *packet_out;
unsigned need;
uint64_t off;
int sz;
off = lsquic_stream_fc_recv_off_const(stream);
need = conn->ifc_conn.cn_pf->pf_max_stream_data_frame_size(stream->id, off);
packet_out = get_writeable_packet(conn, need);
if (!packet_out)
return 0;
sz = conn->ifc_conn.cn_pf->pf_gen_max_stream_data_frame(
packet_out->po_data + packet_out->po_data_sz,
lsquic_packet_out_avail(packet_out), stream->id, off);
if (sz < 0)
{
ABORT_ERROR("Generating MAX_STREAM_DATA frame failed");
return 0;
}
EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "generated %d-byte MAX_STREAM_DATA "
"frame; stream_id: %"PRIu64"; offset: %"PRIu64, sz, stream->id, off);
if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0,
QUIC_FRAME_MAX_STREAM_DATA, packet_out->po_data_sz, sz))
{
ABORT_ERROR("adding frame to packet failed: %d", errno);
return 0;
}
lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, sz);
packet_out->po_frame_types |= 1 << QUIC_FRAME_MAX_STREAM_DATA;
lsquic_stream_max_stream_data_sent(stream);
return 1;
}
/* Return true if generated, false otherwise */
static int
generate_stream_blocked_frame (struct ietf_full_conn *conn,
struct lsquic_stream *stream)
{
struct lsquic_packet_out *packet_out;
unsigned need;
uint64_t off;
int sz;
off = lsquic_stream_combined_send_off(stream);
need = conn->ifc_conn.cn_pf->pf_stream_blocked_frame_size(stream->id, off);
packet_out = get_writeable_packet(conn, need);
if (!packet_out)
return 0;
sz = conn->ifc_conn.cn_pf->pf_gen_stream_blocked_frame(
packet_out->po_data + packet_out->po_data_sz,
lsquic_packet_out_avail(packet_out), stream->id, off);
if (sz < 0)
{
ABORT_ERROR("Generating STREAM_BLOCKED frame failed");
return 0;
}
LSQ_DEBUG("generated %d-byte STREAM_BLOCKED "
"frame; stream_id: %"PRIu64"; offset: %"PRIu64, sz, stream->id, off);
EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "generated %d-byte STREAM_BLOCKED "
"frame; stream_id: %"PRIu64"; offset: %"PRIu64, sz, stream->id, off);
if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0,
QUIC_FRAME_STREAM_BLOCKED, packet_out->po_data_sz, sz))
{
ABORT_ERROR("adding frame to packet failed: %d", errno);
return 0;
}
lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, sz);
packet_out->po_frame_types |= 1 << QUIC_FRAME_STREAM_BLOCKED;
lsquic_stream_blocked_frame_sent(stream);
return 1;
}
static int
generate_stop_sending_frame_by_id (struct ietf_full_conn *conn,
lsquic_stream_id_t stream_id, enum http_error_code error_code)
{
struct lsquic_packet_out *packet_out;
size_t need;
int w;
need = conn->ifc_conn.cn_pf->pf_stop_sending_frame_size(stream_id,
error_code);
packet_out = get_writeable_packet(conn, need);
if (!packet_out)
return -1;
w = conn->ifc_conn.cn_pf->pf_gen_stop_sending_frame(
packet_out->po_data + packet_out->po_data_sz,
lsquic_packet_out_avail(packet_out),
stream_id, error_code);
if (w < 0)
{
ABORT_ERROR("generating STOP_SENDING frame failed: %d", errno);
return -1;
}
LSQ_DEBUG("generated %d-byte STOP_SENDING frame (stream id: %"PRIu64", "
"error code: %u)", w, stream_id, error_code);
EV_LOG_GENERATED_STOP_SENDING_FRAME(LSQUIC_LOG_CONN_ID, stream_id,
error_code);
if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0,
QUIC_FRAME_STOP_SENDING, packet_out->po_data_sz, w))
{
ABORT_ERROR("adding frame to packet failed: %d", errno);
return -1;
}
packet_out->po_frame_types |= QUIC_FTBIT_STOP_SENDING;
lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, w);
return 0;
}
/* Return true if generated, false otherwise */
static int
generate_stop_sending_frame (struct ietf_full_conn *conn,
struct lsquic_stream *stream)
{
if (0 == generate_stop_sending_frame_by_id(conn, stream->id, HEC_NO_ERROR))
{
lsquic_stream_ss_frame_sent(stream);
return 1;
}
else
return 0;
}
static void
generate_stop_sending_frames (struct ietf_full_conn *conn, lsquic_time_t now)
{
struct stream_id_to_ss *sits;
assert(conn->ifc_send_flags & SF_SEND_STOP_SENDING);
while (!STAILQ_EMPTY(&conn->ifc_stream_ids_to_ss))
{
sits = STAILQ_FIRST(&conn->ifc_stream_ids_to_ss);
if (0 == generate_stop_sending_frame_by_id(conn, sits->sits_stream_id,
sits->sits_error_code))
{
STAILQ_REMOVE_HEAD(&conn->ifc_stream_ids_to_ss, sits_next);
free(sits);
}
else
break;
}
if (STAILQ_EMPTY(&conn->ifc_stream_ids_to_ss))
conn->ifc_send_flags &= ~SF_SEND_STOP_SENDING;
}
/* Return true if generated, false otherwise */
static int
generate_rst_stream_frame (struct ietf_full_conn *conn,
struct lsquic_stream *stream)
{
lsquic_packet_out_t *packet_out;
unsigned need;
int sz;
need = conn->ifc_conn.cn_pf->pf_rst_frame_size(stream->id,
stream->tosend_off, stream->error_code);
packet_out = get_writeable_packet(conn, need);
if (!packet_out)
{
LSQ_DEBUG("cannot get writeable packet for RESET_STREAM frame");
return 0;
}
sz = conn->ifc_conn.cn_pf->pf_gen_rst_frame(
packet_out->po_data + packet_out->po_data_sz,
lsquic_packet_out_avail(packet_out), stream->id,
stream->tosend_off, stream->error_code);
if (sz < 0)
{
ABORT_ERROR("gen_rst_frame failed");
return 0;
}
if (0 != lsquic_packet_out_add_stream(packet_out, conn->ifc_pub.mm, stream,
QUIC_FRAME_RST_STREAM, packet_out->po_data_sz, sz))
{
ABORT_ERROR("adding frame to packet failed: %d", errno);
return 0;
}
lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, sz);
packet_out->po_frame_types |= 1 << QUIC_FRAME_RST_STREAM;
lsquic_stream_rst_frame_sent(stream);
LSQ_DEBUG("wrote RST: stream %"PRIu64"; offset 0x%"PRIX64"; error code "
"%"PRIu64, stream->id, stream->tosend_off, stream->error_code);
return 1;
}
static int
is_our_stream (const struct ietf_full_conn *conn,
const struct lsquic_stream *stream)
{
const unsigned is_server = !!(conn->ifc_flags & IFC_SERVER);
return (1 & stream->id) == is_server;
}
static int
is_peer_initiated (const struct ietf_full_conn *conn,
lsquic_stream_id_t stream_id)
{
const unsigned is_server = !!(conn->ifc_flags & IFC_SERVER);
return (1 & stream_id) != is_server;
}
static void
sched_max_bidi_streams (void *conn_p)
{
struct ietf_full_conn *conn = conn_p;
conn->ifc_send_flags |= SF_SEND_MAX_STREAMS_BIDI;
conn->ifc_delayed_send &= ~SF_SEND_MAX_STREAMS_BIDI;
LSQ_DEBUG("schedule MAX_STREAMS frame for bidirectional streams (was "
"delayed)");
}
/* Do not allow peer to open more streams while QPACK decoder stream has
* unsent data.
*/
static int
can_give_peer_streams_credit (struct ietf_full_conn *conn, enum stream_dir sd)
{
/* This logic only applies to HTTP servers. */
if ((conn->ifc_flags & (IFC_SERVER|IFC_HTTP)) != (IFC_SERVER|IFC_HTTP))
return 1;
/* HTTP client does not open unidirectional streams (other than the
* standard three), not applicable.
*/
if (SD_UNI == sd)
return 1;
if (conn->ifc_delayed_send & (SF_SEND_MAX_STREAMS << sd))
return 0;
if (lsquic_qdh_arm_if_unsent(&conn->ifc_qdh, sched_max_bidi_streams, conn))
{
LSQ_DEBUG("delay sending more streams credit to peer until QPACK "
"decoder sends unsent data");
conn->ifc_delayed_send |= SF_SEND_MAX_STREAMS << sd;
return 0;
}
else
return 1;
}
/* Because stream IDs are distributed unevenly, it is more efficient to
* maintain four sets of closed stream IDs.
*/
static void
conn_mark_stream_closed (struct ietf_full_conn *conn,
lsquic_stream_id_t stream_id)
{
lsquic_stream_id_t shifted_id;
uint64_t max_allowed, thresh;
enum stream_id_type idx;
enum stream_dir sd;
idx = stream_id & SIT_MASK;
shifted_id = stream_id >> SIT_SHIFT;
if (is_peer_initiated(conn, stream_id)
&& !lsquic_set64_has(&conn->ifc_closed_stream_ids[idx], shifted_id))
{
sd = (stream_id >> SD_SHIFT) & 1;
++conn->ifc_closed_peer_streams[sd];
if (0 == (conn->ifc_send_flags & (SF_SEND_MAX_STREAMS << sd)))
{
max_allowed = conn->ifc_max_allowed_stream_id[idx] >> SIT_SHIFT;
thresh = conn->ifc_closed_peer_streams[sd]
+ conn->ifc_max_streams_in[sd] / 2;
if (thresh >= max_allowed && can_give_peer_streams_credit(conn, sd))
{
LSQ_DEBUG("closed incoming %sdirectional streams reached "
"%"PRIu64", scheduled MAX_STREAMS frame",
sd == SD_UNI ? "uni" : "bi",
conn->ifc_closed_peer_streams[sd]);
conn->ifc_send_flags |= SF_SEND_MAX_STREAMS << sd;
}
}
}
if (0 == lsquic_set64_add(&conn->ifc_closed_stream_ids[idx], shifted_id))
LSQ_DEBUG("marked stream %"PRIu64" as closed", stream_id);
else
ABORT_ERROR("could not add element to set: %s", strerror(errno));
}
static int
conn_is_stream_closed (struct ietf_full_conn *conn,
lsquic_stream_id_t stream_id)
{
enum stream_id_type idx = stream_id & SIT_MASK;
stream_id >>= SIT_SHIFT;
return lsquic_set64_has(&conn->ifc_closed_stream_ids[idx], stream_id);
}
static int
either_side_going_away (const struct ietf_full_conn *conn)
{
return (conn->ifc_flags & IFC_GOING_AWAY)
|| (conn->ifc_conn.cn_flags & LSCONN_PEER_GOING_AWAY);
}
static void
maybe_create_delayed_streams (struct ietf_full_conn *conn)
{
unsigned avail, delayed;
delayed = conn->ifc_n_delayed_streams;
if (0 == delayed)
return;
avail = ietf_full_conn_ci_n_avail_streams(&conn->ifc_conn);
while (avail > 0)
{
if (0 == create_bidi_stream_out(conn))
{
--avail;
--conn->ifc_n_delayed_streams;
if (0 == conn->ifc_n_delayed_streams)
break;
}
else
{
LSQ_INFO("cannot create BIDI stream");
break;
}
}
LSQ_DEBUG("created %u delayed stream%.*s",
delayed - conn->ifc_n_delayed_streams,
delayed - conn->ifc_n_delayed_streams != 1, "s");
}
static int
have_bidi_streams (const struct ietf_full_conn *conn)
{
const struct lsquic_stream *stream;
struct lsquic_hash_elem *el;
for (el = lsquic_hash_first(conn->ifc_pub.all_streams); el;
el = lsquic_hash_next(conn->ifc_pub.all_streams))
{
stream = lsquic_hashelem_getdata(el);
if (SIT_BIDI_CLIENT == (stream->id & SIT_MASK))
return 1;
}
return 0;
}
static void
maybe_close_conn (struct ietf_full_conn *conn)
{
if ((conn->ifc_flags & (IFC_CLOSING|IFC_GOING_AWAY|IFC_SERVER))
== (IFC_GOING_AWAY|IFC_SERVER)
&& !have_bidi_streams(conn))
{
conn->ifc_flags |= IFC_CLOSING|IFC_GOAWAY_CLOSE;
conn->ifc_send_flags |= SF_SEND_CONN_CLOSE;
LSQ_DEBUG("closing connection: GOAWAY sent and no responses remain");
}
}
static void
service_streams (struct ietf_full_conn *conn)
{
struct lsquic_hash_elem *el;
lsquic_stream_t *stream, *next;
for (stream = TAILQ_FIRST(&conn->ifc_pub.service_streams); stream;
stream = next)
{
next = TAILQ_NEXT(stream, next_service_stream);
if (stream->sm_qflags & SMQF_ABORT_CONN)
/* No need to unset this flag or remove this stream: the connection
* is about to be aborted.
*/
ABORT_ERROR("aborted due to error in stream %"PRIu64, stream->id);
if (stream->sm_qflags & SMQF_CALL_ONCLOSE)
lsquic_stream_call_on_close(stream);
if (stream->sm_qflags & SMQF_FREE_STREAM)
{
TAILQ_REMOVE(&conn->ifc_pub.service_streams, stream,
next_service_stream);
if (!(stream->sm_bflags & SMBF_CRYPTO))
{
el = lsquic_hash_find(conn->ifc_pub.all_streams,
&stream->id, sizeof(stream->id));
if (el)
lsquic_hash_erase(conn->ifc_pub.all_streams, el);
conn_mark_stream_closed(conn, stream->id);
}
else
assert(!(stream->sm_hash_el.qhe_flags & QHE_HASHED));
lsquic_stream_destroy(stream);
}
}
/* TODO: this chunk of code, too, should probably live elsewhere */
if (either_side_going_away(conn))
{
while (conn->ifc_n_delayed_streams)
{
--conn->ifc_n_delayed_streams;
LSQ_DEBUG("goaway mode: delayed stream results in null ctor");
(void) conn->ifc_enpub->enp_stream_if->on_new_stream(
conn->ifc_enpub->enp_stream_if_ctx, NULL);
}
maybe_close_conn(conn);
}
else
maybe_create_delayed_streams(conn);
}
static int
process_stream_ready_to_send (struct ietf_full_conn *conn,
struct lsquic_stream *stream)
{
int r = 1;
if (stream->sm_qflags & SMQF_SEND_MAX_STREAM_DATA)
r &= generate_max_stream_data_frame(conn, stream);
if (stream->sm_qflags & SMQF_SEND_BLOCKED)
r &= generate_stream_blocked_frame(conn, stream);
if (stream->sm_qflags & SMQF_SEND_RST)
r &= generate_rst_stream_frame(conn, stream);
if (stream->sm_qflags & SMQF_SEND_STOP_SENDING)
r &= generate_stop_sending_frame(conn, stream);
return r;
}
static void
process_streams_ready_to_send (struct ietf_full_conn *conn)
{
struct lsquic_stream *stream;
union prio_iter pi;
assert(!TAILQ_EMPTY(&conn->ifc_pub.sending_streams));
conn->ifc_pii->pii_init(&pi, TAILQ_FIRST(&conn->ifc_pub.sending_streams),
TAILQ_LAST(&conn->ifc_pub.sending_streams, lsquic_streams_tailq),
(uintptr_t) &TAILQ_NEXT((lsquic_stream_t *) NULL, next_send_stream),
&conn->ifc_pub, "send", NULL, NULL);
for (stream = conn->ifc_pii->pii_first(&pi); stream;
stream = conn->ifc_pii->pii_next(&pi))
if (!process_stream_ready_to_send(conn, stream))
break;
conn->ifc_pii->pii_cleanup(&pi);
}
static void
ietf_full_conn_ci_write_ack (struct lsquic_conn *lconn,
struct lsquic_packet_out *packet_out)
{
struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn;
generate_ack_frame_for_pns(conn, packet_out, PNS_APP, lsquic_time_now());
}
static int
ietf_full_conn_ci_want_datagram_write (struct lsquic_conn *lconn, int is_want)
{
struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn;
int old;
if (conn->ifc_flags & IFC_DATAGRAMS)
{
old = !!(conn->ifc_mflags & MF_WANT_DATAGRAM_WRITE);
if (is_want)
conn->ifc_mflags |= MF_WANT_DATAGRAM_WRITE;
else
conn->ifc_mflags &= ~MF_WANT_DATAGRAM_WRITE;
LSQ_DEBUG("turn %s \"want datagram write\" flag",
is_want ? "on" : "off");
return old;
}
else
return -1;
}
static void
ietf_full_conn_ci_client_call_on_new (struct lsquic_conn *lconn)
{
struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn;
assert(conn->ifc_flags & IFC_CREATED_OK);
lconn->cn_conn_ctx = conn->ifc_enpub->enp_stream_if->on_new_conn(
conn->ifc_enpub->enp_stream_if_ctx, lconn);
}
static void
ietf_full_conn_ci_close (struct lsquic_conn *lconn)
{
struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn;
struct lsquic_stream *stream;
struct lsquic_hash_elem *el;
enum stream_dir sd;
if (!(conn->ifc_flags & IFC_CLOSING))
{
for (el = lsquic_hash_first(conn->ifc_pub.all_streams); el;
el = lsquic_hash_next(conn->ifc_pub.all_streams))
{
stream = lsquic_hashelem_getdata(el);
sd = (stream->id >> SD_SHIFT) & 1;
if (SD_BIDI == sd)
lsquic_stream_shutdown_internal(stream);
}
conn->ifc_flags |= IFC_CLOSING;
conn->ifc_send_flags |= SF_SEND_CONN_CLOSE;
lsquic_engine_add_conn_to_tickable(conn->ifc_enpub, lconn);
}
}
static void
ietf_full_conn_ci_abort (struct lsquic_conn *lconn)
{
struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn;
LSQ_INFO("User aborted connection");
conn->ifc_flags |= IFC_ABORTED;
}
static void
retire_dcid (struct ietf_full_conn *conn, struct dcid_elem **dce)
{
if ((*dce)->de_hash_el.qhe_flags & QHE_HASHED)
lsquic_hash_erase(conn->ifc_enpub->enp_srst_hash, &(*dce)->de_hash_el);
TAILQ_INSERT_TAIL(&conn->ifc_to_retire, *dce, de_next_to_ret);
LSQ_DEBUG("prepare to retire DCID seqno %"PRIu32"", (*dce)->de_seqno);
*dce = NULL;
conn->ifc_send_flags |= SF_SEND_RETIRE_CID;
}
static void
retire_seqno (struct ietf_full_conn *conn, unsigned seqno)
{
struct dcid_elem *dce;
dce = lsquic_malo_get(conn->ifc_pub.mm->malo.dcid_elem);
if (dce)
{
memset(dce, 0, sizeof(*dce));
dce->de_seqno = seqno;
TAILQ_INSERT_TAIL(&conn->ifc_to_retire, dce, de_next_to_ret);
LSQ_DEBUG("prepare to retire DCID seqno %"PRIu32, seqno);
conn->ifc_send_flags |= SF_SEND_RETIRE_CID;
}
else
LSQ_INFO("%s: cannot allocate dce", __func__);
}
/* This function exists for testing purposes.
*
* The user can switch DCIDs and request that the old DCID is retired.
*
* If the user calls this function frequently in a short amount of time,
* this should trigger the CID issuance throttling.
*/
static void
ietf_full_conn_ci_retire_cid (struct lsquic_conn *lconn)
{
struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn;
struct dcid_elem **el, **dces[2];
int eq;
/*
* Find two DCIDs:
* 1. the current DCID that will be retire_cid
* 2. an available DCID that will be switched
* Continue searching until there are no more DCIDs
* or when both DCIDs are found.
*/
dces[0] = NULL; // future DCID (does not match current DCID)
dces[1] = NULL; // current DCID (does match current DCID)
for (el = conn->ifc_dces; el < DCES_END(conn) && !(dces[0] && dces[1]); ++el)
if (*el)
{
eq = LSQUIC_CIDS_EQ(&(*el)->de_cid, CUR_DCID(conn));
if (!dces[eq])
dces[eq] = el;
}
if (!dces[1])
{
ABORT_WARN("%s: cannot find own DCID", __func__);
return;
}
if (!dces[0])
{
LSQ_INFO("No DCID available: cannot switch");
/* TODO: implemened delayed switch */
// conn->ifc_flags |= IFC_SWITCH_DCID;
return;
}
/*
* Switch DCID.
*/
*CUR_DCID(conn) = (*dces[0])->de_cid;
if (CUR_CPATH(conn)->cop_flags & COP_SPIN_BIT)
CUR_CPATH(conn)->cop_spin_bit = 0;
LSQ_INFOC("switched DCID to %"CID_FMT, CID_BITS(CUR_DCID(conn)));
/*
* Mark old DCID for retirement.
*/
retire_dcid(conn, dces[1]);
}
static void
drop_crypto_streams (struct ietf_full_conn *conn)
{
struct lsquic_stream **streamp;
unsigned count;
if ((conn->ifc_flags & (IFC_SERVER|IFC_PROC_CRYPTO)) != IFC_PROC_CRYPTO)
return;
conn->ifc_flags &= ~IFC_PROC_CRYPTO;
count = 0;
for (streamp = conn->ifc_u.cli.crypto_streams; streamp <
conn->ifc_u.cli.crypto_streams + sizeof(conn->ifc_u.cli.crypto_streams)
/ sizeof(conn->ifc_u.cli.crypto_streams[0]); ++streamp)
if (*streamp)
{
lsquic_stream_force_finish(*streamp);
*streamp = NULL;
++count;
}
LSQ_DEBUG("dropped %u crypto stream%.*s", count, count != 1, "s");
}
static void
ietf_full_conn_ci_destroy (struct lsquic_conn *lconn)
{
struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn;
struct lsquic_stream **streamp, *stream;
struct stream_id_to_ss *sits;
struct dcid_elem **dcep, *dce;
struct lsquic_hash_elem *el;
unsigned i;
if (!(conn->ifc_flags & IFC_SERVER))
{
for (streamp = conn->ifc_u.cli.crypto_streams; streamp <
conn->ifc_u.cli.crypto_streams
+ sizeof(conn->ifc_u.cli.crypto_streams)
/ sizeof(conn->ifc_u.cli.crypto_streams[0]); ++streamp)
if (*streamp)
lsquic_stream_destroy(*streamp);
}
while ((el = lsquic_hash_first(conn->ifc_pub.all_streams)))
{
stream = lsquic_hashelem_getdata(el);
lsquic_hash_erase(conn->ifc_pub.all_streams, el);
lsquic_stream_destroy(stream);
}
if (conn->ifc_flags & IFC_HTTP)
{
lsquic_qdh_cleanup(&conn->ifc_qdh);
lsquic_qeh_cleanup(&conn->ifc_qeh);
}
for (dcep = conn->ifc_dces; dcep < conn->ifc_dces + sizeof(conn->ifc_dces)
/ sizeof(conn->ifc_dces[0]); ++dcep)
if (*dcep)
{
if ((*dcep)->de_hash_el.qhe_flags & QHE_HASHED)
lsquic_hash_erase(conn->ifc_enpub->enp_srst_hash,
&(*dcep)->de_hash_el);
lsquic_malo_put(*dcep);
}
while ((dce = TAILQ_FIRST(&conn->ifc_to_retire)))
{
TAILQ_REMOVE(&conn->ifc_to_retire, dce, de_next_to_ret);
lsquic_malo_put(dce);
}
lsquic_send_ctl_cleanup(&conn->ifc_send_ctl);
for (i = 0; i < N_PNS; ++i)
lsquic_rechist_cleanup(&conn->ifc_rechist[i]);
lsquic_malo_destroy(conn->ifc_pub.packet_out_malo);
if (conn->ifc_flags & IFC_CREATED_OK)
conn->ifc_enpub->enp_stream_if->on_conn_closed(&conn->ifc_conn);
if (conn->ifc_conn.cn_enc_session)
conn->ifc_conn.cn_esf.i->esfi_destroy(conn->ifc_conn.cn_enc_session);
while (!STAILQ_EMPTY(&conn->ifc_stream_ids_to_ss))
{
sits = STAILQ_FIRST(&conn->ifc_stream_ids_to_ss);
STAILQ_REMOVE_HEAD(&conn->ifc_stream_ids_to_ss, sits_next);
free(sits);
}
if (conn->ifc_flags & IFC_SERVER)
{
if (conn->ifc_pub.u.ietf.promises)
lsquic_hash_destroy(conn->ifc_pub.u.ietf.promises);
}
for (i = 0; i < N_SITS; ++i)
lsquic_set64_cleanup(&conn->ifc_closed_stream_ids[i]);
if (conn->ifc_bpus)
{
for (el = lsquic_hash_first(conn->ifc_bpus); el;
el = lsquic_hash_next(conn->ifc_bpus))
free(lsquic_hashelem_getdata(el));
lsquic_hash_destroy(conn->ifc_bpus);
}
lsquic_hash_destroy(conn->ifc_pub.all_streams);
#if LSQUIC_CONN_STATS
if (conn->ifc_flags & IFC_CREATED_OK)
{
LSQ_NOTICE("# ticks: %lu", conn->ifc_stats.n_ticks);
LSQ_NOTICE("sent %lu packets", conn->ifc_stats.out.packets);
LSQ_NOTICE("received %lu packets, of which %lu were not decryptable, %lu were "
"dups and %lu were errors; sent %lu packets, avg stream data per outgoing"
" packet is %lu bytes",
conn->ifc_stats.in.packets, conn->ifc_stats.in.undec_packets,
conn->ifc_stats.in.dup_packets, conn->ifc_stats.in.err_packets,
conn->ifc_stats.out.packets,
conn->ifc_stats.out.stream_data_sz /
(conn->ifc_stats.out.packets ? conn->ifc_stats.out.packets : 1));
if (conn->ifc_flags & IFC_DELAYED_ACKS)
LSQ_NOTICE("delayed ACKs settings: (%u/%.3f/%.3f/%.3f/%.3f/%.3f); "
"packet tolerances sent: count: %u, min: %u, max: %u",
conn->ifc_settings->es_ptpc_periodicity,
conn->ifc_settings->es_ptpc_target,
conn->ifc_settings->es_ptpc_prop_gain,
conn->ifc_settings->es_ptpc_int_gain,
conn->ifc_settings->es_ptpc_err_thresh,
conn->ifc_settings->es_ptpc_err_divisor,
conn->ifc_ack_freq_seqno,
conn->ifc_min_pack_tol_sent, conn->ifc_max_pack_tol_sent);
LSQ_NOTICE("ACKs: delayed acks on: %s; in: %lu; processed: %lu; merged: %lu",
conn->ifc_flags & IFC_DELAYED_ACKS ? "yes" : "no",
conn->ifc_stats.in.n_acks, conn->ifc_stats.in.n_acks_proc,
conn->ifc_stats.in.n_acks_merged);
}
if (conn->ifc_last_stats)
free(conn->ifc_last_stats);
#endif
EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "full connection destroyed");
free(conn->ifc_errmsg);
free(conn);
}
static uint64_t
calc_drain_time (const struct ietf_full_conn *conn)
{
lsquic_time_t drain_time, pto, srtt, var;
/* PTO Calculation: [draft-ietf-quic-recovery-18], Section 6.2.2.1;
* Drain time: [draft-ietf-quic-transport-19], Section 10.1.
*/
srtt = lsquic_rtt_stats_get_srtt(&conn->ifc_pub.rtt_stats);
var = lsquic_rtt_stats_get_rttvar(&conn->ifc_pub.rtt_stats);
pto = srtt + 4 * var + TP_DEF_MAX_ACK_DELAY * 1000;
drain_time = 3 * pto;
return drain_time;
}
static lsquic_time_t
ietf_full_conn_ci_drain_time (const struct lsquic_conn *lconn)
{
struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn;
lsquic_time_t drain_time;
/* Only applicable to a server whose connection was not timed out */
if ((conn->ifc_flags & (IFC_SERVER|IFC_TIMED_OUT)) != IFC_SERVER)
{
LSQ_DEBUG("drain time is zero (don't drain)");
return 0;
}
drain_time = calc_drain_time(conn);
LSQ_DEBUG("drain time is %"PRIu64" usec", drain_time);
return drain_time;
}
static void
ietf_full_conn_ci_going_away (struct lsquic_conn *lconn)
{
struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn;
if (conn->ifc_flags & IFC_HTTP)
{
if (!(conn->ifc_flags & (IFC_CLOSING|IFC_GOING_AWAY)))
{
LSQ_INFO("connection marked as going away");
conn->ifc_flags |= IFC_GOING_AWAY;
const lsquic_stream_id_t stream_id = conn->ifc_max_req_id + N_SITS;
if (valid_stream_id(stream_id))
{
if (0 == lsquic_hcso_write_goaway(&conn->ifc_hcso,
conn->ifc_max_req_id))
lsquic_engine_add_conn_to_tickable(conn->ifc_enpub, lconn);
else
/* We're already going away, don't abort because of this */
LSQ_WARN("could not write GOAWAY frame");
}
maybe_close_conn(conn);
}
}
else
LSQ_NOTICE("going away has no effect in non-HTTP mode");
}
static void
handshake_failed (struct lsquic_conn *lconn)
{
struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn;
LSQ_DEBUG("handshake failed");
lsquic_alarmset_unset(&conn->ifc_alset, AL_HANDSHAKE);
conn->ifc_flags |= IFC_HSK_FAILED;
}
static struct dcid_elem *
get_new_dce (struct ietf_full_conn *conn)
{
struct dcid_elem **el;
for (el = conn->ifc_dces; el < conn->ifc_dces + sizeof(conn->ifc_dces)
/ sizeof(conn->ifc_dces[0]); ++el)
if (!*el)
return *el = lsquic_malo_get(conn->ifc_pub.mm->malo.dcid_elem);
return NULL;
}
static void
queue_streams_blocked_frame (struct ietf_full_conn *conn, enum stream_dir sd)
{
enum stream_id_type sit;
uint64_t limit;
if (0 == (conn->ifc_send_flags & (SF_SEND_STREAMS_BLOCKED << sd)))
{
conn->ifc_send_flags |= SF_SEND_STREAMS_BLOCKED << sd;
sit = gen_sit(conn->ifc_flags & IFC_SERVER, sd);
limit = conn->ifc_max_allowed_stream_id[sit] >> SIT_SHIFT;
conn->ifc_send.streams_blocked[sd] = limit;
LSQ_DEBUG("scheduled %sdirectional STREAMS_BLOCKED (limit=%"PRIu64
") frame", sd == SD_BIDI ? "bi" : "uni", limit);
}
else
LSQ_DEBUG("%sdirectional STREAMS_BLOCKED frame already queued",
sd == SD_BIDI ? "bi" : "uni");
}
static void
retire_cid_from_tp (struct ietf_full_conn *conn,
const struct transport_params *params)
{
struct dcid_elem *dce;
dce = get_new_dce(conn);
if (!dce)
{
ABORT_ERROR("cannot allocate DCE");
return;
}
memset(dce, 0, sizeof(*dce));
dce->de_cid = params->tp_preferred_address.cid;
dce->de_seqno = 1;
memcpy(dce->de_srst, params->tp_preferred_address.srst,
sizeof(dce->de_srst));
dce->de_flags = DE_SRST;
TAILQ_INSERT_TAIL(&conn->ifc_to_retire, dce, de_next_to_ret);
LSQ_DEBUG("prepare to retire DCID seqno %"PRIu32, dce->de_seqno);
conn->ifc_send_flags |= SF_SEND_RETIRE_CID;
}
static enum { BM_MIGRATING, BM_NOT_MIGRATING, BM_ERROR, }
try_to_begin_migration (struct ietf_full_conn *conn,
const struct transport_params *params)
{
struct conn_path *copath;
struct dcid_elem *dce;
int is_ipv6;
union {
struct sockaddr_in v4;
struct sockaddr_in6 v6;
} sockaddr;
if (!conn->ifc_settings->es_allow_migration)
{
LSQ_DEBUG("Migration not allowed: retire PreferredAddress CID");
return BM_NOT_MIGRATING;
}
if (conn->ifc_conn.cn_version <= LSQVER_ID28 /* Starting with ID-29,
disable_active_migration TP applies only to the time period during
the handshake. Our client does not migrate during the handshake:
this code runs only after handshake has succeeded. */
&& (params->tp_set & (1 << TPI_DISABLE_ACTIVE_MIGRATION)))
{
LSQ_DEBUG("TP disables migration: retire PreferredAddress CID");
return BM_NOT_MIGRATING;
}
is_ipv6 = NP_IS_IPv6(CUR_NPATH(conn));
if ((is_ipv6 && !lsquic_tp_has_pref_ipv6(params))
|| (!is_ipv6 && !lsquic_tp_has_pref_ipv4(params)))
{
/* XXX This is a limitation in the client code outside of the library.
* To support cross-IP-version migration, we need to add some callbacks
* to open a different socket.
*/
LSQ_DEBUG("Cannot migrate from IPv%u to IPv%u", is_ipv6 ? 6 : 4,
is_ipv6 ? 4 : 6);
return BM_NOT_MIGRATING;
}
if (0 == params->tp_preferred_address.cid.len)
{
/* TODO: mark with a new flag and begin migration when a non-zero length
* DCID becomes available.
*/
LSQ_DEBUG("Cannot migrate using zero-length DCID");
return BM_NOT_MIGRATING;
}
dce = get_new_dce(conn);
if (!dce)
{
ABORT_WARN("cannot allocate DCE");
return BM_ERROR;
}
memset(dce, 0, sizeof(*dce));
dce->de_cid = params->tp_preferred_address.cid;
dce->de_seqno = 1;
dce->de_flags = DE_SRST;
memcpy(dce->de_srst, params->tp_preferred_address.srst,
sizeof(dce->de_srst));
if (conn->ifc_enpub->enp_srst_hash)
{
if (!lsquic_hash_insert(conn->ifc_enpub->enp_srst_hash,
dce->de_srst, sizeof(dce->de_srst), &conn->ifc_conn,
&dce->de_hash_el))
{
lsquic_malo_put(dce);
ABORT_WARN("cannot insert DCE");
return BM_ERROR;
}
}
if (is_ipv6)
{
sockaddr.v6.sin6_family = AF_INET6;
sockaddr.v6.sin6_port = htons(params->tp_preferred_address.ipv6_port);
memcpy(&sockaddr.v6.sin6_addr, params->tp_preferred_address.ipv6_addr,
sizeof(sockaddr.v6.sin6_addr));
}
else
{
sockaddr.v4.sin_family = AF_INET;
sockaddr.v4.sin_port = htons(params->tp_preferred_address.ipv4_port);
memcpy(&sockaddr.v4.sin_addr, params->tp_preferred_address.ipv4_addr,
sizeof(sockaddr.v4.sin_addr));
}
copath = &conn->ifc_paths[1];
assert(!(conn->ifc_used_paths & (1 << (copath - conn->ifc_paths))));
migra_begin(conn, copath, dce, (struct sockaddr *) &sockaddr, params);
return BM_MIGRATING;
}
static void
maybe_start_migration (struct ietf_full_conn *conn)
{
struct lsquic_conn *const lconn = &conn->ifc_conn;
const struct transport_params *params;
params = lconn->cn_esf.i->esfi_get_peer_transport_params(
lconn->cn_enc_session);
if (params->tp_set & (1 << TPI_PREFERRED_ADDRESS))
switch (try_to_begin_migration(conn, params))
{
case BM_MIGRATING:
break;
case BM_NOT_MIGRATING:
if (lconn->cn_version == LSQVER_ID27)
retire_cid_from_tp(conn, params);
else
{
/*
* [draft-ietf-quic-transport-28] Section 5.1.1:
" Connection IDs that are issued and not
" retired are considered active; any active connection ID is valid for
" use with the current connection at any time, in any packet type.
" This includes the connection ID issued by the server via the
" preferred_address transport parameter.
*/
LSQ_DEBUG("not migrating: save DCID from transport params");
(void) insert_new_dcid(conn, 1,
&params->tp_preferred_address.cid,
params->tp_preferred_address.srst, 0);
}
break;
case BM_ERROR:
ABORT_QUIETLY(0, TEC_INTERNAL_ERROR, "error initiating migration");
break;
}
}
static int
apply_trans_params (struct ietf_full_conn *conn,
const struct transport_params *params)
{
struct lsquic_stream *stream;
struct lsquic_hash_elem *el;
enum stream_id_type sit;
uint64_t limit;
if ((params->tp_set & (1 << TPI_LOSS_BITS))
&& conn->ifc_settings->es_ql_bits == 2)
{
LSQ_DEBUG("turn on QL loss bits");
lsquic_send_ctl_do_ql_bits(&conn->ifc_send_ctl);
}
if (params->tp_init_max_streams_bidi > (1ull << 60)
|| params->tp_init_max_streams_uni > (1ull << 60))
{
if (params->tp_init_max_streams_bidi > (1ull << 60))
ABORT_QUIETLY(0, TEC_STREAM_LIMIT_ERROR, "init_max_streams_bidi is "
"too large: %"PRIu64, params->tp_init_max_streams_bidi);
else
ABORT_QUIETLY(0, TEC_STREAM_LIMIT_ERROR, "init_max_streams_uni is "
"too large: %"PRIu64, params->tp_init_max_streams_uni);
return -1;
}
sit = gen_sit(conn->ifc_flags & IFC_SERVER, SD_BIDI);
conn->ifc_max_allowed_stream_id[sit] =
params->tp_init_max_streams_bidi << SIT_SHIFT;
sit = gen_sit(conn->ifc_flags & IFC_SERVER, SD_UNI);
conn->ifc_max_allowed_stream_id[sit] =
params->tp_init_max_streams_uni << SIT_SHIFT;
conn->ifc_max_stream_data_uni = params->tp_init_max_stream_data_uni;
if (params->tp_init_max_data < conn->ifc_pub.conn_cap.cc_sent)
{
ABORT_WARN("peer specified init_max_data=%"PRIu64" bytes, which is "
"smaller than the amount of data already sent on this connection "
"(%"PRIu64" bytes)", params->tp_init_max_data,
conn->ifc_pub.conn_cap.cc_sent);
return -1;
}
conn->ifc_pub.conn_cap.cc_max = params->tp_init_max_data;
for (el = lsquic_hash_first(conn->ifc_pub.all_streams); el;
el = lsquic_hash_next(conn->ifc_pub.all_streams))
{
stream = lsquic_hashelem_getdata(el);
if (is_our_stream(conn, stream))
limit = params->tp_init_max_stream_data_bidi_remote;
else
limit = params->tp_init_max_stream_data_bidi_local;
if (0 != lsquic_stream_set_max_send_off(stream, limit))
{
ABORT_WARN("cannot set peer-supplied max_stream_data=%"PRIu64
"on stream %"PRIu64, limit, stream->id);
return -1;
}
}
if (conn->ifc_flags & IFC_SERVER)
conn->ifc_cfg.max_stream_send
= params->tp_init_max_stream_data_bidi_local;
else
conn->ifc_cfg.max_stream_send
= params->tp_init_max_stream_data_bidi_remote;
conn->ifc_cfg.ack_exp = params->tp_ack_delay_exponent;
switch ((!!conn->ifc_settings->es_idle_timeout << 1)
| !!params->tp_max_idle_timeout)
{
case (0 << 1) | 0:
LSQ_DEBUG("neither side specified max idle time out, turn it off");
break;
case (0 << 1) | 1:
LSQ_DEBUG("peer specified max idle timeout of %"PRIu64" ms (vs ours "
"of zero): use it", params->tp_max_idle_timeout);
conn->ifc_idle_to = params->tp_max_idle_timeout * 1000;
break;
case (1 << 1) | 0:
LSQ_DEBUG("peer did not specify max idle timeout, while ours is "
"%u ms: use it", conn->ifc_settings->es_idle_timeout * 1000);
conn->ifc_idle_to = conn->ifc_settings->es_idle_timeout * 1000000;
break;
default:/* (1 << 1) | 1 */
LSQ_DEBUG("our max idle timeout is %u ms, peer's is %"PRIu64" ms; "
"use minimum value of %"PRIu64" ms",
conn->ifc_settings->es_idle_timeout * 1000,
params->tp_max_idle_timeout,
MIN(conn->ifc_settings->es_idle_timeout * 1000,
params->tp_max_idle_timeout));
conn->ifc_idle_to = 1000 * MIN(conn->ifc_settings->es_idle_timeout
* 1000, params->tp_max_idle_timeout);
break;
}
if (conn->ifc_idle_to >= 2000000
&& conn->ifc_enpub->enp_settings.es_ping_period)
conn->ifc_ping_period = conn->ifc_idle_to / 2;
else
conn->ifc_ping_period = 0;
LSQ_DEBUG("PING period is set to %"PRIu64" usec", conn->ifc_ping_period);
if (conn->ifc_settings->es_delayed_acks
&& (params->tp_set
& ((1 << TPI_MIN_ACK_DELAY)|(1 << TPI_MIN_ACK_DELAY_02))))
{
/* We do not use the min_ack_delay value for anything at the moment,
* as ACK_FREQUENCY frames we generate do not change the peer's max
* ACK delay. When or if we do decide to do it, don't forget to use
* the correct value here -- based on which TP is set!
*/
LSQ_DEBUG("delayed ACKs enabled");
conn->ifc_flags |= IFC_DELAYED_ACKS;
lsquic_alarmset_init_alarm(&conn->ifc_alset, AL_PACK_TOL,
packet_tolerance_alarm_expired, conn);
}
if (conn->ifc_settings->es_timestamps
&& (params->tp_set & (1 << TPI_TIMESTAMPS))
&& (params->tp_numerics[TPI_TIMESTAMPS] & TS_WANT_THEM))
{
LSQ_DEBUG("timestamps enabled: will send TIMESTAMP frames");
conn->ifc_flags |= IFC_TIMESTAMPS;
}
if (conn->ifc_settings->es_datagrams
&& (params->tp_set & (1 << TPI_MAX_DATAGRAM_FRAME_SIZE)))
{
LSQ_DEBUG("datagrams enabled");
conn->ifc_flags |= IFC_DATAGRAMS;
conn->ifc_max_dg_sz =
params->tp_numerics[TPI_MAX_DATAGRAM_FRAME_SIZE] > USHRT_MAX
? USHRT_MAX : params->tp_numerics[TPI_MAX_DATAGRAM_FRAME_SIZE];
}
conn->ifc_pub.max_peer_ack_usec = params->tp_max_ack_delay * 1000;
if ((params->tp_set & (1 << TPI_MAX_UDP_PAYLOAD_SIZE))
/* Second check is so that we don't truncate a large value when
* storing it in unsigned short.
*/
&& params->tp_numerics[TPI_MAX_UDP_PAYLOAD_SIZE]
< TP_DEF_MAX_UDP_PAYLOAD_SIZE)
conn->ifc_max_udp_payload = params->tp_numerics[TPI_MAX_UDP_PAYLOAD_SIZE];
else
conn->ifc_max_udp_payload = TP_DEF_MAX_UDP_PAYLOAD_SIZE;
if (conn->ifc_max_udp_payload < CUR_NPATH(conn)->np_pack_size)
{
CUR_NPATH(conn)->np_pack_size = conn->ifc_max_udp_payload;
LSQ_DEBUG("decrease packet size to %hu bytes",
CUR_NPATH(conn)->np_pack_size);
}
if (params->tp_active_connection_id_limit > conn->ifc_conn.cn_n_cces)
conn->ifc_active_cids_limit = conn->ifc_conn.cn_n_cces;
else
conn->ifc_active_cids_limit = params->tp_active_connection_id_limit;
conn->ifc_first_active_cid_seqno = conn->ifc_scid_seqno;
return 0;
}
static void
randomize_qpack_settings (struct ietf_full_conn *conn, const char *side,
unsigned *dyn_table_size, unsigned *max_risked_streams)
{
const unsigned char nybble = lsquic_crand_get_nybble(
conn->ifc_enpub->enp_crand);
/* For each setting, select one of four levels:
* Table size: 0, 1/4, 1/2, and 1/1 of dyn_table_size
* Risked streams: 0, 1, 5, and max_risked_streams
*/
switch (nybble & 3)
{ case 0: *dyn_table_size = 0; break;
case 1: *dyn_table_size /= 4; break;
case 2: *dyn_table_size /= 2; break;
default: break;
}
if (*dyn_table_size)
switch ((nybble >> 2) & 3)
{ case 0: *max_risked_streams = 0; break;
case 1: *max_risked_streams = MIN(1, *max_risked_streams); break;
case 2: *max_risked_streams = MIN(5, *max_risked_streams); break;
default: break;
}
else
*max_risked_streams = 0;
LSQ_INFO("randomized QPACK %s settings: table size: %u; risked "
"streams: %u", side, *dyn_table_size, *max_risked_streams);
}
static int
init_http (struct ietf_full_conn *conn)
{
unsigned max_risked_streams, dyn_table_size;
fiu_return_on("full_conn_ietf/init_http", -1);
lsquic_qeh_init(&conn->ifc_qeh, &conn->ifc_conn);
if (conn->ifc_settings->es_qpack_experiment)
{
conn->ifc_qeh.qeh_exp_rec = lsquic_qpack_exp_new();
if (conn->ifc_qeh.qeh_exp_rec)
{
conn->ifc_qeh.qeh_exp_rec->qer_flags |= QER_SERVER & conn->ifc_flags;
conn->ifc_qeh.qeh_exp_rec->qer_flags |= QER_ENCODER;
}
}
if (0 == avail_streams_count(conn, conn->ifc_flags & IFC_SERVER,
SD_UNI))
{
ABORT_QUIETLY(1, HEC_GENERAL_PROTOCOL_ERROR, "cannot create "
"control stream due to peer-imposed limit");
conn->ifc_error = CONN_ERR(1, HEC_GENERAL_PROTOCOL_ERROR);
return -1;
}
if (0 != create_ctl_stream_out(conn))
{
ABORT_WARN("cannot create outgoing control stream");
return -1;
}
dyn_table_size = conn->ifc_settings->es_qpack_dec_max_size;
max_risked_streams = conn->ifc_settings->es_qpack_dec_max_blocked;
if (conn->ifc_settings->es_qpack_experiment == 2)
randomize_qpack_settings(conn, "decoder", &dyn_table_size,
&max_risked_streams);
if (0 != lsquic_hcso_write_settings(&conn->ifc_hcso,
conn->ifc_settings->es_max_header_list_size, dyn_table_size,
max_risked_streams, conn->ifc_flags & IFC_SERVER))
{
ABORT_WARN("cannot write SETTINGS");
return -1;
}
if (!(conn->ifc_flags & IFC_SERVER)
&& (conn->ifc_u.cli.ifcli_flags & IFCLI_PUSH_ENABLED)
&& 0 != lsquic_hcso_write_max_push_id(&conn->ifc_hcso,
conn->ifc_u.cli.ifcli_max_push_id))
{
ABORT_WARN("cannot write MAX_PUSH_ID");
return -1;
}
if (0 != lsquic_qdh_init(&conn->ifc_qdh, &conn->ifc_conn,
conn->ifc_flags & IFC_SERVER, conn->ifc_enpub,
dyn_table_size, max_risked_streams))
{
ABORT_WARN("cannot initialize QPACK decoder");
return -1;
}
if (avail_streams_count(conn, conn->ifc_flags & IFC_SERVER, SD_UNI) > 0)
{
if (0 != create_qdec_stream_out(conn))
{
ABORT_WARN("cannot create outgoing QPACK decoder stream");
return -1;
}
}
else
{
queue_streams_blocked_frame(conn, SD_UNI);
LSQ_DEBUG("cannot create outgoing QPACK decoder stream due to "
"unidir limits");
}
conn->ifc_flags |= IFC_HTTP_INITED;
return 0;
}
static int
handshake_ok (struct lsquic_conn *lconn)
{
struct ietf_full_conn *const conn = (struct ietf_full_conn *) lconn;
struct dcid_elem *dce;
const struct transport_params *params;
char buf[MAX_TP_STR_SZ];
fiu_return_on("full_conn_ietf/handshake_ok", -1);
/* Need to set this flag even we hit an error in the rest of this funciton.
* This is because this flag is used to calculate packet out header size
*/
lconn->cn_flags |= LSCONN_HANDSHAKE_DONE;
params = lconn->cn_esf.i->esfi_get_peer_transport_params(
lconn->cn_enc_session);
if (!params)
{
ABORT_WARN("could not get transport parameters");
return -1;
}
LSQ_DEBUG("peer transport parameters: %s",
((lconn->cn_version == LSQVER_ID27 ? lsquic_tp_to_str_27
: lsquic_tp_to_str)(params, buf, sizeof(buf)), buf));
if (0 != apply_trans_params(conn, params))
return -1;
dce = get_new_dce(conn);
if (!dce)
{
ABORT_WARN("cannot allocate DCE");
return -1;
}
memset(dce, 0, sizeof(*dce));
dce->de_cid = *CUR_DCID(conn);
dce->de_seqno = 0;
if (params->tp_set & (1 << TPI_STATELESS_RESET_TOKEN))
{
memcpy(dce->de_srst, params->tp_stateless_reset_token,
sizeof(dce->de_srst));
dce->de_flags = DE_SRST | DE_ASSIGNED;
if (conn->ifc_enpub->enp_srst_hash)
{
if (!lsquic_hash_insert(conn->ifc_enpub->enp_srst_hash,
dce->de_srst, sizeof(dce->de_srst), &conn->ifc_conn,
&dce->de_hash_el))
{
ABORT_WARN("cannot insert DCE");
return -1;
}
}
}
else
dce->de_flags = DE_ASSIGNED;
LSQ_INFO("applied peer transport parameters");
if ((conn->ifc_flags & (IFC_HTTP|IFC_HTTP_INITED)) == IFC_HTTP)
if (0 != init_http(conn))
return -1;
if (conn->ifc_settings->es_dplpmtud)
conn->ifc_mflags |= MF_CHECK_MTU_PROBE;
if (can_issue_cids(conn) && CN_SCID(&conn->ifc_conn)->len != 0)
conn->ifc_send_flags |= SF_SEND_NEW_CID;
maybe_create_delayed_streams(conn);
if (!(conn->ifc_flags & IFC_SERVER))
lsquic_send_ctl_0rtt_to_1rtt(&conn->ifc_send_ctl);
return 0;
}
static void
ietf_full_conn_ci_hsk_done (struct lsquic_conn *lconn,
enum lsquic_hsk_status status)
{
struct ietf_full_conn *const conn = (struct ietf_full_conn *) lconn;
lsquic_alarmset_unset(&conn->ifc_alset, AL_HANDSHAKE);
switch (status)
{
case LSQ_HSK_OK:
case LSQ_HSK_RESUMED_OK:
if (0 == handshake_ok(lconn))
{
if (!(conn->ifc_flags & IFC_SERVER))
lsquic_send_ctl_begin_optack_detection(&conn->ifc_send_ctl);
}
else
{
LSQ_INFO("handshake was reported successful, but later processing "
"produced an error");
status = LSQ_HSK_FAIL;
handshake_failed(lconn);
}
break;
default:
case LSQ_HSK_RESUMED_FAIL: /* IETF crypto never returns this */
assert(0);
/* fall-through */
case LSQ_HSK_FAIL:
handshake_failed(lconn);
break;
}
if (conn->ifc_enpub->enp_stream_if->on_hsk_done)
conn->ifc_enpub->enp_stream_if->on_hsk_done(lconn, status);
}
static void
ietf_full_conn_ci_tls_alert (struct lsquic_conn *lconn, uint8_t alert)
{
struct ietf_full_conn *const conn = (struct ietf_full_conn *) lconn;
ABORT_QUIETLY(0, 0x100 + alert, "TLS alert %"PRIu8, alert);
}
static int
ietf_full_conn_ci_report_live (struct lsquic_conn *lconn, lsquic_time_t now)
{
struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn;
if (conn->ifc_last_live_update + 30000000 < now)
{
conn->ifc_last_live_update = now;
return 1;
}
else
return 0;
}
static int
ietf_full_conn_ci_is_push_enabled (struct lsquic_conn *lconn)
{
struct ietf_full_conn *const conn = (struct ietf_full_conn *) lconn;
return (conn->ifc_flags & IFC_SERVER)
&& (conn->ifc_u.ser.ifser_flags
& (IFSER_PUSH_ENABLED|IFSER_MAX_PUSH_ID))
== (IFSER_PUSH_ENABLED|IFSER_MAX_PUSH_ID)
&& conn->ifc_u.ser.ifser_next_push_id
<= conn->ifc_u.ser.ifser_max_push_id
&& !either_side_going_away(conn)
&& avail_streams_count(conn, 1, SD_UNI) > 0
;
}
static void
undo_stream_creation (struct ietf_full_conn *conn,
struct lsquic_stream *stream)
{
enum stream_dir sd;
assert(stream->sm_hash_el.qhe_flags & QHE_HASHED);
assert(!(stream->stream_flags & STREAM_ONCLOSE_DONE));
LSQ_DEBUG("undo creation of stream %"PRIu64, stream->id);
lsquic_hash_erase(conn->ifc_pub.all_streams, &stream->sm_hash_el);
sd = (stream->id >> SD_SHIFT) & 1;
--conn->ifc_n_created_streams[sd];
lsquic_stream_destroy(stream);
}
/* This function is long because there are a lot of steps to perform, several
* things can go wrong, which we want to roll back, yet at the same time we
* want to do everything efficiently.
*/
static int
ietf_full_conn_ci_push_stream (struct lsquic_conn *lconn, void *hset,
struct lsquic_stream *dep_stream, const struct lsquic_http_headers *headers)
{
struct ietf_full_conn *const conn = (struct ietf_full_conn *) lconn;
unsigned char *header_block_buf, *end, *p;
size_t hea_sz, enc_sz;
ssize_t prefix_sz;
struct lsquic_hash_elem *el;
struct push_promise *promise;
struct lsquic_stream *pushed_stream;
struct uncompressed_headers *uh;
enum lsqpack_enc_status enc_st;
int i;
unsigned char discard[2];
struct lsxpack_header *xhdr;
if (!ietf_full_conn_ci_is_push_enabled(lconn)
|| !lsquic_stream_can_push(dep_stream))
{
LSQ_DEBUG("cannot push using stream %"PRIu64, dep_stream->id);
return -1;
}
if (!hset)
{
LSQ_ERROR("header set must be specified when pushing");
return -1;
}
if (0 != lsqpack_enc_start_header(&conn->ifc_qeh.qeh_encoder, 0, 0))
{
LSQ_WARN("cannot start header for push stream");
return -1;
}
header_block_buf = lsquic_mm_get_4k(conn->ifc_pub.mm);
if (!header_block_buf)
{
LSQ_WARN("cannot allocate 4k");
(void) lsqpack_enc_cancel_header(&conn->ifc_qeh.qeh_encoder);
return -1;
}
/* Generate header block in cheap 4K memory. It it will be copied to
* a new push_promise object.
*/
p = header_block_buf;
end = header_block_buf + 0x1000;
enc_sz = 0; /* Should not change */
for (i = 0; i < headers->count; ++i)
{
xhdr = &headers->headers[i];
if (!xhdr->buf)
continue;
hea_sz = end - p;
enc_st = lsqpack_enc_encode(&conn->ifc_qeh.qeh_encoder, NULL,
&enc_sz, p, &hea_sz, xhdr, LQEF_NO_HIST_UPD|LQEF_NO_DYN);
if (enc_st == LQES_OK)
p += hea_sz;
else
{
(void) lsqpack_enc_cancel_header(&conn->ifc_qeh.qeh_encoder);
lsquic_mm_put_4k(conn->ifc_pub.mm, header_block_buf);
LSQ_DEBUG("cannot encode header field for push %u", enc_st);
return -1;
}
}
prefix_sz = lsqpack_enc_end_header(&conn->ifc_qeh.qeh_encoder,
discard, sizeof(discard), NULL);
if (!(prefix_sz == 2 && discard[0] == 0 && discard[1] == 0))
{
LSQ_WARN("stream push: unexpected prefix values %zd, %hhu, %hhu",
prefix_sz, discard[0], discard[1]);
lsquic_mm_put_4k(conn->ifc_pub.mm, header_block_buf);
return -1;
}
LSQ_DEBUG("generated push promise header block of %ld bytes",
(long) (p - header_block_buf));
pushed_stream = create_push_stream(conn);
if (!pushed_stream)
{
LSQ_WARN("could not create push stream");
lsquic_mm_put_4k(conn->ifc_pub.mm, header_block_buf);
return -1;
}
promise = malloc(sizeof(*promise) + (p - header_block_buf));
if (!promise)
{
LSQ_WARN("stream push: cannot allocate promise");
lsquic_mm_put_4k(conn->ifc_pub.mm, header_block_buf);
undo_stream_creation(conn, pushed_stream);
return -1;
}
uh = malloc(sizeof(*uh));
if (!uh)
{
LSQ_WARN("stream push: cannot allocate uh");
free(promise);
lsquic_mm_put_4k(conn->ifc_pub.mm, header_block_buf);
undo_stream_creation(conn, pushed_stream);
return -1;
}
uh->uh_stream_id = pushed_stream->id;
uh->uh_oth_stream_id = 0;
uh->uh_weight = lsquic_stream_priority(dep_stream) / 2 + 1;
uh->uh_exclusive = 0;
uh->uh_flags = UH_FIN;
uh->uh_hset = hset;
memset(promise, 0, sizeof(*promise));
promise->pp_refcnt = 1; /* This function itself keeps a reference */
memcpy(promise->pp_content_buf, header_block_buf, p - header_block_buf);
promise->pp_content_len = p - header_block_buf;
promise->pp_id = conn->ifc_u.ser.ifser_next_push_id++;
lsquic_mm_put_4k(conn->ifc_pub.mm, header_block_buf);
el = lsquic_hash_insert(conn->ifc_pub.u.ietf.promises,
&promise->pp_id, sizeof(promise->pp_id), promise,
&promise->pp_hash_id);
if (!el)
{
LSQ_WARN("cannot insert push promise (ID)");
undo_stream_creation(conn, pushed_stream);
lsquic_pp_put(promise, conn->ifc_pub.u.ietf.promises);
free(uh);
return -1;
}
if (0 != lsquic_stream_push_promise(dep_stream, promise))
{
LSQ_DEBUG("push promise failed");
undo_stream_creation(conn, pushed_stream);
lsquic_pp_put(promise, conn->ifc_pub.u.ietf.promises);
free(uh);
return -1;
}
if (0 != lsquic_stream_uh_in(pushed_stream, uh))
{
LSQ_WARN("stream barfed when fed synthetic request");
undo_stream_creation(conn, pushed_stream);
free(uh);
if (0 != lsquic_hcso_write_cancel_push(&conn->ifc_hcso,
promise->pp_id))
ABORT_WARN("cannot write CANCEL_PUSH");
lsquic_pp_put(promise, conn->ifc_pub.u.ietf.promises);
return -1;
}
/* Linking push promise with pushed stream is necessary for cancellation */
++promise->pp_refcnt;
promise->pp_pushed_stream = pushed_stream;
pushed_stream->sm_promise = promise;
lsquic_stream_call_on_new(pushed_stream);
lsquic_pp_put(promise, conn->ifc_pub.u.ietf.promises);
return 0;
}
static int
ietf_full_conn_ci_is_tickable (struct lsquic_conn *lconn)
{
struct ietf_full_conn *const conn = (struct ietf_full_conn *) lconn;
struct lsquic_stream *stream;
if (!TAILQ_EMPTY(&conn->ifc_pub.service_streams))
{
LSQ_DEBUG("tickable: there are streams to be serviced");
return 1;
}
if ((conn->ifc_enpub->enp_flags & ENPUB_CAN_SEND)
&& (should_generate_ack(conn, IFC_ACK_QUEUED) ||
!lsquic_send_ctl_sched_is_blocked(&conn->ifc_send_ctl)))
{
/* XXX What about queued ACKs: why check but not make tickable? */
if (conn->ifc_send_flags)
{
LSQ_DEBUG("tickable: send flags: 0x%X", conn->ifc_send_flags);
goto check_can_send;
}
if (lsquic_send_ctl_has_sendable(&conn->ifc_send_ctl))
{
LSQ_DEBUG("tickable: has sendable packets");
return 1; /* Don't check can_send: already on scheduled queue */
}
if (conn->ifc_conn.cn_flags & LSCONN_SEND_BLOCKED)
{
LSQ_DEBUG("tickable: send DATA_BLOCKED frame");
goto check_can_send;
}
if (conn->ifc_mflags & MF_WANT_DATAGRAM_WRITE)
{
LSQ_DEBUG("tickable: want to write DATAGRAM frame");
goto check_can_send;
}
if (conn->ifc_conn.cn_flags & LSCONN_HANDSHAKE_DONE ?
lsquic_send_ctl_has_buffered(&conn->ifc_send_ctl) :
lsquic_send_ctl_has_buffered_high(&conn->ifc_send_ctl))
{
LSQ_DEBUG("tickable: has buffered packets");
goto check_can_send;
}
if (!TAILQ_EMPTY(&conn->ifc_pub.sending_streams))
{
LSQ_DEBUG("tickable: there are sending streams");
goto check_can_send;
}
TAILQ_FOREACH(stream, &conn->ifc_pub.write_streams, next_write_stream)
if (lsquic_stream_write_avail(stream))
{
LSQ_DEBUG("tickable: stream %"PRIu64" can be written to",
stream->id);
goto check_can_send;
}
goto check_readable_streams;
check_can_send:
if (lsquic_send_ctl_can_send(&conn->ifc_send_ctl))
return 1;
}
check_readable_streams:
TAILQ_FOREACH(stream, &conn->ifc_pub.read_streams, next_read_stream)
if (lsquic_stream_readable(stream))
{
LSQ_DEBUG("tickable: stream %"PRIu64" can be read from",
stream->id);
return 1;
}
LSQ_DEBUG("not tickable");
return 0;
}
static enum tick_st
immediate_close (struct ietf_full_conn *conn)
{
struct lsquic_packet_out *packet_out;
const char *error_reason;
struct conn_err conn_err;
int sz;
if (conn->ifc_flags & (IFC_TICK_CLOSE|IFC_GOT_PRST))
return TICK_CLOSE;
if (!(conn->ifc_flags & IFC_SERVER)
&& conn->ifc_u.cli.ifcli_ver_neg.vn_state != VN_END)
return TICK_CLOSE;
conn->ifc_flags |= IFC_TICK_CLOSE;
/* No reason to send anything that's been scheduled if connection is
* being closed immedately. This also ensures that packet numbers
* sequence is always increasing.
*/
lsquic_send_ctl_drop_scheduled(&conn->ifc_send_ctl);
if ((conn->ifc_flags & (IFC_TIMED_OUT|IFC_HSK_FAILED))
&& conn->ifc_settings->es_silent_close)
return TICK_CLOSE;
packet_out = lsquic_send_ctl_new_packet_out(&conn->ifc_send_ctl, 0,
PNS_APP, CUR_NPATH(conn));
if (!packet_out)
{
LSQ_WARN("cannot allocate packet: %s", strerror(errno));
return TICK_CLOSE;
}
assert(conn->ifc_flags & (IFC_ERROR|IFC_ABORTED|IFC_HSK_FAILED));
if (conn->ifc_error.u.err != 0)
{
conn_err = conn->ifc_error;
error_reason = conn->ifc_errmsg;
}
else if (conn->ifc_flags & IFC_ERROR)
{
conn_err = CONN_ERR(0, TEC_INTERNAL_ERROR);
error_reason = "connection error";
}
else if (conn->ifc_flags & IFC_ABORTED)
{
conn_err = CONN_ERR(0, TEC_NO_ERROR);
error_reason = "user aborted connection";
}
else if (conn->ifc_flags & IFC_HSK_FAILED)
{
conn_err = CONN_ERR(0, TEC_NO_ERROR);
error_reason = "handshake failed";
}
else
{
conn_err = CONN_ERR(0, TEC_NO_ERROR);
error_reason = NULL;
}
lsquic_send_ctl_scheduled_one(&conn->ifc_send_ctl, packet_out);
sz = conn->ifc_conn.cn_pf->pf_gen_connect_close_frame(
packet_out->po_data + packet_out->po_data_sz,
lsquic_packet_out_avail(packet_out), conn_err.app_error,
conn_err.u.err, error_reason,
error_reason ? strlen(error_reason) : 0);
if (sz < 0) {
LSQ_WARN("%s failed", __func__);
return TICK_CLOSE;
}
if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0,
QUIC_FRAME_CONNECTION_CLOSE, packet_out->po_data_sz, sz))
{
LSQ_WARN("%s: adding frame to packet failed: %d", __func__, errno);
return TICK_CLOSE;
}
lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, sz);
packet_out->po_frame_types |= 1 << QUIC_FRAME_CONNECTION_CLOSE;
conn->ifc_mflags |= MF_CONN_CLOSE_PACK;
LSQ_DEBUG("generated CONNECTION_CLOSE frame in its own packet");
return TICK_SEND|TICK_CLOSE;
}
static void
process_streams_read_events (struct ietf_full_conn *conn)
{
struct lsquic_stream *stream;
int iters;
enum stream_q_flags q_flags, needs_service;
union prio_iter pi;
static const char *const labels[2] = { "read-0", "read-1", };
if (TAILQ_EMPTY(&conn->ifc_pub.read_streams))
return;
conn->ifc_pub.cp_flags &= ~CP_STREAM_UNBLOCKED;
iters = 0;
do
{
conn->ifc_pii->pii_init(&pi, TAILQ_FIRST(&conn->ifc_pub.read_streams),
TAILQ_LAST(&conn->ifc_pub.read_streams, lsquic_streams_tailq),
(uintptr_t) &TAILQ_NEXT((lsquic_stream_t *) NULL, next_read_stream),
&conn->ifc_pub, labels[iters], NULL, NULL);
needs_service = 0;
for (stream = conn->ifc_pii->pii_first(&pi); stream;
stream = conn->ifc_pii->pii_next(&pi))
{
q_flags = stream->sm_qflags & SMQF_SERVICE_FLAGS;
lsquic_stream_dispatch_read_events(stream);
needs_service |= q_flags ^ (stream->sm_qflags & SMQF_SERVICE_FLAGS);
}
conn->ifc_pii->pii_cleanup(&pi);
if (needs_service)
service_streams(conn);
}
while (iters++ == 0 && (conn->ifc_pub.cp_flags & CP_STREAM_UNBLOCKED));
}
static void
process_crypto_stream_read_events (struct ietf_full_conn *conn)
{
struct lsquic_stream **stream;
assert(!(conn->ifc_flags & IFC_SERVER));
for (stream = conn->ifc_u.cli.crypto_streams; stream <
conn->ifc_u.cli.crypto_streams + sizeof(conn->ifc_u.cli.crypto_streams)
/ sizeof(conn->ifc_u.cli.crypto_streams[0]); ++stream)
if (*stream && (*stream)->sm_qflags & SMQF_WANT_READ)
lsquic_stream_dispatch_read_events(*stream);
}
static void
process_crypto_stream_write_events (struct ietf_full_conn *conn)
{
struct lsquic_stream **stream;
assert(!(conn->ifc_flags & IFC_SERVER));
for (stream = conn->ifc_u.cli.crypto_streams; stream <
conn->ifc_u.cli.crypto_streams + sizeof(conn->ifc_u.cli.crypto_streams)
/ sizeof(conn->ifc_u.cli.crypto_streams[0]); ++stream)
if (*stream && (*stream)->sm_qflags & SMQF_WRITE_Q_FLAGS)
lsquic_stream_dispatch_write_events(*stream);
}
static void
maybe_conn_flush_special_streams (struct ietf_full_conn *conn)
{
if (!(conn->ifc_flags & IFC_HTTP))
return;
struct lsquic_stream *const streams[] = {
conn->ifc_hcso.how_stream,
conn->ifc_qeh.qeh_enc_sm_out,
conn->ifc_qdh.qdh_dec_sm_out,
};
struct lsquic_stream *const *stream;
for (stream = streams; stream < streams + sizeof(streams)
/ sizeof(streams[0]); ++stream)
if (*stream && lsquic_stream_has_data_to_flush(*stream))
(void) lsquic_stream_flush(*stream);
}
static int
write_is_possible (struct ietf_full_conn *conn)
{
const lsquic_packet_out_t *packet_out;
packet_out = lsquic_send_ctl_last_scheduled(&conn->ifc_send_ctl, PNS_APP,
CUR_NPATH(conn), 0);
return (packet_out && lsquic_packet_out_avail(packet_out) > 10)
|| lsquic_send_ctl_can_send(&conn->ifc_send_ctl);
}
static void
process_streams_write_events (struct ietf_full_conn *conn, int high_prio)
{
struct lsquic_stream *stream;
union prio_iter pi;
conn->ifc_pii->pii_init(&pi, TAILQ_FIRST(&conn->ifc_pub.write_streams),
TAILQ_LAST(&conn->ifc_pub.write_streams, lsquic_streams_tailq),
(uintptr_t) &TAILQ_NEXT((lsquic_stream_t *) NULL, next_write_stream),
&conn->ifc_pub,
high_prio ? "write-high" : "write-low", NULL, NULL);
if (high_prio)
conn->ifc_pii->pii_drop_non_high(&pi);
else
conn->ifc_pii->pii_drop_high(&pi);
for (stream = conn->ifc_pii->pii_first(&pi);
stream && write_is_possible(conn);
stream = conn->ifc_pii->pii_next(&pi))
if (stream->sm_qflags & SMQF_WRITE_Q_FLAGS)
lsquic_stream_dispatch_write_events(stream);
conn->ifc_pii->pii_cleanup(&pi);
maybe_conn_flush_special_streams(conn);
}
static int
conn_ok_to_close (const struct ietf_full_conn *conn)
{
assert(conn->ifc_flags & IFC_CLOSING);
return !(conn->ifc_flags & IFC_SERVER)
|| (conn->ifc_flags & IFC_RECV_CLOSE)
|| (
!lsquic_send_ctl_have_outgoing_stream_frames(&conn->ifc_send_ctl)
&& !have_bidi_streams(conn)
&& lsquic_send_ctl_have_unacked_stream_frames(
&conn->ifc_send_ctl) == 0);
}
static void
generate_connection_close_packet (struct ietf_full_conn *conn)
{
struct lsquic_packet_out *packet_out;
int sz;
/* FIXME Select PNS based on handshake status (possible on the client): if
* appropriate keys are not available, encryption will fail.
*/
packet_out = lsquic_send_ctl_new_packet_out(&conn->ifc_send_ctl, 0, PNS_APP,
CUR_NPATH(conn));
if (!packet_out)
{
ABORT_ERROR("cannot allocate packet: %s", strerror(errno));
return;
}
lsquic_send_ctl_scheduled_one(&conn->ifc_send_ctl, packet_out);
sz = conn->ifc_conn.cn_pf->pf_gen_connect_close_frame(
packet_out->po_data + packet_out->po_data_sz,
lsquic_packet_out_avail(packet_out), 0, TEC_NO_ERROR, NULL, 0);
if (sz < 0) {
ABORT_ERROR("generate_connection_close_packet failed");
return;
}
if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0,
QUIC_FRAME_CONNECTION_CLOSE, packet_out->po_data_sz, sz))
{
ABORT_ERROR("adding frame to packet failed: %d", errno);
return;
}
lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, sz);
packet_out->po_frame_types |= 1 << QUIC_FRAME_CONNECTION_CLOSE;
conn->ifc_mflags |= MF_CONN_CLOSE_PACK;
LSQ_DEBUG("generated CONNECTION_CLOSE frame in its own packet");
conn->ifc_send_flags &= ~SF_SEND_CONN_CLOSE;
}
static void
log_conn_flow_control (struct ietf_full_conn *conn)
{
LSQ_DEBUG("connection flow cap: wrote: %"PRIu64
"; max: %"PRIu64, conn->ifc_pub.conn_cap.cc_sent,
conn->ifc_pub.conn_cap.cc_max);
LSQ_DEBUG("connection flow control window: read: %"PRIu64
"; max: %"PRIu64, conn->ifc_pub.cfcw.cf_max_recv_off,
conn->ifc_pub.cfcw.cf_recv_off);
}
static void
generate_ping_frame (struct ietf_full_conn *conn, lsquic_time_t unused)
{
struct lsquic_packet_out *packet_out;
int sz;
packet_out = get_writeable_packet(conn, 1);
if (!packet_out)
{
LSQ_DEBUG("cannot get writeable packet for PING frame");
return;
}
sz = conn->ifc_conn.cn_pf->pf_gen_ping_frame(
packet_out->po_data + packet_out->po_data_sz,
lsquic_packet_out_avail(packet_out));
if (sz < 0) {
ABORT_ERROR("gen_ping_frame failed");
return;
}
if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0,
QUIC_FRAME_PING, packet_out->po_data_sz, sz))
{
ABORT_ERROR("adding frame to packet failed: %d", errno);
return;
}
lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, sz);
packet_out->po_frame_types |= 1 << QUIC_FRAME_PING;
LSQ_DEBUG("wrote PING frame");
conn->ifc_send_flags &= ~SF_SEND_PING;
if (!(conn->ifc_flags & IFC_SERVER))
log_conn_flow_control(conn);
}
static void
generate_handshake_done_frame (struct ietf_full_conn *conn,
lsquic_time_t unused)
{
struct lsquic_packet_out *packet_out;
unsigned need;
int sz;
need = conn->ifc_conn.cn_pf->pf_handshake_done_frame_size();
packet_out = get_writeable_packet(conn, need);
if (!packet_out)
return;
sz = conn->ifc_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)
{
ABORT_ERROR("generate_handshake_done_frame failed");
return;
}
if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0,
QUIC_FRAME_HANDSHAKE_DONE, packet_out->po_data_sz, sz))
{
ABORT_ERROR("adding frame to packet failed: %d", errno);
return;
}
lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, sz);
packet_out->po_frame_types |= QUIC_FTBIT_HANDSHAKE_DONE;
LSQ_DEBUG("generated HANDSHAKE_DONE frame");
conn->ifc_send_flags &= ~SF_SEND_HANDSHAKE_DONE;
}
static void
generate_ack_frequency_frame (struct ietf_full_conn *conn, lsquic_time_t unused)
{
struct lsquic_packet_out *packet_out;
unsigned need;
int sz;
/* We tell the peer to ignore reordering because we skip packet numbers to
* detect optimistic ACK attacks.
*/
const int ignore = 1;
need = conn->ifc_conn.cn_pf->pf_ack_frequency_frame_size(
conn->ifc_ack_freq_seqno, conn->ifc_last_calc_pack_tol,
conn->ifc_pub.max_peer_ack_usec);
packet_out = get_writeable_packet(conn, need);
if (!packet_out)
{
LSQ_DEBUG("cannot get writeable packet for ACK_FREQUENCY frame");
return;
}
sz = conn->ifc_conn.cn_pf->pf_gen_ack_frequency_frame(
packet_out->po_data + packet_out->po_data_sz,
lsquic_packet_out_avail(packet_out),
conn->ifc_ack_freq_seqno, conn->ifc_last_calc_pack_tol,
conn->ifc_pub.max_peer_ack_usec, ignore);
if (sz < 0)
{
ABORT_ERROR("gen_ack_frequency_frame failed");
return;
}
if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0,
QUIC_FRAME_ACK_FREQUENCY, packet_out->po_data_sz, sz))
{
ABORT_ERROR("adding frame to packet failed: %d", errno);
return;
}
conn->ifc_last_pack_tol = conn->ifc_last_calc_pack_tol;
lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, sz);
packet_out->po_frame_types |= QUIC_FTBIT_ACK_FREQUENCY;
EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID,
"Generated ACK_FREQUENCY(seqno: %u; pack_tol: %u; "
"upd: %u; ignore: %d)", conn->ifc_ack_freq_seqno,
conn->ifc_last_pack_tol, conn->ifc_pub.max_peer_ack_usec, ignore);
LSQ_DEBUG("Generated ACK_FREQUENCY(seqno: %u; pack_tol: %u; "
"upd: %u; ignore: %d)", conn->ifc_ack_freq_seqno,
conn->ifc_last_pack_tol, conn->ifc_pub.max_peer_ack_usec, ignore);
++conn->ifc_ack_freq_seqno;
conn->ifc_send_flags &= ~SF_SEND_ACK_FREQUENCY;
#if LSQUIC_CONN_STATS
if (conn->ifc_last_pack_tol > conn->ifc_max_pack_tol_sent)
conn->ifc_max_pack_tol_sent = conn->ifc_last_pack_tol;
if (conn->ifc_last_pack_tol < conn->ifc_min_pack_tol_sent
|| 0 == conn->ifc_min_pack_tol_sent)
conn->ifc_min_pack_tol_sent = conn->ifc_last_pack_tol;
#endif
}
static void
maybe_pad_packet (struct ietf_full_conn *conn,
struct lsquic_packet_out *packet_out)
{
unsigned short avail;
avail = lsquic_packet_out_avail(packet_out);
if (avail)
{
memset(packet_out->po_data + packet_out->po_data_sz, 0, avail);
lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, avail);
packet_out->po_frame_types |= QUIC_FTBIT_PADDING;
LSQ_DEBUG("added %hu-byte PADDING frame to packet %"PRIu64, avail,
packet_out->po_packno);
}
}
static void
generate_path_chal_frame (struct ietf_full_conn *conn, lsquic_time_t now,
unsigned path_id)
{
struct lsquic_packet_out *packet_out;
struct conn_path *copath;
unsigned need;
int w;
char hexbuf[ sizeof(copath->cop_path_chals[0]) * 2 + 1 ];
/* For now, we only support sending path challenges on a single path.
* This restriction may need to be lifted if the client is probing
* several paths at the same time.
*/
if (!(conn->ifc_flags & IFC_SERVER))
assert(path_id == conn->ifc_mig_path_id);
copath = &conn->ifc_paths[path_id];
if (copath->cop_n_chals >= sizeof(copath->cop_path_chals)
/ sizeof(copath->cop_path_chals[0]))
{
/* TODO: path failure? */
assert(0);
return;
}
need = conn->ifc_conn.cn_pf->pf_path_chal_frame_size();
packet_out = get_writeable_packet_on_path(conn, need, &copath->cop_path, 1);
if (!packet_out)
return;
RAND_bytes((void *) &copath->cop_path_chals[copath->cop_n_chals],
sizeof(copath->cop_path_chals[0]));
w = conn->ifc_conn.cn_pf->pf_gen_path_chal_frame(
packet_out->po_data + packet_out->po_data_sz,
lsquic_packet_out_avail(packet_out),
copath->cop_path_chals[copath->cop_n_chals]);
if (w < 0)
{
ABORT_ERROR("generating PATH_CHALLENGE frame failed: %d", errno);
return;
}
LSQ_DEBUG("generated %d-byte PATH_CHALLENGE frame; challenge: %s"
", seq: %u", w,
HEXSTR((unsigned char *) &copath->cop_path_chals[copath->cop_n_chals],
sizeof(copath->cop_path_chals[copath->cop_n_chals]), hexbuf),
copath->cop_n_chals);
++copath->cop_n_chals;
EV_LOG_GENERATED_PATH_CHAL_FRAME(LSQUIC_LOG_CONN_ID, conn->ifc_conn.cn_pf,
packet_out->po_data + packet_out->po_data_sz, w);
if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0,
QUIC_FRAME_PATH_CHALLENGE, packet_out->po_data_sz, w))
{
ABORT_ERROR("adding frame to packet failed: %d", errno);
return;
}
packet_out->po_frame_types |= QUIC_FTBIT_PATH_CHALLENGE;
lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, w);
packet_out->po_regen_sz += w;
maybe_pad_packet(conn, packet_out);
conn->ifc_send_flags &= ~(SF_SEND_PATH_CHAL << path_id);
lsquic_alarmset_set(&conn->ifc_alset, AL_PATH_CHAL + path_id,
now + (INITIAL_CHAL_TIMEOUT << (copath->cop_n_chals - 1)));
}
static void
generate_path_chal_0 (struct ietf_full_conn *conn, lsquic_time_t now)
{
generate_path_chal_frame(conn, now, 0);
}
static void
generate_path_chal_1 (struct ietf_full_conn *conn, lsquic_time_t now)
{
generate_path_chal_frame(conn, now, 1);
}
static void
generate_path_chal_2 (struct ietf_full_conn *conn, lsquic_time_t now)
{
generate_path_chal_frame(conn, now, 2);
}
static void
generate_path_chal_3 (struct ietf_full_conn *conn, lsquic_time_t now)
{
generate_path_chal_frame(conn, now, 3);
}
static void
generate_path_resp_frame (struct ietf_full_conn *conn, lsquic_time_t now,
unsigned path_id)
{
struct lsquic_packet_out *packet_out;
struct conn_path *copath;
unsigned need;
int w;
copath = &conn->ifc_paths[path_id];
need = conn->ifc_conn.cn_pf->pf_path_resp_frame_size();
packet_out = get_writeable_packet_on_path(conn, need, &copath->cop_path, 1);
if (!packet_out)
return;
w = conn->ifc_conn.cn_pf->pf_gen_path_resp_frame(
packet_out->po_data + packet_out->po_data_sz,
lsquic_packet_out_avail(packet_out),
copath->cop_inc_chal);
if (w < 0)
{
ABORT_ERROR("generating PATH_RESPONSE frame failed: %d", errno);
return;
}
LSQ_DEBUG("generated %d-byte PATH_RESPONSE frame; response: %016"PRIX64,
w, copath->cop_inc_chal);
EV_LOG_GENERATED_PATH_RESP_FRAME(LSQUIC_LOG_CONN_ID, conn->ifc_conn.cn_pf,
packet_out->po_data + packet_out->po_data_sz, w);
if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0,
QUIC_FRAME_PATH_RESPONSE, packet_out->po_data_sz, w))
{
ABORT_ERROR("adding frame to packet failed: %d", errno);
return;
}
packet_out->po_frame_types |= QUIC_FTBIT_PATH_RESPONSE;
lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, w);
maybe_pad_packet(conn, packet_out);
packet_out->po_regen_sz += w;
conn->ifc_send_flags &= ~(SF_SEND_PATH_RESP << path_id);
}
static void
generate_path_resp_0 (struct ietf_full_conn *conn, lsquic_time_t now)
{
generate_path_resp_frame(conn, now, 0);
}
static void
generate_path_resp_1 (struct ietf_full_conn *conn, lsquic_time_t now)
{
generate_path_resp_frame(conn, now, 1);
}
static void
generate_path_resp_2 (struct ietf_full_conn *conn, lsquic_time_t now)
{
generate_path_resp_frame(conn, now, 2);
}
static void
generate_path_resp_3 (struct ietf_full_conn *conn, lsquic_time_t now)
{
generate_path_resp_frame(conn, now, 3);
}
static struct lsquic_packet_out *
ietf_full_conn_ci_next_packet_to_send (struct lsquic_conn *lconn,
const struct to_coal *to_coal)
{
struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn;
struct lsquic_packet_out *packet_out;
const struct conn_path *cpath;
packet_out = lsquic_send_ctl_next_packet_to_send(&conn->ifc_send_ctl,
to_coal);
if (packet_out)
{
cpath = NPATH2CPATH(packet_out->po_path);
lsquic_packet_out_set_spin_bit(packet_out, cpath->cop_spin_bit);
}
return packet_out;
}
static struct lsquic_packet_out *
ietf_full_conn_ci_next_packet_to_send_pre_hsk (struct lsquic_conn *lconn,
const struct to_coal *to_coal)
{
struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn;
struct lsquic_packet_out *packet_out;
packet_out = ietf_full_conn_ci_next_packet_to_send(lconn, to_coal);
if (packet_out)
++conn->ifc_u.cli.ifcli_packets_out;
return packet_out;
}
static lsquic_time_t
ietf_full_conn_ci_next_tick_time (struct lsquic_conn *lconn, unsigned *why)
{
struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn;
lsquic_time_t alarm_time, pacer_time, now;
enum alarm_id al_id;
alarm_time = lsquic_alarmset_mintime(&conn->ifc_alset, &al_id);
pacer_time = lsquic_send_ctl_next_pacer_time(&conn->ifc_send_ctl);
if (pacer_time && LSQ_LOG_ENABLED(LSQ_LOG_DEBUG))
{
now = lsquic_time_now();
if (pacer_time < now)
LSQ_DEBUG("%s: pacer is %"PRIu64" usec in the past", __func__,
now - pacer_time);
}
if (alarm_time && pacer_time)
{
if (alarm_time < pacer_time)
{
*why = N_AEWS + al_id;
return alarm_time;
}
else
{
*why = AEW_PACER;
return pacer_time;
}
}
else if (alarm_time)
{
*why = N_AEWS + al_id;
return alarm_time;
}
else if (pacer_time)
{
*why = AEW_PACER;
return pacer_time;
}
else
return 0;
}
static ptrdiff_t
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
process_padding_frame (struct ietf_full_conn *conn,
struct lsquic_packet_in *packet_in, const unsigned char *p, size_t len)
{
return (unsigned) count_zero_bytes(p, len);
}
static void
handshake_confirmed (struct ietf_full_conn *conn)
{
ignore_hsk(conn);
/* Even in ID-25, we wait for 1-RTT ACK on the server before dropping keys.
*/
conn->ifc_conn.cn_esf.i->esfi_handshake_confirmed(
conn->ifc_conn.cn_enc_session);
if (!(conn->ifc_flags & (IFC_SERVER|IFC_MIGRA)))
{
conn->ifc_flags |= IFC_MIGRA; /* Perform migration just once */
maybe_start_migration(conn);
}
}
static float
calc_target (lsquic_time_t srtt_ms)
{
if (srtt_ms <= 5 * 1000)
return 2.5;
if (srtt_ms <= 10 * 1000)
return 2.0;
if (srtt_ms <= 15 * 1000)
return 1.6;
if (srtt_ms <= 20 * 1000)
return 1.4;
if (srtt_ms <= 30 * 1000)
return 1.3;
if (srtt_ms <= 40 * 1000)
return 1.2;
if (srtt_ms <= 50 * 1000)
return 1.1;
if (srtt_ms <= 60 * 1000)
return 1.0;
if (srtt_ms <= 70 * 1000)
return 0.9;
if (srtt_ms <= 80 * 1000)
return 0.8;
if (srtt_ms <= 100 * 1000)
return 0.7;
return 0.5;
}
static void
packet_tolerance_alarm_expired (enum alarm_id al_id, void *ctx,
lsquic_time_t expiry, lsquic_time_t now)
{
struct ietf_full_conn *const conn = ctx;
const float Kp = conn->ifc_settings->es_ptpc_prop_gain,
Ki = conn->ifc_settings->es_ptpc_int_gain,
err_thresh = conn->ifc_settings->es_ptpc_err_thresh,
err_divisor = conn->ifc_settings->es_ptpc_err_divisor;
const unsigned periodicity = conn->ifc_settings->es_ptpc_periodicity;
const unsigned max_packtol = conn->ifc_settings->es_ptpc_max_packtol;
float avg_acks_per_rtt, error, combined_error, normalized,
combined_error_abs, target, rtts;
double dt;
lsquic_time_t srtt, begin_t;
srtt = lsquic_rtt_stats_get_srtt(&conn->ifc_pub.rtt_stats);
if (srtt == 0)
goto end;
if (0 == conn->ifc_pts.n_acks)
/* Don't reset last_sample and calculate average for both this and next
* period the next time around.
*/
goto end;
if (conn->ifc_settings->es_ptpc_dyn_target)
target = calc_target(srtt);
else
target = conn->ifc_settings->es_ptpc_target;
dt = periodicity * (double) srtt / 1000000;
begin_t = conn->ifc_pts.last_sample ? conn->ifc_pts.last_sample
: conn->ifc_created;
/*
LSQ_DEBUG("begin: %"PRIu64"; now: %"PRIu64"; SRTT: %"PRIu64"; acks: %u",
begin_t, now, srtt, conn->ifc_pts.n_acks);
*/
rtts = (float) (now - begin_t) / (float) srtt;
avg_acks_per_rtt = (float) conn->ifc_pts.n_acks / (float) rtts;
normalized = avg_acks_per_rtt * M_E / target;
error = logf(normalized) - 1;
conn->ifc_pts.integral_error += error * (float) dt;
combined_error = Kp * error + Ki * conn->ifc_pts.integral_error;
combined_error_abs = fabsf(combined_error);
conn->ifc_pts.last_sample = now;
if (combined_error_abs > err_thresh)
{
unsigned adj = combined_error_abs / err_divisor;
unsigned last_pack_tol = conn->ifc_last_pack_tol;
if (0 == last_pack_tol)
{
last_pack_tol = (unsigned)
lsquic_senhist_largest(&conn->ifc_send_ctl.sc_senhist)
/ conn->ifc_pts.n_acks;
LSQ_DEBUG("packets sent: %"PRIu64"; ACKs received: %u; implied "
"tolerance: %u",
lsquic_senhist_largest(&conn->ifc_send_ctl.sc_senhist),
conn->ifc_pts.n_acks, last_pack_tol);
if (last_pack_tol < 2)
last_pack_tol = 2;
else if (last_pack_tol >= max_packtol)
last_pack_tol = max_packtol / 2;
}
if (combined_error > 0)
{
conn->ifc_last_calc_pack_tol = last_pack_tol + adj;
if (conn->ifc_last_calc_pack_tol >= max_packtol)
{
/* Clamp integral error when we can go no higher */
conn->ifc_pts.integral_error -= error * (float) dt;
conn->ifc_last_calc_pack_tol = max_packtol;
}
}
else
{
if (adj + 2 < last_pack_tol)
conn->ifc_last_calc_pack_tol = last_pack_tol - adj;
else
conn->ifc_last_calc_pack_tol = 2;
if (conn->ifc_last_calc_pack_tol == 2)
{
/* Clamp integral error when we can go no lower */
conn->ifc_pts.integral_error -= error * (float) dt;
}
}
if (conn->ifc_last_calc_pack_tol != conn->ifc_last_pack_tol)
{
LSQ_DEBUG("old packet tolerance target: %u, schedule ACK_FREQUENCY "
"%s to %u", conn->ifc_last_pack_tol,
combined_error > 0 ? "increase" : "decrease",
conn->ifc_last_calc_pack_tol);
conn->ifc_send_flags |= SF_SEND_ACK_FREQUENCY;
}
else
{
LSQ_DEBUG("packet tolerance unchanged at %u", conn->ifc_last_pack_tol);
conn->ifc_send_flags &= ~SF_SEND_ACK_FREQUENCY;
}
}
else
conn->ifc_send_flags &= ~SF_SEND_ACK_FREQUENCY;
LSQ_DEBUG("avg ACKs per RTT: %.3f; normalized: %.3f; target: %.3f; error: %.3f; "
"p-error: %.3f, i-error: %.3f; Overall: %.3f; "
"packet tolerance: current: %u, last: %u",
avg_acks_per_rtt, normalized, target, error, Kp * error,
conn->ifc_pts.integral_error, combined_error,
conn->ifc_last_calc_pack_tol, conn->ifc_last_pack_tol);
/* Until we have the first value, don't reset the counters */
if (conn->ifc_last_calc_pack_tol != 0)
conn->ifc_pts.n_acks = 0;
end:
if (lsquic_send_ctl_have_unacked_retx_data(&conn->ifc_send_ctl))
{
LSQ_DEBUG("set PACK_TOL alarm %"PRIu64" microseconds into the future",
srtt * periodicity);
lsquic_alarmset_set(&conn->ifc_alset, al_id, now + srtt * periodicity);
}
else
LSQ_DEBUG("no unacked retx data: do not rearm the packet tolerance "
"alarm");
}
static int
process_ack (struct ietf_full_conn *conn, struct ack_info *acki,
lsquic_time_t received, lsquic_time_t now)
{
enum packnum_space pns;
lsquic_packno_t packno;
int one_rtt_acked;
CONN_STATS(in.n_acks_proc, 1);
LSQ_DEBUG("Processing ACK");
one_rtt_acked = lsquic_send_ctl_1rtt_acked(&conn->ifc_send_ctl);
if (0 == lsquic_send_ctl_got_ack(&conn->ifc_send_ctl, acki, received, now))
{
pns = acki->pns;
packno = lsquic_send_ctl_largest_ack2ed(&conn->ifc_send_ctl, pns);
/* It's OK to skip valid packno 0: the alternative is too expensive */
if (packno)
lsquic_rechist_stop_wait(&conn->ifc_rechist[ pns ], packno + 1);
/* ACK of 1-RTT packet indicates that handshake has been confirmed: */
if (!one_rtt_acked && lsquic_send_ctl_1rtt_acked(&conn->ifc_send_ctl))
{
if (!(conn->ifc_flags & IFC_IGNORE_INIT))
ignore_init(conn);
handshake_confirmed(conn);
}
return 0;
}
else
{
ABORT_ERROR("Received invalid ACK");
return -1;
}
}
static unsigned
process_path_challenge_frame (struct ietf_full_conn *conn,
struct lsquic_packet_in *packet_in, const unsigned char *p, size_t len)
{
struct conn_path *const path = &conn->ifc_paths[packet_in->pi_path_id];
int parsed_len;
char hexbuf[sizeof(path->cop_inc_chal) * 2 + 1];
parsed_len = conn->ifc_conn.cn_pf->pf_parse_path_chal_frame(p, len,
/* It's OK to overwrite incoming challenge, only reply to latest */
&path->cop_inc_chal);
if (parsed_len > 0)
{
LSQ_DEBUG("received path challenge %s for path #%hhu",
HEXSTR((unsigned char *) &path->cop_inc_chal,
sizeof(path->cop_inc_chal), hexbuf), packet_in->pi_path_id);
conn->ifc_send_flags |= SF_SEND_PATH_RESP << packet_in->pi_path_id;
return parsed_len;
}
else
return 0;
}
/* Why "maybe?" Because it is possible that the peer did not provide us
* enough CIDs and we had to reuse one. See init_new_path().
*/
static void
maybe_retire_dcid (struct ietf_full_conn *conn, const lsquic_cid_t *dcid)
{
struct conn_path *copath;
struct dcid_elem **dce;
unsigned eqs;
eqs = 0;
for (copath = conn->ifc_paths; copath < conn->ifc_paths
+ sizeof(conn->ifc_paths) / sizeof(conn->ifc_paths[0]); ++copath)
eqs += LSQUIC_CIDS_EQ(&copath->cop_path.np_dcid, dcid);
if (eqs > 1)
{
LSQ_INFOC("cannot retire %"CID_FMT", as it is used on more than one"
"path ", CID_BITS(dcid));
return;
}
for (dce = conn->ifc_dces; dce < DCES_END(conn); ++dce)
if (*dce && ((*dce)->de_flags & DE_ASSIGNED)
&& LSQUIC_CIDS_EQ(&(*dce)->de_cid, dcid))
break;
assert(dce < DCES_END(conn));
if (dce < DCES_END(conn))
retire_dcid(conn, dce);
}
/* Return true if the two paths differ only in peer port */
static int
only_peer_port_changed (const struct network_path *old,
struct network_path *new)
{
const struct sockaddr *old_sa, *new_sa;
if (!lsquic_sockaddr_eq(NP_LOCAL_SA(old), NP_LOCAL_SA(new)))
return 0;
old_sa = NP_PEER_SA(old);
new_sa = NP_PEER_SA(new);
if (old_sa->sa_family == AF_INET)
return old_sa->sa_family == new_sa->sa_family
&& ((struct sockaddr_in *) old_sa)->sin_addr.s_addr
== ((struct sockaddr_in *) new_sa)->sin_addr.s_addr
&& ((struct sockaddr_in *) old_sa)->sin_port
!= /* NE! */((struct sockaddr_in *) new_sa)->sin_port;
else
return old_sa->sa_family == new_sa->sa_family
&& ((struct sockaddr_in6 *) old_sa)->sin6_port != /* NE! */
((struct sockaddr_in6 *) new_sa)->sin6_port
&& 0 == memcmp(&((struct sockaddr_in6 *) old_sa)->sin6_addr,
&((struct sockaddr_in6 *) new_sa)->sin6_addr,
sizeof(((struct sockaddr_in6 *) new_sa)->sin6_addr));
}
static void
switch_path_to (struct ietf_full_conn *conn, unsigned char path_id)
{
const unsigned char old_path_id = conn->ifc_cur_path_id;
const int keep_path_properties = conn->ifc_settings->es_optimistic_nat
&& only_peer_port_changed(CUR_NPATH(conn),
&conn->ifc_paths[path_id].cop_path);
assert(conn->ifc_cur_path_id != path_id);
EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "switched paths");
if (keep_path_properties)
{
conn->ifc_paths[path_id].cop_path.np_pack_size
= CUR_NPATH(conn)->np_pack_size;
LSQ_DEBUG("keep path properties: set MTU to %hu",
conn->ifc_paths[path_id].cop_path.np_pack_size);
}
lsquic_send_ctl_repath(&conn->ifc_send_ctl,
CUR_NPATH(conn), &conn->ifc_paths[path_id].cop_path,
keep_path_properties);
maybe_retire_dcid(conn, &CUR_NPATH(conn)->np_dcid);
conn->ifc_cur_path_id = path_id;
conn->ifc_pub.path = CUR_NPATH(conn);
conn->ifc_conn.cn_cur_cce_idx = CUR_CPATH(conn)->cop_cce_idx;
conn->ifc_send_flags &= ~(SF_SEND_PATH_CHAL << old_path_id);
conn->ifc_send_flags &= ~(SF_SEND_PATH_RESP << old_path_id);
lsquic_alarmset_unset(&conn->ifc_alset, AL_PATH_CHAL + old_path_id);
if (conn->ifc_flags & IFC_SERVER)
wipe_path(conn, old_path_id);
}
static unsigned
process_path_response_frame (struct ietf_full_conn *conn,
struct lsquic_packet_in *packet_in, const unsigned char *p, size_t len)
{
struct conn_path *path;
int parsed_len;
unsigned i;
unsigned char path_id;
uint64_t path_resp;
char hexbuf[ sizeof(path_resp) * 2 + 1 ];
parsed_len = conn->ifc_conn.cn_pf->pf_parse_path_resp_frame(p, len,
&path_resp);
if (parsed_len <= 0)
return 0;
LSQ_DEBUG("received path response: %s",
HEXSTR((unsigned char *) &path_resp, sizeof(path_resp), hexbuf));
for (path = conn->ifc_paths; path < conn->ifc_paths
+ sizeof(conn->ifc_paths) / sizeof(conn->ifc_paths[0]); ++path)
{
path_id = path - conn->ifc_paths;
if ((1 << path_id) & conn->ifc_used_paths)
for (i = 0; i < path->cop_n_chals; ++i)
if (path_resp == path->cop_path_chals[i])
goto found;
}
ABORT_QUIETLY(0, TEC_PROTOCOL_VIOLATION,
"received path response %s that does not correspond to any "
"challenge sent on this path",
HEXSTR((unsigned char *) &path_resp, sizeof(path_resp), hexbuf));
return 0;
found:
path->cop_flags |= COP_VALIDATED;
conn->ifc_send_flags &= ~(SF_SEND_PATH_CHAL << path_id);
lsquic_alarmset_unset(&conn->ifc_alset, AL_PATH_CHAL + path_id);
switch ((path_id != conn->ifc_cur_path_id) |
(!!(path->cop_flags & COP_GOT_NONPROB) << 1))
{
case 3:
LSQ_INFO("path validated: switching from path #%hhu to path #%hhu",
conn->ifc_cur_path_id, path_id);
switch_path_to(conn, path_id);
break;
case 1:
if (conn->ifc_flags & IFC_SERVER)
/* If you see this message in the log file, remember that
* COP_GOT_NONPROB is set after all frames in a packet have
* been processed.
*/
LSQ_DEBUG("path #%hhu validated, but since no non-probing frames "
"have been received, delay switching to it",
path_id);
else
{
LSQ_INFO("path validated: switching from path #%hhu to path #%hhu",
conn->ifc_cur_path_id, path_id);
switch_path_to(conn, path_id);
}
break;
default:
LSQ_DEBUG("current path validated");
break;
}
return parsed_len;
}
static lsquic_stream_t *
find_stream_by_id (struct ietf_full_conn *conn, lsquic_stream_id_t stream_id)
{
struct lsquic_hash_elem *el;
el = lsquic_hash_find(conn->ifc_pub.all_streams, &stream_id,
sizeof(stream_id));
if (el)
return lsquic_hashelem_getdata(el);
else
return NULL;
}
static void
maybe_schedule_ss_for_stream (struct ietf_full_conn *conn,
lsquic_stream_id_t stream_id, enum http_error_code error_code)
{
struct stream_id_to_ss *sits;
if (conn_is_stream_closed(conn, stream_id))
return;
sits = malloc(sizeof(*sits));
if (!sits)
return;
sits->sits_stream_id = stream_id;
sits->sits_error_code = error_code;
STAILQ_INSERT_TAIL(&conn->ifc_stream_ids_to_ss, sits, sits_next);
conn->ifc_send_flags |= SF_SEND_STOP_SENDING;
conn_mark_stream_closed(conn, stream_id);
}
struct buffered_priority_update
{
struct lsquic_hash_elem hash_el;
lsquic_stream_id_t stream_id;
struct lsquic_ext_http_prio ehp;
};
/* This function is called to create incoming streams */
static struct lsquic_stream *
new_stream (struct ietf_full_conn *conn, lsquic_stream_id_t stream_id,
enum stream_ctor_flags flags)
{
const struct lsquic_stream_if *iface;
struct buffered_priority_update *bpu;
struct lsquic_hash_elem *el;
void *stream_ctx;
struct lsquic_stream *stream;
unsigned initial_window;
const int call_on_new = flags & SCF_CALL_ON_NEW;
flags &= ~SCF_CALL_ON_NEW;
flags |= SCF_DI_AUTOSWITCH|SCF_IETF;
if ((conn->ifc_flags & IFC_HTTP) && ((stream_id >> SD_SHIFT) & 1) == SD_UNI)
{
iface = unicla_if_ptr;
stream_ctx = conn;
#if CLIENT_PUSH_SUPPORT
/* FIXME: This logic does not work for push streams. Perhaps one way
* to address this is to reclassify them later?
*/
#endif
flags |= SCF_CRITICAL;
}
else
{
iface = conn->ifc_enpub->enp_stream_if;
stream_ctx = conn->ifc_enpub->enp_stream_if_ctx;
if (conn->ifc_enpub->enp_settings.es_rw_once)
flags |= SCF_DISP_RW_ONCE;
if (conn->ifc_enpub->enp_settings.es_delay_onclose)
flags |= SCF_DELAY_ONCLOSE;
if (conn->ifc_flags & IFC_HTTP)
{
flags |= SCF_HTTP;
if (conn->ifc_pii == &ext_prio_iter_if)
flags |= SCF_HTTP_PRIO;
}
}
if (((stream_id >> SD_SHIFT) & 1) == SD_UNI)
initial_window = conn->ifc_enpub->enp_settings
.es_init_max_stream_data_uni;
else
initial_window = conn->ifc_enpub->enp_settings
.es_init_max_stream_data_bidi_remote;
stream = lsquic_stream_new(stream_id, &conn->ifc_pub,
iface, stream_ctx, initial_window,
conn->ifc_cfg.max_stream_send, flags);
if (stream)
{
if (conn->ifc_bpus)
{
el = lsquic_hash_find(conn->ifc_bpus, &stream->id,
sizeof(stream->id));
if (el)
{
LSQ_DEBUG("apply buffered PRIORITY_UPDATE to stream %"PRIu64,
stream->id);
lsquic_hash_erase(conn->ifc_bpus, el);
bpu = lsquic_hashelem_getdata(el);
(void) lsquic_stream_set_http_prio(stream, &bpu->ehp);
free(bpu);
}
}
if (lsquic_hash_insert(conn->ifc_pub.all_streams, &stream->id,
sizeof(stream->id), stream, &stream->sm_hash_el))
{
if (call_on_new)
lsquic_stream_call_on_new(stream);
}
else
{
lsquic_stream_destroy(stream);
stream = NULL;
}
}
return stream;
}
static int
conn_is_send_only_stream (const struct ietf_full_conn *conn,
lsquic_stream_id_t stream_id)
{
enum stream_id_type sit;
sit = stream_id & SIT_MASK;
if (conn->ifc_flags & IFC_SERVER)
return sit == SIT_UNI_SERVER;
else
return sit == SIT_UNI_CLIENT;
}
static int
conn_is_receive_only_stream (const struct ietf_full_conn *conn,
lsquic_stream_id_t stream_id)
{
enum stream_id_type sit;
sit = stream_id & SIT_MASK;
if (conn->ifc_flags & IFC_SERVER)
return sit == SIT_UNI_CLIENT;
else
return sit == SIT_UNI_SERVER;
}
static unsigned
process_rst_stream_frame (struct ietf_full_conn *conn,
struct lsquic_packet_in *packet_in, const unsigned char *p, size_t len)
{
lsquic_stream_id_t stream_id;
uint64_t offset, error_code;
lsquic_stream_t *stream;
int call_on_new;
const int parsed_len = conn->ifc_conn.cn_pf->pf_parse_rst_frame(p, len,
&stream_id, &offset, &error_code);
if (parsed_len < 0)
return 0;
EV_LOG_RST_STREAM_FRAME_IN(LSQUIC_LOG_CONN_ID, stream_id, offset,
error_code);
LSQ_DEBUG("Got RST_STREAM; stream: %"PRIu64"; offset: 0x%"PRIX64, stream_id,
offset);
if (conn_is_send_only_stream(conn, stream_id))
{
ABORT_QUIETLY(0, TEC_STREAM_STATE_ERROR,
"received RESET_STREAM on send-only stream %"PRIu64, stream_id);
return 0;
}
call_on_new = 0;
stream = find_stream_by_id(conn, stream_id);
if (!stream)
{
if (conn_is_stream_closed(conn, stream_id))
{
LSQ_DEBUG("got reset frame for closed stream %"PRIu64, stream_id);
return parsed_len;
}
if (!is_peer_initiated(conn, stream_id))
{
ABORT_ERROR("received reset for never-initiated stream %"PRIu64,
stream_id);
return 0;
}
stream = new_stream(conn, stream_id, 0);
if (!stream)
{
ABORT_ERROR("cannot create new stream: %s", strerror(errno));
return 0;
}
++call_on_new;
}
if (0 != lsquic_stream_rst_in(stream, offset, error_code))
{
ABORT_ERROR("received invalid RST_STREAM");
return 0;
}
if (call_on_new)
lsquic_stream_call_on_new(stream);
return parsed_len;
}
static unsigned
process_stop_sending_frame (struct ietf_full_conn *conn,
struct lsquic_packet_in *packet_in, const unsigned char *p, size_t len)
{
struct lsquic_stream *stream;
lsquic_stream_id_t stream_id, max_allowed;
uint64_t error_code;
int parsed_len, our_stream;
enum stream_state_sending sss;
parsed_len = conn->ifc_conn.cn_pf->pf_parse_stop_sending_frame(p, len,
&stream_id, &error_code);
if (parsed_len < 0)
return 0;
EV_LOG_STOP_SENDING_FRAME_IN(LSQUIC_LOG_CONN_ID, stream_id, error_code);
LSQ_DEBUG("Got STOP_SENDING; stream: %"PRIu64"; error code: %"PRIu64,
stream_id, error_code);
if (conn_is_receive_only_stream(conn, stream_id))
{
ABORT_QUIETLY(0, TEC_STREAM_STATE_ERROR,
"received STOP_SENDING on receive-only stream %"PRIu64, stream_id);
return 0;
}
our_stream = !is_peer_initiated(conn, stream_id);
stream = find_stream_by_id(conn, stream_id);
if (stream)
{
if (our_stream &&
SSS_READY == (sss = lsquic_stream_sending_state(stream)))
{
ABORT_QUIETLY(0, TEC_PROTOCOL_VIOLATION, "stream %"PRIu64" is in "
"%s state: receipt of STOP_SENDING frame is a violation",
stream_id, lsquic_sss2str[sss]);
return 0;
}
lsquic_stream_stop_sending_in(stream, error_code);
}
else if (conn_is_stream_closed(conn, stream_id))
LSQ_DEBUG("stream %"PRIu64" is closed: ignore STOP_SENDING frame",
stream_id);
else if (our_stream)
{
ABORT_QUIETLY(0, TEC_STREAM_STATE_ERROR, "received STOP_SENDING frame "
"on locally initiated stream that has not yet been opened");
return 0;
}
else
{
max_allowed = conn->ifc_max_allowed_stream_id[stream_id & SIT_MASK];
if (stream_id >= max_allowed)
{
ABORT_QUIETLY(0, TEC_STREAM_LIMIT_ERROR, "incoming STOP_SENDING "
"for stream %"PRIu64" would exceed allowed max of %"PRIu64,
stream_id, max_allowed);
return 0;
}
if (conn->ifc_flags & IFC_GOING_AWAY)
{
LSQ_DEBUG("going away: reject new incoming stream %"PRIu64,
stream_id);
maybe_schedule_ss_for_stream(conn, stream_id, HEC_REQUEST_REJECTED);
return parsed_len;
}
stream = new_stream(conn, stream_id, 0);
if (!stream)
{
ABORT_ERROR("cannot create new stream: %s", strerror(errno));
return 0;
}
lsquic_stream_stop_sending_in(stream, error_code);
lsquic_stream_call_on_new(stream);
}
return parsed_len;
}
static unsigned
discard_crypto_frame (struct ietf_full_conn *conn,
struct lsquic_packet_in *packet_in, const unsigned char *p, size_t len)
{
struct stream_frame stream_frame;
int parsed_len;
parsed_len = conn->ifc_conn.cn_pf->pf_parse_crypto_frame(p, len,
&stream_frame);
if (parsed_len > 0)
{
LSQ_DEBUG("discard %d-byte CRYPTO frame", parsed_len);
return (unsigned) parsed_len;
}
else
return 0;
}
/* In the server, we only wait for Finished frame */
static unsigned
process_crypto_frame_server (struct ietf_full_conn *conn,
struct lsquic_packet_in *packet_in, const unsigned char *p, size_t len)
{
struct stream_frame stream_frame;
enum enc_level enc_level;
int parsed_len;
parsed_len = conn->ifc_conn.cn_pf->pf_parse_crypto_frame(p, len,
&stream_frame);
if (parsed_len < 0)
return 0;
enc_level = lsquic_packet_in_enc_level(packet_in);
EV_LOG_CRYPTO_FRAME_IN(LSQUIC_LOG_CONN_ID, &stream_frame, enc_level);
LSQ_DEBUG("Got CRYPTO frame for enc level #%u", enc_level);
if (!(conn->ifc_flags & IFC_PROC_CRYPTO))
{
LSQ_DEBUG("discard %d-byte CRYPTO frame: handshake has been confirmed",
parsed_len);
return (unsigned) parsed_len;
}
if (enc_level < ENC_LEV_INIT)
{ /* Must be dup */
LSQ_DEBUG("discard %d-byte CRYPTO frame on level %s", parsed_len,
lsquic_enclev2str[enc_level]);
return (unsigned) parsed_len;
}
if (0 != conn->ifc_conn.cn_esf.i->esfi_data_in(
conn->ifc_conn.cn_enc_session,
lsquic_packet_in_enc_level(packet_in),
stream_frame.data_frame.df_data,
stream_frame.data_frame.df_size))
{
LSQ_DEBUG("feeding CRYPTO frame to enc session failed");
return 0;
}
if (!conn->ifc_conn.cn_esf.i->esfi_in_init(conn->ifc_conn.cn_enc_session))
{
LSQ_DEBUG("handshake confirmed: send HANDSHAKE_DONE");
conn->ifc_flags &= ~IFC_PROC_CRYPTO;
conn->ifc_send_flags |= SF_SEND_HANDSHAKE_DONE;
}
return (unsigned) parsed_len;
}
static unsigned
process_crypto_frame_client (struct ietf_full_conn *conn,
struct lsquic_packet_in *packet_in, const unsigned char *p, size_t len)
{
struct stream_frame *stream_frame;
struct lsquic_stream *stream;
enum enc_level enc_level;
int parsed_len;
/* Ignore CRYPTO frames in server mode and in client mode after SSL object
* is gone.
*/
if (!(conn->ifc_flags & IFC_PROC_CRYPTO))
return discard_crypto_frame(conn, packet_in, p, len);
stream_frame = lsquic_malo_get(conn->ifc_pub.mm->malo.stream_frame);
if (!stream_frame)
{
LSQ_WARN("could not allocate stream frame: %s", strerror(errno));
return 0;
}
parsed_len = conn->ifc_conn.cn_pf->pf_parse_crypto_frame(p, len,
stream_frame);
if (parsed_len < 0) {
lsquic_malo_put(stream_frame);
ABORT_QUIETLY(0, TEC_FRAME_ENCODING_ERROR,
"cannot decode CRYPTO frame");
return 0;
}
enc_level = lsquic_packet_in_enc_level(packet_in);
EV_LOG_CRYPTO_FRAME_IN(LSQUIC_LOG_CONN_ID, stream_frame, enc_level);
LSQ_DEBUG("Got CRYPTO frame for enc level #%u", enc_level);
if ((conn->ifc_conn.cn_flags & LSCONN_HANDSHAKE_DONE)
&& enc_level != ENC_LEV_FORW)
{
LSQ_DEBUG("handshake complete: ignore CRYPTO frames in "
"non-forward-secure packets");
return parsed_len;
}
if (conn->ifc_flags & IFC_CLOSING)
{
LSQ_DEBUG("Connection closing: ignore frame");
lsquic_malo_put(stream_frame);
return parsed_len;
}
assert(!(conn->ifc_flags & IFC_SERVER));
if (conn->ifc_u.cli.crypto_streams[enc_level])
stream = conn->ifc_u.cli.crypto_streams[enc_level];
else
{
stream = lsquic_stream_new_crypto(enc_level, &conn->ifc_pub,
&lsquic_cry_sm_if, conn->ifc_conn.cn_enc_session,
SCF_IETF|SCF_DI_AUTOSWITCH|SCF_CALL_ON_NEW|SCF_CRITICAL);
if (!stream)
{
lsquic_malo_put(stream_frame);
ABORT_WARN("cannot create crypto stream for level %u", enc_level);
return 0;
}
conn->ifc_u.cli.crypto_streams[enc_level] = stream;
(void) lsquic_stream_wantread(stream, 1);
}
stream_frame->packet_in = lsquic_packet_in_get(packet_in);
if (0 != lsquic_stream_frame_in(stream, stream_frame))
{
ABORT_ERROR("cannot insert stream frame");
return 0;
}
if (!(conn->ifc_conn.cn_flags & LSCONN_HANDSHAKE_DONE))
{ /* To enable decryption, process handshake stream as soon as its
* data frames are received.
*
* TODO: this does not work when packets are reordered. A more
* flexible solution would defer packet decryption if handshake
* has not been completed yet. Nevertheless, this is good enough
* for now.
*/
lsquic_stream_dispatch_read_events(stream);
}
return parsed_len;
}
static unsigned
process_crypto_frame (struct ietf_full_conn *conn,
struct lsquic_packet_in *packet_in, const unsigned char *p, size_t len)
{
if (conn->ifc_flags & IFC_SERVER)
return process_crypto_frame_server(conn, packet_in, p, len);
else
return process_crypto_frame_client(conn, packet_in, p, len);
}
static unsigned
process_stream_frame (struct ietf_full_conn *conn,
struct lsquic_packet_in *packet_in, const unsigned char *p, size_t len)
{
struct stream_frame *stream_frame;
struct lsquic_stream *stream;
int parsed_len;
stream_frame = lsquic_malo_get(conn->ifc_pub.mm->malo.stream_frame);
if (!stream_frame)
{
LSQ_WARN("could not allocate stream frame: %s", strerror(errno));
return 0;
}
parsed_len = conn->ifc_conn.cn_pf->pf_parse_stream_frame(p, len,
stream_frame);
if (parsed_len < 0) {
lsquic_malo_put(stream_frame);
ABORT_QUIETLY(0, TEC_FRAME_ENCODING_ERROR,
"cannot decode STREAM frame");
return 0;
}
EV_LOG_STREAM_FRAME_IN(LSQUIC_LOG_CONN_ID, stream_frame);
LSQ_DEBUG("Got stream frame for stream #%"PRIu64, stream_frame->stream_id);
CONN_STATS(in.stream_frames, 1);
CONN_STATS(in.stream_data_sz, stream_frame->data_frame.df_size);
if (conn_is_send_only_stream(conn, stream_frame->stream_id))
{
ABORT_QUIETLY(0, TEC_STREAM_STATE_ERROR, "received STREAM frame "
"on send-only stream %"PRIu64, stream_frame->stream_id);
return 0;
}
if ((conn->ifc_flags & (IFC_SERVER|IFC_HTTP)) == IFC_HTTP
&& SIT_BIDI_SERVER == (stream_frame->stream_id & SIT_MASK))
{
ABORT_QUIETLY(1, HEC_STREAM_CREATION_ERROR, "HTTP/3 server "
"is not allowed to initiate bidirectional streams (got "
"STREAM frame for stream %"PRIu64, stream_frame->stream_id);
return 0;
}
if (conn->ifc_flags & IFC_CLOSING)
{
LSQ_DEBUG("Connection closing: ignore frame");
lsquic_malo_put(stream_frame);
return parsed_len;
}
stream = find_stream_by_id(conn, stream_frame->stream_id);
if (!stream)
{
if (conn_is_stream_closed(conn, stream_frame->stream_id))
{
LSQ_DEBUG("drop frame for closed stream %"PRIu64,
stream_frame->stream_id);
lsquic_malo_put(stream_frame);
return parsed_len;
}
if (is_peer_initiated(conn, stream_frame->stream_id))
{
const lsquic_stream_id_t max_allowed =
conn->ifc_max_allowed_stream_id[stream_frame->stream_id & SIT_MASK];
if (stream_frame->stream_id >= max_allowed)
{
ABORT_QUIETLY(0, TEC_STREAM_LIMIT_ERROR, "incoming stream "
"%"PRIu64" exceeds allowed max of %"PRIu64,
stream_frame->stream_id, max_allowed);
lsquic_malo_put(stream_frame);
return 0;
}
if (conn->ifc_flags & IFC_GOING_AWAY)
{
LSQ_DEBUG("going away: reject new incoming stream %"PRIu64,
stream_frame->stream_id);
maybe_schedule_ss_for_stream(conn, stream_frame->stream_id,
HEC_REQUEST_REJECTED);
lsquic_malo_put(stream_frame);
return parsed_len;
}
}
else
{
ABORT_QUIETLY(0, TEC_STREAM_STATE_ERROR, "received STREAM frame "
"for never-initiated stream");
lsquic_malo_put(stream_frame);
return 0;
}
stream = new_stream(conn, stream_frame->stream_id, SCF_CALL_ON_NEW);
if (!stream)
{
ABORT_ERROR("cannot create new stream: %s", strerror(errno));
lsquic_malo_put(stream_frame);
return 0;
}
if (SD_BIDI == ((stream_frame->stream_id >> SD_SHIFT) & 1)
&& (!valid_stream_id(conn->ifc_max_req_id)
|| conn->ifc_max_req_id < stream_frame->stream_id))
conn->ifc_max_req_id = stream_frame->stream_id;
}
stream_frame->packet_in = lsquic_packet_in_get(packet_in);
if (0 != lsquic_stream_frame_in(stream, stream_frame))
{
ABORT_ERROR("cannot insert stream frame");
return 0;
}
/* Don't wait for the regular on_read dispatch in order to save an
* unnecessary blocked/unblocked sequence.
*/
if ((conn->ifc_flags & IFC_HTTP) && conn->ifc_qdh.qdh_enc_sm_in == stream)
lsquic_stream_dispatch_read_events(conn->ifc_qdh.qdh_enc_sm_in);
return parsed_len;
}
static unsigned
process_ack_frame (struct ietf_full_conn *conn,
struct lsquic_packet_in *packet_in, const unsigned char *p, size_t len)
{
struct ack_info *new_acki;
enum packnum_space pns;
int parsed_len;
lsquic_time_t warn_time;
CONN_STATS(in.n_acks, 1);
if (conn->ifc_flags & IFC_HAVE_SAVED_ACK)
new_acki = conn->ifc_pub.mm->acki;
else
new_acki = &conn->ifc_ack;
parsed_len = conn->ifc_conn.cn_pf->pf_parse_ack_frame(p, len, new_acki,
conn->ifc_cfg.ack_exp);
if (parsed_len < 0)
goto err;
/* This code to throw out old ACKs is what keeps us compliant with this
* requirement:
*
* [draft-ietf-quic-transport-18] Section 13.3.2.
*
> Processing counts out of order can result in verification failure.
> An endpoint SHOULD NOT perform this verification if the ACK frame is
> received in a packet with packet number lower than a previously
> received ACK frame. Verifying based on ACK frames that arrive out of
> order can result in disabling ECN unnecessarily.
*/
pns = lsquic_hety2pns[ packet_in->pi_header_type ];
if (is_valid_packno(conn->ifc_max_ack_packno[pns]) &&
packet_in->pi_packno <= conn->ifc_max_ack_packno[pns])
{
LSQ_DEBUG("Ignore old ack (max %"PRIu64")",
conn->ifc_max_ack_packno[pns]);
return parsed_len;
}
EV_LOG_ACK_FRAME_IN(LSQUIC_LOG_CONN_ID, new_acki);
conn->ifc_max_ack_packno[pns] = packet_in->pi_packno;
new_acki->pns = pns;
++conn->ifc_pts.n_acks;
/* Only cache ACKs for PNS_APP */
if (pns == PNS_APP && new_acki == &conn->ifc_ack)
{
LSQ_DEBUG("Saved ACK");
conn->ifc_flags |= IFC_HAVE_SAVED_ACK;
conn->ifc_saved_ack_received = packet_in->pi_received;
}
else if (pns == PNS_APP)
{
if (0 == lsquic_merge_acks(&conn->ifc_ack, new_acki))
{
CONN_STATS(in.n_acks_merged, 1);
LSQ_DEBUG("merged into saved ACK, getting %s",
(lsquic_acki2str(&conn->ifc_ack, conn->ifc_pub.mm->ack_str,
MAX_ACKI_STR_SZ), conn->ifc_pub.mm->ack_str));
}
else
{
LSQ_DEBUG("could not merge new ACK into saved ACK");
if (0 != process_ack(conn, &conn->ifc_ack, packet_in->pi_received,
packet_in->pi_received))
goto err;
conn->ifc_ack = *new_acki;
}
conn->ifc_saved_ack_received = packet_in->pi_received;
}
else
{
if (0 != process_ack(conn, new_acki, packet_in->pi_received,
packet_in->pi_received))
goto err;
}
return parsed_len;
err:
warn_time = lsquic_time_now();
if (0 == conn->ifc_enpub->enp_last_warning[WT_ACKPARSE_FULL]
|| conn->ifc_enpub->enp_last_warning[WT_ACKPARSE_FULL]
+ WARNING_INTERVAL < warn_time)
{
conn->ifc_enpub->enp_last_warning[WT_ACKPARSE_FULL] = warn_time;
LSQ_WARN("Invalid ACK frame");
}
return 0;
}
static unsigned
process_ping_frame (struct ietf_full_conn *conn,
struct lsquic_packet_in *packet_in, const unsigned char *p, size_t len)
{ /* This frame causes ACK frame to be queued, but nothing to do here;
* return the length of this frame.
*/
EV_LOG_PING_FRAME_IN(LSQUIC_LOG_CONN_ID);
LSQ_DEBUG("received PING");
if (conn->ifc_flags & IFC_SERVER)
log_conn_flow_control(conn);
return 1;
}
static int
is_benign_transport_error_code (uint64_t error_code)
{
switch (error_code)
{
case TEC_NO_ERROR:
case TEC_INTERNAL_ERROR:
return 1;
default:
return 0;
}
}
static int
is_benign_application_error_code (uint64_t error_code)
{
switch (error_code)
{
case HEC_NO_ERROR:
case HEC_INTERNAL_ERROR:
return 1;
default:
return 0;
}
}
static unsigned
process_connection_close_frame (struct ietf_full_conn *conn,
struct lsquic_packet_in *packet_in, const unsigned char *p, size_t len)
{
lsquic_stream_t *stream;
struct lsquic_hash_elem *el;
uint64_t error_code;
uint16_t reason_len;
uint8_t reason_off;
int parsed_len, app_error;
const char *ua;
parsed_len = conn->ifc_conn.cn_pf->pf_parse_connect_close_frame(p, len,
&app_error, &error_code, &reason_len, &reason_off);
if (parsed_len < 0)
return 0;
EV_LOG_CONNECTION_CLOSE_FRAME_IN(LSQUIC_LOG_CONN_ID, error_code,
(int) reason_len, (const char *) p + reason_off);
if (LSQ_LOG_ENABLED(LSQ_LOG_NOTICE)
&& !( (!app_error && is_benign_transport_error_code(error_code))
||( app_error && is_benign_application_error_code(error_code))))
{
if (conn->ifc_flags & IFC_HTTP)
{
ua = lsquic_qdh_get_ua(&conn->ifc_qdh);
if (!ua)
ua = "unknown peer";
}
else
ua = "non-HTTP/3 peer";
LSQ_NOTICE("Received CONNECTION_CLOSE from <%s> with %s-level error "
"code %"PRIu64", reason: `%.*s'", ua,
app_error ? "application" : "transport", error_code,
(int) reason_len, (const char *) p + reason_off);
}
else
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);
conn->ifc_flags |= IFC_RECV_CLOSE;
if (!(conn->ifc_flags & IFC_CLOSING))
{
for (el = lsquic_hash_first(conn->ifc_pub.all_streams); el;
el = lsquic_hash_next(conn->ifc_pub.all_streams))
{
stream = lsquic_hashelem_getdata(el);
lsquic_stream_shutdown_internal(stream);
}
conn->ifc_flags |= IFC_CLOSING;
}
return parsed_len;
}
static unsigned
process_max_data_frame (struct ietf_full_conn *conn,
struct lsquic_packet_in *packet_in, const unsigned char *p, size_t len)
{
uint64_t max_data;
int parsed_len;
parsed_len = conn->ifc_conn.cn_pf->pf_parse_max_data(p, len, &max_data);
if (parsed_len < 0)
return 0;
EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "MAX_DATA frame in; offset: %"PRIu64,
max_data);
if (max_data > conn->ifc_pub.conn_cap.cc_max)
{
LSQ_DEBUG("max data goes from %"PRIu64" to %"PRIu64,
conn->ifc_pub.conn_cap.cc_max, max_data);
conn->ifc_pub.conn_cap.cc_max = max_data;
}
else
LSQ_DEBUG("newly supplied max data=%"PRIu64" is not larger than the "
"current value=%"PRIu64", ignoring", max_data,
conn->ifc_pub.conn_cap.cc_max);
return parsed_len;
}
static unsigned
process_max_stream_data_frame (struct ietf_full_conn *conn,
struct lsquic_packet_in *packet_in, const unsigned char *p, size_t len)
{
struct lsquic_stream *stream;
lsquic_stream_id_t stream_id;
uint64_t max_data;
int parsed_len;
parsed_len = conn->ifc_conn.cn_pf->pf_parse_max_stream_data_frame(p, len,
&stream_id, &max_data);
if (parsed_len < 0)
return 0;
EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "MAX_STREAM_DATA frame in; "
"stream_id: %"PRIu64"; offset: %"PRIu64, stream_id, max_data);
if (conn_is_receive_only_stream(conn, stream_id))
{
ABORT_QUIETLY(0, TEC_STREAM_STATE_ERROR,
"received MAX_STREAM_DATA on receive-only stream %"PRIu64, stream_id);
return 0;
}
stream = find_stream_by_id(conn, stream_id);
if (stream)
lsquic_stream_window_update(stream, max_data);
else if (conn_is_stream_closed(conn, stream_id))
LSQ_DEBUG("stream %"PRIu64" is closed: ignore MAX_STREAM_DATA frame",
stream_id);
else
{
ABORT_QUIETLY(0, TEC_STREAM_STATE_ERROR, "received MAX_STREAM_DATA "
"frame on never-opened stream %"PRIu64, stream_id);
return 0;
}
return parsed_len;
}
static unsigned
process_max_streams_frame (struct ietf_full_conn *conn,
struct lsquic_packet_in *packet_in, const unsigned char *p, size_t len)
{
lsquic_stream_id_t max_stream_id;
enum stream_id_type sit;
enum stream_dir sd;
uint64_t max_streams;
int parsed_len;
parsed_len = conn->ifc_conn.cn_pf->pf_parse_max_streams_frame(p, len,
&sd, &max_streams);
if (parsed_len < 0)
return 0;
sit = gen_sit(conn->ifc_flags & IFC_SERVER, sd);
max_stream_id = max_streams << SIT_SHIFT;
if (max_stream_id > VINT_MAX_VALUE)
{
ABORT_QUIETLY(0, TEC_FRAME_ENCODING_ERROR,
"MAX_STREAMS: max %s stream ID of %"PRIu64" exceeds maximum "
"stream ID", sd == SD_BIDI ? "bidi" : "uni", max_stream_id);
return 0;
}
if (max_stream_id > conn->ifc_max_allowed_stream_id[sit])
{
LSQ_DEBUG("max %s stream ID updated from %"PRIu64" to %"PRIu64,
sd == SD_BIDI ? "bidi" : "uni",
conn->ifc_max_allowed_stream_id[sit], max_stream_id);
conn->ifc_max_allowed_stream_id[sit] = max_stream_id;
}
else
LSQ_DEBUG("ignore old max %s streams value of %"PRIu64,
sd == SD_BIDI ? "bidi" : "uni", max_streams);
return parsed_len;
}
/* Returns true if current DCID was retired. In this case, it must be
* replaced.
*/
static int
retire_dcids_prior_to (struct ietf_full_conn *conn, unsigned retire_prior_to)
{
struct dcid_elem **el;
int update_cur_dcid = 0;
#if LSQUIC_LOWEST_LOG_LEVEL >= LSQ_LOG_DEBUG
unsigned count = 0;
#endif
for (el = conn->ifc_dces; el < conn->ifc_dces + sizeof(conn->ifc_dces)
/ sizeof(conn->ifc_dces[0]); ++el)
if (*el && (*el)->de_seqno < retire_prior_to)
{
update_cur_dcid |= LSQUIC_CIDS_EQ(&(*el)->de_cid, CUR_DCID(conn));
retire_dcid(conn, el);
#if LSQUIC_LOWEST_LOG_LEVEL >= LSQ_LOG_DEBUG
++count;
#endif
}
LSQ_DEBUG("retired %u DCID%s due to Retire Prior To=%u", count,
count != 1 ? "s" : "", retire_prior_to);
return update_cur_dcid;
}
static int
insert_new_dcid (struct ietf_full_conn *conn, uint64_t seqno,
const lsquic_cid_t *cid, const unsigned char *token, int update_cur_dcid)
{
struct dcid_elem **dce, **el;
char tokstr[IQUIC_SRESET_TOKEN_SZ * 2 + 1];
dce = NULL;
for (el = conn->ifc_dces; el < conn->ifc_dces + sizeof(conn->ifc_dces)
/ sizeof(conn->ifc_dces[0]); ++el)
if (*el)
{
if ((*el)->de_seqno == seqno)
{
if (!LSQUIC_CIDS_EQ(&(*el)->de_cid, cid))
{
ABORT_QUIETLY(0, TEC_PROTOCOL_VIOLATION,
"NEW_CONNECTION_ID: already have CID seqno %"PRIu64
" but with a different CID", seqno);
return -1;
}
else
{
LSQ_DEBUG("Ignore duplicate CID seqno %"PRIu64, seqno);
return 0;
}
}
else if (LSQUIC_CIDS_EQ(&(*el)->de_cid, cid))
{
ABORT_QUIETLY(0, TEC_PROTOCOL_VIOLATION,
"NEW_CONNECTION_ID: received the same CID with sequence "
"numbers %u and %"PRIu64, (*el)->de_seqno, seqno);
return -1;
}
else if (((*el)->de_flags & DE_SRST)
&& 0 == memcmp((*el)->de_srst, token,
IQUIC_SRESET_TOKEN_SZ))
{
ABORT_QUIETLY(0, TEC_PROTOCOL_VIOLATION,
"NEW_CONNECTION_ID: received second instance of reset "
"token %s in seqno %"PRIu64", same as in seqno %u",
(lsquic_hexstr(token, IQUIC_SRESET_TOKEN_SZ, tokstr,
sizeof(tokstr)), tokstr),
seqno, (*el)->de_seqno);
return -1;
}
}
else if (!dce)
dce = el;
if (!dce)
{
ABORT_QUIETLY(0, TEC_CONNECTION_ID_LIMIT_ERROR,
"NEW_CONNECTION_ID: received connection ID that is going over the "
"limit of %u CIDs", MAX_IETF_CONN_DCIDS);
return -1;
}
*dce = lsquic_malo_get(conn->ifc_pub.mm->malo.dcid_elem);
if (*dce)
{
memset(*dce, 0, sizeof(**dce));
(*dce)->de_seqno = seqno;
(*dce)->de_cid = *cid;
memcpy((*dce)->de_srst, token, sizeof((*dce)->de_srst));
(*dce)->de_flags |= DE_SRST;
if (update_cur_dcid)
{
*CUR_DCID(conn) = *cid;
if (CUR_CPATH(conn)->cop_flags & COP_SPIN_BIT)
CUR_CPATH(conn)->cop_spin_bit = 0;
}
}
else
LSQ_WARN("cannot allocate dce to insert DCID seqno %"PRIu64, seqno);
return 0;
}
static unsigned
process_new_connection_id_frame (struct ietf_full_conn *conn,
struct lsquic_packet_in *packet_in, const unsigned char *p, size_t len)
{
const unsigned char *token;
const char *action_str;
lsquic_cid_t cid;
uint64_t seqno, retire_prior_to;
int parsed_len, update_cur_dcid;
parsed_len = conn->ifc_conn.cn_pf->pf_parse_new_conn_id(p, len,
&seqno, &retire_prior_to, &cid, &token);
if (parsed_len < 0)
{
if (parsed_len == -2)
ABORT_QUIETLY(0, TEC_FRAME_ENCODING_ERROR,
"NEW_CONNECTION_ID contains invalid CID length");
return 0;
}
if (seqno > UINT32_MAX || retire_prior_to > UINT32_MAX)
{ /* It is wasteful to use 8-byte integers for these counters, so this
* is the guard here. This will "Never Happen."
*/
LSQ_INFO("ignoring unreasonably high seqno=%"PRIu64" or Retire Prior "
"To=%"PRIu64, seqno, retire_prior_to);
return parsed_len;
}
if (retire_prior_to > seqno)
{
ABORT_QUIETLY(0, TEC_FRAME_ENCODING_ERROR,
"NEW_CONNECTION_ID: Retire Prior To=%"PRIu64" is larger then the "
"Sequence Number=%"PRIu64, retire_prior_to, seqno);
return 0;
}
if (CUR_DCID(conn)->len == 0)
{
ABORT_QUIETLY(0, TEC_PROTOCOL_VIOLATION, "Received NEW_CONNECTION_ID "
"frame, but current DCID is zero-length");
return 0;
}
if (seqno < conn->ifc_last_retire_prior_to)
{
retire_seqno(conn, seqno);
action_str = "Ignored (seqno smaller than last retire_prior_to";
goto end;
}
if (retire_prior_to > conn->ifc_last_retire_prior_to)
{
conn->ifc_last_retire_prior_to = retire_prior_to;
update_cur_dcid = retire_dcids_prior_to(conn, retire_prior_to);
}
else
update_cur_dcid = 0;
if (0 != insert_new_dcid(conn, seqno, &cid, token, update_cur_dcid))
return 0;
action_str = "Saved";
end:
LSQ_DEBUGC("Got new connection ID from peer: seq=%"PRIu64"; "
"cid: %"CID_FMT". %s.", seqno, CID_BITS(&cid), action_str);
return parsed_len;
}
static void
retire_cid (struct ietf_full_conn *conn, struct conn_cid_elem *cce,
lsquic_time_t now)
{
struct lsquic_conn *const lconn = &conn->ifc_conn;
lsquic_time_t drain_time;
drain_time = calc_drain_time(conn);
LSQ_DEBUGC("retiring CID %"CID_FMT"; seqno: %u; %s; drain time %"PRIu64
" usec", CID_BITS(&cce->cce_cid), cce->cce_seqno,
(cce->cce_flags & CCE_SEQNO) ? "" : "original", drain_time);
if (cce->cce_flags & CCE_SEQNO)
--conn->ifc_active_cids_count;
lsquic_engine_retire_cid(conn->ifc_enpub, lconn, cce - lconn->cn_cces, now,
drain_time);
memset(cce, 0, sizeof(*cce));
if (can_issue_cids(conn)
&& !(lsquic_alarmset_is_set(&conn->ifc_alset, AL_CID_THROT)))
maybe_get_rate_available_scid_slot(conn, now);
}
static unsigned
process_retire_connection_id_frame (struct ietf_full_conn *conn,
struct lsquic_packet_in *packet_in, const unsigned char *p, size_t len)
{
struct lsquic_conn *const lconn = &conn->ifc_conn;
struct conn_cid_elem *cce;
uint64_t seqno;
int parsed_len;
/* [draft-ietf-quic-transport-25] Section 19.16
*
* - Peer cannot retire zero-lenth CID. (MUST treat as PROTOCOL_VIOLATION)
* - Peer cannot retire CID with sequence number that has not been
* allocated yet. (MUST treat as PROTOCOL_VIOLATION)
* - Peer cannot retire CID that matches the DCID in packet.
* (MAY treat as PROTOCOL_VIOLATION)
*/
if (conn->ifc_settings->es_scid_len == 0)
{
ABORT_QUIETLY(0, TEC_PROTOCOL_VIOLATION, "cannot retire zero-length CID");
return 0;
}
parsed_len = conn->ifc_conn.cn_pf->pf_parse_retire_cid_frame(p, len,
&seqno);
if (parsed_len < 0)
return 0;
EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "got RETIRE_CONNECTION_ID frame: "
"seqno=%"PRIu64, seqno);
if (seqno >= conn->ifc_scid_seqno)
{
ABORT_QUIETLY(0, TEC_PROTOCOL_VIOLATION, "cannot retire CID seqno="
"%"PRIu64" as it has not been allocated yet", seqno);
return 0;
}
for (cce = lconn->cn_cces; cce < END_OF_CCES(lconn); ++cce)
if ((lconn->cn_cces_mask & (1 << (cce - lconn->cn_cces))
&& (cce->cce_flags & CCE_SEQNO)
&& cce->cce_seqno == seqno))
break;
conn->ifc_active_cids_count -= seqno >= conn->ifc_first_active_cid_seqno;
if (cce < END_OF_CCES(lconn))
{
if (LSQUIC_CIDS_EQ(&cce->cce_cid, &packet_in->pi_dcid))
{
ABORT_QUIETLY(0, TEC_PROTOCOL_VIOLATION, "cannot retire CID "
"seqno=%"PRIu64", for it is used as DCID in the packet", seqno);
return 0;
}
retire_cid(conn, cce, packet_in->pi_received);
if (lconn->cn_cur_cce_idx == cce - lconn->cn_cces)
{
cce = find_cce_by_cid(conn, &packet_in->pi_dcid);
if (cce)
{
cce->cce_flags |= CCE_USED;
lconn->cn_cur_cce_idx = cce - lconn->cn_cces;
LSQ_DEBUGC("current SCID was retired; set current SCID to "
"%"CID_FMT" based on DCID in incoming packet",
CID_BITS(&packet_in->pi_dcid));
}
else
LSQ_WARN("current SCID was retired; no new SCID candidate");
/* This could theoretically happen when zero-length CIDs were
* used. Currently, there should be no way lsquic could get
* into this situation.
*/
}
}
else
LSQ_DEBUG("cannot retire CID seqno=%"PRIu64": not found", seqno);
LOG_SCIDS(conn);
return parsed_len;
}
static unsigned
process_new_token_frame (struct ietf_full_conn *conn,
struct lsquic_packet_in *packet_in, const unsigned char *p, size_t len)
{
const unsigned char *token;
size_t token_sz;
char *token_str;
int parsed_len;
parsed_len = conn->ifc_conn.cn_pf->pf_parse_new_token_frame(p, len, &token,
&token_sz);
if (parsed_len < 0)
return 0;
if (0 == token_sz)
{
ABORT_QUIETLY(0, TEC_FRAME_ENCODING_ERROR, "received an empty "
"NEW_TOKEN frame");
return 0;
}
if (LSQ_LOG_ENABLED(LSQ_LOG_DEBUG)
|| LSQ_LOG_ENABLED_EXT(LSQ_LOG_DEBUG, LSQLM_EVENT))
{
token_str = malloc(token_sz * 2 + 1);
if (token_str)
{
lsquic_hexstr(token, token_sz, token_str, token_sz * 2 + 1);
LSQ_DEBUG("Got %zu-byte NEW_TOKEN %s", token_sz, token_str);
EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "got NEW_TOKEN %s",
token_str);
free(token_str);
}
}
if (conn->ifc_enpub->enp_stream_if->on_new_token)
conn->ifc_enpub->enp_stream_if->on_new_token(
conn->ifc_enpub->enp_stream_if_ctx, token, token_sz);
return parsed_len;
}
static unsigned
process_stream_blocked_frame (struct ietf_full_conn *conn,
struct lsquic_packet_in *packet_in, const unsigned char *p, size_t len)
{
struct lsquic_stream *stream;
lsquic_stream_id_t stream_id;
uint64_t peer_off;
int parsed_len;
parsed_len = conn->ifc_conn.cn_pf->pf_parse_stream_blocked_frame(p,
len, &stream_id, &peer_off);
if (parsed_len < 0)
return 0;
EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "STREAM_BLOCKED frame in: stream "
"%"PRIu64"; offset %"PRIu64, stream_id, peer_off);
LSQ_DEBUG("received STREAM_BLOCKED frame: stream %"PRIu64
"; offset %"PRIu64, stream_id, peer_off);
if (conn_is_send_only_stream(conn, stream_id))
{
ABORT_QUIETLY(0, TEC_STREAM_STATE_ERROR,
"received STREAM_BLOCKED frame on send-only stream %"PRIu64,
stream_id);
return 0;
}
stream = find_stream_by_id(conn, stream_id);
if (stream)
lsquic_stream_peer_blocked(stream, peer_off);
else
LSQ_DEBUG("stream %"PRIu64" not found - ignore STREAM_BLOCKED frame",
stream_id);
return parsed_len;
}
static unsigned
process_streams_blocked_frame (struct ietf_full_conn *conn,
struct lsquic_packet_in *packet_in, const unsigned char *p, size_t len)
{
lsquic_stream_id_t max_stream_id;
uint64_t stream_limit;
enum stream_dir sd;
int parsed_len;
parsed_len = conn->ifc_conn.cn_pf->pf_parse_streams_blocked_frame(p,
len, &sd, &stream_limit);
if (parsed_len < 0)
return 0;
max_stream_id = stream_limit << SIT_SHIFT;
if (max_stream_id > VINT_MAX_VALUE)
{
ABORT_QUIETLY(0, TEC_FRAME_ENCODING_ERROR,
"STREAMS_BLOCKED: max %s stream ID of %"PRIu64" exceeds maximum "
"stream ID", sd == SD_BIDI ? "bidi" : "uni", max_stream_id);
return 0;
}
LSQ_DEBUG("received STREAMS_BLOCKED frame: limited to %"PRIu64
" %sdirectional stream%.*s", stream_limit, sd == SD_UNI ? "uni" : "bi",
stream_limit != 1, "s");
/* We don't do anything with this information -- at least for now */
return parsed_len;
}
static unsigned
process_blocked_frame (struct ietf_full_conn *conn,
struct lsquic_packet_in *packet_in, const unsigned char *p, size_t len)
{
uint64_t peer_off;
int parsed_len;
parsed_len = conn->ifc_conn.cn_pf->pf_parse_blocked_frame(p, len,
&peer_off);
if (parsed_len < 0)
return 0;
EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "BLOCKED frame in: offset %"PRIu64,
peer_off);
LSQ_DEBUG("received BLOCKED frame: offset %"PRIu64, peer_off);
if (peer_off > conn->ifc_last_max_data_off_sent
&& !(conn->ifc_send_flags & SF_SEND_MAX_DATA))
{
conn->ifc_send_flags |= SF_SEND_MAX_DATA;
LSQ_DEBUG("marked to send MAX_DATA frame");
}
else if (conn->ifc_send_flags & SF_SEND_MAX_DATA)
LSQ_DEBUG("MAX_STREAM_DATA frame is already scheduled");
else
LSQ_DEBUG("MAX_DATA(%"PRIu64") has already been either "
"packetized or sent to peer", conn->ifc_last_max_data_off_sent);
return parsed_len;
}
static unsigned
process_handshake_done_frame (struct ietf_full_conn *conn,
struct lsquic_packet_in *packet_in, const unsigned char *p, size_t len)
{
int parsed_len;
parsed_len = conn->ifc_conn.cn_pf->pf_parse_handshake_done_frame(p, len);
if (parsed_len < 0)
return 0;
EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "HANDSHAKE_DONE frame in");
LSQ_DEBUG("received HANDSHAKE_DONE frame");
if (conn->ifc_flags & IFC_SERVER)
{
ABORT_QUIETLY(0, TEC_PROTOCOL_VIOLATION,
"Client cannot send HANDSHAKE_DONE frame");
return 0;
}
handshake_confirmed(conn);
return parsed_len;
}
static unsigned
process_ack_frequency_frame (struct ietf_full_conn *conn,
struct lsquic_packet_in *packet_in, const unsigned char *p, size_t len)
{
uint64_t seqno, pack_tol, upd_mad;
int parsed_len, ignore;
if (!(conn->ifc_flags & IFC_DELAYED_ACKS))
{
ABORT_QUIETLY(0, TEC_PROTOCOL_VIOLATION,
"Received unexpected ACK_FREQUENCY frame (not negotiated)");
return 0;
}
parsed_len = conn->ifc_conn.cn_pf->pf_parse_ack_frequency_frame(p, len,
&seqno, &pack_tol, &upd_mad, &ignore);
if (parsed_len < 0)
return 0;
EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "ACK_FREQUENCY(seqno: %"PRIu64"; "
"pack_tol: %"PRIu64"; upd: %"PRIu64"; ignore: %d) frame in", seqno,
pack_tol, upd_mad, ignore);
LSQ_DEBUG("ACK_FREQUENCY(seqno: %"PRIu64"; pack_tol: %"PRIu64"; "
"upd: %"PRIu64"; ignore: %d) frame in", seqno, pack_tol, upd_mad,
ignore);
if (pack_tol == 0)
{
ABORT_QUIETLY(0, TEC_PROTOCOL_VIOLATION,
"Packet Tolerance of zero is invalid");
return 0;
}
if (upd_mad < TP_MIN_ACK_DELAY)
{
ABORT_QUIETLY(0, TEC_PROTOCOL_VIOLATION,
"Update Max Ack Delay value of %"PRIu64" usec is invalid, as it "
"is smaller than the advertised min_ack_delay of %u usec",
upd_mad, TP_MIN_ACK_DELAY);
return 0;
}
if (conn->ifc_max_ack_freq_seqno > 0
&& seqno <= conn->ifc_max_ack_freq_seqno)
{
LSQ_DEBUG("ignore old ACK_FREQUENCY frame");
return parsed_len;
}
conn->ifc_max_ack_freq_seqno = seqno;
if (pack_tol < UINT_MAX)
{
LSQ_DEBUG("set packet tolerance to %"PRIu64, pack_tol);
conn->ifc_max_retx_since_last_ack = pack_tol;
}
if (upd_mad != conn->ifc_max_ack_delay)
{
conn->ifc_max_ack_delay = upd_mad;
LSQ_DEBUG("set Max Ack Delay to new value of %"PRIu64" usec",
conn->ifc_max_ack_delay);
}
else
LSQ_DEBUG("keep Max Ack Delay unchanged at %"PRIu64" usec",
conn->ifc_max_ack_delay);
if (ignore)
{
conn->ifc_mflags |= MF_IGNORE_MISSING;
conn->ifc_flags &= ~IFC_ACK_HAD_MISS;
}
else
conn->ifc_mflags &= ~MF_IGNORE_MISSING;
return parsed_len;
}
static unsigned
process_timestamp_frame (struct ietf_full_conn *conn,
struct lsquic_packet_in *packet_in, const unsigned char *p, size_t len)
{
ABORT_QUIETLY(0, TEC_PROTOCOL_VIOLATION,
"Received unexpected TIMESTAMP frame (not negotiated)");
return 0;
}
static unsigned
process_datagram_frame (struct ietf_full_conn *conn,
struct lsquic_packet_in *packet_in, const unsigned char *p, size_t len)
{
const void *data;
size_t data_sz;
int parsed_len;
if (!(conn->ifc_flags & IFC_DATAGRAMS))
{
ABORT_QUIETLY(0, TEC_PROTOCOL_VIOLATION,
"Received unexpected DATAGRAM frame (not negotiated)");
return 0;
}
parsed_len = conn->ifc_conn.cn_pf->pf_parse_datagram_frame(p, len,
&data, &data_sz);
if (parsed_len < 0)
return 0;
EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "%zd-byte DATAGRAM", data_sz);
LSQ_DEBUG("%zd-byte DATAGRAM", data_sz);
conn->ifc_enpub->enp_stream_if->on_datagram(&conn->ifc_conn, data, data_sz);
return parsed_len;
}
typedef unsigned (*process_frame_f)(
struct ietf_full_conn *, struct lsquic_packet_in *,
const unsigned char *p, size_t);
static process_frame_f const process_frames[N_QUIC_FRAMES] =
{
[QUIC_FRAME_PADDING] = process_padding_frame,
[QUIC_FRAME_RST_STREAM] = process_rst_stream_frame,
[QUIC_FRAME_CONNECTION_CLOSE] = process_connection_close_frame,
[QUIC_FRAME_MAX_DATA] = process_max_data_frame,
[QUIC_FRAME_MAX_STREAM_DATA] = process_max_stream_data_frame,
[QUIC_FRAME_MAX_STREAMS] = process_max_streams_frame,
[QUIC_FRAME_PING] = process_ping_frame,
[QUIC_FRAME_BLOCKED] = process_blocked_frame,
[QUIC_FRAME_STREAM_BLOCKED] = process_stream_blocked_frame,
[QUIC_FRAME_STREAMS_BLOCKED] = process_streams_blocked_frame,
[QUIC_FRAME_NEW_CONNECTION_ID] = process_new_connection_id_frame,
[QUIC_FRAME_NEW_TOKEN] = process_new_token_frame,
[QUIC_FRAME_STOP_SENDING] = process_stop_sending_frame,
[QUIC_FRAME_ACK] = process_ack_frame,
[QUIC_FRAME_PATH_CHALLENGE] = process_path_challenge_frame,
[QUIC_FRAME_PATH_RESPONSE] = process_path_response_frame,
[QUIC_FRAME_RETIRE_CONNECTION_ID] = process_retire_connection_id_frame,
[QUIC_FRAME_STREAM] = process_stream_frame,
[QUIC_FRAME_CRYPTO] = process_crypto_frame,
[QUIC_FRAME_HANDSHAKE_DONE] = process_handshake_done_frame,
[QUIC_FRAME_ACK_FREQUENCY] = process_ack_frequency_frame,
[QUIC_FRAME_TIMESTAMP] = process_timestamp_frame,
[QUIC_FRAME_DATAGRAM] = process_datagram_frame,
};
static unsigned
process_packet_frame (struct ietf_full_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;
char str[8 * 2 + 1];
enc_level = lsquic_packet_in_enc_level(packet_in);
type = conn->ifc_conn.cn_pf->pf_parse_frame_type(p, len);
if (lsquic_legal_frames_by_level[conn->ifc_conn.cn_version][enc_level]
& (1 << type))
{
LSQ_DEBUG("about to process %s frame", frame_type_2_str[type]);
packet_in->pi_frame_types |= 1 << type;
return process_frames[type](conn, packet_in, p, len);
}
else
{
LSQ_DEBUG("invalid frame %u (bytes: %s) at encryption level %s",
type, HEXSTR(p, MIN(len, 8), str), lsquic_enclev2str[enc_level]);
return 0;
}
}
static struct dcid_elem *
find_unassigned_dcid (struct ietf_full_conn *conn)
{
struct dcid_elem **dce;
for (dce = conn->ifc_dces; dce < DCES_END(conn); ++dce)
if (*dce && !((*dce)->de_flags & DE_ASSIGNED))
return *dce;
return NULL;
}
static struct conn_cid_elem *
find_cce_by_cid (struct ietf_full_conn *conn, const lsquic_cid_t *cid)
{
struct lsquic_conn *const lconn = &conn->ifc_conn;
struct conn_cid_elem *cce;
for (cce = lconn->cn_cces; cce < END_OF_CCES(lconn); ++cce)
if ((lconn->cn_cces_mask & (1 << (cce - lconn->cn_cces)))
&& LSQUIC_CIDS_EQ(&cce->cce_cid, cid))
return cce;
return NULL;
}
static int
init_new_path (struct ietf_full_conn *conn, struct conn_path *path,
int dcid_changed)
{
struct dcid_elem *dce;
dce = find_unassigned_dcid(conn);
if (dce)
{
LSQ_DEBUGC("assigned new DCID %"CID_FMT" to new path %u",
CID_BITS(&dce->de_cid), (unsigned) (path - conn->ifc_paths));
path->cop_path.np_dcid = dce->de_cid;
dce->de_flags |= DE_ASSIGNED;
}
else if (!dcid_changed || CUR_DCID(conn)->len == 0)
{
/* It is OK to reuse DCID if it is zero-length or ir the peer did not
* use a new DCID when its address changed. See
* [draft-ietf-quic-transport-24] Section 9.5.
*/
path->cop_path.np_dcid = CUR_NPATH(conn)->np_dcid;
LSQ_DEBUGC("assigned already-used DCID %"CID_FMT" to new path %u, "
"as incoming DCID did not change",
CID_BITS(&path->cop_path.np_dcid),
(unsigned) (path - conn->ifc_paths));
}
else
{
LSQ_DEBUG("Don't have an unassigned DCID: cannot initialize path");
return -1;
}
path->cop_path.np_pack_size
= calc_base_packet_size(conn, NP_IS_IPv6(&path->cop_path));
if (conn->ifc_max_udp_payload < path->cop_path.np_pack_size)
path->cop_path.np_pack_size = conn->ifc_max_udp_payload;
LSQ_DEBUG("initialized path %u", (unsigned) (path - conn->ifc_paths));
return 0;
}
static int
on_new_or_unconfirmed_path (struct ietf_full_conn *conn,
const struct lsquic_packet_in *packet_in)
{
struct lsquic_conn *const lconn = &conn->ifc_conn;
struct conn_path *const path = &conn->ifc_paths[packet_in->pi_path_id];
struct conn_cid_elem *cce;
int dcid_changed;
char cidbuf_[MAX_CID_LEN * 2 + 1];
/* An endpoint only changes the address that it sends packets to in
* response to the highest-numbered non-probing packet. This ensures
* that an endpoint does not send packets to an old peer address in the
* case that it receives reordered packets.
*
* [draft-ietf-quic-transport-20], Section 9.3.
*/
if (lsquic_packet_in_non_probing(packet_in)
&& packet_in->pi_packno > conn->ifc_max_non_probing)
path->cop_flags |= COP_GOT_NONPROB;
/* If we cannot find a SCID at this point, something is wrong. */
cce = find_cce_by_cid(conn, &packet_in->pi_dcid);
if (!cce)
{
ABORT_ERROR("DCID %"CID_FMT" not found on new path",
CID_BITS(&packet_in->pi_dcid));
return -1;
}
dcid_changed = !(cce->cce_flags & CCE_USED);
if (!(path->cop_flags & COP_INITIALIZED))
{
LSQ_DEBUGC("current SCID: %"CID_FMT, CID_BITS(CN_SCID(&conn->ifc_conn)));
LSQ_DEBUGC("packet in DCID: %"CID_FMT"; changed: %d",
CID_BITS(&packet_in->pi_dcid), dcid_changed);
if (0 == init_new_path(conn, path, dcid_changed))
path->cop_flags |= COP_INITIALIZED;
else
return -1;
conn->ifc_send_flags |= SF_SEND_PATH_CHAL << packet_in->pi_path_id;
LSQ_DEBUG("scheduled return path challenge on path %hhu",
packet_in->pi_path_id);
}
else if ((path->cop_flags & (COP_VALIDATED|COP_GOT_NONPROB))
== (COP_VALIDATED|COP_GOT_NONPROB))
{
assert(path->cop_flags & COP_INITIALIZED);
LSQ_DEBUG("received non-probing frame on validated path %hhu, "
"switch to it", packet_in->pi_path_id);
switch_path_to(conn, packet_in->pi_path_id);
}
path->cop_cce_idx = cce - lconn->cn_cces;
cce->cce_flags |= CCE_USED;
LOG_SCIDS(conn);
return 0;
}
static void
parse_regular_packet (struct ietf_full_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;
while (p < pend)
{
len = process_packet_frame(conn, packet_in, p, pend - p);
if (len > 0)
p += len;
else
{
ABORT_ERROR("Error parsing frame");
break;
}
}
}
/* From [draft-ietf-quic-transport-24] Section 13.2.1:
* " An endpoint MUST NOT send a non-ack-eliciting packet in response
* " to a non-ack-eliciting packet, even if there are packet gaps
* " which precede the received packet.
*
* To ensure that we always send an ack-eliciting packet in this case, we
* check that there are frames that are about to be written.
*/
static int
many_in_and_will_write (struct ietf_full_conn *conn)
{
return conn->ifc_n_slack_all > MAX_ANY_PACKETS_SINCE_LAST_ACK
&& (conn->ifc_send_flags
|| !TAILQ_EMPTY(&conn->ifc_pub.sending_streams)
|| !TAILQ_EMPTY(&conn->ifc_pub.write_streams))
;
}
static void
force_queueing_ack_app (struct ietf_full_conn *conn)
{
lsquic_alarmset_unset(&conn->ifc_alset, AL_ACK_APP);
lsquic_send_ctl_sanity_check(&conn->ifc_send_ctl);
conn->ifc_flags |= IFC_ACK_QUED_APP;
LSQ_DEBUG("force-queued ACK");
}
enum was_missing {
/* Note that particular enum values matter for speed */
WM_NONE = 0,
WM_MAX_GAP = 1, /* Newly arrived ackable packet introduced a gap in incoming
* packet number sequence.
*/
WM_SMALLER = 2, /* Newly arrived ackable packet is smaller than previously
* seen maximum number.
*/
};
static void
try_queueing_ack_app (struct ietf_full_conn *conn,
enum was_missing was_missing, int ecn, lsquic_time_t now)
{
lsquic_time_t srtt, ack_timeout;
if (conn->ifc_n_slack_akbl[PNS_APP] >= conn->ifc_max_retx_since_last_ack
/* From [draft-ietf-quic-transport-29] Section 13.2.1:
" Similarly, packets marked with the ECN Congestion Experienced (CE)
" codepoint in the IP header SHOULD be acknowledged immediately, to
" reduce the peer's response time to congestion events.
*/
|| (ecn == ECN_CE
&& lsquic_send_ctl_ecn_turned_on(&conn->ifc_send_ctl))
|| (was_missing == WM_MAX_GAP)
|| ((conn->ifc_flags & IFC_ACK_HAD_MISS)
&& was_missing == WM_SMALLER
&& conn->ifc_n_slack_akbl[PNS_APP] > 0)
|| many_in_and_will_write(conn))
{
lsquic_alarmset_unset(&conn->ifc_alset, AL_ACK_APP);
lsquic_send_ctl_sanity_check(&conn->ifc_send_ctl);
conn->ifc_flags |= IFC_ACK_QUED_APP;
LSQ_DEBUG("%s ACK queued: ackable: %u; all: %u; had_miss: %d; "
"was_missing: %d",
lsquic_pns2str[PNS_APP], conn->ifc_n_slack_akbl[PNS_APP],
conn->ifc_n_slack_all,
!!(conn->ifc_flags & IFC_ACK_HAD_MISS), (int) was_missing);
}
else if (conn->ifc_n_slack_akbl[PNS_APP] > 0)
{
if (!lsquic_alarmset_is_set(&conn->ifc_alset, AL_ACK_APP))
{
/* See https://github.com/quicwg/base-drafts/issues/3304 for more */
srtt = lsquic_rtt_stats_get_srtt(&conn->ifc_pub.rtt_stats);
if (srtt)
ack_timeout = MAX(1000, MIN(conn->ifc_max_ack_delay, srtt / 4));
else
ack_timeout = conn->ifc_max_ack_delay;
lsquic_alarmset_set(&conn->ifc_alset, AL_ACK_APP,
now + ack_timeout);
LSQ_DEBUG("%s ACK alarm set to %"PRIu64, lsquic_pns2str[PNS_APP],
now + ack_timeout);
}
else
LSQ_DEBUG("%s ACK alarm already set to %"PRIu64" usec from now",
lsquic_pns2str[PNS_APP],
conn->ifc_alset.as_expiry[AL_ACK_APP] - now);
}
}
static void
try_queueing_ack_init_or_hsk (struct ietf_full_conn *conn,
enum packnum_space pns)
{
if (conn->ifc_n_slack_akbl[pns] > 0)
{
conn->ifc_flags |= IFC_ACK_QUED_INIT << pns;
LSQ_DEBUG("%s ACK queued: ackable: %u",
lsquic_pns2str[pns], conn->ifc_n_slack_akbl[pns]);
}
}
static int
maybe_queue_opp_ack (struct ietf_full_conn *conn)
{
if (/* If there is at least one ackable packet */
conn->ifc_n_slack_akbl[PNS_APP] > 0
/* ...and there are things to write */
&& (!TAILQ_EMPTY(&conn->ifc_pub.write_streams) || conn->ifc_send_flags)
/* ...and writing is possible */
&& write_is_possible(conn))
{
lsquic_alarmset_unset(&conn->ifc_alset, AL_ACK_APP);
lsquic_send_ctl_sanity_check(&conn->ifc_send_ctl);
conn->ifc_flags |= IFC_ACK_QUED_APP;
LSQ_DEBUG("%s ACK queued opportunistically", lsquic_pns2str[PNS_APP]);
return 1;
}
else
return 0;
}
static int
verify_retry_packet (struct ietf_full_conn *conn,
const struct lsquic_packet_in *packet_in)
{
unsigned char *pseudo_packet;
size_t out_len, ad_len;
unsigned ret_ver;
int verified;
if (1 + CUR_DCID(conn)->len + packet_in->pi_data_sz > 0x1000)
{
/* Cover the theoretical possibility that we cannot fit the pseudo-
* packet and 16-byte decrypted output into 4 KB:
*/
LSQ_INFO("%s: Retry packet is too long: %hu bytes", __func__,
packet_in->pi_data_sz);
return -1;
}
pseudo_packet = lsquic_mm_get_4k(conn->ifc_pub.mm);
if (!pseudo_packet)
{
LSQ_INFO("%s: cannot allocate memory", __func__);
return -1;
}
pseudo_packet[0] = CUR_DCID(conn)->len;
memcpy(pseudo_packet + 1, CUR_DCID(conn)->idbuf, CUR_DCID(conn)->len);
memcpy(pseudo_packet + 1 + CUR_DCID(conn)->len, packet_in->pi_data,
packet_in->pi_data_sz);
ret_ver = lsquic_version_2_retryver(conn->ifc_conn.cn_version);
out_len = 0;
ad_len = 1 + CUR_DCID(conn)->len + packet_in->pi_data_sz - 16;
verified = 1 == EVP_AEAD_CTX_open(
&conn->ifc_enpub->enp_retry_aead_ctx[ret_ver],
pseudo_packet + ad_len, &out_len, out_len,
lsquic_retry_nonce_buf[ret_ver], IETF_RETRY_NONCE_SZ,
pseudo_packet + ad_len, 16, pseudo_packet, ad_len)
&& out_len == 0;
lsquic_mm_put_4k(conn->ifc_pub.mm, pseudo_packet);
return verified ? 0 : -1;
}
static int
process_retry_packet (struct ietf_full_conn *conn,
struct lsquic_packet_in *packet_in)
{
lsquic_cid_t scid;
if (conn->ifc_flags & (IFC_SERVER|IFC_RETRIED))
{
/* [draft-ietf-quic-transport-24] Section 17.2.5:
" After the client has received and processed an Initial or Retry
" packet from the server, it MUST discard any subsequent Retry
" packets that it receives.
*/
LSQ_DEBUG("ignore Retry packet");
return 0;
}
if (CUR_DCID(conn)->len == packet_in->pi_scid_len
&& 0 == memcmp(CUR_DCID(conn)->idbuf,
packet_in->pi_data + packet_in->pi_scid_off,
packet_in->pi_scid_len))
{
/*
* [draft-ietf-quic-transport-24] Section 17.2.5:
" A client MUST discard a Retry packet that contains a Source
" Connection ID field that is identical to the Destination
" Connection ID field of its Initial packet.
*/
LSQ_DEBUG("server provided same SCID as ODCID: discard packet");
return 0;
}
if (0 != verify_retry_packet(conn, packet_in))
{
LSQ_DEBUG("cannot verify retry packet: ignore it");
return 0;
}
if (0 != lsquic_send_ctl_retry(&conn->ifc_send_ctl,
packet_in->pi_data + packet_in->pi_token,
packet_in->pi_token_size))
return -1;
lsquic_scid_from_packet_in(packet_in, &scid);
if (0 != conn->ifc_conn.cn_esf.i->esfi_reset_dcid(
conn->ifc_conn.cn_enc_session, CUR_DCID(conn), &scid))
return -1;
*CUR_DCID(conn) = scid;
if (CUR_CPATH(conn)->cop_flags & COP_SPIN_BIT)
CUR_CPATH(conn)->cop_spin_bit = 0;
lsquic_alarmset_unset(&conn->ifc_alset, AL_RETX_INIT);
lsquic_alarmset_unset(&conn->ifc_alset, AL_RETX_HSK);
lsquic_alarmset_unset(&conn->ifc_alset, AL_RETX_APP);
LSQ_INFO("Received a retry packet. Will retry.");
conn->ifc_flags |= IFC_RETRIED;
return 0;
}
static int
is_stateless_reset (struct ietf_full_conn *conn,
const struct lsquic_packet_in *packet_in)
{
struct lsquic_hash_elem *el;
if (packet_in->pi_data_sz < IQUIC_MIN_SRST_SIZE)
return 0;
el = lsquic_hash_find(conn->ifc_enpub->enp_srst_hash,
packet_in->pi_data + packet_in->pi_data_sz - IQUIC_SRESET_TOKEN_SZ,
IQUIC_SRESET_TOKEN_SZ);
if (!el)
return 0;
#ifndef NDEBUG
const struct lsquic_conn *reset_lconn;
reset_lconn = lsquic_hashelem_getdata(el);
assert(reset_lconn == &conn->ifc_conn);
#endif
return 1;
}
/*
* Sets the new current SCID if the DCID in the incoming packet:
* (1) was issued by this endpoint and
* (2) has not been used before.
*/
static int
on_dcid_change (struct ietf_full_conn *conn, const lsquic_cid_t *dcid_in)
{
struct lsquic_conn *const lconn = &conn->ifc_conn; /* Shorthand */
struct conn_cid_elem *cce;
LSQ_DEBUG("peer switched its DCID, attempt to switch own SCID");
for (cce = lconn->cn_cces; cce < END_OF_CCES(lconn); ++cce)
if (cce - lconn->cn_cces != lconn->cn_cur_cce_idx
&& (lconn->cn_cces_mask & (1 << (cce - lconn->cn_cces)))
&& LSQUIC_CIDS_EQ(&cce->cce_cid, dcid_in))
break;
if (cce >= END_OF_CCES(lconn))
{
ABORT_WARN("DCID not found");
return -1;
}
if (cce->cce_flags & CCE_USED)
{
LSQ_DEBUGC("Current CID: %"CID_FMT, CID_BITS(CN_SCID(lconn)));
LSQ_DEBUGC("DCID %"CID_FMT" has been used, not switching",
CID_BITS(dcid_in));
return 0;
}
cce->cce_flags |= CCE_USED;
lconn->cn_cur_cce_idx = cce - lconn->cn_cces;
LSQ_DEBUGC("%s: set SCID to %"CID_FMT, __func__, CID_BITS(CN_SCID(lconn)));
LOG_SCIDS(conn);
return 0;
}
static void
ignore_init (struct ietf_full_conn *conn)
{
LSQ_DEBUG("henceforth, no Initial packets shall be sent or received");
conn->ifc_flags |= IFC_IGNORE_INIT;
conn->ifc_flags &= ~(IFC_ACK_QUED_INIT << PNS_INIT);
lsquic_send_ctl_empty_pns(&conn->ifc_send_ctl, PNS_INIT);
lsquic_rechist_cleanup(&conn->ifc_rechist[PNS_INIT]);
if (!(conn->ifc_flags & IFC_SERVER))
{
if (conn->ifc_u.cli.crypto_streams[ENC_LEV_CLEAR])
{
lsquic_stream_destroy(conn->ifc_u.cli.crypto_streams[ENC_LEV_CLEAR]);
conn->ifc_u.cli.crypto_streams[ENC_LEV_CLEAR] = NULL;
}
conn->ifc_conn.cn_if = ietf_full_conn_iface_ptr;
}
}
static void
ignore_hsk (struct ietf_full_conn *conn)
{
LSQ_DEBUG("henceforth, no Handshake packets shall be sent or received");
conn->ifc_flags |= IFC_IGNORE_HSK;
conn->ifc_flags &= ~(IFC_ACK_QUED_INIT << PNS_HSK);
lsquic_send_ctl_empty_pns(&conn->ifc_send_ctl, PNS_HSK);
lsquic_rechist_cleanup(&conn->ifc_rechist[PNS_HSK]);
if (!(conn->ifc_flags & IFC_SERVER))
if (conn->ifc_u.cli.crypto_streams[ENC_LEV_INIT])
{
lsquic_stream_destroy(conn->ifc_u.cli.crypto_streams[ENC_LEV_INIT]);
conn->ifc_u.cli.crypto_streams[ENC_LEV_INIT] = NULL;
}
}
static void
record_dcid (struct ietf_full_conn *conn,
const struct lsquic_packet_in *packet_in)
{
unsigned orig_cid_len;
orig_cid_len = CUR_DCID(conn)->len;
conn->ifc_flags |= IFC_DCID_SET;
lsquic_scid_from_packet_in(packet_in, CUR_DCID(conn));
LSQ_DEBUGC("set DCID to %"CID_FMT, CID_BITS(CUR_DCID(conn)));
lsquic_send_ctl_cidlen_change(&conn->ifc_send_ctl, orig_cid_len,
CUR_DCID(conn)->len);
}
static int
holes_after (struct lsquic_rechist *rechist, lsquic_packno_t packno)
{
const struct lsquic_packno_range *first_range;
first_range = lsquic_rechist_peek(rechist);
/* If it's not in the very first range, there is obviously a gap
* between it and the maximum packet number. If the packet number
* in question preceeds the cutoff, we assume that there are no
* holes (as we simply have no information).
*/
return first_range
&& packno < first_range->low
&& packno > lsquic_rechist_cutoff(rechist);
}
static int
process_regular_packet (struct ietf_full_conn *conn,
struct lsquic_packet_in *packet_in)
{
struct conn_path *cpath;
enum packnum_space pns;
enum received_st st;
enum dec_packin dec_packin;
enum was_missing was_missing;
int is_rechist_empty;
unsigned char saved_path_id;
if (HETY_RETRY == packet_in->pi_header_type)
return process_retry_packet(conn, packet_in);
CONN_STATS(in.packets, 1);
pns = lsquic_hety2pns[ packet_in->pi_header_type ];
if (pns == PNS_INIT)
conn->ifc_conn.cn_esf.i->esfi_set_iscid(conn->ifc_conn.cn_enc_session,
packet_in);
else if (pns == PNS_HSK)
lsquic_send_ctl_maybe_calc_rough_rtt(&conn->ifc_send_ctl, pns - 1);
if ((pns == PNS_INIT && (conn->ifc_flags & IFC_IGNORE_INIT))
|| (pns == PNS_HSK && (conn->ifc_flags & IFC_IGNORE_HSK)))
{
/* Don't bother decrypting */
LSQ_DEBUG("ignore %s packet",
pns == PNS_INIT ? "Initial" : "Handshake");
EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "ignore %s packet",
lsquic_pns2str[pns]);
return 0;
}
/* If a client receives packets from an unknown server address, the client
* MUST discard these packets.
* [draft-ietf-quic-transport-20], Section 9
*/
if (packet_in->pi_path_id != conn->ifc_cur_path_id
&& 0 == (conn->ifc_flags & IFC_SERVER)
&& !(packet_in->pi_path_id == conn->ifc_mig_path_id
&& migra_is_on(conn, conn->ifc_mig_path_id)))
{
/* The "known server address" is recorded in the current path. */
switch ((NP_IS_IPv6(CUR_NPATH(conn)) << 1) |
NP_IS_IPv6(&conn->ifc_paths[packet_in->pi_path_id].cop_path))
{
case (1 << 1) | 1: /* IPv6 */
if (lsquic_sockaddr_eq(NP_PEER_SA(CUR_NPATH(conn)), NP_PEER_SA(
&conn->ifc_paths[packet_in->pi_path_id].cop_path)))
goto known_peer_addr;
break;
case (0 << 1) | 0: /* IPv4 */
if (lsquic_sockaddr_eq(NP_PEER_SA(CUR_NPATH(conn)), NP_PEER_SA(
&conn->ifc_paths[packet_in->pi_path_id].cop_path)))
goto known_peer_addr;
break;
}
LSQ_DEBUG("ignore packet from unknown server address");
return 0;
}
known_peer_addr:
/* The packet is decrypted before receive history is updated. This is
* done to make sure that a bad packet won't occupy a slot in receive
* history and subsequent good packet won't be marked as a duplicate.
*/
if (0 == (packet_in->pi_flags & PI_DECRYPTED))
{
dec_packin = conn->ifc_conn.cn_esf_c->esf_decrypt_packet(
conn->ifc_conn.cn_enc_session, conn->ifc_enpub,
&conn->ifc_conn, packet_in);
switch (dec_packin)
{
case DECPI_BADCRYPT:
case DECPI_TOO_SHORT:
if (conn->ifc_enpub->enp_settings.es_honor_prst
/* In server mode, even if we do support stateless reset packets,
* they are handled in lsquic_engine.c. No need to have this
* logic here.
*/
&& !(conn->ifc_flags & IFC_SERVER)
&& is_stateless_reset(conn, packet_in))
{
LSQ_INFO("received stateless reset packet: aborting connection");
conn->ifc_flags |= IFC_GOT_PRST;
return -1;
}
else if (dec_packin == DECPI_BADCRYPT)
{
CONN_STATS(in.undec_packets, 1);
LSQ_INFO("could not decrypt packet (type %s)",
lsquic_hety2str[packet_in->pi_header_type]);
return 0;
}
else
{
CONN_STATS(in.undec_packets, 1);
LSQ_INFO("packet is too short to be decrypted");
return 0;
}
case DECPI_NOT_YET:
return 0;
case DECPI_NOMEM:
return 0;
case DECPI_VIOLATION:
ABORT_QUIETLY(0, TEC_PROTOCOL_VIOLATION,
"decrypter reports protocol violation");
return -1;
case DECPI_OK:
/* Receiving any other type of packet precludes subsequent retries.
* We only set it if decryption is successful.
*/
conn->ifc_flags |= IFC_RETRIED;
break;
}
}
EV_LOG_PACKET_IN(LSQUIC_LOG_CONN_ID, packet_in);
is_rechist_empty = lsquic_rechist_is_empty(&conn->ifc_rechist[pns]);
st = lsquic_rechist_received(&conn->ifc_rechist[pns], packet_in->pi_packno,
packet_in->pi_received);
switch (st) {
case REC_ST_OK:
if (!(conn->ifc_flags & (IFC_SERVER|IFC_DCID_SET))
&& (packet_in->pi_scid_len))
record_dcid(conn, packet_in);
saved_path_id = conn->ifc_cur_path_id;
parse_regular_packet(conn, packet_in);
if (saved_path_id == conn->ifc_cur_path_id)
{
if (conn->ifc_cur_path_id != packet_in->pi_path_id)
{
if (0 != on_new_or_unconfirmed_path(conn, packet_in))
{
LSQ_DEBUG("path %hhu invalid, cancel any path response "
"on it", packet_in->pi_path_id);
conn->ifc_send_flags &= ~(SF_SEND_PATH_RESP
<< packet_in->pi_path_id);
}
}
else if (!LSQUIC_CIDS_EQ(CN_SCID(&conn->ifc_conn),
&packet_in->pi_dcid))
{
if (0 != on_dcid_change(conn, &packet_in->pi_dcid))
return -1;
}
}
if (lsquic_packet_in_non_probing(packet_in)
&& packet_in->pi_packno > conn->ifc_max_non_probing)
conn->ifc_max_non_probing = packet_in->pi_packno;
/* From [draft-ietf-quic-transport-30] Section 13.2.1:
*
" In order to assist loss detection at the sender, an endpoint SHOULD
" generate and send an ACK frame without delay when it receives an ack-
" eliciting packet either:
"
" * when the received packet has a packet number less than another
" ack-eliciting packet that has been received, or
"
" * when the packet has a packet number larger than the highest-
" numbered ack-eliciting packet that has been received and there are
" missing packets between that packet and this packet.
*
*/
if (packet_in->pi_frame_types & IQUIC_FRAME_ACKABLE_MASK)
{
if (PNS_APP == pns /* was_missing is only used in PNS_APP */)
{
if (packet_in->pi_packno > conn->ifc_max_ackable_packno_in)
{
was_missing = (enum was_missing) /* WM_MAX_GAP is 1 */
!is_rechist_empty /* Don't count very first packno */
&& conn->ifc_max_ackable_packno_in + 1
< packet_in->pi_packno
&& holes_after(&conn->ifc_rechist[PNS_APP],
conn->ifc_max_ackable_packno_in);
conn->ifc_max_ackable_packno_in = packet_in->pi_packno;
}
else
was_missing = (enum was_missing) /* WM_SMALLER is 2 */
/* The check is necessary (rather setting was_missing to
* WM_SMALLER) because we cannot guarantee that peer does
* not have bugs.
*/
((packet_in->pi_packno
< conn->ifc_max_ackable_packno_in) << 1);
}
else
was_missing = WM_NONE;
++conn->ifc_n_slack_akbl[pns];
}
else
was_missing = WM_NONE;
conn->ifc_n_slack_all += PNS_APP == pns;
if (0 == (conn->ifc_flags & (IFC_ACK_QUED_INIT << pns)))
{
if (PNS_APP == pns)
try_queueing_ack_app(conn, was_missing,
lsquic_packet_in_ecn(packet_in), packet_in->pi_received);
else
try_queueing_ack_init_or_hsk(conn, pns);
}
conn->ifc_incoming_ecn <<= 1;
conn->ifc_incoming_ecn |=
lsquic_packet_in_ecn(packet_in) != ECN_NOT_ECT;
++conn->ifc_ecn_counts_in[pns][ lsquic_packet_in_ecn(packet_in) ];
if (PNS_APP == pns
&& (cpath = &conn->ifc_paths[packet_in->pi_path_id],
cpath->cop_flags & COP_SPIN_BIT)
/* [draft-ietf-quic-transport-30] Section 17.3.1 talks about
* how spin bit value is set.
*/
&& (packet_in->pi_packno > cpath->cop_max_packno
/* Zero means "unset", in which case any incoming packet
* number will do. On receipt of second packet numbered
* zero, the rechist module will dup it and this code path
* won't hit.
*/
|| cpath->cop_max_packno == 0))
{
cpath->cop_max_packno = packet_in->pi_packno;
if (conn->ifc_flags & IFC_SERVER)
cpath->cop_spin_bit = lsquic_packet_in_spin_bit(packet_in);
else
cpath->cop_spin_bit = !lsquic_packet_in_spin_bit(packet_in);
}
conn->ifc_pub.bytes_in += packet_in->pi_data_sz;
if ((conn->ifc_mflags & MF_VALIDATE_PATH) &&
(packet_in->pi_header_type == HETY_NOT_SET
|| packet_in->pi_header_type == HETY_HANDSHAKE))
{
conn->ifc_mflags &= ~MF_VALIDATE_PATH;
lsquic_send_ctl_path_validated(&conn->ifc_send_ctl);
}
return 0;
case REC_ST_DUP:
CONN_STATS(in.dup_packets, 1);
LSQ_INFO("packet %"PRIu64" is a duplicate", packet_in->pi_packno);
return 0;
default:
assert(0);
/* Fall through */
case REC_ST_ERR:
CONN_STATS(in.err_packets, 1);
LSQ_INFO("error processing packet %"PRIu64, packet_in->pi_packno);
return -1;
}
}
static int
verneg_ok (const struct ietf_full_conn *conn)
{
enum lsquic_version ver;
ver = highest_bit_set(conn->ifc_u.cli.ifcli_ver_neg.vn_supp);
return (1 << ver) & LSQUIC_IETF_DRAFT_VERSIONS;
}
/* This function is used by the client when version negotiation is not yet
* complete.
*/
static int
process_incoming_packet_verneg (struct ietf_full_conn *conn,
struct lsquic_packet_in *packet_in)
{
int s;
struct ver_iter vi;
lsquic_ver_tag_t ver_tag;
enum lsquic_version version;
unsigned versions;
if (lsquic_packet_in_is_verneg(packet_in))
{
LSQ_DEBUG("Processing version-negotiation packet");
if (conn->ifc_u.cli.ifcli_ver_neg.vn_state != VN_START)
{
LSQ_DEBUG("ignore a likely duplicate version negotiation packet");
return 0;
}
if (!(LSQUIC_CIDS_EQ(&conn->ifc_conn.cn_cid, &packet_in->pi_dcid)
&& CUR_DCID(conn)->len == packet_in->pi_scid_len
&& 0 == memcmp(CUR_DCID(conn)->idbuf, packet_in->pi_data
+ packet_in->pi_scid_off, packet_in->pi_scid_len)))
{
LSQ_DEBUG("SCID and DCID in verneg packet don't match what we "
"sent: ignore");
return 0;
}
versions = 0;
for (s = lsquic_packet_in_ver_first(packet_in, &vi, &ver_tag); s;
s = lsquic_packet_in_ver_next(&vi, &ver_tag))
{
version = lsquic_tag2ver(ver_tag);
if (version < N_LSQVER)
{
versions |= 1 << version;
LSQ_DEBUG("server supports version %s", lsquic_ver2str[version]);
EV_LOG_VER_NEG(LSQUIC_LOG_CONN_ID,
"supports", lsquic_ver2str[version]);
}
}
/* [draft-ietf-quic-transport-28] Section 6.2:
" A client MUST discard a Version Negotiation packet that lists the
" QUIC version selected by the client.
*/
if (versions & (1 << conn->ifc_u.cli.ifcli_ver_neg.vn_ver))
{
LSQ_DEBUG("server replied with version we sent, %s, ignore",
lsquic_ver2str[conn->ifc_u.cli.ifcli_ver_neg.vn_ver]);
return 0;
}
/* [draft-ietf-quic-transport-28] Section 6.2:
" A client that supports only this version of QUIC MUST abandon the
" current connection attempt if it receives a Version Negotiation
" packet [...]
*/
if (!verneg_ok(conn))
{
ABORT_ERROR("version negotiation not permitted in this version "
"of QUIC");
return -1;
}
versions &= conn->ifc_u.cli.ifcli_ver_neg.vn_supp;
if (0 == versions)
{
ABORT_ERROR("client does not support any of the server-specified "
"versions");
return -1;
}
set_versions(conn, versions, NULL);
conn->ifc_u.cli.ifcli_ver_neg.vn_state = VN_IN_PROGRESS;
lsquic_send_ctl_expire_all(&conn->ifc_send_ctl);
return 0;
}
assert(conn->ifc_u.cli.ifcli_ver_neg.vn_tag);
assert(conn->ifc_u.cli.ifcli_ver_neg.vn_state != VN_END);
conn->ifc_u.cli.ifcli_ver_neg.vn_state = VN_END;
conn->ifc_u.cli.ifcli_ver_neg.vn_tag = NULL;
conn->ifc_conn.cn_version = conn->ifc_u.cli.ifcli_ver_neg.vn_ver;
conn->ifc_conn.cn_flags |= LSCONN_VER_SET;
LSQ_DEBUG("end of version negotiation: agreed upon %s",
lsquic_ver2str[conn->ifc_u.cli.ifcli_ver_neg.vn_ver]);
EV_LOG_VER_NEG(LSQUIC_LOG_CONN_ID,
"agreed", lsquic_ver2str[conn->ifc_u.cli.ifcli_ver_neg.vn_ver]);
conn->ifc_process_incoming_packet = process_incoming_packet_fast;
return process_regular_packet(conn, packet_in);
}
/* This function is used after version negotiation is completed */
static int
process_incoming_packet_fast (struct ietf_full_conn *conn,
struct lsquic_packet_in *packet_in)
{
return process_regular_packet(conn, packet_in);
}
static void
set_earliest_idle_alarm (struct ietf_full_conn *conn, lsquic_time_t idle_conn_to)
{
lsquic_time_t exp;
if (conn->ifc_pub.last_prog
&& (assert(conn->ifc_mflags & MF_NOPROG_TIMEOUT),
exp = conn->ifc_pub.last_prog + conn->ifc_enpub->enp_noprog_timeout,
exp < idle_conn_to))
idle_conn_to = exp;
if (idle_conn_to)
lsquic_alarmset_set(&conn->ifc_alset, AL_IDLE, idle_conn_to);
}
static void
ietf_full_conn_ci_packet_in (struct lsquic_conn *lconn,
struct lsquic_packet_in *packet_in)
{
struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn;
CONN_STATS(in.bytes, packet_in->pi_data_sz);
set_earliest_idle_alarm(conn, conn->ifc_idle_to
? packet_in->pi_received + conn->ifc_idle_to : 0);
if (0 == (conn->ifc_flags & IFC_IMMEDIATE_CLOSE_FLAGS))
if (0 != conn->ifc_process_incoming_packet(conn, packet_in))
conn->ifc_flags |= IFC_ERROR;
}
static void
ietf_full_conn_ci_packet_not_sent (struct lsquic_conn *lconn,
struct lsquic_packet_out *packet_out)
{
#ifndef NDEBUG
if (packet_out->po_flags & PO_ENCRYPTED)
assert(packet_out->po_lflags & POL_HEADER_PROT);
#endif
struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn;
lsquic_send_ctl_delayed_one(&conn->ifc_send_ctl, packet_out);
}
static void
ietf_full_conn_ci_packet_too_large (struct lsquic_conn *lconn,
struct lsquic_packet_out *packet_out)
{
struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn;
#ifndef NDEBUG
assert(packet_out->po_lflags & POL_HEADER_PROT);
#endif
lsquic_senhist_add(&conn->ifc_send_ctl.sc_senhist, packet_out->po_packno);
lsquic_send_ctl_sanity_check(&conn->ifc_send_ctl);
if (packet_out->po_flags & PO_MTU_PROBE)
{
LSQ_DEBUG("%zu-byte MTU probe in packet %"PRIu64" is too large",
lsquic_packet_out_sent_sz(&conn->ifc_conn, packet_out),
packet_out->po_packno);
mtu_probe_too_large(conn, packet_out);
}
else
ABORT_WARN("non-MTU probe %zu-byte packet %"PRIu64" is too large",
lsquic_packet_out_sent_sz(&conn->ifc_conn, packet_out),
packet_out->po_packno);
lsquic_packet_out_destroy(packet_out, conn->ifc_enpub,
packet_out->po_path->np_peer_ctx);
}
/* Calling of ignore_init() must be delayed until all batched packets have
* been returned by the engine.
*/
static void
pre_hsk_packet_sent_or_delayed (struct ietf_full_conn *conn,
const struct lsquic_packet_out *packet_out)
{
#ifndef NDEBUG
if (packet_out->po_flags & PO_ENCRYPTED)
assert(packet_out->po_lflags & POL_HEADER_PROT);
#endif
/* Once IFC_IGNORE_INIT is set, the pre-hsk wrapper is removed: */
assert(!(conn->ifc_flags & IFC_IGNORE_INIT));
--conn->ifc_u.cli.ifcli_packets_out;
if (PNS_HSK == lsquic_packet_out_pns(packet_out))
conn->ifc_u.cli.ifcli_flags |= IFCLI_HSK_SENT_OR_DEL;
if (0 == conn->ifc_u.cli.ifcli_packets_out
&& (conn->ifc_u.cli.ifcli_flags & IFCLI_HSK_SENT_OR_DEL))
ignore_init(conn);
}
static void
ietf_full_conn_ci_packet_not_sent_pre_hsk (struct lsquic_conn *lconn,
struct lsquic_packet_out *packet_out)
{
struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn;
ietf_full_conn_ci_packet_not_sent(lconn, packet_out);
pre_hsk_packet_sent_or_delayed(conn, packet_out);
}
static void
ietf_full_conn_ci_packet_sent (struct lsquic_conn *lconn,
struct lsquic_packet_out *packet_out)
{
struct ietf_full_conn *const conn = (struct ietf_full_conn *) lconn;
int s;
if (packet_out->po_frame_types & IQUIC_FRAME_RETX_MASK)
conn->ifc_n_cons_unretx = 0;
else
++conn->ifc_n_cons_unretx;
s = lsquic_send_ctl_sent_packet(&conn->ifc_send_ctl, packet_out);
if (s != 0)
ABORT_ERROR("sent packet failed: %s", strerror(errno));
++conn->ifc_ecn_counts_out[ lsquic_packet_out_pns(packet_out) ]
[ lsquic_packet_out_ecn(packet_out) ];
/* Set blocked keep-alive for a [1,8] seconds */
if (packet_out->po_frame_types
& (QUIC_FTBIT_BLOCKED|QUIC_FTBIT_STREAM_BLOCKED))
lsquic_alarmset_set(&conn->ifc_alset, AL_BLOCKED_KA,
packet_out->po_sent + (1 + (7 & lsquic_crand_get_nybble(
conn->ifc_enpub->enp_crand))) * 1000000);
conn->ifc_pub.bytes_out += lsquic_packet_out_sent_sz(&conn->ifc_conn,
packet_out);
CONN_STATS(out.packets, 1);
CONN_STATS(out.bytes, lsquic_packet_out_sent_sz(lconn, packet_out));
}
static void
ietf_full_conn_ci_packet_sent_pre_hsk (struct lsquic_conn *lconn,
struct lsquic_packet_out *packet_out)
{
struct ietf_full_conn *const conn = (struct ietf_full_conn *) lconn;
ietf_full_conn_ci_packet_sent(lconn, packet_out);
pre_hsk_packet_sent_or_delayed(conn, packet_out);
}
static void (*const send_funcs[N_SEND])(
struct ietf_full_conn *, lsquic_time_t) =
{
[SEND_NEW_CID] = generate_new_cid_frames,
[SEND_RETIRE_CID] = generate_retire_cid_frames,
[SEND_STREAMS_BLOCKED_UNI] = generate_streams_blocked_uni_frame,
[SEND_STREAMS_BLOCKED_BIDI] = generate_streams_blocked_bidi_frame,
[SEND_MAX_STREAMS_UNI] = generate_max_streams_uni_frame,
[SEND_MAX_STREAMS_BIDI] = generate_max_streams_bidi_frame,
[SEND_STOP_SENDING] = generate_stop_sending_frames,
[SEND_PATH_CHAL_PATH_0] = generate_path_chal_0,
[SEND_PATH_CHAL_PATH_1] = generate_path_chal_1,
[SEND_PATH_CHAL_PATH_2] = generate_path_chal_2,
[SEND_PATH_CHAL_PATH_3] = generate_path_chal_3,
[SEND_PATH_RESP_PATH_0] = generate_path_resp_0,
[SEND_PATH_RESP_PATH_1] = generate_path_resp_1,
[SEND_PATH_RESP_PATH_2] = generate_path_resp_2,
[SEND_PATH_RESP_PATH_3] = generate_path_resp_3,
[SEND_PING] = generate_ping_frame,
[SEND_HANDSHAKE_DONE] = generate_handshake_done_frame,
[SEND_ACK_FREQUENCY] = generate_ack_frequency_frame,
};
/* List bits that have corresponding entries in send_funcs */
#define SEND_WITH_FUNCS (SF_SEND_NEW_CID|SF_SEND_RETIRE_CID\
|SF_SEND_STREAMS_BLOCKED_UNI|SF_SEND_STREAMS_BLOCKED_BIDI\
|SF_SEND_MAX_STREAMS_UNI|SF_SEND_MAX_STREAMS_BIDI\
|SF_SEND_PATH_CHAL_PATH_0|SF_SEND_PATH_CHAL_PATH_1\
|SF_SEND_PATH_CHAL_PATH_2|SF_SEND_PATH_CHAL_PATH_3\
|SF_SEND_PATH_RESP_PATH_0|SF_SEND_PATH_RESP_PATH_1\
|SF_SEND_PATH_RESP_PATH_2|SF_SEND_PATH_RESP_PATH_3\
|SF_SEND_PING|SF_SEND_HANDSHAKE_DONE\
|SF_SEND_ACK_FREQUENCY\
|SF_SEND_STOP_SENDING)
/* This should be called before lsquic_alarmset_ring_expired() */
static void
maybe_set_noprogress_alarm (struct ietf_full_conn *conn, lsquic_time_t now)
{
lsquic_time_t exp;
if (conn->ifc_mflags & MF_NOPROG_TIMEOUT)
{
if (conn->ifc_pub.last_tick)
{
exp = conn->ifc_pub.last_prog + conn->ifc_enpub->enp_noprog_timeout;
if (!lsquic_alarmset_is_set(&conn->ifc_alset, AL_IDLE)
|| exp < conn->ifc_alset.as_expiry[AL_IDLE])
lsquic_alarmset_set(&conn->ifc_alset, AL_IDLE, exp);
conn->ifc_pub.last_tick = now;
}
else
{
conn->ifc_pub.last_tick = now;
conn->ifc_pub.last_prog = now;
}
}
}
static void
check_or_schedule_mtu_probe (struct ietf_full_conn *conn, lsquic_time_t now)
{
struct conn_path *const cpath = CUR_CPATH(conn);
struct dplpmtud_state *const ds = &cpath->cop_dplpmtud;
struct lsquic_packet_out *packet_out;
unsigned short saved_packet_sz, avail, mtu_ceiling, net_header_sz, probe_sz;
int sz;
if (ds->ds_flags & DS_PROBE_SENT)
{
assert(ds->ds_probe_sent + conn->ifc_enpub->enp_mtu_probe_timer < now);
LSQ_DEBUG("MTU probe of %hu bytes lost", ds->ds_probed_size);
ds->ds_flags &= ~DS_PROBE_SENT;
conn->ifc_mflags |= MF_CHECK_MTU_PROBE;
if (ds->ds_probe_count >= 3)
{
LSQ_DEBUG("MTU probe of %hu bytes lost after %hhu tries",
ds->ds_probed_size, ds->ds_probe_count);
ds->ds_failed_size = ds->ds_probed_size;
ds->ds_probe_count = 0;
}
}
assert(0 == ds->ds_probe_sent
|| ds->ds_probe_sent + conn->ifc_enpub->enp_mtu_probe_timer < now);
if (!(conn->ifc_conn.cn_flags & LSCONN_HANDSHAKE_DONE)
|| (conn->ifc_flags & IFC_CLOSING)
|| ~0ull == lsquic_senhist_largest(&conn->ifc_send_ctl.sc_senhist)
|| lsquic_senhist_largest(&conn->ifc_send_ctl.sc_senhist) < 30
|| lsquic_send_ctl_in_recovery(&conn->ifc_send_ctl)
|| !lsquic_send_ctl_can_send_probe(&conn->ifc_send_ctl,
&cpath->cop_path))
{
return;
}
if (ds->ds_failed_size)
mtu_ceiling = ds->ds_failed_size;
else if (conn->ifc_settings->es_max_plpmtu)
mtu_ceiling = conn->ifc_settings->es_max_plpmtu;
else
{
net_header_sz = TRANSPORT_OVERHEAD(NP_IS_IPv6(&cpath->cop_path));
mtu_ceiling = 1500 - net_header_sz;
}
if (conn->ifc_max_udp_payload < mtu_ceiling)
{
LSQ_DEBUG("cap MTU ceiling to peer's max_udp_payload_size TP of %hu "
"bytes", conn->ifc_max_udp_payload);
mtu_ceiling = conn->ifc_max_udp_payload;
}
if (cpath->cop_path.np_pack_size >= mtu_ceiling
|| (float) cpath->cop_path.np_pack_size / (float) mtu_ceiling >= 0.99)
{
LSQ_DEBUG("stop MTU probing on path %hhu having achieved about "
"%.1f%% efficiency (detected MTU: %hu; failed MTU: %hu)",
cpath->cop_path.np_path_id,
100. * (float) cpath->cop_path.np_pack_size / (float) mtu_ceiling,
cpath->cop_path.np_pack_size, ds->ds_failed_size);
conn->ifc_mflags &= ~MF_CHECK_MTU_PROBE;
return;
}
LSQ_DEBUG("MTU ratio: %hu / %hu = %.4f",
cpath->cop_path.np_pack_size, mtu_ceiling,
(float) cpath->cop_path.np_pack_size / (float) mtu_ceiling);
if (!ds->ds_failed_size && mtu_ceiling < 1500)
/* Try the largest ethernet MTU immediately */
probe_sz = mtu_ceiling;
else if (cpath->cop_path.np_pack_size * 2 >= mtu_ceiling)
/* Pick half-way point */
probe_sz = (mtu_ceiling + cpath->cop_path.np_pack_size) / 2;
else
probe_sz = cpath->cop_path.np_pack_size * 2;
/* XXX Changing np_pack_size is action at a distance */
saved_packet_sz = cpath->cop_path.np_pack_size;
cpath->cop_path.np_pack_size = probe_sz;
packet_out = lsquic_send_ctl_new_packet_out(&conn->ifc_send_ctl,
0, PNS_APP, CUR_NPATH(conn));
if (!packet_out)
goto restore_packet_size;
sz = conn->ifc_conn.cn_pf->pf_gen_ping_frame(
packet_out->po_data + packet_out->po_data_sz,
lsquic_packet_out_avail(packet_out));
if (sz < 0) {
ABORT_ERROR("gen_ping_frame failed");
goto restore_packet_size;
}
/* We don't record frame records for MTU probes as they are never
* resized, only discarded.
*/
lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, sz);
packet_out->po_frame_types |= 1 << QUIC_FRAME_PING;
avail = lsquic_packet_out_avail(packet_out);
if (avail)
{
memset(packet_out->po_data + packet_out->po_data_sz, 0, avail);
lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, avail);
packet_out->po_frame_types |= 1 << QUIC_FRAME_PADDING;
}
packet_out->po_flags |= PO_MTU_PROBE;
lsquic_send_ctl_scheduled_one(&conn->ifc_send_ctl, packet_out);
LSQ_DEBUG("generated MTU probe of %hu bytes in packet %"PRIu64,
cpath->cop_path.np_pack_size, packet_out->po_packno);
#ifndef NDEBUG
ds->ds_probe_sent = now;
#endif
ds->ds_probe_packno = packet_out->po_packno;
ds->ds_probed_size = probe_sz;
ds->ds_flags |= DS_PROBE_SENT;
++ds->ds_probe_count;
conn->ifc_mflags &= ~MF_CHECK_MTU_PROBE;
assert(!lsquic_alarmset_is_set(&conn->ifc_alset, AL_MTU_PROBE));
lsquic_alarmset_set(&conn->ifc_alset, AL_MTU_PROBE,
now + conn->ifc_enpub->enp_mtu_probe_timer);
restore_packet_size:
cpath->cop_path.np_pack_size = saved_packet_sz;
}
static void
ietf_full_conn_ci_mtu_probe_acked (struct lsquic_conn *lconn,
const struct lsquic_packet_out *packet_out)
{
struct ietf_full_conn *const conn = (struct ietf_full_conn *) lconn;
struct conn_path *cpath;
struct dplpmtud_state *ds;
unsigned char path_id;
path_id = packet_out->po_path->np_path_id;
cpath = &conn->ifc_paths[path_id];
ds = &cpath->cop_dplpmtud;
if (ds->ds_probe_packno != packet_out->po_packno)
{
LSQ_DEBUG("Acked MTU probe packet %"PRIu64" on path %hhu, but it is "
"old: discard", packet_out->po_packno, path_id);
return;
}
ds->ds_flags &= ~DS_PROBE_SENT;
ds->ds_probe_count = 0;
cpath->cop_path.np_pack_size = lsquic_packet_out_sent_sz(&conn->ifc_conn,
packet_out);
LSQ_INFO("update path %hhu MTU to %hu bytes", path_id,
cpath->cop_path.np_pack_size);
conn->ifc_mflags &= ~MF_CHECK_MTU_PROBE;
lsquic_alarmset_set(&conn->ifc_alset, AL_MTU_PROBE,
packet_out->po_sent + conn->ifc_enpub->enp_mtu_probe_timer);
LSQ_DEBUG("set alarm to %"PRIu64" usec ", packet_out->po_sent + conn->ifc_enpub->enp_mtu_probe_timer);
}
static void
mtu_probe_too_large (struct ietf_full_conn *conn,
const struct lsquic_packet_out *packet_out)
{
struct conn_path *cpath;
unsigned char path_id;
path_id = packet_out->po_path->np_path_id;
cpath = &conn->ifc_paths[path_id];
cpath->cop_dplpmtud.ds_failed_size
= lsquic_packet_out_sent_sz(&conn->ifc_conn, packet_out);
}
static void
ietf_full_conn_ci_retx_timeout (struct lsquic_conn *lconn)
{
struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn;
unsigned short pack_size;
struct conn_path *cpath;
int resize;
resize = 0;
for (cpath = conn->ifc_paths; cpath < conn->ifc_paths + N_PATHS; ++cpath)
if (cpath->cop_flags & COP_INITIALIZED)
{
pack_size = calc_base_packet_size(conn,
NP_IS_IPv6(&cpath->cop_path));
if (cpath->cop_path.np_pack_size > pack_size)
{
LSQ_DEBUG("RTO occurred: change packet size of path %hhu "
"to %hu bytes", cpath->cop_path.np_path_id, pack_size);
cpath->cop_path.np_pack_size = pack_size;
resize |= 1;
}
}
if (resize)
lsquic_send_ctl_resize(&conn->ifc_send_ctl);
else
LSQ_DEBUG("RTO occurred, but no MTUs to reset");
if (lsquic_send_ctl_ecn_turned_on(&conn->ifc_send_ctl))
{
LSQ_INFO("RTO occurred, disable ECN");
lsquic_send_ctl_disable_ecn(&conn->ifc_send_ctl);
if (lsquic_rechist_first(&conn->ifc_rechist[PNS_APP]))
{
LSQ_DEBUG("Send wrong ECN counts to peer so that it turns off "
"ECN as well");
memset(conn->ifc_ecn_counts_in[PNS_APP], 0,
sizeof(conn->ifc_ecn_counts_in[PNS_APP]));
conn->ifc_mflags |= MF_SEND_WRONG_COUNTS;
force_queueing_ack_app(conn);
conn->ifc_send_flags |= SF_SEND_PING;
}
}
}
static void
ietf_full_conn_ci_early_data_failed (struct lsquic_conn *lconn)
{
struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn;
LSQ_DEBUG("early data failed");
lsquic_send_ctl_stash_0rtt_packets(&conn->ifc_send_ctl);
}
static size_t
ietf_full_conn_ci_get_min_datagram_size (struct lsquic_conn *lconn)
{
struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn;
return (size_t) conn->ifc_min_dg_sz;
}
static int
ietf_full_conn_ci_set_min_datagram_size (struct lsquic_conn *lconn,
size_t new_size)
{
struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn;
const struct transport_params *const params =
lconn->cn_esf.i->esfi_get_peer_transport_params(lconn->cn_enc_session);
if (!(conn->ifc_flags & IFC_DATAGRAMS))
{
LSQ_WARN("datagrams are not enabled: cannot set minimum size");
return -1;
}
if (new_size > USHRT_MAX)
{
LSQ_DEBUG("min datagram size cannot be larger than %hu", USHRT_MAX);
return -1;
}
if (new_size > params->tp_numerics[TPI_MAX_DATAGRAM_FRAME_SIZE])
{
LSQ_DEBUG("maximum datagram frame size is %"PRIu64", cannot change it "
"to %zd", params->tp_numerics[TPI_MAX_DATAGRAM_FRAME_SIZE],
new_size);
return -1;
}
conn->ifc_min_dg_sz = new_size;
LSQ_DEBUG("set minimum datagram size to %zd bytes", new_size);
return 0;
}
/* Return true if datagram was written, false otherwise */
static int
write_datagram (struct ietf_full_conn *conn)
{
struct lsquic_packet_out *packet_out;
size_t need;
int w;
need = conn->ifc_conn.cn_pf->pf_datagram_frame_size(conn->ifc_min_dg_sz);
packet_out = get_writeable_packet(conn, need);
if (!packet_out)
return 0;
w = conn->ifc_conn.cn_pf->pf_gen_datagram_frame(
packet_out->po_data + packet_out->po_data_sz,
lsquic_packet_out_avail(packet_out), conn->ifc_min_dg_sz,
conn->ifc_max_dg_sz,
conn->ifc_enpub->enp_stream_if->on_dg_write, &conn->ifc_conn);
if (w < 0)
{
LSQ_DEBUG("could not generate DATAGRAM frame");
return 0;
}
if (0 != lsquic_packet_out_add_frame(packet_out, conn->ifc_pub.mm, 0,
QUIC_FRAME_DATAGRAM, packet_out->po_data_sz, w))
{
ABORT_ERROR("adding DATAGRAME frame to packet failed: %d", errno);
return 0;
}
packet_out->po_frame_types |= QUIC_FTBIT_DATAGRAM;
lsquic_send_ctl_incr_pack_sz(&conn->ifc_send_ctl, packet_out, w);
/* XXX The DATAGRAM frame should really be a regen. Do it when we
* no longer require these frame types to be at the beginning of the
* packet.
*/
return 1;
}
static enum tick_st
ietf_full_conn_ci_tick (struct lsquic_conn *lconn, lsquic_time_t now)
{
struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn;
int have_delayed_packets, s;
enum tick_st tick = 0;
unsigned n;
#define CLOSE_IF_NECESSARY() do { \
if (conn->ifc_flags & IFC_IMMEDIATE_CLOSE_FLAGS) \
{ \
tick |= immediate_close(conn); \
goto close_end; \
} \
} while (0)
#define RETURN_IF_OUT_OF_PACKETS() do { \
if (!lsquic_send_ctl_can_send(&conn->ifc_send_ctl)) \
{ \
if (0 == lsquic_send_ctl_n_scheduled(&conn->ifc_send_ctl)) \
{ \
LSQ_DEBUG("used up packet allowance, quiet now (line %d)", \
__LINE__); \
tick |= TICK_QUIET; \
} \
else \
{ \
LSQ_DEBUG("used up packet allowance, sending now (line %d)",\
__LINE__); \
tick |= TICK_SEND; \
} \
goto end; \
} \
} while (0)
CONN_STATS(n_ticks, 1);
if (conn->ifc_flags & IFC_HAVE_SAVED_ACK)
{
(void) /* If there is an error, we'll fail shortly */
process_ack(conn, &conn->ifc_ack, conn->ifc_saved_ack_received, now);
conn->ifc_flags &= ~IFC_HAVE_SAVED_ACK;
}
maybe_set_noprogress_alarm(conn, now);
lsquic_send_ctl_tick_in(&conn->ifc_send_ctl, now);
lsquic_send_ctl_set_buffer_stream_packets(&conn->ifc_send_ctl, 1);
CLOSE_IF_NECESSARY();
lsquic_alarmset_ring_expired(&conn->ifc_alset, now);
CLOSE_IF_NECESSARY();
/* To make things simple, only stream 1 is active until the handshake
* has been completed. This will be adjusted in the future: the client
* does not want to wait if it has the server information.
*/
if (conn->ifc_conn.cn_flags & LSCONN_HANDSHAKE_DONE)
process_streams_read_events(conn);
else
process_crypto_stream_read_events(conn);
CLOSE_IF_NECESSARY();
if (lsquic_send_ctl_pacer_blocked(&conn->ifc_send_ctl))
goto end_write;
if (conn->ifc_flags & IFC_FIRST_TICK)
{
conn->ifc_flags &= ~IFC_FIRST_TICK;
have_delayed_packets = 0;
}
else
/* If there are any scheduled packets at this point, it means that
* they were not sent during previous tick; in other words, they
* are delayed. When there are delayed packets, the only packet
* we sometimes add is a packet with an ACK frame, and we add it
* to the *front* of the queue.
*/
have_delayed_packets =
lsquic_send_ctl_maybe_squeeze_sched(&conn->ifc_send_ctl);
if (should_generate_ack(conn, IFC_ACK_QUEUED) ||
(!have_delayed_packets && maybe_queue_opp_ack(conn)))
{
if (have_delayed_packets)
lsquic_send_ctl_reset_packnos(&conn->ifc_send_ctl);
n = generate_ack_frame(conn, now);
CLOSE_IF_NECESSARY();
if (have_delayed_packets && n)
lsquic_send_ctl_ack_to_front(&conn->ifc_send_ctl, n);
}
if (have_delayed_packets)
{
/* The reason for not adding the other frames below to the packet
* carrying ACK frame generated when there are delayed packets is
* so that if the ACK packet itself is delayed, it can be dropped
* and replaced by new ACK packet. This way, we are never more
* than 1 packet over CWND.
*/
tick |= TICK_SEND;
goto end;
}
/* Try to fit MAX_DATA before checking if we have run out of room.
* If it does not fit, it will be tried next time around.
*/
if (lsquic_cfcw_fc_offsets_changed(&conn->ifc_pub.cfcw) ||
(conn->ifc_send_flags & SF_SEND_MAX_DATA))
{
conn->ifc_send_flags |= SF_SEND_MAX_DATA;
generate_max_data_frame(conn);
CLOSE_IF_NECESSARY();
}
if (conn->ifc_send_flags & SEND_WITH_FUNCS)
{
enum send send;
for (send = 0; send < N_SEND; ++send)
if (conn->ifc_send_flags & (1 << send) & SEND_WITH_FUNCS)
{
send_funcs[send](conn, now);
CLOSE_IF_NECESSARY();
}
}
if (conn->ifc_mflags & MF_CHECK_MTU_PROBE)
check_or_schedule_mtu_probe(conn, now);
n = lsquic_send_ctl_reschedule_packets(&conn->ifc_send_ctl);
if (n > 0)
CLOSE_IF_NECESSARY();
if (conn->ifc_conn.cn_flags & LSCONN_SEND_BLOCKED)
{
RETURN_IF_OUT_OF_PACKETS();
if (generate_blocked_frame(conn))
conn->ifc_conn.cn_flags &= ~LSCONN_SEND_BLOCKED;
}
if (!TAILQ_EMPTY(&conn->ifc_pub.sending_streams))
{
process_streams_ready_to_send(conn);
CLOSE_IF_NECESSARY();
}
lsquic_send_ctl_set_buffer_stream_packets(&conn->ifc_send_ctl, 0);
if (!(conn->ifc_conn.cn_flags & LSCONN_HANDSHAKE_DONE))
{
s = lsquic_send_ctl_schedule_buffered(&conn->ifc_send_ctl,
BPT_HIGHEST_PRIO);
conn->ifc_flags |= (s < 0) << IFC_BIT_ERROR;
if (0 == s)
process_crypto_stream_write_events(conn);
if (!(conn->ifc_mflags & MF_DOING_0RTT))
{
lsquic_send_ctl_maybe_app_limited(&conn->ifc_send_ctl,
CUR_NPATH(conn));
goto end_write;
}
}
maybe_conn_flush_special_streams(conn);
s = lsquic_send_ctl_schedule_buffered(&conn->ifc_send_ctl, BPT_HIGHEST_PRIO);
conn->ifc_flags |= (s < 0) << IFC_BIT_ERROR;
if (!write_is_possible(conn))
goto end_write;
while ((conn->ifc_mflags & MF_WANT_DATAGRAM_WRITE) && write_datagram(conn))
if (!write_is_possible(conn))
goto end_write;
if (!TAILQ_EMPTY(&conn->ifc_pub.write_streams))
{
process_streams_write_events(conn, 1);
if (!write_is_possible(conn))
goto end_write;
}
s = lsquic_send_ctl_schedule_buffered(&conn->ifc_send_ctl, BPT_OTHER_PRIO);
conn->ifc_flags |= (s < 0) << IFC_BIT_ERROR;
if (!write_is_possible(conn))
goto end_write;
if (!TAILQ_EMPTY(&conn->ifc_pub.write_streams))
process_streams_write_events(conn, 0);
lsquic_send_ctl_maybe_app_limited(&conn->ifc_send_ctl, CUR_NPATH(conn));
end_write:
if ((conn->ifc_flags & IFC_CLOSING) && conn_ok_to_close(conn))
{
LSQ_DEBUG("connection is OK to close");
conn->ifc_flags |= IFC_TICK_CLOSE;
if (!(conn->ifc_mflags & MF_CONN_CLOSE_PACK)
/* Generate CONNECTION_CLOSE frame if:
* ... this is a client and handshake was successful;
*/
&& (!(conn->ifc_flags & (IFC_SERVER|IFC_HSK_FAILED))
/* or: sent a GOAWAY frame;
*/
|| (conn->ifc_flags & IFC_GOAWAY_CLOSE)
/* or: we received CONNECTION_CLOSE and we are not a server
* that chooses not to send CONNECTION_CLOSE responses.
* From [draft-ietf-quic-transport-29]:
" An endpoint that receives a CONNECTION_CLOSE frame MAY send
" a single packet containing a CONNECTION_CLOSE frame before
" entering the draining state
*/
|| ((conn->ifc_flags & IFC_RECV_CLOSE)
&& !((conn->ifc_flags & IFC_SERVER)
&& conn->ifc_settings->es_silent_close))
/* or: we have packets to send. */
|| 0 != lsquic_send_ctl_n_scheduled(&conn->ifc_send_ctl))
)
{
RETURN_IF_OUT_OF_PACKETS();
generate_connection_close_packet(conn);
tick |= TICK_SEND|TICK_CLOSE;
}
else
tick |= TICK_CLOSE;
goto end;
}
if (0 == lsquic_send_ctl_n_scheduled(&conn->ifc_send_ctl))
{
if (conn->ifc_send_flags & SF_SEND_PING)
{
RETURN_IF_OUT_OF_PACKETS();
generate_ping_frame(conn, now);
CLOSE_IF_NECESSARY();
assert(lsquic_send_ctl_n_scheduled(&conn->ifc_send_ctl) != 0);
}
else
{
tick |= TICK_QUIET;
goto end;
}
}
else if (conn->ifc_ping_period)
{
lsquic_alarmset_unset(&conn->ifc_alset, AL_PING);
lsquic_send_ctl_sanity_check(&conn->ifc_send_ctl);
conn->ifc_send_flags &= ~SF_SEND_PING; /* It may have rung */
}
/* [draft-ietf-quic-transport-11] Section 7.9:
*
* The PING frame can be used to keep a connection alive when an
* application or application protocol wishes to prevent the connection
* from timing out. An application protocol SHOULD provide guidance
* about the conditions under which generating a PING is recommended.
* This guidance SHOULD indicate whether it is the client or the server
* that is expected to send the PING. Having both endpoints send PING
* frames without coordination can produce an excessive number of
* packets and poor performance.
*/
if (conn->ifc_ping_period
&& lsquic_hash_count(conn->ifc_pub.all_streams) > 0)
lsquic_alarmset_set(&conn->ifc_alset, AL_PING,
now + conn->ifc_ping_period);
tick |= TICK_SEND;
end:
service_streams(conn);
CLOSE_IF_NECESSARY();
close_end:
lsquic_send_ctl_set_buffer_stream_packets(&conn->ifc_send_ctl, 1);
lsquic_send_ctl_tick_out(&conn->ifc_send_ctl);
return tick;
}
static enum LSQUIC_CONN_STATUS
ietf_full_conn_ci_status (struct lsquic_conn *lconn, char *errbuf, size_t bufsz)
{
struct ietf_full_conn *const conn = (struct ietf_full_conn *) lconn;
size_t n;
/* Test the common case first: */
if (!(conn->ifc_flags & (IFC_ERROR
|IFC_TIMED_OUT
|IFC_ABORTED
|IFC_GOT_PRST
|IFC_HSK_FAILED
|IFC_CLOSING
|IFC_GOING_AWAY)))
{
if (lconn->cn_flags & LSCONN_PEER_GOING_AWAY)
return LSCONN_ST_PEER_GOING_AWAY;
else if (lconn->cn_flags & LSCONN_HANDSHAKE_DONE)
return LSCONN_ST_CONNECTED;
else
return LSCONN_ST_HSK_IN_PROGRESS;
}
if (errbuf && bufsz)
{
if (conn->ifc_errmsg)
{
n = bufsz < MAX_ERRMSG ? bufsz : MAX_ERRMSG;
strncpy(errbuf, conn->ifc_errmsg, n);
errbuf[n - 1] = '\0';
}
else
errbuf[0] = '\0';
}
if (conn->ifc_flags & IFC_ERROR)
return LSCONN_ST_ERROR;
if (conn->ifc_flags & IFC_TIMED_OUT)
return LSCONN_ST_TIMED_OUT;
if (conn->ifc_flags & IFC_ABORTED)
return LSCONN_ST_USER_ABORTED;
if (conn->ifc_flags & IFC_GOT_PRST)
return LSCONN_ST_RESET;
if (conn->ifc_flags & IFC_HSK_FAILED)
return LSCONN_ST_HSK_FAILURE;
if (conn->ifc_flags & IFC_CLOSING)
return LSCONN_ST_CLOSED;
assert(conn->ifc_flags & IFC_GOING_AWAY);
return LSCONN_ST_GOING_AWAY;
}
static void
ietf_full_conn_ci_stateless_reset (struct lsquic_conn *lconn)
{
struct ietf_full_conn *const conn = (struct ietf_full_conn *) lconn;
conn->ifc_flags |= IFC_GOT_PRST;
LSQ_INFO("stateless reset reported");
}
static struct lsquic_engine *
ietf_full_conn_ci_get_engine (struct lsquic_conn *lconn)
{
struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn;
return conn->ifc_enpub->enp_engine;
}
static unsigned
ietf_full_conn_ci_n_pending_streams (const struct lsquic_conn *lconn)
{
struct ietf_full_conn *const conn = (struct ietf_full_conn *) lconn;
return conn->ifc_n_delayed_streams;
}
static unsigned
ietf_full_conn_ci_n_avail_streams (const struct lsquic_conn *lconn)
{
struct ietf_full_conn *const conn = (struct ietf_full_conn *) lconn;
return avail_streams_count(conn, conn->ifc_flags & IFC_SERVER, SD_BIDI);
}
static int
handshake_done_or_doing_sess_resume (const struct ietf_full_conn *conn)
{
return (conn->ifc_conn.cn_flags & LSCONN_HANDSHAKE_DONE)
|| conn->ifc_conn.cn_esf_c->esf_is_sess_resume_enabled(
conn->ifc_conn.cn_enc_session);
}
static void
ietf_full_conn_ci_make_stream (struct lsquic_conn *lconn)
{
struct ietf_full_conn *const conn = (struct ietf_full_conn *) lconn;
if (handshake_done_or_doing_sess_resume(conn)
&& ietf_full_conn_ci_n_avail_streams(lconn) > 0)
{
if (0 != create_bidi_stream_out(conn))
ABORT_ERROR("could not create new stream: %s", strerror(errno));
}
else if (either_side_going_away(conn))
{
(void) conn->ifc_enpub->enp_stream_if->on_new_stream(
conn->ifc_enpub->enp_stream_if_ctx, NULL);
LSQ_DEBUG("going away: no streams will be initiated");
}
else
{
++conn->ifc_n_delayed_streams;
LSQ_DEBUG("delayed stream creation. Backlog size: %u",
conn->ifc_n_delayed_streams);
}
}
static void
ietf_full_conn_ci_internal_error (struct lsquic_conn *lconn,
const char *format, ...)
{
struct ietf_full_conn *const conn = (struct ietf_full_conn *) lconn;
LSQ_INFO("internal error reported");
ABORT_QUIETLY(0, TEC_INTERNAL_ERROR, "Internal error");
}
static void
ietf_full_conn_ci_abort_error (struct lsquic_conn *lconn, int is_app,
unsigned error_code, const char *fmt, ...)
{
struct ietf_full_conn *const conn = (struct ietf_full_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);
ABORT_QUIETLY(is_app, error_code, "%s", err_str);
}
static int
path_matches_local_sa (const struct network_path *path,
const struct sockaddr *local_sa)
{
return lsquic_sockaddr_eq(NP_LOCAL_SA(path), local_sa);
}
static const lsquic_cid_t *
ietf_full_conn_ci_get_log_cid (const struct lsquic_conn *lconn)
{
struct ietf_full_conn *const conn = (struct ietf_full_conn *) lconn;
if (lconn->cn_flags & LSCONN_SERVER)
{
if (CUR_DCID(conn)->len)
return CUR_DCID(conn);
else
return CN_SCID(lconn);
}
if (CN_SCID(lconn)->len)
return CN_SCID(lconn);
else
return CUR_DCID(conn);
}
static struct network_path *
ietf_full_conn_ci_get_path (struct lsquic_conn *lconn,
const struct sockaddr *sa)
{
struct ietf_full_conn *const conn = (struct ietf_full_conn *) lconn;
struct conn_path *copath;
if (NULL == sa || path_matches_local_sa(CUR_NPATH(conn), sa))
return CUR_NPATH(conn);
for (copath = conn->ifc_paths; copath < conn->ifc_paths
+ sizeof(conn->ifc_paths) / sizeof(conn->ifc_paths[0]); ++copath)
if ((conn->ifc_used_paths & (1 << (copath - conn->ifc_paths)))
&& path_matches_local_sa(&copath->cop_path, sa))
return &copath->cop_path;
return CUR_NPATH(conn);
}
static int
path_matches (const struct network_path *path,
const struct sockaddr *local_sa, const struct sockaddr *peer_sa)
{
return local_sa->sa_family == NP_LOCAL_SA(path)->sa_family
&& lsquic_sockaddr_eq(local_sa, NP_LOCAL_SA(path))
&& lsquic_sockaddr_eq(peer_sa, NP_PEER_SA(path));
}
static void
record_to_path (struct ietf_full_conn *conn, struct conn_path *copath, void *peer_ctx,
const struct sockaddr *local_sa, const struct sockaddr *peer_sa)
{
struct network_path *path;
size_t len;
char path_str[2][INET6_ADDRSTRLEN + sizeof(":65535")];
LSQ_DEBUG("record path %d: (%s - %s)", (int) (copath - conn->ifc_paths),
SA2STR(local_sa, path_str[0]), SA2STR(peer_sa, path_str[1]));
path = &copath->cop_path;
len = local_sa->sa_family == AF_INET ? sizeof(struct sockaddr_in)
: sizeof(struct sockaddr_in6);
memcpy(NP_LOCAL_SA(path), local_sa, len);
len = peer_sa->sa_family == AF_INET ? sizeof(struct sockaddr_in)
: sizeof(struct sockaddr_in6);
memcpy(NP_PEER_SA(path), peer_sa, len);
path->np_peer_ctx = peer_ctx;
}
static unsigned char
ietf_full_conn_ci_record_addrs (struct lsquic_conn *lconn, void *peer_ctx,
const struct sockaddr *local_sa, const struct sockaddr *peer_sa)
{
struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn;
struct network_path *path;
struct conn_path *copath, *first_unused, *first_unvalidated, *first_other,
*victim;
path = &conn->ifc_paths[conn->ifc_cur_path_id].cop_path;
if (path_matches(path, local_sa, peer_sa))
{
path->np_peer_ctx = peer_ctx;
return conn->ifc_cur_path_id;
}
first_unvalidated = NULL;
first_unused = NULL;
first_other = NULL;
for (copath = conn->ifc_paths; copath < conn->ifc_paths
+ sizeof(conn->ifc_paths) / sizeof(conn->ifc_paths[0]); ++copath)
{
if (conn->ifc_used_paths & (1 << (copath - conn->ifc_paths)))
{
if (path_matches(&copath->cop_path, local_sa, peer_sa))
{
copath->cop_path.np_peer_ctx = peer_ctx;
return copath - conn->ifc_paths;
}
if (!first_unvalidated
&& (0 == (copath->cop_flags & COP_VALIDATED)))
first_unvalidated = copath;
else if (!first_other)
first_other = copath;
}
else if (!first_unused)
first_unused = copath;
}
if (first_unused)
{
record_to_path(conn, first_unused, peer_ctx, local_sa, peer_sa);
if (0 == conn->ifc_used_paths && !(conn->ifc_flags & IFC_SERVER))
{
/* First path is considered valid immediately */
first_unused->cop_flags |= COP_VALIDATED;
maybe_enable_spin(conn, first_unused);
}
LSQ_DEBUG("record new path ID %d",
(int) (first_unused - conn->ifc_paths));
conn->ifc_used_paths |= 1 << (first_unused - conn->ifc_paths);
return first_unused - conn->ifc_paths;
}
if (first_unvalidated || first_other)
{
victim = first_unvalidated ? first_unvalidated : first_other;
record_to_path(conn, victim, peer_ctx, local_sa, peer_sa);
return victim - conn->ifc_paths;
}
return conn->ifc_cur_path_id;
}
static void
ietf_full_conn_ci_drop_crypto_streams (struct lsquic_conn *lconn)
{
struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn;
drop_crypto_streams(conn);
}
void
ietf_full_conn_ci_count_garbage (struct lsquic_conn *lconn, size_t garbage_sz)
{
struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn;
conn->ifc_pub.bytes_in = garbage_sz;
LSQ_DEBUG("count %zd bytes of garbage, new value: %u bytes", garbage_sz,
conn->ifc_pub.bytes_in);
}
#if LSQUIC_CONN_STATS
static const struct conn_stats *
ietf_full_conn_ci_get_stats (struct lsquic_conn *lconn)
{
struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn;
return &conn->ifc_stats;
}
#include "lsquic_cong_ctl.h"
static void
ietf_full_conn_ci_log_stats (struct lsquic_conn *lconn)
{
struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn;
struct batch_size_stats *const bs = &conn->ifc_enpub->enp_batch_size_stats;
struct conn_stats diff_stats;
uint64_t cwnd;
char cidstr[MAX_CID_LEN * 2 + 1];
if (!conn->ifc_last_stats)
{
conn->ifc_last_stats = calloc(1, sizeof(*conn->ifc_last_stats));
if (!conn->ifc_last_stats)
return;
LSQ_DEBUG("allocated last stats");
}
cwnd = conn->ifc_send_ctl.sc_ci->cci_get_cwnd(
conn->ifc_send_ctl.sc_cong_ctl);
lsquic_conn_stats_diff(&conn->ifc_stats, conn->ifc_last_stats, &diff_stats);
lsquic_logger_log1(LSQ_LOG_NOTICE, LSQLM_CONN_STATS,
"%s: ticks: %lu; cwnd: %"PRIu64"; conn flow: max: %"PRIu64
", avail: %"PRIu64"; packets: sent: %lu, lost: %lu, retx: %lu, rcvd: %lu"
"; batch: count: %u; min: %u; max: %u; avg: %.2f",
(lsquic_cid2str(LSQUIC_LOG_CONN_ID, cidstr), cidstr),
diff_stats.n_ticks, cwnd,
conn->ifc_pub.conn_cap.cc_max,
lsquic_conn_cap_avail(&conn->ifc_pub.conn_cap),
diff_stats.out.packets, diff_stats.out.lost_packets,
diff_stats.out.retx_packets, diff_stats.in.packets,
bs->count, bs->min, bs->max, bs->avg);
*conn->ifc_last_stats = conn->ifc_stats;
memset(bs, 0, sizeof(*bs));
}
#endif
#define IETF_FULL_CONN_FUNCS \
.ci_abort = ietf_full_conn_ci_abort, \
.ci_abort_error = ietf_full_conn_ci_abort_error, \
.ci_ack_snapshot = ietf_full_conn_ci_ack_snapshot, \
.ci_ack_rollback = ietf_full_conn_ci_ack_rollback, \
.ci_retire_cid = ietf_full_conn_ci_retire_cid, \
.ci_can_write_ack = ietf_full_conn_ci_can_write_ack, \
.ci_cancel_pending_streams = ietf_full_conn_ci_cancel_pending_streams, \
.ci_client_call_on_new = ietf_full_conn_ci_client_call_on_new, \
.ci_close = ietf_full_conn_ci_close, \
.ci_count_garbage = ietf_full_conn_ci_count_garbage, \
.ci_destroy = ietf_full_conn_ci_destroy, \
.ci_drain_time = ietf_full_conn_ci_drain_time, \
.ci_drop_crypto_streams = ietf_full_conn_ci_drop_crypto_streams, \
.ci_early_data_failed = ietf_full_conn_ci_early_data_failed, \
.ci_get_engine = ietf_full_conn_ci_get_engine, \
.ci_get_log_cid = ietf_full_conn_ci_get_log_cid, \
.ci_get_min_datagram_size= ietf_full_conn_ci_get_min_datagram_size, \
.ci_get_path = ietf_full_conn_ci_get_path, \
.ci_going_away = ietf_full_conn_ci_going_away, \
.ci_hsk_done = ietf_full_conn_ci_hsk_done, \
.ci_internal_error = ietf_full_conn_ci_internal_error, \
.ci_is_push_enabled = ietf_full_conn_ci_is_push_enabled, \
.ci_is_tickable = ietf_full_conn_ci_is_tickable, \
.ci_make_stream = ietf_full_conn_ci_make_stream, \
.ci_mtu_probe_acked = ietf_full_conn_ci_mtu_probe_acked, \
.ci_n_avail_streams = ietf_full_conn_ci_n_avail_streams, \
.ci_n_pending_streams = ietf_full_conn_ci_n_pending_streams, \
.ci_next_tick_time = ietf_full_conn_ci_next_tick_time, \
.ci_packet_in = ietf_full_conn_ci_packet_in, \
.ci_push_stream = ietf_full_conn_ci_push_stream, \
.ci_record_addrs = ietf_full_conn_ci_record_addrs, \
.ci_report_live = ietf_full_conn_ci_report_live, \
.ci_retx_timeout = ietf_full_conn_ci_retx_timeout, \
.ci_set_min_datagram_size= ietf_full_conn_ci_set_min_datagram_size, \
.ci_status = ietf_full_conn_ci_status, \
.ci_stateless_reset = ietf_full_conn_ci_stateless_reset, \
.ci_tick = ietf_full_conn_ci_tick, \
.ci_tls_alert = ietf_full_conn_ci_tls_alert, \
.ci_want_datagram_write = ietf_full_conn_ci_want_datagram_write, \
.ci_write_ack = ietf_full_conn_ci_write_ack
static const struct conn_iface ietf_full_conn_iface = {
IETF_FULL_CONN_FUNCS,
.ci_next_packet_to_send = ietf_full_conn_ci_next_packet_to_send,
.ci_packet_not_sent = ietf_full_conn_ci_packet_not_sent,
.ci_packet_sent = ietf_full_conn_ci_packet_sent,
.ci_packet_too_large = ietf_full_conn_ci_packet_too_large,
#if LSQUIC_CONN_STATS
.ci_get_stats = ietf_full_conn_ci_get_stats,
.ci_log_stats = ietf_full_conn_ci_log_stats,
#endif
};
static const struct conn_iface *ietf_full_conn_iface_ptr =
&ietf_full_conn_iface;
static const struct conn_iface ietf_full_conn_prehsk_iface = {
IETF_FULL_CONN_FUNCS,
.ci_next_packet_to_send = ietf_full_conn_ci_next_packet_to_send_pre_hsk,
.ci_packet_not_sent = ietf_full_conn_ci_packet_not_sent_pre_hsk,
.ci_packet_sent = ietf_full_conn_ci_packet_sent_pre_hsk,
#if LSQUIC_CONN_STATS
.ci_get_stats = ietf_full_conn_ci_get_stats,
.ci_log_stats = ietf_full_conn_ci_log_stats,
#endif
};
static const struct conn_iface *ietf_full_conn_prehsk_iface_ptr =
&ietf_full_conn_prehsk_iface;
static void
on_cancel_push_client (void *ctx, uint64_t push_id)
{
struct ietf_full_conn *const conn = ctx;
EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "Received CANCEL_PUSH(%"PRIu64")",
push_id);
if (conn->ifc_u.cli.ifcli_flags & IFCLI_PUSH_ENABLED)
{
ABORT_QUIETLY(1, HEC_ID_ERROR, "received CANCEL_PUSH but push is "
"not enabled");
return;
}
if (push_id > conn->ifc_u.cli.ifcli_max_push_id)
{
ABORT_QUIETLY(1, HEC_ID_ERROR, "received CANCEL_PUSH with ID=%"PRIu64
", which is greater than the maximum Push ID=%"PRIu64, push_id,
conn->ifc_u.cli.ifcli_max_push_id);
return;
}
#if CLIENT_PUSH_SUPPORT
LSQ_WARN("TODO: support for CANCEL_PUSH is not implemented");
#endif
}
/* Careful: this puts promise */
static void
cancel_push_promise (struct ietf_full_conn *conn, struct push_promise *promise)
{
LSQ_DEBUG("cancel promise %"PRIu64, promise->pp_id);
/* Remove promise from hash to prevent multiple cancellations */
lsquic_hash_erase(conn->ifc_pub.u.ietf.promises, &promise->pp_hash_id);
/* But let stream dtor free the promise object as sm_promise may yet
* be used by the stream in some ways.
*/
lsquic_stream_shutdown_internal(promise->pp_pushed_stream);
if (0 != lsquic_hcso_write_cancel_push(&conn->ifc_hcso, promise->pp_id))
ABORT_WARN("cannot write CANCEL_PUSH");
lsquic_pp_put(promise, conn->ifc_pub.u.ietf.promises);
}
static void
on_cancel_push_server (void *ctx, uint64_t push_id)
{
struct ietf_full_conn *const conn = ctx;
struct lsquic_hash_elem *el;
struct push_promise *promise;
EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "Received CANCEL_PUSH(%"PRIu64")",
push_id);
if (push_id >= conn->ifc_u.ser.ifser_next_push_id)
{
ABORT_QUIETLY(1, HEC_ID_ERROR, "received CANCEL_PUSH with ID=%"PRIu64
", which is greater than the maximum Push ID ever generated by "
"this connection", push_id);
return;
}
el = lsquic_hash_find(conn->ifc_pub.u.ietf.promises, &push_id,
sizeof(push_id));
if (!el)
{
LSQ_DEBUG("push promise %"PRIu64" not found", push_id);
return;
}
promise = lsquic_hashelem_getdata(el);
cancel_push_promise(conn, promise);
}
static void
on_max_push_id_client (void *ctx, uint64_t push_id)
{
struct ietf_full_conn *const conn = ctx;
ABORT_QUIETLY(1, HEC_FRAME_UNEXPECTED, "client does not expect the server "
"to send MAX_PUSH_ID frame");
}
static void
on_max_push_id (void *ctx, uint64_t push_id)
{
struct ietf_full_conn *const conn = ctx;
if (!(conn->ifc_u.ser.ifser_flags & IFSER_MAX_PUSH_ID)
|| push_id > conn->ifc_u.ser.ifser_max_push_id)
{
conn->ifc_u.ser.ifser_max_push_id = push_id;
conn->ifc_u.ser.ifser_flags |= IFSER_MAX_PUSH_ID;
LSQ_DEBUG("set MAX_PUSH_ID to %"PRIu64, push_id);
}
else if (push_id < conn->ifc_u.ser.ifser_max_push_id)
ABORT_QUIETLY(1, HEC_ID_ERROR, "MAX_PUSH_ID reduced from "
"%"PRIu64" to %"PRIu64, conn->ifc_u.ser.ifser_max_push_id, push_id);
else
LSQ_DEBUG("ignore repeated value of MAX_PUSH_ID=%"PRIu64, push_id);
}
static void
on_settings_frame (void *ctx)
{
struct ietf_full_conn *const conn = ctx;
unsigned dyn_table_size, max_risked_streams;
LSQ_DEBUG("SETTINGS frame");
if (conn->ifc_flags & IFC_HAVE_PEER_SET)
{
ABORT_WARN("second incoming SETTING frame on HTTP control stream");
return;
}
conn->ifc_flags |= IFC_HAVE_PEER_SET;
dyn_table_size = MIN(conn->ifc_settings->es_qpack_enc_max_size,
conn->ifc_peer_hq_settings.header_table_size);
max_risked_streams = MIN(conn->ifc_settings->es_qpack_enc_max_blocked,
conn->ifc_peer_hq_settings.qpack_blocked_streams);
if (conn->ifc_settings->es_qpack_experiment == 2)
randomize_qpack_settings(conn, "encoder", &dyn_table_size,
&max_risked_streams);
if (conn->ifc_qeh.qeh_exp_rec)
{
conn->ifc_qeh.qeh_exp_rec->qer_peer_max_size
= conn->ifc_peer_hq_settings.header_table_size;
conn->ifc_qeh.qeh_exp_rec->qer_used_max_size = dyn_table_size;
conn->ifc_qeh.qeh_exp_rec->qer_peer_max_blocked
= conn->ifc_peer_hq_settings.qpack_blocked_streams;
conn->ifc_qeh.qeh_exp_rec->qer_used_max_blocked = max_risked_streams;
}
if (0 != lsquic_qeh_settings(&conn->ifc_qeh,
conn->ifc_peer_hq_settings.header_table_size,
dyn_table_size, max_risked_streams, conn->ifc_flags & IFC_SERVER))
ABORT_WARN("could not initialize QPACK encoder handler");
if (avail_streams_count(conn, conn->ifc_flags & IFC_SERVER, SD_UNI) > 0)
{
if (0 != create_qenc_stream_out(conn))
ABORT_WARN("cannot create outgoing QPACK encoder stream");
}
else
{
queue_streams_blocked_frame(conn, SD_UNI);
LSQ_DEBUG("cannot create QPACK encoder stream due to unidir limit");
}
maybe_create_delayed_streams(conn);
}
static void
on_setting (void *ctx, uint64_t setting_id, uint64_t value)
{
struct ietf_full_conn *const conn = ctx;
switch (setting_id)
{
case HQSID_QPACK_BLOCKED_STREAMS:
LSQ_DEBUG("Peer's SETTINGS_QPACK_BLOCKED_STREAMS=%"PRIu64, value);
conn->ifc_peer_hq_settings.qpack_blocked_streams = value;
break;
case HQSID_QPACK_MAX_TABLE_CAPACITY:
LSQ_DEBUG("Peer's SETTINGS_QPACK_MAX_TABLE_CAPACITY=%"PRIu64, value);
conn->ifc_peer_hq_settings.header_table_size = value;
break;
case HQSID_MAX_HEADER_LIST_SIZE:
LSQ_DEBUG("Peer's SETTINGS_MAX_HEADER_LIST_SIZE=%"PRIu64"; "
"we ignore it", value);
break;
default:
LSQ_DEBUG("received unknown SETTING 0x%"PRIX64"=0x%"PRIX64
"; ignore it", setting_id, value);
break;
case 2: /* HTTP/2 SETTINGS_ENABLE_PUSH */
case 3: /* HTTP/2 SETTINGS_MAX_CONCURRENT_STREAMS */
case 4: /* HTTP/2 SETTINGS_INITIAL_WINDOW_SIZE */
case 5: /* HTTP/2 SETTINGS_MAX_FRAME_SIZE */
/* [draft-ietf-quic-http-30] Section 7.2.4.1 */
ABORT_QUIETLY(1, HEC_SETTINGS_ERROR, "unexpected HTTP/2 setting "
"%"PRIu64, setting_id);
break;
}
}
static void
on_goaway_server_27 (void *ctx, uint64_t stream_id)
{
struct ietf_full_conn *const conn = ctx;
ABORT_QUIETLY(1, HEC_FRAME_UNEXPECTED,
"client should not send GOAWAY frames");
}
static void
on_goaway_client_28 (void *ctx, uint64_t stream_id)
{
struct ietf_full_conn *const conn = ctx;
struct lsquic_stream *stream;
struct lsquic_hash_elem *el;
enum stream_id_type sit;
sit = stream_id & SIT_MASK;
if (sit != SIT_BIDI_CLIENT)
{
ABORT_QUIETLY(1, HEC_ID_ERROR,
"stream ID %"PRIu64" in GOAWAY frame", stream_id);
return;
}
if (conn->ifc_conn.cn_flags & LSCONN_PEER_GOING_AWAY)
{
LSQ_DEBUG("ignore duplicate GOAWAY frame");
return;
}
conn->ifc_conn.cn_flags |= LSCONN_PEER_GOING_AWAY;
LSQ_DEBUG("received GOAWAY frame, last good stream ID: %"PRIu64, stream_id);
if (conn->ifc_enpub->enp_stream_if->on_goaway_received)
conn->ifc_enpub->enp_stream_if->on_goaway_received(&conn->ifc_conn);
for (el = lsquic_hash_first(conn->ifc_pub.all_streams); el;
el = lsquic_hash_next(conn->ifc_pub.all_streams))
{
stream = lsquic_hashelem_getdata(el);
if (stream->id >= stream_id
&& (stream->id & SIT_MASK) == SIT_BIDI_CLIENT)
{
lsquic_stream_received_goaway(stream);
}
}
}
static void
on_goaway_client (void *ctx, uint64_t stream_id)
{
struct ietf_full_conn *const conn = ctx;
struct lsquic_stream *stream;
struct lsquic_hash_elem *el;
enum stream_id_type sit;
sit = stream_id & SIT_MASK;
if (sit != SIT_BIDI_CLIENT)
{
ABORT_QUIETLY(1, HEC_ID_ERROR,
"stream ID %"PRIu64" in GOAWAY frame", stream_id);
return;
}
LSQ_DEBUG("received GOAWAY frame, last good stream ID: %"PRIu64, stream_id);
if (conn->ifc_conn.cn_flags & LSCONN_PEER_GOING_AWAY)
{
if (stream_id == conn->ifc_u.cli.ifcli_min_goaway_stream_id)
{
LSQ_DEBUG("ignore duplicate GOAWAY frame");
return;
}
if (stream_id > conn->ifc_u.cli.ifcli_min_goaway_stream_id)
{
ABORT_QUIETLY(1, HEC_ID_ERROR,
"stream ID %"PRIu64" is larger than one already seen in a "
"previous GOAWAY frame, %"PRIu64, stream_id,
conn->ifc_u.cli.ifcli_min_goaway_stream_id);
return;
}
}
else
{
conn->ifc_u.cli.ifcli_min_goaway_stream_id = stream_id;
conn->ifc_conn.cn_flags |= LSCONN_PEER_GOING_AWAY;
if (conn->ifc_enpub->enp_stream_if->on_goaway_received)
conn->ifc_enpub->enp_stream_if->on_goaway_received(&conn->ifc_conn);
}
for (el = lsquic_hash_first(conn->ifc_pub.all_streams); el;
el = lsquic_hash_next(conn->ifc_pub.all_streams))
{
stream = lsquic_hashelem_getdata(el);
if (stream->id >= stream_id
&& (stream->id & SIT_MASK) == SIT_BIDI_CLIENT)
{
lsquic_stream_received_goaway(stream);
}
}
}
static void
on_goaway_server (void *ctx, uint64_t max_push_id)
{
struct ietf_full_conn *const conn = ctx;
struct push_promise *promise;
struct lsquic_hash_elem *el;
EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "Received GOAWAY(%"PRIu64")",
max_push_id);
for (el = lsquic_hash_first(conn->ifc_pub.u.ietf.promises); el;
el = lsquic_hash_next(conn->ifc_pub.u.ietf.promises))
{
promise = lsquic_hashelem_getdata(el);
if (promise->pp_id >= max_push_id)
cancel_push_promise(conn, promise);
}
}
static void
on_priority_update_client (void *ctx, enum hq_frame_type frame_type,
uint64_t id, const char *pfv, size_t pfv_sz)
{
struct ietf_full_conn *const conn = ctx;
if (conn->ifc_pii == &ext_prio_iter_if)
ABORT_QUIETLY(1, HEC_FRAME_UNEXPECTED, "Frame type %u is not "
"expected to be sent by the server", (unsigned) frame_type);
/* else ignore */
}
/* This should not happen often, so do not bother to optimize memory. */
static int
buffer_priority_update (struct ietf_full_conn *conn,
lsquic_stream_id_t stream_id, const struct lsquic_ext_http_prio *ehp)
{
struct buffered_priority_update *bpu;
struct lsquic_hash_elem *el;
if (!conn->ifc_bpus)
{
conn->ifc_bpus = lsquic_hash_create();
if (!conn->ifc_bpus)
{
ABORT_ERROR("cannot allocate BPUs hash");
return -1;
}
goto insert_new;
}
el = lsquic_hash_find(conn->ifc_bpus, &stream_id, sizeof(stream_id));
if (el)
{
bpu = lsquic_hashelem_getdata(el);
bpu->ehp = *ehp;
return 0;
}
insert_new:
bpu = malloc(sizeof(*bpu));
if (!bpu)
{
ABORT_ERROR("cannot allocate BPU");
return -1;
}
bpu->hash_el.qhe_flags = 0;
bpu->stream_id = stream_id;
bpu->ehp = *ehp;
if (!lsquic_hash_insert(conn->ifc_bpus, &bpu->stream_id,
sizeof(bpu->stream_id), bpu, &bpu->hash_el))
{
free(bpu);
ABORT_ERROR("cannot insert BPU");
return -1;
}
return 0;
}
static void
on_priority_update_server (void *ctx, enum hq_frame_type frame_type,
uint64_t id, const char *pfv, size_t pfv_sz)
{
struct ietf_full_conn *const conn = ctx;
struct lsquic_hash_elem *el;
struct push_promise *promise;
struct lsquic_stream *stream;
enum stream_id_type sit;
struct lsquic_ext_http_prio ehp;
if (conn->ifc_pii != &ext_prio_iter_if)
{
LSQ_DEBUG("Ignore PRIORITY_UPDATE frame");
return;
}
if (frame_type == HQFT_PRIORITY_UPDATE_STREAM)
{
sit = id & SIT_MASK;
if (sit != SIT_BIDI_CLIENT)
{
ABORT_QUIETLY(1, HEC_ID_ERROR, "PRIORITY_UPDATE for non-request "
"stream");
return;
}
if (id >= conn->ifc_max_allowed_stream_id[sit])
{
ABORT_QUIETLY(1, HEC_ID_ERROR, "PRIORITY_UPDATE for non-existing "
"stream %"PRIu64" exceeds allowed max of %"PRIu64,
id, conn->ifc_max_allowed_stream_id[sit]);
return;
}
stream = find_stream_by_id(conn, id);
if (!stream && conn_is_stream_closed(conn, id))
{
LSQ_DEBUG("stream %"PRIu64" closed, ignore PRIORITY_UPDATE", id);
return;
}
}
else
{
if (id >= conn->ifc_u.ser.ifser_next_push_id)
{
ABORT_QUIETLY(1, HEC_ID_ERROR, "received PRIORITY_UPDATE with "
"ID=%"PRIu64", which is greater than the maximum Push ID "
"ever generated by this connection", id);
return;
}
el = lsquic_hash_find(conn->ifc_pub.u.ietf.promises, &id, sizeof(id));
if (!el)
{
LSQ_DEBUG("push promise %"PRIu64" not found, ignore "
"PRIORITY_UPDATE", id);
return;
}
promise = lsquic_hashelem_getdata(el);
stream = promise->pp_pushed_stream;
assert(stream);
}
ehp = (struct lsquic_ext_http_prio) {
.urgency = LSQUIC_DEF_HTTP_URGENCY,
.incremental = LSQUIC_DEF_HTTP_INCREMENTAL,
};
if (pfv_sz)
{
switch (lsquic_http_parse_pfv(pfv, pfv_sz, NULL, &ehp,
(char *) conn->ifc_pub.mm->acki,
sizeof(*conn->ifc_pub.mm->acki)))
{
case 0:
LSQ_DEBUG("Parsed PFV `%.*s' correctly", (int) pfv_sz, pfv);
break;
case -2: /* Out of memory, ignore */
LSQ_INFO("Ignore PFV `%.*s': out of memory", (int) pfv_sz, pfv);
return;
default:
LSQ_INFO("connection error due to invalid PFV `%.*s'",
(int) pfv_sz, pfv);
/* From the draft (between versions 1 and 2):
" Failure to parse the Priority Field Value MUST be treated
" as a connection error of type FRAME_ENCODING_ERROR.
*/
ABORT_QUIETLY(1, HEC_FRAME_ERROR, "cannot parse Priority Field "
"Value in PRIORITY_UPDATE frame");
return;
}
}
else
{ /* Empty PFV means "use defaults" */ }
if (stream)
(void) lsquic_stream_set_http_prio(stream, &ehp);
else
{
assert(frame_type == HQFT_PRIORITY_UPDATE_STREAM);
if (0 == buffer_priority_update(conn, id, &ehp))
LSQ_INFO("buffered priority update for stream %"PRIu64"; "
"urgency: %hhu, incremental: %hhd", id, ehp.urgency,
ehp.incremental);
}
}
static void
on_unexpected_frame (void *ctx, uint64_t frame_type)
{
struct ietf_full_conn *const conn = ctx;
ABORT_QUIETLY(1, HEC_FRAME_UNEXPECTED, "Frame type %"PRIu64" is not "
"allowed on the control stream", frame_type);
}
static const struct hcsi_callbacks hcsi_callbacks_server_27 =
{
.on_cancel_push = on_cancel_push_server,
.on_max_push_id = on_max_push_id,
.on_settings_frame = on_settings_frame,
.on_setting = on_setting,
.on_goaway = on_goaway_server_27,
.on_unexpected_frame = on_unexpected_frame,
.on_priority_update = on_priority_update_server,
};
static const struct hcsi_callbacks hcsi_callbacks_client_27 =
{
.on_cancel_push = on_cancel_push_client,
.on_max_push_id = on_max_push_id_client,
.on_settings_frame = on_settings_frame,
.on_setting = on_setting,
.on_goaway = on_goaway_client_28 /* sic */,
.on_unexpected_frame = on_unexpected_frame,
.on_priority_update = on_priority_update_client,
};
static const struct hcsi_callbacks hcsi_callbacks_server_28 =
{
.on_cancel_push = on_cancel_push_server,
.on_max_push_id = on_max_push_id,
.on_settings_frame = on_settings_frame,
.on_setting = on_setting,
.on_goaway = on_goaway_server /* sic */,
.on_unexpected_frame = on_unexpected_frame,
.on_priority_update = on_priority_update_server,
};
static const struct hcsi_callbacks hcsi_callbacks_client_28 =
{
.on_cancel_push = on_cancel_push_client,
.on_max_push_id = on_max_push_id_client,
.on_settings_frame = on_settings_frame,
.on_setting = on_setting,
.on_goaway = on_goaway_client_28,
.on_unexpected_frame = on_unexpected_frame,
.on_priority_update = on_priority_update_client,
};
static const struct hcsi_callbacks hcsi_callbacks_server_29 =
{
.on_cancel_push = on_cancel_push_server,
.on_max_push_id = on_max_push_id,
.on_settings_frame = on_settings_frame,
.on_setting = on_setting,
.on_goaway = on_goaway_server,
.on_unexpected_frame = on_unexpected_frame,
.on_priority_update = on_priority_update_server,
};
static const struct hcsi_callbacks hcsi_callbacks_client_29 =
{
.on_cancel_push = on_cancel_push_client,
.on_max_push_id = on_max_push_id_client,
.on_settings_frame = on_settings_frame,
.on_setting = on_setting,
.on_goaway = on_goaway_client,
.on_unexpected_frame = on_unexpected_frame,
.on_priority_update = on_priority_update_client,
};
static lsquic_stream_ctx_t *
hcsi_on_new (void *stream_if_ctx, struct lsquic_stream *stream)
{
struct ietf_full_conn *const conn = (void *) stream_if_ctx;
const struct hcsi_callbacks *callbacks;
conn->ifc_stream_hcsi = stream;
switch ((!!(conn->ifc_flags & IFC_SERVER) << 8) | conn->ifc_conn.cn_version)
{
case (0 << 8) | LSQVER_ID27:
callbacks = &hcsi_callbacks_client_27;
break;
case (1 << 8) | LSQVER_ID27:
callbacks = &hcsi_callbacks_server_27;
break;
case (0 << 8) | LSQVER_ID28:
callbacks = &hcsi_callbacks_client_28;
break;
case (1 << 8) | LSQVER_ID28:
callbacks = &hcsi_callbacks_server_28;
break;
case (0 << 8) | LSQVER_ID29:
case (0 << 8) | LSQVER_ID32:
callbacks = &hcsi_callbacks_client_29;
break;
default:
assert(0);
/* fallthru */
case (1 << 8) | LSQVER_ID29:
case (1 << 8) | LSQVER_ID32:
callbacks = &hcsi_callbacks_server_29;
break;
}
lsquic_hcsi_reader_init(&conn->ifc_hcsi.reader, &conn->ifc_conn,
callbacks, conn);
lsquic_stream_wantread(stream, 1);
return stream_if_ctx;
}
struct feed_hcsi_ctx
{
struct ietf_full_conn *conn;
int s;
};
static size_t
feed_hcsi_reader (void *ctx, const unsigned char *buf, size_t bufsz, int fin)
{
struct feed_hcsi_ctx *feed_ctx = ctx;
struct ietf_full_conn *conn = feed_ctx->conn;
feed_ctx->s = lsquic_hcsi_reader_feed(&conn->ifc_hcsi.reader, buf, bufsz);
return bufsz;
}
static void
hcsi_on_read (struct lsquic_stream *stream, lsquic_stream_ctx_t *ctx)
{
struct ietf_full_conn *const conn = (void *) ctx;
struct lsquic_conn *const lconn = &conn->ifc_conn;
struct feed_hcsi_ctx feed_ctx = { conn, 0, };
ssize_t nread;
nread = lsquic_stream_readf(stream, feed_hcsi_reader, &feed_ctx);
LSQ_DEBUG("fed %zd bytes to HTTP control stream reader, status=%d",
nread, feed_ctx.s);
if (nread < 0)
{
lsquic_stream_wantread(stream, 0);
ABORT_WARN("error reading from HTTP control stream");
}
else if (nread == 0)
{
lsquic_stream_wantread(stream, 0);
LSQ_INFO("control stream closed by peer: abort connection");
lconn->cn_if->ci_abort_error(lconn, 1,
HEC_CLOSED_CRITICAL_STREAM, "control stream closed");
}
else if (feed_ctx.s != 0)
{
lsquic_stream_wantread(stream, 0);
ABORT_WARN("error processing HTTP control stream");
}
}
static void
hcsi_on_write (struct lsquic_stream *stream, lsquic_stream_ctx_t *ctx)
{
assert(0);
}
static void
hcsi_on_close (struct lsquic_stream *stream, lsquic_stream_ctx_t *ctx)
{
struct ietf_full_conn *const conn = (void *) ctx;
conn->ifc_stream_hcsi = NULL;
}
static const struct lsquic_stream_if hcsi_if =
{
.on_new_stream = hcsi_on_new,
.on_read = hcsi_on_read,
.on_write = hcsi_on_write,
.on_close = hcsi_on_close,
};
static void
apply_uni_stream_class (struct ietf_full_conn *conn,
struct lsquic_stream *stream, uint64_t stream_type)
{
switch (stream_type)
{
case HQUST_CONTROL:
if (!conn->ifc_stream_hcsi)
{
LSQ_DEBUG("Incoming HTTP control stream ID: %"PRIu64,
stream->id);
lsquic_stream_set_stream_if(stream, &hcsi_if, conn);
}
else
{
ABORT_QUIETLY(1, HEC_STREAM_CREATION_ERROR,
"Control stream %"PRIu64" already exists: cannot create "
"second control stream %"PRIu64, conn->ifc_stream_hcsi->id,
stream->id);
lsquic_stream_close(stream);
}
break;
case HQUST_QPACK_ENC:
if (!lsquic_qdh_has_enc_stream(&conn->ifc_qdh))
{
LSQ_DEBUG("Incoming QPACK encoder stream ID: %"PRIu64,
stream->id);
lsquic_stream_set_stream_if(stream, lsquic_qdh_enc_sm_in_if,
&conn->ifc_qdh);
}
else
{
ABORT_QUIETLY(1, HEC_STREAM_CREATION_ERROR,
"Incoming QPACK encoder stream %"PRIu64" already exists: "
"cannot create second stream %"PRIu64,
conn->ifc_qdh.qdh_enc_sm_in->id, stream->id);
lsquic_stream_close(stream);
}
break;
case HQUST_QPACK_DEC:
if (!lsquic_qeh_has_dec_stream(&conn->ifc_qeh))
{
LSQ_DEBUG("Incoming QPACK decoder stream ID: %"PRIu64,
stream->id);
lsquic_stream_set_stream_if(stream, lsquic_qeh_dec_sm_in_if,
&conn->ifc_qeh);
}
else
{
ABORT_QUIETLY(1, HEC_STREAM_CREATION_ERROR,
"Incoming QPACK decoder stream %"PRIu64" already exists: "
"cannot create second stream %"PRIu64,
conn->ifc_qeh.qeh_dec_sm_in->id, stream->id);
lsquic_stream_close(stream);
}
break;
case HQUST_PUSH:
if (conn->ifc_flags & IFC_SERVER)
{
ABORT_QUIETLY(1, HEC_STREAM_CREATION_ERROR,
"clients can't open push streams");
}
else
{
LSQ_DEBUG("Refuse push stream %"PRIu64, stream->id);
maybe_schedule_ss_for_stream(conn, stream->id,
HEC_REQUEST_CANCELLED);
}
lsquic_stream_close(stream);
break;
default:
LSQ_DEBUG("unknown unidirectional stream %"PRIu64 " of type %"PRIu64
", will send STOP_SENDING and close", stream->id, stream_type);
/* XXX This approach may be risky, as it assumes that the peer updates
* its flow control window correctly. The safe way to do it is to
* create a stream and wait for RESET_STREAM frame. This is not an
* issue in the normal case, as the server does not allow the peer to
* create more than 3 unidirectional streams.
*/
maybe_schedule_ss_for_stream(conn, stream->id,
HEC_STREAM_CREATION_ERROR);
lsquic_stream_close(stream);
break;
}
}
static lsquic_stream_ctx_t *
unicla_on_new (void *stream_if_ctx, struct lsquic_stream *stream)
{
lsquic_stream_wantread(stream, 1);
stream->sm_uni_type_state.pos = 0;
return stream_if_ctx;
}
struct unicla_ctx
{
struct varint_read_state *state;
enum { UC_MORE, UC_ERROR, UC_DONE, } status;
};
static const char *const unicla_stat2str[] = {
[UC_ERROR] = "UC_ERROR", [UC_MORE] = "UC_MORE", [UC_DONE] = "UC_DONE",
};
static size_t
unicla_readf (void *ctx, const unsigned char *begin, size_t sz, int fin)
{
struct unicla_ctx *const unicla_ctx = ctx;
const unsigned char *buf = begin;
int s;
switch (unicla_ctx->status)
{
case UC_MORE:
s = lsquic_varint_read_nb(&buf, begin + sz, unicla_ctx->state);
if (s == 0)
unicla_ctx->status = UC_DONE;
else if (fin)
unicla_ctx->status = UC_ERROR;
return buf - begin;
case UC_DONE:
return 0;
default:
return sz;
}
}
static void
unicla_on_read (struct lsquic_stream *stream, lsquic_stream_ctx_t *ctx)
{
struct ietf_full_conn *const conn = (void *) ctx;
struct unicla_ctx unicla_ctx = { .state = &stream->sm_uni_type_state,
.status = UC_MORE, };
ssize_t nr;
nr = lsquic_stream_readf(stream, unicla_readf, &unicla_ctx);
LSQ_DEBUG("unistream classifier read %zd byte%.*s, status: %s", nr,
nr != 1, "s", unicla_stat2str[unicla_ctx.status]);
if (nr > 0)
{
if (unicla_ctx.status == UC_DONE)
apply_uni_stream_class(conn, stream, unicla_ctx.state->val);
else if (unicla_ctx.status == UC_ERROR)
goto unexpected_fin;
/* else: do nothing */
}
else if (nr < 0) /* This should never happen */
{
LSQ_WARN("unicla: cannot read from stream %"PRIu64, stream->id);
lsquic_stream_close(stream);
}
else
{
unexpected_fin:
LSQ_INFO("unicla: unexpected FIN while reading stream type from "
"stream %"PRIu64, stream->id);
lsquic_stream_close(stream);
}
}
static void
unicla_on_write (struct lsquic_stream *stream, lsquic_stream_ctx_t *ctx)
{
assert(0);
}
static void
unicla_on_close (struct lsquic_stream *stream, lsquic_stream_ctx_t *ctx)
{
}
static const struct lsquic_stream_if unicla_if =
{
.on_new_stream = unicla_on_new,
.on_read = unicla_on_read,
.on_write = unicla_on_write,
.on_close = unicla_on_close,
};
static const struct lsquic_stream_if *unicla_if_ptr = &unicla_if;
typedef char dcid_elem_fits_in_128_bytes[sizeof(struct dcid_elem) <= 128 ? 1 : - 1];