mac80211: accept public action frames with mismatched BSSID
[pandora-kernel.git] / net / mac80211 / rx.c
index c74e542..7d22641 100644 (file)
@@ -16,6 +16,7 @@
 #include <linux/netdevice.h>
 #include <linux/etherdevice.h>
 #include <linux/rcupdate.h>
+#include <linux/export.h>
 #include <net/mac80211.h>
 #include <net/ieee80211_radiotap.h>
 
@@ -140,8 +141,9 @@ ieee80211_add_rx_radiotap_header(struct ieee80211_local *local,
        pos++;
 
        /* IEEE80211_RADIOTAP_RATE */
-       if (status->flag & RX_FLAG_HT) {
+       if (!rate || status->flag & RX_FLAG_HT) {
                /*
+                * Without rate information don't add it. If we have,
                 * MCS information is a separate field in radiotap,
                 * added below. The byte here is needed as padding
                 * for the channel though, so initialise it to 0.
@@ -162,12 +164,14 @@ ieee80211_add_rx_radiotap_header(struct ieee80211_local *local,
        else if (status->flag & RX_FLAG_HT)
                put_unaligned_le16(IEEE80211_CHAN_DYN | IEEE80211_CHAN_2GHZ,
                                   pos);
-       else if (rate->flags & IEEE80211_RATE_ERP_G)
+       else if (rate && rate->flags & IEEE80211_RATE_ERP_G)
                put_unaligned_le16(IEEE80211_CHAN_OFDM | IEEE80211_CHAN_2GHZ,
                                   pos);
-       else
+       else if (rate)
                put_unaligned_le16(IEEE80211_CHAN_CCK | IEEE80211_CHAN_2GHZ,
                                   pos);
+       else
+               put_unaligned_le16(IEEE80211_CHAN_2GHZ, pos);
        pos += 2;
 
        /* IEEE80211_RADIOTAP_DBM_ANTSIGNAL */
@@ -744,10 +748,11 @@ static void ieee80211_rx_reorder_ampdu(struct ieee80211_rx_data *rx)
        struct ieee80211_local *local = rx->local;
        struct ieee80211_hw *hw = &local->hw;
        struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data;
+       struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
        struct sta_info *sta = rx->sta;
        struct tid_ampdu_rx *tid_agg_rx;
        u16 sc;
-       int tid;
+       u8 tid, ack_policy;
 
        if (!ieee80211_is_data_qos(hdr->frame_control))
                goto dont_reorder;
@@ -760,6 +765,8 @@ static void ieee80211_rx_reorder_ampdu(struct ieee80211_rx_data *rx)
        if (!sta)
                goto dont_reorder;
 
+       ack_policy = *ieee80211_get_qos_ctl(hdr) &
+                    IEEE80211_QOS_CTL_ACK_POLICY_MASK;
        tid = *ieee80211_get_qos_ctl(hdr) & IEEE80211_QOS_CTL_TID_MASK;
 
        tid_agg_rx = rcu_dereference(sta->ampdu_mlme.tid_rx[tid]);
@@ -770,6 +777,15 @@ static void ieee80211_rx_reorder_ampdu(struct ieee80211_rx_data *rx)
        if (unlikely(hdr->frame_control & cpu_to_le16(IEEE80211_STYPE_NULLFUNC)))
                goto dont_reorder;
 
+       /* not part of a BA session */
+       if (ack_policy != IEEE80211_QOS_CTL_ACK_POLICY_BLOCKACK &&
+           ack_policy != IEEE80211_QOS_CTL_ACK_POLICY_NORMAL)
+               goto dont_reorder;
+
+       /* not actually part of this BA session */
+       if (!(status->rx_flags & IEEE80211_RX_RA_MATCH))
+               goto dont_reorder;
+
        /* new, potentially un-ordered, ampdu frame - process it */
 
        /* reset session timer */
@@ -854,6 +870,13 @@ ieee80211_rx_h_check(struct ieee80211_rx_data *rx)
                            rx->sdata->control_port_protocol)
                                return RX_CONTINUE;
                }
+
+               if (rx->sdata->vif.type == NL80211_IFTYPE_AP &&
+                   cfg80211_rx_spurious_frame(rx->sdata->dev,
+                                              hdr->addr2,
+                                              GFP_ATOMIC))
+                       return RX_DROP_UNUSABLE;
+
                return RX_DROP_MONITOR;
        }
 
