1 1.1 christos /* 2 1.1 christos * WPA Supplicant - background scan and roaming module: simple 3 1.1 christos * Copyright (c) 2009-2010, Jouni Malinen <j (at) w1.fi> 4 1.1 christos * 5 1.1.1.4 christos * This software may be distributed under the terms of the BSD license. 6 1.1.1.4 christos * See README for more details. 7 1.1 christos */ 8 1.1 christos 9 1.1 christos #include "includes.h" 10 1.1 christos 11 1.1 christos #include "common.h" 12 1.1 christos #include "eloop.h" 13 1.1 christos #include "drivers/driver.h" 14 1.1 christos #include "config_ssid.h" 15 1.1 christos #include "wpa_supplicant_i.h" 16 1.1 christos #include "driver_i.h" 17 1.1 christos #include "scan.h" 18 1.1.1.7 christos #include "config.h" 19 1.1.1.7 christos #include "wnm_sta.h" 20 1.1.1.7 christos #include "bss.h" 21 1.1 christos #include "bgscan.h" 22 1.1 christos 23 1.1 christos struct bgscan_simple_data { 24 1.1 christos struct wpa_supplicant *wpa_s; 25 1.1 christos const struct wpa_ssid *ssid; 26 1.1.1.7 christos unsigned int use_btm_query; 27 1.1.1.7 christos unsigned int scan_action_count; 28 1.1 christos int scan_interval; 29 1.1 christos int signal_threshold; 30 1.1.1.3 christos int short_scan_count; /* counter for scans using short scan interval */ 31 1.1.1.4 christos int max_short_scans; /* maximum times we short-scan before back-off */ 32 1.1 christos int short_interval; /* use if signal < threshold */ 33 1.1 christos int long_interval; /* use if signal > threshold */ 34 1.1.1.5 christos struct os_reltime last_bgscan; 35 1.1 christos }; 36 1.1 christos 37 1.1 christos 38 1.1.1.7 christos static void bgscan_simple_timeout(void *eloop_ctx, void *timeout_ctx); 39 1.1.1.7 christos 40 1.1.1.7 christos 41 1.1.1.7 christos static bool bgscan_simple_btm_query(struct wpa_supplicant *wpa_s, 42 1.1.1.7 christos struct bgscan_simple_data *data) 43 1.1.1.7 christos { 44 1.1.1.7 christos unsigned int mod; 45 1.1.1.7 christos 46 1.1.1.7 christos if (!data->use_btm_query || wpa_s->conf->disable_btm || 47 1.1.1.7 christos !wpa_s->current_bss || 48 1.1.1.7 christos !wpa_bss_ext_capab(wpa_s->current_bss, 49 1.1.1.7 christos WLAN_EXT_CAPAB_BSS_TRANSITION)) 50 1.1.1.7 christos return false; 51 1.1.1.7 christos 52 1.1.1.7 christos /* Try BTM x times, scan on x + 1 */ 53 1.1.1.7 christos data->scan_action_count++; 54 1.1.1.7 christos mod = data->scan_action_count % (data->use_btm_query + 1); 55 1.1.1.7 christos if (mod >= data->use_btm_query) 56 1.1.1.7 christos return false; 57 1.1.1.7 christos 58 1.1.1.7 christos wpa_printf(MSG_DEBUG, 59 1.1.1.7 christos "bgscan simple: Send BSS transition management query %d/%d", 60 1.1.1.7 christos mod, data->use_btm_query); 61 1.1.1.7 christos if (wnm_send_bss_transition_mgmt_query( 62 1.1.1.7 christos wpa_s, WNM_TRANSITION_REASON_BETTER_AP_FOUND, NULL, 0)) { 63 1.1.1.7 christos wpa_printf(MSG_DEBUG, 64 1.1.1.7 christos "bgscan simple: Failed to send BSS transition management query"); 65 1.1.1.7 christos /* Fall through and do regular scan */ 66 1.1.1.7 christos return false; 67 1.1.1.7 christos } 68 1.1.1.7 christos 69 1.1.1.7 christos /* Start a new timeout for the next one. We don't have scan callback to 70 1.1.1.7 christos * otherwise trigger future progress when using BTM path. */ 71 1.1.1.7 christos eloop_register_timeout(data->scan_interval, 0, 72 1.1.1.7 christos bgscan_simple_timeout, data, NULL); 73 1.1.1.7 christos return true; 74 1.1.1.7 christos } 75 1.1.1.7 christos 76 1.1.1.7 christos 77 1.1 christos static void bgscan_simple_timeout(void *eloop_ctx, void *timeout_ctx) 78 1.1 christos { 79 1.1 christos struct bgscan_simple_data *data = eloop_ctx; 80 1.1 christos struct wpa_supplicant *wpa_s = data->wpa_s; 81 1.1 christos struct wpa_driver_scan_params params; 82 1.1 christos 83 1.1.1.7 christos if (bgscan_simple_btm_query(wpa_s, data)) 84 1.1.1.7 christos goto scan_ok; 85 1.1.1.7 christos 86 1.1 christos os_memset(¶ms, 0, sizeof(params)); 87 1.1 christos params.num_ssids = 1; 88 1.1 christos params.ssids[0].ssid = data->ssid->ssid; 89 1.1 christos params.ssids[0].ssid_len = data->ssid->ssid_len; 90 1.1 christos params.freqs = data->ssid->scan_freq; 91 1.1 christos 92 1.1 christos /* 93 1.1 christos * A more advanced bgscan module would learn about most like channels 94 1.1 christos * over time and request scans only for some channels (probing others 95 1.1 christos * every now and then) to reduce effect on the data connection. 96 1.1 christos */ 97 1.1 christos 98 1.1 christos wpa_printf(MSG_DEBUG, "bgscan simple: Request a background scan"); 99 1.1.1.7 christos if (wpa_supplicant_trigger_scan(wpa_s, ¶ms, true, false)) { 100 1.1 christos wpa_printf(MSG_DEBUG, "bgscan simple: Failed to trigger scan"); 101 1.1 christos eloop_register_timeout(data->scan_interval, 0, 102 1.1 christos bgscan_simple_timeout, data, NULL); 103 1.1.1.3 christos } else { 104 1.1.1.7 christos scan_ok: 105 1.1.1.3 christos if (data->scan_interval == data->short_interval) { 106 1.1.1.3 christos data->short_scan_count++; 107 1.1.1.6 christos if (data->short_scan_count >= data->max_short_scans) { 108 1.1.1.3 christos data->scan_interval = data->long_interval; 109 1.1.1.3 christos wpa_printf(MSG_DEBUG, "bgscan simple: Backing " 110 1.1.1.3 christos "off to long scan interval"); 111 1.1.1.3 christos } 112 1.1.1.4 christos } else if (data->short_scan_count > 0) { 113 1.1.1.4 christos /* 114 1.1.1.4 christos * If we lasted a long scan interval without any 115 1.1.1.4 christos * CQM triggers, decrease the short-scan count, 116 1.1.1.4 christos * which allows 1 more short-scan interval to 117 1.1.1.4 christos * occur in the future when CQM triggers. 118 1.1.1.4 christos */ 119 1.1.1.4 christos data->short_scan_count--; 120 1.1.1.3 christos } 121 1.1.1.5 christos os_get_reltime(&data->last_bgscan); 122 1.1.1.3 christos } 123 1.1 christos } 124 1.1 christos 125 1.1 christos 126 1.1 christos static int bgscan_simple_get_params(struct bgscan_simple_data *data, 127 1.1 christos const char *params) 128 1.1 christos { 129 1.1 christos const char *pos; 130 1.1 christos 131 1.1.1.7 christos data->use_btm_query = 0; 132 1.1.1.7 christos 133 1.1 christos data->short_interval = atoi(params); 134 1.1 christos 135 1.1 christos pos = os_strchr(params, ':'); 136 1.1 christos if (pos == NULL) 137 1.1 christos return 0; 138 1.1 christos pos++; 139 1.1 christos data->signal_threshold = atoi(pos); 140 1.1 christos pos = os_strchr(pos, ':'); 141 1.1 christos if (pos == NULL) { 142 1.1 christos wpa_printf(MSG_ERROR, "bgscan simple: Missing scan interval " 143 1.1 christos "for high signal"); 144 1.1 christos return -1; 145 1.1 christos } 146 1.1 christos pos++; 147 1.1 christos data->long_interval = atoi(pos); 148 1.1.1.7 christos pos = os_strchr(pos, ':'); 149 1.1.1.7 christos if (pos) { 150 1.1.1.7 christos pos++; 151 1.1.1.7 christos data->use_btm_query = atoi(pos); 152 1.1.1.7 christos } 153 1.1 christos 154 1.1 christos return 0; 155 1.1 christos } 156 1.1 christos 157 1.1 christos 158 1.1 christos static void * bgscan_simple_init(struct wpa_supplicant *wpa_s, 159 1.1 christos const char *params, 160 1.1 christos const struct wpa_ssid *ssid) 161 1.1 christos { 162 1.1 christos struct bgscan_simple_data *data; 163 1.1 christos 164 1.1 christos data = os_zalloc(sizeof(*data)); 165 1.1 christos if (data == NULL) 166 1.1 christos return NULL; 167 1.1 christos data->wpa_s = wpa_s; 168 1.1 christos data->ssid = ssid; 169 1.1 christos if (bgscan_simple_get_params(data, params) < 0) { 170 1.1 christos os_free(data); 171 1.1 christos return NULL; 172 1.1 christos } 173 1.1 christos if (data->short_interval <= 0) 174 1.1 christos data->short_interval = 30; 175 1.1 christos if (data->long_interval <= 0) 176 1.1 christos data->long_interval = 30; 177 1.1 christos 178 1.1 christos wpa_printf(MSG_DEBUG, "bgscan simple: Signal strength threshold %d " 179 1.1 christos "Short bgscan interval %d Long bgscan interval %d", 180 1.1 christos data->signal_threshold, data->short_interval, 181 1.1 christos data->long_interval); 182 1.1 christos 183 1.1 christos if (data->signal_threshold && 184 1.1 christos wpa_drv_signal_monitor(wpa_s, data->signal_threshold, 4) < 0) { 185 1.1 christos wpa_printf(MSG_ERROR, "bgscan simple: Failed to enable " 186 1.1 christos "signal strength monitoring"); 187 1.1 christos } 188 1.1 christos 189 1.1 christos data->scan_interval = data->short_interval; 190 1.1.1.4 christos data->max_short_scans = data->long_interval / data->short_interval + 1; 191 1.1.1.3 christos if (data->signal_threshold) { 192 1.1.1.7 christos wpa_s->signal_threshold = data->signal_threshold; 193 1.1.1.3 christos /* Poll for signal info to set initial scan interval */ 194 1.1.1.3 christos struct wpa_signal_info siginfo; 195 1.1.1.3 christos if (wpa_drv_signal_poll(wpa_s, &siginfo) == 0 && 196 1.1.1.7 christos siginfo.data.signal >= data->signal_threshold) 197 1.1.1.3 christos data->scan_interval = data->long_interval; 198 1.1.1.3 christos } 199 1.1.1.3 christos wpa_printf(MSG_DEBUG, "bgscan simple: Init scan interval: %d", 200 1.1.1.3 christos data->scan_interval); 201 1.1 christos eloop_register_timeout(data->scan_interval, 0, bgscan_simple_timeout, 202 1.1 christos data, NULL); 203 1.1.1.2 christos 204 1.1.1.2 christos /* 205 1.1.1.2 christos * This function is called immediately after an association, so it is 206 1.1.1.2 christos * reasonable to assume that a scan was completed recently. This makes 207 1.1.1.2 christos * us skip an immediate new scan in cases where the current signal 208 1.1.1.2 christos * level is below the bgscan threshold. 209 1.1.1.2 christos */ 210 1.1.1.5 christos os_get_reltime(&data->last_bgscan); 211 1.1.1.2 christos 212 1.1 christos return data; 213 1.1 christos } 214 1.1 christos 215 1.1 christos 216 1.1 christos static void bgscan_simple_deinit(void *priv) 217 1.1 christos { 218 1.1 christos struct bgscan_simple_data *data = priv; 219 1.1 christos eloop_cancel_timeout(bgscan_simple_timeout, data, NULL); 220 1.1.1.7 christos if (data->signal_threshold) { 221 1.1.1.7 christos data->wpa_s->signal_threshold = 0; 222 1.1 christos wpa_drv_signal_monitor(data->wpa_s, 0, 0); 223 1.1.1.7 christos } 224 1.1 christos os_free(data); 225 1.1 christos } 226 1.1 christos 227 1.1 christos 228 1.1.1.3 christos static int bgscan_simple_notify_scan(void *priv, 229 1.1.1.3 christos struct wpa_scan_results *scan_res) 230 1.1 christos { 231 1.1 christos struct bgscan_simple_data *data = priv; 232 1.1 christos 233 1.1 christos wpa_printf(MSG_DEBUG, "bgscan simple: scan result notification"); 234 1.1 christos 235 1.1 christos eloop_cancel_timeout(bgscan_simple_timeout, data, NULL); 236 1.1 christos eloop_register_timeout(data->scan_interval, 0, bgscan_simple_timeout, 237 1.1 christos data, NULL); 238 1.1 christos 239 1.1 christos /* 240 1.1 christos * A more advanced bgscan could process scan results internally, select 241 1.1 christos * the BSS and request roam if needed. This sample uses the existing 242 1.1 christos * BSS/ESS selection routine. Change this to return 1 if selection is 243 1.1 christos * done inside the bgscan module. 244 1.1 christos */ 245 1.1 christos 246 1.1 christos return 0; 247 1.1 christos } 248 1.1 christos 249 1.1 christos 250 1.1 christos static void bgscan_simple_notify_beacon_loss(void *priv) 251 1.1 christos { 252 1.1 christos wpa_printf(MSG_DEBUG, "bgscan simple: beacon loss"); 253 1.1 christos /* TODO: speed up background scanning */ 254 1.1 christos } 255 1.1 christos 256 1.1 christos 257 1.1.1.3 christos static void bgscan_simple_notify_signal_change(void *priv, int above, 258 1.1.1.3 christos int current_signal, 259 1.1.1.3 christos int current_noise, 260 1.1.1.3 christos int current_txrate) 261 1.1 christos { 262 1.1 christos struct bgscan_simple_data *data = priv; 263 1.1.1.2 christos int scan = 0; 264 1.1.1.5 christos struct os_reltime now; 265 1.1 christos 266 1.1 christos if (data->short_interval == data->long_interval || 267 1.1 christos data->signal_threshold == 0) 268 1.1 christos return; 269 1.1 christos 270 1.1 christos wpa_printf(MSG_DEBUG, "bgscan simple: signal level changed " 271 1.1.1.3 christos "(above=%d current_signal=%d current_noise=%d " 272 1.1.1.3 christos "current_txrate=%d))", above, current_signal, 273 1.1.1.3 christos current_noise, current_txrate); 274 1.1 christos if (data->scan_interval == data->long_interval && !above) { 275 1.1.1.2 christos wpa_printf(MSG_DEBUG, "bgscan simple: Start using short " 276 1.1.1.2 christos "bgscan interval"); 277 1.1 christos data->scan_interval = data->short_interval; 278 1.1.1.5 christos os_get_reltime(&now); 279 1.1.1.4 christos if (now.sec > data->last_bgscan.sec + 1 && 280 1.1.1.4 christos data->short_scan_count <= data->max_short_scans) 281 1.1.1.4 christos /* 282 1.1.1.4 christos * If we haven't just previously (<1 second ago) 283 1.1.1.4 christos * performed a scan, and we haven't depleted our 284 1.1.1.4 christos * budget for short-scans, perform a scan 285 1.1.1.4 christos * immediately. 286 1.1.1.4 christos */ 287 1.1.1.2 christos scan = 1; 288 1.1.1.3 christos else if (data->last_bgscan.sec + data->long_interval > 289 1.1.1.3 christos now.sec + data->scan_interval) { 290 1.1.1.3 christos /* 291 1.1.1.3 christos * Restart scan interval timer if currently scheduled 292 1.1.1.3 christos * scan is too far in the future. 293 1.1.1.3 christos */ 294 1.1.1.3 christos eloop_cancel_timeout(bgscan_simple_timeout, data, 295 1.1.1.3 christos NULL); 296 1.1.1.3 christos eloop_register_timeout(data->scan_interval, 0, 297 1.1.1.3 christos bgscan_simple_timeout, data, 298 1.1.1.3 christos NULL); 299 1.1.1.3 christos } 300 1.1 christos } else if (data->scan_interval == data->short_interval && above) { 301 1.1 christos wpa_printf(MSG_DEBUG, "bgscan simple: Start using long bgscan " 302 1.1 christos "interval"); 303 1.1 christos data->scan_interval = data->long_interval; 304 1.1 christos eloop_cancel_timeout(bgscan_simple_timeout, data, NULL); 305 1.1 christos eloop_register_timeout(data->scan_interval, 0, 306 1.1 christos bgscan_simple_timeout, data, NULL); 307 1.1 christos } else if (!above) { 308 1.1 christos /* 309 1.1 christos * Signal dropped further 4 dB. Request a new scan if we have 310 1.1 christos * not yet scanned in a while. 311 1.1 christos */ 312 1.1.1.5 christos os_get_reltime(&now); 313 1.1.1.2 christos if (now.sec > data->last_bgscan.sec + 10) 314 1.1.1.2 christos scan = 1; 315 1.1.1.2 christos } 316 1.1.1.2 christos 317 1.1.1.2 christos if (scan) { 318 1.1.1.2 christos wpa_printf(MSG_DEBUG, "bgscan simple: Trigger immediate scan"); 319 1.1.1.2 christos eloop_cancel_timeout(bgscan_simple_timeout, data, NULL); 320 1.1.1.2 christos eloop_register_timeout(0, 0, bgscan_simple_timeout, data, 321 1.1.1.2 christos NULL); 322 1.1 christos } 323 1.1 christos } 324 1.1 christos 325 1.1 christos 326 1.1 christos const struct bgscan_ops bgscan_simple_ops = { 327 1.1 christos .name = "simple", 328 1.1 christos .init = bgscan_simple_init, 329 1.1 christos .deinit = bgscan_simple_deinit, 330 1.1 christos .notify_scan = bgscan_simple_notify_scan, 331 1.1 christos .notify_beacon_loss = bgscan_simple_notify_beacon_loss, 332 1.1 christos .notify_signal_change = bgscan_simple_notify_signal_change, 333 1.1 christos }; 334