hostapd.py 16 KB

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