@@ -1323,15 +1346,20 @@ ieee80211_rx_h_sta_process(struct ieee80211_rx_data *rx)
 
                /*
                 * If we receive a 4-addr nullfunc frame from a STA
-                * that was not moved to a 4-addr STA vlan yet, drop
-                * the frame to the monitor interface, to make sure
-                * that hostapd sees it
+                * that was not moved to a 4-addr STA vlan yet send
+                * the event to userspace and for older hostapd drop
+                * the frame to the monitor interface.
                 */
                if (ieee80211_has_a4(hdr->frame_control) &&
                    (rx->sdata->vif.type == NL80211_IFTYPE_AP ||
                     (rx->sdata->vif.type == NL80211_IFTYPE_AP_VLAN &&
-                     !rx->sdata->u.vlan.sta)))
+                     !rx->sdata->u.vlan.sta))) {
+                       if (!test_and_set_sta_flag(sta, WLAN_STA_4ADDR_EVENT))
+                               cfg80211_rx_unexpected_4addr_frame(
+                                       rx->sdata->dev, sta->sta.addr,
+                                       GFP_ATOMIC);
                        return RX_DROP_MONITOR;
+               }
                /*
                 * Update counter and free packet here to avoid
                 * counting this as a dropped packed.
@@ -1867,13 +1895,16 @@ ieee80211_rx_h_amsdu(struct ieee80211_rx_data *rx)
 static ieee80211_rx_result
 ieee80211_rx_h_mesh_fwding(struct ieee80211_rx_data *rx)
 {
-       struct ieee80211_hdr *hdr;
+       struct ieee80211_hdr *fwd_hdr, *hdr;
+       struct ieee80211_tx_info *info;
        struct ieee80211s_hdr *mesh_hdr;
-       unsigned int hdrlen;
        struct sk_buff *skb = rx->skb, *fwd_skb;
        struct ieee80211_local *local = rx->local;
        struct ieee80211_sub_if_data *sdata = rx->sdata;
        struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
+       struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh;
+       __le16 reason = cpu_to_le16(WLAN_REASON_MESH_PATH_NOFORWARD);
+       u16 q, hdrlen;
 
        hdr = (struct ieee80211_hdr *) skb->data;
        hdrlen = ieee80211_hdrlen(hdr->frame_control);
@@ -1889,14 +1920,7 @@ ieee80211_rx_h_mesh_fwding(struct ieee80211_rx_data *rx)
                return RX_CONTINUE;
 
        if (!mesh_hdr->ttl)
-               /* illegal frame */
-               return RX_DROP_MONITOR;
-
-       if (ieee80211_queue_stopped(&local->hw, skb_get_queue_mapping(skb))) {
-               IEEE80211_IFSTA_MESH_CTR_INC(&sdata->u.mesh,
-                                               dropped_frames_congestion);
                return RX_DROP_MONITOR;
-       }
 
        if (mesh_hdr->flags & MESH_FLAGS_AE) {
                struct mesh_path *mppath;
@@ -1929,60 +1953,50 @@ ieee80211_rx_h_mesh_fwding(struct ieee80211_rx_data *rx)
            compare_ether_addr(sdata->vif.addr, hdr->addr3) == 0)
                return RX_CONTINUE;
 
-       mesh_hdr->ttl--;
+       q = ieee80211_select_queue_80211(local, skb, hdr);
+       if (ieee80211_queue_stopped(&local->hw, q)) {
+               IEEE80211_IFSTA_MESH_CTR_INC(ifmsh, dropped_frames_congestion);
+               return RX_DROP_MONITOR;
+       }
+       skb_set_queue_mapping(skb, q);
 
