sme.c 30 KB
Newer Older
1
/*
2
3
4
 * SME code for cfg80211
 * both driver SME event handling and the SME implementation
 * (for nl80211's connect() and wext)
5
6
7
8
9
10
11
 *
 * Copyright 2009	Johannes Berg <johannes@sipsolutions.net>
 * Copyright (C) 2009   Intel Corporation. All rights reserved.
 */

#include <linux/etherdevice.h>
#include <linux/if_arp.h>
12
#include <linux/slab.h>
13
#include <linux/workqueue.h>
14
#include <linux/wireless.h>
15
#include <linux/export.h>
16
#include <net/iw_handler.h>
17
18
19
#include <net/cfg80211.h>
#include <net/rtnetlink.h>
#include "nl80211.h"
20
#include "reg.h"
21
#include "rdev-ops.h"
22

23
24
25
26
27
28
/*
 * Software SME in cfg80211, using auth/assoc/deauth calls to the
 * driver. This is is for implementing nl80211's connect/disconnect
 * and wireless extensions (if configured.)
 */

29
30
31
32
33
34
35
36
struct cfg80211_conn {
	struct cfg80211_connect_params params;
	/* these are sub-states of the _CONNECTING sme_state */
	enum {
		CFG80211_CONN_SCANNING,
		CFG80211_CONN_SCAN_AGAIN,
		CFG80211_CONN_AUTHENTICATE_NEXT,
		CFG80211_CONN_AUTHENTICATING,
37
		CFG80211_CONN_AUTH_FAILED_TIMEOUT,
38
39
		CFG80211_CONN_ASSOCIATE_NEXT,
		CFG80211_CONN_ASSOCIATING,
40
		CFG80211_CONN_ASSOC_FAILED,
41
		CFG80211_CONN_ASSOC_FAILED_TIMEOUT,
42
		CFG80211_CONN_DEAUTH,
43
		CFG80211_CONN_ABANDON,
44
		CFG80211_CONN_CONNECTED,
45
	} state;
46
	u8 bssid[ETH_ALEN], prev_bssid[ETH_ALEN];
47
	const u8 *ie;
48
	size_t ie_len;
49
	bool auto_auth, prev_bssid_valid;
50
51
};

52
static void cfg80211_sme_free(struct wireless_dev *wdev)
53
{
54
55
	if (!wdev->conn)
		return;
56

57
58
59
	kfree(wdev->conn->ie);
	kfree(wdev->conn);
	wdev->conn = NULL;
60
61
}

62
63
static int cfg80211_conn_scan(struct wireless_dev *wdev)
{
64
	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
65
66
67
68
	struct cfg80211_scan_request *request;
	int n_channels, err;

	ASSERT_RTNL();
Johannes Berg's avatar
Johannes Berg committed
69
	ASSERT_WDEV_LOCK(wdev);
70

71
	if (rdev->scan_req || rdev->scan_msg)
72
73
		return -EBUSY;

74
	if (wdev->conn->params.channel)
75
		n_channels = 1;
76
77
	else
		n_channels = ieee80211_get_num_supported_channels(wdev->wiphy);
78
79
80
81
82
83
84

	request = kzalloc(sizeof(*request) + sizeof(request->ssids[0]) +
			  sizeof(request->channels[0]) * n_channels,
			  GFP_KERNEL);
	if (!request)
		return -ENOMEM;

85
	if (wdev->conn->params.channel) {
86
		enum nl80211_band band = wdev->conn->params.channel->band;
87
88
89
90
91
92
93
		struct ieee80211_supported_band *sband =
			wdev->wiphy->bands[band];

		if (!sband) {
			kfree(request);
			return -EINVAL;
		}
94
		request->channels[0] = wdev->conn->params.channel;
95
96
		request->rates[band] = (1 << sband->n_bitrates) - 1;
	} else {
97
		int i = 0, j;
98
		enum nl80211_band band;
99
100
		struct ieee80211_supported_band *bands;
		struct ieee80211_channel *channel;
101

102
		for (band = 0; band < NUM_NL80211_BANDS; band++) {
103
104
			bands = wdev->wiphy->bands[band];
			if (!bands)
105
				continue;
106
107
108
109
110
111
112
			for (j = 0; j < bands->n_channels; j++) {
				channel = &bands->channels[j];
				if (channel->flags & IEEE80211_CHAN_DISABLED)
					continue;
				request->channels[i++] = channel;
			}
			request->rates[band] = (1 << bands->n_bitrates) - 1;
113
		}
114
		n_channels = i;
115
116
	}
	request->n_channels = n_channels;
117
	request->ssids = (void *)&request->channels[n_channels];
118
119
120
121
122
123
	request->n_ssids = 1;

	memcpy(request->ssids[0].ssid, wdev->conn->params.ssid,
		wdev->conn->params.ssid_len);
	request->ssids[0].ssid_len = wdev->conn->params.ssid_len;

124
125
	eth_broadcast_addr(request->bssid);

Johannes Berg's avatar
Johannes Berg committed
126
	request->wdev = wdev;
127
	request->wiphy = &rdev->wiphy;
128
	request->scan_start = jiffies;
129

130
	rdev->scan_req = request;
131

132
	err = rdev_scan(rdev, request);
133
134
	if (!err) {
		wdev->conn->state = CFG80211_CONN_SCANNING;
Johannes Berg's avatar
Johannes Berg committed
135
		nl80211_send_scan_start(rdev, wdev);
136
		dev_hold(wdev->netdev);
137
	} else {
138
		rdev->scan_req = NULL;
139
140
141
142
143
		kfree(request);
	}
	return err;
}

144
145
static int cfg80211_conn_do_work(struct wireless_dev *wdev,
				 enum nl80211_timeout_reason *treason)
