From b1f122964e862bcb15d0237b4b852429957b7ebc Mon Sep 17 00:00:00 2001 From: Jouni Malinen Date: Mon, 27 Aug 2012 18:13:10 +0300 Subject: [PATCH] Add generic GAS request mechanism The new gas_request and gas_response_get commands can be used to request arbitary GAS queries to be performed. These can be used with ANQP or with other (including vendor specific) advertisement protocols. gas_request [Query] gas_response_get [offset,length] For example, ANQP query for Capability list in interactive wpa_cli session: > gas_request 02:00:00:00:01:00 00 000102000101 <3>GAS-RESPONSE-INFO addr=02:00:00:00:01:00 dialog_token=0 status_code=0 resp_len=32 > gas_response_get 02:00:00:00:01:00 00 01011c00010102010501070108010c01dddd0c00506f9a110200020304050607 > gas_response_get 02:00:00:00:01:00 00 0,10 01011c00010102010501 > gas_response_get 02:00:00:00:01:00 00 10,10 070108010c01dddd0c00 > gas_response_get 02:00:00:00:01:00 00 20,10 506f9a11020002030405 > gas_response_get 02:00:00:00:01:00 00 30,2 0607 It should be noted that the maximum length of the response buffer is currently 4096 bytes which allows about 2000 bytes of the response data to be fetched with a single gas_response_get command. If the response is longer, it can be fetched in pieces as shown in the example above. Signed-hostap: Jouni Malinen --- src/common/gas.c | 4 +- src/common/gas.h | 3 +- src/common/wpa_ctrl.h | 2 + wpa_supplicant/ctrl_iface.c | 122 ++++++++++++++++++++++++++++++ wpa_supplicant/interworking.c | 81 ++++++++++++++++++++ wpa_supplicant/interworking.h | 5 +- wpa_supplicant/wpa_cli.c | 20 +++++ wpa_supplicant/wpa_supplicant.c | 2 + wpa_supplicant/wpa_supplicant_i.h | 4 + 9 files changed, 239 insertions(+), 4 deletions(-) diff --git a/src/common/gas.c b/src/common/gas.c index a67325c3c..cff9254b7 100644 --- a/src/common/gas.c +++ b/src/common/gas.c @@ -1,7 +1,7 @@ /* * Generic advertisement service (GAS) (IEEE 802.11u) * Copyright (c) 2009, Atheros Communications - * Copyright (c) 2011, Qualcomm Atheros + * Copyright (c) 2011-2012, Qualcomm Atheros * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -31,7 +31,7 @@ gas_build_req(u8 action, u8 dialog_token, size_t size) } -static struct wpabuf * gas_build_initial_req(u8 dialog_token, size_t size) +struct wpabuf * gas_build_initial_req(u8 dialog_token, size_t size) { return gas_build_req(WLAN_PA_GAS_INITIAL_REQ, dialog_token, size); diff --git a/src/common/gas.h b/src/common/gas.h index 8664a79e3..306adc58c 100644 --- a/src/common/gas.h +++ b/src/common/gas.h @@ -1,7 +1,7 @@ /* * Generic advertisement service (GAS) (IEEE 802.11u) * Copyright (c) 2009, Atheros Communications - * Copyright (c) 2011, Qualcomm Atheros + * Copyright (c) 2011-2012, Qualcomm Atheros * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -10,6 +10,7 @@ #ifndef GAS_H #define GAS_H +struct wpabuf * gas_build_initial_req(u8 dialog_token, size_t size); struct wpabuf * gas_build_comeback_req(u8 dialog_token); struct wpabuf * gas_build_initial_resp(u8 dialog_token, u16 status_code, u16 comeback_delay, size_t size); diff --git a/src/common/wpa_ctrl.h b/src/common/wpa_ctrl.h index c2243f15d..0bcfcaeed 100644 --- a/src/common/wpa_ctrl.h +++ b/src/common/wpa_ctrl.h @@ -133,6 +133,8 @@ extern "C" { #define INTERWORKING_AP "INTERWORKING-AP " #define INTERWORKING_NO_MATCH "INTERWORKING-NO-MATCH " +#define GAS_RESPONSE_INFO "GAS-RESPONSE-INFO " + /* hostapd control interface - fixed message prefixes */ #define WPS_EVENT_PIN_NEEDED "WPS-PIN-NEEDED " #define WPS_EVENT_NEW_AP_SETTINGS "WPS-NEW-AP-SETTINGS " diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c index 0279fb23b..6f6c1ed67 100644 --- a/wpa_supplicant/ctrl_iface.c +++ b/wpa_supplicant/ctrl_iface.c @@ -3957,6 +3957,122 @@ static int get_anqp(struct wpa_supplicant *wpa_s, char *dst) return anqp_send_req(wpa_s, dst_addr, id, num_id); } + + +static int gas_request(struct wpa_supplicant *wpa_s, char *cmd) +{ + u8 dst_addr[ETH_ALEN]; + struct wpabuf *advproto, *query = NULL; + int used, ret = -1; + char *pos, *end; + size_t len; + + used = hwaddr_aton2(cmd, dst_addr); + if (used < 0) + return -1; + + pos = cmd + used; + while (*pos == ' ') + pos++; + + /* Advertisement Protocol ID */ + end = os_strchr(pos, ' '); + if (end) + len = end - pos; + else + len = os_strlen(pos); + if (len & 0x01) + return -1; + len /= 2; + if (len == 0) + return -1; + advproto = wpabuf_alloc(len); + if (advproto == NULL) + return -1; + if (hexstr2bin(pos, wpabuf_put(advproto, len), len) < 0) + goto fail; + + if (end) { + /* Optional Query Request */ + pos = end + 1; + while (*pos == ' ') + pos++; + + len = os_strlen(pos); + if (len) { + if (len & 0x01) + goto fail; + len /= 2; + if (len == 0) + goto fail; + query = wpabuf_alloc(len); + if (query == NULL) + goto fail; + if (hexstr2bin(pos, wpabuf_put(query, len), len) < 0) + goto fail; + } + } + + ret = gas_send_request(wpa_s, dst_addr, advproto, query); + +fail: + wpabuf_free(advproto); + wpabuf_free(query); + + return ret; +} + + +static int gas_response_get(struct wpa_supplicant *wpa_s, char *cmd, char *buf, + size_t buflen) +{ + u8 addr[ETH_ALEN]; + int dialog_token; + int used; + char *pos; + size_t resp_len, start, requested_len; + + if (!wpa_s->last_gas_resp) + return -1; + + used = hwaddr_aton2(cmd, addr); + if (used < 0) + return -1; + + pos = cmd + used; + while (*pos == ' ') + pos++; + dialog_token = atoi(pos); + + if (os_memcmp(addr, wpa_s->last_gas_addr, ETH_ALEN) != 0 || + dialog_token != wpa_s->last_gas_dialog_token) + return -1; + + resp_len = wpabuf_len(wpa_s->last_gas_resp); + start = 0; + requested_len = resp_len; + + pos = os_strchr(pos, ' '); + if (pos) { + start = atoi(pos); + if (start > resp_len) + return os_snprintf(buf, buflen, "FAIL-Invalid range"); + pos = os_strchr(pos, ','); + if (pos == NULL) + return -1; + pos++; + requested_len = atoi(pos); + if (start + requested_len > resp_len) + return os_snprintf(buf, buflen, "FAIL-Invalid range"); + } + + if (requested_len * 2 + 1 > buflen) + return os_snprintf(buf, buflen, "FAIL-Too long response"); + + return wpa_snprintf_hex(buf, buflen, + wpabuf_head_u8(wpa_s->last_gas_resp) + start, + requested_len); +} #endif /* CONFIG_INTERWORKING */ @@ -4432,6 +4548,12 @@ char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s, } else if (os_strncmp(buf, "ANQP_GET ", 9) == 0) { if (get_anqp(wpa_s, buf + 9) < 0) reply_len = -1; + } else if (os_strncmp(buf, "GAS_REQUEST ", 12) == 0) { + if (gas_request(wpa_s, buf + 12) < 0) + reply_len = -1; + } else if (os_strncmp(buf, "GAS_RESPONSE_GET ", 17) == 0) { + reply_len = gas_response_get(wpa_s, buf + 17, reply, + reply_size); #endif /* CONFIG_INTERWORKING */ #ifdef CONFIG_HS20 } else if (os_strncmp(buf, "HS20_ANQP_GET ", 14) == 0) { diff --git a/wpa_supplicant/interworking.c b/wpa_supplicant/interworking.c index 29ff80978..11c57b4a7 100644 --- a/wpa_supplicant/interworking.c +++ b/wpa_supplicant/interworking.c @@ -1757,3 +1757,84 @@ int interworking_select(struct wpa_supplicant *wpa_s, int auto_select) return 0; } + + +static void gas_resp_cb(void *ctx, const u8 *addr, 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; + + wpa_msg(wpa_s, MSG_INFO, GAS_RESPONSE_INFO "addr=" MACSTR + " dialog_token=%d status_code=%d resp_len=%d", + MAC2STR(addr), dialog_token, status_code, + resp ? (int) wpabuf_len(resp) : -1); + if (!resp) + return; + + wpabuf_free(wpa_s->last_gas_resp); + wpa_s->last_gas_resp = wpabuf_dup(resp); + if (wpa_s->last_gas_resp == NULL) + return; + os_memcpy(wpa_s->last_gas_addr, addr, ETH_ALEN); + wpa_s->last_gas_dialog_token = dialog_token; +} + + +int gas_send_request(struct wpa_supplicant *wpa_s, const u8 *dst, + const struct wpabuf *adv_proto, + const struct wpabuf *query) +{ + struct wpabuf *buf; + int ret = 0; + int freq; + struct wpa_bss *bss; + int res; + size_t len; + u8 query_resp_len_limit = 0, pame_bi = 0; + + 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, "GAS request to " MACSTR " (freq %d MHz)", + MAC2STR(dst), freq); + wpa_hexdump_buf(MSG_DEBUG, "Advertisement Protocol ID", adv_proto); + wpa_hexdump_buf(MSG_DEBUG, "GAS Query", query); + + len = 3 + wpabuf_len(adv_proto) + 2; + if (query) + len += wpabuf_len(query); + buf = gas_build_initial_req(0, len); + if (buf == NULL) + return -1; + + /* Advertisement Protocol IE */ + wpabuf_put_u8(buf, WLAN_EID_ADV_PROTO); + wpabuf_put_u8(buf, 1 + wpabuf_len(adv_proto)); /* Length */ + wpabuf_put_u8(buf, (query_resp_len_limit & 0x7f) | + (pame_bi ? 0x80 : 0)); + wpabuf_put_buf(buf, adv_proto); + + /* GAS Query */ + if (query) { + wpabuf_put_le16(buf, wpabuf_len(query)); + wpabuf_put_buf(buf, query); + } else + wpabuf_put_le16(buf, 0); + + res = gas_query_req(wpa_s->gas, dst, freq, buf, gas_resp_cb, wpa_s); + if (res < 0) { + wpa_printf(MSG_DEBUG, "GAS: Failed to send Query Request"); + ret = -1; + } else + wpa_printf(MSG_DEBUG, "GAS: Query started with dialog token " + "%u", res); + + wpabuf_free(buf); + return ret; +} diff --git a/wpa_supplicant/interworking.h b/wpa_supplicant/interworking.h index 7dda8d823..60566e5a2 100644 --- a/wpa_supplicant/interworking.h +++ b/wpa_supplicant/interworking.h @@ -1,6 +1,6 @@ /* * Interworking (IEEE 802.11u) - * Copyright (c) 2011, Qualcomm Atheros + * Copyright (c) 2011-2012, Qualcomm Atheros * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -17,6 +17,9 @@ 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 gas_send_request(struct wpa_supplicant *wpa_s, const u8 *dst, + const struct wpabuf *adv_proto, + const struct wpabuf *query); int interworking_fetch_anqp(struct wpa_supplicant *wpa_s); void interworking_stop_fetch_anqp(struct wpa_supplicant *wpa_s); int interworking_select(struct wpa_supplicant *wpa_s, int auto_select); diff --git a/wpa_supplicant/wpa_cli.c b/wpa_supplicant/wpa_cli.c index 3bc56f6f2..b5c3b0f63 100644 --- a/wpa_supplicant/wpa_cli.c +++ b/wpa_supplicant/wpa_cli.c @@ -2019,6 +2019,20 @@ static int wpa_cli_cmd_anqp_get(struct wpa_ctrl *ctrl, int argc, char *argv[]) { return wpa_cli_cmd(ctrl, "ANQP_GET", 2, argc, argv); } + + +static int wpa_cli_cmd_gas_request(struct wpa_ctrl *ctrl, int argc, + char *argv[]) +{ + return wpa_cli_cmd(ctrl, "GAS_REQUEST", 2, argc, argv); +} + + +static int wpa_cli_cmd_gas_response_get(struct wpa_ctrl *ctrl, int argc, + char *argv[]) +{ + return wpa_cli_cmd(ctrl, "GAS_RESPONSE_GET", 2, argc, argv); +} #endif /* CONFIG_INTERWORKING */ @@ -2493,6 +2507,12 @@ static struct wpa_cli_cmd wpa_cli_commands[] = { { "anqp_get", wpa_cli_cmd_anqp_get, wpa_cli_complete_bss, cli_cmd_flag_none, " [,]... = request ANQP information" }, + { "gas_request", wpa_cli_cmd_gas_request, wpa_cli_complete_bss, + cli_cmd_flag_none, + " [QueryReq] = GAS request" }, + { "gas_response_get", wpa_cli_cmd_gas_response_get, + wpa_cli_complete_bss, cli_cmd_flag_none, + " [start,len] = Fetch last GAS response" }, #endif /* CONFIG_INTERWORKING */ #ifdef CONFIG_HS20 { "hs20_anqp_get", wpa_cli_cmd_hs20_anqp_get, wpa_cli_complete_bss, diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c index e0b42ed3e..6f44bf8a7 100644 --- a/wpa_supplicant/wpa_supplicant.c +++ b/wpa_supplicant/wpa_supplicant.c @@ -468,6 +468,8 @@ static void wpa_supplicant_cleanup(struct wpa_supplicant *wpa_s) ext_password_deinit(wpa_s->ext_pw); wpa_s->ext_pw = NULL; + + wpabuf_free(wpa_s->last_gas_resp); } diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h index 632f6bc94..4494bcccc 100644 --- a/wpa_supplicant/wpa_supplicant_i.h +++ b/wpa_supplicant/wpa_supplicant_i.h @@ -585,6 +585,10 @@ struct wpa_supplicant { int disconnect_reason; struct ext_password_data *ext_pw; + + struct wpabuf *last_gas_resp; + u8 last_gas_addr[ETH_ALEN]; + u8 last_gas_dialog_token; };