Add support for using random local MAC address
authorJouni Malinen <j@w1.fi>
Sat, 27 Sep 2014 16:12:41 +0000 (19:12 +0300)
committerJouni Malinen <j@w1.fi>
Sat, 27 Sep 2014 17:07:19 +0000 (20:07 +0300)
This adds experimental support for wpa_supplicant to assign random local
MAC addresses for both pre-association cases (scan, GAS/ANQP) and for
connections. MAC address policy for each part can be controlled
separately and the connection part can be set per network block.

This requires support from the driver to allow local MAC address to be
changed if random address policy is enabled. It should also be noted
that number of drivers would not support concurrent operations (e.g.,
P2P and station association) with random addresses in use for one or
both.

This functionality can be controlled with the global configuration
parameters mac_addr and preassoc_mac_addr which set the default MAC
address policies for connections and pre-association operations (scan
and GAS/ANQP while not connected). The global rand_addr_lifetime
parameter can be used to set the lifetime of a random MAC address in
seconds (default: 60 seconds). This is used to avoid unnecessarily
frequent MAC address changes since those are likely to result in driver
clearing most of its state. It should be noted that the random MAC
address does not expire during an ESS connection, i.e., this lifetime is
only for the case where the device is disconnected.

The mac_addr parameter can also be set in the network blocks to define
different behavior per network. For example, the global mac_addr=1 and
preassoc_mac_addr=1 settings and mac_addr=0 in a home network profile
would result in behavior where all scanning is performed using a random
MAC address while connections to new networks (e.g.,
Interworking/Hotspot 2.0) would use random address and connections to
the home network would use the permanent MAC address.

Signed-off-by: Jouni Malinen <j@w1.fi>
wpa_supplicant/config.c
wpa_supplicant/config.h
wpa_supplicant/config_file.c
wpa_supplicant/config_ssid.h
wpa_supplicant/ctrl_iface.c
wpa_supplicant/gas_query.c
wpa_supplicant/scan.c
wpa_supplicant/wpa_supplicant.c
wpa_supplicant/wpa_supplicant.conf
wpa_supplicant/wpa_supplicant_i.h

index 8cd4a2f..f3a4917 100644 (file)
@@ -1754,6 +1754,7 @@ static const struct parse_data ssid_fields[] = {
 #ifdef CONFIG_HS20
        { INT(update_identifier) },
 #endif /* CONFIG_HS20 */
+       { INT_RANGE(mac_addr, 0, 1) },
 };
 
 #undef OFFSET
@@ -2211,6 +2212,7 @@ void wpa_config_set_network_defaults(struct wpa_ssid *ssid)
 #ifdef CONFIG_IEEE80211W
        ssid->ieee80211w = MGMT_FRAME_PROTECTION_DEFAULT;
 #endif /* CONFIG_IEEE80211W */
+       ssid->mac_addr = -1;
 }
 
 