146
{
147
	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
Johannes Berg's avatar
Johannes Berg committed
148
	struct cfg80211_connect_params *params;
149
	struct cfg80211_assoc_request req = {};
Johannes Berg's avatar
Johannes Berg committed
150
	int err;
151

Johannes Berg's avatar
Johannes Berg committed
152
153
	ASSERT_WDEV_LOCK(wdev);

154
155
156
	if (!wdev->conn)
		return 0;

Johannes Berg's avatar
Johannes Berg committed
157
158
	params = &wdev->conn->params;

159
	switch (wdev->conn->state) {
160
161
162
	case CFG80211_CONN_SCANNING:
		/* didn't find it during scan ... */
		return -ENOENT;
163
164
165
	case CFG80211_CONN_SCAN_AGAIN:
		return cfg80211_conn_scan(wdev);
	case CFG80211_CONN_AUTHENTICATE_NEXT:
Johannes Berg's avatar
Johannes Berg committed
166
167
		if (WARN_ON(!rdev->ops->auth))
			return -EOPNOTSUPP;
Johannes Berg's avatar
Johannes Berg committed
168
		wdev->conn->state = CFG80211_CONN_AUTHENTICATING;
169
170
171
172
173
174
175
		return cfg80211_mlme_auth(rdev, wdev->netdev,
					  params->channel, params->auth_type,
					  params->bssid,
					  params->ssid, params->ssid_len,
					  NULL, 0,
					  params->key, params->key_len,
					  params->key_idx, NULL, 0);
176
177
	case CFG80211_CONN_AUTH_FAILED_TIMEOUT:
		*treason = NL80211_TIMEOUT_AUTH;
178
		return -ENOTCONN;
179
	case CFG80211_CONN_ASSOCIATE_NEXT:
Johannes Berg's avatar
Johannes Berg committed
180
181
		if (WARN_ON(!rdev->ops->assoc))
			return -EOPNOTSUPP;
Johannes Berg's avatar
Johannes Berg committed
182
		wdev->conn->state = CFG80211_CONN_ASSOCIATING;
183
		if (wdev->conn->prev_bssid_valid)
184
185
186
187
188
189
190
191
192
193
194
			req.prev_bssid = wdev->conn->prev_bssid;
		req.ie = params->ie;
		req.ie_len = params->ie_len;
		req.use_mfp = params->mfp != NL80211_MFP_NO;
		req.crypto = params->crypto;
		req.flags = params->flags;
		req.ht_capa = params->ht_capa;
		req.ht_capa_mask = params->ht_capa_mask;
		req.vht_capa = params->vht_capa;
		req.vht_capa_mask = params->vht_capa_mask;

195
196
197
		err = cfg80211_mlme_assoc(rdev, wdev->netdev, params->channel,
					  params->bssid, params->ssid,
					  params->ssid_len, &req);
Johannes Berg's avatar
Johannes Berg committed
198
		if (err)
199
200
201
202
			cfg80211_mlme_deauth(rdev, wdev->netdev, params->bssid,
					     NULL, 0,
					     WLAN_REASON_DEAUTH_LEAVING,
					     false);
Johannes Berg's avatar
Johannes Berg committed
203
		return err;
204
205
206
	case CFG80211_CONN_ASSOC_FAILED_TIMEOUT:
		*treason = NL80211_TIMEOUT_ASSOC;
		/* fall through */
207
208
209
210
211
	case CFG80211_CONN_ASSOC_FAILED:
		cfg80211_mlme_deauth(rdev, wdev->netdev, params->bssid,
				     NULL, 0,
				     WLAN_REASON_DEAUTH_LEAVING, false);
		return -ENOTCONN;
212
	case CFG80211_CONN_DEAUTH:
213
214
215
		cfg80211_mlme_deauth(rdev, wdev->netdev, params->bssid,
				     NULL, 0,
				     WLAN_REASON_DEAUTH_LEAVING, false);
216
217
		/* fall through */
	case CFG80211_CONN_ABANDON:
218
219
		/* free directly, disconnected event already sent */
		cfg80211_sme_free(wdev);
220
		return 0;
221
222
223
224
225
226
227
	default:
		return 0;
	}
}

void cfg80211_conn_work(struct work_struct *work)
{
228
	struct cfg80211_registered_device *rdev =
229
230
		container_of(work, struct cfg80211_registered_device, conn_work);
	struct wireless_dev *wdev;
Johannes Berg's avatar
Johannes Berg committed
231
	u8 bssid_buf[ETH_ALEN], *bssid = NULL;
232
	enum nl80211_timeout_reason treason;
233
234
235

	rtnl_lock();

236
	list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
237
238
239
		if (!wdev->netdev)
			continue;

Johannes Berg's avatar
Johannes Berg committed
240
241
242
		wdev_lock(wdev);
		if (!netif_running(wdev->netdev)) {
			wdev_unlock(wdev);
243
			continue;
Johannes Berg's avatar
Johannes Berg committed
244
		}
245
246
		if (!wdev->conn ||
		    wdev->conn->state == CFG80211_CONN_CONNECTED) {
Johannes Berg's avatar
Johannes Berg committed
247
			wdev_unlock(wdev);
248
			continue;
Johannes Berg's avatar
Johannes Berg committed
249
		}
Johannes Berg's avatar
Johannes Berg committed
250
251
252
253
		if (wdev->conn->params.bssid) {
			memcpy(bssid_buf, wdev->conn->params.bssid, ETH_ALEN);
			bssid = bssid_buf;
		}
254
255
		treason = NL80211_TIMEOUT_UNSPECIFIED;
		if (cfg80211_conn_do_work(wdev, &treason)) {
Johannes Berg's avatar
Johannes Berg committed
256
			__cfg80211_connect_result(
257
					wdev->netdev, bssid,
258
259
					NULL, 0, NULL, 0, -1, false, NULL,
					treason);
260
		}
Johannes Berg's avatar
Johannes Berg committed
261
		wdev_unlock(wdev);
262
263
264
265
266
	}

	rtnl_unlock();
}

