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@xxxxxxxx> Tested-by: Allen Ye <allen.ye@xxxxxxxxxxxx> Tested-by: Krishna Chaitanya <chaitanya.mgit@xxxxxxxxx> Signed-off-by: Lorenzo Bianconi <lorenzo@xxxxxxxxxx> --- 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@xxxxxxxxxx> + * + * 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.45.0 _______________________________________________ Hostap mailing list Hostap@xxxxxxxxxxxxxxxxxxx http://lists.infradead.org/mailman/listinfo/hostap