From 30bccca01d7ac6a626c8786d73b91a81dee6a3e0 Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Sun, 5 Dec 2021 14:30:58 +0900 Subject: [PATCH 1/3] add option to intentionally fragment payload --- include/quicly.h | 4 ++++ lib/defaults.c | 2 ++ lib/quicly.c | 14 ++++++++++++++ src/cli.c | 6 +++++- 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/include/quicly.h b/include/quicly.h index 2431acf7..3a8ba038 100644 --- a/include/quicly.h +++ b/include/quicly.h @@ -319,6 +319,10 @@ struct st_quicly_context_t { * 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..ac867fc9 100644 --- a/lib/defaults.c +++ b/lib/defaults.c @@ -46,6 +46,7 @@ const quicly_context_t quicly_spec_context = {NULL, DEFAULT_PRE_VALIDATION_AMPLIFICATION_LIMIT, 0, /* ack_frequency */ 0, /* enlarge_client_hello */ + 0, /* fragment_payload */ NULL, NULL, /* on_stream_open */ &quicly_default_stream_scheduler, @@ -74,6 +75,7 @@ const quicly_context_t quicly_performant_context = {NULL, DEFAULT_PRE_VALIDATION_AMPLIFICATION_LIMIT, 0, /* ack_frequency */ 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..bad63399 100644 --- a/lib/quicly.c +++ b/lib/quicly.c @@ -3665,8 +3665,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..9e4a8140 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" @@ -1094,7 +1095,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:h")) != -1) { switch (ch) { case 'a': assert(negotiated_protocols.count < PTLS_ELEMENTSOF(negotiated_protocols.list)); @@ -1156,6 +1157,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) { From 6fa59fbb387547d7fe61eff4f5b99e8d14d8fba5 Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Sun, 5 Dec 2021 14:53:18 +0900 Subject: [PATCH 2/3] add option to destroy packets being sent at a specified ratio --- include/quicly.h | 4 ++++ lib/defaults.c | 2 ++ lib/quicly.c | 9 +++++++++ src/cli.c | 12 +++++++++++- 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/include/quicly.h b/include/quicly.h index 3a8ba038..9f5970f1 100644 --- a/include/quicly.h +++ b/include/quicly.h @@ -315,6 +315,10 @@ 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 + */ + uint16_t destroy_packet_ratio; /** * expand client hello so that it does not fit into one datagram */ diff --git a/lib/defaults.c b/lib/defaults.c index ac867fc9..48885d0b 100644 --- a/lib/defaults.c +++ b/lib/defaults.c @@ -45,6 +45,7 @@ const quicly_context_t quicly_spec_context = {NULL, QUICLY_PROTOCOL_VERSION_1, DEFAULT_PRE_VALIDATION_AMPLIFICATION_LIMIT, 0, /* ack_frequency */ + 0, /* destroy_packet_ratio */ 0, /* enlarge_client_hello */ 0, /* fragment_payload */ NULL, @@ -74,6 +75,7 @@ const quicly_context_t quicly_performant_context = {NULL, QUICLY_PROTOCOL_VERSION_1, DEFAULT_PRE_VALIDATION_AMPLIFICATION_LIMIT, 0, /* ack_frequency */ + 0, /* destroy_packet_ratio */ 0, /* enlarge_client_hello */ 0, /* fragment_payload */ NULL, diff --git a/lib/quicly.c b/lib/quicly.c index bad63399..15bbd81c 100644 --- a/lib/quicly.c +++ b/lib/quicly.c @@ -3111,6 +3111,15 @@ 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) { + uint16_t rand_ratio; + conn->super.ctx->tls->random_bytes(&rand_ratio, sizeof(rand_ratio)); + rand_ratio %= 1024; + if (rand_ratio < 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; diff --git a/src/cli.c b/src/cli.c index 9e4a8140..b7f10eb8 100644 --- a/src/cli.c +++ b/src/cli.c @@ -1052,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); @@ -1095,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:Ff: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)); @@ -1295,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) From ebf401e8beb4a3015ce7f925f429358ab283929b Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Sun, 5 Dec 2021 15:27:28 +0900 Subject: [PATCH 3/3] use xorshift64 for repeatability --- include/quicly.h | 7 +++++-- lib/defaults.c | 5 +++-- lib/quicly.c | 19 ++++++++++++++----- src/cli.c | 2 +- 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/include/quicly.h b/include/quicly.h index 9f5970f1..5d8d1362 100644 --- a/include/quicly.h +++ b/include/quicly.h @@ -316,9 +316,12 @@ struct st_quicly_context_t { */ uint16_t ack_frequency; /** - * destroy the packet being sent at given ratio + * destroy the packet being sent at given ratio (xorshift with given seed is used for each connection) */ - uint16_t destroy_packet_ratio; + struct { + uint16_t ratio; + uint64_t seed; + } destroy_packet; /** * expand client hello so that it does not fit into one datagram */ diff --git a/lib/defaults.c b/lib/defaults.c index 48885d0b..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,7 @@ const quicly_context_t quicly_spec_context = {NULL, QUICLY_PROTOCOL_VERSION_1, DEFAULT_PRE_VALIDATION_AMPLIFICATION_LIMIT, 0, /* ack_frequency */ - 0, /* destroy_packet_ratio */ + {0, DEFAULT_DESTROY_PACKET_SEED}, /* destroy_packet */ 0, /* enlarge_client_hello */ 0, /* fragment_payload */ NULL, @@ -75,7 +76,7 @@ const quicly_context_t quicly_performant_context = {NULL, QUICLY_PROTOCOL_VERSION_1, DEFAULT_PRE_VALIDATION_AMPLIFICATION_LIMIT, 0, /* ack_frequency */ - 0, /* destroy_packet_ratio */ + {0, DEFAULT_DESTROY_PACKET_SEED}, /* destroy_packet */ 0, /* enlarge_client_hello */ 0, /* fragment_payload */ NULL, diff --git a/lib/quicly.c b/lib/quicly.c index 15bbd81c..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); @@ -3112,11 +3123,9 @@ static int commit_send_packet(quicly_conn_t *conn, quicly_send_context_t *s, int coalesced); /* packet drill: intentionally destroy the packet at given ratio */ - if (conn->super.ctx->destroy_packet_ratio > 0) { - uint16_t rand_ratio; - conn->super.ctx->tls->random_bytes(&rand_ratio, sizeof(rand_ratio)); - rand_ratio %= 1024; - if (rand_ratio < conn->super.ctx->destroy_packet_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; } diff --git a/src/cli.c b/src/cli.c index b7f10eb8..fcd0556f 100644 --- a/src/cli.c +++ b/src/cli.c @@ -1303,7 +1303,7 @@ int main(int argc, char **argv) fprintf(stderr, "failed to parse packet destroy ratio (-Y): %s\n", optarg); exit(1); } - ctx.destroy_packet_ratio = (uint16_t)(ratio * 1024); + ctx.destroy_packet.ratio = (uint16_t)(ratio * 1024); } break; case 'y': { size_t i;