summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--man/systemd.link.xml17
-rw-r--r--man/systemd.network.xml17
-rw-r--r--src/libsystemd-network/network-internal.c239
-rw-r--r--src/libsystemd-network/network-internal.h9
-rw-r--r--src/network/meson.build1
-rw-r--r--src/network/networkctl.c12
-rw-r--r--src/network/networkd-network-gperf.gperf9
-rw-r--r--src/network/networkd-network.c20
-rw-r--r--src/network/networkd-network.h1
-rw-r--r--src/network/test-networkd-conf.c25
-rw-r--r--src/udev/net/link-config-gperf.gperf9
-rw-r--r--src/udev/net/link-config.c30
-rw-r--r--src/udev/net/link-config.h1
-rw-r--r--src/udev/udev-event.c28
-rw-r--r--src/udev/udev-node.c24
-rw-r--r--src/udev/udev-rules.c2
-rw-r--r--test/fuzz/fuzz-link-parser/directives.link1
-rw-r--r--test/fuzz/fuzz-network-parser/directives.network1
-rw-r--r--test/test-network/conf/13-not-match-udev-property.network6
-rw-r--r--test/test-network/conf/14-match-udev-property.network7
-rwxr-xr-xtest/test-network/systemd-networkd-tests.py57
21 files changed, 402 insertions, 114 deletions
diff --git a/man/systemd.link.xml b/man/systemd.link.xml
index b1be32955e..7ea9a71107 100644
--- a/man/systemd.link.xml
+++ b/man/systemd.link.xml
@@ -103,7 +103,7 @@
<term><varname>Driver=</varname></term>
<listitem>
<para>A whitespace-separated list of shell-style globs matching the driver currently bound to the
- device, as exposed by the udev property <varname>DRIVER</varname> of its parent device, or if that
+ device, as exposed by the udev property <varname>ID_NET_DRIVER</varname> of its parent device, or if that
is not set, the driver as exposed by <command>ethtool -i</command> of the device itself.</para>
</listitem>
</varlistentry>
@@ -116,6 +116,21 @@
</listitem>
</varlistentry>
<varlistentry>
+ <term><varname>Property=</varname></term>
+ <listitem>
+ <para>A whitespace-separated list of udev property name with its value after a equal
+ (<literal>=</literal>). If multiple properties are specified, the test results are ANDed.
+ If the list is prefixed with a "!", the test is inverted. If a value contains white
+ spaces, then please quote whole key and value pair. If a value contains quotation, then
+ please escape the quotation with <literal>\</literal>.</para>
+
+ <para>Example: if a .link file has the following:
+ <programlisting>Property=ID_MODEL_ID=9999 "ID_VENDOR_FROM_DATABASE=vendor name" "KEY=with \"quotation\""</programlisting>
+ then, the .link file matches only when an interface has all the above three properties.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
<term><varname>Host=</varname></term>
<listitem>
<para>Matches against the hostname or machine ID of the host. See <varname>ConditionHost=</varname> in
diff --git a/man/systemd.network.xml b/man/systemd.network.xml
index bad673b44e..d32b60a2c9 100644
--- a/man/systemd.network.xml
+++ b/man/systemd.network.xml
@@ -113,7 +113,7 @@
<listitem>
<para>A whitespace-separated list of shell-style globs
matching the driver currently bound to the device, as
- exposed by the udev property <literal>DRIVER</literal>
+ exposed by the udev property <literal>ID_NET_DRIVER</literal>
of its parent device, or if that is not set the driver
as exposed by <literal>ethtool -i</literal> of the
device itself. If the list is prefixed with a "!", the
@@ -139,6 +139,21 @@
</listitem>
</varlistentry>
<varlistentry>
+ <term><varname>Property=</varname></term>
+ <listitem>
+ <para>A whitespace-separated list of udev property name with its value after a equal
+ (<literal>=</literal>). If multiple properties are specified, the test results are ANDed.
+ If the list is prefixed with a "!", the test is inverted. If a value contains white
+ spaces, then please quote whole key and value pair. If a value contains quotation, then
+ please escape the quotation with <literal>\</literal>.</para>
+
+ <para>Example: if a .network file has the following:
+ <programlisting>Property=ID_MODEL_ID=9999 "ID_VENDOR_FROM_DATABASE=vendor name" "KEY=with \"quotation\""</programlisting>
+ then, the .network file matches only when an interface has all the above three properties.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
<term><varname>Host=</varname></term>
<listitem>
<para>Matches against the hostname or machine ID of the host. See
diff --git a/src/libsystemd-network/network-internal.c b/src/libsystemd-network/network-internal.c
index 9a1b2fba89..1f2e5c7e65 100644
--- a/src/libsystemd-network/network-internal.c
+++ b/src/libsystemd-network/network-internal.c
@@ -12,6 +12,7 @@
#include "conf-parser.h"
#include "device-util.h"
#include "dhcp-lease-internal.h"
+#include "env-util.h"
#include "ether-addr-util.h"
#include "hexdecoct.h"
#include "log.h"
@@ -73,26 +74,66 @@ int net_get_unique_predictable_data(sd_device *device, bool use_sysname, uint64_
return 0;
}
-static bool net_condition_test_strv(char * const *raw_patterns,
- const char *string) {
- if (strv_isempty(raw_patterns))
+static bool net_condition_test_strv(char * const *patterns, const char *string) {
+ char * const *p;
+ bool match = false, has_positive_rule = false;
+
+ if (strv_isempty(patterns))
return true;
- /* If the patterns begin with "!", edit it out and negate the test. */
- if (raw_patterns[0][0] == '!') {
- char **patterns;
- size_t i, length;
+ STRV_FOREACH(p, patterns) {
+ const char *q = *p;
+ bool invert;
+
+ invert = *q == '!';
+ q += invert;
+
+ if (!invert)
+ has_positive_rule = true;
+
+ if (string && fnmatch(q, string, 0) == 0) {
+ if (invert)
+ return false;
+ else
+ match = true;
+ }
+ }
+
+ return has_positive_rule ? match : true;
+}
+
+static int net_condition_test_property(char * const *match_property, sd_device *device) {
+ char * const *p;
+
+ if (strv_isempty(match_property))
+ return true;
+
+ STRV_FOREACH(p, match_property) {
+ _cleanup_free_ char *key = NULL;
+ const char *val, *dev_val;
+ bool invert, v;
+
+ invert = **p == '!';
+
+ val = strchr(*p + invert, '=');
+ if (!val)
+ return -EINVAL;
+
+ key = strndup(*p + invert, val - *p - invert);
+ if (!key)
+ return -ENOMEM;
+
+ val++;
- length = strv_length(raw_patterns) + 1; /* Include the NULL. */
- patterns = newa(char*, length);
- patterns[0] = raw_patterns[0] + 1; /* Skip the "!". */
- for (i = 1; i < length; i++)
- patterns[i] = raw_patterns[i];
+ v = device &&
+ sd_device_get_property_value(device, key, &dev_val) >= 0 &&
+ fnmatch(val, dev_val, 0) == 0;
- return !string || !strv_fnmatch(patterns, string, 0);
+ if (invert ? v : !v)
+ return false;
}
- return string && strv_fnmatch(raw_patterns, string, 0);
+ return true;
}
bool net_match_config(Set *match_mac,
@@ -100,12 +141,25 @@ bool net_match_config(Set *match_mac,
char * const *match_drivers,
char * const *match_types,
char * const *match_names,
+ char * const *match_property,
+ sd_device *device,
const struct ether_addr *dev_mac,
- const char *dev_path,
- const char *dev_driver,
- const char *dev_type,
const char *dev_name) {
+ const char *dev_path = NULL, *dev_driver = NULL, *dev_type = NULL, *mac_str;
+
+ if (device) {
+ (void) sd_device_get_property_value(device, "ID_PATH", &dev_path);
+ (void) sd_device_get_property_value(device, "ID_NET_DRIVER", &dev_driver);
+ (void) sd_device_get_devtype(device, &dev_type);
+
+ if (!dev_name)
+ (void) sd_device_get_sysname(device, &dev_name);
+ if (!dev_mac &&
+ sd_device_get_sysattr_value(device, "address", &mac_str) >= 0)
+ dev_mac = ether_aton(mac_str);
+ }
+
if (match_mac && (!dev_mac || !set_contains(match_mac, dev_mac)))
return false;
@@ -121,6 +175,9 @@ bool net_match_config(Set *match_mac,
if (!net_condition_test_strv(match_names, dev_name))
return false;
+ if (!net_condition_test_property(match_property, device))
+ return false;
+
return true;
}
@@ -164,7 +221,7 @@ int config_parse_net_condition(const char *unit,
return 0;
}
-int config_parse_ifnames(
+int config_parse_match_strv(
const char *unit,
const char *filename,
unsigned line,
@@ -176,7 +233,9 @@ int config_parse_ifnames(
void *data,
void *userdata) {
+ const char *p = rvalue;
char ***sv = data;
+ bool invert;
int r;
assert(filename);
@@ -184,30 +243,154 @@ int config_parse_ifnames(
assert(rvalue);
assert(data);
+ if (isempty(rvalue)) {
+ *sv = strv_free(*sv);
+ return 0;
+ }
+
+ invert = *p == '!';
+ p += invert;
+
for (;;) {
- _cleanup_free_ char *word = NULL;
+ _cleanup_free_ char *word = NULL, *k = NULL;
- r = extract_first_word(&rvalue, &word, NULL, 0);
+ r = extract_first_word(&p, &word, NULL, EXTRACT_UNQUOTE|EXTRACT_RETAIN_ESCAPE);
+ if (r == 0)
+ return 0;
+ if (r == -ENOMEM)
+ return log_oom();
if (r < 0) {
- log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse interface name list: %s", rvalue);
+ log_syntax(unit, LOG_ERR, filename, line, r, "Invalid syntax, ignoring: %s", rvalue);
return 0;
}
+
+ if (invert) {
+ k = strjoin("!", word);
+ if (!k)
+ return log_oom();
+ } else
+ k = TAKE_PTR(word);
+
+ r = strv_consume(sv, TAKE_PTR(k));
+ if (r < 0)
+ return log_oom();
+ }
+}
+
+int config_parse_match_ifnames(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ const char *p = rvalue;
+ char ***sv = data;
+ bool invert;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ invert = *p == '!';
+ p += invert;
+
+ for (;;) {
+ _cleanup_free_ char *word = NULL, *k = NULL;
+
+ r = extract_first_word(&p, &word, NULL, 0);
if (r == 0)
- break;
+ return 0;
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, 0,
+ "Failed to parse interface name list: %s", rvalue);
+ return 0;
+ }
if (!ifname_valid(word)) {
- log_syntax(unit, LOG_ERR, filename, line, 0, "Interface name is not valid or too long, ignoring assignment: %s", rvalue);
- return 0;
+ log_syntax(unit, LOG_ERR, filename, line, 0,
+ "Interface name is not valid or too long, ignoring assignment: %s", word);
+ continue;
}
- r = strv_push(sv, word);
+ if (invert) {
+ k = strjoin("!", word);
+ if (!k)
+ return log_oom();
+ } else
+ k = TAKE_PTR(word);
+
+ r = strv_consume(sv, TAKE_PTR(k));
if (r < 0)
return log_oom();
-
- word = NULL;
}
+}
- return 0;
+int config_parse_match_property(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ const char *p = rvalue;
+ char ***sv = data;
+ bool invert;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ invert = *p == '!';
+ p += invert;
+
+ for (;;) {
+ _cleanup_free_ char *word = NULL, *k = NULL;
+
+ r = extract_first_word(&p, &word, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNQUOTE);
+ if (r == 0)
+ return 0;
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, 0,
+ "Invalid syntax, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ if (!env_assignment_is_valid(word)) {
+ log_syntax(unit, LOG_ERR, filename, line, 0,
+ "Invalid property or value, ignoring assignment: %s", word);
+ continue;
+ }
+
+ if (invert) {
+ k = strjoin("!", word);
+ if (!k)
+ return log_oom();
+ } else
+ k = TAKE_PTR(word);
+
+ r = strv_consume(sv, TAKE_PTR(k));
+ if (r < 0)
+ return log_oom();
+ }
}
int config_parse_ifalias(const char *unit,
diff --git a/src/libsystemd-network/network-internal.h b/src/libsystemd-network/network-internal.h
index ebfb1c3c75..7059c8ae45 100644
--- a/src/libsystemd-network/network-internal.h
+++ b/src/libsystemd-network/network-internal.h
@@ -19,16 +19,17 @@ bool net_match_config(Set *match_mac,
char * const *match_driver,
char * const *match_type,
char * const *match_name,
+ char * const *match_property,
+ sd_device *device,
const struct ether_addr *dev_mac,
- const char *dev_path,
- const char *dev_driver,
- const char *dev_type,
const char *dev_name);
CONFIG_PARSER_PROTOTYPE(config_parse_net_condition);
CONFIG_PARSER_PROTOTYPE(config_parse_hwaddr);
CONFIG_PARSER_PROTOTYPE(config_parse_hwaddrs);
-CONFIG_PARSER_PROTOTYPE(config_parse_ifnames);
+CONFIG_PARSER_PROTOTYPE(config_parse_match_strv);
+CONFIG_PARSER_PROTOTYPE(config_parse_match_ifnames);
+CONFIG_PARSER_PROTOTYPE(config_parse_match_property);
CONFIG_PARSER_PROTOTYPE(config_parse_ifalias);
CONFIG_PARSER_PROTOTYPE(config_parse_bridge_port_priority);
diff --git a/src/network/meson.build b/src/network/meson.build
index 0e302be6c0..14951c544c 100644
--- a/src/network/meson.build
+++ b/src/network/meson.build
@@ -198,7 +198,6 @@ if conf.get('ENABLE_NETWORKD') == 1
[]],
[['src/network/test-network-tables.c',
- 'src/network/test-network-tables.c',
test_tables_h],
[libnetworkd_core,
libudev_static,
diff --git a/src/network/networkctl.c b/src/network/networkctl.c
index 5d91779395..2a748b8599 100644
--- a/src/network/networkctl.c
+++ b/src/network/networkctl.c
@@ -1034,8 +1034,8 @@ static int link_status_one(
if (r < 0)
return r;
r = table_add_cell_stringf(table, NULL, "%s%s%s (%s%s%s)",
- on_color_operational, strna(operational_state), off_color_operational,
- on_color_setup, strna(setup_state), off_color_setup);
+ on_color_operational, strna(operational_state), off_color_operational,
+ on_color_setup, strna(setup_state), off_color_setup);
if (r < 0)
return r;
@@ -1097,10 +1097,10 @@ static int link_status_one(
if (r < 0)
return r;
r = table_add_cell_stringf(table, NULL, "%s%s%s%s",
- ether_addr_to_string(&info->mac_address, ea),
- description ? " (" : "",
- description,
- description ? ")" : "");
+ ether_addr_to_string(&info->mac_address, ea),
+ description ? " (" : "",
+ strempty(description),
+ description ? ")" : "");
if (r < 0)
return r;
}
diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index 0aef7f0206..e6723f2e90 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -26,10 +26,11 @@ struct ConfigPerfItem;
%includes
%%
Match.MACAddress, config_parse_hwaddrs, 0, offsetof(Network, match_mac)
-Match.Path, config_parse_strv, 0, offsetof(Network, match_path)
-Match.Driver, config_parse_strv, 0, offsetof(Network, match_driver)
-Match.Type, config_parse_strv, 0, offsetof(Network, match_type)
-Match.Name, config_parse_ifnames, 0, offsetof(Network, match_name)
+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)
+Match.Name, config_parse_match_ifnames, 0, offsetof(Network, match_name)
+Match.Property, config_parse_match_property, 0, offsetof(Network, match_property)
Match.Host, config_parse_net_condition, CONDITION_HOST, offsetof(Network, conditions)
Match.Virtualization, config_parse_net_condition, CONDITION_VIRTUALIZATION, offsetof(Network, conditions)
Match.KernelCommandLine, config_parse_net_condition, CONDITION_KERNEL_COMMAND_LINE, offsetof(Network, conditions)
diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c
index 2bc413e3dd..0b5205e8e2 100644
--- a/src/network/networkd-network.c
+++ b/src/network/networkd-network.c
@@ -157,7 +157,8 @@ int network_verify(Network *network) {
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) && !network->conditions)
+ strv_isempty(network->match_name) && strv_isempty(network->match_property) &&
+ !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.",
@@ -507,6 +508,7 @@ static Network *network_free(Network *network) {
strv_free(network->match_driver);
strv_free(network->match_type);
strv_free(network->match_name);
+ strv_free(network->match_property);
condition_free_list(network->conditions);
free(network->description);
@@ -607,26 +609,16 @@ int network_get_by_name(Manager *manager, const char *name, Network **ret) {
int network_get(Manager *manager, sd_device *device,
const char *ifname, const struct ether_addr *address,
Network **ret) {
- const char *path = NULL, *driver = NULL, *devtype = NULL;
Network *network;
Iterator i;
assert(manager);
assert(ret);
- if (device) {
- (void) sd_device_get_property_value(device, "ID_PATH", &path);
-
- (void) sd_device_get_property_value(device, "ID_NET_DRIVER", &driver);
-
- (void) sd_device_get_devtype(device, &devtype);
- }
-
ORDERED_HASHMAP_FOREACH(network, manager->networks, i)
- if (net_match_config(network->match_mac, network->match_path,
- network->match_driver, network->match_type,
- network->match_name,
- address, path, driver, devtype, ifname)) {
+ if (net_match_config(network->match_mac, network->match_path, network->match_driver,
+ network->match_type, network->match_name, network->match_property,
+ device, address, ifname)) {
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 b5f4a6e098..72f1b94666 100644
--- a/src/network/networkd-network.h
+++ b/src/network/networkd-network.h
@@ -61,6 +61,7 @@ struct Network {
char **match_driver;
char **match_type;
char **match_name;
+ char **match_property;
LIST_HEAD(Condition, conditions);
char *description;
diff --git a/src/network/test-networkd-conf.c b/src/network/test-networkd-conf.c
index dfb41f801b..07ca127654 100644
--- a/src/network/test-networkd-conf.c
+++ b/src/network/test-networkd-conf.c
@@ -174,7 +174,7 @@ static void test_config_parse_address_one(const char *rvalue, int family, unsign
assert_se(network = new0(Network, 1));
network->n_ref = 1;
assert_se(network->filename = strdup("hogehoge.network"));
- assert_se(config_parse_ifnames("network", "filename", 1, "section", 1, "Name", 0, "*", &network->match_name, network) == 0);
+ assert_se(config_parse_match_ifnames("network", "filename", 1, "section", 1, "Name", 0, "*", &network->match_name, network) == 0);
assert_se(config_parse_address("network", "filename", 1, "section", 1, "Address", 0, rvalue, network, network) == 0);
assert_se(network->n_static_addresses == 1);
assert_se(network_verify(network) >= 0);
@@ -215,6 +215,27 @@ static void test_config_parse_address(void) {
test_config_parse_address_one("::1/-1", AF_INET6, 0, NULL, 0);
}
+static void test_config_parse_match_ifnames(void) {
+ _cleanup_strv_free_ char **names = NULL;
+
+ assert_se(config_parse_match_ifnames("network", "filename", 1, "section", 1, "Name", 0, "!hoge hogehoge foo", &names, NULL) == 0);
+ assert_se(config_parse_match_ifnames("network", "filename", 1, "section", 1, "Name", 0, "!baz", &names, NULL) == 0);
+ assert_se(config_parse_match_ifnames("network", "filename", 1, "section", 1, "Name", 0, "aaa bbb ccc", &names, NULL) == 0);
+
+ strv_equal(names, STRV_MAKE("!hoge", "!hogehoge", "!foo", "!baz", "aaa", "bbb", "ccc"));
+}
+
+static void test_config_parse_match_strv(void) {
+ _cleanup_strv_free_ char **names = NULL;
+
+ assert_se(config_parse_match_strv("network", "filename", 1, "section", 1, "Name", 0, "!hoge hogehoge foo", &names, NULL) == 0);
+ assert_se(config_parse_match_strv("network", "filename", 1, "section", 1, "Name", 0, "!baz", &names, NULL) == 0);
+ assert_se(config_parse_match_strv("network", "filename", 1, "section", 1, "Name", 0,
+ "KEY=val \"KEY2=val with space\" \"KEY3=val with \\\"quotation\\\"\"", &names, NULL) == 0);
+
+ strv_equal(names, STRV_MAKE("!hoge", "!hogehoge", "!foo", "!baz", "KEY=val", "KEY2=val with space", "KEY3=val with \"quotation\""));
+}
+
int main(int argc, char **argv) {
log_parse_environment();
log_open();
@@ -223,6 +244,8 @@ int main(int argc, char **argv) {
test_config_parse_duid_rawdata();
test_config_parse_hwaddr();
test_config_parse_address();
+ test_config_parse_match_ifnames();
+ test_config_parse_match_strv();
return 0;
}
diff --git a/src/udev/net/link-config-gperf.gperf b/src/udev/net/link-config-gperf.gperf
index 9698211d1d..a3d7dec88c 100644
--- a/src/udev/net/link-config-gperf.gperf
+++ b/src/udev/net/link-config-gperf.gperf
@@ -20,10 +20,11 @@ struct ConfigPerfItem;
%includes
%%
Match.MACAddress, config_parse_hwaddrs, 0, offsetof(link_config, match_mac)
-Match.OriginalName, config_parse_ifnames, 0, offsetof(link_config, match_name)
-Match.Path, config_parse_strv, 0, offsetof(link_config, match_path)
-Match.Driver, config_parse_strv, 0, offsetof(link_config, match_driver)
-Match.Type, config_parse_strv, 0, offsetof(link_config, match_type)
+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)
+Match.Type, config_parse_match_strv, 0, offsetof(link_config, match_type)
+Match.Property, config_parse_match_property, 0, offsetof(link_config, match_property)
Match.Host, config_parse_net_condition, CONDITION_HOST, offsetof(link_config, conditions)
Match.Virtualization, config_parse_net_condition, CONDITION_VIRTUALIZATION, offsetof(link_config, conditions)
Match.KernelCommandLine, config_parse_net_condition, CONDITION_KERNEL_COMMAND_LINE, offsetof(link_config, conditions)
diff --git a/src/udev/net/link-config.c b/src/udev/net/link-config.c
index 611add9ae0..9989e6ab65 100644
--- a/src/udev/net/link-config.c
+++ b/src/udev/net/link-config.c
@@ -51,6 +51,7 @@ static void link_config_free(link_config *link) {
strv_free(link->match_driver);
strv_free(link->match_type);
strv_free(link->match_name);
+ strv_free(link->match_property);
condition_free_list(link->conditions);
free(link->description);
@@ -161,7 +162,7 @@ int link_load_one(link_config_ctx *ctx, const char *filename) {
if (set_isempty(link->match_mac) && strv_isempty(link->match_path) &&
strv_isempty(link->match_driver) && strv_isempty(link->match_type) &&
- strv_isempty(link->match_name) && !link->conditions)
+ 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. "
"If that is intended, please add OriginalName=* in the [Match] section.",
@@ -240,42 +241,29 @@ int link_config_get(link_config_ctx *ctx, sd_device *device, link_config **ret)
assert(ret);
LIST_FOREACH(links, link, ctx->links) {
- const char *address = NULL, *id_path = NULL, *id_net_driver = NULL, *devtype = NULL, *sysname = NULL;
-
- (void) sd_device_get_sysattr_value(device, "address", &address);
- (void) sd_device_get_property_value(device, "ID_PATH", &id_path);
- (void) sd_device_get_property_value(device, "ID_NET_DRIVER", &id_net_driver);
- (void) sd_device_get_devtype(device, &devtype);
- (void) sd_device_get_sysname(device, &sysname);
-
if (net_match_config(link->match_mac, link->match_path, link->match_driver,
- link->match_type, link->match_name,
- address ? ether_aton(address) : NULL,
- id_path,
- id_net_driver,
- devtype,
- sysname)) {
+ link->match_type, link->match_name, link->match_property,
+ device, NULL, NULL)) {
if (link->match_name) {
unsigned name_assign_type = NET_NAME_UNKNOWN;
(void) link_unsigned_attribute(device, "name_assign_type", &name_assign_type);
if (name_assign_type == NET_NAME_ENUM && !strv_contains(link->match_name, "*")) {
- log_warning("Config file %s applies to device based on potentially unpredictable interface name '%s'",
- link->filename, sysname);
+ log_device_warning(device, "Config file %s applies to device based on potentially unpredictable interface name",
+ link->filename);
*ret = link;
return 0;
} else if (name_assign_type == NET_NAME_RENAMED) {
- log_warning("Config file %s matches device based on renamed interface name '%s', ignoring",
- link->filename, sysname);
+ log_device_warning(device, "Config file %s matches device based on renamed interface name, ignoring",
+ link->filename);
continue;
}
}
- log_debug("Config file %s applies to device %s",
- link->filename, sysname);
+ log_device_debug(device, "Config file %s is applied", link->filename);
*ret = link;
return 0;
diff --git a/src/udev/net/link-config.h b/src/udev/net/link-config.h
index a45a0e709a..cd99cd54d4 100644
--- a/src/udev/net/link-config.h
+++ b/src/udev/net/link-config.h
@@ -40,6 +40,7 @@ struct link_config {
char **match_driver;
char **match_type;
char **match_name;
+ char **match_property;
LIST_HEAD(Condition, conditions);
char *description;
diff --git a/src/udev/udev-event.c b/src/udev/udev-event.c
index 025a37bc26..dbdb9065dc 100644
--- a/src/udev/udev-event.c
+++ b/src/udev/udev-event.c
@@ -819,7 +819,6 @@ static int rename_netif(UdevEvent *event) {
static int update_devnode(UdevEvent *event) {
sd_device *dev = event->dev;
- bool apply;
int r;
r = sd_device_get_devnum(dev, NULL);
@@ -834,17 +833,13 @@ static int update_devnode(UdevEvent *event) {
if (!uid_is_valid(event->uid)) {
r = device_get_devnode_uid(dev, &event->uid);
- if (r == -ENOENT)
- event->uid = 0;
- else if (r < 0)
+ if (r < 0 && r != -ENOENT)
return log_device_error_errno(dev, r, "Failed to get devnode UID: %m");
}
if (!gid_is_valid(event->gid)) {
r = device_get_devnode_gid(dev, &event->gid);
- if (r == -ENOENT)
- event->gid = 0;
- else if (r < 0)
+ if (r < 0 && r != -ENOENT)
return log_device_error_errno(dev, r, "Failed to get devnode GID: %m");
}
@@ -852,21 +847,14 @@ static int update_devnode(UdevEvent *event) {
r = device_get_devnode_mode(dev, &event->mode);
if (r < 0 && r != -ENOENT)
return log_device_error_errno(dev, r, "Failed to get devnode mode: %m");
- if (r == -ENOENT) {
- if (event->gid > 0)
- /* default 0660 if a group is assigned */
- event->mode = 0660;
- else
- /* default 0600 */
- event->mode = 0600;
- }
}
+ if (event->mode == MODE_INVALID && gid_is_valid(event->gid) && event->gid > 0)
+ /* If group is set, but mode is not set, "upgrade" mode for the group. */
+ event->mode = 0660;
+
+ bool apply_mac = device_for_action(dev, DEVICE_ACTION_ADD);
- apply = device_for_action(dev, DEVICE_ACTION_ADD) ||
- uid_is_valid(event->uid) ||
- gid_is_valid(event->gid) ||
- event->mode != MODE_INVALID;
- return udev_node_add(dev, apply, event->mode, event->uid, event->gid, event->seclabel_list);
+ return udev_node_add(dev, apply_mac, event->mode, event->uid, event->gid, event->seclabel_list);
}
static void event_execute_rules_on_remove(
diff --git a/src/udev/udev-node.c b/src/udev/udev-node.c
index 7d25478577..7e3447f7fa 100644
--- a/src/udev/udev-node.c
+++ b/src/udev/udev-node.c
@@ -26,6 +26,7 @@
#include "string-util.h"
#include "strxcpyx.h"
#include "udev-node.h"
+#include "user-util.h"
static int node_symlink(sd_device *dev, const char *node, const char *slink) {
_cleanup_free_ char *slink_dirname = NULL, *target = NULL;
@@ -270,13 +271,14 @@ int udev_node_update_old_links(sd_device *dev, sd_device *dev_old) {
return 0;
}
-static int node_permissions_apply(sd_device *dev, bool apply,
+static int node_permissions_apply(sd_device *dev, bool apply_mac,
mode_t mode, uid_t uid, gid_t gid,
OrderedHashmap *seclabel_list) {
const char *devnode, *subsystem, *id_filename = NULL;
struct stat stats;
dev_t devnum;
- int r = 0;
+ bool apply_mode, apply_uid, apply_gid;
+ int r;
assert(dev);
@@ -297,23 +299,29 @@ static int node_permissions_apply(sd_device *dev, bool apply,
mode |= S_IFCHR;
if (lstat(devnode, &stats) < 0)
- return log_device_debug_errno(dev, errno, "cannot stat() node '%s' (%m)", devnode);
+ return log_device_debug_errno(dev, errno, "cannot stat() node %s: %m", devnode);
- if (((stats.st_mode & S_IFMT) != (mode & S_IFMT)) || (stats.st_rdev != devnum))
- return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EEXIST), "Found node '%s' with non-matching devnum %s, skip handling",
+ if ((mode != MODE_INVALID && (stats.st_mode & S_IFMT) != (mode & S_IFMT)) || stats.st_rdev != devnum)
+ return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EEXIST),
+ "Found node '%s' with non-matching devnum %s, skip handling",
devnode, id_filename);
- if (apply) {
+ apply_mode = mode != MODE_INVALID && (stats.st_mode & 0777) != (mode & 0777);
+ apply_uid = uid_is_valid(uid) && stats.st_uid != uid;
+ apply_gid = gid_is_valid(gid) && stats.st_gid != gid;
+
+ if (apply_mode || apply_uid || apply_gid || apply_mac) {
bool selinux = false, smack = false;
const char *name, *label;
Iterator i;
- if ((stats.st_mode & 0777) != (mode & 0777) || stats.st_uid != uid || stats.st_gid != gid) {
+ if (apply_mode || apply_uid || apply_gid) {
log_device_debug(dev, "Setting permissions %s, %#o, uid=%u, gid=%u", devnode, mode, uid, gid);
r = chmod_and_chown(devnode, mode, uid, gid);
if (r < 0)
- log_device_warning_errno(dev, r, "Failed to set owner/mode of %s to uid=" UID_FMT ", gid=" GID_FMT ", mode=%#o: %m", devnode, uid, gid, mode);
+ log_device_warning_errno(dev, r, "Failed to set owner/mode of %s to uid=" UID_FMT ", gid=" GID_FMT ", mode=%#o: %m",
+ devnode, uid, gid, mode);
} else
log_device_debug(dev, "Preserve permissions of %s, %#o, uid=%u, gid=%u", devnode, mode, uid, gid);
diff --git a/src/udev/udev-rules.c b/src/udev/udev-rules.c
index bbba2b49d2..303594fa51 100644
--- a/src/udev/udev-rules.c
+++ b/src/udev/udev-rules.c
@@ -502,7 +502,7 @@ static int rule_line_add_token(UdevRuleLine *rule_line, UdevRuleTokenType type,
SET_FLAG(rule_line->type, LINE_HAS_DEVLINK, true);
else if (token->type >= _TK_A_MIN ||
- IN_SET(token->type,
+ IN_SET(token->type, TK_M_PROGRAM,
TK_M_IMPORT_FILE, TK_M_IMPORT_PROGRAM, TK_M_IMPORT_BUILTIN,
TK_M_IMPORT_DB, TK_M_IMPORT_CMDLINE, TK_M_IMPORT_PARENT))
SET_FLAG(rule_line->type, LINE_UPDATE_SOMETHING, true);
diff --git a/test/fuzz/fuzz-link-parser/directives.link b/test/fuzz/fuzz-link-parser/directives.link
index 5925e5ad12..61155063a8 100644
--- a/test/fuzz/fuzz-link-parser/directives.link
+++ b/test/fuzz/fuzz-link-parser/directives.link
@@ -4,6 +4,7 @@ OriginalName=
Path=
Driver=
Type=
+Property=
Host=
Virtualization=
KernelCommandLine=
diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network
index 496c52336c..26dd83d8da 100644
--- a/test/fuzz/fuzz-network-parser/directives.network
+++ b/test/fuzz/fuzz-network-parser/directives.network
@@ -20,6 +20,7 @@ Driver=
Architecture=
Path=
Name=
+Property=
Virtualization=
KernelCommandLine=
Host=
diff --git a/test/test-network/conf/13-not-match-udev-property.network b/test/test-network/conf/13-not-match-udev-property.network
new file mode 100644
index 0000000000..f0a530e9db
--- /dev/null
+++ b/test/test-network/conf/13-not-match-udev-property.network
@@ -0,0 +1,6 @@
+[Match]
+Name=dummy98
+Property=INTERFACE=hoge
+
+[Network]
+IPv6AcceptRA=false
diff --git a/test/test-network/conf/14-match-udev-property.network b/test/test-network/conf/14-match-udev-property.network
new file mode 100644
index 0000000000..b632af166a
--- /dev/null
+++ b/test/test-network/conf/14-match-udev-property.network
@@ -0,0 +1,7 @@
+[Match]
+Name=dummy98
+Property=INTERFACE=dummy98
+
+[Network]
+IPv6AcceptRA=false
+Address=10.1.2.3/16
diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py
index 9c274c128d..4e9160181c 100755
--- a/test/test-network/systemd-networkd-tests.py
+++ b/test/test-network/systemd-networkd-tests.py
@@ -91,6 +91,23 @@ def expectedFailureIfRoutingPolicyIPProtoIsNotAvailable():
return f
+def expectedFailureIfLinkFileFieldIsNotSet():
+ def f(func):
+ support = False
+ rc = call('ip link add name dummy99 type dummy')
+ if rc == 0:
+ ret = run('udevadm info -w10s /sys/class/net/dummy99', stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ if ret.returncode == 0 and 'E: ID_NET_LINK_FILE=' in ret.stdout.rstrip():
+ support = True
+ call('ip link del dummy99')
+
+ if support:
+ return func
+ else:
+ return unittest.expectedFailure(func)
+
+ return f
+
def expectedFailureIfEthtoolDoesNotSupportDriver():
def f(func):
support = False
@@ -371,6 +388,35 @@ class NetworkctlTests(unittest.TestCase, Utilities):
output = check_output(*networkctl_cmd, 'status', 'test1', env=env)
self.assertRegex(output, 'MTU: 1600')
+ def test_type(self):
+ copy_unit_to_networkd_unit_path('11-dummy.netdev', '11-dummy.network')
+ start_networkd()
+ wait_online(['test1:degraded'])
+
+ output = check_output(*networkctl_cmd, 'status', 'test1')
+ print(output)
+ self.assertRegex(output, 'Type: ether')
+
+ output = check_output(*networkctl_cmd, 'status', 'lo')
+ print(output)
+ self.assertRegex(output, 'Type: loopback')
+
+ @expectedFailureIfLinkFileFieldIsNotSet()
+ def test_udev_link_file(self):
+ copy_unit_to_networkd_unit_path('11-dummy.netdev', '11-dummy.network')
+ start_networkd()
+ wait_online(['test1:degraded'])
+
+ output = check_output(*networkctl_cmd, 'status', 'test1')
+ print(output)
+ self.assertRegex(output, r'Link File: (?:/usr)/lib/systemd/network/99-default.link')
+ self.assertRegex(output, r'Network File: /run/systemd/network/11-dummy.network')
+
+ output = check_output(*networkctl_cmd, 'status', 'lo')
+ print(output)
+ self.assertRegex(output, r'Link File: (?:/usr)/lib/systemd/network/99-default.link')
+ self.assertRegex(output, r'Network File: n/a')
+
@expectedFailureIfEthtoolDoesNotSupportDriver()
def test_udev_driver(self):
copy_unit_to_networkd_unit_path('11-dummy.netdev', '11-dummy.network',
@@ -463,6 +509,8 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
'11-dummy.netdev',
'11-dummy.network',
'12-dummy.netdev',
+ '13-not-match-udev-property.network',
+ '14-match-udev-property.network',
'15-name-conflict-test.netdev',
'21-macvlan.netdev',
'21-macvtap.netdev',
@@ -578,6 +626,15 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
print(output)
self.assertRegex(output, '00:50:56:c0:00:28')
+ def test_match_udev_property(self):
+ copy_unit_to_networkd_unit_path('12-dummy.netdev', '13-not-match-udev-property.network', '14-match-udev-property.network')
+ start_networkd()
+ wait_online(['dummy98:routable'])
+
+ output = check_output('networkctl status dummy98')
+ print(output)
+ self.assertRegex(output, 'Network File: /run/systemd/network/14-match-udev-property')
+
def test_wait_online_any(self):
copy_unit_to_networkd_unit_path('25-bridge.netdev', '25-bridge.network', '11-dummy.netdev', '11-dummy.network')
start_networkd()