[PATCH v6 3/4] hostapd: ap: add AFC client support
Krishna Chaitanya
chaitanya.mgit at gmail.com
Tue May 14 13:30:17 PDT 2024
On Tue, May 14, 2024 at 5:39 PM Lorenzo Bianconi <lorenzo at kernel.org> 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 at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/hostap
More information about the Hostap
mailing list