]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
DEV: udp: implement pseudo-random reordering/loss
authorWilly Tarreau <w@1wt.eu>
Thu, 3 Mar 2022 16:36:53 +0000 (17:36 +0100)
committerWilly Tarreau <w@1wt.eu>
Thu, 3 Mar 2022 16:54:04 +0000 (17:54 +0100)
By passing a 3rd argument it's now possible to set a randomness level
according to which received packets will be replaced by one of the 20
previous ones. This happens in both directions and the buffer is common
so that it's possible to receive responses on requests and conversely,
which adds to the perturbation. E.g:

    ./dev/udp/udp-perturb 127.0.0.4:9443 127.0.0.4:8443 10

This will add 10% randomness on forwarded packets between these two
ports.

dev/udp/udp-perturb.c

index 7a8b6f84bf70dff1e2719019e0e2ccd44e6cbe60..b6432575b821ce4fad2fc06dd04a2dfcd1640363 100644 (file)
@@ -66,8 +66,17 @@ struct sockaddr_storage frt_addr; // listen address
 struct sockaddr_storage srv_addr; // server address
 
 #define MAXPKTSIZE 16384
+#define MAXREORDER 20
 char trash[MAXPKTSIZE];
 
+/* history buffer, to resend random packets */
+struct {
+       char buf[MAXPKTSIZE];
+       size_t len;
+} history[MAXREORDER];
+int history_idx = 0;
+unsigned int rand_rate = 0;
+
 struct conn conns[MAXCONN];        // sole connection for now
 int fd_frt;
 
@@ -86,6 +95,19 @@ __attribute__((noreturn)) void die(int code, const char *format, ...)
        exit(code);
 }
 
+/* Xorshift RNG */
+unsigned int prng_state = ~0U/3; // half bits set, but any seed will fit
+static inline unsigned int prng(unsigned int range)
+{
+       unsigned int x = prng_state;
+
+       x ^= x << 13;
+       x ^= x >> 17;
+       x ^= x << 5;
+       prng_state = x;
+        return ((unsigned long long)x * (range - 1) + x) >> 32;
+}
+
 /* converts str in the form [<ipv4>|<ipv6>|<hostname>]:port to struct sockaddr_storage.
  * Returns < 0 with err set in case of error.
  */
@@ -259,12 +281,37 @@ int handle_frt(int fd, struct pollfd *pfd, struct conn *conns, int nbconn)
        struct sockaddr_storage addr;
        socklen_t addrlen;
        struct conn *conn;
+       char *pktbuf = trash;
        int ret;
        int i;
 
-       ret = recvfrom(fd, trash, sizeof(trash), MSG_DONTWAIT | MSG_NOSIGNAL,
+       if (rand_rate > 0) {
+               /* keep a copy of this packet */
+               history_idx++;
+               if (history_idx >= MAXREORDER)
+                       history_idx = 0;
+               pktbuf = history[history_idx].buf;
+       }
+
+       ret = recvfrom(fd, pktbuf, MAXPKTSIZE, MSG_DONTWAIT | MSG_NOSIGNAL,
                       (struct sockaddr *)&addr, &addrlen);
 
+       if (rand_rate > 0) {
+               history[history_idx].len = ret; // note: we may store -1/EAGAIN
+               if (prng(100) < rand_rate) {
+                       /* return a random buffer or nothing */
+                       int idx = prng(MAXREORDER + 1) - 1;
+                       if (idx < 0) {
+                               /* pretend we didn't receive anything */
+                               return 0;
+                       }
+                       pktbuf = history[idx].buf;
+                       ret    = history[idx].len;
+                       if (ret < 0)
+                               errno = EAGAIN;
+               }
+       }
+
        if (ret == 0)
                return 0;
 
@@ -305,7 +352,7 @@ int handle_frt(int fd, struct pollfd *pfd, struct conn *conns, int nbconn)
        if (conn->fd_bck < 0)
                return 0;
 
-       ret = send(conn->fd_bck, trash, ret, MSG_DONTWAIT | MSG_NOSIGNAL);
+       ret = send(conn->fd_bck, pktbuf, ret, MSG_DONTWAIT | MSG_NOSIGNAL);
        return ret;
 }
 
@@ -315,11 +362,36 @@ int handle_bck(int fd, struct pollfd *pfd, struct conn *conns, int nbconn)
        struct sockaddr_storage addr;
        socklen_t addrlen;
        struct conn *conn;
+       char *pktbuf = trash;
        int ret;
 
-       ret = recvfrom(fd, trash, sizeof(trash), MSG_DONTWAIT | MSG_NOSIGNAL,
+       if (rand_rate > 0) {
+               /* keep a copy of this packet */
+               history_idx++;
+               if (history_idx >= MAXREORDER)
+                       history_idx = 0;
+               pktbuf = history[history_idx].buf;
+       }
+
+       ret = recvfrom(fd, pktbuf, MAXPKTSIZE, MSG_DONTWAIT | MSG_NOSIGNAL,
                       (struct sockaddr *)&addr, &addrlen);
 
+       if (rand_rate > 0) {
+               history[history_idx].len = ret; // note: we may store -1/EAGAIN
+               if (prng(100) < rand_rate) {
+                       /* return a random buffer or nothing */
+                       int idx = prng(MAXREORDER + 1) - 1;
+                       if (idx < 0) {
+                               /* pretend we didn't receive anything */
+                               return 0;
+                       }
+                       pktbuf = history[idx].buf;
+                       ret    = history[idx].len;
+                       if (ret < 0)
+                               errno = EAGAIN;
+               }
+       }
+
        if (ret == 0)
                return 0;
 
@@ -330,7 +402,7 @@ int handle_bck(int fd, struct pollfd *pfd, struct conn *conns, int nbconn)
        if (!conn)
                return 0;
 
-       ret = sendto(fd_frt, trash, ret, MSG_DONTWAIT | MSG_NOSIGNAL,
+       ret = sendto(fd_frt, pktbuf, ret, MSG_DONTWAIT | MSG_NOSIGNAL,
                     (struct sockaddr *)&conn->cli_addr,
                     conn->cli_addr.ss_family == AF_INET6 ?
                     sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in));
@@ -348,7 +420,7 @@ int main(int argc, char **argv)
        err.msg = malloc(err.size);
 
        if (argc < 3)
-               die(1, "Usage: %s [<laddr>:]<lport> [<saddr>:]<sport>\n", argv[0]);
+               die(1, "Usage: %s [<laddr>:]<lport> [<saddr>:]<sport> [rand_rate%%]\n", argv[0]);
 
        if (addr_to_ss(argv[1], &frt_addr, &err) < 0)
                die(1, "parsing listen address: %s\n", err.msg);
@@ -356,6 +428,9 @@ int main(int argc, char **argv)
        if (addr_to_ss(argv[2], &srv_addr, &err) < 0)
                die(1, "parsing server address: %s\n", err.msg);
 
+       if (argc > 3)
+               rand_rate = atoi(argv[3]);
+
        pfd = calloc(sizeof(struct pollfd), MAXCONN + 1);
        if (!pfd)
                die(1, "out of memory\n");