diff --git a/include/quicly.h b/include/quicly.h index 2431acf7..5d8d1362 100644 --- a/include/quicly.h +++ b/include/quicly.h @@ -315,10 +315,21 @@ struct st_quicly_context_t { * will request the peer to send one ACK every 1/8 RTT (or CWND). 0 disables the use of the delayed-ack extension. */ uint16_t ack_frequency; + /** + * destroy the packet being sent at given ratio (xorshift with given seed is used for each connection) + */ + struct { + uint16_t ratio; + uint64_t seed; + } destroy_packet; /** * expand client hello so that it does not fit into one datagram */ unsigned expand_client_hello : 1; + /** + * intentionally fragment stream payload; this is useful for testing retransmission + */ + unsigned fragment_payload : 1; /** * */ diff --git a/lib/defaults.c b/lib/defaults.c index 6b7b3ebb..a502e091 100644 --- a/lib/defaults.c +++ b/lib/defaults.c @@ -28,6 +28,7 @@ #define DEFAULT_MAX_CRYPTO_BYTES 65536 #define DEFAULT_INITCWND_PACKETS 10 #define DEFAULT_PRE_VALIDATION_AMPLIFICATION_LIMIT 3 +#define DEFAULT_DESTROY_PACKET_SEED 88172645463325252 /* profile that employs IETF specified values */ const quicly_context_t quicly_spec_context = {NULL, /* tls */ @@ -45,7 +46,9 @@ const quicly_context_t quicly_spec_context = {NULL, QUICLY_PROTOCOL_VERSION_1, DEFAULT_PRE_VALIDATION_AMPLIFICATION_LIMIT, 0, /* ack_frequency */ + {0, DEFAULT_DESTROY_PACKET_SEED}, /* destroy_packet */ 0, /* enlarge_client_hello */ + 0, /* fragment_payload */ NULL, NULL, /* on_stream_open */ &quicly_default_stream_scheduler, @@ -73,7 +76,9 @@ const quicly_context_t quicly_performant_context = {NULL, QUICLY_PROTOCOL_VERSION_1, DEFAULT_PRE_VALIDATION_AMPLIFICATION_LIMIT, 0, /* ack_frequency */ + {0, DEFAULT_DESTROY_PACKET_SEED}, /* destroy_packet */ 0, /* enlarge_client_hello */ + 0, /* fragment_payload */ NULL, NULL, /* on_stream_open */ &quicly_default_stream_scheduler, diff --git a/lib/quicly.c b/lib/quicly.c index f9e63928..6aa920da 100644 --- a/lib/quicly.c +++ b/lib/quicly.c @@ -242,6 +242,10 @@ struct st_quicly_conn_t { * next PN to be skipped */ uint64_t next_pn_to_skip; + /** + * RNG used to determine if packet should be destroyed + */ + uint64_t destroy_packet_rng; /** * */ @@ -454,6 +458,12 @@ static const quicly_transport_parameters_t default_transport_params = {.max_udp_ .active_connection_id_limit = QUICLY_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT}; +static void xorshift64(uint64_t *x) +{ + *x = *x ^ (*x << 7); + *x = *x ^ (*x >> 9); +} + static const struct st_ptls_salt_t *get_salt(uint32_t protocol_version) { static const struct st_ptls_salt_t @@ -2115,6 +2125,7 @@ static quicly_conn_t *create_connection(quicly_context_t *ctx, uint32_t protocol &conn->super.remote.transport_params.max_ack_delay, &conn->super.remote.transport_params.ack_delay_exponent); conn->egress.next_pn_to_skip = calc_next_pn_to_skip(conn->super.ctx->tls, 0, initcwnd, conn->super.ctx->initial_egress_max_udp_payload_size); + conn->egress.destroy_packet_rng = conn->super.ctx->destroy_packet.seed; conn->egress.max_udp_payload_size = conn->super.ctx->initial_egress_max_udp_payload_size; init_max_streams(&conn->egress.max_streams.uni); init_max_streams(&conn->egress.max_streams.bidi); @@ -3111,6 +3122,13 @@ static int commit_send_packet(quicly_conn_t *conn, quicly_send_context_t *s, int s->dst_payload_from - s->payload_buf.datagram, conn->egress.packet_number, coalesced); + /* packet drill: intentionally destroy the packet at given ratio */ + if (conn->super.ctx->destroy_packet.ratio > 0) { + xorshift64(&conn->egress.destroy_packet_rng); + if (conn->egress.destroy_packet_rng % 1024 < conn->super.ctx->destroy_packet.ratio) + s->dst[-1] ^= 1; + } + /* update CC, commit sentmap */ if (s->target.ack_eliciting) { packet_bytes_in_flight = s->dst - s->target.first_byte_at; @@ -3665,8 +3683,22 @@ int quicly_send_stream(quicly_stream_t *stream, quicly_send_context_t *s) } assert(len != 0); + /* [fragment] trim output to cause fragmentation at the receiver */ + if (stream->conn->super.ctx->fragment_payload) { + size_t new_len = len < 4 ? 1 : len / 4; + if (new_len < len) + wrote_all = 0; + len = new_len; + } + adjust_stream_frame_layout(&s->dst, s->dst_end, &len, &wrote_all, &frame_type_at); + /* [fragment] append PADDING to prevent more frames from getting added to the same packet */ + if (stream->conn->super.ctx->fragment_payload && s->dst < s->dst_end) { + memset(s->dst, QUICLY_FRAME_TYPE_PADDING, s->dst_end - s->dst); + s->dst = s->dst_end; + } + /* determine if the frame incorporates FIN */ if (off + len == stream->sendstate.final_size) { assert(!quicly_sendstate_is_open(&stream->sendstate)); diff --git a/src/cli.c b/src/cli.c index d738156f..fcd0556f 100644 --- a/src/cli.c +++ b/src/cli.c @@ -1022,6 +1022,7 @@ static void usage(const char *cmd) " -E expand Client Hello (sends multiple client Initials)\n" " -f fraction increases the induced ack frequency to specified\n" " fraction of CWND (default: 0)\n" + " -F introduce fragmentation\n" " -G enable UDP generic segmentation offload\n" " -i interval interval to reissue requests (in milliseconds)\n" " -I timeout idle timeout (in milliseconds; default: 600,000)\n" @@ -1051,6 +1052,8 @@ static void usage(const char *cmd) " -x named-group named group to be used (default: secp256r1)\n" " -X max bidirectional stream count (default: 100)\n" " -y cipher-suite cipher-suite to be used (default: all)\n" + " -Y ratio destroy the packet being sent at given ratio\n" + " (default: 0)\n" " -h print this help\n" "\n", cmd); @@ -1094,7 +1097,7 @@ int main(int argc, char **argv) address_token_aead.dec = ptls_aead_new(&ptls_openssl_aes128gcm, &ptls_openssl_sha256, 0, secret, ""); } - while ((ch = getopt(argc, argv, "a:b:B:c:C:Dd:k:Ee:f:Gi:I:K:l:M:m:NnOp:P:Rr:S:s:u:U:Vvw:W:x:X:y:h")) != -1) { + while ((ch = getopt(argc, argv, "a:b:B:c:C:Dd:k:Ee:Ff:Gi:I:K:l:M:m:NnOp:P:Rr:S:s:u:U:Vvw:W:x:X:Y:y:h")) != -1) { switch (ch) { case 'a': assert(negotiated_protocols.count < PTLS_ELEMENTSOF(negotiated_protocols.list)); @@ -1156,6 +1159,9 @@ int main(int argc, char **argv) } setvbuf(quicly_trace_fp, NULL, _IONBF, 0); break; + case 'F': + ctx.fragment_payload = 1; + break; case 'f': { double fraction; if (sscanf(optarg, "%lf", &fraction) != 1) { @@ -1291,6 +1297,14 @@ int main(int argc, char **argv) exit(1); } break; + case 'Y': { + double ratio; + if (sscanf(optarg, "%lf", &ratio) != 1 || !(0 <= ratio && ratio <= 1)) { + fprintf(stderr, "failed to parse packet destroy ratio (-Y): %s\n", optarg); + exit(1); + } + ctx.destroy_packet.ratio = (uint16_t)(ratio * 1024); + } break; case 'y': { size_t i; for (i = 0; cipher_suites[i] != NULL; ++i)