cfg80211/mac80211: allow registering for and sending action frames
[pandora-kernel.git] / net / mac80211 / rx.c
index f60dfca..a6080d8 100644 (file)
@@ -2,7 +2,7 @@
  * Copyright 2002-2005, Instant802 Networks, Inc.
  * Copyright 2005-2006, Devicescape Software, Inc.
  * Copyright 2006-2007 Jiri Benc <jbenc@suse.cz>
- * Copyright 2007      Johannes Berg <johannes@sipsolutions.net>
+ * Copyright 2007-2010 Johannes Berg <johannes@sipsolutions.net>
  *
  * 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
@@ -1111,6 +1111,18 @@ ieee80211_rx_h_sta_process(struct ieee80211_rx_data *rx)
        if (ieee80211_is_nullfunc(hdr->frame_control) ||
            ieee80211_is_qos_nullfunc(hdr->frame_control)) {
                I802_DEBUG_INC(rx->local->rx_handlers_drop_nullfunc);
+
+               /*
+                * 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
+                */
+               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)))
+                       return RX_DROP_MONITOR;
                /*
                 * Update counter and free packet here to avoid
                 * counting this as a dropped packed.
@@ -1665,7 +1677,9 @@ ieee80211_rx_h_mesh_fwding(struct ieee80211_rx_data *rx)
                        memset(info, 0, sizeof(*info));
                        info->flags |= IEEE80211_TX_INTFL_NEED_TXPROCESSING;
                        info->control.vif = &rx->sdata->vif;
-                       ieee80211_select_queue(local, fwd_skb);
+                       skb_set_queue_mapping(skb,
+                               ieee80211_select_queue(rx->sdata, fwd_skb));
+                       ieee80211_set_qos_hdr(local, skb);
                        if (is_multicast_ether_addr(fwd_hdr->addr1))
                                IEEE80211_IFSTA_MESH_CTR_INC(&sdata->u.mesh,
                                                                fwded_mcast);
@@ -1705,6 +1719,7 @@ static ieee80211_rx_result debug_noinline
 ieee80211_rx_h_data(struct ieee80211_rx_data *rx)
 {
        struct ieee80211_sub_if_data *sdata = rx->sdata;
+       struct ieee80211_local *local = rx->local;
        struct net_device *dev = sdata->dev;
        struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)rx->skb->data;
        __le16 fc = hdr->frame_control;
@@ -1736,6 +1751,13 @@ ieee80211_rx_h_data(struct ieee80211_rx_data *rx)
        dev->stats.rx_packets++;
        dev->stats.rx_bytes += rx->skb->len;
 
+       if (ieee80211_is_data(hdr->frame_control) &&
+           !is_multicast_ether_addr(hdr->addr1) &&
+           local->hw.conf.dynamic_ps_timeout > 0 && local->ps_sdata) {
+                       mod_timer(&local->dynamic_ps_timer, jiffies +
+                        msecs_to_jiffies(local->hw.conf.dynamic_ps_timeout));
+       }
+
        ieee80211_deliver_skb(rx);
 
        return RX_QUEUED;
@@ -1833,23 +1855,25 @@ ieee80211_rx_h_action(struct ieee80211_rx_data *rx)
        struct ieee80211_local *local = rx->local;
        struct ieee80211_sub_if_data *sdata = rx->sdata;
        struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *) rx->skb->data;
+       struct sk_buff *nskb;
+       struct ieee80211_rx_status *status;
        int len = rx->skb->len;
 
        if (!ieee80211_is_action(mgmt->frame_control))
                return RX_CONTINUE;
 
-       if (!rx->sta)
-               return RX_DROP_MONITOR;
+       /* drop too small frames */
+       if (len < IEEE80211_MIN_ACTION_SIZE)
+               return RX_DROP_UNUSABLE;
+
+       if (!rx->sta && mgmt->u.action.category != WLAN_CATEGORY_PUBLIC)
+               return RX_DROP_UNUSABLE;
 
        if (!(rx->flags & IEEE80211_RX_RA_MATCH))
-               return RX_DROP_MONITOR;
+               return RX_DROP_UNUSABLE;
 
        if (ieee80211_drop_unencrypted(rx, mgmt->frame_control))
-               return RX_DROP_MONITOR;
-
-       /* all categories we currently handle have action_code */
-       if (len < IEEE80211_MIN_ACTION_SIZE + 1)
-               return RX_DROP_MONITOR;
+               return RX_DROP_UNUSABLE;
 
        switch (mgmt->u.action.category) {
        case WLAN_CATEGORY_BACK:
@@ -1862,7 +1886,11 @@ ieee80211_rx_h_action(struct ieee80211_rx_data *rx)
                if (sdata->vif.type != NL80211_IFTYPE_STATION &&
                    sdata->vif.type != NL80211_IFTYPE_AP_VLAN &&
                    sdata->vif.type != NL80211_IFTYPE_AP)
-                       return RX_DROP_MONITOR;
+                       break;
+
+               /* verify action_code is present */
+               if (len < IEEE80211_MIN_ACTION_SIZE + 1)
+                       break;
 
                switch (mgmt->u.action.u.addba_req.action_code) {
                case WLAN_ACTION_ADDBA_REQ:
@@ -1870,45 +1898,49 @@ ieee80211_rx_h_action(struct ieee80211_rx_data *rx)
                                   sizeof(mgmt->u.action.u.addba_req)))
                                return RX_DROP_MONITOR;
                        ieee80211_process_addba_request(local, rx->sta, mgmt, len);
