1# Utils
2# Copyright (c) 2016, Tieto Corporation
3#
4# This software may be distributed under the terms of the BSD license.
5# See README for more details.
6
7import re
8import time
9from remotehost import Host
10import hostapd
11import config
12
13class TestSkip(Exception):
14    def __init__(self, reason):
15        self.reason = reason
16    def __str__(self):
17        return self.reason
18
19# get host based on name
20def get_host(devices, dev_name):
21    dev = config.get_device(devices, dev_name)
22    host = Host(host=dev['hostname'],
23                ifname=dev['ifname'],
24                port=dev['port'],
25                name=dev['name'])
26    host.dev = dev
27    return host
28
29# Run setup_hw - hardware specific
30def setup_hw_host_iface(host, iface, setup_params, force_restart=False):
31    try:
32        setup_hw = setup_params['setup_hw']
33        restart = ""
34        try:
35            if setup_params['restart_device'] == True:
36                restart = "-R"
37        except:
38            pass
39
40        if force_restart:
41            restart = "-R"
42
43        host.execute([setup_hw, "-I", iface, restart])
44    except:
45        pass
46
47def setup_hw_host(host, setup_params, force_restart=False):
48    ifaces = re.split('; | |, ', host.ifname)
49    for iface in ifaces:
50        setup_hw_host_iface(host, iface, setup_params, force_restart)
51
52def setup_hw(hosts, setup_params, force_restart=False):
53    for host in hosts:
54        setup_hw_host(host, setup_params, force_restart)
55
56# get traces - hw specific
57def trace_start(hosts, setup_params):
58    for host in hosts:
59        trace_start_stop(host, setup_params, start=True)
60
61def trace_stop(hosts, setup_params):
62    for host in hosts:
63        trace_start_stop(host, setup_params, start=False)
64
65def trace_start_stop(host, setup_params, start):
66    if setup_params['trace'] == False:
67        return
68    try:
69        start_trace = setup_params['trace_start']
70        stop_trace = setup_params['trace_stop']
71        if start:
72            cmd = start_trace
73        else:
74            cmd = stop_trace
75        trace_dir = setup_params['log_dir'] + host.ifname + "/remote_traces"
76        host.add_log(trace_dir + "/*")
77        host.execute([cmd, "-I", host.ifname, "-D", trace_dir])
78    except:
79        pass
80
81# get perf
82def perf_start(hosts, setup_params):
83    for host in hosts:
84        perf_start_stop(host, setup_params, start=True)
85
86def perf_stop(hosts, setup_params):
87    for host in hosts:
88        perf_start_stop(host, setup_params, start=False)
89
90def perf_start_stop(host, setup_params, start):
91    if setup_params['perf'] == False:
92        return
93    try:
94        perf_start = setup_params['perf_start']
95        perf_stop = setup_params['perf_stop']
96        if start:
97            cmd = perf_start
98        else:
99            cmd = perf_stop
100        perf_dir = setup_params['log_dir'] + host.ifname + "/remote_perf"
101        host.add_log(perf_dir + "/*")
102        host.execute([cmd, "-I", host.ifname, "-D", perf_dir])
103    except:
104        pass
105
106# hostapd/wpa_supplicant helpers
107def run_hostapd(host, setup_params):
108    log_file = None
109    remote_cli = False
110    try:
111        tc_name = setup_params['tc_name']
112        log_dir = setup_params['log_dir']
113        log_file = log_dir + tc_name + "_hostapd_" + host.name + "_" + host.ifname + ".log"
114        host.execute(["rm", log_file])
115        log = " -f " + log_file
116    except:
117        log = ""
118
119    if log_file:
120        host.add_log(log_file)
121    pidfile = setup_params['log_dir'] + "hostapd_" + host.ifname + "_" + setup_params['tc_name'] + ".pid"
122
123    if 'remote_cli' in setup_params:
124        remote_cli = setup_params['remote_cli']
125
126    if remote_cli:
127        ctrl_global_path = '/var/run/hapd-global' + '-' + host.ifname
128        host.global_iface = ctrl_global_path
129        host.dev['remote_cli'] = True
130        host.dev['global_ctrl_override'] = ctrl_global_path
131    else:
132        ctrl_global_path = "udp:" + host.port
133        host.global_iface = "udp"
134        host.dev['remote_cli'] = False
135
136    status, buf = host.execute([setup_params['hostapd'], "-B", "-ddt", "-g",
137                                ctrl_global_path, "-P", pidfile, log])
138    if status != 0:
139        raise Exception("Could not run hostapd: " + buf)
140
141def run_wpasupplicant(host, setup_params):
142    log_file = None
143    remote_cli = False
144    try:
145        tc_name = setup_params['tc_name']
146        log_dir = setup_params['log_dir']
147        log_file = log_dir + tc_name + "_wpa_supplicant_" + host.name + "_" + host.ifname + ".log"
148        host.execute(["rm", log_file])
149        log = " -f " + log_file
150    except:
151        log = ""
152
153    if log_file:
154        host.add_log(log_file)
155    pidfile = setup_params['log_dir'] + "wpa_supplicant_" + host.ifname + "_" + setup_params['tc_name'] + ".pid"
156
157    if 'remote_cli' in setup_params:
158        remote_cli = setup_params['remote_cli']
159
160    if remote_cli:
161        ctrl_global_path = '/var/run/wpas-global' + "-" + host.ifname
162        host.global_iface = ctrl_global_path
163        host.remote_cli = True
164
165    if not remote_cli:
166        ctrl_global_path = "udp:" + host.port
167        host.global_iface = "udp"
168        host.remote_cli = False
169
170    status, buf = host.execute([setup_params['wpa_supplicant'], "-B", "-ddt",
171                                "-g", ctrl_global_path, "-P", pidfile, log])
172    if status != 0:
173        raise Exception("Could not run wpa_supplicant: " + buf)
174
175def kill_wpasupplicant(host, setup_params):
176    pidfile = setup_params['log_dir'] + "wpa_supplicant_" + host.ifname + "_" + setup_params['tc_name'] + ".pid"
177    host.execute(["kill `cat " + pidfile + "`"])
178
179def kill_hostapd(host, setup_params):
180    pidfile = setup_params['log_dir'] + "hostapd_" + host.ifname + "_" + setup_params['tc_name'] + ".pid"
181    host.execute(["kill `cat " + pidfile + "`"])
182
183def get_ap_params(channel="1", bw="HT20", country="US", security="open", ht_capab=None, vht_capab=None):
184    ssid = "test_" + channel + "_" + security + "_" + bw
185
186    if bw == "b_only":
187        params = hostapd.b_only_params(channel, ssid, country)
188    elif bw == "g_only":
189        params = hostapd.g_only_params(channel, ssid, country)
190    elif bw == "g_only_wmm":
191        params = hostapd.g_only_params(channel, ssid, country)
192        params['wmm_enabled'] = "1"
193    elif bw == "a_only":
194        params = hostapd.a_only_params(channel, ssid, country)
195    elif bw == "a_only_wmm":
196        params = hostapd.a_only_params(channel, ssid, country)
197        params['wmm_enabled'] = "1"
198    elif bw == "HT20":
199        params = hostapd.ht20_params(channel, ssid, country)
200        if ht_capab:
201            try:
202                params['ht_capab'] = params['ht_capab'] + ht_capab
203            except:
204                params['ht_capab'] = ht_capab
205    elif bw == "HT40+":
206        params = hostapd.ht40_plus_params(channel, ssid, country)
207        if ht_capab:
208            params['ht_capab'] = params['ht_capab'] + ht_capab
209    elif bw == "HT40-":
210        params = hostapd.ht40_minus_params(channel, ssid, country)
211        if ht_capab:
212            params['ht_capab'] = params['ht_capab'] + ht_capab
213    elif bw == "VHT80":
214        params = hostapd.ht40_plus_params(channel, ssid, country)
215        if ht_capab:
216            params['ht_capab'] = params['ht_capab'] + ht_capab
217        if vht_capab:
218            try:
219                params['vht_capab'] = params['vht_capab'] + vht_capab
220            except:
221                params['vht_capab'] = vht_capab
222        params['ieee80211ac'] = "1"
223        params['vht_oper_chwidth'] = "1"
224        params['vht_oper_centr_freq_seg0_idx'] = str(int(channel) + 6)
225    else:
226        params = {}
227
228    # now setup security params
229    if security == "tkip":
230        sec_params = hostapd.wpa_params(passphrase="testtest")
231    elif security == "ccmp":
232        sec_params = hostapd.wpa2_params(passphrase="testtest")
233    elif security == "mixed":
234        sec_params = hostapd.wpa_mixed_params(passphrase="testtest")
235    elif security == "wep":
236        sec_params = {"wep_key0" : "123456789a",
237                      "wep_default_key" : "0",
238                      "auth_algs" : "1"}
239    elif security == "wep_shared":
240        sec_params = {"wep_key0" : "123456789a",
241                      "wep_default_key" : "0",
242                      "auth_algs" : "2"}
243    else:
244        sec_params = {}
245
246    params.update(sec_params)
247
248    return params
249
250# ip helpers
251def get_ipv4(client, ifname=None):
252    if ifname is None:
253        ifname = client.ifname
254    status, buf = client.execute(["ifconfig", ifname])
255    lines = buf.splitlines()
256
257    for line in lines:
258        res = line.find("inet addr:")
259        if res != -1:
260            break
261
262    if res != -1:
263        words = line.split()
264        addr = words[1].split(":")
265        return addr[1]
266
267    return "unknown"
268
269def get_ipv6(client, ifname=None):
270    res = -1
271    if ifname is None:
272        ifname = client.ifname
273    status, buf = client.execute(["ifconfig", ifname])
274    lines = buf.splitlines()
275
276    for line in lines:
277        res = line.find("Scope:Link")
278        if res == -1:
279            res = line.find("<link>")
280        if res != -1:
281            break
282
283    if res != -1:
284        words = line.split()
285        if words[0] == "inet6" and words[1] == "addr:":
286            addr_mask = words[2]
287            addr = addr_mask.split("/")
288            return addr[0]
289        if words[0] == "inet6":
290            return words[1]
291
292    return "unknown"
293
294def get_ip(client, addr_type="ipv6", iface=None):
295    if addr_type == "ipv6":
296        return get_ipv6(client, iface)
297    elif addr_type == "ipv4":
298        return get_ipv4(client, iface)
299    else:
300        return "unknown addr_type: " + addr_type
301
302def get_ipv4_addr(setup_params, number):
303    try:
304        ipv4_base = setup_params['ipv4_test_net']
305    except:
306        ipv4_base = "172.16.12.0"
307
308    parts = ipv4_base.split('.')
309    ipv4 = parts[0] + "." + parts[1] + "." + parts[2] + "." + str(number)
310
311    return ipv4
312
313def get_mac_addr(host, iface=None):
314    if iface == None:
315        iface = host.ifname
316    status, buf = host.execute(["ifconfig", iface])
317    if status != 0:
318        raise Exception("ifconfig " + iface)
319    words = buf.split()
320    found = 0
321    for word in words:
322        if found == 1:
323            return word
324        if word == "HWaddr" or word == "ether":
325            found = 1
326    raise Exception("Could not find HWaddr")
327
328# connectivity/ping helpers
329def get_ping_packet_loss(ping_res):
330    loss_line = ""
331    lines = ping_res.splitlines()
332    for line in lines:
333        if line.find("packet loss") != -1:
334            loss_line = line
335            break;
336
337    if loss_line == "":
338        return "100%"
339
340    sections = loss_line.split(",")
341
342    for section in sections:
343        if section.find("packet loss") != -1:
344            words = section.split()
345            return words[0]
346
347    return "100%"
348
349def ac_to_ping_ac(qos):
350    if qos == "be":
351        qos_param = "0x00"
352    elif qos == "bk":
353        qos_param = "0x20"
354    elif qos == "vi":
355        qos_param = "0xA0"
356    elif qos == "vo":
357        qos_param = "0xE0"
358    else:
359        qos_param = "0x00"
360    return qos_param
361
362def ping_run(host, ip, result, ifname=None, addr_type="ipv4", deadline="5", qos=None):
363    if ifname is None:
364       ifname = host.ifname
365    if addr_type == "ipv6":
366        ping = ["ping6"]
367    else:
368        ping = ["ping"]
369
370    ping = ping + ["-w", deadline, "-I", ifname]
371    if qos:
372        ping = ping + ["-Q", ac_to_ping_ac(qos)]
373    ping = ping + [ip]
374
375    flush_arp_cache(host)
376
377    thread = host.thread_run(ping, result)
378    return thread
379
380def ping_wait(host, thread, timeout=None):
381    host.thread_wait(thread, timeout)
382    if thread.is_alive():
383        raise Exception("ping thread still alive")
384
385def flush_arp_cache(host):
386    host.execute(["ip", "-s", "-s", "neigh", "flush", "all"])
387
388def check_connectivity(a, b, addr_type="ipv4", deadline="5", qos=None):
389    addr_a = get_ip(a, addr_type)
390    addr_b = get_ip(b, addr_type)
391
392    if addr_type == "ipv4":
393        ping = ["ping"]
394    else:
395        ping = ["ping6"]
396
397    ping_a_b = ping + ["-w", deadline, "-I", a.ifname]
398    ping_b_a = ping + ["-w", deadline, "-I", b.ifname]
399    if qos:
400        ping_a_b = ping_a_b + ["-Q", ac_to_ping_ac(qos)]
401        ping_b_a = ping_b_a + ["-Q", ac_to_ping_ac(qos)]
402    ping_a_b = ping_a_b + [addr_b]
403    ping_b_a = ping_b_a + [addr_a]
404
405    # Clear arp cache
406    flush_arp_cache(a)
407    flush_arp_cache(b)
408
409    status, buf = a.execute(ping_a_b)
410    if status == 2 and ping == "ping6":
411        # tentative possible for a while, try again
412        time.sleep(3)
413        status, buf = a.execute(ping_a_b)
414    if status != 0:
415        raise Exception("ping " + a.name + "/" + a.ifname + " >> " + b.name + "/" + b.ifname)
416
417    a_b = get_ping_packet_loss(buf)
418
419    # Clear arp cache
420    flush_arp_cache(a)
421    flush_arp_cache(b)
422
423    status, buf = b.execute(ping_b_a)
424    if status != 0:
425        raise Exception("ping " + b.name + "/" + b.ifname + " >> " + a.name + "/" + a.ifname)
426
427    b_a = get_ping_packet_loss(buf)
428
429    if int(a_b[:-1]) > 40:
430        raise Exception("Too high packet lost: " + a_b)
431
432    if int(b_a[:-1]) > 40:
433        raise Exception("Too high packet lost: " + b_a)
434
435    return a_b, b_a
436
437
438# iperf helpers
439def get_iperf_speed(iperf_res, pattern="Mbits/sec"):
440    lines = iperf_res.splitlines()
441    sum_line = ""
442    last_line = ""
443    count = 0
444    res = -1
445
446    # first find last SUM line
447    for line in lines:
448        res = line.find("[SUM]")
449        if res != -1:
450            sum_line = line
451
452    # next check SUM status
453    if sum_line != "":
454        words = sum_line.split()
455        for word in words:
456            res = word.find(pattern)
457            if res != -1:
458                return words[count - 1] + " " + pattern
459            count = count + 1
460
461    # no SUM - one thread - find last line
462    for line in lines:
463        res = line.find(pattern)
464        if res != -1:
465            last_line = line
466
467    if last_line == "":
468        return "0 " + pattern
469
470    count = 0
471    words = last_line.split()
472    for word in words:
473        res = word.find(pattern)
474        if res != -1:
475            return words[count - 1] + " " + pattern
476            break;
477        count = count + 1
478    return "0 " + pattern
479
480def ac_to_iperf_ac(qos):
481    if qos == "be":
482        qos_param = "0x00"
483    elif qos == "bk":
484        qos_param = "0x20"
485    elif qos == "vi":
486        qos_param = "0xA0"
487    elif qos == "vo":
488        qos_param = "0xE0"
489    else:
490        qos_param = "0x00"
491    return qos_param
492
493def iperf_run(server, client, server_ip, client_res, server_res,
494              l4="udp", bw="30M", test_time="30", parallel="5",
495              qos="be", param=" -i 5 ", ifname=None, l3="ipv4",
496              port="5001", iperf="iperf"):
497    if ifname == None:
498        ifname = client.ifname
499
500    if iperf == "iperf":
501        iperf_server = [iperf]
502    elif iperf == "iperf3":
503        iperf_server = [iperf, "-1"]
504
505    if l3 == "ipv4":
506        iperf_client = [iperf, "-c", server_ip, "-p", port]
507        iperf_server = iperf_server + ["-p", port]
508    elif l3 == "ipv6":
509        iperf_client = [iperf, "-V", "-c", server_ip  + "%" + ifname, "-p", port]
510        iperf_server = iperf_server + ["-V", "-p", port]
511    else:
512        return -1, -1
513
514    iperf_server = iperf_server + ["-s", "-f", "m", param]
515    iperf_client = iperf_client + ["-f", "m", "-t", test_time]
516
517    if parallel != "1":
518        iperf_client = iperf_client + ["-P", parallel]
519
520    if l4 == "udp":
521        if iperf != "iperf3":
522            iperf_server = iperf_server + ["-u"]
523        iperf_client = iperf_client + ["-u", "-b", bw]
524
525    if qos:
526        iperf_client = iperf_client + ["-Q", ac_to_iperf_ac(qos)]
527
528    flush_arp_cache(server)
529    flush_arp_cache(client)
530
531    server_thread = server.thread_run(iperf_server, server_res)
532    time.sleep(1)
533    client_thread = client.thread_run(iperf_client, client_res)
534
535    return server_thread, client_thread
536
537def iperf_wait(server, client, server_thread, client_thread, timeout=None, iperf="iperf"):
538    client.thread_wait(client_thread, timeout)
539    if client_thread.is_alive():
540        raise Exception("iperf client thread still alive")
541
542    server.thread_wait(server_thread, 5)
543    if server_thread.is_alive():
544        server.execute(["killall", "-s", "INT", iperf])
545        time.sleep(1)
546
547    server.thread_wait(server_thread, 5)
548    if server_thread.is_alive():
549        raise Exception("iperf server thread still alive")
550
551    return
552
553def run_tp_test(server, client, l3="ipv4", iperf="iperf", l4="tcp", test_time="10", parallel="5",
554                qos="be", bw="30M", ifname=None, port="5001"):
555    client_res = []
556    server_res = []
557
558    server_ip = get_ip(server, l3)
559    time.sleep(1)
560    server_thread, client_thread = iperf_run(server, client, server_ip, client_res, server_res,
561                                             l3=l3, iperf=iperf, l4=l4, test_time=test_time,
562                                             parallel=parallel, qos=qos, bw=bw, ifname=ifname,
563                                             port=port)
564    iperf_wait(server, client, server_thread, client_thread, iperf=iperf, timeout=int(test_time) + 10)
565
566    if client_res[0] != 0:
567        raise Exception(iperf + " client: " + client_res[1])
568    if server_res[0] != 0:
569        raise Exception(iperf + " server: " + server_res[1])
570    if client_res[1] is None:
571        raise Exception(iperf + " client result issue")
572    if server_res[1] is None:
573        raise Exception(iperf + " server result issue")
574
575    if iperf == "iperf":
576          result = server_res[1]
577    if iperf == "iperf3":
578          result = client_res[1]
579
580    speed = get_iperf_speed(result)
581    return speed
582
583def get_iperf_bw(bw, parallel, spacial_streams=2):
584    if bw == "b_only":
585        max_tp = 11
586    elif bw == "g_only" or bw == "g_only_wmm" or bw == "a_only" or bw == "a_only_wmm":
587        max_tp = 54
588    elif bw == "HT20":
589        max_tp = 72 * spacial_streams
590    elif bw == "HT40+" or bw == "HT40-":
591        max_tp = 150 * spacial_streams
592    elif bw == "VHT80":
593        max_tp = 433 * spacial_streams
594    else:
595        max_tp = 150
596
597    max_tp = 1.2 * max_tp
598
599    return str(int(max_tp/int(parallel))) + "M"
600