P2P: Reduce redundant PSK generation for GO
[mech_eap.git] / wpa_supplicant / p2p_supplicant.c
index 6abdd68..78b0d20 100644 (file)
@@ -260,7 +260,7 @@ static int wpas_p2p_group_delete(struct wpa_supplicant *wpa_s,
                 */
                ssid = wpa_s->conf->ssid;
                while (ssid) {
-                       if (ssid->p2p_group)
+                       if (ssid->p2p_group && ssid->disabled != 2)
                                break;
                        ssid = ssid->next;
                }
@@ -360,6 +360,7 @@ static int wpas_p2p_group_delete(struct wpa_supplicant *wpa_s,
                wpa_config_remove_network(wpa_s->conf, id);
                wpa_supplicant_clear_status(wpa_s);
                wpa_supplicant_cancel_sched_scan(wpa_s);
+               wpa_s->sta_scan_pending = 0;
        } else {
                wpa_printf(MSG_DEBUG, "P2P: Temporary group network not "
                           "found");
@@ -836,7 +837,7 @@ static void p2p_go_configured(void *ctx, void *data)
                                          params->peer_device_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);
+                                         wpa_s->p2p_pin, NULL, 0, 0);
        os_free(wpa_s->go_params);
        wpa_s->go_params = NULL;
 }
@@ -887,7 +888,11 @@ static void wpas_start_wps_go(struct wpa_supplicant *wpa_s,
                wpa_config_remove_network(wpa_s->conf, ssid->id);
                return;
        }
-       wpa_config_update_psk(ssid);
+       ssid->psk_set = params->psk_set;
+       if (ssid->psk_set)
+               os_memcpy(ssid->psk, params->psk, sizeof(ssid->psk));
+       else
+               wpa_config_update_psk(ssid);
        ssid->ap_max_inactivity = wpa_s->parent->conf->p2p_go_max_inactivity;
 
        wpa_s->ap_configured_cb = p2p_go_configured;
@@ -1233,6 +1238,135 @@ static int wpas_send_probe_resp(void *ctx, const struct wpabuf *buf)
 }
 
 
+/*
+ * DNS Header section is used only to calculate compression pointers, so the
+ * contents of this data does not matter, but the length needs to be reserved
+ * in the virtual packet.
+ */
+#define DNS_HEADER_LEN 12
+
+/*
+ * 27-octet in-memory packet from P2P specification containing two implied
+ * queries for _tcp.lcoal. PTR IN and _udp.local. PTR IN
+ */
+#define P2P_SD_IN_MEMORY_LEN 27
+
+static int p2p_sd_dns_uncompress_label(char **upos, char *uend, u8 *start,
+                                      u8 **spos, const u8 *end)
+{
+       while (*spos < end) {
+               u8 val = ((*spos)[0] & 0xc0) >> 6;
+               int len;
+
+               if (val == 1 || val == 2) {
+                       /* These are reserved values in RFC 1035 */
+                       wpa_printf(MSG_DEBUG, "P2P: Invalid domain name "
+                                  "sequence starting with 0x%x", val);
+                       return -1;
+               }
+
+               if (val == 3) {
+                       u16 offset;
+                       u8 *spos_tmp;
+
+                       /* Offset */
+                       if (*spos + 2 > end) {
+                               wpa_printf(MSG_DEBUG, "P2P: No room for full "
+                                          "DNS offset field");
+                               return -1;
+                       }
+
+                       offset = (((*spos)[0] & 0x3f) << 8) | (*spos)[1];
+                       if (offset >= *spos - start) {
+                               wpa_printf(MSG_DEBUG, "P2P: Invalid DNS "
+                                          "pointer offset %u", offset);
+                               return -1;
+                       }
+
+                       (*spos) += 2;
+                       spos_tmp = start + offset;
+                       return p2p_sd_dns_uncompress_label(upos, uend, start,
+                                                          &spos_tmp,
+                                                          *spos - 2);
+               }
+
+               /* Label */
+               len = (*spos)[0] & 0x3f;
+               if (len == 0)
+                       return 0;
+
+               (*spos)++;
+               if (*spos + len > end) {
+                       wpa_printf(MSG_DEBUG, "P2P: Invalid domain name "
+                                  "sequence - no room for label with length "
+                                  "%u", len);
+                       return -1;
+               }
+
+               if (*upos + len + 2 > uend)
+                       return -2;
+
+               os_memcpy(*upos, *spos, len);
+               *spos += len;
+               *upos += len;
+               (*upos)[0] = '.';
+               (*upos)++;
+               (*upos)[0] = '\0';
+       }
+
+       return 0;
+}
+
+
+/* Uncompress domain names per RFC 1035 using the P2P SD in-memory packet.
+ * Returns -1 on parsing error (invalid input sequence), -2 if output buffer is
+ * not large enough */
+static int p2p_sd_dns_uncompress(char *buf, size_t buf_len, const u8 *msg,
+                                size_t msg_len, size_t offset)
+{
+       /* 27-octet in-memory packet from P2P specification */
+       const char *prefix = "\x04_tcp\x05local\x00\x00\x0C\x00\x01"
+               "\x04_udp\xC0\x11\x00\x0C\x00\x01";
+       u8 *tmp, *end, *spos;
+       char *upos, *uend;
+       int ret = 0;
+
+       if (buf_len < 2)
+               return -1;
+       if (offset > msg_len)
+               return -1;
+
+       tmp = os_malloc(DNS_HEADER_LEN + P2P_SD_IN_MEMORY_LEN + msg_len);
+       if (tmp == NULL)
+               return -1;
+       spos = tmp + DNS_HEADER_LEN + P2P_SD_IN_MEMORY_LEN;
+       end = spos + msg_len;
+       spos += offset;
+
+       os_memset(tmp, 0, DNS_HEADER_LEN);
+       os_memcpy(tmp + DNS_HEADER_LEN, prefix, P2P_SD_IN_MEMORY_LEN);
+       os_memcpy(tmp + DNS_HEADER_LEN + P2P_SD_IN_MEMORY_LEN, msg, msg_len);
+
+       upos = buf;
+       uend = buf + buf_len;
+
+       ret = p2p_sd_dns_uncompress_label(&upos, uend, tmp, &spos, end);
+       if (ret) {
+               os_free(tmp);
+               return ret;
+       }
+
+       if (upos == buf) {
+               upos[0] = '.';
+               upos[1] = '\0';
+       } else if (upos[-1] == '.')
+               upos[-1] = '\0';
+
+       os_free(tmp);
+       return 0;
+}
+
+
 static struct p2p_srv_bonjour *
 wpas_p2p_service_get_bonjour(struct wpa_supplicant *wpa_s,
                             const struct wpabuf *query)
@@ -1323,13 +1457,40 @@ static void wpas_sd_all_bonjour(struct wpa_supplicant *wpa_s,
 }
 
 
+static int match_bonjour_query(struct p2p_srv_bonjour *bsrv, const u8 *query,
+                              size_t query_len)
+{
+       char str_rx[256], str_srv[256];
+
+       if (query_len < 3 || wpabuf_len(bsrv->query) < 3)
+               return 0; /* Too short to include DNS Type and Version */
+       if (os_memcmp(query + query_len - 3,
+                     wpabuf_head_u8(bsrv->query) + wpabuf_len(bsrv->query) - 3,
+                     3) != 0)
+               return 0; /* Mismatch in DNS Type or Version */
+       if (query_len == wpabuf_len(bsrv->query) &&
+           os_memcmp(query, wpabuf_head(bsrv->query), query_len - 3) == 0)
+               return 1; /* Binary match */
+
+       if (p2p_sd_dns_uncompress(str_rx, sizeof(str_rx), query, query_len - 3,
+                                 0))
+               return 0; /* Failed to uncompress query */
+       if (p2p_sd_dns_uncompress(str_srv, sizeof(str_srv),
+                                 wpabuf_head(bsrv->query),
+                                 wpabuf_len(bsrv->query) - 3, 0))
+               return 0; /* Failed to uncompress service */
+
+       return os_strcmp(str_rx, str_srv) == 0;
+}
+
+
 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;
+       int matches = 0;
 
        wpa_hexdump_ascii(MSG_DEBUG, "P2P: SD Request for Bonjour",
                          query, query_len);
@@ -1345,39 +1506,52 @@ static void wpas_sd_req_bonjour(struct wpa_supplicant *wpa_s,
                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);
