]> git.proxmox.com Git - mirror_ubuntu-zesty-kernel.git/blobdiff - net/mac80211/rx.c
mac80211: rcu-ify scan and scheduled scan request pointers
[mirror_ubuntu-zesty-kernel.git] / net / mac80211 / rx.c
index b04ca4049c95f276aa4627501d5ced01e8a3b878..d9bbb73d4436cb13193f5dde7745cb7a71ae97de 100644 (file)
@@ -39,7 +39,8 @@
  * only useful for monitoring.
  */
 static struct sk_buff *remove_monitor_info(struct ieee80211_local *local,
-                                          struct sk_buff *skb)
+                                          struct sk_buff *skb,
+                                          unsigned int rtap_vendor_space)
 {
        if (local->hw.flags & IEEE80211_HW_RX_INCLUDES_FCS) {
                if (likely(skb->len > FCS_LEN))
@@ -52,20 +53,25 @@ static struct sk_buff *remove_monitor_info(struct ieee80211_local *local,
                }
        }
 
+       __pskb_pull(skb, rtap_vendor_space);
+
        return skb;
 }
 
-static inline bool should_drop_frame(struct sk_buff *skb, int present_fcs_len)
+static inline bool should_drop_frame(struct sk_buff *skb, int present_fcs_len,
+                                    unsigned int rtap_vendor_space)
 {
        struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
-       struct ieee80211_hdr *hdr = (void *)skb->data;
+       struct ieee80211_hdr *hdr;
+
+       hdr = (void *)(skb->data + rtap_vendor_space);
 
        if (status->flag & (RX_FLAG_FAILED_FCS_CRC |
                            RX_FLAG_FAILED_PLCP_CRC |
                            RX_FLAG_AMPDU_IS_ZEROLEN))
                return true;
 
-       if (unlikely(skb->len < 16 + present_fcs_len))
+       if (unlikely(skb->len < 16 + present_fcs_len + rtap_vendor_space))
                return true;
 
        if (ieee80211_is_ctl(hdr->frame_control) &&
@@ -77,8 +83,9 @@ static inline bool should_drop_frame(struct sk_buff *skb, int present_fcs_len)
 }
 
 static int
-ieee80211_rx_radiotap_space(struct ieee80211_local *local,
-                           struct ieee80211_rx_status *status)
+ieee80211_rx_radiotap_hdrlen(struct ieee80211_local *local,
+                            struct ieee80211_rx_status *status,
+                            struct sk_buff *skb)
 {
        int len;
 
@@ -121,6 +128,21 @@ ieee80211_rx_radiotap_space(struct ieee80211_local *local,
                len += 2 * hweight8(status->chains);
        }
 
+       if (status->flag & RX_FLAG_RADIOTAP_VENDOR_DATA) {
+               struct ieee80211_vendor_radiotap *rtap = (void *)skb->data;
+
+               /* vendor presence bitmap */
+               len += 4;
+               /* alignment for fixed 6-byte vendor data header */
+               len = ALIGN(len, 2);
+               /* vendor data header */
+               len += 6;
+               if (WARN_ON(rtap->align == 0))
+                       rtap->align = 1;
+               len = ALIGN(len, rtap->align);
+               len += rtap->len + rtap->pad;
+       }
+
        return len;
 }
 
@@ -144,13 +166,20 @@ ieee80211_add_rx_radiotap_header(struct ieee80211_local *local,
        u16 channel_flags = 0;
        int mpdulen, chain;
        unsigned long chains = status->chains;
+       struct ieee80211_vendor_radiotap rtap = {};
+
+       if (status->flag & RX_FLAG_RADIOTAP_VENDOR_DATA) {
+               rtap = *(struct ieee80211_vendor_radiotap *)skb->data;
+               /* rtap.len and rtap.pad are undone immediately */
+               skb_pull(skb, sizeof(rtap) + rtap.len + rtap.pad);
+       }
 
        mpdulen = skb->len;
        if (!(has_fcs && (local->hw.flags & IEEE80211_HW_RX_INCLUDES_FCS)))
                mpdulen += FCS_LEN;
 
        rthdr = (struct ieee80211_radiotap_header *)skb_push(skb, rtap_len);
-       memset(rthdr, 0, rtap_len);
+       memset(rthdr, 0, rtap_len - rtap.len - rtap.pad);
        it_present = &rthdr->it_present;
 
        /* radiotap header, set always present flags */
@@ -172,6 +201,14 @@ ieee80211_add_rx_radiotap_header(struct ieee80211_local *local,
                                 BIT(IEEE80211_RADIOTAP_DBM_ANTSIGNAL);
        }
 
+       if (status->flag & RX_FLAG_RADIOTAP_VENDOR_DATA) {
+               it_present_val |= BIT(IEEE80211_RADIOTAP_VENDOR_NAMESPACE) |
+                                 BIT(IEEE80211_RADIOTAP_EXT);
+               put_unaligned_le32(it_present_val, it_present);
+               it_present++;
+               it_present_val = rtap.present;
+       }
+
        put_unaligned_le32(it_present_val, it_present);
 
        pos = (void *)(it_present + 1);
@@ -366,6 +403,22 @@ ieee80211_add_rx_radiotap_header(struct ieee80211_local *local,
                *pos++ = status->chain_signal[chain];
                *pos++ = chain;
        }
+
+       if (status->flag & RX_FLAG_RADIOTAP_VENDOR_DATA) {
+               /* ensure 2 byte alignment for the vendor field as required */
+               if ((pos - (u8 *)rthdr) & 1)
+                       *pos++ = 0;
+               *pos++ = rtap.oui[0];
+               *pos++ = rtap.oui[1];
+               *pos++ = rtap.oui[2];
+               *pos++ = rtap.subns;
+               put_unaligned_le16(rtap.len, pos);
+               pos += 2;
+               /* align the actual payload as requested */
+               while ((pos - (u8 *)rthdr) & (rtap.align - 1))
+                       *pos++ = 0;
+               /* data (and possible padding) already follows */
+       }
 }
 
 /*
@@ -379,10 +432,17 @@ ieee80211_rx_monitor(struct ieee80211_local *local, struct sk_buff *origskb,
 {
        struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(origskb);
        struct ieee80211_sub_if_data *sdata;
-       int needed_headroom;
+       int rt_hdrlen, needed_headroom;
        struct sk_buff *skb, *skb2;
        struct net_device *prev_dev = NULL;
        int present_fcs_len = 0;
+       unsigned int rtap_vendor_space = 0;
+
+       if (unlikely(status->flag & RX_FLAG_RADIOTAP_VENDOR_DATA)) {
+               struct ieee80211_vendor_radiotap *rtap = (void *)origskb->data;
+
+               rtap_vendor_space = sizeof(*rtap) + rtap->len + rtap->pad;
+       }
 
        /*
         * First, we may need to make a copy of the skb because
@@ -396,25 +456,27 @@ ieee80211_rx_monitor(struct ieee80211_local *local, struct sk_buff *origskb,
        if (local->hw.flags & IEEE80211_HW_RX_INCLUDES_FCS)
                present_fcs_len = FCS_LEN;
 
-       /* ensure hdr->frame_control is in skb head */
-       if (!pskb_may_pull(origskb, 2)) {
+       /* ensure hdr->frame_control and vendor radiotap data are in skb head */
+       if (!pskb_may_pull(origskb, 2 + rtap_vendor_space)) {
                dev_kfree_skb(origskb);
                return NULL;
        }
 
        if (!local->monitors) {
-               if (should_drop_frame(origskb, present_fcs_len)) {
+               if (should_drop_frame(origskb, present_fcs_len,
+                                     rtap_vendor_space)) {
                        dev_kfree_skb(origskb);
                        return NULL;
                }
 
-               return remove_monitor_info(local, origskb);
+               return remove_monitor_info(local, origskb, rtap_vendor_space);
        }
 
        /* room for the radiotap header based on driver features */
-       needed_headroom = ieee80211_rx_radiotap_space(local, status);
+       rt_hdrlen = ieee80211_rx_radiotap_hdrlen(local, status, origskb);
+       needed_headroom = rt_hdrlen - rtap_vendor_space;
 
-       if (should_drop_frame(origskb, present_fcs_len)) {
+       if (should_drop_frame(origskb, present_fcs_len, rtap_vendor_space)) {
                /* only need to expand headroom if necessary */
                skb = origskb;
                origskb = NULL;
@@ -438,15 +500,15 @@ ieee80211_rx_monitor(struct ieee80211_local *local, struct sk_buff *origskb,
                 */
                skb = skb_copy_expand(origskb, needed_headroom, 0, GFP_ATOMIC);
 
-               origskb = remove_monitor_info(local, origskb);
+               origskb = remove_monitor_info(local, origskb,
+                                             rtap_vendor_space);
 
                if (!skb)
                        return origskb;
        }
 
        /* prepend radiotap information */
-       ieee80211_add_rx_radiotap_header(local, skb, rate, needed_headroom,
-                                        true);
+       ieee80211_add_rx_radiotap_header(local, skb, rate, rt_hdrlen, true);
 
        skb_reset_mac_header(skb);
        skb->ip_summed = CHECKSUM_UNNECESSARY;
@@ -985,7 +1047,7 @@ static void ieee80211_rx_reorder_ampdu(struct ieee80211_rx_data *rx,
 }
 
 static ieee80211_rx_result debug_noinline
-ieee80211_rx_h_check(struct ieee80211_rx_data *rx)
+ieee80211_rx_h_check_dup(struct ieee80211_rx_data *rx)
 {
        struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)rx->skb->data;
        struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(rx->skb);
@@ -994,10 +1056,16 @@ ieee80211_rx_h_check(struct ieee80211_rx_data *rx)
         * Drop duplicate 802.11 retransmissions
         * (IEEE 802.11-2012: 9.3.2.10 "Duplicate detection and recovery")
         */
-       if (rx->skb->len >= 24 && rx->sta &&
-           !ieee80211_is_ctl(hdr->frame_control) &&
-           !ieee80211_is_qos_nullfunc(hdr->frame_control) &&
-           !is_multicast_ether_addr(hdr->addr1)) {
+
+       if (rx->skb->len < 24)
+               return RX_CONTINUE;
+
+       if (ieee80211_is_ctl(hdr->frame_control) ||
+           ieee80211_is_qos_nullfunc(hdr->frame_control) ||
+           is_multicast_ether_addr(hdr->addr1))
+               return RX_CONTINUE;
+
+       if (rx->sta) {
                if (unlikely(ieee80211_has_retry(hdr->frame_control) &&
                             rx->sta->last_seq_ctrl[rx->seqno_idx] ==
                             hdr->seq_ctrl)) {
@@ -1011,6 +1079,14 @@ ieee80211_rx_h_check(struct ieee80211_rx_data *rx)
                }
        }
 
+       return RX_CONTINUE;
+}
+
+static ieee80211_rx_result debug_noinline
+ieee80211_rx_h_check(struct ieee80211_rx_data *rx)
+{
+       struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)rx->skb->data;
+
        if (unlikely(rx->skb->len < 16)) {
                I802_DEBUG_INC(rx->local->rx_handlers_drop_short);
                return RX_DROP_MONITOR;
@@ -1032,6 +1108,7 @@ ieee80211_rx_h_check(struct ieee80211_rx_data *rx)
                      ieee80211_is_pspoll(hdr->frame_control)) &&
                     rx->sdata->vif.type != NL80211_IFTYPE_ADHOC &&
                     rx->sdata->vif.type != NL80211_IFTYPE_WDS &&
+                    rx->sdata->vif.type != NL80211_IFTYPE_OCB &&
                     (!rx->sta || !test_sta_flag(rx->sta, WLAN_STA_ASSOC)))) {
                /*
                 * accept port control frames from the AP even when it's not
@@ -1272,6 +1349,12 @@ ieee80211_rx_h_sta_process(struct ieee80211_rx_data *rx)
                                sta->last_rx_rate_vht_nss = status->vht_nss;
                        }
                }
+       } else if (rx->sdata->vif.type == NL80211_IFTYPE_OCB) {
+               u8 *bssid = ieee80211_get_bssid(hdr, rx->skb->len,
+                                               NL80211_IFTYPE_OCB);
+               /* OCB uses wild-card BSSID */
+               if (is_broadcast_ether_addr(bssid))
+                       sta->last_rx = jiffies;
        } else if (!is_multicast_ether_addr(hdr->addr1)) {
                /*
                 * Mesh beacons will update last_rx when if they are found to
@@ -2250,6 +2333,27 @@ ieee80211_rx_h_data(struct ieee80211_rx_data *rx)
        if (!ieee80211_frame_allowed(rx, fc))
                return RX_DROP_MONITOR;
 
+       /* directly handle TDLS channel switch requests/responses */
+       if (unlikely(((struct ethhdr *)rx->skb->data)->h_proto ==
+                                               cpu_to_be16(ETH_P_TDLS))) {
+               struct ieee80211_tdls_data *tf = (void *)rx->skb->data;
+
+               if (pskb_may_pull(rx->skb,
+                                 offsetof(struct ieee80211_tdls_data, u)) &&
+                   tf->payload_type == WLAN_TDLS_SNAP_RFTYPE &&
+                   tf->category == WLAN_CATEGORY_TDLS &&
+                   (tf->action_code == WLAN_TDLS_CHANNEL_SWITCH_REQUEST ||
+                    tf->action_code == WLAN_TDLS_CHANNEL_SWITCH_RESPONSE)) {
+                       rx->skb->pkt_type = IEEE80211_SDATA_QUEUE_TDLS_CHSW;
+                       skb_queue_tail(&sdata->skb_queue, rx->skb);
+                       ieee80211_queue_work(&rx->local->hw, &sdata->work);
+                       if (rx->sta)
+                               rx->sta->rx_packets++;
+
+                       return RX_QUEUED;
+               }
+       }
+
        if (rx->sdata->vif.type == NL80211_IFTYPE_AP_VLAN &&
            unlikely(port_control) && sdata->bss) {
                sdata = container_of(sdata->bss, struct ieee80211_sub_if_data,
@@ -2820,6 +2924,7 @@ ieee80211_rx_h_mgmt(struct ieee80211_rx_data *rx)
 
        if (!ieee80211_vif_is_mesh(&sdata->vif) &&
            sdata->vif.type != NL80211_IFTYPE_ADHOC &&
+           sdata->vif.type != NL80211_IFTYPE_OCB &&
            sdata->vif.type != NL80211_IFTYPE_STATION)
                return RX_DROP_MONITOR;
 
@@ -2884,8 +2989,10 @@ static void ieee80211_rx_cooked_monitor(struct ieee80211_rx_data *rx,
        if (!local->cooked_mntrs)
                goto out_free_skb;
 
+       /* vendor data is long removed here */
+       status->flag &= ~RX_FLAG_RADIOTAP_VENDOR_DATA;
        /* room for the radiotap header based on driver features */
-       needed_headroom = ieee80211_rx_radiotap_space(local, status);
+       needed_headroom = ieee80211_rx_radiotap_hdrlen(local, status, skb);
 
        if (skb_headroom(skb) < needed_headroom &&
            pskb_expand_head(skb, needed_headroom, 0, GFP_ATOMIC))
@@ -3038,6 +3145,7 @@ static void ieee80211_invoke_rx_handlers(struct ieee80211_rx_data *rx)
                        goto rxh_next;  \
        } while (0);
 
+       CALL_RXH(ieee80211_rx_h_check_dup)
        CALL_RXH(ieee80211_rx_h_check)
 
        ieee80211_rx_reorder_ampdu(rx, &reorder_release);
@@ -3130,6 +3238,33 @@ static bool prepare_for_handlers(struct ieee80211_rx_data *rx,
                                                 BIT(rate_idx));
                }
                break;
+       case NL80211_IFTYPE_OCB:
+               if (!bssid)
+                       return false;
+               if (ieee80211_is_beacon(hdr->frame_control)) {
+                       return false;
+               } else if (!is_broadcast_ether_addr(bssid)) {
+                       ocb_dbg(sdata, "BSSID mismatch in OCB mode!\n");
+                       return false;
+               } else if (!multicast &&
+                          !ether_addr_equal(sdata->dev->dev_addr,
+                                            hdr->addr1)) {
+                       /* if we are in promisc mode we also accept
+                        * packets not destined for us
+                        */
+                       if (!(sdata->dev->flags & IFF_PROMISC))
+                               return false;
+                       rx->flags &= ~IEEE80211_RX_RA_MATCH;
+               } else if (!rx->sta) {
+                       int rate_idx;
+                       if (status->flag & RX_FLAG_HT)
+                               rate_idx = 0; /* TODO: HT rates */
+                       else
+                               rate_idx = status->rate_idx;
+                       ieee80211_ocb_rx_no_sta(sdata, bssid, hdr->addr2,
+                                               BIT(rate_idx));
+               }
+               break;
        case NL80211_IFTYPE_MESH_POINT:
                if (!multicast &&
                    !ether_addr_equal(sdata->vif.addr, hdr->addr1)) {