summaryrefslogtreecommitdiff
path: root/src/op_mode
diff options
context:
space:
mode:
Diffstat (limited to 'src/op_mode')
-rwxr-xr-xsrc/op_mode/bgp.py170
-rwxr-xr-xsrc/op_mode/conntrack_sync.py219
-rwxr-xr-xsrc/op_mode/dynamic_dns.py16
-rwxr-xr-xsrc/op_mode/ipsec.py216
-rwxr-xr-xsrc/op_mode/show_vpn_ra.py56
-rwxr-xr-xsrc/op_mode/show_wwan.py8
6 files changed, 449 insertions, 236 deletions
diff --git a/src/op_mode/bgp.py b/src/op_mode/bgp.py
index 3f6d45dd7..af9ea788b 100755
--- a/src/op_mode/bgp.py
+++ b/src/op_mode/bgp.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2022 VyOS maintainers and contributors
+# Copyright (C) 2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -15,101 +15,133 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Purpose:
-# Displays bgp neighbors information.
-# Used by the "show bgp (vrf <tag>) ipv4|ipv6 neighbors" commands.
+# Displays BGP neighbors and tables information.
import re
import sys
import typing
-import jmespath
from jinja2 import Template
-from humps import decamelize
-
-from vyos.configquery import ConfigTreeQuery
import vyos.opmode
-ArgFamily = typing.Literal['inet', 'inet6']
-
frr_command_template = Template("""
-{% if family %}
- show bgp
- {{ 'vrf ' ~ vrf if vrf else '' }}
- {{ 'ipv6' if family == 'inet6' else 'ipv4'}}
- {{ 'neighbor ' ~ peer if peer else 'summary' }}
+show bgp
+
+{## VRF and family modifiers that may precede any options ##}
+
+{% if vrf %}
+ vrf {{vrf}}
+{% endif %}
+
+{% if family == "inet" %}
+ ipv4
+{% elif family == "inet6" %}
+ ipv6
+{% elif family == "l2vpn" %}
+ l2vpn evpn
+{% endif %}
+
+{% if family_modifier == "unicast" %}
+ unicast
+{% elif family_modifier == "multicast" %}
+ multicast
+{% elif family_modifier == "flowspec" %}
+ flowspec
+{% elif family_modifier == "vpn" %}
+ vpn
+{% endif %}
+
+{## Mutually exclusive query parameters ##}
+
+{# Network prefix #}
+{% if prefix %}
+ {{prefix}}
+
+ {% if longer_prefixes %}
+ longer-prefixes
+ {% elif best_path %}
+ bestpath
+ {% endif %}
{% endif %}
+{# Regex #}
+{% if regex %}
+ regex {{regex}}
+{% endif %}
+
+{## Raw modifier ##}
+
{% if raw %}
json
{% endif %}
""")
+ArgFamily = typing.Literal['inet', 'inet6', 'l2vpn']
+ArgFamilyModifier = typing.Literal['unicast', 'labeled_unicast', 'multicast', 'vpn', 'flowspec']
+
+def show_summary(raw: bool):
+ from vyos.util import cmd
+
+ if raw:
+ from json import loads
+
+ output = cmd(f"vtysh -c 'show bgp summary json'").strip()
-def _verify(func):
- """Decorator checks if BGP config exists
- BGP configuration can be present under vrf <tag>
- If we do npt get arg 'peer' then it can be 'bgp summary'
- """
- from functools import wraps
-
- @wraps(func)
- def _wrapper(*args, **kwargs):
- config = ConfigTreeQuery()
- afi = 'ipv6' if kwargs.get('family') == 'inet6' else 'ipv4'
- global_vrfs = ['all', 'default']
- peer = kwargs.get('peer')
- vrf = kwargs.get('vrf')
- unconf_message = f'BGP or neighbor is not configured'
- # Add option to check the specific neighbor if we have arg 'peer'
- peer_opt = f'neighbor {peer} address-family {afi}-unicast' if peer else ''
- vrf_opt = ''
- if vrf and vrf not in global_vrfs:
- vrf_opt = f'vrf name {vrf}'
- # Check if config does not exist
- if not config.exists(f'{vrf_opt} protocols bgp {peer_opt}'):
- raise vyos.opmode.UnconfiguredSubsystem(unconf_message)
- return func(*args, **kwargs)
-
- return _wrapper
-
-
-@_verify
-def show_neighbors(raw: bool,
- family: ArgFamily,
- peer: typing.Optional[str],
- vrf: typing.Optional[str]):
- kwargs = dict(locals())
- frr_command = frr_command_template.render(kwargs)
- frr_command = re.sub(r'\s+', ' ', frr_command)
+ # FRR 8.5 correctly returns an empty object when BGP is not running,
+ # we don't need to do anything special here
+ return loads(output)
+ else:
+ output = cmd(f"vtysh -c 'show bgp summary'")
+ return output
+def show_neighbors(raw: bool):
from vyos.util import cmd
- output = cmd(f"vtysh -c '{frr_command}'")
+ from vyos.utils.dict import dict_to_list
if raw:
from json import loads
- data = loads(output)
- # Get list of the peers
- peers = jmespath.search('*.peers | [0]', data)
- if peers:
- # Create new dict, delete old key 'peers'
- # add key 'peers' neighbors to the list
- list_peers = []
- new_dict = jmespath.search('* | [0]', data)
- if 'peers' in new_dict:
- new_dict.pop('peers')
-
- for neighbor, neighbor_options in peers.items():
- neighbor_options['neighbor'] = neighbor
- list_peers.append(neighbor_options)
- new_dict['peers'] = list_peers
- return decamelize(new_dict)
- data = jmespath.search('* | [0]', data)
- return decamelize(data)
+ output = cmd(f"vtysh -c 'show bgp neighbors json'").strip()
+ d = loads(output)
+ return dict_to_list(d, save_key_to="neighbor")
else:
+ output = cmd(f"vtysh -c 'show bgp neighbors'")
return output
+def show(raw: bool,
+ family: ArgFamily,
+ family_modifier: ArgFamilyModifier,
+ prefix: typing.Optional[str],
+ longer_prefixes: typing.Optional[bool],
+ best_path: typing.Optional[bool],
+ regex: typing.Optional[str],
+ vrf: typing.Optional[str]):
+ from vyos.utils.dict import dict_to_list
+
+ if (longer_prefixes or best_path) and (prefix is None):
+ raise ValueError("longer_prefixes and best_path can only be used when prefix is given")
+ elif (family == "l2vpn") and (family_modifier is not None):
+ raise ValueError("l2vpn family does not accept any modifiers")
+ else:
+ kwargs = dict(locals())
+
+ frr_command = frr_command_template.render(kwargs)
+ frr_command = re.sub(r'\s+', ' ', frr_command)
+
+ from vyos.util import cmd
+ output = cmd(f"vtysh -c '{frr_command}'")
+
+ if raw:
+ from json import loads
+ d = loads(output)
+ if not ("routes" in d):
+ raise vyos.opmode.InternalError("FRR returned a BGP table with no routes field")
+ d = d["routes"]
+ routes = dict_to_list(d, save_key_to="route_key")
+ return routes
+ else:
+ return output
if __name__ == '__main__':
try:
diff --git a/src/op_mode/conntrack_sync.py b/src/op_mode/conntrack_sync.py
index 54ecd6d0e..c3345a936 100755
--- a/src/op_mode/conntrack_sync.py
+++ b/src/op_mode/conntrack_sync.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021 VyOS maintainers and contributors
+# Copyright (C) 2022 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -15,9 +15,12 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
+import sys
import syslog
import xmltodict
+import vyos.opmode
+
from argparse import ArgumentParser
from vyos.configquery import CliShellApiConfigQuery
from vyos.configquery import ConfigTreeQuery
@@ -31,36 +34,23 @@ conntrackd_bin = '/usr/sbin/conntrackd'
conntrackd_config = '/run/conntrackd/conntrackd.conf'
failover_state_file = '/var/run/vyatta-conntrackd-failover-state'
-parser = ArgumentParser(description='Conntrack Sync')
-group = parser.add_mutually_exclusive_group()
-group.add_argument('--restart', help='Restart connection tracking synchronization service', action='store_true')
-group.add_argument('--reset-cache-internal', help='Reset internal cache', action='store_true')
-group.add_argument('--reset-cache-external', help='Reset external cache', action='store_true')
-group.add_argument('--show-internal', help='Show internal (main) tracking cache', action='store_true')
-group.add_argument('--show-external', help='Show external (main) tracking cache', action='store_true')
-group.add_argument('--show-internal-expect', help='Show internal (expect) tracking cache', action='store_true')
-group.add_argument('--show-external-expect', help='Show external (expect) tracking cache', action='store_true')
-group.add_argument('--show-statistics', help='Show connection syncing statistics', action='store_true')
-group.add_argument('--show-status', help='Show conntrack-sync status', action='store_true')
-
def is_configured():
""" Check if conntrack-sync service is configured """
config = CliShellApiConfigQuery()
if not config.exists(['service', 'conntrack-sync']):
- print('Service conntrackd-sync not configured!')
- exit(1)
+ raise vyos.opmode.UnconfiguredSubsystem("conntrack-sync is not configured!")
def send_bulk_update():
""" send bulk update of internal-cache to other systems """
tmp = run(f'{conntrackd_bin} -C {conntrackd_config} -B')
if tmp > 0:
- print('ERROR: failed to send bulk update to other conntrack-sync systems')
+ raise vyos.opmode.Error('Failed to send bulk update to other conntrack-sync systems')
def request_sync():
""" request resynchronization with other systems """
tmp = run(f'{conntrackd_bin} -C {conntrackd_config} -n')
if tmp > 0:
- print('ERROR: failed to request resynchronization of external cache')
+ raise vyos.opmode.Error('Failed to request resynchronization of external cache')
def flush_cache(direction):
""" flush conntrackd cache (internal or external) """
@@ -68,9 +58,9 @@ def flush_cache(direction):
raise ValueError()
tmp = run(f'{conntrackd_bin} -C {conntrackd_config} -f {direction}')
if tmp > 0:
- print('ERROR: failed to clear {direction} cache')
+ raise vyos.opmode.Error('Failed to clear {direction} cache')
-def xml_to_stdout(xml):
+def from_xml(raw, xml):
out = []
for line in xml.splitlines():
if line == '\n':
@@ -78,108 +68,131 @@ def xml_to_stdout(xml):
parsed = xmltodict.parse(line)
out.append(parsed)
- print(render_to_string('conntrackd/conntrackd.op-mode.j2', {'data' : out}))
-
-if __name__ == '__main__':
- args = parser.parse_args()
- syslog.openlog(ident='conntrack-tools', logoption=syslog.LOG_PID,
- facility=syslog.LOG_INFO)
+ if raw:
+ return out
+ else:
+ return render_to_string('conntrackd/conntrackd.op-mode.j2', {'data' : out})
+
+def restart():
+ is_configured()
+ if commit_in_progress():
+ raise vyos.opmode.CommitInProgress('Cannot restart conntrackd while a commit is in progress')
+
+ syslog.syslog('Restarting conntrack sync service...')
+ cmd('systemctl restart conntrackd.service')
+ # request resynchronization with other systems
+ request_sync()
+ # send bulk update of internal-cache to other systems
+ send_bulk_update()
+
+def reset_external_cache():
+ is_configured()
+ syslog.syslog('Resetting external cache of conntrack sync service...')
+
+ # flush the external cache
+ flush_cache('external')
+ # request resynchronization with other systems
+ request_sync()
+
+def reset_internal_cache():
+ is_configured()
+ syslog.syslog('Resetting internal cache of conntrack sync service...')
+ # flush the internal cache
+ flush_cache('internal')
+
+ # request resynchronization of internal cache with kernel conntrack table
+ tmp = run(f'{conntrackd_bin} -C {conntrackd_config} -R')
+ if tmp > 0:
+ print('ERROR: failed to resynchronize internal cache with kernel conntrack table')
- if args.restart:
- is_configured()
- if commit_in_progress():
- print('Cannot restart conntrackd while a commit is in progress')
- exit(1)
-
- syslog.syslog('Restarting conntrack sync service...')
- cmd('systemctl restart conntrackd.service')
- # request resynchronization with other systems
- request_sync()
- # send bulk update of internal-cache to other systems
- send_bulk_update()
-
- elif args.reset_cache_external:
- is_configured()
- syslog.syslog('Resetting external cache of conntrack sync service...')
+ # send bulk update of internal-cache to other systems
+ send_bulk_update()
- # flush the external cache
- flush_cache('external')
- # request resynchronization with other systems
- request_sync()
+def _show_cache(raw, opts):
+ is_configured()
+ out = cmd(f'{conntrackd_bin} -C {conntrackd_config} {opts} -x')
+ return from_xml(raw, out)
- elif args.reset_cache_internal:
- is_configured()
- syslog.syslog('Resetting internal cache of conntrack sync service...')
- # flush the internal cache
- flush_cache('internal')
+def show_external_cache(raw: bool):
+ opts = '-e ct'
+ return _show_cache(raw, opts)
- # request resynchronization of internal cache with kernel conntrack table
- tmp = run(f'{conntrackd_bin} -C {conntrackd_config} -R')
- if tmp > 0:
- print('ERROR: failed to resynchronize internal cache with kernel conntrack table')
+def show_external_expect(raw: bool):
+ opts = '-e expect'
+ return _show_cache(raw, opts)
- # send bulk update of internal-cache to other systems
- send_bulk_update()
+def show_internal_cache(raw: bool):
+ opts = '-i ct'
+ return _show_cache(raw, opts)
- elif args.show_external or args.show_internal or args.show_external_expect or args.show_internal_expect:
- is_configured()
- opt = ''
- if args.show_external:
- opt = '-e ct'
- elif args.show_external_expect:
- opt = '-e expect'
- elif args.show_internal:
- opt = '-i ct'
- elif args.show_internal_expect:
- opt = '-i expect'
-
- if args.show_external or args.show_internal:
- print('Main Table Entries:')
- else:
- print('Expect Table Entries:')
- out = cmd(f'sudo {conntrackd_bin} -C {conntrackd_config} {opt} -x')
- xml_to_stdout(out)
+def show_internal_expect(raw: bool):
+ opts = '-i expect'
+ return _show_cache(raw, opts)
- elif args.show_statistics:
+def show_statistics(raw: bool):
+ if raw:
+ raise vyos.opmode.UnsupportedOperation("Machine-readable conntrack-sync statistics are not available yet")
+ else:
is_configured()
config = ConfigTreeQuery()
print('\nMain Table Statistics:\n')
- call(f'sudo {conntrackd_bin} -C {conntrackd_config} -s')
+ call(f'{conntrackd_bin} -C {conntrackd_config} -s')
print()
if config.exists(['service', 'conntrack-sync', 'expect-sync']):
print('\nExpect Table Statistics:\n')
- call(f'sudo {conntrackd_bin} -C {conntrackd_config} -s exp')
+ call(f'{conntrackd_bin} -C {conntrackd_config} -s exp')
print()
- elif args.show_status:
- is_configured()
- config = ConfigTreeQuery()
- ct_sync_intf = config.list_nodes(['service', 'conntrack-sync', 'interface'])
- ct_sync_intf = ', '.join(ct_sync_intf)
- failover_state = "no transition yet!"
- expect_sync_protocols = "disabled"
-
- if config.exists(['service', 'conntrack-sync', 'failover-mechanism', 'vrrp']):
- failover_mechanism = "vrrp"
- vrrp_sync_grp = config.value(['service', 'conntrack-sync', 'failover-mechanism', 'vrrp', 'sync-group'])
-
- if os.path.isfile(failover_state_file):
- with open(failover_state_file, "r") as f:
- failover_state = f.readline()
-
- if config.exists(['service', 'conntrack-sync', 'expect-sync']):
- expect_sync_protocols = config.values(['service', 'conntrack-sync', 'expect-sync'])
- if 'all' in expect_sync_protocols:
- expect_sync_protocols = ["ftp", "sip", "h323", "nfs", "sqlnet"]
+def show_status(raw: bool):
+ is_configured()
+ config = ConfigTreeQuery()
+ ct_sync_intf = config.list_nodes(['service', 'conntrack-sync', 'interface'])
+ ct_sync_intf = ', '.join(ct_sync_intf)
+ failover_state = "no transition yet!"
+ expect_sync_protocols = []
+
+ if config.exists(['service', 'conntrack-sync', 'failover-mechanism', 'vrrp']):
+ failover_mechanism = "vrrp"
+ vrrp_sync_grp = config.value(['service', 'conntrack-sync', 'failover-mechanism', 'vrrp', 'sync-group'])
+
+ if os.path.isfile(failover_state_file):
+ with open(failover_state_file, "r") as f:
+ failover_state = f.readline()
+
+ if config.exists(['service', 'conntrack-sync', 'expect-sync']):
+ expect_sync_protocols = config.values(['service', 'conntrack-sync', 'expect-sync'])
+ if 'all' in expect_sync_protocols:
+ expect_sync_protocols = ["ftp", "sip", "h323", "nfs", "sqlnet"]
+
+ if raw:
+ status_data = {
+ "sync_interface": ct_sync_intf,
+ "failover_mechanism": failover_mechanism,
+ "sync_group": vrrp_sync_grp,
+ "last_transition": failover_state,
+ "sync_protocols": expect_sync_protocols
+ }
+
+ return status_data
+ else:
+ if expect_sync_protocols:
expect_sync_protocols = ', '.join(expect_sync_protocols)
-
+ else:
+ expect_sync_protocols = "disabled"
show_status = (f'\nsync-interface : {ct_sync_intf}\n'
f'failover-mechanism : {failover_mechanism} [sync-group {vrrp_sync_grp}]\n'
- f'last state transition : {failover_state}'
+ f'last state transition : {failover_state}\n'
f'ExpectationSync : {expect_sync_protocols}')
- print(show_status)
+ return show_status
- else:
- parser.print_help()
- exit(1)
+if __name__ == '__main__':
+ syslog.openlog(ident='conntrack-tools', logoption=syslog.LOG_PID, facility=syslog.LOG_INFO)
+
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except (ValueError, vyos.opmode.Error) as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/op_mode/dynamic_dns.py b/src/op_mode/dynamic_dns.py
index 2cba33cc8..d41a74db3 100755
--- a/src/op_mode/dynamic_dns.py
+++ b/src/op_mode/dynamic_dns.py
@@ -21,6 +21,7 @@ import time
from tabulate import tabulate
from vyos.config import Config
+from vyos.template import is_ipv4, is_ipv6
from vyos.util import call
cache_file = r'/run/ddclient/ddclient.cache'
@@ -46,7 +47,7 @@ def _get_formatted_host_records(host_data):
def show_status():
- # A ddclient status file must not always exist
+ # A ddclient status file might not always exist
if not os.path.exists(cache_file):
sys.exit(0)
@@ -62,9 +63,20 @@ def show_status():
# we pick up the ones we are interested in
for kvraw in line.split(' ')[0].split(','):
k, v = kvraw.split('=')
- if k in columns.keys():
+ if k in list(columns.keys()) + ['ip', 'status']: # ip and status are legacy keys
props[k] = v
+ # Extract IPv4 and IPv6 address and status from legacy keys
+ # Dual-stack isn't supported in legacy format, 'ip' and 'status' are for one of IPv4 or IPv6
+ if 'ip' in props:
+ if is_ipv4(props['ip']):
+ props['ipv4'] = props['ip']
+ props['status-ipv4'] = props['status']
+ elif is_ipv6(props['ip']):
+ props['ipv6'] = props['ip']
+ props['status-ipv6'] = props['status']
+ del props['ip']
+
# Convert mtime to human readable format
if 'mtime' in props:
props['mtime'] = time.strftime(
diff --git a/src/op_mode/ipsec.py b/src/op_mode/ipsec.py
index 7f4fb72e5..db4948d7a 100755
--- a/src/op_mode/ipsec.py
+++ b/src/op_mode/ipsec.py
@@ -13,7 +13,6 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
import re
import sys
import typing
@@ -24,6 +23,7 @@ from tabulate import tabulate
from vyos.util import convert_data
from vyos.util import seconds_to_human
+from vyos.util import cmd
from vyos.configquery import ConfigTreeQuery
import vyos.opmode
@@ -46,6 +46,25 @@ def _get_raw_data_sas():
except (vyos.ipsec.ViciInitiateError) as err:
raise vyos.opmode.UnconfiguredSubsystem(err)
+
+def _get_output_swanctl_sas_from_list(ra_output_list: list) -> str:
+ """
+ Template for output for VICI
+ Inserts \n after each IKE SA
+ :param ra_output_list: IKE SAs list
+ :type ra_output_list: list
+ :return: formatted string
+ :rtype: str
+ """
+ output = '';
+ for sa_val in ra_output_list:
+ for sa in sa_val.values():
+ swanctl_output: str = cmd(
+ f'sudo swanctl -l --ike-id {sa["uniqueid"]}')
+ output = f'{output}{swanctl_output}\n\n'
+ return output
+
+
def _get_formatted_output_sas(sas):
sa_data = []
for sa in sas:
@@ -444,6 +463,7 @@ def reset_peer(peer: str, tunnel: typing.Optional[str] = None):
except (vyos.ipsec.ViciCommandError) as err:
raise vyos.opmode.IncorrectValue(err)
+
def reset_all_peers():
sitetosite_list = _get_all_sitetosite_peers_name_list()
if sitetosite_list:
@@ -457,6 +477,7 @@ def reset_all_peers():
raise vyos.opmode.UnconfiguredSubsystem(
'VPN IPSec site-to-site is not configured, aborting')
+
def _get_ra_session_list_by_username(username: typing.Optional[str] = None):
"""
Return list of remote-access IKE_SAs uniqueids
@@ -466,15 +487,15 @@ def _get_ra_session_list_by_username(username: typing.Optional[str] = None):
:rtype:
"""
list_sa_id = []
- sa_list = vyos.ipsec.get_vici_sas()
+ sa_list = _get_raw_data_sas()
for sa_val in sa_list:
for sa in sa_val.values():
if 'remote-eap-id' in sa:
if username:
- if username == sa['remote-eap-id'].decode():
- list_sa_id.append(sa['uniqueid'].decode())
+ if username == sa['remote-eap-id']:
+ list_sa_id.append(sa['uniqueid'])
else:
- list_sa_id.append(sa['uniqueid'].decode())
+ list_sa_id.append(sa['uniqueid'])
return list_sa_id
@@ -556,6 +577,24 @@ def show_sa(raw: bool):
return _get_formatted_output_sas(sa_data)
+def _get_output_sas_detail(ra_output_list: list) -> str:
+ """
+ Formate all IKE SAs detail output
+ :param ra_output_list: IKE SAs list
+ :type ra_output_list: list
+ :return: formatted RA IKE SAs detail output
+ :rtype: str
+ """
+ return _get_output_swanctl_sas_from_list(ra_output_list)
+
+
+def show_sa_detail(raw: bool):
+ sa_data = _get_raw_data_sas()
+ if raw:
+ return sa_data
+ return _get_output_sas_detail(sa_data)
+
+
def show_connections(raw: bool):
list_conns = _get_convert_data_connections()
list_sas = _get_raw_data_sas()
@@ -573,6 +612,173 @@ def show_connections_summary(raw: bool):
return _get_raw_connections_summary(list_conns, list_sas)
+def _get_ra_sessions(username: typing.Optional[str] = None) -> list:
+ """
+ Return list of remote-access IKE_SAs from VICI by username.
+ If username unspecified, return all remote-access IKE_SAs
+ :param username: Username of RA connection
+ :type username: str
+ :return: list of ra remote-access IKE_SAs
+ :rtype: list
+ """
+ list_sa = []
+ sa_list = _get_raw_data_sas()
+ for conn in sa_list:
+ for sa in conn.values():
+ if 'remote-eap-id' in sa:
+ if username:
+ if username == sa['remote-eap-id']:
+ list_sa.append(conn)
+ else:
+ list_sa.append(conn)
+ return list_sa
+
+
+def _filter_ikesas(list_sa: list, filter_key: str, filter_value: str) -> list:
+ """
+ Filter IKE SAs by specifice key
+ :param list_sa: list of IKE SAs
+ :type list_sa: list
+ :param filter_key: Filter Key
+ :type filter_key: str
+ :param filter_value: Filter Value
+ :type filter_value: str
+ :return: Filtered list of IKE SAs
+ :rtype: list
+ """
+ filtered_sa_list = []
+ for conn in list_sa:
+ for sa in conn.values():
+ if sa[filter_key] and sa[filter_key] == filter_value:
+ filtered_sa_list.append(conn)
+ return filtered_sa_list
+
+
+def _get_last_installed_childsa(sa: dict) -> str:
+ """
+ Return name of last installed active Child SA
+ :param sa: Dictionary with Child SAs
+ :type sa: dict
+ :return: Name of the Last installed active Child SA
+ :rtype: str
+ """
+ child_sa_name = None
+ child_sa_id = 0
+ for sa_name, child_sa in sa['child-sas'].items():
+ if child_sa['state'] == 'INSTALLED':
+ if child_sa_id == 0 or int(child_sa['uniqueid']) > child_sa_id:
+ child_sa_id = int(child_sa['uniqueid'])
+ child_sa_name = sa_name
+ return child_sa_name
+
+
+def _get_formatted_ike_proposal(sa: dict) -> str:
+ """
+ Return IKE proposal string in format
+ EncrALG-EncrKeySize/PFR/HASH/DH-GROUP
+ :param sa: IKE SA
+ :type sa: dict
+ :return: IKE proposal string
+ :rtype: str
+ """
+ proposal = ''
+ proposal = f'{proposal}{sa["encr-alg"]}' if 'encr-alg' in sa else proposal
+ proposal = f'{proposal}-{sa["encr-keysize"]}' if 'encr-keysize' in sa else proposal
+ proposal = f'{proposal}/{sa["prf-alg"]}' if 'prf-alg' in sa else proposal
+ proposal = f'{proposal}/{sa["integ-alg"]}' if 'integ-alg' in sa else proposal
+ proposal = f'{proposal}/{sa["dh-group"]}' if 'dh-group' in sa else proposal
+ return proposal
+
+
+def _get_formatted_ipsec_proposal(sa: dict) -> str:
+ """
+ Return IPSec proposal string in format
+ Protocol: EncrALG-EncrKeySize/HASH/PFS
+ :param sa: Child SA
+ :type sa: dict
+ :return: IPSec proposal string
+ :rtype: str
+ """
+ proposal = ''
+ proposal = f'{proposal}{sa["protocol"]}' if 'protocol' in sa else proposal
+ proposal = f'{proposal}:{sa["encr-alg"]}' if 'encr-alg' in sa else proposal
+ proposal = f'{proposal}-{sa["encr-keysize"]}' if 'encr-keysize' in sa else proposal
+ proposal = f'{proposal}/{sa["integ-alg"]}' if 'integ-alg' in sa else proposal
+ proposal = f'{proposal}/{sa["dh-group"]}' if 'dh-group' in sa else proposal
+ return proposal
+
+
+def _get_output_ra_sas_detail(ra_output_list: list) -> str:
+ """
+ Formate RA IKE SAs detail output
+ :param ra_output_list: IKE SAs list
+ :type ra_output_list: list
+ :return: formatted RA IKE SAs detail output
+ :rtype: str
+ """
+ return _get_output_swanctl_sas_from_list(ra_output_list)
+
+
+def _get_formatted_output_ra_summary(ra_output_list: list):
+ sa_data = []
+ for conn in ra_output_list:
+ for sa in conn.values():
+ sa_id = sa['uniqueid'] if 'uniqueid' in sa else ''
+ sa_username = sa['remote-eap-id'] if 'remote-eap-id' in sa else ''
+ sa_protocol = f'IKEv{sa["version"]}' if 'version' in sa else ''
+ sa_remotehost = sa['remote-host'] if 'remote-host' in sa else ''
+ sa_remoteid = sa['remote-id'] if 'remote-id' in sa else ''
+ sa_ike_proposal = _get_formatted_ike_proposal(sa)
+ sa_tunnel_ip = sa['remote-vips']
+ child_sa_key = _get_last_installed_childsa(sa)
+ if child_sa_key:
+ child_sa = sa['child-sas'][child_sa_key]
+ sa_ipsec_proposal = _get_formatted_ipsec_proposal(child_sa)
+ sa_state = "UP"
+ sa_uptime = seconds_to_human(sa['established'])
+ else:
+ sa_ipsec_proposal = ''
+ sa_state = "DOWN"
+ sa_uptime = ''
+ sa_data.append(
+ [sa_id, sa_username, sa_protocol, sa_state, sa_uptime,
+ sa_tunnel_ip,
+ sa_remotehost, sa_remoteid, sa_ike_proposal,
+ sa_ipsec_proposal])
+
+ headers = ["Connection ID", "Username", "Protocol", "State", "Uptime",
+ "Tunnel IP", "Remote Host", "Remote ID", "IKE Proposal",
+ "IPSec Proposal"]
+ sa_data = sorted(sa_data, key=_alphanum_key)
+ output = tabulate(sa_data, headers)
+ return output
+
+
+def show_ra_detail(raw: bool, username: typing.Optional[str] = None,
+ conn_id: typing.Optional[str] = None):
+ list_sa: list = _get_ra_sessions()
+ if username:
+ list_sa = _filter_ikesas(list_sa, 'remote-eap-id', username)
+ elif conn_id:
+ list_sa = _filter_ikesas(list_sa, 'uniqueid', conn_id)
+ if not list_sa:
+ raise vyos.opmode.IncorrectValue(
+ f'No active connections found, aborting')
+ if raw:
+ return list_sa
+ return _get_output_ra_sas_detail(list_sa)
+
+
+def show_ra_summary(raw: bool):
+ list_sa: list = _get_ra_sessions()
+ if not list_sa:
+ raise vyos.opmode.IncorrectValue(
+ f'No active connections found, aborting')
+ if raw:
+ return list_sa
+ return _get_formatted_output_ra_summary(list_sa)
+
+
if __name__ == '__main__':
try:
res = vyos.opmode.run(sys.modules[__name__])
diff --git a/src/op_mode/show_vpn_ra.py b/src/op_mode/show_vpn_ra.py
deleted file mode 100755
index 73688c4ea..000000000
--- a/src/op_mode/show_vpn_ra.py
+++ /dev/null
@@ -1,56 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2019 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import os
-import sys
-import re
-
-from vyos.util import popen
-
-# chech connection to pptp and l2tp daemon
-def get_sessions():
- absent_pptp = False
- absent_l2tp = False
- pptp_cmd = "accel-cmd -p 2003 show sessions"
- l2tp_cmd = "accel-cmd -p 2004 show sessions"
- err_pattern = "^Connection.+failed$"
- # This value for chack only output header without sessions.
- len_def_header = 170
-
- # Check pptp
- output, err = popen(pptp_cmd, decode='utf-8')
- if not err and len(output) > len_def_header and not re.search(err_pattern, output):
- print(output)
- else:
- absent_pptp = True
-
- # Check l2tp
- output, err = popen(l2tp_cmd, decode='utf-8')
- if not err and len(output) > len_def_header and not re.search(err_pattern, output):
- print(output)
- else:
- absent_l2tp = True
-
- if absent_l2tp and absent_pptp:
- print("No active remote access VPN sessions")
-
-
-def main():
- get_sessions()
-
-
-if __name__ == '__main__':
- main()
diff --git a/src/op_mode/show_wwan.py b/src/op_mode/show_wwan.py
index 529b5bd0f..eb601a456 100755
--- a/src/op_mode/show_wwan.py
+++ b/src/op_mode/show_wwan.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021 VyOS maintainers and contributors
+# Copyright (C) 2021-2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -17,6 +17,7 @@
import argparse
from sys import exit
+from vyos.configquery import ConfigTreeQuery
from vyos.util import cmd
parser = argparse.ArgumentParser()
@@ -49,6 +50,11 @@ def qmi_cmd(device, command, silent=False):
if __name__ == '__main__':
args = parser.parse_args()
+ tmp = ConfigTreeQuery()
+ if not tmp.exists(['interfaces', 'wwan', args.interface]):
+ print(f'Interface "{args.interface}" unconfigured!')
+ exit(1)
+
# remove the WWAN prefix from the interface, required for the CDC interface
if_num = args.interface.replace('wwan','')
cdc = f'/dev/cdc-wdm{if_num}'