]> git.ipfire.org Git - thirdparty/hostap.git/blob - tests/hwsim/hostapd.py
tests: Wait for AP-STA-CONNECT before running connectivity test
[thirdparty/hostap.git] / tests / hwsim / hostapd.py
1 # Python class for controlling hostapd
2 # Copyright (c) 2013-2019, Jouni Malinen <j@w1.fi>
3 #
4 # This software may be distributed under the terms of the BSD license.
5 # See README for more details.
6
7 import os
8 import time
9 import logging
10 import binascii
11 import struct
12 import wpaspy
13 import remotehost
14 import utils
15 import subprocess
16
17 logger = logging.getLogger()
18 hapd_ctrl = '/var/run/hostapd'
19 hapd_global = '/var/run/hostapd-global'
20
21 def mac2tuple(mac):
22 return struct.unpack('6B', binascii.unhexlify(mac.replace(':', '')))
23
24 class HostapdGlobal:
25 def __init__(self, apdev=None, global_ctrl_override=None):
26 try:
27 hostname = apdev['hostname']
28 port = apdev['port']
29 except:
30 hostname = None
31 port = 8878
32 self.host = remotehost.Host(hostname)
33 self.hostname = hostname
34 self.port = port
35 if hostname is None:
36 global_ctrl = hapd_global
37 if global_ctrl_override:
38 global_ctrl = global_ctrl_override
39 self.ctrl = wpaspy.Ctrl(global_ctrl)
40 self.mon = wpaspy.Ctrl(global_ctrl)
41 self.dbg = ""
42 else:
43 self.ctrl = wpaspy.Ctrl(hostname, port)
44 self.mon = wpaspy.Ctrl(hostname, port)
45 self.dbg = hostname + "/" + str(port)
46 self.mon.attach()
47
48 def cmd_execute(self, cmd_array, shell=False):
49 if self.hostname is None:
50 if shell:
51 cmd = ' '.join(cmd_array)
52 else:
53 cmd = cmd_array
54 proc = subprocess.Popen(cmd, stderr=subprocess.STDOUT,
55 stdout=subprocess.PIPE, shell=shell)
56 out = proc.communicate()[0]
57 ret = proc.returncode
58 return ret, out.decode()
59 else:
60 return self.host.execute(cmd_array)
61
62 def request(self, cmd, timeout=10):
63 logger.debug(self.dbg + ": CTRL(global): " + cmd)
64 return self.ctrl.request(cmd, timeout)
65
66 def wait_event(self, events, timeout):
67 start = os.times()[4]
68 while True:
69 while self.mon.pending():
70 ev = self.mon.recv()
71 logger.debug(self.dbg + "(global): " + ev)
72 for event in events:
73 if event in ev:
74 return ev
75 now = os.times()[4]
76 remaining = start + timeout - now
77 if remaining <= 0:
78 break
79 if not self.mon.pending(timeout=remaining):
80 break
81 return None
82
83 def add(self, ifname, driver=None):
84 cmd = "ADD " + ifname + " " + hapd_ctrl
85 if driver:
86 cmd += " " + driver
87 res = self.request(cmd)
88 if "OK" not in res:
89 raise Exception("Could not add hostapd interface " + ifname)
90
91 def add_iface(self, ifname, confname):
92 res = self.request("ADD " + ifname + " config=" + confname)
93 if "OK" not in res:
94 raise Exception("Could not add hostapd interface")
95
96 def add_bss(self, phy, confname, ignore_error=False):
97 res = self.request("ADD bss_config=" + phy + ":" + confname)
98 if "OK" not in res:
99 if not ignore_error:
100 raise Exception("Could not add hostapd BSS")
101
102 def remove(self, ifname):
103 self.request("REMOVE " + ifname, timeout=30)
104
105 def relog(self):
106 self.request("RELOG")
107
108 def flush(self):
109 self.request("FLUSH")
110
111 def get_ctrl_iface_port(self, ifname):
112 if self.hostname is None:
113 return None
114
115 res = self.request("INTERFACES ctrl")
116 lines = res.splitlines()
117 found = False
118 for line in lines:
119 words = line.split()
120 if words[0] == ifname:
121 found = True
122 break
123 if not found:
124 raise Exception("Could not find UDP port for " + ifname)
125 res = line.find("ctrl_iface=udp:")
126 if res == -1:
127 raise Exception("Wrong ctrl_interface format")
128 words = line.split(":")
129 return int(words[1])
130
131 def terminate(self):
132 self.mon.detach()
133 self.mon.close()
134 self.mon = None
135 self.ctrl.terminate()
136 self.ctrl = None
137
138 class Hostapd:
139 def __init__(self, ifname, bssidx=0, hostname=None, port=8877):
140 self.hostname = hostname
141 self.host = remotehost.Host(hostname, ifname)
142 self.ifname = ifname
143 if hostname is None:
144 self.ctrl = wpaspy.Ctrl(os.path.join(hapd_ctrl, ifname))
145 self.mon = wpaspy.Ctrl(os.path.join(hapd_ctrl, ifname))
146 self.dbg = ifname
147 else:
148 self.ctrl = wpaspy.Ctrl(hostname, port)
149 self.mon = wpaspy.Ctrl(hostname, port)
150 self.dbg = hostname + "/" + ifname
151 self.mon.attach()
152 self.bssid = None
153 self.bssidx = bssidx
154
155 def cmd_execute(self, cmd_array, shell=False):
156 if self.hostname is None:
157 if shell:
158 cmd = ' '.join(cmd_array)
159 else:
160 cmd = cmd_array
161 proc = subprocess.Popen(cmd, stderr=subprocess.STDOUT,
162 stdout=subprocess.PIPE, shell=shell)
163 out = proc.communicate()[0]
164 ret = proc.returncode
165 return ret, out.decode()
166 else:
167 return self.host.execute(cmd_array)
168
169 def close_ctrl(self):
170 if self.mon is not None:
171 self.mon.detach()
172 self.mon.close()
173 self.mon = None
174 self.ctrl.close()
175 self.ctrl = None
176
177 def own_addr(self):
178 if self.bssid is None:
179 self.bssid = self.get_status_field('bssid[%d]' % self.bssidx)
180 return self.bssid
181
182 def request(self, cmd):
183 logger.debug(self.dbg + ": CTRL: " + cmd)
184 return self.ctrl.request(cmd)
185
186 def ping(self):
187 return "PONG" in self.request("PING")
188
189 def set(self, field, value):
190 if "OK" not in self.request("SET " + field + " " + value):
191 raise Exception("Failed to set hostapd parameter " + field)
192
193 def set_defaults(self):
194 self.set("driver", "nl80211")
195 self.set("hw_mode", "g")
196 self.set("channel", "1")
197 self.set("ieee80211n", "1")
198 self.set("logger_stdout", "-1")
199 self.set("logger_stdout_level", "0")
200
201 def set_open(self, ssid):
202 self.set_defaults()
203 self.set("ssid", ssid)
204
205 def set_wpa2_psk(self, ssid, passphrase):
206 self.set_defaults()
207 self.set("ssid", ssid)
208 self.set("wpa_passphrase", passphrase)
209 self.set("wpa", "2")
210 self.set("wpa_key_mgmt", "WPA-PSK")
211 self.set("rsn_pairwise", "CCMP")
212
213 def set_wpa_psk(self, ssid, passphrase):
214 self.set_defaults()
215 self.set("ssid", ssid)
216 self.set("wpa_passphrase", passphrase)
217 self.set("wpa", "1")
218 self.set("wpa_key_mgmt", "WPA-PSK")
219 self.set("wpa_pairwise", "TKIP")
220
221 def set_wpa_psk_mixed(self, ssid, passphrase):
222 self.set_defaults()
223 self.set("ssid", ssid)
224 self.set("wpa_passphrase", passphrase)
225 self.set("wpa", "3")
226 self.set("wpa_key_mgmt", "WPA-PSK")
227 self.set("wpa_pairwise", "TKIP")
228 self.set("rsn_pairwise", "CCMP")
229
230 def set_wep(self, ssid, key):
231 self.set_defaults()
232 self.set("ssid", ssid)
233 self.set("wep_key0", key)
234
235 def enable(self):
236 if "OK" not in self.request("ENABLE"):
237 raise Exception("Failed to enable hostapd interface " + self.ifname)
238
239 def disable(self):
240 if "OK" not in self.request("DISABLE"):
241 raise Exception("Failed to disable hostapd interface " + self.ifname)
242
243 def dump_monitor(self):
244 while self.mon.pending():
245 ev = self.mon.recv()
246 logger.debug(self.dbg + ": " + ev)
247
248 def wait_event(self, events, timeout):
249 start = os.times()[4]
250 while True:
251 while self.mon.pending():
252 ev = self.mon.recv()
253 logger.debug(self.dbg + ": " + ev)
254 for event in events:
255 if event in ev:
256 return ev
257 now = os.times()[4]
258 remaining = start + timeout - now
259 if remaining <= 0:
260 break
261 if not self.mon.pending(timeout=remaining):
262 break
263 return None
264
265 def wait_sta(self, addr=None, timeout=2):
266 ev = self.wait_event("AP-STA-CONNECT", timeout=timeout)
267 if ev is None:
268 raise Exception("AP did not report STA connection")
269 if addr and addr not in ev:
270 raise Exception("Unexpected STA address in connection event: " + ev)
271
272 def get_status(self):
273 res = self.request("STATUS")
274 lines = res.splitlines()
275 vals = dict()
276 for l in lines:
277 [name, value] = l.split('=', 1)
278 vals[name] = value
279 return vals
280
281 def get_status_field(self, field):
282 vals = self.get_status()
283 if field in vals:
284 return vals[field]
285 return None
286
287 def get_driver_status(self):
288 res = self.request("STATUS-DRIVER")
289 lines = res.splitlines()
290 vals = dict()
291 for l in lines:
292 [name, value] = l.split('=', 1)
293 vals[name] = value
294 return vals
295
296 def get_driver_status_field(self, field):
297 vals = self.get_driver_status()
298 if field in vals:
299 return vals[field]
300 return None
301
302 def get_config(self):
303 res = self.request("GET_CONFIG")
304 lines = res.splitlines()
305 vals = dict()
306 for l in lines:
307 [name, value] = l.split('=', 1)
308 vals[name] = value
309 return vals
310
311 def mgmt_rx(self, timeout=5):
312 ev = self.wait_event(["MGMT-RX"], timeout=timeout)
313 if ev is None:
314 return None
315 msg = {}
316 frame = binascii.unhexlify(ev.split(' ')[1])
317 msg['frame'] = frame
318
319 hdr = struct.unpack('<HH6B6B6BH', frame[0:24])
320 msg['fc'] = hdr[0]
321 msg['subtype'] = (hdr[0] >> 4) & 0xf
322 hdr = hdr[1:]
323 msg['duration'] = hdr[0]
324 hdr = hdr[1:]
325 msg['da'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6]
326 hdr = hdr[6:]
327 msg['sa'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6]
328 hdr = hdr[6:]
329 msg['bssid'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6]
330 hdr = hdr[6:]
331 msg['seq_ctrl'] = hdr[0]
332 msg['payload'] = frame[24:]
333
334 return msg
335
336 def mgmt_tx(self, msg):
337 t = (msg['fc'], 0) + mac2tuple(msg['da']) + mac2tuple(msg['sa']) + mac2tuple(msg['bssid']) + (0,)
338 hdr = struct.pack('<HH6B6B6BH', *t)
339 res = self.request("MGMT_TX " + binascii.hexlify(hdr + msg['payload']).decode())
340 if "OK" not in res:
341 raise Exception("MGMT_TX command to hostapd failed")
342
343 def get_sta(self, addr, info=None, next=False):
344 cmd = "STA-NEXT " if next else "STA "
345 if addr is None:
346 res = self.request("STA-FIRST")
347 elif info:
348 res = self.request(cmd + addr + " " + info)
349 else:
350 res = self.request(cmd + addr)
351 lines = res.splitlines()
352 vals = dict()
353 first = True
354 for l in lines:
355 if first and '=' not in l:
356 vals['addr'] = l
357 first = False
358 else:
359 [name, value] = l.split('=', 1)
360 vals[name] = value
361 return vals
362
363 def get_mib(self, param=None):
364 if param:
365 res = self.request("MIB " + param)
366 else:
367 res = self.request("MIB")
368 lines = res.splitlines()
369 vals = dict()
370 for l in lines:
371 name_val = l.split('=', 1)
372 if len(name_val) > 1:
373 vals[name_val[0]] = name_val[1]
374 return vals
375
376 def get_pmksa(self, addr):
377 res = self.request("PMKSA")
378 lines = res.splitlines()
379 for l in lines:
380 if addr not in l:
381 continue
382 vals = dict()
383 [index, aa, pmkid, expiration, opportunistic] = l.split(' ')
384 vals['index'] = index
385 vals['pmkid'] = pmkid
386 vals['expiration'] = expiration
387 vals['opportunistic'] = opportunistic
388 return vals
389 return None
390
391 def dpp_qr_code(self, uri):
392 res = self.request("DPP_QR_CODE " + uri)
393 if "FAIL" in res:
394 raise Exception("Failed to parse QR Code URI")
395 return int(res)
396
397 def dpp_bootstrap_gen(self, type="qrcode", chan=None, mac=None, info=None,
398 curve=None, key=None):
399 cmd = "DPP_BOOTSTRAP_GEN type=" + type
400 if chan:
401 cmd += " chan=" + chan
402 if mac:
403 if mac is True:
404 mac = self.own_addr()
405 cmd += " mac=" + mac.replace(':', '')
406 if info:
407 cmd += " info=" + info
408 if curve:
409 cmd += " curve=" + curve
410 if key:
411 cmd += " key=" + key
412 res = self.request(cmd)
413 if "FAIL" in res:
414 raise Exception("Failed to generate bootstrapping info")
415 return int(res)
416
417 def dpp_listen(self, freq, netrole=None, qr=None, role=None):
418 cmd = "DPP_LISTEN " + str(freq)
419 if netrole:
420 cmd += " netrole=" + netrole
421 if qr:
422 cmd += " qr=" + qr
423 if role:
424 cmd += " role=" + role
425 if "OK" not in self.request(cmd):
426 raise Exception("Failed to start listen operation")
427
428 def dpp_auth_init(self, peer=None, uri=None, conf=None, configurator=None,
429 extra=None, own=None, role=None, neg_freq=None,
430 ssid=None, passphrase=None, expect_fail=False):
431 cmd = "DPP_AUTH_INIT"
432 if peer is None:
433 peer = self.dpp_qr_code(uri)
434 cmd += " peer=%d" % peer
435 if own is not None:
436 cmd += " own=%d" % own
437 if role:
438 cmd += " role=" + role
439 if extra:
440 cmd += " " + extra
441 if conf:
442 cmd += " conf=" + conf
443 if configurator is not None:
444 cmd += " configurator=%d" % configurator
445 if neg_freq:
446 cmd += " neg_freq=%d" % neg_freq
447 if ssid:
448 cmd += " ssid=" + binascii.hexlify(ssid.encode()).decode()
449 if passphrase:
450 cmd += " pass=" + binascii.hexlify(passphrase.encode()).decode()
451 res = self.request(cmd)
452 if expect_fail:
453 if "FAIL" not in res:
454 raise Exception("DPP authentication started unexpectedly")
455 return
456 if "OK" not in res:
457 raise Exception("Failed to initiate DPP Authentication")
458
459 def dpp_pkex_init(self, identifier, code, role=None, key=None, curve=None,
460 extra=None, use_id=None):
461 if use_id is None:
462 id1 = self.dpp_bootstrap_gen(type="pkex", key=key, curve=curve)
463 else:
464 id1 = use_id
465 cmd = "own=%d " % id1
466 if identifier:
467 cmd += "identifier=%s " % identifier
468 cmd += "init=1 "
469 if role:
470 cmd += "role=%s " % role
471 if extra:
472 cmd += extra + " "
473 cmd += "code=%s" % code
474 res = self.request("DPP_PKEX_ADD " + cmd)
475 if "FAIL" in res:
476 raise Exception("Failed to set PKEX data (initiator)")
477 return id1
478
479 def dpp_pkex_resp(self, freq, identifier, code, key=None, curve=None,
480 listen_role=None):
481 id0 = self.dpp_bootstrap_gen(type="pkex", key=key, curve=curve)
482 cmd = "own=%d " % id0
483 if identifier:
484 cmd += "identifier=%s " % identifier
485 cmd += "code=%s" % code
486 res = self.request("DPP_PKEX_ADD " + cmd)
487 if "FAIL" in res:
488 raise Exception("Failed to set PKEX data (responder)")
489 self.dpp_listen(freq, role=listen_role)
490
491 def dpp_configurator_add(self, curve=None, key=None):
492 cmd = "DPP_CONFIGURATOR_ADD"
493 if curve:
494 cmd += " curve=" + curve
495 if key:
496 cmd += " key=" + key
497 res = self.request(cmd)
498 if "FAIL" in res:
499 raise Exception("Failed to add configurator")
500 return int(res)
501
502 def dpp_configurator_remove(self, conf_id):
503 res = self.request("DPP_CONFIGURATOR_REMOVE %d" % conf_id)
504 if "OK" not in res:
505 raise Exception("DPP_CONFIGURATOR_REMOVE failed")
506
507 def note(self, txt):
508 self.request("NOTE " + txt)
509
510 def add_ap(apdev, params, wait_enabled=True, no_enable=False, timeout=30,
511 global_ctrl_override=None, driver=False):
512 if isinstance(apdev, dict):
513 ifname = apdev['ifname']
514 try:
515 hostname = apdev['hostname']
516 port = apdev['port']
517 logger.info("Starting AP " + hostname + "/" + port + " " + ifname)
518 except:
519 logger.info("Starting AP " + ifname)
520 hostname = None
521 port = 8878
522 else:
523 ifname = apdev
524 logger.info("Starting AP " + ifname + " (old add_ap argument type)")
525 hostname = None
526 port = 8878
527 hapd_global = HostapdGlobal(apdev,
528 global_ctrl_override=global_ctrl_override)
529 hapd_global.remove(ifname)
530 hapd_global.add(ifname, driver=driver)
531 port = hapd_global.get_ctrl_iface_port(ifname)
532 hapd = Hostapd(ifname, hostname=hostname, port=port)
533 if not hapd.ping():
534 raise Exception("Could not ping hostapd")
535 hapd.set_defaults()
536 fields = ["ssid", "wpa_passphrase", "nas_identifier", "wpa_key_mgmt",
537 "wpa",
538 "wpa_pairwise", "rsn_pairwise", "auth_server_addr",
539 "acct_server_addr", "osu_server_uri"]
540 for field in fields:
541 if field in params:
542 hapd.set(field, params[field])
543 for f, v in list(params.items()):
544 if f in fields:
545 continue
546 if isinstance(v, list):
547 for val in v:
548 hapd.set(f, val)
549 else:
550 hapd.set(f, v)
551 if no_enable:
552 return hapd
553 hapd.enable()
554 if wait_enabled:
555 ev = hapd.wait_event(["AP-ENABLED", "AP-DISABLED"], timeout=timeout)
556 if ev is None:
557 raise Exception("AP startup timed out")
558 if "AP-ENABLED" not in ev:
559 raise Exception("AP startup failed")
560 return hapd
561
562 def add_bss(apdev, ifname, confname, ignore_error=False):
563 phy = utils.get_phy(apdev)
564 try:
565 hostname = apdev['hostname']
566 port = apdev['port']
567 logger.info("Starting BSS " + hostname + "/" + port + " phy=" + phy + " ifname=" + ifname)
568 except:
569 logger.info("Starting BSS phy=" + phy + " ifname=" + ifname)
570 hostname = None
571 port = 8878
572 hapd_global = HostapdGlobal(apdev)
573 hapd_global.add_bss(phy, confname, ignore_error)
574 port = hapd_global.get_ctrl_iface_port(ifname)
575 hapd = Hostapd(ifname, hostname=hostname, port=port)
576 if not hapd.ping():
577 raise Exception("Could not ping hostapd")
578 return hapd
579
580 def add_iface(apdev, confname):
581 ifname = apdev['ifname']
582 try:
583 hostname = apdev['hostname']
584 port = apdev['port']
585 logger.info("Starting interface " + hostname + "/" + port + " " + ifname)
586 except:
587 logger.info("Starting interface " + ifname)
588 hostname = None
589 port = 8878
590 hapd_global = HostapdGlobal(apdev)
591 hapd_global.add_iface(ifname, confname)
592 port = hapd_global.get_ctrl_iface_port(ifname)
593 hapd = Hostapd(ifname, hostname=hostname, port=port)
594 if not hapd.ping():
595 raise Exception("Could not ping hostapd")
596 return hapd
597
598 def remove_bss(apdev, ifname=None):
599 if ifname == None:
600 ifname = apdev['ifname']
601 try:
602 hostname = apdev['hostname']
603 port = apdev['port']
604 logger.info("Removing BSS " + hostname + "/" + port + " " + ifname)
605 except:
606 logger.info("Removing BSS " + ifname)
607 hapd_global = HostapdGlobal(apdev)
608 hapd_global.remove(ifname)
609
610 def terminate(apdev):
611 try:
612 hostname = apdev['hostname']
613 port = apdev['port']
614 logger.info("Terminating hostapd " + hostname + "/" + port)
615 except:
616 logger.info("Terminating hostapd")
617 hapd_global = HostapdGlobal(apdev)
618 hapd_global.terminate()
619
620 def wpa2_params(ssid=None, passphrase=None):
621 params = {"wpa": "2",
622 "wpa_key_mgmt": "WPA-PSK",
623 "rsn_pairwise": "CCMP"}
624 if ssid:
625 params["ssid"] = ssid
626 if passphrase:
627 params["wpa_passphrase"] = passphrase
628 return params
629
630 def wpa_params(ssid=None, passphrase=None):
631 params = {"wpa": "1",
632 "wpa_key_mgmt": "WPA-PSK",
633 "wpa_pairwise": "TKIP"}
634 if ssid:
635 params["ssid"] = ssid
636 if passphrase:
637 params["wpa_passphrase"] = passphrase
638 return params
639
640 def wpa_mixed_params(ssid=None, passphrase=None):
641 params = {"wpa": "3",
642 "wpa_key_mgmt": "WPA-PSK",
643 "wpa_pairwise": "TKIP",
644 "rsn_pairwise": "CCMP"}
645 if ssid:
646 params["ssid"] = ssid
647 if passphrase:
648 params["wpa_passphrase"] = passphrase
649 return params
650
651 def radius_params():
652 params = {"auth_server_addr": "127.0.0.1",
653 "auth_server_port": "1812",
654 "auth_server_shared_secret": "radius",
655 "nas_identifier": "nas.w1.fi"}
656 return params
657
658 def wpa_eap_params(ssid=None):
659 params = radius_params()
660 params["wpa"] = "1"
661 params["wpa_key_mgmt"] = "WPA-EAP"
662 params["wpa_pairwise"] = "TKIP"
663 params["ieee8021x"] = "1"
664 if ssid:
665 params["ssid"] = ssid
666 return params
667
668 def wpa2_eap_params(ssid=None):
669 params = radius_params()
670 params["wpa"] = "2"
671 params["wpa_key_mgmt"] = "WPA-EAP"
672 params["rsn_pairwise"] = "CCMP"
673 params["ieee8021x"] = "1"
674 if ssid:
675 params["ssid"] = ssid
676 return params
677
678 def b_only_params(channel="1", ssid=None, country=None):
679 params = {"hw_mode": "b",
680 "channel": channel}
681 if ssid:
682 params["ssid"] = ssid
683 if country:
684 params["country_code"] = country
685 return params
686
687 def g_only_params(channel="1", ssid=None, country=None):
688 params = {"hw_mode": "g",
689 "channel": channel}
690 if ssid:
691 params["ssid"] = ssid
692 if country:
693 params["country_code"] = country
694 return params
695
696 def a_only_params(channel="36", ssid=None, country=None):
697 params = {"hw_mode": "a",
698 "channel": channel}
699 if ssid:
700 params["ssid"] = ssid
701 if country:
702 params["country_code"] = country
703 return params
704
705 def ht20_params(channel="1", ssid=None, country=None):
706 params = {"ieee80211n": "1",
707 "channel": channel,
708 "hw_mode": "g"}
709 if int(channel) > 14:
710 params["hw_mode"] = "a"
711 if ssid:
712 params["ssid"] = ssid
713 if country:
714 params["country_code"] = country
715 return params
716
717 def ht40_plus_params(channel="1", ssid=None, country=None):
718 params = ht20_params(channel, ssid, country)
719 params['ht_capab'] = "[HT40+]"
720 return params
721
722 def ht40_minus_params(channel="1", ssid=None, country=None):
723 params = ht20_params(channel, ssid, country)
724 params['ht_capab'] = "[HT40-]"
725 return params
726
727 def cmd_execute(apdev, cmd, shell=False):
728 hapd_global = HostapdGlobal(apdev)
729 return hapd_global.cmd_execute(cmd, shell=shell)