aboutsummaryrefslogtreecommitdiffstats
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
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.
-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 */
};