hostapd.py 10 KB

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