mirror of
https://github.com/vanhoefm/fragattacks.git
synced 2024-12-01 19:58:22 -05:00
44abecbf02
Commit 7139cf4a4f
('P2P: Decrement
sd_pending_bcast_queries when sd returns success') added support for
retrying P2P SD queries. However, it did this without limiting how many
retries are allowed. This can result in excessive number of retries if a
peer device does not show up on its Listen channel and there is a
pending SD query to it. Limit the maximum number of SD retries to 100
per p2p_find operation for each peer to avoid unlimited retries.
Signed-off-by: Jouni Malinen <jouni@qca.qualcomm.com>
918 lines
21 KiB
C
918 lines
21 KiB
C
/*
|
|
* Wi-Fi Direct - P2P service discovery
|
|
* Copyright (c) 2009, Atheros Communications
|
|
*
|
|
* This software may be distributed under the terms of the BSD license.
|
|
* See README for more details.
|
|
*/
|
|
|
|
#include "includes.h"
|
|
|
|
#include "common.h"
|
|
#include "common/ieee802_11_defs.h"
|
|
#include "common/gas.h"
|
|
#include "p2p_i.h"
|
|
#include "p2p.h"
|
|
|
|
|
|
#ifdef CONFIG_WIFI_DISPLAY
|
|
static int wfd_wsd_supported(struct wpabuf *wfd)
|
|
{
|
|
const u8 *pos, *end;
|
|
u8 subelem;
|
|
u16 len;
|
|
|
|
if (wfd == NULL)
|
|
return 0;
|
|
|
|
pos = wpabuf_head(wfd);
|
|
end = pos + wpabuf_len(wfd);
|
|
|
|
while (pos + 3 <= end) {
|
|
subelem = *pos++;
|
|
len = WPA_GET_BE16(pos);
|
|
pos += 2;
|
|
if (pos + len > end)
|
|
break;
|
|
|
|
if (subelem == WFD_SUBELEM_DEVICE_INFO && len >= 6) {
|
|
u16 info = WPA_GET_BE16(pos);
|
|
return !!(info & 0x0040);
|
|
}
|
|
|
|
pos += len;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif /* CONFIG_WIFI_DISPLAY */
|
|
|
|
struct p2p_sd_query * p2p_pending_sd_req(struct p2p_data *p2p,
|
|
struct p2p_device *dev)
|
|
{
|
|
struct p2p_sd_query *q;
|
|
int wsd = 0;
|
|
int count = 0;
|
|
|
|
if (!(dev->info.dev_capab & P2P_DEV_CAPAB_SERVICE_DISCOVERY))
|
|
return NULL; /* peer does not support SD */
|
|
#ifdef CONFIG_WIFI_DISPLAY
|
|
if (wfd_wsd_supported(dev->info.wfd_subelems))
|
|
wsd = 1;
|
|
#endif /* CONFIG_WIFI_DISPLAY */
|
|
|
|
for (q = p2p->sd_queries; q; q = q->next) {
|
|
/* Use WSD only if the peer indicates support or it */
|
|
if (q->wsd && !wsd)
|
|
continue;
|
|
/* if the query is a broadcast query */
|
|
if (q->for_all_peers) {
|
|
/*
|
|
* check if there are any broadcast queries pending for
|
|
* this device
|
|
*/
|
|
if (dev->sd_pending_bcast_queries <= 0)
|
|
return NULL;
|
|
/* query number that needs to be send to the device */
|
|
if (count == dev->sd_pending_bcast_queries - 1)
|
|
goto found;
|
|
count++;
|
|
}
|
|
if (!q->for_all_peers &&
|
|
os_memcmp(q->peer, dev->info.p2p_device_addr, ETH_ALEN) ==
|
|
0)
|
|
goto found;
|
|
}
|
|
|
|
return NULL;
|
|
|
|
found:
|
|
if (dev->sd_reqs > 100) {
|
|
p2p_dbg(p2p, "Too many SD request attempts to " MACSTR
|
|
" - skip remaining queries",
|
|
MAC2STR(dev->info.p2p_device_addr));
|
|
return NULL;
|
|
}
|
|
return q;
|
|
}
|
|
|
|
|
|
static void p2p_decrease_sd_bc_queries(struct p2p_data *p2p, int query_number)
|
|
{
|
|
struct p2p_device *dev;
|
|
|
|
p2p->num_p2p_sd_queries--;
|
|
dl_list_for_each(dev, &p2p->devices, struct p2p_device, list) {
|
|
if (query_number <= dev->sd_pending_bcast_queries - 1) {
|
|
/*
|
|
* Query not yet sent to the device and it is to be
|
|
* removed, so update the pending count.
|
|
*/
|
|
dev->sd_pending_bcast_queries--;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static int p2p_unlink_sd_query(struct p2p_data *p2p,
|
|
struct p2p_sd_query *query)
|
|
{
|
|
struct p2p_sd_query *q, *prev;
|
|
int query_number = 0;
|
|
|
|
q = p2p->sd_queries;
|
|
prev = NULL;
|
|
while (q) {
|
|
if (q == query) {
|
|
/* If the query is a broadcast query, decrease one from
|
|
* all the devices */
|
|
if (query->for_all_peers)
|
|
p2p_decrease_sd_bc_queries(p2p, query_number);
|
|
if (prev)
|
|
prev->next = q->next;
|
|
else
|
|
p2p->sd_queries = q->next;
|
|
if (p2p->sd_query == query)
|
|
p2p->sd_query = NULL;
|
|
return 1;
|
|
}
|
|
if (q->for_all_peers)
|
|
query_number++;
|
|
prev = q;
|
|
q = q->next;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void p2p_free_sd_query(struct p2p_sd_query *q)
|
|
{
|
|
if (q == NULL)
|
|
return;
|
|
wpabuf_free(q->tlvs);
|
|
os_free(q);
|
|
}
|
|
|
|
|
|
void p2p_free_sd_queries(struct p2p_data *p2p)
|
|
{
|
|
struct p2p_sd_query *q, *prev;
|
|
q = p2p->sd_queries;
|
|
p2p->sd_queries = NULL;
|
|
while (q) {
|
|
prev = q;
|
|
q = q->next;
|
|
p2p_free_sd_query(prev);
|
|
}
|
|
p2p->num_p2p_sd_queries = 0;
|
|
}
|
|
|
|
|
|
static struct wpabuf * p2p_build_sd_query(u16 update_indic,
|
|
struct wpabuf *tlvs)
|
|
{
|
|
struct wpabuf *buf;
|
|
u8 *len_pos;
|
|
|
|
buf = gas_anqp_build_initial_req(0, 100 + wpabuf_len(tlvs));
|
|
if (buf == NULL)
|
|
return NULL;
|
|
|
|
/* ANQP Query Request Frame */
|
|
len_pos = gas_anqp_add_element(buf, ANQP_VENDOR_SPECIFIC);
|
|
wpabuf_put_be32(buf, P2P_IE_VENDOR_TYPE);
|
|
wpabuf_put_le16(buf, update_indic); /* Service Update Indicator */
|
|
wpabuf_put_buf(buf, tlvs);
|
|
gas_anqp_set_element_len(buf, len_pos);
|
|
|
|
gas_anqp_set_len(buf);
|
|
|
|
return buf;
|
|
}
|
|
|
|
|
|
static void p2p_send_gas_comeback_req(struct p2p_data *p2p, const u8 *dst,
|
|
u8 dialog_token, int freq)
|
|
{
|
|
struct wpabuf *req;
|
|
|
|
req = gas_build_comeback_req(dialog_token);
|
|
if (req == NULL)
|
|
return;
|
|
|
|
p2p->pending_action_state = P2P_NO_PENDING_ACTION;
|
|
if (p2p_send_action(p2p, freq, dst, p2p->cfg->dev_addr, dst,
|
|
wpabuf_head(req), wpabuf_len(req), 200) < 0)
|
|
p2p_dbg(p2p, "Failed to send Action frame");
|
|
|
|
wpabuf_free(req);
|
|
}
|
|
|
|
|
|
static struct wpabuf * p2p_build_sd_response(u8 dialog_token, u16 status_code,
|
|
u16 comeback_delay,
|
|
u16 update_indic,
|
|
const struct wpabuf *tlvs)
|
|
{
|
|
struct wpabuf *buf;
|
|
u8 *len_pos;
|
|
|
|
buf = gas_anqp_build_initial_resp(dialog_token, status_code,
|
|
comeback_delay,
|
|
100 + (tlvs ? wpabuf_len(tlvs) : 0));
|
|
if (buf == NULL)
|
|
return NULL;
|
|
|
|
if (tlvs) {
|
|
/* ANQP Query Response Frame */
|
|
len_pos = gas_anqp_add_element(buf, ANQP_VENDOR_SPECIFIC);
|
|
wpabuf_put_be32(buf, P2P_IE_VENDOR_TYPE);
|
|
/* Service Update Indicator */
|
|
wpabuf_put_le16(buf, update_indic);
|
|
wpabuf_put_buf(buf, tlvs);
|
|
gas_anqp_set_element_len(buf, len_pos);
|
|
}
|
|
|
|
gas_anqp_set_len(buf);
|
|
|
|
return buf;
|
|
}
|
|
|
|
|
|
static struct wpabuf * p2p_build_gas_comeback_resp(u8 dialog_token,
|
|
u16 status_code,
|
|
u16 update_indic,
|
|
const u8 *data, size_t len,
|
|
u8 frag_id, u8 more,
|
|
u16 total_len)
|
|
{
|
|
struct wpabuf *buf;
|
|
|
|
buf = gas_anqp_build_comeback_resp(dialog_token, status_code, frag_id,
|
|
more, 0, 100 + len);
|
|
if (buf == NULL)
|
|
return NULL;
|
|
|
|
if (frag_id == 0) {
|
|
/* ANQP Query Response Frame */
|
|
wpabuf_put_le16(buf, ANQP_VENDOR_SPECIFIC); /* Info ID */
|
|
wpabuf_put_le16(buf, 3 + 1 + 2 + total_len);
|
|
wpabuf_put_be32(buf, P2P_IE_VENDOR_TYPE);
|
|
/* Service Update Indicator */
|
|
wpabuf_put_le16(buf, update_indic);
|
|
}
|
|
|
|
wpabuf_put_data(buf, data, len);
|
|
gas_anqp_set_len(buf);
|
|
|
|
return buf;
|
|
}
|
|
|
|
|
|
int p2p_start_sd(struct p2p_data *p2p, struct p2p_device *dev)
|
|
{
|
|
struct wpabuf *req;
|
|
int ret = 0;
|
|
struct p2p_sd_query *query;
|
|
int freq;
|
|
unsigned int wait_time;
|
|
|
|
freq = dev->listen_freq > 0 ? dev->listen_freq : dev->oper_freq;
|
|
if (freq <= 0) {
|
|
p2p_dbg(p2p, "No Listen/Operating frequency known for the peer "
|
|
MACSTR " to send SD Request",
|
|
MAC2STR(dev->info.p2p_device_addr));
|
|
return -1;
|
|
}
|
|
|
|
query = p2p_pending_sd_req(p2p, dev);
|
|
if (query == NULL)
|
|
return -1;
|
|
|
|
p2p_dbg(p2p, "Start Service Discovery with " MACSTR,
|
|
MAC2STR(dev->info.p2p_device_addr));
|
|
|
|
req = p2p_build_sd_query(p2p->srv_update_indic, query->tlvs);
|
|
if (req == NULL)
|
|
return -1;
|
|
|
|
dev->sd_reqs++;
|
|
p2p->sd_peer = dev;
|
|
p2p->sd_query = query;
|
|
p2p->pending_action_state = P2P_PENDING_SD;
|
|
|
|
wait_time = 5000;
|
|
if (p2p->cfg->max_listen && wait_time > p2p->cfg->max_listen)
|
|
wait_time = p2p->cfg->max_listen;
|
|
if (p2p_send_action(p2p, freq, dev->info.p2p_device_addr,
|
|
p2p->cfg->dev_addr, dev->info.p2p_device_addr,
|
|
wpabuf_head(req), wpabuf_len(req), wait_time) < 0) {
|
|
p2p_dbg(p2p, "Failed to send Action frame");
|
|
ret = -1;
|
|
}
|
|
|
|
wpabuf_free(req);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
void p2p_rx_gas_initial_req(struct p2p_data *p2p, const u8 *sa,
|
|
const u8 *data, size_t len, int rx_freq)
|
|
{
|
|
const u8 *pos = data;
|
|
const u8 *end = data + len;
|
|
const u8 *next;
|
|
u8 dialog_token;
|
|
u16 slen;
|
|
int freq;
|
|
u16 update_indic;
|
|
|
|
|
|
if (p2p->cfg->sd_request == NULL)
|
|
return;
|
|
|
|
if (rx_freq > 0)
|
|
freq = rx_freq;
|
|
else
|
|
freq = p2p_channel_to_freq(p2p->cfg->reg_class,
|
|
p2p->cfg->channel);
|
|
if (freq < 0)
|
|
return;
|
|
|
|
if (len < 1 + 2)
|
|
return;
|
|
|
|
dialog_token = *pos++;
|
|
p2p_dbg(p2p, "GAS Initial Request from " MACSTR
|
|
" (dialog token %u, freq %d)",
|
|
MAC2STR(sa), dialog_token, rx_freq);
|
|
|
|
if (*pos != WLAN_EID_ADV_PROTO) {
|
|
p2p_dbg(p2p, "Unexpected IE in GAS Initial Request: %u", *pos);
|
|
return;
|
|
}
|
|
pos++;
|
|
|
|
slen = *pos++;
|
|
next = pos + slen;
|
|
if (next > end || slen < 2) {
|
|
p2p_dbg(p2p, "Invalid IE in GAS Initial Request");
|
|
return;
|
|
}
|
|
pos++; /* skip QueryRespLenLimit and PAME-BI */
|
|
|
|
if (*pos != ACCESS_NETWORK_QUERY_PROTOCOL) {
|
|
p2p_dbg(p2p, "Unsupported GAS advertisement protocol id %u",
|
|
*pos);
|
|
return;
|
|
}
|
|
|
|
pos = next;
|
|
/* Query Request */
|
|
if (pos + 2 > end)
|
|
return;
|
|
slen = WPA_GET_LE16(pos);
|
|
pos += 2;
|
|
if (pos + slen > end)
|
|
return;
|
|
end = pos + slen;
|
|
|
|
/* ANQP Query Request */
|
|
if (pos + 4 > end)
|
|
return;
|
|
if (WPA_GET_LE16(pos) != ANQP_VENDOR_SPECIFIC) {
|
|
p2p_dbg(p2p, "Unsupported ANQP Info ID %u", WPA_GET_LE16(pos));
|
|
return;
|
|
}
|
|
pos += 2;
|
|
|
|
slen = WPA_GET_LE16(pos);
|
|
pos += 2;
|
|
if (pos + slen > end || slen < 3 + 1) {
|
|
p2p_dbg(p2p, "Invalid ANQP Query Request length");
|
|
return;
|
|
}
|
|
|
|
if (WPA_GET_BE32(pos) != P2P_IE_VENDOR_TYPE) {
|
|
p2p_dbg(p2p, "Unsupported ANQP vendor OUI-type %08x",
|
|
WPA_GET_BE32(pos));
|
|
return;
|
|
}
|
|
pos += 4;
|
|
|
|
if (pos + 2 > end)
|
|
return;
|
|
update_indic = WPA_GET_LE16(pos);
|
|
p2p_dbg(p2p, "Service Update Indicator: %u", update_indic);
|
|
pos += 2;
|
|
|
|
p2p->cfg->sd_request(p2p->cfg->cb_ctx, freq, sa, dialog_token,
|
|
update_indic, pos, end - pos);
|
|
/* the response will be indicated with a call to p2p_sd_response() */
|
|
}
|
|
|
|
|
|
void p2p_sd_response(struct p2p_data *p2p, int freq, const u8 *dst,
|
|
u8 dialog_token, const struct wpabuf *resp_tlvs)
|
|
{
|
|
struct wpabuf *resp;
|
|
|
|
/* TODO: fix the length limit to match with the maximum frame length */
|
|
if (wpabuf_len(resp_tlvs) > 1400) {
|
|
p2p_dbg(p2p, "SD response long enough to require fragmentation");
|
|
if (p2p->sd_resp) {
|
|
/*
|
|
* TODO: Could consider storing the fragmented response
|
|
* separately for each peer to avoid having to drop old
|
|
* one if there is more than one pending SD query.
|
|
* Though, that would eat more memory, so there are
|
|
* also benefits to just using a single buffer.
|
|
*/
|
|
p2p_dbg(p2p, "Drop previous SD response");
|
|
wpabuf_free(p2p->sd_resp);
|
|
}
|
|
p2p->sd_resp = wpabuf_dup(resp_tlvs);
|
|
if (p2p->sd_resp == NULL) {
|
|
p2p_err(p2p, "Failed to allocate SD response fragmentation area");
|
|
return;
|
|
}
|
|
os_memcpy(p2p->sd_resp_addr, dst, ETH_ALEN);
|
|
p2p->sd_resp_dialog_token = dialog_token;
|
|
p2p->sd_resp_pos = 0;
|
|
p2p->sd_frag_id = 0;
|
|
resp = p2p_build_sd_response(dialog_token, WLAN_STATUS_SUCCESS,
|
|
1, p2p->srv_update_indic, NULL);
|
|
} else {
|
|
p2p_dbg(p2p, "SD response fits in initial response");
|
|
resp = p2p_build_sd_response(dialog_token,
|
|
WLAN_STATUS_SUCCESS, 0,
|
|
p2p->srv_update_indic, resp_tlvs);
|
|
}
|
|
if (resp == NULL)
|
|
return;
|
|
|
|
p2p->pending_action_state = P2P_NO_PENDING_ACTION;
|
|
if (p2p_send_action(p2p, freq, dst, p2p->cfg->dev_addr,
|
|
p2p->cfg->dev_addr,
|
|
wpabuf_head(resp), wpabuf_len(resp), 200) < 0)
|
|
p2p_dbg(p2p, "Failed to send Action frame");
|
|
|
|
wpabuf_free(resp);
|
|
}
|
|
|
|
|
|
void p2p_rx_gas_initial_resp(struct p2p_data *p2p, const u8 *sa,
|
|
const u8 *data, size_t len, int rx_freq)
|
|
{
|
|
const u8 *pos = data;
|
|
const u8 *end = data + len;
|
|
const u8 *next;
|
|
u8 dialog_token;
|
|
u16 status_code;
|
|
u16 comeback_delay;
|
|
u16 slen;
|
|
u16 update_indic;
|
|
|
|
if (p2p->state != P2P_SD_DURING_FIND || p2p->sd_peer == NULL ||
|
|
os_memcmp(sa, p2p->sd_peer->info.p2p_device_addr, ETH_ALEN) != 0) {
|
|
p2p_dbg(p2p, "Ignore unexpected GAS Initial Response from "
|
|
MACSTR, MAC2STR(sa));
|
|
return;
|
|
}
|
|
p2p->cfg->send_action_done(p2p->cfg->cb_ctx);
|
|
p2p_clear_timeout(p2p);
|
|
|
|
p2p_dbg(p2p, "Received GAS Initial Response from " MACSTR " (len=%d)",
|
|
MAC2STR(sa), (int) len);
|
|
|
|
if (len < 5 + 2) {
|
|
p2p_dbg(p2p, "Too short GAS Initial Response frame");
|
|
return;
|
|
}
|
|
|
|
dialog_token = *pos++;
|
|
/* TODO: check dialog_token match */
|
|
status_code = WPA_GET_LE16(pos);
|
|
pos += 2;
|
|
comeback_delay = WPA_GET_LE16(pos);
|
|
pos += 2;
|
|
p2p_dbg(p2p, "dialog_token=%u status_code=%u comeback_delay=%u",
|
|
dialog_token, status_code, comeback_delay);
|
|
if (status_code) {
|
|
p2p_dbg(p2p, "Service Discovery failed: status code %u",
|
|
status_code);
|
|
return;
|
|
}
|
|
|
|
if (*pos != WLAN_EID_ADV_PROTO) {
|
|
p2p_dbg(p2p, "Unexpected IE in GAS Initial Response: %u", *pos);
|
|
return;
|
|
}
|
|
pos++;
|
|
|
|
slen = *pos++;
|
|
next = pos + slen;
|
|
if (next > end || slen < 2) {
|
|
p2p_dbg(p2p, "Invalid IE in GAS Initial Response");
|
|
return;
|
|
}
|
|
pos++; /* skip QueryRespLenLimit and PAME-BI */
|
|
|
|
if (*pos != ACCESS_NETWORK_QUERY_PROTOCOL) {
|
|
p2p_dbg(p2p, "Unsupported GAS advertisement protocol id %u",
|
|
*pos);
|
|
return;
|
|
}
|
|
|
|
pos = next;
|
|
/* Query Response */
|
|
if (pos + 2 > end) {
|
|
p2p_dbg(p2p, "Too short Query Response");
|
|
return;
|
|
}
|
|
slen = WPA_GET_LE16(pos);
|
|
pos += 2;
|
|
p2p_dbg(p2p, "Query Response Length: %d", slen);
|
|
if (pos + slen > end) {
|
|
p2p_dbg(p2p, "Not enough Query Response data");
|
|
return;
|
|
}
|
|
end = pos + slen;
|
|
|
|
if (comeback_delay) {
|
|
p2p_dbg(p2p, "Fragmented response - request fragments");
|
|
if (p2p->sd_rx_resp) {
|
|
p2p_dbg(p2p, "Drop old SD reassembly buffer");
|
|
wpabuf_free(p2p->sd_rx_resp);
|
|
p2p->sd_rx_resp = NULL;
|
|
}
|
|
p2p_send_gas_comeback_req(p2p, sa, dialog_token, rx_freq);
|
|
return;
|
|
}
|
|
|
|
/* ANQP Query Response */
|
|
if (pos + 4 > end)
|
|
return;
|
|
if (WPA_GET_LE16(pos) != ANQP_VENDOR_SPECIFIC) {
|
|
p2p_dbg(p2p, "Unsupported ANQP Info ID %u", WPA_GET_LE16(pos));
|
|
return;
|
|
}
|
|
pos += 2;
|
|
|
|
slen = WPA_GET_LE16(pos);
|
|
pos += 2;
|
|
if (pos + slen > end || slen < 3 + 1) {
|
|
p2p_dbg(p2p, "Invalid ANQP Query Response length");
|
|
return;
|
|
}
|
|
|
|
if (WPA_GET_BE32(pos) != P2P_IE_VENDOR_TYPE) {
|
|
p2p_dbg(p2p, "Unsupported ANQP vendor OUI-type %08x",
|
|
WPA_GET_BE32(pos));
|
|
return;
|
|
}
|
|
pos += 4;
|
|
|
|
if (pos + 2 > end)
|
|
return;
|
|
update_indic = WPA_GET_LE16(pos);
|
|
p2p_dbg(p2p, "Service Update Indicator: %u", update_indic);
|
|
pos += 2;
|
|
|
|
p2p->sd_peer = NULL;
|
|
|
|
if (p2p->sd_query) {
|
|
if (!p2p->sd_query->for_all_peers) {
|
|
struct p2p_sd_query *q;
|
|
p2p_dbg(p2p, "Remove completed SD query %p",
|
|
p2p->sd_query);
|
|
q = p2p->sd_query;
|
|
p2p_unlink_sd_query(p2p, p2p->sd_query);
|
|
p2p_free_sd_query(q);
|
|
}
|
|
p2p->sd_query = NULL;
|
|
}
|
|
|
|
if (p2p->cfg->sd_response)
|
|
p2p->cfg->sd_response(p2p->cfg->cb_ctx, sa, update_indic,
|
|
pos, end - pos);
|
|
p2p_continue_find(p2p);
|
|
}
|
|
|
|
|
|
void p2p_rx_gas_comeback_req(struct p2p_data *p2p, const u8 *sa,
|
|
const u8 *data, size_t len, int rx_freq)
|
|
{
|
|
struct wpabuf *resp;
|
|
u8 dialog_token;
|
|
size_t frag_len;
|
|
int more = 0;
|
|
|
|
wpa_hexdump(MSG_DEBUG, "P2P: RX GAS Comeback Request", data, len);
|
|
if (len < 1)
|
|
return;
|
|
dialog_token = *data;
|
|
p2p_dbg(p2p, "Dialog Token: %u", dialog_token);
|
|
if (dialog_token != p2p->sd_resp_dialog_token) {
|
|
p2p_dbg(p2p, "No pending SD response fragment for dialog token %u",
|
|
dialog_token);
|
|
return;
|
|
}
|
|
|
|
if (p2p->sd_resp == NULL) {
|
|
p2p_dbg(p2p, "No pending SD response fragment available");
|
|
return;
|
|
}
|
|
if (os_memcmp(sa, p2p->sd_resp_addr, ETH_ALEN) != 0) {
|
|
p2p_dbg(p2p, "No pending SD response fragment for " MACSTR,
|
|
MAC2STR(sa));
|
|
return;
|
|
}
|
|
|
|
frag_len = wpabuf_len(p2p->sd_resp) - p2p->sd_resp_pos;
|
|
if (frag_len > 1400) {
|
|
frag_len = 1400;
|
|
more = 1;
|
|
}
|
|
resp = p2p_build_gas_comeback_resp(dialog_token, WLAN_STATUS_SUCCESS,
|
|
p2p->srv_update_indic,
|
|
wpabuf_head_u8(p2p->sd_resp) +
|
|
p2p->sd_resp_pos, frag_len,
|
|
p2p->sd_frag_id, more,
|
|
wpabuf_len(p2p->sd_resp));
|
|
if (resp == NULL)
|
|
return;
|
|
p2p_dbg(p2p, "Send GAS Comeback Response (frag_id %d more=%d frag_len=%d)",
|
|
p2p->sd_frag_id, more, (int) frag_len);
|
|
p2p->sd_frag_id++;
|
|
p2p->sd_resp_pos += frag_len;
|
|
|
|
if (more) {
|
|
p2p_dbg(p2p, "%d more bytes remain to be sent",
|
|
(int) (wpabuf_len(p2p->sd_resp) - p2p->sd_resp_pos));
|
|
} else {
|
|
p2p_dbg(p2p, "All fragments of SD response sent");
|
|
wpabuf_free(p2p->sd_resp);
|
|
p2p->sd_resp = NULL;
|
|
}
|
|
|
|
p2p->pending_action_state = P2P_NO_PENDING_ACTION;
|
|
if (p2p_send_action(p2p, rx_freq, sa, p2p->cfg->dev_addr,
|
|
p2p->cfg->dev_addr,
|
|
wpabuf_head(resp), wpabuf_len(resp), 200) < 0)
|
|
p2p_dbg(p2p, "Failed to send Action frame");
|
|
|
|
wpabuf_free(resp);
|
|
}
|
|
|
|
|
|
void p2p_rx_gas_comeback_resp(struct p2p_data *p2p, const u8 *sa,
|
|
const u8 *data, size_t len, int rx_freq)
|
|
{
|
|
const u8 *pos = data;
|
|
const u8 *end = data + len;
|
|
const u8 *next;
|
|
u8 dialog_token;
|
|
u16 status_code;
|
|
u8 frag_id;
|
|
u8 more_frags;
|
|
u16 comeback_delay;
|
|
u16 slen;
|
|
|
|
wpa_hexdump(MSG_DEBUG, "P2P: RX GAS Comeback Response", data, len);
|
|
|
|
if (p2p->state != P2P_SD_DURING_FIND || p2p->sd_peer == NULL ||
|
|
os_memcmp(sa, p2p->sd_peer->info.p2p_device_addr, ETH_ALEN) != 0) {
|
|
p2p_dbg(p2p, "Ignore unexpected GAS Comeback Response from "
|
|
MACSTR, MAC2STR(sa));
|
|
return;
|
|
}
|
|
p2p->cfg->send_action_done(p2p->cfg->cb_ctx);
|
|
p2p_clear_timeout(p2p);
|
|
|
|
p2p_dbg(p2p, "Received GAS Comeback Response from " MACSTR " (len=%d)",
|
|
MAC2STR(sa), (int) len);
|
|
|
|
if (len < 6 + 2) {
|
|
p2p_dbg(p2p, "Too short GAS Comeback Response frame");
|
|
return;
|
|
}
|
|
|
|
dialog_token = *pos++;
|
|
/* TODO: check dialog_token match */
|
|
status_code = WPA_GET_LE16(pos);
|
|
pos += 2;
|
|
frag_id = *pos & 0x7f;
|
|
more_frags = (*pos & 0x80) >> 7;
|
|
pos++;
|
|
comeback_delay = WPA_GET_LE16(pos);
|
|
pos += 2;
|
|
p2p_dbg(p2p, "dialog_token=%u status_code=%u frag_id=%d more_frags=%d "
|
|
"comeback_delay=%u",
|
|
dialog_token, status_code, frag_id, more_frags,
|
|
comeback_delay);
|
|
/* TODO: check frag_id match */
|
|
if (status_code) {
|
|
p2p_dbg(p2p, "Service Discovery failed: status code %u",
|
|
status_code);
|
|
return;
|
|
}
|
|
|
|
if (*pos != WLAN_EID_ADV_PROTO) {
|
|
p2p_dbg(p2p, "Unexpected IE in GAS Comeback Response: %u",
|
|
*pos);
|
|
return;
|
|
}
|
|
pos++;
|
|
|
|
slen = *pos++;
|
|
next = pos + slen;
|
|
if (next > end || slen < 2) {
|
|
p2p_dbg(p2p, "Invalid IE in GAS Comeback Response");
|
|
return;
|
|
}
|
|
pos++; /* skip QueryRespLenLimit and PAME-BI */
|
|
|
|
if (*pos != ACCESS_NETWORK_QUERY_PROTOCOL) {
|
|
p2p_dbg(p2p, "Unsupported GAS advertisement protocol id %u",
|
|
*pos);
|
|
return;
|
|
}
|
|
|
|
pos = next;
|
|
/* Query Response */
|
|
if (pos + 2 > end) {
|
|
p2p_dbg(p2p, "Too short Query Response");
|
|
return;
|
|
}
|
|
slen = WPA_GET_LE16(pos);
|
|
pos += 2;
|
|
p2p_dbg(p2p, "Query Response Length: %d", slen);
|
|
if (pos + slen > end) {
|
|
p2p_dbg(p2p, "Not enough Query Response data");
|
|
return;
|
|
}
|
|
if (slen == 0) {
|
|
p2p_dbg(p2p, "No Query Response data");
|
|
return;
|
|
}
|
|
end = pos + slen;
|
|
|
|
if (p2p->sd_rx_resp) {
|
|
/*
|
|
* ANQP header is only included in the first fragment; rest of
|
|
* the fragments start with continue TLVs.
|
|
*/
|
|
goto skip_nqp_header;
|
|
}
|
|
|
|
/* ANQP Query Response */
|
|
if (pos + 4 > end)
|
|
return;
|
|
if (WPA_GET_LE16(pos) != ANQP_VENDOR_SPECIFIC) {
|
|
p2p_dbg(p2p, "Unsupported ANQP Info ID %u", WPA_GET_LE16(pos));
|
|
return;
|
|
}
|
|
pos += 2;
|
|
|
|
slen = WPA_GET_LE16(pos);
|
|
pos += 2;
|
|
p2p_dbg(p2p, "ANQP Query Response length: %u", slen);
|
|
if (slen < 3 + 1) {
|
|
p2p_dbg(p2p, "Invalid ANQP Query Response length");
|
|
return;
|
|
}
|
|
if (pos + 4 > end)
|
|
return;
|
|
|
|
if (WPA_GET_BE32(pos) != P2P_IE_VENDOR_TYPE) {
|
|
p2p_dbg(p2p, "Unsupported ANQP vendor OUI-type %08x",
|
|
WPA_GET_BE32(pos));
|
|
return;
|
|
}
|
|
pos += 4;
|
|
|
|
if (pos + 2 > end)
|
|
return;
|
|
p2p->sd_rx_update_indic = WPA_GET_LE16(pos);
|
|
p2p_dbg(p2p, "Service Update Indicator: %u", p2p->sd_rx_update_indic);
|
|
pos += 2;
|
|
|
|
skip_nqp_header:
|
|
if (wpabuf_resize(&p2p->sd_rx_resp, end - pos) < 0)
|
|
return;
|
|
wpabuf_put_data(p2p->sd_rx_resp, pos, end - pos);
|
|
p2p_dbg(p2p, "Current SD reassembly buffer length: %u",
|
|
(unsigned int) wpabuf_len(p2p->sd_rx_resp));
|
|
|
|
if (more_frags) {
|
|
p2p_dbg(p2p, "More fragments remains");
|
|
/* TODO: what would be a good size limit? */
|
|
if (wpabuf_len(p2p->sd_rx_resp) > 64000) {
|
|
wpabuf_free(p2p->sd_rx_resp);
|
|
p2p->sd_rx_resp = NULL;
|
|
p2p_dbg(p2p, "Too long SD response - drop it");
|
|
return;
|
|
}
|
|
p2p_send_gas_comeback_req(p2p, sa, dialog_token, rx_freq);
|
|
return;
|
|
}
|
|
|
|
p2p->sd_peer = NULL;
|
|
|
|
if (p2p->sd_query) {
|
|
if (!p2p->sd_query->for_all_peers) {
|
|
struct p2p_sd_query *q;
|
|
p2p_dbg(p2p, "Remove completed SD query %p",
|
|
p2p->sd_query);
|
|
q = p2p->sd_query;
|
|
p2p_unlink_sd_query(p2p, p2p->sd_query);
|
|
p2p_free_sd_query(q);
|
|
}
|
|
p2p->sd_query = NULL;
|
|
}
|
|
|
|
if (p2p->cfg->sd_response)
|
|
p2p->cfg->sd_response(p2p->cfg->cb_ctx, sa,
|
|
p2p->sd_rx_update_indic,
|
|
wpabuf_head(p2p->sd_rx_resp),
|
|
wpabuf_len(p2p->sd_rx_resp));
|
|
wpabuf_free(p2p->sd_rx_resp);
|
|
p2p->sd_rx_resp = NULL;
|
|
|
|
p2p_continue_find(p2p);
|
|
}
|
|
|
|
|
|
void * p2p_sd_request(struct p2p_data *p2p, const u8 *dst,
|
|
const struct wpabuf *tlvs)
|
|
{
|
|
struct p2p_sd_query *q;
|
|
|
|
q = os_zalloc(sizeof(*q));
|
|
if (q == NULL)
|
|
return NULL;
|
|
|
|
if (dst)
|
|
os_memcpy(q->peer, dst, ETH_ALEN);
|
|
else
|
|
q->for_all_peers = 1;
|
|
|
|
q->tlvs = wpabuf_dup(tlvs);
|
|
if (q->tlvs == NULL) {
|
|
p2p_free_sd_query(q);
|
|
return NULL;
|
|
}
|
|
|
|
q->next = p2p->sd_queries;
|
|
p2p->sd_queries = q;
|
|
p2p_dbg(p2p, "Added SD Query %p", q);
|
|
|
|
if (dst == NULL) {
|
|
struct p2p_device *dev;
|
|
|
|
p2p->num_p2p_sd_queries++;
|
|
|
|
/* Update all the devices for the newly added broadcast query */
|
|
dl_list_for_each(dev, &p2p->devices, struct p2p_device, list) {
|
|
if (dev->sd_pending_bcast_queries <= 0)
|
|
dev->sd_pending_bcast_queries = 1;
|
|
else
|
|
dev->sd_pending_bcast_queries++;
|
|
}
|
|
}
|
|
|
|
return q;
|
|
}
|
|
|
|
|
|
#ifdef CONFIG_WIFI_DISPLAY
|
|
void * p2p_sd_request_wfd(struct p2p_data *p2p, const u8 *dst,
|
|
const struct wpabuf *tlvs)
|
|
{
|
|
struct p2p_sd_query *q;
|
|
q = p2p_sd_request(p2p, dst, tlvs);
|
|
if (q)
|
|
q->wsd = 1;
|
|
return q;
|
|
}
|
|
#endif /* CONFIG_WIFI_DISPLAY */
|
|
|
|
|
|
void p2p_sd_service_update(struct p2p_data *p2p)
|
|
{
|
|
p2p->srv_update_indic++;
|
|
}
|
|
|
|
|
|
int p2p_sd_cancel_request(struct p2p_data *p2p, void *req)
|
|
{
|
|
if (p2p_unlink_sd_query(p2p, req)) {
|
|
p2p_dbg(p2p, "Cancel pending SD query %p", req);
|
|
p2p_free_sd_query(req);
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|