diff options
Diffstat (limited to 'src')
| -rwxr-xr-x | src/conf_mode/protocols_bgp.py | 10 | ||||
| -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/qos/1-to-2 | 48 | ||||
| -rwxr-xr-x | src/op_mode/image_installer.py | 12 | ||||
| -rwxr-xr-x | src/op_mode/show_openvpn.py | 6 | 
7 files changed, 158 insertions, 36 deletions
| 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/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/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/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) | 
