1# wmediumd validity checks
2# Copyright (c) 2015, Intel Deutschland GmbH
3#
4# This software may be distributed under the terms of the BSD license.
5# See README for more details.
6
7import tempfile, os, subprocess, errno, hwsim_utils, time
8from utils import HwsimSkip
9from wpasupplicant import WpaSupplicant
10from tshark import run_tshark
11from test_ap_open import _test_ap_open
12from test_scan import test_scan_only_one as _test_scan_only_one
13from test_wpas_mesh import check_mesh_support, check_mesh_group_added
14from test_wpas_mesh import check_mesh_peer_connected, add_open_mesh_network
15from test_wpas_mesh import check_mesh_group_removed
16
17class LocalVariables:
18    revs = []
19
20CFG = """
21ifaces :
22{
23    ids = ["%s", "%s"]
24    links = (
25        (0, 1, 30)
26    )
27}
28"""
29
30CFG2 = """
31ifaces :
32{
33    ids = ["%s", "%s", "%s"]
34}
35
36model:
37{
38    type = "prob"
39
40    links = (
41        (0, 1, 0.000000),
42        (0, 2, 0.000000),
43        (1, 2, 1.000000)
44    )
45}
46"""
47
48CFG3 = """
49ifaces :
50{
51    ids = ["%s", "%s", "%s", "%s", "%s"]
52}
53
54model:
55{
56    type = "prob"
57
58    default_prob = 1.0
59    links = (
60        (0, 1, 0.000000),
61        (1, 2, 0.000000),
62        (2, 3, 0.000000),
63        (3, 4, 0.000000)
64    )
65}
66"""
67
68def get_wmediumd_version():
69    if len(LocalVariables.revs) > 0:
70        return LocalVariables.revs
71
72    try:
73        verstr = subprocess.check_output(['wmediumd', '-V']).decode()
74    except OSError as e:
75        if e.errno == errno.ENOENT:
76            raise HwsimSkip('wmediumd not available')
77        raise
78
79    vernum = verstr.split(' ')[1][1:]
80    LocalVariables.revs = vernum.split('.')
81    for i in range(0, len(LocalVariables.revs)):
82        LocalVariables.revs[i] = int(LocalVariables.revs[i])
83    while len(LocalVariables.revs) < 3:
84        LocalVariables.revs += [0]
85
86    return LocalVariables.revs
87
88def require_wmediumd_version(major, minor, patch):
89    revs = get_wmediumd_version()
90    if revs[0] < major or revs[1] < minor or revs[2] < patch:
91        raise HwsimSkip('wmediumd v%s.%s.%s is too old for this test' %
92                        (revs[0], revs[1], revs[2]))
93
94def output_wmediumd_log(p, params, data):
95    log_file = open(os.path.abspath(os.path.join(params['logdir'],
96                                                 'wmediumd.log')), 'a')
97    log_file.write(data)
98    log_file.close()
99
100def start_wmediumd(fn, params):
101    try:
102        p = subprocess.Popen(['wmediumd', '-c', fn],
103                             stdout=subprocess.PIPE,
104                             stderr=subprocess.STDOUT)
105    except OSError as e:
106        if e.errno == errno.ENOENT:
107            raise HwsimSkip('wmediumd not available')
108        raise
109
110    logs = ''
111    while True:
112        line = p.stdout.readline().decode()
113        if not line:
114            output_wmediumd_log(p, params, logs)
115            raise Exception('wmediumd was terminated unexpectedly')
116        if line.find('REGISTER SENT!') > -1:
117            break
118        logs += line
119    return p
120
121def stop_wmediumd(p, params):
122    p.terminate()
123    p.wait()
124    stdoutdata, stderrdata = p.communicate()
125    output_wmediumd_log(p, params, stdoutdata.decode())
126
127def test_wmediumd_simple(dev, apdev, params):
128    """test a simple wmediumd configuration"""
129    fd, fn = tempfile.mkstemp()
130    try:
131        f = os.fdopen(fd, 'w')
132        f.write(CFG % (apdev[0]['bssid'], dev[0].own_addr()))
133        f.close()
134        p = start_wmediumd(fn, params)
135        try:
136            _test_ap_open(dev, apdev)
137        finally:
138            stop_wmediumd(p, params)
139        # test that releasing hwsim works correctly
140        _test_ap_open(dev, apdev)
141    finally:
142        os.unlink(fn)
143
144def test_wmediumd_path_simple(dev, apdev, params):
145    """test a mesh path"""
146    # 0 and 1 is connected
147    # 0 and 2 is connected
148    # 1 and 2 is not connected
149    # 1 --- 0 --- 2
150    # |           |
151    # +-----X-----+
152    # This tests if 1 and 2 can communicate each other via 0.
153    require_wmediumd_version(0, 3, 1)
154    fd, fn = tempfile.mkstemp()
155    try:
156        f = os.fdopen(fd, 'w')
157        f.write(CFG2 % (dev[0].own_addr(), dev[1].own_addr(),
158                        dev[2].own_addr()))
159        f.close()
160        p = start_wmediumd(fn, params)
161        try:
162            _test_wmediumd_path_simple(dev, apdev)
163        finally:
164            stop_wmediumd(p, params)
165    finally:
166        os.unlink(fn)
167
168def _test_wmediumd_path_simple(dev, apdev):
169    for i in range(0, 3):
170        check_mesh_support(dev[i])
171        add_open_mesh_network(dev[i], freq="2462", basic_rates="60 120 240")
172
173    # Check for mesh joined
174    for i in range(0, 3):
175        check_mesh_group_added(dev[i])
176
177        state = dev[i].get_status_field("wpa_state")
178        if state != "COMPLETED":
179            raise Exception("Unexpected wpa_state on dev" + str(i) + ": " + state)
180
181        mode = dev[i].get_status_field("mode")
182        if mode != "mesh":
183            raise Exception("Unexpected mode: " + mode)
184
185    # Check for peer connected
186    check_mesh_peer_connected(dev[0])
187    check_mesh_peer_connected(dev[0])
188    check_mesh_peer_connected(dev[1])
189    check_mesh_peer_connected(dev[2])
190
191    # Test connectivity 1->2 and 2->1
192    hwsim_utils.test_connectivity(dev[1], dev[2])
193
194    # Check mpath table on 0
195    res, data = dev[0].cmd_execute(['iw', dev[0].ifname, 'mpath', 'dump'])
196    if res != 0:
197        raise Exception("iw command failed on dev0")
198    if data.find(dev[1].own_addr() + ' ' +  dev[1].own_addr()) == -1 or \
199       data.find(dev[2].own_addr() + ' ' +  dev[2].own_addr()) == -1:
200        raise Exception("mpath not found on dev0:\n" + data)
201    if data.find(dev[0].own_addr()) > -1:
202        raise Exception("invalid mpath found on dev0:\n" + data)
203
204    # Check mpath table on 1
205    res, data = dev[1].cmd_execute(['iw', dev[1].ifname, 'mpath', 'dump'])
206    if res != 0:
207        raise Exception("iw command failed on dev1")
208    if data.find(dev[0].own_addr() + ' ' +  dev[0].own_addr()) == -1 or \
209       data.find(dev[2].own_addr() + ' ' +  dev[0].own_addr()) == -1:
210        raise Exception("mpath not found on dev1:\n" + data)
211    if data.find(dev[2].own_addr() + ' ' +  dev[2].own_addr()) > -1 or \
212       data.find(dev[1].own_addr()) > -1:
213        raise Exception("invalid mpath found on dev1:\n" + data)
214
215    # Check mpath table on 2
216    res, data = dev[2].cmd_execute(['iw', dev[2].ifname, 'mpath', 'dump'])
217    if res != 0:
218        raise Exception("iw command failed on dev2")
219    if data.find(dev[0].own_addr() + ' ' +  dev[0].own_addr()) == -1 or \
220       data.find(dev[1].own_addr() + ' ' +  dev[0].own_addr()) == -1:
221        raise Exception("mpath not found on dev2:\n" + data)
222    if data.find(dev[1].own_addr() + ' ' +  dev[1].own_addr()) > -1 or \
223       data.find(dev[2].own_addr()) > -1:
224        raise Exception("invalid mpath found on dev2:\n" + data)
225
226    # remove mesh groups
227    for i in range(0, 3):
228        dev[i].mesh_group_remove()
229        check_mesh_group_removed(dev[i])
230        dev[i].dump_monitor()
231
232def test_wmediumd_path_ttl(dev, apdev, params):
233    """Mesh path request TTL"""
234    # 0 --- 1 --- 2 --- 3 --- 4
235    # Test the TTL of mesh path request.
236    # If the TTL is shorter than path, the mesh path request should be dropped.
237    require_wmediumd_version(0, 3, 1)
238
239    local_dev = []
240    for i in range(0, 3):
241        local_dev.append(dev[i])
242
243    for i in range(5, 7):
244        wpas = WpaSupplicant(global_iface='/tmp/wpas-wlan5')
245        wpas.interface_add("wlan" + str(i))
246        check_mesh_support(wpas)
247        temp_dev = wpas.request("MESH_INTERFACE_ADD ifname=mesh" + str(i))
248        if "FAIL" in temp_dev:
249            raise Exception("MESH_INTERFACE_ADD failed")
250        local_dev.append(WpaSupplicant(ifname=temp_dev))
251
252    fd, fn = tempfile.mkstemp()
253    try:
254        f = os.fdopen(fd, 'w')
255        f.write(CFG3 % (local_dev[0].own_addr(), local_dev[1].own_addr(),
256                        local_dev[2].own_addr(), local_dev[3].own_addr(),
257                        local_dev[4].own_addr()))
258        f.close()
259        p = start_wmediumd(fn, params)
260        try:
261            _test_wmediumd_path_ttl(local_dev, True)
262            _test_wmediumd_path_ttl(local_dev, False)
263        finally:
264            stop_wmediumd(p, params)
265    finally:
266        os.unlink(fn)
267        for i in range(5, 7):
268            wpas.interface_remove("wlan" + str(i))
269
270def _test_wmediumd_path_ttl(dev, ok):
271    for i in range(0, 5):
272        check_mesh_support(dev[i])
273        add_open_mesh_network(dev[i], freq="2462", basic_rates="60 120 240")
274
275    # Check for mesh joined
276    for i in range(0, 5):
277        check_mesh_group_added(dev[i])
278
279        state = dev[i].get_status_field("wpa_state")
280        if state != "COMPLETED":
281            raise Exception("Unexpected wpa_state on dev" + str(i) + ": " + state)
282
283        mode = dev[i].get_status_field("mode")
284        if mode != "mesh":
285            raise Exception("Unexpected mode: " + mode)
286
287    # set mesh path request ttl
288    subprocess.check_call(["iw", "dev", dev[0].ifname, "set", "mesh_param",
289                           "mesh_element_ttl=" + ("4" if ok else "3")])
290
291    # Check for peer connected
292    for i in range(0, 5):
293        check_mesh_peer_connected(dev[i])
294    for i in range(1, 4):
295        check_mesh_peer_connected(dev[i])
296
297    # Test connectivity 0->4 and 0->4
298    hwsim_utils.test_connectivity(dev[0], dev[4], success_expected=ok)
299
300    # Check mpath table on 0
301    res, data = dev[0].cmd_execute(['iw', dev[0].ifname, 'mpath', 'dump'])
302    if res != 0:
303        raise Exception("iw command failed on dev0")
304    if ok:
305        if data.find(dev[1].own_addr() + ' ' +  dev[1].own_addr()) == -1 or \
306           data.find(dev[4].own_addr() + ' ' +  dev[1].own_addr()) == -1:
307            raise Exception("mpath not found on dev0:\n" + data)
308    else:
309        if data.find(dev[1].own_addr() + ' ' +  dev[1].own_addr()) == -1 or \
310           data.find(dev[4].own_addr() + ' 00:00:00:00:00:00') == -1:
311            raise Exception("mpath not found on dev0:\n" + data)
312    if data.find(dev[0].own_addr()) > -1 or \
313       data.find(dev[2].own_addr()) > -1 or \
314       data.find(dev[3].own_addr()) > -1:
315        raise Exception("invalid mpath found on dev0:\n" + data)
316
317    # remove mesh groups
318    for i in range(0, 3):
319        dev[i].mesh_group_remove()
320        check_mesh_group_removed(dev[i])
321        dev[i].dump_monitor()
322
323def test_wmediumd_path_rann(dev, apdev, params):
324    """Mesh path with RANN"""
325    # 0 and 1 is connected
326    # 0 and 2 is connected
327    # 1 and 2 is not connected
328    # 2 is mesh root and RANN enabled
329    # 1 --- 0 --- 2
330    # |           |
331    # +-----X-----+
332    # This tests if 1 and 2 can communicate each other via 0.
333    require_wmediumd_version(0, 3, 1)
334    fd, fn = tempfile.mkstemp()
335    try:
336        f = os.fdopen(fd, 'w')
337        f.write(CFG2 % (dev[0].own_addr(), dev[1].own_addr(),
338                        dev[2].own_addr()))
339        f.close()
340        p = start_wmediumd(fn, params)
341        try:
342            _test_wmediumd_path_rann(dev, apdev)
343        finally:
344            stop_wmediumd(p, params)
345    finally:
346        os.unlink(fn)
347
348    capfile = os.path.join(params['logdir'], "hwsim0.pcapng")
349
350    # check Root STA address in root announcement element
351    filt = "wlan.fc.type_subtype == 0x000d && " + \
352           "wlan_mgt.fixed.mesh_action == 0x01 && " + \
353           "wlan_mgt.tag.number == 126"
354    out = run_tshark(capfile, filt, ["wlan.rann.root_sta"])
355    if out is None:
356        raise Exception("No captured data found\n")
357    if out.find(dev[2].own_addr()) == -1 or \
358       out.find(dev[0].own_addr()) > -1 or \
359       out.find(dev[1].own_addr()) > -1:
360        raise Exception("RANN should be sent by dev2 only:\n" + out)
361
362    # check RANN interval is in range
363    filt = "wlan.sa == 02:00:00:00:02:00 && " + \
364           "wlan.fc.type_subtype == 0x000d && " + \
365           "wlan_mgt.fixed.mesh_action == 0x01 && " + \
366           "wlan_mgt.tag.number == 126"
367    out = run_tshark(capfile, filt, ["frame.time_relative"])
368    if out is None:
369        raise Exception("No captured data found\n")
370    lines = out.splitlines()
371    prev = float(lines[len(lines) - 1])
372    for i in reversed(list(range(1, len(lines) - 1))):
373        now = float(lines[i])
374        if prev - now < 1.0 or 3.0 < prev - now:
375            raise Exception("RANN interval " + str(prev - now) +
376                            "(sec) should be close to 2.0(sec)\n")
377        prev = now
378
379    # check no one uses broadcast path request
380    filt = "wlan.da == ff:ff:ff:ff:ff:ff && " + \
381           "wlan.fc.type_subtype == 0x000d && " + \
382           "wlan_mgt.fixed.mesh_action == 0x01 && " + \
383           "wlan_mgt.tag.number == 130"
384    out = run_tshark(capfile, filt, ["wlan.sa", "wlan.da"])
385    if out is None:
386        raise Exception("No captured data found\n")
387    if len(out) > 0:
388        raise Exception("invalid broadcast path requests\n" + out)
389
390def _test_wmediumd_path_rann(dev, apdev):
391    for i in range(0, 3):
392        check_mesh_support(dev[i])
393        add_open_mesh_network(dev[i], freq="2462", basic_rates="60 120 240")
394
395    # Check for mesh joined
396    for i in range(0, 3):
397        check_mesh_group_added(dev[i])
398
399        state = dev[i].get_status_field("wpa_state")
400        if state != "COMPLETED":
401            raise Exception("Unexpected wpa_state on dev" + str(i) + ": " + state)
402
403        mode = dev[i].get_status_field("mode")
404        if mode != "mesh":
405            raise Exception("Unexpected mode: " + mode)
406
407    # set node 2 as RANN supported root
408    subprocess.check_call(["iw", "dev", dev[0].ifname, "set", "mesh_param",
409                          "mesh_hwmp_rootmode=0"])
410    subprocess.check_call(["iw", "dev", dev[1].ifname, "set", "mesh_param",
411                          "mesh_hwmp_rootmode=0"])
412    subprocess.check_call(["iw", "dev", dev[2].ifname, "set", "mesh_param",
413                          "mesh_hwmp_rootmode=4"])
414    subprocess.check_call(["iw", "dev", dev[2].ifname, "set", "mesh_param",
415                          "mesh_hwmp_rann_interval=2000"])
416
417    # Check for peer connected
418    check_mesh_peer_connected(dev[0])
419    check_mesh_peer_connected(dev[0])
420    check_mesh_peer_connected(dev[1])
421    check_mesh_peer_connected(dev[2])
422
423    # Wait for RANN frame
424    time.sleep(10)
425
426    # Test connectivity 1->2 and 2->1
427    hwsim_utils.test_connectivity(dev[1], dev[2])
428
429    # Check mpath table on 0
430    res, data = dev[0].cmd_execute(['iw', dev[0].ifname, 'mpath', 'dump'])
431    if res != 0:
432        raise Exception("iw command failed on dev0")
433    if data.find(dev[1].own_addr() + ' ' +  dev[1].own_addr()) == -1 or \
434       data.find(dev[2].own_addr() + ' ' +  dev[2].own_addr()) == -1:
435        raise Exception("mpath not found on dev0:\n" + data)
436    if data.find(dev[0].own_addr()) > -1:
437        raise Exception("invalid mpath found on dev0:\n" + data)
438
439    # Check mpath table on 1
440    res, data = dev[1].cmd_execute(['iw', dev[1].ifname, 'mpath', 'dump'])
441    if res != 0:
442        raise Exception("iw command failed on dev1")
443    if data.find(dev[0].own_addr() + ' ' +  dev[0].own_addr()) == -1 or \
444       data.find(dev[2].own_addr() + ' ' +  dev[0].own_addr()) == -1:
445        raise Exception("mpath not found on dev1:\n" + data)
446    if data.find(dev[2].own_addr() + ' ' +  dev[2].own_addr()) > -1 or \
447       data.find(dev[1].own_addr()) > -1:
448        raise Exception("invalid mpath found on dev1:\n" + data)
449
450    # Check mpath table on 2
451    res, data = dev[2].cmd_execute(['iw', dev[2].ifname, 'mpath', 'dump'])
452    if res != 0:
453        raise Exception("iw command failed on dev2")
454    if data.find(dev[0].own_addr() + ' ' +  dev[0].own_addr()) == -1 or \
455       data.find(dev[1].own_addr() + ' ' +  dev[0].own_addr()) == -1:
456        raise Exception("mpath not found on dev2:\n" + data)
457    if data.find(dev[1].own_addr() + ' ' +  dev[1].own_addr()) > -1 or \
458       data.find(dev[2].own_addr()) > -1:
459        raise Exception("invalid mpath found on dev2:\n" + data)
460
461    # remove mesh groups
462    for i in range(0, 3):
463        dev[i].mesh_group_remove()
464        check_mesh_group_removed(dev[i])
465        dev[i].dump_monitor()
466
467def test_wmediumd_scan_only_one(dev, apdev, params):
468    """Test that scanning with a single active AP only returns that one (wmediund)"""
469    fd, fn = tempfile.mkstemp()
470    try:
471        f = os.fdopen(fd, 'w')
472        f.write(CFG % (apdev[0]['bssid'], dev[0].own_addr()))
473        f.close()
474        p = start_wmediumd(fn, params)
475        try:
476            _test_scan_only_one(dev, apdev)
477        finally:
478            stop_wmediumd(p, params)
479    finally:
480        os.unlink(fn)
481