-       if (status->rx_flags & IEEE80211_RX_RA_MATCH) {
-               if (!mesh_hdr->ttl)
-                       IEEE80211_IFSTA_MESH_CTR_INC(&rx->sdata->u.mesh,
-                                                    dropped_frames_ttl);
-               else {
-                       struct ieee80211_hdr *fwd_hdr;
-                       struct ieee80211_tx_info *info;
-
-                       fwd_skb = skb_copy(skb, GFP_ATOMIC);
-
-                       if (!fwd_skb && net_ratelimit())
-                               printk(KERN_DEBUG "%s: failed to clone mesh frame\n",
-                                                  sdata->name);
-                       if (!fwd_skb)
-                               goto out;
-
-                       fwd_hdr =  (struct ieee80211_hdr *) fwd_skb->data;
-                       memcpy(fwd_hdr->addr2, sdata->vif.addr, ETH_ALEN);
-                       info = IEEE80211_SKB_CB(fwd_skb);
-                       memset(info, 0, sizeof(*info));
-                       info->flags |= IEEE80211_TX_INTFL_NEED_TXPROCESSING;
-                       info->control.vif = &rx->sdata->vif;
-                       if (is_multicast_ether_addr(fwd_hdr->addr1)) {
-                               IEEE80211_IFSTA_MESH_CTR_INC(&sdata->u.mesh,
-                                                               fwded_mcast);
-                               skb_set_queue_mapping(fwd_skb,
-                                       ieee80211_select_queue(sdata, fwd_skb));
-                               ieee80211_set_qos_hdr(sdata, fwd_skb);
-                       } else {
-                               int err;
-                               /*
-                                * Save TA to addr1 to send TA a path error if a
-                                * suitable next hop is not found
-                                */
-                               memcpy(fwd_hdr->addr1, fwd_hdr->addr2,
-                                               ETH_ALEN);
-                               err = mesh_nexthop_lookup(fwd_skb, sdata);
-                               /* Failed to immediately resolve next hop:
-                                * fwded frame was dropped or will be added
-                                * later to the pending skb queue.  */
-                               if (err)
-                                       return RX_DROP_MONITOR;
-
-                               IEEE80211_IFSTA_MESH_CTR_INC(&sdata->u.mesh,
-                                                               fwded_unicast);
-                       }
-                       IEEE80211_IFSTA_MESH_CTR_INC(&sdata->u.mesh,
-                                                    fwded_frames);
-                       ieee80211_add_pending_skb(local, fwd_skb);
-               }
+       if (!(status->rx_flags & IEEE80211_RX_RA_MATCH))
+               goto out;
+
+       if (!--mesh_hdr->ttl) {
+               IEEE80211_IFSTA_MESH_CTR_INC(ifmsh, dropped_frames_ttl);
+               return RX_DROP_MONITOR;
+       }
+
+       fwd_skb = skb_copy(skb, GFP_ATOMIC);
+       if (!fwd_skb) {
+               if (net_ratelimit())
+                       printk(KERN_DEBUG "%s: failed to clone mesh frame\n",
+                                       sdata->name);
+               goto out;
        }
 
+       fwd_hdr =  (struct ieee80211_hdr *) fwd_skb->data;
+       info = IEEE80211_SKB_CB(fwd_skb);
+       memset(info, 0, sizeof(*info));
+       info->flags |= IEEE80211_TX_INTFL_NEED_TXPROCESSING;
+       info->control.vif = &rx->sdata->vif;
+       info->control.jiffies = jiffies;
+       if (is_multicast_ether_addr(fwd_hdr->addr1)) {
+               IEEE80211_IFSTA_MESH_CTR_INC(ifmsh, fwded_mcast);
+               memcpy(fwd_hdr->addr2, sdata->vif.addr, ETH_ALEN);
+       } else if (!mesh_nexthop_lookup(fwd_skb, sdata)) {
+               IEEE80211_IFSTA_MESH_CTR_INC(ifmsh, fwded_unicast);
+       } else {
+               /* unable to resolve next hop */
+               mesh_path_error_tx(ifmsh->mshcfg.element_ttl, fwd_hdr->addr3,
+                                   0, reason, fwd_hdr->addr2, sdata);
+               IEEE80211_IFSTA_MESH_CTR_INC(ifmsh, dropped_frames_no_route);
+               return RX_DROP_MONITOR;
+       }
+
+       IEEE80211_IFSTA_MESH_CTR_INC(ifmsh, fwded_frames);
+       ieee80211_add_pending_skb(local, fwd_skb);
  out:
        if (is_multicast_ether_addr(hdr->addr1) ||
            sdata->dev->flags & IFF_PROMISC)
@@ -2010,12 +2024,17 @@ ieee80211_rx_h_data(struct ieee80211_rx_data *rx)
                return RX_DROP_MONITOR;
 
        /*
-        * Allow the cooked monitor interface of an AP to see 4-addr frames so
-        * that a 4-addr station can be detected and moved into a separate VLAN
+        * Send unexpected-4addr-frame event to hostapd. For older versions,
+        * also drop the frame to cooked monitor interfaces.
         */
        if (ieee80211_has_a4(hdr->frame_control) &&
-           sdata->vif.type == NL80211_IFTYPE_AP)
+           sdata->vif.type == NL80211_IFTYPE_AP) {
+               if (rx->sta &&
+                   !test_and_set_sta_flag(rx->sta, WLAN_STA_4ADDR_EVENT))
+                       cfg80211_rx_unexpected_4addr_frame(
+                               rx->sdata->dev, rx->sta->sta.addr, GFP_ATOMIC);
                return RX_DROP_MONITOR;
+       }
 
        err = __ieee80211_data_to_8023(rx, &port_control);
        if (unlikely(err))
