aboutsummaryrefslogtreecommitdiffstats
path: root/wpa_supplicant
diff options
context:
space:
mode:
authorJouni Malinen <jouni@qca.qualcomm.com>2011-10-16 19:44:28 (GMT)
committerJouni Malinen <j@w1.fi>2011-10-16 20:55:34 (GMT)
commitafc064fe7ae899966f5de28b3f11881b70c7046e (patch)
treef6b374875d40d081fa9aae5ed789f6d0c664a674 /wpa_supplicant
parent40eac89023b439df9f12b290ab47d6ea06841331 (diff)
downloadhostap-afc064fe7ae899966f5de28b3f11881b70c7046e.zip
hostap-afc064fe7ae899966f5de28b3f11881b70c7046e.tar.gz
hostap-afc064fe7ae899966f5de28b3f11881b70c7046e.tar.bz2
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.
Diffstat (limited to 'wpa_supplicant')
-rw-r--r--wpa_supplicant/Makefile1
-rw-r--r--wpa_supplicant/bss.c9
-rw-r--r--wpa_supplicant/bss.h10
-rw-r--r--wpa_supplicant/ctrl_iface.c92
-rw-r--r--wpa_supplicant/interworking.c353
-rw-r--r--wpa_supplicant/interworking.h29
-rw-r--r--wpa_supplicant/wpa_cli.c45
-rw-r--r--wpa_supplicant/wpa_supplicant_i.h4
8 files changed, 543 insertions, 0 deletions
diff --git a/wpa_supplicant/Makefile b/wpa_supplicant/Makefile
index 1ec7b68..2c46a85 100644
--- a/wpa_supplicant/Makefile
+++ b/wpa_supplicant/Makefile
@@ -210,6 +210,7 @@ endif
endif
ifdef CONFIG_INTERWORKING
+OBJS += interworking.o
CFLAGS += -DCONFIG_INTERWORKING
NEED_GAS=y
endif
diff --git a/wpa_supplicant/bss.c b/wpa_supplicant/bss.c
index 21d6322..078e22d 100644
--- a/wpa_supplicant/bss.c
+++ b/wpa_supplicant/bss.c
@@ -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);
}
diff --git a/wpa_supplicant/bss.h b/wpa_supplicant/bss.h
index 992b9c0..bb19f49 100644
--- a/wpa_supplicant/bss.h
+++ b/wpa_supplicant/bss.h
@@ -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 */
diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c
index 2605a05..ec1de2b 100644
--- a/wpa_supplicant/ctrl_iface.c
+++ b/wpa_supplicant/ctrl_iface.c
@@ -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(
diff --git a/wpa_supplicant/interworking.c b/wpa_supplicant/interworking.c
new file mode 100644
index 0000000..5259c34
--- /dev/null
+++ b/wpa_supplicant/interworking.c
@@ -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;
+ }
+}
diff --git a/wpa_supplicant/interworking.h b/wpa_supplicant/interworking.h
new file mode 100644
index 0000000..2a9790b
--- /dev/null
+++ b/wpa_supplicant/interworking.h
@@ -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 */
diff --git a/wpa_supplicant/wpa_cli.c b/wpa_supplicant/wpa_cli.c
index 7a0fdf8..077d9f1 100644
--- a/wpa_supplicant/wpa_cli.c
+++ b/wpa_supplicant/wpa_cli.c
@@ -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,
diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h
index d49c88a..ff3addf 100644
--- a/wpa_supplicant/wpa_supplicant_i.h
+++ b/wpa_supplicant/wpa_supplicant_i.h
@@ -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 */
};