From 9d44e93638ccc189c99305b2b423222b2dab2b9c Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Wed, 7 Oct 2026 19:56:56 +0200 Subject: wwan: T3871: name a modem in the namespace the CLI reads Anything that was not wireless was named ethN, so an LTE module came up as an ethernet port: bound, working and invisible, because 'interfaces wwan' looks at wwanN and nothing else. Give it its own namespace, keyed on DEVTYPE, and drop what an earlier version filed under an ethernet name - that entry still matches the modem by path and would capture it again. --- python/vyos/ifconfig/ifname_store.py | 38 +++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) (limited to 'python') diff --git a/python/vyos/ifconfig/ifname_store.py b/python/vyos/ifconfig/ifname_store.py index 2145b8ce0..eadd3994e 100644 --- a/python/vyos/ifconfig/ifname_store.py +++ b/python/vyos/ifconfig/ifname_store.py @@ -133,6 +133,18 @@ def is_wireless(ifname: str, sys_class_net: str = '/sys/class/net') -> bool: return (Path(sys_class_net) / ifname / 'phy80211').exists() +def is_wwan(ifname: str, sys_class_net: str = '/sys/class/net') -> bool: + """A modem, which the CLI addresses as wwanN. An ethernet port carries no + DEVTYPE at all, so this is exact - and it is the same property systemd + matches on in a .link file. + """ + try: + uevent = (Path(sys_class_net) / ifname / 'uevent').read_text() + except OSError: + return False + return 'DEVTYPE=wwan' in uevent.split() + + def read_properties(ifname: str) -> dict: """udev properties for an interface.""" try: @@ -183,6 +195,7 @@ def discover_devices(sys_class_net: str = '/sys/class/net') -> list: 'name': entry.name, 'mac': mac, 'wireless': is_wireless(entry.name, sys_class_net), + 'wwan': is_wwan(entry.name, sys_class_net), 'properties': read_properties(entry.name), }) @@ -303,6 +316,8 @@ def save_store(store: dict, path: Path = None) -> bool: def _prefix_for(device: dict) -> str: + if device.get('wwan'): + return 'wwan' return 'wlan' if device.get('wireless') else 'eth' @@ -323,6 +338,20 @@ def resolve(devices: list, store: dict) -> tuple: entries = store.get('interfaces', {}) seen = store.get('hardware', {}) + # a modem an earlier version filed under an ethernet name. The entry still + # matches it by path, so it would be captured again and stay invisible to + #'interfaces wwan' - drop it and let it be named as what it is. The name + # cannot be recognised on its own, and neither can the key: this one reads + # ID_NET_NAME_PATH=wwp..., but a device falling back to ID_PATH reads + # usb-... like anything else on that bus. + misfiled = {name for name, key in entries.items() + if not name.startswith('wwan') + and any(d.get('wwan') and device_matches(d['properties'], key) + for d in devices)} + if misfiled: + entries = {n: k for n, k in entries.items() if n not in misfiled} + seen = {n: m for n, m in seen.items() if n not in misfiled} + assigned = {} # final name -> device report = {'matched': {}, 'moved': {}, 'replaced': {}, 'bootstrapped': {}} @@ -412,6 +441,13 @@ LINK_DIR = Path('/etc/systemd/network') LINK_PREFIX = '10-vyos-' +def _link_type_for(name: str) -> str: + for prefix in ('wlan', 'wwan'): + if name.startswith(prefix): + return prefix + return 'ether' + + def render_link(name: str, key: str) -> str: """Render a systemd .link file body, matching on the stored property. @@ -424,7 +460,7 @@ def render_link(name: str, key: str) -> str: '# Generated by VyOS - do not edit.\n' "# Interface names are managed via 'interface-mapping.json'.\n" '[Match]\n' - f"Type={'wlan' if name.startswith('wlan') else 'ether'}\n" + f'Type={_link_type_for(name)}\n' f'Property={key}\n' '\n' '[Link]\n' -- cgit v1.2.3