wext: handle mode switches correctly for mac80211
[libeap.git] / src / drivers / driver_wext.c
index 69aae16..c9d7399 100644 (file)
@@ -631,9 +631,8 @@ static void wpa_driver_wext_event_wireless(struct wpa_driver_wext_data *drv,
                        wpa_printf(MSG_DEBUG, "Wireless event: new AP: "
                                   MACSTR,
                                   MAC2STR((u8 *) iwe->u.ap_addr.sa_data));
-                       if (os_memcmp(iwe->u.ap_addr.sa_data,
-                                     "\x00\x00\x00\x00\x00\x00", ETH_ALEN) ==
-                           0 ||
+                       if (is_zero_ether_addr(
+                                   (const u8 *) iwe->u.ap_addr.sa_data) ||
                            os_memcmp(iwe->u.ap_addr.sa_data,
                                      "\x44\x44\x44\x44\x44\x44", ETH_ALEN) ==
                            0) {
@@ -736,7 +735,7 @@ static void wpa_driver_wext_event_rtm_newlink(struct wpa_driver_wext_data *drv,
                   (ifi->ifi_flags & IFF_UP) ? "[UP]" : "",
                   (ifi->ifi_flags & IFF_RUNNING) ? "[RUNNING]" : "",
                   (ifi->ifi_flags & IFF_LOWER_UP) ? "[LOWER_UP]" : "",
-                  (ifi->ifi_flags & IFF_DORMANT) ? "[DORMANT" : "");
+                  (ifi->ifi_flags & IFF_DORMANT) ? "[DORMANT]" : "");
        /*
         * Some drivers send the association event before the operup event--in
         * this case, lifting operstate in wpa_driver_wext_set_operstate()
@@ -1294,8 +1293,15 @@ static void wext_get_scan_freq(struct iw_event *iwe,
                /*
                 * Some drivers do not report frequency, but a channel.
                 * Try to map this to frequency by assuming they are using
-                * IEEE 802.11b/g.
+                * IEEE 802.11b/g.  But don't overwrite a previously parsed
+                * frequency if the driver sends both frequency and channel,
+                * since the driver may be sending an A-band channel that we
+                * don't handle here.
                 */
+
+               if (res->res.freq)
+                       return;
+
                if (iwe->u.freq.m >= 1 && iwe->u.freq.m <= 13) {
                        res->res.freq = 2407 + 5 * iwe->u.freq.m;
                        return;
@@ -2145,17 +2151,54 @@ int wpa_driver_wext_set_mode(void *priv, int mode)
 {
        struct wpa_driver_wext_data *drv = priv;
        struct iwreq iwr;
-       int ret = 0;
+       int ret = -1, flags;
+       unsigned int new_mode = mode ? IW_MODE_ADHOC : IW_MODE_INFRA;
 
        os_memset(&iwr, 0, sizeof(iwr));
        os_strlcpy(iwr.ifr_name, drv->ifname, IFNAMSIZ);
-       iwr.u.mode = mode ? IW_MODE_ADHOC : IW_MODE_INFRA;
+       iwr.u.mode = new_mode;
+       if (ioctl(drv->ioctl_sock, SIOCSIWMODE, &iwr) == 0) {
+               ret = 0;
+               goto done;
+       }
 
-       if (ioctl(drv->ioctl_sock, SIOCSIWMODE, &iwr) < 0) {
+       if (errno != EBUSY) {
                perror("ioctl[SIOCSIWMODE]");
-               ret = -1;
+               goto done;
+       }
+
+       /* mac80211 doesn't allow mode changes while the device is up, so if
+        * the device isn't in the mode we're about to change to, take device
+        * down, try to set the mode again, and bring it back up.
+        */
+       if (ioctl(drv->ioctl_sock, SIOCGIWMODE, &iwr) < 0) {
+               perror("ioctl[SIOCGIWMODE]");
+               goto done;
+       }
+
+       if (iwr.u.mode == new_mode) {
+               ret = 0;
+               goto done;
+       }
+
+       if (wpa_driver_wext_get_ifflags(drv, &flags) == 0) {
+               (void) wpa_driver_wext_set_ifflags(drv, flags & ~IFF_UP);
+
+               /* Try to set the mode again while the interface is down */
+               iwr.u.mode = new_mode;
+               if (ioctl(drv->ioctl_sock, SIOCSIWMODE, &iwr) < 0)
+                       perror("ioctl[SIOCSIWMODE]");
+               else
+                       ret = 0;
+
+               /* Ignore return value of get_ifflags to ensure that the device
+                * is always up like it was before this function was called.
+                */
+               (void) wpa_driver_wext_get_ifflags(drv, &flags);
+               (void) wpa_driver_wext_set_ifflags(drv, flags | IFF_UP);
        }
 
+done:
        return ret;
 }