diff --git a/src/p2p/p2p.c b/src/p2p/p2p.c index 41d0613eb..6fb65a1ea 100644 --- a/src/p2p/p2p.c +++ b/src/p2p/p2p.c @@ -159,6 +159,13 @@ struct p2ps_advertisement * p2p_get_p2ps_adv_list(struct p2p_data *p2p) } +void p2p_set_intended_addr(struct p2p_data *p2p, const u8 *intended_addr) +{ + if (p2p && intended_addr) + os_memcpy(p2p->intended_addr, intended_addr, ETH_ALEN); +} + + u16 p2p_get_provisioning_info(struct p2p_data *p2p, const u8 *addr) { struct p2p_device *dev = NULL; diff --git a/src/p2p/p2p.h b/src/p2p/p2p.h index 9c7b98974..317dbacda 100644 --- a/src/p2p/p2p.h +++ b/src/p2p/p2p.h @@ -1955,6 +1955,13 @@ const u8 * p2p_get_go_neg_peer(struct p2p_data *p2p); */ unsigned int p2p_get_group_num_members(struct p2p_group *group); +/** + * p2p_client_limit_reached - Check if client limit is reached + * @group: P2P group context from p2p_group_init() + * Returns: 1 if no of clients limit reached + */ +int p2p_client_limit_reached(struct p2p_group *group); + /** * p2p_iterate_group_members - Iterate group members * @group: P2P group context from p2p_group_init() @@ -2170,6 +2177,8 @@ void p2p_loop_on_known_peers(struct p2p_data *p2p, void p2p_set_vendor_elems(struct p2p_data *p2p, struct wpabuf **vendor_elem); +void p2p_set_intended_addr(struct p2p_data *p2p, const u8 *intended_addr); + struct p2ps_advertisement * p2p_service_p2ps_id(struct p2p_data *p2p, u32 adv_id); int p2p_service_add_asp(struct p2p_data *p2p, int auto_accept, u32 adv_id, diff --git a/src/p2p/p2p_group.c b/src/p2p/p2p_group.c index e19eab7db..41ca99faa 100644 --- a/src/p2p/p2p_group.c +++ b/src/p2p/p2p_group.c @@ -988,6 +988,15 @@ unsigned int p2p_get_group_num_members(struct p2p_group *group) } +int p2p_client_limit_reached(struct p2p_group *group) +{ + if (!group || !group->cfg) + return 1; + + return group->num_members >= group->cfg->max_clients; +} + + const u8 * p2p_iterate_group_members(struct p2p_group *group, void **next) { struct p2p_group_member *iter = *next; diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c index 2ee85efb4..6eb759023 100644 --- a/wpa_supplicant/ctrl_iface.c +++ b/wpa_supplicant/ctrl_iface.c @@ -4699,7 +4699,7 @@ static int p2p_ctrl_prov_disc(struct wpa_supplicant *wpa_s, char *cmd) else if (os_strstr(pos, " auto") != NULL) use = WPAS_P2P_PD_AUTO; - return wpas_p2p_prov_disc(wpa_s, addr, pos, use); + return wpas_p2p_prov_disc(wpa_s, addr, pos, use, NULL); } diff --git a/wpa_supplicant/dbus/dbus_new_handlers_p2p.c b/wpa_supplicant/dbus/dbus_new_handlers_p2p.c index a14d35ddc..24822bfd8 100644 --- a/wpa_supplicant/dbus/dbus_new_handlers_p2p.c +++ b/wpa_supplicant/dbus/dbus_new_handlers_p2p.c @@ -730,7 +730,7 @@ DBusMessage * wpas_dbus_handler_p2p_prov_disc_req(DBusMessage *message, wpa_s = wpa_s->p2p_dev; if (wpas_p2p_prov_disc(wpa_s, peer_addr, config_method, - WPAS_P2P_PD_FOR_GO_NEG) < 0) + WPAS_P2P_PD_FOR_GO_NEG, NULL) < 0) return wpas_dbus_error_unknown_error(message, "Failed to send provision discovery request"); diff --git a/wpa_supplicant/p2p_supplicant.c b/wpa_supplicant/p2p_supplicant.c index a3bc6c724..95aa9707e 100644 --- a/wpa_supplicant/p2p_supplicant.c +++ b/wpa_supplicant/p2p_supplicant.c @@ -123,6 +123,8 @@ static void wpas_p2p_stop_find_oper(struct wpa_supplicant *wpa_s); static void wpas_stop_listen(void *ctx); static void wpas_p2p_psk_failure_removal(void *eloop_ctx, void *timeout_ctx); static void wpas_p2p_group_deinit(struct wpa_supplicant *wpa_s); +static int wpas_p2p_add_group_interface(struct wpa_supplicant *wpa_s, + enum wpa_driver_if_type type); /* @@ -475,6 +477,34 @@ static int wpas_p2p_disconnect_safely(struct wpa_supplicant *wpa_s, } +/* Find an interface for a P2P group where we are the GO */ +static struct wpa_supplicant * +wpas_p2p_get_go_group(struct wpa_supplicant *wpa_s) +{ + struct wpa_supplicant *save = NULL; + struct wpa_ssid *s; + + if (!wpa_s) + return NULL; + + 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; + + /* Prefer a group with connected clients */ + if (p2p_get_group_num_members(wpa_s->p2p_group)) + return wpa_s; + save = wpa_s; + } + } + + /* No group with connected clients, so pick the one without (if any) */ + return save; +} + + /* 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) @@ -516,6 +546,193 @@ wpas_p2p_get_persistent_go(struct wpa_supplicant *wpa_s) } +static u8 p2ps_group_capability(void *ctx, u8 incoming, u8 role) +{ + struct wpa_supplicant *wpa_s = ctx, *tmp_wpa_s; + struct wpa_ssid *s; + u8 conncap = P2PS_SETUP_NONE; + unsigned int owned_members = 0; + unsigned int owner = 0; + unsigned int client = 0; + struct wpa_supplicant *go_wpa_s; + struct wpa_ssid *persistent_go; + int p2p_no_group_iface; + + wpa_printf(MSG_DEBUG, "P2P: Conncap - in:%d role:%d", incoming, role); + + /* + * For non-concurrent capable devices: + * If persistent_go, then no new. + * If GO, then no client. + * If client, then no GO. + */ + go_wpa_s = wpas_p2p_get_go_group(wpa_s); + persistent_go = wpas_p2p_get_persistent_go(wpa_s); + p2p_no_group_iface = wpa_s->conf->p2p_no_group_iface; + + wpa_printf(MSG_DEBUG, "P2P: GO(iface)=%p persistent(ssid)=%p", + go_wpa_s, persistent_go); + + for (tmp_wpa_s = wpa_s->global->ifaces; tmp_wpa_s; + tmp_wpa_s = tmp_wpa_s->next) { + for (s = tmp_wpa_s->conf->ssid; s; s = s->next) { + wpa_printf(MSG_DEBUG, + "P2P: sup:%p ssid:%p disabled:%d p2p:%d mode:%d", + tmp_wpa_s, s, s->disabled, + s->p2p_group, s->mode); + if (!s->disabled && s->p2p_group) { + if (s->mode == WPAS_MODE_P2P_GO) { + owned_members += + p2p_get_group_num_members( + tmp_wpa_s->p2p_group); + owner++; + } else + client++; + } + } + } + + /* If not concurrent, restrict our choices */ + if (p2p_no_group_iface) { + wpa_printf(MSG_DEBUG, "P2P: p2p_no_group_iface"); + + if (client) + return P2PS_SETUP_NONE; + + if (go_wpa_s) { + if (role == P2PS_SETUP_CLIENT || + incoming == P2PS_SETUP_GROUP_OWNER || + p2p_client_limit_reached(go_wpa_s->p2p_group)) + return P2PS_SETUP_NONE; + + return P2PS_SETUP_GROUP_OWNER; + } + + if (persistent_go) { + if (role == P2PS_SETUP_NONE || role == P2PS_SETUP_NEW) { + if (!incoming) + return P2PS_SETUP_GROUP_OWNER | + P2PS_SETUP_CLIENT; + if (incoming == P2PS_SETUP_NEW) { + u8 r; + + if (os_get_random(&r, sizeof(r)) < 0 || + (r & 1)) + return P2PS_SETUP_CLIENT; + return P2PS_SETUP_GROUP_OWNER; + } + } + } + } + + /* If a required role has been specified, handle it here */ + if (role && role != P2PS_SETUP_NEW) { + switch (incoming) { + case P2PS_SETUP_NONE: + case P2PS_SETUP_NEW: + case P2PS_SETUP_GROUP_OWNER | P2PS_SETUP_CLIENT: + case P2PS_SETUP_GROUP_OWNER | P2PS_SETUP_NEW: + conncap = role; + goto grp_owner; + + case P2PS_SETUP_GROUP_OWNER: + /* + * Must be a complimentary role - cannot be a client to + * more than one peer. + */ + if (incoming == role || client) + return P2PS_SETUP_NONE; + + return P2PS_SETUP_CLIENT; + + case P2PS_SETUP_CLIENT: + /* Must be a complimentary role */ + if (incoming != role) { + conncap = P2PS_SETUP_GROUP_OWNER; + goto grp_owner; + } + + default: + return P2PS_SETUP_NONE; + } + } + + /* + * For now, we only will support ownership of one group, and being a + * client of one group. Therefore, if we have either an existing GO + * group, or an existing client group, we will not do a new GO + * negotiation, but rather try to re-use the existing groups. + */ + switch (incoming) { + case P2PS_SETUP_NONE: + case P2PS_SETUP_NEW: + if (client) + conncap = P2PS_SETUP_GROUP_OWNER; + else if (!owned_members) + conncap = P2PS_SETUP_NEW; + else if (incoming == P2PS_SETUP_NONE) + conncap = P2PS_SETUP_GROUP_OWNER | P2PS_SETUP_CLIENT; + else + conncap = P2PS_SETUP_CLIENT; + break; + + case P2PS_SETUP_CLIENT: + conncap = P2PS_SETUP_GROUP_OWNER; + break; + + case P2PS_SETUP_GROUP_OWNER: + if (!client) + conncap = P2PS_SETUP_CLIENT; + break; + + case P2PS_SETUP_GROUP_OWNER | P2PS_SETUP_NEW: + case P2PS_SETUP_GROUP_OWNER | P2PS_SETUP_CLIENT: + if (client) + conncap = P2PS_SETUP_GROUP_OWNER; + else { + u8 r; + + if (os_get_random(&r, sizeof(r)) < 0 || + (r & 1)) + conncap = P2PS_SETUP_CLIENT; + else + conncap = P2PS_SETUP_GROUP_OWNER; + } + break; + + default: + return P2PS_SETUP_NONE; + } + +grp_owner: + if ((conncap & P2PS_SETUP_GROUP_OWNER) || + (!incoming && (conncap & P2PS_SETUP_NEW))) { + if (go_wpa_s && p2p_client_limit_reached(go_wpa_s->p2p_group)) + conncap &= ~P2PS_SETUP_GROUP_OWNER; + wpa_printf(MSG_DEBUG, "P2P: GOs:%d members:%d conncap:%d", + owner, owned_members, conncap); + + s = wpas_p2p_get_persistent_go(wpa_s); + + if (!s && !owner && p2p_no_group_iface) { + p2p_set_intended_addr(wpa_s->global->p2p, + wpa_s->own_addr); + } else if (!s && !owner) { + 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 P2PS_SETUP_NONE; + } + p2p_set_intended_addr(wpa_s->global->p2p, + wpa_s->pending_interface_addr); + } + } + + return conncap; +} + + static int wpas_p2p_group_delete(struct wpa_supplicant *wpa_s, enum p2p_group_removal_reason removal_reason) { @@ -6293,13 +6510,24 @@ int wpas_p2p_wps_eapol_cb(struct wpa_supplicant *wpa_s) int wpas_p2p_prov_disc(struct wpa_supplicant *wpa_s, const u8 *peer_addr, const char *config_method, - enum wpas_p2p_prov_disc_use use) + enum wpas_p2p_prov_disc_use use, + struct p2ps_provision *p2ps_prov) { u16 config_methods; wpa_s->p2p_fallback_to_go_neg = 0; wpa_s->pending_pd_use = NORMAL_PD; - if (os_strncmp(config_method, "display", 7) == 0) + if (p2ps_prov && use == WPAS_P2P_PD_FOR_ASP) { + p2ps_prov->conncap = p2ps_group_capability( + wpa_s, P2PS_SETUP_NONE, p2ps_prov->role); + wpa_printf(MSG_DEBUG, + "P2P: %s conncap: %d - ASP parsed: %x %x %d %s", + __func__, p2ps_prov->conncap, + p2ps_prov->adv_id, p2ps_prov->conncap, + p2ps_prov->status, p2ps_prov->info); + + config_methods = 0; + } else if (os_strncmp(config_method, "display", 7) == 0) config_methods = WPS_CONFIG_DISPLAY; else if (os_strncmp(config_method, "keypad", 6) == 0) config_methods = WPS_CONFIG_KEYPAD; @@ -6308,6 +6536,7 @@ int wpas_p2p_prov_disc(struct wpa_supplicant *wpa_s, const u8 *peer_addr, config_methods = WPS_CONFIG_PUSHBUTTON; else { wpa_printf(MSG_DEBUG, "P2P: Unknown config method"); + os_free(p2ps_prov); return -1; } @@ -6328,10 +6557,12 @@ int wpas_p2p_prov_disc(struct wpa_supplicant *wpa_s, const u8 *peer_addr, return 0; } - if (wpa_s->global->p2p == NULL || wpa_s->global->p2p_disabled) + if (wpa_s->global->p2p == NULL || wpa_s->global->p2p_disabled) { + os_free(p2ps_prov); return -1; + } - return p2p_prov_disc_req(wpa_s->global->p2p, peer_addr, NULL, + return p2p_prov_disc_req(wpa_s->global->p2p, peer_addr, p2ps_prov, config_methods, use == WPAS_P2P_PD_FOR_JOIN, 0, 1); } diff --git a/wpa_supplicant/p2p_supplicant.h b/wpa_supplicant/p2p_supplicant.h index fad7f6fd0..1cf157364 100644 --- a/wpa_supplicant/p2p_supplicant.h +++ b/wpa_supplicant/p2p_supplicant.h @@ -15,6 +15,7 @@ enum p2p_send_action_result; struct p2p_peer_info; struct p2p_channels; struct wps_event_fail; +struct p2ps_provision; int wpas_p2p_add_p2pdev_interface(struct wpa_supplicant *wpa_s, const char *conf_p2p_dev); @@ -41,11 +42,13 @@ struct p2p_group * wpas_p2p_group_init(struct wpa_supplicant *wpa_s, enum wpas_p2p_prov_disc_use { WPAS_P2P_PD_FOR_GO_NEG, WPAS_P2P_PD_FOR_JOIN, - WPAS_P2P_PD_AUTO + WPAS_P2P_PD_AUTO, + WPAS_P2P_PD_FOR_ASP }; int wpas_p2p_prov_disc(struct wpa_supplicant *wpa_s, const u8 *peer_addr, const char *config_method, - enum wpas_p2p_prov_disc_use use); + enum wpas_p2p_prov_disc_use use, + struct p2ps_provision *p2ps_prov); void wpas_send_action_tx_status(struct wpa_supplicant *wpa_s, const u8 *dst, const u8 *data, size_t data_len, enum p2p_send_action_result result); diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h index 7d22000a0..9ee36a628 100644 --- a/wpa_supplicant/wpa_supplicant_i.h +++ b/wpa_supplicant/wpa_supplicant_i.h @@ -771,7 +771,7 @@ struct wpa_supplicant { int force_long_sd; u16 pending_pd_config_methods; enum { - NORMAL_PD, AUTO_PD_GO_NEG, AUTO_PD_JOIN + NORMAL_PD, AUTO_PD_GO_NEG, AUTO_PD_JOIN, AUTO_PD_ASP } pending_pd_use; /*