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.
This commit is contained in:
Willy Tarreau 2022-03-03 17:36:53 +01:00
parent c927137785
commit e7a7fb4390

View 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");