1 /* 2 * hostapd - MBO 3 * Copyright (c) 2016, Qualcomm Atheros, Inc. 4 * 5 * This software may be distributed under the terms of the BSD license. 6 * See README for more details. 7 */ 8 9 #include "utils/includes.h" 10 11 #include "utils/common.h" 12 #include "common/ieee802_11_defs.h" 13 #include "common/ieee802_11_common.h" 14 #include "hostapd.h" 15 #include "sta_info.h" 16 #include "mbo_ap.h" 17 18 19 void mbo_ap_sta_free(struct sta_info *sta) 20 { 21 struct mbo_non_pref_chan_info *info, *prev; 22 23 info = sta->non_pref_chan; 24 sta->non_pref_chan = NULL; 25 while (info) { 26 prev = info; 27 info = info->next; 28 os_free(prev); 29 } 30 } 31 32 33 static void mbo_ap_parse_non_pref_chan(struct sta_info *sta, 34 const u8 *buf, size_t len) 35 { 36 struct mbo_non_pref_chan_info *info, *tmp; 37 char channels[200], *pos, *end; 38 size_t num_chan, i; 39 int ret; 40 41 if (len <= 3) 42 return; /* Not enough room for any channels */ 43 44 num_chan = len - 3; 45 info = os_zalloc(sizeof(*info) + num_chan); 46 if (!info) 47 return; 48 info->op_class = buf[0]; 49 info->pref = buf[len - 2]; 50 info->reason_code = buf[len - 1]; 51 info->num_channels = num_chan; 52 buf++; 53 os_memcpy(info->channels, buf, num_chan); 54 if (!sta->non_pref_chan) { 55 sta->non_pref_chan = info; 56 } else { 57 tmp = sta->non_pref_chan; 58 while (tmp->next) 59 tmp = tmp->next; 60 tmp->next = info; 61 } 62 63 pos = channels; 64 end = pos + sizeof(channels); 65 *pos = '\0'; 66 for (i = 0; i < num_chan; i++) { 67 ret = os_snprintf(pos, end - pos, "%s%u", 68 i == 0 ? "" : " ", buf[i]); 69 if (os_snprintf_error(end - pos, ret)) { 70 *pos = '\0'; 71 break; 72 } 73 pos += ret; 74 } 75 76 wpa_printf(MSG_DEBUG, "MBO: STA " MACSTR 77 " non-preferred channel list (op class %u, pref %u, reason code %u, channels %s)", 78 MAC2STR(sta->addr), info->op_class, info->pref, 79 info->reason_code, channels); 80 } 81 82 83 void mbo_ap_check_sta_assoc(struct hostapd_data *hapd, struct sta_info *sta, 84 struct ieee802_11_elems *elems) 85 { 86 const u8 *pos, *attr, *end; 87 size_t len; 88 89 if (!hapd->conf->mbo_enabled || !elems->mbo) 90 return; 91 92 pos = elems->mbo + 4; 93 len = elems->mbo_len - 4; 94 wpa_hexdump(MSG_DEBUG, "MBO: Association Request attributes", pos, len); 95 96 attr = get_ie(pos, len, MBO_ATTR_ID_CELL_DATA_CAPA); 97 if (attr && attr[1] >= 1) 98 sta->cell_capa = attr[2]; 99 100 mbo_ap_sta_free(sta); 101 end = pos + len; 102 while (end - pos > 1) { 103 u8 ie_len = pos[1]; 104 105 if (2 + ie_len > end - pos) 106 break; 107 108 if (pos[0] == MBO_ATTR_ID_NON_PREF_CHAN_REPORT) 109 mbo_ap_parse_non_pref_chan(sta, pos + 2, ie_len); 110 pos += 2 + pos[1]; 111 } 112 } 113 114 115 int mbo_ap_get_info(struct sta_info *sta, char *buf, size_t buflen) 116 { 117 char *pos = buf, *end = buf + buflen; 118 int ret; 119 struct mbo_non_pref_chan_info *info; 120 u8 i; 121 unsigned int count = 0; 122 123 if (!sta->cell_capa) 124 return 0; 125 126 ret = os_snprintf(pos, end - pos, "mbo_cell_capa=%u\n", sta->cell_capa); 127 if (os_snprintf_error(end - pos, ret)) 128 return pos - buf; 129 pos += ret; 130 131 for (info = sta->non_pref_chan; info; info = info->next) { 132 char *pos2 = pos; 133 134 ret = os_snprintf(pos2, end - pos2, 135 "non_pref_chan[%u]=%u:%u:%u:", 136 count, info->op_class, info->pref, 137 info->reason_code); 138 count++; 139 if (os_snprintf_error(end - pos2, ret)) 140 break; 141 pos2 += ret; 142 143 for (i = 0; i < info->num_channels; i++) { 144 ret = os_snprintf(pos2, end - pos2, "%u%s", 145 info->channels[i], 146 i + 1 < info->num_channels ? 147 "," : ""); 148 if (os_snprintf_error(end - pos2, ret)) { 149 pos2 = NULL; 150 break; 151 } 152 pos2 += ret; 153 } 154 155 if (!pos2) 156 break; 157 ret = os_snprintf(pos2, end - pos2, "\n"); 158 if (os_snprintf_error(end - pos2, ret)) 159 break; 160 pos2 += ret; 161 pos = pos2; 162 } 163 164 return pos - buf; 165 } 166 167 168 static void mbo_ap_wnm_notif_req_cell_capa(struct sta_info *sta, 169 const u8 *buf, size_t len) 170 { 171 if (len < 1) 172 return; 173 wpa_printf(MSG_DEBUG, "MBO: STA " MACSTR 174 " updated cellular data capability: %u", 175 MAC2STR(sta->addr), buf[0]); 176 sta->cell_capa = buf[0]; 177 } 178 179 180 static void mbo_ap_wnm_notif_req_elem(struct sta_info *sta, u8 type, 181 const u8 *buf, size_t len, 182 int *first_non_pref_chan) 183 { 184 switch (type) { 185 case WFA_WNM_NOTIF_SUBELEM_NON_PREF_CHAN_REPORT: 186 if (*first_non_pref_chan) { 187 /* 188 * Need to free the previously stored entries now to 189 * allow the update to replace all entries. 190 */ 191 *first_non_pref_chan = 0; 192 mbo_ap_sta_free(sta); 193 } 194 mbo_ap_parse_non_pref_chan(sta, buf, len); 195 break; 196 case WFA_WNM_NOTIF_SUBELEM_CELL_DATA_CAPA: 197 mbo_ap_wnm_notif_req_cell_capa(sta, buf, len); 198 break; 199 default: 200 wpa_printf(MSG_DEBUG, 201 "MBO: Ignore unknown WNM Notification WFA subelement %u", 202 type); 203 break; 204 } 205 } 206 207 208 void mbo_ap_wnm_notification_req(struct hostapd_data *hapd, const u8 *addr, 209 const u8 *buf, size_t len) 210 { 211 const u8 *pos, *end; 212 u8 ie_len; 213 struct sta_info *sta; 214 int first_non_pref_chan = 1; 215 216 if (!hapd->conf->mbo_enabled) 217 return; 218 219 sta = ap_get_sta(hapd, addr); 220 if (!sta) 221 return; 222 223 pos = buf; 224 end = buf + len; 225 226 while (end - pos > 1) { 227 ie_len = pos[1]; 228 229 if (2 + ie_len > end - pos) 230 break; 231 232 if (pos[0] == WLAN_EID_VENDOR_SPECIFIC && 233 ie_len >= 4 && WPA_GET_BE24(pos + 2) == OUI_WFA) 234 mbo_ap_wnm_notif_req_elem(sta, pos[5], 235 pos + 6, ie_len - 4, 236 &first_non_pref_chan); 237 else 238 wpa_printf(MSG_DEBUG, 239 "MBO: Ignore unknown WNM Notification element %u (len=%u)", 240 pos[0], pos[1]); 241 242 pos += 2 + pos[1]; 243 } 244 } 245