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