hostapd.py 18 KB

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