diff options
| author | Christian Breunig <christian@breunig.cc> | 2026-09-24 15:27:27 +0000 |
|---|---|---|
| committer | Christian Breunig <christian@breunig.cc> | 2026-09-24 15:27:27 +0000 |
| commit | 112fb62b8b15653c0a26e0de56f326cd2d67da9b (patch) | |
| tree | 58c5cbfa31d0ee3e22f5d5f8f07a8d6c24295739 /python | |
| parent | f148cc7c47a7fc6f81a06817de5f24e0836d0792 (diff) | |
| download | vyos-1x-112fb62b8b15653c0a26e0de56f326cd2d67da9b.tar.gz vyos-1x-112fb62b8b15653c0a26e0de56f326cd2d67da9b.zip | |
wwan: T9350: properly disconnect "disconnect interface wwanN"
Two things kept an operator disconnect from taking effect.
vyos-netlinkd re-dials every configured WWAN interface that has lost its
session. It cannot tell a session lost to RF signal loss from one that was
taken down on purpose, so the disconnect was undone on the next reconcile
pass, at most 30 seconds later:
$ disconnect interface wwan0
08:53:33 t+10s state=registered
08:53:43 t+20s state=registered
08:53:53 t+30s state=connected <- re-dialled
08:54:03 t+40s state=connected
vyos-netlinkd: WWAN interface wwan0 is not connected, re-dialling...
vyos-netlinkd: Re-dialled WWAN interface wwan0
Record the intent instead of inferring it from the session state. op-mode
"disconnect" drops a marker in /run/vyos-wwan, which _reconcile_wwan() honours
and "connect" removes again. conf_mode removes it too, so a commit keeps
re-asserting the configured state - the marker only survives for as long as
nobody says otherwise, and never across a reboot.
Diffstat (limited to 'python')
| -rw-r--r-- | python/vyos/utils/wwan.py | 42 |
1 files changed, 40 insertions, 2 deletions
diff --git a/python/vyos/utils/wwan.py b/python/vyos/utils/wwan.py index fd8cd4f2e..e677c7fa8 100644 --- a/python/vyos/utils/wwan.py +++ b/python/vyos/utils/wwan.py @@ -23,6 +23,8 @@ invocations in one place means a re-dial can never drift from what conf_mode would have done. """ +import os + from time import sleep from vyos.utils.dict import dict_search @@ -39,6 +41,14 @@ service_name = 'ModemManager.service' modem_wait_timeout = 25 modem_wait_poll = 0.250 +# Markers for WWAN interfaces the operator has taken down with op-mode +# "disconnect interface wwanN". vyos-netlinkd re-dials every configured +# interface that has lost its session, which would otherwise bring the +# interface back up within one reconcile interval and make the disconnect look +# like it never happened. Kept in /run - a manual disconnect is not meant to +# survive a reboot, and a commit re-asserts the configured state too. +admin_disconnect_dir = '/run/vyos-wwan' + def modem_index(ifname: str) -> str: """ Return the ModemManager modem index backing an interface, wwan0 -> 0 """ if not ifname.startswith('wwan'): @@ -92,11 +102,39 @@ def connect_options(config: dict) -> str: return options -def modem_disconnect(ifname: str) -> None: +def _admin_disconnect_marker(ifname: str) -> str: + return os.path.join(admin_disconnect_dir, f'{ifname}.disconnected') + +def set_admin_disconnected(ifname: str) -> None: + """ Mark a WWAN interface as administratively disconnected, so it is left + alone by the re-dial pass in vyos-netlinkd """ + # rejects anything that is not a WWAN interface, so a bogus name can never + # end up creating a file here + modem_index(ifname) + os.makedirs(admin_disconnect_dir, exist_ok=True) + with open(_admin_disconnect_marker(ifname), 'w'): + pass + +def clear_admin_disconnected(ifname: str) -> None: + """ Take a WWAN interface out of the administratively disconnected state """ + modem_index(ifname) + try: + os.unlink(_admin_disconnect_marker(ifname)) + except FileNotFoundError: + pass + +def is_admin_disconnected(ifname: str) -> bool: + """ True if a WWAN interface was taken down with op-mode "disconnect" and + has not been connected again since """ + return os.path.exists(_admin_disconnect_marker(ifname)) + +def modem_disconnect(ifname: str, quiet: bool = False) -> None: """ Disconnect every bearer of the modem backing ifname. The number of bearers a modem can hold is limited, so we always disconnect before we dial again. """ - call(f'mmcli --modem {modem_index(ifname)} --simple-disconnect') + call(f'mmcli --modem {modem_index(ifname)} --simple-disconnect', + stdout=DEVNULL if quiet else None, + stderr=DEVNULL if quiet else None) def modem_connect(ifname: str, options: str) -> bool: """ Dial the modem backing ifname, returns True if the modem connected """ |
