git: c0b65123d09c - main - wtap: Implement IBSS mode on wtap(4)

From: Bjoern A. Zeeb <bz_at_FreeBSD.org>
Date: Mon, 15 Aug 2022 16:36:20 UTC
The branch main has been updated by bz:

URL: https://cgit.FreeBSD.org/src/commit/?id=c0b65123d09c735930c2915890de3b39055f4cc8

commit c0b65123d09c735930c2915890de3b39055f4cc8
Author:     En-Wei Wu <enweiwu@FreeBSD.org>
AuthorDate: 2022-08-15 16:21:15 +0000
Commit:     Bjoern A. Zeeb <bz@FreeBSD.org>
CommitDate: 2022-08-15 16:35:36 +0000

    wtap: Implement IBSS mode on wtap(4)
    
    For TSF and beacon generation, each STA has to start its local TSF timer
    and sends beacon frames when it reaches the state IEEE80211_S_RUN.
    The TSF timer will be used for IBSS merge and beacon frame transmission.
    
    The TSF timer is kept in the HAL of wtap(4). It is working by
    continuously updating its value on timer interrupt simulated
    by callout_reset().
    
    When receiving beacons, the STA will be merged into the IBSS if
    - the STA has the same SSID as the beacon sender
    - the STA's TSF timer is smaller than the beacon sender's TSF timer.
    
    After the merging process, the younger STA will be in the IBSS
    created by the older STA and the younger STA stops sending beacon
    frames.  So beacon frames will always be sent by the oldest STA
    in IBSS.
    
    Sponsored by:   Google, Inc. (GSoC 2022)
    Reviewed By:    lwhsu, adrian
    Differential Revision: https://reviews.freebsd.org/D35841
---
 sys/dev/wtap/if_wtap.c      | 78 ++++++++++++++++++++++++++++++++++++++++++---
 sys/dev/wtap/if_wtapvar.h   |  1 +
 sys/dev/wtap/wtap_hal/hal.c | 34 ++++++++++++++++++++
 sys/dev/wtap/wtap_hal/hal.h | 11 +++++++
 4 files changed, 120 insertions(+), 4 deletions(-)

diff --git a/sys/dev/wtap/if_wtap.c b/sys/dev/wtap/if_wtap.c
index 214f9f739407..4e05c70b53fa 100644
--- a/sys/dev/wtap/if_wtap.c
+++ b/sys/dev/wtap/if_wtap.c
@@ -41,6 +41,7 @@
 
 #include <net80211/ieee80211_ratectl.h>
 #include "if_medium.h"
+#include "wtap_hal/hal.h"
 
 /*
  * This _requires_ vimage to be useful.
@@ -149,10 +150,39 @@ wtap_recv_mgmt(struct ieee80211_node *ni, struct mbuf *m,
     int subtype, const struct ieee80211_rx_stats *stats, int rssi, int nf)
 {
 	struct ieee80211vap *vap = ni->ni_vap;
+	struct wtap_softc *sc = vap->iv_ic->ic_softc;
 #if 0
 	DWTAP_PRINTF("[%d] %s\n", myath_id(ni), __func__);
 #endif
+	/*
+	 * Call up first so subsequent work can use information
+	 * potentially stored in the node (e.g. for ibss merge).
+	 */
 	WTAP_VAP(vap)->av_recv_mgmt(ni, m, subtype, stats, rssi, nf);
+
+	switch (subtype) {
+	case IEEE80211_FC0_SUBTYPE_BEACON:
+	case IEEE80211_FC0_SUBTYPE_PROBE_RESP:
+		if (vap->iv_opmode == IEEE80211_M_IBSS &&
+		    vap->iv_state == IEEE80211_S_RUN &&
+		    ieee80211_ibss_merge_check(ni)) {
+			uint64_t tsf = wtap_hal_get_tsf(sc->hal);
+
+			/*
+			 * Handle ibss merge as needed; check the tsf on the
+			 * frame before attempting the merge.  The 802.11 spec
+			 * says the station should change it's bssid to match
+			 * the oldest station with the same ssid, where oldest
+			 * is determined by the tsf.  Note that hardware
+			 * reconfiguration happens through callback to
+			 * ath_newstate as the state machine will go from
+			 * RUN -> RUN when this happens.
+			 */
+			if (le64toh(ni->ni_tstamp.tsf) >= tsf)
+				(void) ieee80211_ibss_merge(ni);
+		}
+		break;
+	}
 }
 
 static int
@@ -193,7 +223,6 @@ wtap_beacon_alloc(struct wtap_softc *sc, struct ieee80211_node *ni)
 		printf("%s: cannot get mbuf\n", __func__);
 		return ENOMEM;
 	}
-	callout_init(&avp->av_swba, 0);
 	avp->bf_node = ieee80211_ref_node(ni);
 
 	return 0;
