diff options
Diffstat (limited to 'contrib/wpa/wpa_supplicant/gas_query.c')
-rw-r--r-- | contrib/wpa/wpa_supplicant/gas_query.c | 262 |
1 files changed, 231 insertions, 31 deletions
diff --git a/contrib/wpa/wpa_supplicant/gas_query.c b/contrib/wpa/wpa_supplicant/gas_query.c index 27bcc7aa6931..10ecce7b4d3d 100644 --- a/contrib/wpa/wpa_supplicant/gas_query.c +++ b/contrib/wpa/wpa_supplicant/gas_query.c @@ -1,7 +1,8 @@ /* * Generic advertisement service (GAS) query * Copyright (c) 2009, Atheros Communications - * Copyright (c) 2011, Qualcomm Atheros + * Copyright (c) 2011-2014, Qualcomm Atheros, Inc. + * Copyright (c) 2011-2014, Jouni Malinen <j@w1.fi> * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -13,6 +14,8 @@ #include "utils/eloop.h" #include "common/ieee802_11_defs.h" #include "common/gas.h" +#include "common/wpa_ctrl.h" +#include "rsn_supp/wpa.h" #include "wpa_supplicant_i.h" #include "driver_i.h" #include "offchannel.h" @@ -20,7 +23,7 @@ /** GAS query timeout in seconds */ -#define GAS_QUERY_TIMEOUT_PERIOD 5 +#define GAS_QUERY_TIMEOUT_PERIOD 2 /** @@ -28,6 +31,7 @@ */ struct gas_query_pending { struct dl_list list; + struct gas_query *gas; u8 addr[ETH_ALEN]; u8 dialog_token; u8 next_frag_id; @@ -35,8 +39,10 @@ struct gas_query_pending { unsigned int offchannel_tx_started:1; int freq; u16 status_code; + struct wpabuf *req; struct wpabuf *adv_proto; struct wpabuf *resp; + struct os_reltime last_oper; void (*cb)(void *ctx, const u8 *dst, u8 dialog_token, enum gas_query_result result, const struct wpabuf *adv_proto, @@ -50,6 +56,8 @@ struct gas_query_pending { struct gas_query { struct wpa_supplicant *wpa_s; struct dl_list pending; /* struct gas_query_pending */ + struct gas_query_pending *current; + struct wpa_radio_work *work; }; @@ -57,6 +65,16 @@ static void gas_query_tx_comeback_timeout(void *eloop_data, void *user_ctx); static void gas_query_timeout(void *eloop_data, void *user_ctx); +static int ms_from_time(struct os_reltime *last) +{ + struct os_reltime now, res; + + os_get_reltime(&now); + os_reltime_sub(&now, last, &res); + return res.sec * 1000 + res.usec / 1000; +} + + /** * gas_query_init - Initialize GAS query component * @wpa_s: Pointer to wpa_supplicant data @@ -77,10 +95,58 @@ struct gas_query * gas_query_init(struct wpa_supplicant *wpa_s) } +static const char * gas_result_txt(enum gas_query_result result) +{ + switch (result) { + case GAS_QUERY_SUCCESS: + return "SUCCESS"; + case GAS_QUERY_FAILURE: + return "FAILURE"; + case GAS_QUERY_TIMEOUT: + return "TIMEOUT"; + case GAS_QUERY_PEER_ERROR: + return "PEER_ERROR"; + case GAS_QUERY_INTERNAL_ERROR: + return "INTERNAL_ERROR"; + case GAS_QUERY_CANCELLED: + return "CANCELLED"; + case GAS_QUERY_DELETED_AT_DEINIT: + return "DELETED_AT_DEINIT"; + } + + return "N/A"; +} + + +static void gas_query_free(struct gas_query_pending *query, int del_list) +{ + struct gas_query *gas = query->gas; + + if (del_list) + dl_list_del(&query->list); + + if (gas->work && gas->work->ctx == query) { + radio_work_done(gas->work); + gas->work = NULL; + } + + wpabuf_free(query->req); + wpabuf_free(query->adv_proto); + wpabuf_free(query->resp); + os_free(query); +} + + static void gas_query_done(struct gas_query *gas, struct gas_query_pending *query, enum gas_query_result result) { + wpa_msg(gas->wpa_s, MSG_INFO, GAS_QUERY_DONE "addr=" MACSTR + " dialog_token=%u freq=%d status_code=%u result=%s", + MAC2STR(query->addr), query->dialog_token, query->freq, + query->status_code, gas_result_txt(result)); + if (gas->current == query) + gas->current = NULL; if (query->offchannel_tx_started) offchannel_send_action_done(gas->wpa_s); eloop_cancel_timeout(gas_query_tx_comeback_timeout, gas, query); @@ -88,9 +154,7 @@ static void gas_query_done(struct gas_query *gas, dl_list_del(&query->list); query->cb(query->ctx, query->addr, query->dialog_token, result, query->adv_proto, query->resp, query->status_code); - wpabuf_free(query->adv_proto); - wpabuf_free(query->resp); - os_free(query); + gas_query_free(query, 0); } @@ -138,17 +202,79 @@ static int gas_query_append(struct gas_query_pending *query, const u8 *data, } +static void gas_query_tx_status(struct wpa_supplicant *wpa_s, + unsigned int freq, const u8 *dst, + const u8 *src, const u8 *bssid, + const u8 *data, size_t data_len, + enum offchannel_send_action_result result) +{ + struct gas_query_pending *query; + struct gas_query *gas = wpa_s->gas; + int dur; + + if (gas->current == NULL) { + wpa_printf(MSG_DEBUG, "GAS: Unexpected TX status: freq=%u dst=" + MACSTR " result=%d - no query in progress", + freq, MAC2STR(dst), result); + return; + } + + query = gas->current; + + dur = ms_from_time(&query->last_oper); + wpa_printf(MSG_DEBUG, "GAS: TX status: freq=%u dst=" MACSTR + " result=%d query=%p dialog_token=%u dur=%d ms", + freq, MAC2STR(dst), result, query, query->dialog_token, dur); + if (os_memcmp(dst, query->addr, ETH_ALEN) != 0) { + wpa_printf(MSG_DEBUG, "GAS: TX status for unexpected destination"); + return; + } + os_get_reltime(&query->last_oper); + + if (result == OFFCHANNEL_SEND_ACTION_SUCCESS) { + eloop_cancel_timeout(gas_query_timeout, gas, query); + eloop_register_timeout(GAS_QUERY_TIMEOUT_PERIOD, 0, + gas_query_timeout, gas, query); + } + if (result == OFFCHANNEL_SEND_ACTION_FAILED) { + eloop_cancel_timeout(gas_query_timeout, gas, query); + eloop_register_timeout(0, 0, gas_query_timeout, gas, query); + } +} + + +static int pmf_in_use(struct wpa_supplicant *wpa_s, const u8 *addr) +{ + if (wpa_s->current_ssid == NULL || + wpa_s->wpa_state < WPA_4WAY_HANDSHAKE || + os_memcmp(addr, wpa_s->bssid, ETH_ALEN) != 0) + return 0; + return wpa_sm_pmf_enabled(wpa_s->wpa); +} + + static int gas_query_tx(struct gas_query *gas, struct gas_query_pending *query, struct wpabuf *req) { - int res; + unsigned int wait_time; + int res, prot = pmf_in_use(gas->wpa_s, query->addr); + wpa_printf(MSG_DEBUG, "GAS: Send action frame to " MACSTR " len=%u " - "freq=%d", MAC2STR(query->addr), - (unsigned int) wpabuf_len(req), query->freq); + "freq=%d prot=%d", MAC2STR(query->addr), + (unsigned int) wpabuf_len(req), query->freq, prot); + if (prot) { + u8 *categ = wpabuf_mhead_u8(req); + *categ = WLAN_ACTION_PROTECTED_DUAL; + } + os_get_reltime(&query->last_oper); + wait_time = 1000; + if (gas->wpa_s->max_remain_on_chan && + wait_time > gas->wpa_s->max_remain_on_chan) + wait_time = gas->wpa_s->max_remain_on_chan; res = offchannel_send_action(gas->wpa_s, query->freq, query->addr, gas->wpa_s->own_addr, query->addr, - wpabuf_head(req), wpabuf_len(req), 1000, - NULL, 0); + wpabuf_head(req), wpabuf_len(req), + wait_time, gas_query_tx_status, 0); if (res == 0) query->offchannel_tx_started = 1; return res; @@ -271,6 +397,11 @@ static void gas_query_rx_comeback(struct gas_query *gas, if (frag_id != query->next_frag_id) { wpa_printf(MSG_DEBUG, "GAS: Unexpected frag_id in response " "from " MACSTR, MAC2STR(query->addr)); + if (frag_id + 1 == query->next_frag_id) { + wpa_printf(MSG_DEBUG, "GAS: Drop frame as possible " + "retry of previous fragment"); + return; + } gas_query_done(gas, query, GAS_QUERY_PEER_ERROR); return; } @@ -291,27 +422,42 @@ static void gas_query_rx_comeback(struct gas_query *gas, /** - * gas_query_rx - Indicate reception of a Public Action frame + * gas_query_rx - Indicate reception of a Public Action or Protected Dual frame * @gas: GAS query data from gas_query_init() * @da: Destination MAC address of the Action frame * @sa: Source MAC address of the Action frame * @bssid: BSSID of the Action frame + * @categ: Category of the Action frame * @data: Payload of the Action frame * @len: Length of @data * @freq: Frequency (in MHz) on which the frame was received * Returns: 0 if the Public Action frame was a GAS frame or -1 if not */ int gas_query_rx(struct gas_query *gas, const u8 *da, const u8 *sa, - const u8 *bssid, const u8 *data, size_t len, int freq) + const u8 *bssid, u8 categ, const u8 *data, size_t len, + int freq) { struct gas_query_pending *query; u8 action, dialog_token, frag_id = 0, more_frags = 0; u16 comeback_delay, resp_len; const u8 *pos, *adv_proto; + int prot, pmf; + unsigned int left; if (gas == NULL || len < 4) return -1; + prot = categ == WLAN_ACTION_PROTECTED_DUAL; + pmf = pmf_in_use(gas->wpa_s, bssid); + if (prot && !pmf) { + wpa_printf(MSG_DEBUG, "GAS: Drop unexpected protected GAS frame when PMF is disabled"); + return 0; + } + if (!prot && pmf) { + wpa_printf(MSG_DEBUG, "GAS: Drop unexpected unprotected GAS frame when PMF is enabled"); + return 0; + } + pos = data; action = *pos++; dialog_token = *pos++; @@ -327,6 +473,9 @@ int gas_query_rx(struct gas_query *gas, const u8 *da, const u8 *sa, return -1; } + wpa_printf(MSG_DEBUG, "GAS: Response in %d ms from " MACSTR, + ms_from_time(&query->last_oper), MAC2STR(sa)); + if (query->wait_comeback && action == WLAN_PA_GAS_INITIAL_RESP) { wpa_printf(MSG_DEBUG, "GAS: Unexpected initial response from " MACSTR " dialog token %u when waiting for comeback " @@ -344,7 +493,10 @@ int gas_query_rx(struct gas_query *gas, const u8 *da, const u8 *sa, query->status_code = WPA_GET_LE16(pos); pos += 2; - if (query->status_code != WLAN_STATUS_SUCCESS) { + if (query->status_code == WLAN_STATUS_QUERY_RESP_OUTSTANDING && + action == WLAN_PA_GAS_COMEBACK_RESP) { + wpa_printf(MSG_DEBUG, "GAS: Allow non-zero status for outstanding comeback response"); + } else if (query->status_code != WLAN_STATUS_SUCCESS) { wpa_printf(MSG_DEBUG, "GAS: Query to " MACSTR " dialog token " "%u failed - status code %u", MAC2STR(sa), dialog_token, query->status_code); @@ -392,17 +544,17 @@ int gas_query_rx(struct gas_query *gas, const u8 *da, const u8 *sa, resp_len = WPA_GET_LE16(pos); pos += 2; - if (pos + resp_len > data + len) { + left = data + len - pos; + if (resp_len > left) { wpa_printf(MSG_DEBUG, "GAS: Truncated Query Response in " "response from " MACSTR, MAC2STR(sa)); return 0; } - if (pos + resp_len < data + len) { + if (resp_len < left) { wpa_printf(MSG_DEBUG, "GAS: Ignore %u octets of extra data " "after Query Response from " MACSTR, - (unsigned int) (data + len - pos - resp_len), - MAC2STR(sa)); + left - resp_len, MAC2STR(sa)); } if (action == WLAN_PA_GAS_COMEBACK_RESP) @@ -421,8 +573,9 @@ static void gas_query_timeout(void *eloop_data, void *user_ctx) struct gas_query *gas = eloop_data; struct gas_query_pending *query = user_ctx; - wpa_printf(MSG_DEBUG, "GAS: No response received for query to " MACSTR, - MAC2STR(query->addr)); + wpa_printf(MSG_DEBUG, "GAS: No response received for query to " MACSTR + " dialog token %u", + MAC2STR(query->addr), query->dialog_token); gas_query_done(gas, query, GAS_QUERY_TIMEOUT); } @@ -441,12 +594,56 @@ static int gas_query_dialog_token_available(struct gas_query *gas, } +static void gas_query_start_cb(struct wpa_radio_work *work, int deinit) +{ + struct gas_query_pending *query = work->ctx; + struct gas_query *gas = query->gas; + struct wpa_supplicant *wpa_s = gas->wpa_s; + + if (deinit) { + if (work->started) { + gas->work = NULL; + gas_query_done(gas, query, GAS_QUERY_DELETED_AT_DEINIT); + return; + } + + gas_query_free(query, 1); + return; + } + + if (wpas_update_random_addr_disassoc(wpa_s) < 0) { + wpa_msg(wpa_s, MSG_INFO, + "Failed to assign random MAC address for GAS"); + gas_query_free(query, 1); + radio_work_done(work); + return; + } + + gas->work = work; + + if (gas_query_tx(gas, query, query->req) < 0) { + wpa_printf(MSG_DEBUG, "GAS: Failed to send Action frame to " + MACSTR, MAC2STR(query->addr)); + gas_query_free(query, 1); + return; + } + gas->current = query; + + wpa_printf(MSG_DEBUG, "GAS: Starting query timeout for dialog token %u", + query->dialog_token); + eloop_register_timeout(GAS_QUERY_TIMEOUT_PERIOD, 0, + gas_query_timeout, gas, query); + +} + + /** * gas_query_req - Request a GAS query * @gas: GAS query data from gas_query_init() * @dst: Destination MAC address for the query * @freq: Frequency (in MHz) for the channel on which to send the query - * @req: GAS query payload + * @req: GAS query payload (to be freed by gas_query module in case of success + * return) * @cb: Callback function for reporting GAS query result and response * @ctx: Context pointer to use with the @cb call * Returns: dialog token (>= 0) on success or -1 on failure @@ -461,43 +658,46 @@ int gas_query_req(struct gas_query *gas, const u8 *dst, int freq, { struct gas_query_pending *query; int dialog_token; + static int next_start = 0; if (wpabuf_len(req) < 3) return -1; for (dialog_token = 0; dialog_token < 256; dialog_token++) { - if (gas_query_dialog_token_available(gas, dst, dialog_token)) + if (gas_query_dialog_token_available( + gas, dst, (next_start + dialog_token) % 256)) break; } if (dialog_token == 256) return -1; /* Too many pending queries */ + dialog_token = (next_start + dialog_token) % 256; + next_start = (dialog_token + 1) % 256; query = os_zalloc(sizeof(*query)); if (query == NULL) return -1; + query->gas = gas; os_memcpy(query->addr, dst, ETH_ALEN); query->dialog_token = dialog_token; query->freq = freq; query->cb = cb; query->ctx = ctx; + query->req = req; dl_list_add(&gas->pending, &query->list); *(wpabuf_mhead_u8(req) + 2) = dialog_token; - wpa_printf(MSG_DEBUG, "GAS: Starting request for " MACSTR - " dialog_token %u", MAC2STR(dst), dialog_token); - if (gas_query_tx(gas, query, req) < 0) { - wpa_printf(MSG_DEBUG, "GAS: Failed to send Action frame to " - MACSTR, MAC2STR(query->addr)); - dl_list_del(&query->list); - os_free(query); + wpa_msg(gas->wpa_s, MSG_INFO, GAS_QUERY_START "addr=" MACSTR + " dialog_token=%u freq=%d", + MAC2STR(query->addr), query->dialog_token, query->freq); + + if (radio_add_work(gas->wpa_s, freq, "gas-query", 0, gas_query_start_cb, + query) < 0) { + gas_query_free(query, 1); return -1; } - eloop_register_timeout(GAS_QUERY_TIMEOUT_PERIOD, 0, gas_query_timeout, - gas, query); - return dialog_token; } |