summaryrefslogtreecommitdiff
path: root/src/system
diff options
context:
space:
mode:
authorChristian Breunig <christian@breunig.cc>2026-10-07 19:57:10 +0200
committerChristian Breunig <christian@breunig.cc>2026-10-31 09:58:29 +0000
commit5fdfcca2a97dabf3c6410a1362c0726142d2acc8 (patch)
treeb7fbbdebe7d1f12552fcb3c938e8e1baa74bb184 /src/system
parentfb0e36092529d4d6947749009c00d7ff74b0c551 (diff)
downloadvyos-1x-5fdfcca2a97dabf3c6410a1362c0726142d2acc8.tar.gz
vyos-1x-5fdfcca2a97dabf3c6410a1362c0726142d2acc8.zip
interfaces: T3871: never rename an interface which is in use
Editing the map and then plugging any card in applied the edit to every interface at once, because the pass re-resolves the whole system whatever woke it. A running port was renamed out from under the configuration still naming it, a bridge member included since members became discoverable. Leave a name alone while the system is relying on it - the port is up, or it has a master. At boot that is true of nothing, as the pass runs before the configuration is applied, so naming is unaffected; afterwards it is true of everything configured, and a card which has just arrived is still down. Say in the log and the status file when a rename was refused, as the map and the running system then disagree until the next boot.
Diffstat (limited to 'src/system')
-rwxr-xr-xsrc/system/vyos-net-name-resolve.py42
1 files changed, 40 insertions, 2 deletions
diff --git a/src/system/vyos-net-name-resolve.py b/src/system/vyos-net-name-resolve.py
index 43d2ed8d2..58966e291 100755
--- a/src/system/vyos-net-name-resolve.py
+++ b/src/system/vyos-net-name-resolve.py
@@ -100,6 +100,36 @@ def get_ifindex(name: str) -> str:
return name
+def interface_in_use(name: str, sys_class_net: str = '/sys/class/net') -> bool:
+ """Is the running system relying on this name? Renaming it then leaves
+ the configuration pointing at a name nothing answers to.
+
+ At boot this is false for everything - the pass runs before the
+ configuration is applied, so the ports are still down. Afterwards, which
+ is the only time the hot-plug unit runs, it is true for every port the
+ configuration touched and for a card which just arrived it is not.
+ """
+ try:
+ flags = int((Path(sys_class_net) / name / 'flags').read_text(), 16)
+ except (OSError, ValueError):
+ return False
+ IFF_UP = 0x1
+ return (bool(flags & IFF_UP)
+ or (Path(sys_class_net) / name / 'master').exists())
+
+
+def refuse_renames_in_use(plan: dict,
+ sys_class_net: str = '/sys/class/net') -> tuple:
+ """Split a rename plan into what may be applied and what must not."""
+ allowed, refused = {}, {}
+ for old, target in plan.items():
+ if interface_in_use(old, sys_class_net):
+ refused[old] = target
+ else:
+ allowed[old] = target
+ return allowed, refused
+
+
def rename_interface(old: str, new: str) -> bool:
run(f'ip link set dev {old} down')
code = run(f'ip link set dev {old} name {new}')
@@ -139,7 +169,7 @@ def safe_bulk_rename(plan: dict) -> dict:
def write_status(store: dict, devices: list, applied: dict, report: dict,
- persisted: bool) -> None:
+ persisted: bool, refused: dict = None) -> None:
"""Publish what this pass decided, to warn about and to inspect later."""
absent = _unmatched(set(store['interfaces'].values()), devices)
status = {
@@ -149,6 +179,8 @@ def write_status(store: dict, devices: list, applied: dict, report: dict,
if key in absent
},
'renamed': applied,
+ # 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', {}),
@@ -210,6 +242,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'
)
+ plan, refused = refuse_renames_in_use(plan)
+ for old, target in sorted(refused.items()):
+ logger.warning(
+ f"'{old}' is in use and was not renamed to '{target}' - the map "
+ 'and the running system disagree until the next boot'
+ )
applied = safe_bulk_rename(plan)
persisted = save_store(store)
@@ -225,7 +263,7 @@ def main():
'resolved for this boot but could not be persisted'
)
- write_status(store, devices, applied, report, persisted)
+ write_status(store, devices, applied, report, persisted, refused)
if __name__ == '__main__':