267
/* Returned bss is reference counted and must be cleaned up appropriately. */
Johannes Berg's avatar
Johannes Berg committed
268
static struct cfg80211_bss *cfg80211_get_conn_bss(struct wireless_dev *wdev)
269
{
270
	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
271
272
	struct cfg80211_bss *bss;

Johannes Berg's avatar
Johannes Berg committed
273
274
	ASSERT_WDEV_LOCK(wdev);

275
276
	bss = cfg80211_get_bss(wdev->wiphy, wdev->conn->params.channel,
			       wdev->conn->params.bssid,
277
278
			       wdev->conn->params.ssid,
			       wdev->conn->params.ssid_len,
279
			       wdev->conn_bss_type,
280
			       IEEE80211_PRIVACY(wdev->conn->params.privacy));
281
	if (!bss)
Johannes Berg's avatar
Johannes Berg committed
282
		return NULL;
283
284
285
286
287

	memcpy(wdev->conn->bssid, bss->bssid, ETH_ALEN);
	wdev->conn->params.bssid = wdev->conn->bssid;
	wdev->conn->params.channel = bss->channel;
	wdev->conn->state = CFG80211_CONN_AUTHENTICATE_NEXT;
288
	schedule_work(&rdev->conn_work);
289

Johannes Berg's avatar
Johannes Berg committed
290
	return bss;
291
292
}

Johannes Berg's avatar
Johannes Berg committed
293
static void __cfg80211_sme_scan_done(struct net_device *dev)
294
295
{
	struct wireless_dev *wdev = dev->ieee80211_ptr;
296
	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
Johannes Berg's avatar
Johannes Berg committed
297
	struct cfg80211_bss *bss;
298

Johannes Berg's avatar
Johannes Berg committed
299
300
	ASSERT_WDEV_LOCK(wdev);

301
	if (!wdev->conn)
302
303
304
305
306
307
		return;

	if (wdev->conn->state != CFG80211_CONN_SCANNING &&
	    wdev->conn->state != CFG80211_CONN_SCAN_AGAIN)
		return;

Johannes Berg's avatar
Johannes Berg committed
308
	bss = cfg80211_get_conn_bss(wdev);
309
	if (bss)
310
		cfg80211_put_bss(&rdev->wiphy, bss);
311
312
	else
		schedule_work(&rdev->conn_work);
313
314
}

Johannes Berg's avatar
Johannes Berg committed
315
316
317
318
319
320
321
322
323
void cfg80211_sme_scan_done(struct net_device *dev)
{
	struct wireless_dev *wdev = dev->ieee80211_ptr;

	wdev_lock(wdev);
	__cfg80211_sme_scan_done(dev);
	wdev_unlock(wdev);
}

324
void cfg80211_sme_rx_auth(struct wireless_dev *wdev, const u8 *buf, size_t len)
325
326
{
	struct wiphy *wiphy = wdev->wiphy;
327
	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
328
329
330
	struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)buf;
	u16 status_code = le16_to_cpu(mgmt->u.auth.status_code);

Johannes Berg's avatar
Johannes Berg committed
331
332
	ASSERT_WDEV_LOCK(wdev);

333
	if (!wdev->conn || wdev->conn->state == CFG80211_CONN_CONNECTED)
334
335
336
337
338
339
340
341
		return;

	if (status_code == WLAN_STATUS_NOT_SUPPORTED_AUTH_ALG &&
	    wdev->conn->auto_auth &&
	    wdev->conn->params.auth_type != NL80211_AUTHTYPE_NETWORK_EAP) {
		/* select automatically between only open, shared, leap */
		switch (wdev->conn->params.auth_type) {
		case NL80211_AUTHTYPE_OPEN_SYSTEM:
Johannes Berg's avatar
Johannes Berg committed
342
343
344
345
346
347
			if (wdev->connect_keys)
				wdev->conn->params.auth_type =
					NL80211_AUTHTYPE_SHARED_KEY;
			else
				wdev->conn->params.auth_type =
					NL80211_AUTHTYPE_NETWORK_EAP;
348
349
350
351
352
353
354
355
356
357
358
359
360
			break;
		case NL80211_AUTHTYPE_SHARED_KEY:
			wdev->conn->params.auth_type =
				NL80211_AUTHTYPE_NETWORK_EAP;
			break;
		default:
			/* huh? */
			wdev->conn->params.auth_type =
				NL80211_AUTHTYPE_OPEN_SYSTEM;
			break;
		}
		wdev->conn->state = CFG80211_CONN_AUTHENTICATE_NEXT;
		schedule_work(&rdev->conn_work);
Johannes Berg's avatar
Johannes Berg committed
361
	} else if (status_code != WLAN_STATUS_SUCCESS) {
362
363
		__cfg80211_connect_result(wdev->netdev, mgmt->bssid,
					  NULL, 0, NULL, 0,
364
365
					  status_code, false, NULL,
					  NL80211_TIMEOUT_UNSPECIFIED);
366
	} else if (wdev->conn->state == CFG80211_CONN_AUTHENTICATING) {
367
368
369
370
		wdev->conn->state = CFG80211_CONN_ASSOCIATE_NEXT;
		schedule_work(&rdev->conn_work);
	}
}
371

372
bool cfg80211_sme_rx_assoc_resp(struct wireless_dev *wdev, u16 status)
373
{
374
	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
375

376
	if (!wdev->conn)
377
378
		return false;

379
380
	if (status == WLAN_STATUS_SUCCESS) {
		wdev->conn->state = CFG80211_CONN_CONNECTED;
381
		return false;
382
	}
383

384
385
386
387
388
389
390
391
392
393
394
395
	if (wdev->conn->prev_bssid_valid) {
		/*
		 * Some stupid APs don't accept reassoc, so we
		 * need to fall back to trying regular assoc;
		 * return true so no event is sent to userspace.
		 */
		wdev->conn->prev_bssid_valid = false;
		wdev->conn->state = CFG80211_CONN_ASSOCIATE_NEXT;
		schedule_work(&rdev->conn_work);
		return true;
	}

396
	wdev->conn->state = CFG80211_CONN_ASSOC_FAILED;
397
	schedule_work(&rdev->conn_work);
398
399
	return false;
}
400

401
402
403
void cfg80211_sme_deauth(struct wireless_dev *wdev)
{
	cfg80211_sme_free(wdev);
404
405
}

