[PATCH 6/7] hostapd: Add reverse CoA over RADIUS/TLS configuration
Seb Adamski
seb at bionic.network
Sun Oct 18 23:38:13 PDT 2026
Add the per-server options auth_server_reverse_coa and
acct_server_reverse_coa for accepting Disconnect-Request and CoA-Request
messages that a RADIUS/TLS server sends on the connection that hostapd
opened to it (draft-ietf-radext-reverse-coa-08, Section 4: a static
per-server flag on the client, off by default). The options follow the
existing auth_server_* and acct_server_* layout and are available only
with CONFIG_RADIUS_TLS=y. Setting an option for a server that does not
use auth_server_type=TLS or acct_server_type=TLS is a configuration
error; the check is done for the complete configuration since the type
can be set after the option.
When a server has the option set, register a handler with the RADIUS
client that passes the requests to the DAS code. If radius_das_port is
set, the existing DAS context is used; otherwise, a DAS context without
a UDP listener is created, so radius_das_port and radius_das_client are
not needed for this. In both cases, radius_das_time_window and the
radius_das_require_* options apply, and the requests are validated and
the responses authenticated with the shared secret of the server whose
connection carried the request.
Do this also on configuration reload so that the option can be enabled
without a restart. As before, the radius_das_* parameters are read only
at startup.
Move the common part of the DAS configuration into a helper function
used for both cases.
Signed-off-by: Seb Adamski <seb at bionic.network>
---
hostapd/config_file.c | 6 +++
hostapd/hostapd.conf | 16 +++++++
src/ap/ap_config.c | 35 ++++++++++++++
src/ap/hostapd.c | 107 ++++++++++++++++++++++++++++++++++++++----
4 files changed, 155 insertions(+), 9 deletions(-)
diff --git a/hostapd/config_file.c b/hostapd/config_file.c
index 2717adc55..a208508f2 100644
--- a/hostapd/config_file.c
+++ b/hostapd/config_file.c
@@ -2875,6 +2875,9 @@ static int hostapd_config_fill(struct hostapd_config *conf,
os_strcmp(buf, "auth_server_private_key_passwd") == 0) {
os_free(bss->radius->auth_server->private_key_passwd);
bss->radius->auth_server->private_key_passwd = os_strdup(pos);
+ } else if (bss->radius->auth_server &&
+ os_strcmp(buf, "auth_server_reverse_coa") == 0) {
+ bss->radius->auth_server->reverse_coa = !!atoi(pos);
#endif /* CONFIG_RADIUS_TLS */
} else if (os_strcmp(buf, "acct_server_addr") == 0) {
if (hostapd_config_read_radius_addr(
@@ -2940,6 +2943,9 @@ static int hostapd_config_fill(struct hostapd_config *conf,
os_strcmp(buf, "acct_server_private_key_passwd") == 0) {
os_free(bss->radius->acct_server->private_key_passwd);
bss->radius->acct_server->private_key_passwd = os_strdup(pos);
+ } else if (bss->radius->acct_server &&
+ os_strcmp(buf, "acct_server_reverse_coa") == 0) {
+ bss->radius->acct_server->reverse_coa = !!atoi(pos);
#endif /* CONFIG_RADIUS_TLS */
} else if (os_strcmp(buf, "radius_retry_primary_interval") == 0) {
bss->radius->retry_primary_interval = atoi(pos);
diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf
index 8f902b753..fd5b0b0dd 100644
--- a/hostapd/hostapd.conf
+++ b/hostapd/hostapd.conf
@@ -1734,6 +1734,22 @@ own_ip_addr=127.0.0.1
#auth_server_client_cert=<path to client certificate>
#auth_server_private_key=<path to private key>
#auth_server_private_key_passwd=<password for decrypting private key>
+#
+# Reverse CoA (draft-ietf-radext-reverse-coa): accept Disconnect-Request and
+# CoA-Request messages that a RADIUS/TLS server, e.g., a proxy, sends on the
+# connection that hostapd opened to it. This is set for each server and
+# requires auth_server_type=TLS (acct_server_type=TLS) for that server and a
+# build with CONFIG_RADIUS_TLS=y. The requests are validated and answered with
+# the shared secret of that server (normally "radsec" for RADIUS/TLS), and
+# radius_das_time_window and radius_das_require_* apply; radius_das_port is
+# not needed. Requests can be received only while the connection is open, and
+# a server can use either the authentication or the accounting connection, so
+# enable this for both if both use RADIUS/TLS. This can be enabled with a
+# configuration reload, but changes to radius_das_* require a restart.
+# 0 = disabled (default)
+# 1 = enabled
+#auth_server_reverse_coa=1
+#acct_server_reverse_coa=1
# Retry interval for trying to return to the primary RADIUS server (in
# seconds). RADIUS client code will automatically try to use the next server
diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c
index fd778d107..f315afe60 100644
--- a/src/ap/ap_config.c
+++ b/src/ap/ap_config.c
@@ -1299,6 +1299,30 @@ static int hostapd_config_check_bss_wmm_params(struct hostapd_bss_config *bss)
}
+#ifdef CONFIG_RADIUS_TLS
+static bool hostapd_config_check_reverse_coa(
+ const struct hostapd_radius_server *servers, int num_servers,
+ const char *name)
+{
+ char abuf[50];
+ int i;
+
+ for (i = 0; i < num_servers; i++) {
+ if (servers[i].reverse_coa && !servers[i].tls) {
+ wpa_printf(MSG_ERROR,
+ "%s_server_reverse_coa=1 requires %s_server_type=TLS (server %s)",
+ name, name,
+ hostapd_ip_txt(&servers[i].addr, abuf,
+ sizeof(abuf)));
+ return false;
+ }
+ }
+
+ return true;
+}
+#endif /* CONFIG_RADIUS_TLS */
+
+
static int hostapd_config_check_bss(struct hostapd_bss_config *bss,
struct hostapd_config *conf,
int full_config)
@@ -1314,6 +1338,17 @@ static int hostapd_config_check_bss(struct hostapd_bss_config *bss,
return -1;
}
+#ifdef CONFIG_RADIUS_TLS
+ if (full_config && bss->radius &&
+ (!hostapd_config_check_reverse_coa(bss->radius->auth_servers,
+ bss->radius->num_auth_servers,
+ "auth") ||
+ !hostapd_config_check_reverse_coa(bss->radius->acct_servers,
+ bss->radius->num_acct_servers,
+ "acct")))
+ return -1;
+#endif /* CONFIG_RADIUS_TLS */
+
#ifdef CONFIG_WEP
if (bss->wpa) {
int wep, i;
diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c
index a4394f73d..0d2e299d2 100644
--- a/src/ap/hostapd.c
+++ b/src/ap/hostapd.c
@@ -125,6 +125,10 @@ void hostapd_reconfig_encryption(struct hostapd_data *hapd)
}
+#if defined(CONFIG_RADIUS_TLS) && !defined(CONFIG_NO_RADIUS)
+static int hostapd_reverse_coa_init(struct hostapd_data *hapd);
+#endif /* CONFIG_RADIUS_TLS && !CONFIG_NO_RADIUS */
+
static void hostapd_reload_bss(struct hostapd_data *hapd)
{
struct hostapd_ssid *ssid;
@@ -138,6 +142,11 @@ static void hostapd_reload_bss(struct hostapd_data *hapd)
#ifndef CONFIG_NO_RADIUS
radius_client_reconfig(hapd->radius, hapd->conf->radius);
+#ifdef CONFIG_RADIUS_TLS
+ if (hostapd_mld_is_first_bss(hapd) && hostapd_reverse_coa_init(hapd))
+ wpa_printf(MSG_ERROR,
+ "Failed to enable reverse CoA after reloading configuration");
+#endif /* CONFIG_RADIUS_TLS */
#endif /* CONFIG_NO_RADIUS */
ssid = &hapd->conf->ssid;
@@ -1388,6 +1397,86 @@ static int hostapd_start_beacon(struct hostapd_data *hapd,
#ifndef CONFIG_NO_RADIUS
+
+static void hostapd_das_conf_init(struct hostapd_data *hapd,
+ struct radius_das_conf *das_conf)
+{
+ struct hostapd_bss_config *conf = hapd->conf;
+
+ os_memset(das_conf, 0, sizeof(*das_conf));
+ das_conf->time_window = conf->radius_das_time_window;
+ das_conf->require_event_timestamp =
+ conf->radius_das_require_event_timestamp;
+ das_conf->require_message_authenticator =
+ conf->radius_das_require_message_authenticator;
+ das_conf->ctx = hapd;
+ das_conf->disconnect = hostapd_das_disconnect;
+ das_conf->coa = hostapd_das_coa;
+}
+
+
+#ifdef CONFIG_RADIUS_TLS
+
+static struct radius_msg *
+hostapd_das_tls_req(struct radius_msg *msg, const u8 *shared_secret,
+ size_t shared_secret_len, const char *from_addr,
+ int from_port, void *ctx)
+{
+ return radius_das_process(ctx, msg, shared_secret, shared_secret_len,
+ from_addr, from_port);
+}
+
+
+static bool
+hostapd_reverse_coa_enabled(const struct hostapd_radius_servers *conf)
+{
+ int i;
+
+ for (i = 0; i < conf->num_auth_servers; i++) {
+ if (conf->auth_servers[i].reverse_coa)
+ return true;
+ }
+
+ for (i = 0; i < conf->num_acct_servers; i++) {
+ if (conf->acct_servers[i].reverse_coa)
+ return true;
+ }
+
+ return false;
+}
+
+
+/*
+ * Accept DAS requests on RADIUS/TLS connections (reverse CoA). This is called
+ * at initialization and on configuration reload, since the option can be
+ * enabled with a reload.
+ */
+static int hostapd_reverse_coa_init(struct hostapd_data *hapd)
+{
+ struct radius_das_conf das_conf;
+
+ if (!hapd->radius || !hostapd_reverse_coa_enabled(hapd->conf->radius))
+ return 0;
+
+ if (!hapd->radius_das) {
+ /* No UDP DAS listener: requests are validated with the shared
+ * secret of the RADIUS/TLS server that sent them. */
+ hostapd_das_conf_init(hapd, &das_conf);
+ hapd->radius_das = radius_das_init(&das_conf);
+ if (!hapd->radius_das)
+ return -1;
+ wpa_printf(MSG_DEBUG,
+ "DAS: Reverse CoA enabled without a UDP listener");
+ }
+
+ radius_client_set_das_handler(hapd->radius, hostapd_das_tls_req,
+ hapd->radius_das);
+ return 0;
+}
+
+#endif /* CONFIG_RADIUS_TLS */
+
+
static int hostapd_bss_radius_init(struct hostapd_data *hapd)
{
struct hostapd_bss_config *conf;
@@ -1413,20 +1502,12 @@ static int hostapd_bss_radius_init(struct hostapd_data *hapd)
if (conf->radius_das_port) {
struct radius_das_conf das_conf;
- os_memset(&das_conf, 0, sizeof(das_conf));
+ hostapd_das_conf_init(hapd, &das_conf);
das_conf.port = conf->radius_das_port;
das_conf.shared_secret = conf->radius_das_shared_secret;
das_conf.shared_secret_len =
conf->radius_das_shared_secret_len;
das_conf.client_addr = &conf->radius_das_client_addr;
- das_conf.time_window = conf->radius_das_time_window;
- das_conf.require_event_timestamp =
- conf->radius_das_require_event_timestamp;
- das_conf.require_message_authenticator =
- conf->radius_das_require_message_authenticator;
- das_conf.ctx = hapd;
- das_conf.disconnect = hostapd_das_disconnect;
- das_conf.coa = hostapd_das_coa;
hapd->radius_das = radius_das_init(&das_conf);
if (!hapd->radius_das) {
wpa_printf(MSG_ERROR,
@@ -1435,6 +1516,14 @@ static int hostapd_bss_radius_init(struct hostapd_data *hapd)
}
}
+#ifdef CONFIG_RADIUS_TLS
+ if (hostapd_reverse_coa_init(hapd)) {
+ wpa_printf(MSG_ERROR,
+ "RADIUS DAS initialization for reverse CoA failed.");
+ return -1;
+ }
+#endif /* CONFIG_RADIUS_TLS */
+
return 0;
}
#endif /* CONFIG_NO_RADIUS */
--
2.54.0 (Apple Git-157)
More information about the Hostap
mailing list