diff options
| author | Alex Kudentsov <43482574+alexk37@users.noreply.github.com> | 2026-10-14 20:46:08 +0700 |
|---|---|---|
| committer | Christian Breunig <christian@breunig.cc> | 2026-10-31 09:58:29 +0000 |
| commit | 153dd688b327c922ec4b371db1fdd11b8ee8cf9e (patch) | |
| tree | 8fcb56faa26c097d86e76f1b7af82bdf0b2d5af2 /src/system | |
| parent | 639d575ad258f6f6ba629a0c50a710b736e4911d (diff) | |
| download | vyos-1x-153dd688b327c922ec4b371db1fdd11b8ee8cf9e.tar.gz vyos-1x-153dd688b327c922ec4b371db1fdd11b8ee8cf9e.zip | |
interfaces: T3871: give a re-created Hyper-V NIC the name of the NIC it replaced
A Hyper-V NIC is known by its MAC alone. One deleted and created again on
the same VM - an Azure NIC re-created, or a Hyper-V adapter removed and
added - comes back with a new MAC, so nothing on it matched the store: its
name stayed reserved, the new NIC got a new name, and its configuration
was left on a name with no NIC. cloud-init does not run again on the same
instance, so nothing else repairs it.
At boot, a vacant name now goes to a new NIC only when the pairing is not
in doubt:
- exactly one recorded name has no NIC, and exactly one new Hyper-V NIC
appeared. With more of either, the order the NICs come up in does not
say which replaced which, so nothing is handed over and the names stay
reserved, as before;
- the name went missing at this pass. The store records which names were
missing at the last pass, so a NIC removed for good does not hand its
name to a NIC added at a later boot;
- the name's own NIC was a Hyper-V one. The store records which names sat
on a Hyper-V NIC; a MAC-keyed name can belong to other hardware, and a
store written before this was recorded hands nothing over (the boot pass
logs that when it holds a name back);
- only at boot. On a running system the configuration under a name is not
applied to the NIC that takes it, so the hotplug unit passes --hotplug,
which names the new NIC like any other and records the vacant name as
missing, so the next boot does not hand it over either.
An adoption is logged as a warning, reported under 'adopted' in the status
file (kept there across hotplug passes while it holds), and repeated by
vyos-router, as a slot match is: verify the port before relying on its
configuration.
Diffstat (limited to 'src/system')
| -rwxr-xr-x | src/system/vyos-net-name-resolve.py | 56 |
1 files changed, 49 insertions, 7 deletions
diff --git a/src/system/vyos-net-name-resolve.py b/src/system/vyos-net-name-resolve.py index d74663707..f3e4e62fb 100755 --- a/src/system/vyos-net-name-resolve.py +++ b/src/system/vyos-net-name-resolve.py @@ -26,6 +26,7 @@ import json import logging import logging.handlers +import sys import time from pathlib import Path @@ -182,16 +183,21 @@ def write_status(store: dict, devices: list, applied: dict, report: dict, # in use, so left alone until the next boot 'rename_refused': refused or {}, # everything recognised from the store, however it was recognised - 'matched': {**report.get('matched', {}), - **report.get('reassigned', {}), - **report.get('moved', {}), - **report.get('replaced', {})}, + 'matched': { + **report.get('matched', {}), + **report.get('reassigned', {}), + **report.get('moved', {}), + **report.get('replaced', {}), + **report.get('adopted', {}), + }, # the name changed hands between cards the store already knows 'reassigned': report.get('reassigned', {}), # the card was found in a different slot - certain, it is the same card 'moved': report.get('moved', {}), # a different card took the slot - the one case that needs verifying 'matched_by_path': report.get('replaced', {}), + # a recorded Hyper-V NIC was gone and its one replacement took its name + 'adopted': report.get('adopted', {}), 'new_hardware': report.get('bootstrapped', {}), 'persisted': persisted, } @@ -201,7 +207,25 @@ def write_status(store: dict, devices: list, applied: dict, report: dict, logger.error(f'could not write status file: {e}') -def main(): +def boot_adoptions(store: dict) -> dict: + """The adoptions the boot pass reported that still hold. A pass for + hot-plugged hardware adopts nothing, and must not wipe the record of + what this boot did.""" + try: + adopted = json.loads(status_file.read_text()).get('adopted') or {} + except (OSError, ValueError, AttributeError): + return {} + hardware = store.get('hardware', {}) + return {name: mac for name, mac in adopted.items() + if hardware.get(name) == mac} + + +def hotplug_requested(argv: list) -> bool: + """--hotplug: started for hardware added to a running system""" + return '--hotplug' in argv + + +def main(hotplug: bool = False): if is_running_as_container(): # a container owns no NIC - its veth pairs have no backing bus device logger.info('running inside a container - skipping interface naming') @@ -229,7 +253,16 @@ def main(): 'reserved.' ) - plan, store, report = resolve(devices, store) + if not hotplug and 'hyperv' not in store and any( + key.startswith('ID_NET_NAME_MAC=') and key in absent + for key in store['interfaces'].values()): + # a name is vacant, and the map was written by code that did not + # record which NICs were Hyper-V ones: the name is not handed over. + # Each pass records the Hyper-V NICs it finds, but never a vacant name + logger.info('the interface map does not say which interfaces were ' + 'Hyper-V NICs - no vacant name is given to a new NIC ' + 'this boot') + plan, store, report = resolve(devices, store, adopt=not hotplug) for name, mac in sorted(report.get('bootstrapped', {}).items()): logger.info(f"new hardware {mac} named '{name}'") @@ -242,6 +275,12 @@ def main(): f"'{name}' was matched to new hardware {mac} by slot - verify it " 'is the port you expect before relying on its configuration' ) + for name, mac in sorted(report.get('adopted', {}).items()): + logger.warning( + f"'{name}' had no NIC this boot and was given to the new Hyper-V " + f'NIC {mac}, which took its place - verify it is the port you ' + 'expect before relying on its configuration' + ) plan, refused = refuse_renames_in_use(plan) for old, target in sorted(refused.items()): logger.warning( @@ -263,6 +302,9 @@ def main(): 'resolved for this boot but could not be persisted' ) + if hotplug: + # reported once, by the boot pass; kept on record until the next boot + report = {**report, 'adopted': boot_adoptions(store)} write_status(store, devices, applied, report, persisted, refused) @@ -274,4 +316,4 @@ if __name__ == '__main__': logger.addHandler(syslog_handler) logger.setLevel(logging.DEBUG) - main() + main(hotplug=hotplug_requested(sys.argv[1:])) |
