Commit 96cabbcb authored by Thomas Haller's avatar Thomas Haller

all: make MAC address randomization algorithm configurable

For the per-connection settings "ethernet.cloned-mac-address"
and "wifi.cloned-mac-address", and for the per-device setting
"wifi.scan-rand-mac-address", we may generate MAC addresses using
either the "random" or "stable" algorithm.

Add new properties "generate-mac-address-mask" that allow to configure
which bits of the MAC address will be scrambled.

By default, the "random" and "stable" algorithms scamble all bits
of the MAC address, including the OUI part and generate a locally-
administered, unicast address.

By specifying a MAC address mask, we can now configure to perserve
parts of the current MAC address of the device. For example, setting
"FF:FF:FF:00:00:00" will preserve the first 3 octects of the current
MAC address.

One can also explicitly specify a MAC address to use instead of the
current MAC address. For example, "FF:FF:FF:00:00:00 68:F7:28:00:00:00"
sets the OUI part of the MAC address to "68:F7:28" while scrambling
the last 3 octects.
Similarly, "02:00:00:00:00:00 00:00:00:00:00:00" will scamble
all bits of the MAC address, except clearing the second-least
significant bit. Thus, creating a burned-in address, globally
administered.

One can also supply a list of MAC addresses like
"FF:FF:FF:00:00:00 68:F7:28:00:00:00 00:0C:29:00:00:00 ..." in which
case a MAC address is choosen randomly.

To fully scamble the MAC address one can configure
"02:00:00:00:00:00 00:00:00:00:00:00 02:00:00:00:00:00".
which also randomly creates either a locally or globally administered
address.

With this, the following macchanger options can be implemented:

  `macchanger --random`
   This is the default if no mask is configured.
   -> ""
   while is the same as:
   -> "00:00:00:00:00:00"
   -> "02:00:00:00:00:00 02:00:00:00:00:00"

  `macchanger --random --bia`
   -> "02:00:00:00:00:00 00:00:00:00:00:00"

  `macchanger --ending`
   This option cannot be fully implemented, because macchanger
   uses the current MAC address but also implies --bia.
   -> "FF:FF:FF:00:00:00"
      This would yields the same result only if the current MAC address
      is already a burned-in address too. Otherwise, it has not the same
      effect as --ending.
   -> "FF:FF:FF:00:00:00 <MAC_ADDR>"
      Alternatively, instead of using the current MAC address,
      spell the OUI part out. But again, that is not really the
      same as macchanger does because you explictly have to name
      the OUI part to use.

  `machanger --another`
  `machanger --another_any`
  -> "FF:FF:FF:00:00:00 <MAC_ADDR> <MAC_ADDR> ..."
     "$(printf "FF:FF:FF:00:00:00 %s\n" "$(sed -n 's/^\([0-9a-fA-F][0-9a-fA-F]\) \([0-9a-fA-F][0-9a-fA-F]\) \([0-9a-fA-F][0-9a-fA-F]\) .*/\1:\2:\3:00:00:00/p' /usr/share/macchanger/wireless.list | xargs)")"