406
void cfg80211_sme_auth_timeout(struct wireless_dev *wdev)
407
{
408
	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
409
410
411
412

	if (!wdev->conn)
		return;

413
	wdev->conn->state = CFG80211_CONN_AUTH_FAILED_TIMEOUT;
414
	schedule_work(&rdev->conn_work);
415
}
416

417
418
void cfg80211_sme_disassoc(struct wireless_dev *wdev)
{
419
	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
420
421
422
423
424

	if (!wdev->conn)
		return;

	wdev->conn->state = CFG80211_CONN_DEAUTH;
425
426
427
	schedule_work(&rdev->conn_work);
}

428
429
void cfg80211_sme_assoc_timeout(struct wireless_dev *wdev)
{
430
	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
431
432
433
434

	if (!wdev->conn)
		return;

435
	wdev->conn->state = CFG80211_CONN_ASSOC_FAILED_TIMEOUT;
436
	schedule_work(&rdev->conn_work);
437
438
}

439
440
441
442
443
444
445
446
447
448
449
void cfg80211_sme_abandon_assoc(struct wireless_dev *wdev)
{
	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);

	if (!wdev->conn)
		return;

	wdev->conn->state = CFG80211_CONN_ABANDON;
	schedule_work(&rdev->conn_work);
}

450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
static int cfg80211_sme_get_conn_ies(struct wireless_dev *wdev,
				     const u8 *ies, size_t ies_len,
				     const u8 **out_ies, size_t *out_ies_len)
{
	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
	u8 *buf;
	size_t offs;

	if (!rdev->wiphy.extended_capabilities_len ||
	    (ies && cfg80211_find_ie(WLAN_EID_EXT_CAPABILITY, ies, ies_len))) {
		*out_ies = kmemdup(ies, ies_len, GFP_KERNEL);
		if (!*out_ies)
			return -ENOMEM;
		*out_ies_len = ies_len;
		return 0;
	}

	buf = kmalloc(ies_len + rdev->wiphy.extended_capabilities_len + 2,
		      GFP_KERNEL);
	if (!buf)
		return -ENOMEM;

	if (ies_len) {
		static const u8 before_extcapa[] = {
			/* not listing IEs expected to be created by driver */
			WLAN_EID_RSN,
			WLAN_EID_QOS_CAPA,
			WLAN_EID_RRM_ENABLED_CAPABILITIES,
			WLAN_EID_MOBILITY_DOMAIN,
			WLAN_EID_SUPPORTED_REGULATORY_CLASSES,
			WLAN_EID_BSS_COEX_2040,
		};

		offs = ieee80211_ie_split(ies, ies_len, before_extcapa,
					  ARRAY_SIZE(before_extcapa), 0);
		memcpy(buf, ies, offs);
		/* leave a whole for extended capabilities IE */
		memcpy(buf + offs + rdev->wiphy.extended_capabilities_len + 2,
		       ies + offs, ies_len - offs);
	} else {
		offs = 0;
	}

	/* place extended capabilities IE (with only driver capabilities) */
	buf[offs] = WLAN_EID_EXT_CAPABILITY;
	buf[offs + 1] = rdev->wiphy.extended_capabilities_len;
	memcpy(buf + offs + 2,
	       rdev->wiphy.extended_capabilities,
	       rdev->wiphy.extended_capabilities_len);

	*out_ies = buf;
	*out_ies_len = ies_len + rdev->wiphy.extended_capabilities_len + 2;

	return 0;
}

506
507
508
509
static int cfg80211_sme_connect(struct wireless_dev *wdev,
				struct cfg80211_connect_params *connect,
				const u8 *prev_bssid)
{
510
	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
511
512
513
514
515
516
	struct cfg80211_bss *bss;
	int err;

	if (!rdev->ops->auth || !rdev->ops->assoc)
		return -EOPNOTSUPP;

517
518
519
520
521
522
523
524
525
526
527
528
	if (wdev->current_bss) {
		if (!prev_bssid)
			return -EALREADY;
		if (prev_bssid &&
		    !ether_addr_equal(prev_bssid, wdev->current_bss->pub.bssid))
			return -ENOTCONN;
		cfg80211_unhold_bss(wdev->current_bss);
		cfg80211_put_bss(wdev->wiphy, &wdev->current_bss->pub);
		wdev->current_bss = NULL;

		cfg80211_sme_free(wdev);
	}
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545

	if (WARN_ON(wdev->conn))
		return -EINPROGRESS;

	wdev->conn = kzalloc(sizeof(*wdev->conn), GFP_KERNEL);
	if (!wdev->conn)
		return -ENOMEM;

	/*
	 * Copy all parameters, and treat explicitly IEs, BSSID, SSID.
	 */
	memcpy(&wdev->conn->params, connect, sizeof(*connect));
	if (connect->bssid) {
		wdev->conn->params.bssid = wdev->conn->bssid;
		memcpy(wdev->conn->bssid, connect->bssid, ETH_ALEN);
	}

546
547
548
549
550
551
	if (cfg80211_sme_get_conn_ies(wdev, connect->ie, connect->ie_len,
				      &wdev->conn->ie,
				      &wdev->conn->params.ie_len)) {
		kfree(wdev->conn);
		wdev->conn = NULL;
		return -ENOMEM;
552
	}
553
	wdev->conn->params.ie = wdev->conn->ie;
554
555
556
557
558
559
560
561
562
563
564

	if (connect->auth_type == NL80211_AUTHTYPE_AUTOMATIC) {
		wdev->conn->auto_auth = true;
		/* start with open system ... should mostly work */
		wdev->conn->params.auth_type =
			NL80211_AUTHTYPE_OPEN_SYSTEM;
	} else {
		wdev->conn->auto_auth = false;
	}

	wdev->conn->params.ssid = wdev->ssid;
565
	wdev->conn->params.ssid_len = wdev->ssid_len;
566
567
568
569
570
571
572
573
574
575
576

	/* see if we have the bss already */
	bss = cfg80211_get_conn_bss(wdev);

	if (prev_bssid) {
		memcpy(wdev->conn->prev_bssid, prev_bssid, ETH_ALEN);
		wdev->conn->prev_bssid_valid = true;
	}

	/* we're good if we have a matching bss struct */
	if (bss) {
577
578
579
		enum nl80211_timeout_reason treason;

		err = cfg80211_conn_do_work(wdev, &treason);
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
		cfg80211_put_bss(wdev->wiphy, bss);
	} else {
		/* otherwise we'll need to scan for the AP first */
		err = cfg80211_conn_scan(wdev);

		/*
		 * If we can't scan right now, then we need to scan again
		 * after the current scan finished, since the parameters
		 * changed (unless we find a good AP anyway).
		 */
		if (err == -EBUSY) {
			err = 0;
			wdev->conn->state = CFG80211_CONN_SCAN_AGAIN;
		}
	}

	if (err)
		cfg80211_sme_free(wdev);

	return err;
}