+       dl_list_for_each(bsrv, &wpa_s->global->p2p_srv_bonjour,
+                        struct p2p_srv_bonjour, list) {
+               if (!match_bonjour_query(bsrv, query, query_len))
+                       continue;
+
+               if (wpabuf_tailroom(resp) <
+                   5 + query_len + wpabuf_len(bsrv->resp))
+                       return;
+
+               matches++;
+
+               /* 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));
 
-       wpabuf_set(&buf, query, query_len);
-       bsrv = wpas_p2p_service_get_bonjour(wpa_s, &buf);
-       if (bsrv == NULL) {
+               /* Response Data */
+               wpabuf_put_data(resp, query, query_len); /* Key */
+               wpabuf_put_buf(resp, bsrv->resp); /* Value */
+
+               WPA_PUT_LE16(len_pos, (u8 *) wpabuf_put(resp, 0) - len_pos - 2);
+       }
+
+       if (matches == 0) {
                wpa_printf(MSG_DEBUG, "P2P: Requested Bonjour service not "
                           "available");
+               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);
 
                /* Status Code */
                wpabuf_put_u8(resp, P2P_SD_REQUESTED_INFO_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);
 }
 
 
@@ -1927,14 +2101,6 @@ int wpas_p2p_service_add_bonjour(struct wpa_supplicant *wpa_s,
 {
        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;
@@ -2317,6 +2483,18 @@ static void wpas_invitation_result(void *ctx, int status, const u8 *bssid)
                return;
        }
 
+       /*
+        * The peer could have missed our ctrl::ack frame for Invitation
+        * Response and continue retransmitting the frame. To reduce the
+        * likelihood of the peer not getting successful TX status for the
+        * Invitation Response frame, wait a short time here before starting
+        * the persistent group so that we will remain on the current channel to
+        * acknowledge any possible retransmission from the peer.
+        */
+       wpa_dbg(wpa_s, MSG_DEBUG, "P2P: 50 ms wait on current channel before "
+               "starting persistent group");
+       os_sleep(0, 50000);
+
        wpas_p2p_group_add_persistent(wpa_s, ssid,
                                      ssid->mode == WPAS_MODE_P2P_GO,
                                      wpa_s->p2p_persistent_go_freq,
@@ -2747,6 +2925,8 @@ int wpas_p2p_init(struct wpa_global *global, struct wpa_supplicant *wpa_s)
 
        p2p.p2p_intra_bss = wpa_s->conf->p2p_intra_bss;
 
+       p2p.max_listen = wpa_s->max_remain_on_chan;
+
        global->p2p = p2p_init(&p2p);
        if (global->p2p == NULL)
                return -1;
@@ -2846,6 +3026,8 @@ void wpas_p2p_deinit_global(struct wpa_global *global)
 
 static int wpas_p2p_create_iface(struct wpa_supplicant *wpa_s)
 {
+       if (wpa_s->conf->p2p_no_group_iface)
+               return 0; /* separate interface disabled per configuration */
        if (wpa_s->drv_flags &
            (WPA_DRIVER_FLAGS_P2P_DEDICATED_INTERFACE |
             WPA_DRIVER_FLAGS_P2P_MGMT_AND_NON_P2P))
@@ -2866,7 +3048,7 @@ static int wpas_p2p_start_go_neg(struct wpa_supplicant *wpa_s,
                                 enum p2p_wps_method wps_method,
                                 int go_intent, const u8 *own_interface_addr,
                                 unsigned int force_freq, int persistent_group,
-                                struct wpa_ssid *ssid)
+                                struct wpa_ssid *ssid, unsigned int pref_freq)
 {
        if (persistent_group && wpa_s->conf->persistent_reconnect)
                persistent_group = 2;
@@ -2888,7 +3070,7 @@ static int wpas_p2p_start_go_neg(struct wpa_supplicant *wpa_s,
                           go_intent, own_interface_addr, force_freq,
                           persistent_group, ssid ? ssid->ssid : NULL,
                           ssid ? ssid->ssid_len : 0,
-                          wpa_s->p2p_pd_before_go_neg);
+                          wpa_s->p2p_pd_before_go_neg, pref_freq);
 }
 
 
@@ -2897,7 +3079,7 @@ static int wpas_p2p_auth_go_neg(struct wpa_supplicant *wpa_s,
                                enum p2p_wps_method wps_method,
                                int go_intent, const u8 *own_interface_addr,
                                unsigned int force_freq, int persistent_group,
-                               struct wpa_ssid *ssid)
+                               struct wpa_ssid *ssid, unsigned int pref_freq)
 {
        if (persistent_group && wpa_s->conf->persistent_reconnect)
                persistent_group = 2;
@@ -2908,7 +3090,7 @@ static int wpas_p2p_auth_go_neg(struct wpa_supplicant *wpa_s,
        return p2p_authorize(wpa_s->global->p2p, peer_addr, wps_method,
                             go_intent, own_interface_addr, force_freq,
                             persistent_group, ssid ? ssid->ssid : NULL,
-                            ssid ? ssid->ssid_len : 0);
+                            ssid ? ssid->ssid_len : 0, pref_freq);
 }
 
 
