hostapd.py 16 KB

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