Iliyan Malchev | 574336e | 2012-08-19 15:08:56 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Driver interaction with extended Linux Wireless Extensions |
| 3 | * |
| 4 | * This program is free software; you can redistribute it and/or modify |
| 5 | * it under the terms of the GNU General Public License version 2 as |
| 6 | * published by the Free Software Foundation. |
| 7 | * |
| 8 | * Alternatively, this software may be distributed under the terms of BSD |
| 9 | * license. |
| 10 | * |
| 11 | */ |
| 12 | |
| 13 | #include "includes.h" |
| 14 | #include <sys/ioctl.h> |
| 15 | #include <net/if_arp.h> |
| 16 | #include <net/if.h> |
| 17 | |
| 18 | #include "linux_wext.h" |
| 19 | #include "common.h" |
| 20 | #include "driver.h" |
| 21 | #include "eloop.h" |
| 22 | #include "priv_netlink.h" |
| 23 | #include "driver_wext.h" |
| 24 | #include "ieee802_11_defs.h" |
| 25 | #include "wpa_common.h" |
| 26 | #include "wpa_ctrl.h" |
| 27 | #include "wpa_supplicant_i.h" |
| 28 | #include "config.h" |
| 29 | #include "linux_ioctl.h" |
| 30 | #include "scan.h" |
| 31 | |
| 32 | #include "driver_cmd_wext.h" |
| 33 | #ifdef ANDROID |
| 34 | #include "android_drv.h" |
| 35 | #endif /* ANDROID */ |
| 36 | |
| 37 | #define RSSI_CMD "RSSI" |
| 38 | #define LINKSPEED_CMD "LINKSPEED" |
| 39 | |
| 40 | /** |
| 41 | * wpa_driver_wext_set_scan_timeout - Set scan timeout to report scan completion |
| 42 | * @priv: Pointer to private wext data from wpa_driver_wext_init() |
| 43 | * |
| 44 | * This function can be used to set registered timeout when starting a scan to |
| 45 | * generate a scan completed event if the driver does not report this. |
| 46 | */ |
| 47 | static void wpa_driver_wext_set_scan_timeout(void *priv) |
| 48 | { |
| 49 | struct wpa_driver_wext_data *drv = priv; |
| 50 | int timeout = 10; /* In case scan A and B bands it can be long */ |
| 51 | |
| 52 | /* Not all drivers generate "scan completed" wireless event, so try to |
| 53 | * read results after a timeout. */ |
| 54 | if (drv->scan_complete_events) { |
| 55 | /* |
| 56 | * The driver seems to deliver SIOCGIWSCAN events to notify |
| 57 | * when scan is complete, so use longer timeout to avoid race |
| 58 | * conditions with scanning and following association request. |
| 59 | */ |
| 60 | timeout = 30; |
| 61 | } |
| 62 | wpa_printf(MSG_DEBUG, "Scan requested - scan timeout %d seconds", |
| 63 | timeout); |
| 64 | eloop_cancel_timeout(wpa_driver_wext_scan_timeout, drv, drv->ctx); |
| 65 | eloop_register_timeout(timeout, 0, wpa_driver_wext_scan_timeout, drv, |
| 66 | drv->ctx); |
| 67 | } |
| 68 | |
| 69 | /** |
| 70 | * wpa_driver_wext_combo_scan - Request the driver to initiate combo scan |
| 71 | * @priv: Pointer to private wext data from wpa_driver_wext_init() |
| 72 | * @params: Scan parameters |
| 73 | * Returns: 0 on success, -1 on failure |
| 74 | */ |
| 75 | int wpa_driver_wext_combo_scan(void *priv, struct wpa_driver_scan_params *params) |
| 76 | { |
| 77 | char buf[WEXT_CSCAN_BUF_LEN]; |
| 78 | struct wpa_driver_wext_data *drv = priv; |
| 79 | struct iwreq iwr; |
| 80 | int ret, bp; |
| 81 | unsigned i; |
| 82 | |
| 83 | if (!drv->driver_is_started) { |
| 84 | wpa_printf(MSG_DEBUG, "%s: Driver stopped", __func__); |
| 85 | return 0; |
| 86 | } |
| 87 | |
| 88 | wpa_printf(MSG_DEBUG, "%s: Start", __func__); |
| 89 | |
| 90 | /* Set list of SSIDs */ |
| 91 | bp = WEXT_CSCAN_HEADER_SIZE; |
| 92 | os_memcpy(buf, WEXT_CSCAN_HEADER, bp); |
| 93 | for(i=0; i < params->num_ssids; i++) { |
| 94 | if ((bp + IW_ESSID_MAX_SIZE + 10) >= (int)sizeof(buf)) |
| 95 | break; |
| 96 | wpa_printf(MSG_DEBUG, "For Scan: %s", params->ssids[i].ssid); |
| 97 | buf[bp++] = WEXT_CSCAN_SSID_SECTION; |
| 98 | buf[bp++] = params->ssids[i].ssid_len; |
| 99 | os_memcpy(&buf[bp], params->ssids[i].ssid, params->ssids[i].ssid_len); |
| 100 | bp += params->ssids[i].ssid_len; |
| 101 | } |
| 102 | |
| 103 | /* Set list of channels */ |
| 104 | buf[bp++] = WEXT_CSCAN_CHANNEL_SECTION; |
| 105 | buf[bp++] = 0; |
| 106 | |
| 107 | /* Set passive dwell time (default is 250) */ |
| 108 | buf[bp++] = WEXT_CSCAN_PASV_DWELL_SECTION; |
| 109 | buf[bp++] = (u8)WEXT_CSCAN_PASV_DWELL_TIME; |
| 110 | buf[bp++] = (u8)(WEXT_CSCAN_PASV_DWELL_TIME >> 8); |
| 111 | |
| 112 | /* Set home dwell time (default is 40) */ |
| 113 | buf[bp++] = WEXT_CSCAN_HOME_DWELL_SECTION; |
| 114 | buf[bp++] = (u8)WEXT_CSCAN_HOME_DWELL_TIME; |
| 115 | buf[bp++] = (u8)(WEXT_CSCAN_HOME_DWELL_TIME >> 8); |
| 116 | |
| 117 | os_memset(&iwr, 0, sizeof(iwr)); |
| 118 | os_strncpy(iwr.ifr_name, drv->ifname, IFNAMSIZ); |
| 119 | iwr.u.data.pointer = buf; |
| 120 | iwr.u.data.length = bp; |
| 121 | |
| 122 | if ((ret = ioctl(drv->ioctl_sock, SIOCSIWPRIV, &iwr)) < 0) { |
| 123 | if (!drv->bgscan_enabled) |
| 124 | wpa_printf(MSG_ERROR, "ioctl[SIOCSIWPRIV] (cscan): %d", ret); |
| 125 | else |
| 126 | ret = 0; /* Hide error in case of bg scan */ |
| 127 | } |
| 128 | return ret; |
| 129 | } |
| 130 | |
| 131 | static int wpa_driver_wext_set_cscan_params(char *buf, size_t buf_len, char *cmd) |
| 132 | { |
| 133 | char *pasv_ptr; |
| 134 | int bp, i; |
| 135 | u16 pasv_dwell = WEXT_CSCAN_PASV_DWELL_TIME_DEF; |
| 136 | u8 channel; |
| 137 | |
| 138 | wpa_printf(MSG_DEBUG, "%s: %s", __func__, cmd); |
| 139 | |
| 140 | /* Get command parameters */ |
| 141 | pasv_ptr = os_strstr(cmd, ",TIME="); |
| 142 | if (pasv_ptr) { |
| 143 | *pasv_ptr = '\0'; |
| 144 | pasv_ptr += 6; |
| 145 | pasv_dwell = (u16)atoi(pasv_ptr); |
| 146 | if (pasv_dwell == 0) |
| 147 | pasv_dwell = WEXT_CSCAN_PASV_DWELL_TIME_DEF; |
| 148 | } |
| 149 | channel = (u8)atoi(cmd + 5); |
| 150 | |
| 151 | bp = WEXT_CSCAN_HEADER_SIZE; |
| 152 | os_memcpy(buf, WEXT_CSCAN_HEADER, bp); |
| 153 | |
| 154 | /* Set list of channels */ |
| 155 | buf[bp++] = WEXT_CSCAN_CHANNEL_SECTION; |
| 156 | buf[bp++] = channel; |
| 157 | if (channel != 0) { |
| 158 | i = (pasv_dwell - 1) / WEXT_CSCAN_PASV_DWELL_TIME_DEF; |
| 159 | for (; i > 0; i--) { |
| 160 | if ((size_t)(bp + 12) >= buf_len) |
| 161 | break; |
| 162 | buf[bp++] = WEXT_CSCAN_CHANNEL_SECTION; |
| 163 | buf[bp++] = channel; |
| 164 | } |
| 165 | } else { |
| 166 | if (pasv_dwell > WEXT_CSCAN_PASV_DWELL_TIME_MAX) |
| 167 | pasv_dwell = WEXT_CSCAN_PASV_DWELL_TIME_MAX; |
| 168 | } |
| 169 | |
| 170 | /* Set passive dwell time (default is 250) */ |
| 171 | buf[bp++] = WEXT_CSCAN_PASV_DWELL_SECTION; |
| 172 | if (channel != 0) { |
| 173 | buf[bp++] = (u8)WEXT_CSCAN_PASV_DWELL_TIME_DEF; |
| 174 | buf[bp++] = (u8)(WEXT_CSCAN_PASV_DWELL_TIME_DEF >> 8); |
| 175 | } else { |
| 176 | buf[bp++] = (u8)pasv_dwell; |
| 177 | buf[bp++] = (u8)(pasv_dwell >> 8); |
| 178 | } |
| 179 | |
| 180 | /* Set home dwell time (default is 40) */ |
| 181 | buf[bp++] = WEXT_CSCAN_HOME_DWELL_SECTION; |
| 182 | buf[bp++] = (u8)WEXT_CSCAN_HOME_DWELL_TIME; |
| 183 | buf[bp++] = (u8)(WEXT_CSCAN_HOME_DWELL_TIME >> 8); |
| 184 | |
| 185 | /* Set cscan type */ |
| 186 | buf[bp++] = WEXT_CSCAN_TYPE_SECTION; |
| 187 | buf[bp++] = WEXT_CSCAN_TYPE_PASSIVE; |
| 188 | return bp; |
| 189 | } |
| 190 | |
| 191 | static char *wpa_driver_get_country_code(int channels) |
| 192 | { |
| 193 | char *country = "US"; /* WEXT_NUMBER_SCAN_CHANNELS_FCC */ |
| 194 | |
| 195 | if (channels == WEXT_NUMBER_SCAN_CHANNELS_ETSI) |
| 196 | country = "EU"; |
| 197 | else if( channels == WEXT_NUMBER_SCAN_CHANNELS_MKK1) |
| 198 | country = "JP"; |
| 199 | return country; |
| 200 | } |
| 201 | |
| 202 | static int wpa_driver_set_backgroundscan_params(void *priv) |
| 203 | { |
| 204 | struct wpa_driver_wext_data *drv = priv; |
| 205 | struct wpa_supplicant *wpa_s; |
| 206 | struct iwreq iwr; |
| 207 | int ret = 0, i = 0, bp; |
| 208 | char buf[WEXT_PNO_MAX_COMMAND_SIZE]; |
| 209 | struct wpa_ssid *ssid_conf; |
| 210 | |
| 211 | if (drv == NULL) { |
| 212 | wpa_printf(MSG_ERROR, "%s: drv is NULL. Exiting", __func__); |
| 213 | return -1; |
| 214 | } |
| 215 | if (drv->ctx == NULL) { |
| 216 | wpa_printf(MSG_ERROR, "%s: drv->ctx is NULL. Exiting", __func__); |
| 217 | return -1; |
| 218 | } |
| 219 | wpa_s = (struct wpa_supplicant *)(drv->ctx); |
| 220 | if (wpa_s->conf == NULL) { |
| 221 | wpa_printf(MSG_ERROR, "%s: wpa_s->conf is NULL. Exiting", __func__); |
| 222 | return -1; |
| 223 | } |
| 224 | ssid_conf = wpa_s->conf->ssid; |
| 225 | |
| 226 | bp = WEXT_PNOSETUP_HEADER_SIZE; |
| 227 | os_memcpy(buf, WEXT_PNOSETUP_HEADER, bp); |
| 228 | buf[bp++] = WEXT_PNO_TLV_PREFIX; |
| 229 | buf[bp++] = WEXT_PNO_TLV_VERSION; |
| 230 | buf[bp++] = WEXT_PNO_TLV_SUBVERSION; |
| 231 | buf[bp++] = WEXT_PNO_TLV_RESERVED; |
| 232 | |
| 233 | while ((i < WEXT_PNO_AMOUNT) && (ssid_conf != NULL)) { |
| 234 | /* Check that there is enough space needed for 1 more SSID, the other sections and null termination */ |
| 235 | if ((bp + WEXT_PNO_SSID_HEADER_SIZE + IW_ESSID_MAX_SIZE + WEXT_PNO_NONSSID_SECTIONS_SIZE + 1) >= (int)sizeof(buf)) |
| 236 | break; |
| 237 | if ((!ssid_conf->disabled) && (ssid_conf->ssid_len <= IW_ESSID_MAX_SIZE)){ |
| 238 | wpa_printf(MSG_DEBUG, "For PNO Scan: %s", ssid_conf->ssid); |
| 239 | buf[bp++] = WEXT_PNO_SSID_SECTION; |
| 240 | buf[bp++] = ssid_conf->ssid_len; |
| 241 | os_memcpy(&buf[bp], ssid_conf->ssid, ssid_conf->ssid_len); |
| 242 | bp += ssid_conf->ssid_len; |
| 243 | i++; |
| 244 | } |
| 245 | ssid_conf = ssid_conf->next; |
| 246 | } |
| 247 | |
| 248 | buf[bp++] = WEXT_PNO_SCAN_INTERVAL_SECTION; |
| 249 | os_snprintf(&buf[bp], WEXT_PNO_SCAN_INTERVAL_LENGTH + 1, "%x", WEXT_PNO_SCAN_INTERVAL); |
| 250 | bp += WEXT_PNO_SCAN_INTERVAL_LENGTH; |
| 251 | |
| 252 | buf[bp++] = WEXT_PNO_REPEAT_SECTION; |
| 253 | os_snprintf(&buf[bp], WEXT_PNO_REPEAT_LENGTH + 1, "%x", WEXT_PNO_REPEAT); |
| 254 | bp += WEXT_PNO_REPEAT_LENGTH; |
| 255 | |
| 256 | buf[bp++] = WEXT_PNO_MAX_REPEAT_SECTION; |
| 257 | os_snprintf(&buf[bp], WEXT_PNO_MAX_REPEAT_LENGTH + 1, "%x", WEXT_PNO_MAX_REPEAT); |
| 258 | bp += WEXT_PNO_MAX_REPEAT_LENGTH + 1; |
| 259 | |
| 260 | os_memset(&iwr, 0, sizeof(iwr)); |
| 261 | os_strncpy(iwr.ifr_name, drv->ifname, IFNAMSIZ); |
| 262 | iwr.u.data.pointer = buf; |
| 263 | iwr.u.data.length = bp; |
| 264 | |
| 265 | ret = ioctl(drv->ioctl_sock, SIOCSIWPRIV, &iwr); |
| 266 | |
| 267 | if (ret < 0) { |
| 268 | wpa_printf(MSG_ERROR, "ioctl[SIOCSIWPRIV] (pnosetup): %d", ret); |
| 269 | drv->errors++; |
| 270 | if (drv->errors > DRV_NUMBER_SEQUENTIAL_ERRORS) { |
| 271 | drv->errors = 0; |
| 272 | wpa_msg(drv->ctx, MSG_INFO, WPA_EVENT_DRIVER_STATE "HANGED"); |
| 273 | } |
| 274 | } else { |
| 275 | drv->errors = 0; |
| 276 | } |
| 277 | return ret; |
| 278 | |
| 279 | } |
| 280 | |
| 281 | int wpa_driver_wext_driver_cmd( void *priv, char *cmd, char *buf, size_t buf_len ) |
| 282 | { |
| 283 | struct wpa_driver_wext_data *drv = priv; |
| 284 | struct wpa_supplicant *wpa_s = (struct wpa_supplicant *)(drv->ctx); |
| 285 | struct iwreq iwr; |
| 286 | int ret = 0, flags; |
| 287 | |
| 288 | wpa_printf(MSG_DEBUG, "%s %s len = %d", __func__, cmd, buf_len); |
| 289 | |
| 290 | if (!drv->driver_is_started && (os_strcasecmp(cmd, "START") != 0)) { |
| 291 | wpa_printf(MSG_ERROR,"WEXT: Driver not initialized yet"); |
| 292 | return -1; |
| 293 | } |
| 294 | |
| 295 | if (os_strcasecmp(cmd, "RSSI-APPROX") == 0) { |
| 296 | os_strncpy(cmd, RSSI_CMD, MAX_DRV_CMD_SIZE); |
| 297 | } else if( os_strncasecmp(cmd, "SCAN-CHANNELS", 13) == 0 ) { |
| 298 | int no_of_chan; |
| 299 | |
| 300 | no_of_chan = atoi(cmd + 13); |
| 301 | os_snprintf(cmd, MAX_DRV_CMD_SIZE, "COUNTRY %s", |
| 302 | wpa_driver_get_country_code(no_of_chan)); |
| 303 | } else if (os_strcasecmp(cmd, "STOP") == 0) { |
| 304 | linux_set_iface_flags(drv->ioctl_sock, drv->ifname, 0); |
| 305 | } else if( os_strcasecmp(cmd, "RELOAD") == 0 ) { |
| 306 | wpa_printf(MSG_DEBUG,"Reload command"); |
| 307 | wpa_msg(drv->ctx, MSG_INFO, WPA_EVENT_DRIVER_STATE "HANGED"); |
| 308 | return ret; |
| 309 | } else if( os_strcasecmp(cmd, "BGSCAN-START") == 0 ) { |
| 310 | ret = wpa_driver_set_backgroundscan_params(priv); |
| 311 | if (ret < 0) { |
| 312 | return ret; |
| 313 | } |
| 314 | os_strncpy(cmd, "PNOFORCE 1", MAX_DRV_CMD_SIZE); |
| 315 | drv->bgscan_enabled = 1; |
| 316 | } else if( os_strcasecmp(cmd, "BGSCAN-STOP") == 0 ) { |
| 317 | os_strncpy(cmd, "PNOFORCE 0", MAX_DRV_CMD_SIZE); |
| 318 | drv->bgscan_enabled = 0; |
| 319 | } |
| 320 | |
| 321 | os_memset(&iwr, 0, sizeof(iwr)); |
| 322 | os_strncpy(iwr.ifr_name, drv->ifname, IFNAMSIZ); |
| 323 | os_memcpy(buf, cmd, strlen(cmd) + 1); |
| 324 | iwr.u.data.pointer = buf; |
| 325 | iwr.u.data.length = buf_len; |
| 326 | |
| 327 | if( os_strncasecmp(cmd, "CSCAN", 5) == 0 ) { |
| 328 | if (!wpa_s->scanning && ((wpa_s->wpa_state <= WPA_SCANNING) || |
| 329 | (wpa_s->wpa_state >= WPA_COMPLETED))) { |
| 330 | iwr.u.data.length = wpa_driver_wext_set_cscan_params(buf, buf_len, cmd); |
| 331 | } else { |
| 332 | wpa_printf(MSG_ERROR, "Ongoing Scan action..."); |
| 333 | return ret; |
| 334 | } |
| 335 | } |
| 336 | |
| 337 | ret = ioctl(drv->ioctl_sock, SIOCSIWPRIV, &iwr); |
| 338 | |
| 339 | if (ret < 0) { |
| 340 | wpa_printf(MSG_ERROR, "%s failed (%d): %s", __func__, ret, cmd); |
| 341 | drv->errors++; |
| 342 | if (drv->errors > DRV_NUMBER_SEQUENTIAL_ERRORS) { |
| 343 | drv->errors = 0; |
| 344 | wpa_msg(drv->ctx, MSG_INFO, WPA_EVENT_DRIVER_STATE "HANGED"); |
| 345 | } |
| 346 | } else { |
| 347 | drv->errors = 0; |
| 348 | ret = 0; |
| 349 | if ((os_strcasecmp(cmd, RSSI_CMD) == 0) || |
| 350 | (os_strcasecmp(cmd, LINKSPEED_CMD) == 0) || |
| 351 | (os_strcasecmp(cmd, "MACADDR") == 0) || |
| 352 | (os_strcasecmp(cmd, "GETPOWER") == 0) || |
| 353 | (os_strcasecmp(cmd, "GETBAND") == 0)) { |
| 354 | ret = strlen(buf); |
| 355 | } else if (os_strcasecmp(cmd, "START") == 0) { |
| 356 | drv->driver_is_started = TRUE; |
| 357 | linux_set_iface_flags(drv->ioctl_sock, drv->ifname, 1); |
| 358 | /* os_sleep(0, WPA_DRIVER_WEXT_WAIT_US); |
| 359 | wpa_msg(drv->ctx, MSG_INFO, WPA_EVENT_DRIVER_STATE "STARTED"); */ |
| 360 | } else if (os_strcasecmp(cmd, "STOP") == 0) { |
| 361 | drv->driver_is_started = FALSE; |
| 362 | /* wpa_msg(drv->ctx, MSG_INFO, WPA_EVENT_DRIVER_STATE "STOPPED"); */ |
| 363 | } else if (os_strncasecmp(cmd, "CSCAN", 5) == 0) { |
| 364 | wpa_driver_wext_set_scan_timeout(priv); |
| 365 | wpa_supplicant_notify_scanning(wpa_s, 1); |
| 366 | } |
| 367 | wpa_printf(MSG_DEBUG, "%s %s len = %d, %d", __func__, buf, ret, strlen(buf)); |
| 368 | } |
| 369 | return ret; |
| 370 | } |
| 371 | |
| 372 | int wpa_driver_signal_poll(void *priv, struct wpa_signal_info *si) |
| 373 | { |
| 374 | char buf[MAX_DRV_CMD_SIZE]; |
| 375 | struct wpa_driver_wext_data *drv = priv; |
| 376 | char *prssi; |
| 377 | int res; |
| 378 | |
| 379 | os_memset(si, 0, sizeof(*si)); |
| 380 | res = wpa_driver_wext_driver_cmd(priv, RSSI_CMD, buf, sizeof(buf)); |
| 381 | /* Answer: SSID rssi -Val */ |
| 382 | if (res < 0) |
| 383 | return res; |
| 384 | prssi = strcasestr(buf, RSSI_CMD); |
| 385 | if (!prssi) |
| 386 | return -1; |
| 387 | si->current_signal = atoi(prssi + strlen(RSSI_CMD) + 1); |
| 388 | |
| 389 | res = wpa_driver_wext_driver_cmd(priv, LINKSPEED_CMD, buf, sizeof(buf)); |
| 390 | /* Answer: LinkSpeed Val */ |
| 391 | if (res < 0) |
| 392 | return res; |
| 393 | si->current_txrate = atoi(buf + strlen(LINKSPEED_CMD) + 1) * 1000; |
| 394 | |
| 395 | return 0; |
| 396 | } |