diff --git a/CHANGELOG b/CHANGELOG index c522320f9..2e6bf8e3e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,18 @@ +2019-12-30 + - 2.8.1 + - [FEATURE] Use occasional packet number gaps to detect optimistic + ACK attacks. + - [BUGFIX] Q050 client: all packet numbers are in the App PNS. + - [OPTIMIZATION] Merge multi-range ACK frames, not just single-range + ACK frames. + - IETF QUIC: use RTT estimate in ack timeout calculation. + - IETF handshake: abort conn when unexpected errors occur. + - Use PING rather than MAX_DATA frames to elicit ACKs from peer. + - Server: enforce 1200 byte Initial minimum packet size. + - [CLEANUP] Remove code to disable gQUIC crypto. + - [CLEANUP] Remove n_timestamps from ACK info struct. + - Optimize driver: reuse previous ancillary message when possible. + 2019-12-23 - 2.8.0 - [FEATURE] Add support for Q050. diff --git a/EXAMPLES.txt b/EXAMPLES.txt index 104b9008e..9f5e20f68 100644 --- a/EXAMPLES.txt +++ b/EXAMPLES.txt @@ -166,19 +166,6 @@ LSQUIC_PACKET_OUT_LIMIT Note 2: see -m option for a related packet-out limitation. -LSQUIC_DISABLE_HANDSHAKE - - If set (to anything, not any particular value), the QUIC handshake is - disabled and packets are not encrypted. This can be useful to: - a) profile functions in LSQUIC without accounting for crypto stuff, - which tends to dwarf everything else; - b) see bytes in the clear on the wire; and - c) compare throughput performance to TCP without crypto. - - This functionality is compiled in if the somewhat-awkwardly named - LSQUIC_ENABLE_HANDSHAKE_DISABLE is set to 1. By default, it is enabled - in debug builds and disabled in optimized builds. - LSQUIC_LOSE_PACKETS_RE If set, this regular expression specifies the numbers of packets which @@ -228,6 +215,10 @@ LSQUIC_USE_POOLS malloc() and free(). This facilitates debugging memory issues. The default is true. +LSQUIC_ACK_ATTACK + + If set to true, generate optimistic ACKs. + Control Network-Related Stuff ----------------------------- @@ -246,10 +237,6 @@ Control Network-Related Stuff More Compilation Options ------------------------ --DLSQUIC_ENABLE_HANDSHAKE_DISABLE=1 - - Support disabling of handshake. See above. - -DLSQUIC_CONN_STATS=1 Track some statistics about connections -- packets in, sent, delayed, @@ -296,3 +283,8 @@ More Compilation Options When compiled with this flag, setting environment variable LSQUIC_ECN_BLACK_HOLE to 1 will emulate ECN black hole: all received packets with ECN markings are dropped on the floor. + +-DLSQUIC_ACK_ATTACK=1 + + Enable ACK attack mode. See LSQUIC_ACK_ATTACK environment variable + entry above. diff --git a/include/lsquic.h b/include/lsquic.h index 9ee2df65e..6b2581473 100644 --- a/include/lsquic.h +++ b/include/lsquic.h @@ -25,7 +25,7 @@ extern "C" { #define LSQUIC_MAJOR_VERSION 2 #define LSQUIC_MINOR_VERSION 8 -#define LSQUIC_PATCH_VERSION 0 +#define LSQUIC_PATCH_VERSION 1 /** * Engine flags: diff --git a/src/liblsquic/lsquic_chsk_stream.c b/src/liblsquic/lsquic_chsk_stream.c index 843522a8d..ccdb13a59 100644 --- a/src/liblsquic/lsquic_chsk_stream.c +++ b/src/liblsquic/lsquic_chsk_stream.c @@ -38,16 +38,6 @@ hsk_client_on_new_stream (void *stream_if_ctx, lsquic_stream_t *stream) LSQ_DEBUG("stream created"); -#if LSQUIC_ENABLE_HANDSHAKE_DISABLE - if (getenv("LSQUIC_DISABLE_HANDSHAKE")) - { - LSQ_WARN("Handshake disabled: faking it"); - c_hsk->lconn->cn_flags |= LSCONN_NO_CRYPTO; - c_hsk->lconn->cn_if->ci_handshake_ok(c_hsk->lconn); - return (void *) c_hsk; - } -#endif - lsquic_stream_wantwrite(stream, 1); return (void *) c_hsk; diff --git a/src/liblsquic/lsquic_conn.h b/src/liblsquic/lsquic_conn.h index 93c3e9491..b86efc914 100644 --- a/src/liblsquic/lsquic_conn.h +++ b/src/liblsquic/lsquic_conn.h @@ -52,9 +52,7 @@ enum lsquic_conn_flags { LSCONN_UNUSED_18 = (1 <<18), LSCONN_ATTQ = (1 <<19), LSCONN_SKIP_ON_PROC = (1 <<20), -#if LSQUIC_ENABLE_HANDSHAKE_DISABLE - LSCONN_NO_CRYPTO = (1 <<21), -#endif + LSCONN_UNUSED_21 = (1 <<21), LSCONN_SERVER = (1 <<22), LSCONN_IETF = (1 <<23), LSCONN_RETRY_CONN = (1 <<24), /* This is a retry connection */ @@ -362,7 +360,7 @@ struct conn_stats { err_packets; /* Error packets(?) */ unsigned long n_acks, n_acks_proc, - n_acks_merged[2]; + n_acks_merged; unsigned long bytes; /* Overall bytes in */ unsigned long headers_uncomp; /* Sum of uncompressed header bytes */ unsigned long headers_comp; /* Sum of compressed header bytes */ diff --git a/src/liblsquic/lsquic_enc_sess.h b/src/liblsquic/lsquic_enc_sess.h index 0299ca613..a82475122 100644 --- a/src/liblsquic/lsquic_enc_sess.h +++ b/src/liblsquic/lsquic_enc_sess.h @@ -51,8 +51,6 @@ enum handshake_error /* TODO: rename this enum */ HS_SHLO = 0, HS_1RTT = 1, HS_SREJ = 2, - HS_DELAYED = 3, - HS_PK_OFFLOAD = 4, }; #ifndef LSQUIC_KEEP_ENC_SESS_HISTORY @@ -208,11 +206,6 @@ struct enc_session_funcs_gquic (*esf_get_dec_key_nonce_f) (enc_session_t *); #endif /* !defined(NDEBUG) */ -#if LSQUIC_ENABLE_HANDSHAKE_DISABLE - void - (*esf_set_handshake_completed) (enc_session_t *); -#endif - /* Create client session */ enc_session_t * (*esf_create_client) (struct lsquic_conn *, const char *domain, diff --git a/src/liblsquic/lsquic_enc_sess_ietf.c b/src/liblsquic/lsquic_enc_sess_ietf.c index f9a80ffb9..43b8fa324 100644 --- a/src/liblsquic/lsquic_enc_sess_ietf.c +++ b/src/liblsquic/lsquic_enc_sess_ietf.c @@ -58,14 +58,6 @@ #define LSQUIC_LOG_CONN_ID lsquic_conn_log_cid(enc_sess->esi_conn) #include "lsquic_logger.h" -/* [draft-ietf-quic-tls-11] Section 5.3.2 */ -#define HSK_SECRET_SZ SHA256_DIGEST_LENGTH - -/* TODO: Specify ciphers */ -#define HSK_CIPHERS "TLS13-AES-128-GCM-SHA256" \ - ":TLS13-AES-256-GCM-SHA384" \ - ":TLS13-CHACHA20-POLY1305-SHA256" - #define KEY_LABEL "quic key" #define KEY_LABEL_SZ (sizeof(KEY_LABEL) - 1) #define IV_LABEL "quic iv" @@ -1585,7 +1577,8 @@ iquic_esfi_post_handshake (struct enc_sess_iquic *enc_sess) return IHS_WANT_READ; else { - LSQ_DEBUG("TODO: abort connection?"); + enc_sess->esi_conn->cn_if->ci_internal_error(enc_sess->esi_conn, + "post-handshake error, code %d", s); return IHS_STOP; } } @@ -2735,8 +2728,8 @@ maybe_write_from_fral (struct enc_sess_iquic *enc_sess, } else { - /* TODO: abort connection */ - LSQ_WARN("cannot write to stream: %s", strerror(errno)); + enc_sess->esi_conn->cn_if->ci_internal_error(enc_sess->esi_conn, + "cannot write to stream: %s", strerror(errno)); lsquic_stream_wantwrite(stream, 0); } } diff --git a/src/liblsquic/lsquic_engine.c b/src/liblsquic/lsquic_engine.c index 833d42113..94f04802a 100644 --- a/src/liblsquic/lsquic_engine.c +++ b/src/liblsquic/lsquic_engine.c @@ -2237,10 +2237,6 @@ iov_size (const struct iovec *iov, const struct iovec *const end) } -/* XXX A lot of extra setup -- two extra arguments to this function, two extra - * connection ref flags and queues -- is just to handle the ENCPA_BADCRYPT case, - * which never really happens. - */ static void send_packets_out (struct lsquic_engine *engine, struct conns_tailq *ticked_conns, @@ -2417,7 +2413,6 @@ reset_deadline (lsquic_engine_t *engine, lsquic_time_t now) } -/* TODO: this is a user-facing function, account for load */ void lsquic_engine_send_unsent_packets (lsquic_engine_t *engine) { diff --git a/src/liblsquic/lsquic_engine_public.h b/src/liblsquic/lsquic_engine_public.h index 4b5213e07..8a29d28ce 100644 --- a/src/liblsquic/lsquic_engine_public.h +++ b/src/liblsquic/lsquic_engine_public.h @@ -21,6 +21,7 @@ enum warning_type { WT_ACKPARSE_MINI, WT_ACKPARSE_FULL, + WT_NO_POISON, N_WARNING_TYPES, }; diff --git a/src/liblsquic/lsquic_ev_log.c b/src/liblsquic/lsquic_ev_log.c index c4af1279e..b5f0c2122 100644 --- a/src/liblsquic/lsquic_ev_log.c +++ b/src/liblsquic/lsquic_ev_log.c @@ -93,14 +93,10 @@ void lsquic_ev_log_ack_frame_in (const lsquic_cid_t *cid, const struct ack_info *acki) { - size_t sz; - char *buf; + char buf[MAX_ACKI_STR_SZ]; - if ((buf = acki2str(acki, &sz))) - { - LCID("ACK frame in: %.*s", (int) sz, buf); - free(buf); - } + lsquic_acki2str(acki, buf, sizeof(buf)); + LCID("ACK frame in: %s", buf); } @@ -361,9 +357,8 @@ lsquic_ev_log_generated_ack_frame (const lsquic_cid_t *cid, size_t ack_buf_sz) { struct ack_info acki; - size_t sz; - char *buf; int len; + char buf[MAX_ACKI_STR_SZ]; len = pf->pf_parse_ack_frame(ack_buf, ack_buf_sz, &acki, TP_DEF_ACK_DELAY_EXP); @@ -373,11 +368,8 @@ lsquic_ev_log_generated_ack_frame (const lsquic_cid_t *cid, return; } - if ((buf = acki2str(&acki, &sz))) - { - LCID("generated ACK frame: %.*s", (int) sz, buf); - free(buf); - } + lsquic_acki2str(&acki, buf, sizeof(buf)); + LCID("generated ACK frame: %s", buf); } diff --git a/src/liblsquic/lsquic_full_conn.c b/src/liblsquic/lsquic_full_conn.c index 027c56919..f39c90cb9 100644 --- a/src/liblsquic/lsquic_full_conn.c +++ b/src/liblsquic/lsquic_full_conn.c @@ -216,11 +216,11 @@ struct full_conn #endif STAILQ_HEAD(, stream_id_to_reset) fc_stream_ids_to_reset; - struct short_ack_info fc_saved_ack_info; lsquic_time_t fc_saved_ack_received; struct network_path fc_path; unsigned fc_orig_versions; /* Client only */ enum enc_level fc_crypto_enc_level; + struct ack_info fc_ack; }; static const struct ver_neg server_ver_neg; @@ -521,31 +521,13 @@ apply_peer_settings (struct full_conn *conn) return 0; #endif -#if LSQUIC_ENABLE_HANDSHAKE_DISABLE - if (conn->fc_conn.cn_flags & LSCONN_NO_CRYPTO) - { /* Magically figure out peer's settings */ - if (conn->fc_flags & FC_SERVER) - { - cfcw = LSQUIC_DF_CFCW_CLIENT; - sfcw = LSQUIC_DF_SFCW_CLIENT; - mids = 100; - } - else + for (n = 0; n < sizeof(tags) / sizeof(tags[0]); ++n) + if (0 != conn->fc_conn.cn_esf.g->esf_get_peer_setting( + conn->fc_conn.cn_enc_session, tags[n].tag, tags[n].val)) { - cfcw = LSQUIC_DF_CFCW_SERVER; - sfcw = LSQUIC_DF_SFCW_SERVER; - mids = 100; + LSQ_INFO("peer did not supply value for %s", tags[n].tag_str); + return -1; } - } - else -#endif - for (n = 0; n < sizeof(tags) / sizeof(tags[0]); ++n) - if (0 != conn->fc_conn.cn_esf.g->esf_get_peer_setting( - conn->fc_conn.cn_enc_session, tags[n].tag, tags[n].val)) - { - LSQ_INFO("peer did not supply value for %s", tags[n].tag_str); - return -1; - } LSQ_DEBUG("peer settings: CFCW: %u; SFCW: %u; MIDS: %u", cfcw, sfcw, mids); @@ -893,6 +875,7 @@ lsquic_gquic_full_conn_server_new (struct lsquic_engine_public *enpub, lsquic_send_ctl_verneg_done(&conn->fc_send_ctl); conn->fc_send_ctl.sc_cur_packno = mc->mc_cur_packno; + lsquic_send_ctl_begin_optack_detection(&conn->fc_send_ctl); /* Remove those that still exist from the set: they will be marked as * received during regular processing in ci_packet_in() later on. @@ -952,10 +935,6 @@ lsquic_gquic_full_conn_server_new (struct lsquic_engine_public *enpub, assert(lconn_mini->cn_flags & LSCONN_HANDSHAKE_DONE); lconn_full->cn_flags |= LSCONN_HANDSHAKE_DONE; -#if LSQUIC_ENABLE_HANDSHAKE_DISABLE - if (getenv("LSQUIC_DISABLE_HANDSHAKE")) - lconn_full->cn_flags |= LSCONN_NO_CRYPTO; -#endif lconn_full->cn_flags |= lconn_mini->cn_flags & LSCONN_PEER_GOING_AWAY /* We are OK with fc_goaway_stream_id = 0 */; @@ -1130,9 +1109,9 @@ full_conn_ci_destroy (lsquic_conn_t *lconn) conn->fc_stats.in.dup_packets, conn->fc_stats.in.err_packets, conn->fc_stats.out.packets, conn->fc_stats.out.stream_data_sz / conn->fc_stats.out.packets); - LSQ_NOTICE("ACKs: in: %lu; processed: %lu; merged to: new %lu, old %lu", + LSQ_NOTICE("ACKs: in: %lu; processed: %lu; merged: %lu", conn->fc_stats.in.n_acks, conn->fc_stats.in.n_acks_proc, - conn->fc_stats.in.n_acks_merged[0], conn->fc_stats.in.n_acks_merged[1]); + conn->fc_stats.in.n_acks_merged); #endif while ((sitr = STAILQ_FIRST(&conn->fc_stream_ids_to_reset))) { @@ -1580,9 +1559,6 @@ process_stream_frame (struct full_conn *conn, lsquic_packet_in_t *packet_in, enc_level = lsquic_packet_in_enc_level(packet_in); if (!is_handshake_stream_id(conn, stream_frame->stream_id) -#if LSQUIC_ENABLE_HANDSHAKE_DISABLE - && !(conn->fc_conn.cn_flags & LSCONN_NO_CRYPTO) -#endif && enc_level == ENC_LEV_CLEAR) { lsquic_malo_put(stream_frame); @@ -1863,28 +1839,21 @@ log_invalid_ack_frame (struct full_conn *conn, const unsigned char *p, int parsed_len, const struct ack_info *acki) { char *buf; - size_t sz; buf = malloc(0x1000); - if (buf) + if (!buf) { - lsquic_senhist_tostr(&conn->fc_send_ctl.sc_senhist, buf, 0x1000); - LSQ_WARN("send history: %s", buf); - lsquic_hexdump(p, parsed_len, buf, 0x1000); - LSQ_WARN("raw ACK frame:\n%s", buf); - free(buf); - } - else LSQ_WARN("malloc failed"); - - buf = acki2str(acki, &sz); - if (buf) - { - LSQ_WARN("parsed ACK frame: %.*s", (int) sz, buf); - free(buf); + return; } - else - LSQ_WARN("malloc failed"); + + lsquic_senhist_tostr(&conn->fc_send_ctl.sc_senhist, buf, 0x1000); + LSQ_WARN("send history: %s", buf); + lsquic_hexdump(p, parsed_len, buf, 0x1000); + LSQ_WARN("raw ACK frame:\n%s", buf); + lsquic_acki2str(acki, buf, 0x1000); + LSQ_WARN("parsed ACK frame: %s", buf); + free(buf); } @@ -1912,108 +1881,11 @@ process_ack (struct full_conn *conn, struct ack_info *acki, } -static int -process_saved_ack (struct full_conn *conn, int restore_parsed_ack, - lsquic_time_t now) -{ - struct ack_info *const acki = conn->fc_pub.mm->acki; - struct lsquic_packno_range range; - unsigned n_ranges, n_timestamps; - lsquic_time_t lack_delta; - int retval; - -#ifdef WIN32 - /* Useless initialization to mollify MSVC: */ - memset(&range, 0, sizeof(range)); - n_ranges = 0; - n_timestamps = 0; - lack_delta = 0; -#endif - - if (restore_parsed_ack) - { - n_ranges = acki->n_ranges; - n_timestamps = acki->n_timestamps; - lack_delta = acki->lack_delta; - range = acki->ranges[0]; - } - - acki->pns = PNS_APP; - acki->n_ranges = 1; - acki->n_timestamps = conn->fc_saved_ack_info.sai_n_timestamps; - acki->lack_delta = conn->fc_saved_ack_info.sai_lack_delta; - acki->ranges[0] = conn->fc_saved_ack_info.sai_range; - - retval = process_ack(conn, acki, conn->fc_saved_ack_received, now); - - if (restore_parsed_ack) - { - acki->n_ranges = n_ranges; - acki->n_timestamps = n_timestamps; - acki->lack_delta = lack_delta; - acki->ranges[0] = range; - } - - return retval; -} - - -static int -new_ack_is_superset (const struct short_ack_info *old, const struct ack_info *new) -{ - const struct lsquic_packno_range *new_range; - - new_range = &new->ranges[ new->n_ranges - 1 ]; - return new_range->low <= old->sai_range.low - && new_range->high >= old->sai_range.high; -} - - -static int -merge_saved_to_new (const struct short_ack_info *old, struct ack_info *new) -{ - struct lsquic_packno_range *smallest_range; - - assert(new->n_ranges > 1); - smallest_range = &new->ranges[ new->n_ranges - 1 ]; - if (old->sai_range.high <= smallest_range->high - && old->sai_range.high >= smallest_range->low - && old->sai_range.low < smallest_range->low) - { - smallest_range->low = old->sai_range.low; - return 1; - } - else - return 0; -} - - -static int -merge_new_to_saved (struct short_ack_info *old, const struct ack_info *new) -{ - const struct lsquic_packno_range *new_range; - - assert(new->n_ranges == 1); - new_range = &new->ranges[0]; - /* Only merge if new is higher, for simplicity. This is also the - * expected case. - */ - if (new_range->high > old->sai_range.high - && new_range->low > old->sai_range.low) - { - old->sai_range.high = new_range->high; - return 1; - } - else - return 0; -} - - static unsigned process_ack_frame (struct full_conn *conn, lsquic_packet_in_t *packet_in, const unsigned char *p, size_t len) { - struct ack_info *const new_acki = conn->fc_pub.mm->acki; + struct ack_info *new_acki; int parsed_len; lsquic_time_t warn_time; @@ -2021,6 +1893,11 @@ process_ack_frame (struct full_conn *conn, lsquic_packet_in_t *packet_in, ++conn->fc_stats.in.n_acks; #endif + if (conn->fc_flags & FC_HAVE_SAVED_ACK) + new_acki = conn->fc_pub.mm->acki; + else + new_acki = &conn->fc_ack; + parsed_len = conn->fc_conn.cn_pf->pf_parse_ack_frame(p, len, new_acki, 0); if (parsed_len < 0) goto err; @@ -2040,77 +1917,33 @@ process_ack_frame (struct full_conn *conn, lsquic_packet_in_t *packet_in, EV_LOG_ACK_FRAME_IN(LSQUIC_LOG_CONN_ID, new_acki); conn->fc_max_ack_packno = packet_in->pi_packno; - if (conn->fc_flags & FC_HAVE_SAVED_ACK) + if (new_acki == &conn->fc_ack) { - LSQ_DEBUG("old ack [%"PRIu64"-%"PRIu64"]", - conn->fc_saved_ack_info.sai_range.high, - conn->fc_saved_ack_info.sai_range.low); - const int is_superset = new_ack_is_superset(&conn->fc_saved_ack_info, - new_acki); - const int is_1range = new_acki->n_ranges == 1; - switch ( - (is_superset << 1) - | (is_1range << 0)) - /* | | - | | - V V */ { - case (0 << 1) | (0 << 0): - if (merge_saved_to_new(&conn->fc_saved_ack_info, new_acki)) - { + LSQ_DEBUG("Saved ACK"); + conn->fc_flags |= FC_HAVE_SAVED_ACK; + conn->fc_saved_ack_received = packet_in->pi_received; + } + else + { + if (0 == lsquic_merge_acks(&conn->fc_ack, new_acki)) + { #if LSQUIC_CONN_STATS - ++conn->fc_stats.in.n_acks_merged[0] + ++conn->fc_stats.in.n_acks_merged; #endif - ; - } - else - process_saved_ack(conn, 1, packet_in->pi_received); - conn->fc_flags &= ~FC_HAVE_SAVED_ACK; - if (0 != process_ack(conn, new_acki, packet_in->pi_received, + LSQ_DEBUG("merged into saved ACK, getting %s", + (lsquic_acki2str(&conn->fc_ack, conn->fc_pub.mm->ack_str, + MAX_ACKI_STR_SZ), conn->fc_pub.mm->ack_str)); + } + else + { + LSQ_DEBUG("could not merge new ACK into saved ACK"); + if (0 != process_ack(conn, &conn->fc_ack, packet_in->pi_received, packet_in->pi_received)) goto err; - break; - case (0 << 1) | (1 << 0): - if (merge_new_to_saved(&conn->fc_saved_ack_info, new_acki)) - { -#if LSQUIC_CONN_STATS - ++conn->fc_stats.in.n_acks_merged[1] -#endif - ; - } - else - { - process_saved_ack(conn, 1, packet_in->pi_received); - conn->fc_saved_ack_info.sai_n_timestamps = new_acki->n_timestamps; - conn->fc_saved_ack_info.sai_range = new_acki->ranges[0]; - } - conn->fc_saved_ack_info.sai_lack_delta = new_acki->lack_delta; - conn->fc_saved_ack_received = packet_in->pi_received; - break; - case (1 << 1) | (0 << 0): - conn->fc_flags &= ~FC_HAVE_SAVED_ACK; - if (0 != process_ack(conn, new_acki, packet_in->pi_received, - packet_in->pi_received)) - goto err; - break; - case (1 << 1) | (1 << 0): - conn->fc_saved_ack_info.sai_n_timestamps = new_acki->n_timestamps; - conn->fc_saved_ack_info.sai_lack_delta = new_acki->lack_delta; - conn->fc_saved_ack_info.sai_range = new_acki->ranges[0]; - conn->fc_saved_ack_received = packet_in->pi_received; - break; + conn->fc_ack = *new_acki; } + conn->fc_saved_ack_received = packet_in->pi_received; } - else if (new_acki->n_ranges == 1) - { - conn->fc_saved_ack_info.sai_n_timestamps = new_acki->n_timestamps; - conn->fc_saved_ack_info.sai_lack_delta = new_acki->lack_delta; - conn->fc_saved_ack_info.sai_range = new_acki->ranges[0]; - conn->fc_saved_ack_received = packet_in->pi_received; - conn->fc_flags |= FC_HAVE_SAVED_ACK; - } - else if (0 != process_ack(conn, new_acki, packet_in->pi_received, - packet_in->pi_received)) - goto err; return parsed_len; @@ -2439,17 +2272,7 @@ reconstruct_packet_number (struct full_conn *conn, lsquic_packet_in_t *packet_in static enum dec_packin conn_decrypt_packet (struct full_conn *conn, lsquic_packet_in_t *packet_in) { -#if LSQUIC_ENABLE_HANDSHAKE_DISABLE - if (conn->fc_conn.cn_flags & LSCONN_NO_CRYPTO) - { - (void) lsquic_conn_copy_and_release_pi_data(&conn->fc_conn, - conn->fc_enpub, packet_in); - packet_in->pi_flags |= PI_DECRYPTED; - return DECPI_OK; - } - else -#endif - return conn->fc_conn.cn_esf_c->esf_decrypt_packet( + return conn->fc_conn.cn_esf_c->esf_decrypt_packet( conn->fc_conn.cn_enc_session, conn->fc_enpub, &conn->fc_conn, packet_in); } @@ -3460,7 +3283,7 @@ full_conn_ci_tick (lsquic_conn_t *lconn, lsquic_time_t now) if (conn->fc_flags & FC_HAVE_SAVED_ACK) { (void) /* If there is an error, we'll fail shortly */ - process_saved_ack(conn, 0, now); + process_ack(conn, &conn->fc_ack, conn->fc_saved_ack_received, now); conn->fc_flags &= ~FC_HAVE_SAVED_ACK; } @@ -3780,6 +3603,8 @@ full_conn_ci_hsk_done (lsquic_conn_t *lconn, enum lsquic_hsk_status status) conn->fc_stream_ifs[STREAM_IF_STD].stream_if->on_zero_rtt_info); if (conn->fc_n_delayed_streams) create_delayed_streams(conn); + if (!(conn->fc_flags & FC_SERVER)) + lsquic_send_ctl_begin_optack_detection(&conn->fc_send_ctl); } } diff --git a/src/liblsquic/lsquic_full_conn_ietf.c b/src/liblsquic/lsquic_full_conn_ietf.c index 245b6cbfe..02fbba3df 100644 --- a/src/liblsquic/lsquic_full_conn_ietf.c +++ b/src/liblsquic/lsquic_full_conn_ietf.c @@ -313,7 +313,6 @@ struct ietf_full_conn struct transport_params ifc_peer_param; STAILQ_HEAD(, stream_id_to_ss) ifc_stream_ids_to_ss; - struct short_ack_info ifc_saved_ack_info; lsquic_time_t ifc_saved_ack_received; lsquic_packno_t ifc_max_ack_packno[N_PNS]; lsquic_packno_t ifc_max_non_probing; @@ -387,6 +386,7 @@ struct ietf_full_conn */ lsquic_time_t ifc_idle_to; lsquic_time_t ifc_ping_period; + struct ack_info ifc_ack; }; #define CUR_CPATH(conn_) (&(conn_)->ifc_paths[(conn_)->ifc_cur_path_id]) @@ -1214,6 +1214,7 @@ lsquic_ietf_full_conn_server_new (struct lsquic_engine_public *enpub, 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 < N_PNS; ++pns) { @@ -1386,12 +1387,13 @@ generate_ack_frame_for_pns (struct ietf_full_conn *conn, else conn->ifc_flags &= ~IFC_ACK_HAD_MISS; LSQ_DEBUG("Put %d bytes of ACK frame into packet on outgoing queue", w); + /* TODO: randomize the 20 to be 20 +/- 8 */ if (conn->ifc_n_cons_unretx >= 20 && !lsquic_send_ctl_have_outgoing_retx_frames(&conn->ifc_send_ctl)) { - LSQ_DEBUG("schedule MAX_DATA frame after %u non-retx " + LSQ_DEBUG("schedule PING frame after %u non-retx " "packets sent", conn->ifc_n_cons_unretx); - conn->ifc_send_flags |= SF_SEND_MAX_DATA; + conn->ifc_send_flags |= SF_SEND_PING; } conn->ifc_n_slack_akbl[pns] = 0; @@ -2874,7 +2876,12 @@ ietf_full_conn_ci_hsk_done (struct lsquic_conn *lconn, { case LSQ_HSK_OK: case LSQ_HSK_0RTT_OK: - if (0 != handshake_ok(lconn)) + 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"); @@ -3544,8 +3551,8 @@ generate_connection_close_packet (struct ietf_full_conn *conn) } -static int -generate_ping_frame (struct ietf_full_conn *conn) +static void +generate_ping_frame (struct ietf_full_conn *conn, lsquic_time_t unused) { struct lsquic_packet_out *packet_out; int sz; @@ -3554,19 +3561,19 @@ generate_ping_frame (struct ietf_full_conn *conn) if (!packet_out) { LSQ_DEBUG("cannot get writeable packet for PING frame"); - return 1; + 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 1; + 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"); - return 0; + conn->ifc_send_flags &= ~SF_SEND_PING; } @@ -3821,103 +3828,6 @@ process_ack (struct ietf_full_conn *conn, struct ack_info *acki, } -static int -process_saved_ack (struct ietf_full_conn *conn, int restore_parsed_ack, - lsquic_time_t now) -{ - struct ack_info *const acki = conn->ifc_pub.mm->acki; - struct lsquic_packno_range range; - unsigned n_ranges, n_timestamps; - lsquic_time_t lack_delta; - int retval; - -#ifdef WIN32 - /* Useless initialization to mollify MSVC: */ - memset(&range, 0, sizeof(range)); - n_ranges = 0; - n_timestamps = 0; - lack_delta = 0; -#endif - - if (restore_parsed_ack) - { - n_ranges = acki->n_ranges; - n_timestamps = acki->n_timestamps; - lack_delta = acki->lack_delta; - range = acki->ranges[0]; - } - - acki->pns = PNS_APP; - acki->n_ranges = 1; - acki->n_timestamps = conn->ifc_saved_ack_info.sai_n_timestamps; - acki->lack_delta = conn->ifc_saved_ack_info.sai_lack_delta; - acki->ranges[0] = conn->ifc_saved_ack_info.sai_range; - - retval = process_ack(conn, acki, conn->ifc_saved_ack_received, now); - - if (restore_parsed_ack) - { - acki->n_ranges = n_ranges; - acki->n_timestamps = n_timestamps; - acki->lack_delta = lack_delta; - acki->ranges[0] = range; - } - - return retval; -} - - -static int -new_ack_is_superset (const struct short_ack_info *old, const struct ack_info *new) -{ - const struct lsquic_packno_range *new_range; - - new_range = &new->ranges[ new->n_ranges - 1 ]; - return new_range->low <= old->sai_range.low - && new_range->high >= old->sai_range.high; -} - - -static int -merge_saved_to_new (const struct short_ack_info *old, struct ack_info *new) -{ - struct lsquic_packno_range *smallest_range; - - assert(new->n_ranges > 1); - smallest_range = &new->ranges[ new->n_ranges - 1 ]; - if (old->sai_range.high <= smallest_range->high - && old->sai_range.high >= smallest_range->low - && old->sai_range.low < smallest_range->low) - { - smallest_range->low = old->sai_range.low; - return 1; - } - else - return 0; -} - - -static int -merge_new_to_saved (struct short_ack_info *old, const struct ack_info *new) -{ - const struct lsquic_packno_range *new_range; - - assert(new->n_ranges == 1); - new_range = &new->ranges[0]; - /* Only merge if new is higher, for simplicity. This is also the - * expected case. - */ - if (new_range->high > old->sai_range.high - && new_range->low > old->sai_range.low) - { - old->sai_range.high = new_range->high; - return 1; - } - else - return 0; -} - - static unsigned process_path_challenge_frame (struct ietf_full_conn *conn, struct lsquic_packet_in *packet_in, const unsigned char *p, size_t len) @@ -4571,11 +4481,16 @@ 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 *const new_acki = conn->ifc_pub.mm->acki; + struct ack_info *new_acki; enum packnum_space pns; int parsed_len; lsquic_time_t warn_time; + 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) @@ -4604,67 +4519,32 @@ process_ack_frame (struct ietf_full_conn *conn, 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; - if (pns != PNS_APP) /* Don't bother optimizing non-APP */ - goto process_ack; - if (conn->ifc_flags & IFC_HAVE_SAVED_ACK) + /* Only cache ACKs for PNS_APP */ + if (pns == PNS_APP && new_acki == &conn->ifc_ack) { - LSQ_DEBUG("old ack [%"PRIu64"-%"PRIu64"]", - conn->ifc_saved_ack_info.sai_range.high, - conn->ifc_saved_ack_info.sai_range.low); - const int is_superset = new_ack_is_superset(&conn->ifc_saved_ack_info, - new_acki); - const int is_1range = new_acki->n_ranges == 1; - switch ( - (is_superset << 1) - | (is_1range << 0)) - /* | | - | | - V V */ { - case (0 << 1) | (0 << 0): - if (!merge_saved_to_new(&conn->ifc_saved_ack_info, new_acki)) - process_saved_ack(conn, 1, packet_in->pi_received); - conn->ifc_flags &= ~IFC_HAVE_SAVED_ACK; - if (0 != process_ack(conn, new_acki, packet_in->pi_received, - packet_in->pi_received)) - goto err; - break; - case (0 << 1) | (1 << 0): - if (!merge_new_to_saved(&conn->ifc_saved_ack_info, new_acki)) - { - process_saved_ack(conn, 1, packet_in->pi_received); - conn->ifc_saved_ack_info.sai_n_timestamps = new_acki->n_timestamps; - conn->ifc_saved_ack_info.sai_range = new_acki->ranges[0]; - } - conn->ifc_saved_ack_info.sai_lack_delta = new_acki->lack_delta; - conn->ifc_saved_ack_received = packet_in->pi_received; - break; - case (1 << 1) | (0 << 0): - conn->ifc_flags &= ~IFC_HAVE_SAVED_ACK; - if (0 != process_ack(conn, new_acki, packet_in->pi_received, - packet_in->pi_received)) - goto err; - break; - case (1 << 1) | (1 << 0): - conn->ifc_saved_ack_info.sai_n_timestamps = new_acki->n_timestamps; - conn->ifc_saved_ack_info.sai_lack_delta = new_acki->lack_delta; - conn->ifc_saved_ack_info.sai_range = new_acki->ranges[0]; - conn->ifc_saved_ack_received = packet_in->pi_received; - break; - } + LSQ_DEBUG("Saved ACK"); + conn->ifc_flags |= IFC_HAVE_SAVED_ACK; + conn->ifc_saved_ack_received = packet_in->pi_received; } - else if (new_acki->n_ranges == 1) + else if (pns == PNS_APP) { - conn->ifc_saved_ack_info.sai_n_timestamps = new_acki->n_timestamps; - conn->ifc_saved_ack_info.sai_n_timestamps = new_acki->n_timestamps; - conn->ifc_saved_ack_info.sai_lack_delta = new_acki->lack_delta; - conn->ifc_saved_ack_info.sai_range = new_acki->ranges[0]; - conn->ifc_saved_ack_received = packet_in->pi_received; - conn->ifc_flags |= IFC_HAVE_SAVED_ACK; + if (0 == lsquic_merge_acks(&conn->ifc_ack, new_acki)) + 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 { - process_ack: if (0 != process_ack(conn, new_acki, packet_in->pi_received, packet_in->pi_received)) goto err; @@ -5428,6 +5308,8 @@ static void try_queueing_ack (struct ietf_full_conn *conn, enum packnum_space pns, int was_missing, lsquic_time_t now) { + lsquic_time_t srtt, ack_timeout; + if (conn->ifc_n_slack_akbl[pns] >= MAX_RETR_PACKETS_SINCE_LAST_ACK || ((conn->ifc_flags & IFC_ACK_HAD_MISS) && was_missing)) { @@ -5441,18 +5323,16 @@ try_queueing_ack (struct ietf_full_conn *conn, enum packnum_space pns, } else if (conn->ifc_n_slack_akbl[pns] > 0) { -/* [draft-ietf-quic-transport-15] Section-7.16.3: - * - * The receiver's delayed acknowledgment timer SHOULD NOT exceed the - * current RTT estimate or the value it indicates in the "max_ack_delay" - * transport parameter - * - * TODO: Need to do MIN(ACK_TIMEOUT, RTT Estimate) - */ + /* 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(ACK_TIMEOUT, srtt / 4)); + else + ack_timeout = ACK_TIMEOUT; lsquic_alarmset_set(&conn->ifc_alset, AL_ACK_INIT + pns, - now + ACK_TIMEOUT); + now + ack_timeout); LSQ_DEBUG("%s ACK alarm set to %"PRIu64, lsquic_pns2str[pns], - now + ACK_TIMEOUT); + now + ack_timeout); } } @@ -6069,6 +5949,7 @@ static void (*const send_funcs[N_SEND])( [SEND_PATH_CHAL_PATH_1] = generate_path_chal_1, [SEND_PATH_RESP_PATH_0] = generate_path_resp_0, [SEND_PATH_RESP_PATH_1] = generate_path_resp_1, + [SEND_PING] = generate_ping_frame, }; @@ -6078,6 +5959,7 @@ static void (*const send_funcs[N_SEND])( |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_RESP_PATH_0|SF_SEND_PATH_RESP_PATH_1\ + |SF_SEND_PING\ |SF_SEND_STOP_SENDING) static enum tick_st @@ -6118,7 +6000,7 @@ ietf_full_conn_ci_tick (struct lsquic_conn *lconn, lsquic_time_t now) if (conn->ifc_flags & IFC_HAVE_SAVED_ACK) { (void) /* If there is an error, we'll fail shortly */ - process_saved_ack(conn, 0, now); + process_ack(conn, &conn->ifc_ack, conn->ifc_saved_ack_received, now); conn->ifc_flags &= ~IFC_HAVE_SAVED_ACK; } @@ -6290,12 +6172,9 @@ ietf_full_conn_ci_tick (struct lsquic_conn *lconn, lsquic_time_t now) if (conn->ifc_send_flags & SF_SEND_PING) { RETURN_IF_OUT_OF_PACKETS(); - if (0 == generate_ping_frame(conn)) - { - conn->ifc_send_flags &= ~SF_SEND_PING; - CLOSE_IF_NECESSARY(); - assert(lsquic_send_ctl_n_scheduled(&conn->ifc_send_ctl) != 0); - } + generate_ping_frame(conn, now); + CLOSE_IF_NECESSARY(); + assert(lsquic_send_ctl_n_scheduled(&conn->ifc_send_ctl) != 0); } else { diff --git a/src/liblsquic/lsquic_handshake.c b/src/liblsquic/lsquic_handshake.c index 9bc23cda5..a1cbafc45 100644 --- a/src/liblsquic/lsquic_handshake.c +++ b/src/liblsquic/lsquic_handshake.c @@ -2199,7 +2199,12 @@ gen_iasn_key (X509 *cert, unsigned char *const out, size_t const sz) } -static enum { GET_SNI_OK, GET_SNI_ERR, GET_SNI_DELAYED, } +static enum { + GET_SNI_OK, + GET_SNI_ERR, +} + + get_sni_SSL_CTX(struct lsquic_enc_session *enc_session, lsquic_lookup_cert_f cb, void *cb_ctx, const struct sockaddr *local) { @@ -2315,10 +2320,6 @@ handle_chlo_frames_data(const uint8_t *data, int len, lsquic_str_setto(&enc_session->chlo, (const char *)data, len); switch (get_sni_SSL_CTX(enc_session, cb, cb_ctx, local)) { - case GET_SNI_DELAYED: - ESHIST_APPEND(enc_session, ESHE_SNI_DELAYED); - LSQ_DEBUG("%s: sni delayed", __func__); - return HS_DELAYED; case GET_SNI_ERR: ESHIST_APPEND(enc_session, ESHE_SNI_FAIL); LSQ_DEBUG("handle_chlo_frames_data got data format error 2."); @@ -2690,8 +2691,6 @@ he2str (enum handshake_error he) case HS_SHLO: return "HS_SHLO"; case HS_1RTT: return "HS_1RTT"; case HS_SREJ: return "HS_SREJ"; - case HS_DELAYED: return "HS_DELAYED"; - case HS_PK_OFFLOAD: return "HS_PK_OFFLOAD"; default: assert(0); return ""; } @@ -3289,11 +3288,6 @@ lsquic_enc_session_handle_chlo(enc_session_t *enc_session_p, LSQ_DEBUG("lsquic_enc_session_handle_chlo call gen_rej1_data"); len = gen_rej1_data(enc_session, out, *out_len, peer, t); - if (len == -2) - { - rtt = HS_PK_OFFLOAD; - goto end; - } if (len < 0) { rtt = HS_ERROR; @@ -3514,17 +3508,6 @@ lsquic_enc_session_get_ua (enc_session_t *enc_session_p) } -#if LSQUIC_ENABLE_HANDSHAKE_DISABLE -static void -set_handshake_completed (struct lsquic_enc_session *enc_session) -{ - enc_session->hsk_state = HSK_COMPLETED; -} - - -#endif - - #ifndef NDEBUG static uint8_t lsquic_enc_session_have_key (enc_session_t *enc_session_p) @@ -3773,34 +3756,6 @@ maybe_dispatch_zero_rtt (enc_session_t *enc_session_p, } -#if LSQUIC_ENABLE_HANDSHAKE_DISABLE -static ssize_t -just_copy_packet (const struct lsquic_conn *lconn, - const struct lsquic_packet_out *packet_out, unsigned char *buf, - size_t bufsz) -{ - int header_sz; - - header_sz = lconn->cn_pf->pf_gen_reg_pkt_header(lconn, packet_out, - buf, bufsz); - if (header_sz < 0) - return -1; - - if ((unsigned) header_sz + packet_out->po_data_sz > bufsz) - { - LSQ_WARN("packet too large (%d bytes) to encrypt", - header_sz + packet_out->po_data_sz); - return -1; - } - - memcpy(buf + header_sz, packet_out->po_data, packet_out->po_data_sz); - return header_sz + packet_out->po_data_sz; -} - - -#endif - - static enum enc_packout gquic_encrypt_packet (enc_session_t *enc_session_p, const struct lsquic_engine_public *enpub, @@ -3809,7 +3764,6 @@ gquic_encrypt_packet (enc_session_t *enc_session_p, struct lsquic_enc_session *const enc_session = enc_session_p; ssize_t enc_sz; size_t bufsz; - unsigned sent_sz; unsigned char *buf; int ipv6; @@ -3828,20 +3782,8 @@ gquic_encrypt_packet (enc_session_t *enc_session_p, return ENCPA_NOMEM; } -#if LSQUIC_ENABLE_HANDSHAKE_DISABLE - if (getenv("LSQUIC_DISABLE_HANDSHAKE")) - { - enc_sz = just_copy_packet(lconn, packet_out, buf, bufsz); - sent_sz = enc_sz + GQUIC_PACKET_HASH_SZ; - } - else -#endif - { - enc_sz = gquic_really_encrypt_packet(enc_session, - lconn, packet_out, buf, bufsz); - sent_sz = enc_sz; - } - + enc_sz = gquic_really_encrypt_packet(enc_session, + lconn, packet_out, buf, bufsz); if (enc_sz < 0) { enpub->enp_pmi->pmi_return(enpub->enp_pmi_ctx, @@ -3851,7 +3793,7 @@ gquic_encrypt_packet (enc_session_t *enc_session_p, packet_out->po_enc_data = buf; packet_out->po_enc_data_sz = enc_sz; - packet_out->po_sent_sz = sent_sz; + packet_out->po_sent_sz = enc_sz; packet_out->po_flags &= ~PO_IPv6; packet_out->po_flags |= PO_ENCRYPTED|PO_SENT_SZ|(ipv6 << POIPv6_SHIFT); @@ -4349,9 +4291,6 @@ struct enc_session_funcs_gquic lsquic_enc_session_gquic_gquic_1 = .esf_get_enc_key_nonce_f = lsquic_enc_session_get_enc_key_nonce_f, .esf_get_dec_key_nonce_f = lsquic_enc_session_get_dec_key_nonce_f, #endif /* !defined(NDEBUG) */ -#if LSQUIC_ENABLE_HANDSHAKE_DISABLE - .esf_set_handshake_completed = set_handshake_completed, -#endif .esf_create_client = lsquic_enc_session_create_client, .esf_gen_chlo = lsquic_enc_session_gen_chlo, .esf_handle_chlo_reply = lsquic_enc_session_handle_chlo_reply, diff --git a/src/liblsquic/lsquic_hspack_valid.c b/src/liblsquic/lsquic_hspack_valid.c index a74406f1e..0d07942a4 100644 --- a/src/liblsquic/lsquic_hspack_valid.c +++ b/src/liblsquic/lsquic_hspack_valid.c @@ -104,7 +104,8 @@ lsquic_is_valid_hs_packet (struct lsquic_engine *engine, case 0x80|0x00|0x20|0x10|0x08: case 0x80|0x40|0x20|0x10|0x00: case 0x80|0x00|0x20|0x10|0x00: - is_valid = lsquic_is_valid_iquic_hs_packet(buf, bufsz, &tag); + is_valid = bufsz >= IQUIC_MIN_INIT_PACKET_SZ + && lsquic_is_valid_iquic_hs_packet(buf, bufsz, &tag); break; /* 1X00 XGGG: ID-22 long header */ case 0x80|0x40|0x00|0x00|0x08: @@ -121,8 +122,8 @@ lsquic_is_valid_hs_packet (struct lsquic_engine *engine, case 0x80|0x00|0x20|0x00|0x08: case 0x80|0x40|0x20|0x00|0x00: case 0x80|0x00|0x20|0x00|0x00: - is_valid = lsquic_is_valid_ietf_v1_or_Q046plus_hs_packet(buf, bufsz, - &tag); + is_valid = bufsz >= IQUIC_MIN_INIT_PACKET_SZ + && lsquic_is_valid_ietf_v1_or_Q046plus_hs_packet(buf, bufsz, &tag); break; /* 01XX XGGG: ID-22 short header */ case 0x00|0x40|0x00|0x00|0x00: diff --git a/src/liblsquic/lsquic_mini_conn.c b/src/liblsquic/lsquic_mini_conn.c index 5dd4406ca..96db5841d 100644 --- a/src/liblsquic/lsquic_mini_conn.c +++ b/src/liblsquic/lsquic_mini_conn.c @@ -35,6 +35,7 @@ #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" @@ -60,9 +61,6 @@ static const struct conn_iface mini_conn_iface_standard; static const struct conn_iface mini_conn_iface_standard_Q050; -#if LSQUIC_ENABLE_HANDSHAKE_DISABLE -static const struct conn_iface mini_conn_iface_disable_handshake; -#endif #if LSQUIC_KEEP_MINICONN_HISTORY @@ -156,18 +154,29 @@ mini_destroy_packet (struct mini_conn *mc, struct lsquic_packet_out *packet_out) static int -packet_in_is_ok (const struct lsquic_packet_in *packet_in) +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 (packet_in->pi_data_sz < 200) /* TODO: 64 packets now */ - { /* mini connection is limited to 32 packets, yet it must send out REJ - * and SHLO, which are about 4 KB combined. + + 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; @@ -184,23 +193,16 @@ mini_conn_new (struct lsquic_engine_public *enp, struct mini_conn *mc; const struct conn_iface *conn_iface; -#if LSQUIC_ENABLE_HANDSHAKE_DISABLE - if (getenv("LSQUIC_DISABLE_HANDSHAKE")) - conn_iface = &mini_conn_iface_disable_handshake; - else -#endif + if (!packet_in_is_ok(version, packet_in)) + return NULL; + switch (version) { - if (!packet_in_is_ok(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; - } + 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); @@ -217,12 +219,7 @@ mini_conn_new (struct lsquic_engine_public *enp, TAILQ_INIT(&mc->mc_packets_out); mc->mc_enpub = enp; mc->mc_created = packet_in->pi_received; -#if LSQUIC_ENABLE_HANDSHAKE_DISABLE - if (conn_iface == &mini_conn_iface_disable_handshake) - mc->mc_path.np_pack_size = 1370; - else -#endif - mc->mc_path.np_pack_size = packet_in->pi_data_sz; + 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]); @@ -675,17 +672,7 @@ record_largest_recv (struct mini_conn *mc, lsquic_time_t t) static enum dec_packin conn_decrypt_packet (struct mini_conn *conn, lsquic_packet_in_t *packet_in) { -#if LSQUIC_ENABLE_HANDSHAKE_DISABLE - if (getenv("LSQUIC_DISABLE_HANDSHAKE")) - { - (void) lsquic_conn_copy_and_release_pi_data(&conn->mc_conn, - conn->mc_enpub, packet_in); - packet_in->pi_flags |= PI_DECRYPTED; - return 0; - } - else -#endif - return conn->mc_conn.cn_esf_c->esf_decrypt_packet( + return conn->mc_conn.cn_esf_c->esf_decrypt_packet( conn->mc_conn.cn_enc_session, conn->mc_enpub, &conn->mc_conn, packet_in); } @@ -2059,44 +2046,6 @@ mini_conn_ci_destroy (struct lsquic_conn *lconn) } -#if LSQUIC_ENABLE_HANDSHAKE_DISABLE -static void -mini_conn_ci_packet_in_disable_handshake (struct lsquic_conn *lconn, - struct lsquic_packet_in *packet_in) -{ - struct mini_conn *mc = (struct mini_conn *) lconn; - if (0 == (mc->mc_flags & MC_ERROR)) - { - if (packet_in->pi_packno > MINICONN_MAX_PACKETS) - mc->mc_flags |= MC_ERROR; - else if (!(mc->mc_received_packnos & - MCONN_PACKET_MASK(packet_in->pi_packno))) - { - lsquic_packet_in_upref(packet_in); - TAILQ_INSERT_TAIL(&mc->mc_packets_in, packet_in, pi_next); - mc->mc_received_packnos |= MCONN_PACKET_MASK(packet_in->pi_packno); - } - } -} - - -static enum tick_st -mini_conn_ci_tick_disable_handshake (struct lsquic_conn *lconn, - lsquic_time_t now) -{ - struct mini_conn *mc = (struct mini_conn *) lconn; - LSQ_WARN("handshake disabled, promote me now"); - lconn->cn_flags |= LSCONN_HANDSHAKE_DONE; - mc->mc_conn.cn_enc_session = - mc->mc_conn.cn_esf.g->esf_create_server(lconn->cn_cid, mc->mc_enpub, 0); - mc->mc_conn.cn_esf.g->esf_set_handshake_completed(mc->mc_conn.cn_enc_session); - return TICK_PROMOTE; -} - - -#endif - - static struct lsquic_engine * mini_conn_ci_get_engine (struct lsquic_conn *lconn) { @@ -2263,25 +2212,6 @@ static const struct conn_iface mini_conn_iface_standard_Q050 = { }; -#if LSQUIC_ENABLE_HANDSHAKE_DISABLE -static const struct conn_iface mini_conn_iface_disable_handshake = { - .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_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_disable_handshake, - .ci_packet_not_sent = mini_conn_ci_packet_not_sent, - .ci_packet_sent = mini_conn_ci_packet_sent, - .ci_tick = mini_conn_ci_tick_disable_handshake, - .ci_tls_alert = mini_conn_ci_tls_alert, -}; -#endif - typedef char largest_recv_holds_at_least_16_seconds[ ((1 << (sizeof(((struct mini_conn *) 0)->mc_largest_recv) * 8)) / 1000000 >= 16) - 1]; diff --git a/src/liblsquic/lsquic_mini_conn_ietf.c b/src/liblsquic/lsquic_mini_conn_ietf.c index 2a1dc31d1..613d792e5 100644 --- a/src/liblsquic/lsquic_mini_conn_ietf.c +++ b/src/liblsquic/lsquic_mini_conn_ietf.c @@ -393,6 +393,13 @@ static const struct crypto_stream_if crypto_stream_if = static int is_first_packet_ok (const struct lsquic_packet_in *packet_in) { + if (packet_in->pi_data_sz < IQUIC_MIN_INIT_PACKET_SZ) + { + /* [draft-ietf-quic-transport-24] Section 14 */ + LSQ_LOG1(LSQ_LOG_DEBUG, "incoming packet too small: %hu bytes", + packet_in->pi_data_sz); + return 0; + } /* TODO: Move decryption of the first packet into this function? */ return 1; /* TODO */ } diff --git a/src/liblsquic/lsquic_mm.c b/src/liblsquic/lsquic_mm.c index b9881cb83..b7d300401 100644 --- a/src/liblsquic/lsquic_mm.c +++ b/src/liblsquic/lsquic_mm.c @@ -88,6 +88,7 @@ lsquic_mm_init (struct lsquic_mm *mm) mm->malo.dcid_elem = lsquic_malo_create(sizeof(struct dcid_elem)); mm->malo.stream_hq_frame = lsquic_malo_create(sizeof(struct stream_hq_frame)); + mm->ack_str = malloc(MAX_ACKI_STR_SZ); #if LSQUIC_USE_POOLS TAILQ_INIT(&mm->free_packets_in); for (i = 0; i < MM_N_OUT_BUCKETS; ++i) @@ -99,7 +100,8 @@ lsquic_mm_init (struct lsquic_mm *mm) #endif if (mm->acki && mm->malo.stream_frame && mm->malo.stream_rec_arr && mm->malo.mini_conn && mm->malo.mini_conn_ietf && mm->malo.packet_in - && mm->malo.stream_hq_frame) + && mm->malo.packet_out && mm->malo.dcid_elem + && mm->malo.stream_hq_frame && mm->ack_str) { return 0; } @@ -128,6 +130,7 @@ lsquic_mm_cleanup (struct lsquic_mm *mm) lsquic_malo_destroy(mm->malo.stream_rec_arr); lsquic_malo_destroy(mm->malo.mini_conn); lsquic_malo_destroy(mm->malo.mini_conn_ietf); + free(mm->ack_str); #if LSQUIC_USE_POOLS for (i = 0; i < MM_N_OUT_BUCKETS; ++i) diff --git a/src/liblsquic/lsquic_mm.h b/src/liblsquic/lsquic_mm.h index e6836d7f2..51e5c6cf9 100644 --- a/src/liblsquic/lsquic_mm.h +++ b/src/liblsquic/lsquic_mm.h @@ -48,6 +48,7 @@ struct lsquic_mm { SLIST_HEAD(, packet_in_buf) packet_in_bufs[MM_N_IN_BUCKETS]; SLIST_HEAD(, four_k_page) four_k_pages; SLIST_HEAD(, sixteen_k_page) sixteen_k_pages; + char *ack_str; }; int diff --git a/src/liblsquic/lsquic_packet_ietf.h b/src/liblsquic/lsquic_packet_ietf.h index 42142503f..ec7f0cb67 100644 --- a/src/liblsquic/lsquic_packet_ietf.h +++ b/src/liblsquic/lsquic_packet_ietf.h @@ -20,4 +20,7 @@ */ #define MIN_INITIAL_DCID_LEN 8 +/* [draft-ietf-quic-transport-24] Section 8.1 */ +#define IQUIC_MIN_INIT_PACKET_SZ 1200 + #endif diff --git a/src/liblsquic/lsquic_packet_out.h b/src/liblsquic/lsquic_packet_out.h index 81bea18fc..3563c8ec5 100644 --- a/src/liblsquic/lsquic_packet_out.h +++ b/src/liblsquic/lsquic_packet_out.h @@ -110,7 +110,7 @@ typedef struct lsquic_packet_out PO_PNS_APP = (1 <<23), /* packet number space. */ PO_RETRY = (1 <<24), /* Retry packet */ PO_RETX = (1 <<25), /* Retransmitted packet: don't append to it */ - PO_UNUSED26 = (1 <<26), /* Unused */ + PO_POISON = (1 <<26), /* Used to detect opt-ACK attack */ PO_LOSS_REC = (1 <<27), /* This structure is a loss record */ /* Only one of PO_SCHED, PO_UNACKED, or PO_LOST can be set. If pressed * for room in the enum, we can switch to using two bits to represent diff --git a/src/liblsquic/lsquic_packints.h b/src/liblsquic/lsquic_packints.h index 27ec79940..fdf96699c 100644 --- a/src/liblsquic/lsquic_packints.h +++ b/src/liblsquic/lsquic_packints.h @@ -15,8 +15,10 @@ struct packet_interval { struct lsquic_packno_range range; }; +TAILQ_HEAD(pinhead, packet_interval); + struct packints { - TAILQ_HEAD(, packet_interval) pk_intervals; + struct pinhead pk_intervals; struct packet_interval *pk_cur; }; diff --git a/src/liblsquic/lsquic_parse.h b/src/liblsquic/lsquic_parse.h index bf1662e1b..bd0d722df 100644 --- a/src/liblsquic/lsquic_parse.h +++ b/src/liblsquic/lsquic_parse.h @@ -17,7 +17,6 @@ enum packet_out_flags; enum lsquic_version; enum stream_dir; -#define LSQUIC_PARSE_ACK_TIMESTAMPS 0 struct ack_info { @@ -28,31 +27,11 @@ struct ack_info * ran out of elements in `ranges'. */ } flags; - unsigned n_timestamps; /* 0 to 255 */ unsigned n_ranges; /* This is at least 1 */ /* Largest acked is ack_info.ranges[0].high */ lsquic_time_t lack_delta; uint64_t ecn_counts[4]; struct lsquic_packno_range ranges[256]; -#if LSQUIC_PARSE_ACK_TIMESTAMPS - struct { - /* Currently we just read these timestamps in (assuming it is - * compiled in, of course), but do not do anything with them. - * When we do, the representation of these fields should be - * switched to whatever is most appropriate/efficient. - */ - unsigned char packet_delta; - uint64_t delta_usec; - } timestamps[255]; -#endif -}; - - -struct short_ack_info -{ - unsigned sai_n_timestamps; - lsquic_time_t sai_lack_delta; - struct lsquic_packno_range sai_range; }; #define largest_acked(acki) (+(acki)->ranges[0].high) @@ -394,8 +373,16 @@ lsquic_gquic_po_header_sz (enum packet_out_flags flags); */ #define twobit_to_1248(bits) (1 << (bits)) -char * -acki2str (const struct ack_info *acki, size_t *sz); +#define ECN_COUNTS_STR " ECT(0): 01234567879012345678790;" \ + " ECT(1): 01234567879012345678790;" \ + " CE: 01234567879012345678790" +#define RANGES_TRUNCATED_STR " ranges truncated! " + +#define MAX_ACKI_STR_SZ (256 * (3 /* [-] */ + 20 /* ~0ULL */ * 2) \ + + sizeof(ECN_COUNTS_STR) + sizeof(RANGES_TRUNCATED_STR)) + +void +lsquic_acki2str (const struct ack_info *acki, char *, size_t); void lsquic_turn_on_fin_Q035_thru_Q046 (unsigned char *); @@ -407,4 +394,7 @@ lsquic_gquic_calc_packno_bits (lsquic_packno_t packno, unsigned lsquic_gquic_packno_bits2len (enum packno_bits); +int +lsquic_merge_acks (struct ack_info *dst, const struct ack_info *src); + #endif diff --git a/src/liblsquic/lsquic_parse_gquic_be.c b/src/liblsquic/lsquic_parse_gquic_be.c index 31c3af9a1..2d955e5d5 100644 --- a/src/liblsquic/lsquic_parse_gquic_be.c +++ b/src/liblsquic/lsquic_parse_gquic_be.c @@ -375,6 +375,7 @@ parse_ack_frame_without_blocks (const unsigned char *buf, size_t buf_len, const unsigned char type = buf[0]; const unsigned char *p = buf + 1; const unsigned char *const pend = buf + buf_len; + unsigned char n_timestamps; const int ack_block_len = twobit_to_1246(type & 3); /* mm */ const int largest_obs_len = twobit_to_1246((type >> 2) & 3); /* ll */ @@ -393,12 +394,12 @@ parse_ack_frame_without_blocks (const unsigned char *buf, size_t buf_len, ack->n_ranges = 1; - ack->n_timestamps = *p; + n_timestamps = *p; ++p; - if (ack->n_timestamps) + if (n_timestamps) { - unsigned timestamps_size = 5 + 3 * (ack->n_timestamps - 1); + unsigned timestamps_size = 5 + 3 * (n_timestamps - 1); CHECK_SPACE(timestamps_size, p, pend); p += timestamps_size; } @@ -419,6 +420,7 @@ parse_ack_frame_with_blocks (const unsigned char *buf, size_t buf_len, const unsigned char type = buf[0]; const unsigned char *p = buf + 1; const unsigned char *const pend = buf + buf_len; + unsigned char n_timestamps; assert((type & 0xC0) == 0x40); /* We're passed correct frame type */ @@ -461,31 +463,14 @@ parse_ack_frame_with_blocks (const unsigned char *buf, size_t buf_len, } ack->n_ranges = n; - ack->n_timestamps = *p; + n_timestamps = *p; ++p; - if (ack->n_timestamps) + if (n_timestamps) { -#if LSQUIC_PARSE_ACK_TIMESTAMPS - CHECK_SPACE(5, p , pend); - ack->timestamps[0].packet_delta = *p++; - memcpy(&ack->timestamps[0].delta_usec, p, 4); - p += 4; - unsigned i; - for (i = 1; i < ack->n_timestamps; ++i) - { - CHECK_SPACE(3, p , pend); - ack->timestamps[i].packet_delta = *p++; - uint64_t delta_time = read_float_time16(p); - p += 2; - ack->timestamps[i].delta_usec = - ack->timestamps[i - 1].delta_usec + delta_time; - } -#else - unsigned timestamps_size = 5 + 3 * (ack->n_timestamps - 1); + unsigned timestamps_size = 5 + 3 * (n_timestamps - 1); CHECK_SPACE(timestamps_size, p, pend); p += timestamps_size; -#endif } assert(p <= pend); diff --git a/src/liblsquic/lsquic_parse_gquic_common.c b/src/liblsquic/lsquic_parse_gquic_common.c index a1e085d12..c91eb7fd9 100644 --- a/src/liblsquic/lsquic_parse_gquic_common.c +++ b/src/liblsquic/lsquic_parse_gquic_common.c @@ -508,29 +508,12 @@ static const char *const ecn2str[4] = }; -#define ECN_COUNTS " ECT(0): 01234567879012345678790;" \ - " ECT(1): 01234567879012345678790;" \ - " CE: 01234567879012345678790" -#define RANGES_TRUNCATED " ranges truncated! " - -char * -acki2str (const struct ack_info *acki, size_t *sz) +void +lsquic_acki2str (const struct ack_info *acki, char *buf, size_t bufsz) { - size_t off, bufsz, nw; + size_t off, nw; enum ecn ecn; unsigned n; - char *buf; - - bufsz = acki->n_ranges * (3 /* [-] */ + 20 /* ~0ULL */ * 2) - + (acki->flags & AI_ECN ? sizeof(ECN_COUNTS) : 0) - + (acki->flags & AI_TRUNCATED ? sizeof(RANGES_TRUNCATED) : 0); - buf = malloc(bufsz); - if (!buf) - { - LSQ_WARN("%s: malloc(%zd) failure: %s", __func__, bufsz, - strerror(errno)); - return NULL; - } off = 0; for (n = 0; n < acki->n_ranges; ++n) @@ -538,18 +521,15 @@ acki2str (const struct ack_info *acki, size_t *sz) nw = snprintf(buf + off, bufsz - off, "[%"PRIu64"-%"PRIu64"]", acki->ranges[n].high, acki->ranges[n].low); if (nw > bufsz - off) - break; + return; off += nw; } if (acki->flags & AI_TRUNCATED) { - nw = snprintf(buf + off, bufsz - off, RANGES_TRUNCATED); + nw = snprintf(buf + off, bufsz - off, RANGES_TRUNCATED_STR); if (nw > bufsz - off) - { - nw = bufsz - off; - goto end; - } + return; off += nw; } @@ -560,14 +540,10 @@ acki2str (const struct ack_info *acki, size_t *sz) nw = snprintf(buf + off, bufsz - off, " %s: %"PRIu64"%.*s", ecn2str[ecn], acki->ecn_counts[ecn], ecn < 3, ";"); if (nw > bufsz - off) - break; + return; off += nw; } } - - end: - *sz = off; - return buf; } @@ -654,3 +630,68 @@ lsquic_gquic_calc_packno_bits (lsquic_packno_t packno, return bits; } + + +/* `dst' serves both as source and destination. `src' is the new frame */ +int +lsquic_merge_acks (struct ack_info *dst, const struct ack_info *src) +{ + const struct lsquic_packno_range *a, *a_end, *b, *b_end, **p; + struct lsquic_packno_range *out, *out_end; + unsigned i; + struct lsquic_packno_range out_ranges[256]; + + if (!(dst->n_ranges && src->n_ranges)) + return -1; + + a = dst->ranges; + a_end = a + dst->n_ranges; + b = src->ranges; + b_end = b + src->n_ranges; + out = out_ranges; + out_end = out + sizeof(out_ranges) / sizeof(out_ranges[0]); + + if (a->high >= b->high) + *out = *a; + else + *out = *b; + + while (1) + { + if (a < a_end && b < b_end) + { + if (a->high >= b->high) + p = &a; + else + p = &b; + } + else if (a < a_end) + p = &a; + else if (b < b_end) + p = &b; + else + { + ++out; + break; + } + + if ((*p)->high + 1 >= out->low) + out->low = (*p)->low; + else if (out + 1 < out_end) + *++out = **p; + else + return -1; + ++*p; + } + + if (src->flags & AI_ECN) + for (i = 0; i < sizeof(src->ecn_counts) + / sizeof(src->ecn_counts[0]); ++i) + dst->ecn_counts[i] += src->ecn_counts[i]; + dst->flags |= src->flags; + dst->lack_delta = src->lack_delta; + dst->n_ranges = out - out_ranges; + memcpy(dst->ranges, out_ranges, sizeof(out_ranges[0]) * dst->n_ranges); + + return 0; +} diff --git a/src/liblsquic/lsquic_parse_ietf_v1.c b/src/liblsquic/lsquic_parse_ietf_v1.c index 9430443fd..9db0a21fb 100644 --- a/src/liblsquic/lsquic_parse_ietf_v1.c +++ b/src/liblsquic/lsquic_parse_ietf_v1.c @@ -681,12 +681,6 @@ ietf_v1_parse_ack_frame (const unsigned char *const buf, size_t buf_len, ack->flags |= AI_ECN; } -#if LSQUIC_PARSE_ACK_TIMESTAMPS -#error Pasing ACK timestamps not supported -#else - ack->n_timestamps = 0; -#endif - return p - buf; } diff --git a/src/liblsquic/lsquic_rechist.c b/src/liblsquic/lsquic_rechist.c index 80c5d3e87..43e4df63d 100644 --- a/src/liblsquic/lsquic_rechist.c +++ b/src/liblsquic/lsquic_rechist.c @@ -30,6 +30,14 @@ lsquic_rechist_init (struct lsquic_rechist *rechist, rechist->rh_cutoff = ietf ? 0 : 1; lsquic_packints_init(&rechist->rh_pints); LSQ_DEBUG("instantiated received packet history"); +#if LSQUIC_ACK_ATTACK + const char *s = getenv("LSQUIC_ACK_ATTACK"); + if (s && atoi(s)) + { + LSQ_NOTICE("ACK attack mode ON!"); + rechist->rh_flags |= RH_ACK_ATTACK; + } +#endif } @@ -144,6 +152,23 @@ lsquic_rechist_largest_recv (const lsquic_rechist_t *rechist) const struct lsquic_packno_range * lsquic_rechist_first (lsquic_rechist_t *rechist) { +#if LSQUIC_ACK_ATTACK + if (rechist->rh_flags & RH_ACK_ATTACK) + { + /* This only performs the lazy variant of the attack. An aggressive + * attack would increase the value of high number. + */ + const struct lsquic_packno_range *range; + + range = lsquic_packints_first(&rechist->rh_pints); + if (!range) + return NULL; + rechist->rh_first = *range; + range = &TAILQ_LAST(&rechist->rh_pints.pk_intervals, pinhead)->range; + rechist->rh_first.low = range->low; + return &rechist->rh_first; + } +#endif return lsquic_packints_first(&rechist->rh_pints); } @@ -151,6 +176,10 @@ lsquic_rechist_first (lsquic_rechist_t *rechist) const struct lsquic_packno_range * lsquic_rechist_next (lsquic_rechist_t *rechist) { +#if LSQUIC_ACK_ATTACK + if (rechist->rh_flags & RH_ACK_ATTACK) + return NULL; +#endif return lsquic_packints_next(&rechist->rh_pints); } diff --git a/src/liblsquic/lsquic_rechist.h b/src/liblsquic/lsquic_rechist.h index e932e1b1b..784ceb0c2 100644 --- a/src/liblsquic/lsquic_rechist.h +++ b/src/liblsquic/lsquic_rechist.h @@ -23,7 +23,13 @@ struct lsquic_rechist { unsigned rh_n_packets; enum { RH_CUTOFF_SET = (1 << 0), +#if LSQUIC_ACK_ATTACK + RH_ACK_ATTACK = (1 << 1), +#endif } rh_flags; +#if LSQUIC_ACK_ATTACK + struct lsquic_packno_range rh_first; +#endif }; typedef struct lsquic_rechist lsquic_rechist_t; diff --git a/src/liblsquic/lsquic_send_ctl.c b/src/liblsquic/lsquic_send_ctl.c index 100f2e4e9..4fccbd5a4 100644 --- a/src/liblsquic/lsquic_send_ctl.c +++ b/src/liblsquic/lsquic_send_ctl.c @@ -10,6 +10,8 @@ #include #include +#include + #include "lsquic_types.h" #include "lsquic_int_types.h" #include "lsquic.h" @@ -110,6 +112,8 @@ send_ctl_retx_bytes_out (const struct lsquic_send_ctl *ctl); static unsigned send_ctl_all_bytes_out (const struct lsquic_send_ctl *ctl); +static void +send_ctl_reschedule_poison (struct lsquic_send_ctl *ctl); #ifdef NDEBUG static @@ -141,7 +145,7 @@ lsquic_send_ctl_have_unacked_stream_frames (const lsquic_send_ctl_t *ctl) const lsquic_packet_out_t *packet_out; TAILQ_FOREACH(packet_out, &ctl->sc_unacked_packets[PNS_APP], po_next) - if (0 == (packet_out->po_flags & PO_LOSS_REC) + if (0 == (packet_out->po_flags & (PO_LOSS_REC|PO_POISON)) && (packet_out->po_frame_types & ((1 << QUIC_FRAME_STREAM) | (1 << QUIC_FRAME_RST_STREAM)))) return 1; @@ -157,7 +161,7 @@ send_ctl_first_unacked_retx_packet (const struct lsquic_send_ctl *ctl, lsquic_packet_out_t *packet_out; TAILQ_FOREACH(packet_out, &ctl->sc_unacked_packets[pns], po_next) - if (0 == (packet_out->po_flags & PO_LOSS_REC) + if (0 == (packet_out->po_flags & (PO_LOSS_REC|PO_POISON)) && (packet_out->po_frame_types & ctl->sc_retx_frames)) return packet_out; @@ -172,7 +176,7 @@ send_ctl_last_unacked_retx_packet (const struct lsquic_send_ctl *ctl, lsquic_packet_out_t *packet_out; TAILQ_FOREACH_REVERSE(packet_out, &ctl->sc_unacked_packets[pns], lsquic_packets_tailq, po_next) - if (0 == (packet_out->po_flags & PO_LOSS_REC) + if (0 == (packet_out->po_flags & (PO_LOSS_REC|PO_POISON)) && (packet_out->po_frame_types & ctl->sc_retx_frames)) return packet_out; return NULL; @@ -354,6 +358,7 @@ lsquic_send_ctl_init (lsquic_send_ctl_t *ctl, struct lsquic_alarmset *alset, == LSCONN_IETF) ctl->sc_flags |= SC_SANITY_CHECK; #endif + ctl->sc_gap = UINT64_MAX - 1 /* Can't have +1 == 0 */; } @@ -494,7 +499,7 @@ send_ctl_unacked_append (struct lsquic_send_ctl *ctl, enum packnum_space pns; pns = lsquic_packet_out_pns(packet_out); - assert(0 == (packet_out->po_flags & PO_LOSS_REC)); + assert(0 == (packet_out->po_flags & (PO_LOSS_REC|PO_POISON))); TAILQ_INSERT_TAIL(&ctl->sc_unacked_packets[pns], packet_out, po_next); packet_out->po_flags |= PO_UNACKED; ctl->sc_bytes_unacked_all += packet_out_sent_sz(packet_out); @@ -569,6 +574,62 @@ send_ctl_sched_remove (struct lsquic_send_ctl *ctl, } +/* Poisoned packets are used to detect optimistic ACK attacks. We only + * use a single poisoned packet at a time. + */ +static int +send_ctl_add_poison (struct lsquic_send_ctl *ctl) +{ + struct lsquic_packet_out *poison; + + poison = lsquic_malo_get(ctl->sc_conn_pub->packet_out_malo); + if (!poison) + return -1; + + memset(poison, 0, sizeof(*poison)); + poison->po_flags = PO_UNACKED|PO_POISON; + poison->po_packno = ctl->sc_gap; + poison->po_loss_chain = poison; /* Won't be used, but just in case */ + TAILQ_INSERT_TAIL(&ctl->sc_unacked_packets[PNS_APP], poison, po_next); + LSQ_DEBUG("insert poisoned packet %"PRIu64, poison->po_packno); + ctl->sc_flags |= SC_POISON; + return 0; +} + + +static void +send_ctl_reschedule_poison (struct lsquic_send_ctl *ctl) +{ + struct lsquic_packet_out *poison; + enum lsq_log_level log_level; + lsquic_time_t approx_now; + + TAILQ_FOREACH(poison, &ctl->sc_unacked_packets[PNS_APP], po_next) + if (poison->po_flags & PO_POISON) + { + LSQ_DEBUG("remove poisoned packet %"PRIu64, poison->po_packno); + TAILQ_REMOVE(&ctl->sc_unacked_packets[PNS_APP], poison, po_next); + lsquic_malo_put(poison); + lsquic_send_ctl_begin_optack_detection(ctl); + ctl->sc_flags &= ~SC_POISON; + return; + } + + approx_now = ctl->sc_last_sent_time; + if (0 == ctl->sc_enpub->enp_last_warning[WT_NO_POISON] + || ctl->sc_enpub->enp_last_warning[WT_NO_POISON] + + WARNING_INTERVAL < approx_now) + { + ctl->sc_enpub->enp_last_warning[WT_NO_POISON] = approx_now; + log_level = LSQ_LOG_WARN; + } + else + log_level = LSQ_LOG_DEBUG; + LSQ_LOG(log_level, "odd: poisoned packet %"PRIu64" not found during " + "reschedule, flag: %d", ctl->sc_gap, !!(ctl->sc_flags & SC_POISON)); +} + + int lsquic_send_ctl_sent_packet (lsquic_send_ctl_t *ctl, struct lsquic_packet_out *packet_out) @@ -579,6 +640,13 @@ lsquic_send_ctl_sent_packet (lsquic_send_ctl_t *ctl, assert(!(packet_out->po_flags & PO_ENCRYPTED)); ctl->sc_last_sent_time = packet_out->po_sent; pns = lsquic_packet_out_pns(packet_out); + if (packet_out->po_packno == ctl->sc_gap + 1 && pns == PNS_APP) + { + assert(!(ctl->sc_flags & SC_POISON)); + lsquic_senhist_add(&ctl->sc_senhist, ctl->sc_gap); + if (0 != send_ctl_add_poison(ctl)) + return -1; + } LSQ_DEBUG("packet %"PRIu64" has been sent (frame types: %s)", packet_out->po_packno, lsquic_frame_types_to_str(frames, sizeof(frames), packet_out->po_frame_types)); @@ -640,7 +708,7 @@ static void send_ctl_destroy_packet (struct lsquic_send_ctl *ctl, struct lsquic_packet_out *packet_out) { - if (0 == (packet_out->po_flags & PO_LOSS_REC)) + if (0 == (packet_out->po_flags & (PO_LOSS_REC|PO_POISON))) lsquic_packet_out_destroy(packet_out, ctl->sc_enpub, packet_out->po_path->np_peer_ctx); else @@ -823,7 +891,7 @@ largest_retx_packet_number (const struct lsquic_send_ctl *ctl, TAILQ_FOREACH_REVERSE(packet_out, &ctl->sc_unacked_packets[pns], lsquic_packets_tailq, po_next) { - if (0 == (packet_out->po_flags & PO_LOSS_REC) + if (0 == (packet_out->po_flags & (PO_LOSS_REC|PO_POISON)) && (packet_out->po_frame_types & ctl->sc_retx_frames)) return packet_out->po_packno; } @@ -848,7 +916,7 @@ send_ctl_detect_losses (struct lsquic_send_ctl *ctl, enum packnum_space pns, { next = TAILQ_NEXT(packet_out, po_next); - if (packet_out->po_flags & PO_LOSS_REC) + if (packet_out->po_flags & (PO_LOSS_REC|PO_POISON)) continue; if (packet_out->po_packno + N_NACKS_BEFORE_RETX < @@ -1012,7 +1080,7 @@ lsquic_send_ctl_got_ack (lsquic_send_ctl_t *ctl, ecn_total_acked += lsquic_packet_out_ecn(packet_out) != ECN_NOT_ECT; ecn_ce_cnt += lsquic_packet_out_ecn(packet_out) == ECN_CE; one_rtt_cnt += lsquic_packet_out_enc_level(packet_out) == ENC_LEV_FORW; - if (0 == (packet_out->po_flags & PO_LOSS_REC)) + if (0 == (packet_out->po_flags & (PO_LOSS_REC|PO_POISON))) { packet_sz = packet_out_sent_sz(packet_out); send_ctl_unacked_remove(ctl, packet_out, packet_sz); @@ -1020,7 +1088,7 @@ lsquic_send_ctl_got_ack (lsquic_send_ctl_t *ctl, LSQ_DEBUG("acking via regular record %"PRIu64, packet_out->po_packno); } - else + else if (packet_out->po_flags & PO_LOSS_REC) { packet_sz = packet_out->po_sent_sz; TAILQ_REMOVE(&ctl->sc_unacked_packets[pns], packet_out, @@ -1033,6 +1101,12 @@ lsquic_send_ctl_got_ack (lsquic_send_ctl_t *ctl, packet_out->po_packno); #endif } + else + { + LSQ_WARN("poisoned packet %"PRIu64" acked", + packet_out->po_packno); + return -1; + } ack2ed[!!(packet_out->po_frame_types & (1 << QUIC_FRAME_ACK))] = packet_out->po_ack2ed; do_rtt |= packet_out->po_packno == largest_acked(acki); @@ -1114,6 +1188,8 @@ lsquic_send_ctl_got_ack (lsquic_send_ctl_t *ctl, lsquic_send_ctl_sanity_check(ctl); if (ctl->sc_ci->cci_end_ack) ctl->sc_ci->cci_end_ack(CGP(ctl), ctl->sc_bytes_unacked_all); + if (ctl->sc_gap < smallest_acked(acki)) + send_ctl_reschedule_poison(ctl); return 0; no_unacked_packets: @@ -1191,7 +1267,13 @@ send_ctl_next_lost (lsquic_send_ctl_t *ctl) static lsquic_packno_t send_ctl_next_packno (lsquic_send_ctl_t *ctl) { - return ++ctl->sc_cur_packno; + lsquic_packno_t packno; + + packno = ++ctl->sc_cur_packno; + if (packno == ctl->sc_gap) + packno = ++ctl->sc_cur_packno; + + return packno; } @@ -1216,7 +1298,7 @@ lsquic_send_ctl_cleanup (lsquic_send_ctl_t *ctl) TAILQ_REMOVE(&ctl->sc_unacked_packets[pns], packet_out, po_next); packet_out->po_flags &= ~PO_UNACKED; #ifndef NDEBUG - if (0 == (packet_out->po_flags & PO_LOSS_REC)) + if (0 == (packet_out->po_flags & (PO_LOSS_REC|PO_POISON))) { ctl->sc_bytes_unacked_all -= packet_out_sent_sz(packet_out); --ctl->sc_n_in_flight_all; @@ -1366,7 +1448,7 @@ send_ctl_expire (struct lsquic_send_ctl *ctl, enum packnum_space pns, packet_out; packet_out = next) { next = TAILQ_NEXT(packet_out, po_next); - if (0 == (packet_out->po_flags & PO_LOSS_REC)) + if (0 == (packet_out->po_flags & (PO_LOSS_REC|PO_POISON))) n_resubmitted += send_ctl_handle_lost_packet(ctl, packet_out, &next); } @@ -1433,7 +1515,7 @@ lsquic_send_ctl_do_sanity_check (const struct lsquic_send_ctl *ctl) prev_packno = packet_out->po_packno; prev_packno_set = 1; } - if (0 == (packet_out->po_flags & PO_LOSS_REC)) + if (0 == (packet_out->po_flags & (PO_LOSS_REC|PO_POISON))) { bytes += packet_out_sent_sz(packet_out); ++count; @@ -1620,6 +1702,8 @@ lsquic_send_ctl_next_packet_to_send (struct lsquic_send_ctl *ctl, size_t size) packet_out->po_lflags &= ~POL_LOSS_BIT; if (packet_out->po_header_type == HETY_NOT_SET) { + if (ctl->sc_gap + 1 == packet_out->po_packno) + ++ctl->sc_square_count; if (ctl->sc_square_count++ & 128) packet_out->po_lflags |= POL_SQUARE_BIT; else @@ -2734,7 +2818,7 @@ lsquic_send_ctl_empty_pns (struct lsquic_send_ctl *ctl, enum packnum_space pns) packet_out = next) { next = TAILQ_NEXT(packet_out, po_next); - if (packet_out->po_flags & PO_LOSS_REC) + if (packet_out->po_flags & (PO_LOSS_REC|PO_POISON)) TAILQ_REMOVE(&ctl->sc_unacked_packets[pns], packet_out, po_next); else { @@ -2849,3 +2933,13 @@ lsquic_send_ctl_cidlen_change (struct lsquic_send_ctl *ctl, else LSQ_DEBUG("no scheduled packets at the time of DCID change"); } + + +void +lsquic_send_ctl_begin_optack_detection (struct lsquic_send_ctl *ctl) +{ + unsigned char buf[1]; + + RAND_bytes(buf, sizeof(buf)); + ctl->sc_gap = ctl->sc_cur_packno + 1 + buf[0]; +} diff --git a/src/liblsquic/lsquic_send_ctl.h b/src/liblsquic/lsquic_send_ctl.h index 73fbbf21b..0f8d74db3 100644 --- a/src/liblsquic/lsquic_send_ctl.h +++ b/src/liblsquic/lsquic_send_ctl.h @@ -47,6 +47,7 @@ enum send_ctl_flags { SC_QL_BITS = 1 << 14, SC_SANITY_CHECK = 1 << 15, SC_CIDLEN = 1 << 16, /* sc_cidlen is set */ + SC_POISON = 1 << 17, /* poisoned packet exists */ }; typedef struct lsquic_send_ctl { @@ -127,6 +128,7 @@ typedef struct lsquic_send_ctl { unsigned sc_retry_count; unsigned sc_rt_count; /* Count round trips */ lsquic_packno_t sc_cur_rt_end; + lsquic_packno_t sc_gap; unsigned sc_loss_count; /* Used to set loss bit */ unsigned sc_square_count;/* Used to set square bit */ signed char sc_cidlen; /* For debug purposes */ @@ -372,4 +374,7 @@ void lsquic_send_ctl_cidlen_change (struct lsquic_send_ctl *, unsigned orig_cid_len, unsigned new_cid_len); +void +lsquic_send_ctl_begin_optack_detection (struct lsquic_send_ctl *); + #endif diff --git a/src/liblsquic/lsquic_stream.c b/src/liblsquic/lsquic_stream.c index 9a7ea033b..9e6080290 100644 --- a/src/liblsquic/lsquic_stream.c +++ b/src/liblsquic/lsquic_stream.c @@ -2898,7 +2898,7 @@ stream_write_to_packet_crypto (struct frame_gen_ctx *fg_ctx, const size_t size) if (stream->sm_bflags & SMBF_IETF) pns = lsquic_enclev2pns[ crypto_level(stream) ]; else - pns = PNS_INIT; + pns = PNS_APP; assert(size > 0); crypto_header_sz = stream->sm_frame_header_sz(stream, size); diff --git a/test/test_common.c b/test/test_common.c index 988b66f03..deb53b1f0 100644 --- a/test/test_common.c +++ b/test/test_common.c @@ -1385,6 +1385,7 @@ send_packets_using_sendmmsg (const struct lsquic_out_spec *specs, enum ctl_what cw; unsigned i; int s, saved_errno; + uintptr_t ancil_key, prev_ancil_key; struct mmsghdr mmsgs[1024]; union { /* cmsg(3) recommends union for proper alignment */ @@ -1404,6 +1405,7 @@ send_packets_using_sendmmsg (const struct lsquic_out_spec *specs, struct cmsghdr cmsg; } ancil [ sizeof(mmsgs) / sizeof(mmsgs[0]) ]; + prev_ancil_key = 0; for (i = 0; i < count && i < sizeof(mmsgs) / sizeof(mmsgs[0]); ++i) { mmsgs[i].msg_hdr.msg_name = (void *) specs[i].dest_sa; @@ -1414,20 +1416,51 @@ send_packets_using_sendmmsg (const struct lsquic_out_spec *specs, mmsgs[i].msg_hdr.msg_iovlen = specs[i].iovlen; mmsgs[i].msg_hdr.msg_flags = 0; if ((sport->sp_flags & SPORT_SERVER) && specs[i].local_sa->sa_family) + { cw = CW_SENDADDR; + ancil_key = (uintptr_t) specs[i].local_sa; + assert(0 == (ancil_key & 3)); + } else + { cw = 0; + ancil_key = 0; + } #if ECN_SUPPORTED if (sport->sp_prog->prog_api.ea_settings->es_ecn && specs[i].ecn) + { cw |= CW_ECN; + ancil_key |= specs[i].ecn; + } #endif - if (cw) + if (cw && prev_ancil_key == ancil_key) + { + /* Reuse previous ancillary message */ + assert(i > 0); +#ifndef WIN32 + mmsgs[i].msg_hdr.msg_control = mmsgs[i - 1].msg_hdr.msg_control; + mmsgs[i].msg_hdr.msg_controllen = mmsgs[i - 1].msg_hdr.msg_controllen; +#else + mmsgs[i].msg_hdr.Control.buf = mmsgs[i - 1].msg_hdr.Control.buf; + mmsgs[i].msg_hdr.Control.len = mmsgs[i - 1].msg_hdr.Control.len; +#endif + } + else if (cw) + { + prev_ancil_key = ancil_key; setup_control_msg(&mmsgs[i].msg_hdr, cw, &specs[i], ancil[i].buf, sizeof(ancil[i].buf)); + } else { - mmsgs[i].msg_hdr.msg_control = NULL; + prev_ancil_key = 0; +#ifndef WIN32 + mmsgs[i].msg_hdr.msg_control = NULL; mmsgs[i].msg_hdr.msg_controllen = 0; +#else + mmsgs[i].msg_hdr.Control.buf = NULL; + mmsgs[i].msg_hdr.Control.len = 0; +#endif } } @@ -1510,6 +1543,7 @@ send_packets_one_by_one (const struct lsquic_out_spec *specs, unsigned count) ]; struct cmsghdr cmsg; } ancil; + uintptr_t ancil_key, prev_ancil_key; if (0 == count) return 0; @@ -1538,6 +1572,7 @@ send_packets_one_by_one (const struct lsquic_out_spec *specs, unsigned count) #endif n = 0; + prev_ancil_key = 0; do { sport = specs[n].peer_ctx; @@ -1563,17 +1598,36 @@ send_packets_one_by_one (const struct lsquic_out_spec *specs, unsigned count) msg.dwFlags = 0; #endif if ((sport->sp_flags & SPORT_SERVER) && specs[n].local_sa->sa_family) + { cw = CW_SENDADDR; + ancil_key = (uintptr_t) specs[n].local_sa; + assert(0 == (ancil_key & 3)); + } else + { cw = 0; + ancil_key = 0; + } #if ECN_SUPPORTED if (sport->sp_prog->prog_api.ea_settings->es_ecn && specs[n].ecn) + { cw |= CW_ECN; + ancil_key |= specs[n].ecn; + } #endif - if (cw) + if (cw && prev_ancil_key == ancil_key) + { + /* Reuse previous ancillary message */ + ; + } + else if (cw) + { + prev_ancil_key = ancil_key; setup_control_msg(&msg, cw, &specs[n], ancil.buf, sizeof(ancil.buf)); + } else { + prev_ancil_key = 0; #ifndef WIN32 msg.msg_control = NULL; msg.msg_controllen = 0; diff --git a/test/unittests/CMakeLists.txt b/test/unittests/CMakeLists.txt index fb7f65491..8c528d532 100644 --- a/test/unittests/CMakeLists.txt +++ b/test/unittests/CMakeLists.txt @@ -78,6 +78,11 @@ IF (CMAKE_SYSTEM_NAME STREQUAL "Linux") SET(TESTS ${TESTS} frame_rw) ENDIF() +IF (NOT CMAKE_SYSTEM_NAME STREQUAL "Windows") + # No regexes on Windows + SET(TESTS ${TESTS} ack_merge) +ENDIF() + FOREACH(TEST_NAME ${TESTS}) ADD_EXECUTABLE(test_${TEST_NAME} test_${TEST_NAME}.c ${ADDL_SOURCES}) diff --git a/test/unittests/test_ack.c b/test/unittests/test_ack.c index 230380b21..880c522ba 100644 --- a/test/unittests/test_ack.c +++ b/test/unittests/test_ack.c @@ -155,7 +155,6 @@ compare_ackis (const struct ack_info *exp, const struct ack_info *got) assert(exp->flags == got->flags); assert(exp->n_ranges == got->n_ranges); assert(exp->lack_delta == got->lack_delta); - assert(exp->n_timestamps == got->n_timestamps); for (i = 0; i < exp->n_ranges; ++i) { diff --git a/test/unittests/test_ack_merge.c b/test/unittests/test_ack_merge.c new file mode 100644 index 000000000..667e2d597 --- /dev/null +++ b/test/unittests/test_ack_merge.c @@ -0,0 +1,185 @@ +/* Copyright (c) 2017 - 2019 LiteSpeed Technologies Inc. See LICENSE. */ +/* Test ACK merge */ + +#include +#include +#include +#include + +#include "lsquic.h" +#include "lsquic_types.h" +#include "lsquic_int_types.h" +#include "lsquic_sizes.h" +#include "lsquic_parse.h" + +struct test +{ + int lineno; + int expect_failure; + char a[MAX_ACKI_STR_SZ]; + char b[MAX_ACKI_STR_SZ]; + char result[MAX_ACKI_STR_SZ]; +}; + + +static const struct test tests[] = +{ + + { + .lineno = __LINE__, + .expect_failure = 1, + .a = "", + .b = "[3-3]", + }, + + { + .lineno = __LINE__, + .expect_failure = 1, + .a = "", + .b = "", + }, + + { + .lineno = __LINE__, + .a = "[3-3]", + .b = "[3-3]", + .result = "[3-3]", + }, + + { + .lineno = __LINE__, + .a = "[3-2]", + .b = "[1-1]", + .result = "[3-1]", + }, + + { + .lineno = __LINE__, + .a = "[15-15][3-2]", + .b = "[1-1]", + .result = "[15-15][3-1]", + }, + + { + .lineno = __LINE__, + .a = "[15-10][5-2]", + .b = "[9-6][1-1]", + .result = "[15-1]", + }, + + { + .lineno = __LINE__, + .a = "[15-10][5-2]", + .b = "[15-10][5-2]", + .result = "[15-10][5-2]", + }, + + { + .lineno = __LINE__, + .a = "[33803-33803][33800-33800][33788-33788][33775-33775][33759-33759][33744-33744][33732-33732][33717-33717][33706-33706][33691-33691][33679-33679][33664-33664][33649-33649][33638-33638][33622-33622][33613-33613][33585-33585][33574-33574][33562-33562][33546-33546][33531-33531][33516-33516][33504-33504][33490-33490][33483-33483][33481-32910][32906-32906][32894-32894][32880-32880][32869-32869][32854-32854][32844-32844][32817-32817][32806-32806][32791-32791][32780-32780][32766-32766][32752-32752][32741-32741][32726-32726][32716-32716][32702-32702][32697-32697][32682-32682][32672-32672][32657-32657][32651-32651][32636-32636][32626-32626][32611-32611][32600-32600][32589-32589][32574-32574][32564-32564][32551-32551][32536-32536][32525-32525][32511-32511][32500-32500][32485-32485][32475-32475][32460-32460][32449-32449][32438-32438][32423-32423][32412-32412][32399-32399][32385-32385][32375-32375][32360-32360][32349-32349][32334-32334][32326-32326][32312-32312][32302-32302][32287-32287][32277-32277][32262-32262][32252-32252][32239-32239][32224-32224][32214-32214][32200-32200][32190-32190][32175-32175][32161-32161][32151-32151][32136-32136][32126-32126][32111-32111][32103-32103][32090-32090][32080-32080][32069-32033][32008-30698][30696-30467]", + .b = "[33480-32910][32906-32906][32894-32894][32880-32880][32869-32869][32854-32854][32844-32844][32817-32817][32806-32806][32791-32791][32780-32780][32766-32766][32752-32752][32741-32741][32726-32726][32716-32716][32702-32702][32697-32697][32682-32682][32672-32672][32657-32657][32651-32651][32636-32636][32626-32626][32611-32611][32600-32600][32589-32589][32574-32574][32564-32564][32551-32551][32536-32536][32525-32525][32511-32511][32500-32500][32485-32485][32475-32475][32460-32460][32449-32449][32438-32438][32423-32423][32412-32412][32399-32399][32385-32385][32375-32375][32360-32360][32349-32349][32334-32334][32326-32326][32312-32312][32302-32302][32287-32287][32277-32277][32262-32262][32252-32252][32239-32239][32224-32224][32214-32214][32200-32200][32190-32190][32175-32175][32161-32161][32151-32151][32136-32136][32126-32126][32111-32111][32103-32103][32090-32090][32080-32080][32069-32033][32008-30698][30696-30467]", + .result = "[33803-33803][33800-33800][33788-33788][33775-33775][33759-33759][33744-33744][33732-33732][33717-33717][33706-33706][33691-33691][33679-33679][33664-33664][33649-33649][33638-33638][33622-33622][33613-33613][33585-33585][33574-33574][33562-33562][33546-33546][33531-33531][33516-33516][33504-33504][33490-33490][33483-33483][33481-32910][32906-32906][32894-32894][32880-32880][32869-32869][32854-32854][32844-32844][32817-32817][32806-32806][32791-32791][32780-32780][32766-32766][32752-32752][32741-32741][32726-32726][32716-32716][32702-32702][32697-32697][32682-32682][32672-32672][32657-32657][32651-32651][32636-32636][32626-32626][32611-32611][32600-32600][32589-32589][32574-32574][32564-32564][32551-32551][32536-32536][32525-32525][32511-32511][32500-32500][32485-32485][32475-32475][32460-32460][32449-32449][32438-32438][32423-32423][32412-32412][32399-32399][32385-32385][32375-32375][32360-32360][32349-32349][32334-32334][32326-32326][32312-32312][32302-32302][32287-32287][32277-32277][32262-32262][32252-32252][32239-32239][32224-32224][32214-32214][32200-32200][32190-32190][32175-32175][32161-32161][32151-32151][32136-32136][32126-32126][32111-32111][32103-32103][32090-32090][32080-32080][32069-32033][32008-30698][30696-30467]", + }, + +}; + + +static regex_t re; + + +static void +init_ranges (struct ack_info *a, const char *s) +{ + regmatch_t m[3]; + + while (0 == regexec(&re, s, sizeof(m) / sizeof(m[0]), m, 0)) + { + a->ranges[a->n_ranges].high = atoi(s + m[1].rm_so); + a->ranges[a->n_ranges].low = atoi(s + m[2].rm_so); + /* Self-check: */ + assert(a->ranges[a->n_ranges].high >= a->ranges[a->n_ranges].low); + ++a->n_ranges; + s += m[0].rm_eo; + } +} + + +static void +run_test_ext (const struct test *test, const char *a_str, const char *b_str) +{ + struct ack_info a, b; + int s; + char ack_str[MAX_ACKI_STR_SZ]; + + memset(&a, 0, sizeof(a)); + memset(&b, 0, sizeof(b)); + + init_ranges(&a, a_str); + init_ranges(&b, b_str); + + s = lsquic_merge_acks(&a, &b); + if (test->expect_failure) + { + assert(s != 0); + return; + } + + assert(s == 0); + lsquic_acki2str(&a, ack_str, sizeof(ack_str)); + assert(0 == strcmp(ack_str, test->result)); +} + + +static void +run_test (const struct test *test) +{ + run_test_ext(test, test->a, test->b); + /* If flipped result should be the same: */ + run_test_ext(test, test->b, test->a); +} + + +static void +test_out_of_ranges (int success) +{ + struct ack_info a, b; + int i, s; + + memset(&a, 0, sizeof(a)); + memset(&b, 0, sizeof(b)); + + for (i = 0; i < 129 - success; ++i) + { + a.ranges[i].high = a.ranges[i].low = 100000 - i * 10; + b.ranges[i].high = b.ranges[i].low = 100000 - 5 - i * 10; + } + a.n_ranges = i; + b.n_ranges = i; + + s = lsquic_merge_acks(&a, &b); + if (success) + assert(s == 0); + else + assert(s != 0); +} + + +int +main (void) +{ + const struct test *test; + int s; + + s = regcomp(&re, "\\[([0-9][0-9]*)-([0-9][0-9]*)\\]", REG_EXTENDED); + assert(s == 0); + + for (test = tests; test < tests + sizeof(tests) / sizeof(tests[0]); ++test) + run_test(test); + + test_out_of_ranges(0); + test_out_of_ranges(1); + + regfree(&re); + + return 0; +} diff --git a/test/unittests/test_ackparse_gquic_be.c b/test/unittests/test_ackparse_gquic_be.c index 235323e1b..54b3cebaa 100644 --- a/test/unittests/test_ackparse_gquic_be.c +++ b/test/unittests/test_ackparse_gquic_be.c @@ -51,7 +51,6 @@ test1 (void) assert(("Number of ranges is 1", acki.n_ranges == 1)); assert(("Largest acked is 0x1234", acki.ranges[0].high == 0x1234)); assert(("Lowest acked is 1", acki.ranges[0].low == 1)); - assert(("Number of timestamps is 0", acki.n_timestamps == 0)); unsigned n = n_acked(&acki); assert(("Number of acked packets is 0x1234", n == 0x1234)); @@ -112,7 +111,6 @@ test2 (void) assert(("Parsed length is correct (29)", len == sizeof(ack_buf))); assert(("Number of ranges is 4", acki.n_ranges == 4)); assert(("Largest acked is 0x1234", acki.ranges[0].high == 0x1234)); - assert(("Number of timestamps is 2", acki.n_timestamps == 2)); unsigned n = n_acked(&acki); assert(("Number of acked packets is 4254", n == 4254)); @@ -163,7 +161,6 @@ test3 (void) assert(("Parsed length is correct (9)", len == sizeof(ack_buf))); assert(("Number of ranges is 2", acki.n_ranges == 2)); assert(("Largest acked is 6", acki.ranges[0].high == 6)); - assert(("Number of timestamps is 0", acki.n_timestamps == 0)); unsigned n = n_acked(&acki); assert(("Number of acked packets is 4", n == 4)); @@ -213,7 +210,6 @@ test4 (void) assert(("Parsed length is correct (9)", len == sizeof(ack_buf))); assert(("Number of ranges is 2", acki.n_ranges == 2)); assert(("Largest acked is 3", acki.ranges[0].high == 3)); - assert(("Number of timestamps is 0", acki.n_timestamps == 0)); unsigned n = n_acked(&acki); assert(("Number of acked packets is 2", n == 2)); @@ -267,7 +263,6 @@ test5 (void) assert(("Parsed length is correct (9)", len == sizeof(ack_buf))); assert(("Number of ranges is 2", acki.n_ranges == 2)); assert(("Largest acked is 0x23456789", acki.ranges[0].high == 0x23456789)); - assert(("Number of timestamps is 0", acki.n_timestamps == 0)); lsquic_packno_t n = n_acked(&acki); assert(("Number of acked packets is correct", n == 0x23456768 + 1)); @@ -316,7 +311,6 @@ test6 (void) assert(("Parsed length is correct", len == sizeof(ack_buf))); assert(("Number of ranges is 2", acki.n_ranges == 2)); assert(("Largest acked is 0xABCD23456789", acki.ranges[0].high == 0xABCD23456789)); - assert(("Number of timestamps is 0", acki.n_timestamps == 0)); lsquic_packno_t n = n_acked(&acki); assert(("Number of acked packets is correct", n == 0xABCD23456768 + 1));