parent 6829871c
This diff is collapsed.
......@@ -283,6 +283,16 @@ void _nm_setting_vlan_get_priorities (NMSettingVlan *setting,
/***********************************************************/
struct ether_addr;
gboolean _nm_utils_generate_mac_address_mask_parse (const char *value,
struct ether_addr *out_mask,
struct ether_addr **out_ouis,
gsize *out_ouis_len,
GError **error);
/***********************************************************/
typedef enum {
NM_BOND_OPTION_TYPE_INT,
NM_BOND_OPTION_TYPE_STRING,
......
......@@ -53,6 +53,7 @@ typedef struct {
gboolean auto_negotiate;
char *device_mac_address;
char *cloned_mac_address;
char *generate_mac_address_mask;
GArray *mac_address_blacklist;
guint32 mtu;
char **s390_subchannels;
......@@ -70,6 +71,7 @@ enum {
PROP_AUTO_NEGOTIATE,
PROP_MAC_ADDRESS,
PROP_CLONED_MAC_ADDRESS,
PROP_GENERATE_MAC_ADDRESS_MASK,
PROP_MAC_ADDRESS_BLACKLIST,
PROP_MTU,
PROP_S390_SUBCHANNELS,
......@@ -188,6 +190,22 @@ nm_setting_wired_get_cloned_mac_address (NMSettingWired *setting)
return NM_SETTING_WIRED_GET_PRIVATE (setting)->cloned_mac_address;
}
/**
* nm_setting_wired_get_generate_mac_address_mask:
* @setting: the #NMSettingWired
*
* Returns: the #NMSettingWired:generate-mac-address-mask property of the setting
*
* Since: 1.4
**/
const char *
nm_setting_wired_get_generate_mac_address_mask (NMSettingWired *setting)
{
g_return_val_if_fail (NM_IS_SETTING_WIRED (setting), NULL);
return NM_SETTING_WIRED_GET_PRIVATE (setting)->generate_mac_address_mask;
}
/**
* nm_setting_wired_get_mac_address_blacklist:
* @setting: the #NMSettingWired
......@@ -612,6 +630,7 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
GHashTableIter iter;
const char *key, *value;
int i;
GError *local = NULL;
if (priv->port && !g_strv_contains (valid_ports, priv->port)) {
g_set_error (error,
......@@ -704,6 +723,20 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
return FALSE;
}
/* generate-mac-address-mask only makes sense with cloned-mac-address "random" or
* "stable". Still, let's not be so strict about that and accept the value
* even if it is unused. */
if (!_nm_utils_generate_mac_address_mask_parse (priv->generate_mac_address_mask,
NULL, NULL, NULL, &local)) {
g_set_error_literal (error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_INVALID_PROPERTY,
local->message);
g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRED_SETTING_NAME, NM_SETTING_WIRED_GENERATE_MAC_ADDRESS_MASK);
g_error_free (local);
return FALSE;
}
if ( NM_FLAGS_ANY (priv->wol, NM_SETTING_WIRED_WAKE_ON_LAN_EXCLUSIVE_FLAGS)
&& !nm_utils_is_power_of_two (priv->wol)) {
g_set_error_literal (error,
......@@ -785,6 +818,7 @@ finalize (GObject *object)
g_free (priv->device_mac_address);
g_free (priv->cloned_mac_address);
g_free (priv->generate_mac_address_mask);
g_array_unref (priv->mac_address_blacklist);
if (priv->s390_subchannels)
......@@ -829,6 +863,10 @@ set_property (GObject *object, guint prop_id,
priv->cloned_mac_address = _nm_utils_hwaddr_canonical_or_invalid (g_value_get_string (value),
ETH_ALEN);
break;
case PROP_GENERATE_MAC_ADDRESS_MASK:
g_free (priv->generate_mac_address_mask);
priv->generate_mac_address_mask = g_value_dup_string (value);
break;
case PROP_MAC_ADDRESS_BLACKLIST:
blacklist = g_value_get_boxed (value);
g_array_set_size (priv->mac_address_blacklist, 0);
......@@ -894,6 +932,9 @@ get_property (GObject *object, guint prop_id,
case PROP_CLONED_MAC_ADDRESS:
g_value_set_string (value, nm_setting_wired_get_cloned_mac_address (setting));
break;
case PROP_GENERATE_MAC_ADDRESS_MASK:
g_value_set_string (value, nm_setting_wired_get_generate_mac_address_mask (setting));
break;
case PROP_MAC_ADDRESS_BLACKLIST:
g_value_set_boxed (value, (char **) priv->mac_address_blacklist->data);
break;
......@@ -1121,6 +1162,53 @@ nm_setting_wired_class_init (NMSettingWiredClass *setting_wired_class)
_nm_utils_hwaddr_cloned_data_synth,
_nm_utils_hwaddr_cloned_data_set);
/**
* NMSettingWired:generate-mac-address-mask:
*
* With #NMSettingWired:cloned-mac-address setting "random" or "stable",
* by default all bits of the MAC address are scrambled and a locally-administered,
* unicast MAC address is created. This property allows to specify that certain bits
* are fixed. Note that the least significant bit of the first MAC address will
* always be unset to create a unicast MAC address.
*
* If the property is %NULL, it is eligible to be overwritten by a default
* connection setting. If the value is still %NULL or an empty string, the
* default is to create a locally-administered, unicast MAC address.
*
* If the value contains one MAC address, this address is used as mask. The set
* bits of the mask are to be filled with the current MAC address of the device,
* while the unset bits are subject to randomization.
* Setting "FE:FF:FF:00:00:00" means to preserve the OUI of the current MAC address
* and only randomize the lower 3 bytes using the "random" or "stable" algorithm.
*
* If the value contains one additional MAC address after the mask,
* this address is used instead of the current MAC address to fill the bits
* that shall not be randomized. For example, a value of
* "FE:FF:FF:00:00:00 68:F7:28:00:00:00" will set the OUI of the MAC address
* to 68:F7:28, while the lower bits are randomized. A value of
* "02:00:00:00:00:00 00:00:00:00:00:00" will create a fully scrambled
* globally-administered, burned-in MAC address.
*
* If the value contains more then one additional MAC addresses, one of
* them is chosen randomly. For example, "02:00:00:00:00:00 00:00:00:00:00:00 02:00:00:00:00:00"
* will create a fully scrambled MAC address, randomly locally or globally
* administered.
**/
/* ---ifcfg-rh---
* property: generate-mac-address-mask
* variable: GENERATE_MAC_ADDRESS_MASK
* description: the MAC address mask for generating randomized and stable
* cloned-mac-address.
* ---end---
*/
g_object_class_install_property
(object_class, PROP_GENERATE_MAC_ADDRESS_MASK,
g_param_spec_string (NM_SETTING_WIRED_GENERATE_MAC_ADDRESS_MASK, "", "",
NULL,
G_PARAM_READWRITE |
NM_SETTING_PARAM_FUZZY_IGNORE |
G_PARAM_STATIC_STRINGS));
/**
* NMSettingWired:mac-address-blacklist:
*
......
......@@ -85,6 +85,7 @@ typedef enum { /*< flags >*/
#define NM_SETTING_WIRED_AUTO_NEGOTIATE "auto-negotiate"
#define NM_SETTING_WIRED_MAC_ADDRESS "mac-address"
#define NM_SETTING_WIRED_CLONED_MAC_ADDRESS "cloned-mac-address"
#define NM_SETTING_WIRED_GENERATE_MAC_ADDRESS_MASK "generate-mac-address-mask"
#define NM_SETTING_WIRED_MAC_ADDRESS_BLACKLIST "mac-address-blacklist"
#define NM_SETTING_WIRED_MTU "mtu"
#define NM_SETTING_WIRED_S390_SUBCHANNELS "s390-subchannels"
......@@ -117,6 +118,9 @@ gboolean nm_setting_wired_get_auto_negotiate (NMSettingWired *setting
const char * nm_setting_wired_get_mac_address (NMSettingWired *setting);
const char * nm_setting_wired_get_cloned_mac_address (NMSettingWired *setting);
NM_AVAILABLE_IN_1_4
const char * nm_setting_wired_get_generate_mac_address_mask (NMSettingWired *setting);
const char * const *nm_setting_wired_get_mac_address_blacklist (NMSettingWired *setting);
guint32 nm_setting_wired_get_num_mac_blacklist_items (NMSettingWired *setting);
const char * nm_setting_wired_get_mac_blacklist_item (NMSettingWired *setting,
......
......@@ -56,6 +56,7 @@ typedef struct {
guint32 tx_power;
char *device_mac_address;
char *cloned_mac_address;
char *generate_mac_address_mask;
GArray *mac_address_blacklist;
guint32 mtu;
GSList *seen_bssids;
......@@ -75,6 +76,7 @@ enum {
PROP_TX_POWER,
PROP_MAC_ADDRESS,
PROP_CLONED_MAC_ADDRESS,
PROP_GENERATE_MAC_ADDRESS_MASK,
PROP_MAC_ADDRESS_BLACKLIST,
PROP_MTU,
PROP_SEEN_BSSIDS,
......@@ -421,6 +423,22 @@ nm_setting_wireless_get_cloned_mac_address (NMSettingWireless *setting)
return NM_SETTING_WIRELESS_GET_PRIVATE (setting)->cloned_mac_address;
}
/**
* nm_setting_wireless_get_generate_mac_address_mask:
* @setting: the #NMSettingWireless
*
* Returns: the #NMSettingWireless:generate-mac-address-mask property of the setting
*
* Since: 1.4
**/
const char *
nm_setting_wireless_get_generate_mac_address_mask (NMSettingWireless *setting)
{
g_return_val_if_fail (NM_IS_SETTING_WIRELESS (setting), NULL);
return NM_SETTING_WIRELESS_GET_PRIVATE (setting)->generate_mac_address_mask;
}
/**
* nm_setting_wireless_get_mac_address_blacklist:
* @setting: the #NMSettingWireless
......@@ -723,6 +741,7 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
GSList *iter;
int i;
gsize length;
GError *local = NULL;
if (!priv->ssid) {
g_set_error_literal (error,
......@@ -814,6 +833,20 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
return FALSE;
}
/* generate-mac-address-mask only makes sense with cloned-mac-address "random" or
* "stable". Still, let's not be so strict about that and accept the value
* even if it is unused. */
if (!_nm_utils_generate_mac_address_mask_parse (priv->generate_mac_address_mask,
NULL, NULL, NULL, &local)) {
g_set_error_literal (error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_INVALID_PROPERTY,
local->message);
g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SETTING_NAME, NM_SETTING_WIRELESS_GENERATE_MAC_ADDRESS_MASK);
g_error_free (local);
return FALSE;
}
for (i = 0; i < priv->mac_address_blacklist->len; i++) {
const char *mac = g_array_index (priv->mac_address_blacklist, const char *, i);
......@@ -935,6 +968,7 @@ finalize (GObject *object)
g_free (priv->bssid);
g_free (priv->device_mac_address);
g_free (priv->cloned_mac_address);
g_free (priv->generate_mac_address_mask);
g_array_unref (priv->mac_address_blacklist);
g_slist_free_full (priv->seen_bssids, g_free);
......@@ -987,6 +1021,10 @@ set_property (GObject *object, guint prop_id,
priv->cloned_mac_address = _nm_utils_hwaddr_canonical_or_invalid (g_value_get_string (value),
ETH_ALEN);
break;
case PROP_GENERATE_MAC_ADDRESS_MASK:
g_free (priv->generate_mac_address_mask);
priv->generate_mac_address_mask = g_value_dup_string (value);
break;
case PROP_MAC_ADDRESS_BLACKLIST:
blacklist = g_value_get_boxed (value);
g_array_set_size (priv->mac_address_blacklist, 0);
......@@ -1054,6 +1092,9 @@ get_property (GObject *object, guint prop_id,
case PROP_CLONED_MAC_ADDRESS:
g_value_set_string (value, nm_setting_wireless_get_cloned_mac_address (setting));
break;
case PROP_GENERATE_MAC_ADDRESS_MASK:
g_value_set_string (value, nm_setting_wireless_get_generate_mac_address_mask (setting));
break;
case PROP_MAC_ADDRESS_BLACKLIST:
g_value_set_boxed (value, (char **) priv->mac_address_blacklist->data);
break;
......@@ -1362,6 +1403,53 @@ nm_setting_wireless_class_init (NMSettingWirelessClass *setting_wireless_class)
_nm_utils_hwaddr_cloned_data_synth,
_nm_utils_hwaddr_cloned_data_set);
/**
* NMSettingWireless:generate-mac-address-mask:
*
* With #NMSettingWireless:cloned-mac-address setting "random" or "stable",
* by default all bits of the MAC address are scrambled and a locally-administered,
* unicast MAC address is created. This property allows to specify that certain bits
* are fixed. Note that the least significant bit of the first MAC address will
* always be unset to create a unicast MAC address.
*
* If the property is %NULL, it is eligible to be overwritten by a default
* connection setting. If the value is still %NULL or an empty string, the
* default is to create a locally-administered, unicast MAC address.
*
* If the value contains one MAC address, this address is used as mask. The set
* bits of the mask are to be filled with the current MAC address of the device,
* while the unset bits are subject to randomization.
* Setting "FE:FF:FF:00:00:00" means to preserve the OUI of the current MAC address
* and only randomize the lower 3 bytes using the "random" or "stable" algorithm.
*
* If the value contains one additional MAC address after the mask,
* this address is used instead of the current MAC address to fill the bits
* that shall not be randomized. For example, a value of
* "FE:FF:FF:00:00:00 68:F7:28:00:00:00" will set the OUI of the MAC address
* to 68:F7:28, while the lower bits are randomized. A value of
* "02:00:00:00:00:00 00:00:00:00:00:00" will create a fully scrambled
* globally-administered, burned-in MAC address.
*
* If the value contains more then one additional MAC addresses, one of
* them is chosen randomly. For example, "02:00:00:00:00:00 00:00:00:00:00:00 02:00:00:00:00:00"
* will create a fully scrambled MAC address, randomly locally or globally
* administered.
**/
/* ---ifcfg-rh---
* property: generate-mac-address-mask
* variable: GENERATE_MAC_ADDRESS_MASK
* description: the MAC address mask for generating randomized and stable
* cloned-mac-address.
* ---end---
*/
g_object_class_install_property
(object_class, PROP_GENERATE_MAC_ADDRESS_MASK,
g_param_spec_string (NM_SETTING_WIRELESS_GENERATE_MAC_ADDRESS_MASK, "", "",
NULL,
G_PARAM_READWRITE |
NM_SETTING_PARAM_FUZZY_IGNORE |
G_PARAM_STATIC_STRINGS));
/**
* NMSettingWireless:mac-address-blacklist:
*
......
......@@ -50,6 +50,7 @@ G_BEGIN_DECLS
#define NM_SETTING_WIRELESS_TX_POWER "tx-power"
#define NM_SETTING_WIRELESS_MAC_ADDRESS "mac-address"
#define NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS "cloned-mac-address"
#define NM_SETTING_WIRELESS_GENERATE_MAC_ADDRESS_MASK "generate-mac-address-mask"
#define NM_SETTING_WIRELESS_MAC_ADDRESS_BLACKLIST "mac-address-blacklist"
#define NM_SETTING_WIRELESS_MTU "mtu"
#define NM_SETTING_WIRELESS_SEEN_BSSIDS "seen-bssids"
......@@ -126,6 +127,9 @@ guint32 nm_setting_wireless_get_tx_power (NMSettingWireless
const char *nm_setting_wireless_get_mac_address (NMSettingWireless *setting);
const char *nm_setting_wireless_get_cloned_mac_address (NMSettingWireless *setting);
NM_AVAILABLE_IN_1_4
const char *nm_setting_wireless_get_generate_mac_address_mask (NMSettingWireless *setting);
const char * const *nm_setting_wireless_get_mac_address_blacklist (NMSettingWireless *setting);
guint32 nm_setting_wireless_get_num_mac_blacklist_items (NMSettingWireless *setting);
const char * nm_setting_wireless_get_mac_blacklist_item (NMSettingWireless *setting,
......
......@@ -3446,6 +3446,91 @@ _nm_utils_hwaddr_from_dbus (GVariant *dbus_value,
/*****************************************************************************/
static char *
_split_word (char *s)
{
/* takes @s and truncates the string on the first white-space.
* then it returns the first word afterwards (again seeking
* over leading white-space). */
for (; s[0]; s++) {
if (g_ascii_isspace (s[0])) {
s[0] = '\0';
s++;
while (g_ascii_isspace (s[0]))
s++;
return s;
}
}
return s;
}
gboolean
_nm_utils_generate_mac_address_mask_parse (const char *value,
struct ether_addr *out_mask,
struct ether_addr **out_ouis,
gsize *out_ouis_len,
GError **error)
{
gs_free char *s_free = NULL;
char *s, *s_next;
struct ether_addr mask;
gs_unref_array GArray *ouis = NULL;
g_return_val_if_fail (!error || !*error, FALSE);
if (!value || !*value) {
/* NULL and "" are valid values and both mean the default
* "q */
if (out_mask) {
memset (out_mask, 0, sizeof (*out_mask));
out_mask->ether_addr_octet[0] |= 0x02;
}
NM_SET_OUT (out_ouis, NULL);
NM_SET_OUT (out_ouis_len, 0);
return TRUE;
}
s_free = g_strdup (value);
s = s_free;
/* skip over leading whitespace */
while (g_ascii_isspace (s[0]))
s++;
/* parse the first mask */
s_next = _split_word (s);
if (!nm_utils_hwaddr_aton (s, &mask, ETH_ALEN)) {
g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
_("not a valid ethernet MAC address for mask at position %lld"),
(long long) (s - s_free));
return FALSE;
}
if (s_next[0]) {
ouis = g_array_sized_new (FALSE, FALSE, sizeof (struct ether_addr), 4);
do {
s = s_next;
s_next = _split_word (s);
g_array_set_size (ouis, ouis->len + 1);
if (!nm_utils_hwaddr_aton (s, &g_array_index (ouis, struct ether_addr, ouis->len - 1), ETH_ALEN)) {
g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
_("not a valid ethernet MAC address #%u at position %lld"),
ouis->len, (long long) (s - s_free));
return FALSE;
}
} while (s_next[0]);
}
NM_SET_OUT (out_mask, mask);
NM_SET_OUT (out_ouis_len, ouis ? ouis->len : 0);
NM_SET_OUT (out_ouis, ouis ? ((struct ether_addr *) g_array_free (g_steal_pointer (&ouis), FALSE)) : NULL);
return TRUE;
}
/*****************************************************************************/
/**
* nm_utils_bin2hexstr:
* @src: (type guint8) (array length=len): an array of bytes
......
......@@ -1941,6 +1941,7 @@ test_connection_diff_a_only (void)
{ NM_SETTING_WIRED_AUTO_NEGOTIATE, NM_SETTING_DIFF_RESULT_IN_A },
{ NM_SETTING_WIRED_MAC_ADDRESS, NM_SETTING_DIFF_RESULT_IN_A },
{ NM_SETTING_WIRED_CLONED_MAC_ADDRESS, NM_SETTING_DIFF_RESULT_IN_A },
{ NM_SETTING_WIRED_GENERATE_MAC_ADDRESS_MASK, NM_SETTING_DIFF_RESULT_IN_A },
{ NM_SETTING_WIRED_MAC_ADDRESS_BLACKLIST, NM_SETTING_DIFF_RESULT_IN_A },
{ NM_SETTING_WIRED_MTU, NM_SETTING_DIFF_RESULT_IN_A },
{ NM_SETTING_WIRED_S390_SUBCHANNELS, NM_SETTING_DIFF_RESULT_IN_A },
......
......@@ -1069,6 +1069,8 @@ global:
nm_setting_connection_get_stable_id;
nm_setting_ip6_config_get_token;
nm_setting_ip_config_get_dns_priority;
nm_setting_wired_get_generate_mac_address_mask;
nm_setting_wireless_get_generate_mac_address_mask;
nm_vpn_editor_plugin_get_plugin_info;
nm_vpn_editor_plugin_get_vt;
nm_vpn_editor_plugin_load;
......
......@@ -569,6 +569,9 @@ ipv6.ip6-privacy=0
<term><varname>ethernet.cloned-mac-address</varname></term>
<listitem><para>If left unspecified, it defaults to "permanent".</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>ethernet.generate-mac-address-mask</varname></term>
</varlistentry>
<varlistentry>
<term><varname>ethernet.wake-on-lan</varname></term>
</varlistentry>
......@@ -600,6 +603,9 @@ ipv6.ip6-privacy=0
<term><varname>wifi.cloned-mac-address</varname></term>
<listitem><para>If left unspecified, it defaults to "permanent".</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>wifi.generate-mac-address-mask</varname></term>
</varlistentry>
<varlistentry>
<term><varname>wifi.mac-address-randomization</varname></term>
<listitem><para>If left unspecified, MAC address randomization is disabled.
......@@ -751,6 +757,17 @@ unmanaged=1
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>wifi.scan-generate-mac-address-mask</varname></term>
<listitem>
<para>
Like the per-connection settings ethernet.generate-mac-address-mask
and wifi.generate-mac-address-mask, this allows to configure the
generated MAC addresses during scanning. See manual of nm-settings
for details.
</para>
</listitem>
</varlistentry>
</variablelist>
</para>
</refsect2>
......
......@@ -11589,6 +11589,34 @@ _get_cloned_mac_address_setting (NMDevice *self, NMConnection *connection, gbool
return addr;
}
static const char *
_get_generate_mac_address_mask_setting (NMDevice *self, NMConnection *connection, gboolean is_wifi, char **out_value)
{
NMSetting *setting;
const char *value = NULL;
char *a;
nm_assert (out_value && !*out_value);
setting = nm_connection_get_setting (connection,
is_wifi ? NM_TYPE_SETTING_WIRELESS : NM_TYPE_SETTING_WIRED);
if (setting) {
value = is_wifi
? nm_setting_wireless_get_generate_mac_address_mask ((NMSettingWireless *) setting)
: nm_setting_wired_get_generate_mac_address_mask ((NMSettingWired *) setting);
if (value)
return value;
}
a = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
is_wifi ? "wifi.generate-mac-address-mask" : "ethernet.generate-mac-mac-address-mask",
self);
if (!a)
return NULL;
*out_value = a;
return a;
}
gboolean
nm_device_hw_addr_is_explict (NMDevice *self)
{
......@@ -11695,6 +11723,7 @@ nm_device_hw_addr_set_cloned (NMDevice *self, NMConnection *connection, gboolean
NMDevicePrivate *priv;
gs_free char *hw_addr_tmp = NULL;
gs_free char *hw_addr_generated = NULL;
gs_free char *generate_mac_address_mask_tmp = NULL;
const char *addr, *addr_setting;
g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
......@@ -11717,7 +11746,8 @@ nm_device_hw_addr_set_cloned (NMDevice *self, NMConnection *connection, gboolean
return FALSE;
priv->hw_addr_type = HW_ADDR_TYPE_PERMANENT;
} else if (NM_IN_STRSET (addr, NM_CLONED_MAC_RANDOM)) {
hw_addr_generated = nm_utils_hw_addr_gen_random_eth ();
hw_addr_generated = nm_utils_hw_addr_gen_random_eth (nm_device_get_initial_hw_address (self),
_get_generate_mac_address_mask_setting (self, connection, is_wifi, &generate_mac_address_mask_tmp));
if (!hw_addr_generated) {
_LOGW (LOGD_DEVICE, "set-hw-addr: failed to generate %s MAC address", "random");
return FALSE;
......@@ -11731,7 +11761,9 @@ nm_device_hw_addr_set_cloned (NMDevice *self, NMConnection *connection, gboolean
stable_id = _get_stable_id (connection, &stable_type);
if (stable_id) {
hw_addr_generated = nm_utils_hw_addr_gen_stable_eth (stable_type, stable_id,
nm_device_get_ip_iface (self));
nm_device_get_ip_iface (self),
nm_device_get_initial_hw_address (self),
_get_generate_mac_address_mask_setting (self, connection, is_wifi, &generate_mac_address_mask_tmp));
}
if (!hw_addr_generated) {
_LOGW (LOGD_DEVICE, "set-hw-addr: failed to generate %s MAC address", "stable");
......
......@@ -1064,6 +1064,8 @@ _hw_addr_set_scanning (NMDeviceWifi *self, gboolean do_reset)
if ( !priv->hw_addr_scan
|| now >= priv->hw_addr_scan_expire) {
gs_free char *generate_mac_address_mask = NULL;
/* the random MAC address for scanning expires after a while.
*
* We don't bother with to update the MAC address exactly when
......@@ -1071,8 +1073,14 @@ _hw_addr_set_scanning (NMDeviceWifi *self, gboolean do_reset)
* a new one.*/
priv->hw_addr_scan_expire = now + (SCAN_RAND_MAC_ADDRESS_EXPIRE_MIN * 60);
generate_mac_address_mask = nm_config_data_get_device_config (NM_CONFIG_GET_DATA,
"wifi.scan-generate-mac-address-mask",
device,
NULL);
g_free (priv->hw_addr_scan);
priv->hw_addr_scan = nm_utils_hw_addr_gen_random_eth ();
priv->hw_addr_scan = nm_utils_hw_addr_gen_random_eth (nm_device_get_initial_hw_address (device),
generate_mac_address_mask);
}
nm_device_hw_addr_set (device, priv->hw_addr_scan, "scanning");
......
......@@ -3176,27 +3176,68 @@ nm_utils_ipv6_addr_set_stable_privacy (NMUtilsStableType stable_type,
/*****************************************************************************/
static void
_hw_addr_eth_complete (guint8 *bin_addr)
{
/* this LSB of the first octet cannot be set,
* it means Unicast vs. Multicast */
bin_addr[0] &= ~1;
_hw_addr_eth_complete (struct ether_addr *addr,
const char *current_mac_address,
const char *generate_mac_address_mask)
{
struct ether_addr mask;
struct ether_addr oui;
struct ether_addr *ouis;
gsize ouis_len;
guint i;
/* the second LSB of the first octet means
* "globally unique, OUI enforced, BIA (burned-in-address)"
* vs. "locally-administered" */
bin_addr[0] |= 2;
* vs. "locally-administered". By default, set it to
* generate locally-administered addresses.
*
* Maybe be overwritten by a mask below. */
addr->ether_addr_octet[0] |= 2;
if (!generate_mac_address_mask || !*generate_mac_address_mask)
goto out;
if (!_nm_utils_generate_mac_address_mask_parse (generate_mac_address_mask,
&mask,
&ouis,
&ouis_len,
NULL))
goto out;
nm_assert ((ouis == NULL) ^ (ouis_len != 0));
if (ouis) {
/* g_random_int() is good enough here. It uses a static GRand instance
* that is seeded from /dev/urandom. */
oui = ouis[g_random_int () % ouis_len];
g_free (ouis);
} else {
if (!nm_utils_hwaddr_aton (current_mac_address, &oui, ETH_ALEN))
goto out;
}
for (i = 0; i < ETH_ALEN; i++) {
const guint8 a = addr->ether_addr_octet[i];
const guint8 o = oui.ether_addr_octet[i];
const guint8 m = mask.ether_addr_octet[i];
addr->ether_addr_octet[i] = (a & ~m) | (o & m);
}
out:
/* The LSB of the first octet must always be cleared,
* it means Unicast vs. Multicast */
addr->ether_addr_octet[0] &= ~1;
}
char *
nm_utils_hw_addr_gen_random_eth (void)
nm_utils_hw_addr_gen_random_eth (const char *current_mac_address,
const char *generate_mac_address_mask)
{
guint8 bin_addr[ETH_ALEN];
struct ether_addr bin_addr;
if (nm_utils_read_urandom (bin_addr, ETH_ALEN) < 0)
if (nm_utils_read_urandom (&bin_addr, ETH_ALEN) < 0)
return NULL;
_hw_addr_eth_complete (bin_addr);
return nm_utils_hwaddr_ntoa (bin_addr, ETH_ALEN);
_hw_addr_eth_complete (&bin_addr, current_mac_address, generate_mac_address_mask);
return nm_utils_hwaddr_ntoa (&bin_addr, ETH_ALEN);
}
static char *
......@@ -3204,13 +3245,15 @@ _hw_addr_gen_stable_eth (NMUtilsStableType stable_type,
const char *stable_id,
const guint8 *secret_key,
gsize key_len,
const char *ifname)
const char *ifname,
const char *current_mac_address,
const char *generate_mac_address_mask)
{
GChecksum *sum;
guint32 tmp;
guint8 digest[32];
gsize len = sizeof (digest);
guint8 bin_addr[ETH_ALEN];
struct ether_addr bin_addr;
guint8 stable_type_uint8;
nm_assert (stable_id);
......@@ -3239,15 +3282,17 @@ _hw_addr_gen_stable_eth (NMUtilsStableType stable_type,
g_return_val_if_fail (len == 32, NULL);
memcpy (bin_addr, digest, ETH_ALEN);
_hw_addr_eth_complete (bin_addr);
return nm_utils_hwaddr_ntoa (bin_addr, ETH_ALEN);
memcpy (&bin_addr, digest, ETH_ALEN);
_hw_addr_eth_complete (&bin_addr, current_mac_address, generate_mac_address_mask);
return nm_utils_hwaddr_ntoa (&bin_addr, ETH_ALEN);
}
char *
nm_utils_hw_addr_gen_stable_eth (NMUtilsStableType stable_type,
const char *stable_id,
const char *ifname)
const char *ifname,
const char *current_mac_address,
const char *generate_mac_address_mask)
{
gs_free guint8 *secret_key = NULL;
gsize key_len = 0;
......@@ -3262,7 +3307,9 @@ nm_utils_hw_addr_gen_stable_eth (NMUtilsStableType stable_type,
stable_id,
secret_key,
key_len,
ifname);
ifname,
current_mac_address,
generate_mac_address_mask);
}
/*****************************************************************************/
......
......@@ -371,10 +371,13 @@ gboolean nm_utils_ipv6_addr_set_stable_privacy (NMUtilsStableType id_type,
guint dad_counter,
GError **error);
char *nm_utils_hw_addr_gen_random_eth (void);
char *nm_utils_hw_addr_gen_random_eth (const char *current_mac_address,
const char *generate_mac_address_mask);
char *nm_utils_hw_addr_gen_stable_eth (NMUtilsStableType stable_type,
const char *stable_id,
const char *iname);
const char *ifname,
const char *current_mac_address,
const char *generate_mac_address_mask);