diff --git a/src/p2p/p2p.c b/src/p2p/p2p.c index 5a7352a01..41d0613eb 100644 --- a/src/p2p/p2p.c +++ b/src/p2p/p2p.c @@ -2887,6 +2887,7 @@ void p2p_deinit(struct p2p_data *p2p) os_free(p2p->cfg->serial_number); os_free(p2p->cfg->pref_chan); os_free(p2p->groups); + os_free(p2p->p2ps_prov); wpabuf_free(p2p->sd_resp); os_free(p2p->after_scan_tx); p2p_remove_wps_vendor_extensions(p2p); diff --git a/src/p2p/p2p.h b/src/p2p/p2p.h index bbf9cbd96..9c7b98974 100644 --- a/src/p2p/p2p.h +++ b/src/p2p/p2p.h @@ -153,6 +153,53 @@ struct p2p_go_neg_results { unsigned int peer_config_timeout; }; +struct p2ps_provision { + /** + * status - Remote returned provisioning status code + */ + int status; + + /** + * adv_id - P2PS Advertisement ID + */ + u32 adv_id; + + /** + * session_id - P2PS Session ID + */ + u32 session_id; + + /** + * method - WPS Method (to be) used to establish session + */ + u16 method; + + /** + * conncap - Connection Capabilities negotiated between P2P peers + */ + u8 conncap; + + /** + * role - Info about the roles to be used for this connection + */ + u8 role; + + /** + * session_mac - MAC address of the peer that started the session + */ + u8 session_mac[ETH_ALEN]; + + /** + * adv_mac - MAC address of the peer advertised the service + */ + u8 adv_mac[ETH_ALEN]; + + /** + * info - Vendor defined extra Provisioning information + */ + char info[0]; +}; + struct p2ps_advertisement { struct p2ps_advertisement *next; @@ -891,6 +938,38 @@ struct p2p_config { * or 0 if not. */ int (*is_p2p_in_progress)(void *ctx); + + /** + * Determine if we have a persistent group we share with remote peer + * @ctx: Callback context from cb_ctx + * @addr: Peer device address to search for + * @ssid: Persistent group SSID or %NULL if any + * @ssid_len: Length of @ssid + * @go_dev_addr: Buffer for returning intended GO P2P Device Address + * @ret_ssid: Buffer for returning group SSID + * @ret_ssid_len: Buffer for returning length of @ssid + * Returns: 1 if a matching persistent group was found, 0 otherwise + */ + int (*get_persistent_group)(void *ctx, const u8 *addr, const u8 *ssid, + size_t ssid_len, u8 *go_dev_addr, + u8 *ret_ssid, size_t *ret_ssid_len); + + /** + * Get information about a possible local GO role + * @ctx: Callback context from cb_ctx + * @intended_addr: Buffer for returning intended GO interface address + * @ssid: Buffer for returning group SSID + * @ssid_len: Buffer for returning length of @ssid + * @group_iface: Buffer for returning whether a separate group interface + * would be used + * Returns: 1 if GO info found, 0 otherwise + * + * This is used to compose New Group settings (SSID, and intended + * address) during P2PS provisioning if results of provisioning *might* + * result in our being an autonomous GO. + */ + int (*get_go_info)(void *ctx, u8 *intended_addr, + u8 *ssid, size_t *ssid_len, int *group_iface); }; @@ -1117,6 +1196,7 @@ 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 + * @p2ps_prov: Provisioning info for P2PS * @config_methods: WPS Config Methods value (only one bit set) * @join: Whether this is used by a client joining an active group * @force_freq: Forced TX frequency for the frame (mainly for the join case) @@ -1132,7 +1212,8 @@ int p2p_reject(struct p2p_data *p2p, const u8 *peer_addr); * 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, int force_freq, + struct p2ps_provision *p2ps_prov, u16 config_methods, + int join, int force_freq, int user_initiated_pd); /** diff --git a/src/p2p/p2p_go_neg.c b/src/p2p/p2p_go_neg.c index fad779186..98abf9d22 100644 --- a/src/p2p/p2p_go_neg.c +++ b/src/p2p/p2p_go_neg.c @@ -227,7 +227,7 @@ int p2p_connect_send(struct p2p_data *p2p, struct p2p_device *dev) else return -1; return p2p_prov_disc_req(p2p, dev->info.p2p_device_addr, - config_method, 0, 0, 1); + NULL, config_method, 0, 0, 1); } freq = dev->listen_freq > 0 ? dev->listen_freq : dev->oper_freq; @@ -494,8 +494,8 @@ void p2p_reselect_channel(struct p2p_data *p2p, } -static int p2p_go_select_channel(struct p2p_data *p2p, struct p2p_device *dev, - u8 *status) +int p2p_go_select_channel(struct p2p_data *p2p, struct p2p_device *dev, + u8 *status) { struct p2p_channels tmp, intersection; diff --git a/src/p2p/p2p_i.h b/src/p2p/p2p_i.h index 127e484c2..f80b9baf3 100644 --- a/src/p2p/p2p_i.h +++ b/src/p2p/p2p_i.h @@ -502,6 +502,7 @@ struct p2p_data { /* ASP Support */ struct p2ps_advertisement *p2ps_adv_list; + struct p2ps_provision *p2ps_prov; u8 wild_card_hash[P2PS_HASH_LEN]; u8 query_hash[P2P_MAX_QUERY_HASH * P2PS_HASH_LEN]; u8 query_count; @@ -847,6 +848,8 @@ int p2p_prepare_channel(struct p2p_data *p2p, struct p2p_device *dev, unsigned int force_freq, unsigned int pref_freq, int go); void p2p_go_neg_wait_timeout(void *eloop_ctx, void *timeout_ctx); +int p2p_go_select_channel(struct p2p_data *p2p, struct p2p_device *dev, + u8 *status); void p2p_dbg(struct p2p_data *p2p, const char *fmt, ...) PRINTF_FORMAT(2, 3); void p2p_info(struct p2p_data *p2p, const char *fmt, ...) diff --git a/src/p2p/p2p_pd.c b/src/p2p/p2p_pd.c index e1013672a..ddf619c26 100644 --- a/src/p2p/p2p_pd.c +++ b/src/p2p/p2p_pd.c @@ -40,14 +40,131 @@ static void p2p_build_wps_ie_config_methods(struct wpabuf *buf, } +static void p2ps_add_new_group_info(struct p2p_data *p2p, struct wpabuf *buf) +{ + int found; + u8 intended_addr[ETH_ALEN]; + u8 ssid[32]; + size_t ssid_len; + int group_iface; + + if (!p2p->cfg->get_go_info) + return; + + found = p2p->cfg->get_go_info( + p2p->cfg->cb_ctx, intended_addr, ssid, + &ssid_len, &group_iface); + if (found) { + p2p_buf_add_group_id(buf, p2p->cfg->dev_addr, + ssid, ssid_len); + p2p_buf_add_intended_addr(buf, intended_addr); + } else { + if (!p2p->ssid_set) { + p2p_build_ssid(p2p, p2p->ssid, &p2p->ssid_len); + p2p->ssid_set = 1; + } + + /* Add pre-composed P2P Group ID */ + p2p_buf_add_group_id(buf, p2p->cfg->dev_addr, + p2p->ssid, p2p->ssid_len); + + if (group_iface) + p2p_buf_add_intended_addr( + buf, p2p->intended_addr); + else + p2p_buf_add_intended_addr( + buf, p2p->cfg->dev_addr); + } +} + + +static void p2ps_add_pd_req_attrs(struct p2p_data *p2p, struct p2p_device *dev, + struct wpabuf *buf, u16 config_methods) +{ + struct p2ps_provision *prov = p2p->p2ps_prov; + u8 feat_cap_mask[] = { 1, 0 }; + int shared_group = 0; + u8 ssid[32]; + size_t ssid_len; + u8 go_dev_addr[ETH_ALEN]; + + /* If we might be explicite group owner, add GO details */ + if (prov->conncap & (P2PS_SETUP_GROUP_OWNER | + P2PS_SETUP_NEW)) + p2ps_add_new_group_info(p2p, buf); + + if (prov->status >= 0) + p2p_buf_add_status(buf, (u8) prov->status); + else + prov->method = config_methods; + + if (p2p->cfg->get_persistent_group) { + shared_group = p2p->cfg->get_persistent_group( + p2p->cfg->cb_ctx, dev->info.p2p_device_addr, NULL, 0, + go_dev_addr, ssid, &ssid_len); + } + + /* Add Operating Channel if conncap includes GO */ + if (shared_group || + (prov->conncap & (P2PS_SETUP_GROUP_OWNER | + P2PS_SETUP_NEW))) { + u8 tmp; + + p2p_go_select_channel(p2p, dev, &tmp); + + if (p2p->op_reg_class && p2p->op_channel) + p2p_buf_add_operating_channel(buf, p2p->cfg->country, + p2p->op_reg_class, + p2p->op_channel); + else + p2p_buf_add_operating_channel(buf, p2p->cfg->country, + p2p->cfg->op_reg_class, + p2p->cfg->op_channel); + } + + p2p_buf_add_channel_list(buf, p2p->cfg->country, &p2p->cfg->channels); + + if (prov->info[0]) + p2p_buf_add_session_info(buf, prov->info); + + p2p_buf_add_connection_capability(buf, prov->conncap); + + p2p_buf_add_advertisement_id(buf, prov->adv_id, prov->adv_mac); + + if (shared_group || prov->conncap == P2PS_SETUP_NEW || + prov->conncap == + (P2PS_SETUP_GROUP_OWNER | P2PS_SETUP_NEW) || + prov->conncap == + (P2PS_SETUP_GROUP_OWNER | P2PS_SETUP_CLIENT)) { + /* Add Config Timeout */ + p2p_buf_add_config_timeout(buf, p2p->go_timeout, + p2p->client_timeout); + } + + p2p_buf_add_listen_channel(buf, p2p->cfg->country, p2p->cfg->reg_class, + p2p->cfg->channel); + + p2p_buf_add_session_id(buf, prov->session_id, prov->session_mac); + + p2p_buf_add_feature_capability(buf, sizeof(feat_cap_mask), + feat_cap_mask); + + if (shared_group) + p2p_buf_add_persistent_group_info(buf, go_dev_addr, + ssid, ssid_len); +} + + static struct wpabuf * p2p_build_prov_disc_req(struct p2p_data *p2p, - u8 dialog_token, - u16 config_methods, - struct p2p_device *go) + struct p2p_device *dev, + int join) { struct wpabuf *buf; u8 *len; size_t extra = 0; + u8 dialog_token = dev->dialog_token; + u16 config_methods = dev->req_config_methods; + struct p2p_device *go = join ? dev : NULL; #ifdef CONFIG_WIFI_DISPLAY if (p2p->wfd_ie_prov_disc_req) @@ -57,6 +174,10 @@ static struct wpabuf * p2p_build_prov_disc_req(struct p2p_data *p2p, if (p2p->vendor_elem && p2p->vendor_elem[VENDOR_ELEM_P2P_PD_REQ]) extra += wpabuf_len(p2p->vendor_elem[VENDOR_ELEM_P2P_PD_REQ]); + if (p2p->p2ps_prov) + extra += os_strlen(p2p->p2ps_prov->info) + 1 + + sizeof(struct p2ps_provision); + buf = wpabuf_alloc(1000 + extra); if (buf == NULL) return NULL; @@ -67,7 +188,9 @@ static struct wpabuf * p2p_build_prov_disc_req(struct p2p_data *p2p, p2p_buf_add_capability(buf, p2p->dev_capab & ~P2P_DEV_CAPAB_CLIENT_DISCOVERABILITY, 0); p2p_buf_add_device_info(buf, p2p, NULL); - if (go) { + if (p2p->p2ps_prov) { + p2ps_add_pd_req_attrs(p2p, dev, buf, config_methods); + } else if (go) { p2p_buf_add_group_id(buf, go->info.p2p_device_addr, go->oper_ssid, go->oper_ssid_len); } @@ -388,9 +511,33 @@ int p2p_send_prov_disc_req(struct p2p_data *p2p, struct p2p_device *dev, /* TODO: use device discoverability request through GO */ } - req = p2p_build_prov_disc_req(p2p, dev->dialog_token, - dev->req_config_methods, - join ? dev : NULL); + if (p2p->p2ps_prov) { + if (p2p->p2ps_prov->status == P2P_SC_SUCCESS_DEFERRED) { + if (p2p->p2ps_prov->method == WPS_CONFIG_DISPLAY) + dev->req_config_methods = WPS_CONFIG_KEYPAD; + else if (p2p->p2ps_prov->method == WPS_CONFIG_KEYPAD) + dev->req_config_methods = WPS_CONFIG_DISPLAY; + else + dev->req_config_methods = WPS_CONFIG_P2PS; + } else { + /* Order of preference, based on peer's capabilities */ + if (p2p->p2ps_prov->method) + dev->req_config_methods = + p2p->p2ps_prov->method; + else if (dev->info.config_methods & WPS_CONFIG_P2PS) + dev->req_config_methods = WPS_CONFIG_P2PS; + else if (dev->info.config_methods & WPS_CONFIG_DISPLAY) + dev->req_config_methods = WPS_CONFIG_DISPLAY; + else + dev->req_config_methods = WPS_CONFIG_KEYPAD; + } + p2p_dbg(p2p, + "Building PD Request based on P2PS config method 0x%x status %d --> req_config_methods 0x%x", + p2p->p2ps_prov->method, p2p->p2ps_prov->status, + dev->req_config_methods); + } + + req = p2p_build_prov_disc_req(p2p, dev, join); if (req == NULL) return -1; @@ -413,6 +560,7 @@ int p2p_send_prov_disc_req(struct p2p_data *p2p, struct p2p_device *dev, int p2p_prov_disc_req(struct p2p_data *p2p, const u8 *peer_addr, + struct p2ps_provision *p2ps_prov, u16 config_methods, int join, int force_freq, int user_initiated_pd) { @@ -424,17 +572,28 @@ int p2p_prov_disc_req(struct p2p_data *p2p, const u8 *peer_addr, if (dev == NULL || (dev->flags & P2P_DEV_PROBE_REQ_ONLY)) { p2p_dbg(p2p, "Provision Discovery Request destination " MACSTR " not yet known", MAC2STR(peer_addr)); + os_free(p2ps_prov); return -1; } p2p_dbg(p2p, "Provision Discovery Request with " MACSTR " (config methods 0x%x)", MAC2STR(peer_addr), config_methods); - if (config_methods == 0) + if (config_methods == 0 && !p2ps_prov) { + os_free(p2ps_prov); return -1; + } + + if (p2ps_prov && p2ps_prov->status == P2P_SC_SUCCESS_DEFERRED && + p2p->p2ps_prov) { + /* Use cached method from deferred provisioning */ + p2ps_prov->method = p2p->p2ps_prov->method; + } /* Reset provisioning info */ dev->wps_prov_info = 0; + os_free(p2p->p2ps_prov); + p2p->p2ps_prov = p2ps_prov; dev->req_config_methods = config_methods; if (join) diff --git a/wpa_supplicant/p2p_supplicant.c b/wpa_supplicant/p2p_supplicant.c index 79469506c..a3bc6c724 100644 --- a/wpa_supplicant/p2p_supplicant.c +++ b/wpa_supplicant/p2p_supplicant.c @@ -475,6 +475,47 @@ static int wpas_p2p_disconnect_safely(struct wpa_supplicant *wpa_s, } +/* Find an active P2P group where we are the GO */ +static struct wpa_ssid * wpas_p2p_group_go_ssid(struct wpa_supplicant *wpa_s, + u8 *bssid) +{ + struct wpa_ssid *s, *empty = NULL; + + if (!wpa_s) + return 0; + + for (wpa_s = wpa_s->global->ifaces; wpa_s; wpa_s = wpa_s->next) { + for (s = wpa_s->conf->ssid; s; s = s->next) { + if (s->disabled || !s->p2p_group || + s->mode != WPAS_MODE_P2P_GO) + continue; + + os_memcpy(bssid, wpa_s->own_addr, ETH_ALEN); + if (p2p_get_group_num_members(wpa_s->p2p_group)) + return s; + empty = s; + } + } + + return empty; +} + + +/* Find a persistent group where we are the GO */ +static struct wpa_ssid * +wpas_p2p_get_persistent_go(struct wpa_supplicant *wpa_s) +{ + struct wpa_ssid *s; + + for (s = wpa_s->conf->ssid; s; s = s->next) { + if (s->disabled == 2 && s->mode == WPAS_MODE_P2P_GO) + return s; + } + + return NULL; +} + + static int wpas_p2p_group_delete(struct wpa_supplicant *wpa_s, enum p2p_group_removal_reason removal_reason) { @@ -4371,6 +4412,51 @@ static void wpas_presence_resp(void *ctx, const u8 *src, u8 status, } +static int wpas_get_persistent_group(void *ctx, const u8 *addr, const u8 *ssid, + size_t ssid_len, u8 *go_dev_addr, + u8 *ret_ssid, size_t *ret_ssid_len) +{ + struct wpa_supplicant *wpa_s = ctx; + struct wpa_ssid *s; + + s = wpas_p2p_get_persistent(wpa_s, addr, ssid, ssid_len); + if (s) { + os_memcpy(ret_ssid, s->ssid, s->ssid_len); + *ret_ssid_len = s->ssid_len; + os_memcpy(go_dev_addr, s->bssid, ETH_ALEN); + return 1; + } + + return 0; +} + + +static int wpas_get_go_info(void *ctx, u8 *intended_addr, + u8 *ssid, size_t *ssid_len, int *group_iface) +{ + struct wpa_supplicant *wpa_s = ctx; + struct wpa_ssid *s; + u8 bssid[ETH_ALEN]; + + s = wpas_p2p_group_go_ssid(wpa_s, bssid); + if (!s) { + s = wpas_p2p_get_persistent_go(wpa_s); + if (s) + os_memcpy(bssid, s->bssid, ETH_ALEN); + } + + *group_iface = wpas_p2p_create_iface(wpa_s); + if (!s) + return 0; + + os_memcpy(intended_addr, bssid, ETH_ALEN); + os_memcpy(ssid, s->ssid, s->ssid_len); + *ssid_len = s->ssid_len; + + return 1; +} + + static int _wpas_p2p_in_progress(void *ctx) { struct wpa_supplicant *wpa_s = ctx; @@ -4425,6 +4511,8 @@ int wpas_p2p_init(struct wpa_global *global, struct wpa_supplicant *wpa_s) p2p.presence_resp = wpas_presence_resp; p2p.is_concurrent_session_active = wpas_is_concurrent_session_active; p2p.is_p2p_in_progress = _wpas_p2p_in_progress; + p2p.get_persistent_group = wpas_get_persistent_group; + p2p.get_go_info = wpas_get_go_info; os_memcpy(wpa_s->global->p2p_dev_addr, wpa_s->own_addr, ETH_ALEN); os_memcpy(p2p.dev_addr, wpa_s->global->p2p_dev_addr, ETH_ALEN); @@ -4844,7 +4932,7 @@ static void wpas_p2p_scan_res_join(struct wpa_supplicant *wpa_s, wpa_printf(MSG_DEBUG, "P2P: Auto PD with " MACSTR " join=%d", MAC2STR(wpa_s->pending_join_dev_addr), join); if (p2p_prov_disc_req(wpa_s->global->p2p, - wpa_s->pending_join_dev_addr, + wpa_s->pending_join_dev_addr, NULL, wpa_s->pending_pd_config_methods, join, 0, wpa_s->user_initiated_pd) < 0) { wpa_s->p2p_auto_pd = 0; @@ -4973,7 +5061,8 @@ static void wpas_p2p_scan_res_join(struct wpa_supplicant *wpa_s, } if (p2p_prov_disc_req(wpa_s->global->p2p, - wpa_s->pending_join_dev_addr, method, 1, + wpa_s->pending_join_dev_addr, + NULL, method, 1, freq, wpa_s->user_initiated_pd) < 0) { wpa_printf(MSG_DEBUG, "P2P: Failed to send Provision " "Discovery Request before joining an " @@ -6242,7 +6331,7 @@ int wpas_p2p_prov_disc(struct wpa_supplicant *wpa_s, const u8 *peer_addr, if (wpa_s->global->p2p == NULL || wpa_s->global->p2p_disabled) return -1; - return p2p_prov_disc_req(wpa_s->global->p2p, peer_addr, + return p2p_prov_disc_req(wpa_s->global->p2p, peer_addr, NULL, config_methods, use == WPAS_P2P_PD_FOR_JOIN, 0, 1); }