hostapd: add AFC support
[openwrt/staging/nbd.git] / package / network / services / hostapd / patches / 800-hostapd-afcd-add-AFC-daemon-support.patch
diff --git a/package/network/services/hostapd/patches/800-hostapd-afcd-add-AFC-daemon-support.patch b/package/network/services/hostapd/patches/800-hostapd-afcd-add-AFC-daemon-support.patch
new file mode 100644 (file)
index 0000000..705e4b0
--- /dev/null
@@ -0,0 +1,575 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Sat, 17 Feb 2024 11:24:44 +0100
+Subject: [PATCH] hostapd: afcd: add AFC daemon support
+
+Introduce Automated Frequency Coordination Daemon (AFCD) support
+for UNII-5 and UNII-7 6GHz bands.
+AFCD will be used by hostapd AFC client in order to forward the AFC
+request to the AFC coordinator and decouple AFC connection management
+from hostapd.
+AFC is required for Standard Power Devices (SPDs) to determine a lists
+of channels and EIRP/PSD powers that are available in the 6GHz spectrum.
+AFCD is tested with AFC DUT Test Harness [0].
+Add afc-reply.json as reference for replies from the AFC coordinator.
+
+[0] https://github.com/Wi-FiTestSuite/AFC-DUT/tree/main
+
+Tested-by: Allen Ye <allen.ye@mediatek.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+---
+ create mode 100644 afc/.gitignore
+ create mode 100644 afc/Makefile
+ create mode 100644 afc/afc-reply.json
+ create mode 100644 afc/afcd.c
+
+--- /dev/null
++++ b/afc/.gitignore
+@@ -0,0 +1 @@
++afcd
+--- /dev/null
++++ b/afc/Makefile
+@@ -0,0 +1,31 @@
++ALL=afcd
++
++include ../src/build.rules
++
++CFLAGS += -I../src/utils
++CFLAGS += -I../src
++
++OBJS=afcd.o
++OBJS += ../src/utils/common.o
++OBJS += ../src/utils/wpa_debug.o
++OBJS += ../src/utils/wpabuf.o
++
++ifndef CONFIG_OS
++ifdef CONFIG_NATIVE_WINDOWS
++CONFIG_OS=win32
++else
++CONFIG_OS=unix
++endif
++endif
++OBJS += ../src/utils/os_$(CONFIG_OS).o
++
++LIBS += -lcurl
++
++_OBJS_VAR := OBJS
++include ../src/objs.mk
++afcd: $(OBJS)
++      $(Q)$(LDO) $(LDFLAGS) -o afcd $(OBJS) $(LIBS)
++      @$(E) "  LD " $@
++
++clean: common-clean
++      rm -f core *~
+--- /dev/null
++++ b/afc/afc-reply.json
+@@ -0,0 +1,215 @@
++{
++   "availableSpectrumInquiryResponses":[
++      {
++         "availabilityExpireTime":"2023-02-23T12:53:18Z",
++         "availableChannelInfo":[
++            {
++               "channelCfi":[
++                  1,
++                  5,
++                  9,
++                  13,
++                  17,
++                  21,
++                  25,
++                  29,
++                  33,
++                  37,
++                  41,
++                  45,
++                  49,
++                  53,
++                  57,
++                  61,
++                  65,
++                  69,
++                  73,
++                  77,
++                  81,
++                  85,
++                  89,
++                  93,
++                  117,
++                  121,
++                  125,
++                  129,
++                  133,
++                  137,
++                  141,
++                  145,
++                  149,
++                  153,
++                  157,
++                  161,
++                  165,
++                  169,
++                  173,
++                  177,
++                  181
++               ],
++               "globalOperatingClass":131,
++               "maxEirp":[
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5
++               ]
++            },
++            {
++               "channelCfi":[
++                  3,
++                  11,
++                  19,
++                  27,
++                  35,
++                  43,
++                  51,
++                  59,
++                  67,
++                  75,
++                  83,
++                  91,
++                  123,
++                  131,
++                  139,
++                  147,
++                  155,
++                  163,
++                  171,
++                  179
++               ],
++               "globalOperatingClass":132,
++               "maxEirp":[
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5
++               ]
++            },
++            {
++               "channelCfi":[
++                  7,
++                  23,
++                  39,
++                  55,
++                  71,
++                  87,
++                  135,
++                  151,
++                  167
++               ],
++               "globalOperatingClass":133,
++               "maxEirp":[
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5
++               ]
++            },
++            {
++               "channelCfi":[
++                  15,
++                  47,
++                  79,
++                  143
++               ],
++               "globalOperatingClass":134,
++               "maxEirp":[
++                  5,
++                  5,
++                  5,
++                  5
++               ]
++            },
++            {
++               "channelCfi":[
++               ],
++               "globalOperatingClass":135,
++               "maxEirp":[
++               ]
++            }
++         ],
++         "availableFrequencyInfo":[
++            {
++               "frequencyRange":{
++                  "highFrequency":6425,
++                  "lowFrequency":5925
++               },
++               "maxPSD":3.98970004336019
++            },
++            {
++               "frequencyRange":{
++                  "highFrequency":6865,
++                  "lowFrequency":6525
++               },
++               "maxPSD":3.98970004336019
++            }
++         ],
++         "requestId":"11235814",
++         "response":{
++            "responseCode":0,
++            "shortDescription":"Success"
++         },
++         "rulesetId":"US_47_CFR_PART_15_SUBPART_E"
++      }
++   ],
++   "version":"1.1"
++}
+--- /dev/null
++++ b/afc/afcd.c
+@@ -0,0 +1,292 @@
++/*
++ * Automated Frequency Coordination Daemon
++ * Copyright (c) 2024, Lorenzo Bianconi <lorenzo@kernel.org>
++ *
++ * This software may be distributed under the terms of the BSD license.
++ * See README for more details.
++ */
++
++#include <curl/curl.h>
++#include <sys/un.h>
++#include <sys/stat.h>
++
++#include "utils/includes.h"
++#include "utils/common.h"
++
++#define CURL_TIMEOUT  60
++#define AFCD_SOCK     "afcd.sock"
++
++struct curl_ctx {
++      char *buf;
++      size_t buf_len;
++};
++
++static volatile bool exiting;
++
++static char *path = "/var/run";
++static char *bearer_token;
++static char *url;
++static int port = 443;
++
++
++static size_t afcd_curl_cb_write(void *ptr, size_t size, size_t nmemb,
++                               void *userdata)
++{
++      struct curl_ctx *ctx = userdata;
++      char *buf;
++
++      buf = os_realloc(ctx->buf, ctx->buf_len + size * nmemb + 1);
++      if (!buf)
++              return 0;
++
++      ctx->buf = buf;
++      os_memcpy(buf + ctx->buf_len, ptr, size * nmemb);
++      buf[ctx->buf_len + size * nmemb] = '\0';
++      ctx->buf_len += size * nmemb;
++
++      return size * nmemb;
++}
++
++
++static int afcd_send_request(struct curl_ctx *ctx, unsigned char *request)
++{
++      struct curl_slist *headers = NULL;
++      CURL *curl;
++      int ret;
++
++      wpa_printf(MSG_DEBUG, "Sending AFC request to %s", url);
++
++      curl_global_init(CURL_GLOBAL_ALL);
++      curl = curl_easy_init();
++      if (!curl)
++              return -ENOMEM;
++
++      headers  = curl_slist_append(headers, "Accept: application/json");
++      headers  = curl_slist_append(headers,
++                                   "Content-Type: application/json");
++      headers  = curl_slist_append(headers, "charset: utf-8");
++
++      curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
++      curl_easy_setopt(curl, CURLOPT_URL, url);
++      curl_easy_setopt(curl, CURLOPT_PORT, port);
++      curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
++      curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,
++                       afcd_curl_cb_write);
++      curl_easy_setopt(curl, CURLOPT_WRITEDATA, ctx);
++      curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcrp/0.1");
++      curl_easy_setopt(curl, CURLOPT_TIMEOUT, CURL_TIMEOUT);
++      curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
++      if (bearer_token)
++              curl_easy_setopt(curl, CURLOPT_XOAUTH2_BEARER, bearer_token);
++      curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BEARER);
++      curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
++      curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 1L);
++      curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request);
++      curl_easy_setopt(curl, CURLOPT_SSL_VERIFYSTATUS, 1L);
++
++      ret = curl_easy_perform(curl);
++      if (ret != CURLE_OK)
++              wpa_printf(MSG_ERROR, "curl_easy_perform failed: %s",
++                         curl_easy_strerror(ret));
++
++      curl_easy_cleanup(curl);
++      curl_global_cleanup();
++
++      return ret == CURLE_OK ? 0 : -EINVAL;
++}
++
++
++static void handle_term(int sig)
++{
++      wpa_printf(MSG_ERROR, "Received signal %d", sig);
++      exiting = true;
++}
++
++
++static void usage(void)
++{
++      wpa_printf(MSG_ERROR,
++                 "%s:\n"
++                 "afcd -u<url> [-p<port>][-t<token>][-D<unix-sock dir>][-P<PID file>][-dB]",
++                 __func__);
++}
++
++
++#define BUFSIZE               8192
++static int afcd_server_run(void)
++{
++      size_t len = os_strlen(path) + 1 + os_strlen(AFCD_SOCK);
++      struct sockaddr_un addr = {
++              .sun_family = AF_UNIX,
++#ifdef __FreeBSD__
++              .sun_len = sizeof(addr),
++#endif /* __FreeBSD__ */
++      };
++      int sockfd, ret = 0;
++      char *fname = NULL;
++      unsigned char *buf;
++      fd_set read_set;
++
++      if (len >= sizeof(addr.sun_path))
++              return -EINVAL;
++
++      if (mkdir(path, S_IRWXU | S_IRWXG) < 0 && errno != EEXIST)
++              return -EINVAL;
++
++      buf = os_malloc(BUFSIZE);
++      if (!buf)
++              return -ENOMEM;
++
++      fname = os_malloc(len + 1);
++      if (!fname) {
++              ret = -ENOMEM;
++              goto free_buf;
++      }
++
++      os_snprintf(fname, len + 1, "%s/%s", path, AFCD_SOCK);
++      fname[len] = '\0';
++      os_strlcpy(addr.sun_path, fname, sizeof(addr.sun_path));
++
++      sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
++      if (sockfd < 0) {
++              wpa_printf(MSG_ERROR, "Failed creating socket");
++              ret = -errno;
++              goto unlink;
++      }
++
++      if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
++              wpa_printf(MSG_ERROR, "Failed to bind socket");
++              ret = -errno;
++              goto close;
++      }
++
++      if (listen(sockfd, 10) < 0) {
++              wpa_printf(MSG_ERROR, "Failed to listen on socket");
++              ret = -errno;
++              goto close;
++      }
++
++      FD_ZERO(&read_set);
++      while (!exiting) {
++              socklen_t addr_len = sizeof(addr);
++              struct sockaddr_in6 c_addr;
++              struct timeval timeout = {
++                      .tv_sec = 1,
++              };
++              struct curl_ctx ctx = {};
++              int fd;
++
++              FD_SET(sockfd, &read_set);
++              if (select(sockfd + 1, &read_set, NULL, NULL, &timeout) < 0) {
++                      if (errno != EINTR) {
++                              wpa_printf(MSG_ERROR,
++                                         "Select failed on socket");
++                              ret = -errno;
++                              break;
++                      }
++                      continue;
++              }
++
++              if (!FD_ISSET(sockfd, &read_set))
++                      continue;
++
++              fd = accept(sockfd, (struct sockaddr *)&c_addr,
++                          &addr_len);
++              if (fd < 0) {
++                      if (errno != EINTR) {
++                              wpa_printf(MSG_ERROR,
++                                         "Failed accepting connections");
++                              ret = -errno;
++                              break;
++                      }
++                      continue;
++              }
++
++              os_memset(buf, 0, BUFSIZE);
++              if (recv(fd, buf, BUFSIZE - 1, 0) <= 0) {
++                      close(fd);
++                      continue;
++              }
++
++              wpa_printf(MSG_DEBUG, "Received request: %s", buf);
++              if (!afcd_send_request(&ctx, buf)) {
++                      wpa_printf(MSG_DEBUG, "Received reply: %s", ctx.buf);
++                      send(fd, ctx.buf, ctx.buf_len, MSG_NOSIGNAL);
++                      free(ctx.buf);
++              }
++              close(fd);
++      }
++close:
++      close(sockfd);
++unlink:
++      unlink(fname);
++      os_free(fname);
++free_buf:
++      os_free(buf);
++
++      return ret;
++}
++
++
++int main(int argc, char **argv)
++{
++      bool daemonize = false;
++      char *pid_file = NULL;
++
++      if (os_program_init())
++              return -1;
++
++      for (;;) {
++              int c = getopt(argc, argv, "u:p:t:D:P:hdB");
++
++              if (c < 0)
++                      break;
++
++              switch (c) {
++              case 'h':
++                      usage();
++                      return 0;
++              case 'B':
++                      daemonize = true;
++                      break;
++              case 'D':
++                      path = optarg;
++                      break;
++              case 'P':
++                      os_free(pid_file);
++                      pid_file = os_rel2abs_path(optarg);
++                      break;
++              case 'u':
++                      url = optarg;
++                      break;
++              case 'p':
++                      port = atoi(optarg);
++                      break;
++              case 'd':
++                      if (wpa_debug_level > 0)
++                              wpa_debug_level--;
++                      break;
++              case 't':
++                      bearer_token = optarg;
++                      break;
++              default:
++                      usage();
++                      return -EINVAL;
++              }
++      }
++
++      if (!url) {
++              usage();
++              return -EINVAL;
++      }
++
++      if (daemonize && os_daemonize(pid_file)) {
++              wpa_printf(MSG_ERROR, "daemon: %s", strerror(errno));
++              return -EINVAL;
++      }
++
++      signal(SIGTERM, handle_term);
++      signal(SIGINT, handle_term);
++
++      return afcd_server_run();
++}