--- a/hostapd/config_file.c +++ b/hostapd/config_file.c @@ -4165,10 +4165,12 @@ static int hostapd_config_fill(struct ho #ifdef CONFIG_MBO } else if (os_strcmp(buf, "mbo") == 0) { bss->mbo_enabled = atoi(pos); - } else if (os_strcmp(buf, "mbo_cell_data_conn_pref") == 0) { - bss->mbo_cell_data_conn_pref = atoi(pos); + } else if (os_strcmp(buf, "mbo_ap_cap_ind") == 0) { + bss->mbo_ap_cap_ind = atoi(pos); } else if (os_strcmp(buf, "oce") == 0) { bss->oce = atoi(pos); + } else if (os_strcmp(buf, "mbo_cell_data_conn_pref") == 0) { + bss->mbo_cell_data_conn_pref = atoi(pos); #endif /* CONFIG_MBO */ #ifdef CONFIG_TESTING_OPTIONS #define PARSE_TEST_PROBABILITY(_val) \ --- a/hostapd/ctrl_iface.c +++ b/hostapd/ctrl_iface.c @@ -888,10 +888,11 @@ static int hostapd_ctrl_iface_bss_tm_req if (pos) { pos += 10; req_mode |= WNM_BSS_TM_REQ_BSS_TERMINATION_INCLUDED; - /* TODO: TSF configurable/learnable */ + /* TODO: TSF learnable */ bss_term_dur[0] = 4; /* Subelement ID */ bss_term_dur[1] = 10; /* Length */ - os_memset(&bss_term_dur[2], 0, 8); + bss_term_dur[2] = atoi(pos); /* TSF */ + os_memset(&bss_term_dur[3], 0, 7); end = os_strchr(pos, ','); if (end == NULL) { wpa_printf(MSG_DEBUG, "Invalid bss_term data"); @@ -901,7 +902,7 @@ static int hostapd_ctrl_iface_bss_tm_req WPA_PUT_LE16(&bss_term_dur[10], atoi(end)); } - nei_len = ieee802_11_parse_candidate_list(cmd, nei_rep, + nei_len = ieee802_11_parse_candidate_list(cmd, sta, nei_rep, sizeof(nei_rep)); if (nei_len < 0) return -1; --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h @@ -722,6 +722,11 @@ struct hostapd_bss_config { * - Set BIT(2) to enable OCE in AP mode */ unsigned int oce; + /** + * Set BIT(6) to advertise cellular data aware capability + * in AP mode + */ + unsigned int mbo_ap_cap_ind; int mbo_cell_data_conn_pref; #endif /* CONFIG_MBO */ --- a/src/ap/gas_serv.c +++ b/src/ap/gas_serv.c @@ -19,6 +19,7 @@ #include "dpp_hostapd.h" #include "sta_info.h" #include "gas_serv.h" +#include "neighbor_db.h" #ifdef CONFIG_DPP @@ -603,6 +604,28 @@ static void anqp_add_domain_name(struct } } +static void anqp_add_neighbor_report(struct hostapd_data *hapd, + struct wpabuf *buf) +{ + if (anqp_add_override(hapd, buf, ANQP_NEIGHBOR_REPORT)) + return; + + if (hapd->conf->radio_measurements[0] & + WLAN_RRM_CAPS_NEIGHBOR_REPORT) { + u8* len, *nei_len; + len = gas_anqp_add_element(buf, ANQP_NEIGHBOR_REPORT); + wpabuf_put_u8(buf, WLAN_EID_NEIGHBOR_REPORT); + nei_len = (u8 *)wpabuf_put(buf, 1); + + if (hostapd_prepare_neighbor_buf(hapd, hapd->own_addr, + buf)) { + buf->used -= 2; + return; + } + *nei_len = ((u8 *)wpabuf_put(buf, 0) - nei_len - 1); + gas_anqp_set_element_len(buf, len); + } +} #ifdef CONFIG_FILS static void anqp_add_fils_realm_info(struct hostapd_data *hapd, @@ -1028,6 +1051,8 @@ gas_serv_build_gas_resp_payload(struct h anqp_add_elem(hapd, buf, ANQP_TDLS_CAPABILITY); if (request & ANQP_REQ_EMERGENCY_NAI) anqp_add_elem(hapd, buf, ANQP_EMERGENCY_NAI); + if (request & ANQP_REQ_NEIGHBOR_REPORT) + anqp_add_neighbor_report(hapd, buf); for (i = 0; i < num_extra_req; i++) { #ifdef CONFIG_FILS @@ -1172,6 +1197,12 @@ static void rx_anqp_query_list_id(struct "Emergency NAI", get_anqp_elem(hapd, info_id) != NULL, qi); break; + case ANQP_NEIGHBOR_REPORT: + set_anqp_req(ANQP_REQ_NEIGHBOR_REPORT, + "Neighbor report", + (hapd->conf->radio_measurements[0] & + WLAN_RRM_CAPS_NEIGHBOR_REPORT), qi); + break; default: #ifdef CONFIG_FILS if (info_id == ANQP_FILS_REALM_INFO && --- a/src/ap/gas_serv.h +++ b/src/ap/gas_serv.h @@ -40,6 +40,8 @@ (1 << (ANQP_TDLS_CAPABILITY - ANQP_QUERY_LIST)) #define ANQP_REQ_EMERGENCY_NAI \ (1 << (ANQP_EMERGENCY_NAI - ANQP_QUERY_LIST)) +#define ANQP_REQ_NEIGHBOR_REPORT \ + (1 << (ANQP_NEIGHBOR_REPORT - ANQP_QUERY_LIST)) /* * First 15 Hotspot 2.0 vendor specific ANQP-elements can be included in the * optimized bitmap. --- a/src/ap/ieee802_11.h +++ b/src/ap/ieee802_11.h @@ -132,6 +132,9 @@ static inline void sae_clear_retransmit_ } #endif /* CONFIG_SAE */ +u8 * hostapd_eid_rm_enabled_capab(struct hostapd_data *hapd, + u8 *eid, size_t len); + #ifdef CONFIG_MBO u8 * hostapd_eid_mbo(struct hostapd_data *hapd, u8 *eid, size_t len); --- a/src/ap/ieee802_11_shared.c +++ b/src/ap/ieee802_11_shared.c @@ -801,11 +801,13 @@ u8 * hostapd_eid_mbo(struct hostapd_data !OCE_STA_CFON_ENABLED(hapd) && !OCE_AP_ENABLED(hapd)) return eid; - if (hapd->conf->mbo_enabled) { + if (hapd->conf->mbo_enabled && hapd->conf->oce & OCE_AP) { *mbo_pos++ = MBO_ATTR_ID_AP_CAPA_IND; *mbo_pos++ = 1; - /* Not Cellular aware */ - *mbo_pos++ = 0; + if (hapd->conf->mbo_ap_cap_ind & MBO_AP_CAPA_CELL_AWARE) + *mbo_pos++ = MBO_AP_CAPA_CELL_AWARE; + else + *mbo_pos++ = 0; } if (hapd->conf->mbo_enabled && hapd->mbo_assoc_disallow) { --- a/src/ap/neighbor_db.c +++ b/src/ap/neighbor_db.c @@ -160,6 +160,24 @@ fail: return -1; } +int hostapd_prepare_neighbor_buf(struct hostapd_data *hapd, + const u8 *bssid, struct wpabuf *nrbuf) +{ + struct hostapd_neighbor_entry *nr; + + nr = hostapd_neighbor_get(hapd, bssid, NULL); + if (!nr) + return -1; + + if (wpabuf_tailroom(nrbuf) < wpabuf_len(nr->nr)) { + wpa_printf(MSG_ERROR, + "Invalid buf size for Neighbor Report\n"); + return -1; + } + + wpabuf_put_buf(nrbuf, nr->nr); + return 0; +} int hostapd_neighbor_remove(struct hostapd_data *hapd, const u8 *bssid, const struct wpa_ssid_value *ssid) --- a/src/ap/neighbor_db.h +++ b/src/ap/neighbor_db.h @@ -19,6 +19,8 @@ int hostapd_neighbor_set(struct hostapd_ const struct wpabuf *nr, const struct wpabuf *lci, const struct wpabuf *civic, int stationary); void hostapd_neighbor_set_own_report(struct hostapd_data *hapd); +int hostapd_prepare_neighbor_buf(struct hostapd_data *hapd, + const u8 *bssid, struct wpabuf *nrbuf); int hostapd_neighbor_remove(struct hostapd_data *hapd, const u8 *bssid, const struct wpa_ssid_value *ssid); void hostapd_free_neighbor_db(struct hostapd_data *hapd); --- a/src/ap/wnm_ap.c +++ b/src/ap/wnm_ap.c @@ -20,6 +20,7 @@ #include "ap/wpa_auth.h" #include "mbo_ap.h" #include "wnm_ap.h" +#include "neighbor_db.h" #define MAX_TFS_IE_LEN 1024 @@ -366,11 +367,27 @@ static int ieee802_11_send_bss_trans_mgm u8 dialog_token) { struct ieee80211_mgmt *mgmt; - size_t len; + size_t len, nr_len = 0; u8 *pos; + u8 req_mode = 0; int res; - mgmt = os_zalloc(sizeof(*mgmt)); +#ifdef CONFIG_MBO + u8 *nr_pos; + struct hostapd_neighbor_entry *nr; + struct wpabuf *nrbuf = NULL; + if (hapd->conf->mbo_enabled) { + dl_list_for_each(nr, &hapd->nr_db, struct hostapd_neighbor_entry, + list) + /* ID and length */ + nr_len += wpabuf_len(nr->nr) + 1 + 1; + + nrbuf = wpabuf_alloc(nr_len); + if (nrbuf == NULL) + return -1; + } +#endif + mgmt = os_zalloc(sizeof(*mgmt) + nr_len); if (mgmt == NULL) return -1; os_memcpy(mgmt->da, addr, ETH_ALEN); @@ -381,7 +398,11 @@ static int ieee802_11_send_bss_trans_mgm mgmt->u.action.category = WLAN_ACTION_WNM; mgmt->u.action.u.bss_tm_req.action = WNM_BSS_TRANS_MGMT_REQ; mgmt->u.action.u.bss_tm_req.dialog_token = dialog_token; - mgmt->u.action.u.bss_tm_req.req_mode = 0; +#ifdef CONFIG_MBO + if (hapd->conf->mbo_enabled) + req_mode |= WNM_BSS_TM_REQ_PREF_CAND_LIST_INCLUDED; +#endif + mgmt->u.action.u.bss_tm_req.req_mode = req_mode; mgmt->u.action.u.bss_tm_req.disassoc_timer = host_to_le16(0); mgmt->u.action.u.bss_tm_req.validity_interval = 1; pos = mgmt->u.action.u.bss_tm_req.variable; @@ -395,6 +416,25 @@ static int ieee802_11_send_bss_trans_mgm le_to_host16(mgmt->u.action.u.bss_tm_req.disassoc_timer), mgmt->u.action.u.bss_tm_req.validity_interval); +#ifdef CONFIG_MBO + if (hapd->conf->mbo_enabled) { + dl_list_for_each(nr, &hapd->nr_db, struct hostapd_neighbor_entry, + list) { + wpabuf_put_u8(nrbuf, WLAN_EID_NEIGHBOR_REPORT); + /* Length to be filled */ + nr_pos = (u8 *)wpabuf_put(nrbuf, 1); + if (hostapd_prepare_neighbor_buf(hapd, nr->bssid, + nrbuf) < 0) { + res = -1; + } + /* Fill in the length field */ + *nr_pos = ((u8 *)wpabuf_put(nrbuf, 0) - nr_pos - 1); + } + os_memcpy(pos, nrbuf->buf, nr_len); + pos += nr_len; + wpabuf_free(nrbuf); + } +#endif len = pos - &mgmt->u.action.category; res = hostapd_drv_send_action(hapd, hapd->iface->freq, 0, mgmt->da, &mgmt->u.action.category, len); --- a/src/common/ieee802_11_common.c +++ b/src/common/ieee802_11_common.c @@ -2290,11 +2290,21 @@ bool is_6ghz_psc_frequency(int freq) } -int ieee802_11_parse_candidate_list(const char *pos, u8 *nei_rep, - size_t nei_rep_len) +int ieee802_11_parse_candidate_list(const char *pos, struct sta_info *sta, + u8 *nei_rep, size_t nei_rep_len) { u8 *nei_pos = nei_rep; const char *end; +#ifdef CONFIG_MBO + u8 non_pref_chan = 0; + u8 *pref_pos = NULL; + int i; + + struct mbo_non_pref_chan_info *info = NULL; + + if (sta && sta->non_pref_chan) + info = sta->non_pref_chan; +#endif /* * BSS Transition Candidate List Entries - Neighbor Report elements @@ -2350,6 +2360,9 @@ int ieee802_11_parse_candidate_list(cons pos++; *nei_pos++ = atoi(pos); /* Channel Number */ +#ifdef CONFIG_MBO + non_pref_chan = atoi(pos); +#endif pos = os_strchr(pos, ','); if (pos == NULL) { wpa_printf(MSG_DEBUG, "Missing PHY Type"); @@ -2381,6 +2394,25 @@ int ieee802_11_parse_candidate_list(cons "Invalid neighbor subelement info"); return -1; } +#ifdef CONFIG_MBO + if (info) { + for (i = 0; i < (len / 2); i++) + if (nei_pos[i] == WNM_NEIGHBOR_BSS_TRANSITION_CANDIDATE && + nei_pos[i + 1] == 0x1) /* length */ + pref_pos = (nei_pos + i + 2); + + /* If STA had updated MBO non-pref chan report, + * use the same candidate preference value in the + * BSS Transition Candidate sub-element. + */ + for ( ; info ; info = info->next) + for (i = 0; i < info->num_channels; i++) + if (non_pref_chan == info->channels[i]) + *pref_pos = info->pref; + + info = sta->non_pref_chan; + } +#endif nei_pos += len / 2; pos = end; } --- a/src/common/ieee802_11_common.h +++ b/src/common/ieee802_11_common.h @@ -11,6 +11,7 @@ #include "defs.h" #include "ieee802_11_defs.h" +#include "ap/sta_info.h" struct element { u8 id; @@ -265,8 +266,8 @@ bool is_6ghz_freq(int freq); bool is_6ghz_op_class(u8 op_class); bool is_6ghz_psc_frequency(int freq); -int ieee802_11_parse_candidate_list(const char *pos, u8 *nei_rep, - size_t nei_rep_len); +int ieee802_11_parse_candidate_list(const char *pos, struct sta_info *sta, + u8 *nei_rep, size_t nei_rep_len); int ieee802_11_ext_capab(const u8 *ie, unsigned int capab); int op_class_to_bandwidth(u8 op_class); --- a/wpa_supplicant/wnm_sta.c +++ b/wpa_supplicant/wnm_sta.c @@ -1630,7 +1630,7 @@ int wnm_send_bss_transition_mgmt_query(s return ret; } - ret = ieee802_11_parse_candidate_list(btm_candidates, + ret = ieee802_11_parse_candidate_list(btm_candidates, NULL, wpabuf_put(buf, 0), max_len); if (ret < 0) {