/*
 * trigger.c -- DF-3307 reproducer (run as root: the hop-by-hop cmsg is
 * privilege-gated in ip6_setpktoption()).
 *
 * Sends one 5000-byte UDP6 datagram to ::1 with a 2048-byte
 * IPV6_HOPOPTS ancillary option (ip6h_len = 255 -> hbhlen 2048).
 * Total packet = 40 + 256 + 8 + 5000 = 5304 bytes > lo0 MTU (set to 1500)
 * -> ip6_output() fragments it, and its fragment path re-dereferences
 * the (freed by the pfil hook) hbh mbuf when rewriting the
 * next-header byte.
 *
 * Also binds a receiver on ::1:9999 first: on a fixed kernel the
 * fragments carry ip6f_nxt = IPPROTO_UDP(17), reassembly succeeds and
 * the datagram is delivered.  (lo0 MTU is set to 1500 first.)  On a vulnerable kernel the frag nxt is
 * read from freed memory (canary 0xAA) and the datagram never arrives.
 */
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

#define HBHLEN 256
#define PAYLEN 5000

static char cbuf[CMSG_SPACE(HBHLEN)];
static char payload[PAYLEN];

int
main(void)
{
	struct sockaddr_in6 to, bindaddr;
	struct msghdr msg;
	struct cmsghdr *cm;
	struct iovec iov;
	struct timeval tv;
	unsigned char *d;
	int tx, rx, n, i;

	memset(&to, 0, sizeof(to));
	to.sin6_family = AF_INET6;
	to.sin6_port = htons(9999);
	to.sin6_addr = in6addr_loopback;

	rx = socket(AF_INET6, SOCK_DGRAM, 0);
	if (rx < 0)
		err(1, "rx socket");
	bindaddr = to;
	if (bind(rx, (struct sockaddr *)&bindaddr, sizeof(bindaddr)) < 0)
		err(1, "bind");

	tx = socket(AF_INET6, SOCK_DGRAM, 0);
	if (tx < 0)
		err(1, "tx socket");

	memset(cbuf, 0, sizeof(cbuf));
	cm = (struct cmsghdr *)cbuf;
	cm->cmsg_level = IPPROTO_IPV6;
	cm->cmsg_type = IPV6_HOPOPTS;
	cm->cmsg_len = CMSG_LEN(HBHLEN);
	d = (unsigned char *)CMSG_DATA(cm);
	d[0] = 0;		/* nxt, filled by ip6_output */
	d[1] = 31;		/* ip6h_len: (31+1)*8 = 256 */
	d[2] = 1;		/* PadN */
	d[3] = HBHLEN - 4;	/* pad length: covers the rest */
	for (i = 4; i < HBHLEN; i++)
		d[i] = 0;

	memset(payload, 'A', sizeof(payload));

	memset(&msg, 0, sizeof(msg));
	iov.iov_base = payload;
	iov.iov_len = sizeof(payload);
	msg.msg_name = &to;
	msg.msg_namelen = sizeof(to);
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;
	msg.msg_control = cbuf;
	msg.msg_controllen = CMSG_LEN(HBHLEN);

	if (sendmsg(tx, &msg, 0) < 0)
		err(1, "sendmsg");
	printf("sendmsg: %d bytes payload + %d hbh ok\n", PAYLEN, HBHLEN);

	tv.tv_sec = 2;
	tv.tv_usec = 0;
	if (setsockopt(rx, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0)
		err(1, "SO_RCVTIMEO");
	n = recv(rx, payload, sizeof(payload), 0);
	if (n < 0)
		printf("receiver: no datagram (fragments malformed): %s\n",
		    strerror(errno));
	else
		printf("receiver: got %d bytes -- datagram delivered\n", n);
	return (0);
}
