diff --git a/man/systemd.link.xml b/man/systemd.link.xml
index 2f231d4978..1a405e989d 100644
--- a/man/systemd.link.xml
+++ b/man/systemd.link.xml
@@ -80,6 +80,17 @@
MACAddress=01:23:45:67:89:ab 00-11-22-33-44-55 AABB.CCDD.EEFF
+
+ PermanentMACAddress=
+
+ A whitespace-separated list of hardware's permanent addresses. While
+ MACAddress= matches the device's current MAC address, this matches the
+ device's permanent MAC address, which may be different from the current one. Use full
+ colon-, hyphen- or dot-delimited hexadecimal. This option may appear more than once, in
+ which case the lists are merged. If the empty string is assigned to this option, the list
+ of hardware addresses defined prior to this is reset.
+
+
OriginalName=
diff --git a/man/systemd.network.xml b/man/systemd.network.xml
index 4cd2520173..de1a854494 100644
--- a/man/systemd.network.xml
+++ b/man/systemd.network.xml
@@ -97,6 +97,17 @@
MACAddress=01:23:45:67:89:ab 00-11-22-33-44-55 AABB.CCDD.EEFF
+
+ PermanentMACAddress=
+
+ A whitespace-separated list of hardware's permanent addresses. While
+ MACAddress= matches the device's current MAC address, this matches the
+ device's permanent MAC address, which may be different from the current one. Use full
+ colon-, hyphen- or dot-delimited hexadecimal. This option may appear more than once, in
+ which case the lists are merged. If the empty string is assigned to this option, the list
+ of hardware addresses defined prior to this is reset.
+
+
Path=
diff --git a/src/libsystemd-network/network-internal.c b/src/libsystemd-network/network-internal.c
index 7198fe4775..0bf0b0e552 100644
--- a/src/libsystemd-network/network-internal.c
+++ b/src/libsystemd-network/network-internal.c
@@ -167,6 +167,7 @@ static const char *const wifi_iftype_table[NL80211_IFTYPE_MAX+1] = {
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(wifi_iftype, enum nl80211_iftype);
bool net_match_config(Set *match_mac,
+ Set *match_permanent_mac,
char * const *match_paths,
char * const *match_drivers,
char * const *match_types,
@@ -177,6 +178,7 @@ bool net_match_config(Set *match_mac,
Set *match_bssid,
sd_device *device,
const struct ether_addr *dev_mac,
+ const struct ether_addr *dev_permanent_mac,
const char *dev_name,
char * const *alternative_names,
enum nl80211_iftype wifi_iftype,
@@ -200,6 +202,12 @@ bool net_match_config(Set *match_mac,
if (match_mac && (!dev_mac || !set_contains(match_mac, dev_mac)))
return false;
+ if (match_permanent_mac &&
+ (!dev_permanent_mac ||
+ ether_addr_is_null(dev_permanent_mac) ||
+ !set_contains(match_permanent_mac, dev_permanent_mac)))
+ return false;
+
if (!net_condition_test_strv(match_paths, dev_path))
return false;
diff --git a/src/libsystemd-network/network-internal.h b/src/libsystemd-network/network-internal.h
index a940b24a42..dff6c8831a 100644
--- a/src/libsystemd-network/network-internal.h
+++ b/src/libsystemd-network/network-internal.h
@@ -16,6 +16,7 @@
#define LINK_BRIDGE_PORT_PRIORITY_MAX 63
bool net_match_config(Set *match_mac,
+ Set *match_permanent_mac,
char * const *match_path,
char * const *match_driver,
char * const *match_type,
@@ -26,6 +27,7 @@ bool net_match_config(Set *match_mac,
Set *match_bssid,
sd_device *device,
const struct ether_addr *dev_mac,
+ const struct ether_addr *dev_permanent_mac,
const char *dev_name,
char * const *alternative_names,
enum nl80211_iftype wifi_iftype,
diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c
index 23d0ee675b..cd370bef57 100644
--- a/src/network/networkd-link.c
+++ b/src/network/networkd-link.c
@@ -12,6 +12,7 @@
#include "dhcp-identifier.h"
#include "dhcp-lease-internal.h"
#include "env-file.h"
+#include "ethtool-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "ipvlan.h"
@@ -617,6 +618,11 @@ static int link_new(Manager *manager, sd_netlink_message *message, Link **ret) {
if (r < 0)
log_link_debug_errno(link, r, "MAC address not found for new device, continuing without");
+ _cleanup_close_ int fd = -1;
+ r = ethtool_get_permanent_macaddr(&fd, link->ifname, &link->permanent_mac);
+ if (r < 0)
+ log_link_debug_errno(link, r, "Permanent MAC address not found for new device, continuing without: %m");
+
r = sd_netlink_message_read_strv(message, IFLA_PROP_LIST, IFLA_ALT_IFNAME, &link->alternative_names);
if (r < 0 && r != -ENODATA)
return r;
@@ -2961,7 +2967,7 @@ static int link_reconfigure_internal(Link *link, sd_netlink_message *m, bool for
}
r = network_get(link->manager, link->sd_device, link->ifname, link->alternative_names,
- &link->mac, link->wlan_iftype, link->ssid, &link->bssid, &network);
+ &link->mac, &link->permanent_mac, link->wlan_iftype, link->ssid, &link->bssid, &network);
if (r == -ENOENT) {
link_enter_unmanaged(link);
return 0;
@@ -3093,7 +3099,7 @@ static int link_initialized_and_synced(Link *link) {
return r;
r = network_get(link->manager, link->sd_device, link->ifname, link->alternative_names,
- &link->mac, link->wlan_iftype, link->ssid, &link->bssid, &network);
+ &link->mac, &link->permanent_mac, link->wlan_iftype, link->ssid, &link->bssid, &network);
if (r == -ENOENT) {
link_enter_unmanaged(link);
return 0;
diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h
index 172e483383..33be661828 100644
--- a/src/network/networkd-link.h
+++ b/src/network/networkd-link.h
@@ -53,6 +53,7 @@ typedef struct Link {
unsigned short iftype;
char *state_file;
struct ether_addr mac;
+ struct ether_addr permanent_mac;
struct in6_addr ipv6ll_address;
uint32_t mtu;
sd_device *sd_device;
diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index ecb82c237f..67b3789ee6 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -28,6 +28,7 @@ struct ConfigPerfItem;
%includes
%%
Match.MACAddress, config_parse_hwaddrs, 0, offsetof(Network, match_mac)
+Match.PermanentMACAddress, config_parse_hwaddrs, 0, offsetof(Network, match_permanent_mac)
Match.Path, config_parse_match_strv, 0, offsetof(Network, match_path)
Match.Driver, config_parse_match_strv, 0, offsetof(Network, match_driver)
Match.Type, config_parse_match_strv, 0, offsetof(Network, match_type)
diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c
index 40394eb766..4fd48be52a 100644
--- a/src/network/networkd-network.c
+++ b/src/network/networkd-network.c
@@ -160,10 +160,10 @@ int network_verify(Network *network) {
assert(network);
assert(network->filename);
- if (set_isempty(network->match_mac) && strv_isempty(network->match_path) &&
- strv_isempty(network->match_driver) && strv_isempty(network->match_type) &&
- strv_isempty(network->match_name) && strv_isempty(network->match_property) &&
- strv_isempty(network->match_ssid) && !network->conditions)
+ if (set_isempty(network->match_mac) && set_isempty(network->match_permanent_mac) &&
+ strv_isempty(network->match_path) && strv_isempty(network->match_driver) &&
+ strv_isempty(network->match_type) && strv_isempty(network->match_name) &&
+ strv_isempty(network->match_property) && strv_isempty(network->match_ssid) && !network->conditions)
log_warning("%s: No valid settings found in the [Match] section. "
"The file will match all interfaces. "
"If that is intended, please add Name=* in the [Match] section.",
@@ -601,6 +601,7 @@ static Network *network_free(Network *network) {
free(network->filename);
set_free_free(network->match_mac);
+ set_free_free(network->match_permanent_mac);
strv_free(network->match_path);
strv_free(network->match_driver);
strv_free(network->match_type);
@@ -721,7 +722,8 @@ int network_get_by_name(Manager *manager, const char *name, Network **ret) {
}
int network_get(Manager *manager, sd_device *device,
- const char *ifname, char * const *alternative_names, const struct ether_addr *address,
+ const char *ifname, char * const *alternative_names,
+ const struct ether_addr *address, const struct ether_addr *permanent_address,
enum nl80211_iftype wlan_iftype, const char *ssid, const struct ether_addr *bssid,
Network **ret) {
Network *network;
@@ -731,10 +733,12 @@ int network_get(Manager *manager, sd_device *device,
assert(ret);
ORDERED_HASHMAP_FOREACH(network, manager->networks, i)
- if (net_match_config(network->match_mac, network->match_path, network->match_driver,
+ if (net_match_config(network->match_mac, network->match_permanent_mac,
+ network->match_path, network->match_driver,
network->match_type, network->match_name, network->match_property,
network->match_wlan_iftype, network->match_ssid, network->match_bssid,
- device, address, ifname, alternative_names, wlan_iftype, ssid, bssid)) {
+ device, address, permanent_address,
+ ifname, alternative_names, wlan_iftype, ssid, bssid)) {
if (network->match_name && device) {
const char *attr;
uint8_t name_assign_type = NET_NAME_UNKNOWN;
diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h
index 783f23c5dd..e1c1c17241 100644
--- a/src/network/networkd-network.h
+++ b/src/network/networkd-network.h
@@ -64,6 +64,7 @@ struct Network {
unsigned n_ref;
Set *match_mac;
+ Set *match_permanent_mac;
char **match_path;
char **match_driver;
char **match_type;
@@ -302,7 +303,8 @@ int network_verify(Network *network);
int network_get_by_name(Manager *manager, const char *name, Network **ret);
int network_get(Manager *manager, sd_device *device, const char *ifname, char * const *alternative_names,
- const struct ether_addr *mac, enum nl80211_iftype wlan_iftype, const char *ssid,
+ const struct ether_addr *mac, const struct ether_addr *permanent_mac,
+ enum nl80211_iftype wlan_iftype, const char *ssid,
const struct ether_addr *bssid, Network **ret);
int network_apply(Network *network, Link *link);
void network_apply_anonymize_if_set(Network *network);
diff --git a/src/network/test-network.c b/src/network/test-network.c
index 9c06860699..7c37563ac2 100644
--- a/src/network/test-network.c
+++ b/src/network/test-network.c
@@ -125,7 +125,7 @@ static void test_network_get(Manager *manager, sd_device *loopback) {
/* let's assume that the test machine does not have a .network file
that applies to the loopback device... */
- assert_se(network_get(manager, loopback, "lo", NULL, &mac, 0, NULL, NULL, &network) == -ENOENT);
+ assert_se(network_get(manager, loopback, "lo", NULL, &mac, &mac, 0, NULL, NULL, &network) == -ENOENT);
assert_se(!network);
}
diff --git a/src/udev/net/link-config-gperf.gperf b/src/udev/net/link-config-gperf.gperf
index a1105fa674..686ff1bc5c 100644
--- a/src/udev/net/link-config-gperf.gperf
+++ b/src/udev/net/link-config-gperf.gperf
@@ -20,6 +20,7 @@ struct ConfigPerfItem;
%includes
%%
Match.MACAddress, config_parse_hwaddrs, 0, offsetof(link_config, match_mac)
+Match.PermanentMACAddress, config_parse_hwaddrs, 0, offsetof(link_config, match_permanent_mac)
Match.OriginalName, config_parse_match_ifnames, 0, offsetof(link_config, match_name)
Match.Path, config_parse_match_strv, 0, offsetof(link_config, match_path)
Match.Driver, config_parse_match_strv, 0, offsetof(link_config, match_driver)
diff --git a/src/udev/net/link-config.c b/src/udev/net/link-config.c
index d199d83411..4a44edfc01 100644
--- a/src/udev/net/link-config.c
+++ b/src/udev/net/link-config.c
@@ -47,6 +47,7 @@ static void link_config_free(link_config *link) {
free(link->filename);
set_free_free(link->match_mac);
+ set_free_free(link->match_permanent_mac);
strv_free(link->match_path);
strv_free(link->match_driver);
strv_free(link->match_type);
@@ -162,8 +163,8 @@ int link_load_one(link_config_ctx *ctx, const char *filename) {
if (link->speed > UINT_MAX)
return -ERANGE;
- if (set_isempty(link->match_mac) && strv_isempty(link->match_path) &&
- strv_isempty(link->match_driver) && strv_isempty(link->match_type) &&
+ if (set_isempty(link->match_mac) && set_isempty(link->match_permanent_mac) &&
+ strv_isempty(link->match_path) && strv_isempty(link->match_driver) && strv_isempty(link->match_type) &&
strv_isempty(link->match_name) && strv_isempty(link->match_property) && !link->conditions)
log_warning("%s: No valid settings found in the [Match] section. "
"The file will match all interfaces. "
@@ -236,16 +237,27 @@ bool link_config_should_reload(link_config_ctx *ctx) {
}
int link_config_get(link_config_ctx *ctx, sd_device *device, link_config **ret) {
+ struct ether_addr permanent_mac = {};
link_config *link;
+ const char *name;
+ int r;
assert(ctx);
assert(device);
assert(ret);
+ r = sd_device_get_sysname(device, &name);
+ if (r < 0)
+ return r;
+
+ r = ethtool_get_permanent_macaddr(&ctx->ethtool_fd, name, &permanent_mac);
+ if (r < 0)
+ log_device_debug_errno(device, r, "Failed to get permanent MAC address, ignoring: %m");
+
LIST_FOREACH(links, link, ctx->links) {
- if (net_match_config(link->match_mac, link->match_path, link->match_driver,
+ if (net_match_config(link->match_mac, link->match_permanent_mac, link->match_path, link->match_driver,
link->match_type, link->match_name, link->match_property, NULL, NULL, NULL,
- device, NULL, NULL, NULL, 0, NULL, NULL)) {
+ device, NULL, &permanent_mac, NULL, NULL, 0, NULL, NULL)) {
if (link->match_name && !strv_contains(link->match_name, "*")) {
unsigned name_assign_type = NET_NAME_UNKNOWN;
diff --git a/src/udev/net/link-config.h b/src/udev/net/link-config.h
index 1c3a3f7fd4..496a8bccb7 100644
--- a/src/udev/net/link-config.h
+++ b/src/udev/net/link-config.h
@@ -36,6 +36,7 @@ struct link_config {
char *filename;
Set *match_mac;
+ Set *match_permanent_mac;
char **match_path;
char **match_driver;
char **match_type;
diff --git a/test/fuzz/fuzz-link-parser/directives.link b/test/fuzz/fuzz-link-parser/directives.link
index abf21f678a..ba8760f12b 100644
--- a/test/fuzz/fuzz-link-parser/directives.link
+++ b/test/fuzz/fuzz-link-parser/directives.link
@@ -1,5 +1,6 @@
[Match]
MACAddress=
+PermanentMACAddress=
OriginalName=
Path=
Driver=
diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network
index 37d31e3a92..0e3adac5ce 100644
--- a/test/fuzz/fuzz-network-parser/directives.network
+++ b/test/fuzz/fuzz-network-parser/directives.network
@@ -28,6 +28,7 @@ Virtualization=
KernelCommandLine=
Host=
MACAddress=
+PermanentMACAddress=
[Link]
RequiredForOnline=
ARP=