hostapd: add AFC support master
authorFelix Fietkau <nbd@nbd.name>
Thu, 4 Apr 2024 12:37:23 +0000 (14:37 +0200)
committerFelix Fietkau <nbd@nbd.name>
Thu, 14 Nov 2024 12:04:25 +0000 (13:04 +0100)
Signed-off-by: Felix Fietkau <nbd@nbd.name>
13 files changed:
package/network/config/wifi-scripts/files/lib/netifd/hostapd.sh
package/network/services/hostapd/Makefile
package/network/services/hostapd/files/afcd.init [new file with mode: 0644]
package/network/services/hostapd/files/afcd.uc [new file with mode: 0644]
package/network/services/hostapd/files/hostapd.uc
package/network/services/hostapd/files/wpad_acl.json
package/network/services/hostapd/patches/800-hostapd-afcd-add-AFC-daemon-support.patch [new file with mode: 0644]
package/network/services/hostapd/patches/801-hostapd-export-hostapd_is_usable_chans-utility-routi.patch [new file with mode: 0644]
package/network/services/hostapd/patches/802-hostapd-ap-add-AFC-client-support.patch [new file with mode: 0644]
package/network/services/hostapd/patches/803-hostapd-update-TPE-IE-according-to-AFC.patch [new file with mode: 0644]
package/network/services/hostapd/patches/804-hostapd_afc_ucode.patch [new file with mode: 0644]
package/network/services/hostapd/src/src/ap/ucode.c
package/network/services/hostapd/src/src/ap/ucode.h

