From patchwork Tue Apr 16 16:16:52 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lorenzo Bianconi X-Patchwork-Id: 1924303 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (2048-bit key; secure) header.d=lists.infradead.org header.i=@lists.infradead.org header.a=rsa-sha256 header.s=bombadil.20210309 header.b=2HDuYk5W; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=kernel.org header.i=@kernel.org header.a=rsa-sha256 header.s=k20201202 header.b=imexJQVS; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=none (no SPF record) smtp.mailfrom=lists.infradead.org (client-ip=2607:7c80:54:3::133; helo=bombadil.infradead.org; envelope-from=hostap-bounces+incoming=patchwork.ozlabs.org@lists.infradead.org; receiver=patchwork.ozlabs.org) Received: from bombadil.infradead.org (bombadil.infradead.org [IPv6:2607:7c80:54:3::133]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4VJq2H3BNFz1yZC for ; Wed, 17 Apr 2024 02:17:43 +1000 (AEST) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender: Content-Transfer-Encoding:Content-Type:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:Cc:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=HeDwIscUVC7OxI5WdI6HqET+AYE5HeG7vpKmrzjWE7o=; b=2HDuYk5W8o6sQx U2RfOyq0rADe5tyBlsrYEMUBIKufhRUGQcImgXX0URzUMmHZ53KqeoaJlMTTldYrmbDs8h3+iNiuR vWqt69YE/xiRMPt7fHmPba0xu2bIWLLh6iynRzE9gxhzLHpLaaLXWF3P6XSXqULyYLHGF03Yvyk/O J20yc0Vs2cOni0hWJsjfqkTvvKatAtKgrCoSALy5AN5POtbp99YW8SXs4T/+QdJojA5WKF/DV71Gu 12vg9EVBX67DptOQm2/c3UDldlaUAlYGJDU7rbHFRMDaSCe4NUBhe8RMSYwAfrcwfCwLiUgNXqDWR ymbNswT9fMDBpMkzhnhQ==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.97.1 #2 (Red Hat Linux)) id 1rwlUh-0000000Cy0S-1BDH; Tue, 16 Apr 2024 16:17:15 +0000 Received: from sin.source.kernel.org ([2604:1380:40e1:4800::1]) by bombadil.infradead.org with esmtps (Exim 4.97.1 #2 (Red Hat Linux)) id 1rwlUd-0000000CxyO-2H1g for hostap@lists.infradead.org; Tue, 16 Apr 2024 16:17:13 +0000 Received: from smtp.kernel.org (transwarp.subspace.kernel.org [100.75.92.58]) by sin.source.kernel.org (Postfix) with ESMTP id 46363CE0F8E; Tue, 16 Apr 2024 16:17:08 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id EBA07C2BD10; Tue, 16 Apr 2024 16:17:04 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1713284226; bh=tVzxnpYp/OUkffGXvM3BRR1xAXfkZKTiZLOYpYYllBE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=imexJQVSEMJPidMaiJmqiH+4TaiXkM6l6kkRKfKGu2Vq9JYIZ3mC8ze16Y6ml4cil 3gG6wIfPg3rEXIMM6dKbfYzCEjKkYYWPxc/36ku0IByz0/DaIQE61T0VmDwWmxyZJR 8oq64nB8V4d5mF1xiDWioU8JLbZ0vP2kWAFNOtaT6ARxQU1PXS0Z7gKGSZlhs8Xe3B uQSNqztEbbN3nTqqBKeYJDkWXFTNdrVy9Nw75DR3u5R7gFcZ9IB8Ci+kn/EjUaVHio H1DY/bnwQ1jV20LfWxTqEmKvoTTJPMHRPDSGg16NOySeud9eBk9P2ICSGIGAGRHK3G zOyVuELqbLEng== From: Lorenzo Bianconi To: hostap@lists.infradead.org Cc: j@w1.fi, lorenzo.bianconi83@gmail.com, ryder.lee@mediatek.com, evelyn.tsai@mediatek.com, nbd@nbd.name, allen.ye@mediatek.com, jesus.manzano@galgus.ai, chaitanya.mgit@gmail.com Subject: [PATCH v5 1/4] hostapd: afcd: add AFC daemon support Date: Tue, 16 Apr 2024 18:16:52 +0200 Message-ID: <0a67c48fcb2da589fe971d46f5380e189c8f8fe8.1713283884.git.lorenzo@kernel.org> X-Mailer: git-send-email 2.44.0 In-Reply-To: References: MIME-Version: 1.0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20240416_091712_040103_E1F6100E X-CRM114-Status: GOOD ( 21.98 ) X-Spam-Score: -4.3 (----) X-Spam-Report: Spam detection software, running on the system "bombadil.infradead.org", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: 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 an [...] Content analysis details: (-4.3 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- -2.3 RCVD_IN_DNSWL_MED RBL: Sender listed at https://www.dnswl.org/, medium trust [2604:1380:40e1:4800:0:0:0:1 listed in] [list.dnswl.org] -0.0 SPF_PASS SPF: sender matches SPF record 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -1.8 DKIMWL_WL_HIGH DKIMwl.org - High trust sender X-BeenThere: hostap@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "Hostap" Errors-To: hostap-bounces+incoming=patchwork.ozlabs.org@lists.infradead.org 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 Tested-by: Allen Ye Signed-off-by: Lorenzo Bianconi --- afc/.gitignore | 1 + afc/Makefile | 31 +++++ afc/afc-reply.txt | 219 +++++++++++++++++++++++++++++++++ afc/afcd.c | 306 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 557 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..581b78fa2 --- /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":"11235814", + "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..572933d09 --- /dev/null +++ b/afc/afcd.c @@ -0,0 +1,306 @@ +/* + * Automated Frequency Coordination Daemon + * Copyright (c) 2024, Lorenzo Bianconi + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include +#include +#include + +#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; + 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"); + if (!headers) + return -ENOMEM; + + tmp = curl_slist_append(headers, "Content-Type: application/json"); + if (!tmp) { + curl_slist_free_all(headers); + return -ENOMEM; + } + headers = tmp; + + tmp = curl_slist_append(headers, "charset: utf-8"); + if (!tmp) { + curl_slist_free_all(headers); + return -ENOMEM; + } + 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)); + + curl_slist_free_all(headers); + 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 [-p][-t][-D][-P][-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(); +} From patchwork Tue Apr 16 16:16:53 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lorenzo Bianconi X-Patchwork-Id: 1924302 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (2048-bit key; secure) header.d=lists.infradead.org header.i=@lists.infradead.org header.a=rsa-sha256 header.s=bombadil.20210309 header.b=zx/T4xYs; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=kernel.org header.i=@kernel.org header.a=rsa-sha256 header.s=k20201202 header.b=eBaUPwnQ; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=none (no SPF record) smtp.mailfrom=lists.infradead.org (client-ip=2607:7c80:54:3::133; helo=bombadil.infradead.org; envelope-from=hostap-bounces+incoming=patchwork.ozlabs.org@lists.infradead.org; receiver=patchwork.ozlabs.org) Received: from bombadil.infradead.org (bombadil.infradead.org [IPv6:2607:7c80:54:3::133]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4VJq290QSwz1yZh for ; Wed, 17 Apr 2024 02:17:36 +1000 (AEST) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender: Content-Transfer-Encoding:Content-Type:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:Cc:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=OsRaVqJJPxzbMyEE+PHaml1rAlKe/m8+KsiAgJzFiYk=; b=zx/T4xYsN3Cyfy CEOe2dT1bx+8bR4LfNPJ7LZ6cLZJcESzFHHSTBWJdvvcNvXUCooHKdvCkVedHbdAuEoHdsgprm9Sr oe3egfwzBFubqN8jkXktN0tRreNf7XondiSJA6ea/akWB7CWqwvajUhCYx2Mk+5dniL3uQ+xG8jcL mUj3PjsanJIH9ju170BQBGPdG9LsWQhx/MSkJnou/YARlxQOxCL16Thvfpw1/rIzX++UlvjpVQFsO ZXrphqqXZ20Vlku3/lk4otdfviaVaL7bLXWxJjULw38jgUGTGN6iYdlGw5/5jaUS63puA0XkleDci RssgDv8u6u9lK3Hynurg==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.97.1 #2 (Red Hat Linux)) id 1rwlUe-0000000Cxza-1qQd; Tue, 16 Apr 2024 16:17:12 +0000 Received: from dfw.source.kernel.org ([139.178.84.217]) by bombadil.infradead.org with esmtps (Exim 4.97.1 #2 (Red Hat Linux)) id 1rwlUc-0000000CxyA-1tys for hostap@lists.infradead.org; Tue, 16 Apr 2024 16:17:11 +0000 Received: from smtp.kernel.org (transwarp.subspace.kernel.org [100.75.92.58]) by dfw.source.kernel.org (Postfix) with ESMTP id DB97261230; Tue, 16 Apr 2024 16:17:09 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id 44F4EC113CE; Tue, 16 Apr 2024 16:17:07 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1713284229; bh=U5dZ1CxSEWVfuTwmbs9rMqAEZ1tvyeKKa+TMYuImun8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=eBaUPwnQSB1PgPboFdTLR2BHwnOzTUG4ut6QO+y+Avd+gsYE4LEsELHNPI4M9jIMJ mt5Y+5IYg5o9O7Awe4XG2DoqrNqHvRnnHcPueDU+t5+WoNUdLZEhaVXYvAIDO2m3wu z1ir9fSu7XlRonf+lNtwSNO+fqDQiunRXQXEcV55Y2KgD0Jo/qughU0k9GZSa/5YCg vHp+KqogvdPvqHIp1AvzAVV73KYQmSmwYnnXa1J49Pw91ls90n9FgczkpQc+ScAHQG cnRu6aqR8T9RU2xCLEyo/6X3z3oVGYMmdQZtT3mxbnFqg1+HUk+9e60mddury8e8Gb fNrNge0X2TgSA== From: Lorenzo Bianconi To: hostap@lists.infradead.org Cc: j@w1.fi, lorenzo.bianconi83@gmail.com, ryder.lee@mediatek.com, evelyn.tsai@mediatek.com, nbd@nbd.name, allen.ye@mediatek.com, jesus.manzano@galgus.ai, chaitanya.mgit@gmail.com Subject: [PATCH v5 2/4] hostapd: export hostapd_is_usable_chans utility routine Date: Tue, 16 Apr 2024 18:16:53 +0200 Message-ID: <938de434185c629b1b0d8b23fec621229d68d8f8.1713283884.git.lorenzo@kernel.org> X-Mailer: git-send-email 2.44.0 In-Reply-To: References: MIME-Version: 1.0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20240416_091710_562657_DAA8CF48 X-CRM114-Status: UNSURE ( 9.85 ) X-CRM114-Notice: Please train this message. X-Spam-Score: -7.0 (-------) X-Spam-Report: Spam detection software, running on the system "bombadil.infradead.org", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: This is a preliminary patch to introduce AFC support. Tested-by: Felix Fietkau Tested-by: Allen Ye Signed-off-by: Lorenzo Bianconi --- src/ap/hw_features.c | 2 +- src/ap/hw_features.h | 6 ++++++ [...] Content analysis details: (-7.0 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- -5.0 RCVD_IN_DNSWL_HI RBL: Sender listed at https://www.dnswl.org/, high trust [139.178.84.217 listed in list.dnswl.org] -0.0 SPF_PASS SPF: sender matches SPF record 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -1.8 DKIMWL_WL_HIGH DKIMwl.org - High trust sender X-BeenThere: hostap@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "Hostap" Errors-To: hostap-bounces+incoming=patchwork.ozlabs.org@lists.infradead.org This is a preliminary patch to introduce AFC support. Tested-by: Felix Fietkau Tested-by: Allen Ye Signed-off-by: Lorenzo Bianconi --- src/ap/hw_features.c | 2 +- src/ap/hw_features.h | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ap/hw_features.c b/src/ap/hw_features.c index c4556603d..85e67080d 100644 --- a/src/ap/hw_features.c +++ b/src/ap/hw_features.c @@ -1008,7 +1008,7 @@ static bool hostapd_is_usable_punct_bitmap(struct hostapd_iface *iface) * 0 = not usable * -1 = not currently usable due to 6 GHz NO-IR */ -static int hostapd_is_usable_chans(struct hostapd_iface *iface) +int hostapd_is_usable_chans(struct hostapd_iface *iface) { int secondary_freq; struct hostapd_channel_data *pri_chan; diff --git a/src/ap/hw_features.h b/src/ap/hw_features.h index c682c6d20..eeffb1abd 100644 --- a/src/ap/hw_features.h +++ b/src/ap/hw_features.h @@ -30,6 +30,7 @@ void hostapd_stop_setup_timers(struct hostapd_iface *iface); int hostapd_hw_skip_mode(struct hostapd_iface *iface, struct hostapd_hw_modes *mode); int hostapd_determine_mode(struct hostapd_iface *iface); +int hostapd_is_usable_chans(struct hostapd_iface *iface); #else /* NEED_AP_MLME */ static inline void hostapd_free_hw_features(struct hostapd_hw_modes *hw_features, @@ -103,6 +104,11 @@ static inline int hostapd_determine_mode(struct hostapd_iface *iface) return 0; } +static inline int hostapd_is_usable_chans(struct hostapd_iface *iface) +{ + return 1; +} + #endif /* NEED_AP_MLME */ #endif /* HW_FEATURES_H */ From patchwork Tue Apr 16 16:16:54 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lorenzo Bianconi X-Patchwork-Id: 1924305 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (2048-bit key; secure) header.d=lists.infradead.org header.i=@lists.infradead.org header.a=rsa-sha256 header.s=bombadil.20210309 header.b=X3KCK1im; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=kernel.org header.i=@kernel.org header.a=rsa-sha256 header.s=k20201202 header.b=rRtk9+wr; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=none (no SPF record) smtp.mailfrom=lists.infradead.org (client-ip=2607:7c80:54:3::133; helo=bombadil.infradead.org; envelope-from=hostap-bounces+incoming=patchwork.ozlabs.org@lists.infradead.org; receiver=patchwork.ozlabs.org) Received: from bombadil.infradead.org (bombadil.infradead.org [IPv6:2607:7c80:54:3::133]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4VJq2Y1fJ3z1yZC for ; Wed, 17 Apr 2024 02:17:57 +1000 (AEST) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender: Content-Transfer-Encoding:Content-Type:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:Cc:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=kZxdewY+WOTEf7Tr/n9UaiEsPrY3RKvcHH8KBf+fzJI=; b=X3KCK1im6arQWh T1Z7E/maJJFFtIjtOonEEaJNAPgOQrmptaH4XFiw3O5BZj4lCsX8bmasCQp7IV18ZbnAU+F/I/1m3 HGgeO/a9bnsxdZ0wUOPnttGSb9WJL+SNXlDuHBNAlEY87Rn8/1hFPY1Ea3sr1VhIY5Hb8BLzIWGSR e9XdYTT47VTb6Z0eurFcqY6xxOXpwlBMQDtjyUapeasCus3g/XKc5fPwqxFuIeLZQKA3dkVehX0Kh xi4Tg8LLh942rfuRNOXWIDeOO8AXWV3NOfjf2ndaYktMN/Dy827/80/UIxJNp4pdRH+YiiJxfGBsy F2UrBwwaBLS+U+XPTYNQ==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.97.1 #2 (Red Hat Linux)) id 1rwlUo-0000000Cy39-4A7c; Tue, 16 Apr 2024 16:17:23 +0000 Received: from sin.source.kernel.org ([2604:1380:40e1:4800::1]) by bombadil.infradead.org with esmtps (Exim 4.97.1 #2 (Red Hat Linux)) id 1rwlUi-0000000Cy0P-1COv for hostap@lists.infradead.org; Tue, 16 Apr 2024 16:17:20 +0000 Received: from smtp.kernel.org (transwarp.subspace.kernel.org [100.75.92.58]) by sin.source.kernel.org (Postfix) with ESMTP id 03167CE0F83; Tue, 16 Apr 2024 16:17:13 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id 939DEC2BD10; Tue, 16 Apr 2024 16:17:09 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1713284231; bh=PtsDXYLDQdxHG6JW6cKz93YKtssiayw4eWs/gXgrTZQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=rRtk9+wr4ah9p8Le/COYvec/yuHJ2AT1NGdHFfR289460laaM3M/mqEdt4UlfLP5/ JxtUBuMN8cRt6XvTAcUH+ecX2/77vMXs8YI4LSNVOSO+mcKI8FpeyCpGy8ZoDgwIkV U00rDHDQ/egETIfrRtPUF3sjB9XL3yt0yEjo397N5gdBwCbr2EZm59rXqcBas+O6g0 PufX1vkMOXBI6KGCB3/C9cdEFQnToXezS/Cf+QcFzs9u6/poFjfOdKsbD4QhFQmAPJ yWin8ru+USuA/qLowaaonP8b86kXq7ZsHIvnlxOFaQ4cefZZMMZg1IH3++Kk+OqYFU llVPYg5lY6+5g== From: Lorenzo Bianconi To: hostap@lists.infradead.org Cc: j@w1.fi, lorenzo.bianconi83@gmail.com, ryder.lee@mediatek.com, evelyn.tsai@mediatek.com, nbd@nbd.name, allen.ye@mediatek.com, jesus.manzano@galgus.ai, chaitanya.mgit@gmail.com Subject: [PATCH v5 3/4] hostapd: ap: add AFC client support Date: Tue, 16 Apr 2024 18:16:54 +0200 Message-ID: X-Mailer: git-send-email 2.44.0 In-Reply-To: References: MIME-Version: 1.0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20240416_091716_958441_6414EBBF X-CRM114-Status: GOOD ( 22.62 ) X-Spam-Score: -4.3 (----) X-Spam-Report: Spam detection software, running on the system "bombadil.infradead.org", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: Introduce Automated Frequency Coordination (AFC) support for UNII-5 and UNII-7 6GHz bands. AFC client will connect to AFCD providing AP related parameter for AFC coordinator (e.g. geolocation, support [...] Content analysis details: (-4.3 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- -2.3 RCVD_IN_DNSWL_MED RBL: Sender listed at https://www.dnswl.org/, medium trust [2604:1380:40e1:4800:0:0:0:1 listed in] [list.dnswl.org] -0.0 SPF_PASS SPF: sender matches SPF record 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -1.8 DKIMWL_WL_HIGH DKIMwl.org - High trust sender X-BeenThere: hostap@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "Hostap" Errors-To: hostap-bounces+incoming=patchwork.ozlabs.org@lists.infradead.org Introduce Automated Frequency Coordination (AFC) support for UNII-5 and UNII-7 6GHz bands. AFC client will connect to AFCD providing AP related parameter for AFC coordinator (e.g. geolocation, supported frequencies, ..). 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. AFC hostapd client is tested with AFC DUT Test Harness [0]. [0] https://github.com/Wi-FiTestSuite/AFC-DUT/tree/main Tested-by: Felix Fietkau Tested-by: Allen Ye Signed-off-by: Lorenzo Bianconi --- hostapd/Makefile | 8 + hostapd/config_file.c | 261 +++++++++++ hostapd/defconfig | 3 + hostapd/hostapd.conf | 42 ++ src/ap/afc.c | 996 ++++++++++++++++++++++++++++++++++++++++++ src/ap/ap_config.c | 16 + src/ap/ap_config.h | 47 ++ src/ap/hostapd.c | 16 + src/ap/hostapd.h | 45 ++ src/ap/hw_features.c | 2 + 10 files changed, 1436 insertions(+) create mode 100644 src/ap/afc.c diff --git a/hostapd/Makefile b/hostapd/Makefile index ca4439234..78171025e 100644 --- a/hostapd/Makefile +++ b/hostapd/Makefile @@ -104,6 +104,14 @@ CFLAGS += -DCONFIG_TAXONOMY OBJS += ../src/ap/taxonomy.o endif +ifdef CONFIG_IEEE80211AX +ifdef CONFIG_AFC +CFLAGS += -DCONFIG_AFC +OBJS += ../src/ap/afc.o +LIBS += -ljson-c +endif +endif + ifdef CONFIG_MODULE_TESTS CFLAGS += -DCONFIG_MODULE_TESTS OBJS += hapd_module_tests.o diff --git a/hostapd/config_file.c b/hostapd/config_file.c index 9a8f34e0d..1e9ed49c1 100644 --- a/hostapd/config_file.c +++ b/hostapd/config_file.c @@ -1281,6 +1281,190 @@ static int hostapd_parse_he_srg_bitmap(u8 *bitmap, char *val) return 0; } + +#ifdef CONFIG_AFC +static int hostapd_afc_parse_cert_ids(struct hostapd_config *conf, char *pos) +{ + struct cert_id *c = NULL; + int i, count = 0; + + while (pos && pos[0]) { + char *p; + + c = os_realloc_array(c, count + 1, sizeof(*c)); + if (!c) + return -ENOMEM; + + i = count; + count++; + + p = os_strchr(pos, ':'); + if (!p) + goto error; + + *p++ = '\0'; + if (!p || !p[0]) + goto error; + + c[i].rulset = os_malloc(os_strlen(pos) + 1); + if (!c[i].rulset) + goto error; + + os_strlcpy(c[i].rulset, pos, os_strlen(pos) + 1); + pos = p; + p = os_strchr(pos, ','); + if (p) + *p++ = '\0'; + + c[i].id = os_malloc(os_strlen(pos) + 1); + if (!c[i].id) + goto error; + + os_strlcpy(c[i].id, pos, os_strlen(pos) + 1); + pos = p; + } + + conf->afc.n_cert_ids = count; + conf->afc.cert_ids = c; + + return 0; + +error: + for (i = 0; i < count; i++) { + os_free(c[i].rulset); + os_free(c[i].id); + } + os_free(c); + + return -ENOMEM; +} + + +static int hostapd_afc_parse_position_data(struct afc_linear_polygon **data, + unsigned int *n_linear_polygon_data, + char *pos) +{ + struct afc_linear_polygon *d = NULL; + int i, count = 0; + + while (pos && pos[0]) { + char *p, *end; + + d = os_realloc_array(d, count + 1, sizeof(*d)); + if (!d) + return -ENOMEM; + + i = count; + count++; + + p = os_strchr(pos, ':'); + if (!p) + goto error; + + *p++ = '\0'; + if (!p || !p[0]) + goto error; + + d[i].longitude = strtod(pos, &end); + if (*end) + goto error; + + pos = p; + p = os_strchr(pos, ','); + if (p) + *p++ = '\0'; + + d[i].latitude = strtod(pos, &end); + if (*end) + goto error; + + pos = p; + } + + *n_linear_polygon_data = count; + *data = d; + + return 0; + +error: + os_free(d); + return -ENOMEM; +} + + +static int hostapd_afc_parse_freq_range(struct hostapd_config *conf, char *pos) +{ + struct afc_freq_range *f = NULL; + int i, count = 0; + + while (pos && pos[0]) { + char *p; + + f = os_realloc_array(f, count + 1, sizeof(*f)); + if (!f) + return -ENOMEM; + + i = count; + count++; + + p = os_strchr(pos, ':'); + if (!p) + goto error; + + *p++ = '\0'; + if (!p || !p[0]) + goto error; + + f[i].low_freq = atoi(pos); + pos = p; + p = os_strchr(pos, ','); + if (p) + *p++ = '\0'; + + f[i].high_freq = atoi(pos); + pos = p; + } + + conf->afc.n_freq_range = count; + conf->afc.freq_range = f; + + return 0; + +error: + os_free(f); + return -ENOMEM; +} + + +static int hostapd_afc_parse_op_class(struct hostapd_config *conf, char *pos) +{ + unsigned int *oc = NULL; + int i, count = 0; + + while (pos && pos[0]) { + char *p; + + oc = os_realloc_array(oc, count + 1, sizeof(*oc)); + if (!oc) + return -ENOMEM; + + i = count; + count++; + + p = os_strchr(pos, ','); + if (p) + *p++ = '\0'; + + oc[i] = atoi(pos); + pos = p; + } + + conf->afc.n_op_class = count; + conf->afc.op_class = oc; + + return 0; +} +#endif /* CONFIG_AFC */ #endif /* CONFIG_IEEE80211AX */ @@ -3862,6 +4046,83 @@ static int hostapd_config_fill(struct hostapd_config *conf, return 1; } bss->unsol_bcast_probe_resp_interval = val; +#ifdef CONFIG_AFC + } else if (os_strcmp(buf, "afcd_sock") == 0) { + conf->afc.socket = os_malloc(os_strlen(pos) + 1); + if (!conf->afc.socket) + return 1; + + os_strlcpy(conf->afc.socket, pos, os_strlen(pos) + 1); + } else if (os_strcmp(buf, "afc_request_version") == 0) { + conf->afc.request.version = os_malloc(os_strlen(pos) + 1); + if (!conf->afc.request.version) + return 1; + + os_strlcpy(conf->afc.request.version, pos, os_strlen(pos) + 1); + } else if (os_strcmp(buf, "afc_request_id") == 0) { + conf->afc.request.id = os_malloc(os_strlen(pos) + 1); + if (!conf->afc.request.id) + return 1; + + os_strlcpy(conf->afc.request.id, pos, os_strlen(pos) + 1); + } else if (os_strcmp(buf, "afc_serial_number") == 0) { + conf->afc.request.sn = os_malloc(os_strlen(pos) + 1); + if (!conf->afc.request.sn) + return 1; + + os_strlcpy(conf->afc.request.sn, pos, os_strlen(pos) + 1); + } else if (os_strcmp(buf, "afc_cert_ids") == 0) { + if (hostapd_afc_parse_cert_ids(conf, pos)) + return 1; + } else if (os_strcmp(buf, "afc_location_type") == 0) { + conf->afc.location.type = atoi(pos); + if (conf->afc.location.type != ELLIPSE && + conf->afc.location.type != LINEAR_POLYGON && + conf->afc.location.type != RADIAL_POLYGON) + return 1; + } else if (os_strcmp(buf, "afc_linear_polygon") == 0) { + if (hostapd_afc_parse_position_data( + &conf->afc.location.linear_polygon_data, + &conf->afc.location.n_linear_polygon_data, + pos)) + return 1; + } else if (os_strcmp(buf, "afc_radial_polygon") == 0) { + if (hostapd_afc_parse_position_data( + (struct afc_linear_polygon **) + &conf->afc.location.radial_polygon_data, + &conf->afc.location.n_radial_polygon_data, + pos)) + return 1; + } else if (os_strcmp(buf, "afc_major_axis") == 0) { + conf->afc.location.major_axis = atoi(pos); + } else if (os_strcmp(buf, "afc_minor_axis") == 0) { + conf->afc.location.minor_axis = atoi(pos); + } else if (os_strcmp(buf, "afc_orientation") == 0) { + conf->afc.location.orientation = atoi(pos); + } else if (os_strcmp(buf, "afc_height") == 0) { + char *end; + + conf->afc.location.height = strtod(pos, &end); + if (*end) + return 1; + } else if (os_strcmp(buf, "afc_height_type") == 0) { + conf->afc.location.height_type = os_malloc(os_strlen(pos) + 1); + if (!conf->afc.location.height_type) + return 1; + + os_strlcpy(conf->afc.location.height_type, pos, + os_strlen(pos) + 1); + } else if (os_strcmp(buf, "afc_vertical_tolerance") == 0) { + conf->afc.location.vertical_tolerance = atoi(pos); + } else if (os_strcmp(buf, "afc_min_power") == 0) { + conf->afc.min_power = atoi(pos); + } else if (os_strcmp(buf, "afc_freq_range") == 0) { + if (hostapd_afc_parse_freq_range(conf, pos)) + return 1; + } else if (os_strcmp(buf, "afc_op_class") == 0) { + if (hostapd_afc_parse_op_class(conf, pos)) + return 1; +#endif /* CONFIG_AFC */ } else if (os_strcmp(buf, "mbssid") == 0) { int mbssid = atoi(pos); if (mbssid < 0 || mbssid > ENHANCED_MBSSID_ENABLED) { diff --git a/hostapd/defconfig b/hostapd/defconfig index 550db697b..66bf894eb 100644 --- a/hostapd/defconfig +++ b/hostapd/defconfig @@ -425,3 +425,6 @@ CONFIG_DPP2=y # Wi-Fi Aware unsynchronized service discovery (NAN USD) #CONFIG_NAN_USD=y + +# Enable Automated Frequency Coordination for 6GHz outdoor +#CONFIG_AFC=y diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf index 51ca05e25..50e0384fd 100644 --- a/hostapd/hostapd.conf +++ b/hostapd/hostapd.conf @@ -1005,6 +1005,48 @@ wmm_ac_vo_acm=0 # Valid range: 0..20 TUs; default is 0 (disabled) #unsol_bcast_probe_resp_interval=0 +##### Automated Frequency Coordination for 6GHz UNII-5 and UNII-7 bands ####### + +# AFC daemon connection socket +#afcd_sock=/var/run/afcd.sock + +# AFC request identification parameters +#afc_request_version=1.1 +#afc_request_id=11235813 +#afc_serial_number=abcdefg +#afc_cert_ids=US_47_CFR_PART_15_SUBPART_E:CID000 +# +# AFC location type: +# 0 = ellipse +# 1 = linear polygon +# 2 = radial polygon +#afc_location_type=0 +# +# AFC ellipse or linear polygon coordinations +#afc_linear_polygon=-122.984157:37.425056 +# +# AFC radial polygon coordinations +#afc_radial_polygon=118.8:92.76,76.44:87.456,98.56:123.33 +# +# AFC ellipse major/minor axis and orientation +#afc_major_axis=100 +#afc_minor_axis=50 +#afc_orientation=70 +# +# AFC device elevation parameters +#afc_height=3.0 +#afc_height_type=AGL +#afc_vertical_tolerance=7 +# +# AFC minimum desired TX power (dbm) +#afc_min_power=24 +# +# AFC request frequency ranges +#afc_freq_range=5925:6425,6525:6875 +# +# AFC request operation classes +#afc_op_class=131,132,133,134,136 + ##### IEEE 802.11be related configuration ##################################### #ieee80211be: Whether IEEE 802.11be (EHT) is enabled diff --git a/src/ap/afc.c b/src/ap/afc.c new file mode 100644 index 000000000..21e1e17cb --- /dev/null +++ b/src/ap/afc.c @@ -0,0 +1,996 @@ +/* + * Automated Frequency Coordination + * Copyright (c) 2024, Lorenzo Bianconi + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include +#include +#include + +#include "utils/includes.h" +#include "utils/common.h" +#include "utils/eloop.h" +#include "hostapd.h" +#include "acs.h" +#include "hw_features.h" + +#define HOSTAPD_AFC_RETRY_TIMEOUT 180 +#define HOSTAPD_AFC_TIMEOUT 86400 /* 24h */ +#define HOSTAPD_AFC_BUFSIZE 8192 + +static void hostapd_afc_timeout_handler(void *eloop_ctx, void *timeout_ctx); + + +static struct json_object * +hostapd_afc_build_location_request(struct hostapd_iface *iface) +{ + struct json_object *location_obj, *center_obj, *ellipse_obj; + struct json_object *elevation_obj, *str_obj; + struct hostapd_config *iconf = iface->conf; + bool is_ap_indoor = he_reg_is_indoor(iconf->he_6ghz_reg_pwr_type); + + location_obj = json_object_new_object(); + if (!location_obj) + return NULL; + + if (iconf->afc.location.type != LINEAR_POLYGON) { + struct afc_linear_polygon *lp = + &iconf->afc.location.linear_polygon_data[0]; + + if (!lp) + goto error; + + ellipse_obj = json_object_new_object(); + if (!ellipse_obj) + goto error; + + center_obj = json_object_new_object(); + if (!center_obj) + goto error; + + json_object_object_add(ellipse_obj, "center", center_obj); + + str_obj = json_object_new_double(lp->longitude); + if (!str_obj) + goto error; + + json_object_object_add(center_obj, "longitude", str_obj); + str_obj = json_object_new_double(lp->latitude); + if (!str_obj) + goto error; + + json_object_object_add(center_obj, "latitude", str_obj); + } + + switch (iconf->afc.location.type) { + case LINEAR_POLYGON: { + struct json_object *outer_boundary_obj; + int i; + + outer_boundary_obj = json_object_new_object(); + if (!outer_boundary_obj) + goto error; + + json_object_object_add(location_obj, "linearPolygon", + outer_boundary_obj); + ellipse_obj = json_object_new_array(); + if (!ellipse_obj) + goto error; + + json_object_object_add(outer_boundary_obj, "outerBoundary", + ellipse_obj); + for (i = 0; + i < iconf->afc.location.n_linear_polygon_data; i++) { + struct afc_linear_polygon *lp = + &iconf->afc.location.linear_polygon_data[i]; + + center_obj = json_object_new_object(); + if (!center_obj) + goto error; + + json_object_array_add(ellipse_obj, center_obj); + str_obj = json_object_new_double(lp->longitude); + if (!str_obj) + goto error; + + json_object_object_add(center_obj, "longitude", + str_obj); + str_obj = json_object_new_double(lp->latitude); + if (!str_obj) + goto error; + + json_object_object_add(center_obj, "latitude", + str_obj); + } + break; + } + case RADIAL_POLYGON: { + struct json_object *outer_boundary_obj; + int i; + + json_object_object_add(location_obj, "radialPolygon", + ellipse_obj); + + outer_boundary_obj = json_object_new_array(); + if (!outer_boundary_obj) + goto error; + + json_object_object_add(ellipse_obj, "outerBoundary", + outer_boundary_obj); + for (i = 0; + i < iconf->afc.location.n_radial_polygon_data; i++) { + struct afc_radial_polygon *rp = + &iconf->afc.location.radial_polygon_data[i]; + struct json_object *angle_obj; + + angle_obj = json_object_new_object(); + if (!angle_obj) + goto error; + + json_object_array_add(outer_boundary_obj, angle_obj); + + str_obj = json_object_new_double(rp->angle); + if (!str_obj) + goto error; + + json_object_object_add(angle_obj, "angle", str_obj); + str_obj = json_object_new_double(rp->length); + if (!str_obj) + goto error; + + json_object_object_add(angle_obj, "length", str_obj); + } + break; + } + case ELLIPSE: + default: + json_object_object_add(location_obj, "ellipse", ellipse_obj); + + str_obj = json_object_new_int(iconf->afc.location.major_axis); + if (!str_obj) + goto error; + + json_object_object_add(ellipse_obj, "majorAxis", str_obj); + str_obj = json_object_new_int(iconf->afc.location.minor_axis); + if (!str_obj) + goto error; + + json_object_object_add(ellipse_obj, "minorAxis", str_obj); + str_obj = json_object_new_int(iconf->afc.location.orientation); + if (!str_obj) + goto error; + + json_object_object_add(ellipse_obj, "orientation", str_obj); + break; + } + + elevation_obj = json_object_new_object(); + if (!elevation_obj) + goto error; + + json_object_object_add(location_obj, "elevation", + elevation_obj); + str_obj = json_object_new_double(iconf->afc.location.height); + if (!str_obj) + goto error; + + json_object_object_add(elevation_obj, "height", str_obj); + if (iconf->afc.location.height_type) { + str_obj = json_object_new_string(iconf->afc.location.height_type); + if (!str_obj) + goto error; + + json_object_object_add(elevation_obj, "heightType", str_obj); + } + + str_obj = json_object_new_int(iconf->afc.location.vertical_tolerance); + if (!str_obj) + goto error; + + json_object_object_add(elevation_obj, "verticalUncertainty", + str_obj); + str_obj = json_object_new_int(is_ap_indoor); + if (!str_obj) + goto error; + + json_object_object_add(location_obj, "indoorDeployment", str_obj); + + return location_obj; + +error: + json_object_put(location_obj); + return NULL; +} + + +static struct json_object * hostapd_afc_get_opclass_chan_list(u8 op_class) +{ + struct json_object *chan_list_obj, *str_obj; + const struct oper_class_map *oper_class; + int chan_offset, chan; + + oper_class = get_oper_class(NULL, op_class); + if (!oper_class) + return NULL; + + chan_list_obj = json_object_new_array(); + if (!chan_list_obj) + return NULL; + + switch (op_class) { + case 132: /* 40MHz */ + chan_offset = 2; + break; + case 133: /* 80MHz */ + chan_offset = 6; + break; + case 134: /* 160MHz */ + chan_offset = 14; + break; + default: + chan_offset = 0; + break; + } + + for (chan = oper_class->min_chan; chan <= oper_class->max_chan; + chan += oper_class->inc) { + str_obj = json_object_new_int(chan + chan_offset); + if (!str_obj) { + json_object_put(chan_list_obj); + return NULL; + } + json_object_array_add(chan_list_obj, str_obj); + } + + return chan_list_obj; +} + + +static struct json_object * +hostapd_afc_build_req_chan_list(struct hostapd_iface *iface) +{ + struct json_object *op_class_list_obj, *str_obj; + struct hostapd_config *iconf = iface->conf; + int i; + + op_class_list_obj = json_object_new_array(); + if (!op_class_list_obj) + return NULL; + + for (i = 0; i < iconf->afc.n_op_class; i++) { + struct json_object *op_class_obj, *chan_list_obj; + u8 op_class = iconf->afc.op_class[i]; + + if (!is_6ghz_op_class(op_class)) + continue; + + op_class_obj = json_object_new_object(); + if (!op_class_obj) + goto error; + + json_object_array_add(op_class_list_obj, op_class_obj); + str_obj = json_object_new_int(op_class); + if (!str_obj) + goto error; + + json_object_object_add(op_class_obj, "globalOperatingClass", + str_obj); + + chan_list_obj = hostapd_afc_get_opclass_chan_list(op_class); + if (!chan_list_obj) + goto error; + + json_object_object_add(op_class_obj, "channelCfi", + chan_list_obj); + } + + return op_class_list_obj; + +error: + json_object_put(op_class_list_obj); + return NULL; +} + + +static struct json_object * +hostapd_afc_build_request(struct hostapd_iface *iface) +{ + struct json_object *l1_obj, *l2_obj, *la1_obj, *la2_obj; + struct json_object *s2_obj, *str_obj, *location_obj; + struct hostapd_config *iconf = iface->conf; + struct json_object *op_class_list_obj; + int i; + + l1_obj = json_object_new_object(); + if (!l1_obj) + return NULL; + + if (iconf->afc.request.version) { + str_obj = json_object_new_string(iconf->afc.request.version); + if (!str_obj) + goto error; + + json_object_object_add(l1_obj, "version", str_obj); + } + + la1_obj = json_object_new_array(); + if (!la1_obj) + goto error; + + json_object_object_add(l1_obj, "availableSpectrumInquiryRequests", + la1_obj); + l2_obj = json_object_new_object(); + if (!l2_obj) + goto error; + + json_object_array_add(la1_obj, l2_obj); + if (iconf->afc.request.id) { + str_obj = json_object_new_string(iconf->afc.request.id); + if (!str_obj) + goto error; + + json_object_object_add(l2_obj, "requestId", str_obj); + } + + s2_obj = json_object_new_object(); + if (!s2_obj) + goto error; + + json_object_object_add(l2_obj, "deviceDescriptor", s2_obj); + if (iconf->afc.request.sn) { + str_obj = json_object_new_string(iconf->afc.request.sn); + if (!str_obj) + goto error; + + json_object_object_add(s2_obj, "serialNumber", str_obj); + } + + la2_obj = json_object_new_array(); + if (!la2_obj) + goto error; + + json_object_object_add(s2_obj, "certificationId", la2_obj); + for (i = 0; i < iconf->afc.n_cert_ids; i++) { + struct json_object *obj; + + obj = json_object_new_object(); + if (!obj) + goto error; + + json_object_array_add(la2_obj, obj); + str_obj = + json_object_new_string(iconf->afc.cert_ids[i].rulset); + if (!str_obj) + goto error; + + json_object_object_add(obj, "rulesetId", str_obj); + str_obj = json_object_new_string(iconf->afc.cert_ids[i].id); + if (!str_obj) + goto error; + + json_object_object_add(obj, "id", str_obj); + } + + location_obj = hostapd_afc_build_location_request(iface); + if (!location_obj) + goto error; + + json_object_object_add(l2_obj, "location", location_obj); + str_obj = json_object_new_int(iconf->afc.min_power); + if (!str_obj) + goto error; + + json_object_object_add(l2_obj, "minDesiredPower", str_obj); + + if (iconf->afc.n_freq_range) { + struct json_object *freq_obj; + + freq_obj = json_object_new_array(); + if (!freq_obj) + goto error; + + json_object_object_add(l2_obj, "inquiredFrequencyRange", + freq_obj); + for (i = 0; i < iconf->afc.n_freq_range; i++) { + struct afc_freq_range *fr = &iconf->afc.freq_range[i]; + struct json_object *obj; + + obj = json_object_new_object(); + if (!obj) + goto error; + + json_object_array_add(freq_obj, obj); + str_obj = json_object_new_int(fr->low_freq); + if (!str_obj) + goto error; + + json_object_object_add(obj, "lowFrequency", str_obj); + str_obj = json_object_new_int(fr->high_freq); + if (!str_obj) + goto error; + + json_object_object_add(obj, "highFrequency", str_obj); + } + } + + op_class_list_obj = hostapd_afc_build_req_chan_list(iface); + if (!op_class_list_obj) + goto error; + + json_object_object_add(l2_obj, "inquiredChannels", op_class_list_obj); + + wpa_printf(MSG_DEBUG, "Pending AFC request: %s", + json_object_get_string(l1_obj)); + + return l1_obj; + +error: + json_object_put(l1_obj); + + return NULL; +} + + +static int +hostad_afc_parse_available_freq_info(struct hostapd_iface *iface, + struct json_object *reply_elem_obj) +{ + struct afc_freq_range_elem *f = NULL; + struct json_object *obj; + int i, count = 0; + + if (!json_object_object_get_ex(reply_elem_obj, + "availableFrequencyInfo", &obj)) + return 0; + + for (i = 0; i < json_object_array_length(obj); i++) { + struct json_object *range_elem_obj, *freq_range_obj; + struct json_object *high_freq_obj, *low_freq_obj; + struct json_object *max_psd_obj; + + range_elem_obj = json_object_array_get_idx(obj, i); + if (!range_elem_obj) + continue; + + if (!json_object_object_get_ex(range_elem_obj, + "frequencyRange", + &freq_range_obj)) + continue; + + if (!json_object_object_get_ex(freq_range_obj, + "lowFrequency", + &low_freq_obj)) + continue; + + if (!json_object_object_get_ex(freq_range_obj, + "highFrequency", + &high_freq_obj)) + continue; + + if (!json_object_object_get_ex(range_elem_obj, "maxPsd", + &max_psd_obj) && + !json_object_object_get_ex(range_elem_obj, "maxPSD", + &max_psd_obj)) + continue; + + f = os_realloc_array(f, count + 1, sizeof(*f)); + if (!f) + return -ENOMEM; + + f[count].low_freq = json_object_get_int(low_freq_obj); + f[count].high_freq = json_object_get_int(high_freq_obj); + f[count++].max_psd = json_object_get_int(max_psd_obj); + } + iface->afc.freq_range = f; + iface->afc.num_freq_range = count; + + return 0; +} + + +static int hostad_afc_update_chan_info(struct afc_chan_info_elem **chan_list, + int *chan_list_size, u8 op_class, + int center_chan, int power) +{ + int num_low_subchan, ch, count = *chan_list_size; + struct afc_chan_info_elem *c = *chan_list; + + switch (op_class) { + case 132: /* 40MHz */ + num_low_subchan = 2; + break; + case 133: /* 80MHz */ + num_low_subchan = 6; + break; + case 134: /* 160MHz */ + num_low_subchan = 14; + break; + default: + num_low_subchan = 0; + break; + } + + for (ch = center_chan - num_low_subchan; + ch <= center_chan + num_low_subchan; ch += 4) { + int i; + + for (i = 0; i < count; i++) { + if (c[i].chan == ch) + break; + } + + if (i == count) { + c = os_realloc_array(c, count + 1, sizeof(*c)); + if (!c) + return -ENOMEM; + + c[count].chan = ch; + c[count++].power = power; + } + } + + *chan_list_size = count; + *chan_list = c; + + return 0; +} + + +static int +hostad_afc_parse_available_chan_info(struct hostapd_iface *iface, + struct json_object *reply_elem_obj) +{ + struct afc_chan_info_elem *c = NULL; + struct json_object *obj; + int i, count = 0; + + if (!json_object_object_get_ex(reply_elem_obj, + "availableChannelInfo", &obj)) + return 0; + + for (i = 0; i < json_object_array_length(obj); i++) { + struct json_object *range_elem_obj, *op_class_obj; + struct json_object *chan_cfi_obj, *max_eirp_obj; + int ch, op_class; + + range_elem_obj = json_object_array_get_idx(obj, i); + if (!range_elem_obj) + continue; + + if (!json_object_object_get_ex(range_elem_obj, + "globalOperatingClass", + &op_class_obj)) + continue; + + if (!json_object_object_get_ex(range_elem_obj, "maxEirp", + &max_eirp_obj)) + continue; + + if (!json_object_object_get_ex(range_elem_obj, "channelCfi", + &chan_cfi_obj)) + continue; + + op_class = json_object_get_int(op_class_obj); + for (ch = 0; + ch < json_object_array_length(chan_cfi_obj); ch++) { + struct json_object *pwr_obj; + struct json_object *ch_obj; + int channel, power; + + ch_obj = json_object_array_get_idx(chan_cfi_obj, ch); + if (!ch_obj) + continue; + + pwr_obj = json_object_array_get_idx(max_eirp_obj, ch); + if (!pwr_obj) + continue; + + channel = json_object_get_int(ch_obj); + power = json_object_get_int(pwr_obj); + + hostad_afc_update_chan_info(&c, &count, op_class, + channel, power); + } + iface->afc.chan_info_list = c; + iface->afc.num_chan_info = count; + } + + return 0; +} + + +static int hostad_afc_get_timeout(struct json_object *obj) +{ + time_t t, now; + struct tm tm; + + if (sscanf(json_object_get_string(obj), "%d-%d-%dT%d:%d:%dZ", + &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, + &tm.tm_min, &tm.tm_sec) <= 0) + return HOSTAPD_AFC_TIMEOUT; + + tm.tm_year -= 1900; + tm.tm_mon -= 1; + tm.tm_isdst = -1; + t = mktime(&tm); + time(&now); + + return now > t ? HOSTAPD_AFC_RETRY_TIMEOUT : (t - now) * 80 / 100; +} + + +static int hostapd_afc_parse_reply(struct hostapd_iface *iface, char *reply) +{ + struct json_object *payload_obj, *reply_obj, *version_obj; + struct hostapd_config *iconf = iface->conf; + int i, request_timeout = -1, ret = -EINVAL; + + wpa_printf(MSG_DEBUG, "Received AFC reply: %s", reply); + payload_obj = json_tokener_parse(reply); + if (!payload_obj) + return -EINVAL; + + if (!json_object_object_get_ex(payload_obj, "version", &version_obj)) + return -EINVAL; + + if (iconf->afc.request.version && + os_strcmp(iconf->afc.request.version, + json_object_get_string(version_obj))) + return -EINVAL; + + if (!json_object_object_get_ex(payload_obj, + "availableSpectrumInquiryResponses", + &reply_obj)) + return -EINVAL; + + for (i = 0; i < json_object_array_length(reply_obj); i++) { + struct json_object *reply_elem_obj, *obj, *status_obj; + int j, status = -EINVAL; + + reply_elem_obj = json_object_array_get_idx(reply_obj, i); + if (!reply_elem_obj) + continue; + + if (!json_object_object_get_ex(reply_elem_obj, "requestId", + &obj)) + continue; + + if (iconf->afc.request.id && + os_strcmp(iconf->afc.request.id, + json_object_get_string(obj))) + continue; + + if (!json_object_object_get_ex(reply_elem_obj, "rulesetId", + &obj)) + continue; + + for (j = 0; j < iconf->afc.n_cert_ids; j++) { + if (!os_strcmp(iconf->afc.cert_ids[j].rulset, + json_object_get_string(obj))) + break; + } + + if (j == iconf->afc.n_cert_ids) + continue; + + if (!json_object_object_get_ex(reply_elem_obj, "response", + &obj)) + continue; + + if (json_object_object_get_ex(obj, "shortDescription", + &status_obj)) + wpa_printf(MSG_DEBUG, "AFC reply element %d: %s", + i, json_object_get_string(status_obj)); + + if (json_object_object_get_ex(obj, "responseCode", + &status_obj)) + status = json_object_get_int(status_obj); + + if (status < 0) + continue; + + if (hostad_afc_parse_available_freq_info(iface, + reply_elem_obj) || + hostad_afc_parse_available_chan_info(iface, + reply_elem_obj)) + continue; + + if (json_object_object_get_ex(reply_elem_obj, + "availabilityExpireTime", + &obj)) { + int timeout = hostad_afc_get_timeout(obj); + + if (request_timeout < 0 || timeout < request_timeout) + request_timeout = timeout; + } + + ret = status; + } + + iface->afc.data_valid = true; + iface->afc.timeout = request_timeout; + if (iface->afc.timeout < 0) + iface->afc.timeout = HOSTAPD_AFC_RETRY_TIMEOUT; + + return ret; +} + + +static int hostapd_afc_send_receive(struct hostapd_iface *iface) +{ + struct hostapd_config *iconf = iface->conf; + json_object *request_obj = NULL; + struct timeval sock_timeout = { + .tv_sec = 5, + }; + struct sockaddr_un addr = { + .sun_family = AF_UNIX, +#ifdef __FreeBSD__ + .sun_len = sizeof(addr), +#endif /* __FreeBSD__ */ + }; + const char *request; + char *buf = NULL; + int sockfd, ret; + fd_set read_set; + + if (iface->afc.data_valid) { + /* AFC data already downloaded from the server */ + return 0; + } + + if (!iconf->afc.socket) { + wpa_printf(MSG_ERROR, "Missing AFC socket string"); + return -EINVAL; + } + + iface->afc.timeout = HOSTAPD_AFC_RETRY_TIMEOUT; + if (os_strlen(iconf->afc.socket) >= sizeof(addr.sun_path)) { + wpa_printf(MSG_ERROR, "Malformed AFC socket string %s", + iconf->afc.socket); + return -EINVAL; + } + + os_strlcpy(addr.sun_path, iconf->afc.socket, sizeof(addr.sun_path)); + sockfd = socket(AF_UNIX, SOCK_STREAM, 0); + if (sockfd < 0) { + wpa_printf(MSG_ERROR, "Failed creating AFC socket"); + return sockfd; + } + + if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + wpa_printf(MSG_ERROR, "Failed connecting AFC socket"); + ret = -EIO; + goto close_sock; + } + + request_obj = hostapd_afc_build_request(iface); + if (!request_obj) { + ret = -ENOMEM; + goto close_sock; + } + + request = json_object_to_json_string(request_obj); + if (send(sockfd, request, strlen(request), 0) < 0) { + wpa_printf(MSG_ERROR, "Failed sending AFC request"); + ret = -EIO; + goto close_sock; + } + + FD_ZERO(&read_set); + FD_SET(sockfd, &read_set); + if (select(sockfd + 1, &read_set, NULL, NULL, &sock_timeout) < 0) { + wpa_printf(MSG_ERROR, "Select failed on AFC socket"); + ret = -errno; + goto close_sock; + } + + if (!FD_ISSET(sockfd, &read_set)) { + ret = -EIO; + goto close_sock; + } + + buf = os_zalloc(HOSTAPD_AFC_BUFSIZE); + if (!buf) { + ret = -ENOMEM; + goto close_sock; + } + + ret = recv(sockfd, buf, HOSTAPD_AFC_BUFSIZE - 1, 0); + if (ret <= 0) + goto close_sock; + + ret = hostapd_afc_parse_reply(iface, buf); + if (ret) + wpa_printf(MSG_ERROR, "Failed parsing AFC reply: %d", ret); +close_sock: + os_free(buf); + json_object_put(request_obj); + close(sockfd); + + return ret; +} + + +static bool hostapd_afc_has_usable_chans(struct hostapd_iface *iface) +{ + const struct oper_class_map *oper_class; + int ch; + + oper_class = get_oper_class(NULL, iface->conf->op_class); + if (!oper_class) + return false; + + for (ch = oper_class->min_chan; ch <= oper_class->max_chan; + ch += oper_class->inc) { + struct hostapd_hw_modes *mode = iface->current_mode; + int i; + + for (i = 0; i < mode->num_channels; i++) { + struct hostapd_channel_data *chan = &mode->channels[i]; + + if (chan->chan == ch && + !(chan->flag & HOSTAPD_CHAN_DISABLED)) + return true; + } + } + + return false; +} + + +int hostapd_afc_handle_request(struct hostapd_iface *iface) +{ + struct hostapd_config *iconf = iface->conf; + int ret; + + /* AFC is required just for standard power AP */ + if (!he_reg_is_sp(iconf->he_6ghz_reg_pwr_type)) + return 1; + + if (!is_6ghz_op_class(iconf->op_class) || !is_6ghz_freq(iface->freq)) + return 1; + + if (iface->state == HAPD_IFACE_ACS) + return 1; + + ret = hostapd_afc_send_receive(iface); + if (ret < 0) { + /* + * If the connection to the AFCD failed, resched for a + * future attempt. + */ + wpa_printf(MSG_ERROR, "AFC connection failed: %d", ret); + if (ret == -EIO) + ret = 0; + goto resched; + } + + hostap_afc_disable_channels(iface); + if (!hostapd_afc_has_usable_chans(iface)) + goto resched; + + /* Trigger an ACS freq scan */ + iconf->channel = 0; + iface->freq = 0; + + if (acs_init(iface) != HOSTAPD_CHAN_ACS) { + wpa_printf(MSG_ERROR, "Could not start ACS"); + ret = -EINVAL; + } + +resched: + eloop_cancel_timeout(hostapd_afc_timeout_handler, iface, NULL); + eloop_register_timeout(iface->afc.timeout, 0, + hostapd_afc_timeout_handler, iface, NULL); + + return ret; +} + + +static void hostapd_afc_delete_data_from_server(struct hostapd_iface *iface) +{ + os_free(iface->afc.chan_info_list); + os_free(iface->afc.freq_range); + + iface->afc.num_freq_range = 0; + iface->afc.num_chan_info = 0; + + iface->afc.chan_info_list = NULL; + iface->afc.freq_range = NULL; + + iface->afc.data_valid = false; +} + + +static void hostapd_afc_timeout_handler(void *eloop_ctx, void *timeout_ctx) +{ + struct hostapd_iface *iface = eloop_ctx; + bool restart_iface = true; + + hostapd_afc_delete_data_from_server(iface); + if (iface->state != HAPD_IFACE_ENABLED) { + /* Hostapd is not fully enabled yet, toggle the interface */ + goto restart_interface; + } + + if (hostapd_afc_send_receive(iface) < 0 || + hostapd_get_hw_features(iface)) { + restart_iface = false; + goto restart_interface; + } + + if (hostapd_is_usable_chans(iface)) + goto resched; + + restart_iface = hostapd_afc_has_usable_chans(iface); + if (restart_iface) { + /* Trigger an ACS freq scan */ + iface->conf->channel = 0; + iface->freq = 0; + } + +restart_interface: + hostapd_disable_iface(iface); + if (restart_iface) + hostapd_enable_iface(iface); +resched: + eloop_register_timeout(iface->afc.timeout, 0, + hostapd_afc_timeout_handler, iface, NULL); +} + + +void hostapd_afc_stop(struct hostapd_iface *iface) +{ + eloop_cancel_timeout(hostapd_afc_timeout_handler, iface, NULL); +} + + +void hostap_afc_disable_channels(struct hostapd_iface *iface) +{ + struct hostapd_hw_modes *mode; + int i; + + if (iface->num_hw_features < HOSTAPD_MODE_IEEE80211A) + return; + + if (!he_reg_is_sp(iface->conf->he_6ghz_reg_pwr_type)) + return; + + if (!iface->afc.data_valid) + return; + + mode = &iface->hw_features[HOSTAPD_MODE_IEEE80211A]; + for (i = 0; i < mode->num_channels; i++) { + struct hostapd_channel_data *chan = &mode->channels[i]; + int j; + + if (!is_6ghz_freq(chan->freq)) + continue; + + for (j = 0; j < iface->afc.num_freq_range; j++) { + if (chan->freq >= iface->afc.freq_range[j].low_freq && + chan->freq <= iface->afc.freq_range[j].high_freq) + break; + } + + if (j != iface->afc.num_freq_range) + continue; + + for (j = 0; j < iface->afc.num_chan_info; j++) { + if (chan->chan == iface->afc.chan_info_list[j].chan) + break; + } + + if (j != iface->afc.num_chan_info) + continue; + + chan->flag |= HOSTAPD_CHAN_DISABLED; + wpa_printf(MSG_MSGDUMP, + "Disabling freq=%d MHz (not allowed by AFC)", + chan->freq); + } +} diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c index e1910d422..2282a574b 100644 --- a/src/ap/ap_config.c +++ b/src/ap/ap_config.c @@ -1036,6 +1036,22 @@ void hostapd_config_free(struct hostapd_config *conf) #endif /* CONFIG_ACS */ wpabuf_free(conf->lci); wpabuf_free(conf->civic); +#ifdef CONFIG_AFC + os_free(conf->afc.socket); + os_free(conf->afc.request.version); + os_free(conf->afc.request.id); + os_free(conf->afc.request.sn); + for (i = 0; i < conf->afc.n_cert_ids; i++) { + os_free(conf->afc.cert_ids[i].rulset); + os_free(conf->afc.cert_ids[i].id); + } + os_free(conf->afc.cert_ids); + os_free(conf->afc.location.height_type); + os_free(conf->afc.location.linear_polygon_data); + os_free(conf->afc.location.radial_polygon_data); + os_free(conf->afc.freq_range); + os_free(conf->afc.op_class); +#endif /* CONFIG_AFC */ os_free(conf); } diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h index aa513be4c..ca26959d5 100644 --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h @@ -1226,6 +1226,53 @@ struct hostapd_config { MBSSID_ENABLED = 1, ENHANCED_MBSSID_ENABLED = 2, } mbssid; + +#ifdef CONFIG_AFC + struct { + char *socket; + struct { + char *version; + char *id; + char *sn; + } request; + unsigned int n_cert_ids; + struct cert_id { + char *rulset; + char *id; + } *cert_ids; + struct { + enum afc_location_type { + ELLIPSE, + LINEAR_POLYGON, + RADIAL_POLYGON, + } type; + unsigned int n_linear_polygon_data; + struct afc_linear_polygon { + double longitude; + double latitude; + } *linear_polygon_data; + unsigned int n_radial_polygon_data; + struct afc_radial_polygon { + double length; + double angle; + } *radial_polygon_data; + int major_axis; + int minor_axis; + int orientation; + double height; + char *height_type; + int vertical_tolerance; + } location; + unsigned int n_freq_range; + struct afc_freq_range { + int low_freq; + int high_freq; + } *freq_range; + unsigned int n_op_class; + unsigned int *op_class; + int min_power; + } afc; +#endif /* CONFIG_AFC */ }; diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c index 56bac4526..c36187586 100644 --- a/src/ap/hostapd.c +++ b/src/ap/hostapd.c @@ -715,6 +715,7 @@ void hostapd_cleanup_iface_partial(struct hostapd_iface *iface) static void hostapd_cleanup_iface(struct hostapd_iface *iface) { wpa_printf(MSG_DEBUG, "%s(%p)", __func__, iface); + hostapd_afc_stop(iface); eloop_cancel_timeout(hostapd_interface_setup_failure_handler, iface, NULL); @@ -2455,6 +2456,16 @@ static int hostapd_setup_interface_complete_sync(struct hostapd_iface *iface, } #endif /* CONFIG_MESH */ +#ifdef CONFIG_IEEE80211AX + /* check AFC for 6GHz channels. */ + res = hostapd_afc_handle_request(iface); + if (res <= 0) { + if (res < 0) + goto fail; + return res; + } +#endif /* CONFIG_IEEE80211AX */ + if (!delay_apply_cfg && hostapd_set_freq(hapd, hapd->iconf->hw_mode, iface->freq, hapd->iconf->channel, @@ -2853,6 +2864,7 @@ void hostapd_interface_deinit(struct hostapd_iface *iface) hostapd_set_state(iface, HAPD_IFACE_DISABLED); + hostapd_afc_stop(iface); eloop_cancel_timeout(channel_list_update_timeout, iface, NULL); iface->wait_channel_update = 0; iface->is_no_ir = false; @@ -2926,6 +2938,10 @@ void hostapd_interface_free(struct hostapd_iface *iface) __func__, iface->bss[j]); os_free(iface->bss[j]); } +#ifdef CONFIG_AFC + os_free(iface->afc.chan_info_list); + os_free(iface->afc.freq_range); +#endif hostapd_cleanup_iface(iface); } diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h index 1d1943ac5..88d111022 100644 --- a/src/ap/hostapd.h +++ b/src/ap/hostapd.h @@ -710,9 +710,54 @@ struct hostapd_iface { bool is_no_ir; bool is_ch_switch_dfs; /* Channel switch from ACS to DFS */ + +#ifdef CONFIG_AFC + struct { + int timeout; + unsigned int num_freq_range; + struct afc_freq_range_elem { + int low_freq; + int high_freq; + /** + * max eirp power spectral density received from + * the AFC coordinator for this band + */ + int max_psd; + } *freq_range; + unsigned int num_chan_info; + struct afc_chan_info_elem { + int chan; + /** + * max eirp power received from the AFC coordinator + * for this channel + */ + int power; + } *chan_info_list; + bool data_valid; + } afc; +#endif /* CONFIG_AFC */ }; /* hostapd.c */ +#ifdef CONFIG_AFC +int hostapd_afc_handle_request(struct hostapd_iface *iface); +void hostapd_afc_stop(struct hostapd_iface *iface); +void hostap_afc_disable_channels(struct hostapd_iface *iface); +#else +static inline int hostapd_afc_handle_request(struct hostapd_iface *iface) +{ + return 1; +} + +static inline void hostapd_afc_stop(struct hostapd_iface *iface) +{ +} + +static inline void hostap_afc_disable_channels(struct hostapd_iface *iface) +{ +} +#endif /* CONFIG_AFC */ + int hostapd_for_each_interface(struct hapd_interfaces *interfaces, int (*cb)(struct hostapd_iface *iface, void *ctx), void *ctx); diff --git a/src/ap/hw_features.c b/src/ap/hw_features.c index 85e67080d..8aa0b3ab5 100644 --- a/src/ap/hw_features.c +++ b/src/ap/hw_features.c @@ -114,6 +114,8 @@ int hostapd_get_hw_features(struct hostapd_iface *iface) iface->hw_features = modes; iface->num_hw_features = num_modes; + hostap_afc_disable_channels(iface); + for (i = 0; i < num_modes; i++) { struct hostapd_hw_modes *feature = &modes[i]; int dfs_enabled = hapd->iconf->ieee80211h && From patchwork Tue Apr 16 16:16:55 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lorenzo Bianconi X-Patchwork-Id: 1924304 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (2048-bit key; secure) header.d=lists.infradead.org header.i=@lists.infradead.org header.a=rsa-sha256 header.s=bombadil.20210309 header.b=p3E3Y/kF; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=kernel.org header.i=@kernel.org header.a=rsa-sha256 header.s=k20201202 header.b=nL9Isyn2; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=none (no SPF record) smtp.mailfrom=lists.infradead.org (client-ip=2607:7c80:54:3::133; helo=bombadil.infradead.org; envelope-from=hostap-bounces+incoming=patchwork.ozlabs.org@lists.infradead.org; receiver=patchwork.ozlabs.org) Received: from bombadil.infradead.org (bombadil.infradead.org [IPv6:2607:7c80:54:3::133]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4VJq2P5DZgz1yZC for ; Wed, 17 Apr 2024 02:17:49 +1000 (AEST) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender: Content-Transfer-Encoding:Content-Type:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:Cc:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=1XmXDQCwCRIwBAluC9eaVLh0+CRdxe80JlsZqanPefU=; b=p3E3Y/kFiF/M1o 4Bs1U5cY7G4aV/s8CyTmTtkfUu+cdKw2zROqvy/+xDVE6sq/ay+Q9RMOtdECq207yRCmPoa9XVrZx 0Tx21wKaO5VotRaS+V0L9PM/4vEdingfoXGgXMaF0Bu/nxmEvcbZy0e/84fkA6wNvq2Au8Y/c1KvC i8zCIicXVCXZCgwGgMgG4YH5xD3x294OZYyJgGAfbt53xHrQro7LeVxblrlGZ6jQhdQ5vXqFSeNM+ 5vcBpto33Buc5q8aIJfZ3Yxh5npkefBtbgrstco5s6AeU8racmNtWOGIOdlkogIdjpiSaf3FcaLr3 DNYUGEvRl3RqvwRVC7PQ==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.97.1 #2 (Red Hat Linux)) id 1rwlUm-0000000Cy2O-2UcW; Tue, 16 Apr 2024 16:17:20 +0000 Received: from sin.source.kernel.org ([145.40.73.55]) by bombadil.infradead.org with esmtps (Exim 4.97.1 #2 (Red Hat Linux)) id 1rwlUj-0000000Cy16-2VlR for hostap@lists.infradead.org; Tue, 16 Apr 2024 16:17:19 +0000 Received: from smtp.kernel.org (transwarp.subspace.kernel.org [100.75.92.58]) by sin.source.kernel.org (Postfix) with ESMTP id 6A520CE0F81; Tue, 16 Apr 2024 16:17:15 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id 11A64C113CE; Tue, 16 Apr 2024 16:17:11 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1713284233; bh=cCrRRCGhuyeDz0vfEWmMozqvl1gTN73Vv+q2yCVVKc0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=nL9Isyn25ru4azKT2sH/+QVfUJu/d0+59jc/aCXmNvwetDksVj4FPnVmb3zkXbUGm vlZTciVVF3N5gVv5R9MADijgkxFI39l5Tji6dwl5SYO0vjOm1k4YSxcJOZDdu3N8s/ MgNYrFNJ0umB7vci+ZCzYioQQJDPFsIaok/Tu/MGsLjoXhcan2cyGaqkI7TFtTMR9c TWJOjjzncNlHK5hqfsOlQC0pttf2RZUFnstgiDCMGXq7Ah5EiZWOhQCDYKcav+Ky04 VGt48eDymzaM+0PNk/jpulR8FdgT2Z+HeqCxUYFd18vZ3qCj/wJ2B01LQm9szxHUGP z8D752HrYqo2A== From: Lorenzo Bianconi To: hostap@lists.infradead.org Cc: j@w1.fi, lorenzo.bianconi83@gmail.com, ryder.lee@mediatek.com, evelyn.tsai@mediatek.com, nbd@nbd.name, allen.ye@mediatek.com, jesus.manzano@galgus.ai, chaitanya.mgit@gmail.com Subject: [PATCH v5 4/4] hostapd: update TPE IE according to AFC Date: Tue, 16 Apr 2024 18:16:55 +0200 Message-ID: <43c6a5b4dd9e380aa7bda91b2e1854e19c696bb1.1713283884.git.lorenzo@kernel.org> X-Mailer: git-send-email 2.44.0 In-Reply-To: References: MIME-Version: 1.0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20240416_091718_138644_8121AF1A X-CRM114-Status: GOOD ( 16.51 ) X-Spam-Score: -4.3 (----) X-Spam-Report: Spam detection software, running on the system "bombadil.infradead.org", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: Update Transmit Power Envelope (TPE) IE according to the reply from AFC coordinator on UNII-5 or UNII-7 6GHz bands. Tested-by: Felix Fietkau Tested-by: Allen Ye Signed-off-by: Lorenzo Bianconi --- src/ap/afc.c | 37 +++++++++++++++++++++++++++++++++++ src/a [...] Content analysis details: (-4.3 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- -2.3 RCVD_IN_DNSWL_MED RBL: Sender listed at https://www.dnswl.org/, medium trust [145.40.73.55 listed in list.dnswl.org] -0.0 SPF_PASS SPF: sender matches SPF record 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -1.8 DKIMWL_WL_HIGH DKIMwl.org - High trust sender X-BeenThere: hostap@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "Hostap" Errors-To: hostap-bounces+incoming=patchwork.ozlabs.org@lists.infradead.org Update Transmit Power Envelope (TPE) IE according to the reply from AFC coordinator on UNII-5 or UNII-7 6GHz bands. Tested-by: Felix Fietkau Tested-by: Allen Ye Signed-off-by: Lorenzo Bianconi --- src/ap/afc.c | 37 +++++++++++++++++++++++++++++++++++ src/ap/hostapd.h | 9 +++++++++ src/ap/ieee802_11.c | 47 ++++++++++++++++++++++++++++----------------- 3 files changed, 75 insertions(+), 18 deletions(-) diff --git a/src/ap/afc.c b/src/ap/afc.c index 21e1e17cb..053c8d267 100644 --- a/src/ap/afc.c +++ b/src/ap/afc.c @@ -994,3 +994,40 @@ void hostap_afc_disable_channels(struct hostapd_iface *iface) chan->freq); } } + + +int hostap_afc_get_chan_max_eirp_power(struct hostapd_iface *iface, bool psd, + int *power) +{ + int i; + + if (!he_reg_is_sp(iface->conf->he_6ghz_reg_pwr_type)) + return -EINVAL; + + if (!iface->afc.data_valid) + return -EINVAL; + + if (psd) { + for (i = 0; i < iface->afc.num_freq_range; i++) { + struct afc_freq_range_elem *f; + + f = &iface->afc.freq_range[i]; + if (iface->freq >= f->low_freq && + iface->freq <= f->high_freq) { + *power = 2 * f->max_psd; + return 0; + } + } + } else { + for (i = 0; i < iface->afc.num_chan_info; i++) { + struct afc_chan_info_elem *c; + + c = &iface->afc.chan_info_list[i]; + if (c->chan == iface->conf->channel) { + *power = 2 * c->power; + return 0; + } + } + } + return -EINVAL; +} diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h index 88d111022..5e8e10449 100644 --- a/src/ap/hostapd.h +++ b/src/ap/hostapd.h @@ -740,10 +740,19 @@ struct hostapd_iface { /* hostapd.c */ #ifdef CONFIG_AFC +int hostap_afc_get_chan_max_eirp_power(struct hostapd_iface *iface, bool psd, + int *power); int hostapd_afc_handle_request(struct hostapd_iface *iface); void hostapd_afc_stop(struct hostapd_iface *iface); void hostap_afc_disable_channels(struct hostapd_iface *iface); #else +static inline int +hostap_afc_get_chan_max_eirp_power(struct hostapd_iface *iface, bool psd, + int *power) +{ + return -EINVAL; +} + static inline int hostapd_afc_handle_request(struct hostapd_iface *iface) { return 1; diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c index 85a39d5ff..df07405ed 100644 --- a/src/ap/ieee802_11.c +++ b/src/ap/ieee802_11.c @@ -7104,42 +7104,53 @@ u8 * hostapd_eid_txpower_envelope(struct hostapd_data *hapd, u8 *eid) */ if (is_6ghz_op_class(iconf->op_class)) { enum max_tx_pwr_interpretation tx_pwr_intrpn; + int err, max_eirp_psd, max_eirp_power; /* Same Maximum Transmit Power for all 20 MHz bands */ tx_pwr_count = 0; tx_pwr_intrpn = REGULATORY_CLIENT_EIRP_PSD; /* Default Transmit Power Envelope for Global Operating Class */ - if (hapd->iconf->reg_def_cli_eirp_psd != -1) - tx_pwr = hapd->iconf->reg_def_cli_eirp_psd; - else - tx_pwr = REG_PSD_MAX_TXPOWER_FOR_DEFAULT_CLIENT * 2; + err = hostap_afc_get_chan_max_eirp_power(iface, true, + &max_eirp_psd); + if (err < 0) { + if (hapd->iconf->reg_def_cli_eirp_psd != -1) + max_eirp_psd = hapd->iconf->reg_def_cli_eirp_psd; + else + max_eirp_psd = REG_PSD_MAX_TXPOWER_FOR_DEFAULT_CLIENT * 2; + } eid = hostapd_add_tpe_info(eid, tx_pwr_count, tx_pwr_intrpn, - REG_DEFAULT_CLIENT, tx_pwr); + REG_DEFAULT_CLIENT, max_eirp_psd); /* Indoor Access Point must include an additional TPE for * subordinate devices */ if (he_reg_is_indoor(iconf->he_6ghz_reg_pwr_type)) { - /* TODO: Extract PSD limits from channel data */ - if (hapd->iconf->reg_sub_cli_eirp_psd != -1) - tx_pwr = hapd->iconf->reg_sub_cli_eirp_psd; - else - tx_pwr = REG_PSD_MAX_TXPOWER_FOR_SUBORDINATE_CLIENT * 2; + if (err < 0) { + /* non-AFC connection */ + if (hapd->iconf->reg_sub_cli_eirp_psd != -1) + max_eirp_psd = hapd->iconf->reg_sub_cli_eirp_psd; + else + max_eirp_psd = REG_PSD_MAX_TXPOWER_FOR_SUBORDINATE_CLIENT * 2; + } eid = hostapd_add_tpe_info(eid, tx_pwr_count, tx_pwr_intrpn, REG_SUBORDINATE_CLIENT, - tx_pwr); + max_eirp_psd); } - if (iconf->reg_def_cli_eirp != -1 && - he_reg_is_sp(iconf->he_6ghz_reg_pwr_type)) - eid = hostapd_add_tpe_info( - eid, tx_pwr_count, REGULATORY_CLIENT_EIRP, - REG_DEFAULT_CLIENT, - hapd->iconf->reg_def_cli_eirp); + if (hostap_afc_get_chan_max_eirp_power(iface, false, + &max_eirp_power)) { + max_eirp_power = iconf->reg_def_cli_eirp; + if (max_eirp_power == -1 || + !he_reg_is_sp(iconf->he_6ghz_reg_pwr_type)) + return eid; + } - return eid; + return hostapd_add_tpe_info(eid, tx_pwr_count, + REGULATORY_CLIENT_EIRP, + REG_DEFAULT_CLIENT, + max_eirp_power); } #endif /* CONFIG_IEEE80211AX */