--- /dev/null
+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();
++}