/*
 * DF-3296 fix validation — same setup as trigger_changeaddr but the pool
 * gets TWO addresses:
 *   - REMOVE nr=0 (not last)  -> must succeed (fix must not over-block)
 *   - REMOVE nr=0 (now last)  -> must fail EBUSY and must NOT panic
 * works on patched modules; on stock modules the first REMOVE of the LAST
 * address is what trigger_changeaddr exercises.
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <net/pf/pfvar.h>

static int dev;

static void
xioc(unsigned long cmd, void *arg, const char *what)
{
	if (ioctl(dev, cmd, arg) == -1) {
		fprintf(stderr, "%s: %s\n", what, strerror(errno));
		exit(2);
	}
}

static void
add_addr(u_int32_t ticket, const char *ip)
{
	struct pfioc_pooladdr pp;

	memset(&pp, 0, sizeof(pp));
	pp.ticket = ticket;
	pp.af = AF_INET;
	pp.addr.addr.type = PF_ADDR_ADDRMASK;
	pp.addr.addr.v.a.addr.v4.s_addr = inet_addr(ip);
	pp.addr.addr.v.a.mask.v4.s_addr = inet_addr("255.255.255.255");
	xioc(DIOCADDADDR, &pp, "DIOCADDADDR");
}

int
main(void)
{
	struct pfioc_pooladdr pp, pca;
	struct pfioc_trans trans;
	struct pfioc_trans_e ioe;
	struct pfioc_rule pr;
	u_int32_t pticket;

	dev = open("/dev/pf", O_RDWR);
	if (dev == -1) {
		perror("open /dev/pf");
		return (2);
	}
	memset(&pp, 0, sizeof(pp));
	xioc(DIOCBEGINADDRS, &pp, "DIOCBEGINADDRS");
	pticket = pp.ticket;
	add_addr(pticket, "10.9.9.9");
	add_addr(pticket, "10.9.9.10");

	memset(&ioe, 0, sizeof(ioe));
	ioe.rs_num = PF_RULESET_NAT;
	memset(&trans, 0, sizeof(trans));
	trans.size = 1;
	trans.esize = sizeof(ioe);
	trans.array = &ioe;
	xioc(DIOCXBEGIN, &trans, "DIOCXBEGIN");

	memset(&pr, 0, sizeof(pr));
	pr.rule.action = PF_NAT;
	pr.rule.af = AF_INET;
	pr.ticket = ioe.ticket;
	pr.pool_ticket = pticket;
	xioc(DIOCADDRULE, &pr, "DIOCADDRULE");
	xioc(DIOCXCOMMIT, &trans, "DIOCXCOMMIT");

	memset(&pr, 0, sizeof(pr));
	pr.rule.action = PF_NAT;
	xioc(DIOCGETRULES, &pr, "DIOCGETRULES");
	printf("setup: %u NAT rule(s), ticket %u, pool has 2 addrs\n",
	    pr.nr, pr.ticket);

	memset(&pca, 0, sizeof(pca));
	pca.action = PF_CHANGE_REMOVE;
	pca.ticket = pr.ticket;
	pca.nr = 0;
	pca.r_num = 0;
	pca.r_action = PF_NAT;
	pca.af = AF_INET;
	if (ioctl(dev, DIOCCHANGEADDR, &pca) == -1)
		printf("remove #1 (not last): FAILED %s (over-blocked?)\n",
		    strerror(errno));
	else
		printf("remove #1 (not last): OK, pool still has 1 addr\n");

	if (ioctl(dev, DIOCCHANGEADDR, &pca) == -1) {
		printf("remove #2 (last): %s (expected EBUSY, no panic)\n",
		    strerror(errno));
		return (errno == EBUSY ? 0 : 1);
	}
	printf("remove #2 (last): SUCCEEDED (bug present!)\n");
	return (1);
}
