diff options
Diffstat (limited to 'src')
27 files changed, 887 insertions, 159 deletions
| diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py index 4b7ab3444..aceb27fb0 100755 --- a/src/conf_mode/container.py +++ b/src/conf_mode/container.py @@ -376,11 +376,11 @@ def generate(container):                  'name': network,                  'id' : sha256(f'{network}'.encode()).hexdigest(),                  'driver': 'bridge', -                'network_interface': f'podman-{network}', +                'network_interface': f'pod-{network}',                  'subnets': [],                  'ipv6_enabled': False,                  'internal': False, -                'dns_enabled': False, +                'dns_enabled': True,                  'ipam_options': {                      'driver': 'host-local'                  } @@ -479,7 +479,7 @@ def apply(container):      # the network interface in advance      if 'network' in container:          for network, network_config in container['network'].items(): -            network_name = f'podman-{network}' +            network_name = f'pod-{network}'              # T5147: Networks are started only as soon as there is a consumer.              # If only a network is created in the first place, no need to assign              # it to a VRF as there's no consumer, yet. diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index c41a442df..190587980 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -282,6 +282,9 @@ def verify_rule(firewall, rule_conf, ipv6):                  if rule_conf['protocol'] not in ['tcp', 'udp', 'tcp_udp']:                      raise ConfigError('Protocol must be tcp, udp, or tcp_udp when specifying a port or port-group') +            if 'port' in side_conf and dict_search_args(side_conf, 'group', 'port_group'): +                raise ConfigError(f'{side} port-group and port cannot both be defined') +      if 'log_options' in rule_conf:          if 'log' not in rule_conf or 'enable' not in rule_conf['log']:              raise ConfigError('log-options defined, but log is not enable') diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 13d84a6fe..6f227b0d1 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2019-2022 VyOS maintainers and contributors +# Copyright (C) 2019-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 @@ -597,7 +597,7 @@ def generate_pki_files(openvpn):  def generate(openvpn):      interface = openvpn['ifname']      directory = os.path.dirname(cfg_file.format(**openvpn)) -    plugin_dir = '/usr/lib/openvpn' +    openvpn['plugin_dir'] = '/usr/lib/openvpn'      # create base config directory on demand      makedir(directory, user, group)      # enforce proper permissions on /run/openvpn diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index cf553f0e8..66505e58d 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -484,26 +484,15 @@ def generate(bgp):      if not bgp or 'deleted' in bgp:          return None -    bgp['protocol'] = 'bgp' # required for frr/vrf.route-map.frr.j2 -    bgp['frr_zebra_config'] = render_to_string('frr/vrf.route-map.frr.j2', bgp)      bgp['frr_bgpd_config']  = render_to_string('frr/bgpd.frr.j2', bgp) -      return None  def apply(bgp):      bgp_daemon = 'bgpd' -    zebra_daemon = 'zebra'      # Save original configuration prior to starting any commit actions      frr_cfg = frr.FRRConfig() -    # The route-map used for the FIB (zebra) is part of the zebra daemon -    frr_cfg.load_configuration(zebra_daemon) -    frr_cfg.modify_section(r'(\s+)?ip protocol bgp route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') -    if 'frr_zebra_config' in bgp: -        frr_cfg.add_before(frr.default_add_before, bgp['frr_zebra_config']) -    frr_cfg.commit_configuration(zebra_daemon) -      # Generate empty helper string which can be ammended to FRR commands, it      # will be either empty (default VRF) or contain the "vrf <name" statement      vrf = '' diff --git a/src/conf_mode/protocols_eigrp.py b/src/conf_mode/protocols_eigrp.py index c1a1a45e1..609b39065 100755 --- a/src/conf_mode/protocols_eigrp.py +++ b/src/conf_mode/protocols_eigrp.py @@ -69,8 +69,6 @@ def get_config(config=None):      # Merge policy dict into "regular" config dict      eigrp = dict_merge(tmp, eigrp) -    import pprint -    pprint.pprint(eigrp)      return eigrp  def verify(eigrp): @@ -80,24 +78,14 @@ def generate(eigrp):      if not eigrp or 'deleted' in eigrp:          return None -    eigrp['protocol'] = 'eigrp' # required for frr/vrf.route-map.frr.j2 -    eigrp['frr_zebra_config'] = render_to_string('frr/vrf.route-map.frr.j2', eigrp)      eigrp['frr_eigrpd_config']  = render_to_string('frr/eigrpd.frr.j2', eigrp)  def apply(eigrp):      eigrp_daemon = 'eigrpd' -    zebra_daemon = 'zebra'      # Save original configuration prior to starting any commit actions      frr_cfg = frr.FRRConfig() -    # The route-map used for the FIB (zebra) is part of the zebra daemon -    frr_cfg.load_configuration(zebra_daemon) -    frr_cfg.modify_section(r'(\s+)?ip protocol eigrp route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') -    if 'frr_zebra_config' in eigrp: -        frr_cfg.add_before(frr.default_add_before, eigrp['frr_zebra_config']) -    frr_cfg.commit_configuration(zebra_daemon) -      # Generate empty helper string which can be ammended to FRR commands, it      # will be either empty (default VRF) or contain the "vrf <name" statement      vrf = '' diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py index cb8ea3be4..af2937db8 100755 --- a/src/conf_mode/protocols_isis.py +++ b/src/conf_mode/protocols_isis.py @@ -203,7 +203,7 @@ def verify(isis):          if list(set(global_range) & set(local_range)):              raise ConfigError(f'Segment-Routing Global Block ({g_low_label_value}/{g_high_label_value}) '\                                f'conflicts with Local Block ({l_low_label_value}/{l_high_label_value})!') -         +      # Check for a blank or invalid value per prefix      if dict_search('segment_routing.prefix', isis):          for prefix, prefix_config in isis['segment_routing']['prefix'].items(): @@ -218,7 +218,7 @@ def verify(isis):      if dict_search('segment_routing.prefix', isis):          for prefix, prefix_config in isis['segment_routing']['prefix'].items():              if 'absolute' in prefix_config: -                if ("explicit_null" in prefix_config['absolute']) and ("no_php_flag" in prefix_config['absolute']):  +                if ("explicit_null" in prefix_config['absolute']) and ("no_php_flag" in prefix_config['absolute']):                      raise ConfigError(f'Segment routing prefix {prefix} cannot have both explicit-null '\                                        f'and no-php-flag configured at the same time.')              elif 'index' in prefix_config: @@ -232,25 +232,15 @@ def generate(isis):      if not isis or 'deleted' in isis:          return None -    isis['protocol'] = 'isis' # required for frr/vrf.route-map.frr.j2 -    isis['frr_zebra_config'] = render_to_string('frr/vrf.route-map.frr.j2', isis)      isis['frr_isisd_config'] = render_to_string('frr/isisd.frr.j2', isis)      return None  def apply(isis):      isis_daemon = 'isisd' -    zebra_daemon = 'zebra'      # Save original configuration prior to starting any commit actions      frr_cfg = frr.FRRConfig() -    # The route-map used for the FIB (zebra) is part of the zebra daemon -    frr_cfg.load_configuration(zebra_daemon) -    frr_cfg.modify_section('(\s+)?ip protocol isis route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') -    if 'frr_zebra_config' in isis: -        frr_cfg.add_before(frr.default_add_before, isis['frr_zebra_config']) -    frr_cfg.commit_configuration(zebra_daemon) -      # Generate empty helper string which can be ammended to FRR commands, it      # will be either empty (default VRF) or contain the "vrf <name" statement      vrf = '' diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py index eb64afa0c..fbb876123 100755 --- a/src/conf_mode/protocols_ospf.py +++ b/src/conf_mode/protocols_ospf.py @@ -256,25 +256,15 @@ def generate(ospf):      if not ospf or 'deleted' in ospf:          return None -    ospf['protocol'] = 'ospf' # required for frr/vrf.route-map.frr.j2 -    ospf['frr_zebra_config'] = render_to_string('frr/vrf.route-map.frr.j2', ospf)      ospf['frr_ospfd_config'] = render_to_string('frr/ospfd.frr.j2', ospf)      return None  def apply(ospf):      ospf_daemon = 'ospfd' -    zebra_daemon = 'zebra'      # Save original configuration prior to starting any commit actions      frr_cfg = frr.FRRConfig() -    # The route-map used for the FIB (zebra) is part of the zebra daemon -    frr_cfg.load_configuration(zebra_daemon) -    frr_cfg.modify_section('(\s+)?ip protocol ospf route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') -    if 'frr_zebra_config' in ospf: -        frr_cfg.add_before(frr.default_add_before, ospf['frr_zebra_config']) -    frr_cfg.commit_configuration(zebra_daemon) -      # Generate empty helper string which can be ammended to FRR commands, it      # will be either empty (default VRF) or contain the "vrf <name" statement      vrf = '' @@ -292,6 +282,7 @@ def apply(ospf):      if 'frr_ospfd_config' in ospf:          frr_cfg.add_before(frr.default_add_before, ospf['frr_ospfd_config']) +      frr_cfg.commit_configuration(ospf_daemon)      return None diff --git a/src/conf_mode/protocols_ospfv3.py b/src/conf_mode/protocols_ospfv3.py index 1e2c02d03..ee1fdd399 100755 --- a/src/conf_mode/protocols_ospfv3.py +++ b/src/conf_mode/protocols_ospfv3.py @@ -146,25 +146,15 @@ def generate(ospfv3):      if not ospfv3 or 'deleted' in ospfv3:          return None -    ospfv3['protocol'] = 'ospf6' # required for frr/vrf.route-map.v6.frr.j2 -    ospfv3['frr_zebra_config'] = render_to_string('frr/vrf.route-map.v6.frr.j2', ospfv3)      ospfv3['new_frr_config'] = render_to_string('frr/ospf6d.frr.j2', ospfv3)      return None  def apply(ospfv3):      ospf6_daemon = 'ospf6d' -    zebra_daemon = 'zebra'      # Save original configuration prior to starting any commit actions      frr_cfg = frr.FRRConfig() -    # The route-map used for the FIB (zebra) is part of the zebra daemon -    frr_cfg.load_configuration(zebra_daemon) -    frr_cfg.modify_section('(\s+)?ipv6 protocol ospf6 route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') -    if 'frr_zebra_config' in ospfv3: -        frr_cfg.add_before(frr.default_add_before, ospfv3['frr_zebra_config']) -    frr_cfg.commit_configuration(zebra_daemon) -      # Generate empty helper string which can be ammended to FRR commands, it      # will be either empty (default VRF) or contain the "vrf <name" statement      vrf = '' diff --git a/src/conf_mode/protocols_static.py b/src/conf_mode/protocols_static.py index 3e5ebb805..7b6150696 100755 --- a/src/conf_mode/protocols_static.py +++ b/src/conf_mode/protocols_static.py @@ -105,20 +105,14 @@ def generate(static):  def apply(static):      static_daemon = 'staticd' -    zebra_daemon = 'zebra'      # Save original configuration prior to starting any commit actions      frr_cfg = frr.FRRConfig() - -    # The route-map used for the FIB (zebra) is part of the zebra daemon -    frr_cfg.load_configuration(zebra_daemon) -    frr_cfg.modify_section(r'^ip protocol static route-map [-a-zA-Z0-9.]+', '') -    frr_cfg.commit_configuration(zebra_daemon)      frr_cfg.load_configuration(static_daemon)      if 'vrf' in static:          vrf = static['vrf'] -        frr_cfg.modify_section(f'^vrf {vrf}', stop_pattern='^exit', remove_stop_mark=True) +        frr_cfg.modify_section(f'^vrf {vrf}', stop_pattern='^exit-vrf', remove_stop_mark=True)      else:          frr_cfg.modify_section(r'^ip route .*')          frr_cfg.modify_section(r'^ipv6 route .*') diff --git a/src/conf_mode/service_monitoring_telegraf.py b/src/conf_mode/service_monitoring_telegraf.py index 363408679..47510ce80 100755 --- a/src/conf_mode/service_monitoring_telegraf.py +++ b/src/conf_mode/service_monitoring_telegraf.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2021-2022 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 @@ -15,6 +15,7 @@  # along with this program.  If not, see <http://www.gnu.org/licenses/>.  import os +import socket  import json  from sys import exit @@ -57,6 +58,13 @@ def get_nft_filter_chains():      return chain_list +def get_hostname() -> str: +    try: +        hostname = socket.getfqdn() +    except socket.gaierror: +        hostname = socket.gethostname() +    return hostname +  def get_config(config=None):      if config:          conf = config @@ -79,6 +87,7 @@ def get_config(config=None):      monitoring = dict_merge(default_values, monitoring)      monitoring['custom_scripts_dir'] = custom_scripts_dir +    monitoring['hostname'] = get_hostname()      monitoring['interfaces_ethernet'] = Section.interfaces('ethernet', vlan=False)      monitoring['nft_chains'] = get_nft_filter_chains() diff --git a/src/conf_mode/system-ip.py b/src/conf_mode/system-ip.py index 0c5063ed3..95865c690 100755 --- a/src/conf_mode/system-ip.py +++ b/src/conf_mode/system-ip.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2019-2022 VyOS maintainers and contributors +# Copyright (C) 2019-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 @@ -18,12 +18,15 @@ from sys import exit  from vyos.config import Config  from vyos.configdict import dict_merge +from vyos.configverify import verify_route_map +from vyos.template import render_to_string  from vyos.util import call  from vyos.util import dict_search  from vyos.util import sysctl_write  from vyos.util import write_file  from vyos.xml import defaults  from vyos import ConfigError +from vyos import frr  from vyos import airbag  airbag.enable() @@ -40,13 +43,30 @@ def get_config(config=None):      default_values = defaults(base)      opt = dict_merge(default_values, opt) +    # When working with FRR we need to know the corresponding address-family +    opt['afi'] = 'ip' + +    # We also need the route-map information from the config +    # +    # XXX: one MUST always call this without the key_mangling() option! See +    # vyos.configverify.verify_common_route_maps() for more information. +    tmp = {'policy' : {'route-map' : conf.get_config_dict(['policy', 'route-map'], +                                                          get_first_key=True)}} +    # Merge policy dict into "regular" config dict +    opt = dict_merge(tmp, opt)      return opt  def verify(opt): -    pass +    if 'protocol' in opt: +        for protocol, protocol_options in opt['protocol'].items(): +            if 'route_map' in protocol_options: +                verify_route_map(protocol_options['route_map'], opt) +    return  def generate(opt): -    pass +    if 'protocol' in opt: +        opt['frr_zebra_config'] = render_to_string('frr/zebra.route-map.frr.j2', opt) +    return  def apply(opt):      # Apply ARP threshold values @@ -78,6 +98,18 @@ def apply(opt):      value = '1' if (tmp != None) else '0'      sysctl_write('net.ipv4.fib_multipath_hash_policy', value) +    if 'protocol' in opt: +        zebra_daemon = 'zebra' +        # Save original configuration prior to starting any commit actions +        frr_cfg = frr.FRRConfig() + +        # The route-map used for the FIB (zebra) is part of the zebra daemon +        frr_cfg.load_configuration(zebra_daemon) +        frr_cfg.modify_section(r'ip protocol \w+ route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') +        if 'frr_zebra_config' in opt: +            frr_cfg.add_before(frr.default_add_before, opt['frr_zebra_config']) +        frr_cfg.commit_configuration(zebra_daemon) +  if __name__ == '__main__':      try:          c = get_config() diff --git a/src/conf_mode/system-ipv6.py b/src/conf_mode/system-ipv6.py index 26aacf46b..b6d3a79c3 100755 --- a/src/conf_mode/system-ipv6.py +++ b/src/conf_mode/system-ipv6.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2019-2022 VyOS maintainers and contributors +# Copyright (C) 2019-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,11 +19,14 @@ import os  from sys import exit  from vyos.config import Config  from vyos.configdict import dict_merge +from vyos.configverify import verify_route_map +from vyos.template import render_to_string  from vyos.util import dict_search  from vyos.util import sysctl_write  from vyos.util import write_file  from vyos.xml import defaults  from vyos import ConfigError +from vyos import frr  from vyos import airbag  airbag.enable() @@ -41,13 +44,30 @@ def get_config(config=None):      default_values = defaults(base)      opt = dict_merge(default_values, opt) +    # When working with FRR we need to know the corresponding address-family +    opt['afi'] = 'ipv6' + +    # We also need the route-map information from the config +    # +    # XXX: one MUST always call this without the key_mangling() option! See +    # vyos.configverify.verify_common_route_maps() for more information. +    tmp = {'policy' : {'route-map' : conf.get_config_dict(['policy', 'route-map'], +                                                          get_first_key=True)}} +    # Merge policy dict into "regular" config dict +    opt = dict_merge(tmp, opt)      return opt  def verify(opt): -    pass +    if 'protocol' in opt: +        for protocol, protocol_options in opt['protocol'].items(): +            if 'route_map' in protocol_options: +                verify_route_map(protocol_options['route_map'], opt) +    return  def generate(opt): -    pass +    if 'protocol' in opt: +        opt['frr_zebra_config'] = render_to_string('frr/zebra.route-map.frr.j2', opt) +    return  def apply(opt):      # configure multipath @@ -78,6 +98,18 @@ def apply(opt):              if name == 'accept_dad':                  write_file(os.path.join(root, name), value) +    if 'protocol' in opt: +        zebra_daemon = 'zebra' +        # Save original configuration prior to starting any commit actions +        frr_cfg = frr.FRRConfig() + +        # The route-map used for the FIB (zebra) is part of the zebra daemon +        frr_cfg.load_configuration(zebra_daemon) +        frr_cfg.modify_section(r'ipv6 protocol \w+ route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') +        if 'frr_zebra_config' in opt: +            frr_cfg.add_before(frr.default_add_before, opt['frr_zebra_config']) +        frr_cfg.commit_configuration(zebra_daemon) +  if __name__ == '__main__':      try:          c = get_config() diff --git a/src/conf_mode/vpn_pptp.py b/src/conf_mode/vpn_pptp.py index 7550c411e..986a19972 100755 --- a/src/conf_mode/vpn_pptp.py +++ b/src/conf_mode/vpn_pptp.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2018-2020 VyOS maintainers and contributors +# Copyright (C) 2018-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 @@ -44,6 +44,8 @@ default_pptp = {      'radius_nas_ip' : '',      'radius_source_address' : '',      'radius_shaper_attr' : '', +    'radius_shaper_enable': False, +    'radius_shaper_multiplier': '',      'radius_shaper_vendor': '',      'radius_dynamic_author' : '',      'chap_secrets_file': pptp_chap_secrets, # used in Jinja2 template @@ -183,15 +185,18 @@ def get_config(config=None):              pptp['radius_dynamic_author'] = dae +        # Rate limit +        if conf.exists(['rate-limit', 'attribute']): +            pptp['radius_shaper_attr'] = conf.return_value(['rate-limit', 'attribute']) +          if conf.exists(['rate-limit', 'enable']): -            pptp['radius_shaper_attr'] = 'Filter-Id' -            c_attr = ['rate-limit', 'enable', 'attribute'] -            if conf.exists(c_attr): -                pptp['radius_shaper_attr'] = conf.return_value(c_attr) - -            c_vendor = ['rate-limit', 'enable', 'vendor'] -            if conf.exists(c_vendor): -                pptp['radius_shaper_vendor'] = conf.return_value(c_vendor) +            pptp['radius_shaper_enable'] = True + +        if conf.exists(['rate-limit', 'multiplier']): +            pptp['radius_shaper_multiplier'] = conf.return_value(['rate-limit', 'multiplier']) + +        if conf.exists(['rate-limit', 'vendor']): +            pptp['radius_shaper_vendor'] = conf.return_value(['rate-limit', 'vendor'])      conf.set_level(base_path)      if conf.exists(['client-ip-pool']): diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py index c17cca3bd..a7ef4cb5c 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -20,9 +20,12 @@ from sys import exit  from json import loads  from vyos.config import Config +from vyos.configdict import dict_merge  from vyos.configdict import node_changed +from vyos.configverify import verify_route_map  from vyos.ifconfig import Interface  from vyos.template import render +from vyos.template import render_to_string  from vyos.util import call  from vyos.util import cmd  from vyos.util import dict_search @@ -99,6 +102,14 @@ def get_config(config=None):          routes = vrf_routing(conf, name)          if routes: vrf['vrf_remove'][name]['route'] = routes +    # We also need the route-map information from the config +    # +    # XXX: one MUST always call this without the key_mangling() option! See +    # vyos.configverify.verify_common_route_maps() for more information. +    tmp = {'policy' : {'route-map' : conf.get_config_dict(['policy', 'route-map'], +                                                          get_first_key=True)}} +    # Merge policy dict into "regular" config dict +    vrf = dict_merge(tmp, vrf)      return vrf  def verify(vrf): @@ -116,35 +127,50 @@ def verify(vrf):          reserved_names = ["add", "all", "broadcast", "default", "delete", "dev", "get", "inet", "mtu", "link", "type",                            "vrf"]          table_ids = [] -        for name, config in vrf['name'].items(): +        for name, vrf_config in vrf['name'].items():              # Reserved VRF names              if name in reserved_names:                  raise ConfigError(f'VRF name "{name}" is reserved and connot be used!')              # table id is mandatory -            if 'table' not in config: +            if 'table' not in vrf_config:                  raise ConfigError(f'VRF "{name}" table id is mandatory!')              # routing table id can't be changed - OS restriction              if os.path.isdir(f'/sys/class/net/{name}'):                  tmp = str(dict_search('linkinfo.info_data.table', get_interface_config(name))) -                if tmp and tmp != config['table']: +                if tmp and tmp != vrf_config['table']:                      raise ConfigError(f'VRF "{name}" table id modification not possible!')              # VRf routing table ID must be unique on the system -            if config['table'] in table_ids: +            if vrf_config['table'] in table_ids:                  raise ConfigError(f'VRF "{name}" table id is not unique!') -            table_ids.append(config['table']) +            table_ids.append(vrf_config['table']) + +            tmp = dict_search('ip.protocol', vrf_config) +            if tmp != None: +                for protocol, protocol_options in tmp.items(): +                    if 'route_map' in protocol_options: +                        verify_route_map(protocol_options['route_map'], vrf) + +            tmp = dict_search('ipv6.protocol', vrf_config) +            if tmp != None: +                for protocol, protocol_options in tmp.items(): +                    if 'route_map' in protocol_options: +                        verify_route_map(protocol_options['route_map'], vrf)      return None  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) -    return None +    # Render VRF Kernel/Zebra route-map filters +    vrf['frr_zebra_config'] = render_to_string('frr/zebra.vrf.route-map.frr.j2', vrf) +    return None  def apply(vrf):      # Documentation @@ -249,6 +275,17 @@ def apply(vrf):              nft_add_element = f'add element inet vrf_zones ct_iface_map {{ "{name}" : {table} }}'              cmd(f'nft {nft_add_element}') +    # Apply FRR filters +    zebra_daemon = 'zebra' +    # Save original configuration prior to starting any commit actions +    frr_cfg = frr.FRRConfig() + +    # The route-map used for the FIB (zebra) is part of the zebra daemon +    frr_cfg.load_configuration(zebra_daemon) +    frr_cfg.modify_section(f'^vrf .+', stop_pattern='^exit-vrf', remove_stop_mark=True) +    if 'frr_zebra_config' in 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: diff --git a/src/conf_mode/vrf_vni.py b/src/conf_mode/vrf_vni.py index 585fdbebf..56069ecbd 100755..100644 --- a/src/conf_mode/vrf_vni.py +++ b/src/conf_mode/vrf_vni.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2020-2021 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 @@ -24,26 +24,35 @@ from vyos import frr  from vyos import airbag  airbag.enable() -frr_daemon = 'zebra' -  def get_config(config=None):      if config:          conf = config      else:          conf = Config() -    base = ['vrf'] -    vrf = conf.get_config_dict(base, get_first_key=True) +    vrf_name = None +    if len(argv) > 1: +        vrf_name = argv[1] + +    base = ['vrf', 'name', vrf_name] +    vrf = { 'name' : conf.get_config_dict(base, key_mangling=('-', '_'), +                                          get_first_key=False) } +      return vrf  def verify(vrf): +    if len(argv) < 2: +        raise ConfigError('VRF parameter not specified when valling vrf_vni.py') +      return None  def generate(vrf): -    vrf['new_frr_config'] = render_to_string('frr/vrf-vni.frr.j2', vrf) +    vrf['new_frr_config'] = render_to_string('frr/zebra.vrf.route-map.frr.j2', vrf)      return None  def apply(vrf): +    frr_daemon = 'zebra' +      # add configuration to FRR      frr_cfg = frr.FRRConfig()      frr_cfg.load_configuration(frr_daemon) diff --git a/src/etc/systemd/system/hostapd@.service.d/override.conf b/src/etc/systemd/system/hostapd@.service.d/override.conf index bb8e81d7a..926c07f94 100644 --- a/src/etc/systemd/system/hostapd@.service.d/override.conf +++ b/src/etc/systemd/system/hostapd@.service.d/override.conf @@ -1,6 +1,8 @@  [Unit]  After=  After=vyos-router.service +ConditionFileNotEmpty= +ConditionFileNotEmpty=/run/hostapd/%i.conf  [Service]  WorkingDirectory=/run/hostapd diff --git a/src/helpers/vyos-failover.py b/src/helpers/vyos-failover.py index 0de945f20..03fb42f57 100755 --- a/src/helpers/vyos-failover.py +++ b/src/helpers/vyos-failover.py @@ -30,7 +30,7 @@ my_name = Path(__file__).stem  def is_route_exists(route, gateway, interface, metric):      """Check if route with expected gateway, dev and metric exists""" -    rc, data = rc_cmd(f'sudo ip --json route show protocol failover {route} ' +    rc, data = rc_cmd(f'ip --json route show protocol failover {route} '                        f'via {gateway} dev {interface} metric {metric}')      if rc == 0:          data = json.loads(data) @@ -72,6 +72,7 @@ def get_best_route_options(route, debug=False):                    f'best_metric: {best_metric}, best_iface: {best_interface}')          return best_gateway, best_interface, best_metric +  def is_port_open(ip, port):      """      Check connection to remote host and port @@ -91,32 +92,54 @@ def is_port_open(ip, port):      finally:          s.close() -def is_target_alive(target=None, iface='', proto='icmp', port=None, debug=False): -    """ -    Host availability check by ICMP, ARP, TCP -    Return True if target checks is successful -    % is_target_alive('192.0.2.1', 'eth1', proto='arp') -    True +def is_target_alive(target_list=None, iface='', proto='icmp', port=None, debug=False): +    """Check the availability of each target in the target_list using +    the specified protocol ICMP, ARP, TCP + +    Args: +        target_list (list): A list of IP addresses or hostnames to check. +        iface (str): The name of the network interface to use for the check. +        proto (str): The protocol to use for the check. Options are 'icmp', 'arp', or 'tcp'. +        port (int): The port number to use for the TCP check. Only applicable if proto is 'tcp'. +        debug (bool): If True, print debug information during the check. + +    Returns: +        bool: True if all targets are reachable, False otherwise. + +    Example: +        % is_target_alive(['192.0.2.1', '192.0.2.5'], 'eth1', proto='arp') +        True      """      if iface != '':          iface = f'-I {iface}' -    if proto == 'icmp': -        command = f'/usr/bin/ping -q {target} {iface} -n -c 2 -W 1' -        rc, response = rc_cmd(command) -        if debug: print(f'    [ CHECK-TARGET ]: [{command}] -- return-code [RC: {rc}]') -        if rc == 0: -            return True -    elif proto == 'arp': -        command = f'/usr/bin/arping -b -c 2 -f -w 1 -i 1 {iface} {target}' -        rc, response = rc_cmd(command) -        if debug: print(f'    [ CHECK-TARGET ]: [{command}] -- return-code [RC: {rc}]') -        if rc == 0: -            return True -    elif proto == 'tcp' and port is not None: -        return True if is_port_open(target, port) else False -    else: -        return False + +    for target in target_list: +        match proto: +            case 'icmp': +                command = f'/usr/bin/ping -q {target} {iface} -n -c 2 -W 1' +                rc, response = rc_cmd(command) +                if debug: +                    print(f'    [ CHECK-TARGET ]: [{command}] -- return-code [RC: {rc}]') +                if rc != 0: +                    return False + +            case 'arp': +                command = f'/usr/bin/arping -b -c 2 -f -w 1 -i 1 {iface} {target}' +                rc, response = rc_cmd(command) +                if debug: +                    print(f'    [ CHECK-TARGET ]: [{command}] -- return-code [RC: {rc}]') +                if rc != 0: +                    return False + +            case _ if proto == 'tcp' and port is not None: +                if not is_port_open(target, port): +                    return False + +            case _: +                return False + +    return True  if __name__ == '__main__': diff --git a/src/migration-scripts/bgp/3-to-4 b/src/migration-scripts/bgp/3-to-4 new file mode 100755 index 000000000..0df2fbec4 --- /dev/null +++ b/src/migration-scripts/bgp/3-to-4 @@ -0,0 +1,64 @@ +#!/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/>. + +# T5150: Rework CLI definitions to apply route-maps between routing daemons +#        and zebra/kernel + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree + +if (len(argv) < 1): +    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'] +# Check if BGP is configured - if so, migrate the CLI node +if config.exists(bgp_base): +    if config.exists(bgp_base + ['route-map']): +        tmp = config.return_value(bgp_base + ['route-map']) + +        config.set(['system', 'ip', 'protocol', 'bgp', 'route-map'], value=tmp) +        config.set_tag(['system', 'ip', 'protocol']) +        config.delete(bgp_base + ['route-map']) + + +# Check if vrf names are configured. Check if BGP is configured - if so, migrate +# the CLI node(s) +if config.exists(['vrf', 'name']): +    for vrf in config.list_nodes(['vrf', 'name']): +        vrf_base = ['vrf', 'name', vrf] +        if config.exists(vrf_base + ['protocols', 'bgp', 'route-map']): +            tmp = config.return_value(vrf_base + ['protocols', 'bgp', 'route-map']) + +            config.set(vrf_base + ['ip', 'protocol', 'bgp', 'route-map'], value=tmp) +            config.set_tag(vrf_base + ['ip', 'protocol', 'bgp']) +            config.delete(vrf_base + ['protocols', 'bgp', 'route-map']) + +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/isis/2-to-3 b/src/migration-scripts/isis/2-to-3 new file mode 100755 index 000000000..4490feb0a --- /dev/null +++ b/src/migration-scripts/isis/2-to-3 @@ -0,0 +1,63 @@ +#!/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/>. + +# T5150: Rework CLI definitions to apply route-maps between routing daemons +#        and zebra/kernel + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree + +if (len(argv) < 1): +    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) + +isis_base = ['protocols', 'isis'] +# Check if IS-IS is configured - if so, migrate the CLI node +if config.exists(isis_base): +    if config.exists(isis_base + ['route-map']): +        tmp = config.return_value(isis_base + ['route-map']) + +        config.set(['system', 'ip', 'protocol', 'isis', 'route-map'], value=tmp) +        config.set_tag(['system', 'ip', 'protocol']) +        config.delete(isis_base + ['route-map']) + +# Check if vrf names are configured. Check if IS-IS is configured - if so, +# migrate  the CLI node(s) +if config.exists(['vrf', 'name']): +    for vrf in config.list_nodes(['vrf', 'name']): +        vrf_base = ['vrf', 'name', vrf] +        if config.exists(vrf_base + ['protocols', 'isis', 'route-map']): +            tmp = config.return_value(vrf_base + ['protocols', 'isis', 'route-map']) + +            config.set(vrf_base + ['ip', 'protocol', 'isis', 'route-map'], value=tmp) +            config.set_tag(vrf_base + ['ip', 'protocol', 'isis']) +            config.delete(vrf_base + ['protocols', 'isis', 'route-map']) + +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/ospf/1-to-2 b/src/migration-scripts/ospf/1-to-2 new file mode 100755 index 000000000..a6beaf04e --- /dev/null +++ b/src/migration-scripts/ospf/1-to-2 @@ -0,0 +1,80 @@ +#!/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/>. + +# T5150: Rework CLI definitions to apply route-maps between routing daemons +#        and zebra/kernel + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree + +if (len(argv) < 1): +    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) + +ospf_base = ['protocols', 'ospf'] +# Check if OSPF is configured - if so, migrate the CLI node +if config.exists(ospf_base): +    if config.exists(ospf_base + ['route-map']): +        tmp = config.return_value(ospf_base + ['route-map']) + +        config.set(['system', 'ip', 'protocol', 'ospf', 'route-map'], value=tmp) +        config.set_tag(['system', 'ip', 'protocol']) +        config.delete(ospf_base + ['route-map']) + +ospfv3_base = ['protocols', 'ospfv3'] +# Check if OSPFv3 is configured - if so, migrate the CLI node +if config.exists(ospfv3_base): +    if config.exists(ospfv3_base + ['route-map']): +        tmp = config.return_value(ospfv3_base + ['route-map']) + +        config.set(['system', 'ipv6', 'protocol', 'ospfv3', 'route-map'], value=tmp) +        config.set_tag(['system', 'ipv6', 'protocol']) +        config.delete(ospfv3_base + ['route-map']) + +# Check if vrf names are configured. Check if OSPF/OSPFv3 is configured - if so, +# migrate the CLI node(s) +if config.exists(['vrf', 'name']): +    for vrf in config.list_nodes(['vrf', 'name']): +        vrf_base = ['vrf', 'name', vrf] +        if config.exists(vrf_base + ['protocols', 'ospf', 'route-map']): +            tmp = config.return_value(vrf_base + ['protocols', 'ospf', 'route-map']) + +            config.set(vrf_base + ['ip', 'protocol', 'ospf', 'route-map'], value=tmp) +            config.set_tag(vrf_base + ['ip', 'protocol', 'ospf']) +            config.delete(vrf_base + ['protocols', 'ospf', 'route-map']) + +        if config.exists(vrf_base + ['protocols', 'ospfv3', 'route-map']): +            tmp = config.return_value(vrf_base + ['protocols', 'ospfv3', 'route-map']) + +            config.set(vrf_base + ['ipv6', 'protocol', 'ospfv3', 'route-map'], value=tmp) +            config.set_tag(vrf_base + ['ipv6', 'protocol', 'ospfv6']) +            config.delete(vrf_base + ['protocols', 'ospfv3', 'route-map']) + +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/quagga/10-to-11 b/src/migration-scripts/quagga/10-to-11 new file mode 100755 index 000000000..04fc16f79 --- /dev/null +++ b/src/migration-scripts/quagga/10-to-11 @@ -0,0 +1,51 @@ +#!/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/>. + +# T5150: Rework CLI definitions to apply route-maps between routing daemons +#        and zebra/kernel + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree + +if (len(argv) < 1): +    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) + +static_base = ['protocols', 'static'] +# Check if static routes are configured - if so, migrate the CLI node +if config.exists(static_base): +    if config.exists(static_base + ['route-map']): +        tmp = config.return_value(static_base + ['route-map']) + +        config.set(['system', 'ip', 'protocol', 'static', 'route-map'], value=tmp) +        config.set_tag(['system', 'ip', 'protocol']) +        config.delete(static_base + ['route-map']) + +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/rip/0-to-1 b/src/migration-scripts/rip/0-to-1 new file mode 100755 index 000000000..60d510001 --- /dev/null +++ b/src/migration-scripts/rip/0-to-1 @@ -0,0 +1,51 @@ +#!/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/>. + +# T5150: Rework CLI definitions to apply route-maps between routing daemons +#        and zebra/kernel + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree + +if (len(argv) < 1): +    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) + +ripng_base = ['protocols', 'ripng'] +# Check if RIPng is configured - if so, migrate the CLI node +if config.exists(ripng_base): +    if config.exists(ripng_base + ['route-map']): +        tmp = config.return_value(ripng_base + ['route-map']) + +        config.set(['system', 'ipv6', 'protocol', 'ripng', 'route-map'], value=tmp) +        config.set_tag(['system', 'ipv6', 'protocol']) +        config.delete(ripng_base + ['route-map']) + +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/op_mode/dhcp.py b/src/op_mode/dhcp.py index 41da14065..fe7f252ba 100755 --- a/src/op_mode/dhcp.py +++ b/src/op_mode/dhcp.py @@ -264,8 +264,10 @@ def show_pool_statistics(raw: bool, family: ArgFamily, pool: 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/dynamic_dns.py b/src/op_mode/dynamic_dns.py index 263a3b6a5..2cba33cc8 100755 --- a/src/op_mode/dynamic_dns.py +++ b/src/op_mode/dynamic_dns.py @@ -16,69 +16,63 @@  import os  import argparse -import jinja2  import sys  import time +from tabulate import tabulate  from vyos.config import Config  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      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))) +            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 columns.keys(): +                    props[k] = v -            if 'status=' in line: -                status = line.split('status=')[1] -                if status: -                    outp['status'] = status.split(',')[0] +            # 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['hosts'].append(outp) +            data.append(props) -    tmpl = jinja2.Template(OUT_TMPL_SRC) -    print(tmpl.render(data)) +    print(_get_formatted_host_records(data))  def update_ddns(): diff --git a/src/op_mode/openvpn.py b/src/op_mode/openvpn.py index 5a1a4914d..d9ae965c5 100755 --- a/src/op_mode/openvpn.py +++ b/src/op_mode/openvpn.py @@ -16,6 +16,7 @@  #  # +import json  import os  import sys  import typing @@ -25,6 +26,7 @@ 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'] @@ -142,6 +144,25 @@ def _get_interface_status(mode: str, interface: str) -> dict:      return 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() @@ -154,6 +175,8 @@ def _get_raw_data(mode: str) -> list:                    conf_dict[x]['mode'].replace('-', '_') == mode]      for intf in interfaces:          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 conf.exists(f'interfaces openvpn {intf} server client'): 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/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() | 
