Updated to hostap_2_6
[mech_eap.git] / libeap / tests / hwsim / hostapd.py
1 # Python class for controlling hostapd
2 # Copyright (c) 2013-2014, 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):
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             self.ctrl = wpaspy.Ctrl(hapd_global)
37             self.mon = wpaspy.Ctrl(hapd_global)
38             self.dbg = ""
39         else:
40             self.ctrl = wpaspy.Ctrl(hostname, port)
41             self.mon = wpaspy.Ctrl(hostname, port)
42             self.dbg = hostname + "/" + str(port)
43         self.mon.attach()
44
45     def cmd_execute(self, cmd_array, shell=False):
46         if self.hostname is None:
47             if shell:
48                 cmd = ' '.join(cmd_array)
49             else:
50                 cmd = cmd_array
51             proc = subprocess.Popen(cmd, stderr=subprocess.STDOUT,
52                                     stdout=subprocess.PIPE, shell=shell)
53             out = proc.communicate()[0]
54             ret = proc.returncode
55             return ret, out
56         else:
57             return self.host.execute(cmd_array)
58
59     def request(self, cmd, timeout=10):
60         logger.debug(self.dbg + ": CTRL(global): " + cmd)
61         return self.ctrl.request(cmd, timeout)
62
63     def wait_event(self, events, timeout):
64         start = os.times()[4]
65         while True:
66             while self.mon.pending():
67                 ev = self.mon.recv()
68                 logger.debug(self.dbg + "(global): " + ev)
69                 for event in events:
70                     if event in ev:
71                         return ev
72             now = os.times()[4]
73             remaining = start + timeout - now
74             if remaining <= 0:
75                 break
76             if not self.mon.pending(timeout=remaining):
77                 break
78         return None
79
80     def add(self, ifname, driver=None):
81         cmd = "ADD " + ifname + " " + hapd_ctrl
82         if driver:
83             cmd += " " + driver
84         res = self.request(cmd)
85         if not "OK" in res:
86             raise Exception("Could not add hostapd interface " + ifname)
87
88     def add_iface(self, ifname, confname):
89         res = self.request("ADD " + ifname + " config=" + confname)
90         if not "OK" in res:
91             raise Exception("Could not add hostapd interface")
92
93     def add_bss(self, phy, confname, ignore_error=False):
94         res = self.request("ADD bss_config=" + phy + ":" + confname)
95         if not "OK" in res:
96             if not ignore_error:
97                 raise Exception("Could not add hostapd BSS")
98
99     def remove(self, ifname):
100         self.request("REMOVE " + ifname, timeout=30)
101
102     def relog(self):
103         self.request("RELOG")
104
105     def flush(self):
106         self.request("FLUSH")
107
108     def get_ctrl_iface_port(self, ifname):
109         if self.hostname is None:
110             return None
111
112         res = self.request("INTERFACES ctrl")
113         lines = res.splitlines()
114         found = False
115         for line in lines:
116             words = line.split()
117             if words[0] == ifname:
118                 found = True
119                 break
120         if not found:
121             raise Exception("Could not find UDP port for " + ifname)
122         res = line.find("ctrl_iface=udp:")
123         if res == -1:
124             raise Exception("Wrong ctrl_interface format")
125         words = line.split(":")
126         return int(words[1])
127
128     def terminate(self):
129         self.mon.detach()
130         self.mon.close()
131         self.mon = None
132         self.ctrl.terminate()
133         self.ctrl = None
134
135 class Hostapd:
136     def __init__(self, ifname, bssidx=0, hostname=None, port=8877):
137         self.hostname = hostname
138         self.host = remotehost.Host(hostname, ifname)
139         self.ifname = ifname
140         if hostname is None:
141             self.ctrl = wpaspy.Ctrl(os.path.join(hapd_ctrl, ifname))
142             self.mon = wpaspy.Ctrl(os.path.join(hapd_ctrl, ifname))
143             self.dbg = ifname
144         else:
145             self.ctrl = wpaspy.Ctrl(hostname, port)
146             self.mon = wpaspy.Ctrl(hostname, port)
147             self.dbg = hostname + "/" + ifname
148         self.mon.attach()
149         self.bssid = None
150         self.bssidx = bssidx
151
152     def cmd_execute(self, cmd_array, shell=False):
153         if self.hostname is None:
154             if shell:
155                 cmd = ' '.join(cmd_array)
156             else:
157                 cmd = cmd_array
158             proc = subprocess.Popen(cmd, stderr=subprocess.STDOUT,
159                                     stdout=subprocess.PIPE, shell=shell)
160             out = proc.communicate()[0]
161             ret = proc.returncode
162             return ret, out
163         else:
164             return self.host.execute(cmd_array)
165
166     def close_ctrl(self):
167         if self.mon is not None:
168             self.mon.detach()
169             self.mon.close()
170             self.mon = None
171             self.ctrl.close()
172             self.ctrl = None
173
174     def own_addr(self):
175         if self.bssid is None:
176             self.bssid = self.get_status_field('bssid[%d]' % self.bssidx)
177         return self.bssid
178
179     def request(self, cmd):
180         logger.debug(self.dbg + ": CTRL: " + cmd)
181         return self.ctrl.request(cmd)
182
183     def ping(self):
184         return "PONG" in self.request("PING")
185
186     def set(self, field, value):
187         if not "OK" in self.request("SET " + field + " " + value):
188             raise Exception("Failed to set hostapd parameter " + field)
189
190     def set_defaults(self):
191         self.set("driver", "nl80211")
192         self.set("hw_mode", "g")
193         self.set("channel", "1")
194         self.set("ieee80211n", "1")
195         self.set("logger_stdout", "-1")
196         self.set("logger_stdout_level", "0")
197
198     def set_open(self, ssid):
199         self.set_defaults()
200         self.set("ssid", ssid)
201
202     def set_wpa2_psk(self, ssid, passphrase):
203         self.set_defaults()
204         self.set("ssid", ssid)
205         self.set("wpa_passphrase", passphrase)
206         self.set("wpa", "2")
207         self.set("wpa_key_mgmt", "WPA-PSK")
208         self.set("rsn_pairwise", "CCMP")
209
210     def set_wpa_psk(self, ssid, passphrase):
211         self.set_defaults()
212         self.set("ssid", ssid)
213         self.set("wpa_passphrase", passphrase)
214         self.set("wpa", "1")
215         self.set("wpa_key_mgmt", "WPA-PSK")
216         self.set("wpa_pairwise", "TKIP")
217
218     def set_wpa_psk_mixed(self, ssid, passphrase):
219         self.set_defaults()
220         self.set("ssid", ssid)
221         self.set("wpa_passphrase", passphrase)
222         self.set("wpa", "3")
223         self.set("wpa_key_mgmt", "WPA-PSK")
224         self.set("wpa_pairwise", "TKIP")
225         self.set("rsn_pairwise", "CCMP")
226
227     def set_wep(self, ssid, key):
228         self.set_defaults()
229         self.set("ssid", ssid)
230         self.set("wep_key0", key)
231
232     def enable(self):
233         if not "OK" in self.request("ENABLE"):
234             raise Exception("Failed to enable hostapd interface " + self.ifname)
235
236     def disable(self):
237         if not "OK" in self.request("DISABLE"):
238             raise Exception("Failed to disable hostapd interface " + self.ifname)
239
240     def dump_monitor(self):
241         while self.mon.pending():
242             ev = self.mon.recv()
243             logger.debug(self.dbg + ": " + ev)
244
245     def wait_event(self, events, timeout):
246         start = os.times()[4]
247         while True:
248             while self.mon.pending():
249                 ev = self.mon.recv()
250                 logger.debug(self.dbg + ": " + ev)
251                 for event in events:
252                     if event in ev:
253                         return ev
254             now = os.times()[4]
255             remaining = start + timeout - now
256             if remaining <= 0:
257                 break
258             if not self.mon.pending(timeout=remaining):
259                 break
260         return None
261
262     def get_status(self):
263         res = self.request("STATUS")
264         lines = res.splitlines()
265         vals = dict()
266         for l in lines:
267             [name,value] = l.split('=', 1)
268             vals[name] = value
269         return vals
270
271     def get_status_field(self, field):
272         vals = self.get_status()
273         if field in vals:
274             return vals[field]
275         return None
276
277     def get_driver_status(self):
278         res = self.request("STATUS-DRIVER")
279         lines = res.splitlines()
280         vals = dict()
281         for l in lines:
282             [name,value] = l.split('=', 1)
283             vals[name] = value
284         return vals
285
286     def get_driver_status_field(self, field):
287         vals = self.get_driver_status()
288         if field in vals:
289             return vals[field]
290         return None
291
292     def get_config(self):
293         res = self.request("GET_CONFIG")
294         lines = res.splitlines()
295         vals = dict()
296         for l in lines:
297             [name,value] = l.split('=', 1)
298             vals[name] = value
299         return vals
300
301     def mgmt_rx(self, timeout=5):
302         ev = self.wait_event(["MGMT-RX"], timeout=timeout)
303         if ev is None:
304             return None
305         msg = {}
306         frame = binascii.unhexlify(ev.split(' ')[1])
307         msg['frame'] = frame
308
309         hdr = struct.unpack('<HH6B6B6BH', frame[0:24])
310         msg['fc'] = hdr[0]
311         msg['subtype'] = (hdr[0] >> 4) & 0xf
312         hdr = hdr[1:]
313         msg['duration'] = hdr[0]
314         hdr = hdr[1:]
315         msg['da'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6]
316         hdr = hdr[6:]
317         msg['sa'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6]
318         hdr = hdr[6:]
319         msg['bssid'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6]
320         hdr = hdr[6:]
321         msg['seq_ctrl'] = hdr[0]
322         msg['payload'] = frame[24:]
323
324         return msg
325
326     def mgmt_tx(self, msg):
327         t = (msg['fc'], 0) + mac2tuple(msg['da']) + mac2tuple(msg['sa']) + mac2tuple(msg['bssid']) + (0,)
328         hdr = struct.pack('<HH6B6B6BH', *t)
329         self.request("MGMT_TX " + binascii.hexlify(hdr + msg['payload']))
330
331     def get_sta(self, addr, info=None, next=False):
332         cmd = "STA-NEXT " if next else "STA "
333         if addr is None:
334             res = self.request("STA-FIRST")
335         elif info:
336             res = self.request(cmd + addr + " " + info)
337         else:
338             res = self.request(cmd + addr)
339         lines = res.splitlines()
340         vals = dict()
341         first = True
342         for l in lines:
343             if first and '=' not in l:
344                 vals['addr'] = l
345                 first = False
346             else:
347                 [name,value] = l.split('=', 1)
348                 vals[name] = value
349         return vals
350
351     def get_mib(self, param=None):
352         if param:
353             res = self.request("MIB " + param)
354         else:
355             res = self.request("MIB")
356         lines = res.splitlines()
357         vals = dict()
358         for l in lines:
359             name_val = l.split('=', 1)
360             if len(name_val) > 1:
361                 vals[name_val[0]] = name_val[1]
362         return vals
363
364     def get_pmksa(self, addr):
365         res = self.request("PMKSA")
366         lines = res.splitlines()
367         for l in lines:
368             if addr not in l:
369                 continue
370             vals = dict()
371             [index,aa,pmkid,expiration,opportunistic] = l.split(' ')
372             vals['index'] = index
373             vals['pmkid'] = pmkid
374             vals['expiration'] = expiration
375             vals['opportunistic'] = opportunistic
376             return vals
377         return None
378
379 def add_ap(apdev, params, wait_enabled=True, no_enable=False, timeout=30):
380         if isinstance(apdev, dict):
381             ifname = apdev['ifname']
382             try:
383                 hostname = apdev['hostname']
384                 port = apdev['port']
385                 logger.info("Starting AP " + hostname + "/" + port + " " + ifname)
386             except:
387                 logger.info("Starting AP " + ifname)
388                 hostname = None
389                 port = 8878
390         else:
391             ifname = apdev
392             logger.info("Starting AP " + ifname + " (old add_ap argument type)")
393             hostname = None
394             port = 8878
395         hapd_global = HostapdGlobal(apdev)
396         hapd_global.remove(ifname)
397         hapd_global.add(ifname)
398         port = hapd_global.get_ctrl_iface_port(ifname)
399         hapd = Hostapd(ifname, hostname=hostname, port=port)
400         if not hapd.ping():
401             raise Exception("Could not ping hostapd")
402         hapd.set_defaults()
403         fields = [ "ssid", "wpa_passphrase", "nas_identifier", "wpa_key_mgmt",
404                    "wpa",
405                    "wpa_pairwise", "rsn_pairwise", "auth_server_addr",
406                    "acct_server_addr", "osu_server_uri" ]
407         for field in fields:
408             if field in params:
409                 hapd.set(field, params[field])
410         for f,v in params.items():
411             if f in fields:
412                 continue
413             if isinstance(v, list):
414                 for val in v:
415                     hapd.set(f, val)
416             else:
417                 hapd.set(f, v)
418         if no_enable:
419             return hapd
420         hapd.enable()
421         if wait_enabled:
422             ev = hapd.wait_event(["AP-ENABLED", "AP-DISABLED"], timeout=timeout)
423             if ev is None:
424                 raise Exception("AP startup timed out")
425             if "AP-ENABLED" not in ev:
426                 raise Exception("AP startup failed")
427         return hapd
428
429 def add_bss(apdev, ifname, confname, ignore_error=False):
430     phy = utils.get_phy(apdev)
431     try:
432         hostname = apdev['hostname']
433         port = apdev['port']
434         logger.info("Starting BSS " + hostname + "/" + port + " phy=" + phy + " ifname=" + ifname)
435     except:
436         logger.info("Starting BSS phy=" + phy + " ifname=" + ifname)
437         hostname = None
438         port = 8878
439     hapd_global = HostapdGlobal(apdev)
440     hapd_global.add_bss(phy, confname, ignore_error)
441     port = hapd_global.get_ctrl_iface_port(ifname)
442     hapd = Hostapd(ifname, hostname=hostname, port=port)
443     if not hapd.ping():
444         raise Exception("Could not ping hostapd")
445     return hapd
446
447 def add_iface(apdev, confname):
448     ifname = apdev['ifname']
449     try:
450         hostname = apdev['hostname']
451         port = apdev['port']
452         logger.info("Starting interface " + hostname + "/" + port + " " + ifname)
453     except:
454         logger.info("Starting interface " + ifname)
455         hostname = None
456         port = 8878
457     hapd_global = HostapdGlobal(apdev)
458     hapd_global.add_iface(ifname, confname)
459     port = hapd_global.get_ctrl_iface_port(ifname)
460     hapd = Hostapd(ifname, hostname=hostname, port=port)
461     if not hapd.ping():
462         raise Exception("Could not ping hostapd")
463     return hapd
464
465 def remove_bss(apdev, ifname=None):
466     if ifname == None:
467         ifname = apdev['ifname']
468     try:
469         hostname = apdev['hostname']
470         port = apdev['port']
471         logger.info("Removing BSS " + hostname + "/" + port + " " + ifname)
472     except:
473         logger.info("Removing BSS " + ifname)
474     hapd_global = HostapdGlobal(apdev)
475     hapd_global.remove(ifname)
476
477 def terminate(apdev):
478     try:
479         hostname = apdev['hostname']
480         port = apdev['port']
481         logger.info("Terminating hostapd " + hostname + "/" + port)
482     except:
483         logger.info("Terminating hostapd")
484     hapd_global = HostapdGlobal(apdev)
485     hapd_global.terminate()
486
487 def wpa2_params(ssid=None, passphrase=None):
488     params = { "wpa": "2",
489                "wpa_key_mgmt": "WPA-PSK",
490                "rsn_pairwise": "CCMP" }
491     if ssid:
492         params["ssid"] = ssid
493     if passphrase:
494         params["wpa_passphrase"] = passphrase
495     return params
496
497 def wpa_params(ssid=None, passphrase=None):
498     params = { "wpa": "1",
499                "wpa_key_mgmt": "WPA-PSK",
500                "wpa_pairwise": "TKIP" }
501     if ssid:
502         params["ssid"] = ssid
503     if passphrase:
504         params["wpa_passphrase"] = passphrase
505     return params
506
507 def wpa_mixed_params(ssid=None, passphrase=None):
508     params = { "wpa": "3",
509                "wpa_key_mgmt": "WPA-PSK",
510                "wpa_pairwise": "TKIP",
511                "rsn_pairwise": "CCMP" }
512     if ssid:
513         params["ssid"] = ssid
514     if passphrase:
515         params["wpa_passphrase"] = passphrase
516     return params
517
518 def radius_params():
519     params = { "auth_server_addr": "127.0.0.1",
520                "auth_server_port": "1812",
521                "auth_server_shared_secret": "radius",
522                "nas_identifier": "nas.w1.fi" }
523     return params
524
525 def wpa_eap_params(ssid=None):
526     params = radius_params()
527     params["wpa"] = "1"
528     params["wpa_key_mgmt"] = "WPA-EAP"
529     params["wpa_pairwise"] = "TKIP"
530     params["ieee8021x"] = "1"
531     if ssid:
532         params["ssid"] = ssid
533     return params
534
535 def wpa2_eap_params(ssid=None):
536     params = radius_params()
537     params["wpa"] = "2"
538     params["wpa_key_mgmt"] = "WPA-EAP"
539     params["rsn_pairwise"] = "CCMP"
540     params["ieee8021x"] = "1"
541     if ssid:
542         params["ssid"] = ssid
543     return params
544
545 def b_only_params(channel="1", ssid=None, country=None):
546     params = { "hw_mode" : "b",
547                "channel" : channel }
548     if ssid:
549         params["ssid"] = ssid
550     if country:
551         params["country_code"] = country
552     return params
553
554 def g_only_params(channel="1", ssid=None, country=None):
555     params = { "hw_mode" : "g",
556                "channel" : channel }
557     if ssid:
558         params["ssid"] = ssid
559     if country:
560         params["country_code"] = country
561     return params
562
563 def a_only_params(channel="36", ssid=None, country=None):
564     params = { "hw_mode" : "a",
565                "channel" : channel }
566     if ssid:
567         params["ssid"] = ssid
568     if country:
569         params["country_code"] = country
570     return params
571
572 def ht20_params(channel="1", ssid=None, country=None):
573     params = { "ieee80211n" : "1",
574                "channel" : channel,
575                "hw_mode" : "g" }
576     if int(channel) > 14:
577         params["hw_mode"] = "a"
578     if ssid:
579         params["ssid"] = ssid
580     if country:
581         params["country_code"] = country
582     return params
583
584 def ht40_plus_params(channel="1", ssid=None, country=None):
585     params = ht20_params(channel, ssid, country)
586     params['ht_capab'] = "[HT40+]"
587     return params
588
589 def ht40_minus_params(channel="1", ssid=None, country=None):
590     params = ht20_params(channel, ssid, country)
591     params['ht_capab'] = "[HT40-]"
592     return params
593
594 def cmd_execute(apdev, cmd, shell=False):
595     hapd_global = HostapdGlobal(apdev)
596     return hapd_global.cmd_execute(cmd, shell=shell)