From ab51a839b03d0afa16a314c17f7eb09b0552263f Mon Sep 17 00:00:00 2001 From: Fatih USTA Date: Thu, 6 Jul 2023 11:35:08 +0300 Subject: [PATCH 1/3] Added pciid match support Signed-off-by: Fatih USTA --- doc/netplan-yaml.md | 15 +++++++++++++++ include/netplan.h | 2 +- netplan/cli/utils.py | 30 +++++++++++++++++++++++++++++- netplan/libnetplan.py | 5 +++-- src/abi.h | 1 + src/generate.c | 5 +++-- src/netplan.c | 1 + src/networkd.c | 10 +++++++++- src/nm.c | 9 +++++++-- src/parse.c | 1 + src/types.c | 1 + src/util.c | 7 ++++++- tests/generator/base.py | 2 ++ tests/generator/test_ethernets.py | 23 ++++++++++++++++++++++- 14 files changed, 101 insertions(+), 11 deletions(-) diff --git a/doc/netplan-yaml.md b/doc/netplan-yaml.md index a6d428501..e2267cebe 100644 --- a/doc/netplan-yaml.md +++ b/doc/netplan-yaml.md @@ -105,6 +105,12 @@ Match devices by MAC when setting options like: `wakeonlan` or `*-offload`. > A sequence of globs is supported, any of which must match. > Matching on driver is *only* supported with networkd. + - **pciid** (scalar) – since **0.107** + + > The PCI ID, corresponding to the `ID_PATH` udev properity. (`0000:98:00.1`) + > Matching on pci id is *only* supported with networkd. + > `udevadm info /sys/class/net/DEVICE_NAME` + Examples: - All cards on second PCI bus: @@ -145,6 +151,15 @@ Match devices by MAC when setting options like: `wakeonlan` or `*-offload`. name: en* ``` + - Matching with PCI ID `0000:98:00.1`: + ```yaml + network: + ethernets: + nic0: + match: + pciid: 0000:98:00.1 + ``` + - **set-name** (scalar) > When matching on unique properties such as path or MAC, or with additional diff --git a/include/netplan.h b/include/netplan.h index e71c55e52..c3253d2de 100644 --- a/include/netplan.h +++ b/include/netplan.h @@ -125,7 +125,7 @@ NETPLAN_PUBLIC gboolean netplan_netdef_has_match(const NetplanNetDefinition* netdef); NETPLAN_PUBLIC gboolean -netplan_netdef_match_interface(const NetplanNetDefinition* netdef, const char* name, const char* mac, const char* driver_name); +netplan_netdef_match_interface(const NetplanNetDefinition* netdef, const char* name, const char* mac, const char* driver_name, const char* pciid); /********** Old API below this ***********/ diff --git a/netplan/cli/utils.py b/netplan/cli/utils.py index 3435168c1..47b318327 100644 --- a/netplan/cli/utils.py +++ b/netplan/cli/utils.py @@ -174,6 +174,33 @@ def get_interface_macaddress(interface): return link.get('addr', '') +def get_interface_pciid(interface): # pragma: nocover (covered in autopkgtest) + devdir = os.path.join('/sys/class/net', interface) + pciid = None + try: + uevent = os.path.realpath(os.path.join(devdir, 'device', 'uevent')) + if os.path.exists(uevent): + with open(uevent) as f: + contents = f.read().splitlines() + f.close() + else: + return None + + for line in contents: + if line.split('=')[0] == 'PCI_SLOT_NAME': + pciid = line.split('=')[1] # 0000:4b:00.0 + break + except IOError as e: + logging.debug('Cannot replug %s: cannot read link %s: %s', interface, devdir, str(e)) + return None + + # 0000:4b:00.0 + if re.match('[0-9a-f]+:[0-9a-f]+:[0-9a-f]+.+', pciid, re.IGNORECASE) is None: + return None + + return pciid + + def find_matching_iface(interfaces: list, netdef): assert isinstance(netdef, np.NetDefinition) assert netdef.has_match @@ -181,7 +208,8 @@ def find_matching_iface(interfaces: list, netdef): matches = list(filter(lambda itf: netdef.match_interface( itf_name=itf, itf_driver=get_interface_driver_name(itf), - itf_mac=get_interface_macaddress(itf)), interfaces)) + itf_mac=get_interface_macaddress(itf), + itf_pciid=get_interface_pciid(itf)), interfaces)) # Return current name of unique matched interface, if available if len(matches) != 1: diff --git a/netplan/libnetplan.py b/netplan/libnetplan.py index b2f435d25..beb530c9d 100644 --- a/netplan/libnetplan.py +++ b/netplan/libnetplan.py @@ -598,12 +598,13 @@ def embedded_switch_mode(self): def delay_virtual_functions_rebind(self): return bool(lib.netplan_netdef_get_delay_virtual_functions_rebind(self._ptr)) - def match_interface(self, itf_name=None, itf_driver=None, itf_mac=None): + def match_interface(self, itf_name=None, itf_driver=None, itf_mac=None, itf_pciid=None): return bool(lib.netplan_netdef_match_interface( self._ptr, itf_name and itf_name.encode('utf-8'), itf_mac and itf_mac.encode('utf-8'), - itf_driver and itf_driver.encode('utf-8'))) + itf_driver and itf_driver.encode('utf-8'), + itf_pciid and itf_pciid.encode('utf-8'))) @property def vf_count(self): diff --git a/src/abi.h b/src/abi.h index 3ab476df9..c91e52da1 100644 --- a/src/abi.h +++ b/src/abi.h @@ -249,6 +249,7 @@ struct netplan_net_definition { char* driver; char* mac; char* original_name; + char* pciid; } match; gboolean has_match; gboolean wake_on_lan; diff --git a/src/generate.c b/src/generate.c index aa2d17053..12118a7ed 100644 --- a/src/generate.c +++ b/src/generate.c @@ -174,13 +174,14 @@ find_interface(gchar* interface, GHashTable* netdefs) } else { const NetplanNetDefinition *nd = (NetplanNetDefinition *)g_ptr_array_index (found, 0); - g_printf("id=%s, backend=%s, set_name=%s, match_name=%s, match_mac=%s, match_driver=%s\n", + g_printf("id=%s, backend=%s, set_name=%s, match_name=%s, match_mac=%s, match_driver=%s, match_pciid=%s\n", nd->id, netplan_backend_name(nd->backend), nd->set_name, nd->match.original_name, nd->match.mac, - nd->match.driver); + nd->match.driver, + nd->match.pciid); } ret = EXIT_SUCCESS; diff --git a/src/netplan.c b/src/netplan.c index 23a0f701d..d955585c2 100644 --- a/src/netplan.c +++ b/src/netplan.c @@ -106,6 +106,7 @@ write_match(yaml_event_t* event, yaml_emitter_t* emitter, const NetplanNetDefini YAML_MAPPING_OPEN(event, emitter); YAML_NONNULL_STRING(event, emitter, "name", def->match.original_name); YAML_NONNULL_STRING(event, emitter, "macaddress", def->match.mac) + YAML_NONNULL_STRING(event, emitter, "pciid", def->match.pciid) if (def->match.driver && strchr(def->match.driver, '\t')) { gchar **split = g_strsplit(def->match.driver, "\t", 0); YAML_SCALAR_PLAIN(event, emitter, "driver"); diff --git a/src/networkd.c b/src/networkd.c index 21b8bad07..dcc26359d 100644 --- a/src/networkd.c +++ b/src/networkd.c @@ -91,6 +91,11 @@ append_match_section(const NetplanNetDefinition* def, GString* s, gboolean match */ g_string_append_printf(s, "PermanentMACAddress=%s\n", def->match.mac); } + + // Append pciid to match interface + if (def->match.pciid) + g_string_append_printf(s, "Path=pci-%s\n", def->match.pciid); + /* name matching is special: if the .link renames the interface, the * .network has to use the renamed one, otherwise the original one */ if (!match_rename && def->match.original_name) @@ -997,7 +1002,7 @@ write_rules_file(const NetplanNetDefinition* def, const char* rootdir) * renamed name. If you match by the unstable kernel name, the * device no longer has that name when udevd reads the file, so * the rule doesn't fire. So only support mac and driver. */ - if (!def->set_name || (!def->match.mac && !def->match.driver)) + if (!def->set_name || (!def->match.mac && !def->match.driver && !def->match.pciid)) return; /* build file contents */ @@ -1014,6 +1019,9 @@ write_rules_file(const NetplanNetDefinition* def, const char* rootdir) if (def->match.mac) g_string_append_printf(s, "ATTR{address}==\"%s\", ", def->match.mac); + if (def->match.pciid) + g_string_append_printf(s, "ENV{ID_PATH}==\"pci-%s\", ", def->match.pciid); + g_string_append_printf(s, "NAME=\"%s\"\n", def->set_name); orig_umask = umask(022); diff --git a/src/nm.c b/src/nm.c index de31bed44..e3bf245d5 100644 --- a/src/nm.c +++ b/src/nm.c @@ -1018,7 +1018,7 @@ netplan_state_finish_nm_write( guint unmanaged = nd->backend == NETPLAN_BACKEND_NM ? 0 : 1; /* Special case: manage or ignore any device of given type on empty "match: {}" stanza */ - if (nd->has_match && !nd->match.driver && !nd->match.mac && !nd->match.original_name) { + if (nd->has_match && !nd->match.driver && !nd->match.mac && !nd->match.original_name && !nd->match.pciid) { nm_type = type_str(nd); g_assert(nm_type); g_string_append_printf(nm_conf, "[device-netplan.%s.%s]\nmatch-device=type:%s\n" @@ -1050,7 +1050,7 @@ netplan_state_finish_nm_write( * from the "match" stanza (e.g. original_name/mac/drivers) * This will match the "old" interface (i.e. original MAC and/or * interface name) if it got changed */ - if (nd->has_match && (nd->match.original_name || nd->match.mac || nd->match.driver)) { + if (nd->has_match && (nd->match.original_name || nd->match.mac || nd->match.driver || nd->match.pciid)) { // match on original name glob // TODO: maybe support matching on multiple name globs in the future (like drivers) g_string_append(udev_rules, prefix); @@ -1077,6 +1077,11 @@ netplan_state_finish_nm_write( g_string_append_printf(udev_rules, " ENV{ID_NET_DRIVER}==\"%s\",", drivers); g_free(drivers); } + + // match pciid + if (nd->match.pciid) + g_string_append_printf(udev_rules, " ENV{ID_PATH}==\"%s\",", nd->match.pciid); + g_string_append(udev_rules, suffix); } } diff --git a/src/parse.c b/src/parse.c index 977b623fa..9fbda6f70 100644 --- a/src/parse.c +++ b/src/parse.c @@ -857,6 +857,7 @@ static const mapping_entry_handler match_handlers[] = { {"driver", YAML_NO_NODE, {.variable=handle_match_driver}}, {"macaddress", YAML_SCALAR_NODE, {.generic=handle_netdef_mac}, netdef_offset(match.mac)}, {"name", YAML_SCALAR_NODE, {.generic=handle_netdef_id}, netdef_offset(match.original_name)}, + {"pciid", YAML_SCALAR_NODE, {.generic=handle_netdef_str}, netdef_offset(match.pciid)}, {NULL} }; diff --git a/src/types.c b/src/types.c index 9fbc4c701..02002e6ea 100644 --- a/src/types.c +++ b/src/types.c @@ -278,6 +278,7 @@ reset_netdef(NetplanNetDefinition* netdef, NetplanDefType new_type, NetplanBacke FREE_AND_NULLIFY(netdef->match.driver); FREE_AND_NULLIFY(netdef->match.mac); FREE_AND_NULLIFY(netdef->match.original_name); + FREE_AND_NULLIFY(netdef->match.pciid); netdef->has_match = FALSE; netdef->wake_on_lan = FALSE; netdef->wowlan = 0; diff --git a/src/util.c b/src/util.c index d337a7787..46cca3ad0 100644 --- a/src/util.c +++ b/src/util.c @@ -824,7 +824,7 @@ netplan_copy_string(const char* input, char* out_buffer, size_t out_size) } gboolean -netplan_netdef_match_interface(const NetplanNetDefinition* netdef, const char* name, const char* mac, const char* driver_name) +netplan_netdef_match_interface(const NetplanNetDefinition* netdef, const char* name, const char* mac, const char* driver_name, const char* pciid) { if (!netdef->has_match) return !g_strcmp0(name, netdef->id); @@ -855,6 +855,11 @@ netplan_netdef_match_interface(const NetplanNetDefinition* netdef, const char* n return matches_driver; } + if (netdef->match.pciid) { + if (g_strcmp0(netdef->match.pciid, pciid)) + return FALSE; + } + return TRUE; } diff --git a/tests/generator/base.py b/tests/generator/base.py index b4bdbfc20..fb10d07fb 100644 --- a/tests/generator/base.py +++ b/tests/generator/base.py @@ -73,6 +73,7 @@ [Service]\nType=oneshot\nTimeoutStartSec=10s\nExecStart=/usr/sbin/netplan apply --only-ovs-cleanup\n' UDEV_MAC_RULE = 'SUBSYSTEM=="net", ACTION=="add", DRIVERS=="%s", ATTR{address}=="%s", NAME="%s"\n' UDEV_NO_MAC_RULE = 'SUBSYSTEM=="net", ACTION=="add", DRIVERS=="%s", NAME="%s"\n' +UDEV_PCIID_RULE = 'SUBSYSTEM=="net", ACTION=="add", DRIVERS=="%s", ENV{ID_PATH}=="pci-%s", NAME="%s"\n' UDEV_SRIOV_RULE = 'ACTION=="add", SUBSYSTEM=="net", ATTRS{sriov_totalvfs}=="?*", RUN+="/usr/sbin/netplan apply --sriov-only"\n' ND_WITHIPGW = '[Match]\nName=%s\n\n[Network]\nLinkLocalAddressing=ipv6\nAddress=%s\nAddress=%s\nGateway=%s\n\ ConfigureWithoutCarrier=yes\n' @@ -102,6 +103,7 @@ NM_UNMANAGED_MAC = 'SUBSYSTEM=="net", ACTION=="add|change|move", ATTR{address}=="%s", ENV{NM_UNMANAGED}="1"\n' NM_MANAGED_DRIVER = 'SUBSYSTEM=="net", ACTION=="add|change|move", ENV{ID_NET_DRIVER}=="%s", ENV{NM_UNMANAGED}="0"\n' NM_UNMANAGED_DRIVER = 'SUBSYSTEM=="net", ACTION=="add|change|move", ENV{ID_NET_DRIVER}=="%s", ENV{NM_UNMANAGED}="1"\n' +NM_UNMANAGED_PCIID = 'SUBSYSTEM=="net", ACTION=="add|change|move", ENV{ID_PATH}=="%s", ENV{NM_UNMANAGED}="1"\n' WOKE_REPLACE_REGEX = ' +# wokeignore:rule=[a-z]+' diff --git a/tests/generator/test_ethernets.py b/tests/generator/test_ethernets.py index 6c1a1016c..1e40691d2 100644 --- a/tests/generator/test_ethernets.py +++ b/tests/generator/test_ethernets.py @@ -20,7 +20,7 @@ from .base import TestBase, ND_DHCP4, UDEV_MAC_RULE, UDEV_NO_MAC_RULE, UDEV_SRIOV_RULE, \ NM_MANAGED, NM_UNMANAGED, NM_MANAGED_MAC, NM_UNMANAGED_MAC, \ - NM_MANAGED_DRIVER, NM_UNMANAGED_DRIVER + NM_MANAGED_DRIVER, NM_UNMANAGED_DRIVER, NM_UNMANAGED_PCIID, UDEV_PCIID_RULE class TestNetworkd(TestBase): @@ -164,6 +164,27 @@ def test_eth_match_by_mac_rename(self): self.assert_nm(None) self.assert_nm_udev(NM_UNMANAGED % 'lom1' + NM_UNMANAGED_MAC % '11:22:33:44:55:66') + def test_eth_match_by_pciid_rename(self): + self.generate('''network: + version: 2 + ethernets: + def1: + match: + pciid: 0000:98:00.1 + set-name: lom1''') + + self.assert_networkd({'def1.link': '[Match]\nPath=pci-0000:98:00.1\n\n[Link]\nName=lom1\nWakeOnLan=off\n', + 'def1.network': '''[Match] +Path=pci-0000:98:00.1 +Name=lom1 + +[Network] +LinkLocalAddressing=ipv6 +'''}) + self.assert_networkd_udev({'def1.rules': (UDEV_PCIID_RULE % ('?*', '0000:98:00.1', 'lom1'))}) + self.assert_nm(None) + self.assert_nm_udev(NM_UNMANAGED % 'lom1' + NM_UNMANAGED_PCIID % '0000:98:00.1') + # https://bugs.launchpad.net/netplan/+bug/1848474 def test_eth_match_by_mac_infiniband(self): self.generate('''network: From 40c3dc4ae139b460f39d212b929f446d31ebf707 Mon Sep 17 00:00:00 2001 From: Fatih USTA Date: Thu, 6 Jul 2023 20:48:10 +0300 Subject: [PATCH 2/3] Fixes wrong definition of variable Signed-off-by: Fatih USTA --- netplan/cli/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/netplan/cli/utils.py b/netplan/cli/utils.py index 47b318327..28d93892b 100644 --- a/netplan/cli/utils.py +++ b/netplan/cli/utils.py @@ -176,7 +176,6 @@ def get_interface_macaddress(interface): def get_interface_pciid(interface): # pragma: nocover (covered in autopkgtest) devdir = os.path.join('/sys/class/net', interface) - pciid = None try: uevent = os.path.realpath(os.path.join(devdir, 'device', 'uevent')) if os.path.exists(uevent): From 1c7ef23ca4288fe9f52331c7a4df3bfa90ae3420 Mon Sep 17 00:00:00 2001 From: Fatih USTA Date: Thu, 6 Jul 2023 20:52:32 +0300 Subject: [PATCH 3/3] Fixes variable definition Signed-off-by: Fatih USTA --- netplan/cli/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/netplan/cli/utils.py b/netplan/cli/utils.py index 28d93892b..28ca4efa1 100644 --- a/netplan/cli/utils.py +++ b/netplan/cli/utils.py @@ -176,6 +176,7 @@ def get_interface_macaddress(interface): def get_interface_pciid(interface): # pragma: nocover (covered in autopkgtest) devdir = os.path.join('/sys/class/net', interface) + pciid = '' try: uevent = os.path.realpath(os.path.join(devdir, 'device', 'uevent')) if os.path.exists(uevent):