From: Felix Fietkau Date: Thu, 4 Apr 2024 12:37:23 +0000 (+0200) Subject: hostapd: add AFC support X-Git-Url: http://git.lede-project.org./?a=commitdiff_plain;h=1e054fa5e8089a4ccd0cb4b3501131e2ed75f28b;p=openwrt%2Fstaging%2Fnbd.git hostapd: add AFC support Signed-off-by: Felix Fietkau --- diff --git a/package/network/config/wifi-scripts/files/lib/netifd/hostapd.sh b/package/network/config/wifi-scripts/files/lib/netifd/hostapd.sh index 3285ee4312..8515142650 100644 --- a/package/network/config/wifi-scripts/files/lib/netifd/hostapd.sh +++ b/package/network/config/wifi-scripts/files/lib/netifd/hostapd.sh @@ -129,9 +129,30 @@ hostapd_common_add_device_config() { config_add_int airtime_mode config_add_int mbssid + config_add_boolean afc + config_add_string \ + afc_request_version afc_request_id afc_serial_number \ + afc_location_type afc_location afc_height afc_height_type + config_add_array afc_cert_ids afc_freq_range afc_op_class + config_add_int \ + afc_min_power afc_major_axis afc_minor_axis afc_orientation \ + afc_vertical_tolerance + hostapd_add_log_config } + +hostapd_get_list() { + local var="$1" + local field="$2" + + local cur __val_list + json_get_values __val_list "$field" + for cur in $__val_list; do + append "$var" "$cur" "," + done +} + hostapd_prepare_device_config() { local config="$1" local driver="$2" @@ -141,7 +162,7 @@ hostapd_prepare_device_config() { json_get_vars country country3 country_ie beacon_int:100 doth require_mode legacy_rates \ acs_chan_bias local_pwr_constraint spectrum_mgmt_required airtime_mode cell_density \ rts_threshold beacon_rate rssi_reject_assoc_rssi rssi_ignore_probe_request maxassoc \ - mbssid:0 band reg_power_type stationary_ap + mbssid:0 band reg_power_type stationary_ap afc hostapd_set_log_options base_cfg @@ -244,6 +265,43 @@ hostapd_prepare_device_config() { [ -n "$maxassoc" ] && append base_cfg "iface_max_num_sta=$maxassoc" "$N" [ "$mbssid" -gt 0 ] && [ "$mbssid" -le 2 ] && append base_cfg "mbssid=$mbssid" "$N" + set_default afc 0 + if [ "$band" != "6g" ]; then + afc=0 + fi + + [ "$afc" -gt 0 ] && { + for v in afc_request_version afc_request_id afc_serial_number afc_min_power afc_height afc_height_type afc_vertical_tolerance \ + afc_major_axis afc_minor_axis afc_orientation; do + json_get_var val $v + append base_cfg "$v=$val" "$N" + done + + for v in afc_cert_ids afc_op_class afc_freq_range; do + val= + hostapd_get_list val $v + append base_cfg "$v=$val" "$N" + done + + json_get_vars afc_location_type afc_location + case "$afc_location_type" in + ellipse) + append base_cfg "afc_location_type=0" "$N" + append base_cfg "afc_linear_polygon=$afc_location" "$N" + ;; + linear_polygon) + append base_cfg "afc_location_type=1" "$N" + append base_cfg "afc_linear_polygon=$afc_location" "$N" + ;; + radial_polygon) + append base_cfg "afc_location_type=2" "$N" + append base_cfg "afc_radial_polygon=$afc_location" "$N" + ;; + esac + + reg_power_type=1 + } + [ "$band" = "6g" ] && { set_default reg_power_type 0 append base_cfg "he_6ghz_reg_pwr_type=$reg_power_type" "$N" diff --git a/package/network/services/hostapd/Makefile b/package/network/services/hostapd/Makefile index 44c73586ca..c553cf173e 100644 --- a/package/network/services/hostapd/Makefile +++ b/package/network/services/hostapd/Makefile @@ -96,6 +96,7 @@ DRIVER_MAKEOPTS= \ CONFIG_IEEE80211AC=$(HOSTAPD_IEEE80211AC) \ CONFIG_IEEE80211AX=$(HOSTAPD_IEEE80211AX) \ CONFIG_IEEE80211BE=$(HOSTAPD_IEEE80211BE) \ + CONFIG_AFC=$(HOSTAPD_IEEE80211AX) \ CONFIG_MBO=$(CONFIG_WPA_MBO_SUPPORT) \ CONFIG_UCODE=y CONFIG_APUP=y @@ -152,6 +153,13 @@ endif DRV_DEPENDS:=+libnl-tiny +define Package/afcd + SECTION:=net + CATEGORY:=Network + SUBMENU:=WirelessAPD + TITLE:=AFC communication daemon + DEPENDS:=+ucode +ucode-mod-uclient +ucode-mod-uloop +endef define Package/hostapd/Default SECTION:=net @@ -595,7 +603,7 @@ TARGET_CPPFLAGS := \ -D_GNU_SOURCE \ $(if $(CONFIG_WPA_MSG_MIN_PRIORITY),-DCONFIG_MSG_MIN_PRIORITY=$(CONFIG_WPA_MSG_MIN_PRIORITY)) -TARGET_LDFLAGS += -lubox -lubus -lblobmsg_json -lucode -lm -lnl-tiny -ludebug +TARGET_LDFLAGS += -lubox -lubus -lblobmsg_json -lucode -lm -lnl-tiny -ludebug -ljson-c ifdef CONFIG_WPA_ENABLE_WEP DRIVER_MAKEOPTS += CONFIG_WEP=y @@ -708,6 +716,12 @@ Package/hostapd-wolfssl/conffiles = $(Package/hostapd-full/conffiles) Package/hostapd-mbedtls/conffiles = $(Package/hostapd-full/conffiles) endif +define Package/afcd/install + $(INSTALL_DIR) $(1)/usr/share/hostap $(1)/etc/init.d + $(INSTALL_BIN) ./files/afcd.init $(1)/etc/init.d/afcd + $(INSTALL_DATA) ./files/afcd.uc $(1)/usr/share/hostap/ +endef + define Install/hostapd $(INSTALL_DIR) $(1)/usr/sbin $(1)/usr/share/hostap $(INSTALL_DATA) ./files/hostapd.uc $(1)/usr/share/hostap/ @@ -824,6 +838,7 @@ endif # Build hostapd-common before its dependents, to avoid # spurious rebuilds when building multiple variants. +$(eval $(call BuildPackage,afcd)) $(eval $(call BuildPackage,hostapd-common)) $(eval $(call BuildPackage,hostapd)) $(eval $(call BuildPackage,hostapd-basic)) diff --git a/package/network/services/hostapd/files/afcd.init b/package/network/services/hostapd/files/afcd.init new file mode 100644 index 0000000000..c2cbe91415 --- /dev/null +++ b/package/network/services/hostapd/files/afcd.init @@ -0,0 +1,30 @@ +#!/bin/sh /etc/rc.common + +START=19 + +USE_PROCD=1 +NAME=afcd + +add_afc() { + config_get_bool disabled "$1" disabled 0 + [ "$disabled" -gt 0 ] && return + + config_get url "$1" url + config_get cert "$1" cert + [ -n "$cert" -a -n "$url" ] || return + + procd_open_instance afcd + procd_set_param command /usr/bin/ucode /usr/share/hostap/afcd.uc -u "$url" -c "$cert" + procd_set_param respawn + procd_close_instance +} + +start_service() { + config_load wireless + config_foreach add_afc afc-server +} + +service_triggers() +{ + procd_add_reload_trigger wireless +} diff --git a/package/network/services/hostapd/files/afcd.uc b/package/network/services/hostapd/files/afcd.uc new file mode 100644 index 0000000000..9bb7b841a7 --- /dev/null +++ b/package/network/services/hostapd/files/afcd.uc @@ -0,0 +1,132 @@ +#!/usr/bin/env ucode +'use strict'; +import { basename } from "fs"; +let uclient = require("uclient"); +let uloop = require("uloop"); +let libubus = require("ubus"); +let opts = {}; +let reqs = []; + +const usage_message = `Usage: ${basename(sourcepath())} +Options: + -u : AFC server URL (required) + -c : AFC server CA certificate + +`; + +function usage() { + warn(usage_message); + exit(1); +} + +while (substr(ARGV[0], 0, 1) == "-") { + let opt = substr(shift(ARGV), 1); + switch (opt) { + case 'u': + opts.url = shift(ARGV); + break; + case 'c': + opts.cert = shift(ARGV); + if (!opts.cert) + usage(); + break; + default: + usage(); + } +} + +if (!opts.url) + usage(); + +function request_done(cb, error) +{ + if (!cb.req) + return; + + if (error) + delete cb.data; + + cb.req.reply({ data: cb.data }, error); + + delete cb.req; + delete cb.client; +} + +const cb_proto = { + data_read: function(cb) { + let cur; + while (length(cur = this.read()) > 0) + cb.data += cur; + }, + data_eof: function(cb) { + request_done(cb, 0); + }, + error: function(cb, code) { + request_done(cb, libubus.STATUS_UNKNOWN_ERROR); + }, +}; + +function handle_request(req) +{ + let cb = proto({ data: "" }, cb_proto); + + let cl = uclient.new(opts.url, null, cb); + + if (!cl.ssl_init({ verify: true, ca_files: [ opts.cert ] })) { + warn(`Failed to initialize SSL\n`); + return false; + } + + if (!cl.connect()) { + warn(`Failed to connect\n`); + return false; + } + + let meta = { + headers: { + "Content-Type": "application/json", + }, + post_data: req.args.data + }; + + if (!cl.request("POST", meta)) { + warn(`Failed to send request\n`); + return false; + } + + cb.client = cl; + cb.req = req; + + return true; +} + +function add_ubus(ubus) { + return ubus.publish("afc", { + request: { + call: function(req) { + if (!req.args.data) + return libubus.STATUS_INVALID_ARGUMENT; + + let ret = handle_request(req); + if (!ret) + return libubus.STATUS_UNKNOWN_ERROR; + + req.defer(); + }, + args: { + data: "", + }, + }, + }); +} + +uloop.init(); + +let ubus = libubus.connect(); +if (!add_ubus(ubus)) { + warn("Failed to publish ubus object\n"); + exit(1); +} + +uloop.run(); +uloop.done(); diff --git a/package/network/services/hostapd/files/hostapd.uc b/package/network/services/hostapd/files/hostapd.uc index e345a678f4..e7b8eb4813 100644 --- a/package/network/services/hostapd/files/hostapd.uc +++ b/package/network/services/hostapd/files/hostapd.uc @@ -1094,6 +1094,12 @@ return { hostapd.udebug_set(null); hostapd.ubus.disconnect(); }, + afc_request: function(iface, data) { + let ret = ubus.call("afc", "request", { data }); + if (type(ret) != "object") + return; + return ret.data; + }, bss_create: function(phy, name, obj) { phy = hostapd.data.config[phy]; if (!phy) diff --git a/package/network/services/hostapd/files/wpad_acl.json b/package/network/services/hostapd/files/wpad_acl.json index 755f836b67..275f79c347 100644 --- a/package/network/services/hostapd/files/wpad_acl.json +++ b/package/network/services/hostapd/files/wpad_acl.json @@ -12,6 +12,9 @@ }, "udebug": { "methods": [ "get_config" ] + }, + "afc": { + "methods": [ "request" ] } }, "subscribe": [ "udebug" ], diff --git a/package/network/services/hostapd/patches/800-hostapd-afcd-add-AFC-daemon-support.patch b/package/network/services/hostapd/patches/800-hostapd-afcd-add-AFC-daemon-support.patch new file mode 100644 index 0000000000..705e4b06d7 --- /dev/null +++ b/package/network/services/hostapd/patches/800-hostapd-afcd-add-AFC-daemon-support.patch @@ -0,0 +1,575 @@ +From: Lorenzo Bianconi +Date: Sat, 17 Feb 2024 11:24:44 +0100 +Subject: [PATCH] hostapd: afcd: add AFC daemon support + +Introduce Automated Frequency Coordination Daemon (AFCD) support +for UNII-5 and UNII-7 6GHz bands. +AFCD will be used by hostapd AFC client in order to forward the AFC +request to the AFC coordinator and decouple AFC connection management +from hostapd. +AFC is required for Standard Power Devices (SPDs) to determine a lists +of channels and EIRP/PSD powers that are available in the 6GHz spectrum. +AFCD is tested with AFC DUT Test Harness [0]. +Add afc-reply.json as reference for replies from the AFC coordinator. + +[0] https://github.com/Wi-FiTestSuite/AFC-DUT/tree/main + +Tested-by: Allen Ye +Signed-off-by: Lorenzo Bianconi +--- + create mode 100644 afc/.gitignore + create mode 100644 afc/Makefile + create mode 100644 afc/afc-reply.json + create mode 100644 afc/afcd.c + +--- /dev/null ++++ b/afc/.gitignore +@@ -0,0 +1 @@ ++afcd +--- /dev/null ++++ b/afc/Makefile +@@ -0,0 +1,31 @@ ++ALL=afcd ++ ++include ../src/build.rules ++ ++CFLAGS += -I../src/utils ++CFLAGS += -I../src ++ ++OBJS=afcd.o ++OBJS += ../src/utils/common.o ++OBJS += ../src/utils/wpa_debug.o ++OBJS += ../src/utils/wpabuf.o ++ ++ifndef CONFIG_OS ++ifdef CONFIG_NATIVE_WINDOWS ++CONFIG_OS=win32 ++else ++CONFIG_OS=unix ++endif ++endif ++OBJS += ../src/utils/os_$(CONFIG_OS).o ++ ++LIBS += -lcurl ++ ++_OBJS_VAR := OBJS ++include ../src/objs.mk ++afcd: $(OBJS) ++ $(Q)$(LDO) $(LDFLAGS) -o afcd $(OBJS) $(LIBS) ++ @$(E) " LD " $@ ++ ++clean: common-clean ++ rm -f core *~ +--- /dev/null ++++ b/afc/afc-reply.json +@@ -0,0 +1,215 @@ ++{ ++ "availableSpectrumInquiryResponses":[ ++ { ++ "availabilityExpireTime":"2023-02-23T12:53:18Z", ++ "availableChannelInfo":[ ++ { ++ "channelCfi":[ ++ 1, ++ 5, ++ 9, ++ 13, ++ 17, ++ 21, ++ 25, ++ 29, ++ 33, ++ 37, ++ 41, ++ 45, ++ 49, ++ 53, ++ 57, ++ 61, ++ 65, ++ 69, ++ 73, ++ 77, ++ 81, ++ 85, ++ 89, ++ 93, ++ 117, ++ 121, ++ 125, ++ 129, ++ 133, ++ 137, ++ 141, ++ 145, ++ 149, ++ 153, ++ 157, ++ 161, ++ 165, ++ 169, ++ 173, ++ 177, ++ 181 ++ ], ++ "globalOperatingClass":131, ++ "maxEirp":[ ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5 ++ ] ++ }, ++ { ++ "channelCfi":[ ++ 3, ++ 11, ++ 19, ++ 27, ++ 35, ++ 43, ++ 51, ++ 59, ++ 67, ++ 75, ++ 83, ++ 91, ++ 123, ++ 131, ++ 139, ++ 147, ++ 155, ++ 163, ++ 171, ++ 179 ++ ], ++ "globalOperatingClass":132, ++ "maxEirp":[ ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5 ++ ] ++ }, ++ { ++ "channelCfi":[ ++ 7, ++ 23, ++ 39, ++ 55, ++ 71, ++ 87, ++ 135, ++ 151, ++ 167 ++ ], ++ "globalOperatingClass":133, ++ "maxEirp":[ ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5, ++ 5 ++ ] ++ }, ++ { ++ "channelCfi":[ ++ 15, ++ 47, ++ 79, ++ 143 ++ ], ++ "globalOperatingClass":134, ++ "maxEirp":[ ++ 5, ++ 5, ++ 5, ++ 5 ++ ] ++ }, ++ { ++ "channelCfi":[ ++ ], ++ "globalOperatingClass":135, ++ "maxEirp":[ ++ ] ++ } ++ ], ++ "availableFrequencyInfo":[ ++ { ++ "frequencyRange":{ ++ "highFrequency":6425, ++ "lowFrequency":5925 ++ }, ++ "maxPSD":3.98970004336019 ++ }, ++ { ++ "frequencyRange":{ ++ "highFrequency":6865, ++ "lowFrequency":6525 ++ }, ++ "maxPSD":3.98970004336019 ++ } ++ ], ++ "requestId":"11235814", ++ "response":{ ++ "responseCode":0, ++ "shortDescription":"Success" ++ }, ++ "rulesetId":"US_47_CFR_PART_15_SUBPART_E" ++ } ++ ], ++ "version":"1.1" ++} +--- /dev/null ++++ b/afc/afcd.c +@@ -0,0 +1,292 @@ ++/* ++ * Automated Frequency Coordination Daemon ++ * Copyright (c) 2024, Lorenzo Bianconi ++ * ++ * 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(); ++} diff --git a/package/network/services/hostapd/patches/801-hostapd-export-hostapd_is_usable_chans-utility-routi.patch b/package/network/services/hostapd/patches/801-hostapd-export-hostapd_is_usable_chans-utility-routi.patch new file mode 100644 index 0000000000..87ecdf93ea --- /dev/null +++ b/package/network/services/hostapd/patches/801-hostapd-export-hostapd_is_usable_chans-utility-routi.patch @@ -0,0 +1,43 @@ +From: Lorenzo Bianconi +Date: Tue, 12 Mar 2024 11:03:55 +0100 +Subject: [PATCH] hostapd: export hostapd_is_usable_chans utility routine + +This is a preliminary patch to introduce AFC support. + +Tested-by: Allen Ye +--- + +--- a/src/ap/hw_features.c ++++ b/src/ap/hw_features.c +@@ -1031,7 +1031,7 @@ static bool hostapd_is_usable_punct_bitm + * 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; +--- a/src/ap/hw_features.h ++++ b/src/ap/hw_features.h +@@ -32,6 +32,7 @@ int hostapd_hw_skip_mode(struct hostapd_ + int hostapd_determine_mode(struct hostapd_iface *iface); + void hostapd_free_multi_hw_info(struct hostapd_multi_hw_info *multi_hw_info); + int hostapd_set_current_hw_info(struct hostapd_iface *iface, int oper_freq); ++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, +@@ -115,6 +116,12 @@ static inline int hostapd_set_current_hw + { + return 0; + } ++ ++static inline int hostapd_is_usable_chans(struct hostapd_iface *iface) ++{ ++ return 1; ++} ++ + #endif /* NEED_AP_MLME */ + + #endif /* HW_FEATURES_H */ diff --git a/package/network/services/hostapd/patches/802-hostapd-ap-add-AFC-client-support.patch b/package/network/services/hostapd/patches/802-hostapd-ap-add-AFC-client-support.patch new file mode 100644 index 0000000000..d69982a68b --- /dev/null +++ b/package/network/services/hostapd/patches/802-hostapd-ap-add-AFC-client-support.patch @@ -0,0 +1,1549 @@ +From: Lorenzo Bianconi +Date: Wed, 17 Jan 2024 15:25:08 +0100 +Subject: [PATCH] hostapd: ap: add AFC client support + +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: Allen Ye +--- + create mode 100644 src/ap/afc.c + +--- a/hostapd/Makefile ++++ b/hostapd/Makefile +@@ -109,6 +109,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 +--- a/hostapd/config_file.c ++++ b/hostapd/config_file.c +@@ -1283,6 +1283,190 @@ static int hostapd_parse_he_srg_bitmap(u + 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 */ + + +@@ -3983,6 +4167,83 @@ static int hostapd_config_fill(struct ho + 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) { +--- a/hostapd/defconfig ++++ b/hostapd/defconfig +@@ -438,3 +438,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 +--- a/hostapd/hostapd.conf ++++ b/hostapd/hostapd.conf +@@ -1030,6 +1030,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 +--- /dev/null ++++ b/src/ap/afc.c +@@ -0,0 +1,979 @@ ++/* ++ * 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, toogle 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; ++ } ++} +--- a/src/ap/ap_config.c ++++ b/src/ap/ap_config.c +@@ -1055,6 +1055,22 @@ void hostapd_config_free(struct hostapd_ + #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); + } +--- a/src/ap/ap_config.h ++++ b/src/ap/ap_config.h +@@ -1287,6 +1287,53 @@ struct hostapd_config { + + /* Whether to enable TWT responder in HT and VHT modes */ + bool ht_vht_twt_responder; ++ ++#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 */ + }; + + +--- a/src/ap/hostapd.c ++++ b/src/ap/hostapd.c +@@ -722,6 +722,7 @@ static void sta_track_deinit(struct host + void hostapd_cleanup_iface_partial(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); + #ifdef NEED_AP_MLME + hostapd_stop_setup_timers(iface); +@@ -2615,6 +2616,16 @@ static int hostapd_setup_interface_compl + } + #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, +@@ -3014,6 +3025,7 @@ void hostapd_interface_deinit(struct hos + + 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; +@@ -3087,6 +3099,10 @@ void hostapd_interface_free(struct hosta + __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); + } + +--- a/src/ap/hostapd.h ++++ b/src/ap/hostapd.h +@@ -771,9 +771,54 @@ struct hostapd_iface { + struct hostapd_multi_hw_info *multi_hw_info; + unsigned int num_multi_hws; + struct hostapd_multi_hw_info *current_hw_info; ++ ++#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); +--- a/src/ap/hw_features.c ++++ b/src/ap/hw_features.c +@@ -117,6 +117,8 @@ int hostapd_get_hw_features(struct hosta + 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 && diff --git a/package/network/services/hostapd/patches/803-hostapd-update-TPE-IE-according-to-AFC.patch b/package/network/services/hostapd/patches/803-hostapd-update-TPE-IE-according-to-AFC.patch new file mode 100644 index 0000000000..866cb98e66 --- /dev/null +++ b/package/network/services/hostapd/patches/803-hostapd-update-TPE-IE-according-to-AFC.patch @@ -0,0 +1,149 @@ +From: Lorenzo Bianconi +Date: Wed, 7 Feb 2024 00:08:18 +0100 +Subject: [PATCH] hostapd: update TPE IE according to AFC + +Update Transmit Power Envelope (TPE) IE according to the reply from AFC +coordinator on UNII-5 or UNII-7 6GHz bands. + +Tested-by: Allen Ye +--- + +--- a/src/ap/afc.c ++++ b/src/ap/afc.c +@@ -977,3 +977,40 @@ void hostap_afc_disable_channels(struct + 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; ++} +--- a/src/ap/hostapd.h ++++ b/src/ap/hostapd.h +@@ -801,10 +801,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; +--- a/src/ap/ieee802_11.c ++++ b/src/ap/ieee802_11.c +@@ -7205,42 +7205,53 @@ u8 * hostapd_eid_txpower_envelope(struct + */ + 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 */ + diff --git a/package/network/services/hostapd/patches/804-hostapd_afc_ucode.patch b/package/network/services/hostapd/patches/804-hostapd_afc_ucode.patch new file mode 100644 index 0000000000..d1a952e229 --- /dev/null +++ b/package/network/services/hostapd/patches/804-hostapd_afc_ucode.patch @@ -0,0 +1,23 @@ +--- a/src/ap/afc.c ++++ b/src/ap/afc.c +@@ -737,6 +737,20 @@ static int hostapd_afc_send_receive(stru + return 0; + } + ++#ifdef UCODE_SUPPORT ++ request_obj = hostapd_afc_build_request(iface); ++ if (!request_obj) ++ return -ENOMEM; ++ ++ request = json_object_to_json_string(request_obj); ++ ret = hostapd_ucode_afc_request(iface, request, buf, sizeof(buf)); ++ json_object_put(request_obj); ++ if (ret < 0) ++ return ret; ++ ++ return hostapd_afc_parse_reply(iface, buf); ++#endif ++ + if (!iconf->afc.socket) { + wpa_printf(MSG_ERROR, "Missing AFC socket string"); + return -EINVAL; diff --git a/package/network/services/hostapd/src/src/ap/ucode.c b/package/network/services/hostapd/src/src/ap/ucode.c index e496b8b7aa..131a264ad3 100644 --- a/package/network/services/hostapd/src/src/ap/ucode.c +++ b/package/network/services/hostapd/src/src/ap/ucode.c @@ -921,6 +921,34 @@ void hostapd_ucode_free_iface(struct hostapd_iface *iface) wpa_ucode_registry_remove(iface_registry, iface->ucode.idx); } +int hostapd_ucode_afc_request(struct hostapd_iface *iface, const char *request, + char *buf, size_t len) +{ + uc_value_t *val; + size_t ret_len; + int ret = -1; + + if (wpa_ucode_call_prepare("afc_request")) + return -1; + + uc_value_push(ucv_get(ucv_string_new(iface->phy))); + uc_value_push(ucv_get(ucv_string_new(request))); + val = wpa_ucode_call(2); + if (ucv_type(val) != UC_STRING) + goto out; + + ret_len = ucv_string_length(val); + if (ret_len >= len) + goto out; + + memcpy(buf, ucv_string_get(val), ret_len + 1); + ret = (int)ret_len; + +out: + ucv_put(val); + return ret; +} + void hostapd_ucode_bss_cb(struct hostapd_data *hapd, const char *type) { uc_value_t *val; diff --git a/package/network/services/hostapd/src/src/ap/ucode.h b/package/network/services/hostapd/src/src/ap/ucode.h index d0b00e5965..44e1eff4f7 100644 --- a/package/network/services/hostapd/src/src/ap/ucode.h +++ b/package/network/services/hostapd/src/src/ap/ucode.h @@ -27,6 +27,8 @@ void hostapd_ucode_free_bss(struct hostapd_data *hapd); void hostapd_ucode_bss_cb(struct hostapd_data *hapd, const char *type); int hostapd_ucode_sta_auth(struct hostapd_data *hapd, struct sta_info *sta); void hostapd_ucode_sta_connected(struct hostapd_data *hapd, struct sta_info *sta); +int hostapd_ucode_afc_request(struct hostapd_iface *iface, const char *request, + char *buf, size_t len); #ifdef CONFIG_APUP void hostapd_ucode_apup_newpeer(struct hostapd_data *hapd, const char *ifname);