From patchwork Wed Apr 10 13:32:19 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lorenzo Bianconi X-Patchwork-Id: 1922006 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=My0b3xsK; 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=EuREB4mJ; 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 4VF3gP6MHfz1yZY for ; Wed, 10 Apr 2024 23:33:21 +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=q7IhzyFEMbM178u74HKdjxO6o3TbZ3MTViObhJ/mxAs=; b=My0b3xsK3IoApr SlDqvsSAL+XJYSMZkUHljD+62BgLoA2PhTY/aASPV/izMam1grKoC0ljyg4aOu81HQ+ThGf+18iV8 qqB/Udoobm50c49QnYyxsZPO339e+yn/TDBlboDpZYotjAnQQbydhHL4iRHO8NOue+hJAMd0AcByr iRRh1QYFzTkTgRl6335HAtZ44+h3shyvcrlrOb+ZR7TMHSt/pVqQlnpv5KuNPaHKy3gnP+hNQGp4z N8T8qBcA/ZgZVGDPK+Jb96cRbwYzQPmNnsIyEb8Ygk0RBv1V3L7chySfPcN7rh19Sme5xz3qETXZV s8dC+cIg8q04Xnf38Dyw==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.97.1 #2 (Red Hat Linux)) id 1ruY4L-00000007BPy-3W3F; Wed, 10 Apr 2024 13:32:53 +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 1ruY4H-00000007BO3-2m2s for hostap@lists.infradead.org; Wed, 10 Apr 2024 13:32:51 +0000 Received: from smtp.kernel.org (transwarp.subspace.kernel.org [100.75.92.58]) by dfw.source.kernel.org (Postfix) with ESMTP id C86AA61CD6; Wed, 10 Apr 2024 13:32:48 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id DD089C433F1; Wed, 10 Apr 2024 13:32:46 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1712755968; bh=Iq1/mhc/cIM+qrrgyVAiB/qI/72MD3YioLn3/wBXdHg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=EuREB4mJI5bNpCw/Rkbr7m9zMyxtn5NCPKxkyJfupXIlYtyDpRM2dNHkbo5UUOsfI Pr3QYV4tct2cHsQdsQIFX5iAg/J6/56iugXKfrH1eXZO4Ztj1HdMRdI1bHr+yaOGlr GCEnQJILhVEt98Ch/UdW73crlOBexyoB9iXH7+U6/e+NpD1k80AZwZk/Lreb2bR40g WMyC7BIRSbKpzynC6DJzmNsWGEiE/GdMDhxiXqxp4mG8Ks64J6SKAu2ftfY8+UhWYq US+j6h2WJT/pJnKspIntPGIxo3FgPLwhOLnGHIsBV61c29W4BFQUjI9CJ2q3dceWJ9 HW2SAUoav8x7Q== 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 Subject: [PATCH v4 1/4] hostapd: afcd: add AFC daemon support Date: Wed, 10 Apr 2024 15:32:19 +0200 Message-ID: <636f5b4c075621220e9dd8785a58d233701b3eb9.1712755468.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-20240410_063249_882526_690192FF X-CRM114-Status: GOOD ( 22.09 ) X-Spam-Score: -7.2 (-------) 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: (-7.2 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_HELO_NONE SPF: HELO does not publish an SPF Record -0.0 SPF_PASS SPF: sender matches SPF record -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain -2.0 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.json | 215 +++++++++++++++++++++++++++++++++ afc/afcd.c | 292 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 539 insertions(+) create mode 100644 afc/.gitignore create mode 100644 afc/Makefile create mode 100644 afc/afc-reply.json 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.json b/afc/afc-reply.json new file mode 100644 index 000000000..35882c74c --- /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" +} diff --git a/afc/afcd.c b/afc/afcd.c new file mode 100644 index 000000000..445db7c39 --- /dev/null +++ b/afc/afcd.c @@ -0,0 +1,292 @@ +/* + * 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; + 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 [-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 Wed Apr 10 13:32:20 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lorenzo Bianconi X-Patchwork-Id: 1922004 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=Eq3B1q0D; 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=MtsRVemh; 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 4VF3gM41FQz1yYB for ; Wed, 10 Apr 2024 23:33:17 +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=fdsxasL9chEYUc1QriCFYgftLoIZvpxypcUDDt00QBw=; b=Eq3B1q0DA24hmh VgU3Kqp4OtxPv9SaxZtGfGwRFLWVr5Z0yBoVMdrXJWKvYQvmMyt/a0KoAPxYUeIAouhdZyupEBbAS Sj4on28tZsco94hWBItjyQ3pqGOZt8Hee1zoQhZsXjWBdrYI/wp1MwKg2RnKJ1q/s3w6En2kyuU0p nAND1w8w3CoA3T5un6Ox7rf7+FSqSmDxW858KmHBnb7GzK4UUdhF2eBb2MiNTs8Y22kpGs34BRQSr gBBudhNaAgkE2jSaZHlGZav6i+EpvERPZF3jm5wGQ5vYN9tkXs0Whipz8OQVM+DpZfBTzShVQLvpH J+4HLtLn2xiFjvKgVKbQ==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.97.1 #2 (Red Hat Linux)) id 1ruY4O-00000007BRq-3P0d; Wed, 10 Apr 2024 13:32:56 +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 1ruY4J-00000007BOl-1D8o for hostap@lists.infradead.org; Wed, 10 Apr 2024 13:32:52 +0000 Received: from smtp.kernel.org (transwarp.subspace.kernel.org [100.75.92.58]) by dfw.source.kernel.org (Postfix) with ESMTP id C453E61CBD; Wed, 10 Apr 2024 13:32:50 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id DBD26C43390; Wed, 10 Apr 2024 13:32:48 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1712755970; bh=kEDFy/oj1nbdEQwpd1DvdMt0mj6eM9QtPM0/GJ9oMZM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=MtsRVemhKVzQSnJlW0AS5j876F9RzYYnzzpMCvCkT8w66zQzcS7LcMUFwCmhU1Syo F+zUU+IN3iFT+WFvm+wfZDG8IhN9yWeJF1WvnK90qAcPgspQor9iUp9denJDSw7nTb y7fwukd0bKhVzGuEfQAM0JqVOMOFN0CbR/o7ww45aeUtInL4Pmq+5g0r471mnxLnYV 7Vp1voFyiXWv56fp0yWqxP4c99r6izqrlv4F8L7/zWuJNBMfH1Ku1wTnroy70J3c7t ID6lMYqCKr4tgS6omihm7JIod+RYdoECM7+vPqSrL/iJ50HkZr68OCxecL9OfqgsOP dZcn7UgjfUpGQ== 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 Subject: [PATCH v4 2/4] hostapd: export hostapd_is_usable_chans utility routine Date: Wed, 10 Apr 2024 15:32:20 +0200 Message-ID: <41a1c05a7a573828cd05359379d4e0383a6ed82b.1712755468.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-20240410_063251_402531_75C4FEA5 X-CRM114-Status: GOOD ( 10.18 ) X-Spam-Score: -7.2 (-------) 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.2 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_HELO_NONE SPF: HELO does not publish an SPF Record -0.0 SPF_PASS SPF: sender matches SPF record -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain -2.0 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 fd401d78a..e652d7504 100644 --- a/src/ap/hw_features.c +++ b/src/ap/hw_features.c @@ -995,7 +995,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 Wed Apr 10 13:32:21 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lorenzo Bianconi X-Patchwork-Id: 1922002 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=OICs1Eik; 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=n/5Zzmun; 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 4VF3gN1mlNz1yZG for ; Wed, 10 Apr 2024 23:33:20 +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=DGIT9d+3PcLNr44g0ttRczg4Q5DIqygAJCp1M31e8Fc=; b=OICs1EikwBXnBh a6ycQHqE4yDnfh5V/70nE069totloFcSYX5cYZvZWkIgWDhXQUOlbwMIIqqPAB1Ov/cc36dOPMrYa lp80o83uT6pzrrz/bvg1F35oy86ukQADWPspyHcSsCSvbS2a92YKO7Nj/z1fdLEG56Mx2FIVVhlJS 8+vMONkJVH0Vv34a5c/fh1P67meGriPQCN9TB9o9X3hupgqnokalE64wV78JONrwzYmEtF2aas3vp mKWz+iL9utgIkYw/g4tXm8f0bGoPyFhaffCJWtixjGXryY+hHIKQloEy+T8xlAZwCWfi+iiZE0egw wu1WCl2uN8eU3OPlhJ2A==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.97.1 #2 (Red Hat Linux)) id 1ruY4S-00000007BTG-2bpp; Wed, 10 Apr 2024 13:33:00 +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 1ruY4L-00000007BPr-2pzJ for hostap@lists.infradead.org; Wed, 10 Apr 2024 13:32:58 +0000 Received: from smtp.kernel.org (transwarp.subspace.kernel.org [100.75.92.58]) by dfw.source.kernel.org (Postfix) with ESMTP id ED4FD61CBD; Wed, 10 Apr 2024 13:32:52 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id DBD5FC43394; Wed, 10 Apr 2024 13:32:50 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1712755972; bh=n3sttiZO9TVgWn/uGp0q+GtZJ28i2sfYGl0xKFhlhaQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=n/5ZzmunyDmlDrg3b/PlI2JVXnSa9Vap9/7bPjwnmTR1JaNNF9SOmrFJlP+L2PXOI yJLXsxBY+duROuhiM3Us32KcoMR3YIHpaVP9wTbpSrMCCmLp2mOIu5p8NQnTmBjVkT 2zVjX4en1LBWEZU2FxTfp6GzrCIsV0PhJfG4GKeIDGeMlgecwaqx3Xi8eS9vi4ydH5 I88rZcdCv7ZYTLfWljWrp1000bhg5E14dN14j1vqkO13euArhiUm/RcbuGyFLMkcWE GNm660dBHibv6fF4r5EcPmpTfps6AWB/xmzW+VbzUywbUUtCc0iSBDFGjTHGOwiXDs bbLk+A+8sW1iA== 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 Subject: [PATCH v4 3/4] hostapd: ap: add AFC client support Date: Wed, 10 Apr 2024 15:32:21 +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-20240410_063253_926245_1CC614B1 X-CRM114-Status: GOOD ( 22.75 ) X-Spam-Score: -7.2 (-------) 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: (-7.2 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_HELO_NONE SPF: HELO does not publish an SPF Record -0.0 SPF_PASS SPF: sender matches SPF record -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain -2.0 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 | 978 ++++++++++++++++++++++++++++++++++++++++++ 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, 1418 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 56b2df3ae..c674e8ef7 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 d80abcac0..0d10998af 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..40af3cb4a --- /dev/null +++ b/src/ap/afc.c @@ -0,0 +1,978 @@ +/* + * 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 4096 + +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]; + + 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); + 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__ */ + }; + char buf[HOSTAPD_AFC_BUFSIZE] = {}; + const char *request; + 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; + } + + ret = recv(sockfd, buf, sizeof(buf) - 1, 0); + if (ret <= 0) + goto close_sock; + + ret = hostapd_afc_parse_reply(iface, buf); +close_sock: + 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; + } +} diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c index 1a18df617..ca67aeb41 100644 --- a/src/ap/ap_config.c +++ b/src/ap/ap_config.c @@ -1035,6 +1035,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 754d55331..2330163c4 100644 --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h @@ -1225,6 +1225,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 f8cb6432d..940a2a0af 100644 --- a/src/ap/hostapd.c +++ b/src/ap/hostapd.c @@ -714,6 +714,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(channel_list_update_timeout, iface, NULL); eloop_cancel_timeout(hostapd_interface_setup_failure_handler, iface, NULL); @@ -2454,6 +2455,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, @@ -2852,6 +2863,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; @@ -2925,6 +2937,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 affe4f604..b74dc75f6 100644 --- a/src/ap/hostapd.h +++ b/src/ap/hostapd.h @@ -700,9 +700,54 @@ struct hostapd_iface { /* Configured freq of interface is NO_IR */ bool is_no_ir; + +#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 e652d7504..222f3dc05 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 Wed Apr 10 13:32:22 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lorenzo Bianconi X-Patchwork-Id: 1922005 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=Oopjahen; 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=QP60DrGq; 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 4VF3gM4xxnz1yZ8 for ; Wed, 10 Apr 2024 23:33:17 +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=8JTgmG/kCU+bwc7I4oUfxxiGAEAEmF7bMEK25As3h1E=; b=Oopjahen1bZ1oL dwddMTJ/HB2qq218EaeljbcHOHI50EMgPYgaUfAh4ymIdSv0TkXMxeQjLuWyI5SY++lA4EudAovZ9 jP5NFQjRdeCOz50KF3c22RpZ3MSKlU/Io2lgY2c3bL6yORDdK7JiY+BlXzNr9ukNnv/45UBtXUiI2 TICrG8yB+7FClm5fP3UP+F4RWmJtst3TGoLpMe4F08t5gb2YMmVYaDsUzCaBL4kn05F+v61d+78r0 /R2OodVbeJRGtg1himtLz3idY9lGrLfKNYnJ+fiLtHWuLo8L5E6pKKoVaOPrEv0v2HPeCO4J+XUG+ AfYJvuAC6hYG7MPQBqzA==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.97.1 #2 (Red Hat Linux)) id 1ruY4Q-00000007BSI-1iSQ; Wed, 10 Apr 2024 13:32:58 +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 1ruY4N-00000007BQk-1yY3 for hostap@lists.infradead.org; Wed, 10 Apr 2024 13:32:57 +0000 Received: from smtp.kernel.org (transwarp.subspace.kernel.org [100.75.92.58]) by dfw.source.kernel.org (Postfix) with ESMTP id E935E61C47; Wed, 10 Apr 2024 13:32:54 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id 0DFF4C43390; Wed, 10 Apr 2024 13:32:52 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1712755974; bh=1b+bQ2fkwqEmf0YH41AZ4P7BJ3/uuBEjC2vV+DfbFdc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=QP60DrGqy8KhKMFEeSxrfiuY25aAAdDxbO3Se76O2ZOceltE+vv7U3t6oAf9hefQW XLPpO7nUu2OviU4GO/zmFJ2vn+oEBe8T8DOb5GnxvAxhar+ZyZT9RJ/2iSHOgoXr7+ Gwv3WA6fUUcHevw7rIxD2830sufJSM1b7AOcyrjGMNDQda4W+9kBOsDTZ/TG8JHOzj uW6XPhW/uWnrdPkeDjDGYhb2F9PJXDDjRpj/mNP5yXidOgWfFiuy46eygz8yxxGDMl xFy69MWCD/uWDRhGTb5hacvvgxJnRNZOhuohS/hi16GE7gSvgjYT2KZLGTKTmW7oM8 zaiKaDt6AnRKQ== 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 Subject: [PATCH v4 4/4] hostapd: update TPE IE according to AFC Date: Wed, 10 Apr 2024 15:32:22 +0200 Message-ID: <376e7c7722b77f65fa42d269e3a9d34b57ff7e6b.1712755468.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-20240410_063255_721895_7A95BB16 X-CRM114-Status: GOOD ( 16.79 ) X-Spam-Score: -7.2 (-------) 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: (-7.2 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_HELO_NONE SPF: HELO does not publish an SPF Record -0.0 SPF_PASS SPF: sender matches SPF record -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain -2.0 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 40af3cb4a..7ba26f07c 100644 --- a/src/ap/afc.c +++ b/src/ap/afc.c @@ -976,3 +976,40 @@ void hostap_afc_disable_channels(struct hostapd_iface *iface) chan->flag |= HOSTAPD_CHAN_DISABLED; } } + + +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 b74dc75f6..de57249f8 100644 --- a/src/ap/hostapd.h +++ b/src/ap/hostapd.h @@ -730,10 +730,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 fcb2d14cf..1ec584b39 100644 --- a/src/ap/ieee802_11.c +++ b/src/ap/ieee802_11.c @@ -7113,42 +7113,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 */