blob: 0dce399f5ea956e58fac06ceb868b71b2327e019 [file] [log] [blame]
developer05f3b2b2024-08-19 19:17:34 +08001From 3bd1d33a94f25337fd70df60ee5a42a60f95cba9 Mon Sep 17 00:00:00 2001
2From: Lorenzo Bianconi <lorenzo@kernel.org>
3Date: Fri, 17 May 2024 11:50:27 +0200
4Subject: [PATCH 007/126] hostapd: ap: add AFC client support
developer66e89bc2024-04-23 14:50:01 +08005
6Introduce Automated Frequency Coordination (AFC) support for UNII-5 and
7UNII-7 6GHz bands.
8AFC client will connect to AFCD providing AP related parameter for AFC
9coordinator (e.g. geolocation, supported frequencies, ..).
10AFC is required for Standard Power Devices (SPDs) to determine a lists
11of channels and EIRP/PSD powers that are available in the 6GHz spectrum.
12AFC hostapd client is tested with AFC DUT Test Harness [0].
13
14[0] https://github.com/Wi-FiTestSuite/AFC-DUT/tree/main
15
developer05f3b2b2024-08-19 19:17:34 +080016Tested-by: Felix Fietkau <nbd@nbd.name>
developer66e89bc2024-04-23 14:50:01 +080017Tested-by: Allen Ye <allen.ye@mediatek.com>
developer05f3b2b2024-08-19 19:17:34 +080018Tested-by: Krishna Chaitanya <chaitanya.mgit@gmail.com>
developer66e89bc2024-04-23 14:50:01 +080019Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
20---
developer05f3b2b2024-08-19 19:17:34 +080021 hostapd/Makefile | 8 +
22 hostapd/config_file.c | 261 +++++++++++
23 hostapd/defconfig | 3 +
24 hostapd/hostapd.conf | 42 ++
25 src/ap/afc.c | 1041 +++++++++++++++++++++++++++++++++++++++++
26 src/ap/ap_config.c | 16 +
27 src/ap/ap_config.h | 47 ++
28 src/ap/hostapd.c | 16 +
29 src/ap/hostapd.h | 45 ++
30 src/ap/hw_features.c | 2 +
31 10 files changed, 1481 insertions(+)
developer66e89bc2024-04-23 14:50:01 +080032 create mode 100644 src/ap/afc.c
33
34diff --git a/hostapd/Makefile b/hostapd/Makefile
developer05f3b2b2024-08-19 19:17:34 +080035index ca4439234..78171025e 100644
developer66e89bc2024-04-23 14:50:01 +080036--- a/hostapd/Makefile
37+++ b/hostapd/Makefile
developer05f3b2b2024-08-19 19:17:34 +080038@@ -104,6 +104,14 @@ CFLAGS += -DCONFIG_TAXONOMY
developer66e89bc2024-04-23 14:50:01 +080039 OBJS += ../src/ap/taxonomy.o
40 endif
41
developer05f3b2b2024-08-19 19:17:34 +080042+ifdef CONFIG_IEEE80211AX
developer66e89bc2024-04-23 14:50:01 +080043+ifdef CONFIG_AFC
44+CFLAGS += -DCONFIG_AFC
45+OBJS += ../src/ap/afc.o
46+LIBS += -ljson-c
47+endif
developer05f3b2b2024-08-19 19:17:34 +080048+endif
developer66e89bc2024-04-23 14:50:01 +080049+
50 ifdef CONFIG_MODULE_TESTS
51 CFLAGS += -DCONFIG_MODULE_TESTS
52 OBJS += hapd_module_tests.o
53diff --git a/hostapd/config_file.c b/hostapd/config_file.c
developer05f3b2b2024-08-19 19:17:34 +080054index 96f1b1749..a86621ed9 100644
developer66e89bc2024-04-23 14:50:01 +080055--- a/hostapd/config_file.c
56+++ b/hostapd/config_file.c
developer05f3b2b2024-08-19 19:17:34 +080057@@ -1281,6 +1281,190 @@ static int hostapd_parse_he_srg_bitmap(u8 *bitmap, char *val)
58 return 0;
59 }
developer66e89bc2024-04-23 14:50:01 +080060
developer05f3b2b2024-08-19 19:17:34 +080061+
developer66e89bc2024-04-23 14:50:01 +080062+#ifdef CONFIG_AFC
63+static int hostapd_afc_parse_cert_ids(struct hostapd_config *conf, char *pos)
64+{
65+ struct cert_id *c = NULL;
66+ int i, count = 0;
67+
68+ while (pos && pos[0]) {
69+ char *p;
70+
71+ c = os_realloc_array(c, count + 1, sizeof(*c));
72+ if (!c)
73+ return -ENOMEM;
74+
75+ i = count;
76+ count++;
77+
78+ p = os_strchr(pos, ':');
79+ if (!p)
80+ goto error;
81+
82+ *p++ = '\0';
83+ if (!p || !p[0])
84+ goto error;
85+
86+ c[i].rulset = os_malloc(os_strlen(pos) + 1);
87+ if (!c[i].rulset)
88+ goto error;
89+
90+ os_strlcpy(c[i].rulset, pos, os_strlen(pos) + 1);
91+ pos = p;
92+ p = os_strchr(pos, ',');
93+ if (p)
94+ *p++ = '\0';
95+
96+ c[i].id = os_malloc(os_strlen(pos) + 1);
97+ if (!c[i].id)
98+ goto error;
99+
100+ os_strlcpy(c[i].id, pos, os_strlen(pos) + 1);
101+ pos = p;
102+ }
103+
104+ conf->afc.n_cert_ids = count;
105+ conf->afc.cert_ids = c;
106+
107+ return 0;
108+
109+error:
110+ for (i = 0; i < count; i++) {
111+ os_free(c[i].rulset);
112+ os_free(c[i].id);
113+ }
114+ os_free(c);
115+
116+ return -ENOMEM;
117+}
118+
119+
120+static int hostapd_afc_parse_position_data(struct afc_linear_polygon **data,
121+ unsigned int *n_linear_polygon_data,
122+ char *pos)
123+{
124+ struct afc_linear_polygon *d = NULL;
125+ int i, count = 0;
126+
127+ while (pos && pos[0]) {
128+ char *p, *end;
129+
130+ d = os_realloc_array(d, count + 1, sizeof(*d));
131+ if (!d)
132+ return -ENOMEM;
133+
134+ i = count;
135+ count++;
136+
137+ p = os_strchr(pos, ':');
138+ if (!p)
139+ goto error;
140+
141+ *p++ = '\0';
142+ if (!p || !p[0])
143+ goto error;
144+
145+ d[i].longitude = strtod(pos, &end);
146+ if (*end)
147+ goto error;
148+
149+ pos = p;
150+ p = os_strchr(pos, ',');
151+ if (p)
152+ *p++ = '\0';
153+
154+ d[i].latitude = strtod(pos, &end);
155+ if (*end)
156+ goto error;
157+
158+ pos = p;
159+ }
160+
161+ *n_linear_polygon_data = count;
162+ *data = d;
163+
164+ return 0;
165+
166+error:
167+ os_free(d);
168+ return -ENOMEM;
169+}
170+
171+
172+static int hostapd_afc_parse_freq_range(struct hostapd_config *conf, char *pos)
173+{
174+ struct afc_freq_range *f = NULL;
175+ int i, count = 0;
176+
177+ while (pos && pos[0]) {
178+ char *p;
179+
180+ f = os_realloc_array(f, count + 1, sizeof(*f));
181+ if (!f)
182+ return -ENOMEM;
183+
184+ i = count;
185+ count++;
186+
187+ p = os_strchr(pos, ':');
188+ if (!p)
189+ goto error;
190+
191+ *p++ = '\0';
192+ if (!p || !p[0])
193+ goto error;
194+
195+ f[i].low_freq = atoi(pos);
196+ pos = p;
197+ p = os_strchr(pos, ',');
198+ if (p)
199+ *p++ = '\0';
200+
201+ f[i].high_freq = atoi(pos);
202+ pos = p;
203+ }
204+
205+ conf->afc.n_freq_range = count;
206+ conf->afc.freq_range = f;
207+
208+ return 0;
209+
210+error:
211+ os_free(f);
212+ return -ENOMEM;
213+}
214+
215+
216+static int hostapd_afc_parse_op_class(struct hostapd_config *conf, char *pos)
217+{
218+ unsigned int *oc = NULL;
219+ int i, count = 0;
220+
221+ while (pos && pos[0]) {
222+ char *p;
223+
224+ oc = os_realloc_array(oc, count + 1, sizeof(*oc));
225+ if (!oc)
226+ return -ENOMEM;
227+
228+ i = count;
229+ count++;
230+
231+ p = os_strchr(pos, ',');
232+ if (p)
233+ *p++ = '\0';
234+
235+ oc[i] = atoi(pos);
236+ pos = p;
237+ }
238+
239+ conf->afc.n_op_class = count;
240+ conf->afc.op_class = oc;
241+
242+ return 0;
243+}
244+#endif /* CONFIG_AFC */
developer05f3b2b2024-08-19 19:17:34 +0800245 #endif /* CONFIG_IEEE80211AX */
246
247
248@@ -3955,6 +4139,83 @@ static int hostapd_config_fill(struct hostapd_config *conf,
developer66e89bc2024-04-23 14:50:01 +0800249 return 1;
250 }
251 bss->unsol_bcast_probe_resp_interval = val;
252+#ifdef CONFIG_AFC
253+ } else if (os_strcmp(buf, "afcd_sock") == 0) {
254+ conf->afc.socket = os_malloc(os_strlen(pos) + 1);
255+ if (!conf->afc.socket)
256+ return 1;
257+
258+ os_strlcpy(conf->afc.socket, pos, os_strlen(pos) + 1);
259+ } else if (os_strcmp(buf, "afc_request_version") == 0) {
260+ conf->afc.request.version = os_malloc(os_strlen(pos) + 1);
261+ if (!conf->afc.request.version)
262+ return 1;
263+
264+ os_strlcpy(conf->afc.request.version, pos, os_strlen(pos) + 1);
265+ } else if (os_strcmp(buf, "afc_request_id") == 0) {
266+ conf->afc.request.id = os_malloc(os_strlen(pos) + 1);
267+ if (!conf->afc.request.id)
268+ return 1;
269+
270+ os_strlcpy(conf->afc.request.id, pos, os_strlen(pos) + 1);
271+ } else if (os_strcmp(buf, "afc_serial_number") == 0) {
272+ conf->afc.request.sn = os_malloc(os_strlen(pos) + 1);
273+ if (!conf->afc.request.sn)
274+ return 1;
275+
276+ os_strlcpy(conf->afc.request.sn, pos, os_strlen(pos) + 1);
277+ } else if (os_strcmp(buf, "afc_cert_ids") == 0) {
278+ if (hostapd_afc_parse_cert_ids(conf, pos))
279+ return 1;
280+ } else if (os_strcmp(buf, "afc_location_type") == 0) {
281+ conf->afc.location.type = atoi(pos);
282+ if (conf->afc.location.type != ELLIPSE &&
283+ conf->afc.location.type != LINEAR_POLYGON &&
284+ conf->afc.location.type != RADIAL_POLYGON)
285+ return 1;
286+ } else if (os_strcmp(buf, "afc_linear_polygon") == 0) {
287+ if (hostapd_afc_parse_position_data(
288+ &conf->afc.location.linear_polygon_data,
289+ &conf->afc.location.n_linear_polygon_data,
290+ pos))
291+ return 1;
292+ } else if (os_strcmp(buf, "afc_radial_polygon") == 0) {
293+ if (hostapd_afc_parse_position_data(
294+ (struct afc_linear_polygon **)
295+ &conf->afc.location.radial_polygon_data,
296+ &conf->afc.location.n_radial_polygon_data,
297+ pos))
298+ return 1;
299+ } else if (os_strcmp(buf, "afc_major_axis") == 0) {
300+ conf->afc.location.major_axis = atoi(pos);
301+ } else if (os_strcmp(buf, "afc_minor_axis") == 0) {
302+ conf->afc.location.minor_axis = atoi(pos);
303+ } else if (os_strcmp(buf, "afc_orientation") == 0) {
304+ conf->afc.location.orientation = atoi(pos);
305+ } else if (os_strcmp(buf, "afc_height") == 0) {
306+ char *end;
307+
308+ conf->afc.location.height = strtod(pos, &end);
309+ if (*end)
310+ return 1;
311+ } else if (os_strcmp(buf, "afc_height_type") == 0) {
312+ conf->afc.location.height_type = os_malloc(os_strlen(pos) + 1);
313+ if (!conf->afc.location.height_type)
314+ return 1;
315+
316+ os_strlcpy(conf->afc.location.height_type, pos,
317+ os_strlen(pos) + 1);
318+ } else if (os_strcmp(buf, "afc_vertical_tolerance") == 0) {
319+ conf->afc.location.vertical_tolerance = atoi(pos);
320+ } else if (os_strcmp(buf, "afc_min_power") == 0) {
321+ conf->afc.min_power = atoi(pos);
322+ } else if (os_strcmp(buf, "afc_freq_range") == 0) {
323+ if (hostapd_afc_parse_freq_range(conf, pos))
324+ return 1;
325+ } else if (os_strcmp(buf, "afc_op_class") == 0) {
326+ if (hostapd_afc_parse_op_class(conf, pos))
327+ return 1;
328+#endif /* CONFIG_AFC */
329 } else if (os_strcmp(buf, "mbssid") == 0) {
330 int mbssid = atoi(pos);
331 if (mbssid < 0 || mbssid > ENHANCED_MBSSID_ENABLED) {
332diff --git a/hostapd/defconfig b/hostapd/defconfig
333index 550db697b..66bf894eb 100644
334--- a/hostapd/defconfig
335+++ b/hostapd/defconfig
336@@ -425,3 +425,6 @@ CONFIG_DPP2=y
337
338 # Wi-Fi Aware unsynchronized service discovery (NAN USD)
339 #CONFIG_NAN_USD=y
340+
341+# Enable Automated Frequency Coordination for 6GHz outdoor
342+#CONFIG_AFC=y
343diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf
developer05f3b2b2024-08-19 19:17:34 +0800344index 93524cf5d..56442c69d 100644
developer66e89bc2024-04-23 14:50:01 +0800345--- a/hostapd/hostapd.conf
346+++ b/hostapd/hostapd.conf
developer05f3b2b2024-08-19 19:17:34 +0800347@@ -1030,6 +1030,48 @@ wmm_ac_vo_acm=0
developer66e89bc2024-04-23 14:50:01 +0800348 # Valid range: 0..20 TUs; default is 0 (disabled)
349 #unsol_bcast_probe_resp_interval=0
350
351+##### Automated Frequency Coordination for 6GHz UNII-5 and UNII-7 bands #######
352+
353+# AFC daemon connection socket
354+#afcd_sock=/var/run/afcd.sock
355+
356+# AFC request identification parameters
357+#afc_request_version=1.1
358+#afc_request_id=11235813
359+#afc_serial_number=abcdefg
360+#afc_cert_ids=US_47_CFR_PART_15_SUBPART_E:CID000
361+#
362+# AFC location type:
363+# 0 = ellipse
364+# 1 = linear polygon
365+# 2 = radial polygon
366+#afc_location_type=0
367+#
368+# AFC ellipse or linear polygon coordinations
369+#afc_linear_polygon=-122.984157:37.425056
370+#
371+# AFC radial polygon coordinations
372+#afc_radial_polygon=118.8:92.76,76.44:87.456,98.56:123.33
373+#
374+# AFC ellipse major/minor axis and orientation
375+#afc_major_axis=100
376+#afc_minor_axis=50
377+#afc_orientation=70
378+#
379+# AFC device elevation parameters
380+#afc_height=3.0
381+#afc_height_type=AGL
382+#afc_vertical_tolerance=7
383+#
384+# AFC minimum desired TX power (dbm)
385+#afc_min_power=24
386+#
387+# AFC request frequency ranges
388+#afc_freq_range=5925:6425,6525:6875
389+#
390+# AFC request operation classes
391+#afc_op_class=131,132,133,134,136
392+
393 ##### IEEE 802.11be related configuration #####################################
394
395 #ieee80211be: Whether IEEE 802.11be (EHT) is enabled
396diff --git a/src/ap/afc.c b/src/ap/afc.c
397new file mode 100644
developer05f3b2b2024-08-19 19:17:34 +0800398index 000000000..cfee83fe7
developer66e89bc2024-04-23 14:50:01 +0800399--- /dev/null
400+++ b/src/ap/afc.c
developer05f3b2b2024-08-19 19:17:34 +0800401@@ -0,0 +1,1041 @@
developer66e89bc2024-04-23 14:50:01 +0800402+/*
403+ * Automated Frequency Coordination
404+ * Copyright (c) 2024, Lorenzo Bianconi <lorenzo@kernel.org>
405+ *
406+ * This software may be distributed under the terms of the BSD license.
407+ * See README for more details.
408+ */
409+
410+#include <json-c/json.h>
411+#include <sys/un.h>
412+#include <time.h>
413+
414+#include "utils/includes.h"
415+#include "utils/common.h"
416+#include "utils/eloop.h"
417+#include "hostapd.h"
418+#include "acs.h"
419+#include "hw_features.h"
420+
421+#define HOSTAPD_AFC_RETRY_TIMEOUT 180
422+#define HOSTAPD_AFC_TIMEOUT 86400 /* 24h */
developer05f3b2b2024-08-19 19:17:34 +0800423+#define HOSTAPD_AFC_BUFSIZE 8192
developer66e89bc2024-04-23 14:50:01 +0800424+
425+static void hostapd_afc_timeout_handler(void *eloop_ctx, void *timeout_ctx);
426+
427+
428+static struct json_object *
429+hostapd_afc_build_location_request(struct hostapd_iface *iface)
430+{
431+ struct json_object *location_obj, *center_obj, *ellipse_obj;
432+ struct json_object *elevation_obj, *str_obj;
433+ struct hostapd_config *iconf = iface->conf;
434+ bool is_ap_indoor = he_reg_is_indoor(iconf->he_6ghz_reg_pwr_type);
435+
436+ location_obj = json_object_new_object();
437+ if (!location_obj)
438+ return NULL;
439+
440+ if (iconf->afc.location.type != LINEAR_POLYGON) {
441+ struct afc_linear_polygon *lp =
442+ &iconf->afc.location.linear_polygon_data[0];
443+
developer05f3b2b2024-08-19 19:17:34 +0800444+ if (!lp)
445+ goto error;
446+
developer66e89bc2024-04-23 14:50:01 +0800447+ ellipse_obj = json_object_new_object();
448+ if (!ellipse_obj)
449+ goto error;
450+
451+ center_obj = json_object_new_object();
452+ if (!center_obj)
453+ goto error;
454+
455+ json_object_object_add(ellipse_obj, "center", center_obj);
456+
457+ str_obj = json_object_new_double(lp->longitude);
458+ if (!str_obj)
459+ goto error;
460+
461+ json_object_object_add(center_obj, "longitude", str_obj);
462+ str_obj = json_object_new_double(lp->latitude);
463+ if (!str_obj)
464+ goto error;
465+
466+ json_object_object_add(center_obj, "latitude", str_obj);
developer66e89bc2024-04-23 14:50:01 +0800467+ }
468+
469+ switch (iconf->afc.location.type) {
470+ case LINEAR_POLYGON: {
471+ struct json_object *outer_boundary_obj;
472+ int i;
473+
474+ outer_boundary_obj = json_object_new_object();
475+ if (!outer_boundary_obj)
476+ goto error;
477+
478+ json_object_object_add(location_obj, "linearPolygon",
479+ outer_boundary_obj);
480+ ellipse_obj = json_object_new_array();
481+ if (!ellipse_obj)
482+ goto error;
483+
484+ json_object_object_add(outer_boundary_obj, "outerBoundary",
485+ ellipse_obj);
486+ for (i = 0;
487+ i < iconf->afc.location.n_linear_polygon_data; i++) {
488+ struct afc_linear_polygon *lp =
489+ &iconf->afc.location.linear_polygon_data[i];
490+
491+ center_obj = json_object_new_object();
492+ if (!center_obj)
493+ goto error;
494+
495+ json_object_array_add(ellipse_obj, center_obj);
496+ str_obj = json_object_new_double(lp->longitude);
497+ if (!str_obj)
498+ goto error;
499+
500+ json_object_object_add(center_obj, "longitude",
501+ str_obj);
502+ str_obj = json_object_new_double(lp->latitude);
503+ if (!str_obj)
504+ goto error;
505+
506+ json_object_object_add(center_obj, "latitude",
507+ str_obj);
508+ }
509+ break;
510+ }
511+ case RADIAL_POLYGON: {
512+ struct json_object *outer_boundary_obj;
513+ int i;
514+
515+ json_object_object_add(location_obj, "radialPolygon",
516+ ellipse_obj);
517+
518+ outer_boundary_obj = json_object_new_array();
519+ if (!outer_boundary_obj)
520+ goto error;
521+
522+ json_object_object_add(ellipse_obj, "outerBoundary",
523+ outer_boundary_obj);
524+ for (i = 0;
525+ i < iconf->afc.location.n_radial_polygon_data; i++) {
526+ struct afc_radial_polygon *rp =
527+ &iconf->afc.location.radial_polygon_data[i];
528+ struct json_object *angle_obj;
529+
530+ angle_obj = json_object_new_object();
531+ if (!angle_obj)
532+ goto error;
533+
534+ json_object_array_add(outer_boundary_obj, angle_obj);
535+
536+ str_obj = json_object_new_double(rp->angle);
537+ if (!str_obj)
538+ goto error;
539+
540+ json_object_object_add(angle_obj, "angle", str_obj);
541+ str_obj = json_object_new_double(rp->length);
542+ if (!str_obj)
543+ goto error;
544+
545+ json_object_object_add(angle_obj, "length", str_obj);
546+ }
547+ break;
548+ }
549+ case ELLIPSE:
550+ default:
551+ json_object_object_add(location_obj, "ellipse", ellipse_obj);
552+
553+ str_obj = json_object_new_int(iconf->afc.location.major_axis);
554+ if (!str_obj)
555+ goto error;
556+
557+ json_object_object_add(ellipse_obj, "majorAxis", str_obj);
558+ str_obj = json_object_new_int(iconf->afc.location.minor_axis);
559+ if (!str_obj)
560+ goto error;
561+
562+ json_object_object_add(ellipse_obj, "minorAxis", str_obj);
563+ str_obj = json_object_new_int(iconf->afc.location.orientation);
564+ if (!str_obj)
565+ goto error;
566+
567+ json_object_object_add(ellipse_obj, "orientation", str_obj);
568+ break;
569+ }
570+
571+ elevation_obj = json_object_new_object();
572+ if (!elevation_obj)
573+ goto error;
574+
575+ json_object_object_add(location_obj, "elevation",
576+ elevation_obj);
577+ str_obj = json_object_new_double(iconf->afc.location.height);
578+ if (!str_obj)
579+ goto error;
580+
581+ json_object_object_add(elevation_obj, "height", str_obj);
developer05f3b2b2024-08-19 19:17:34 +0800582+ if (iconf->afc.location.height_type) {
583+ str_obj = json_object_new_string(iconf->afc.location.height_type);
584+ if (!str_obj)
585+ goto error;
developer66e89bc2024-04-23 14:50:01 +0800586+
developer05f3b2b2024-08-19 19:17:34 +0800587+ json_object_object_add(elevation_obj, "heightType", str_obj);
588+ }
589+
developer66e89bc2024-04-23 14:50:01 +0800590+ str_obj = json_object_new_int(iconf->afc.location.vertical_tolerance);
591+ if (!str_obj)
592+ goto error;
593+
594+ json_object_object_add(elevation_obj, "verticalUncertainty",
595+ str_obj);
596+ str_obj = json_object_new_int(is_ap_indoor);
597+ if (!str_obj)
598+ goto error;
599+
600+ json_object_object_add(location_obj, "indoorDeployment", str_obj);
601+
602+ return location_obj;
603+
604+error:
605+ json_object_put(location_obj);
606+ return NULL;
607+}
608+
609+
610+static struct json_object * hostapd_afc_get_opclass_chan_list(u8 op_class)
611+{
612+ struct json_object *chan_list_obj, *str_obj;
613+ const struct oper_class_map *oper_class;
614+ int chan_offset, chan;
615+
616+ oper_class = get_oper_class(NULL, op_class);
617+ if (!oper_class)
618+ return NULL;
619+
620+ chan_list_obj = json_object_new_array();
621+ if (!chan_list_obj)
622+ return NULL;
623+
624+ switch (op_class) {
625+ case 132: /* 40MHz */
626+ chan_offset = 2;
627+ break;
628+ case 133: /* 80MHz */
629+ chan_offset = 6;
630+ break;
631+ case 134: /* 160MHz */
632+ chan_offset = 14;
633+ break;
634+ default:
635+ chan_offset = 0;
636+ break;
637+ }
638+
639+ for (chan = oper_class->min_chan; chan <= oper_class->max_chan;
640+ chan += oper_class->inc) {
641+ str_obj = json_object_new_int(chan + chan_offset);
642+ if (!str_obj) {
643+ json_object_put(chan_list_obj);
644+ return NULL;
645+ }
646+ json_object_array_add(chan_list_obj, str_obj);
647+ }
648+
649+ return chan_list_obj;
650+}
651+
652+
653+static struct json_object *
654+hostapd_afc_build_req_chan_list(struct hostapd_iface *iface)
655+{
656+ struct json_object *op_class_list_obj, *str_obj;
657+ struct hostapd_config *iconf = iface->conf;
658+ int i;
659+
660+ op_class_list_obj = json_object_new_array();
661+ if (!op_class_list_obj)
662+ return NULL;
663+
664+ for (i = 0; i < iconf->afc.n_op_class; i++) {
665+ struct json_object *op_class_obj, *chan_list_obj;
666+ u8 op_class = iconf->afc.op_class[i];
667+
668+ if (!is_6ghz_op_class(op_class))
669+ continue;
670+
671+ op_class_obj = json_object_new_object();
672+ if (!op_class_obj)
673+ goto error;
674+
675+ json_object_array_add(op_class_list_obj, op_class_obj);
676+ str_obj = json_object_new_int(op_class);
677+ if (!str_obj)
678+ goto error;
679+
680+ json_object_object_add(op_class_obj, "globalOperatingClass",
681+ str_obj);
682+
683+ chan_list_obj = hostapd_afc_get_opclass_chan_list(op_class);
684+ if (!chan_list_obj)
685+ goto error;
686+
687+ json_object_object_add(op_class_obj, "channelCfi",
688+ chan_list_obj);
689+ }
690+
691+ return op_class_list_obj;
692+
693+error:
694+ json_object_put(op_class_list_obj);
695+ return NULL;
696+}
697+
698+
699+static struct json_object *
700+hostapd_afc_build_request(struct hostapd_iface *iface)
701+{
702+ struct json_object *l1_obj, *l2_obj, *la1_obj, *la2_obj;
703+ struct json_object *s2_obj, *str_obj, *location_obj;
704+ struct hostapd_config *iconf = iface->conf;
705+ struct json_object *op_class_list_obj;
706+ int i;
707+
708+ l1_obj = json_object_new_object();
709+ if (!l1_obj)
710+ return NULL;
711+
712+ if (iconf->afc.request.version) {
713+ str_obj = json_object_new_string(iconf->afc.request.version);
714+ if (!str_obj)
715+ goto error;
716+
717+ json_object_object_add(l1_obj, "version", str_obj);
718+ }
719+
720+ la1_obj = json_object_new_array();
721+ if (!la1_obj)
722+ goto error;
723+
724+ json_object_object_add(l1_obj, "availableSpectrumInquiryRequests",
725+ la1_obj);
726+ l2_obj = json_object_new_object();
727+ if (!l2_obj)
728+ goto error;
729+
730+ json_object_array_add(la1_obj, l2_obj);
731+ if (iconf->afc.request.id) {
732+ str_obj = json_object_new_string(iconf->afc.request.id);
733+ if (!str_obj)
734+ goto error;
735+
736+ json_object_object_add(l2_obj, "requestId", str_obj);
737+ }
738+
739+ s2_obj = json_object_new_object();
740+ if (!s2_obj)
741+ goto error;
742+
743+ json_object_object_add(l2_obj, "deviceDescriptor", s2_obj);
744+ if (iconf->afc.request.sn) {
745+ str_obj = json_object_new_string(iconf->afc.request.sn);
746+ if (!str_obj)
747+ goto error;
748+
749+ json_object_object_add(s2_obj, "serialNumber", str_obj);
750+ }
751+
752+ la2_obj = json_object_new_array();
753+ if (!la2_obj)
754+ goto error;
755+
756+ json_object_object_add(s2_obj, "certificationId", la2_obj);
757+ for (i = 0; i < iconf->afc.n_cert_ids; i++) {
758+ struct json_object *obj;
759+
760+ obj = json_object_new_object();
761+ if (!obj)
762+ goto error;
763+
764+ json_object_array_add(la2_obj, obj);
765+ str_obj =
766+ json_object_new_string(iconf->afc.cert_ids[i].rulset);
767+ if (!str_obj)
768+ goto error;
769+
770+ json_object_object_add(obj, "rulesetId", str_obj);
771+ str_obj = json_object_new_string(iconf->afc.cert_ids[i].id);
772+ if (!str_obj)
773+ goto error;
774+
775+ json_object_object_add(obj, "id", str_obj);
776+ }
777+
778+ location_obj = hostapd_afc_build_location_request(iface);
779+ if (!location_obj)
780+ goto error;
781+
782+ json_object_object_add(l2_obj, "location", location_obj);
783+ str_obj = json_object_new_int(iconf->afc.min_power);
784+ if (!str_obj)
785+ goto error;
786+
787+ json_object_object_add(l2_obj, "minDesiredPower", str_obj);
788+
789+ if (iconf->afc.n_freq_range) {
790+ struct json_object *freq_obj;
791+
792+ freq_obj = json_object_new_array();
793+ if (!freq_obj)
794+ goto error;
795+
796+ json_object_object_add(l2_obj, "inquiredFrequencyRange",
797+ freq_obj);
798+ for (i = 0; i < iconf->afc.n_freq_range; i++) {
799+ struct afc_freq_range *fr = &iconf->afc.freq_range[i];
800+ struct json_object *obj;
801+
802+ obj = json_object_new_object();
803+ if (!obj)
804+ goto error;
805+
806+ json_object_array_add(freq_obj, obj);
807+ str_obj = json_object_new_int(fr->low_freq);
808+ if (!str_obj)
809+ goto error;
810+
811+ json_object_object_add(obj, "lowFrequency", str_obj);
812+ str_obj = json_object_new_int(fr->high_freq);
813+ if (!str_obj)
814+ goto error;
815+
816+ json_object_object_add(obj, "highFrequency", str_obj);
817+ }
818+ }
819+
820+ op_class_list_obj = hostapd_afc_build_req_chan_list(iface);
821+ if (!op_class_list_obj)
822+ goto error;
823+
824+ json_object_object_add(l2_obj, "inquiredChannels", op_class_list_obj);
825+
826+ wpa_printf(MSG_DEBUG, "Pending AFC request: %s",
827+ json_object_get_string(l1_obj));
828+
829+ return l1_obj;
830+
831+error:
832+ json_object_put(l1_obj);
833+
834+ return NULL;
835+}
836+
837+
838+static int
839+hostad_afc_parse_available_freq_info(struct hostapd_iface *iface,
840+ struct json_object *reply_elem_obj)
841+{
842+ struct afc_freq_range_elem *f = NULL;
843+ struct json_object *obj;
844+ int i, count = 0;
845+
846+ if (!json_object_object_get_ex(reply_elem_obj,
847+ "availableFrequencyInfo", &obj))
848+ return 0;
849+
850+ for (i = 0; i < json_object_array_length(obj); i++) {
851+ struct json_object *range_elem_obj, *freq_range_obj;
852+ struct json_object *high_freq_obj, *low_freq_obj;
853+ struct json_object *max_psd_obj;
854+
855+ range_elem_obj = json_object_array_get_idx(obj, i);
developer05f3b2b2024-08-19 19:17:34 +0800856+ if (!range_elem_obj)
857+ continue;
858+
developer66e89bc2024-04-23 14:50:01 +0800859+ if (!json_object_object_get_ex(range_elem_obj,
860+ "frequencyRange",
861+ &freq_range_obj))
862+ continue;
863+
864+ if (!json_object_object_get_ex(freq_range_obj,
865+ "lowFrequency",
866+ &low_freq_obj))
867+ continue;
868+
869+ if (!json_object_object_get_ex(freq_range_obj,
870+ "highFrequency",
871+ &high_freq_obj))
872+ continue;
873+
874+ if (!json_object_object_get_ex(range_elem_obj, "maxPsd",
875+ &max_psd_obj) &&
876+ !json_object_object_get_ex(range_elem_obj, "maxPSD",
877+ &max_psd_obj))
878+ continue;
879+
880+ f = os_realloc_array(f, count + 1, sizeof(*f));
881+ if (!f)
882+ return -ENOMEM;
883+
884+ f[count].low_freq = json_object_get_int(low_freq_obj);
885+ f[count].high_freq = json_object_get_int(high_freq_obj);
886+ f[count++].max_psd = json_object_get_int(max_psd_obj);
887+ }
888+ iface->afc.freq_range = f;
889+ iface->afc.num_freq_range = count;
890+
891+ return 0;
892+}
893+
894+
895+static int hostad_afc_update_chan_info(struct afc_chan_info_elem **chan_list,
896+ int *chan_list_size, u8 op_class,
897+ int center_chan, int power)
898+{
899+ int num_low_subchan, ch, count = *chan_list_size;
900+ struct afc_chan_info_elem *c = *chan_list;
901+
902+ switch (op_class) {
903+ case 132: /* 40MHz */
904+ num_low_subchan = 2;
905+ break;
906+ case 133: /* 80MHz */
907+ num_low_subchan = 6;
908+ break;
909+ case 134: /* 160MHz */
910+ num_low_subchan = 14;
911+ break;
912+ default:
913+ num_low_subchan = 0;
914+ break;
915+ }
916+
917+ for (ch = center_chan - num_low_subchan;
918+ ch <= center_chan + num_low_subchan; ch += 4) {
919+ int i;
920+
921+ for (i = 0; i < count; i++) {
922+ if (c[i].chan == ch)
923+ break;
924+ }
925+
926+ if (i == count) {
927+ c = os_realloc_array(c, count + 1, sizeof(*c));
928+ if (!c)
929+ return -ENOMEM;
930+
931+ c[count].chan = ch;
932+ c[count++].power = power;
933+ }
934+ }
935+
936+ *chan_list_size = count;
937+ *chan_list = c;
938+
939+ return 0;
940+}
941+
942+
943+static int
944+hostad_afc_parse_available_chan_info(struct hostapd_iface *iface,
945+ struct json_object *reply_elem_obj)
946+{
947+ struct afc_chan_info_elem *c = NULL;
948+ struct json_object *obj;
949+ int i, count = 0;
950+
951+ if (!json_object_object_get_ex(reply_elem_obj,
952+ "availableChannelInfo", &obj))
953+ return 0;
954+
955+ for (i = 0; i < json_object_array_length(obj); i++) {
956+ struct json_object *range_elem_obj, *op_class_obj;
957+ struct json_object *chan_cfi_obj, *max_eirp_obj;
958+ int ch, op_class;
959+
960+ range_elem_obj = json_object_array_get_idx(obj, i);
developer05f3b2b2024-08-19 19:17:34 +0800961+ if (!range_elem_obj)
962+ continue;
963+
developer66e89bc2024-04-23 14:50:01 +0800964+ if (!json_object_object_get_ex(range_elem_obj,
965+ "globalOperatingClass",
966+ &op_class_obj))
967+ continue;
968+
969+ if (!json_object_object_get_ex(range_elem_obj, "maxEirp",
970+ &max_eirp_obj))
971+ continue;
972+
973+ if (!json_object_object_get_ex(range_elem_obj, "channelCfi",
974+ &chan_cfi_obj))
975+ continue;
976+
977+ op_class = json_object_get_int(op_class_obj);
978+ for (ch = 0;
979+ ch < json_object_array_length(chan_cfi_obj); ch++) {
980+ struct json_object *pwr_obj;
981+ struct json_object *ch_obj;
982+ int channel, power;
983+
984+ ch_obj = json_object_array_get_idx(chan_cfi_obj, ch);
985+ if (!ch_obj)
986+ continue;
987+
988+ pwr_obj = json_object_array_get_idx(max_eirp_obj, ch);
989+ if (!pwr_obj)
990+ continue;
991+
992+ channel = json_object_get_int(ch_obj);
993+ power = json_object_get_int(pwr_obj);
994+
995+ hostad_afc_update_chan_info(&c, &count, op_class,
996+ channel, power);
997+ }
998+ iface->afc.chan_info_list = c;
999+ iface->afc.num_chan_info = count;
1000+ }
1001+
1002+ return 0;
1003+}
1004+
1005+
1006+static int hostad_afc_get_timeout(struct json_object *obj)
1007+{
1008+ time_t t, now;
1009+ struct tm tm;
1010+
1011+ if (sscanf(json_object_get_string(obj), "%d-%d-%dT%d:%d:%dZ",
1012+ &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour,
1013+ &tm.tm_min, &tm.tm_sec) <= 0)
1014+ return HOSTAPD_AFC_TIMEOUT;
1015+
1016+ tm.tm_year -= 1900;
1017+ tm.tm_mon -= 1;
1018+ tm.tm_isdst = -1;
1019+ t = mktime(&tm);
1020+ time(&now);
1021+
1022+ return now > t ? HOSTAPD_AFC_RETRY_TIMEOUT : (t - now) * 80 / 100;
1023+}
1024+
1025+
1026+static int hostapd_afc_parse_reply(struct hostapd_iface *iface, char *reply)
1027+{
1028+ struct json_object *payload_obj, *reply_obj, *version_obj;
1029+ struct hostapd_config *iconf = iface->conf;
1030+ int i, request_timeout = -1, ret = -EINVAL;
1031+
1032+ wpa_printf(MSG_DEBUG, "Received AFC reply: %s", reply);
1033+ payload_obj = json_tokener_parse(reply);
developer05f3b2b2024-08-19 19:17:34 +08001034+ if (!payload_obj) {
1035+ wpa_printf(MSG_ERROR, "Failed to parse AFC reply payload");
developer66e89bc2024-04-23 14:50:01 +08001036+ return -EINVAL;
developer05f3b2b2024-08-19 19:17:34 +08001037+ }
developer66e89bc2024-04-23 14:50:01 +08001038+
developer05f3b2b2024-08-19 19:17:34 +08001039+ if (!json_object_object_get_ex(payload_obj, "version", &version_obj)) {
1040+ wpa_printf(MSG_ERROR, "Missing version in AFC reply");
developer66e89bc2024-04-23 14:50:01 +08001041+ return -EINVAL;
developer05f3b2b2024-08-19 19:17:34 +08001042+ }
developer66e89bc2024-04-23 14:50:01 +08001043+
1044+ if (iconf->afc.request.version &&
1045+ os_strcmp(iconf->afc.request.version,
developer05f3b2b2024-08-19 19:17:34 +08001046+ json_object_get_string(version_obj))) {
1047+ wpa_printf(MSG_ERROR, "Mismatch in AFC reply version");
developer66e89bc2024-04-23 14:50:01 +08001048+ return -EINVAL;
developer05f3b2b2024-08-19 19:17:34 +08001049+ }
developer66e89bc2024-04-23 14:50:01 +08001050+
1051+ if (!json_object_object_get_ex(payload_obj,
1052+ "availableSpectrumInquiryResponses",
developer05f3b2b2024-08-19 19:17:34 +08001053+ &reply_obj)) {
1054+ wpa_printf(MSG_ERROR,
1055+ "Missing availableSpectrumInquiry in AFC reply");
developer66e89bc2024-04-23 14:50:01 +08001056+ return -EINVAL;
developer05f3b2b2024-08-19 19:17:34 +08001057+ }
developer66e89bc2024-04-23 14:50:01 +08001058+
1059+ for (i = 0; i < json_object_array_length(reply_obj); i++) {
1060+ struct json_object *reply_elem_obj, *obj, *status_obj;
1061+ int j, status = -EINVAL;
1062+
1063+ reply_elem_obj = json_object_array_get_idx(reply_obj, i);
developer05f3b2b2024-08-19 19:17:34 +08001064+ if (!reply_elem_obj) {
1065+ wpa_printf(MSG_DEBUG,
1066+ "Failed to get reply element at index %d",
1067+ i);
developer66e89bc2024-04-23 14:50:01 +08001068+ continue;
developer05f3b2b2024-08-19 19:17:34 +08001069+ }
developer66e89bc2024-04-23 14:50:01 +08001070+
1071+ if (!json_object_object_get_ex(reply_elem_obj, "requestId",
developer05f3b2b2024-08-19 19:17:34 +08001072+ &obj)) {
1073+ wpa_printf(MSG_DEBUG,
1074+ "Missing requestId in reply element %d", i);
developer66e89bc2024-04-23 14:50:01 +08001075+ continue;
developer05f3b2b2024-08-19 19:17:34 +08001076+ }
developer66e89bc2024-04-23 14:50:01 +08001077+
1078+ if (iconf->afc.request.id &&
1079+ os_strcmp(iconf->afc.request.id,
developer05f3b2b2024-08-19 19:17:34 +08001080+ json_object_get_string(obj))) {
1081+ wpa_printf(MSG_DEBUG,
1082+ "RequestId mismatch in reply element %d",
1083+ i);
developer66e89bc2024-04-23 14:50:01 +08001084+ continue;
developer05f3b2b2024-08-19 19:17:34 +08001085+ }
developer66e89bc2024-04-23 14:50:01 +08001086+
1087+ if (!json_object_object_get_ex(reply_elem_obj, "rulesetId",
developer05f3b2b2024-08-19 19:17:34 +08001088+ &obj)) {
1089+ wpa_printf(MSG_DEBUG,
1090+ "Missing rulesetId in reply element %d", i);
developer66e89bc2024-04-23 14:50:01 +08001091+ continue;
developer05f3b2b2024-08-19 19:17:34 +08001092+ }
developer66e89bc2024-04-23 14:50:01 +08001093+
1094+ for (j = 0; j < iconf->afc.n_cert_ids; j++) {
1095+ if (!os_strcmp(iconf->afc.cert_ids[j].rulset,
1096+ json_object_get_string(obj)))
1097+ break;
1098+ }
1099+
developer05f3b2b2024-08-19 19:17:34 +08001100+ if (j == iconf->afc.n_cert_ids) {
1101+ wpa_printf(MSG_DEBUG,
1102+ "RulesetId mismatch in reply element %d",
1103+ i);
developer66e89bc2024-04-23 14:50:01 +08001104+ continue;
developer05f3b2b2024-08-19 19:17:34 +08001105+ }
developer66e89bc2024-04-23 14:50:01 +08001106+
1107+ if (!json_object_object_get_ex(reply_elem_obj, "response",
developer05f3b2b2024-08-19 19:17:34 +08001108+ &obj)) {
1109+ wpa_printf(MSG_DEBUG,
1110+ "Missing response field in reply element %d",
1111+ i);
developer66e89bc2024-04-23 14:50:01 +08001112+ continue;
developer05f3b2b2024-08-19 19:17:34 +08001113+ }
developer66e89bc2024-04-23 14:50:01 +08001114+
1115+ if (json_object_object_get_ex(obj, "shortDescription",
1116+ &status_obj))
1117+ wpa_printf(MSG_DEBUG, "AFC reply element %d: %s",
1118+ i, json_object_get_string(status_obj));
1119+
1120+ if (json_object_object_get_ex(obj, "responseCode",
1121+ &status_obj))
1122+ status = json_object_get_int(status_obj);
1123+
developer05f3b2b2024-08-19 19:17:34 +08001124+ if (status < 0) {
1125+ wpa_printf(MSG_DEBUG,
1126+ "Reply element %d invalid responseCode: %d",
1127+ i, status);
developer66e89bc2024-04-23 14:50:01 +08001128+ continue;
developer05f3b2b2024-08-19 19:17:34 +08001129+ }
developer66e89bc2024-04-23 14:50:01 +08001130+
1131+ if (hostad_afc_parse_available_freq_info(iface,
1132+ reply_elem_obj) ||
1133+ hostad_afc_parse_available_chan_info(iface,
1134+ reply_elem_obj))
1135+ continue;
1136+
1137+ if (json_object_object_get_ex(reply_elem_obj,
1138+ "availabilityExpireTime",
1139+ &obj)) {
1140+ int timeout = hostad_afc_get_timeout(obj);
1141+
1142+ if (request_timeout < 0 || timeout < request_timeout)
1143+ request_timeout = timeout;
1144+ }
1145+
1146+ ret = status;
1147+ }
1148+
1149+ iface->afc.data_valid = true;
1150+ iface->afc.timeout = request_timeout;
1151+ if (iface->afc.timeout < 0)
1152+ iface->afc.timeout = HOSTAPD_AFC_RETRY_TIMEOUT;
1153+
1154+ return ret;
1155+}
1156+
1157+
1158+static int hostapd_afc_send_receive(struct hostapd_iface *iface)
1159+{
1160+ struct hostapd_config *iconf = iface->conf;
1161+ json_object *request_obj = NULL;
1162+ struct timeval sock_timeout = {
1163+ .tv_sec = 5,
1164+ };
1165+ struct sockaddr_un addr = {
1166+ .sun_family = AF_UNIX,
1167+#ifdef __FreeBSD__
1168+ .sun_len = sizeof(addr),
1169+#endif /* __FreeBSD__ */
1170+ };
developer66e89bc2024-04-23 14:50:01 +08001171+ const char *request;
developer05f3b2b2024-08-19 19:17:34 +08001172+ char *buf = NULL;
developer66e89bc2024-04-23 14:50:01 +08001173+ int sockfd, ret;
1174+ fd_set read_set;
1175+
1176+ if (iface->afc.data_valid) {
1177+ /* AFC data already downloaded from the server */
1178+ return 0;
1179+ }
1180+
developer05f3b2b2024-08-19 19:17:34 +08001181+ if (!iconf->afc.socket) {
1182+ wpa_printf(MSG_ERROR, "Missing AFC socket string");
1183+ return -EINVAL;
1184+ }
1185+
developer66e89bc2024-04-23 14:50:01 +08001186+ iface->afc.timeout = HOSTAPD_AFC_RETRY_TIMEOUT;
1187+ if (os_strlen(iconf->afc.socket) >= sizeof(addr.sun_path)) {
1188+ wpa_printf(MSG_ERROR, "Malformed AFC socket string %s",
1189+ iconf->afc.socket);
1190+ return -EINVAL;
1191+ }
1192+
1193+ os_strlcpy(addr.sun_path, iconf->afc.socket, sizeof(addr.sun_path));
1194+ sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
1195+ if (sockfd < 0) {
1196+ wpa_printf(MSG_ERROR, "Failed creating AFC socket");
1197+ return sockfd;
1198+ }
1199+
1200+ if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
1201+ wpa_printf(MSG_ERROR, "Failed connecting AFC socket");
1202+ ret = -EIO;
1203+ goto close_sock;
1204+ }
1205+
1206+ request_obj = hostapd_afc_build_request(iface);
1207+ if (!request_obj) {
1208+ ret = -ENOMEM;
1209+ goto close_sock;
1210+ }
1211+
1212+ request = json_object_to_json_string(request_obj);
1213+ if (send(sockfd, request, strlen(request), 0) < 0) {
1214+ wpa_printf(MSG_ERROR, "Failed sending AFC request");
1215+ ret = -EIO;
1216+ goto close_sock;
1217+ }
1218+
1219+ FD_ZERO(&read_set);
1220+ FD_SET(sockfd, &read_set);
1221+ if (select(sockfd + 1, &read_set, NULL, NULL, &sock_timeout) < 0) {
1222+ wpa_printf(MSG_ERROR, "Select failed on AFC socket");
1223+ ret = -errno;
1224+ goto close_sock;
1225+ }
1226+
1227+ if (!FD_ISSET(sockfd, &read_set)) {
1228+ ret = -EIO;
1229+ goto close_sock;
1230+ }
1231+
developer05f3b2b2024-08-19 19:17:34 +08001232+ buf = os_zalloc(HOSTAPD_AFC_BUFSIZE);
1233+ if (!buf) {
1234+ ret = -ENOMEM;
1235+ goto close_sock;
1236+ }
1237+
1238+ ret = recv(sockfd, buf, HOSTAPD_AFC_BUFSIZE - 1, 0);
developer66e89bc2024-04-23 14:50:01 +08001239+ if (ret <= 0)
1240+ goto close_sock;
1241+
1242+ ret = hostapd_afc_parse_reply(iface, buf);
developer05f3b2b2024-08-19 19:17:34 +08001243+ if (ret)
1244+ wpa_printf(MSG_ERROR, "Failed parsing AFC reply: %d", ret);
developer66e89bc2024-04-23 14:50:01 +08001245+close_sock:
developer05f3b2b2024-08-19 19:17:34 +08001246+ os_free(buf);
developer66e89bc2024-04-23 14:50:01 +08001247+ json_object_put(request_obj);
1248+ close(sockfd);
1249+
1250+ return ret;
1251+}
1252+
1253+
1254+static bool hostapd_afc_has_usable_chans(struct hostapd_iface *iface)
1255+{
1256+ const struct oper_class_map *oper_class;
1257+ int ch;
1258+
1259+ oper_class = get_oper_class(NULL, iface->conf->op_class);
1260+ if (!oper_class)
1261+ return false;
1262+
1263+ for (ch = oper_class->min_chan; ch <= oper_class->max_chan;
1264+ ch += oper_class->inc) {
1265+ struct hostapd_hw_modes *mode = iface->current_mode;
1266+ int i;
1267+
1268+ for (i = 0; i < mode->num_channels; i++) {
1269+ struct hostapd_channel_data *chan = &mode->channels[i];
1270+
1271+ if (chan->chan == ch &&
1272+ !(chan->flag & HOSTAPD_CHAN_DISABLED))
developer05f3b2b2024-08-19 19:17:34 +08001273+ return true;
developer66e89bc2024-04-23 14:50:01 +08001274+ }
1275+ }
1276+
1277+ return false;
1278+}
1279+
1280+
1281+int hostapd_afc_handle_request(struct hostapd_iface *iface)
1282+{
1283+ struct hostapd_config *iconf = iface->conf;
1284+ int ret;
1285+
1286+ /* AFC is required just for standard power AP */
1287+ if (!he_reg_is_sp(iconf->he_6ghz_reg_pwr_type))
1288+ return 1;
1289+
1290+ if (!is_6ghz_op_class(iconf->op_class) || !is_6ghz_freq(iface->freq))
1291+ return 1;
1292+
1293+ if (iface->state == HAPD_IFACE_ACS)
1294+ return 1;
1295+
1296+ ret = hostapd_afc_send_receive(iface);
1297+ if (ret < 0) {
1298+ /*
1299+ * If the connection to the AFCD failed, resched for a
1300+ * future attempt.
1301+ */
1302+ wpa_printf(MSG_ERROR, "AFC connection failed: %d", ret);
1303+ if (ret == -EIO)
1304+ ret = 0;
1305+ goto resched;
1306+ }
1307+
1308+ hostap_afc_disable_channels(iface);
1309+ if (!hostapd_afc_has_usable_chans(iface))
1310+ goto resched;
1311+
developer05f3b2b2024-08-19 19:17:34 +08001312+ if (!hostapd_is_usable_chans(iface)) {
1313+ /* Trigger an ACS freq scan */
1314+ iconf->channel = 0;
1315+ iface->freq = 0;
developer66e89bc2024-04-23 14:50:01 +08001316+
developer05f3b2b2024-08-19 19:17:34 +08001317+ if (acs_init(iface) != HOSTAPD_CHAN_ACS) {
1318+ wpa_printf(MSG_ERROR, "Could not start ACS");
1319+ ret = -EINVAL;
1320+ }
1321+ } else {
1322+ ret = 1;
developer66e89bc2024-04-23 14:50:01 +08001323+ }
1324+
1325+resched:
1326+ eloop_cancel_timeout(hostapd_afc_timeout_handler, iface, NULL);
1327+ eloop_register_timeout(iface->afc.timeout, 0,
1328+ hostapd_afc_timeout_handler, iface, NULL);
1329+
1330+ return ret;
1331+}
1332+
1333+
1334+static void hostapd_afc_delete_data_from_server(struct hostapd_iface *iface)
1335+{
1336+ os_free(iface->afc.chan_info_list);
1337+ os_free(iface->afc.freq_range);
1338+
1339+ iface->afc.num_freq_range = 0;
1340+ iface->afc.num_chan_info = 0;
1341+
1342+ iface->afc.chan_info_list = NULL;
1343+ iface->afc.freq_range = NULL;
1344+
1345+ iface->afc.data_valid = false;
1346+}
1347+
1348+
1349+static void hostapd_afc_timeout_handler(void *eloop_ctx, void *timeout_ctx)
1350+{
1351+ struct hostapd_iface *iface = eloop_ctx;
1352+ bool restart_iface = true;
1353+
1354+ hostapd_afc_delete_data_from_server(iface);
1355+ if (iface->state != HAPD_IFACE_ENABLED) {
developer05f3b2b2024-08-19 19:17:34 +08001356+ /* Hostapd is not fully enabled yet, toggle the interface */
developer66e89bc2024-04-23 14:50:01 +08001357+ goto restart_interface;
1358+ }
1359+
1360+ if (hostapd_afc_send_receive(iface) < 0 ||
1361+ hostapd_get_hw_features(iface)) {
1362+ restart_iface = false;
1363+ goto restart_interface;
1364+ }
1365+
1366+ if (hostapd_is_usable_chans(iface))
1367+ goto resched;
1368+
1369+ restart_iface = hostapd_afc_has_usable_chans(iface);
1370+ if (restart_iface) {
1371+ /* Trigger an ACS freq scan */
1372+ iface->conf->channel = 0;
1373+ iface->freq = 0;
1374+ }
1375+
1376+restart_interface:
1377+ hostapd_disable_iface(iface);
1378+ if (restart_iface)
1379+ hostapd_enable_iface(iface);
1380+resched:
1381+ eloop_register_timeout(iface->afc.timeout, 0,
1382+ hostapd_afc_timeout_handler, iface, NULL);
1383+}
developer05f3b2b2024-08-19 19:17:34 +08001384+
1385+
1386+void hostapd_afc_stop(struct hostapd_iface *iface)
1387+{
1388+ eloop_cancel_timeout(hostapd_afc_timeout_handler, iface, NULL);
1389+}
1390+
1391+
1392+void hostap_afc_disable_channels(struct hostapd_iface *iface)
1393+{
1394+ struct hostapd_hw_modes *mode = NULL;
1395+ int i;
1396+
1397+ for (i = 0; i < iface->num_hw_features; i++) {
1398+ mode = &iface->hw_features[i];
1399+ if (mode->mode == HOSTAPD_MODE_IEEE80211A &&
1400+ mode->is_6ghz)
1401+ break;
1402+ }
1403+
1404+ if (i == iface->num_hw_features)
1405+ return;
1406+
1407+ if (!he_reg_is_sp(iface->conf->he_6ghz_reg_pwr_type))
1408+ return;
1409+
1410+ if (!iface->afc.data_valid)
1411+ return;
1412+
1413+ for (i = 0; i < mode->num_channels; i++) {
1414+ struct hostapd_channel_data *chan = &mode->channels[i];
1415+ int j;
1416+
1417+ if (!is_6ghz_freq(chan->freq))
1418+ continue;
1419+
1420+ for (j = 0; j < iface->afc.num_freq_range; j++) {
1421+ if (chan->freq >= iface->afc.freq_range[j].low_freq &&
1422+ chan->freq <= iface->afc.freq_range[j].high_freq)
1423+ break;
1424+ }
1425+
1426+ if (j != iface->afc.num_freq_range)
1427+ continue;
1428+
1429+ for (j = 0; j < iface->afc.num_chan_info; j++) {
1430+ if (chan->chan == iface->afc.chan_info_list[j].chan)
1431+ break;
1432+ }
1433+
1434+ if (j != iface->afc.num_chan_info)
1435+ continue;
1436+
1437+ chan->flag |= HOSTAPD_CHAN_DISABLED;
1438+ wpa_printf(MSG_MSGDUMP,
1439+ "Disabling freq=%d MHz (not allowed by AFC)",
1440+ chan->freq);
1441+ }
1442+}
developer66e89bc2024-04-23 14:50:01 +08001443diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c
developer05f3b2b2024-08-19 19:17:34 +08001444index c6aa49610..9e34e029a 100644
developer66e89bc2024-04-23 14:50:01 +08001445--- a/src/ap/ap_config.c
1446+++ b/src/ap/ap_config.c
developer05f3b2b2024-08-19 19:17:34 +08001447@@ -1047,6 +1047,22 @@ void hostapd_config_free(struct hostapd_config *conf)
developer66e89bc2024-04-23 14:50:01 +08001448 #endif /* CONFIG_ACS */
1449 wpabuf_free(conf->lci);
1450 wpabuf_free(conf->civic);
1451+#ifdef CONFIG_AFC
1452+ os_free(conf->afc.socket);
1453+ os_free(conf->afc.request.version);
1454+ os_free(conf->afc.request.id);
1455+ os_free(conf->afc.request.sn);
1456+ for (i = 0; i < conf->afc.n_cert_ids; i++) {
1457+ os_free(conf->afc.cert_ids[i].rulset);
1458+ os_free(conf->afc.cert_ids[i].id);
1459+ }
1460+ os_free(conf->afc.cert_ids);
1461+ os_free(conf->afc.location.height_type);
1462+ os_free(conf->afc.location.linear_polygon_data);
1463+ os_free(conf->afc.location.radial_polygon_data);
1464+ os_free(conf->afc.freq_range);
1465+ os_free(conf->afc.op_class);
1466+#endif /* CONFIG_AFC */
1467
1468 os_free(conf);
1469 }
1470diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h
developer05f3b2b2024-08-19 19:17:34 +08001471index d42076785..e6669e6a3 100644
developer66e89bc2024-04-23 14:50:01 +08001472--- a/src/ap/ap_config.h
1473+++ b/src/ap/ap_config.h
developer05f3b2b2024-08-19 19:17:34 +08001474@@ -1249,6 +1249,53 @@ struct hostapd_config {
1475
1476 /* Whether to enable TWT responder in HT and VHT modes */
1477 bool ht_vht_twt_responder;
developer66e89bc2024-04-23 14:50:01 +08001478+
1479+#ifdef CONFIG_AFC
1480+ struct {
1481+ char *socket;
1482+ struct {
1483+ char *version;
1484+ char *id;
1485+ char *sn;
1486+ } request;
1487+ unsigned int n_cert_ids;
1488+ struct cert_id {
1489+ char *rulset;
1490+ char *id;
1491+ } *cert_ids;
1492+ struct {
1493+ enum afc_location_type {
1494+ ELLIPSE,
1495+ LINEAR_POLYGON,
1496+ RADIAL_POLYGON,
1497+ } type;
1498+ unsigned int n_linear_polygon_data;
1499+ struct afc_linear_polygon {
1500+ double longitude;
1501+ double latitude;
1502+ } *linear_polygon_data;
1503+ unsigned int n_radial_polygon_data;
1504+ struct afc_radial_polygon {
1505+ double length;
1506+ double angle;
1507+ } *radial_polygon_data;
1508+ int major_axis;
1509+ int minor_axis;
1510+ int orientation;
1511+ double height;
1512+ char *height_type;
1513+ int vertical_tolerance;
1514+ } location;
1515+ unsigned int n_freq_range;
1516+ struct afc_freq_range {
1517+ int low_freq;
1518+ int high_freq;
1519+ } *freq_range;
1520+ unsigned int n_op_class;
1521+ unsigned int *op_class;
1522+ int min_power;
1523+ } afc;
1524+#endif /* CONFIG_AFC */
1525 };
1526
1527
1528diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c
developer05f3b2b2024-08-19 19:17:34 +08001529index 36d48ae09..5a8cdc90e 100644
developer66e89bc2024-04-23 14:50:01 +08001530--- a/src/ap/hostapd.c
1531+++ b/src/ap/hostapd.c
developer05f3b2b2024-08-19 19:17:34 +08001532@@ -715,6 +715,7 @@ void hostapd_cleanup_iface_partial(struct hostapd_iface *iface)
1533 static void hostapd_cleanup_iface(struct hostapd_iface *iface)
1534 {
1535 wpa_printf(MSG_DEBUG, "%s(%p)", __func__, iface);
1536+ hostapd_afc_stop(iface);
1537 eloop_cancel_timeout(hostapd_interface_setup_failure_handler, iface,
1538 NULL);
1539
1540@@ -2559,6 +2560,16 @@ static int hostapd_setup_interface_complete_sync(struct hostapd_iface *iface,
developer66e89bc2024-04-23 14:50:01 +08001541 }
1542 #endif /* CONFIG_MESH */
1543
developer05f3b2b2024-08-19 19:17:34 +08001544+#ifdef CONFIG_IEEE80211AX
developer66e89bc2024-04-23 14:50:01 +08001545+ /* check AFC for 6GHz channels. */
1546+ res = hostapd_afc_handle_request(iface);
1547+ if (res <= 0) {
1548+ if (res < 0)
1549+ goto fail;
1550+ return res;
1551+ }
developer05f3b2b2024-08-19 19:17:34 +08001552+#endif /* CONFIG_IEEE80211AX */
developer66e89bc2024-04-23 14:50:01 +08001553+
1554 if (!delay_apply_cfg &&
1555 hostapd_set_freq(hapd, hapd->iconf->hw_mode, iface->freq,
1556 hapd->iconf->channel,
developer05f3b2b2024-08-19 19:17:34 +08001557@@ -2957,6 +2968,7 @@ void hostapd_interface_deinit(struct hostapd_iface *iface)
1558
1559 hostapd_set_state(iface, HAPD_IFACE_DISABLED);
1560
1561+ hostapd_afc_stop(iface);
1562 eloop_cancel_timeout(channel_list_update_timeout, iface, NULL);
1563 iface->wait_channel_update = 0;
1564 iface->is_no_ir = false;
1565@@ -3030,6 +3042,10 @@ void hostapd_interface_free(struct hostapd_iface *iface)
developer66e89bc2024-04-23 14:50:01 +08001566 __func__, iface->bss[j]);
1567 os_free(iface->bss[j]);
1568 }
1569+#ifdef CONFIG_AFC
1570+ os_free(iface->afc.chan_info_list);
1571+ os_free(iface->afc.freq_range);
1572+#endif
1573 hostapd_cleanup_iface(iface);
1574 }
1575
developer66e89bc2024-04-23 14:50:01 +08001576diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h
developer05f3b2b2024-08-19 19:17:34 +08001577index 2ef63e5f2..d67a0afa0 100644
developer66e89bc2024-04-23 14:50:01 +08001578--- a/src/ap/hostapd.h
1579+++ b/src/ap/hostapd.h
developer05f3b2b2024-08-19 19:17:34 +08001580@@ -718,9 +718,54 @@ struct hostapd_iface {
developer66e89bc2024-04-23 14:50:01 +08001581 bool is_no_ir;
developer05f3b2b2024-08-19 19:17:34 +08001582
1583 bool is_ch_switch_dfs; /* Channel switch from ACS to DFS */
developer66e89bc2024-04-23 14:50:01 +08001584+
1585+#ifdef CONFIG_AFC
1586+ struct {
1587+ int timeout;
1588+ unsigned int num_freq_range;
1589+ struct afc_freq_range_elem {
1590+ int low_freq;
1591+ int high_freq;
1592+ /**
1593+ * max eirp power spectral density received from
1594+ * the AFC coordinator for this band
1595+ */
1596+ int max_psd;
1597+ } *freq_range;
1598+ unsigned int num_chan_info;
1599+ struct afc_chan_info_elem {
1600+ int chan;
1601+ /**
1602+ * max eirp power received from the AFC coordinator
1603+ * for this channel
1604+ */
1605+ int power;
1606+ } *chan_info_list;
1607+ bool data_valid;
1608+ } afc;
1609+#endif /* CONFIG_AFC */
1610 };
1611
1612 /* hostapd.c */
developer05f3b2b2024-08-19 19:17:34 +08001613+#ifdef CONFIG_AFC
developer66e89bc2024-04-23 14:50:01 +08001614+int hostapd_afc_handle_request(struct hostapd_iface *iface);
developer05f3b2b2024-08-19 19:17:34 +08001615+void hostapd_afc_stop(struct hostapd_iface *iface);
1616+void hostap_afc_disable_channels(struct hostapd_iface *iface);
1617+#else
1618+static inline int hostapd_afc_handle_request(struct hostapd_iface *iface)
1619+{
1620+ return 1;
1621+}
1622+
1623+static inline void hostapd_afc_stop(struct hostapd_iface *iface)
1624+{
1625+}
1626+
1627+static inline void hostap_afc_disable_channels(struct hostapd_iface *iface)
1628+{
1629+}
1630+#endif /* CONFIG_AFC */
developer66e89bc2024-04-23 14:50:01 +08001631+
1632 int hostapd_for_each_interface(struct hapd_interfaces *interfaces,
1633 int (*cb)(struct hostapd_iface *iface,
1634 void *ctx), void *ctx);
1635diff --git a/src/ap/hw_features.c b/src/ap/hw_features.c
developer05f3b2b2024-08-19 19:17:34 +08001636index 85e67080d..8aa0b3ab5 100644
developer66e89bc2024-04-23 14:50:01 +08001637--- a/src/ap/hw_features.c
1638+++ b/src/ap/hw_features.c
1639@@ -114,6 +114,8 @@ int hostapd_get_hw_features(struct hostapd_iface *iface)
1640 iface->hw_features = modes;
1641 iface->num_hw_features = num_modes;
1642
1643+ hostap_afc_disable_channels(iface);
1644+
1645 for (i = 0; i < num_modes; i++) {
1646 struct hostapd_hw_modes *feature = &modes[i];
1647 int dfs_enabled = hapd->iconf->ieee80211h &&
1648--
developer05f3b2b2024-08-19 19:17:34 +080016492.18.0
developer66e89bc2024-04-23 14:50:01 +08001650