--- /dev/null
+#!/bin/bash
+set -e
+
+# Configuration
+LOG_PREFIX=/dev/shm/benchmark-log
+
+# Size in bytes
+SIZE=2e6
+
+# Rate of generated stream in bits/s
+STREAMRATE=10e6
+
+# Network parameters
+# Some realistic values:
+# - Gbit LAN connection: RATE=1gbit DELAY=0.4ms JITTER=0.04ms LOSS=0%
+# - Fast WAN connection: RATE=100mbit DELAY=50ms JITTER=3ms LOSS=0%
+# - 5GHz WiFi connection: RATE=90mbit DELAY=5ms JITTER=1ms LOSS=0%
+RATE=100mbit
+DELAY=10ms
+JITTER=1ms
+LOSS=0.1%
+
+# Maximum achievable bandwidth is limited to BUFSIZE / (2 * DELAY)
+# The Linux kernel has a default maximum send buffer of 4 MiB
+#export BUFSIZE=4194304
+
+# Require root permissions
+if [ "$USER" != "root" ]; then
+ exec sudo "$0" "$@"
+fi
+
+# Remove old log files
+rm -f $LOG_PREFIX-* 2>/dev/null
+
+# Clean up old namespaces
+ip link del utcp-left 2>/dev/null || true
+ip link del utcp-right 2>/dev/null || true
+ip netns delete utcp-left 2>/dev/null || true
+ip netns delete utcp-right 2>/dev/null || true
+
+# Set up the left namespace
+ip netns add utcp-left
+ip link add name utcp-left type veth peer name utcp-right
+ip link set utcp-left netns utcp-left
+
+ip netns exec utcp-left ethtool -K utcp-left tso off
+ip netns exec utcp-left ip link set dev lo up
+ip netns exec utcp-left ip addr add dev utcp-left 192.168.1.1/24
+ip netns exec utcp-left ip link set utcp-left up
+
+#ip netns exec utcp-left tc qdisc del dev utcp-left root
+ip netns exec utcp-left tc qdisc add dev utcp-left root netem rate $RATE delay $DELAY $JITTER loss random $LOSS
+
+# Set up the right namespace
+ip netns add utcp-right
+ip link set utcp-right netns utcp-right
+
+ip netns exec utcp-right ethtool -K utcp-right tso off
+ip netns exec utcp-right ip link set dev lo up
+ip netns exec utcp-right ip addr add dev utcp-right 192.168.1.2/24
+ip netns exec utcp-right ip link set utcp-right up
+
+#ip netns exec utcp-right tc qdisc del dev utcp-right root
+ip netns exec utcp-right tc qdisc add dev utcp-right root netem rate $RATE delay $DELAY $JITTER loss random $LOSS
+# Test using kernel TCP
+ip netns exec utcp-right tcpdump -i utcp-right -w $LOG_PREFIX-socat.pcap port 9999 2>/dev/null &
+ip netns exec utcp-left socat TCP4-LISTEN:9999 - </dev/null | ./stream -r $STREAMRATE -t $SIZE -v &
+sleep 0.1
+./stream -r $STREAMRATE -t $SIZE | ip netns exec utcp-right time socat - TCP4:192.168.1.1:9999 2>$LOG_PREFIX-socat-client.txt >/dev/null
+sleep 0.1
+kill $(jobs -p) 2>/dev/null
+
+# Test using UTCP
+ip netns exec utcp-right tcpdump -i utcp-right -w $LOG_PREFIX-utcp.pcap udp port 9999 2>/dev/null &
+ip netns exec utcp-left ./test 9999 2>$LOG_PREFIX-server.txt </dev/null | ./stream -r $STREAMRATE -t $SIZE -v &
+sleep 0.1
+./stream -r $STREAMRATE -t $SIZE | ip netns exec utcp-right time ./test 192.168.1.1 9999 2>$LOG_PREFIX-client.txt >/dev/null
+sleep 0.1
+kill $(jobs -p) 2>/dev/null
+
+# Print timing statistics
+echo "Regular TCP:"
+tail -2 $LOG_PREFIX-socat-client.txt
+
+echo
+echo "UTCP:"
+tail -3 $LOG_PREFIX-client.txt
+
+# If sudo was used, ensure the log files can be read by the user
+if [ -n "$SUDO_USER" ]; then
+ chown $SUDO_USER $LOG_PREFIX-*
+fi
--- /dev/null
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <getopt.h>
+#include <err.h>
+#include <time.h>
+#include <unistd.h>
+
+int main(int argc, char *argv[]) {
+ static const struct option longopts[] = {
+ {"verify", 0, NULL, 'v'},
+ {"rate", 1, NULL, 'r'},
+ {"fps", 1, NULL, 'f'},
+ {"total", 1, NULL, 't'},
+ {NULL, 0, NULL, 0},
+ };
+
+ int opt;
+ bool verify = false;
+ float rate = 1e6;
+ float fps = 60;
+ float total = 1.0 / 0.0;
+
+ while((opt = getopt_long(argc, argv, "vr:f:t:", longopts, &optind)) != -1) {
+ switch(opt) {
+ case 'v':
+ verify = true;
+ break;
+
+ case 'r':
+ rate = atof(optarg);
+ break;
+
+ case 'f':
+ fps = atof(optarg);
+ break;
+
+ case 't':
+ total = atof(optarg);
+ break;
+
+ default:
+ fprintf(stderr, "Usage: %s [-v] [-r bitrate] [-f frames_per_second]\n", argv[0]);
+ return 1;
+ }
+ }
+
+ size_t framesize = rate / fps / 8;
+ framesize &= ~0xf;
+ long interval = 1e9 / fps;
+
+ if(!framesize || interval <= 0) {
+ err(1, "invalid parameters");
+ }
+
+ char *buf = malloc(framesize + 16);
+
+ if(!buf) {
+ err(1, "malloc(%zu)", framesize);
+ }
+
+ uint64_t counter = 0;
+ struct timespec now, next = {0};
+ clock_gettime(CLOCK_REALTIME, &now);
+
+ while(total > 0) {
+ if(!verify) {
+ size_t tosend = framesize;
+ char *p = buf;
+
+ memcpy(buf, &now, sizeof(now));
+
+ for(uint64_t *q = (uint64_t *)(buf + sizeof(now)); (char *)q < buf + framesize; q++) {
+ *q = counter++;
+ }
+
+ while(tosend) {
+ ssize_t sent = write(1, p, tosend);
+
+ if(sent <= 0) {
+ err(1, "write(1, %p, %zu)", p, tosend);
+ }
+
+ tosend -= sent;
+ p += sent;
+ }
+
+ next = now;
+ next.tv_nsec += interval;
+
+ while(next.tv_nsec >= 1000000000) {
+ next.tv_nsec -= 1000000000;
+ next.tv_sec++;
+ }
+
+ clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &next, NULL);
+ now = next;
+ total -= framesize;
+ } else {
+ struct timespec *ts = (struct timespec *)buf;
+ size_t toread = sizeof(*ts);
+ char *p = buf;
+
+ while(toread) {
+ ssize_t result = read(0, p, toread);
+
+ if(result <= 0) {
+ err(1, "read(1, %p, %zu)", p, toread);
+ }
+
+ toread -= result;
+ p += result;
+ }
+
+ clock_gettime(CLOCK_REALTIME, &now);
+
+ toread = framesize - sizeof(now);
+
+ while(toread) {
+ ssize_t result = read(0, p, toread);
+
+ if(result <= 0) {
+ err(1, "read(1, %p, %zu)", p, toread);
+ }
+
+ toread -= result;
+ p += result;
+ }
+
+ clock_gettime(CLOCK_REALTIME, &next);
+
+ for(uint64_t *q = (uint64_t *)(buf + sizeof(now)); (char *)q < buf + framesize; q++) {
+ if(*q != counter++) {
+ uint64_t offset = (counter - 1) * 8;
+ offset += ((counter * 8) / (framesize - sizeof(now))) * sizeof(now);
+ err(1, "verification failed at offset %lu", offset);
+ }
+ }
+
+ float dt1 = now.tv_sec - ts->tv_sec + 1e-9 * (now.tv_nsec - ts->tv_nsec);
+ float dt2 = next.tv_sec - now.tv_sec + 1e-9 * (next.tv_nsec - now.tv_nsec);
+
+ fprintf(stderr, "\rDelay: %8.3f ms, burst bandwidth: %8.0f Mbit/s", dt1 * 1e3, (framesize - sizeof(now)) / dt2 * 8 / 1e6);
+
+ total -= framesize;
+ }
+ }
+
+ if(verify) {
+ fprintf(stderr, "\n");
+ }
+}