1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2c3896d2cSLuis Carlos Cobo /*
3264d9b7dSRui Paulo * Copyright (c) 2008, 2009 open80211s Ltd.
415ddba5fSAnjaneyulu * Copyright (C) 2019, 2021-2023 Intel Corporation
5c3896d2cSLuis Carlos Cobo * Author: Luis Carlos Cobo <luisca@cozybit.com>
6c3896d2cSLuis Carlos Cobo */
75a0e3ad6STejun Heo #include <linux/gfp.h>
8902acc78SJohannes Berg #include <linux/kernel.h>
9902acc78SJohannes Berg #include <linux/random.h>
10b2d09103SIngo Molnar #include <linux/rculist.h>
11b2d09103SIngo Molnar
12c3896d2cSLuis Carlos Cobo #include "ieee80211_i.h"
132c8dccc7SJohannes Berg #include "rate.h"
14c3896d2cSLuis Carlos Cobo #include "mesh.h"
15c3896d2cSLuis Carlos Cobo
16a69bd8e6SBob Copeland #define PLINK_CNF_AID(mgmt) ((mgmt)->u.action.u.self_prot.variable + 2)
178db09850SThomas Pedersen #define PLINK_GET_LLID(p) (p + 2)
188db09850SThomas Pedersen #define PLINK_GET_PLID(p) (p + 4)
19c3896d2cSLuis Carlos Cobo
20433f5bc1SJohannes Berg #define mod_plink_timer(s, t) (mod_timer(&s->mesh->plink_timer, \
21cc57ac53SNicholas Mc Guire jiffies + msecs_to_jiffies(t)))
22c3896d2cSLuis Carlos Cobo
23c3896d2cSLuis Carlos Cobo enum plink_event {
24c3896d2cSLuis Carlos Cobo PLINK_UNDEFINED,
25c3896d2cSLuis Carlos Cobo OPN_ACPT,
26c3896d2cSLuis Carlos Cobo OPN_RJCT,
27c3896d2cSLuis Carlos Cobo OPN_IGNR,
28c3896d2cSLuis Carlos Cobo CNF_ACPT,
29c3896d2cSLuis Carlos Cobo CNF_RJCT,
30c3896d2cSLuis Carlos Cobo CNF_IGNR,
31c3896d2cSLuis Carlos Cobo CLS_ACPT,
32c3896d2cSLuis Carlos Cobo CLS_IGNR
33c3896d2cSLuis Carlos Cobo };
34c3896d2cSLuis Carlos Cobo
3595e48addSThomas Pedersen static const char * const mplstates[] = {
3695e48addSThomas Pedersen [NL80211_PLINK_LISTEN] = "LISTEN",
3795e48addSThomas Pedersen [NL80211_PLINK_OPN_SNT] = "OPN-SNT",
3895e48addSThomas Pedersen [NL80211_PLINK_OPN_RCVD] = "OPN-RCVD",
3995e48addSThomas Pedersen [NL80211_PLINK_CNF_RCVD] = "CNF_RCVD",
4095e48addSThomas Pedersen [NL80211_PLINK_ESTAB] = "ESTAB",
4195e48addSThomas Pedersen [NL80211_PLINK_HOLDING] = "HOLDING",
4295e48addSThomas Pedersen [NL80211_PLINK_BLOCKED] = "BLOCKED"
4395e48addSThomas Pedersen };
4495e48addSThomas Pedersen
4595e48addSThomas Pedersen static const char * const mplevents[] = {
4695e48addSThomas Pedersen [PLINK_UNDEFINED] = "NONE",
4795e48addSThomas Pedersen [OPN_ACPT] = "OPN_ACPT",
4895e48addSThomas Pedersen [OPN_RJCT] = "OPN_RJCT",
4995e48addSThomas Pedersen [OPN_IGNR] = "OPN_IGNR",
5095e48addSThomas Pedersen [CNF_ACPT] = "CNF_ACPT",
5195e48addSThomas Pedersen [CNF_RJCT] = "CNF_RJCT",
5295e48addSThomas Pedersen [CNF_IGNR] = "CNF_IGNR",
5395e48addSThomas Pedersen [CLS_ACPT] = "CLS_ACPT",
5495e48addSThomas Pedersen [CLS_IGNR] = "CLS_IGNR"
5595e48addSThomas Pedersen };
5695e48addSThomas Pedersen
5736c9bb29SBob Copeland /* We only need a valid sta if user configured a minimum rssi_threshold. */
rssi_threshold_check(struct ieee80211_sub_if_data * sdata,struct sta_info * sta)5836c9bb29SBob Copeland static bool rssi_threshold_check(struct ieee80211_sub_if_data *sdata,
5936c9bb29SBob Copeland struct sta_info *sta)
6036c9bb29SBob Copeland {
6136c9bb29SBob Copeland s32 rssi_threshold = sdata->u.mesh.mshcfg.rssi_threshold;
6236c9bb29SBob Copeland return rssi_threshold == 0 ||
63e5a9f8d0SJohannes Berg (sta &&
64046d2e7cSSriram R (s8)-ewma_signal_read(&sta->deflink.rx_stats_avg.signal) >
65e5a9f8d0SJohannes Berg rssi_threshold);
6636c9bb29SBob Copeland }
6736c9bb29SBob Copeland
68c3896d2cSLuis Carlos Cobo /**
69c3896d2cSLuis Carlos Cobo * mesh_plink_fsm_restart - restart a mesh peer link finite state machine
70c3896d2cSLuis Carlos Cobo *
7123c7a29cSRui Paulo * @sta: mesh peer link to restart
72c3896d2cSLuis Carlos Cobo *
73433f5bc1SJohannes Berg * Locking: this function must be called holding sta->mesh->plink_lock
74c3896d2cSLuis Carlos Cobo */
mesh_plink_fsm_restart(struct sta_info * sta)75c3896d2cSLuis Carlos Cobo static inline void mesh_plink_fsm_restart(struct sta_info *sta)
76c3896d2cSLuis Carlos Cobo {
77433f5bc1SJohannes Berg lockdep_assert_held(&sta->mesh->plink_lock);
78433f5bc1SJohannes Berg sta->mesh->plink_state = NL80211_PLINK_LISTEN;
79433f5bc1SJohannes Berg sta->mesh->llid = sta->mesh->plid = sta->mesh->reason = 0;
80433f5bc1SJohannes Berg sta->mesh->plink_retries = 0;
81c3896d2cSLuis Carlos Cobo }
82c3896d2cSLuis Carlos Cobo
833b144658SThomas Pedersen /*
843b144658SThomas Pedersen * mesh_set_short_slot_time - enable / disable ERP short slot time.
853b144658SThomas Pedersen *
863b144658SThomas Pedersen * The standard indirectly mandates mesh STAs to turn off short slot time by
873b144658SThomas Pedersen * disallowing advertising this (802.11-2012 8.4.1.4), but that doesn't mean we
883b144658SThomas Pedersen * can't be sneaky about it. Enable short slot time if all mesh STAs in the
893b144658SThomas Pedersen * MBSS support ERP rates.
903b144658SThomas Pedersen *
913b144658SThomas Pedersen * Returns BSS_CHANGED_ERP_SLOT or 0 for no change.
923b144658SThomas Pedersen */
mesh_set_short_slot_time(struct ieee80211_sub_if_data * sdata)9315ddba5fSAnjaneyulu static u64 mesh_set_short_slot_time(struct ieee80211_sub_if_data *sdata)
943b144658SThomas Pedersen {
953b144658SThomas Pedersen struct ieee80211_local *local = sdata->local;
9621a8e9ddSMohammed Shafi Shajakhan struct ieee80211_supported_band *sband;
973b144658SThomas Pedersen struct sta_info *sta;
9815ddba5fSAnjaneyulu u32 erp_rates = 0;
9915ddba5fSAnjaneyulu u64 changed = 0;
1003b144658SThomas Pedersen int i;
1013b144658SThomas Pedersen bool short_slot = false;
1023b144658SThomas Pedersen
10321a8e9ddSMohammed Shafi Shajakhan sband = ieee80211_get_sband(sdata);
10421a8e9ddSMohammed Shafi Shajakhan if (!sband)
10521a8e9ddSMohammed Shafi Shajakhan return changed;
10621a8e9ddSMohammed Shafi Shajakhan
10721a8e9ddSMohammed Shafi Shajakhan if (sband->band == NL80211_BAND_5GHZ) {
1083b144658SThomas Pedersen /* (IEEE 802.11-2012 19.4.5) */
1093b144658SThomas Pedersen short_slot = true;
1103b144658SThomas Pedersen goto out;
11121a8e9ddSMohammed Shafi Shajakhan } else if (sband->band != NL80211_BAND_2GHZ) {
1123b144658SThomas Pedersen goto out;
11321a8e9ddSMohammed Shafi Shajakhan }
1143b144658SThomas Pedersen
1153b144658SThomas Pedersen for (i = 0; i < sband->n_bitrates; i++)
1163b144658SThomas Pedersen if (sband->bitrates[i].flags & IEEE80211_RATE_ERP_G)
1173b144658SThomas Pedersen erp_rates |= BIT(i);
1183b144658SThomas Pedersen
1193b144658SThomas Pedersen if (!erp_rates)
1203b144658SThomas Pedersen goto out;
1213b144658SThomas Pedersen
1223b144658SThomas Pedersen rcu_read_lock();
1233b144658SThomas Pedersen list_for_each_entry_rcu(sta, &local->sta_list, list) {
1243b144658SThomas Pedersen if (sdata != sta->sdata ||
125433f5bc1SJohannes Berg sta->mesh->plink_state != NL80211_PLINK_ESTAB)
1263b144658SThomas Pedersen continue;
1273b144658SThomas Pedersen
1283b144658SThomas Pedersen short_slot = false;
129046d2e7cSSriram R if (erp_rates & sta->sta.deflink.supp_rates[sband->band])
1303b144658SThomas Pedersen short_slot = true;
1313b144658SThomas Pedersen else
1323b144658SThomas Pedersen break;
1333b144658SThomas Pedersen }
1343b144658SThomas Pedersen rcu_read_unlock();
1353b144658SThomas Pedersen
1363b144658SThomas Pedersen out:
1373b144658SThomas Pedersen if (sdata->vif.bss_conf.use_short_slot != short_slot) {
1383b144658SThomas Pedersen sdata->vif.bss_conf.use_short_slot = short_slot;
1393b144658SThomas Pedersen changed = BSS_CHANGED_ERP_SLOT;
1403b144658SThomas Pedersen mpl_dbg(sdata, "mesh_plink %pM: ERP short slot time %d\n",
1413b144658SThomas Pedersen sdata->vif.addr, short_slot);
1423b144658SThomas Pedersen }
1433b144658SThomas Pedersen return changed;
1443b144658SThomas Pedersen }
1453b144658SThomas Pedersen
1462c53040fSBen Hutchings /**
147cbf9322eSAshok Nagarajan * mesh_set_ht_prot_mode - set correct HT protection mode
14821439b65SJohannes Berg * @sdata: the (mesh) interface to handle
14957aac7c5SAshok Nagarajan *
150cbf9322eSAshok Nagarajan * Section 9.23.3.5 of IEEE 80211-2012 describes the protection rules for HT
151cbf9322eSAshok Nagarajan * mesh STA in a MBSS. Three HT protection modes are supported for now, non-HT
152cbf9322eSAshok Nagarajan * mixed mode, 20MHz-protection and no-protection mode. non-HT mixed mode is
153cbf9322eSAshok Nagarajan * selected if any non-HT peers are present in our MBSS. 20MHz-protection mode
154cbf9322eSAshok Nagarajan * is selected if all peers in our 20/40MHz MBSS support HT and at least one
155cbf9322eSAshok Nagarajan * HT20 peer is present. Otherwise no-protection mode is selected.
15657aac7c5SAshok Nagarajan */
mesh_set_ht_prot_mode(struct ieee80211_sub_if_data * sdata)15715ddba5fSAnjaneyulu static u64 mesh_set_ht_prot_mode(struct ieee80211_sub_if_data *sdata)
15857aac7c5SAshok Nagarajan {
15957aac7c5SAshok Nagarajan struct ieee80211_local *local = sdata->local;
16057aac7c5SAshok Nagarajan struct sta_info *sta;
16157aac7c5SAshok Nagarajan u16 ht_opmode;
16257aac7c5SAshok Nagarajan bool non_ht_sta = false, ht20_sta = false;
16357aac7c5SAshok Nagarajan
1640418a445SSimon Wunderlich switch (sdata->vif.bss_conf.chandef.width) {
1650418a445SSimon Wunderlich case NL80211_CHAN_WIDTH_20_NOHT:
1660418a445SSimon Wunderlich case NL80211_CHAN_WIDTH_5:
1670418a445SSimon Wunderlich case NL80211_CHAN_WIDTH_10:
16857aac7c5SAshok Nagarajan return 0;
1690418a445SSimon Wunderlich default:
1700418a445SSimon Wunderlich break;
1710418a445SSimon Wunderlich }
17257aac7c5SAshok Nagarajan
17357aac7c5SAshok Nagarajan rcu_read_lock();
17457aac7c5SAshok Nagarajan list_for_each_entry_rcu(sta, &local->sta_list, list) {
175cbf9322eSAshok Nagarajan if (sdata != sta->sdata ||
176433f5bc1SJohannes Berg sta->mesh->plink_state != NL80211_PLINK_ESTAB)
177cbf9322eSAshok Nagarajan continue;
178cbf9322eSAshok Nagarajan
179046d2e7cSSriram R if (sta->sta.deflink.bandwidth > IEEE80211_STA_RX_BW_20)
18052ac8c48SThomas Pedersen continue;
18152ac8c48SThomas Pedersen
182046d2e7cSSriram R if (!sta->sta.deflink.ht_cap.ht_supported) {
18352ac8c48SThomas Pedersen mpl_dbg(sdata, "nonHT sta (%pM) is present\n",
18452ac8c48SThomas Pedersen sta->sta.addr);
18557aac7c5SAshok Nagarajan non_ht_sta = true;
18657aac7c5SAshok Nagarajan break;
18757aac7c5SAshok Nagarajan }
18852ac8c48SThomas Pedersen
18952ac8c48SThomas Pedersen mpl_dbg(sdata, "HT20 sta (%pM) is present\n", sta->sta.addr);
19052ac8c48SThomas Pedersen ht20_sta = true;
19157aac7c5SAshok Nagarajan }
19257aac7c5SAshok Nagarajan rcu_read_unlock();
19357aac7c5SAshok Nagarajan
19457aac7c5SAshok Nagarajan if (non_ht_sta)
19557aac7c5SAshok Nagarajan ht_opmode = IEEE80211_HT_OP_MODE_PROTECTION_NONHT_MIXED;
196466f310dSJohannes Berg else if (ht20_sta &&
1974bf88530SJohannes Berg sdata->vif.bss_conf.chandef.width > NL80211_CHAN_WIDTH_20)
19857aac7c5SAshok Nagarajan ht_opmode = IEEE80211_HT_OP_MODE_PROTECTION_20MHZ;
19957aac7c5SAshok Nagarajan else
20057aac7c5SAshok Nagarajan ht_opmode = IEEE80211_HT_OP_MODE_PROTECTION_NONE;
20157aac7c5SAshok Nagarajan
20252ac8c48SThomas Pedersen if (sdata->vif.bss_conf.ht_operation_mode == ht_opmode)
20352ac8c48SThomas Pedersen return 0;
20452ac8c48SThomas Pedersen
20557aac7c5SAshok Nagarajan sdata->vif.bss_conf.ht_operation_mode = ht_opmode;
20670c33eaaSAshok Nagarajan sdata->u.mesh.mshcfg.ht_opmode = ht_opmode;
20752ac8c48SThomas Pedersen mpl_dbg(sdata, "selected new HT protection mode %d\n", ht_opmode);
20852ac8c48SThomas Pedersen return BSS_CHANGED_HT;
20957aac7c5SAshok Nagarajan }
21057aac7c5SAshok Nagarajan
mesh_plink_frame_tx(struct ieee80211_sub_if_data * sdata,struct sta_info * sta,enum ieee80211_self_protected_actioncode action,u8 * da,u16 llid,u16 plid,u16 reason)211f698d856SJasper Bryant-Greene static int mesh_plink_frame_tx(struct ieee80211_sub_if_data *sdata,
212a69bd8e6SBob Copeland struct sta_info *sta,
21354ef656bSThomas Pedersen enum ieee80211_self_protected_actioncode action,
2146f101ef0SChun-Yeow Yeoh u8 *da, u16 llid, u16 plid, u16 reason)
215bf7cd94dSJohannes Berg {
216f698d856SJasper Bryant-Greene struct ieee80211_local *local = sdata->local;
2173b69a9c5SThomas Pedersen struct sk_buff *skb;
218e7570dfbSThomas Pedersen struct ieee80211_tx_info *info;
219c3896d2cSLuis Carlos Cobo struct ieee80211_mgmt *mgmt;
220c3896d2cSLuis Carlos Cobo bool include_plid = false;
2218db09850SThomas Pedersen u16 peering_proto = 0;
2223b69a9c5SThomas Pedersen u8 *pos, ie_len = 4;
223df1875c4SRyder Lee u8 ie_len_he_cap, ie_len_eht_cap;
2244c121fd6SJohannes Berg int hdr_len = offsetofend(struct ieee80211_mgmt, u.action.u.self_prot);
225f609a43dSThomas Pedersen int err = -ENOMEM;
226c3896d2cSLuis Carlos Cobo
22760ad72daSSven Eckelmann ie_len_he_cap = ieee80211_ie_len_he_cap(sdata,
22860ad72daSSven Eckelmann NL80211_IFTYPE_MESH_POINT);
229df1875c4SRyder Lee ie_len_eht_cap = ieee80211_ie_len_eht_cap(sdata,
230df1875c4SRyder Lee NL80211_IFTYPE_MESH_POINT);
23165e8b0ccSJavier Cardona skb = dev_alloc_skb(local->tx_headroom +
2323b69a9c5SThomas Pedersen hdr_len +
2333b69a9c5SThomas Pedersen 2 + /* capability info */
2343b69a9c5SThomas Pedersen 2 + /* AID */
2353b69a9c5SThomas Pedersen 2 + 8 + /* supported rates */
2363b69a9c5SThomas Pedersen 2 + (IEEE80211_MAX_SUPP_RATES - 8) +
2373b69a9c5SThomas Pedersen 2 + sdata->u.mesh.mesh_id_len +
2383b69a9c5SThomas Pedersen 2 + sizeof(struct ieee80211_meshconf_ie) +
239176f3608SThomas Pedersen 2 + sizeof(struct ieee80211_ht_cap) +
240074d46d1SJohannes Berg 2 + sizeof(struct ieee80211_ht_operation) +
241c85fb53cSBob Copeland 2 + sizeof(struct ieee80211_vht_cap) +
242c85fb53cSBob Copeland 2 + sizeof(struct ieee80211_vht_operation) +
24360ad72daSSven Eckelmann ie_len_he_cap +
24460ad72daSSven Eckelmann 2 + 1 + sizeof(struct ieee80211_he_operation) +
245d1b7524bSRajkumar Manoharan sizeof(struct ieee80211_he_6ghz_oper) +
24624a2042cSRajkumar Manoharan 2 + 1 + sizeof(struct ieee80211_he_6ghz_capa) +
247df1875c4SRyder Lee ie_len_eht_cap +
248df1875c4SRyder Lee 2 + 1 + offsetof(struct ieee80211_eht_operation, optional) +
249df1875c4SRyder Lee offsetof(struct ieee80211_eht_operation_info, optional) +
2503b69a9c5SThomas Pedersen 2 + 8 + /* peering IE */
2513b69a9c5SThomas Pedersen sdata->u.mesh.ie_len);
252c3896d2cSLuis Carlos Cobo if (!skb)
25387d84c45SBob Copeland return err;
254e7570dfbSThomas Pedersen info = IEEE80211_SKB_CB(skb);
25565e8b0ccSJavier Cardona skb_reserve(skb, local->tx_headroom);
256b080db58SJohannes Berg mgmt = skb_put_zero(skb, hdr_len);
257e7827a70SHarvey Harrison mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
258c3896d2cSLuis Carlos Cobo IEEE80211_STYPE_ACTION);
259c3896d2cSLuis Carlos Cobo memcpy(mgmt->da, da, ETH_ALEN);
26047846c9bSJohannes Berg memcpy(mgmt->sa, sdata->vif.addr, ETH_ALEN);
261915b5c50SJavier Cardona memcpy(mgmt->bssid, sdata->vif.addr, ETH_ALEN);
2628db09850SThomas Pedersen mgmt->u.action.category = WLAN_CATEGORY_SELF_PROTECTED;
2638db09850SThomas Pedersen mgmt->u.action.u.self_prot.action_code = action;
264c3896d2cSLuis Carlos Cobo
2658db09850SThomas Pedersen if (action != WLAN_SP_MESH_PEERING_CLOSE) {
26621a8e9ddSMohammed Shafi Shajakhan struct ieee80211_supported_band *sband;
26721a8e9ddSMohammed Shafi Shajakhan enum nl80211_band band;
26821a8e9ddSMohammed Shafi Shajakhan
26921a8e9ddSMohammed Shafi Shajakhan sband = ieee80211_get_sband(sdata);
27021a8e9ddSMohammed Shafi Shajakhan if (!sband) {
27121a8e9ddSMohammed Shafi Shajakhan err = -EINVAL;
27221a8e9ddSMohammed Shafi Shajakhan goto free;
27321a8e9ddSMohammed Shafi Shajakhan }
27421a8e9ddSMohammed Shafi Shajakhan band = sband->band;
27555de908aSJohannes Berg
2768db09850SThomas Pedersen /* capability info */
277e45a79daSJohannes Berg pos = skb_put_zero(skb, 2);
2788db09850SThomas Pedersen if (action == WLAN_SP_MESH_PEERING_CONFIRM) {
2798db09850SThomas Pedersen /* AID */
2808db09850SThomas Pedersen pos = skb_put(skb, 2);
281a69bd8e6SBob Copeland put_unaligned_le16(sta->sta.aid, pos);
282c3896d2cSLuis Carlos Cobo }
28355de908aSJohannes Berg if (ieee80211_add_srates_ie(sdata, skb, true, band) ||
28455de908aSJohannes Berg ieee80211_add_ext_srates_ie(sdata, skb, true, band) ||
285bf7cd94dSJohannes Berg mesh_add_rsn_ie(sdata, skb) ||
286bf7cd94dSJohannes Berg mesh_add_meshid_ie(sdata, skb) ||
287bf7cd94dSJohannes Berg mesh_add_meshconf_ie(sdata, skb))
288f609a43dSThomas Pedersen goto free;
2898db09850SThomas Pedersen } else { /* WLAN_SP_MESH_PEERING_CLOSE */
290e7570dfbSThomas Pedersen info->flags |= IEEE80211_TX_CTL_NO_ACK;
291bf7cd94dSJohannes Berg if (mesh_add_meshid_ie(sdata, skb))
292f609a43dSThomas Pedersen goto free;
293c3896d2cSLuis Carlos Cobo }
294c3896d2cSLuis Carlos Cobo
2958db09850SThomas Pedersen /* Add Mesh Peering Management element */
296c3896d2cSLuis Carlos Cobo switch (action) {
29754ef656bSThomas Pedersen case WLAN_SP_MESH_PEERING_OPEN:
298c3896d2cSLuis Carlos Cobo break;
29954ef656bSThomas Pedersen case WLAN_SP_MESH_PEERING_CONFIRM:
3008db09850SThomas Pedersen ie_len += 2;
301c3896d2cSLuis Carlos Cobo include_plid = true;
302c3896d2cSLuis Carlos Cobo break;
30354ef656bSThomas Pedersen case WLAN_SP_MESH_PEERING_CLOSE:
3048db09850SThomas Pedersen if (plid) {
3058db09850SThomas Pedersen ie_len += 2;
306c3896d2cSLuis Carlos Cobo include_plid = true;
307c3896d2cSLuis Carlos Cobo }
3088db09850SThomas Pedersen ie_len += 2; /* reason code */
309c3896d2cSLuis Carlos Cobo break;
3108db09850SThomas Pedersen default:
311f609a43dSThomas Pedersen err = -EINVAL;
312f609a43dSThomas Pedersen goto free;
313c3896d2cSLuis Carlos Cobo }
314c3896d2cSLuis Carlos Cobo
3158db09850SThomas Pedersen if (WARN_ON(skb_tailroom(skb) < 2 + ie_len))
316f609a43dSThomas Pedersen goto free;
3178db09850SThomas Pedersen
318c3896d2cSLuis Carlos Cobo pos = skb_put(skb, 2 + ie_len);
3198db09850SThomas Pedersen *pos++ = WLAN_EID_PEER_MGMT;
320c3896d2cSLuis Carlos Cobo *pos++ = ie_len;
3218db09850SThomas Pedersen memcpy(pos, &peering_proto, 2);
322c3896d2cSLuis Carlos Cobo pos += 2;
3236f101ef0SChun-Yeow Yeoh put_unaligned_le16(llid, pos);
3248db09850SThomas Pedersen pos += 2;
3258db09850SThomas Pedersen if (include_plid) {
3266f101ef0SChun-Yeow Yeoh put_unaligned_le16(plid, pos);
3278db09850SThomas Pedersen pos += 2;
328c3896d2cSLuis Carlos Cobo }
32954ef656bSThomas Pedersen if (action == WLAN_SP_MESH_PEERING_CLOSE) {
3306f101ef0SChun-Yeow Yeoh put_unaligned_le16(reason, pos);
3318db09850SThomas Pedersen pos += 2;
332c3896d2cSLuis Carlos Cobo }
333176f3608SThomas Pedersen
334176f3608SThomas Pedersen if (action != WLAN_SP_MESH_PEERING_CLOSE) {
335bf7cd94dSJohannes Berg if (mesh_add_ht_cap_ie(sdata, skb) ||
336c85fb53cSBob Copeland mesh_add_ht_oper_ie(sdata, skb) ||
337c85fb53cSBob Copeland mesh_add_vht_cap_ie(sdata, skb) ||
33860ad72daSSven Eckelmann mesh_add_vht_oper_ie(sdata, skb) ||
33960ad72daSSven Eckelmann mesh_add_he_cap_ie(sdata, skb, ie_len_he_cap) ||
34024a2042cSRajkumar Manoharan mesh_add_he_oper_ie(sdata, skb) ||
341df1875c4SRyder Lee mesh_add_he_6ghz_cap_ie(sdata, skb) ||
342df1875c4SRyder Lee mesh_add_eht_cap_ie(sdata, skb, ie_len_eht_cap) ||
343df1875c4SRyder Lee mesh_add_eht_oper_ie(sdata, skb))
344f609a43dSThomas Pedersen goto free;
345176f3608SThomas Pedersen }
346176f3608SThomas Pedersen
347bf7cd94dSJohannes Berg if (mesh_add_vendor_ies(sdata, skb))
348f609a43dSThomas Pedersen goto free;
349c3896d2cSLuis Carlos Cobo
35062ae67beSJohannes Berg ieee80211_tx_skb(sdata, skb);
351c3896d2cSLuis Carlos Cobo return 0;
352f609a43dSThomas Pedersen free:
353f609a43dSThomas Pedersen kfree_skb(skb);
354f609a43dSThomas Pedersen return err;
355c3896d2cSLuis Carlos Cobo }
356c3896d2cSLuis Carlos Cobo
357fa87a656SBob Copeland /**
358fa87a656SBob Copeland * __mesh_plink_deactivate - deactivate mesh peer link
359fa87a656SBob Copeland *
360fa87a656SBob Copeland * @sta: mesh peer link to deactivate
361fa87a656SBob Copeland *
362e596af82SBob Copeland * Mesh paths with this peer as next hop should be flushed
363e596af82SBob Copeland * by the caller outside of plink_lock.
364e596af82SBob Copeland *
365fa87a656SBob Copeland * Returns beacon changed flag if the beacon content changed.
366fa87a656SBob Copeland *
367fa87a656SBob Copeland * Locking: the caller must hold sta->mesh->plink_lock
368fa87a656SBob Copeland */
__mesh_plink_deactivate(struct sta_info * sta)36915ddba5fSAnjaneyulu static u64 __mesh_plink_deactivate(struct sta_info *sta)
370fa87a656SBob Copeland {
371fa87a656SBob Copeland struct ieee80211_sub_if_data *sdata = sta->sdata;
37215ddba5fSAnjaneyulu u64 changed = 0;
373fa87a656SBob Copeland
374fa87a656SBob Copeland lockdep_assert_held(&sta->mesh->plink_lock);
375fa87a656SBob Copeland
376fa87a656SBob Copeland if (sta->mesh->plink_state == NL80211_PLINK_ESTAB)
377fa87a656SBob Copeland changed = mesh_plink_dec_estab_count(sdata);
378fa87a656SBob Copeland sta->mesh->plink_state = NL80211_PLINK_BLOCKED;
379fa87a656SBob Copeland
380fa87a656SBob Copeland ieee80211_mps_sta_status_update(sta);
381fa87a656SBob Copeland changed |= ieee80211_mps_set_sta_local_pm(sta,
382fa87a656SBob Copeland NL80211_MESH_POWER_UNKNOWN);
383fa87a656SBob Copeland
384fa87a656SBob Copeland return changed;
385fa87a656SBob Copeland }
386fa87a656SBob Copeland
387fa87a656SBob Copeland /**
388fa87a656SBob Copeland * mesh_plink_deactivate - deactivate mesh peer link
389fa87a656SBob Copeland *
390fa87a656SBob Copeland * @sta: mesh peer link to deactivate
391fa87a656SBob Copeland *
392fa87a656SBob Copeland * All mesh paths with this peer as next hop will be flushed
393fa87a656SBob Copeland */
mesh_plink_deactivate(struct sta_info * sta)39415ddba5fSAnjaneyulu u64 mesh_plink_deactivate(struct sta_info *sta)
395fa87a656SBob Copeland {
396fa87a656SBob Copeland struct ieee80211_sub_if_data *sdata = sta->sdata;
39715ddba5fSAnjaneyulu u64 changed;
398fa87a656SBob Copeland
399fa87a656SBob Copeland spin_lock_bh(&sta->mesh->plink_lock);
400fa87a656SBob Copeland changed = __mesh_plink_deactivate(sta);
401efc401f4SBob Copeland
402efc401f4SBob Copeland if (!sdata->u.mesh.user_mpm) {
403fa87a656SBob Copeland sta->mesh->reason = WLAN_REASON_MESH_PEER_CANCELED;
404a69bd8e6SBob Copeland mesh_plink_frame_tx(sdata, sta, WLAN_SP_MESH_PEERING_CLOSE,
405efc401f4SBob Copeland sta->sta.addr, sta->mesh->llid,
406efc401f4SBob Copeland sta->mesh->plid, sta->mesh->reason);
407efc401f4SBob Copeland }
408fa87a656SBob Copeland spin_unlock_bh(&sta->mesh->plink_lock);
409efc401f4SBob Copeland if (!sdata->u.mesh.user_mpm)
410efc401f4SBob Copeland del_timer_sync(&sta->mesh->plink_timer);
411e596af82SBob Copeland mesh_path_flush_by_nexthop(sta);
412fa87a656SBob Copeland
413efc401f4SBob Copeland /* make sure no readers can access nexthop sta from here on */
414efc401f4SBob Copeland synchronize_net();
415efc401f4SBob Copeland
416fa87a656SBob Copeland return changed;
417fa87a656SBob Copeland }
418fa87a656SBob Copeland
mesh_sta_info_init(struct ieee80211_sub_if_data * sdata,struct sta_info * sta,struct ieee802_11_elems * elems)419296fcba3SThomas Pedersen static void mesh_sta_info_init(struct ieee80211_sub_if_data *sdata,
420296fcba3SThomas Pedersen struct sta_info *sta,
4211d6741d8SBob Copeland struct ieee802_11_elems *elems)
42254ab1ffbSThomas Pedersen {
42354ab1ffbSThomas Pedersen struct ieee80211_local *local = sdata->local;
42454ab1ffbSThomas Pedersen struct ieee80211_supported_band *sband;
425f68d776aSThomas Pedersen u32 rates, basic_rates = 0, changed = 0;
426046d2e7cSSriram R enum ieee80211_sta_rx_bandwidth bw = sta->sta.deflink.bandwidth;
42754ab1ffbSThomas Pedersen
42821a8e9ddSMohammed Shafi Shajakhan sband = ieee80211_get_sband(sdata);
42921a8e9ddSMohammed Shafi Shajakhan if (!sband)
43021a8e9ddSMohammed Shafi Shajakhan return;
43121a8e9ddSMohammed Shafi Shajakhan
43221a8e9ddSMohammed Shafi Shajakhan rates = ieee80211_sta_get_rates(sdata, elems, sband->band,
43321a8e9ddSMohammed Shafi Shajakhan &basic_rates);
43454ab1ffbSThomas Pedersen
435433f5bc1SJohannes Berg spin_lock_bh(&sta->mesh->plink_lock);
436046d2e7cSSriram R sta->deflink.rx_stats.last_rx = jiffies;
437296fcba3SThomas Pedersen
438296fcba3SThomas Pedersen /* rates and capabilities don't change during peering */
439433f5bc1SJohannes Berg if (sta->mesh->plink_state == NL80211_PLINK_ESTAB &&
440433f5bc1SJohannes Berg sta->mesh->processed_beacon)
441296fcba3SThomas Pedersen goto out;
442433f5bc1SJohannes Berg sta->mesh->processed_beacon = true;
443bae35d92SChun-Yeow Yeoh
444046d2e7cSSriram R if (sta->sta.deflink.supp_rates[sband->band] != rates)
445f68d776aSThomas Pedersen changed |= IEEE80211_RC_SUPP_RATES_CHANGED;
446046d2e7cSSriram R sta->sta.deflink.supp_rates[sband->band] = rates;
44754ab1ffbSThomas Pedersen
44852ac8c48SThomas Pedersen if (ieee80211_ht_cap_ie_to_sta_ht_cap(sdata, sband,
449c71420dbSJohannes Berg elems->ht_cap_elem,
450c71420dbSJohannes Berg &sta->deflink))
451f68d776aSThomas Pedersen changed |= IEEE80211_RC_BW_CHANGED;
45252ac8c48SThomas Pedersen
453c85fb53cSBob Copeland ieee80211_vht_cap_ie_to_sta_vht_cap(sdata, sband,
454084cf2aeSJohannes Berg elems->vht_cap_elem, NULL,
455c71420dbSJohannes Berg &sta->deflink);
456c85fb53cSBob Copeland
45760ad72daSSven Eckelmann ieee80211_he_cap_ie_to_sta_he_cap(sdata, sband, elems->he_cap,
4581bb9a8a4SJohannes Berg elems->he_cap_len,
4591bb9a8a4SJohannes Berg elems->he_6ghz_capa,
460c71420dbSJohannes Berg &sta->deflink);
46160ad72daSSven Eckelmann
462df1875c4SRyder Lee ieee80211_eht_cap_ie_to_sta_eht_cap(sdata, sband, elems->he_cap,
463df1875c4SRyder Lee elems->he_cap_len,
464df1875c4SRyder Lee elems->eht_cap, elems->eht_cap_len,
465df1875c4SRyder Lee &sta->deflink);
466df1875c4SRyder Lee
467046d2e7cSSriram R if (bw != sta->sta.deflink.bandwidth)
468abcff6efSJanusz.Dziedzic@tieto.com changed |= IEEE80211_RC_BW_CHANGED;
469abcff6efSJanusz.Dziedzic@tieto.com
47052ac8c48SThomas Pedersen /* HT peer is operating 20MHz-only */
47152ac8c48SThomas Pedersen if (elems->ht_operation &&
47252ac8c48SThomas Pedersen !(elems->ht_operation->ht_param &
47352ac8c48SThomas Pedersen IEEE80211_HT_PARAM_CHAN_WIDTH_ANY)) {
474046d2e7cSSriram R if (sta->sta.deflink.bandwidth != IEEE80211_STA_RX_BW_20)
47552ac8c48SThomas Pedersen changed |= IEEE80211_RC_BW_CHANGED;
476046d2e7cSSriram R sta->sta.deflink.bandwidth = IEEE80211_STA_RX_BW_20;
47757aac7c5SAshok Nagarajan }
478c7d25828SThomas Pedersen
4791d6741d8SBob Copeland if (!test_sta_flag(sta, WLAN_STA_RATE_CONTROL))
48054ab1ffbSThomas Pedersen rate_control_rate_init(sta);
481f68d776aSThomas Pedersen else
482b4f85443SJohannes Berg rate_control_rate_update(local, sband, sta, 0, changed);
483296fcba3SThomas Pedersen out:
484433f5bc1SJohannes Berg spin_unlock_bh(&sta->mesh->plink_lock);
485296fcba3SThomas Pedersen }
48654ab1ffbSThomas Pedersen
mesh_allocate_aid(struct ieee80211_sub_if_data * sdata)4870e0060fcSBob Copeland static int mesh_allocate_aid(struct ieee80211_sub_if_data *sdata)
4880e0060fcSBob Copeland {
4890e0060fcSBob Copeland struct sta_info *sta;
4900e0060fcSBob Copeland unsigned long *aid_map;
4910e0060fcSBob Copeland int aid;
4920e0060fcSBob Copeland
49337babce9SChristophe JAILLET aid_map = bitmap_zalloc(IEEE80211_MAX_AID + 1, GFP_KERNEL);
4940e0060fcSBob Copeland if (!aid_map)
4950e0060fcSBob Copeland return -ENOMEM;
4960e0060fcSBob Copeland
4970e0060fcSBob Copeland /* reserve aid 0 for mcast indication */
4980e0060fcSBob Copeland __set_bit(0, aid_map);
4990e0060fcSBob Copeland
5000e0060fcSBob Copeland rcu_read_lock();
5010e0060fcSBob Copeland list_for_each_entry_rcu(sta, &sdata->local->sta_list, list)
5020e0060fcSBob Copeland __set_bit(sta->sta.aid, aid_map);
5030e0060fcSBob Copeland rcu_read_unlock();
5040e0060fcSBob Copeland
5050e0060fcSBob Copeland aid = find_first_zero_bit(aid_map, IEEE80211_MAX_AID + 1);
50637babce9SChristophe JAILLET bitmap_free(aid_map);
5070e0060fcSBob Copeland
5080e0060fcSBob Copeland if (aid > IEEE80211_MAX_AID)
5090e0060fcSBob Copeland return -ENOBUFS;
5100e0060fcSBob Copeland
5110e0060fcSBob Copeland return aid;
5120e0060fcSBob Copeland }
5130e0060fcSBob Copeland
514296fcba3SThomas Pedersen static struct sta_info *
__mesh_sta_info_alloc(struct ieee80211_sub_if_data * sdata,u8 * hw_addr)515296fcba3SThomas Pedersen __mesh_sta_info_alloc(struct ieee80211_sub_if_data *sdata, u8 *hw_addr)
516296fcba3SThomas Pedersen {
517296fcba3SThomas Pedersen struct sta_info *sta;
5180e0060fcSBob Copeland int aid;
519296fcba3SThomas Pedersen
520296fcba3SThomas Pedersen if (sdata->local->num_sta >= MESH_MAX_PLINKS)
521e87278e7SThomas Pedersen return NULL;
522e87278e7SThomas Pedersen
5230e0060fcSBob Copeland aid = mesh_allocate_aid(sdata);
5240e0060fcSBob Copeland if (aid < 0)
5250e0060fcSBob Copeland return NULL;
5260e0060fcSBob Copeland
527f36fe0a2SJohannes Berg sta = sta_info_alloc(sdata, hw_addr, GFP_KERNEL);
528296fcba3SThomas Pedersen if (!sta)
529296fcba3SThomas Pedersen return NULL;
530296fcba3SThomas Pedersen
531433f5bc1SJohannes Berg sta->mesh->plink_state = NL80211_PLINK_LISTEN;
532a74a8c84SJohannes Berg sta->sta.wme = true;
5330e0060fcSBob Copeland sta->sta.aid = aid;
534296fcba3SThomas Pedersen
535296fcba3SThomas Pedersen sta_info_pre_move_state(sta, IEEE80211_STA_AUTH);
536296fcba3SThomas Pedersen sta_info_pre_move_state(sta, IEEE80211_STA_ASSOC);
537296fcba3SThomas Pedersen sta_info_pre_move_state(sta, IEEE80211_STA_AUTHORIZED);
538296fcba3SThomas Pedersen
53954ab1ffbSThomas Pedersen return sta;
54054ab1ffbSThomas Pedersen }
54154ab1ffbSThomas Pedersen
542296fcba3SThomas Pedersen static struct sta_info *
mesh_sta_info_alloc(struct ieee80211_sub_if_data * sdata,u8 * addr,struct ieee802_11_elems * elems,struct ieee80211_rx_status * rx_status)543296fcba3SThomas Pedersen mesh_sta_info_alloc(struct ieee80211_sub_if_data *sdata, u8 *addr,
544ecbc12adSBob Copeland struct ieee802_11_elems *elems,
545ecbc12adSBob Copeland struct ieee80211_rx_status *rx_status)
546296fcba3SThomas Pedersen {
547296fcba3SThomas Pedersen struct sta_info *sta = NULL;
548296fcba3SThomas Pedersen
549a6dad6a2SThomas Pedersen /* Userspace handles station allocation */
550a6dad6a2SThomas Pedersen if (sdata->u.mesh.user_mpm ||
55111197d00SMasashi Honma sdata->u.mesh.security & IEEE80211_MESH_SEC_AUTHED) {
55211197d00SMasashi Honma if (mesh_peer_accepts_plinks(elems) &&
553ecbc12adSBob Copeland mesh_plink_availables(sdata)) {
554ecbc12adSBob Copeland int sig = 0;
555ecbc12adSBob Copeland
556ecbc12adSBob Copeland if (ieee80211_hw_check(&sdata->local->hw, SIGNAL_DBM))
557ecbc12adSBob Copeland sig = rx_status->signal;
558ecbc12adSBob Copeland
559296fcba3SThomas Pedersen cfg80211_notify_new_peer_candidate(sdata->dev, addr,
560296fcba3SThomas Pedersen elems->ie_start,
561296fcba3SThomas Pedersen elems->total_len,
562ecbc12adSBob Copeland sig, GFP_KERNEL);
563ecbc12adSBob Copeland }
56411197d00SMasashi Honma } else
565296fcba3SThomas Pedersen sta = __mesh_sta_info_alloc(sdata, addr);
566296fcba3SThomas Pedersen
567296fcba3SThomas Pedersen return sta;
568296fcba3SThomas Pedersen }
569296fcba3SThomas Pedersen
570296fcba3SThomas Pedersen /*
571296fcba3SThomas Pedersen * mesh_sta_info_get - return mesh sta info entry for @addr.
572296fcba3SThomas Pedersen *
573296fcba3SThomas Pedersen * @sdata: local meshif
574296fcba3SThomas Pedersen * @addr: peer's address
575296fcba3SThomas Pedersen * @elems: IEs from beacon or mesh peering frame.
576ecbc12adSBob Copeland * @rx_status: rx status for the frame for signal reporting
577296fcba3SThomas Pedersen *
578296fcba3SThomas Pedersen * Return existing or newly allocated sta_info under RCU read lock.
579296fcba3SThomas Pedersen * (re)initialize with given IEs.
580296fcba3SThomas Pedersen */
581296fcba3SThomas Pedersen static struct sta_info *
mesh_sta_info_get(struct ieee80211_sub_if_data * sdata,u8 * addr,struct ieee802_11_elems * elems,struct ieee80211_rx_status * rx_status)582296fcba3SThomas Pedersen mesh_sta_info_get(struct ieee80211_sub_if_data *sdata,
583ecbc12adSBob Copeland u8 *addr, struct ieee802_11_elems *elems,
584ecbc12adSBob Copeland struct ieee80211_rx_status *rx_status) __acquires(RCU)
585296fcba3SThomas Pedersen {
586296fcba3SThomas Pedersen struct sta_info *sta = NULL;
587296fcba3SThomas Pedersen
588296fcba3SThomas Pedersen rcu_read_lock();
589296fcba3SThomas Pedersen sta = sta_info_get(sdata, addr);
590296fcba3SThomas Pedersen if (sta) {
5911d6741d8SBob Copeland mesh_sta_info_init(sdata, sta, elems);
592296fcba3SThomas Pedersen } else {
593296fcba3SThomas Pedersen rcu_read_unlock();
594296fcba3SThomas Pedersen /* can't run atomic */
595ecbc12adSBob Copeland sta = mesh_sta_info_alloc(sdata, addr, elems, rx_status);
596296fcba3SThomas Pedersen if (!sta) {
597296fcba3SThomas Pedersen rcu_read_lock();
598296fcba3SThomas Pedersen return NULL;
599296fcba3SThomas Pedersen }
600296fcba3SThomas Pedersen
6011d6741d8SBob Copeland mesh_sta_info_init(sdata, sta, elems);
6023b4797bcSThomas Pedersen
603296fcba3SThomas Pedersen if (sta_info_insert_rcu(sta))
604296fcba3SThomas Pedersen return NULL;
605296fcba3SThomas Pedersen }
606296fcba3SThomas Pedersen
607296fcba3SThomas Pedersen return sta;
608296fcba3SThomas Pedersen }
609296fcba3SThomas Pedersen
610296fcba3SThomas Pedersen /*
611296fcba3SThomas Pedersen * mesh_neighbour_update - update or initialize new mesh neighbor.
612296fcba3SThomas Pedersen *
613296fcba3SThomas Pedersen * @sdata: local meshif
614296fcba3SThomas Pedersen * @addr: peer's address
615296fcba3SThomas Pedersen * @elems: IEs from beacon or mesh peering frame
616ecbc12adSBob Copeland * @rx_status: rx status for the frame for signal reporting
617296fcba3SThomas Pedersen *
618296fcba3SThomas Pedersen * Initiates peering if appropriate.
619296fcba3SThomas Pedersen */
mesh_neighbour_update(struct ieee80211_sub_if_data * sdata,u8 * hw_addr,struct ieee802_11_elems * elems,struct ieee80211_rx_status * rx_status)620f743ff49SThomas Pedersen void mesh_neighbour_update(struct ieee80211_sub_if_data *sdata,
621f743ff49SThomas Pedersen u8 *hw_addr,
622ecbc12adSBob Copeland struct ieee802_11_elems *elems,
623ecbc12adSBob Copeland struct ieee80211_rx_status *rx_status)
624c3896d2cSLuis Carlos Cobo {
625c3896d2cSLuis Carlos Cobo struct sta_info *sta;
62615ddba5fSAnjaneyulu u64 changed = 0;
627c3896d2cSLuis Carlos Cobo
628ecbc12adSBob Copeland sta = mesh_sta_info_get(sdata, hw_addr, elems, rx_status);
62954ab1ffbSThomas Pedersen if (!sta)
63054ab1ffbSThomas Pedersen goto out;
63154ab1ffbSThomas Pedersen
632dbdaee7aSBob Copeland sta->mesh->connected_to_gate = elems->mesh_config->meshconf_form &
633dbdaee7aSBob Copeland IEEE80211_MESHCONF_FORM_CONNECTED_TO_GATE;
634dbdaee7aSBob Copeland
6351570ca59SJavier Cardona if (mesh_peer_accepts_plinks(elems) &&
636433f5bc1SJohannes Berg sta->mesh->plink_state == NL80211_PLINK_LISTEN &&
637472dbc45SJohannes Berg sdata->u.mesh.accepting_plinks &&
63855335137SAshok Nagarajan sdata->u.mesh.mshcfg.auto_open_plinks &&
63936c9bb29SBob Copeland rssi_threshold_check(sdata, sta))
64039886b61SThomas Pedersen changed = mesh_plink_open(sta);
641c3896d2cSLuis Carlos Cobo
6423f52b7e3SMarco Porsch ieee80211_mps_frame_release(sta, elems);
64354ab1ffbSThomas Pedersen out:
644d0709a65SJohannes Berg rcu_read_unlock();
6452b5e1967SThomas Pedersen ieee80211_mbss_info_change_notify(sdata, changed);
646c3896d2cSLuis Carlos Cobo }
647c3896d2cSLuis Carlos Cobo
mesh_plink_timer(struct timer_list * t)6484c02d62fSKees Cook void mesh_plink_timer(struct timer_list *t)
649c3896d2cSLuis Carlos Cobo {
6504c02d62fSKees Cook struct mesh_sta *mesh = from_timer(mesh, t, plink_timer);
651c3896d2cSLuis Carlos Cobo struct sta_info *sta;
6526f101ef0SChun-Yeow Yeoh u16 reason = 0;
653c3896d2cSLuis Carlos Cobo struct ieee80211_sub_if_data *sdata;
654453e66f2SMarco Porsch struct mesh_config *mshcfg;
6554efec451SThomas Pedersen enum ieee80211_self_protected_actioncode action = 0;
656c3896d2cSLuis Carlos Cobo
657d0709a65SJohannes Berg /*
658d0709a65SJohannes Berg * This STA is valid because sta_info_destroy() will
659d0709a65SJohannes Berg * del_timer_sync() this timer after having made sure
660d0709a65SJohannes Berg * it cannot be readded (by deleting the plink.)
661d0709a65SJohannes Berg */
6624c02d62fSKees Cook sta = mesh->plink_sta;
663c3896d2cSLuis Carlos Cobo
664690205f1SStanislaw Gruszka if (sta->sdata->local->quiescing)
6655bb644a0SJohannes Berg return;
6665bb644a0SJohannes Berg
667433f5bc1SJohannes Berg spin_lock_bh(&sta->mesh->plink_lock);
6682b470c39SBob Copeland
6692b470c39SBob Copeland /* If a timer fires just before a state transition on another CPU,
6702b470c39SBob Copeland * we may have already extended the timeout and changed state by the
6712b470c39SBob Copeland * time we've acquired the lock and arrived here. In that case,
6722b470c39SBob Copeland * skip this timer and wait for the new one.
6732b470c39SBob Copeland */
674433f5bc1SJohannes Berg if (time_before(jiffies, sta->mesh->plink_timer.expires)) {
6752b470c39SBob Copeland mpl_dbg(sta->sdata,
6762b470c39SBob Copeland "Ignoring timer for %pM in state %s (timer adjusted)",
677433f5bc1SJohannes Berg sta->sta.addr, mplstates[sta->mesh->plink_state]);
678433f5bc1SJohannes Berg spin_unlock_bh(&sta->mesh->plink_lock);
679c3896d2cSLuis Carlos Cobo return;
680c3896d2cSLuis Carlos Cobo }
6812b470c39SBob Copeland
6822b470c39SBob Copeland /* del_timer() and handler may race when entering these states */
683433f5bc1SJohannes Berg if (sta->mesh->plink_state == NL80211_PLINK_LISTEN ||
684433f5bc1SJohannes Berg sta->mesh->plink_state == NL80211_PLINK_ESTAB) {
6852b470c39SBob Copeland mpl_dbg(sta->sdata,
6862b470c39SBob Copeland "Ignoring timer for %pM in state %s (timer deleted)",
687433f5bc1SJohannes Berg sta->sta.addr, mplstates[sta->mesh->plink_state]);
688433f5bc1SJohannes Berg spin_unlock_bh(&sta->mesh->plink_lock);
6892b470c39SBob Copeland return;
6902b470c39SBob Copeland }
6912b470c39SBob Copeland
692bdcbd8e0SJohannes Berg mpl_dbg(sta->sdata,
6933088f7d2SThomas Pedersen "Mesh plink timer for %pM fired on state %s\n",
694433f5bc1SJohannes Berg sta->sta.addr, mplstates[sta->mesh->plink_state]);
695d0709a65SJohannes Berg sdata = sta->sdata;
696453e66f2SMarco Porsch mshcfg = &sdata->u.mesh.mshcfg;
697c3896d2cSLuis Carlos Cobo
698433f5bc1SJohannes Berg switch (sta->mesh->plink_state) {
69957cf8043SJavier Cardona case NL80211_PLINK_OPN_RCVD:
70057cf8043SJavier Cardona case NL80211_PLINK_OPN_SNT:
701c3896d2cSLuis Carlos Cobo /* retry timer */
702433f5bc1SJohannes Berg if (sta->mesh->plink_retries < mshcfg->dot11MeshMaxRetries) {
703c3896d2cSLuis Carlos Cobo u32 rand;
704bdcbd8e0SJohannes Berg mpl_dbg(sta->sdata,
705bdcbd8e0SJohannes Berg "Mesh plink for %pM (retry, timeout): %d %d\n",
706433f5bc1SJohannes Berg sta->sta.addr, sta->mesh->plink_retries,
707433f5bc1SJohannes Berg sta->mesh->plink_timeout);
708c3896d2cSLuis Carlos Cobo get_random_bytes(&rand, sizeof(u32));
709433f5bc1SJohannes Berg sta->mesh->plink_timeout = sta->mesh->plink_timeout +
710433f5bc1SJohannes Berg rand % sta->mesh->plink_timeout;
711433f5bc1SJohannes Berg ++sta->mesh->plink_retries;
712433f5bc1SJohannes Berg mod_plink_timer(sta, sta->mesh->plink_timeout);
7134efec451SThomas Pedersen action = WLAN_SP_MESH_PEERING_OPEN;
714c3896d2cSLuis Carlos Cobo break;
715c3896d2cSLuis Carlos Cobo }
7166f101ef0SChun-Yeow Yeoh reason = WLAN_REASON_MESH_MAX_RETRIES;
717fc0561dcSGustavo A. R. Silva fallthrough;
71857cf8043SJavier Cardona case NL80211_PLINK_CNF_RCVD:
719c3896d2cSLuis Carlos Cobo /* confirm timer */
720c3896d2cSLuis Carlos Cobo if (!reason)
7216f101ef0SChun-Yeow Yeoh reason = WLAN_REASON_MESH_CONFIRM_TIMEOUT;
722433f5bc1SJohannes Berg sta->mesh->plink_state = NL80211_PLINK_HOLDING;
723453e66f2SMarco Porsch mod_plink_timer(sta, mshcfg->dot11MeshHoldingTimeout);
7244efec451SThomas Pedersen action = WLAN_SP_MESH_PEERING_CLOSE;
725c3896d2cSLuis Carlos Cobo break;
72657cf8043SJavier Cardona case NL80211_PLINK_HOLDING:
727c3896d2cSLuis Carlos Cobo /* holding timer */
728433f5bc1SJohannes Berg del_timer(&sta->mesh->plink_timer);
729c3896d2cSLuis Carlos Cobo mesh_plink_fsm_restart(sta);
730c3896d2cSLuis Carlos Cobo break;
731c3896d2cSLuis Carlos Cobo default:
732c3896d2cSLuis Carlos Cobo break;
733c3896d2cSLuis Carlos Cobo }
734433f5bc1SJohannes Berg spin_unlock_bh(&sta->mesh->plink_lock);
7354efec451SThomas Pedersen if (action)
736a69bd8e6SBob Copeland mesh_plink_frame_tx(sdata, sta, action, sta->sta.addr,
737433f5bc1SJohannes Berg sta->mesh->llid, sta->mesh->plid, reason);
738c3896d2cSLuis Carlos Cobo }
739c3896d2cSLuis Carlos Cobo
mesh_plink_timer_set(struct sta_info * sta,u32 timeout)7400df2f6c1SNicholas Mc Guire static inline void mesh_plink_timer_set(struct sta_info *sta, u32 timeout)
741c3896d2cSLuis Carlos Cobo {
742433f5bc1SJohannes Berg sta->mesh->plink_timeout = timeout;
7434c02d62fSKees Cook mod_timer(&sta->mesh->plink_timer, jiffies + msecs_to_jiffies(timeout));
744c3896d2cSLuis Carlos Cobo }
745c3896d2cSLuis Carlos Cobo
llid_in_use(struct ieee80211_sub_if_data * sdata,u16 llid)746204d1304SThomas Pedersen static bool llid_in_use(struct ieee80211_sub_if_data *sdata,
7476f101ef0SChun-Yeow Yeoh u16 llid)
748204d1304SThomas Pedersen {
749204d1304SThomas Pedersen struct ieee80211_local *local = sdata->local;
750204d1304SThomas Pedersen bool in_use = false;
751204d1304SThomas Pedersen struct sta_info *sta;
752204d1304SThomas Pedersen
753204d1304SThomas Pedersen rcu_read_lock();
754204d1304SThomas Pedersen list_for_each_entry_rcu(sta, &local->sta_list, list) {
755520c75dcSMatthias Schiffer if (sdata != sta->sdata)
756520c75dcSMatthias Schiffer continue;
757520c75dcSMatthias Schiffer
758433f5bc1SJohannes Berg if (!memcmp(&sta->mesh->llid, &llid, sizeof(llid))) {
759204d1304SThomas Pedersen in_use = true;
760204d1304SThomas Pedersen break;
761204d1304SThomas Pedersen }
762204d1304SThomas Pedersen }
763204d1304SThomas Pedersen rcu_read_unlock();
764204d1304SThomas Pedersen
765204d1304SThomas Pedersen return in_use;
766204d1304SThomas Pedersen }
767204d1304SThomas Pedersen
mesh_get_new_llid(struct ieee80211_sub_if_data * sdata)7686f101ef0SChun-Yeow Yeoh static u16 mesh_get_new_llid(struct ieee80211_sub_if_data *sdata)
769204d1304SThomas Pedersen {
770204d1304SThomas Pedersen u16 llid;
771204d1304SThomas Pedersen
772204d1304SThomas Pedersen do {
773204d1304SThomas Pedersen get_random_bytes(&llid, sizeof(llid));
7746f101ef0SChun-Yeow Yeoh } while (llid_in_use(sdata, llid));
775204d1304SThomas Pedersen
7766f101ef0SChun-Yeow Yeoh return llid;
777204d1304SThomas Pedersen }
778204d1304SThomas Pedersen
mesh_plink_open(struct sta_info * sta)77915ddba5fSAnjaneyulu u64 mesh_plink_open(struct sta_info *sta)
780c3896d2cSLuis Carlos Cobo {
781d0709a65SJohannes Berg struct ieee80211_sub_if_data *sdata = sta->sdata;
78215ddba5fSAnjaneyulu u64 changed;
783c3896d2cSLuis Carlos Cobo
784c2c98fdeSJohannes Berg if (!test_sta_flag(sta, WLAN_STA_AUTH))
78539886b61SThomas Pedersen return 0;
78653e80511SJavier Cardona
787433f5bc1SJohannes Berg spin_lock_bh(&sta->mesh->plink_lock);
788433f5bc1SJohannes Berg sta->mesh->llid = mesh_get_new_llid(sdata);
789433f5bc1SJohannes Berg if (sta->mesh->plink_state != NL80211_PLINK_LISTEN &&
790433f5bc1SJohannes Berg sta->mesh->plink_state != NL80211_PLINK_BLOCKED) {
791433f5bc1SJohannes Berg spin_unlock_bh(&sta->mesh->plink_lock);
79239886b61SThomas Pedersen return 0;
793c3896d2cSLuis Carlos Cobo }
794433f5bc1SJohannes Berg sta->mesh->plink_state = NL80211_PLINK_OPN_SNT;
795453e66f2SMarco Porsch mesh_plink_timer_set(sta, sdata->u.mesh.mshcfg.dot11MeshRetryTimeout);
796433f5bc1SJohannes Berg spin_unlock_bh(&sta->mesh->plink_lock);
797bdcbd8e0SJohannes Berg mpl_dbg(sdata,
798bdcbd8e0SJohannes Berg "Mesh plink: starting establishment with %pM\n",
7990c68ae26SJohannes Berg sta->sta.addr);
800c3896d2cSLuis Carlos Cobo
8013f52b7e3SMarco Porsch /* set the non-peer mode to active during peering */
80239886b61SThomas Pedersen changed = ieee80211_mps_local_status_update(sdata);
8033f52b7e3SMarco Porsch
804a69bd8e6SBob Copeland mesh_plink_frame_tx(sdata, sta, WLAN_SP_MESH_PEERING_OPEN,
805433f5bc1SJohannes Berg sta->sta.addr, sta->mesh->llid, 0, 0);
80639886b61SThomas Pedersen return changed;
807c3896d2cSLuis Carlos Cobo }
808c3896d2cSLuis Carlos Cobo
mesh_plink_block(struct sta_info * sta)80915ddba5fSAnjaneyulu u64 mesh_plink_block(struct sta_info *sta)
810c3896d2cSLuis Carlos Cobo {
81115ddba5fSAnjaneyulu u64 changed;
812c9370197SJohn W. Linville
813433f5bc1SJohannes Berg spin_lock_bh(&sta->mesh->plink_lock);
814df323818SMarco Porsch changed = __mesh_plink_deactivate(sta);
815433f5bc1SJohannes Berg sta->mesh->plink_state = NL80211_PLINK_BLOCKED;
816433f5bc1SJohannes Berg spin_unlock_bh(&sta->mesh->plink_lock);
817e596af82SBob Copeland mesh_path_flush_by_nexthop(sta);
818c9370197SJohn W. Linville
81939886b61SThomas Pedersen return changed;
820c3896d2cSLuis Carlos Cobo }
821c3896d2cSLuis Carlos Cobo
mesh_plink_close(struct ieee80211_sub_if_data * sdata,struct sta_info * sta,enum plink_event event)822e76d67f0SBob Copeland static void mesh_plink_close(struct ieee80211_sub_if_data *sdata,
823e76d67f0SBob Copeland struct sta_info *sta,
824e76d67f0SBob Copeland enum plink_event event)
825e76d67f0SBob Copeland {
826e76d67f0SBob Copeland struct mesh_config *mshcfg = &sdata->u.mesh.mshcfg;
8276f101ef0SChun-Yeow Yeoh u16 reason = (event == CLS_ACPT) ?
8286f101ef0SChun-Yeow Yeoh WLAN_REASON_MESH_CLOSE : WLAN_REASON_MESH_CONFIG;
829e76d67f0SBob Copeland
830433f5bc1SJohannes Berg sta->mesh->reason = reason;
831433f5bc1SJohannes Berg sta->mesh->plink_state = NL80211_PLINK_HOLDING;
832272a9e26SBob Copeland mod_plink_timer(sta, mshcfg->dot11MeshHoldingTimeout);
833e76d67f0SBob Copeland }
834e76d67f0SBob Copeland
mesh_plink_establish(struct ieee80211_sub_if_data * sdata,struct sta_info * sta)83515ddba5fSAnjaneyulu static u64 mesh_plink_establish(struct ieee80211_sub_if_data *sdata,
836e76d67f0SBob Copeland struct sta_info *sta)
837e76d67f0SBob Copeland {
838e76d67f0SBob Copeland struct mesh_config *mshcfg = &sdata->u.mesh.mshcfg;
83915ddba5fSAnjaneyulu u64 changed = 0;
840e76d67f0SBob Copeland
841433f5bc1SJohannes Berg del_timer(&sta->mesh->plink_timer);
842433f5bc1SJohannes Berg sta->mesh->plink_state = NL80211_PLINK_ESTAB;
843e76d67f0SBob Copeland changed |= mesh_plink_inc_estab_count(sdata);
844e76d67f0SBob Copeland changed |= mesh_set_ht_prot_mode(sdata);
845e76d67f0SBob Copeland changed |= mesh_set_short_slot_time(sdata);
846e76d67f0SBob Copeland mpl_dbg(sdata, "Mesh plink with %pM ESTABLISHED\n", sta->sta.addr);
847e76d67f0SBob Copeland ieee80211_mps_sta_status_update(sta);
848e76d67f0SBob Copeland changed |= ieee80211_mps_set_sta_local_pm(sta, mshcfg->power_mode);
849e76d67f0SBob Copeland return changed;
850e76d67f0SBob Copeland }
851e76d67f0SBob Copeland
852c7e67811SThomas Pedersen /**
853c7e67811SThomas Pedersen * mesh_plink_fsm - step @sta MPM based on @event
854c7e67811SThomas Pedersen *
855c7e67811SThomas Pedersen * @sdata: interface
856c7e67811SThomas Pedersen * @sta: mesh neighbor
857c7e67811SThomas Pedersen * @event: peering event
858c7e67811SThomas Pedersen *
859c7e67811SThomas Pedersen * Return: changed MBSS flags
860c7e67811SThomas Pedersen */
mesh_plink_fsm(struct ieee80211_sub_if_data * sdata,struct sta_info * sta,enum plink_event event)86115ddba5fSAnjaneyulu static u64 mesh_plink_fsm(struct ieee80211_sub_if_data *sdata,
862c7e67811SThomas Pedersen struct sta_info *sta, enum plink_event event)
863c7e67811SThomas Pedersen {
864c7e67811SThomas Pedersen struct mesh_config *mshcfg = &sdata->u.mesh.mshcfg;
865c7e67811SThomas Pedersen enum ieee80211_self_protected_actioncode action = 0;
86615ddba5fSAnjaneyulu u64 changed = 0;
867e596af82SBob Copeland bool flush = false;
868c7e67811SThomas Pedersen
869c7e67811SThomas Pedersen mpl_dbg(sdata, "peer %pM in state %s got event %s\n", sta->sta.addr,
870433f5bc1SJohannes Berg mplstates[sta->mesh->plink_state], mplevents[event]);
871c7e67811SThomas Pedersen
872433f5bc1SJohannes Berg spin_lock_bh(&sta->mesh->plink_lock);
873433f5bc1SJohannes Berg switch (sta->mesh->plink_state) {
874c7e67811SThomas Pedersen case NL80211_PLINK_LISTEN:
875c7e67811SThomas Pedersen switch (event) {
876c7e67811SThomas Pedersen case CLS_ACPT:
877c7e67811SThomas Pedersen mesh_plink_fsm_restart(sta);
878c7e67811SThomas Pedersen break;
879c7e67811SThomas Pedersen case OPN_ACPT:
880433f5bc1SJohannes Berg sta->mesh->plink_state = NL80211_PLINK_OPN_RCVD;
881433f5bc1SJohannes Berg sta->mesh->llid = mesh_get_new_llid(sdata);
882c7e67811SThomas Pedersen mesh_plink_timer_set(sta,
883c7e67811SThomas Pedersen mshcfg->dot11MeshRetryTimeout);
884c7e67811SThomas Pedersen
885c7e67811SThomas Pedersen /* set the non-peer mode to active during peering */
886c7e67811SThomas Pedersen changed |= ieee80211_mps_local_status_update(sdata);
887c7e67811SThomas Pedersen action = WLAN_SP_MESH_PEERING_OPEN;
888c7e67811SThomas Pedersen break;
889c7e67811SThomas Pedersen default:
890c7e67811SThomas Pedersen break;
891c7e67811SThomas Pedersen }
892c7e67811SThomas Pedersen break;
893c7e67811SThomas Pedersen case NL80211_PLINK_OPN_SNT:
894c7e67811SThomas Pedersen switch (event) {
895c7e67811SThomas Pedersen case OPN_RJCT:
896c7e67811SThomas Pedersen case CNF_RJCT:
897c7e67811SThomas Pedersen case CLS_ACPT:
898c7e67811SThomas Pedersen mesh_plink_close(sdata, sta, event);
899c7e67811SThomas Pedersen action = WLAN_SP_MESH_PEERING_CLOSE;
900c7e67811SThomas Pedersen break;
901c7e67811SThomas Pedersen case OPN_ACPT:
902c7e67811SThomas Pedersen /* retry timer is left untouched */
903433f5bc1SJohannes Berg sta->mesh->plink_state = NL80211_PLINK_OPN_RCVD;
904c7e67811SThomas Pedersen action = WLAN_SP_MESH_PEERING_CONFIRM;
905c7e67811SThomas Pedersen break;
906c7e67811SThomas Pedersen case CNF_ACPT:
907433f5bc1SJohannes Berg sta->mesh->plink_state = NL80211_PLINK_CNF_RCVD;
9082b470c39SBob Copeland mod_plink_timer(sta, mshcfg->dot11MeshConfirmTimeout);
909c7e67811SThomas Pedersen break;
910c7e67811SThomas Pedersen default:
911c7e67811SThomas Pedersen break;
912c7e67811SThomas Pedersen }
913c7e67811SThomas Pedersen break;
914c7e67811SThomas Pedersen case NL80211_PLINK_OPN_RCVD:
915c7e67811SThomas Pedersen switch (event) {
916c7e67811SThomas Pedersen case OPN_RJCT:
917c7e67811SThomas Pedersen case CNF_RJCT:
918c7e67811SThomas Pedersen case CLS_ACPT:
919c7e67811SThomas Pedersen mesh_plink_close(sdata, sta, event);
920c7e67811SThomas Pedersen action = WLAN_SP_MESH_PEERING_CLOSE;
921c7e67811SThomas Pedersen break;
922c7e67811SThomas Pedersen case OPN_ACPT:
923c7e67811SThomas Pedersen action = WLAN_SP_MESH_PEERING_CONFIRM;
924c7e67811SThomas Pedersen break;
925c7e67811SThomas Pedersen case CNF_ACPT:
926c7e67811SThomas Pedersen changed |= mesh_plink_establish(sdata, sta);
927c7e67811SThomas Pedersen break;
928c7e67811SThomas Pedersen default:
929c7e67811SThomas Pedersen break;
930c7e67811SThomas Pedersen }
931c7e67811SThomas Pedersen break;
932c7e67811SThomas Pedersen case NL80211_PLINK_CNF_RCVD:
933c7e67811SThomas Pedersen switch (event) {
934c7e67811SThomas Pedersen case OPN_RJCT:
935c7e67811SThomas Pedersen case CNF_RJCT:
936c7e67811SThomas Pedersen case CLS_ACPT:
937c7e67811SThomas Pedersen mesh_plink_close(sdata, sta, event);
938c7e67811SThomas Pedersen action = WLAN_SP_MESH_PEERING_CLOSE;
939c7e67811SThomas Pedersen break;
940c7e67811SThomas Pedersen case OPN_ACPT:
941c7e67811SThomas Pedersen changed |= mesh_plink_establish(sdata, sta);
942c7e67811SThomas Pedersen action = WLAN_SP_MESH_PEERING_CONFIRM;
943c7e67811SThomas Pedersen break;
944c7e67811SThomas Pedersen default:
945c7e67811SThomas Pedersen break;
946c7e67811SThomas Pedersen }
947c7e67811SThomas Pedersen break;
948c7e67811SThomas Pedersen case NL80211_PLINK_ESTAB:
949c7e67811SThomas Pedersen switch (event) {
950c7e67811SThomas Pedersen case CLS_ACPT:
951c7e67811SThomas Pedersen changed |= __mesh_plink_deactivate(sta);
952c7e67811SThomas Pedersen changed |= mesh_set_ht_prot_mode(sdata);
953c7e67811SThomas Pedersen changed |= mesh_set_short_slot_time(sdata);
954c7e67811SThomas Pedersen mesh_plink_close(sdata, sta, event);
955c7e67811SThomas Pedersen action = WLAN_SP_MESH_PEERING_CLOSE;
956e596af82SBob Copeland flush = true;
957c7e67811SThomas Pedersen break;
958c7e67811SThomas Pedersen case OPN_ACPT:
959c7e67811SThomas Pedersen action = WLAN_SP_MESH_PEERING_CONFIRM;
960c7e67811SThomas Pedersen break;
961c7e67811SThomas Pedersen default:
962c7e67811SThomas Pedersen break;
963c7e67811SThomas Pedersen }
964c7e67811SThomas Pedersen break;
965c7e67811SThomas Pedersen case NL80211_PLINK_HOLDING:
966c7e67811SThomas Pedersen switch (event) {
967c7e67811SThomas Pedersen case CLS_ACPT:
968433f5bc1SJohannes Berg del_timer(&sta->mesh->plink_timer);
969c7e67811SThomas Pedersen mesh_plink_fsm_restart(sta);
970c7e67811SThomas Pedersen break;
971c7e67811SThomas Pedersen case OPN_ACPT:
972c7e67811SThomas Pedersen case CNF_ACPT:
973c7e67811SThomas Pedersen case OPN_RJCT:
974c7e67811SThomas Pedersen case CNF_RJCT:
975c7e67811SThomas Pedersen action = WLAN_SP_MESH_PEERING_CLOSE;
976c7e67811SThomas Pedersen break;
977c7e67811SThomas Pedersen default:
978c7e67811SThomas Pedersen break;
979c7e67811SThomas Pedersen }
980c7e67811SThomas Pedersen break;
981c7e67811SThomas Pedersen default:
982c7e67811SThomas Pedersen /* should not get here, PLINK_BLOCKED is dealt with at the
983c7e67811SThomas Pedersen * beginning of the function
984c7e67811SThomas Pedersen */
985c7e67811SThomas Pedersen break;
986c7e67811SThomas Pedersen }
987433f5bc1SJohannes Berg spin_unlock_bh(&sta->mesh->plink_lock);
988e596af82SBob Copeland if (flush)
989e596af82SBob Copeland mesh_path_flush_by_nexthop(sta);
990c7e67811SThomas Pedersen if (action) {
991a69bd8e6SBob Copeland mesh_plink_frame_tx(sdata, sta, action, sta->sta.addr,
992433f5bc1SJohannes Berg sta->mesh->llid, sta->mesh->plid,
993433f5bc1SJohannes Berg sta->mesh->reason);
994c7e67811SThomas Pedersen
995c7e67811SThomas Pedersen /* also send confirm in open case */
996c7e67811SThomas Pedersen if (action == WLAN_SP_MESH_PEERING_OPEN) {
997a69bd8e6SBob Copeland mesh_plink_frame_tx(sdata, sta,
998c7e67811SThomas Pedersen WLAN_SP_MESH_PEERING_CONFIRM,
999433f5bc1SJohannes Berg sta->sta.addr, sta->mesh->llid,
1000433f5bc1SJohannes Berg sta->mesh->plid, 0);
1001c7e67811SThomas Pedersen }
1002c7e67811SThomas Pedersen }
1003c7e67811SThomas Pedersen
1004c7e67811SThomas Pedersen return changed;
1005c7e67811SThomas Pedersen }
1006c7e67811SThomas Pedersen
1007c99a89edSThomas Pedersen /*
1008c99a89edSThomas Pedersen * mesh_plink_get_event - get correct MPM event
1009c99a89edSThomas Pedersen *
1010c99a89edSThomas Pedersen * @sdata: interface
1011c99a89edSThomas Pedersen * @sta: peer, leave NULL if processing a frame from a new suitable peer
1012c99a89edSThomas Pedersen * @elems: peering management IEs
1013c99a89edSThomas Pedersen * @ftype: frame type
1014c99a89edSThomas Pedersen * @llid: peer's peer link ID
1015c99a89edSThomas Pedersen * @plid: peer's local link ID
1016c99a89edSThomas Pedersen *
1017c99a89edSThomas Pedersen * Return: new peering event for @sta, but PLINK_UNDEFINED should be treated as
1018c99a89edSThomas Pedersen * an error.
1019c99a89edSThomas Pedersen */
1020c99a89edSThomas Pedersen 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)1021c99a89edSThomas Pedersen mesh_plink_get_event(struct ieee80211_sub_if_data *sdata,
1022c99a89edSThomas Pedersen struct sta_info *sta,
1023c99a89edSThomas Pedersen struct ieee802_11_elems *elems,
1024c99a89edSThomas Pedersen enum ieee80211_self_protected_actioncode ftype,
10256f101ef0SChun-Yeow Yeoh u16 llid, u16 plid)
1026c99a89edSThomas Pedersen {
1027c99a89edSThomas Pedersen enum plink_event event = PLINK_UNDEFINED;
1028c99a89edSThomas Pedersen u8 ie_len = elems->peering_len;
1029c99a89edSThomas Pedersen bool matches_local;
1030c99a89edSThomas Pedersen
1031c99a89edSThomas Pedersen matches_local = (ftype == WLAN_SP_MESH_PEERING_CLOSE ||
1032c99a89edSThomas Pedersen mesh_matches_local(sdata, elems));
1033c99a89edSThomas Pedersen
1034c99a89edSThomas Pedersen /* deny open request from non-matching peer */
1035c99a89edSThomas Pedersen if (!matches_local && !sta) {
1036c99a89edSThomas Pedersen event = OPN_RJCT;
1037c99a89edSThomas Pedersen goto out;
1038c99a89edSThomas Pedersen }
1039c99a89edSThomas Pedersen
1040c99a89edSThomas Pedersen if (!sta) {
1041c99a89edSThomas Pedersen if (ftype != WLAN_SP_MESH_PEERING_OPEN) {
1042c99a89edSThomas Pedersen mpl_dbg(sdata, "Mesh plink: cls or cnf from unknown peer\n");
1043c99a89edSThomas Pedersen goto out;
1044c99a89edSThomas Pedersen }
1045c99a89edSThomas Pedersen /* ftype == WLAN_SP_MESH_PEERING_OPEN */
1046c99a89edSThomas Pedersen if (!mesh_plink_free_count(sdata)) {
1047c99a89edSThomas Pedersen mpl_dbg(sdata, "Mesh plink error: no more free plinks\n");
1048c99a89edSThomas Pedersen goto out;
1049c99a89edSThomas Pedersen }
1050b8631c00SSunil Shahu
1051b8631c00SSunil Shahu /* new matching peer */
1052b8631c00SSunil Shahu event = OPN_ACPT;
1053b8631c00SSunil Shahu goto out;
1054c99a89edSThomas Pedersen } else {
1055c99a89edSThomas Pedersen if (!test_sta_flag(sta, WLAN_STA_AUTH)) {
1056c99a89edSThomas Pedersen mpl_dbg(sdata, "Mesh plink: Action frame from non-authed peer\n");
1057c99a89edSThomas Pedersen goto out;
1058c99a89edSThomas Pedersen }
1059433f5bc1SJohannes Berg if (sta->mesh->plink_state == NL80211_PLINK_BLOCKED)
1060c99a89edSThomas Pedersen goto out;
1061c99a89edSThomas Pedersen }
1062c99a89edSThomas Pedersen
1063c99a89edSThomas Pedersen switch (ftype) {
1064c99a89edSThomas Pedersen case WLAN_SP_MESH_PEERING_OPEN:
1065c99a89edSThomas Pedersen if (!matches_local)
1066c99a89edSThomas Pedersen event = OPN_RJCT;
1067*26479609SJohannes Berg else if (!mesh_plink_free_count(sdata) ||
1068433f5bc1SJohannes Berg (sta->mesh->plid && sta->mesh->plid != plid))
1069c99a89edSThomas Pedersen event = OPN_IGNR;
1070c99a89edSThomas Pedersen else
1071c99a89edSThomas Pedersen event = OPN_ACPT;
1072c99a89edSThomas Pedersen break;
1073c99a89edSThomas Pedersen case WLAN_SP_MESH_PEERING_CONFIRM:
1074c99a89edSThomas Pedersen if (!matches_local)
1075c99a89edSThomas Pedersen event = CNF_RJCT;
1076*26479609SJohannes Berg else if (!mesh_plink_free_count(sdata) ||
1077433f5bc1SJohannes Berg sta->mesh->llid != llid ||
1078433f5bc1SJohannes Berg (sta->mesh->plid && sta->mesh->plid != plid))
1079c99a89edSThomas Pedersen event = CNF_IGNR;
1080c99a89edSThomas Pedersen else
1081c99a89edSThomas Pedersen event = CNF_ACPT;
1082c99a89edSThomas Pedersen break;
1083c99a89edSThomas Pedersen case WLAN_SP_MESH_PEERING_CLOSE:
1084433f5bc1SJohannes Berg if (sta->mesh->plink_state == NL80211_PLINK_ESTAB)
1085c99a89edSThomas Pedersen /* Do not check for llid or plid. This does not
1086c99a89edSThomas Pedersen * follow the standard but since multiple plinks
1087c99a89edSThomas Pedersen * per sta are not supported, it is necessary in
1088c99a89edSThomas Pedersen * order to avoid a livelock when MP A sees an
1089c99a89edSThomas Pedersen * establish peer link to MP B but MP B does not
1090c99a89edSThomas Pedersen * see it. This can be caused by a timeout in
1091c99a89edSThomas Pedersen * B's peer link establishment or B beign
1092c99a89edSThomas Pedersen * restarted.
1093c99a89edSThomas Pedersen */
1094c99a89edSThomas Pedersen event = CLS_ACPT;
1095433f5bc1SJohannes Berg else if (sta->mesh->plid != plid)
1096c99a89edSThomas Pedersen event = CLS_IGNR;
1097433f5bc1SJohannes Berg else if (ie_len == 8 && sta->mesh->llid != llid)
1098c99a89edSThomas Pedersen event = CLS_IGNR;
1099c99a89edSThomas Pedersen else
1100c99a89edSThomas Pedersen event = CLS_ACPT;
1101c99a89edSThomas Pedersen break;
1102c99a89edSThomas Pedersen default:
1103c99a89edSThomas Pedersen mpl_dbg(sdata, "Mesh plink: unknown frame subtype\n");
1104c99a89edSThomas Pedersen break;
1105c99a89edSThomas Pedersen }
1106c99a89edSThomas Pedersen
1107c99a89edSThomas Pedersen out:
1108c99a89edSThomas Pedersen return event;
1109c99a89edSThomas Pedersen }
1110c99a89edSThomas Pedersen
111105a23ae9SThomas Pedersen static void
mesh_process_plink_frame(struct ieee80211_sub_if_data * sdata,struct ieee80211_mgmt * mgmt,struct ieee802_11_elems * elems,struct ieee80211_rx_status * rx_status)111205a23ae9SThomas Pedersen mesh_process_plink_frame(struct ieee80211_sub_if_data *sdata,
111305a23ae9SThomas Pedersen struct ieee80211_mgmt *mgmt,
1114ecbc12adSBob Copeland struct ieee802_11_elems *elems,
1115ecbc12adSBob Copeland struct ieee80211_rx_status *rx_status)
1116c3896d2cSLuis Carlos Cobo {
111705a23ae9SThomas Pedersen
1118c3896d2cSLuis Carlos Cobo struct sta_info *sta;
1119c3896d2cSLuis Carlos Cobo enum plink_event event;
112054ef656bSThomas Pedersen enum ieee80211_self_protected_actioncode ftype;
112115ddba5fSAnjaneyulu u64 changed = 0;
1122c99a89edSThomas Pedersen u8 ie_len = elems->peering_len;
11236f101ef0SChun-Yeow Yeoh u16 plid, llid = 0;
1124c3896d2cSLuis Carlos Cobo
112505a23ae9SThomas Pedersen if (!elems->peering) {
1126bdcbd8e0SJohannes Berg mpl_dbg(sdata,
1127bdcbd8e0SJohannes Berg "Mesh plink: missing necessary peer link ie\n");
1128c3896d2cSLuis Carlos Cobo return;
1129c3896d2cSLuis Carlos Cobo }
1130bf7cd94dSJohannes Berg
113105a23ae9SThomas Pedersen if (elems->rsn_len &&
1132b130e5ceSJavier Cardona sdata->u.mesh.security == IEEE80211_MESH_SEC_NONE) {
1133bdcbd8e0SJohannes Berg mpl_dbg(sdata,
1134bdcbd8e0SJohannes Berg "Mesh plink: can't establish link with secure peer\n");
11355cff5e01SJavier Cardona return;
11365cff5e01SJavier Cardona }
1137c3896d2cSLuis Carlos Cobo
11388db09850SThomas Pedersen ftype = mgmt->u.action.u.self_prot.action_code;
11398db09850SThomas Pedersen if ((ftype == WLAN_SP_MESH_PEERING_OPEN && ie_len != 4) ||
11408db09850SThomas Pedersen (ftype == WLAN_SP_MESH_PEERING_CONFIRM && ie_len != 6) ||
11418db09850SThomas Pedersen (ftype == WLAN_SP_MESH_PEERING_CLOSE && ie_len != 6
11428db09850SThomas Pedersen && ie_len != 8)) {
1143bdcbd8e0SJohannes Berg mpl_dbg(sdata,
1144bdcbd8e0SJohannes Berg "Mesh plink: incorrect plink ie length %d %d\n",
11450938393fSRui Paulo ftype, ie_len);
1146c3896d2cSLuis Carlos Cobo return;
1147c3896d2cSLuis Carlos Cobo }
1148c3896d2cSLuis Carlos Cobo
114954ef656bSThomas Pedersen if (ftype != WLAN_SP_MESH_PEERING_CLOSE &&
115005a23ae9SThomas Pedersen (!elems->mesh_id || !elems->mesh_config)) {
1151bdcbd8e0SJohannes Berg mpl_dbg(sdata, "Mesh plink: missing necessary ie\n");
1152c3896d2cSLuis Carlos Cobo return;
1153c3896d2cSLuis Carlos Cobo }
1154c3896d2cSLuis Carlos Cobo /* Note the lines below are correct, the llid in the frame is the plid
1155c3896d2cSLuis Carlos Cobo * from the point of view of this host.
1156c3896d2cSLuis Carlos Cobo */
1157f8134fedSBob Copeland plid = get_unaligned_le16(PLINK_GET_LLID(elems->peering));
115854ef656bSThomas Pedersen if (ftype == WLAN_SP_MESH_PEERING_CONFIRM ||
1159f8134fedSBob Copeland (ftype == WLAN_SP_MESH_PEERING_CLOSE && ie_len == 8))
1160f8134fedSBob Copeland llid = get_unaligned_le16(PLINK_GET_PLID(elems->peering));
1161c3896d2cSLuis Carlos Cobo
1162296fcba3SThomas Pedersen /* WARNING: Only for sta pointer, is dropped & re-acquired */
1163d0709a65SJohannes Berg rcu_read_lock();
1164d0709a65SJohannes Berg
1165abe60632SJohannes Berg sta = sta_info_get(sdata, mgmt->sa);
116632cb05bfSBob Copeland
116755335137SAshok Nagarajan if (ftype == WLAN_SP_MESH_PEERING_OPEN &&
116836c9bb29SBob Copeland !rssi_threshold_check(sdata, sta)) {
1169bdcbd8e0SJohannes Berg mpl_dbg(sdata, "Mesh plink: %pM does not meet rssi threshold\n",
11703d4f9699SAshok Nagarajan mgmt->sa);
1171fc10302eSThomas Pedersen goto unlock_rcu;
117255335137SAshok Nagarajan }
117355335137SAshok Nagarajan
1174c3896d2cSLuis Carlos Cobo /* Now we will figure out the appropriate event... */
1175c99a89edSThomas Pedersen event = mesh_plink_get_event(sdata, sta, elems, ftype, llid, plid);
117654ab1ffbSThomas Pedersen
117754ab1ffbSThomas Pedersen if (event == OPN_ACPT) {
1178296fcba3SThomas Pedersen rcu_read_unlock();
117954ab1ffbSThomas Pedersen /* allocate sta entry if necessary and update info */
1180ecbc12adSBob Copeland sta = mesh_sta_info_get(sdata, mgmt->sa, elems, rx_status);
118154ab1ffbSThomas Pedersen if (!sta) {
1182bdcbd8e0SJohannes Berg mpl_dbg(sdata, "Mesh plink: failed to init peer!\n");
1183fc10302eSThomas Pedersen goto unlock_rcu;
118454ab1ffbSThomas Pedersen }
1185433f5bc1SJohannes Berg sta->mesh->plid = plid;
1186c99a89edSThomas Pedersen } else if (!sta && event == OPN_RJCT) {
1187a69bd8e6SBob Copeland mesh_plink_frame_tx(sdata, NULL, WLAN_SP_MESH_PEERING_CLOSE,
1188c99a89edSThomas Pedersen mgmt->sa, 0, plid,
11896f101ef0SChun-Yeow Yeoh WLAN_REASON_MESH_CONFIG);
1190c99a89edSThomas Pedersen goto unlock_rcu;
1191c99a89edSThomas Pedersen } else if (!sta || event == PLINK_UNDEFINED) {
1192c99a89edSThomas Pedersen /* something went wrong */
1193c99a89edSThomas Pedersen goto unlock_rcu;
1194c3896d2cSLuis Carlos Cobo }
1195c3896d2cSLuis Carlos Cobo
1196a69bd8e6SBob Copeland if (event == CNF_ACPT) {
11976c6fa496SBob Copeland /* 802.11-2012 13.3.7.2 - update plid on CNF if not set */
11980e0060fcSBob Copeland if (!sta->mesh->plid)
1199433f5bc1SJohannes Berg sta->mesh->plid = plid;
1200a69bd8e6SBob Copeland
1201a69bd8e6SBob Copeland sta->mesh->aid = get_unaligned_le16(PLINK_CNF_AID(mgmt));
1202a69bd8e6SBob Copeland }
12036c6fa496SBob Copeland
1204c7e67811SThomas Pedersen changed |= mesh_plink_fsm(sdata, sta, event);
1205d0709a65SJohannes Berg
1206fc10302eSThomas Pedersen unlock_rcu:
1207d0709a65SJohannes Berg rcu_read_unlock();
120857aac7c5SAshok Nagarajan
1209ecccd072SThomas Pedersen if (changed)
12102b5e1967SThomas Pedersen ieee80211_mbss_info_change_notify(sdata, changed);
1211c3896d2cSLuis Carlos Cobo }
121205a23ae9SThomas Pedersen
mesh_rx_plink_frame(struct ieee80211_sub_if_data * sdata,struct ieee80211_mgmt * mgmt,size_t len,struct ieee80211_rx_status * rx_status)121305a23ae9SThomas Pedersen void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata,
121405a23ae9SThomas Pedersen struct ieee80211_mgmt *mgmt, size_t len,
121505a23ae9SThomas Pedersen struct ieee80211_rx_status *rx_status)
121605a23ae9SThomas Pedersen {
12175d24828dSJohannes Berg struct ieee802_11_elems *elems;
121805a23ae9SThomas Pedersen size_t baselen;
121905a23ae9SThomas Pedersen u8 *baseaddr;
122005a23ae9SThomas Pedersen
122105a23ae9SThomas Pedersen /* need action_code, aux */
122205a23ae9SThomas Pedersen if (len < IEEE80211_MIN_ACTION_SIZE + 3)
122305a23ae9SThomas Pedersen return;
122405a23ae9SThomas Pedersen
122505a23ae9SThomas Pedersen if (sdata->u.mesh.user_mpm)
122605a23ae9SThomas Pedersen /* userspace must register for these */
122705a23ae9SThomas Pedersen return;
122805a23ae9SThomas Pedersen
122905a23ae9SThomas Pedersen if (is_multicast_ether_addr(mgmt->da)) {
123005a23ae9SThomas Pedersen mpl_dbg(sdata,
123105a23ae9SThomas Pedersen "Mesh plink: ignore frame from multicast address\n");
123205a23ae9SThomas Pedersen return;
123305a23ae9SThomas Pedersen }
123405a23ae9SThomas Pedersen
123505a23ae9SThomas Pedersen baseaddr = mgmt->u.action.u.self_prot.variable;
123605a23ae9SThomas Pedersen baselen = (u8 *) mgmt->u.action.u.self_prot.variable - (u8 *) mgmt;
123705a23ae9SThomas Pedersen if (mgmt->u.action.u.self_prot.action_code ==
123805a23ae9SThomas Pedersen WLAN_SP_MESH_PEERING_CONFIRM) {
123905a23ae9SThomas Pedersen baseaddr += 4;
124005a23ae9SThomas Pedersen baselen += 4;
1241b3e7de87SBob Copeland
1242b3e7de87SBob Copeland if (baselen > len)
1243b3e7de87SBob Copeland return;
124405a23ae9SThomas Pedersen }
124538c6aa29SJohannes Berg elems = ieee802_11_parse_elems(baseaddr, len - baselen, true, NULL);
12461c8d8012SJohannes Berg if (elems) {
12475d24828dSJohannes Berg mesh_process_plink_frame(sdata, mgmt, elems, rx_status);
12485d24828dSJohannes Berg kfree(elems);
124905a23ae9SThomas Pedersen }
12501c8d8012SJohannes Berg }
1251