afc064fe7a
Add mechanism for using GAS/ANQP to query Interworking related information from APs. The received information is stored in the BSS table and can be viewed with ctrl_iface BSS command. New ctrl_iface command ANQP_GET can be used to fetch ANQP elements from a specific AP. Additional commands FETCH_ANQP and STOP_FETCH_ANQP can be used to initiate and stop an iteration through all APs in the BSS table that indicate support Interworking to fetch ANQP elements from them.
353 lines
8.5 KiB
C
353 lines
8.5 KiB
C
/*
|
|
* Interworking (IEEE 802.11u)
|
|
* Copyright (c) 2011, Qualcomm Atheros
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* Alternatively, this software may be distributed under the terms of BSD
|
|
* license.
|
|
*
|
|
* See README and COPYING for more details.
|
|
*/
|
|
|
|
#include "includes.h"
|
|
|
|
#include "common.h"
|
|
#include "common/ieee802_11_defs.h"
|
|
#include "common/gas.h"
|
|
#include "drivers/driver.h"
|
|
#include "wpa_supplicant_i.h"
|
|
#include "bss.h"
|
|
#include "gas_query.h"
|
|
#include "interworking.h"
|
|
|
|
|
|
static void interworking_next_anqp_fetch(struct wpa_supplicant *wpa_s);
|
|
|
|
|
|
static struct wpabuf * anqp_build_req(u16 info_ids[], size_t num_ids,
|
|
struct wpabuf *extra)
|
|
{
|
|
struct wpabuf *buf;
|
|
size_t i;
|
|
u8 *len_pos;
|
|
|
|
buf = gas_anqp_build_initial_req(0, 4 + num_ids * 2 +
|
|
(extra ? wpabuf_len(extra) : 0));
|
|
if (buf == NULL)
|
|
return NULL;
|
|
|
|
len_pos = gas_anqp_add_element(buf, ANQP_QUERY_LIST);
|
|
for (i = 0; i < num_ids; i++)
|
|
wpabuf_put_le16(buf, info_ids[i]);
|
|
gas_anqp_set_element_len(buf, len_pos);
|
|
if (extra)
|
|
wpabuf_put_buf(buf, extra);
|
|
|
|
gas_anqp_set_len(buf);
|
|
|
|
return buf;
|
|
}
|
|
|
|
|
|
static void interworking_anqp_resp_cb(void *ctx, const u8 *dst,
|
|
u8 dialog_token,
|
|
enum gas_query_result result,
|
|
const struct wpabuf *adv_proto,
|
|
const struct wpabuf *resp,
|
|
u16 status_code)
|
|
{
|
|
struct wpa_supplicant *wpa_s = ctx;
|
|
|
|
anqp_resp_cb(wpa_s, dst, dialog_token, result, adv_proto, resp,
|
|
status_code);
|
|
interworking_next_anqp_fetch(wpa_s);
|
|
}
|
|
|
|
|
|
static int interworking_anqp_send_req(struct wpa_supplicant *wpa_s,
|
|
struct wpa_bss *bss)
|
|
{
|
|
struct wpabuf *buf;
|
|
int ret = 0;
|
|
int res;
|
|
u16 info_ids[] = {
|
|
ANQP_CAPABILITY_LIST,
|
|
ANQP_VENUE_NAME,
|
|
ANQP_NETWORK_AUTH_TYPE,
|
|
ANQP_ROAMING_CONSORTIUM,
|
|
ANQP_IP_ADDR_TYPE_AVAILABILITY,
|
|
ANQP_NAI_REALM,
|
|
ANQP_3GPP_CELLULAR_NETWORK,
|
|
ANQP_DOMAIN_NAME
|
|
};
|
|
struct wpabuf *extra = NULL;
|
|
|
|
wpa_printf(MSG_DEBUG, "Interworking: ANQP Query Request to " MACSTR,
|
|
MAC2STR(bss->bssid));
|
|
|
|
buf = anqp_build_req(info_ids, sizeof(info_ids) / sizeof(info_ids[0]),
|
|
extra);
|
|
wpabuf_free(extra);
|
|
if (buf == NULL)
|
|
return -1;
|
|
|
|
res = gas_query_req(wpa_s->gas, bss->bssid, bss->freq, buf,
|
|
interworking_anqp_resp_cb, wpa_s);
|
|
if (res < 0) {
|
|
wpa_printf(MSG_DEBUG, "ANQP: Failed to send Query Request");
|
|
ret = -1;
|
|
} else
|
|
wpa_printf(MSG_DEBUG, "ANQP: Query started with dialog token "
|
|
"%u", res);
|
|
|
|
wpabuf_free(buf);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static void interworking_next_anqp_fetch(struct wpa_supplicant *wpa_s)
|
|
{
|
|
struct wpa_bss *bss;
|
|
int found = 0;
|
|
const u8 *ie;
|
|
|
|
if (!wpa_s->fetch_anqp_in_progress)
|
|
return;
|
|
|
|
dl_list_for_each(bss, &wpa_s->bss, struct wpa_bss, list) {
|
|
if (!(bss->caps & IEEE80211_CAP_ESS))
|
|
continue;
|
|
ie = wpa_bss_get_ie(bss, WLAN_EID_EXT_CAPAB);
|
|
if (ie == NULL || ie[1] < 4 || !(ie[5] & 0x80))
|
|
continue; /* AP does not support Interworking */
|
|
|
|
if (!(bss->flags & WPA_BSS_ANQP_FETCH_TRIED)) {
|
|
found++;
|
|
bss->flags |= WPA_BSS_ANQP_FETCH_TRIED;
|
|
wpa_msg(wpa_s, MSG_INFO, "Starting ANQP fetch for "
|
|
MACSTR, MAC2STR(bss->bssid));
|
|
interworking_anqp_send_req(wpa_s, bss);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (found == 0) {
|
|
wpa_msg(wpa_s, MSG_INFO, "ANQP fetch completed");
|
|
wpa_s->fetch_anqp_in_progress = 0;
|
|
}
|
|
}
|
|
|
|
|
|
int interworking_fetch_anqp(struct wpa_supplicant *wpa_s)
|
|
{
|
|
struct wpa_bss *bss;
|
|
|
|
if (wpa_s->fetch_anqp_in_progress)
|
|
return 0;
|
|
|
|
dl_list_for_each(bss, &wpa_s->bss, struct wpa_bss, list)
|
|
bss->flags &= ~WPA_BSS_ANQP_FETCH_TRIED;
|
|
|
|
wpa_s->fetch_anqp_in_progress = 1;
|
|
interworking_next_anqp_fetch(wpa_s);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void interworking_stop_fetch_anqp(struct wpa_supplicant *wpa_s)
|
|
{
|
|
if (!wpa_s->fetch_anqp_in_progress)
|
|
return;
|
|
|
|
wpa_s->fetch_anqp_in_progress = 0;
|
|
}
|
|
|
|
|
|
int anqp_send_req(struct wpa_supplicant *wpa_s, const u8 *dst,
|
|
u16 info_ids[], size_t num_ids)
|
|
{
|
|
struct wpabuf *buf;
|
|
int ret = 0;
|
|
int freq;
|
|
struct wpa_bss *bss;
|
|
int res;
|
|
|
|
freq = wpa_s->assoc_freq;
|
|
bss = wpa_bss_get_bssid(wpa_s, dst);
|
|
if (bss)
|
|
freq = bss->freq;
|
|
if (freq <= 0)
|
|
return -1;
|
|
|
|
wpa_printf(MSG_DEBUG, "ANQP: Query Request to " MACSTR " for %u id(s)",
|
|
MAC2STR(dst), (unsigned int) num_ids);
|
|
|
|
buf = anqp_build_req(info_ids, num_ids, NULL);
|
|
if (buf == NULL)
|
|
return -1;
|
|
|
|
res = gas_query_req(wpa_s->gas, dst, freq, buf, anqp_resp_cb, wpa_s);
|
|
if (res < 0) {
|
|
wpa_printf(MSG_DEBUG, "ANQP: Failed to send Query Request");
|
|
ret = -1;
|
|
} else
|
|
wpa_printf(MSG_DEBUG, "ANQP: Query started with dialog token "
|
|
"%u", res);
|
|
|
|
wpabuf_free(buf);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static void interworking_parse_rx_anqp_resp(struct wpa_supplicant *wpa_s,
|
|
const u8 *sa, u16 info_id,
|
|
const u8 *data, size_t slen)
|
|
{
|
|
const u8 *pos = data;
|
|
struct wpa_bss *bss = wpa_bss_get_bssid(wpa_s, sa);
|
|
|
|
switch (info_id) {
|
|
case ANQP_CAPABILITY_LIST:
|
|
wpa_msg(wpa_s, MSG_INFO, "RX-ANQP " MACSTR
|
|
" ANQP Capability list", MAC2STR(sa));
|
|
break;
|
|
case ANQP_VENUE_NAME:
|
|
wpa_msg(wpa_s, MSG_INFO, "RX-ANQP " MACSTR
|
|
" Venue Name", MAC2STR(sa));
|
|
wpa_hexdump_ascii(MSG_DEBUG, "ANQP: Venue Name", pos, slen);
|
|
if (bss) {
|
|
wpabuf_free(bss->anqp_venue_name);
|
|
bss->anqp_venue_name = wpabuf_alloc_copy(pos, slen);
|
|
}
|
|
break;
|
|
case ANQP_NETWORK_AUTH_TYPE:
|
|
wpa_msg(wpa_s, MSG_INFO, "RX-ANQP " MACSTR
|
|
" Network Authentication Type information",
|
|
MAC2STR(sa));
|
|
wpa_hexdump_ascii(MSG_DEBUG, "ANQP: Network Authentication "
|
|
"Type", pos, slen);
|
|
if (bss) {
|
|
wpabuf_free(bss->anqp_network_auth_type);
|
|
bss->anqp_network_auth_type =
|
|
wpabuf_alloc_copy(pos, slen);
|
|
}
|
|
break;
|
|
case ANQP_ROAMING_CONSORTIUM:
|
|
wpa_msg(wpa_s, MSG_INFO, "RX-ANQP " MACSTR
|
|
" Roaming Consortium list", MAC2STR(sa));
|
|
wpa_hexdump_ascii(MSG_DEBUG, "ANQP: Roaming Consortium",
|
|
pos, slen);
|
|
if (bss) {
|
|
wpabuf_free(bss->anqp_roaming_consortium);
|
|
bss->anqp_roaming_consortium =
|
|
wpabuf_alloc_copy(pos, slen);
|
|
}
|
|
break;
|
|
case ANQP_IP_ADDR_TYPE_AVAILABILITY:
|
|
wpa_msg(wpa_s, MSG_INFO, "RX-ANQP " MACSTR
|
|
" IP Address Type Availability information",
|
|
MAC2STR(sa));
|
|
wpa_hexdump(MSG_MSGDUMP, "ANQP: IP Address Availability",
|
|
pos, slen);
|
|
if (bss) {
|
|
wpabuf_free(bss->anqp_ip_addr_type_availability);
|
|
bss->anqp_ip_addr_type_availability =
|
|
wpabuf_alloc_copy(pos, slen);
|
|
}
|
|
break;
|
|
case ANQP_NAI_REALM:
|
|
wpa_msg(wpa_s, MSG_INFO, "RX-ANQP " MACSTR
|
|
" NAI Realm list", MAC2STR(sa));
|
|
wpa_hexdump_ascii(MSG_DEBUG, "ANQP: NAI Realm", pos, slen);
|
|
if (bss) {
|
|
wpabuf_free(bss->anqp_nai_realm);
|
|
bss->anqp_nai_realm = wpabuf_alloc_copy(pos, slen);
|
|
}
|
|
break;
|
|
case ANQP_3GPP_CELLULAR_NETWORK:
|
|
wpa_msg(wpa_s, MSG_INFO, "RX-ANQP " MACSTR
|
|
" 3GPP Cellular Network information", MAC2STR(sa));
|
|
wpa_hexdump_ascii(MSG_DEBUG, "ANQP: 3GPP Cellular Network",
|
|
pos, slen);
|
|
if (bss) {
|
|
wpabuf_free(bss->anqp_3gpp);
|
|
bss->anqp_3gpp = wpabuf_alloc_copy(pos, slen);
|
|
}
|
|
break;
|
|
case ANQP_DOMAIN_NAME:
|
|
wpa_msg(wpa_s, MSG_INFO, "RX-ANQP " MACSTR
|
|
" Domain Name list", MAC2STR(sa));
|
|
wpa_hexdump_ascii(MSG_MSGDUMP, "ANQP: Domain Name", pos, slen);
|
|
if (bss) {
|
|
wpabuf_free(bss->anqp_domain_name);
|
|
bss->anqp_domain_name = wpabuf_alloc_copy(pos, slen);
|
|
}
|
|
break;
|
|
case ANQP_VENDOR_SPECIFIC:
|
|
if (slen < 3)
|
|
return;
|
|
|
|
switch (WPA_GET_BE24(pos)) {
|
|
default:
|
|
wpa_printf(MSG_DEBUG, "Interworking: Unsupported "
|
|
"vendor-specific ANQP OUI %06x",
|
|
WPA_GET_BE24(pos));
|
|
return;
|
|
}
|
|
break;
|
|
default:
|
|
wpa_printf(MSG_DEBUG, "Interworking: Unsupported ANQP Info ID "
|
|
"%u", info_id);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void anqp_resp_cb(void *ctx, const u8 *dst, u8 dialog_token,
|
|
enum gas_query_result result,
|
|
const struct wpabuf *adv_proto,
|
|
const struct wpabuf *resp, u16 status_code)
|
|
{
|
|
struct wpa_supplicant *wpa_s = ctx;
|
|
const u8 *pos;
|
|
const u8 *end;
|
|
u16 info_id;
|
|
u16 slen;
|
|
|
|
if (result != GAS_QUERY_SUCCESS)
|
|
return;
|
|
|
|
pos = wpabuf_head(adv_proto);
|
|
if (wpabuf_len(adv_proto) < 4 || pos[0] != WLAN_EID_ADV_PROTO ||
|
|
pos[1] < 2 || pos[3] != ACCESS_NETWORK_QUERY_PROTOCOL) {
|
|
wpa_printf(MSG_DEBUG, "ANQP: Unexpected Advertisement "
|
|
"Protocol in response");
|
|
return;
|
|
}
|
|
|
|
pos = wpabuf_head(resp);
|
|
end = pos + wpabuf_len(resp);
|
|
|
|
while (pos < end) {
|
|
if (pos + 4 > end) {
|
|
wpa_printf(MSG_DEBUG, "ANQP: Invalid element");
|
|
break;
|
|
}
|
|
info_id = WPA_GET_LE16(pos);
|
|
pos += 2;
|
|
slen = WPA_GET_LE16(pos);
|
|
pos += 2;
|
|
if (pos + slen > end) {
|
|
wpa_printf(MSG_DEBUG, "ANQP: Invalid element length "
|
|
"for Info ID %u", info_id);
|
|
break;
|
|
}
|
|
interworking_parse_rx_anqp_resp(wpa_s, dst, info_id, pos,
|
|
slen);
|
|
pos += slen;
|
|
}
|
|
}
|