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.
This commit is contained in:
Dmitri Tikhonov 2019-10-15 17:02:21 -04:00
parent ad08470cea
commit 0adf085acf
27 changed files with 1668 additions and 92 deletions

View file

@ -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.

View file

@ -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)

View file

@ -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
----------------------

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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";
}
}

View file

@ -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

View file

@ -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 *);

View file

@ -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,
};

View file

@ -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;
}

View file

@ -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 *,

View file

@ -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;
}

View file

@ -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))

View file

@ -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)

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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)

View file

@ -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))

245
test/echo_client.c Normal file
View file

@ -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 <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/queue.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <event2/event.h>
#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);
}

228
test/echo_server.c Normal file
View file

@ -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 <assert.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/queue.h>
#include <time.h>
#include <unistd.h>
#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);
}

View file

@ -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);

518
test/md5_client.c Normal file
View file

@ -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 <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/queue.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <event2/event.h>
#include <openssl/md5.h>
#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);
}

340
test/md5_server.c Normal file
View file

@ -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 <assert.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/queue.h>
#include <time.h>
#include <unistd.h>
#include <openssl/md5.h>
#include <event2/event.h>
#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);
}

View file

@ -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]);

View file

@ -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];