| From f25cbdd28ff46a7beedd07f575614da7c7d269d7 Mon Sep 17 00:00:00 2001 |
| From: Lorenzo Bianconi <lorenzo@kernel.org> |
| Date: Fri, 17 May 2024 11:50:25 +0200 |
| Subject: [PATCH 005/126] 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: Felix Fietkau <nbd@nbd.name> |
| Tested-by: Allen Ye <allen.ye@mediatek.com> |
| Tested-by: Krishna Chaitanya <chaitanya.mgit@gmail.com> |
| Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org> |
| --- |
| afc/.gitignore | 1 + |
| afc/Makefile | 31 +++++ |
| afc/afc-reply.txt | 219 +++++++++++++++++++++++++++++++++ |
| afc/afcd.c | 305 ++++++++++++++++++++++++++++++++++++++++++++++ |
| 4 files changed, 556 insertions(+) |
| create mode 100644 afc/.gitignore |
| create mode 100644 afc/Makefile |
| create mode 100644 afc/afc-reply.txt |
| create mode 100644 afc/afcd.c |
| |
| diff --git a/afc/.gitignore b/afc/.gitignore |
| new file mode 100644 |
| index 000000000..8d8cca905 |
| --- /dev/null |
| +++ b/afc/.gitignore |
| @@ -0,0 +1 @@ |
| +afcd |
| diff --git a/afc/Makefile b/afc/Makefile |
| new file mode 100644 |
| index 000000000..a83bd01db |
| --- /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 *~ |
| diff --git a/afc/afc-reply.txt b/afc/afc-reply.txt |
| new file mode 100644 |
| index 000000000..aaa4f8956 |
| --- /dev/null |
| +++ b/afc/afc-reply.txt |
| @@ -0,0 +1,219 @@ |
| +HTTP/1.1 200 OK |
| +Content-Type: application/json |
| +Content-Length: 4843 |
| + |
| +{ |
| + "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":"11235813", |
| + "response":{ |
| + "responseCode":0, |
| + "shortDescription":"Success" |
| + }, |
| + "rulesetId":"US_47_CFR_PART_15_SUBPART_E" |
| + } |
| + ], |
| + "version":"1.1" |
| +} |
| diff --git a/afc/afcd.c b/afc/afcd.c |
| new file mode 100644 |
| index 000000000..2b99940ae |
| --- /dev/null |
| +++ b/afc/afcd.c |
| @@ -0,0 +1,305 @@ |
| +/* |
| + * 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, *tmp; |
| + int ret = CURLE_FAILED_INIT; |
| + CURL *curl; |
| + |
| + wpa_printf(MSG_DEBUG, "Sending AFC request to %s", url); |
| + |
| + curl_global_init(CURL_GLOBAL_ALL); |
| + curl = curl_easy_init(); |
| + if (!curl) |
| + goto out_global_cleanup; |
| + |
| + headers = curl_slist_append(headers, "Accept: application/json"); |
| + if (!headers) |
| + goto out_easy_cleanup; |
| + |
| + tmp = curl_slist_append(headers, "Content-Type: application/json"); |
| + if (!tmp) |
| + goto out_slist_free_all; |
| + headers = tmp; |
| + |
| + tmp = curl_slist_append(headers, "charset: utf-8"); |
| + if (!tmp) |
| + goto out_slist_free_all; |
| + headers = tmp; |
| + |
| + 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)); |
| + |
| +out_slist_free_all: |
| + curl_slist_free_all(headers); |
| +out_easy_cleanup: |
| + curl_easy_cleanup(curl); |
| +out_global_cleanup: |
| + 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(); |
| +} |
| -- |
| 2.18.0 |
| |