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 
7 import re
8 import time
9 from remotehost import Host
10 import hostapd
11 import config
12 
13 class 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
20 def 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
30 def 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 
47 def 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 
52 def 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
57 def trace_start(hosts, setup_params):
58     for host in hosts:
59         trace_start_stop(host, setup_params, start=True)
60 
61 def trace_stop(hosts, setup_params):
62     for host in hosts:
63         trace_start_stop(host, setup_params, start=False)
64 
65 def 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
82 def perf_start(hosts, setup_params):
83     for host in hosts:
84         perf_start_stop(host, setup_params, start=True)
85 
86 def perf_stop(hosts, setup_params):
87     for host in hosts:
88         perf_start_stop(host, setup_params, start=False)
89 
90 def 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
107 def 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 
141 def 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 
175 def 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 
179 def 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 
183 def 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
251 def 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 
269 def 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 
294 def 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 
302 def 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 
313 def 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
329 def 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 
349 def 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 
362 def 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 
380 def 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 
385 def flush_arp_cache(host):
386     host.execute(["ip", "-s", "-s", "neigh", "flush", "all"])
387 
388 def 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
439 def 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 
480 def 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 
493 def 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 
537 def 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 
553 def 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 
583 def 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