From: Jouni Malinen Date: Sun, 18 Jul 2010 21:30:25 +0000 (-0700) Subject: P2P: Add initial version of P2P Module X-Git-Url: http://www.project-moonshot.org/gitweb/?p=libeap.git;a=commitdiff_plain;h=b22128efdc01099b3ad9bb62d3dc168316691b2a P2P: Add initial version of P2P Module --- diff --git a/src/Makefile b/src/Makefile index f47da7b..d73a175 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,4 +1,4 @@ -SUBDIRS=ap common crypto drivers eapol_auth eapol_supp eap_common eap_peer eap_server l2_packet radius rsn_supp tls utils wps +SUBDIRS=ap common crypto drivers eapol_auth eapol_supp eap_common eap_peer eap_server l2_packet p2p radius rsn_supp tls utils wps all: for d in $(SUBDIRS); do [ -d $$d ] && $(MAKE) -C $$d; done diff --git a/src/ap/p2p_hostapd.c b/src/ap/p2p_hostapd.c new file mode 100644 index 0000000..7f99830 --- /dev/null +++ b/src/ap/p2p_hostapd.c @@ -0,0 +1,30 @@ +/* + * hostapd / P2P integration + * Copyright (c) 2009, Atheros Communications + * + * 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 "utils/includes.h" + +#include "utils/common.h" +#include "p2p/p2p.h" +#include "sta_info.h" +#include "p2p_hostapd.h" + + +int hostapd_p2p_get_mib_sta(struct hostapd_data *hapd, struct sta_info *sta, + char *buf, size_t buflen) +{ + if (sta->p2p_ie == NULL) + return 0; + + return p2p_ie_text(sta->p2p_ie, buf, buf + buflen); +} diff --git a/src/ap/p2p_hostapd.h b/src/ap/p2p_hostapd.h new file mode 100644 index 0000000..31aed15 --- /dev/null +++ b/src/ap/p2p_hostapd.h @@ -0,0 +1,33 @@ +/* + * hostapd / P2P integration + * Copyright (c) 2009, Atheros Communications + * + * 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 P2P_HOSTAPD_H +#define P2P_HOSTAPD_H + +#ifdef CONFIG_P2P + +int hostapd_p2p_get_mib_sta(struct hostapd_data *hapd, struct sta_info *sta, + char *buf, size_t buflen); + +#else /* CONFIG_P2P */ + +int hostapd_p2p_get_mib_sta(struct hostapd_data *hapd, struct sta_info *sta, + char *buf, size_t buflen) +{ + return 0; +} + +#endif /* CONFIG_P2P */ + +#endif /* P2P_HOSTAPD_H */ diff --git a/src/common/wpa_ctrl.h b/src/common/wpa_ctrl.h index 5780484..88412ba 100644 --- a/src/common/wpa_ctrl.h +++ b/src/common/wpa_ctrl.h @@ -90,6 +90,32 @@ extern "C" { #define WPS_EVENT_ER_ENROLLEE_REMOVE "WPS-ER-ENROLLEE-REMOVE " #define WPS_EVENT_ER_AP_SETTINGS "WPS-ER-AP-SETTINGS " +/** P2P device found */ +#define P2P_EVENT_DEVICE_FOUND "P2P-DEVICE-FOUND " +/** A P2P device requested GO negotiation, but we were not ready to start the + * negotiation */ +#define P2P_EVENT_GO_NEG_REQUEST "P2P-GO-NEG-REQUEST " +#define P2P_EVENT_GO_NEG_SUCCESS "P2P-GO-NEG-SUCCESS " +#define P2P_EVENT_GO_NEG_FAILURE "P2P-GO-NEG-FAILURE " +#define P2P_EVENT_GROUP_FORMATION_SUCCESS "P2P-GROUP-FORMATION-SUCCESS " +#define P2P_EVENT_GROUP_FORMATION_FAILURE "P2P-GROUP-FORMATION-FAILURE " +#define P2P_EVENT_GROUP_STARTED "P2P-GROUP-STARTED " +#define P2P_EVENT_GROUP_REMOVED "P2P-GROUP-REMOVED " +/* parameters: */ +#define P2P_EVENT_PROV_DISC_SHOW_PIN "P2P-PROV-DISC-SHOW-PIN " +/* parameters: */ +#define P2P_EVENT_PROV_DISC_ENTER_PIN "P2P-PROV-DISC-ENTER-PIN " +/* parameters: */ +#define P2P_EVENT_PROV_DISC_PBC_REQ "P2P-PROV-DISC-PBC-REQ " +/* parameters: */ +#define P2P_EVENT_PROV_DISC_PBC_RESP "P2P-PROV-DISC-PBC-RESP " +/* parameters: */ +#define P2P_EVENT_SERV_DISC_REQ "P2P-SERV-DISC-REQ " +/* parameters: */ +#define P2P_EVENT_SERV_DISC_RESP "P2P-SERV-DISC-RESP " +#define P2P_EVENT_INVITATION_RECEIVED "P2P-INVITATION-RECEIVED " +#define P2P_EVENT_INVITATION_RESULT "P2P-INVITATION-RESULT " + /* 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/src/p2p/Makefile b/src/p2p/Makefile new file mode 100644 index 0000000..cffba62 --- /dev/null +++ b/src/p2p/Makefile @@ -0,0 +1,9 @@ +all: + @echo Nothing to be made. + +clean: + for d in $(SUBDIRS); do make -C $$d clean; done + rm -f *~ *.o *.d + +install: + @echo Nothing to be made. diff --git a/src/p2p/p2p.c b/src/p2p/p2p.c new file mode 100644 index 0000000..6fb1a0c --- /dev/null +++ b/src/p2p/p2p.c @@ -0,0 +1,2900 @@ +/* + * Wi-Fi Direct - P2P module + * Copyright (c) 2009-2010, Atheros Communications + * + * 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 "eloop.h" +#include "common/ieee802_11_defs.h" +#include "common/ieee802_11_common.h" +#include "wps/wps_i.h" +#include "p2p_i.h" +#include "p2p.h" + + +static void p2p_state_timeout(void *eloop_ctx, void *timeout_ctx); +static void p2p_device_free(struct p2p_data *p2p, struct p2p_device *dev); +static void p2p_process_presence_req(struct p2p_data *p2p, const u8 *da, + const u8 *sa, const u8 *data, size_t len, + int rx_freq); +static void p2p_process_presence_resp(struct p2p_data *p2p, const u8 *da, + const u8 *sa, const u8 *data, + size_t len); +static void p2p_ext_listen_timeout(void *eloop_ctx, void *timeout_ctx); + + +/** + * P2P_PEER_EXPIRATION_AGE - Number of seconds after which inactive peer + * entries will be removed + */ +#define P2P_PEER_EXPIRATION_AGE 300 + +#define P2P_PEER_EXPIRATION_INTERVAL (P2P_PEER_EXPIRATION_AGE / 2) + +static void p2p_expire_peers(struct p2p_data *p2p) +{ + struct p2p_device *dev, *n; + struct os_time now; + + os_get_time(&now); + dl_list_for_each_safe(dev, n, &p2p->devices, struct p2p_device, list) { + if (dev->last_seen.sec + P2P_PEER_EXPIRATION_AGE >= now.sec) + continue; + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Expiring old peer " + "entry " MACSTR, MAC2STR(dev->p2p_device_addr)); + dl_list_del(&dev->list); + p2p_device_free(p2p, dev); + } +} + + +static void p2p_expiration_timeout(void *eloop_ctx, void *timeout_ctx) +{ + struct p2p_data *p2p = eloop_ctx; + p2p_expire_peers(p2p); + eloop_register_timeout(P2P_PEER_EXPIRATION_INTERVAL, 0, + p2p_expiration_timeout, p2p, NULL); +} + + +static const char * p2p_state_txt(int state) +{ + switch (state) { + case P2P_IDLE: + return "IDLE"; + case P2P_SEARCH: + return "SEARCH"; + case P2P_CONNECT: + return "CONNECT"; + case P2P_CONNECT_LISTEN: + return "CONNECT_LISTEN"; + case P2P_GO_NEG: + return "GO_NEG"; + case P2P_LISTEN_ONLY: + return "LISTEN_ONLY"; + case P2P_WAIT_PEER_CONNECT: + return "WAIT_PEER_CONNECT"; + case P2P_WAIT_PEER_IDLE: + return "WAIT_PEER_IDLE"; + case P2P_SD_DURING_FIND: + return "SD_DURING_FIND"; + case P2P_PROVISIONING: + return "PROVISIONING"; + case P2P_PD_DURING_FIND: + return "PD_DURING_FIND"; + case P2P_INVITE: + return "INVITE"; + case P2P_INVITE_LISTEN: + return "INVITE_LISTEN"; + default: + return "?"; + } +} + + +void p2p_set_state(struct p2p_data *p2p, int new_state) +{ + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: State %s -> %s", + p2p_state_txt(p2p->state), p2p_state_txt(new_state)); + p2p->state = new_state; +} + + +void p2p_set_timeout(struct p2p_data *p2p, unsigned int sec, unsigned int usec) +{ + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Set timeout (state=%s): %u.%06u sec", + p2p_state_txt(p2p->state), sec, usec); + eloop_cancel_timeout(p2p_state_timeout, p2p, NULL); + eloop_register_timeout(sec, usec, p2p_state_timeout, p2p, NULL); +} + + +void p2p_clear_timeout(struct p2p_data *p2p) +{ + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Clear timeout (state=%s)", + p2p_state_txt(p2p->state)); + eloop_cancel_timeout(p2p_state_timeout, p2p, NULL); +} + + +void p2p_go_neg_failed(struct p2p_data *p2p, struct p2p_device *peer, + int status) +{ + struct p2p_go_neg_results res; + p2p_clear_timeout(p2p); + p2p_set_state(p2p, P2P_IDLE); + p2p->go_neg_peer = NULL; + + os_memset(&res, 0, sizeof(res)); + res.status = status; + if (peer) { + os_memcpy(res.peer_device_addr, peer->p2p_device_addr, + ETH_ALEN); + os_memcpy(res.peer_interface_addr, peer->intended_addr, + ETH_ALEN); + } + p2p->cfg->go_neg_completed(p2p->cfg->cb_ctx, &res); +} + + +static void p2p_listen_in_find(struct p2p_data *p2p) +{ + unsigned int r, tu; + int freq; + struct wpabuf *ies; + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Starting short listen state (state=%s)", + p2p_state_txt(p2p->state)); + + freq = p2p_channel_to_freq(p2p->cfg->country, p2p->cfg->reg_class, + p2p->cfg->channel); + if (freq < 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Unknown regulatory class/channel"); + return; + } + + os_get_random((u8 *) &r, sizeof(r)); + tu = (r % ((p2p->max_disc_int - p2p->min_disc_int) + 1) + + p2p->min_disc_int) * 100; + + p2p->pending_listen_freq = freq; + p2p->pending_listen_sec = 0; + p2p->pending_listen_usec = 1024 * tu; + + ies = p2p_build_probe_resp_ies(p2p); + if (ies == NULL) + return; + + if (p2p->cfg->start_listen(p2p->cfg->cb_ctx, freq, 1024 * tu / 1000, + ies) < 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Failed to start listen mode"); + p2p->pending_listen_freq = 0; + } + wpabuf_free(ies); +} + + +int p2p_listen(struct p2p_data *p2p, unsigned int timeout) +{ + int freq; + struct wpabuf *ies; + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Going to listen(only) state"); + + freq = p2p_channel_to_freq(p2p->cfg->country, p2p->cfg->reg_class, + p2p->cfg->channel); + if (freq < 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Unknown regulatory class/channel"); + return -1; + } + + p2p->pending_listen_freq = freq; + p2p->pending_listen_sec = timeout / 1000; + p2p->pending_listen_usec = (timeout % 1000) * 1000; + + if (p2p->p2p_scan_running) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: p2p_scan running - delay start of listen state"); + p2p->start_after_scan = P2P_AFTER_SCAN_LISTEN; + return 0; + } + + ies = p2p_build_probe_resp_ies(p2p); + if (ies == NULL) + return -1; + + if (p2p->cfg->start_listen(p2p->cfg->cb_ctx, freq, timeout, ies) < 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Failed to start listen mode"); + p2p->pending_listen_freq = 0; + wpabuf_free(ies); + return -1; + } + wpabuf_free(ies); + + p2p_set_state(p2p, P2P_LISTEN_ONLY); + + return 0; +} + + +static void p2p_device_clear_reported(struct p2p_data *p2p) +{ + struct p2p_device *dev; + dl_list_for_each(dev, &p2p->devices, struct p2p_device, list) + dev->flags &= ~P2P_DEV_REPORTED; +} + + +/** + * p2p_get_device - Fetch a peer entry + * @p2p: P2P module context from p2p_init() + * @addr: P2P Device Address of the peer + * Returns: Pointer to the device entry or %NULL if not found + */ +struct p2p_device * p2p_get_device(struct p2p_data *p2p, const u8 *addr) +{ + struct p2p_device *dev; + dl_list_for_each(dev, &p2p->devices, struct p2p_device, list) { + if (os_memcmp(dev->p2p_device_addr, addr, ETH_ALEN) == 0) + return dev; + } + return NULL; +} + + +/** + * p2p_get_device_interface - Fetch a peer entry based on P2P Interface Address + * @p2p: P2P module context from p2p_init() + * @addr: P2P Interface Address of the peer + * Returns: Pointer to the device entry or %NULL if not found + */ +struct p2p_device * p2p_get_device_interface(struct p2p_data *p2p, + const u8 *addr) +{ + struct p2p_device *dev; + dl_list_for_each(dev, &p2p->devices, struct p2p_device, list) { + if (os_memcmp(dev->interface_addr, addr, ETH_ALEN) == 0) + return dev; + } + return NULL; +} + + +/** + * p2p_create_device - Create a peer entry + * @p2p: P2P module context from p2p_init() + * @addr: P2P Device Address of the peer + * Returns: Pointer to the device entry or %NULL on failure + * + * If there is already an entry for the peer, it will be returned instead of + * creating a new one. + */ +static struct p2p_device * p2p_create_device(struct p2p_data *p2p, + const u8 *addr) +{ + struct p2p_device *dev, *oldest = NULL; + size_t count = 0; + + dev = p2p_get_device(p2p, addr); + if (dev) + return dev; + + dl_list_for_each(dev, &p2p->devices, struct p2p_device, list) { + count++; + if (oldest == NULL || + os_time_before(&dev->last_seen, &oldest->last_seen)) + oldest = dev; + } + if (count + 1 > p2p->cfg->max_peers && oldest) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Remove oldest peer entry to make room for a new " + "peer"); + dl_list_del(&oldest->list); + p2p_device_free(p2p, oldest); + } + + dev = os_zalloc(sizeof(*dev)); + if (dev == NULL) + return NULL; + dl_list_add(&p2p->devices, &dev->list); + os_memcpy(dev->p2p_device_addr, addr, ETH_ALEN); + + return dev; +} + + +static void p2p_copy_client_info(struct p2p_device *dev, + struct p2p_client_info *cli) +{ + os_memcpy(dev->device_name, cli->dev_name, cli->dev_name_len); + dev->device_name[cli->dev_name_len] = '\0'; + dev->dev_capab = cli->dev_capab; + dev->config_methods = cli->config_methods; + os_memcpy(dev->pri_dev_type, cli->pri_dev_type, 8); +} + + +static int p2p_add_group_clients(struct p2p_data *p2p, const u8 *go_dev_addr, + const u8 *go_interface_addr, int freq, + const u8 *gi, size_t gi_len) +{ + struct p2p_group_info info; + size_t c; + struct p2p_device *dev; + + if (gi == NULL) + return 0; + + if (p2p_group_info_parse(gi, gi_len, &info) < 0) + return -1; + + /* + * Clear old data for this group; if the devices are still in the + * group, the information will be restored in the loop following this. + */ + dl_list_for_each(dev, &p2p->devices, struct p2p_device, list) { + if (os_memcpy(dev->member_in_go_iface, go_interface_addr, + ETH_ALEN) == 0) { + os_memset(dev->member_in_go_iface, 0, ETH_ALEN); + os_memset(dev->member_in_go_dev, 0, ETH_ALEN); + } + } + + for (c = 0; c < info.num_clients; c++) { + struct p2p_client_info *cli = &info.client[c]; + dev = p2p_get_device(p2p, cli->p2p_device_addr); + if (dev) { + /* + * Update information only if we have not received this + * directly from the client. + */ + if (dev->flags & (P2P_DEV_GROUP_CLIENT_ONLY | + P2P_DEV_PROBE_REQ_ONLY)) + p2p_copy_client_info(dev, cli); + if (dev->flags & P2P_DEV_PROBE_REQ_ONLY) { + dev->flags &= ~P2P_DEV_PROBE_REQ_ONLY; + } + } else { + dev = p2p_create_device(p2p, cli->p2p_device_addr); + if (dev == NULL) + continue; + dev->flags |= P2P_DEV_GROUP_CLIENT_ONLY; + p2p_copy_client_info(dev, cli); + dev->oper_freq = freq; + p2p->cfg->dev_found( + p2p->cfg->cb_ctx, dev->p2p_device_addr, + dev->p2p_device_addr, dev->pri_dev_type, + dev->device_name, dev->config_methods, + dev->dev_capab, 0); + } + + os_memcpy(dev->interface_addr, cli->p2p_interface_addr, + ETH_ALEN); + os_get_time(&dev->last_seen); + os_memcpy(dev->member_in_go_dev, go_dev_addr, ETH_ALEN); + os_memcpy(dev->member_in_go_iface, go_interface_addr, + ETH_ALEN); + } + + return 0; +} + + +/** + * p2p_add_device - Add peer entries based on scan results + * @p2p: P2P module context from p2p_init() + * @addr: Source address of Beacon or Probe Response frame (may be either + * P2P Device Address or P2P Interface Address) + * @level: Signal level (signal strength of the received frame from the peer) + * @freq: Frequency on which the Beacon or Probe Response frame was received + * @ies: IEs from the Beacon or Probe Response frame + * @ies_len: Length of ies buffer in octets + * Returns: 0 on success, -1 on failure + * + * If the scan result is for a GO, the clients in the group will also be added + * to the peer table. + */ +static int p2p_add_device(struct p2p_data *p2p, const u8 *addr, int freq, + int level, const u8 *ies, size_t ies_len) +{ + struct p2p_device *dev; + struct p2p_message msg; + const u8 *p2p_dev_addr; + + os_memset(&msg, 0, sizeof(msg)); + if (p2p_parse_ies(ies, ies_len, &msg)) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Failed to parse P2P IE for a device entry"); + p2p_parse_free(&msg); + return -1; + } + + if (msg.p2p_device_addr) + p2p_dev_addr = msg.p2p_device_addr; + else if (msg.device_id) + p2p_dev_addr = msg.device_id; + else { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Ignore scan data without P2P Device Info or " + "P2P Device Id"); + p2p_parse_free(&msg); + return -1; + } + + dev = p2p_create_device(p2p, p2p_dev_addr); + if (dev == NULL) { + p2p_parse_free(&msg); + return -1; + } + os_get_time(&dev->last_seen); + dev->flags &= ~(P2P_DEV_PROBE_REQ_ONLY | P2P_DEV_GROUP_CLIENT_ONLY); + + if (os_memcmp(addr, p2p_dev_addr, ETH_ALEN) != 0) + os_memcpy(dev->interface_addr, addr, ETH_ALEN); + if (msg.ssid && + (msg.ssid[1] != P2P_WILDCARD_SSID_LEN || + os_memcmp(msg.ssid + 2, P2P_WILDCARD_SSID, P2P_WILDCARD_SSID_LEN) + != 0)) { + os_memcpy(dev->oper_ssid, msg.ssid + 2, msg.ssid[1]); + dev->oper_ssid_len = msg.ssid[1]; + } + + if (freq >= 2412 && freq <= 2484 && msg.ds_params && + *msg.ds_params >= 1 && *msg.ds_params <= 14) { + int ds_freq; + if (*msg.ds_params == 14) + ds_freq = 2484; + else + ds_freq = 2407 + *msg.ds_params * 5; + if (freq != ds_freq) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Update Listen frequency based on DS " + "Parameter Set IE: %d -> %d MHz", + freq, ds_freq); + freq = ds_freq; + } + } + + if (dev->listen_freq && dev->listen_freq != freq) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Update Listen frequency based on scan " + "results (" MACSTR " %d -> %d MHz (DS param %d)", + MAC2STR(dev->p2p_device_addr), dev->listen_freq, freq, + msg.ds_params ? *msg.ds_params : -1); + } + dev->listen_freq = freq; + dev->level = level; + + if (msg.pri_dev_type) + os_memcpy(dev->pri_dev_type, msg.pri_dev_type, + sizeof(dev->pri_dev_type)); + os_memcpy(dev->device_name, msg.device_name, sizeof(dev->device_name)); + dev->config_methods = msg.config_methods ? msg.config_methods : + msg.wps_config_methods; + if (msg.capability) { + dev->dev_capab = msg.capability[0]; + dev->group_capab = msg.capability[1]; + } + + if (msg.ext_listen_timing) { + dev->ext_listen_period = WPA_GET_LE16(msg.ext_listen_timing); + dev->ext_listen_interval = + WPA_GET_LE16(msg.ext_listen_timing + 2); + } + + p2p_add_group_clients(p2p, p2p_dev_addr, addr, freq, msg.group_info, + msg.group_info_len); + + p2p_parse_free(&msg); + + if (p2p_pending_sd_req(p2p, dev)) + dev->flags |= P2P_DEV_SD_SCHEDULE; + + if (dev->flags & P2P_DEV_REPORTED) + return 0; + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Peer found with Listen frequency %d MHz", freq); + if (dev->flags & P2P_DEV_USER_REJECTED) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Do not report rejected device"); + return 0; + } + p2p->cfg->dev_found(p2p->cfg->cb_ctx, addr, dev->p2p_device_addr, + dev->pri_dev_type, dev->device_name, + dev->config_methods, dev->dev_capab, + dev->group_capab); + dev->flags |= P2P_DEV_REPORTED; + + return 0; +} + + +static void p2p_device_free(struct p2p_data *p2p, struct p2p_device *dev) +{ + if (p2p->go_neg_peer == dev) + p2p->go_neg_peer = NULL; + if (p2p->invite_peer == dev) + p2p->invite_peer = NULL; + if (p2p->sd_peer == dev) + p2p->sd_peer = NULL; + if (p2p->pending_client_disc_go == dev) + p2p->pending_client_disc_go = NULL; + + os_free(dev); +} + + +static int p2p_get_next_prog_freq(struct p2p_data *p2p) +{ + struct p2p_channels *c; + struct p2p_reg_class *cla; + size_t cl, ch; + int found = 0; + u8 reg_class; + u8 channel; + int freq; + + c = &p2p->cfg->channels; + for (cl = 0; cl < c->reg_classes; cl++) { + cla = &c->reg_class[cl]; + if (cla->reg_class != p2p->last_prog_scan_class) + continue; + for (ch = 0; ch < cla->channels; ch++) { + if (cla->channel[ch] == p2p->last_prog_scan_chan) { + found = 1; + break; + } + } + if (found) + break; + } + + if (!found) { + /* Start from beginning */ + reg_class = c->reg_class[0].reg_class; + channel = c->reg_class[0].channel[0]; + } else { + /* Pick the next channel */ + ch++; + if (ch == cla->channels) { + cl++; + if (cl == c->reg_classes) + cl = 0; + ch = 0; + } + reg_class = c->reg_class[cl].reg_class; + channel = c->reg_class[cl].channel[ch]; + } + + freq = p2p_channel_to_freq(p2p->cfg->country, reg_class, channel); + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Next progressive search " + "channel: reg_class %u channel %u -> %d MHz", + reg_class, channel, freq); + p2p->last_prog_scan_class = reg_class; + p2p->last_prog_scan_chan = channel; + + if (freq == 2412 || freq == 2437 || freq == 2462) + return 0; /* No need to add social channels */ + return freq; +} + + +static void p2p_search(struct p2p_data *p2p) +{ + int freq = 0; + enum p2p_scan_type type; + + if (p2p->drv_in_listen) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Driver is still " + "in Listen state - wait for it to end before " + "continuing"); + return; + } + p2p->cfg->stop_listen(p2p->cfg->cb_ctx); + + if (p2p->go_neg_peer) { + /* + * Only scan the known listen frequency of the peer + * during GO Negotiation start. + */ + freq = p2p->go_neg_peer->listen_freq; + if (freq <= 0) + freq = p2p->go_neg_peer->oper_freq; + type = P2P_SCAN_SPECIFIC; + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Starting search " + "for freq %u (GO Neg)", freq); + } else if (p2p->invite_peer) { + /* + * Only scan the known listen frequency of the peer + * during Invite start. + */ + freq = p2p->invite_peer->listen_freq; + if (freq <= 0) + freq = p2p->invite_peer->oper_freq; + type = P2P_SCAN_SPECIFIC; + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Starting search " + "for freq %u (Invite)", freq); + } else if (p2p->find_type == P2P_FIND_PROGRESSIVE && + (freq = p2p_get_next_prog_freq(p2p)) > 0) { + type = P2P_SCAN_SOCIAL_PLUS_ONE; + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Starting search " + "(+ freq %u)", freq); + } else { + type = P2P_SCAN_SOCIAL; + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Starting search"); + } + + if (p2p->cfg->p2p_scan(p2p->cfg->cb_ctx, type, freq) < 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Scan request failed"); + p2p_continue_find(p2p); + } +} + + +static void p2p_find_timeout(void *eloop_ctx, void *timeout_ctx) +{ + struct p2p_data *p2p = eloop_ctx; + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Find timeout -> stop"); + p2p_stop_find(p2p); +} + + +static int p2p_run_after_scan(struct p2p_data *p2p) +{ + struct p2p_device *dev; + enum p2p_after_scan op; + + op = p2p->start_after_scan; + p2p->start_after_scan = P2P_AFTER_SCAN_NOTHING; + switch (op) { + case P2P_AFTER_SCAN_NOTHING: + break; + case P2P_AFTER_SCAN_LISTEN: + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Start previously " + "requested Listen state"); + p2p_listen(p2p, p2p->pending_listen_sec * 1000 + + p2p->pending_listen_usec / 1000); + return 1; + case P2P_AFTER_SCAN_CONNECT: + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Start previously " + "requested connect with " MACSTR, + MAC2STR(p2p->after_scan_peer)); + dev = p2p_get_device(p2p, p2p->after_scan_peer); + if (dev == NULL) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Peer not " + "known anymore"); + break; + } + p2p_connect_send(p2p, dev); + return 1; + } + + return 0; +} + + +/* + * p2p_scan recovery timeout + * + * Many drivers are using 30 second timeout on scan results. Allow a bit larger + * timeout for this to avoid hitting P2P timeout unnecessarily. + */ +#define P2P_SCAN_TIMEOUT 35 + +static void p2p_scan_timeout(void *eloop_ctx, void *timeout_ctx) +{ + struct p2p_data *p2p = eloop_ctx; + int running; + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: p2p_scan timeout " + "(running=%d)", p2p->p2p_scan_running); + running = p2p->p2p_scan_running; + /* Make sure we recover from missed scan results callback */ + p2p->p2p_scan_running = 0; + + if (running) + p2p_run_after_scan(p2p); +} + + +int p2p_find(struct p2p_data *p2p, unsigned int timeout, + enum p2p_discovery_type type) +{ + int res; + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Starting find (type=%d)", + type); + if (p2p->p2p_scan_running) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: p2p_scan is " + "already running"); + } + p2p->start_after_scan = P2P_AFTER_SCAN_NOTHING; + p2p_clear_timeout(p2p); + p2p->cfg->stop_listen(p2p->cfg->cb_ctx); + p2p->find_type = type; + p2p_device_clear_reported(p2p); + p2p_set_state(p2p, P2P_SEARCH); + eloop_cancel_timeout(p2p_find_timeout, p2p, NULL); + if (timeout) + eloop_register_timeout(timeout, 0, p2p_find_timeout, + p2p, NULL); + switch (type) { + case P2P_FIND_START_WITH_FULL: + case P2P_FIND_PROGRESSIVE: + res = p2p->cfg->p2p_scan(p2p->cfg->cb_ctx, P2P_SCAN_FULL, 0); + break; + case P2P_FIND_ONLY_SOCIAL: + res = p2p->cfg->p2p_scan(p2p->cfg->cb_ctx, P2P_SCAN_SOCIAL, 0); + break; + default: + return -1; + } + + if (res == 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Running p2p_scan"); + p2p->p2p_scan_running = 1; + eloop_cancel_timeout(p2p_scan_timeout, p2p, NULL); + eloop_register_timeout(P2P_SCAN_TIMEOUT, 0, p2p_scan_timeout, + p2p, NULL); + } else { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Failed to start " + "p2p_scan"); + } + + return res; +} + + +void p2p_stop_find(struct p2p_data *p2p) +{ + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Stopping find"); + eloop_cancel_timeout(p2p_find_timeout, p2p, NULL); + p2p_clear_timeout(p2p); + p2p_set_state(p2p, P2P_IDLE); + p2p->start_after_scan = P2P_AFTER_SCAN_NOTHING; + p2p->go_neg_peer = NULL; + p2p->sd_peer = NULL; + p2p->invite_peer = NULL; + p2p->cfg->stop_listen(p2p->cfg->cb_ctx); +} + + +int p2p_connect(struct p2p_data *p2p, const u8 *peer_addr, + enum p2p_wps_method wps_method, + int go_intent, const u8 *own_interface_addr, + unsigned int force_freq, int persistent_group) +{ + struct p2p_device *dev; + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Request to start group negotiation - peer=" MACSTR + " GO Intent=%d Intended Interface Address=" MACSTR + " wps_method=%d persistent_group=%d", + MAC2STR(peer_addr), go_intent, MAC2STR(own_interface_addr), + wps_method, persistent_group); + + if (force_freq) { + if (p2p_freq_to_channel(p2p->cfg->country, force_freq, + &p2p->op_reg_class, &p2p->op_channel) < + 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Unsupported frequency %u MHz", + force_freq); + return -1; + } + p2p->channels.reg_classes = 1; + p2p->channels.reg_class[0].channels = 1; + p2p->channels.reg_class[0].reg_class = p2p->op_reg_class; + p2p->channels.reg_class[0].channel[0] = p2p->op_channel; + } else { + p2p->op_reg_class = p2p->cfg->op_reg_class; + p2p->op_channel = p2p->cfg->op_channel; + os_memcpy(&p2p->channels, &p2p->cfg->channels, + sizeof(struct p2p_channels)); + } + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Own preference for operation channel: " + "Regulatory Class %u Channel %u%s", + p2p->op_reg_class, p2p->op_channel, + force_freq ? " (forced)" : ""); + + dev = p2p_get_device(p2p, peer_addr); + if (dev == NULL || (dev->flags & P2P_DEV_PROBE_REQ_ONLY)) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Cannot connect to unknown P2P Device " MACSTR, + MAC2STR(peer_addr)); + return -1; + } + + if (dev->flags & P2P_DEV_GROUP_CLIENT_ONLY) { + if (!(dev->dev_capab & P2P_DEV_CAPAB_CLIENT_DISCOVERABILITY)) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Cannot connect to P2P Device " MACSTR + " that is in a group and is not discoverable", + MAC2STR(peer_addr)); + return -1; + } + if (dev->oper_freq <= 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Cannot connect to P2P Device " MACSTR + " with incomplete information", + MAC2STR(peer_addr)); + return -1; + } + + /* + * First, try to connect directly. If the peer does not + * acknowledge frames, assume it is sleeping and use device + * discoverability via the GO at that point. + */ + } + + dev->flags &= ~P2P_DEV_NOT_YET_READY; + dev->flags &= ~P2P_DEV_USER_REJECTED; + dev->flags &= ~P2P_DEV_WAIT_GO_NEG_RESPONSE; + dev->flags &= ~P2P_DEV_WAIT_GO_NEG_CONFIRM; + dev->go_neg_req_sent = 0; + dev->go_state = UNKNOWN_GO; + if (persistent_group) + dev->flags |= P2P_DEV_PREFER_PERSISTENT_GROUP; + else + dev->flags &= ~P2P_DEV_PREFER_PERSISTENT_GROUP; + p2p->go_intent = go_intent; + os_memcpy(p2p->intended_addr, own_interface_addr, ETH_ALEN); + + if (p2p->state != P2P_IDLE) + p2p_stop_find(p2p); + + dev->wps_method = wps_method; + dev->status = P2P_SC_SUCCESS; + if (p2p->p2p_scan_running) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: p2p_scan running - delay connect send"); + p2p->start_after_scan = P2P_AFTER_SCAN_CONNECT; + os_memcpy(p2p->after_scan_peer, peer_addr, ETH_ALEN); + return 0; + } + p2p->start_after_scan = P2P_AFTER_SCAN_NOTHING; + + return p2p_connect_send(p2p, dev); +} + + +int p2p_authorize(struct p2p_data *p2p, const u8 *peer_addr, + enum p2p_wps_method wps_method, + int go_intent, const u8 *own_interface_addr, + unsigned int force_freq, int persistent_group) +{ + struct p2p_device *dev; + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Request to authorize group negotiation - peer=" MACSTR + " GO Intent=%d Intended Interface Address=" MACSTR + " wps_method=%d persistent_group=%d", + MAC2STR(peer_addr), go_intent, MAC2STR(own_interface_addr), + wps_method, persistent_group); + + if (force_freq) { + if (p2p_freq_to_channel(p2p->cfg->country, force_freq, + &p2p->op_reg_class, &p2p->op_channel) < + 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Unsupported frequency %u MHz", + force_freq); + return -1; + } + p2p->channels.reg_classes = 1; + p2p->channels.reg_class[0].channels = 1; + p2p->channels.reg_class[0].reg_class = p2p->op_reg_class; + p2p->channels.reg_class[0].channel[0] = p2p->op_channel; + } else { + p2p->op_reg_class = p2p->cfg->op_reg_class; + p2p->op_channel = p2p->cfg->op_channel; + os_memcpy(&p2p->channels, &p2p->cfg->channels, + sizeof(struct p2p_channels)); + } + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Own preference for operation channel: " + "Regulatory Class %u Channel %u%s", + p2p->op_reg_class, p2p->op_channel, + force_freq ? " (forced)" : ""); + + dev = p2p_get_device(p2p, peer_addr); + if (dev == NULL) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Cannot authorize unknown P2P Device " MACSTR, + MAC2STR(peer_addr)); + return -1; + } + + dev->flags &= ~P2P_DEV_NOT_YET_READY; + dev->flags &= ~P2P_DEV_USER_REJECTED; + dev->go_neg_req_sent = 0; + dev->go_state = UNKNOWN_GO; + if (persistent_group) + dev->flags |= P2P_DEV_PREFER_PERSISTENT_GROUP; + else + dev->flags &= ~P2P_DEV_PREFER_PERSISTENT_GROUP; + p2p->go_intent = go_intent; + os_memcpy(p2p->intended_addr, own_interface_addr, ETH_ALEN); + + dev->wps_method = wps_method; + dev->status = P2P_SC_SUCCESS; + + return 0; +} + + +void p2p_add_dev_info(struct p2p_data *p2p, const u8 *addr, + struct p2p_device *dev, struct p2p_message *msg) +{ + os_get_time(&dev->last_seen); + + if (msg->pri_dev_type) + os_memcpy(dev->pri_dev_type, msg->pri_dev_type, + sizeof(dev->pri_dev_type)); + os_memcpy(dev->device_name, msg->device_name, + sizeof(dev->device_name)); + dev->config_methods = msg->config_methods ? msg->config_methods : + msg->wps_config_methods; + if (msg->capability) { + dev->dev_capab = msg->capability[0]; + dev->group_capab = msg->capability[1]; + } + if (msg->listen_channel) { + int freq; + freq = p2p_channel_to_freq((char *) msg->listen_channel, + msg->listen_channel[3], + msg->listen_channel[4]); + if (freq < 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Unknown peer Listen channel: " + "country=%c%c(0x%02x) reg_class=%u channel=%u", + msg->listen_channel[0], + msg->listen_channel[1], + msg->listen_channel[2], + msg->listen_channel[3], + msg->listen_channel[4]); + } else { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Update " + "peer " MACSTR " Listen channel: %u -> %u MHz", + MAC2STR(dev->p2p_device_addr), + dev->listen_freq, freq); + dev->listen_freq = freq; + } + } + if (msg->ext_listen_timing) { + dev->ext_listen_period = WPA_GET_LE16(msg->ext_listen_timing); + dev->ext_listen_interval = + WPA_GET_LE16(msg->ext_listen_timing + 2); + } + + if (dev->flags & P2P_DEV_PROBE_REQ_ONLY) { + dev->flags &= ~P2P_DEV_PROBE_REQ_ONLY; + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Completed device entry based on data from " + "GO Negotiation Request"); + } else { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Created device entry based on GO Neg Req: " + MACSTR " dev_capab=0x%x group_capab=0x%x name='%s' " + "listen_freq=%d", + MAC2STR(dev->p2p_device_addr), dev->dev_capab, + dev->group_capab, dev->device_name, dev->listen_freq); + } + + dev->flags &= ~P2P_DEV_GROUP_CLIENT_ONLY; + + if (dev->flags & P2P_DEV_USER_REJECTED) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Do not report rejected device"); + return; + } + + p2p->cfg->dev_found(p2p->cfg->cb_ctx, addr, dev->p2p_device_addr, + dev->pri_dev_type, dev->device_name, + dev->config_methods, dev->dev_capab, + dev->group_capab); +} + + +void p2p_build_ssid(struct p2p_data *p2p, u8 *ssid, size_t *ssid_len) +{ + os_memcpy(ssid, P2P_WILDCARD_SSID, P2P_WILDCARD_SSID_LEN); + p2p_random((char *) &ssid[P2P_WILDCARD_SSID_LEN], 2); + os_memcpy(&ssid[P2P_WILDCARD_SSID_LEN + 2], + p2p->cfg->ssid_postfix, p2p->cfg->ssid_postfix_len); + *ssid_len = P2P_WILDCARD_SSID_LEN + 2 + p2p->cfg->ssid_postfix_len; +} + + +int p2p_go_params(struct p2p_data *p2p, struct p2p_go_neg_results *params) +{ + p2p_build_ssid(p2p, params->ssid, ¶ms->ssid_len); + p2p_random(params->passphrase, 8); + return 0; +} + + +void p2p_go_complete(struct p2p_data *p2p, struct p2p_device *peer) +{ + struct p2p_go_neg_results res; + int go = peer->go_state == LOCAL_GO; + struct p2p_channels intersection; + int freqs; + size_t i, j; + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: GO Negotiation with " MACSTR " completed (%s will be " + "GO)", MAC2STR(peer->p2p_device_addr), + go ? "local end" : "peer"); + + os_memset(&res, 0, sizeof(res)); + res.role_go = go; + os_memcpy(res.peer_device_addr, peer->p2p_device_addr, ETH_ALEN); + os_memcpy(res.peer_interface_addr, peer->intended_addr, ETH_ALEN); + res.wps_method = peer->wps_method; + if (peer->flags & P2P_DEV_PREFER_PERSISTENT_GROUP) + res.persistent_group = 1; + + if (go) { + /* Setup AP mode for WPS provisioning */ + res.freq = p2p_channel_to_freq(p2p->cfg->country, + p2p->op_reg_class, + p2p->op_channel); + os_memcpy(res.ssid, p2p->ssid, p2p->ssid_len); + res.ssid_len = p2p->ssid_len; + p2p_random(res.passphrase, 8); + } else + res.freq = peer->oper_freq; + + p2p_channels_intersect(&p2p->channels, &peer->channels, + &intersection); + freqs = 0; + for (i = 0; i < intersection.reg_classes; i++) { + struct p2p_reg_class *c = &intersection.reg_class[i]; + if (freqs + 1 == P2P_MAX_CHANNELS) + break; + for (j = 0; j < c->channels; j++) { + int freq; + if (freqs + 1 == P2P_MAX_CHANNELS) + break; + freq = p2p_channel_to_freq(peer->country, c->reg_class, + c->channel[j]); + if (freq < 0) + continue; + res.freq_list[freqs++] = freq; + } + } + + p2p_clear_timeout(p2p); + peer->go_neg_req_sent = 0; + peer->wps_method = WPS_NOT_READY; + + p2p_set_state(p2p, P2P_PROVISIONING); + p2p->cfg->go_neg_completed(p2p->cfg->cb_ctx, &res); +} + + +static void p2p_rx_p2p_action(struct p2p_data *p2p, const u8 *sa, + const u8 *data, size_t len, int rx_freq) +{ + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: RX P2P Public Action from " MACSTR, MAC2STR(sa)); + wpa_hexdump(MSG_MSGDUMP, "P2P: P2P Public Action contents", data, len); + + if (len < 1) + return; + + switch (data[0]) { + case P2P_GO_NEG_REQ: + p2p_process_go_neg_req(p2p, sa, data + 1, len - 1, rx_freq); + break; + case P2P_GO_NEG_RESP: + p2p_process_go_neg_resp(p2p, sa, data + 1, len - 1, rx_freq); + break; + case P2P_GO_NEG_CONF: + p2p_process_go_neg_conf(p2p, sa, data + 1, len - 1); + break; + case P2P_INVITATION_REQ: + p2p_process_invitation_req(p2p, sa, data + 1, len - 1, + rx_freq); + break; + case P2P_INVITATION_RESP: + p2p_process_invitation_resp(p2p, sa, data + 1, len - 1); + break; + case P2P_PROV_DISC_REQ: + p2p_process_prov_disc_req(p2p, sa, data + 1, len - 1, rx_freq); + break; + case P2P_PROV_DISC_RESP: + p2p_process_prov_disc_resp(p2p, sa, data + 1, len - 1); + break; + case P2P_DEV_DISC_REQ: + p2p_process_dev_disc_req(p2p, sa, data + 1, len - 1, rx_freq); + break; + case P2P_DEV_DISC_RESP: + p2p_process_dev_disc_resp(p2p, sa, data + 1, len - 1); + break; + default: + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Unsupported P2P Public Action frame type %d", + data[0]); + break; + } +} + + +void p2p_rx_action_public(struct p2p_data *p2p, const u8 *da, const u8 *sa, + const u8 *bssid, const u8 *data, size_t len, + int freq) +{ + if (len < 1) + return; + + switch (data[0]) { + case WLAN_PA_VENDOR_SPECIFIC: + data++; + len--; + if (len < 3) + return; + if (WPA_GET_BE24(data) != OUI_WFA) + return; + + data += 3; + len -= 3; + if (len < 1) + return; + + if (*data != P2P_OUI_TYPE) + return; + + p2p_rx_p2p_action(p2p, sa, data + 1, len - 1, freq); + break; + case WLAN_PA_GAS_INITIAL_REQ: + p2p_rx_gas_initial_req(p2p, sa, data + 1, len - 1, freq); + break; + case WLAN_PA_GAS_INITIAL_RESP: + p2p_rx_gas_initial_resp(p2p, sa, data + 1, len - 1); + break; + } +} + + +void p2p_rx_action(struct p2p_data *p2p, const u8 *da, const u8 *sa, + const u8 *bssid, u8 category, + const u8 *data, size_t len, int freq) +{ + if (category == WLAN_ACTION_PUBLIC) { + p2p_rx_action_public(p2p, da, sa, bssid, data, len, freq); + return; + } + + if (category != WLAN_ACTION_VENDOR_SPECIFIC) + return; + + if (len < 4) + return; + + if (WPA_GET_BE24(data) != OUI_WFA) + return; + data += 3; + len -= 3; + + if (*data != P2P_OUI_TYPE) + return; + data++; + len--; + + /* P2P action frame */ + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: RX P2P Action from " MACSTR, MAC2STR(sa)); + wpa_hexdump(MSG_MSGDUMP, "P2P: P2P Action contents", data, len); + + if (len < 1) + return; + switch (data[0]) { + case P2P_NOA: + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Received P2P Action - Notice of Absence"); + /* TODO */ + break; + case P2P_PRESENCE_REQ: + p2p_process_presence_req(p2p, da, sa, data + 1, len - 1, freq); + break; + case P2P_PRESENCE_RESP: + p2p_process_presence_resp(p2p, da, sa, data + 1, len - 1); + break; + case P2P_GO_DISC_REQ: + p2p_process_go_disc_req(p2p, da, sa, data + 1, len - 1, freq); + break; + default: + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Received P2P Action - unknown type %u", data[0]); + break; + } +} + + +static void p2p_go_neg_start(void *eloop_ctx, void *timeout_ctx) +{ + struct p2p_data *p2p = eloop_ctx; + if (p2p->go_neg_peer == NULL) + return; + p2p->cfg->stop_listen(p2p->cfg->cb_ctx); + p2p->go_neg_peer->status = P2P_SC_SUCCESS; + p2p_connect_send(p2p, p2p->go_neg_peer); +} + + +static void p2p_invite_start(void *eloop_ctx, void *timeout_ctx) +{ + struct p2p_data *p2p = eloop_ctx; + if (p2p->invite_peer == NULL) + return; + p2p->cfg->stop_listen(p2p->cfg->cb_ctx); + p2p_invite_send(p2p, p2p->invite_peer, p2p->invite_go_dev_addr); +} + + +static void p2p_add_dev_from_probe_req(struct p2p_data *p2p, const u8 *addr, + const u8 *ie, size_t ie_len) +{ + struct p2p_message msg; + struct p2p_device *dev; + + os_memset(&msg, 0, sizeof(msg)); + if (p2p_parse_ies(ie, ie_len, &msg) < 0 || msg.p2p_attributes == NULL) + { + p2p_parse_free(&msg); + return; /* not a P2P probe */ + } + + if (msg.ssid == NULL || msg.ssid[1] != P2P_WILDCARD_SSID_LEN || + os_memcmp(msg.ssid + 2, P2P_WILDCARD_SSID, P2P_WILDCARD_SSID_LEN) + != 0) { + /* The Probe Request is not part of P2P Device Discovery. It is + * not known whether the source address of the frame is the P2P + * Device Address or P2P Interface Address. Do not add a new + * peer entry based on this frames. + */ + p2p_parse_free(&msg); + return; + } + + dev = p2p_get_device(p2p, addr); + if (dev) { + if (dev->country[0] == 0 && msg.listen_channel) + os_memcpy(dev->country, msg.listen_channel, 3); + p2p_parse_free(&msg); + return; /* already known */ + } + + dev = p2p_create_device(p2p, addr); + if (dev == NULL) { + p2p_parse_free(&msg); + return; + } + + os_get_time(&dev->last_seen); + dev->flags |= P2P_DEV_PROBE_REQ_ONLY; + + if (msg.capability) { + dev->dev_capab = msg.capability[0]; + dev->group_capab = msg.capability[1]; + } + + if (msg.listen_channel) { + os_memcpy(dev->country, msg.listen_channel, 3); + dev->listen_freq = p2p_channel_to_freq(dev->country, + msg.listen_channel[3], + msg.listen_channel[4]); + } + + os_memcpy(dev->device_name, msg.device_name, sizeof(dev->device_name)); + + if (msg.wps_pri_dev_type) + os_memcpy(dev->pri_dev_type, msg.wps_pri_dev_type, + sizeof(dev->pri_dev_type)); + + p2p_parse_free(&msg); + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Created device entry based on Probe Req: " MACSTR + " dev_capab=0x%x group_capab=0x%x name='%s' listen_freq=%d", + MAC2STR(dev->p2p_device_addr), dev->dev_capab, + dev->group_capab, dev->device_name, dev->listen_freq); +} + + +struct p2p_device * p2p_add_dev_from_go_neg_req(struct p2p_data *p2p, + const u8 *addr, + struct p2p_message *msg) +{ + struct p2p_device *dev; + + dev = p2p_get_device(p2p, addr); + if (dev) { + os_get_time(&dev->last_seen); + return dev; /* already known */ + } + + dev = p2p_create_device(p2p, addr); + if (dev == NULL) + return NULL; + + p2p_add_dev_info(p2p, addr, dev, msg); + + return dev; +} + + +static int dev_type_match(const u8 *dev_type, const u8 *req_dev_type) +{ + if (os_memcmp(dev_type, req_dev_type, WPS_DEV_TYPE_LEN) == 0) + return 1; + if (os_memcmp(dev_type, req_dev_type, 2) == 0 && + WPA_GET_BE32(&req_dev_type[2]) == 0 && + WPA_GET_BE16(&req_dev_type[6]) == 0) + return 1; /* Category match with wildcard OUI/sub-category */ + return 0; +} + + +int dev_type_list_match(const u8 *dev_type, const u8 *req_dev_type[], + size_t num_req_dev_type) +{ + size_t i; + for (i = 0; i < num_req_dev_type; i++) { + if (dev_type_match(dev_type, req_dev_type[i])) + return 1; + } + return 0; +} + + +/** + * p2p_match_dev_type - Match local device type with requested type + * @p2p: P2P module context from p2p_init() + * @wps: WPS TLVs from Probe Request frame (concatenated WPS IEs) + * Returns: 1 on match, 0 on mismatch + * + * This function can be used to match the Requested Device Type attribute in + * WPS IE with the local device types for deciding whether to reply to a Probe + * Request frame. + */ +int p2p_match_dev_type(struct p2p_data *p2p, struct wpabuf *wps) +{ + struct wps_parse_attr attr; + size_t i; + + if (wps_parse_msg(wps, &attr)) + return 1; /* assume no Requested Device Type attributes */ + + if (attr.num_req_dev_type == 0) + return 1; /* no Requested Device Type attributes -> match */ + + if (dev_type_list_match(p2p->cfg->pri_dev_type, attr.req_dev_type, + attr.num_req_dev_type)) + return 1; /* Own Primary Device Type matches */ + + for (i = 0; i < p2p->cfg->num_sec_dev_types; i++) + if (dev_type_list_match(p2p->cfg->sec_dev_type[i], + attr.req_dev_type, + attr.num_req_dev_type)) + return 1; /* Own Secondary Device Type matches */ + + /* No matching device type found */ + return 0; +} + + +struct wpabuf * p2p_build_probe_resp_ies(struct p2p_data *p2p) +{ + struct wpabuf *buf; + u8 *len; + + buf = wpabuf_alloc(1000); + if (buf == NULL) + return NULL; + + /* TODO: add more info into WPS IE; maybe get from WPS module? */ + p2p_build_wps_ie(p2p, buf, DEV_PW_DEFAULT, 1); + + /* P2P IE */ + len = p2p_buf_add_ie_hdr(buf); + p2p_buf_add_capability(buf, p2p->dev_capab, 0); + if (p2p->ext_listen_interval) + p2p_buf_add_ext_listen_timing(buf, p2p->ext_listen_period, + p2p->ext_listen_interval); + p2p_buf_add_device_info(buf, p2p, NULL); + p2p_buf_update_ie_hdr(buf, len); + + return buf; +} + + +static void p2p_reply_probe(struct p2p_data *p2p, const u8 *addr, const u8 *ie, + size_t ie_len) +{ + struct ieee802_11_elems elems; + struct wpabuf *buf; + struct ieee80211_mgmt *resp; + struct wpabuf *wps; + struct wpabuf *ies; + + if (!p2p->in_listen || !p2p->drv_in_listen) { + /* not in Listen state - ignore Probe Request */ + return; + } + + if (ieee802_11_parse_elems((u8 *) ie, ie_len, &elems, 0) == + ParseFailed) { + /* Ignore invalid Probe Request frames */ + return; + } + + if (elems.p2p == NULL) { + /* not a P2P probe - ignore it */ + return; + } + + if (elems.ssid == NULL || elems.ssid_len != P2P_WILDCARD_SSID_LEN || + os_memcmp(elems.ssid, P2P_WILDCARD_SSID, P2P_WILDCARD_SSID_LEN) != + 0) { + /* not using P2P Wildcard SSID - ignore */ + return; + } + + /* Check Requested Device Type match */ + wps = ieee802_11_vendor_ie_concat(ie, ie_len, WPS_DEV_OUI_WFA); + if (wps && !p2p_match_dev_type(p2p, wps)) { + wpabuf_free(wps); + /* No match with Requested Device Type */ + return; + } + wpabuf_free(wps); + + if (!p2p->cfg->send_probe_resp) + return; /* Response generated elsewhere */ + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Reply to P2P Probe Request in Listen state"); + + /* + * We do not really have a specific BSS that this frame is advertising, + * so build a frame that has some information in valid format. This is + * really only used for discovery purposes, not to learn exact BSS + * parameters. + */ + ies = p2p_build_probe_resp_ies(p2p); + if (ies == NULL) + return; + + buf = wpabuf_alloc(200 + wpabuf_len(ies)); + if (buf == NULL) { + wpabuf_free(ies); + return; + } + + resp = NULL; + resp = wpabuf_put(buf, resp->u.probe_resp.variable - (u8 *) resp); + + resp->frame_control = host_to_le16((WLAN_FC_TYPE_MGMT << 2) | + (WLAN_FC_STYPE_PROBE_RESP << 4)); + os_memcpy(resp->da, addr, ETH_ALEN); + os_memcpy(resp->sa, p2p->cfg->dev_addr, ETH_ALEN); + os_memcpy(resp->bssid, p2p->cfg->dev_addr, ETH_ALEN); + resp->u.probe_resp.beacon_int = host_to_le16(100); + /* hardware or low-level driver will setup seq_ctrl and timestamp */ + resp->u.probe_resp.capab_info = + host_to_le16(WLAN_CAPABILITY_SHORT_PREAMBLE | + WLAN_CAPABILITY_PRIVACY | + WLAN_CAPABILITY_SHORT_SLOT_TIME); + + wpabuf_put_u8(buf, WLAN_EID_SSID); + wpabuf_put_u8(buf, P2P_WILDCARD_SSID_LEN); + wpabuf_put_data(buf, P2P_WILDCARD_SSID, P2P_WILDCARD_SSID_LEN); + + wpabuf_put_u8(buf, WLAN_EID_SUPP_RATES); + wpabuf_put_u8(buf, 8); + wpabuf_put_u8(buf, (60 / 5) | 0x80); + wpabuf_put_u8(buf, 90 / 5); + wpabuf_put_u8(buf, (120 / 5) | 0x80); + wpabuf_put_u8(buf, 180 / 5); + wpabuf_put_u8(buf, (240 / 5) | 0x80); + wpabuf_put_u8(buf, 360 / 5); + wpabuf_put_u8(buf, 480 / 5); + wpabuf_put_u8(buf, 540 / 5); + + wpabuf_put_u8(buf, WLAN_EID_DS_PARAMS); + wpabuf_put_u8(buf, 1); + wpabuf_put_u8(buf, p2p->cfg->channel); + + wpabuf_put_buf(buf, ies); + wpabuf_free(ies); + + p2p->cfg->send_probe_resp(p2p->cfg->cb_ctx, buf); + + wpabuf_free(buf); +} + + +int p2p_probe_req_rx(struct p2p_data *p2p, const u8 *addr, const u8 *ie, + size_t ie_len) +{ + p2p_add_dev_from_probe_req(p2p, addr, ie, ie_len); + + p2p_reply_probe(p2p, addr, ie, ie_len); + + if ((p2p->state == P2P_CONNECT || p2p->state == P2P_CONNECT_LISTEN) && + p2p->go_neg_peer && + os_memcmp(addr, p2p->go_neg_peer->p2p_device_addr, ETH_ALEN) == 0) + { + /* Received a Probe Request from GO Negotiation peer */ + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Found GO Negotiation peer - try to start GO " + "negotiation from timeout"); + eloop_register_timeout(0, 0, p2p_go_neg_start, p2p, NULL); + return 1; + } + + if ((p2p->state == P2P_INVITE || p2p->state == P2P_INVITE_LISTEN) && + p2p->invite_peer && + os_memcmp(addr, p2p->invite_peer->p2p_device_addr, ETH_ALEN) == 0) + { + /* Received a Probe Request from Invite peer */ + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Found Invite peer - try to start Invite from " + "timeout"); + eloop_register_timeout(0, 0, p2p_invite_start, p2p, NULL); + return 1; + } + + return 0; +} + + +static int p2p_assoc_req_ie_wlan_ap(struct p2p_data *p2p, const u8 *bssid, + u8 *buf, size_t len) +{ + struct wpabuf *tmp; + u8 *lpos; + size_t tmplen; + int res; + + if (!(p2p->dev_capab & P2P_DEV_CAPAB_INFRA_MANAGED)) + return 0; + + /* + * (Re)Association Request - P2P IE + * P2P Capability attribute (shall be present) + * P2P Interface attribute (present if concurrent device) + */ + tmp = wpabuf_alloc(200); + if (tmp == NULL) + return -1; + + lpos = p2p_buf_add_ie_hdr(tmp); + p2p_buf_add_capability(tmp, p2p->dev_capab, 0); + if (p2p->dev_capab & P2P_DEV_CAPAB_CONCURRENT_OPER) + p2p_buf_add_p2p_interface(tmp, p2p); + p2p_buf_update_ie_hdr(tmp, lpos); + + tmplen = wpabuf_len(tmp); + if (tmplen > len) + res = -1; + else { + os_memcpy(buf, wpabuf_head(tmp), tmplen); + res = tmplen; + } + wpabuf_free(tmp); + + return res; +} + + +int p2p_assoc_req_ie(struct p2p_data *p2p, const u8 *bssid, u8 *buf, + size_t len, int p2p_group) +{ + struct wpabuf *tmp; + u8 *lpos; + struct p2p_device *peer; + size_t tmplen; + int res; + + if (!p2p_group) + return p2p_assoc_req_ie_wlan_ap(p2p, bssid, buf, len); + + /* + * (Re)Association Request - P2P IE + * P2P Capability attribute (shall be present) + * Extended Listen Timing (may be present) + * P2P Device Info attribute (shall be present) + */ + tmp = wpabuf_alloc(200); + if (tmp == NULL) + return -1; + + peer = bssid ? p2p_get_device(p2p, bssid) : NULL; + + lpos = p2p_buf_add_ie_hdr(tmp); + p2p_buf_add_capability(tmp, p2p->dev_capab, 0); + p2p_buf_add_device_info(tmp, p2p, peer); + p2p_buf_update_ie_hdr(tmp, lpos); + + tmplen = wpabuf_len(tmp); + if (tmplen > len) + res = -1; + else { + os_memcpy(buf, wpabuf_head(tmp), tmplen); + res = tmplen; + } + wpabuf_free(tmp); + + return res; +} + + +int p2p_scan_result_text(const u8 *ies, size_t ies_len, char *buf, char *end) +{ + struct wpabuf *p2p_ie; + int ret; + + p2p_ie = ieee802_11_vendor_ie_concat(ies, ies_len, P2P_IE_VENDOR_TYPE); + if (p2p_ie == NULL) + return 0; + + ret = p2p_attr_text(p2p_ie, buf, end); + wpabuf_free(p2p_ie); + return ret; +} + + +static void p2p_clear_go_neg(struct p2p_data *p2p) +{ + p2p->go_neg_peer = NULL; + p2p_clear_timeout(p2p); + p2p_set_state(p2p, P2P_IDLE); +} + + +void p2p_wps_success_cb(struct p2p_data *p2p, const u8 *mac_addr) +{ + if (p2p->go_neg_peer == NULL) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: No pending Group Formation - " + "ignore WPS registration success notification"); + return; /* No pending Group Formation */ + } + + if (os_memcmp(mac_addr, p2p->go_neg_peer->intended_addr, ETH_ALEN) != + 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Ignore WPS registration success notification " + "for " MACSTR " (GO Negotiation peer " MACSTR ")", + MAC2STR(mac_addr), + MAC2STR(p2p->go_neg_peer->intended_addr)); + return; /* Ignore unexpected peer address */ + } + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Group Formation completed successfully with " MACSTR, + MAC2STR(mac_addr)); + + p2p_clear_go_neg(p2p); +} + + +void p2p_group_formation_failed(struct p2p_data *p2p) +{ + if (p2p->go_neg_peer == NULL) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: No pending Group Formation - " + "ignore group formation failure notification"); + return; /* No pending Group Formation */ + } + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Group Formation failed with " MACSTR, + MAC2STR(p2p->go_neg_peer->intended_addr)); + + p2p_clear_go_neg(p2p); +} + + +struct p2p_data * p2p_init(const struct p2p_config *cfg) +{ + struct p2p_data *p2p; + + if (cfg->max_peers < 1) + return NULL; + + p2p = os_zalloc(sizeof(*p2p) + sizeof(*cfg)); + if (p2p == NULL) + return NULL; + p2p->cfg = (struct p2p_config *) (p2p + 1); + os_memcpy(p2p->cfg, cfg, sizeof(*cfg)); + if (cfg->dev_name) + p2p->cfg->dev_name = os_strdup(cfg->dev_name); + + p2p->min_disc_int = 1; + p2p->max_disc_int = 3; + + os_get_random(&p2p->next_tie_breaker, 1); + p2p->next_tie_breaker &= 0x01; + if (cfg->sd_request) + p2p->dev_capab |= P2P_DEV_CAPAB_SERVICE_DISCOVERY; + p2p->dev_capab |= P2P_DEV_CAPAB_INVITATION_PROCEDURE; + if (cfg->concurrent_operations) + p2p->dev_capab |= P2P_DEV_CAPAB_CONCURRENT_OPER; + p2p->dev_capab |= P2P_DEV_CAPAB_CLIENT_DISCOVERABILITY; + + dl_list_init(&p2p->devices); + + eloop_register_timeout(P2P_PEER_EXPIRATION_INTERVAL, 0, + p2p_expiration_timeout, p2p, NULL); + + return p2p; +} + + +void p2p_deinit(struct p2p_data *p2p) +{ + eloop_cancel_timeout(p2p_expiration_timeout, p2p, NULL); + eloop_cancel_timeout(p2p_ext_listen_timeout, p2p, NULL); + eloop_cancel_timeout(p2p_scan_timeout, p2p, NULL); + p2p_flush(p2p); + os_free(p2p->cfg->dev_name); + os_free(p2p->groups); + os_free(p2p); +} + + +void p2p_flush(struct p2p_data *p2p) +{ + struct p2p_device *dev, *prev; + p2p_clear_timeout(p2p); + p2p_set_state(p2p, P2P_IDLE); + p2p->start_after_scan = P2P_AFTER_SCAN_NOTHING; + p2p->go_neg_peer = NULL; + eloop_cancel_timeout(p2p_find_timeout, p2p, NULL); + dl_list_for_each_safe(dev, prev, &p2p->devices, struct p2p_device, + list) { + dl_list_del(&dev->list); + p2p_device_free(p2p, dev); + } + p2p_free_sd_queries(p2p); +} + + +int p2p_set_dev_name(struct p2p_data *p2p, const char *dev_name) +{ + os_free(p2p->cfg->dev_name); + if (dev_name) { + p2p->cfg->dev_name = os_strdup(dev_name); + if (p2p->cfg->dev_name == NULL) + return -1; + } else + p2p->cfg->dev_name = NULL; + return 0; +} + + +int p2p_set_pri_dev_type(struct p2p_data *p2p, const u8 *pri_dev_type) +{ + os_memcpy(p2p->cfg->pri_dev_type, pri_dev_type, 8); + return 0; +} + + +int p2p_set_sec_dev_types(struct p2p_data *p2p, const u8 dev_types[][8], + size_t num_dev_types) +{ + if (num_dev_types > P2P_SEC_DEVICE_TYPES) + num_dev_types = P2P_SEC_DEVICE_TYPES; + p2p->cfg->num_sec_dev_types = num_dev_types; + os_memcpy(p2p->cfg->sec_dev_type, dev_types, num_dev_types * 8); + return 0; +} + + +int p2p_set_country(struct p2p_data *p2p, const char *country) +{ + os_memcpy(p2p->cfg->country, country, 3); + return 0; +} + + +void p2p_continue_find(struct p2p_data *p2p) +{ + struct p2p_device *dev; + p2p_set_state(p2p, P2P_SEARCH); + dl_list_for_each(dev, &p2p->devices, struct p2p_device, list) { + if (dev->flags & P2P_DEV_SD_SCHEDULE) { + if (p2p_start_sd(p2p, dev) == 0) + return; + else + break; + } else if (dev->req_config_methods) { + if (p2p_send_prov_disc_req(p2p, dev, 0) == 0) + return; + } + } + + p2p_listen_in_find(p2p); +} + + +static void p2p_sd_cb(struct p2p_data *p2p, int success) +{ + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Service Discovery Query TX callback: success=%d", + success); + p2p->pending_action_state = P2P_NO_PENDING_ACTION; + + if (!success) { + if (p2p->sd_peer) { + p2p->sd_peer->flags &= ~P2P_DEV_SD_SCHEDULE; + p2p->sd_peer = NULL; + } + p2p_continue_find(p2p); + return; + } + + if (p2p->sd_peer == NULL) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: No SD peer entry known"); + p2p_continue_find(p2p); + return; + } + + /* Wait for response from the peer */ + p2p_set_state(p2p, P2P_SD_DURING_FIND); + p2p_set_timeout(p2p, 0, 200000); +} + + +static void p2p_prov_disc_cb(struct p2p_data *p2p, int success) +{ + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Provision Discovery Request TX callback: success=%d", + success); + p2p->pending_action_state = P2P_NO_PENDING_ACTION; + + if (!success) { + if (p2p->state != P2P_IDLE) + p2p_continue_find(p2p); + return; + } + + /* Wait for response from the peer */ + if (p2p->state == P2P_SEARCH) + p2p_set_state(p2p, P2P_PD_DURING_FIND); + p2p_set_timeout(p2p, 0, 200000); +} + + +int p2p_scan_res_handler(struct p2p_data *p2p, const u8 *bssid, int freq, + int level, const u8 *ies, size_t ies_len) +{ + p2p_add_device(p2p, bssid, freq, level, ies, ies_len); + + if (p2p->go_neg_peer && p2p->state == P2P_SEARCH && + os_memcmp(p2p->go_neg_peer->p2p_device_addr, bssid, ETH_ALEN) == 0) + { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Found GO Negotiation peer - try to start GO " + "negotiation"); + p2p_connect_send(p2p, p2p->go_neg_peer); + return 1; + } + + return 0; +} + + +void p2p_scan_res_handled(struct p2p_data *p2p) +{ + if (!p2p->p2p_scan_running) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: p2p_scan was not " + "running, but scan results received"); + } + p2p->p2p_scan_running = 0; + eloop_cancel_timeout(p2p_scan_timeout, p2p, NULL); + + if (p2p_run_after_scan(p2p)) + return; + if (p2p->state == P2P_SEARCH) + p2p_continue_find(p2p); +} + + +void p2p_scan_ie(struct p2p_data *p2p, struct wpabuf *ies) +{ + u8 *len = p2p_buf_add_ie_hdr(ies); + p2p_buf_add_capability(ies, p2p->dev_capab, 0); + if (p2p->cfg->reg_class && p2p->cfg->channel) + p2p_buf_add_listen_channel(ies, p2p->cfg->country, + p2p->cfg->reg_class, + p2p->cfg->channel); + if (p2p->ext_listen_interval) + p2p_buf_add_ext_listen_timing(ies, p2p->ext_listen_period, + p2p->ext_listen_interval); + /* TODO: p2p_buf_add_operating_channel() if GO */ + p2p_buf_update_ie_hdr(ies, len); +} + + +int p2p_ie_text(struct wpabuf *p2p_ie, char *buf, char *end) +{ + return p2p_attr_text(p2p_ie, buf, end); +} + + +static void p2p_go_neg_req_cb(struct p2p_data *p2p, int success) +{ + struct p2p_device *dev = p2p->go_neg_peer; + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: GO Negotiation Request TX callback: success=%d", + success); + + if (dev == NULL) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: No pending GO Negotiation"); + return; + } + + if (success) { + dev->go_neg_req_sent++; + if (dev->flags & P2P_DEV_USER_REJECTED) { + p2p_set_state(p2p, P2P_IDLE); + return; + } + } + + if (!success && + (dev->dev_capab & P2P_DEV_CAPAB_CLIENT_DISCOVERABILITY) && + !is_zero_ether_addr(dev->member_in_go_dev)) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Peer " MACSTR " did not acknowledge request - " + "try to use device discoverability through its GO", + MAC2STR(dev->p2p_device_addr)); + p2p->cfg->send_action_done(p2p->cfg->cb_ctx); + p2p_send_dev_disc_req(p2p, dev); + return; + } + + /* + * Use P2P find, if needed, to find the other device from its listen + * channel. + */ + p2p_set_state(p2p, P2P_CONNECT); + p2p_set_timeout(p2p, 0, 100000); +} + + +static void p2p_go_neg_resp_cb(struct p2p_data *p2p, int success) +{ + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: GO Negotiation Response TX callback: success=%d", + success); + if (!p2p->go_neg_peer && p2p->state == P2P_PROVISIONING) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Ignore TX callback event - GO Negotiation is " + "not running anymore"); + return; + } + p2p_set_state(p2p, P2P_CONNECT); + p2p_set_timeout(p2p, 0, 100000); +} + + +static void p2p_go_neg_resp_failure_cb(struct p2p_data *p2p, int success) +{ + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: GO Negotiation Response (failure) TX callback: " + "success=%d", success); +} + + +static void p2p_go_neg_conf_cb(struct p2p_data *p2p, int success) +{ + struct p2p_device *dev; + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: GO Negotiation Confirm TX callback: success=%d", + success); + p2p->cfg->send_action_done(p2p->cfg->cb_ctx); + if (!success) { + /* + * It looks like the TX status for GO Negotiation Confirm is + * often showing failure even when the peer has actually + * received the frame. Since the peer may change channels + * immediately after having received the frame, we may not see + * an Ack for retries, so just dropping a single frame may + * trigger this. To allow the group formation to succeed if the + * peer did indeed receive the frame, continue regardless of + * the TX status. + */ + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Assume GO Negotiation Confirm TX was actually " + "received by the peer even though Ack was not " + "reported"); + } + + dev = p2p->go_neg_peer; + if (dev == NULL) + return; + + p2p_go_complete(p2p, dev); +} + + +void p2p_send_action_cb(struct p2p_data *p2p, unsigned int freq, const u8 *dst, + const u8 *src, const u8 *bssid, int success) +{ + enum p2p_pending_action_state state; + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Action frame TX callback (state=%d freq=%u dst=" MACSTR + " src=" MACSTR " bssid=" MACSTR " success=%d", + p2p->pending_action_state, freq, MAC2STR(dst), MAC2STR(src), + MAC2STR(bssid), success); + state = p2p->pending_action_state; + p2p->pending_action_state = P2P_NO_PENDING_ACTION; + switch (state) { + case P2P_NO_PENDING_ACTION: + break; + case P2P_PENDING_GO_NEG_REQUEST: + p2p_go_neg_req_cb(p2p, success); + break; + case P2P_PENDING_GO_NEG_RESPONSE: + p2p_go_neg_resp_cb(p2p, success); + break; + case P2P_PENDING_GO_NEG_RESPONSE_FAILURE: + p2p_go_neg_resp_failure_cb(p2p, success); + break; + case P2P_PENDING_GO_NEG_CONFIRM: + p2p_go_neg_conf_cb(p2p, success); + break; + case P2P_PENDING_SD: + p2p_sd_cb(p2p, success); + break; + case P2P_PENDING_PD: + p2p_prov_disc_cb(p2p, success); + break; + case P2P_PENDING_INVITATION_REQUEST: + p2p_invitation_req_cb(p2p, success); + break; + case P2P_PENDING_INVITATION_RESPONSE: + p2p_invitation_resp_cb(p2p, success); + break; + case P2P_PENDING_DEV_DISC_REQUEST: + p2p_dev_disc_req_cb(p2p, success); + break; + case P2P_PENDING_DEV_DISC_RESPONSE: + p2p_dev_disc_resp_cb(p2p, success); + break; + case P2P_PENDING_GO_DISC_REQ: + p2p_go_disc_req_cb(p2p, success); + break; + } +} + + +void p2p_listen_cb(struct p2p_data *p2p, unsigned int freq, + unsigned int duration) +{ + if (freq == p2p->pending_client_disc_freq) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Client discoverability remain-awake completed"); + p2p->pending_client_disc_freq = 0; + return; + } + + if (freq != p2p->pending_listen_freq) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Unexpected listen callback for freq=%u " + "duration=%u (pending_listen_freq=%u)", + freq, duration, p2p->pending_listen_freq); + return; + } + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Starting Listen timeout(%u,%u) on freq=%u based on " + "callback", + p2p->pending_listen_sec, p2p->pending_listen_usec, + p2p->pending_listen_freq); + p2p->in_listen = 1; + p2p->drv_in_listen = 1; + if (p2p->pending_listen_sec || p2p->pending_listen_usec) { + /* + * Add 20 msec extra wait to avoid race condition with driver + * remain-on-channel end event, i.e., give driver more time to + * complete the operation before our timeout expires. + */ + p2p_set_timeout(p2p, p2p->pending_listen_sec, + p2p->pending_listen_usec + 20000); + } + + p2p->pending_listen_freq = 0; +} + + +int p2p_listen_end(struct p2p_data *p2p, unsigned int freq) +{ + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Driver ended Listen " + "state (freq=%u)", freq); + p2p->drv_in_listen = 0; + if (p2p->in_listen) + return 0; /* Internal timeout will trigger the next step */ + + if (p2p->state == P2P_CONNECT_LISTEN && p2p->go_neg_peer) { + p2p_set_state(p2p, P2P_CONNECT); + p2p_connect_send(p2p, p2p->go_neg_peer); + return 1; + } else if (p2p->state == P2P_SEARCH) { + p2p_search(p2p); + return 1; + } + + return 0; +} + + +static void p2p_timeout_connect(struct p2p_data *p2p) +{ + p2p->cfg->send_action_done(p2p->cfg->cb_ctx); + p2p_set_state(p2p, P2P_CONNECT_LISTEN); + p2p_listen_in_find(p2p); +} + + +static void p2p_timeout_connect_listen(struct p2p_data *p2p) +{ + if (p2p->go_neg_peer) { + if (p2p->drv_in_listen) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Driver is " + "still in Listen state; wait for it to " + "complete"); + return; + } + p2p_set_state(p2p, P2P_CONNECT); + p2p_connect_send(p2p, p2p->go_neg_peer); + } else + p2p_set_state(p2p, P2P_IDLE); +} + + +static void p2p_timeout_wait_peer_connect(struct p2p_data *p2p) +{ + /* + * TODO: could remain constantly in Listen state for some time if there + * are no other concurrent uses for the radio. For now, go to listen + * state once per second to give other uses a chance to use the radio. + */ + p2p_set_state(p2p, P2P_WAIT_PEER_IDLE); + p2p_set_timeout(p2p, 1, 0); +} + + +static void p2p_timeout_wait_peer_idle(struct p2p_data *p2p) +{ + struct p2p_device *dev = p2p->go_neg_peer; + + if (dev == NULL) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Unknown GO Neg peer - stop GO Neg wait"); + return; + } + + dev->wait_count++; + if (dev->wait_count >= 120) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Timeout on waiting peer to become ready for GO " + "Negotiation"); + p2p_go_neg_failed(p2p, dev, -1); + return; + } + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Go to Listen state while waiting for the peer to become " + "ready for GO Negotiation"); + p2p_set_state(p2p, P2P_WAIT_PEER_CONNECT); + p2p_listen_in_find(p2p); +} + + +static void p2p_timeout_sd_during_find(struct p2p_data *p2p) +{ + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Service Discovery Query timeout"); + if (p2p->sd_peer) { + p2p->cfg->send_action_done(p2p->cfg->cb_ctx); + p2p->sd_peer->flags &= ~P2P_DEV_SD_SCHEDULE; + p2p->sd_peer = NULL; + } + p2p_continue_find(p2p); +} + + +static void p2p_timeout_prov_disc_during_find(struct p2p_data *p2p) +{ + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Provision Discovery Request timeout"); + p2p->cfg->send_action_done(p2p->cfg->cb_ctx); + p2p_continue_find(p2p); +} + + +static void p2p_timeout_invite(struct p2p_data *p2p) +{ + p2p->cfg->send_action_done(p2p->cfg->cb_ctx); + p2p_set_state(p2p, P2P_INVITE_LISTEN); + p2p_listen_in_find(p2p); +} + + +static void p2p_timeout_invite_listen(struct p2p_data *p2p) +{ + if (p2p->invite_peer && p2p->invite_peer->invitation_reqs < 100) { + p2p_set_state(p2p, P2P_INVITE); + p2p_invite_send(p2p, p2p->invite_peer, + p2p->invite_go_dev_addr); + } else { + if (p2p->invite_peer) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Invitation Request retry limit reached"); + if (p2p->cfg->invitation_result) + p2p->cfg->invitation_result( + p2p->cfg->cb_ctx, -1, NULL); + } + p2p_set_state(p2p, P2P_IDLE); + } +} + + +static void p2p_state_timeout(void *eloop_ctx, void *timeout_ctx) +{ + struct p2p_data *p2p = eloop_ctx; + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Timeout (state=%s)", + p2p_state_txt(p2p->state)); + + p2p->in_listen = 0; + + switch (p2p->state) { + case P2P_IDLE: + break; + case P2P_SEARCH: + p2p_search(p2p); + break; + case P2P_CONNECT: + p2p_timeout_connect(p2p); + break; + case P2P_CONNECT_LISTEN: + p2p_timeout_connect_listen(p2p); + break; + case P2P_GO_NEG: + break; + case P2P_LISTEN_ONLY: + if (p2p->ext_listen_only) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Extended Listen Timing - Listen State " + "completed"); + p2p->ext_listen_only = 0; + p2p_set_state(p2p, P2P_IDLE); + } + break; + case P2P_WAIT_PEER_CONNECT: + p2p_timeout_wait_peer_connect(p2p); + break; + case P2P_WAIT_PEER_IDLE: + p2p_timeout_wait_peer_idle(p2p); + break; + case P2P_SD_DURING_FIND: + p2p_timeout_sd_during_find(p2p); + break; + case P2P_PROVISIONING: + break; + case P2P_PD_DURING_FIND: + p2p_timeout_prov_disc_during_find(p2p); + break; + case P2P_INVITE: + p2p_timeout_invite(p2p); + break; + case P2P_INVITE_LISTEN: + p2p_timeout_invite_listen(p2p); + break; + } +} + + +int p2p_reject(struct p2p_data *p2p, const u8 *peer_addr) +{ + struct p2p_device *dev; + + dev = p2p_get_device(p2p, peer_addr); + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Local request to reject " + "connection attempts by peer " MACSTR, MAC2STR(peer_addr)); + if (dev == NULL) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Peer " MACSTR + " unknown", MAC2STR(peer_addr)); + return -1; + } + dev->status = P2P_SC_FAIL_REJECTED_BY_USER; + dev->flags |= P2P_DEV_USER_REJECTED; + return 0; +} + + +static const char * p2p_wps_method_text(enum p2p_wps_method method) +{ + switch (method) { + case WPS_NOT_READY: + return "not-ready"; + case WPS_PIN_LABEL: + return "Label"; + case WPS_PIN_DISPLAY: + return "Display"; + case WPS_PIN_KEYPAD: + return "Keypad"; + case WPS_PBC: + return "PBC"; + } + + return "??"; +} + + +static const char * p2p_go_state_text(enum p2p_go_state go_state) +{ + switch (go_state) { + case UNKNOWN_GO: + return "unknown"; + case LOCAL_GO: + return "local"; + case REMOTE_GO: + return "remote"; + } + + return "??"; +} + + +int p2p_get_peer_info(struct p2p_data *p2p, const u8 *addr, int next, + char *buf, size_t buflen) +{ + struct p2p_device *dev; + int res; + char *pos, *end; + struct os_time now; + char devtype[WPS_DEV_TYPE_BUFSIZE]; + + if (addr) + dev = p2p_get_device(p2p, addr); + else + dev = dl_list_first(&p2p->devices, struct p2p_device, list); + + if (dev && next) { + dev = dl_list_first(&dev->list, struct p2p_device, list); + if (&dev->list == &p2p->devices) + dev = NULL; + } + + if (dev == NULL) + return -1; + + pos = buf; + end = buf + buflen; + + res = os_snprintf(pos, end - pos, MACSTR "\n", + MAC2STR(dev->p2p_device_addr)); + if (res < 0 || res >= end - pos) + return pos - buf; + pos += res; + + os_get_time(&now); + res = os_snprintf(pos, end - pos, + "age=%d\n" + "listen_freq=%d\n" + "level=%d\n" + "wps_method=%s\n" + "interface_addr=" MACSTR "\n" + "member_in_go_dev=" MACSTR "\n" + "member_in_go_iface=" MACSTR "\n" + "pri_dev_type=%s\n" + "device_name=%s\n" + "config_methods=0x%x\n" + "dev_capab=0x%x\n" + "group_capab=0x%x\n" + "go_neg_req_sent=%d\n" + "go_state=%s\n" + "dialog_token=%u\n" + "intended_addr=" MACSTR "\n" + "country=%c%c\n" + "oper_freq=%d\n" + "req_config_methods=0x%x\n" + "flags=%s%s%s%s%s%s%s%s%s%s%s%s%s\n" + "status=%d\n" + "wait_count=%u\n" + "invitation_reqs=%u\n", + (int) (now.sec - dev->last_seen.sec), + dev->listen_freq, + dev->level, + p2p_wps_method_text(dev->wps_method), + MAC2STR(dev->interface_addr), + MAC2STR(dev->member_in_go_dev), + MAC2STR(dev->member_in_go_iface), + wps_dev_type_bin2str(dev->pri_dev_type, + devtype, sizeof(devtype)), + dev->device_name, + dev->config_methods, + dev->dev_capab, + dev->group_capab, + dev->go_neg_req_sent, + p2p_go_state_text(dev->go_state), + dev->dialog_token, + MAC2STR(dev->intended_addr), + dev->country[0] ? dev->country[0] : '_', + dev->country[1] ? dev->country[1] : '_', + dev->oper_freq, + dev->req_config_methods, + dev->flags & P2P_DEV_PROBE_REQ_ONLY ? + "[PROBE_REQ_ONLY]" : "", + dev->flags & P2P_DEV_REPORTED ? "[REPORTED]" : "", + dev->flags & P2P_DEV_NOT_YET_READY ? + "[NOT_YET_READY]" : "", + dev->flags & P2P_DEV_SD_INFO ? "[SD_INFO]" : "", + dev->flags & P2P_DEV_SD_SCHEDULE ? "[SD_SCHEDULE]" : + "", + dev->flags & P2P_DEV_PD_PEER_DISPLAY ? + "[PD_PEER_DISPLAY]" : "", + dev->flags & P2P_DEV_PD_PEER_KEYPAD ? + "[PD_PEER_KEYPAD]" : "", + dev->flags & P2P_DEV_USER_REJECTED ? + "[USER_REJECTED]" : "", + dev->flags & P2P_DEV_PEER_WAITING_RESPONSE ? + "[PEER_WAITING_RESPONSE]" : "", + dev->flags & P2P_DEV_PREFER_PERSISTENT_GROUP ? + "[PREFER_PERSISTENT_GROUP]" : "", + dev->flags & P2P_DEV_WAIT_GO_NEG_RESPONSE ? + "[WAIT_GO_NEG_RESPONSE]" : "", + dev->flags & P2P_DEV_WAIT_GO_NEG_CONFIRM ? + "[WAIT_GO_NEG_CONFIRM]" : "", + dev->flags & P2P_DEV_GROUP_CLIENT_ONLY ? + "[GROUP_CLIENT_ONLY]" : "", + dev->status, + dev->wait_count, + dev->invitation_reqs); + if (res < 0 || res >= end - pos) + return pos - buf; + pos += res; + + if (dev->ext_listen_period) { + res = os_snprintf(pos, end - pos, + "ext_listen_period=%u\n" + "ext_listen_interval=%u\n", + dev->ext_listen_period, + dev->ext_listen_interval); + if (res < 0 || res >= end - pos) + return pos - buf; + pos += res; + } + + if (dev->oper_ssid_len) { + res = os_snprintf(pos, end - pos, + "oper_ssid=%s\n", + wpa_ssid_txt(dev->oper_ssid, + dev->oper_ssid_len)); + if (res < 0 || res >= end - pos) + return pos - buf; + pos += res; + } + + return pos - buf; +} + + +void p2p_set_client_discoverability(struct p2p_data *p2p, int enabled) +{ + if (enabled) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Client " + "discoverability enabled"); + p2p->dev_capab |= P2P_DEV_CAPAB_CLIENT_DISCOVERABILITY; + } else { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Client " + "discoverability disabled"); + p2p->dev_capab &= ~P2P_DEV_CAPAB_CLIENT_DISCOVERABILITY; + } +} + + +static struct wpabuf * p2p_build_presence_req(u32 duration1, u32 interval1, + u32 duration2, u32 interval2) +{ + struct wpabuf *req; + struct p2p_noa_desc desc1, desc2, *ptr1 = NULL, *ptr2 = NULL; + u8 *len; + + req = wpabuf_alloc(100); + if (req == NULL) + return NULL; + + if (duration1 || interval1) { + os_memset(&desc1, 0, sizeof(desc1)); + desc1.count_type = 1; + desc1.duration = duration1; + desc1.interval = interval1; + ptr1 = &desc1; + + if (duration2 || interval2) { + os_memset(&desc2, 0, sizeof(desc2)); + desc2.count_type = 2; + desc2.duration = duration2; + desc2.interval = interval2; + ptr2 = &desc2; + } + } + + p2p_buf_add_action_hdr(req, P2P_PRESENCE_REQ, 1); + len = p2p_buf_add_ie_hdr(req); + p2p_buf_add_noa(req, 0, 0, 0, ptr1, ptr2); + p2p_buf_update_ie_hdr(req, len); + + return req; +} + + +int p2p_presence_req(struct p2p_data *p2p, const u8 *go_interface_addr, + const u8 *own_interface_addr, unsigned int freq, + u32 duration1, u32 interval1, u32 duration2, + u32 interval2) +{ + struct wpabuf *req; + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Send Presence Request to " + "GO " MACSTR " (own interface " MACSTR ") freq=%u dur1=%u " + "int1=%u dur2=%u int2=%u", + MAC2STR(go_interface_addr), MAC2STR(own_interface_addr), + freq, duration1, interval1, duration2, interval2); + + req = p2p_build_presence_req(duration1, interval1, duration2, + interval2); + if (req == NULL) + return -1; + + p2p->pending_action_state = P2P_NO_PENDING_ACTION; + if (p2p->cfg->send_action(p2p->cfg->cb_ctx, freq, go_interface_addr, + own_interface_addr, + go_interface_addr, + wpabuf_head(req), wpabuf_len(req), 200) < 0) + { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Failed to send Action frame"); + } + wpabuf_free(req); + + return 0; +} + + +static struct wpabuf * p2p_build_presence_resp(u8 status, const u8 *noa, + size_t noa_len, u8 dialog_token) +{ + struct wpabuf *resp; + u8 *len; + + resp = wpabuf_alloc(100 + noa_len); + if (resp == NULL) + return NULL; + + p2p_buf_add_action_hdr(resp, P2P_PRESENCE_RESP, dialog_token); + len = p2p_buf_add_ie_hdr(resp); + p2p_buf_add_status(resp, status); + if (noa) { + wpabuf_put_u8(resp, P2P_ATTR_NOTICE_OF_ABSENCE); + wpabuf_put_le16(resp, noa_len); + wpabuf_put_data(resp, noa, noa_len); + } else + p2p_buf_add_noa(resp, 0, 0, 0, NULL, NULL); + p2p_buf_update_ie_hdr(resp, len); + + return resp; +} + + +static void p2p_process_presence_req(struct p2p_data *p2p, const u8 *da, + const u8 *sa, const u8 *data, size_t len, + int rx_freq) +{ + struct p2p_message msg; + u8 status; + struct wpabuf *resp; + size_t g; + struct p2p_group *group = NULL; + int parsed = 0; + u8 noa[50]; + int noa_len; + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Received P2P Action - P2P Presence Request"); + + for (g = 0; g < p2p->num_groups; g++) { + if (os_memcmp(da, p2p_group_get_interface_addr(p2p->groups[g]), + ETH_ALEN) == 0) { + group = p2p->groups[g]; + break; + } + } + if (group == NULL) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Ignore P2P Presence Request for unknown group " + MACSTR, MAC2STR(da)); + return; + } + + if (p2p_parse(data, len, &msg) < 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Failed to parse P2P Presence Request"); + status = P2P_SC_FAIL_INVALID_PARAMS; + goto fail; + } + parsed = 1; + + if (msg.noa == NULL) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: No NoA attribute in P2P Presence Request"); + status = P2P_SC_FAIL_INVALID_PARAMS; + goto fail; + } + + status = p2p_group_presence_req(group, sa, msg.noa, msg.noa_len); + +fail: + if (p2p->cfg->get_noa) + noa_len = p2p->cfg->get_noa(p2p->cfg->cb_ctx, da, noa, + sizeof(noa)); + else + noa_len = -1; + resp = p2p_build_presence_resp(status, noa_len > 0 ? noa : NULL, + noa_len > 0 ? noa_len : 0, + msg.dialog_token); + if (parsed) + p2p_parse_free(&msg); + if (resp == NULL) + return; + + p2p->pending_action_state = P2P_NO_PENDING_ACTION; + if (p2p->cfg->send_action(p2p->cfg->cb_ctx, rx_freq, sa, da, da, + wpabuf_head(resp), wpabuf_len(resp), 200) < + 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Failed to send Action frame"); + } + wpabuf_free(resp); +} + + +static void p2p_process_presence_resp(struct p2p_data *p2p, const u8 *da, + const u8 *sa, const u8 *data, size_t len) +{ + struct p2p_message msg; + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Received P2P Action - P2P Presence Response"); + + if (p2p_parse(data, len, &msg) < 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Failed to parse P2P Presence Response"); + return; + } + + if (msg.status == NULL || msg.noa == NULL) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: No Status or NoA attribute in P2P Presence " + "Response"); + p2p_parse_free(&msg); + return; + } + + if (*msg.status) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: P2P Presence Request was rejected: status %u", + *msg.status); + p2p_parse_free(&msg); + return; + } + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: P2P Presence Request was accepted"); + wpa_hexdump(MSG_DEBUG, "P2P: P2P Presence Response - NoA", + msg.noa, msg.noa_len); + /* TODO: process NoA */ + p2p_parse_free(&msg); +} + + +static void p2p_ext_listen_timeout(void *eloop_ctx, void *timeout_ctx) +{ + struct p2p_data *p2p = eloop_ctx; + + if (p2p->ext_listen_interval) { + /* Schedule next extended listen timeout */ + eloop_register_timeout(p2p->ext_listen_interval_sec, + p2p->ext_listen_interval_usec, + p2p_ext_listen_timeout, p2p, NULL); + } + + if (p2p->state != P2P_IDLE) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Skip Extended " + "Listen timeout in active state (%s)", + p2p_state_txt(p2p->state)); + return; + } + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Extended Listen timeout"); + p2p->ext_listen_only = 1; + if (p2p_listen(p2p, p2p->ext_listen_period) < 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Failed to start " + "Listen state for Extended Listen Timing"); + p2p->ext_listen_only = 0; + } +} + + +int p2p_ext_listen(struct p2p_data *p2p, unsigned int period, + unsigned int interval) +{ + if (period > 65535 || interval > 65535 || period > interval || + (period == 0 && interval > 0) || (period > 0 && interval == 0)) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Invalid Extended Listen Timing request: " + "period=%u interval=%u", period, interval); + return -1; + } + + eloop_cancel_timeout(p2p_ext_listen_timeout, p2p, NULL); + + if (interval == 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Disabling Extended Listen Timing"); + p2p->ext_listen_period = 0; + p2p->ext_listen_interval = 0; + return 0; + } + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Enabling Extended Listen Timing: period %u msec, " + "interval %u msec", period, interval); + p2p->ext_listen_period = period; + p2p->ext_listen_interval = interval; + p2p->ext_listen_interval_sec = interval / 1000; + p2p->ext_listen_interval_usec = (interval % 1000) * 1000; + + eloop_register_timeout(p2p->ext_listen_interval_sec, + p2p->ext_listen_interval_usec, + p2p_ext_listen_timeout, p2p, NULL); + + return 0; +} + + +void p2p_deauth_notif(struct p2p_data *p2p, const u8 *bssid, u16 reason_code, + const u8 *ie, size_t ie_len) +{ + struct p2p_message msg; + + if (bssid == NULL || ie == NULL) + return; + + os_memset(&msg, 0, sizeof(msg)); + if (p2p_parse_ies(ie, ie_len, &msg)) + return; + if (msg.minor_reason_code == NULL) + return; + + wpa_msg(p2p->cfg->msg_ctx, MSG_INFO, + "P2P: Deauthentication notification BSSID " MACSTR + " reason_code=%u minor_reason_code=%u", + MAC2STR(bssid), reason_code, *msg.minor_reason_code); + + p2p_parse_free(&msg); +} + + +void p2p_disassoc_notif(struct p2p_data *p2p, const u8 *bssid, u16 reason_code, + const u8 *ie, size_t ie_len) +{ + struct p2p_message msg; + + if (bssid == NULL || ie == NULL) + return; + + os_memset(&msg, 0, sizeof(msg)); + if (p2p_parse_ies(ie, ie_len, &msg)) + return; + if (msg.minor_reason_code == NULL) + return; + + wpa_msg(p2p->cfg->msg_ctx, MSG_INFO, + "P2P: Disassociation notification BSSID " MACSTR + " reason_code=%u minor_reason_code=%u", + MAC2STR(bssid), reason_code, *msg.minor_reason_code); + + p2p_parse_free(&msg); +} + + +void p2p_set_managed_oper(struct p2p_data *p2p, int enabled) +{ + if (enabled) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Managed P2P " + "Device operations enabled"); + p2p->dev_capab |= P2P_DEV_CAPAB_INFRA_MANAGED; + } else { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Managed P2P " + "Device operations disabled"); + p2p->dev_capab &= ~P2P_DEV_CAPAB_INFRA_MANAGED; + } +} + + +int p2p_set_listen_channel(struct p2p_data *p2p, u8 reg_class, u8 channel) +{ + if (p2p_channel_to_freq(p2p->cfg->country, reg_class, channel) < 0) + return -1; + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Set Listen channel: " + "reg_class %u channel %u", reg_class, channel); + p2p->cfg->reg_class = reg_class; + p2p->cfg->channel = channel; + + return 0; +} + + +int p2p_set_ssid_postfix(struct p2p_data *p2p, const u8 *postfix, size_t len) +{ + wpa_hexdump_ascii(MSG_DEBUG, "P2P: New SSID postfix", postfix, len); + if (postfix == NULL) { + p2p->cfg->ssid_postfix_len = 0; + return 0; + } + if (len > sizeof(p2p->cfg->ssid_postfix)) + return -1; + os_memcpy(p2p->cfg->ssid_postfix, postfix, len); + p2p->cfg->ssid_postfix_len = len; + return 0; +} + + +int p2p_get_interface_addr(struct p2p_data *p2p, const u8 *dev_addr, + u8 *iface_addr) +{ + struct p2p_device *dev = p2p_get_device(p2p, dev_addr); + if (dev == NULL || is_zero_ether_addr(dev->interface_addr)) + return -1; + os_memcpy(iface_addr, dev->interface_addr, ETH_ALEN); + return 0; +} diff --git a/src/p2p/p2p.h b/src/p2p/p2p.h new file mode 100644 index 0000000..a1b7cab --- /dev/null +++ b/src/p2p/p2p.h @@ -0,0 +1,1216 @@ +/* + * Wi-Fi Direct - P2P module + * Copyright (c) 2009-2010, Atheros Communications + * + * 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 P2P_H +#define P2P_H + +/** + * P2P_MAX_REG_CLASSES - Maximum number of regulatory classes + */ +#define P2P_MAX_REG_CLASSES 10 + +/** + * P2P_MAX_REG_CLASS_CHANNELS - Maximum number of channels per regulatory class + */ +#define P2P_MAX_REG_CLASS_CHANNELS 20 + +/** + * struct p2p_channels - List of supported channels + */ +struct p2p_channels { + /** + * struct p2p_reg_class - Supported regulatory class + */ + struct p2p_reg_class { + /** + * reg_class - Regulatory class (IEEE 802.11-2007, Annex J) + */ + u8 reg_class; + + /** + * channel - Supported channels + */ + u8 channel[P2P_MAX_REG_CLASS_CHANNELS]; + + /** + * channels - Number of channel entries in use + */ + size_t channels; + } reg_class[P2P_MAX_REG_CLASSES]; + + /** + * reg_classes - Number of reg_class entries in use + */ + size_t reg_classes; +}; + +enum p2p_wps_method { + WPS_NOT_READY, WPS_PIN_LABEL, WPS_PIN_DISPLAY, WPS_PIN_KEYPAD, WPS_PBC +}; + +/** + * struct p2p_go_neg_results - P2P Group Owner Negotiation results + */ +struct p2p_go_neg_results { + /** + * status - Negotiation result (Status Code) + * + * 0 (P2P_SC_SUCCESS) indicates success. Non-zero values indicate + * failed negotiation. + */ + int status; + + /** + * role_go - Whether local end is Group Owner + */ + int role_go; + + /** + * freq - Frequency of the group operational channel in MHz + */ + int freq; + + /** + * ssid - SSID of the group + */ + u8 ssid[32]; + + /** + * ssid_len - Length of SSID in octets + */ + size_t ssid_len; + + /** + * passphrase - WPA2-Personal passphrase for the group (GO only) + */ + char passphrase[64]; + + /** + * peer_device_addr - P2P Device Address of the peer + */ + u8 peer_device_addr[ETH_ALEN]; + + /** + * peer_interface_addr - P2P Interface Address of the peer + */ + u8 peer_interface_addr[ETH_ALEN]; + + /** + * wps_method - WPS method to be used during provisioning + */ + enum p2p_wps_method wps_method; + +#define P2P_MAX_CHANNELS 50 + + /** + * freq_list - Zero-terminated list of possible operational channels + */ + int freq_list[P2P_MAX_CHANNELS]; + + /** + * persistent_group - Whether the group should be made persistent + */ + int persistent_group; +}; + +struct p2p_data; + +enum p2p_scan_type { + P2P_SCAN_SOCIAL, + P2P_SCAN_FULL, + P2P_SCAN_SPECIFIC, + P2P_SCAN_SOCIAL_PLUS_ONE +}; + +/** + * struct p2p_config - P2P configuration + * + * This configuration is provided to the P2P module during initialization with + * p2p_init(). + */ +struct p2p_config { + /** + * country - Country code to use in P2P operations + */ + char country[3]; + + /** + * reg_class - Regulatory class for own listen channel + */ + u8 reg_class; + + /** + * channel - Own listen channel + */ + u8 channel; + + /** + * Regulatory class for own operational channel + */ + u8 op_reg_class; + + /** + * op_channel - Own operational channel + */ + u8 op_channel; + + /** + * channels - Own supported regulatory classes and channels + * + * List of supposerted channels per regulatory class. The regulatory + * classes are defined in IEEE Std 802.11-2007 Annex J and the + * numbering of the clases depends on the configured country code. + */ + struct p2p_channels channels; + + /** + * pri_dev_type - Primary Device Type (see WPS) + */ + u8 pri_dev_type[8]; + + /** + * P2P_SEC_DEVICE_TYPES - Maximum number of secondary device types + */ +#define P2P_SEC_DEVICE_TYPES 5 + + /** + * sec_dev_type - Optional secondary device types + */ + u8 sec_dev_type[P2P_SEC_DEVICE_TYPES][8]; + + /** + * dev_addr - P2P Device Address + */ + u8 dev_addr[ETH_ALEN]; + + /** + * dev_name - Device Name + */ + char *dev_name; + + /** + * num_sec_dev_types - Number of sec_dev_type entries + */ + size_t num_sec_dev_types; + + /** + * concurrent_operations - Whether concurrent operations are supported + */ + int concurrent_operations; + + /** + * max_peers - Maximum number of discovered peers to remember + * + * If more peers are discovered, older entries will be removed to make + * room for the new ones. + */ + size_t max_peers; + + /** + * ssid_postfix - Postfix data to add to the SSID + * + * This data will be added to the end of the SSID after the + * DIRECT- prefix. + */ + u8 ssid_postfix[32 - 9]; + + /** + * ssid_postfix_len - Length of the ssid_postfix data + */ + size_t ssid_postfix_len; + + /** + * msg_ctx - Context to use with wpa_msg() calls + */ + void *msg_ctx; + + /** + * cb_ctx - Context to use with callback functions + */ + void *cb_ctx; + + + /* Callbacks to request lower layer driver operations */ + + /** + * p2p_scan - Request a P2P scan/search + * @ctx: Callback context from cb_ctx + * @type: Scan type + * @freq: Specific frequency (MHz) to scan or 0 for no restriction + * Returns: 0 on success, -1 on failure + * + * This callback function is used to request a P2P scan or search + * operation to be completed. Type type argument specifies which type + * of scan is to be done. @P2P_SCAN_SOCIAL indicates that only the + * social channels (1, 6, 11) should be scanned. @P2P_SCAN_FULL + * indicates that all channels are to be scanned. @P2P_SCAN_SPECIFIC + * request a scan of a single channel specified by freq. + * @P2P_SCAN_SOCIAL_PLUS_ONE request scan of all the social channels + * plus one extra channel specified by freq. + * + * The full scan is used for the initial scan to find group owners from + * all. The other types are used during search phase scan of the social + * channels (with potential variation if the Listen channel of the + * target peer is known or if other channels are scanned in steps). + * + * The scan results are returned after this call by calling + * p2p_scan_res_handler() for each scan result that has a P2P IE and + * then calling p2p_scan_res_handled() to indicate that all scan + * results have been indicated. + */ + int (*p2p_scan)(void *ctx, enum p2p_scan_type type, int freq); + + /** + * send_probe_resp - Transmit a Probe Response frame + * @ctx: Callback context from cb_ctx + * @buf: Probe Response frame (including the header and body) + * Returns: 0 on success, -1 on failure + * + * This function is used to reply to Probe Request frames that were + * indicated with a call to p2p_probe_req_rx(). The response is to be + * sent on the same channel or to be dropped if the driver is not + * anymore listening to Probe Request frames. + * + * Alternatively, the responsibility for building the Probe Response + * frames in Listen state may be in another system component in which + * case this function need to be implemented (i.e., the function + * pointer can be %NULL). The WPS and P2P IEs to be added for Probe + * Response frames in such a case are available from the + * start_listen() callback. It should be noted that the received Probe + * Request frames must be indicated by calling p2p_probe_req_rx() even + * if this send_probe_resp() is not used. + */ + int (*send_probe_resp)(void *ctx, const struct wpabuf *buf); + + /** + * send_action - Transmit an Action frame + * @ctx: Callback context from cb_ctx + * @freq: Frequency in MHz for the channel on which to transmit + * @dst: Destination MAC address (Address 1) + * @src: Source MAC address (Address 2) + * @bssid: BSSID (Address 3) + * @buf: Frame body (starting from Category field) + * @len: Length of buf in octets + * @wait_time: How many msec to wait for a response frame + * Returns: 0 on success, -1 on failure + * + * The Action frame may not be transmitted immediately and the status + * of the transmission must be reported by calling + * p2p_send_action_cb() once the frame has either been transmitted or + * it has been dropped due to excessive retries or other failure to + * transmit. + */ + int (*send_action)(void *ctx, unsigned int freq, const u8 *dst, + const u8 *src, const u8 *bssid, const u8 *buf, + size_t len, unsigned int wait_time); + + /** + * send_action_done - Notify that Action frame sequence was completed + * @ctx: Callback context from cb_ctx + * + * This function is called when the Action frame sequence that was + * started with send_action() has been completed, i.e., when there is + * no need to wait for a response from the destination peer anymore. + */ + void (*send_action_done)(void *ctx); + + /** + * start_listen - Start Listen state + * @ctx: Callback context from cb_ctx + * @freq: Frequency of the listen channel in MHz + * @duration: Duration for the Listen state in milliseconds + * @probe_resp_ie: IE(s) to be added to Probe Response frames + * Returns: 0 on success, -1 on failure + * + * This Listen state may not start immediately since the driver may + * have other pending operations to complete first. Once the Listen + * state has started, p2p_listen_cb() must be called to notify the P2P + * module. Once the Listen state is stopped, p2p_listen_end() must be + * called to notify the P2P module that the driver is not in the Listen + * state anymore. + * + * If the send_probe_resp() is not used for generating the response, + * the IEs from probe_resp_ie need to be added to the end of the Probe + * Response frame body. If send_probe_resp() is used, the probe_resp_ie + * information can be ignored. + */ + int (*start_listen)(void *ctx, unsigned int freq, + unsigned int duration, + const struct wpabuf *probe_resp_ie); + /** + * stop_listen - Stop Listen state + * @ctx: Callback context from cb_ctx + * + * This callback can be used to stop a Listen state operation that was + * previously requested with start_listen(). + */ + void (*stop_listen)(void *ctx); + + /** + * get_noa - Get current Notice of Absence attribute payload + * @ctx: Callback context from cb_ctx + * @interface_addr: P2P Interface Address of the GO + * @buf: Buffer for returning NoA + * @buf_len: Buffer length in octets + * Returns: Number of octets used in buf, 0 to indicate no NoA is being + * advertized, or -1 on failure + * + * This function is used to fetch the current Notice of Absence + * attribute value from GO. + */ + int (*get_noa)(void *ctx, const u8 *interface_addr, u8 *buf, + size_t buf_len); + + /* Callbacks to notify events to upper layer management entity */ + + /** + * dev_found - Notification of a found P2P Device + * @ctx: Callback context from cb_ctx + * @addr: Source address of the message triggering this notification + * @dev_addr: P2P Device Address of the found P2P Device + * @pri_dev_type: Primary Device Type + * @dev_name: Device Name + * @config_methods: Configuration Methods + * @dev_capab: Device Capabilities + * @group_capab: Group Capabilities + * + * This callback is used to notify that a new P2P Device has been + * found. This may happen, e.g., during Search state based on scan + * results or during Listen state based on receive Probe Request and + * Group Owner Negotiation Request. + */ + void (*dev_found)(void *ctx, const u8 *addr, const u8 *dev_addr, + const u8 *pri_dev_type, const char *dev_name, + u16 config_methods, u8 dev_capab, u8 group_capab); + + /** + * go_neg_req_rx - Notification of a receive GO Negotiation Request + * @ctx: Callback context from cb_ctx + * @src: Source address of the message triggering this notification + * + * This callback is used to notify that a P2P Device is requesting + * group owner negotiation with us, but we do not have all the + * necessary information to start GO Negotiation. This indicates that + * the local user has not authorized the connection yet by providing a + * PIN or PBC button press. This information can be provided with a + * call to p2p_connect(). + */ + void (*go_neg_req_rx)(void *ctx, const u8 *src); + + /** + * go_neg_completed - Notification of GO Negotiation results + * @ctx: Callback context from cb_ctx + * @res: GO Negotiation results + * + * This callback is used to notify that Group Owner Negotiation has + * been completed. Non-zero struct p2p_go_neg_results::status indicates + * failed negotiation. In case of success, this function is responsible + * for creating a new group interface (or using the existing interface + * depending on driver features), setting up the group interface in + * proper mode based on struct p2p_go_neg_results::role_go and + * initializing WPS provisioning either as a Registrar (if GO) or as an + * Enrollee. Successful WPS provisioning must be indicated by calling + * p2p_wps_success_cb(). The callee is responsible for timing out group + * formation if WPS provisioning cannot be completed successfully + * within 15 seconds. + */ + void (*go_neg_completed)(void *ctx, struct p2p_go_neg_results *res); + + /** + * sd_request - Callback on Service Discovery Request + * @ctx: Callback context from cb_ctx + * @freq: Frequency (in MHz) of the channel + * @sa: Source address of the request + * @dialog_token: Dialog token + * @update_indic: Service Update Indicator from the source of request + * @tlvs: P2P Service Request TLV(s) + * @tlvs_len: Length of tlvs buffer in octets + * + * This callback is used to indicate reception of a service discovery + * request. Response to the query must be indicated by calling + * p2p_sd_response() with the context information from the arguments to + * this callback function. + * + * This callback handler can be set to %NULL to indicate that service + * discovery is not supported. + */ + void (*sd_request)(void *ctx, int freq, const u8 *sa, u8 dialog_token, + u16 update_indic, const u8 *tlvs, size_t tlvs_len); + + /** + * sd_response - Callback on Service Discovery Response + * @ctx: Callback context from cb_ctx + * @sa: Source address of the request + * @update_indic: Service Update Indicator from the source of response + * @tlvs: P2P Service Response TLV(s) + * @tlvs_len: Length of tlvs buffer in octets + * + * This callback is used to indicate reception of a service discovery + * response. This callback handler can be set to %NULL if no service + * discovery requests are used. The information provided with this call + * is replies to the queries scheduled with p2p_sd_request(). + */ + void (*sd_response)(void *ctx, const u8 *sa, u16 update_indic, + const u8 *tlvs, size_t tlvs_len); + + /** + * prov_disc_req - Callback on Provisiong Discovery Request + * @ctx: Callback context from cb_ctx + * @peer: Source address of the request + * @config_methods: Requested WPS Config Method + * @dev_addr: P2P Device Address of the found P2P Device + * @pri_dev_type: Primary Device Type + * @dev_name: Device Name + * @supp_config_methods: Supported configuration Methods + * @dev_capab: Device Capabilities + * @group_capab: Group Capabilities + * + * This callback is used to indicate reception of a Provision Discovery + * Request frame that the P2P module accepted. + */ + void (*prov_disc_req)(void *ctx, const u8 *peer, u16 config_methods, + const u8 *dev_addr, const u8 *pri_dev_type, + const char *dev_name, u16 supp_config_methods, + u8 dev_capab, u8 group_capab); + + /** + * prov_disc_resp - Callback on Provisiong Discovery Response + * @ctx: Callback context from cb_ctx + * @peer: Source address of the response + * @config_methods: Value from p2p_prov_disc_req() or 0 on failure + * + * This callback is used to indicate reception of a Provision Discovery + * Response frame for a pending request scheduled with + * p2p_prov_disc_req(). This callback handler can be set to %NULL if + * provision discovery is not used. + */ + void (*prov_disc_resp)(void *ctx, const u8 *peer, u16 config_methods); + + /** + * invitation_process - Optional callback for processing Invitations + * @ctx: Callback context from cb_ctx + * @sa: Source address of the Invitation Request + * @bssid: P2P Group BSSID from the request or %NULL if not included + * @go_dev_addr: GO Device Address from P2P Group ID + * @ssid: SSID from P2P Group ID + * @ssid_len: Length of ssid buffer in octets + * @go: Variable for returning whether the local end is GO in the group + * @group_bssid: Buffer for returning P2P Group BSSID (if local end GO) + * @force_freq: Variable for returning forced frequency for the group + * @persistent_group: Whether this is an invitation to reinvoke a + * persistent group (instead of invitation to join an active + * group) + * Returns: Status code (P2P_SC_*) + * + * This optional callback can be used to implement persistent reconnect + * by allowing automatic restarting of persistent groups without user + * interaction. If this callback is not implemented (i.e., is %NULL), + * the received Invitation Request frames are replied with + * %P2P_SC_REQ_RECEIVED status and indicated to upper layer with the + * invitation_result() callback. + * + * If the requested parameters are acceptable and the group is known, + * %P2P_SC_SUCCESS may be returned. If the requested group is unknown, + * %P2P_SC_FAIL_UNKNOWN_GROUP should be returned. %P2P_SC_REQ_RECEIVED + * can be returned if there is not enough data to provide immediate + * response, i.e., if some sort of user interaction is needed. The + * invitation_received() callback will be called in that case + * immediately after this call. + */ + u8 (*invitation_process)(void *ctx, const u8 *sa, const u8 *bssid, + const u8 *go_dev_addr, const u8 *ssid, + size_t ssid_len, int *go, u8 *group_bssid, + int *force_freq, int persistent_group); + + /** + * invitation_received - Callback on Invitation Request RX + * @ctx: Callback context from cb_ctx + * @sa: Source address of the Invitation Request + * @bssid: P2P Group BSSID or %NULL if not received + * @ssid: SSID of the group + * @ssid_len: Length of ssid in octets + * @go_dev_addr: GO Device Address + * @status: Response Status + * @op_freq: Operational frequency for the group + * + * This callback is used to indicate sending of an Invitation Response + * for a received Invitation Request. If status == 0 (success), the + * upper layer code is responsible for starting the group. status == 1 + * indicates need to get user authorization for the group. Other status + * values indicate that the invitation request was rejected. + */ + void (*invitation_received)(void *ctx, const u8 *sa, const u8 *bssid, + const u8 *ssid, size_t ssid_len, + const u8 *go_dev_addr, u8 status, + int op_freq); + + /** + * invitation_result - Callback on Invitation result + * @ctx: Callback context from cb_ctx + * @status: Negotiation result (Status Code) + * @bssid: P2P Group BSSID or %NULL if not received + * + * This callback is used to indicate result of an Invitation procedure + * started with a call to p2p_invite(). The indicated status code is + * the value received from the peer in Invitation Response with 0 + * (P2P_SC_SUCCESS) indicating success or -1 to indicate a timeout or a + * local failure in transmitting the Invitation Request. + */ + void (*invitation_result)(void *ctx, int status, const u8 *bssid); +}; + + +/* P2P module initialization/deinitialization */ + +/** + * p2p_init - Initialize P2P module + * @cfg: P2P module configuration + * Returns: Pointer to private data or %NULL on failure + * + * This function is used to initialize global P2P module context (one per + * device). The P2P module will keep a copy of the configuration data, so the + * caller does not need to maintain this structure. However, the callback + * functions and the context parameters to them must be kept available until + * the P2P module is deinitialized with p2p_deinit(). + */ +struct p2p_data * p2p_init(const struct p2p_config *cfg); + +/** + * p2p_deinit - Deinitialize P2P module + * @p2p: P2P module context from p2p_init() + */ +void p2p_deinit(struct p2p_data *p2p); + +/** + * p2p_flush - Flush P2P module state + * @p2p: P2P module context from p2p_init() + * + * This command removes the P2P module state like peer device entries. + */ +void p2p_flush(struct p2p_data *p2p); + +/** + * p2p_set_dev_name - Set device name + * @p2p: P2P module context from p2p_init() + * Returns: 0 on success, -1 on failure + * + * This function can be used to update the P2P module configuration with + * information that was not available at the time of the p2p_init() call. + */ +int p2p_set_dev_name(struct p2p_data *p2p, const char *dev_name); + +/** + * p2p_set_pri_dev_type - Set primary device type + * @p2p: P2P module context from p2p_init() + * Returns: 0 on success, -1 on failure + * + * This function can be used to update the P2P module configuration with + * information that was not available at the time of the p2p_init() call. + */ +int p2p_set_pri_dev_type(struct p2p_data *p2p, const u8 *pri_dev_type); + +/** + * p2p_set_sec_dev_types - Set secondary device types + * @p2p: P2P module context from p2p_init() + * Returns: 0 on success, -1 on failure + * + * This function can be used to update the P2P module configuration with + * information that was not available at the time of the p2p_init() call. + */ +int p2p_set_sec_dev_types(struct p2p_data *p2p, const u8 dev_types[][8], + size_t num_dev_types); + +int p2p_set_country(struct p2p_data *p2p, const char *country); + + +/* Commands from upper layer management entity */ + +enum p2p_discovery_type { + P2P_FIND_START_WITH_FULL, + P2P_FIND_ONLY_SOCIAL, + P2P_FIND_PROGRESSIVE +}; + +/** + * p2p_find - Start P2P Find (Device Discovery) + * @p2p: P2P module context from p2p_init() + * @timeout: Timeout for find operation in seconds or 0 for no timeout + * @type: Device Discovery type + * Returns: 0 on success, -1 on failure + */ +int p2p_find(struct p2p_data *p2p, unsigned int timeout, + enum p2p_discovery_type type); + +/** + * p2p_stop_find - Stop P2P Find (Device Discovery) + * @p2p: P2P module context from p2p_init() + */ +void p2p_stop_find(struct p2p_data *p2p); + +/** + * p2p_listen - Start P2P Listen state for specified duration + * @p2p: P2P module context from p2p_init() + * @timeout: Listen state duration in milliseconds + * Returns: 0 on success, -1 on failure + * + * This function can be used to request the P2P module to keep the device + * discoverable on the listen channel for an extended set of time. At least in + * its current form, this is mainly used for testing purposes and may not be of + * much use for normal P2P operations. + */ +int p2p_listen(struct p2p_data *p2p, unsigned int timeout); + +/** + * p2p_connect - Start P2P group formation (GO negotiation) + * @p2p: P2P module context from p2p_init() + * @peer_addr: MAC address of the peer P2P client + * @wps_method: WPS method to be used in provisioning + * @go_intent: Local GO intent value (1..15) + * @own_interface_addr: Intended interface address to use with the group + * @force_freq: The only allowed channel frequency in MHz or 0 + * @persistent_group: Whether to create a persistent group + * Returns: 0 on success, -1 on failure + */ +int p2p_connect(struct p2p_data *p2p, const u8 *peer_addr, + enum p2p_wps_method wps_method, + int go_intent, const u8 *own_interface_addr, + unsigned int force_freq, int persistent_group); + +/** + * p2p_authorize - Authorize P2P group formation (GO negotiation) + * @p2p: P2P module context from p2p_init() + * @peer_addr: MAC address of the peer P2P client + * @wps_method: WPS method to be used in provisioning + * @go_intent: Local GO intent value (1..15) + * @own_interface_addr: Intended interface address to use with the group + * @force_freq: The only allowed channel frequency in MHz or 0 + * @persistent_group: Whether to create a persistent group + * Returns: 0 on success, -1 on failure + * + * This is like p2p_connect(), but the actual group negotiation is not + * initiated automatically, i.e., the other end is expected to do that. + */ +int p2p_authorize(struct p2p_data *p2p, const u8 *peer_addr, + enum p2p_wps_method wps_method, + int go_intent, const u8 *own_interface_addr, + unsigned int force_freq, int persistent_group); + +/** + * p2p_reject - Reject peer device (explicitly block connection attempts) + * @p2p: P2P module context from p2p_init() + * @peer_addr: MAC address of the peer P2P client + * Returns: 0 on success, -1 on failure + */ +int p2p_reject(struct p2p_data *p2p, const u8 *peer_addr); + +/** + * p2p_prov_disc_req - Send Provision Discovery Request + * @p2p: P2P module context from p2p_init() + * @peer_addr: MAC address of the peer P2P client + * @config_methods: WPS Config Methods value (only one bit set) + * @join: Whether this is used by a client joining an active group + * Returns: 0 on success, -1 on failure + * + * This function can be used to request a discovered P2P peer to display a PIN + * (config_methods = WPS_CONFIG_DISPLAY) or be prepared to enter a PIN from us + * (config_methods = WPS_CONFIG_KEYPAD). The Provision Discovery Request frame + * is transmitted once immediately and if no response is received, the frame + * will be sent again whenever the target device is discovered during device + * dsicovery (start with a p2p_find() call). Response from the peer is + * indicated with the p2p_config::prov_disc_resp() callback. + */ +int p2p_prov_disc_req(struct p2p_data *p2p, const u8 *peer_addr, + u16 config_methods, int join); + +/** + * p2p_sd_request - Schedule a service discovery query + * @p2p: P2P module context from p2p_init() + * @dst: Destination peer or %NULL to apply for all peers + * @tlvs: P2P Service Query TLV(s) + * Returns: Reference to the query or %NULL on failure + * + * Response to the query is indicated with the p2p_config::sd_response() + * callback. + */ +void * p2p_sd_request(struct p2p_data *p2p, const u8 *dst, + const struct wpabuf *tlvs); + +/** + * p2p_sd_cancel_request - Cancel a pending service discovery query + * @p2p: P2P module context from p2p_init() + * @req: Query reference from p2p_sd_request() + * Returns: 0 if request for cancelled; -1 if not found + */ +int p2p_sd_cancel_request(struct p2p_data *p2p, void *req); + +/** + * p2p_sd_response - Send response to a service discovery query + * @p2p: P2P module context from p2p_init() + * @freq: Frequency from p2p_config::sd_request() callback + * @dst: Destination address from p2p_config::sd_request() callback + * @dialog_token: Dialog token from p2p_config::sd_request() callback + * @resp_tlvs: P2P Service Response TLV(s) + * + * This function is called as a response to the request indicated with + * p2p_config::sd_request() callback. + */ +void p2p_sd_response(struct p2p_data *p2p, int freq, const u8 *dst, + u8 dialog_token, const struct wpabuf *resp_tlvs); + +/** + * p2p_sd_service_update - Indicate a change in local services + * @p2p: P2P module context from p2p_init() + * + * This function needs to be called whenever there is a change in availability + * of the local services. This will increment the Service Update Indicator + * value which will be used in SD Request and Response frames. + */ +void p2p_sd_service_update(struct p2p_data *p2p); + + +enum p2p_invite_role { + P2P_INVITE_ROLE_GO, + P2P_INVITE_ROLE_ACTIVE_GO, + P2P_INVITE_ROLE_CLIENT +}; + +/** + * p2p_invite - Invite a P2P Device into a group + * @p2p: P2P module context from p2p_init() + * @peer: Device Address of the peer P2P Device + * @role: Local role in the group + * @bssid: Group BSSID or %NULL if not known + * @ssid: Group SSID + * @ssid_len: Length of ssid in octets + * @force_freq: The only allowed channel frequency in MHz or 0 + * @go_dev_addr: Forced GO Device Address or %NULL if none + * @persistent_group: Whether this is to reinvoke a persistent group + * Returns: 0 on success, -1 on failure + */ +int p2p_invite(struct p2p_data *p2p, const u8 *peer, enum p2p_invite_role role, + const u8 *bssid, const u8 *ssid, size_t ssid_len, + unsigned int force_freq, const u8 *go_dev_addr, + int persistent_group); + +/** + * p2p_presence_req - Request GO presence + * @p2p: P2P module context from p2p_init() + * @go_interface_addr: GO P2P Interface Address + * @own_interface_addr: Own P2P Interface Address for this group + * @freq: Group operating frequence (in MHz) + * @duration1: Preferred presence duration in microseconds + * @interval1: Preferred presence interval in microseconds + * @duration2: Acceptable presence duration in microseconds + * @interval2: Acceptable presence interval in microseconds + * Returns: 0 on success, -1 on failure + * + * If both duration and interval values are zero, the parameter pair is not + * specified (i.e., to remove Presence Request, use duration1 = interval1 = 0). + */ +int p2p_presence_req(struct p2p_data *p2p, const u8 *go_interface_addr, + const u8 *own_interface_addr, unsigned int freq, + u32 duration1, u32 interval1, u32 duration2, + u32 interval2); + +/** + * p2p_ext_listen - Set Extended Listen Timing + * @p2p: P2P module context from p2p_init() + * @freq: Group operating frequence (in MHz) + * @period: Availability period in milliseconds (1-65535; 0 to disable) + * @interval: Availability interval in milliseconds (1-65535; 0 to disable) + * Returns: 0 on success, -1 on failure + * + * This function can be used to enable or disable (period = interval = 0) + * Extended Listen Timing. When enabled, the P2P Device will become + * discoverable (go into Listen State) every @interval milliseconds for at + * least @period milliseconds. + */ +int p2p_ext_listen(struct p2p_data *p2p, unsigned int period, + unsigned int interval); + +/* Event notifications from upper layer management operations */ + +/** + * p2p_wps_success_cb - Report successfully completed WPS provisioning + * @p2p: P2P module context from p2p_init() + * @mac_addr: Peer address + * + * This function is used to report successfully completed WPS provisioning + * during group formation in both GO/Registrar and client/Enrollee roles. + */ +void p2p_wps_success_cb(struct p2p_data *p2p, const u8 *mac_addr); + +/** + * p2p_group_formation_failed - Report failed WPS provisioning + * @p2p: P2P module context from p2p_init() + * + * This function is used to report failed group formation. This can happen + * either due to failed WPS provisioning or due to 15 second timeout during + * the provisioning phase. + */ +void p2p_group_formation_failed(struct p2p_data *p2p); + + +/* Event notifications from lower layer driver operations */ + +/** + * p2p_probe_req_rx - Report reception of a Probe Request frame + * @p2p: P2P module context from p2p_init() + * @addr: Source MAC address + * @ie: Information elements from the Probe Request frame body + * @ie_len: Length of ie buffer in octets + * Returns: 0 to indicate the frame was not processed or 1 if it was + */ +int p2p_probe_req_rx(struct p2p_data *p2p, const u8 *addr, const u8 *ie, + size_t ie_len); + +/** + * p2p_rx_action - Report received Action frame + * @p2p: P2P module context from p2p_init() + * @da: Destination address of the received Action frame + * @sa: Source address of the received Action frame + * @bssid: Address 3 of the received Action frame + * @category: Category of the received Action frame + * @data: Action frame body after the Category field + * @len: Length of the data buffer in octets + * @freq: Frequency (in MHz) on which the frame was received + */ +void p2p_rx_action(struct p2p_data *p2p, const u8 *da, const u8 *sa, + const u8 *bssid, u8 category, + const u8 *data, size_t len, int freq); + +/** + * p2p_scan_res_handler - Indicate a P2P scan results + * @p2p: P2P module context from p2p_init() + * @bssid: BSSID of the scan result + * @freq: Frequency of the channel on which the device was found in MHz + * @level: Signal level (signal strength of the received Beacon/Probe Response + * frame) + * @ies: Pointer to IEs from the scan result + * @ies_len: Length of the ies buffer + * Returns: 0 to continue or 1 to stop scan result indication + * + * This function is called to indicate a scan result entry with P2P IE from a + * scan requested with struct p2p_config::p2p_scan(). This can be called during + * the actual scan process (i.e., whenever a new device is found) or as a + * sequence of calls after the full scan has been completed. The former option + * can result in optimized operations, but may not be supported by all + * driver/firmware designs. The ies buffer need to include at least the P2P IE, + * but it is recommended to include all IEs received from the device. The + * caller does not need to check that the IEs contain a P2P IE before calling + * this function since frames will be filtered internally if needed. + * + * This function will return 1 if it wants to stop scan result iteration (and + * scan in general if it is still in progress). This is used to allow faster + * start of a pending operation, e.g., to start a pending GO negotiation. + */ +int p2p_scan_res_handler(struct p2p_data *p2p, const u8 *bssid, int freq, + int level, const u8 *ies, size_t ies_len); + +/** + * p2p_scan_res_handled - Indicate end of scan results + * @p2p: P2P module context from p2p_init() + * + * This function is called to indicate that all P2P scan results from a scan + * have been reported with zero or more calls to p2p_scan_res_handler(). This + * function must be called as a response to successful + * struct p2p_config::p2p_scan() call if none of the p2p_scan_res_handler() + * calls stopped iteration. + */ +void p2p_scan_res_handled(struct p2p_data *p2p); + +/** + * p2p_send_action_cb - Notify TX status of an Action frame + * @p2p: P2P module context from p2p_init() + * @freq: Channel frequency in MHz + * @dst: Destination MAC address (Address 1) + * @src: Source MAC address (Address 2) + * @bssid: BSSID (Address 3) + * @success: Whether the transmission succeeded + * + * This function is used to indicate the result of an Action frame transmission + * that was requested with struct p2p_config::send_action() callback. + */ +void p2p_send_action_cb(struct p2p_data *p2p, unsigned int freq, const u8 *dst, + const u8 *src, const u8 *bssid, int success); + +/** + * p2p_listen_cb - Indicate the start of a requested Listen state + * @p2p: P2P module context from p2p_init() + * @freq: Listen channel frequency in MHz + * @duration: Duration for the Listen state in milliseconds + * + * This function is used to indicate that a Listen state requested with + * struct p2p_config::start_listen() callback has started. + */ +void p2p_listen_cb(struct p2p_data *p2p, unsigned int freq, + unsigned int duration); + +/** + * p2p_listen_end - Indicate the end of a requested Listen state + * @p2p: P2P module context from p2p_init() + * @freq: Listen channel frequency in MHz + * Returns: 0 if no operations were started, 1 if an operation was started + * + * This function is used to indicate that a Listen state requested with + * struct p2p_config::start_listen() callback has ended. + */ +int p2p_listen_end(struct p2p_data *p2p, unsigned int freq); + +void p2p_deauth_notif(struct p2p_data *p2p, const u8 *bssid, u16 reason_code, + const u8 *ie, size_t ie_len); + +void p2p_disassoc_notif(struct p2p_data *p2p, const u8 *bssid, u16 reason_code, + const u8 *ie, size_t ie_len); + + +/* Per-group P2P state for GO */ + +struct p2p_group; + +/** + * struct p2p_group_config - P2P group configuration + * + * This configuration is provided to the P2P module during initialization of + * the per-group information with p2p_group_init(). + */ +struct p2p_group_config { + /** + * persistent_group - Whether the group is persistent + */ + int persistent_group; + + /** + * interface_addr - P2P Interface Address of the group + */ + u8 interface_addr[ETH_ALEN]; + + /** + * cb_ctx - Context to use with callback functions + */ + void *cb_ctx; + + /** + * ie_update - Notification of IE update + * @ctx: Callback context from cb_ctx + * @beacon_ies: P2P IE for Beacon frames or %NULL if no change + * @proberesp_ies: P2P Ie for Probe Response frames + * + * P2P module uses this callback function to notify whenever the P2P IE + * in Beacon or Probe Response frames should be updated based on group + * events. + * + * The callee is responsible for freeing the returned buffer(s) with + * wpabuf_free(). + */ + void (*ie_update)(void *ctx, struct wpabuf *beacon_ies, + struct wpabuf *proberesp_ies); +}; + +/** + * p2p_group_init - Initialize P2P group + * @p2p: P2P module context from p2p_init() + * @config: P2P group configuration (will be freed by p2p_group_deinit()) + * Returns: Pointer to private data or %NULL on failure + * + * This function is used to initialize per-group P2P module context. Currently, + * this is only used to manage GO functionality and P2P clients do not need to + * create an instance of this per-group information. + */ +struct p2p_group * p2p_group_init(struct p2p_data *p2p, + struct p2p_group_config *config); + +/** + * p2p_group_deinit - Deinitialize P2P group + * @group: P2P group context from p2p_group_init() + */ +void p2p_group_deinit(struct p2p_group *group); + +/** + * p2p_group_notif_assoc - Notification of P2P client association with GO + * @group: P2P group context from p2p_group_init() + * @addr: Interface address of the P2P client + * @ie: IEs from the (Re)association Request frame + * @len: Length of the ie buffer in octets + * Returns: 0 on success, -1 on failure + */ +int p2p_group_notif_assoc(struct p2p_group *group, const u8 *addr, + const u8 *ie, size_t len); + +/** + * p2p_group_assoc_resp_ie - Build P2P IE for (re)association response + * @group: P2P group context from p2p_group_init() + * @status: Status value (P2P_SC_SUCCESS if association succeeded) + * Returns: P2P IE for (Re)association Response or %NULL on failure + * + * The caller is responsible for freeing the returned buffer with + * wpabuf_free(). + */ +struct wpabuf * p2p_group_assoc_resp_ie(struct p2p_group *group, u8 status); + +/** + * p2p_group_notif_disassoc - Notification of P2P client disassociation from GO + * @group: P2P group context from p2p_group_init() + * @addr: Interface address of the P2P client + */ +void p2p_group_notif_disassoc(struct p2p_group *group, const u8 *addr); + +/** + * p2p_group_notif_formation_done - Notification of completed group formation + * @group: P2P group context from p2p_group_init() + */ +void p2p_group_notif_formation_done(struct p2p_group *group); + +/** + * p2p_group_notif_noa - Notification of NoA change + * @group: P2P group context from p2p_group_init() + * @noa: Notice of Absence attribute payload, %NULL if none + * @noa_len: Length of noa buffer in octets + * Returns: 0 on success, -1 on failure + * + * Notify the P2P group management about a new NoA contents. This will be + * inserted into the P2P IEs in Beacon and Probe Response frames with rest of + * the group information. + */ +int p2p_group_notif_noa(struct p2p_group *group, const u8 *noa, + size_t noa_len); + +/** + * p2p_group_match_dev_type - Match device types in group with requested type + * @group: P2P group context from p2p_group_init() + * @wps: WPS TLVs from Probe Request frame (concatenated WPS IEs) + * Returns: 1 on match, 0 on mismatch + * + * This function can be used to match the Requested Device Type attribute in + * WPS IE with the device types of a group member for deciding whether a GO + * should reply to a Probe Request frame. Match will be reported if the WPS IE + * is not requested any specific device type. + */ +int p2p_group_match_dev_type(struct p2p_group *group, struct wpabuf *wps); + +/** + * p2p_group_go_discover - Send GO Discoverability Request to a group client + * @group: P2P group context from p2p_group_init() + * Returns: 0 on success (frame scheduled); -1 if client was not found + */ +int p2p_group_go_discover(struct p2p_group *group, const u8 *dev_id, + const u8 *searching_dev, int rx_freq); + + +/* Generic helper functions */ + +/** + * p2p_ie_text - Build text format description of P2P IE + * @p2p_ie: P2P IE + * @buf: Buffer for returning text + * @end: Pointer to the end of the buf area + * Returns: Number of octets written to the buffer or -1 on failure + * + * This function can be used to parse P2P IE contents into text format + * field=value lines. + */ +int p2p_ie_text(struct wpabuf *p2p_ie, char *buf, char *end); + +/** + * p2p_scan_result_text - Build text format description of P2P IE + * @ies: Information elements from scan results + * @ies_len: ies buffer length in octets + * @buf: Buffer for returning text + * @end: Pointer to the end of the buf area + * Returns: Number of octets written to the buffer or -1 on failure + * + * This function can be used to parse P2P IE contents into text format + * field=value lines. + */ +int p2p_scan_result_text(const u8 *ies, size_t ies_len, char *buf, char *end); + +/** + * p2p_assoc_req_ie - Build P2P IE for (Re)Association Request frame + * @p2p: P2P module context from p2p_init() + * @bssid: BSSID + * @buf: Buffer for writing the P2P IE + * @len: Maximum buf length in octets + * @p2p_group: Whether this is for association with a P2P GO + * Returns: Number of octets written into buf or -1 on failure + */ +int p2p_assoc_req_ie(struct p2p_data *p2p, const u8 *bssid, u8 *buf, + size_t len, int p2p_group); + +/** + * p2p_scan_ie - Build P2P IE for Probe Request + * @p2p: P2P module context from p2p_init() + * @ies: Buffer for writing P2P IE + */ +void p2p_scan_ie(struct p2p_data *p2p, struct wpabuf *ies); + +/** + * p2p_go_params - Generate random P2P group parameters + * @p2p: P2P module context from p2p_init() + * @params: Buffer for parameters + * Returns: 0 on success, -1 on failure + */ +int p2p_go_params(struct p2p_data *p2p, struct p2p_go_neg_results *params); + +/** + * p2p_get_group_capab - Get Group Capability from P2P IE data + * @p2p_ie: P2P IE(s) contents + * Returns: Group Capability + */ +u8 p2p_get_group_capab(const struct wpabuf *p2p_ie); + +/** + * p2p_get_go_dev_addr - Get P2P Device Address from P2P IE data + * @p2p_ie: P2P IE(s) contents + * Returns: Pointer to P2P Device Address or %NULL if not included + */ +const u8 * p2p_get_go_dev_addr(const struct wpabuf *p2p_ie); + +/** + * p2p_get_peer_info - Get P2P peer information in text format + * @p2p: P2P module context from p2p_init() + * @addr: P2P Device Address of the peer or %NULL to indicate the first peer + * @next: Whether to select the peer entry following the one indicated by addr + * @buf: Buffer for returning text + * @buflen: Maximum buffer length + * Returns: Number of octets written to the buffer or -1 on failure + */ +int p2p_get_peer_info(struct p2p_data *p2p, const u8 *addr, int next, + char *buf, size_t buflen); + +/** + * p2p_set_client_discoverability - Set client discoverability capability + * @p2p: P2P module context from p2p_init() + * @enabled: Whether client discoverability will be enabled + * + * This function can be used to disable (and re-enable) client discoverability. + * This capability is enabled by default and should not be disabled in normal + * use cases, i.e., this is mainly for testing purposes. + */ +void p2p_set_client_discoverability(struct p2p_data *p2p, int enabled); + +/** + * p2p_set_manageD_oper - Set managed P2P Device operations capability + * @p2p: P2P module context from p2p_init() + * @enabled: Whether managed P2P Device operations will be enabled + */ +void p2p_set_managed_oper(struct p2p_data *p2p, int enabled); + +int p2p_set_listen_channel(struct p2p_data *p2p, u8 reg_class, u8 channel); + +int p2p_set_ssid_postfix(struct p2p_data *p2p, const u8 *postfix, size_t len); + +int p2p_get_interface_addr(struct p2p_data *p2p, const u8 *dev_addr, + u8 *iface_addr); + +#endif /* P2P_H */ diff --git a/src/p2p/p2p_build.c b/src/p2p/p2p_build.c new file mode 100644 index 0000000..c45ae89 --- /dev/null +++ b/src/p2p/p2p_build.c @@ -0,0 +1,389 @@ +/* + * P2P - IE builder + * Copyright (c) 2009-2010, Atheros Communications + * + * 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 "wps/wps_i.h" +#include "p2p_i.h" + + +void p2p_buf_add_action_hdr(struct wpabuf *buf, u8 subtype, u8 dialog_token) +{ + wpabuf_put_u8(buf, WLAN_ACTION_VENDOR_SPECIFIC); + wpabuf_put_be24(buf, OUI_WFA); + wpabuf_put_u8(buf, P2P_OUI_TYPE); + + wpabuf_put_u8(buf, subtype); /* OUI Subtype */ + wpabuf_put_u8(buf, dialog_token); + wpa_printf(MSG_DEBUG, "P2P: * Dialog Token: %d", dialog_token); +} + + +void p2p_buf_add_public_action_hdr(struct wpabuf *buf, u8 subtype, + u8 dialog_token) +{ + wpabuf_put_u8(buf, WLAN_ACTION_PUBLIC); + wpabuf_put_u8(buf, WLAN_PA_VENDOR_SPECIFIC); + wpabuf_put_be24(buf, OUI_WFA); + wpabuf_put_u8(buf, P2P_OUI_TYPE); + + wpabuf_put_u8(buf, subtype); /* OUI Subtype */ + wpabuf_put_u8(buf, dialog_token); + wpa_printf(MSG_DEBUG, "P2P: * Dialog Token: %d", dialog_token); +} + + +u8 * p2p_buf_add_ie_hdr(struct wpabuf *buf) +{ + u8 *len; + + /* P2P IE header */ + wpabuf_put_u8(buf, WLAN_EID_VENDOR_SPECIFIC); + len = wpabuf_put(buf, 1); /* IE length to be filled */ + wpabuf_put_be24(buf, OUI_WFA); + wpabuf_put_u8(buf, P2P_OUI_TYPE); + wpa_printf(MSG_DEBUG, "P2P: * P2P IE header"); + return len; +} + + +void p2p_buf_update_ie_hdr(struct wpabuf *buf, u8 *len) +{ + /* Update P2P IE Length */ + *len = (u8 *) wpabuf_put(buf, 0) - len - 1; +} + + +void p2p_buf_add_capability(struct wpabuf *buf, u8 dev_capab, u8 group_capab) +{ + /* P2P Capability */ + wpabuf_put_u8(buf, P2P_ATTR_CAPABILITY); + wpabuf_put_le16(buf, 2); + wpabuf_put_u8(buf, dev_capab); /* Device Capabilities */ + wpabuf_put_u8(buf, group_capab); /* Group Capabilities */ + wpa_printf(MSG_DEBUG, "P2P: * Capability dev=%02x group=%02x", + dev_capab, group_capab); +} + + +void p2p_buf_add_go_intent(struct wpabuf *buf, u8 go_intent) +{ + /* Group Owner Intent */ + wpabuf_put_u8(buf, P2P_ATTR_GROUP_OWNER_INTENT); + wpabuf_put_le16(buf, 1); + wpabuf_put_u8(buf, go_intent); + wpa_printf(MSG_DEBUG, "P2P: * GO Intent: Intent %u Tie breaker %u", + go_intent >> 1, go_intent & 0x01); +} + + +void p2p_buf_add_listen_channel(struct wpabuf *buf, const char *country, + u8 reg_class, u8 channel) +{ + /* Listen Channel */ + wpabuf_put_u8(buf, P2P_ATTR_LISTEN_CHANNEL); + wpabuf_put_le16(buf, 5); + wpabuf_put_data(buf, country, 3); + wpabuf_put_u8(buf, reg_class); /* Regulatory Class */ + wpabuf_put_u8(buf, channel); /* Channel Number */ + wpa_printf(MSG_DEBUG, "P2P: * Listen Channel: Regulatory Class %u " + "Channel %u", reg_class, channel); +} + + +void p2p_buf_add_operating_channel(struct wpabuf *buf, const char *country, + u8 reg_class, u8 channel) +{ + /* Operating Channel */ + wpabuf_put_u8(buf, P2P_ATTR_OPERATING_CHANNEL); + wpabuf_put_le16(buf, 5); + wpabuf_put_data(buf, country, 3); + wpabuf_put_u8(buf, reg_class); /* Regulatory Class */ + wpabuf_put_u8(buf, channel); /* Channel Number */ + wpa_printf(MSG_DEBUG, "P2P: * Operating Channel: Regulatory Class %u " + "Channel %u", reg_class, channel); +} + + +void p2p_buf_add_channel_list(struct wpabuf *buf, const char *country, + struct p2p_channels *chan) +{ + u8 *len; + size_t i; + + /* Channel List */ + wpabuf_put_u8(buf, P2P_ATTR_CHANNEL_LIST); + len = wpabuf_put(buf, 2); /* IE length to be filled */ + wpabuf_put_data(buf, country, 3); /* Country String */ + + for (i = 0; i < chan->reg_classes; i++) { + struct p2p_reg_class *c = &chan->reg_class[i]; + wpabuf_put_u8(buf, c->reg_class); + wpabuf_put_u8(buf, c->channels); + wpabuf_put_data(buf, c->channel, c->channels); + } + + /* Update attribute length */ + WPA_PUT_LE16(len, (u8 *) wpabuf_put(buf, 0) - len - 2); + wpa_printf(MSG_DEBUG, "P2P: * Channel List"); +} + + +void p2p_buf_add_status(struct wpabuf *buf, u8 status) +{ + /* Status */ + wpabuf_put_u8(buf, P2P_ATTR_STATUS); + wpabuf_put_le16(buf, 1); + wpabuf_put_u8(buf, status); + wpa_printf(MSG_DEBUG, "P2P: * Status: %d", status); +} + + +void p2p_buf_add_device_info(struct wpabuf *buf, struct p2p_data *p2p, + struct p2p_device *peer) +{ + u8 *len; + u16 methods; + size_t nlen, i; + + /* P2P Device Info */ + wpabuf_put_u8(buf, P2P_ATTR_DEVICE_INFO); + len = wpabuf_put(buf, 2); /* IE length to be filled */ + + /* P2P Device address */ + wpabuf_put_data(buf, p2p->cfg->dev_addr, ETH_ALEN); + + /* Config Methods */ + methods = 0; + if (peer) { + if (peer->wps_method == WPS_PBC) + methods |= WPS_CONFIG_PUSHBUTTON; + else if (peer->wps_method == WPS_PIN_LABEL) + methods |= WPS_CONFIG_LABEL; + else if (peer->wps_method == WPS_PIN_DISPLAY || + peer->wps_method == WPS_PIN_KEYPAD) + methods |= WPS_CONFIG_DISPLAY | WPS_CONFIG_KEYPAD; + } else { + methods |= WPS_CONFIG_PUSHBUTTON; + methods |= WPS_CONFIG_DISPLAY | WPS_CONFIG_KEYPAD; + } + wpabuf_put_be16(buf, methods); + + /* Primary Device Type */ + wpabuf_put_data(buf, p2p->cfg->pri_dev_type, + sizeof(p2p->cfg->pri_dev_type)); + + /* Number of Secondary Device Types */ + wpabuf_put_u8(buf, p2p->cfg->num_sec_dev_types); + + /* Secondary Device Type List */ + for (i = 0; i < p2p->cfg->num_sec_dev_types; i++) + wpabuf_put_data(buf, p2p->cfg->sec_dev_type[i], + WPS_DEV_TYPE_LEN); + + /* Device Name */ + nlen = p2p->cfg->dev_name ? os_strlen(p2p->cfg->dev_name) : 0; + wpabuf_put_be16(buf, ATTR_DEV_NAME); + wpabuf_put_be16(buf, nlen); + wpabuf_put_data(buf, p2p->cfg->dev_name, nlen); + + /* Update attribute length */ + WPA_PUT_LE16(len, (u8 *) wpabuf_put(buf, 0) - len - 2); + wpa_printf(MSG_DEBUG, "P2P: * Device Info"); +} + + +void p2p_buf_add_device_id(struct wpabuf *buf, const u8 *dev_addr) +{ + /* P2P Device ID */ + wpabuf_put_u8(buf, P2P_ATTR_DEVICE_ID); + wpabuf_put_le16(buf, ETH_ALEN); + wpabuf_put_data(buf, dev_addr, ETH_ALEN); + wpa_printf(MSG_DEBUG, "P2P: * Device ID: " MACSTR, MAC2STR(dev_addr)); +} + + +void p2p_buf_add_config_timeout(struct wpabuf *buf, u8 go_timeout, + u8 client_timeout) +{ + /* Configuration Timeout */ + wpabuf_put_u8(buf, P2P_ATTR_CONFIGURATION_TIMEOUT); + wpabuf_put_le16(buf, 2); + wpabuf_put_u8(buf, go_timeout); + wpabuf_put_u8(buf, client_timeout); + wpa_printf(MSG_DEBUG, "P2P: * Configuration Timeout: GO %d (*10ms) " + "client %d (*10ms)", go_timeout, client_timeout); +} + + +void p2p_buf_add_intended_addr(struct wpabuf *buf, const u8 *interface_addr) +{ + /* Intended P2P Interface Address */ + wpabuf_put_u8(buf, P2P_ATTR_INTENDED_INTERFACE_ADDR); + wpabuf_put_le16(buf, ETH_ALEN); + wpabuf_put_data(buf, interface_addr, ETH_ALEN); + wpa_printf(MSG_DEBUG, "P2P: * Intended P2P Interface Address " MACSTR, + MAC2STR(interface_addr)); +} + + +void p2p_buf_add_group_bssid(struct wpabuf *buf, const u8 *bssid) +{ + /* P2P Group BSSID */ + wpabuf_put_u8(buf, P2P_ATTR_GROUP_BSSID); + wpabuf_put_le16(buf, ETH_ALEN); + wpabuf_put_data(buf, bssid, ETH_ALEN); + wpa_printf(MSG_DEBUG, "P2P: * P2P Group BSSID " MACSTR, + MAC2STR(bssid)); +} + + +void p2p_buf_add_group_id(struct wpabuf *buf, const u8 *dev_addr, + const u8 *ssid, size_t ssid_len) +{ + /* P2P Group ID */ + wpabuf_put_u8(buf, P2P_ATTR_GROUP_ID); + wpabuf_put_le16(buf, ETH_ALEN + ssid_len); + wpabuf_put_data(buf, dev_addr, ETH_ALEN); + wpabuf_put_data(buf, ssid, ssid_len); + wpa_printf(MSG_DEBUG, "P2P: * P2P Group ID " MACSTR, + MAC2STR(dev_addr)); +} + + +void p2p_buf_add_invitation_flags(struct wpabuf *buf, u8 flags) +{ + /* Invitation Flags */ + wpabuf_put_u8(buf, P2P_ATTR_INVITATION_FLAGS); + wpabuf_put_le16(buf, 1); + wpabuf_put_u8(buf, flags); + wpa_printf(MSG_DEBUG, "P2P: * Invitation Flags: bitmap 0x%x", flags); +} + + +static void p2p_buf_add_noa_desc(struct wpabuf *buf, struct p2p_noa_desc *desc) +{ + if (desc == NULL) + return; + + wpabuf_put_u8(buf, desc->count_type); + wpabuf_put_le32(buf, desc->duration); + wpabuf_put_le32(buf, desc->interval); + wpabuf_put_le32(buf, desc->start_time); +} + + +void p2p_buf_add_noa(struct wpabuf *buf, u8 noa_index, u8 opp_ps, u8 ctwindow, + struct p2p_noa_desc *desc1, struct p2p_noa_desc *desc2) +{ + /* Notice of Absence */ + wpabuf_put_u8(buf, P2P_ATTR_NOTICE_OF_ABSENCE); + wpabuf_put_le16(buf, 2 + (desc1 ? 13 : 0) + (desc2 ? 13 : 0)); + wpabuf_put_u8(buf, noa_index); + wpabuf_put_u8(buf, (opp_ps ? 0x80 : 0) | (ctwindow & 0x7f)); + p2p_buf_add_noa_desc(buf, desc1); + p2p_buf_add_noa_desc(buf, desc2); + wpa_printf(MSG_DEBUG, "P2P: * Notice of Absence"); +} + + +void p2p_buf_add_ext_listen_timing(struct wpabuf *buf, u16 period, + u16 interval) +{ + /* Extended Listen Timing */ + wpabuf_put_u8(buf, P2P_ATTR_EXT_LISTEN_TIMING); + wpabuf_put_le16(buf, 4); + wpabuf_put_le16(buf, period); + wpabuf_put_le16(buf, interval); + wpa_printf(MSG_DEBUG, "P2P: * Extended Listen Timing (period %u msec " + "interval %u msec)", period, interval); +} + + +void p2p_buf_add_p2p_interface(struct wpabuf *buf, struct p2p_data *p2p) +{ + /* P2P Interface */ + wpabuf_put_u8(buf, P2P_ATTR_INTERFACE); + wpabuf_put_le16(buf, ETH_ALEN + 1 + ETH_ALEN); + /* P2P Device address */ + wpabuf_put_data(buf, p2p->cfg->dev_addr, ETH_ALEN); + /* + * FIX: Fetch interface address list from driver. Do not include + * the P2P Device address if it is never used as interface address. + */ + /* P2P Interface Address Count */ + wpabuf_put_u8(buf, 1); + wpabuf_put_data(buf, p2p->cfg->dev_addr, ETH_ALEN); +} + + +void p2p_build_wps_ie(struct p2p_data *p2p, struct wpabuf *buf, u16 pw_id, + int all_attr) +{ + u8 *len; + wpabuf_put_u8(buf, WLAN_EID_VENDOR_SPECIFIC); + len = wpabuf_put(buf, 1); + wpabuf_put_be32(buf, WPS_DEV_OUI_WFA); + + wps_build_version(buf); + + if (all_attr) { + wpabuf_put_be16(buf, ATTR_WPS_STATE); + wpabuf_put_be16(buf, 1); + wpabuf_put_u8(buf, WPS_STATE_NOT_CONFIGURED); + } + + /* Device Password ID */ + wpabuf_put_be16(buf, ATTR_DEV_PASSWORD_ID); + wpabuf_put_be16(buf, 2); + wpa_printf(MSG_DEBUG, "P2P: WPS IE Device Password ID: %d", pw_id); + wpabuf_put_be16(buf, pw_id); + + if (all_attr) { + size_t nlen; + + wpabuf_put_be16(buf, ATTR_RESPONSE_TYPE); + wpabuf_put_be16(buf, 1); + wpabuf_put_u8(buf, WPS_RESP_ENROLLEE_INFO); + +#if 0 + /* FIX */ + wps_build_uuid_e(buf, reg->wps->uuid); + wps_build_manufacturer(dev, buf); + wps_build_model_name(dev, buf); + wps_build_model_number(dev, buf); + wps_build_serial_number(dev, buf); +#endif + + wpabuf_put_be16(buf, ATTR_PRIMARY_DEV_TYPE); + wpabuf_put_be16(buf, WPS_DEV_TYPE_LEN); + wpabuf_put_data(buf, p2p->cfg->pri_dev_type, WPS_DEV_TYPE_LEN); + + wpabuf_put_be16(buf, ATTR_DEV_NAME); + nlen = p2p->cfg->dev_name ? os_strlen(p2p->cfg->dev_name) : 0; + wpabuf_put_be16(buf, nlen); + if (p2p->cfg->dev_name) + wpabuf_put_data(buf, p2p->cfg->dev_name, nlen); + + wpabuf_put_be16(buf, ATTR_CONFIG_METHODS); + wpabuf_put_be16(buf, 2); + wpabuf_put_be16(buf, 0); /* FIX: ? */ + } + + wps_build_version2(buf); + + p2p_buf_update_ie_hdr(buf, len); +} diff --git a/src/p2p/p2p_dev_disc.c b/src/p2p/p2p_dev_disc.c new file mode 100644 index 0000000..ac2a177 --- /dev/null +++ b/src/p2p/p2p_dev_disc.c @@ -0,0 +1,357 @@ +/* + * Wi-Fi Direct - P2P Device Discoverability procedure + * Copyright (c) 2010, Atheros Communications + * + * 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 "p2p_i.h" +#include "p2p.h" + + +static struct wpabuf * p2p_build_dev_disc_req(struct p2p_data *p2p, + struct p2p_device *go, + const u8 *dev_id) +{ + struct wpabuf *buf; + u8 *len; + + buf = wpabuf_alloc(100); + if (buf == NULL) + return NULL; + + go->dialog_token++; + if (go->dialog_token == 0) + go->dialog_token = 1; + p2p_buf_add_public_action_hdr(buf, P2P_DEV_DISC_REQ, go->dialog_token); + + len = p2p_buf_add_ie_hdr(buf); + p2p_buf_add_device_id(buf, dev_id); + p2p_buf_add_group_id(buf, go->p2p_device_addr, go->oper_ssid, + go->oper_ssid_len); + p2p_buf_update_ie_hdr(buf, len); + + return buf; +} + + +void p2p_dev_disc_req_cb(struct p2p_data *p2p, int success) +{ + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Device Discoverability Request TX callback: success=%d", + success); + + if (!success) { + /* + * Use P2P find, if needed, to find the other device or to + * retry device discoverability. + */ + p2p_set_state(p2p, P2P_CONNECT); + p2p_set_timeout(p2p, 0, 100000); + return; + } + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: GO acknowledged Device Discoverability Request - wait " + "for response"); + /* + * TODO: is the remain-on-channel from Action frame TX long enough for + * most cases or should we try to increase its duration and/or start + * another remain-on-channel if needed once the previous one expires? + */ +} + + +int p2p_send_dev_disc_req(struct p2p_data *p2p, struct p2p_device *dev) +{ + struct p2p_device *go; + struct wpabuf *req; + + go = p2p_get_device(p2p, dev->member_in_go_dev); + if (go == NULL || dev->oper_freq <= 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Could not find peer entry for GO and frequency " + "to send Device Discoverability Request"); + return -1; + } + + req = p2p_build_dev_disc_req(p2p, go, dev->p2p_device_addr); + if (req == NULL) + return -1; + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Sending Device Discoverability Request to GO " MACSTR + " for client " MACSTR, + MAC2STR(go->p2p_device_addr), MAC2STR(dev->p2p_device_addr)); + + p2p->pending_client_disc_go = go; + os_memcpy(p2p->pending_client_disc_addr, dev->p2p_device_addr, + ETH_ALEN); + p2p->pending_action_state = P2P_PENDING_DEV_DISC_REQUEST; + if (p2p->cfg->send_action(p2p->cfg->cb_ctx, dev->oper_freq, + go->p2p_device_addr, p2p->cfg->dev_addr, + go->p2p_device_addr, + wpabuf_head(req), wpabuf_len(req), 1000) < 0) + { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Failed to send Action frame"); + wpabuf_free(req); + /* TODO: how to recover from failure? */ + return -1; + } + + wpabuf_free(req); + + return 0; +} + + +static struct wpabuf * p2p_build_dev_disc_resp(u8 dialog_token, u8 status) +{ + struct wpabuf *buf; + u8 *len; + + buf = wpabuf_alloc(100); + if (buf == NULL) + return NULL; + + p2p_buf_add_public_action_hdr(buf, P2P_DEV_DISC_RESP, dialog_token); + + len = p2p_buf_add_ie_hdr(buf); + p2p_buf_add_status(buf, status); + p2p_buf_update_ie_hdr(buf, len); + + return buf; +} + + +void p2p_dev_disc_resp_cb(struct p2p_data *p2p, int success) +{ + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Device Discoverability Response TX callback: success=%d", + success); + p2p->cfg->send_action_done(p2p->cfg->cb_ctx); +} + + +static void p2p_send_dev_disc_resp(struct p2p_data *p2p, u8 dialog_token, + const u8 *addr, int freq, u8 status) +{ + struct wpabuf *resp; + + resp = p2p_build_dev_disc_resp(dialog_token, status); + if (resp == NULL) + return; + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Sending Device Discoverability Response to " MACSTR + " (status %u freq %d)", + MAC2STR(addr), status, freq); + + p2p->pending_action_state = P2P_PENDING_DEV_DISC_RESPONSE; + if (p2p->cfg->send_action(p2p->cfg->cb_ctx, freq, addr, + p2p->cfg->dev_addr, p2p->cfg->dev_addr, + wpabuf_head(resp), wpabuf_len(resp), 200) < + 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Failed to send Action frame"); + } + + wpabuf_free(resp); +} + + +void p2p_process_dev_disc_req(struct p2p_data *p2p, const u8 *sa, + const u8 *data, size_t len, int rx_freq) +{ + struct p2p_message msg; + size_t g; + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Received Device Discoverability Request from " MACSTR + " (freq=%d)", MAC2STR(sa), rx_freq); + + if (p2p_parse(data, len, &msg)) + return; + + if (msg.dialog_token == 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Invalid Dialog Token 0 (must be nonzero) in " + "Device Discoverability Request"); + p2p_send_dev_disc_resp(p2p, msg.dialog_token, sa, rx_freq, + P2P_SC_FAIL_INVALID_PARAMS); + p2p_parse_free(&msg); + return; + } + + if (msg.device_id == NULL) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: P2P Device ID attribute missing from Device " + "Discoverability Request"); + p2p_send_dev_disc_resp(p2p, msg.dialog_token, sa, rx_freq, + P2P_SC_FAIL_INVALID_PARAMS); + p2p_parse_free(&msg); + return; + } + + for (g = 0; g < p2p->num_groups; g++) { + if (p2p_group_go_discover(p2p->groups[g], msg.device_id, sa, + rx_freq) == 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Scheduled " + "GO Discoverability Request for the target " + "device"); + /* + * P2P group code will use a callback to indicate TX + * status, so that we can reply to the request once the + * target client has acknowledged the request or it has + * timed out. + */ + p2p->pending_dev_disc_dialog_token = msg.dialog_token; + os_memcpy(p2p->pending_dev_disc_addr, sa, ETH_ALEN); + p2p->pending_dev_disc_freq = rx_freq; + p2p_parse_free(&msg); + return; + } + } + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Requested client " + "was not found in any group or did not support client " + "discoverability"); + p2p_send_dev_disc_resp(p2p, msg.dialog_token, sa, rx_freq, + P2P_SC_FAIL_UNABLE_TO_ACCOMMODATE); + p2p_parse_free(&msg); +} + + +void p2p_process_dev_disc_resp(struct p2p_data *p2p, const u8 *sa, + const u8 *data, size_t len) +{ + struct p2p_message msg; + struct p2p_device *go; + u8 status; + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Received Device Discoverability Response from " MACSTR, + MAC2STR(sa)); + + go = p2p->pending_client_disc_go; + if (go == NULL || os_memcmp(sa, go->p2p_device_addr, ETH_ALEN) != 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Ignore unexpected " + "Device Discoverability Response"); + return; + } + + if (p2p_parse(data, len, &msg)) + return; + + if (msg.status == NULL) { + p2p_parse_free(&msg); + return; + } + + if (msg.dialog_token != go->dialog_token) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Ignore Device " + "Discoverability Response with unexpected dialog " + "token %u (expected %u)", + msg.dialog_token, go->dialog_token); + p2p_parse_free(&msg); + return; + } + + status = *msg.status; + p2p_parse_free(&msg); + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Device Discoverability Response status %u", status); + + if (p2p->go_neg_peer == NULL || + os_memcmp(p2p->pending_client_disc_addr, + p2p->go_neg_peer->p2p_device_addr, ETH_ALEN) != 0 || + os_memcmp(p2p->go_neg_peer->member_in_go_dev, go->p2p_device_addr, + ETH_ALEN) != 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: No pending " + "operation with the client discoverability peer " + "anymore"); + return; + } + + if (status == 0) { + /* + * Peer is expected to be awake for at least 100 TU; try to + * connect immediately. + */ + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Client discoverability request succeeded"); + p2p_set_timeout(p2p, 0, 0); + } else { + /* + * Client discoverability request failed; try to connect from + * timeout. + */ + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Client discoverability request failed"); + p2p_set_timeout(p2p, 0, 500000); + } + +} + + +void p2p_go_disc_req_cb(struct p2p_data *p2p, int success) +{ + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: GO Discoverability Request TX callback: success=%d", + success); + p2p->cfg->send_action_done(p2p->cfg->cb_ctx); + + if (p2p->pending_dev_disc_dialog_token == 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: No pending Device " + "Discoverability Request"); + return; + } + + p2p_send_dev_disc_resp(p2p, p2p->pending_dev_disc_dialog_token, + p2p->pending_dev_disc_addr, + p2p->pending_dev_disc_freq, + success ? P2P_SC_SUCCESS : + P2P_SC_FAIL_UNABLE_TO_ACCOMMODATE); + + p2p->pending_dev_disc_dialog_token = 0; +} + + +void p2p_process_go_disc_req(struct p2p_data *p2p, const u8 *da, const u8 *sa, + const u8 *data, size_t len, int rx_freq) +{ + unsigned int tu; + struct wpabuf *ies; + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Received GO Discoverability Request - remain awake for " + "100 TU"); + + ies = p2p_build_probe_resp_ies(p2p); + if (ies == NULL) + return; + + /* Remain awake 100 TU on operating channel */ + p2p->pending_client_disc_freq = rx_freq; + tu = 100; + if (p2p->cfg->start_listen(p2p->cfg->cb_ctx, rx_freq, 1024 * tu / 1000, + ies) < 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Failed to start listen mode for client " + "discoverability"); + } + wpabuf_free(ies); +} diff --git a/src/p2p/p2p_go_neg.c b/src/p2p/p2p_go_neg.c new file mode 100644 index 0000000..dcd5dbd --- /dev/null +++ b/src/p2p/p2p_go_neg.c @@ -0,0 +1,1064 @@ +/* + * Wi-Fi Direct - P2P Group Owner Negotiation + * Copyright (c) 2009-2010, Atheros Communications + * + * 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 "wps/wps_defs.h" +#include "p2p_i.h" +#include "p2p.h" + + +static int p2p_go_det(u8 own_intent, u8 peer_value) +{ + u8 peer_intent = peer_value >> 1; + if (own_intent == peer_intent) { + if (own_intent == P2P_MAX_GO_INTENT) + return -1; /* both devices want to become GO */ + + /* Use tie breaker bit to determine GO */ + return (peer_value & 0x01) ? 0 : 1; + } + + return own_intent > peer_intent; +} + + +int p2p_peer_channels_check(struct p2p_data *p2p, struct p2p_channels *own, + struct p2p_device *dev, + const u8 *channel_list, size_t channel_list_len) +{ + const u8 *pos, *end; + struct p2p_channels *ch; + size_t channels; + struct p2p_channels intersection; + + ch = &dev->channels; + os_memset(ch, 0, sizeof(*ch)); + pos = channel_list; + end = channel_list + channel_list_len; + + if (end - pos < 3) + return -1; + os_memcpy(dev->country, pos, 3); + wpa_hexdump_ascii(MSG_DEBUG, "P2P: Peer country", pos, 3); + if (pos[3] != 0x04 && os_memcmp(pos, p2p->cfg->country, 2) != 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_INFO, + "P2P: Mismatching country (ours=%c%c peer's=%c%c", + p2p->cfg->country[0], p2p->cfg->country[1], + pos[0], pos[1]); + return -1; + } + pos += 3; + + while (pos + 2 < end) { + struct p2p_reg_class *cl = &ch->reg_class[ch->reg_classes]; + cl->reg_class = *pos++; + if (pos + 1 + pos[0] > end) { + wpa_msg(p2p->cfg->msg_ctx, MSG_INFO, + "P2P: Invalid peer Channel List"); + return -1; + } + channels = *pos++; + cl->channels = channels > P2P_MAX_REG_CLASS_CHANNELS ? + P2P_MAX_REG_CLASS_CHANNELS : channels; + os_memcpy(cl->channel, pos, cl->channels); + pos += channels; + ch->reg_classes++; + if (ch->reg_classes == P2P_MAX_REG_CLASSES) + break; + } + + p2p_channels_intersect(own, &dev->channels, &intersection); + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Own reg_classes %d " + "peer reg_classes %d intersection reg_classes %d", + (int) own->reg_classes, + (int) dev->channels.reg_classes, + (int) intersection.reg_classes); + if (intersection.reg_classes == 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_INFO, + "P2P: No common channels found"); + return -1; + } + return 0; +} + + +static int p2p_peer_channels(struct p2p_data *p2p, struct p2p_device *dev, + const u8 *channel_list, size_t channel_list_len) +{ + return p2p_peer_channels_check(p2p, &p2p->channels, dev, + channel_list, channel_list_len); +} + + +static u16 p2p_wps_method_pw_id(enum p2p_wps_method wps_method) +{ + switch (wps_method) { + case WPS_PIN_LABEL: + return DEV_PW_DEFAULT; + case WPS_PIN_DISPLAY: + return DEV_PW_REGISTRAR_SPECIFIED; + case WPS_PIN_KEYPAD: + return DEV_PW_USER_SPECIFIED; + case WPS_PBC: + return DEV_PW_PUSHBUTTON; + default: + return DEV_PW_DEFAULT; + } +} + + +static const char * p2p_wps_method_str(enum p2p_wps_method wps_method) +{ + switch (wps_method) { + case WPS_PIN_LABEL: + return "Label"; + case WPS_PIN_DISPLAY: + return "Display"; + case WPS_PIN_KEYPAD: + return "Keypad"; + case WPS_PBC: + return "PBC"; + default: + return "??"; + } +} + + +static struct wpabuf * p2p_build_go_neg_req(struct p2p_data *p2p, + struct p2p_device *peer) +{ + struct wpabuf *buf; + u8 *len; + u8 group_capab; + + buf = wpabuf_alloc(1000); + if (buf == NULL) + return NULL; + + peer->dialog_token++; + if (peer->dialog_token == 0) + peer->dialog_token = 1; + p2p_buf_add_public_action_hdr(buf, P2P_GO_NEG_REQ, peer->dialog_token); + + len = p2p_buf_add_ie_hdr(buf); + group_capab = 0; + if (peer->flags & P2P_DEV_PREFER_PERSISTENT_GROUP) + group_capab |= P2P_GROUP_CAPAB_PERSISTENT_GROUP; + p2p_buf_add_capability(buf, p2p->dev_capab, group_capab); + p2p_buf_add_go_intent(buf, (p2p->go_intent << 1) | + p2p->next_tie_breaker); + p2p->next_tie_breaker = !p2p->next_tie_breaker; + p2p_buf_add_config_timeout(buf, 100, 20); + p2p_buf_add_listen_channel(buf, p2p->cfg->country, p2p->cfg->reg_class, + p2p->cfg->channel); + if (p2p->ext_listen_interval) + p2p_buf_add_ext_listen_timing(buf, p2p->ext_listen_period, + p2p->ext_listen_interval); + p2p_buf_add_intended_addr(buf, p2p->intended_addr); + p2p_buf_add_channel_list(buf, p2p->cfg->country, &p2p->channels); + p2p_buf_add_device_info(buf, p2p, peer); + p2p_buf_add_operating_channel(buf, p2p->cfg->country, + p2p->op_reg_class, p2p->op_channel); + p2p_buf_update_ie_hdr(buf, len); + + /* WPS IE with Device Password ID attribute */ + p2p_build_wps_ie(p2p, buf, p2p_wps_method_pw_id(peer->wps_method), 0); + + return buf; +} + + +int p2p_connect_send(struct p2p_data *p2p, struct p2p_device *dev) +{ + struct wpabuf *req; + int freq; + + freq = dev->listen_freq > 0 ? dev->listen_freq : dev->oper_freq; + if (freq <= 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: No Listen/Operating frequency known for the " + "peer " MACSTR " to send GO Negotiation Request", + MAC2STR(dev->p2p_device_addr)); + return -1; + } + + req = p2p_build_go_neg_req(p2p, dev); + if (req == NULL) + return -1; + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Sending GO Negotiation Request"); + p2p_set_state(p2p, P2P_CONNECT); + p2p->pending_action_state = P2P_PENDING_GO_NEG_REQUEST; + p2p->go_neg_peer = dev; + dev->flags |= P2P_DEV_WAIT_GO_NEG_RESPONSE; + if (p2p->cfg->send_action(p2p->cfg->cb_ctx, freq, + dev->p2p_device_addr, p2p->cfg->dev_addr, + dev->p2p_device_addr, + wpabuf_head(req), wpabuf_len(req), 200) < 0) + { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Failed to send Action frame"); + /* Use P2P find to recover and retry */ + p2p_set_timeout(p2p, 0, 0); + } + + wpabuf_free(req); + + return 0; +} + + +static struct wpabuf * p2p_build_go_neg_resp(struct p2p_data *p2p, + struct p2p_device *peer, + u8 dialog_token, u8 status, + u8 tie_breaker) +{ + struct wpabuf *buf; + u8 *len; + u8 group_capab; + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Building GO Negotiation Response"); + buf = wpabuf_alloc(1000); + if (buf == NULL) + return NULL; + + p2p_buf_add_public_action_hdr(buf, P2P_GO_NEG_RESP, dialog_token); + + len = p2p_buf_add_ie_hdr(buf); + p2p_buf_add_status(buf, status); + group_capab = 0; + if (peer && (peer->flags & P2P_DEV_PREFER_PERSISTENT_GROUP)) + group_capab |= P2P_GROUP_CAPAB_PERSISTENT_GROUP; + p2p_buf_add_capability(buf, p2p->dev_capab, group_capab); + p2p_buf_add_go_intent(buf, (p2p->go_intent << 1) | tie_breaker); + p2p_buf_add_config_timeout(buf, 100, 20); + if (peer && peer->go_state == REMOTE_GO) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Omit Operating " + "Channel attribute"); + } else { + p2p_buf_add_operating_channel(buf, p2p->cfg->country, + p2p->op_reg_class, + p2p->op_channel); + } + p2p_buf_add_intended_addr(buf, p2p->intended_addr); + if (status || peer == NULL) { + p2p_buf_add_channel_list(buf, p2p->cfg->country, + &p2p->channels); + } else if (peer->go_state == REMOTE_GO) { + p2p_buf_add_channel_list(buf, p2p->cfg->country, + &p2p->channels); + } else { + struct p2p_channels res; + p2p_channels_intersect(&p2p->channels, &peer->channels, + &res); + p2p_buf_add_channel_list(buf, p2p->cfg->country, &res); + } + p2p_buf_add_device_info(buf, p2p, peer); + if (peer && peer->go_state == LOCAL_GO) { + p2p_buf_add_group_id(buf, p2p->cfg->dev_addr, p2p->ssid, + p2p->ssid_len); + } + p2p_buf_update_ie_hdr(buf, len); + + /* WPS IE with Device Password ID attribute */ + p2p_build_wps_ie(p2p, buf, + p2p_wps_method_pw_id(peer ? peer->wps_method : + WPS_NOT_READY), 0); + + return buf; +} + + +void p2p_process_go_neg_req(struct p2p_data *p2p, const u8 *sa, + const u8 *data, size_t len, int rx_freq) +{ + struct p2p_device *dev = NULL; + struct wpabuf *resp; + struct p2p_message msg; + u8 status = P2P_SC_FAIL_INVALID_PARAMS; + int tie_breaker = 0; + int freq; + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Received GO Negotiation Request from " MACSTR + "(freq=%d)", MAC2STR(sa), rx_freq); + + if (p2p_parse(data, len, &msg)) + return; + + if (!msg.capability) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Mandatory Capability attribute missing from GO " + "Negotiation Request"); +#ifdef CONFIG_P2P_STRICT + goto fail; +#endif /* CONFIG_P2P_STRICT */ + } + + if (msg.go_intent) + tie_breaker = *msg.go_intent & 0x01; + else { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Mandatory GO Intent attribute missing from GO " + "Negotiation Request"); +#ifdef CONFIG_P2P_STRICT + goto fail; +#endif /* CONFIG_P2P_STRICT */ + } + + if (!msg.config_timeout) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Mandatory Configuration Timeout attribute " + "missing from GO Negotiation Request"); +#ifdef CONFIG_P2P_STRICT + goto fail; +#endif /* CONFIG_P2P_STRICT */ + } + + if (!msg.listen_channel) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: No Listen Channel attribute received"); + goto fail; + } + if (!msg.operating_channel) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: No Operating Channel attribute received"); + goto fail; + } + if (!msg.channel_list) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: No Channel List attribute received"); + goto fail; + } + if (!msg.intended_addr) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: No Intended P2P Interface Address attribute " + "received"); + goto fail; + } + if (!msg.p2p_device_info) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: No P2P Device Info attribute received"); + goto fail; + } + + if (os_memcmp(msg.p2p_device_addr, sa, ETH_ALEN) != 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Unexpected GO Negotiation Request SA=" MACSTR + " != dev_addr=" MACSTR, + MAC2STR(sa), MAC2STR(msg.p2p_device_addr)); + goto fail; + } + + dev = p2p_get_device(p2p, sa); + + if (msg.status && *msg.status) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Unexpected Status attribute (%d) in GO " + "Negotiation Request", *msg.status); + goto fail; + } + + if (dev == NULL) + dev = p2p_add_dev_from_go_neg_req(p2p, sa, &msg); + else if (dev->flags & P2P_DEV_PROBE_REQ_ONLY) + p2p_add_dev_info(p2p, sa, dev, &msg); + if (dev && dev->flags & P2P_DEV_USER_REJECTED) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: User has rejected this peer"); + status = P2P_SC_FAIL_REJECTED_BY_USER; + } else if (dev == NULL || dev->wps_method == WPS_NOT_READY) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Not ready for GO negotiation with " MACSTR, + MAC2STR(sa)); + status = P2P_SC_FAIL_INFO_CURRENTLY_UNAVAILABLE; + if (dev) + dev->flags |= P2P_DEV_PEER_WAITING_RESPONSE; + p2p->cfg->go_neg_req_rx(p2p->cfg->cb_ctx, sa); + } else if (p2p->go_neg_peer && p2p->go_neg_peer != dev) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Already in Group Formation with another peer"); + status = P2P_SC_FAIL_UNABLE_TO_ACCOMMODATE; + } else { + int go; + + if (!p2p->go_neg_peer) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Starting " + "GO Negotiation with previously authorized " + "peer"); + /* TODO: check if force_freq is needed */ + p2p->op_reg_class = p2p->cfg->op_reg_class; + p2p->op_channel = p2p->cfg->op_channel; + os_memcpy(&p2p->channels, &p2p->cfg->channels, + sizeof(struct p2p_channels)); + } + + dev->flags &= ~P2P_DEV_NOT_YET_READY; + + if (!msg.go_intent) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: No GO Intent attribute received"); + goto fail; + } + if ((*msg.go_intent >> 1) > P2P_MAX_GO_INTENT) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Invalid GO Intent value (%u) received", + *msg.go_intent >> 1); + goto fail; + } + + if (dev->go_neg_req_sent && + os_memcmp(sa, p2p->cfg->dev_addr, ETH_ALEN) > 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Do not reply since peer has higher " + "address and GO Neg Request already sent"); + p2p_parse_free(&msg); + return; + } + + go = p2p_go_det(p2p->go_intent, *msg.go_intent); + if (go < 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Incompatible GO Intent"); + status = P2P_SC_FAIL_BOTH_GO_INTENT_15; + goto fail; + } + + if (p2p_peer_channels(p2p, dev, msg.channel_list, + msg.channel_list_len) < 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: No common channels found"); + status = P2P_SC_FAIL_NO_COMMON_CHANNELS; + goto fail; + } + + switch (msg.dev_password_id) { + case DEV_PW_DEFAULT: + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: PIN from peer Label"); + if (dev->wps_method != WPS_PIN_KEYPAD) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: We have wps_method=%s -> " + "incompatible", + p2p_wps_method_str(dev->wps_method)); + status = P2P_SC_FAIL_INCOMPATIBLE_PROV_METHOD; + goto fail; + } + break; + case DEV_PW_REGISTRAR_SPECIFIED: + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: PIN from peer Display"); + if (dev->wps_method != WPS_PIN_KEYPAD) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: We have wps_method=%s -> " + "incompatible", + p2p_wps_method_str(dev->wps_method)); + status = P2P_SC_FAIL_INCOMPATIBLE_PROV_METHOD; + goto fail; + } + break; + case DEV_PW_USER_SPECIFIED: + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Peer entered PIN on Keypad"); + if (dev->wps_method != WPS_PIN_LABEL && + dev->wps_method != WPS_PIN_DISPLAY) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: We have wps_method=%s -> " + "incompatible", + p2p_wps_method_str(dev->wps_method)); + status = P2P_SC_FAIL_INCOMPATIBLE_PROV_METHOD; + goto fail; + } + break; + case DEV_PW_PUSHBUTTON: + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Peer using pushbutton"); + if (dev->wps_method != WPS_PBC) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: We have wps_method=%s -> " + "incompatible", + p2p_wps_method_str(dev->wps_method)); + status = P2P_SC_FAIL_INCOMPATIBLE_PROV_METHOD; + goto fail; + } + break; + default: + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Unsupported Device Password ID %d", + msg.dev_password_id); + status = P2P_SC_FAIL_INCOMPATIBLE_PROV_METHOD; + goto fail; + } + + if (go) { + struct p2p_channels intersection; + size_t i; + p2p_channels_intersect(&p2p->channels, &dev->channels, + &intersection); + if (intersection.reg_classes == 0 || + intersection.reg_class[0].channels == 0) { + status = P2P_SC_FAIL_NO_COMMON_CHANNELS; + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: No common channels found"); + goto fail; + } + for (i = 0; i < intersection.reg_classes; i++) { + struct p2p_reg_class *c; + c = &intersection.reg_class[i]; + wpa_printf(MSG_DEBUG, "P2P: reg_class %u", + c->reg_class); + wpa_hexdump(MSG_DEBUG, "P2P: channels", + c->channel, c->channels); + } + if (!p2p_channels_includes(&intersection, + p2p->op_reg_class, + p2p->op_channel)) { + struct p2p_reg_class *cl; + cl = &intersection.reg_class[0]; + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Selected operating channel " + "(reg_class %u channel %u) not " + "acceptable to the peer - pick " + "another channel (reg_class %u " + "channel %u)", + p2p->op_reg_class, p2p->op_channel, + cl->reg_class, cl->channel[0]); + p2p->op_reg_class = cl->reg_class; + p2p->op_channel = cl->channel[0]; + } + + p2p_build_ssid(p2p, p2p->ssid, &p2p->ssid_len); + } + + dev->go_state = go ? LOCAL_GO : REMOTE_GO; + dev->oper_freq = p2p_channel_to_freq((const char *) + msg.operating_channel, + msg.operating_channel[3], + msg.operating_channel[4]); + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Peer operating " + "channel preference: %d MHz", dev->oper_freq); + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: GO Negotiation with " MACSTR, MAC2STR(sa)); + if (p2p->state != P2P_IDLE) + p2p_stop_find(p2p); + p2p_set_state(p2p, P2P_GO_NEG); + p2p_clear_timeout(p2p); + dev->dialog_token = msg.dialog_token; + os_memcpy(dev->intended_addr, msg.intended_addr, ETH_ALEN); + p2p->go_neg_peer = dev; + status = P2P_SC_SUCCESS; + } + +fail: + resp = p2p_build_go_neg_resp(p2p, dev, msg.dialog_token, status, + !tie_breaker); + p2p_parse_free(&msg); + if (resp == NULL) + return; + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Sending GO Negotiation Response"); + if (rx_freq > 0) + freq = rx_freq; + else + freq = p2p_channel_to_freq(p2p->cfg->country, + p2p->cfg->reg_class, + p2p->cfg->channel); + if (freq < 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Unknown regulatory class/channel"); + wpabuf_free(resp); + return; + } + if (status == P2P_SC_SUCCESS) { + p2p->pending_action_state = P2P_PENDING_GO_NEG_RESPONSE; + dev->flags |= P2P_DEV_WAIT_GO_NEG_CONFIRM; + } else + p2p->pending_action_state = + P2P_PENDING_GO_NEG_RESPONSE_FAILURE; + if (p2p->cfg->send_action(p2p->cfg->cb_ctx, freq, sa, + p2p->cfg->dev_addr, p2p->cfg->dev_addr, + wpabuf_head(resp), wpabuf_len(resp), 200) < + 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Failed to send Action frame"); + } + + wpabuf_free(resp); +} + + +static struct wpabuf * p2p_build_go_neg_conf(struct p2p_data *p2p, + struct p2p_device *peer, + u8 dialog_token, u8 status, + const u8 *resp_chan, int go) +{ + struct wpabuf *buf; + u8 *len; + struct p2p_channels res; + u8 group_capab; + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Building GO Negotiation Confirm"); + buf = wpabuf_alloc(1000); + if (buf == NULL) + return NULL; + + p2p_buf_add_public_action_hdr(buf, P2P_GO_NEG_CONF, dialog_token); + + len = p2p_buf_add_ie_hdr(buf); + p2p_buf_add_status(buf, status); + group_capab = 0; + if (peer->flags & P2P_DEV_PREFER_PERSISTENT_GROUP) + group_capab |= P2P_GROUP_CAPAB_PERSISTENT_GROUP; + p2p_buf_add_capability(buf, p2p->dev_capab, group_capab); + if (go || resp_chan == NULL) + p2p_buf_add_operating_channel(buf, p2p->cfg->country, + p2p->op_reg_class, + p2p->op_channel); + else + p2p_buf_add_operating_channel(buf, (const char *) resp_chan, + resp_chan[3], resp_chan[4]); + p2p_channels_intersect(&p2p->channels, &peer->channels, &res); + p2p_buf_add_channel_list(buf, p2p->cfg->country, &res); + if (go) { + p2p_buf_add_group_id(buf, p2p->cfg->dev_addr, p2p->ssid, + p2p->ssid_len); + } + p2p_buf_update_ie_hdr(buf, len); + + return buf; +} + + +void p2p_process_go_neg_resp(struct p2p_data *p2p, const u8 *sa, + const u8 *data, size_t len, int rx_freq) +{ + struct p2p_device *dev; + struct wpabuf *conf; + int go = -1; + struct p2p_message msg; + u8 status = P2P_SC_SUCCESS; + int freq; + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Received GO Negotiation Response from " MACSTR + " (freq=%d)", MAC2STR(sa), rx_freq); + dev = p2p_get_device(p2p, sa); + if (dev == NULL || dev->wps_method == WPS_NOT_READY || + dev != p2p->go_neg_peer) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Not ready for GO negotiation with " MACSTR, + MAC2STR(sa)); + return; + } + + if (p2p_parse(data, len, &msg)) + return; + + if (!(dev->flags & P2P_DEV_WAIT_GO_NEG_RESPONSE)) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Was not expecting GO Negotiation Response - " + "ignore"); + p2p_parse_free(&msg); + return; + } + dev->flags &= ~P2P_DEV_WAIT_GO_NEG_RESPONSE; + + if (msg.dialog_token != dev->dialog_token) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Unexpected Dialog Token %u (expected %u)", + msg.dialog_token, dev->dialog_token); + p2p_parse_free(&msg); + return; + } + + if (!msg.status) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: No Status attribute received"); + status = P2P_SC_FAIL_INVALID_PARAMS; + goto fail; + } + if (*msg.status) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: GO Negotiation rejected: status %d", + *msg.status); + dev->go_neg_req_sent = 0; + if (*msg.status == P2P_SC_FAIL_INFO_CURRENTLY_UNAVAILABLE) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Wait for the peer to become ready for " + "GO Negotiation"); + dev->flags |= P2P_DEV_NOT_YET_READY; + dev->wait_count = 0; + p2p_set_state(p2p, P2P_WAIT_PEER_IDLE); + p2p_set_timeout(p2p, 0, 0); + } else { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Stop GO Negotiation attempt"); + p2p_go_neg_failed(p2p, dev, *msg.status); + } + p2p->cfg->send_action_done(p2p->cfg->cb_ctx); + p2p_parse_free(&msg); + return; + } + + if (!msg.capability) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Mandatory Capability attribute missing from GO " + "Negotiation Response"); +#ifdef CONFIG_P2P_STRICT + status = P2P_SC_FAIL_INVALID_PARAMS; + goto fail; +#endif /* CONFIG_P2P_STRICT */ + } + + if (!msg.p2p_device_info) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Mandatory P2P Device Info attribute missing " + "from GO Negotiation Response"); +#ifdef CONFIG_P2P_STRICT + status = P2P_SC_FAIL_INVALID_PARAMS; + goto fail; +#endif /* CONFIG_P2P_STRICT */ + } + + if (!msg.intended_addr) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: No Intended P2P Interface Address attribute " + "received"); + status = P2P_SC_FAIL_INVALID_PARAMS; + goto fail; + } + + if (!msg.go_intent) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: No GO Intent attribute received"); + status = P2P_SC_FAIL_INVALID_PARAMS; + goto fail; + } + if ((*msg.go_intent >> 1) > P2P_MAX_GO_INTENT) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Invalid GO Intent value (%u) received", + *msg.go_intent >> 1); + status = P2P_SC_FAIL_INVALID_PARAMS; + goto fail; + } + + go = p2p_go_det(p2p->go_intent, *msg.go_intent); + if (go < 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Incompatible GO Intent"); + status = P2P_SC_FAIL_INCOMPATIBLE_PARAMS; + goto fail; + } + + if (!go && msg.group_id) { + /* TODO: Store SSID for Provisioning step */ + } else if (!go) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Mandatory P2P Group ID attribute missing from " + "GO Negotiation Response"); +#ifdef CONFIG_P2P_STRICT + status = P2P_SC_FAIL_INVALID_PARAMS; + goto fail; +#endif /* CONFIG_P2P_STRICT */ + } + + if (!msg.config_timeout) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Mandatory Configuration Timeout attribute " + "missing from GO Negotiation Response"); +#ifdef CONFIG_P2P_STRICT + status = P2P_SC_FAIL_INVALID_PARAMS; + goto fail; +#endif /* CONFIG_P2P_STRICT */ + } + + if (!msg.operating_channel && !go) { + /* + * Note: P2P Client may omit Operating Channel attribute to + * indicate it does not have a preference. + */ + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: No Operating Channel attribute received"); + status = P2P_SC_FAIL_INVALID_PARAMS; + goto fail; + } + if (!msg.channel_list) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: No Channel List attribute received"); + status = P2P_SC_FAIL_INVALID_PARAMS; + goto fail; + } + + if (p2p_peer_channels(p2p, dev, msg.channel_list, + msg.channel_list_len) < 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: No common channels found"); + status = P2P_SC_FAIL_NO_COMMON_CHANNELS; + goto fail; + } + + if (msg.operating_channel) { + dev->oper_freq = p2p_channel_to_freq((const char *) + msg.operating_channel, + msg.operating_channel[3], + msg.operating_channel[4]); + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Peer operating " + "channel preference: %d MHz", dev->oper_freq); + } else + dev->oper_freq = 0; + + switch (msg.dev_password_id) { + case DEV_PW_DEFAULT: + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: PIN from peer Label"); + if (dev->wps_method != WPS_PIN_KEYPAD) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: We have wps_method=%s -> " + "incompatible", + p2p_wps_method_str(dev->wps_method)); + status = P2P_SC_FAIL_INCOMPATIBLE_PROV_METHOD; + goto fail; + } + break; + case DEV_PW_REGISTRAR_SPECIFIED: + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: PIN from peer Display"); + if (dev->wps_method != WPS_PIN_KEYPAD) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: We have wps_method=%s -> " + "incompatible", + p2p_wps_method_str(dev->wps_method)); + status = P2P_SC_FAIL_INCOMPATIBLE_PROV_METHOD; + goto fail; + } + break; + case DEV_PW_USER_SPECIFIED: + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Peer entered PIN on Keypad"); + if (dev->wps_method != WPS_PIN_LABEL && + dev->wps_method != WPS_PIN_DISPLAY) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: We have wps_method=%s -> " + "incompatible", + p2p_wps_method_str(dev->wps_method)); + status = P2P_SC_FAIL_INCOMPATIBLE_PROV_METHOD; + goto fail; + } + break; + case DEV_PW_PUSHBUTTON: + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Peer using pushbutton"); + if (dev->wps_method != WPS_PBC) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: We have wps_method=%s -> " + "incompatible", + p2p_wps_method_str(dev->wps_method)); + status = P2P_SC_FAIL_INCOMPATIBLE_PROV_METHOD; + goto fail; + } + break; + default: + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Unsupported Device Password ID %d", + msg.dev_password_id); + status = P2P_SC_FAIL_INCOMPATIBLE_PROV_METHOD; + goto fail; + } + + if (go) { + struct p2p_channels intersection; + size_t i; + p2p_channels_intersect(&p2p->channels, &dev->channels, + &intersection); + if (intersection.reg_classes == 0 || + intersection.reg_class[0].channels == 0) { + status = P2P_SC_FAIL_NO_COMMON_CHANNELS; + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: No common channels found"); + goto fail; + } + for (i = 0; i < intersection.reg_classes; i++) { + struct p2p_reg_class *c; + c = &intersection.reg_class[i]; + wpa_printf(MSG_DEBUG, "P2P: reg_class %u", + c->reg_class); + wpa_hexdump(MSG_DEBUG, "P2P: channels", + c->channel, c->channels); + } + if (!p2p_channels_includes(&intersection, p2p->op_reg_class, + p2p->op_channel)) { + struct p2p_reg_class *cl; + cl = &intersection.reg_class[0]; + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Selected operating channel " + "(reg_class %u channel %u) not " + "acceptable to the peer - pick " + "another channel (reg_class %u " + "channel %u)", + p2p->op_reg_class, p2p->op_channel, + cl->reg_class, cl->channel[0]); + p2p->op_reg_class = cl->reg_class; + p2p->op_channel = cl->channel[0]; + } + + p2p_build_ssid(p2p, p2p->ssid, &p2p->ssid_len); + } + + p2p_set_state(p2p, P2P_GO_NEG); + p2p_clear_timeout(p2p); + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: GO Negotiation with " MACSTR, MAC2STR(sa)); + os_memcpy(dev->intended_addr, msg.intended_addr, ETH_ALEN); + +fail: + conf = p2p_build_go_neg_conf(p2p, dev, msg.dialog_token, status, + msg.operating_channel, go); + p2p_parse_free(&msg); + if (conf == NULL) + return; + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Sending GO Negotiation Confirm"); + if (status == P2P_SC_SUCCESS) { + p2p->pending_action_state = P2P_PENDING_GO_NEG_CONFIRM; + dev->go_state = go ? LOCAL_GO : REMOTE_GO; + } else + p2p->pending_action_state = P2P_NO_PENDING_ACTION; + if (rx_freq > 0) + freq = rx_freq; + else + freq = dev->listen_freq; + if (p2p->cfg->send_action(p2p->cfg->cb_ctx, freq, sa, + p2p->cfg->dev_addr, sa, + wpabuf_head(conf), wpabuf_len(conf), 200) < + 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Failed to send Action frame"); + p2p_go_neg_failed(p2p, dev, -1); + } + wpabuf_free(conf); +} + + +void p2p_process_go_neg_conf(struct p2p_data *p2p, const u8 *sa, + const u8 *data, size_t len) +{ + struct p2p_device *dev; + struct p2p_message msg; + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Received GO Negotiation Confirm from " MACSTR, + MAC2STR(sa)); + dev = p2p_get_device(p2p, sa); + if (dev == NULL || dev->wps_method == WPS_NOT_READY || + dev != p2p->go_neg_peer) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Not ready for GO negotiation with " MACSTR, + MAC2STR(sa)); + return; + } + + if (p2p->pending_action_state == P2P_PENDING_GO_NEG_RESPONSE) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Stopped waiting " + "for TX status on GO Negotiation Response since we " + "already received Confirmation"); + p2p->pending_action_state = P2P_NO_PENDING_ACTION; + } + + if (p2p_parse(data, len, &msg)) + return; + + if (!(dev->flags & P2P_DEV_WAIT_GO_NEG_CONFIRM)) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Was not expecting GO Negotiation Confirm - " + "ignore"); + return; + } + dev->flags &= ~P2P_DEV_WAIT_GO_NEG_CONFIRM; + + if (msg.dialog_token != dev->dialog_token) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Unexpected Dialog Token %u (expected %u)", + msg.dialog_token, dev->dialog_token); + p2p_parse_free(&msg); + return; + } + + if (!msg.status) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: No Status attribute received"); + p2p_parse_free(&msg); + return; + } + if (*msg.status) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: GO Negotiation rejected: status %d", + *msg.status); + p2p_parse_free(&msg); + return; + } + + if (dev->go_state == REMOTE_GO && msg.group_id) { + /* TODO: Store SSID for Provisioning step */ + } else if (dev->go_state == REMOTE_GO) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Mandatory P2P Group ID attribute missing from " + "GO Negotiation Confirmation"); +#ifdef CONFIG_P2P_STRICT + p2p_parse_free(&msg); + return; +#endif /* CONFIG_P2P_STRICT */ + } + + if (!msg.operating_channel) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Mandatory Operating Channel attribute missing " + "from GO Negotiation Confirmation"); +#ifdef CONFIG_P2P_STRICT + p2p_parse_free(&msg); + return; +#endif /* CONFIG_P2P_STRICT */ + } + + if (!msg.channel_list) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Mandatory Operating Channel attribute missing " + "from GO Negotiation Confirmation"); +#ifdef CONFIG_P2P_STRICT + p2p_parse_free(&msg); + return; +#endif /* CONFIG_P2P_STRICT */ + } + + p2p_parse_free(&msg); + + if (dev->go_state == UNKNOWN_GO) { + /* + * This should not happen since GO negotiation has already + * been completed. + */ + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Unexpected GO Neg state - do not know which end " + "becomes GO"); + return; + } + + p2p_go_complete(p2p, dev); +} diff --git a/src/p2p/p2p_group.c b/src/p2p/p2p_group.c new file mode 100644 index 0000000..ab0f9a1 --- /dev/null +++ b/src/p2p/p2p_group.c @@ -0,0 +1,628 @@ +/* + * Wi-Fi Direct - P2P group operations + * Copyright (c) 2009-2010, Atheros Communications + * + * 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/ieee802_11_common.h" +#include "wps/wps_defs.h" +#include "wps/wps_i.h" +#include "p2p_i.h" +#include "p2p.h" + + +struct p2p_group_member { + struct p2p_group_member *next; + u8 addr[ETH_ALEN]; /* P2P Interface Address */ + u8 dev_addr[ETH_ALEN]; /* P2P Device Address */ + struct wpabuf *p2p_ie; + struct wpabuf *client_info; + u8 dev_capab; +}; + +/** + * struct p2p_group - Internal P2P module per-group data + */ +struct p2p_group { + struct p2p_data *p2p; + struct p2p_group_config *cfg; + struct p2p_group_member *members; + int group_formation; + int beacon_update; + struct wpabuf *noa; +}; + + +static void p2p_group_update_ies(struct p2p_group *group); + + +struct p2p_group * p2p_group_init(struct p2p_data *p2p, + struct p2p_group_config *config) +{ + struct p2p_group *group, **groups; + + group = os_zalloc(sizeof(*group)); + if (group == NULL) + return NULL; + + groups = os_realloc(p2p->groups, (p2p->num_groups + 1) * + sizeof(struct p2p_group *)); + if (groups == NULL) { + os_free(group); + return NULL; + } + groups[p2p->num_groups++] = group; + p2p->groups = groups; + + group->p2p = p2p; + group->cfg = config; + group->group_formation = 1; + group->beacon_update = 1; + p2p_group_update_ies(group); + + return group; +} + + +static void p2p_group_free_member(struct p2p_group_member *m) +{ + wpabuf_free(m->p2p_ie); + wpabuf_free(m->client_info); + os_free(m); +} + + +static void p2p_group_free_members(struct p2p_group *group) +{ + struct p2p_group_member *m, *prev; + m = group->members; + group->members = NULL; + while (m) { + prev = m; + m = m->next; + p2p_group_free_member(prev); + } +} + + +void p2p_group_deinit(struct p2p_group *group) +{ + size_t g; + struct p2p_data *p2p = group->p2p; + + if (group == NULL) + return; + + for (g = 0; g < p2p->num_groups; g++) { + if (p2p->groups[g] == group) { + while (g + 1 < p2p->num_groups) { + p2p->groups[g] = p2p->groups[g + 1]; + g++; + } + p2p->num_groups--; + break; + } + } + + p2p_group_free_members(group); + os_free(group->cfg); + wpabuf_free(group->noa); + os_free(group); +} + + +static void p2p_client_info(struct wpabuf *ie, struct p2p_group_member *m) +{ + if (wpabuf_tailroom(ie) < wpabuf_len(m->client_info) + 1) + return; + wpabuf_put_buf(ie, m->client_info); +} + + +static void p2p_group_add_common_ies(struct p2p_group *group, + struct wpabuf *ie) +{ + u8 dev_capab = 0, group_capab = 0; + + /* P2P Capability */ + dev_capab |= P2P_DEV_CAPAB_SERVICE_DISCOVERY; + dev_capab |= P2P_DEV_CAPAB_INVITATION_PROCEDURE; + group_capab |= P2P_GROUP_CAPAB_GROUP_OWNER; + if (group->cfg->persistent_group) + group_capab |= P2P_GROUP_CAPAB_PERSISTENT_GROUP; + group_capab |= P2P_GROUP_CAPAB_INTRA_BSS_DIST; + if (group->group_formation) + group_capab |= P2P_GROUP_CAPAB_GROUP_FORMATION; + p2p_buf_add_capability(ie, dev_capab, group_capab); +} + + +static void p2p_group_add_noa(struct wpabuf *ie, struct wpabuf *noa) +{ + if (noa == NULL) + return; + /* Notice of Absence */ + wpabuf_put_u8(ie, P2P_ATTR_NOTICE_OF_ABSENCE); + wpabuf_put_le16(ie, wpabuf_len(noa)); + wpabuf_put_buf(ie, noa); +} + + +static struct wpabuf * p2p_group_build_beacon_ie(struct p2p_group *group) +{ + struct wpabuf *ie; + u8 *len; + + ie = wpabuf_alloc(257); + if (ie == NULL) + return NULL; + + len = p2p_buf_add_ie_hdr(ie); + p2p_group_add_common_ies(group, ie); + p2p_buf_add_device_id(ie, group->p2p->cfg->dev_addr); + p2p_group_add_noa(ie, group->noa); + p2p_buf_update_ie_hdr(ie, len); + + return ie; +} + + +static struct wpabuf * p2p_group_build_probe_resp_ie(struct p2p_group *group) +{ + u8 *group_info; + struct wpabuf *ie; + struct p2p_group_member *m; + u8 *len; + + ie = wpabuf_alloc(257); + if (ie == NULL) + return NULL; + + len = p2p_buf_add_ie_hdr(ie); + + p2p_group_add_common_ies(group, ie); + p2p_group_add_noa(ie, group->noa); + + /* P2P Device Info */ + p2p_buf_add_device_info(ie, group->p2p, NULL); + + if (group->members) { + /* P2P Group Info */ + group_info = wpabuf_put(ie, 0); + wpabuf_put_u8(ie, P2P_ATTR_GROUP_INFO); + wpabuf_put_le16(ie, 0); /* Length to be filled */ + for (m = group->members; m; m = m->next) + p2p_client_info(ie, m); + WPA_PUT_LE16(group_info + 1, + (u8 *) wpabuf_put(ie, 0) - group_info - 3); + } + + p2p_buf_update_ie_hdr(ie, len); + return ie; +} + + +static void p2p_group_update_ies(struct p2p_group *group) +{ + struct wpabuf *beacon_ie; + struct wpabuf *probe_resp_ie; + + probe_resp_ie = p2p_group_build_probe_resp_ie(group); + if (probe_resp_ie == NULL) + return; + wpa_hexdump_buf(MSG_MSGDUMP, "P2P: Update GO Probe Response P2P IE", + probe_resp_ie); + + if (group->beacon_update) { + beacon_ie = p2p_group_build_beacon_ie(group); + if (beacon_ie) + group->beacon_update = 0; + wpa_hexdump_buf(MSG_MSGDUMP, "P2P: Update GO Beacon P2P IE", + beacon_ie); + } else + beacon_ie = NULL; + + group->cfg->ie_update(group->cfg->cb_ctx, beacon_ie, probe_resp_ie); +} + + +/** + * p2p_build_client_info - Build P2P Client Info Descriptor + * @addr: MAC address of the peer device + * @p2p_ie: P2P IE from (Re)Association Request + * @dev_capab: Buffer for returning Device Capability + * @dev_addr: Buffer for returning P2P Device Address + * Returns: P2P Client Info Descriptor or %NULL on failure + * + * This function builds P2P Client Info Descriptor based on the information + * available from (Re)Association Request frame. Group owner can use this to + * build the P2P Group Info attribute for Probe Response frames. + */ +static struct wpabuf * p2p_build_client_info(const u8 *addr, + struct wpabuf *p2p_ie, + u8 *dev_capab, u8 *dev_addr) +{ + const u8 *spos; + struct p2p_message msg; + u8 *len_pos; + struct wpabuf *buf; + + if (p2p_ie == NULL) + return NULL; + + os_memset(&msg, 0, sizeof(msg)); + if (p2p_parse_p2p_ie(p2p_ie, &msg) || + msg.capability == NULL || msg.p2p_device_info == NULL) + return NULL; + + buf = wpabuf_alloc(ETH_ALEN + 1 + 1 + msg.p2p_device_info_len); + if (buf == NULL) + return NULL; + + *dev_capab = msg.capability[0]; + os_memcpy(dev_addr, msg.p2p_device_addr, ETH_ALEN); + + spos = msg.p2p_device_info; /* P2P Device address */ + + /* P2P Client Info Descriptor */ + /* Length to be set */ + len_pos = wpabuf_put(buf, 1); + /* P2P Device address */ + wpabuf_put_data(buf, spos, ETH_ALEN); + /* P2P Interface address */ + wpabuf_put_data(buf, addr, ETH_ALEN); + /* Device Capability Bitmap */ + wpabuf_put_u8(buf, msg.capability[0]); + /* + * Config Methods, Primary Device Type, Number of Secondary Device + * Types, Secondary Device Type List, Device Name copied from + * Device Info + */ + wpabuf_put_data(buf, spos + ETH_ALEN, + msg.p2p_device_info_len - ETH_ALEN); + + *len_pos = wpabuf_len(buf) - 1; + + + return buf; +} + + +int p2p_group_notif_assoc(struct p2p_group *group, const u8 *addr, + const u8 *ie, size_t len) +{ + struct p2p_group_member *m; + + if (group == NULL) + return -1; + + m = os_zalloc(sizeof(*m)); + if (m == NULL) + return -1; + os_memcpy(m->addr, addr, ETH_ALEN); + m->p2p_ie = ieee802_11_vendor_ie_concat(ie, len, P2P_IE_VENDOR_TYPE); + if (m->p2p_ie == NULL) { + p2p_group_free_member(m); + return -1; + } + + m->client_info = p2p_build_client_info(addr, m->p2p_ie, &m->dev_capab, + m->dev_addr); + if (m->client_info == NULL) { + p2p_group_free_member(m); + return -1; + } + + m->next = group->members; + group->members = m; + + p2p_group_update_ies(group); + + return 0; +} + + +struct wpabuf * p2p_group_assoc_resp_ie(struct p2p_group *group, u8 status) +{ + struct wpabuf *resp; + u8 *rlen; + + /* + * (Re)Association Response - P2P IE + * Status attribute (shall be present when association request is + * denied) + * Extended Listen Timing (may be present) + */ + resp = wpabuf_alloc(20); + if (resp == NULL) + return NULL; + rlen = p2p_buf_add_ie_hdr(resp); + if (status != P2P_SC_SUCCESS) + p2p_buf_add_status(resp, status); + p2p_buf_update_ie_hdr(resp, rlen); + + return resp; +} + + +void p2p_group_notif_disassoc(struct p2p_group *group, const u8 *addr) +{ + struct p2p_group_member *m, *prev; + + if (group == NULL) + return; + + m = group->members; + prev = NULL; + while (m) { + if (os_memcmp(m->addr, addr, ETH_ALEN) == 0) + break; + prev = m; + m = m->next; + } + + if (m) { + if (prev) + prev->next = m->next; + else + group->members = m->next; + p2p_group_free_member(m); + p2p_group_update_ies(group); + } +} + + +/** + * p2p_match_dev_type_member - Match client device type with requested type + * @m: Group member + * @wps: WPS TLVs from Probe Request frame (concatenated WPS IEs) + * Returns: 1 on match, 0 on mismatch + * + * This function can be used to match the Requested Device Type attribute in + * WPS IE with the device types of a group member for deciding whether a GO + * should reply to a Probe Request frame. + */ +static int p2p_match_dev_type_member(struct p2p_group_member *m, + struct wpabuf *wps) +{ + const u8 *pos, *end; + struct wps_parse_attr attr; + u8 num_sec; + + if (m->client_info == NULL || wps == NULL) + return 0; + + pos = wpabuf_head(m->client_info); + end = pos + wpabuf_len(m->client_info); + + pos += 1 + 2 * ETH_ALEN + 1 + 2; + if (end - pos < WPS_DEV_TYPE_LEN + 1) + return 0; + + if (wps_parse_msg(wps, &attr)) + return 1; /* assume no Requested Device Type attributes */ + + if (attr.num_req_dev_type == 0) + return 1; /* no Requested Device Type attributes -> match */ + + if (dev_type_list_match(pos, attr.req_dev_type, attr.num_req_dev_type)) + return 1; /* Match with client Primary Device Type */ + + pos += WPS_DEV_TYPE_LEN; + num_sec = *pos++; + if (end - pos < num_sec * WPS_DEV_TYPE_LEN) + return 0; + while (num_sec > 0) { + num_sec--; + if (dev_type_list_match(pos, attr.req_dev_type, + attr.num_req_dev_type)) + return 1; /* Match with client Secondary Device Type */ + pos += WPS_DEV_TYPE_LEN; + } + + /* No matching device type found */ + return 0; +} + + +int p2p_group_match_dev_type(struct p2p_group *group, struct wpabuf *wps) +{ + struct p2p_group_member *m; + + if (p2p_match_dev_type(group->p2p, wps)) + return 1; /* Match with own device type */ + + for (m = group->members; m; m = m->next) { + if (p2p_match_dev_type_member(m, wps)) + return 1; /* Match with group client device type */ + } + + /* No match with Requested Device Type */ + return 0; +} + + +void p2p_group_notif_formation_done(struct p2p_group *group) +{ + if (group == NULL) + return; + group->group_formation = 0; + group->beacon_update = 1; + p2p_group_update_ies(group); +} + + +int p2p_group_notif_noa(struct p2p_group *group, const u8 *noa, + size_t noa_len) +{ + if (noa == NULL) { + wpabuf_free(group->noa); + group->noa = NULL; + } else { + if (group->noa) { + if (wpabuf_size(group->noa) >= noa_len) { + group->noa->size = 0; + wpabuf_put_data(group->noa, noa, noa_len); + } else { + wpabuf_free(group->noa); + group->noa = NULL; + } + } + + if (!group->noa) { + group->noa = wpabuf_alloc_copy(noa, noa_len); + if (group->noa == NULL) + return -1; + } + } + + group->beacon_update = 1; + p2p_group_update_ies(group); + return 0; +} + + +static struct p2p_group_member * p2p_group_get_client(struct p2p_group *group, + const u8 *dev_id) +{ + struct p2p_group_member *m; + + for (m = group->members; m; m = m->next) { + if (os_memcmp(dev_id, m->dev_addr, ETH_ALEN) == 0) + return m; + } + + return NULL; +} + + +static struct p2p_group_member * p2p_group_get_client_iface( + struct p2p_group *group, const u8 *interface_addr) +{ + struct p2p_group_member *m; + + for (m = group->members; m; m = m->next) { + if (os_memcmp(interface_addr, m->addr, ETH_ALEN) == 0) + return m; + } + + return NULL; +} + + +static struct wpabuf * p2p_build_go_disc_req(void) +{ + struct wpabuf *buf; + + buf = wpabuf_alloc(100); + if (buf == NULL) + return NULL; + + p2p_buf_add_action_hdr(buf, P2P_GO_DISC_REQ, 0); + + return buf; +} + + +int p2p_group_go_discover(struct p2p_group *group, const u8 *dev_id, + const u8 *searching_dev, int rx_freq) +{ + struct p2p_group_member *m; + struct wpabuf *req; + struct p2p_data *p2p = group->p2p; + int freq; + + m = p2p_group_get_client(group, dev_id); + if (m == NULL) { + wpa_printf(MSG_DEBUG, "P2P: Requested client was not in this " + "group " MACSTR, + MAC2STR(group->cfg->interface_addr)); + return -1; + } + + if (!(m->dev_capab & P2P_DEV_CAPAB_CLIENT_DISCOVERABILITY)) { + wpa_printf(MSG_DEBUG, "P2P: Requested client does not support " + "client discoverability"); + return -1; + } + + wpa_printf(MSG_DEBUG, "P2P: Schedule GO Discoverability Request to be " + "sent to " MACSTR, MAC2STR(dev_id)); + + req = p2p_build_go_disc_req(); + if (req == NULL) + return -1; + + /* TODO: Should really use group operating frequency here */ + freq = rx_freq; + + p2p->pending_action_state = P2P_PENDING_GO_DISC_REQ; + if (p2p->cfg->send_action(p2p->cfg->cb_ctx, freq, m->addr, + group->cfg->interface_addr, + group->cfg->interface_addr, + wpabuf_head(req), wpabuf_len(req), 200) < 0) + { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Failed to send Action frame"); + } + + wpabuf_free(req); + + return 0; +} + + +const u8 * p2p_group_get_interface_addr(struct p2p_group *group) +{ + return group->cfg->interface_addr; +} + + +u8 p2p_group_presence_req(struct p2p_group *group, + const u8 *client_interface_addr, + const u8 *noa, size_t noa_len) +{ + struct p2p_group_member *m; + u8 curr_noa[50]; + int curr_noa_len; + + m = p2p_group_get_client_iface(group, client_interface_addr); + if (m == NULL) { + wpa_printf(MSG_DEBUG, "P2P: Client was not in this group"); + return P2P_SC_FAIL_UNABLE_TO_ACCOMMODATE; + } + + wpa_hexdump(MSG_DEBUG, "P2P: Presence Request NoA", noa, noa_len); + + if (group->p2p->cfg->get_noa) + curr_noa_len = group->p2p->cfg->get_noa( + group->p2p->cfg->cb_ctx, group->cfg->interface_addr, + curr_noa, sizeof(curr_noa)); + else + curr_noa_len = -1; + if (curr_noa_len < 0) + wpa_printf(MSG_DEBUG, "P2P: Failed to fetch current NoA"); + else if (curr_noa_len == 0) + wpa_printf(MSG_DEBUG, "P2P: No NoA being advertized"); + else + wpa_hexdump(MSG_DEBUG, "P2P: Current NoA", curr_noa, + curr_noa_len); + + /* TODO: properly process request and store copy */ + if (curr_noa_len > 0) + return P2P_SC_FAIL_UNABLE_TO_ACCOMMODATE; + + return P2P_SC_SUCCESS; +} diff --git a/src/p2p/p2p_i.h b/src/p2p/p2p_i.h new file mode 100644 index 0000000..ccd46d3 --- /dev/null +++ b/src/p2p/p2p_i.h @@ -0,0 +1,580 @@ +/* + * P2P - Internal definitions for P2P module + * Copyright (c) 2009-2010, Atheros Communications + * + * 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 P2P_I_H +#define P2P_I_H + +#include "utils/list.h" +#include "p2p.h" + +/* TODO: add removal of expired P2P device entries */ + +enum p2p_go_state { + UNKNOWN_GO, + LOCAL_GO, + REMOTE_GO +}; + +/** + * struct p2p_device - P2P Device data (internal to P2P module) + */ +struct p2p_device { + struct dl_list list; + struct os_time last_seen; + int listen_freq; + int level; + enum p2p_wps_method wps_method; + + u8 p2p_device_addr[ETH_ALEN]; /* P2P Device Address of the peer */ + u8 pri_dev_type[8]; + char device_name[33]; + u16 config_methods; + u8 dev_capab; + u8 group_capab; + + /* + * If the peer was discovered based on an interface address (e.g., GO + * from Beacon/Probe Response), the interface address is stored here. + * p2p_device_addr must still be set in such a case to the unique + * identifier for the P2P Device. + */ + u8 interface_addr[ETH_ALEN]; + + /* + * P2P Device Address of the GO in whose group this P2P Device is a + * client. + */ + u8 member_in_go_dev[ETH_ALEN]; + + /* + * P2P Interface Address of the GO in whose group this P2P Device is a + * client. + */ + u8 member_in_go_iface[ETH_ALEN]; + + int go_neg_req_sent; + enum p2p_go_state go_state; + u8 dialog_token; + u8 intended_addr[ETH_ALEN]; + + char country[3]; + struct p2p_channels channels; + int oper_freq; + u8 oper_ssid[32]; + size_t oper_ssid_len; + + /** + * req_config_methods - Pending provisioning discovery methods + */ + u16 req_config_methods; + +#define P2P_DEV_PROBE_REQ_ONLY BIT(0) +#define P2P_DEV_REPORTED BIT(1) +#define P2P_DEV_NOT_YET_READY BIT(2) +#define P2P_DEV_SD_INFO BIT(3) +#define P2P_DEV_SD_SCHEDULE BIT(4) +#define P2P_DEV_PD_PEER_DISPLAY BIT(5) +#define P2P_DEV_PD_PEER_KEYPAD BIT(6) +#define P2P_DEV_USER_REJECTED BIT(7) +#define P2P_DEV_PEER_WAITING_RESPONSE BIT(8) +#define P2P_DEV_PREFER_PERSISTENT_GROUP BIT(9) +#define P2P_DEV_WAIT_GO_NEG_RESPONSE BIT(10) +#define P2P_DEV_WAIT_GO_NEG_CONFIRM BIT(11) +#define P2P_DEV_GROUP_CLIENT_ONLY BIT(12) + unsigned int flags; + + int status; /* enum p2p_status_code */ + unsigned int wait_count; + unsigned int invitation_reqs; + + u16 ext_listen_period; + u16 ext_listen_interval; +}; + +struct p2p_sd_query { + struct p2p_sd_query *next; + u8 peer[ETH_ALEN]; + int for_all_peers; + struct wpabuf *tlvs; +}; + +/** + * struct p2p_data - P2P module data (internal to P2P module) + */ +struct p2p_data { + /** + * cfg - P2P module configuration + * + * This is included in the same memory allocation with the + * struct p2p_data and as such, must not be freed separately. + */ + struct p2p_config *cfg; + + /** + * state - The current P2P state + */ + enum p2p_state { + /** + * P2P_IDLE - Idle + */ + P2P_IDLE, + + /** + * P2P_SEARCH - Search (Device Discovery) + */ + P2P_SEARCH, + + /** + * P2P_CONNECT - Trying to start GO Negotiation + */ + P2P_CONNECT, + + /** + * P2P_CONNECT_LISTEN - Listen during GO Negotiation start + */ + P2P_CONNECT_LISTEN, + + /** + * P2P_GO_NEG - In GO Negotiation + */ + P2P_GO_NEG, + + /** + * P2P_LISTEN_ONLY - Listen only + */ + P2P_LISTEN_ONLY, + + /** + * P2P_WAIT_PEER_CONNECT - Waiting peer in List for GO Neg + */ + P2P_WAIT_PEER_CONNECT, + + /** + * P2P_WAIT_PEER_IDLE - Waiting peer idle for GO Neg + */ + P2P_WAIT_PEER_IDLE, + + /** + * P2P_SD_DURING_FIND - Service Discovery during find + */ + P2P_SD_DURING_FIND, + + /** + * P2P_PROVISIONING - Provisioning (during group formation) + */ + P2P_PROVISIONING, + + /** + * P2P_PD_DURING_FIND - Provision Discovery during find + */ + P2P_PD_DURING_FIND, + + /** + * P2P_INVITE - Trying to start Invite + */ + P2P_INVITE, + + /** + * P2P_INVITE_LISTEN - Listen during Invite + */ + P2P_INVITE_LISTEN, + } state; + + /** + * min_disc_int - minDiscoverableInterval + */ + int min_disc_int; + + /** + * max_disc_int - maxDiscoverableInterval + */ + int max_disc_int; + + /** + * devices - List of known P2P Device peers + */ + struct dl_list devices; + + /** + * go_neg_peer - Pointer to GO Negotiation peer + */ + struct p2p_device *go_neg_peer; + + /** + * invite_peer - Pointer to Invite peer + */ + struct p2p_device *invite_peer; + + const u8 *invite_go_dev_addr; + u8 invite_go_dev_addr_buf[ETH_ALEN]; + + /** + * sd_peer - Pointer to Service Discovery peer + */ + struct p2p_device *sd_peer; + + /** + * sd_query - Pointer to Service Discovery query + */ + struct p2p_sd_query *sd_query; + + /* GO Negotiation data */ + + /** + * intended_addr - Local Intended P2P Interface Address + * + * This address is used during group owner negotiation as the Intended + * P2P Interface Address and the group interface will be created with + * address as the local address in case of successfully completed + * negotiation. + */ + u8 intended_addr[ETH_ALEN]; + + /** + * go_intent - Local GO Intent to be used during GO Negotiation + */ + u8 go_intent; + + /** + * next_tie_breaker - Next tie-breaker value to use in GO Negotiation + */ + u8 next_tie_breaker; + + /** + * ssid - Selected SSID for GO Negotiation (if local end will be GO) + */ + u8 ssid[32]; + + /** + * ssid_len - ssid length in octets + */ + size_t ssid_len; + + /** + * Regulatory class for own operational channel + */ + u8 op_reg_class; + + /** + * op_channel - Own operational channel + */ + u8 op_channel; + + /** + * channels - Own supported regulatory classes and channels + * + * List of supposerted channels per regulatory class. The regulatory + * classes are defined in IEEE Std 802.11-2007 Annex J and the + * numbering of the clases depends on the configured country code. + */ + struct p2p_channels channels; + + enum p2p_pending_action_state { + P2P_NO_PENDING_ACTION, + P2P_PENDING_GO_NEG_REQUEST, + P2P_PENDING_GO_NEG_RESPONSE, + P2P_PENDING_GO_NEG_RESPONSE_FAILURE, + P2P_PENDING_GO_NEG_CONFIRM, + P2P_PENDING_SD, + P2P_PENDING_PD, + P2P_PENDING_INVITATION_REQUEST, + P2P_PENDING_INVITATION_RESPONSE, + P2P_PENDING_DEV_DISC_REQUEST, + P2P_PENDING_DEV_DISC_RESPONSE, + P2P_PENDING_GO_DISC_REQ + } pending_action_state; + + unsigned int pending_listen_freq; + unsigned int pending_listen_sec; + unsigned int pending_listen_usec; + + u8 dev_capab; + + int in_listen; + int drv_in_listen; + + /** + * sd_queries - Pending service discovery queries + */ + struct p2p_sd_query *sd_queries; + + /** + * srv_update_indic - Service Update Indicator for local services + */ + u16 srv_update_indic; + + /* P2P Invitation data */ + enum p2p_invite_role inv_role; + u8 inv_bssid[ETH_ALEN]; + int inv_bssid_set; + u8 inv_ssid[32]; + size_t inv_ssid_len; + u8 inv_sa[ETH_ALEN]; + u8 inv_group_bssid[ETH_ALEN]; + u8 *inv_group_bssid_ptr; + u8 inv_go_dev_addr[ETH_ALEN]; + u8 inv_status; + int inv_op_freq; + int inv_persistent; + + enum p2p_discovery_type find_type; + u8 last_prog_scan_class; + u8 last_prog_scan_chan; + int p2p_scan_running; + enum p2p_after_scan { + P2P_AFTER_SCAN_NOTHING, + P2P_AFTER_SCAN_LISTEN, + P2P_AFTER_SCAN_CONNECT + } start_after_scan; + u8 after_scan_peer[ETH_ALEN]; + + struct p2p_group **groups; + size_t num_groups; + + struct p2p_device *pending_client_disc_go; + u8 pending_client_disc_addr[ETH_ALEN]; + u8 pending_dev_disc_dialog_token; + u8 pending_dev_disc_addr[ETH_ALEN]; + int pending_dev_disc_freq; + unsigned int pending_client_disc_freq; + + int ext_listen_only; + unsigned int ext_listen_period; + unsigned int ext_listen_interval; + unsigned int ext_listen_interval_sec; + unsigned int ext_listen_interval_usec; +}; + +/** + * struct p2p_message - Parsed P2P message (or P2P IE) + */ +struct p2p_message { + struct wpabuf *p2p_attributes; + struct wpabuf *wps_attributes; + + u8 dialog_token; + + const u8 *capability; + const u8 *go_intent; + const u8 *status; + const u8 *listen_channel; + const u8 *operating_channel; + const u8 *channel_list; + u8 channel_list_len; + const u8 *config_timeout; + const u8 *intended_addr; + const u8 *group_bssid; + const u8 *invitation_flags; + + const u8 *group_info; + size_t group_info_len; + + const u8 *group_id; + size_t group_id_len; + + const u8 *device_id; + + const u8 *manageability; + + const u8 *noa; + size_t noa_len; + + const u8 *ext_listen_timing; + + const u8 *minor_reason_code; + + /* P2P Device Info */ + const u8 *p2p_device_info; + size_t p2p_device_info_len; + const u8 *p2p_device_addr; + const u8 *pri_dev_type; + u8 num_sec_dev_types; + char device_name[33]; + u16 config_methods; + + /* WPS IE */ + u16 dev_password_id; + u16 wps_config_methods; + const u8 *wps_pri_dev_type; + + /* DS Parameter Set IE */ + const u8 *ds_params; + + /* SSID IE */ + const u8 *ssid; +}; + + +#define P2P_MAX_GROUP_ENTRIES 50 + +struct p2p_group_info { + unsigned int num_clients; + struct p2p_client_info { + const u8 *p2p_device_addr; + const u8 *p2p_interface_addr; + u8 dev_capab; + u16 config_methods; + const u8 *pri_dev_type; + u8 num_sec_dev_types; + const u8 *sec_dev_types; + const char *dev_name; + size_t dev_name_len; + } client[P2P_MAX_GROUP_ENTRIES]; +}; + + +/* p2p_utils.c */ +int p2p_random(char *buf, size_t len); +int p2p_channel_to_freq(const char *country, int reg_class, int channel); +int p2p_freq_to_channel(const char *country, unsigned int freq, u8 *reg_class, + u8 *channel); +void p2p_channels_intersect(const struct p2p_channels *a, + const struct p2p_channels *b, + struct p2p_channels *res); +int p2p_channels_includes(const struct p2p_channels *channels, u8 reg_class, + u8 channel); + +/* p2p_parse.c */ +int p2p_parse_p2p_ie(const struct wpabuf *buf, struct p2p_message *msg); +int p2p_parse_ies(const u8 *data, size_t len, struct p2p_message *msg); +int p2p_parse(const u8 *data, size_t len, struct p2p_message *msg); +void p2p_parse_free(struct p2p_message *msg); +int p2p_attr_text(struct wpabuf *data, char *buf, char *end); +int p2p_group_info_parse(const u8 *gi, size_t gi_len, + struct p2p_group_info *info); + +/* p2p_build.c */ + +struct p2p_noa_desc { + u8 count_type; + u32 duration; + u32 interval; + u32 start_time; +}; + +/* p2p_group.c */ +const u8 * p2p_group_get_interface_addr(struct p2p_group *group); +u8 p2p_group_presence_req(struct p2p_group *group, + const u8 *client_interface_addr, + const u8 *noa, size_t noa_len); + + +void p2p_buf_add_action_hdr(struct wpabuf *buf, u8 subtype, u8 dialog_token); +void p2p_buf_add_public_action_hdr(struct wpabuf *buf, u8 subtype, + u8 dialog_token); +u8 * p2p_buf_add_ie_hdr(struct wpabuf *buf); +void p2p_buf_add_status(struct wpabuf *buf, u8 status); +void p2p_buf_add_device_info(struct wpabuf *buf, struct p2p_data *p2p, + struct p2p_device *peer); +void p2p_buf_add_device_id(struct wpabuf *buf, const u8 *dev_addr); +void p2p_buf_update_ie_hdr(struct wpabuf *buf, u8 *len); +void p2p_buf_add_capability(struct wpabuf *buf, u8 dev_capab, u8 group_capab); +void p2p_buf_add_go_intent(struct wpabuf *buf, u8 go_intent); +void p2p_buf_add_listen_channel(struct wpabuf *buf, const char *country, + u8 reg_class, u8 channel); +void p2p_buf_add_operating_channel(struct wpabuf *buf, const char *country, + u8 reg_class, u8 channel); +void p2p_buf_add_channel_list(struct wpabuf *buf, const char *country, + struct p2p_channels *chan); +void p2p_buf_add_config_timeout(struct wpabuf *buf, u8 go_timeout, + u8 client_timeout); +void p2p_buf_add_intended_addr(struct wpabuf *buf, const u8 *interface_addr); +void p2p_buf_add_group_bssid(struct wpabuf *buf, const u8 *bssid); +void p2p_buf_add_group_id(struct wpabuf *buf, const u8 *dev_addr, + const u8 *ssid, size_t ssid_len); +void p2p_buf_add_invitation_flags(struct wpabuf *buf, u8 flags); +void p2p_buf_add_noa(struct wpabuf *buf, u8 noa_index, u8 opp_ps, u8 ctwindow, + struct p2p_noa_desc *desc1, struct p2p_noa_desc *desc2); +void p2p_buf_add_ext_listen_timing(struct wpabuf *buf, u16 period, + u16 interval); +void p2p_buf_add_p2p_interface(struct wpabuf *buf, struct p2p_data *p2p); +void p2p_build_wps_ie(struct p2p_data *p2p, struct wpabuf *buf, u16 pw_id, + int all_attr); + +/* p2p_sd.c */ +struct p2p_sd_query * p2p_pending_sd_req(struct p2p_data *p2p, + struct p2p_device *dev); +void p2p_free_sd_queries(struct p2p_data *p2p); +void p2p_rx_gas_initial_req(struct p2p_data *p2p, const u8 *sa, + const u8 *data, size_t len, int rx_freq); +void p2p_rx_gas_initial_resp(struct p2p_data *p2p, const u8 *sa, + const u8 *data, size_t len); +int p2p_start_sd(struct p2p_data *p2p, struct p2p_device *dev); + +/* p2p_go_neg.c */ +int p2p_peer_channels_check(struct p2p_data *p2p, struct p2p_channels *own, + struct p2p_device *dev, + const u8 *channel_list, size_t channel_list_len); +void p2p_process_go_neg_req(struct p2p_data *p2p, const u8 *sa, + const u8 *data, size_t len, int rx_freq); +void p2p_process_go_neg_resp(struct p2p_data *p2p, const u8 *sa, + const u8 *data, size_t len, int rx_freq); +void p2p_process_go_neg_conf(struct p2p_data *p2p, const u8 *sa, + const u8 *data, size_t len); +int p2p_connect_send(struct p2p_data *p2p, struct p2p_device *dev); + +/* p2p_pd.c */ +void p2p_process_prov_disc_req(struct p2p_data *p2p, const u8 *sa, + const u8 *data, size_t len, int rx_freq); +void p2p_process_prov_disc_resp(struct p2p_data *p2p, const u8 *sa, + const u8 *data, size_t len); +int p2p_send_prov_disc_req(struct p2p_data *p2p, struct p2p_device *dev, + int join); + +/* p2p_invitation.c */ +void p2p_process_invitation_req(struct p2p_data *p2p, const u8 *sa, + const u8 *data, size_t len, int rx_freq); +void p2p_process_invitation_resp(struct p2p_data *p2p, const u8 *sa, + const u8 *data, size_t len); +int p2p_invite_send(struct p2p_data *p2p, struct p2p_device *dev, + const u8 *go_dev_addr); +void p2p_invitation_req_cb(struct p2p_data *p2p, int success); +void p2p_invitation_resp_cb(struct p2p_data *p2p, int success); + +/* p2p_dev_disc.c */ +void p2p_process_dev_disc_req(struct p2p_data *p2p, const u8 *sa, + const u8 *data, size_t len, int rx_freq); +void p2p_dev_disc_req_cb(struct p2p_data *p2p, int success); +int p2p_send_dev_disc_req(struct p2p_data *p2p, struct p2p_device *dev); +void p2p_dev_disc_resp_cb(struct p2p_data *p2p, int success); +void p2p_process_dev_disc_resp(struct p2p_data *p2p, const u8 *sa, + const u8 *data, size_t len); +void p2p_go_disc_req_cb(struct p2p_data *p2p, int success); +void p2p_process_go_disc_req(struct p2p_data *p2p, const u8 *da, const u8 *sa, + const u8 *data, size_t len, int rx_freq); + +/* p2p.c */ +void p2p_set_state(struct p2p_data *p2p, int new_state); +void p2p_set_timeout(struct p2p_data *p2p, unsigned int sec, + unsigned int usec); +void p2p_clear_timeout(struct p2p_data *p2p); +void p2p_continue_find(struct p2p_data *p2p); +struct p2p_device * p2p_add_dev_from_go_neg_req(struct p2p_data *p2p, + const u8 *addr, + struct p2p_message *msg); +void p2p_add_dev_info(struct p2p_data *p2p, const u8 *addr, + struct p2p_device *dev, struct p2p_message *msg); +struct p2p_device * p2p_get_device(struct p2p_data *p2p, const u8 *addr); +struct p2p_device * p2p_get_device_interface(struct p2p_data *p2p, + const u8 *addr); +void p2p_go_neg_failed(struct p2p_data *p2p, struct p2p_device *peer, + int status); +void p2p_go_complete(struct p2p_data *p2p, struct p2p_device *peer); +int p2p_match_dev_type(struct p2p_data *p2p, struct wpabuf *wps); +int dev_type_list_match(const u8 *dev_type, const u8 *req_dev_type[], + size_t num_req_dev_type); +struct wpabuf * p2p_build_probe_resp_ies(struct p2p_data *p2p); +void p2p_build_ssid(struct p2p_data *p2p, u8 *ssid, size_t *ssid_len); + +#endif /* P2P_I_H */ diff --git a/src/p2p/p2p_invitation.c b/src/p2p/p2p_invitation.c new file mode 100644 index 0000000..89662b2 --- /dev/null +++ b/src/p2p/p2p_invitation.c @@ -0,0 +1,474 @@ +/* + * Wi-Fi Direct - P2P Invitation procedure + * Copyright (c) 2010, Atheros Communications + * + * 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 "p2p_i.h" +#include "p2p.h" + + +static struct wpabuf * p2p_build_invitation_req(struct p2p_data *p2p, + struct p2p_device *peer, + const u8 *go_dev_addr) +{ + struct wpabuf *buf; + u8 *len; + const u8 *dev_addr; + + buf = wpabuf_alloc(1000); + if (buf == NULL) + return NULL; + + peer->dialog_token++; + if (peer->dialog_token == 0) + peer->dialog_token = 1; + p2p_buf_add_public_action_hdr(buf, P2P_INVITATION_REQ, + peer->dialog_token); + + len = p2p_buf_add_ie_hdr(buf); + if (p2p->inv_role == P2P_INVITE_ROLE_ACTIVE_GO) + p2p_buf_add_config_timeout(buf, 0, 0); + else + p2p_buf_add_config_timeout(buf, 100, 20); + p2p_buf_add_operating_channel(buf, p2p->cfg->country, + p2p->op_reg_class, p2p->op_channel); + if (p2p->inv_bssid_set) + p2p_buf_add_group_bssid(buf, p2p->inv_bssid); + p2p_buf_add_channel_list(buf, p2p->cfg->country, &p2p->channels); + if (go_dev_addr) + dev_addr = go_dev_addr; + else if (p2p->inv_role == P2P_INVITE_ROLE_CLIENT) + dev_addr = peer->p2p_device_addr; + else + dev_addr = p2p->cfg->dev_addr; + p2p_buf_add_group_id(buf, dev_addr, p2p->inv_ssid, p2p->inv_ssid_len); + p2p_buf_add_invitation_flags(buf, p2p->inv_persistent ? + P2P_INVITATION_FLAGS_TYPE : 0); + p2p_buf_update_ie_hdr(buf, len); + + return buf; +} + + +static struct wpabuf * p2p_build_invitation_resp(struct p2p_data *p2p, + struct p2p_device *peer, + u8 dialog_token, u8 status, + const u8 *group_bssid, + u8 reg_class, u8 channel, + struct p2p_channels *channels) +{ + struct wpabuf *buf; + u8 *len; + + buf = wpabuf_alloc(1000); + if (buf == NULL) + return NULL; + + p2p_buf_add_public_action_hdr(buf, P2P_INVITATION_RESP, + dialog_token); + + len = p2p_buf_add_ie_hdr(buf); + p2p_buf_add_status(buf, status); + p2p_buf_add_config_timeout(buf, 0, 0); /* FIX */ + if (reg_class && channel) + p2p_buf_add_operating_channel(buf, p2p->cfg->country, + reg_class, channel); + if (group_bssid) + p2p_buf_add_group_bssid(buf, group_bssid); + if (channels) + p2p_buf_add_channel_list(buf, p2p->cfg->country, channels); + p2p_buf_update_ie_hdr(buf, len); + + return buf; +} + + +void p2p_process_invitation_req(struct p2p_data *p2p, const u8 *sa, + const u8 *data, size_t len, int rx_freq) +{ + struct p2p_device *dev; + struct p2p_message msg; + struct wpabuf *resp = NULL; + u8 status = P2P_SC_FAIL_INFO_CURRENTLY_UNAVAILABLE; + int freq; + int go = 0; + u8 group_bssid[ETH_ALEN], *bssid; + int op_freq = 0; + u8 reg_class = 0, channel = 0; + struct p2p_channels intersection, *channels = NULL; + int persistent; + + os_memset(group_bssid, 0, sizeof(group_bssid)); + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Received Invitation Request from " MACSTR " (freq=%d)", + MAC2STR(sa), rx_freq); + + if (p2p_parse(data, len, &msg)) + return; + + dev = p2p_get_device(p2p, sa); + if (dev == NULL) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Reject Invitation Request from unknown peer " + MACSTR, MAC2STR(sa)); + status = P2P_SC_FAIL_INFO_CURRENTLY_UNAVAILABLE; + goto fail; + } + + if (!msg.group_id || !msg.channel_list) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Mandatory attribute missing in Invitation " + "Request from " MACSTR, MAC2STR(sa)); + status = P2P_SC_FAIL_INVALID_PARAMS; + goto fail; + } + + if (msg.invitation_flags) + persistent = *msg.invitation_flags & P2P_INVITATION_FLAGS_TYPE; + else { + /* Invitation Flags is a mandatory attribute starting from P2P + * spec 1.06. As a backwards compatibility mechanism, assume + * the request was for a persistent group if the attribute is + * missing. + */ + wpa_printf(MSG_DEBUG, "P2P: Mandatory Invitation Flags " + "attribute missing from Invitation Request"); + persistent = 1; + } + + if (p2p_peer_channels_check(p2p, &p2p->cfg->channels, dev, + msg.channel_list, msg.channel_list_len) < + 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: No common channels found"); + status = P2P_SC_FAIL_NO_COMMON_CHANNELS; + goto fail; + } + + if (p2p->cfg->invitation_process) { + status = p2p->cfg->invitation_process( + p2p->cfg->cb_ctx, sa, msg.group_bssid, msg.group_id, + msg.group_id + ETH_ALEN, msg.group_id_len - ETH_ALEN, + &go, group_bssid, &op_freq, persistent); + } + + if (op_freq) { + if (p2p_freq_to_channel(p2p->cfg->country, op_freq, + ®_class, &channel) < 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Unknown forced freq %d MHz from " + "invitation_process()", op_freq); + status = P2P_SC_FAIL_NO_COMMON_CHANNELS; + goto fail; + } + + p2p_channels_intersect(&p2p->cfg->channels, &dev->channels, + &intersection); + if (!p2p_channels_includes(&intersection, reg_class, channel)) + { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: forced freq %d MHz not in the supported " + "channels interaction", op_freq); + status = P2P_SC_FAIL_NO_COMMON_CHANNELS; + goto fail; + } + + if (status == P2P_SC_SUCCESS) + channels = &intersection; + } else { + op_freq = p2p_channel_to_freq(p2p->cfg->country, + p2p->cfg->op_reg_class, + p2p->cfg->op_channel); + if (op_freq < 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Unknown operational channel " + "(country=%c%c reg_class=%u channel=%u)", + p2p->cfg->country[0], p2p->cfg->country[1], + p2p->cfg->op_reg_class, p2p->cfg->op_channel); + status = P2P_SC_FAIL_NO_COMMON_CHANNELS; + goto fail; + } + + p2p_channels_intersect(&p2p->cfg->channels, &dev->channels, + &intersection); + if (status == P2P_SC_SUCCESS) { + reg_class = p2p->cfg->op_reg_class; + channel = p2p->cfg->op_channel; + channels = &intersection; + } + } + +fail: + if (go && status == P2P_SC_SUCCESS && !is_zero_ether_addr(group_bssid)) + bssid = group_bssid; + else + bssid = NULL; + resp = p2p_build_invitation_resp(p2p, dev, msg.dialog_token, status, + bssid, reg_class, channel, channels); + + if (resp == NULL) + goto out; + + if (rx_freq > 0) + freq = rx_freq; + else + freq = p2p_channel_to_freq(p2p->cfg->country, + p2p->cfg->reg_class, + p2p->cfg->channel); + if (freq < 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Unknown regulatory class/channel"); + goto out; + } + + /* + * Store copy of invitation data to be used when processing TX status + * callback for the Acton frame. + */ + os_memcpy(p2p->inv_sa, sa, ETH_ALEN); + if (msg.group_bssid) { + os_memcpy(p2p->inv_group_bssid, msg.group_bssid, ETH_ALEN); + p2p->inv_group_bssid_ptr = p2p->inv_group_bssid; + } else + p2p->inv_group_bssid_ptr = NULL; + if (msg.group_id_len - ETH_ALEN <= 32) { + os_memcpy(p2p->inv_ssid, msg.group_id + ETH_ALEN, + msg.group_id_len - ETH_ALEN); + p2p->inv_ssid_len = msg.group_id_len - ETH_ALEN; + } + os_memcpy(p2p->inv_go_dev_addr, msg.group_id, ETH_ALEN); + p2p->inv_status = status; + p2p->inv_op_freq = op_freq; + + p2p->pending_action_state = P2P_PENDING_INVITATION_RESPONSE; + if (p2p->cfg->send_action(p2p->cfg->cb_ctx, freq, sa, + p2p->cfg->dev_addr, p2p->cfg->dev_addr, + wpabuf_head(resp), wpabuf_len(resp), 200) < + 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Failed to send Action frame"); + } + +out: + wpabuf_free(resp); + p2p_parse_free(&msg); +} + + +void p2p_process_invitation_resp(struct p2p_data *p2p, const u8 *sa, + const u8 *data, size_t len) +{ + struct p2p_device *dev; + struct p2p_message msg; + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Received Invitation Response from " MACSTR, + MAC2STR(sa)); + + dev = p2p_get_device(p2p, sa); + if (dev == NULL) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Ignore Invitation Response from unknown peer " + MACSTR, MAC2STR(sa)); + return; + } + + if (dev != p2p->invite_peer) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Ignore unexpected Invitation Response from peer " + MACSTR, MAC2STR(sa)); + return; + } + + if (p2p_parse(data, len, &msg)) + return; + + if (!msg.status) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Mandatory Status attribute missing in " + "Invitation Response from " MACSTR, MAC2STR(sa)); + p2p_parse_free(&msg); + return; + } + + if (p2p->cfg->invitation_result) + p2p->cfg->invitation_result(p2p->cfg->cb_ctx, *msg.status, + msg.group_bssid); + + p2p_parse_free(&msg); + + p2p_clear_timeout(p2p); + p2p_set_state(p2p, P2P_IDLE); + p2p->invite_peer = NULL; +} + + +int p2p_invite_send(struct p2p_data *p2p, struct p2p_device *dev, + const u8 *go_dev_addr) +{ + struct wpabuf *req; + int freq; + + freq = dev->listen_freq > 0 ? dev->listen_freq : dev->oper_freq; + if (freq <= 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: No Listen/Operating frequency known for the " + "peer " MACSTR " to send Invitation Request", + MAC2STR(dev->p2p_device_addr)); + return -1; + } + + req = p2p_build_invitation_req(p2p, dev, go_dev_addr); + if (req == NULL) + return -1; + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Sending Invitation Request"); + p2p_set_state(p2p, P2P_INVITE); + p2p->pending_action_state = P2P_PENDING_INVITATION_REQUEST; + p2p->invite_peer = dev; + dev->invitation_reqs++; + if (p2p->cfg->send_action(p2p->cfg->cb_ctx, freq, + dev->p2p_device_addr, p2p->cfg->dev_addr, + dev->p2p_device_addr, + wpabuf_head(req), wpabuf_len(req), 200) < 0) + { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Failed to send Action frame"); + /* Use P2P find to recover and retry */ + p2p_set_timeout(p2p, 0, 0); + } + + wpabuf_free(req); + + return 0; +} + + +void p2p_invitation_req_cb(struct p2p_data *p2p, int success) +{ + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Invitation Request TX callback: success=%d", success); + + if (p2p->invite_peer == NULL) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: No pending Invite"); + return; + } + + /* + * Use P2P find, if needed, to find the other device from its listen + * channel. + */ + p2p_set_state(p2p, P2P_INVITE); + p2p_set_timeout(p2p, 0, 100000); +} + + +void p2p_invitation_resp_cb(struct p2p_data *p2p, int success) +{ + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Invitation Response TX callback: success=%d", success); + p2p->cfg->send_action_done(p2p->cfg->cb_ctx); + + if (success && p2p->cfg->invitation_received) { + p2p->cfg->invitation_received(p2p->cfg->cb_ctx, + p2p->inv_sa, + p2p->inv_group_bssid, + p2p->inv_ssid, p2p->inv_ssid_len, + p2p->inv_go_dev_addr, + p2p->inv_status, + p2p->inv_op_freq); + } +} + + +int p2p_invite(struct p2p_data *p2p, const u8 *peer, enum p2p_invite_role role, + const u8 *bssid, const u8 *ssid, size_t ssid_len, + unsigned int force_freq, const u8 *go_dev_addr, + int persistent_group) +{ + struct p2p_device *dev; + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Request to invite peer " MACSTR " role=%d", + MAC2STR(peer), role); + if (bssid) + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Invitation for BSSID " MACSTR, MAC2STR(bssid)); + if (go_dev_addr) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Invitation for GO Device Address " MACSTR, + MAC2STR(go_dev_addr)); + os_memcpy(p2p->invite_go_dev_addr_buf, go_dev_addr, ETH_ALEN); + p2p->invite_go_dev_addr = p2p->invite_go_dev_addr_buf; + } else + p2p->invite_go_dev_addr = NULL; + wpa_hexdump_ascii(MSG_DEBUG, "P2P: Invitation for SSID", + ssid, ssid_len); + + dev = p2p_get_device(p2p, peer); + if (dev == NULL || (dev->listen_freq <= 0 && dev->oper_freq <= 0)) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Cannot invite unknown P2P Device " MACSTR, + MAC2STR(peer)); + return -1; + } + + if (dev->flags & P2P_DEV_GROUP_CLIENT_ONLY) { + if (!(dev->dev_capab & P2P_DEV_CAPAB_CLIENT_DISCOVERABILITY)) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Cannot invite a P2P Device " MACSTR + " that is in a group and is not discoverable", + MAC2STR(peer)); + } + /* TODO: use device discoverability request through GO */ + } + + dev->invitation_reqs = 0; + + if (force_freq) { + if (p2p_freq_to_channel(p2p->cfg->country, force_freq, + &p2p->op_reg_class, &p2p->op_channel) < + 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Unsupported frequency %u MHz", + force_freq); + return -1; + } + p2p->channels.reg_classes = 1; + p2p->channels.reg_class[0].channels = 1; + p2p->channels.reg_class[0].reg_class = p2p->op_reg_class; + p2p->channels.reg_class[0].channel[0] = p2p->op_channel; + } else { + p2p->op_reg_class = p2p->cfg->op_reg_class; + p2p->op_channel = p2p->cfg->op_channel; + os_memcpy(&p2p->channels, &p2p->cfg->channels, + sizeof(struct p2p_channels)); + } + + if (p2p->state != P2P_IDLE) + p2p_stop_find(p2p); + + p2p->inv_role = role; + p2p->inv_bssid_set = bssid != NULL; + if (bssid) + os_memcpy(p2p->inv_bssid, bssid, ETH_ALEN); + os_memcpy(p2p->inv_ssid, ssid, ssid_len); + p2p->inv_ssid_len = ssid_len; + p2p->inv_persistent = persistent_group; + return p2p_invite_send(p2p, dev, go_dev_addr); +} diff --git a/src/p2p/p2p_parse.c b/src/p2p/p2p_parse.c new file mode 100644 index 0000000..07b9b51 --- /dev/null +++ b/src/p2p/p2p_parse.c @@ -0,0 +1,682 @@ +/* + * P2P - IE parser + * Copyright (c) 2009-2010, Atheros Communications + * + * 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/ieee802_11_common.h" +#include "wps/wps_i.h" +#include "p2p_i.h" + + +static int p2p_parse_attribute(u8 id, const u8 *data, u16 len, + struct p2p_message *msg) +{ + const u8 *pos; + size_t i, nlen; + char devtype[WPS_DEV_TYPE_BUFSIZE]; + + switch (id) { + case P2P_ATTR_CAPABILITY: + if (len < 2) { + wpa_printf(MSG_DEBUG, "P2P: Too short Capability " + "attribute (length %d)", len); + return -1; + } + msg->capability = data; + wpa_printf(MSG_DEBUG, "P2P: * Device Capability %02x " + "Group Capability %02x", + data[0], data[1]); + break; + case P2P_ATTR_DEVICE_ID: + if (len < ETH_ALEN) { + wpa_printf(MSG_DEBUG, "P2P: Too short Device ID " + "attribute (length %d)", len); + return -1; + } + msg->device_id = data; + wpa_printf(MSG_DEBUG, "P2P: * Device ID " MACSTR, + MAC2STR(msg->device_id)); + break; + case P2P_ATTR_GROUP_OWNER_INTENT: + if (len < 1) { + wpa_printf(MSG_DEBUG, "P2P: Too short GO Intent " + "attribute (length %d)", len); + return -1; + } + msg->go_intent = data; + wpa_printf(MSG_DEBUG, "P2P: * GO Intent: Intent %u " + "Tie breaker %u", data[0] >> 1, data[0] & 0x01); + break; + case P2P_ATTR_STATUS: + if (len < 1) { + wpa_printf(MSG_DEBUG, "P2P: Too short Status " + "attribute (length %d)", len); + return -1; + } + msg->status = data; + wpa_printf(MSG_DEBUG, "P2P: * Status: %d", data[0]); + break; + case P2P_ATTR_LISTEN_CHANNEL: + if (len == 0) { + wpa_printf(MSG_DEBUG, "P2P: * Listen Channel: Ignore " + "null channel"); + break; + } + if (len < 5) { + wpa_printf(MSG_DEBUG, "P2P: Too short Listen Channel " + "attribute (length %d)", len); + return -1; + } + msg->listen_channel = data; + wpa_printf(MSG_DEBUG, "P2P: * Listen Channel: " + "Country %c%c(0x%02x) Regulatory " + "Class %d Channel Number %d", data[0], data[1], + data[2], data[3], data[4]); + break; + case P2P_ATTR_OPERATING_CHANNEL: + if (len == 0) { + wpa_printf(MSG_DEBUG, "P2P: * Operating Channel: " + "Ignore null channel"); + break; + } + if (len < 5) { + wpa_printf(MSG_DEBUG, "P2P: Too short Operating " + "Channel attribute (length %d)", len); + return -1; + } + msg->operating_channel = data; + wpa_printf(MSG_DEBUG, "P2P: * Operating Channel: " + "Country %c%c(0x%02x) Regulatory " + "Class %d Channel Number %d", data[0], data[1], + data[2], data[3], data[4]); + break; + case P2P_ATTR_CHANNEL_LIST: + if (len < 3) { + wpa_printf(MSG_DEBUG, "P2P: Too short Channel List " + "attribute (length %d)", len); + return -1; + } + msg->channel_list = data; + msg->channel_list_len = len; + wpa_printf(MSG_DEBUG, "P2P: * Channel List: Country String " + "'%c%c(0x%02x)'", data[0], data[1], data[2]); + wpa_hexdump(MSG_MSGDUMP, "P2P: Channel List", + msg->channel_list, msg->channel_list_len); + break; + case P2P_ATTR_GROUP_INFO: + msg->group_info = data; + msg->group_info_len = len; + wpa_printf(MSG_DEBUG, "P2P: * Group Info"); + break; + case P2P_ATTR_DEVICE_INFO: + if (len < ETH_ALEN + 2 + 8 + 1) { + wpa_printf(MSG_DEBUG, "P2P: Too short Device Info " + "attribute (length %d)", len); + return -1; + } + msg->p2p_device_info = data; + msg->p2p_device_info_len = len; + pos = data; + msg->p2p_device_addr = pos; + pos += ETH_ALEN; + msg->config_methods = WPA_GET_BE16(pos); + pos += 2; + msg->pri_dev_type = pos; + pos += 8; + msg->num_sec_dev_types = *pos++; + if (msg->num_sec_dev_types * 8 > data + len - pos) { + wpa_printf(MSG_DEBUG, "P2P: Device Info underflow"); + return -1; + } + pos += msg->num_sec_dev_types * 8; + if (data + len - pos < 4) { + wpa_printf(MSG_DEBUG, "P2P: Invalid Device Name " + "length %d", (int) (data + len - pos)); + return -1; + } + if (WPA_GET_BE16(pos) != ATTR_DEV_NAME) { + wpa_hexdump(MSG_DEBUG, "P2P: Unexpected Device Name " + "header", pos, 4); + return -1; + } + pos += 2; + nlen = WPA_GET_BE16(pos); + pos += 2; + if (data + len - pos < (int) nlen || nlen > 32) { + wpa_printf(MSG_DEBUG, "P2P: Invalid Device Name " + "length %d (buf len %d)", (int) nlen, + (int) (data + len - pos)); + return -1; + } + os_memcpy(msg->device_name, pos, nlen); + for (i = 0; i < nlen; i++) { + if (msg->device_name[i] == '\0') + break; + if (msg->device_name[i] < 32) + msg->device_name[i] = '_'; + } + wpa_printf(MSG_DEBUG, "P2P: * Device Info: addr " MACSTR + " primary device type %s device name '%s' " + "config methods 0x%x", + MAC2STR(msg->p2p_device_addr), + wps_dev_type_bin2str(msg->pri_dev_type, devtype, + sizeof(devtype)), + msg->device_name, msg->config_methods); + break; + case P2P_ATTR_CONFIGURATION_TIMEOUT: + if (len < 2) { + wpa_printf(MSG_DEBUG, "P2P: Too short Configuration " + "Timeout attribute (length %d)", len); + return -1; + } + msg->config_timeout = data; + wpa_printf(MSG_DEBUG, "P2P: * Configuration Timeout"); + break; + case P2P_ATTR_INTENDED_INTERFACE_ADDR: + if (len < ETH_ALEN) { + wpa_printf(MSG_DEBUG, "P2P: Too short Intended P2P " + "Interface Address attribute (length %d)", + len); + return -1; + } + msg->intended_addr = data; + wpa_printf(MSG_DEBUG, "P2P: * Intended P2P Interface Address: " + MACSTR, MAC2STR(msg->intended_addr)); + break; + case P2P_ATTR_GROUP_BSSID: + if (len < ETH_ALEN) { + wpa_printf(MSG_DEBUG, "P2P: Too short P2P Group BSSID " + "attribute (length %d)", len); + return -1; + } + msg->group_bssid = data; + wpa_printf(MSG_DEBUG, "P2P: * P2P Group BSSID: " MACSTR, + MAC2STR(msg->group_bssid)); + break; + case P2P_ATTR_GROUP_ID: + if (len < ETH_ALEN || len > ETH_ALEN + 32) { + wpa_printf(MSG_DEBUG, "P2P: Invalid P2P Group ID " + "attribute length %d", len); + return -1; + } + msg->group_id = data; + msg->group_id_len = len; + wpa_printf(MSG_DEBUG, "P2P: * P2P Group ID: Device Address " + MACSTR, MAC2STR(msg->group_id)); + wpa_hexdump_ascii(MSG_DEBUG, "P2P: * P2P Group ID: SSID", + msg->group_id + ETH_ALEN, + msg->group_id_len - ETH_ALEN); + break; + case P2P_ATTR_INVITATION_FLAGS: + if (len < 1) { + wpa_printf(MSG_DEBUG, "P2P: Too short Invitation " + "Flag attribute (length %d)", len); + return -1; + } + msg->invitation_flags = data; + wpa_printf(MSG_DEBUG, "P2P: * Invitation Flags: bitmap 0x%x", + data[0]); + break; + case P2P_ATTR_MANAGEABILITY: + if (len < 1) { + wpa_printf(MSG_DEBUG, "P2P: Too short Manageability " + "attribute (length %d)", len); + return -1; + } + msg->manageability = data; + wpa_printf(MSG_DEBUG, "P2P: * Manageability: bitmap 0x%x", + data[0]); + break; + case P2P_ATTR_NOTICE_OF_ABSENCE: + if (len < 2) { + wpa_printf(MSG_DEBUG, "P2P: Too short Notice of " + "Absence attribute (length %d)", len); + return -1; + } + msg->noa = data; + msg->noa_len = len; + wpa_printf(MSG_DEBUG, "P2P: * Notice of Absence"); + break; + case P2P_ATTR_EXT_LISTEN_TIMING: + if (len < 4) { + wpa_printf(MSG_DEBUG, "P2P: Too short Extended Listen " + "Timing attribute (length %d)", len); + return -1; + } + msg->ext_listen_timing = data; + wpa_printf(MSG_DEBUG, "P2P: * Extended Listen Timing " + "(period %u msec interval %u msec)", + WPA_GET_LE16(msg->ext_listen_timing), + WPA_GET_LE16(msg->ext_listen_timing + 2)); + break; + case P2P_ATTR_MINOR_REASON_CODE: + if (len < 1) { + wpa_printf(MSG_DEBUG, "P2P: Too short Minor Reason " + "Code attribute (length %d)", len); + return -1; + } + msg->minor_reason_code = data; + wpa_printf(MSG_DEBUG, "P2P: * Minor Reason Code: %u", + *msg->minor_reason_code); + break; + default: + wpa_printf(MSG_DEBUG, "P2P: Skipped unknown attribute %d " + "(length %d)", id, len); + break; + } + + return 0; +} + + +/** + * p2p_parse_p2p_ie - Parse P2P IE + * @buf: Concatenated P2P IE(s) payload + * @msg: Buffer for returning parsed attributes + * Returns: 0 on success, -1 on failure + * + * Note: Caller is responsible for clearing the msg data structure before + * calling this function. + */ +int p2p_parse_p2p_ie(const struct wpabuf *buf, struct p2p_message *msg) +{ + const u8 *pos = wpabuf_head_u8(buf); + const u8 *end = pos + wpabuf_len(buf); + + wpa_printf(MSG_DEBUG, "P2P: Parsing P2P IE"); + + while (pos < end) { + u16 attr_len; + if (pos + 2 >= end) { + wpa_printf(MSG_DEBUG, "P2P: Invalid P2P attribute"); + return -1; + } + attr_len = WPA_GET_LE16(pos + 1); + wpa_printf(MSG_DEBUG, "P2P: Attribute %d length %u", + pos[0], attr_len); + if (pos + 3 + attr_len > end) { + wpa_printf(MSG_DEBUG, "P2P: Attribute underflow " + "(len=%u left=%d)", + attr_len, (int) (end - pos - 3)); + wpa_hexdump(MSG_MSGDUMP, "P2P: Data", pos, end - pos); + return -1; + } + if (p2p_parse_attribute(pos[0], pos + 3, attr_len, msg)) + return -1; + pos += 3 + attr_len; + } + + return 0; +} + + +static int p2p_parse_wps_ie(const struct wpabuf *buf, struct p2p_message *msg) +{ + struct wps_parse_attr attr; + + wpa_printf(MSG_DEBUG, "P2P: Parsing WPS IE"); + if (wps_parse_msg(buf, &attr)) + return -1; + if (attr.dev_name && attr.dev_name_len < sizeof(msg->device_name) && + !msg->device_name[0]) + os_memcpy(msg->device_name, attr.dev_name, attr.dev_name_len); + if (attr.config_methods) { + msg->wps_config_methods = + WPA_GET_BE16(attr.config_methods); + wpa_printf(MSG_DEBUG, "P2P: Config Methods (WPS): 0x%x", + msg->wps_config_methods); + } + if (attr.dev_password_id) { + msg->dev_password_id = WPA_GET_BE16(attr.dev_password_id); + wpa_printf(MSG_DEBUG, "P2P: Device Password ID: %d", + msg->dev_password_id); + } + if (attr.primary_dev_type) { + char devtype[WPS_DEV_TYPE_BUFSIZE]; + msg->wps_pri_dev_type = attr.primary_dev_type; + wpa_printf(MSG_DEBUG, "P2P: Primary Device Type (WPS): %s", + wps_dev_type_bin2str(msg->wps_pri_dev_type, devtype, + sizeof(devtype))); + } + + return 0; +} + + +/** + * p2p_parse_ies - Parse P2P message IEs (both WPS and P2P IE) + * @data: IEs from the message + * @len: Length of data buffer in octets + * @msg: Buffer for returning parsed attributes + * Returns: 0 on success, -1 on failure + * + * Note: Caller is responsible for clearing the msg data structure before + * calling this function. + * + * Note: Caller must free temporary memory allocations by calling + * p2p_parse_free() when the parsed data is not needed anymore. + */ +int p2p_parse_ies(const u8 *data, size_t len, struct p2p_message *msg) +{ + struct ieee802_11_elems elems; + + ieee802_11_parse_elems(data, len, &elems, 0); + if (elems.ds_params && elems.ds_params_len >= 1) + msg->ds_params = elems.ds_params; + if (elems.ssid) + msg->ssid = elems.ssid - 2; + + msg->wps_attributes = ieee802_11_vendor_ie_concat(data, len, + WPS_DEV_OUI_WFA); + if (msg->wps_attributes && + p2p_parse_wps_ie(msg->wps_attributes, msg)) { + p2p_parse_free(msg); + return -1; + } + + msg->p2p_attributes = ieee802_11_vendor_ie_concat(data, len, + P2P_IE_VENDOR_TYPE); + if (msg->p2p_attributes && + p2p_parse_p2p_ie(msg->p2p_attributes, msg)) { + wpa_printf(MSG_DEBUG, "P2P: Failed to parse P2P IE data"); + if (msg->p2p_attributes) + wpa_hexdump_buf(MSG_MSGDUMP, "P2P: P2P IE data", + msg->p2p_attributes); + p2p_parse_free(msg); + return -1; + } + + return 0; +} + + +/** + * p2p_parse - Parse a P2P Action frame contents + * @data: Action frame payload after Category and Code fields + * @len: Length of data buffer in octets + * @msg: Buffer for returning parsed attributes + * Returns: 0 on success, -1 on failure + * + * Note: Caller must free temporary memory allocations by calling + * p2p_parse_free() when the parsed data is not needed anymore. + */ +int p2p_parse(const u8 *data, size_t len, struct p2p_message *msg) +{ + os_memset(msg, 0, sizeof(*msg)); + wpa_printf(MSG_DEBUG, "P2P: Parsing the received message"); + if (len < 1) { + wpa_printf(MSG_DEBUG, "P2P: No Dialog Token in the message"); + return -1; + } + msg->dialog_token = data[0]; + wpa_printf(MSG_DEBUG, "P2P: * Dialog Token: %d", msg->dialog_token); + + return p2p_parse_ies(data + 1, len - 1, msg); +} + + +/** + * p2p_parse_free - Free temporary data from P2P parsing + * @msg: Parsed attributes + */ +void p2p_parse_free(struct p2p_message *msg) +{ + wpabuf_free(msg->p2p_attributes); + msg->p2p_attributes = NULL; + wpabuf_free(msg->wps_attributes); + msg->wps_attributes = NULL; +} + + +int p2p_group_info_parse(const u8 *gi, size_t gi_len, + struct p2p_group_info *info) +{ + const u8 *g, *gend; + + os_memset(info, 0, sizeof(*info)); + if (gi == NULL) + return 0; + + g = gi; + gend = gi + gi_len; + while (g < gend) { + struct p2p_client_info *cli; + const u8 *t, *cend; + int count; + + cli = &info->client[info->num_clients]; + cend = g + 1 + g[0]; + if (cend > gend) + return -1; /* invalid data */ + /* g at start of P2P Client Info Descriptor */ + /* t at Device Capability Bitmap */ + t = g + 1 + 2 * ETH_ALEN; + if (t > cend) + return -1; /* invalid data */ + cli->p2p_device_addr = g + 1; + cli->p2p_interface_addr = g + 1 + ETH_ALEN; + cli->dev_capab = t[0]; + + if (t + 1 + 2 + 8 + 1 > cend) + return -1; /* invalid data */ + + cli->config_methods = WPA_GET_BE16(&t[1]); + cli->pri_dev_type = &t[3]; + + t += 1 + 2 + 8; + /* t at Number of Secondary Device Types */ + cli->num_sec_dev_types = *t++; + if (t + 8 * cli->num_sec_dev_types > cend) + return -1; /* invalid data */ + cli->sec_dev_types = t; + t += 8 * cli->num_sec_dev_types; + + /* t at Device Name in WPS TLV format */ + if (t + 2 + 2 > cend) + return -1; /* invalid data */ + if (WPA_GET_BE16(t) != ATTR_DEV_NAME) + return -1; /* invalid Device Name TLV */ + t += 2; + count = WPA_GET_BE16(t); + t += 2; + if (count > cend - t) + return -1; /* invalid Device Name TLV */ + if (count >= 32) + count = 32; + cli->dev_name = (const char *) t; + cli->dev_name_len = count; + + g = cend; + + info->num_clients++; + if (info->num_clients == P2P_MAX_GROUP_ENTRIES) + return -1; + } + + return 0; +} + + +static int p2p_group_info_text(const u8 *gi, size_t gi_len, char *buf, + char *end) +{ + char *pos = buf; + int ret; + struct p2p_group_info info; + unsigned int i; + + if (p2p_group_info_parse(gi, gi_len, &info) < 0) + return 0; + + for (i = 0; i < info.num_clients; i++) { + struct p2p_client_info *cli; + char name[33]; + char devtype[WPS_DEV_TYPE_BUFSIZE]; + u8 s; + int count; + + cli = &info.client[i]; + ret = os_snprintf(pos, end - pos, "p2p_group_client: " + "dev=" MACSTR " iface=" MACSTR, + MAC2STR(cli->p2p_device_addr), + MAC2STR(cli->p2p_interface_addr)); + if (ret < 0 || ret >= end - pos) + return pos - buf; + pos += ret; + + ret = os_snprintf(pos, end - pos, + " dev_capab=0x%x config_methods=0x%x " + "dev_type=%s", + cli->dev_capab, cli->config_methods, + wps_dev_type_bin2str(cli->pri_dev_type, + devtype, + sizeof(devtype))); + if (ret < 0 || ret >= end - pos) + return pos - buf; + pos += ret; + + for (s = 0; s < cli->num_sec_dev_types; s++) { + ret = os_snprintf(pos, end - pos, " dev_type=%s", + wps_dev_type_bin2str( + &cli->sec_dev_types[s * 8], + devtype, sizeof(devtype))); + if (ret < 0 || ret >= end - pos) + return pos - buf; + pos += ret; + } + + os_memcpy(name, cli->dev_name, cli->dev_name_len); + name[cli->dev_name_len] = '\0'; + count = (int) cli->dev_name_len - 1; + while (count >= 0) { + if (name[count] < 32) + name[count] = '_'; + count--; + } + + ret = os_snprintf(pos, end - pos, " dev_name='%s'\n", name); + if (ret < 0 || ret >= end - pos) + return pos - buf; + pos += ret; + } + + return pos - buf; +} + + +/** + * p2p_attr_text - Build text format description of P2P IE attributes + * @data: P2P IE contents + * @buf: Buffer for returning text + * @end: Pointer to the end of the buf area + * Returns: Number of octets written to the buffer or -1 on faikure + * + * This function can be used to parse P2P IE contents into text format + * field=value lines. + */ +int p2p_attr_text(struct wpabuf *data, char *buf, char *end) +{ + struct p2p_message msg; + char *pos = buf; + int ret; + + os_memset(&msg, 0, sizeof(msg)); + if (p2p_parse_p2p_ie(data, &msg)) + return -1; + + if (msg.capability) { + ret = os_snprintf(pos, end - pos, + "p2p_dev_capab=0x%x\n" + "p2p_group_capab=0x%x\n", + msg.capability[0], msg.capability[1]); + if (ret < 0 || ret >= end - pos) + return pos - buf; + pos += ret; + } + + if (msg.pri_dev_type) { + char devtype[WPS_DEV_TYPE_BUFSIZE]; + ret = os_snprintf(pos, end - pos, + "p2p_primary_device_type=%s\n", + wps_dev_type_bin2str(msg.pri_dev_type, + devtype, + sizeof(devtype))); + if (ret < 0 || ret >= end - pos) + return pos - buf; + pos += ret; + } + + ret = os_snprintf(pos, end - pos, "p2p_device_name=%s\n", + msg.device_name); + if (ret < 0 || ret >= end - pos) + return pos - buf; + pos += ret; + + if (msg.p2p_device_addr) { + ret = os_snprintf(pos, end - pos, "p2p_device_addr=" MACSTR + "\n", + MAC2STR(msg.p2p_device_addr)); + if (ret < 0 || ret >= end - pos) + return pos - buf; + pos += ret; + } + + ret = os_snprintf(pos, end - pos, "p2p_config_methods=0x%x\n", + msg.config_methods); + if (ret < 0 || ret >= end - pos) + return pos - buf; + pos += ret; + + ret = p2p_group_info_text(msg.group_info, msg.group_info_len, + pos, end); + if (ret < 0) + return pos - buf; + pos += ret; + + return pos - buf; +} + + +u8 p2p_get_group_capab(const struct wpabuf *p2p_ie) +{ + struct p2p_message msg; + + os_memset(&msg, 0, sizeof(msg)); + if (p2p_parse_p2p_ie(p2p_ie, &msg)) + return 0; + + if (!msg.capability) + return 0; + + return msg.capability[1]; +} + + +const u8 * p2p_get_go_dev_addr(const struct wpabuf *p2p_ie) +{ + struct p2p_message msg; + + os_memset(&msg, 0, sizeof(msg)); + if (p2p_parse_p2p_ie(p2p_ie, &msg)) + return NULL; + + if (msg.p2p_device_addr) + return msg.p2p_device_addr; + if (msg.device_id) + return msg.device_id; + + return NULL; +} diff --git a/src/p2p/p2p_pd.c b/src/p2p/p2p_pd.c new file mode 100644 index 0000000..2a16315 --- /dev/null +++ b/src/p2p/p2p_pd.c @@ -0,0 +1,340 @@ +/* + * Wi-Fi Direct - P2P provision discovery + * Copyright (c) 2009-2010, Atheros Communications + * + * 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 "wps/wps_defs.h" +#include "p2p_i.h" +#include "p2p.h" + + +static void p2p_build_wps_ie_config_methods(struct wpabuf *buf, + u16 config_methods) +{ + u8 *len; + wpabuf_put_u8(buf, WLAN_EID_VENDOR_SPECIFIC); + len = wpabuf_put(buf, 1); + wpabuf_put_be32(buf, WPS_DEV_OUI_WFA); + + /* Config Methods */ + wpabuf_put_be16(buf, ATTR_CONFIG_METHODS); + wpabuf_put_be16(buf, 2); + wpabuf_put_be16(buf, config_methods); + + p2p_buf_update_ie_hdr(buf, len); +} + + +static struct wpabuf * p2p_build_prov_disc_req(struct p2p_data *p2p, + u8 dialog_token, + u16 config_methods, + struct p2p_device *go) +{ + struct wpabuf *buf; + u8 *len; + + buf = wpabuf_alloc(1000); + if (buf == NULL) + return NULL; + + p2p_buf_add_public_action_hdr(buf, P2P_PROV_DISC_REQ, dialog_token); + + len = p2p_buf_add_ie_hdr(buf); + p2p_buf_add_capability(buf, p2p->dev_capab, 0); + p2p_buf_add_device_info(buf, p2p, NULL); + if (go) { + p2p_buf_add_group_id(buf, go->p2p_device_addr, go->oper_ssid, + go->oper_ssid_len); + } + p2p_buf_update_ie_hdr(buf, len); + + /* WPS IE with Config Methods attribute */ + p2p_build_wps_ie_config_methods(buf, config_methods); + + return buf; +} + + +static struct wpabuf * p2p_build_prov_disc_resp(struct p2p_data *p2p, + u8 dialog_token, + u16 config_methods) +{ + struct wpabuf *buf; + + buf = wpabuf_alloc(100); + if (buf == NULL) + return NULL; + + p2p_buf_add_public_action_hdr(buf, P2P_PROV_DISC_RESP, dialog_token); + + /* WPS IE with Config Methods attribute */ + p2p_build_wps_ie_config_methods(buf, config_methods); + + return buf; +} + + +void p2p_process_prov_disc_req(struct p2p_data *p2p, const u8 *sa, + const u8 *data, size_t len, int rx_freq) +{ + struct p2p_message msg; + struct p2p_device *dev; + int freq; + int reject = 1; + struct wpabuf *resp; + + if (p2p_parse(data, len, &msg)) + return; + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Received Provision Discovery Request from " MACSTR + " with config methods 0x%x (freq=%d)", + MAC2STR(sa), msg.wps_config_methods, rx_freq); + + dev = p2p_get_device(p2p, sa); + if (dev == NULL) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Provision Discovery Request from " + "unknown peer " MACSTR, MAC2STR(sa)); + } + + if (!(msg.wps_config_methods & + (WPS_CONFIG_DISPLAY | WPS_CONFIG_KEYPAD | + WPS_CONFIG_PUSHBUTTON))) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Unsupported " + "Config Methods in Provision Discovery Request"); + goto out; + } + + if (dev) + dev->flags &= ~(P2P_DEV_PD_PEER_DISPLAY | + P2P_DEV_PD_PEER_KEYPAD); + if (msg.wps_config_methods & WPS_CONFIG_DISPLAY) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Peer " MACSTR + " requested us to show a PIN on display", MAC2STR(sa)); + if (dev) + dev->flags |= P2P_DEV_PD_PEER_KEYPAD; + } else if (msg.wps_config_methods & WPS_CONFIG_KEYPAD) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Peer " MACSTR + " requested us to write its PIN using keypad", + MAC2STR(sa)); + if (dev) + dev->flags |= P2P_DEV_PD_PEER_DISPLAY; + } + + reject = 0; + +out: + resp = p2p_build_prov_disc_resp(p2p, msg.dialog_token, + reject ? 0 : msg.wps_config_methods); + if (resp == NULL) { + p2p_parse_free(&msg); + return; + } + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Sending Provision Discovery Response"); + if (rx_freq > 0) + freq = rx_freq; + else + freq = p2p_channel_to_freq(p2p->cfg->country, + p2p->cfg->reg_class, + p2p->cfg->channel); + if (freq < 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Unknown regulatory class/channel"); + wpabuf_free(resp); + p2p_parse_free(&msg); + return; + } + p2p->pending_action_state = P2P_NO_PENDING_ACTION; + if (p2p->cfg->send_action(p2p->cfg->cb_ctx, freq, sa, + p2p->cfg->dev_addr, p2p->cfg->dev_addr, + wpabuf_head(resp), wpabuf_len(resp), 200) < + 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Failed to send Action frame"); + } + + wpabuf_free(resp); + + if (!reject && p2p->cfg->prov_disc_req) { + const u8 *dev_addr = sa; + if (msg.p2p_device_addr) + dev_addr = msg.p2p_device_addr; + p2p->cfg->prov_disc_req(p2p->cfg->cb_ctx, sa, + msg.wps_config_methods, + dev_addr, msg.pri_dev_type, + msg.device_name, msg.config_methods, + msg.capability ? msg.capability[0] : 0, + msg.capability ? msg.capability[1] : + 0); + + } + p2p_parse_free(&msg); +} + + +void p2p_process_prov_disc_resp(struct p2p_data *p2p, const u8 *sa, + const u8 *data, size_t len) +{ + struct p2p_message msg; + struct p2p_device *dev; + u16 report_config_methods = 0; + + if (p2p_parse(data, len, &msg)) + return; + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Received Provisioning Discovery Response from " MACSTR + " with config methods 0x%x", + MAC2STR(sa), msg.wps_config_methods); + + dev = p2p_get_device(p2p, sa); + if (dev == NULL || !dev->req_config_methods) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Ignore Provisioning Discovery Response from " + MACSTR " with no pending request", MAC2STR(sa)); + p2p_parse_free(&msg); + return; + } + + if (dev->dialog_token != msg.dialog_token) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Ignore Provisioning Discovery Response with " + "unexpected Dialog Token %u (expected %u)", + msg.dialog_token, dev->dialog_token); + p2p_parse_free(&msg); + return; + } + + if (msg.wps_config_methods != dev->req_config_methods) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Peer rejected " + "our Provisioning Discovery Request"); + p2p_parse_free(&msg); + goto out; + } + + report_config_methods = dev->req_config_methods; + dev->flags &= ~(P2P_DEV_PD_PEER_DISPLAY | + P2P_DEV_PD_PEER_KEYPAD); + if (dev->req_config_methods & WPS_CONFIG_DISPLAY) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Peer " MACSTR + " accepted to show a PIN on display", MAC2STR(sa)); + dev->flags |= P2P_DEV_PD_PEER_DISPLAY; + } else if (msg.wps_config_methods & WPS_CONFIG_KEYPAD) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Peer " MACSTR + " accepted to write our PIN using keypad", + MAC2STR(sa)); + dev->flags |= P2P_DEV_PD_PEER_KEYPAD; + } + p2p_parse_free(&msg); + +out: + dev->req_config_methods = 0; + p2p->cfg->send_action_done(p2p->cfg->cb_ctx); + if (p2p->cfg->prov_disc_resp) + p2p->cfg->prov_disc_resp(p2p->cfg->cb_ctx, sa, + report_config_methods); +} + + +int p2p_send_prov_disc_req(struct p2p_data *p2p, struct p2p_device *dev, + int join) +{ + struct wpabuf *req; + int freq; + + freq = dev->listen_freq > 0 ? dev->listen_freq : dev->oper_freq; + if (freq <= 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: No Listen/Operating frequency known for the " + "peer " MACSTR " to send Provision Discovery Request", + MAC2STR(dev->p2p_device_addr)); + return -1; + } + + if (dev->flags & P2P_DEV_GROUP_CLIENT_ONLY) { + if (!(dev->dev_capab & P2P_DEV_CAPAB_CLIENT_DISCOVERABILITY)) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Cannot use PD with P2P Device " MACSTR + " that is in a group and is not discoverable", + MAC2STR(dev->p2p_device_addr)); + return -1; + } + /* TODO: use device discoverability request through GO */ + } + + dev->dialog_token++; + if (dev->dialog_token == 0) + dev->dialog_token = 1; + req = p2p_build_prov_disc_req(p2p, dev->dialog_token, + dev->req_config_methods, + join ? dev : NULL); + if (req == NULL) + return -1; + + p2p->pending_action_state = P2P_PENDING_PD; + if (p2p->cfg->send_action(p2p->cfg->cb_ctx, freq, + dev->p2p_device_addr, p2p->cfg->dev_addr, + dev->p2p_device_addr, + wpabuf_head(req), wpabuf_len(req), 200) < 0) + { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Failed to send Action frame"); + wpabuf_free(req); + return -1; + } + + wpabuf_free(req); + return 0; +} + + +int p2p_prov_disc_req(struct p2p_data *p2p, const u8 *peer_addr, + u16 config_methods, int join) +{ + struct p2p_device *dev; + + dev = p2p_get_device(p2p, peer_addr); + if (dev == NULL) + dev = p2p_get_device_interface(p2p, peer_addr); + if (dev == NULL || (dev->flags & P2P_DEV_PROBE_REQ_ONLY)) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Provision " + "Discovery Request destination " MACSTR + " not yet known", MAC2STR(peer_addr)); + return -1; + } + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Provision Discovery " + "Request with " MACSTR " (config methods 0x%x)", + MAC2STR(peer_addr), config_methods); + if (config_methods == 0) + return -1; + + dev->req_config_methods = config_methods; + + if (p2p->go_neg_peer || + (p2p->state != P2P_IDLE && p2p->state != P2P_SEARCH && + p2p->state != P2P_LISTEN_ONLY)) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Busy with other " + "operations; postpone Provision Discovery Request " + "with " MACSTR " (config methods 0x%x)", + MAC2STR(peer_addr), config_methods); + return 0; + } + + return p2p_send_prov_disc_req(p2p, dev, join); +} diff --git a/src/p2p/p2p_sd.c b/src/p2p/p2p_sd.c new file mode 100644 index 0000000..7ac4a27 --- /dev/null +++ b/src/p2p/p2p_sd.c @@ -0,0 +1,526 @@ +/* + * Wi-Fi Direct - P2P service discovery + * Copyright (c) 2009, Atheros Communications + * + * 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 "p2p_i.h" +#include "p2p.h" + + +struct p2p_sd_query * p2p_pending_sd_req(struct p2p_data *p2p, + struct p2p_device *dev) +{ + struct p2p_sd_query *q; + + if (!(dev->dev_capab & P2P_DEV_CAPAB_SERVICE_DISCOVERY)) + return 0; /* peer does not support SD */ + + for (q = p2p->sd_queries; q; q = q->next) { + if (q->for_all_peers && !(dev->flags & P2P_DEV_SD_INFO)) + return q; + if (!q->for_all_peers && + os_memcmp(q->peer, dev->p2p_device_addr, ETH_ALEN) == 0) + return q; + } + + return NULL; +} + + +static int p2p_unlink_sd_query(struct p2p_data *p2p, + struct p2p_sd_query *query) +{ + struct p2p_sd_query *q, *prev; + q = p2p->sd_queries; + prev = NULL; + while (q) { + if (q == query) { + if (prev) + prev->next = q->next; + else + p2p->sd_queries = q->next; + if (p2p->sd_query == query) + p2p->sd_query = NULL; + return 1; + } + 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); + } +} + + +static struct wpabuf * p2p_build_sd_query(u16 update_indic, + struct wpabuf *tlvs) +{ + struct wpabuf *buf; + u8 *len_pos, *len_pos2; + + buf = wpabuf_alloc(1000 + wpabuf_len(tlvs)); + if (buf == NULL) + return NULL; + + wpabuf_put_u8(buf, WLAN_ACTION_PUBLIC); + wpabuf_put_u8(buf, WLAN_PA_GAS_INITIAL_REQ); + wpabuf_put_u8(buf, 0); /* Dialog Token */ + + /* Advertisement Protocol IE */ + wpabuf_put_u8(buf, WLAN_EID_ADV_PROTO); + wpabuf_put_u8(buf, 2); /* Length */ + wpabuf_put_u8(buf, 0); /* QueryRespLenLimit | PAME-BI */ + wpabuf_put_u8(buf, NATIVE_QUERY_PROTOCOL); /* Advertisement Protocol */ + + /* Query Request */ + len_pos = wpabuf_put(buf, 2); /* Length (to be filled) */ + + /* NQP Query Request Frame */ + wpabuf_put_le16(buf, NQP_VENDOR_SPECIFIC); /* Info ID */ + len_pos2 = wpabuf_put(buf, 2); /* Length (to be filled) */ + wpabuf_put_be24(buf, OUI_WFA); + wpabuf_put_u8(buf, P2P_OUI_TYPE); + wpabuf_put_le16(buf, update_indic); /* Service Update Indicator */ + wpabuf_put_buf(buf, tlvs); + + WPA_PUT_LE16(len_pos2, (u8 *) wpabuf_put(buf, 0) - len_pos2 - 2); + WPA_PUT_LE16(len_pos, (u8 *) wpabuf_put(buf, 0) - len_pos - 2); + + return buf; +} + + +static struct wpabuf * p2p_build_sd_response(u8 dialog_token, u16 status_code, + u16 update_indic, + const struct wpabuf *tlvs) +{ + struct wpabuf *buf; + u8 *len_pos, *len_pos2; + + buf = wpabuf_alloc(1000 + wpabuf_len(tlvs)); + if (buf == NULL) + return NULL; + + wpabuf_put_u8(buf, WLAN_ACTION_PUBLIC); + wpabuf_put_u8(buf, WLAN_PA_GAS_INITIAL_RESP); + wpabuf_put_u8(buf, dialog_token); + wpabuf_put_le16(buf, status_code); + wpabuf_put_le16(buf, 0); /* GAS Comeback Delay */ + + /* Advertisement Protocol IE */ + wpabuf_put_u8(buf, WLAN_EID_ADV_PROTO); + wpabuf_put_u8(buf, 2); /* Length */ + wpabuf_put_u8(buf, 0x7f); /* QueryRespLenLimit | PAME-BI */ + wpabuf_put_u8(buf, NATIVE_QUERY_PROTOCOL); /* Advertisement Protocol */ + + /* Query Response */ + len_pos = wpabuf_put(buf, 2); /* Length (to be filled) */ + + /* NQP Query Response Frame */ + wpabuf_put_le16(buf, NQP_VENDOR_SPECIFIC); /* Info ID */ + len_pos2 = wpabuf_put(buf, 2); /* Length (to be filled) */ + wpabuf_put_be24(buf, OUI_WFA); + wpabuf_put_u8(buf, P2P_OUI_TYPE); + wpabuf_put_le16(buf, update_indic); /* Service Update Indicator */ + wpabuf_put_buf(buf, tlvs); + + WPA_PUT_LE16(len_pos2, (u8 *) wpabuf_put(buf, 0) - len_pos2 - 2); + WPA_PUT_LE16(len_pos, (u8 *) wpabuf_put(buf, 0) - len_pos - 2); + + 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; + + freq = dev->listen_freq > 0 ? dev->listen_freq : dev->oper_freq; + if (freq <= 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: No Listen/Operating frequency known for the " + "peer " MACSTR " to send SD Request", + MAC2STR(dev->p2p_device_addr)); + return -1; + } + + query = p2p_pending_sd_req(p2p, dev); + if (query == NULL) + return -1; + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Start Service Discovery with " MACSTR, + MAC2STR(dev->p2p_device_addr)); + + req = p2p_build_sd_query(p2p->srv_update_indic, query->tlvs); + if (req == NULL) + return -1; + + p2p->sd_peer = dev; + p2p->sd_query = query; + p2p->pending_action_state = P2P_PENDING_SD; + + if (p2p->cfg->send_action(p2p->cfg->cb_ctx, freq, + dev->p2p_device_addr, p2p->cfg->dev_addr, + dev->p2p_device_addr, + wpabuf_head(req), wpabuf_len(req), 5000) < 0) + { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "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->country, + p2p->cfg->reg_class, + p2p->cfg->channel); + if (freq < 0) + return; + + if (len < 1 + 2) + return; + + dialog_token = *pos++; + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: GAS Initial Request from " MACSTR " (dialog token %u, " + "freq %d)", + MAC2STR(sa), dialog_token, rx_freq); + + if (*pos != WLAN_EID_ADV_PROTO) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Unexpected IE in GAS Initial Request: %u", *pos); + return; + } + pos++; + + slen = *pos++; + next = pos + slen; + if (next > end || slen < 2) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Invalid IE in GAS Initial Request"); + return; + } + pos++; /* skip QueryRespLenLimit and PAME-BI */ + + if (*pos != NATIVE_QUERY_PROTOCOL) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "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; + + /* NQP Query Request */ + if (pos + 4 > end) + return; + if (WPA_GET_LE16(pos) != NQP_VENDOR_SPECIFIC) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Unsupported NQP Info ID %u", WPA_GET_LE16(pos)); + return; + } + pos += 2; + + slen = WPA_GET_LE16(pos); + pos += 2; + if (pos + slen > end || slen < 3 + 1) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Invalid NQP Query Request length"); + return; + } + + if (WPA_GET_BE24(pos) != OUI_WFA) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Unsupported NQP OUI %06x", WPA_GET_BE24(pos)); + return; + } + pos += 3; + + if (*pos != P2P_OUI_TYPE) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Unsupported NQP vendor type %u", *pos); + return; + } + pos++; + + if (pos + 2 > end) + return; + update_indic = WPA_GET_LE16(pos); + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "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; + resp = p2p_build_sd_response(dialog_token, WLAN_STATUS_SUCCESS, + p2p->srv_update_indic, resp_tlvs); + if (resp == NULL) + return; + + p2p->pending_action_state = P2P_NO_PENDING_ACTION; + if (p2p->cfg->send_action(p2p->cfg->cb_ctx, freq, + dst, p2p->cfg->dev_addr, p2p->cfg->dev_addr, + wpabuf_head(resp), wpabuf_len(resp), 200) < + 0) + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "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) +{ + 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->p2p_device_addr, ETH_ALEN) != 0) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Ignore unexpected GAS Initial Response from " + MACSTR, MAC2STR(sa)); + return; + } + p2p->cfg->send_action_done(p2p->cfg->cb_ctx); + p2p_clear_timeout(p2p); + + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Received GAS Initial Response from " MACSTR, + MAC2STR(sa)); + + if (len < 5 + 2) + 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; + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: dialog_token=%u status_code=%u comeback_delay=%u", + dialog_token, status_code, comeback_delay); + + if (*pos != WLAN_EID_ADV_PROTO) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Unexpected IE in GAS Initial Response: %u", + *pos); + return; + } + pos++; + + slen = *pos++; + next = pos + slen; + if (next > end || slen < 2) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Invalid IE in GAS Initial Response"); + return; + } + pos++; /* skip QueryRespLenLimit and PAME-BI */ + + if (*pos != NATIVE_QUERY_PROTOCOL) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Unsupported GAS advertisement protocol id %u", + *pos); + return; + } + + pos = next; + /* Query Response */ + if (pos + 2 > end) + return; + slen = WPA_GET_LE16(pos); + pos += 2; + if (pos + slen > end) + return; + end = pos + slen; + + /* NQP Query Response */ + if (pos + 4 > end) + return; + if (WPA_GET_LE16(pos) != NQP_VENDOR_SPECIFIC) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Unsupported NQP Info ID %u", WPA_GET_LE16(pos)); + return; + } + pos += 2; + + slen = WPA_GET_LE16(pos); + pos += 2; + if (pos + slen > end || slen < 3 + 1) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Invalid NQP Query Response length"); + return; + } + + if (WPA_GET_BE24(pos) != OUI_WFA) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Unsupported NQP OUI %06x", WPA_GET_BE24(pos)); + return; + } + pos += 3; + + if (*pos != P2P_OUI_TYPE) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Unsupported NQP vendor type %u", *pos); + return; + } + pos++; + + if (pos + 2 > end) + return; + update_indic = WPA_GET_LE16(pos); + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Service Update Indicator: %u", update_indic); + pos += 2; + + p2p->sd_peer->flags |= P2P_DEV_SD_INFO; + p2p->sd_peer->flags &= ~P2P_DEV_SD_SCHEDULE; + p2p->sd_peer = NULL; + + if (p2p->sd_query) { + if (!p2p->sd_query->for_all_peers) { + struct p2p_sd_query *q; + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "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_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; + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, "P2P: Added SD Query %p", q); + + return q; +} + + +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)) { + wpa_msg(p2p->cfg->msg_ctx, MSG_DEBUG, + "P2P: Cancel pending SD query %p", req); + p2p_free_sd_query(req); + return 0; + } + return -1; +} diff --git a/src/p2p/p2p_utils.c b/src/p2p/p2p_utils.c new file mode 100644 index 0000000..7e8870a --- /dev/null +++ b/src/p2p/p2p_utils.c @@ -0,0 +1,260 @@ +/* + * P2P - generic helper functions + * Copyright (c) 2009, Atheros Communications + * + * 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 "p2p_i.h" + + +/** + * p2p_random - Generate random string for SSID and passphrase + * @buf: Buffer for returning the result + * @len: Number of octets to write to the buffer + * Returns: 0 on success, -1 on failure + * + * This function generates a random string using the following character set: + * 'A'-'Z', 'a'-'z', '0'-'9'. + */ +int p2p_random(char *buf, size_t len) +{ + u8 val; + size_t i; + u8 letters = 'Z' - 'A' + 1; + u8 numbers = 10; + + if (os_get_random((unsigned char *) buf, len)) + return -1; + /* Character set: 'A'-'Z', 'a'-'z', '0'-'9' */ + for (i = 0; i < len; i++) { + val = buf[i]; + val %= 2 * letters + numbers; + if (val < letters) + buf[i] = 'A' + val; + else if (val < 2 * letters) + buf[i] = 'a' + (val - letters); + else + buf[i] = '0' + (val - 2 * letters); + } + + return 0; +} + + +static int p2p_channel_to_freq_j4(int reg_class, int channel) +{ + /* Table J-4 in P802.11REVmb/D4.0 - Global operating classes */ + /* TODO: more regulatory classes */ + switch (reg_class) { + case 81: + /* channels 1..13 */ + if (channel < 1 || channel > 13) + return -1; + return 2407 + 5 * channel; + case 82: + /* channel 14 */ + if (channel != 14) + return -1; + return 2414 + 5 * channel; + case 83: /* channels 1..9; 40 MHz */ + case 84: /* channels 5..13; 40 MHz */ + if (channel < 1 || channel > 13) + return -1; + return 2407 + 5 * channel; + case 115: /* channels 36,40,44,48; indoor only */ + case 118: /* channels 52,56,60,64; dfs */ + if (channel < 36 || channel > 64) + return -1; + return 5000 + 5 * channel; + case 124: /* channels 149,153,157,161 */ + case 125: /* channels 149,153,157,161,165,169 */ + if (channel < 149 || channel > 161) + return -1; + return 5000 + 5 * channel; + case 116: /* channels 36,44; 40 MHz; indoor only */ + case 117: /* channels 40,48; 40 MHz; indoor only */ + case 119: /* channels 52,60; 40 MHz; dfs */ + case 120: /* channels 56,64; 40 MHz; dfs */ + if (channel < 36 || channel > 64) + return -1; + return 5000 + 5 * channel; + case 126: /* channels 149,157; 40 MHz */ + case 127: /* channels 153,161; 40 MHz */ + if (channel < 149 || channel > 161) + return -1; + return 5000 + 5 * channel; + } + return -1; +} + + +/** + * p2p_channel_to_freq - Convert channel info to frequency + * @country: Country code + * @reg_class: Regulatory class + * @channel: Channel number + * Returns: Frequency in MHz or -1 if the specified channel is unknown + */ +int p2p_channel_to_freq(const char *country, int reg_class, int channel) +{ + if (country[2] == 0x04) + return p2p_channel_to_freq_j4(reg_class, channel); + + /* These are mainly for backwards compatibility; to be removed */ + switch (reg_class) { + case 1: /* US/1, EU/1, JP/1 = 5 GHz, channels 36,40,44,48 */ + if (channel < 36 || channel > 48) + return -1; + return 5000 + 5 * channel; + case 3: /* US/3 = 5 GHz, channels 149,153,157,161 */ + case 5: /* US/5 = 5 GHz, channels 149,153,157,161 */ + if (channel < 149 || channel > 161) + return -1; + return 5000 + 5 * channel; + case 4: /* EU/4 = 2.407 GHz, channels 1..13 */ + case 12: /* US/12 = 2.407 GHz, channels 1..11 */ + case 30: /* JP/30 = 2.407 GHz, channels 1..13 */ + if (channel < 1 || channel > 13) + return -1; + return 2407 + 5 * channel; + case 31: /* JP/31 = 2.414 GHz, channel 14 */ + if (channel != 14) + return -1; + return 2414 + 5 * channel; + } + + return -1; +} + + +/** + * p2p_freq_to_channel - Convert frequency into channel info + * @country: Country code + * @reg_class: Buffer for returning regulatory class + * @channel: Buffer for returning channel number + * Returns: 0 on success, -1 if the specified frequency is unknown + */ +int p2p_freq_to_channel(const char *country, unsigned int freq, u8 *reg_class, + u8 *channel) +{ + /* TODO: more operating classes */ + if (freq >= 2412 && freq <= 2472) { + *reg_class = 81; /* 2.407 GHz, channels 1..13 */ + *channel = (freq - 2407) / 5; + return 0; + } + + if (freq == 2484) { + *reg_class = 82; /* channel 14 */ + *channel = 14; + return 0; + } + + if (freq >= 5180 && freq <= 5240) { + *reg_class = 115; /* 5 GHz, channels 36..48 */ + *channel = (freq - 5000) / 5; + return 0; + } + + if (freq >= 5745 && freq <= 5805) { + *reg_class = 124; /* 5 GHz, channels 149..161 */ + *channel = (freq - 5000) / 5; + return 0; + } + + return -1; +} + + +static void p2p_reg_class_intersect(const struct p2p_reg_class *a, + const struct p2p_reg_class *b, + struct p2p_reg_class *res) +{ + size_t i, j; + + res->reg_class = a->reg_class; + + for (i = 0; i < a->channels; i++) { + for (j = 0; j < b->channels; j++) { + if (a->channel[i] != b->channel[j]) + continue; + res->channel[res->channels] = a->channel[i]; + res->channels++; + if (res->channels == P2P_MAX_REG_CLASS_CHANNELS) + return; + } + } +} + + +/** + * p2p_channels_intersect - Intersection of supported channel lists + * @a: First set of supported channels + * @b: Second set of supported channels + * @res: Data structure for returning the intersection of support channels + * + * This function can be used to find a common set of supported channels. Both + * input channels sets are assumed to use the same country code. If different + * country codes are used, the regulatory class numbers may not be matched + * correctly and results are undefined. + */ +void p2p_channels_intersect(const struct p2p_channels *a, + const struct p2p_channels *b, + struct p2p_channels *res) +{ + size_t i, j; + + os_memset(res, 0, sizeof(*res)); + + for (i = 0; i < a->reg_classes; i++) { + const struct p2p_reg_class *a_reg = &a->reg_class[i]; + for (j = 0; j < b->reg_classes; j++) { + const struct p2p_reg_class *b_reg = &b->reg_class[j]; + if (a_reg->reg_class != b_reg->reg_class) + continue; + p2p_reg_class_intersect( + a_reg, b_reg, + &res->reg_class[res->reg_classes]); + if (res->reg_class[res->reg_classes].channels) { + res->reg_classes++; + if (res->reg_classes == P2P_MAX_REG_CLASSES) + return; + } + } + } +} + + +/** + * p2p_channels_includes - Check whether a channel is included in the list + * @channels: List of supported channels + * @reg_class: Regulatory class of the channel to search + * @channel: Channel number of the channel to search + * Returns: 1 if channel was found or 0 if not + */ +int p2p_channels_includes(const struct p2p_channels *channels, u8 reg_class, + u8 channel) +{ + size_t i, j; + for (i = 0; i < channels->reg_classes; i++) { + const struct p2p_reg_class *reg = &channels->reg_class[i]; + if (reg->reg_class != reg_class) + continue; + for (j = 0; j < reg->channels; j++) { + if (reg->channel[j] == channel) + return 1; + } + } + return 0; +} diff --git a/src/utils/wpabuf.h b/src/utils/wpabuf.h index a150455..cccfcc8 100644 --- a/src/utils/wpabuf.h +++ b/src/utils/wpabuf.h @@ -117,6 +117,12 @@ static inline void wpabuf_put_le16(struct wpabuf *buf, u16 data) WPA_PUT_LE16(pos, data); } +static inline void wpabuf_put_le32(struct wpabuf *buf, u32 data) +{ + u8 *pos = wpabuf_put(buf, 4); + WPA_PUT_LE32(pos, data); +} + static inline void wpabuf_put_be16(struct wpabuf *buf, u16 data) { u8 *pos = wpabuf_put(buf, 2); diff --git a/wpa_supplicant/Makefile b/wpa_supplicant/Makefile index 3301e61..c113dd6 100644 --- a/wpa_supplicant/Makefile +++ b/wpa_supplicant/Makefile @@ -166,6 +166,26 @@ CFLAGS += -DCONFIG_IBSS_RSN OBJS += ibss_rsn.o endif +ifdef CONFIG_P2P +OBJS += p2p_supplicant.o +OBJS += ../src/p2p/p2p.o +OBJS += ../src/p2p/p2p_utils.o +OBJS += ../src/p2p/p2p_parse.o +OBJS += ../src/p2p/p2p_build.o +OBJS += ../src/p2p/p2p_go_neg.o +OBJS += ../src/p2p/p2p_sd.o +OBJS += ../src/p2p/p2p_pd.o +OBJS += ../src/p2p/p2p_invitation.o +OBJS += ../src/p2p/p2p_dev_disc.o +OBJS += ../src/p2p/p2p_group.o +OBJS += ../src/ap/p2p_hostapd.o +CFLAGS += -DCONFIG_P2P +NEED_80211_COMMON=y +ifdef CONFIG_P2P_STRICT +CFLAGS += -DCONFIG_P2P_STRICT +endif +endif + ifdef CONFIG_NO_WPA2 CFLAGS += -DCONFIG_NO_WPA2 endif diff --git a/wpa_supplicant/ap.c b/wpa_supplicant/ap.c index ab63d6f..85a8bcd 100644 --- a/wpa_supplicant/ap.c +++ b/wpa_supplicant/ap.c @@ -22,6 +22,7 @@ #ifdef NEED_AP_MLME #include "ap/ieee802_11.h" #endif /* NEED_AP_MLME */ +#include "ap/beacon.h" #include "ap/ieee802_1x.h" #include "ap/wps_hostapd.h" #include "ap/ctrl_iface_ap.h" @@ -471,6 +472,23 @@ int ap_ctrl_iface_wpa_get_status(struct wpa_supplicant *wpa_s, char *buf, #endif /* CONFIG_CTRL_IFACE */ +int wpa_supplicant_ap_update_beacon(struct wpa_supplicant *wpa_s) +{ + struct hostapd_iface *iface = wpa_s->ap_iface; + struct wpa_ssid *ssid = wpa_s->current_ssid; + struct hostapd_data *hapd; + + if (ssid == NULL || wpa_s->ap_iface == NULL) + return -1; + + ieee802_11_set_beacons(iface); + hapd = iface->bss[0]; + hapd->drv.set_ap_wps_ie(hapd); + + return 0; +} + + int wpa_supplicant_ap_mac_addr_filter(struct wpa_supplicant *wpa_s, const u8 *addr) { diff --git a/wpa_supplicant/ap.h b/wpa_supplicant/ap.h index 381a432..b162d14 100644 --- a/wpa_supplicant/ap.h +++ b/wpa_supplicant/ap.h @@ -37,6 +37,7 @@ void ap_tx_status(void *ctx, const u8 *addr, void ap_rx_from_unknown_sta(void *ctx, const u8 *frame, size_t len); void ap_mgmt_rx(void *ctx, struct rx_mgmt *rx_mgmt); void ap_mgmt_tx_cb(void *ctx, const u8 *buf, size_t len, u16 stype, int ok); +int wpa_supplicant_ap_update_beacon(struct wpa_supplicant *wpa_s); int wpa_supplicant_ap_mac_addr_filter(struct wpa_supplicant *wpa_s, const u8 *addr); diff --git a/wpa_supplicant/p2p_supplicant.c b/wpa_supplicant/p2p_supplicant.c new file mode 100644 index 0000000..16864c7 --- /dev/null +++ b/wpa_supplicant/p2p_supplicant.c @@ -0,0 +1,3139 @@ +/* + * wpa_supplicant - P2P + * Copyright (c) 2009-2010, Atheros Communications + * + * 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 "eloop.h" +#include "common/ieee802_11_common.h" +#include "common/ieee802_11_defs.h" +#include "common/wpa_ctrl.h" +#include "wps/wps_i.h" +#include "p2p/p2p.h" +#include "ap/hostapd.h" +#include "wpa_supplicant_i.h" +#include "driver_i.h" +#include "ap.h" +#include "config_ssid.h" +#include "config.h" +#include "mlme.h" +#include "notify.h" +#include "scan.h" +#include "bss.h" +#include "wps_supplicant.h" +#include "p2p_supplicant.h" + + +static void wpas_p2p_long_listen_timeout(void *eloop_ctx, void *timeout_ctx); +static struct wpa_supplicant * +wpas_p2p_get_group_iface(struct wpa_supplicant *wpa_s, int addr_allocated, + int go); +static int wpas_p2p_join_start(struct wpa_supplicant *wpa_s); +static int wpas_p2p_create_iface(struct wpa_supplicant *wpa_s); + + +static void wpas_p2p_scan_res_handler(struct wpa_supplicant *wpa_s, + struct wpa_scan_results *scan_res) +{ + size_t i; + + if (wpa_s->global->p2p_disabled) + return; + + wpa_printf(MSG_DEBUG, "P2P: Scan results received (%d BSS)", + (int) scan_res->num); + + for (i = 0; i < scan_res->num; i++) { + struct wpa_scan_res *bss = scan_res->res[i]; + if (p2p_scan_res_handler(wpa_s->global->p2p, bss->bssid, + bss->freq, bss->level, + (const u8 *) (bss + 1), + bss->ie_len) > 0) + return; + } + + p2p_scan_res_handled(wpa_s->global->p2p); +} + + +static int wpas_p2p_scan(void *ctx, enum p2p_scan_type type, int freq) +{ + struct wpa_supplicant *wpa_s = ctx; + struct wpa_driver_scan_params params; + int ret; + struct wpabuf *wps_ie, *ies; + int social_channels[] = { 2412, 2437, 2462, 0, 0 }; + + os_memset(¶ms, 0, sizeof(params)); + + /* P2P Wildcard SSID */ + params.num_ssids = 1; + params.ssids[0].ssid = (u8 *) P2P_WILDCARD_SSID; + params.ssids[0].ssid_len = P2P_WILDCARD_SSID_LEN; + + wpa_s->wps->dev.p2p = 1; + wps_ie = wps_build_probe_req_ie(0, &wpa_s->wps->dev, wpa_s->wps->uuid, + WPS_REQ_ENROLLEE); + if (wps_ie == NULL) + return -1; + + ies = wpabuf_alloc(wpabuf_len(wps_ie) + 100); + if (ies == NULL) { + wpabuf_free(wps_ie); + return -1; + } + wpabuf_put_buf(ies, wps_ie); + wpabuf_free(wps_ie); + + p2p_scan_ie(wpa_s->global->p2p, ies); + + params.extra_ies = wpabuf_head(ies); + params.extra_ies_len = wpabuf_len(ies); + + switch (type) { + case P2P_SCAN_SOCIAL: + params.freqs = social_channels; + break; + case P2P_SCAN_FULL: + break; + case P2P_SCAN_SPECIFIC: + social_channels[0] = freq; + social_channels[1] = 0; + params.freqs = social_channels; + break; + case P2P_SCAN_SOCIAL_PLUS_ONE: + social_channels[3] = freq; + params.freqs = social_channels; + break; + } + + wpa_s->scan_res_handler = wpas_p2p_scan_res_handler; + if (wpa_s->drv_flags & WPA_DRIVER_FLAGS_USER_SPACE_MLME) + ret = ieee80211_sta_req_scan(wpa_s, ¶ms); + else + ret = wpa_drv_scan(wpa_s, ¶ms); + + wpabuf_free(ies); + + return ret; +} + + +#ifdef CONFIG_CLIENT_MLME +static void p2p_rx_action_mlme(void *ctx, const u8 *buf, size_t len, int freq) +{ + struct wpa_supplicant *wpa_s = ctx; + const struct ieee80211_mgmt *mgmt; + size_t hdr_len; + + if (wpa_s->global->p2p_disabled) + return; + mgmt = (const struct ieee80211_mgmt *) buf; + hdr_len = (const u8 *) &mgmt->u.action.u.vs_public_action.action - buf; + if (hdr_len > len) + return; + p2p_rx_action(wpa_s->global->p2p, mgmt->da, mgmt->sa, mgmt->bssid, + mgmt->u.action.category, + &mgmt->u.action.u.vs_public_action.action, + len - hdr_len, freq); +} +#endif /* CONFIG_CLIENT_MLME */ + + +static enum wpa_driver_if_type wpas_p2p_if_type(int p2p_group_interface) +{ + switch (p2p_group_interface) { + case P2P_GROUP_INTERFACE_PENDING: + return WPA_IF_P2P_GROUP; + case P2P_GROUP_INTERFACE_GO: + return WPA_IF_P2P_GO; + case P2P_GROUP_INTERFACE_CLIENT: + return WPA_IF_P2P_CLIENT; + } + + return WPA_IF_P2P_GROUP; +} + + +static void wpas_p2p_group_delete(struct wpa_supplicant *wpa_s) +{ + struct wpa_ssid *ssid; + char *gtype; + + ssid = wpa_s->current_ssid; + if (ssid == NULL) { + /* + * The current SSID was not known, but there may still be a + * pending P2P group interface waiting for provisioning. + */ + ssid = wpa_s->conf->ssid; + while (ssid) { + if (ssid->p2p_group && + (ssid->mode == WPAS_MODE_P2P_GROUP_FORMATION || + (ssid->key_mgmt & WPA_KEY_MGMT_WPS))) + break; + ssid = ssid->next; + } + } + if (wpa_s->p2p_group_interface == P2P_GROUP_INTERFACE_GO) + gtype = "GO"; + else if (wpa_s->p2p_group_interface == P2P_GROUP_INTERFACE_CLIENT || + (ssid && ssid->mode == WPAS_MODE_INFRA)) { + wpa_s->reassociate = 0; + wpa_s->disconnected = 1; + wpa_supplicant_deauthenticate(wpa_s, + WLAN_REASON_DEAUTH_LEAVING); + gtype = "client"; + } else + gtype = "GO"; + wpa_msg(wpa_s->parent, MSG_INFO, P2P_EVENT_GROUP_REMOVED "%s %s", + wpa_s->ifname, gtype); + if (wpa_s->p2p_group_interface != NOT_P2P_GROUP_INTERFACE) { + struct wpa_global *global; + char *ifname; + enum wpa_driver_if_type type; + wpa_printf(MSG_DEBUG, "P2P: Remove group interface %s", + wpa_s->ifname); + global = wpa_s->global; + ifname = os_strdup(wpa_s->ifname); + type = wpas_p2p_if_type(wpa_s->p2p_group_interface); + wpa_supplicant_remove_iface(wpa_s->global, wpa_s); + wpa_s = global->ifaces; + if (wpa_s && ifname) + wpa_drv_if_remove(wpa_s, type, ifname); + os_free(ifname); + return; + } + + wpa_printf(MSG_DEBUG, "P2P: Remove temporary group network"); + if (ssid && (ssid->p2p_group || + ssid->mode == WPAS_MODE_P2P_GROUP_FORMATION || + (ssid->key_mgmt & WPA_KEY_MGMT_WPS))) { + int id = ssid->id; + if (ssid == wpa_s->current_ssid) + wpa_s->current_ssid = NULL; + wpas_notify_network_removed(wpa_s, ssid); + wpa_config_remove_network(wpa_s->conf, id); + wpa_supplicant_clear_status(wpa_s); + } else { + wpa_printf(MSG_DEBUG, "P2P: Temporary group network not " + "found"); + } + wpa_supplicant_ap_deinit(wpa_s); +} + + +static int wpas_p2p_persistent_group(struct wpa_supplicant *wpa_s, + u8 *go_dev_addr, + const u8 *ssid, size_t ssid_len) +{ + struct wpa_bss *bss; + const u8 *bssid; + struct wpabuf *p2p; + u8 group_capab; + const u8 *addr; + + if (wpa_s->go_params) + bssid = wpa_s->go_params->peer_interface_addr; + else + bssid = wpa_s->bssid; + + bss = wpa_bss_get(wpa_s, bssid, ssid, ssid_len); + if (bss == NULL) { + u8 iface_addr[ETH_ALEN]; + if (p2p_get_interface_addr(wpa_s->global->p2p, bssid, + iface_addr) == 0) + bss = wpa_bss_get(wpa_s, iface_addr, ssid, ssid_len); + } + if (bss == NULL) { + wpa_printf(MSG_DEBUG, "P2P: Could not figure out whether " + "group is persistent - BSS " MACSTR " not found", + MAC2STR(bssid)); + return 0; + } + + p2p = wpa_bss_get_vendor_ie_multi(bss, P2P_IE_VENDOR_TYPE); + if (p2p == NULL) { + wpa_printf(MSG_DEBUG, "P2P: Could not figure out whether " + "group is persistent - BSS " MACSTR + " did not include P2P IE", MAC2STR(bssid)); + wpa_hexdump(MSG_DEBUG, "P2P: Probe Response IEs", + (u8 *) (bss + 1), bss->ie_len); + wpa_hexdump(MSG_DEBUG, "P2P: Beacon IEs", + ((u8 *) bss + 1) + bss->ie_len, + bss->beacon_ie_len); + return 0; + } + + group_capab = p2p_get_group_capab(p2p); + addr = p2p_get_go_dev_addr(p2p); + wpa_printf(MSG_DEBUG, "P2P: Checking whether group is persistent: " + "group_capab=0x%x", group_capab); + if (addr) { + os_memcpy(go_dev_addr, addr, ETH_ALEN); + wpa_printf(MSG_DEBUG, "P2P: GO Device Address " MACSTR, + MAC2STR(addr)); + } else + os_memset(go_dev_addr, 0, ETH_ALEN); + wpabuf_free(p2p); + + wpa_printf(MSG_DEBUG, "P2P: BSS " MACSTR " group_capab=0x%x " + "go_dev_addr=" MACSTR, + MAC2STR(bssid), group_capab, MAC2STR(go_dev_addr)); + + return group_capab & P2P_GROUP_CAPAB_PERSISTENT_GROUP; +} + + +static void wpas_p2p_store_persistent_group(struct wpa_supplicant *wpa_s, + struct wpa_ssid *ssid, + const u8 *go_dev_addr) +{ + struct wpa_ssid *s; + int changed = 0; + + wpa_printf(MSG_DEBUG, "P2P: Storing credentials for a persistent " + "group (GO Dev Addr " MACSTR ")", MAC2STR(go_dev_addr)); + for (s = wpa_s->conf->ssid; s; s = s->next) { + if (s->disabled == 2 && + os_memcmp(go_dev_addr, s->bssid, ETH_ALEN) == 0 && + s->ssid_len == ssid->ssid_len && + os_memcmp(ssid->ssid, s->ssid, ssid->ssid_len) == 0) + break; + } + + if (s) { + wpa_printf(MSG_DEBUG, "P2P: Update existing persistent group " + "entry"); + if (ssid->passphrase && !s->passphrase) + changed = 1; + else if (ssid->passphrase && s->passphrase && + os_strcmp(ssid->passphrase, s->passphrase) != 0) + changed = 1; + } else { + wpa_printf(MSG_DEBUG, "P2P: Create a new persistent group " + "entry"); + changed = 1; + s = wpa_config_add_network(wpa_s->conf); + if (s == NULL) + return; + wpa_config_set_network_defaults(s); + } + + s->p2p_group = 1; + s->p2p_persistent_group = 1; + s->disabled = 2; + s->bssid_set = 1; + os_memcpy(s->bssid, go_dev_addr, ETH_ALEN); + s->mode = ssid->mode; + s->auth_alg = WPA_AUTH_ALG_OPEN; + s->key_mgmt = WPA_KEY_MGMT_PSK; + s->proto = WPA_PROTO_RSN; + s->pairwise_cipher = WPA_CIPHER_CCMP; + if (ssid->passphrase) { + os_free(s->passphrase); + s->passphrase = os_strdup(ssid->passphrase); + } + if (ssid->psk_set) { + s->psk_set = 1; + os_memcpy(s->psk, ssid->psk, 32); + } + if (s->passphrase && !s->psk_set) + wpa_config_update_psk(s); + if (s->ssid == NULL || s->ssid_len < ssid->ssid_len) { + os_free(s->ssid); + s->ssid = os_malloc(ssid->ssid_len); + } + if (s->ssid) { + s->ssid_len = ssid->ssid_len; + os_memcpy(s->ssid, ssid->ssid, s->ssid_len); + } + +#ifndef CONFIG_NO_CONFIG_WRITE + if (changed && wpa_s->conf->update_config && + wpa_config_write(wpa_s->confname, wpa_s->conf)) { + wpa_printf(MSG_DEBUG, "P2P: Failed to update configuration"); + } +#endif /* CONFIG_NO_CONFIG_WRITE */ +} + + +static void wpas_group_formation_completed(struct wpa_supplicant *wpa_s, + int success) +{ + struct wpa_ssid *ssid; + const char *ssid_txt; + int client; + int persistent; + u8 go_dev_addr[ETH_ALEN]; + + /* + * This callback is likely called for the main interface. Update wpa_s + * to use the group interface if a new interface was created for the + * group. + */ + if (wpa_s->global->p2p_group_formation) + wpa_s = wpa_s->global->p2p_group_formation; + wpa_s->p2p_in_provisioning = 0; + + if (!success) { + wpa_msg(wpa_s->parent, MSG_INFO, + P2P_EVENT_GROUP_FORMATION_FAILURE); + wpas_p2p_group_delete(wpa_s); + return; + } + + wpa_msg(wpa_s->parent, MSG_INFO, P2P_EVENT_GROUP_FORMATION_SUCCESS); + + ssid = wpa_s->current_ssid; + if (ssid && ssid->mode == WPAS_MODE_P2P_GROUP_FORMATION) { + ssid->mode = WPAS_MODE_P2P_GO; + p2p_group_notif_formation_done(wpa_s->p2p_group); + wpa_supplicant_ap_mac_addr_filter(wpa_s, NULL); + } + + persistent = 0; + if (ssid) { + ssid_txt = wpa_ssid_txt(ssid->ssid, ssid->ssid_len); + client = ssid->mode == WPAS_MODE_INFRA; + if (ssid->mode == WPAS_MODE_P2P_GO) { + persistent = ssid->p2p_persistent_group; + os_memcpy(go_dev_addr, wpa_s->parent->own_addr, + ETH_ALEN); + } else + persistent = wpas_p2p_persistent_group(wpa_s, + go_dev_addr, + ssid->ssid, + ssid->ssid_len); + } else { + ssid_txt = ""; + client = wpa_s->p2p_group_interface == + P2P_GROUP_INTERFACE_CLIENT; + } + + wpa_s->show_group_started = 0; + if (client) { + /* + * Indicate event only after successfully completed 4-way + * handshake, i.e., when the interface is ready for data + * packets. + */ + wpa_s->show_group_started = 1; + } else if (ssid && ssid->passphrase == NULL && ssid->psk_set) { + char psk[65]; + wpa_snprintf_hex(psk, sizeof(psk), ssid->psk, 32); + wpa_msg(wpa_s->parent, MSG_INFO, P2P_EVENT_GROUP_STARTED + "%s GO ssid=\"%s\" psk=%s go_dev_addr=" MACSTR "%s", + wpa_s->ifname, ssid_txt, psk, MAC2STR(go_dev_addr), + persistent ? " [PERSISTENT]" : ""); + } else { + wpa_msg(wpa_s->parent, MSG_INFO, P2P_EVENT_GROUP_STARTED + "%s GO ssid=\"%s\" passphrase=\"%s\" go_dev_addr=" + MACSTR "%s", + wpa_s->ifname, ssid_txt, + ssid && ssid->passphrase ? ssid->passphrase : "", + MAC2STR(go_dev_addr), + persistent ? " [PERSISTENT]" : ""); + } + + if (persistent) + wpas_p2p_store_persistent_group(wpa_s->parent, ssid, + go_dev_addr); +} + + +static void wpas_send_action_cb(void *eloop_ctx, void *timeout_ctx) +{ + struct wpa_supplicant *wpa_s = eloop_ctx; + struct wpa_supplicant *iface; + int res; + int without_roc; + + without_roc = wpa_s->pending_action_without_roc; + wpa_s->pending_action_without_roc = 0; + + if (wpa_s->pending_action_tx == NULL) + return; + + if (wpa_s->off_channel_freq != wpa_s->pending_action_freq && + wpa_s->pending_action_freq != 0) { + wpa_printf(MSG_DEBUG, "P2P: Pending Action frame TX " + "waiting for another freq=%u (off_channel_freq=%u)", + wpa_s->pending_action_freq, + wpa_s->off_channel_freq); + if (without_roc && wpa_s->off_channel_freq == 0) { + /* + * We may get here if wpas_send_action() found us to be + * on the correct channel, but remain-on-channel cancel + * event was received before getting here. + */ + wpa_printf(MSG_DEBUG, "P2P: Schedule " + "remain-on-channel to send Action frame"); + if (wpa_drv_remain_on_channel( + wpa_s, wpa_s->pending_action_freq, 200) < + 0) { + wpa_printf(MSG_DEBUG, "P2P: Failed to request " + "driver to remain on channel (%u " + "MHz) for Action Frame TX", + wpa_s->pending_action_freq); + } + } + return; + } + + /* + * This call is likely going to be on the P2P device instance if the + * driver uses a separate interface for that purpose. However, some + * Action frames are actually sent within a P2P Group and when that is + * the case, we need to follow power saving (e.g., GO buffering the + * frame for a client in PS mode or a client following the adverised + * NoA from its GO). To make that easier for the driver, select the + * correct group interface here. + */ + if (os_memcmp(wpa_s->pending_action_src, wpa_s->own_addr, ETH_ALEN) != + 0) { + /* + * Try to find a group interface that matches with the source + * address. + */ + iface = wpa_s->global->ifaces; + while (iface) { + if (os_memcmp(wpa_s->pending_action_src, + iface->own_addr, ETH_ALEN) == 0) + break; + } + if (iface) { + wpa_printf(MSG_DEBUG, "P2P: Use group interface %s " + "instead of interface %s for Action TX", + iface->ifname, wpa_s->ifname); + } else + iface = wpa_s; + } else + iface = wpa_s; + + wpa_printf(MSG_DEBUG, "P2P: Sending pending Action frame to " + MACSTR " using interface %s", + MAC2STR(wpa_s->pending_action_dst), iface->ifname); + res = wpa_drv_send_action(iface, wpa_s->pending_action_freq, + wpa_s->pending_action_dst, + wpa_s->pending_action_src, + wpa_s->pending_action_bssid, + wpabuf_head(wpa_s->pending_action_tx), + wpabuf_len(wpa_s->pending_action_tx)); + if (res) { + wpa_printf(MSG_DEBUG, "P2P: Failed to send the pending " + "Action frame"); + /* + * Use fake TX status event to allow P2P state machine to + * continue. + */ + wpas_send_action_tx_status( + wpa_s, wpa_s->pending_action_dst, + wpabuf_head(wpa_s->pending_action_tx), + wpabuf_len(wpa_s->pending_action_tx), 0); + } +} + + +void wpas_send_action_tx_status(struct wpa_supplicant *wpa_s, const u8 *dst, + const u8 *data, size_t data_len, int ack) +{ + if (wpa_s->global->p2p_disabled) + return; + + if (wpa_s->pending_action_tx == NULL) { + wpa_printf(MSG_DEBUG, "P2P: Ignore Action TX status - no " + "pending operation"); + return; + } + + if (os_memcmp(dst, wpa_s->pending_action_dst, ETH_ALEN) != 0) { + wpa_printf(MSG_DEBUG, "P2P: Ignore Action TX status - unknown " + "destination address"); + return; + } + + wpabuf_free(wpa_s->pending_action_tx); + wpa_s->pending_action_tx = NULL; + + p2p_send_action_cb(wpa_s->global->p2p, wpa_s->pending_action_freq, + wpa_s->pending_action_dst, + wpa_s->pending_action_src, + wpa_s->pending_action_bssid, + ack); + + if (wpa_s->pending_pd_before_join && + (os_memcmp(wpa_s->pending_action_dst, wpa_s->pending_join_dev_addr, + ETH_ALEN) == 0 || + os_memcmp(wpa_s->pending_action_dst, + wpa_s->pending_join_iface_addr, ETH_ALEN) == 0)) { + wpa_s->pending_pd_before_join = 0; + wpa_printf(MSG_DEBUG, "P2P: Starting pending " + "join-existing-group operation"); + wpas_p2p_join_start(wpa_s); + } +} + + +static int wpas_send_action(void *ctx, unsigned int freq, const u8 *dst, + const u8 *src, const u8 *bssid, const u8 *buf, + size_t len, unsigned int wait_time) +{ + struct wpa_supplicant *wpa_s = ctx; + + wpa_printf(MSG_DEBUG, "P2P: Send action frame: freq=%d dst=" MACSTR + " src=" MACSTR " bssid=" MACSTR, + freq, MAC2STR(dst), MAC2STR(src), MAC2STR(bssid)); + + if (wpa_s->pending_action_tx) { + wpa_printf(MSG_DEBUG, "P2P: Dropped pending Action frame TX " + "to " MACSTR, MAC2STR(wpa_s->pending_action_dst)); + wpabuf_free(wpa_s->pending_action_tx); + } + wpa_s->pending_action_tx = wpabuf_alloc(len); + if (wpa_s->pending_action_tx == NULL) + return -1; + wpabuf_put_data(wpa_s->pending_action_tx, buf, len); + os_memcpy(wpa_s->pending_action_src, src, ETH_ALEN); + os_memcpy(wpa_s->pending_action_dst, dst, ETH_ALEN); + os_memcpy(wpa_s->pending_action_bssid, bssid, ETH_ALEN); + wpa_s->pending_action_freq = freq; + + if (wpa_s->off_channel_freq == freq || freq == 0) { + /* Already on requested channel; send immediately */ + /* TODO: Would there ever be need to extend the current + * duration on the channel? */ + wpa_s->pending_action_without_roc = 1; + eloop_cancel_timeout(wpas_send_action_cb, wpa_s, NULL); + eloop_register_timeout(0, 0, wpas_send_action_cb, wpa_s, NULL); + return 0; + } + wpa_s->pending_action_without_roc = 0; + + wpa_printf(MSG_DEBUG, "P2P: Schedule Action frame to be transmitted " + "once the driver gets to the requested channel"); + if (wait_time > wpa_s->max_remain_on_chan) + wait_time = wpa_s->max_remain_on_chan; + if (wpa_drv_remain_on_channel(wpa_s, freq, wait_time) < 0) { + wpa_printf(MSG_DEBUG, "P2P: Failed to request driver " + "to remain on channel (%u MHz) for Action " + "Frame TX", freq); + return -1; + } + + return 0; +} + + +static void wpas_send_action_done(void *ctx) +{ + struct wpa_supplicant *wpa_s = ctx; + wpa_printf(MSG_DEBUG, "P2P: Action frame sequence done notification"); + wpabuf_free(wpa_s->pending_action_tx); + wpa_s->pending_action_tx = NULL; + if (wpa_s->off_channel_freq) { + wpa_drv_cancel_remain_on_channel(wpa_s); + wpa_s->off_channel_freq = 0; + } +} + + +static int wpas_copy_go_neg_results(struct wpa_supplicant *wpa_s, + struct p2p_go_neg_results *params) +{ + if (wpa_s->go_params == NULL) { + wpa_s->go_params = os_malloc(sizeof(*params)); + if (wpa_s->go_params == NULL) + return -1; + } + os_memcpy(wpa_s->go_params, params, sizeof(*params)); + return 0; +} + + +static void wpas_start_wps_enrollee(struct wpa_supplicant *wpa_s, + struct p2p_go_neg_results *res) +{ + wpa_supplicant_ap_deinit(wpa_s); + wpas_copy_go_neg_results(wpa_s, res); + if (res->wps_method == WPS_PBC) + wpas_wps_start_pbc(wpa_s, NULL /* res->peer_interface_addr */, + 1); + else + wpas_wps_start_pin(wpa_s, res->peer_interface_addr, + wpa_s->p2p_pin, 1); +} + + +static void p2p_go_configured(void *ctx, void *data) +{ + struct wpa_supplicant *wpa_s = ctx; + struct p2p_go_neg_results *params = data; + struct wpa_ssid *ssid; + + ssid = wpa_s->current_ssid; + if (ssid && ssid->mode == WPAS_MODE_P2P_GO) { + wpa_printf(MSG_DEBUG, "P2P: Group setup without provisioning"); + wpa_msg(wpa_s->parent, MSG_INFO, P2P_EVENT_GROUP_STARTED + "%s GO ssid=\"%s\" passphrase=\"%s\" go_dev_addr=" + MACSTR "%s", + wpa_s->ifname, + wpa_ssid_txt(ssid->ssid, ssid->ssid_len), + params->passphrase ? params->passphrase : "", + MAC2STR(wpa_s->parent->own_addr), + params->persistent_group ? " [PERSISTENT]" : ""); + if (params->persistent_group) + wpas_p2p_store_persistent_group( + wpa_s->parent, ssid, + wpa_s->parent->own_addr); + return; + } + + wpa_printf(MSG_DEBUG, "P2P: Setting up WPS for GO provisioning"); + if (wpa_supplicant_ap_mac_addr_filter(wpa_s, + params->peer_interface_addr)) { + wpa_printf(MSG_DEBUG, "P2P: Failed to setup MAC address " + "filtering"); + return; + } + if (params->wps_method == WPS_PBC) + wpa_supplicant_ap_wps_pbc(wpa_s, params->peer_interface_addr); + else if (wpa_s->p2p_pin[0]) + wpa_supplicant_ap_wps_pin(wpa_s, params->peer_interface_addr, + wpa_s->p2p_pin, NULL, 0); + os_free(wpa_s->go_params); + wpa_s->go_params = NULL; +} + + +static void wpas_start_wps_go(struct wpa_supplicant *wpa_s, + struct p2p_go_neg_results *params, + int group_formation) +{ + struct wpa_ssid *ssid; + + if (wpas_copy_go_neg_results(wpa_s, params) < 0) + return; + + ssid = wpa_config_add_network(wpa_s->conf); + if (ssid == NULL) + return; + + wpa_config_set_network_defaults(ssid); + ssid->temporary = 1; + ssid->p2p_group = 1; + ssid->p2p_persistent_group = params->persistent_group; + ssid->mode = group_formation ? WPAS_MODE_P2P_GROUP_FORMATION : + WPAS_MODE_P2P_GO; + ssid->frequency = params->freq; + ssid->ssid = os_zalloc(params->ssid_len + 1); + if (ssid->ssid) { + os_memcpy(ssid->ssid, params->ssid, params->ssid_len); + ssid->ssid_len = params->ssid_len; + } + ssid->auth_alg = WPA_AUTH_ALG_OPEN; + ssid->key_mgmt = WPA_KEY_MGMT_PSK; + ssid->proto = WPA_PROTO_RSN; + ssid->pairwise_cipher = WPA_CIPHER_CCMP; + ssid->passphrase = os_strdup(params->passphrase); + + wpa_s->ap_configured_cb = p2p_go_configured; + wpa_s->ap_configured_cb_ctx = wpa_s; + wpa_s->ap_configured_cb_data = wpa_s->go_params; + wpa_s->connect_without_scan = 1; + wpa_s->reassociate = 1; + wpa_s->disconnected = 0; + wpa_supplicant_req_scan(wpa_s, 0, 0); +} + + +static void wpas_p2p_clone_config(struct wpa_supplicant *dst, + const struct wpa_supplicant *src) +{ + struct wpa_config *d; + const struct wpa_config *s; + + d = dst->conf; + s = src->conf; + +#define C(n) if (s->n) d->n = os_strdup(s->n) + C(device_name); + C(manufacturer); + C(model_name); + C(model_number); + C(serial_number); + C(device_type); + C(config_methods); +#undef C +} + + +static int wpas_p2p_add_group_interface(struct wpa_supplicant *wpa_s, + enum wpa_driver_if_type type) +{ + char ifname[120], force_ifname[120]; + + if (wpa_s->pending_interface_name[0]) { + wpa_printf(MSG_DEBUG, "P2P: Pending virtual interface exists " + "- skip creation of a new one"); + if (is_zero_ether_addr(wpa_s->pending_interface_addr)) { + wpa_printf(MSG_DEBUG, "P2P: Pending virtual address " + "unknown?! ifname='%s'", + wpa_s->pending_interface_name); + return -1; + } + return 0; + } + + os_snprintf(ifname, sizeof(ifname), "%s-p2p-%d", wpa_s->ifname, + wpa_s->p2p_group_idx); + force_ifname[0] = '\0'; + + wpa_printf(MSG_DEBUG, "P2P: Create a new interface %s for the group", + ifname); + wpa_s->p2p_group_idx++; + + wpa_s->pending_interface_type = type; + if (wpa_drv_if_add(wpa_s, type, ifname, NULL, NULL, force_ifname, + wpa_s->pending_interface_addr) < 0) { + wpa_printf(MSG_ERROR, "P2P: Failed to create new group " + "interface"); + return -1; + } + + if (force_ifname[0]) { + wpa_printf(MSG_DEBUG, "P2P: Driver forced interface name %s", + force_ifname); + os_strlcpy(wpa_s->pending_interface_name, force_ifname, + sizeof(wpa_s->pending_interface_name)); + } else + os_strlcpy(wpa_s->pending_interface_name, ifname, + sizeof(wpa_s->pending_interface_name)); + wpa_printf(MSG_DEBUG, "P2P: Created pending virtual interface %s addr " + MACSTR, wpa_s->pending_interface_name, + MAC2STR(wpa_s->pending_interface_addr)); + + return 0; +} + + +static void wpas_p2p_remove_pending_group_interface( + struct wpa_supplicant *wpa_s) +{ + if (!wpa_s->pending_interface_name[0] || + is_zero_ether_addr(wpa_s->pending_interface_addr)) + return; /* No pending virtual interface */ + + wpa_printf(MSG_DEBUG, "P2P: Removing pending group interface %s", + wpa_s->pending_interface_name); + wpa_drv_if_remove(wpa_s, wpa_s->pending_interface_type, + wpa_s->pending_interface_name); + os_memset(wpa_s->pending_interface_addr, 0, ETH_ALEN); + wpa_s->pending_interface_name[0] = '\0'; +} + + +static struct wpa_supplicant * +wpas_p2p_init_group_interface(struct wpa_supplicant *wpa_s, int go) +{ + struct wpa_interface iface; + struct wpa_supplicant *group_wpa_s; + + if (!wpa_s->pending_interface_name[0]) { + wpa_printf(MSG_ERROR, "P2P: No pending group interface"); + return NULL; + } + + os_memset(&iface, 0, sizeof(iface)); + iface.ifname = wpa_s->pending_interface_name; + iface.driver = wpa_s->driver->name; + iface.ctrl_interface = wpa_s->conf->ctrl_interface; + iface.driver_param = wpa_s->conf->driver_param; + group_wpa_s = wpa_supplicant_add_iface(wpa_s->global, &iface); + if (group_wpa_s == NULL) { + wpa_printf(MSG_ERROR, "P2P: Failed to create new " + "wpa_supplicant interface"); + return NULL; + } + wpa_s->pending_interface_name[0] = '\0'; + group_wpa_s->parent = wpa_s; + group_wpa_s->p2p_group_interface = go ? P2P_GROUP_INTERFACE_GO : + P2P_GROUP_INTERFACE_CLIENT; + wpa_s->global->p2p_group_formation = group_wpa_s; + + wpas_p2p_clone_config(group_wpa_s, wpa_s); + + return group_wpa_s; +} + + +static void wpas_p2p_group_formation_timeout(void *eloop_ctx, + void *timeout_ctx) +{ + struct wpa_supplicant *wpa_s = eloop_ctx; + wpa_printf(MSG_DEBUG, "P2P: Group Formation timed out"); + if (wpa_s->global->p2p) + p2p_group_formation_failed(wpa_s->global->p2p); + wpas_group_formation_completed(wpa_s, 0); +} + + +void wpas_go_neg_completed(void *ctx, struct p2p_go_neg_results *res) +{ + struct wpa_supplicant *wpa_s = ctx; + + if (wpa_s->off_channel_freq) { + wpa_drv_cancel_remain_on_channel(wpa_s); + wpa_s->off_channel_freq = 0; + } + + if (res->status) { + wpa_msg(wpa_s, MSG_INFO, P2P_EVENT_GO_NEG_FAILURE "status=%d", + res->status); + wpas_p2p_remove_pending_group_interface(wpa_s); + return; + } + + wpa_msg(wpa_s, MSG_INFO, P2P_EVENT_GO_NEG_SUCCESS); + + if (wpa_s->create_p2p_iface) { + struct wpa_supplicant *group_wpa_s = + wpas_p2p_init_group_interface(wpa_s, res->role_go); + if (group_wpa_s == NULL) { + wpas_p2p_remove_pending_group_interface(wpa_s); + return; + } + if (group_wpa_s != wpa_s) + os_memcpy(group_wpa_s->p2p_pin, wpa_s->p2p_pin, + sizeof(group_wpa_s->p2p_pin)); + os_memset(wpa_s->pending_interface_addr, 0, ETH_ALEN); + wpa_s->pending_interface_name[0] = '\0'; + group_wpa_s->p2p_in_provisioning = 1; + + if (res->role_go) + wpas_start_wps_go(group_wpa_s, res, 1); + else + wpas_start_wps_enrollee(group_wpa_s, res); + } else { + wpa_s->p2p_in_provisioning = 1; + wpa_s->global->p2p_group_formation = wpa_s; + + if (res->role_go) + wpas_start_wps_go(wpa_s, res, 1); + else + wpas_start_wps_enrollee(ctx, res); + } + + wpa_s->p2p_long_listen = 0; + eloop_cancel_timeout(wpas_p2p_long_listen_timeout, wpa_s, NULL); + + eloop_cancel_timeout(wpas_p2p_group_formation_timeout, wpa_s, NULL); + /* TODO: add peer Config Timeout */ + eloop_register_timeout(15, 0, wpas_p2p_group_formation_timeout, wpa_s, + NULL); +} + + +void wpas_go_neg_req_rx(void *ctx, const u8 *src) +{ + struct wpa_supplicant *wpa_s = ctx; + wpa_msg(wpa_s, MSG_INFO, P2P_EVENT_GO_NEG_REQUEST MACSTR, + MAC2STR(src)); +} + + +void wpas_dev_found(void *ctx, const u8 *addr, const u8 *dev_addr, + const u8 *pri_dev_type, const char *dev_name, + u16 config_methods, u8 dev_capab, u8 group_capab) +{ + struct wpa_supplicant *wpa_s = ctx; + char devtype[WPS_DEV_TYPE_BUFSIZE]; + wpa_msg(wpa_s, MSG_INFO, P2P_EVENT_DEVICE_FOUND MACSTR + " p2p_dev_addr=" MACSTR + " pri_dev_type=%s name='%s' config_methods=0x%x " + "dev_capab=0x%x group_capab=0x%x", + MAC2STR(addr), MAC2STR(dev_addr), + wps_dev_type_bin2str(pri_dev_type, devtype, sizeof(devtype)), + dev_name, config_methods, dev_capab, group_capab); +} + + +static int wpas_start_listen(void *ctx, unsigned int freq, + unsigned int duration, + const struct wpabuf *probe_resp_ie) +{ + struct wpa_supplicant *wpa_s = ctx; + + wpa_drv_set_ap_wps_ie(wpa_s, NULL, probe_resp_ie); + + if (wpa_drv_probe_req_report(wpa_s, 1) < 0) { + wpa_printf(MSG_DEBUG, "P2P: Failed to request the driver to " + "report received Probe Request frames"); + return -1; + } + + wpa_s->pending_listen_freq = freq; + wpa_s->pending_listen_duration = duration; + + if (wpa_drv_remain_on_channel(wpa_s, freq, duration) < 0) { + wpa_printf(MSG_DEBUG, "P2P: Failed to request the driver " + "to remain on channel (%u MHz) for Listen " + "state", freq); + wpa_s->pending_listen_freq = 0; + return -1; + } + + return 0; +} + + +static void wpas_stop_listen(void *ctx) +{ + struct wpa_supplicant *wpa_s = ctx; + if (wpa_s->off_channel_freq) { + wpa_drv_cancel_remain_on_channel(wpa_s); + wpa_s->off_channel_freq = 0; + } + wpa_drv_probe_req_report(wpa_s, 0); +} + + +static int wpas_send_probe_resp(void *ctx, const struct wpabuf *buf) +{ + struct wpa_supplicant *wpa_s = ctx; + return wpa_drv_send_mlme(wpa_s, wpabuf_head(buf), wpabuf_len(buf)); +} + + +static struct p2p_srv_bonjour * +wpas_p2p_service_get_bonjour(struct wpa_supplicant *wpa_s, + const struct wpabuf *query) +{ + struct p2p_srv_bonjour *bsrv; + size_t len; + + len = wpabuf_len(query); + dl_list_for_each(bsrv, &wpa_s->global->p2p_srv_bonjour, + struct p2p_srv_bonjour, list) { + if (len == wpabuf_len(bsrv->query) && + os_memcmp(wpabuf_head(query), wpabuf_head(bsrv->query), + len) == 0) + return bsrv; + } + return NULL; +} + + +static struct p2p_srv_upnp * +wpas_p2p_service_get_upnp(struct wpa_supplicant *wpa_s, u8 version, + const char *service) +{ + struct p2p_srv_upnp *usrv; + + dl_list_for_each(usrv, &wpa_s->global->p2p_srv_upnp, + struct p2p_srv_upnp, list) { + if (version == usrv->version && + os_strcmp(service, usrv->service) == 0) + return usrv; + } + return NULL; +} + + +static void wpas_sd_add_proto_not_avail(struct wpabuf *resp, u8 srv_proto, + u8 srv_trans_id) +{ + u8 *len_pos; + + if (wpabuf_tailroom(resp) < 5) + return; + + /* Length (to be filled) */ + len_pos = wpabuf_put(resp, 2); + wpabuf_put_u8(resp, srv_proto); + wpabuf_put_u8(resp, srv_trans_id); + /* Status Code */ + wpabuf_put_u8(resp, P2P_SD_PROTO_NOT_AVAILABLE); + /* Response Data: empty */ + WPA_PUT_LE16(len_pos, (u8 *) wpabuf_put(resp, 0) - len_pos - 2); +} + + +static void wpas_sd_all_bonjour(struct wpa_supplicant *wpa_s, + struct wpabuf *resp, u8 srv_trans_id) +{ + struct p2p_srv_bonjour *bsrv; + u8 *len_pos; + + wpa_printf(MSG_DEBUG, "P2P: SD Request for all Bonjour services"); + + if (dl_list_empty(&wpa_s->global->p2p_srv_bonjour)) { + wpa_printf(MSG_DEBUG, "P2P: Bonjour protocol not available"); + return; + } + + dl_list_for_each(bsrv, &wpa_s->global->p2p_srv_bonjour, + struct p2p_srv_bonjour, list) { + if (wpabuf_tailroom(resp) < + 5 + wpabuf_len(bsrv->query) + wpabuf_len(bsrv->resp)) + return; + /* Length (to be filled) */ + len_pos = wpabuf_put(resp, 2); + wpabuf_put_u8(resp, P2P_SERV_BONJOUR); + wpabuf_put_u8(resp, srv_trans_id); + /* Status Code */ + wpabuf_put_u8(resp, P2P_SD_SUCCESS); + wpa_hexdump_ascii(MSG_DEBUG, "P2P: Matching Bonjour service", + wpabuf_head(bsrv->resp), + wpabuf_len(bsrv->resp)); + /* Response Data */ + wpabuf_put_buf(resp, bsrv->query); /* Key */ + wpabuf_put_buf(resp, bsrv->resp); /* Value */ + WPA_PUT_LE16(len_pos, (u8 *) wpabuf_put(resp, 0) - len_pos - + 2); + } +} + + +static void wpas_sd_req_bonjour(struct wpa_supplicant *wpa_s, + struct wpabuf *resp, u8 srv_trans_id, + const u8 *query, size_t query_len) +{ + struct p2p_srv_bonjour *bsrv; + struct wpabuf buf; + u8 *len_pos; + + wpa_hexdump_ascii(MSG_DEBUG, "P2P: SD Request for Bonjour", + query, query_len); + if (dl_list_empty(&wpa_s->global->p2p_srv_bonjour)) { + wpa_printf(MSG_DEBUG, "P2P: Bonjour protocol not available"); + wpas_sd_add_proto_not_avail(resp, P2P_SERV_BONJOUR, + srv_trans_id); + return; + } + + if (query_len == 0) { + wpas_sd_all_bonjour(wpa_s, resp, srv_trans_id); + return; + } + + if (wpabuf_tailroom(resp) < 5) + return; + /* Length (to be filled) */ + len_pos = wpabuf_put(resp, 2); + wpabuf_put_u8(resp, P2P_SERV_BONJOUR); + wpabuf_put_u8(resp, srv_trans_id); + + wpabuf_set(&buf, query, query_len); + bsrv = wpas_p2p_service_get_bonjour(wpa_s, &buf); + if (bsrv == NULL) { + wpa_printf(MSG_DEBUG, "P2P: Requested Bonjour service not " + "available"); + + /* Status Code */ + wpabuf_put_u8(resp, P2P_SD_QUERY_DATA_NOT_AVAILABLE); + /* Response Data: empty */ + WPA_PUT_LE16(len_pos, (u8 *) wpabuf_put(resp, 0) - len_pos - + 2); + return; + } + + /* Status Code */ + wpabuf_put_u8(resp, P2P_SD_SUCCESS); + wpa_hexdump_ascii(MSG_DEBUG, "P2P: Matching Bonjour service", + wpabuf_head(bsrv->resp), wpabuf_len(bsrv->resp)); + + if (wpabuf_tailroom(resp) >= + wpabuf_len(bsrv->query) + wpabuf_len(bsrv->resp)) { + /* Response Data */ + wpabuf_put_buf(resp, bsrv->query); /* Key */ + wpabuf_put_buf(resp, bsrv->resp); /* Value */ + } + WPA_PUT_LE16(len_pos, (u8 *) wpabuf_put(resp, 0) - len_pos - 2); +} + + +static void wpas_sd_all_upnp(struct wpa_supplicant *wpa_s, + struct wpabuf *resp, u8 srv_trans_id) +{ + struct p2p_srv_upnp *usrv; + u8 *len_pos; + + wpa_printf(MSG_DEBUG, "P2P: SD Request for all UPnP services"); + + if (dl_list_empty(&wpa_s->global->p2p_srv_upnp)) { + wpa_printf(MSG_DEBUG, "P2P: UPnP protocol not available"); + return; + } + + dl_list_for_each(usrv, &wpa_s->global->p2p_srv_upnp, + struct p2p_srv_upnp, list) { + if (wpabuf_tailroom(resp) < 5 + 1 + os_strlen(usrv->service)) + return; + + /* Length (to be filled) */ + len_pos = wpabuf_put(resp, 2); + wpabuf_put_u8(resp, P2P_SERV_UPNP); + wpabuf_put_u8(resp, srv_trans_id); + + /* Status Code */ + wpabuf_put_u8(resp, P2P_SD_SUCCESS); + /* Response Data */ + wpabuf_put_u8(resp, usrv->version); + wpa_printf(MSG_DEBUG, "P2P: Matching UPnP Service: %s", + usrv->service); + wpabuf_put_str(resp, usrv->service); + WPA_PUT_LE16(len_pos, (u8 *) wpabuf_put(resp, 0) - len_pos - + 2); + } +} + + +static void wpas_sd_req_upnp(struct wpa_supplicant *wpa_s, + struct wpabuf *resp, u8 srv_trans_id, + const u8 *query, size_t query_len) +{ + struct p2p_srv_upnp *usrv; + u8 *len_pos; + u8 version; + char *str; + int count = 0; + + wpa_hexdump_ascii(MSG_DEBUG, "P2P: SD Request for UPnP", + query, query_len); + + if (dl_list_empty(&wpa_s->global->p2p_srv_upnp)) { + wpa_printf(MSG_DEBUG, "P2P: UPnP protocol not available"); + wpas_sd_add_proto_not_avail(resp, P2P_SERV_UPNP, + srv_trans_id); + return; + } + + if (query_len == 0) { + wpas_sd_all_upnp(wpa_s, resp, srv_trans_id); + return; + } + + version = query[0]; + str = os_malloc(query_len); + if (str == NULL) + return; + os_memcpy(str, query + 1, query_len - 1); + str[query_len - 1] = '\0'; + + if (wpabuf_tailroom(resp) < 5) + return; + + /* Length (to be filled) */ + len_pos = wpabuf_put(resp, 2); + wpabuf_put_u8(resp, P2P_SERV_UPNP); + wpabuf_put_u8(resp, srv_trans_id); + + dl_list_for_each(usrv, &wpa_s->global->p2p_srv_upnp, + struct p2p_srv_upnp, list) { + if (version != usrv->version) + continue; + + if (os_strcmp(str, "ssdp:all") != 0 && + os_strstr(usrv->service, str) == NULL) + continue; + + if (wpabuf_tailroom(resp) < 2) + break; + if (count == 0) { + /* Status Code */ + wpabuf_put_u8(resp, P2P_SD_SUCCESS); + /* Response Data */ + wpabuf_put_u8(resp, version); + } else + wpabuf_put_u8(resp, ','); + + count++; + + wpa_printf(MSG_DEBUG, "P2P: Matching UPnP Service: %s", + usrv->service); + if (wpabuf_tailroom(resp) < os_strlen(usrv->service)) + break; + wpabuf_put_str(resp, usrv->service); + } + + if (count == 0) { + wpa_printf(MSG_DEBUG, "P2P: Requested UPnP service not " + "available"); + /* Status Code */ + wpabuf_put_u8(resp, P2P_SD_QUERY_DATA_NOT_AVAILABLE); + /* Response Data: empty */ + } + + WPA_PUT_LE16(len_pos, (u8 *) wpabuf_put(resp, 0) - len_pos - 2); +} + + +void wpas_sd_request(void *ctx, int freq, const u8 *sa, u8 dialog_token, + u16 update_indic, const u8 *tlvs, size_t tlvs_len) +{ + struct wpa_supplicant *wpa_s = ctx; + const u8 *pos = tlvs; + const u8 *end = tlvs + tlvs_len; + const u8 *tlv_end; + u16 slen; + struct wpabuf *resp; + u8 srv_proto, srv_trans_id; + size_t buf_len; + char *buf; + + wpa_hexdump(MSG_MSGDUMP, "P2P: Service Discovery Request TLVs", + tlvs, tlvs_len); + buf_len = 2 * tlvs_len + 1; + buf = os_malloc(buf_len); + if (buf) { + wpa_snprintf_hex(buf, buf_len, tlvs, tlvs_len); + wpa_msg_ctrl(wpa_s, MSG_INFO, P2P_EVENT_SERV_DISC_REQ "%d " + MACSTR " %u %u %s", + freq, MAC2STR(sa), dialog_token, update_indic, + buf); + os_free(buf); + } + + if (wpa_s->p2p_sd_over_ctrl_iface) + return; /* to be processed by an external program */ + + resp = wpabuf_alloc(10000); + if (resp == NULL) + return; + + while (pos + 1 < end) { + wpa_printf(MSG_DEBUG, "P2P: Service Request TLV"); + slen = WPA_GET_LE16(pos); + pos += 2; + if (pos + slen > end || slen < 2) { + wpa_printf(MSG_DEBUG, "P2P: Unexpected Query Data " + "length"); + wpabuf_free(resp); + return; + } + tlv_end = pos + slen; + + srv_proto = *pos++; + wpa_printf(MSG_DEBUG, "P2P: Service Protocol Type %u", + srv_proto); + srv_trans_id = *pos++; + wpa_printf(MSG_DEBUG, "P2P: Service Transaction ID %u", + srv_trans_id); + + wpa_hexdump(MSG_MSGDUMP, "P2P: Query Data", + pos, tlv_end - pos); + + switch (srv_proto) { + case P2P_SERV_ALL_SERVICES: + wpa_printf(MSG_DEBUG, "P2P: Service Discovery Request " + "for all services"); + if (dl_list_empty(&wpa_s->global->p2p_srv_upnp) && + dl_list_empty(&wpa_s->global->p2p_srv_bonjour)) { + wpa_printf(MSG_DEBUG, "P2P: No service " + "discovery protocols available"); + wpas_sd_add_proto_not_avail( + resp, P2P_SERV_ALL_SERVICES, + srv_trans_id); + break; + } + wpas_sd_all_bonjour(wpa_s, resp, srv_trans_id); + wpas_sd_all_upnp(wpa_s, resp, srv_trans_id); + break; + case P2P_SERV_BONJOUR: + wpas_sd_req_bonjour(wpa_s, resp, srv_trans_id, + pos, tlv_end - pos); + break; + case P2P_SERV_UPNP: + wpas_sd_req_upnp(wpa_s, resp, srv_trans_id, + pos, tlv_end - pos); + break; + default: + wpa_printf(MSG_DEBUG, "P2P: Unavailable service " + "protocol %u", srv_proto); + wpas_sd_add_proto_not_avail(resp, srv_proto, + srv_trans_id); + break; + } + + pos = tlv_end; + } + + wpas_p2p_sd_response(wpa_s, freq, sa, dialog_token, resp); + + wpabuf_free(resp); +} + + +void wpas_sd_response(void *ctx, const u8 *sa, u16 update_indic, + const u8 *tlvs, size_t tlvs_len) +{ + struct wpa_supplicant *wpa_s = ctx; + const u8 *pos = tlvs; + const u8 *end = tlvs + tlvs_len; + const u8 *tlv_end; + u16 slen; + size_t buf_len; + char *buf; + + wpa_hexdump(MSG_MSGDUMP, "P2P: Service Discovery Response TLVs", + tlvs, tlvs_len); + buf_len = 2 * tlvs_len + 1; + buf = os_malloc(buf_len); + if (buf) { + wpa_snprintf_hex(buf, buf_len, tlvs, tlvs_len); + wpa_msg_ctrl(wpa_s, MSG_INFO, P2P_EVENT_SERV_DISC_RESP MACSTR + " %u %s", + MAC2STR(sa), update_indic, buf); + os_free(buf); + } + + while (pos < end) { + u8 srv_proto, srv_trans_id, status; + + wpa_printf(MSG_DEBUG, "P2P: Service Response TLV"); + slen = WPA_GET_LE16(pos); + pos += 2; + if (pos + slen > end || slen < 3) { + wpa_printf(MSG_DEBUG, "P2P: Unexpected Response Data " + "length"); + return; + } + tlv_end = pos + slen; + + srv_proto = *pos++; + wpa_printf(MSG_DEBUG, "P2P: Service Protocol Type %u", + srv_proto); + srv_trans_id = *pos++; + wpa_printf(MSG_DEBUG, "P2P: Service Transaction ID %u", + srv_trans_id); + status = *pos++; + wpa_printf(MSG_DEBUG, "P2P: Status Code ID %u", + status); + + wpa_hexdump(MSG_MSGDUMP, "P2P: Response Data", + pos, tlv_end - pos); + + pos = tlv_end; + } +} + + +void * wpas_p2p_sd_request(struct wpa_supplicant *wpa_s, const u8 *dst, + const struct wpabuf *tlvs) +{ + return p2p_sd_request(wpa_s->global->p2p, dst, tlvs); +} + + +void * wpas_p2p_sd_request_upnp(struct wpa_supplicant *wpa_s, const u8 *dst, + u8 version, const char *query) +{ + struct wpabuf *tlvs; + void *ret; + + tlvs = wpabuf_alloc(2 + 1 + 1 + 1 + os_strlen(query)); + if (tlvs == NULL) + return NULL; + wpabuf_put_le16(tlvs, 1 + 1 + 1 + os_strlen(query)); + wpabuf_put_u8(tlvs, P2P_SERV_UPNP); /* Service Protocol Type */ + wpabuf_put_u8(tlvs, 1); /* Service Transaction ID */ + wpabuf_put_u8(tlvs, version); + wpabuf_put_str(tlvs, query); + ret = wpas_p2p_sd_request(wpa_s, dst, tlvs); + wpabuf_free(tlvs); + return ret; +} + + +int wpas_p2p_sd_cancel_request(struct wpa_supplicant *wpa_s, void *req) +{ + return p2p_sd_cancel_request(wpa_s->global->p2p, req); +} + + +void wpas_p2p_sd_response(struct wpa_supplicant *wpa_s, int freq, + const u8 *dst, u8 dialog_token, + const struct wpabuf *resp_tlvs) +{ + p2p_sd_response(wpa_s->global->p2p, freq, dst, dialog_token, + resp_tlvs); +} + + +void wpas_p2p_sd_service_update(struct wpa_supplicant *wpa_s) +{ + p2p_sd_service_update(wpa_s->global->p2p); +} + + +static void wpas_p2p_srv_bonjour_free(struct p2p_srv_bonjour *bsrv) +{ + dl_list_del(&bsrv->list); + wpabuf_free(bsrv->query); + wpabuf_free(bsrv->resp); + os_free(bsrv); +} + + +static void wpas_p2p_srv_upnp_free(struct p2p_srv_upnp *usrv) +{ + dl_list_del(&usrv->list); + os_free(usrv->service); + os_free(usrv); +} + + +void wpas_p2p_service_flush(struct wpa_supplicant *wpa_s) +{ + struct p2p_srv_bonjour *bsrv, *bn; + struct p2p_srv_upnp *usrv, *un; + + dl_list_for_each_safe(bsrv, bn, &wpa_s->global->p2p_srv_bonjour, + struct p2p_srv_bonjour, list) + wpas_p2p_srv_bonjour_free(bsrv); + + dl_list_for_each_safe(usrv, un, &wpa_s->global->p2p_srv_upnp, + struct p2p_srv_upnp, list) + wpas_p2p_srv_upnp_free(usrv); + + wpas_p2p_sd_service_update(wpa_s); +} + + +int wpas_p2p_service_add_bonjour(struct wpa_supplicant *wpa_s, + struct wpabuf *query, struct wpabuf *resp) +{ + struct p2p_srv_bonjour *bsrv; + + bsrv = wpas_p2p_service_get_bonjour(wpa_s, query); + if (bsrv) { + wpabuf_free(query); + wpabuf_free(bsrv->resp); + bsrv->resp = resp; + return 0; + } + + bsrv = os_zalloc(sizeof(*bsrv)); + if (bsrv == NULL) + return -1; + bsrv->query = query; + bsrv->resp = resp; + dl_list_add(&wpa_s->global->p2p_srv_bonjour, &bsrv->list); + + wpas_p2p_sd_service_update(wpa_s); + return 0; +} + + +int wpas_p2p_service_del_bonjour(struct wpa_supplicant *wpa_s, + const struct wpabuf *query) +{ + struct p2p_srv_bonjour *bsrv; + + bsrv = wpas_p2p_service_get_bonjour(wpa_s, query); + if (bsrv == NULL) + return -1; + wpas_p2p_srv_bonjour_free(bsrv); + wpas_p2p_sd_service_update(wpa_s); + return 0; +} + + +int wpas_p2p_service_add_upnp(struct wpa_supplicant *wpa_s, u8 version, + const char *service) +{ + struct p2p_srv_upnp *usrv; + + if (wpas_p2p_service_get_upnp(wpa_s, version, service)) + return 0; /* Already listed */ + usrv = os_zalloc(sizeof(*usrv)); + if (usrv == NULL) + return -1; + usrv->version = version; + usrv->service = os_strdup(service); + if (usrv->service == NULL) { + os_free(usrv); + return -1; + } + dl_list_add(&wpa_s->global->p2p_srv_upnp, &usrv->list); + + wpas_p2p_sd_service_update(wpa_s); + return 0; +} + + +int wpas_p2p_service_del_upnp(struct wpa_supplicant *wpa_s, u8 version, + const char *service) +{ + struct p2p_srv_upnp *usrv; + + usrv = wpas_p2p_service_get_upnp(wpa_s, version, service); + if (usrv == NULL) + return -1; + wpas_p2p_srv_upnp_free(usrv); + wpas_p2p_sd_service_update(wpa_s); + return 0; +} + + +static void wpas_prov_disc_local_display(struct wpa_supplicant *wpa_s, + const u8 *peer, const char *params) +{ + wpa_msg(wpa_s, MSG_INFO, P2P_EVENT_PROV_DISC_SHOW_PIN MACSTR " %08d%s", + MAC2STR(peer), wps_generate_pin(), params); +} + + +static void wpas_prov_disc_local_keypad(struct wpa_supplicant *wpa_s, + const u8 *peer, const char *params) +{ + wpa_msg(wpa_s, MSG_INFO, P2P_EVENT_PROV_DISC_ENTER_PIN MACSTR "%s", + MAC2STR(peer), params); +} + + +void wpas_prov_disc_req(void *ctx, const u8 *peer, u16 config_methods, + const u8 *dev_addr, const u8 *pri_dev_type, + const char *dev_name, u16 supp_config_methods, + u8 dev_capab, u8 group_capab) +{ + struct wpa_supplicant *wpa_s = ctx; + char devtype[WPS_DEV_TYPE_BUFSIZE]; + char params[200]; + u8 empty_dev_type[8]; + + if (pri_dev_type == NULL) { + os_memset(empty_dev_type, 0, sizeof(empty_dev_type)); + pri_dev_type = empty_dev_type; + } + os_snprintf(params, sizeof(params), " p2p_dev_addr=" MACSTR + " pri_dev_type=%s name='%s' config_methods=0x%x " + "dev_capab=0x%x group_capab=0x%x", + MAC2STR(dev_addr), + wps_dev_type_bin2str(pri_dev_type, devtype, + sizeof(devtype)), + dev_name, supp_config_methods, dev_capab, group_capab); + params[sizeof(params) - 1] = '\0'; + + if (config_methods & WPS_CONFIG_DISPLAY) + wpas_prov_disc_local_display(wpa_s, peer, params); + else if (config_methods & WPS_CONFIG_KEYPAD) + wpas_prov_disc_local_keypad(wpa_s, peer, params); + else if (config_methods & WPS_CONFIG_PUSHBUTTON) + wpa_msg(wpa_s, MSG_INFO, P2P_EVENT_PROV_DISC_PBC_REQ MACSTR + "%s", MAC2STR(peer), params); +} + + +void wpas_prov_disc_resp(void *ctx, const u8 *peer, u16 config_methods) +{ + struct wpa_supplicant *wpa_s = ctx; + if (config_methods & WPS_CONFIG_DISPLAY) + wpas_prov_disc_local_keypad(wpa_s, peer, ""); + else if (config_methods & WPS_CONFIG_KEYPAD) + wpas_prov_disc_local_display(wpa_s, peer, ""); + else if (config_methods & WPS_CONFIG_PUSHBUTTON) + wpa_msg(wpa_s, MSG_INFO, P2P_EVENT_PROV_DISC_PBC_RESP MACSTR, + MAC2STR(peer)); +} + + +static u8 wpas_invitation_process(void *ctx, const u8 *sa, const u8 *bssid, + const u8 *go_dev_addr, const u8 *ssid, + size_t ssid_len, int *go, u8 *group_bssid, + int *force_freq, int persistent_group) +{ + struct wpa_supplicant *wpa_s = ctx; + struct wpa_ssid *s; + u8 cur_bssid[ETH_ALEN]; + int res; + + if (!persistent_group) { + wpa_printf(MSG_DEBUG, "P2P: Invitation from " MACSTR + " to join an active group", MAC2STR(sa)); + /* + * Do not accept the invitation automatically; notify user and + * request approval. + */ + return P2P_SC_FAIL_INFO_CURRENTLY_UNAVAILABLE; + } + + if (!wpa_s->conf->persistent_reconnect) + return P2P_SC_FAIL_INFO_CURRENTLY_UNAVAILABLE; + + for (s = wpa_s->conf->ssid; s; s = s->next) { + if (s->disabled == 2 && + os_memcmp(s->bssid, go_dev_addr, ETH_ALEN) == 0 && + s->ssid_len == ssid_len && + os_memcmp(ssid, s->ssid, ssid_len) == 0) + break; + } + + if (!s) { + wpa_printf(MSG_DEBUG, "P2P: Invitation from " MACSTR + " requested reinvocation of an unknown group", + MAC2STR(sa)); + return P2P_SC_FAIL_UNKNOWN_GROUP; + } + + if (s->mode == WPAS_MODE_P2P_GO && !wpas_p2p_create_iface(wpa_s)) { + *go = 1; + if (wpa_s->wpa_state >= WPA_AUTHENTICATING) { + wpa_printf(MSG_DEBUG, "P2P: The only available " + "interface is already in use - reject " + "invitation"); + return P2P_SC_FAIL_UNABLE_TO_ACCOMMODATE; + } + os_memcpy(group_bssid, wpa_s->own_addr, ETH_ALEN); + } else if (s->mode == WPAS_MODE_P2P_GO) { + *go = 1; + if (wpas_p2p_add_group_interface(wpa_s, WPA_IF_P2P_GO) < 0) + { + wpa_printf(MSG_ERROR, "P2P: Failed to allocate a new " + "interface address for the group"); + return P2P_SC_FAIL_UNABLE_TO_ACCOMMODATE; + } + os_memcpy(group_bssid, wpa_s->pending_interface_addr, + ETH_ALEN); + } + + if (wpa_s->current_ssid && wpa_drv_get_bssid(wpa_s, cur_bssid) == 0 && + wpa_s->assoc_freq) { + wpa_printf(MSG_DEBUG, "P2P: Trying to force channel to match " + "the channel we are already using"); + *force_freq = wpa_s->assoc_freq; + } + + res = wpa_drv_shared_freq(wpa_s); + if (res > 0) { + wpa_printf(MSG_DEBUG, "P2P: Trying to force channel to match " + "with the channel we are already using on a " + "shared interface"); + *force_freq = res; + } + + return P2P_SC_SUCCESS; +} + + +static void wpas_invitation_received(void *ctx, const u8 *sa, const u8 *bssid, + const u8 *ssid, size_t ssid_len, + const u8 *go_dev_addr, u8 status, + int op_freq) +{ + struct wpa_supplicant *wpa_s = ctx; + struct wpa_ssid *s; + + for (s = wpa_s->conf->ssid; s; s = s->next) { + if (s->disabled == 2 && + s->ssid_len == ssid_len && + os_memcmp(ssid, s->ssid, ssid_len) == 0) + break; + } + + if (status == P2P_SC_SUCCESS) { + wpa_printf(MSG_DEBUG, "P2P: Invitation from peer " MACSTR + " was accepted; op_freq=%d MHz", + MAC2STR(sa), op_freq); + if (s) { + wpas_p2p_group_add_persistent( + wpa_s, s, s->mode == WPAS_MODE_P2P_GO, 0); + } + return; + } + + if (status != P2P_SC_FAIL_INFO_CURRENTLY_UNAVAILABLE) { + wpa_printf(MSG_DEBUG, "P2P: Invitation from peer " MACSTR + " was rejected (status %u)", MAC2STR(sa), status); + return; + } + + if (!s) { + if (bssid) { + wpa_msg(wpa_s, MSG_INFO, P2P_EVENT_INVITATION_RECEIVED + "sa=" MACSTR " go_dev_addr=" MACSTR + " bssid=" MACSTR " unknown-network", + MAC2STR(sa), MAC2STR(go_dev_addr), + MAC2STR(bssid)); + } else { + wpa_msg(wpa_s, MSG_INFO, P2P_EVENT_INVITATION_RECEIVED + "sa=" MACSTR " go_dev_addr=" MACSTR + " unknown-network", + MAC2STR(sa), MAC2STR(go_dev_addr)); + } + return; + } + + wpa_msg(wpa_s, MSG_INFO, P2P_EVENT_INVITATION_RECEIVED "sa=" MACSTR + " persistent=%d", MAC2STR(sa), s->id); +} + + +static void wpas_invitation_result(void *ctx, int status, const u8 *bssid) +{ + struct wpa_supplicant *wpa_s = ctx; + struct wpa_ssid *ssid; + + if (bssid) { + wpa_msg(wpa_s, MSG_INFO, P2P_EVENT_INVITATION_RESULT + "status=%d " MACSTR, + status, MAC2STR(bssid)); + } else { + wpa_msg(wpa_s, MSG_INFO, P2P_EVENT_INVITATION_RESULT + "status=%d ", status); + } + + if (status != P2P_SC_SUCCESS) { + wpas_p2p_remove_pending_group_interface(wpa_s); + return; + } + + ssid = wpa_config_get_network(wpa_s->conf, + wpa_s->pending_invite_ssid_id); + if (ssid == NULL) { + wpa_printf(MSG_ERROR, "P2P: Could not find persistent group " + "data matching with invitation"); + return; + } + + wpas_p2p_group_add_persistent(wpa_s, ssid, + ssid->mode == WPAS_MODE_P2P_GO, 0); +} + + +static int wpas_p2p_setup_channels(struct wpa_supplicant *wpa_s, + struct p2p_config *p2p) +{ + struct hostapd_hw_modes *modes; + u16 num_modes, flags; + int i, cla; + int band24 = 0, band5_low = 0, band5_high = 0; + + /* TODO: more detailed selection of channels within reg_class based on + * driver capabilities */ + + modes = wpa_drv_get_hw_feature_data(wpa_s, &num_modes, &flags); + if (modes == NULL) { + wpa_printf(MSG_DEBUG, "P2P: Driver did not support fetching " + "of all supported channels; assume dualband " + "support"); + band24 = band5_low = band5_high = 1; + } else { + for (i = 0; i < num_modes; i++) { + struct hostapd_hw_modes *mode; + mode = &modes[i]; + if (mode->mode == HOSTAPD_MODE_IEEE80211G) { + band24 = 1; + } else if (mode->mode == HOSTAPD_MODE_IEEE80211A) { + int j; + for (j = 0; j < mode->num_channels; j++) { + struct hostapd_channel_data *ch; + ch = &mode->channels[j]; + if (ch->chan == 36) + band5_low = 1; + else if (ch->chan == 157) + band5_high = 1; + } + } + } + } + + cla = 0; + + if (band24) { + wpa_printf(MSG_DEBUG, "P2P: Enable operating classes for " + "2.4 GHz band"); + + /* Operating class 81 - 2.4 GHz band channels 1..13 */ + p2p->channels.reg_class[cla].reg_class = 81; + p2p->channels.reg_class[cla].channels = 13; + for (i = 0; i < 13; i++) + p2p->channels.reg_class[cla].channel[i] = i + 1; + cla++; + + /* Operating class 82 - 2.4 GHz band channel 14 */ + p2p->channels.reg_class[cla].reg_class = 82; + p2p->channels.reg_class[cla].channels = 1; + p2p->channels.reg_class[cla].channel[0] = 14; + cla++; + +#if 0 + /* Operating class 83 - 2.4 GHz band channels 1..9; 40 MHz */ + p2p->channels.reg_class[cla].reg_class = 83; + p2p->channels.reg_class[cla].channels = 9; + for (i = 0; i < 9; i++) + p2p->channels.reg_class[cla].channel[i] = i + 1; + cla++; + + /* Operating class 84 - 2.4 GHz band channels 5..13; 40 MHz */ + p2p->channels.reg_class[cla].reg_class = 84; + p2p->channels.reg_class[cla].channels = 9; + for (i = 0; i < 9; i++) + p2p->channels.reg_class[cla].channel[i] = i + 5; + cla++; +#endif + } + + if (band5_low) { + wpa_printf(MSG_DEBUG, "P2P: Enable operating classes for " + "lower 5 GHz band"); + + /* Operating class 115 - 5 GHz, channels 36-48 */ + p2p->channels.reg_class[cla].reg_class = 115; + p2p->channels.reg_class[cla].channels = 4; + p2p->channels.reg_class[cla].channel[0] = 36; + p2p->channels.reg_class[cla].channel[1] = 40; + p2p->channels.reg_class[cla].channel[2] = 44; + p2p->channels.reg_class[cla].channel[3] = 48; + cla++; + +#if 0 + /* Operating class 116 - 5 GHz, channels 36,44; 40 MHz */ + p2p->channels.reg_class[cla].reg_class = 116; + p2p->channels.reg_class[cla].channels = 2; + p2p->channels.reg_class[cla].channel[0] = 36; + p2p->channels.reg_class[cla].channel[1] = 44; + cla++; + + /* Operating class 117 - 5 GHz, channels 40,48; 40 MHz */ + p2p->channels.reg_class[cla].reg_class = 117; + p2p->channels.reg_class[cla].channels = 2; + p2p->channels.reg_class[cla].channel[0] = 40; + p2p->channels.reg_class[cla].channel[1] = 48; + cla++; +#endif + } + + if (band5_high) { + wpa_printf(MSG_DEBUG, "P2P: Enable operating classes for " + "higher 5 GHz band"); + + /* Operating class 124 - 5 GHz, channels 149,153,157,161 */ + p2p->channels.reg_class[cla].reg_class = 124; + p2p->channels.reg_class[cla].channels = 4; + p2p->channels.reg_class[cla].channel[0] = 149; + p2p->channels.reg_class[cla].channel[1] = 153; + p2p->channels.reg_class[cla].channel[2] = 157; + p2p->channels.reg_class[cla].channel[3] = 161; + cla++; + +#if 0 + /* Operating class 126 - 5 GHz, channels 149,157; 40 MHz */ + p2p->channels.reg_class[cla].reg_class = 126; + p2p->channels.reg_class[cla].channels = 2; + p2p->channels.reg_class[cla].channel[0] = 149; + p2p->channels.reg_class[cla].channel[1] = 157; + cla++; + + /* Operating class 127 - 5 GHz, channels 153,161; 40 MHz */ + p2p->channels.reg_class[cla].reg_class = 127; + p2p->channels.reg_class[cla].channels = 2; + p2p->channels.reg_class[cla].channel[0] = 153; + p2p->channels.reg_class[cla].channel[1] = 161; + cla++; +#endif + } + + p2p->channels.reg_classes = cla; + + if (modes) + ieee80211_sta_free_hw_features(modes, num_modes); + + return 0; +} + + +static int wpas_get_noa(void *ctx, const u8 *interface_addr, u8 *buf, + size_t buf_len) +{ + struct wpa_supplicant *wpa_s = ctx; + + for (wpa_s = wpa_s->global->ifaces; wpa_s; wpa_s = wpa_s->next) { + if (os_memcmp(wpa_s->own_addr, interface_addr, ETH_ALEN) == 0) + break; + } + if (wpa_s == NULL) + return -1; + + return wpa_drv_get_noa(wpa_s, buf, buf_len); +} + + +/** + * wpas_p2p_init - Initialize P2P module for %wpa_supplicant + * @global: Pointer to global data from wpa_supplicant_init() + * @wpa_s: Pointer to wpa_supplicant data from wpa_supplicant_add_iface() + * Returns: 0 on success, -1 on failure + */ +int wpas_p2p_init(struct wpa_global *global, struct wpa_supplicant *wpa_s) +{ + struct p2p_config p2p; + unsigned int r; + int i; + + if (!(wpa_s->drv_flags & WPA_DRIVER_FLAGS_P2P_CAPABLE)) + return 0; + +#ifdef CONFIG_CLIENT_MLME + if (!(wpa_s->drv_flags & WPA_DRIVER_FLAGS_P2P_MGMT)) { + wpa_s->mlme.public_action_cb = p2p_rx_action_mlme; + wpa_s->mlme.public_action_cb_ctx = wpa_s; + } +#endif /* CONFIG_CLIENT_MLME */ + + if (wpa_drv_disable_11b_rates(wpa_s, 1) < 0) { + wpa_printf(MSG_DEBUG, "P2P: Failed to disable 11b rates"); + /* Continue anyway; this is not really a fatal error */ + } + + if (global->p2p) + return 0; + + os_memset(&p2p, 0, sizeof(p2p)); + p2p.msg_ctx = wpa_s; + p2p.cb_ctx = wpa_s; + p2p.p2p_scan = wpas_p2p_scan; + p2p.send_action = wpas_send_action; + p2p.send_action_done = wpas_send_action_done; + p2p.go_neg_completed = wpas_go_neg_completed; + p2p.go_neg_req_rx = wpas_go_neg_req_rx; + p2p.dev_found = wpas_dev_found; + p2p.start_listen = wpas_start_listen; + p2p.stop_listen = wpas_stop_listen; + p2p.send_probe_resp = wpas_send_probe_resp; + p2p.sd_request = wpas_sd_request; + p2p.sd_response = wpas_sd_response; + p2p.prov_disc_req = wpas_prov_disc_req; + p2p.prov_disc_resp = wpas_prov_disc_resp; + p2p.invitation_process = wpas_invitation_process; + p2p.invitation_received = wpas_invitation_received; + p2p.invitation_result = wpas_invitation_result; + p2p.get_noa = wpas_get_noa; + + os_memcpy(wpa_s->global->p2p_dev_addr, wpa_s->own_addr, ETH_ALEN); + os_memcpy(p2p.dev_addr, wpa_s->own_addr, ETH_ALEN); + p2p.dev_name = wpa_s->conf->device_name; + + if (wpa_s->conf->p2p_listen_reg_class && + wpa_s->conf->p2p_listen_channel) { + p2p.reg_class = wpa_s->conf->p2p_listen_reg_class; + p2p.channel = wpa_s->conf->p2p_listen_channel; + } else { + p2p.reg_class = 81; + /* + * Pick one of the social channels randomly as the listen + * channel. + */ + os_get_random((u8 *) &r, sizeof(r)); + p2p.channel = 1 + (r % 3) * 5; + } + + if (wpa_s->conf->p2p_oper_reg_class && + wpa_s->conf->p2p_oper_channel) { + p2p.op_reg_class = wpa_s->conf->p2p_oper_reg_class; + p2p.op_channel = wpa_s->conf->p2p_oper_channel; + } else { + p2p.op_reg_class = 81; + /* + * For initial tests, pick the operation channel randomly. + * TODO: Use scan results (etc.) to select the best channel. + */ + p2p.op_channel = 1 + r % 11; + } + wpa_printf(MSG_DEBUG, "P2P: Own listen channel: %d " + "Own preferred operation channel: %d", + p2p.channel, p2p.op_channel); + if (wpa_s->conf->country[0] && wpa_s->conf->country[1]) { + os_memcpy(p2p.country, wpa_s->conf->country, 2); + p2p.country[2] = 0x04; + } else + os_memcpy(p2p.country, "US\x04", 3); + + if (wpas_p2p_setup_channels(wpa_s, &p2p)) { + wpa_printf(MSG_ERROR, "P2P: Failed to configure supported " + "channel list"); + return -1; + } + + if (wpa_s->conf->device_type && + wps_dev_type_str2bin(wpa_s->conf->device_type, p2p.pri_dev_type) < + 0) { + wpa_printf(MSG_ERROR, "P2P: Invalid device_type"); + return -1; + } + + for (i = 0; i < MAX_SEC_DEVICE_TYPES; i++) { + if (wpa_s->conf->sec_device_type[i] == NULL) + continue; + if (wps_dev_type_str2bin( + wpa_s->conf->sec_device_type[i], + p2p.sec_dev_type[p2p.num_sec_dev_types]) < 0) { + wpa_printf(MSG_ERROR, "P2P: Invalid sec_device_type"); + return -1; + } + p2p.num_sec_dev_types++; + if (p2p.num_sec_dev_types == P2P_SEC_DEVICE_TYPES) + break; + } + + p2p.concurrent_operations = !!(wpa_s->drv_flags & + WPA_DRIVER_FLAGS_P2P_CONCURRENT); + + p2p.max_peers = 100; + + if (wpa_s->conf->p2p_ssid_postfix) { + p2p.ssid_postfix_len = + os_strlen(wpa_s->conf->p2p_ssid_postfix); + if (p2p.ssid_postfix_len > sizeof(p2p.ssid_postfix)) + p2p.ssid_postfix_len = sizeof(p2p.ssid_postfix); + os_memcpy(p2p.ssid_postfix, wpa_s->conf->p2p_ssid_postfix, + p2p.ssid_postfix_len); + } + + global->p2p = p2p_init(&p2p); + if (global->p2p == NULL) + return -1; + + return 0; +} + + +/** + * wpas_p2p_deinit - Deinitialize per-interface P2P data + * @wpa_s: Pointer to wpa_supplicant data from wpa_supplicant_add_iface() + * + * This function deinitialize per-interface P2P data. + */ +void wpas_p2p_deinit(struct wpa_supplicant *wpa_s) +{ + if (wpa_s->driver && wpa_s->drv_priv) + wpa_drv_probe_req_report(wpa_s, 0); + os_free(wpa_s->go_params); + wpa_s->go_params = NULL; + wpabuf_free(wpa_s->pending_action_tx); + wpa_s->pending_action_tx = NULL; + eloop_cancel_timeout(wpas_send_action_cb, wpa_s, NULL); + eloop_cancel_timeout(wpas_p2p_group_formation_timeout, wpa_s, NULL); + wpa_s->p2p_long_listen = 0; + eloop_cancel_timeout(wpas_p2p_long_listen_timeout, wpa_s, NULL); + wpas_p2p_remove_pending_group_interface(wpa_s); + + /* TODO: remove group interface from the driver if this wpa_s instance + * is on top of a P2P group interface */ +} + + +/** + * wpas_p2p_deinit_global - Deinitialize global P2P module + * @global: Pointer to global data from wpa_supplicant_init() + * + * This function deinitializes the global (per device) P2P module. + */ +void wpas_p2p_deinit_global(struct wpa_global *global) +{ + struct wpa_supplicant *wpa_s, *tmp; + char *ifname; + + if (global->p2p == NULL) + return; + + /* Remove remaining P2P group interfaces */ + wpa_s = global->ifaces; + while (wpa_s && wpa_s->p2p_group_interface != NOT_P2P_GROUP_INTERFACE) + wpa_s = wpa_s->next; + while (wpa_s) { + enum wpa_driver_if_type type; + tmp = global->ifaces; + while (tmp && + (tmp == wpa_s || + tmp->p2p_group_interface == NOT_P2P_GROUP_INTERFACE)) { + tmp = tmp->next; + } + if (tmp == NULL) + break; + ifname = os_strdup(tmp->ifname); + type = wpas_p2p_if_type(tmp->p2p_group_interface); + wpa_supplicant_remove_iface(global, tmp); + if (ifname) + wpa_drv_if_remove(wpa_s, type, ifname); + os_free(ifname); + } + + p2p_deinit(global->p2p); + global->p2p = NULL; +} + + +static int wpas_p2p_create_iface(struct wpa_supplicant *wpa_s) +{ + if (wpa_s->drv_flags & WPA_DRIVER_FLAGS_P2P_DEDICATED_INTERFACE) + return 1; /* P2P group requires a new interface in every case + */ + if (!(wpa_s->drv_flags & WPA_DRIVER_FLAGS_P2P_CONCURRENT)) + return 0; /* driver does not support concurrent operations */ + if (wpa_s->global->ifaces->next) + return 1; /* more that one interface already in use */ + if (wpa_s->wpa_state >= WPA_AUTHENTICATING) + return 1; /* this interface is already in use */ + return 0; +} + + +static int wpas_p2p_start_go_neg(struct wpa_supplicant *wpa_s, + const u8 *peer_addr, + enum p2p_wps_method wps_method, + int go_intent, const u8 *own_interface_addr, + unsigned int force_freq, int persistent_group) +{ + return p2p_connect(wpa_s->global->p2p, peer_addr, wps_method, + go_intent, own_interface_addr, force_freq, + persistent_group); +} + + +static int wpas_p2p_auth_go_neg(struct wpa_supplicant *wpa_s, + const u8 *peer_addr, + enum p2p_wps_method wps_method, + int go_intent, const u8 *own_interface_addr, + unsigned int force_freq, int persistent_group) +{ + return p2p_authorize(wpa_s->global->p2p, peer_addr, wps_method, + go_intent, own_interface_addr, force_freq, + persistent_group); +} + + +static int wpas_p2p_join(struct wpa_supplicant *wpa_s, const u8 *iface_addr, + const u8 *dev_addr, enum p2p_wps_method wps_method) +{ + struct wpa_bss *bss; + + wpa_printf(MSG_DEBUG, "P2P: Request to join existing group (iface " + MACSTR " dev " MACSTR ")", + MAC2STR(iface_addr), MAC2STR(dev_addr)); + + os_memcpy(wpa_s->pending_join_iface_addr, iface_addr, ETH_ALEN); + os_memcpy(wpa_s->pending_join_dev_addr, dev_addr, ETH_ALEN); + wpa_s->pending_join_wps_method = wps_method; + + /* Make sure we are not running find during connection establishment */ + wpas_p2p_stop_find(wpa_s); + + bss = wpa_bss_get_bssid(wpa_s, iface_addr); + if (bss) { + u16 method; + + wpa_printf(MSG_DEBUG, "P2P: Send Provision Discovery Request " + "prior to joining an existing group (GO " MACSTR + " freq=%u MHz)", + MAC2STR(dev_addr), bss->freq); + wpa_s->pending_pd_before_join = 1; + + switch (wps_method) { + case WPS_PIN_LABEL: + case WPS_PIN_DISPLAY: + method = WPS_CONFIG_KEYPAD; + break; + case WPS_PIN_KEYPAD: + method = WPS_CONFIG_DISPLAY; + break; + case WPS_PBC: + method = WPS_CONFIG_PUSHBUTTON; + break; + default: + method = 0; + break; + } + + if (p2p_prov_disc_req(wpa_s->global->p2p, dev_addr, method, 1) + < 0) { + wpa_printf(MSG_DEBUG, "P2P: Failed to send Provision " + "Discovery Request before joining an " + "existing group"); + wpa_s->pending_pd_before_join = 0; + goto start; + } + + /* + * Actual join operation will be started from the Action frame + * TX status callback. + */ + return 0; + } + + wpa_printf(MSG_DEBUG, "P2P: Target BSS/GO not yet in BSS table - " + "cannot send Provision Discovery Request"); + +start: + /* Start join operation immediately */ + return wpas_p2p_join_start(wpa_s); +} + + +static int wpas_p2p_join_start(struct wpa_supplicant *wpa_s) +{ + struct wpa_supplicant *group; + struct p2p_go_neg_results res; + + group = wpas_p2p_get_group_iface(wpa_s, 0, 0); + if (group == NULL) + return -1; + if (group != wpa_s) + os_memcpy(group->p2p_pin, wpa_s->p2p_pin, + sizeof(group->p2p_pin)); + + group->p2p_in_provisioning = 1; + + os_memset(&res, 0, sizeof(res)); + os_memcpy(res.peer_interface_addr, wpa_s->pending_join_iface_addr, + ETH_ALEN); + res.wps_method = wpa_s->pending_join_wps_method; + wpas_start_wps_enrollee(group, &res); + + return 0; +} + + +/** + * wpas_p2p_connect - Request P2P Group Formation to be started + * @wpa_s: Pointer to wpa_supplicant data from wpa_supplicant_add_iface() + * @peer_addr: Address of the peer P2P Device + * @pin: PIN to use during provisioning or %NULL to indicate PBC mode + * @persistent_group: Whether to create a persistent group + * @join: Whether to join an existing group (as a client) instead of starting + * Group Owner negotiation; @peer_addr is BSSID in that case + * @auth: Whether to only authorize the connection instead of doing that and + * initiating Group Owner negotiation + * @go_intent: GO Intent or -1 to use default + * @freq: Frequency for the group or 0 for auto-selection + * Returns: 0 or new PIN (if pin was %NULL) on success, -1 on failure + */ +int wpas_p2p_connect(struct wpa_supplicant *wpa_s, const u8 *peer_addr, + const char *pin, enum p2p_wps_method wps_method, + int persistent_group, int join, int auth, int go_intent, + int freq) +{ + int force_freq = 0; + u8 bssid[ETH_ALEN]; + int ret = 0; + enum wpa_driver_if_type iftype; + + if (go_intent < 0) + go_intent = wpa_s->conf->p2p_go_intent; + + if (!auth) + wpa_s->p2p_long_listen = 0; + + if (pin) + os_strlcpy(wpa_s->p2p_pin, pin, sizeof(wpa_s->p2p_pin)); + else if (wps_method == WPS_PIN_DISPLAY) { + ret = wps_generate_pin(); + os_snprintf(wpa_s->p2p_pin, sizeof(wpa_s->p2p_pin), "%08d", + ret); + wpa_printf(MSG_DEBUG, "P2P: Randomly generated PIN: %s", + wpa_s->p2p_pin); + } else + wpa_s->p2p_pin[0] = '\0'; + + if (join) { + u8 iface_addr[ETH_ALEN]; + if (p2p_get_interface_addr(wpa_s->global->p2p, peer_addr, + iface_addr) < 0) + os_memcpy(iface_addr, peer_addr, ETH_ALEN); + if (wpas_p2p_join(wpa_s, iface_addr, peer_addr, wps_method) < + 0) + return -1; + return ret; + } + + if (freq > 0) + force_freq = freq; + else if (wpa_s->current_ssid && wpa_drv_get_bssid(wpa_s, bssid) == 0 && + wpa_s->assoc_freq) + force_freq = wpa_s->assoc_freq; + else { + force_freq = wpa_drv_shared_freq(wpa_s); + if (force_freq < 0) + force_freq = 0; + } + + if (force_freq > 0) { + wpa_printf(MSG_DEBUG, "P2P: Trying to force us to use the " + "channel we are already using (%u MHz) on another " + "interface", force_freq); + } + + wpa_s->create_p2p_iface = wpas_p2p_create_iface(wpa_s); + + if (!wpa_s->create_p2p_iface) { + if (auth) { + if (wpas_p2p_auth_go_neg(wpa_s, peer_addr, wps_method, + go_intent, wpa_s->own_addr, + force_freq, persistent_group) + < 0) + return -1; + return ret; + } + if (wpas_p2p_start_go_neg(wpa_s, peer_addr, wps_method, + go_intent, wpa_s->own_addr, + force_freq, persistent_group) < 0) + return -1; + return ret; + } + + /* Prepare to add a new interface for the group */ + iftype = WPA_IF_P2P_GROUP; + if (join) + iftype = WPA_IF_P2P_CLIENT; + else if (go_intent == 15) + iftype = WPA_IF_P2P_GO; + if (wpas_p2p_add_group_interface(wpa_s, iftype) < 0) { + wpa_printf(MSG_ERROR, "P2P: Failed to allocate a new " + "interface for the group"); + return -1; + } + + if (auth) { + if (wpas_p2p_auth_go_neg(wpa_s, peer_addr, wps_method, + go_intent, + wpa_s->pending_interface_addr, + force_freq, persistent_group) < 0) + return -1; + return ret; + } + if (wpas_p2p_start_go_neg(wpa_s, peer_addr, wps_method, go_intent, + wpa_s->pending_interface_addr, + force_freq, persistent_group) < 0) { + wpas_p2p_remove_pending_group_interface(wpa_s); + return -1; + } + return ret; +} + + +/** + * wpas_p2p_remain_on_channel_cb - Indication of remain-on-channel start + * @wpa_s: Pointer to wpa_supplicant data from wpa_supplicant_add_iface() + * @freq: Frequency of the channel in MHz + * @duration: Duration of the stay on the channel in milliseconds + * + * This callback is called when the driver indicates that it has started the + * requested remain-on-channel duration. + */ +void wpas_p2p_remain_on_channel_cb(struct wpa_supplicant *wpa_s, + unsigned int freq, unsigned int duration) +{ + wpa_s->off_channel_freq = freq; + wpas_send_action_cb(wpa_s, NULL); + if (wpa_s->off_channel_freq == wpa_s->pending_listen_freq) { + p2p_listen_cb(wpa_s->global->p2p, wpa_s->pending_listen_freq, + wpa_s->pending_listen_duration); + wpa_s->pending_listen_freq = 0; + } +} + + +static int wpas_p2p_listen_start(struct wpa_supplicant *wpa_s, + unsigned int timeout) +{ + /* Limit maximum Listen state time based on driver limitation. */ + if (timeout > wpa_s->max_remain_on_chan) + timeout = wpa_s->max_remain_on_chan; + + return p2p_listen(wpa_s->global->p2p, timeout); +} + + +/** + * wpas_p2p_cancel_remain_on_channel_cb - Remain-on-channel timeout + * @wpa_s: Pointer to wpa_supplicant data from wpa_supplicant_add_iface() + * @freq: Frequency of the channel in MHz + * + * This callback is called when the driver indicates that a remain-on-channel + * operation has been completed, i.e., the duration on the requested channel + * has timed out. + */ +void wpas_p2p_cancel_remain_on_channel_cb(struct wpa_supplicant *wpa_s, + unsigned int freq) +{ + wpa_s->off_channel_freq = 0; + if (p2p_listen_end(wpa_s->global->p2p, freq) > 0) + return; /* P2P module started a new operation */ + if (wpa_s->pending_action_tx) + return; + if (wpa_s->p2p_long_listen > 0) + wpa_s->p2p_long_listen -= 5; + if (wpa_s->p2p_long_listen > 0) { + wpa_printf(MSG_DEBUG, "P2P: Continuing long Listen state"); + wpas_p2p_listen_start(wpa_s, wpa_s->p2p_long_listen * 1000); + } +} + + +/** + * wpas_p2p_group_remove - Remove a P2P group + * @wpa_s: Pointer to wpa_supplicant data from wpa_supplicant_add_iface() + * @ifname: Network interface name of the group interface or "*" to remove all + * groups + * Returns: 0 on success, -1 on failure + * + * This function is used to remove a P2P group. This can be used to disconnect + * from a group in which the local end is a P2P Client or to end a P2P Group in + * case the local end is the Group Owner. If a virtual network interface was + * created for this group, that interface will be removed. Otherwise, only the + * configured P2P group network will be removed from the interface. + */ +int wpas_p2p_group_remove(struct wpa_supplicant *wpa_s, const char *ifname) +{ + struct wpa_global *global = wpa_s->global; + + if (os_strcmp(ifname, "*") == 0) { + struct wpa_supplicant *prev; + wpa_s = global->ifaces; + while (wpa_s) { + prev = wpa_s; + wpa_s = wpa_s->next; + wpas_p2p_group_delete(prev); + } + return 0; + } + + for (wpa_s = global->ifaces; wpa_s; wpa_s = wpa_s->next) { + if (os_strcmp(wpa_s->ifname, ifname) == 0) + break; + } + + if (wpa_s == NULL) + return -1; + + wpas_p2p_group_delete(wpa_s); + + return 0; +} + + +static void wpas_p2p_init_go_params(struct wpa_supplicant *wpa_s, + struct p2p_go_neg_results *params, + int freq) +{ + u8 bssid[ETH_ALEN]; + int res; + + os_memset(params, 0, sizeof(*params)); + params->role_go = 1; + params->freq = 2412; + if (freq) + params->freq = freq; + else if (wpa_s->conf->p2p_oper_reg_class == 81 && + wpa_s->conf->p2p_oper_channel >= 1 && + wpa_s->conf->p2p_oper_channel <= 11) + params->freq = 2407 + 5 * wpa_s->conf->p2p_oper_channel; + else if (wpa_s->conf->p2p_oper_reg_class == 115 || + wpa_s->conf->p2p_oper_reg_class == 118) + params->freq = 5000 + 5 * wpa_s->conf->p2p_oper_channel; + if (wpa_s->current_ssid && wpa_drv_get_bssid(wpa_s, bssid) == 0 && + wpa_s->assoc_freq && !freq) { + wpa_printf(MSG_DEBUG, "P2P: Force GO on the channel we are " + "already using"); + params->freq = wpa_s->assoc_freq; + } + + res = wpa_drv_shared_freq(wpa_s); + if (res > 0 && !freq) { + wpa_printf(MSG_DEBUG, "P2P: Force GO on the channel we are " + "already using on a shared interface"); + params->freq = res; + } +} + + +static struct wpa_supplicant * +wpas_p2p_get_group_iface(struct wpa_supplicant *wpa_s, int addr_allocated, + int go) +{ + struct wpa_supplicant *group_wpa_s; + + if (!wpas_p2p_create_iface(wpa_s)) + return wpa_s; + + if (wpas_p2p_add_group_interface(wpa_s, go ? WPA_IF_P2P_GO : + WPA_IF_P2P_CLIENT) < 0) + return NULL; + group_wpa_s = wpas_p2p_init_group_interface(wpa_s, go); + if (group_wpa_s == NULL) { + wpas_p2p_remove_pending_group_interface(wpa_s); + return NULL; + } + + return group_wpa_s; +} + + +/** + * wpas_p2p_group_add - Add a new P2P group with local end as Group Owner + * @wpa_s: Pointer to wpa_supplicant data from wpa_supplicant_add_iface() + * @persistent_group: Whether to create a persistent group + * @freq: Frequency for the group or 0 to indicate no hardcoding + * Returns: 0 on success, -1 on failure + * + * This function creates a new P2P group with the local end as the Group Owner, + * i.e., without using Group Owner Negotiation. + */ +int wpas_p2p_group_add(struct wpa_supplicant *wpa_s, int persistent_group, + int freq) +{ + struct p2p_go_neg_results params; + + wpas_p2p_init_go_params(wpa_s, ¶ms, freq); + p2p_go_params(wpa_s->global->p2p, ¶ms); + params.persistent_group = persistent_group; + + wpa_s = wpas_p2p_get_group_iface(wpa_s, 0, 1); + if (wpa_s == NULL) + return -1; + wpas_start_wps_go(wpa_s, ¶ms, 0); + + return 0; +} + + +static int wpas_start_p2p_client(struct wpa_supplicant *wpa_s, + struct wpa_ssid *params, int addr_allocated) +{ + struct wpa_ssid *ssid; + + wpa_s = wpas_p2p_get_group_iface(wpa_s, addr_allocated, 0); + if (wpa_s == NULL) + return -1; + + wpa_supplicant_ap_deinit(wpa_s); + + ssid = wpa_config_add_network(wpa_s->conf); + if (ssid == NULL) + return -1; + wpas_notify_network_added(wpa_s, ssid); + wpa_config_set_network_defaults(ssid); + ssid->temporary = 1; + ssid->proto = WPA_PROTO_RSN; + ssid->pairwise_cipher = WPA_CIPHER_CCMP; + ssid->group_cipher = WPA_CIPHER_CCMP; + ssid->key_mgmt = WPA_KEY_MGMT_PSK; + ssid->ssid = os_malloc(params->ssid_len); + if (ssid->ssid == NULL) { + wpas_notify_network_removed(wpa_s, ssid); + wpa_config_remove_network(wpa_s->conf, ssid->id); + return -1; + } + os_memcpy(ssid->ssid, params->ssid, params->ssid_len); + ssid->ssid_len = params->ssid_len; + ssid->p2p_group = 1; + if (params->psk_set) { + os_memcpy(ssid->psk, params->psk, 32); + ssid->psk_set = 1; + } + if (params->passphrase) + ssid->passphrase = os_strdup(params->passphrase); + + wpa_supplicant_select_network(wpa_s, ssid); + + wpa_s->show_group_started = 1; + + return 0; +} + + +int wpas_p2p_group_add_persistent(struct wpa_supplicant *wpa_s, + struct wpa_ssid *ssid, int addr_allocated, + int freq) +{ + struct p2p_go_neg_results params; + + if (ssid->disabled != 2 || ssid->ssid == NULL) + return -1; + + wpa_s->p2p_long_listen = 0; + eloop_cancel_timeout(wpas_p2p_long_listen_timeout, wpa_s, NULL); + + if (ssid->mode == WPAS_MODE_INFRA) + return wpas_start_p2p_client(wpa_s, ssid, addr_allocated); + + if (ssid->mode != WPAS_MODE_P2P_GO) + return -1; + + wpas_p2p_init_go_params(wpa_s, ¶ms, freq); + + params.role_go = 1; + if (ssid->passphrase == NULL || + os_strlen(ssid->passphrase) >= sizeof(params.passphrase)) { + wpa_printf(MSG_DEBUG, "P2P: Invalid passphrase in persistent " + "group"); + return -1; + } + os_strlcpy(params.passphrase, ssid->passphrase, + sizeof(params.passphrase)); + os_memcpy(params.ssid, ssid->ssid, ssid->ssid_len); + params.ssid_len = ssid->ssid_len; + params.persistent_group = 1; + + wpa_s = wpas_p2p_get_group_iface(wpa_s, addr_allocated, 1); + if (wpa_s == NULL) + return -1; + + wpas_start_wps_go(wpa_s, ¶ms, 0); + + return 0; +} + + +static void wpas_p2p_ie_update(void *ctx, struct wpabuf *beacon_ies, + struct wpabuf *proberesp_ies) +{ + struct wpa_supplicant *wpa_s = ctx; + if (wpa_s->ap_iface) { + struct hostapd_data *hapd = wpa_s->ap_iface->bss[0]; + if (beacon_ies) { + wpabuf_free(hapd->p2p_beacon_ie); + hapd->p2p_beacon_ie = beacon_ies; + } + wpabuf_free(hapd->p2p_probe_resp_ie); + hapd->p2p_probe_resp_ie = proberesp_ies; + } else { + wpabuf_free(beacon_ies); + wpabuf_free(proberesp_ies); + } + wpa_supplicant_ap_update_beacon(wpa_s); +} + + +struct p2p_group * wpas_p2p_group_init(struct wpa_supplicant *wpa_s, + int persistent_group, + int group_formation) +{ + struct p2p_group *group; + struct p2p_group_config *cfg; + + cfg = os_zalloc(sizeof(*cfg)); + if (cfg == NULL) + return NULL; + + cfg->persistent_group = persistent_group; + os_memcpy(cfg->interface_addr, wpa_s->own_addr, ETH_ALEN); + cfg->cb_ctx = wpa_s; + cfg->ie_update = wpas_p2p_ie_update; + + group = p2p_group_init(wpa_s->global->p2p, cfg); + if (group == NULL) + os_free(cfg); + if (!group_formation) + p2p_group_notif_formation_done(group); + wpa_s->p2p_group = group; + return group; +} + + +void wpas_p2p_wps_success(struct wpa_supplicant *wpa_s, const u8 *peer_addr, + int registrar) +{ + if (!wpa_s->p2p_in_provisioning) { + wpa_printf(MSG_DEBUG, "P2P: Ignore WPS success event - P2P " + "provisioning not in progress"); + return; + } + + eloop_cancel_timeout(wpas_p2p_group_formation_timeout, wpa_s->parent, + NULL); + if (wpa_s->global->p2p) + p2p_wps_success_cb(wpa_s->global->p2p, peer_addr); + wpas_group_formation_completed(wpa_s, 1); +} + + +int wpas_p2p_prov_disc(struct wpa_supplicant *wpa_s, const u8 *peer_addr, + const char *config_method) +{ + u16 config_methods; + + if (os_strcmp(config_method, "display") == 0) + config_methods = WPS_CONFIG_DISPLAY; + else if (os_strcmp(config_method, "keypad") == 0) + config_methods = WPS_CONFIG_KEYPAD; + else if (os_strcmp(config_method, "pbc") == 0 || + os_strcmp(config_method, "pushbutton") == 0) + config_methods = WPS_CONFIG_PUSHBUTTON; + else + return -1; + + if (wpa_s->global->p2p == NULL) + return -1; + + return p2p_prov_disc_req(wpa_s->global->p2p, peer_addr, + config_methods, 0); +} + + +int wpas_p2p_scan_result_text(const u8 *ies, size_t ies_len, char *buf, + char *end) +{ + return p2p_scan_result_text(ies, ies_len, buf, end); +} + + +int wpas_p2p_find(struct wpa_supplicant *wpa_s, unsigned int timeout, + enum p2p_discovery_type type) +{ + wpa_s->p2p_long_listen = 0; + + return p2p_find(wpa_s->global->p2p, timeout, type); +} + + +void wpas_p2p_stop_find(struct wpa_supplicant *wpa_s) +{ + wpa_s->p2p_long_listen = 0; + + p2p_stop_find(wpa_s->global->p2p); + + wpas_p2p_remove_pending_group_interface(wpa_s); +} + + +static void wpas_p2p_long_listen_timeout(void *eloop_ctx, void *timeout_ctx) +{ + struct wpa_supplicant *wpa_s = eloop_ctx; + wpa_s->p2p_long_listen = 0; +} + + +int wpas_p2p_listen(struct wpa_supplicant *wpa_s, unsigned int timeout) +{ + int res; + + if (timeout == 0) { + /* + * This is a request for unlimited Listen state. However, at + * least for now, this is mapped to a Listen state for one + * hour. + */ + timeout = 3600; + } + eloop_cancel_timeout(wpas_p2p_long_listen_timeout, wpa_s, NULL); + wpa_s->p2p_long_listen = 0; + + res = wpas_p2p_listen_start(wpa_s, timeout * 1000); + if (res == 0 && timeout * 1000 > wpa_s->max_remain_on_chan) { + wpa_s->p2p_long_listen = timeout; + eloop_register_timeout(timeout, 0, + wpas_p2p_long_listen_timeout, + wpa_s, NULL); + } + + return res; +} + + +int wpas_p2p_assoc_req_ie(struct wpa_supplicant *wpa_s, const u8 *bssid, + u8 *buf, size_t len, int p2p_group) +{ + if (wpa_s->global->p2p_disabled) + return -1; + if (wpa_s->global->p2p == NULL) + return -1; + + return p2p_assoc_req_ie(wpa_s->global->p2p, bssid, buf, len, + p2p_group); +} + + +int wpas_p2p_probe_req_rx(struct wpa_supplicant *wpa_s, const u8 *addr, + const u8 *ie, size_t ie_len) +{ + if (wpa_s->global->p2p_disabled) + return 0; + if (wpa_s->global->p2p == NULL) + return 0; + + return p2p_probe_req_rx(wpa_s->global->p2p, addr, ie, ie_len); +} + + +void wpas_p2p_rx_action(struct wpa_supplicant *wpa_s, const u8 *da, + const u8 *sa, const u8 *bssid, + u8 category, const u8 *data, size_t len, int freq) +{ + if (wpa_s->global->p2p_disabled) + return; + if (wpa_s->global->p2p == NULL) + return; + + p2p_rx_action(wpa_s->global->p2p, da, sa, bssid, category, data, len, + freq); +} + + +void wpas_p2p_scan_ie(struct wpa_supplicant *wpa_s, struct wpabuf *ies) +{ + if (wpa_s->global->p2p_disabled) + return; + if (wpa_s->global->p2p == NULL) + return; + + p2p_scan_ie(wpa_s->global->p2p, ies); +} + + +void wpas_p2p_group_deinit(struct wpa_supplicant *wpa_s) +{ + p2p_group_deinit(wpa_s->p2p_group); + wpa_s->p2p_group = NULL; +} + + +int wpas_p2p_reject(struct wpa_supplicant *wpa_s, const u8 *addr) +{ + wpa_s->p2p_long_listen = 0; + + return p2p_reject(wpa_s->global->p2p, addr); +} + + +/* Invite to reinvoke a persistent group */ +int wpas_p2p_invite(struct wpa_supplicant *wpa_s, const u8 *peer_addr, + struct wpa_ssid *ssid, const u8 *go_dev_addr) +{ + enum p2p_invite_role role; + u8 *bssid = NULL; + + if (ssid->mode == WPAS_MODE_P2P_GO) { + role = P2P_INVITE_ROLE_GO; + if (peer_addr == NULL) { + wpa_printf(MSG_DEBUG, "P2P: Missing peer " + "address in invitation command"); + return -1; + } + if (wpas_p2p_create_iface(wpa_s)) { + if (wpas_p2p_add_group_interface(wpa_s, + WPA_IF_P2P_GO) < 0) { + wpa_printf(MSG_ERROR, "P2P: Failed to " + "allocate a new interface for the " + "group"); + return -1; + } + bssid = wpa_s->pending_interface_addr; + } else + bssid = wpa_s->own_addr; + } else { + role = P2P_INVITE_ROLE_CLIENT; + peer_addr = ssid->bssid; + } + wpa_s->pending_invite_ssid_id = ssid->id; + + return p2p_invite(wpa_s->global->p2p, peer_addr, role, bssid, + ssid->ssid, ssid->ssid_len, 0, go_dev_addr, 1); +} + + +/* Invite to join an active group */ +int wpas_p2p_invite_group(struct wpa_supplicant *wpa_s, const char *ifname, + const u8 *peer_addr, const u8 *go_dev_addr) +{ + struct wpa_global *global = wpa_s->global; + enum p2p_invite_role role; + u8 *bssid = NULL; + struct wpa_ssid *ssid; + + for (wpa_s = global->ifaces; wpa_s; wpa_s = wpa_s->next) { + if (os_strcmp(wpa_s->ifname, ifname) == 0) + break; + } + if (wpa_s == NULL) { + wpa_printf(MSG_DEBUG, "P2P: Interface '%s' not found", ifname); + return -1; + } + + ssid = wpa_s->current_ssid; + if (ssid == NULL) { + wpa_printf(MSG_DEBUG, "P2P: No current SSID to use for " + "invitation"); + return -1; + } + + if (ssid->mode == WPAS_MODE_P2P_GO) { + role = P2P_INVITE_ROLE_ACTIVE_GO; + bssid = wpa_s->own_addr; + if (go_dev_addr == NULL) + go_dev_addr = wpa_s->own_addr; + } else { + role = P2P_INVITE_ROLE_CLIENT; + if (wpa_s->wpa_state < WPA_ASSOCIATED) { + wpa_printf(MSG_DEBUG, "P2P: Not associated - cannot " + "invite to current group"); + return -1; + } + bssid = wpa_s->bssid; + if (go_dev_addr == NULL && + !is_zero_ether_addr(wpa_s->go_dev_addr)) + go_dev_addr = wpa_s->go_dev_addr; + } + wpa_s->pending_invite_ssid_id = -1; + + return p2p_invite(wpa_s->global->p2p, peer_addr, role, bssid, + ssid->ssid, ssid->ssid_len, 0, go_dev_addr, 0); +} + + +void wpas_p2p_completed(struct wpa_supplicant *wpa_s) +{ + struct wpa_ssid *ssid = wpa_s->current_ssid; + const char *ssid_txt; + u8 go_dev_addr[ETH_ALEN]; + int persistent; + + if (!wpa_s->show_group_started || !ssid) + return; + + wpa_s->show_group_started = 0; + + ssid_txt = wpa_ssid_txt(ssid->ssid, ssid->ssid_len); + os_memset(go_dev_addr, 0, ETH_ALEN); + if (ssid->bssid_set) + os_memcpy(go_dev_addr, ssid->bssid, ETH_ALEN); + persistent = wpas_p2p_persistent_group(wpa_s, go_dev_addr, ssid->ssid, + ssid->ssid_len); + os_memcpy(wpa_s->go_dev_addr, go_dev_addr, ETH_ALEN); + + if (ssid->passphrase == NULL && ssid->psk_set) { + char psk[65]; + wpa_snprintf_hex(psk, sizeof(psk), ssid->psk, 32); + wpa_msg(wpa_s->parent, MSG_INFO, P2P_EVENT_GROUP_STARTED + "%s client ssid=\"%s\" psk=%s go_dev_addr=" MACSTR + "%s", + wpa_s->ifname, ssid_txt, psk, MAC2STR(go_dev_addr), + persistent ? " [PERSISTENT]" : ""); + } else { + wpa_msg(wpa_s->parent, MSG_INFO, P2P_EVENT_GROUP_STARTED + "%s client ssid=\"%s\" passphrase=\"%s\" go_dev_addr=" + MACSTR "%s", + wpa_s->ifname, ssid_txt, + ssid->passphrase ? ssid->passphrase : "", + MAC2STR(go_dev_addr), + persistent ? " [PERSISTENT]" : ""); + } + + if (persistent) + wpas_p2p_store_persistent_group(wpa_s->parent, ssid, + go_dev_addr); +} + + +int wpas_p2p_presence_req(struct wpa_supplicant *wpa_s, u32 duration1, + u32 interval1, u32 duration2, u32 interval2) +{ + if (wpa_s->wpa_state < WPA_ASSOCIATED || + wpa_s->current_ssid == NULL || + wpa_s->current_ssid->mode != WPAS_MODE_INFRA) + return -1; + + return p2p_presence_req(wpa_s->global->p2p, wpa_s->bssid, + wpa_s->own_addr, wpa_s->assoc_freq, + duration1, interval1, duration2, interval2); +} + + +int wpas_p2p_ext_listen(struct wpa_supplicant *wpa_s, unsigned int period, + unsigned int interval) +{ + return p2p_ext_listen(wpa_s->global->p2p, period, interval); +} + + +void wpas_p2p_deauth_notif(struct wpa_supplicant *wpa_s, const u8 *bssid, + u16 reason_code, const u8 *ie, size_t ie_len) +{ + if (wpa_s->global->p2p_disabled) + return; + + p2p_deauth_notif(wpa_s->global->p2p, bssid, reason_code, ie, ie_len); +} + + +void wpas_p2p_disassoc_notif(struct wpa_supplicant *wpa_s, const u8 *bssid, + u16 reason_code, const u8 *ie, size_t ie_len) +{ + if (wpa_s->global->p2p_disabled) + return; + + p2p_disassoc_notif(wpa_s->global->p2p, bssid, reason_code, ie, ie_len); +} + + +void wpas_p2p_update_config(struct wpa_supplicant *wpa_s) +{ + struct p2p_data *p2p = wpa_s->global->p2p; + + if (p2p == NULL) + return; + + if (wpa_s->conf->changed_parameters & CFG_CHANGED_DEVICE_NAME) + p2p_set_dev_name(p2p, wpa_s->conf->device_name); + + if (wpa_s->conf->changed_parameters & CFG_CHANGED_DEVICE_TYPE) { + u8 pri_dev_type[8]; + if (wpa_s->conf->device_type) { + if (wps_dev_type_str2bin(wpa_s->conf->device_type, + pri_dev_type) < 0) { + wpa_printf(MSG_ERROR, "P2P: Invalid " + "device_type"); + } else + p2p_set_pri_dev_type(p2p, pri_dev_type); + } + } + + if (wpa_s->conf->changed_parameters & CFG_CHANGED_SEC_DEVICE_TYPE) { + u8 sec_dev_type[P2P_SEC_DEVICE_TYPES][8]; + size_t num = 0; + int i; + for (i = 0; i < MAX_SEC_DEVICE_TYPES; i++) { + if (wpa_s->conf->sec_device_type[i] == NULL) + continue; + if (wps_dev_type_str2bin( + wpa_s->conf->sec_device_type[i], + sec_dev_type[num]) < 0) { + wpa_printf(MSG_ERROR, "P2P: Invalid " + "sec_device_type"); + continue; + } + num++; + if (num == P2P_SEC_DEVICE_TYPES) + break; + } + p2p_set_sec_dev_types(p2p, (void *) sec_dev_type, num); + } + + if ((wpa_s->conf->changed_parameters & CFG_CHANGED_COUNTRY) && + wpa_s->conf->country[0] && wpa_s->conf->country[1]) { + char country[3]; + country[0] = wpa_s->conf->country[0]; + country[1] = wpa_s->conf->country[1]; + country[2] = 0x04; + p2p_set_country(p2p, country); + } + + if (wpa_s->conf->changed_parameters & CFG_CHANGED_P2P_SSID_POSTFIX) { + p2p_set_ssid_postfix(p2p, (u8 *) wpa_s->conf->p2p_ssid_postfix, + wpa_s->conf->p2p_ssid_postfix ? + os_strlen(wpa_s->conf->p2p_ssid_postfix) : + 0); + } +} diff --git a/wpa_supplicant/p2p_supplicant.h b/wpa_supplicant/p2p_supplicant.h new file mode 100644 index 0000000..6b28dc3 --- /dev/null +++ b/wpa_supplicant/p2p_supplicant.h @@ -0,0 +1,110 @@ +/* + * wpa_supplicant - P2P + * Copyright (c) 2009-2010, Atheros Communications + * + * 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 P2P_SUPPLICANT_H +#define P2P_SUPPLICANT_H + +enum p2p_wps_method; + +int wpas_p2p_init(struct wpa_global *global, struct wpa_supplicant *wpa_s); +void wpas_p2p_deinit(struct wpa_supplicant *wpa_s); +void wpas_p2p_deinit_global(struct wpa_global *global); +int wpas_p2p_connect(struct wpa_supplicant *wpa_s, const u8 *peer_addr, + const char *pin, enum p2p_wps_method wps_method, + int persistent_group, int join, int auth, int go_intent, + int freq); +void wpas_p2p_remain_on_channel_cb(struct wpa_supplicant *wpa_s, + unsigned int freq, unsigned int duration); +void wpas_p2p_cancel_remain_on_channel_cb(struct wpa_supplicant *wpa_s, + unsigned int freq); +int wpas_p2p_group_remove(struct wpa_supplicant *wpa_s, const char *ifname); +int wpas_p2p_group_add(struct wpa_supplicant *wpa_s, int persistent_group, + int freq); +int wpas_p2p_group_add_persistent(struct wpa_supplicant *wpa_s, + struct wpa_ssid *ssid, int addr_allocated, + int freq); +struct p2p_group * wpas_p2p_group_init(struct wpa_supplicant *wpa_s, + int persistent_group, + int group_formation); +void wpas_p2p_wps_success(struct wpa_supplicant *wpa_s, const u8 *peer_addr, + int registrar); +int wpas_p2p_prov_disc(struct wpa_supplicant *wpa_s, const u8 *peer_addr, + const char *config_method); +void wpas_send_action_tx_status(struct wpa_supplicant *wpa_s, const u8 *dst, + const u8 *data, size_t data_len, int ack); +int wpas_p2p_scan_result_text(const u8 *ies, size_t ies_len, char *buf, + char *end); +enum p2p_discovery_type; +int wpas_p2p_find(struct wpa_supplicant *wpa_s, unsigned int timeout, + enum p2p_discovery_type type); +void wpas_p2p_stop_find(struct wpa_supplicant *wpa_s); +int wpas_p2p_listen(struct wpa_supplicant *wpa_s, unsigned int timeout); +int wpas_p2p_assoc_req_ie(struct wpa_supplicant *wpa_s, const u8 *bssid, + u8 *buf, size_t len, int p2p_group); +int wpas_p2p_probe_req_rx(struct wpa_supplicant *wpa_s, const u8 *addr, + const u8 *ie, size_t ie_len); +void wpas_p2p_rx_action(struct wpa_supplicant *wpa_s, const u8 *da, + const u8 *sa, const u8 *bssid, + u8 category, const u8 *data, size_t len, int freq); +void wpas_p2p_scan_ie(struct wpa_supplicant *wpa_s, struct wpabuf *ies); +void wpas_p2p_group_deinit(struct wpa_supplicant *wpa_s); +void wpas_dev_found(void *ctx, const u8 *addr, const u8 *dev_addr, + const u8 *pri_dev_type, const char *dev_name, + u16 config_methods, u8 dev_capab, u8 group_capab); +void wpas_go_neg_completed(void *ctx, struct p2p_go_neg_results *res); +void wpas_go_neg_req_rx(void *ctx, const u8 *src); +void wpas_prov_disc_req(void *ctx, const u8 *peer, u16 config_methods, + const u8 *dev_addr, const u8 *pri_dev_type, + const char *dev_name, u16 supp_config_methods, + u8 dev_capab, u8 group_capab); +void wpas_prov_disc_resp(void *ctx, const u8 *peer, u16 config_methods); +void wpas_sd_request(void *ctx, int freq, const u8 *sa, u8 dialog_token, + u16 update_indic, const u8 *tlvs, size_t tlvs_len); +void wpas_sd_response(void *ctx, const u8 *sa, u16 update_indic, + const u8 *tlvs, size_t tlvs_len); +void * wpas_p2p_sd_request(struct wpa_supplicant *wpa_s, const u8 *dst, + const struct wpabuf *tlvs); +void * wpas_p2p_sd_request_upnp(struct wpa_supplicant *wpa_s, const u8 *dst, + u8 version, const char *query); +int wpas_p2p_sd_cancel_request(struct wpa_supplicant *wpa_s, void *req); +void wpas_p2p_sd_response(struct wpa_supplicant *wpa_s, int freq, + const u8 *dst, u8 dialog_token, + const struct wpabuf *resp_tlvs); +void wpas_p2p_sd_service_update(struct wpa_supplicant *wpa_s); +void wpas_p2p_service_flush(struct wpa_supplicant *wpa_s); +int wpas_p2p_service_add_bonjour(struct wpa_supplicant *wpa_s, + struct wpabuf *query, struct wpabuf *resp); +int wpas_p2p_service_del_bonjour(struct wpa_supplicant *wpa_s, + const struct wpabuf *query); +int wpas_p2p_service_add_upnp(struct wpa_supplicant *wpa_s, u8 version, + const char *service); +int wpas_p2p_service_del_upnp(struct wpa_supplicant *wpa_s, u8 version, + const char *service); +int wpas_p2p_reject(struct wpa_supplicant *wpa_s, const u8 *addr); +int wpas_p2p_invite(struct wpa_supplicant *wpa_s, const u8 *peer_addr, + struct wpa_ssid *ssid, const u8 *go_dev_addr); +int wpas_p2p_invite_group(struct wpa_supplicant *wpa_s, const char *ifname, + const u8 *peer_addr, const u8 *go_dev_addr); +void wpas_p2p_completed(struct wpa_supplicant *wpa_s); +int wpas_p2p_presence_req(struct wpa_supplicant *wpa_s, u32 duration1, + u32 interval1, u32 duration2, u32 interval2); +int wpas_p2p_ext_listen(struct wpa_supplicant *wpa_s, unsigned int period, + unsigned int interval); +void wpas_p2p_deauth_notif(struct wpa_supplicant *wpa_s, const u8 *bssid, + u16 reason_code, const u8 *ie, size_t ie_len); +void wpas_p2p_disassoc_notif(struct wpa_supplicant *wpa_s, const u8 *bssid, + u16 reason_code, const u8 *ie, size_t ie_len); +void wpas_p2p_update_config(struct wpa_supplicant *wpa_s); + +#endif /* P2P_SUPPLICANT_H */ diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c index d6b12e8..b69197e 100644 --- a/wpa_supplicant/wpa_supplicant.c +++ b/wpa_supplicant/wpa_supplicant.c @@ -42,6 +42,7 @@ #include "ibss_rsn.h" #include "sme.h" #include "ap.h" +#include "p2p_supplicant.h" #include "notify.h" #include "bgscan.h" #include "bss.h" @@ -427,6 +428,10 @@ static void wpa_supplicant_cleanup(struct wpa_supplicant *wpa_s) #ifdef CONFIG_AP wpa_supplicant_ap_deinit(wpa_s); #endif /* CONFIG_AP */ + +#ifdef CONFIG_P2P + wpas_p2p_deinit(wpa_s); +#endif /* CONFIG_P2P */ } @@ -545,6 +550,9 @@ void wpa_supplicant_set_state(struct wpa_supplicant *wpa_s, wpa_s->reassociated_connection = 1; wpa_drv_set_operstate(wpa_s, 1); wpa_s->after_wps = 0; +#ifdef CONFIG_P2P + wpas_p2p_completed(wpa_s); +#endif /* CONFIG_P2P */ } else if (state == WPA_DISCONNECTED || state == WPA_ASSOCIATING || state == WPA_ASSOCIATED) { wpa_s->new_connection = 1; @@ -586,7 +594,7 @@ static void wpa_supplicant_terminate(int sig, void *signal_ctx) } -static void wpa_supplicant_clear_status(struct wpa_supplicant *wpa_s) +void wpa_supplicant_clear_status(struct wpa_supplicant *wpa_s) { enum wpa_states old_state = wpa_s->wpa_state; @@ -1884,6 +1892,7 @@ static struct wpa_supplicant * wpa_supplicant_alloc(void) return NULL; wpa_s->scan_req = 1; wpa_s->new_connection = 1; + wpa_s->parent = wpa_s; return wpa_s; } @@ -2092,6 +2101,13 @@ next_driver: } #endif /* CONFIG_IBSS_RSN */ +#ifdef CONFIG_P2P + if (wpas_p2p_init(wpa_s->global, wpa_s) < 0) { + wpa_printf(MSG_ERROR, "Failed to init P2P"); + return -1; + } +#endif /* CONFIG_P2P */ + if (wpa_bss_init(wpa_s) < 0) return -1; @@ -2220,6 +2236,8 @@ int wpa_supplicant_remove_iface(struct wpa_global *global, wpa_printf(MSG_DEBUG, "Removing interface %s", wpa_s->ifname); + if (global->p2p_group_formation == wpa_s) + global->p2p_group_formation = NULL; wpa_supplicant_deinit_iface(wpa_s, 1); os_free(wpa_s); @@ -2279,6 +2297,8 @@ struct wpa_global * wpa_supplicant_init(struct wpa_params *params) global = os_zalloc(sizeof(*global)); if (global == NULL) return NULL; + dl_list_init(&global->p2p_srv_bonjour); + dl_list_init(&global->p2p_srv_upnp); global->params.daemonize = params->daemonize; global->params.wait_for_monitor = params->wait_for_monitor; global->params.dbus_ctrl_interface = params->dbus_ctrl_interface; @@ -2392,6 +2412,10 @@ void wpa_supplicant_deinit(struct wpa_global *global) if (global == NULL) return; +#ifdef CONFIG_P2P + wpas_p2p_deinit_global(global); +#endif /* CONFIG_P2P */ + while (global->ifaces) wpa_supplicant_remove_iface(global, global->ifaces); @@ -2434,5 +2458,9 @@ void wpa_supplicant_update_config(struct wpa_supplicant *wpa_s) wpas_wps_update_config(wpa_s); #endif /* CONFIG_WPS */ +#ifdef CONFIG_P2P + wpas_p2p_update_config(wpa_s); +#endif /* CONFIG_P2P */ + wpa_s->conf->changed_parameters = 0; } diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h index 2ee22be..4f4bdf4 100644 --- a/wpa_supplicant/wpa_supplicant_i.h +++ b/wpa_supplicant/wpa_supplicant_i.h @@ -182,6 +182,18 @@ struct wpa_params { char *override_ctrl_interface; }; +struct p2p_srv_bonjour { + struct dl_list list; + struct wpabuf *query; + struct wpabuf *resp; +}; + +struct p2p_srv_upnp { + struct dl_list list; + u8 version; + char *service; +}; + /** * struct wpa_global - Internal, global data for all %wpa_supplicant interfaces * @@ -196,6 +208,12 @@ struct wpa_global { void **drv_priv; size_t drv_count; struct os_time suspend_time; + struct p2p_data *p2p; + struct wpa_supplicant *p2p_group_formation; + u8 p2p_dev_addr[ETH_ALEN]; + struct dl_list p2p_srv_bonjour; /* struct p2p_srv_bonjour */ + struct dl_list p2p_srv_upnp; /* struct p2p_srv_upnp */ + int p2p_disabled; }; @@ -303,6 +321,7 @@ struct wpa_client_mlme { */ struct wpa_supplicant { struct wpa_global *global; + struct wpa_supplicant *parent; struct wpa_supplicant *next; struct l2_packet_data *l2; struct l2_packet_data *l2_br; @@ -409,7 +428,7 @@ struct wpa_supplicant { u8 ssid[32]; size_t ssid_len; int freq; - u8 assoc_req_ie[80]; + u8 assoc_req_ie[200]; size_t assoc_req_ie_len; int mfp; int ft_used; @@ -429,6 +448,42 @@ struct wpa_supplicant { void *ap_configured_cb_data; #endif /* CONFIG_AP */ +#ifdef CONFIG_P2P + struct p2p_go_neg_results *go_params; + int create_p2p_iface; + u8 pending_interface_addr[ETH_ALEN]; + char pending_interface_name[IFNAMSIZ]; + int pending_interface_type; + int p2p_group_idx; + unsigned int off_channel_freq; + struct wpabuf *pending_action_tx; + u8 pending_action_src[ETH_ALEN]; + u8 pending_action_dst[ETH_ALEN]; + u8 pending_action_bssid[ETH_ALEN]; + unsigned int pending_action_freq; + int pending_action_without_roc; + unsigned int pending_listen_freq; + unsigned int pending_listen_duration; + enum { + NOT_P2P_GROUP_INTERFACE, + P2P_GROUP_INTERFACE_PENDING, + P2P_GROUP_INTERFACE_GO, + P2P_GROUP_INTERFACE_CLIENT + } p2p_group_interface; + struct p2p_group *p2p_group; + int p2p_long_listen; + char p2p_pin[10]; + int p2p_sd_over_ctrl_iface; + int p2p_in_provisioning; + int pending_invite_ssid_id; + int show_group_started; + u8 go_dev_addr[ETH_ALEN]; + int pending_pd_before_join; + u8 pending_join_iface_addr[ETH_ALEN]; + u8 pending_join_dev_addr[ETH_ALEN]; + int pending_join_wps_method; +#endif /* CONFIG_P2P */ + struct wpa_ssid *bgscan_ssid; const struct bgscan_ops *bgscan; void *bgscan_priv; @@ -501,6 +556,7 @@ void wpa_supplicant_rx_eapol(void *ctx, const u8 *src_addr, enum wpa_key_mgmt key_mgmt2driver(int key_mgmt); enum wpa_cipher cipher_suite2driver(int cipher); void wpa_supplicant_update_config(struct wpa_supplicant *wpa_s); +void wpa_supplicant_clear_status(struct wpa_supplicant *wpa_s); /* events.c */ void wpa_supplicant_mark_disassoc(struct wpa_supplicant *wpa_s); diff --git a/wpa_supplicant/wps_supplicant.c b/wpa_supplicant/wps_supplicant.c index 2da1697..6d95921 100644 --- a/wpa_supplicant/wps_supplicant.c +++ b/wpa_supplicant/wps_supplicant.c @@ -32,6 +32,7 @@ #include "blacklist.h" #include "bss.h" #include "scan.h" +#include "p2p_supplicant.h" #include "wps_supplicant.h" @@ -399,6 +400,9 @@ static void wpa_supplicant_wps_event_success(struct wpa_supplicant *wpa_s) wpa_msg(wpa_s, MSG_INFO, WPS_EVENT_SUCCESS); wpa_s->wps_success = 1; wpas_notify_wps_event_success(wpa_s); +#ifdef CONFIG_P2P + wpas_p2p_wps_success(wpa_s, wpa_s->bssid, 0); +#endif /* CONFIG_P2P */ }