]>
Commit | Line | Data |
---|---|---|
7fd15145 | 1 | # RADIUS tests |
5ff53fd6 | 2 | # Copyright (c) 2013-2015, 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 | ||
2ab957af | 7 | import binascii |
7c8f5ea6 | 8 | import hashlib |
a47f815f | 9 | import hmac |
7fd15145 JM |
10 | import logging |
11 | logger = logging.getLogger() | |
5ff53fd6 | 12 | import os |
8f614cd7 | 13 | import select |
a47f815f | 14 | import struct |
6a188ba3 | 15 | import subprocess |
8f614cd7 | 16 | import threading |
7fd15145 JM |
17 | import time |
18 | ||
19 | import hostapd | |
03aac597 | 20 | from utils import HwsimSkip, require_under_vm, skip_with_fips |
2ab957af | 21 | from test_ap_hs20 import build_dhcp_ack |
03aac597 | 22 | from test_ap_ft import ft_params1 |
7fd15145 JM |
23 | |
24 | def connect(dev, ssid, wait_connect=True): | |
25 | dev.connect(ssid, key_mgmt="WPA-EAP", scan_freq="2412", | |
26 | eap="PSK", identity="psk.user@example.com", | |
27 | password_hex="0123456789abcdef0123456789abcdef", | |
28 | wait_connect=wait_connect) | |
29 | ||
30 | def test_radius_auth_unreachable(dev, apdev): | |
31 | """RADIUS Authentication server unreachable""" | |
32 | params = hostapd.wpa2_eap_params(ssid="radius-auth") | |
33 | params['auth_server_port'] = "18139" | |
34 | hostapd.add_ap(apdev[0]['ifname'], params) | |
35 | hapd = hostapd.Hostapd(apdev[0]['ifname']) | |
36 | connect(dev[0], "radius-auth", wait_connect=False) | |
37 | ev = dev[0].wait_event(["CTRL-EVENT-EAP-STARTED"]) | |
38 | if ev is None: | |
39 | raise Exception("Timeout on EAP start") | |
40 | logger.info("Checking for RADIUS retries") | |
41 | time.sleep(4) | |
42 | mib = hapd.get_mib() | |
43 | if "radiusAuthClientAccessRequests" not in mib: | |
44 | raise Exception("Missing MIB fields") | |
45 | if int(mib["radiusAuthClientAccessRetransmissions"]) < 1: | |
46 | raise Exception("Missing RADIUS Authentication retransmission") | |
47 | if int(mib["radiusAuthClientPendingRequests"]) < 1: | |
48 | raise Exception("Missing pending RADIUS Authentication request") | |
49 | ||
6a188ba3 JM |
50 | def test_radius_auth_unreachable2(dev, apdev): |
51 | """RADIUS Authentication server unreachable (2)""" | |
c4668009 | 52 | subprocess.call(['ip', 'ro', 'replace', '192.168.213.17', 'dev', 'lo']) |
6a188ba3 JM |
53 | params = hostapd.wpa2_eap_params(ssid="radius-auth") |
54 | params['auth_server_addr'] = "192.168.213.17" | |
55 | params['auth_server_port'] = "18139" | |
56 | hostapd.add_ap(apdev[0]['ifname'], params) | |
57 | hapd = hostapd.Hostapd(apdev[0]['ifname']) | |
c4668009 | 58 | subprocess.call(['ip', 'ro', 'del', '192.168.213.17', 'dev', 'lo']) |
6a188ba3 JM |
59 | connect(dev[0], "radius-auth", wait_connect=False) |
60 | ev = dev[0].wait_event(["CTRL-EVENT-EAP-STARTED"]) | |
61 | if ev is None: | |
62 | raise Exception("Timeout on EAP start") | |
63 | logger.info("Checking for RADIUS retries") | |
64 | time.sleep(4) | |
65 | mib = hapd.get_mib() | |
66 | if "radiusAuthClientAccessRequests" not in mib: | |
67 | raise Exception("Missing MIB fields") | |
68 | if int(mib["radiusAuthClientAccessRetransmissions"]) < 1: | |
69 | raise Exception("Missing RADIUS Authentication retransmission") | |
70 | ||
abeea374 JM |
71 | def test_radius_auth_unreachable3(dev, apdev): |
72 | """RADIUS Authentication server initially unreachable, but then available""" | |
73 | subprocess.call(['ip', 'ro', 'replace', 'blackhole', '192.168.213.18']) | |
74 | params = hostapd.wpa2_eap_params(ssid="radius-auth") | |
75 | params['auth_server_addr'] = "192.168.213.18" | |
76 | hostapd.add_ap(apdev[0]['ifname'], params) | |
77 | hapd = hostapd.Hostapd(apdev[0]['ifname']) | |
78 | connect(dev[0], "radius-auth", wait_connect=False) | |
79 | ev = dev[0].wait_event(["CTRL-EVENT-EAP-STARTED"]) | |
80 | if ev is None: | |
81 | raise Exception("Timeout on EAP start") | |
82 | subprocess.call(['ip', 'ro', 'del', 'blackhole', '192.168.213.18']) | |
83 | time.sleep(0.1) | |
84 | dev[0].request("DISCONNECT") | |
85 | hapd.set('auth_server_addr_replace', '127.0.0.1') | |
86 | dev[0].request("RECONNECT") | |
87 | ||
88 | dev[0].wait_connected() | |
89 | ||
7fd15145 JM |
90 | def test_radius_acct_unreachable(dev, apdev): |
91 | """RADIUS Accounting server unreachable""" | |
92 | params = hostapd.wpa2_eap_params(ssid="radius-acct") | |
93 | params['acct_server_addr'] = "127.0.0.1" | |
94 | params['acct_server_port'] = "18139" | |
95 | params['acct_server_shared_secret'] = "radius" | |
96 | hostapd.add_ap(apdev[0]['ifname'], params) | |
97 | hapd = hostapd.Hostapd(apdev[0]['ifname']) | |
98 | connect(dev[0], "radius-acct") | |
99 | logger.info("Checking for RADIUS retries") | |
100 | time.sleep(4) | |
101 | mib = hapd.get_mib() | |
102 | if "radiusAccClientRetransmissions" not in mib: | |
103 | raise Exception("Missing MIB fields") | |
104 | if int(mib["radiusAccClientRetransmissions"]) < 2: | |
105 | raise Exception("Missing RADIUS Accounting retransmissions") | |
106 | if int(mib["radiusAccClientPendingRequests"]) < 2: | |
107 | raise Exception("Missing pending RADIUS Accounting requests") | |
4287bb76 | 108 | |
6a188ba3 JM |
109 | def test_radius_acct_unreachable2(dev, apdev): |
110 | """RADIUS Accounting server unreachable(2)""" | |
c4668009 | 111 | subprocess.call(['ip', 'ro', 'replace', '192.168.213.17', 'dev', 'lo']) |
6a188ba3 JM |
112 | params = hostapd.wpa2_eap_params(ssid="radius-acct") |
113 | params['acct_server_addr'] = "192.168.213.17" | |
114 | params['acct_server_port'] = "18139" | |
115 | params['acct_server_shared_secret'] = "radius" | |
116 | hostapd.add_ap(apdev[0]['ifname'], params) | |
117 | hapd = hostapd.Hostapd(apdev[0]['ifname']) | |
c4668009 | 118 | subprocess.call(['ip', 'ro', 'del', '192.168.213.17', 'dev', 'lo']) |
6a188ba3 JM |
119 | connect(dev[0], "radius-acct") |
120 | logger.info("Checking for RADIUS retries") | |
121 | time.sleep(4) | |
122 | mib = hapd.get_mib() | |
123 | if "radiusAccClientRetransmissions" not in mib: | |
124 | raise Exception("Missing MIB fields") | |
125 | if int(mib["radiusAccClientRetransmissions"]) < 1 and int(mib["radiusAccClientPendingRequests"]) < 1: | |
126 | raise Exception("Missing pending or retransmitted RADIUS Accounting requests") | |
127 | ||
48d9065f JM |
128 | def test_radius_acct_unreachable3(dev, apdev): |
129 | """RADIUS Accounting server initially unreachable, but then available""" | |
96cbb7b5 | 130 | require_under_vm() |
48d9065f JM |
131 | subprocess.call(['ip', 'ro', 'replace', 'blackhole', '192.168.213.18']) |
132 | as_hapd = hostapd.Hostapd("as") | |
133 | as_mib_start = as_hapd.get_mib(param="radius_server") | |
134 | params = hostapd.wpa2_eap_params(ssid="radius-acct") | |
135 | params['acct_server_addr'] = "192.168.213.18" | |
136 | params['acct_server_port'] = "1813" | |
137 | params['acct_server_shared_secret'] = "radius" | |
138 | hostapd.add_ap(apdev[0]['ifname'], params) | |
139 | hapd = hostapd.Hostapd(apdev[0]['ifname']) | |
140 | connect(dev[0], "radius-acct") | |
141 | subprocess.call(['ip', 'ro', 'del', 'blackhole', '192.168.213.18']) | |
142 | time.sleep(0.1) | |
143 | dev[0].request("DISCONNECT") | |
144 | hapd.set('acct_server_addr_replace', '127.0.0.1') | |
145 | dev[0].request("RECONNECT") | |
146 | dev[0].wait_connected() | |
147 | time.sleep(1) | |
148 | as_mib_end = as_hapd.get_mib(param="radius_server") | |
149 | req_s = int(as_mib_start['radiusAccServTotalResponses']) | |
150 | req_e = int(as_mib_end['radiusAccServTotalResponses']) | |
151 | if req_e <= req_s: | |
152 | raise Exception("Unexpected RADIUS server acct MIB value") | |
153 | ||
1b5664f0 JM |
154 | def test_radius_acct_unreachable4(dev, apdev): |
155 | """RADIUS Accounting server unreachable and multiple STAs""" | |
156 | params = hostapd.wpa2_eap_params(ssid="radius-acct") | |
157 | params['acct_server_addr'] = "127.0.0.1" | |
158 | params['acct_server_port'] = "18139" | |
159 | params['acct_server_shared_secret'] = "radius" | |
160 | hostapd.add_ap(apdev[0]['ifname'], params) | |
161 | hapd = hostapd.Hostapd(apdev[0]['ifname']) | |
162 | for i in range(20): | |
163 | connect(dev[0], "radius-acct") | |
164 | dev[0].request("REMOVE_NETWORK all") | |
165 | dev[0].wait_disconnected() | |
166 | ||
4287bb76 JM |
167 | def test_radius_acct(dev, apdev): |
168 | """RADIUS Accounting""" | |
4fcee244 JM |
169 | as_hapd = hostapd.Hostapd("as") |
170 | as_mib_start = as_hapd.get_mib(param="radius_server") | |
4287bb76 JM |
171 | params = hostapd.wpa2_eap_params(ssid="radius-acct") |
172 | params['acct_server_addr'] = "127.0.0.1" | |
173 | params['acct_server_port'] = "1813" | |
174 | params['acct_server_shared_secret'] = "radius" | |
18b92c6c JM |
175 | params['radius_auth_req_attr'] = [ "126:s:Operator", "77:s:testing" ] |
176 | params['radius_acct_req_attr'] = [ "126:s:Operator", "77:s:testing" ] | |
4287bb76 JM |
177 | hostapd.add_ap(apdev[0]['ifname'], params) |
178 | hapd = hostapd.Hostapd(apdev[0]['ifname']) | |
179 | connect(dev[0], "radius-acct") | |
4056b0c7 JM |
180 | dev[1].connect("radius-acct", key_mgmt="WPA-EAP", scan_freq="2412", |
181 | eap="PAX", identity="test-class", | |
182 | password_hex="0123456789abcdef0123456789abcdef") | |
86c18618 JM |
183 | dev[2].connect("radius-acct", key_mgmt="WPA-EAP", |
184 | eap="GPSK", identity="gpsk-cui", | |
185 | password="abcdefghijklmnop0123456789abcdef", | |
186 | scan_freq="2412") | |
4287bb76 JM |
187 | logger.info("Checking for RADIUS counters") |
188 | count = 0 | |
189 | while True: | |
190 | mib = hapd.get_mib() | |
86c18618 | 191 | if int(mib['radiusAccClientResponses']) >= 3: |
4287bb76 JM |
192 | break |
193 | time.sleep(0.1) | |
194 | count += 1 | |
195 | if count > 10: | |
196 | raise Exception("Did not receive Accounting-Response packets") | |
197 | ||
198 | if int(mib['radiusAccClientRetransmissions']) > 0: | |
199 | raise Exception("Unexpected Accounting-Request retransmission") | |
4fcee244 JM |
200 | |
201 | as_mib_end = as_hapd.get_mib(param="radius_server") | |
202 | ||
203 | req_s = int(as_mib_start['radiusAccServTotalRequests']) | |
204 | req_e = int(as_mib_end['radiusAccServTotalRequests']) | |
205 | if req_e < req_s + 2: | |
206 | raise Exception("Unexpected RADIUS server acct MIB value") | |
207 | ||
208 | acc_s = int(as_mib_start['radiusAuthServAccessAccepts']) | |
209 | acc_e = int(as_mib_end['radiusAuthServAccessAccepts']) | |
210 | if acc_e < acc_s + 1: | |
211 | raise Exception("Unexpected RADIUS server auth MIB value") | |
414236b5 | 212 | |
44f43942 JM |
213 | def test_radius_acct_non_ascii_ssid(dev, apdev): |
214 | """RADIUS Accounting and non-ASCII SSID""" | |
215 | params = hostapd.wpa2_eap_params() | |
216 | params['acct_server_addr'] = "127.0.0.1" | |
217 | params['acct_server_port'] = "1813" | |
218 | params['acct_server_shared_secret'] = "radius" | |
219 | ssid2 = "740665007374" | |
220 | params['ssid2'] = ssid2 | |
221 | hostapd.add_ap(apdev[0]['ifname'], params) | |
222 | dev[0].connect(ssid2=ssid2, key_mgmt="WPA-EAP", scan_freq="2412", | |
223 | eap="PSK", identity="psk.user@example.com", | |
224 | password_hex="0123456789abcdef0123456789abcdef") | |
225 | ||
414236b5 JM |
226 | def test_radius_acct_pmksa_caching(dev, apdev): |
227 | """RADIUS Accounting with PMKSA caching""" | |
228 | as_hapd = hostapd.Hostapd("as") | |
229 | as_mib_start = as_hapd.get_mib(param="radius_server") | |
230 | params = hostapd.wpa2_eap_params(ssid="radius-acct") | |
231 | params['acct_server_addr'] = "127.0.0.1" | |
232 | params['acct_server_port'] = "1813" | |
233 | params['acct_server_shared_secret'] = "radius" | |
234 | hapd = hostapd.add_ap(apdev[0]['ifname'], params) | |
235 | connect(dev[0], "radius-acct") | |
236 | dev[1].connect("radius-acct", key_mgmt="WPA-EAP", scan_freq="2412", | |
237 | eap="PAX", identity="test-class", | |
238 | password_hex="0123456789abcdef0123456789abcdef") | |
239 | for d in [ dev[0], dev[1] ]: | |
240 | d.request("REASSOCIATE") | |
5f35a5e2 | 241 | d.wait_connected(timeout=15, error="Reassociation timed out") |
414236b5 JM |
242 | |
243 | count = 0 | |
244 | while True: | |
245 | mib = hapd.get_mib() | |
246 | if int(mib['radiusAccClientResponses']) >= 4: | |
247 | break | |
248 | time.sleep(0.1) | |
249 | count += 1 | |
250 | if count > 10: | |
251 | raise Exception("Did not receive Accounting-Response packets") | |
252 | ||
253 | if int(mib['radiusAccClientRetransmissions']) > 0: | |
254 | raise Exception("Unexpected Accounting-Request retransmission") | |
255 | ||
256 | as_mib_end = as_hapd.get_mib(param="radius_server") | |
257 | ||
258 | req_s = int(as_mib_start['radiusAccServTotalRequests']) | |
259 | req_e = int(as_mib_end['radiusAccServTotalRequests']) | |
260 | if req_e < req_s + 2: | |
261 | raise Exception("Unexpected RADIUS server acct MIB value") | |
262 | ||
263 | acc_s = int(as_mib_start['radiusAuthServAccessAccepts']) | |
264 | acc_e = int(as_mib_end['radiusAuthServAccessAccepts']) | |
265 | if acc_e < acc_s + 1: | |
266 | raise Exception("Unexpected RADIUS server auth MIB value") | |
a3b2bdaf | 267 | |
9a19cc08 JM |
268 | def test_radius_acct_interim(dev, apdev): |
269 | """RADIUS Accounting interim update""" | |
270 | as_hapd = hostapd.Hostapd("as") | |
271 | params = hostapd.wpa2_eap_params(ssid="radius-acct") | |
272 | params['acct_server_addr'] = "127.0.0.1" | |
273 | params['acct_server_port'] = "1813" | |
274 | params['acct_server_shared_secret'] = "radius" | |
275 | params['radius_acct_interim_interval'] = "1" | |
276 | hostapd.add_ap(apdev[0]['ifname'], params) | |
277 | hapd = hostapd.Hostapd(apdev[0]['ifname']) | |
278 | connect(dev[0], "radius-acct") | |
279 | logger.info("Checking for RADIUS counters") | |
280 | as_mib_start = as_hapd.get_mib(param="radius_server") | |
281 | time.sleep(3.1) | |
282 | as_mib_end = as_hapd.get_mib(param="radius_server") | |
283 | req_s = int(as_mib_start['radiusAccServTotalRequests']) | |
284 | req_e = int(as_mib_end['radiusAccServTotalRequests']) | |
285 | if req_e < req_s + 3: | |
286 | raise Exception("Unexpected RADIUS server acct MIB value") | |
287 | ||
8b5f1095 JM |
288 | def test_radius_acct_interim_unreachable(dev, apdev): |
289 | """RADIUS Accounting interim update with unreachable server""" | |
290 | params = hostapd.wpa2_eap_params(ssid="radius-acct") | |
291 | params['acct_server_addr'] = "127.0.0.1" | |
292 | params['acct_server_port'] = "18139" | |
293 | params['acct_server_shared_secret'] = "radius" | |
294 | params['radius_acct_interim_interval'] = "1" | |
295 | hapd = hostapd.add_ap(apdev[0]['ifname'], params) | |
296 | start = hapd.get_mib() | |
297 | connect(dev[0], "radius-acct") | |
298 | logger.info("Waiting for interium accounting updates") | |
299 | time.sleep(3.1) | |
300 | end = hapd.get_mib() | |
301 | req_s = int(start['radiusAccClientTimeouts']) | |
302 | req_e = int(end['radiusAccClientTimeouts']) | |
303 | if req_e < req_s + 2: | |
304 | raise Exception("Unexpected RADIUS server acct MIB value") | |
305 | ||
9961c70a JM |
306 | def test_radius_acct_interim_unreachable2(dev, apdev): |
307 | """RADIUS Accounting interim update with unreachable server (retry)""" | |
308 | params = hostapd.wpa2_eap_params(ssid="radius-acct") | |
309 | params['acct_server_addr'] = "127.0.0.1" | |
310 | params['acct_server_port'] = "18139" | |
311 | params['acct_server_shared_secret'] = "radius" | |
312 | # Use long enough interim update interval to allow RADIUS retransmission | |
313 | # case (3 seconds) to trigger first. | |
314 | params['radius_acct_interim_interval'] = "4" | |
315 | hapd = hostapd.add_ap(apdev[0]['ifname'], params) | |
316 | start = hapd.get_mib() | |
317 | connect(dev[0], "radius-acct") | |
318 | logger.info("Waiting for interium accounting updates") | |
319 | time.sleep(7.5) | |
320 | end = hapd.get_mib() | |
321 | req_s = int(start['radiusAccClientTimeouts']) | |
322 | req_e = int(end['radiusAccClientTimeouts']) | |
323 | if req_e < req_s + 2: | |
324 | raise Exception("Unexpected RADIUS server acct MIB value") | |
325 | ||
2ab957af JM |
326 | def test_radius_acct_ipaddr(dev, apdev): |
327 | """RADIUS Accounting and Framed-IP-Address""" | |
328 | try: | |
329 | _test_radius_acct_ipaddr(dev, apdev) | |
330 | finally: | |
331 | subprocess.call(['ip', 'link', 'set', 'dev', 'ap-br0', 'down'], | |
332 | stderr=open('/dev/null', 'w')) | |
333 | subprocess.call(['brctl', 'delbr', 'ap-br0'], | |
334 | stderr=open('/dev/null', 'w')) | |
335 | ||
336 | def _test_radius_acct_ipaddr(dev, apdev): | |
337 | params = { "ssid": "radius-acct-open", | |
338 | 'acct_server_addr': "127.0.0.1", | |
339 | 'acct_server_port': "1813", | |
340 | 'acct_server_shared_secret': "radius", | |
341 | 'proxy_arp': '1', | |
342 | 'ap_isolate': '1', | |
343 | 'bridge': 'ap-br0' } | |
e1195efd JM |
344 | hapd = hostapd.add_ap(apdev[0]['ifname'], params, no_enable=True) |
345 | try: | |
346 | hapd.enable() | |
347 | except: | |
348 | # For now, do not report failures due to missing kernel support | |
349 | raise HwsimSkip("Could not start hostapd - assume proxyarp not supported in kernel version") | |
2ab957af JM |
350 | bssid = apdev[0]['bssid'] |
351 | ||
352 | subprocess.call(['brctl', 'setfd', 'ap-br0', '0']) | |
353 | subprocess.call(['ip', 'link', 'set', 'dev', 'ap-br0', 'up']) | |
354 | ||
355 | dev[0].connect("radius-acct-open", key_mgmt="NONE", scan_freq="2412") | |
356 | addr0 = dev[0].own_addr() | |
357 | ||
358 | pkt = build_dhcp_ack(dst_ll="ff:ff:ff:ff:ff:ff", src_ll=bssid, | |
359 | ip_src="192.168.1.1", ip_dst="255.255.255.255", | |
360 | yiaddr="192.168.1.123", chaddr=addr0) | |
361 | if "OK" not in hapd.request("DATA_TEST_FRAME ifname=ap-br0 " + binascii.hexlify(pkt)): | |
362 | raise Exception("DATA_TEST_FRAME failed") | |
363 | ||
364 | dev[0].request("DISCONNECT") | |
365 | dev[0].wait_disconnected() | |
366 | hapd.disable() | |
367 | ||
99216897 JM |
368 | def send_and_check_reply(srv, req, code, error_cause=0): |
369 | reply = srv.SendPacket(req) | |
370 | logger.debug("RADIUS response from hostapd") | |
371 | for i in reply.keys(): | |
372 | logger.debug("%s: %s" % (i, reply[i])) | |
373 | if reply.code != code: | |
374 | raise Exception("Unexpected response code") | |
375 | if error_cause: | |
376 | if 'Error-Cause' not in reply: | |
377 | raise Exception("Missing Error-Cause") | |
378 | if reply['Error-Cause'][0] != error_cause: | |
379 | raise Exception("Unexpected Error-Cause: {}".format(reply['Error-Cause'])) | |
380 | ||
03aac597 JM |
381 | def test_radius_acct_psk(dev, apdev): |
382 | """RADIUS Accounting - PSK""" | |
383 | as_hapd = hostapd.Hostapd("as") | |
384 | params = hostapd.wpa2_params(ssid="radius-acct", passphrase="12345678") | |
385 | params['acct_server_addr'] = "127.0.0.1" | |
386 | params['acct_server_port'] = "1813" | |
387 | params['acct_server_shared_secret'] = "radius" | |
388 | hapd = hostapd.add_ap(apdev[0]['ifname'], params) | |
389 | dev[0].connect("radius-acct", psk="12345678", scan_freq="2412") | |
390 | ||
391 | def test_radius_acct_psk_sha256(dev, apdev): | |
392 | """RADIUS Accounting - PSK SHA256""" | |
393 | as_hapd = hostapd.Hostapd("as") | |
394 | params = hostapd.wpa2_params(ssid="radius-acct", passphrase="12345678") | |
395 | params["wpa_key_mgmt"] = "WPA-PSK-SHA256" | |
396 | params['acct_server_addr'] = "127.0.0.1" | |
397 | params['acct_server_port'] = "1813" | |
398 | params['acct_server_shared_secret'] = "radius" | |
399 | hapd = hostapd.add_ap(apdev[0]['ifname'], params) | |
400 | dev[0].connect("radius-acct", key_mgmt="WPA-PSK-SHA256", | |
401 | psk="12345678", scan_freq="2412") | |
402 | ||
403 | def test_radius_acct_ft_psk(dev, apdev): | |
404 | """RADIUS Accounting - FT-PSK""" | |
405 | as_hapd = hostapd.Hostapd("as") | |
406 | params = ft_params1(ssid="radius-acct", passphrase="12345678") | |
407 | params['acct_server_addr'] = "127.0.0.1" | |
408 | params['acct_server_port'] = "1813" | |
409 | params['acct_server_shared_secret'] = "radius" | |
410 | hapd = hostapd.add_ap(apdev[0]['ifname'], params) | |
411 | dev[0].connect("radius-acct", key_mgmt="FT-PSK", | |
412 | psk="12345678", scan_freq="2412") | |
413 | ||
414 | def test_radius_acct_ieee8021x(dev, apdev): | |
415 | """RADIUS Accounting - IEEE 802.1X""" | |
416 | skip_with_fips(dev[0]) | |
417 | as_hapd = hostapd.Hostapd("as") | |
418 | params = hostapd.radius_params() | |
419 | params["ssid"] = "radius-acct-1x" | |
420 | params["ieee8021x"] = "1" | |
421 | params["wep_key_len_broadcast"] = "13" | |
422 | params["wep_key_len_unicast"] = "13" | |
423 | params['acct_server_addr'] = "127.0.0.1" | |
424 | params['acct_server_port'] = "1813" | |
425 | params['acct_server_shared_secret'] = "radius" | |
426 | hapd = hostapd.add_ap(apdev[0]['ifname'], params) | |
427 | dev[0].connect("radius-acct-1x", key_mgmt="IEEE8021X", eap="PSK", | |
428 | identity="psk.user@example.com", | |
429 | password_hex="0123456789abcdef0123456789abcdef", | |
430 | scan_freq="2412") | |
431 | ||
a3b2bdaf JM |
432 | def test_radius_das_disconnect(dev, apdev): |
433 | """RADIUS Dynamic Authorization Extensions - Disconnect""" | |
434 | try: | |
435 | import pyrad.client | |
436 | import pyrad.packet | |
437 | import pyrad.dictionary | |
438 | import radius_das | |
439 | except ImportError: | |
81e787b7 | 440 | raise HwsimSkip("No pyrad modules available") |
a3b2bdaf JM |
441 | |
442 | params = hostapd.wpa2_eap_params(ssid="radius-das") | |
443 | params['radius_das_port'] = "3799" | |
444 | params['radius_das_client'] = "127.0.0.1 secret" | |
445 | params['radius_das_require_event_timestamp'] = "1" | |
41ff0fa6 JM |
446 | params['own_ip_addr'] = "127.0.0.1" |
447 | params['nas_identifier'] = "nas.example.com" | |
a3b2bdaf JM |
448 | hapd = hostapd.add_ap(apdev[0]['ifname'], params) |
449 | connect(dev[0], "radius-das") | |
450 | addr = dev[0].p2p_interface_addr() | |
451 | sta = hapd.get_sta(addr) | |
452 | id = sta['dot1xAuthSessionId'] | |
453 | ||
454 | dict = pyrad.dictionary.Dictionary("dictionary.radius") | |
455 | ||
456 | srv = pyrad.client.Client(server="127.0.0.1", acctport=3799, | |
457 | secret="secret", dict=dict) | |
458 | srv.retries = 1 | |
459 | srv.timeout = 1 | |
460 | ||
461 | logger.info("Disconnect-Request with incorrect secret") | |
462 | req = radius_das.DisconnectPacket(dict=dict, secret="incorrect", | |
463 | User_Name="foo", | |
464 | NAS_Identifier="localhost", | |
465 | Event_Timestamp=int(time.time())) | |
466 | logger.debug(req) | |
467 | try: | |
468 | reply = srv.SendPacket(req) | |
469 | raise Exception("Unexpected response to Disconnect-Request") | |
470 | except pyrad.client.Timeout: | |
471 | logger.info("Disconnect-Request with incorrect secret properly ignored") | |
472 | ||
473 | logger.info("Disconnect-Request without Event-Timestamp") | |
474 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
475 | User_Name="psk.user@example.com") | |
476 | logger.debug(req) | |
477 | try: | |
478 | reply = srv.SendPacket(req) | |
479 | raise Exception("Unexpected response to Disconnect-Request") | |
480 | except pyrad.client.Timeout: | |
481 | logger.info("Disconnect-Request without Event-Timestamp properly ignored") | |
482 | ||
483 | logger.info("Disconnect-Request with non-matching Event-Timestamp") | |
484 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
485 | User_Name="psk.user@example.com", | |
486 | Event_Timestamp=123456789) | |
487 | logger.debug(req) | |
488 | try: | |
489 | reply = srv.SendPacket(req) | |
490 | raise Exception("Unexpected response to Disconnect-Request") | |
491 | except pyrad.client.Timeout: | |
492 | logger.info("Disconnect-Request with non-matching Event-Timestamp properly ignored") | |
493 | ||
494 | logger.info("Disconnect-Request with unsupported attribute") | |
495 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
496 | User_Name="foo", | |
497 | User_Password="foo", | |
498 | Event_Timestamp=int(time.time())) | |
99216897 | 499 | send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, 401) |
a3b2bdaf JM |
500 | |
501 | logger.info("Disconnect-Request with invalid Calling-Station-Id") | |
502 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
503 | User_Name="foo", | |
504 | Calling_Station_Id="foo", | |
505 | Event_Timestamp=int(time.time())) | |
99216897 | 506 | send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, 407) |
a3b2bdaf JM |
507 | |
508 | logger.info("Disconnect-Request with mismatching User-Name") | |
509 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
510 | User_Name="foo", | |
511 | Event_Timestamp=int(time.time())) | |
99216897 | 512 | send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, 503) |
a3b2bdaf JM |
513 | |
514 | logger.info("Disconnect-Request with mismatching Calling-Station-Id") | |
515 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
516 | Calling_Station_Id="12:34:56:78:90:aa", | |
517 | Event_Timestamp=int(time.time())) | |
99216897 | 518 | send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, 503) |
a3b2bdaf JM |
519 | |
520 | logger.info("Disconnect-Request with mismatching Acct-Session-Id") | |
521 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
522 | Acct_Session_Id="12345678-87654321", | |
523 | Event_Timestamp=int(time.time())) | |
99216897 | 524 | send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, 503) |
a3b2bdaf | 525 | |
e94a3f62 JM |
526 | logger.info("Disconnect-Request with mismatching Acct-Session-Id (len)") |
527 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
528 | Acct_Session_Id="12345678", | |
529 | Event_Timestamp=int(time.time())) | |
99216897 | 530 | send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, 503) |
e94a3f62 JM |
531 | |
532 | logger.info("Disconnect-Request with mismatching Acct-Multi-Session-Id") | |
533 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
534 | Acct_Multi_Session_Id="12345678+87654321", | |
535 | Event_Timestamp=int(time.time())) | |
99216897 | 536 | send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, 503) |
e94a3f62 JM |
537 | |
538 | logger.info("Disconnect-Request with mismatching Acct-Multi-Session-Id (len)") | |
539 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
540 | Acct_Multi_Session_Id="12345678", | |
541 | Event_Timestamp=int(time.time())) | |
6ace81ea JM |
542 | send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, 503) |
543 | ||
544 | logger.info("Disconnect-Request with no session identification attributes") | |
545 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
546 | Event_Timestamp=int(time.time())) | |
99216897 | 547 | send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, 503) |
e94a3f62 | 548 | |
a3b2bdaf JM |
549 | ev = dev[0].wait_event(["CTRL-EVENT-DISCONNECTED"], timeout=1) |
550 | if ev is not None: | |
551 | raise Exception("Unexpected disconnection") | |
552 | ||
41ff0fa6 JM |
553 | logger.info("Disconnect-Request with mismatching NAS-IP-Address") |
554 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
555 | NAS_IP_Address="192.168.3.4", | |
556 | Acct_Session_Id=id, | |
557 | Event_Timestamp=int(time.time())) | |
99216897 | 558 | send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, 403) |
41ff0fa6 JM |
559 | |
560 | logger.info("Disconnect-Request with mismatching NAS-Identifier") | |
561 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
562 | NAS_Identifier="unknown.example.com", | |
563 | Acct_Session_Id=id, | |
564 | Event_Timestamp=int(time.time())) | |
99216897 | 565 | send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, 403) |
41ff0fa6 JM |
566 | |
567 | ev = dev[0].wait_event(["CTRL-EVENT-DISCONNECTED"], timeout=1) | |
568 | if ev is not None: | |
569 | raise Exception("Unexpected disconnection") | |
570 | ||
a3b2bdaf JM |
571 | logger.info("Disconnect-Request with matching Acct-Session-Id") |
572 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
41ff0fa6 JM |
573 | NAS_IP_Address="127.0.0.1", |
574 | NAS_Identifier="nas.example.com", | |
a3b2bdaf JM |
575 | Acct_Session_Id=id, |
576 | Event_Timestamp=int(time.time())) | |
99216897 | 577 | send_and_check_reply(srv, req, pyrad.packet.DisconnectACK) |
a3b2bdaf | 578 | |
5f35a5e2 JM |
579 | dev[0].wait_disconnected(timeout=10) |
580 | dev[0].wait_connected(timeout=10, error="Re-connection timed out") | |
a3b2bdaf | 581 | |
e94a3f62 JM |
582 | logger.info("Disconnect-Request with matching Acct-Multi-Session-Id") |
583 | sta = hapd.get_sta(addr) | |
584 | multi_sess_id = sta['authMultiSessionId'] | |
585 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
586 | NAS_IP_Address="127.0.0.1", | |
587 | NAS_Identifier="nas.example.com", | |
588 | Acct_Multi_Session_Id=multi_sess_id, | |
589 | Event_Timestamp=int(time.time())) | |
99216897 | 590 | send_and_check_reply(srv, req, pyrad.packet.DisconnectACK) |
e94a3f62 JM |
591 | |
592 | dev[0].wait_disconnected(timeout=10) | |
593 | dev[0].wait_connected(timeout=10, error="Re-connection timed out") | |
594 | ||
a3b2bdaf JM |
595 | logger.info("Disconnect-Request with matching User-Name") |
596 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
41ff0fa6 | 597 | NAS_Identifier="nas.example.com", |
a3b2bdaf JM |
598 | User_Name="psk.user@example.com", |
599 | Event_Timestamp=int(time.time())) | |
99216897 | 600 | send_and_check_reply(srv, req, pyrad.packet.DisconnectACK) |
a3b2bdaf | 601 | |
5f35a5e2 JM |
602 | dev[0].wait_disconnected(timeout=10) |
603 | dev[0].wait_connected(timeout=10, error="Re-connection timed out") | |
a3b2bdaf JM |
604 | |
605 | logger.info("Disconnect-Request with matching Calling-Station-Id") | |
606 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
41ff0fa6 | 607 | NAS_IP_Address="127.0.0.1", |
a3b2bdaf JM |
608 | Calling_Station_Id=addr, |
609 | Event_Timestamp=int(time.time())) | |
99216897 | 610 | send_and_check_reply(srv, req, pyrad.packet.DisconnectACK) |
a3b2bdaf | 611 | |
5f35a5e2 | 612 | dev[0].wait_disconnected(timeout=10) |
e58f59cb | 613 | ev = dev[0].wait_event(["CTRL-EVENT-EAP-STARTED", "CTRL-EVENT-CONNECTED"]) |
a3b2bdaf JM |
614 | if ev is None: |
615 | raise Exception("Timeout while waiting for re-connection") | |
e58f59cb JM |
616 | if "CTRL-EVENT-EAP-STARTED" not in ev: |
617 | raise Exception("Unexpected skipping of EAP authentication in reconnection") | |
5f35a5e2 | 618 | dev[0].wait_connected(timeout=10, error="Re-connection timed out") |
a3b2bdaf JM |
619 | |
620 | logger.info("Disconnect-Request with matching Calling-Station-Id and non-matching CUI") | |
621 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
622 | Calling_Station_Id=addr, | |
623 | Chargeable_User_Identity="foo@example.com", | |
624 | Event_Timestamp=int(time.time())) | |
99216897 | 625 | send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, error_cause=503) |
55497a51 | 626 | |
945cc2fd JM |
627 | logger.info("Disconnect-Request with matching CUI") |
628 | dev[1].connect("radius-das", key_mgmt="WPA-EAP", | |
629 | eap="GPSK", identity="gpsk-cui", | |
630 | password="abcdefghijklmnop0123456789abcdef", | |
631 | scan_freq="2412") | |
632 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
633 | Chargeable_User_Identity="gpsk-chargeable-user-identity", | |
634 | Event_Timestamp=int(time.time())) | |
99216897 | 635 | send_and_check_reply(srv, req, pyrad.packet.DisconnectACK) |
945cc2fd | 636 | |
5f35a5e2 JM |
637 | dev[1].wait_disconnected(timeout=10) |
638 | dev[1].wait_connected(timeout=10, error="Re-connection timed out") | |
945cc2fd JM |
639 | |
640 | ev = dev[0].wait_event(["CTRL-EVENT-DISCONNECTED"], timeout=1) | |
641 | if ev is not None: | |
642 | raise Exception("Unexpected disconnection") | |
643 | ||
9142b4dd JM |
644 | connect(dev[2], "radius-das") |
645 | ||
646 | logger.info("Disconnect-Request with matching User-Name - multiple sessions matching") | |
647 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
648 | NAS_Identifier="nas.example.com", | |
649 | User_Name="psk.user@example.com", | |
650 | Event_Timestamp=int(time.time())) | |
99216897 | 651 | send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, error_cause=508) |
9142b4dd JM |
652 | |
653 | logger.info("Disconnect-Request with User-Name matching multiple sessions, Calling-Station-Id only one") | |
654 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
655 | NAS_Identifier="nas.example.com", | |
656 | Calling_Station_Id=addr, | |
657 | User_Name="psk.user@example.com", | |
658 | Event_Timestamp=int(time.time())) | |
99216897 | 659 | send_and_check_reply(srv, req, pyrad.packet.DisconnectACK) |
9142b4dd JM |
660 | |
661 | dev[0].wait_disconnected(timeout=10) | |
662 | dev[0].wait_connected(timeout=10, error="Re-connection timed out") | |
663 | ||
664 | ev = dev[2].wait_event(["CTRL-EVENT-DISCONNECTED"], timeout=1) | |
665 | if ev is not None: | |
666 | raise Exception("Unexpected disconnection") | |
667 | ||
05dad77c JM |
668 | logger.info("Disconnect-Request with matching Acct-Multi-Session-Id after disassociation") |
669 | sta = hapd.get_sta(addr) | |
670 | multi_sess_id = sta['authMultiSessionId'] | |
671 | dev[0].request("DISCONNECT") | |
672 | dev[0].wait_disconnected(timeout=10) | |
673 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
674 | NAS_IP_Address="127.0.0.1", | |
675 | NAS_Identifier="nas.example.com", | |
676 | Acct_Multi_Session_Id=multi_sess_id, | |
677 | Event_Timestamp=int(time.time())) | |
99216897 | 678 | send_and_check_reply(srv, req, pyrad.packet.DisconnectACK) |
05dad77c JM |
679 | |
680 | dev[0].request("RECONNECT") | |
681 | ev = dev[0].wait_event(["CTRL-EVENT-EAP-STARTED"], timeout=15) | |
682 | if ev is None: | |
683 | raise Exception("Timeout on EAP start") | |
684 | dev[0].wait_connected(timeout=15) | |
685 | ||
686 | logger.info("Disconnect-Request with matching User-Name after disassociation") | |
687 | dev[0].request("DISCONNECT") | |
688 | dev[0].wait_disconnected(timeout=10) | |
689 | dev[2].request("DISCONNECT") | |
690 | dev[2].wait_disconnected(timeout=10) | |
691 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
692 | NAS_IP_Address="127.0.0.1", | |
693 | NAS_Identifier="nas.example.com", | |
694 | User_Name="psk.user@example.com", | |
695 | Event_Timestamp=int(time.time())) | |
99216897 | 696 | send_and_check_reply(srv, req, pyrad.packet.DisconnectACK) |
05dad77c JM |
697 | |
698 | logger.info("Disconnect-Request with matching CUI after disassociation") | |
699 | dev[1].request("DISCONNECT") | |
700 | dev[1].wait_disconnected(timeout=10) | |
701 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
702 | NAS_IP_Address="127.0.0.1", | |
703 | NAS_Identifier="nas.example.com", | |
704 | Chargeable_User_Identity="gpsk-chargeable-user-identity", | |
705 | Event_Timestamp=int(time.time())) | |
99216897 | 706 | send_and_check_reply(srv, req, pyrad.packet.DisconnectACK) |
05dad77c JM |
707 | |
708 | logger.info("Disconnect-Request with matching Calling-Station-Id after disassociation") | |
709 | dev[0].request("RECONNECT") | |
710 | ev = dev[0].wait_event(["CTRL-EVENT-EAP-STARTED"], timeout=15) | |
711 | if ev is None: | |
712 | raise Exception("Timeout on EAP start") | |
713 | dev[0].wait_connected(timeout=15) | |
714 | dev[0].request("DISCONNECT") | |
715 | dev[0].wait_disconnected(timeout=10) | |
716 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
717 | NAS_IP_Address="127.0.0.1", | |
718 | NAS_Identifier="nas.example.com", | |
719 | Calling_Station_Id=addr, | |
720 | Event_Timestamp=int(time.time())) | |
99216897 | 721 | send_and_check_reply(srv, req, pyrad.packet.DisconnectACK) |
05dad77c JM |
722 | |
723 | logger.info("Disconnect-Request with mismatching Calling-Station-Id after disassociation") | |
724 | req = radius_das.DisconnectPacket(dict=dict, secret="secret", | |
725 | NAS_IP_Address="127.0.0.1", | |
726 | NAS_Identifier="nas.example.com", | |
727 | Calling_Station_Id=addr, | |
728 | Event_Timestamp=int(time.time())) | |
99216897 | 729 | send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, error_cause=503) |
05dad77c | 730 | |
55497a51 JM |
731 | def test_radius_das_coa(dev, apdev): |
732 | """RADIUS Dynamic Authorization Extensions - CoA""" | |
733 | try: | |
734 | import pyrad.client | |
735 | import pyrad.packet | |
736 | import pyrad.dictionary | |
737 | import radius_das | |
738 | except ImportError: | |
81e787b7 | 739 | raise HwsimSkip("No pyrad modules available") |
55497a51 JM |
740 | |
741 | params = hostapd.wpa2_eap_params(ssid="radius-das") | |
742 | params['radius_das_port'] = "3799" | |
743 | params['radius_das_client'] = "127.0.0.1 secret" | |
744 | params['radius_das_require_event_timestamp'] = "1" | |
745 | hapd = hostapd.add_ap(apdev[0]['ifname'], params) | |
746 | connect(dev[0], "radius-das") | |
747 | addr = dev[0].p2p_interface_addr() | |
748 | sta = hapd.get_sta(addr) | |
749 | id = sta['dot1xAuthSessionId'] | |
750 | ||
751 | dict = pyrad.dictionary.Dictionary("dictionary.radius") | |
752 | ||
753 | srv = pyrad.client.Client(server="127.0.0.1", acctport=3799, | |
754 | secret="secret", dict=dict) | |
755 | srv.retries = 1 | |
756 | srv.timeout = 1 | |
757 | ||
758 | # hostapd does not currently support CoA-Request, so NAK is expected | |
759 | logger.info("CoA-Request with matching Acct-Session-Id") | |
760 | req = radius_das.CoAPacket(dict=dict, secret="secret", | |
761 | Acct_Session_Id=id, | |
762 | Event_Timestamp=int(time.time())) | |
99216897 | 763 | send_and_check_reply(srv, req, pyrad.packet.CoANAK, error_cause=405) |
9d756af7 JM |
764 | |
765 | def test_radius_ipv6(dev, apdev): | |
766 | """RADIUS connection over IPv6""" | |
767 | params = {} | |
768 | params['ssid'] = 'as' | |
769 | params['beacon_int'] = '2000' | |
770 | params['radius_server_clients'] = 'auth_serv/radius_clients_ipv6.conf' | |
771 | params['radius_server_ipv6'] = '1' | |
772 | params['radius_server_auth_port'] = '18129' | |
773 | params['radius_server_acct_port'] = '18139' | |
774 | params['eap_server'] = '1' | |
775 | params['eap_user_file'] = 'auth_serv/eap_user.conf' | |
776 | params['ca_cert'] = 'auth_serv/ca.pem' | |
777 | params['server_cert'] = 'auth_serv/server.pem' | |
778 | params['private_key'] = 'auth_serv/server.key' | |
779 | hostapd.add_ap(apdev[1]['ifname'], params) | |
780 | ||
781 | params = hostapd.wpa2_eap_params(ssid="radius-ipv6") | |
782 | params['auth_server_addr'] = "::0" | |
783 | params['auth_server_port'] = "18129" | |
784 | params['acct_server_addr'] = "::0" | |
785 | params['acct_server_port'] = "18139" | |
786 | params['acct_server_shared_secret'] = "radius" | |
787 | params['own_ip_addr'] = "::0" | |
788 | hostapd.add_ap(apdev[0]['ifname'], params) | |
789 | connect(dev[0], "radius-ipv6") | |
19d64886 JM |
790 | |
791 | def test_radius_macacl(dev, apdev): | |
792 | """RADIUS MAC ACL""" | |
793 | params = hostapd.radius_params() | |
794 | params["ssid"] = "radius" | |
795 | params["macaddr_acl"] = "2" | |
796 | hostapd.add_ap(apdev[0]['ifname'], params) | |
797 | dev[0].connect("radius", key_mgmt="NONE", scan_freq="2412") | |
0aee8330 | 798 | |
4a4cd04c JM |
799 | def test_radius_macacl_acct(dev, apdev): |
800 | """RADIUS MAC ACL and accounting enabled""" | |
801 | params = hostapd.radius_params() | |
802 | params["ssid"] = "radius" | |
803 | params["macaddr_acl"] = "2" | |
804 | params['acct_server_addr'] = "127.0.0.1" | |
805 | params['acct_server_port'] = "1813" | |
806 | params['acct_server_shared_secret'] = "radius" | |
807 | hostapd.add_ap(apdev[0]['ifname'], params) | |
808 | dev[0].connect("radius", key_mgmt="NONE", scan_freq="2412") | |
809 | dev[1].connect("radius", key_mgmt="NONE", scan_freq="2412") | |
810 | dev[1].request("DISCONNECT") | |
811 | dev[1].wait_disconnected() | |
812 | dev[1].request("RECONNECT") | |
813 | ||
0aee8330 JM |
814 | def test_radius_failover(dev, apdev): |
815 | """RADIUS Authentication and Accounting server failover""" | |
c4668009 | 816 | subprocess.call(['ip', 'ro', 'replace', '192.168.213.17', 'dev', 'lo']) |
0aee8330 JM |
817 | as_hapd = hostapd.Hostapd("as") |
818 | as_mib_start = as_hapd.get_mib(param="radius_server") | |
819 | params = hostapd.wpa2_eap_params(ssid="radius-failover") | |
820 | params["auth_server_addr"] = "192.168.213.17" | |
821 | params["auth_server_port"] = "1812" | |
822 | params["auth_server_shared_secret"] = "testing" | |
823 | params['acct_server_addr'] = "192.168.213.17" | |
824 | params['acct_server_port'] = "1813" | |
825 | params['acct_server_shared_secret'] = "testing" | |
5ff53fd6 | 826 | params['radius_retry_primary_interval'] = "20" |
0aee8330 JM |
827 | hapd = hostapd.add_ap(apdev[0]['ifname'], params, no_enable=True) |
828 | hapd.set("auth_server_addr", "127.0.0.1") | |
829 | hapd.set("auth_server_port", "1812") | |
830 | hapd.set("auth_server_shared_secret", "radius") | |
831 | hapd.set('acct_server_addr', "127.0.0.1") | |
832 | hapd.set('acct_server_port', "1813") | |
833 | hapd.set('acct_server_shared_secret', "radius") | |
834 | hapd.enable() | |
835 | ev = hapd.wait_event(["AP-ENABLED", "AP-DISABLED"], timeout=30) | |
836 | if ev is None: | |
837 | raise Exception("AP startup timed out") | |
838 | if "AP-ENABLED" not in ev: | |
839 | raise Exception("AP startup failed") | |
5ff53fd6 | 840 | start = os.times()[4] |
0aee8330 JM |
841 | |
842 | try: | |
c4668009 | 843 | subprocess.call(['ip', 'ro', 'replace', 'prohibit', '192.168.213.17']) |
0aee8330 JM |
844 | dev[0].request("SET EAPOL::authPeriod 5") |
845 | connect(dev[0], "radius-failover", wait_connect=False) | |
5ff53fd6 | 846 | dev[0].wait_connected(timeout=20) |
0aee8330 JM |
847 | finally: |
848 | dev[0].request("SET EAPOL::authPeriod 30") | |
c4668009 | 849 | subprocess.call(['ip', 'ro', 'del', '192.168.213.17']) |
0aee8330 JM |
850 | |
851 | as_mib_end = as_hapd.get_mib(param="radius_server") | |
852 | req_s = int(as_mib_start['radiusAccServTotalRequests']) | |
853 | req_e = int(as_mib_end['radiusAccServTotalRequests']) | |
854 | if req_e <= req_s: | |
855 | raise Exception("Unexpected RADIUS server acct MIB value") | |
8f614cd7 | 856 | |
5ff53fd6 JM |
857 | end = os.times()[4] |
858 | try: | |
859 | subprocess.call(['ip', 'ro', 'replace', 'prohibit', '192.168.213.17']) | |
860 | dev[1].request("SET EAPOL::authPeriod 5") | |
861 | if end - start < 21: | |
862 | time.sleep(21 - (end - start)) | |
863 | connect(dev[1], "radius-failover", wait_connect=False) | |
864 | dev[1].wait_connected(timeout=20) | |
865 | finally: | |
866 | dev[1].request("SET EAPOL::authPeriod 30") | |
867 | subprocess.call(['ip', 'ro', 'del', '192.168.213.17']) | |
868 | ||
a47f815f JM |
869 | def run_pyrad_server(srv, t_events): |
870 | srv.RunWithStop(t_events) | |
8f614cd7 JM |
871 | |
872 | def test_radius_protocol(dev, apdev): | |
873 | """RADIUS Authentication protocol tests with a fake server""" | |
874 | try: | |
875 | import pyrad.server | |
876 | import pyrad.packet | |
877 | import pyrad.dictionary | |
878 | except ImportError: | |
81e787b7 | 879 | raise HwsimSkip("No pyrad modules available") |
8f614cd7 JM |
880 | |
881 | class TestServer(pyrad.server.Server): | |
882 | def _HandleAuthPacket(self, pkt): | |
883 | pyrad.server.Server._HandleAuthPacket(self, pkt) | |
884 | logger.info("Received authentication request") | |
885 | reply = self.CreateReplyPacket(pkt) | |
886 | reply.code = pyrad.packet.AccessAccept | |
a47f815f JM |
887 | if self.t_events['msg_auth'].is_set(): |
888 | logger.info("Add Message-Authenticator") | |
889 | if self.t_events['wrong_secret'].is_set(): | |
890 | logger.info("Use incorrect RADIUS shared secret") | |
891 | pw = "incorrect" | |
892 | else: | |
893 | pw = reply.secret | |
894 | hmac_obj = hmac.new(pw) | |
895 | hmac_obj.update(struct.pack("B", reply.code)) | |
896 | hmac_obj.update(struct.pack("B", reply.id)) | |
897 | ||
898 | # reply attributes | |
899 | reply.AddAttribute("Message-Authenticator", | |
900 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") | |
901 | attrs = reply._PktEncodeAttributes() | |
902 | ||
903 | # Length | |
904 | flen = 4 + 16 + len(attrs) | |
905 | hmac_obj.update(struct.pack(">H", flen)) | |
906 | hmac_obj.update(pkt.authenticator) | |
907 | hmac_obj.update(attrs) | |
908 | if self.t_events['double_msg_auth'].is_set(): | |
909 | logger.info("Include two Message-Authenticator attributes") | |
910 | else: | |
911 | del reply[80] | |
912 | reply.AddAttribute("Message-Authenticator", hmac_obj.digest()) | |
8f614cd7 JM |
913 | self.SendReplyPacket(pkt.fd, reply) |
914 | ||
a47f815f | 915 | def RunWithStop(self, t_events): |
8f614cd7 JM |
916 | self._poll = select.poll() |
917 | self._fdmap = {} | |
918 | self._PrepareSockets() | |
a47f815f | 919 | self.t_events = t_events |
8f614cd7 | 920 | |
a47f815f | 921 | while not t_events['stop'].is_set(): |
8f614cd7 JM |
922 | for (fd, event) in self._poll.poll(1000): |
923 | if event == select.POLLIN: | |
924 | try: | |
925 | fdo = self._fdmap[fd] | |
926 | self._ProcessInput(fdo) | |
927 | except ServerPacketError as err: | |
928 | logger.info("pyrad server dropping packet: " + str(err)) | |
929 | except pyrad.packet.PacketError as err: | |
930 | logger.info("pyrad server received invalid packet: " + str(err)) | |
931 | else: | |
932 | logger.error("Unexpected event in pyrad server main loop") | |
933 | ||
934 | srv = TestServer(dict=pyrad.dictionary.Dictionary("dictionary.radius"), | |
935 | authport=18138, acctport=18139) | |
936 | srv.hosts["127.0.0.1"] = pyrad.server.RemoteHost("127.0.0.1", | |
937 | "radius", | |
938 | "localhost") | |
939 | srv.BindToAddress("") | |
a47f815f JM |
940 | t_events = {} |
941 | t_events['stop'] = threading.Event() | |
942 | t_events['msg_auth'] = threading.Event() | |
943 | t_events['wrong_secret'] = threading.Event() | |
944 | t_events['double_msg_auth'] = threading.Event() | |
945 | t = threading.Thread(target=run_pyrad_server, args=(srv, t_events)) | |
8f614cd7 JM |
946 | t.start() |
947 | ||
948 | try: | |
949 | params = hostapd.wpa2_eap_params(ssid="radius-test") | |
950 | params['auth_server_port'] = "18138" | |
951 | hapd = hostapd.add_ap(apdev[0]['ifname'], params) | |
952 | connect(dev[0], "radius-test", wait_connect=False) | |
953 | ev = dev[0].wait_event(["CTRL-EVENT-EAP-STARTED"], timeout=15) | |
954 | if ev is None: | |
955 | raise Exception("Timeout on EAP start") | |
956 | time.sleep(1) | |
a47f815f JM |
957 | dev[0].request("REMOVE_NETWORK all") |
958 | time.sleep(0.1) | |
959 | dev[0].dump_monitor() | |
960 | t_events['msg_auth'].set() | |
961 | t_events['wrong_secret'].set() | |
962 | connect(dev[0], "radius-test", wait_connect=False) | |
963 | time.sleep(1) | |
964 | dev[0].request("REMOVE_NETWORK all") | |
965 | time.sleep(0.1) | |
966 | dev[0].dump_monitor() | |
967 | t_events['wrong_secret'].clear() | |
968 | connect(dev[0], "radius-test", wait_connect=False) | |
969 | time.sleep(1) | |
970 | dev[0].request("REMOVE_NETWORK all") | |
971 | time.sleep(0.1) | |
972 | dev[0].dump_monitor() | |
973 | t_events['double_msg_auth'].set() | |
974 | connect(dev[0], "radius-test", wait_connect=False) | |
975 | time.sleep(1) | |
8f614cd7 | 976 | finally: |
a47f815f | 977 | t_events['stop'].set() |
8f614cd7 | 978 | t.join() |
7c8f5ea6 JM |
979 | |
980 | def test_radius_psk(dev, apdev): | |
981 | """WPA2 with PSK from RADIUS""" | |
982 | try: | |
983 | import pyrad.server | |
984 | import pyrad.packet | |
985 | import pyrad.dictionary | |
986 | except ImportError: | |
81e787b7 | 987 | raise HwsimSkip("No pyrad modules available") |
7c8f5ea6 JM |
988 | |
989 | class TestServer(pyrad.server.Server): | |
990 | def _HandleAuthPacket(self, pkt): | |
991 | pyrad.server.Server._HandleAuthPacket(self, pkt) | |
992 | logger.info("Received authentication request") | |
993 | reply = self.CreateReplyPacket(pkt) | |
994 | reply.code = pyrad.packet.AccessAccept | |
995 | a = "\xab\xcd" | |
996 | secret = reply.secret | |
997 | if self.t_events['long'].is_set(): | |
998 | p = b'\x10' + "0123456789abcdef" + 15 * b'\x00' | |
999 | b = hashlib.md5(secret + pkt.authenticator + a).digest() | |
1000 | pp = bytearray(p[0:16]) | |
1001 | bb = bytearray(b) | |
1002 | cc = bytearray(pp[i] ^ bb[i] for i in range(len(bb))) | |
1003 | ||
1004 | b = hashlib.md5(reply.secret + bytes(cc)).digest() | |
1005 | pp = bytearray(p[16:32]) | |
1006 | bb = bytearray(b) | |
1007 | cc += bytearray(pp[i] ^ bb[i] for i in range(len(bb))) | |
1008 | ||
1009 | data = '\x00' + a + bytes(cc) | |
1010 | else: | |
1011 | p = b'\x08' + "12345678" + 7 * b'\x00' | |
1012 | b = hashlib.md5(secret + pkt.authenticator + a).digest() | |
1013 | pp = bytearray(p) | |
1014 | bb = bytearray(b) | |
1015 | cc = bytearray(pp[i] ^ bb[i] for i in range(len(bb))) | |
1016 | data = '\x00' + a + bytes(cc) | |
1017 | reply.AddAttribute("Tunnel-Password", data) | |
1018 | self.SendReplyPacket(pkt.fd, reply) | |
1019 | ||
1020 | def RunWithStop(self, t_events): | |
1021 | self._poll = select.poll() | |
1022 | self._fdmap = {} | |
1023 | self._PrepareSockets() | |
1024 | self.t_events = t_events | |
1025 | ||
1026 | while not t_events['stop'].is_set(): | |
1027 | for (fd, event) in self._poll.poll(1000): | |
1028 | if event == select.POLLIN: | |
1029 | try: | |
1030 | fdo = self._fdmap[fd] | |
1031 | self._ProcessInput(fdo) | |
1032 | except ServerPacketError as err: | |
1033 | logger.info("pyrad server dropping packet: " + str(err)) | |
1034 | except pyrad.packet.PacketError as err: | |
1035 | logger.info("pyrad server received invalid packet: " + str(err)) | |
1036 | else: | |
1037 | logger.error("Unexpected event in pyrad server main loop") | |
1038 | ||
1039 | srv = TestServer(dict=pyrad.dictionary.Dictionary("dictionary.radius"), | |
1040 | authport=18138, acctport=18139) | |
1041 | srv.hosts["127.0.0.1"] = pyrad.server.RemoteHost("127.0.0.1", | |
1042 | "radius", | |
1043 | "localhost") | |
1044 | srv.BindToAddress("") | |
1045 | t_events = {} | |
1046 | t_events['stop'] = threading.Event() | |
1047 | t_events['long'] = threading.Event() | |
1048 | t = threading.Thread(target=run_pyrad_server, args=(srv, t_events)) | |
1049 | t.start() | |
1050 | ||
1051 | try: | |
1052 | ssid = "test-wpa2-psk" | |
1053 | params = hostapd.radius_params() | |
1054 | params['ssid'] = ssid | |
1055 | params["wpa"] = "2" | |
1056 | params["wpa_key_mgmt"] = "WPA-PSK" | |
1057 | params["rsn_pairwise"] = "CCMP" | |
1058 | params['macaddr_acl'] = '2' | |
1059 | params['wpa_psk_radius'] = '2' | |
1060 | params['auth_server_port'] = "18138" | |
1061 | hapd = hostapd.add_ap(apdev[0]['ifname'], params) | |
1062 | dev[0].connect(ssid, psk="12345678", scan_freq="2412") | |
1063 | t_events['long'].set() | |
1064 | dev[1].connect(ssid, psk="0123456789abcdef", scan_freq="2412") | |
1065 | finally: | |
1066 | t_events['stop'].set() | |
1067 | t.join() | |
7c5658c6 JM |
1068 | |
1069 | def test_radius_auth_force_client_addr(dev, apdev): | |
1070 | """RADIUS client address specified""" | |
1071 | params = hostapd.wpa2_eap_params(ssid="radius-auth") | |
1072 | params['radius_client_addr'] = "127.0.0.1" | |
1073 | hapd = hostapd.add_ap(apdev[0]['ifname'], params) | |
1074 | connect(dev[0], "radius-auth") | |
1075 | ||
1076 | def test_radius_auth_force_invalid_client_addr(dev, apdev): | |
1077 | """RADIUS client address specified and invalid address""" | |
1078 | params = hostapd.wpa2_eap_params(ssid="radius-auth") | |
1079 | #params['radius_client_addr'] = "10.11.12.14" | |
1080 | params['radius_client_addr'] = "1::2" | |
1081 | hapd = hostapd.add_ap(apdev[0]['ifname'], params) | |
1082 | connect(dev[0], "radius-auth", wait_connect=False) | |
1083 | ev = dev[0].wait_event(["CTRL-EVENT-EAP-STARTED"]) | |
1084 | if ev is None: | |
1085 | raise Exception("Timeout on EAP start") | |
1086 | ev = dev[0].wait_event(["CTRL-EVENT-CONNECTED"], timeout=1) | |
1087 | if ev is not None: | |
1088 | raise Exception("Unexpected connection") | |
58980654 JM |
1089 | |
1090 | def add_message_auth(req): | |
1091 | req.authenticator = req.CreateAuthenticator() | |
1092 | hmac_obj = hmac.new(req.secret) | |
1093 | hmac_obj.update(struct.pack("B", req.code)) | |
1094 | hmac_obj.update(struct.pack("B", req.id)) | |
1095 | ||
1096 | # request attributes | |
1097 | req.AddAttribute("Message-Authenticator", | |
1098 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") | |
1099 | attrs = req._PktEncodeAttributes() | |
1100 | ||
1101 | # Length | |
1102 | flen = 4 + 16 + len(attrs) | |
1103 | hmac_obj.update(struct.pack(">H", flen)) | |
1104 | hmac_obj.update(req.authenticator) | |
1105 | hmac_obj.update(attrs) | |
1106 | del req[80] | |
1107 | req.AddAttribute("Message-Authenticator", hmac_obj.digest()) | |
1108 | ||
1109 | def test_radius_server_failures(dev, apdev): | |
1110 | """RADIUS server failure cases""" | |
1111 | try: | |
1112 | import pyrad.client | |
1113 | import pyrad.packet | |
1114 | import pyrad.dictionary | |
1115 | except ImportError: | |
1116 | raise HwsimSkip("No pyrad modules available") | |
1117 | ||
1118 | dict = pyrad.dictionary.Dictionary("dictionary.radius") | |
1119 | client = pyrad.client.Client(server="127.0.0.1", authport=1812, | |
1120 | secret="radius", dict=dict) | |
1121 | client.retries = 1 | |
1122 | client.timeout = 1 | |
1123 | ||
1124 | # unexpected State | |
1125 | req = client.CreateAuthPacket(code=pyrad.packet.AccessRequest, | |
1126 | User_Name="foo") | |
1127 | req['State'] = 'foo-state' | |
1128 | add_message_auth(req) | |
1129 | reply = client.SendPacket(req) | |
1130 | if reply.code != pyrad.packet.AccessReject: | |
1131 | raise Exception("Unexpected RADIUS response code " + str(reply.code)) | |
1132 | ||
1133 | # no EAP-Message | |
1134 | req = client.CreateAuthPacket(code=pyrad.packet.AccessRequest, | |
1135 | User_Name="foo") | |
1136 | add_message_auth(req) | |
1137 | try: | |
1138 | reply = client.SendPacket(req) | |
1139 | raise Exception("Unexpected response") | |
1140 | except pyrad.client.Timeout: | |
1141 | pass | |
bfcc073c MB |
1142 | |
1143 | def test_ap_vlan_wpa2_psk_radius_required(dev, apdev): | |
1144 | """AP VLAN with WPA2-PSK and RADIUS attributes required""" | |
1145 | try: | |
1146 | import pyrad.server | |
1147 | import pyrad.packet | |
1148 | import pyrad.dictionary | |
1149 | except ImportError: | |
1150 | raise HwsimSkip("No pyrad modules available") | |
1151 | ||
1152 | class TestServer(pyrad.server.Server): | |
1153 | def _HandleAuthPacket(self, pkt): | |
1154 | pyrad.server.Server._HandleAuthPacket(self, pkt) | |
1155 | logger.info("Received authentication request") | |
1156 | reply = self.CreateReplyPacket(pkt) | |
1157 | reply.code = pyrad.packet.AccessAccept | |
1158 | secret = reply.secret | |
0b5740fd JM |
1159 | if self.t_events['extra'].is_set(): |
1160 | reply.AddAttribute("Chargeable-User-Identity", "test-cui") | |
1161 | reply.AddAttribute("User-Name", "test-user") | |
bfcc073c MB |
1162 | if self.t_events['long'].is_set(): |
1163 | reply.AddAttribute("Tunnel-Type", 13) | |
1164 | reply.AddAttribute("Tunnel-Medium-Type", 6) | |
1165 | reply.AddAttribute("Tunnel-Private-Group-ID", "1") | |
1166 | self.SendReplyPacket(pkt.fd, reply) | |
1167 | ||
1168 | def RunWithStop(self, t_events): | |
1169 | self._poll = select.poll() | |
1170 | self._fdmap = {} | |
1171 | self._PrepareSockets() | |
1172 | self.t_events = t_events | |
1173 | ||
1174 | while not t_events['stop'].is_set(): | |
1175 | for (fd, event) in self._poll.poll(1000): | |
1176 | if event == select.POLLIN: | |
1177 | try: | |
1178 | fdo = self._fdmap[fd] | |
1179 | self._ProcessInput(fdo) | |
1180 | except ServerPacketError as err: | |
1181 | logger.info("pyrad server dropping packet: " + str(err)) | |
1182 | except pyrad.packet.PacketError as err: | |
1183 | logger.info("pyrad server received invalid packet: " + str(err)) | |
1184 | else: | |
1185 | logger.error("Unexpected event in pyrad server main loop") | |
1186 | ||
1187 | srv = TestServer(dict=pyrad.dictionary.Dictionary("dictionary.radius"), | |
1188 | authport=18138, acctport=18139) | |
1189 | srv.hosts["127.0.0.1"] = pyrad.server.RemoteHost("127.0.0.1", | |
1190 | "radius", | |
1191 | "localhost") | |
1192 | srv.BindToAddress("") | |
1193 | t_events = {} | |
1194 | t_events['stop'] = threading.Event() | |
1195 | t_events['long'] = threading.Event() | |
0b5740fd | 1196 | t_events['extra'] = threading.Event() |
bfcc073c MB |
1197 | t = threading.Thread(target=run_pyrad_server, args=(srv, t_events)) |
1198 | t.start() | |
1199 | ||
1200 | try: | |
1201 | ssid = "test-wpa2-psk" | |
1202 | params = hostapd.radius_params() | |
1203 | params['ssid'] = ssid | |
1204 | params["wpa"] = "2" | |
1205 | params["wpa_key_mgmt"] = "WPA-PSK" | |
1206 | params["rsn_pairwise"] = "CCMP" | |
1207 | params['macaddr_acl'] = '2' | |
1208 | params['dynamic_vlan'] = "2" | |
1209 | params['wpa_passphrase'] = '0123456789abcdefghi' | |
1210 | params['auth_server_port'] = "18138" | |
1211 | hapd = hostapd.add_ap(apdev[0]['ifname'], params) | |
0b5740fd | 1212 | |
bfcc073c | 1213 | logger.info("connecting without VLAN") |
0b5740fd JM |
1214 | dev[0].connect(ssid, psk="0123456789abcdefghi", scan_freq="2412", |
1215 | wait_connect=False) | |
bfcc073c MB |
1216 | ev = dev[0].wait_event(["CTRL-EVENT-CONNECTED", |
1217 | "CTRL-EVENT-SSID-TEMP-DISABLED"], timeout=20) | |
1218 | if ev is None: | |
1219 | raise Exception("Timeout on connection attempt") | |
1220 | if "CTRL-EVENT-CONNECTED" in ev: | |
1221 | raise Exception("Unexpected success without vlan parameters") | |
1222 | logger.info("connecting without VLAN failed as expected") | |
0b5740fd JM |
1223 | |
1224 | logger.info("connecting without VLAN (CUI/User-Name)") | |
1225 | t_events['extra'].set() | |
1226 | dev[1].connect(ssid, psk="0123456789abcdefghi", scan_freq="2412", | |
1227 | wait_connect=False) | |
1228 | ev = dev[1].wait_event(["CTRL-EVENT-CONNECTED", | |
1229 | "CTRL-EVENT-SSID-TEMP-DISABLED"], timeout=20) | |
1230 | if ev is None: | |
1231 | raise Exception("Timeout on connection attempt") | |
1232 | if "CTRL-EVENT-CONNECTED" in ev: | |
1233 | raise Exception("Unexpected success without vlan parameters(2)") | |
1234 | logger.info("connecting without VLAN failed as expected(2)") | |
1235 | t_events['extra'].clear() | |
1236 | ||
bfcc073c MB |
1237 | t_events['long'].set() |
1238 | logger.info("connecting with VLAN") | |
0b5740fd JM |
1239 | dev[2].connect(ssid, psk="0123456789abcdefghi", scan_freq="2412", |
1240 | wait_connect=False) | |
bfcc073c MB |
1241 | ev = dev[2].wait_event(["CTRL-EVENT-CONNECTED", |
1242 | "CTRL-EVENT-SSID-TEMP-DISABLED"], timeout=20) | |
1243 | if ev is None: | |
1244 | raise Exception("Timeout on connection attempt") | |
1245 | if "CTRL-EVENT-SSID-TEMP-DISABLED" in ev: | |
1246 | raise Exception("Unexpected failure with vlan parameters") | |
1247 | logger.info("connecting with VLAN succeeded as expected") | |
1248 | finally: | |
1249 | t_events['stop'].set() | |
1250 | t.join() |