]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
Add support for bandwidth limitation in noisydgram BIO filter
authorTomas Mraz <tomas@openssl.org>
Mon, 5 Feb 2024 15:03:15 +0000 (16:03 +0100)
committerTomas Mraz <tomas@openssl.org>
Fri, 15 Mar 2024 09:19:19 +0000 (10:19 +0100)
Reviewed-by: Hugo Landau <hlandau@openssl.org>
Reviewed-by: Neil Horman <nhorman@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/23588)

test/helpers/noisydgrambio.c
test/helpers/quictestlib.h

index 0dab50762ae97bc2bb80e40a4dc9d880fb3148a3..4660250e9c790719ef2e17a8de3c3a7f4bdb25e1 100644 (file)
 #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();
index 79de17d153255d76a3102facf59cae873a245822..228d396f41c25c73d9333e8d324d1e06d90b52b1 100644 (file)
@@ -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);