From: Felix Fietkau <nbd@nbd.name>
Date: Fri, 16 Dec 2022 13:32:48 +0100
Subject: [PATCH] hostapd: allow sharing the incoming DAS port across multiple
 interfaces

Use the NAS identifier to find the right receiver context on incoming messages

--- a/src/ap/hostapd.c
+++ b/src/ap/hostapd.c
@@ -1379,6 +1379,7 @@ static int hostapd_bss_radius_init(struc
 
 		os_memset(&das_conf, 0, sizeof(das_conf));
 		das_conf.port = conf->radius_das_port;
+		das_conf.nas_identifier = conf->nas_identifier;
 		das_conf.shared_secret = conf->radius_das_shared_secret;
 		das_conf.shared_secret_len =
 			conf->radius_das_shared_secret_len;
--- a/src/radius/radius_das.c
+++ b/src/radius/radius_das.c
@@ -12,13 +12,26 @@
 #include "utils/common.h"
 #include "utils/eloop.h"
 #include "utils/ip_addr.h"
+#include "utils/list.h"
 #include "radius.h"
 #include "radius_das.h"
 
 
-struct radius_das_data {
+static struct dl_list das_ports = DL_LIST_HEAD_INIT(das_ports);
+
+struct radius_das_port {
+	struct dl_list list;
+	struct dl_list das_data;
+
+	int port;
 	int sock;
+};
+
+struct radius_das_data {
+	struct dl_list list;
+	struct radius_das_port *port;
 	u8 *shared_secret;
+	u8 *nas_identifier;
 	size_t shared_secret_len;
 	struct hostapd_ip_addr client_addr;
 	unsigned int time_window;
@@ -388,56 +401,17 @@ fail:
 }
 
 
-static void radius_das_receive(int sock, void *eloop_ctx, void *sock_ctx)
+static void
+radius_das_receive_msg(struct radius_das_data *das, struct radius_msg *msg,
+		       struct sockaddr *from, socklen_t fromlen,
+		       char *abuf, int from_port)
 {
-	struct radius_das_data *das = eloop_ctx;
-	u8 buf[1500];
-	union {
-		struct sockaddr_storage ss;
-		struct sockaddr_in sin;
-#ifdef CONFIG_IPV6
-		struct sockaddr_in6 sin6;
-#endif /* CONFIG_IPV6 */
-	} from;
-	char abuf[50];
-	int from_port = 0;
-	socklen_t fromlen;
-	int len;
-	struct radius_msg *msg, *reply = NULL;
+	struct radius_msg *reply = NULL;
 	struct radius_hdr *hdr;
 	struct wpabuf *rbuf;
+	struct os_time now;
 	u32 val;
 	int res;
-	struct os_time now;
-
-	fromlen = sizeof(from);
-	len = recvfrom(sock, buf, sizeof(buf), 0,
-		       (struct sockaddr *) &from.ss, &fromlen);
-	if (len < 0) {
-		wpa_printf(MSG_ERROR, "DAS: recvfrom: %s", strerror(errno));
-		return;
-	}
-
-	os_strlcpy(abuf, inet_ntoa(from.sin.sin_addr), sizeof(abuf));
-	from_port = ntohs(from.sin.sin_port);
-
-	wpa_printf(MSG_DEBUG, "DAS: Received %d bytes from %s:%d",
-		   len, abuf, from_port);
-	if (das->client_addr.u.v4.s_addr &&
-	    das->client_addr.u.v4.s_addr != from.sin.sin_addr.s_addr) {
-		wpa_printf(MSG_DEBUG, "DAS: Drop message from unknown client");
-		return;
-	}
-
-	msg = radius_msg_parse(buf, len);
-	if (msg == NULL) {
-		wpa_printf(MSG_DEBUG, "DAS: Parsing incoming RADIUS packet "
-			   "from %s:%d failed", abuf, from_port);
-		return;
-	}
-
-	if (wpa_debug_level <= MSG_MSGDUMP)
-		radius_msg_dump(msg);
 
 	if (radius_msg_verify_das_req(msg, das->shared_secret,
 				       das->shared_secret_len,
@@ -504,9 +478,8 @@ static void radius_das_receive(int sock,
 			radius_msg_dump(reply);
 
 		rbuf = radius_msg_get_buf(reply);
-		res = sendto(das->sock, wpabuf_head(rbuf),
-			     wpabuf_len(rbuf), 0,
-			     (struct sockaddr *) &from.ss, fromlen);
+		res = sendto(das->port->sock, wpabuf_head(rbuf),
+			     wpabuf_len(rbuf), 0, from, fromlen);
 		if (res < 0) {
 			wpa_printf(MSG_ERROR, "DAS: sendto(to %s:%d): %s",
 				   abuf, from_port, strerror(errno));
@@ -518,6 +491,72 @@ fail:
 	radius_msg_free(reply);
 }
 
+static void radius_das_receive(int sock, void *eloop_ctx, void *sock_ctx)
+{
+	struct radius_das_port *p = eloop_ctx;
+	struct radius_das_data *das;
+	u8 buf[1500];
+	union {
+		struct sockaddr_storage ss;
+		struct sockaddr_in sin;
+#ifdef CONFIG_IPV6
+		struct sockaddr_in6 sin6;
+#endif /* CONFIG_IPV6 */
+	} from;
+	struct radius_msg *msg;
+	size_t nasid_len = 0;
+	u8 *nasid_buf = NULL;
+	char abuf[50];
+	int from_port = 0;
+	socklen_t fromlen;
+	int found = 0;
+	int len;
+
+	fromlen = sizeof(from);
+	len = recvfrom(sock, buf, sizeof(buf), 0,
+		       (struct sockaddr *) &from.ss, &fromlen);
+	if (len < 0) {
+		wpa_printf(MSG_ERROR, "DAS: recvfrom: %s", strerror(errno));
+		return;
+	}
+
+	os_strlcpy(abuf, inet_ntoa(from.sin.sin_addr), sizeof(abuf));
+	from_port = ntohs(from.sin.sin_port);
+
+	msg = radius_msg_parse(buf, len);
+	if (msg == NULL) {
+		wpa_printf(MSG_DEBUG, "DAS: Parsing incoming RADIUS packet "
+			   "from %s:%d failed", abuf, from_port);
+		return;
+	}
+
+	wpa_printf(MSG_DEBUG, "DAS: Received %d bytes from %s:%d",
+		   len, abuf, from_port);
+
+	if (wpa_debug_level <= MSG_MSGDUMP)
+		radius_msg_dump(msg);
+
+	radius_msg_get_attr_ptr(msg, RADIUS_ATTR_NAS_IDENTIFIER,
+				&nasid_buf, &nasid_len, NULL);
+	dl_list_for_each(das, &p->das_data, struct radius_das_data, list) {
+		if (das->client_addr.u.v4.s_addr &&
+		    das->client_addr.u.v4.s_addr != from.sin.sin_addr.s_addr)
+			continue;
+
+		if (das->nas_identifier && nasid_buf &&
+		    (nasid_len != os_strlen(das->nas_identifier) ||
+		     os_memcmp(das->nas_identifier, nasid_buf, nasid_len) != 0))
+			continue;
+
+		found = 1;
+		radius_das_receive_msg(das, msg, (struct sockaddr *)&from.ss,
+				       fromlen, abuf, from_port);
+	}
+
+	if (!found)
+		wpa_printf(MSG_DEBUG, "DAS: Drop message from unknown client");
+}
+
 
 static int radius_das_open_socket(int port)
 {
@@ -543,6 +582,49 @@ static int radius_das_open_socket(int po
 }
 
 
+static struct radius_das_port *
+radius_das_open_port(int port)
+{
+	struct radius_das_port *p;
+
+	dl_list_for_each(p, &das_ports, struct radius_das_port, list) {
+		if (p->port == port)
+			return p;
+	}
+
+	p = os_zalloc(sizeof(*p));
+	if (p == NULL)
+		return NULL;
+
+	dl_list_init(&p->das_data);
+	p->port = port;
+	p->sock = radius_das_open_socket(port);
+	if (p->sock < 0)
+		goto free_port;
+
+	if (eloop_register_read_sock(p->sock, radius_das_receive, p, NULL))
+		goto close_port;
+
+	dl_list_add(&das_ports, &p->list);
+
+	return p;
+
+close_port:
+	close(p->sock);
+free_port:
+	os_free(p);
+
+	return NULL;
+}
+
+static void radius_das_close_port(struct radius_das_port *p)
+{
+	dl_list_del(&p->list);
+	eloop_unregister_read_sock(p->sock);
+	close(p->sock);
+	free(p);
+}
+
 struct radius_das_data *
 radius_das_init(struct radius_das_conf *conf)
 {
@@ -563,6 +645,8 @@ radius_das_init(struct radius_das_conf *
 	das->ctx = conf->ctx;
 	das->disconnect = conf->disconnect;
 	das->coa = conf->coa;
+	if (conf->nas_identifier)
+		das->nas_identifier = os_strdup(conf->nas_identifier);
 
 	os_memcpy(&das->client_addr, conf->client_addr,
 		  sizeof(das->client_addr));
@@ -575,19 +659,15 @@ radius_das_init(struct radius_das_conf *
 	}
 	das->shared_secret_len = conf->shared_secret_len;
 
-	das->sock = radius_das_open_socket(conf->port);
-	if (das->sock < 0) {
+	das->port = radius_das_open_port(conf->port);
+	if (!das->port) {
 		wpa_printf(MSG_ERROR, "Failed to open UDP socket for RADIUS "
 			   "DAS");
 		radius_das_deinit(das);
 		return NULL;
 	}
 
-	if (eloop_register_read_sock(das->sock, radius_das_receive, das, NULL))
-	{
-		radius_das_deinit(das);
-		return NULL;
-	}
+	dl_list_add(&das->port->das_data, &das->list);
 
 	return das;
 }
@@ -598,11 +678,14 @@ void radius_das_deinit(struct radius_das
 	if (das == NULL)
 		return;
 
-	if (das->sock >= 0) {
-		eloop_unregister_read_sock(das->sock);
-		close(das->sock);
+	if (das->port) {
+		dl_list_del(&das->list);
+
+		if (dl_list_empty(&das->port->das_data))
+			radius_das_close_port(das->port);
 	}
 
+	os_free(das->nas_identifier);
 	os_free(das->shared_secret);
 	os_free(das);
 }
--- a/src/radius/radius_das.h
+++ b/src/radius/radius_das.h
@@ -44,6 +44,7 @@ struct radius_das_attrs {
 struct radius_das_conf {
 	int port;
 	const u8 *shared_secret;
+	const u8 *nas_identifier;
 	size_t shared_secret_len;
 	const struct hostapd_ip_addr *client_addr;
 	unsigned int time_window;