-                       break;
+                       goto handled;
                case WLAN_ACTION_ADDBA_RESP:
                        if (len < (IEEE80211_MIN_ACTION_SIZE +
                                   sizeof(mgmt->u.action.u.addba_resp)))
-                               return RX_DROP_MONITOR;
+                               break;
                        ieee80211_process_addba_resp(local, rx->sta, mgmt, len);
-                       break;
+                       goto handled;
                case WLAN_ACTION_DELBA:
                        if (len < (IEEE80211_MIN_ACTION_SIZE +
                                   sizeof(mgmt->u.action.u.delba)))
-                               return RX_DROP_MONITOR;
+                               break;
                        ieee80211_process_delba(sdata, rx->sta, mgmt, len);
-                       break;
+                       goto handled;
                }
                break;
        case WLAN_CATEGORY_SPECTRUM_MGMT:
                if (local->hw.conf.channel->band != IEEE80211_BAND_5GHZ)
-                       return RX_DROP_MONITOR;
+                       break;
 
                if (sdata->vif.type != NL80211_IFTYPE_STATION)
-                       return RX_DROP_MONITOR;
+                       break;
+
+               /* verify action_code is present */
+               if (len < IEEE80211_MIN_ACTION_SIZE + 1)
+                       break;
 
                switch (mgmt->u.action.u.measurement.action_code) {
                case WLAN_ACTION_SPCT_MSR_REQ:
                        if (len < (IEEE80211_MIN_ACTION_SIZE +
                                   sizeof(mgmt->u.action.u.measurement)))
-                               return RX_DROP_MONITOR;
+                               break;
                        ieee80211_process_measurement_req(sdata, mgmt, len);
-                       break;
+                       goto handled;
                case WLAN_ACTION_SPCT_CHL_SWITCH:
                        if (len < (IEEE80211_MIN_ACTION_SIZE +
                                   sizeof(mgmt->u.action.u.chan_switch)))
-                               return RX_DROP_MONITOR;
+                               break;
 
                        if (sdata->vif.type != NL80211_IFTYPE_STATION)
-                               return RX_DROP_MONITOR;
+                               break;
 
                        if (memcmp(mgmt->bssid, sdata->u.mgd.bssid, ETH_ALEN))
-                               return RX_DROP_MONITOR;
+                               break;
 
                        return ieee80211_sta_rx_mgmt(sdata, rx->skb);
                }
@@ -1916,26 +1948,64 @@ ieee80211_rx_h_action(struct ieee80211_rx_data *rx)
        case WLAN_CATEGORY_SA_QUERY:
                if (len < (IEEE80211_MIN_ACTION_SIZE +
                           sizeof(mgmt->u.action.u.sa_query)))
-                       return RX_DROP_MONITOR;
+                       break;
+
                switch (mgmt->u.action.u.sa_query.action) {
                case WLAN_ACTION_SA_QUERY_REQUEST:
                        if (sdata->vif.type != NL80211_IFTYPE_STATION)
-                               return RX_DROP_MONITOR;
+                               break;
                        ieee80211_process_sa_query_req(sdata, mgmt, len);
-                       break;
-               case WLAN_ACTION_SA_QUERY_RESPONSE:
-                       /*
-                        * SA Query response is currently only used in AP mode
-                        * and it is processed in user space.
-                        */
-                       return RX_CONTINUE;
+                       goto handled;
                }
                break;
-       default:
-               return RX_CONTINUE;
        }
 
