/*
 * DF-3330 harness -- hwmp_send_action() takes TWO references on
 * iv_bss for broadcast HWMP frames; only one is ever released by the
 * mbuf free path (ieee80211_free_node once in ieee80211_raw_output).
 *
 * VERBATIM ieee80211_hwmp.c:616-624:
 *	if (IEEE80211_IS_MULTICAST(da)) {
 *		ni = ieee80211_ref_node(vap->iv_bss);   (ref #1, line 619)
 *		...
 *		ieee80211_ref_node(ni);                 (ref #2, line 624)
 *	}
 *	else
 *		ni = ieee80211_mesh_find_txnode(vap, da);
 * (DragonFly-only divergence: upstream FreeBSD has a single ref.)
 *
 * Each broadcast PREQ/PERR/RANN therefore leaks exactly one iv_bss
 * node reference: an unauthenticated mesh peer flooding broadcast
 * PREQs pins the bss node (and blocks node-table teardown) forever.
 */
#include <stdio.h>
#include <string.h>

#define	IEEE80211_ADDR_LEN 6

static int bss_refcnt = 1;		/* creation reference */
static long refs_taken, refs_released;

static void *
ieee80211_ref_node(void *ni)
{
	refs_taken++;
	(*(int *)ni)++;
	return ni;
}

static void
ieee80211_free_node(void *ni)
{
	refs_released++;
	(*(int *)ni)--;
}

static int bss = 1;			/* emulates vap->iv_bss node (creation ref) */

/* verbatim control flow of the ni selection + the single release that
 * the tx path performs (ieee80211_raw_output -> ... -> free_node once) */
static int
hwmp_send_action(const unsigned char da[IEEE80211_ADDR_LEN])
{
	void *ni;

	if ((da[0] & 1)) {			/* IEEE80211_IS_MULTICAST */
		ni = ieee80211_ref_node(&bss);	/* line 619 */
		ieee80211_ref_node(ni);		/* line 624 */
	} else {
		ni = &bss;			/* find_txnode: takes its own ref */
		ieee80211_ref_node(ni);
	}
	/* ... mbuf built, ieee80211_raw_output(vap, ni, m, ...) ... */
	ieee80211_free_node(ni);		/* the ONE release */
	return 0;
}

int
main(void)
{
	const unsigned char bcast[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
	const unsigned char ucast[6] = { 0x02, 0, 0, 0, 0, 1 };
	int i;

	printf("DF-3330 (hwmp_send_action double ref, hwmp.c:616-624)\n");
	printf("bss refcnt at start: %d\n", bss);
	for (i = 0; i < 1000; i++)
		hwmp_send_action(bcast);
	printf("after 1000 broadcast HWMP frames: refcnt=%d (refs taken=%ld,"
	    " released=%ld)\n", bss, refs_taken, refs_released);
	for (i = 0; i < 1000; i++)
		hwmp_send_action(ucast);
	printf("after 1000 unicast HWMP frames:  refcnt=%d (cumulative"
	    " taken=%ld, released=%ld)\n", bss, refs_taken, refs_released);

	if (bss == 1 + 1000) {
		printf("RESULT: REPRODUCED -- one iv_bss reference leaked per"
		    " BROADCAST frame (unicast path is balanced: %d==%d after"
		    " both phases it should be 1001, is %d)\n",
		    1001, 1001, bss);
		return 0;
	}
	printf("RESULT: not reproduced (refcnt=%d)\n", bss);
	return 1;
}
