diff options
46 files changed, 991 insertions, 667 deletions
diff --git a/data/templates/zabbix-agent/zabbix-agent.conf.j2 b/data/templates/zabbix-agent/zabbix-agent.conf.j2 index e6dcef872..b8df2d177 100644 --- a/data/templates/zabbix-agent/zabbix-agent.conf.j2 +++ b/data/templates/zabbix-agent/zabbix-agent.conf.j2 @@ -75,3 +75,16 @@ Include={{ directory }}/*.conf Timeout={{ timeout }} {% endif %} +{% if authentication is vyos_defined and authentication.mode is vyos_defined %} +{% if authentication.mode == "pre-shared-secret" %} +TLSConnect=psk +TLSAccept=psk +{% endif %} +{% if authentication.psk.secret is vyos_defined %} +TLSPSKFile={{ service_psk_file }} +{% endif %} +{% if authentication.psk.id is vyos_defined %} +TLSPSKIdentity={{ authentication.psk.id }} +{% endif %} +{% endif %} + diff --git a/interface-definitions/include/auth-mode-pre-shared-secret.xml.i b/interface-definitions/include/auth-mode-pre-shared-secret.xml.i new file mode 100644 index 000000000..cf1003917 --- /dev/null +++ b/interface-definitions/include/auth-mode-pre-shared-secret.xml.i @@ -0,0 +1,14 @@ +<!-- include start from auth-mode-pre-shared-secret.xml.i --> +<leafNode name="mode"> + <properties> + <help>Authentication mode</help> + <completionHelp> + <list>pre-shared-secret</list> + </completionHelp> + <valueHelp> + <format>pre-shared-secret</format> + <description>Use a pre-shared secret key</description> + </valueHelp> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/auth-psk-id.xml.i b/interface-definitions/include/auth-psk-id.xml.i new file mode 100644 index 000000000..ab2451045 --- /dev/null +++ b/interface-definitions/include/auth-psk-id.xml.i @@ -0,0 +1,11 @@ +<!-- include start from auth-psk-id.xml.i --> +<leafNode name="id"> + <properties> + <help>ID for authentication</help> + <valueHelp> + <format>txt</format> + <description>ID used for authentication</description> + </valueHelp> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/auth-psk-secret.xml.i b/interface-definitions/include/auth-psk-secret.xml.i new file mode 100644 index 000000000..24257dcab --- /dev/null +++ b/interface-definitions/include/auth-psk-secret.xml.i @@ -0,0 +1,15 @@ +<!-- include start from auth-psk-secret.xml.i --> +<leafNode name="secret"> + <properties> + <help>pre-shared secret key</help> + <valueHelp> + <format>txt</format> + <description>16byte pre-shared-secret key (32 character hexadecimal key)</description> + </valueHelp> + <constraint> + <validator name="psk-secret"/> + </constraint> + <constraintErrorMessage>Pre-Shared-Keys must be at leas 16 bytes long, which implies at least 32 characterss</constraintErrorMessage> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/qos/class-match.xml.i b/interface-definitions/include/qos/class-match.xml.i index 77d1933a3..3ad5547f2 100644 --- a/interface-definitions/include/qos/class-match.xml.i +++ b/interface-definitions/include/qos/class-match.xml.i @@ -29,12 +29,12 @@ <leafNode name="protocol"> <properties> <help>Ethernet protocol for this match</help> - <!-- this refers to /etc/protocols --> + <!-- this refers to /etc/ethertypes --> <completionHelp> <list>all 802.1Q 802_2 802_3 aarp aoe arp atalk dec ip ipv6 ipx lat localtalk rarp snap x25</list> </completionHelp> <valueHelp> - <format>u32:0-65535</format> + <format>u32:1-65535</format> <description>Ethernet protocol number</description> </valueHelp> <valueHelp> @@ -50,7 +50,7 @@ <description>Internet IP (IPv4)</description> </valueHelp> <valueHelp> - <format>ipv6</format> + <format>_ipv6</format> <description>Internet IP (IPv6)</description> </valueHelp> <valueHelp> @@ -59,7 +59,7 @@ </valueHelp> <valueHelp> <format>atalk</format> - <description>Appletalk</description> + <description>AppleTalk</description> </valueHelp> <valueHelp> <format>ipx</format> @@ -69,8 +69,48 @@ <format>802.1Q</format> <description>802.1Q VLAN tag</description> </valueHelp> + <valueHelp> + <format>802_2</format> + <description>IEEE 802.2</description> + </valueHelp> + <valueHelp> + <format>802_3</format> + <description>IEEE 802.3</description> + </valueHelp> + <valueHelp> + <format>aarp</format> + <description>AppleTalk Address Resolution Protocol</description> + </valueHelp> + <valueHelp> + <format>aoe</format> + <description>ATA over Ethernet</description> + </valueHelp> + <valueHelp> + <format>dec</format> + <description>DECnet Protocol</description> + </valueHelp> + <valueHelp> + <format>lat</format> + <description>Local Area Transport</description> + </valueHelp> + <valueHelp> + <format>localtalk</format> + <description>Apple LocalTalk</description> + </valueHelp> + <valueHelp> + <format>rarp</format> + <description>Reverse Address Resolution Protocol</description> + </valueHelp> + <valueHelp> + <format>snap</format> + <description>Subnetwork Access Protocol</description> + </valueHelp> + <valueHelp> + <format>x25</format> + <description>X.25 Packet-Switching Protocol</description> + </valueHelp> <constraint> - <validator name="ip-protocol"/> + <validator name="ether-type"/> </constraint> </properties> </leafNode> diff --git a/interface-definitions/include/stunnel/psk.xml.i b/interface-definitions/include/stunnel/psk.xml.i index db11a93d3..a8226c866 100644 --- a/interface-definitions/include/stunnel/psk.xml.i +++ b/interface-definitions/include/stunnel/psk.xml.i @@ -4,27 +4,8 @@ <help>Pre-shared key name</help> </properties> <children> - <leafNode name="id"> - <properties> - <help>ID for authentication</help> - <valueHelp> - <format>txt</format> - <description>ID used for authentication</description> - </valueHelp> - </properties> - </leafNode> - <leafNode name="secret"> - <properties> - <help>pre-shared secret key</help> - <valueHelp> - <format>txt</format> - <description>pre-shared secret key are required to be at least 16 bytes long, which implies at least 32 characters for hexadecimal key</description> - </valueHelp> - <constraint> - <validator name="psk-secret"/> - </constraint> - </properties> - </leafNode> + #include <include/auth-psk-id.xml.i> + #include <include/auth-psk-secret.xml.i> </children> </tagNode> <!-- include end --> diff --git a/interface-definitions/service_monitoring_zabbix-agent.xml.in b/interface-definitions/service_monitoring_zabbix-agent.xml.in index e44b31312..122e61e8b 100644 --- a/interface-definitions/service_monitoring_zabbix-agent.xml.in +++ b/interface-definitions/service_monitoring_zabbix-agent.xml.in @@ -10,6 +10,23 @@ <priority>1280</priority> </properties> <children> + <node name="authentication"> + <properties> + <help>Authentication</help> + </properties> + <children> + #include <include/auth-mode-pre-shared-secret.xml.i> + <node name="psk"> + <properties> + <help>Pre-shared key</help> + </properties> + <children> + #include <include/auth-psk-id.xml.i> + #include <include/auth-psk-secret.xml.i> + </children> + </node> + </children> + </node> <leafNode name="directory"> <properties> <help>Folder containing individual Zabbix-agent configuration files</help> diff --git a/interface-definitions/vpn_ipsec.xml.in b/interface-definitions/vpn_ipsec.xml.in index 5540021e2..0cf526fad 100644 --- a/interface-definitions/vpn_ipsec.xml.in +++ b/interface-definitions/vpn_ipsec.xml.in @@ -722,18 +722,7 @@ <help>Authentication</help> </properties> <children> - <leafNode name="mode"> - <properties> - <help>Authentication mode</help> - <completionHelp> - <list>pre-shared-secret</list> - </completionHelp> - <valueHelp> - <format>pre-shared-secret</format> - <description>Use a pre-shared secret key</description> - </valueHelp> - </properties> - </leafNode> + #include <include/auth-mode-pre-shared-secret.xml.i> #include <include/ipsec/authentication-pre-shared-secret.xml.i> </children> </node> diff --git a/op-mode-definitions/generate-psk.xml.in b/op-mode-definitions/generate-psk.xml.in new file mode 100644 index 000000000..69963f5be --- /dev/null +++ b/op-mode-definitions/generate-psk.xml.in @@ -0,0 +1,28 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="generate"> + <children> + <node name="psk"> + <properties> + <help>Generate PSK key</help> + </properties> + <children> + <node name="random"> + <properties> + <help>Generate random hex PSK key</help> + </properties> + <command>${vyos_op_scripts_dir}/generate_psk.py</command> + <children> + <tagNode name="size"> + <properties> + <help>Key size in bytes</help> + </properties> + <command>${vyos_op_scripts_dir}/generate_psk.py --hex_size "$5"</command> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index cbcbf9f72..5a353b110 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -19,7 +19,6 @@ A library for retrieving value dicts from VyOS configs in a declarative fashion. import os import json -from vyos.defaults import frr_debug_enable from vyos.utils.dict import dict_search from vyos.utils.process import cmd @@ -665,497 +664,3 @@ def get_accel_dict(config, base, chap_secrets, with_pki=False): dict['authentication']['radius']['server'][server]['acct_port'] = '0' return dict - -def get_frrender_dict(conf, argv=None) -> dict: - from copy import deepcopy - from vyos.config import config_dict_merge - from vyos.frrender import frr_protocols - - # Create an empty dictionary which will be filled down the code path and - # returned to the caller - dict = {} - - if argv and len(argv) > 1: - dict['vrf_context'] = argv[1] - - def dict_helper_ospf_defaults(ospf, path): - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - default_values = conf.get_config_defaults(path, key_mangling=('-', '_'), - get_first_key=True, recursive=True) - - # We have to cleanup the default dict, as default values could enable features - # which are not explicitly enabled on the CLI. Example: default-information - # originate comes with a default metric-type of 2, which will enable the - # entire default-information originate tree, even when not set via CLI so we - # need to check this first and probably drop that key. - if dict_search('default_information.originate', ospf) is None: - del default_values['default_information'] - if 'mpls_te' not in ospf: - del default_values['mpls_te'] - if 'graceful_restart' not in ospf: - del default_values['graceful_restart'] - for area_num in default_values.get('area', []): - if dict_search(f'area.{area_num}.area_type.nssa', ospf) is None: - del default_values['area'][area_num]['area_type']['nssa'] - - for protocol in ['babel', 'bgp', 'connected', 'isis', 'kernel', 'rip', 'static']: - if dict_search(f'redistribute.{protocol}', ospf) is None: - del default_values['redistribute'][protocol] - if not bool(default_values['redistribute']): - del default_values['redistribute'] - - for interface in ospf.get('interface', []): - # We need to reload the defaults on every pass b/c of - # hello-multiplier dependency on dead-interval - # If hello-multiplier is set, we need to remove the default from - # dead-interval. - if 'hello_multiplier' in ospf['interface'][interface]: - del default_values['interface'][interface]['dead_interval'] - - ospf = config_dict_merge(default_values, ospf) - return ospf - - def dict_helper_ospfv3_defaults(ospfv3, path): - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - default_values = conf.get_config_defaults(path, key_mangling=('-', '_'), - get_first_key=True, recursive=True) - - # We have to cleanup the default dict, as default values could enable features - # which are not explicitly enabled on the CLI. Example: default-information - # originate comes with a default metric-type of 2, which will enable the - # entire default-information originate tree, even when not set via CLI so we - # need to check this first and probably drop that key. - if dict_search('default_information.originate', ospfv3) is None: - del default_values['default_information'] - if 'graceful_restart' not in ospfv3: - del default_values['graceful_restart'] - - for protocol in ['babel', 'bgp', 'connected', 'isis', 'kernel', 'ripng', 'static']: - if dict_search(f'redistribute.{protocol}', ospfv3) is None: - del default_values['redistribute'][protocol] - if not bool(default_values['redistribute']): - del default_values['redistribute'] - - default_values.pop('interface', {}) - - # merge in remaining default values - ospfv3 = config_dict_merge(default_values, ospfv3) - return ospfv3 - - def dict_helper_pim_defaults(pim, path): - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - default_values = conf.get_config_defaults(path, key_mangling=('-', '_'), - get_first_key=True, recursive=True) - - # We have to cleanup the default dict, as default values could enable features - # which are not explicitly enabled on the CLI. - for interface in pim.get('interface', []): - if 'igmp' not in pim['interface'][interface]: - del default_values['interface'][interface]['igmp'] - - pim = config_dict_merge(default_values, pim) - return pim - - # Ethernet and bonding interfaces can participate in EVPN which is configured via FRR - tmp = {} - for if_type in ['ethernet', 'bonding']: - interface_path = ['interfaces', if_type] - if not conf.exists(interface_path): - continue - for interface in conf.list_nodes(interface_path): - evpn_path = interface_path + [interface, 'evpn'] - if not conf.exists(evpn_path): - continue - - evpn = conf.get_config_dict(evpn_path, key_mangling=('-', '_')) - tmp.update({interface : evpn}) - # At least one participating EVPN interface found, add to result dict - if tmp: dict['interfaces'] = tmp - - # Zebra prefix exchange for Kernel IP/IPv6 and routing protocols - for ip_version in ['ip', 'ipv6']: - ip_cli_path = ['system', ip_version] - ip_dict = conf.get_config_dict(ip_cli_path, key_mangling=('-', '_'), - get_first_key=True, with_recursive_defaults=True) - if ip_dict: - ip_dict['afi'] = ip_version - dict.update({ip_version : ip_dict}) - - # Enable SNMP agentx support - # SNMP AgentX support cannot be disabled once enabled - if conf.exists(['service', 'snmp']): - dict['snmp'] = {} - - # We will always need the policy key - dict['policy'] = conf.get_config_dict(['policy'], key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True) - - # We need to check the CLI if the BABEL node is present and thus load in all the default - # values present on the CLI - that's why we have if conf.exists() - babel_cli_path = ['protocols', 'babel'] - if conf.exists(babel_cli_path): - babel = conf.get_config_dict(babel_cli_path, key_mangling=('-', '_'), - get_first_key=True, - with_recursive_defaults=True) - dict.update({'babel' : babel}) - - # We need to check the CLI if the BFD node is present and thus load in all the default - # values present on the CLI - that's why we have if conf.exists() - bfd_cli_path = ['protocols', 'bfd'] - if conf.exists(bfd_cli_path): - bfd = conf.get_config_dict(bfd_cli_path, key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True, - with_recursive_defaults=True) - dict.update({'bfd' : bfd}) - - # We need to check the CLI if the BGP node is present and thus load in all the default - # values present on the CLI - that's why we have if conf.exists() - bgp_cli_path = ['protocols', 'bgp'] - if conf.exists(bgp_cli_path): - bgp = conf.get_config_dict(bgp_cli_path, key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True, - with_recursive_defaults=True) - bgp['dependent_vrfs'] = {} - dict.update({'bgp' : bgp}) - elif conf.exists_effective(bgp_cli_path): - dict.update({'bgp' : {'deleted' : '', 'dependent_vrfs' : {}}}) - - # We need to check the CLI if the EIGRP node is present and thus load in all the default - # values present on the CLI - that's why we have if conf.exists() - eigrp_cli_path = ['protocols', 'eigrp'] - if conf.exists(eigrp_cli_path): - isis = conf.get_config_dict(eigrp_cli_path, key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True, - with_recursive_defaults=True) - dict.update({'eigrp' : isis}) - elif conf.exists_effective(eigrp_cli_path): - dict.update({'eigrp' : {'deleted' : ''}}) - - # We need to check the CLI if the ISIS node is present and thus load in all the default - # values present on the CLI - that's why we have if conf.exists() - isis_cli_path = ['protocols', 'isis'] - if conf.exists(isis_cli_path): - isis = conf.get_config_dict(isis_cli_path, key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True, - with_recursive_defaults=True) - dict.update({'isis' : isis}) - elif conf.exists_effective(isis_cli_path): - dict.update({'isis' : {'deleted' : ''}}) - - # We need to check the CLI if the MPLS node is present and thus load in all the default - # values present on the CLI - that's why we have if conf.exists() - mpls_cli_path = ['protocols', 'mpls'] - if conf.exists(mpls_cli_path): - mpls = conf.get_config_dict(mpls_cli_path, key_mangling=('-', '_'), - get_first_key=True) - dict.update({'mpls' : mpls}) - elif conf.exists_effective(mpls_cli_path): - dict.update({'mpls' : {'deleted' : ''}}) - - # We need to check the CLI if the OPENFABRIC node is present and thus load in all the default - # values present on the CLI - that's why we have if conf.exists() - openfabric_cli_path = ['protocols', 'openfabric'] - if conf.exists(openfabric_cli_path): - openfabric = conf.get_config_dict(openfabric_cli_path, key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True) - dict.update({'openfabric' : openfabric}) - elif conf.exists_effective(openfabric_cli_path): - dict.update({'openfabric' : {'deleted' : ''}}) - - # We need to check the CLI if the OSPF node is present and thus load in all the default - # values present on the CLI - that's why we have if conf.exists() - ospf_cli_path = ['protocols', 'ospf'] - if conf.exists(ospf_cli_path): - ospf = conf.get_config_dict(ospf_cli_path, key_mangling=('-', '_'), - get_first_key=True) - ospf = dict_helper_ospf_defaults(ospf, ospf_cli_path) - dict.update({'ospf' : ospf}) - elif conf.exists_effective(ospf_cli_path): - dict.update({'ospf' : {'deleted' : ''}}) - - # We need to check the CLI if the OSPFv3 node is present and thus load in all the default - # values present on the CLI - that's why we have if conf.exists() - ospfv3_cli_path = ['protocols', 'ospfv3'] - if conf.exists(ospfv3_cli_path): - ospfv3 = conf.get_config_dict(ospfv3_cli_path, key_mangling=('-', '_'), - get_first_key=True) - ospfv3 = dict_helper_ospfv3_defaults(ospfv3, ospfv3_cli_path) - dict.update({'ospfv3' : ospfv3}) - elif conf.exists_effective(ospfv3_cli_path): - dict.update({'ospfv3' : {'deleted' : ''}}) - - # We need to check the CLI if the PIM node is present and thus load in all the default - # values present on the CLI - that's why we have if conf.exists() - pim_cli_path = ['protocols', 'pim'] - if conf.exists(pim_cli_path): - pim = conf.get_config_dict(pim_cli_path, key_mangling=('-', '_'), - get_first_key=True) - pim = dict_helper_pim_defaults(pim, pim_cli_path) - dict.update({'pim' : pim}) - elif conf.exists_effective(pim_cli_path): - dict.update({'pim' : {'deleted' : ''}}) - - # We need to check the CLI if the PIM6 node is present and thus load in all the default - # values present on the CLI - that's why we have if conf.exists() - pim6_cli_path = ['protocols', 'pim6'] - if conf.exists(pim6_cli_path): - pim6 = conf.get_config_dict(pim6_cli_path, key_mangling=('-', '_'), - get_first_key=True, - with_recursive_defaults=True) - dict.update({'pim6' : pim6}) - elif conf.exists_effective(pim6_cli_path): - dict.update({'pim6' : {'deleted' : ''}}) - - # We need to check the CLI if the RIP node is present and thus load in all the default - # values present on the CLI - that's why we have if conf.exists() - rip_cli_path = ['protocols', 'rip'] - if conf.exists(rip_cli_path): - rip = conf.get_config_dict(rip_cli_path, key_mangling=('-', '_'), - get_first_key=True, - with_recursive_defaults=True) - dict.update({'rip' : rip}) - elif conf.exists_effective(rip_cli_path): - dict.update({'rip' : {'deleted' : ''}}) - - # We need to check the CLI if the RIPng node is present and thus load in all the default - # values present on the CLI - that's why we have if conf.exists() - ripng_cli_path = ['protocols', 'ripng'] - if conf.exists(ripng_cli_path): - ripng = conf.get_config_dict(ripng_cli_path, key_mangling=('-', '_'), - get_first_key=True, - with_recursive_defaults=True) - dict.update({'ripng' : ripng}) - elif conf.exists_effective(ripng_cli_path): - dict.update({'ripng' : {'deleted' : ''}}) - - # We need to check the CLI if the RPKI node is present and thus load in all the default - # values present on the CLI - that's why we have if conf.exists() - rpki_cli_path = ['protocols', 'rpki'] - if conf.exists(rpki_cli_path): - rpki = conf.get_config_dict(rpki_cli_path, key_mangling=('-', '_'), - get_first_key=True, with_pki=True, - with_recursive_defaults=True) - rpki_ssh_key_base = '/run/frr/id_rpki' - for cache, cache_config in rpki.get('cache',{}).items(): - if 'ssh' in cache_config: - cache_config['ssh']['public_key_file'] = f'{rpki_ssh_key_base}_{cache}.pub' - cache_config['ssh']['private_key_file'] = f'{rpki_ssh_key_base}_{cache}' - dict.update({'rpki' : rpki}) - elif conf.exists_effective(rpki_cli_path): - dict.update({'rpki' : {'deleted' : ''}}) - - # We need to check the CLI if the Segment Routing node is present and thus load in - # all the default values present on the CLI - that's why we have if conf.exists() - sr_cli_path = ['protocols', 'segment-routing'] - if conf.exists(sr_cli_path): - sr = conf.get_config_dict(sr_cli_path, key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True, - with_recursive_defaults=True) - dict.update({'segment_routing' : sr}) - elif conf.exists_effective(sr_cli_path): - dict.update({'segment_routing' : {'deleted' : ''}}) - - # We need to check the CLI if the static node is present and thus load in - # all the default values present on the CLI - that's why we have if conf.exists() - static_cli_path = ['protocols', 'static'] - if conf.exists(static_cli_path): - static = conf.get_config_dict(static_cli_path, key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True) - - # T3680 - get a list of all interfaces currently configured to use DHCP - tmp = get_dhcp_interfaces(conf) - if tmp: static.update({'dhcp' : tmp}) - tmp = get_pppoe_interfaces(conf) - if tmp: static.update({'pppoe' : tmp}) - - dict.update({'static' : static}) - elif conf.exists_effective(static_cli_path): - dict.update({'static' : {'deleted' : ''}}) - - # keep a re-usable list of dependent VRFs - dependent_vrfs_default = {} - if 'bgp' in dict: - dependent_vrfs_default = deepcopy(dict['bgp']) - # we do not need to nest the 'dependent_vrfs' key - simply remove it - if 'dependent_vrfs' in dependent_vrfs_default: - del dependent_vrfs_default['dependent_vrfs'] - - vrf_cli_path = ['vrf', 'name'] - if conf.exists(vrf_cli_path): - vrf = conf.get_config_dict(vrf_cli_path, key_mangling=('-', '_'), - get_first_key=False, - no_tag_node_value_mangle=True) - # We do not have any VRF related default values on the CLI. The defaults will only - # come into place under the protocols tree, thus we can safely merge them with the - # appropriate routing protocols - for vrf_name, vrf_config in vrf['name'].items(): - bgp_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'bgp'] - if 'bgp' in vrf_config.get('protocols', []): - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - default_values = conf.get_config_defaults(bgp_vrf_path, key_mangling=('-', '_'), - get_first_key=True, recursive=True) - - # merge in remaining default values - vrf_config['protocols']['bgp'] = config_dict_merge(default_values, - vrf_config['protocols']['bgp']) - - # Add this BGP VRF instance as dependency into the default VRF - if 'bgp' in dict: - dict['bgp']['dependent_vrfs'].update({vrf_name : deepcopy(vrf_config)}) - - vrf_config['protocols']['bgp']['dependent_vrfs'] = conf.get_config_dict( - vrf_cli_path, key_mangling=('-', '_'), get_first_key=True, - no_tag_node_value_mangle=True) - - # We can safely delete ourself from the dependent VRF list - if vrf_name in vrf_config['protocols']['bgp']['dependent_vrfs']: - del vrf_config['protocols']['bgp']['dependent_vrfs'][vrf_name] - - # Add dependency on possible existing default VRF to this VRF - if 'bgp' in dict: - vrf_config['protocols']['bgp']['dependent_vrfs'].update({'default': {'protocols': { - 'bgp': dependent_vrfs_default}}}) - elif conf.exists_effective(bgp_vrf_path): - # Add this BGP VRF instance as dependency into the default VRF - tmp = {'deleted' : '', 'dependent_vrfs': deepcopy(vrf['name'])} - # We can safely delete ourself from the dependent VRF list - if vrf_name in tmp['dependent_vrfs']: - del tmp['dependent_vrfs'][vrf_name] - - # Add dependency on possible existing default VRF to this VRF - if 'bgp' in dict: - tmp['dependent_vrfs'].update({'default': {'protocols': { - 'bgp': dependent_vrfs_default}}}) - - if 'bgp' in dict: - dict['bgp']['dependent_vrfs'].update({vrf_name : {'protocols': tmp} }) - - if 'protocols' not in vrf['name'][vrf_name]: - vrf['name'][vrf_name].update({'protocols': {'bgp' : tmp}}) - else: - vrf['name'][vrf_name]['protocols'].update({'bgp' : tmp}) - - # We need to check the CLI if the EIGRP node is present and thus load in all the default - # values present on the CLI - that's why we have if conf.exists() - eigrp_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'eigrp'] - if 'eigrp' in vrf_config.get('protocols', []): - eigrp = conf.get_config_dict(eigrp_vrf_path, key_mangling=('-', '_'), get_first_key=True, - no_tag_node_value_mangle=True) - vrf['name'][vrf_name]['protocols'].update({'eigrp' : isis}) - elif conf.exists_effective(eigrp_vrf_path): - vrf['name'][vrf_name]['protocols'].update({'eigrp' : {'deleted' : ''}}) - - # We need to check the CLI if the ISIS node is present and thus load in all the default - # values present on the CLI - that's why we have if conf.exists() - isis_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'isis'] - if 'isis' in vrf_config.get('protocols', []): - isis = conf.get_config_dict(isis_vrf_path, key_mangling=('-', '_'), get_first_key=True, - no_tag_node_value_mangle=True, with_recursive_defaults=True) - vrf['name'][vrf_name]['protocols'].update({'isis' : isis}) - elif conf.exists_effective(isis_vrf_path): - vrf['name'][vrf_name]['protocols'].update({'isis' : {'deleted' : ''}}) - - # We need to check the CLI if the OSPF node is present and thus load in all the default - # values present on the CLI - that's why we have if conf.exists() - ospf_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'ospf'] - if 'ospf' in vrf_config.get('protocols', []): - ospf = conf.get_config_dict(ospf_vrf_path, key_mangling=('-', '_'), get_first_key=True) - ospf = dict_helper_ospf_defaults(vrf_config['protocols']['ospf'], ospf_vrf_path) - vrf['name'][vrf_name]['protocols'].update({'ospf' : ospf}) - elif conf.exists_effective(ospf_vrf_path): - vrf['name'][vrf_name]['protocols'].update({'ospf' : {'deleted' : ''}}) - - # We need to check the CLI if the OSPFv3 node is present and thus load in all the default - # values present on the CLI - that's why we have if conf.exists() - ospfv3_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'ospfv3'] - if 'ospfv3' in vrf_config.get('protocols', []): - ospfv3 = conf.get_config_dict(ospfv3_vrf_path, key_mangling=('-', '_'), get_first_key=True) - ospfv3 = dict_helper_ospfv3_defaults(vrf_config['protocols']['ospfv3'], ospfv3_vrf_path) - vrf['name'][vrf_name]['protocols'].update({'ospfv3' : ospfv3}) - elif conf.exists_effective(ospfv3_vrf_path): - vrf['name'][vrf_name]['protocols'].update({'ospfv3' : {'deleted' : ''}}) - - # We need to check the CLI if the static node is present and thus load in all the default - # values present on the CLI - that's why we have if conf.exists() - static_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'static'] - if 'static' in vrf_config.get('protocols', []): - static = conf.get_config_dict(static_vrf_path, key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True) - # T3680 - get a list of all interfaces currently configured to use DHCP - tmp = get_dhcp_interfaces(conf, vrf_name) - if tmp: static.update({'dhcp' : tmp}) - tmp = get_pppoe_interfaces(conf, vrf_name) - if tmp: static.update({'pppoe' : tmp}) - - vrf['name'][vrf_name]['protocols'].update({'static': static}) - elif conf.exists_effective(static_vrf_path): - vrf['name'][vrf_name]['protocols'].update({'static': {'deleted' : ''}}) - - vrf_vni_path = ['vrf', 'name', vrf_name, 'vni'] - if conf.exists(vrf_vni_path): - vrf_config.update({'vni': conf.return_value(vrf_vni_path)}) - - dict.update({'vrf' : vrf}) - elif conf.exists_effective(vrf_cli_path): - effective_vrf = conf.get_config_dict(vrf_cli_path, key_mangling=('-', '_'), - get_first_key=False, - no_tag_node_value_mangle=True, - effective=True) - vrf = {'name' : {}} - for vrf_name, vrf_config in effective_vrf.get('name', {}).items(): - vrf['name'].update({vrf_name : {}}) - for protocol in frr_protocols: - if protocol in vrf_config.get('protocols', []): - # Create initial protocols key if not present - if 'protocols' not in vrf['name'][vrf_name]: - vrf['name'][vrf_name].update({'protocols' : {}}) - # All routing protocols are deleted when we pass this point - tmp = {'deleted' : ''} - - # Special treatment for BGP routing protocol - if protocol == 'bgp': - tmp['dependent_vrfs'] = {} - if 'name' in vrf: - tmp['dependent_vrfs'] = conf.get_config_dict( - vrf_cli_path, key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True, - effective=True) - # Add dependency on possible existing default VRF to this VRF - if 'bgp' in dict: - tmp['dependent_vrfs'].update({'default': {'protocols': { - 'bgp': dependent_vrfs_default}}}) - # We can safely delete ourself from the dependent VRF list - if vrf_name in tmp['dependent_vrfs']: - del tmp['dependent_vrfs'][vrf_name] - - # Update VRF related dict - vrf['name'][vrf_name]['protocols'].update({protocol : tmp}) - - dict.update({'vrf' : vrf}) - - if os.path.exists(frr_debug_enable): - print('======== < BEGIN > ==========') - import pprint - pprint.pprint(dict) - print('========= < END > ===========') - - # Use singleton instance of the FRR render class - if hasattr(conf, 'frrender_cls'): - frrender = getattr(conf, 'frrender_cls') - dict.update({'frrender_cls' : frrender}) - frrender.generate(dict) - - return dict diff --git a/python/vyos/frrender.py b/python/vyos/frrender.py index 0c9dde315..badc5d59f 100644 --- a/python/vyos/frrender.py +++ b/python/vyos/frrender.py @@ -22,16 +22,15 @@ import os from time import sleep from vyos.defaults import frr_debug_enable +from vyos.utils.dict import dict_search from vyos.utils.file import write_file from vyos.utils.process import cmd from vyos.utils.process import rc_cmd from vyos.template import render_to_string from vyos import ConfigError -DEBUG_ON = os.path.exists(frr_debug_enable) - def debug(message): - if not DEBUG_ON: + if not os.path.exists(frr_debug_enable): return print(message) @@ -54,13 +53,496 @@ rip_daemon = 'ripd' ripng_daemon = 'ripngd' zebra_daemon = 'zebra' +def get_frrender_dict(conf, argv=None) -> dict: + from copy import deepcopy + from vyos.config import config_dict_merge + from vyos.configdict import get_dhcp_interfaces + from vyos.configdict import get_pppoe_interfaces + + # Create an empty dictionary which will be filled down the code path and + # returned to the caller + dict = {} + + if argv and len(argv) > 1: + dict['vrf_context'] = argv[1] + + def dict_helper_ospf_defaults(ospf, path): + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + default_values = conf.get_config_defaults(path, key_mangling=('-', '_'), + get_first_key=True, recursive=True) + + # We have to cleanup the default dict, as default values could enable features + # which are not explicitly enabled on the CLI. Example: default-information + # originate comes with a default metric-type of 2, which will enable the + # entire default-information originate tree, even when not set via CLI so we + # need to check this first and probably drop that key. + if dict_search('default_information.originate', ospf) is None: + del default_values['default_information'] + if 'mpls_te' not in ospf: + del default_values['mpls_te'] + if 'graceful_restart' not in ospf: + del default_values['graceful_restart'] + for area_num in default_values.get('area', []): + if dict_search(f'area.{area_num}.area_type.nssa', ospf) is None: + del default_values['area'][area_num]['area_type']['nssa'] + + for protocol in ['babel', 'bgp', 'connected', 'isis', 'kernel', 'rip', 'static']: + if dict_search(f'redistribute.{protocol}', ospf) is None: + del default_values['redistribute'][protocol] + if not bool(default_values['redistribute']): + del default_values['redistribute'] + + for interface in ospf.get('interface', []): + # We need to reload the defaults on every pass b/c of + # hello-multiplier dependency on dead-interval + # If hello-multiplier is set, we need to remove the default from + # dead-interval. + if 'hello_multiplier' in ospf['interface'][interface]: + del default_values['interface'][interface]['dead_interval'] + + ospf = config_dict_merge(default_values, ospf) + return ospf + + def dict_helper_ospfv3_defaults(ospfv3, path): + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + default_values = conf.get_config_defaults(path, key_mangling=('-', '_'), + get_first_key=True, recursive=True) + + # We have to cleanup the default dict, as default values could enable features + # which are not explicitly enabled on the CLI. Example: default-information + # originate comes with a default metric-type of 2, which will enable the + # entire default-information originate tree, even when not set via CLI so we + # need to check this first and probably drop that key. + if dict_search('default_information.originate', ospfv3) is None: + del default_values['default_information'] + if 'graceful_restart' not in ospfv3: + del default_values['graceful_restart'] + + for protocol in ['babel', 'bgp', 'connected', 'isis', 'kernel', 'ripng', 'static']: + if dict_search(f'redistribute.{protocol}', ospfv3) is None: + del default_values['redistribute'][protocol] + if not bool(default_values['redistribute']): + del default_values['redistribute'] + + default_values.pop('interface', {}) + + # merge in remaining default values + ospfv3 = config_dict_merge(default_values, ospfv3) + return ospfv3 + + def dict_helper_pim_defaults(pim, path): + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + default_values = conf.get_config_defaults(path, key_mangling=('-', '_'), + get_first_key=True, recursive=True) + + # We have to cleanup the default dict, as default values could enable features + # which are not explicitly enabled on the CLI. + for interface in pim.get('interface', []): + if 'igmp' not in pim['interface'][interface]: + del default_values['interface'][interface]['igmp'] + + pim = config_dict_merge(default_values, pim) + return pim + + # Ethernet and bonding interfaces can participate in EVPN which is configured via FRR + tmp = {} + for if_type in ['ethernet', 'bonding']: + interface_path = ['interfaces', if_type] + if not conf.exists(interface_path): + continue + for interface in conf.list_nodes(interface_path): + evpn_path = interface_path + [interface, 'evpn'] + if not conf.exists(evpn_path): + continue + + evpn = conf.get_config_dict(evpn_path, key_mangling=('-', '_')) + tmp.update({interface : evpn}) + # At least one participating EVPN interface found, add to result dict + if tmp: dict['interfaces'] = tmp + + # Zebra prefix exchange for Kernel IP/IPv6 and routing protocols + for ip_version in ['ip', 'ipv6']: + ip_cli_path = ['system', ip_version] + ip_dict = conf.get_config_dict(ip_cli_path, key_mangling=('-', '_'), + get_first_key=True, with_recursive_defaults=True) + if ip_dict: + ip_dict['afi'] = ip_version + dict.update({ip_version : ip_dict}) + + # Enable SNMP agentx support + # SNMP AgentX support cannot be disabled once enabled + if conf.exists(['service', 'snmp']): + dict['snmp'] = {} + + # We will always need the policy key + dict['policy'] = conf.get_config_dict(['policy'], key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + + # We need to check the CLI if the BABEL node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + babel_cli_path = ['protocols', 'babel'] + if conf.exists(babel_cli_path): + babel = conf.get_config_dict(babel_cli_path, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) + dict.update({'babel' : babel}) + + # We need to check the CLI if the BFD node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + bfd_cli_path = ['protocols', 'bfd'] + if conf.exists(bfd_cli_path): + bfd = conf.get_config_dict(bfd_cli_path, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + with_recursive_defaults=True) + dict.update({'bfd' : bfd}) + + # We need to check the CLI if the BGP node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + bgp_cli_path = ['protocols', 'bgp'] + if conf.exists(bgp_cli_path): + bgp = conf.get_config_dict(bgp_cli_path, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + with_recursive_defaults=True) + bgp['dependent_vrfs'] = {} + dict.update({'bgp' : bgp}) + elif conf.exists_effective(bgp_cli_path): + dict.update({'bgp' : {'deleted' : '', 'dependent_vrfs' : {}}}) + + # We need to check the CLI if the EIGRP node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + eigrp_cli_path = ['protocols', 'eigrp'] + if conf.exists(eigrp_cli_path): + isis = conf.get_config_dict(eigrp_cli_path, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + with_recursive_defaults=True) + dict.update({'eigrp' : isis}) + elif conf.exists_effective(eigrp_cli_path): + dict.update({'eigrp' : {'deleted' : ''}}) + + # We need to check the CLI if the ISIS node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + isis_cli_path = ['protocols', 'isis'] + if conf.exists(isis_cli_path): + isis = conf.get_config_dict(isis_cli_path, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + with_recursive_defaults=True) + dict.update({'isis' : isis}) + elif conf.exists_effective(isis_cli_path): + dict.update({'isis' : {'deleted' : ''}}) + + # We need to check the CLI if the MPLS node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + mpls_cli_path = ['protocols', 'mpls'] + if conf.exists(mpls_cli_path): + mpls = conf.get_config_dict(mpls_cli_path, key_mangling=('-', '_'), + get_first_key=True) + dict.update({'mpls' : mpls}) + elif conf.exists_effective(mpls_cli_path): + dict.update({'mpls' : {'deleted' : ''}}) + + # We need to check the CLI if the OPENFABRIC node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + openfabric_cli_path = ['protocols', 'openfabric'] + if conf.exists(openfabric_cli_path): + openfabric = conf.get_config_dict(openfabric_cli_path, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + dict.update({'openfabric' : openfabric}) + elif conf.exists_effective(openfabric_cli_path): + dict.update({'openfabric' : {'deleted' : ''}}) + + # We need to check the CLI if the OSPF node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + ospf_cli_path = ['protocols', 'ospf'] + if conf.exists(ospf_cli_path): + ospf = conf.get_config_dict(ospf_cli_path, key_mangling=('-', '_'), + get_first_key=True) + ospf = dict_helper_ospf_defaults(ospf, ospf_cli_path) + dict.update({'ospf' : ospf}) + elif conf.exists_effective(ospf_cli_path): + dict.update({'ospf' : {'deleted' : ''}}) + + # We need to check the CLI if the OSPFv3 node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + ospfv3_cli_path = ['protocols', 'ospfv3'] + if conf.exists(ospfv3_cli_path): + ospfv3 = conf.get_config_dict(ospfv3_cli_path, key_mangling=('-', '_'), + get_first_key=True) + ospfv3 = dict_helper_ospfv3_defaults(ospfv3, ospfv3_cli_path) + dict.update({'ospfv3' : ospfv3}) + elif conf.exists_effective(ospfv3_cli_path): + dict.update({'ospfv3' : {'deleted' : ''}}) + + # We need to check the CLI if the PIM node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + pim_cli_path = ['protocols', 'pim'] + if conf.exists(pim_cli_path): + pim = conf.get_config_dict(pim_cli_path, key_mangling=('-', '_'), + get_first_key=True) + pim = dict_helper_pim_defaults(pim, pim_cli_path) + dict.update({'pim' : pim}) + elif conf.exists_effective(pim_cli_path): + dict.update({'pim' : {'deleted' : ''}}) + + # We need to check the CLI if the PIM6 node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + pim6_cli_path = ['protocols', 'pim6'] + if conf.exists(pim6_cli_path): + pim6 = conf.get_config_dict(pim6_cli_path, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) + dict.update({'pim6' : pim6}) + elif conf.exists_effective(pim6_cli_path): + dict.update({'pim6' : {'deleted' : ''}}) + + # We need to check the CLI if the RIP node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + rip_cli_path = ['protocols', 'rip'] + if conf.exists(rip_cli_path): + rip = conf.get_config_dict(rip_cli_path, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) + dict.update({'rip' : rip}) + elif conf.exists_effective(rip_cli_path): + dict.update({'rip' : {'deleted' : ''}}) + + # We need to check the CLI if the RIPng node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + ripng_cli_path = ['protocols', 'ripng'] + if conf.exists(ripng_cli_path): + ripng = conf.get_config_dict(ripng_cli_path, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) + dict.update({'ripng' : ripng}) + elif conf.exists_effective(ripng_cli_path): + dict.update({'ripng' : {'deleted' : ''}}) + + # We need to check the CLI if the RPKI node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + rpki_cli_path = ['protocols', 'rpki'] + if conf.exists(rpki_cli_path): + rpki = conf.get_config_dict(rpki_cli_path, key_mangling=('-', '_'), + get_first_key=True, with_pki=True, + with_recursive_defaults=True) + rpki_ssh_key_base = '/run/frr/id_rpki' + for cache, cache_config in rpki.get('cache',{}).items(): + if 'ssh' in cache_config: + cache_config['ssh']['public_key_file'] = f'{rpki_ssh_key_base}_{cache}.pub' + cache_config['ssh']['private_key_file'] = f'{rpki_ssh_key_base}_{cache}' + dict.update({'rpki' : rpki}) + elif conf.exists_effective(rpki_cli_path): + dict.update({'rpki' : {'deleted' : ''}}) + + # We need to check the CLI if the Segment Routing node is present and thus load in + # all the default values present on the CLI - that's why we have if conf.exists() + sr_cli_path = ['protocols', 'segment-routing'] + if conf.exists(sr_cli_path): + sr = conf.get_config_dict(sr_cli_path, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + with_recursive_defaults=True) + dict.update({'segment_routing' : sr}) + elif conf.exists_effective(sr_cli_path): + dict.update({'segment_routing' : {'deleted' : ''}}) + + # We need to check the CLI if the static node is present and thus load in + # all the default values present on the CLI - that's why we have if conf.exists() + static_cli_path = ['protocols', 'static'] + if conf.exists(static_cli_path): + static = conf.get_config_dict(static_cli_path, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + + # T3680 - get a list of all interfaces currently configured to use DHCP + tmp = get_dhcp_interfaces(conf) + if tmp: static.update({'dhcp' : tmp}) + tmp = get_pppoe_interfaces(conf) + if tmp: static.update({'pppoe' : tmp}) + + dict.update({'static' : static}) + elif conf.exists_effective(static_cli_path): + dict.update({'static' : {'deleted' : ''}}) + + # keep a re-usable list of dependent VRFs + dependent_vrfs_default = {} + if 'bgp' in dict: + dependent_vrfs_default = deepcopy(dict['bgp']) + # we do not need to nest the 'dependent_vrfs' key - simply remove it + if 'dependent_vrfs' in dependent_vrfs_default: + del dependent_vrfs_default['dependent_vrfs'] + + vrf_cli_path = ['vrf', 'name'] + if conf.exists(vrf_cli_path): + vrf = conf.get_config_dict(vrf_cli_path, key_mangling=('-', '_'), + get_first_key=False, + no_tag_node_value_mangle=True) + # We do not have any VRF related default values on the CLI. The defaults will only + # come into place under the protocols tree, thus we can safely merge them with the + # appropriate routing protocols + for vrf_name, vrf_config in vrf['name'].items(): + bgp_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'bgp'] + if 'bgp' in vrf_config.get('protocols', []): + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + default_values = conf.get_config_defaults(bgp_vrf_path, key_mangling=('-', '_'), + get_first_key=True, recursive=True) + + # merge in remaining default values + vrf_config['protocols']['bgp'] = config_dict_merge(default_values, + vrf_config['protocols']['bgp']) + + # Add this BGP VRF instance as dependency into the default VRF + if 'bgp' in dict: + dict['bgp']['dependent_vrfs'].update({vrf_name : deepcopy(vrf_config)}) + + vrf_config['protocols']['bgp']['dependent_vrfs'] = conf.get_config_dict( + vrf_cli_path, key_mangling=('-', '_'), get_first_key=True, + no_tag_node_value_mangle=True) + + # We can safely delete ourself from the dependent VRF list + if vrf_name in vrf_config['protocols']['bgp']['dependent_vrfs']: + del vrf_config['protocols']['bgp']['dependent_vrfs'][vrf_name] + + # Add dependency on possible existing default VRF to this VRF + if 'bgp' in dict: + vrf_config['protocols']['bgp']['dependent_vrfs'].update({'default': {'protocols': { + 'bgp': dependent_vrfs_default}}}) + elif conf.exists_effective(bgp_vrf_path): + # Add this BGP VRF instance as dependency into the default VRF + tmp = {'deleted' : '', 'dependent_vrfs': deepcopy(vrf['name'])} + # We can safely delete ourself from the dependent VRF list + if vrf_name in tmp['dependent_vrfs']: + del tmp['dependent_vrfs'][vrf_name] + + # Add dependency on possible existing default VRF to this VRF + if 'bgp' in dict: + tmp['dependent_vrfs'].update({'default': {'protocols': { + 'bgp': dependent_vrfs_default}}}) + + if 'bgp' in dict: + dict['bgp']['dependent_vrfs'].update({vrf_name : {'protocols': tmp} }) + + if 'protocols' not in vrf['name'][vrf_name]: + vrf['name'][vrf_name].update({'protocols': {'bgp' : tmp}}) + else: + vrf['name'][vrf_name]['protocols'].update({'bgp' : tmp}) + + # We need to check the CLI if the EIGRP node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + eigrp_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'eigrp'] + if 'eigrp' in vrf_config.get('protocols', []): + eigrp = conf.get_config_dict(eigrp_vrf_path, key_mangling=('-', '_'), get_first_key=True, + no_tag_node_value_mangle=True) + vrf['name'][vrf_name]['protocols'].update({'eigrp' : isis}) + elif conf.exists_effective(eigrp_vrf_path): + vrf['name'][vrf_name]['protocols'].update({'eigrp' : {'deleted' : ''}}) + + # We need to check the CLI if the ISIS node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + isis_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'isis'] + if 'isis' in vrf_config.get('protocols', []): + isis = conf.get_config_dict(isis_vrf_path, key_mangling=('-', '_'), get_first_key=True, + no_tag_node_value_mangle=True, with_recursive_defaults=True) + vrf['name'][vrf_name]['protocols'].update({'isis' : isis}) + elif conf.exists_effective(isis_vrf_path): + vrf['name'][vrf_name]['protocols'].update({'isis' : {'deleted' : ''}}) + + # We need to check the CLI if the OSPF node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + ospf_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'ospf'] + if 'ospf' in vrf_config.get('protocols', []): + ospf = conf.get_config_dict(ospf_vrf_path, key_mangling=('-', '_'), get_first_key=True) + ospf = dict_helper_ospf_defaults(vrf_config['protocols']['ospf'], ospf_vrf_path) + vrf['name'][vrf_name]['protocols'].update({'ospf' : ospf}) + elif conf.exists_effective(ospf_vrf_path): + vrf['name'][vrf_name]['protocols'].update({'ospf' : {'deleted' : ''}}) + + # We need to check the CLI if the OSPFv3 node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + ospfv3_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'ospfv3'] + if 'ospfv3' in vrf_config.get('protocols', []): + ospfv3 = conf.get_config_dict(ospfv3_vrf_path, key_mangling=('-', '_'), get_first_key=True) + ospfv3 = dict_helper_ospfv3_defaults(vrf_config['protocols']['ospfv3'], ospfv3_vrf_path) + vrf['name'][vrf_name]['protocols'].update({'ospfv3' : ospfv3}) + elif conf.exists_effective(ospfv3_vrf_path): + vrf['name'][vrf_name]['protocols'].update({'ospfv3' : {'deleted' : ''}}) + + # We need to check the CLI if the static node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + static_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'static'] + if 'static' in vrf_config.get('protocols', []): + static = conf.get_config_dict(static_vrf_path, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + # T3680 - get a list of all interfaces currently configured to use DHCP + tmp = get_dhcp_interfaces(conf, vrf_name) + if tmp: static.update({'dhcp' : tmp}) + tmp = get_pppoe_interfaces(conf, vrf_name) + if tmp: static.update({'pppoe' : tmp}) + + vrf['name'][vrf_name]['protocols'].update({'static': static}) + elif conf.exists_effective(static_vrf_path): + vrf['name'][vrf_name]['protocols'].update({'static': {'deleted' : ''}}) + + vrf_vni_path = ['vrf', 'name', vrf_name, 'vni'] + if conf.exists(vrf_vni_path): + vrf_config.update({'vni': conf.return_value(vrf_vni_path)}) + + dict.update({'vrf' : vrf}) + elif conf.exists_effective(vrf_cli_path): + effective_vrf = conf.get_config_dict(vrf_cli_path, key_mangling=('-', '_'), + get_first_key=False, + no_tag_node_value_mangle=True, + effective=True) + vrf = {'name' : {}} + for vrf_name, vrf_config in effective_vrf.get('name', {}).items(): + vrf['name'].update({vrf_name : {}}) + for protocol in frr_protocols: + if protocol in vrf_config.get('protocols', []): + # Create initial protocols key if not present + if 'protocols' not in vrf['name'][vrf_name]: + vrf['name'][vrf_name].update({'protocols' : {}}) + # All routing protocols are deleted when we pass this point + tmp = {'deleted' : ''} + + # Special treatment for BGP routing protocol + if protocol == 'bgp': + tmp['dependent_vrfs'] = {} + if 'name' in vrf: + tmp['dependent_vrfs'] = conf.get_config_dict( + vrf_cli_path, key_mangling=('-', '_'), + get_first_key=True, no_tag_node_value_mangle=True, + effective=True) + # Add dependency on possible existing default VRF to this VRF + if 'bgp' in dict: + tmp['dependent_vrfs'].update({'default': {'protocols': { + 'bgp': dependent_vrfs_default}}}) + # We can safely delete ourself from the dependent VRF list + if vrf_name in tmp['dependent_vrfs']: + del tmp['dependent_vrfs'][vrf_name] + + # Update VRF related dict + vrf['name'][vrf_name]['protocols'].update({protocol : tmp}) + + dict.update({'vrf' : vrf}) + + return dict + class FRRender: def __init__(self): self._frr_conf = '/run/frr/config/vyos.frr.conf' - def generate(self, config): - if not isinstance(config, dict): - tmp = type(config) + def generate(self, config_dict) -> None: + if not isinstance(config_dict, dict): + tmp = type(config_dict) raise ValueError(f'Config must be of type "dict" and not "{tmp}"!') def inline_helper(config_dict) -> str: @@ -124,23 +606,23 @@ class FRRender: output += '\n' return output - debug('======< RENDERING CONFIG >======') + debug('FRR: START CONFIGURATION RENDERING') # we can not reload an empty file, thus we always embed the marker output = '!\n' # Enable SNMP agentx support # SNMP AgentX support cannot be disabled once enabled - if 'snmp' in config: + if 'snmp' in config_dict: output += 'agentx\n' # Add routing protocols in global VRF - output += inline_helper(config) + output += inline_helper(config_dict) # Interface configuration for EVPN is not VRF related - if 'interfaces' in config: - output += render_to_string('frr/evpn.mh.frr.j2', {'interfaces' : config['interfaces']}) + if 'interfaces' in config_dict: + output += render_to_string('frr/evpn.mh.frr.j2', {'interfaces' : config_dict['interfaces']}) output += '\n' - if 'vrf' in config and 'name' in config['vrf']: - output += render_to_string('frr/zebra.vrf.route-map.frr.j2', config['vrf']) - for vrf, vrf_config in config['vrf']['name'].items(): + if 'vrf' in config_dict and 'name' in config_dict['vrf']: + output += render_to_string('frr/zebra.vrf.route-map.frr.j2', config_dict['vrf']) + for vrf, vrf_config in config_dict['vrf']['name'].items(): if 'protocols' not in vrf_config: continue for protocol in vrf_config['protocols']: @@ -155,23 +637,25 @@ class FRRender: raise ConfigError('FRR configuration contains "!!" which is not allowed') debug(output) - debug('======< RENDERING CONFIG COMPLETE >======') write_file(self._frr_conf, output) + debug('FRR: RENDERING CONFIG COMPLETE') + return None def apply(self, count_max=5): count = 0 emsg = '' while count < count_max: count += 1 - debug(f'FRR: Reloading configuration - tries: {count} | Python class ID: {id(self)}') + debug(f'FRR: reloading configuration - tries: {count} | Python class ID: {id(self)}') cmdline = '/usr/lib/frr/frr-reload.py --reload' - if DEBUG_ON: cmdline += ' --debug' + if os.path.exists(frr_debug_enable): + cmdline += ' --debug' rc, emsg = rc_cmd(f'{cmdline} {self._frr_conf}') if rc != 0: sleep(2) continue debug(emsg) - debug('======< DONE APPLYING CONFIG >======') + debug('FRR: configuration reload complete') break if count >= count_max: diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index cd562e1fe..eac9f61f5 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -26,8 +26,9 @@ from netifaces import ifaddresses # this is not the same as socket.AF_INET/INET6 from netifaces import AF_INET from netifaces import AF_INET6 +from netaddr import EUI +from netaddr import mac_unix_expanded -from vyos import ConfigError from vyos.configdict import list_diff from vyos.configdict import dict_merge from vyos.configdict import get_vlan_ids @@ -61,9 +62,7 @@ from vyos.ifconfig.control import Control from vyos.ifconfig.vrrp import VRRP from vyos.ifconfig.operational import Operational from vyos.ifconfig import Section - -from netaddr import EUI -from netaddr import mac_unix_expanded +from vyos import ConfigError link_local_prefix = 'fe80::/64' @@ -339,8 +338,8 @@ class Interface(Control): # Any instance of Interface, such as Interface('eth0') can be used # safely to access the generic function in this class as 'type' is # unset, the class can not be created - if not self.iftype: - raise Exception(f'interface "{ifname}" not found') + if not hasattr(self, 'iftype'): + raise ConfigError(f'Interface "{ifname}" has no "iftype" attribute defined!') self.config['type'] = self.iftype # Should an Instance of a child class (EthernetIf, DummyIf, ..) diff --git a/python/vyos/ifconfig/macvlan.py b/python/vyos/ifconfig/macvlan.py index 2266879ec..fb7f1d298 100644 --- a/python/vyos/ifconfig/macvlan.py +++ b/python/vyos/ifconfig/macvlan.py @@ -1,4 +1,4 @@ -# Copyright 2019-2022 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -42,6 +42,5 @@ class MACVLANIf(Interface): self.set_admin_state('down') def set_mode(self, mode): - ifname = self.config['ifname'] - cmd = f'ip link set dev {ifname} type macvlan mode {mode}' + cmd = f'ip link set dev {self.ifname} type {self.iftype} mode {mode}' return self._cmd(cmd) diff --git a/python/vyos/qos/base.py b/python/vyos/qos/base.py index 3da9afe04..66df5d107 100644 --- a/python/vyos/qos/base.py +++ b/python/vyos/qos/base.py @@ -245,8 +245,6 @@ class QoSBase: prio = cls_config['priority'] filter_cmd_base += f' prio {prio}' - filter_cmd_base += ' protocol all' - if 'match' in cls_config: has_filter = False has_action_policy = any(tmp in ['exceed', 'bandwidth', 'burst'] for tmp in cls_config) @@ -254,13 +252,17 @@ class QoSBase: for index, (match, match_config) in enumerate(cls_config['match'].items(), start=1): filter_cmd = filter_cmd_base if not has_filter: - for key in ['mark', 'vif', 'ip', 'ipv6', 'interface']: + for key in ['mark', 'vif', 'ip', 'ipv6', 'interface', 'ether']: if key in match_config: has_filter = True break + tmp = dict_search(f'ether.protocol', match_config) or 'all' + filter_cmd += f' protocol {tmp}' + if self.qostype in ['shaper', 'shaper_hfsc'] and 'prio ' not in filter_cmd: filter_cmd += f' prio {index}' + if 'mark' in match_config: mark = match_config['mark'] filter_cmd += f' handle {mark} fw' @@ -273,7 +275,7 @@ class QoSBase: iif = Interface(iif_name).get_ifindex() filter_cmd += f' basic match "meta(rt_iif eq {iif})"' - for af in ['ip', 'ipv6']: + for af in ['ip', 'ipv6', 'ether']: tc_af = af if af == 'ipv6': tc_af = 'ip6' @@ -281,67 +283,77 @@ class QoSBase: if af in match_config: filter_cmd += ' u32' - tmp = dict_search(f'{af}.source.address', match_config) - if tmp: filter_cmd += f' match {tc_af} src {tmp}' - - tmp = dict_search(f'{af}.source.port', match_config) - if tmp: filter_cmd += f' match {tc_af} sport {tmp} 0xffff' - - tmp = dict_search(f'{af}.destination.address', match_config) - if tmp: filter_cmd += f' match {tc_af} dst {tmp}' - - tmp = dict_search(f'{af}.destination.port', match_config) - if tmp: filter_cmd += f' match {tc_af} dport {tmp} 0xffff' - - tmp = dict_search(f'{af}.protocol', match_config) - if tmp: - tmp = get_protocol_by_name(tmp) - filter_cmd += f' match {tc_af} protocol {tmp} 0xff' - - tmp = dict_search(f'{af}.dscp', match_config) - if tmp: - tmp = self._get_dsfield(tmp) - if af == 'ip': - filter_cmd += f' match {tc_af} dsfield {tmp} 0xff' - elif af == 'ipv6': - filter_cmd += f' match u16 {tmp} 0x0ff0 at 0' - - # Will match against total length of an IPv4 packet and - # payload length of an IPv6 packet. - # - # IPv4 : match u16 0x0000 ~MAXLEN at 2 - # IPv6 : match u16 0x0000 ~MAXLEN at 4 - tmp = dict_search(f'{af}.max_length', match_config) - if tmp: - # We need the 16 bit two's complement of the maximum - # packet length - tmp = hex(0xffff & ~int(tmp)) - - if af == 'ip': - filter_cmd += f' match u16 0x0000 {tmp} at 2' - elif af == 'ipv6': - filter_cmd += f' match u16 0x0000 {tmp} at 4' - - # We match against specific TCP flags - we assume the IPv4 - # header length is 20 bytes and assume the IPv6 packet is - # not using extension headers (hence a ip header length of 40 bytes) - # TCP Flags are set on byte 13 of the TCP header. - # IPv4 : match u8 X X at 33 - # IPv6 : match u8 X X at 53 - # with X = 0x02 for SYN and X = 0x10 for ACK - tmp = dict_search(f'{af}.tcp', match_config) - if tmp: - mask = 0 - if 'ack' in tmp: - mask |= 0x10 - if 'syn' in tmp: - mask |= 0x02 - mask = hex(mask) - - if af == 'ip': - filter_cmd += f' match u8 {mask} {mask} at 33' - elif af == 'ipv6': - filter_cmd += f' match u8 {mask} {mask} at 53' + if af == 'ether': + src = dict_search(f'{af}.source', match_config) + if src: filter_cmd += f' match {tc_af} src {src}' + + dst = dict_search(f'{af}.destination', match_config) + if dst: filter_cmd += f' match {tc_af} dst {dst}' + + if not src and not dst: + filter_cmd += f' match u32 0 0' + else: + tmp = dict_search(f'{af}.source.address', match_config) + if tmp: filter_cmd += f' match {tc_af} src {tmp}' + + tmp = dict_search(f'{af}.source.port', match_config) + if tmp: filter_cmd += f' match {tc_af} sport {tmp} 0xffff' + + tmp = dict_search(f'{af}.destination.address', match_config) + if tmp: filter_cmd += f' match {tc_af} dst {tmp}' + + tmp = dict_search(f'{af}.destination.port', match_config) + if tmp: filter_cmd += f' match {tc_af} dport {tmp} 0xffff' + ### + tmp = dict_search(f'{af}.protocol', match_config) + if tmp: + tmp = get_protocol_by_name(tmp) + filter_cmd += f' match {tc_af} protocol {tmp} 0xff' + + tmp = dict_search(f'{af}.dscp', match_config) + if tmp: + tmp = self._get_dsfield(tmp) + if af == 'ip': + filter_cmd += f' match {tc_af} dsfield {tmp} 0xff' + elif af == 'ipv6': + filter_cmd += f' match u16 {tmp} 0x0ff0 at 0' + + # Will match against total length of an IPv4 packet and + # payload length of an IPv6 packet. + # + # IPv4 : match u16 0x0000 ~MAXLEN at 2 + # IPv6 : match u16 0x0000 ~MAXLEN at 4 + tmp = dict_search(f'{af}.max_length', match_config) + if tmp: + # We need the 16 bit two's complement of the maximum + # packet length + tmp = hex(0xffff & ~int(tmp)) + + if af == 'ip': + filter_cmd += f' match u16 0x0000 {tmp} at 2' + elif af == 'ipv6': + filter_cmd += f' match u16 0x0000 {tmp} at 4' + + # We match against specific TCP flags - we assume the IPv4 + # header length is 20 bytes and assume the IPv6 packet is + # not using extension headers (hence a ip header length of 40 bytes) + # TCP Flags are set on byte 13 of the TCP header. + # IPv4 : match u8 X X at 33 + # IPv6 : match u8 X X at 53 + # with X = 0x02 for SYN and X = 0x10 for ACK + tmp = dict_search(f'{af}.tcp', match_config) + if tmp: + mask = 0 + if 'ack' in tmp: + mask |= 0x10 + if 'syn' in tmp: + mask |= 0x02 + mask = hex(mask) + + if af == 'ip': + filter_cmd += f' match u8 {mask} {mask} at 33' + elif af == 'ipv6': + filter_cmd += f' match u8 {mask} {mask} at 53' if index != max_index or not has_action_policy: # avoid duplicate last match rule diff --git a/python/vyos/utils/process.py b/python/vyos/utils/process.py index ce880f4a4..d8aabb822 100644 --- a/python/vyos/utils/process.py +++ b/python/vyos/utils/process.py @@ -128,7 +128,7 @@ def run(command, flag='', shell=None, input=None, timeout=None, env=None, def cmd(command, flag='', shell=None, input=None, timeout=None, env=None, stdout=PIPE, stderr=PIPE, decode='utf-8', raising=None, message='', - expect=[0]): + expect=[0], auth=''): """ A wrapper around popen, which returns the stdout and will raise the error code of a command @@ -139,7 +139,7 @@ def cmd(command, flag='', shell=None, input=None, timeout=None, env=None, expect: a list of error codes to consider as normal """ decoded, code = popen( - command, flag, + f'{auth} {command}'.strip(), flag, stdout=stdout, stderr=stderr, input=input, timeout=timeout, env=env, shell=shell, diff --git a/smoketest/scripts/cli/test_qos.py b/smoketest/scripts/cli/test_qos.py index 7714cd3e0..231743344 100755 --- a/smoketest/scripts/cli/test_qos.py +++ b/smoketest/scripts/cli/test_qos.py @@ -1237,6 +1237,72 @@ class TestQoS(VyOSUnitTestSHIM.TestCase): self.assertIn('filter parent ffff: protocol all pref 255 basic chain 0', tc_filters) self.assertIn('action order 1: police 0x2 rate 1Gbit burst 125000000b mtu 2Kb action drop overhead 0b', tc_filters) + def test_24_policy_shaper_match_ether(self): + interface = self._interfaces[0] + bandwidth = 250 + default_bandwidth = 20 + default_ceil = 30 + class_bandwidth = 50 + class_ceil = 80 + + shaper_name = f'qos-shaper-{interface}' + + self.cli_set(base_path + ['interface', interface, 'egress', shaper_name]) + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'bandwidth', f'{bandwidth}mbit']) + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'default', 'bandwidth', f'{default_bandwidth}mbit']) + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'default', 'ceiling', f'{default_ceil}mbit']) + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'default', 'queue-type', 'fair-queue']) + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'class', '23', 'bandwidth', f'{class_bandwidth}mbit']) + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'class', '23', 'ceiling', f'{class_ceil}mbit']) + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'class', '23', 'match', '10', 'ether', 'protocol', 'all']) + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'class', '23', 'match', '10', 'ether', 'destination', '0c:89:0a:2e:00:00']) + self.cli_set(base_path + ['policy', 'shaper', shaper_name, 'class', '23', 'match', '10', 'ether', 'source', '0c:89:0a:2e:00:01']) + + # commit changes + self.cli_commit() + + config_entries = ( + f'root rate {bandwidth}Mbit ceil {bandwidth}Mbit', + f'prio 0 rate {class_bandwidth}Mbit ceil {class_ceil}Mbit', + f'prio 7 rate {default_bandwidth}Mbit ceil {default_ceil}Mbit' + ) + + output = cmd(f'tc class show dev {interface}') + + for config_entry in config_entries: + self.assertIn(config_entry, output) + + filter = get_tc_filter_details(interface) + self.assertIn('match 0c890a2e/ffffffff at -8', filter) + self.assertIn('match 00010000/ffff0000 at -4', filter) + self.assertIn('match 00000c89/0000ffff at -16', filter) + self.assertIn('match 0a2e0000/ffffffff at -12', filter) + + for proto in ['802.1Q', '802_2', '802_3', 'aarp', 'aoe', 'arp', 'atalk', + 'dec', 'ip', 'ipv6', 'ipx', 'lat', 'localtalk', 'rarp', + 'snap', 'x25', 1, 255, 65535]: + self.cli_set( + base_path + ['policy', 'shaper', shaper_name, 'class', '23', + 'match', '10', 'ether', 'protocol', str(proto)]) + self.cli_commit() + + if isinstance(proto, int): + if proto == 1: + self.assertIn(f'filter parent 1: protocol 802_3 pref', + get_tc_filter_details(interface)) + else: + self.assertIn(f'filter parent 1: protocol [{proto}] pref', + get_tc_filter_details(interface)) + + elif proto == '0x000C': + # see other codes in the iproute2 eg https://github.com/iproute2/iproute2/blob/413cf4f03a9b6a219c94b86f41d67992b0a14b82/include/uapi/linux/if_ether.h#L130 + self.assertIn(f'filter parent 1: protocol can pref', + get_tc_filter_details(interface)) + + else: + self.assertIn(f'filter parent 1: protocol {proto} pref', + get_tc_filter_details(interface)) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_dns_dynamic.py b/smoketest/scripts/cli/test_service_dns_dynamic.py index c39d4467a..3e14976fc 100755 --- a/smoketest/scripts/cli/test_service_dns_dynamic.py +++ b/smoketest/scripts/cli/test_service_dns_dynamic.py @@ -22,11 +22,10 @@ from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError from vyos.utils.process import cmd -from vyos.utils.process import process_running +from vyos.utils.process import process_named_running DDCLIENT_SYSTEMD_UNIT = '/run/systemd/system/ddclient.service.d/override.conf' DDCLIENT_CONF = '/run/ddclient/ddclient.conf' -DDCLIENT_PID = '/run/ddclient/ddclient.pid' DDCLIENT_PNAME = 'ddclient' base_path = ['service', 'dns', 'dynamic'] @@ -46,14 +45,14 @@ class TestServiceDDNS(VyOSUnitTestSHIM.TestCase): def tearDown(self): # Check for running process - self.assertTrue(process_running(DDCLIENT_PID)) + self.assertTrue(process_named_running(DDCLIENT_PNAME)) # Delete DDNS configuration self.cli_delete(base_path) self.cli_commit() - # PID file must no londer exist after process exited - self.assertFalse(os.path.exists(DDCLIENT_PID)) + # Check for process not running anymore + self.assertFalse(process_named_running(DDCLIENT_PNAME)) # IPv4 standard DDNS service configuration def test_01_dyndns_service_standard(self): diff --git a/smoketest/scripts/cli/test_service_monitoring_zabbix-agent.py b/smoketest/scripts/cli/test_service_monitoring_zabbix-agent.py index a60dae0a0..522f9df0f 100755 --- a/smoketest/scripts/cli/test_service_monitoring_zabbix-agent.py +++ b/smoketest/scripts/cli/test_service_monitoring_zabbix-agent.py @@ -23,6 +23,7 @@ from vyos.utils.file import read_file PROCESS_NAME = 'zabbix_agent2' ZABBIX_AGENT_CONF = '/run/zabbix/zabbix-agent2.conf' +ZABBIX_PSK_FILE = f'/run/zabbix/zabbix-agent2.psk' base_path = ['service', 'monitoring', 'zabbix-agent'] @@ -82,6 +83,26 @@ class TestZabbixAgent(VyOSUnitTestSHIM.TestCase): self.assertIn(f'Timeout={timeout}', config) self.assertIn(f'Hostname={hostname}', config) + def test_02_zabbix_agent_psk_auth(self): + secret = '8703ce4cb3f51279acba895e1421d69d8a7e2a18546d013d564ad87ac3957f29' + self.cli_set(base_path + ['server', '127.0.0.1']) + self.cli_set(base_path + ['authentication', 'mode', 'pre-shared-secret']) + self.cli_set(base_path + ['authentication', 'psk', 'id', 'smoke_test']) + self.cli_set(base_path + ['authentication', 'psk', 'secret', secret]) + self.cli_commit() + + config = read_file(ZABBIX_AGENT_CONF) + self.assertIn('TLSConnect=psk', config) + self.assertIn('TLSAccept=psk', config) + self.assertIn('TLSPSKIdentity=smoke_test', config) + self.assertIn(f'TLSPSKFile={ZABBIX_PSK_FILE}', config) + self.assertEqual(secret, read_file(ZABBIX_PSK_FILE)) + + secret = '8703ce4cb3f51279acba895e1421d69d8a7e2a18546d013d564ad87ac3957f88' + self.cli_set(base_path + ['authentication', 'psk', 'secret', secret]) + self.cli_commit() + self.assertEqual(secret, read_file(ZABBIX_PSK_FILE)) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/conf_mode/interfaces_bonding.py b/src/conf_mode/interfaces_bonding.py index 0844d2913..4f1141dcb 100755 --- a/src/conf_mode/interfaces_bonding.py +++ b/src/conf_mode/interfaces_bonding.py @@ -17,7 +17,6 @@ from sys import exit from vyos.config import Config -from vyos.configdict import get_frrender_dict from vyos.configdict import get_interface_dict from vyos.configdict import is_node_changed from vyos.configdict import leaf_node_changed @@ -32,6 +31,7 @@ from vyos.configverify import verify_mtu_ipv6 from vyos.configverify import verify_vlan_config from vyos.configverify import verify_vrf from vyos.frrender import FRRender +from vyos.frrender import get_frrender_dict from vyos.ifconfig import BondIf from vyos.ifconfig.ethernet import EthernetIf from vyos.ifconfig import Section diff --git a/src/conf_mode/interfaces_ethernet.py b/src/conf_mode/interfaces_ethernet.py index 5024e6982..41c89fdf8 100755 --- a/src/conf_mode/interfaces_ethernet.py +++ b/src/conf_mode/interfaces_ethernet.py @@ -20,7 +20,6 @@ from sys import exit from vyos.base import Warning from vyos.config import Config -from vyos.configdict import get_frrender_dict from vyos.configdict import get_interface_dict from vyos.configdict import is_node_changed from vyos.configverify import verify_address @@ -35,6 +34,7 @@ from vyos.configverify import verify_bond_bridge_member from vyos.configverify import verify_eapol from vyos.ethtool import Ethtool from vyos.frrender import FRRender +from vyos.frrender import get_frrender_dict from vyos.ifconfig import EthernetIf from vyos.ifconfig import BondIf from vyos.utils.dict import dict_search diff --git a/src/conf_mode/policy.py b/src/conf_mode/policy.py index 5e71a612d..a90e33e81 100755 --- a/src/conf_mode/policy.py +++ b/src/conf_mode/policy.py @@ -17,10 +17,10 @@ from sys import exit from vyos.config import Config -from vyos.configdict import get_frrender_dict from vyos.configverify import has_frr_protocol_in_dict from vyos.frrender import FRRender from vyos.frrender import frr_protocols +from vyos.frrender import get_frrender_dict from vyos.utils.dict import dict_search from vyos.utils.process import is_systemd_service_running from vyos import ConfigError diff --git a/src/conf_mode/protocols_babel.py b/src/conf_mode/protocols_babel.py index 48b7ae734..80a847af8 100755 --- a/src/conf_mode/protocols_babel.py +++ b/src/conf_mode/protocols_babel.py @@ -17,11 +17,11 @@ from sys import exit from vyos.config import Config -from vyos.configdict import get_frrender_dict from vyos.configverify import has_frr_protocol_in_dict from vyos.configverify import verify_access_list from vyos.configverify import verify_prefix_list from vyos.frrender import FRRender +from vyos.frrender import get_frrender_dict from vyos.utils.dict import dict_search from vyos.utils.process import is_systemd_service_running from vyos import ConfigError diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py index 2e7d40676..d3bc3e961 100755 --- a/src/conf_mode/protocols_bfd.py +++ b/src/conf_mode/protocols_bfd.py @@ -15,10 +15,10 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. from vyos.config import Config -from vyos.configdict import get_frrender_dict from vyos.configverify import verify_vrf from vyos.configverify import has_frr_protocol_in_dict from vyos.frrender import FRRender +from vyos.frrender import get_frrender_dict from vyos.template import is_ipv6 from vyos.utils.network import is_ipv6_link_local from vyos.utils.process import is_systemd_service_running diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index 60f3f2ad0..c4af717af 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -19,12 +19,12 @@ from sys import argv from vyos.base import Warning from vyos.config import Config -from vyos.configdict import get_frrender_dict from vyos.configverify import has_frr_protocol_in_dict from vyos.configverify import verify_prefix_list from vyos.configverify import verify_route_map from vyos.configverify import verify_vrf from vyos.frrender import FRRender +from vyos.frrender import get_frrender_dict from vyos.template import is_ip from vyos.template import is_interface from vyos.utils.dict import dict_search diff --git a/src/conf_mode/protocols_eigrp.py b/src/conf_mode/protocols_eigrp.py index 8f49bb151..324ff883f 100755 --- a/src/conf_mode/protocols_eigrp.py +++ b/src/conf_mode/protocols_eigrp.py @@ -18,11 +18,11 @@ from sys import exit from sys import argv from vyos.config import Config -from vyos.configdict import get_frrender_dict from vyos.configverify import has_frr_protocol_in_dict from vyos.configverify import verify_vrf from vyos.utils.process import is_systemd_service_running from vyos.frrender import FRRender +from vyos.frrender import get_frrender_dict from vyos import ConfigError from vyos import airbag airbag.enable() diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py index 1e5f0d6e8..1c994492e 100755 --- a/src/conf_mode/protocols_isis.py +++ b/src/conf_mode/protocols_isis.py @@ -18,11 +18,11 @@ from sys import exit from sys import argv from vyos.config import Config -from vyos.configdict import get_frrender_dict from vyos.configverify import has_frr_protocol_in_dict from vyos.configverify import verify_common_route_maps from vyos.configverify import verify_interface_exists from vyos.frrender import FRRender +from vyos.frrender import get_frrender_dict from vyos.ifconfig import Interface from vyos.utils.dict import dict_search from vyos.utils.network import get_interface_config diff --git a/src/conf_mode/protocols_mpls.py b/src/conf_mode/protocols_mpls.py index e8097b7ff..33d9a6dae 100755 --- a/src/conf_mode/protocols_mpls.py +++ b/src/conf_mode/protocols_mpls.py @@ -20,9 +20,9 @@ from sys import exit from glob import glob from vyos.config import Config -from vyos.configdict import get_frrender_dict from vyos.configverify import has_frr_protocol_in_dict from vyos.frrender import FRRender +from vyos.frrender import get_frrender_dict from vyos.utils.dict import dict_search from vyos.utils.file import read_file from vyos.utils.process import is_systemd_service_running diff --git a/src/conf_mode/protocols_openfabric.py b/src/conf_mode/protocols_openfabric.py index 41c5d9544..7df11fb20 100644 --- a/src/conf_mode/protocols_openfabric.py +++ b/src/conf_mode/protocols_openfabric.py @@ -18,11 +18,11 @@ from sys import exit from vyos.base import Warning from vyos.config import Config -from vyos.configdict import get_frrender_dict from vyos.configverify import verify_interface_exists from vyos.configverify import has_frr_protocol_in_dict from vyos.utils.process import is_systemd_service_running from vyos.frrender import FRRender +from vyos.frrender import get_frrender_dict from vyos import ConfigError from vyos import airbag airbag.enable() diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py index f2c95a63c..c06c0aafc 100755 --- a/src/conf_mode/protocols_ospf.py +++ b/src/conf_mode/protocols_ospf.py @@ -18,13 +18,13 @@ from sys import exit from sys import argv from vyos.config import Config -from vyos.configdict import get_frrender_dict from vyos.configverify import verify_common_route_maps from vyos.configverify import verify_route_map from vyos.configverify import verify_interface_exists from vyos.configverify import verify_access_list from vyos.configverify import has_frr_protocol_in_dict from vyos.frrender import FRRender +from vyos.frrender import get_frrender_dict from vyos.utils.dict import dict_search from vyos.utils.network import get_interface_config from vyos.utils.process import is_systemd_service_running diff --git a/src/conf_mode/protocols_ospfv3.py b/src/conf_mode/protocols_ospfv3.py index ac189c378..2563eb7d5 100755 --- a/src/conf_mode/protocols_ospfv3.py +++ b/src/conf_mode/protocols_ospfv3.py @@ -18,12 +18,12 @@ from sys import exit from sys import argv from vyos.config import Config -from vyos.configdict import get_frrender_dict from vyos.configverify import verify_common_route_maps from vyos.configverify import verify_route_map from vyos.configverify import verify_interface_exists from vyos.configverify import has_frr_protocol_in_dict from vyos.frrender import FRRender +from vyos.frrender import get_frrender_dict from vyos.ifconfig import Interface from vyos.utils.dict import dict_search from vyos.utils.network import get_interface_config diff --git a/src/conf_mode/protocols_pim.py b/src/conf_mode/protocols_pim.py index 477895b0b..632099964 100755 --- a/src/conf_mode/protocols_pim.py +++ b/src/conf_mode/protocols_pim.py @@ -22,10 +22,10 @@ from signal import SIGTERM from sys import exit from vyos.config import Config -from vyos.configdict import get_frrender_dict from vyos.configverify import verify_interface_exists from vyos.configverify import has_frr_protocol_in_dict from vyos.frrender import FRRender +from vyos.frrender import get_frrender_dict from vyos.frrender import pim_daemon from vyos.utils.process import is_systemd_service_running from vyos.utils.process import process_named_running diff --git a/src/conf_mode/protocols_pim6.py b/src/conf_mode/protocols_pim6.py index 3a9b876cc..03a79139a 100755 --- a/src/conf_mode/protocols_pim6.py +++ b/src/conf_mode/protocols_pim6.py @@ -19,11 +19,11 @@ from ipaddress import IPv6Network from sys import exit from vyos.config import Config -from vyos.configdict import get_frrender_dict from vyos.configverify import has_frr_protocol_in_dict from vyos.configverify import verify_interface_exists from vyos.utils.process import is_systemd_service_running from vyos.frrender import FRRender +from vyos.frrender import get_frrender_dict from vyos import ConfigError from vyos import airbag airbag.enable() diff --git a/src/conf_mode/protocols_rip.py b/src/conf_mode/protocols_rip.py index 39743f965..ec9dfbb8b 100755 --- a/src/conf_mode/protocols_rip.py +++ b/src/conf_mode/protocols_rip.py @@ -17,12 +17,12 @@ from sys import exit from vyos.config import Config -from vyos.configdict import get_frrender_dict from vyos.configverify import has_frr_protocol_in_dict from vyos.configverify import verify_common_route_maps from vyos.configverify import verify_access_list from vyos.configverify import verify_prefix_list from vyos.frrender import FRRender +from vyos.frrender import get_frrender_dict from vyos.utils.dict import dict_search from vyos.utils.process import is_systemd_service_running from vyos import ConfigError diff --git a/src/conf_mode/protocols_ripng.py b/src/conf_mode/protocols_ripng.py index 14f038444..9a9ac8ec8 100755 --- a/src/conf_mode/protocols_ripng.py +++ b/src/conf_mode/protocols_ripng.py @@ -17,12 +17,12 @@ from sys import exit from vyos.config import Config -from vyos.configdict import get_frrender_dict from vyos.configverify import has_frr_protocol_in_dict from vyos.configverify import verify_common_route_maps from vyos.configverify import verify_access_list from vyos.configverify import verify_prefix_list from vyos.frrender import FRRender +from vyos.frrender import get_frrender_dict from vyos.utils.dict import dict_search from vyos.utils.process import is_systemd_service_running from vyos import ConfigError diff --git a/src/conf_mode/protocols_rpki.py b/src/conf_mode/protocols_rpki.py index 5ad656586..ef0250e3d 100755 --- a/src/conf_mode/protocols_rpki.py +++ b/src/conf_mode/protocols_rpki.py @@ -20,9 +20,9 @@ from glob import glob from sys import exit from vyos.config import Config -from vyos.configdict import get_frrender_dict from vyos.configverify import has_frr_protocol_in_dict from vyos.frrender import FRRender +from vyos.frrender import get_frrender_dict from vyos.pki import wrap_openssh_public_key from vyos.pki import wrap_openssh_private_key from vyos.utils.dict import dict_search_args diff --git a/src/conf_mode/protocols_segment-routing.py b/src/conf_mode/protocols_segment-routing.py index 99cf87556..f2bd42a79 100755 --- a/src/conf_mode/protocols_segment-routing.py +++ b/src/conf_mode/protocols_segment-routing.py @@ -17,10 +17,10 @@ from sys import exit from vyos.config import Config -from vyos.configdict import get_frrender_dict from vyos.configdict import list_diff from vyos.configverify import has_frr_protocol_in_dict from vyos.frrender import FRRender +from vyos.frrender import get_frrender_dict from vyos.ifconfig import Section from vyos.utils.dict import dict_search from vyos.utils.process import is_systemd_service_running diff --git a/src/conf_mode/protocols_static.py b/src/conf_mode/protocols_static.py index 9d02db6dd..1b9e51167 100755 --- a/src/conf_mode/protocols_static.py +++ b/src/conf_mode/protocols_static.py @@ -19,11 +19,11 @@ from sys import exit from sys import argv from vyos.config import Config -from vyos.configdict import get_frrender_dict from vyos.configverify import has_frr_protocol_in_dict from vyos.configverify import verify_common_route_maps from vyos.configverify import verify_vrf from vyos.frrender import FRRender +from vyos.frrender import get_frrender_dict from vyos.utils.process import is_systemd_service_running from vyos.template import render from vyos import ConfigError diff --git a/src/conf_mode/service_monitoring_zabbix-agent.py b/src/conf_mode/service_monitoring_zabbix-agent.py index 98d8a32ca..f17146a8d 100755 --- a/src/conf_mode/service_monitoring_zabbix-agent.py +++ b/src/conf_mode/service_monitoring_zabbix-agent.py @@ -18,6 +18,8 @@ import os from vyos.config import Config from vyos.template import render +from vyos.utils.dict import dict_search +from vyos.utils.file import write_file from vyos.utils.process import call from vyos import ConfigError from vyos import airbag @@ -26,6 +28,7 @@ airbag.enable() service_name = 'zabbix-agent2' service_conf = f'/run/zabbix/{service_name}.conf' +service_psk_file = f'/run/zabbix/{service_name}.psk' systemd_override = r'/run/systemd/system/zabbix-agent2.service.d/10-override.conf' @@ -49,6 +52,8 @@ def get_config(config=None): if 'directory' in config and config['directory'].endswith('/'): config['directory'] = config['directory'][:-1] + config['service_psk_file'] = service_psk_file + return config @@ -60,18 +65,34 @@ def verify(config): if 'server' not in config: raise ConfigError('Server is required!') + if 'authentication' in config and dict_search("authentication.mode", + config) == 'pre_shared_secret': + if 'id' not in config['authentication']['psk']: + raise ConfigError( + 'PSK identity is required for pre-shared-secret authentication mode') + + if 'secret' not in config['authentication']['psk']: + raise ConfigError( + 'PSK secret is required for pre-shared-secret authentication mode') + def generate(config): # bail out early - looks like removal from running config if config is None: # Remove old config and return - config_files = [service_conf, systemd_override] + config_files = [service_conf, systemd_override, service_psk_file] for file in config_files: if os.path.isfile(file): os.unlink(file) return None + if not dict_search("authentication.psk.secret", config): + if os.path.isfile(service_psk_file): + os.unlink(service_psk_file) + else: + write_file(service_psk_file, config["authentication"]["psk"]["secret"]) + # Write configuration file render(service_conf, 'zabbix-agent/zabbix-agent.conf.j2', config) render(systemd_override, 'zabbix-agent/10-override.conf.j2', config) diff --git a/src/conf_mode/system_ip.py b/src/conf_mode/system_ip.py index 86843eb78..7f3796168 100755 --- a/src/conf_mode/system_ip.py +++ b/src/conf_mode/system_ip.py @@ -19,10 +19,10 @@ from sys import exit from vyos.config import Config from vyos.configdep import set_dependents from vyos.configdep import call_dependents -from vyos.configdict import get_frrender_dict from vyos.configverify import has_frr_protocol_in_dict from vyos.configverify import verify_route_map from vyos.frrender import FRRender +from vyos.frrender import get_frrender_dict from vyos.utils.dict import dict_search from vyos.utils.process import is_systemd_service_active from vyos.utils.process import is_systemd_service_running diff --git a/src/conf_mode/system_ipv6.py b/src/conf_mode/system_ipv6.py index 593b8f7f3..309869b2f 100755 --- a/src/conf_mode/system_ipv6.py +++ b/src/conf_mode/system_ipv6.py @@ -20,10 +20,10 @@ from sys import exit from vyos.config import Config from vyos.configdep import set_dependents from vyos.configdep import call_dependents -from vyos.configdict import get_frrender_dict from vyos.configverify import has_frr_protocol_in_dict from vyos.configverify import verify_route_map from vyos.frrender import FRRender +from vyos.frrender import get_frrender_dict from vyos.utils.dict import dict_search from vyos.utils.file import write_file from vyos.utils.process import is_systemd_service_active diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py index 6533f493f..74780b601 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -19,11 +19,11 @@ from jmespath import search from json import loads from vyos.config import Config -from vyos.configdict import get_frrender_dict from vyos.configdict import node_changed from vyos.configverify import verify_route_map from vyos.firewall import conntrack_required from vyos.frrender import FRRender +from vyos.frrender import get_frrender_dict from vyos.ifconfig import Interface from vyos.template import render from vyos.utils.dict import dict_search diff --git a/src/helpers/latest-image-url.py b/src/helpers/latest-image-url.py new file mode 100755 index 000000000..ea201ef7c --- /dev/null +++ b/src/helpers/latest-image-url.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 + +import sys + +from vyos.configquery import ConfigTreeQuery +from vyos.version import get_remote_version + + +if __name__ == '__main__': + image_path = '' + + config = ConfigTreeQuery() + if config.exists('system update-check url'): + configured_url_version = config.value('system update-check url') + remote_url_list = get_remote_version(configured_url_version) + if remote_url_list: + image_path = remote_url_list[0].get('url') + else: + sys.exit(1) + + print(image_path) diff --git a/src/op_mode/generate_psk.py b/src/op_mode/generate_psk.py new file mode 100644 index 000000000..d51293712 --- /dev/null +++ b/src/op_mode/generate_psk.py @@ -0,0 +1,45 @@ +#!/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 argparse + +from vyos.utils.process import cmd + + +def validate_hex_size(value): + """Validate that the hex_size is between 32 and 512.""" + try: + value = int(value) + except ValueError: + raise argparse.ArgumentTypeError("hex_size must be integer.") + + if value < 32 or value > 512: + raise argparse.ArgumentTypeError("hex_size must be between 32 and 512.") + return value + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument( + "--hex_size", + type=validate_hex_size, + help='PKS value size in hex format. Default is 32 bytes.', + default=32, + + required=False, + ) + args = parser.parse_args() + + print(cmd(f'openssl rand -hex {args.hex_size}'))
\ No newline at end of file diff --git a/src/op_mode/image_installer.py b/src/op_mode/image_installer.py index bdc16de15..1da112673 100755 --- a/src/op_mode/image_installer.py +++ b/src/op_mode/image_installer.py @@ -33,14 +33,13 @@ from errno import ENOSPC from psutil import disk_partitions from vyos.configtree import ConfigTree -from vyos.configquery import ConfigTreeQuery from vyos.remote import download from vyos.system import disk, grub, image, compat, raid, SYSTEM_CFG_VER from vyos.template import render from vyos.utils.io import ask_input, ask_yes_no, select_entry from vyos.utils.file import chmod_2775 -from vyos.utils.process import cmd, run -from vyos.version import get_remote_version, get_version_data +from vyos.utils.process import cmd, run, rc_cmd +from vyos.version import get_version_data # define text messages MSG_ERR_NOT_LIVE: str = 'The system is already installed. Please use "add system image" instead.' @@ -99,6 +98,7 @@ FILE_ROOTFS_SRC: str = '/usr/lib/live/mount/medium/live/filesystem.squashfs' ISO_DOWNLOAD_PATH: str = '/tmp/vyos_installation.iso' external_download_script = '/usr/libexec/vyos/simple-download.py' +external_latest_image_url_script = '/usr/libexec/vyos/latest-image-url.py' # default boot variables DEFAULT_BOOT_VARS: dict[str, str] = { @@ -532,10 +532,10 @@ def download_file(local_file: str, remote_path: str, vrf: str, download(local_file, remote_path, progressbar=progressbar, check_space=check_space, raise_error=True) else: - vrf_cmd = f'REMOTE_USERNAME={username} REMOTE_PASSWORD={password} \ - ip vrf exec {vrf} {external_download_script} \ - --local-file {local_file} --remote-path {remote_path}' - cmd(vrf_cmd) + remote_auth = f'REMOTE_USERNAME={username} REMOTE_PASSWORD={password}' + vrf_cmd = f'ip vrf exec {vrf} {external_download_script} \ + --local-file {local_file} --remote-path {remote_path}' + cmd(vrf_cmd, auth=remote_auth) def image_fetch(image_path: str, vrf: str = None, username: str = '', password: str = '', @@ -550,11 +550,15 @@ def image_fetch(image_path: str, vrf: str = None, """ # Latest version gets url from configured "system update-check url" if image_path == 'latest': - config = ConfigTreeQuery() - if config.exists('system update-check url'): - configured_url_version = config.value('system update-check url') - remote_url_list = get_remote_version(configured_url_version) - image_path = remote_url_list[0].get('url') + command = external_latest_image_url_script + if vrf: + command = f'REMOTE_USERNAME={username} REMOTE_PASSWORD={password} \ + ip vrf exec {vrf} ' + command + code, output = rc_cmd(command) + if code: + print(output) + exit(MSG_INFO_INSTALL_EXIT) + image_path = output if output else image_path try: # check a type of path diff --git a/src/services/vyos-configd b/src/services/vyos-configd index ecad85801..d558e8c26 100755 --- a/src/services/vyos-configd +++ b/src/services/vyos-configd @@ -38,6 +38,7 @@ from vyos.configsource import ConfigSourceError from vyos.configdiff import get_commit_scripts from vyos.config import Config from vyos.frrender import FRRender +from vyos.frrender import get_frrender_dict from vyos import ConfigError CFG_GROUP = 'vyattacfg' @@ -333,6 +334,8 @@ if __name__ == '__main__': if hasattr(config, 'frrender_cls') and res == R_SUCCESS: frrender_cls = getattr(config, 'frrender_cls') + tmp = get_frrender_dict(config) + frrender_cls.generate(tmp) frrender_cls.apply() else: logger.critical(f'Unexpected message: {message}') diff --git a/src/validators/ether-type b/src/validators/ether-type new file mode 100644 index 000000000..926db26d3 --- /dev/null +++ b/src/validators/ether-type @@ -0,0 +1,37 @@ +#!/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 re +from sys import argv,exit + +if __name__ == '__main__': + if len(argv) != 2: + exit(1) + + input = argv[1] + try: + # ethertype can be in the range 1 - 65535 + if int(input) in range(1, 65536): + exit(0) + except ValueError: + pass + + pattern = "!?\\b(all|ip|ipv6|ipx|802.1Q|802_2|802_3|aarp|aoe|arp|atalk|dec|lat|localtalk|rarp|snap|x25)\\b" + if re.match(pattern, input): + exit(0) + + print(f'Error: {input} is not a valid ether type or protocol.') + exit(1) |