@@ -202,7 +231,6 @@ wtap_beacon_alloc(struct wtap_softc *sc, struct ieee80211_node *ni)
 static void
 wtap_beacon_config(struct wtap_softc *sc, struct ieee80211vap *vap)
 {
-
 	DWTAP_PRINTF("%s\n", __func__);
 }
 
@@ -211,7 +239,10 @@ wtap_beacon_intrp(void *arg)
 {
 	struct wtap_vap *avp = arg;
 	struct ieee80211vap *vap = arg;
+	struct wtap_softc *sc = vap->iv_ic->ic_softc;
+	struct ieee80211_frame *wh;
 	struct mbuf *m;
+	uint64_t tsf;
 
 	if (vap->iv_state < IEEE80211_S_RUN) {
 	    DWTAP_PRINTF("Skip beacon, not running, state %d", vap->iv_state);
@@ -230,6 +261,11 @@ wtap_beacon_intrp(void *arg)
 		    " changed size.\n",__func__);
 	}
 
+	/* Get TSF from HAL, and insert it into beacon frame */
+	tsf = wtap_hal_get_tsf(sc->hal);
+	wh = mtod(m, struct ieee80211_frame *);
+	memcpy(&wh[1], &tsf, sizeof(tsf));
+
 	if (ieee80211_radiotap_active_vap(vap))
 	    ieee80211_radiotap_tx(vap, m);
 
@@ -264,11 +300,37 @@ wtap_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg)
 		ieee80211_free_node(ni);
 		ni = ieee80211_ref_node(vap->iv_bss);
 		switch (vap->iv_opmode) {
+		case IEEE80211_M_IBSS:
 		case IEEE80211_M_MBSS:
+			/*
+			 * Stop any previous beacon callout. This may be
+			 * necessary, for example, when an ibss merge
+			 * causes reconfiguration; there will be a state
+			 * transition from RUN->RUN that means we may
+			 * be called with beacon transmission active.
+			 */
+			callout_stop(&avp->av_swba);
+
 			error = wtap_beacon_alloc(sc, ni);
 			if (error != 0)
 				goto bad;
+
+			/*
+			 * If joining an adhoc network defer beacon timer
+			 * configuration to the next beacon frame so we
+			 * have a current TSF to use.  Otherwise we're
+			 * starting an ibss/bss so there's no need to delay;
+			 * if this is the first vap moving to RUN state, then
+			 * beacon state needs to be [re]configured.
+			 */
+			if (vap->iv_opmode == IEEE80211_M_IBSS &&
+			    ni->ni_tstamp.tsf != 0)
+				break;
+
 			wtap_beacon_config(sc, vap);
+
+			/* Start TSF timer from now, and start s/w beacon alert */
+			wtap_hal_reset_tsf(sc->hal);
 			callout_reset(&avp->av_swba, avp->av_bcinterval,
 			    wtap_beacon_intrp, vap);
 			break;
@@ -314,7 +376,7 @@ wtap_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ],
 	avp->av_md = sc->sc_md;
 	avp->av_bcinterval = msecs_to_ticks(BEACON_INTRERVAL + 100*sc->id);
 	vap = (struct ieee80211vap *) avp;
-	error = ieee80211_vap_setup(ic, vap, name, unit, IEEE80211_M_MBSS,
+	error = ieee80211_vap_setup(ic, vap, name, unit, opmode,
 	    flags | IEEE80211_CLONE_NOBEACONS, bssid);
 	if (error) {
 		free(avp, M_80211_VAP);
@@ -337,6 +399,7 @@ wtap_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ],
 	avp->av_dev = make_dev(&wtap_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600,
 	    "%s", (const char *)vap->iv_ifp->if_xname);
 	avp->av_dev->si_drv1 = sc;
+	callout_init(&avp->av_swba, 0);
 
 	/* TODO this is a hack to force it to choose the rate we want */
 	ni = ieee80211_ref_node(vap->iv_bss);
@@ -460,6 +523,13 @@ wtap_rx_proc(void *arg, int npending)
 			free(bf, M_WTAP_RXBUF);
 			return;
 		}
+
+		/*
+		 * It's weird to do this, but sometimes wtap will
+		 * receive AMPDU packets (like ping(8)) even when
+		 * the ic does not supports 11n HT.
+		 */
+		m->m_flags &= ~M_AMPDU;
 #if 0
 		ieee80211_dump_pkt(ic, mtod(m, caddr_t), 0,0,0);
 #endif
@@ -585,7 +655,7 @@ wtap_attach(struct wtap_softc *sc, const uint8_t *macaddr)
 	ic->ic_name = sc->name;
 	ic->ic_phytype = IEEE80211_T_DS;
 	ic->ic_opmode = IEEE80211_M_MBSS;
-	ic->ic_caps = IEEE80211_C_MBSS;
+	ic->ic_caps = IEEE80211_C_MBSS | IEEE80211_C_IBSS;
 
 	ic->ic_max_keyix = 128; /* A value read from Atheros ATH_KEYMAX */
 
