Commit | Line | Data |
---|---|---|
b2441318 | 1 | // SPDX-License-Identifier: GPL-2.0 |
6039f6d2 JM |
2 | /* |
3 | * cfg80211 MLME SAP interface | |
4 | * | |
5 | * Copyright (c) 2009, Jouni Malinen <j@w1.fi> | |
33d8783c | 6 | * Copyright (c) 2015 Intel Deutschland GmbH |
a096a860 | 7 | * Copyright (C) 2019-2020, 2022-2025 Intel Corporation |
6039f6d2 JM |
8 | */ |
9 | ||
10 | #include <linux/kernel.h> | |
11 | #include <linux/module.h> | |
c6fb08aa | 12 | #include <linux/etherdevice.h> |
6039f6d2 JM |
13 | #include <linux/netdevice.h> |
14 | #include <linux/nl80211.h> | |
5a0e3ad6 | 15 | #include <linux/slab.h> |
a9a11622 | 16 | #include <linux/wireless.h> |
6039f6d2 | 17 | #include <net/cfg80211.h> |
a9a11622 | 18 | #include <net/iw_handler.h> |
6039f6d2 JM |
19 | #include "core.h" |
20 | #include "nl80211.h" | |
e35e4d28 HG |
21 | #include "rdev-ops.h" |
22 | ||
6039f6d2 | 23 | |
cd47c0f5 | 24 | void cfg80211_rx_assoc_resp(struct net_device *dev, |
88f29324 | 25 | const struct cfg80211_rx_assoc_resp_data *data) |
6039f6d2 | 26 | { |
6829c878 JB |
27 | struct wireless_dev *wdev = dev->ieee80211_ptr; |
28 | struct wiphy *wiphy = wdev->wiphy; | |
f26cbf40 | 29 | struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); |
cd47c0f5 | 30 | struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)data->buf; |
5cd212cb JB |
31 | struct cfg80211_connect_resp_params cr = { |
32 | .timeout_reason = NL80211_TIMEOUT_UNSPECIFIED, | |
33 | .req_ie = data->req_ies, | |
34 | .req_ie_len = data->req_ies_len, | |
35 | .resp_ie = mgmt->u.assoc_resp.variable, | |
36 | .resp_ie_len = data->len - | |
37 | offsetof(struct ieee80211_mgmt, | |
38 | u.assoc_resp.variable), | |
39 | .status = le16_to_cpu(mgmt->u.assoc_resp.status_code), | |
40 | .ap_mld_addr = data->ap_mld_addr, | |
41 | }; | |
42 | unsigned int link_id; | |
80ca2571 | 43 | |
5cd212cb | 44 | for (link_id = 0; link_id < ARRAY_SIZE(data->links); link_id++) { |
53ad07e9 | 45 | cr.links[link_id].status = data->links[link_id].status; |
c434b2be JB |
46 | cr.links[link_id].bss = data->links[link_id].bss; |
47 | ||
53ad07e9 JB |
48 | WARN_ON_ONCE(cr.links[link_id].status != WLAN_STATUS_SUCCESS && |
49 | (!cr.ap_mld_addr || !cr.links[link_id].bss)); | |
50 | ||
5cd212cb JB |
51 | if (!cr.links[link_id].bss) |
52 | continue; | |
53 | cr.links[link_id].bssid = data->links[link_id].bss->bssid; | |
54 | cr.links[link_id].addr = data->links[link_id].addr; | |
55 | /* need to have local link addresses for MLO connections */ | |
234249d8 WG |
56 | WARN_ON(cr.ap_mld_addr && |
57 | !is_valid_ether_addr(cr.links[link_id].addr)); | |
5cd212cb | 58 | |
81151ce4 JB |
59 | BUG_ON(!cr.links[link_id].bss->channel); |
60 | ||
5cd212cb JB |
61 | if (cr.links[link_id].bss->channel->band == NL80211_BAND_S1GHZ) { |
62 | WARN_ON(link_id); | |
63 | cr.resp_ie = (u8 *)&mgmt->u.s1g_assoc_resp.variable; | |
64 | cr.resp_ie_len = data->len - | |
65 | offsetof(struct ieee80211_mgmt, | |
66 | u.s1g_assoc_resp.variable); | |
67 | } | |
5349a0f7 | 68 | |
5cd212cb JB |
69 | if (cr.ap_mld_addr) |
70 | cr.valid_links |= BIT(link_id); | |
71 | } | |
6829c878 | 72 | |
5cd212cb | 73 | trace_cfg80211_send_rx_assoc(dev, data); |
cb0b4beb | 74 | |
f401a6f7 JB |
75 | /* |
76 | * This is a bit of a hack, we don't notify userspace of | |
77 | * a (re-)association reply if we tried to send a reassoc | |
78 | * and got a reject -- we only try again with an assoc | |
79 | * frame instead of reassoc. | |
80 | */ | |
5349a0f7 | 81 | if (cfg80211_sme_rx_assoc_resp(wdev, cr.status)) { |
5cd212cb JB |
82 | for (link_id = 0; link_id < ARRAY_SIZE(data->links); link_id++) { |
83 | struct cfg80211_bss *bss = data->links[link_id].bss; | |
84 | ||
85 | if (!bss) | |
86 | continue; | |
87 | ||
88 | cfg80211_unhold_bss(bss_from_pub(bss)); | |
89 | cfg80211_put_bss(wiphy, bss); | |
90 | } | |
8d61ffa5 | 91 | return; |
95de817b | 92 | } |
f401a6f7 | 93 | |
cd47c0f5 | 94 | nl80211_send_rx_assoc(rdev, dev, data); |
ceca7b71 | 95 | /* update current_bss etc., consumes the bss reference */ |
5349a0f7 | 96 | __cfg80211_connect_result(dev, &cr, cr.status == WLAN_STATUS_SUCCESS); |
6039f6d2 | 97 | } |
6ff57cf8 | 98 | EXPORT_SYMBOL(cfg80211_rx_assoc_resp); |
6039f6d2 | 99 | |
ceca7b71 JB |
100 | static void cfg80211_process_auth(struct wireless_dev *wdev, |
101 | const u8 *buf, size_t len) | |
102 | { | |
f26cbf40 | 103 | struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy); |
ceca7b71 JB |
104 | |
105 | nl80211_send_rx_auth(rdev, wdev->netdev, buf, len, GFP_KERNEL); | |
106 | cfg80211_sme_rx_auth(wdev, buf, len); | |
107 | } | |
108 | ||
109 | static void cfg80211_process_deauth(struct wireless_dev *wdev, | |
3bb02143 JB |
110 | const u8 *buf, size_t len, |
111 | bool reconnect) | |
6039f6d2 | 112 | { |
f26cbf40 | 113 | struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy); |
6829c878 | 114 | struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)buf; |
19957bb3 | 115 | const u8 *bssid = mgmt->bssid; |
ceca7b71 JB |
116 | u16 reason_code = le16_to_cpu(mgmt->u.deauth.reason_code); |
117 | bool from_ap = !ether_addr_equal(mgmt->sa, wdev->netdev->dev_addr); | |
6829c878 | 118 | |
3bb02143 | 119 | nl80211_send_deauth(rdev, wdev->netdev, buf, len, reconnect, GFP_KERNEL); |
19957bb3 | 120 | |
7b0a0e3c | 121 | if (!wdev->connected || !ether_addr_equal(wdev->u.client.connected_addr, bssid)) |
ceca7b71 | 122 | return; |
6829c878 | 123 | |
ceca7b71 JB |
124 | __cfg80211_disconnected(wdev->netdev, NULL, 0, reason_code, from_ap); |
125 | cfg80211_sme_deauth(wdev); | |
667503dd | 126 | } |
6039f6d2 | 127 | |
ceca7b71 | 128 | static void cfg80211_process_disassoc(struct wireless_dev *wdev, |
3bb02143 JB |
129 | const u8 *buf, size_t len, |
130 | bool reconnect) | |
6039f6d2 | 131 | { |
f26cbf40 | 132 | struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy); |
6829c878 | 133 | struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)buf; |
19957bb3 | 134 | const u8 *bssid = mgmt->bssid; |
ceca7b71 JB |
135 | u16 reason_code = le16_to_cpu(mgmt->u.disassoc.reason_code); |
136 | bool from_ap = !ether_addr_equal(mgmt->sa, wdev->netdev->dev_addr); | |
6829c878 | 137 | |
3bb02143 JB |
138 | nl80211_send_disassoc(rdev, wdev->netdev, buf, len, reconnect, |
139 | GFP_KERNEL); | |
a3b8b056 | 140 | |
7b0a0e3c JB |
141 | if (WARN_ON(!wdev->connected || |
142 | !ether_addr_equal(wdev->u.client.connected_addr, bssid))) | |
596a07c1 | 143 | return; |
6829c878 | 144 | |
ceca7b71 JB |
145 | __cfg80211_disconnected(wdev->netdev, NULL, 0, reason_code, from_ap); |
146 | cfg80211_sme_disassoc(wdev); | |
667503dd | 147 | } |
1965c853 | 148 | |
6ff57cf8 JB |
149 | void cfg80211_rx_mlme_mgmt(struct net_device *dev, const u8 *buf, size_t len) |
150 | { | |
151 | struct wireless_dev *wdev = dev->ieee80211_ptr; | |
6ff57cf8 JB |
152 | struct ieee80211_mgmt *mgmt = (void *)buf; |
153 | ||
076fc877 | 154 | lockdep_assert_wiphy(wdev->wiphy); |
6ff57cf8 JB |
155 | |
156 | trace_cfg80211_rx_mlme_mgmt(dev, buf, len); | |
157 | ||
158 | if (WARN_ON(len < 2)) | |
159 | return; | |
160 | ||
ceca7b71 JB |
161 | if (ieee80211_is_auth(mgmt->frame_control)) |
162 | cfg80211_process_auth(wdev, buf, len); | |
163 | else if (ieee80211_is_deauth(mgmt->frame_control)) | |
3bb02143 | 164 | cfg80211_process_deauth(wdev, buf, len, false); |
ceca7b71 | 165 | else if (ieee80211_is_disassoc(mgmt->frame_control)) |
3bb02143 | 166 | cfg80211_process_disassoc(wdev, buf, len, false); |
6ff57cf8 JB |
167 | } |
168 | EXPORT_SYMBOL(cfg80211_rx_mlme_mgmt); | |
169 | ||
170 | void cfg80211_auth_timeout(struct net_device *dev, const u8 *addr) | |
a58ce43f JB |
171 | { |
172 | struct wireless_dev *wdev = dev->ieee80211_ptr; | |
173 | struct wiphy *wiphy = wdev->wiphy; | |
f26cbf40 | 174 | struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); |
a58ce43f | 175 | |
4ee3e063 | 176 | trace_cfg80211_send_auth_timeout(dev, addr); |
a58ce43f JB |
177 | |
178 | nl80211_send_auth_timeout(rdev, dev, addr, GFP_KERNEL); | |
ceca7b71 | 179 | cfg80211_sme_auth_timeout(wdev); |
1965c853 | 180 | } |
6ff57cf8 | 181 | EXPORT_SYMBOL(cfg80211_auth_timeout); |
1965c853 | 182 | |
f662d2f4 JB |
183 | void cfg80211_assoc_failure(struct net_device *dev, |
184 | struct cfg80211_assoc_failure *data) | |
1965c853 | 185 | { |
6829c878 JB |
186 | struct wireless_dev *wdev = dev->ieee80211_ptr; |
187 | struct wiphy *wiphy = wdev->wiphy; | |
f26cbf40 | 188 | struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); |
f662d2f4 JB |
189 | const u8 *addr = data->ap_mld_addr ?: data->bss[0]->bssid; |
190 | int i; | |
19957bb3 | 191 | |
f662d2f4 | 192 | trace_cfg80211_send_assoc_failure(dev, data); |
cb0b4beb | 193 | |
f662d2f4 JB |
194 | if (data->timeout) { |
195 | nl80211_send_assoc_timeout(rdev, dev, addr, GFP_KERNEL); | |
196 | cfg80211_sme_assoc_timeout(wdev); | |
197 | } else { | |
198 | cfg80211_sme_abandon_assoc(wdev); | |
199 | } | |
959867fa | 200 | |
f662d2f4 JB |
201 | for (i = 0; i < ARRAY_SIZE(data->bss); i++) { |
202 | struct cfg80211_bss *bss = data->bss[i]; | |
6ff57cf8 | 203 | |
f662d2f4 JB |
204 | if (!bss) |
205 | continue; | |
e6f462df | 206 | |
f662d2f4 JB |
207 | cfg80211_unhold_bss(bss_from_pub(bss)); |
208 | cfg80211_put_bss(wiphy, bss); | |
209 | } | |
e6f462df | 210 | } |
f662d2f4 | 211 | EXPORT_SYMBOL(cfg80211_assoc_failure); |
e6f462df | 212 | |
3bb02143 JB |
213 | void cfg80211_tx_mlme_mgmt(struct net_device *dev, const u8 *buf, size_t len, |
214 | bool reconnect) | |
6ff57cf8 JB |
215 | { |
216 | struct wireless_dev *wdev = dev->ieee80211_ptr; | |
217 | struct ieee80211_mgmt *mgmt = (void *)buf; | |
218 | ||
076fc877 | 219 | lockdep_assert_wiphy(wdev->wiphy); |
6ff57cf8 | 220 | |
3bb02143 | 221 | trace_cfg80211_tx_mlme_mgmt(dev, buf, len, reconnect); |
6ff57cf8 JB |
222 | |
223 | if (WARN_ON(len < 2)) | |
224 | return; | |
225 | ||
226 | if (ieee80211_is_deauth(mgmt->frame_control)) | |
3bb02143 | 227 | cfg80211_process_deauth(wdev, buf, len, reconnect); |
6ff57cf8 | 228 | else |
3bb02143 | 229 | cfg80211_process_disassoc(wdev, buf, len, reconnect); |
6ff57cf8 JB |
230 | } |
231 | EXPORT_SYMBOL(cfg80211_tx_mlme_mgmt); | |
1965c853 | 232 | |
a3b8b056 JM |
233 | void cfg80211_michael_mic_failure(struct net_device *dev, const u8 *addr, |
234 | enum nl80211_key_type key_type, int key_id, | |
e6d6e342 | 235 | const u8 *tsc, gfp_t gfp) |
a3b8b056 JM |
236 | { |
237 | struct wiphy *wiphy = dev->ieee80211_ptr->wiphy; | |
f26cbf40 | 238 | struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); |
3d23e349 | 239 | #ifdef CONFIG_CFG80211_WEXT |
f58d4ed9 | 240 | union iwreq_data wrqu; |
e6d6e342 | 241 | char *buf = kmalloc(128, gfp); |
f58d4ed9 JB |
242 | |
243 | if (buf) { | |
f58d4ed9 | 244 | memset(&wrqu, 0, sizeof(wrqu)); |
7f78840c DA |
245 | wrqu.data.length = |
246 | sprintf(buf, "MLME-MICHAELMICFAILURE." | |
247 | "indication(keyid=%d %scast addr=%pM)", | |
248 | key_id, key_type == NL80211_KEYTYPE_GROUP | |
249 | ? "broad" : "uni", addr); | |
f58d4ed9 JB |
250 | wireless_send_event(dev, IWEVCUSTOM, &wrqu, buf); |
251 | kfree(buf); | |
252 | } | |
253 | #endif | |
254 | ||
4ee3e063 | 255 | trace_cfg80211_michael_mic_failure(dev, addr, key_type, key_id, tsc); |
e6d6e342 | 256 | nl80211_michael_mic_failure(rdev, dev, addr, key_type, key_id, tsc, gfp); |
a3b8b056 JM |
257 | } |
258 | EXPORT_SYMBOL(cfg80211_michael_mic_failure); | |
19957bb3 JB |
259 | |
260 | /* some MLME handling for userspace SME */ | |
91bf9b26 JB |
261 | int cfg80211_mlme_auth(struct cfg80211_registered_device *rdev, |
262 | struct net_device *dev, | |
325839da | 263 | struct cfg80211_auth_request *req) |
19957bb3 JB |
264 | { |
265 | struct wireless_dev *wdev = dev->ieee80211_ptr; | |
19957bb3 | 266 | |
076fc877 | 267 | lockdep_assert_wiphy(wdev->wiphy); |
667503dd | 268 | |
325839da JB |
269 | if (!req->bss) |
270 | return -ENOENT; | |
271 | ||
d648c230 JB |
272 | if (req->link_id >= 0 && |
273 | !(wdev->wiphy->flags & WIPHY_FLAG_SUPPORTS_MLO)) | |
274 | return -EINVAL; | |
275 | ||
325839da JB |
276 | if (req->auth_type == NL80211_AUTHTYPE_SHARED_KEY) { |
277 | if (!req->key || !req->key_len || | |
278 | req->key_idx < 0 || req->key_idx > 3) | |
fffd0934 | 279 | return -EINVAL; |
325839da | 280 | } |
fffd0934 | 281 | |
7b0a0e3c | 282 | if (wdev->connected && |
325839da | 283 | ether_addr_equal(req->bss->bssid, wdev->u.client.connected_addr)) |
0a9b5e17 JB |
284 | return -EALREADY; |
285 | ||
5d4e04bf JB |
286 | if (ether_addr_equal(req->bss->bssid, dev->dev_addr) || |
287 | (req->link_id >= 0 && | |
288 | ether_addr_equal(req->ap_mld_addr, dev->dev_addr))) | |
289 | return -EINVAL; | |
290 | ||
325839da | 291 | return rdev_auth(rdev, dev, req); |
19957bb3 JB |
292 | } |
293 | ||
7e7c8926 BG |
294 | /* Do a logical ht_capa &= ht_capa_mask. */ |
295 | void cfg80211_oper_and_ht_capa(struct ieee80211_ht_cap *ht_capa, | |
296 | const struct ieee80211_ht_cap *ht_capa_mask) | |
297 | { | |
298 | int i; | |
299 | u8 *p1, *p2; | |
300 | if (!ht_capa_mask) { | |
301 | memset(ht_capa, 0, sizeof(*ht_capa)); | |
302 | return; | |
303 | } | |
304 | ||
305 | p1 = (u8*)(ht_capa); | |
306 | p2 = (u8*)(ht_capa_mask); | |
81c5dce2 | 307 | for (i = 0; i < sizeof(*ht_capa); i++) |
7e7c8926 BG |
308 | p1[i] &= p2[i]; |
309 | } | |
310 | ||
81c5dce2 | 311 | /* Do a logical vht_capa &= vht_capa_mask. */ |
ee2aca34 JB |
312 | void cfg80211_oper_and_vht_capa(struct ieee80211_vht_cap *vht_capa, |
313 | const struct ieee80211_vht_cap *vht_capa_mask) | |
314 | { | |
315 | int i; | |
316 | u8 *p1, *p2; | |
317 | if (!vht_capa_mask) { | |
318 | memset(vht_capa, 0, sizeof(*vht_capa)); | |
319 | return; | |
320 | } | |
321 | ||
322 | p1 = (u8*)(vht_capa); | |
323 | p2 = (u8*)(vht_capa_mask); | |
324 | for (i = 0; i < sizeof(*vht_capa); i++) | |
325 | p1[i] &= p2[i]; | |
326 | } | |
327 | ||
ccb964b4 JB |
328 | static int |
329 | cfg80211_mlme_check_mlo_compat(const struct ieee80211_multi_link_elem *mle_a, | |
330 | const struct ieee80211_multi_link_elem *mle_b, | |
331 | struct netlink_ext_ack *extack) | |
19957bb3 | 332 | { |
ccb964b4 | 333 | const struct ieee80211_mle_basic_common_info *common_a, *common_b; |
19957bb3 | 334 | |
ccb964b4 JB |
335 | common_a = (const void *)mle_a->variable; |
336 | common_b = (const void *)mle_b->variable; | |
337 | ||
338 | if (memcmp(common_a->mld_mac_addr, common_b->mld_mac_addr, ETH_ALEN)) { | |
339 | NL_SET_ERR_MSG(extack, "AP MLD address mismatch"); | |
340 | return -EINVAL; | |
341 | } | |
342 | ||
ccb964b4 JB |
343 | if (ieee80211_mle_get_eml_cap((const u8 *)mle_a) != |
344 | ieee80211_mle_get_eml_cap((const u8 *)mle_b)) { | |
345 | NL_SET_ERR_MSG(extack, "link EML capabilities mismatch"); | |
346 | return -EINVAL; | |
347 | } | |
348 | ||
349 | if (ieee80211_mle_get_mld_capa_op((const u8 *)mle_a) != | |
350 | ieee80211_mle_get_mld_capa_op((const u8 *)mle_b)) { | |
351 | NL_SET_ERR_MSG(extack, "link MLD capabilities/ops mismatch"); | |
352 | return -EINVAL; | |
353 | } | |
354 | ||
2bf50225 JB |
355 | if (ieee80211_mle_get_ext_mld_capa_op((const u8 *)mle_a) != |
356 | ieee80211_mle_get_ext_mld_capa_op((const u8 *)mle_b)) { | |
357 | NL_SET_ERR_MSG(extack, | |
358 | "extended link MLD capabilities/ops mismatch"); | |
359 | return -EINVAL; | |
360 | } | |
361 | ||
ccb964b4 JB |
362 | return 0; |
363 | } | |
364 | ||
365 | static int cfg80211_mlme_check_mlo(struct net_device *dev, | |
366 | struct cfg80211_assoc_request *req, | |
367 | struct netlink_ext_ack *extack) | |
368 | { | |
369 | const struct ieee80211_multi_link_elem *mles[ARRAY_SIZE(req->links)] = {}; | |
370 | int i; | |
371 | ||
372 | if (req->link_id < 0) | |
373 | return 0; | |
374 | ||
375 | if (!req->links[req->link_id].bss) { | |
376 | NL_SET_ERR_MSG(extack, "no BSS for assoc link"); | |
377 | return -EINVAL; | |
378 | } | |
379 | ||
380 | rcu_read_lock(); | |
381 | for (i = 0; i < ARRAY_SIZE(req->links); i++) { | |
382 | const struct cfg80211_bss_ies *ies; | |
383 | const struct element *ml; | |
667503dd | 384 | |
d648c230 JB |
385 | if (!req->links[i].bss) |
386 | continue; | |
ccb964b4 JB |
387 | |
388 | if (ether_addr_equal(req->links[i].bss->bssid, dev->dev_addr)) { | |
389 | NL_SET_ERR_MSG(extack, "BSSID must not be our address"); | |
390 | req->links[i].error = -EINVAL; | |
391 | goto error; | |
d648c230 | 392 | } |
5d4e04bf | 393 | |
ccb964b4 JB |
394 | ies = rcu_dereference(req->links[i].bss->ies); |
395 | ml = cfg80211_find_ext_elem(WLAN_EID_EXT_EHT_MULTI_LINK, | |
396 | ies->data, ies->len); | |
397 | if (!ml) { | |
398 | NL_SET_ERR_MSG(extack, "MLO BSS w/o ML element"); | |
399 | req->links[i].error = -EINVAL; | |
400 | goto error; | |
401 | } | |
402 | ||
403 | if (!ieee80211_mle_type_ok(ml->data + 1, | |
404 | IEEE80211_ML_CONTROL_TYPE_BASIC, | |
405 | ml->datalen - 1)) { | |
406 | NL_SET_ERR_MSG(extack, "BSS with invalid ML element"); | |
407 | req->links[i].error = -EINVAL; | |
408 | goto error; | |
409 | } | |
410 | ||
411 | mles[i] = (const void *)(ml->data + 1); | |
412 | ||
413 | if (ieee80211_mle_get_link_id((const u8 *)mles[i]) != i) { | |
414 | NL_SET_ERR_MSG(extack, "link ID mismatch"); | |
415 | req->links[i].error = -EINVAL; | |
416 | goto error; | |
417 | } | |
418 | } | |
419 | ||
420 | if (WARN_ON(!mles[req->link_id])) | |
421 | goto error; | |
422 | ||
423 | for (i = 0; i < ARRAY_SIZE(req->links); i++) { | |
424 | if (i == req->link_id || !req->links[i].bss) | |
425 | continue; | |
426 | ||
427 | if (WARN_ON(!mles[i])) | |
428 | goto error; | |
429 | ||
430 | if (cfg80211_mlme_check_mlo_compat(mles[req->link_id], mles[i], | |
431 | extack)) { | |
432 | req->links[i].error = -EINVAL; | |
433 | goto error; | |
434 | } | |
d648c230 JB |
435 | } |
436 | ||
ccb964b4 JB |
437 | rcu_read_unlock(); |
438 | return 0; | |
439 | error: | |
440 | rcu_read_unlock(); | |
441 | return -EINVAL; | |
442 | } | |
443 | ||
444 | /* Note: caller must cfg80211_put_bss() regardless of result */ | |
445 | int cfg80211_mlme_assoc(struct cfg80211_registered_device *rdev, | |
446 | struct net_device *dev, | |
447 | struct cfg80211_assoc_request *req, | |
448 | struct netlink_ext_ack *extack) | |
449 | { | |
450 | struct wireless_dev *wdev = dev->ieee80211_ptr; | |
451 | int err; | |
452 | ||
453 | lockdep_assert_wiphy(wdev->wiphy); | |
454 | ||
455 | err = cfg80211_mlme_check_mlo(dev, req, extack); | |
456 | if (err) | |
457 | return err; | |
458 | ||
7b0a0e3c JB |
459 | if (wdev->connected && |
460 | (!req->prev_bssid || | |
461 | !ether_addr_equal(wdev->u.client.connected_addr, req->prev_bssid))) | |
19957bb3 JB |
462 | return -EALREADY; |
463 | ||
5d4e04bf JB |
464 | if ((req->bss && ether_addr_equal(req->bss->bssid, dev->dev_addr)) || |
465 | (req->link_id >= 0 && | |
466 | ether_addr_equal(req->ap_mld_addr, dev->dev_addr))) | |
467 | return -EINVAL; | |
468 | ||
f62fab73 | 469 | cfg80211_oper_and_ht_capa(&req->ht_capa_mask, |
7e7c8926 | 470 | rdev->wiphy.ht_capa_mod_mask); |
f62fab73 | 471 | cfg80211_oper_and_vht_capa(&req->vht_capa_mask, |
ee2aca34 | 472 | rdev->wiphy.vht_capa_mod_mask); |
7e7c8926 | 473 | |
f62fab73 | 474 | err = rdev_assoc(rdev, dev, req); |
0f759448 | 475 | if (!err) { |
d648c230 JB |
476 | int link_id; |
477 | ||
478 | if (req->bss) { | |
479 | cfg80211_ref_bss(&rdev->wiphy, req->bss); | |
480 | cfg80211_hold_bss(bss_from_pub(req->bss)); | |
481 | } | |
482 | ||
483 | for (link_id = 0; link_id < ARRAY_SIZE(req->links); link_id++) { | |
484 | if (!req->links[link_id].bss) | |
485 | continue; | |
486 | cfg80211_ref_bss(&rdev->wiphy, req->links[link_id].bss); | |
487 | cfg80211_hold_bss(bss_from_pub(req->links[link_id].bss)); | |
488 | } | |
0f759448 | 489 | } |
19957bb3 JB |
490 | return err; |
491 | } | |
492 | ||
91bf9b26 JB |
493 | int cfg80211_mlme_deauth(struct cfg80211_registered_device *rdev, |
494 | struct net_device *dev, const u8 *bssid, | |
495 | const u8 *ie, int ie_len, u16 reason, | |
496 | bool local_state_change) | |
19957bb3 JB |
497 | { |
498 | struct wireless_dev *wdev = dev->ieee80211_ptr; | |
95de817b JB |
499 | struct cfg80211_deauth_request req = { |
500 | .bssid = bssid, | |
501 | .reason_code = reason, | |
502 | .ie = ie, | |
503 | .ie_len = ie_len, | |
6863255b | 504 | .local_state_change = local_state_change, |
95de817b | 505 | }; |
19957bb3 | 506 | |
076fc877 | 507 | lockdep_assert_wiphy(wdev->wiphy); |
667503dd | 508 | |
ceca7b71 | 509 | if (local_state_change && |
7b0a0e3c JB |
510 | (!wdev->connected || |
511 | !ether_addr_equal(wdev->u.client.connected_addr, bssid))) | |
95de817b | 512 | return 0; |
19957bb3 | 513 | |
bd2522b1 | 514 | if (ether_addr_equal(wdev->disconnect_bssid, bssid) || |
7b0a0e3c JB |
515 | (wdev->connected && |
516 | ether_addr_equal(wdev->u.client.connected_addr, bssid))) | |
bd2522b1 AZ |
517 | wdev->conn_owner_nlportid = 0; |
518 | ||
e35e4d28 | 519 | return rdev_deauth(rdev, dev, &req); |
19957bb3 JB |
520 | } |
521 | ||
91bf9b26 | 522 | int cfg80211_mlme_disassoc(struct cfg80211_registered_device *rdev, |
8f6e0dfc | 523 | struct net_device *dev, const u8 *ap_addr, |
91bf9b26 JB |
524 | const u8 *ie, int ie_len, u16 reason, |
525 | bool local_state_change) | |
19957bb3 JB |
526 | { |
527 | struct wireless_dev *wdev = dev->ieee80211_ptr; | |
7ade7036 JB |
528 | struct cfg80211_disassoc_request req = { |
529 | .reason_code = reason, | |
530 | .local_state_change = local_state_change, | |
531 | .ie = ie, | |
532 | .ie_len = ie_len, | |
8f6e0dfc | 533 | .ap_addr = ap_addr, |
7ade7036 | 534 | }; |
ceca7b71 | 535 | int err; |
19957bb3 | 536 | |
076fc877 | 537 | lockdep_assert_wiphy(wdev->wiphy); |
667503dd | 538 | |
7b0a0e3c | 539 | if (!wdev->connected) |
f9d6b402 JB |
540 | return -ENOTCONN; |
541 | ||
8f6e0dfc | 542 | if (memcmp(wdev->u.client.connected_addr, ap_addr, ETH_ALEN)) |
19957bb3 JB |
543 | return -ENOTCONN; |
544 | ||
ceca7b71 JB |
545 | err = rdev_disassoc(rdev, dev, &req); |
546 | if (err) | |
547 | return err; | |
548 | ||
549 | /* driver should have reported the disassoc */ | |
7b0a0e3c | 550 | WARN_ON(wdev->connected); |
ceca7b71 | 551 | return 0; |
667503dd JB |
552 | } |
553 | ||
19957bb3 JB |
554 | void cfg80211_mlme_down(struct cfg80211_registered_device *rdev, |
555 | struct net_device *dev) | |
556 | { | |
557 | struct wireless_dev *wdev = dev->ieee80211_ptr; | |
95de817b | 558 | u8 bssid[ETH_ALEN]; |
19957bb3 | 559 | |
076fc877 | 560 | lockdep_assert_wiphy(wdev->wiphy); |
667503dd | 561 | |
19957bb3 JB |
562 | if (!rdev->ops->deauth) |
563 | return; | |
564 | ||
7b0a0e3c | 565 | if (!wdev->connected) |
95de817b | 566 | return; |
19957bb3 | 567 | |
7b0a0e3c | 568 | memcpy(bssid, wdev->u.client.connected_addr, ETH_ALEN); |
ceca7b71 JB |
569 | cfg80211_mlme_deauth(rdev, dev, bssid, NULL, 0, |
570 | WLAN_REASON_DEAUTH_LEAVING, false); | |
19957bb3 | 571 | } |
9588bbd5 | 572 | |
2e161f78 | 573 | struct cfg80211_mgmt_registration { |
026331c4 | 574 | struct list_head list; |
33d8783c | 575 | struct wireless_dev *wdev; |
026331c4 | 576 | |
15e47304 | 577 | u32 nlportid; |
026331c4 JM |
578 | |
579 | int match_len; | |
580 | ||
2e161f78 JB |
581 | __le16 frame_type; |
582 | ||
9dba48a6 JB |
583 | bool multicast_rx; |
584 | ||
026331c4 JM |
585 | u8 match[]; |
586 | }; | |
587 | ||
6cd536fe | 588 | static void cfg80211_mgmt_registrations_update(struct wireless_dev *wdev) |
33d8783c | 589 | { |
6cd536fe JB |
590 | struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy); |
591 | struct wireless_dev *tmp; | |
33d8783c | 592 | struct cfg80211_mgmt_registration *reg; |
6cd536fe | 593 | struct mgmt_frame_regs upd = {}; |
33d8783c | 594 | |
a05829a7 | 595 | lockdep_assert_held(&rdev->wiphy.mtx); |
33d8783c | 596 | |
09b1d5dc | 597 | spin_lock_bh(&rdev->mgmt_registrations_lock); |
79ea1e12 | 598 | if (!wdev->mgmt_registrations_need_update) { |
09b1d5dc | 599 | spin_unlock_bh(&rdev->mgmt_registrations_lock); |
79ea1e12 JB |
600 | return; |
601 | } | |
602 | ||
6cd536fe JB |
603 | rcu_read_lock(); |
604 | list_for_each_entry_rcu(tmp, &rdev->wiphy.wdev_list, list) { | |
79ea1e12 | 605 | list_for_each_entry(reg, &tmp->mgmt_registrations, list) { |
6cd536fe | 606 | u32 mask = BIT(le16_to_cpu(reg->frame_type) >> 4); |
9dba48a6 JB |
607 | u32 mcast_mask = 0; |
608 | ||
609 | if (reg->multicast_rx) | |
610 | mcast_mask = mask; | |
33d8783c | 611 | |
6cd536fe | 612 | upd.global_stypes |= mask; |
9dba48a6 JB |
613 | upd.global_mcast_stypes |= mcast_mask; |
614 | ||
615 | if (tmp == wdev) { | |
6cd536fe | 616 | upd.interface_stypes |= mask; |
9dba48a6 JB |
617 | upd.interface_mcast_stypes |= mcast_mask; |
618 | } | |
33d8783c | 619 | } |
33d8783c | 620 | } |
6cd536fe JB |
621 | rcu_read_unlock(); |
622 | ||
79ea1e12 | 623 | wdev->mgmt_registrations_need_update = 0; |
09b1d5dc | 624 | spin_unlock_bh(&rdev->mgmt_registrations_lock); |
79ea1e12 | 625 | |
6cd536fe | 626 | rdev_update_mgmt_frame_registrations(rdev, wdev, &upd); |
33d8783c JB |
627 | } |
628 | ||
6cd536fe | 629 | void cfg80211_mgmt_registrations_update_wk(struct work_struct *wk) |
33d8783c | 630 | { |
79ea1e12 JB |
631 | struct cfg80211_registered_device *rdev; |
632 | struct wireless_dev *wdev; | |
633 | ||
634 | rdev = container_of(wk, struct cfg80211_registered_device, | |
635 | mgmt_registrations_update_wk); | |
33d8783c | 636 | |
f42d22d3 JB |
637 | guard(wiphy)(&rdev->wiphy); |
638 | ||
79ea1e12 JB |
639 | list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) |
640 | cfg80211_mgmt_registrations_update(wdev); | |
33d8783c JB |
641 | } |
642 | ||
15e47304 | 643 | int cfg80211_mlme_register_mgmt(struct wireless_dev *wdev, u32 snd_portid, |
2e161f78 | 644 | u16 frame_type, const u8 *match_data, |
9dba48a6 JB |
645 | int match_len, bool multicast_rx, |
646 | struct netlink_ext_ack *extack) | |
026331c4 | 647 | { |
09b1d5dc | 648 | struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy); |
2e161f78 | 649 | struct cfg80211_mgmt_registration *reg, *nreg; |
026331c4 | 650 | int err = 0; |
2e161f78 | 651 | u16 mgmt_type; |
9dba48a6 | 652 | bool update_multicast = false; |
2e161f78 JB |
653 | |
654 | if (!wdev->wiphy->mgmt_stypes) | |
655 | return -EOPNOTSUPP; | |
656 | ||
ff74c51e IP |
657 | if ((frame_type & IEEE80211_FCTL_FTYPE) != IEEE80211_FTYPE_MGMT) { |
658 | NL_SET_ERR_MSG(extack, "frame type not management"); | |
2e161f78 | 659 | return -EINVAL; |
ff74c51e | 660 | } |
2e161f78 | 661 | |
ff74c51e IP |
662 | if (frame_type & ~(IEEE80211_FCTL_FTYPE | IEEE80211_FCTL_STYPE)) { |
663 | NL_SET_ERR_MSG(extack, "Invalid frame type"); | |
2e161f78 | 664 | return -EINVAL; |
ff74c51e | 665 | } |
2e161f78 JB |
666 | |
667 | mgmt_type = (frame_type & IEEE80211_FCTL_STYPE) >> 4; | |
ff74c51e IP |
668 | if (!(wdev->wiphy->mgmt_stypes[wdev->iftype].rx & BIT(mgmt_type))) { |
669 | NL_SET_ERR_MSG(extack, | |
670 | "Registration to specific type not supported"); | |
671 | return -EINVAL; | |
672 | } | |
673 | ||
674 | /* | |
675 | * To support Pre Association Security Negotiation (PASN), registration | |
676 | * for authentication frames should be supported. However, as some | |
677 | * versions of the user space daemons wrongly register to all types of | |
678 | * authentication frames (which might result in unexpected behavior) | |
679 | * allow such registration if the request is for a specific | |
680 | * authentication algorithm number. | |
681 | */ | |
682 | if (wdev->iftype == NL80211_IFTYPE_STATION && | |
683 | (frame_type & IEEE80211_FCTL_STYPE) == IEEE80211_STYPE_AUTH && | |
684 | !(match_data && match_len >= 2)) { | |
685 | NL_SET_ERR_MSG(extack, | |
686 | "Authentication algorithm number required"); | |
2e161f78 | 687 | return -EINVAL; |
ff74c51e | 688 | } |
026331c4 JM |
689 | |
690 | nreg = kzalloc(sizeof(*reg) + match_len, GFP_KERNEL); | |
691 | if (!nreg) | |
692 | return -ENOMEM; | |
693 | ||
09b1d5dc | 694 | spin_lock_bh(&rdev->mgmt_registrations_lock); |
026331c4 | 695 | |
2e161f78 | 696 | list_for_each_entry(reg, &wdev->mgmt_registrations, list) { |
026331c4 JM |
697 | int mlen = min(match_len, reg->match_len); |
698 | ||
2e161f78 JB |
699 | if (frame_type != le16_to_cpu(reg->frame_type)) |
700 | continue; | |
701 | ||
026331c4 | 702 | if (memcmp(reg->match, match_data, mlen) == 0) { |
9dba48a6 JB |
703 | if (reg->multicast_rx != multicast_rx) { |
704 | update_multicast = true; | |
705 | reg->multicast_rx = multicast_rx; | |
706 | break; | |
707 | } | |
ff74c51e | 708 | NL_SET_ERR_MSG(extack, "Match already configured"); |
026331c4 JM |
709 | err = -EALREADY; |
710 | break; | |
711 | } | |
712 | } | |
713 | ||
6cd536fe | 714 | if (err) |
026331c4 | 715 | goto out; |
026331c4 | 716 | |
9dba48a6 JB |
717 | if (update_multicast) { |
718 | kfree(nreg); | |
719 | } else { | |
720 | memcpy(nreg->match, match_data, match_len); | |
721 | nreg->match_len = match_len; | |
722 | nreg->nlportid = snd_portid; | |
723 | nreg->frame_type = cpu_to_le16(frame_type); | |
724 | nreg->wdev = wdev; | |
725 | nreg->multicast_rx = multicast_rx; | |
726 | list_add(&nreg->list, &wdev->mgmt_registrations); | |
727 | } | |
79ea1e12 | 728 | wdev->mgmt_registrations_need_update = 1; |
09b1d5dc | 729 | spin_unlock_bh(&rdev->mgmt_registrations_lock); |
33d8783c | 730 | |
6cd536fe | 731 | cfg80211_mgmt_registrations_update(wdev); |
271733cf | 732 | |
33d8783c JB |
733 | return 0; |
734 | ||
026331c4 | 735 | out: |
6cd536fe | 736 | kfree(nreg); |
09b1d5dc | 737 | spin_unlock_bh(&rdev->mgmt_registrations_lock); |
271733cf | 738 | |
026331c4 JM |
739 | return err; |
740 | } | |
741 | ||
15e47304 | 742 | void cfg80211_mlme_unregister_socket(struct wireless_dev *wdev, u32 nlportid) |
026331c4 | 743 | { |
271733cf | 744 | struct wiphy *wiphy = wdev->wiphy; |
f26cbf40 | 745 | struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); |
2e161f78 | 746 | struct cfg80211_mgmt_registration *reg, *tmp; |
026331c4 | 747 | |
09b1d5dc | 748 | spin_lock_bh(&rdev->mgmt_registrations_lock); |
026331c4 | 749 | |
2e161f78 | 750 | list_for_each_entry_safe(reg, tmp, &wdev->mgmt_registrations, list) { |
15e47304 | 751 | if (reg->nlportid != nlportid) |
271733cf JB |
752 | continue; |
753 | ||
271733cf | 754 | list_del(®->list); |
6cd536fe | 755 | kfree(reg); |
33d8783c | 756 | |
79ea1e12 JB |
757 | wdev->mgmt_registrations_need_update = 1; |
758 | schedule_work(&rdev->mgmt_registrations_update_wk); | |
026331c4 JM |
759 | } |
760 | ||
09b1d5dc | 761 | spin_unlock_bh(&rdev->mgmt_registrations_lock); |
28946da7 | 762 | |
5de17984 AS |
763 | if (nlportid && rdev->crit_proto_nlportid == nlportid) { |
764 | rdev->crit_proto_nlportid = 0; | |
765 | rdev_crit_proto_stop(rdev, wdev); | |
766 | } | |
767 | ||
15e47304 EB |
768 | if (nlportid == wdev->ap_unexpected_nlportid) |
769 | wdev->ap_unexpected_nlportid = 0; | |
026331c4 JM |
770 | } |
771 | ||
2e161f78 | 772 | void cfg80211_mlme_purge_registrations(struct wireless_dev *wdev) |
026331c4 | 773 | { |
09b1d5dc | 774 | struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy); |
6cd536fe | 775 | struct cfg80211_mgmt_registration *reg, *tmp; |
026331c4 | 776 | |
09b1d5dc | 777 | spin_lock_bh(&rdev->mgmt_registrations_lock); |
6cd536fe JB |
778 | list_for_each_entry_safe(reg, tmp, &wdev->mgmt_registrations, list) { |
779 | list_del(®->list); | |
780 | kfree(reg); | |
781 | } | |
79ea1e12 | 782 | wdev->mgmt_registrations_need_update = 1; |
09b1d5dc | 783 | spin_unlock_bh(&rdev->mgmt_registrations_lock); |
33d8783c | 784 | |
6cd536fe | 785 | cfg80211_mgmt_registrations_update(wdev); |
026331c4 JM |
786 | } |
787 | ||
6df2810a AO |
788 | static bool cfg80211_allowed_address(struct wireless_dev *wdev, const u8 *addr) |
789 | { | |
790 | int i; | |
791 | ||
792 | for_each_valid_link(wdev, i) { | |
793 | if (ether_addr_equal(addr, wdev->links[i].addr)) | |
794 | return true; | |
795 | } | |
796 | ||
797 | return ether_addr_equal(addr, wdev_address(wdev)); | |
798 | } | |
799 | ||
69334861 VJ |
800 | static bool cfg80211_allowed_random_address(struct wireless_dev *wdev, |
801 | const struct ieee80211_mgmt *mgmt) | |
802 | { | |
803 | if (ieee80211_is_auth(mgmt->frame_control) || | |
804 | ieee80211_is_deauth(mgmt->frame_control)) { | |
805 | /* Allow random TA to be used with authentication and | |
806 | * deauthentication frames if the driver has indicated support. | |
807 | */ | |
808 | if (wiphy_ext_feature_isset( | |
809 | wdev->wiphy, | |
810 | NL80211_EXT_FEATURE_AUTH_AND_DEAUTH_RANDOM_TA)) | |
811 | return true; | |
812 | } else if (ieee80211_is_action(mgmt->frame_control) && | |
813 | mgmt->u.action.category == WLAN_CATEGORY_PUBLIC) { | |
814 | /* Allow random TA to be used with Public Action frames if the | |
815 | * driver has indicated support. | |
816 | */ | |
817 | if (!wdev->connected && | |
818 | wiphy_ext_feature_isset( | |
819 | wdev->wiphy, | |
820 | NL80211_EXT_FEATURE_MGMT_TX_RANDOM_TA)) | |
821 | return true; | |
822 | ||
823 | if (wdev->connected && | |
824 | wiphy_ext_feature_isset( | |
825 | wdev->wiphy, | |
826 | NL80211_EXT_FEATURE_MGMT_TX_RANDOM_TA_CONNECTED)) | |
827 | return true; | |
828 | } | |
829 | ||
830 | return false; | |
831 | } | |
832 | ||
2e161f78 | 833 | int cfg80211_mlme_mgmt_tx(struct cfg80211_registered_device *rdev, |
71bbc994 | 834 | struct wireless_dev *wdev, |
b176e629 | 835 | struct cfg80211_mgmt_tx_params *params, u64 *cookie) |
026331c4 | 836 | { |
026331c4 | 837 | const struct ieee80211_mgmt *mgmt; |
2e161f78 JB |
838 | u16 stype; |
839 | ||
076fc877 JB |
840 | lockdep_assert_wiphy(&rdev->wiphy); |
841 | ||
2e161f78 JB |
842 | if (!wdev->wiphy->mgmt_stypes) |
843 | return -EOPNOTSUPP; | |
026331c4 | 844 | |
2e161f78 | 845 | if (!rdev->ops->mgmt_tx) |
026331c4 | 846 | return -EOPNOTSUPP; |
2e161f78 | 847 | |
b176e629 | 848 | if (params->len < 24 + 1) |
026331c4 JM |
849 | return -EINVAL; |
850 | ||
b176e629 | 851 | mgmt = (const struct ieee80211_mgmt *)params->buf; |
2e161f78 JB |
852 | |
853 | if (!ieee80211_is_mgmt(mgmt->frame_control)) | |
026331c4 | 854 | return -EINVAL; |
2e161f78 JB |
855 | |
856 | stype = le16_to_cpu(mgmt->frame_control) & IEEE80211_FCTL_STYPE; | |
857 | if (!(wdev->wiphy->mgmt_stypes[wdev->iftype].tx & BIT(stype >> 4))) | |
858 | return -EINVAL; | |
859 | ||
860 | if (ieee80211_is_action(mgmt->frame_control) && | |
861 | mgmt->u.action.category != WLAN_CATEGORY_PUBLIC) { | |
663fcafd JB |
862 | int err = 0; |
863 | ||
663fcafd JB |
864 | switch (wdev->iftype) { |
865 | case NL80211_IFTYPE_ADHOC: | |
7b0a0e3c JB |
866 | /* |
867 | * check for IBSS DA must be done by driver as | |
868 | * cfg80211 doesn't track the stations | |
869 | */ | |
870 | if (!wdev->u.ibss.current_bss || | |
871 | !ether_addr_equal(wdev->u.ibss.current_bss->pub.bssid, | |
872 | mgmt->bssid)) { | |
873 | err = -ENOTCONN; | |
874 | break; | |
875 | } | |
876 | break; | |
663fcafd JB |
877 | case NL80211_IFTYPE_STATION: |
878 | case NL80211_IFTYPE_P2P_CLIENT: | |
7b0a0e3c | 879 | if (!wdev->connected) { |
663fcafd JB |
880 | err = -ENOTCONN; |
881 | break; | |
882 | } | |
883 | ||
7b0a0e3c JB |
884 | /* FIXME: MLD may address this differently */ |
885 | ||
886 | if (!ether_addr_equal(wdev->u.client.connected_addr, | |
ac422d3c | 887 | mgmt->bssid)) { |
663fcafd JB |
888 | err = -ENOTCONN; |
889 | break; | |
890 | } | |
891 | ||
663fcafd | 892 | /* for station, check that DA is the AP */ |
7b0a0e3c | 893 | if (!ether_addr_equal(wdev->u.client.connected_addr, |
ac422d3c | 894 | mgmt->da)) { |
663fcafd JB |
895 | err = -ENOTCONN; |
896 | break; | |
897 | } | |
898 | break; | |
899 | case NL80211_IFTYPE_AP: | |
900 | case NL80211_IFTYPE_P2P_GO: | |
901 | case NL80211_IFTYPE_AP_VLAN: | |
19085ef3 RS |
902 | if (!ether_addr_equal(mgmt->bssid, wdev_address(wdev)) && |
903 | (params->link_id < 0 || | |
904 | !ether_addr_equal(mgmt->bssid, | |
905 | wdev->links[params->link_id].addr))) | |
663fcafd JB |
906 | err = -EINVAL; |
907 | break; | |
0778a6a3 | 908 | case NL80211_IFTYPE_MESH_POINT: |
ac422d3c | 909 | if (!ether_addr_equal(mgmt->sa, mgmt->bssid)) { |
0778a6a3 JC |
910 | err = -EINVAL; |
911 | break; | |
912 | } | |
913 | /* | |
914 | * check for mesh DA must be done by driver as | |
915 | * cfg80211 doesn't track the stations | |
916 | */ | |
917 | break; | |
98104fde JB |
918 | case NL80211_IFTYPE_P2P_DEVICE: |
919 | /* | |
920 | * fall through, P2P device only supports | |
921 | * public action frames | |
922 | */ | |
cb3b7d87 | 923 | case NL80211_IFTYPE_NAN: |
663fcafd JB |
924 | default: |
925 | err = -EOPNOTSUPP; | |
926 | break; | |
927 | } | |
663fcafd JB |
928 | |
929 | if (err) | |
930 | return err; | |
026331c4 JM |
931 | } |
932 | ||
69334861 VJ |
933 | if (!cfg80211_allowed_address(wdev, mgmt->sa) && |
934 | !cfg80211_allowed_random_address(wdev, mgmt)) | |
935 | return -EINVAL; | |
026331c4 | 936 | |
c528d7a2 | 937 | /* Transmit the management frame as requested by user space */ |
b176e629 | 938 | return rdev_mgmt_tx(rdev, wdev, params, cookie); |
026331c4 JM |
939 | } |
940 | ||
00b3d840 AS |
941 | bool cfg80211_rx_mgmt_ext(struct wireless_dev *wdev, |
942 | struct cfg80211_rx_info *info) | |
026331c4 | 943 | { |
026331c4 | 944 | struct wiphy *wiphy = wdev->wiphy; |
f26cbf40 | 945 | struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); |
2e161f78 JB |
946 | struct cfg80211_mgmt_registration *reg; |
947 | const struct ieee80211_txrx_stypes *stypes = | |
948 | &wiphy->mgmt_stypes[wdev->iftype]; | |
00b3d840 | 949 | struct ieee80211_mgmt *mgmt = (void *)info->buf; |
2e161f78 JB |
950 | const u8 *data; |
951 | int data_len; | |
026331c4 | 952 | bool result = false; |
2e161f78 JB |
953 | __le16 ftype = mgmt->frame_control & |
954 | cpu_to_le16(IEEE80211_FCTL_FTYPE | IEEE80211_FCTL_STYPE); | |
955 | u16 stype; | |
026331c4 | 956 | |
00b3d840 | 957 | trace_cfg80211_rx_mgmt(wdev, info); |
2e161f78 | 958 | stype = (le16_to_cpu(mgmt->frame_control) & IEEE80211_FCTL_STYPE) >> 4; |
026331c4 | 959 | |
4ee3e063 BL |
960 | if (!(stypes->rx & BIT(stype))) { |
961 | trace_cfg80211_return_bool(false); | |
2e161f78 | 962 | return false; |
4ee3e063 | 963 | } |
026331c4 | 964 | |
00b3d840 AS |
965 | data = info->buf + ieee80211_hdrlen(mgmt->frame_control); |
966 | data_len = info->len - ieee80211_hdrlen(mgmt->frame_control); | |
2e161f78 | 967 | |
09b1d5dc | 968 | spin_lock_bh(&rdev->mgmt_registrations_lock); |
2e161f78 JB |
969 | |
970 | list_for_each_entry(reg, &wdev->mgmt_registrations, list) { | |
971 | if (reg->frame_type != ftype) | |
972 | continue; | |
026331c4 | 973 | |
2e161f78 | 974 | if (reg->match_len > data_len) |
026331c4 JM |
975 | continue; |
976 | ||
2e161f78 | 977 | if (memcmp(reg->match, data, reg->match_len)) |
026331c4 JM |
978 | continue; |
979 | ||
980 | /* found match! */ | |
981 | ||
982 | /* Indicate the received Action frame to user space */ | |
00b3d840 AS |
983 | if (nl80211_send_mgmt(rdev, wdev, reg->nlportid, info, |
984 | GFP_ATOMIC)) | |
026331c4 JM |
985 | continue; |
986 | ||
987 | result = true; | |
988 | break; | |
989 | } | |
990 | ||
09b1d5dc | 991 | spin_unlock_bh(&rdev->mgmt_registrations_lock); |
026331c4 | 992 | |
4ee3e063 | 993 | trace_cfg80211_return_bool(result); |
026331c4 JM |
994 | return result; |
995 | } | |
00b3d840 | 996 | EXPORT_SYMBOL(cfg80211_rx_mgmt_ext); |
026331c4 | 997 | |
b35a51c7 VT |
998 | void cfg80211_sched_dfs_chan_update(struct cfg80211_registered_device *rdev) |
999 | { | |
1000 | cancel_delayed_work(&rdev->dfs_update_channels_wk); | |
1001 | queue_delayed_work(cfg80211_wq, &rdev->dfs_update_channels_wk, 0); | |
1002 | } | |
1003 | ||
04f39047 SW |
1004 | void cfg80211_dfs_channels_update_work(struct work_struct *work) |
1005 | { | |
a85a7e28 | 1006 | struct delayed_work *delayed_work = to_delayed_work(work); |
04f39047 SW |
1007 | struct cfg80211_registered_device *rdev; |
1008 | struct cfg80211_chan_def chandef; | |
1009 | struct ieee80211_supported_band *sband; | |
1010 | struct ieee80211_channel *c; | |
1011 | struct wiphy *wiphy; | |
1012 | bool check_again = false; | |
1013 | unsigned long timeout, next_time = 0; | |
b35a51c7 VT |
1014 | unsigned long time_dfs_update; |
1015 | enum nl80211_radar_event radar_event; | |
04f39047 SW |
1016 | int bandid, i; |
1017 | ||
04f39047 SW |
1018 | rdev = container_of(delayed_work, struct cfg80211_registered_device, |
1019 | dfs_update_channels_wk); | |
1020 | wiphy = &rdev->wiphy; | |
1021 | ||
5fe231e8 | 1022 | rtnl_lock(); |
57fbcce3 | 1023 | for (bandid = 0; bandid < NUM_NL80211_BANDS; bandid++) { |
04f39047 SW |
1024 | sband = wiphy->bands[bandid]; |
1025 | if (!sband) | |
1026 | continue; | |
1027 | ||
1028 | for (i = 0; i < sband->n_channels; i++) { | |
1029 | c = &sband->channels[i]; | |
1030 | ||
b35a51c7 VT |
1031 | if (!(c->flags & IEEE80211_CHAN_RADAR)) |
1032 | continue; | |
1033 | ||
1034 | if (c->dfs_state != NL80211_DFS_UNAVAILABLE && | |
1035 | c->dfs_state != NL80211_DFS_AVAILABLE) | |
04f39047 SW |
1036 | continue; |
1037 | ||
b35a51c7 VT |
1038 | if (c->dfs_state == NL80211_DFS_UNAVAILABLE) { |
1039 | time_dfs_update = IEEE80211_DFS_MIN_NOP_TIME_MS; | |
1040 | radar_event = NL80211_RADAR_NOP_FINISHED; | |
1041 | } else { | |
1042 | if (regulatory_pre_cac_allowed(wiphy) || | |
1043 | cfg80211_any_wiphy_oper_chan(wiphy, c)) | |
1044 | continue; | |
1045 | ||
1046 | time_dfs_update = REG_PRE_CAC_EXPIRY_GRACE_MS; | |
1047 | radar_event = NL80211_RADAR_PRE_CAC_EXPIRED; | |
1048 | } | |
1049 | ||
1050 | timeout = c->dfs_state_entered + | |
1051 | msecs_to_jiffies(time_dfs_update); | |
04f39047 SW |
1052 | |
1053 | if (time_after_eq(jiffies, timeout)) { | |
1054 | c->dfs_state = NL80211_DFS_USABLE; | |
bbe09bbc MK |
1055 | c->dfs_state_entered = jiffies; |
1056 | ||
04f39047 SW |
1057 | cfg80211_chandef_create(&chandef, c, |
1058 | NL80211_CHAN_NO_HT); | |
1059 | ||
1060 | nl80211_radar_notify(rdev, &chandef, | |
b35a51c7 VT |
1061 | radar_event, NULL, |
1062 | GFP_ATOMIC); | |
89766727 VT |
1063 | |
1064 | regulatory_propagate_dfs_state(wiphy, &chandef, | |
1065 | c->dfs_state, | |
1066 | radar_event); | |
04f39047 SW |
1067 | continue; |
1068 | } | |
1069 | ||
1070 | if (!check_again) | |
1071 | next_time = timeout - jiffies; | |
1072 | else | |
1073 | next_time = min(next_time, timeout - jiffies); | |
1074 | check_again = true; | |
1075 | } | |
1076 | } | |
5fe231e8 | 1077 | rtnl_unlock(); |
04f39047 SW |
1078 | |
1079 | /* reschedule if there are other channels waiting to be cleared again */ | |
1080 | if (check_again) | |
1081 | queue_delayed_work(cfg80211_wq, &rdev->dfs_update_channels_wk, | |
1082 | next_time); | |
1083 | } | |
1084 | ||
1085 | ||
c47240cb LB |
1086 | void __cfg80211_radar_event(struct wiphy *wiphy, |
1087 | struct cfg80211_chan_def *chandef, | |
1088 | bool offchan, gfp_t gfp) | |
04f39047 | 1089 | { |
f26cbf40 | 1090 | struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); |
04f39047 | 1091 | |
c47240cb | 1092 | trace_cfg80211_radar_event(wiphy, chandef, offchan); |
04f39047 SW |
1093 | |
1094 | /* only set the chandef supplied channel to unavailable, in | |
1095 | * case the radar is detected on only one of multiple channels | |
1096 | * spanned by the chandef. | |
1097 | */ | |
1098 | cfg80211_set_dfs_state(wiphy, chandef, NL80211_DFS_UNAVAILABLE); | |
1099 | ||
c47240cb | 1100 | if (offchan) |
a95bfb87 | 1101 | queue_work(cfg80211_wq, &rdev->background_cac_abort_wk); |
c47240cb | 1102 | |
b35a51c7 | 1103 | cfg80211_sched_dfs_chan_update(rdev); |
04f39047 SW |
1104 | |
1105 | nl80211_radar_notify(rdev, chandef, NL80211_RADAR_DETECTED, NULL, gfp); | |
89766727 VT |
1106 | |
1107 | memcpy(&rdev->radar_chandef, chandef, sizeof(struct cfg80211_chan_def)); | |
1108 | queue_work(cfg80211_wq, &rdev->propagate_radar_detect_wk); | |
04f39047 | 1109 | } |
c47240cb | 1110 | EXPORT_SYMBOL(__cfg80211_radar_event); |
04f39047 SW |
1111 | |
1112 | void cfg80211_cac_event(struct net_device *netdev, | |
d2859df5 | 1113 | const struct cfg80211_chan_def *chandef, |
81f67d60 AKS |
1114 | enum nl80211_radar_event event, gfp_t gfp, |
1115 | unsigned int link_id) | |
04f39047 SW |
1116 | { |
1117 | struct wireless_dev *wdev = netdev->ieee80211_ptr; | |
1118 | struct wiphy *wiphy = wdev->wiphy; | |
f26cbf40 | 1119 | struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); |
04f39047 SW |
1120 | unsigned long timeout; |
1121 | ||
81f67d60 AKS |
1122 | if (WARN_ON(wdev->valid_links && |
1123 | !(wdev->valid_links & BIT(link_id)))) | |
7b0a0e3c JB |
1124 | return; |
1125 | ||
81f67d60 | 1126 | trace_cfg80211_cac_event(netdev, event, link_id); |
04f39047 | 1127 | |
0b779823 | 1128 | if (WARN_ON(!wdev->links[link_id].cac_started && |
62c16f21 | 1129 | event != NL80211_RADAR_CAC_STARTED)) |
04f39047 SW |
1130 | return; |
1131 | ||
04f39047 SW |
1132 | switch (event) { |
1133 | case NL80211_RADAR_CAC_FINISHED: | |
0b779823 AKS |
1134 | timeout = wdev->links[link_id].cac_start_time + |
1135 | msecs_to_jiffies(wdev->links[link_id].cac_time_ms); | |
04f39047 | 1136 | WARN_ON(!time_after_eq(jiffies, timeout)); |
d2859df5 | 1137 | cfg80211_set_dfs_state(wiphy, chandef, NL80211_DFS_AVAILABLE); |
89766727 VT |
1138 | memcpy(&rdev->cac_done_chandef, chandef, |
1139 | sizeof(struct cfg80211_chan_def)); | |
1140 | queue_work(cfg80211_wq, &rdev->propagate_cac_done_wk); | |
b35a51c7 | 1141 | cfg80211_sched_dfs_chan_update(rdev); |
7b506ff6 | 1142 | fallthrough; |
04f39047 | 1143 | case NL80211_RADAR_CAC_ABORTED: |
0b779823 | 1144 | wdev->links[link_id].cac_started = false; |
2cb021f5 DL |
1145 | break; |
1146 | case NL80211_RADAR_CAC_STARTED: | |
0b779823 | 1147 | wdev->links[link_id].cac_started = true; |
04f39047 SW |
1148 | break; |
1149 | default: | |
1150 | WARN_ON(1); | |
1151 | return; | |
1152 | } | |
04f39047 | 1153 | |
d2859df5 | 1154 | nl80211_radar_notify(rdev, chandef, event, netdev, gfp); |
04f39047 SW |
1155 | } |
1156 | EXPORT_SYMBOL(cfg80211_cac_event); | |
bc2dfc02 | 1157 | |
bc2dfc02 | 1158 | static void |
a95bfb87 LB |
1159 | __cfg80211_background_cac_event(struct cfg80211_registered_device *rdev, |
1160 | struct wireless_dev *wdev, | |
1161 | const struct cfg80211_chan_def *chandef, | |
1162 | enum nl80211_radar_event event) | |
bc2dfc02 LB |
1163 | { |
1164 | struct wiphy *wiphy = &rdev->wiphy; | |
1165 | struct net_device *netdev; | |
1166 | ||
1167 | lockdep_assert_wiphy(&rdev->wiphy); | |
1168 | ||
91e89c77 LB |
1169 | if (!cfg80211_chandef_valid(chandef)) |
1170 | return; | |
1171 | ||
a95bfb87 | 1172 | if (!rdev->background_radar_wdev) |
bc2dfc02 LB |
1173 | return; |
1174 | ||
1175 | switch (event) { | |
1176 | case NL80211_RADAR_CAC_FINISHED: | |
1177 | cfg80211_set_dfs_state(wiphy, chandef, NL80211_DFS_AVAILABLE); | |
1178 | memcpy(&rdev->cac_done_chandef, chandef, sizeof(*chandef)); | |
1179 | queue_work(cfg80211_wq, &rdev->propagate_cac_done_wk); | |
1180 | cfg80211_sched_dfs_chan_update(rdev); | |
a95bfb87 | 1181 | wdev = rdev->background_radar_wdev; |
bc2dfc02 LB |
1182 | break; |
1183 | case NL80211_RADAR_CAC_ABORTED: | |
a95bfb87 | 1184 | if (!cancel_delayed_work(&rdev->background_cac_done_wk)) |
c47240cb | 1185 | return; |
a95bfb87 | 1186 | wdev = rdev->background_radar_wdev; |
bc2dfc02 LB |
1187 | break; |
1188 | case NL80211_RADAR_CAC_STARTED: | |
bc2dfc02 LB |
1189 | break; |
1190 | default: | |
1191 | return; | |
1192 | } | |
1193 | ||
1194 | netdev = wdev ? wdev->netdev : NULL; | |
1195 | nl80211_radar_notify(rdev, chandef, event, netdev, GFP_KERNEL); | |
1196 | } | |
1197 | ||
1507b153 | 1198 | static void |
a95bfb87 LB |
1199 | cfg80211_background_cac_event(struct cfg80211_registered_device *rdev, |
1200 | const struct cfg80211_chan_def *chandef, | |
1201 | enum nl80211_radar_event event) | |
1507b153 | 1202 | { |
f42d22d3 JB |
1203 | guard(wiphy)(&rdev->wiphy); |
1204 | ||
a95bfb87 LB |
1205 | __cfg80211_background_cac_event(rdev, rdev->background_radar_wdev, |
1206 | chandef, event); | |
1507b153 LB |
1207 | } |
1208 | ||
a95bfb87 | 1209 | void cfg80211_background_cac_done_wk(struct work_struct *work) |
1507b153 LB |
1210 | { |
1211 | struct delayed_work *delayed_work = to_delayed_work(work); | |
1212 | struct cfg80211_registered_device *rdev; | |
1213 | ||
1214 | rdev = container_of(delayed_work, struct cfg80211_registered_device, | |
a95bfb87 LB |
1215 | background_cac_done_wk); |
1216 | cfg80211_background_cac_event(rdev, &rdev->background_radar_chandef, | |
1217 | NL80211_RADAR_CAC_FINISHED); | |
1507b153 LB |
1218 | } |
1219 | ||
a95bfb87 | 1220 | void cfg80211_background_cac_abort_wk(struct work_struct *work) |
1507b153 LB |
1221 | { |
1222 | struct cfg80211_registered_device *rdev; | |
1223 | ||
1224 | rdev = container_of(work, struct cfg80211_registered_device, | |
a95bfb87 LB |
1225 | background_cac_abort_wk); |
1226 | cfg80211_background_cac_event(rdev, &rdev->background_radar_chandef, | |
1227 | NL80211_RADAR_CAC_ABORTED); | |
1507b153 LB |
1228 | } |
1229 | ||
a95bfb87 | 1230 | void cfg80211_background_cac_abort(struct wiphy *wiphy) |
bc2dfc02 LB |
1231 | { |
1232 | struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); | |
1233 | ||
a95bfb87 | 1234 | queue_work(cfg80211_wq, &rdev->background_cac_abort_wk); |
bc2dfc02 | 1235 | } |
a95bfb87 | 1236 | EXPORT_SYMBOL(cfg80211_background_cac_abort); |
bc2dfc02 LB |
1237 | |
1238 | int | |
a95bfb87 LB |
1239 | cfg80211_start_background_radar_detection(struct cfg80211_registered_device *rdev, |
1240 | struct wireless_dev *wdev, | |
1241 | struct cfg80211_chan_def *chandef) | |
bc2dfc02 LB |
1242 | { |
1243 | unsigned int cac_time_ms; | |
1244 | int err; | |
1245 | ||
1246 | lockdep_assert_wiphy(&rdev->wiphy); | |
1247 | ||
1248 | if (!wiphy_ext_feature_isset(&rdev->wiphy, | |
a95bfb87 | 1249 | NL80211_EXT_FEATURE_RADAR_BACKGROUND)) |
bc2dfc02 LB |
1250 | return -EOPNOTSUPP; |
1251 | ||
84158164 | 1252 | /* Offchannel chain already locked by another wdev */ |
a95bfb87 | 1253 | if (rdev->background_radar_wdev && rdev->background_radar_wdev != wdev) |
84158164 LB |
1254 | return -EBUSY; |
1255 | ||
1256 | /* CAC already in progress on the offchannel chain */ | |
a95bfb87 LB |
1257 | if (rdev->background_radar_wdev == wdev && |
1258 | delayed_work_pending(&rdev->background_cac_done_wk)) | |
bc2dfc02 LB |
1259 | return -EBUSY; |
1260 | ||
a95bfb87 | 1261 | err = rdev_set_radar_background(rdev, chandef); |
bc2dfc02 LB |
1262 | if (err) |
1263 | return err; | |
1264 | ||
1265 | cac_time_ms = cfg80211_chandef_dfs_cac_time(&rdev->wiphy, chandef); | |
1266 | if (!cac_time_ms) | |
1267 | cac_time_ms = IEEE80211_DFS_MIN_CAC_TIME_MS; | |
1268 | ||
a95bfb87 LB |
1269 | rdev->background_radar_chandef = *chandef; |
1270 | rdev->background_radar_wdev = wdev; /* Get offchain ownership */ | |
84158164 | 1271 | |
a95bfb87 LB |
1272 | __cfg80211_background_cac_event(rdev, wdev, chandef, |
1273 | NL80211_RADAR_CAC_STARTED); | |
1274 | queue_delayed_work(cfg80211_wq, &rdev->background_cac_done_wk, | |
bc2dfc02 LB |
1275 | msecs_to_jiffies(cac_time_ms)); |
1276 | ||
1277 | return 0; | |
1278 | } | |
1279 | ||
a95bfb87 | 1280 | void cfg80211_stop_background_radar_detection(struct wireless_dev *wdev) |
bc2dfc02 LB |
1281 | { |
1282 | struct wiphy *wiphy = wdev->wiphy; | |
1283 | struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); | |
1284 | ||
1285 | lockdep_assert_wiphy(wiphy); | |
1286 | ||
a95bfb87 | 1287 | if (wdev != rdev->background_radar_wdev) |
bc2dfc02 LB |
1288 | return; |
1289 | ||
a95bfb87 LB |
1290 | rdev_set_radar_background(rdev, NULL); |
1291 | rdev->background_radar_wdev = NULL; /* Release offchain ownership */ | |
bc2dfc02 | 1292 | |
a95bfb87 LB |
1293 | __cfg80211_background_cac_event(rdev, wdev, |
1294 | &rdev->background_radar_chandef, | |
1295 | NL80211_RADAR_CAC_ABORTED); | |
bc2dfc02 | 1296 | } |
65c1c041 IP |
1297 | |
1298 | int cfg80211_assoc_ml_reconf(struct cfg80211_registered_device *rdev, | |
1299 | struct net_device *dev, | |
a096a860 | 1300 | struct cfg80211_ml_reconf_req *req) |
65c1c041 IP |
1301 | { |
1302 | struct wireless_dev *wdev = dev->ieee80211_ptr; | |
1303 | int err; | |
1304 | ||
1305 | lockdep_assert_wiphy(wdev->wiphy); | |
1306 | ||
a096a860 | 1307 | err = rdev_assoc_ml_reconf(rdev, dev, req); |
65c1c041 IP |
1308 | if (!err) { |
1309 | int link_id; | |
1310 | ||
1311 | for (link_id = 0; link_id < IEEE80211_MLD_MAX_NUM_LINKS; | |
1312 | link_id++) { | |
a096a860 | 1313 | if (!req->add_links[link_id].bss) |
65c1c041 IP |
1314 | continue; |
1315 | ||
a096a860 JB |
1316 | cfg80211_ref_bss(&rdev->wiphy, req->add_links[link_id].bss); |
1317 | cfg80211_hold_bss(bss_from_pub(req->add_links[link_id].bss)); | |
65c1c041 IP |
1318 | } |
1319 | } | |
1320 | ||
1321 | return err; | |
1322 | } | |
1323 | ||
1324 | void cfg80211_mlo_reconf_add_done(struct net_device *dev, | |
1325 | struct cfg80211_mlo_reconf_done_data *data) | |
1326 | { | |
1327 | struct wireless_dev *wdev = dev->ieee80211_ptr; | |
1328 | struct wiphy *wiphy = wdev->wiphy; | |
1329 | int link_id; | |
1330 | ||
1331 | lockdep_assert_wiphy(wiphy); | |
1332 | ||
1333 | trace_cfg80211_mlo_reconf_add_done(dev, data->added_links, | |
1334 | data->buf, data->len); | |
1335 | ||
1336 | if (WARN_ON(!wdev->valid_links)) | |
1337 | return; | |
1338 | ||
1339 | if (WARN_ON(wdev->iftype != NL80211_IFTYPE_STATION && | |
1340 | wdev->iftype != NL80211_IFTYPE_P2P_CLIENT)) | |
1341 | return; | |
1342 | ||
1343 | /* validate that a BSS is given for each added link */ | |
1344 | for (link_id = 0; link_id < ARRAY_SIZE(data->links); link_id++) { | |
1345 | struct cfg80211_bss *bss = data->links[link_id].bss; | |
1346 | ||
1347 | if (!(data->added_links & BIT(link_id))) | |
1348 | continue; | |
1349 | ||
1350 | if (WARN_ON(!bss)) | |
1351 | return; | |
1352 | } | |
1353 | ||
1354 | for (link_id = 0; link_id < ARRAY_SIZE(data->links); link_id++) { | |
1355 | struct cfg80211_bss *bss = data->links[link_id].bss; | |
1356 | ||
1357 | if (!bss) | |
1358 | continue; | |
1359 | ||
1360 | if (data->added_links & BIT(link_id)) { | |
1361 | wdev->links[link_id].client.current_bss = | |
1362 | bss_from_pub(bss); | |
e16caea7 IP |
1363 | |
1364 | memcpy(wdev->links[link_id].addr, | |
1365 | data->links[link_id].addr, | |
1366 | ETH_ALEN); | |
65c1c041 IP |
1367 | } else { |
1368 | cfg80211_unhold_bss(bss_from_pub(bss)); | |
1369 | cfg80211_put_bss(wiphy, bss); | |
1370 | } | |
1371 | } | |
1372 | ||
1373 | wdev->valid_links |= data->added_links; | |
1374 | nl80211_mlo_reconf_add_done(dev, data); | |
1375 | } | |
1376 | EXPORT_SYMBOL(cfg80211_mlo_reconf_add_done); |