From c267753ba2cc006907c57cf11b06d658f783682f Mon Sep 17 00:00:00 2001 From: Jouni Malinen Date: Sat, 27 Sep 2014 19:12:41 +0300 Subject: [PATCH] Add support for using random local MAC address 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 --- wpa_supplicant/config.c | 6 +++ wpa_supplicant/config.h | 26 +++++++++++ wpa_supplicant/config_file.c | 11 +++++ wpa_supplicant/config_ssid.h | 12 +++++ wpa_supplicant/ctrl_iface.c | 5 ++ wpa_supplicant/gas_query.c | 9 ++++ wpa_supplicant/scan.c | 7 +++ wpa_supplicant/wpa_supplicant.c | 74 ++++++++++++++++++++++++++++++ wpa_supplicant/wpa_supplicant.conf | 22 +++++++++ wpa_supplicant/wpa_supplicant_i.h | 6 +++ 10 files changed, 178 insertions(+) diff --git a/wpa_supplicant/config.c b/wpa_supplicant/config.c index 8cd4a2f06..f3a491710 100644 --- a/wpa_supplicant/config.c +++ b/wpa_supplicant/config.c @@ -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 diff --git a/wpa_supplicant/config.h b/wpa_supplicant/config.h index 52add9da3..75257c552 100644 --- a/wpa_supplicant/config.h +++ b/wpa_supplicant/config.h @@ -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; }; diff --git a/wpa_supplicant/config_file.c b/wpa_supplicant/config_file.c index 73ad57a50..5c8f04509 100644 --- a/wpa_supplicant/config_file.c +++ b/wpa_supplicant/config_file.c @@ -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 */ diff --git a/wpa_supplicant/config_ssid.h b/wpa_supplicant/config_ssid.h index 26b91bd18..b5dbf6ee3 100644 --- a/wpa_supplicant/config_ssid.h +++ b/wpa_supplicant/config_ssid.h @@ -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 */ diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c index c2b75f31c..510b802ca 100644 --- a/wpa_supplicant/ctrl_iface.c +++ b/wpa_supplicant/ctrl_iface.c @@ -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; diff --git a/wpa_supplicant/gas_query.c b/wpa_supplicant/gas_query.c index 39862681c..3a89674fa 100644 --- a/wpa_supplicant/gas_query.c +++ b/wpa_supplicant/gas_query.c @@ -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) { diff --git a/wpa_supplicant/scan.c b/wpa_supplicant/scan.c index ec808772c..debceb913 100644 --- a/wpa_supplicant/scan.c +++ b/wpa_supplicant/scan.c @@ -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) diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c index 9e3fe8462..92af1124f 100644 --- a/wpa_supplicant/wpa_supplicant.c +++ b/wpa_supplicant/wpa_supplicant.c @@ -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; } diff --git a/wpa_supplicant/wpa_supplicant.conf b/wpa_supplicant/wpa_supplicant.conf index 2a0dc204f..f2eaaa891 100644 --- a/wpa_supplicant/wpa_supplicant.conf +++ b/wpa_supplicant/wpa_supplicant.conf @@ -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 diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h index be779d884..2b6ef79ad 100644 --- a/wpa_supplicant/wpa_supplicant_i.h +++ b/wpa_supplicant/wpa_supplicant_i.h @@ -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