@@ -2170,6 +2189,18 @@ ieee80211_rx_h_mgmt_check(struct ieee80211_rx_data *rx)
        if (!ieee80211_is_mgmt(mgmt->frame_control))
                return RX_DROP_MONITOR;
 
+       if (rx->sdata->vif.type == NL80211_IFTYPE_AP &&
+           ieee80211_is_beacon(mgmt->frame_control) &&
+           !(rx->flags & IEEE80211_RX_BEACON_REPORTED)) {
+               struct ieee80211_rx_status *status;
+
+               status = IEEE80211_SKB_RXCB(rx->skb);
+               cfg80211_report_obss_beacon(rx->local->hw.wiphy,
+                                           rx->skb->data, rx->skb->len,
+                                           status->freq, GFP_ATOMIC);
+               rx->flags |= IEEE80211_RX_BEACON_REPORTED;
+       }
+
        if (!(status->rx_flags & IEEE80211_RX_RA_MATCH))
                return RX_DROP_MONITOR;
 
@@ -2203,15 +2234,11 @@ ieee80211_rx_h_action(struct ieee80211_rx_data *rx)
 
        switch (mgmt->u.action.category) {
        case WLAN_CATEGORY_BACK:
-               /*
-                * The aggregation code is not prepared to handle
-                * anything but STA/AP due to the BSSID handling;
-                * IBSS could work in the code but isn't supported
-                * by drivers or the standard.
-                */
                if (sdata->vif.type != NL80211_IFTYPE_STATION &&
+                   sdata->vif.type != NL80211_IFTYPE_MESH_POINT &&
                    sdata->vif.type != NL80211_IFTYPE_AP_VLAN &&
-                   sdata->vif.type != NL80211_IFTYPE_AP)
+                   sdata->vif.type != NL80211_IFTYPE_AP &&
+                   sdata->vif.type != NL80211_IFTYPE_ADHOC)
                        break;
 
                /* verify action_code is present */
@@ -2770,10 +2797,17 @@ static int prepare_for_handlers(struct ieee80211_rx_data *rx,
                                return 0;
                } else if (!ieee80211_bssid_match(bssid,
                                        sdata->vif.addr)) {
+                       /*
+                        * Accept public action frames even when the
+                        * BSSID doesn't match, this is used for P2P
+                        * and location updates. Note that mac80211
+                        * itself never looks at these frames.
+                        */
+                       if (!(status->rx_flags & IEEE80211_RX_IN_SCAN) &&
+                           ieee80211_is_public_action(hdr, skb->len))
+                               return 1;
                        if (!(status->rx_flags & IEEE80211_RX_IN_SCAN) &&
-                           !ieee80211_is_beacon(hdr->frame_control) &&
-                           !(ieee80211_is_action(hdr->frame_control) &&
-                             sdata->vif.p2p))
+                           !ieee80211_is_beacon(hdr->frame_control))
                                return 0;
                        status->rx_flags &= ~IEEE80211_RX_RA_MATCH;
                }