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