tests: Pass full apdev to add_ap() function (1)
[mech_eap.git] / tests / hwsim / test_radius.py
1 # RADIUS tests
2 # Copyright (c) 2013-2015, 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
7 import binascii
8 import hashlib
9 import hmac
10 import logging
11 logger = logging.getLogger()
12 import os
13 import select
14 import struct
15 import subprocess
16 import threading
17 import time
18
19 import hostapd
20 from utils import HwsimSkip, require_under_vm, skip_with_fips
21 from test_ap_hs20 import build_dhcp_ack
22 from test_ap_ft import ft_params1
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], 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
50 def test_radius_auth_unreachable2(dev, apdev):
51     """RADIUS Authentication server unreachable (2)"""
52     subprocess.call(['ip', 'ro', 'replace', '192.168.213.17', 'dev', 'lo'])
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], params)
57     hapd = hostapd.Hostapd(apdev[0]['ifname'])
58     subprocess.call(['ip', 'ro', 'del', '192.168.213.17', 'dev', 'lo'])
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
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], 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
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], 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")
108
109 def test_radius_acct_unreachable2(dev, apdev):
110     """RADIUS Accounting server unreachable(2)"""
111     subprocess.call(['ip', 'ro', 'replace', '192.168.213.17', 'dev', 'lo'])
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], params)
117     hapd = hostapd.Hostapd(apdev[0]['ifname'])
118     subprocess.call(['ip', 'ro', 'del', '192.168.213.17', 'dev', 'lo'])
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
128 def test_radius_acct_unreachable3(dev, apdev):
129     """RADIUS Accounting server initially unreachable, but then available"""
130     require_under_vm()
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], 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
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], 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
167 def test_radius_acct(dev, apdev):
168     """RADIUS Accounting"""
169     as_hapd = hostapd.Hostapd("as")
170     as_mib_start = as_hapd.get_mib(param="radius_server")
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"
175     params['radius_auth_req_attr'] = [ "126:s:Operator", "77:s:testing" ]
176     params['radius_acct_req_attr'] = [ "126:s:Operator", "77:s:testing" ]
177     hostapd.add_ap(apdev[0], params)
178     hapd = hostapd.Hostapd(apdev[0]['ifname'])
179     connect(dev[0], "radius-acct")
180     dev[1].connect("radius-acct", key_mgmt="WPA-EAP", scan_freq="2412",
181                    eap="PAX", identity="test-class",
182                    password_hex="0123456789abcdef0123456789abcdef")
183     dev[2].connect("radius-acct", key_mgmt="WPA-EAP",
184                    eap="GPSK", identity="gpsk-cui",
185                    password="abcdefghijklmnop0123456789abcdef",
186                    scan_freq="2412")
187     logger.info("Checking for RADIUS counters")
188     count = 0
189     while True:
190         mib = hapd.get_mib()
191         if int(mib['radiusAccClientResponses']) >= 3:
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")
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")
212
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], 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
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], 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")
241         d.wait_connected(timeout=15, error="Reassociation timed out")
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")
267
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], 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
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], 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
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], 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
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' }
344     hapd = hostapd.add_ap(apdev[0], 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")
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
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
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], 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], 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], 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], 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
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:
440         raise HwsimSkip("No pyrad modules available")
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"
446     params['own_ip_addr'] = "127.0.0.1"
447     params['nas_identifier'] = "nas.example.com"
448     hapd = hostapd.add_ap(apdev[0], 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()))
499     send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, 401)
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()))
506     send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, 407)
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()))
512     send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, 503)
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()))
518     send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, 503)
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()))
524     send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, 503)
525
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()))
530     send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, 503)
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()))
536     send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, 503)
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()))
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()))
547     send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, 503)
548
549     ev = dev[0].wait_event(["CTRL-EVENT-DISCONNECTED"], timeout=1)
550     if ev is not None:
551         raise Exception("Unexpected disconnection")
552
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()))
558     send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, 403)
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()))
565     send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, 403)
566
567     ev = dev[0].wait_event(["CTRL-EVENT-DISCONNECTED"], timeout=1)
568     if ev is not None:
569         raise Exception("Unexpected disconnection")
570
571     logger.info("Disconnect-Request with matching Acct-Session-Id")
572     req = radius_das.DisconnectPacket(dict=dict, secret="secret",
573                                       NAS_IP_Address="127.0.0.1",
574                                       NAS_Identifier="nas.example.com",
575                                       Acct_Session_Id=id,
576                                       Event_Timestamp=int(time.time()))
577     send_and_check_reply(srv, req, pyrad.packet.DisconnectACK)
578
579     dev[0].wait_disconnected(timeout=10)
580     dev[0].wait_connected(timeout=10, error="Re-connection timed out")
581
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()))
590     send_and_check_reply(srv, req, pyrad.packet.DisconnectACK)
591
592     dev[0].wait_disconnected(timeout=10)
593     dev[0].wait_connected(timeout=10, error="Re-connection timed out")
594
595     logger.info("Disconnect-Request with matching User-Name")
596     req = radius_das.DisconnectPacket(dict=dict, secret="secret",
597                                       NAS_Identifier="nas.example.com",
598                                       User_Name="psk.user@example.com",
599                                       Event_Timestamp=int(time.time()))
600     send_and_check_reply(srv, req, pyrad.packet.DisconnectACK)
601
602     dev[0].wait_disconnected(timeout=10)
603     dev[0].wait_connected(timeout=10, error="Re-connection timed out")
604
605     logger.info("Disconnect-Request with matching Calling-Station-Id")
606     req = radius_das.DisconnectPacket(dict=dict, secret="secret",
607                                       NAS_IP_Address="127.0.0.1",
608                                       Calling_Station_Id=addr,
609                                       Event_Timestamp=int(time.time()))
610     send_and_check_reply(srv, req, pyrad.packet.DisconnectACK)
611
612     dev[0].wait_disconnected(timeout=10)
613     ev = dev[0].wait_event(["CTRL-EVENT-EAP-STARTED", "CTRL-EVENT-CONNECTED"])
614     if ev is None:
615         raise Exception("Timeout while waiting for re-connection")
616     if "CTRL-EVENT-EAP-STARTED" not in ev:
617         raise Exception("Unexpected skipping of EAP authentication in reconnection")
618     dev[0].wait_connected(timeout=10, error="Re-connection timed out")
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()))
625     send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, error_cause=503)
626
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()))
635     send_and_check_reply(srv, req, pyrad.packet.DisconnectACK)
636
637     dev[1].wait_disconnected(timeout=10)
638     dev[1].wait_connected(timeout=10, error="Re-connection timed out")
639
640     ev = dev[0].wait_event(["CTRL-EVENT-DISCONNECTED"], timeout=1)
641     if ev is not None:
642         raise Exception("Unexpected disconnection")
643
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()))
651     send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, error_cause=508)
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()))
659     send_and_check_reply(srv, req, pyrad.packet.DisconnectACK)
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
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()))
678     send_and_check_reply(srv, req, pyrad.packet.DisconnectACK)
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()))
696     send_and_check_reply(srv, req, pyrad.packet.DisconnectACK)
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()))
706     send_and_check_reply(srv, req, pyrad.packet.DisconnectACK)
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()))
721     send_and_check_reply(srv, req, pyrad.packet.DisconnectACK)
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()))
729     send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, error_cause=503)
730
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:
739         raise HwsimSkip("No pyrad modules available")
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], 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()))
763     send_and_check_reply(srv, req, pyrad.packet.CoANAK, error_cause=405)
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], 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], params)
789     connect(dev[0], "radius-ipv6")
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], params)
797     dev[0].connect("radius", key_mgmt="NONE", scan_freq="2412")
798
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], 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
814 def test_radius_failover(dev, apdev):
815     """RADIUS Authentication and Accounting server failover"""
816     subprocess.call(['ip', 'ro', 'replace', '192.168.213.17', 'dev', 'lo'])
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"
826     params['radius_retry_primary_interval'] = "20"
827     hapd = hostapd.add_ap(apdev[0], 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")
840     start = os.times()[4]
841
842     try:
843         subprocess.call(['ip', 'ro', 'replace', 'prohibit', '192.168.213.17'])
844         dev[0].request("SET EAPOL::authPeriod 5")
845         connect(dev[0], "radius-failover", wait_connect=False)
846         dev[0].wait_connected(timeout=20)
847     finally:
848         dev[0].request("SET EAPOL::authPeriod 30")
849         subprocess.call(['ip', 'ro', 'del', '192.168.213.17'])
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")
856
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
869 def run_pyrad_server(srv, t_events):
870     srv.RunWithStop(t_events)
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:
879         raise HwsimSkip("No pyrad modules available")
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
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())
913             self.SendReplyPacket(pkt.fd, reply)
914
915         def RunWithStop(self, t_events):
916             self._poll = select.poll()
917             self._fdmap = {}
918             self._PrepareSockets()
919             self.t_events = t_events
920
921             while not t_events['stop'].is_set():
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("")
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))
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], 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)
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)
976     finally:
977         t_events['stop'].set()
978         t.join()
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:
987         raise HwsimSkip("No pyrad modules available")
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], 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()
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], 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], 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")
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
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
1159             if self.t_events['extra'].is_set():
1160                 reply.AddAttribute("Chargeable-User-Identity", "test-cui")
1161                 reply.AddAttribute("User-Name", "test-user")
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()
1196     t_events['extra'] = threading.Event()
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], params)
1212
1213         logger.info("connecting without VLAN")
1214         dev[0].connect(ssid, psk="0123456789abcdefghi", scan_freq="2412",
1215                        wait_connect=False)
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")
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
1237         t_events['long'].set()
1238         logger.info("connecting with VLAN")
1239         dev[2].connect(ssid, psk="0123456789abcdefghi", scan_freq="2412",
1240                        wait_connect=False)
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()