hostapd.py 19 KB

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