static int cfg80211_sme_disconnect(struct wireless_dev *wdev, u16 reason)
{
604
	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
	int err;

	if (!wdev->conn)
		return 0;

	if (!rdev->ops->deauth)
		return -EOPNOTSUPP;

	if (wdev->conn->state == CFG80211_CONN_SCANNING ||
	    wdev->conn->state == CFG80211_CONN_SCAN_AGAIN) {
		err = 0;
		goto out;
	}

	/* wdev->conn->params.bssid must be set if > SCANNING */
	err = cfg80211_mlme_deauth(rdev, wdev->netdev,
				   wdev->conn->params.bssid,
				   NULL, 0, reason, false);
 out:
	cfg80211_sme_free(wdev);
	return err;
}

/*
 * code shared for in-device and software SME
 */

static bool cfg80211_is_all_idle(void)
{
	struct cfg80211_registered_device *rdev;
	struct wireless_dev *wdev;
	bool is_all_idle = true;

	/*
	 * All devices must be idle as otherwise if you are actively
	 * scanning some new beacon hints could be learned and would
	 * count as new regulatory hints.
	 */
	list_for_each_entry(rdev, &cfg80211_rdev_list, list) {
644
		list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
			wdev_lock(wdev);
			if (wdev->conn || wdev->current_bss)
				is_all_idle = false;
			wdev_unlock(wdev);
		}
	}

	return is_all_idle;
}

static void disconnect_work(struct work_struct *work)
{
	rtnl_lock();
	if (cfg80211_is_all_idle())
		regulatory_hint_disconnect();
	rtnl_unlock();
}

static DECLARE_WORK(cfg80211_disconnect_work, disconnect_work);


/*
 * API calls for drivers implementing connect/disconnect and
 * SME event handling
 */

671
/* This method must consume bss one way or another */
Johannes Berg's avatar
Johannes Berg committed
672
673
674
void __cfg80211_connect_result(struct net_device *dev, const u8 *bssid,
			       const u8 *req_ie, size_t req_ie_len,
			       const u8 *resp_ie, size_t resp_ie_len,
675
			       int status, bool wextev,
676
677
			       struct cfg80211_bss *bss,
			       enum nl80211_timeout_reason timeout_reason)
678
679
{
	struct wireless_dev *wdev = dev->ieee80211_ptr;
680
	const u8 *country_ie;
Johannes Berg's avatar
Johannes Berg committed
681
#ifdef CONFIG_CFG80211_WEXT
682
683
684
	union iwreq_data wrqu;
#endif

Johannes Berg's avatar
Johannes Berg committed
685
686
	ASSERT_WDEV_LOCK(wdev);

687
	if (WARN_ON(wdev->iftype != NL80211_IFTYPE_STATION &&
688
689
		    wdev->iftype != NL80211_IFTYPE_P2P_CLIENT)) {
		cfg80211_put_bss(wdev->wiphy, bss);
690
		return;
691
	}
692

693
	nl80211_send_connect_result(wiphy_to_rdev(wdev->wiphy), dev,
694
				    bssid, req_ie, req_ie_len,
695
				    resp_ie, resp_ie_len,
696
				    status, timeout_reason, GFP_KERNEL);
697

Johannes Berg's avatar
Johannes Berg committed
698
#ifdef CONFIG_CFG80211_WEXT
699
700
701
702
	if (wextev) {
		if (req_ie && status == WLAN_STATUS_SUCCESS) {
			memset(&wrqu, 0, sizeof(wrqu));
			wrqu.data.length = req_ie_len;
Zhu Yi's avatar
Zhu Yi committed
703
			wireless_send_event(dev, IWEVASSOCREQIE, &wrqu, req_ie);
704
705
706
707
708
709
710
711
712
713
		}

		if (resp_ie && status == WLAN_STATUS_SUCCESS) {
			memset(&wrqu, 0, sizeof(wrqu));
			wrqu.data.length = resp_ie_len;
			wireless_send_event(dev, IWEVASSOCRESPIE, &wrqu, resp_ie);
		}

		memset(&wrqu, 0, sizeof(wrqu));
		wrqu.ap_addr.sa_family = ARPHRD_ETHER;
714
		if (bssid && status == WLAN_STATUS_SUCCESS) {
715
			memcpy(wrqu.ap_addr.sa_data, bssid, ETH_ALEN);
716
717
718
			memcpy(wdev->wext.prev_bssid, bssid, ETH_ALEN);
			wdev->wext.prev_bssid_valid = true;
		}
719
720
721
722
		wireless_send_event(dev, SIOCGIWAP, &wrqu, NULL);
	}
#endif

723
	if (!bss && (status == WLAN_STATUS_SUCCESS)) {
724
		WARN_ON_ONCE(!wiphy_to_rdev(wdev->wiphy)->ops->connect);
725
726
		bss = cfg80211_get_bss(wdev->wiphy, NULL, bssid,
				       wdev->ssid, wdev->ssid_len,
727
				       wdev->conn_bss_type,
728
				       IEEE80211_PRIVACY_ANY);
729
730
731
732
		if (bss)
			cfg80211_hold_bss(bss_from_pub(bss));
	}

733
734
	if (wdev->current_bss) {
		cfg80211_unhold_bss(wdev->current_bss);
735
		cfg80211_put_bss(wdev->wiphy, &wdev->current_bss->pub);
736
737
738
		wdev->current_bss = NULL;
	}

Johannes Berg's avatar
Johannes Berg committed
739
	if (status != WLAN_STATUS_SUCCESS) {
740
		kzfree(wdev->connect_keys);
Johannes Berg's avatar
Johannes Berg committed
741
		wdev->connect_keys = NULL;
742
		wdev->ssid_len = 0;
743
		wdev->conn_owner_nlportid = 0;
744
745
746
747
		if (bss) {
			cfg80211_unhold_bss(bss_from_pub(bss));
			cfg80211_put_bss(wdev->wiphy, bss);
		}
748
		cfg80211_sme_free(wdev);
Johannes Berg's avatar
Johannes Berg committed
749
		return;
750
	}
Johannes Berg's avatar
Johannes Berg committed
751

752
753
	if (WARN_ON(!bss))
		return;
Johannes Berg's avatar
Johannes Berg committed
754
755
756

	wdev->current_bss = bss_from_pub(bss);

757
758
	if (!(wdev->wiphy->flags & WIPHY_FLAG_HAS_STATIC_WEP))
		cfg80211_upload_connect_keys(wdev);
759

760
761
762
763
764
765
766
767
768
	rcu_read_lock();
	country_ie = ieee80211_bss_get_ie(bss, WLAN_EID_COUNTRY);
	if (!country_ie) {
		rcu_read_unlock();
		return;
	}

	country_ie = kmemdup(country_ie, 2 + country_ie[1], GFP_ATOMIC);
	rcu_read_unlock();
769
770
771
772
773
774
775
776
777

	if (!country_ie)
		return;

	/*
	 * ieee80211_bss_get_ie() ensures we can access:
	 * - country_ie + 2, the start of the country ie data, and
	 * - and country_ie[1] which is the IE length
	 */
778
779
	regulatory_hint_country_ie(wdev->wiphy, bss->channel->band,
				   country_ie + 2, country_ie[1]);
780
	kfree(country_ie);
781
}
782

