From 37ffd4a1fa02b37919b5c7a8059ba57d3c49b1f0 Mon Sep 17 00:00:00 2001 From: Tomas Mraz Date: Mon, 5 Feb 2024 16:03:15 +0100 Subject: Add support for bandwidth limitation in noisydgram BIO filter Reviewed-by: Hugo Landau Reviewed-by: Neil Horman (Merged from https://github.com/openssl/openssl/pull/23588) --- test/helpers/noisydgrambio.c | 157 +++++++++++++++++++++++++++++++++++++++++-- test/helpers/quictestlib.h | 11 ++- 2 files changed, 162 insertions(+), 6 deletions(-) (limited to 'test') diff --git a/test/helpers/noisydgrambio.c b/test/helpers/noisydgrambio.c index 0dab50762a..4660250e9c 100644 --- a/test/helpers/noisydgrambio.c +++ b/test/helpers/noisydgrambio.c @@ -12,12 +12,30 @@ #include "../testutil.h" #define MSG_DATA_LEN_MAX 1472 +#define SAMPLING_WINDOW_PERIOD 10 /* in milliseconds */ +#define MAX_PKTS_PER_WINDOW 1024 + +struct pkt_info_st { + size_t size; + OSSL_TIME timestamp; +}; + +struct bw_limiter_st { + struct pkt_info_st pinfos[MAX_PKTS_PER_WINDOW]; /* ring buffer */ + size_t start, num; /* ring buffer start and number of items */ + size_t size_sum; /* sum of packet sizes in window */ + size_t bw; /* bandwidth in bytes/ms */ +}; struct noisy_dgram_st { uint64_t this_dgram; BIO_MSG msg; uint64_t reinject_dgram; int backoff; + int noise_rate; /* 1 in noise_rate packets will get noise */ + struct bw_limiter_st recv_limit, send_limit; + OSSL_TIME (*now_cb)(void *arg); + void *now_cb_arg; }; static long noisy_dgram_ctrl(BIO *bio, int cmd, long num, void *ptr) @@ -42,6 +60,48 @@ static long noisy_dgram_ctrl(BIO *bio, int cmd, long num, void *ptr) ret = 1; break; } + case BIO_CTRL_NOISE_RATE: { + struct noisy_dgram_st *data; + + data = BIO_get_data(bio); + if (!TEST_ptr(data)) + return 0; + data->noise_rate = (int)num; + ret = 1; + break; + } + case BIO_CTRL_NOISE_RECV_BANDWIDTH: { + struct noisy_dgram_st *data; + + data = BIO_get_data(bio); + if (!TEST_ptr(data)) + return 0; + data->recv_limit.bw = (size_t)num; + ret = 1; + break; + } + case BIO_CTRL_NOISE_SEND_BANDWIDTH: { + struct noisy_dgram_st *data; + + data = BIO_get_data(bio); + if (!TEST_ptr(data)) + return 0; + data->send_limit.bw = (size_t)num; + ret = 1; + break; + } + case BIO_CTRL_NOISE_SET_NOW_CB: { + struct noisy_dgram_st *data; + struct bio_noise_now_cb_st *now_cb = ptr; + + data = BIO_get_data(bio); + if (!TEST_ptr(data)) + return 0; + data->now_cb = now_cb->now_cb; + data->now_cb_arg = now_cb->now_cb_arg; + ret = 1; + break; + } default: ret = BIO_ctrl(next, cmd, num, ptr); break; @@ -49,15 +109,89 @@ static long noisy_dgram_ctrl(BIO *bio, int cmd, long num, void *ptr) return ret; } +static size_t bandwidth_limit(struct bw_limiter_st *limit, OSSL_TIME now, + BIO_MSG *msg, size_t num_msg) +{ + size_t i; + OSSL_TIME sampling_start + = ossl_time_subtract(now, ossl_ms2time(SAMPLING_WINDOW_PERIOD)); + + if (limit->bw == 0) /* 0 -> no limit */ + return num_msg; + + if (num_msg > MAX_PKTS_PER_WINDOW) + num_msg = MAX_PKTS_PER_WINDOW; + + /* trim the start of the ring buffer */ + for (i = 0; i < limit->num; i++) { + size_t idx = (limit->start + i) % MAX_PKTS_PER_WINDOW; + + if (ossl_time_compare(limit->pinfos[idx].timestamp, sampling_start) >= 0) + break; + limit->size_sum -= limit->pinfos[idx].size; + } + limit->start = (limit->start + i) % MAX_PKTS_PER_WINDOW; + limit->num -= i; + + for (i = 0; i < num_msg; ++i) { + size_t end; + size_t pktsize = msg[i].data_len; + + if ((limit->size_sum + pktsize) / SAMPLING_WINDOW_PERIOD > limit->bw) { + /* + * Throw out all the packets once reaching the limit, + * although some following packets could still fit. + * This is accurate enough. + */ +#ifdef OSSL_NOISY_DGRAM_DEBUG + printf("**BW limit applied: now: %llu orig packets %u new packets %u\n", + (unsigned long long)ossl_time2ms(now), + (unsigned int)num_msg, (unsigned int) i); +#endif + num_msg = i; + break; + } + + if (limit->num >= MAX_PKTS_PER_WINDOW) { + limit->size_sum -= limit->pinfos[limit->start].size; + limit->start = (limit->start + 1) % MAX_PKTS_PER_WINDOW; + } else { + ++limit->num; + } + end = (limit->start + limit->num) % MAX_PKTS_PER_WINDOW; + limit->pinfos[end].size = pktsize; + limit->pinfos[end].timestamp = now; + limit->size_sum += pktsize; + } + return num_msg; +} + static int noisy_dgram_sendmmsg(BIO *bio, BIO_MSG *msg, size_t stride, size_t num_msg, uint64_t flags, size_t *msgs_processed) { BIO *next = BIO_next(bio); + struct noisy_dgram_st *data; + OSSL_TIME now; if (next == NULL) return 0; + data = BIO_get_data(bio); + if (!TEST_ptr(data)) + return 0; + + now = data->now_cb != NULL ? data->now_cb(data->now_cb_arg) + : ossl_time_now(); + + /* bandwidth limit can be applied on both sides */ + num_msg = bandwidth_limit(&data->send_limit, now, msg, num_msg); + if (num_msg == 0) { + *msgs_processed = 0; + ERR_raise(ERR_LIB_BIO, BIO_R_NON_FATAL); + return 0; + } + /* * We only introduce noise when receiving messages. We just pass this on * to the underlying BIO. @@ -65,7 +199,7 @@ static int noisy_dgram_sendmmsg(BIO *bio, BIO_MSG *msg, size_t stride, return BIO_sendmmsg(next, msg, stride, num_msg, flags, msgs_processed); } -/* 1 in NOISE_RATE datagrams will be noisy. With a value of 5 that is 20% */ +/* Default noise_rate value. With a value of 5 that is 20% packets. */ #define NOISE_RATE 5 /* @@ -90,14 +224,14 @@ static int noisy_dgram_sendmmsg(BIO *bio, BIO_MSG *msg, size_t stride, */ #define MAX_DGRAM_REINJECT 4 -static void get_noise(int long_header, uint64_t *reinject, int *should_drop, - uint16_t *flip, size_t *flip_offset) +static void get_noise(int noise_rate, int long_header, uint64_t *reinject, + int *should_drop, uint16_t *flip, size_t *flip_offset) { uint32_t type; *flip = 0; - if (test_random() % NOISE_RATE != 0) { + if (test_random() % noise_rate != 0) { *reinject = 0; *should_drop = 0; return; @@ -173,6 +307,7 @@ static int noisy_dgram_recvmmsg(BIO *bio, BIO_MSG *msg, size_t stride, size_t i, j, data_len = 0, msg_cnt = 0; BIO_MSG *thismsg; struct noisy_dgram_st *data; + OSSL_TIME now; if (!TEST_ptr(next)) return 0; @@ -211,7 +346,16 @@ static int noisy_dgram_recvmmsg(BIO *bio, BIO_MSG *msg, size_t stride, printf("End of pre-filter datagram list\nApplying noise filters:\n"); #endif + now = data->now_cb != NULL ? data->now_cb(data->now_cb_arg) + : ossl_time_now(); + msg_cnt = *msgs_processed; + msg_cnt = bandwidth_limit(&data->recv_limit, now, msg, msg_cnt); + if (msg_cnt == 0) + goto end; + + if (data->noise_rate == 0) + goto end; /* Introduce noise */ for (i = 0, thismsg = msg; @@ -245,7 +389,8 @@ static int noisy_dgram_recvmmsg(BIO *bio, BIO_MSG *msg, size_t stride, data->reinject_dgram = 0; } - get_noise(/* long header */ (((uint8_t *)thismsg->data)[0] & 0x80) != 0, + get_noise(data->noise_rate, + /* long header */ (((uint8_t *)thismsg->data)[0] & 0x80) != 0, &reinject, &should_drop, &flip, &flip_offset); if (data->backoff) { /* @@ -313,6 +458,7 @@ static int noisy_dgram_recvmmsg(BIO *bio, BIO_MSG *msg, size_t stride, printf("End of post-filter datagram list\n"); #endif + end: *msgs_processed = msg_cnt; if (msg_cnt == 0) { @@ -341,6 +487,7 @@ static int noisy_dgram_new(BIO *bio) if (!TEST_ptr(data)) return 0; + data->noise_rate = NOISE_RATE; data->msg.data = OPENSSL_malloc(MSG_DATA_LEN_MAX); data->msg.peer = BIO_ADDR_new(); data->msg.local = BIO_ADDR_new(); diff --git a/test/helpers/quictestlib.h b/test/helpers/quictestlib.h index 79de17d153..228d396f41 100644 --- a/test/helpers/quictestlib.h +++ b/test/helpers/quictestlib.h @@ -247,7 +247,16 @@ int qtest_fault_resize_datagram(QTEST_FAULT *fault, size_t newlen); /* Copy a BIO_MSG */ int bio_msg_copy(BIO_MSG *dst, BIO_MSG *src); -#define BIO_CTRL_NOISE_BACK_OFF 1001 +#define BIO_CTRL_NOISE_BACK_OFF 1001 +#define BIO_CTRL_NOISE_RATE 1002 +#define BIO_CTRL_NOISE_RECV_BANDWIDTH 1003 +#define BIO_CTRL_NOISE_SEND_BANDWIDTH 1004 +#define BIO_CTRL_NOISE_SET_NOW_CB 1005 + +struct bio_noise_now_cb_st { + OSSL_TIME (*now_cb)(void *); + void *now_cb_arg; +}; /* BIO filter for simulating a noisy UDP socket */ const BIO_METHOD *bio_f_noisy_dgram_filter(void); -- cgit v1.2.3