From 112fb62b8b15653c0a26e0de56f326cd2d67da9b Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Thu, 24 Sep 2026 15:27:27 +0000 Subject: 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. --- src/conf_mode/interfaces_wwan.py | 6 ++++++ src/op_mode/connect_disconnect.py | 28 +++++++++++++++++++++++++--- src/services/vyos-netlinkd | 8 ++++++++ 3 files changed, 39 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/conf_mode/interfaces_wwan.py b/src/conf_mode/interfaces_wwan.py index 7e02d9d7a..dd5a1e8c3 100755 --- a/src/conf_mode/interfaces_wwan.py +++ b/src/conf_mode/interfaces_wwan.py @@ -30,6 +30,7 @@ from vyos.configverify import verify_mtu_ipv6 from vyos.ifconfig import WWANIf from vyos.utils.network import is_wwan_connected from vyos.utils.process import cmdl +from vyos.utils.wwan import clear_admin_disconnected from vyos.utils.wwan import connect_options from vyos.utils.wwan import modem_connect from vyos.utils.wwan import modem_disconnect @@ -117,6 +118,11 @@ def generate(wwan): return None def apply(wwan): + # A commit re-asserts the configured state, so it also takes the interface + # out of the administratively disconnected state that op-mode "disconnect + # interface wwanN" put it in. + clear_admin_disconnected(wwan['ifname']) + # ModemManager is required to dial WWAN connections - one instance is # required to serve all modems. Activate ModemManager on first invocation # of any WWAN interface. diff --git a/src/op_mode/connect_disconnect.py b/src/op_mode/connect_disconnect.py index 17cf9d7e0..9b43e4fd2 100755 --- a/src/op_mode/connect_disconnect.py +++ b/src/op_mode/connect_disconnect.py @@ -19,10 +19,13 @@ import argparse from psutil import process_iter +from vyos.ifconfig import WWANIf from vyos.utils.process import call from vyos.utils.commit import commit_in_progress from vyos.utils.network import is_wwan_connected -from vyos.utils.process import DEVNULL +from vyos.utils.wwan import clear_admin_disconnected +from vyos.utils.wwan import modem_disconnect +from vyos.utils.wwan import set_admin_disconnected def check_ppp_interface(interface): if not os.path.isfile(f'/etc/ppp/peers/{interface}'): @@ -52,6 +55,9 @@ def connect(interface): print(f'Interface {interface}: connecting...') call(f'systemctl restart ppp@{interface}.service') elif interface.startswith('wwan'): + # Take the interface out of the administratively disconnected state, so + # vyos-netlinkd re-dials it again should the session be lost later on + clear_admin_disconnected(interface) if is_wwan_connected(interface): print(f'Interface {interface}: already connected!') else: @@ -72,11 +78,27 @@ def disconnect(interface): print(f'Interface {interface}: disconnecting...') call(f'systemctl stop ppp@{interface}.service') elif interface.startswith('wwan'): + # Keep the interface down until it is connected again. vyos-netlinkd + # re-dials every configured WWAN interface that lost its session, and + # would otherwise undo this disconnect on its next reconcile pass. The + # marker is set even when the session is already down, so a disconnect + # issued while the modem happens to be searching for a network is not + # silently ignored. + set_admin_disconnected(interface) if not is_wwan_connected(interface): print(f'Interface {interface}: connection is already down') else: - modem = interface.lstrip('wwan') - call(f'mmcli --modem {modem} --simple-disconnect', stdout=DEVNULL) + print(f'Interface {interface}: disconnecting...') + modem_disconnect(interface, quiet=True) + + # Dropping the bearer does not touch the netdev - a cdc_mbim/qmi_wwan + # interface stays IFF_UP with carrier and an operstate of "unknown" + # once ModemManager is done, so it would keep reporting u/u and hold + # on to the address the DHCP client got for the session that just + # ended. Tear it down the same way conf_mode does for "disable". + w = WWANIf(interface) + if w.exists(interface): + w.remove() else: print(f'Unknown interface {interface}, cannot disconnect. Aborting!') diff --git a/src/services/vyos-netlinkd b/src/services/vyos-netlinkd index 5cce1ceff..4c91561d2 100755 --- a/src/services/vyos-netlinkd +++ b/src/services/vyos-netlinkd @@ -43,6 +43,7 @@ from vyos.utils.process import cmdl from vyos.utils.process import is_systemd_service_active from vyos.utils.process import stop_systemd_unit from vyos.utils.wwan import connect_options +from vyos.utils.wwan import is_admin_disconnected from vyos.utils.wwan import modem_connect from vyos.utils.wwan import modem_disconnect from vyos.utils.wwan import start_modem_manager @@ -370,6 +371,13 @@ def _reconcile_wwan() -> None: _wwan_clear_backoff(ifname) continue + # Do not undo an explicit op-mode "disconnect interface wwanN" - the + # session stays down until it is connected again or the configuration + # is re-applied by a commit. + if is_admin_disconnected(ifname): + _wwan_clear_backoff(ifname) + continue + if is_wwan_connected(ifname): _wwan_clear_backoff(ifname) continue -- cgit v1.2.3