nl80211: Support network hierarchy of a master interface under bridge
[mech_eap.git] / src / drivers / driver_nl80211.c
index cdd1504..5fb6652 100644 (file)
@@ -181,9 +181,14 @@ static int nl80211_send_frame_cmd(struct i802_bss *bss,
 static int wpa_driver_nl80211_probe_req_report(struct i802_bss *bss,
                                               int report);
 
-static void add_ifidx(struct wpa_driver_nl80211_data *drv, int ifidx);
-static void del_ifidx(struct wpa_driver_nl80211_data *drv, int ifidx);
-static int have_ifidx(struct wpa_driver_nl80211_data *drv, int ifidx);
+#define IFIDX_ANY -1
+
+static void add_ifidx(struct wpa_driver_nl80211_data *drv, int ifidx,
+                     int ifidx_reason);
+static void del_ifidx(struct wpa_driver_nl80211_data *drv, int ifidx,
+                     int ifidx_reason);
+static int have_ifidx(struct wpa_driver_nl80211_data *drv, int ifidx,
+                     int ifidx_reason);
 
 static int nl80211_set_channel(struct i802_bss *bss,
                               struct hostapd_freq_params *freq, int set_chan);
@@ -883,7 +888,7 @@ nl80211_find_drv(struct nl80211_global *global, int idx, u8 *buf, size_t len)
        dl_list_for_each(drv, &global->interfaces,
                         struct wpa_driver_nl80211_data, list) {
                if (wpa_driver_nl80211_own_ifindex(drv, idx, buf, len) ||
-                   have_ifidx(drv, idx))
+                   have_ifidx(drv, idx, IFIDX_ANY))
                        return drv;
        }
        return NULL;
@@ -1062,7 +1067,7 @@ static void wpa_driver_nl80211_event_rtm_newlink(void *ctx,
                }
                wpa_printf(MSG_DEBUG, "nl80211: Add ifindex %u for bridge %s",
                           brid, namebuf);
-               add_ifidx(drv, brid);
+               add_ifidx(drv, brid, ifi->ifi_index);
 
                for (bss = drv->first_bss; bss; bss = bss->next) {
                        if (os_strcmp(ifname, bss->ifname) == 0) {
@@ -1149,7 +1154,7 @@ static void wpa_driver_nl80211_event_rtm_dellink(void *ctx,
                                   "nl80211: Remove ifindex %u for bridge %s",
                                   brid, namebuf);
                }
-               del_ifidx(drv, brid);
+               del_ifidx(drv, brid, ifi->ifi_index);
        }
 }
 
