--- a/hostapd/config_file.c +++ b/hostapd/config_file.c @@ -4065,10 +4065,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 @@ -880,10 +880,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"); --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h @@ -712,6 +712,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 @@ -797,11 +797,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 @@ -364,11 +365,26 @@ static int ieee802_11_send_bss_trans_mgm u8 dialog_token) { struct ieee80211_mgmt *mgmt; - size_t len; - u8 *pos; + size_t len, nr_len = 0; + u8 *pos, *nr_pos; + u8 req_mode = 0; int res; - mgmt = os_zalloc(sizeof(*mgmt)); +#ifdef CONFIG_MBO + 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); @@ -379,7 +395,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; @@ -392,6 +412,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);