summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
authorChristian Breunig <christian@breunig.cc>2026-09-24 15:27:27 +0000
committerChristian Breunig <christian@breunig.cc>2026-09-24 15:27:27 +0000
commit112fb62b8b15653c0a26e0de56f326cd2d67da9b (patch)
tree58c5cbfa31d0ee3e22f5d5f8f07a8d6c24295739 /python
parentf148cc7c47a7fc6f81a06817de5f24e0836d0792 (diff)
downloadvyos-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.py42
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 """