Commit 33b64eb2 authored by Luis Carlos Cobo's avatar Luis Carlos Cobo Committed by John W. Linville

mac80211: support for mesh interfaces in mac80211 data path

This changes the TX/RX paths in mac80211 to support mesh interfaces.
This code will be cleaned up later again before being enabled.
Signed-off-by: default avatarLuis Carlos Cobo <luisca@cozybit.com>
Signed-off-by: default avatarJohannes Berg <johannes@sipsolutions.net>
Signed-off-by: default avatarJohn W. Linville <linville@tuxdriver.com>
parent 2e3c8736
...@@ -20,6 +20,9 @@ ...@@ -20,6 +20,9 @@
#include "ieee80211_i.h" #include "ieee80211_i.h"
#include "ieee80211_led.h" #include "ieee80211_led.h"
#ifdef CONFIG_MAC80211_MESH
#include "mesh.h"
#endif
#include "wep.h" #include "wep.h"
#include "wpa.h" #include "wpa.h"
#include "tkip.h" #include "tkip.h"
...@@ -390,10 +393,60 @@ ieee80211_rx_h_passive_scan(struct ieee80211_txrx_data *rx) ...@@ -390,10 +393,60 @@ ieee80211_rx_h_passive_scan(struct ieee80211_txrx_data *rx)
return RX_CONTINUE; return RX_CONTINUE;
} }
#ifdef CONFIG_MAC80211_MESH
#define msh_h_get(h, l) ((struct ieee80211s_hdr *) ((u8 *)h + l))
static ieee80211_rx_result
ieee80211_rx_mesh_check(struct ieee80211_txrx_data *rx)
{
int hdrlen = ieee80211_get_hdrlen(rx->fc);
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) rx->skb->data;
if ((rx->fc & IEEE80211_FCTL_FTYPE) == IEEE80211_FTYPE_DATA) {
if (!((rx->fc & IEEE80211_FCTL_FROMDS) &&
(rx->fc & IEEE80211_FCTL_TODS)))
return RX_DROP_MONITOR;
if (memcmp(hdr->addr4, rx->dev->dev_addr, ETH_ALEN) == 0)
return RX_DROP_MONITOR;
}
/* If there is not an established peer link and this is not a peer link
* establisment frame, beacon or probe, drop the frame.
*/
if (!rx->sta || rx->sta->plink_state != ESTAB) {
struct ieee80211_mgmt *mgmt;
if ((rx->fc & IEEE80211_FCTL_FTYPE) != IEEE80211_FTYPE_MGMT)
return RX_DROP_MONITOR;
switch (rx->fc & IEEE80211_FCTL_STYPE) {
case IEEE80211_STYPE_ACTION:
mgmt = (struct ieee80211_mgmt *)hdr;
if (mgmt->u.action.category != PLINK_CATEGORY)
return RX_DROP_MONITOR;
/* fall through on else */
case IEEE80211_STYPE_PROBE_REQ:
case IEEE80211_STYPE_PROBE_RESP:
case IEEE80211_STYPE_BEACON:
return RX_CONTINUE;
break;
default:
return RX_DROP_MONITOR;
}
} else if ((rx->fc & IEEE80211_FCTL_FTYPE) == IEEE80211_FTYPE_DATA &&
is_broadcast_ether_addr(hdr->addr1) &&
mesh_rmc_check(hdr->addr4, msh_h_get(hdr, hdrlen), rx->dev))
return RX_DROP_MONITOR;
else
return RX_CONTINUE;
}
#endif
static ieee80211_rx_result static ieee80211_rx_result
ieee80211_rx_h_check(struct ieee80211_txrx_data *rx) ieee80211_rx_h_check(struct ieee80211_txrx_data *rx)
{ {
struct ieee80211_hdr *hdr; struct ieee80211_hdr *hdr;
hdr = (struct ieee80211_hdr *) rx->skb->data; hdr = (struct ieee80211_hdr *) rx->skb->data;
/* Drop duplicate 802.11 retransmissions (IEEE 802.11 Chap. 9.2.9) */ /* Drop duplicate 802.11 retransmissions (IEEE 802.11 Chap. 9.2.9) */
...@@ -423,6 +476,12 @@ ieee80211_rx_h_check(struct ieee80211_txrx_data *rx) ...@@ -423,6 +476,12 @@ ieee80211_rx_h_check(struct ieee80211_txrx_data *rx)
* deauth/disassoc frames when needed. In addition, hostapd is * deauth/disassoc frames when needed. In addition, hostapd is
* responsible for filtering on both auth and assoc states. * responsible for filtering on both auth and assoc states.
*/ */
#ifdef CONFIG_MAC80211_MESH
if (rx->sdata->vif.type == IEEE80211_IF_TYPE_MESH_POINT)
return ieee80211_rx_mesh_check(rx);
#endif
if (unlikely(((rx->fc & IEEE80211_FCTL_FTYPE) == IEEE80211_FTYPE_DATA || if (unlikely(((rx->fc & IEEE80211_FCTL_FTYPE) == IEEE80211_FTYPE_DATA ||
((rx->fc & IEEE80211_FCTL_FTYPE) == IEEE80211_FTYPE_CTL && ((rx->fc & IEEE80211_FCTL_FTYPE) == IEEE80211_FTYPE_CTL &&
(rx->fc & IEEE80211_FCTL_STYPE) == IEEE80211_STYPE_PSPOLL)) && (rx->fc & IEEE80211_FCTL_STYPE) == IEEE80211_STYPE_PSPOLL)) &&
...@@ -657,6 +716,8 @@ ieee80211_rx_h_sta_process(struct ieee80211_txrx_data *rx) ...@@ -657,6 +716,8 @@ ieee80211_rx_h_sta_process(struct ieee80211_txrx_data *rx)
/* Update last_rx only for unicast frames in order to prevent /* Update last_rx only for unicast frames in order to prevent
* the Probe Request frames (the only broadcast frames from a * the Probe Request frames (the only broadcast frames from a
* STA in infrastructure mode) from keeping a connection alive. * STA in infrastructure mode) from keeping a connection alive.
* Mesh beacons will update last_rx when if they are found to
* match the current local configuration when processed.
*/ */
sta->last_rx = jiffies; sta->last_rx = jiffies;
} }
...@@ -1050,6 +1111,23 @@ ieee80211_data_to_8023(struct ieee80211_txrx_data *rx) ...@@ -1050,6 +1111,23 @@ ieee80211_data_to_8023(struct ieee80211_txrx_data *rx)
hdrlen = ieee80211_get_hdrlen(fc); hdrlen = ieee80211_get_hdrlen(fc);
#ifdef CONFIG_MAC80211_MESH
if (sdata->vif.type == IEEE80211_IF_TYPE_MESH_POINT) {
int meshhdrlen = ieee80211_get_mesh_hdrlen(
(struct ieee80211s_hdr *) (skb->data + hdrlen));
/* Copy on cb:
* - mesh header: to be used for mesh forwarding
* decision. It will also be used as mesh header template at
* tx.c:ieee80211_subif_start_xmit() if interface
* type is mesh and skb->pkt_type == PACKET_OTHERHOST
* - ta: to be used if a RERR needs to be sent.
*/
memcpy(skb->cb, skb->data + hdrlen, meshhdrlen);
memcpy(MESH_PREQ(skb), hdr->addr2, ETH_ALEN);
hdrlen += meshhdrlen;
}
#endif
/* convert IEEE 802.11 header + possible LLC headers into Ethernet /* convert IEEE 802.11 header + possible LLC headers into Ethernet
* header * header
* IEEE 802.11 address fields: * IEEE 802.11 address fields:
...@@ -1083,9 +1161,10 @@ ieee80211_data_to_8023(struct ieee80211_txrx_data *rx) ...@@ -1083,9 +1161,10 @@ ieee80211_data_to_8023(struct ieee80211_txrx_data *rx)
memcpy(dst, hdr->addr3, ETH_ALEN); memcpy(dst, hdr->addr3, ETH_ALEN);
memcpy(src, hdr->addr4, ETH_ALEN); memcpy(src, hdr->addr4, ETH_ALEN);
if (unlikely(sdata->vif.type != IEEE80211_IF_TYPE_WDS)) { if (unlikely(sdata->vif.type != IEEE80211_IF_TYPE_WDS &&
if (net_ratelimit()) sdata->vif.type != IEEE80211_IF_TYPE_MESH_POINT)) {
printk(KERN_DEBUG "%s: dropped FromDS&ToDS " if (net_ratelimit())
printk(KERN_DEBUG "%s: dropped FromDS&ToDS "
"frame (RA=%s TA=%s DA=%s SA=%s)\n", "frame (RA=%s TA=%s DA=%s SA=%s)\n",
rx->dev->name, rx->dev->name,
print_mac(mac, hdr->addr1), print_mac(mac, hdr->addr1),
...@@ -1227,6 +1306,39 @@ ieee80211_deliver_skb(struct ieee80211_txrx_data *rx) ...@@ -1227,6 +1306,39 @@ ieee80211_deliver_skb(struct ieee80211_txrx_data *rx)
} }
} }
#ifdef CONFIG_MAC80211_MESH
/* Mesh forwarding */
if (sdata->vif.type == IEEE80211_IF_TYPE_MESH_POINT) {
u8 *mesh_ttl = &((struct ieee80211s_hdr *)skb->cb)->ttl;
(*mesh_ttl)--;
if (is_multicast_ether_addr(skb->data)) {
if (*mesh_ttl > 0) {
xmit_skb = skb_copy(skb, GFP_ATOMIC);
if (!xmit_skb && net_ratelimit())
printk(KERN_DEBUG "%s: failed to clone "
"multicast frame\n", dev->name);
else
xmit_skb->pkt_type = PACKET_OTHERHOST;
} else
sdata->u.sta.mshstats.dropped_frames_ttl++;
} else if (skb->pkt_type != PACKET_OTHERHOST &&
compare_ether_addr(dev->dev_addr, skb->data) != 0) {
if (*mesh_ttl == 0) {
sdata->u.sta.mshstats.dropped_frames_ttl++;
dev_kfree_skb(skb);
skb = NULL;
} else {
xmit_skb = skb;
xmit_skb->pkt_type = PACKET_OTHERHOST;
if (!(dev->flags & IFF_PROMISC))
skb = NULL;
}
}
}
#endif
if (skb) { if (skb) {
/* deliver to local stack */ /* deliver to local stack */
skb->protocol = eth_type_trans(skb, dev); skb->protocol = eth_type_trans(skb, dev);
...@@ -1444,7 +1556,8 @@ ieee80211_rx_h_mgmt(struct ieee80211_txrx_data *rx) ...@@ -1444,7 +1556,8 @@ ieee80211_rx_h_mgmt(struct ieee80211_txrx_data *rx)
sdata = IEEE80211_DEV_TO_SUB_IF(rx->dev); sdata = IEEE80211_DEV_TO_SUB_IF(rx->dev);
if ((sdata->vif.type == IEEE80211_IF_TYPE_STA || if ((sdata->vif.type == IEEE80211_IF_TYPE_STA ||
sdata->vif.type == IEEE80211_IF_TYPE_IBSS) && sdata->vif.type == IEEE80211_IF_TYPE_IBSS ||
sdata->vif.type == IEEE80211_IF_TYPE_MESH_POINT) &&
!(sdata->flags & IEEE80211_SDATA_USERSPACE_MLME)) !(sdata->flags & IEEE80211_SDATA_USERSPACE_MLME))
ieee80211_sta_rx_mgmt(rx->dev, rx->skb, rx->u.rx.status); ieee80211_sta_rx_mgmt(rx->dev, rx->skb, rx->u.rx.status);
else else
......
...@@ -26,6 +26,9 @@ ...@@ -26,6 +26,9 @@
#include "ieee80211_i.h" #include "ieee80211_i.h"
#include "ieee80211_led.h" #include "ieee80211_led.h"
#ifdef CONFIG_MAC80211_MESH
#include "mesh.h"
#endif
#include "wep.h" #include "wep.h"
#include "wpa.h" #include "wpa.h"
#include "wme.h" #include "wme.h"
...@@ -249,6 +252,9 @@ ieee80211_tx_h_check_assoc(struct ieee80211_txrx_data *tx) ...@@ -249,6 +252,9 @@ ieee80211_tx_h_check_assoc(struct ieee80211_txrx_data *tx)
(tx->fc & IEEE80211_FCTL_STYPE) != IEEE80211_STYPE_PROBE_REQ)) (tx->fc & IEEE80211_FCTL_STYPE) != IEEE80211_STYPE_PROBE_REQ))
return TX_DROP; return TX_DROP;
if (tx->sdata->vif.type == IEEE80211_IF_TYPE_MESH_POINT)
return TX_CONTINUE;
if (tx->flags & IEEE80211_TXRXD_TXPS_BUFFERED) if (tx->flags & IEEE80211_TXRXD_TXPS_BUFFERED)
return TX_CONTINUE; return TX_CONTINUE;
...@@ -1384,8 +1390,9 @@ int ieee80211_subif_start_xmit(struct sk_buff *skb, ...@@ -1384,8 +1390,9 @@ int ieee80211_subif_start_xmit(struct sk_buff *skb,
struct ieee80211_tx_packet_data *pkt_data; struct ieee80211_tx_packet_data *pkt_data;
struct ieee80211_sub_if_data *sdata; struct ieee80211_sub_if_data *sdata;
int ret = 1, head_need; int ret = 1, head_need;
u16 ethertype, hdrlen, fc; u16 ethertype, hdrlen, meshhdrlen = 0, fc;
struct ieee80211_hdr hdr; struct ieee80211_hdr hdr;
struct ieee80211s_hdr mesh_hdr;
const u8 *encaps_data; const u8 *encaps_data;
int encaps_len, skip_header_bytes; int encaps_len, skip_header_bytes;
int nh_pos, h_pos; int nh_pos, h_pos;
...@@ -1427,6 +1434,37 @@ int ieee80211_subif_start_xmit(struct sk_buff *skb, ...@@ -1427,6 +1434,37 @@ int ieee80211_subif_start_xmit(struct sk_buff *skb,
memcpy(hdr.addr4, skb->data + ETH_ALEN, ETH_ALEN); memcpy(hdr.addr4, skb->data + ETH_ALEN, ETH_ALEN);
hdrlen = 30; hdrlen = 30;
break; break;
#ifdef CONFIG_MAC80211_MESH
case IEEE80211_IF_TYPE_MESH_POINT:
fc |= IEEE80211_FCTL_FROMDS | IEEE80211_FCTL_TODS;
/* RA TA DA SA */
if (is_multicast_ether_addr(skb->data))
memcpy(hdr.addr1, skb->data, ETH_ALEN);
else if (mesh_nexthop_lookup(hdr.addr1, skb, dev))
return 0;
memcpy(hdr.addr2, dev->dev_addr, ETH_ALEN);
memcpy(hdr.addr3, skb->data, ETH_ALEN);
memcpy(hdr.addr4, skb->data + ETH_ALEN, ETH_ALEN);
if (skb->pkt_type == PACKET_OTHERHOST) {
/* Forwarded frame, keep mesh ttl and seqnum */
struct ieee80211s_hdr *prev_meshhdr;
prev_meshhdr = ((struct ieee80211s_hdr *)skb->cb);
meshhdrlen = ieee80211_get_mesh_hdrlen(prev_meshhdr);
memcpy(&mesh_hdr, prev_meshhdr, meshhdrlen);
sdata->u.sta.mshstats.fwded_frames++;
} else {
if (!sdata->u.sta.mshcfg.dot11MeshTTL) {
/* Do not send frames with mesh_ttl == 0 */
sdata->u.sta.mshstats.dropped_frames_ttl++;
ret = 0;
goto fail;
}
meshhdrlen = ieee80211_new_mesh_header(&mesh_hdr,
sdata);
}
hdrlen = 30;
break;
#endif
case IEEE80211_IF_TYPE_STA: case IEEE80211_IF_TYPE_STA:
fc |= IEEE80211_FCTL_TODS; fc |= IEEE80211_FCTL_TODS;
/* BSSID SA DA */ /* BSSID SA DA */
...@@ -1471,8 +1509,8 @@ int ieee80211_subif_start_xmit(struct sk_buff *skb, ...@@ -1471,8 +1509,8 @@ int ieee80211_subif_start_xmit(struct sk_buff *skb,
* EAPOL frames from the local station. * EAPOL frames from the local station.
*/ */
if (unlikely(!is_multicast_ether_addr(hdr.addr1) && if (unlikely(!is_multicast_ether_addr(hdr.addr1) &&
!(sta_flags & WLAN_STA_AUTHORIZED) && !(sta_flags & WLAN_STA_AUTHORIZED) &&
!(ethertype == ETH_P_PAE && !(ethertype == ETH_P_PAE &&
compare_ether_addr(dev->dev_addr, compare_ether_addr(dev->dev_addr,
skb->data + ETH_ALEN) == 0))) { skb->data + ETH_ALEN) == 0))) {
#ifdef CONFIG_MAC80211_VERBOSE_DEBUG #ifdef CONFIG_MAC80211_VERBOSE_DEBUG
...@@ -1525,7 +1563,7 @@ int ieee80211_subif_start_xmit(struct sk_buff *skb, ...@@ -1525,7 +1563,7 @@ int ieee80211_subif_start_xmit(struct sk_buff *skb,
* build in headroom in __dev_alloc_skb() (linux/skbuff.h) and * build in headroom in __dev_alloc_skb() (linux/skbuff.h) and
* alloc_skb() (net/core/skbuff.c) * alloc_skb() (net/core/skbuff.c)
*/ */
head_need = hdrlen + encaps_len + local->tx_headroom; head_need = hdrlen + encaps_len + meshhdrlen + local->tx_headroom;
head_need -= skb_headroom(skb); head_need -= skb_headroom(skb);
/* We are going to modify skb data, so make a copy of it if happens to /* We are going to modify skb data, so make a copy of it if happens to
...@@ -1559,6 +1597,12 @@ int ieee80211_subif_start_xmit(struct sk_buff *skb, ...@@ -1559,6 +1597,12 @@ int ieee80211_subif_start_xmit(struct sk_buff *skb,
h_pos += encaps_len; h_pos += encaps_len;
} }
if (meshhdrlen > 0) {
memcpy(skb_push(skb, meshhdrlen), &mesh_hdr, meshhdrlen);
nh_pos += meshhdrlen;
h_pos += meshhdrlen;
}
if (fc & IEEE80211_STYPE_QOS_DATA) { if (fc & IEEE80211_STYPE_QOS_DATA) {
__le16 *qos_control; __le16 *qos_control;
...@@ -1734,6 +1778,40 @@ static void ieee80211_beacon_add_tim(struct ieee80211_local *local, ...@@ -1734,6 +1778,40 @@ static void ieee80211_beacon_add_tim(struct ieee80211_local *local,
read_unlock_bh(&local->sta_lock); read_unlock_bh(&local->sta_lock);
} }
#ifdef CONFIG_MAC80211_MESH
static struct sk_buff *ieee80211_mesh_beacon_get(struct net_device *dev)
{
struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
struct sk_buff *skb = dev_alloc_skb(local->hw.extra_tx_headroom + 400);
struct ieee80211_mgmt *mgmt;
u8 *pos;
if (!skb)
return NULL;
skb_reserve(skb, local->hw.extra_tx_headroom);
mgmt = (struct ieee80211_mgmt *)
skb_put(skb, 24 + sizeof(mgmt->u.beacon));
memset(mgmt, 0, 24 + sizeof(mgmt->u.beacon));
mgmt->frame_control = IEEE80211_FC(IEEE80211_FTYPE_MGMT,
IEEE80211_STYPE_BEACON);
memset(mgmt->da, 0xff, ETH_ALEN);
memcpy(mgmt->sa, dev->dev_addr, ETH_ALEN);
/* BSSID is left zeroed, wildcard value */
mgmt->u.beacon.beacon_int =
cpu_to_le16(local->hw.conf.beacon_int);
mgmt->u.beacon.capab_info = 0x0; /* 0x0 for MPs */
pos = skb_put(skb, 2);
*pos++ = WLAN_EID_SSID;
*pos++ = 0x0;
mesh_mgmt_ies_add(skb, dev);
return skb;
}
#endif
struct sk_buff *ieee80211_beacon_get(struct ieee80211_hw *hw, struct sk_buff *ieee80211_beacon_get(struct ieee80211_hw *hw,
struct ieee80211_vif *vif, struct ieee80211_vif *vif,
struct ieee80211_tx_control *control) struct ieee80211_tx_control *control)
...@@ -1746,6 +1824,8 @@ struct sk_buff *ieee80211_beacon_get(struct ieee80211_hw *hw, ...@@ -1746,6 +1824,8 @@ struct sk_buff *ieee80211_beacon_get(struct ieee80211_hw *hw,
struct rate_selection rsel; struct rate_selection rsel;
struct beacon_data *beacon; struct beacon_data *beacon;
struct ieee80211_supported_band *sband; struct ieee80211_supported_band *sband;
int *num_beacons;
int err = 0;
sband = local->hw.wiphy->bands[local->hw.conf.channel->band]; sband = local->hw.wiphy->bands[local->hw.conf.channel->band];
...@@ -1753,11 +1833,51 @@ struct sk_buff *ieee80211_beacon_get(struct ieee80211_hw *hw, ...@@ -1753,11 +1833,51 @@ struct sk_buff *ieee80211_beacon_get(struct ieee80211_hw *hw,
sdata = vif_to_sdata(vif); sdata = vif_to_sdata(vif);
bdev = sdata->dev; bdev = sdata->dev;
ap = &sdata->u.ap;
beacon = rcu_dereference(ap->beacon); switch (sdata->vif.type) {
case IEEE80211_IF_TYPE_AP:
ap = &sdata->u.ap;
beacon = rcu_dereference(ap->beacon);
if (!ap || !beacon) {
err = -1;
break;
}
/* headroom, head length, tail length and maximum TIM length */
skb = dev_alloc_skb(local->tx_headroom + beacon->head_len +
beacon->tail_len + 256);
if (!skb)
goto out;
skb_reserve(skb, local->tx_headroom);
memcpy(skb_put(skb, beacon->head_len), beacon->head,
beacon->head_len);
if (!ap || sdata->vif.type != IEEE80211_IF_TYPE_AP || !beacon) { ieee80211_include_sequence(sdata,
(struct ieee80211_hdr *)skb->data);
ieee80211_beacon_add_tim(local, ap, skb, beacon);
if (beacon->tail)
memcpy(skb_put(skb, beacon->tail_len), beacon->tail,
beacon->tail_len);
num_beacons = &ap->num_beacons;
break;
#ifdef CONFIG_MAC80211_MESH
case IEEE80211_IF_TYPE_MESH_POINT:
skb = ieee80211_mesh_beacon_get(bdev);
num_beacons = &sdata->u.sta.num_beacons;
break;
#endif
default:
err = -1;
break;
}
if (err) {
#ifdef CONFIG_MAC80211_VERBOSE_DEBUG #ifdef CONFIG_MAC80211_VERBOSE_DEBUG
if (net_ratelimit()) if (net_ratelimit())
printk(KERN_DEBUG "no beacon data avail for %s\n", printk(KERN_DEBUG "no beacon data avail for %s\n",
...@@ -1767,24 +1887,6 @@ struct sk_buff *ieee80211_beacon_get(struct ieee80211_hw *hw, ...@@ -1767,24 +1887,6 @@ struct sk_buff *ieee80211_beacon_get(struct ieee80211_hw *hw,
goto out; goto out;
} }
/* headroom, head length, tail length and maximum TIM length */
skb = dev_alloc_skb(local->tx_headroom + beacon->head_len +
beacon->tail_len + 256);
if (!skb)
goto out;
skb_reserve(skb, local->tx_headroom);
memcpy(skb_put(skb, beacon->head_len), beacon->head,
beacon->head_len);
ieee80211_include_sequence(sdata, (struct ieee80211_hdr *)skb->data);
ieee80211_beacon_add_tim(local, ap, skb, beacon);
if (beacon->tail)
memcpy(skb_put(skb, beacon->tail_len), beacon->tail,
beacon->tail_len);
if (control) { if (control) {
rate_control_get_rate(local->mdev, sband, skb, &rsel); rate_control_get_rate(local->mdev, sband, skb, &rsel);
if (!rsel.rate) { if (!rsel.rate) {
...@@ -1808,10 +1910,8 @@ struct sk_buff *ieee80211_beacon_get(struct ieee80211_hw *hw, ...@@ -1808,10 +1910,8 @@ struct sk_buff *ieee80211_beacon_get(struct ieee80211_hw *hw,
control->retry_limit = 1; control->retry_limit = 1;
control->flags |= IEEE80211_TXCTL_CLEAR_PS_FILT; control->flags |= IEEE80211_TXCTL_CLEAR_PS_FILT;
} }
(*num_beacons)++;
ap->num_beacons++; out:
out:
rcu_read_unlock(); rcu_read_unlock();
return skb; return skb;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment