Interworking: Add ANQP query requests

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.
This commit is contained in:
Jouni Malinen 2011-10-16 22:44:28 +03:00 committed by Jouni Malinen
parent 40eac89023
commit afc064fe7a
8 changed files with 543 additions and 0 deletions

View file

@ -210,6 +210,7 @@ endif
endif
ifdef CONFIG_INTERWORKING
OBJS += interworking.o
CFLAGS += -DCONFIG_INTERWORKING
NEED_GAS=y
endif

View file

@ -50,6 +50,15 @@ static void wpa_bss_remove(struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
" SSID '%s'", bss->id, MAC2STR(bss->bssid),
wpa_ssid_txt(bss->ssid, bss->ssid_len));
wpas_notify_bss_removed(wpa_s, bss->bssid, bss->id);
#ifdef CONFIG_INTERWORKING
wpabuf_free(bss->anqp_venue_name);
wpabuf_free(bss->anqp_network_auth_type);
wpabuf_free(bss->anqp_roaming_consortium);
wpabuf_free(bss->anqp_ip_addr_type_availability);
wpabuf_free(bss->anqp_nai_realm);
wpabuf_free(bss->anqp_3gpp);
wpabuf_free(bss->anqp_domain_name);
#endif /* CONFIG_INTERWORKING */
os_free(bss);
}

View file

@ -23,6 +23,7 @@ struct wpa_scan_res;
#define WPA_BSS_LEVEL_DBM BIT(3)
#define WPA_BSS_AUTHENTICATED BIT(4)
#define WPA_BSS_ASSOCIATED BIT(5)
#define WPA_BSS_ANQP_FETCH_TRIED BIT(6)
/**
* struct wpa_bss - BSS table
@ -65,6 +66,15 @@ struct wpa_bss {
int level;
u64 tsf;
struct os_time last_update;
#ifdef CONFIG_INTERWORKING
struct wpabuf *anqp_venue_name;
struct wpabuf *anqp_network_auth_type;
struct wpabuf *anqp_roaming_consortium;
struct wpabuf *anqp_ip_addr_type_availability;
struct wpabuf *anqp_nai_realm;
struct wpabuf *anqp_3gpp;
struct wpabuf *anqp_domain_name;
#endif /* CONFIG_INTERWORKING */
size_t ie_len;
size_t beacon_ie_len;
/* followed by ie_len octets of IEs */

View file

@ -38,6 +38,7 @@
#include "bss.h"
#include "scan.h"
#include "ctrl_iface.h"
#include "interworking.h"
extern struct wpa_driver_ops *wpa_drivers[];
@ -1870,6 +1871,41 @@ static int wpa_supplicant_ctrl_iface_get_capability(
}
#ifdef CONFIG_INTERWORKING
static char * anqp_add_hex(char *pos, char *end, const char *title,
struct wpabuf *data)
{
char *start = pos;
size_t i;
int ret;
const u8 *d;
if (data == NULL)
return start;
ret = os_snprintf(pos, end - pos, "%s=", title);
if (ret < 0 || ret >= end - pos)
return start;
pos += ret;
d = wpabuf_head_u8(data);
for (i = 0; i < wpabuf_len(data); i++) {
ret = os_snprintf(pos, end - pos, "%02x", *d++);
if (ret < 0 || ret >= end - pos)
return start;
pos += ret;
}
ret = os_snprintf(pos, end - pos, "\n");
if (ret < 0 || ret >= end - pos)
return start;
pos += ret;
return pos;
}
#endif /* CONFIG_INTERWORKING */
static int wpa_supplicant_ctrl_iface_bss(struct wpa_supplicant *wpa_s,
const char *cmd, char *buf,
size_t buflen)
@ -2013,6 +2049,20 @@ static int wpa_supplicant_ctrl_iface_bss(struct wpa_supplicant *wpa_s,
pos += ret;
#endif /* CONFIG_P2P */
#ifdef CONFIG_INTERWORKING
pos = anqp_add_hex(pos, end, "anqp_venue_name", bss->anqp_venue_name);
pos = anqp_add_hex(pos, end, "anqp_network_auth_type",
bss->anqp_network_auth_type);
pos = anqp_add_hex(pos, end, "anqp_roaming_consortium",
bss->anqp_roaming_consortium);
pos = anqp_add_hex(pos, end, "anqp_ip_addr_type_availability",
bss->anqp_ip_addr_type_availability);
pos = anqp_add_hex(pos, end, "anqp_nai_realm", bss->anqp_nai_realm);
pos = anqp_add_hex(pos, end, "anqp_3gpp", bss->anqp_3gpp);
pos = anqp_add_hex(pos, end, "anqp_domain_name",
bss->anqp_domain_name);
#endif /* CONFIG_INTERWORKING */
return pos - buf;
}
@ -2880,6 +2930,38 @@ static int p2p_ctrl_ext_listen(struct wpa_supplicant *wpa_s, char *cmd)
#endif /* CONFIG_P2P */
#ifdef CONFIG_INTERWORKING
static int get_anqp(struct wpa_supplicant *wpa_s, char *dst)
{
u8 dst_addr[ETH_ALEN];
int used;
char *pos;
#define MAX_ANQP_INFO_ID 100
u16 id[MAX_ANQP_INFO_ID];
size_t num_id = 0;
used = hwaddr_aton2(dst, dst_addr);
if (used < 0)
return -1;
pos = dst + used;
while (num_id < MAX_ANQP_INFO_ID) {
id[num_id] = atoi(pos);
if (id[num_id])
num_id++;
pos = os_strchr(pos + 1, ',');
if (pos == NULL)
break;
pos++;
}
if (num_id == 0)
return -1;
return anqp_send_req(wpa_s, dst_addr, id, num_id);
}
#endif /* CONFIG_INTERWORKING */
static int wpa_supplicant_ctrl_iface_sta_autoconnect(
struct wpa_supplicant *wpa_s, char *cmd)
{
@ -3174,6 +3256,16 @@ char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s,
if (p2p_ctrl_ext_listen(wpa_s, "") < 0)
reply_len = -1;
#endif /* CONFIG_P2P */
#ifdef CONFIG_INTERWORKING
} else if (os_strcmp(buf, "FETCH_ANQP") == 0) {
if (interworking_fetch_anqp(wpa_s) < 0)
reply_len = -1;
} else if (os_strcmp(buf, "STOP_FETCH_ANQP") == 0) {
interworking_stop_fetch_anqp(wpa_s);
} else if (os_strncmp(buf, "ANQP_GET ", 9) == 0) {
if (get_anqp(wpa_s, buf + 9) < 0)
reply_len = -1;
#endif /* CONFIG_INTERWORKING */
} else if (os_strncmp(buf, WPA_CTRL_RSP, os_strlen(WPA_CTRL_RSP)) == 0)
{
if (wpa_supplicant_ctrl_iface_ctrl_rsp(

View file

@ -0,0 +1,353 @@
/*
* 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;
}
}

View file

@ -0,0 +1,29 @@
/*
* 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.
*/
#ifndef INTERWORKING_H
#define INTERWORKING_H
enum gas_query_result;
int anqp_send_req(struct wpa_supplicant *wpa_s, const u8 *dst,
u16 info_ids[], size_t num_ids);
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);
int interworking_fetch_anqp(struct wpa_supplicant *wpa_s);
void interworking_stop_fetch_anqp(struct wpa_supplicant *wpa_s);
#endif /* INTERWORKING_H */

View file

@ -2219,6 +2219,42 @@ static int wpa_cli_cmd_p2p_ext_listen(struct wpa_ctrl *ctrl, int argc,
#endif /* CONFIG_P2P */
#ifdef CONFIG_INTERWORKING
static int wpa_cli_cmd_fetch_anqp(struct wpa_ctrl *ctrl, int argc,
char *argv[])
{
return wpa_ctrl_command(ctrl, "FETCH_ANQP");
}
static int wpa_cli_cmd_stop_fetch_anqp(struct wpa_ctrl *ctrl, int argc,
char *argv[])
{
return wpa_ctrl_command(ctrl, "STOP_FETCH_ANQP");
}
static int wpa_cli_cmd_anqp_get(struct wpa_ctrl *ctrl, int argc, char *argv[])
{
char cmd[100];
int res;
if (argc != 2) {
printf("Invalid ANQP_GET command: needs two arguments "
"(addr and info id list)\n");
return -1;
}
res = os_snprintf(cmd, sizeof(cmd), "ANQP_GET %s %s",
argv[0], argv[1]);
if (res < 0 || (size_t) res >= sizeof(cmd))
return -1;
cmd[sizeof(cmd) - 1] = '\0';
return wpa_ctrl_command(ctrl, cmd);
}
#endif /* CONFIG_INTERWORKING */
static int wpa_cli_cmd_sta_autoconnect(struct wpa_ctrl *ctrl, int argc,
char *argv[])
{
@ -2613,6 +2649,15 @@ static struct wpa_cli_cmd wpa_cli_commands[] = {
{ "p2p_ext_listen", wpa_cli_cmd_p2p_ext_listen, cli_cmd_flag_none,
"[<period> <interval>] = set extended listen timing" },
#endif /* CONFIG_P2P */
#ifdef CONFIG_INTERWORKING
{ "fetch_anqp", wpa_cli_cmd_fetch_anqp, cli_cmd_flag_none,
"= fetch ANQP information for all APs" },
{ "stop_fetch_anqp", wpa_cli_cmd_stop_fetch_anqp, cli_cmd_flag_none,
"= stop fetch_anqp operation" },
{ "anqp_get", wpa_cli_cmd_anqp_get, cli_cmd_flag_none,
"<addr> <info id>[,<info id>]... = request ANQP information" },
#endif /* CONFIG_INTERWORKING */
{ "sta_autoconnect", wpa_cli_cmd_sta_autoconnect, cli_cmd_flag_none,
"<0/1> = disable/enable automatic reconnection" },
{ "tdls_discover", wpa_cli_cmd_tdls_discover,

View file

@ -589,6 +589,10 @@ struct wpa_supplicant {
int best_overall_freq;
struct gas_query *gas;
#ifdef CONFIG_INTERWORKING
int fetch_anqp_in_progress;
#endif /* CONFIG_INTERWORKING */
};