/* Copyright (c) 2017 - 2019 LiteSpeed Technologies Inc. See LICENSE. */ /* * lsquic_mini_conn.c -- Mini connection. * * Mini connection is only used in server mode -- this assumption is relied * upon by the code in this file. * * The purpose of this connection is to process incoming handshakes using * minimal amount of resources until we confirm that the client is sending * valid data. Here, we only process Stream 1 data; other packets are * spooled, if necessary. When mini connection is promoted to full * connection, the state, including spooled incoming packets, is transferred * to the full connection. * * Note that mini connections do not retransmit lost packets. This is to * minimize the effect of magnification attacks. Clients like Chrome and * Opera fall back to using TCP if QUIC handshake times out. */ #include #include #include #include #include #include #include "lsquic.h" #include "lsquic_int_types.h" #include "lsquic_hash.h" #include "lsquic_conn.h" #include "lsquic_rtt.h" #include "lsquic_mini_conn.h" #include "lsquic_mm.h" #include "lsquic_malo.h" #include "lsquic_packet_common.h" #include "lsquic_packet_gquic.h" #include "lsquic_packet_ietf.h" #include "lsquic_packet_in.h" #include "lsquic_packet_out.h" #include "lsquic_util.h" #include "lsquic_str.h" #include "lsquic_enc_sess.h" #include "lsquic_parse.h" #include "lsquic_engine_public.h" #include "lsquic_sfcw.h" #include "lsquic_varint.h" #include "lsquic_hq.h" #include "lsquic_varint.h" #include "lsquic_stream.h" #include "lsquic_rechist.h" #include "lsquic_ev_log.h" #include "lsquic_qtags.h" #include "lsquic_attq.h" #include "lsquic_alarmset.h" #define LSQUIC_LOGGER_MODULE LSQLM_MINI_CONN #define LSQUIC_LOG_CONN_ID lsquic_conn_log_cid(&mc->mc_conn) #include "lsquic_logger.h" static const struct conn_iface mini_conn_iface_standard; static const struct conn_iface mini_conn_iface_standard_Q050; #if LSQUIC_KEEP_MINICONN_HISTORY static void mchist_append (struct mini_conn *mc, enum miniconn_history_event mh_event) { enum miniconn_history_event prev_event; mchist_idx_t idx; int plus; idx = (mc->mc_hist_idx - 1) & MCHIST_MASK; plus = MCHE_PLUS == mc->mc_hist_buf[ idx ]; idx = (idx - plus) & MCHIST_MASK; prev_event = mc->mc_hist_buf[ idx ]; if (!(prev_event == mh_event && plus)) { if (prev_event == mh_event) mh_event = MCHE_PLUS; mc->mc_hist_buf[ MCHIST_MASK & mc->mc_hist_idx++ ] = mh_event; } } # define MCHIST_APPEND(mc, event) mchist_append(mc, event) #else # define MCHIST_APPEND(mc, event) #endif static void process_deferred_packets (struct mini_conn *mc); /* If this is not true, highest_bit_set() may be broken */ typedef char packno_set_is_unsigned_long[ (sizeof(unsigned long long) == sizeof(mconn_packno_set_t)) - 1]; static unsigned highest_bit_set (unsigned long long sz) { #if __GNUC__ unsigned clz = __builtin_clzll(sz); return 63 - clz; #else unsigned long y; unsigned n; n = 64; y = sz >> 32; if (y) { n -= 32; sz = y; } y = sz >> 16; if (y) { n -= 16; sz = y; } y = sz >> 8; if (y) { n -= 8; sz = y; } y = sz >> 4; if (y) { n -= 4; sz = y; } y = sz >> 2; if (y) { n -= 2; sz = y; } y = sz >> 1; if (y) return 63 - n + 2; return 63 - n + sz; #endif } static unsigned lowest_bit_set (unsigned v) { #if __GNUC__ return __builtin_ctz(v); #else unsigned n; n = 0; if (0 == (v & ((1 << 16) - 1))) { n += 16; v >>= 16; } if (0 == (v & ((1 << 8) - 1))) { n += 8; v >>= 8; } if (0 == (v & ((1 << 4) - 1))) { n += 4; v >>= 4; } if (0 == (v & ((1 << 2) - 1))) { n += 2; v >>= 2; } if (0 == (v & ((1 << 1) - 1))) { n += 1; } return n; #endif } static int is_handshake_stream_id (const struct mini_conn *conn, lsquic_stream_id_t stream_id) { return conn->mc_conn.cn_version < LSQVER_050 && stream_id == 1; } static void mini_destroy_packet (struct mini_conn *mc, struct lsquic_packet_out *packet_out) { lsquic_packet_out_destroy(packet_out, mc->mc_enpub, mc->mc_path.np_peer_ctx); } static int packet_in_is_ok (enum lsquic_version version, const struct lsquic_packet_in *packet_in) { size_t min_size; if (packet_in->pi_data_sz > GQUIC_MAX_PACKET_SZ) { LSQ_LOG1(LSQ_LOG_DEBUG, "incoming packet too large: %hu bytes", packet_in->pi_data_sz); return 0; } if ((1 << version) & LSQUIC_GQUIC_HEADER_VERSIONS) /* This is a very lax number, it allows the server to send * 64 * 200 = 12KB of output (REJ and SHLO). */ min_size = 200; else /* Chrome enforces 1200-byte minimum initial packet limit */ min_size = IQUIC_MIN_INIT_PACKET_SZ; if (packet_in->pi_data_sz < min_size) { LSQ_LOG1(LSQ_LOG_DEBUG, "incoming packet too small: %hu bytes", packet_in->pi_data_sz); return 0; } return 1; } lsquic_conn_t * mini_conn_new (struct lsquic_engine_public *enp, const struct lsquic_packet_in *packet_in, enum lsquic_version version) { struct mini_conn *mc; const struct conn_iface *conn_iface; if (!packet_in_is_ok(version, packet_in)) return NULL; switch (version) { case LSQVER_050: conn_iface = &mini_conn_iface_standard_Q050; break; default: conn_iface = &mini_conn_iface_standard; break; } mc = lsquic_malo_get(enp->enp_mm.malo.mini_conn); if (!mc) { LSQ_LOG1(LSQ_LOG_WARN, "cannot allocate mini connection: %s", strerror(errno)); return NULL; } memset(mc, 0, sizeof(*mc)); TAILQ_INIT(&mc->mc_deferred); TAILQ_INIT(&mc->mc_packets_in); TAILQ_INIT(&mc->mc_packets_out); mc->mc_enpub = enp; mc->mc_created = packet_in->pi_received; mc->mc_path.np_pack_size = packet_in->pi_data_sz; mc->mc_conn.cn_cces = mc->mc_cces; mc->mc_conn.cn_cces_mask = 1; mc->mc_conn.cn_n_cces = sizeof(mc->mc_cces) / sizeof(mc->mc_cces[0]); mc->mc_conn.cn_version = version; mc->mc_conn.cn_pf = select_pf_by_ver(version); mc->mc_conn.cn_esf_c = select_esf_common_by_ver(version); mc->mc_conn.cn_esf.g = select_esf_gquic_by_ver(version); mc->mc_conn.cn_cid = packet_in->pi_conn_id; mc->mc_conn.cn_flags = LSCONN_MINI | LSCONN_SERVER; mc->mc_conn.cn_if = conn_iface; LSQ_DEBUG("created mini connection object"); MCHIST_APPEND(mc, MCHE_CREATED); return &mc->mc_conn; } static int in_acked_range (const struct ack_info *acki, lsquic_packno_t n) /* This is a copy */ { int in_range = 0; unsigned i; for (i = 0; i < acki->n_ranges; ++i) in_range += acki->ranges[i].high >= n && acki->ranges[i].low <= n; return in_range > 0; } static void take_rtt_sample (struct mini_conn *mc, const lsquic_packet_out_t *packet_out, lsquic_time_t now, lsquic_time_t lack_delta) { assert(packet_out->po_sent); lsquic_time_t measured_rtt = now - packet_out->po_sent; if (lack_delta < measured_rtt) { lsquic_rtt_stats_update(&mc->mc_rtt_stats, measured_rtt, lack_delta); LSQ_DEBUG("srtt: %"PRIu64" usec, var: %"PRIu64, lsquic_rtt_stats_get_srtt(&mc->mc_rtt_stats), lsquic_rtt_stats_get_rttvar(&mc->mc_rtt_stats)); } } static unsigned process_ack_frame (struct mini_conn *mc, lsquic_packet_in_t *packet_in, const unsigned char *p, size_t len) { int parsed_len; int n_newly_acked; unsigned n; lsquic_packet_out_t *packet_out, *next; struct ack_info *acki; lsquic_packno_t packno; lsquic_time_t warn_time; char buf[200]; acki = mc->mc_enpub->enp_mm.acki; parsed_len = mc->mc_conn.cn_pf->pf_parse_ack_frame(p, len, acki, 0); if (parsed_len < 0) return 0; if (empty_ack_frame(acki)) { LSQ_DEBUG("Ignore empty ACK frame"); return parsed_len; } if (packet_in->pi_packno <= mc->mc_max_ack_packno) { LSQ_DEBUG("Ignore old ack (max %u)", mc->mc_max_ack_packno); return parsed_len; } /* Verify ACK frame and update list of acked packet numbers: */ for (n = 0; n < acki->n_ranges; ++n) for (packno = acki->ranges[n].low; packno <= acki->ranges[n].high; ++packno) if (packno > MINICONN_MAX_PACKETS || 0 == (MCONN_PACKET_MASK(packno) & mc->mc_sent_packnos)) { warn_time = lsquic_time_now(); if (0 == mc->mc_enpub->enp_last_warning[WT_ACKPARSE_MINI] || mc->mc_enpub->enp_last_warning[WT_ACKPARSE_MINI] + WARNING_INTERVAL < warn_time) { mc->mc_enpub->enp_last_warning[WT_ACKPARSE_MINI] = warn_time; lsquic_hexdump(p, len, buf, sizeof(buf)); LSQ_WARN("packet %"PRIu64" was never sent; ACK " "frame:\n%s", packno, buf); } else LSQ_DEBUG("packet %"PRIu64" was never sent", packno); MCHIST_APPEND(mc, MCHE_UNSENT_ACKED); return 0; } else mc->mc_acked_packnos |= MCONN_PACKET_MASK(packno); EV_LOG_ACK_FRAME_IN(LSQUIC_LOG_CONN_ID, acki); n_newly_acked = 0; for (packet_out = TAILQ_FIRST(&mc->mc_packets_out); packet_out; packet_out = next) { next = TAILQ_NEXT(packet_out, po_next); if (in_acked_range(acki, packet_out->po_packno)) { ++n_newly_acked; LSQ_DEBUG("Got ACK for packet %"PRIu64, packet_out->po_packno); if (packet_out->po_packno == largest_acked(acki)) take_rtt_sample(mc, packet_out, packet_in->pi_received, acki->lack_delta); TAILQ_REMOVE(&mc->mc_packets_out, packet_out, po_next); mini_destroy_packet(mc, packet_out); } } if (n_newly_acked > 0) mc->mc_hsk_count = 0; return parsed_len; } static unsigned process_blocked_frame (struct mini_conn *mc, lsquic_packet_in_t *packet_in, const unsigned char *p, size_t len) { lsquic_stream_id_t stream_id; int parsed_len; parsed_len = mc->mc_conn.cn_pf->pf_parse_blocked_frame(p, len, &stream_id); if (parsed_len < 0) return 0; EV_LOG_BLOCKED_FRAME_IN(LSQUIC_LOG_CONN_ID, stream_id); LSQ_DEBUG("Peer reports stream %"PRIu64" as blocked", stream_id); return parsed_len; } static mconn_packno_set_t drop_packets_out (struct mini_conn *mc) { struct lsquic_packet_out *packet_out; mconn_packno_set_t in_flight = 0; while ((packet_out = TAILQ_FIRST(&mc->mc_packets_out))) { TAILQ_REMOVE(&mc->mc_packets_out, packet_out, po_next); if (packet_out->po_flags & PO_SENT) in_flight |= MCONN_PACKET_MASK(packet_out->po_packno); mini_destroy_packet(mc, packet_out); } return in_flight; } static unsigned process_connection_close_frame (struct mini_conn *mc, lsquic_packet_in_t *packet_in, const unsigned char *p, size_t len) { uint64_t error_code; uint16_t reason_len; uint8_t reason_off; int parsed_len; (void) drop_packets_out(mc); parsed_len = mc->mc_conn.cn_pf->pf_parse_connect_close_frame(p, len, NULL, &error_code, &reason_len, &reason_off); if (parsed_len < 0) return 0; mc->mc_error_code = (uint64_t) error_code; EV_LOG_CONNECTION_CLOSE_FRAME_IN(LSQUIC_LOG_CONN_ID, error_code, (int) reason_len, (const char *) p + reason_off); if (error_code != 25 /* No recent network activity */ && error_code != 62 /* An active session exists for the given IP */ && error_code != 27 ) /* Write failed with error: -142 (Unknown error)*/ { LSQ_WARN("Received CONNECTION_CLOSE frame (code: %"PRIu64"; reason: %.*s)", error_code, (int) reason_len, (const char *) p + reason_off); } MCHIST_APPEND(mc, MCHE_CONN_CLOSE); return 0; /* This shuts down the connection */ } static unsigned process_goaway_frame (struct mini_conn *mc, lsquic_packet_in_t *packet_in, const unsigned char *p, size_t len) { lsquic_stream_id_t stream_id; uint32_t error_code; uint16_t reason_length; const char *reason; int parsed_len; parsed_len = mc->mc_conn.cn_pf->pf_parse_goaway_frame(p, len, &error_code, &stream_id, &reason_length, &reason); if (parsed_len < 0) return 0; EV_LOG_GOAWAY_FRAME_IN(LSQUIC_LOG_CONN_ID, error_code, stream_id, reason_length, reason); LSQ_DEBUG("received GOAWAY frame, last good stream ID: %"PRIu64", " "error code: 0x%X, reason: `%.*s'", stream_id, error_code, reason_length, reason); if (stream_id != 0) /* This is odd. We warn: */ LSQ_WARN("stream ID is %"PRIu64" in GOAWAY frame", stream_id); mc->mc_conn.cn_flags |= LSCONN_PEER_GOING_AWAY; return parsed_len; } static unsigned process_invalid_frame (struct mini_conn *mc, lsquic_packet_in_t *packet_in, const unsigned char *p, size_t len) { LSQ_INFO("invalid frame"); MCHIST_APPEND(mc, MCHE_INVALID_FRAME); return 0; } static unsigned 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 mini_conn *mc, lsquic_packet_in_t *packet_in, const unsigned char *p, size_t len) { len = (size_t) count_zero_bytes(p, len); EV_LOG_PADDING_FRAME_IN(LSQUIC_LOG_CONN_ID, len); return len; } static unsigned process_ping_frame (struct mini_conn *mc, lsquic_packet_in_t *packet_in, const unsigned char *p, size_t len) { EV_LOG_PING_FRAME_IN(LSQUIC_LOG_CONN_ID); return 1; } static unsigned process_rst_stream_frame (struct mini_conn *mc, lsquic_packet_in_t *packet_in, const unsigned char *p, size_t len) { lsquic_stream_id_t stream_id; uint64_t offset, error_code; int parsed_len; parsed_len = mc->mc_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 (is_handshake_stream_id(mc, stream_id)) { LSQ_INFO("handshake stream reset, closing connection"); return 0; } else return parsed_len; } static unsigned process_stop_waiting_frame (struct mini_conn *mc, lsquic_packet_in_t *packet_in, const unsigned char *p, size_t len) { lsquic_packno_t least; enum packno_bits bits = lsquic_packet_in_packno_bits(packet_in); int parsed_len; parsed_len = mc->mc_conn.cn_pf->pf_parse_stop_waiting_frame(p, len, packet_in->pi_packno, bits, &least); if (parsed_len < 0) return 0; EV_LOG_STOP_WAITING_FRAME_IN(LSQUIC_LOG_CONN_ID, least); LSQ_DEBUG("Got STOP_WAITING frame, least unacked: %"PRIu64, least); if (least > MINICONN_MAX_PACKETS) return 0; else { mc->mc_cutoff = least; return parsed_len; } } static unsigned process_stream_frame (struct mini_conn *mc, lsquic_packet_in_t *packet_in, const unsigned char *p, size_t len) { stream_frame_t stream_frame; int parsed_len; parsed_len = mc->mc_conn.cn_pf->pf_parse_stream_frame(p, len, &stream_frame); if (parsed_len < 0) 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); if (is_handshake_stream_id(mc, stream_frame.stream_id)) { if (packet_in->pi_flags & PI_HSK_STREAM) { /* This is not supported for simplicity. The spec recommends * not putting more than one stream frame from the same stream * into a single packet. If this changes and clients actually * do that, we can revisit this code. */ LSQ_INFO("two handshake stream frames in single incoming packet"); MCHIST_APPEND(mc, MCHE_2HSK_1STREAM); return 0; } if (stream_frame.data_frame.df_offset >= mc->mc_read_off) { packet_in->pi_flags |= PI_HSK_STREAM; packet_in->pi_hsk_stream = p - packet_in->pi_data; mc->mc_flags |= MC_HAVE_NEW_HSK; MCHIST_APPEND(mc, MCHE_NEW_HSK); if (0 == stream_frame.data_frame.df_offset) /* First CHLO message: update maximum packet size */ mc->mc_path.np_pack_size = packet_in->pi_data_sz; } else { LSQ_DEBUG("drop duplicate frame"); MCHIST_APPEND(mc, MCHE_DUP_HSK); } } return parsed_len; } static unsigned process_crypto_frame (struct mini_conn *mc, struct lsquic_packet_in *packet_in, const unsigned char *p, size_t len) { stream_frame_t stream_frame; int parsed_len; parsed_len = mc->mc_conn.cn_pf->pf_parse_crypto_frame(p, len, &stream_frame); if (parsed_len < 0) return 0; EV_LOG_CRYPTO_FRAME_IN(LSQUIC_LOG_CONN_ID, &stream_frame, lsquic_packet_in_enc_level(packet_in)); LSQ_DEBUG("Got CRYPTO frame at encryption level %s", lsquic_enclev2str[lsquic_packet_in_enc_level(packet_in)]); if (packet_in->pi_flags & PI_HSK_STREAM) { /* This is not supported for simplicity: assume a single CRYPTO frame * per packet. If this changes, we can revisit this code. */ LSQ_INFO("two handshake stream frames in single incoming packet"); MCHIST_APPEND(mc, MCHE_2HSK_1STREAM); return 0; } if (stream_frame.data_frame.df_offset >= mc->mc_read_off) { packet_in->pi_flags |= PI_HSK_STREAM; packet_in->pi_hsk_stream = p - packet_in->pi_data; mc->mc_flags |= MC_HAVE_NEW_HSK; MCHIST_APPEND(mc, MCHE_NEW_HSK); if (0 == stream_frame.data_frame.df_offset) /* First CHLO message: update maximum packet size */ mc->mc_path.np_pack_size = packet_in->pi_data_sz; } else { LSQ_DEBUG("drop duplicate frame"); MCHIST_APPEND(mc, MCHE_DUP_HSK); } return parsed_len; } static unsigned process_window_update_frame (struct mini_conn *mc, lsquic_packet_in_t *packet_in, const unsigned char *p, size_t len) { lsquic_stream_id_t stream_id; uint64_t offset; int parsed_len; parsed_len = mc->mc_conn.cn_pf->pf_parse_window_update_frame(p, len, &stream_id, &offset); if (parsed_len < 0) return 0; EV_LOG_WINDOW_UPDATE_FRAME_IN(LSQUIC_LOG_CONN_ID, stream_id, offset); if (is_handshake_stream_id(mc, stream_id)) /* This should not happen: why would the client send us WINDOW_UPDATE * on stream 1? */ LSQ_WARN("client sent WINDOW_UPDATE for handshake stream, " "offset %"PRIu64, offset); return parsed_len; } typedef unsigned (*process_frame_f)( struct mini_conn *, lsquic_packet_in_t *, const unsigned char *p, size_t); static process_frame_f const process_frames[N_QUIC_FRAMES] = { [QUIC_FRAME_ACK] = process_ack_frame, [QUIC_FRAME_BLOCKED] = process_blocked_frame, [QUIC_FRAME_CONNECTION_CLOSE] = process_connection_close_frame, [QUIC_FRAME_CRYPTO] = process_crypto_frame, [QUIC_FRAME_GOAWAY] = process_goaway_frame, [QUIC_FRAME_INVALID] = process_invalid_frame, [QUIC_FRAME_PADDING] = process_padding_frame, [QUIC_FRAME_PING] = process_ping_frame, [QUIC_FRAME_RST_STREAM] = process_rst_stream_frame, [QUIC_FRAME_STOP_WAITING] = process_stop_waiting_frame, [QUIC_FRAME_STREAM] = process_stream_frame, [QUIC_FRAME_WINDOW_UPDATE] = process_window_update_frame, }; static unsigned process_packet_frame (struct mini_conn *mc, lsquic_packet_in_t *packet_in, const unsigned char *p, size_t len) { enum quic_frame_type type = mc->mc_conn.cn_pf->pf_parse_frame_type(p[0]); packet_in->pi_frame_types |= 1 << type; return process_frames[type](mc, packet_in, p, len); } static void record_largest_recv (struct mini_conn *mc, lsquic_time_t t) { if (t < mc->mc_created) { LSQ_WARN("largest received predates creation"); return; } t -= mc->mc_created; mc->mc_largest_recv[0] = t; mc->mc_largest_recv[1] = t >> 8; mc->mc_largest_recv[2] = t >> 16; LSQ_DEBUG("recorded largest received timestamp as %"PRIu64" usec since " "creation", t); } static enum dec_packin conn_decrypt_packet (struct mini_conn *conn, lsquic_packet_in_t *packet_in) { return conn->mc_conn.cn_esf_c->esf_decrypt_packet( conn->mc_conn.cn_enc_session, conn->mc_enpub, &conn->mc_conn, packet_in); } /* PRP: Process Regular Packet */ enum proc_rp { PRP_KEEP, PRP_DEFER, PRP_DROP, PRP_ERROR, }; static enum proc_rp conn_decrypt_packet_or (struct mini_conn *mc, struct lsquic_packet_in *packet_in) { if (DECPI_OK == conn_decrypt_packet(mc, packet_in)) { MCHIST_APPEND(mc, MCHE_DECRYPTED); return PRP_KEEP; } else if (mc->mc_conn.cn_esf.g->esf_have_key_gt_one( mc->mc_conn.cn_enc_session)) { LSQ_INFO("could not decrypt packet: drop"); mc->mc_dropped_packnos |= MCONN_PACKET_MASK(packet_in->pi_packno); MCHIST_APPEND(mc, MCHE_UNDECR_DROP); return PRP_DROP; } else if ((packet_in->pi_flags & PI_OWN_DATA) || 0 == lsquic_conn_copy_and_release_pi_data(&mc->mc_conn, mc->mc_enpub, packet_in)) { assert(packet_in->pi_flags & PI_OWN_DATA); LSQ_INFO("could not decrypt packet: defer"); mc->mc_deferred_packnos |= MCONN_PACKET_MASK(packet_in->pi_packno); MCHIST_APPEND(mc, MCHE_UNDECR_DEFER); return PRP_DEFER; } else { MCHIST_APPEND(mc, MCHE_ENOMEM); return PRP_ERROR; /* Memory allocation must have failed */ } } static enum proc_rp process_regular_packet (struct mini_conn *mc, lsquic_packet_in_t *packet_in) { const unsigned char *p, *pend; enum proc_rp prp; unsigned len; /* Decrypt packet if necessary */ if (0 == (packet_in->pi_flags & PI_DECRYPTED)) { prp = conn_decrypt_packet_or(mc, packet_in); if (prp != PRP_KEEP) return prp; } /* Update receive history before processing the packet: if there is an * error, the connection is terminated and recording this packet number * is helpful when it is printed along with other diagnostics in dtor. */ if (0 == mc->mc_received_packnos || packet_in->pi_packno > highest_bit_set(mc->mc_received_packnos) + 1) record_largest_recv(mc, packet_in->pi_received); mc->mc_received_packnos |= MCONN_PACKET_MASK(packet_in->pi_packno); /* Parse and process frames */ 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(mc, packet_in, p, pend - p); if (len > 0) p += len; else { if (mc->mc_conn.cn_pf->pf_parse_frame_type(p[0]) != QUIC_FRAME_CONNECTION_CLOSE) LSQ_WARN("error parsing frame: packno %"PRIu64"; sz: %u; type: " "0x%X", packet_in->pi_packno, packet_in->pi_data_sz, p[0]); MCHIST_APPEND(mc, MCHE_EFRAME); return PRP_ERROR; } } mc->mc_flags |= MC_GEN_ACK; return PRP_KEEP; } struct hsk_chunk { lsquic_packet_in_t *hsk_packet_in; const unsigned char *hsk_data; unsigned hsk_off; unsigned hsk_sz; }; static int compare_hsk_chunks (const void *ap, const void *bp) { const struct hsk_chunk *a = ap; const struct hsk_chunk *b = bp; return (a->hsk_off > b->hsk_off) - (b->hsk_off > a->hsk_off); } struct mini_stream_ctx { const unsigned char *buf; size_t bufsz; size_t off; }; static int mini_stream_has_data (const struct mini_stream_ctx *ms_ctx) { return ms_ctx->off < ms_ctx->bufsz; } static size_t mini_stream_read (void *stream, void *buf, size_t len, int *reached_fin) { struct mini_stream_ctx *ms_ctx = stream; size_t avail = ms_ctx->bufsz - ms_ctx->off; if (avail < len) len = avail; memcpy(buf, ms_ctx->buf + ms_ctx->off, len); ms_ctx->off += len; *reached_fin = 0; return len; } /* Wrapper to throw out reached_fin */ static size_t mini_stream_read_for_crypto (void *stream, void *buf, size_t len) { size_t retval; int reached_fin; retval = mini_stream_read(stream, buf, len, &reached_fin); return retval; } static size_t mini_stream_size (void *stream) { struct mini_stream_ctx *ms_ctx = stream; size_t avail = ms_ctx->bufsz - ms_ctx->off; return avail; } static int mini_stream_fin (void *stream) { /* There is never a FIN on the handshake stream */ return 0; } static lsquic_packno_t next_packno (struct mini_conn *mc) { if (mc->mc_cur_packno < MINICONN_MAX_PACKETS) { return ++mc->mc_cur_packno; } else { if (!(mc->mc_flags & MC_OO_PACKNOS)) { MCHIST_APPEND(mc, MCHE_OUT_OF_PACKNOS); mc->mc_flags |= MC_OO_PACKNOS; LSQ_DEBUG("ran out of outgoing packet numbers"); } return MINICONN_MAX_PACKETS + 1; } } static lsquic_packet_out_t * allocate_packet_out (struct mini_conn *mc, const unsigned char *nonce) { lsquic_packet_out_t *packet_out; lsquic_packno_t packno; packno = next_packno(mc); if (packno > MINICONN_MAX_PACKETS) { LSQ_DEBUG("ran out of outgoing packet numbers, won't allocate packet"); return NULL; } packet_out = lsquic_packet_out_new(&mc->mc_enpub->enp_mm, NULL, 1, &mc->mc_conn, GQUIC_PACKNO_LEN_1, NULL, nonce, &mc->mc_path); if (!packet_out) { LSQ_WARN("could not allocate packet: %s", strerror(errno)); return NULL; } packet_out->po_loss_chain = packet_out; packet_out->po_packno = packno; packet_out->po_flags |= PO_MINI; if (mc->mc_flags & MC_HAVE_SHLO) { packet_out->po_flags |= PO_HELLO; packet_out->po_header_type = HETY_0RTT; } if (mc->mc_conn.cn_version >= LSQVER_050) { if (nonce) packet_out->po_header_type = HETY_0RTT; else packet_out->po_header_type = HETY_INITIAL; } lsquic_packet_out_set_pns(packet_out, PNS_APP); TAILQ_INSERT_TAIL(&mc->mc_packets_out, packet_out, po_next); LSQ_DEBUG("allocated packet #%"PRIu64", nonce: %d", packno, !!nonce); MCHIST_APPEND(mc, MCHE_NEW_PACKET_OUT); EV_LOG_PACKET_CREATED(LSQUIC_LOG_CONN_ID, packet_out); return packet_out; } static struct lsquic_packet_out * to_packet_pre_Q050 (struct mini_conn *mc, struct mini_stream_ctx *ms_ctx, const unsigned char *nonce) { struct lsquic_packet_out *packet_out; size_t cur_off; int len; packet_out = allocate_packet_out(mc, nonce); if (!packet_out) return NULL; cur_off = ms_ctx->off; len = mc->mc_conn.cn_pf->pf_gen_stream_frame( packet_out->po_data + packet_out->po_data_sz, lsquic_packet_out_avail(packet_out), 1, mc->mc_write_off, mini_stream_fin(ms_ctx), mini_stream_size(ms_ctx), mini_stream_read, ms_ctx); if (len < 0) { LSQ_WARN("cannot generate STREAM frame (avail: %u)", lsquic_packet_out_avail(packet_out)); return NULL; } mc->mc_write_off += ms_ctx->off - cur_off; EV_LOG_GENERATED_STREAM_FRAME(LSQUIC_LOG_CONN_ID, mc->mc_conn.cn_pf, packet_out->po_data + packet_out->po_data_sz, len); packet_out->po_data_sz += len; packet_out->po_frame_types |= 1 << QUIC_FRAME_STREAM; if (0 == lsquic_packet_out_avail(packet_out)) packet_out->po_flags |= PO_STREAM_END; return packet_out; } static struct lsquic_packet_out * to_packet_Q050plus (struct mini_conn *mc, struct mini_stream_ctx *ms_ctx, const unsigned char *nonce) { struct lsquic_packet_out *packet_out; size_t cur_off; int len; if (nonce && !(mc->mc_flags & MC_WR_OFF_RESET)) { mc->mc_write_off = 0; mc->mc_flags |= MC_WR_OFF_RESET; } packet_out = allocate_packet_out(mc, nonce); if (!packet_out) return NULL; cur_off = ms_ctx->off; len = mc->mc_conn.cn_pf->pf_gen_crypto_frame( packet_out->po_data + packet_out->po_data_sz, lsquic_packet_out_avail(packet_out), mc->mc_write_off, mini_stream_size(ms_ctx), mini_stream_read_for_crypto, ms_ctx); if (len < 0) { LSQ_WARN("cannot generate CRYPTO frame (avail: %u)", lsquic_packet_out_avail(packet_out)); return NULL; } mc->mc_write_off += ms_ctx->off - cur_off; EV_LOG_GENERATED_CRYPTO_FRAME(LSQUIC_LOG_CONN_ID, mc->mc_conn.cn_pf, packet_out->po_data + packet_out->po_data_sz, len); packet_out->po_data_sz += len; packet_out->po_frame_types |= 1 << QUIC_FRAME_CRYPTO; return packet_out; } static int packetize_response (struct mini_conn *mc, const unsigned char *buf, size_t bufsz, const unsigned char *nonce) { struct mini_stream_ctx ms_ctx; lsquic_packet_out_t *packet_out; struct lsquic_packet_out * (*const to_packet) (struct mini_conn *, struct mini_stream_ctx *, const unsigned char *) = mc->mc_conn.cn_version < LSQVER_050 ? to_packet_pre_Q050 : to_packet_Q050plus; LSQ_DEBUG("Packetizing %zd bytes of handshake response", bufsz); ms_ctx.buf = buf; ms_ctx.bufsz = bufsz; ms_ctx.off = 0; do { packet_out = to_packet(mc, &ms_ctx, nonce); if (!packet_out) return -1; } while (mini_stream_has_data(&ms_ctx)); /* PAD the last packet with NULs. ACK and STOP_WAITING go into a separate * packet. */ if (lsquic_packet_out_avail(packet_out)) { EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "generated PADDING frame %u " "bytes long", lsquic_packet_out_avail(packet_out)); memset(packet_out->po_data + packet_out->po_data_sz, 0, lsquic_packet_out_avail(packet_out)); packet_out->po_data_sz += lsquic_packet_out_avail(packet_out); packet_out->po_frame_types |= 1 << QUIC_FRAME_PADDING; } return 0; } static int continue_handshake (struct mini_conn *mc) { lsquic_packet_in_t *packet_in; unsigned n_hsk_chunks = 0, n_contig, n, bufsz, off; int s, rv; size_t out_len; enum handshake_error he; unsigned char *buf_in_16k, *buf_out; const unsigned char *buf_in; time_t t; stream_frame_t frame; struct hsk_chunk hsk_chunks[MINICONN_MAX_PACKETS], *hsk_chunk; unsigned char nonce_buf[32]; int nonce_set = 0; int (*parse_frame)(const unsigned char *, size_t, struct stream_frame *) = mc->mc_conn.cn_version < LSQVER_050 ? mc->mc_conn.cn_pf->pf_parse_stream_frame : mc->mc_conn.cn_pf->pf_parse_crypto_frame; /* Get handshake stream data from each packet that contains a handshake * stream frame and place them into `hsk_chunks' array. */ TAILQ_FOREACH(packet_in, &mc->mc_packets_in, pi_next) { assert(n_hsk_chunks < sizeof(hsk_chunks) / sizeof(hsk_chunks[0])); if (0 == (packet_in->pi_flags & PI_HSK_STREAM)) continue; s = parse_frame(packet_in->pi_data + packet_in->pi_hsk_stream, packet_in->pi_data_sz - packet_in->pi_hsk_stream, &frame); if (-1 == s) { LSQ_WARN("cannot process hsk stream frame in packet %"PRIu64, packet_in->pi_packno); return -1; } hsk_chunk = &hsk_chunks[ n_hsk_chunks++ ]; hsk_chunk->hsk_packet_in = packet_in; hsk_chunk->hsk_data = frame.data_frame.df_data; hsk_chunk->hsk_off = frame.data_frame.df_offset; hsk_chunk->hsk_sz = frame.data_frame.df_size; } assert(n_hsk_chunks > 0); if (n_hsk_chunks > 1) { /* Sort handshake stream data */ qsort(hsk_chunks, n_hsk_chunks, sizeof(hsk_chunks[0]), compare_hsk_chunks); /* Figure out how many packets contain handshake stream data in a * contiguous buffer and how large this data is. */ for (n = 1, n_contig = 1, bufsz = hsk_chunks[0].hsk_sz; n < n_hsk_chunks; ++n) if (hsk_chunks[n - 1].hsk_off + hsk_chunks[n - 1].hsk_sz == hsk_chunks[n].hsk_off) { ++n_contig; bufsz += hsk_chunks[n].hsk_sz; } else break; } else { n_contig = 1; bufsz = hsk_chunks[0].hsk_sz; } /* Handshake handler expects to start reading at a particular offset. */ if (hsk_chunks[0].hsk_off != mc->mc_read_off) { LSQ_DEBUG("smallest hsk offset is %u, need %hu", hsk_chunks[0].hsk_off, mc->mc_read_off); MCHIST_APPEND(mc, MCHE_HELLO_HOLE); return 0; } LSQ_DEBUG("# of contiguous stream frames: %u out of %u; offset: %u; " "total size: %u", n_contig, n_hsk_chunks, hsk_chunks[0].hsk_off, bufsz); if (bufsz > 16 * 1024) { LSQ_INFO("too much contiguous handshake data (%u bytes); max: %u", bufsz, 16 * 1024); MCHIST_APPEND(mc, MCHE_HELLO_TOO_MUCH); return -1; } /* From here on, since we need to clean up, we use `rv' and `goto end' * to handle error conditions and cleanup. */ rv = -1; if (n_contig > 1) { buf_in = buf_in_16k = lsquic_mm_get_16k(&mc->mc_enpub->enp_mm); if (!buf_in) { LSQ_WARN("could not allocate in buffer: %s", strerror(errno)); buf_out = NULL; goto end; } /* Create a single contiguous buffer to pass to lsquic_enc_session_handle_chlo */ off = 0; for (n = 0; n < n_contig; ++n) { memcpy(buf_in_16k + off, hsk_chunks[n].hsk_data, hsk_chunks[n].hsk_sz); off += hsk_chunks[n].hsk_sz; } assert(off == bufsz); } else { buf_in_16k = NULL; buf_in = hsk_chunks[0].hsk_data; } buf_out = lsquic_mm_get_16k(&mc->mc_enpub->enp_mm); if (!buf_out) { LSQ_WARN("could not allocate out buffer: %s", strerror(errno)); goto end; } out_len = 16 * 1024; /* Allocate enc_session for the server if first time around: */ if (!mc->mc_conn.cn_enc_session) { mc->mc_conn.cn_enc_session = mc->mc_conn.cn_esf.g->esf_create_server(&mc->mc_conn, mc->mc_conn.cn_cid, mc->mc_enpub); if (!mc->mc_conn.cn_enc_session) { LSQ_WARN("cannot create new enc session"); goto end; } MCHIST_APPEND(mc, MCHE_NEW_ENC_SESS); } t = time(NULL); he = mc->mc_conn.cn_esf.g->esf_handle_chlo(mc->mc_conn.cn_enc_session, mc->mc_conn.cn_version, buf_in, bufsz, t, NP_PEER_SA(&mc->mc_path), NP_LOCAL_SA(&mc->mc_path), buf_out, &out_len, nonce_buf, &nonce_set); if (HS_SHLO == he) mc->mc_flags |= MC_HAVE_SHLO; else mc->mc_flags &= ~MC_HAVE_SHLO; MCHIST_APPEND(mc, he == DATA_NOT_ENOUGH ? MCHE_HANDLE_NOT_ENOUGH : he == HS_SHLO ? MCHE_HANDLE_SHLO : he == HS_1RTT ? MCHE_HANDLE_1RTT : he == HS_2RTT ? MCHE_HANDLE_2RTT : he == HS_ERROR ? MCHE_HANDLE_ERROR : MCHE_HAHDLE_UNKNOWN); if ((HS_SHLO == he || HS_1RTT == he) && !mc->mc_rtt_stats.srtt) { uint32_t irtt; if (0 == mc->mc_conn.cn_esf.g->esf_get_peer_setting( mc->mc_conn.cn_enc_session, QTAG_IRTT, &irtt)) { /* Do not allow the client to specify unreasonable values: * smaller than 10ms or larger than 15s. Per reference * implementation. */ if (irtt > 15 * 1000 * 1000) irtt = 15 * 1000 * 1000; else if (irtt < 10 * 1000) irtt = 10 * 1000; lsquic_rtt_stats_update(&mc->mc_rtt_stats, irtt, 0); LSQ_DEBUG("Set initial SRTT to %"PRIu32" usec based on client-" "supplied IRTT value", irtt); } } switch (he) { case DATA_NOT_ENOUGH: LSQ_DEBUG("lsquic_enc_session_handle_chlo needs more data"); break; case HS_SHLO: mc->mc_conn.cn_flags |= LSCONN_HANDSHAKE_DONE; mc->mc_flags |= MC_PROMOTE; LSQ_DEBUG("lsquic_enc_session_handle_chlo returned %d, promote", he); /* Fall through */ case HS_1RTT: assert(out_len > 0); if (mc->mc_conn.cn_version < LSQVER_046 && !mc->mc_conn.cn_esf.g->esf_get_peer_option( mc->mc_conn.cn_enc_session, QTAG_NSTP)) mc->mc_flags |= MC_STOP_WAIT_ON; if (0 != packetize_response(mc, buf_out, out_len, nonce_set ? nonce_buf : NULL)) goto end; mc->mc_read_off += bufsz; for (n = 0; n < n_contig; ++n) hsk_chunks[n].hsk_packet_in->pi_flags &= ~PI_HSK_STREAM; LSQ_DEBUG("read offset is now %hu", mc->mc_read_off); break; default: LSQ_WARN("unexpected return value from lsquic_enc_session_handle_chlo: %u", he); /* fallthru */ case HS_ERROR: #if !LSQUIC_KEEP_ENC_SESS_HISTORY mc->mc_conn.cn_esf.g->esf_destroy(mc->mc_conn.cn_enc_session); mc->mc_conn.cn_enc_session = NULL; #endif mc->mc_flags |= MC_HSK_ERR; LSQ_INFO("lsquic_enc_session_handle_chlo returned an error (%d)", he); goto end; } rv = 0; end: mc->mc_flags &= ~MC_HAVE_SHLO; if (buf_in_16k) lsquic_mm_put_16k(&mc->mc_enpub->enp_mm, buf_in_16k); if (buf_out) lsquic_mm_put_16k(&mc->mc_enpub->enp_mm, buf_out); return rv; } struct mini_rechist { const struct mini_conn *mc; mconn_packno_set_t cur_set; int cur_idx; struct lsquic_packno_range range; /* We return a pointer to this */ }; static void mini_rechist_init (struct mini_rechist *rechist, const struct mini_conn *mc) { rechist->mc = mc; rechist->cur_set = 0; rechist->cur_idx = 0; } static lsquic_time_t mini_rechist_largest_recv (void *rechist_ctx) { struct mini_rechist *rechist = rechist_ctx; const struct mini_conn *mc = rechist->mc; lsquic_time_t delta = mc->mc_largest_recv[0] + (mc->mc_largest_recv[1] << 8) + (mc->mc_largest_recv[2] << 16); LSQ_DEBUG("%s: largest received: %"PRIu64" usec since creation", __func__, delta); return mc->mc_created + delta; } static const struct lsquic_packno_range * mini_rechist_next (void *rechist_ctx) { struct mini_rechist *rechist = rechist_ctx; const struct mini_conn *mc = rechist->mc; mconn_packno_set_t packnos; int i; packnos = rechist->cur_set; if (0 == packnos) return NULL; /* There may be a faster way to do this, but for now, we just want * correctness. */ for (i = rechist->cur_idx; i >= 0; --i) if (packnos & (1ULL << i)) { rechist->range.low = i + 1; rechist->range.high = i + 1; break; } assert(i >= 0); /* We must have hit at least one bit */ --i; for ( ; i >= 0 && (packnos & (1ULL << i)); --i) rechist->range.low = i + 1; if (i >= 0) { rechist->cur_set = packnos & ((1ULL << i) - 1); rechist->cur_idx = i; } else rechist->cur_set = 0; LSQ_DEBUG("%s: return [%"PRIu64", %"PRIu64"]", __func__, rechist->range.low, rechist->range.high); return &rechist->range; } static const struct lsquic_packno_range * mini_rechist_first (void *rechist_ctx) { struct mini_rechist *rechist = rechist_ctx; rechist->cur_set = rechist->mc->mc_received_packnos; rechist->cur_idx = highest_bit_set(rechist->cur_set); return mini_rechist_next(rechist_ctx); } static lsquic_packno_t least_unacked (const struct mini_conn *mc) { mconn_packno_set_t unacked; lsquic_packno_t packno; unacked = mc->mc_sent_packnos & ~mc->mc_acked_packnos; if (unacked) packno = lowest_bit_set(unacked) + 1; else packno = highest_bit_set(mc->mc_sent_packnos) + 2; LSQ_DEBUG("%s: least unacked: %"PRIu64, __func__, packno); return packno; } static int generate_ack_and_stop_waiting (struct mini_conn *mc, lsquic_time_t now) { lsquic_packet_out_t *packet_out; struct mini_rechist rechist; int len, not_used_has_missing; lsquic_packno_t lunack; /* Chrome's quic_server places ACK and STOP_WAITING frames into a separate * packet. */ packet_out = allocate_packet_out(mc, NULL); if (!packet_out) return -1; /* Generate ACK frame */ mini_rechist_init(&rechist, mc); len = mc->mc_conn.cn_pf->pf_gen_ack_frame(packet_out->po_data + packet_out->po_data_sz, lsquic_packet_out_avail(packet_out), mini_rechist_first, mini_rechist_next, mini_rechist_largest_recv, &rechist, now, ¬_used_has_missing, &packet_out->po_ack2ed, NULL); if (len < 0) { LSQ_WARN("could not generate ACK frame"); return -1; } EV_LOG_GENERATED_ACK_FRAME(LSQUIC_LOG_CONN_ID, mc->mc_conn.cn_pf, packet_out->po_data + packet_out->po_data_sz, len); packet_out->po_frame_types |= 1 << QUIC_FRAME_ACK; packet_out->po_data_sz += len; packet_out->po_regen_sz += len; LSQ_DEBUG("wrote ACK frame of size %d", len); /* Generate STOP_WAITING frame */ if ((mc->mc_flags & MC_STOP_WAIT_ON) && mc->mc_sent_packnos) { lunack = least_unacked(mc); len = mc->mc_conn.cn_pf->pf_gen_stop_waiting_frame(packet_out->po_data + packet_out->po_data_sz, lsquic_packet_out_avail(packet_out), packet_out->po_packno, lsquic_packet_out_packno_bits(packet_out), lunack); if (len < 0) { LSQ_WARN("could not generate STOP_WAITING frame"); return -1; } packet_out->po_data_sz += len; packet_out->po_regen_sz += len; packet_out->po_frame_types |= 1 << QUIC_FRAME_STOP_WAITING; LSQ_DEBUG("wrote STOP_WAITING frame of size %d", len); EV_LOG_GENERATED_STOP_WAITING_FRAME(LSQUIC_LOG_CONN_ID, lunack); } else if (mc->mc_flags & MC_STOP_WAIT_ON) LSQ_DEBUG("nothing sent: no need to generate STOP_WAITING frame"); mc->mc_flags |= MC_UNSENT_ACK; return 0; } static int calc_retx_timeout (const struct mini_conn *mc) { lsquic_time_t to; to = lsquic_rtt_stats_get_srtt(&mc->mc_rtt_stats); if (to) { to += to / 2; if (to < 10000) to = 10000; } else to = 300000; return to << mc->mc_hsk_count; } static void return_enc_data (struct mini_conn *mc, struct lsquic_packet_out *packet_out) { mc->mc_enpub->enp_pmi->pmi_return(mc->mc_enpub->enp_pmi_ctx, mc->mc_path.np_peer_ctx, packet_out->po_enc_data, lsquic_packet_out_ipv6(packet_out)); packet_out->po_flags &= ~PO_ENCRYPTED; packet_out->po_enc_data = NULL; } static int repackage_packet (struct mini_conn *mc, lsquic_packet_out_t *packet_out) { const lsquic_packno_t oldno = packet_out->po_packno; const lsquic_packno_t packno = next_packno(mc); if (packno > MINICONN_MAX_PACKETS) return -1; LSQ_DEBUG("Packet %"PRIu64" repackaged for resending as packet %"PRIu64, oldno, packno); EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "packet %"PRIu64" repackaged for " "resending as packet %"PRIu64, oldno, packno); packet_out->po_packno = packno; packet_out->po_flags &= ~PO_SENT; if (packet_out->po_flags & PO_ENCRYPTED) return_enc_data(mc, packet_out); TAILQ_INSERT_TAIL(&mc->mc_packets_out, packet_out, po_next); return 0; } static int handle_losses_and_have_unsent (struct mini_conn *mc, lsquic_time_t now) { TAILQ_HEAD(, lsquic_packet_out) lost_packets = TAILQ_HEAD_INITIALIZER(lost_packets); lsquic_packet_out_t *packet_out, *next; lsquic_time_t retx_to = 0; unsigned n_to_send = 0; for (packet_out = TAILQ_FIRST(&mc->mc_packets_out); packet_out; packet_out = next) { next = TAILQ_NEXT(packet_out, po_next); if (packet_out->po_flags & PO_SENT) { if (0 == retx_to) retx_to = calc_retx_timeout(mc); if (packet_out->po_sent + retx_to < now) { LSQ_DEBUG("packet %"PRIu64" has been lost (rto: %"PRIu64")", packet_out->po_packno, retx_to); TAILQ_REMOVE(&mc->mc_packets_out, packet_out, po_next); TAILQ_INSERT_TAIL(&lost_packets, packet_out, po_next); mc->mc_lost_packnos |= MCONN_PACKET_MASK(packet_out->po_packno); MCHIST_APPEND(mc, MCHE_PACKET_LOST); } } else ++n_to_send; } mc->mc_hsk_count += !TAILQ_EMPTY(&lost_packets); while ((packet_out = TAILQ_FIRST(&lost_packets))) { TAILQ_REMOVE(&lost_packets, packet_out, po_next); if ((packet_out->po_frame_types & GQUIC_FRAME_RETRANSMITTABLE_MASK) && 0 == repackage_packet(mc, packet_out)) ++n_to_send; else mini_destroy_packet(mc, packet_out); } return n_to_send > 0; } static int warning_is_warranted (const struct mini_conn *mc) { return (mc->mc_flags & (MC_HSK_ERR|MC_OO_PACKNOS)) || 0x1C /* QUIC_HANDSHAKE_FAILED */ == mc->mc_error_code || 0x1D /* QUIC_CRYPTO_TAGS_OUT_OF_ORDER */ == mc->mc_error_code || 0x1E /* QUIC_CRYPTO_TOO_MANY_ENTRIES */ == mc->mc_error_code || 0x1F /* QUIC_CRYPTO_INVALID_VALUE_LENGTH */ == mc->mc_error_code || 0x21 /* QUIC_INVALID_CRYPTO_MESSAGE_TYPE */ == mc->mc_error_code || 0x22 /* QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER */ == mc->mc_error_code || 0x23 /* QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND */ == mc->mc_error_code || 0x24 /* QUIC_CRYPTO_MESSAGE_PARAMETER_NO_OVERLAP */ == mc->mc_error_code || 0x29 /* QUIC_CRYPTO_TOO_MANY_REJECTS */ == mc->mc_error_code || 0x2A /* QUIC_PROOF_INVALID */ == mc->mc_error_code || 0x2B /* QUIC_CRYPTO_DUPLICATE_TAG */ == mc->mc_error_code || 0x2C /* QUIC_CRYPTO_ENCRYPTION_LEVEL_INCORRECT */ == mc->mc_error_code || 0x2D /* QUIC_CRYPTO_SERVER_CONFIG_EXPIRED */ == mc->mc_error_code || 0x35 /* QUIC_CRYPTO_SYMMETRIC_KEY_SETUP_FAILED */ == mc->mc_error_code ; } #if LSQUIC_KEEP_ENC_SESS_HISTORY static void maybe_log_enc_sess_history (const struct mini_conn *mc) { char eshist[ESHIST_STR_SIZE]; enum lsq_log_level log_level; const char *ua; if (warning_is_warranted(mc)) log_level = LSQ_LOG_WARN; else log_level = LSQ_LOG_DEBUG; if (mc->mc_conn.cn_enc_session) { mc->mc_conn.cn_esf.g->esf_get_hist(mc->mc_conn.cn_enc_session, eshist); ua = mc->mc_conn.cn_esf.g->esf_get_ua(mc->mc_conn.cn_enc_session); LSQ_LOG1(log_level, "enc hist %s; User-Agent: %s", eshist, ua ? ua : ""); } else LSQ_LOG1(log_level, "enc session gone: no history to log"); } #endif static int have_packets_to_send (struct mini_conn *mc, lsquic_time_t now) { return handle_losses_and_have_unsent(mc, now); } static enum tick_st mini_conn_ci_tick (struct lsquic_conn *lconn, lsquic_time_t now) { struct mini_conn *mc = (struct mini_conn *) lconn; enum tick_st tick; ++mc->mc_n_ticks; if (mc->mc_created + mc->mc_enpub->enp_settings.es_handshake_to < now) { LSQ_DEBUG("connection expired: closing"); tick = TICK_CLOSE; goto end; } if (mc->mc_flags & MC_ERROR) { tick = TICK_CLOSE; goto end; } if ((mc->mc_flags & (MC_UNSENT_ACK|MC_GEN_ACK)) == MC_GEN_ACK) { if (0 != generate_ack_and_stop_waiting(mc, now)) { mc->mc_flags |= MC_ERROR; tick = TICK_CLOSE; goto end; } else mc->mc_flags &= ~MC_GEN_ACK; } if (have_packets_to_send(mc, now)) tick = TICK_SEND; else tick = TICK_QUIET; if (mc->mc_flags & MC_PROMOTE) tick |= TICK_PROMOTE; end: #if LSQUIC_KEEP_ENC_SESS_HISTORY if (tick & (TICK_CLOSE|TICK_PROMOTE)) maybe_log_enc_sess_history(mc); #endif return tick; } static void process_packet (struct mini_conn *mc, struct lsquic_packet_in *packet_in) { switch (process_regular_packet(mc, packet_in)) { case PRP_KEEP: assert(packet_in->pi_flags & PI_OWN_DATA); lsquic_packet_in_upref(packet_in); TAILQ_INSERT_TAIL(&mc->mc_packets_in, packet_in, pi_next); if (mc->mc_flags & MC_HAVE_NEW_HSK) { if (0 != continue_handshake(mc)) mc->mc_flags |= MC_ERROR; mc->mc_flags &= ~MC_HAVE_NEW_HSK; } break; case PRP_DEFER: assert(packet_in->pi_flags & PI_OWN_DATA); lsquic_packet_in_upref(packet_in); if (mc->mc_n_deferred < MINI_CONN_MAX_DEFERRED) { TAILQ_INSERT_TAIL(&mc->mc_deferred, packet_in, pi_next); ++mc->mc_n_deferred; } else LSQ_DEBUG("won't defer more than %u packets: drop", MINI_CONN_MAX_DEFERRED); break; case PRP_ERROR: mc->mc_flags |= MC_ERROR; break; case PRP_DROP: break; } } /* Keep deferred list ordered by packet number, so that we can process all * of them in a single pass. */ static void insert_into_deferred (struct mini_conn *mc, lsquic_packet_in_t *new_packet) { lsquic_packet_in_t *packet_in; lsquic_packet_in_upref(new_packet); TAILQ_FOREACH(packet_in, &mc->mc_deferred, pi_next) if (packet_in->pi_packno > new_packet->pi_packno) break; if (packet_in) TAILQ_INSERT_BEFORE(packet_in, new_packet, pi_next); else TAILQ_INSERT_TAIL(&mc->mc_deferred, new_packet, pi_next); ++mc->mc_n_deferred; } static void process_deferred_packets (struct mini_conn *mc) { lsquic_packet_in_t *last, *packet_in; int reached_last; last = TAILQ_LAST(&mc->mc_deferred, head_packet_in); do { packet_in = TAILQ_FIRST(&mc->mc_deferred); TAILQ_REMOVE(&mc->mc_deferred, packet_in, pi_next); --mc->mc_n_deferred; process_packet(mc, packet_in); reached_last = packet_in == last; lsquic_packet_in_put(&mc->mc_enpub->enp_mm, packet_in); } while (!reached_last); } #if LSQUIC_RECORD_INORD_HIST /* Packet number is encoded as a sequence of 1-bits and stored in mc_inord_hist * separated by 0 bits. For example, sequence of packet numbers 3, 2, 1 would * be encoded as (starting with LSB) 1110110100000000... This is not the most * space-efficient scheme, but it is simple to implement and should suffice for * our purposes. */ static void record_inord_packno (struct mini_conn *mc, lsquic_packno_t packno) { int n_avail; lsquic_packno_t mask; for ( ; mc->mc_inord_idx < sizeof(mc->mc_inord_hist) / sizeof(mc->mc_inord_hist[0]); ++mc->mc_inord_idx) { if (mc->mc_inord_hist[ mc->mc_inord_idx ]) n_avail = __builtin_clzll(mc->mc_inord_hist[ mc->mc_inord_idx ]) - 1; else n_avail = sizeof(mc->mc_inord_hist[ mc->mc_inord_idx ]) * 8; if (n_avail >= (int) packno) { mask = (1ULL << (int) packno) - 1; mask <<= sizeof(mc->mc_inord_hist[ mc->mc_inord_idx ]) * 8 - n_avail; mc->mc_inord_hist[ mc->mc_inord_idx ] |= mask; return; /* Success */ } } } static void inord_to_str (const struct mini_conn *mc, char *buf, size_t bufsz) { unsigned long long hist; size_t off; ssize_t nw; unsigned n; int n_trail; off = 0; for (n = 0; n < sizeof(mc->mc_inord_hist) / sizeof(mc->mc_inord_hist[0]); ++n) { hist = mc->mc_inord_hist[n]; while (hist) { n_trail = __builtin_ctzll(~hist); nw = snprintf(buf + off, bufsz - off, /* No spaces are included on purpose: this makes it a single * field and thus easy to process log using standard command- * line tools, such as sork -k, for example. */ (off ? ",%d" : "%d"), n_trail); if ((size_t) nw > bufsz - off || nw < 0) break; off += nw; hist >>= n_trail + 1; } } buf[ bufsz - 1 ] = '\0'; /* CYA */ } #endif static void mini_conn_ci_packet_in (struct lsquic_conn *lconn, struct lsquic_packet_in *packet_in) { struct mini_conn *mc = (struct mini_conn *) lconn; #if LSQUIC_RECORD_INORD_HIST record_inord_packno(mc, packet_in->pi_packno); #endif #if 0 /* A convenient way to test lsquic_is_valid_hs_packet(): */ if (!(mc->mc_sent_packnos)) assert(lsquic_is_valid_hs_packet(NULL, packet_in->pi_data, packet_in->pi_data_sz)); #endif if (mc->mc_flags & MC_ERROR) { LSQ_DEBUG("error state: ignore packet %"PRIu64, packet_in->pi_packno); return; } if (lsquic_packet_in_is_gquic_prst(packet_in)) { LSQ_INFO("received reset packet"); mc->mc_flags |= MC_ERROR; MCHIST_APPEND(mc, MCHE_PRST_IN); return; } LSQ_DEBUG("packet in: %"PRIu64, packet_in->pi_packno); EV_LOG_PACKET_IN(LSQUIC_LOG_CONN_ID, packet_in); /* Check receive history */ if (0 == packet_in->pi_packno) { LSQ_DEBUG("invalid packet number 0"); mc->mc_flags |= MC_ERROR; MCHIST_APPEND(mc, MCHE_PACKET0_IN); return; } if (packet_in->pi_packno > MINICONN_MAX_PACKETS) { LSQ_DEBUG("packet number %"PRIu64" is too large (max %zd)", packet_in->pi_packno, MINICONN_MAX_PACKETS); mc->mc_flags |= MC_ERROR; MCHIST_APPEND(mc, MCHE_PACKET2LARGE_IN); return; } if (MCONN_PACKET_MASK(packet_in->pi_packno) & mc->mc_received_packnos) { LSQ_DEBUG("duplicate packet %"PRIu64", ignoring", packet_in->pi_packno); MCHIST_APPEND(mc, MCHE_PACKET_DUP_IN); return; } if (TAILQ_EMPTY(&mc->mc_deferred)) process_packet(mc, packet_in); else if (mc->mc_n_deferred < MINI_CONN_MAX_DEFERRED) { insert_into_deferred(mc, packet_in); process_deferred_packets(mc); } else LSQ_DEBUG("won't defer more than %u packets: drop", MINI_CONN_MAX_DEFERRED); } /* Q050 is different is that packet numbers are not known until after the * packet is decrypted, so we have to follow different logic here. */ static void mini_conn_ci_Q050_packet_in (struct lsquic_conn *lconn, struct lsquic_packet_in *packet_in) { struct mini_conn *mc = (struct mini_conn *) lconn; enum proc_rp prp; if (mc->mc_flags & MC_ERROR) { LSQ_DEBUG("error state: ignore packet"); return; } if (!mc->mc_conn.cn_enc_session) { mc->mc_conn.cn_enc_session = mc->mc_conn.cn_esf.g->esf_create_server(&mc->mc_conn, mc->mc_conn.cn_cid, mc->mc_enpub); if (!mc->mc_conn.cn_enc_session) { LSQ_WARN("cannot create new enc session"); mc->mc_flags |= MC_ERROR; return; } MCHIST_APPEND(mc, MCHE_NEW_ENC_SESS); } assert(!(packet_in->pi_flags & PI_DECRYPTED)); prp = conn_decrypt_packet_or(mc, packet_in); switch (prp) { case PRP_KEEP: break; case PRP_DROP: return; case PRP_ERROR: mc->mc_flags |= MC_ERROR; return; default: if (mc->mc_n_deferred >= MINI_CONN_MAX_DEFERRED) { LSQ_DEBUG("won't defer more than %u packets: drop", MINI_CONN_MAX_DEFERRED); return; } assert(prp == PRP_DEFER); assert(packet_in->pi_flags & PI_OWN_DATA); lsquic_packet_in_upref(packet_in); TAILQ_INSERT_TAIL(&mc->mc_deferred, packet_in, pi_next); ++mc->mc_n_deferred; return; } assert(prp == PRP_KEEP); process_packet(mc, packet_in); } static struct lsquic_packet_out * mini_conn_ci_next_packet_to_send (struct lsquic_conn *lconn, size_t size) { struct mini_conn *mc = (struct mini_conn *) lconn; lsquic_packet_out_t *packet_out; assert(0 == size); TAILQ_FOREACH(packet_out, &mc->mc_packets_out, po_next) { if (packet_out->po_flags & PO_SENT) continue; packet_out->po_flags |= PO_SENT; LSQ_DEBUG("packet_to_send: %"PRIu64, packet_out->po_packno); return packet_out; } return NULL; } static void mini_conn_ci_packet_sent (struct lsquic_conn *lconn, struct lsquic_packet_out *packet_out) { struct mini_conn *mc = (struct mini_conn *) lconn; mc->mc_sent_packnos |= MCONN_PACKET_MASK(packet_out->po_packno); if (packet_out->po_frame_types & (1 << QUIC_FRAME_ACK)) { assert(mc->mc_flags & MC_UNSENT_ACK); mc->mc_flags &= ~MC_UNSENT_ACK; } LSQ_DEBUG("%s: packet %"PRIu64" sent", __func__, packet_out->po_packno); MCHIST_APPEND(mc, MCHE_PACKET_SENT); } static void mini_conn_ci_packet_not_sent (struct lsquic_conn *lconn, struct lsquic_packet_out *packet_out) { struct mini_conn *mc = (struct mini_conn *) lconn; packet_out->po_flags &= ~PO_SENT; LSQ_DEBUG("%s: packet %"PRIu64" not sent", __func__, packet_out->po_packno); MCHIST_APPEND(mc, MCHE_PACKET_DELAYED); } static void mini_conn_ci_destroy (struct lsquic_conn *lconn) { assert(!(lconn->cn_flags & LSCONN_HASHED)); struct mini_conn *mc = (struct mini_conn *) lconn; lsquic_packet_in_t *packet_in; mconn_packno_set_t still_deferred = 0, in_flight; enum lsq_log_level log_level; #if LSQUIC_RECORD_INORD_HIST char inord_str[0x100]; #endif while ((packet_in = TAILQ_FIRST(&mc->mc_packets_in))) { TAILQ_REMOVE(&mc->mc_packets_in, packet_in, pi_next); lsquic_packet_in_put(&mc->mc_enpub->enp_mm, packet_in); } while ((packet_in = TAILQ_FIRST(&mc->mc_deferred))) { TAILQ_REMOVE(&mc->mc_deferred, packet_in, pi_next); --mc->mc_n_deferred; still_deferred |= MCONN_PACKET_MASK(packet_in->pi_packno); lsquic_packet_in_put(&mc->mc_enpub->enp_mm, packet_in); } if (TAILQ_EMPTY(&mc->mc_packets_out)) in_flight = ~0ull; /* Indicates that packets were dropped before */ else in_flight = drop_packets_out(mc); if (mc->mc_conn.cn_enc_session) mc->mc_conn.cn_esf.g->esf_destroy(mc->mc_conn.cn_enc_session); log_level = warning_is_warranted(mc) ? LSQ_LOG_WARN : LSQ_LOG_DEBUG; #if LSQUIC_RECORD_INORD_HIST if (LSQ_LOG_ENABLED(log_level)) inord_to_str(mc, inord_str, sizeof(inord_str)); #endif #if LSQUIC_KEEP_MINICONN_HISTORY const unsigned hist_idx = MCHIST_MASK & mc->mc_hist_idx; if (MCHE_EMPTY == mc->mc_hist_buf[ hist_idx ]) LSQ_LOG(log_level, "destroyed. Diagnostics: conn flags: 0x%X, " "mc flags: 0x%X, " #if LSQUIC_RECORD_INORD_HIST "incoming-history (trunc: %d) %s, " #endif "received: %"PRIX64", sent: %"PRIX64", lost: %"PRIX64", " "deferred: %"PRIX64", still-deferred: %"PRIX64", " "dropped: %"PRIX64", in-flight: %"PRIX64", acked: %"PRIX64", " "error_code: 0x%X, ticks: %hu, pack size: %hu, " "lifetime: %"PRIu64" usec, version: %s, " "mc hist: %.*s", mc->mc_conn.cn_flags, mc->mc_flags, #if LSQUIC_RECORD_INORD_HIST mc->mc_inord_idx >= sizeof(mc->mc_inord_hist) / sizeof(mc->mc_inord_hist[0]), inord_str, #endif mc->mc_received_packnos, mc->mc_sent_packnos, mc->mc_lost_packnos, mc->mc_deferred_packnos, still_deferred, mc->mc_dropped_packnos, in_flight, mc->mc_acked_packnos, mc->mc_error_code, mc->mc_n_ticks, mc->mc_conn.cn_pack_size, lsquic_time_now() - mc->mc_created, lsquic_ver2str[mc->mc_conn.cn_version], (int) hist_idx, mc->mc_hist_buf); else LSQ_LOG(log_level, "destroyed. Diagnostics: conn flags: 0x%X, " "mc flags: 0x%X, " #if LSQUIC_RECORD_INORD_HIST "incoming-history (trunc: %d) %s, " #endif "received: %"PRIX64", sent: %"PRIX64", lost: %"PRIX64", " "deferred: %"PRIX64", still-deferred: %"PRIX64", " "dropped: %"PRIX64", in-flight: %"PRIX64", acked: %"PRIX64", " "error_code: 0x%X, ticks: %hu, pack size: %hu, " "lifetime: %"PRIu64" usec, version: %s, " "mc hist: %.*s%.*s", mc->mc_conn.cn_flags, mc->mc_flags, #if LSQUIC_RECORD_INORD_HIST mc->mc_inord_idx >= sizeof(mc->mc_inord_hist) / sizeof(mc->mc_inord_hist[0]), inord_str, #endif mc->mc_received_packnos, mc->mc_sent_packnos, mc->mc_lost_packnos, mc->mc_deferred_packnos, still_deferred, mc->mc_dropped_packnos, in_flight, mc->mc_acked_packnos, mc->mc_error_code, mc->mc_n_ticks, mc->mc_conn.cn_pack_size, lsquic_time_now() - mc->mc_created, lsquic_ver2str[mc->mc_conn.cn_version], (int) (sizeof(mc->mc_hist_buf) - hist_idx), mc->mc_hist_buf + hist_idx, (int) hist_idx, mc->mc_hist_buf); #else LSQ_LOG(log_level, "destroyed. Diagnostics: conn flags: 0x%X, " "mc flags: 0x%X, " #if LSQUIC_RECORD_INORD_HIST "incoming-history (trunc: %d) %s, " #endif "received: %"PRIX64", sent: %"PRIX64", lost: %"PRIX64", " "deferred: %"PRIX64", still-deferred: %"PRIX64", " "dropped: %"PRIX64", in-flight: %"PRIX64", acked: %"PRIX64", " "error_code: 0x%X, ticks: %hu, pack size: %hu, " "lifetime: %"PRIu64" usec", mc->mc_conn.cn_flags, mc->mc_flags, #if LSQUIC_RECORD_INORD_HIST mc->mc_inord_idx >= sizeof(mc->mc_inord_hist) / sizeof(mc->mc_inord_hist[0]), inord_str, #endif mc->mc_received_packnos, mc->mc_sent_packnos, mc->mc_lost_packnos, mc->mc_deferred_packnos, still_deferred, mc->mc_dropped_packnos, in_flight, mc->mc_acked_packnos, mc->mc_error_code, mc->mc_n_ticks, mc->mc_path.np_pack_size, lsquic_time_now() - mc->mc_created); #endif EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "mini connection destroyed"); lsquic_malo_put(mc); } static struct lsquic_engine * mini_conn_ci_get_engine (struct lsquic_conn *lconn) { struct mini_conn *mc = (struct mini_conn *) lconn; return mc->mc_enpub->enp_engine; } static void mini_conn_ci_hsk_done (struct lsquic_conn *lconn, enum lsquic_hsk_status status) { assert(0); } static int mini_conn_ci_is_tickable (struct lsquic_conn *lconn) { /* A mini connection is never tickable: Either there are incoming * packets, in which case, the connection is going to be ticked, or * there is an alarm pending, in which case it will be handled via * the attq. */ return 0; } static lsquic_time_t mini_conn_ci_next_tick_time (struct lsquic_conn *lconn, unsigned *why) { struct mini_conn *mc = (struct mini_conn *) lconn; lsquic_packet_out_t *packet_out; lsquic_time_t exp_time, retx_time; exp_time = mc->mc_created + mc->mc_enpub->enp_settings.es_handshake_to; TAILQ_FOREACH(packet_out, &mc->mc_packets_out, po_next) if (packet_out->po_flags & PO_SENT) { retx_time = packet_out->po_sent + calc_retx_timeout(mc); if (retx_time < exp_time) { *why = N_AEWS + AL_RETX_HSK; return retx_time; } else { *why = AEW_MINI_EXPIRE; return exp_time; } } *why = AEW_MINI_EXPIRE; return exp_time; } static void mini_conn_ci_client_call_on_new (struct lsquic_conn *lconn) { assert(0); } static void mini_conn_ci_internal_error (struct lsquic_conn *lconn, const char *format, ...) { struct mini_conn *mc = (struct mini_conn *) lconn; LSQ_INFO("internal error reported"); mc->mc_flags |= MC_ERROR; } /* This function should not be called, as this is specific to IETF QUIC */ static void mini_conn_ci_abort_error (struct lsquic_conn *lconn, int is_app, unsigned error_code, const char *fmt, ...) { struct mini_conn *mc = (struct mini_conn *) lconn; assert(0); LSQ_WARN("(GQUIC) abort error is called unexpectedly"); mc->mc_flags |= MC_ERROR; } static void mini_conn_ci_tls_alert (struct lsquic_conn *lconn, uint8_t alert) { assert(0); } static unsigned char mini_conn_ci_record_addrs (struct lsquic_conn *lconn, void *peer_ctx, const struct sockaddr *local_sa, const struct sockaddr *peer_sa) { struct mini_conn *mc = (struct mini_conn *) lconn; struct lsquic_packet_out *packet_out; size_t len; if (NP_IS_IPv6(&mc->mc_path) != (AF_INET6 == peer_sa->sa_family)) TAILQ_FOREACH(packet_out, &mc->mc_packets_out, po_next) if ((packet_out->po_flags & (PO_SENT|PO_ENCRYPTED)) == PO_ENCRYPTED) return_enc_data(mc, packet_out); len = local_sa->sa_family == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6); memcpy(mc->mc_path.np_peer_addr, peer_sa, len); memcpy(mc->mc_path.np_local_addr, local_sa, len); mc->mc_path.np_peer_ctx = peer_ctx; return 0; } static struct network_path * mini_conn_ci_get_path (struct lsquic_conn *lconn, const struct sockaddr *sa) { struct mini_conn *mc = (struct mini_conn *) lconn; return &mc->mc_path; } static const struct conn_iface mini_conn_iface_standard = { .ci_abort_error = mini_conn_ci_abort_error, .ci_client_call_on_new = mini_conn_ci_client_call_on_new, .ci_destroy = mini_conn_ci_destroy, .ci_get_engine = mini_conn_ci_get_engine, .ci_get_path = mini_conn_ci_get_path, .ci_hsk_done = mini_conn_ci_hsk_done, .ci_internal_error = mini_conn_ci_internal_error, .ci_is_tickable = mini_conn_ci_is_tickable, .ci_next_packet_to_send = mini_conn_ci_next_packet_to_send, .ci_next_tick_time = mini_conn_ci_next_tick_time, .ci_packet_in = mini_conn_ci_packet_in, .ci_packet_not_sent = mini_conn_ci_packet_not_sent, .ci_packet_sent = mini_conn_ci_packet_sent, .ci_record_addrs = mini_conn_ci_record_addrs, .ci_tick = mini_conn_ci_tick, .ci_tls_alert = mini_conn_ci_tls_alert, }; static const struct conn_iface mini_conn_iface_standard_Q050 = { .ci_abort_error = mini_conn_ci_abort_error, .ci_client_call_on_new = mini_conn_ci_client_call_on_new, .ci_destroy = mini_conn_ci_destroy, .ci_get_engine = mini_conn_ci_get_engine, .ci_get_path = mini_conn_ci_get_path, .ci_hsk_done = mini_conn_ci_hsk_done, .ci_internal_error = mini_conn_ci_internal_error, .ci_is_tickable = mini_conn_ci_is_tickable, .ci_next_packet_to_send = mini_conn_ci_next_packet_to_send, .ci_next_tick_time = mini_conn_ci_next_tick_time, .ci_packet_in = mini_conn_ci_Q050_packet_in, .ci_packet_not_sent = mini_conn_ci_packet_not_sent, .ci_packet_sent = mini_conn_ci_packet_sent, .ci_record_addrs = mini_conn_ci_record_addrs, .ci_tick = mini_conn_ci_tick, .ci_tls_alert = mini_conn_ci_tls_alert, }; typedef char largest_recv_holds_at_least_16_seconds[ ((1 << (sizeof(((struct mini_conn *) 0)->mc_largest_recv) * 8)) / 1000000 >= 16) - 1]; typedef char max_lifespan_smaller_than_largest_recv[ ((1 << (sizeof(((struct mini_conn *) 0)->mc_largest_recv) * 8)) > MAX_MINI_CONN_LIFESPAN_IN_USEC) - 1];