On Tue, May 14, 2024 at 5:39 PM Lorenzo Bianconi <lorenzo@xxxxxxxxxx> wrote: > +static int hostapd_afc_parse_reply(struct hostapd_iface *iface, char *reply) > +{ > + struct json_object *payload_obj, *reply_obj, *version_obj; > + struct hostapd_config *iconf = iface->conf; > + int i, request_timeout = -1, ret = -EINVAL; > + > + wpa_printf(MSG_DEBUG, "Received AFC reply: %s", reply); > + payload_obj = json_tokener_parse(reply); > + if (!payload_obj) > + return -EINVAL; > + > + if (!json_object_object_get_ex(payload_obj, "version", &version_obj)) > + return -EINVAL; > + > + if (iconf->afc.request.version && > + os_strcmp(iconf->afc.request.version, > + json_object_get_string(version_obj))) > + return -EINVAL; > + > + if (!json_object_object_get_ex(payload_obj, > + "availableSpectrumInquiryResponses", > + &reply_obj)) > + return -EINVAL; > + > + for (i = 0; i < json_object_array_length(reply_obj); i++) { > + struct json_object *reply_elem_obj, *obj, *status_obj; > + int j, status = -EINVAL; > + > + reply_elem_obj = json_object_array_get_idx(reply_obj, i); > + if (!reply_elem_obj) > + continue; > + > + if (!json_object_object_get_ex(reply_elem_obj, "requestId", > + &obj)) > + continue; > + > + if (iconf->afc.request.id && > + os_strcmp(iconf->afc.request.id, > + json_object_get_string(obj))) > + continue; > + > + if (!json_object_object_get_ex(reply_elem_obj, "rulesetId", > + &obj)) > + continue; > + > + for (j = 0; j < iconf->afc.n_cert_ids; j++) { > + if (!os_strcmp(iconf->afc.cert_ids[j].rulset, > + json_object_get_string(obj))) > + break; > + } > + > + if (j == iconf->afc.n_cert_ids) > + continue; > + > + if (!json_object_object_get_ex(reply_elem_obj, "response", > + &obj)) > + continue; > + > + if (json_object_object_get_ex(obj, "shortDescription", > + &status_obj)) > + wpa_printf(MSG_DEBUG, "AFC reply element %d: %s", > + i, json_object_get_string(status_obj)); > + > + if (json_object_object_get_ex(obj, "responseCode", > + &status_obj)) > + status = json_object_get_int(status_obj); > + > + if (status < 0) > + continue; > + > + if (hostad_afc_parse_available_freq_info(iface, > + reply_elem_obj) || > + hostad_afc_parse_available_chan_info(iface, > + reply_elem_obj)) > + continue; > + > + if (json_object_object_get_ex(reply_elem_obj, > + "availabilityExpireTime", > + &obj)) { > + int timeout = hostad_afc_get_timeout(obj); > + > + if (request_timeout < 0 || timeout < request_timeout) > + request_timeout = timeout; > + } > + > + ret = status; > + } > + > + iface->afc.data_valid = true; > + iface->afc.timeout = request_timeout; > + if (iface->afc.timeout < 0) > + iface->afc.timeout = HOSTAPD_AFC_RETRY_TIMEOUT; > + > + return ret; > +} IMHO, this function could use a bit of error logging, as it is a parser, something like the below (GH co-pilot generated) diff --git a/src/ap/afc.c b/src/ap/afc.c index 053c8d267..34bcddd46 100644 --- a/src/ap/afc.c +++ b/src/ap/afc.c @@ -630,77 +630,86 @@ static int hostapd_afc_parse_reply(struct hostapd_iface *iface, char *reply) wpa_printf(MSG_DEBUG, "Received AFC reply: %s", reply); payload_obj = json_tokener_parse(reply); - if (!payload_obj) + if (!payload_obj) { + wpa_printf(MSG_ERROR, "Failed to parse AFC reply payload"); return -EINVAL; + } - if (!json_object_object_get_ex(payload_obj, "version", &version_obj)) + if (!json_object_object_get_ex(payload_obj, "version", &version_obj)) { + wpa_printf(MSG_ERROR, "Missing version field in AFC reply"); return -EINVAL; + } if (iconf->afc.request.version && - os_strcmp(iconf->afc.request.version, - json_object_get_string(version_obj))) + os_strcmp(iconf->afc.request.version, + json_object_get_string(version_obj))) { + wpa_printf(MSG_ERROR, "Mismatch in AFC reply version"); return -EINVAL; + } if (!json_object_object_get_ex(payload_obj, - "availableSpectrumInquiryResponses", - &reply_obj)) + "availableSpectrumInquiryResponses", + &reply_obj)) { + wpa_printf(MSG_ERROR, "Missing availableSpectrumInquiryResponses field in AFC reply"); 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) + if (!reply_elem_obj) { + wpa_printf(MSG_ERROR, "Failed to get reply element at index %d", i); continue; + } - if (!json_object_object_get_ex(reply_elem_obj, "requestId", - &obj)) + if (!json_object_object_get_ex(reply_elem_obj, "requestId", &obj)) { + wpa_printf(MSG_ERROR, "Missing requestId field in reply element %d", i); continue; + } - if (iconf->afc.request.id && - os_strcmp(iconf->afc.request.id, - json_object_get_string(obj))) + if (iconf->afc.request.id && os_strcmp(iconf->afc.request.id, json_object_get_string(obj))) { + wpa_printf(MSG_DEBUG, "Skipping reply element %d due to mismatch in requestId", i); continue; + } - if (!json_object_object_get_ex(reply_elem_obj, "rulesetId", - &obj)) + if (!json_object_object_get_ex(reply_elem_obj, "rulesetId", &obj)) { + wpa_printf(MSG_ERROR, "Missing rulesetId field in reply element %d", i); 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))) + if (!os_strcmp(iconf->afc.cert_ids[j].rulset, json_object_get_string(obj))) break; } - if (j == iconf->afc.n_cert_ids) + if (j == iconf->afc.n_cert_ids) { + wpa_printf(MSG_DEBUG, "Skipping reply element %d due to mismatch in rulesetId", i); continue; + } - if (!json_object_object_get_ex(reply_elem_obj, "response", - &obj)) + if (!json_object_object_get_ex(reply_elem_obj, "response", &obj)) { + wpa_printf(MSG_ERROR, "Missing response field in reply element %d", i); 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, "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)) + if (json_object_object_get_ex(obj, "responseCode", &status_obj)) status = json_object_get_int(status_obj); - if (status < 0) + if (status < 0) { + wpa_printf(MSG_DEBUG, "Skipping reply element %d due to negative responseCode", i); continue; + } - if (hostad_afc_parse_available_freq_info(iface, - reply_elem_obj) || - hostad_afc_parse_available_chan_info(iface, - reply_elem_obj)) + 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)) { + if (json_object_object_get_ex(reply_elem_obj, "availabilityExpireTime", &obj)) { int timeout = hostad_afc_get_timeout(obj); if (request_timeout < 0 || timeout < request_timeout) > + > +static int hostapd_afc_send_receive(struct hostapd_iface *iface) > +{ > + struct hostapd_config *iconf = iface->conf; > + json_object *request_obj = NULL; > + struct timeval sock_timeout = { > + .tv_sec = 5, > + }; > + struct sockaddr_un addr = { > + .sun_family = AF_UNIX, > +#ifdef __FreeBSD__ > + .sun_len = sizeof(addr), > +#endif /* __FreeBSD__ */ > + }; > + const char *request; > + char *buf = NULL; > + int sockfd, ret; > + fd_set read_set; > + > + if (iface->afc.data_valid) { > + /* AFC data already downloaded from the server */ > + return 0; > + } > + > + if (!iconf->afc.socket) { > + wpa_printf(MSG_ERROR, "Missing AFC socket string"); > + return -EINVAL; > + } > + > + iface->afc.timeout = HOSTAPD_AFC_RETRY_TIMEOUT; > + if (os_strlen(iconf->afc.socket) >= sizeof(addr.sun_path)) { > + wpa_printf(MSG_ERROR, "Malformed AFC socket string %s", > + iconf->afc.socket); > + return -EINVAL; > + } > + > + os_strlcpy(addr.sun_path, iconf->afc.socket, sizeof(addr.sun_path)); > + sockfd = socket(AF_UNIX, SOCK_STREAM, 0); > + if (sockfd < 0) { > + wpa_printf(MSG_ERROR, "Failed creating AFC socket"); > + return sockfd; > + } > + > + if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { > + wpa_printf(MSG_ERROR, "Failed connecting AFC socket"); > + ret = -EIO; > + goto close_sock; > + } > + > + request_obj = hostapd_afc_build_request(iface); > + if (!request_obj) { > + ret = -ENOMEM; > + goto close_sock; > + } > + > + request = json_object_to_json_string(request_obj); > + if (send(sockfd, request, strlen(request), 0) < 0) { > + wpa_printf(MSG_ERROR, "Failed sending AFC request"); > + ret = -EIO; > + goto close_sock; > + } > + > + FD_ZERO(&read_set); > + FD_SET(sockfd, &read_set); > + if (select(sockfd + 1, &read_set, NULL, NULL, &sock_timeout) < 0) { > + wpa_printf(MSG_ERROR, "Select failed on AFC socket"); > + ret = -errno; > + goto close_sock; > + } > + > + if (!FD_ISSET(sockfd, &read_set)) { > + ret = -EIO; > + goto close_sock; > + } > + > + buf = os_zalloc(HOSTAPD_AFC_BUFSIZE); > + if (!buf) { > + ret = -ENOMEM; > + goto close_sock; > + } > + > + ret = recv(sockfd, buf, HOSTAPD_AFC_BUFSIZE - 1, 0); > + if (ret <= 0) > + goto close_sock; > + > + ret = hostapd_afc_parse_reply(iface, buf); > + if (ret) > + wpa_printf(MSG_ERROR, "Failed parsing AFC reply: %d", ret); > +close_sock: > + os_free(buf); > + json_object_put(request_obj); > + close(sockfd); > + > + return ret; > +} > + > + > +static bool hostapd_afc_has_usable_chans(struct hostapd_iface *iface) > +{ > + const struct oper_class_map *oper_class; > + int ch; > + > + oper_class = get_oper_class(NULL, iface->conf->op_class); > + if (!oper_class) > + return false; > + > + for (ch = oper_class->min_chan; ch <= oper_class->max_chan; > + ch += oper_class->inc) { > + struct hostapd_hw_modes *mode = iface->current_mode; > + int i; > + > + for (i = 0; i < mode->num_channels; i++) { > + struct hostapd_channel_data *chan = &mode->channels[i]; > + > + if (chan->chan == ch && > + !(chan->flag & HOSTAPD_CHAN_DISABLED)) > + return true; > + } > + } > + > + return false; > +} > + > + > +int hostapd_afc_handle_request(struct hostapd_iface *iface) > +{ > + struct hostapd_config *iconf = iface->conf; > + int ret; > + > + /* AFC is required just for standard power AP */ > + if (!he_reg_is_sp(iconf->he_6ghz_reg_pwr_type)) > + return 1; > + > + if (!is_6ghz_op_class(iconf->op_class) || !is_6ghz_freq(iface->freq)) > + return 1; > + > + if (iface->state == HAPD_IFACE_ACS) > + return 1; > + > + ret = hostapd_afc_send_receive(iface); > + if (ret < 0) { > + /* > + * If the connection to the AFCD failed, resched for a > + * future attempt. > + */ > + wpa_printf(MSG_ERROR, "AFC connection failed: %d", ret); > + if (ret == -EIO) > + ret = 0; > + goto resched; > + } > + > + hostap_afc_disable_channels(iface); > + if (!hostapd_afc_has_usable_chans(iface)) > + goto resched; > + > + /* Trigger an ACS freq scan */ > + iconf->channel = 0; > + iface->freq = 0; > + > + if (acs_init(iface) != HOSTAPD_CHAN_ACS) { > + wpa_printf(MSG_ERROR, "Could not start ACS"); > + ret = -EINVAL; > + } > + > +resched: > + eloop_cancel_timeout(hostapd_afc_timeout_handler, iface, NULL); > + eloop_register_timeout(iface->afc.timeout, 0, > + hostapd_afc_timeout_handler, iface, NULL); > + > + return ret; > +} > + > + > +static void hostapd_afc_delete_data_from_server(struct hostapd_iface *iface) > +{ > + os_free(iface->afc.chan_info_list); > + os_free(iface->afc.freq_range); > + > + iface->afc.num_freq_range = 0; > + iface->afc.num_chan_info = 0; > + > + iface->afc.chan_info_list = NULL; > + iface->afc.freq_range = NULL; > + > + iface->afc.data_valid = false; > +} > + > + > +static void hostapd_afc_timeout_handler(void *eloop_ctx, void *timeout_ctx) > +{ > + struct hostapd_iface *iface = eloop_ctx; > + bool restart_iface = true; > + > + hostapd_afc_delete_data_from_server(iface); > + if (iface->state != HAPD_IFACE_ENABLED) { > + /* Hostapd is not fully enabled yet, toggle the interface */ > + goto restart_interface; > + } > + > + if (hostapd_afc_send_receive(iface) < 0 || > + hostapd_get_hw_features(iface)) { > + restart_iface = false; > + goto restart_interface; > + } > + > + if (hostapd_is_usable_chans(iface)) > + goto resched; > + > + restart_iface = hostapd_afc_has_usable_chans(iface); > + if (restart_iface) { > + /* Trigger an ACS freq scan */ > + iface->conf->channel = 0; > + iface->freq = 0; > + } > + > +restart_interface: > + hostapd_disable_iface(iface); > + if (restart_iface) > + hostapd_enable_iface(iface); > +resched: > + eloop_register_timeout(iface->afc.timeout, 0, > + hostapd_afc_timeout_handler, iface, NULL); > +} > + > + > +void hostapd_afc_stop(struct hostapd_iface *iface) > +{ > + eloop_cancel_timeout(hostapd_afc_timeout_handler, iface, NULL); > +} > + > + > +void hostap_afc_disable_channels(struct hostapd_iface *iface) > +{ > + struct hostapd_hw_modes *mode; > + int i; > + > + if (iface->num_hw_features < HOSTAPD_MODE_IEEE80211A) > + return; > + > + if (!he_reg_is_sp(iface->conf->he_6ghz_reg_pwr_type)) > + return; > + > + if (!iface->afc.data_valid) > + return; > + > + mode = &iface->hw_features[HOSTAPD_MODE_IEEE80211A]; > + for (i = 0; i < mode->num_channels; i++) { > + struct hostapd_channel_data *chan = &mode->channels[i]; > + int j; > + > + if (!is_6ghz_freq(chan->freq)) > + continue; > + > + for (j = 0; j < iface->afc.num_freq_range; j++) { > + if (chan->freq >= iface->afc.freq_range[j].low_freq && > + chan->freq <= iface->afc.freq_range[j].high_freq) > + break; > + } > + > + if (j != iface->afc.num_freq_range) > + continue; > + > + for (j = 0; j < iface->afc.num_chan_info; j++) { > + if (chan->chan == iface->afc.chan_info_list[j].chan) > + break; > + } > + > + if (j != iface->afc.num_chan_info) > + continue; > + > + chan->flag |= HOSTAPD_CHAN_DISABLED; > + wpa_printf(MSG_MSGDUMP, > + "Disabling freq=%d MHz (not allowed by AFC)", > + chan->freq); > + } > +} > diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c > index e1910d422..2282a574b 100644 > --- a/src/ap/ap_config.c > +++ b/src/ap/ap_config.c > @@ -1036,6 +1036,22 @@ 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.socket); > + os_free(conf->afc.request.version); > + os_free(conf->afc.request.id); > + os_free(conf->afc.request.sn); > + for (i = 0; i < conf->afc.n_cert_ids; i++) { > + os_free(conf->afc.cert_ids[i].rulset); > + os_free(conf->afc.cert_ids[i].id); > + } > + os_free(conf->afc.cert_ids); > + os_free(conf->afc.location.height_type); > + os_free(conf->afc.location.linear_polygon_data); > + os_free(conf->afc.location.radial_polygon_data); > + os_free(conf->afc.freq_range); > + os_free(conf->afc.op_class); > +#endif /* CONFIG_AFC */ > > os_free(conf); > } > diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h > index 49a2cea16..86c78828f 100644 > --- a/src/ap/ap_config.h > +++ b/src/ap/ap_config.h > @@ -1229,6 +1229,53 @@ struct hostapd_config { > > /* Whether to enable TWT responder in HT and VHT modes */ > bool ht_vht_twt_responder; > + > +#ifdef CONFIG_AFC > + struct { > + char *socket; > + struct { > + char *version; > + char *id; > + char *sn; > + } request; > + unsigned int n_cert_ids; > + struct cert_id { > + char *rulset; > + char *id; > + } *cert_ids; > + struct { > + enum afc_location_type { > + ELLIPSE, > + LINEAR_POLYGON, > + RADIAL_POLYGON, > + } type; > + unsigned int n_linear_polygon_data; > + struct afc_linear_polygon { > + double longitude; > + double latitude; > + } *linear_polygon_data; > + unsigned int n_radial_polygon_data; > + struct afc_radial_polygon { > + double length; > + double angle; > + } *radial_polygon_data; > + int major_axis; > + int minor_axis; > + int orientation; > + double height; > + char *height_type; > + int vertical_tolerance; > + } location; > + unsigned int n_freq_range; > + struct afc_freq_range { > + int low_freq; > + int high_freq; > + } *freq_range; > + unsigned int n_op_class; > + unsigned int *op_class; > + int min_power; > + } afc; > +#endif /* CONFIG_AFC */ > }; > > > diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c > index 0506b418f..c1293a612 100644 > --- a/src/ap/hostapd.c > +++ b/src/ap/hostapd.c > @@ -715,6 +715,7 @@ void hostapd_cleanup_iface_partial(struct hostapd_iface *iface) > static void hostapd_cleanup_iface(struct hostapd_iface *iface) > { > wpa_printf(MSG_DEBUG, "%s(%p)", __func__, iface); > + hostapd_afc_stop(iface); > eloop_cancel_timeout(hostapd_interface_setup_failure_handler, iface, > NULL); > > @@ -2498,6 +2499,16 @@ static int hostapd_setup_interface_complete_sync(struct hostapd_iface *iface, > } > #endif /* CONFIG_MESH */ > > +#ifdef CONFIG_IEEE80211AX > + /* check AFC for 6GHz channels. */ > + res = hostapd_afc_handle_request(iface); > + if (res <= 0) { > + if (res < 0) > + goto fail; > + return res; > + } > +#endif /* CONFIG_IEEE80211AX */ > + > if (!delay_apply_cfg && > hostapd_set_freq(hapd, hapd->iconf->hw_mode, iface->freq, > hapd->iconf->channel, > @@ -2896,6 +2907,7 @@ void hostapd_interface_deinit(struct hostapd_iface *iface) > > hostapd_set_state(iface, HAPD_IFACE_DISABLED); > > + hostapd_afc_stop(iface); > eloop_cancel_timeout(channel_list_update_timeout, iface, NULL); > iface->wait_channel_update = 0; > iface->is_no_ir = false; > @@ -2969,6 +2981,10 @@ void hostapd_interface_free(struct hostapd_iface *iface) > __func__, iface->bss[j]); > os_free(iface->bss[j]); > } > +#ifdef CONFIG_AFC > + os_free(iface->afc.chan_info_list); > + os_free(iface->afc.freq_range); > +#endif > hostapd_cleanup_iface(iface); > } > > diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h > index 1d1943ac5..88d111022 100644 > --- a/src/ap/hostapd.h > +++ b/src/ap/hostapd.h > @@ -710,9 +710,54 @@ struct hostapd_iface { > bool is_no_ir; > > bool is_ch_switch_dfs; /* Channel switch from ACS to DFS */ > + > +#ifdef CONFIG_AFC > + struct { > + int timeout; > + unsigned int num_freq_range; > + struct afc_freq_range_elem { > + int low_freq; > + int high_freq; > + /** > + * max eirp power spectral density received from > + * the AFC coordinator for this band > + */ > + int max_psd; > + } *freq_range; > + unsigned int num_chan_info; > + struct afc_chan_info_elem { > + int chan; > + /** > + * max eirp power received from the AFC coordinator > + * for this channel > + */ > + int power; > + } *chan_info_list; > + bool data_valid; > + } afc; > +#endif /* CONFIG_AFC */ > }; > > /* hostapd.c */ > +#ifdef CONFIG_AFC > +int hostapd_afc_handle_request(struct hostapd_iface *iface); > +void hostapd_afc_stop(struct hostapd_iface *iface); > +void hostap_afc_disable_channels(struct hostapd_iface *iface); > +#else > +static inline int hostapd_afc_handle_request(struct hostapd_iface *iface) > +{ > + return 1; > +} > + > +static inline void hostapd_afc_stop(struct hostapd_iface *iface) > +{ > +} > + > +static inline void hostap_afc_disable_channels(struct hostapd_iface *iface) > +{ > +} > +#endif /* CONFIG_AFC */ > + > int hostapd_for_each_interface(struct hapd_interfaces *interfaces, > int (*cb)(struct hostapd_iface *iface, > void *ctx), void *ctx); > diff --git a/src/ap/hw_features.c b/src/ap/hw_features.c > index 85e67080d..8aa0b3ab5 100644 > --- a/src/ap/hw_features.c > +++ b/src/ap/hw_features.c > @@ -114,6 +114,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.45.0 > > > _______________________________________________ > Hostap mailing list > Hostap@xxxxxxxxxxxxxxxxxxx > http://lists.infradead.org/mailman/listinfo/hostap _______________________________________________ Hostap mailing list Hostap@xxxxxxxxxxxxxxxxxxx http://lists.infradead.org/mailman/listinfo/hostap