/* * IEEE 802.11 Common routines * Copyright (c) 2002-2019, Jouni Malinen * * This software may be distributed under the terms of the BSD license. * See README for more details. */ #include "includes.h" #include "common.h" #include "defs.h" #include "ieee802_11_defs.h" #include "ieee802_11_common.h" #include "common/wpa_supplicant_i.h" /** * get_ie - Fetch a specified information element from IEs buffer * @ies: Information elements buffer * @len: Information elements buffer length * @eid: Information element identifier (WLAN_EID_*) * Returns: Pointer to the information element (id field) or %NULL if not found * * This function returns the first matching information element in the IEs * buffer or %NULL in case the element is not found. */ const u8 * get_ie(const u8 *ies, size_t len, u8 eid) { const struct element *elem; if (!ies) return NULL; for_each_element_id(elem, eid, ies, len) return &elem->id; return NULL; } int ieee802_11_ie_count(const u8 *ies, size_t ies_len) { const struct element *elem; int count = 0; if (ies == NULL) return 0; for_each_element(elem, ies, ies_len) count++; return count; } const u8 * get_vendor_ie(const u8 *ies, size_t len, u32 vendor_type) { const struct element *elem; for_each_element_id(elem, WLAN_EID_VENDOR_SPECIFIC, ies, len) { if (elem->datalen >= 4 && vendor_type == WPA_GET_BE32(elem->data)) return &elem->id; } return NULL; } size_t mbo_add_ie(u8 *buf, size_t len, const u8 *attr, size_t attr_len) { /* * MBO IE requires 6 bytes without the attributes: EID (1), length (1), * OUI (3), OUI type (1). */ if (len < 6 + attr_len) { wpa_printf(MSG_DEBUG, "MBO: Not enough room in buffer for MBO IE: buf len = %zu, attr_len = %zu", len, attr_len); return 0; } *buf++ = WLAN_EID_VENDOR_SPECIFIC; *buf++ = attr_len + 4; WPA_PUT_BE24(buf, OUI_WFA); buf += 3; *buf++ = MBO_OUI_TYPE; os_memcpy(buf, attr, attr_len); return 6 + attr_len; } int ieee802_11_parse_candidate_list(const char *pos, u8 *nei_rep, size_t nei_rep_len) { u8 *nei_pos = nei_rep; const char *end; /* * BSS Transition Candidate List Entries - Neighbor Report elements * neighbor=,,, * ,[,] */ while (pos) { u8 *nei_start; long int val; char *endptr, *tmp; pos = os_strstr(pos, " neighbor="); if (!pos) break; if (nei_pos + 15 > nei_rep + nei_rep_len) { wpa_printf(MSG_DEBUG, "Not enough room for additional neighbor"); return -1; } pos += 10; nei_start = nei_pos; *nei_pos++ = WLAN_EID_NEIGHBOR_REPORT; nei_pos++; /* length to be filled in */ if (hwaddr_aton2(pos, nei_pos) < 0) { wpa_printf(MSG_DEBUG, "Invalid BSSID"); return -1; } nei_pos += ETH_ALEN; pos += 17; if (*pos != ',') { wpa_printf(MSG_DEBUG, "Missing BSSID Information"); return -1; } pos++; val = strtol(pos, &endptr, 0); WPA_PUT_LE32(nei_pos, val); nei_pos += 4; if (*endptr != ',') { wpa_printf(MSG_DEBUG, "Missing Operating Class"); return -1; } pos = endptr + 1; *nei_pos++ = atoi(pos); /* Operating Class */ pos = os_strchr(pos, ','); if (pos == NULL) { wpa_printf(MSG_DEBUG, "Missing Channel Number"); return -1; } pos++; *nei_pos++ = atoi(pos); /* Channel Number */ pos = os_strchr(pos, ','); if (pos == NULL) { wpa_printf(MSG_DEBUG, "Missing PHY Type"); return -1; } pos++; *nei_pos++ = atoi(pos); /* PHY Type */ end = os_strchr(pos, ' '); tmp = os_strchr(pos, ','); if (tmp && (!end || tmp < end)) { /* Optional Subelements (hexdump) */ size_t len; pos = tmp + 1; end = os_strchr(pos, ' '); if (end) len = end - pos; else len = os_strlen(pos); if (nei_pos + len / 2 > nei_rep + nei_rep_len) { wpa_printf(MSG_DEBUG, "Not enough room for neighbor subelements"); return -1; } if (len & 0x01 || hexstr2bin(pos, nei_pos, len / 2) < 0) { wpa_printf(MSG_DEBUG, "Invalid neighbor subelement info"); return -1; } nei_pos += len / 2; pos = end; } nei_start[1] = nei_pos - nei_start - 2; } return nei_pos - nei_rep; } #ifdef CONFIG_SAE_PK static int ieee802_11_parse_vendor_specific(struct wpa_supplicant *wpa_s, const struct element *elem, const u8* pos) { u32 oui; oui = WPA_GET_BE24(pos); switch (oui) { case OUI_WFA: switch (pos[3]) { case SAE_PK_OUI_TYPE: wpa_s->sae_pk_elems.sae_pk_len = elem->datalen - 4; wpa_s->sae_pk_elems.sae_pk = (u8*)os_zalloc(sizeof(u8)*(elem->datalen-4)); os_memcpy(wpa_s->sae_pk_elems.sae_pk, pos+4, elem->datalen-4); break; default: wpa_printf(MSG_EXCESSIVE, "Unknown WFA " "information element ignored " "(type=%d len=%lu)", pos[3], (unsigned long) elem->datalen); return -1; } break; default: wpa_printf(MSG_EXCESSIVE, "unknown vendor specific " "information element ignored (vendor OUI " "%02x:%02x:%02x len=%lu)", pos[0], pos[1], pos[2], (unsigned long) elem->datalen); return -1; } return 0; } static int ieee802_11_parse_extension(struct wpa_supplicant *wpa_s, const struct element *elem, const u8* pos){ // do not consider extension_id element len in datalen if (elem->datalen < 1) { wpa_printf(MSG_DEBUG, "short information element (Ext)"); return -1; } u8 ext_id; ext_id = *pos++; switch (ext_id) { case WLAN_EID_EXT_FILS_KEY_CONFIRM: wpa_s->sae_pk_elems.fils_key_confirm_len = elem->datalen - 1; wpa_s->sae_pk_elems.fils_key_confirm = (u8*)os_zalloc(sizeof(u8)*(elem->datalen - 1)); os_memcpy(wpa_s->sae_pk_elems.fils_key_confirm, pos, elem->datalen - 1); break; case WLAN_EID_EXT_FILS_PUBLIC_KEY: wpa_s->sae_pk_elems.fils_pk_len = elem->datalen - 1; wpa_s->sae_pk_elems.fils_pk = (u8*)os_zalloc(sizeof(u8)*(elem->datalen - 1)); os_memcpy(wpa_s->sae_pk_elems.fils_pk, pos, elem->datalen - 1); break; default: wpa_printf(MSG_EXCESSIVE, "IEEE 802.11 element parsing ignored unknown element extension (ext_id=%u elen=%u)", ext_id, (unsigned int) elem->datalen-1); return -1; } return 0; } #endif /* CONFIG_SAE_PK */ /** * ieee802_11_parse_elems - Parse information elements in management frames * @start: Pointer to the start of IEs * @len: Length of IE buffer in octets * @elems: Data structure for parsed elements * @show_errors: Whether to show parsing errors in debug log * Returns: Parsing result */ int ieee802_11_parse_elems(struct wpa_supplicant *wpa_s, const u8 *start, size_t len) { #if defined(CONFIG_RRM) || defined(CONFIG_SAE_PK) const struct element *elem; u8 unknown = 0; if (!start) return 0; for_each_element(elem, start, len) { u8 id = elem->id; const u8 *pos = elem->data; switch (id) { #ifdef CONFIG_RRM case WLAN_EID_RRM_ENABLED_CAPABILITIES: os_memcpy(wpa_s->rrm_ie, pos, 5); wpa_s->rrm.rrm_used = true; break; #endif #ifdef CONFIG_SAE_PK case WLAN_EID_EXTENSION: if(ieee802_11_parse_extension(wpa_s, elem, pos) != 0){ unknown++; } break; case WLAN_EID_VENDOR_SPECIFIC: if(ieee802_11_parse_vendor_specific(wpa_s, elem, pos) != 0){ unknown++; } break; #endif /*CONFIG_SAE_PK*/ #ifdef CONFIG_RRM case WLAN_EID_EXT_CAPAB: /* extended caps can go beyond 8 octacts but we aren't using them now */ os_memcpy(wpa_s->extend_caps, pos, 5); break; #endif default: break; } } if (unknown) return -1; #endif /* defined(CONFIG_RRM) || defined(CONFIG_SAE_PK) */ return 0; } struct wpabuf * ieee802_11_vendor_ie_concat(const u8 *ies, size_t ies_len, u32 oui_type) { struct wpabuf *buf; const struct element *elem, *found = NULL; for_each_element_id(elem, WLAN_EID_VENDOR_SPECIFIC, ies, ies_len) { if (elem->datalen >= 4 && WPA_GET_BE32(elem->data) == oui_type) { found = elem; break; } } if (!found) return NULL; /* No specified vendor IE found */ buf = wpabuf_alloc(ies_len); if (buf == NULL) return NULL; /* * There may be multiple vendor IEs in the message, so need to * concatenate their data fields. */ for_each_element_id(elem, WLAN_EID_VENDOR_SPECIFIC, ies, ies_len) { if (elem->datalen >= 4 && WPA_GET_BE32(elem->data) == oui_type) wpabuf_put_data(buf, elem->data + 4, elem->datalen - 4); } return buf; } int ieee802_11_ext_capab(const u8 *ie, unsigned int capab) { if (!ie || ie[1] <= capab / 8) return 0; return !!(ie[2 + capab / 8] & BIT(capab % 8)); } u8 get_operating_class(u8 chan, int sec_channel) { u8 op_class; if (chan < 1 || chan > 14) return 0; if (sec_channel == 1) op_class = 83; else if (sec_channel == -1) op_class = 84; else op_class = 81; return op_class; }