1 From: Lorenzo Bianconi <lorenzo@kernel.org>
2 Date: Wed, 17 Jan 2024 15:25:08 +0100
3 Subject: [PATCH] hostapd: ap: add AFC client support
5 Introduce Automated Frequency Coordination (AFC) support for UNII-5 and
7 AFC client will connect to AFCD providing AP related parameter for AFC
8 coordinator (e.g. geolocation, supported frequencies, ..).
9 AFC is required for Standard Power Devices (SPDs) to determine a lists
10 of channels and EIRP/PSD powers that are available in the 6GHz spectrum.
11 AFC hostapd client is tested with AFC DUT Test Harness [0].
13 [0] https://github.com/Wi-FiTestSuite/AFC-DUT/tree/main
15 Tested-by: Allen Ye <allen.ye@mediatek.com>
17 create mode 100644 src/ap/afc.c
19 --- a/hostapd/Makefile
20 +++ b/hostapd/Makefile
21 @@ -108,6 +108,14 @@ CFLAGS += -DCONFIG_TAXONOMY
22 OBJS += ../src/ap/taxonomy.o
25 +ifdef CONFIG_IEEE80211AX
27 +CFLAGS += -DCONFIG_AFC
28 +OBJS += ../src/ap/afc.o
33 ifdef CONFIG_MODULE_TESTS
34 CFLAGS += -DCONFIG_MODULE_TESTS
35 OBJS += hapd_module_tests.o
36 --- a/hostapd/config_file.c
37 +++ b/hostapd/config_file.c
38 @@ -1283,6 +1283,190 @@ static int hostapd_parse_he_srg_bitmap(u
44 +static int hostapd_afc_parse_cert_ids(struct hostapd_config *conf, char *pos)
46 + struct cert_id *c = NULL;
49 + while (pos && pos[0]) {
52 + c = os_realloc_array(c, count + 1, sizeof(*c));
59 + p = os_strchr(pos, ':');
67 + c[i].rulset = os_malloc(os_strlen(pos) + 1);
71 + os_strlcpy(c[i].rulset, pos, os_strlen(pos) + 1);
73 + p = os_strchr(pos, ',');
77 + c[i].id = os_malloc(os_strlen(pos) + 1);
81 + os_strlcpy(c[i].id, pos, os_strlen(pos) + 1);
85 + conf->afc.n_cert_ids = count;
86 + conf->afc.cert_ids = c;
91 + for (i = 0; i < count; i++) {
92 + os_free(c[i].rulset);
101 +static int hostapd_afc_parse_position_data(struct afc_linear_polygon **data,
102 + unsigned int *n_linear_polygon_data,
105 + struct afc_linear_polygon *d = NULL;
108 + while (pos && pos[0]) {
111 + d = os_realloc_array(d, count + 1, sizeof(*d));
118 + p = os_strchr(pos, ':');
126 + d[i].longitude = strtod(pos, &end);
131 + p = os_strchr(pos, ',');
135 + d[i].latitude = strtod(pos, &end);
142 + *n_linear_polygon_data = count;
153 +static int hostapd_afc_parse_freq_range(struct hostapd_config *conf, char *pos)
155 + struct afc_freq_range *f = NULL;
158 + while (pos && pos[0]) {
161 + f = os_realloc_array(f, count + 1, sizeof(*f));
168 + p = os_strchr(pos, ':');
176 + f[i].low_freq = atoi(pos);
178 + p = os_strchr(pos, ',');
182 + f[i].high_freq = atoi(pos);
186 + conf->afc.n_freq_range = count;
187 + conf->afc.freq_range = f;
197 +static int hostapd_afc_parse_op_class(struct hostapd_config *conf, char *pos)
199 + unsigned int *oc = NULL;
202 + while (pos && pos[0]) {
205 + oc = os_realloc_array(oc, count + 1, sizeof(*oc));
212 + p = os_strchr(pos, ',');
220 + conf->afc.n_op_class = count;
221 + conf->afc.op_class = oc;
225 +#endif /* CONFIG_AFC */
226 #endif /* CONFIG_IEEE80211AX */
229 @@ -3888,6 +4072,83 @@ static int hostapd_config_fill(struct ho
232 bss->unsol_bcast_probe_resp_interval = val;
234 + } else if (os_strcmp(buf, "afcd_sock") == 0) {
235 + conf->afc.socket = os_malloc(os_strlen(pos) + 1);
236 + if (!conf->afc.socket)
239 + os_strlcpy(conf->afc.socket, pos, os_strlen(pos) + 1);
240 + } else if (os_strcmp(buf, "afc_request_version") == 0) {
241 + conf->afc.request.version = os_malloc(os_strlen(pos) + 1);
242 + if (!conf->afc.request.version)
245 + os_strlcpy(conf->afc.request.version, pos, os_strlen(pos) + 1);
246 + } else if (os_strcmp(buf, "afc_request_id") == 0) {
247 + conf->afc.request.id = os_malloc(os_strlen(pos) + 1);
248 + if (!conf->afc.request.id)
251 + os_strlcpy(conf->afc.request.id, pos, os_strlen(pos) + 1);
252 + } else if (os_strcmp(buf, "afc_serial_number") == 0) {
253 + conf->afc.request.sn = os_malloc(os_strlen(pos) + 1);
254 + if (!conf->afc.request.sn)
257 + os_strlcpy(conf->afc.request.sn, pos, os_strlen(pos) + 1);
258 + } else if (os_strcmp(buf, "afc_cert_ids") == 0) {
259 + if (hostapd_afc_parse_cert_ids(conf, pos))
261 + } else if (os_strcmp(buf, "afc_location_type") == 0) {
262 + conf->afc.location.type = atoi(pos);
263 + if (conf->afc.location.type != ELLIPSE &&
264 + conf->afc.location.type != LINEAR_POLYGON &&
265 + conf->afc.location.type != RADIAL_POLYGON)
267 + } else if (os_strcmp(buf, "afc_linear_polygon") == 0) {
268 + if (hostapd_afc_parse_position_data(
269 + &conf->afc.location.linear_polygon_data,
270 + &conf->afc.location.n_linear_polygon_data,
273 + } else if (os_strcmp(buf, "afc_radial_polygon") == 0) {
274 + if (hostapd_afc_parse_position_data(
275 + (struct afc_linear_polygon **)
276 + &conf->afc.location.radial_polygon_data,
277 + &conf->afc.location.n_radial_polygon_data,
280 + } else if (os_strcmp(buf, "afc_major_axis") == 0) {
281 + conf->afc.location.major_axis = atoi(pos);
282 + } else if (os_strcmp(buf, "afc_minor_axis") == 0) {
283 + conf->afc.location.minor_axis = atoi(pos);
284 + } else if (os_strcmp(buf, "afc_orientation") == 0) {
285 + conf->afc.location.orientation = atoi(pos);
286 + } else if (os_strcmp(buf, "afc_height") == 0) {
289 + conf->afc.location.height = strtod(pos, &end);
292 + } else if (os_strcmp(buf, "afc_height_type") == 0) {
293 + conf->afc.location.height_type = os_malloc(os_strlen(pos) + 1);
294 + if (!conf->afc.location.height_type)
297 + os_strlcpy(conf->afc.location.height_type, pos,
298 + os_strlen(pos) + 1);
299 + } else if (os_strcmp(buf, "afc_vertical_tolerance") == 0) {
300 + conf->afc.location.vertical_tolerance = atoi(pos);
301 + } else if (os_strcmp(buf, "afc_min_power") == 0) {
302 + conf->afc.min_power = atoi(pos);
303 + } else if (os_strcmp(buf, "afc_freq_range") == 0) {
304 + if (hostapd_afc_parse_freq_range(conf, pos))
306 + } else if (os_strcmp(buf, "afc_op_class") == 0) {
307 + if (hostapd_afc_parse_op_class(conf, pos))
309 +#endif /* CONFIG_AFC */
310 } else if (os_strcmp(buf, "mbssid") == 0) {
311 int mbssid = atoi(pos);
312 if (mbssid < 0 || mbssid > ENHANCED_MBSSID_ENABLED) {
313 --- a/hostapd/defconfig
314 +++ b/hostapd/defconfig
315 @@ -438,3 +438,6 @@ CONFIG_DPP2=y
317 # Wi-Fi Aware unsynchronized service discovery (NAN USD)
320 +# Enable Automated Frequency Coordination for 6GHz outdoor
322 --- a/hostapd/hostapd.conf
323 +++ b/hostapd/hostapd.conf
324 @@ -1005,6 +1005,48 @@ wmm_ac_vo_acm=0
325 # Valid range: 0..20 TUs; default is 0 (disabled)
326 #unsol_bcast_probe_resp_interval=0
328 +##### Automated Frequency Coordination for 6GHz UNII-5 and UNII-7 bands #######
330 +# AFC daemon connection socket
331 +#afcd_sock=/var/run/afcd.sock
333 +# AFC request identification parameters
334 +#afc_request_version=1.1
335 +#afc_request_id=11235813
336 +#afc_serial_number=abcdefg
337 +#afc_cert_ids=US_47_CFR_PART_15_SUBPART_E:CID000
339 +# AFC location type:
341 +# 1 = linear polygon
342 +# 2 = radial polygon
343 +#afc_location_type=0
345 +# AFC ellipse or linear polygon coordinations
346 +#afc_linear_polygon=-122.984157:37.425056
348 +# AFC radial polygon coordinations
349 +#afc_radial_polygon=118.8:92.76,76.44:87.456,98.56:123.33
351 +# AFC ellipse major/minor axis and orientation
356 +# AFC device elevation parameters
358 +#afc_height_type=AGL
359 +#afc_vertical_tolerance=7
361 +# AFC minimum desired TX power (dbm)
364 +# AFC request frequency ranges
365 +#afc_freq_range=5925:6425,6525:6875
367 +# AFC request operation classes
368 +#afc_op_class=131,132,133,134,136
370 ##### IEEE 802.11be related configuration #####################################
372 #ieee80211be: Whether IEEE 802.11be (EHT) is enabled
377 + * Automated Frequency Coordination
378 + * Copyright (c) 2024, Lorenzo Bianconi <lorenzo@kernel.org>
380 + * This software may be distributed under the terms of the BSD license.
381 + * See README for more details.
384 +#include <json-c/json.h>
388 +#include "utils/includes.h"
389 +#include "utils/common.h"
390 +#include "utils/eloop.h"
391 +#include "hostapd.h"
393 +#include "hw_features.h"
395 +#define HOSTAPD_AFC_RETRY_TIMEOUT 180
396 +#define HOSTAPD_AFC_TIMEOUT 86400 /* 24h */
397 +#define HOSTAPD_AFC_BUFSIZE 4096
399 +static void hostapd_afc_timeout_handler(void *eloop_ctx, void *timeout_ctx);
402 +static struct json_object *
403 +hostapd_afc_build_location_request(struct hostapd_iface *iface)
405 + struct json_object *location_obj, *center_obj, *ellipse_obj;
406 + struct json_object *elevation_obj, *str_obj;
407 + struct hostapd_config *iconf = iface->conf;
408 + bool is_ap_indoor = he_reg_is_indoor(iconf->he_6ghz_reg_pwr_type);
410 + location_obj = json_object_new_object();
414 + if (iconf->afc.location.type != LINEAR_POLYGON) {
415 + struct afc_linear_polygon *lp =
416 + &iconf->afc.location.linear_polygon_data[0];
418 + ellipse_obj = json_object_new_object();
422 + center_obj = json_object_new_object();
426 + json_object_object_add(ellipse_obj, "center", center_obj);
428 + str_obj = json_object_new_double(lp->longitude);
432 + json_object_object_add(center_obj, "longitude", str_obj);
433 + str_obj = json_object_new_double(lp->latitude);
437 + json_object_object_add(center_obj, "latitude", str_obj);
441 + switch (iconf->afc.location.type) {
442 + case LINEAR_POLYGON: {
443 + struct json_object *outer_boundary_obj;
446 + outer_boundary_obj = json_object_new_object();
447 + if (!outer_boundary_obj)
450 + json_object_object_add(location_obj, "linearPolygon",
451 + outer_boundary_obj);
452 + ellipse_obj = json_object_new_array();
456 + json_object_object_add(outer_boundary_obj, "outerBoundary",
459 + i < iconf->afc.location.n_linear_polygon_data; i++) {
460 + struct afc_linear_polygon *lp =
461 + &iconf->afc.location.linear_polygon_data[i];
463 + center_obj = json_object_new_object();
467 + json_object_array_add(ellipse_obj, center_obj);
468 + str_obj = json_object_new_double(lp->longitude);
472 + json_object_object_add(center_obj, "longitude",
474 + str_obj = json_object_new_double(lp->latitude);
478 + json_object_object_add(center_obj, "latitude",
483 + case RADIAL_POLYGON: {
484 + struct json_object *outer_boundary_obj;
487 + json_object_object_add(location_obj, "radialPolygon",
490 + outer_boundary_obj = json_object_new_array();
491 + if (!outer_boundary_obj)
494 + json_object_object_add(ellipse_obj, "outerBoundary",
495 + outer_boundary_obj);
497 + i < iconf->afc.location.n_radial_polygon_data; i++) {
498 + struct afc_radial_polygon *rp =
499 + &iconf->afc.location.radial_polygon_data[i];
500 + struct json_object *angle_obj;
502 + angle_obj = json_object_new_object();
506 + json_object_array_add(outer_boundary_obj, angle_obj);
508 + str_obj = json_object_new_double(rp->angle);
512 + json_object_object_add(angle_obj, "angle", str_obj);
513 + str_obj = json_object_new_double(rp->length);
517 + json_object_object_add(angle_obj, "length", str_obj);
523 + json_object_object_add(location_obj, "ellipse", ellipse_obj);
525 + str_obj = json_object_new_int(iconf->afc.location.major_axis);
529 + json_object_object_add(ellipse_obj, "majorAxis", str_obj);
530 + str_obj = json_object_new_int(iconf->afc.location.minor_axis);
534 + json_object_object_add(ellipse_obj, "minorAxis", str_obj);
535 + str_obj = json_object_new_int(iconf->afc.location.orientation);
539 + json_object_object_add(ellipse_obj, "orientation", str_obj);
543 + elevation_obj = json_object_new_object();
544 + if (!elevation_obj)
547 + json_object_object_add(location_obj, "elevation",
549 + str_obj = json_object_new_double(iconf->afc.location.height);
553 + json_object_object_add(elevation_obj, "height", str_obj);
554 + str_obj = json_object_new_string(iconf->afc.location.height_type);
558 + json_object_object_add(elevation_obj, "heightType", str_obj);
559 + str_obj = json_object_new_int(iconf->afc.location.vertical_tolerance);
563 + json_object_object_add(elevation_obj, "verticalUncertainty",
565 + str_obj = json_object_new_int(is_ap_indoor);
569 + json_object_object_add(location_obj, "indoorDeployment", str_obj);
571 + return location_obj;
574 + json_object_put(location_obj);
579 +static struct json_object * hostapd_afc_get_opclass_chan_list(u8 op_class)
581 + struct json_object *chan_list_obj, *str_obj;
582 + const struct oper_class_map *oper_class;
583 + int chan_offset, chan;
585 + oper_class = get_oper_class(NULL, op_class);
589 + chan_list_obj = json_object_new_array();
590 + if (!chan_list_obj)
593 + switch (op_class) {
594 + case 132: /* 40MHz */
597 + case 133: /* 80MHz */
600 + case 134: /* 160MHz */
608 + for (chan = oper_class->min_chan; chan <= oper_class->max_chan;
609 + chan += oper_class->inc) {
610 + str_obj = json_object_new_int(chan + chan_offset);
612 + json_object_put(chan_list_obj);
615 + json_object_array_add(chan_list_obj, str_obj);
618 + return chan_list_obj;
622 +static struct json_object *
623 +hostapd_afc_build_req_chan_list(struct hostapd_iface *iface)
625 + struct json_object *op_class_list_obj, *str_obj;
626 + struct hostapd_config *iconf = iface->conf;
629 + op_class_list_obj = json_object_new_array();
630 + if (!op_class_list_obj)
633 + for (i = 0; i < iconf->afc.n_op_class; i++) {
634 + struct json_object *op_class_obj, *chan_list_obj;
635 + u8 op_class = iconf->afc.op_class[i];
637 + if (!is_6ghz_op_class(op_class))
640 + op_class_obj = json_object_new_object();
644 + json_object_array_add(op_class_list_obj, op_class_obj);
645 + str_obj = json_object_new_int(op_class);
649 + json_object_object_add(op_class_obj, "globalOperatingClass",
652 + chan_list_obj = hostapd_afc_get_opclass_chan_list(op_class);
653 + if (!chan_list_obj)
656 + json_object_object_add(op_class_obj, "channelCfi",
660 + return op_class_list_obj;
663 + json_object_put(op_class_list_obj);
668 +static struct json_object *
669 +hostapd_afc_build_request(struct hostapd_iface *iface)
671 + struct json_object *l1_obj, *l2_obj, *la1_obj, *la2_obj;
672 + struct json_object *s2_obj, *str_obj, *location_obj;
673 + struct hostapd_config *iconf = iface->conf;
674 + struct json_object *op_class_list_obj;
677 + l1_obj = json_object_new_object();
681 + if (iconf->afc.request.version) {
682 + str_obj = json_object_new_string(iconf->afc.request.version);
686 + json_object_object_add(l1_obj, "version", str_obj);
689 + la1_obj = json_object_new_array();
693 + json_object_object_add(l1_obj, "availableSpectrumInquiryRequests",
695 + l2_obj = json_object_new_object();
699 + json_object_array_add(la1_obj, l2_obj);
700 + if (iconf->afc.request.id) {
701 + str_obj = json_object_new_string(iconf->afc.request.id);
705 + json_object_object_add(l2_obj, "requestId", str_obj);
708 + s2_obj = json_object_new_object();
712 + json_object_object_add(l2_obj, "deviceDescriptor", s2_obj);
713 + if (iconf->afc.request.sn) {
714 + str_obj = json_object_new_string(iconf->afc.request.sn);
718 + json_object_object_add(s2_obj, "serialNumber", str_obj);
721 + la2_obj = json_object_new_array();
725 + json_object_object_add(s2_obj, "certificationId", la2_obj);
726 + for (i = 0; i < iconf->afc.n_cert_ids; i++) {
727 + struct json_object *obj;
729 + obj = json_object_new_object();
733 + json_object_array_add(la2_obj, obj);
735 + json_object_new_string(iconf->afc.cert_ids[i].rulset);
739 + json_object_object_add(obj, "rulesetId", str_obj);
740 + str_obj = json_object_new_string(iconf->afc.cert_ids[i].id);
744 + json_object_object_add(obj, "id", str_obj);
747 + location_obj = hostapd_afc_build_location_request(iface);
751 + json_object_object_add(l2_obj, "location", location_obj);
752 + str_obj = json_object_new_int(iconf->afc.min_power);
756 + json_object_object_add(l2_obj, "minDesiredPower", str_obj);
758 + if (iconf->afc.n_freq_range) {
759 + struct json_object *freq_obj;
761 + freq_obj = json_object_new_array();
765 + json_object_object_add(l2_obj, "inquiredFrequencyRange",
767 + for (i = 0; i < iconf->afc.n_freq_range; i++) {
768 + struct afc_freq_range *fr = &iconf->afc.freq_range[i];
769 + struct json_object *obj;
771 + obj = json_object_new_object();
775 + json_object_array_add(freq_obj, obj);
776 + str_obj = json_object_new_int(fr->low_freq);
780 + json_object_object_add(obj, "lowFrequency", str_obj);
781 + str_obj = json_object_new_int(fr->high_freq);
785 + json_object_object_add(obj, "highFrequency", str_obj);
789 + op_class_list_obj = hostapd_afc_build_req_chan_list(iface);
790 + if (!op_class_list_obj)
793 + json_object_object_add(l2_obj, "inquiredChannels", op_class_list_obj);
795 + wpa_printf(MSG_DEBUG, "Pending AFC request: %s",
796 + json_object_get_string(l1_obj));
801 + json_object_put(l1_obj);
808 +hostad_afc_parse_available_freq_info(struct hostapd_iface *iface,
809 + struct json_object *reply_elem_obj)
811 + struct afc_freq_range_elem *f = NULL;
812 + struct json_object *obj;
815 + if (!json_object_object_get_ex(reply_elem_obj,
816 + "availableFrequencyInfo", &obj))
819 + for (i = 0; i < json_object_array_length(obj); i++) {
820 + struct json_object *range_elem_obj, *freq_range_obj;
821 + struct json_object *high_freq_obj, *low_freq_obj;
822 + struct json_object *max_psd_obj;
824 + range_elem_obj = json_object_array_get_idx(obj, i);
825 + if (!range_elem_obj)
828 + if (!json_object_object_get_ex(range_elem_obj,
833 + if (!json_object_object_get_ex(freq_range_obj,
838 + if (!json_object_object_get_ex(freq_range_obj,
843 + if (!json_object_object_get_ex(range_elem_obj, "maxPsd",
845 + !json_object_object_get_ex(range_elem_obj, "maxPSD",
849 + f = os_realloc_array(f, count + 1, sizeof(*f));
853 + f[count].low_freq = json_object_get_int(low_freq_obj);
854 + f[count].high_freq = json_object_get_int(high_freq_obj);
855 + f[count++].max_psd = json_object_get_int(max_psd_obj);
857 + iface->afc.freq_range = f;
858 + iface->afc.num_freq_range = count;
864 +static int hostad_afc_update_chan_info(struct afc_chan_info_elem **chan_list,
865 + int *chan_list_size, u8 op_class,
866 + int center_chan, int power)
868 + int num_low_subchan, ch, count = *chan_list_size;
869 + struct afc_chan_info_elem *c = *chan_list;
871 + switch (op_class) {
872 + case 132: /* 40MHz */
873 + num_low_subchan = 2;
875 + case 133: /* 80MHz */
876 + num_low_subchan = 6;
878 + case 134: /* 160MHz */
879 + num_low_subchan = 14;
882 + num_low_subchan = 0;
886 + for (ch = center_chan - num_low_subchan;
887 + ch <= center_chan + num_low_subchan; ch += 4) {
890 + for (i = 0; i < count; i++) {
891 + if (c[i].chan == ch)
896 + c = os_realloc_array(c, count + 1, sizeof(*c));
900 + c[count].chan = ch;
901 + c[count++].power = power;
905 + *chan_list_size = count;
913 +hostad_afc_parse_available_chan_info(struct hostapd_iface *iface,
914 + struct json_object *reply_elem_obj)
916 + struct afc_chan_info_elem *c = NULL;
917 + struct json_object *obj;
920 + if (!json_object_object_get_ex(reply_elem_obj,
921 + "availableChannelInfo", &obj))
924 + for (i = 0; i < json_object_array_length(obj); i++) {
925 + struct json_object *range_elem_obj, *op_class_obj;
926 + struct json_object *chan_cfi_obj, *max_eirp_obj;
929 + range_elem_obj = json_object_array_get_idx(obj, i);
930 + if (!range_elem_obj)
933 + if (!json_object_object_get_ex(range_elem_obj,
934 + "globalOperatingClass",
938 + if (!json_object_object_get_ex(range_elem_obj, "maxEirp",
942 + if (!json_object_object_get_ex(range_elem_obj, "channelCfi",
946 + op_class = json_object_get_int(op_class_obj);
948 + ch < json_object_array_length(chan_cfi_obj); ch++) {
949 + struct json_object *pwr_obj;
950 + struct json_object *ch_obj;
951 + int channel, power;
953 + ch_obj = json_object_array_get_idx(chan_cfi_obj, ch);
957 + pwr_obj = json_object_array_get_idx(max_eirp_obj, ch);
961 + channel = json_object_get_int(ch_obj);
962 + power = json_object_get_int(pwr_obj);
964 + hostad_afc_update_chan_info(&c, &count, op_class,
967 + iface->afc.chan_info_list = c;
968 + iface->afc.num_chan_info = count;
975 +static int hostad_afc_get_timeout(struct json_object *obj)
980 + if (sscanf(json_object_get_string(obj), "%d-%d-%dT%d:%d:%dZ",
981 + &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour,
982 + &tm.tm_min, &tm.tm_sec) <= 0)
983 + return HOSTAPD_AFC_TIMEOUT;
985 + tm.tm_year -= 1900;
991 + return now > t ? HOSTAPD_AFC_RETRY_TIMEOUT : (t - now) * 80 / 100;
995 +static int hostapd_afc_parse_reply(struct hostapd_iface *iface, char *reply)
997 + struct json_object *payload_obj, *reply_obj, *version_obj;
998 + struct hostapd_config *iconf = iface->conf;
999 + int i, request_timeout = -1, ret = -EINVAL;
1001 + wpa_printf(MSG_DEBUG, "Received AFC reply: %s", reply);
1002 + payload_obj = json_tokener_parse(reply);
1006 + if (!json_object_object_get_ex(payload_obj, "version", &version_obj))
1009 + if (iconf->afc.request.version &&
1010 + os_strcmp(iconf->afc.request.version,
1011 + json_object_get_string(version_obj)))
1014 + if (!json_object_object_get_ex(payload_obj,
1015 + "availableSpectrumInquiryResponses",
1019 + for (i = 0; i < json_object_array_length(reply_obj); i++) {
1020 + struct json_object *reply_elem_obj, *obj, *status_obj;
1021 + int j, status = -EINVAL;
1023 + reply_elem_obj = json_object_array_get_idx(reply_obj, i);
1024 + if (!reply_elem_obj)
1027 + if (!json_object_object_get_ex(reply_elem_obj, "requestId",
1031 + if (iconf->afc.request.id &&
1032 + os_strcmp(iconf->afc.request.id,
1033 + json_object_get_string(obj)))
1036 + if (!json_object_object_get_ex(reply_elem_obj, "rulesetId",
1040 + for (j = 0; j < iconf->afc.n_cert_ids; j++) {
1041 + if (!os_strcmp(iconf->afc.cert_ids[j].rulset,
1042 + json_object_get_string(obj)))
1046 + if (j == iconf->afc.n_cert_ids)
1049 + if (!json_object_object_get_ex(reply_elem_obj, "response",
1053 + if (json_object_object_get_ex(obj, "shortDescription",
1055 + wpa_printf(MSG_DEBUG, "AFC reply element %d: %s",
1056 + i, json_object_get_string(status_obj));
1058 + if (json_object_object_get_ex(obj, "responseCode",
1060 + status = json_object_get_int(status_obj);
1065 + if (hostad_afc_parse_available_freq_info(iface,
1066 + reply_elem_obj) ||
1067 + hostad_afc_parse_available_chan_info(iface,
1071 + if (json_object_object_get_ex(reply_elem_obj,
1072 + "availabilityExpireTime",
1074 + int timeout = hostad_afc_get_timeout(obj);
1076 + if (request_timeout < 0 || timeout < request_timeout)
1077 + request_timeout = timeout;
1083 + iface->afc.data_valid = true;
1084 + iface->afc.timeout = request_timeout;
1085 + if (iface->afc.timeout < 0)
1086 + iface->afc.timeout = HOSTAPD_AFC_RETRY_TIMEOUT;
1092 +static int hostapd_afc_send_receive(struct hostapd_iface *iface)
1094 + struct hostapd_config *iconf = iface->conf;
1095 + json_object *request_obj = NULL;
1096 + struct timeval sock_timeout = {
1099 + struct sockaddr_un addr = {
1100 + .sun_family = AF_UNIX,
1102 + .sun_len = sizeof(addr),
1103 +#endif /* __FreeBSD__ */
1105 + char buf[HOSTAPD_AFC_BUFSIZE] = {};
1106 + const char *request;
1110 + if (iface->afc.data_valid) {
1111 + /* AFC data already downloaded from the server */
1115 + if (!iconf->afc.socket) {
1116 + wpa_printf(MSG_ERROR, "Missing AFC socket string");
1120 + iface->afc.timeout = HOSTAPD_AFC_RETRY_TIMEOUT;
1121 + if (os_strlen(iconf->afc.socket) >= sizeof(addr.sun_path)) {
1122 + wpa_printf(MSG_ERROR, "Malformed AFC socket string %s",
1123 + iconf->afc.socket);
1127 + os_strlcpy(addr.sun_path, iconf->afc.socket, sizeof(addr.sun_path));
1128 + sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
1130 + wpa_printf(MSG_ERROR, "Failed creating AFC socket");
1134 + if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
1135 + wpa_printf(MSG_ERROR, "Failed connecting AFC socket");
1140 + request_obj = hostapd_afc_build_request(iface);
1141 + if (!request_obj) {
1146 + request = json_object_to_json_string(request_obj);
1147 + if (send(sockfd, request, strlen(request), 0) < 0) {
1148 + wpa_printf(MSG_ERROR, "Failed sending AFC request");
1153 + FD_ZERO(&read_set);
1154 + FD_SET(sockfd, &read_set);
1155 + if (select(sockfd + 1, &read_set, NULL, NULL, &sock_timeout) < 0) {
1156 + wpa_printf(MSG_ERROR, "Select failed on AFC socket");
1161 + if (!FD_ISSET(sockfd, &read_set)) {
1166 + ret = recv(sockfd, buf, sizeof(buf) - 1, 0);
1170 + ret = hostapd_afc_parse_reply(iface, buf);
1172 + json_object_put(request_obj);
1179 +static bool hostapd_afc_has_usable_chans(struct hostapd_iface *iface)
1181 + const struct oper_class_map *oper_class;
1184 + oper_class = get_oper_class(NULL, iface->conf->op_class);
1188 + for (ch = oper_class->min_chan; ch <= oper_class->max_chan;
1189 + ch += oper_class->inc) {
1190 + struct hostapd_hw_modes *mode = iface->current_mode;
1193 + for (i = 0; i < mode->num_channels; i++) {
1194 + struct hostapd_channel_data *chan = &mode->channels[i];
1196 + if (chan->chan == ch &&
1197 + !(chan->flag & HOSTAPD_CHAN_DISABLED))
1206 +int hostapd_afc_handle_request(struct hostapd_iface *iface)
1208 + struct hostapd_config *iconf = iface->conf;
1211 + /* AFC is required just for standard power AP */
1212 + if (!he_reg_is_sp(iconf->he_6ghz_reg_pwr_type))
1215 + if (!is_6ghz_op_class(iconf->op_class) || !is_6ghz_freq(iface->freq))
1218 + if (iface->state == HAPD_IFACE_ACS)
1221 + ret = hostapd_afc_send_receive(iface);
1224 + * If the connection to the AFCD failed, resched for a
1227 + wpa_printf(MSG_ERROR, "AFC connection failed: %d", ret);
1233 + hostap_afc_disable_channels(iface);
1234 + if (!hostapd_afc_has_usable_chans(iface))
1237 + /* Trigger an ACS freq scan */
1238 + iconf->channel = 0;
1241 + if (acs_init(iface) != HOSTAPD_CHAN_ACS) {
1242 + wpa_printf(MSG_ERROR, "Could not start ACS");
1247 + eloop_cancel_timeout(hostapd_afc_timeout_handler, iface, NULL);
1248 + eloop_register_timeout(iface->afc.timeout, 0,
1249 + hostapd_afc_timeout_handler, iface, NULL);
1255 +static void hostapd_afc_delete_data_from_server(struct hostapd_iface *iface)
1257 + os_free(iface->afc.chan_info_list);
1258 + os_free(iface->afc.freq_range);
1260 + iface->afc.num_freq_range = 0;
1261 + iface->afc.num_chan_info = 0;
1263 + iface->afc.chan_info_list = NULL;
1264 + iface->afc.freq_range = NULL;
1266 + iface->afc.data_valid = false;
1270 +static void hostapd_afc_timeout_handler(void *eloop_ctx, void *timeout_ctx)
1272 + struct hostapd_iface *iface = eloop_ctx;
1273 + bool restart_iface = true;
1275 + hostapd_afc_delete_data_from_server(iface);
1276 + if (iface->state != HAPD_IFACE_ENABLED) {
1277 + /* Hostapd is not fully enabled yet, toogle the interface */
1278 + goto restart_interface;
1281 + if (hostapd_afc_send_receive(iface) < 0 ||
1282 + hostapd_get_hw_features(iface)) {
1283 + restart_iface = false;
1284 + goto restart_interface;
1287 + if (hostapd_is_usable_chans(iface))
1290 + restart_iface = hostapd_afc_has_usable_chans(iface);
1291 + if (restart_iface) {
1292 + /* Trigger an ACS freq scan */
1293 + iface->conf->channel = 0;
1298 + hostapd_disable_iface(iface);
1299 + if (restart_iface)
1300 + hostapd_enable_iface(iface);
1302 + eloop_register_timeout(iface->afc.timeout, 0,
1303 + hostapd_afc_timeout_handler, iface, NULL);
1307 +void hostapd_afc_stop(struct hostapd_iface *iface)
1309 + eloop_cancel_timeout(hostapd_afc_timeout_handler, iface, NULL);
1313 +void hostap_afc_disable_channels(struct hostapd_iface *iface)
1315 + struct hostapd_hw_modes *mode;
1318 + if (iface->num_hw_features < HOSTAPD_MODE_IEEE80211A)
1321 + if (!he_reg_is_sp(iface->conf->he_6ghz_reg_pwr_type))
1324 + if (!iface->afc.data_valid)
1327 + mode = &iface->hw_features[HOSTAPD_MODE_IEEE80211A];
1328 + for (i = 0; i < mode->num_channels; i++) {
1329 + struct hostapd_channel_data *chan = &mode->channels[i];
1332 + if (!is_6ghz_freq(chan->freq))
1335 + for (j = 0; j < iface->afc.num_freq_range; j++) {
1336 + if (chan->freq >= iface->afc.freq_range[j].low_freq &&
1337 + chan->freq <= iface->afc.freq_range[j].high_freq)
1341 + if (j != iface->afc.num_freq_range)
1344 + for (j = 0; j < iface->afc.num_chan_info; j++) {
1345 + if (chan->chan == iface->afc.chan_info_list[j].chan)
1349 + if (j != iface->afc.num_chan_info)
1352 + chan->flag |= HOSTAPD_CHAN_DISABLED;
1355 --- a/src/ap/ap_config.c
1356 +++ b/src/ap/ap_config.c
1357 @@ -1033,6 +1033,22 @@ void hostapd_config_free(struct hostapd_
1358 #endif /* CONFIG_ACS */
1359 wpabuf_free(conf->lci);
1360 wpabuf_free(conf->civic);
1362 + os_free(conf->afc.socket);
1363 + os_free(conf->afc.request.version);
1364 + os_free(conf->afc.request.id);
1365 + os_free(conf->afc.request.sn);
1366 + for (i = 0; i < conf->afc.n_cert_ids; i++) {
1367 + os_free(conf->afc.cert_ids[i].rulset);
1368 + os_free(conf->afc.cert_ids[i].id);
1370 + os_free(conf->afc.cert_ids);
1371 + os_free(conf->afc.location.height_type);
1372 + os_free(conf->afc.location.linear_polygon_data);
1373 + os_free(conf->afc.location.radial_polygon_data);
1374 + os_free(conf->afc.freq_range);
1375 + os_free(conf->afc.op_class);
1376 +#endif /* CONFIG_AFC */
1380 --- a/src/ap/ap_config.h
1381 +++ b/src/ap/ap_config.h
1382 @@ -1225,6 +1225,53 @@ struct hostapd_config {
1384 ENHANCED_MBSSID_ENABLED = 2,
1395 + unsigned int n_cert_ids;
1401 + enum afc_location_type {
1406 + unsigned int n_linear_polygon_data;
1407 + struct afc_linear_polygon {
1410 + } *linear_polygon_data;
1411 + unsigned int n_radial_polygon_data;
1412 + struct afc_radial_polygon {
1415 + } *radial_polygon_data;
1420 + char *height_type;
1421 + int vertical_tolerance;
1423 + unsigned int n_freq_range;
1424 + struct afc_freq_range {
1428 + unsigned int n_op_class;
1429 + unsigned int *op_class;
1432 +#endif /* CONFIG_AFC */
1436 --- a/src/ap/hostapd.c
1437 +++ b/src/ap/hostapd.c
1438 @@ -683,6 +683,7 @@ static void sta_track_deinit(struct host
1439 void hostapd_cleanup_iface_partial(struct hostapd_iface *iface)
1441 wpa_printf(MSG_DEBUG, "%s(%p)", __func__, iface);
1442 + hostapd_afc_stop(iface);
1443 eloop_cancel_timeout(channel_list_update_timeout, iface, NULL);
1445 hostapd_stop_setup_timers(iface);
1446 @@ -2449,6 +2450,16 @@ static int hostapd_setup_interface_compl
1448 #endif /* CONFIG_MESH */
1450 +#ifdef CONFIG_IEEE80211AX
1451 + /* check AFC for 6GHz channels. */
1452 + res = hostapd_afc_handle_request(iface);
1458 +#endif /* CONFIG_IEEE80211AX */
1460 if (!delay_apply_cfg &&
1461 hostapd_set_freq(hapd, hapd->iconf->hw_mode, iface->freq,
1462 hapd->iconf->channel,
1463 @@ -2846,6 +2857,7 @@ void hostapd_interface_deinit(struct hos
1465 hostapd_set_state(iface, HAPD_IFACE_DISABLED);
1467 + hostapd_afc_stop(iface);
1468 eloop_cancel_timeout(channel_list_update_timeout, iface, NULL);
1469 iface->wait_channel_update = 0;
1470 iface->is_no_ir = false;
1471 @@ -2881,6 +2893,10 @@ void hostapd_interface_free(struct hosta
1472 __func__, iface->bss[j]);
1473 os_free(iface->bss[j]);
1476 + os_free(iface->afc.chan_info_list);
1477 + os_free(iface->afc.freq_range);
1479 hostapd_cleanup_iface(iface);
1482 --- a/src/ap/hostapd.h
1483 +++ b/src/ap/hostapd.h
1484 @@ -702,9 +702,54 @@ struct hostapd_iface {
1486 /* Configured freq of interface is NO_IR */
1492 + unsigned int num_freq_range;
1493 + struct afc_freq_range_elem {
1497 + * max eirp power spectral density received from
1498 + * the AFC coordinator for this band
1502 + unsigned int num_chan_info;
1503 + struct afc_chan_info_elem {
1506 + * max eirp power received from the AFC coordinator
1507 + * for this channel
1510 + } *chan_info_list;
1513 +#endif /* CONFIG_AFC */
1518 +int hostapd_afc_handle_request(struct hostapd_iface *iface);
1519 +void hostapd_afc_stop(struct hostapd_iface *iface);
1520 +void hostap_afc_disable_channels(struct hostapd_iface *iface);
1522 +static inline int hostapd_afc_handle_request(struct hostapd_iface *iface)
1527 +static inline void hostapd_afc_stop(struct hostapd_iface *iface)
1531 +static inline void hostap_afc_disable_channels(struct hostapd_iface *iface)
1534 +#endif /* CONFIG_AFC */
1536 int hostapd_for_each_interface(struct hapd_interfaces *interfaces,
1537 int (*cb)(struct hostapd_iface *iface,
1538 void *ctx), void *ctx);
1539 --- a/src/ap/hw_features.c
1540 +++ b/src/ap/hw_features.c
1541 @@ -114,6 +114,8 @@ int hostapd_get_hw_features(struct hosta
1542 iface->hw_features = modes;
1543 iface->num_hw_features = num_modes;
1545 + hostap_afc_disable_channels(iface);
1547 for (i = 0; i < num_modes; i++) {
1548 struct hostapd_hw_modes *feature = &modes[i];
1549 int dfs_enabled = hapd->iconf->ieee80211h &&