diff options
Diffstat (limited to 'src/op_mode')
-rwxr-xr-x | src/op_mode/accelppp.py | 44 | ||||
-rwxr-xr-x | src/op_mode/bgp.py | 169 | ||||
-rwxr-xr-x | src/op_mode/conntrack.py | 4 | ||||
-rwxr-xr-x | src/op_mode/conntrack_sync.py | 219 | ||||
-rwxr-xr-x | src/op_mode/dhcp.py | 38 | ||||
-rwxr-xr-x | src/op_mode/dns.py | 4 | ||||
-rwxr-xr-x | src/op_mode/dynamic_dns.py | 92 | ||||
-rwxr-xr-x | src/op_mode/generate_public_key_command.py | 59 | ||||
-rwxr-xr-x | src/op_mode/interfaces.py | 52 | ||||
-rwxr-xr-x | src/op_mode/ipsec.py | 438 | ||||
-rwxr-xr-x | src/op_mode/nat.py | 10 | ||||
-rwxr-xr-x | src/op_mode/neighbor.py | 8 | ||||
-rwxr-xr-x | src/op_mode/nhrp.py | 101 | ||||
-rwxr-xr-x | src/op_mode/openvpn.py | 66 | ||||
-rwxr-xr-x | src/op_mode/pki.py | 3 | ||||
-rwxr-xr-x | src/op_mode/reset_vpn.py | 75 | ||||
-rwxr-xr-x | src/op_mode/restart_frr.py | 2 | ||||
-rwxr-xr-x | src/op_mode/route.py | 6 | ||||
-rwxr-xr-x | src/op_mode/sflow.py | 108 | ||||
-rwxr-xr-x | src/op_mode/show_interfaces.py | 310 | ||||
-rwxr-xr-x | src/op_mode/show_openconnect_otp.py | 2 | ||||
-rw-r--r-- | src/op_mode/show_techsupport_report.py | 303 | ||||
-rwxr-xr-x | src/op_mode/show_vpn_ra.py | 56 | ||||
-rwxr-xr-x | src/op_mode/show_wwan.py | 8 | ||||
-rwxr-xr-x | src/op_mode/vpn_ipsec.py | 61 |
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) |