index 817ead71afd063467f6e022f122c5a2a625d2ba3..25ac8a33cd62ae20216ec80b3d3f63b28b426b0d 100644 (file)
@@ -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"
index 16dfbff2b06d5cc4686415d4a614f17f12e4b446..740af821921cd0ebf368bcd9f2b81aecca4f3526 100644 (file)
@@ -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 (file)
index 0000000..c2cbe91
--- /dev/null
@@ -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 (file)
index 0000000..9bb7b84
--- /dev/null
@@ -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>
+Options:
+       -u <url>:               AFC server URL (required)
+       -c <path>:              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();
index 2d9ce2874901d22b172244ad1bfb1f0f9a90fe3a..19002b90bbaa86538439ebb62721f8e881617b97 100644 (file)
@@ -1022,6 +1022,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)
index 755f836b67329e3453fa664cbe2e96dd9eed3cf0..275f79c34785066702dcbc91627c76b50f310555 100644 (file)
@@ -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 (file)
index 0000000..705e4b0
--- /dev/null
@@ -0,0 +1,575 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Sat, 17 Feb 2024 11:24:44 +0100
+Subject: [PATCH] hostapd: afcd: add AFC daemon support
+
+Introduce Automated Frequency Coordination Daemon (AFCD) support
+for UNII-5 and UNII-7 6GHz bands.
+AFCD will be used by hostapd AFC client in order to forward the AFC
+request to the AFC coordinator and decouple AFC connection management
+from hostapd.
+AFC is required for Standard Power Devices (SPDs) to determine a lists
+of channels and EIRP/PSD powers that are available in the 6GHz spectrum.
+AFCD is tested with AFC DUT Test Harness [0].
+Add afc-reply.json as reference for replies from the AFC coordinator.
+
+[0] https://github.com/Wi-FiTestSuite/AFC-DUT/tree/main
+
+Tested-by: Allen Ye <allen.ye@mediatek.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+---
+ create mode 100644 afc/.gitignore
+ create mode 100644 afc/Makefile
+ create mode 100644 afc/afc-reply.json
+ create mode 100644 afc/afcd.c
+
+--- /dev/null
++++ b/afc/.gitignore
+@@ -0,0 +1 @@
++afcd
+--- /dev/null
++++ b/afc/Makefile
+@@ -0,0 +1,31 @@
++ALL=afcd
++
++include ../src/build.rules
++
++CFLAGS += -I../src/utils
++CFLAGS += -I../src
++
++OBJS=afcd.o
++OBJS += ../src/utils/common.o
++OBJS += ../src/utils/wpa_debug.o
++OBJS += ../src/utils/wpabuf.o
++
++ifndef CONFIG_OS
++ifdef CONFIG_NATIVE_WINDOWS
++CONFIG_OS=win32
++else
++CONFIG_OS=unix
++endif
++endif
++OBJS += ../src/utils/os_$(CONFIG_OS).o
++
++LIBS += -lcurl
++
++_OBJS_VAR := OBJS
++include ../src/objs.mk
++afcd: $(OBJS)
++      $(Q)$(LDO) $(LDFLAGS) -o afcd $(OBJS) $(LIBS)
++      @$(E) "  LD " $@
++
++clean: common-clean
++      rm -f core *~
+--- /dev/null
++++ b/afc/afc-reply.json
+@@ -0,0 +1,215 @@
++{
++   "availableSpectrumInquiryResponses":[
++      {
++         "availabilityExpireTime":"2023-02-23T12:53:18Z",
++         "availableChannelInfo":[
++            {
++               "channelCfi":[
++                  1,
++                  5,
++                  9,
++                  13,
++                  17,
++                  21,
++                  25,
++                  29,
++                  33,
++                  37,
++                  41,
++                  45,
++                  49,
++                  53,
++                  57,
++                  61,
++                  65,
++                  69,
++                  73,
++                  77,
++                  81,
++                  85,
++                  89,
++                  93,
++                  117,
++                  121,
++                  125,
++                  129,
++                  133,
++                  137,
++                  141,
++                  145,
++                  149,
++                  153,
++                  157,
++                  161,
++                  165,
++                  169,
++                  173,
++                  177,
++                  181
++               ],
++               "globalOperatingClass":131,
++               "maxEirp":[
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5
++               ]
++            },
++            {
++               "channelCfi":[
++                  3,
++                  11,
++                  19,
++                  27,
++                  35,
++                  43,
++                  51,
++                  59,
++                  67,
++                  75,
++                  83,
++                  91,
++                  123,
++                  131,
++                  139,
++                  147,
++                  155,
++                  163,
++                  171,
++                  179
++               ],
++               "globalOperatingClass":132,
++               "maxEirp":[
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5
++               ]
++            },
++            {
++               "channelCfi":[
++                  7,
++                  23,
++                  39,
++                  55,
++                  71,
++                  87,
++                  135,
++                  151,
++                  167
++               ],
++               "globalOperatingClass":133,
++               "maxEirp":[
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5,
++                  5
++               ]
++            },
++            {
++               "channelCfi":[
++                  15,
++                  47,
++                  79,
++                  143
++               ],
++               "globalOperatingClass":134,
++               "maxEirp":[
++                  5,
++                  5,
++                  5,
++                  5
++               ]
++            },
++            {
++               "channelCfi":[
++               ],
++               "globalOperatingClass":135,
++               "maxEirp":[
++               ]
++            }
++         ],
++         "availableFrequencyInfo":[
++            {
++               "frequencyRange":{
++                  "highFrequency":6425,
++                  "lowFrequency":5925
++               },
++               "maxPSD":3.98970004336019
++            },
++            {
++               "frequencyRange":{
++                  "highFrequency":6865,
++                  "lowFrequency":6525
++               },
++               "maxPSD":3.98970004336019
++            }
++         ],
++         "requestId":"11235814",
++         "response":{
++            "responseCode":0,
++            "shortDescription":"Success"
++         },
++         "rulesetId":"US_47_CFR_PART_15_SUBPART_E"
++      }
++   ],
++   "version":"1.1"
++}
+--- /dev/null
++++ b/afc/afcd.c
+@@ -0,0 +1,292 @@
++/*
++ * Automated Frequency Coordination Daemon
++ * Copyright (c) 2024, Lorenzo Bianconi <lorenzo@kernel.org>
++ *
++ * This software may be distributed under the terms of the BSD license.
++ * See README for more details.
++ */
++
++#include <curl/curl.h>
++#include <sys/un.h>
++#include <sys/stat.h>
++
++#include "utils/includes.h"
++#include "utils/common.h"
++
++#define CURL_TIMEOUT  60
++#define AFCD_SOCK     "afcd.sock"
++
++struct curl_ctx {
++      char *buf;
++      size_t buf_len;
++};
++
++static volatile bool exiting;
++
++static char *path = "/var/run";
++static char *bearer_token;
++static char *url;
++static int port = 443;
++
++
++static size_t afcd_curl_cb_write(void *ptr, size_t size, size_t nmemb,
++                               void *userdata)
++{
++      struct curl_ctx *ctx = userdata;
++      char *buf;
++
++      buf = os_realloc(ctx->buf, ctx->buf_len + size * nmemb + 1);
++      if (!buf)
++              return 0;
++
++      ctx->buf = buf;
++      os_memcpy(buf + ctx->buf_len, ptr, size * nmemb);
++      buf[ctx->buf_len + size * nmemb] = '\0';
++      ctx->buf_len += size * nmemb;
++
++      return size * nmemb;
++}
++
++
++static int afcd_send_request(struct curl_ctx *ctx, unsigned char *request)
++{
++      struct curl_slist *headers = NULL;
++      CURL *curl;
++      int ret;
++
++      wpa_printf(MSG_DEBUG, "Sending AFC request to %s", url);
++
++      curl_global_init(CURL_GLOBAL_ALL);
++      curl = curl_easy_init();
++      if (!curl)
++              return -ENOMEM;
++
++      headers  = curl_slist_append(headers, "Accept: application/json");
++      headers  = curl_slist_append(headers,
++                                   "Content-Type: application/json");
++      headers  = curl_slist_append(headers, "charset: utf-8");
++
++      curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
++      curl_easy_setopt(curl, CURLOPT_URL, url);
++      curl_easy_setopt(curl, CURLOPT_PORT, port);
++      curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
++      curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,
++                       afcd_curl_cb_write);
++      curl_easy_setopt(curl, CURLOPT_WRITEDATA, ctx);
++      curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcrp/0.1");
++      curl_easy_setopt(curl, CURLOPT_TIMEOUT, CURL_TIMEOUT);
++      curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
++      if (bearer_token)
++              curl_easy_setopt(curl, CURLOPT_XOAUTH2_BEARER, bearer_token);
++      curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BEARER);
++      curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
++      curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 1L);
++      curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request);
++      curl_easy_setopt(curl, CURLOPT_SSL_VERIFYSTATUS, 1L);
++
++      ret = curl_easy_perform(curl);
++      if (ret != CURLE_OK)
++              wpa_printf(MSG_ERROR, "curl_easy_perform failed: %s",
++                         curl_easy_strerror(ret));
++
++      curl_easy_cleanup(curl);
++      curl_global_cleanup();
++
++      return ret == CURLE_OK ? 0 : -EINVAL;
++}
++
++
++static void handle_term(int sig)
++{
++      wpa_printf(MSG_ERROR, "Received signal %d", sig);
++      exiting = true;
++}
++
++
++static void usage(void)
++{
++      wpa_printf(MSG_ERROR,
++                 "%s:\n"
++                 "afcd -u<url> [-p<port>][-t<token>][-D<unix-sock dir>][-P<PID file>][-dB]",
++                 __func__);
++}
++
++
++#define BUFSIZE               8192
++static int afcd_server_run(void)
++{
++      size_t len = os_strlen(path) + 1 + os_strlen(AFCD_SOCK);
++      struct sockaddr_un addr = {
++              .sun_family = AF_UNIX,
++#ifdef __FreeBSD__
++              .sun_len = sizeof(addr),
++#endif /* __FreeBSD__ */
++      };
++      int sockfd, ret = 0;
++      char *fname = NULL;
++      unsigned char *buf;
++      fd_set read_set;
++
++      if (len >= sizeof(addr.sun_path))
++              return -EINVAL;
++
++      if (mkdir(path, S_IRWXU | S_IRWXG) < 0 && errno != EEXIST)
++              return -EINVAL;
++
++      buf = os_malloc(BUFSIZE);
++      if (!buf)
++              return -ENOMEM;
++
++      fname = os_malloc(len + 1);
++      if (!fname) {
++              ret = -ENOMEM;
++              goto free_buf;
++      }
++
++      os_snprintf(fname, len + 1, "%s/%s", path, AFCD_SOCK);
++      fname[len] = '\0';
++      os_strlcpy(addr.sun_path, fname, sizeof(addr.sun_path));
++
++      sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
++      if (sockfd < 0) {
++              wpa_printf(MSG_ERROR, "Failed creating socket");
++              ret = -errno;
++              goto unlink;
++      }
++
++      if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
++              wpa_printf(MSG_ERROR, "Failed to bind socket");
++              ret = -errno;
++              goto close;
++      }
++
++      if (listen(sockfd, 10) < 0) {
++              wpa_printf(MSG_ERROR, "Failed to listen on socket");
++              ret = -errno;
++              goto close;
++      }
++
++      FD_ZERO(&read_set);
++      while (!exiting) {
++              socklen_t addr_len = sizeof(addr);
++              struct sockaddr_in6 c_addr;
++              struct timeval timeout = {
++                      .tv_sec = 1,
++              };
++              struct curl_ctx ctx = {};
++              int fd;
++
++              FD_SET(sockfd, &read_set);
++              if (select(sockfd + 1, &read_set, NULL, NULL, &timeout) < 0) {
++                      if (errno != EINTR) {
++                              wpa_printf(MSG_ERROR,
++                                         "Select failed on socket");
++                              ret = -errno;
++                              break;
++                      }
++                      continue;
++              }
++
++              if (!FD_ISSET(sockfd, &read_set))
++                      continue;
++
++              fd = accept(sockfd, (struct sockaddr *)&c_addr,
++                          &addr_len);
++              if (fd < 0) {
++                      if (errno != EINTR) {
++                              wpa_printf(MSG_ERROR,
++                                         "Failed accepting connections");
++                              ret = -errno;
++                              break;
++                      }
++                      continue;
++              }
++
++              os_memset(buf, 0, BUFSIZE);
++              if (recv(fd, buf, BUFSIZE - 1, 0) <= 0) {
++                      close(fd);
++                      continue;
++              }
++
++              wpa_printf(MSG_DEBUG, "Received request: %s", buf);
++              if (!afcd_send_request(&ctx, buf)) {
++                      wpa_printf(MSG_DEBUG, "Received reply: %s", ctx.buf);
++                      send(fd, ctx.buf, ctx.buf_len, MSG_NOSIGNAL);
++                      free(ctx.buf);
++              }
++              close(fd);
++      }
++close:
++      close(sockfd);
++unlink:
++      unlink(fname);
++      os_free(fname);
++free_buf:
++      os_free(buf);
++
++      return ret;
++}
++
++
++int main(int argc, char **argv)
++{
++      bool daemonize = false;
++      char *pid_file = NULL;
++
++      if (os_program_init())
++              return -1;
++
++      for (;;) {
++              int c = getopt(argc, argv, "u:p:t:D:P:hdB");
++
++              if (c < 0)
++                      break;
++
++              switch (c) {
++              case 'h':
++                      usage();
++                      return 0;
++              case 'B':
++                      daemonize = true;
++                      break;
++              case 'D':
++                      path = optarg;
++                      break;
++              case 'P':
++                      os_free(pid_file);
++                      pid_file = os_rel2abs_path(optarg);
++                      break;
++              case 'u':
++                      url = optarg;
++                      break;
++              case 'p':
++                      port = atoi(optarg);
++                      break;
++              case 'd':
++                      if (wpa_debug_level > 0)
++                              wpa_debug_level--;
++                      break;
++              case 't':
++                      bearer_token = optarg;
++                      break;
++              default:
++                      usage();
++                      return -EINVAL;
++              }
++      }
++
++      if (!url) {
++              usage();
++              return -EINVAL;
++      }
++
++      if (daemonize && os_daemonize(pid_file)) {
++              wpa_printf(MSG_ERROR, "daemon: %s", strerror(errno));
++              return -EINVAL;
++      }
++
++      signal(SIGTERM, handle_term);
++      signal(SIGINT, handle_term);
++
++      return afcd_server_run();
++}
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 (file)
index 0000000..87ecdf9
--- /dev/null
@@ -0,0 +1,43 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+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 <allen.ye@mediatek.com>
+---
+
+--- 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 (file)
index 0000000..d69982a
--- /dev/null
@@ -0,0 +1,1549 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+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 <allen.ye@mediatek.com>
+---
+ 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 <lorenzo@kernel.org>
++ *
++ * This software may be distributed under the terms of the BSD license.
++ * See README for more details.
++ */
++
++#include <json-c/json.h>
++#include <sys/un.h>
++#include <time.h>
++
++#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 (file)
index 0000000..866cb98
--- /dev/null
@@ -0,0 +1,149 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+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 <allen.ye@mediatek.com>
+---
+
+--- 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 (file)
index 0000000..d1a952e
--- /dev/null
@@ -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;
index 2da2b4dc9387384122fa9eec566f7dca9ac1e3d1..86883ca1850c3501cb7470f21729c7a4405ba8e5 100644 (file)
@@ -881,6 +881,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;
index d0b00e59652a06973f14ed989493ced8e34b81ba..44e1eff4f7d917f4f2aab60eaec31baf0d0dddd4 100644 (file)
@@ -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);