summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
authorAlex Kudentsov <43482574+alexk37@users.noreply.github.com>2026-10-14 20:45:45 +0700
committerChristian Breunig <christian@breunig.cc>2026-10-31 09:58:29 +0000
commit639d575ad258f6f6ba629a0c50a710b736e4911d (patch)
treeed62f4f285fd487d10c80c80e900dfb2637ce609 /python
parent2c4b01409a625605009b6e6b29046723a4b2bd57 (diff)
downloadvyos-1x-639d575ad258f6f6ba629a0c50a710b736e4911d.tar.gz
vyos-1x-639d575ad258f6f6ba629a0c50a710b736e4911d.zip
interfaces: T3871: keep an accelerated-networking VF off its NIC's .link file
On Azure every Hyper-V NIC shares one ID_PATH and has no slot, so it is keyed on its MAC. Its accelerated-networking VF carries the same MAC and matched the NIC's .link file, so udev tried to give the VF the NIC's name. The .link file of a Hyper-V NIC now also matches Driver=hv_netvsc, so the VF cannot match it. This holds for a lone NIC keyed on the VMBus path too. A NIC absent at a pass cannot say what it is, so its file keeps the pin it already carries. NICs that are not Hyper-V ones get no Driver line, and their files do not change. The 34-to-35 migration passes the hardware it found, so the pin is written on the first boot of an upgrade too.
Diffstat (limited to 'python')
-rw-r--r--python/vyos/ifconfig/ifname_store.py43
1 files changed, 40 insertions, 3 deletions
diff --git a/python/vyos/ifconfig/ifname_store.py b/python/vyos/ifconfig/ifname_store.py
index e0a6b4cbe..20731aed1 100644
--- a/python/vyos/ifconfig/ifname_store.py
+++ b/python/vyos/ifconfig/ifname_store.py
@@ -60,6 +60,8 @@ _PORT_LABEL = re.compile(r'^Ethernet(\d+)$')
# compares tuples element-wise, so a None here would raise against an int
_LABEL_UNKNOWN = 1 << 30
_VMWARE_PCI_VENDOR = '0x15ad'
+# a Hyper-V NIC carries no slot, path or label of its own - only its MAC
+_HYPERV_DRIVER = 'hv_netvsc'
_DMI_SYS_VENDOR = Path('/sys/class/dmi/id/sys_vendor')
_MAC_RE = re.compile(r'([0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5})')
@@ -119,6 +121,11 @@ def canonical_sort_key(device: dict) -> tuple:
return (rank, label, segments, path, device.get('mac') or '')
+def is_hyperv_nic(device: dict) -> bool:
+ properties = device.get('properties') or {}
+ return properties.get('ID_NET_DRIVER') == _HYPERV_DRIVER
+
+
def _sysfs(path: Path) -> str:
try:
return path.read_text().strip()
@@ -529,40 +536,70 @@ def _link_type_for(name: str) -> str:
return 'ether'
-def render_link(name: str, key: str) -> str:
+def render_link(name: str, key: str, driver: str = '') -> str:
"""Render a systemd .link file body, matching on the stored property.
The device type has to match as well, and a radio is 'wlan' - a .link
naming a type the device is not simply never applies, leaving the
interface on its probe-order name. resolve() already names by type (see
_prefix_for()), so the recorded name is what says which.
+
+ A MAC is not unique to one interface: an accelerated-networking VF
+ carries its NIC's MAC, and matched the NIC's file - udev then tried to
+ give the VF the NIC's name. The driver tells them apart.
"""
+ match_driver = f'Driver={driver}\n' if driver else ''
return (
'# Generated by VyOS - do not edit.\n'
"# Interface names are managed via 'interface-mapping.json'.\n"
'[Match]\n'
f'Type={_link_type_for(name)}\n'
f'Property={key}\n'
+ f'{match_driver}'
'\n'
'[Link]\n'
f'Name={name}\n'
)
-def sync_link_files(store: dict, link_dir: Path = None) -> list:
+def _pinned_driver(path: Path, key: str) -> str:
+ """The driver an existing file already pins for this key, if any"""
+ try:
+ body = path.read_text()
+ except OSError:
+ return ''
+ if f'Property={key}\n' not in body:
+ return ''
+ m = re.search(r'^Driver=(\S+)$', body, re.M)
+ return m.group(1) if m else ''
+
+
+def sync_link_files(store: dict, link_dir: Path = None, devices: list = None) -> list:
"""One .link file per recorded name, so udev applies it as the device is
added - before anything can observe a probe-order name.
"""
link_dir = link_dir or LINK_DIR
link_dir.mkdir(parents=True, exist_ok=True)
+ # a Hyper-V NIC shares its MAC with its accelerated-networking VF, so
+ # its file names the driver too - also when the NIC is keyed on the VMBus
+ # path, as a lone one is. A NIC absent this boot cannot say which, and
+ # keeps the pin its file already carries.
+ drivers = {}
+ for device in devices or []:
+ key = device.get('key') or ''
+ if key and is_hyperv_nic(device):
+ drivers[key] = _HYPERV_DRIVER
+
written = {}
for name, key in store.get('interfaces', {}).items():
# never for a radio: the key is the phy's address, which every
# interface created on that phy shares, so the file would rename
# whichever of them udev saw next
if key and not name.startswith('wlan'):
- written[link_dir / f'{LINK_PREFIX}{name}.link'] = render_link(name, key)
+ path = link_dir / f'{LINK_PREFIX}{name}.link'
+ driver = drivers.get(key) or _pinned_driver(path, key)
+ written[path] = render_link(name, key, driver)
for path, body in written.items():
if path.exists() and path.read_text() == body: