/* DF-2581 -- radix_node_head leak in ipfw3 table create/delete.
 * table_delete_dispatch (ip_fw3_table.c:115-127) rn_flush()es the node tree
 * but never rn_freehead()s table_ctx->{mask,node} (in-source XXX), and
 * table_create_dispatch overwrites the pointers on re-create without
 * freeing.  Each create->delete cycle leaks 2 radix_node_heads per CPU.
 * Also documents: table DELETE on a never-created table calls
 * rn_flush(NULL,...) -> deterministic NULL-deref panic (separate defect).
 *
 * usage: fw3_table_leak [cycles]
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

struct ipfw_x_header { uint16_t opcode; uint16_t _pad; };
#define IP_FW_X           49
#define IP_FW_TABLE_CREATE 73
#define IP_FW_TABLE_DELETE 74

struct tbl_msg { int id; int type; int count; char name[16]; };

static int
fw3_tbl(int opcode, int id, int type)
{
	int s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
	unsigned char buf[128];
	struct ipfw_x_header *x = (struct ipfw_x_header *)buf;
	struct tbl_msg *t = (struct tbl_msg *)(buf + 4);
	int rc;
	if (s < 0) { perror("socket"); exit(1); }
	x->opcode = opcode; x->_pad = 0;
	memset(t, 0, sizeof(*t));
	t->id = id; t->type = type;
	strcpy(t->name, "df2581");
	rc = setsockopt(s, IPPROTO_IP, IP_FW_X, buf, 4 + sizeof(*t));
	if (rc < 0)
		fprintf(stderr, "table op %d: %s\n", opcode, strerror(errno));
	close(s);
	return rc;
}

static long
freepages(void)
{
	int v;
	size_t vl = sizeof(v);
	if (sysctlbyname("vm.stats.vm.v_free_count", &v, &vl, NULL, 0) == 0)
		return v;
	return -1;
}

static long
zonepages(void)
{
	int v;
	size_t vl = sizeof(v);
	if (sysctlbyname("vm.zone_kmem_pages", &v, &vl, NULL, 0) == 0)
		return v;
	return -1;
}

int
main(int argc, char **argv)
{
	int cycles = (argc > 1) ? atoi(argv[1]) : 2000;
	int i;
	long f0, f1, z0, z1;

	setvbuf(stdout, NULL, _IONBF, 0);
	f0 = freepages();
	z0 = zonepages();
	printf("before: v_free_count=%ld zone_kmem_pages=%ld\n", f0, z0);

	for (i = 0; i < cycles; i++) {
		if (fw3_tbl(IP_FW_TABLE_CREATE, 1, 1) < 0) return 1;
		if (fw3_tbl(IP_FW_TABLE_DELETE, 1, 0) < 0) return 1;
		if ((i % 500) == 499) {
			f1 = freepages();
			printf("cycle %d: v_free_count=%ld (delta %+ld) zone_kmem_pages=%ld (+%ld)\n",
			    i + 1, f1, f1 - f0, zonepages(), zonepages() - z0);
		}
	}
	f1 = freepages();
	z1 = zonepages();
	printf("after %d cycles: v_free_count=%ld zone_kmem_pages %ld -> %ld (+%ld pages = %ld KB kernel heap)\n",
	    cycles, f1, z0, z1, z1 - z0, (z1 - z0) * 4);
	if (z1 - z0 > 100)
		printf("DF2581: LEAK CONFIRMED -- %ld zone pages (%ld KB kernel heap) "
		    "consumed by %d create/delete cycles\n", z1 - z0, (z1 - z0) * 4, cycles);
	else
		printf("DF2581: no significant free-page drop\n");
	return 0;
}
