wext: Fix scan result signal levels when driver reports in dBm
[libeap.git] / src / drivers / driver_wext.c
index cb89a9e..f0de6aa 100644 (file)
@@ -1,6 +1,6 @@
 /*
- * WPA Supplicant - driver interaction with generic Linux Wireless Extensions
- * Copyright (c) 2003-2007, Jouni Malinen <j@w1.fi>
+ * Driver interaction with generic Linux Wireless Extensions
+ * Copyright (c) 2003-2010, Jouni Malinen <j@w1.fi>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 as
@@ -20,6 +20,7 @@
 
 #include "includes.h"
 #include <sys/ioctl.h>
+#include <sys/stat.h>
 #include <net/if_arp.h>
 
 #include "wireless_copy.h"
@@ -30,6 +31,7 @@
 #include "priv_netlink.h"
 #include "netlink.h"
 #include "linux_ioctl.h"
+#include "rfkill.h"
 #include "driver.h"
 #include "driver_wext.h"
 
@@ -633,6 +635,19 @@ static void wpa_driver_wext_event_rtm_newlink(void *ctx, struct ifinfomsg *ifi,
                   (ifi->ifi_flags & IFF_RUNNING) ? "[RUNNING]" : "",
                   (ifi->ifi_flags & IFF_LOWER_UP) ? "[LOWER_UP]" : "",
                   (ifi->ifi_flags & IFF_DORMANT) ? "[DORMANT]" : "");
+
+       if (!drv->if_disabled && !(ifi->ifi_flags & IFF_UP)) {
+               wpa_printf(MSG_DEBUG, "WEXT: Interface down");
+               drv->if_disabled = 1;
+               wpa_supplicant_event(drv->ctx, EVENT_INTERFACE_DISABLED, NULL);
+       }
+
+       if (drv->if_disabled && (ifi->ifi_flags & IFF_UP)) {
+               wpa_printf(MSG_DEBUG, "WEXT: Interface up");
+               drv->if_disabled = 0;
+               wpa_supplicant_event(drv->ctx, EVENT_INTERFACE_ENABLED, NULL);
+       }
+
        /*
         * Some drivers send the association event before the operup event--in
         * this case, lifting operstate in wpa_driver_wext_set_operstate()
@@ -686,6 +701,29 @@ static void wpa_driver_wext_event_rtm_dellink(void *ctx, struct ifinfomsg *ifi,
 }
 
 
+static void wpa_driver_wext_rfkill_blocked(void *ctx)
+{
+       wpa_printf(MSG_DEBUG, "WEXT: RFKILL blocked");
+       /*
+        * This may be for any interface; use ifdown event to disable
+        * interface.
+        */
+}
+
+
+static void wpa_driver_wext_rfkill_unblocked(void *ctx)
+{
+       struct wpa_driver_wext_data *drv = ctx;
+       wpa_printf(MSG_DEBUG, "WEXT: RFKILL unblocked");
+       if (linux_set_iface_flags(drv->ioctl_sock, drv->ifname, 1)) {
+               wpa_printf(MSG_DEBUG, "WEXT: Could not set interface UP "
+                          "after rfkill unblock");
+               return;
+       }
+       /* rtnetlink ifup handler will report interface as enabled */
+}
+
+
 /**
  * wpa_driver_wext_init - Initialize WE driver interface
  * @ctx: context to be used when calling wpa_supplicant functions,
@@ -697,6 +735,9 @@ void * wpa_driver_wext_init(void *ctx, const char *ifname)
 {
        struct wpa_driver_wext_data *drv;
        struct netlink_config *cfg;
+       struct rfkill_config *rcfg;
+       char path[128];
+       struct stat buf;
 
        drv = os_zalloc(sizeof(*drv));
        if (drv == NULL)
@@ -704,6 +745,12 @@ void * wpa_driver_wext_init(void *ctx, const char *ifname)
        drv->ctx = ctx;
        os_strlcpy(drv->ifname, ifname, sizeof(drv->ifname));
 
+       os_snprintf(path, sizeof(path), "/sys/class/net/%s/phy80211", ifname);
+       if (stat(path, &buf) == 0) {
+               wpa_printf(MSG_DEBUG, "WEXT: cfg80211-based driver detected");
+               drv->cfg80211 = 1;
+       }
+
        drv->ioctl_sock = socket(PF_INET, SOCK_DGRAM, 0);
        if (drv->ioctl_sock < 0) {
                perror("socket(PF_INET,SOCK_DGRAM)");
@@ -722,6 +769,19 @@ void * wpa_driver_wext_init(void *ctx, const char *ifname)
                goto err2;
        }
 
+       rcfg = os_zalloc(sizeof(*rcfg));
+       if (rcfg == NULL)
+               goto err3;
+       rcfg->ctx = drv;
+       os_strlcpy(rcfg->ifname, ifname, sizeof(rcfg->ifname));
+       rcfg->blocked_cb = wpa_driver_wext_rfkill_blocked;
+       rcfg->unblocked_cb = wpa_driver_wext_rfkill_unblocked;
+       drv->rfkill = rfkill_init(rcfg);
+       if (drv->rfkill == NULL) {
+               wpa_printf(MSG_DEBUG, "WEXT: RFKILL status not available");
+               os_free(rcfg);
+       }
+
        drv->mlme_sock = -1;
 
        if (wpa_driver_wext_finish_drv_init(drv) < 0)
@@ -732,6 +792,7 @@ void * wpa_driver_wext_init(void *ctx, const char *ifname)
        return drv;
 
 err3:
+       rfkill_deinit(drv->rfkill);
        netlink_deinit(drv->netlink);
 err2:
        close(drv->ioctl_sock);
@@ -741,10 +802,29 @@ err1:
 }
 
 
+static void wpa_driver_wext_send_rfkill(void *eloop_ctx, void *timeout_ctx)
+{
+       wpa_supplicant_event(timeout_ctx, EVENT_INTERFACE_DISABLED, NULL);
+}
+
+
 static int wpa_driver_wext_finish_drv_init(struct wpa_driver_wext_data *drv)
 {
-       if (linux_set_iface_flags(drv->ioctl_sock, drv->ifname, 1) < 0)
-               return -1;
+       int send_rfkill_event = 0;
+
+       if (linux_set_iface_flags(drv->ioctl_sock, drv->ifname, 1) < 0) {
+               if (rfkill_is_blocked(drv->rfkill)) {
+                       wpa_printf(MSG_DEBUG, "WEXT: Could not yet enable "
+                                  "interface '%s' due to rfkill",
+                                  drv->ifname);
+                       drv->if_disabled = 1;
+                       send_rfkill_event = 1;
+               } else {
+                       wpa_printf(MSG_ERROR, "WEXT: Could not set "
+                                  "interface '%s' UP", drv->ifname);
+                       return -1;
+               }
+       }
 
        /*
         * Make sure that the driver does not have any obsolete PMKID entries.
@@ -786,6 +866,11 @@ static int wpa_driver_wext_finish_drv_init(struct wpa_driver_wext_data *drv)
        netlink_send_oper_ifla(drv->netlink, drv->ifindex,
                               1, IF_OPER_DORMANT);
 
+       if (send_rfkill_event) {
+               eloop_register_timeout(0, 0, wpa_driver_wext_send_rfkill,
+                                      drv, drv->ctx);
+       }
+
        return 0;
 }
 
@@ -813,6 +898,7 @@ void wpa_driver_wext_deinit(void *priv)
 
        netlink_send_oper_ifla(drv->netlink, drv->ifindex, 0, IF_OPER_UP);
        netlink_deinit(drv->netlink);
+       rfkill_deinit(drv->rfkill);
 
        if (drv->mlme_sock >= 0)
                eloop_unregister_read_sock(drv->mlme_sock);
@@ -1031,7 +1117,8 @@ static void wext_get_scan_freq(struct iw_event *iwe,
 }
 
 
-static void wext_get_scan_qual(struct iw_event *iwe,
+static void wext_get_scan_qual(struct wpa_driver_wext_data *drv,
+                              struct iw_event *iwe,
                               struct wext_scan_data *res)
 {
        res->res.qual = iwe->u.qual.qual;
@@ -1045,6 +1132,14 @@ static void wext_get_scan_qual(struct iw_event *iwe,
                res->res.flags |= WPA_SCAN_NOISE_INVALID;
        if (iwe->u.qual.updated & IW_QUAL_DBM)
                res->res.flags |= WPA_SCAN_LEVEL_DBM;
+       if ((iwe->u.qual.updated & IW_QUAL_DBM) ||
+           ((iwe->u.qual.level != 0) &&
+            (iwe->u.qual.level > drv->max_level))) {
+               if (iwe->u.qual.level >= 64)
+                       res->res.level -= 0x100;
+               if (iwe->u.qual.noise >= 64)
+                       res->res.noise -= 0x100;
+       }
 }
 
 
@@ -1133,8 +1228,9 @@ static void wext_get_scan_custom(struct iw_event *iwe,
                tmp = os_realloc(res->ie, res->ie_len + bytes);
                if (tmp == NULL)
                        return;
-               hexstr2bin(spos, tmp + res->ie_len, bytes);
                res->ie = tmp;
+               if (hexstr2bin(spos, tmp + res->ie_len, bytes) < 0)
+                       return;
                res->ie_len += bytes;
        } else if (clen > 7 && os_strncmp(custom, "rsn_ie=", 7) == 0) {
                char *spos;
@@ -1147,8 +1243,9 @@ static void wext_get_scan_custom(struct iw_event *iwe,
                tmp = os_realloc(res->ie, res->ie_len + bytes);
                if (tmp == NULL)
                        return;
-               hexstr2bin(spos, tmp + res->ie_len, bytes);
                res->ie = tmp;
+               if (hexstr2bin(spos, tmp + res->ie_len, bytes) < 0)
+                       return;
                res->ie_len += bytes;
        } else if (clen > 4 && os_strncmp(custom, "tsf=", 4) == 0) {
                char *spos;
@@ -1161,7 +1258,10 @@ static void wext_get_scan_custom(struct iw_event *iwe,
                        return;
                }
                bytes /= 2;
-               hexstr2bin(spos, bin, bytes);
+               if (hexstr2bin(spos, bin, bytes) < 0) {
+                       wpa_printf(MSG_DEBUG, "WEXT: Invalid TSF value");
+                       return;
+               }
                res->res.tsf += WPA_GET_BE64(bin);
        }
 }
@@ -1315,7 +1415,7 @@ struct wpa_scan_results * wpa_driver_wext_get_scan_results(void *priv)
                        wext_get_scan_freq(iwe, &data);
                        break;
                case IWEVQUAL:
-                       wext_get_scan_qual(iwe, &data);
+                       wext_get_scan_qual(drv, iwe, &data);
                        break;
                case SIOCGIWENCODE:
                        wext_get_scan_encode(iwe, &data);
@@ -1413,6 +1513,8 @@ static int wpa_driver_wext_get_range(void *priv)
                           "assuming WPA is not supported");
        }
 
+       drv->max_level = range->max_qual.level;
+
        os_free(range);
        return 0;
 }
@@ -1704,6 +1806,19 @@ static void wpa_driver_wext_disconnect(struct wpa_driver_wext_data *drv)
        }
 
        if (iwr.u.mode == IW_MODE_INFRA) {
+               if (drv->cfg80211) {
+                       /*
+                        * cfg80211 supports SIOCSIWMLME commands, so there is
+                        * no need for the random SSID hack, but clear the
+                        * BSSID and SSID.
+                        */
+                       if (wpa_driver_wext_set_bssid(drv, null_bssid) < 0 ||
+                           wpa_driver_wext_set_ssid(drv, (u8 *) "", 0) < 0) {
+                               wpa_printf(MSG_DEBUG, "WEXT: Failed to clear "
+                                          "to disconnect");
+                       }
+                       return;
+               }
                /*
                 * Clear the BSSID selection and set a random SSID to make sure
                 * the driver will not be trying to associate with something
@@ -1853,6 +1968,14 @@ int wpa_driver_wext_associate(void *priv,
 
        wpa_printf(MSG_DEBUG, "%s", __FUNCTION__);
 
+       if (drv->cfg80211) {
+               /*
+                * Stop cfg80211 from trying to associate before we are done
+                * with all parameters.
+                */
+               wpa_driver_wext_set_ssid(drv, (u8 *) "", 0);
+       }
+
        if (wpa_driver_wext_set_drop_unencrypted(drv, params->drop_unencrypted)
            < 0)
                ret = -1;
@@ -1940,11 +2063,15 @@ int wpa_driver_wext_associate(void *priv,
 #endif /* CONFIG_IEEE80211W */
        if (params->freq && wpa_driver_wext_set_freq(drv, params->freq) < 0)
                ret = -1;
-       if (wpa_driver_wext_set_ssid(drv, params->ssid, params->ssid_len) < 0)
+       if (!drv->cfg80211 &&
+           wpa_driver_wext_set_ssid(drv, params->ssid, params->ssid_len) < 0)
                ret = -1;
        if (params->bssid &&
            wpa_driver_wext_set_bssid(drv, params->bssid) < 0)
                ret = -1;
+       if (drv->cfg80211 &&
+           wpa_driver_wext_set_ssid(drv, params->ssid, params->ssid_len) < 0)
+               ret = -1;
 
        return ret;
 }