mirror of
https://github.com/Telecominfraproject/wlan-ap.git
synced 2025-11-01 19:07:47 +00:00
576 lines
13 KiB
Diff
576 lines
13 KiB
Diff
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();
|
|
+}
|