diff --git a/wpa_supplicant/Makefile b/wpa_supplicant/Makefile index 1ec7b6820..2c46a8595 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 21d63221b..078e22d7c 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 992b9c03c..bb19f492c 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 2605a0529..ec1de2ba3 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 000000000..5259c34a2 --- /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 000000000..2a9790bb4 --- /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 7a0fdf8b6..077d9f14b 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, "[ ] = 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, + " [,]... = 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 d49c88abf..ff3addf74 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 */ };