developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1 | From 3bd1d33a94f25337fd70df60ee5a42a60f95cba9 Mon Sep 17 00:00:00 2001 |
| 2 | From: Lorenzo Bianconi <lorenzo@kernel.org> |
| 3 | Date: Fri, 17 May 2024 11:50:27 +0200 |
| 4 | Subject: [PATCH 007/126] hostapd: ap: add AFC client support |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 5 | |
| 6 | Introduce Automated Frequency Coordination (AFC) support for UNII-5 and |
| 7 | UNII-7 6GHz bands. |
| 8 | AFC client will connect to AFCD providing AP related parameter for AFC |
| 9 | coordinator (e.g. geolocation, supported frequencies, ..). |
| 10 | AFC is required for Standard Power Devices (SPDs) to determine a lists |
| 11 | of channels and EIRP/PSD powers that are available in the 6GHz spectrum. |
| 12 | AFC hostapd client is tested with AFC DUT Test Harness [0]. |
| 13 | |
| 14 | [0] https://github.com/Wi-FiTestSuite/AFC-DUT/tree/main |
| 15 | |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 16 | Tested-by: Felix Fietkau <nbd@nbd.name> |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 17 | Tested-by: Allen Ye <allen.ye@mediatek.com> |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 18 | Tested-by: Krishna Chaitanya <chaitanya.mgit@gmail.com> |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 19 | Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org> |
| 20 | --- |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 21 | 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(+) |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 32 | create mode 100644 src/ap/afc.c |
| 33 | |
| 34 | diff --git a/hostapd/Makefile b/hostapd/Makefile |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 35 | index ca4439234..78171025e 100644 |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 36 | --- a/hostapd/Makefile |
| 37 | +++ b/hostapd/Makefile |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 38 | @@ -104,6 +104,14 @@ CFLAGS += -DCONFIG_TAXONOMY |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 39 | OBJS += ../src/ap/taxonomy.o |
| 40 | endif |
| 41 | |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 42 | +ifdef CONFIG_IEEE80211AX |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 43 | +ifdef CONFIG_AFC |
| 44 | +CFLAGS += -DCONFIG_AFC |
| 45 | +OBJS += ../src/ap/afc.o |
| 46 | +LIBS += -ljson-c |
| 47 | +endif |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 48 | +endif |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 49 | + |
| 50 | ifdef CONFIG_MODULE_TESTS |
| 51 | CFLAGS += -DCONFIG_MODULE_TESTS |
| 52 | OBJS += hapd_module_tests.o |
| 53 | diff --git a/hostapd/config_file.c b/hostapd/config_file.c |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 54 | index 96f1b1749..a86621ed9 100644 |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 55 | --- a/hostapd/config_file.c |
| 56 | +++ b/hostapd/config_file.c |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 57 | @@ -1281,6 +1281,190 @@ static int hostapd_parse_he_srg_bitmap(u8 *bitmap, char *val) |
| 58 | return 0; |
| 59 | } |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 60 | |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 61 | + |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 62 | +#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 */ |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 245 | #endif /* CONFIG_IEEE80211AX */ |
| 246 | |
| 247 | |
| 248 | @@ -3955,6 +4139,83 @@ static int hostapd_config_fill(struct hostapd_config *conf, |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 249 | 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) { |
| 332 | diff --git a/hostapd/defconfig b/hostapd/defconfig |
| 333 | index 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 |
| 343 | diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 344 | index 93524cf5d..56442c69d 100644 |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 345 | --- a/hostapd/hostapd.conf |
| 346 | +++ b/hostapd/hostapd.conf |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 347 | @@ -1030,6 +1030,48 @@ wmm_ac_vo_acm=0 |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 348 | # 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 |
| 396 | diff --git a/src/ap/afc.c b/src/ap/afc.c |
| 397 | new file mode 100644 |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 398 | index 000000000..cfee83fe7 |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 399 | --- /dev/null |
| 400 | +++ b/src/ap/afc.c |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 401 | @@ -0,0 +1,1041 @@ |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 402 | +/* |
| 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 */ |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 423 | +#define HOSTAPD_AFC_BUFSIZE 8192 |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 424 | + |
| 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 | + |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 444 | + if (!lp) |
| 445 | + goto error; |
| 446 | + |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 447 | + 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); |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 467 | + } |
| 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); |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 582 | + 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; |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 586 | + |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 587 | + json_object_object_add(elevation_obj, "heightType", str_obj); |
| 588 | + } |
| 589 | + |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 590 | + 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); |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 856 | + if (!range_elem_obj) |
| 857 | + continue; |
| 858 | + |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 859 | + 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); |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 961 | + if (!range_elem_obj) |
| 962 | + continue; |
| 963 | + |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 964 | + 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); |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1034 | + if (!payload_obj) { |
| 1035 | + wpa_printf(MSG_ERROR, "Failed to parse AFC reply payload"); |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1036 | + return -EINVAL; |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1037 | + } |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1038 | + |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1039 | + if (!json_object_object_get_ex(payload_obj, "version", &version_obj)) { |
| 1040 | + wpa_printf(MSG_ERROR, "Missing version in AFC reply"); |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1041 | + return -EINVAL; |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1042 | + } |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1043 | + |
| 1044 | + if (iconf->afc.request.version && |
| 1045 | + os_strcmp(iconf->afc.request.version, |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1046 | + json_object_get_string(version_obj))) { |
| 1047 | + wpa_printf(MSG_ERROR, "Mismatch in AFC reply version"); |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1048 | + return -EINVAL; |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1049 | + } |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1050 | + |
| 1051 | + if (!json_object_object_get_ex(payload_obj, |
| 1052 | + "availableSpectrumInquiryResponses", |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1053 | + &reply_obj)) { |
| 1054 | + wpa_printf(MSG_ERROR, |
| 1055 | + "Missing availableSpectrumInquiry in AFC reply"); |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1056 | + return -EINVAL; |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1057 | + } |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1058 | + |
| 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); |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1064 | + if (!reply_elem_obj) { |
| 1065 | + wpa_printf(MSG_DEBUG, |
| 1066 | + "Failed to get reply element at index %d", |
| 1067 | + i); |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1068 | + continue; |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1069 | + } |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1070 | + |
| 1071 | + if (!json_object_object_get_ex(reply_elem_obj, "requestId", |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1072 | + &obj)) { |
| 1073 | + wpa_printf(MSG_DEBUG, |
| 1074 | + "Missing requestId in reply element %d", i); |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1075 | + continue; |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1076 | + } |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1077 | + |
| 1078 | + if (iconf->afc.request.id && |
| 1079 | + os_strcmp(iconf->afc.request.id, |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1080 | + json_object_get_string(obj))) { |
| 1081 | + wpa_printf(MSG_DEBUG, |
| 1082 | + "RequestId mismatch in reply element %d", |
| 1083 | + i); |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1084 | + continue; |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1085 | + } |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1086 | + |
| 1087 | + if (!json_object_object_get_ex(reply_elem_obj, "rulesetId", |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1088 | + &obj)) { |
| 1089 | + wpa_printf(MSG_DEBUG, |
| 1090 | + "Missing rulesetId in reply element %d", i); |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1091 | + continue; |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1092 | + } |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1093 | + |
| 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 | + |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1100 | + if (j == iconf->afc.n_cert_ids) { |
| 1101 | + wpa_printf(MSG_DEBUG, |
| 1102 | + "RulesetId mismatch in reply element %d", |
| 1103 | + i); |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1104 | + continue; |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1105 | + } |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1106 | + |
| 1107 | + if (!json_object_object_get_ex(reply_elem_obj, "response", |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1108 | + &obj)) { |
| 1109 | + wpa_printf(MSG_DEBUG, |
| 1110 | + "Missing response field in reply element %d", |
| 1111 | + i); |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1112 | + continue; |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1113 | + } |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1114 | + |
| 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 | + |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1124 | + if (status < 0) { |
| 1125 | + wpa_printf(MSG_DEBUG, |
| 1126 | + "Reply element %d invalid responseCode: %d", |
| 1127 | + i, status); |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1128 | + continue; |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1129 | + } |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1130 | + |
| 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 | + }; |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1171 | + const char *request; |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1172 | + char *buf = NULL; |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1173 | + 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 | + |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1181 | + if (!iconf->afc.socket) { |
| 1182 | + wpa_printf(MSG_ERROR, "Missing AFC socket string"); |
| 1183 | + return -EINVAL; |
| 1184 | + } |
| 1185 | + |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1186 | + 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 | + |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1232 | + 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); |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1239 | + if (ret <= 0) |
| 1240 | + goto close_sock; |
| 1241 | + |
| 1242 | + ret = hostapd_afc_parse_reply(iface, buf); |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1243 | + if (ret) |
| 1244 | + wpa_printf(MSG_ERROR, "Failed parsing AFC reply: %d", ret); |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1245 | +close_sock: |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1246 | + os_free(buf); |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1247 | + 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)) |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1273 | + return true; |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1274 | + } |
| 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 | + |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1312 | + if (!hostapd_is_usable_chans(iface)) { |
| 1313 | + /* Trigger an ACS freq scan */ |
| 1314 | + iconf->channel = 0; |
| 1315 | + iface->freq = 0; |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1316 | + |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1317 | + 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; |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1323 | + } |
| 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) { |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1356 | + /* Hostapd is not fully enabled yet, toggle the interface */ |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1357 | + 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 | +} |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1384 | + |
| 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 | +} |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1443 | diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1444 | index c6aa49610..9e34e029a 100644 |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1445 | --- a/src/ap/ap_config.c |
| 1446 | +++ b/src/ap/ap_config.c |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1447 | @@ -1047,6 +1047,22 @@ void hostapd_config_free(struct hostapd_config *conf) |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1448 | #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 | } |
| 1470 | diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1471 | index d42076785..e6669e6a3 100644 |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1472 | --- a/src/ap/ap_config.h |
| 1473 | +++ b/src/ap/ap_config.h |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1474 | @@ -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; |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1478 | + |
| 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 | |
| 1528 | diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1529 | index 36d48ae09..5a8cdc90e 100644 |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1530 | --- a/src/ap/hostapd.c |
| 1531 | +++ b/src/ap/hostapd.c |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1532 | @@ -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, |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1541 | } |
| 1542 | #endif /* CONFIG_MESH */ |
| 1543 | |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1544 | +#ifdef CONFIG_IEEE80211AX |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1545 | + /* 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 | + } |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1552 | +#endif /* CONFIG_IEEE80211AX */ |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1553 | + |
| 1554 | if (!delay_apply_cfg && |
| 1555 | hostapd_set_freq(hapd, hapd->iconf->hw_mode, iface->freq, |
| 1556 | hapd->iconf->channel, |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1557 | @@ -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) |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1566 | __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 | |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1576 | diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1577 | index 2ef63e5f2..d67a0afa0 100644 |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1578 | --- a/src/ap/hostapd.h |
| 1579 | +++ b/src/ap/hostapd.h |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1580 | @@ -718,9 +718,54 @@ struct hostapd_iface { |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1581 | bool is_no_ir; |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1582 | |
| 1583 | bool is_ch_switch_dfs; /* Channel switch from ACS to DFS */ |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1584 | + |
| 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 */ |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1613 | +#ifdef CONFIG_AFC |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1614 | +int hostapd_afc_handle_request(struct hostapd_iface *iface); |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1615 | +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 */ |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1631 | + |
| 1632 | int hostapd_for_each_interface(struct hapd_interfaces *interfaces, |
| 1633 | int (*cb)(struct hostapd_iface *iface, |
| 1634 | void *ctx), void *ctx); |
| 1635 | diff --git a/src/ap/hw_features.c b/src/ap/hw_features.c |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1636 | index 85e67080d..8aa0b3ab5 100644 |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1637 | --- 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 | -- |
developer | 05f3b2b | 2024-08-19 19:17:34 +0800 | [diff] [blame] | 1649 | 2.18.0 |
developer | 66e89bc | 2024-04-23 14:50:01 +0800 | [diff] [blame] | 1650 | |