hostapd.py 17 KB

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