]>
Commit | Line | Data |
---|---|---|
7fd15145 | 1 | # RADIUS tests |
a3b2bdaf | 2 | # Copyright (c) 2013-2014, Jouni Malinen <j@w1.fi> |
7fd15145 JM |
3 | # |
4 | # This software may be distributed under the terms of the BSD license. | |
5 | # See README for more details. | |
6 | ||
7 | import logging | |
8 | logger = logging.getLogger() | |
9 | import time | |
10 | ||
11 | import hostapd | |
12 | ||
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 | ||
19 | def test_radius_auth_unreachable(dev, apdev): | |
20 | """RADIUS Authentication server unreachable""" | |
21 | params = hostapd.wpa2_eap_params(ssid="radius-auth") | |
22 | params['auth_server_port'] = "18139" | |
23 | hostapd.add_ap(apdev[0]['ifname'], params) | |
24 | hapd = hostapd.Hostapd(apdev[0]['ifname']) | |
25 | connect(dev[0], "radius-auth", wait_connect=False) | |
26 | ev = dev[0].wait_event(["CTRL-EVENT-EAP-STARTED"]) | |
27 | if ev is None: | |
28 | raise Exception("Timeout on EAP start") | |
29 | logger.info("Checking for RADIUS retries") | |
30 | time.sleep(4) | |
31 | mib = hapd.get_mib() | |
32 | if "radiusAuthClientAccessRequests" not in mib: | |
33 | raise Exception("Missing MIB fields") | |
34 | if int(mib["radiusAuthClientAccessRetransmissions"]) < 1: | |
35 | raise Exception("Missing RADIUS Authentication retransmission") | |
36 | if int(mib["radiusAuthClientPendingRequests"]) < 1: | |
37 | raise Exception("Missing pending RADIUS Authentication request") | |
38 | ||
39 | def test_radius_acct_unreachable(dev, apdev): | |
40 | """RADIUS Accounting server unreachable""" | |
41 | params = hostapd.wpa2_eap_params(ssid="radius-acct") | |
42 | params['acct_server_addr'] = "127.0.0.1" | |
43 | params['acct_server_port'] = "18139" | |
44 | params['acct_server_shared_secret'] = "radius" | |
45 | hostapd.add_ap(apdev[0]['ifname'], params) | |
46 | hapd = hostapd.Hostapd(apdev[0]['ifname']) | |
47 | connect(dev[0], "radius-acct") | |
48 | logger.info("Checking for RADIUS retries") | |
49 | time.sleep(4) | |
50 | mib = hapd.get_mib() | |
51 | if "radiusAccClientRetransmissions" not in mib: | |
52 | raise Exception("Missing MIB fields") | |
53 | if int(mib["radiusAccClientRetransmissions"]) < 2: | |
54 | raise Exception("Missing RADIUS Accounting retransmissions") | |
55 | if int(mib["radiusAccClientPendingRequests"]) < 2: | |
56 | raise Exception("Missing pending RADIUS Accounting requests") | |
4287bb76 JM |
57 | |
58 | def test_radius_acct(dev, apdev): | |
59 | """RADIUS Accounting""" | |
4fcee244 JM |
60 | as_hapd = hostapd.Hostapd("as") |
61 | as_mib_start = as_hapd.get_mib(param="radius_server") | |
4287bb76 JM |
62 | params = hostapd.wpa2_eap_params(ssid="radius-acct") |
63 | params['acct_server_addr'] = "127.0.0.1" | |
64 | params['acct_server_port'] = "1813" | |
65 | params['acct_server_shared_secret'] = "radius" | |
66 | hostapd.add_ap(apdev[0]['ifname'], params) | |
67 | hapd = hostapd.Hostapd(apdev[0]['ifname']) | |
68 | connect(dev[0], "radius-acct") | |
69 | logger.info("Checking for RADIUS counters") | |
70 | count = 0 | |
71 | while True: | |
72 | mib = hapd.get_mib() | |
73 | if int(mib['radiusAccClientResponses']) >= 2: | |
74 | break | |
75 | time.sleep(0.1) | |
76 | count += 1 | |
77 | if count > 10: | |
78 | raise Exception("Did not receive Accounting-Response packets") | |
79 | ||
80 | if int(mib['radiusAccClientRetransmissions']) > 0: | |
81 | raise Exception("Unexpected Accounting-Request retransmission") | |
4fcee244 JM |
82 | |
83 | as_mib_end = as_hapd.get_mib(param="radius_server") | |
84 | ||
85 | req_s = int(as_mib_start['radiusAccServTotalRequests']) | |
86 | req_e = int(as_mib_end['radiusAccServTotalRequests']) | |
87 | if req_e < req_s + 2: | |
88 | raise Exception("Unexpected RADIUS server acct MIB value") | |
89 | ||
90 | acc_s = int(as_mib_start['radiusAuthServAccessAccepts']) | |
91 | acc_e = int(as_mib_end['radiusAuthServAccessAccepts']) | |
92 | if acc_e < acc_s + 1: | |
93 | raise Exception("Unexpected RADIUS server auth MIB value") | |
a3b2bdaf JM |
94 | |
95 | def test_radius_das_disconnect(dev, apdev): | |
96 | """RADIUS Dynamic Authorization Extensions - Disconnect""" | |
97 | try: | |
98 | import pyrad.client | |
99 | import pyrad.packet | |
100 | import pyrad.dictionary | |
101 | import radius_das | |
102 | except ImportError: | |
103 | return "skip" | |
104 | ||
105 | params = hostapd.wpa2_eap_params(ssid="radius-das") | |
106 | params['radius_das_port'] = "3799" | |
107 | params['radius_das_client'] = "127.0.0.1 secret" | |
108 | params['radius_das_require_event_timestamp'] = "1" | |
41ff0fa6 JM |
109 | params['own_ip_addr'] = "127.0.0.1" |
110 | params['nas_identifier'] = "nas.example.com" | |
a3b2bdaf JM |
111 | hapd = hostapd.add_ap(apdev[0]['ifname'], params) |
112 | connect(dev[0], "radius-das") | |
113 | addr = dev[0].p2p_interface_addr() | |
114 | sta = hapd.get_sta(addr) | |
115 | id = sta['dot1xAuthSessionId'] | |
116 | ||
117 | dict = pyrad.dictionary.Dictionary("dictionary.radius") | |
118 | ||
119 | srv = pyrad.client.Client(server="127.0.0.1", acctport=3799, | |
120 | secret="secret", dict=dict) | |
121 | srv.retries = 1 | |
122 | srv.timeout = 1 | |
123 | ||
124 | logger.info("Disconnect-Request with incorrect secret") | |
125 | req = radius_das.DisconnectPacket(dict=dict, secret="incorrect", | |
126 | User_Name="foo", | |
127 | NAS_Identifier="localhost", | |
128 | Event_Timestamp=int(time.time())) | |
129 | logger.debug(req) | |
130 | try: | |
131 | reply = srv.SendPacket(req) | |
132 | raise Exception("Unexpected response to Disconnect-Request") | |
133 | except pyrad.client.Timeout: | |
134 | logger.info("Disconnect-Request with incorrect secret properly ignored") | |
135 | ||
136 | logger.info("Disconnect-Request without Event-Timestamp") | |
137 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
138 | User_Name="psk.user@example.com") | |
139 | logger.debug(req) | |
140 | try: | |
141 | reply = srv.SendPacket(req) | |
142 | raise Exception("Unexpected response to Disconnect-Request") | |
143 | except pyrad.client.Timeout: | |
144 | logger.info("Disconnect-Request without Event-Timestamp properly ignored") | |
145 | ||
146 | logger.info("Disconnect-Request with non-matching Event-Timestamp") | |
147 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
148 | User_Name="psk.user@example.com", | |
149 | Event_Timestamp=123456789) | |
150 | logger.debug(req) | |
151 | try: | |
152 | reply = srv.SendPacket(req) | |
153 | raise Exception("Unexpected response to Disconnect-Request") | |
154 | except pyrad.client.Timeout: | |
155 | logger.info("Disconnect-Request with non-matching Event-Timestamp properly ignored") | |
156 | ||
157 | logger.info("Disconnect-Request with unsupported attribute") | |
158 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
159 | User_Name="foo", | |
160 | User_Password="foo", | |
161 | Event_Timestamp=int(time.time())) | |
162 | reply = srv.SendPacket(req) | |
163 | logger.debug("RADIUS response from hostapd") | |
164 | for i in reply.keys(): | |
165 | logger.debug("%s: %s" % (i, reply[i])) | |
166 | if reply.code != pyrad.packet.DisconnectNAK: | |
167 | raise Exception("Unexpected response code") | |
168 | if 'Error-Cause' not in reply: | |
169 | raise Exception("Missing Error-Cause") | |
170 | if reply['Error-Cause'][0] != 401: | |
171 | raise Exception("Unexpected Error-Cause: {}".format(reply['Error-Cause'])) | |
172 | ||
173 | logger.info("Disconnect-Request with invalid Calling-Station-Id") | |
174 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
175 | User_Name="foo", | |
176 | Calling_Station_Id="foo", | |
177 | Event_Timestamp=int(time.time())) | |
178 | reply = srv.SendPacket(req) | |
179 | logger.debug("RADIUS response from hostapd") | |
180 | for i in reply.keys(): | |
181 | logger.debug("%s: %s" % (i, reply[i])) | |
182 | if reply.code != pyrad.packet.DisconnectNAK: | |
183 | raise Exception("Unexpected response code") | |
184 | if 'Error-Cause' not in reply: | |
185 | raise Exception("Missing Error-Cause") | |
186 | if reply['Error-Cause'][0] != 407: | |
187 | raise Exception("Unexpected Error-Cause: {}".format(reply['Error-Cause'])) | |
188 | ||
189 | logger.info("Disconnect-Request with mismatching User-Name") | |
190 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
191 | User_Name="foo", | |
192 | Event_Timestamp=int(time.time())) | |
193 | reply = srv.SendPacket(req) | |
194 | logger.debug("RADIUS response from hostapd") | |
195 | for i in reply.keys(): | |
196 | logger.debug("%s: %s" % (i, reply[i])) | |
197 | if reply.code != pyrad.packet.DisconnectNAK: | |
198 | raise Exception("Unexpected response code") | |
199 | if 'Error-Cause' not in reply: | |
200 | raise Exception("Missing Error-Cause") | |
201 | if reply['Error-Cause'][0] != 503: | |
202 | raise Exception("Unexpected Error-Cause: {}".format(reply['Error-Cause'])) | |
203 | ||
204 | logger.info("Disconnect-Request with mismatching Calling-Station-Id") | |
205 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
206 | Calling_Station_Id="12:34:56:78:90:aa", | |
207 | Event_Timestamp=int(time.time())) | |
208 | reply = srv.SendPacket(req) | |
209 | logger.debug("RADIUS response from hostapd") | |
210 | for i in reply.keys(): | |
211 | logger.debug("%s: %s" % (i, reply[i])) | |
212 | if reply.code != pyrad.packet.DisconnectNAK: | |
213 | raise Exception("Unexpected response code") | |
214 | if 'Error-Cause' not in reply: | |
215 | raise Exception("Missing Error-Cause") | |
216 | if reply['Error-Cause'][0] != 503: | |
217 | raise Exception("Unexpected Error-Cause: {}".format(reply['Error-Cause'])) | |
218 | ||
219 | logger.info("Disconnect-Request with mismatching Acct-Session-Id") | |
220 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
221 | Acct_Session_Id="12345678-87654321", | |
222 | Event_Timestamp=int(time.time())) | |
223 | reply = srv.SendPacket(req) | |
224 | logger.debug("RADIUS response from hostapd") | |
225 | for i in reply.keys(): | |
226 | logger.debug("%s: %s" % (i, reply[i])) | |
227 | if reply.code != pyrad.packet.DisconnectNAK: | |
228 | raise Exception("Unexpected response code") | |
229 | if 'Error-Cause' not in reply: | |
230 | raise Exception("Missing Error-Cause") | |
231 | if reply['Error-Cause'][0] != 503: | |
232 | raise Exception("Unexpected Error-Cause: {}".format(reply['Error-Cause'])) | |
233 | ||
234 | ev = dev[0].wait_event(["CTRL-EVENT-DISCONNECTED"], timeout=1) | |
235 | if ev is not None: | |
236 | raise Exception("Unexpected disconnection") | |
237 | ||
41ff0fa6 JM |
238 | logger.info("Disconnect-Request with mismatching NAS-IP-Address") |
239 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
240 | NAS_IP_Address="192.168.3.4", | |
241 | Acct_Session_Id=id, | |
242 | Event_Timestamp=int(time.time())) | |
243 | reply = srv.SendPacket(req) | |
244 | logger.debug("RADIUS response from hostapd") | |
245 | for i in reply.keys(): | |
246 | logger.debug("%s: %s" % (i, reply[i])) | |
247 | if reply.code != pyrad.packet.DisconnectNAK: | |
248 | raise Exception("Unexpected response code") | |
249 | if 'Error-Cause' not in reply: | |
250 | raise Exception("Missing Error-Cause") | |
251 | if reply['Error-Cause'][0] != 403: | |
252 | raise Exception("Unexpected Error-Cause: {}".format(reply['Error-Cause'])) | |
253 | ||
254 | logger.info("Disconnect-Request with mismatching NAS-Identifier") | |
255 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
256 | NAS_Identifier="unknown.example.com", | |
257 | Acct_Session_Id=id, | |
258 | Event_Timestamp=int(time.time())) | |
259 | reply = srv.SendPacket(req) | |
260 | logger.debug("RADIUS response from hostapd") | |
261 | for i in reply.keys(): | |
262 | logger.debug("%s: %s" % (i, reply[i])) | |
263 | if reply.code != pyrad.packet.DisconnectNAK: | |
264 | raise Exception("Unexpected response code") | |
265 | if 'Error-Cause' not in reply: | |
266 | raise Exception("Missing Error-Cause") | |
267 | if reply['Error-Cause'][0] != 403: | |
268 | raise Exception("Unexpected Error-Cause: {}".format(reply['Error-Cause'])) | |
269 | ||
270 | ev = dev[0].wait_event(["CTRL-EVENT-DISCONNECTED"], timeout=1) | |
271 | if ev is not None: | |
272 | raise Exception("Unexpected disconnection") | |
273 | ||
a3b2bdaf JM |
274 | logger.info("Disconnect-Request with matching Acct-Session-Id") |
275 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
41ff0fa6 JM |
276 | NAS_IP_Address="127.0.0.1", |
277 | NAS_Identifier="nas.example.com", | |
a3b2bdaf JM |
278 | Acct_Session_Id=id, |
279 | Event_Timestamp=int(time.time())) | |
280 | reply = srv.SendPacket(req) | |
281 | logger.debug("RADIUS response from hostapd") | |
282 | for i in reply.keys(): | |
283 | logger.debug("%s: %s" % (i, reply[i])) | |
284 | if reply.code != pyrad.packet.DisconnectACK: | |
285 | raise Exception("Unexpected response code") | |
286 | ||
287 | ev = dev[0].wait_event(["CTRL-EVENT-DISCONNECTED"]) | |
288 | if ev is None: | |
289 | raise Exception("Timeout while waiting for disconnection") | |
290 | ev = dev[0].wait_event(["CTRL-EVENT-CONNECTED"]) | |
291 | if ev is None: | |
292 | raise Exception("Timeout while waiting for re-connection") | |
293 | ||
294 | logger.info("Disconnect-Request with matching User-Name") | |
295 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
41ff0fa6 | 296 | NAS_Identifier="nas.example.com", |
a3b2bdaf JM |
297 | User_Name="psk.user@example.com", |
298 | Event_Timestamp=int(time.time())) | |
299 | reply = srv.SendPacket(req) | |
300 | logger.debug("RADIUS response from hostapd") | |
301 | for i in reply.keys(): | |
302 | logger.debug("%s: %s" % (i, reply[i])) | |
303 | if reply.code != pyrad.packet.DisconnectACK: | |
304 | raise Exception("Unexpected response code") | |
305 | ||
306 | ev = dev[0].wait_event(["CTRL-EVENT-DISCONNECTED"]) | |
307 | if ev is None: | |
308 | raise Exception("Timeout while waiting for disconnection") | |
309 | ev = dev[0].wait_event(["CTRL-EVENT-CONNECTED"]) | |
310 | if ev is None: | |
311 | raise Exception("Timeout while waiting for re-connection") | |
312 | ||
313 | logger.info("Disconnect-Request with matching Calling-Station-Id") | |
314 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
41ff0fa6 | 315 | NAS_IP_Address="127.0.0.1", |
a3b2bdaf JM |
316 | Calling_Station_Id=addr, |
317 | Event_Timestamp=int(time.time())) | |
318 | reply = srv.SendPacket(req) | |
319 | logger.debug("RADIUS response from hostapd") | |
320 | for i in reply.keys(): | |
321 | logger.debug("%s: %s" % (i, reply[i])) | |
322 | if reply.code != pyrad.packet.DisconnectACK: | |
323 | raise Exception("Unexpected response code") | |
324 | ||
325 | ev = dev[0].wait_event(["CTRL-EVENT-DISCONNECTED"]) | |
326 | if ev is None: | |
327 | raise Exception("Timeout while waiting for disconnection") | |
e58f59cb | 328 | ev = dev[0].wait_event(["CTRL-EVENT-EAP-STARTED", "CTRL-EVENT-CONNECTED"]) |
a3b2bdaf JM |
329 | if ev is None: |
330 | raise Exception("Timeout while waiting for re-connection") | |
e58f59cb JM |
331 | if "CTRL-EVENT-EAP-STARTED" not in ev: |
332 | raise Exception("Unexpected skipping of EAP authentication in reconnection") | |
333 | ev = dev[0].wait_event(["CTRL-EVENT-CONNECTED"]) | |
334 | if ev is None: | |
335 | raise Exception("Timeout while waiting for re-connection to complete") | |
a3b2bdaf JM |
336 | |
337 | logger.info("Disconnect-Request with matching Calling-Station-Id and non-matching CUI") | |
338 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
339 | Calling_Station_Id=addr, | |
340 | Chargeable_User_Identity="foo@example.com", | |
341 | Event_Timestamp=int(time.time())) | |
342 | reply = srv.SendPacket(req) | |
343 | logger.debug("RADIUS response from hostapd") | |
344 | for i in reply.keys(): | |
345 | logger.debug("%s: %s" % (i, reply[i])) | |
346 | if reply.code != pyrad.packet.DisconnectACK: | |
347 | raise Exception("Unexpected response code") | |
348 | ||
349 | ev = dev[0].wait_event(["CTRL-EVENT-DISCONNECTED"]) | |
350 | if ev is None: | |
351 | raise Exception("Timeout while waiting for disconnection") | |
352 | ev = dev[0].wait_event(["CTRL-EVENT-CONNECTED"]) | |
353 | if ev is None: | |
354 | raise Exception("Timeout while waiting for re-connection") | |
55497a51 JM |
355 | |
356 | def test_radius_das_coa(dev, apdev): | |
357 | """RADIUS Dynamic Authorization Extensions - CoA""" | |
358 | try: | |
359 | import pyrad.client | |
360 | import pyrad.packet | |
361 | import pyrad.dictionary | |
362 | import radius_das | |
363 | except ImportError: | |
364 | return "skip" | |
365 | ||
366 | params = hostapd.wpa2_eap_params(ssid="radius-das") | |
367 | params['radius_das_port'] = "3799" | |
368 | params['radius_das_client'] = "127.0.0.1 secret" | |
369 | params['radius_das_require_event_timestamp'] = "1" | |
370 | hapd = hostapd.add_ap(apdev[0]['ifname'], params) | |
371 | connect(dev[0], "radius-das") | |
372 | addr = dev[0].p2p_interface_addr() | |
373 | sta = hapd.get_sta(addr) | |
374 | id = sta['dot1xAuthSessionId'] | |
375 | ||
376 | dict = pyrad.dictionary.Dictionary("dictionary.radius") | |
377 | ||
378 | srv = pyrad.client.Client(server="127.0.0.1", acctport=3799, | |
379 | secret="secret", dict=dict) | |
380 | srv.retries = 1 | |
381 | srv.timeout = 1 | |
382 | ||
383 | # hostapd does not currently support CoA-Request, so NAK is expected | |
384 | logger.info("CoA-Request with matching Acct-Session-Id") | |
385 | req = radius_das.CoAPacket(dict=dict, secret="secret", | |
386 | Acct_Session_Id=id, | |
387 | Event_Timestamp=int(time.time())) | |
388 | reply = srv.SendPacket(req) | |
389 | logger.debug("RADIUS response from hostapd") | |
390 | for i in reply.keys(): | |
391 | logger.debug("%s: %s" % (i, reply[i])) | |
392 | if reply.code != pyrad.packet.CoANAK: | |
393 | raise Exception("Unexpected response code") | |
394 | if 'Error-Cause' not in reply: | |
395 | raise Exception("Missing Error-Cause") | |
396 | if reply['Error-Cause'][0] != 405: | |
397 | raise Exception("Unexpected Error-Cause: {}".format(reply['Error-Cause'])) |