@@ -1716,6 +1721,7 @@ static void * wpa_driver_nl80211_drv_init(void *ctx, const char *ifname,
 
        drv->num_if_indices = sizeof(drv->default_if_indices) / sizeof(int);
        drv->if_indices = drv->default_if_indices;
+       drv->if_indices_reason = drv->default_if_indices_reason;
 
        drv->first_bss = os_zalloc(sizeof(*drv->first_bss));
        if (!drv->first_bss) {
@@ -2390,6 +2396,9 @@ static void wpa_driver_nl80211_deinit(struct i802_bss *bss)
        if (drv->if_indices != drv->default_if_indices)
                os_free(drv->if_indices);
 
+       if (drv->if_indices_reason != drv->default_if_indices_reason)
+               os_free(drv->if_indices_reason);
+
        if (drv->disabled_11b_rates)
                nl80211_disable_11b_rates(drv, drv->ifindex, 0);
 
@@ -3584,6 +3593,12 @@ static int wpa_driver_nl80211_set_ap(void *priv,
        }
 #endif /* CONFIG_P2P */
 
+       if (params->pbss) {
+               wpa_printf(MSG_DEBUG, "nl80211: PBSS");
+               if (nla_put_flag(msg, NL80211_ATTR_PBSS))
+                       goto fail;
+       }
+
        ret = send_and_recv_msgs(drv, msg, NULL, NULL);
        if (ret) {
                wpa_printf(MSG_DEBUG, "nl80211: Beacon set failed: %d (%s)",
@@ -3742,6 +3757,8 @@ static u32 sta_flags_nl80211(int flags)
                f |= BIT(NL80211_STA_FLAG_TDLS_PEER);
        if (flags & WPA_STA_AUTHENTICATED)
                f |= BIT(NL80211_STA_FLAG_AUTHENTICATED);
+       if (flags & WPA_STA_ASSOCIATED)
+               f |= BIT(NL80211_STA_FLAG_ASSOCIATED);
 
        return f;
 }
@@ -3794,7 +3811,17 @@ static int wpa_driver_nl80211_sta_add(void *priv,
        if (!msg || nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, params->addr))
                goto fail;
 
-       if (!params->set || (params->flags & WPA_STA_TDLS_PEER)) {
+       /*
+        * Set the below properties only in one of the following cases:
+        * 1. New station is added, already associated.
+        * 2. Set WPA_STA_TDLS_PEER station.
+        * 3. Set an already added unassociated station, if driver supports
+        * full AP client state. (Set these properties after station became
+        * associated will be rejected by the driver).
+        */
+       if (!params->set || (params->flags & WPA_STA_TDLS_PEER) ||
+           (params->set && FULL_AP_CLIENT_STATE_SUPP(drv->capa.flags) &&
+            (params->flags & WPA_STA_ASSOCIATED))) {
                wpa_hexdump(MSG_DEBUG, "  * supported rates",
                            params->supp_rates, params->supp_rates_len);
                wpa_printf(MSG_DEBUG, "  * capability=0x%x",
@@ -3842,9 +3869,12 @@ static int wpa_driver_nl80211_sta_add(void *priv,
                        /*
                         * cfg80211 validates that AID is non-zero, so we have
                         * to make this a non-zero value for the TDLS case where
-                        * a dummy STA entry is used for now.
+                        * a dummy STA entry is used for now and for a station
+                        * that is still not associated.
                         */
-                       wpa_printf(MSG_DEBUG, "  * aid=1 (TDLS workaround)");
+                       wpa_printf(MSG_DEBUG, "  * aid=1 (%s workaround)",
+                                  (params->flags & WPA_STA_TDLS_PEER) ?
+                                  "TDLS" : "UNASSOC_STA");
                        if (nla_put_u16(msg, NL80211_ATTR_STA_AID, 1))
                                goto fail;
                }
@@ -3857,6 +3887,15 @@ static int wpa_driver_nl80211_sta_add(void *priv,
                wpa_printf(MSG_DEBUG, "  * peer_aid=%u", params->aid);
                if (nla_put_u16(msg, NL80211_ATTR_PEER_AID, params->aid))
                        goto fail;
+       } else if (FULL_AP_CLIENT_STATE_SUPP(drv->capa.flags) &&
+                  (params->flags & WPA_STA_ASSOCIATED)) {
+               wpa_printf(MSG_DEBUG, "  * aid=%u", params->aid);
+               wpa_printf(MSG_DEBUG, "  * listen_interval=%u",
+                          params->listen_interval);
+               if (nla_put_u16(msg, NL80211_ATTR_STA_AID, params->aid) ||
+                   nla_put_u16(msg, NL80211_ATTR_STA_LISTEN_INTERVAL,
+                               params->listen_interval))
+                       goto fail;
        }
 
        if (params->vht_opmode_enabled) {
@@ -3887,6 +3926,36 @@ static int wpa_driver_nl80211_sta_add(void *priv,
        os_memset(&upd, 0, sizeof(upd));
        upd.set = sta_flags_nl80211(params->flags);
        upd.mask = upd.set | sta_flags_nl80211(params->flags_mask);
+
+       /*
+        * If the driver doesn't support full AP client state, ignore ASSOC/AUTH
+        * flags, as nl80211 driver moves a new station, by default, into
+        * associated state.
+        *
+        * On the other hand, if the driver supports that feature and the
+        * station is added in unauthenticated state, set the
+        * authenticated/associated bits in the mask to prevent moving this
+        * station to associated state before it is actually associated.
+        *
+        * This is irrelevant for mesh mode where the station is added to the
+        * driver as authenticated already, and ASSOCIATED isn't part of the
+        * nl80211 API.
+        */
+       if (!is_mesh_interface(drv->nlmode)) {
+               if (!FULL_AP_CLIENT_STATE_SUPP(drv->capa.flags)) {
+                       wpa_printf(MSG_DEBUG,
+                                  "nl80211: Ignore ASSOC/AUTH flags since driver doesn't support full AP client state");
+                       upd.mask &= ~(BIT(NL80211_STA_FLAG_ASSOCIATED) |
+                                     BIT(NL80211_STA_FLAG_AUTHENTICATED));
+               } else if (!params->set &&
+                          !(params->flags & WPA_STA_TDLS_PEER)) {
+                       if (!(params->flags & WPA_STA_AUTHENTICATED))
+                               upd.mask |= BIT(NL80211_STA_FLAG_AUTHENTICATED);
+                       if (!(params->flags & WPA_STA_ASSOCIATED))
+                               upd.mask |= BIT(NL80211_STA_FLAG_ASSOCIATED);
+               }
+       }
+
        wpa_printf(MSG_DEBUG, "  * flags set=0x%x mask=0x%x",
                   upd.set, upd.mask);
        if (nla_put(msg, NL80211_ATTR_STA_FLAGS2, sizeof(upd), &upd))
@@ -4009,7 +4078,11 @@ void nl80211_remove_iface(struct wpa_driver_nl80211_data *drv, int ifidx)
        /* stop listening for EAPOL on this interface */
        dl_list_for_each(drv2, &drv->global->interfaces,
                         struct wpa_driver_nl80211_data, list)
-               del_ifidx(drv2, ifidx);
+       {
+               del_ifidx(drv2, ifidx, IFIDX_ANY);
+               /* Remove all bridges learned for this iface */
+               del_ifidx(drv2, IFIDX_ANY, ifidx);
+       }
 
        msg = nl80211_ifindex_msg(drv, ifidx, 0, NL80211_CMD_DEL_INTERFACE);
        if (send_and_recv_msgs(drv, msg, NULL, NULL) == 0)
@@ -4117,7 +4190,7 @@ static int nl80211_create_iface_once(struct wpa_driver_nl80211_data *drv,
            iftype == NL80211_IFTYPE_WDS ||
            iftype == NL80211_IFTYPE_MONITOR) {
                /* start listening for EAPOL on this interface */
-               add_ifidx(drv, ifidx);
+               add_ifidx(drv, ifidx, IFIDX_ANY);
        }
 
        if (addr && iftype != NL80211_IFTYPE_MONITOR &&
@@ -4549,8 +4622,9 @@ retry:
                        goto fail;
        }
 
-       if (nl80211_ht_vht_overrides(msg, params) < 0)
-               return -1;
+       ret = nl80211_ht_vht_overrides(msg, params);
+       if (ret < 0)
+               goto fail;
 
        ret = send_and_recv_msgs(drv, msg, NULL, NULL);
        msg = NULL;
@@ -4743,6 +4817,12 @@ static int nl80211_connect_common(struct wpa_driver_nl80211_data *drv,
        if (params->p2p)
                wpa_printf(MSG_DEBUG, "  * P2P group");
 
+       if (params->pbss) {
+               wpa_printf(MSG_DEBUG, "  * PBSS");
+               if (nla_put_flag(msg, NL80211_ATTR_PBSS))
+                       return -1;
+       }
+
        return 0;
 }
 
@@ -5314,6 +5394,8 @@ static int get_sta_handler(struct nl_msg *msg, void *arg)
                [NL80211_STA_INFO_RX_PACKETS] = { .type = NLA_U32 },
                [NL80211_STA_INFO_TX_PACKETS] = { .type = NLA_U32 },
                [NL80211_STA_INFO_TX_FAILED] = { .type = NLA_U32 },
+               [NL80211_STA_INFO_RX_BYTES64] = { .type = NLA_U64 },
+               [NL80211_STA_INFO_TX_BYTES64] = { .type = NLA_U64 },
        };
 
        nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
@@ -5339,10 +5421,23 @@ static int get_sta_handler(struct nl_msg *msg, void *arg)
        if (stats[NL80211_STA_INFO_INACTIVE_TIME])
                data->inactive_msec =
                        nla_get_u32(stats[NL80211_STA_INFO_INACTIVE_TIME]);
+       /* For backwards compatibility, fetch the 32-bit counters first. */
        if (stats[NL80211_STA_INFO_RX_BYTES])
                data->rx_bytes = nla_get_u32(stats[NL80211_STA_INFO_RX_BYTES]);
        if (stats[NL80211_STA_INFO_TX_BYTES])
                data->tx_bytes = nla_get_u32(stats[NL80211_STA_INFO_TX_BYTES]);
+       if (stats[NL80211_STA_INFO_RX_BYTES64] &&
+           stats[NL80211_STA_INFO_TX_BYTES64]) {
+               /*
+                * The driver supports 64-bit counters, so use them to override
+                * the 32-bit values.
+                */
+               data->rx_bytes =
+                       nla_get_u64(stats[NL80211_STA_INFO_RX_BYTES64]);
+               data->tx_bytes =
+                       nla_get_u64(stats[NL80211_STA_INFO_TX_BYTES64]);
+               data->bytes_64bit = 1;
+       }
        if (stats[NL80211_STA_INFO_RX_PACKETS])
                data->rx_packets =
                        nla_get_u32(stats[NL80211_STA_INFO_RX_PACKETS]);
@@ -5553,7 +5648,9 @@ static void dump_ifidx(struct wpa_driver_nl80211_data *drv)
        for (i = 0; i < drv->num_if_indices; i++) {
                if (!drv->if_indices[i])
                        continue;
-               res = os_snprintf(pos, end - pos, " %d", drv->if_indices[i]);
+               res = os_snprintf(pos, end - pos, " %d(%d)",
+                                 drv->if_indices[i],
+                                 drv->if_indices_reason[i]);
                if (os_snprintf_error(end - pos, res))
                        break;
                pos += res;
@@ -5565,14 +5662,16 @@ static void dump_ifidx(struct wpa_driver_nl80211_data *drv)
 }
 
 
-static void add_ifidx(struct wpa_driver_nl80211_data *drv, int ifidx)
+static void add_ifidx(struct wpa_driver_nl80211_data *drv, int ifidx,
+                     int ifidx_reason)
 {
        int i;
-       int *old;
+       int *old, *old_reason;
 
-       wpa_printf(MSG_DEBUG, "nl80211: Add own interface ifindex %d",
-                  ifidx);
-       if (have_ifidx(drv, ifidx)) {
+       wpa_printf(MSG_DEBUG,
+                  "nl80211: Add own interface ifindex %d (ifidx_reason %d)",
+                  ifidx, ifidx_reason);
+       if (have_ifidx(drv, ifidx, ifidx_reason)) {
                wpa_printf(MSG_DEBUG, "nl80211: ifindex %d already in the list",
                           ifidx);
                return;
@@ -5580,6 +5679,7 @@ static void add_ifidx(struct wpa_driver_nl80211_data *drv, int ifidx)
        for (i = 0; i < drv->num_if_indices; i++) {
                if (drv->if_indices[i] == 0) {
                        drv->if_indices[i] = ifidx;
+                       drv->if_indices_reason[i] = ifidx_reason;
                        dump_ifidx(drv);
                        return;
                }
@@ -5590,32 +5690,57 @@ static void add_ifidx(struct wpa_driver_nl80211_data *drv, int ifidx)
        else
                old = NULL;
 
+       if (drv->if_indices_reason != drv->default_if_indices_reason)
+               old_reason = drv->if_indices_reason;
+       else
+               old_reason = NULL;
+
        drv->if_indices = os_realloc_array(old, drv->num_if_indices + 1,
                                           sizeof(int));
+       drv->if_indices_reason = os_realloc_array(old_reason,
+                                                 drv->num_if_indices + 1,
+                                                 sizeof(int));
        if (!drv->if_indices) {
                if (!old)
                        drv->if_indices = drv->default_if_indices;
                else
                        drv->if_indices = old;
+       }
+       if (!drv->if_indices_reason) {
+               if (!old_reason)
+                       drv->if_indices_reason = drv->default_if_indices_reason;
+               else
+                       drv->if_indices = old_reason;
+       }
+       if (!drv->if_indices || !drv->if_indices_reason) {
                wpa_printf(MSG_ERROR, "Failed to reallocate memory for "
                           "interfaces");
                wpa_printf(MSG_ERROR, "Ignoring EAPOL on interface %d", ifidx);
                return;
-       } else if (!old)
+       }
+       if (!old)
                os_memcpy(drv->if_indices, drv->default_if_indices,
                          sizeof(drv->default_if_indices));
+       if (!old_reason)
+               os_memcpy(drv->if_indices_reason,
+                         drv->default_if_indices_reason,
+                         sizeof(drv->default_if_indices_reason));
        drv->if_indices[drv->num_if_indices] = ifidx;
+       drv->if_indices_reason[drv->num_if_indices] = ifidx_reason;
        drv->num_if_indices++;
        dump_ifidx(drv);
 }
 
 
-static void del_ifidx(struct wpa_driver_nl80211_data *drv, int ifidx)
+static void del_ifidx(struct wpa_driver_nl80211_data *drv, int ifidx,
+                     int ifidx_reason)
 {
        int i;
 
        for (i = 0; i < drv->num_if_indices; i++) {
-               if (drv->if_indices[i] == ifidx) {
+               if ((drv->if_indices[i] == ifidx || ifidx == IFIDX_ANY) &&
+                   (drv->if_indices_reason[i] == ifidx_reason ||
+                    ifidx_reason == IFIDX_ANY)) {
                        drv->if_indices[i] = 0;
                        break;
                }
@@ -5624,12 +5749,15 @@ static void del_ifidx(struct wpa_driver_nl80211_data *drv, int ifidx)
 }
 
 
-static int have_ifidx(struct wpa_driver_nl80211_data *drv, int ifidx)
+static int have_ifidx(struct wpa_driver_nl80211_data *drv, int ifidx,
+                     int ifidx_reason)
 {
        int i;
 
        for (i = 0; i < drv->num_if_indices; i++)
-               if (drv->if_indices[i] == ifidx)
+               if (drv->if_indices[i] == ifidx &&
+                   (drv->if_indices_reason[i] == ifidx_reason ||
+                    ifidx_reason == IFIDX_ANY))
                        return 1;
 
        return 0;
@@ -5694,7 +5822,7 @@ static void handle_eapol(int sock, void *eloop_ctx, void *sock_ctx)
                return;
        }
 
-       if (have_ifidx(drv, lladdr.sll_ifindex))
+       if (have_ifidx(drv, lladdr.sll_ifindex, IFIDX_ANY))
                drv_event_eapol_rx(drv->ctx, lladdr.sll_addr, buf, len);
 }
 
@@ -5721,7 +5849,7 @@ static int i802_check_bridge(struct wpa_driver_nl80211_data *drv,
                }
                bss->added_bridge = 1;
                br_ifindex = if_nametoindex(brname);
-               add_ifidx(drv, br_ifindex);
+               add_ifidx(drv, br_ifindex, drv->ifindex);
        }
        bss->br_ifindex = br_ifindex;
 
@@ -5783,7 +5911,15 @@ static void *i802_init(struct hostapd_data *hapd,
                wpa_printf(MSG_DEBUG, "nl80211: Interface %s is in master %s",
                        params->ifname, master_ifname);
                /* start listening for EAPOL on the master interface */
-               add_ifidx(drv, if_nametoindex(master_ifname));
+               add_ifidx(drv, if_nametoindex(master_ifname), drv->ifindex);
+
+               /* check if master itself is under bridge */
+               if (linux_br_get(master_ifname, master_ifname) == 0) {
+                       wpa_printf(MSG_DEBUG, "nl80211: which is in bridge %s",
+                                  master_ifname);
+                       br_ifindex = if_nametoindex(master_ifname);
+                       os_strlcpy(bss->brname, master_ifname, IFNAMSIZ);
+               }
        } else {
                master_ifname[0] = '\0';
        }
@@ -5794,14 +5930,14 @@ static void *i802_init(struct hostapd_data *hapd,
                if (params->bridge[i]) {
                        ifindex = if_nametoindex(params->bridge[i]);
                        if (ifindex)
-                               add_ifidx(drv, ifindex);
+                               add_ifidx(drv, ifindex, drv->ifindex);
                        if (ifindex == br_ifindex)
                                br_added = 1;
                }
        }
 
        /* start listening for EAPOL on the default AP interface */
-       add_ifidx(drv, drv->ifindex);
+       add_ifidx(drv, drv->ifindex, IFIDX_ANY);
 
        if (params->num_bridge && params->bridge[0]) {
                if (i802_check_bridge(drv, bss, params->bridge[0],
@@ -5813,7 +5949,7 @@ static void *i802_init(struct hostapd_data *hapd,
 
        if (!br_added && br_ifindex &&
            (params->num_bridge == 0 || !params->bridge[0]))
-               add_ifidx(drv, br_ifindex);
+               add_ifidx(drv, br_ifindex, drv->ifindex);
 
 #ifdef CONFIG_LIBNL3_ROUTE
        if (bss->added_if_into_bridge) {
@@ -6099,7 +6235,7 @@ static int wpa_driver_nl80211_if_add(void *priv, enum wpa_driver_if_type type,
             nlmode == NL80211_IFTYPE_AP_VLAN ||
             nlmode == NL80211_IFTYPE_WDS ||
             nlmode == NL80211_IFTYPE_MONITOR))
-               add_ifidx(drv, ifidx);
+               add_ifidx(drv, ifidx, IFIDX_ANY);
 
        return 0;
 }
@@ -6119,8 +6255,10 @@ static int wpa_driver_nl80211_if_remove(struct i802_bss *bss,
        else if (ifindex > 0 && !bss->added_if) {
                struct wpa_driver_nl80211_data *drv2;
                dl_list_for_each(drv2, &drv->global->interfaces,
-                                struct wpa_driver_nl80211_data, list)
-                       del_ifidx(drv2, ifindex);
+                                struct wpa_driver_nl80211_data, list) {
+                       del_ifidx(drv2, ifindex, IFIDX_ANY);
+                       del_ifidx(drv2, IFIDX_ANY, ifindex);
+               }
        }
 
        if (type != WPA_IF_AP_BSS)
@@ -6678,9 +6816,9 @@ static int nl80211_send_frame(void *priv, const u8 *data, size_t data_len,
 
 static int nl80211_set_param(void *priv, const char *param)
 {
-       wpa_printf(MSG_DEBUG, "nl80211: driver param='%s'", param);
        if (param == NULL)
                return 0;
+       wpa_printf(MSG_DEBUG, "nl80211: driver param='%s'", param);
 
 #ifdef CONFIG_P2P
        if (os_strstr(param, "use_p2p_group_interface=1")) {