hostapd.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  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. import os
  7. import time
  8. import logging
  9. import binascii
  10. import struct
  11. import wpaspy
  12. logger = logging.getLogger()
  13. hapd_ctrl = '/var/run/hostapd'
  14. hapd_global = '/var/run/hostapd-global'
  15. def mac2tuple(mac):
  16. return struct.unpack('6B', binascii.unhexlify(mac.replace(':','')))
  17. class HostapdGlobal:
  18. def __init__(self, hostname=None, port=8878):
  19. self.hostname = hostname
  20. self.port = port
  21. if hostname is None:
  22. self.ctrl = wpaspy.Ctrl(hapd_global)
  23. self.mon = wpaspy.Ctrl(hapd_global)
  24. else:
  25. self.ctrl = wpaspy.Ctrl(hostname, port)
  26. self.mon = wpaspy.Ctrl(hostname, port)
  27. self.mon.attach()
  28. def request(self, cmd):
  29. return self.ctrl.request(cmd)
  30. def wait_event(self, events, timeout):
  31. start = os.times()[4]
  32. while True:
  33. while self.mon.pending():
  34. ev = self.mon.recv()
  35. logger.debug("(global): " + ev)
  36. for event in events:
  37. if event in ev:
  38. return ev
  39. now = os.times()[4]
  40. remaining = start + timeout - now
  41. if remaining <= 0:
  42. break
  43. if not self.mon.pending(timeout=remaining):
  44. break
  45. return None
  46. def request(self, cmd):
  47. return self.ctrl.request(cmd)
  48. def add(self, ifname, driver=None):
  49. cmd = "ADD " + ifname + " " + hapd_ctrl
  50. if driver:
  51. cmd += " " + driver
  52. res = self.ctrl.request(cmd)
  53. if not "OK" in res:
  54. raise Exception("Could not add hostapd interface " + ifname)
  55. def add_iface(self, ifname, confname):
  56. res = self.ctrl.request("ADD " + ifname + " config=" + confname)
  57. if not "OK" in res:
  58. raise Exception("Could not add hostapd interface")
  59. def add_bss(self, phy, confname, ignore_error=False):
  60. res = self.ctrl.request("ADD bss_config=" + phy + ":" + confname)
  61. if not "OK" in res:
  62. if not ignore_error:
  63. raise Exception("Could not add hostapd BSS")
  64. def remove(self, ifname):
  65. self.ctrl.request("REMOVE " + ifname, timeout=30)
  66. def relog(self):
  67. self.ctrl.request("RELOG")
  68. def flush(self):
  69. self.ctrl.request("FLUSH")
  70. class Hostapd:
  71. def __init__(self, ifname, bssidx=0, hostname=None, port=8877):
  72. self.ifname = ifname
  73. if hostname is None:
  74. self.ctrl = wpaspy.Ctrl(os.path.join(hapd_ctrl, ifname))
  75. self.mon = wpaspy.Ctrl(os.path.join(hapd_ctrl, ifname))
  76. else:
  77. self.ctrl = wpaspy.Ctrl(hostname, port)
  78. self.mon = wpaspy.Ctrl(hostname, port)
  79. self.mon.attach()
  80. self.bssid = None
  81. self.bssidx = bssidx
  82. def own_addr(self):
  83. if self.bssid is None:
  84. self.bssid = self.get_status_field('bssid[%d]' % self.bssidx)
  85. return self.bssid
  86. def request(self, cmd):
  87. logger.debug(self.ifname + ": CTRL: " + cmd)
  88. return self.ctrl.request(cmd)
  89. def ping(self):
  90. return "PONG" in self.request("PING")
  91. def set(self, field, value):
  92. if not "OK" in self.request("SET " + field + " " + value):
  93. raise Exception("Failed to set hostapd parameter " + field)
  94. def set_defaults(self):
  95. self.set("driver", "nl80211")
  96. self.set("hw_mode", "g")
  97. self.set("channel", "1")
  98. self.set("ieee80211n", "1")
  99. self.set("logger_stdout", "-1")
  100. self.set("logger_stdout_level", "0")
  101. def set_open(self, ssid):
  102. self.set_defaults()
  103. self.set("ssid", ssid)
  104. def set_wpa2_psk(self, ssid, passphrase):
  105. self.set_defaults()
  106. self.set("ssid", ssid)
  107. self.set("wpa_passphrase", passphrase)
  108. self.set("wpa", "2")
  109. self.set("wpa_key_mgmt", "WPA-PSK")
  110. self.set("rsn_pairwise", "CCMP")
  111. def set_wpa_psk(self, ssid, passphrase):
  112. self.set_defaults()
  113. self.set("ssid", ssid)
  114. self.set("wpa_passphrase", passphrase)
  115. self.set("wpa", "1")
  116. self.set("wpa_key_mgmt", "WPA-PSK")
  117. self.set("wpa_pairwise", "TKIP")
  118. def set_wpa_psk_mixed(self, ssid, passphrase):
  119. self.set_defaults()
  120. self.set("ssid", ssid)
  121. self.set("wpa_passphrase", passphrase)
  122. self.set("wpa", "3")
  123. self.set("wpa_key_mgmt", "WPA-PSK")
  124. self.set("wpa_pairwise", "TKIP")
  125. self.set("rsn_pairwise", "CCMP")
  126. def set_wep(self, ssid, key):
  127. self.set_defaults()
  128. self.set("ssid", ssid)
  129. self.set("wep_key0", key)
  130. def enable(self):
  131. if not "OK" in self.request("ENABLE"):
  132. raise Exception("Failed to enable hostapd interface " + self.ifname)
  133. def disable(self):
  134. if not "OK" in self.request("DISABLE"):
  135. raise Exception("Failed to disable hostapd interface " + self.ifname)
  136. def dump_monitor(self):
  137. while self.mon.pending():
  138. ev = self.mon.recv()
  139. logger.debug(self.ifname + ": " + ev)
  140. def wait_event(self, events, timeout):
  141. start = os.times()[4]
  142. while True:
  143. while self.mon.pending():
  144. ev = self.mon.recv()
  145. logger.debug(self.ifname + ": " + ev)
  146. for event in events:
  147. if event in ev:
  148. return ev
  149. now = os.times()[4]
  150. remaining = start + timeout - now
  151. if remaining <= 0:
  152. break
  153. if not self.mon.pending(timeout=remaining):
  154. break
  155. return None
  156. def get_status(self):
  157. res = self.request("STATUS")
  158. lines = res.splitlines()
  159. vals = dict()
  160. for l in lines:
  161. [name,value] = l.split('=', 1)
  162. vals[name] = value
  163. return vals
  164. def get_status_field(self, field):
  165. vals = self.get_status()
  166. if field in vals:
  167. return vals[field]
  168. return None
  169. def get_driver_status(self):
  170. res = self.request("STATUS-DRIVER")
  171. lines = res.splitlines()
  172. vals = dict()
  173. for l in lines:
  174. [name,value] = l.split('=', 1)
  175. vals[name] = value
  176. return vals
  177. def get_driver_status_field(self, field):
  178. vals = self.get_driver_status()
  179. if field in vals:
  180. return vals[field]
  181. return None
  182. def get_config(self):
  183. res = self.request("GET_CONFIG")
  184. lines = res.splitlines()
  185. vals = dict()
  186. for l in lines:
  187. [name,value] = l.split('=', 1)
  188. vals[name] = value
  189. return vals
  190. def mgmt_rx(self, timeout=5):
  191. ev = self.wait_event(["MGMT-RX"], timeout=timeout)
  192. if ev is None:
  193. return None
  194. msg = {}
  195. frame = binascii.unhexlify(ev.split(' ')[1])
  196. msg['frame'] = frame
  197. hdr = struct.unpack('<HH6B6B6BH', frame[0:24])
  198. msg['fc'] = hdr[0]
  199. msg['subtype'] = (hdr[0] >> 4) & 0xf
  200. hdr = hdr[1:]
  201. msg['duration'] = hdr[0]
  202. hdr = hdr[1:]
  203. msg['da'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6]
  204. hdr = hdr[6:]
  205. msg['sa'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6]
  206. hdr = hdr[6:]
  207. msg['bssid'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6]
  208. hdr = hdr[6:]
  209. msg['seq_ctrl'] = hdr[0]
  210. msg['payload'] = frame[24:]
  211. return msg
  212. def mgmt_tx(self, msg):
  213. t = (msg['fc'], 0) + mac2tuple(msg['da']) + mac2tuple(msg['sa']) + mac2tuple(msg['bssid']) + (0,)
  214. hdr = struct.pack('<HH6B6B6BH', *t)
  215. self.request("MGMT_TX " + binascii.hexlify(hdr + msg['payload']))
  216. def get_sta(self, addr, info=None, next=False):
  217. cmd = "STA-NEXT " if next else "STA "
  218. if addr is None:
  219. res = self.request("STA-FIRST")
  220. elif info:
  221. res = self.request(cmd + addr + " " + info)
  222. else:
  223. res = self.request(cmd + addr)
  224. lines = res.splitlines()
  225. vals = dict()
  226. first = True
  227. for l in lines:
  228. if first and '=' not in l:
  229. vals['addr'] = l
  230. first = False
  231. else:
  232. [name,value] = l.split('=', 1)
  233. vals[name] = value
  234. return vals
  235. def get_mib(self, param=None):
  236. if param:
  237. res = self.request("MIB " + param)
  238. else:
  239. res = self.request("MIB")
  240. lines = res.splitlines()
  241. vals = dict()
  242. for l in lines:
  243. name_val = l.split('=', 1)
  244. if len(name_val) > 1:
  245. vals[name_val[0]] = name_val[1]
  246. return vals
  247. def add_ap(ifname, params, wait_enabled=True, no_enable=False, timeout=30,
  248. hostname=None, port=8878):
  249. logger.info("Starting AP " + ifname)
  250. hapd_global = HostapdGlobal(hostname=hostname, port=port)
  251. hapd_global.remove(ifname)
  252. hapd_global.add(ifname)
  253. hapd = Hostapd(ifname, hostname=hostname)
  254. if not hapd.ping():
  255. raise Exception("Could not ping hostapd")
  256. hapd.set_defaults()
  257. fields = [ "ssid", "wpa_passphrase", "nas_identifier", "wpa_key_mgmt",
  258. "wpa",
  259. "wpa_pairwise", "rsn_pairwise", "auth_server_addr",
  260. "acct_server_addr", "osu_server_uri" ]
  261. for field in fields:
  262. if field in params:
  263. hapd.set(field, params[field])
  264. for f,v in params.items():
  265. if f in fields:
  266. continue
  267. if isinstance(v, list):
  268. for val in v:
  269. hapd.set(f, val)
  270. else:
  271. hapd.set(f, v)
  272. if no_enable:
  273. return hapd
  274. hapd.enable()
  275. if wait_enabled:
  276. ev = hapd.wait_event(["AP-ENABLED", "AP-DISABLED"], timeout=timeout)
  277. if ev is None:
  278. raise Exception("AP startup timed out")
  279. if "AP-ENABLED" not in ev:
  280. raise Exception("AP startup failed")
  281. return hapd
  282. def add_bss(phy, ifname, confname, ignore_error=False, hostname=None,
  283. port=8878):
  284. logger.info("Starting BSS phy=" + phy + " ifname=" + ifname)
  285. hapd_global = HostapdGlobal(hostname=hostname, port=port)
  286. hapd_global.add_bss(phy, confname, ignore_error)
  287. hapd = Hostapd(ifname, hostname=hostname)
  288. if not hapd.ping():
  289. raise Exception("Could not ping hostapd")
  290. def add_iface(ifname, confname, hostname=None, port=8878):
  291. logger.info("Starting interface " + ifname)
  292. hapd_global = HostapdGlobal(hostname=hostname, port=port)
  293. hapd_global.add_iface(ifname, confname)
  294. hapd = Hostapd(ifname, hostname=hostname)
  295. if not hapd.ping():
  296. raise Exception("Could not ping hostapd")
  297. def remove_bss(ifname, hostname=None, port=8878):
  298. logger.info("Removing BSS " + ifname)
  299. hapd_global = HostapdGlobal(hostname=hostname, port=port)
  300. hapd_global.remove(ifname)
  301. def wpa2_params(ssid=None, passphrase=None):
  302. params = { "wpa": "2",
  303. "wpa_key_mgmt": "WPA-PSK",
  304. "rsn_pairwise": "CCMP" }
  305. if ssid:
  306. params["ssid"] = ssid
  307. if passphrase:
  308. params["wpa_passphrase"] = passphrase
  309. return params
  310. def wpa_params(ssid=None, passphrase=None):
  311. params = { "wpa": "1",
  312. "wpa_key_mgmt": "WPA-PSK",
  313. "wpa_pairwise": "TKIP" }
  314. if ssid:
  315. params["ssid"] = ssid
  316. if passphrase:
  317. params["wpa_passphrase"] = passphrase
  318. return params
  319. def wpa_mixed_params(ssid=None, passphrase=None):
  320. params = { "wpa": "3",
  321. "wpa_key_mgmt": "WPA-PSK",
  322. "wpa_pairwise": "TKIP",
  323. "rsn_pairwise": "CCMP" }
  324. if ssid:
  325. params["ssid"] = ssid
  326. if passphrase:
  327. params["wpa_passphrase"] = passphrase
  328. return params
  329. def radius_params():
  330. params = { "auth_server_addr": "127.0.0.1",
  331. "auth_server_port": "1812",
  332. "auth_server_shared_secret": "radius",
  333. "nas_identifier": "nas.w1.fi" }
  334. return params
  335. def wpa_eap_params(ssid=None):
  336. params = radius_params()
  337. params["wpa"] = "1"
  338. params["wpa_key_mgmt"] = "WPA-EAP"
  339. params["wpa_pairwise"] = "TKIP"
  340. params["ieee8021x"] = "1"
  341. if ssid:
  342. params["ssid"] = ssid
  343. return params
  344. def wpa2_eap_params(ssid=None):
  345. params = radius_params()
  346. params["wpa"] = "2"
  347. params["wpa_key_mgmt"] = "WPA-EAP"
  348. params["rsn_pairwise"] = "CCMP"
  349. params["ieee8021x"] = "1"
  350. if ssid:
  351. params["ssid"] = ssid
  352. return params