mirror of
https://github.com/adulau/aha.git
synced 2024-12-29 12:16:20 +00:00
914828fad0
An earlier optimization on removing unnecessary traffic on cooked monitor interfaces ("mac80211: reduce the amount of unnecessary traffic on cooked monitor interfaces ") ended up removing quite a bit more than just unnecessary traffic. It was not supposed to remove TX status reporting for injected frames, but ended up doing it by checking the injected flag in skb->cb only after that field had been cleared with memset.. Fix this by taking a local copy of the injected flag before skb->cb is cleared. This broke user space applications that depend on getting TX status notifications for injected data frames. For example, STA inactivity poll from hostapd did not work and ended up kicking out stations even if they were still present. Signed-off-by: Jouni Malinen <j@w1.fi> Signed-off-by: John W. Linville <linville@tuxdriver.com>
341 lines
10 KiB
C
341 lines
10 KiB
C
/*
|
|
* Copyright 2002-2005, Instant802 Networks, Inc.
|
|
* Copyright 2005-2006, Devicescape Software, Inc.
|
|
* Copyright 2006-2007 Jiri Benc <jbenc@suse.cz>
|
|
* Copyright 2008-2009 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
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <net/mac80211.h>
|
|
#include "ieee80211_i.h"
|
|
#include "rate.h"
|
|
#include "mesh.h"
|
|
#include "led.h"
|
|
|
|
|
|
void ieee80211_tx_status_irqsafe(struct ieee80211_hw *hw,
|
|
struct sk_buff *skb)
|
|
{
|
|
struct ieee80211_local *local = hw_to_local(hw);
|
|
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
|
|
int tmp;
|
|
|
|
skb->pkt_type = IEEE80211_TX_STATUS_MSG;
|
|
skb_queue_tail(info->flags & IEEE80211_TX_CTL_REQ_TX_STATUS ?
|
|
&local->skb_queue : &local->skb_queue_unreliable, skb);
|
|
tmp = skb_queue_len(&local->skb_queue) +
|
|
skb_queue_len(&local->skb_queue_unreliable);
|
|
while (tmp > IEEE80211_IRQSAFE_QUEUE_LIMIT &&
|
|
(skb = skb_dequeue(&local->skb_queue_unreliable))) {
|
|
dev_kfree_skb_irq(skb);
|
|
tmp--;
|
|
I802_DEBUG_INC(local->tx_status_drop);
|
|
}
|
|
tasklet_schedule(&local->tasklet);
|
|
}
|
|
EXPORT_SYMBOL(ieee80211_tx_status_irqsafe);
|
|
|
|
static void ieee80211_handle_filtered_frame(struct ieee80211_local *local,
|
|
struct sta_info *sta,
|
|
struct sk_buff *skb)
|
|
{
|
|
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
|
|
|
|
/*
|
|
* XXX: This is temporary!
|
|
*
|
|
* The problem here is that when we get here, the driver will
|
|
* quite likely have pretty much overwritten info->control by
|
|
* using info->driver_data or info->rate_driver_data. Thus,
|
|
* when passing out the frame to the driver again, we would be
|
|
* passing completely bogus data since the driver would then
|
|
* expect a properly filled info->control. In mac80211 itself
|
|
* the same problem occurs, since we need info->control.vif
|
|
* internally.
|
|
*
|
|
* To fix this, we should send the frame through TX processing
|
|
* again. However, it's not that simple, since the frame will
|
|
* have been software-encrypted (if applicable) already, and
|
|
* encrypting it again doesn't do much good. So to properly do
|
|
* that, we not only have to skip the actual 'raw' encryption
|
|
* (key selection etc. still has to be done!) but also the
|
|
* sequence number assignment since that impacts the crypto
|
|
* encapsulation, of course.
|
|
*
|
|
* Hence, for now, fix the bug by just dropping the frame.
|
|
*/
|
|
goto drop;
|
|
|
|
sta->tx_filtered_count++;
|
|
|
|
/*
|
|
* Clear the TX filter mask for this STA when sending the next
|
|
* packet. If the STA went to power save mode, this will happen
|
|
* when it wakes up for the next time.
|
|
*/
|
|
set_sta_flags(sta, WLAN_STA_CLEAR_PS_FILT);
|
|
|
|
/*
|
|
* This code races in the following way:
|
|
*
|
|
* (1) STA sends frame indicating it will go to sleep and does so
|
|
* (2) hardware/firmware adds STA to filter list, passes frame up
|
|
* (3) hardware/firmware processes TX fifo and suppresses a frame
|
|
* (4) we get TX status before having processed the frame and
|
|
* knowing that the STA has gone to sleep.
|
|
*
|
|
* This is actually quite unlikely even when both those events are
|
|
* processed from interrupts coming in quickly after one another or
|
|
* even at the same time because we queue both TX status events and
|
|
* RX frames to be processed by a tasklet and process them in the
|
|
* same order that they were received or TX status last. Hence, there
|
|
* is no race as long as the frame RX is processed before the next TX
|
|
* status, which drivers can ensure, see below.
|
|
*
|
|
* Note that this can only happen if the hardware or firmware can
|
|
* actually add STAs to the filter list, if this is done by the
|
|
* driver in response to set_tim() (which will only reduce the race
|
|
* this whole filtering tries to solve, not completely solve it)
|
|
* this situation cannot happen.
|
|
*
|
|
* To completely solve this race drivers need to make sure that they
|
|
* (a) don't mix the irq-safe/not irq-safe TX status/RX processing
|
|
* functions and
|
|
* (b) always process RX events before TX status events if ordering
|
|
* can be unknown, for example with different interrupt status
|
|
* bits.
|
|
*/
|
|
if (test_sta_flags(sta, WLAN_STA_PS_STA) &&
|
|
skb_queue_len(&sta->tx_filtered) < STA_MAX_TX_BUFFER) {
|
|
skb_queue_tail(&sta->tx_filtered, skb);
|
|
return;
|
|
}
|
|
|
|
if (!test_sta_flags(sta, WLAN_STA_PS_STA) &&
|
|
!(info->flags & IEEE80211_TX_INTFL_RETRIED)) {
|
|
/* Software retry the packet once */
|
|
info->flags |= IEEE80211_TX_INTFL_RETRIED;
|
|
ieee80211_add_pending_skb(local, skb);
|
|
return;
|
|
}
|
|
|
|
drop:
|
|
#ifdef CONFIG_MAC80211_VERBOSE_DEBUG
|
|
if (net_ratelimit())
|
|
printk(KERN_DEBUG "%s: dropped TX filtered frame, "
|
|
"queue_len=%d PS=%d @%lu\n",
|
|
wiphy_name(local->hw.wiphy),
|
|
skb_queue_len(&sta->tx_filtered),
|
|
!!test_sta_flags(sta, WLAN_STA_PS_STA), jiffies);
|
|
#endif
|
|
dev_kfree_skb(skb);
|
|
}
|
|
|
|
void ieee80211_tx_status(struct ieee80211_hw *hw, struct sk_buff *skb)
|
|
{
|
|
struct sk_buff *skb2;
|
|
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data;
|
|
struct ieee80211_local *local = hw_to_local(hw);
|
|
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
|
|
u16 frag, type;
|
|
__le16 fc;
|
|
struct ieee80211_supported_band *sband;
|
|
struct ieee80211_tx_status_rtap_hdr *rthdr;
|
|
struct ieee80211_sub_if_data *sdata;
|
|
struct net_device *prev_dev = NULL;
|
|
struct sta_info *sta;
|
|
int retry_count = -1, i;
|
|
bool injected;
|
|
|
|
for (i = 0; i < IEEE80211_TX_MAX_RATES; i++) {
|
|
/* the HW cannot have attempted that rate */
|
|
if (i >= hw->max_rates) {
|
|
info->status.rates[i].idx = -1;
|
|
info->status.rates[i].count = 0;
|
|
}
|
|
|
|
retry_count += info->status.rates[i].count;
|
|
}
|
|
if (retry_count < 0)
|
|
retry_count = 0;
|
|
|
|
rcu_read_lock();
|
|
|
|
sband = local->hw.wiphy->bands[info->band];
|
|
|
|
sta = sta_info_get(local, hdr->addr1);
|
|
|
|
if (sta) {
|
|
if (!(info->flags & IEEE80211_TX_STAT_ACK) &&
|
|
test_sta_flags(sta, WLAN_STA_PS_STA)) {
|
|
/*
|
|
* The STA is in power save mode, so assume
|
|
* that this TX packet failed because of that.
|
|
*/
|
|
ieee80211_handle_filtered_frame(local, sta, skb);
|
|
rcu_read_unlock();
|
|
return;
|
|
}
|
|
|
|
fc = hdr->frame_control;
|
|
|
|
if ((info->flags & IEEE80211_TX_STAT_AMPDU_NO_BACK) &&
|
|
(ieee80211_is_data_qos(fc))) {
|
|
u16 tid, ssn;
|
|
u8 *qc;
|
|
|
|
qc = ieee80211_get_qos_ctl(hdr);
|
|
tid = qc[0] & 0xf;
|
|
ssn = ((le16_to_cpu(hdr->seq_ctrl) + 0x10)
|
|
& IEEE80211_SCTL_SEQ);
|
|
ieee80211_send_bar(sta->sdata, hdr->addr1,
|
|
tid, ssn);
|
|
}
|
|
|
|
if (info->flags & IEEE80211_TX_STAT_TX_FILTERED) {
|
|
ieee80211_handle_filtered_frame(local, sta, skb);
|
|
rcu_read_unlock();
|
|
return;
|
|
} else {
|
|
if (!(info->flags & IEEE80211_TX_STAT_ACK))
|
|
sta->tx_retry_failed++;
|
|
sta->tx_retry_count += retry_count;
|
|
}
|
|
|
|
rate_control_tx_status(local, sband, sta, skb);
|
|
if (ieee80211_vif_is_mesh(&sta->sdata->vif))
|
|
ieee80211s_update_metric(local, sta, skb);
|
|
}
|
|
|
|
rcu_read_unlock();
|
|
|
|
ieee80211_led_tx(local, 0);
|
|
|
|
/* SNMP counters
|
|
* Fragments are passed to low-level drivers as separate skbs, so these
|
|
* are actually fragments, not frames. Update frame counters only for
|
|
* the first fragment of the frame. */
|
|
|
|
frag = le16_to_cpu(hdr->seq_ctrl) & IEEE80211_SCTL_FRAG;
|
|
type = le16_to_cpu(hdr->frame_control) & IEEE80211_FCTL_FTYPE;
|
|
|
|
if (info->flags & IEEE80211_TX_STAT_ACK) {
|
|
if (frag == 0) {
|
|
local->dot11TransmittedFrameCount++;
|
|
if (is_multicast_ether_addr(hdr->addr1))
|
|
local->dot11MulticastTransmittedFrameCount++;
|
|
if (retry_count > 0)
|
|
local->dot11RetryCount++;
|
|
if (retry_count > 1)
|
|
local->dot11MultipleRetryCount++;
|
|
}
|
|
|
|
/* This counter shall be incremented for an acknowledged MPDU
|
|
* with an individual address in the address 1 field or an MPDU
|
|
* with a multicast address in the address 1 field of type Data
|
|
* or Management. */
|
|
if (!is_multicast_ether_addr(hdr->addr1) ||
|
|
type == IEEE80211_FTYPE_DATA ||
|
|
type == IEEE80211_FTYPE_MGMT)
|
|
local->dot11TransmittedFragmentCount++;
|
|
} else {
|
|
if (frag == 0)
|
|
local->dot11FailedCount++;
|
|
}
|
|
|
|
/* this was a transmitted frame, but now we want to reuse it */
|
|
skb_orphan(skb);
|
|
|
|
/*
|
|
* This is a bit racy but we can avoid a lot of work
|
|
* with this test...
|
|
*/
|
|
if (!local->monitors && !local->cooked_mntrs) {
|
|
dev_kfree_skb(skb);
|
|
return;
|
|
}
|
|
|
|
/* send frame to monitor interfaces now */
|
|
|
|
if (skb_headroom(skb) < sizeof(*rthdr)) {
|
|
printk(KERN_ERR "ieee80211_tx_status: headroom too small\n");
|
|
dev_kfree_skb(skb);
|
|
return;
|
|
}
|
|
|
|
rthdr = (struct ieee80211_tx_status_rtap_hdr *)
|
|
skb_push(skb, sizeof(*rthdr));
|
|
|
|
memset(rthdr, 0, sizeof(*rthdr));
|
|
rthdr->hdr.it_len = cpu_to_le16(sizeof(*rthdr));
|
|
rthdr->hdr.it_present =
|
|
cpu_to_le32((1 << IEEE80211_RADIOTAP_TX_FLAGS) |
|
|
(1 << IEEE80211_RADIOTAP_DATA_RETRIES) |
|
|
(1 << IEEE80211_RADIOTAP_RATE));
|
|
|
|
if (!(info->flags & IEEE80211_TX_STAT_ACK) &&
|
|
!is_multicast_ether_addr(hdr->addr1))
|
|
rthdr->tx_flags |= cpu_to_le16(IEEE80211_RADIOTAP_F_TX_FAIL);
|
|
|
|
/*
|
|
* XXX: Once radiotap gets the bitmap reset thing the vendor
|
|
* extensions proposal contains, we can actually report
|
|
* the whole set of tries we did.
|
|
*/
|
|
if ((info->status.rates[0].flags & IEEE80211_TX_RC_USE_RTS_CTS) ||
|
|
(info->status.rates[0].flags & IEEE80211_TX_RC_USE_CTS_PROTECT))
|
|
rthdr->tx_flags |= cpu_to_le16(IEEE80211_RADIOTAP_F_TX_CTS);
|
|
else if (info->status.rates[0].flags & IEEE80211_TX_RC_USE_RTS_CTS)
|
|
rthdr->tx_flags |= cpu_to_le16(IEEE80211_RADIOTAP_F_TX_RTS);
|
|
if (info->status.rates[0].idx >= 0 &&
|
|
!(info->status.rates[0].flags & IEEE80211_TX_RC_MCS))
|
|
rthdr->rate = sband->bitrates[
|
|
info->status.rates[0].idx].bitrate / 5;
|
|
|
|
/* for now report the total retry_count */
|
|
rthdr->data_retries = retry_count;
|
|
|
|
/* Need to make a copy before skb->cb gets cleared */
|
|
injected = !!(info->flags & IEEE80211_TX_CTL_INJECTED);
|
|
|
|
/* XXX: is this sufficient for BPF? */
|
|
skb_set_mac_header(skb, 0);
|
|
skb->ip_summed = CHECKSUM_UNNECESSARY;
|
|
skb->pkt_type = PACKET_OTHERHOST;
|
|
skb->protocol = htons(ETH_P_802_2);
|
|
memset(skb->cb, 0, sizeof(skb->cb));
|
|
|
|
rcu_read_lock();
|
|
list_for_each_entry_rcu(sdata, &local->interfaces, list) {
|
|
if (sdata->vif.type == NL80211_IFTYPE_MONITOR) {
|
|
if (!netif_running(sdata->dev))
|
|
continue;
|
|
|
|
if ((sdata->u.mntr_flags & MONITOR_FLAG_COOK_FRAMES) &&
|
|
!injected &&
|
|
(type == IEEE80211_FTYPE_DATA))
|
|
continue;
|
|
|
|
if (prev_dev) {
|
|
skb2 = skb_clone(skb, GFP_ATOMIC);
|
|
if (skb2) {
|
|
skb2->dev = prev_dev;
|
|
netif_rx(skb2);
|
|
}
|
|
}
|
|
|
|
prev_dev = sdata->dev;
|
|
}
|
|
}
|
|
if (prev_dev) {
|
|
skb->dev = prev_dev;
|
|
netif_rx(skb);
|
|
skb = NULL;
|
|
}
|
|
rcu_read_unlock();
|
|
dev_kfree_skb(skb);
|
|
}
|
|
EXPORT_SYMBOL(ieee80211_tx_status);
|