783
784
785
786
/* Consumes bss object one way or another */
void cfg80211_connect_bss(struct net_device *dev, const u8 *bssid,
			  struct cfg80211_bss *bss, const u8 *req_ie,
			  size_t req_ie_len, const u8 *resp_ie,
787
788
			  size_t resp_ie_len, int status, gfp_t gfp,
			  enum nl80211_timeout_reason timeout_reason)
789
{
Johannes Berg's avatar
Johannes Berg committed
790
	struct wireless_dev *wdev = dev->ieee80211_ptr;
791
	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
Johannes Berg's avatar
Johannes Berg committed
792
793
794
	struct cfg80211_event *ev;
	unsigned long flags;

795
796
797
798
799
800
801
802
803
804
	if (bss) {
		/* Make sure the bss entry provided by the driver is valid. */
		struct cfg80211_internal_bss *ibss = bss_from_pub(bss);

		if (WARN_ON(list_empty(&ibss->list))) {
			cfg80211_put_bss(wdev->wiphy, bss);
			return;
		}
	}

Johannes Berg's avatar
Johannes Berg committed
805
	ev = kzalloc(sizeof(*ev) + req_ie_len + resp_ie_len, gfp);
806
807
	if (!ev) {
		cfg80211_put_bss(wdev->wiphy, bss);
Johannes Berg's avatar
Johannes Berg committed
808
		return;
809
	}
Johannes Berg's avatar
Johannes Berg committed
810
811

	ev->type = EVENT_CONNECT_RESULT;
812
813
	if (bssid)
		memcpy(ev->cr.bssid, bssid, ETH_ALEN);
814
815
816
817
818
819
820
821
822
823
	if (req_ie_len) {
		ev->cr.req_ie = ((u8 *)ev) + sizeof(*ev);
		ev->cr.req_ie_len = req_ie_len;
		memcpy((void *)ev->cr.req_ie, req_ie, req_ie_len);
	}
	if (resp_ie_len) {
		ev->cr.resp_ie = ((u8 *)ev) + sizeof(*ev) + req_ie_len;
		ev->cr.resp_ie_len = resp_ie_len;
		memcpy((void *)ev->cr.resp_ie, resp_ie, resp_ie_len);
	}
824
825
826
	if (bss)
		cfg80211_hold_bss(bss_from_pub(bss));
	ev->cr.bss = bss;
Johannes Berg's avatar
Johannes Berg committed
827
	ev->cr.status = status;
828
	ev->cr.timeout_reason = timeout_reason;
Johannes Berg's avatar
Johannes Berg committed
829
830
831
832

	spin_lock_irqsave(&wdev->event_lock, flags);
	list_add_tail(&ev->list, &wdev->event_list);
	spin_unlock_irqrestore(&wdev->event_lock, flags);
833
	queue_work(cfg80211_wq, &rdev->event_work);
834
}
835
EXPORT_SYMBOL(cfg80211_connect_bss);
836

837
/* Consumes bss object one way or another */
838
void __cfg80211_roamed(struct wireless_dev *wdev,
839
		       struct cfg80211_bss *bss,
Johannes Berg's avatar
Johannes Berg committed
840
841
		       const u8 *req_ie, size_t req_ie_len,
		       const u8 *resp_ie, size_t resp_ie_len)
