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/10/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> 51178354Ssam#include <net/if_media.h> 52178354Ssam 53178354Ssam#include <net80211/ieee80211_var.h> 54178354Ssam 55227293Sedstatic MALLOC_DEFINE(M_80211_DFS, "80211dfs", "802.11 DFS state"); 56178354Ssam 57193115Ssamstatic int ieee80211_nol_timeout = 30*60; /* 30 minutes */ 58193115SsamSYSCTL_INT(_net_wlan, OID_AUTO, nol_timeout, CTLFLAG_RW, 59193115Ssam &ieee80211_nol_timeout, 0, "NOL timeout (secs)"); 60178354Ssam#define NOL_TIMEOUT msecs_to_ticks(ieee80211_nol_timeout*1000) 61193115Ssam 62193115Ssamstatic int ieee80211_cac_timeout = 60; /* 60 seconds */ 63193115SsamSYSCTL_INT(_net_wlan, OID_AUTO, cac_timeout, CTLFLAG_RW, 64193115Ssam &ieee80211_cac_timeout, 0, "CAC timeout (secs)"); 65178354Ssam#define CAC_TIMEOUT msecs_to_ticks(ieee80211_cac_timeout*1000) 66178354Ssam 67230793Sadrian/* 68230793Sadrian DFS* In order to facilitate debugging, a couple of operating 69230793Sadrian * modes aside from the default are needed. 70230793Sadrian * 71230793Sadrian * 0 - default CAC/NOL behaviour - ie, start CAC, place 72230793Sadrian * channel on NOL list. 73230793Sadrian * 1 - send CAC, but don't change channel or add the channel 74230793Sadrian * to the NOL list. 75230793Sadrian * 2 - just match on radar, don't send CAC or place channel in 76230793Sadrian * the NOL list. 77230793Sadrian */ 78230793Sadrianstatic int ieee80211_dfs_debug = DFS_DBG_NONE; 79230793Sadrian 80230793Sadrian/* 81230793Sadrian * This option must not be included in the default kernel 82230793Sadrian * as it allows users to plainly disable CAC/NOL handling. 83230793Sadrian */ 84230793Sadrian#ifdef IEEE80211_DFS_DEBUG 85230793SadrianSYSCTL_INT(_net_wlan, OID_AUTO, dfs_debug, CTLFLAG_RW, 86230793Sadrian &ieee80211_dfs_debug, 0, "DFS debug behaviour"); 87230793Sadrian#endif 88230793Sadrian 89227331Sadrianstatic int 90227331Sadriannull_set_quiet(struct ieee80211_node *ni, u_int8_t *quiet_elm) 91227331Sadrian{ 92227331Sadrian return ENOSYS; 93227331Sadrian} 94227331Sadrian 95178354Ssamvoid 96178354Ssamieee80211_dfs_attach(struct ieee80211com *ic) 97178354Ssam{ 98178354Ssam struct ieee80211_dfs_state *dfs = &ic->ic_dfs; 99178354Ssam 100193844Ssam callout_init_mtx(&dfs->nol_timer, IEEE80211_LOCK_OBJ(ic), 0); 101193844Ssam callout_init_mtx(&dfs->cac_timer, IEEE80211_LOCK_OBJ(ic), 0); 102227331Sadrian 103227331Sadrian ic->ic_set_quiet = null_set_quiet; 104178354Ssam} 105178354Ssam 106178354Ssamvoid 107178354Ssamieee80211_dfs_detach(struct ieee80211com *ic) 108178354Ssam{ 109178354Ssam /* NB: we assume no locking is needed */ 110178354Ssam ieee80211_dfs_reset(ic); 111178354Ssam} 112178354Ssam 113178354Ssamvoid 114178354Ssamieee80211_dfs_reset(struct ieee80211com *ic) 115178354Ssam{ 116178354Ssam struct ieee80211_dfs_state *dfs = &ic->ic_dfs; 117178354Ssam int i; 118178354Ssam 119178354Ssam /* NB: we assume no locking is needed */ 120178354Ssam /* NB: cac_timer should be cleared by the state machine */ 121178354Ssam callout_drain(&dfs->nol_timer); 122178354Ssam for (i = 0; i < ic->ic_nchans; i++) 123178354Ssam ic->ic_channels[i].ic_state = 0; 124178354Ssam dfs->lastchan = NULL; 125178354Ssam} 126178354Ssam 127178354Ssamstatic void 128178354Ssamcac_timeout(void *arg) 129178354Ssam{ 130178354Ssam struct ieee80211vap *vap = arg; 131178354Ssam struct ieee80211com *ic = vap->iv_ic; 132178354Ssam struct ieee80211_dfs_state *dfs = &ic->ic_dfs; 133178354Ssam int i; 134178354Ssam 135193844Ssam IEEE80211_LOCK_ASSERT(ic); 136193844Ssam 137178354Ssam if (vap->iv_state != IEEE80211_S_CAC) /* NB: just in case */ 138178354Ssam return; 139178354Ssam /* 140178354Ssam * When radar is detected during a CAC we are woken 141178354Ssam * up prematurely to switch to a new channel. 142178354Ssam * Check the channel to decide how to act. 143178354Ssam */ 144178354Ssam if (IEEE80211_IS_CHAN_RADAR(ic->ic_curchan)) { 145178354Ssam ieee80211_notify_cac(ic, ic->ic_curchan, 146178354Ssam IEEE80211_NOTIFY_CAC_RADAR); 147178354Ssam 148178354Ssam if_printf(vap->iv_ifp, 149178354Ssam "CAC timer on channel %u (%u MHz) stopped due to radar\n", 150178354Ssam ic->ic_curchan->ic_ieee, ic->ic_curchan->ic_freq); 151178354Ssam 152178354Ssam /* XXX clobbers any existing desired channel */ 153178354Ssam /* NB: dfs->newchan may be NULL, that's ok */ 154178354Ssam vap->iv_des_chan = dfs->newchan; 155343894Savos ieee80211_new_state_locked(vap, IEEE80211_S_SCAN, 0); 156178354Ssam } else { 157178354Ssam if_printf(vap->iv_ifp, 158178354Ssam "CAC timer on channel %u (%u MHz) expired; " 159178354Ssam "no radar detected\n", 160178354Ssam ic->ic_curchan->ic_ieee, ic->ic_curchan->ic_freq); 161178354Ssam /* 162178354Ssam * Mark all channels with the current frequency 163178354Ssam * as having completed CAC; this keeps us from 164178354Ssam * doing it again until we change channels. 165178354Ssam */ 166178354Ssam for (i = 0; i < ic->ic_nchans; i++) { 167178354Ssam struct ieee80211_channel *c = &ic->ic_channels[i]; 168178354Ssam if (c->ic_freq == ic->ic_curchan->ic_freq) 169178354Ssam c->ic_state |= IEEE80211_CHANSTATE_CACDONE; 170178354Ssam } 171178354Ssam ieee80211_notify_cac(ic, ic->ic_curchan, 172178354Ssam IEEE80211_NOTIFY_CAC_EXPIRE); 173178354Ssam ieee80211_cac_completeswitch(vap); 174178354Ssam } 175178354Ssam} 176178354Ssam 177178354Ssam/* 178178354Ssam * Initiate the CAC timer. The driver is responsible 179178354Ssam * for setting up the hardware to scan for radar on the 180178354Ssam * channnel, we just handle timing things out. 181178354Ssam */ 182178354Ssamvoid 183178354Ssamieee80211_dfs_cac_start(struct ieee80211vap *vap) 184178354Ssam{ 185178354Ssam struct ieee80211com *ic = vap->iv_ic; 186178354Ssam struct ieee80211_dfs_state *dfs = &ic->ic_dfs; 187178354Ssam 188178354Ssam IEEE80211_LOCK_ASSERT(ic); 189178354Ssam 190178354Ssam callout_reset(&dfs->cac_timer, CAC_TIMEOUT, cac_timeout, vap); 191178354Ssam if_printf(vap->iv_ifp, "start %d second CAC timer on channel %u (%u MHz)\n", 192178354Ssam ticks_to_secs(CAC_TIMEOUT), 193178354Ssam ic->ic_curchan->ic_ieee, ic->ic_curchan->ic_freq); 194178354Ssam ieee80211_notify_cac(ic, ic->ic_curchan, IEEE80211_NOTIFY_CAC_START); 195178354Ssam} 196178354Ssam 197178354Ssam/* 198178354Ssam * Clear the CAC timer. 199178354Ssam */ 200178354Ssamvoid 201178354Ssamieee80211_dfs_cac_stop(struct ieee80211vap *vap) 202178354Ssam{ 203178354Ssam struct ieee80211com *ic = vap->iv_ic; 204178354Ssam struct ieee80211_dfs_state *dfs = &ic->ic_dfs; 205178354Ssam 206178354Ssam IEEE80211_LOCK_ASSERT(ic); 207178354Ssam 208178354Ssam /* NB: racey but not important */ 209178354Ssam if (callout_pending(&dfs->cac_timer)) { 210178354Ssam if_printf(vap->iv_ifp, "stop CAC timer on channel %u (%u MHz)\n", 211178354Ssam ic->ic_curchan->ic_ieee, ic->ic_curchan->ic_freq); 212178354Ssam ieee80211_notify_cac(ic, ic->ic_curchan, 213178354Ssam IEEE80211_NOTIFY_CAC_STOP); 214178354Ssam } 215178354Ssam callout_stop(&dfs->cac_timer); 216178354Ssam} 217178354Ssam 218178354Ssamvoid 219178354Ssamieee80211_dfs_cac_clear(struct ieee80211com *ic, 220178354Ssam const struct ieee80211_channel *chan) 221178354Ssam{ 222178354Ssam int i; 223178354Ssam 224178354Ssam for (i = 0; i < ic->ic_nchans; i++) { 225178354Ssam struct ieee80211_channel *c = &ic->ic_channels[i]; 226178354Ssam if (c->ic_freq == chan->ic_freq) 227178354Ssam c->ic_state &= ~IEEE80211_CHANSTATE_CACDONE; 228178354Ssam } 229178354Ssam} 230178354Ssam 231178354Ssamstatic void 232178354Ssamdfs_timeout(void *arg) 233178354Ssam{ 234178354Ssam struct ieee80211com *ic = arg; 235178354Ssam struct ieee80211_dfs_state *dfs = &ic->ic_dfs; 236178354Ssam struct ieee80211_channel *c; 237178354Ssam int i, oldest, now; 238178354Ssam 239193844Ssam IEEE80211_LOCK_ASSERT(ic); 240193844Ssam 241178354Ssam now = oldest = ticks; 242178354Ssam for (i = 0; i < ic->ic_nchans; i++) { 243178354Ssam c = &ic->ic_channels[i]; 244178354Ssam if (IEEE80211_IS_CHAN_RADAR(c)) { 245178354Ssam if (time_after_eq(now, dfs->nol_event[i]+NOL_TIMEOUT)) { 246178354Ssam c->ic_state &= ~IEEE80211_CHANSTATE_RADAR; 247178354Ssam if (c->ic_state & IEEE80211_CHANSTATE_NORADAR) { 248178354Ssam /* 249178354Ssam * NB: do this here so we get only one 250178354Ssam * msg instead of one for every channel 251178354Ssam * table entry. 252178354Ssam */ 253178354Ssam if_printf(ic->ic_ifp, "radar on channel" 254178354Ssam " %u (%u MHz) cleared after timeout\n", 255178354Ssam c->ic_ieee, c->ic_freq); 256178354Ssam /* notify user space */ 257178354Ssam c->ic_state &= 258178354Ssam ~IEEE80211_CHANSTATE_NORADAR; 259178354Ssam ieee80211_notify_radar(ic, c); 260178354Ssam } 261178354Ssam } else if (dfs->nol_event[i] < oldest) 262178354Ssam oldest = dfs->nol_event[i]; 263178354Ssam } 264178354Ssam } 265178354Ssam if (oldest != now) { 266178354Ssam /* arrange to process next channel up for a status change */ 267196785Ssam callout_schedule(&dfs->nol_timer, oldest + NOL_TIMEOUT - now); 268178354Ssam } 269178354Ssam} 270178354Ssam 271178354Ssamstatic void 272178354Ssamannounce_radar(struct ifnet *ifp, const struct ieee80211_channel *curchan, 273178354Ssam const struct ieee80211_channel *newchan) 274178354Ssam{ 275178354Ssam if (newchan == NULL) 276178354Ssam if_printf(ifp, "radar detected on channel %u (%u MHz)\n", 277178354Ssam curchan->ic_ieee, curchan->ic_freq); 278178354Ssam else 279178354Ssam if_printf(ifp, "radar detected on channel %u (%u MHz), " 280178354Ssam "moving to channel %u (%u MHz)\n", 281178354Ssam curchan->ic_ieee, curchan->ic_freq, 282178354Ssam newchan->ic_ieee, newchan->ic_freq); 283178354Ssam} 284178354Ssam 285178354Ssam/* 286178354Ssam * Handle a radar detection event on a channel. The channel is 287178354Ssam * added to the NOL list and we record the time of the event. 288178354Ssam * Entries are aged out after NOL_TIMEOUT. If radar was 289178354Ssam * detected while doing CAC we force a state/channel change. 290178354Ssam * Otherwise radar triggers a channel switch using the CSA 291178354Ssam * mechanism (when the channel is the bss channel). 292178354Ssam */ 293178354Ssamvoid 294178354Ssamieee80211_dfs_notify_radar(struct ieee80211com *ic, struct ieee80211_channel *chan) 295178354Ssam{ 296178354Ssam struct ieee80211_dfs_state *dfs = &ic->ic_dfs; 297178354Ssam int i, now; 298178354Ssam 299178354Ssam IEEE80211_LOCK_ASSERT(ic); 300178354Ssam 301178354Ssam /* 302230793Sadrian * If doing DFS debugging (mode 2), don't bother 303230793Sadrian * running the rest of this function. 304230793Sadrian * 305230793Sadrian * Simply announce the presence of the radar and continue 306230793Sadrian * along merrily. 307178354Ssam */ 308230793Sadrian if (ieee80211_dfs_debug == DFS_DBG_NOCSANOL) { 309230793Sadrian announce_radar(ic->ic_ifp, chan, chan); 310230793Sadrian ieee80211_notify_radar(ic, chan); 311230793Sadrian return; 312230793Sadrian } 313230793Sadrian 314230793Sadrian /* 315230793Sadrian * Don't mark the channel and don't put it into NOL 316230793Sadrian * if we're doing DFS debugging. 317230793Sadrian */ 318230793Sadrian if (ieee80211_dfs_debug == DFS_DBG_NONE) { 319230793Sadrian /* 320230793Sadrian * Mark all entries with this frequency. Notify user 321230793Sadrian * space and arrange for notification when the radar 322230793Sadrian * indication is cleared. Then kick the NOL processing 323230793Sadrian * thread if not already running. 324230793Sadrian */ 325230793Sadrian now = ticks; 326230793Sadrian for (i = 0; i < ic->ic_nchans; i++) { 327230793Sadrian struct ieee80211_channel *c = &ic->ic_channels[i]; 328230793Sadrian if (c->ic_freq == chan->ic_freq) { 329230793Sadrian c->ic_state &= ~IEEE80211_CHANSTATE_CACDONE; 330230793Sadrian c->ic_state |= IEEE80211_CHANSTATE_RADAR; 331230793Sadrian dfs->nol_event[i] = now; 332230793Sadrian } 333178354Ssam } 334230793Sadrian ieee80211_notify_radar(ic, chan); 335230793Sadrian chan->ic_state |= IEEE80211_CHANSTATE_NORADAR; 336230793Sadrian if (!callout_pending(&dfs->nol_timer)) 337230793Sadrian callout_reset(&dfs->nol_timer, NOL_TIMEOUT, 338230793Sadrian dfs_timeout, ic); 339178354Ssam } 340178354Ssam 341178354Ssam /* 342178354Ssam * If radar is detected on the bss channel while 343178354Ssam * doing CAC; force a state change by scheduling the 344178354Ssam * callout to be dispatched asap. Otherwise, if this 345178354Ssam * event is for the bss channel then we must quiet 346178354Ssam * traffic and schedule a channel switch. 347178354Ssam * 348178354Ssam * Note this allows us to receive notification about 349178354Ssam * channels other than the bss channel; not sure 350178354Ssam * that can/will happen but it's simple to support. 351178354Ssam */ 352178354Ssam if (chan == ic->ic_bsschan) { 353178354Ssam /* XXX need a way to defer to user app */ 354178354Ssam 355230793Sadrian /* 356230793Sadrian * Don't flip over to a new channel if 357230793Sadrian * we are currently doing DFS debugging. 358230793Sadrian */ 359230793Sadrian if (ieee80211_dfs_debug == DFS_DBG_NONE) 360230793Sadrian dfs->newchan = ieee80211_dfs_pickchannel(ic); 361230793Sadrian else 362230793Sadrian dfs->newchan = chan; 363230793Sadrian 364178354Ssam announce_radar(ic->ic_ifp, chan, dfs->newchan); 365178354Ssam 366178354Ssam if (callout_pending(&dfs->cac_timer)) 367181193Ssam callout_schedule(&dfs->cac_timer, 0); 368178354Ssam else if (dfs->newchan != NULL) { 369178354Ssam /* XXX mode 1, switch count 2 */ 370178354Ssam /* XXX calculate switch count based on max 371178354Ssam switch time and beacon interval? */ 372178354Ssam ieee80211_csa_startswitch(ic, dfs->newchan, 1, 2); 373178354Ssam } else { 374178354Ssam /* 375178354Ssam * Spec says to stop all transmissions and 376178354Ssam * wait on the current channel for an entry 377178354Ssam * on the NOL to expire. 378178354Ssam */ 379178354Ssam /*XXX*/ 380223583Sadrian if_printf(ic->ic_ifp, "%s: No free channels; waiting for entry " 381223583Sadrian "on NOL to expire\n", __func__); 382178354Ssam } 383178354Ssam } else { 384178354Ssam /* 385178354Ssam * Issue rate-limited console msgs. 386178354Ssam */ 387178354Ssam if (dfs->lastchan != chan) { 388178354Ssam dfs->lastchan = chan; 389178354Ssam dfs->cureps = 0; 390178354Ssam announce_radar(ic->ic_ifp, chan, NULL); 391178354Ssam } else if (ppsratecheck(&dfs->lastevent, &dfs->cureps, 1)) { 392178354Ssam announce_radar(ic->ic_ifp, chan, NULL); 393178354Ssam } 394178354Ssam } 395178354Ssam} 396178354Ssam 397178354Ssamstruct ieee80211_channel * 398178354Ssamieee80211_dfs_pickchannel(struct ieee80211com *ic) 399178354Ssam{ 400178354Ssam struct ieee80211_channel *c; 401178354Ssam int i, flags; 402178354Ssam uint16_t v; 403178354Ssam 404178354Ssam /* 405178354Ssam * Consult the scan cache first. 406178354Ssam */ 407178354Ssam flags = ic->ic_curchan->ic_flags & IEEE80211_CHAN_ALL; 408178354Ssam /* 409178354Ssam * XXX if curchan is HT this will never find a channel 410178354Ssam * XXX 'cuz we scan only legacy channels 411178354Ssam */ 412178354Ssam c = ieee80211_scan_pickchannel(ic, flags); 413178354Ssam if (c != NULL) 414178354Ssam return c; 415178354Ssam /* 416178354Ssam * No channel found in scan cache; select a compatible 417178354Ssam * one at random (skipping channels where radar has 418178354Ssam * been detected). 419178354Ssam */ 420178354Ssam get_random_bytes(&v, sizeof(v)); 421178354Ssam v %= ic->ic_nchans; 422178354Ssam for (i = v; i < ic->ic_nchans; i++) { 423178354Ssam c = &ic->ic_channels[i]; 424178354Ssam if (!IEEE80211_IS_CHAN_RADAR(c) && 425178354Ssam (c->ic_flags & flags) == flags) 426178354Ssam return c; 427178354Ssam } 428178354Ssam for (i = 0; i < v; i++) { 429178354Ssam c = &ic->ic_channels[i]; 430178354Ssam if (!IEEE80211_IS_CHAN_RADAR(c) && 431178354Ssam (c->ic_flags & flags) == flags) 432178354Ssam return c; 433178354Ssam } 434178354Ssam if_printf(ic->ic_ifp, "HELP, no channel located to switch to!\n"); 435178354Ssam return NULL; 436178354Ssam} 437