DEV: udp: implement pseudo-random reordering/loss
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.
diff --git a/dev/udp/udp-perturb.c b/dev/udp/udp-perturb.c
index 7a8b6f8..b643257 100644
--- a/dev/udp/udp-perturb.c
+++ b/dev/udp/udp-perturb.c
@@ -66,8 +66,17 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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");