@@ -2940,6 +3122,7 @@ static void wpas_p2p_pd_before_join_timeout(void *eloop_ctx, void *timeout_ctx)
        struct wpa_supplicant *wpa_s = eloop_ctx;
        if (!wpa_s->pending_pd_before_join)
                return;
+       wpa_s->pending_pd_before_join = 0;
        /*
         * Provision Discovery Response may have been lost - try to connect
         * anyway since we do not need any information from this PD.
@@ -3386,7 +3569,7 @@ int wpas_p2p_connect(struct wpa_supplicant *wpa_s, const u8 *peer_addr,
                     int go_intent, int freq, int persistent_id, int pd,
                     int ht40)
 {
-       int force_freq = 0, oper_freq = 0;
+       int force_freq = 0, pref_freq = 0, oper_freq = 0;
        u8 bssid[ETH_ALEN];
        int ret = 0;
        enum wpa_driver_if_type iftype;
@@ -3499,6 +3682,13 @@ int wpas_p2p_connect(struct wpa_supplicant *wpa_s, const u8 *peer_addr,
                           "(%u MHz) not available for P2P - try to use "
                           "another channel", oper_freq);
                force_freq = 0;
+       } else if (oper_freq > 0 &&
+                  (wpa_s->drv_flags &
+                   WPA_DRIVER_FLAGS_MULTI_CHANNEL_CONCURRENT)) {
+               wpa_printf(MSG_DEBUG, "P2P: Trying to prefer the channel we "
+                          "are already using (%u MHz) on another interface",
+                          oper_freq);
+               pref_freq = oper_freq;
        } else if (oper_freq > 0) {
                wpa_printf(MSG_DEBUG, "P2P: Trying to force us to use the "
                           "channel we are already using (%u MHz) on another "
@@ -3526,15 +3716,15 @@ int wpas_p2p_connect(struct wpa_supplicant *wpa_s, const u8 *peer_addr,
        if (auth) {
                if (wpas_p2p_auth_go_neg(wpa_s, peer_addr, wps_method,
                                         go_intent, if_addr,
-                                        force_freq, persistent_group, ssid) <
-                   0)
+                                        force_freq, persistent_group, ssid,
+                                        pref_freq) < 0)
                        return -1;
                return ret;
        }
 
        if (wpas_p2p_start_go_neg(wpa_s, peer_addr, wps_method,
                                  go_intent, if_addr, force_freq,
-                                 persistent_group, ssid) < 0) {
+                                 persistent_group, ssid, pref_freq) < 0) {
                if (wpa_s->create_p2p_iface)
                        wpas_p2p_remove_pending_group_interface(wpa_s);
                return -1;
@@ -3931,6 +4121,9 @@ int wpas_p2p_group_add_persistent(struct wpa_supplicant *wpa_s,
                return -1;
 
        params.role_go = 1;
+       params.psk_set = ssid->psk_set;
+       if (params.psk_set)
+               os_memcpy(params.psk, ssid->psk, sizeof(params.psk));
        if (ssid->passphrase == NULL ||
            os_strlen(ssid->passphrase) >= sizeof(params.passphrase)) {
                wpa_printf(MSG_DEBUG, "P2P: Invalid passphrase in persistent "
@@ -4573,8 +4766,15 @@ int wpas_p2p_ext_listen(struct wpa_supplicant *wpa_s, unsigned int period,
 
 static int wpas_p2p_is_client(struct wpa_supplicant *wpa_s)
 {
-       return wpa_s->current_ssid != NULL &&
-               wpa_s->current_ssid->p2p_group &&
+       if (wpa_s->current_ssid == NULL) {
+               /*
+                * current_ssid can be clearead when P2P client interface gets
+                * disconnected, so assume this interface was used as P2P
+                * client.
+                */
+               return 1;
+       }
+       return wpa_s->current_ssid->p2p_group &&
                wpa_s->current_ssid->mode == WPAS_MODE_INFRA;
 }
 
@@ -5044,6 +5244,10 @@ int wpas_p2p_cancel(struct wpa_supplicant *wpa_s)
                        found = 1;
                        eloop_cancel_timeout(wpas_p2p_group_formation_timeout,
                                             wpa_s->parent, NULL);
+                       if (wpa_s->p2p_in_provisioning) {
+                               wpas_group_formation_completed(wpa_s, 0);
+                               break;
+                       }
                        wpas_p2p_group_delete(wpa_s,
                                              P2P_GROUP_REMOVAL_REQUESTED);
                        break;