From patchwork Mon May 22 19:34:12 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andrei Otcheretianski X-Patchwork-Id: 1784709 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; spf=none (no SPF record) smtp.mailfrom=lists.infradead.org (client-ip=2607:7c80:54:3::133; helo=bombadil.infradead.org; envelope-from=hostap-bounces+incoming=patchwork.ozlabs.org@lists.infradead.org; receiver=) Authentication-Results: legolas.ozlabs.org; dkim=pass (2048-bit key; secure) header.d=lists.infradead.org header.i=@lists.infradead.org header.a=rsa-sha256 header.s=bombadil.20210309 header.b=qJHK5TLn; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=intel.com header.i=@intel.com header.a=rsa-sha256 header.s=Intel header.b=k6pUnteo; dkim-atps=neutral Received: from bombadil.infradead.org (bombadil.infradead.org [IPv6:2607:7c80:54:3::133]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-384) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4QQBMx1jX5z20Q6 for ; Tue, 23 May 2023 08:05:31 +1000 (AEST) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender: Content-Transfer-Encoding:Content-Type:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To: Message-Id:Date:Subject:Cc:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=F8dgbCw2kvZjbaXCdU56oeNC/Xfceyfty/vjRrvQneQ=; b=qJHK5TLnLSBkf7 ZxtMVYMiA6F2tQEO3KIdxErfb5GktuGPmAToqCzcH57v1VixAonb3Qhgj7uqCL+W2d7IAyAIfKHzz hP1sGaqnYBCe81h+OoN6Toc7PrqYPZ1J097XiuWG8BJGDrDu9+L5oDAF9id5HIsAOoBSHKWx5wNMN x/3jYAiWfh+QmqdfPPCgQI+TRudulb8O0nrMjq+5/dnchFFpIMsLll/o46EzbYoeLzQk6lEN9AznA dOPzKQEOMB3gQHpfjlF9siFV0u3IQ9ekOan3o1nDhnhRFo0HoVNtXeQpl48aQAmPJDmP/xMFf57Qf fgueEhJSwZsuZExOkeKw==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.96 #2 (Red Hat Linux)) id 1q1DeA-008B6t-0N; Mon, 22 May 2023 22:04:54 +0000 Received: from mga04.intel.com ([192.55.52.120]) by bombadil.infradead.org with esmtps (Exim 4.96 #2 (Red Hat Linux)) id 1q1BJo-007iR4-1e for hostap@lists.infradead.org; Mon, 22 May 2023 19:35:47 +0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1684784144; x=1716320144; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=YtvQCVJUIsmDD+a9UTP3UW7vDEeHRrL9d4Bk9JzcpyU=; b=k6pUnteo8lw+/dyAuimfahkLQhT8KPc/6S2meodD6OZyaWMzphN9Ytt/ ZFbg8MUlhk+eUdFQ8OTwpuK4lxCQVBVVThHJ4j/MQLMJkItafD0O0SH44 eseWkpgnR3f7ALG0hs4nTOUS1ZdQEgRQH+uf1Dwv0+X0XQzQfB2+OM5c3 E0z5dvQGmKK4jsQOA+C4FxYpCbYD8kfq86pJfQ0j8RdLZL2jDBtwF5DHj 4U91LZUJ+Eb13dXHi2AlliRrmjSiPwNz5N3pgPgHOZC/3ul6w3xPSZ8Mf EKEhgOxDPUTgXwMP0dRRKV/D8pcz6L0XMvJwMtjrOyr/DtuNWNry23wwc Q==; X-IronPort-AV: E=McAfee;i="6600,9927,10718"; a="351869383" X-IronPort-AV: E=Sophos;i="6.00,184,1681196400"; d="scan'208";a="351869383" Received: from orsmga004.jf.intel.com ([10.7.209.38]) by fmsmga104.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 22 May 2023 12:35:43 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=McAfee;i="6600,9927,10718"; a="827809480" X-IronPort-AV: E=Sophos;i="6.00,184,1681196400"; d="scan'208";a="827809480" Received: from ghorst-mobl3.ger.corp.intel.com (HELO aotchere-desk.intel.com) ([10.254.149.56]) by orsmga004-auth.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 22 May 2023 12:35:42 -0700 From: Andrei Otcheretianski To: hostap@lists.infradead.org Cc: Andrei Otcheretianski Subject: [PATCH v2 44/44] tests: Add basic MLD hwsim tests Date: Mon, 22 May 2023 22:34:12 +0300 Message-Id: <20230522193412.658666-45-andrei.otcheretianski@intel.com> X-Mailer: git-send-email 2.38.1 In-Reply-To: <20230522193412.658666-1-andrei.otcheretianski@intel.com> References: <20230522193412.658666-1-andrei.otcheretianski@intel.com> MIME-Version: 1.0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20230522_123544_613839_A2444140 X-CRM114-Status: GOOD ( 14.55 ) X-Spam-Score: -2.7 (--) X-Spam-Report: Spam detection software, running on the system "bombadil.infradead.org", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: Signed-off-by: Andrei Otcheretianski --- tests/hwsim/example-hostapd.config | 1 + tests/hwsim/hostapd.py | 77 +++++++- tests/hwsim/hwsim.py | 11 +- tests/hwsim/test_e [...] Content analysis details: (-2.7 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- -2.3 RCVD_IN_DNSWL_MED RBL: Sender listed at https://www.dnswl.org/, medium trust [192.55.52.120 listed in list.dnswl.org] 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record 0.0 SPF_NONE SPF: sender does not publish an SPF Record -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain -0.2 DKIMWL_WL_HIGH DKIMwl.org - High trust sender X-BeenThere: hostap@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "Hostap" Errors-To: hostap-bounces+incoming=patchwork.ozlabs.org@lists.infradead.org Signed-off-by: Andrei Otcheretianski --- tests/hwsim/example-hostapd.config | 1 + tests/hwsim/hostapd.py | 77 +++++++- tests/hwsim/hwsim.py | 11 +- tests/hwsim/test_eht.py | 293 +++++++++++++++++++++++++++++ 4 files changed, 375 insertions(+), 7 deletions(-) diff --git a/tests/hwsim/example-hostapd.config b/tests/hwsim/example-hostapd.config index 5b7130fdcd..e6f91fe388 100644 --- a/tests/hwsim/example-hostapd.config +++ b/tests/hwsim/example-hostapd.config @@ -117,3 +117,4 @@ CONFIG_DPP2=y CONFIG_WEP=y CONFIG_PASN=y CONFIG_AIRTIME_POLICY=y +CONFIG_IEEE80211BE=y diff --git a/tests/hwsim/hostapd.py b/tests/hwsim/hostapd.py index 77b210b6e6..e47aa146db 100644 --- a/tests/hwsim/hostapd.py +++ b/tests/hwsim/hostapd.py @@ -101,6 +101,11 @@ class HostapdGlobal: if not ignore_error: raise Exception("Could not add hostapd BSS") + def add_link(self, ifname, confname): + res = self.request("ADD " + ifname + " config=" + confname) + if "OK" not in res: + raise Exception("Could not add hostapd link") + def remove(self, ifname): self.request("REMOVE " + ifname, timeout=30) @@ -141,13 +146,13 @@ class HostapdGlobal: self.host.send_file(src, dst) class Hostapd: - def __init__(self, ifname, bssidx=0, hostname=None, port=8877): + def __init__(self, ifname, bssidx=0, hostname=None, ctrl=hapd_ctrl, port=8877): self.hostname = hostname self.host = remotehost.Host(hostname, ifname) self.ifname = ifname if hostname is None: - self.ctrl = wpaspy.Ctrl(os.path.join(hapd_ctrl, ifname)) - self.mon = wpaspy.Ctrl(os.path.join(hapd_ctrl, ifname)) + self.ctrl = wpaspy.Ctrl(os.path.join(ctrl, ifname)) + self.mon = wpaspy.Ctrl(os.path.join(ctrl, ifname)) self.dbg = ifname else: self.ctrl = wpaspy.Ctrl(hostname, port) @@ -156,6 +161,7 @@ class Hostapd: self.mon.attach() self.bssid = None self.bssidx = bssidx + self.mld_addr = None def cmd_execute(self, cmd_array, shell=False): if self.hostname is None: @@ -184,8 +190,15 @@ class Hostapd: self.bssid = self.get_status_field('bssid[%d]' % self.bssidx) return self.bssid + def own_mld_addr(self): + if self.mld_addr is None: + self.mld_addr = self.get_status_field('mld_addr[%d]' % self.bssidx) + return self.mld_addr + def get_addr(self, group=False): - return self.own_addr() + if self.own_mld_addr() is None: + return self.own_addr() + return self.own_mld_addr() def request(self, cmd): logger.debug(self.dbg + ": CTRL: " + cmd) @@ -682,6 +695,33 @@ def add_iface(apdev, confname): raise Exception("Could not ping hostapd") return hapd +def add_mld_link(apdev, params): + if isinstance(apdev, dict): + ifname = apdev['ifname'] + try: + hostname = apdev['hostname'] + port = apdev['port'] + logger.info("Adding link on: " + hostname + "/" + port + " ifname=" + ifname) + except: + logger.info("Adding link on: ifname=" + ifname) + hostname = None + port = 8878 + else: + ifname = apdev + logger.info("Adding link on: ifname=" + ifname) + hostname = None + port = 8878 + + hapd_global = HostapdGlobal(apdev) + confname, ctrl_iface = cfg_mld_link_file(ifname, params) + hapd_global.send_file(confname, confname) + hapd_global.add_link(ifname, confname) + port = hapd_global.get_ctrl_iface_port(ifname) + hapd = Hostapd(ifname, hostname=hostname, ctrl=ctrl_iface, port=port) + if not hapd.ping(): + raise Exception("Could not ping hostapd") + return hapd + def remove_bss(apdev, ifname=None): if ifname == None: ifname = apdev['ifname'] @@ -904,3 +944,32 @@ def cfg_file(apdev, conf, ifname=None): return fname return conf + +idx = 0 +def cfg_mld_link_file(ifname, params): + global idx + ctrl_iface="/var/run/hostapd" + conf = "link-%d.conf" % idx + + fd, fname = tempfile.mkstemp(dir='/tmp', prefix=conf + '-') + f = os.fdopen(fd, 'w') + + if idx != 0: + ctrl_iface="/var/run/hostapd_%d" % idx + + f.write("ctrl_interface=%s\n" % ctrl_iface) + f.write("driver=nl80211\n") + f.write("ieee80211n=1\n") + f.write("ieee80211ac=1\n") + f.write("ieee80211ax=1\n") + f.write("ieee80211be=1\n") + f.write("interface=%s\n" % ifname) + f.write("mld_ap=1\n") + f.write("mld_id=0\n") + + for k, v in list(params.items()): + f.write("{}={}\n".format(k,v)) + + idx = idx + 1 + + return fname, ctrl_iface \ No newline at end of file diff --git a/tests/hwsim/hwsim.py b/tests/hwsim/hwsim.py index bc8aabdd49..5b1f858c95 100644 --- a/tests/hwsim/hwsim.py +++ b/tests/hwsim/hwsim.py @@ -17,6 +17,7 @@ HWSIM_ATTR_CHANNELS = 9 HWSIM_ATTR_RADIO_ID = 10 HWSIM_ATTR_SUPPORT_P2P_DEVICE = 14 HWSIM_ATTR_USE_CHANCTX = 15 +HWSIM_ATTR_MLO_SUPPORT = 25 # the controller class class HWSimController(object): @@ -25,7 +26,7 @@ class HWSimController(object): self._fid = netlink.genl_controller.get_family_id(b'MAC80211_HWSIM') def create_radio(self, n_channels=None, use_chanctx=False, - use_p2p_device=False): + use_p2p_device=False, use_mlo=False): attrs = [] if n_channels: attrs.append(netlink.U32Attr(HWSIM_ATTR_CHANNELS, n_channels)) @@ -33,6 +34,8 @@ class HWSimController(object): attrs.append(netlink.FlagAttr(HWSIM_ATTR_USE_CHANCTX)) if use_p2p_device: attrs.append(netlink.FlagAttr(HWSIM_ATTR_SUPPORT_P2P_DEVICE)) + if use_mlo: + attrs.append(netlink.FlagAttr(HWSIM_ATTR_MLO_SUPPORT)) msg = netlink.GenlMessage(self._fid, HWSIM_CMD_CREATE_RADIO, flags=netlink.NLM_F_REQUEST | @@ -50,17 +53,19 @@ class HWSimController(object): class HWSimRadio(object): def __init__(self, n_channels=None, use_chanctx=False, - use_p2p_device=False): + use_p2p_device=False, use_mlo=False): self._controller = HWSimController() self._n_channels = n_channels self._use_chanctx = use_chanctx self._use_p2p_dev = use_p2p_device + self._use_mlo = use_mlo def __enter__(self): self._radio_id = self._controller.create_radio( n_channels=self._n_channels, use_chanctx=self._use_chanctx, - use_p2p_device=self._use_p2p_dev) + use_p2p_device=self._use_p2p_dev, + use_mlo=self._use_mlo) if self._radio_id < 0: raise Exception("Failed to create radio (err:%d)" % self._radio_id) try: diff --git a/tests/hwsim/test_eht.py b/tests/hwsim/test_eht.py index ebc846c0d4..17b52ba049 100644 --- a/tests/hwsim/test_eht.py +++ b/tests/hwsim/test_eht.py @@ -6,6 +6,57 @@ import hostapd from utils import * +from hwsim import HWSimRadio +import hwsim_utils +from wpasupplicant import WpaSupplicant +import re + +def _eht_verify_wifi_version(dev): + status = dev.get_status() + logger.info("station status: " + str(status)) + + if 'wifi_generation' not in status: + raise Exception("Missing wifi_generation information") + if status['wifi_generation'] != "7": + raise Exception("Unexpected wifi_generation value: " + status['wifi_generation']) + +def _eht_verify_status(wpas, hapd, freq, bw, is_ht=False, is_vht=False, mld=False): + status = hapd.get_status() + + logger.info("hostapd STATUS: " + str(status)) + if is_ht and status["ieee80211n"] != "1": + raise Exception("Unexpected STATUS ieee80211n value") + if is_vht and status["ieee80211ac"] != "1": + raise Exception("Unexpected STATUS ieee80211ac value") + if status["ieee80211ax"] != "1": + raise Exception("Unexpected STATUS ieee80211ax value") + if status["ieee80211be"] != "1": + raise Exception("Unexpected STATUS ieee80211be value") + + sta = hapd.get_sta(wpas.own_addr()) + logger.info("hostapd STA: " + str(sta)) + if is_ht and "[HT]" not in sta['flags']: + raise Exception("Missing STA flag: HT") + if is_vht and "[VHT]" not in sta['flags']: + raise Exception("Missing STA flag: VHT") + if "[HE]" not in sta['flags']: + raise Exception("Missing STA flag: HE") + if "[EHT]" not in sta['flags']: + raise Exception("Missing STA flag: EHT") + + sig = wpas.request("SIGNAL_POLL").splitlines() + + # TODO: with MLD connection, signal poll logic is still not implemented. + # While mac80211 maintains the station using the MLD address, the information + # is maintained in the link stations, but it is not sent to user space yet. + if not mld: + if "FREQUENCY=%s" % freq not in sig: + raise Exception("Unexpected SIGNAL_POLL value(1): " + str(sig)) + if "WIDTH=%s MHz" % bw not in sig: + raise Exception("Unexpected SIGNAL_POLL value(2): " + str(sig)) + +def _test_traffic(wpas, hapd): + hwsim_utils.test_connectivity(wpas, hapd) def test_eht_open(dev, apdev): """EHT AP with open mode configuration""" @@ -118,3 +169,245 @@ def test_eht_sae_mlo(dev, apdev): finally: dev[0].set("sae_groups", "") dev[0].set("sae_pwe", "0") + +def _eht_mld_enable_ap(iface, params): + hapd = hostapd.add_mld_link(iface, params) + hapd.enable() + + ev = hapd.wait_event(["AP-ENABLED", "AP-DISABLED"], timeout=1) + if ev is None: + raise Exception("AP startup timed out") + if "AP-ENABLED" not in ev: + raise Exception("AP startup failed") + + return hapd + +def eht_mld_ap_wpa2_params(ssid, passphrase=None, key_mgmt="WPA-PSK-SHA256", mfp="2", pwe=None, + beacon_prot="1"): + params = hostapd.wpa2_params(ssid=ssid, passphrase=passphrase, wpa_key_mgmt=key_mgmt, ieee80211w=mfp) + + params['ieee80211n'] = '1' + params['ieee80211ax'] = '1' + params['ieee80211be'] = '1' + params['channel'] = '1' + params['hw_mode'] = 'g' + params['group_mgmt_cipher'] = "AES-128-CMAC" + params['beacon_prot'] = beacon_prot + + if pwe is not None: + params['sae_pwe'] = pwe + + return params + +def test_eht_mld_discovery(dev, apdev): + """EHT MLD AP discovery""" + + with HWSimRadio(use_mlo=True) as (hapd_radio, hapd_iface), \ + HWSimRadio(use_mlo=True) as (wpas_radio, wpas_iface): + + wpas = WpaSupplicant(global_iface='/tmp/wpas-wlan5') + wpas.interface_add(wpas_iface) + + ssid = "mld_ap" + link0_params = {"ssid": ssid, + "hw_mode": "g", + "channel": "1"} + link1_params = {"ssid": ssid, + "hw_mode": "g", + "channel": "2"} + + hapd0 = _eht_mld_enable_ap(hapd_iface, link0_params) + hapd1 = _eht_mld_enable_ap(hapd_iface, link1_params) + + res = wpas.request("SCAN freq=2412,2417") + if "FAIL" in res: + raise Exception("Failed to start scan") + + ev = wpas.wait_event(["CTRL-EVENT-SCAN-STARTED"]) + if ev is None: + raise Exception("Scan did not start") + + ev = wpas.wait_event(["CTRL-EVENT-SCAN-RESULTS"]) + if ev is None: + raise Exception("Scan did not complete") + + logger.info("Scan done") + + rnr_pattern = re.compile(".*ap_info.*, mld ID=0, link ID=", re.MULTILINE) + ml_pattern = re.compile(".*multi-link:.*, MLD ID=0x0", re.MULTILINE) + + bss = wpas.request("BSS " + hapd0.own_addr()) + logger.info("BSS 0: " + str(bss)) + + if rnr_pattern.search(bss) is None: + raise Exception("RNR element not found for first link") + + if ml_pattern.search(bss) is None: + raise Exception("ML element not found for first link") + + bss = wpas.request("BSS " + hapd1.own_addr()) + logger.info("BSS 1: " + str(bss)) + + if rnr_pattern.search(bss) is None: + raise Exception("RNR element not found for second link") + + if ml_pattern.search(bss) is None: + raise Exception("ML element not found for second link") + +def _eht_mld_owe_two_links(dev, apdev): + with HWSimRadio(use_mlo=True) as (hapd0_radio, hapd0_iface), \ + HWSimRadio(use_mlo=True) as (hapd1_radio, hapd1_iface), \ + HWSimRadio(use_mlo=True) as (wpas_radio, wpas_iface): + + wpas = WpaSupplicant(global_iface='/tmp/wpas-wlan5') + wpas.interface_add(wpas_iface) + + ssid = "mld_ap_owe_two_link" + params = eht_mld_ap_wpa2_params(ssid, key_mgmt="OWE", mfp="2") + + hapd0 = _eht_mld_enable_ap(hapd0_iface, params) + + params['channel'] = '6' + + hapd1 = _eht_mld_enable_ap(hapd0_iface, params) + # check legacy client connection + dev[0].connect(ssid, scan_freq="2437", key_mgmt="OWE", ieee80211w="2") + wpas.connect(ssid, scan_freq="2412 2437", key_mgmt="OWE", ieee80211w="2") + + _eht_verify_status(wpas, hapd0, 2412, 20, is_ht=True, mld=True) + _eht_verify_wifi_version(wpas) + _test_traffic(wpas, hapd0) + _test_traffic(wpas, hapd1) + +def test_eht_mld_owe_two_links(dev, apdev): + """EHT MLD AP with MLD client OWE connection using two links""" + _eht_mld_owe_two_links(dev, apdev) + +def test_eht_mld_sae_single_link(dev, apdev): + """EHT MLD AP with MLD client SAE H2E connection using single link""" + + with HWSimRadio(use_mlo=True) as (hapd_radio, hapd_iface), \ + HWSimRadio(use_mlo=True) as (wpas_radio, wpas_iface): + wpas = WpaSupplicant(global_iface='/tmp/wpas-wlan5') + wpas.interface_add(wpas_iface) + + passphrase = 'qwertyuiop' + ssid = "mld_ap_sae_single_link" + params = eht_mld_ap_wpa2_params(ssid, passphrase, key_mgmt="SAE", mfp="2", pwe='2') + + hapd0 = _eht_mld_enable_ap(hapd_iface, params) + + wpas.set("sae_pwe", "1") + wpas.connect(ssid, sae_password=passphrase, scan_freq="2412", key_mgmt="SAE", ieee80211w="2") + + _eht_verify_status(wpas, hapd0, 2412, 20, is_ht=True, mld=True) + _eht_verify_wifi_version(wpas) + _test_traffic(wpas, hapd0) + +def _test_eht_mld_sae_two_links(dev, apdev, beacon_prot="1"): + """EHT MLD AP with MLD client SAE H2E connection using two links""" + + with HWSimRadio(use_mlo=True) as (hapd_radio, hapd_iface), \ + HWSimRadio(use_mlo=True) as (wpas_radio, wpas_iface): + + wpas = WpaSupplicant(global_iface='/tmp/wpas-wlan5') + wpas.interface_add(wpas_iface) + + passphrase = 'qwertyuiop' + ssid = "mld_ap_sae_two_link" + params = eht_mld_ap_wpa2_params(ssid, passphrase, + key_mgmt="SAE", mfp="2", pwe='1', beacon_prot=beacon_prot) + + hapd0 = _eht_mld_enable_ap(hapd_iface, params) + + params['channel'] = '6' + + hapd1 = _eht_mld_enable_ap(hapd_iface, params) + + wpas.set("sae_pwe", "1") + wpas.connect(ssid, sae_password=passphrase, scan_freq="2412 2437", + key_mgmt="SAE", ieee80211w="2", beacon_prot="1") + + _eht_verify_status(wpas, hapd0, 2412, 20, is_ht=True, mld=True) + _eht_verify_wifi_version(wpas) + _test_traffic(wpas, hapd0) + _test_traffic(wpas, hapd1) + +def test_eht_mld_sae_two_links(dev, apdev): + """EHT MLD AP with MLD client SAE H2E connection using two links""" + _test_eht_mld_sae_two_links(dev, apdev) + +def test_eht_mld_sae_two_links_no_beacon_prot(dev, apdev): + """EHT MLD AP with MLD client SAE H2E connection using two links and no beacon protection""" + _test_eht_mld_sae_two_links(dev, apdev, beacon_prot="0") + +def test_eht_mld_sae_ext_one_link(dev, apdev): + """EHT MLD AP with MLD client SAE-EXT H2E connection using single link""" + + with HWSimRadio(use_mlo=True) as (hapd_radio, hapd_iface), \ + HWSimRadio(use_mlo=True) as (wpas_radio, wpas_iface): + + wpas = WpaSupplicant(global_iface='/tmp/wpas-wlan5') + wpas.interface_add(wpas_iface) + + passphrase = 'qwertyuiop' + ssid = "mld_ap_sae_ext_single_link" + params = eht_mld_ap_wpa2_params(ssid, passphrase, key_mgmt="SAE-EXT-KEY") + + hapd0 = _eht_mld_enable_ap(hapd_iface, params) + + wpas.connect(ssid, sae_password=passphrase, scan_freq="2412", key_mgmt="SAE-EXT-KEY", + ieee80211w="2") + + _eht_verify_status(wpas, hapd0, 2412, 20, is_ht=True, mld=True) + _eht_verify_wifi_version(wpas) + _test_traffic(wpas, hapd0) + +def test_eht_mld_sae_ext_two_links(dev, apdev): + """EHT MLD AP with MLD client SAE-EXT H2E connection using two links""" + + with HWSimRadio(use_mlo=True) as (hapd_radio, hapd_iface), \ + HWSimRadio(use_mlo=True) as (wpas_radio, wpas_iface): + + wpas = WpaSupplicant(global_iface='/tmp/wpas-wlan5') + wpas.interface_add(wpas_iface) + + passphrase = 'qwertyuiop' + ssid = "mld_ap_sae_two_link" + params = eht_mld_ap_wpa2_params(ssid, passphrase, key_mgmt="SAE-EXT-KEY") + + hapd0 = _eht_mld_enable_ap(hapd_iface, params) + + params['channel'] = '6' + + hapd1 = _eht_mld_enable_ap(hapd_iface, params) + + wpas.connect(ssid, sae_password=passphrase, scan_freq="2412 2437", + key_mgmt="SAE-EXT-KEY", ieee80211w="2") + + _eht_verify_status(wpas, hapd0, 2412, 20, is_ht=True, mld=True) + _eht_verify_wifi_version(wpas) + _test_traffic(wpas, hapd0) + _test_traffic(wpas, hapd1) + +def test_eht_mld_sae_legacy_client(dev, apdev): + """EHT MLD AP with legacy client SAE H2E connection""" + + with HWSimRadio(use_mlo=True) as (hapd_radio, hapd_iface): + passphrase = 'qwertyuiop' + ssid = "mld_ap_sae_two_link" + params = eht_mld_ap_wpa2_params(ssid, passphrase, + key_mgmt="SAE", mfp="2", pwe='1') + + hapd0 = _eht_mld_enable_ap(hapd_iface, params) + + params['channel'] = '6' + + hapd1 = _eht_mld_enable_ap(hapd_iface, params) + + dev[0].set("sae_pwe", "1") + dev[0].connect(ssid, sae_password=passphrase, scan_freq="2412", + key_mgmt="SAE", ieee80211w="2", beacon_prot="1") + + _eht_verify_status(dev[0], hapd0, 2412, 20, is_ht=True) + _test_traffic(dev[0], hapd0)