hostapd: allow sharing the incoming DAS port across multiple interfaces
authorFelix Fietkau <nbd@nbd.name>
Fri, 16 Dec 2022 12:32:48 +0000 (13:32 +0100)
committerFelix Fietkau <nbd@nbd.name>
Tue, 27 Dec 2022 18:10:04 +0000 (19:10 +0100)
Use the NAS identifier to find the right receiver context on incoming messages

Signed-off-by: Felix Fietkau <nbd@nbd.name>
package/network/services/hostapd/patches/761-shared_das_port.patch [new file with mode: 0644]

diff --git a/package/network/services/hostapd/patches/761-shared_das_port.patch b/package/network/services/hostapd/patches/761-shared_das_port.patch
new file mode 100644 (file)
index 0000000..7516b73
--- /dev/null
@@ -0,0 +1,298 @@
+--- 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;
+--- a/src/ap/hostapd.c
++++ b/src/ap/hostapd.c
+@@ -1367,6 +1367,7 @@ static int hostapd_setup_bss(struct host
+               struct radius_das_conf das_conf;
+               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;
+@@ -378,56 +391,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,
+@@ -494,9 +468,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));
+@@ -508,6 +481,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)
+ {
+@@ -533,6 +572,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)
+ {
+@@ -553,6 +635,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));
+@@ -565,19 +649,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;
+ }
+@@ -588,11 +668,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);
+ }