-       rx->sta->rx_packets++;
+       /*
+        * For AP mode, hostapd is responsible for handling any action
+        * frames that we didn't handle, including returning unknown
+        * ones. For all other modes we will return them to the sender,
+        * setting the 0x80 bit in the action category, as required by
+        * 802.11-2007 7.3.1.11.
+        */
+       if (sdata->vif.type == NL80211_IFTYPE_AP ||
+           sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
+               return RX_DROP_MONITOR;
+
+       /*
+        * Getting here means the kernel doesn't know how to handle
+        * it, but maybe userspace does ... include returned frames
+        * so userspace can register for those to know whether ones
+        * it transmitted were processed or returned.
+        */
+       status = IEEE80211_SKB_RXCB(rx->skb);
+
+       if (sdata->vif.type == NL80211_IFTYPE_STATION &&
+           cfg80211_rx_action(rx->sdata->dev, status->freq,
+                              rx->skb->data, rx->skb->len,
+                              GFP_ATOMIC))
+               goto handled;
+
+       /* do not return rejected action frames */
+       if (mgmt->u.action.category & 0x80)
+               return RX_DROP_UNUSABLE;
+
+       nskb = skb_copy_expand(rx->skb, local->hw.extra_tx_headroom, 0,
+                              GFP_ATOMIC);
+       if (nskb) {
+               struct ieee80211_mgmt *mgmt = (void *)nskb->data;
+
+               mgmt->u.action.category |= 0x80;
+               memcpy(mgmt->da, mgmt->sa, ETH_ALEN);
+               memcpy(mgmt->sa, rx->sdata->vif.addr, ETH_ALEN);
+
+               memset(nskb->cb, 0, sizeof(nskb->cb));
+
+               ieee80211_tx_skb(rx->sdata, nskb);
+       }
+
+ handled:
+       if (rx->sta)
+               rx->sta->rx_packets++;
        dev_kfree_skb(rx->skb);
        return RX_QUEUED;
 }
@@ -1945,6 +2015,7 @@ ieee80211_rx_h_mgmt(struct ieee80211_rx_data *rx)
 {
        struct ieee80211_sub_if_data *sdata = rx->sdata;
        struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *) rx->skb->data;
+       ieee80211_rx_result rxs;
 
        if (!(rx->flags & IEEE80211_RX_RA_MATCH))
                return RX_DROP_MONITOR;
@@ -1952,6 +2023,10 @@ ieee80211_rx_h_mgmt(struct ieee80211_rx_data *rx)
        if (ieee80211_drop_unencrypted(rx, mgmt->frame_control))
                return RX_DROP_MONITOR;
 
+       rxs = ieee80211_work_rx_mgmt(rx->sdata, rx->skb);
+       if (rxs != RX_CONTINUE)
+               return rxs;
+
        if (ieee80211_vif_is_mesh(&sdata->vif))
                return ieee80211_mesh_rx_mgmt(sdata, rx->skb);
 
@@ -2221,8 +2296,8 @@ static int prepare_for_handlers(struct ieee80211_sub_if_data *sdata,
                                rate_idx = 0; /* TODO: HT rates */
                        else
                                rate_idx = status->rate_idx;
-                       rx->sta = ieee80211_ibss_add_sta(sdata, bssid, hdr->addr2,
-                               BIT(rate_idx));
+                       rx->sta = ieee80211_ibss_add_sta(sdata, bssid,
+                                       hdr->addr2, BIT(rate_idx), GFP_ATOMIC);
                }
                break;
        case NL80211_IFTYPE_MESH_POINT:
@@ -2325,22 +2400,6 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,
                            sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
                                continue;
 
-                       rx.sta = sta_info_get(sdata, hdr->addr2);
-
-                       rx.flags |= IEEE80211_RX_RA_MATCH;
-                       prepares = prepare_for_handlers(sdata, &rx, hdr);
-
-                       if (!prepares)
-                               continue;
-
-                       if (status->flag & RX_FLAG_MMIC_ERROR) {
-                               rx.sdata = sdata;
-                               if (rx.flags & IEEE80211_RX_RA_MATCH)
-                                       ieee80211_rx_michael_mic_report(hdr,
-                                                                       &rx);
-                               continue;
-                       }
-
                        /*
                         * frame is destined for this interface, but if it's
                         * not also for the previous one we handle that after
@@ -2352,6 +2411,22 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,
                                continue;
                        }
 
+                       rx.sta = sta_info_get_bss(prev, hdr->addr2);
+
+                       rx.flags |= IEEE80211_RX_RA_MATCH;
+                       prepares = prepare_for_handlers(prev, &rx, hdr);
+
+                       if (!prepares)
+                               goto next;
+
+                       if (status->flag & RX_FLAG_MMIC_ERROR) {
+                               rx.sdata = prev;
+                               if (rx.flags & IEEE80211_RX_RA_MATCH)
+                                       ieee80211_rx_michael_mic_report(hdr,
+                                                                       &rx);
+                               goto next;
+                       }
+
                        /*
                         * frame was destined for the previous interface
                         * so invoke RX handlers for it
@@ -2364,11 +2439,22 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,
                                               "multicast frame for %s\n",
                                               wiphy_name(local->hw.wiphy),
                                               prev->name);
-                               continue;
+                               goto next;
                        }
                        ieee80211_invoke_rx_handlers(prev, &rx, skb_new, rate);
+next:
                        prev = sdata;
                }
+
+               if (prev) {
+                       rx.sta = sta_info_get_bss(prev, hdr->addr2);
+
+                       rx.flags |= IEEE80211_RX_RA_MATCH;
+                       prepares = prepare_for_handlers(prev, &rx, hdr);
+
+                       if (!prepares)
+                               prev = NULL;
+               }
        }
        if (prev)
                ieee80211_invoke_rx_handlers(prev, &rx, skb, rate);