summaryrefslogtreecommitdiff
path: root/src/op_mode
diff options
context:
space:
mode:
Diffstat (limited to 'src/op_mode')
-rwxr-xr-xsrc/op_mode/accelppp.py44
-rwxr-xr-xsrc/op_mode/bgp.py169
-rwxr-xr-xsrc/op_mode/conntrack.py4
-rwxr-xr-xsrc/op_mode/conntrack_sync.py219
-rwxr-xr-xsrc/op_mode/dhcp.py38
-rwxr-xr-xsrc/op_mode/dns.py4
-rwxr-xr-xsrc/op_mode/dynamic_dns.py92
-rwxr-xr-xsrc/op_mode/generate_public_key_command.py59
-rwxr-xr-xsrc/op_mode/interfaces.py52
-rwxr-xr-xsrc/op_mode/ipsec.py438
-rwxr-xr-xsrc/op_mode/nat.py10
-rwxr-xr-xsrc/op_mode/neighbor.py8
-rwxr-xr-xsrc/op_mode/nhrp.py101
-rwxr-xr-xsrc/op_mode/openvpn.py66
-rwxr-xr-xsrc/op_mode/pki.py3
-rwxr-xr-xsrc/op_mode/reset_vpn.py75
-rwxr-xr-xsrc/op_mode/restart_frr.py2
-rwxr-xr-xsrc/op_mode/route.py6
-rwxr-xr-xsrc/op_mode/sflow.py108
-rwxr-xr-xsrc/op_mode/show_interfaces.py310
-rwxr-xr-xsrc/op_mode/show_openconnect_otp.py2
-rw-r--r--src/op_mode/show_techsupport_report.py303
-rwxr-xr-xsrc/op_mode/show_vpn_ra.py56
-rwxr-xr-xsrc/op_mode/show_wwan.py8
-rwxr-xr-xsrc/op_mode/vpn_ipsec.py61
25 files changed, 1405 insertions, 833 deletions
diff --git a/src/op_mode/accelppp.py b/src/op_mode/accelppp.py
index 2fd045dc3..00de45fc8 100755
--- a/src/op_mode/accelppp.py
+++ b/src/op_mode/accelppp.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2022 VyOS maintainers and contributors
+# Copyright (C) 2022-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
@@ -27,34 +27,56 @@ from vyos.util import rc_cmd
accel_dict = {
'ipoe': {
'port': 2002,
- 'path': 'service ipoe-server'
+ 'path': 'service ipoe-server',
+ 'base_path': 'service ipoe-server'
},
'pppoe': {
'port': 2001,
- 'path': 'service pppoe-server'
+ 'path': 'service pppoe-server',
+ 'base_path': 'service pppoe-server'
},
'pptp': {
'port': 2003,
- 'path': 'vpn pptp'
+ 'path': 'vpn pptp',
+ 'base_path': 'vpn pptp'
},
'l2tp': {
'port': 2004,
- 'path': 'vpn l2tp'
+ 'path': 'vpn l2tp',
+ 'base_path': 'vpn l2tp remote-access'
},
'sstp': {
'port': 2005,
- 'path': 'vpn sstp'
+ 'path': 'vpn sstp',
+ 'base_path': 'vpn sstp'
}
}
-def _get_raw_statistics(accel_output, pattern):
- return vyos.accel_ppp.get_server_statistics(accel_output, pattern, sep=':')
+def _get_config_settings(protocol):
+ '''Get config dict from VyOS configuration'''
+ conf = ConfigTreeQuery()
+ base_path = accel_dict[protocol]['base_path']
+ data = conf.get_config_dict(base_path,
+ key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
+ if conf.exists(f'{base_path} authentication local-users'):
+ # Delete sensitive data
+ del data['authentication']['local_users']
+ return {'config_option': data}
+
+
+def _get_raw_statistics(accel_output, pattern, protocol):
+ return {
+ **vyos.accel_ppp.get_server_statistics(accel_output, pattern, sep=':'),
+ **_get_config_settings(protocol)
+ }
def _get_raw_sessions(port):
- cmd_options = 'show sessions ifname,username,ip,ip6,ip6-dp,type,state,' \
- 'uptime-raw,calling-sid,called-sid,sid,comp,rx-bytes-raw,' \
+ cmd_options = 'show sessions ifname,username,ip,ip6,ip6-dp,type,rate-limit,' \
+ 'state,uptime-raw,calling-sid,called-sid,sid,comp,rx-bytes-raw,' \
'tx-bytes-raw,rx-pkts,tx-pkts'
output = vyos.accel_ppp.accel_cmd(port, cmd_options)
parsed_data: list[dict[str, str]] = vyos.accel_ppp.accel_out_parse(
@@ -103,7 +125,7 @@ def show_statistics(raw: bool, protocol: str):
rc, output = rc_cmd(f'/usr/bin/accel-cmd -p {port} show stat')
if raw:
- return _get_raw_statistics(output, pattern)
+ return _get_raw_statistics(output, pattern, protocol)
return output
diff --git a/src/op_mode/bgp.py b/src/op_mode/bgp.py
index 23001a9d7..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,100 +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
-
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: str,
- 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.py b/src/op_mode/conntrack.py
index df213cc5a..ea7c4c208 100755
--- a/src/op_mode/conntrack.py
+++ b/src/op_mode/conntrack.py
@@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
+import typing
import xmltodict
from tabulate import tabulate
@@ -23,6 +24,7 @@ from vyos.util import run
import vyos.opmode
+ArgFamily = typing.Literal['inet', 'inet6']
def _get_xml_data(family):
"""
@@ -126,7 +128,7 @@ def get_formatted_output(dict_data):
return output
-def show(raw: bool, family: str):
+def show(raw: bool, family: ArgFamily):
family = 'ipv6' if family == 'inet6' else 'ipv4'
conntrack_data = _get_raw_data(family)
if raw:
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/dhcp.py b/src/op_mode/dhcp.py
index b9e6e7bc9..fe7f252ba 100755
--- a/src/op_mode/dhcp.py
+++ b/src/op_mode/dhcp.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2022 VyOS maintainers and contributors
+# Copyright (C) 2022-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
@@ -36,6 +36,9 @@ lease_valid_states = ['all', 'active', 'free', 'expired', 'released', 'abandoned
sort_valid_inet = ['end', 'mac', 'hostname', 'ip', 'pool', 'remaining', 'start', 'state']
sort_valid_inet6 = ['end', 'iaid_duid', 'ip', 'last_communication', 'pool', 'remaining', 'state', 'type']
+ArgFamily = typing.Literal['inet', 'inet6']
+ArgState = typing.Literal['all', 'active', 'free', 'expired', 'released', 'abandoned', 'reset', 'backup']
+
def _utc_to_local(utc_dt):
return datetime.fromtimestamp((datetime.fromtimestamp(utc_dt) - datetime(1970, 1, 1)).total_seconds())
@@ -82,7 +85,7 @@ def _get_raw_server_leases(family='inet', pool=None, sorted=None, state=[]) -> l
data_lease['ip'] = lease.ip
data_lease['state'] = lease.binding_state
data_lease['pool'] = lease.sets.get('shared-networkname', '')
- data_lease['end'] = lease.end.timestamp()
+ data_lease['end'] = lease.end.timestamp() if lease.end else None
if family == 'inet':
data_lease['mac'] = lease.ethernet
@@ -95,17 +98,18 @@ def _get_raw_server_leases(family='inet', pool=None, sorted=None, state=[]) -> l
lease_types_long = {'na': 'non-temporary', 'ta': 'temporary', 'pd': 'prefix delegation'}
data_lease['type'] = lease_types_long[lease.type]
- data_lease['remaining'] = lease.end - datetime.utcnow()
+ data_lease['remaining'] = '-'
- if data_lease['remaining'].days >= 0:
- # substraction gives us a timedelta object which can't be formatted with strftime
- # so we use str(), split gets rid of the microseconds
- data_lease['remaining'] = str(data_lease["remaining"]).split('.')[0]
- else:
- data_lease['remaining'] = ''
+ if lease.end:
+ data_lease['remaining'] = lease.end - datetime.utcnow()
+
+ if data_lease['remaining'].days >= 0:
+ # substraction gives us a timedelta object which can't be formatted with strftime
+ # so we use str(), split gets rid of the microseconds
+ data_lease['remaining'] = str(data_lease["remaining"]).split('.')[0]
# Do not add old leases
- if data_lease['remaining'] != '' and data_lease['pool'] in pool:
+ if data_lease['remaining'] != '' and data_lease['pool'] in pool and data_lease['state'] != 'free':
if not state or data_lease['state'] in state:
data.append(data_lease)
@@ -137,7 +141,7 @@ def _get_formatted_server_leases(raw_data, family='inet'):
start = lease.get('start')
start = _utc_to_local(start).strftime('%Y/%m/%d %H:%M:%S')
end = lease.get('end')
- end = _utc_to_local(end).strftime('%Y/%m/%d %H:%M:%S')
+ end = _utc_to_local(end).strftime('%Y/%m/%d %H:%M:%S') if end else '-'
remain = lease.get('remaining')
pool = lease.get('pool')
hostname = lease.get('hostname')
@@ -248,7 +252,7 @@ def _verify(func):
@_verify
-def show_pool_statistics(raw: bool, family: str, pool: typing.Optional[str]):
+def show_pool_statistics(raw: bool, family: ArgFamily, pool: typing.Optional[str]):
pool_data = _get_raw_pool_statistics(family=family, pool=pool)
if raw:
return pool_data
@@ -257,11 +261,13 @@ def show_pool_statistics(raw: bool, family: str, pool: typing.Optional[str]):
@_verify
-def show_server_leases(raw: bool, family: str, pool: typing.Optional[str],
- sorted: typing.Optional[str], state: typing.Optional[str]):
+def show_server_leases(raw: bool, family: ArgFamily, pool: typing.Optional[str],
+ sorted: typing.Optional[str], state: typing.Optional[ArgState]):
# if dhcp server is down, inactive leases may still be shown as active, so warn the user.
- if not is_systemd_service_running('isc-dhcp-server.service'):
- Warning('DHCP server is configured but not started. Data may be stale.')
+ v = '6' if family == 'inet6' else ''
+ service_name = 'DHCPv6' if family == 'inet6' else 'DHCP'
+ if not is_systemd_service_running(f'isc-dhcp-server{v}.service'):
+ Warning(f'{service_name} server is configured but not started. Data may be stale.')
v = 'v6' if family == 'inet6' else ''
if pool and pool not in _get_dhcp_pools(family=family):
diff --git a/src/op_mode/dns.py b/src/op_mode/dns.py
index a0e47d7ad..f8863c530 100755
--- a/src/op_mode/dns.py
+++ b/src/op_mode/dns.py
@@ -17,7 +17,6 @@
import sys
-from sys import exit
from tabulate import tabulate
from vyos.configquery import ConfigTreeQuery
@@ -75,8 +74,7 @@ def show_forwarding_statistics(raw: bool):
config = ConfigTreeQuery()
if not config.exists('service dns forwarding'):
- print("DNS forwarding is not configured")
- exit(0)
+ raise vyos.opmode.UnconfiguredSubsystem('DNS forwarding is not configured')
dns_data = _get_raw_forwarding_statistics()
if raw:
diff --git a/src/op_mode/dynamic_dns.py b/src/op_mode/dynamic_dns.py
index 263a3b6a5..d41a74db3 100755
--- a/src/op_mode/dynamic_dns.py
+++ b/src/op_mode/dynamic_dns.py
@@ -16,69 +16,75 @@
import os
import argparse
-import jinja2
import sys
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'
-OUT_TMPL_SRC = """
-{% for entry in hosts %}
-ip address : {{ entry.ip }}
-host-name : {{ entry.host }}
-last update : {{ entry.time }}
-update-status: {{ entry.status }}
+columns = {
+ 'host': 'Hostname',
+ 'ipv4': 'IPv4 address',
+ 'status-ipv4': 'IPv4 status',
+ 'ipv6': 'IPv6 address',
+ 'status-ipv6': 'IPv6 status',
+ 'mtime': 'Last update',
+}
+
+
+def _get_formatted_host_records(host_data):
+ data_entries = []
+ for entry in host_data:
+ data_entries.append([entry.get(key) for key in columns.keys()])
+
+ header = columns.values()
+ output = tabulate(data_entries, header, numalign='left')
+ return output
-{% endfor %}
-"""
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)
- data = {
- 'hosts': []
- }
+ data = []
with open(cache_file, 'r') as f:
for line in f:
if line.startswith('#'):
continue
- outp = {
- 'host': '',
- 'ip': '',
- 'time': ''
- }
-
- if 'host=' in line:
- host = line.split('host=')[1]
- if host:
- outp['host'] = host.split(',')[0]
-
- if 'ip=' in line:
- ip = line.split('ip=')[1]
- if ip:
- outp['ip'] = ip.split(',')[0]
-
- if 'mtime=' in line:
- mtime = line.split('mtime=')[1]
- if mtime:
- outp['time'] = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(int(mtime.split(',')[0], base=10)))
-
- if 'status=' in line:
- status = line.split('status=')[1]
- if status:
- outp['status'] = status.split(',')[0]
-
- data['hosts'].append(outp)
-
- tmpl = jinja2.Template(OUT_TMPL_SRC)
- print(tmpl.render(data))
+ props = {}
+ # ddclient cache rows have properties in 'key=value' format separated by comma
+ # we pick up the ones we are interested in
+ for kvraw in line.split(' ')[0].split(','):
+ k, v = kvraw.split('=')
+ 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(
+ "%Y-%m-%d %H:%M:%S", time.localtime(int(props['mtime'], base=10)))
+
+ data.append(props)
+
+ print(_get_formatted_host_records(data))
def update_ddns():
diff --git a/src/op_mode/generate_public_key_command.py b/src/op_mode/generate_public_key_command.py
index f071ae350..8ba55c901 100755
--- a/src/op_mode/generate_public_key_command.py
+++ b/src/op_mode/generate_public_key_command.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2022 VyOS maintainers and contributors
+# Copyright (C) 2022-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
@@ -19,28 +19,51 @@ import sys
import urllib.parse
import vyos.remote
+from vyos.template import generate_uuid4
-def get_key(path):
+
+def get_key(path) -> list:
+ """Get public keys from a local file or remote URL
+
+ Args:
+ path: Path to the public keys file
+
+ Returns: list of public keys split by new line
+
+ """
url = urllib.parse.urlparse(path)
if url.scheme == 'file' or url.scheme == '':
with open(os.path.expanduser(path), 'r') as f:
key_string = f.read()
else:
key_string = vyos.remote.get_remote_config(path)
- return key_string.split()
-
-try:
- username = sys.argv[1]
- algorithm, key, identifier = get_key(sys.argv[2])
-except Exception as e:
- print("Failed to retrieve the public key: {}".format(e))
- sys.exit(1)
-
-print('# To add this key as an embedded key, run the following commands:')
-print('configure')
-print(f'set system login user {username} authentication public-keys {identifier} key {key}')
-print(f'set system login user {username} authentication public-keys {identifier} type {algorithm}')
-print('commit')
-print('save')
-print('exit')
+ return key_string.split('\n')
+
+
+if __name__ == "__main__":
+ first_loop = True
+
+ for k in get_key(sys.argv[2]):
+ k = k.split()
+ # Skip empty list entry
+ if k == []:
+ continue
+
+ try:
+ username = sys.argv[1]
+ # Github keys don't have identifier for example 'vyos@localhost'
+ # 'ssh-rsa AAAA... vyos@localhost'
+ # Generate uuid4 identifier
+ identifier = f'github@{generate_uuid4("")}' if sys.argv[2].startswith('https://github.com') else k[2]
+ algorithm, key = k[0], k[1]
+ except Exception as e:
+ print("Failed to retrieve the public key: {}".format(e))
+ sys.exit(1)
+
+ if first_loop:
+ print('# To add this key as an embedded key, run the following commands:')
+ print('configure')
+ print(f'set system login user {username} authentication public-keys {identifier} key {key}')
+ print(f'set system login user {username} authentication public-keys {identifier} type {algorithm}')
+ first_loop = False
diff --git a/src/op_mode/interfaces.py b/src/op_mode/interfaces.py
index 678c74980..f38b95a71 100755
--- a/src/op_mode/interfaces.py
+++ b/src/op_mode/interfaces.py
@@ -207,7 +207,11 @@ def _get_raw_data(ifname: typing.Optional[str],
res_intf['description'] = interface.get_alias()
- res_intf['stats'] = interface.operational.get_stats()
+ stats = interface.operational.get_stats()
+ for k in list(stats):
+ stats[k] = _get_counter_val(cache[k], stats[k])
+
+ res_intf['stats'] = stats
ret.append(res_intf)
@@ -273,6 +277,10 @@ def _get_counter_data(ifname: typing.Optional[str],
res_intf['rx_bytes'] = _get_counter_val(cache['rx_bytes'], stats['rx_bytes'])
res_intf['tx_packets'] = _get_counter_val(cache['tx_packets'], stats['tx_packets'])
res_intf['tx_bytes'] = _get_counter_val(cache['tx_bytes'], stats['tx_bytes'])
+ res_intf['rx_dropped'] = _get_counter_val(cache['rx_dropped'], stats['rx_dropped'])
+ res_intf['tx_dropped'] = _get_counter_val(cache['tx_dropped'], stats['tx_dropped'])
+ res_intf['rx_over_errors'] = _get_counter_val(cache['rx_over_errors'], stats['rx_over_errors'])
+ res_intf['tx_carrier_errors'] = _get_counter_val(cache['tx_carrier_errors'], stats['tx_carrier_errors'])
ret.append(res_intf)
@@ -364,19 +372,23 @@ def _format_show_summary(data):
@catch_broken_pipe
def _format_show_counters(data: list):
- formatting = '%-12s %10s %10s %10s %10s'
- print(formatting % ('Interface', 'Rx Packets', 'Rx Bytes', 'Tx Packets', 'Tx Bytes'))
-
- for intf in data:
- print(formatting % (
- intf['ifname'],
- intf['rx_packets'],
- intf['rx_bytes'],
- intf['tx_packets'],
- intf['tx_bytes']
- ))
-
- return 0
+ data_entries = []
+ for entry in data:
+ interface = entry.get('ifname')
+ rx_packets = entry.get('rx_packets')
+ rx_bytes = entry.get('rx_bytes')
+ tx_packets = entry.get('tx_packets')
+ tx_bytes = entry.get('tx_bytes')
+ rx_dropped = entry.get('rx_dropped')
+ tx_dropped = entry.get('tx_dropped')
+ rx_errors = entry.get('rx_over_errors')
+ tx_errors = entry.get('tx_carrier_errors')
+ data_entries.append([interface, rx_packets, rx_bytes, tx_packets, tx_bytes, rx_dropped, tx_dropped, rx_errors, tx_errors])
+
+ headers = ['Interface', 'Rx Packets', 'Rx Bytes', 'Tx Packets', 'Tx Bytes', 'Rx Dropped', 'Tx Dropped', 'Rx Errors', 'Tx Errors']
+ output = tabulate(data_entries, headers, numalign="left")
+ print (output)
+ return output
def show(raw: bool, intf_name: typing.Optional[str],
intf_type: typing.Optional[str],
@@ -402,6 +414,18 @@ def show_counters(raw: bool, intf_name: typing.Optional[str],
return data
return _format_show_counters(data)
+def clear_counters(intf_name: typing.Optional[str],
+ intf_type: typing.Optional[str],
+ vif: bool, vrrp: bool):
+ for interface in filtered_interfaces(intf_name, intf_type, vif, vrrp):
+ interface.operational.clear_counters()
+
+def reset_counters(intf_name: typing.Optional[str],
+ intf_type: typing.Optional[str],
+ vif: bool, vrrp: bool):
+ for interface in filtered_interfaces(intf_name, intf_type, vif, vrrp):
+ interface.operational.reset_counters()
+
if __name__ == '__main__':
try:
res = vyos.opmode.run(sys.modules[__name__])
diff --git a/src/op_mode/ipsec.py b/src/op_mode/ipsec.py
index f6417764a..db4948d7a 100755
--- a/src/op_mode/ipsec.py
+++ b/src/op_mode/ipsec.py
@@ -13,26 +13,21 @@
#
# 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 re
import sys
import typing
-from collections import OrderedDict
from hurry import filesize
from re import split as re_split
from tabulate import tabulate
-from subprocess import TimeoutExpired
-from vyos.util import call
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
-
-
-SWANCTL_CONF = '/etc/swanctl/swanctl.conf'
+import vyos.ipsec
def _convert(text):
@@ -43,21 +38,31 @@ def _alphanum_key(key):
return [_convert(c) for c in re_split('([0-9]+)', str(key))]
-def _get_vici_sas():
- from vici import Session as vici_session
-
+def _get_raw_data_sas():
try:
- session = vici_session()
- except Exception:
- raise vyos.opmode.UnconfiguredSubsystem("IPsec not initialized")
- sas = list(session.list_sas())
- return sas
+ get_sas = vyos.ipsec.get_vici_sas()
+ sas = convert_data(get_sas)
+ return sas
+ except (vyos.ipsec.ViciInitiateError) as err:
+ raise vyos.opmode.UnconfiguredSubsystem(err)
-def _get_raw_data_sas():
- get_sas = _get_vici_sas()
- sas = convert_data(get_sas)
- return sas
+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):
@@ -139,22 +144,14 @@ def _get_formatted_output_sas(sas):
# Connections block
-def _get_vici_connections():
- from vici import Session as vici_session
-
- try:
- session = vici_session()
- except Exception:
- raise vyos.opmode.UnconfiguredSubsystem("IPsec not initialized")
- connections = list(session.list_conns())
- return connections
-
def _get_convert_data_connections():
- get_connections = _get_vici_connections()
- connections = convert_data(get_connections)
- return connections
-
+ try:
+ get_connections = vyos.ipsec.get_vici_connections()
+ connections = convert_data(get_connections)
+ return connections
+ except (vyos.ipsec.ViciInitiateError) as err:
+ raise vyos.opmode.UnconfiguredSubsystem(err)
def _get_parent_sa_proposal(connection_name: str, data: list) -> dict:
"""Get parent SA proposals by connection name
@@ -239,7 +236,8 @@ def _get_child_sa_state(connection_name: str, tunnel_name: str,
# Get all child SA states
# there can be multiple SAs per tunnel
child_sa_states = [
- v['state'] for k, v in child_sas.items() if v['name'] == tunnel_name
+ v['state'] for k, v in child_sas.items() if
+ v['name'] == tunnel_name
]
return 'up' if 'INSTALLED' in child_sa_states else child_sa
@@ -406,39 +404,170 @@ def _get_formatted_output_conections(data):
# Connections block end
-def get_peer_connections(peer, tunnel):
- search = rf'^[\s]*({peer}-(tunnel-[\d]+|vti)).*'
- matches = []
- if not os.path.exists(SWANCTL_CONF):
- raise vyos.opmode.UnconfiguredSubsystem("IPsec not initialized")
- suffix = None if tunnel is None else (f'tunnel-{tunnel}' if
- tunnel.isnumeric() else tunnel)
- with open(SWANCTL_CONF, 'r') as f:
- for line in f.readlines():
- result = re.match(search, line)
- if result:
- if tunnel is None:
- matches.append(result[1])
+def _get_childsa_id_list(ike_sas: list) -> list:
+ """
+ Generate list of CHILD SA ids based on list of OrderingDict
+ wich is returned by vici
+ :param ike_sas: list of IKE SAs generated by vici
+ :type ike_sas: list
+ :return: list of IKE SAs ids
+ :rtype: list
+ """
+ list_childsa_id: list = []
+ for ike in ike_sas:
+ for ike_sa in ike.values():
+ for child_sa in ike_sa['child-sas'].values():
+ list_childsa_id.append(child_sa['uniqueid'].decode('ascii'))
+ return list_childsa_id
+
+
+def _get_all_sitetosite_peers_name_list() -> list:
+ """
+ Return site-to-site peers configuration
+ :return: site-to-site peers configuration
+ :rtype: list
+ """
+ conf: ConfigTreeQuery = ConfigTreeQuery()
+ config_path = ['vpn', 'ipsec', 'site-to-site', 'peer']
+ peers_config = conf.get_config_dict(config_path, key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
+ peers_list: list = []
+ for name in peers_config:
+ peers_list.append(name)
+ return peers_list
+
+
+def reset_peer(peer: str, tunnel: typing.Optional[str] = None):
+ # Convert tunnel to Strongwan format of CHILD_SA
+ tunnel_sw = None
+ if tunnel:
+ if tunnel.isnumeric():
+ tunnel_sw = f'{peer}-tunnel-{tunnel}'
+ elif tunnel == 'vti':
+ tunnel_sw = f'{peer}-vti'
+ try:
+ sa_list: list = vyos.ipsec.get_vici_sas_by_name(peer, tunnel_sw)
+ if not sa_list:
+ raise vyos.opmode.IncorrectValue(
+ f'Peer\'s {peer} SA(s) not found, aborting')
+ if tunnel and sa_list:
+ childsa_id_list: list = _get_childsa_id_list(sa_list)
+ if not childsa_id_list:
+ raise vyos.opmode.IncorrectValue(
+ f'Peer {peer} tunnel {tunnel} SA(s) not found, aborting')
+ vyos.ipsec.terminate_vici_by_name(peer, tunnel_sw)
+ print(f'Peer {peer} reset result: success')
+ except (vyos.ipsec.ViciInitiateError) as err:
+ raise vyos.opmode.UnconfiguredSubsystem(err)
+ 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:
+ for peer_name in sitetosite_list:
+ try:
+ reset_peer(peer_name)
+ except (vyos.opmode.IncorrectValue) as err:
+ print(err)
+ print('Peers reset result: success')
+ else:
+ 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
+ :param username:
+ :type username:
+ :return:
+ :rtype:
+ """
+ list_sa_id = []
+ 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']:
+ list_sa_id.append(sa['uniqueid'])
else:
- if result[2] == suffix:
- matches.append(result[1])
- return matches
+ list_sa_id.append(sa['uniqueid'])
+ return list_sa_id
-def reset_peer(peer: str, tunnel:typing.Optional[str]):
- conns = get_peer_connections(peer, tunnel)
+def reset_ra(username: typing.Optional[str] = None):
+ #Reset remote-access ipsec sessions
+ if username:
+ list_sa_id = _get_ra_session_list_by_username(username)
+ else:
+ list_sa_id = _get_ra_session_list_by_username()
+ if list_sa_id:
+ vyos.ipsec.terminate_vici_ikeid_list(list_sa_id)
- if not conns:
- raise vyos.opmode.IncorrectValue('Peer or tunnel(s) not found, aborting')
- for conn in conns:
+def reset_profile_dst(profile: str, tunnel: str, nbma_dst: str):
+ if profile and tunnel and nbma_dst:
+ ike_sa_name = f'dmvpn-{profile}-{tunnel}'
try:
- call(f'sudo /usr/sbin/ipsec down {conn}{{*}}', timeout = 10)
- call(f'sudo /usr/sbin/ipsec up {conn}', timeout = 10)
- except TimeoutExpired as e:
- raise vyos.opmode.InternalError(f'Timed out while resetting {conn}')
-
- print('Peer reset result: success')
+ # Get IKE SAs
+ sa_list = convert_data(
+ vyos.ipsec.get_vici_sas_by_name(ike_sa_name, None))
+ if not sa_list:
+ raise vyos.opmode.IncorrectValue(
+ f'SA(s) for profile {profile} tunnel {tunnel} not found, aborting')
+ sa_nbma_list = list([x for x in sa_list if
+ ike_sa_name in x and x[ike_sa_name][
+ 'remote-host'] == nbma_dst])
+ if not sa_nbma_list:
+ raise vyos.opmode.IncorrectValue(
+ f'SA(s) for profile {profile} tunnel {tunnel} remote-host {nbma_dst} not found, aborting')
+ # terminate IKE SAs
+ vyos.ipsec.terminate_vici_ikeid_list(list(
+ [x[ike_sa_name]['uniqueid'] for x in sa_nbma_list if
+ ike_sa_name in x]))
+ # initiate IKE SAs
+ for ike in sa_nbma_list:
+ if ike_sa_name in ike:
+ vyos.ipsec.vici_initiate(ike_sa_name, 'dmvpn',
+ ike[ike_sa_name]['local-host'],
+ ike[ike_sa_name]['remote-host'])
+ print(
+ f'Profile {profile} tunnel {tunnel} remote-host {nbma_dst} reset result: success')
+ except (vyos.ipsec.ViciInitiateError) as err:
+ raise vyos.opmode.UnconfiguredSubsystem(err)
+ except (vyos.ipsec.ViciCommandError) as err:
+ raise vyos.opmode.IncorrectValue(err)
+
+
+def reset_profile_all(profile: str, tunnel: str):
+ if profile and tunnel:
+ ike_sa_name = f'dmvpn-{profile}-{tunnel}'
+ try:
+ # Get IKE SAs
+ sa_list: list = convert_data(
+ vyos.ipsec.get_vici_sas_by_name(ike_sa_name, None))
+ if not sa_list:
+ raise vyos.opmode.IncorrectValue(
+ f'SA(s) for profile {profile} tunnel {tunnel} not found, aborting')
+ # terminate IKE SAs
+ vyos.ipsec.terminate_vici_by_name(ike_sa_name, None)
+ # initiate IKE SAs
+ for ike in sa_list:
+ if ike_sa_name in ike:
+ vyos.ipsec.vici_initiate(ike_sa_name, 'dmvpn',
+ ike[ike_sa_name]['local-host'],
+ ike[ike_sa_name]['remote-host'])
+ print(
+ f'Profile {profile} tunnel {tunnel} remote-host {ike[ike_sa_name]["remote-host"]} reset result: success')
+ print(f'Profile {profile} tunnel {tunnel} reset result: success')
+ except (vyos.ipsec.ViciInitiateError) as err:
+ raise vyos.opmode.UnconfiguredSubsystem(err)
+ except (vyos.ipsec.ViciCommandError) as err:
+ raise vyos.opmode.IncorrectValue(err)
def show_sa(raw: bool):
@@ -448,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()
@@ -465,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/nat.py b/src/op_mode/nat.py
index cf06de0e9..c92795745 100755
--- a/src/op_mode/nat.py
+++ b/src/op_mode/nat.py
@@ -31,6 +31,8 @@ from vyos.util import dict_search
base = 'nat'
unconf_message = 'NAT is not configured'
+ArgDirection = typing.Literal['source', 'destination']
+ArgFamily = typing.Literal['inet', 'inet6']
def _get_xml_translation(direction, family, address=None):
"""
@@ -298,7 +300,7 @@ def _verify(func):
@_verify
-def show_rules(raw: bool, direction: str, family: str):
+def show_rules(raw: bool, direction: ArgDirection, family: ArgFamily):
nat_rules = _get_raw_data_rules(direction, family)
if raw:
return nat_rules
@@ -307,7 +309,7 @@ def show_rules(raw: bool, direction: str, family: str):
@_verify
-def show_statistics(raw: bool, direction: str, family: str):
+def show_statistics(raw: bool, direction: ArgDirection, family: ArgFamily):
nat_statistics = _get_raw_data_rules(direction, family)
if raw:
return nat_statistics
@@ -316,8 +318,8 @@ def show_statistics(raw: bool, direction: str, family: str):
@_verify
-def show_translations(raw: bool, direction:
- str, family: str,
+def show_translations(raw: bool, direction: ArgDirection,
+ family: ArgFamily,
address: typing.Optional[str],
verbose: typing.Optional[bool]):
family = 'ipv6' if family == 'inet6' else 'ipv4'
diff --git a/src/op_mode/neighbor.py b/src/op_mode/neighbor.py
index 264dbdc72..b329ea280 100755
--- a/src/op_mode/neighbor.py
+++ b/src/op_mode/neighbor.py
@@ -32,6 +32,9 @@ import typing
import vyos.opmode
+ArgFamily = typing.Literal['inet', 'inet6']
+ArgState = typing.Literal['reachable', 'stale', 'failed', 'permanent']
+
def interface_exists(interface):
import os
return os.path.exists(f'/sys/class/net/{interface}')
@@ -88,7 +91,8 @@ def format_neighbors(neighs, interface=None):
headers = ["Address", "Interface", "Link layer address", "State"]
return tabulate(neighs, headers)
-def show(raw: bool, family: str, interface: typing.Optional[str], state: typing.Optional[str]):
+def show(raw: bool, family: ArgFamily, interface: typing.Optional[str],
+ state: typing.Optional[ArgState]):
""" Display neighbor table contents """
data = get_raw_data(family, interface, state=state)
@@ -97,7 +101,7 @@ def show(raw: bool, family: str, interface: typing.Optional[str], state: typing.
else:
return format_neighbors(data, interface)
-def reset(family: str, interface: typing.Optional[str], address: typing.Optional[str]):
+def reset(family: ArgFamily, interface: typing.Optional[str], address: typing.Optional[str]):
from vyos.util import run
if address and interface:
diff --git a/src/op_mode/nhrp.py b/src/op_mode/nhrp.py
new file mode 100755
index 000000000..5ff91a59c
--- /dev/null
+++ b/src/op_mode/nhrp.py
@@ -0,0 +1,101 @@
+#!/usr/bin/env python3
+#
+# 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
+# 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 sys
+import tabulate
+import vyos.opmode
+
+from vyos.util import cmd
+from vyos.util import process_named_running
+from vyos.util import colon_separated_to_dict
+
+
+def _get_formatted_output(output_dict: dict) -> str:
+ """
+ Create formatted table for CLI output
+ :param output_dict: dictionary for API
+ :type output_dict: dict
+ :return: tabulate string
+ :rtype: str
+ """
+ print(f"Status: {output_dict['Status']}")
+ output: str = tabulate.tabulate(output_dict['routes'], headers='keys',
+ numalign="left")
+ return output
+
+
+def _get_formatted_dict(output_string: str) -> dict:
+ """
+ Format string returned from CMD to API list
+ :param output_string: String received by CMD
+ :type output_string: str
+ :return: dictionary for API
+ :rtype: dict
+ """
+ formatted_dict: dict = {
+ 'Status': '',
+ 'routes': []
+ }
+ output_list: list = output_string.split('\n\n')
+ for list_a in output_list:
+ output_dict = colon_separated_to_dict(list_a, True)
+ if 'Status' in output_dict:
+ formatted_dict['Status'] = output_dict['Status']
+ else:
+ formatted_dict['routes'].append(output_dict)
+ return formatted_dict
+
+
+def show_interface(raw: bool):
+ """
+ Command 'show nhrp interface'
+ :param raw: if API
+ :type raw: bool
+ """
+ if not process_named_running('opennhrp'):
+ raise vyos.opmode.UnconfiguredSubsystem('OpenNHRP is not running.')
+ interface_string: str = cmd('sudo opennhrpctl interface show')
+ interface_dict: dict = _get_formatted_dict(interface_string)
+ if raw:
+ return interface_dict
+ else:
+ return _get_formatted_output(interface_dict)
+
+
+def show_tunnel(raw: bool):
+ """
+ Command 'show nhrp tunnel'
+ :param raw: if API
+ :type raw: bool
+ """
+ if not process_named_running('opennhrp'):
+ raise vyos.opmode.UnconfiguredSubsystem('OpenNHRP is not running.')
+ tunnel_string: str = cmd('sudo opennhrpctl show')
+ tunnel_dict: list = _get_formatted_dict(tunnel_string)
+ if raw:
+ return tunnel_dict
+ else:
+ return _get_formatted_output(tunnel_dict)
+
+
+if __name__ == '__main__':
+ 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/openvpn.py b/src/op_mode/openvpn.py
index 3797a7153..d9ae965c5 100755
--- a/src/op_mode/openvpn.py
+++ b/src/op_mode/openvpn.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2022 VyOS maintainers and contributors
+# Copyright (C) 2022-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
@@ -16,16 +16,21 @@
#
#
+import json
import os
import sys
+import typing
from tabulate import tabulate
import vyos.opmode
from vyos.util import bytes_to_human
from vyos.util import commit_in_progress
from vyos.util import call
+from vyos.util import rc_cmd
from vyos.config import Config
+ArgMode = typing.Literal['client', 'server', 'site_to_site']
+
def _get_tunnel_address(peer_host, peer_port, status_file):
peer = peer_host + ':' + peer_port
lst = []
@@ -50,7 +55,7 @@ def _get_tunnel_address(peer_host, peer_port, status_file):
def _get_interface_status(mode: str, interface: str) -> dict:
status_file = f'/run/openvpn/{interface}.status'
- data = {
+ data: dict = {
'mode': mode,
'intf': interface,
'local_host': '',
@@ -60,7 +65,7 @@ def _get_interface_status(mode: str, interface: str) -> dict:
}
if not os.path.exists(status_file):
- raise vyos.opmode.DataUnavailable('No information for interface {interface}')
+ return data
with open(status_file, 'r') as f:
lines = f.readlines()
@@ -139,30 +144,54 @@ def _get_interface_status(mode: str, interface: str) -> dict:
return data
-def _get_raw_data(mode: str) -> dict:
- data = {}
+
+def _get_interface_state(iface):
+ rc, out = rc_cmd(f'ip --json link show dev {iface}')
+ try:
+ data = json.loads(out)
+ except:
+ return 'DOWN'
+ return data[0].get('operstate', 'DOWN')
+
+
+def _get_interface_description(iface):
+ rc, out = rc_cmd(f'ip --json link show dev {iface}')
+ try:
+ data = json.loads(out)
+ except:
+ return ''
+ return data[0].get('ifalias', '')
+
+
+def _get_raw_data(mode: str) -> list:
+ data: list = []
conf = Config()
conf_dict = conf.get_config_dict(['interfaces', 'openvpn'],
get_first_key=True)
if not conf_dict:
return data
- interfaces = [x for x in list(conf_dict) if conf_dict[x]['mode'] == mode]
+ interfaces = [x for x in list(conf_dict) if
+ conf_dict[x]['mode'].replace('-', '_') == mode]
for intf in interfaces:
- data[intf] = _get_interface_status(mode, intf)
- d = data[intf]
+ d = _get_interface_status(mode, intf)
+ d['state'] = _get_interface_state(intf)
+ d['description'] = _get_interface_description(intf)
d['local_host'] = conf_dict[intf].get('local-host', '')
d['local_port'] = conf_dict[intf].get('local-port', '')
- if mode in ['client', 'site-to-site']:
+ if conf.exists(f'interfaces openvpn {intf} server client'):
+ d['configured_clients'] = conf.list_nodes(f'interfaces openvpn {intf} server client')
+ if mode in ['client', 'site_to_site']:
for client in d['clients']:
if 'shared-secret-key-file' in list(conf_dict[intf]):
client['name'] = 'None (PSK)'
client['remote_host'] = conf_dict[intf].get('remote-host', [''])[0]
client['remote_port'] = conf_dict[intf].get('remote-port', '1194')
+ data.append(d)
return data
-def _format_openvpn(data: dict) -> str:
+def _format_openvpn(data: list) -> str:
if not data:
out = 'No OpenVPN interfaces configured'
return out
@@ -171,11 +200,12 @@ def _format_openvpn(data: dict) -> str:
'TX bytes', 'RX bytes', 'Connected Since']
out = ''
- data_out = []
- for intf in list(data):
- l_host = data[intf]['local_host']
- l_port = data[intf]['local_port']
- for client in list(data[intf]['clients']):
+ for d in data:
+ data_out = []
+ intf = d['intf']
+ l_host = d['local_host']
+ l_port = d['local_port']
+ for client in d['clients']:
r_host = client['remote_host']
r_port = client['remote_port']
@@ -190,11 +220,13 @@ def _format_openvpn(data: dict) -> str:
data_out.append([name, remote, tunnel, local, tx_bytes,
rx_bytes, online_since])
- out += tabulate(data_out, headers)
+ if data_out:
+ out += tabulate(data_out, headers)
+ out += "\n"
return out
-def show(raw: bool, mode: str) -> str:
+def show(raw: bool, mode: ArgMode) -> typing.Union[list,str]:
openvpn_data = _get_raw_data(mode)
if raw:
diff --git a/src/op_mode/pki.py b/src/op_mode/pki.py
index 1e78c3a03..b054690b0 100755
--- a/src/op_mode/pki.py
+++ b/src/op_mode/pki.py
@@ -87,6 +87,9 @@ def get_config_certificate(name=None):
def get_certificate_ca(cert, ca_certs):
# Find CA certificate for given certificate
+ if not ca_certs:
+ return None
+
for ca_name, ca_dict in ca_certs.items():
if 'certificate' not in ca_dict:
continue
diff --git a/src/op_mode/reset_vpn.py b/src/op_mode/reset_vpn.py
index 3a0ad941c..46195d6cd 100755
--- a/src/op_mode/reset_vpn.py
+++ b/src/op_mode/reset_vpn.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019 VyOS maintainers and contributors
+# Copyright (C) 2022-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
@@ -13,60 +13,49 @@
#
# 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 sys
-import argparse
+import typing
from vyos.util import run
+import vyos.opmode
+
cmd_dict = {
- 'cmd_base' : '/usr/bin/accel-cmd -p {} terminate {} {}',
- 'vpn_types' : {
- 'pptp' : 2003,
- 'l2tp' : 2004,
- 'sstp' : 2005
+ 'cmd_base': '/usr/bin/accel-cmd -p {} terminate {} {}',
+ 'vpn_types': {
+ 'pptp': 2003,
+ 'l2tp': 2004,
+ 'sstp': 2005
}
}
-def terminate_sessions(username='', interface='', protocol=''):
- # Reset vpn connections by username
+def reset_conn(protocol: str, username: typing.Optional[str] = None,
+ interface: typing.Optional[str] = None):
if protocol in cmd_dict['vpn_types']:
- if username == "all_users":
- run(cmd_dict['cmd_base'].format(cmd_dict['vpn_types'][protocol], 'all', ''))
- else:
- run(cmd_dict['cmd_base'].format(cmd_dict['vpn_types'][protocol], 'username', username))
-
- # Reset vpn connections by ifname
- elif interface:
- for proto in cmd_dict['vpn_types']:
- run(cmd_dict['cmd_base'].format(cmd_dict['vpn_types'][proto], 'if', interface))
-
- elif username:
- # Reset all vpn connections
- if username == "all_users":
- for proto in cmd_dict['vpn_types']:
- run(cmd_dict['cmd_base'].format(cmd_dict['vpn_types'][proto], 'all', ''))
+ # Reset by Interface
+ if interface:
+ run(cmd_dict['cmd_base'].format(cmd_dict['vpn_types'][protocol],
+ 'if', interface))
+ return
+ # Reset by username
+ if username:
+ run(cmd_dict['cmd_base'].format(cmd_dict['vpn_types'][protocol],
+ 'username', username))
+ # Reset all
else:
- for proto in cmd_dict['vpn_types']:
- run(cmd_dict['cmd_base'].format(cmd_dict['vpn_types'][proto], 'username', username))
-
-def main():
- #parese args
- parser = argparse.ArgumentParser()
- parser.add_argument('--username', help='Terminate by username (all_users used for disconnect all users)', required=False)
- parser.add_argument('--interface', help='Terminate by interface', required=False)
- parser.add_argument('--protocol', help='Set protocol (pptp|l2tp|sstp)', required=False)
- args = parser.parse_args()
-
- if args.username or args.interface:
- terminate_sessions(username=args.username, interface=args.interface, protocol=args.protocol)
+ run(cmd_dict['cmd_base'].format(cmd_dict['vpn_types'][protocol],
+ 'all',
+ ''))
else:
- print("Param --username or --interface required")
- sys.exit(1)
-
- terminate_sessions()
+ vyos.opmode.IncorrectValue('Unknown VPN Protocol, aborting')
if __name__ == '__main__':
- main()
+ 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/restart_frr.py b/src/op_mode/restart_frr.py
index 91b25567a..680d9f8cc 100755
--- a/src/op_mode/restart_frr.py
+++ b/src/op_mode/restart_frr.py
@@ -139,7 +139,7 @@ def _reload_config(daemon):
# define program arguments
cmd_args_parser = argparse.ArgumentParser(description='restart frr daemons')
cmd_args_parser.add_argument('--action', choices=['restart'], required=True, help='action to frr daemons')
-cmd_args_parser.add_argument('--daemon', choices=['bfdd', 'bgpd', 'ldpd', 'ospfd', 'ospf6d', 'isisd', 'ripd', 'ripngd', 'staticd', 'zebra'], required=False, nargs='*', help='select single or multiple daemons')
+cmd_args_parser.add_argument('--daemon', choices=['bfdd', 'bgpd', 'ldpd', 'ospfd', 'ospf6d', 'isisd', 'ripd', 'ripngd', 'staticd', 'zebra', 'babeld'], required=False, nargs='*', help='select single or multiple daemons')
# parse arguments
cmd_args = cmd_args_parser.parse_args()
diff --git a/src/op_mode/route.py b/src/op_mode/route.py
index 7f0f9cbac..d6d6b7d6f 100755
--- a/src/op_mode/route.py
+++ b/src/op_mode/route.py
@@ -54,7 +54,9 @@ frr_command_template = Template("""
{% endif %}
""")
-def show_summary(raw: bool, family: str, table: typing.Optional[int], vrf: typing.Optional[str]):
+ArgFamily = typing.Literal['inet', 'inet6']
+
+def show_summary(raw: bool, family: ArgFamily, table: typing.Optional[int], vrf: typing.Optional[str]):
from vyos.util import cmd
if family == 'inet':
@@ -94,7 +96,7 @@ def show_summary(raw: bool, family: str, table: typing.Optional[int], vrf: typin
return output
def show(raw: bool,
- family: str,
+ family: ArgFamily,
net: typing.Optional[str],
table: typing.Optional[int],
protocol: typing.Optional[str],
diff --git a/src/op_mode/sflow.py b/src/op_mode/sflow.py
new file mode 100755
index 000000000..88f70d6bd
--- /dev/null
+++ b/src/op_mode/sflow.py
@@ -0,0 +1,108 @@
+#!/usr/bin/env python3
+#
+# 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
+# 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 dbus
+import sys
+
+from tabulate import tabulate
+
+from vyos.configquery import ConfigTreeQuery
+from vyos.util import cmd
+
+import vyos.opmode
+
+
+def _get_raw_sflow():
+ bus = dbus.SystemBus()
+ config = ConfigTreeQuery()
+
+ interfaces = config.values('system sflow interface')
+ servers = config.list_nodes('system sflow server')
+
+ sflow = bus.get_object('net.sflow.hsflowd', '/net/sflow/hsflowd')
+ sflow_telemetry = dbus.Interface(
+ sflow, dbus_interface='net.sflow.hsflowd.telemetry')
+ agent_address = sflow_telemetry.GetAgent()
+ samples_dropped = int(sflow_telemetry.Get('dropped_samples'))
+ packet_drop_sent = int(sflow_telemetry.Get('event_samples'))
+ samples_packet_sent = int(sflow_telemetry.Get('flow_samples'))
+ samples_counter_sent = int(sflow_telemetry.Get('counter_samples'))
+ datagrams_sent = int(sflow_telemetry.Get('datagrams'))
+ rtmetric_samples = int(sflow_telemetry.Get('rtmetric_samples'))
+ event_samples_suppressed = int(sflow_telemetry.Get('event_samples_suppressed'))
+ samples_suppressed = int(sflow_telemetry.Get('flow_samples_suppressed'))
+ counter_samples_suppressed = int(
+ sflow_telemetry.Get("counter_samples_suppressed"))
+ version = sflow_telemetry.GetVersion()
+
+ sflow_dict = {
+ 'agent_address': agent_address,
+ 'sflow_interfaces': interfaces,
+ 'sflow_servers': servers,
+ 'counter_samples_sent': samples_counter_sent,
+ 'datagrams_sent': datagrams_sent,
+ 'packet_drop_sent': packet_drop_sent,
+ 'packet_samples_dropped': samples_dropped,
+ 'packet_samples_sent': samples_packet_sent,
+ 'rtmetric_samples': rtmetric_samples,
+ 'event_samples_suppressed': event_samples_suppressed,
+ 'flow_samples_suppressed': samples_suppressed,
+ 'counter_samples_suppressed': counter_samples_suppressed,
+ 'hsflowd_version': version
+ }
+ return sflow_dict
+
+
+def _get_formatted_sflow(data):
+ table = [
+ ['Agent address', f'{data.get("agent_address")}'],
+ ['sFlow interfaces', f'{data.get("sflow_interfaces", "n/a")}'],
+ ['sFlow servers', f'{data.get("sflow_servers", "n/a")}'],
+ ['Counter samples sent', f'{data.get("counter_samples_sent")}'],
+ ['Datagrams sent', f'{data.get("datagrams_sent")}'],
+ ['Packet samples sent', f'{data.get("packet_samples_sent")}'],
+ ['Packet samples dropped', f'{data.get("packet_samples_dropped")}'],
+ ['Packet drops sent', f'{data.get("packet_drop_sent")}'],
+ ['Packet drops suppressed', f'{data.get("event_samples_suppressed")}'],
+ ['Flow samples suppressed', f'{data.get("flow_samples_suppressed")}'],
+ ['Counter samples suppressed', f'{data.get("counter_samples_suppressed")}']
+ ]
+
+ return tabulate(table)
+
+
+def show(raw: bool):
+
+ config = ConfigTreeQuery()
+ if not config.exists('system sflow'):
+ raise vyos.opmode.UnconfiguredSubsystem(
+ '"system sflow" is not configured!')
+
+ sflow_data = _get_raw_sflow()
+ if raw:
+ return sflow_data
+ else:
+ return _get_formatted_sflow(sflow_data)
+
+
+if __name__ == '__main__':
+ 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/show_interfaces.py b/src/op_mode/show_interfaces.py
deleted file mode 100755
index eac068274..000000000
--- a/src/op_mode/show_interfaces.py
+++ /dev/null
@@ -1,310 +0,0 @@
-#!/usr/bin/env python3
-
-# Copyright 2017-2021 VyOS maintainers and contributors <maintainers@vyos.io>
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library 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
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library. If not, see <http://www.gnu.org/licenses/>.
-
-import os
-import re
-import sys
-import glob
-import argparse
-
-from vyos.ifconfig import Section
-from vyos.ifconfig import Interface
-from vyos.ifconfig import VRRP
-from vyos.util import cmd, call
-
-
-# interfaces = Sections.reserved()
-interfaces = ['eno', 'ens', 'enp', 'enx', 'eth', 'vmnet', 'lo', 'tun', 'wan', 'pppoe']
-glob_ifnames = '/sys/class/net/({})*'.format('|'.join(interfaces))
-
-
-actions = {}
-def register(name):
- """
- Decorator to register a function into actions with a name.
- `actions[name]' can be used to call the registered functions.
- We wrap each function in a SIGPIPE handler as all registered functions
- can be subject to a broken pipe if there are a lot of interfaces.
- """
- def _register(function):
- def handled_function(*args, **kwargs):
- try:
- function(*args, **kwargs)
- except BrokenPipeError:
- # Flush output to /dev/null and bail out.
- os.dup2(os.open(os.devnull, os.O_WRONLY), sys.stdout.fileno())
- sys.exit(1)
- actions[name] = handled_function
- return handled_function
- return _register
-
-
-def filtered_interfaces(ifnames, iftypes, vif, vrrp):
- """
- get all the interfaces from the OS and returns them
- ifnames can be used to filter which interfaces should be considered
-
- ifnames: a list of interfaces names to consider, empty do not filter
- return an instance of the interface class
- """
- if isinstance(iftypes, list):
- for iftype in iftypes:
- yield from filtered_interfaces(ifnames, iftype, vif, vrrp)
-
- for ifname in Section.interfaces(iftypes):
- # Bail out early if interface name not part of our search list
- if ifnames and ifname not in ifnames:
- continue
-
- # As we are only "reading" from the interface - we must use the
- # generic base class which exposes all the data via a common API
- interface = Interface(ifname, create=False, debug=False)
-
- # VLAN interfaces have a '.' in their name by convention
- if vif and not '.' in ifname:
- continue
-
- if vrrp:
- vrrp_interfaces = VRRP.active_interfaces()
- if ifname not in vrrp_interfaces:
- continue
-
- yield interface
-
-
-def split_text(text, used=0):
- """
- take a string and attempt to split it to fit with the width of the screen
-
- text: the string to split
- used: number of characted already used in the screen
- """
- no_tty = call('tty -s')
-
- returned = cmd('stty size') if not no_tty else ''
- if len(returned) == 2:
- rows, columns = [int(_) for _ in returned]
- else:
- rows, columns = (40, 80)
-
- desc_len = columns - used
-
- line = ''
- for word in text.split():
- if len(line) + len(word) < desc_len:
- line = f'{line} {word}'
- continue
- if line:
- yield line[1:]
- else:
- line = f'{line} {word}'
-
- yield line[1:]
-
-
-def get_counter_val(clear, now):
- """
- attempt to correct a counter if it wrapped, copied from perl
-
- clear: previous counter
- now: the current counter
- """
- # This function has to deal with both 32 and 64 bit counters
- if clear == 0:
- return now
-
- # device is using 64 bit values assume they never wrap
- value = now - clear
- if (now >> 32) != 0:
- return value
-
- # The counter has rolled. If the counter has rolled
- # multiple times since the clear value, then this math
- # is meaningless.
- if (value < 0):
- value = (4294967296 - clear) + now
-
- return value
-
-
-@register('help')
-def usage(*args):
- print(f"Usage: {sys.argv[0]} [intf=NAME|intf-type=TYPE|vif|vrrp] action=ACTION")
- print(f" NAME = " + ' | '.join(Section.interfaces()))
- print(f" TYPE = " + ' | '.join(Section.sections()))
- print(f" ACTION = " + ' | '.join(actions))
- sys.exit(1)
-
-
-@register('allowed')
-def run_allowed(**kwarg):
- sys.stdout.write(' '.join(Section.interfaces()))
-
-
-def pppoe(ifname):
- out = cmd(f'ps -C pppd -f')
- if ifname in out:
- return 'C'
- elif ifname in [_.split('/')[-1] for _ in glob.glob('/etc/ppp/peers/pppoe*')]:
- return 'D'
- return ''
-
-
-@register('show')
-def run_show_intf(ifnames, iftypes, vif, vrrp):
- handled = []
- for interface in filtered_interfaces(ifnames, iftypes, vif, vrrp):
- handled.append(interface.ifname)
- cache = interface.operational.load_counters()
-
- out = cmd(f'ip addr show {interface.ifname}')
- out = re.sub(f'^\d+:\s+','',out)
- if re.search('link/tunnel6', out):
- tunnel = cmd(f'ip -6 tun show {interface.ifname}')
- # tun0: ip/ipv6 remote ::2 local ::1 encaplimit 4 hoplimit 64 tclass inherit flowlabel inherit (flowinfo 0x00000000)
- tunnel = re.sub('.*encap', 'encap', tunnel)
- out = re.sub('(\n\s+)(link/tunnel6)', f'\g<1>{tunnel}\g<1>\g<2>', out)
-
- print(out)
-
- timestamp = int(cache.get('timestamp', 0))
- if timestamp:
- when = interface.operational.strtime(timestamp)
- print(f' Last clear: {when}')
-
- description = interface.get_alias()
- if description:
- print(f' Description: {description}')
-
- print()
- print(interface.operational.formated_stats())
-
- for ifname in ifnames:
- if ifname not in handled and ifname.startswith('pppoe'):
- state = pppoe(ifname)
- if not state:
- continue
- string = {
- 'C': 'Coming up',
- 'D': 'Link down',
- }[state]
- print('{}: {}'.format(ifname, string))
-
-
-@register('show-brief')
-def run_show_intf_brief(ifnames, iftypes, vif, vrrp):
- format1 = '%-16s %-33s %-4s %s'
- format2 = '%-16s %s'
-
- print('Codes: S - State, L - Link, u - Up, D - Down, A - Admin Down')
- print(format1 % ("Interface", "IP Address", "S/L", "Description"))
- print(format1 % ("---------", "----------", "---", "-----------"))
-
- handled = []
- for interface in filtered_interfaces(ifnames, iftypes, vif, vrrp):
- handled.append(interface.ifname)
-
- oper_state = interface.operational.get_state()
- admin_state = interface.get_admin_state()
-
- intf = [interface.ifname,]
-
- oper = ['u', ] if oper_state in ('up', 'unknown') else ['D', ]
- admin = ['u', ] if admin_state in ('up', 'unknown') else ['A', ]
- addrs = [_ for _ in interface.get_addr() if not _.startswith('fe80::')] or ['-', ]
- descs = list(split_text(interface.get_alias(),0))
-
- while intf or oper or admin or addrs or descs:
- i = intf.pop(0) if intf else ''
- a = addrs.pop(0) if addrs else ''
- d = descs.pop(0) if descs else ''
- s = [admin.pop(0)] if admin else []
- l = [oper.pop(0)] if oper else []
- if len(a) < 33:
- print(format1 % (i, a, '/'.join(s+l), d))
- else:
- print(format2 % (i, a))
- print(format1 % ('', '', '/'.join(s+l), d))
-
- for ifname in ifnames:
- if ifname not in handled and ifname.startswith('pppoe'):
- state = pppoe(ifname)
- if not state:
- continue
- string = {
- 'C': 'u/D',
- 'D': 'A/D',
- }[state]
- print(format1 % (ifname, '', string, ''))
-
-
-@register('show-count')
-def run_show_counters(ifnames, iftypes, vif, vrrp):
- formating = '%-12s %10s %10s %10s %10s'
- print(formating % ('Interface', 'Rx Packets', 'Rx Bytes', 'Tx Packets', 'Tx Bytes'))
-
- for interface in filtered_interfaces(ifnames, iftypes, vif, vrrp):
- oper = interface.operational.get_state()
-
- if oper not in ('up','unknown'):
- continue
-
- stats = interface.operational.get_stats()
- cache = interface.operational.load_counters()
- print(formating % (
- interface.ifname,
- get_counter_val(cache['rx_packets'], stats['rx_packets']),
- get_counter_val(cache['rx_bytes'], stats['rx_bytes']),
- get_counter_val(cache['tx_packets'], stats['tx_packets']),
- get_counter_val(cache['tx_bytes'], stats['tx_bytes']),
- ))
-
-
-@register('clear')
-def run_clear_intf(ifnames, iftypes, vif, vrrp):
- for interface in filtered_interfaces(ifnames, iftypes, vif, vrrp):
- print(f'Clearing {interface.ifname}')
- interface.operational.clear_counters()
-
-
-@register('reset')
-def run_reset_intf(ifnames, iftypes, vif, vrrp):
- for interface in filtered_interfaces(ifnames, iftypes, vif, vrrp):
- interface.operational.reset_counters()
-
-
-if __name__ == '__main__':
- parser = argparse.ArgumentParser(add_help=False, description='Show interface information')
- parser.add_argument('--intf', action="store", type=str, default='', help='only show the specified interface(s)')
- parser.add_argument('--intf-type', action="store", type=str, default='', help='only show the specified interface type')
- parser.add_argument('--action', action="store", type=str, default='show', help='action to perform')
- parser.add_argument('--vif', action='store_true', default=False, help="only show vif interfaces")
- parser.add_argument('--vrrp', action='store_true', default=False, help="only show vrrp interfaces")
- parser.add_argument('--help', action='store_true', default=False, help="show help")
-
- args = parser.parse_args()
-
- def missing(*args):
- print('Invalid action [{args.action}]')
- usage()
-
- actions.get(args.action, missing)(
- [_ for _ in args.intf.split(' ') if _],
- [_ for _ in args.intf_type.split(' ') if _],
- args.vif,
- args.vrrp
- )
diff --git a/src/op_mode/show_openconnect_otp.py b/src/op_mode/show_openconnect_otp.py
index ae532ccc9..88982c50b 100755
--- a/src/op_mode/show_openconnect_otp.py
+++ b/src/op_mode/show_openconnect_otp.py
@@ -46,7 +46,7 @@ def get_otp_ocserv(username):
# options which we need to update into the dictionary retrived.
default_values = defaults(base)
ocserv = dict_merge(default_values, ocserv)
- # workaround a "know limitation" - https://phabricator.vyos.net/T2665
+ # workaround a "know limitation" - https://vyos.dev/T2665
del ocserv['authentication']['local_users']['username']['otp']
if not ocserv["authentication"]["local_users"]["username"]:
return None
diff --git a/src/op_mode/show_techsupport_report.py b/src/op_mode/show_techsupport_report.py
new file mode 100644
index 000000000..782004144
--- /dev/null
+++ b/src/op_mode/show_techsupport_report.py
@@ -0,0 +1,303 @@
+#!/usr/bin/env python3
+#
+# 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
+# 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
+
+from typing import List
+from vyos.util import rc_cmd
+from vyos.ifconfig import Section
+from vyos.ifconfig import Interface
+
+
+def print_header(command: str) -> None:
+ """Prints a command with headers '-'.
+
+ Example:
+
+ % print_header('Example command')
+
+ ---------------
+ Example command
+ ---------------
+ """
+ header_length = len(command) * '-'
+ print(f"\n{header_length}\n{command}\n{header_length}")
+
+
+def execute_command(command: str, header_text: str) -> None:
+ """Executes a command and prints the output with a header.
+
+ Example:
+ % execute_command('uptime', "Uptime of the system")
+
+ --------------------
+ Uptime of the system
+ --------------------
+ 20:21:57 up 9:04, 5 users, load average: 0.00, 0.00, 0.0
+
+ """
+ print_header(header_text)
+ try:
+ rc, output = rc_cmd(command)
+ print(output)
+ except Exception as e:
+ print(f"Error executing command: {command}")
+ print(f"Error message: {e}")
+
+
+def op(cmd: str) -> str:
+ """Returns a command with the VyOS operational mode wrapper."""
+ return f'/opt/vyatta/bin/vyatta-op-cmd-wrapper {cmd}'
+
+
+def get_ethernet_interfaces() -> List[Interface]:
+ """Returns a list of Ethernet interfaces."""
+ return Section.interfaces('ethernet')
+
+
+def show_version() -> None:
+ """Prints the VyOS version and package changes."""
+ execute_command(op('show version'), 'VyOS Version and Package Changes')
+
+
+def show_config_file() -> None:
+ """Prints the contents of a configuration file with a header."""
+ execute_command('cat /opt/vyatta/etc/config/config.boot', 'Configuration file')
+
+
+def show_running_config() -> None:
+ """Prints the running configuration."""
+ execute_command(op('show configuration'), 'Running configuration')
+
+
+def show_package_repository_config() -> None:
+ """Prints the package repository configuration file."""
+ execute_command('cat /etc/apt/sources.list', 'Package Repository Configuration File')
+ execute_command('ls -l /etc/apt/sources.list.d/', 'Repositories')
+
+
+def show_user_startup_scripts() -> None:
+ """Prints the user startup scripts."""
+ execute_command('cat /config/scripts/vyos-postconfig-bootup.script', 'User Startup Scripts')
+
+
+def show_frr_config() -> None:
+ """Prints the FRR configuration."""
+ execute_command('vtysh -c "show run"', 'FRR configuration')
+
+
+def show_interfaces() -> None:
+ """Prints the interfaces."""
+ execute_command(op('show interfaces'), 'Interfaces')
+
+
+def show_interface_statistics() -> None:
+ """Prints the interface statistics."""
+ execute_command('ip -s link show', 'Interface statistics')
+
+
+def show_physical_interface_statistics() -> None:
+ """Prints the physical interface statistics."""
+ execute_command('/usr/bin/true', 'Physical Interface statistics')
+ for iface in get_ethernet_interfaces():
+ # Exclude vlans
+ if '.' in iface:
+ continue
+ execute_command(f'ethtool --driver {iface}', f'ethtool --driver {iface}')
+ execute_command(f'ethtool --statistics {iface}', f'ethtool --statistics {iface}')
+ execute_command(f'ethtool --show-ring {iface}', f'ethtool --show-ring {iface}')
+ execute_command(f'ethtool --show-coalesce {iface}', f'ethtool --show-coalesce {iface}')
+ execute_command(f'ethtool --pause {iface}', f'ethtool --pause {iface}')
+ execute_command(f'ethtool --show-features {iface}', f'ethtool --show-features {iface}')
+ execute_command(f'ethtool --phy-statistics {iface}', f'ethtool --phy-statistics {iface}')
+ execute_command('netstat --interfaces', 'netstat --interfaces')
+ execute_command('netstat --listening', 'netstat --listening')
+ execute_command('cat /proc/net/dev', 'cat /proc/net/dev')
+
+
+def show_bridge() -> None:
+ """Show bridge interfaces."""
+ execute_command(op('show bridge'), 'Show bridge')
+
+
+def show_arp() -> None:
+ """Prints ARP entries."""
+ execute_command(op('show arp'), 'ARP Table (Total entries)')
+ execute_command(op('show ipv6 neighbors'), 'show ipv6 neighbors')
+
+
+def show_route() -> None:
+ """Prints routing information."""
+
+ cmd_list_route = [
+ "show ip route bgp | head -108",
+ "show ip route cache",
+ "show ip route connected",
+ "show ip route forward",
+ "show ip route isis | head -108",
+ "show ip route kernel",
+ "show ip route ospf | head -108",
+ "show ip route rip",
+ "show ip route static",
+ "show ip route summary",
+ "show ip route supernets-only",
+ "show ip route table all",
+ "show ip route vrf all",
+ "show ipv6 route bgp | head 108",
+ "show ipv6 route cache",
+ "show ipv6 route connected",
+ "show ipv6 route forward",
+ "show ipv6 route isis",
+ "show ipv6 route kernel",
+ "show ipv6 route ospf",
+ "show ipv6 route rip",
+ "show ipv6 route static",
+ "show ipv6 route summary",
+ "show ipv6 route table all",
+ "show ipv6 route vrf all",
+ ]
+ for command in cmd_list_route:
+ execute_command(op(command), command)
+
+
+def show_firewall() -> None:
+ """Prints firweall information."""
+ execute_command('sudo nft list ruleset', 'nft list ruleset')
+
+
+def show_system() -> None:
+ """Prints system parameters."""
+ execute_command(op('show system image version'), 'Show System Image Version')
+ execute_command(op('show system image storage'), 'Show System Image Storage')
+
+
+def show_date() -> None:
+ """Print the current date."""
+ execute_command('date', 'Current Time')
+
+
+def show_installed_packages() -> None:
+ """Prints installed packages."""
+ execute_command('dpkg --list', 'Installed Packages')
+
+
+def show_loaded_modules() -> None:
+ """Prints loaded modules /proc/modules"""
+ execute_command('cat /proc/modules', 'Loaded Modules')
+
+
+def show_cpu_statistics() -> None:
+ """Prints CPU statistics."""
+ execute_command('/usr/bin/true', 'CPU')
+ execute_command('lscpu', 'Installed CPU\'s')
+ execute_command('top --iterations 1 --batch-mode --accum-time-toggle', 'Cumulative CPU Time Used by Running Processes')
+ execute_command('cat /proc/loadavg', 'Load Average')
+
+
+def show_system_interrupts() -> None:
+ """Prints system interrupts."""
+ execute_command('cat /proc/interrupts', 'Hardware Interrupt Counters')
+
+
+def show_soft_irqs() -> None:
+ """Prints soft IRQ's."""
+ execute_command('cat /proc/softirqs', 'Soft IRQ\'s')
+
+
+def show_softnet_statistics() -> None:
+ """Prints softnet statistics."""
+ execute_command('cat /proc/net/softnet_stat', 'cat /proc/net/softnet_stat')
+
+
+def show_running_processes() -> None:
+ """Prints current running processes"""
+ execute_command('ps -ef', 'Running Processes')
+
+
+def show_memory_usage() -> None:
+ """Prints memory usage"""
+ execute_command('/usr/bin/true', 'Memory')
+ execute_command('cat /proc/meminfo', 'Installed Memory')
+ execute_command('free', 'Memory Usage')
+
+
+def list_disks():
+ disks = set()
+ with open('/proc/partitions') as partitions_file:
+ for line in partitions_file:
+ fields = line.strip().split()
+ if len(fields) == 4 and fields[3].isalpha() and fields[3] != 'name':
+ disks.add(fields[3])
+ return disks
+
+
+def show_storage() -> None:
+ """Prints storage information."""
+ execute_command('cat /proc/devices', 'Devices')
+ execute_command('cat /proc/partitions', 'Partitions')
+
+ for disk in list_disks():
+ execute_command(f'fdisk --list /dev/{disk}', f'Partitioning for disk {disk}')
+
+
+def main():
+ # Configuration data
+ show_version()
+ show_config_file()
+ show_running_config()
+ show_package_repository_config()
+ show_user_startup_scripts()
+ show_frr_config()
+
+ # Interfaces
+ show_interfaces()
+ show_interface_statistics()
+ show_physical_interface_statistics()
+ show_bridge()
+ show_arp()
+
+ # Routing
+ show_route()
+
+ # Firewall
+ show_firewall()
+
+ # System
+ show_system()
+ show_date()
+ show_installed_packages()
+ show_loaded_modules()
+
+ # CPU
+ show_cpu_statistics()
+ show_system_interrupts()
+ show_soft_irqs()
+ show_softnet_statistics()
+
+ # Memory
+ show_memory_usage()
+
+ # Storage
+ show_storage()
+
+ # Processes
+ show_running_processes()
+
+ # TODO: Get information from clouds
+
+
+if __name__ == "__main__":
+ main()
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}'
diff --git a/src/op_mode/vpn_ipsec.py b/src/op_mode/vpn_ipsec.py
index 2392cfe92..b81d1693e 100755
--- a/src/op_mode/vpn_ipsec.py
+++ b/src/op_mode/vpn_ipsec.py
@@ -16,12 +16,12 @@
import re
import argparse
-from subprocess import TimeoutExpired
from vyos.util import call
SWANCTL_CONF = '/etc/swanctl/swanctl.conf'
+
def get_peer_connections(peer, tunnel, return_all = False):
search = rf'^[\s]*(peer_{peer}_(tunnel_[\d]+|vti)).*'
matches = []
@@ -34,57 +34,6 @@ def get_peer_connections(peer, tunnel, return_all = False):
matches.append(result[1])
return matches
-def reset_peer(peer, tunnel):
- if not peer:
- print('Invalid peer, aborting')
- return
-
- conns = get_peer_connections(peer, tunnel, return_all = (not tunnel or tunnel == 'all'))
-
- if not conns:
- print('Tunnel(s) not found, aborting')
- return
-
- result = True
- for conn in conns:
- try:
- call(f'/usr/sbin/ipsec down {conn}{{*}}', timeout = 10)
- call(f'/usr/sbin/ipsec up {conn}', timeout = 10)
- except TimeoutExpired as e:
- print(f'Timed out while resetting {conn}')
- result = False
-
-
- print('Peer reset result: ' + ('success' if result else 'failed'))
-
-def get_profile_connection(profile, tunnel = None):
- search = rf'(dmvpn-{profile}-[\w]+)' if tunnel == 'all' else rf'(dmvpn-{profile}-{tunnel})'
- with open(SWANCTL_CONF, 'r') as f:
- for line in f.readlines():
- result = re.search(search, line)
- if result:
- return result[1]
- return None
-
-def reset_profile(profile, tunnel):
- if not profile:
- print('Invalid profile, aborting')
- return
-
- if not tunnel:
- print('Invalid tunnel, aborting')
- return
-
- conn = get_profile_connection(profile)
-
- if not conn:
- print('Profile not found, aborting')
- return
-
- call(f'/usr/sbin/ipsec down {conn}')
- result = call(f'/usr/sbin/ipsec up {conn}')
-
- print('Profile reset result: ' + ('success' if result == 0 else 'failed'))
def debug_peer(peer, tunnel):
peer = peer.replace(':', '-')
@@ -119,6 +68,7 @@ def debug_peer(peer, tunnel):
for conn in conns:
call(f'/usr/sbin/ipsec statusall | grep {conn}')
+
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--action', help='Control action', required=True)
@@ -127,9 +77,6 @@ if __name__ == '__main__':
args = parser.parse_args()
- if args.action == 'reset-peer':
- reset_peer(args.name, args.tunnel)
- elif args.action == "reset-profile":
- reset_profile(args.name, args.tunnel)
- elif args.action == "vpn-debug":
+
+ if args.action == "vpn-debug":
debug_peer(args.name, args.tunnel)