842
{
Johannes Berg's avatar
Johannes Berg committed
843
#ifdef CONFIG_CFG80211_WEXT
844
845
	union iwreq_data wrqu;
#endif
Johannes Berg's avatar
Johannes Berg committed
846
847
	ASSERT_WDEV_LOCK(wdev);

848
849
	if (WARN_ON(wdev->iftype != NL80211_IFTYPE_STATION &&
		    wdev->iftype != NL80211_IFTYPE_P2P_CLIENT))
850
		goto out;
851

852
	if (WARN_ON(!wdev->current_bss))
853
		goto out;
854
855

	cfg80211_unhold_bss(wdev->current_bss);
856
	cfg80211_put_bss(wdev->wiphy, &wdev->current_bss->pub);
857
858
	wdev->current_bss = NULL;

Johannes Berg's avatar
Johannes Berg committed
859
860
	cfg80211_hold_bss(bss_from_pub(bss));
	wdev->current_bss = bss_from_pub(bss);
861

862
863
	nl80211_send_roamed(wiphy_to_rdev(wdev->wiphy),
			    wdev->netdev, bss->bssid,
Johannes Berg's avatar
Johannes Berg committed
864
865
			    req_ie, req_ie_len, resp_ie, resp_ie_len,
			    GFP_KERNEL);
866

Johannes Berg's avatar
Johannes Berg committed
867
#ifdef CONFIG_CFG80211_WEXT
868
869
870
	if (req_ie) {
		memset(&wrqu, 0, sizeof(wrqu));
		wrqu.data.length = req_ie_len;
Zhu Yi's avatar
Zhu Yi committed
871
		wireless_send_event(wdev->netdev, IWEVASSOCREQIE,
Johannes Berg's avatar
Johannes Berg committed
872
				    &wrqu, req_ie);
873
874
875
876
877
	}

	if (resp_ie) {
		memset(&wrqu, 0, sizeof(wrqu));
		wrqu.data.length = resp_ie_len;
Johannes Berg's avatar
Johannes Berg committed
878
879
		wireless_send_event(wdev->netdev, IWEVASSOCRESPIE,
				    &wrqu, resp_ie);
880
881
882
883
	}

	memset(&wrqu, 0, sizeof(wrqu));
	wrqu.ap_addr.sa_family = ARPHRD_ETHER;
884
885
	memcpy(wrqu.ap_addr.sa_data, bss->bssid, ETH_ALEN);
	memcpy(wdev->wext.prev_bssid, bss->bssid, ETH_ALEN);
886
	wdev->wext.prev_bssid_valid = true;
Johannes Berg's avatar
Johannes Berg committed
887
	wireless_send_event(wdev->netdev, SIOCGIWAP, &wrqu, NULL);
888
#endif
889
890
891

	return;
out:
892
	cfg80211_put_bss(wdev->wiphy, bss);
893
}
Johannes Berg's avatar
Johannes Berg committed
894

895
896
897
void cfg80211_roamed(struct net_device *dev,
		     struct ieee80211_channel *channel,
		     const u8 *bssid,
Johannes Berg's avatar
Johannes Berg committed
898
899
		     const u8 *req_ie, size_t req_ie_len,
		     const u8 *resp_ie, size_t resp_ie_len, gfp_t gfp)
900
901
902
903
904
{
	struct wireless_dev *wdev = dev->ieee80211_ptr;
	struct cfg80211_bss *bss;

	bss = cfg80211_get_bss(wdev->wiphy, channel, bssid, wdev->ssid,
905
			       wdev->ssid_len,
906
			       wdev->conn_bss_type, IEEE80211_PRIVACY_ANY);
907
908
909
910
911
912
913
914
	if (WARN_ON(!bss))
		return;

	cfg80211_roamed_bss(dev, bss, req_ie, req_ie_len, resp_ie,
			    resp_ie_len, gfp);
}
EXPORT_SYMBOL(cfg80211_roamed);

915
/* Consumes bss object one way or another */
916
917
918
919
void cfg80211_roamed_bss(struct net_device *dev,
			 struct cfg80211_bss *bss, const u8 *req_ie,
			 size_t req_ie_len, const u8 *resp_ie,
			 size_t resp_ie_len, gfp_t gfp)
Johannes Berg's avatar
Johannes Berg committed
920
921
{
	struct wireless_dev *wdev = dev->ieee80211_ptr;
922
	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
Johannes Berg's avatar
Johannes Berg committed
923
924
925
	struct cfg80211_event *ev;
	unsigned long flags;

926
927
928
	if (WARN_ON(!bss))
		return;

Johannes Berg's avatar
Johannes Berg committed
929
	ev = kzalloc(sizeof(*ev) + req_ie_len + resp_ie_len, gfp);
930
	if (!ev) {
931
		cfg80211_put_bss(wdev->wiphy, bss);
Johannes Berg's avatar
Johannes Berg committed
932
		return;
933
	}
Johannes Berg's avatar
Johannes Berg committed
934
935
936
937
938
939
940
941

	ev->type = EVENT_ROAMED;
	ev->rm.req_ie = ((u8 *)ev) + sizeof(*ev);
	ev->rm.req_ie_len = req_ie_len;
	memcpy((void *)ev->rm.req_ie, req_ie, req_ie_len);
	ev->rm.resp_ie = ((u8 *)ev) + sizeof(*ev) + req_ie_len;
	ev->rm.resp_ie_len = resp_ie_len;
	memcpy((void *)ev->rm.resp_ie, resp_ie, resp_ie_len);
942
	ev->rm.bss = bss;
Johannes Berg's avatar
Johannes Berg committed
943
944
945
946

	spin_lock_irqsave(&wdev->event_lock, flags);
	list_add_tail(&ev->list, &wdev->event_list);
	spin_unlock_irqrestore(&wdev->event_lock, flags);
947
	queue_work(cfg80211_wq, &rdev->event_work);
Johannes Berg's avatar
Johannes Berg committed
948
}
949
EXPORT_SYMBOL(cfg80211_roamed_bss);
950

Johannes Berg's avatar
Johannes Berg committed
951
void __cfg80211_disconnected(struct net_device *dev, const u8 *ie,
952
			     size_t ie_len, u16 reason, bool from_ap)
953
954
{
	struct wireless_dev *wdev = dev->ieee80211_ptr;
955
	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
Johannes Berg's avatar
Johannes Berg committed
956
	int i;
Johannes Berg's avatar
Johannes Berg committed
957
#ifdef CONFIG_CFG80211_WEXT
958
959
960
	union iwreq_data wrqu;
#endif

Johannes Berg's avatar
Johannes Berg committed
961
962
	ASSERT_WDEV_LOCK(wdev);

963
964
	if (WARN_ON(wdev->iftype != NL80211_IFTYPE_STATION &&
		    wdev->iftype != NL80211_IFTYPE_P2P_CLIENT))
965
966
967
968
		return;

	if (wdev->current_bss) {
		cfg80211_unhold_bss(wdev->current_bss);
969
		cfg80211_put_bss(wdev->wiphy, &wdev->current_bss->pub);
970
971
972
	}

	wdev->current_bss = NULL;
973
	wdev->ssid_len = 0;
974
	wdev->conn_owner_nlportid = 0;
975

Johannes Berg's avatar
Johannes Berg committed
976
977
	nl80211_send_disconnected(rdev, dev, reason, ie, ie_len, from_ap);

978
979
980
981
982
983
	/* stop critical protocol if supported */
	if (rdev->ops->crit_proto_stop && rdev->crit_proto_nlportid) {
		rdev->crit_proto_nlportid = 0;
		rdev_crit_proto_stop(rdev, wdev);
	}

Johannes Berg's avatar
Johannes Berg committed
984
985
986
987
988
989
	/*
	 * Delete all the keys ... pairwise keys can't really
	 * exist any more anyway, but default keys might.
	 */
	if (rdev->ops->del_key)
		for (i = 0; i < 6; i++)
990
			rdev_del_key(rdev, dev, i, false, NULL);
991

992
993
	rdev_set_qos_map(rdev, dev, NULL);

Johannes Berg's avatar
Johannes Berg committed
994
#ifdef CONFIG_CFG80211_WEXT
995
996
997
	memset(&wrqu, 0, sizeof(wrqu));
	wrqu.ap_addr.sa_family = ARPHRD_ETHER;
	wireless_send_event(dev, SIOCGIWAP, &wrqu, NULL);
998
	wdev->wext.connect.ssid_len = 0;
999
#endif
1000
1001

	schedule_work(&cfg80211_disconnect_work);
1002
1003
1004
}

void cfg80211_disconnected(struct net_device *dev, u16 reason,
1005
1006
			   const u8 *ie, size_t ie_len,
			   bool locally_generated, gfp_t gfp)
1007
{
Johannes Berg's avatar
Johannes Berg committed
1008
	struct wireless_dev *wdev = dev->ieee80211_ptr;
1009
	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
Johannes Berg's avatar
Johannes Berg committed
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
	struct cfg80211_event *ev;
	unsigned long flags;

	ev = kzalloc(sizeof(*ev) + ie_len, gfp);
	if (!ev)
		return;

	ev->type = EVENT_DISCONNECTED;
	ev->dc.ie = ((u8 *)ev) + sizeof(*ev);
	ev->dc.ie_len = ie_len;
	memcpy((void *)ev->dc.ie, ie, ie_len);
	ev->dc.reason = reason;
1022
	ev->dc.locally_generated = locally_generated;
Johannes Berg's avatar
Johannes Berg committed
1023
1024
1025
1026

	spin_lock_irqsave(&wdev->event_lock, flags);
	list_add_tail(&ev->list, &wdev->event_list);
	spin_unlock_irqrestore(&wdev->event_lock, flags);
1027
	queue_work(cfg80211_wq, &rdev->event_work);
1028
1029
1030
}
EXPORT_SYMBOL(cfg80211_disconnected);

1031
1032
1033
/*
 * API calls for nl80211/wext compatibility code
 */
1034
1035
1036
1037
1038
int cfg80211_connect(struct cfg80211_registered_device *rdev,
		     struct net_device *dev,
		     struct cfg80211_connect_params *connect,
		     struct cfg80211_cached_keys *connkeys,
		     const u8 *prev_bssid)
1039
1040
{
	struct wireless_dev *wdev = dev->ieee80211_ptr;
Johannes Berg's avatar
Johannes Berg committed
1041
1042
1043
	int err;

	ASSERT_WDEV_LOCK(wdev);
1044

Johannes Berg's avatar
Johannes Berg committed
1045
	if (WARN_ON(wdev->connect_keys)) {
1046
		kzfree(wdev->connect_keys);
Johannes Berg's avatar
Johannes Berg committed
1047
1048
1049
		wdev->connect_keys = NULL;
	}

1050
1051
1052
	cfg80211_oper_and_ht_capa(&connect->ht_capa_mask,
				  rdev->wiphy.ht_capa_mod_mask);

Johannes Berg's avatar
Johannes Berg committed
1053
1054
	if (connkeys && connkeys->def >= 0) {
		int idx;
Samuel Ortiz's avatar
Samuel Ortiz committed
1055
		u32 cipher;
Johannes Berg's avatar
Johannes Berg committed
1056
1057

		idx = connkeys->def;
Samuel Ortiz's avatar
Samuel Ortiz committed
1058
		cipher = connkeys->params[idx].cipher;
Johannes Berg's avatar
Johannes Berg committed
1059
		/* If given a WEP key we may need it for shared key auth */
Samuel Ortiz's avatar
Samuel Ortiz committed
1060
1061
		if (cipher == WLAN_CIPHER_SUITE_WEP40 ||
		    cipher == WLAN_CIPHER_SUITE_WEP104) {
Johannes Berg's avatar
Johannes Berg committed
1062
1063
1064
			connect->key_idx = idx;
			connect->key = connkeys->params[idx].key;
			connect->key_len = connkeys->params[idx].key_len;
Samuel Ortiz's avatar
Samuel Ortiz committed
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076

			/*
			 * If ciphers are not set (e.g. when going through
			 * iwconfig), we have to set them appropriately here.
			 */
			if (connect->crypto.cipher_group == 0)
				connect->crypto.cipher_group = cipher;

			if (connect->crypto.n_ciphers_pairwise == 0) {
				connect->crypto.n_ciphers_pairwise = 1;
				connect->crypto.ciphers_pairwise[0] = cipher;
			}
Johannes Berg's avatar
Johannes Berg committed
1077
		}
1078
1079
1080

		connect->crypto.wep_keys = connkeys->params;
		connect->crypto.wep_tx_key = connkeys->def;
1081
1082
1083
	} else {
		if (WARN_ON(connkeys))
			return -EINVAL;
Johannes Berg's avatar
Johannes Berg committed
1084
1085
	}

1086
1087
1088
	wdev->connect_keys = connkeys;
	memcpy(wdev->ssid, connect->ssid, connect->ssid_len);
	wdev->ssid_len = connect->ssid_len;
Johannes Berg's avatar