1# Test cases for Multi-AP
2# Copyright (c) 2018, The Linux Foundation
3#
4# This software may be distributed under the terms of the BSD license.
5# See README for more details.
6
7import hostapd
8from wpasupplicant import WpaSupplicant
9from utils import *
10
11def test_multi_ap_association(dev, apdev):
12    """Multi-AP association in backhaul BSS"""
13    run_multi_ap_association(dev, apdev, 1)
14    dev[1].connect("multi-ap", psk="12345678", scan_freq="2412",
15                   wait_connect=False)
16    ev = dev[1].wait_event(["CTRL-EVENT-DISCONNECTED",
17                            "CTRL-EVENT-CONNECTED",
18                            "CTRL-EVENT-ASSOC-REJECT"],
19                           timeout=5)
20    dev[1].request("DISCONNECT")
21    if ev is None:
22        raise Exception("Connection result not reported")
23    if "CTRL-EVENT-ASSOC-REJECT" not in ev:
24        raise Exception("Association rejection not reported")
25    if "status_code=12" not in ev:
26        raise Exception("Unexpected association status code: " + ev)
27
28def test_multi_ap_association_shared_bss(dev, apdev):
29    """Multi-AP association in backhaul BSS (with fronthaul BSS enabled)"""
30    run_multi_ap_association(dev, apdev, 3)
31    dev[1].connect("multi-ap", psk="12345678", scan_freq="2412")
32
33def test_multi_ap_backhaul_shared_bss(dev, apdev):
34    """Multi-AP backhaul to backhaul+fronthaul BSS"""
35    hapd = run_multi_ap_association(dev, apdev, 3, wait_connect=False,
36                                    wds_sta=True)
37    ev = hapd.wait_event(["WDS-STA-INTERFACE-ADDED"], timeout=10)
38    if ev is None:
39        raise Exception("No WDS-STA-INTERFACE-ADDED event seen")
40
41def run_multi_ap_association(dev, apdev, multi_ap, wait_connect=True,
42                             wds_sta=False):
43    params = hostapd.wpa2_params(ssid="multi-ap", passphrase="12345678")
44    if multi_ap:
45        params["multi_ap"] = str(multi_ap)
46    if wds_sta:
47        params["wds_sta"] = "1"
48    hapd = hostapd.add_ap(apdev[0], params)
49
50    dev[0].connect("multi-ap", psk="12345678", scan_freq="2412",
51                   multi_ap_backhaul_sta="1", wait_connect=wait_connect)
52    return hapd
53
54def test_multi_ap_backhaul_roam_with_bridge(dev, apdev):
55    """Multi-AP backhaul BSS reassociation to another BSS with bridge"""
56    br_ifname = 'sta-br0'
57    ifname = 'wlan5'
58    try:
59        run_multi_ap_backhaul_roam_with_bridge(dev, apdev)
60    finally:
61        subprocess.call(['ip', 'link', 'set', 'dev', br_ifname, 'down'])
62        subprocess.call(['brctl', 'delif', br_ifname, ifname])
63        subprocess.call(['brctl', 'delbr', br_ifname])
64        subprocess.call(['iw', ifname, 'set', '4addr', 'off'])
65
66def run_multi_ap_backhaul_roam_with_bridge(dev, apdev):
67    br_ifname = 'sta-br0'
68    ifname = 'wlan5'
69    wpas = WpaSupplicant(global_iface='/tmp/wpas-wlan5')
70    subprocess.call(['brctl', 'addbr', br_ifname])
71    subprocess.call(['brctl', 'setfd', br_ifname, '0'])
72    subprocess.call(['ip', 'link', 'set', 'dev', br_ifname, 'up'])
73    subprocess.call(['iw', ifname, 'set', '4addr', 'on'])
74    subprocess.check_call(['brctl', 'addif', br_ifname, ifname])
75    wpas.interface_add(ifname, br_ifname=br_ifname)
76    wpas.flush_scan_cache()
77
78    params = hostapd.wpa2_params(ssid="multi-ap", passphrase="12345678")
79    params["multi_ap"] = "1"
80    hapd = hostapd.add_ap(apdev[0], params)
81
82    wpas.connect("multi-ap", psk="12345678", scan_freq="2412",
83                 multi_ap_backhaul_sta="1")
84
85    hapd2 = hostapd.add_ap(apdev[1], params)
86    bssid2 = hapd2.own_addr()
87    wpas.scan_for_bss(bssid2, freq="2412", force_scan=True)
88    wpas.roam(bssid2)
89
90def test_multi_ap_disabled_on_ap(dev, apdev):
91    """Multi-AP association attempt when disabled on AP"""
92    run_multi_ap_association(dev, apdev, 0, wait_connect=False)
93    ev = dev[0].wait_event(["CTRL-EVENT-DISCONNECTED",
94                            "CTRL-EVENT-CONNECTED"],
95                           timeout=5)
96    dev[0].request("DISCONNECT")
97    if ev is None:
98        raise Exception("Connection result not reported")
99    if "CTRL-EVENT-DISCONNECTED" not in ev:
100        raise Exception("Unexpected connection result")
101
102def test_multi_ap_fronthaul_on_ap(dev, apdev):
103    """Multi-AP association attempt when only fronthaul BSS on AP"""
104    run_multi_ap_association(dev, apdev, 2, wait_connect=False)
105    ev = dev[0].wait_event(["CTRL-EVENT-DISCONNECTED",
106                            "CTRL-EVENT-CONNECTED",
107                            "CTRL-EVENT-ASSOC-REJECT"],
108                           timeout=5)
109    dev[0].request("DISCONNECT")
110    if ev is None:
111        raise Exception("Connection result not reported")
112    if "CTRL-EVENT-DISCONNECTED" not in ev:
113        raise Exception("Unexpected connection result")
114
115def remove_apdev(dev, ifname):
116    hglobal = hostapd.HostapdGlobal()
117    hglobal.remove(ifname)
118    dev.cmd_execute(['iw', ifname, 'del'])
119
120def run_multi_ap_wps(dev, apdev, params, params_backhaul=None, add_apdev=False,
121                     run_csa=False, allow_csa_fail=False):
122    """Helper for running Multi-AP WPS tests
123
124    dev[0] does multi_ap WPS, dev[1] does normal WPS. apdev[0] is the fronthaul
125    BSS. If there is a separate backhaul BSS, it must have been set up by the
126    caller. params are the normal SSID parameters, they will be extended with
127    the WPS parameters. multi_ap_bssid must be given if it is not equal to the
128    fronthaul BSSID."""
129
130    wpas_apdev = None
131
132    if params_backhaul:
133        hapd_backhaul = hostapd.add_ap(apdev[1], params_backhaul)
134        multi_ap_bssid =  hapd_backhaul.own_addr()
135    else:
136        multi_ap_bssid = apdev[0]['bssid']
137
138    params.update({"wps_state": "2", "eap_server": "1"})
139
140    # WPS with multi-ap station dev[0]
141    hapd = hostapd.add_ap(apdev[0], params)
142    conf = hapd.request("GET_CONFIG").splitlines()
143    if "ssid=" + params['ssid'] not in conf:
144        raise Exception("GET_CONFIG did not show correct ssid entry")
145    if "multi_ap" in params and \
146       "multi_ap=" + params["multi_ap"] not in conf:
147        raise Exception("GET_CONFIG did not show correct multi_ap entry")
148    if "multi_ap_backhaul_ssid" in params and \
149       "multi_ap_backhaul_ssid=" + params["multi_ap_backhaul_ssid"].strip('"') not in conf:
150        raise Exception("GET_CONFIG did not show correct multi_ap_backhaul_ssid entry")
151    if "wpa" in params and "multi_ap_backhaul_wpa_passphrase" in params and \
152       "multi_ap_backhaul_wpa_passphrase=" + params["multi_ap_backhaul_wpa_passphrase"] not in conf:
153        raise Exception("GET_CONFIG did not show correct multi_ap_backhaul_wpa_passphrase entry")
154    if "multi_ap_backhaul_wpa_psk" in params and \
155       "multi_ap_backhaul_wpa_psk=" + params["multi_ap_backhaul_wpa_psk"] not in conf:
156        raise Exception("GET_CONFIG did not show correct multi_ap_backhaul_wpa_psk entry")
157    hapd.request("WPS_PBC")
158    if "PBC Status: Active" not in hapd.request("WPS_GET_STATUS"):
159        raise Exception("PBC status not shown correctly")
160
161    dev[0].request("WPS_PBC multi_ap=1")
162    dev[0].wait_connected(timeout=20)
163    status = dev[0].get_status()
164    if status['wpa_state'] != 'COMPLETED' or status['bssid'] != multi_ap_bssid:
165        raise Exception("Not fully connected")
166    if status['ssid'] != params['multi_ap_backhaul_ssid'].strip('"'):
167        raise Exception("Unexpected SSID %s != %s" % (status['ssid'], params["multi_ap_backhaul_ssid"]))
168    if status['pairwise_cipher'] != 'CCMP':
169        raise Exception("Unexpected encryption configuration %s" % status['pairwise_cipher'])
170    if status['key_mgmt'] != 'WPA2-PSK':
171        raise Exception("Unexpected key_mgmt")
172
173    status = hapd.request("WPS_GET_STATUS")
174    if "PBC Status: Disabled" not in status:
175        raise Exception("PBC status not shown correctly")
176    if "Last WPS result: Success" not in status:
177        raise Exception("Last WPS result not shown correctly")
178    if "Peer Address: " + dev[0].own_addr() not in status:
179        raise Exception("Peer address not shown correctly")
180
181    if len(dev[0].list_networks()) != 1:
182        raise Exception("Unexpected number of network blocks")
183
184    # WPS with non-Multi-AP station dev[1]
185    hapd.request("WPS_PBC")
186    if "PBC Status: Active" not in hapd.request("WPS_GET_STATUS"):
187        raise Exception("PBC status not shown correctly")
188
189    dev[1].request("WPS_PBC")
190    dev[1].wait_connected(timeout=20)
191    status = dev[1].get_status()
192    if status['wpa_state'] != 'COMPLETED' or status['bssid'] != apdev[0]['bssid']:
193        raise Exception("Not fully connected")
194    if status['ssid'] != params["ssid"]:
195        raise Exception("Unexpected SSID")
196    # Fronthaul may be something else than WPA2-PSK so don't test it.
197
198    status = hapd.request("WPS_GET_STATUS")
199    if "PBC Status: Disabled" not in status:
200        raise Exception("PBC status not shown correctly")
201    if "Last WPS result: Success" not in status:
202        raise Exception("Last WPS result not shown correctly")
203    if "Peer Address: " + dev[1].own_addr() not in status:
204        raise Exception("Peer address not shown correctly")
205
206    if len(dev[1].list_networks()) != 1:
207        raise Exception("Unexpected number of network blocks")
208
209    try:
210        # Add apdev to the same phy that dev[0]
211        if add_apdev:
212            wpas_apdev = {}
213            wpas_apdev['ifname'] = dev[0].ifname + "_ap"
214            status, buf = dev[0].cmd_execute(['iw', dev[0].ifname,
215                                              'interface', 'add',
216                                              wpas_apdev['ifname'],
217                                              'type', 'managed'])
218            if status != 0:
219                raise Exception("iw interface add failed")
220            wpas_hapd = hostapd.add_ap(wpas_apdev, params)
221
222        if run_csa:
223            if 'OK' not in hapd.request("CHAN_SWITCH 5 2462 ht"):
224                raise Exception("chan switch request failed")
225
226            ev = hapd.wait_event(["AP-CSA-FINISHED"], timeout=5)
227            if not ev:
228                raise Exception("chan switch failed")
229
230            # now check station
231            ev = dev[0].wait_event(["CTRL-EVENT-CHANNEL-SWITCH",
232                                    "CTRL-EVENT-DISCONNECTED"], timeout=5)
233            if not ev:
234                raise Exception("sta - no chanswitch event")
235            if "CTRL-EVENT-CHANNEL-SWITCH" not in ev and not allow_csa_fail:
236                raise Exception("Received disconnection event instead of channel switch event")
237
238        if add_apdev:
239            remove_apdev(dev[0], wpas_apdev['ifname'])
240    except:
241        if wpas_apdev:
242            remove_apdev(dev[0], wpas_apdev['ifname'])
243        raise
244
245    return hapd
246
247def test_multi_ap_wps_shared(dev, apdev):
248    """WPS on shared fronthaul/backhaul AP"""
249    ssid = "multi-ap-wps"
250    passphrase = "12345678"
251    params = hostapd.wpa2_params(ssid=ssid, passphrase=passphrase)
252    params.update({"multi_ap": "3",
253                   "multi_ap_backhaul_ssid": '"%s"' % ssid,
254                   "multi_ap_backhaul_wpa_passphrase": passphrase})
255    hapd = run_multi_ap_wps(dev, apdev, params)
256    # Verify WPS parameter update with Multi-AP
257    if "OK" not in hapd.request("RELOAD"):
258        raise Exception("hostapd RELOAD failed")
259    dev[0].wait_disconnected()
260    dev[0].request("REMOVE_NETWORK all")
261    hapd.request("WPS_PBC")
262    dev[0].request("WPS_PBC multi_ap=1")
263    dev[0].wait_connected(timeout=20)
264
265def test_multi_ap_wps_shared_csa(dev, apdev):
266    """WPS on shared fronthaul/backhaul AP, run CSA"""
267    ssid = "multi-ap-wps-csa"
268    passphrase = "12345678"
269    params = hostapd.wpa2_params(ssid=ssid, passphrase=passphrase)
270    params.update({"multi_ap": "3",
271                   "multi_ap_backhaul_ssid": '"%s"' % ssid,
272                   "multi_ap_backhaul_wpa_passphrase": passphrase})
273    run_multi_ap_wps(dev, apdev, params, run_csa=True)
274
275def test_multi_ap_wps_shared_apdev_csa(dev, apdev):
276    """WPS on shared fronthaul/backhaul AP add apdev on same phy and run CSA"""
277    ssid = "multi-ap-wps-apdev-csa"
278    passphrase = "12345678"
279    params = hostapd.wpa2_params(ssid=ssid, passphrase=passphrase)
280    params.update({"multi_ap": "3",
281                   "multi_ap_backhaul_ssid": '"%s"' % ssid,
282                   "multi_ap_backhaul_wpa_passphrase": passphrase})
283    # This case is currently failing toc omplete CSA on the station interface.
284    # For the time being, ignore that to avoid always failing tests. Full
285    # validation can be enabled once the issue behind this is fixed.
286    run_multi_ap_wps(dev, apdev, params, add_apdev=True, run_csa=True,
287                     allow_csa_fail=True)
288
289def test_multi_ap_wps_shared_psk(dev, apdev):
290    """WPS on shared fronthaul/backhaul AP using PSK"""
291    ssid = "multi-ap-wps"
292    psk = "1234567890abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
293    params = hostapd.wpa2_params(ssid=ssid)
294    params.update({"wpa_psk": psk,
295                   "multi_ap": "3",
296                   "multi_ap_backhaul_ssid": '"%s"' % ssid,
297                   "multi_ap_backhaul_wpa_psk": psk})
298    run_multi_ap_wps(dev, apdev, params)
299
300def test_multi_ap_wps_split(dev, apdev):
301    """WPS on split fronthaul and backhaul AP"""
302    backhaul_ssid = "multi-ap-backhaul-wps"
303    backhaul_passphrase = "87654321"
304    params = hostapd.wpa2_params(ssid="multi-ap-fronthaul-wps",
305                                 passphrase="12345678")
306    params.update({"multi_ap": "2",
307                   "multi_ap_backhaul_ssid": '"%s"' % backhaul_ssid,
308                   "multi_ap_backhaul_wpa_passphrase": backhaul_passphrase})
309    params_backhaul = hostapd.wpa2_params(ssid=backhaul_ssid,
310                                          passphrase=backhaul_passphrase)
311    params_backhaul.update({"multi_ap": "1"})
312
313    run_multi_ap_wps(dev, apdev, params, params_backhaul)
314
315def test_multi_ap_wps_split_psk(dev, apdev):
316    """WPS on split fronthaul and backhaul AP"""
317    backhaul_ssid = "multi-ap-backhaul-wps"
318    backhaul_psk = "1234567890abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
319    params = hostapd.wpa2_params(ssid="multi-ap-fronthaul-wps",
320                                 passphrase="12345678")
321    params.update({"multi_ap": "2",
322                   "multi_ap_backhaul_ssid": '"%s"' % backhaul_ssid,
323                   "multi_ap_backhaul_wpa_psk": backhaul_psk})
324    params_backhaul = hostapd.wpa2_params(ssid=backhaul_ssid)
325    params_backhaul.update({"multi_ap": "1", "wpa_psk": backhaul_psk})
326
327    run_multi_ap_wps(dev, apdev, params, params_backhaul)
328
329def test_multi_ap_wps_split_mixed(dev, apdev):
330    """WPS on split fronthaul and backhaul AP with mixed-mode fronthaul"""
331    skip_without_tkip(dev[0])
332    backhaul_ssid = "multi-ap-backhaul-wps"
333    backhaul_passphrase = "87654321"
334    params = hostapd.wpa_mixed_params(ssid="multi-ap-fronthaul-wps",
335                                      passphrase="12345678")
336    params.update({"multi_ap": "2",
337                   "multi_ap_backhaul_ssid": '"%s"' % backhaul_ssid,
338                   "multi_ap_backhaul_wpa_passphrase": backhaul_passphrase})
339    params_backhaul = hostapd.wpa2_params(ssid=backhaul_ssid,
340                                          passphrase=backhaul_passphrase)
341    params_backhaul.update({"multi_ap": "1"})
342
343    run_multi_ap_wps(dev, apdev, params, params_backhaul)
344
345def test_multi_ap_wps_split_open(dev, apdev):
346    """WPS on split fronthaul and backhaul AP with open fronthaul"""
347    backhaul_ssid = "multi-ap-backhaul-wps"
348    backhaul_passphrase = "87654321"
349    params = {"ssid": "multi-ap-wps-fronthaul", "multi_ap": "2",
350              "multi_ap_backhaul_ssid": '"%s"' % backhaul_ssid,
351              "multi_ap_backhaul_wpa_passphrase": backhaul_passphrase}
352    params_backhaul = hostapd.wpa2_params(ssid=backhaul_ssid,
353                                          passphrase=backhaul_passphrase)
354    params_backhaul.update({"multi_ap": "1"})
355
356    run_multi_ap_wps(dev, apdev, params, params_backhaul)
357
358def test_multi_ap_wps_fail_non_multi_ap(dev, apdev):
359    """Multi-AP WPS on non-WPS AP fails"""
360
361    params = hostapd.wpa2_params(ssid="non-multi-ap-wps", passphrase="12345678")
362    params.update({"wps_state": "2", "eap_server": "1"})
363
364    hapd = hostapd.add_ap(apdev[0], params)
365    hapd.request("WPS_PBC")
366    if "PBC Status: Active" not in hapd.request("WPS_GET_STATUS"):
367        raise Exception("PBC status not shown correctly")
368
369    dev[0].scan_for_bss(apdev[0]['bssid'], freq="2412")
370    dev[0].request("WPS_PBC %s multi_ap=1" % apdev[0]['bssid'])
371    # Since we will fail to associate and WPS doesn't even get started, there
372    # isn't much we can do except wait for timeout. For PBC, it is not possible
373    # to change the timeout from 2 minutes. Instead of waiting for the timeout,
374    # just check that WPS doesn't finish within reasonable time.
375    for i in range(2):
376        ev = dev[0].wait_event(["WPS-SUCCESS", "WPS-FAIL",
377                                "CTRL-EVENT-DISCONNECTED"], timeout=10)
378        if ev and "WPS-" in ev:
379            raise Exception("WPS operation completed: " + ev)
380    dev[0].request("WPS_CANCEL")
381