static int nl80211_connect(struct sk_buff *skb, struct genl_info *info)
{
	struct cfg80211_registered_device *rdev = info->user_ptr[0];
	struct net_device *dev = info->user_ptr[1];
	struct cfg80211_connect_params connect;
	struct wiphy *wiphy;
	struct cfg80211_cached_keys *connkeys = NULL;
	int err;

	memset(&connect, 0, sizeof(connect));

	if (!info->attrs[NL80211_ATTR_SSID] ||
	    !nla_len(info->attrs[NL80211_ATTR_SSID]))
		return -EINVAL;

	if (info->attrs[NL80211_ATTR_AUTH_TYPE]) {
		connect.auth_type =
			nla_get_u32(info->attrs[NL80211_ATTR_AUTH_TYPE]);
		if (!nl80211_valid_auth_type(rdev, connect.auth_type,
					     NL80211_CMD_CONNECT))
			return -EINVAL;
	} else
		connect.auth_type = NL80211_AUTHTYPE_AUTOMATIC;

	connect.privacy = info->attrs[NL80211_ATTR_PRIVACY];

	if (info->attrs[NL80211_ATTR_WANT_1X_4WAY_HS] &&
	    !wiphy_ext_feature_isset(&rdev->wiphy,
				     NL80211_EXT_FEATURE_4WAY_HANDSHAKE_STA_1X))
		return -EINVAL;
	connect.want_1x = info->attrs[NL80211_ATTR_WANT_1X_4WAY_HS];

	err = nl80211_crypto_settings(rdev, info, &connect.crypto,
				      NL80211_MAX_NR_CIPHER_SUITES);
	if (err)
		return err;

	if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_STATION &&
	    dev->ieee80211_ptr->iftype != NL80211_IFTYPE_P2P_CLIENT)
		return -EOPNOTSUPP;

	wiphy = &rdev->wiphy;

	connect.bg_scan_period = -1;
	if (info->attrs[NL80211_ATTR_BG_SCAN_PERIOD] &&
		(wiphy->flags & WIPHY_FLAG_SUPPORTS_FW_ROAM)) {
		connect.bg_scan_period =
			nla_get_u16(info->attrs[NL80211_ATTR_BG_SCAN_PERIOD]);
	}

	if (info->attrs[NL80211_ATTR_MAC])
		connect.bssid = nla_data(info->attrs[NL80211_ATTR_MAC]);
	else if (info->attrs[NL80211_ATTR_MAC_HINT])
		connect.bssid_hint =
			nla_data(info->attrs[NL80211_ATTR_MAC_HINT]);
	connect.ssid = nla_data(info->attrs[NL80211_ATTR_SSID]);
	connect.ssid_len = nla_len(info->attrs[NL80211_ATTR_SSID]);

	if (info->attrs[NL80211_ATTR_IE]) {
		connect.ie = nla_data(info->attrs[NL80211_ATTR_IE]);
		connect.ie_len = nla_len(info->attrs[NL80211_ATTR_IE]);
	}

	if (info->attrs[NL80211_ATTR_USE_MFP]) {
		connect.mfp = nla_get_u32(info->attrs[NL80211_ATTR_USE_MFP]);
		if (connect.mfp == NL80211_MFP_OPTIONAL &&
		    !wiphy_ext_feature_isset(&rdev->wiphy,
					     NL80211_EXT_FEATURE_MFP_OPTIONAL))
			return -EOPNOTSUPP;
	} else {
		connect.mfp = NL80211_MFP_NO;
	}

	if (info->attrs[NL80211_ATTR_PREV_BSSID])
		connect.prev_bssid =
			nla_data(info->attrs[NL80211_ATTR_PREV_BSSID]);

	if (info->attrs[NL80211_ATTR_WIPHY_FREQ]) {
		connect.channel = nl80211_get_valid_chan(
			wiphy, info->attrs[NL80211_ATTR_WIPHY_FREQ]);
		if (!connect.channel)
			return -EINVAL;
	} else if (info->attrs[NL80211_ATTR_WIPHY_FREQ_HINT]) {
		connect.channel_hint = nl80211_get_valid_chan(
			wiphy, info->attrs[NL80211_ATTR_WIPHY_FREQ_HINT]);
		if (!connect.channel_hint)
			return -EINVAL;
	}

	if (info->attrs[NL80211_ATTR_WIPHY_EDMG_CHANNELS]) {
		connect.edmg.channels =
		      nla_get_u8(info->attrs[NL80211_ATTR_WIPHY_EDMG_CHANNELS]);

		if (info->attrs[NL80211_ATTR_WIPHY_EDMG_BW_CONFIG])
			connect.edmg.bw_config =
				nla_get_u8(info->attrs[NL80211_ATTR_WIPHY_EDMG_BW_CONFIG]);
	}

	if (connect.privacy && info->attrs[NL80211_ATTR_KEYS]) {
		connkeys = nl80211_parse_connkeys(rdev, info, NULL);
		if (IS_ERR(connkeys))
			return PTR_ERR(connkeys);
	}

	if (nla_get_flag(info->attrs[NL80211_ATTR_DISABLE_HT]))
		connect.flags |= ASSOC_REQ_DISABLE_HT;

	if (info->attrs[NL80211_ATTR_HT_CAPABILITY_MASK])
		memcpy(&connect.ht_capa_mask,
		       nla_data(info->attrs[NL80211_ATTR_HT_CAPABILITY_MASK]),
		       sizeof(connect.ht_capa_mask));

	if (info->attrs[NL80211_ATTR_HT_CAPABILITY]) {
		if (!info->attrs[NL80211_ATTR_HT_CAPABILITY_MASK]) {
			kzfree(connkeys);
			return -EINVAL;
		}
		memcpy(&connect.ht_capa,
		       nla_data(info->attrs[NL80211_ATTR_HT_CAPABILITY]),
		       sizeof(connect.ht_capa));
	}

	if (nla_get_flag(info->attrs[NL80211_ATTR_DISABLE_VHT]))
		connect.flags |= ASSOC_REQ_DISABLE_VHT;

	if (info->attrs[NL80211_ATTR_VHT_CAPABILITY_MASK])
		memcpy(&connect.vht_capa_mask,
		       nla_data(info->attrs[NL80211_ATTR_VHT_CAPABILITY_MASK]),
		       sizeof(connect.vht_capa_mask));

	if (info->attrs[NL80211_ATTR_VHT_CAPABILITY]) {
		if (!info->attrs[NL80211_ATTR_VHT_CAPABILITY_MASK]) {
			kzfree(connkeys);
			return -EINVAL;
		}
		memcpy(&connect.vht_capa,
		       nla_data(info->attrs[NL80211_ATTR_VHT_CAPABILITY]),
		       sizeof(connect.vht_capa));
	}

	if (nla_get_flag(info->attrs[NL80211_ATTR_USE_RRM])) {
		if (!((rdev->wiphy.features &
			NL80211_FEATURE_DS_PARAM_SET_IE_IN_PROBES) &&
		       (rdev->wiphy.features & NL80211_FEATURE_QUIET)) &&
		    !wiphy_ext_feature_isset(&rdev->wiphy,
					     NL80211_EXT_FEATURE_RRM)) {
			kzfree(connkeys);
			return -EINVAL;
		}
		connect.flags |= ASSOC_REQ_USE_RRM;
	}

	connect.pbss = nla_get_flag(info->attrs[NL80211_ATTR_PBSS]);
	if (connect.pbss && !rdev->wiphy.bands[NL80211_BAND_60GHZ]) {
		kzfree(connkeys);
		return -EOPNOTSUPP;
	}

	if (info->attrs[NL80211_ATTR_BSS_SELECT]) {
		/* bss selection makes no sense if bssid is set */
		if (connect.bssid) {
			kzfree(connkeys);
			return -EINVAL;
		}

		err = parse_bss_select(info->attrs[NL80211_ATTR_BSS_SELECT],
				       wiphy, &connect.bss_select);
		if (err) {
			kzfree(connkeys);
			return err;
		}
	}

	if (wiphy_ext_feature_isset(&rdev->wiphy,
				    NL80211_EXT_FEATURE_FILS_SK_OFFLOAD) &&
	    info->attrs[NL80211_ATTR_FILS_ERP_USERNAME] &&
	    info->attrs[NL80211_ATTR_FILS_ERP_REALM] &&
	    info->attrs[NL80211_ATTR_FILS_ERP_NEXT_SEQ_NUM] &&
	    info->attrs[NL80211_ATTR_FILS_ERP_RRK]) {
		connect.fils_erp_username =
			nla_data(info->attrs[NL80211_ATTR_FILS_ERP_USERNAME]);
		connect.fils_erp_username_len =
			nla_len(info->attrs[NL80211_ATTR_FILS_ERP_USERNAME]);
		connect.fils_erp_realm =
			nla_data(info->attrs[NL80211_ATTR_FILS_ERP_REALM]);
		connect.fils_erp_realm_len =
			nla_len(info->attrs[NL80211_ATTR_FILS_ERP_REALM]);
		connect.fils_erp_next_seq_num =
			nla_get_u16(
			   info->attrs[NL80211_ATTR_FILS_ERP_NEXT_SEQ_NUM]);
		connect.fils_erp_rrk =
			nla_data(info->attrs[NL80211_ATTR_FILS_ERP_RRK]);
		connect.fils_erp_rrk_len =
			nla_len(info->attrs[NL80211_ATTR_FILS_ERP_RRK]);
	} else if (info->attrs[NL80211_ATTR_FILS_ERP_USERNAME] ||
		   info->attrs[NL80211_ATTR_FILS_ERP_REALM] ||
		   info->attrs[NL80211_ATTR_FILS_ERP_NEXT_SEQ_NUM] ||
		   info->attrs[NL80211_ATTR_FILS_ERP_RRK]) {
		kzfree(connkeys);
		return -EINVAL;
	}

	if (nla_get_flag(info->attrs[NL80211_ATTR_EXTERNAL_AUTH_SUPPORT])) {
		if (!info->attrs[NL80211_ATTR_SOCKET_OWNER]) {
			kzfree(connkeys);
			GENL_SET_ERR_MSG(info,
					 "external auth requires connection ownership");
			return -EINVAL;
		}
		connect.flags |= CONNECT_REQ_EXTERNAL_AUTH_SUPPORT;
	}

	wdev_lock(dev->ieee80211_ptr);

	err = cfg80211_connect(rdev, dev, &connect, connkeys,
			       connect.prev_bssid);
	if (err)
		kzfree(connkeys);

	if (!err && info->attrs[NL80211_ATTR_SOCKET_OWNER]) {
		dev->ieee80211_ptr->conn_owner_nlportid = info->snd_portid;
		if (connect.bssid)
			memcpy(dev->ieee80211_ptr->disconnect_bssid,
			       connect.bssid, ETH_ALEN);
		else
			memset(dev->ieee80211_ptr->disconnect_bssid,
			       0, ETH_ALEN);
	}

	wdev_unlock(dev->ieee80211_ptr);

	return err;
}