diff --git a/src/ap/mbo_ap.c b/src/ap/mbo_ap.c index 287cece78..5e0f92a9f 100644 --- a/src/ap/mbo_ap.c +++ b/src/ap/mbo_ap.c @@ -16,10 +16,75 @@ #include "mbo_ap.h" +void mbo_ap_sta_free(struct sta_info *sta) +{ + struct mbo_non_pref_chan_info *info, *prev; + + info = sta->non_pref_chan; + sta->non_pref_chan = NULL; + while (info) { + prev = info; + info = info->next; + os_free(prev); + } +} + + +static void mbo_ap_parse_non_pref_chan(struct sta_info *sta, + const u8 *buf, size_t len) +{ + struct mbo_non_pref_chan_info *info, *tmp; + char channels[200], *pos, *end; + size_t num_chan, i; + int ret; + + if (len <= 4) + return; /* Not enough room for any channels */ + + num_chan = len - 4; + info = os_zalloc(sizeof(*info) + num_chan); + if (!info) + return; + info->op_class = buf[0]; + info->pref = buf[len - 3]; + info->reason_code = buf[len - 2]; + info->reason_detail = buf[len - 1]; + info->num_channels = num_chan; + buf++; + os_memcpy(info->channels, buf, num_chan); + if (!sta->non_pref_chan) { + sta->non_pref_chan = info; + } else { + tmp = sta->non_pref_chan; + while (tmp->next) + tmp = tmp->next; + tmp->next = info; + } + + pos = channels; + end = pos + sizeof(channels); + *pos = '\0'; + for (i = 0; i < num_chan; i++) { + ret = os_snprintf(pos, end - pos, "%s%u", + i == 0 ? "" : " ", buf[i]); + if (os_snprintf_error(end - pos, ret)) { + *pos = '\0'; + break; + } + pos += ret; + } + + wpa_printf(MSG_DEBUG, "MBO: STA " MACSTR + " non-preferred channel list (op class %u, pref %u, reason code %u, reason detail %u, channels %s)", + MAC2STR(sta->addr), info->op_class, info->pref, + info->reason_code, info->reason_detail, channels); +} + + void mbo_ap_check_sta_assoc(struct hostapd_data *hapd, struct sta_info *sta, struct ieee802_11_elems *elems) { - const u8 *pos, *attr; + const u8 *pos, *attr, *end; size_t len; if (!hapd->conf->mbo_enabled || !elems->mbo) @@ -32,20 +97,72 @@ void mbo_ap_check_sta_assoc(struct hostapd_data *hapd, struct sta_info *sta, attr = get_ie(pos, len, MBO_ATTR_ID_CELL_DATA_CAPA); if (attr && attr[1] >= 1) sta->cell_capa = attr[2]; + + mbo_ap_sta_free(sta); + end = pos + len; + while (end - pos > 1) { + u8 ie_len = pos[1]; + + if (2 + ie_len > end - pos) + break; + + if (pos[0] == MBO_ATTR_ID_NON_PREF_CHAN_REPORT) + mbo_ap_parse_non_pref_chan(sta, pos + 2, ie_len); + pos += 2 + pos[1]; + } } int mbo_ap_get_info(struct sta_info *sta, char *buf, size_t buflen) { + char *pos = buf, *end = buf + buflen; int ret; + struct mbo_non_pref_chan_info *info; + u8 i; + unsigned int count = 0; if (!sta->cell_capa) return 0; - ret = os_snprintf(buf, buflen, "mbo_cell_capa=%u\n", sta->cell_capa); - if (os_snprintf_error(buflen, ret)) - return 0; - return ret; + ret = os_snprintf(pos, end - pos, "mbo_cell_capa=%u\n", sta->cell_capa); + if (os_snprintf_error(end - pos, ret)) + return pos - buf; + pos += ret; + + for (info = sta->non_pref_chan; info; info = info->next) { + char *pos2 = pos; + + ret = os_snprintf(pos2, end - pos2, + "non_pref_chan[%u]=%u:%u:%u:%u:", + count, info->op_class, info->pref, + info->reason_code, info->reason_detail); + count++; + if (os_snprintf_error(end - pos2, ret)) + break; + pos2 += ret; + + for (i = 0; i < info->num_channels; i++) { + ret = os_snprintf(pos2, end - pos2, "%u%s", + info->channels[i], + i + 1 < info->num_channels ? + "," : ""); + if (os_snprintf_error(end - pos2, ret)) { + pos2 = NULL; + break; + } + pos2 += ret; + } + + if (!pos2) + break; + ret = os_snprintf(pos2, end - pos2, "\n"); + if (os_snprintf_error(end - pos2, ret)) + break; + pos2 += ret; + pos = pos2; + } + + return pos - buf; } @@ -62,9 +179,21 @@ static void mbo_ap_wnm_notif_req_cell_capa(struct sta_info *sta, static void mbo_ap_wnm_notif_req_elem(struct sta_info *sta, u8 type, - const u8 *buf, size_t len) + const u8 *buf, size_t len, + int *first_non_pref_chan) { switch (type) { + case WFA_WNM_NOTIF_SUBELEM_NON_PREF_CHAN_REPORT: + if (*first_non_pref_chan) { + /* + * Need to free the previously stored entries now to + * allow the update to replace all entries. + */ + *first_non_pref_chan = 0; + mbo_ap_sta_free(sta); + } + mbo_ap_parse_non_pref_chan(sta, buf, len); + break; case WFA_WNM_NOTIF_SUBELEM_CELL_DATA_CAPA: mbo_ap_wnm_notif_req_cell_capa(sta, buf, len); break; @@ -83,6 +212,7 @@ void mbo_ap_wnm_notification_req(struct hostapd_data *hapd, const u8 *addr, const u8 *pos, *end; u8 ie_len; struct sta_info *sta; + int first_non_pref_chan = 1; if (!hapd->conf->mbo_enabled) return; @@ -103,7 +233,8 @@ void mbo_ap_wnm_notification_req(struct hostapd_data *hapd, const u8 *addr, if (pos[0] == WLAN_EID_VENDOR_SPECIFIC && ie_len >= 4 && WPA_GET_BE24(pos + 2) == OUI_WFA) mbo_ap_wnm_notif_req_elem(sta, pos[5], - pos + 6, ie_len - 4); + pos + 6, ie_len - 4, + &first_non_pref_chan); else wpa_printf(MSG_DEBUG, "MBO: Ignore unknown WNM Notification element %u (len=%u)", diff --git a/src/ap/mbo_ap.h b/src/ap/mbo_ap.h index cee2300d7..9f37f2802 100644 --- a/src/ap/mbo_ap.h +++ b/src/ap/mbo_ap.h @@ -20,6 +20,7 @@ void mbo_ap_check_sta_assoc(struct hostapd_data *hapd, struct sta_info *sta, int mbo_ap_get_info(struct sta_info *sta, char *buf, size_t buflen); void mbo_ap_wnm_notification_req(struct hostapd_data *hapd, const u8 *addr, const u8 *buf, size_t len); +void mbo_ap_sta_free(struct sta_info *sta); #else /* CONFIG_MBO */ @@ -41,6 +42,10 @@ static inline void mbo_ap_wnm_notification_req(struct hostapd_data *hapd, { } +static inline void mbo_ap_sta_free(struct sta_info *sta) +{ +} + #endif /* CONFIG_MBO */ #endif /* MBO_AP_H */ diff --git a/src/ap/sta_info.c b/src/ap/sta_info.c index 2a9df8408..60058e45c 100644 --- a/src/ap/sta_info.c +++ b/src/ap/sta_info.c @@ -32,6 +32,7 @@ #include "ap_drv_ops.h" #include "gas_serv.h" #include "wnm_ap.h" +#include "mbo_ap.h" #include "ndisc_snoop.h" #include "sta_info.h" #include "vlan.h" @@ -326,6 +327,8 @@ void ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta) os_free(sta->sae); #endif /* CONFIG_SAE */ + mbo_ap_sta_free(sta); + os_free(sta); } diff --git a/src/ap/sta_info.h b/src/ap/sta_info.h index 0dd545a96..08e795e1c 100644 --- a/src/ap/sta_info.h +++ b/src/ap/sta_info.h @@ -46,6 +46,16 @@ #define WLAN_SUPP_RATES_MAX 32 +struct mbo_non_pref_chan_info { + struct mbo_non_pref_chan_info *next; + u8 op_class; + u8 pref; + u8 reason_code; + u8 reason_detail; + u8 num_channels; + u8 channels[]; +}; + struct sta_info { struct sta_info *next; /* next entry in sta list */ struct sta_info *hnext; /* next entry in hash table list */ @@ -178,6 +188,7 @@ struct sta_info { #ifdef CONFIG_MBO u8 cell_capa; /* 0 = unknown (not an MBO STA); otherwise, * enum mbo_cellular_capa values */ + struct mbo_non_pref_chan_info *non_pref_chan; #endif /* CONFIG_MBO */ };