Introduce Automated Frequency Coordination (AFC) support for UNII-5 and UNII-7 6GHz bands. AFC client will connect to AFCD providing AP related parameter for AFC coordinator (e.g. geolocation, supported frequencies, ..). AFC is required for Standard Power Devices (SPDs) to determine a lists of channels and EIRP/PSD powers that are available in the 6GHz spectrum. Signed-off-by: Lorenzo Bianconi <lorenzo@xxxxxxxxxx> --- hostapd/Makefile | 6 + hostapd/config_file.c | 275 ++++++++++++++ hostapd/defconfig | 3 + hostapd/hostapd.conf | 42 +++ src/ap/afc.c | 815 ++++++++++++++++++++++++++++++++++++++++++ src/ap/ap_config.c | 15 + src/ap/ap_config.h | 47 +++ src/ap/hostapd.c | 53 +++ src/ap/hostapd.h | 28 ++ src/ap/hw_features.c | 2 + 10 files changed, 1286 insertions(+) create mode 100644 src/ap/afc.c diff --git a/hostapd/Makefile b/hostapd/Makefile index 94421d43b..0514b7bdc 100644 --- a/hostapd/Makefile +++ b/hostapd/Makefile @@ -103,6 +103,12 @@ CFLAGS += -DCONFIG_TAXONOMY OBJS += ../src/ap/taxonomy.o endif +ifdef CONFIG_AFC +CFLAGS += -DCONFIG_AFC +OBJS += ../src/ap/afc.o +LIBS += -ljson-c +endif + ifdef CONFIG_MODULE_TESTS CFLAGS += -DCONFIG_MODULE_TESTS OBJS += hapd_module_tests.o diff --git a/hostapd/config_file.c b/hostapd/config_file.c index 1af083917..1c3e612b3 100644 --- a/hostapd/config_file.c +++ b/hostapd/config_file.c @@ -2436,6 +2436,207 @@ static int get_u16(const char *pos, int line, u16 *ret_val) #endif /* CONFIG_IEEE80211BE */ +#ifdef CONFIG_AFC +static int hostapd_afc_parse_sock(struct hostapd_config *conf, char *pos) +{ + struct sockaddr_in6 *sin6 = &conf->afc.sock; + char addr[INET6_ADDRSTRLEN] = {}; + int port; + + if (sscanf(pos, "[%39[^]]]:%d", addr, &port) != 2) + return -EINVAL; + + memset(sin6, 0, sizeof(*sin6)); + sin6->sin6_family = AF_INET6; + sin6->sin6_port = htons(port); + return inet_pton(AF_INET6, addr, &sin6->sin6_addr) == 1 ? 0 : -EINVAL; +} + + +static int hostapd_afc_parse_cert_ids(struct hostapd_config *conf, char *pos) +{ + struct cert_id *c = NULL; + int i, count = 0; + + while (pos && pos[0]) { + char *p; + + c = os_realloc_array(c, count + 1, sizeof(*c)); + if (!c) + return -ENOMEM; + + i = count; + count++; + + p = os_strchr(pos, ':'); + if (!p) + goto error; + + *p++ = '\0'; + if (!p || !p[0]) + goto error; + + c[i].rulset = os_malloc(os_strlen(pos) + 1); + if (!c[i].rulset) + goto error; + + os_strlcpy(c[i].rulset, pos, os_strlen(pos) + 1); + pos = p; + p = os_strchr(pos, ','); + if (p) + *p++ = '\0'; + + c[i].id = os_malloc(os_strlen(pos) + 1); + if (!c[i].id) + goto error; + + os_strlcpy(c[i].id, pos, os_strlen(pos) + 1); + pos = p; + } + + conf->afc.n_cert_ids = count; + conf->afc.cert_ids = c; + + return 0; + +error: + for (i = 0; i < count; i++) { + os_free(c[i].rulset); + os_free(c[i].id); + } + os_free(c); + + return -ENOMEM; +} + + +static int hostapd_afc_parse_position_data(struct afc_linear_polygon **data, + unsigned int *n_linear_polygon_data, + char *pos) +{ + struct afc_linear_polygon *d = NULL; + int i, count = 0; + + while (pos && pos[0]) { + char *p, *end; + + d = os_realloc_array(d, count + 1, sizeof(*d)); + if (!d) + return -ENOMEM; + + i = count; + count++; + + p = os_strchr(pos, ':'); + if (!p) + goto error; + + *p++ = '\0'; + if (!p || !p[0]) + goto error; + + d[i].longitude = strtod(pos, &end); + if (*end) + goto error; + + pos = p; + p = os_strchr(pos, ','); + if (p) + *p++ = '\0'; + + d[i].latitude = strtod(pos, &end); + if (*end) + goto error; + + pos = p; + } + + *n_linear_polygon_data = count; + *data = d; + + return 0; + +error: + os_free(d); + return -ENOMEM; +} + + +static int hostapd_afc_parse_freq_range(struct hostapd_config *conf, char *pos) +{ + struct afc_freq_range *f = NULL; + int i, count = 0; + + while (pos && pos[0]) { + char *p; + + f = os_realloc_array(f, count + 1, sizeof(*f)); + if (!f) + return -ENOMEM; + + i = count; + count++; + + p = os_strchr(pos, ':'); + if (!p) + goto error; + + *p++ = '\0'; + if (!p || !p[0]) + goto error; + + f[i].low_freq = atoi(pos); + pos = p; + p = os_strchr(pos, ','); + if (p) + *p++ = '\0'; + + f[i].high_freq = atoi(pos); + pos = p; + } + + conf->afc.n_freq_range = count; + conf->afc.freq_range = f; + + return 0; + +error: + os_free(f); + return -ENOMEM; +} + + +static int hostapd_afc_parse_op_class(struct hostapd_config *conf, char *pos) +{ + unsigned int *oc = NULL; + int i, count = 0; + + while (pos && pos[0]) { + char *p; + + oc = os_realloc_array(oc, count + 1, sizeof(*oc)); + if (!oc) + return -ENOMEM; + + i = count; + count++; + + p = os_strchr(pos, ','); + if (p) + *p++ = '\0'; + + oc[i] = atoi(pos); + pos = p; + } + + conf->afc.n_op_class = count; + conf->afc.op_class = oc; + + return 0; +} +#endif /* CONFIG_AFC */ + + static int hostapd_config_fill(struct hostapd_config *conf, struct hostapd_bss_config *bss, const char *buf, char *pos, int line) @@ -3800,6 +4001,80 @@ static int hostapd_config_fill(struct hostapd_config *conf, return 1; } bss->unsol_bcast_probe_resp_interval = val; +#ifdef CONFIG_AFC + } else if (os_strcmp(buf, "afcd_sock") == 0) { + if (hostapd_afc_parse_sock(conf, pos)) + return 1; + } else if (os_strcmp(buf, "afc_request_version") == 0) { + conf->afc.request.version = os_malloc(os_strlen(pos) + 1); + if (!conf->afc.request.version) + return 1; + + os_strlcpy(conf->afc.request.version, pos, os_strlen(pos) + 1); + } else if (os_strcmp(buf, "afc_request_id") == 0) { + conf->afc.request.id = os_malloc(os_strlen(pos) + 1); + if (!conf->afc.request.id) + return 1; + + os_strlcpy(conf->afc.request.id, pos, os_strlen(pos) + 1); + } else if (os_strcmp(buf, "afc_serial_number") == 0) { + conf->afc.request.sn = os_malloc(os_strlen(pos) + 1); + if (!conf->afc.request.sn) + return 1; + + os_strlcpy(conf->afc.request.sn, pos, os_strlen(pos) + 1); + } else if (os_strcmp(buf, "afc_cert_ids") == 0) { + if (hostapd_afc_parse_cert_ids(conf, pos)) + return 1; + } else if (os_strcmp(buf, "afc_location_type") == 0) { + conf->afc.location.type = atoi(pos); + if (conf->afc.location.type != ELLIPSE && + conf->afc.location.type != LINEAR_POLYGON && + conf->afc.location.type != RADIAL_POLYGON) + return 1; + } else if (os_strcmp(buf, "afc_linear_polygon") == 0) { + if (hostapd_afc_parse_position_data( + &conf->afc.location.linear_polygon_data, + &conf->afc.location.n_linear_polygon_data, + pos)) + return 1; + } else if (os_strcmp(buf, "afc_radial_polygon") == 0) { + if (hostapd_afc_parse_position_data( + (struct afc_linear_polygon **) + &conf->afc.location.radial_polygon_data, + &conf->afc.location.n_radial_polygon_data, + pos)) + return 1; + } else if (os_strcmp(buf, "afc_major_axis") == 0) { + conf->afc.location.major_axis = atoi(pos); + } else if (os_strcmp(buf, "afc_minor_axis") == 0) { + conf->afc.location.minor_axis = atoi(pos); + } else if (os_strcmp(buf, "afc_orientation") == 0) { + conf->afc.location.orientation = atoi(pos); + } else if (os_strcmp(buf, "afc_height") == 0) { + char *end; + + conf->afc.location.height = strtod(pos, &end); + if (*end) + return 1; + } else if (os_strcmp(buf, "afc_height_type") == 0) { + conf->afc.location.height_type = os_malloc(os_strlen(pos) + 1); + if (!conf->afc.location.height_type) + return 1; + + os_strlcpy(conf->afc.location.height_type, pos, + os_strlen(pos) + 1); + } else if (os_strcmp(buf, "afc_vertical_tolerance") == 0) { + conf->afc.location.vertical_tolerance = atoi(pos); + } else if (os_strcmp(buf, "afc_min_power") == 0) { + conf->afc.min_power = atoi(pos); + } else if (os_strcmp(buf, "afc_freq_range") == 0) { + if (hostapd_afc_parse_freq_range(conf, pos)) + return 1; + } else if (os_strcmp(buf, "afc_op_class") == 0) { + if (hostapd_afc_parse_op_class(conf, pos)) + return 1; +#endif /* CONFIG_AFC */ } else if (os_strcmp(buf, "mbssid") == 0) { int mbssid = atoi(pos); if (mbssid < 0 || mbssid > ENHANCED_MBSSID_ENABLED) { diff --git a/hostapd/defconfig b/hostapd/defconfig index 2f5f3f6d0..ce2cdc977 100644 --- a/hostapd/defconfig +++ b/hostapd/defconfig @@ -422,3 +422,6 @@ CONFIG_DPP2=y # Wi-Fi Aware unsynchronized service discovery (NAN USD) #CONFIG_NAN_USD=y + +# Enable Automated Frequency Coordination for 6GHz outdoor +#CONFIG_AFC=y diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf index 13576499a..6f3e3ed29 100644 --- a/hostapd/hostapd.conf +++ b/hostapd/hostapd.conf @@ -1005,6 +1005,48 @@ wmm_ac_vo_acm=0 # Valid range: 0..20 TUs; default is 0 (disabled) #unsol_bcast_probe_resp_interval=0 +##### Automated Frequency Coordination for 6GHz UNII-5 and UNII-7 bands ####### + +# AFC daemon connection socket +#afcd_sock=[::ffff:127.0.0.1]:12345 + +# AFC request identification parameters +#afc_request_version=1.1 +#afc_request_id=11235813 +#afc_serial_number=abcdefg +#afc_cert_ids=US_47_CFR_PART_15_SUBPART_E:CID000 +# +# AFC location type: +# 0 = ellipse +# 1 = linear polygon +# 2 = radial polygon +#afc_location_type=0 +# +# AFC ellipse or linear polygon coordinations +#afc_linear_polygon=-122.984157:37.425056 +# +# AFC radial polygon coordinations +#afc_radial_polygon=118.8:92.76,76.44:87.456,98.56:123.33 +# +# AFC ellipse major/minor axis and orientation +#afc_major_axis=100 +#afc_minor_axis=50 +#afc_orientation=70 +# +# AFC device elevation parameters +#afc_height=3.0 +#afc_height_type=AGL +#afc_vertical_tolerance=7 +# +# AFC minimum desired TX power (dbm) +#afc_min_power=24 +# +# AFC request frequency ranges +#afc_freq_range=5925:6425,6525:6875 +# +# AFC request operation classes +#afc_op_class=131,132,133,134,136 + ##### IEEE 802.11be related configuration ##################################### #ieee80211be: Whether IEEE 802.11be (EHT) is enabled diff --git a/src/ap/afc.c b/src/ap/afc.c new file mode 100644 index 000000000..44ef55fec --- /dev/null +++ b/src/ap/afc.c @@ -0,0 +1,815 @@ +/* + * Automated Frequency Coordination + * Copyright (c) 2024, Lorenzo Bianconi <lorenzo@xxxxxxxxxx> + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include <json-c/json.h> +#include <time.h> + +#include "utils/includes.h" +#include "utils/common.h" +#include "utils/eloop.h" +#include "hostapd.h" +#include "acs.h" + +#define HOSTAPD_AFC_RETRY_TIMEOUT 180 +#define HOSTAPD_AFC_TIMEOUT 86400 /* 24h */ + +struct afc_curl_ctx { + int timeout; + char *buf; + size_t buf_len; +}; + +static void hostapd_afc_timeout_handler(void *eloop_ctx, void *timeout_ctx); + + +static struct json_object * +hostapd_afc_build_location_request(struct hostapd_iface *iface) +{ + struct json_object *location_obj, *center_obj, *ellipse_obj; + struct json_object *elevation_obj, *str_obj; + struct hostapd_config *iconf = iface->conf; + bool is_ap_indoor = he_reg_is_indoor(iconf->he_6ghz_reg_pwr_type); + + location_obj = json_object_new_object(); + if (!location_obj) + return NULL; + + if (iconf->afc.location.type != LINEAR_POLYGON) { + struct afc_linear_polygon *lp = + &iconf->afc.location.linear_polygon_data[0]; + + ellipse_obj = json_object_new_object(); + if (!ellipse_obj) + goto error; + + center_obj = json_object_new_object(); + if (!center_obj) + goto error; + + json_object_object_add(ellipse_obj, "center", center_obj); + + str_obj = json_object_new_double(lp->longitude); + if (!str_obj) + goto error; + + json_object_object_add(center_obj, "longitude", str_obj); + str_obj = json_object_new_double(lp->latitude); + if (!str_obj) + goto error; + + json_object_object_add(center_obj, "latitude", str_obj); + + } + + switch (iconf->afc.location.type) { + case LINEAR_POLYGON: { + struct json_object *outer_boundary_obj; + int i; + + outer_boundary_obj = json_object_new_object(); + if (!outer_boundary_obj) + goto error; + + json_object_object_add(location_obj, "linearPolygon", + outer_boundary_obj); + ellipse_obj = json_object_new_array(); + if (!ellipse_obj) + goto error; + + json_object_object_add(outer_boundary_obj, "outerBoundary", + ellipse_obj); + for (i = 0; + i < iconf->afc.location.n_linear_polygon_data; i++) { + struct afc_linear_polygon *lp = + &iconf->afc.location.linear_polygon_data[i]; + + center_obj = json_object_new_object(); + if (!center_obj) + goto error; + + json_object_array_add(ellipse_obj, center_obj); + str_obj = json_object_new_double(lp->longitude); + if (!str_obj) + goto error; + + json_object_object_add(center_obj, "longitude", + str_obj); + str_obj = json_object_new_double(lp->latitude); + if (!str_obj) + goto error; + + json_object_object_add(center_obj, "latitude", + str_obj); + } + break; + } + case RADIAL_POLYGON: { + struct json_object *outer_boundary_obj; + int i; + + json_object_object_add(location_obj, "radialPolygon", + ellipse_obj); + + outer_boundary_obj = json_object_new_array(); + if (!outer_boundary_obj) + goto error; + + json_object_object_add(ellipse_obj, "outerBoundary", + outer_boundary_obj); + for (i = 0; + i < iconf->afc.location.n_radial_polygon_data; i++) { + struct afc_radial_polygon *rp = + &iconf->afc.location.radial_polygon_data[i]; + struct json_object *angle_obj; + + angle_obj = json_object_new_object(); + if (!angle_obj) + goto error; + + json_object_array_add(outer_boundary_obj, angle_obj); + + str_obj = json_object_new_double(rp->angle); + if (!str_obj) + goto error; + + json_object_object_add(angle_obj, "angle", str_obj); + str_obj = json_object_new_double(rp->length); + if (!str_obj) + goto error; + + json_object_object_add(angle_obj, "length", str_obj); + } + break; + } + case ELLIPSE: + default: + json_object_object_add(location_obj, "ellipse", ellipse_obj); + + str_obj = json_object_new_int(iconf->afc.location.major_axis); + if (!str_obj) + goto error; + + json_object_object_add(ellipse_obj, "majorAxis", str_obj); + str_obj = json_object_new_int(iconf->afc.location.minor_axis); + if (!str_obj) + goto error; + + json_object_object_add(ellipse_obj, "minorAxis", str_obj); + str_obj = json_object_new_int(iconf->afc.location.orientation); + if (!str_obj) + goto error; + + json_object_object_add(ellipse_obj, "orientation", str_obj); + break; + } + + elevation_obj = json_object_new_object(); + if (!elevation_obj) + goto error; + + json_object_object_add(location_obj, "elevation", + elevation_obj); + str_obj = json_object_new_double(iconf->afc.location.height); + if (!str_obj) + goto error; + + json_object_object_add(elevation_obj, "height", str_obj); + str_obj = json_object_new_string(iconf->afc.location.height_type); + if (!str_obj) + goto error; + + json_object_object_add(elevation_obj, "heightType", str_obj); + str_obj = json_object_new_int(iconf->afc.location.vertical_tolerance); + if (!str_obj) + goto error; + + json_object_object_add(elevation_obj, "verticalUncertainty", + str_obj); + str_obj = json_object_new_int(is_ap_indoor); + if (!str_obj) + goto error; + + json_object_object_add(location_obj, "indoorDeployment", str_obj); + + return location_obj; + +error: + json_object_put(location_obj); + return NULL; +} + + +static void +hostapd_afc_get_opclass_chan_list(struct hostapd_iface *iface, u8 op_class, + u8 *chan_list, u16 *n_chan_list) +{ + struct hostapd_hw_modes *mode = iface->current_mode; + int i, count = 0; + + memset(chan_list, 0, mode->num_channels * sizeof(*chan_list)); + for (i = 0; i < mode->num_channels; i++) { + struct hostapd_channel_data *chan = &mode->channels[i]; + + if (!is_6ghz_freq(chan->freq)) + continue; + + if (ieee80211_chan_to_freq(iface->conf->country, op_class, + chan->chan) < 0) + continue; + + chan_list[count++] = chan->chan; + } + *n_chan_list = count; +} + + +static struct json_object * +hostapd_afc_build_request(struct hostapd_iface *iface) +{ + struct json_object *l1_obj, *l2_obj, *la1_obj, *la2_obj; + struct json_object *s2_obj, *str_obj, *location_obj; + struct hostapd_hw_modes *mode = iface->current_mode; + struct hostapd_config *iconf = iface->conf; + u8 *chan_list = NULL; + int i; + + l1_obj = json_object_new_object(); + if (!l1_obj) + return NULL; + + if (iconf->afc.request.version) { + str_obj = json_object_new_string(iconf->afc.request.version); + if (!str_obj) + goto error; + + json_object_object_add(l1_obj, "version", str_obj); + } + + la1_obj = json_object_new_array(); + if (!la1_obj) + goto error; + + json_object_object_add(l1_obj, "availableSpectrumInquiryRequests", + la1_obj); + l2_obj = json_object_new_object(); + if (!l2_obj) + goto error; + + json_object_array_add(la1_obj, l2_obj); + if (iconf->afc.request.id) { + str_obj = json_object_new_string(iconf->afc.request.id); + if (!str_obj) + goto error; + + json_object_object_add(l2_obj, "requestId", str_obj); + } + + s2_obj = json_object_new_object(); + if (!s2_obj) + goto error; + + json_object_object_add(l2_obj, "deviceDescriptor", s2_obj); + if (iconf->afc.request.sn) { + str_obj = json_object_new_string(iconf->afc.request.sn); + if (!str_obj) + goto error; + + json_object_object_add(s2_obj, "serialNumber", str_obj); + } + + la2_obj = json_object_new_array(); + if (!la2_obj) + goto error; + + json_object_object_add(s2_obj, "certificationId", la2_obj); + for (i = 0; i < iconf->afc.n_cert_ids; i++) { + struct json_object *obj; + + obj = json_object_new_object(); + if (!obj) + goto error; + + json_object_array_add(la2_obj, obj); + str_obj = + json_object_new_string(iconf->afc.cert_ids[i].rulset); + if (!str_obj) + goto error; + + json_object_object_add(obj, "rulesetId", str_obj); + str_obj = json_object_new_string(iconf->afc.cert_ids[i].id); + if (!str_obj) + goto error; + + json_object_object_add(obj, "id", str_obj); + } + + location_obj = hostapd_afc_build_location_request(iface); + if (!location_obj) + goto error; + + json_object_object_add(l2_obj, "location", location_obj); + str_obj = json_object_new_int(iconf->afc.min_power); + if (!str_obj) + goto error; + + json_object_object_add(l2_obj, "minDesiredPower", str_obj); + + if (iconf->afc.n_freq_range) { + struct json_object *freq_obj; + + freq_obj = json_object_new_array(); + if (!freq_obj) + goto error; + + json_object_object_add(l2_obj, "inquiredFrequencyRange", + freq_obj); + for (i = 0; i < iconf->afc.n_freq_range; i++) { + struct afc_freq_range *fr = &iconf->afc.freq_range[i]; + struct json_object *obj; + + obj = json_object_new_object(); + if (!obj) + goto error; + + json_object_array_add(freq_obj, obj); + str_obj = json_object_new_int(fr->low_freq); + if (!str_obj) + goto error; + + json_object_object_add(obj, "lowFrequency", str_obj); + str_obj = json_object_new_int(fr->high_freq); + if (!str_obj) + goto error; + + json_object_object_add(obj, "highFrequency", str_obj); + } + } + + if (iconf->afc.n_op_class) { + struct json_object *op_class_list_obj; + + chan_list = os_malloc(mode->num_channels * sizeof(*chan_list)); + if (!chan_list) + goto error; + + op_class_list_obj = json_object_new_array(); + if (!op_class_list_obj) + goto error; + + json_object_object_add(l2_obj, "inquiredChannels", + op_class_list_obj); + for (i = 0; i < iconf->afc.n_op_class; i++) { + struct json_object *op_class_obj, *chan_list_obj; + u16 n_chan_list = 0; + int j; + + hostapd_afc_get_opclass_chan_list( + iface, iconf->afc.op_class[i], + chan_list, &n_chan_list); + if (!n_chan_list) + continue; + + op_class_obj = json_object_new_object(); + if (!op_class_obj) + goto error; + + json_object_array_add(op_class_list_obj, op_class_obj); + str_obj = json_object_new_int(iconf->afc.op_class[i]); + if (!str_obj) + goto error; + + json_object_object_add(op_class_obj, + "globalOperatingClass", + str_obj); + chan_list_obj = json_object_new_array(); + if (!chan_list_obj) + goto error; + + json_object_object_add(op_class_obj, "channelCfi", + chan_list_obj); + for (j = 0; j < n_chan_list; j++) { + str_obj = json_object_new_int(chan_list[j]); + if (!str_obj) + goto error; + + json_object_array_add(chan_list_obj, str_obj); + } + } + free(chan_list); + } + + wpa_printf(MSG_DEBUG, "Pending AFC request: %s", + json_object_get_string(l1_obj)); + + return l1_obj; + +error: + free(chan_list); + json_object_put(l1_obj); + + return NULL; +} + + +static int +hostad_afc_parse_available_freq_info(struct hostapd_iface *iface, + struct json_object *reply_elem_obj) +{ + struct hostapd_hw_modes *mode = iface->current_mode; + struct afc_freq_range_elem *f = NULL; + struct json_object *obj; + int i, count = 0; + + if (!json_object_object_get_ex(reply_elem_obj, + "availableFrequencyInfo", &obj)) + return 0; + + for (i = 0; i < json_object_array_length(obj); i++) { + struct json_object *range_elem_obj, *freq_range_obj; + struct json_object *high_freq_obj, *low_freq_obj; + struct json_object *max_psd_obj; + + range_elem_obj = json_object_array_get_idx(obj, i); + if (!json_object_object_get_ex(range_elem_obj, + "frequencyRange", + &freq_range_obj)) + continue; + + if (!json_object_object_get_ex(freq_range_obj, + "lowFrequency", + &low_freq_obj)) + continue; + + if (!json_object_object_get_ex(freq_range_obj, + "highFrequency", + &high_freq_obj)) + continue; + + if (!json_object_object_get_ex(range_elem_obj, "maxPsd", + &max_psd_obj) && + !json_object_object_get_ex(range_elem_obj, "maxPSD", + &max_psd_obj)) + continue; + + f = os_realloc_array(f, count + 1, sizeof(*f)); + if (!f) + return -ENOMEM; + + f[count].low_freq = json_object_get_int(low_freq_obj); + f[count].high_freq = json_object_get_int(high_freq_obj); + f[count++].max_psd = json_object_get_int(max_psd_obj); + } + os_free(iface->afc.freq_range); + iface->afc.freq_range = f; + iface->afc.num_freq_range = count; + + for (i = 0; i < mode->num_channels; i++) { + struct hostapd_channel_data *chan = &mode->channels[i]; + int j; + + if (!is_6ghz_freq(chan->freq)) + continue; + + for (j = 0; j < count; j++) { + if (chan->freq >= f[j].low_freq && + chan->freq <= f[j].high_freq) + break; + } + + if (j == count) + chan->flag |= HOSTAPD_CHAN_DISABLED; + } + + return 0; +} + + +static int +hostad_afc_parse_available_chan_info(struct hostapd_iface *iface, + struct json_object *reply_elem_obj) +{ + struct json_object *obj; + int i; + + if (!json_object_object_get_ex(reply_elem_obj, + "availableChannelInfo", &obj)) + return 0; + + for (i = 0; i < json_object_array_length(obj); i++) { + struct hostapd_hw_modes *mode = iface->current_mode; + struct json_object *range_elem_obj, *op_class_obj; + struct json_object *chan_cfi_obj, *max_eirp_obj; + struct afc_chan_info_elem *c = NULL; + int j, ch, op_class, count = 0; + + range_elem_obj = json_object_array_get_idx(obj, i); + if (!json_object_object_get_ex(range_elem_obj, + "globalOperatingClass", + &op_class_obj)) + continue; + + op_class = json_object_get_int(op_class_obj); + if (op_class != iface->conf->op_class) + continue; + + if (!json_object_object_get_ex(range_elem_obj, "maxEirp", + &max_eirp_obj)) + continue; + + if (!json_object_object_get_ex(range_elem_obj, "channelCfi", + &chan_cfi_obj)) + continue; + + for (ch = 0; + ch < json_object_array_length(chan_cfi_obj); ch++) { + struct json_object *pwr_obj; + struct json_object *ch_obj; + int channel, power; + + ch_obj = json_object_array_get_idx(chan_cfi_obj, ch); + if (!ch_obj) + continue; + + pwr_obj = json_object_array_get_idx(max_eirp_obj, ch); + if (!pwr_obj) + continue; + + channel = json_object_get_int(ch_obj); + power = json_object_get_int(pwr_obj); + + c = os_realloc_array(c, count + 1, sizeof(*c)); + if (!c) + return -ENOMEM; + + c[count].chan = channel; + c[count++].power = power; + } + os_free(iface->afc.chan_info_list); + iface->afc.chan_info_list = c; + iface->afc.num_chan_info = count; + + for (j = 0; j < mode->num_channels; j++) { + struct hostapd_channel_data *chan = &mode->channels[j]; + int n; + + if (!is_6ghz_freq(chan->freq)) + continue; + + for (n = 0; n < count; n++) { + if (chan->chan == c[n].chan) + break; + } + + if (n == count) + chan->flag |= HOSTAPD_CHAN_DISABLED; + } + } + + return 0; +} + + +static int hostad_afc_get_timeout(struct json_object *obj) +{ + time_t t, now; + struct tm tm; + + if (sscanf(json_object_get_string(obj), "%d-%d-%dT%d:%d:%dZ", + &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, + &tm.tm_min, &tm.tm_sec) <= 0) + return HOSTAPD_AFC_TIMEOUT; + + tm.tm_year -= 1900; + tm.tm_mon -= 1; + tm.tm_isdst = -1; + t = mktime(&tm); + time(&now); + + return now > t ? HOSTAPD_AFC_RETRY_TIMEOUT : (t - now) * 80 / 100; +} + + +static int hostapd_afc_parse_reply(struct hostapd_iface *iface, char *reply) +{ + int i, request_timeout = HOSTAPD_AFC_RETRY_TIMEOUT, ret = -EINVAL; + struct json_object *payload_obj, *reply_obj, *version_obj; + struct hostapd_config *iconf = iface->conf; + + wpa_printf(MSG_DEBUG, "Received AFC reply: %s", reply); + payload_obj = json_tokener_parse(reply); + if (!payload_obj) + return -EINVAL; + + if (!json_object_object_get_ex(payload_obj, "version", &version_obj)) + return -EINVAL; + + if (iconf->afc.request.version && + os_strcmp(iconf->afc.request.version, + json_object_get_string(version_obj))) + return -EINVAL; + + if (!json_object_object_get_ex(payload_obj, + "availableSpectrumInquiryResponses", + &reply_obj)) + return -EINVAL; + + for (i = 0; i < json_object_array_length(reply_obj); i++) { + struct json_object *reply_elem_obj, *obj, *status_obj; + int j, status = -EINVAL; + + reply_elem_obj = json_object_array_get_idx(reply_obj, i); + if (!reply_elem_obj) + continue; + + if (!json_object_object_get_ex(reply_elem_obj, "requestId", + &obj)) + continue; + + if (iconf->afc.request.id && + os_strcmp(iconf->afc.request.id, + json_object_get_string(obj))) + continue; + + if (!json_object_object_get_ex(reply_elem_obj, "rulesetId", + &obj)) + continue; + + for (j = 0; j < iconf->afc.n_cert_ids; j++) { + if (!os_strcmp(iconf->afc.cert_ids[j].rulset, + json_object_get_string(obj))) + break; + } + + if (j == iconf->afc.n_cert_ids) + continue; + + if (!json_object_object_get_ex(reply_elem_obj, "response", + &obj)) + continue; + + if (json_object_object_get_ex(obj, "shortDescription", + &status_obj)) + wpa_printf(MSG_DEBUG, "AFC reply element %d: %s", + i, json_object_get_string(status_obj)); + + if (json_object_object_get_ex(obj, "responseCode", + &status_obj)) + status = json_object_get_int(status_obj); + + if (status < 0) + continue; + + if (hostad_afc_parse_available_freq_info(iface, + reply_elem_obj) || + hostad_afc_parse_available_chan_info(iface, + reply_elem_obj)) + continue; + + if (json_object_object_get_ex(reply_elem_obj, + "availabilityExpireTime", + &obj)) { + int timeout = hostad_afc_get_timeout(obj); + + if (timeout < request_timeout) + request_timeout = timeout; + } + + ret = status; + } + + return ret < 0 ? ret : request_timeout; +} + + +#define BUFSIZE 4096 +static int hostapd_afc_send_receive(struct hostapd_iface *iface) +{ + int sockfd, err, request_timeout = HOSTAPD_AFC_RETRY_TIMEOUT; + struct hostapd_config *iconf = iface->conf; + json_object *request_obj = NULL; + struct timeval sock_timeout = { + .tv_sec = 2, + }; + char buf[BUFSIZE] = {}; + const char *request; + fd_set read_set; + + if (eloop_is_timeout_registered(hostapd_afc_timeout_handler, + iface, NULL)) + return 0; + + sockfd = socket(AF_INET6, SOCK_STREAM, 0); + if (sockfd < 0) { + wpa_printf(MSG_ERROR, "Failed creating AFC socket"); + return sockfd; + } + + err = connect(sockfd, (struct sockaddr *)&iconf->afc.sock, + sizeof(iconf->afc.sock)); + if (err < 0) { + wpa_printf(MSG_ERROR, "Failed connecting AFC socket"); + goto out; + } + + request_obj = hostapd_afc_build_request(iface); + if (!request_obj) { + err = -ENOMEM; + goto out; + } + + request = json_object_to_json_string(request_obj); + err = send(sockfd, request, strlen(request), 0); + if (err < 0) { + wpa_printf(MSG_ERROR, "Failed sending AFC request"); + goto out; + } + + FD_ZERO(&read_set); + FD_SET(sockfd, &read_set); + if (select(sockfd + 1, &read_set, NULL, NULL, &sock_timeout) < 0) { + wpa_printf(MSG_ERROR, "Select failed on AFC socket"); + err = -errno; + goto out; + } + + if (!FD_ISSET(sockfd, &read_set)) + goto out; + + if (recv(sockfd, buf, sizeof(buf), 0) <= 0) + goto out; + + err = hostapd_afc_parse_reply(iface, buf); + if (err > 0) { + request_timeout = err; + err = 0; + } +out: + close(sockfd); + json_object_put(request_obj); + eloop_register_timeout(request_timeout, 0, hostapd_afc_timeout_handler, + iface, NULL); + + return err; +} + + +int hostapd_afc_handle_request(struct hostapd_iface *iface) +{ + struct hostapd_config *iconf = iface->conf; + int ret; + + if (iface->afc.completed) + return 1; + + /* AFC is required just for standard power AP */ + if (!he_reg_is_sp(iconf->he_6ghz_reg_pwr_type)) + return 1; + + if (!is_6ghz_op_class(iconf->op_class) || !is_6ghz_freq(iface->freq)) + return 1; + + ret = hostapd_afc_send_receive(iface); + if (ret) + return ret; + + /* Trigger a ACS freq scan */ + iface->freq = 0; + iconf->channel = 0; + iface->afc.completed = true; + + if (acs_init(iface) != HOSTAPD_CHAN_ACS) { + wpa_printf(MSG_ERROR, "Could not start ACS"); + ret = -EINVAL; + } + + return ret; +} + + +static void hostapd_afc_timeout_handler(void *eloop_ctx, void *timeout_ctx) +{ + struct hostapd_iface *iface = eloop_ctx; + + if (!hostapd_afc_send_receive(iface)) { + struct hostapd_hw_modes *mode = iface->current_mode; + int i; + + for (i = 0; i < mode->num_channels; i++) { + struct hostapd_channel_data *chan = &mode->channels[i]; + + if (chan->freq == iface->freq && + !(chan->flag & HOSTAPD_CHAN_DISABLED)) + return; + } + } + + /* Toggle interface to trigger new AFC connection */ + iface->afc.completed = false; + hostapd_disable_iface(iface); + hostapd_enable_iface(iface); +} diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c index f760e340b..460536510 100644 --- a/src/ap/ap_config.c +++ b/src/ap/ap_config.c @@ -1029,6 +1029,21 @@ void hostapd_config_free(struct hostapd_config *conf) #endif /* CONFIG_ACS */ wpabuf_free(conf->lci); wpabuf_free(conf->civic); +#ifdef CONFIG_AFC + os_free(conf->afc.request.version); + os_free(conf->afc.request.id); + os_free(conf->afc.request.sn); + for (i = 0; i < conf->afc.n_cert_ids; i++) { + os_free(conf->afc.cert_ids[i].rulset); + os_free(conf->afc.cert_ids[i].id); + } + os_free(conf->afc.cert_ids); + os_free(conf->afc.location.height_type); + os_free(conf->afc.location.linear_polygon_data); + os_free(conf->afc.location.radial_polygon_data); + os_free(conf->afc.freq_range); + os_free(conf->afc.op_class); +#endif /* CONFIG_AFC */ os_free(conf); } diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h index de02ddafd..310a6f151 100644 --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h @@ -1217,6 +1217,53 @@ struct hostapd_config { MBSSID_ENABLED = 1, ENHANCED_MBSSID_ENABLED = 2, } mbssid; + +#ifdef CONFIG_AFC + struct { + struct sockaddr_in6 sock; + struct { + char *version; + char *id; + char *sn; + } request; + unsigned int n_cert_ids; + struct cert_id { + char *rulset; + char *id; + } *cert_ids; + struct { + enum afc_location_type { + ELLIPSE, + LINEAR_POLYGON, + RADIAL_POLYGON, + } type; + unsigned int n_linear_polygon_data; + struct afc_linear_polygon { + double longitude; + double latitude; + } *linear_polygon_data; + unsigned int n_radial_polygon_data; + struct afc_radial_polygon { + double length; + double angle; + } *radial_polygon_data; + int major_axis; + int minor_axis; + int orientation; + double height; + char *height_type; + int vertical_tolerance; + } location; + unsigned int n_freq_range; + struct afc_freq_range { + int low_freq; + int high_freq; + } *freq_range; + unsigned int n_op_class; + unsigned int *op_class; + int min_power; + } afc; +#endif /* CONFIG_AFC */ }; diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c index ddbcabc49..d0ea86811 100644 --- a/src/ap/hostapd.c +++ b/src/ap/hostapd.c @@ -2415,6 +2415,16 @@ static int hostapd_setup_interface_complete_sync(struct hostapd_iface *iface, } #endif /* CONFIG_MESH */ +#ifdef CONFIG_AFC + /* check AFC for 6GHz channels. */ + res = hostapd_afc_handle_request(iface); + if (res <= 0) { + if (res < 0) + goto fail; + return res; + } +#endif /* CONFIG_AFC */ + if (!delay_apply_cfg && hostapd_set_freq(hapd, hapd->iconf->hw_mode, iface->freq, hapd->iconf->channel, @@ -2847,6 +2857,10 @@ void hostapd_interface_free(struct hostapd_iface *iface) os_free(iface->bss[j]); } hostapd_cleanup_iface(iface); +#ifdef CONFIG_AFC + os_free(iface->afc.chan_info_list); + os_free(iface->afc.freq_range); +#endif } @@ -4423,3 +4437,42 @@ struct hostapd_data * hostapd_mld_get_link_bss(struct hostapd_data *hapd, return NULL; } #endif /* CONFIG_IEEE80211BE */ + + +void hostap_afc_disable_channels(struct hostapd_iface *iface) +{ +#ifdef CONFIG_AFC + struct hostapd_hw_modes *mode; + int i; + + if (iface->num_hw_features < HOSTAPD_MODE_IEEE80211A) + return; + + if (!he_reg_is_sp(iface->conf->he_6ghz_reg_pwr_type)) + return; + + mode = &iface->hw_features[HOSTAPD_MODE_IEEE80211A]; + for (i = 0; i < mode->num_channels; i++) { + struct hostapd_channel_data *chan = &mode->channels[i]; + int j; + + if (!is_6ghz_freq(iface->freq)) + continue; + + for (j = 0; j < iface->afc.num_freq_range; j++) { + if (chan->freq >= iface->afc.freq_range[j].low_freq || + chan->freq <= iface->afc.freq_range[j].high_freq) + break; + } + if (j && j == iface->afc.num_freq_range) + chan->flag |= HOSTAPD_CHAN_DISABLED; + + for (j = 0; j < iface->afc.num_chan_info; j++) { + if (chan->chan == iface->afc.chan_info_list[j].chan) + break; + } + if (j && j == iface->afc.num_chan_info) + chan->flag |= HOSTAPD_CHAN_DISABLED; + } +#endif /* CONFIG_AFC */ +} diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h index 333955486..bdb3075be 100644 --- a/src/ap/hostapd.h +++ b/src/ap/hostapd.h @@ -674,9 +674,37 @@ struct hostapd_iface { /* Configured freq of interface is NO_IR */ bool is_no_ir; + +#ifdef CONFIG_AFC + struct { + bool completed; + unsigned int num_freq_range; + struct afc_freq_range_elem { + int low_freq; + int high_freq; + /** + * max eirp power spectral density received from + * the AFC coordinator for this band + */ + int max_psd; + } *freq_range; + unsigned int num_chan_info; + struct afc_chan_info_elem { + int chan; + /** + * max eirp power received from the AFC coordinator + * for this channel + */ + int power; + } *chan_info_list; + } afc; +#endif /* CONFIG_AFC */ }; /* hostapd.c */ +void hostap_afc_disable_channels(struct hostapd_iface *iface); +int hostapd_afc_handle_request(struct hostapd_iface *iface); + int hostapd_for_each_interface(struct hapd_interfaces *interfaces, int (*cb)(struct hostapd_iface *iface, void *ctx), void *ctx); diff --git a/src/ap/hw_features.c b/src/ap/hw_features.c index 596f2f020..62a53c269 100644 --- a/src/ap/hw_features.c +++ b/src/ap/hw_features.c @@ -116,6 +116,8 @@ int hostapd_get_hw_features(struct hostapd_iface *iface) iface->hw_features = modes; iface->num_hw_features = num_modes; + hostap_afc_disable_channels(iface); + for (i = 0; i < num_modes; i++) { struct hostapd_hw_modes *feature = &modes[i]; int dfs_enabled = hapd->iconf->ieee80211h && -- 2.43.2 _______________________________________________ Hostap mailing list Hostap@xxxxxxxxxxxxxxxxxxx http://lists.infradead.org/mailman/listinfo/hostap