blob: a4eb06142f0db46ecd392c4de4386919aeb2730f [file] [log] [blame]
developer66e89bc2024-04-23 14:50:01 +08001From c43241d046e8a6ae75549c23d470b94f16c74ca7 Mon Sep 17 00:00:00 2001
2From: Aditya Kumar Singh <quic_adisi@quicinc.com>
3Date: Thu, 28 Mar 2024 23:46:51 +0530
4Subject: [PATCH 021/104] tests: MLO: add basic cohosted MLDs functionality
5 testing
6
7Add test case to test basic cohosted MLDs functionality. Add helper
8functions to create the configuration file, start hostapd instance.
9
10Client connectivty test case will be added via a subsequent change.
11
12eht_mld_cohosted_discovery: 2 co-hosted MLDs without non-MLD RNR. Basic
13bring up and beacon, MLD RNR, scan validation.
14
15eht_mld_cohosted_discovery_with_rnr: Same like eht_mld_cohosted_discovery
16but additionally non-MLD RNR (rnr=1) is also enabled. Validate the non-MLD
17RNR as well.
18
19Signed-off-by: Aditya Kumar Singh <quic_adisi@quicinc.com>
20---
21 tests/hwsim/test_eht.py | 230 ++++++++++++++++++++++++++++++++++++++++
22 1 file changed, 230 insertions(+)
23
24diff --git a/tests/hwsim/test_eht.py b/tests/hwsim/test_eht.py
25index a012fe4e7..732406219 100644
26--- a/tests/hwsim/test_eht.py
27+++ b/tests/hwsim/test_eht.py
28@@ -15,6 +15,7 @@ from tshark import run_tshark
29 from test_gas import hs20_ap_params
30 from test_dpp import check_dpp_capab, wait_auth_success
31 from test_rrm import build_beacon_request, run_req_beacon, BeaconReport
32+import os, subprocess, time, tempfile
33
34 def eht_verify_wifi_version(dev):
35 status = dev.get_status()
36@@ -1823,3 +1824,232 @@ def test_eht_mlo_csa(dev, apdev):
37 traffic_test(wpas, hapd0)
38
39 #TODO: CSA on non-first link
40+
41+def create_base_conf_file(iface, channel, prefix='hostapd-', hw_mode='g',
42+ op_class=None):
43+ # Create configuration file and add phy characteristics
44+ fd, fname = tempfile.mkstemp(dir='/tmp',
45+ prefix=prefix + iface + "-chan-" + str(channel) + "-")
46+ f = os.fdopen(fd, 'w')
47+
48+ f.write("driver=nl80211\n")
49+ f.write("hw_mode=" + str(hw_mode) + "\n")
50+ f.write("ieee80211n=1\n")
51+ if hw_mode == 'a' and \
52+ (op_class is None or \
53+ op_class not in [131, 132, 133, 134, 135, 136, 137]):
54+ f.write("ieee80211ac=1\n")
55+ f.write("ieee80211ax=1\n")
56+ f.write("ieee80211be=1\n")
57+ f.write("channel=" + str(channel) + "\n")
58+
59+ return f, fname
60+
61+def append_bss_conf_to_file(f, ifname, params, first=False):
62+ # Add BSS specific characteristics
63+ config = "bss"
64+
65+ if first:
66+ config = "interface"
67+
68+ f.write("\n" + config + "=%s\n" % ifname)
69+
70+ for k, v in list(params.items()):
71+ f.write("{}={}\n".format(k,v))
72+
73+ f.write("mld_ap=1\n")
74+
75+def dump_config(fname):
76+ with open(fname, 'r') as f:
77+ cfg = f.read()
78+ logger.debug("hostapd config: " + str(fname) + "\n" + cfg)
79+
80+def get_config(iface, count, ssid, passphrase, channel, bssid_regex,
81+ rnr=False, debug=False):
82+ f, fname = create_base_conf_file(iface, channel=channel)
83+ hapds = []
84+
85+ for i in range(count):
86+ if i == 0:
87+ ifname = iface
88+ else:
89+ ifname = iface + "-" + str(i)
90+
91+ set_ssid = ssid + str(i)
92+ set_passphrase = passphrase + str(i)
93+ params = hostapd.wpa2_params(ssid=set_ssid, passphrase=set_passphrase,
94+ wpa_key_mgmt="SAE", ieee80211w="2")
95+ params['sae_pwe'] = "2"
96+ params['group_mgmt_cipher'] = "AES-128-CMAC"
97+ params['beacon_prot'] = "1"
98+ params["ctrl_interface"] = "/var/run/hostapd/chan_" + str(channel)
99+ params["bssid"] = bssid_regex % (i + 1)
100+
101+ if rnr:
102+ params["rnr"]="1"
103+
104+ append_bss_conf_to_file(f, ifname, params, first=(i == 0))
105+
106+ hapds.append([ifname, params["ctrl_interface"], i])
107+
108+ f.close()
109+
110+ if debug:
111+ dump_config(fname)
112+
113+ return fname, hapds
114+
115+def start_ap(prefix, configs):
116+ pid = prefix + ".hostapd.pid"
117+ configs = configs.split()
118+
119+ cmd = ['../../hostapd/hostapd', '-ddKtB', '-P', pid, '-f',
120+ prefix + ".hostapd-log"]
121+
122+ cmd = cmd + configs
123+
124+ logger.info("Starting APs")
125+ res = subprocess.check_call(cmd)
126+ if res != 0:
127+ raise Exception("Could not start hostapd: %s" % str(res))
128+
129+ # Wait for hostapd to complete initialization and daemonize.
130+ time.sleep(2)
131+
132+ if not os.path.exists(pid):
133+ raise Exception("hostapd did not create PID file.")
134+
135+def get_mld_devs(hapd_iface, count, prefix, rnr=False):
136+ fname1, hapds1 = get_config(hapd_iface, count=count, ssid="mld-",
137+ passphrase="qwertyuiop-", channel=1,
138+ bssid_regex="02:00:00:00:07:%02x",
139+ rnr=rnr, debug=True)
140+ fname2, hapds2 = get_config(hapd_iface, count=count, ssid="mld-",
141+ passphrase="qwertyuiop-", channel=6,
142+ bssid_regex="02:00:00:00:08:%02x",
143+ rnr=rnr, debug=True)
144+
145+ start_ap(prefix, fname1 + " " + fname2)
146+
147+ hapd_mld1_link0 = hostapd.Hostapd(ifname=hapds1[0][0], ctrl=hapds1[0][1],
148+ bssidx=hapds1[0][2])
149+ hapd_mld1_link1 = hostapd.Hostapd(ifname=hapds2[0][0], ctrl=hapds2[0][1],
150+ bssidx=hapds2[0][2])
151+
152+ hapd_mld2_link0 = hostapd.Hostapd(ifname=hapds1[1][0], ctrl=hapds1[1][1],
153+ bssidx=hapds1[1][2])
154+ hapd_mld2_link1 = hostapd.Hostapd(ifname=hapds2[1][0], ctrl=hapds2[1][1],
155+ bssidx=hapds2[1][2])
156+
157+ if not hapd_mld1_link0.ping():
158+ raise Exception("Could not ping hostapd")
159+
160+ if not hapd_mld1_link1.ping():
161+ raise Exception("Could not ping hostapd")
162+
163+ if not hapd_mld2_link0.ping():
164+ raise Exception("Could not ping hostapd")
165+
166+ if not hapd_mld2_link1.ping():
167+ raise Exception("Could not ping hostapd")
168+
169+ os.remove(fname1)
170+ os.remove(fname2)
171+
172+ return [hapd_mld1_link0, hapd_mld1_link1, hapd_mld2_link0, hapd_mld2_link1]
173+
174+def stop_mld_devs(hapds, pid):
175+ pid = pid + ".hostapd.pid"
176+
177+ if "OK" not in hapds[0].request("TERMINATE"):
178+ raise Exception("Failed to terminate hostapd process")
179+
180+ ev = hapds[0].wait_event(["CTRL-EVENT-TERMINATING"], timeout=15)
181+ if ev is None:
182+ raise Exception("CTRL-EVENT-TERMINATING not seen")
183+
184+ time.sleep(0.5)
185+
186+ if os.path.exists(pid):
187+ raise Exception("PID file exits after process termination")
188+
189+def eht_parse_rnr(bss, rnr=False, exp_bssid=None):
190+ partner_rnr_pattern = re.compile(".*ap_info.*, mld ID=0, link ID=",
191+ re.MULTILINE)
192+ ml_pattern = re.compile(".*multi-link:.*, MLD addr=.*", re.MULTILINE)
193+
194+ if partner_rnr_pattern.search(bss) is None:
195+ raise Exception("RNR element not found for first link of first MLD")
196+
197+ if ml_pattern.search(bss) is None:
198+ raise Exception("ML element not found for first link of first MLD")
199+
200+ if not rnr:
201+ return
202+
203+ coloc_rnr_pattern = re.compile(".*ap_info.*, mld ID=255, link ID=..",
204+ re.MULTILINE)
205+
206+ if coloc_rnr_pattern.search(bss) is None:
207+ raise Exception("RNR element not found for co-located BSS")
208+
209+ line = coloc_rnr_pattern.search(bss).group()
210+ if line.count('bssid') > 1:
211+ raise Exception("More than one BSS found for co-located RNR")
212+
213+ # Get the BSSID carried in the RNR
214+ index = line.rindex('bssid')
215+ bssid = line[index+len('bssid')+1:].split(',')[0]
216+
217+ # Get the MLD ID carried in the RNR
218+ index = line.rindex('link ID')
219+ link_id = line[index+len('link ID')+1:].split(',')[0]
220+
221+ if link_id != "15":
222+ raise Exception("Unexpected link ID for co-located BSS which is not own partner")
223+
224+ if bssid != exp_bssid:
225+ raise Exception("Unexpected BSSID for co-located BSS")
226+
227+def eht_mld_cohosted_discovery(dev, apdev, params, rnr=False):
228+ with HWSimRadio(use_mlo=True, n_channels=2) as (hapd_radio, hapd_iface), \
229+ HWSimRadio(use_mlo=True) as (wpas_radio, wpas_iface):
230+
231+ wpas = WpaSupplicant(global_iface='/tmp/wpas-wlan5')
232+ wpas.interface_add(wpas_iface)
233+
234+ hapds = get_mld_devs(hapd_iface=hapd_iface, count=2, prefix=params['prefix'],
235+ rnr=rnr)
236+
237+ # Only scan link 0
238+ res = wpas.request("SCAN freq=2412")
239+ if "FAIL" in res:
240+ raise Exception("Failed to start scan")
241+
242+ ev = wpas.wait_event(["CTRL-EVENT-SCAN-STARTED"])
243+ if ev is None:
244+ raise Exception("Scan did not start")
245+
246+ ev = wpas.wait_event(["CTRL-EVENT-SCAN-RESULTS"])
247+ if ev is None:
248+ raise Exception("Scan did not complete")
249+
250+ logger.info("Scan done")
251+
252+ bss = wpas.request("BSS " + hapds[0].own_addr())
253+ logger.info("BSS 0_0: " + str(bss))
254+ eht_parse_rnr(bss, rnr, hapds[2].own_addr())
255+
256+ bss = wpas.request("BSS " + hapds[2].own_addr())
257+ logger.info("BSS 1_0: " + str(bss))
258+ eht_parse_rnr(bss, rnr, hapds[0].own_addr())
259+
260+ stop_mld_devs(hapds, params['prefix'])
261+
262+def test_eht_mld_cohosted_discovery(dev, apdev, params):
263+ """EHT 2 AP MLDs discovery"""
264+ eht_mld_cohosted_discovery(dev, apdev, params)
265+
266+def test_eht_mld_cohosted_discovery_with_rnr(dev, apdev, params):
267+ """EHT 2 AP MLDs discovery (with co-location RNR)"""
268+ eht_mld_cohosted_discovery(dev, apdev, params, rnr=True)
269--
2702.39.2
271