summaryrefslogtreecommitdiff
path: root/net/mac80211/mesh_plink.c
diff options
context:
space:
mode:
Diffstat (limited to 'net/mac80211/mesh_plink.c')
-rw-r--r--net/mac80211/mesh_plink.c709
1 files changed, 368 insertions, 341 deletions
diff --git a/net/mac80211/mesh_plink.c b/net/mac80211/mesh_plink.c
index 4301aa5aa227..e8f60aa2e848 100644
--- a/net/mac80211/mesh_plink.c
+++ b/net/mac80211/mesh_plink.c
@@ -19,12 +19,6 @@
#define mod_plink_timer(s, t) (mod_timer(&s->plink_timer, \
jiffies + HZ * t / 1000))
-/* We only need a valid sta if user configured a minimum rssi_threshold. */
-#define rssi_threshold_check(sta, sdata) \
- (sdata->u.mesh.mshcfg.rssi_threshold == 0 ||\
- (sta && (s8) -ewma_read(&sta->avg_signal) > \
- sdata->u.mesh.mshcfg.rssi_threshold))
-
enum plink_event {
PLINK_UNDEFINED,
OPN_ACPT,
@@ -61,7 +55,17 @@ static const char * const mplevents[] = {
static int mesh_plink_frame_tx(struct ieee80211_sub_if_data *sdata,
enum ieee80211_self_protected_actioncode action,
- u8 *da, __le16 llid, __le16 plid, __le16 reason);
+ u8 *da, u16 llid, u16 plid, u16 reason);
+
+
+/* We only need a valid sta if user configured a minimum rssi_threshold. */
+static bool rssi_threshold_check(struct ieee80211_sub_if_data *sdata,
+ struct sta_info *sta)
+{
+ s32 rssi_threshold = sdata->u.mesh.mshcfg.rssi_threshold;
+ return rssi_threshold == 0 ||
+ (sta && (s8) -ewma_read(&sta->avg_signal) > rssi_threshold);
+}
/**
* mesh_plink_fsm_restart - restart a mesh peer link finite state machine
@@ -242,7 +246,7 @@ u32 mesh_plink_deactivate(struct sta_info *sta)
spin_lock_bh(&sta->lock);
changed = __mesh_plink_deactivate(sta);
- sta->reason = cpu_to_le16(WLAN_REASON_MESH_PEER_CANCELED);
+ sta->reason = WLAN_REASON_MESH_PEER_CANCELED;
mesh_plink_frame_tx(sdata, WLAN_SP_MESH_PEERING_CLOSE,
sta->sta.addr, sta->llid, sta->plid,
sta->reason);
@@ -253,7 +257,7 @@ u32 mesh_plink_deactivate(struct sta_info *sta)
static int mesh_plink_frame_tx(struct ieee80211_sub_if_data *sdata,
enum ieee80211_self_protected_actioncode action,
- u8 *da, __le16 llid, __le16 plid, __le16 reason)
+ u8 *da, u16 llid, u16 plid, u16 reason)
{
struct ieee80211_local *local = sdata->local;
struct sk_buff *skb;
@@ -279,7 +283,7 @@ static int mesh_plink_frame_tx(struct ieee80211_sub_if_data *sdata,
2 + 8 + /* peering IE */
sdata->u.mesh.ie_len);
if (!skb)
- return -1;
+ return err;
info = IEEE80211_SKB_CB(skb);
skb_reserve(skb, local->tx_headroom);
mgmt = (struct ieee80211_mgmt *) skb_put(skb, hdr_len);
@@ -301,7 +305,7 @@ static int mesh_plink_frame_tx(struct ieee80211_sub_if_data *sdata,
if (action == WLAN_SP_MESH_PEERING_CONFIRM) {
/* AID */
pos = skb_put(skb, 2);
- memcpy(pos + 2, &plid, 2);
+ put_unaligned_le16(plid, pos + 2);
}
if (ieee80211_add_srates_ie(sdata, skb, true, band) ||
ieee80211_add_ext_srates_ie(sdata, skb, true, band) ||
@@ -343,14 +347,14 @@ static int mesh_plink_frame_tx(struct ieee80211_sub_if_data *sdata,
*pos++ = ie_len;
memcpy(pos, &peering_proto, 2);
pos += 2;
- memcpy(pos, &llid, 2);
+ put_unaligned_le16(llid, pos);
pos += 2;
if (include_plid) {
- memcpy(pos, &plid, 2);
+ put_unaligned_le16(plid, pos);
pos += 2;
}
if (action == WLAN_SP_MESH_PEERING_CLOSE) {
- memcpy(pos, &reason, 2);
+ put_unaligned_le16(reason, pos);
pos += 2;
}
@@ -433,6 +437,7 @@ __mesh_sta_info_alloc(struct ieee80211_sub_if_data *sdata, u8 *hw_addr)
sta_info_pre_move_state(sta, IEEE80211_STA_AUTHORIZED);
set_sta_flag(sta, WLAN_STA_WME);
+ sta->sta.wme = true;
return sta;
}
@@ -518,7 +523,7 @@ void mesh_neighbour_update(struct ieee80211_sub_if_data *sdata,
sta->plink_state == NL80211_PLINK_LISTEN &&
sdata->u.mesh.accepting_plinks &&
sdata->u.mesh.mshcfg.auto_open_plinks &&
- rssi_threshold_check(sta, sdata))
+ rssi_threshold_check(sdata, sta))
changed = mesh_plink_open(sta);
ieee80211_mps_frame_release(sta, elems);
@@ -530,9 +535,10 @@ out:
static void mesh_plink_timer(unsigned long data)
{
struct sta_info *sta;
- __le16 llid, plid, reason;
+ u16 reason = 0;
struct ieee80211_sub_if_data *sdata;
struct mesh_config *mshcfg;
+ enum ieee80211_self_protected_actioncode action = 0;
/*
* This STA is valid because sta_info_destroy() will
@@ -553,9 +559,6 @@ static void mesh_plink_timer(unsigned long data)
mpl_dbg(sta->sdata,
"Mesh plink timer for %pM fired on state %s\n",
sta->sta.addr, mplstates[sta->plink_state]);
- reason = 0;
- llid = sta->llid;
- plid = sta->plid;
sdata = sta->sdata;
mshcfg = &sdata->u.mesh.mshcfg;
@@ -574,33 +577,31 @@ static void mesh_plink_timer(unsigned long data)
rand % sta->plink_timeout;
++sta->plink_retries;
mod_plink_timer(sta, sta->plink_timeout);
- spin_unlock_bh(&sta->lock);
- mesh_plink_frame_tx(sdata, WLAN_SP_MESH_PEERING_OPEN,
- sta->sta.addr, llid, 0, 0);
+ action = WLAN_SP_MESH_PEERING_OPEN;
break;
}
- reason = cpu_to_le16(WLAN_REASON_MESH_MAX_RETRIES);
+ reason = WLAN_REASON_MESH_MAX_RETRIES;
/* fall through on else */
case NL80211_PLINK_CNF_RCVD:
/* confirm timer */
if (!reason)
- reason = cpu_to_le16(WLAN_REASON_MESH_CONFIRM_TIMEOUT);
+ reason = WLAN_REASON_MESH_CONFIRM_TIMEOUT;
sta->plink_state = NL80211_PLINK_HOLDING;
mod_plink_timer(sta, mshcfg->dot11MeshHoldingTimeout);
- spin_unlock_bh(&sta->lock);
- mesh_plink_frame_tx(sdata, WLAN_SP_MESH_PEERING_CLOSE,
- sta->sta.addr, llid, plid, reason);
+ action = WLAN_SP_MESH_PEERING_CLOSE;
break;
case NL80211_PLINK_HOLDING:
/* holding timer */
del_timer(&sta->plink_timer);
mesh_plink_fsm_restart(sta);
- spin_unlock_bh(&sta->lock);
break;
default:
- spin_unlock_bh(&sta->lock);
break;
}
+ spin_unlock_bh(&sta->lock);
+ if (action)
+ mesh_plink_frame_tx(sdata, action, sta->sta.addr,
+ sta->llid, sta->plid, reason);
}
static inline void mesh_plink_timer_set(struct sta_info *sta, int timeout)
@@ -612,9 +613,40 @@ static inline void mesh_plink_timer_set(struct sta_info *sta, int timeout)
add_timer(&sta->plink_timer);
}
+static bool llid_in_use(struct ieee80211_sub_if_data *sdata,
+ u16 llid)
+{
+ struct ieee80211_local *local = sdata->local;
+ bool in_use = false;
+ struct sta_info *sta;
+
+ rcu_read_lock();
+ list_for_each_entry_rcu(sta, &local->sta_list, list) {
+ if (!memcmp(&sta->llid, &llid, sizeof(llid))) {
+ in_use = true;
+ break;
+ }
+ }
+ rcu_read_unlock();
+
+ return in_use;
+}
+
+static u16 mesh_get_new_llid(struct ieee80211_sub_if_data *sdata)
+{
+ u16 llid;
+
+ do {
+ get_random_bytes(&llid, sizeof(llid));
+ /* for mesh PS we still only have the AID range for TIM bits */
+ llid = (llid % IEEE80211_MAX_AID) + 1;
+ } while (llid_in_use(sdata, llid));
+
+ return llid;
+}
+
u32 mesh_plink_open(struct sta_info *sta)
{
- __le16 llid;
struct ieee80211_sub_if_data *sdata = sta->sdata;
u32 changed;
@@ -622,8 +654,7 @@ u32 mesh_plink_open(struct sta_info *sta)
return 0;
spin_lock_bh(&sta->lock);
- get_random_bytes(&llid, 2);
- sta->llid = llid;
+ sta->llid = mesh_get_new_llid(sdata);
if (sta->plink_state != NL80211_PLINK_LISTEN &&
sta->plink_state != NL80211_PLINK_BLOCKED) {
spin_unlock_bh(&sta->lock);
@@ -640,7 +671,7 @@ u32 mesh_plink_open(struct sta_info *sta)
changed = ieee80211_mps_local_status_update(sdata);
mesh_plink_frame_tx(sdata, WLAN_SP_MESH_PEERING_OPEN,
- sta->sta.addr, llid, 0, 0);
+ sta->sta.addr, sta->llid, 0, 0);
return changed;
}
@@ -656,390 +687,147 @@ u32 mesh_plink_block(struct sta_info *sta)
return changed;
}
-
-void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata,
- struct ieee80211_mgmt *mgmt, size_t len,
- struct ieee80211_rx_status *rx_status)
+static void mesh_plink_close(struct ieee80211_sub_if_data *sdata,
+ struct sta_info *sta,
+ enum plink_event event)
{
struct mesh_config *mshcfg = &sdata->u.mesh.mshcfg;
- struct ieee802_11_elems elems;
- struct sta_info *sta;
- enum plink_event event;
- enum ieee80211_self_protected_actioncode ftype;
- size_t baselen;
- bool matches_local = true;
- u8 ie_len;
- u8 *baseaddr;
- u32 changed = 0;
- __le16 plid, llid, reason;
-
- /* need action_code, aux */
- if (len < IEEE80211_MIN_ACTION_SIZE + 3)
- return;
-
- if (sdata->u.mesh.user_mpm)
- /* userspace must register for these */
- return;
-
- if (is_multicast_ether_addr(mgmt->da)) {
- mpl_dbg(sdata,
- "Mesh plink: ignore frame from multicast address\n");
- return;
- }
-
- baseaddr = mgmt->u.action.u.self_prot.variable;
- baselen = (u8 *) mgmt->u.action.u.self_prot.variable - (u8 *) mgmt;
- if (mgmt->u.action.u.self_prot.action_code ==
- WLAN_SP_MESH_PEERING_CONFIRM) {
- baseaddr += 4;
- baselen += 4;
- }
- ieee802_11_parse_elems(baseaddr, len - baselen, true, &elems);
-
- if (!elems.peering) {
- mpl_dbg(sdata,
- "Mesh plink: missing necessary peer link ie\n");
- return;
- }
- if (elems.rsn_len &&
- sdata->u.mesh.security == IEEE80211_MESH_SEC_NONE) {
- mpl_dbg(sdata,
- "Mesh plink: can't establish link with secure peer\n");
- return;
- }
+ u16 reason = (event == CLS_ACPT) ?
+ WLAN_REASON_MESH_CLOSE : WLAN_REASON_MESH_CONFIG;
- ftype = mgmt->u.action.u.self_prot.action_code;
- ie_len = elems.peering_len;
- if ((ftype == WLAN_SP_MESH_PEERING_OPEN && ie_len != 4) ||
- (ftype == WLAN_SP_MESH_PEERING_CONFIRM && ie_len != 6) ||
- (ftype == WLAN_SP_MESH_PEERING_CLOSE && ie_len != 6
- && ie_len != 8)) {
- mpl_dbg(sdata,
- "Mesh plink: incorrect plink ie length %d %d\n",
- ftype, ie_len);
- return;
- }
-
- if (ftype != WLAN_SP_MESH_PEERING_CLOSE &&
- (!elems.mesh_id || !elems.mesh_config)) {
- mpl_dbg(sdata, "Mesh plink: missing necessary ie\n");
- return;
- }
- /* Note the lines below are correct, the llid in the frame is the plid
- * from the point of view of this host.
- */
- memcpy(&plid, PLINK_GET_LLID(elems.peering), 2);
- if (ftype == WLAN_SP_MESH_PEERING_CONFIRM ||
- (ftype == WLAN_SP_MESH_PEERING_CLOSE && ie_len == 8))
- memcpy(&llid, PLINK_GET_PLID(elems.peering), 2);
-
- /* WARNING: Only for sta pointer, is dropped & re-acquired */
- rcu_read_lock();
-
- sta = sta_info_get(sdata, mgmt->sa);
- if (!sta && ftype != WLAN_SP_MESH_PEERING_OPEN) {
- mpl_dbg(sdata, "Mesh plink: cls or cnf from unknown peer\n");
- rcu_read_unlock();
- return;
- }
-
- if (ftype == WLAN_SP_MESH_PEERING_OPEN &&
- !rssi_threshold_check(sta, sdata)) {
- mpl_dbg(sdata, "Mesh plink: %pM does not meet rssi threshold\n",
- mgmt->sa);
- rcu_read_unlock();
- return;
- }
-
- if (sta && !test_sta_flag(sta, WLAN_STA_AUTH)) {
- mpl_dbg(sdata, "Mesh plink: Action frame from non-authed peer\n");
- rcu_read_unlock();
- return;
- }
+ sta->reason = reason;
+ sta->plink_state = NL80211_PLINK_HOLDING;
+ mod_plink_timer(sta, mshcfg->dot11MeshHoldingTimeout);
+}
- if (sta && sta->plink_state == NL80211_PLINK_BLOCKED) {
- rcu_read_unlock();
- return;
- }
+static u32 mesh_plink_establish(struct ieee80211_sub_if_data *sdata,
+ struct sta_info *sta)
+{
+ struct mesh_config *mshcfg = &sdata->u.mesh.mshcfg;
+ u32 changed = 0;
- /* Now we will figure out the appropriate event... */
- event = PLINK_UNDEFINED;
- if (ftype != WLAN_SP_MESH_PEERING_CLOSE &&
- !mesh_matches_local(sdata, &elems)) {
- matches_local = false;
- switch (ftype) {
- case WLAN_SP_MESH_PEERING_OPEN:
- event = OPN_RJCT;
- break;
- case WLAN_SP_MESH_PEERING_CONFIRM:
- event = CNF_RJCT;
- break;
- default:
- break;
- }
- }
+ del_timer(&sta->plink_timer);
+ sta->plink_state = NL80211_PLINK_ESTAB;
+ changed |= mesh_plink_inc_estab_count(sdata);
+ changed |= mesh_set_ht_prot_mode(sdata);
+ changed |= mesh_set_short_slot_time(sdata);
+ mpl_dbg(sdata, "Mesh plink with %pM ESTABLISHED\n", sta->sta.addr);
+ ieee80211_mps_sta_status_update(sta);
+ changed |= ieee80211_mps_set_sta_local_pm(sta, mshcfg->power_mode);
+ return changed;
+}
- if (!sta && !matches_local) {
- rcu_read_unlock();
- reason = cpu_to_le16(WLAN_REASON_MESH_CONFIG);
- llid = 0;
- mesh_plink_frame_tx(sdata, WLAN_SP_MESH_PEERING_CLOSE,
- mgmt->sa, llid, plid, reason);
- return;
- } else if (!sta) {
- /* ftype == WLAN_SP_MESH_PEERING_OPEN */
- if (!mesh_plink_free_count(sdata)) {
- mpl_dbg(sdata, "Mesh plink error: no more free plinks\n");
- rcu_read_unlock();
- return;
- }
- event = OPN_ACPT;
- } else if (matches_local) {
- switch (ftype) {
- case WLAN_SP_MESH_PEERING_OPEN:
- if (!mesh_plink_free_count(sdata) ||
- (sta->plid && sta->plid != plid))
- event = OPN_IGNR;
- else
- event = OPN_ACPT;
- break;
- case WLAN_SP_MESH_PEERING_CONFIRM:
- if (!mesh_plink_free_count(sdata) ||
- (sta->llid != llid || sta->plid != plid))
- event = CNF_IGNR;
- else
- event = CNF_ACPT;
- break;
- case WLAN_SP_MESH_PEERING_CLOSE:
- if (sta->plink_state == NL80211_PLINK_ESTAB)
- /* Do not check for llid or plid. This does not
- * follow the standard but since multiple plinks
- * per sta are not supported, it is necessary in
- * order to avoid a livelock when MP A sees an
- * establish peer link to MP B but MP B does not
- * see it. This can be caused by a timeout in
- * B's peer link establishment or B beign
- * restarted.
- */
- event = CLS_ACPT;
- else if (sta->plid != plid)
- event = CLS_IGNR;
- else if (ie_len == 7 && sta->llid != llid)
- event = CLS_IGNR;
- else
- event = CLS_ACPT;
- break;
- default:
- mpl_dbg(sdata, "Mesh plink: unknown frame subtype\n");
- rcu_read_unlock();
- return;
- }
- }
+/**
+ * mesh_plink_fsm - step @sta MPM based on @event
+ *
+ * @sdata: interface
+ * @sta: mesh neighbor
+ * @event: peering event
+ *
+ * Return: changed MBSS flags
+ */
+static u32 mesh_plink_fsm(struct ieee80211_sub_if_data *sdata,
+ struct sta_info *sta, enum plink_event event)
+{
+ struct mesh_config *mshcfg = &sdata->u.mesh.mshcfg;
+ enum ieee80211_self_protected_actioncode action = 0;
+ u32 changed = 0;
- if (event == OPN_ACPT) {
- rcu_read_unlock();
- /* allocate sta entry if necessary and update info */
- sta = mesh_sta_info_get(sdata, mgmt->sa, &elems);
- if (!sta) {
- mpl_dbg(sdata, "Mesh plink: failed to init peer!\n");
- rcu_read_unlock();
- return;
- }
- }
+ mpl_dbg(sdata, "peer %pM in state %s got event %s\n", sta->sta.addr,
+ mplstates[sta->plink_state], mplevents[event]);
- mpl_dbg(sdata, "peer %pM in state %s got event %s\n", mgmt->sa,
- mplstates[sta->plink_state], mplevents[event]);
- reason = 0;
spin_lock_bh(&sta->lock);
switch (sta->plink_state) {
- /* spin_unlock as soon as state is updated at each case */
case NL80211_PLINK_LISTEN:
switch (event) {
case CLS_ACPT:
mesh_plink_fsm_restart(sta);
- spin_unlock_bh(&sta->lock);
break;
case OPN_ACPT:
sta->plink_state = NL80211_PLINK_OPN_RCVD;
- sta->plid = plid;
- get_random_bytes(&llid, 2);
- sta->llid = llid;
+ sta->llid = mesh_get_new_llid(sdata);
mesh_plink_timer_set(sta,
mshcfg->dot11MeshRetryTimeout);
/* set the non-peer mode to active during peering */
changed |= ieee80211_mps_local_status_update(sdata);
-
- spin_unlock_bh(&sta->lock);
- mesh_plink_frame_tx(sdata,
- WLAN_SP_MESH_PEERING_OPEN,
- sta->sta.addr, llid, 0, 0);
- mesh_plink_frame_tx(sdata,
- WLAN_SP_MESH_PEERING_CONFIRM,
- sta->sta.addr, llid, plid, 0);
+ action = WLAN_SP_MESH_PEERING_OPEN;
break;
default:
- spin_unlock_bh(&sta->lock);
break;
}
break;
-
case NL80211_PLINK_OPN_SNT:
switch (event) {
case OPN_RJCT:
case CNF_RJCT:
- reason = cpu_to_le16(WLAN_REASON_MESH_CONFIG);
case CLS_ACPT:
- if (!reason)
- reason = cpu_to_le16(WLAN_REASON_MESH_CLOSE);
- sta->reason = reason;
- sta->plink_state = NL80211_PLINK_HOLDING;
- if (!mod_plink_timer(sta,
- mshcfg->dot11MeshHoldingTimeout))
- sta->ignore_plink_timer = true;
-
- llid = sta->llid;
- spin_unlock_bh(&sta->lock);
- mesh_plink_frame_tx(sdata,
- WLAN_SP_MESH_PEERING_CLOSE,
- sta->sta.addr, llid, plid, reason);
+ mesh_plink_close(sdata, sta, event);
+ action = WLAN_SP_MESH_PEERING_CLOSE;
break;
case OPN_ACPT:
/* retry timer is left untouched */
sta->plink_state = NL80211_PLINK_OPN_RCVD;
- sta->plid = plid;
- llid = sta->llid;
- spin_unlock_bh(&sta->lock);
- mesh_plink_frame_tx(sdata,
- WLAN_SP_MESH_PEERING_CONFIRM,
- sta->sta.addr, llid, plid, 0);
+ action = WLAN_SP_MESH_PEERING_CONFIRM;
break;
case CNF_ACPT:
sta->plink_state = NL80211_PLINK_CNF_RCVD;
if (!mod_plink_timer(sta,
mshcfg->dot11MeshConfirmTimeout))
sta->ignore_plink_timer = true;
-
- spin_unlock_bh(&sta->lock);
break;
default:
- spin_unlock_bh(&sta->lock);
break;
}
break;
-
case NL80211_PLINK_OPN_RCVD:
switch (event) {
case OPN_RJCT:
case CNF_RJCT:
- reason = cpu_to_le16(WLAN_REASON_MESH_CONFIG);
case CLS_ACPT:
- if (!reason)
- reason = cpu_to_le16(WLAN_REASON_MESH_CLOSE);
- sta->reason = reason;
- sta->plink_state = NL80211_PLINK_HOLDING;
- if (!mod_plink_timer(sta,
- mshcfg->dot11MeshHoldingTimeout))
- sta->ignore_plink_timer = true;
-
- llid = sta->llid;
- spin_unlock_bh(&sta->lock);
- mesh_plink_frame_tx(sdata, WLAN_SP_MESH_PEERING_CLOSE,
- sta->sta.addr, llid, plid, reason);
+ mesh_plink_close(sdata, sta, event);
+ action = WLAN_SP_MESH_PEERING_CLOSE;
break;
case OPN_ACPT:
- llid = sta->llid;
- spin_unlock_bh(&sta->lock);
- mesh_plink_frame_tx(sdata,
- WLAN_SP_MESH_PEERING_CONFIRM,
- sta->sta.addr, llid, plid, 0);
+ action = WLAN_SP_MESH_PEERING_CONFIRM;
break;
case CNF_ACPT:
- del_timer(&sta->plink_timer);
- sta->plink_state = NL80211_PLINK_ESTAB;
- spin_unlock_bh(&sta->lock);
- changed |= mesh_plink_inc_estab_count(sdata);
- changed |= mesh_set_ht_prot_mode(sdata);
- changed |= mesh_set_short_slot_time(sdata);
- mpl_dbg(sdata, "Mesh plink with %pM ESTABLISHED\n",
- sta->sta.addr);
- ieee80211_mps_sta_status_update(sta);
- changed |= ieee80211_mps_set_sta_local_pm(sta,
- mshcfg->power_mode);
+ changed |= mesh_plink_establish(sdata, sta);
break;
default:
- spin_unlock_bh(&sta->lock);
break;
}
break;
-
case NL80211_PLINK_CNF_RCVD:
switch (event) {
case OPN_RJCT:
case CNF_RJCT:
- reason = cpu_to_le16(WLAN_REASON_MESH_CONFIG);
case CLS_ACPT:
- if (!reason)
- reason = cpu_to_le16(WLAN_REASON_MESH_CLOSE);
- sta->reason = reason;
- sta->plink_state = NL80211_PLINK_HOLDING;
- if (!mod_plink_timer(sta,
- mshcfg->dot11MeshHoldingTimeout))
- sta->ignore_plink_timer = true;
-
- llid = sta->llid;
- spin_unlock_bh(&sta->lock);
- mesh_plink_frame_tx(sdata,
- WLAN_SP_MESH_PEERING_CLOSE,
- sta->sta.addr, llid, plid, reason);
+ mesh_plink_close(sdata, sta, event);
+ action = WLAN_SP_MESH_PEERING_CLOSE;
break;
case OPN_ACPT:
- del_timer(&sta->plink_timer);
- sta->plink_state = NL80211_PLINK_ESTAB;
- spin_unlock_bh(&sta->lock);
- changed |= mesh_plink_inc_estab_count(sdata);
- changed |= mesh_set_ht_prot_mode(sdata);
- changed |= mesh_set_short_slot_time(sdata);
- mpl_dbg(sdata, "Mesh plink with %pM ESTABLISHED\n",
- sta->sta.addr);
- mesh_plink_frame_tx(sdata,
- WLAN_SP_MESH_PEERING_CONFIRM,
- sta->sta.addr, llid, plid, 0);
- ieee80211_mps_sta_status_update(sta);
- changed |= ieee80211_mps_set_sta_local_pm(sta,
- mshcfg->power_mode);
+ changed |= mesh_plink_establish(sdata, sta);
+ action = WLAN_SP_MESH_PEERING_CONFIRM;
break;
default:
- spin_unlock_bh(&sta->lock);
break;
}
break;
-
case NL80211_PLINK_ESTAB:
switch (event) {
case CLS_ACPT:
- reason = cpu_to_le16(WLAN_REASON_MESH_CLOSE);
- sta->reason = reason;
changed |= __mesh_plink_deactivate(sta);
- sta->plink_state = NL80211_PLINK_HOLDING;
- llid = sta->llid;
- mod_plink_timer(sta, mshcfg->dot11MeshHoldingTimeout);
- spin_unlock_bh(&sta->lock);
changed |= mesh_set_ht_prot_mode(sdata);
changed |= mesh_set_short_slot_time(sdata);
- mesh_plink_frame_tx(sdata, WLAN_SP_MESH_PEERING_CLOSE,
- sta->sta.addr, llid, plid, reason);
+ mesh_plink_close(sdata, sta, event);
+ action = WLAN_SP_MESH_PEERING_CLOSE;
break;
case OPN_ACPT:
- llid = sta->llid;
- spin_unlock_bh(&sta->lock);
- mesh_plink_frame_tx(sdata,
- WLAN_SP_MESH_PEERING_CONFIRM,
- sta->sta.addr, llid, plid, 0);
+ action = WLAN_SP_MESH_PEERING_CONFIRM;
break;
default:
- spin_unlock_bh(&sta->lock);
break;
}
break;
@@ -1049,32 +837,271 @@ void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata,
if (del_timer(&sta->plink_timer))
sta->ignore_plink_timer = 1;
mesh_plink_fsm_restart(sta);
- spin_unlock_bh(&sta->lock);
break;
case OPN_ACPT:
case CNF_ACPT:
case OPN_RJCT:
case CNF_RJCT:
- llid = sta->llid;
- reason = sta->reason;
- spin_unlock_bh(&sta->lock);
- mesh_plink_frame_tx(sdata, WLAN_SP_MESH_PEERING_CLOSE,
- sta->sta.addr, llid, plid, reason);
+ action = WLAN_SP_MESH_PEERING_CLOSE;
break;
default:
- spin_unlock_bh(&sta->lock);
+ break;
}
break;
default:
/* should not get here, PLINK_BLOCKED is dealt with at the
* beginning of the function
*/
- spin_unlock_bh(&sta->lock);
break;
}
+ spin_unlock_bh(&sta->lock);
+ if (action) {
+ mesh_plink_frame_tx(sdata, action, sta->sta.addr,
+ sta->llid, sta->plid, sta->reason);
+
+ /* also send confirm in open case */
+ if (action == WLAN_SP_MESH_PEERING_OPEN) {
+ mesh_plink_frame_tx(sdata,
+ WLAN_SP_MESH_PEERING_CONFIRM,
+ sta->sta.addr, sta->llid,
+ sta->plid, 0);
+ }
+ }
+
+ return changed;
+}
+
+/*
+ * mesh_plink_get_event - get correct MPM event
+ *
+ * @sdata: interface
+ * @sta: peer, leave NULL if processing a frame from a new suitable peer
+ * @elems: peering management IEs
+ * @ftype: frame type
+ * @llid: peer's peer link ID
+ * @plid: peer's local link ID
+ *
+ * Return: new peering event for @sta, but PLINK_UNDEFINED should be treated as
+ * an error.
+ */
+static enum plink_event
+mesh_plink_get_event(struct ieee80211_sub_if_data *sdata,
+ struct sta_info *sta,
+ struct ieee802_11_elems *elems,
+ enum ieee80211_self_protected_actioncode ftype,
+ u16 llid, u16 plid)
+{
+ enum plink_event event = PLINK_UNDEFINED;
+ u8 ie_len = elems->peering_len;
+ bool matches_local;
+
+ matches_local = (ftype == WLAN_SP_MESH_PEERING_CLOSE ||
+ mesh_matches_local(sdata, elems));
+
+ /* deny open request from non-matching peer */
+ if (!matches_local && !sta) {
+ event = OPN_RJCT;
+ goto out;
+ }
+
+ if (!sta) {
+ if (ftype != WLAN_SP_MESH_PEERING_OPEN) {
+ mpl_dbg(sdata, "Mesh plink: cls or cnf from unknown peer\n");
+ goto out;
+ }
+ /* ftype == WLAN_SP_MESH_PEERING_OPEN */
+ if (!mesh_plink_free_count(sdata)) {
+ mpl_dbg(sdata, "Mesh plink error: no more free plinks\n");
+ goto out;
+ }
+ } else {
+ if (!test_sta_flag(sta, WLAN_STA_AUTH)) {
+ mpl_dbg(sdata, "Mesh plink: Action frame from non-authed peer\n");
+ goto out;
+ }
+ if (sta->plink_state == NL80211_PLINK_BLOCKED)
+ goto out;
+ }
+
+ /* new matching peer */
+ if (!sta) {
+ event = OPN_ACPT;
+ goto out;
+ }
+
+ switch (ftype) {
+ case WLAN_SP_MESH_PEERING_OPEN:
+ if (!matches_local)
+ event = OPN_RJCT;
+ if (!mesh_plink_free_count(sdata) ||
+ (sta->plid && sta->plid != plid))
+ event = OPN_IGNR;
+ else
+ event = OPN_ACPT;
+ break;
+ case WLAN_SP_MESH_PEERING_CONFIRM:
+ if (!matches_local)
+ event = CNF_RJCT;
+ if (!mesh_plink_free_count(sdata) ||
+ (sta->llid != llid || sta->plid != plid))
+ event = CNF_IGNR;
+ else
+ event = CNF_ACPT;
+ break;
+ case WLAN_SP_MESH_PEERING_CLOSE:
+ if (sta->plink_state == NL80211_PLINK_ESTAB)
+ /* Do not check for llid or plid. This does not
+ * follow the standard but since multiple plinks
+ * per sta are not supported, it is necessary in
+ * order to avoid a livelock when MP A sees an
+ * establish peer link to MP B but MP B does not
+ * see it. This can be caused by a timeout in
+ * B's peer link establishment or B beign
+ * restarted.
+ */
+ event = CLS_ACPT;
+ else if (sta->plid != plid)
+ event = CLS_IGNR;
+ else if (ie_len == 8 && sta->llid != llid)
+ event = CLS_IGNR;
+ else
+ event = CLS_ACPT;
+ break;
+ default:
+ mpl_dbg(sdata, "Mesh plink: unknown frame subtype\n");
+ break;
+ }
+
+out:
+ return event;
+}
+static void
+mesh_process_plink_frame(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_mgmt *mgmt,
+ struct ieee802_11_elems *elems)
+{
+
+ struct sta_info *sta;
+ enum plink_event event;
+ enum ieee80211_self_protected_actioncode ftype;
+ u32 changed = 0;
+ u8 ie_len = elems->peering_len;
+ __le16 _plid, _llid;
+ u16 plid, llid = 0;
+
+ if (!elems->peering) {
+ mpl_dbg(sdata,
+ "Mesh plink: missing necessary peer link ie\n");
+ return;
+ }
+
+ if (elems->rsn_len &&
+ sdata->u.mesh.security == IEEE80211_MESH_SEC_NONE) {
+ mpl_dbg(sdata,
+ "Mesh plink: can't establish link with secure peer\n");
+ return;
+ }
+
+ ftype = mgmt->u.action.u.self_prot.action_code;
+ if ((ftype == WLAN_SP_MESH_PEERING_OPEN && ie_len != 4) ||
+ (ftype == WLAN_SP_MESH_PEERING_CONFIRM && ie_len != 6) ||
+ (ftype == WLAN_SP_MESH_PEERING_CLOSE && ie_len != 6
+ && ie_len != 8)) {
+ mpl_dbg(sdata,
+ "Mesh plink: incorrect plink ie length %d %d\n",
+ ftype, ie_len);
+ return;
+ }
+
+ if (ftype != WLAN_SP_MESH_PEERING_CLOSE &&
+ (!elems->mesh_id || !elems->mesh_config)) {
+ mpl_dbg(sdata, "Mesh plink: missing necessary ie\n");
+ return;
+ }
+ /* Note the lines below are correct, the llid in the frame is the plid
+ * from the point of view of this host.
+ */
+ memcpy(&_plid, PLINK_GET_LLID(elems->peering), sizeof(__le16));
+ plid = le16_to_cpu(_plid);
+ if (ftype == WLAN_SP_MESH_PEERING_CONFIRM ||
+ (ftype == WLAN_SP_MESH_PEERING_CLOSE && ie_len == 8)) {
+ memcpy(&_llid, PLINK_GET_PLID(elems->peering), sizeof(__le16));
+ llid = le16_to_cpu(_llid);
+ }
+
+ /* WARNING: Only for sta pointer, is dropped & re-acquired */
+ rcu_read_lock();
+
+ sta = sta_info_get(sdata, mgmt->sa);
+
+ if (ftype == WLAN_SP_MESH_PEERING_OPEN &&
+ !rssi_threshold_check(sdata, sta)) {
+ mpl_dbg(sdata, "Mesh plink: %pM does not meet rssi threshold\n",
+ mgmt->sa);
+ goto unlock_rcu;
+ }
+
+ /* Now we will figure out the appropriate event... */
+ event = mesh_plink_get_event(sdata, sta, elems, ftype, llid, plid);
+
+ if (event == OPN_ACPT) {
+ rcu_read_unlock();
+ /* allocate sta entry if necessary and update info */
+ sta = mesh_sta_info_get(sdata, mgmt->sa, elems);
+ if (!sta) {
+ mpl_dbg(sdata, "Mesh plink: failed to init peer!\n");
+ goto unlock_rcu;
+ }
+ sta->plid = plid;
+ } else if (!sta && event == OPN_RJCT) {
+ mesh_plink_frame_tx(sdata, WLAN_SP_MESH_PEERING_CLOSE,
+ mgmt->sa, 0, plid,
+ WLAN_REASON_MESH_CONFIG);
+ goto unlock_rcu;
+ } else if (!sta || event == PLINK_UNDEFINED) {
+ /* something went wrong */
+ goto unlock_rcu;
+ }
+
+ changed |= mesh_plink_fsm(sdata, sta, event);
+
+unlock_rcu:
rcu_read_unlock();
if (changed)
ieee80211_mbss_info_change_notify(sdata, changed);
}
+
+void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_mgmt *mgmt, size_t len,
+ struct ieee80211_rx_status *rx_status)
+{
+ struct ieee802_11_elems elems;
+ size_t baselen;
+ u8 *baseaddr;
+
+ /* need action_code, aux */
+ if (len < IEEE80211_MIN_ACTION_SIZE + 3)
+ return;
+
+ if (sdata->u.mesh.user_mpm)
+ /* userspace must register for these */
+ return;
+
+ if (is_multicast_ether_addr(mgmt->da)) {
+ mpl_dbg(sdata,
+ "Mesh plink: ignore frame from multicast address\n");
+ return;
+ }
+
+ baseaddr = mgmt->u.action.u.self_prot.variable;
+ baselen = (u8 *) mgmt->u.action.u.self_prot.variable - (u8 *) mgmt;
+ if (mgmt->u.action.u.self_prot.action_code ==
+ WLAN_SP_MESH_PEERING_CONFIRM) {
+ baseaddr += 4;
+ baselen += 4;
+ }
+ ieee802_11_parse_elems(baseaddr, len - baselen, true, &elems);
+ mesh_process_plink_frame(sdata, mgmt, &elems);
+}