From 0adf085acfd2faa2c29e3d35be90bc7d7d452dbd Mon Sep 17 00:00:00 2001 From: Dmitri Tikhonov Date: Tue, 15 Oct 2019 17:02:21 -0400 Subject: [PATCH] Release 2.4.7 - Add echo client and server to the distibution. - Add MD5 client and server to the distibution. - Fix http_client: check command-line arguments better, prevent crash. - Fix IETF conn: can_write_ack() should only care about APP PNS. - Client: delay stream creation until handshake succeds. - Reset HTTP stream whose write end is closed prematurely. - Fix tickable(): mirror behavior of tick() wrt buffered packets. - Log reason why engine is tickable. --- CHANGELOG | 11 + CMakeLists.txt | 8 + EXAMPLES.txt | 17 + include/lsquic.h | 2 +- src/liblsquic/lsquic_alarmset.c | 10 +- src/liblsquic/lsquic_alarmset.h | 4 +- src/liblsquic/lsquic_attq.c | 29 +- src/liblsquic/lsquic_attq.h | 19 +- src/liblsquic/lsquic_conn.h | 2 +- src/liblsquic/lsquic_enc_sess_ietf.c | 9 + src/liblsquic/lsquic_engine.c | 72 +++- src/liblsquic/lsquic_engine_public.h | 2 +- src/liblsquic/lsquic_full_conn.c | 57 ++- src/liblsquic/lsquic_full_conn_ietf.c | 48 ++- src/liblsquic/lsquic_min_heap.h | 2 + src/liblsquic/lsquic_mini_conn.c | 11 +- src/liblsquic/lsquic_mini_conn_ietf.c | 11 +- src/liblsquic/lsquic_send_ctl.c | 4 +- src/liblsquic/lsquic_send_ctl.h | 3 + src/liblsquic/lsquic_stream.c | 8 +- test/echo_client.c | 245 ++++++++++++ test/echo_server.c | 228 ++++++++++++ test/http_client.c | 6 + test/md5_client.c | 518 ++++++++++++++++++++++++++ test/md5_server.c | 340 +++++++++++++++++ test/unittests/test_attq.c | 66 ++-- test/unittests/test_stream.c | 28 ++ 27 files changed, 1668 insertions(+), 92 deletions(-) create mode 100644 test/echo_client.c create mode 100644 test/echo_server.c create mode 100644 test/md5_client.c create mode 100644 test/md5_server.c diff --git a/CHANGELOG b/CHANGELOG index 7a5bef834..2aa012758 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,14 @@ +2019-10-15 + - 2.4.7 + - Add echo client and server to the distibution. + - Add MD5 client and server to the distibution. + - Fix http_client: check command-line arguments better, prevent crash. + - Fix IETF conn: can_write_ack() should only care about APP PNS. + - Client: delay stream creation until handshake succeds. + - Reset HTTP stream whose write end is closed prematurely. + - Fix tickable(): mirror behavior of tick() wrt buffered packets. + - Log reason why engine is tickable. + 2019-10-11 - 2.4.6 - Minor code cleanup and logging improvements. diff --git a/CMakeLists.txt b/CMakeLists.txt index ce30364aa..e9a676ba2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -200,6 +200,10 @@ ELSE() ENDIF() add_executable(http_server test/http_server.c test/prog.c test/test_common.c test/test_cert.c) +add_executable(md5_server test/md5_server.c test/prog.c test/test_common.c test/test_cert.c) +add_executable(md5_client test/md5_client.c test/prog.c test/test_common.c test/test_cert.c) +add_executable(echo_server test/echo_server.c test/prog.c test/test_common.c test/test_cert.c) +add_executable(echo_client test/echo_client.c test/prog.c test/test_common.c test/test_cert.c) SET(LIBS lsquic ${EVENT_LIB} ${BORINGSSL_LIB_ssl} ${BORINGSSL_LIB_crypto} ${ZLIB_LIB} ${LIBS}) @@ -230,6 +234,10 @@ ENDIF() TARGET_LINK_LIBRARIES(http_client ${LIBS}) TARGET_LINK_LIBRARIES(http_server ${LIBS}) +TARGET_LINK_LIBRARIES(md5_server ${LIBS}) +TARGET_LINK_LIBRARIES(md5_client ${LIBS}) +TARGET_LINK_LIBRARIES(echo_server ${LIBS}) +TARGET_LINK_LIBRARIES(echo_client ${LIBS}) add_subdirectory(src) diff --git a/EXAMPLES.txt b/EXAMPLES.txt index e7c019486..104b9008e 100644 --- a/EXAMPLES.txt +++ b/EXAMPLES.txt @@ -7,6 +7,23 @@ LSQUIC comes with several examples of how the library is used. The client and server programs described below are built on a common framework and share many options. +Echo client and server +---------------------- + +Echo client and server (see test/echo_{client,server}.c) are for simple +line-based request and reply communication. Only one stream per connection +is supported for simplicity. The client reads input from stdin. + +MD5 client and server +--------------------- + +See test/md5_{client,server}.c + +MD5 server accepts connections, computes MD5 sum of streams' (one or more) +payload, and sends back the checksum. MD5 client sends one or more file +contents to the server. Both client and server support various options to +exercise different aspects of LSQUIC. + HTTP client and server ---------------------- diff --git a/include/lsquic.h b/include/lsquic.h index 417e583ed..f85efafcb 100644 --- a/include/lsquic.h +++ b/include/lsquic.h @@ -25,7 +25,7 @@ extern "C" { #define LSQUIC_MAJOR_VERSION 2 #define LSQUIC_MINOR_VERSION 4 -#define LSQUIC_PATCH_VERSION 6 +#define LSQUIC_PATCH_VERSION 7 /** * Engine flags: diff --git a/src/liblsquic/lsquic_alarmset.c b/src/liblsquic/lsquic_alarmset.c index 2724c55cc..5289b1236 100644 --- a/src/liblsquic/lsquic_alarmset.c +++ b/src/liblsquic/lsquic_alarmset.c @@ -32,7 +32,7 @@ lsquic_alarmset_init_alarm (lsquic_alarmset_t *alset, enum alarm_id al_id, } -static const char *const lsquic_alid2str[] = +const char *const lsquic_alid2str[] = { [AL_HANDSHAKE] = "HANDSHAKE", [AL_RETX_INIT] = "RETX_INIT", @@ -75,20 +75,22 @@ lsquic_alarmset_ring_expired (lsquic_alarmset_t *alset, lsquic_time_t now) lsquic_time_t -lsquic_alarmset_mintime (const lsquic_alarmset_t *alset) +lsquic_alarmset_mintime (const lsquic_alarmset_t *alset, enum alarm_id *idp) { lsquic_time_t expiry; - enum alarm_id al_id; + enum alarm_id al_id, ret_id; if (alset->as_armed_set) { expiry = UINT64_MAX; - for (al_id = 0; al_id < MAX_LSQUIC_ALARMS; ++al_id) + for (al_id = 0, ret_id = 0; al_id < MAX_LSQUIC_ALARMS; ++al_id) if ((alset->as_armed_set & (1 << al_id)) && alset->as_expiry[al_id] < expiry) { expiry = alset->as_expiry[al_id]; + ret_id = al_id; } + *idp = ret_id; return expiry; } else diff --git a/src/liblsquic/lsquic_alarmset.h b/src/liblsquic/lsquic_alarmset.h index d550717ac..9c2ebca10 100644 --- a/src/liblsquic/lsquic_alarmset.h +++ b/src/liblsquic/lsquic_alarmset.h @@ -94,6 +94,8 @@ void lsquic_alarmset_ring_expired (lsquic_alarmset_t *, lsquic_time_t now); lsquic_time_t -lsquic_alarmset_mintime (const lsquic_alarmset_t *); +lsquic_alarmset_mintime (const lsquic_alarmset_t *, enum alarm_id *); + +extern const char *const lsquic_alid2str[]; #endif diff --git a/src/liblsquic/lsquic_attq.c b/src/liblsquic/lsquic_attq.c index 9720a7fa0..76b64e6f1 100644 --- a/src/liblsquic/lsquic_attq.c +++ b/src/liblsquic/lsquic_attq.c @@ -19,6 +19,8 @@ #include "lsquic_types.h" #include "lsquic_int_types.h" #include "lsquic_attq.h" +#include "lsquic_packet_common.h" +#include "lsquic_alarmset.h" #include "lsquic_malo.h" #include "lsquic_hash.h" #include "lsquic_conn.h" @@ -107,7 +109,7 @@ attq_swap (struct attq *q, unsigned a, unsigned b) int attq_add (struct attq *q, struct lsquic_conn *conn, - lsquic_time_t advisory_time) + lsquic_time_t advisory_time, enum ae_why why) { struct attq_elem *el, **heap; unsigned n, i; @@ -129,6 +131,7 @@ attq_add (struct attq *q, struct lsquic_conn *conn, if (!el) return -1; el->ae_adv_time = advisory_time; + el->ae_why = why; /* The only place linkage between conn and attq_elem occurs: */ el->ae_conn = conn; @@ -256,11 +259,29 @@ attq_count_before (struct attq *q, lsquic_time_t cutoff) } -const lsquic_time_t * -attq_next_time (struct attq *q) +const struct attq_elem * +attq_next (struct attq *q) { if (q->aq_nelem > 0) - return &q->aq_heap[0]->ae_adv_time; + return q->aq_heap[0]; else return NULL; } + + +const char * +lsquic_attq_why2str (enum ae_why why) +{ + switch (why) + { + case AEW_PACER: + return "PACER"; + case AEW_MINI_EXPIRE: + return "MINI-EXPIRE"; + default: + why -= N_AEWS; + if ((unsigned) why < (unsigned) MAX_LSQUIC_ALARMS) + return lsquic_alid2str[why]; + return "UNKNOWN"; + } +} diff --git a/src/liblsquic/lsquic_attq.h b/src/liblsquic/lsquic_attq.h index dde4550c3..4d3880e07 100644 --- a/src/liblsquic/lsquic_attq.h +++ b/src/liblsquic/lsquic_attq.h @@ -18,6 +18,15 @@ struct attq_elem struct lsquic_conn *ae_conn; lsquic_time_t ae_adv_time; unsigned ae_heap_idx; + /* The "why" describes why the connection is in the Advisory Tick Time + * Queue. Values past the range describe different alarm types (see + * enum alarm_id). + */ + enum ae_why { + AEW_PACER, + AEW_MINI_EXPIRE, + N_AEWS + } ae_why; }; @@ -29,7 +38,8 @@ attq_destroy (struct attq *); /* Return 0 on success, -1 on failure (malloc) */ int -attq_add (struct attq *, struct lsquic_conn *, lsquic_time_t advisory_time); +attq_add (struct attq *, struct lsquic_conn *, lsquic_time_t advisory_time, + enum ae_why); void attq_remove (struct attq *, struct lsquic_conn *); @@ -40,7 +50,10 @@ attq_pop (struct attq *, lsquic_time_t cutoff); unsigned attq_count_before (struct attq *, lsquic_time_t cutoff); -const lsquic_time_t * -attq_next_time (struct attq *); +const struct attq_elem * +attq_next (struct attq *); + +const char * +lsquic_attq_why2str (enum ae_why); #endif diff --git a/src/liblsquic/lsquic_conn.h b/src/liblsquic/lsquic_conn.h index c7e2efdf8..78f4f168d 100644 --- a/src/liblsquic/lsquic_conn.h +++ b/src/liblsquic/lsquic_conn.h @@ -123,7 +123,7 @@ struct conn_iface (*ci_is_tickable) (struct lsquic_conn *); lsquic_time_t - (*ci_next_tick_time) (struct lsquic_conn *); + (*ci_next_tick_time) (struct lsquic_conn *, unsigned *why); int (*ci_can_write_ack) (struct lsquic_conn *); diff --git a/src/liblsquic/lsquic_enc_sess_ietf.c b/src/liblsquic/lsquic_enc_sess_ietf.c index c1962b996..96e8acff3 100644 --- a/src/liblsquic/lsquic_enc_sess_ietf.c +++ b/src/liblsquic/lsquic_enc_sess_ietf.c @@ -2138,6 +2138,14 @@ iquic_esf_alg_keysize (enc_session_t *enc_session_p) } +static int +iquic_esf_zero_rtt_enabled (enc_session_t *enc_session_p) +{ + struct enc_sess_iquic *const enc_sess = enc_session_p; + return enc_sess->esi_zero_rtt_buf != NULL; +} + + int iquic_esfi_reset_dcid (enc_session_t *enc_session_p, const lsquic_cid_t *old_dcid, const lsquic_cid_t *new_dcid) @@ -2204,6 +2212,7 @@ const struct enc_session_funcs_common lsquic_enc_session_common_ietf_v1 = .esf_cipher = iquic_esf_cipher, .esf_keysize = iquic_esf_keysize, .esf_alg_keysize = iquic_esf_alg_keysize, + .esf_is_zero_rtt_enabled = iquic_esf_zero_rtt_enabled, }; diff --git a/src/liblsquic/lsquic_engine.c b/src/liblsquic/lsquic_engine.c index 19364a9c4..8e2536061 100644 --- a/src/liblsquic/lsquic_engine.c +++ b/src/liblsquic/lsquic_engine.c @@ -1179,7 +1179,7 @@ lsquic_engine_add_conn_to_tickable (struct lsquic_engine_public *enpub, void lsquic_engine_add_conn_to_attq (struct lsquic_engine_public *enpub, - lsquic_conn_t *conn, lsquic_time_t tick_time) + lsquic_conn_t *conn, lsquic_time_t tick_time, unsigned why) { lsquic_engine_t *const engine = (lsquic_engine_t *) enpub; if (conn->cn_flags & LSCONN_TICKABLE) @@ -1194,11 +1194,11 @@ lsquic_engine_add_conn_to_attq (struct lsquic_engine_public *enpub, if (lsquic_conn_adv_time(conn) != tick_time) { attq_remove(engine->attq, conn); - if (0 != attq_add(engine->attq, conn, tick_time)) + if (0 != attq_add(engine->attq, conn, tick_time, why)) engine_decref_conn(engine, conn, LSCONN_ATTQ); } } - else if (0 == attq_add(engine->attq, conn, tick_time)) + else if (0 == attq_add(engine->attq, conn, tick_time, why)) engine_incref_conn(conn, LSCONN_ATTQ); } @@ -2355,7 +2355,7 @@ process_connections (lsquic_engine_t *engine, conn_iter_f next_conn, { lsquic_conn_t *conn; enum tick_st tick_st; - unsigned i; + unsigned i, why; lsquic_time_t next_tick_time; struct conns_stailq closed_conns; struct conns_tailq ticked_conns; @@ -2453,10 +2453,10 @@ process_connections (lsquic_engine_t *engine, conn_iter_f next_conn, } else if (!(conn->cn_flags & LSCONN_ATTQ)) { - next_tick_time = conn->cn_if->ci_next_tick_time(conn); + next_tick_time = conn->cn_if->ci_next_tick_time(conn, &why); if (next_tick_time) { - if (0 == attq_add(engine->attq, conn, next_tick_time)) + if (0 == attq_add(engine->attq, conn, next_tick_time, why)) engine_incref_conn(conn, LSCONN_ATTQ); } else @@ -2590,38 +2590,80 @@ lsquic_engine_cooldown (lsquic_engine_t *engine) int lsquic_engine_earliest_adv_tick (lsquic_engine_t *engine, int *diff) { - const lsquic_time_t *next_attq_time; + const struct attq_elem *next_attq; lsquic_time_t now, next_time; + const struct lsquic_conn *conn; + const lsquic_cid_t *cid; + const enum lsq_log_level L = LSQ_LOG_DEBUG; /* Easy toggle */ ENGINE_CALLS_INCR(engine); - if (((engine->flags & ENG_PAST_DEADLINE) + if ((engine->flags & ENG_PAST_DEADLINE) && lsquic_mh_count(&engine->conns_out)) - || (engine->pr_queue && prq_have_pending(engine->pr_queue)) - || lsquic_mh_count(&engine->conns_tickable)) { + conn = lsquic_mh_peek(&engine->conns_out); + cid = lsquic_conn_log_cid(conn); + LSQ_LOGC(L, "next advisory tick is now: went past deadline last time " + "and have %u outgoing connection%.*s (%"CID_FMT" first)", + lsquic_mh_count(&engine->conns_out), + lsquic_mh_count(&engine->conns_out) != 1, "s", CID_BITS(cid)); *diff = 0; return 1; } - next_attq_time = attq_next_time(engine->attq); + if (engine->pr_queue && prq_have_pending(engine->pr_queue)) + { + LSQ_LOG(L, "next advisory tick is now: have pending PRQ elements"); + *diff = 0; + return 1; + } + + if (lsquic_mh_count(&engine->conns_tickable)) + { + conn = lsquic_mh_peek(&engine->conns_tickable); + cid = lsquic_conn_log_cid(conn); + LSQ_LOGC(L, "next advisory tick is now: have %u tickable " + "connection%.*s (%"CID_FMT" first)", + lsquic_mh_count(&engine->conns_tickable), + lsquic_mh_count(&engine->conns_tickable) != 1, "s", CID_BITS(cid)); + *diff = 0; + return 1; + } + + next_attq = attq_next(engine->attq); if (engine->pub.enp_flags & ENPUB_CAN_SEND) { - if (next_attq_time) - next_time = *next_attq_time; + if (next_attq) + next_time = next_attq->ae_adv_time; else return 0; } else { - if (next_attq_time) - next_time = MIN(*next_attq_time, engine->resume_sending_at); + if (next_attq) + { + next_time = next_attq->ae_adv_time; + if (engine->resume_sending_at < next_time) + { + next_time = engine->resume_sending_at; + next_attq = NULL; + } + } else next_time = engine->resume_sending_at; } now = lsquic_time_now(); *diff = (int) ((int64_t) next_time - (int64_t) now); + if (next_attq) + { + cid = lsquic_conn_log_cid(next_attq->ae_conn); + LSQ_LOGC(L, "next advisory tick is %d usec away: conn %"CID_FMT + ": %s", *diff, CID_BITS(cid), + lsquic_attq_why2str(next_attq->ae_why)); + } + else + LSQ_LOG(L, "next advisory tick is %d usec away: resume sending", *diff); return 1; } diff --git a/src/liblsquic/lsquic_engine_public.h b/src/liblsquic/lsquic_engine_public.h index 101c11ac8..4b5213e07 100644 --- a/src/liblsquic/lsquic_engine_public.h +++ b/src/liblsquic/lsquic_engine_public.h @@ -72,7 +72,7 @@ lsquic_engine_add_conn_to_tickable (struct lsquic_engine_public *, */ void lsquic_engine_add_conn_to_attq (struct lsquic_engine_public *enpub, - lsquic_conn_t *, lsquic_time_t); + lsquic_conn_t *, lsquic_time_t, unsigned why); void lsquic_engine_retire_cid (struct lsquic_engine_public *, diff --git a/src/liblsquic/lsquic_full_conn.c b/src/liblsquic/lsquic_full_conn.c index 446af2be4..b5d651e1f 100644 --- a/src/liblsquic/lsquic_full_conn.c +++ b/src/liblsquic/lsquic_full_conn.c @@ -63,6 +63,7 @@ #include "lsquic_version.h" #include "lsquic_headers.h" #include "lsquic_handshake.h" +#include "lsquic_attq.h" #include "lsquic_conn.h" #include "lsquic_conn_public.h" @@ -1326,11 +1327,21 @@ full_conn_ci_n_avail_streams (const lsquic_conn_t *lconn) } +static int +handshake_done_or_doing_zero_rtt (const struct full_conn *conn) +{ + return (conn->fc_conn.cn_flags & LSCONN_HANDSHAKE_DONE) + || conn->fc_conn.cn_esf_c->esf_is_zero_rtt_enabled( + conn->fc_conn.cn_enc_session); +} + + static void full_conn_ci_make_stream (struct lsquic_conn *lconn) { struct full_conn *conn = (struct full_conn *) lconn; - if (full_conn_ci_n_avail_streams(lconn) > 0) + if (handshake_done_or_doing_zero_rtt(conn) + && full_conn_ci_n_avail_streams(lconn) > 0) { if (!new_stream(conn, generate_stream_id(conn), SCF_CALL_ON_NEW)) ABORT_ERROR("could not create new stream: %s", strerror(errno)); @@ -3440,9 +3451,7 @@ full_conn_ci_tick (lsquic_conn_t *lconn, lsquic_time_t now) } lsquic_send_ctl_set_buffer_stream_packets(&conn->fc_send_ctl, 0); - if (!(conn->fc_conn.cn_flags & LSCONN_HANDSHAKE_DONE) && - !conn->fc_conn.cn_esf_c->esf_is_zero_rtt_enabled( - conn->fc_conn.cn_enc_session)) + if (!handshake_done_or_doing_zero_rtt(conn)) { process_hsk_stream_write_events(conn); goto end_write; @@ -3623,12 +3632,14 @@ full_conn_ci_hsk_done (lsquic_conn_t *lconn, enum lsquic_hsk_status status) if (conn->fc_stream_ifs[STREAM_IF_STD].stream_if->on_hsk_done) conn->fc_stream_ifs[STREAM_IF_STD].stream_if->on_hsk_done(lconn, status); - if ((status == LSQ_HSK_OK || status == LSQ_HSK_0RTT_OK) - && conn->fc_stream_ifs[STREAM_IF_STD].stream_if->on_zero_rtt_info) + if (status == LSQ_HSK_OK || status == LSQ_HSK_0RTT_OK) { - conn->fc_conn.cn_esf.g->esf_maybe_dispatch_zero_rtt( - conn->fc_conn.cn_enc_session, &conn->fc_conn, - conn->fc_stream_ifs[STREAM_IF_STD].stream_if->on_zero_rtt_info); + if (conn->fc_stream_ifs[STREAM_IF_STD].stream_if->on_zero_rtt_info) + conn->fc_conn.cn_esf.g->esf_maybe_dispatch_zero_rtt( + conn->fc_conn.cn_enc_session, &conn->fc_conn, + conn->fc_stream_ifs[STREAM_IF_STD].stream_if->on_zero_rtt_info); + if (conn->fc_n_delayed_streams) + create_delayed_streams(conn); } } @@ -4227,7 +4238,8 @@ full_conn_ci_is_tickable (lsquic_conn_t *lconn) LSQ_DEBUG("tickable: flags: 0x%X", conn->fc_flags & send_flags); goto check_can_send; } - if (lsquic_send_ctl_has_buffered(&conn->fc_send_ctl)) + if ((conn->fc_conn.cn_flags & LSCONN_HANDSHAKE_DONE) + && lsquic_send_ctl_has_buffered(&conn->fc_send_ctl)) { LSQ_DEBUG("tickable: has buffered packets"); goto check_can_send; @@ -4237,9 +4249,7 @@ full_conn_ci_is_tickable (lsquic_conn_t *lconn) LSQ_DEBUG("tickable: there are sending streams"); goto check_can_send; } - if ((conn->fc_conn.cn_flags & LSCONN_HANDSHAKE_DONE) || - conn->fc_conn.cn_esf_c->esf_is_zero_rtt_enabled( - conn->fc_conn.cn_enc_session)) + if (handshake_done_or_doing_zero_rtt(conn)) { TAILQ_FOREACH(stream, &conn->fc_pub.write_streams, next_write_stream) @@ -4283,12 +4293,13 @@ full_conn_ci_is_tickable (lsquic_conn_t *lconn) static lsquic_time_t -full_conn_ci_next_tick_time (lsquic_conn_t *lconn) +full_conn_ci_next_tick_time (lsquic_conn_t *lconn, unsigned *why) { struct full_conn *conn = (struct full_conn *) lconn; lsquic_time_t alarm_time, pacer_time, now; + enum alarm_id al_id; - alarm_time = lsquic_alarmset_mintime(&conn->fc_alset); + alarm_time = lsquic_alarmset_mintime(&conn->fc_alset, &al_id); pacer_time = lsquic_send_ctl_next_pacer_time(&conn->fc_send_ctl); if (pacer_time && LSQ_LOG_ENABLED(LSQ_LOG_DEBUG)) @@ -4302,14 +4313,28 @@ full_conn_ci_next_tick_time (lsquic_conn_t *lconn) if (alarm_time && pacer_time) { if (alarm_time < pacer_time) + { + *why = N_AEWS + al_id; return alarm_time; + } else + { + *why = AEW_PACER; return pacer_time; + } } else if (alarm_time) + { + *why = N_AEWS + al_id; return alarm_time; - else + } + else if (pacer_time) + { + *why = AEW_PACER; return pacer_time; + } + else + return 0; } diff --git a/src/liblsquic/lsquic_full_conn_ietf.c b/src/liblsquic/lsquic_full_conn_ietf.c index 0263cc0ce..56473c075 100644 --- a/src/liblsquic/lsquic_full_conn_ietf.c +++ b/src/liblsquic/lsquic_full_conn_ietf.c @@ -17,6 +17,7 @@ #include "lsquic.h" #include "lsquic_types.h" #include "lsquic_int_types.h" +#include "lsquic_attq.h" #include "lsquic_packet_common.h" #include "lsquic_packet_ietf.h" #include "lsquic_packet_in.h" @@ -1301,7 +1302,8 @@ lsquic_ietf_full_conn_server_new (struct lsquic_engine_public *enpub, static int -should_generate_ack (struct ietf_full_conn *conn) +should_generate_ack (struct ietf_full_conn *conn, + enum ifull_conn_flags ack_queued) { unsigned lost_acks; @@ -1312,7 +1314,7 @@ should_generate_ack (struct ietf_full_conn *conn) if (lost_acks) conn->ifc_flags |= lost_acks << IFCBIT_ACK_QUED_SHIFT; - return (conn->ifc_flags & IFC_ACK_QUEUED) != 0; + return (conn->ifc_flags & ack_queued) != 0; } @@ -1320,7 +1322,7 @@ static int ietf_full_conn_ci_can_write_ack (struct lsquic_conn *lconn) { struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn; - return should_generate_ack(conn); + return should_generate_ack(conn, IFC_ACK_QUED_APP); } @@ -3226,7 +3228,7 @@ ietf_full_conn_ci_is_tickable (struct lsquic_conn *lconn) } if ((conn->ifc_enpub->enp_flags & ENPUB_CAN_SEND) - && (should_generate_ack(conn) || + && (should_generate_ack(conn, IFC_ACK_QUEUED) || !lsquic_send_ctl_sched_is_blocked(&conn->ifc_send_ctl))) { /* XXX What about queued ACKs: why check but not make tickable? */ @@ -3235,7 +3237,9 @@ ietf_full_conn_ci_is_tickable (struct lsquic_conn *lconn) LSQ_DEBUG("tickable: send flags: 0x%X", conn->ifc_send_flags); goto check_can_send; } - if (lsquic_send_ctl_has_buffered(&conn->ifc_send_ctl)) + if (conn->ifc_conn.cn_flags & LSCONN_HANDSHAKE_DONE ? + lsquic_send_ctl_has_buffered(&conn->ifc_send_ctl) : + lsquic_send_ctl_has_buffered_high(&conn->ifc_send_ctl)) { LSQ_DEBUG("tickable: has buffered packets"); goto check_can_send; @@ -3683,12 +3687,13 @@ ietf_full_conn_ci_next_packet_to_send (struct lsquic_conn *lconn, size_t size) static lsquic_time_t -ietf_full_conn_ci_next_tick_time (struct lsquic_conn *lconn) +ietf_full_conn_ci_next_tick_time (struct lsquic_conn *lconn, unsigned *why) { struct ietf_full_conn *conn = (struct ietf_full_conn *) lconn; lsquic_time_t alarm_time, pacer_time, now; + enum alarm_id al_id; - alarm_time = lsquic_alarmset_mintime(&conn->ifc_alset); + alarm_time = lsquic_alarmset_mintime(&conn->ifc_alset, &al_id); pacer_time = lsquic_send_ctl_next_pacer_time(&conn->ifc_send_ctl); if (pacer_time && LSQ_LOG_ENABLED(LSQ_LOG_DEBUG)) @@ -3702,14 +3707,28 @@ ietf_full_conn_ci_next_tick_time (struct lsquic_conn *lconn) if (alarm_time && pacer_time) { if (alarm_time < pacer_time) + { + *why = N_AEWS + al_id; return alarm_time; + } else + { + *why = AEW_PACER; return pacer_time; + } } else if (alarm_time) + { + *why = N_AEWS + al_id; return alarm_time; - else + } + else if (pacer_time) + { + *why = AEW_PACER; return pacer_time; + } + else + return 0; } @@ -5981,7 +6000,7 @@ ietf_full_conn_ci_tick (struct lsquic_conn *lconn, lsquic_time_t now) have_delayed_packets = lsquic_send_ctl_maybe_squeeze_sched(&conn->ifc_send_ctl); - if (should_generate_ack(conn)) + if (should_generate_ack(conn, IFC_ACK_QUEUED)) { if (have_delayed_packets) lsquic_send_ctl_reset_packnos(&conn->ifc_send_ctl); @@ -6263,12 +6282,21 @@ ietf_full_conn_ci_n_avail_streams (const struct lsquic_conn *lconn) } +static int +handshake_done_or_doing_zero_rtt (const struct ietf_full_conn *conn) +{ + return (conn->ifc_conn.cn_flags & LSCONN_HANDSHAKE_DONE) + || conn->ifc_conn.cn_esf_c->esf_is_zero_rtt_enabled( + conn->ifc_conn.cn_enc_session); +} + + static void ietf_full_conn_ci_make_stream (struct lsquic_conn *lconn) { struct ietf_full_conn *const conn = (struct ietf_full_conn *) lconn; - if ((lconn->cn_flags & LSCONN_HANDSHAKE_DONE) + if (handshake_done_or_doing_zero_rtt(conn) && ietf_full_conn_ci_n_avail_streams(lconn) > 0) { if (0 != create_bidi_stream_out(conn)) diff --git a/src/liblsquic/lsquic_min_heap.h b/src/liblsquic/lsquic_min_heap.h index f7cf4d6ba..4be3a5cca 100644 --- a/src/liblsquic/lsquic_min_heap.h +++ b/src/liblsquic/lsquic_min_heap.h @@ -29,6 +29,8 @@ lsquic_mh_insert (struct min_heap *, struct lsquic_conn *conn, uint64_t val); struct lsquic_conn * lsquic_mh_pop (struct min_heap *); +#define lsquic_mh_peek(heap) ((heap)->mh_elems[0].mhe_conn) + #define lsquic_mh_count(heap) (+(heap)->mh_nelem) #define lsquic_mh_nalloc(heap) (+(heap)->mh_nalloc) diff --git a/src/liblsquic/lsquic_mini_conn.c b/src/liblsquic/lsquic_mini_conn.c index b4ae44a4f..5705100b3 100644 --- a/src/liblsquic/lsquic_mini_conn.c +++ b/src/liblsquic/lsquic_mini_conn.c @@ -50,6 +50,8 @@ #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) @@ -1892,7 +1894,7 @@ mini_conn_ci_is_tickable (struct lsquic_conn *lconn) static lsquic_time_t -mini_conn_ci_next_tick_time (struct lsquic_conn *lconn) +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; @@ -1905,11 +1907,18 @@ mini_conn_ci_next_tick_time (struct lsquic_conn *lconn) { 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; } diff --git a/src/liblsquic/lsquic_mini_conn_ietf.c b/src/liblsquic/lsquic_mini_conn_ietf.c index befbc43bc..17dd22009 100644 --- a/src/liblsquic/lsquic_mini_conn_ietf.c +++ b/src/liblsquic/lsquic_mini_conn_ietf.c @@ -32,6 +32,8 @@ #include "lsquic_trans_params.h" #include "lsquic_ietf.h" #include "lsquic_packet_ietf.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(&conn->imc_conn) @@ -532,7 +534,7 @@ imico_calc_retx_timeout (const struct ietf_mini_conn *conn) static lsquic_time_t -ietf_mini_conn_ci_next_tick_time (struct lsquic_conn *lconn) +ietf_mini_conn_ci_next_tick_time (struct lsquic_conn *lconn, unsigned *why) { struct ietf_mini_conn *conn = (struct ietf_mini_conn *) lconn; const struct lsquic_packet_out *packet_out; @@ -546,11 +548,18 @@ ietf_mini_conn_ci_next_tick_time (struct lsquic_conn *lconn) { retx_time = packet_out->po_sent + imico_calc_retx_timeout(conn); 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; } diff --git a/src/liblsquic/lsquic_send_ctl.c b/src/liblsquic/lsquic_send_ctl.c index c4bdaabe2..330ada1fd 100644 --- a/src/liblsquic/lsquic_send_ctl.c +++ b/src/liblsquic/lsquic_send_ctl.c @@ -42,6 +42,7 @@ #include "lsquic_enc_sess.h" #include "lsquic_hash.h" #include "lsquic_malo.h" +#include "lsquic_attq.h" #define LSQUIC_LOGGER_MODULE LSQLM_SENDCTL #define LSQUIC_LOG_CONN_ID lsquic_conn_log_cid(ctl->sc_conn_pub->lconn) @@ -1294,7 +1295,8 @@ lsquic_send_ctl_can_send (lsquic_send_ctl_t *ctl) { ctl->sc_flags &= ~SC_SCHED_TICK; lsquic_engine_add_conn_to_attq(ctl->sc_enpub, - ctl->sc_conn_pub->lconn, pacer_next_sched(&ctl->sc_pacer)); + ctl->sc_conn_pub->lconn, pacer_next_sched(&ctl->sc_pacer), + AEW_PACER); } return 0; } diff --git a/src/liblsquic/lsquic_send_ctl.h b/src/liblsquic/lsquic_send_ctl.h index e386cf6c8..9084438bf 100644 --- a/src/liblsquic/lsquic_send_ctl.h +++ b/src/liblsquic/lsquic_send_ctl.h @@ -281,6 +281,9 @@ lsquic_send_ctl_schedule_buffered (lsquic_send_ctl_t *, enum buf_packet_type); TAILQ_FIRST(&(ctl)->sc_buffered_packets[BPT_HIGHEST_PRIO].bpq_packets) \ || TAILQ_FIRST(&(ctl)->sc_buffered_packets[BPT_OTHER_PRIO].bpq_packets )) +#define lsquic_send_ctl_has_buffered_high(ctl) ( \ + !TAILQ_EMPTY(&(ctl)->sc_buffered_packets[BPT_HIGHEST_PRIO].bpq_packets)) + #define lsquic_send_ctl_invalidate_bpt_cache(ctl) do { \ (ctl)->sc_cached_bpt.stream_id = UINT64_MAX; \ } while (0) diff --git a/src/liblsquic/lsquic_stream.c b/src/liblsquic/lsquic_stream.c index 9d51ce81c..7637771f3 100644 --- a/src/liblsquic/lsquic_stream.c +++ b/src/liblsquic/lsquic_stream.c @@ -1536,7 +1536,13 @@ stream_shutdown_write (lsquic_stream_t *stream) && !stream_is_incoming_unidir(stream) && !(stream->sm_qflags & SMQF_SEND_RST)) { - if (stream->sm_n_buffered == 0) + if ((stream->sm_bflags & SMBF_USE_HEADERS) + && !(stream->stream_flags & STREAM_HEADERS_SENT)) + { + LSQ_DEBUG("headers not sent, send a reset"); + lsquic_stream_reset(stream, 0); + } + else if (stream->sm_n_buffered == 0) { if (0 == lsquic_send_ctl_turn_on_fin(stream->conn_pub->send_ctl, stream)) diff --git a/test/echo_client.c b/test/echo_client.c new file mode 100644 index 000000000..b7c98918e --- /dev/null +++ b/test/echo_client.c @@ -0,0 +1,245 @@ +/* Copyright (c) 2017 - 2019 LiteSpeed Technologies Inc. See LICENSE. */ +/* + * echo_client.c -- This is really a "line client:" it connects to QUIC server + * and sends it stuff, line by line. It works in tandem with echo_server. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "lsquic.h" +#include "test_common.h" +#include "prog.h" + +#include "../src/liblsquic/lsquic_logger.h" + +struct lsquic_conn_ctx; + +struct echo_client_ctx { + struct lsquic_conn_ctx *conn_h; + struct prog *prog; +}; + +struct lsquic_conn_ctx { + lsquic_conn_t *conn; + struct echo_client_ctx *client_ctx; +}; + + +static lsquic_conn_ctx_t * +echo_client_on_new_conn (void *stream_if_ctx, lsquic_conn_t *conn) +{ + struct echo_client_ctx *client_ctx = stream_if_ctx; + lsquic_conn_ctx_t *conn_h = malloc(sizeof(*conn_h)); + conn_h->conn = conn; + conn_h->client_ctx = client_ctx; + client_ctx->conn_h = conn_h; + lsquic_conn_make_stream(conn); + return conn_h; +} + + +static void +echo_client_on_conn_closed (lsquic_conn_t *conn) +{ + lsquic_conn_ctx_t *conn_h = lsquic_conn_get_ctx(conn); + LSQ_NOTICE("Connection closed"); + prog_stop(conn_h->client_ctx->prog); + free(conn_h); +} + + +struct lsquic_stream_ctx { + lsquic_stream_t *stream; + struct echo_client_ctx *client_ctx; + struct event *read_stdin_ev; + char buf[0x100]; + size_t buf_off; +}; + + +static void +read_stdin (int fd, short what, void *ctx) +{ + ssize_t nr; + lsquic_stream_ctx_t *st_h = ctx; + + nr = read(fd, st_h->buf + st_h->buf_off++, 1); + LSQ_DEBUG("read %zd bytes from stdin", nr); + if (0 == nr) + { + lsquic_stream_shutdown(st_h->stream, 2); + } + else if (-1 == nr) + { + perror("read"); + exit(1); + } + else if ('\n' == st_h->buf[ st_h->buf_off - 1 ]) + { + LSQ_DEBUG("read newline: wantwrite"); + lsquic_stream_wantwrite(st_h->stream, 1); + lsquic_engine_process_conns(st_h->client_ctx->prog->prog_engine); + } + else if (st_h->buf_off == sizeof(st_h->buf)) + { + LSQ_NOTICE("line too long"); + exit(2); + } + else + event_add(st_h->read_stdin_ev, NULL); +} + + +static lsquic_stream_ctx_t * +echo_client_on_new_stream (void *stream_if_ctx, lsquic_stream_t *stream) +{ + lsquic_stream_ctx_t *st_h = calloc(1, sizeof(*st_h)); + st_h->stream = stream; + st_h->client_ctx = stream_if_ctx; + st_h->buf_off = 0; + st_h->read_stdin_ev = event_new(prog_eb(st_h->client_ctx->prog), + STDIN_FILENO, EV_READ, read_stdin, st_h); + event_add(st_h->read_stdin_ev, NULL); + return st_h; +} + + +static void +echo_client_on_read (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) +{ + char c; + size_t nr; + + nr = lsquic_stream_read(stream, &c, 1); + if (0 == nr) + { + lsquic_stream_shutdown(stream, 2); + return; + } + printf("%c", c); + fflush(stdout); + if ('\n' == c) + { + event_add(st_h->read_stdin_ev, NULL); + lsquic_stream_wantread(stream, 0); + } +} + + +static void +echo_client_on_write (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) +{ + /* Here we make an assumption that we can write the whole buffer. + * Don't do it in a real program. + */ + lsquic_stream_write(stream, st_h->buf, st_h->buf_off); + st_h->buf_off = 0; + + lsquic_stream_flush(stream); + lsquic_stream_wantwrite(stream, 0); + lsquic_stream_wantread(stream, 1); +} + + +static void +echo_client_on_close (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) +{ + LSQ_NOTICE("%s called", __func__); + if (st_h->read_stdin_ev) + { + event_del(st_h->read_stdin_ev); + event_free(st_h->read_stdin_ev); + } + free(st_h); + lsquic_conn_close(lsquic_stream_conn(stream)); +} + + +const struct lsquic_stream_if client_echo_stream_if = { + .on_new_conn = echo_client_on_new_conn, + .on_conn_closed = echo_client_on_conn_closed, + .on_new_stream = echo_client_on_new_stream, + .on_read = echo_client_on_read, + .on_write = echo_client_on_write, + .on_close = echo_client_on_close, +}; + + +static void +usage (const char *prog) +{ + const char *const slash = strrchr(prog, '/'); + if (slash) + prog = slash + 1; + LSQ_NOTICE( +"Usage: %s [opts]\n" +"\n" +"Options:\n" + , prog); +} + + +int +main (int argc, char **argv) +{ + int opt, s; + struct sport_head sports; + struct prog prog; + struct echo_client_ctx client_ctx; + + memset(&client_ctx, 0, sizeof(client_ctx)); + client_ctx.prog = &prog; + + TAILQ_INIT(&sports); + prog_init(&prog, 0, &sports, &client_echo_stream_if, &client_ctx); + + while (-1 != (opt = getopt(argc, argv, PROG_OPTS "h"))) + { + switch (opt) { + case 'h': + usage(argv[0]); + prog_print_common_options(&prog, stdout); + exit(0); + default: + if (0 != prog_set_opt(&prog, opt, optarg)) + exit(1); + } + } + + int flags = fcntl(STDIN_FILENO, F_GETFL); + flags |= O_NONBLOCK; + if (0 != fcntl(STDIN_FILENO, F_SETFL, flags)) + { + perror("fcntl"); + exit(1); + } + + if (0 != prog_prep(&prog)) + { + LSQ_ERROR("could not prep"); + exit(EXIT_FAILURE); + } + if (0 != prog_connect(&prog, NULL, 0)) + { + LSQ_ERROR("could not connect"); + exit(EXIT_FAILURE); + } + + LSQ_DEBUG("entering event loop"); + + s = prog_run(&prog); + prog_cleanup(&prog); + + exit(0 == s ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/test/echo_server.c b/test/echo_server.c new file mode 100644 index 000000000..cdef8046f --- /dev/null +++ b/test/echo_server.c @@ -0,0 +1,228 @@ +/* Copyright (c) 2017 - 2019 LiteSpeed Technologies Inc. See LICENSE. */ +/* + * echo_server.c -- QUIC server that echoes back input line by line + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lsquic.h" +#include "test_common.h" +#include "prog.h" + +#include "../src/liblsquic/lsquic_logger.h" + + +struct lsquic_conn_ctx; + +struct echo_server_ctx { + TAILQ_HEAD(, lsquic_conn_ctx) conn_ctxs; + unsigned max_reqs; + int n_conn; + struct sport_head sports; + struct prog *prog; +}; + +struct lsquic_conn_ctx { + TAILQ_ENTRY(lsquic_conn_ctx) next_connh; + lsquic_conn_t *conn; + struct echo_server_ctx *server_ctx; +}; + + +static lsquic_conn_ctx_t * +echo_server_on_new_conn (void *stream_if_ctx, lsquic_conn_t *conn) +{ + struct echo_server_ctx *server_ctx = stream_if_ctx; + lsquic_conn_ctx_t *conn_h = calloc(1, sizeof(*conn_h)); + conn_h->conn = conn; + conn_h->server_ctx = server_ctx; + TAILQ_INSERT_TAIL(&server_ctx->conn_ctxs, conn_h, next_connh); + LSQ_NOTICE("New connection!"); + print_conn_info(conn); + return conn_h; +} + + +static void +echo_server_on_conn_closed (lsquic_conn_t *conn) +{ + lsquic_conn_ctx_t *conn_h = lsquic_conn_get_ctx(conn); + if (conn_h->server_ctx->n_conn) + { + --conn_h->server_ctx->n_conn; + LSQ_NOTICE("Connection closed, remaining: %d", conn_h->server_ctx->n_conn); + if (0 == conn_h->server_ctx->n_conn) + prog_stop(conn_h->server_ctx->prog); + } + else + LSQ_NOTICE("Connection closed"); + TAILQ_REMOVE(&conn_h->server_ctx->conn_ctxs, conn_h, next_connh); + free(conn_h); +} + + +struct lsquic_stream_ctx { + lsquic_stream_t *stream; + struct echo_server_ctx *server_ctx; + char buf[0x100]; + size_t buf_off; +}; + + +static lsquic_stream_ctx_t * +echo_server_on_new_stream (void *stream_if_ctx, lsquic_stream_t *stream) +{ + lsquic_stream_ctx_t *st_h = malloc(sizeof(*st_h)); + st_h->stream = stream; + st_h->server_ctx = stream_if_ctx; + st_h->buf_off = 0; + lsquic_stream_wantread(stream, 1); + return st_h; +} + + +static struct lsquic_conn_ctx * +find_conn_h (const struct echo_server_ctx *server_ctx, lsquic_stream_t *stream) +{ + struct lsquic_conn_ctx *conn_h; + lsquic_conn_t *conn; + + conn = lsquic_stream_conn(stream); + TAILQ_FOREACH(conn_h, &server_ctx->conn_ctxs, next_connh) + if (conn_h->conn == conn) + return conn_h; + return NULL; +} + + +static void +echo_server_on_read (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) +{ + struct lsquic_conn_ctx *conn_h; + size_t nr; + + nr = lsquic_stream_read(stream, st_h->buf + st_h->buf_off++, 1); + if (0 == nr) + { + LSQ_NOTICE("EOF: closing connection"); + lsquic_stream_shutdown(stream, 2); + conn_h = find_conn_h(st_h->server_ctx, stream); + lsquic_conn_close(conn_h->conn); + } + else if ('\n' == st_h->buf[ st_h->buf_off - 1 ]) + { + /* Found end of line: echo it back */ + lsquic_stream_wantwrite(stream, 1); + lsquic_stream_wantread(stream, 0); + } + else if (st_h->buf_off == sizeof(st_h->buf)) + { + /* Out of buffer space: line too long */ + LSQ_NOTICE("run out of buffer space"); + lsquic_stream_shutdown(stream, 2); + } + else + { + /* Keep reading */; + } +} + + +static void +echo_server_on_write (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) +{ + lsquic_stream_write(stream, st_h->buf, st_h->buf_off); + st_h->buf_off = 0; + lsquic_stream_flush(stream); + lsquic_stream_wantwrite(stream, 0); + lsquic_stream_wantread(stream, 1); +} + + +static void +echo_server_on_stream_close (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) +{ + struct lsquic_conn_ctx *conn_h; + LSQ_NOTICE("%s called", __func__); + conn_h = find_conn_h(st_h->server_ctx, stream); + LSQ_WARN("%s: TODO: free connection handler %p", __func__, conn_h); + free(st_h); +} + + +const struct lsquic_stream_if server_echo_stream_if = { + .on_new_conn = echo_server_on_new_conn, + .on_conn_closed = echo_server_on_conn_closed, + .on_new_stream = echo_server_on_new_stream, + .on_read = echo_server_on_read, + .on_write = echo_server_on_write, + .on_close = echo_server_on_stream_close, +}; + + +static void +usage (const char *prog) +{ + const char *const slash = strrchr(prog, '/'); + if (slash) + prog = slash + 1; + printf( +"Usage: %s [opts]\n" +"\n" +"Options:\n" + , prog); +} + + +int +main (int argc, char **argv) +{ + int opt, s; + struct prog prog; + struct echo_server_ctx server_ctx; + + memset(&server_ctx, 0, sizeof(server_ctx)); + server_ctx.prog = &prog; + TAILQ_INIT(&server_ctx.sports); + TAILQ_INIT(&server_ctx.conn_ctxs); + + prog_init(&prog, LSENG_SERVER, &server_ctx.sports, + &server_echo_stream_if, &server_ctx); + + while (-1 != (opt = getopt(argc, argv, PROG_OPTS "hn:"))) + { + switch (opt) { + case 'n': + server_ctx.n_conn = atoi(optarg); + break; + case 'h': + usage(argv[0]); + prog_print_common_options(&prog, stdout); + exit(0); + default: + if (0 != prog_set_opt(&prog, opt, optarg)) + exit(1); + } + } + + if (0 != prog_prep(&prog)) + { + LSQ_ERROR("could not prep"); + exit(EXIT_FAILURE); + } + + LSQ_DEBUG("entering event loop"); + + s = prog_run(&prog); + prog_cleanup(&prog); + + exit(0 == s ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/test/http_client.c b/test/http_client.c index 148fb4b33..a8a1ab74d 100644 --- a/test/http_client.c +++ b/test/http_client.c @@ -1563,6 +1563,12 @@ main (int argc, char **argv) LSQ_ERROR("could not prep"); exit(EXIT_FAILURE); } + if (!(client_ctx.hostname || prog.prog_hostname)) + { + fprintf(stderr, "Specify hostname (used for SNI and :authority) via " + "-H option\n"); + exit(EXIT_FAILURE); + } if (was_empty && token) sport_set_token(TAILQ_LAST(&sports, sport_head), token); diff --git a/test/md5_client.c b/test/md5_client.c new file mode 100644 index 000000000..a1ee4ff5d --- /dev/null +++ b/test/md5_client.c @@ -0,0 +1,518 @@ +/* Copyright (c) 2017 - 2019 LiteSpeed Technologies Inc. See LICENSE. */ +/* + * md5_client.c -- This client sends one or more files to MD5 QUIC server + * for MD5 sum calculation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "lsquic.h" +#include "test_common.h" +#include "prog.h" + +#include "../src/liblsquic/lsquic_logger.h" +#include "../src/liblsquic/lsquic_int_types.h" +#include "../src/liblsquic/lsquic_varint.h" +#include "../src/liblsquic/lsquic_hq.h" +#include "../src/liblsquic/lsquic_sfcw.h" +#include "../src/liblsquic/lsquic_hash.h" +#include "../src/liblsquic/lsquic_stream.h" + +/* Set to non-zero value to test out what happens when reset is sent */ +#define RESET_AFTER_N_WRITES 0 + +static int g_write_file = 1; + +#define LOCAL_BUF_SIZE 0x100 + +static struct { + unsigned stream_id; /* If set, reset this stream ID */ + off_t offset; /* Reset it after writing this many bytes */ +} g_reset_stream; + +struct file { + LIST_ENTRY(file) next_file; + const char *filename; + struct lsquic_reader reader; + int fd; + unsigned priority; + enum { + FILE_RESET = (1 << 0), + } file_flags; + size_t md5_off; + char md5str[MD5_DIGEST_LENGTH * 2]; +}; + +struct lsquic_conn_ctx; + +struct client_ctx { + struct lsquic_conn_ctx *conn_h; + LIST_HEAD(, file) files; + unsigned n_files; + struct file *cur_file; + lsquic_engine_t *engine; + struct service_port *sport; + struct prog *prog; +}; + +struct lsquic_conn_ctx { + lsquic_conn_t *conn; + struct client_ctx *client_ctx; +}; + + +static lsquic_conn_ctx_t * +client_on_new_conn (void *stream_if_ctx, lsquic_conn_t *conn) +{ + struct client_ctx *client_ctx = stream_if_ctx; + lsquic_conn_ctx_t *conn_h = malloc(sizeof(*conn_h)); + conn_h->conn = conn; + conn_h->client_ctx = client_ctx; + client_ctx->conn_h = conn_h; + assert(client_ctx->n_files > 0); + unsigned n = client_ctx->n_files; + while (n--) + lsquic_conn_make_stream(conn); + print_conn_info(conn); + return conn_h; +} + + +static void +client_on_goaway_received (lsquic_conn_t *conn) +{ + LSQ_NOTICE("GOAWAY received"); +} + + +static void +client_on_conn_closed (lsquic_conn_t *conn) +{ + lsquic_conn_ctx_t *conn_h = lsquic_conn_get_ctx(conn); + LSQ_NOTICE("Connection closed"); + prog_stop(conn_h->client_ctx->prog); + free(conn_h); +} + + +struct lsquic_stream_ctx { + lsquic_stream_t *stream; + struct client_ctx *client_ctx; + struct file *file; + struct event *read_stdin_ev; + struct { + int initialized; + size_t size, + off; + } small; +}; + + +static lsquic_stream_ctx_t * +client_on_new_stream (void *stream_if_ctx, lsquic_stream_t *stream) +{ + struct client_ctx *const client_ctx = stream_if_ctx; + if (!stream) + { + assert(client_ctx->n_files > 0); + LSQ_NOTICE("%s: got null stream: no more streams possible; # files: %u", + __func__, client_ctx->n_files); + --client_ctx->n_files; + if (0 == client_ctx->n_files) + { + LSQ_DEBUG("closing connection"); + lsquic_conn_close(client_ctx->conn_h->conn); + } + return NULL; + } + lsquic_stream_ctx_t *st_h = calloc(1, sizeof(*st_h)); + st_h->stream = stream; + st_h->client_ctx = stream_if_ctx; + if (LIST_EMPTY(&st_h->client_ctx->files)) + { + /* XXX: perhaps we should not be able to write immediately: there may + * be internal memory constraints... + */ + lsquic_stream_write(stream, "client request", 14); + (void) lsquic_stream_flush(stream); + lsquic_stream_wantwrite(stream, 0); + lsquic_stream_wantread(stream, 1); + } + else + { + st_h->file = LIST_FIRST(&st_h->client_ctx->files); + if (g_write_file) + { + st_h->file->fd = -1; + st_h->file->reader.lsqr_read = test_reader_read; + st_h->file->reader.lsqr_size = test_reader_size; + st_h->file->reader.lsqr_ctx = create_lsquic_reader_ctx(st_h->file->filename); + if (!st_h->file->reader.lsqr_ctx) + exit(1); + } + else + { + st_h->file->fd = open(st_h->file->filename, O_RDONLY); + if (st_h->file->fd < 0) + { + LSQ_ERROR("could not open %s for reading: %s", + st_h->file->filename, strerror(errno)); + exit(1); + } + } + LIST_REMOVE(st_h->file, next_file); + lsquic_stream_set_priority(stream, st_h->file->priority); + lsquic_stream_wantwrite(stream, 1); + } + return st_h; +} + + +static size_t +buf_reader_size (void *reader_ctx) +{ + lsquic_stream_ctx_t *const st_h = reader_ctx; + struct stat st; + off_t off; + + if (st_h->small.initialized) + goto initialized; + + if (0 != fstat(st_h->file->fd, &st)) + { + LSQ_ERROR("fstat failed: %s", strerror(errno)); + goto err; + } + + off = lseek(st_h->file->fd, 0, SEEK_CUR); + if (off == (off_t) -1) + { + LSQ_ERROR("lseek failed: %s", strerror(errno)); + goto err; + } + + if (st.st_size < off) + { + LSQ_ERROR("size mismatch"); + goto err; + } + + st_h->small.initialized = 1; + st_h->small.off = off; + st_h->small.size = st.st_size; + + initialized: + if (st_h->small.size - st_h->small.off > LOCAL_BUF_SIZE) + return LOCAL_BUF_SIZE; + else + return st_h->small.size - st_h->small.off; + + err: + close(st_h->file->fd); + st_h->file->fd = 0; + return 0; +} + + +static size_t +buf_reader_read (void *reader_ctx, void *buf, size_t count) +{ + lsquic_stream_ctx_t *const st_h = reader_ctx; + ssize_t nr; + unsigned char local_buf[LOCAL_BUF_SIZE]; + + assert(st_h->small.initialized); + + if (count > sizeof(local_buf)) + count = sizeof(local_buf); + + nr = read(st_h->file->fd, local_buf, count); + if (nr < 0) + { + LSQ_ERROR("read: %s", strerror(errno)); + close(st_h->file->fd); + st_h->file->fd = 0; + return 0; + } + + memcpy(buf, local_buf, nr); + st_h->small.off += nr; + return nr; +} + + +static void +client_file_on_write_buf (lsquic_stream_ctx_t *st_h) +{ + ssize_t nw; + struct lsquic_reader reader = { + .lsqr_read = buf_reader_read, + .lsqr_size = buf_reader_size, + .lsqr_ctx = st_h, + }; + + if (g_reset_stream.stream_id == lsquic_stream_id(st_h->stream) && + lseek(st_h->file->fd, 0, SEEK_CUR) >= g_reset_stream.offset) + { + lsquic_stream_reset(st_h->stream, 0x01 /* QUIC_INTERNAL_ERROR */); + g_reset_stream.stream_id = 0; /* Reset only once */ + } + + nw = lsquic_stream_writef(st_h->stream, &reader); + if (-1 == nw) + { + if (ECONNRESET == errno) + st_h->file->file_flags |= FILE_RESET; + LSQ_WARN("lsquic_stream_read: %s", strerror(errno)); + lsquic_stream_close(st_h->stream); + return; + } + +#if RESET_AFTER_N_WRITES + static int write_count = 0; + if (write_count++ > RESET_AFTER_N_WRITES) + lsquic_stream_reset(st_h->stream, 0); +#endif + + if (0 == nw) + { + (void) close(st_h->file->fd); + if (0 == lsquic_stream_shutdown(st_h->stream, 1)) + lsquic_stream_wantread(st_h->stream, 1); + else + { + if (ECONNRESET == errno) + st_h->file->file_flags |= FILE_RESET; + LSQ_WARN("lsquic_stream_shutdown: %s", strerror(errno)); + lsquic_stream_close(st_h->stream); + } + } +} + + +static void +client_file_on_write_efficient (lsquic_stream_t *stream, + lsquic_stream_ctx_t *st_h) +{ + ssize_t nw; + + nw = lsquic_stream_writef(stream, &st_h->file->reader); + if (nw < 0) + { + LSQ_ERROR("write error: %s", strerror(errno)); + exit(1); + } + if (nw == 0) + { + destroy_lsquic_reader_ctx(st_h->file->reader.lsqr_ctx); + st_h->file->reader.lsqr_ctx = NULL; + if (0 == lsquic_stream_shutdown(st_h->stream, 1)) + lsquic_stream_wantread(st_h->stream, 1); + else + { + if (ECONNRESET == errno) + st_h->file->file_flags |= FILE_RESET; + LSQ_WARN("lsquic_stream_shutdown: %s", strerror(errno)); + lsquic_stream_close(st_h->stream); + } + } +} + + +static void +client_file_on_write (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) +{ + if (g_write_file) + client_file_on_write_efficient(stream, st_h); + else + client_file_on_write_buf(st_h); +} + + +static void +client_file_on_read (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) +{ + char buf; + /* We expect to read in 32-character MD5 string */ + size_t ntoread = sizeof(st_h->file->md5str) - st_h->file->md5_off; + if (0 == ntoread) + { + lsquic_stream_wantread(stream, 0); + /* XXX What about an error (due to RST_STREAM) here: how are we to + * handle it? + */ + /* Expect a FIN */ + if (0 == lsquic_stream_read(stream, &buf, sizeof(buf))) + { + LSQ_NOTICE("%.*s %s", (int) sizeof(st_h->file->md5str), + st_h->file->md5str, + st_h->file->filename); + fflush(stdout); + LSQ_DEBUG("# of files: %d", st_h->client_ctx->n_files); + lsquic_stream_shutdown(stream, 0); + } + else + LSQ_ERROR("expected FIN from stream!"); + } + else + { + ssize_t nr = lsquic_stream_read(stream, + st_h->file->md5str + st_h->file->md5_off, ntoread); + if (-1 == nr) + { + if (ECONNRESET == errno) + st_h->file->file_flags |= FILE_RESET; + LSQ_WARN("lsquic_stream_read: %s", strerror(errno)); + lsquic_stream_close(stream); + return; + } + else + st_h->file->md5_off += nr; + } +} + + +static void +client_file_on_close (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) +{ + --st_h->client_ctx->n_files; + LSQ_NOTICE("%s called for stream %"PRIu64", # files: %u", __func__, + lsquic_stream_id(stream), st_h->client_ctx->n_files); + if (0 == st_h->client_ctx->n_files) + lsquic_conn_close(st_h->client_ctx->conn_h->conn); + if (!(st_h->file->file_flags & FILE_RESET) && 0 == RESET_AFTER_N_WRITES) + assert(st_h->file->md5_off == sizeof(st_h->file->md5str)); + if (st_h->file->reader.lsqr_ctx) + { + destroy_lsquic_reader_ctx(st_h->file->reader.lsqr_ctx); + st_h->file->reader.lsqr_ctx = NULL; + } + if (st_h->file->fd >= 0) + (void) close(st_h->file->fd); + free(st_h->file); + free(st_h); +} + + +const struct lsquic_stream_if client_file_stream_if = { + .on_new_conn = client_on_new_conn, + .on_goaway_received = client_on_goaway_received, + .on_conn_closed = client_on_conn_closed, + .on_new_stream = client_on_new_stream, + .on_read = client_file_on_read, + .on_write = client_file_on_write, + .on_close = client_file_on_close, +}; + + +static void +usage (const char *prog) +{ + const char *const slash = strrchr(prog, '/'); + if (slash) + prog = slash + 1; + printf( +"Usage: %s [opts]\n" +"\n" +"Options:\n" +" -f FILE File to send to the server -- must be specified at least\n" +" once.\n" +" -b Use buffering API for sending files over rather than\n" +" the efficient version.\n" +" -p PRIORITY Applicatble to previous file specified with -f\n" +" -r STREAM_ID:OFFSET\n" +" Reset stream STREAM_ID after sending more that OFFSET bytes.\n" + , prog); +} + + +int +main (int argc, char **argv) +{ + int opt, s; + struct sport_head sports; + struct prog prog; + struct client_ctx client_ctx; + struct file *file; + + file = NULL; + memset(&client_ctx, 0, sizeof(client_ctx)); + client_ctx.prog = &prog; + + TAILQ_INIT(&sports); + prog_init(&prog, 0, &sports, &client_file_stream_if, &client_ctx); + + while (-1 != (opt = getopt(argc, argv, PROG_OPTS "bhr:f:p:"))) + { + switch (opt) { + case 'p': + if (file) + file->priority = atoi(optarg); + else + { + fprintf(stderr, "No file to apply priority to\n"); + exit(1); + } + break; + case 'b': + g_write_file = 0; + break; + case 'f': + file = calloc(1, sizeof(*file)); + LIST_INSERT_HEAD(&client_ctx.files, file, next_file); + ++client_ctx.n_files; + file->filename = optarg; + break; + case 'r': + g_reset_stream.stream_id = atoi(optarg); + g_reset_stream.offset = atoi(strchr(optarg, ':') + 1); + break; + case 'h': + usage(argv[0]); + prog_print_common_options(&prog, stdout); + exit(0); + default: + if (0 != prog_set_opt(&prog, opt, optarg)) + exit(1); + } + } + + if (LIST_EMPTY(&client_ctx.files)) + { + fprintf(stderr, "please specify one of more files using -f\n"); + exit(1); + } + + if (0 != prog_prep(&prog)) + { + LSQ_ERROR("could not prep"); + exit(EXIT_FAILURE); + } + client_ctx.sport = TAILQ_FIRST(&sports); + + if (0 != prog_connect(&prog, NULL, 0)) + { + LSQ_ERROR("could not connect"); + exit(EXIT_FAILURE); + } + + LSQ_DEBUG("entering event loop"); + + s = prog_run(&prog); + prog_cleanup(&prog); + + exit(0 == s ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/test/md5_server.c b/test/md5_server.c new file mode 100644 index 000000000..d5b4907f3 --- /dev/null +++ b/test/md5_server.c @@ -0,0 +1,340 @@ +/* Copyright (c) 2017 - 2019 LiteSpeed Technologies Inc. See LICENSE. */ +/* + * md5_server.c -- Read one or more streams from the client and return + * MD5 sum of the payload. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "lsquic.h" +#include "test_common.h" +#include "prog.h" + +#include "../src/liblsquic/lsquic_logger.h" + + +static int g_really_calculate_md5 = 1; + +/* Turn on to test whether stream reset is being sent when stream is closed + * prematurely. + */ +static struct { + unsigned stream_id; + unsigned long limit; + unsigned long n_read; +} g_premature_close; + +struct lsquic_conn_ctx; + +struct server_ctx { + TAILQ_HEAD(, lsquic_conn_ctx) conn_ctxs; + unsigned max_reqs; + int n_conn; + time_t expiry; + struct sport_head sports; + struct prog *prog; +}; + +struct lsquic_conn_ctx { + TAILQ_ENTRY(lsquic_conn_ctx) next_connh; + lsquic_conn_t *conn; + unsigned n_reqs, n_closed; + struct server_ctx *server_ctx; +}; + + +static lsquic_conn_ctx_t * +server_on_new_conn (void *stream_if_ctx, lsquic_conn_t *conn) +{ + struct server_ctx *server_ctx = stream_if_ctx; + lsquic_conn_ctx_t *conn_h = calloc(1, sizeof(*conn_h)); + conn_h->conn = conn; + conn_h->server_ctx = server_ctx; + TAILQ_INSERT_TAIL(&server_ctx->conn_ctxs, conn_h, next_connh); + LSQ_NOTICE("New connection!"); + print_conn_info(conn); + return conn_h; +} + + +static void +server_on_conn_closed (lsquic_conn_t *conn) +{ + lsquic_conn_ctx_t *conn_h = lsquic_conn_get_ctx(conn); + int stopped; + + if (conn_h->server_ctx->expiry && conn_h->server_ctx->expiry < time(NULL)) + { + LSQ_NOTICE("reached engine expiration time, shut down"); + prog_stop(conn_h->server_ctx->prog); + stopped = 1; + } + else + stopped = 0; + + if (conn_h->server_ctx->n_conn) + { + --conn_h->server_ctx->n_conn; + LSQ_NOTICE("Connection closed, remaining: %d", conn_h->server_ctx->n_conn); + if (0 == conn_h->server_ctx->n_conn && !stopped) + prog_stop(conn_h->server_ctx->prog); + } + else + LSQ_NOTICE("Connection closed"); + TAILQ_REMOVE(&conn_h->server_ctx->conn_ctxs, conn_h, next_connh); + free(conn_h); +} + + +struct lsquic_stream_ctx { + lsquic_stream_t *stream; + struct server_ctx *server_ctx; + MD5_CTX md5ctx; + unsigned char md5sum[MD5_DIGEST_LENGTH]; + char md5str[MD5_DIGEST_LENGTH * 2 + 1]; +}; + + +static struct lsquic_conn_ctx * +find_conn_h (const struct server_ctx *server_ctx, lsquic_stream_t *stream) +{ + struct lsquic_conn_ctx *conn_h; + lsquic_conn_t *conn; + + conn = lsquic_stream_conn(stream); + TAILQ_FOREACH(conn_h, &server_ctx->conn_ctxs, next_connh) + if (conn_h->conn == conn) + return conn_h; + return NULL; +} + + +static lsquic_stream_ctx_t * +server_md5_on_new_stream (void *stream_if_ctx, lsquic_stream_t *stream) +{ + struct lsquic_conn_ctx *conn_h; + lsquic_stream_ctx_t *st_h = malloc(sizeof(*st_h)); + st_h->stream = stream; + st_h->server_ctx = stream_if_ctx; + lsquic_stream_wantread(stream, 1); + if (g_really_calculate_md5) + MD5_Init(&st_h->md5ctx); + conn_h = find_conn_h(st_h->server_ctx, stream); + assert(conn_h); + conn_h->n_reqs++; + LSQ_NOTICE("request #%u", conn_h->n_reqs); + if (st_h->server_ctx->max_reqs && + conn_h->n_reqs >= st_h->server_ctx->max_reqs) + { + /* The assert guards the assumption that after the we mark the + * connection as going away, no new streams are opened and thus + * this callback is not called. + */ + assert(conn_h->n_reqs == st_h->server_ctx->max_reqs); + LSQ_NOTICE("reached maximum requests: %u, going away", + st_h->server_ctx->max_reqs); + lsquic_conn_going_away(conn_h->conn); + } + return st_h; +} + + +static void +server_md5_on_read (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) +{ + char buf[0x1000]; + ssize_t nr; + + nr = lsquic_stream_read(stream, buf, sizeof(buf)); + if (-1 == nr) + { + /* This should never return an error if we only call read() once + * per callback. + */ + perror("lsquic_stream_read"); + lsquic_stream_shutdown(stream, 0); + return; + } + + if (g_premature_close.limit && + g_premature_close.stream_id == lsquic_stream_id(stream)) + { + g_premature_close.n_read += nr; + if (g_premature_close.n_read > g_premature_close.limit) + { + LSQ_WARN("Done after reading %lu bytes", g_premature_close.n_read); + lsquic_stream_shutdown(stream, 0); + return; + } + } + + if (nr) + { + if (g_really_calculate_md5) + MD5_Update(&st_h->md5ctx, buf, nr); + } + else + { + lsquic_stream_wantread(stream, 0); + if (g_really_calculate_md5) + { + MD5_Final(st_h->md5sum, &st_h->md5ctx); + snprintf(st_h->md5str, sizeof(st_h->md5str), + "%02x%02x%02x%02x%02x%02x%02x%02x" + "%02x%02x%02x%02x%02x%02x%02x%02x" + , st_h->md5sum[0] + , st_h->md5sum[1] + , st_h->md5sum[2] + , st_h->md5sum[3] + , st_h->md5sum[4] + , st_h->md5sum[5] + , st_h->md5sum[6] + , st_h->md5sum[7] + , st_h->md5sum[8] + , st_h->md5sum[9] + , st_h->md5sum[10] + , st_h->md5sum[11] + , st_h->md5sum[12] + , st_h->md5sum[13] + , st_h->md5sum[14] + , st_h->md5sum[15] + ); + } + else + { + memset(st_h->md5str, '0', sizeof(st_h->md5str) - 1); + st_h->md5str[sizeof(st_h->md5str) - 1] = '\0'; + } + lsquic_stream_wantwrite(stream, 1); + lsquic_stream_shutdown(stream, 0); + } +} + + +static void +server_md5_on_write (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) +{ + ssize_t nw; + nw = lsquic_stream_write(stream, st_h->md5str, sizeof(st_h->md5str) - 1); + if (-1 == nw) + { + perror("lsquic_stream_write"); + return; + } + lsquic_stream_wantwrite(stream, 0); + lsquic_stream_shutdown(stream, 1); +} + + +static void +server_on_close (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) +{ + struct lsquic_conn_ctx *conn_h; + LSQ_NOTICE("%s called", __func__); + conn_h = find_conn_h(st_h->server_ctx, stream); + conn_h->n_closed++; + if (st_h->server_ctx->max_reqs && + conn_h->n_closed >= st_h->server_ctx->max_reqs) + { + assert(conn_h->n_closed == st_h->server_ctx->max_reqs); + LSQ_NOTICE("closing connection after completing %u requests", + conn_h->n_closed); + lsquic_conn_close(conn_h->conn); + } + free(st_h); +} + + +const struct lsquic_stream_if server_md5_stream_if = { + .on_new_conn = server_on_new_conn, + .on_conn_closed = server_on_conn_closed, + .on_new_stream = server_md5_on_new_stream, + .on_read = server_md5_on_read, + .on_write = server_md5_on_write, + .on_close = server_on_close, +}; + + +static void +usage (const char *prog) +{ + const char *const slash = strrchr(prog, '/'); + if (slash) + prog = slash + 1; + printf( +"Usage: %s [opts]\n" +"\n" +"Options:\n" +" -e EXPIRY Stop engine after this many seconds. The expiration is\n" +" checked when connections are closed.\n" + , prog); +} + + +int +main (int argc, char **argv) +{ + int opt, s; + struct prog prog; + struct server_ctx server_ctx; + + memset(&server_ctx, 0, sizeof(server_ctx)); + TAILQ_INIT(&server_ctx.conn_ctxs); + server_ctx.prog = &prog; + TAILQ_INIT(&server_ctx.sports); + prog_init(&prog, LSENG_SERVER, &server_ctx.sports, + &server_md5_stream_if, &server_ctx); + + while (-1 != (opt = getopt(argc, argv, PROG_OPTS "hr:Fn:e:p:"))) + { + switch (opt) { + case 'F': + g_really_calculate_md5 = 0; + break; + case 'p': + g_premature_close.stream_id = atoi(optarg); + g_premature_close.limit = atoi(strchr(optarg, ':') + 1); + break; + case 'r': + server_ctx.max_reqs = atoi(optarg); + break; + case 'e': + server_ctx.expiry = time(NULL) + atoi(optarg); + break; + case 'n': + server_ctx.n_conn = atoi(optarg); + break; + case 'h': + usage(argv[0]); + prog_print_common_options(&prog, stdout); + exit(0); + default: + if (0 != prog_set_opt(&prog, opt, optarg)) + exit(1); + } + } + + if (0 != prog_prep(&prog)) + { + LSQ_ERROR("could not prep"); + exit(EXIT_FAILURE); + } + + LSQ_DEBUG("entering event loop"); + + s = prog_run(&prog); + prog_cleanup(&prog); + + exit(0 == s ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/test/unittests/test_attq.c b/test/unittests/test_attq.c index 0979b32fb..00281127d 100644 --- a/test/unittests/test_attq.c +++ b/test/unittests/test_attq.c @@ -58,8 +58,9 @@ test_attq_ordering (enum sort_action sa) { struct attq *q; struct lsquic_conn *conns, *conn; + const struct attq_elem *next_attq; lsquic_time_t prev; - const lsquic_time_t *t; + lsquic_time_t t; unsigned i; int s; @@ -88,7 +89,7 @@ test_attq_ordering (enum sort_action sa) conns = calloc(sizeof(curiosity), sizeof(conns[0])); for (i = 0; i < sizeof(curiosity); ++i) { - s = attq_add(q, &conns[i], (lsquic_time_t) curiosity[i]); + s = attq_add(q, &conns[i], (lsquic_time_t) curiosity[i], 0); assert(s == 0); } @@ -116,17 +117,18 @@ test_attq_ordering (enum sort_action sa) for (i = 0; i < sizeof(curiosity); ++i) { - t = attq_next_time(q); - assert(t); + next_attq = attq_next(q); + assert(next_attq); + t = next_attq->ae_adv_time; if (i > 0) - assert(*t >= prev); - prev = *t; + assert(t >= prev); + prev = t; conn = attq_pop(q, ~0ULL); assert(conn); } - t = attq_next_time(q); - assert(!t); + next_attq = attq_next(q); + assert(!next_attq); conn = attq_pop(q, ~0ULL); assert(!conn); @@ -145,12 +147,12 @@ test_attq_removal_1 (void) q = attq_create(); conns = calloc(6, sizeof(conns[0])); - attq_add(q, &conns[0], 1); - attq_add(q, &conns[1], 4); - attq_add(q, &conns[2], 2); - attq_add(q, &conns[3], 5); - attq_add(q, &conns[4], 6); - attq_add(q, &conns[5], 3); + attq_add(q, &conns[0], 1, 0); + attq_add(q, &conns[1], 4, 0); + attq_add(q, &conns[2], 2, 0); + attq_add(q, &conns[3], 5, 0); + attq_add(q, &conns[4], 6, 0); + attq_add(q, &conns[5], 3, 0); attq_remove(q, &conns[3]); @@ -169,15 +171,15 @@ test_attq_removal_2 (void) q = attq_create(); conns = calloc(9, sizeof(conns[0])); - attq_add(q, &conns[0], 1); - attq_add(q, &conns[1], 5); - attq_add(q, &conns[2], 6); - attq_add(q, &conns[3], 9); - attq_add(q, &conns[4], 11); - attq_add(q, &conns[5], 8); - attq_add(q, &conns[6], 15); - attq_add(q, &conns[7], 17); - attq_add(q, &conns[8], 21); + attq_add(q, &conns[0], 1, 0); + attq_add(q, &conns[1], 5, 0); + attq_add(q, &conns[2], 6, 0); + attq_add(q, &conns[3], 9, 0); + attq_add(q, &conns[4], 11, 0); + attq_add(q, &conns[5], 8, 0); + attq_add(q, &conns[6], 15, 0); + attq_add(q, &conns[7], 17, 0); + attq_add(q, &conns[8], 21, 0); attq_remove(q, &conns[1]); @@ -196,15 +198,15 @@ test_attq_removal_3 (void) q = attq_create(); conns = calloc(9, sizeof(conns[0])); - attq_add(q, &conns[0], 1); - attq_add(q, &conns[1], 9); - attq_add(q, &conns[2], 22); - attq_add(q, &conns[3], 17); - attq_add(q, &conns[4], 11); - attq_add(q, &conns[5], 33); - attq_add(q, &conns[6], 27); - attq_add(q, &conns[7], 21); - attq_add(q, &conns[8], 19); + attq_add(q, &conns[0], 1, 0); + attq_add(q, &conns[1], 9, 0); + attq_add(q, &conns[2], 22, 0); + attq_add(q, &conns[3], 17, 0); + attq_add(q, &conns[4], 11, 0); + attq_add(q, &conns[5], 33, 0); + attq_add(q, &conns[6], 27, 0); + attq_add(q, &conns[7], 21, 0); + attq_add(q, &conns[8], 19, 0); attq_remove(q, &conns[1]); diff --git a/test/unittests/test_stream.c b/test/unittests/test_stream.c index 0c98f59cd..d7ce7199e 100644 --- a/test/unittests/test_stream.c +++ b/test/unittests/test_stream.c @@ -2014,6 +2014,33 @@ test_insert_edge_cases (void) } +/* When HTTP stream is closed unexpectedly, send a reset instead of creating + * an empty STREAM frame with a FIN bit set. + */ +static void +test_unexpected_http_close (void) +{ + struct test_objs tobjs; + lsquic_stream_t *stream; + int s; + + stream_ctor_flags |= SCF_HTTP; + init_test_objs(&tobjs, 0x4000, 0x4000, NULL); + + stream = new_stream(&tobjs, 123); + assert(stream->sm_bflags & SMBF_USE_HEADERS); /* Self-check */ + s = lsquic_stream_close(stream); + assert(s == 0); + assert(stream->sm_qflags & SMQF_SEND_RST); + assert(stream->sm_qflags & SMQF_CALL_ONCLOSE); + assert(!lsquic_send_ctl_has_buffered(&tobjs.send_ctl)); + + lsquic_stream_destroy(stream); + deinit_test_objs(&tobjs); + stream_ctor_flags &= ~SCF_HTTP; +} + + static void test_writing_to_stream_schedule_stream_packets_immediately (void) { @@ -3060,6 +3087,7 @@ main (int argc, char **argv) test_reading_from_stream2(); test_overlaps(); test_insert_edge_cases(); + test_unexpected_http_close(); { int idx[6];