diff --git a/sys/dev/wtap/if_wtapvar.h b/sys/dev/wtap/if_wtapvar.h
index 966704cc481e..1a0983521f4e 100644
--- a/sys/dev/wtap/if_wtapvar.h
+++ b/sys/dev/wtap/if_wtapvar.h
@@ -134,6 +134,7 @@ struct wtap_softc {
 	int32_t			id;
 	int32_t			up;
 	struct wtap_medium	*sc_md;		/* interface medium */
+	struct wtap_hal		*hal;
 	struct ieee80211_node*	(* sc_node_alloc)
 	    (struct ieee80211vap *, const uint8_t [IEEE80211_ADDR_LEN]);
 	void (*sc_node_free)(struct ieee80211_node *);
diff --git a/sys/dev/wtap/wtap_hal/hal.c b/sys/dev/wtap/wtap_hal/hal.c
index 5bdcc40c9ccb..31f705d98e61 100644
--- a/sys/dev/wtap/wtap_hal/hal.c
+++ b/sys/dev/wtap/wtap_hal/hal.c
@@ -73,6 +73,9 @@ init_hal(struct wtap_hal *hal)
 	init_medium(hal->hal_md);
 	/* register event handler for packets */
 	TASK_INIT(&hal->hal_md->tx_handler->proc, 0, hal_tx_proc, hal);
+
+	callout_init_mtx(&hal->hw.timer_intr, &hal->hal_mtx, 0);
+	hal->hw.timer_intr_intval = msecs_to_ticks(HAL_TIMER_INTVAL);
 }
 
 void
@@ -182,6 +185,7 @@ new_wtap(struct wtap_hal *hal, int32_t id)
 	    sizeof(struct wtap_softc), M_WTAP, M_NOWAIT | M_ZERO);
 	hal->hal_devs[id]->sc_md = hal->hal_md;
 	hal->hal_devs[id]->id = id;
+	hal->hal_devs[id]->hal = hal;
 	snprintf(hal->hal_devs[id]->name, sizeof(hal->hal_devs[id]->name),
 	    "wtap%d", id);
 	mtx_init(&hal->hal_devs[id]->sc_mtx, "wtap_softc mtx", NULL,
@@ -212,3 +216,33 @@ free_wtap(struct wtap_hal *hal, int32_t id)
 	hal->hal_devs[id] = NULL;
 	return 0;
 }
+
+void
+wtap_hal_timer_intr(void *arg)
+{
+	struct wtap_hal *hal = arg;
+	uint32_t intval = hal->hw.timer_intr_intval;
+
+	hal->hw.tsf += ticks_to_msecs(intval);
+
+	callout_schedule(&hal->hw.timer_intr, intval);
+}
+
+void
+wtap_hal_reset_tsf(struct wtap_hal *hal)
+{
+	mtx_lock(&hal->hal_mtx);
+
+	callout_stop(&hal->hw.timer_intr);
+	hal->hw.tsf = 0;
+	callout_reset(&hal->hw.timer_intr, hal->hw.timer_intr_intval,
+	    wtap_hal_timer_intr, hal);
+
+	mtx_unlock(&hal->hal_mtx);
+}
+
+uint64_t
+wtap_hal_get_tsf(struct wtap_hal *hal)
+{
+	return (hal->hw.tsf);
+}
diff --git a/sys/dev/wtap/wtap_hal/hal.h b/sys/dev/wtap/wtap_hal/hal.h
index 8446bdbcdd1a..9dc05b566b79 100644
--- a/sys/dev/wtap/wtap_hal/hal.h
+++ b/sys/dev/wtap/wtap_hal/hal.h
@@ -38,11 +38,19 @@
 #include "../plugins/wtap_plugin.h"
 #include "handler.h"
 
+#define	HAL_TIMER_INTVAL	50 /* in msecs */
+
 struct wtap_hal {
 	struct wtap_medium	*hal_md;
 	struct mtx		hal_mtx;
 	struct wtap_plugin	*plugin;
 	struct wtap_softc 	*hal_devs[MAX_NBR_WTAP];
+	/* hardware information */
+	struct hw {
+		struct callout timer_intr;
+		uint32_t timer_intr_intval;
+		uint64_t tsf;
+	} hw;
 };
 
 void init_hal(struct wtap_hal *);
@@ -51,5 +59,8 @@ void register_plugin(struct wtap_hal *, struct wtap_plugin *);
 void deregister_plugin(struct wtap_hal *);
 int32_t new_wtap(struct wtap_hal *, int32_t id);
 int32_t free_wtap(struct wtap_hal *, int32_t id);
+void wtap_hal_timer_intr(void *);
+void wtap_hal_reset_tsf(struct wtap_hal *);
+uint64_t wtap_hal_get_tsf(struct wtap_hal *);
 
 #endif