1178354Ssam/*- 2178354Ssam * Copyright (c) 2007-2008 Sam Leffler, Errno Consulting 3178354Ssam * All rights reserved. 4178354Ssam * 5178354Ssam * Redistribution and use in source and binary forms, with or without 6178354Ssam * modification, are permitted provided that the following conditions 7178354Ssam * are met: 8178354Ssam * 1. Redistributions of source code must retain the above copyright 9178354Ssam * notice, this list of conditions and the following disclaimer. 10178354Ssam * 2. Redistributions in binary form must reproduce the above copyright 11178354Ssam * notice, this list of conditions and the following disclaimer in the 12178354Ssam * documentation and/or other materials provided with the distribution. 13178354Ssam * 14178354Ssam * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15178354Ssam * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16178354Ssam * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17178354Ssam * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 18178354Ssam * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 19178354Ssam * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20178354Ssam * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21178354Ssam * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22178354Ssam * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23178354Ssam * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24178354Ssam */ 25178354Ssam 26178354Ssam#include <sys/cdefs.h> 27178354Ssam#ifdef __FreeBSD__ 28178354Ssam__FBSDID("$FreeBSD: stable/11/sys/net80211/ieee80211_dfs.c 343894 2019-02-08 04:48:12Z avos $"); 29178354Ssam#endif 30178354Ssam 31178354Ssam/* 32178354Ssam * IEEE 802.11 DFS/Radar support. 33178354Ssam */ 34178354Ssam#include "opt_inet.h" 35178354Ssam#include "opt_wlan.h" 36178354Ssam 37178354Ssam#include <sys/param.h> 38178354Ssam#include <sys/systm.h> 39178354Ssam#include <sys/mbuf.h> 40178354Ssam#include <sys/malloc.h> 41178354Ssam#include <sys/kernel.h> 42178354Ssam 43178354Ssam#include <sys/socket.h> 44178354Ssam#include <sys/sockio.h> 45178354Ssam#include <sys/endian.h> 46178354Ssam#include <sys/errno.h> 47178354Ssam#include <sys/proc.h> 48178354Ssam#include <sys/sysctl.h> 49178354Ssam 50178354Ssam#include <net/if.h> 51257176Sglebius#include <net/if_var.h> 52178354Ssam#include <net/if_media.h> 53257241Sglebius#include <net/ethernet.h> 54178354Ssam 55178354Ssam#include <net80211/ieee80211_var.h> 56178354Ssam 57227293Sedstatic MALLOC_DEFINE(M_80211_DFS, "80211dfs", "802.11 DFS state"); 58178354Ssam 59193115Ssamstatic int ieee80211_nol_timeout = 30*60; /* 30 minutes */ 60193115SsamSYSCTL_INT(_net_wlan, OID_AUTO, nol_timeout, CTLFLAG_RW, 61193115Ssam &ieee80211_nol_timeout, 0, "NOL timeout (secs)"); 62178354Ssam#define NOL_TIMEOUT msecs_to_ticks(ieee80211_nol_timeout*1000) 63193115Ssam 64193115Ssamstatic int ieee80211_cac_timeout = 60; /* 60 seconds */ 65193115SsamSYSCTL_INT(_net_wlan, OID_AUTO, cac_timeout, CTLFLAG_RW, 66193115Ssam &ieee80211_cac_timeout, 0, "CAC timeout (secs)"); 67178354Ssam#define CAC_TIMEOUT msecs_to_ticks(ieee80211_cac_timeout*1000) 68178354Ssam 69230793Sadrian/* 70230793Sadrian DFS* In order to facilitate debugging, a couple of operating 71230793Sadrian * modes aside from the default are needed. 72230793Sadrian * 73230793Sadrian * 0 - default CAC/NOL behaviour - ie, start CAC, place 74230793Sadrian * channel on NOL list. 75230793Sadrian * 1 - send CAC, but don't change channel or add the channel 76230793Sadrian * to the NOL list. 77230793Sadrian * 2 - just match on radar, don't send CAC or place channel in 78230793Sadrian * the NOL list. 79230793Sadrian */ 80230793Sadrianstatic int ieee80211_dfs_debug = DFS_DBG_NONE; 81230793Sadrian 82230793Sadrian/* 83230793Sadrian * This option must not be included in the default kernel 84230793Sadrian * as it allows users to plainly disable CAC/NOL handling. 85230793Sadrian */ 86230793Sadrian#ifdef IEEE80211_DFS_DEBUG 87230793SadrianSYSCTL_INT(_net_wlan, OID_AUTO, dfs_debug, CTLFLAG_RW, 88230793Sadrian &ieee80211_dfs_debug, 0, "DFS debug behaviour"); 89230793Sadrian#endif 90230793Sadrian 91227331Sadrianstatic int 92227331Sadriannull_set_quiet(struct ieee80211_node *ni, u_int8_t *quiet_elm) 93227331Sadrian{ 94227331Sadrian return ENOSYS; 95227331Sadrian} 96227331Sadrian 97178354Ssamvoid 98178354Ssamieee80211_dfs_attach(struct ieee80211com *ic) 99178354Ssam{ 100178354Ssam struct ieee80211_dfs_state *dfs = &ic->ic_dfs; 101178354Ssam 102193844Ssam callout_init_mtx(&dfs->nol_timer, IEEE80211_LOCK_OBJ(ic), 0); 103193844Ssam callout_init_mtx(&dfs->cac_timer, IEEE80211_LOCK_OBJ(ic), 0); 104227331Sadrian 105227331Sadrian ic->ic_set_quiet = null_set_quiet; 106178354Ssam} 107178354Ssam 108178354Ssamvoid 109178354Ssamieee80211_dfs_detach(struct ieee80211com *ic) 110178354Ssam{ 111178354Ssam /* NB: we assume no locking is needed */ 112178354Ssam ieee80211_dfs_reset(ic); 113178354Ssam} 114178354Ssam 115178354Ssamvoid 116178354Ssamieee80211_dfs_reset(struct ieee80211com *ic) 117178354Ssam{ 118178354Ssam struct ieee80211_dfs_state *dfs = &ic->ic_dfs; 119178354Ssam int i; 120178354Ssam 121178354Ssam /* NB: we assume no locking is needed */ 122178354Ssam /* NB: cac_timer should be cleared by the state machine */ 123178354Ssam callout_drain(&dfs->nol_timer); 124178354Ssam for (i = 0; i < ic->ic_nchans; i++) 125178354Ssam ic->ic_channels[i].ic_state = 0; 126178354Ssam dfs->lastchan = NULL; 127178354Ssam} 128178354Ssam 129178354Ssamstatic void 130178354Ssamcac_timeout(void *arg) 131178354Ssam{ 132178354Ssam struct ieee80211vap *vap = arg; 133178354Ssam struct ieee80211com *ic = vap->iv_ic; 134178354Ssam struct ieee80211_dfs_state *dfs = &ic->ic_dfs; 135178354Ssam int i; 136178354Ssam 137193844Ssam IEEE80211_LOCK_ASSERT(ic); 138193844Ssam 139178354Ssam if (vap->iv_state != IEEE80211_S_CAC) /* NB: just in case */ 140178354Ssam return; 141178354Ssam /* 142178354Ssam * When radar is detected during a CAC we are woken 143178354Ssam * up prematurely to switch to a new channel. 144178354Ssam * Check the channel to decide how to act. 145178354Ssam */ 146178354Ssam if (IEEE80211_IS_CHAN_RADAR(ic->ic_curchan)) { 147178354Ssam ieee80211_notify_cac(ic, ic->ic_curchan, 148178354Ssam IEEE80211_NOTIFY_CAC_RADAR); 149178354Ssam 150178354Ssam if_printf(vap->iv_ifp, 151178354Ssam "CAC timer on channel %u (%u MHz) stopped due to radar\n", 152178354Ssam ic->ic_curchan->ic_ieee, ic->ic_curchan->ic_freq); 153178354Ssam 154178354Ssam /* XXX clobbers any existing desired channel */ 155178354Ssam /* NB: dfs->newchan may be NULL, that's ok */ 156178354Ssam vap->iv_des_chan = dfs->newchan; 157343894Savos ieee80211_new_state_locked(vap, IEEE80211_S_SCAN, 0); 158178354Ssam } else { 159178354Ssam if_printf(vap->iv_ifp, 160178354Ssam "CAC timer on channel %u (%u MHz) expired; " 161178354Ssam "no radar detected\n", 162178354Ssam ic->ic_curchan->ic_ieee, ic->ic_curchan->ic_freq); 163178354Ssam /* 164178354Ssam * Mark all channels with the current frequency 165178354Ssam * as having completed CAC; this keeps us from 166178354Ssam * doing it again until we change channels. 167178354Ssam */ 168178354Ssam for (i = 0; i < ic->ic_nchans; i++) { 169178354Ssam struct ieee80211_channel *c = &ic->ic_channels[i]; 170178354Ssam if (c->ic_freq == ic->ic_curchan->ic_freq) 171178354Ssam c->ic_state |= IEEE80211_CHANSTATE_CACDONE; 172178354Ssam } 173178354Ssam ieee80211_notify_cac(ic, ic->ic_curchan, 174178354Ssam IEEE80211_NOTIFY_CAC_EXPIRE); 175178354Ssam ieee80211_cac_completeswitch(vap); 176178354Ssam } 177178354Ssam} 178178354Ssam 179178354Ssam/* 180178354Ssam * Initiate the CAC timer. The driver is responsible 181178354Ssam * for setting up the hardware to scan for radar on the 182178354Ssam * channnel, we just handle timing things out. 183178354Ssam */ 184178354Ssamvoid 185178354Ssamieee80211_dfs_cac_start(struct ieee80211vap *vap) 186178354Ssam{ 187178354Ssam struct ieee80211com *ic = vap->iv_ic; 188178354Ssam struct ieee80211_dfs_state *dfs = &ic->ic_dfs; 189178354Ssam 190178354Ssam IEEE80211_LOCK_ASSERT(ic); 191178354Ssam 192178354Ssam callout_reset(&dfs->cac_timer, CAC_TIMEOUT, cac_timeout, vap); 193178354Ssam if_printf(vap->iv_ifp, "start %d second CAC timer on channel %u (%u MHz)\n", 194178354Ssam ticks_to_secs(CAC_TIMEOUT), 195178354Ssam ic->ic_curchan->ic_ieee, ic->ic_curchan->ic_freq); 196178354Ssam ieee80211_notify_cac(ic, ic->ic_curchan, IEEE80211_NOTIFY_CAC_START); 197178354Ssam} 198178354Ssam 199178354Ssam/* 200178354Ssam * Clear the CAC timer. 201178354Ssam */ 202178354Ssamvoid 203178354Ssamieee80211_dfs_cac_stop(struct ieee80211vap *vap) 204178354Ssam{ 205178354Ssam struct ieee80211com *ic = vap->iv_ic; 206178354Ssam struct ieee80211_dfs_state *dfs = &ic->ic_dfs; 207178354Ssam 208178354Ssam IEEE80211_LOCK_ASSERT(ic); 209178354Ssam 210178354Ssam /* NB: racey but not important */ 211178354Ssam if (callout_pending(&dfs->cac_timer)) { 212178354Ssam if_printf(vap->iv_ifp, "stop CAC timer on channel %u (%u MHz)\n", 213178354Ssam ic->ic_curchan->ic_ieee, ic->ic_curchan->ic_freq); 214178354Ssam ieee80211_notify_cac(ic, ic->ic_curchan, 215178354Ssam IEEE80211_NOTIFY_CAC_STOP); 216178354Ssam } 217178354Ssam callout_stop(&dfs->cac_timer); 218178354Ssam} 219178354Ssam 220178354Ssamvoid 221178354Ssamieee80211_dfs_cac_clear(struct ieee80211com *ic, 222178354Ssam const struct ieee80211_channel *chan) 223178354Ssam{ 224178354Ssam int i; 225178354Ssam 226178354Ssam for (i = 0; i < ic->ic_nchans; i++) { 227178354Ssam struct ieee80211_channel *c = &ic->ic_channels[i]; 228178354Ssam if (c->ic_freq == chan->ic_freq) 229178354Ssam c->ic_state &= ~IEEE80211_CHANSTATE_CACDONE; 230178354Ssam } 231178354Ssam} 232178354Ssam 233178354Ssamstatic void 234178354Ssamdfs_timeout(void *arg) 235178354Ssam{ 236178354Ssam struct ieee80211com *ic = arg; 237178354Ssam struct ieee80211_dfs_state *dfs = &ic->ic_dfs; 238178354Ssam struct ieee80211_channel *c; 239178354Ssam int i, oldest, now; 240178354Ssam 241193844Ssam IEEE80211_LOCK_ASSERT(ic); 242193844Ssam 243178354Ssam now = oldest = ticks; 244178354Ssam for (i = 0; i < ic->ic_nchans; i++) { 245178354Ssam c = &ic->ic_channels[i]; 246178354Ssam if (IEEE80211_IS_CHAN_RADAR(c)) { 247297405Sadrian if (ieee80211_time_after_eq(now, dfs->nol_event[i]+NOL_TIMEOUT)) { 248178354Ssam c->ic_state &= ~IEEE80211_CHANSTATE_RADAR; 249178354Ssam if (c->ic_state & IEEE80211_CHANSTATE_NORADAR) { 250178354Ssam /* 251178354Ssam * NB: do this here so we get only one 252178354Ssam * msg instead of one for every channel 253178354Ssam * table entry. 254178354Ssam */ 255283529Sglebius ic_printf(ic, "radar on channel %u " 256283529Sglebius "(%u MHz) cleared after timeout\n", 257178354Ssam c->ic_ieee, c->ic_freq); 258178354Ssam /* notify user space */ 259178354Ssam c->ic_state &= 260178354Ssam ~IEEE80211_CHANSTATE_NORADAR; 261178354Ssam ieee80211_notify_radar(ic, c); 262178354Ssam } 263178354Ssam } else if (dfs->nol_event[i] < oldest) 264178354Ssam oldest = dfs->nol_event[i]; 265178354Ssam } 266178354Ssam } 267178354Ssam if (oldest != now) { 268178354Ssam /* arrange to process next channel up for a status change */ 269196785Ssam callout_schedule(&dfs->nol_timer, oldest + NOL_TIMEOUT - now); 270178354Ssam } 271178354Ssam} 272178354Ssam 273178354Ssamstatic void 274283529Sglebiusannounce_radar(struct ieee80211com *ic, const struct ieee80211_channel *curchan, 275178354Ssam const struct ieee80211_channel *newchan) 276178354Ssam{ 277178354Ssam if (newchan == NULL) 278283529Sglebius ic_printf(ic, "radar detected on channel %u (%u MHz)\n", 279178354Ssam curchan->ic_ieee, curchan->ic_freq); 280178354Ssam else 281283529Sglebius ic_printf(ic, "radar detected on channel %u (%u MHz), " 282178354Ssam "moving to channel %u (%u MHz)\n", 283178354Ssam curchan->ic_ieee, curchan->ic_freq, 284178354Ssam newchan->ic_ieee, newchan->ic_freq); 285178354Ssam} 286178354Ssam 287178354Ssam/* 288178354Ssam * Handle a radar detection event on a channel. The channel is 289178354Ssam * added to the NOL list and we record the time of the event. 290178354Ssam * Entries are aged out after NOL_TIMEOUT. If radar was 291178354Ssam * detected while doing CAC we force a state/channel change. 292178354Ssam * Otherwise radar triggers a channel switch using the CSA 293178354Ssam * mechanism (when the channel is the bss channel). 294178354Ssam */ 295178354Ssamvoid 296178354Ssamieee80211_dfs_notify_radar(struct ieee80211com *ic, struct ieee80211_channel *chan) 297178354Ssam{ 298178354Ssam struct ieee80211_dfs_state *dfs = &ic->ic_dfs; 299178354Ssam int i, now; 300178354Ssam 301178354Ssam IEEE80211_LOCK_ASSERT(ic); 302178354Ssam 303178354Ssam /* 304230793Sadrian * If doing DFS debugging (mode 2), don't bother 305230793Sadrian * running the rest of this function. 306230793Sadrian * 307230793Sadrian * Simply announce the presence of the radar and continue 308230793Sadrian * along merrily. 309178354Ssam */ 310230793Sadrian if (ieee80211_dfs_debug == DFS_DBG_NOCSANOL) { 311283529Sglebius announce_radar(ic, chan, chan); 312230793Sadrian ieee80211_notify_radar(ic, chan); 313230793Sadrian return; 314230793Sadrian } 315230793Sadrian 316230793Sadrian /* 317230793Sadrian * Don't mark the channel and don't put it into NOL 318230793Sadrian * if we're doing DFS debugging. 319230793Sadrian */ 320230793Sadrian if (ieee80211_dfs_debug == DFS_DBG_NONE) { 321230793Sadrian /* 322230793Sadrian * Mark all entries with this frequency. Notify user 323230793Sadrian * space and arrange for notification when the radar 324230793Sadrian * indication is cleared. Then kick the NOL processing 325230793Sadrian * thread if not already running. 326230793Sadrian */ 327230793Sadrian now = ticks; 328230793Sadrian for (i = 0; i < ic->ic_nchans; i++) { 329230793Sadrian struct ieee80211_channel *c = &ic->ic_channels[i]; 330230793Sadrian if (c->ic_freq == chan->ic_freq) { 331230793Sadrian c->ic_state &= ~IEEE80211_CHANSTATE_CACDONE; 332230793Sadrian c->ic_state |= IEEE80211_CHANSTATE_RADAR; 333230793Sadrian dfs->nol_event[i] = now; 334230793Sadrian } 335178354Ssam } 336230793Sadrian ieee80211_notify_radar(ic, chan); 337230793Sadrian chan->ic_state |= IEEE80211_CHANSTATE_NORADAR; 338230793Sadrian if (!callout_pending(&dfs->nol_timer)) 339230793Sadrian callout_reset(&dfs->nol_timer, NOL_TIMEOUT, 340230793Sadrian dfs_timeout, ic); 341178354Ssam } 342178354Ssam 343178354Ssam /* 344178354Ssam * If radar is detected on the bss channel while 345178354Ssam * doing CAC; force a state change by scheduling the 346178354Ssam * callout to be dispatched asap. Otherwise, if this 347178354Ssam * event is for the bss channel then we must quiet 348178354Ssam * traffic and schedule a channel switch. 349178354Ssam * 350178354Ssam * Note this allows us to receive notification about 351178354Ssam * channels other than the bss channel; not sure 352178354Ssam * that can/will happen but it's simple to support. 353178354Ssam */ 354178354Ssam if (chan == ic->ic_bsschan) { 355178354Ssam /* XXX need a way to defer to user app */ 356178354Ssam 357230793Sadrian /* 358230793Sadrian * Don't flip over to a new channel if 359230793Sadrian * we are currently doing DFS debugging. 360230793Sadrian */ 361230793Sadrian if (ieee80211_dfs_debug == DFS_DBG_NONE) 362230793Sadrian dfs->newchan = ieee80211_dfs_pickchannel(ic); 363230793Sadrian else 364230793Sadrian dfs->newchan = chan; 365230793Sadrian 366283529Sglebius announce_radar(ic, chan, dfs->newchan); 367178354Ssam 368178354Ssam if (callout_pending(&dfs->cac_timer)) 369181193Ssam callout_schedule(&dfs->cac_timer, 0); 370178354Ssam else if (dfs->newchan != NULL) { 371178354Ssam /* XXX mode 1, switch count 2 */ 372178354Ssam /* XXX calculate switch count based on max 373178354Ssam switch time and beacon interval? */ 374178354Ssam ieee80211_csa_startswitch(ic, dfs->newchan, 1, 2); 375178354Ssam } else { 376178354Ssam /* 377178354Ssam * Spec says to stop all transmissions and 378178354Ssam * wait on the current channel for an entry 379178354Ssam * on the NOL to expire. 380178354Ssam */ 381178354Ssam /*XXX*/ 382283529Sglebius ic_printf(ic, "%s: No free channels; waiting for entry " 383223583Sadrian "on NOL to expire\n", __func__); 384178354Ssam } 385178354Ssam } else { 386178354Ssam /* 387178354Ssam * Issue rate-limited console msgs. 388178354Ssam */ 389178354Ssam if (dfs->lastchan != chan) { 390178354Ssam dfs->lastchan = chan; 391178354Ssam dfs->cureps = 0; 392283529Sglebius announce_radar(ic, chan, NULL); 393178354Ssam } else if (ppsratecheck(&dfs->lastevent, &dfs->cureps, 1)) { 394283529Sglebius announce_radar(ic, chan, NULL); 395178354Ssam } 396178354Ssam } 397178354Ssam} 398178354Ssam 399178354Ssamstruct ieee80211_channel * 400178354Ssamieee80211_dfs_pickchannel(struct ieee80211com *ic) 401178354Ssam{ 402178354Ssam struct ieee80211_channel *c; 403178354Ssam int i, flags; 404178354Ssam uint16_t v; 405178354Ssam 406178354Ssam /* 407178354Ssam * Consult the scan cache first. 408178354Ssam */ 409178354Ssam flags = ic->ic_curchan->ic_flags & IEEE80211_CHAN_ALL; 410178354Ssam /* 411178354Ssam * XXX if curchan is HT this will never find a channel 412178354Ssam * XXX 'cuz we scan only legacy channels 413178354Ssam */ 414178354Ssam c = ieee80211_scan_pickchannel(ic, flags); 415178354Ssam if (c != NULL) 416178354Ssam return c; 417178354Ssam /* 418178354Ssam * No channel found in scan cache; select a compatible 419178354Ssam * one at random (skipping channels where radar has 420178354Ssam * been detected). 421178354Ssam */ 422178354Ssam get_random_bytes(&v, sizeof(v)); 423178354Ssam v %= ic->ic_nchans; 424178354Ssam for (i = v; i < ic->ic_nchans; i++) { 425178354Ssam c = &ic->ic_channels[i]; 426178354Ssam if (!IEEE80211_IS_CHAN_RADAR(c) && 427178354Ssam (c->ic_flags & flags) == flags) 428178354Ssam return c; 429178354Ssam } 430178354Ssam for (i = 0; i < v; i++) { 431178354Ssam c = &ic->ic_channels[i]; 432178354Ssam if (!IEEE80211_IS_CHAN_RADAR(c) && 433178354Ssam (c->ic_flags & flags) == flags) 434178354Ssam return c; 435178354Ssam } 436283529Sglebius ic_printf(ic, "HELP, no channel located to switch to!\n"); 437178354Ssam return NULL; 438178354Ssam} 439