summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorChristian Breunig <christian@breunig.cc>2026-09-28 21:18:12 +0200
committerGitHub <noreply@github.com>2026-09-28 21:18:12 +0200
commit504c97e7c661de8ea771a8daa7a3c7d6c8e5d86b (patch)
treed7d3d0120fa74465e34ce2948c55f8fc249d6165 /src
parent39528a2d74fd1f18433cc4cbec301f869181e5ec (diff)
parent9b5110a868ef413fdc1303f834220d99af8ceead (diff)
downloadvyos-1x-504c97e7c661de8ea771a8daa7a3c7d6c8e5d86b.tar.gz
vyos-1x-504c97e7c661de8ea771a8daa7a3c7d6c8e5d86b.zip
Merge pull request #5503 from c-po/T9354
op-mode: T9354: replace "reset connection" with "reconnect interface"
Diffstat (limited to 'src')
-rwxr-xr-xsrc/op_mode/connect_disconnect.py60
-rw-r--r--src/services/api/rest/models.py14
-rw-r--r--src/services/api/rest/routers.py23
3 files changed, 95 insertions, 2 deletions
diff --git a/src/op_mode/connect_disconnect.py b/src/op_mode/connect_disconnect.py
index 17cf9d7e0..e30f512a0 100755
--- a/src/op_mode/connect_disconnect.py
+++ b/src/op_mode/connect_disconnect.py
@@ -15,15 +15,22 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
+import re
import argparse
+from argparse import SUPPRESS
from psutil import process_iter
from vyos.utils.process import call
+from vyos.utils.process import rc_cmd
from vyos.utils.commit import commit_in_progress
from vyos.utils.network import is_wwan_connected
from vyos.utils.process import DEVNULL
+# Dialer interfaces we support - also sanitizes the name before it becomes
+# part of a systemd-run(1) command line
+dialer_interface_re = r'(pppoe|sstpc|wwan)\d+'
+
def check_ppp_interface(interface):
if not os.path.isfile(f'/etc/ppp/peers/{interface}'):
print(f'Interface {interface} does not exist!')
@@ -80,6 +87,51 @@ def disconnect(interface):
else:
print(f'Unknown interface {interface}, cannot disconnect. Aborting!')
+def reconnect(interface):
+ """ Reconnect dialer interface """
+
+ if interface.startswith('pppoe') or interface.startswith('sstpc'):
+ check_ppp_interface(interface)
+ # Restarting the service hangs up and dials again in one operation,
+ # leaving no window in which we could be interrupted with the link down
+ print(f'Interface {interface}: reconnecting...')
+ call(f'systemctl restart ppp@{interface}.service')
+ elif interface.startswith('wwan'):
+ # The modem must be disconnected before it can be dialed again
+ disconnect(interface)
+ connect(interface)
+ else:
+ print(f'Unknown interface {interface}, cannot reconnect. Aborting!')
+
+def reconnect_detached(interface):
+ """ Reconnect dialer interface from a transient systemd unit.
+
+ A reconnect is usually issued via the very interface being reconnected.
+ Once the link drops, the calling session dies and this process would be
+ killed by SIGHUP - or SIGPIPE on the next write to the dead terminal -
+ before the interface is dialed again, leaving the system unreachable. """
+
+ if not re.fullmatch(dialer_interface_re, interface):
+ print(f'Unknown interface {interface}, cannot reconnect. Aborting!')
+ exit(1)
+
+ unit = f'vyos-reconnect-{interface}'
+ command = (
+ f'systemd-run --quiet --collect --unit={unit} '
+ f'{os.path.abspath(__file__)} --reconnect --detached '
+ f'--interface {interface}'
+ )
+
+ rc, output = rc_cmd(command)
+ if rc != 0:
+ print(f'Interface {interface}: cannot reconnect: {output}')
+ exit(1)
+
+ print(
+ f'Interface {interface}: reconnecting in the background, this session '
+ f'may be disconnected. Progress is logged to journal unit {unit}.'
+ )
+
def main():
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
@@ -87,6 +139,8 @@ def main():
group.add_argument("--disconnect", help="Take down connection-oriented network interface", action="store_true")
group.add_argument("--reconnect", help="Reconnect connection-oriented network interface", action="store_true")
parser.add_argument("--interface", help="Interface name", action="store", required=True)
+ # Set only when we re-launch ourself detached from the calling session
+ parser.add_argument('--detached', action='store_true', help=SUPPRESS)
args = parser.parse_args()
# Disallow connecting interfaces while their configuration might be changing
@@ -100,8 +154,10 @@ def main():
elif args.disconnect:
disconnect(args.interface)
elif args.reconnect:
- disconnect(args.interface)
- connect(args.interface)
+ if args.detached:
+ reconnect(args.interface)
+ else:
+ reconnect_detached(args.interface)
else:
parser.print_help()
diff --git a/src/services/api/rest/models.py b/src/services/api/rest/models.py
index 8321ad2e3..8762c3bff 100644
--- a/src/services/api/rest/models.py
+++ b/src/services/api/rest/models.py
@@ -291,6 +291,20 @@ class ResetModel(ApiModel):
}
+class ReconnectModel(ApiModel):
+ op: StrictStr
+ path: List[StrictStr]
+
+ class Config:
+ json_schema_extra = {
+ 'example': {
+ 'key': 'id_key',
+ 'op': 'reconnect',
+ 'path': ['op', 'mode', 'path'],
+ }
+ }
+
+
class PoweroffModel(ApiModel):
op: StrictStr
path: List[StrictStr]
diff --git a/src/services/api/rest/routers.py b/src/services/api/rest/routers.py
index 1808e5a5b..c2aa033bc 100644
--- a/src/services/api/rest/routers.py
+++ b/src/services/api/rest/routers.py
@@ -74,6 +74,7 @@ from .models import GenerateModel
from .models import ShowModel
from .models import RebootModel
from .models import ResetModel
+from .models import ReconnectModel
from .models import RenewModel
from .models import ImportPkiModel
from .models import PingModel
@@ -934,6 +935,28 @@ def reset_op(data: ResetModel):
return success(res)
+@router.post('/reconnect')
+def reconnect_op(data: ReconnectModel):
+ state = SessionState()
+ session = state.session
+
+ op = data.op
+ path = data.path
+
+ try:
+ if op == 'reconnect':
+ res = session.reconnect(path)
+ else:
+ return error(400, f"'{op}' is not a valid operation")
+ except ConfigSessionError as e:
+ return error(400, str(e))
+ except Exception:
+ LOG.critical(traceback.format_exc())
+ return error(500, 'An internal error occurred. Check the logs for details.')
+
+ return success(res)
+
+
@router.post('/import-pki')
def import_pki(data: ImportPkiModel):
# pylint: disable=consider-using-with