hostapd.py 17 KB

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