blob: 73319643978619742bab6172ae80ffa27f8c7b4b [file] [log] [blame]
/*
* Driver interaction with extended Linux Wireless Extensions
*
* This software is distributed under the terms of BSD license.
*
*/
#include "includes.h"
#include <sys/ioctl.h>
#include <net/if_arp.h>
#include <net/if.h>
#include "linux_wext.h"
#include "common.h"
#include "driver.h"
#include "eloop.h"
#include "priv_netlink.h"
#include "driver_wext.h"
#include "ieee802_11_defs.h"
#include "wpa_common.h"
#include "wpa_ctrl.h"
#include "wpa_supplicant_i.h"
#include "config.h"
#include "linux_ioctl.h"
#include "scan.h"
#include "driver_cmd_wext.h"
#ifdef ANDROID
#include "android_drv.h"
#endif /* ANDROID */
#define RSSI_CMD "RSSI"
#define LINKSPEED_CMD "LINKSPEED"
/**
* wpa_driver_wext_set_scan_timeout - Set scan timeout to report scan completion
* @priv: Pointer to private wext data from wpa_driver_wext_init()
*
* This function can be used to set registered timeout when starting a scan to
* generate a scan completed event if the driver does not report this.
*/
static void wpa_driver_wext_set_scan_timeout(void *priv)
{
struct wpa_driver_wext_data *drv = priv;
int timeout = 10; /* In case scan A and B bands it can be long */
/* Not all drivers generate "scan completed" wireless event, so try to
* read results after a timeout. */
if (drv->scan_complete_events) {
/*
* The driver seems to deliver SIOCGIWSCAN events to notify
* when scan is complete, so use longer timeout to avoid race
* conditions with scanning and following association request.
*/
timeout = 30;
}
wpa_printf(MSG_DEBUG, "Scan requested - scan timeout %d seconds",
timeout);
eloop_cancel_timeout(wpa_driver_wext_scan_timeout, drv, drv->ctx);
eloop_register_timeout(timeout, 0, wpa_driver_wext_scan_timeout, drv,
drv->ctx);
}
/**
* wpa_driver_wext_combo_scan - Request the driver to initiate combo scan
* @priv: Pointer to private wext data from wpa_driver_wext_init()
* @params: Scan parameters
* Returns: 0 on success, -1 on failure
*/
int wpa_driver_wext_combo_scan(void *priv, struct wpa_driver_scan_params *params)
{
char buf[WEXT_CSCAN_BUF_LEN];
struct wpa_driver_wext_data *drv = priv;
struct iwreq iwr;
int ret, bp;
unsigned i;
if (!drv->driver_is_started) {
wpa_printf(MSG_DEBUG, "%s: Driver stopped", __func__);
return 0;
}
wpa_printf(MSG_DEBUG, "%s: Start", __func__);
/* Set list of SSIDs */
bp = WEXT_CSCAN_HEADER_SIZE;
os_memcpy(buf, WEXT_CSCAN_HEADER, bp);
for(i=0; i < params->num_ssids; i++) {
if ((bp + IW_ESSID_MAX_SIZE + 10) >= (int)sizeof(buf))
break;
wpa_printf(MSG_DEBUG, "For Scan: %s", params->ssids[i].ssid);
buf[bp++] = WEXT_CSCAN_SSID_SECTION;
buf[bp++] = params->ssids[i].ssid_len;
os_memcpy(&buf[bp], params->ssids[i].ssid, params->ssids[i].ssid_len);
bp += params->ssids[i].ssid_len;
}
/* Set list of channels */
buf[bp++] = WEXT_CSCAN_CHANNEL_SECTION;
buf[bp++] = 0;
/* Set passive dwell time (default is 250) */
buf[bp++] = WEXT_CSCAN_PASV_DWELL_SECTION;
buf[bp++] = (u8)WEXT_CSCAN_PASV_DWELL_TIME;
buf[bp++] = (u8)(WEXT_CSCAN_PASV_DWELL_TIME >> 8);
/* Set home dwell time (default is 40) */
buf[bp++] = WEXT_CSCAN_HOME_DWELL_SECTION;
buf[bp++] = (u8)WEXT_CSCAN_HOME_DWELL_TIME;
buf[bp++] = (u8)(WEXT_CSCAN_HOME_DWELL_TIME >> 8);
os_memset(&iwr, 0, sizeof(iwr));
os_strlcpy(iwr.ifr_name, drv->ifname, IFNAMSIZ);
iwr.u.data.pointer = buf;
iwr.u.data.length = bp;
if ((ret = ioctl(drv->ioctl_sock, SIOCSIWPRIV, &iwr)) < 0) {
if (!drv->bgscan_enabled)
wpa_printf(MSG_ERROR, "ioctl[SIOCSIWPRIV] (cscan): %d", ret);
else
ret = 0; /* Hide error in case of bg scan */
}
return ret;
}
static int wpa_driver_wext_set_cscan_params(char *buf, size_t buf_len, char *cmd)
{
char *pasv_ptr;
int bp, i;
u16 pasv_dwell = WEXT_CSCAN_PASV_DWELL_TIME_DEF;
u8 channel;
wpa_printf(MSG_DEBUG, "%s: %s", __func__, cmd);
/* Get command parameters */
pasv_ptr = os_strstr(cmd, ",TIME=");
if (pasv_ptr) {
*pasv_ptr = '\0';
pasv_ptr += 6;
pasv_dwell = (u16)atoi(pasv_ptr);
if (pasv_dwell == 0)
pasv_dwell = WEXT_CSCAN_PASV_DWELL_TIME_DEF;
}
channel = (u8)atoi(cmd + 5);
bp = WEXT_CSCAN_HEADER_SIZE;
os_memcpy(buf, WEXT_CSCAN_HEADER, bp);
/* Set list of channels */
buf[bp++] = WEXT_CSCAN_CHANNEL_SECTION;
buf[bp++] = channel;
if (channel != 0) {
i = (pasv_dwell - 1) / WEXT_CSCAN_PASV_DWELL_TIME_DEF;
for (; i > 0; i--) {
if ((size_t)(bp + 12) >= buf_len)
break;
buf[bp++] = WEXT_CSCAN_CHANNEL_SECTION;
buf[bp++] = channel;
}
} else {
if (pasv_dwell > WEXT_CSCAN_PASV_DWELL_TIME_MAX)
pasv_dwell = WEXT_CSCAN_PASV_DWELL_TIME_MAX;
}
/* Set passive dwell time (default is 250) */
buf[bp++] = WEXT_CSCAN_PASV_DWELL_SECTION;
if (channel != 0) {
buf[bp++] = (u8)WEXT_CSCAN_PASV_DWELL_TIME_DEF;
buf[bp++] = (u8)(WEXT_CSCAN_PASV_DWELL_TIME_DEF >> 8);
} else {
buf[bp++] = (u8)pasv_dwell;
buf[bp++] = (u8)(pasv_dwell >> 8);
}
/* Set home dwell time (default is 40) */
buf[bp++] = WEXT_CSCAN_HOME_DWELL_SECTION;
buf[bp++] = (u8)WEXT_CSCAN_HOME_DWELL_TIME;
buf[bp++] = (u8)(WEXT_CSCAN_HOME_DWELL_TIME >> 8);
/* Set cscan type */
buf[bp++] = WEXT_CSCAN_TYPE_SECTION;
buf[bp++] = WEXT_CSCAN_TYPE_PASSIVE;
return bp;
}
static char *wpa_driver_get_country_code(int channels)
{
char *country = "US"; /* WEXT_NUMBER_SCAN_CHANNELS_FCC */
if (channels == WEXT_NUMBER_SCAN_CHANNELS_ETSI)
country = "EU";
else if( channels == WEXT_NUMBER_SCAN_CHANNELS_MKK1)
country = "JP";
return country;
}
static int wpa_driver_set_backgroundscan_params(void *priv)
{
struct wpa_driver_wext_data *drv = priv;
struct wpa_supplicant *wpa_s;
struct iwreq iwr;
int ret = 0, i = 0, bp;
char buf[WEXT_PNO_MAX_COMMAND_SIZE];
struct wpa_ssid *ssid_conf;
if (drv == NULL) {
wpa_printf(MSG_ERROR, "%s: drv is NULL. Exiting", __func__);
return -1;
}
if (drv->ctx == NULL) {
wpa_printf(MSG_ERROR, "%s: drv->ctx is NULL. Exiting", __func__);
return -1;
}
wpa_s = (struct wpa_supplicant *)(drv->ctx);
if (wpa_s->conf == NULL) {
wpa_printf(MSG_ERROR, "%s: wpa_s->conf is NULL. Exiting", __func__);
return -1;
}
ssid_conf = wpa_s->conf->ssid;
bp = WEXT_PNOSETUP_HEADER_SIZE;
os_memcpy(buf, WEXT_PNOSETUP_HEADER, bp);
buf[bp++] = WEXT_PNO_TLV_PREFIX;
buf[bp++] = WEXT_PNO_TLV_VERSION;
buf[bp++] = WEXT_PNO_TLV_SUBVERSION;
buf[bp++] = WEXT_PNO_TLV_RESERVED;
while ((i < WEXT_PNO_AMOUNT) && (ssid_conf != NULL)) {
/* Check that there is enough space needed for 1 more SSID, the other sections and null termination */
if ((bp + WEXT_PNO_SSID_HEADER_SIZE + IW_ESSID_MAX_SIZE + WEXT_PNO_NONSSID_SECTIONS_SIZE + 1) >= (int)sizeof(buf))
break;
if ((!ssid_conf->disabled) && (ssid_conf->ssid_len <= IW_ESSID_MAX_SIZE)){
wpa_printf(MSG_DEBUG, "For PNO Scan: %s", ssid_conf->ssid);
buf[bp++] = WEXT_PNO_SSID_SECTION;
buf[bp++] = ssid_conf->ssid_len;
os_memcpy(&buf[bp], ssid_conf->ssid, ssid_conf->ssid_len);
bp += ssid_conf->ssid_len;
i++;
}
ssid_conf = ssid_conf->next;
}
buf[bp++] = WEXT_PNO_SCAN_INTERVAL_SECTION;
os_snprintf(&buf[bp], WEXT_PNO_SCAN_INTERVAL_LENGTH + 1, "%x", WEXT_PNO_SCAN_INTERVAL);
bp += WEXT_PNO_SCAN_INTERVAL_LENGTH;
buf[bp++] = WEXT_PNO_REPEAT_SECTION;
os_snprintf(&buf[bp], WEXT_PNO_REPEAT_LENGTH + 1, "%x", WEXT_PNO_REPEAT);
bp += WEXT_PNO_REPEAT_LENGTH;
buf[bp++] = WEXT_PNO_MAX_REPEAT_SECTION;
os_snprintf(&buf[bp], WEXT_PNO_MAX_REPEAT_LENGTH + 1, "%x", WEXT_PNO_MAX_REPEAT);
bp += WEXT_PNO_MAX_REPEAT_LENGTH + 1;
os_memset(&iwr, 0, sizeof(iwr));
os_strlcpy(iwr.ifr_name, drv->ifname, IFNAMSIZ);
iwr.u.data.pointer = buf;
iwr.u.data.length = bp;
ret = ioctl(drv->ioctl_sock, SIOCSIWPRIV, &iwr);
if (ret < 0) {
wpa_printf(MSG_ERROR, "ioctl[SIOCSIWPRIV] (pnosetup): %d", ret);
drv->errors++;
if (drv->errors > DRV_NUMBER_SEQUENTIAL_ERRORS) {
drv->errors = 0;
wpa_msg(drv->ctx, MSG_INFO, WPA_EVENT_DRIVER_STATE "HANGED");
}
} else {
drv->errors = 0;
}
return ret;
}
int wpa_driver_wext_driver_cmd( void *priv, char *cmd, char *buf, size_t buf_len )
{
struct wpa_driver_wext_data *drv = priv;
struct wpa_supplicant *wpa_s = (struct wpa_supplicant *)(drv->ctx);
struct iwreq iwr;
int ret = 0, flags;
wpa_printf(MSG_DEBUG, "%s %s len = %d", __func__, cmd, buf_len);
if (!drv->driver_is_started && (os_strcasecmp(cmd, "START") != 0)) {
wpa_printf(MSG_ERROR,"WEXT: Driver not initialized yet");
return -1;
}
if (os_strcasecmp(cmd, "RSSI-APPROX") == 0) {
os_strlcpy(cmd, RSSI_CMD, MAX_DRV_CMD_SIZE);
} else if( os_strncasecmp(cmd, "SCAN-CHANNELS", 13) == 0 ) {
int no_of_chan;
no_of_chan = atoi(cmd + 13);
os_snprintf(cmd, MAX_DRV_CMD_SIZE, "COUNTRY %s",
wpa_driver_get_country_code(no_of_chan));
} else if (os_strcasecmp(cmd, "STOP") == 0) {
linux_set_iface_flags(drv->ioctl_sock, drv->ifname, 0);
} else if( os_strcasecmp(cmd, "RELOAD") == 0 ) {
wpa_printf(MSG_DEBUG,"Reload command");
wpa_msg(drv->ctx, MSG_INFO, WPA_EVENT_DRIVER_STATE "HANGED");
return ret;
} else if( os_strcasecmp(cmd, "BGSCAN-START") == 0 ) {
ret = wpa_driver_set_backgroundscan_params(priv);
if (ret < 0) {
return ret;
}
os_strlcpy(cmd, "PNOFORCE 1", MAX_DRV_CMD_SIZE);
drv->bgscan_enabled = 1;
} else if( os_strcasecmp(cmd, "BGSCAN-STOP") == 0 ) {
os_strlcpy(cmd, "PNOFORCE 0", MAX_DRV_CMD_SIZE);
drv->bgscan_enabled = 0;
}
os_memset(&iwr, 0, sizeof(iwr));
os_strlcpy(iwr.ifr_name, drv->ifname, IFNAMSIZ);
os_memcpy(buf, cmd, strlen(cmd) + 1);
iwr.u.data.pointer = buf;
iwr.u.data.length = buf_len;
if( os_strncasecmp(cmd, "CSCAN", 5) == 0 ) {
if (!wpa_s->scanning && ((wpa_s->wpa_state <= WPA_SCANNING) ||
(wpa_s->wpa_state >= WPA_COMPLETED))) {
iwr.u.data.length = wpa_driver_wext_set_cscan_params(buf, buf_len, cmd);
} else {
wpa_printf(MSG_ERROR, "Ongoing Scan action...");
return ret;
}
}
ret = ioctl(drv->ioctl_sock, SIOCSIWPRIV, &iwr);
if (ret < 0) {
wpa_printf(MSG_ERROR, "%s failed (%d): %s", __func__, ret, cmd);
drv->errors++;
if (drv->errors > DRV_NUMBER_SEQUENTIAL_ERRORS) {
drv->errors = 0;
wpa_msg(drv->ctx, MSG_INFO, WPA_EVENT_DRIVER_STATE "HANGED");
}
} else {
drv->errors = 0;
ret = 0;
if ((os_strcasecmp(cmd, RSSI_CMD) == 0) ||
(os_strcasecmp(cmd, LINKSPEED_CMD) == 0) ||
(os_strcasecmp(cmd, "MACADDR") == 0) ||
(os_strcasecmp(cmd, "GETPOWER") == 0) ||
(os_strcasecmp(cmd, "GETBAND") == 0)) {
ret = strlen(buf);
} else if (os_strcasecmp(cmd, "START") == 0) {
drv->driver_is_started = TRUE;
linux_set_iface_flags(drv->ioctl_sock, drv->ifname, 1);
/* os_sleep(0, WPA_DRIVER_WEXT_WAIT_US);
wpa_msg(drv->ctx, MSG_INFO, WPA_EVENT_DRIVER_STATE "STARTED"); */
} else if (os_strcasecmp(cmd, "STOP") == 0) {
drv->driver_is_started = FALSE;
/* wpa_msg(drv->ctx, MSG_INFO, WPA_EVENT_DRIVER_STATE "STOPPED"); */
} else if (os_strncasecmp(cmd, "CSCAN", 5) == 0) {
wpa_driver_wext_set_scan_timeout(priv);
wpa_supplicant_notify_scanning(wpa_s, 1);
}
wpa_printf(MSG_DEBUG, "%s %s len = %d, %d", __func__, buf, ret, strlen(buf));
}
return ret;
}
int wpa_driver_signal_poll(void *priv, struct wpa_signal_info *si)
{
char buf[MAX_DRV_CMD_SIZE];
struct wpa_driver_wext_data *drv = priv;
char *prssi;
int res;
os_memset(si, 0, sizeof(*si));
res = wpa_driver_wext_driver_cmd(priv, RSSI_CMD, buf, sizeof(buf));
/* Answer: SSID rssi -Val */
if (res < 0)
return res;
prssi = strcasestr(buf, RSSI_CMD);
if (!prssi)
return -1;
si->current_signal = atoi(prssi + strlen(RSSI_CMD) + 1);
res = wpa_driver_wext_driver_cmd(priv, LINKSPEED_CMD, buf, sizeof(buf));
/* Answer: LinkSpeed Val */
if (res < 0)
return res;
si->current_txrate = atoi(buf + strlen(LINKSPEED_CMD) + 1) * 1000;
return 0;
}