diff options
| author | Alex Kudentsov <43482574+alexk37@users.noreply.github.com> | 2026-10-14 20:45:45 +0700 |
|---|---|---|
| committer | Christian Breunig <christian@breunig.cc> | 2026-10-31 09:58:29 +0000 |
| commit | 639d575ad258f6f6ba629a0c50a710b736e4911d (patch) | |
| tree | ed62f4f285fd487d10c80c80e900dfb2637ce609 /python | |
| parent | 2c4b01409a625605009b6e6b29046723a4b2bd57 (diff) | |
| download | vyos-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.py | 43 |
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: |