@@ -3287,6 +3289,7 @@ struct wpa_config * wpa_config_alloc_empty(const char *ctrl_interface,
        config->wmm_ac_params[2] = ac_vi;
        config->wmm_ac_params[3] = ac_vo;
        config->p2p_search_delay = DEFAULT_P2P_SEARCH_DELAY;
+       config->rand_addr_lifetime = DEFAULT_RAND_ADDR_LIFETIME;
 
        if (ctrl_interface)
                config->ctrl_interface = os_strdup(ctrl_interface);
@@ -3909,6 +3912,9 @@ static const struct global_parse_data global_fields[] = {
        { STR(osu_dir), 0 },
        { STR(wowlan_triggers), 0 },
        { INT(p2p_search_delay), 0},
+       { INT(mac_addr), 0 },
+       { INT(rand_addr_lifetime), 0 },
+       { INT(preassoc_mac_addr), 0 },
 };
 
 #undef FUNC
index 52add9d..75257c5 100644 (file)
@@ -27,6 +27,7 @@
 #define DEFAULT_ACCESS_NETWORK_TYPE 15
 #define DEFAULT_SCAN_CUR_FREQ 0
 #define DEFAULT_P2P_SEARCH_DELAY 500
+#define DEFAULT_RAND_ADDR_LIFETIME 60
 
 #include "config_ssid.h"
 #include "wps/wps.h"
@@ -1051,6 +1052,31 @@ struct wpa_config {
         * resources.
         */
        unsigned int p2p_search_delay;
+
+       /**
+        * mac_addr - MAC address policy default
+        *
+        * 0 = use permanent MAC address
+        * 1 = use random MAC address for each ESS connection
+        *
+        * By default, permanent MAC address is used unless policy is changed by
+        * the per-network mac_addr parameter. Global mac_addr=1 can be used to
+        * change this default behavior.
+        */
+       int mac_addr;
+
+       /**
+        * rand_addr_lifetime - Lifetime of random MAC address in seconds
+        */
+       unsigned int rand_addr_lifetime;
+
+       /**
+        * preassoc_mac_addr - Pre-association MAC address policy
+        *
+        * 0 = use permanent MAC address
+        * 1 = use random MAC address
+        */
+       int preassoc_mac_addr;
 };
 
 
index 73ad57a..5c8f045 100644 (file)
@@ -742,6 +742,7 @@ static void wpa_config_write_network(FILE *f, struct wpa_ssid *ssid)
 #ifdef CONFIG_HS20
        INT(update_identifier);
 #endif /* CONFIG_HS20 */
+       write_int(f, "mac_addr", ssid->mac_addr, -1);
 
 #undef STR
 #undef INT
@@ -1179,6 +1180,16 @@ static void wpa_config_write_global(FILE *f, struct wpa_config *config)
        if (config->p2p_search_delay != DEFAULT_P2P_SEARCH_DELAY)
                fprintf(f, "p2p_search_delay=%u\n",
                        config->p2p_search_delay);
+
+       if (config->mac_addr)
+               fprintf(f, "mac_addr=%d\n", config->mac_addr);
+
+       if (config->rand_addr_lifetime != DEFAULT_RAND_ADDR_LIFETIME)
+               fprintf(f, "rand_addr_lifetime=%u\n",
+                       config->rand_addr_lifetime);
+
+       if (config->preassoc_mac_addr)
+               fprintf(f, "preassoc_mac_addr=%d\n", config->preassoc_mac_addr);
 }
 
 #endif /* CONFIG_NO_CONFIG_WRITE */
index 26b91bd..b5dbf6e 100644 (file)
@@ -653,6 +653,18 @@ struct wpa_ssid {
 #endif /* CONFIG_HS20 */
 
        unsigned int wps_run;
+
+       /**
+        * mac_addr - MAC address policy
+        *
+        * 0 = use permanent MAC address
+        * 1 = use random MAC address for each ESS connection
+        *
+        * Internally, special value -1 is used to indicate that the parameter
+        * was not specified in the configuration (i.e., default behavior is
+        * followed).
+        */
+       int mac_addr;
 };
 
 #endif /* CONFIG_SSID_H */
index c2b75f3..510b802 100644 (file)
@@ -2480,6 +2480,8 @@ static int wpa_supplicant_ctrl_iface_remove_network(
                        struct wpa_ssid *remove_ssid = ssid;
                        id = ssid->id;
                        ssid = ssid->next;
+                       if (wpa_s->last_ssid == remove_ssid)
+                               wpa_s->last_ssid = NULL;
                        wpas_notify_network_removed(wpa_s, remove_ssid);
                        wpa_config_remove_network(wpa_s->conf, id);
                }
@@ -2498,6 +2500,9 @@ static int wpa_supplicant_ctrl_iface_remove_network(
                return -1;
        }
 
+       if (wpa_s->last_ssid == ssid)
+               wpa_s->last_ssid = NULL;
+
        if (ssid == wpa_s->current_ssid || wpa_s->current_ssid == NULL) {
 #ifdef CONFIG_SME
                wpa_s->sme.prev_bssid_set = 0;
index 3986268..3a89674 100644 (file)
@@ -597,6 +597,7 @@ static void gas_query_start_cb(struct wpa_radio_work *work, int deinit)
 {
        struct gas_query_pending *query = work->ctx;
        struct gas_query *gas = query->gas;
+       struct wpa_supplicant *wpa_s = gas->wpa_s;
 
        if (deinit) {
                if (work->started) {
@@ -609,6 +610,14 @@ static void gas_query_start_cb(struct wpa_radio_work *work, int deinit)
                return;
        }
 
+       if (wpas_update_random_addr_disassoc(wpa_s) < 0) {
+               wpa_msg(wpa_s, MSG_INFO,
+                       "Failed to assign random MAC address for GAS");
+               gas_query_free(query, 1);
+               radio_work_done(work);
+               return;
+       }
+
        gas->work = work;
 
        if (gas_query_tx(gas, query, query->req) < 0) {
index ec80877..debceb9 100644 (file)
@@ -158,6 +158,13 @@ static void wpas_trigger_scan_cb(struct wpa_radio_work *work, int deinit)
                return;
        }
 
+       if (wpas_update_random_addr_disassoc(wpa_s) < 0) {
+               wpa_msg(wpa_s, MSG_INFO,
+                       "Failed to assign random MAC address for a scan");
+               radio_work_done(work);
+               return;
+       }
+
        wpa_supplicant_notify_scanning(wpa_s, 1);
 
        if (wpa_s->clear_driver_scan_cache)
index 9e3fe84..92af112 100644 (file)
@@ -1380,6 +1380,55 @@ void wpas_connect_work_done(struct wpa_supplicant *wpa_s)
 }
 
 
+int wpas_update_random_addr(struct wpa_supplicant *wpa_s)
+{
+       struct os_reltime now;
+       u8 addr[ETH_ALEN];
+
+       os_get_reltime(&now);
+       if (wpa_s->last_mac_addr_change.sec != 0 &&
+           !os_reltime_expired(&now, &wpa_s->last_mac_addr_change,
+                               wpa_s->conf->rand_addr_lifetime)) {
+               wpa_msg(wpa_s, MSG_DEBUG,
+                       "Previously selected random MAC address has not yet expired");
+               return 0;
+       }
+
+       if (random_mac_addr(addr) < 0)
+               return -1;
+
+       if (wpa_drv_set_mac_addr(wpa_s, addr) < 0) {
+               wpa_msg(wpa_s, MSG_INFO,
+                       "Failed to set random MAC address");
+               return -1;
+       }
+
+       os_get_reltime(&wpa_s->last_mac_addr_change);
+       wpa_s->mac_addr_changed = 1;
+
+       if (wpa_supplicant_update_mac_addr(wpa_s) < 0) {
+               wpa_msg(wpa_s, MSG_INFO,
+                       "Could not update MAC address information");
+               return -1;
+       }
+
+       wpa_msg(wpa_s, MSG_DEBUG, "Using random MAC address " MACSTR,
+               MAC2STR(addr));
+
+       return 0;
+}
+
+
+int wpas_update_random_addr_disassoc(struct wpa_supplicant *wpa_s)
+{
+       if (wpa_s->wpa_state >= WPA_AUTHENTICATING ||
+           !wpa_s->conf->preassoc_mac_addr)
+               return 0;
+
+       return wpas_update_random_addr(wpa_s);
+}
+
+
 static void wpas_start_assoc_cb(struct wpa_radio_work *work, int deinit);
 
 /**
@@ -1395,6 +1444,29 @@ void wpa_supplicant_associate(struct wpa_supplicant *wpa_s,
 {
        struct wpa_connect_work *cwork;
 
+       if (wpa_s->last_ssid == ssid) {
+               wpa_dbg(wpa_s, MSG_DEBUG, "Re-association to the same ESS");
+       } else if (ssid->mac_addr == 1 ||
+                  (ssid->mac_addr == -1 && wpa_s->conf->mac_addr == 1)) {
+               if (wpas_update_random_addr(wpa_s) < 0)
+                       return;
+               wpa_sm_pmksa_cache_flush(wpa_s->wpa, ssid);
+       } else if (wpa_s->mac_addr_changed) {
+               if (wpa_drv_set_mac_addr(wpa_s, NULL) < 0) {
+                       wpa_msg(wpa_s, MSG_INFO,
+                               "Could not restore permanent MAC address");
+                       return;
+               }
+               wpa_s->mac_addr_changed = 0;
+               if (wpa_supplicant_update_mac_addr(wpa_s) < 0) {
+                       wpa_msg(wpa_s, MSG_INFO,
+                               "Could not update MAC address information");
+                       return;
+               }
+               wpa_msg(wpa_s, MSG_DEBUG, "Using permanent MAC address");
+       }
+       wpa_s->last_ssid = ssid;
+
 #ifdef CONFIG_IBSS_RSN
        ibss_rsn_deinit(wpa_s->ibss_rsn);
        wpa_s->ibss_rsn = NULL;
@@ -2662,6 +2734,8 @@ int wpa_supplicant_update_mac_addr(struct wpa_supplicant *wpa_s)
                return -1;
        }
 
+       wpa_sm_set_own_addr(wpa_s->wpa, wpa_s->own_addr);
+
        return 0;
 }
 
index 2a0dc20..f2eaaa8 100644 (file)
@@ -332,6 +332,23 @@ fast_reauth=1
 # 1:  Scan current operating frequency if another VIF on the same radio
 #     is already associated.
 
+# MAC address policy default
+# 0 = use permanent MAC address
+# 1 = use random MAC address for each ESS connection
+#
+# By default, permanent MAC address is used unless policy is changed by
+# the per-network mac_addr parameter. Global mac_addr=1 can be used to
+# change this default behavior.
+#mac_addr=0
+
+# Lifetime of random MAC address in seconds (default: 60)
+#rand_addr_lifetime=60
+
+# MAC address policy for pre-association operations (scanning, ANQP)
+# 0 = use permanent MAC address
+# 1 = use random MAC address
+#preassoc_mac_addr=0
+
 # Interworking (IEEE 802.11u)
 
 # Enable Interworking
@@ -962,6 +979,11 @@ fast_reauth=1
 # Beacon interval (default: 100 TU)
 #beacon_int=100
 
+# MAC address policy
+# 0 = use permanent MAC address
+# 1 = use random MAC address for each ESS connection
+#mac_addr=0
+
 # disable_ht: Whether HT (802.11n) should be disabled.
 # 0 = HT enabled (if AP supports it)
 # 1 = HT disabled
index be779d8..2b6ef79 100644 (file)
@@ -421,6 +421,7 @@ struct wpa_supplicant {
        int disconnected; /* all connections disabled; i.e., do no reassociate
                           * before this has been cleared */
        struct wpa_ssid *current_ssid;
+       struct wpa_ssid *last_ssid;
        struct wpa_bss *current_bss;
        int ap_ies_from_associnfo;
        unsigned int assoc_freq;
@@ -609,6 +610,9 @@ struct wpa_supplicant {
        unsigned int last_eapol_matches_bssid:1;
        unsigned int eap_expected_failure:1;
        unsigned int reattach:1; /* reassociation to the same BSS requested */
+       unsigned int mac_addr_changed:1;
+
+       struct os_reltime last_mac_addr_change;
 
        struct ibss_rsn *ibss_rsn;
 
@@ -958,6 +962,8 @@ int disallowed_ssid(struct wpa_supplicant *wpa_s, const u8 *ssid,
                    size_t ssid_len);
 void wpas_request_connection(struct wpa_supplicant *wpa_s);
 int wpas_build_ext_capab(struct wpa_supplicant *wpa_s, u8 *buf, size_t buflen);
+int wpas_update_random_addr(struct wpa_supplicant *wpa_s);
+int wpas_update_random_addr_disassoc(struct wpa_supplicant *wpa_s);
 
 /**
  * wpa_supplicant_ctrl_iface_ctrl_rsp_handle - Handle a control response