[PATCH 0/7] RADIUS/TLS reverse CoA (draft-ietf-radext-reverse-coa)
Seb Adamski
seb at bionic.network
Sun Oct 18 23:38:07 PDT 2026
This series lets hostapd accept Disconnect-Request and CoA-Request
messages that a RADIUS/TLS server sends on the TLS connection that
hostapd opened to it ("reverse CoA", draft-ietf-radext-reverse-coa-08).
This is needed when the NAS reaches its RADIUS proxy only outbound,
e.g., behind NAT in a roaming federation that uses RADIUS/TLS: the
proxy cannot open a connection to a UDP DAS listener on the NAS, so it
forwards the request over the existing connection. Section 6 of the
draft extends the routing of RFC 8559, Section 2.1 for this, and
Section 4 requires a static per-server flag on the client, off by
default, with no negotiation on the connection.
Patches:
1-2: Fixes for pre-existing DAS issues that apply on their own:
radius_das_init() could close file descriptor 0 on an early
failure, and a request with Proxy-State was NAKed with 401 instead
of having Proxy-State echoed in the response (RFC 5176, 3.1;
RFC 8559, 4.3.2).
3: RADIUS/TLS receive path: reassemble the stream by the RADIUS Length
field and decrypt all received data before processing it. This is
a pre-existing problem (a second TLS record in the same read was
lost when anything was sent on the connection, a second message in
a record was ignored, and a split message was dropped), but
server-initiated messages make it much more likely.
4: Refactor: DAS request processing separated from the UDP socket
handler, with the shared secret as a parameter. No change in
behavior.
5: RADIUS: accept the requests on the connection of a server
with the flag set; drop them (with a log message) everywhere else
before matching against pending requests. All RADIUS/TLS output
goes through one function that uses MSG_NOSIGNAL and queues data
on a short write or EAGAIN. CoA-NAK uses Error-Cause 406 instead of
405 when CoA is not supported.
6: hostapd configuration: auth_server_reverse_coa and
acct_server_reverse_coa, documented in hostapd.conf. Also applied
on configuration reload.
7: hwsim tests with a small Python RADIUS/TLS server.
Overlap with "[PATCH 0/4] RADIUS/TLS with several messages in flight"
(4 Oct 2026): patch 3 here fixes the same two problems as patches 1/4
and 2/4 of that series, the TLS input buffer reset by
tls_connection_encrypt() and the missing framing by RADIUS Length.
That series changes the OpenSSL wrapper; this one drains the decrypt
function from radius_client.c and leaves the TLS wrappers alone, so
there is no change for the EAP methods. Either fix is fine for the
rest of this series. If the 4 Oct series is applied first, patch 3 can
be dropped and patch 5 goes on top of its radius_client_tls_rx(); I
have tested that combination (their four patches, then patches 1, 2
and 4-7 of this series) with the tests from both series. The tests
are complementary: that series covers back-to-back responses to
Accounting-Requests, this one covers server-initiated requests in the
same framing shapes.
Open design question: the option is per server
(auth_server_reverse_coa/acct_server_reverse_coa), which follows the
per-server flag in the draft and the existing auth_server_*/acct_server_*
layout. A single BSS-level option (e.g., radius_reverse_coa=1 for every
RADIUS/TLS server of the BSS) would be simpler to configure, since a
proxy can use either the authentication or the accounting connection.
I am happy to change it to that if preferred.
Not included, possible follow-ups:
- duplicate request detection per connection (RFC 5176, 2.3; RFC 6613,
2.6.1 says the peer must not retransmit on the same connection)
- keeping the connection open with Status-Server watchdog messages and
reconnecting when idle (draft-ietf-radext-reverse-coa-08, Section 5,
Tw = 15 s); today, requests can only be received while a connection
is open
- hostapd does not default to or enforce the shared secret "radsec" on
RADIUS/TLS servers (pre-existing)
- the TLS handshake send path does not use MSG_NOSIGNAL, and
application data received together with the last handshake message
is not processed (both pre-existing)
Testing:
The new test cases need hostapd built with CONFIG_RADIUS_TLS=y (they are
skipped otherwise) and pyrad. With CONFIG_RADIUS_TLS=y added to
tests/hwsim/example-hostapd.config:
./run-tests.py radius_das_proxy_state 'radius_tls_reverse_coa_*'
or the whole module with ./run-tests.py -f radius. All radius_das_*,
radius_tls_* and new tests pass, also with hostapd built with
-fsanitize=address,undefined. Builds checked with and without
CONFIG_RADIUS_TLS (hwsim and default configs), wpa_supplicant and
eapol_test. The series was also run against a radsecproxy and a
FreeRADIUS server that send Disconnect-Request down the NAS's
connection, through two proxy hops.
Based on main at fcccc8c82.
Seb Adamski (7):
RADIUS DAS: Initialize socket to -1 before bind
RADIUS DAS: Echo Proxy-State in responses
RADIUS: Reassemble RADIUS/TLS stream by RADIUS Length
RADIUS DAS: Separate request processing from the UDP socket handler
RADIUS: Accept Disconnect-Request and CoA-Request over RADIUS/TLS
hostapd: Add reverse CoA over RADIUS/TLS configuration
tests: RADIUS/TLS reverse CoA
hostapd/config_file.c | 6 +
hostapd/hostapd.conf | 16 +
src/ap/ap_config.c | 35 +
src/ap/hostapd.c | 107 ++-
src/radius/radius_client.c | 625 +++++++++++++---
src/radius/radius_client.h | 20 +
src/radius/radius_das.c | 164 +++--
src/radius/radius_das.h | 26 +
.../auth_serv/radius_clients_radsec.conf | 1 +
tests/hwsim/dictionary.radius | 2 +
tests/hwsim/radius_tls_server.py | 429 +++++++++++
tests/hwsim/test_radius.py | 677 +++++++++++++++++-
12 files changed, 1940 insertions(+), 168 deletions(-)
create mode 100644 tests/hwsim/auth_serv/radius_clients_radsec.conf
create mode 100644 tests/hwsim/radius_tls_server.py
base-commit: fcccc8c82e0b33e348f3f34f3777186c9efc73f5
--
2.54.0 (Apple Git-157)
More information about the Hostap
mailing list