1# RADIUS tests
2# Copyright (c) 2013-2024, 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
7from remotehost import remote_compatible
8import binascii
9import hashlib
10import hmac
11import logging
12logger = logging.getLogger()
13import os
14import select
15import signal
16import struct
17import subprocess
18import threading
19import time
20
21import hostapd
22from utils import *
23from test_ap_hs20 import build_dhcp_ack
24from test_ap_ft import ft_params1
25from test_eap_proto import add_message_authenticator_attr, build_message_auth
26
27def connect(dev, ssid, wait_connect=True):
28    dev.connect(ssid, key_mgmt="WPA-EAP", scan_freq="2412",
29                eap="PSK", identity="psk.user@example.com",
30                password_hex="0123456789abcdef0123456789abcdef",
31                wait_connect=wait_connect)
32
33@remote_compatible
34def test_radius_auth_unreachable(dev, apdev):
35    """RADIUS Authentication server unreachable"""
36    params = hostapd.wpa2_eap_params(ssid="radius-auth")
37    params['auth_server_port'] = "18139"
38    hapd = hostapd.add_ap(apdev[0], params)
39    connect(dev[0], "radius-auth", wait_connect=False)
40    ev = dev[0].wait_event(["CTRL-EVENT-EAP-STARTED"])
41    if ev is None:
42        raise Exception("Timeout on EAP start")
43    logger.info("Checking for RADIUS retries")
44    time.sleep(4)
45    mib = hapd.get_mib()
46    if "radiusAuthClientAccessRequests" not in mib:
47        raise Exception("Missing MIB fields")
48    if int(mib["radiusAuthClientAccessRetransmissions"]) < 1:
49        raise Exception("Missing RADIUS Authentication retransmission")
50    if int(mib["radiusAuthClientPendingRequests"]) < 1:
51        raise Exception("Missing pending RADIUS Authentication request")
52
53def test_radius_auth_unreachable2(dev, apdev):
54    """RADIUS Authentication server unreachable (2)"""
55    subprocess.call(['ip', 'ro', 'replace', '192.168.213.17', 'dev', 'lo'])
56    params = hostapd.wpa2_eap_params(ssid="radius-auth")
57    params['auth_server_addr'] = "192.168.213.17"
58    params['auth_server_port'] = "18139"
59    hapd = hostapd.add_ap(apdev[0], params)
60    subprocess.call(['ip', 'ro', 'del', '192.168.213.17', 'dev', 'lo'])
61    connect(dev[0], "radius-auth", wait_connect=False)
62    ev = dev[0].wait_event(["CTRL-EVENT-EAP-STARTED"])
63    if ev is None:
64        raise Exception("Timeout on EAP start")
65    logger.info("Checking for RADIUS retries")
66    time.sleep(4)
67    mib = hapd.get_mib()
68    if "radiusAuthClientAccessRequests" not in mib:
69        raise Exception("Missing MIB fields")
70    logger.info("radiusAuthClientAccessRetransmissions: " + mib["radiusAuthClientAccessRetransmissions"])
71
72def test_radius_auth_unreachable3(dev, apdev):
73    """RADIUS Authentication server initially unreachable, but then available"""
74    subprocess.call(['ip', 'ro', 'replace', 'blackhole', '192.168.213.18'])
75    params = hostapd.wpa2_eap_params(ssid="radius-auth")
76    params['auth_server_addr'] = "192.168.213.18"
77    hapd = hostapd.add_ap(apdev[0], params)
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
90def 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    hapd = hostapd.add_ap(apdev[0], params)
97    connect(dev[0], "radius-acct")
98    logger.info("Checking for RADIUS retries")
99    time.sleep(4)
100    mib = hapd.get_mib()
101    if "radiusAccClientRetransmissions" not in mib:
102        raise Exception("Missing MIB fields")
103    if int(mib["radiusAccClientRetransmissions"]) < 2:
104        raise Exception("Missing RADIUS Accounting retransmissions")
105    if int(mib["radiusAccClientPendingRequests"]) < 2:
106        raise Exception("Missing pending RADIUS Accounting requests")
107
108def test_radius_acct_unreachable2(dev, apdev):
109    """RADIUS Accounting server unreachable(2)"""
110    subprocess.call(['ip', 'ro', 'replace', '192.168.213.17', 'dev', 'lo'])
111    params = hostapd.wpa2_eap_params(ssid="radius-acct")
112    params['acct_server_addr'] = "192.168.213.17"
113    params['acct_server_port'] = "18139"
114    params['acct_server_shared_secret'] = "radius"
115    hapd = hostapd.add_ap(apdev[0], params)
116    subprocess.call(['ip', 'ro', 'del', '192.168.213.17', 'dev', 'lo'])
117    connect(dev[0], "radius-acct")
118    logger.info("Checking for RADIUS retries")
119    found = False
120    for i in range(4):
121        time.sleep(1)
122        mib = hapd.get_mib()
123        if "radiusAccClientRetransmissions" not in mib:
124            raise Exception("Missing MIB fields")
125        if int(mib["radiusAccClientRetransmissions"]) > 0 or \
126           int(mib["radiusAccClientPendingRequests"]) > 0:
127            found = True
128    if not found:
129        raise Exception("Missing pending or retransmitted RADIUS Accounting requests")
130
131def test_radius_acct_unreachable3(dev, apdev):
132    """RADIUS Accounting server initially unreachable, but then available"""
133    require_under_vm()
134    subprocess.call(['ip', 'ro', 'replace', 'blackhole', '192.168.213.18'])
135    as_hapd = hostapd.Hostapd("as")
136    as_mib_start = as_hapd.get_mib(param="radius_server")
137    params = hostapd.wpa2_eap_params(ssid="radius-acct")
138    params['acct_server_addr'] = "192.168.213.18"
139    params['acct_server_port'] = "1813"
140    params['acct_server_shared_secret'] = "radius"
141    hapd = hostapd.add_ap(apdev[0], params)
142    connect(dev[0], "radius-acct")
143    subprocess.call(['ip', 'ro', 'del', 'blackhole', '192.168.213.18'])
144    time.sleep(0.1)
145    dev[0].request("DISCONNECT")
146    hapd.set('acct_server_addr_replace', '127.0.0.1')
147    dev[0].request("RECONNECT")
148    dev[0].wait_connected()
149    time.sleep(1)
150    as_mib_end = as_hapd.get_mib(param="radius_server")
151    req_s = int(as_mib_start['radiusAccServTotalResponses'])
152    req_e = int(as_mib_end['radiusAccServTotalResponses'])
153    if req_e <= req_s:
154        raise Exception("Unexpected RADIUS server acct MIB value")
155
156def test_radius_acct_unreachable4(dev, apdev):
157    """RADIUS Accounting server unreachable and multiple STAs"""
158    params = hostapd.wpa2_eap_params(ssid="radius-acct")
159    params['acct_server_addr'] = "127.0.0.1"
160    params['acct_server_port'] = "18139"
161    params['acct_server_shared_secret'] = "radius"
162    hapd = hostapd.add_ap(apdev[0], params)
163    for i in range(20):
164        connect(dev[0], "radius-acct")
165        dev[0].request("REMOVE_NETWORK all")
166        dev[0].wait_disconnected()
167
168def test_radius_acct(dev, apdev):
169    """RADIUS Accounting"""
170    as_hapd = hostapd.Hostapd("as")
171    as_mib_start = as_hapd.get_mib(param="radius_server")
172    params = hostapd.wpa2_eap_params(ssid="radius-acct")
173    params['acct_server_addr'] = "127.0.0.1"
174    params['acct_server_port'] = "1813"
175    params['acct_server_shared_secret'] = "radius"
176    params['radius_auth_req_attr'] = ["126:s:Operator", "77:s:testing",
177                                      "62:d:1"]
178    params['radius_acct_req_attr'] = ["126:s:Operator", "62:d:1",
179                                      "77:s:testing"]
180    hapd = hostapd.add_ap(apdev[0], params)
181    connect(dev[0], "radius-acct")
182    dev[1].connect("radius-acct", key_mgmt="WPA-EAP", scan_freq="2412",
183                   eap="PAX", identity="test-class",
184                   password_hex="0123456789abcdef0123456789abcdef")
185    dev[2].connect("radius-acct", key_mgmt="WPA-EAP",
186                   eap="GPSK", identity="gpsk-cui",
187                   password="abcdefghijklmnop0123456789abcdef",
188                   scan_freq="2412")
189    logger.info("Checking for RADIUS counters")
190    count = 0
191    while True:
192        mib = hapd.get_mib()
193        if int(mib['radiusAccClientResponses']) >= 3:
194            break
195        time.sleep(0.1)
196        count += 1
197        if count > 10:
198            raise Exception("Did not receive Accounting-Response packets")
199
200    if int(mib['radiusAccClientRetransmissions']) > 0:
201        raise Exception("Unexpected Accounting-Request retransmission")
202
203    as_mib_end = as_hapd.get_mib(param="radius_server")
204
205    req_s = int(as_mib_start['radiusAccServTotalRequests'])
206    req_e = int(as_mib_end['radiusAccServTotalRequests'])
207    if req_e < req_s + 2:
208        raise Exception("Unexpected RADIUS server acct MIB value")
209
210    acc_s = int(as_mib_start['radiusAuthServAccessAccepts'])
211    acc_e = int(as_mib_end['radiusAuthServAccessAccepts'])
212    if acc_e < acc_s + 1:
213        raise Exception("Unexpected RADIUS server auth MIB value")
214
215def test_radius_req_attr(dev, apdev, params):
216    """RADIUS request attributes"""
217    try:
218        import sqlite3
219    except ImportError:
220        raise HwsimSkip("No sqlite3 module available")
221    db = os.path.join(params['logdir'], "radius_req_attr.sqlite")
222    as_hapd = hostapd.Hostapd("as")
223    params = hostapd.wpa2_eap_params(ssid="radius-req-attr")
224    params['acct_server_addr'] = "127.0.0.1"
225    params['acct_server_port'] = "1813"
226    params['acct_server_shared_secret'] = "radius"
227    params['radius_auth_req_attr'] = ["126:s:Operator"]
228    params['radius_acct_req_attr'] = ["126:s:Operator"]
229    params['radius_req_attr_sqlite'] = db
230    hapd = hostapd.add_ap(apdev[0], params)
231
232    with sqlite3.connect(db) as conn:
233        sql = "INSERT INTO radius_attributes(sta,reqtype,attr) VALUES (?,?,?)"
234        for e in [(dev[0].own_addr(), "auth", "77:s:conn-info-0"),
235                  (dev[1].own_addr(), "auth", "77:s:conn-info-1"),
236                  (dev[1].own_addr(), "auth", "77:s:conn-info-1a"),
237                  (dev[1].own_addr(), "acct", "77:s:conn-info-1b")]:
238            conn.execute(sql, e)
239        conn.commit()
240
241    connect(dev[0], "radius-req-attr")
242    connect(dev[1], "radius-req-attr")
243    connect(dev[2], "radius-req-attr")
244
245def test_radius_acct_non_ascii_ssid(dev, apdev):
246    """RADIUS Accounting and non-ASCII SSID"""
247    params = hostapd.wpa2_eap_params()
248    params['acct_server_addr'] = "127.0.0.1"
249    params['acct_server_port'] = "1813"
250    params['acct_server_shared_secret'] = "radius"
251    ssid2 = "740665007374"
252    params['ssid2'] = ssid2
253    hostapd.add_ap(apdev[0], params)
254    dev[0].connect(ssid2=ssid2, key_mgmt="WPA-EAP", scan_freq="2412",
255                   eap="PSK", identity="psk.user@example.com",
256                   password_hex="0123456789abcdef0123456789abcdef")
257
258def test_radius_acct_pmksa_caching(dev, apdev):
259    """RADIUS Accounting with PMKSA caching"""
260    as_hapd = hostapd.Hostapd("as")
261    as_mib_start = as_hapd.get_mib(param="radius_server")
262    params = hostapd.wpa2_eap_params(ssid="radius-acct")
263    params['acct_server_addr'] = "127.0.0.1"
264    params['acct_server_port'] = "1813"
265    params['acct_server_shared_secret'] = "radius"
266    hapd = hostapd.add_ap(apdev[0], params)
267    connect(dev[0], "radius-acct")
268    dev[1].connect("radius-acct", key_mgmt="WPA-EAP", scan_freq="2412",
269                   eap="PAX", identity="test-class",
270                   password_hex="0123456789abcdef0123456789abcdef")
271    for d in [dev[0], dev[1]]:
272        d.request("REASSOCIATE")
273        d.wait_connected(timeout=15, error="Reassociation timed out")
274
275    count = 0
276    while True:
277        mib = hapd.get_mib()
278        if int(mib['radiusAccClientResponses']) >= 4:
279            break
280        time.sleep(0.1)
281        count += 1
282        if count > 10:
283            raise Exception("Did not receive Accounting-Response packets")
284
285    if int(mib['radiusAccClientRetransmissions']) > 0:
286        raise Exception("Unexpected Accounting-Request retransmission")
287
288    as_mib_end = as_hapd.get_mib(param="radius_server")
289
290    req_s = int(as_mib_start['radiusAccServTotalRequests'])
291    req_e = int(as_mib_end['radiusAccServTotalRequests'])
292    if req_e < req_s + 2:
293        raise Exception("Unexpected RADIUS server acct MIB value")
294
295    acc_s = int(as_mib_start['radiusAuthServAccessAccepts'])
296    acc_e = int(as_mib_end['radiusAuthServAccessAccepts'])
297    if acc_e < acc_s + 1:
298        raise Exception("Unexpected RADIUS server auth MIB value")
299
300def test_radius_acct_interim(dev, apdev):
301    """RADIUS Accounting interim update"""
302    as_hapd = hostapd.Hostapd("as")
303    params = hostapd.wpa2_eap_params(ssid="radius-acct")
304    params['acct_server_addr'] = "127.0.0.1"
305    params['acct_server_port'] = "1813"
306    params['acct_server_shared_secret'] = "radius"
307    params['radius_acct_interim_interval'] = "1"
308    hapd = hostapd.add_ap(apdev[0], params)
309    connect(dev[0], "radius-acct")
310    logger.info("Checking for RADIUS counters")
311    as_mib_start = as_hapd.get_mib(param="radius_server")
312    time.sleep(4.1)
313    as_mib_end = as_hapd.get_mib(param="radius_server")
314    req_s = int(as_mib_start['radiusAccServTotalRequests'])
315    req_e = int(as_mib_end['radiusAccServTotalRequests'])
316    if req_e < req_s + 3:
317        raise Exception("Unexpected RADIUS server acct MIB value (req_e=%d req_s=%d)" % (req_e, req_s))
318    # Disable Accounting server and wait for interim update retries to fail and
319    # expire.
320    as_hapd.disable()
321    time.sleep(15)
322    as_hapd.enable()
323    ok = False
324    for i in range(10):
325        time.sleep(1)
326        as_mib = as_hapd.get_mib(param="radius_server")
327        if int(as_mib['radiusAccServTotalRequests']) > 0:
328            ok = True
329            break
330    if not ok:
331        raise Exception("Accounting updates did not seen after server restart")
332
333def test_radius_acct_interim_unreachable(dev, apdev):
334    """RADIUS Accounting interim update with unreachable server"""
335    params = hostapd.wpa2_eap_params(ssid="radius-acct")
336    params['acct_server_addr'] = "127.0.0.1"
337    params['acct_server_port'] = "18139"
338    params['acct_server_shared_secret'] = "radius"
339    params['radius_acct_interim_interval'] = "1"
340    hapd = hostapd.add_ap(apdev[0], params)
341    start = hapd.get_mib()
342    connect(dev[0], "radius-acct")
343    logger.info("Waiting for interium accounting updates")
344    time.sleep(3.1)
345    end = hapd.get_mib()
346    req_s = int(start['radiusAccClientTimeouts'])
347    req_e = int(end['radiusAccClientTimeouts'])
348    if req_e < req_s + 2:
349        raise Exception("Unexpected RADIUS server acct MIB value")
350
351def test_radius_acct_interim_unreachable2(dev, apdev):
352    """RADIUS Accounting interim update with unreachable server (retry)"""
353    params = hostapd.wpa2_eap_params(ssid="radius-acct")
354    params['acct_server_addr'] = "127.0.0.1"
355    params['acct_server_port'] = "18139"
356    params['acct_server_shared_secret'] = "radius"
357    # Use long enough interim update interval to allow RADIUS retransmission
358    # case (3 seconds) to trigger first.
359    params['radius_acct_interim_interval'] = "4"
360    hapd = hostapd.add_ap(apdev[0], params)
361    start = hapd.get_mib()
362    connect(dev[0], "radius-acct")
363    logger.info("Waiting for interium accounting updates")
364    time.sleep(7.5)
365    end = hapd.get_mib()
366    req_s = int(start['radiusAccClientTimeouts'])
367    req_e = int(end['radiusAccClientTimeouts'])
368    if req_e < req_s + 2:
369        raise Exception("Unexpected RADIUS server acct MIB value")
370
371def test_radius_acct_ipaddr(dev, apdev):
372    """RADIUS Accounting and Framed-IP-Address"""
373    try:
374        _test_radius_acct_ipaddr(dev, apdev)
375    finally:
376        subprocess.call(['ip', 'link', 'set', 'dev', 'ap-br0', 'down'],
377                        stderr=open('/dev/null', 'w'))
378        subprocess.call(['brctl', 'delbr', 'ap-br0'],
379                        stderr=open('/dev/null', 'w'))
380
381def _test_radius_acct_ipaddr(dev, apdev):
382    params = {"ssid": "radius-acct-open",
383              'acct_server_addr': "127.0.0.1",
384              'acct_server_port': "1813",
385              'acct_server_shared_secret': "radius",
386              'proxy_arp': '1',
387              'ap_isolate': '1',
388              'bridge': 'ap-br0'}
389    hapd = hostapd.add_ap(apdev[0], params, no_enable=True)
390    try:
391        hapd.enable()
392    except:
393        # For now, do not report failures due to missing kernel support
394        raise HwsimSkip("Could not start hostapd - assume proxyarp not supported in kernel version")
395    bssid = apdev[0]['bssid']
396
397    subprocess.call(['brctl', 'setfd', 'ap-br0', '0'])
398    subprocess.call(['ip', 'link', 'set', 'dev', 'ap-br0', 'up'])
399
400    dev[0].connect("radius-acct-open", key_mgmt="NONE", scan_freq="2412")
401    addr0 = dev[0].own_addr()
402
403    pkt = build_dhcp_ack(dst_ll="ff:ff:ff:ff:ff:ff", src_ll=bssid,
404                         ip_src="192.168.1.1", ip_dst="255.255.255.255",
405                         yiaddr="192.168.1.123", chaddr=addr0)
406    if "OK" not in hapd.request("DATA_TEST_FRAME ifname=ap-br0 " + binascii.hexlify(pkt).decode()):
407        raise Exception("DATA_TEST_FRAME failed")
408
409    dev[0].request("DISCONNECT")
410    dev[0].wait_disconnected()
411    hapd.disable()
412
413def send_and_check_reply(srv, req, code, error_cause=0):
414    reply = srv.SendPacket(req)
415    logger.debug("RADIUS response from hostapd")
416    for i in list(reply.keys()):
417        logger.debug("%s: %s" % (i, reply[i]))
418    if reply.code != code:
419        raise Exception("Unexpected response code")
420    if error_cause:
421        if 'Error-Cause' not in reply:
422            raise Exception("Missing Error-Cause")
423            if reply['Error-Cause'][0] != error_cause:
424                raise Exception("Unexpected Error-Cause: {}".format(reply['Error-Cause']))
425
426def test_radius_acct_psk(dev, apdev):
427    """RADIUS Accounting - PSK"""
428    as_hapd = hostapd.Hostapd("as")
429    params = hostapd.wpa2_params(ssid="radius-acct", passphrase="12345678")
430    params['acct_server_addr'] = "127.0.0.1"
431    params['acct_server_port'] = "1813"
432    params['acct_server_shared_secret'] = "radius"
433    hapd = hostapd.add_ap(apdev[0], params)
434    dev[0].connect("radius-acct", psk="12345678", scan_freq="2412")
435
436def test_radius_acct_psk_sha256(dev, apdev):
437    """RADIUS Accounting - PSK SHA256"""
438    as_hapd = hostapd.Hostapd("as")
439    params = hostapd.wpa2_params(ssid="radius-acct", passphrase="12345678")
440    params["wpa_key_mgmt"] = "WPA-PSK-SHA256"
441    params['acct_server_addr'] = "127.0.0.1"
442    params['acct_server_port'] = "1813"
443    params['acct_server_shared_secret'] = "radius"
444    hapd = hostapd.add_ap(apdev[0], params)
445    dev[0].connect("radius-acct", key_mgmt="WPA-PSK-SHA256",
446                   psk="12345678", scan_freq="2412")
447
448def test_radius_acct_ft_psk(dev, apdev):
449    """RADIUS Accounting - FT-PSK"""
450    as_hapd = hostapd.Hostapd("as")
451    params = ft_params1(ssid="radius-acct", passphrase="12345678")
452    params['acct_server_addr'] = "127.0.0.1"
453    params['acct_server_port'] = "1813"
454    params['acct_server_shared_secret'] = "radius"
455    hapd = hostapd.add_ap(apdev[0], params)
456    dev[0].connect("radius-acct", key_mgmt="FT-PSK",
457                   psk="12345678", scan_freq="2412")
458
459def test_radius_acct_ieee8021x(dev, apdev):
460    """RADIUS Accounting - IEEE 802.1X"""
461    check_wep_capa(dev[0])
462    skip_with_fips(dev[0])
463    as_hapd = hostapd.Hostapd("as")
464    params = hostapd.radius_params()
465    params["ssid"] = "radius-acct-1x"
466    params["ieee8021x"] = "1"
467    params["wep_key_len_broadcast"] = "13"
468    params["wep_key_len_unicast"] = "13"
469    params['acct_server_addr'] = "127.0.0.1"
470    params['acct_server_port'] = "1813"
471    params['acct_server_shared_secret'] = "radius"
472    hapd = hostapd.add_ap(apdev[0], params)
473    dev[0].connect("radius-acct-1x", key_mgmt="IEEE8021X", eap="PSK",
474                   identity="psk.user@example.com",
475                   password_hex="0123456789abcdef0123456789abcdef",
476                   scan_freq="2412")
477
478def test_radius_das_disconnect(dev, apdev):
479    """RADIUS Dynamic Authorization Extensions - Disconnect"""
480    try:
481        import pyrad.client
482        import pyrad.packet
483        import pyrad.dictionary
484        import radius_das
485    except ImportError:
486        raise HwsimSkip("No pyrad modules available")
487
488    params = hostapd.wpa2_eap_params(ssid="radius-das")
489    params['radius_das_port'] = "3799"
490    params['radius_das_client'] = "127.0.0.1 secret"
491    params['radius_das_require_event_timestamp'] = "1"
492    params['own_ip_addr'] = "127.0.0.1"
493    params['nas_identifier'] = "nas.example.com"
494    hapd = hostapd.add_ap(apdev[0], params)
495    connect(dev[0], "radius-das")
496    hapd.wait_sta(addr=dev[0].own_addr())
497
498    addr = dev[0].p2p_interface_addr()
499    sta = hapd.get_sta(addr)
500    id = sta['dot1xAuthSessionId']
501
502    dict = pyrad.dictionary.Dictionary("dictionary.radius")
503
504    srv = pyrad.client.Client(server="127.0.0.1", acctport=3799,
505                              secret=b"secret", dict=dict)
506    srv.retries = 1
507    srv.timeout = 1
508
509    logger.info("Disconnect-Request with incorrect secret")
510    req = radius_das.DisconnectPacket(dict=dict, secret=b"incorrect",
511                                      User_Name="foo",
512                                      NAS_Identifier="localhost",
513                                      Event_Timestamp=int(time.time()))
514    logger.debug(req)
515    try:
516        reply = srv.SendPacket(req)
517        raise Exception("Unexpected response to Disconnect-Request")
518    except pyrad.client.Timeout:
519        logger.info("Disconnect-Request with incorrect secret properly ignored")
520
521    logger.info("Disconnect-Request without Event-Timestamp")
522    req = radius_das.DisconnectPacket(dict=dict, secret=b"secret",
523                                      User_Name="psk.user@example.com")
524    logger.debug(req)
525    try:
526        reply = srv.SendPacket(req)
527        raise Exception("Unexpected response to Disconnect-Request")
528    except pyrad.client.Timeout:
529        logger.info("Disconnect-Request without Event-Timestamp properly ignored")
530
531    logger.info("Disconnect-Request with non-matching Event-Timestamp")
532    req = radius_das.DisconnectPacket(dict=dict, secret=b"secret",
533                                      User_Name="psk.user@example.com",
534                                      Event_Timestamp=123456789)
535    logger.debug(req)
536    try:
537        reply = srv.SendPacket(req)
538        raise Exception("Unexpected response to Disconnect-Request")
539    except pyrad.client.Timeout:
540        logger.info("Disconnect-Request with non-matching Event-Timestamp properly ignored")
541
542    logger.info("Disconnect-Request with unsupported attribute")
543    req = radius_das.DisconnectPacket(dict=dict, secret=b"secret",
544                                      User_Name="foo",
545                                      User_Password="foo",
546                                      Event_Timestamp=int(time.time()))
547    send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, 401)
548
549    logger.info("Disconnect-Request with invalid Calling-Station-Id")
550    req = radius_das.DisconnectPacket(dict=dict, secret=b"secret",
551                                      User_Name="foo",
552                                      Calling_Station_Id="foo",
553                                      Event_Timestamp=int(time.time()))
554    send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, 407)
555
556    logger.info("Disconnect-Request with mismatching User-Name")
557    req = radius_das.DisconnectPacket(dict=dict, secret=b"secret",
558                                      User_Name="foo",
559                                      Event_Timestamp=int(time.time()))
560    send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, 503)
561
562    logger.info("Disconnect-Request with mismatching Calling-Station-Id")
563    req = radius_das.DisconnectPacket(dict=dict, secret=b"secret",
564                                      Calling_Station_Id="12:34:56:78:90:aa",
565                                      Event_Timestamp=int(time.time()))
566    send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, 503)
567
568    logger.info("Disconnect-Request with mismatching Acct-Session-Id")
569    req = radius_das.DisconnectPacket(dict=dict, secret=b"secret",
570                                      Acct_Session_Id="12345678-87654321",
571                                      Event_Timestamp=int(time.time()))
572    send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, 503)
573
574    logger.info("Disconnect-Request with mismatching Acct-Session-Id (len)")
575    req = radius_das.DisconnectPacket(dict=dict, secret=b"secret",
576                                      Acct_Session_Id="12345678",
577                                      Event_Timestamp=int(time.time()))
578    send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, 503)
579
580    logger.info("Disconnect-Request with mismatching Acct-Multi-Session-Id")
581    req = radius_das.DisconnectPacket(dict=dict, secret=b"secret",
582                                      Acct_Multi_Session_Id="12345678+87654321",
583                                      Event_Timestamp=int(time.time()))
584    send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, 503)
585
586    logger.info("Disconnect-Request with mismatching Acct-Multi-Session-Id (len)")
587    req = radius_das.DisconnectPacket(dict=dict, secret=b"secret",
588                                      Acct_Multi_Session_Id="12345678",
589                                      Event_Timestamp=int(time.time()))
590    send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, 503)
591
592    logger.info("Disconnect-Request with no session identification attributes")
593    req = radius_das.DisconnectPacket(dict=dict, secret=b"secret",
594                                      Event_Timestamp=int(time.time()))
595    send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, 503)
596
597    ev = dev[0].wait_event(["CTRL-EVENT-DISCONNECTED"], timeout=1)
598    if ev is not None:
599        raise Exception("Unexpected disconnection")
600    dev[0].dump_monitor()
601
602    logger.info("Disconnect-Request with mismatching NAS-IP-Address")
603    req = radius_das.DisconnectPacket(dict=dict, secret=b"secret",
604                                      NAS_IP_Address="192.168.3.4",
605                                      Acct_Session_Id=id,
606                                      Event_Timestamp=int(time.time()))
607    send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, 403)
608
609    logger.info("Disconnect-Request with mismatching NAS-Identifier")
610    req = radius_das.DisconnectPacket(dict=dict, secret=b"secret",
611                                      NAS_Identifier="unknown.example.com",
612                                      Acct_Session_Id=id,
613                                      Event_Timestamp=int(time.time()))
614    send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, 403)
615
616    ev = dev[0].wait_event(["CTRL-EVENT-DISCONNECTED"], timeout=1)
617    if ev is not None:
618        raise Exception("Unexpected disconnection")
619    dev[0].dump_monitor()
620
621    logger.info("Disconnect-Request with matching Acct-Session-Id")
622    req = radius_das.DisconnectPacket(dict=dict, secret=b"secret",
623                                      NAS_IP_Address="127.0.0.1",
624                                      NAS_Identifier="nas.example.com",
625                                      Acct_Session_Id=id,
626                                      Event_Timestamp=int(time.time()))
627    send_and_check_reply(srv, req, pyrad.packet.DisconnectACK)
628
629    dev[0].wait_disconnected(timeout=10)
630    hapd.wait_sta_disconnect(addr=dev[0].own_addr())
631    dev[0].wait_connected(timeout=10, error="Re-connection timed out")
632    hapd.wait_sta(addr=dev[0].own_addr())
633    dev[0].dump_monitor()
634
635    logger.info("Disconnect-Request with matching Acct-Multi-Session-Id")
636    sta = hapd.get_sta(addr)
637    multi_sess_id = sta['authMultiSessionId']
638    req = radius_das.DisconnectPacket(dict=dict, secret=b"secret",
639                                      NAS_IP_Address="127.0.0.1",
640                                      NAS_Identifier="nas.example.com",
641                                      Acct_Multi_Session_Id=multi_sess_id,
642                                      Event_Timestamp=int(time.time()))
643    send_and_check_reply(srv, req, pyrad.packet.DisconnectACK)
644
645    dev[0].wait_disconnected(timeout=10)
646    hapd.wait_sta_disconnect(addr=dev[0].own_addr())
647    dev[0].wait_connected(timeout=10, error="Re-connection timed out")
648    hapd.wait_sta(addr=dev[0].own_addr())
649    dev[0].dump_monitor()
650
651    logger.info("Disconnect-Request with matching User-Name")
652    req = radius_das.DisconnectPacket(dict=dict, secret=b"secret",
653                                      NAS_Identifier="nas.example.com",
654                                      User_Name="psk.user@example.com",
655                                      Event_Timestamp=int(time.time()))
656    send_and_check_reply(srv, req, pyrad.packet.DisconnectACK)
657
658    dev[0].wait_disconnected(timeout=10)
659    hapd.wait_sta_disconnect(addr=dev[0].own_addr())
660    dev[0].wait_connected(timeout=10, error="Re-connection timed out")
661    hapd.wait_sta(addr=dev[0].own_addr())
662    dev[0].dump_monitor()
663
664    logger.info("Disconnect-Request with matching Calling-Station-Id")
665    req = radius_das.DisconnectPacket(dict=dict, secret=b"secret",
666                                      NAS_IP_Address="127.0.0.1",
667                                      Calling_Station_Id=addr,
668                                      Event_Timestamp=int(time.time()))
669    send_and_check_reply(srv, req, pyrad.packet.DisconnectACK)
670
671    dev[0].wait_disconnected(timeout=10)
672    hapd.wait_sta_disconnect(addr=dev[0].own_addr())
673    ev = dev[0].wait_event(["CTRL-EVENT-EAP-STARTED", "CTRL-EVENT-CONNECTED"])
674    if ev is None:
675        raise Exception("Timeout while waiting for re-connection")
676    if "CTRL-EVENT-EAP-STARTED" not in ev:
677        raise Exception("Unexpected skipping of EAP authentication in reconnection")
678    dev[0].wait_connected(timeout=10, error="Re-connection timed out")
679    hapd.wait_sta(addr=dev[0].own_addr())
680    dev[0].dump_monitor()
681
682    logger.info("Disconnect-Request with matching Calling-Station-Id and non-matching CUI")
683    req = radius_das.DisconnectPacket(dict=dict, secret=b"secret",
684                                      Calling_Station_Id=addr,
685                                      Chargeable_User_Identity="foo@example.com",
686                                      Event_Timestamp=int(time.time()))
687    send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, error_cause=503)
688
689    hapd.dump_monitor()
690
691    logger.info("Disconnect-Request with matching CUI")
692    dev[1].connect("radius-das", key_mgmt="WPA-EAP",
693                   eap="GPSK", identity="gpsk-cui",
694                   password="abcdefghijklmnop0123456789abcdef",
695                   scan_freq="2412")
696    hapd.wait_sta(addr=dev[1].own_addr())
697    req = radius_das.DisconnectPacket(dict=dict, secret=b"secret",
698                                      Chargeable_User_Identity="gpsk-chargeable-user-identity",
699                                      Event_Timestamp=int(time.time()))
700    send_and_check_reply(srv, req, pyrad.packet.DisconnectACK)
701
702    dev[1].wait_disconnected(timeout=10)
703    hapd.wait_sta_disconnect(addr=dev[1].own_addr())
704    dev[1].wait_connected(timeout=10, error="Re-connection timed out")
705    hapd.wait_sta(addr=dev[1].own_addr())
706
707    ev = dev[0].wait_event(["CTRL-EVENT-DISCONNECTED"], timeout=1)
708    if ev is not None:
709        raise Exception("Unexpected disconnection")
710
711    connect(dev[2], "radius-das")
712    hapd.wait_sta(addr=dev[2].own_addr())
713    dev[0].dump_monitor()
714
715    logger.info("Disconnect-Request with matching User-Name - multiple sessions matching")
716    req = radius_das.DisconnectPacket(dict=dict, secret=b"secret",
717                                      NAS_Identifier="nas.example.com",
718                                      User_Name="psk.user@example.com",
719                                      Event_Timestamp=int(time.time()))
720    send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, error_cause=508)
721
722    logger.info("Disconnect-Request with User-Name matching multiple sessions, Calling-Station-Id only one")
723    req = radius_das.DisconnectPacket(dict=dict, secret=b"secret",
724                                      NAS_Identifier="nas.example.com",
725                                      Calling_Station_Id=addr,
726                                      User_Name="psk.user@example.com",
727                                      Event_Timestamp=int(time.time()))
728    send_and_check_reply(srv, req, pyrad.packet.DisconnectACK)
729
730    dev[0].wait_disconnected(timeout=10)
731    hapd.wait_sta_disconnect(addr=dev[0].own_addr())
732    dev[0].wait_connected(timeout=10, error="Re-connection timed out")
733    hapd.wait_sta(addr=dev[0].own_addr())
734    dev[0].dump_monitor()
735
736    ev = dev[2].wait_event(["CTRL-EVENT-DISCONNECTED"], timeout=1)
737    if ev is not None:
738        raise Exception("Unexpected disconnection")
739
740    logger.info("Disconnect-Request with matching Acct-Multi-Session-Id after disassociation")
741    sta = hapd.get_sta(addr)
742    multi_sess_id = sta['authMultiSessionId']
743    dev[0].request("DISCONNECT")
744    dev[0].wait_disconnected(timeout=10)
745    hapd.wait_sta_disconnect(addr=dev[0].own_addr())
746    req = radius_das.DisconnectPacket(dict=dict, secret=b"secret",
747                                      NAS_IP_Address="127.0.0.1",
748                                      NAS_Identifier="nas.example.com",
749                                      Acct_Multi_Session_Id=multi_sess_id,
750                                      Event_Timestamp=int(time.time()))
751    send_and_check_reply(srv, req, pyrad.packet.DisconnectACK)
752
753    dev[0].request("RECONNECT")
754    ev = dev[0].wait_event(["CTRL-EVENT-EAP-STARTED"], timeout=15)
755    if ev is None:
756        raise Exception("Timeout on EAP start")
757    dev[0].wait_connected(timeout=15)
758    hapd.wait_sta(addr=dev[0].own_addr())
759    dev[0].dump_monitor()
760
761    logger.info("Disconnect-Request with matching User-Name after disassociation")
762    dev[0].request("DISCONNECT")
763    dev[0].wait_disconnected(timeout=10)
764    dev[0].dump_monitor()
765    hapd.wait_sta_disconnect(addr=dev[0].own_addr())
766    dev[2].request("DISCONNECT")
767    dev[2].wait_disconnected(timeout=10)
768    hapd.wait_sta_disconnect(addr=dev[2].own_addr())
769    req = radius_das.DisconnectPacket(dict=dict, secret=b"secret",
770                                      NAS_IP_Address="127.0.0.1",
771                                      NAS_Identifier="nas.example.com",
772                                      User_Name="psk.user@example.com",
773                                      Event_Timestamp=int(time.time()))
774    send_and_check_reply(srv, req, pyrad.packet.DisconnectACK)
775
776    logger.info("Disconnect-Request with matching CUI after disassociation")
777    dev[1].request("DISCONNECT")
778    dev[1].wait_disconnected(timeout=10)
779    req = radius_das.DisconnectPacket(dict=dict, secret=b"secret",
780                                      NAS_IP_Address="127.0.0.1",
781                                      NAS_Identifier="nas.example.com",
782                                      Chargeable_User_Identity="gpsk-chargeable-user-identity",
783                                      Event_Timestamp=int(time.time()))
784    send_and_check_reply(srv, req, pyrad.packet.DisconnectACK)
785
786    logger.info("Disconnect-Request with matching Calling-Station-Id after disassociation")
787    dev[0].request("RECONNECT")
788    ev = dev[0].wait_event(["CTRL-EVENT-EAP-STARTED"], timeout=15)
789    if ev is None:
790        raise Exception("Timeout on EAP start")
791    dev[0].wait_connected(timeout=15)
792    hapd.wait_sta(addr=dev[0].own_addr())
793    dev[0].dump_monitor()
794    dev[0].request("DISCONNECT")
795    dev[0].wait_disconnected(timeout=10)
796    hapd.wait_sta_disconnect(addr=dev[0].own_addr())
797    dev[0].dump_monitor()
798    req = radius_das.DisconnectPacket(dict=dict, secret=b"secret",
799                                      NAS_IP_Address="127.0.0.1",
800                                      NAS_Identifier="nas.example.com",
801                                      Calling_Station_Id=addr,
802                                      Event_Timestamp=int(time.time()))
803    send_and_check_reply(srv, req, pyrad.packet.DisconnectACK)
804
805    logger.info("Disconnect-Request with mismatching Calling-Station-Id after disassociation")
806    req = radius_das.DisconnectPacket(dict=dict, secret=b"secret",
807                                      NAS_IP_Address="127.0.0.1",
808                                      NAS_Identifier="nas.example.com",
809                                      Calling_Station_Id=addr,
810                                      Event_Timestamp=int(time.time()))
811    send_and_check_reply(srv, req, pyrad.packet.DisconnectNAK, error_cause=503)
812
813def add_message_auth_req(req):
814    req.authenticator = req.CreateAuthenticator()
815    hmac_obj = hmac.new(req.secret, digestmod=hashlib.md5)
816    hmac_obj.update(struct.pack("B", req.code))
817    hmac_obj.update(struct.pack("B", req.id))
818
819    # request attributes
820    req.AddAttribute("Message-Authenticator", 16*b"\x00")
821    attrs = b''
822    for code, datalst in sorted(req.items()):
823        for data in datalst:
824            attrs += req._PktEncodeAttribute(code, data)
825
826    # Length
827    flen = 4 + 16 + len(attrs)
828    hmac_obj.update(struct.pack(">H", flen))
829    hmac_obj.update(16*b"\x00") # all zeros Authenticator in calculation
830    hmac_obj.update(attrs)
831    del req[80]
832    add_message_authenticator_attr(req, hmac_obj.digest())
833
834def test_radius_das_disconnect_time_window(dev, apdev):
835    """RADIUS Dynamic Authorization Extensions - Disconnect - time window"""
836    try:
837        import pyrad.client
838        import pyrad.packet
839        import pyrad.dictionary
840        import radius_das
841    except ImportError:
842        raise HwsimSkip("No pyrad modules available")
843
844    params = hostapd.wpa2_eap_params(ssid="radius-das")
845    params['radius_das_port'] = "3799"
846    params['radius_das_client'] = "127.0.0.1 secret"
847    params['radius_das_require_event_timestamp'] = "1"
848    params['radius_das_require_message_authenticator'] = "1"
849    params['radius_das_time_window'] = "10"
850    params['own_ip_addr'] = "127.0.0.1"
851    params['nas_identifier'] = "nas.example.com"
852    hapd = hostapd.add_ap(apdev[0], params)
853    connect(dev[0], "radius-das")
854    addr = dev[0].own_addr()
855    sta = hapd.get_sta(addr)
856    id = sta['dot1xAuthSessionId']
857
858    dict = pyrad.dictionary.Dictionary("dictionary.radius")
859
860    srv = pyrad.client.Client(server="127.0.0.1", acctport=3799,
861                              secret=b"secret", dict=dict)
862    srv.retries = 1
863    srv.timeout = 1
864
865    logger.info("Disconnect-Request with unsupported attribute")
866    req = radius_das.DisconnectPacket(dict=dict, secret=b"secret",
867                                      NAS_IP_Address="127.0.0.1",
868                                      NAS_Identifier="nas.example.com",
869                                      Calling_Station_Id=addr,
870                                      Event_Timestamp=int(time.time()) - 50)
871    add_message_auth_req(req)
872    logger.debug(req)
873    try:
874        reply = srv.SendPacket(req)
875        raise Exception("Unexpected response to Disconnect-Request")
876    except pyrad.client.Timeout:
877        logger.info("Disconnect-Request with non-matching Event-Timestamp properly ignored")
878
879    logger.info("Disconnect-Request with unsupported attribute")
880    req = radius_das.DisconnectPacket(dict=dict, secret=b"secret",
881                                      NAS_IP_Address="127.0.0.1",
882                                      NAS_Identifier="nas.example.com",
883                                      Calling_Station_Id=addr,
884                                      Event_Timestamp=int(time.time()))
885    add_message_auth_req(req)
886    send_and_check_reply(srv, req, pyrad.packet.DisconnectACK)
887
888def test_radius_das_coa(dev, apdev):
889    """RADIUS Dynamic Authorization Extensions - CoA"""
890    try:
891        import pyrad.client
892        import pyrad.packet
893        import pyrad.dictionary
894        import radius_das
895    except ImportError:
896        raise HwsimSkip("No pyrad modules available")
897
898    params = hostapd.wpa2_eap_params(ssid="radius-das")
899    params['radius_das_port'] = "3799"
900    params['radius_das_client'] = "127.0.0.1 secret"
901    params['radius_das_require_event_timestamp'] = "1"
902    hapd = hostapd.add_ap(apdev[0], params)
903    connect(dev[0], "radius-das")
904    addr = dev[0].p2p_interface_addr()
905    sta = hapd.get_sta(addr)
906    id = sta['dot1xAuthSessionId']
907
908    dict = pyrad.dictionary.Dictionary("dictionary.radius")
909
910    srv = pyrad.client.Client(server="127.0.0.1", acctport=3799,
911                              secret=b"secret", dict=dict)
912    srv.retries = 1
913    srv.timeout = 1
914
915    # hostapd does not currently support CoA-Request, so NAK is expected
916    logger.info("CoA-Request with matching Acct-Session-Id")
917    req = radius_das.CoAPacket(dict=dict, secret=b"secret",
918                               Acct_Session_Id=id,
919                               Event_Timestamp=int(time.time()))
920    send_and_check_reply(srv, req, pyrad.packet.CoANAK, error_cause=405)
921
922def test_radius_ipv6(dev, apdev):
923    """RADIUS connection over IPv6"""
924    params = {}
925    params['ssid'] = 'as'
926    params['beacon_int'] = '2000'
927    params['radius_server_clients'] = 'auth_serv/radius_clients_ipv6.conf'
928    params['radius_server_ipv6'] = '1'
929    params['radius_server_auth_port'] = '18129'
930    params['radius_server_acct_port'] = '18139'
931    params['eap_server'] = '1'
932    params['eap_user_file'] = 'auth_serv/eap_user.conf'
933    params['ca_cert'] = 'auth_serv/ca.pem'
934    params['server_cert'] = 'auth_serv/server.pem'
935    params['private_key'] = 'auth_serv/server.key'
936    hostapd.add_ap(apdev[1], params)
937
938    params = hostapd.wpa2_eap_params(ssid="radius-ipv6")
939    params['auth_server_addr'] = "::0"
940    params['auth_server_port'] = "18129"
941    params['acct_server_addr'] = "::0"
942    params['acct_server_port'] = "18139"
943    params['acct_server_shared_secret'] = "radius"
944    params['own_ip_addr'] = "::0"
945    hostapd.add_ap(apdev[0], params)
946    connect(dev[0], "radius-ipv6")
947
948def test_radius_macacl(dev, apdev):
949    """RADIUS MAC ACL"""
950    params = hostapd.radius_params()
951    params["ssid"] = "radius"
952    params["macaddr_acl"] = "2"
953    hostapd.add_ap(apdev[0], params)
954    dev[0].connect("radius", key_mgmt="NONE", scan_freq="2412")
955
956    # Invalid VLAN ID from RADIUS server
957    dev[2].connect("radius", key_mgmt="NONE", scan_freq="2412")
958    dev[2].request("REMOVE_NETWORK all")
959    dev[2].wait_disconnected()
960    dev[2].connect("radius", key_mgmt="NONE", scan_freq="2412")
961
962def test_radius_macacl_acct(dev, apdev):
963    """RADIUS MAC ACL and accounting enabled"""
964    params = hostapd.radius_params()
965    params["ssid"] = "radius"
966    params["macaddr_acl"] = "2"
967    params['acct_server_addr'] = "127.0.0.1"
968    params['acct_server_port'] = "1813"
969    params['acct_server_shared_secret'] = "radius"
970    hostapd.add_ap(apdev[0], params)
971    dev[0].connect("radius", key_mgmt="NONE", scan_freq="2412")
972    dev[1].connect("radius", key_mgmt="NONE", scan_freq="2412")
973    dev[1].request("DISCONNECT")
974    dev[1].wait_disconnected()
975    dev[1].request("RECONNECT")
976
977def test_radius_macacl_oom(dev, apdev):
978    """RADIUS MAC ACL and OOM"""
979    params = hostapd.radius_params()
980    params["ssid"] = "radius"
981    params["macaddr_acl"] = "2"
982    hapd = hostapd.add_ap(apdev[0], params)
983    bssid = hapd.own_addr()
984
985    dev[0].scan_for_bss(bssid, freq="2412")
986    with alloc_fail(hapd, 1, "hostapd_allowed_address"):
987        dev[0].connect("radius", key_mgmt="NONE", scan_freq="2412")
988
989    dev[1].scan_for_bss(bssid, freq="2412")
990    with alloc_fail(hapd, 2, "hostapd_allowed_address"):
991        dev[1].connect("radius", key_mgmt="NONE", scan_freq="2412")
992
993    dev[2].scan_for_bss(bssid, freq="2412")
994    with alloc_fail(hapd, 2, "=hostapd_allowed_address"):
995        dev[2].connect("radius", key_mgmt="NONE", scan_freq="2412")
996
997def test_radius_macacl_unreachable(dev, apdev):
998    """RADIUS MAC ACL and server unreachable"""
999    params = hostapd.radius_params()
1000    params['auth_server_port'] = "18139"
1001    params["ssid"] = "radius"
1002    params["macaddr_acl"] = "2"
1003    hapd = hostapd.add_ap(apdev[0], params)
1004    bssid = hapd.own_addr()
1005
1006    dev[0].scan_for_bss(bssid, freq="2412")
1007    dev[0].connect("radius", key_mgmt="NONE", scan_freq="2412",
1008                   wait_connect=False)
1009    ev = dev[0].wait_event(["CTRL-EVENT-CONNECTED"], timeout=3)
1010    if ev is not None:
1011        raise Exception("Unexpected connection")
1012
1013    logger.info("Fix authentication server port")
1014    hapd.set("auth_server_port", "1812")
1015    hapd.disable()
1016    hapd.enable()
1017    dev[0].wait_connected(timeout=20)
1018    dev[0].request("DISCONNECT")
1019    dev[0].wait_disconnected()
1020
1021def test_radius_failover(dev, apdev):
1022    """RADIUS Authentication and Accounting server failover"""
1023    subprocess.call(['ip', 'ro', 'replace', '192.168.213.17', 'dev', 'lo'])
1024    as_hapd = hostapd.Hostapd("as")
1025    as_mib_start = as_hapd.get_mib(param="radius_server")
1026    params = hostapd.wpa2_eap_params(ssid="radius-failover")
1027    params["auth_server_addr"] = "192.168.213.17"
1028    params["auth_server_port"] = "1812"
1029    params["auth_server_shared_secret"] = "testing"
1030    params['acct_server_addr'] = "192.168.213.17"
1031    params['acct_server_port'] = "1813"
1032    params['acct_server_shared_secret'] = "testing"
1033    params['radius_retry_primary_interval'] = "20"
1034    hapd = hostapd.add_ap(apdev[0], params, no_enable=True)
1035    hapd.set("auth_server_addr", "127.0.0.1")
1036    hapd.set("auth_server_port", "1812")
1037    hapd.set("auth_server_shared_secret", "radius")
1038    hapd.set('acct_server_addr', "127.0.0.1")
1039    hapd.set('acct_server_port', "1813")
1040    hapd.set('acct_server_shared_secret', "radius")
1041    hapd.enable()
1042    ev = hapd.wait_event(["AP-ENABLED", "AP-DISABLED"], timeout=30)
1043    if ev is None:
1044        raise Exception("AP startup timed out")
1045        if "AP-ENABLED" not in ev:
1046            raise Exception("AP startup failed")
1047    start = os.times()[4]
1048
1049    try:
1050        subprocess.call(['ip', 'ro', 'replace', 'prohibit', '192.168.213.17'])
1051        dev[0].request("SET EAPOL::authPeriod 5")
1052        connect(dev[0], "radius-failover", wait_connect=False)
1053        dev[0].wait_connected(timeout=20)
1054    finally:
1055        dev[0].request("SET EAPOL::authPeriod 30")
1056        subprocess.call(['ip', 'ro', 'del', '192.168.213.17'])
1057
1058    as_mib_end = as_hapd.get_mib(param="radius_server")
1059    req_s = int(as_mib_start['radiusAccServTotalRequests'])
1060    req_e = int(as_mib_end['radiusAccServTotalRequests'])
1061    if req_e <= req_s:
1062        raise Exception("Unexpected RADIUS server acct MIB value")
1063
1064    end = os.times()[4]
1065    try:
1066        subprocess.call(['ip', 'ro', 'replace', 'prohibit', '192.168.213.17'])
1067        dev[1].request("SET EAPOL::authPeriod 5")
1068        if end - start < 21:
1069            time.sleep(21 - (end - start))
1070        connect(dev[1], "radius-failover", wait_connect=False)
1071        dev[1].wait_connected(timeout=20)
1072    finally:
1073        dev[1].request("SET EAPOL::authPeriod 30")
1074        subprocess.call(['ip', 'ro', 'del', '192.168.213.17'])
1075
1076def run_pyrad_server(srv, t_events):
1077    srv.RunWithStop(t_events)
1078
1079def test_radius_protocol(dev, apdev):
1080    """RADIUS Authentication protocol tests with a fake server"""
1081    try:
1082        import pyrad.server
1083        import pyrad.packet
1084        import pyrad.dictionary
1085    except ImportError:
1086        raise HwsimSkip("No pyrad modules available")
1087
1088    class TestServer(pyrad.server.Server):
1089        def _HandleAuthPacket(self, pkt):
1090            pyrad.server.Server._HandleAuthPacket(self, pkt)
1091            logger.info("Received authentication request")
1092            reply = self.CreateReplyPacket(pkt)
1093            reply.code = pyrad.packet.AccessAccept
1094            if self.t_events['msg_auth'].is_set():
1095                logger.info("Add Message-Authenticator")
1096                if self.t_events['wrong_secret'].is_set():
1097                    logger.info("Use incorrect RADIUS shared secret")
1098                    pw = b"incorrect"
1099                else:
1100                    pw = reply.secret
1101                hmac_obj = hmac.new(pw, digestmod=hashlib.md5)
1102                hmac_obj.update(struct.pack("B", reply.code))
1103                hmac_obj.update(struct.pack("B", reply.id))
1104
1105                # reply attributes
1106                reply.AddAttribute("Message-Authenticator", 16*b"\x00")
1107                attrs = reply._PktEncodeAttributes()
1108
1109                # Length
1110                flen = 4 + 16 + len(attrs)
1111                hmac_obj.update(struct.pack(">H", flen))
1112                hmac_obj.update(pkt.authenticator)
1113                hmac_obj.update(attrs)
1114                if self.t_events['double_msg_auth'].is_set():
1115                    logger.info("Include two Message-Authenticator attributes")
1116                else:
1117                    del reply[80]
1118                add_message_authenticator_attr(reply, hmac_obj.digest())
1119            self.SendReplyPacket(pkt.fd, reply)
1120
1121        def RunWithStop(self, t_events):
1122            self._poll = select.poll()
1123            self._fdmap = {}
1124            self._PrepareSockets()
1125            self.t_events = t_events
1126
1127            while not t_events['stop'].is_set():
1128                for (fd, event) in self._poll.poll(1000):
1129                    if event == select.POLLIN:
1130                        try:
1131                            fdo = self._fdmap[fd]
1132                            self._ProcessInput(fdo)
1133                        except pyrad.server.ServerPacketError as err:
1134                            logger.info("pyrad server dropping packet: " + str(err))
1135                        except pyrad.packet.PacketError as err:
1136                            logger.info("pyrad server received invalid packet: " + str(err))
1137                    else:
1138                        logger.error("Unexpected event in pyrad server main loop")
1139
1140            for fd in self.authfds + self.acctfds:
1141                fd.close()
1142
1143    srv = TestServer(dict=pyrad.dictionary.Dictionary("dictionary.radius"),
1144                     authport=18138, acctport=18139)
1145    srv.hosts["127.0.0.1"] = pyrad.server.RemoteHost("127.0.0.1",
1146                                                     b"radius",
1147                                                     "localhost")
1148    srv.BindToAddress("127.0.0.1")
1149    t_events = {}
1150    t_events['stop'] = threading.Event()
1151    t_events['msg_auth'] = threading.Event()
1152    t_events['wrong_secret'] = threading.Event()
1153    t_events['double_msg_auth'] = threading.Event()
1154    t = threading.Thread(target=run_pyrad_server, args=(srv, t_events))
1155    t.start()
1156
1157    try:
1158        params = hostapd.wpa2_eap_params(ssid="radius-test")
1159        params['auth_server_port'] = "18138"
1160        hapd = hostapd.add_ap(apdev[0], params)
1161        connect(dev[0], "radius-test", wait_connect=False)
1162        ev = dev[0].wait_event(["CTRL-EVENT-EAP-STARTED"], timeout=15)
1163        if ev is None:
1164            raise Exception("Timeout on EAP start")
1165        time.sleep(1)
1166        dev[0].request("REMOVE_NETWORK all")
1167        time.sleep(0.1)
1168        dev[0].dump_monitor()
1169        t_events['msg_auth'].set()
1170        t_events['wrong_secret'].set()
1171        connect(dev[0], "radius-test", wait_connect=False)
1172        time.sleep(1)
1173        dev[0].request("REMOVE_NETWORK all")
1174        time.sleep(0.1)
1175        dev[0].dump_monitor()
1176        t_events['wrong_secret'].clear()
1177        connect(dev[0], "radius-test", wait_connect=False)
1178        time.sleep(1)
1179        dev[0].request("REMOVE_NETWORK all")
1180        time.sleep(0.1)
1181        dev[0].dump_monitor()
1182        t_events['double_msg_auth'].set()
1183        connect(dev[0], "radius-test", wait_connect=False)
1184        time.sleep(1)
1185    finally:
1186        t_events['stop'].set()
1187        t.join()
1188
1189def build_tunnel_password(secret, authenticator, psk):
1190    a = b"\xab\xcd"
1191    psk = psk.encode()
1192    padlen = 16 - (1 + len(psk)) % 16
1193    if padlen == 16:
1194        padlen = 0
1195    p = struct.pack('B', len(psk)) + psk + padlen * b'\x00'
1196    cc_all = bytes()
1197    b = hashlib.md5(secret + authenticator + a).digest()
1198    while len(p) > 0:
1199        pp = bytearray(p[0:16])
1200        p = p[16:]
1201        bb = bytearray(b)
1202        cc = bytearray(pp[i] ^ bb[i] for i in range(len(bb)))
1203        cc_all += cc
1204        b = hashlib.md5(secret + cc).digest()
1205    data = b'\x00' + a + bytes(cc_all)
1206    return data
1207
1208def start_radius_psk_server(psk, invalid_code=False, acct_interim_interval=0,
1209                            session_timeout=0, reject=False,
1210                            inject_invalid=False):
1211    try:
1212        import pyrad.server
1213        import pyrad.packet
1214        import pyrad.dictionary
1215    except ImportError:
1216        raise HwsimSkip("No pyrad modules available")
1217
1218    class TestServer(pyrad.server.Server):
1219        def _HandleAuthPacket(self, pkt):
1220            pyrad.server.Server._HandleAuthPacket(self, pkt)
1221            logger.info("Received authentication request")
1222
1223            if self.t_events['inject_invalid']:
1224                reply = self.CreateReplyPacket(pkt)
1225                reply.code = pyrad.packet.AccessAccept
1226                build_message_auth(pkt, reply, secret=b'\x00')
1227                self.SendReplyPacket(pkt.fd, reply)
1228
1229            reply = self.CreateReplyPacket(pkt)
1230            reply.code = pyrad.packet.AccessAccept
1231            if self.t_events['invalid_code']:
1232                reply.code = pyrad.packet.AccessRequest
1233            if self.t_events['reject']:
1234                reply.code = pyrad.packet.AccessReject
1235            data = build_tunnel_password(reply.secret, pkt.authenticator,
1236                                         self.t_events['psk'])
1237            reply.AddAttribute("Tunnel-Password", data)
1238            if self.t_events['acct_interim_interval']:
1239                reply.AddAttribute("Acct-Interim-Interval",
1240                                   self.t_events['acct_interim_interval'])
1241            if self.t_events['session_timeout']:
1242                reply.AddAttribute("Session-Timeout",
1243                                   self.t_events['session_timeout'])
1244            build_message_auth(pkt, reply)
1245
1246            self.SendReplyPacket(pkt.fd, reply)
1247
1248        def RunWithStop(self, t_events):
1249            self._poll = select.poll()
1250            self._fdmap = {}
1251            self._PrepareSockets()
1252            self.t_events = t_events
1253
1254            while not t_events['stop'].is_set():
1255                for (fd, event) in self._poll.poll(1000):
1256                    if event == select.POLLIN:
1257                        try:
1258                            fdo = self._fdmap[fd]
1259                            self._ProcessInput(fdo)
1260                        except pyrad.server.ServerPacketError as err:
1261                            logger.info("pyrad server dropping packet: " + str(err))
1262                        except pyrad.packet.PacketError as err:
1263                            logger.info("pyrad server received invalid packet: " + str(err))
1264                    else:
1265                        logger.error("Unexpected event in pyrad server main loop")
1266
1267            for fd in self.authfds + self.acctfds:
1268                fd.close()
1269
1270    srv = TestServer(dict=pyrad.dictionary.Dictionary("dictionary.radius"),
1271                     authport=18138, acctport=18139)
1272    srv.hosts["127.0.0.1"] = pyrad.server.RemoteHost("127.0.0.1",
1273                                                     b"radius",
1274                                                     "localhost")
1275    srv.BindToAddress("127.0.0.1")
1276    t_events = {}
1277    t_events['stop'] = threading.Event()
1278    t_events['psk'] = psk
1279    t_events['invalid_code'] = invalid_code
1280    t_events['acct_interim_interval'] = acct_interim_interval
1281    t_events['session_timeout'] = session_timeout
1282    t_events['reject'] = reject
1283    t_events['inject_invalid'] = inject_invalid
1284    t = threading.Thread(target=run_pyrad_server, args=(srv, t_events))
1285    t.start()
1286    return t, t_events
1287
1288def hostapd_radius_psk_test_params():
1289    params = hostapd.radius_params()
1290    params['ssid'] = "test-wpa2-psk"
1291    params["wpa"] = "2"
1292    params["wpa_key_mgmt"] = "WPA-PSK"
1293    params["rsn_pairwise"] = "CCMP"
1294    params['macaddr_acl'] = '2'
1295    params['wpa_psk_radius'] = '2'
1296    params['auth_server_port'] = "18138"
1297    return params
1298
1299def test_radius_psk(dev, apdev):
1300    """WPA2 with PSK from RADIUS"""
1301    t, t_events = start_radius_psk_server("12345678")
1302
1303    try:
1304        params = hostapd_radius_psk_test_params()
1305        hapd = hostapd.add_ap(apdev[0], params)
1306        dev[0].connect("test-wpa2-psk", psk="12345678", scan_freq="2412")
1307        t_events['psk'] = "0123456789abcdef"
1308        dev[1].connect("test-wpa2-psk", psk="0123456789abcdef",
1309                       scan_freq="2412")
1310    finally:
1311        t_events['stop'].set()
1312        t.join()
1313
1314def test_radius_psk_during_4way_hs(dev, apdev):
1315    """WPA2 with PSK from RADIUS during 4-way handshake"""
1316    run_radius_psk_during_4way_hs(dev, apdev, 0)
1317
1318def test_radius_psk_during_4way_hs_session_timeout(dev, apdev):
1319    """WPA2 with PSK from RADIUS during 4-way handshake with Session-Timeout"""
1320    run_radius_psk_during_4way_hs(dev, apdev, 10000)
1321
1322def run_radius_psk_during_4way_hs(dev, apdev, session_timeout):
1323    t, t_events = start_radius_psk_server("12345678",
1324                                          session_timeout=session_timeout)
1325
1326    try:
1327        params = hostapd_radius_psk_test_params()
1328        params['macaddr_acl'] = '0'
1329        params['wpa_psk_radius'] = '3'
1330        hapd = hostapd.add_ap(apdev[0], params)
1331        dev[0].connect("test-wpa2-psk", psk="12345678", scan_freq="2412")
1332        t_events['psk'] = "0123456789abcdef"
1333        dev[1].connect("test-wpa2-psk", psk="0123456789abcdef",
1334                       scan_freq="2412")
1335    finally:
1336        t_events['stop'].set()
1337        t.join()
1338
1339def test_radius_psk_invalid(dev, apdev):
1340    """WPA2 with invalid PSK from RADIUS"""
1341    t, t_events = start_radius_psk_server("1234567")
1342
1343    try:
1344        params = hostapd_radius_psk_test_params()
1345        hapd = hostapd.add_ap(apdev[0], params)
1346        dev[0].connect("test-wpa2-psk", psk="12345678", scan_freq="2412",
1347                       wait_connect=False)
1348        time.sleep(1)
1349    finally:
1350        t_events['stop'].set()
1351        t.join()
1352
1353def test_radius_psk_invalid2(dev, apdev):
1354    """WPA2 with invalid PSK (hexstring) from RADIUS"""
1355    t, t_events = start_radius_psk_server(64*'q')
1356
1357    try:
1358        params = hostapd_radius_psk_test_params()
1359        hapd = hostapd.add_ap(apdev[0], params)
1360        dev[0].connect("test-wpa2-psk", psk="12345678", scan_freq="2412",
1361                       wait_connect=False)
1362        time.sleep(1)
1363    finally:
1364        t_events['stop'].set()
1365        t.join()
1366
1367def test_radius_psk_hex_psk(dev, apdev):
1368    """WPA2 with PSK hexstring from RADIUS"""
1369    t, t_events = start_radius_psk_server(64*'2', acct_interim_interval=19,
1370                                          session_timeout=123)
1371
1372    try:
1373        params = hostapd_radius_psk_test_params()
1374        hapd = hostapd.add_ap(apdev[0], params)
1375        dev[0].connect("test-wpa2-psk", raw_psk=64*'2', scan_freq="2412")
1376    finally:
1377        t_events['stop'].set()
1378        t.join()
1379
1380def test_radius_psk_unknown_code(dev, apdev):
1381    """WPA2 with PSK from RADIUS and unknown code"""
1382    t, t_events = start_radius_psk_server(64*'2', invalid_code=True)
1383
1384    try:
1385        params = hostapd_radius_psk_test_params()
1386        hapd = hostapd.add_ap(apdev[0], params)
1387        dev[0].connect("test-wpa2-psk", psk="12345678", scan_freq="2412",
1388                       wait_connect=False)
1389        time.sleep(1)
1390    finally:
1391        t_events['stop'].set()
1392        t.join()
1393
1394def test_radius_psk_reject(dev, apdev):
1395    """WPA2 with PSK from RADIUS and reject"""
1396    t, t_events = start_radius_psk_server("12345678", reject=True)
1397
1398    try:
1399        params = hostapd_radius_psk_test_params()
1400        hapd = hostapd.add_ap(apdev[0], params)
1401        dev[0].connect("test-wpa2-psk", psk="12345678", scan_freq="2412",
1402                       wait_connect=False)
1403        ev = dev[0].wait_event(["CTRL-EVENT-AUTH-REJECT"], timeout=10)
1404        if ev is None:
1405            raise Exception("No CTRL-EVENT-AUTH-REJECT event")
1406        dev[0].request("DISCONNECT")
1407    finally:
1408        t_events['stop'].set()
1409        t.join()
1410
1411def test_radius_psk_reject_during_4way_hs(dev, apdev):
1412    """WPA2 with PSK from RADIUS and reject"""
1413    t, t_events = start_radius_psk_server("12345678", reject=True)
1414
1415    try:
1416        params = hostapd_radius_psk_test_params()
1417        params['macaddr_acl'] = '0'
1418        params['wpa_psk_radius'] = '3'
1419        hapd = hostapd.add_ap(apdev[0], params)
1420        dev[0].connect("test-wpa2-psk", psk="12345678", scan_freq="2412",
1421                       wait_connect=False)
1422        dev[0].wait_disconnected()
1423        dev[0].request("DISCONNECT")
1424    finally:
1425        t_events['stop'].set()
1426        t.join()
1427
1428def test_radius_psk_oom(dev, apdev):
1429    """WPA2 with PSK from RADIUS and OOM"""
1430    t, t_events = start_radius_psk_server(64*'2')
1431
1432    try:
1433        params = hostapd_radius_psk_test_params()
1434        hapd = hostapd.add_ap(apdev[0], params)
1435        bssid = hapd.own_addr()
1436        dev[0].scan_for_bss(bssid, freq="2412")
1437        with alloc_fail(hapd, 1, "=hostapd_acl_recv_radius"):
1438            dev[0].connect("test-wpa2-psk", psk="12345678", scan_freq="2412",
1439                           wait_connect=False)
1440            wait_fail_trigger(hapd, "GET_ALLOC_FAIL")
1441    finally:
1442        t_events['stop'].set()
1443        t.join()
1444
1445def test_radius_psk_discard(dev, apdev):
1446    """WPA2 with PSK from RADIUS and discarding invalid RADIUS messages"""
1447    t, t_events = start_radius_psk_server("12345678", inject_invalid=True)
1448
1449    try:
1450        params = hostapd_radius_psk_test_params()
1451        hapd = hostapd.add_ap(apdev[0], params)
1452        dev[0].connect("test-wpa2-psk", psk="12345678", scan_freq="2412")
1453        t_events['psk'] = "0123456789abcdef"
1454        dev[1].connect("test-wpa2-psk", psk="0123456789abcdef",
1455                       scan_freq="2412")
1456    finally:
1457        t_events['stop'].set()
1458        t.join()
1459
1460def test_radius_sae_password(dev, apdev):
1461    """WPA3 with SAE password from RADIUS"""
1462    check_sae_capab(dev[0])
1463    check_sae_capab(dev[1])
1464
1465    t, t_events = start_radius_psk_server("12345678")
1466
1467    try:
1468        params = hostapd_radius_psk_test_params()
1469        params['ssid'] = "test-wpa3-sae"
1470        params["wpa_key_mgmt"] = "SAE"
1471        params['ieee80211w'] = '2'
1472        hapd = hostapd.add_ap(apdev[0], params)
1473        dev[0].set("sae_groups", "")
1474        dev[0].connect("test-wpa3-sae", sae_password="12345678", key_mgmt="SAE",
1475                       ieee80211w="2", scan_freq="2412")
1476        t_events['psk'] = "0123456789abcdef"
1477        dev[1].set("sae_groups", "")
1478        dev[1].connect("test-wpa3-sae", sae_password="0123456789abcdef",
1479                       key_mgmt="SAE", ieee80211w="2", scan_freq="2412")
1480    finally:
1481        t_events['stop'].set()
1482        t.join()
1483
1484def test_radius_psk_default(dev, apdev):
1485    """WPA2 with default PSK"""
1486    ssid = "test-wpa2-psk"
1487    params = hostapd.radius_params()
1488    params['ssid'] = ssid
1489    params["wpa"] = "2"
1490    params["wpa_key_mgmt"] = "WPA-PSK"
1491    params["rsn_pairwise"] = "CCMP"
1492    params['macaddr_acl'] = '2'
1493    params['wpa_psk_radius'] = '1'
1494    params['wpa_passphrase'] = 'qwertyuiop'
1495    hapd = hostapd.add_ap(apdev[0], params)
1496
1497    dev[0].connect(ssid, psk="qwertyuiop", scan_freq="2412")
1498    dev[0].dump_monitor()
1499    dev[0].request("REMOVE_NETWORK all")
1500    dev[0].wait_disconnected()
1501    dev[0].dump_monitor()
1502
1503    hapd.disable()
1504    hapd.set("wpa_psk_radius", "2")
1505    hapd.enable()
1506    dev[0].connect(ssid, psk="qwertyuiop", scan_freq="2412", wait_connect=False)
1507    ev = dev[0].wait_event(["CTRL-EVENT-AUTH-REJECT"], timeout=10)
1508    if ev is None:
1509        raise Exception("No CTRL-EVENT-AUTH-REJECT event")
1510    dev[0].request("DISCONNECT")
1511
1512def test_radius_auth_force_client_addr(dev, apdev):
1513    """RADIUS client address specified"""
1514    params = hostapd.wpa2_eap_params(ssid="radius-auth")
1515    params['radius_client_addr'] = "127.0.0.1"
1516    hapd = hostapd.add_ap(apdev[0], params)
1517    connect(dev[0], "radius-auth")
1518
1519def test_radius_auth_force_client_dev(dev, apdev):
1520    """RADIUS client device specified"""
1521    params = hostapd.wpa2_eap_params(ssid="radius-auth")
1522    params['radius_client_dev'] = "lo"
1523    hapd = hostapd.add_ap(apdev[0], params)
1524    connect(dev[0], "radius-auth")
1525
1526@remote_compatible
1527def test_radius_auth_force_invalid_client_addr(dev, apdev):
1528    """RADIUS client address specified and invalid address"""
1529    params = hostapd.wpa2_eap_params(ssid="radius-auth")
1530    #params['radius_client_addr'] = "10.11.12.14"
1531    params['radius_client_addr'] = "1::2"
1532    hapd = hostapd.add_ap(apdev[0], params)
1533    connect(dev[0], "radius-auth", wait_connect=False)
1534    ev = dev[0].wait_event(["CTRL-EVENT-EAP-STARTED"])
1535    if ev is None:
1536        raise Exception("Timeout on EAP start")
1537    ev = dev[0].wait_event(["CTRL-EVENT-CONNECTED"], timeout=1)
1538    if ev is not None:
1539        raise Exception("Unexpected connection")
1540
1541def add_message_auth(req):
1542    req.authenticator = req.CreateAuthenticator()
1543    hmac_obj = hmac.new(req.secret, digestmod=hashlib.md5)
1544    hmac_obj.update(struct.pack("B", req.code))
1545    hmac_obj.update(struct.pack("B", req.id))
1546
1547    # request attributes
1548    req.AddAttribute("Message-Authenticator", 16*b"\x00")
1549    attrs = req._PktEncodeAttributes()
1550
1551    # Length
1552    flen = 4 + 16 + len(attrs)
1553    hmac_obj.update(struct.pack(">H", flen))
1554    hmac_obj.update(req.authenticator)
1555    hmac_obj.update(attrs)
1556    del req[80]
1557    add_message_authenticator_attr(req, hmac_obj.digest())
1558
1559def test_radius_server_failures(dev, apdev):
1560    """RADIUS server failure cases"""
1561    try:
1562        import pyrad.client
1563        import pyrad.packet
1564        import pyrad.dictionary
1565    except ImportError:
1566        raise HwsimSkip("No pyrad modules available")
1567
1568    dict = pyrad.dictionary.Dictionary("dictionary.radius")
1569    client = pyrad.client.Client(server="127.0.0.1", authport=1812,
1570                                 secret=b"radius", dict=dict)
1571    client.retries = 1
1572    client.timeout = 1
1573
1574    # unexpected State
1575    req = client.CreateAuthPacket(code=pyrad.packet.AccessRequest,
1576                                  User_Name="foo")
1577    req['State'] = b'foo-state'
1578    add_message_auth(req)
1579    reply = client.SendPacket(req)
1580    if reply.code != pyrad.packet.AccessReject:
1581        raise Exception("Unexpected RADIUS response code " + str(reply.code))
1582
1583    # no EAP-Message
1584    req = client.CreateAuthPacket(code=pyrad.packet.AccessRequest,
1585                                  User_Name="foo")
1586    add_message_auth(req)
1587    try:
1588        reply = client.SendPacket(req)
1589        raise Exception("Unexpected response")
1590    except pyrad.client.Timeout:
1591        pass
1592
1593def test_ap_vlan_wpa2_psk_radius_required(dev, apdev):
1594    """AP VLAN with WPA2-PSK and RADIUS attributes required"""
1595    try:
1596        import pyrad.server
1597        import pyrad.packet
1598        import pyrad.dictionary
1599    except ImportError:
1600        raise HwsimSkip("No pyrad modules available")
1601
1602    class TestServer(pyrad.server.Server):
1603        def _HandleAuthPacket(self, pkt):
1604            pyrad.server.Server._HandleAuthPacket(self, pkt)
1605            logger.info("Received authentication request")
1606            reply = self.CreateReplyPacket(pkt)
1607            reply.code = pyrad.packet.AccessAccept
1608            secret = reply.secret
1609            if self.t_events['extra'].is_set():
1610                reply.AddAttribute("Chargeable-User-Identity", "test-cui")
1611                reply.AddAttribute("User-Name", "test-user")
1612            if self.t_events['long'].is_set():
1613                reply.AddAttribute("Tunnel-Type", 13)
1614                reply.AddAttribute("Tunnel-Medium-Type", 6)
1615                reply.AddAttribute("Tunnel-Private-Group-ID", "1")
1616            build_message_auth(pkt, reply)
1617            self.SendReplyPacket(pkt.fd, reply)
1618
1619        def RunWithStop(self, t_events):
1620            self._poll = select.poll()
1621            self._fdmap = {}
1622            self._PrepareSockets()
1623            self.t_events = t_events
1624
1625            while not t_events['stop'].is_set():
1626                for (fd, event) in self._poll.poll(1000):
1627                    if event == select.POLLIN:
1628                        try:
1629                            fdo = self._fdmap[fd]
1630                            self._ProcessInput(fdo)
1631                        except pyrad.server.ServerPacketError as err:
1632                            logger.info("pyrad server dropping packet: " + str(err))
1633                        except pyrad.packet.PacketError as err:
1634                            logger.info("pyrad server received invalid packet: " + str(err))
1635                    else:
1636                        logger.error("Unexpected event in pyrad server main loop")
1637
1638            for fd in self.authfds + self.acctfds:
1639                fd.close()
1640
1641    srv = TestServer(dict=pyrad.dictionary.Dictionary("dictionary.radius"),
1642                     authport=18138, acctport=18139)
1643    srv.hosts["127.0.0.1"] = pyrad.server.RemoteHost("127.0.0.1",
1644                                                     b"radius",
1645                                                     "localhost")
1646    srv.BindToAddress("127.0.0.1")
1647    t_events = {}
1648    t_events['stop'] = threading.Event()
1649    t_events['long'] = threading.Event()
1650    t_events['extra'] = threading.Event()
1651    t = threading.Thread(target=run_pyrad_server, args=(srv, t_events))
1652    t.start()
1653
1654    try:
1655        ssid = "test-wpa2-psk"
1656        params = hostapd.radius_params()
1657        params['ssid'] = ssid
1658        params["wpa"] = "2"
1659        params["wpa_key_mgmt"] = "WPA-PSK"
1660        params["rsn_pairwise"] = "CCMP"
1661        params['macaddr_acl'] = '2'
1662        params['dynamic_vlan'] = "2"
1663        params['wpa_passphrase'] = '0123456789abcdefghi'
1664        params['auth_server_port'] = "18138"
1665        hapd = hostapd.add_ap(apdev[0], params)
1666
1667        logger.info("connecting without VLAN")
1668        dev[0].connect(ssid, psk="0123456789abcdefghi", scan_freq="2412",
1669                       wait_connect=False)
1670        ev = dev[0].wait_event(["CTRL-EVENT-CONNECTED",
1671                                "CTRL-EVENT-SSID-TEMP-DISABLED"], timeout=20)
1672        if ev is None:
1673            raise Exception("Timeout on connection attempt")
1674        if "CTRL-EVENT-CONNECTED" in ev:
1675            raise Exception("Unexpected success without vlan parameters")
1676        logger.info("connecting without VLAN failed as expected")
1677
1678        logger.info("connecting without VLAN (CUI/User-Name)")
1679        t_events['extra'].set()
1680        dev[1].connect(ssid, psk="0123456789abcdefghi", scan_freq="2412",
1681                       wait_connect=False)
1682        ev = dev[1].wait_event(["CTRL-EVENT-CONNECTED",
1683                                "CTRL-EVENT-SSID-TEMP-DISABLED"], timeout=20)
1684        if ev is None:
1685            raise Exception("Timeout on connection attempt")
1686        if "CTRL-EVENT-CONNECTED" in ev:
1687            raise Exception("Unexpected success without vlan parameters(2)")
1688        logger.info("connecting without VLAN failed as expected(2)")
1689        t_events['extra'].clear()
1690
1691        t_events['long'].set()
1692        logger.info("connecting with VLAN")
1693        dev[2].connect(ssid, psk="0123456789abcdefghi", scan_freq="2412",
1694                       wait_connect=False)
1695        ev = dev[2].wait_event(["CTRL-EVENT-CONNECTED",
1696                                "CTRL-EVENT-SSID-TEMP-DISABLED"], timeout=20)
1697        if ev is None:
1698            raise Exception("Timeout on connection attempt")
1699        if "CTRL-EVENT-SSID-TEMP-DISABLED" in ev:
1700            raise Exception("Unexpected failure with vlan parameters")
1701        logger.info("connecting with VLAN succeeded as expected")
1702    finally:
1703        t_events['stop'].set()
1704        t.join()
1705
1706def test_radius_mppe_failure(dev, apdev):
1707    """RADIUS failure when adding MPPE keys"""
1708    params = {"ssid": "as", "beacon_int": "2000",
1709              "radius_server_clients": "auth_serv/radius_clients.conf",
1710              "radius_server_auth_port": '18127',
1711              "eap_server": "1",
1712              "eap_user_file": "auth_serv/eap_user.conf",
1713              "ca_cert": "auth_serv/ca.pem",
1714              "server_cert": "auth_serv/server.pem",
1715              "private_key": "auth_serv/server.key"}
1716    authsrv = hostapd.add_ap(apdev[1], params)
1717
1718    params = hostapd.wpa2_eap_params(ssid="test-wpa2-eap")
1719    params['auth_server_port'] = "18127"
1720    hapd = hostapd.add_ap(apdev[0], params)
1721
1722    with fail_test(authsrv, 1, "os_get_random;radius_msg_add_mppe_keys"):
1723        dev[0].connect("test-wpa2-eap", key_mgmt="WPA-EAP", eap="TTLS",
1724                       identity="user", anonymous_identity="ttls",
1725                       password="password",
1726                       ca_cert="auth_serv/ca.pem", phase2="autheap=GTC",
1727                       wait_connect=False, scan_freq="2412")
1728        dev[0].wait_disconnected()
1729        dev[0].request("REMOVE_NETWORK all")
1730
1731def test_radius_acct_failure(dev, apdev):
1732    """RADIUS Accounting and failure to add attributes"""
1733    # Connection goes through, but Accounting-Request cannot be sent out due to
1734    # NAS-Identifier being too long to fit into a RADIUS attribute.
1735    params = {"ssid": "radius-acct-open",
1736              'acct_server_addr': "127.0.0.1",
1737              'acct_server_port': "1813",
1738              'acct_server_shared_secret': "radius",
1739              'nas_identifier': 255*'A'}
1740    hapd = hostapd.add_ap(apdev[0], params)
1741    dev[0].connect("radius-acct-open", key_mgmt="NONE", scan_freq="2412")
1742
1743def test_radius_acct_failure_oom(dev, apdev):
1744    """RADIUS Accounting and failure to add attributes due to OOM"""
1745    params = {"ssid": "radius-acct-open",
1746              'acct_server_addr': "127.0.0.1",
1747              'acct_server_port': "1813",
1748              'acct_server_shared_secret': "radius",
1749              'radius_acct_interim_interval': "1",
1750              'nas_identifier': 250*'A',
1751              'radius_acct_req_attr': ["126:s:" + 250*'B',
1752                                       "77:s:" + 250*'C',
1753                                       "127:s:" + 250*'D',
1754                                       "181:s:" + 250*'E']}
1755    hapd = hostapd.add_ap(apdev[0], params)
1756    bssid = hapd.own_addr()
1757
1758    dev[0].scan_for_bss(bssid, freq="2412")
1759    with alloc_fail(hapd, 1, "radius_msg_add_attr;?radius_msg_add_attr_int32;=accounting_msg"):
1760        dev[0].connect("radius-acct-open", key_mgmt="NONE", scan_freq="2412")
1761        wait_fail_trigger(hapd, "GET_ALLOC_FAIL")
1762        dev[0].request("REMOVE_NETWORK all")
1763        dev[0].wait_disconnected()
1764
1765    dev[1].scan_for_bss(bssid, freq="2412")
1766    with alloc_fail(hapd, 1, "accounting_sta_report"):
1767        dev[1].connect("radius-acct-open", key_mgmt="NONE", scan_freq="2412")
1768        wait_fail_trigger(hapd, "GET_ALLOC_FAIL")
1769        dev[1].request("REMOVE_NETWORK all")
1770        dev[1].wait_disconnected()
1771
1772    tests = [(1, "radius_msg_add_attr;?radius_msg_add_attr_int32;=accounting_msg"),
1773             (2, "radius_msg_add_attr;accounting_msg"),
1774             (3, "radius_msg_add_attr;accounting_msg")]
1775    for count, func in tests:
1776        with fail_test(hapd, count, func):
1777            dev[0].connect("radius-acct-open", key_mgmt="NONE",
1778                           scan_freq="2412")
1779            wait_fail_trigger(hapd, "GET_FAIL")
1780            dev[0].request("REMOVE_NETWORK all")
1781            dev[0].wait_disconnected()
1782
1783    dev[0].connect("radius-acct-open", key_mgmt="NONE", scan_freq="2412")
1784    with fail_test(hapd, 8,
1785                   "radius_msg_add_attr;?radius_msg_add_attr_int32;=accounting_sta_report"):
1786        dev[0].request("REMOVE_NETWORK all")
1787        dev[0].wait_disconnected()
1788        wait_fail_trigger(hapd, "GET_FAIL")
1789
1790    with fail_test(hapd, 1, "radius_msg_add_attr;=accounting_report_state"):
1791        hapd.disable()
1792
1793def test_radius_acct_failure_oom_rsn(dev, apdev):
1794    """RADIUS Accounting in RSN and failure to add attributes due to OOM"""
1795    params = hostapd.wpa2_eap_params(ssid="radius-acct")
1796    params['acct_server_addr'] = "127.0.0.1"
1797    params['acct_server_port'] = "1813"
1798    params['acct_server_shared_secret'] = "radius"
1799    params['radius_acct_interim_interval'] = "1"
1800    params['nas_identifier'] = 250*'A'
1801    params['radius_acct_req_attr'] = ["126:s:" + 250*'B',
1802                                      "77:s:" + 250*'C',
1803                                      "127:s:" + 250*'D',
1804                                      "181:s:" + 250*'E']
1805    hapd = hostapd.add_ap(apdev[0], params)
1806    bssid = hapd.own_addr()
1807
1808    dev[0].scan_for_bss(bssid, freq="2412")
1809    with alloc_fail(hapd, 1, "radius_msg_add_attr;?radius_msg_add_attr_int32;=accounting_msg"):
1810        connect(dev[0], "radius-acct")
1811        wait_fail_trigger(hapd, "GET_ALLOC_FAIL")
1812
1813    dev[1].scan_for_bss(bssid, freq="2412")
1814    with alloc_fail(hapd, 1, "accounting_sta_report"):
1815        connect(dev[1], "radius-acct")
1816        wait_fail_trigger(hapd, "GET_ALLOC_FAIL")
1817
1818    dev[2].scan_for_bss(bssid, freq="2412")
1819    connect(dev[2], "radius-acct")
1820
1821    for i in range(1, 8):
1822        with alloc_fail(hapd, i, "radius_msg_add_attr;?radius_msg_add_attr_int32;=accounting_msg"):
1823            wait_fail_trigger(hapd, "GET_ALLOC_FAIL")
1824
1825    for i in range(1, 15):
1826        with alloc_fail(hapd, i, "radius_msg_add_attr;?radius_msg_add_attr_int32;=accounting_sta_report"):
1827            wait_fail_trigger(hapd, "GET_ALLOC_FAIL")
1828
1829def test_radius_acct_failure_sta_data(dev, apdev):
1830    """RADIUS Accounting and failure to get STA data"""
1831    params = {"ssid": "radius-acct-open",
1832              'acct_server_addr': "127.0.0.1",
1833              'acct_server_port': "1813",
1834              'acct_server_shared_secret': "radius"}
1835    hapd = hostapd.add_ap(apdev[0], params)
1836
1837    with fail_test(hapd, 1, "accounting_sta_update_stats"):
1838        dev[0].connect("radius-acct-open", key_mgmt="NONE", scan_freq="2412")
1839        dev[0].request("DISCONNECT")
1840        dev[0].wait_disconnected()
1841        hapd.wait_event(["AP-STA-DISCONNECTED"], timeout=1)
1842
1843def test_radius_tls_freeradius(dev, apdev, test_params):
1844    """RADIUS/TLS with FreeRADIUS"""
1845    if not os.path.exists("FreeRADIUS"):
1846        raise HwsimSkip("FreeRADIUS not available")
1847
1848    confdir = "FreeRADIUS/etc/raddb"
1849    certdir = confdir + "/certs"
1850    pidfile = "/tmp/radiusd.pid"
1851
1852    subprocess.call(['FreeRADIUS/sbin/radiusd',
1853                     '-d', confdir,
1854                     '-xx',
1855                     '-l', test_params['prefix'] + ".freeradius"])
1856    time.sleep(1)
1857    if not os.path.exists(pidfile):
1858        raise Exception("Could not start FreeRADIUS")
1859
1860    params = hostapd.wpa2_eap_params(ssid="radius-tls")
1861    for s in ["auth", "acct"]:
1862        params[s + '_server_addr'] = "127.0.0.1"
1863        params[s + '_server_port'] = "2083"
1864        params[s + '_server_type'] = "TLS"
1865        params[s + '_server_shared_secret'] = "radsec"
1866        params[s + '_server_ca_cert'] = certdir + "/ca.pem"
1867        params[s + '_server_client_cert'] = certdir + "/client.pem"
1868        params[s + '_server_private_key'] = certdir + "/client.key"
1869        params[s + '_server_private_key_passwd'] = "whatever"
1870
1871    try:
1872        hapd = hostapd.add_ap(apdev[0], params)
1873        time.sleep(1)
1874        dev[0].connect("radius-tls", key_mgmt="WPA-EAP", scan_freq="2412",
1875                       eap="PEAP", identity="bob", password="hello")
1876        time.sleep(1)
1877        dev[0].request("DISCONNECT")
1878        dev[0].wait_disconnected()
1879        time.sleep(1)
1880    finally:
1881        with open(pidfile, "r") as f:
1882            pid = int(f.read())
1883            if pid > 0:
1884                os.kill(pid, signal.SIGTERM)
1885
1886def foo():
1887    params['auth_server_addr'] = "127.0.0.1"
1888    params['auth_server_port'] = "2083"
1889    params['auth_server_type'] = "TLS"
1890    params['auth_server_shared_secret'] = "radsec"
1891    params['auth_server_ca_cert'] = certdir + "/ca.pem"
1892    params['auth_server_client_cert'] = certdir + "/client.pem"
1893    params['auth_server_private_key'] = certdir + "/client.key"
1894    params['auth_server_private_key_passwd'] = "whatever"
1895    params['acct_server_addr'] = "127.0.0.1"
1896    params['acct_server_port'] = "2083"
1897    params['acct_server_type'] = "TLS"
1898    params['acct_server_shared_secret'] = "radsec"
1899    params['acct_server_ca_cert'] = certdir + "/ca.pem"
1900    params['acct_server_client_cert'] = certdir + "/client.pem"
1901    params['acct_server_private_key'] = certdir + "/client.key"
1902    params['acct_server_private_key_passwd'] = "whatever"
1903