diff options
Diffstat (limited to 'src')
-rwxr-xr-x | src/conf_mode/protocols_bfd.py | 3 | ||||
-rwxr-xr-x | src/conf_mode/protocols_bgp.py | 10 | ||||
-rwxr-xr-x | src/conf_mode/service_dhcp-server.py | 28 | ||||
-rwxr-xr-x | src/conf_mode/system_option.py | 11 | ||||
-rwxr-xr-x | src/conf_mode/system_sflow.py | 11 | ||||
-rwxr-xr-x | src/conf_mode/vrf.py | 47 | ||||
-rwxr-xr-x | src/migration-scripts/bgp/4-to-5 | 67 | ||||
-rwxr-xr-x | src/migration-scripts/https/5-to-6 | 4 | ||||
-rwxr-xr-x | src/migration-scripts/policy/4-to-5 | 48 | ||||
-rwxr-xr-x | src/migration-scripts/qos/1-to-2 | 48 | ||||
-rwxr-xr-x | src/op_mode/image_installer.py | 12 | ||||
-rwxr-xr-x | src/op_mode/multicast.py | 72 | ||||
-rwxr-xr-x | src/op_mode/show_openvpn.py | 6 |
13 files changed, 308 insertions, 59 deletions
diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py index dab784662..37421efb4 100755 --- a/src/conf_mode/protocols_bfd.py +++ b/src/conf_mode/protocols_bfd.py @@ -72,6 +72,9 @@ def verify(bfd): if 'source' in peer_config and 'interface' in peer_config['source']: raise ConfigError('BFD multihop and source interface cannot be used together') + if 'minimum_ttl' in peer_config and 'multihop' not in peer_config: + raise ConfigError('Minimum TTL is only available for multihop BFD sessions!') + if 'profile' in peer_config: profile_name = peer_config['profile'] if 'profile' not in bfd or profile_name not in bfd['profile']: diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index f6f3370c3..d90dfe45b 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2023 VyOS maintainers and contributors +# Copyright (C) 2020-2024 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 @@ -509,6 +509,14 @@ def verify(bgp): if verify_vrf_as_import(vrf_name, afi, bgp['dependent_vrfs']): raise ConfigError( 'Command "import vrf" conflicts with "route-target vpn both" command!') + if dict_search('route_target.vpn.export', afi_config): + raise ConfigError( + 'Command "route-target vpn export" conflicts '\ + 'with "route-target vpn both" command!') + if dict_search('route_target.vpn.import', afi_config): + raise ConfigError( + 'Command "route-target vpn import" conflicts '\ + 'with "route-target vpn both" command!') if dict_search('route_target.vpn.import', afi_config): if verify_vrf_as_import(vrf_name, afi, bgp['dependent_vrfs']): diff --git a/src/conf_mode/service_dhcp-server.py b/src/conf_mode/service_dhcp-server.py index 9632b91fc..91ea354b6 100755 --- a/src/conf_mode/service_dhcp-server.py +++ b/src/conf_mode/service_dhcp-server.py @@ -246,19 +246,21 @@ def verify(dhcp): raise ConfigError(f'Either MAC address or Client identifier (DUID) is required for ' f'static mapping "{mapping}" within shared-network "{network}, {subnet}"!') - if mapping_config['ip_address'] in used_ips: - raise ConfigError(f'Configured IP address for static mapping "{mapping}" already exists on another static mapping') - used_ips.append(mapping_config['ip_address']) - - if 'mac' in mapping_config: - if mapping_config['mac'] in used_mac: - raise ConfigError(f'Configured MAC address for static mapping "{mapping}" already exists on another static mapping') - used_mac.append(mapping_config['mac']) - - if 'duid' in mapping_config: - if mapping_config['duid'] in used_duid: - raise ConfigError(f'Configured DUID for static mapping "{mapping}" already exists on another static mapping') - used_duid.append(mapping_config['duid']) + if 'disable' not in mapping_config: + if mapping_config['ip_address'] in used_ips: + raise ConfigError(f'Configured IP address for static mapping "{mapping}" already exists on another static mapping') + used_ips.append(mapping_config['ip_address']) + + if 'disable' not in mapping_config: + if 'mac' in mapping_config: + if mapping_config['mac'] in used_mac: + raise ConfigError(f'Configured MAC address for static mapping "{mapping}" already exists on another static mapping') + used_mac.append(mapping_config['mac']) + + if 'duid' in mapping_config: + if mapping_config['duid'] in used_duid: + raise ConfigError(f'Configured DUID for static mapping "{mapping}" already exists on another static mapping') + used_duid.append(mapping_config['duid']) # There must be one subnet connected to a listen interface. # This only counts if the network itself is not disabled! diff --git a/src/conf_mode/system_option.py b/src/conf_mode/system_option.py index d92121b3d..3b5b67437 100755 --- a/src/conf_mode/system_option.py +++ b/src/conf_mode/system_option.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2023 VyOS maintainers and contributors +# Copyright (C) 2019-2024 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 @@ -22,6 +22,7 @@ from time import sleep from vyos.config import Config from vyos.configverify import verify_source_interface +from vyos.system import grub_util from vyos.template import render from vyos.utils.process import cmd from vyos.utils.process import is_systemd_service_running @@ -39,7 +40,6 @@ time_format_to_locale = { '24-hour': 'en_GB.UTF-8' } - def get_config(config=None): if config: conf = config @@ -87,6 +87,13 @@ def verify(options): def generate(options): render(curlrc_config, 'system/curlrc.j2', options) render(ssh_config, 'system/ssh_config.j2', options) + + cmdline_options = [] + if 'kernel' in options: + if 'disable_mitigations' in options['kernel']: + cmdline_options.append('mitigations=off') + grub_util.update_kernel_cmdline_options(' '.join(cmdline_options)) + return None def apply(options): diff --git a/src/conf_mode/system_sflow.py b/src/conf_mode/system_sflow.py index 2df1bbb7a..41119b494 100755 --- a/src/conf_mode/system_sflow.py +++ b/src/conf_mode/system_sflow.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2023 VyOS maintainers and contributors +# Copyright (C) 2023-2024 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,6 +19,7 @@ import os from sys import exit from vyos.config import Config +from vyos.configverify import verify_vrf from vyos.template import render from vyos.utils.process import call from vyos.utils.network import is_addr_assigned @@ -46,7 +47,6 @@ def get_config(config=None): return sflow - def verify(sflow): if not sflow: return None @@ -68,9 +68,8 @@ def verify(sflow): if 'server' not in sflow: raise ConfigError('You need to configure at least one sFlow server!') - # return True if all checks were passed - return True - + verify_vrf(sflow) + return None def generate(sflow): if not sflow: @@ -81,7 +80,6 @@ def generate(sflow): # Reload systemd manager configuration call('systemctl daemon-reload') - def apply(sflow): if not sflow: # Stop flow-accounting daemon and remove configuration file @@ -93,7 +91,6 @@ def apply(sflow): # Start/reload flow-accounting daemon call(f'systemctl restart {systemd_service}') - if __name__ == '__main__': try: config = get_config() diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py index 9b1b6355f..f2c544aa6 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2023 VyOS maintainers and contributors +# Copyright (C) 2020-2024 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,13 +27,12 @@ from vyos.ifconfig import Interface from vyos.template import render from vyos.template import render_to_string from vyos.utils.dict import dict_search +from vyos.utils.kernel import check_kmod from vyos.utils.network import get_interface_config from vyos.utils.network import get_vrf_members from vyos.utils.network import interface_exists from vyos.utils.process import call from vyos.utils.process import cmd -from vyos.utils.process import popen -from vyos.utils.process import run from vyos.utils.system import sysctl_write from vyos import ConfigError from vyos import frr @@ -41,17 +40,29 @@ from vyos import airbag airbag.enable() config_file = '/etc/iproute2/rt_tables.d/vyos-vrf.conf' -nft_vrf_config = '/tmp/nftables-vrf-zones' - -def has_rule(af : str, priority : int, table : str): - """ Check if a given ip rule exists """ +k_mod = ['vrf'] + +def has_rule(af : str, priority : int, table : str=None): + """ + Check if a given ip rule exists + $ ip --json -4 rule show + [{'l3mdev': None, 'priority': 1000, 'src': 'all'}, + {'action': 'unreachable', 'l3mdev': None, 'priority': 2000, 'src': 'all'}, + {'priority': 32765, 'src': 'all', 'table': 'local'}, + {'priority': 32766, 'src': 'all', 'table': 'main'}, + {'priority': 32767, 'src': 'all', 'table': 'default'}] + """ if af not in ['-4', '-6']: raise ValueError() - command = f'ip -j {af} rule show' + command = f'ip --detail --json {af} rule show' for tmp in loads(cmd(command)): - if {'priority', 'table'} <= set(tmp): + if 'priority' in tmp and 'table' in tmp: if tmp['priority'] == priority and tmp['table'] == table: return True + elif 'priority' in tmp and table in tmp: + # l3mdev table has a different layout + if tmp['priority'] == priority: + return True return False def vrf_interfaces(c, match): @@ -173,8 +184,6 @@ def verify(vrf): def generate(vrf): # Render iproute2 VR helper names render(config_file, 'iproute2/vrf.conf.j2', vrf) - # Render nftables zones config - render(nft_vrf_config, 'firewall/nftables-vrf-zones.j2', vrf) # Render VRF Kernel/Zebra route-map filters vrf['frr_zebra_config'] = render_to_string('frr/zebra.vrf.route-map.frr.j2', vrf) @@ -227,14 +236,6 @@ def apply(vrf): sysctl_write('net.vrf.strict_mode', strict_mode) if 'name' in vrf: - # Separate VRFs in conntrack table - # check if table already exists - _, err = popen('nft list table inet vrf_zones') - # If not, create a table - if err and os.path.exists(nft_vrf_config): - cmd(f'nft -f {nft_vrf_config}') - os.unlink(nft_vrf_config) - # Linux routing uses rules to find tables - routing targets are then # looked up in those tables. If the lookup got a matching route, the # process ends. @@ -318,17 +319,11 @@ def apply(vrf): frr_cfg.add_before(frr.default_add_before, vrf['frr_zebra_config']) frr_cfg.commit_configuration(zebra_daemon) - # return to default lookup preference when no VRF is configured - if 'name' not in vrf: - # Remove VRF zones table from nftables - tmp = run('nft list table inet vrf_zones') - if tmp == 0: - cmd('nft delete table inet vrf_zones') - return None if __name__ == '__main__': try: + check_kmod(k_mod) c = get_config() verify(c) generate(c) diff --git a/src/migration-scripts/bgp/4-to-5 b/src/migration-scripts/bgp/4-to-5 new file mode 100755 index 000000000..c4eb9ec72 --- /dev/null +++ b/src/migration-scripts/bgp/4-to-5 @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 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/>. + +# Delete 'protocols bgp address-family ipv6-unicast route-target vpn +# import/export', if 'protocols bgp address-family ipv6-unicast +# route-target vpn both' exists + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree + +if len(argv) < 2: + print("Must specify file name!") + exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +config = ConfigTree(config_file) + +bgp_base = ['protocols', 'bgp'] +# Delete 'import/export' in default vrf if 'both' exists +if config.exists(bgp_base): + for address_family in ['ipv4-unicast', 'ipv6-unicast']: + rt_path = bgp_base + ['address-family', address_family, 'route-target', + 'vpn'] + if config.exists(rt_path + ['both']): + if config.exists(rt_path + ['import']): + config.delete(rt_path + ['import']) + if config.exists(rt_path + ['export']): + config.delete(rt_path + ['export']) + +# Delete import/export in vrfs if both exists +if config.exists(['vrf', 'name']): + for vrf in config.list_nodes(['vrf', 'name']): + vrf_base = ['vrf', 'name', vrf] + for address_family in ['ipv4-unicast', 'ipv6-unicast']: + rt_path = vrf_base + bgp_base + ['address-family', address_family, + 'route-target', 'vpn'] + if config.exists(rt_path + ['both']): + if config.exists(rt_path + ['import']): + config.delete(rt_path + ['import']) + if config.exists(rt_path + ['export']): + config.delete(rt_path + ['export']) + +try: + with open(file_name, 'w') as f: + f.write(config.to_string()) +except OSError as e: + print(f'Failed to save the modified config: {e}') + exit(1) diff --git a/src/migration-scripts/https/5-to-6 b/src/migration-scripts/https/5-to-6 index 6d6efd32c..0090adccb 100755 --- a/src/migration-scripts/https/5-to-6 +++ b/src/migration-scripts/https/5-to-6 @@ -43,11 +43,11 @@ if not config.exists(base): # Nothing to do sys.exit(0) -if config.exists(base + ['certificates']): +if config.exists(base + ['certificates', 'certbot']): # both domain-name and email must be set on CLI - ensured by previous verify() domain_names = config.return_values(base + ['certificates', 'certbot', 'domain-name']) email = config.return_value(base + ['certificates', 'certbot', 'email']) - config.delete(base + ['certificates']) + config.delete(base + ['certificates', 'certbot']) # Set default certname based on domain-name cert_name = 'https-' + domain_names[0].split('.')[0] diff --git a/src/migration-scripts/policy/4-to-5 b/src/migration-scripts/policy/4-to-5 index f6f889c35..5b8fee17e 100755 --- a/src/migration-scripts/policy/4-to-5 +++ b/src/migration-scripts/policy/4-to-5 @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2022 VyOS maintainers and contributors +# Copyright (C) 2022-2024 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 @@ -37,7 +37,53 @@ base4 = ['policy', 'route'] base6 = ['policy', 'route6'] config = ConfigTree(config_file) + +def delete_orphaned_interface_policy(config, iftype, ifname, vif=None, vifs=None, vifc=None): + """Delete unexpected policy on interfaces in cases when + policy does not exist but inreface has a policy configuration + Example T5941: + set interfaces bonding bond0 vif 995 policy + """ + if_path = ['interfaces', iftype, ifname] + + if vif: + if_path += ['vif', vif] + elif vifs: + if_path += ['vif-s', vifs] + if vifc: + if_path += ['vif-c', vifc] + + if not config.exists(if_path + ['policy']): + return + + config.delete(if_path + ['policy']) + + if not config.exists(base4) and not config.exists(base6): + # Delete orphaned nodes on interfaces T5941 + for iftype in config.list_nodes(['interfaces']): + for ifname in config.list_nodes(['interfaces', iftype]): + delete_orphaned_interface_policy(config, iftype, ifname) + + if config.exists(['interfaces', iftype, ifname, 'vif']): + for vif in config.list_nodes(['interfaces', iftype, ifname, 'vif']): + delete_orphaned_interface_policy(config, iftype, ifname, vif=vif) + + if config.exists(['interfaces', iftype, ifname, 'vif-s']): + for vifs in config.list_nodes(['interfaces', iftype, ifname, 'vif-s']): + delete_orphaned_interface_policy(config, iftype, ifname, vifs=vifs) + + if config.exists(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): + for vifc in config.list_nodes(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): + delete_orphaned_interface_policy(config, iftype, ifname, vifs=vifs, vifc=vifc) + + try: + with open(file_name, 'w') as f: + f.write(config.to_string()) + except OSError as e: + print("Failed to save the modified config: {}".format(e)) + exit(1) + # Nothing to do exit(0) diff --git a/src/migration-scripts/qos/1-to-2 b/src/migration-scripts/qos/1-to-2 index cca32d06e..666811e5a 100755 --- a/src/migration-scripts/qos/1-to-2 +++ b/src/migration-scripts/qos/1-to-2 @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2022 VyOS maintainers and contributors +# Copyright (C) 2022-2024 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 @@ -40,7 +40,53 @@ with open(file_name, 'r') as f: base = ['traffic-policy'] config = ConfigTree(config_file) + +def delete_orphaned_interface_policy(config, iftype, ifname, vif=None, vifs=None, vifc=None): + """Delete unexpected traffic-policy on interfaces in cases when + policy does not exist but inreface has a policy configuration + Example T5941: + set interfaces bonding bond0 vif 995 traffic-policy + """ + if_path = ['interfaces', iftype, ifname] + + if vif: + if_path += ['vif', vif] + elif vifs: + if_path += ['vif-s', vifs] + if vifc: + if_path += ['vif-c', vifc] + + if not config.exists(if_path + ['traffic-policy']): + return + + config.delete(if_path + ['traffic-policy']) + + if not config.exists(base): + # Delete orphaned nodes on interfaces T5941 + for iftype in config.list_nodes(['interfaces']): + for ifname in config.list_nodes(['interfaces', iftype]): + delete_orphaned_interface_policy(config, iftype, ifname) + + if config.exists(['interfaces', iftype, ifname, 'vif']): + for vif in config.list_nodes(['interfaces', iftype, ifname, 'vif']): + delete_orphaned_interface_policy(config, iftype, ifname, vif=vif) + + if config.exists(['interfaces', iftype, ifname, 'vif-s']): + for vifs in config.list_nodes(['interfaces', iftype, ifname, 'vif-s']): + delete_orphaned_interface_policy(config, iftype, ifname, vifs=vifs) + + if config.exists(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): + for vifc in config.list_nodes(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): + delete_orphaned_interface_policy(config, iftype, ifname, vifs=vifs, vifc=vifc) + + try: + with open(file_name, 'w') as f: + f.write(config.to_string()) + except OSError as e: + print("Failed to save the modified config: {}".format(e)) + exit(1) + # Nothing to do exit(0) diff --git a/src/op_mode/image_installer.py b/src/op_mode/image_installer.py index fad6face7..501e9b804 100755 --- a/src/op_mode/image_installer.py +++ b/src/op_mode/image_installer.py @@ -69,8 +69,8 @@ MSG_WARN_ISO_SIGN_INVALID: str = 'Signature is not valid. Do you want to continu MSG_WARN_ISO_SIGN_UNAVAL: str = 'Signature is not available. Do you want to continue with installation?' MSG_WARN_ROOT_SIZE_TOOBIG: str = 'The size is too big. Try again.' MSG_WARN_ROOT_SIZE_TOOSMALL: str = 'The size is too small. Try again' -MSG_WARN_IMAGE_NAME_WRONG: str = 'The suggested name is unsupported!\n' -'It must be between 1 and 32 characters long and contains only the next characters: .+-_ a-z A-Z 0-9' +MSG_WARN_IMAGE_NAME_WRONG: str = 'The suggested name is unsupported!\n'\ +'It must be between 1 and 64 characters long and contains only the next characters: .+-_ a-z A-Z 0-9' CONST_MIN_DISK_SIZE: int = 2147483648 # 2 GB CONST_MIN_ROOT_SIZE: int = 1610612736 # 1.5 GB # a reserved space: 2MB for header, 1 MB for BIOS partition, 256 MB for EFI @@ -812,7 +812,11 @@ def add_image(image_path: str, vrf: str = None, username: str = '', f'Adding image would downgrade image tools to v.{cfg_ver}; disallowed') if not no_prompt: - image_name: str = ask_input(MSG_INPUT_IMAGE_NAME, version_name) + while True: + image_name: str = ask_input(MSG_INPUT_IMAGE_NAME, version_name) + if image.validate_name(image_name): + break + print(MSG_WARN_IMAGE_NAME_WRONG) set_as_default: bool = ask_yes_no(MSG_INPUT_IMAGE_DEFAULT, default=True) else: image_name: str = version_name @@ -867,7 +871,7 @@ def add_image(image_path: str, vrf: str = None, username: str = '', except Exception as err: # unmount an ISO and cleanup cleanup([str(iso_path)]) - exit(f'Whooops: {err}') + exit(f'Error: {err}') def parse_arguments() -> Namespace: diff --git a/src/op_mode/multicast.py b/src/op_mode/multicast.py new file mode 100755 index 000000000..0666f8af3 --- /dev/null +++ b/src/op_mode/multicast.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 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 json +import sys +import typing + +from tabulate import tabulate +from vyos.utils.process import cmd + +import vyos.opmode + +ArgFamily = typing.Literal['inet', 'inet6'] + +def _get_raw_data(family, interface=None): + tmp = 'ip -4' + if family == 'inet6': + tmp = 'ip -6' + tmp = f'{tmp} -j maddr show' + if interface: + tmp = f'{tmp} dev {interface}' + output = cmd(tmp) + data = json.loads(output) + if not data: + return [] + return data + +def _get_formatted_output(raw_data): + data_entries = [] + + # sort result by interface name + for interface in sorted(raw_data, key=lambda x: x['ifname']): + for address in interface['maddr']: + tmp = [] + tmp.append(interface['ifname']) + tmp.append(address['family']) + tmp.append(address['address']) + + data_entries.append(tmp) + + headers = ["Interface", "Family", "Address"] + output = tabulate(data_entries, headers, numalign="left") + return output + +def show_group(raw: bool, family: ArgFamily, interface: typing.Optional[str]): + multicast_data = _get_raw_data(family=family, interface=interface) + if raw: + return multicast_data + else: + return _get_formatted_output(multicast_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_openvpn.py b/src/op_mode/show_openvpn.py index e29e594a5..6abafc8b6 100755 --- a/src/op_mode/show_openvpn.py +++ b/src/op_mode/show_openvpn.py @@ -63,9 +63,11 @@ def get_vpn_tunnel_address(peer, interface): # filter out subnet entries lst = [l for l in lst[1:] if '/' not in l.split(',')[0]] - tunnel_ip = lst[0].split(',')[0] + if lst: + tunnel_ip = lst[0].split(',')[0] + return tunnel_ip - return tunnel_ip + return 'n/a' def get_status(mode, interface): status_file = '/var/run/openvpn/{}.status'.format(interface) |