test_radius.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  1. # RADIUS tests
  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 logging
  7. logger = logging.getLogger()
  8. import select
  9. import subprocess
  10. import threading
  11. import time
  12. import hostapd
  13. def connect(dev, ssid, wait_connect=True):
  14. dev.connect(ssid, key_mgmt="WPA-EAP", scan_freq="2412",
  15. eap="PSK", identity="psk.user@example.com",
  16. password_hex="0123456789abcdef0123456789abcdef",
  17. wait_connect=wait_connect)
  18. def test_radius_auth_unreachable(dev, apdev):
  19. """RADIUS Authentication server unreachable"""
  20. params = hostapd.wpa2_eap_params(ssid="radius-auth")
  21. params['auth_server_port'] = "18139"
  22. hostapd.add_ap(apdev[0]['ifname'], params)
  23. hapd = hostapd.Hostapd(apdev[0]['ifname'])
  24. connect(dev[0], "radius-auth", wait_connect=False)
  25. ev = dev[0].wait_event(["CTRL-EVENT-EAP-STARTED"])
  26. if ev is None:
  27. raise Exception("Timeout on EAP start")
  28. logger.info("Checking for RADIUS retries")
  29. time.sleep(4)
  30. mib = hapd.get_mib()
  31. if "radiusAuthClientAccessRequests" not in mib:
  32. raise Exception("Missing MIB fields")
  33. if int(mib["radiusAuthClientAccessRetransmissions"]) < 1:
  34. raise Exception("Missing RADIUS Authentication retransmission")
  35. if int(mib["radiusAuthClientPendingRequests"]) < 1:
  36. raise Exception("Missing pending RADIUS Authentication request")
  37. def test_radius_auth_unreachable2(dev, apdev):
  38. """RADIUS Authentication server unreachable (2)"""
  39. subprocess.call(['sudo', 'ip', 'ro', 'replace', '192.168.213.17', 'dev',
  40. 'lo'])
  41. params = hostapd.wpa2_eap_params(ssid="radius-auth")
  42. params['auth_server_addr'] = "192.168.213.17"
  43. params['auth_server_port'] = "18139"
  44. hostapd.add_ap(apdev[0]['ifname'], params)
  45. hapd = hostapd.Hostapd(apdev[0]['ifname'])
  46. subprocess.call(['sudo', 'ip', 'ro', 'del', '192.168.213.17', 'dev', 'lo'])
  47. connect(dev[0], "radius-auth", wait_connect=False)
  48. ev = dev[0].wait_event(["CTRL-EVENT-EAP-STARTED"])
  49. if ev is None:
  50. raise Exception("Timeout on EAP start")
  51. logger.info("Checking for RADIUS retries")
  52. time.sleep(4)
  53. mib = hapd.get_mib()
  54. if "radiusAuthClientAccessRequests" not in mib:
  55. raise Exception("Missing MIB fields")
  56. if int(mib["radiusAuthClientAccessRetransmissions"]) < 1:
  57. raise Exception("Missing RADIUS Authentication retransmission")
  58. def test_radius_acct_unreachable(dev, apdev):
  59. """RADIUS Accounting server unreachable"""
  60. params = hostapd.wpa2_eap_params(ssid="radius-acct")
  61. params['acct_server_addr'] = "127.0.0.1"
  62. params['acct_server_port'] = "18139"
  63. params['acct_server_shared_secret'] = "radius"
  64. hostapd.add_ap(apdev[0]['ifname'], params)
  65. hapd = hostapd.Hostapd(apdev[0]['ifname'])
  66. connect(dev[0], "radius-acct")
  67. logger.info("Checking for RADIUS retries")
  68. time.sleep(4)
  69. mib = hapd.get_mib()
  70. if "radiusAccClientRetransmissions" not in mib:
  71. raise Exception("Missing MIB fields")
  72. if int(mib["radiusAccClientRetransmissions"]) < 2:
  73. raise Exception("Missing RADIUS Accounting retransmissions")
  74. if int(mib["radiusAccClientPendingRequests"]) < 2:
  75. raise Exception("Missing pending RADIUS Accounting requests")
  76. def test_radius_acct_unreachable2(dev, apdev):
  77. """RADIUS Accounting server unreachable(2)"""
  78. subprocess.call(['sudo', 'ip', 'ro', 'replace', '192.168.213.17', 'dev',
  79. 'lo'])
  80. params = hostapd.wpa2_eap_params(ssid="radius-acct")
  81. params['acct_server_addr'] = "192.168.213.17"
  82. params['acct_server_port'] = "18139"
  83. params['acct_server_shared_secret'] = "radius"
  84. hostapd.add_ap(apdev[0]['ifname'], params)
  85. hapd = hostapd.Hostapd(apdev[0]['ifname'])
  86. subprocess.call(['sudo', 'ip', 'ro', 'del', '192.168.213.17', 'dev', 'lo'])
  87. connect(dev[0], "radius-acct")
  88. logger.info("Checking for RADIUS retries")
  89. time.sleep(4)
  90. mib = hapd.get_mib()
  91. if "radiusAccClientRetransmissions" not in mib:
  92. raise Exception("Missing MIB fields")
  93. if int(mib["radiusAccClientRetransmissions"]) < 1 and int(mib["radiusAccClientPendingRequests"]) < 1:
  94. raise Exception("Missing pending or retransmitted RADIUS Accounting requests")
  95. def test_radius_acct(dev, apdev):
  96. """RADIUS Accounting"""
  97. as_hapd = hostapd.Hostapd("as")
  98. as_mib_start = as_hapd.get_mib(param="radius_server")
  99. params = hostapd.wpa2_eap_params(ssid="radius-acct")
  100. params['acct_server_addr'] = "127.0.0.1"
  101. params['acct_server_port'] = "1813"
  102. params['acct_server_shared_secret'] = "radius"
  103. params['radius_auth_req_attr'] = [ "126:s:Operator", "77:s:testing" ]
  104. params['radius_acct_req_attr'] = [ "126:s:Operator", "77:s:testing" ]
  105. hostapd.add_ap(apdev[0]['ifname'], params)
  106. hapd = hostapd.Hostapd(apdev[0]['ifname'])
  107. connect(dev[0], "radius-acct")
  108. dev[1].connect("radius-acct", key_mgmt="WPA-EAP", scan_freq="2412",
  109. eap="PAX", identity="test-class",
  110. password_hex="0123456789abcdef0123456789abcdef")
  111. dev[2].connect("radius-acct", key_mgmt="WPA-EAP",
  112. eap="GPSK", identity="gpsk-cui",
  113. password="abcdefghijklmnop0123456789abcdef",
  114. scan_freq="2412")
  115. logger.info("Checking for RADIUS counters")
  116. count = 0
  117. while True:
  118. mib = hapd.get_mib()
  119. if int(mib['radiusAccClientResponses']) >= 3:
  120. break
  121. time.sleep(0.1)
  122. count += 1
  123. if count > 10:
  124. raise Exception("Did not receive Accounting-Response packets")
  125. if int(mib['radiusAccClientRetransmissions']) > 0:
  126. raise Exception("Unexpected Accounting-Request retransmission")
  127. as_mib_end = as_hapd.get_mib(param="radius_server")
  128. req_s = int(as_mib_start['radiusAccServTotalRequests'])
  129. req_e = int(as_mib_end['radiusAccServTotalRequests'])
  130. if req_e < req_s + 2:
  131. raise Exception("Unexpected RADIUS server acct MIB value")
  132. acc_s = int(as_mib_start['radiusAuthServAccessAccepts'])
  133. acc_e = int(as_mib_end['radiusAuthServAccessAccepts'])
  134. if acc_e < acc_s + 1:
  135. raise Exception("Unexpected RADIUS server auth MIB value")
  136. def test_radius_acct_interim(dev, apdev):
  137. """RADIUS Accounting interim update"""
  138. as_hapd = hostapd.Hostapd("as")
  139. params = hostapd.wpa2_eap_params(ssid="radius-acct")
  140. params['acct_server_addr'] = "127.0.0.1"
  141. params['acct_server_port'] = "1813"
  142. params['acct_server_shared_secret'] = "radius"
  143. params['radius_acct_interim_interval'] = "1"
  144. hostapd.add_ap(apdev[0]['ifname'], params)
  145. hapd = hostapd.Hostapd(apdev[0]['ifname'])
  146. connect(dev[0], "radius-acct")
  147. logger.info("Checking for RADIUS counters")
  148. as_mib_start = as_hapd.get_mib(param="radius_server")
  149. time.sleep(3.1)
  150. as_mib_end = as_hapd.get_mib(param="radius_server")
  151. req_s = int(as_mib_start['radiusAccServTotalRequests'])
  152. req_e = int(as_mib_end['radiusAccServTotalRequests'])
  153. if req_e < req_s + 3:
  154. raise Exception("Unexpected RADIUS server acct MIB value")
  155. def test_radius_acct_interim_unreachable(dev, apdev):
  156. """RADIUS Accounting interim update with unreachable server"""
  157. params = hostapd.wpa2_eap_params(ssid="radius-acct")
  158. params['acct_server_addr'] = "127.0.0.1"
  159. params['acct_server_port'] = "18139"
  160. params['acct_server_shared_secret'] = "radius"
  161. params['radius_acct_interim_interval'] = "1"
  162. hapd = hostapd.add_ap(apdev[0]['ifname'], params)
  163. start = hapd.get_mib()
  164. connect(dev[0], "radius-acct")
  165. logger.info("Waiting for interium accounting updates")
  166. time.sleep(3.1)
  167. end = hapd.get_mib()
  168. req_s = int(start['radiusAccClientTimeouts'])
  169. req_e = int(end['radiusAccClientTimeouts'])
  170. if req_e < req_s + 2:
  171. raise Exception("Unexpected RADIUS server acct MIB value")
  172. def test_radius_das_disconnect(dev, apdev):
  173. """RADIUS Dynamic Authorization Extensions - Disconnect"""
  174. try:
  175. import pyrad.client
  176. import pyrad.packet
  177. import pyrad.dictionary
  178. import radius_das
  179. except ImportError:
  180. return "skip"
  181. params = hostapd.wpa2_eap_params(ssid="radius-das")
  182. params['radius_das_port'] = "3799"
  183. params['radius_das_client'] = "127.0.0.1 secret"
  184. params['radius_das_require_event_timestamp'] = "1"
  185. params['own_ip_addr'] = "127.0.0.1"
  186. params['nas_identifier'] = "nas.example.com"
  187. hapd = hostapd.add_ap(apdev[0]['ifname'], params)
  188. connect(dev[0], "radius-das")
  189. addr = dev[0].p2p_interface_addr()
  190. sta = hapd.get_sta(addr)
  191. id = sta['dot1xAuthSessionId']
  192. dict = pyrad.dictionary.Dictionary("dictionary.radius")
  193. srv = pyrad.client.Client(server="127.0.0.1", acctport=3799,
  194. secret="secret", dict=dict)
  195. srv.retries = 1
  196. srv.timeout = 1
  197. logger.info("Disconnect-Request with incorrect secret")
  198. req = radius_das.DisconnectPacket(dict=dict, secret="incorrect",
  199. User_Name="foo",
  200. NAS_Identifier="localhost",
  201. Event_Timestamp=int(time.time()))
  202. logger.debug(req)
  203. try:
  204. reply = srv.SendPacket(req)
  205. raise Exception("Unexpected response to Disconnect-Request")
  206. except pyrad.client.Timeout:
  207. logger.info("Disconnect-Request with incorrect secret properly ignored")
  208. logger.info("Disconnect-Request without Event-Timestamp")
  209. req = radius_das.DisconnectPacket(dict=dict, secret="secret",
  210. User_Name="psk.user@example.com")
  211. logger.debug(req)
  212. try:
  213. reply = srv.SendPacket(req)
  214. raise Exception("Unexpected response to Disconnect-Request")
  215. except pyrad.client.Timeout:
  216. logger.info("Disconnect-Request without Event-Timestamp properly ignored")
  217. logger.info("Disconnect-Request with non-matching Event-Timestamp")
  218. req = radius_das.DisconnectPacket(dict=dict, secret="secret",
  219. User_Name="psk.user@example.com",
  220. Event_Timestamp=123456789)
  221. logger.debug(req)
  222. try:
  223. reply = srv.SendPacket(req)
  224. raise Exception("Unexpected response to Disconnect-Request")
  225. except pyrad.client.Timeout:
  226. logger.info("Disconnect-Request with non-matching Event-Timestamp properly ignored")
  227. logger.info("Disconnect-Request with unsupported attribute")
  228. req = radius_das.DisconnectPacket(dict=dict, secret="secret",
  229. User_Name="foo",
  230. User_Password="foo",
  231. Event_Timestamp=int(time.time()))
  232. reply = srv.SendPacket(req)
  233. logger.debug("RADIUS response from hostapd")
  234. for i in reply.keys():
  235. logger.debug("%s: %s" % (i, reply[i]))
  236. if reply.code != pyrad.packet.DisconnectNAK:
  237. raise Exception("Unexpected response code")
  238. if 'Error-Cause' not in reply:
  239. raise Exception("Missing Error-Cause")
  240. if reply['Error-Cause'][0] != 401:
  241. raise Exception("Unexpected Error-Cause: {}".format(reply['Error-Cause']))
  242. logger.info("Disconnect-Request with invalid Calling-Station-Id")
  243. req = radius_das.DisconnectPacket(dict=dict, secret="secret",
  244. User_Name="foo",
  245. Calling_Station_Id="foo",
  246. Event_Timestamp=int(time.time()))
  247. reply = srv.SendPacket(req)
  248. logger.debug("RADIUS response from hostapd")
  249. for i in reply.keys():
  250. logger.debug("%s: %s" % (i, reply[i]))
  251. if reply.code != pyrad.packet.DisconnectNAK:
  252. raise Exception("Unexpected response code")
  253. if 'Error-Cause' not in reply:
  254. raise Exception("Missing Error-Cause")
  255. if reply['Error-Cause'][0] != 407:
  256. raise Exception("Unexpected Error-Cause: {}".format(reply['Error-Cause']))
  257. logger.info("Disconnect-Request with mismatching User-Name")
  258. req = radius_das.DisconnectPacket(dict=dict, secret="secret",
  259. User_Name="foo",
  260. Event_Timestamp=int(time.time()))
  261. reply = srv.SendPacket(req)
  262. logger.debug("RADIUS response from hostapd")
  263. for i in reply.keys():
  264. logger.debug("%s: %s" % (i, reply[i]))
  265. if reply.code != pyrad.packet.DisconnectNAK:
  266. raise Exception("Unexpected response code")
  267. if 'Error-Cause' not in reply:
  268. raise Exception("Missing Error-Cause")
  269. if reply['Error-Cause'][0] != 503:
  270. raise Exception("Unexpected Error-Cause: {}".format(reply['Error-Cause']))
  271. logger.info("Disconnect-Request with mismatching Calling-Station-Id")
  272. req = radius_das.DisconnectPacket(dict=dict, secret="secret",
  273. Calling_Station_Id="12:34:56:78:90:aa",
  274. Event_Timestamp=int(time.time()))
  275. reply = srv.SendPacket(req)
  276. logger.debug("RADIUS response from hostapd")
  277. for i in reply.keys():
  278. logger.debug("%s: %s" % (i, reply[i]))
  279. if reply.code != pyrad.packet.DisconnectNAK:
  280. raise Exception("Unexpected response code")
  281. if 'Error-Cause' not in reply:
  282. raise Exception("Missing Error-Cause")
  283. if reply['Error-Cause'][0] != 503:
  284. raise Exception("Unexpected Error-Cause: {}".format(reply['Error-Cause']))
  285. logger.info("Disconnect-Request with mismatching Acct-Session-Id")
  286. req = radius_das.DisconnectPacket(dict=dict, secret="secret",
  287. Acct_Session_Id="12345678-87654321",
  288. Event_Timestamp=int(time.time()))
  289. reply = srv.SendPacket(req)
  290. logger.debug("RADIUS response from hostapd")
  291. for i in reply.keys():
  292. logger.debug("%s: %s" % (i, reply[i]))
  293. if reply.code != pyrad.packet.DisconnectNAK:
  294. raise Exception("Unexpected response code")
  295. if 'Error-Cause' not in reply:
  296. raise Exception("Missing Error-Cause")
  297. if reply['Error-Cause'][0] != 503:
  298. raise Exception("Unexpected Error-Cause: {}".format(reply['Error-Cause']))
  299. ev = dev[0].wait_event(["CTRL-EVENT-DISCONNECTED"], timeout=1)
  300. if ev is not None:
  301. raise Exception("Unexpected disconnection")
  302. logger.info("Disconnect-Request with mismatching NAS-IP-Address")
  303. req = radius_das.DisconnectPacket(dict=dict, secret="secret",
  304. NAS_IP_Address="192.168.3.4",
  305. Acct_Session_Id=id,
  306. Event_Timestamp=int(time.time()))
  307. reply = srv.SendPacket(req)
  308. logger.debug("RADIUS response from hostapd")
  309. for i in reply.keys():
  310. logger.debug("%s: %s" % (i, reply[i]))
  311. if reply.code != pyrad.packet.DisconnectNAK:
  312. raise Exception("Unexpected response code")
  313. if 'Error-Cause' not in reply:
  314. raise Exception("Missing Error-Cause")
  315. if reply['Error-Cause'][0] != 403:
  316. raise Exception("Unexpected Error-Cause: {}".format(reply['Error-Cause']))
  317. logger.info("Disconnect-Request with mismatching NAS-Identifier")
  318. req = radius_das.DisconnectPacket(dict=dict, secret="secret",
  319. NAS_Identifier="unknown.example.com",
  320. Acct_Session_Id=id,
  321. Event_Timestamp=int(time.time()))
  322. reply = srv.SendPacket(req)
  323. logger.debug("RADIUS response from hostapd")
  324. for i in reply.keys():
  325. logger.debug("%s: %s" % (i, reply[i]))
  326. if reply.code != pyrad.packet.DisconnectNAK:
  327. raise Exception("Unexpected response code")
  328. if 'Error-Cause' not in reply:
  329. raise Exception("Missing Error-Cause")
  330. if reply['Error-Cause'][0] != 403:
  331. raise Exception("Unexpected Error-Cause: {}".format(reply['Error-Cause']))
  332. ev = dev[0].wait_event(["CTRL-EVENT-DISCONNECTED"], timeout=1)
  333. if ev is not None:
  334. raise Exception("Unexpected disconnection")
  335. logger.info("Disconnect-Request with matching Acct-Session-Id")
  336. req = radius_das.DisconnectPacket(dict=dict, secret="secret",
  337. NAS_IP_Address="127.0.0.1",
  338. NAS_Identifier="nas.example.com",
  339. Acct_Session_Id=id,
  340. Event_Timestamp=int(time.time()))
  341. reply = srv.SendPacket(req)
  342. logger.debug("RADIUS response from hostapd")
  343. for i in reply.keys():
  344. logger.debug("%s: %s" % (i, reply[i]))
  345. if reply.code != pyrad.packet.DisconnectACK:
  346. raise Exception("Unexpected response code")
  347. ev = dev[0].wait_event(["CTRL-EVENT-DISCONNECTED"])
  348. if ev is None:
  349. raise Exception("Timeout while waiting for disconnection")
  350. ev = dev[0].wait_event(["CTRL-EVENT-CONNECTED"])
  351. if ev is None:
  352. raise Exception("Timeout while waiting for re-connection")
  353. logger.info("Disconnect-Request with matching User-Name")
  354. req = radius_das.DisconnectPacket(dict=dict, secret="secret",
  355. NAS_Identifier="nas.example.com",
  356. User_Name="psk.user@example.com",
  357. Event_Timestamp=int(time.time()))
  358. reply = srv.SendPacket(req)
  359. logger.debug("RADIUS response from hostapd")
  360. for i in reply.keys():
  361. logger.debug("%s: %s" % (i, reply[i]))
  362. if reply.code != pyrad.packet.DisconnectACK:
  363. raise Exception("Unexpected response code")
  364. ev = dev[0].wait_event(["CTRL-EVENT-DISCONNECTED"])
  365. if ev is None:
  366. raise Exception("Timeout while waiting for disconnection")
  367. ev = dev[0].wait_event(["CTRL-EVENT-CONNECTED"])
  368. if ev is None:
  369. raise Exception("Timeout while waiting for re-connection")
  370. logger.info("Disconnect-Request with matching Calling-Station-Id")
  371. req = radius_das.DisconnectPacket(dict=dict, secret="secret",
  372. NAS_IP_Address="127.0.0.1",
  373. Calling_Station_Id=addr,
  374. Event_Timestamp=int(time.time()))
  375. reply = srv.SendPacket(req)
  376. logger.debug("RADIUS response from hostapd")
  377. for i in reply.keys():
  378. logger.debug("%s: %s" % (i, reply[i]))
  379. if reply.code != pyrad.packet.DisconnectACK:
  380. raise Exception("Unexpected response code")
  381. ev = dev[0].wait_event(["CTRL-EVENT-DISCONNECTED"])
  382. if ev is None:
  383. raise Exception("Timeout while waiting for disconnection")
  384. ev = dev[0].wait_event(["CTRL-EVENT-EAP-STARTED", "CTRL-EVENT-CONNECTED"])
  385. if ev is None:
  386. raise Exception("Timeout while waiting for re-connection")
  387. if "CTRL-EVENT-EAP-STARTED" not in ev:
  388. raise Exception("Unexpected skipping of EAP authentication in reconnection")
  389. ev = dev[0].wait_event(["CTRL-EVENT-CONNECTED"])
  390. if ev is None:
  391. raise Exception("Timeout while waiting for re-connection to complete")
  392. logger.info("Disconnect-Request with matching Calling-Station-Id and non-matching CUI")
  393. req = radius_das.DisconnectPacket(dict=dict, secret="secret",
  394. Calling_Station_Id=addr,
  395. Chargeable_User_Identity="foo@example.com",
  396. Event_Timestamp=int(time.time()))
  397. reply = srv.SendPacket(req)
  398. logger.debug("RADIUS response from hostapd")
  399. for i in reply.keys():
  400. logger.debug("%s: %s" % (i, reply[i]))
  401. if reply.code != pyrad.packet.DisconnectACK:
  402. raise Exception("Unexpected response code")
  403. ev = dev[0].wait_event(["CTRL-EVENT-DISCONNECTED"])
  404. if ev is None:
  405. raise Exception("Timeout while waiting for disconnection")
  406. ev = dev[0].wait_event(["CTRL-EVENT-CONNECTED"])
  407. if ev is None:
  408. raise Exception("Timeout while waiting for re-connection")
  409. logger.info("Disconnect-Request with matching CUI")
  410. dev[1].connect("radius-das", key_mgmt="WPA-EAP",
  411. eap="GPSK", identity="gpsk-cui",
  412. password="abcdefghijklmnop0123456789abcdef",
  413. scan_freq="2412")
  414. req = radius_das.DisconnectPacket(dict=dict, secret="secret",
  415. Chargeable_User_Identity="gpsk-chargeable-user-identity",
  416. Event_Timestamp=int(time.time()))
  417. reply = srv.SendPacket(req)
  418. logger.debug("RADIUS response from hostapd")
  419. for i in reply.keys():
  420. logger.debug("%s: %s" % (i, reply[i]))
  421. if reply.code != pyrad.packet.DisconnectACK:
  422. raise Exception("Unexpected response code")
  423. ev = dev[1].wait_event(["CTRL-EVENT-DISCONNECTED"])
  424. if ev is None:
  425. raise Exception("Timeout while waiting for disconnection")
  426. ev = dev[1].wait_event(["CTRL-EVENT-CONNECTED"])
  427. if ev is None:
  428. raise Exception("Timeout while waiting for re-connection")
  429. ev = dev[0].wait_event(["CTRL-EVENT-DISCONNECTED"], timeout=1)
  430. if ev is not None:
  431. raise Exception("Unexpected disconnection")
  432. def test_radius_das_coa(dev, apdev):
  433. """RADIUS Dynamic Authorization Extensions - CoA"""
  434. try:
  435. import pyrad.client
  436. import pyrad.packet
  437. import pyrad.dictionary
  438. import radius_das
  439. except ImportError:
  440. return "skip"
  441. params = hostapd.wpa2_eap_params(ssid="radius-das")
  442. params['radius_das_port'] = "3799"
  443. params['radius_das_client'] = "127.0.0.1 secret"
  444. params['radius_das_require_event_timestamp'] = "1"
  445. hapd = hostapd.add_ap(apdev[0]['ifname'], params)
  446. connect(dev[0], "radius-das")
  447. addr = dev[0].p2p_interface_addr()
  448. sta = hapd.get_sta(addr)
  449. id = sta['dot1xAuthSessionId']
  450. dict = pyrad.dictionary.Dictionary("dictionary.radius")
  451. srv = pyrad.client.Client(server="127.0.0.1", acctport=3799,
  452. secret="secret", dict=dict)
  453. srv.retries = 1
  454. srv.timeout = 1
  455. # hostapd does not currently support CoA-Request, so NAK is expected
  456. logger.info("CoA-Request with matching Acct-Session-Id")
  457. req = radius_das.CoAPacket(dict=dict, secret="secret",
  458. Acct_Session_Id=id,
  459. Event_Timestamp=int(time.time()))
  460. reply = srv.SendPacket(req)
  461. logger.debug("RADIUS response from hostapd")
  462. for i in reply.keys():
  463. logger.debug("%s: %s" % (i, reply[i]))
  464. if reply.code != pyrad.packet.CoANAK:
  465. raise Exception("Unexpected response code")
  466. if 'Error-Cause' not in reply:
  467. raise Exception("Missing Error-Cause")
  468. if reply['Error-Cause'][0] != 405:
  469. raise Exception("Unexpected Error-Cause: {}".format(reply['Error-Cause']))
  470. def test_radius_ipv6(dev, apdev):
  471. """RADIUS connection over IPv6"""
  472. params = {}
  473. params['ssid'] = 'as'
  474. params['beacon_int'] = '2000'
  475. params['radius_server_clients'] = 'auth_serv/radius_clients_ipv6.conf'
  476. params['radius_server_ipv6'] = '1'
  477. params['radius_server_auth_port'] = '18129'
  478. params['radius_server_acct_port'] = '18139'
  479. params['eap_server'] = '1'
  480. params['eap_user_file'] = 'auth_serv/eap_user.conf'
  481. params['ca_cert'] = 'auth_serv/ca.pem'
  482. params['server_cert'] = 'auth_serv/server.pem'
  483. params['private_key'] = 'auth_serv/server.key'
  484. hostapd.add_ap(apdev[1]['ifname'], params)
  485. params = hostapd.wpa2_eap_params(ssid="radius-ipv6")
  486. params['auth_server_addr'] = "::0"
  487. params['auth_server_port'] = "18129"
  488. params['acct_server_addr'] = "::0"
  489. params['acct_server_port'] = "18139"
  490. params['acct_server_shared_secret'] = "radius"
  491. params['own_ip_addr'] = "::0"
  492. hostapd.add_ap(apdev[0]['ifname'], params)
  493. connect(dev[0], "radius-ipv6")
  494. def test_radius_macacl(dev, apdev):
  495. """RADIUS MAC ACL"""
  496. params = hostapd.radius_params()
  497. params["ssid"] = "radius"
  498. params["macaddr_acl"] = "2"
  499. hostapd.add_ap(apdev[0]['ifname'], params)
  500. dev[0].connect("radius", key_mgmt="NONE", scan_freq="2412")
  501. def test_radius_failover(dev, apdev):
  502. """RADIUS Authentication and Accounting server failover"""
  503. subprocess.call(['sudo', 'ip', 'ro', 'replace', '192.168.213.17', 'dev',
  504. 'lo'])
  505. as_hapd = hostapd.Hostapd("as")
  506. as_mib_start = as_hapd.get_mib(param="radius_server")
  507. params = hostapd.wpa2_eap_params(ssid="radius-failover")
  508. params["auth_server_addr"] = "192.168.213.17"
  509. params["auth_server_port"] = "1812"
  510. params["auth_server_shared_secret"] = "testing"
  511. params['acct_server_addr'] = "192.168.213.17"
  512. params['acct_server_port'] = "1813"
  513. params['acct_server_shared_secret'] = "testing"
  514. hapd = hostapd.add_ap(apdev[0]['ifname'], params, no_enable=True)
  515. hapd.set("auth_server_addr", "127.0.0.1")
  516. hapd.set("auth_server_port", "1812")
  517. hapd.set("auth_server_shared_secret", "radius")
  518. hapd.set('acct_server_addr', "127.0.0.1")
  519. hapd.set('acct_server_port', "1813")
  520. hapd.set('acct_server_shared_secret', "radius")
  521. hapd.enable()
  522. ev = hapd.wait_event(["AP-ENABLED", "AP-DISABLED"], timeout=30)
  523. if ev is None:
  524. raise Exception("AP startup timed out")
  525. if "AP-ENABLED" not in ev:
  526. raise Exception("AP startup failed")
  527. try:
  528. subprocess.call(['sudo', 'ip', 'ro', 'replace', 'prohibit',
  529. '192.168.213.17'])
  530. dev[0].request("SET EAPOL::authPeriod 5")
  531. connect(dev[0], "radius-failover", wait_connect=False)
  532. ev = dev[0].wait_event(["CTRL-EVENT-CONNECTED"], timeout=60)
  533. if ev is None:
  534. raise Exception("Connection with the AP timed out")
  535. finally:
  536. dev[0].request("SET EAPOL::authPeriod 30")
  537. subprocess.call(['sudo', 'ip', 'ro', 'del', '192.168.213.17'])
  538. as_mib_end = as_hapd.get_mib(param="radius_server")
  539. req_s = int(as_mib_start['radiusAccServTotalRequests'])
  540. req_e = int(as_mib_end['radiusAccServTotalRequests'])
  541. if req_e <= req_s:
  542. raise Exception("Unexpected RADIUS server acct MIB value")
  543. def run_pyrad_server(srv, stop_event):
  544. srv.RunWithStop(stop_event)
  545. def test_radius_protocol(dev, apdev):
  546. """RADIUS Authentication protocol tests with a fake server"""
  547. try:
  548. import pyrad.server
  549. import pyrad.packet
  550. import pyrad.dictionary
  551. except ImportError:
  552. return "skip"
  553. class TestServer(pyrad.server.Server):
  554. def _HandleAuthPacket(self, pkt):
  555. pyrad.server.Server._HandleAuthPacket(self, pkt)
  556. logger.info("Received authentication request")
  557. reply = self.CreateReplyPacket(pkt)
  558. reply.code = pyrad.packet.AccessAccept
  559. # TODO: Add attributes. For now, this works as a test case for
  560. # missing Message-Authenticator.
  561. self.SendReplyPacket(pkt.fd, reply)
  562. def RunWithStop(self, stop_event):
  563. self._poll = select.poll()
  564. self._fdmap = {}
  565. self._PrepareSockets()
  566. while not stop_event.is_set():
  567. for (fd, event) in self._poll.poll(1000):
  568. if event == select.POLLIN:
  569. try:
  570. fdo = self._fdmap[fd]
  571. self._ProcessInput(fdo)
  572. except ServerPacketError as err:
  573. logger.info("pyrad server dropping packet: " + str(err))
  574. except pyrad.packet.PacketError as err:
  575. logger.info("pyrad server received invalid packet: " + str(err))
  576. else:
  577. logger.error("Unexpected event in pyrad server main loop")
  578. srv = TestServer(dict=pyrad.dictionary.Dictionary("dictionary.radius"),
  579. authport=18138, acctport=18139)
  580. srv.hosts["127.0.0.1"] = pyrad.server.RemoteHost("127.0.0.1",
  581. "radius",
  582. "localhost")
  583. srv.BindToAddress("")
  584. t_stop = threading.Event()
  585. t = threading.Thread(target=run_pyrad_server, args=(srv, t_stop))
  586. t.start()
  587. try:
  588. params = hostapd.wpa2_eap_params(ssid="radius-test")
  589. params['auth_server_port'] = "18138"
  590. hapd = hostapd.add_ap(apdev[0]['ifname'], params)
  591. connect(dev[0], "radius-test", wait_connect=False)
  592. ev = dev[0].wait_event(["CTRL-EVENT-EAP-STARTED"], timeout=15)
  593. if ev is None:
  594. raise Exception("Timeout on EAP start")
  595. time.sleep(1)
  596. finally:
  597. t_stop.set()
  598. t.join()