hostapd.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  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. def get_ctrl_iface_port(self, ifname):
  71. if self.hostname is None:
  72. return None
  73. res = self.ctrl.request("INTERFACES ctrl")
  74. lines = res.splitlines()
  75. found = False
  76. for line in lines:
  77. words = line.split()
  78. if words[0] == ifname:
  79. found = True
  80. break
  81. if not found:
  82. raise Exception("Could not find UDP port for " + ifname)
  83. res = line.find("ctrl_iface=udp:")
  84. if res == -1:
  85. raise Exception("Wrong ctrl_interface format")
  86. words = line.split(":")
  87. return int(words[1])
  88. def terminate(self):
  89. self.mon.detach()
  90. self.mon.close()
  91. self.mon = None
  92. self.ctrl.terminate()
  93. self.ctrl = None
  94. class Hostapd:
  95. def __init__(self, ifname, bssidx=0, hostname=None, port=8877):
  96. self.ifname = ifname
  97. if hostname is None:
  98. self.ctrl = wpaspy.Ctrl(os.path.join(hapd_ctrl, ifname))
  99. self.mon = wpaspy.Ctrl(os.path.join(hapd_ctrl, ifname))
  100. else:
  101. self.ctrl = wpaspy.Ctrl(hostname, port)
  102. self.mon = wpaspy.Ctrl(hostname, port)
  103. self.mon.attach()
  104. self.bssid = None
  105. self.bssidx = bssidx
  106. def close_ctrl(self):
  107. if self.mon is not None:
  108. self.mon.detach()
  109. self.mon.close()
  110. self.mon = None
  111. self.ctrl.close()
  112. self.ctrl = None
  113. def own_addr(self):
  114. if self.bssid is None:
  115. self.bssid = self.get_status_field('bssid[%d]' % self.bssidx)
  116. return self.bssid
  117. def request(self, cmd):
  118. logger.debug(self.ifname + ": CTRL: " + cmd)
  119. return self.ctrl.request(cmd)
  120. def ping(self):
  121. return "PONG" in self.request("PING")
  122. def set(self, field, value):
  123. if not "OK" in self.request("SET " + field + " " + value):
  124. raise Exception("Failed to set hostapd parameter " + field)
  125. def set_defaults(self):
  126. self.set("driver", "nl80211")
  127. self.set("hw_mode", "g")
  128. self.set("channel", "1")
  129. self.set("ieee80211n", "1")
  130. self.set("logger_stdout", "-1")
  131. self.set("logger_stdout_level", "0")
  132. def set_open(self, ssid):
  133. self.set_defaults()
  134. self.set("ssid", ssid)
  135. def set_wpa2_psk(self, ssid, passphrase):
  136. self.set_defaults()
  137. self.set("ssid", ssid)
  138. self.set("wpa_passphrase", passphrase)
  139. self.set("wpa", "2")
  140. self.set("wpa_key_mgmt", "WPA-PSK")
  141. self.set("rsn_pairwise", "CCMP")
  142. def set_wpa_psk(self, ssid, passphrase):
  143. self.set_defaults()
  144. self.set("ssid", ssid)
  145. self.set("wpa_passphrase", passphrase)
  146. self.set("wpa", "1")
  147. self.set("wpa_key_mgmt", "WPA-PSK")
  148. self.set("wpa_pairwise", "TKIP")
  149. def set_wpa_psk_mixed(self, ssid, passphrase):
  150. self.set_defaults()
  151. self.set("ssid", ssid)
  152. self.set("wpa_passphrase", passphrase)
  153. self.set("wpa", "3")
  154. self.set("wpa_key_mgmt", "WPA-PSK")
  155. self.set("wpa_pairwise", "TKIP")
  156. self.set("rsn_pairwise", "CCMP")
  157. def set_wep(self, ssid, key):
  158. self.set_defaults()
  159. self.set("ssid", ssid)
  160. self.set("wep_key0", key)
  161. def enable(self):
  162. if not "OK" in self.request("ENABLE"):
  163. raise Exception("Failed to enable hostapd interface " + self.ifname)
  164. def disable(self):
  165. if not "OK" in self.request("DISABLE"):
  166. raise Exception("Failed to disable hostapd interface " + self.ifname)
  167. def dump_monitor(self):
  168. while self.mon.pending():
  169. ev = self.mon.recv()
  170. logger.debug(self.ifname + ": " + ev)
  171. def wait_event(self, events, timeout):
  172. start = os.times()[4]
  173. while True:
  174. while self.mon.pending():
  175. ev = self.mon.recv()
  176. logger.debug(self.ifname + ": " + ev)
  177. for event in events:
  178. if event in ev:
  179. return ev
  180. now = os.times()[4]
  181. remaining = start + timeout - now
  182. if remaining <= 0:
  183. break
  184. if not self.mon.pending(timeout=remaining):
  185. break
  186. return None
  187. def get_status(self):
  188. res = self.request("STATUS")
  189. lines = res.splitlines()
  190. vals = dict()
  191. for l in lines:
  192. [name,value] = l.split('=', 1)
  193. vals[name] = value
  194. return vals
  195. def get_status_field(self, field):
  196. vals = self.get_status()
  197. if field in vals:
  198. return vals[field]
  199. return None
  200. def get_driver_status(self):
  201. res = self.request("STATUS-DRIVER")
  202. lines = res.splitlines()
  203. vals = dict()
  204. for l in lines:
  205. [name,value] = l.split('=', 1)
  206. vals[name] = value
  207. return vals
  208. def get_driver_status_field(self, field):
  209. vals = self.get_driver_status()
  210. if field in vals:
  211. return vals[field]
  212. return None
  213. def get_config(self):
  214. res = self.request("GET_CONFIG")
  215. lines = res.splitlines()
  216. vals = dict()
  217. for l in lines:
  218. [name,value] = l.split('=', 1)
  219. vals[name] = value
  220. return vals
  221. def mgmt_rx(self, timeout=5):
  222. ev = self.wait_event(["MGMT-RX"], timeout=timeout)
  223. if ev is None:
  224. return None
  225. msg = {}
  226. frame = binascii.unhexlify(ev.split(' ')[1])
  227. msg['frame'] = frame
  228. hdr = struct.unpack('<HH6B6B6BH', frame[0:24])
  229. msg['fc'] = hdr[0]
  230. msg['subtype'] = (hdr[0] >> 4) & 0xf
  231. hdr = hdr[1:]
  232. msg['duration'] = hdr[0]
  233. hdr = hdr[1:]
  234. msg['da'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6]
  235. hdr = hdr[6:]
  236. msg['sa'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6]
  237. hdr = hdr[6:]
  238. msg['bssid'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6]
  239. hdr = hdr[6:]
  240. msg['seq_ctrl'] = hdr[0]
  241. msg['payload'] = frame[24:]
  242. return msg
  243. def mgmt_tx(self, msg):
  244. t = (msg['fc'], 0) + mac2tuple(msg['da']) + mac2tuple(msg['sa']) + mac2tuple(msg['bssid']) + (0,)
  245. hdr = struct.pack('<HH6B6B6BH', *t)
  246. self.request("MGMT_TX " + binascii.hexlify(hdr + msg['payload']))
  247. def get_sta(self, addr, info=None, next=False):
  248. cmd = "STA-NEXT " if next else "STA "
  249. if addr is None:
  250. res = self.request("STA-FIRST")
  251. elif info:
  252. res = self.request(cmd + addr + " " + info)
  253. else:
  254. res = self.request(cmd + addr)
  255. lines = res.splitlines()
  256. vals = dict()
  257. first = True
  258. for l in lines:
  259. if first and '=' not in l:
  260. vals['addr'] = l
  261. first = False
  262. else:
  263. [name,value] = l.split('=', 1)
  264. vals[name] = value
  265. return vals
  266. def get_mib(self, param=None):
  267. if param:
  268. res = self.request("MIB " + param)
  269. else:
  270. res = self.request("MIB")
  271. lines = res.splitlines()
  272. vals = dict()
  273. for l in lines:
  274. name_val = l.split('=', 1)
  275. if len(name_val) > 1:
  276. vals[name_val[0]] = name_val[1]
  277. return vals
  278. def get_pmksa(self, addr):
  279. res = self.request("PMKSA")
  280. lines = res.splitlines()
  281. for l in lines:
  282. if addr not in l:
  283. continue
  284. vals = dict()
  285. [index,aa,pmkid,expiration,opportunistic] = l.split(' ')
  286. vals['index'] = index
  287. vals['pmkid'] = pmkid
  288. vals['expiration'] = expiration
  289. vals['opportunistic'] = opportunistic
  290. return vals
  291. return None
  292. def add_ap(ifname, params, wait_enabled=True, no_enable=False, timeout=30,
  293. hostname=None, port=8878):
  294. logger.info("Starting AP " + ifname)
  295. hapd_global = HostapdGlobal(hostname=hostname, port=port)
  296. hapd_global.remove(ifname)
  297. hapd_global.add(ifname)
  298. port = hapd_global.get_ctrl_iface_port(ifname)
  299. hapd = Hostapd(ifname, hostname=hostname, port=port)
  300. if not hapd.ping():
  301. raise Exception("Could not ping hostapd")
  302. hapd.set_defaults()
  303. fields = [ "ssid", "wpa_passphrase", "nas_identifier", "wpa_key_mgmt",
  304. "wpa",
  305. "wpa_pairwise", "rsn_pairwise", "auth_server_addr",
  306. "acct_server_addr", "osu_server_uri" ]
  307. for field in fields:
  308. if field in params:
  309. hapd.set(field, params[field])
  310. for f,v in params.items():
  311. if f in fields:
  312. continue
  313. if isinstance(v, list):
  314. for val in v:
  315. hapd.set(f, val)
  316. else:
  317. hapd.set(f, v)
  318. if no_enable:
  319. return hapd
  320. hapd.enable()
  321. if wait_enabled:
  322. ev = hapd.wait_event(["AP-ENABLED", "AP-DISABLED"], timeout=timeout)
  323. if ev is None:
  324. raise Exception("AP startup timed out")
  325. if "AP-ENABLED" not in ev:
  326. raise Exception("AP startup failed")
  327. return hapd
  328. def add_bss(phy, ifname, confname, ignore_error=False, hostname=None,
  329. port=8878):
  330. logger.info("Starting BSS phy=" + phy + " ifname=" + ifname)
  331. hapd_global = HostapdGlobal(hostname=hostname, port=port)
  332. hapd_global.add_bss(phy, confname, ignore_error)
  333. port = hapd_global.get_ctrl_iface_port(ifname)
  334. hapd = Hostapd(ifname, hostname=hostname, port=port)
  335. if not hapd.ping():
  336. raise Exception("Could not ping hostapd")
  337. def add_iface(ifname, confname, hostname=None, port=8878):
  338. logger.info("Starting interface " + ifname)
  339. hapd_global = HostapdGlobal(hostname=hostname, port=port)
  340. hapd_global.add_iface(ifname, confname)
  341. port = hapd_global.get_ctrl_iface_port(ifname)
  342. hapd = Hostapd(ifname, hostname=hostname, port=port)
  343. if not hapd.ping():
  344. raise Exception("Could not ping hostapd")
  345. def remove_bss(ifname, hostname=None, port=8878):
  346. logger.info("Removing BSS " + ifname)
  347. hapd_global = HostapdGlobal(hostname=hostname, port=port)
  348. hapd_global.remove(ifname)
  349. def terminate(hostname=None, port=8878):
  350. logger.info("Terminating hostapd")
  351. hapd_global = HostapdGlobal(hostname=hostname, port=port)
  352. hapd_global.terminate()
  353. def wpa2_params(ssid=None, passphrase=None):
  354. params = { "wpa": "2",
  355. "wpa_key_mgmt": "WPA-PSK",
  356. "rsn_pairwise": "CCMP" }
  357. if ssid:
  358. params["ssid"] = ssid
  359. if passphrase:
  360. params["wpa_passphrase"] = passphrase
  361. return params
  362. def wpa_params(ssid=None, passphrase=None):
  363. params = { "wpa": "1",
  364. "wpa_key_mgmt": "WPA-PSK",
  365. "wpa_pairwise": "TKIP" }
  366. if ssid:
  367. params["ssid"] = ssid
  368. if passphrase:
  369. params["wpa_passphrase"] = passphrase
  370. return params
  371. def wpa_mixed_params(ssid=None, passphrase=None):
  372. params = { "wpa": "3",
  373. "wpa_key_mgmt": "WPA-PSK",
  374. "wpa_pairwise": "TKIP",
  375. "rsn_pairwise": "CCMP" }
  376. if ssid:
  377. params["ssid"] = ssid
  378. if passphrase:
  379. params["wpa_passphrase"] = passphrase
  380. return params
  381. def radius_params():
  382. params = { "auth_server_addr": "127.0.0.1",
  383. "auth_server_port": "1812",
  384. "auth_server_shared_secret": "radius",
  385. "nas_identifier": "nas.w1.fi" }
  386. return params
  387. def wpa_eap_params(ssid=None):
  388. params = radius_params()
  389. params["wpa"] = "1"
  390. params["wpa_key_mgmt"] = "WPA-EAP"
  391. params["wpa_pairwise"] = "TKIP"
  392. params["ieee8021x"] = "1"
  393. if ssid:
  394. params["ssid"] = ssid
  395. return params
  396. def wpa2_eap_params(ssid=None):
  397. params = radius_params()
  398. params["wpa"] = "2"
  399. params["wpa_key_mgmt"] = "WPA-EAP"
  400. params["rsn_pairwise"] = "CCMP"
  401. params["ieee8021x"] = "1"
  402. if ssid:
  403. params["ssid"] = ssid
  404. return params
  405. def b_only_params(channel="1", ssid=None, country=None):
  406. params = { "hw_mode" : "b",
  407. "channel" : channel }
  408. if ssid:
  409. params["ssid"] = ssid
  410. if country:
  411. params["country_code"] = country
  412. return params
  413. def g_only_params(channel="1", ssid=None, country=None):
  414. params = { "hw_mode" : "g",
  415. "channel" : channel }
  416. if ssid:
  417. params["ssid"] = ssid
  418. if country:
  419. params["country_code"] = country
  420. return params
  421. def a_only_params(channel="36", ssid=None, country=None):
  422. params = { "hw_mode" : "a",
  423. "channel" : channel }
  424. if ssid:
  425. params["ssid"] = ssid
  426. if country:
  427. params["country_code"] = country
  428. return params
  429. def ht20_params(channel="1", ssid=None, country=None):
  430. params = { "ieee80211n" : "1",
  431. "channel" : channel,
  432. "hw_mode" : "g" }
  433. if int(channel) > 14:
  434. params["hw_mode"] = "a"
  435. if ssid:
  436. params["ssid"] = ssid
  437. if country:
  438. params["country_code"] = country
  439. return params
  440. def ht40_plus_params(channel="1", ssid=None, country=None):
  441. params = ht20_params(channel, ssid, country)
  442. params['ht_capab'] = "[HT40+]"
  443. return params
  444. def ht40_minus_params(channel="1", ssid=None, country=None):
  445. params = ht20_params(channel, ssid, country)
  446. params['ht_capab'] = "[HT40-]"
  447. return params