summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/zabbix-agent/zabbix-agent.conf.j213
-rw-r--r--interface-definitions/include/auth-mode-pre-shared-secret.xml.i14
-rw-r--r--interface-definitions/include/auth-psk-id.xml.i11
-rw-r--r--interface-definitions/include/auth-psk-secret.xml.i15
-rw-r--r--interface-definitions/include/qos/class-match.xml.i50
-rw-r--r--interface-definitions/include/stunnel/psk.xml.i23
-rw-r--r--interface-definitions/service_monitoring_zabbix-agent.xml.in17
-rw-r--r--interface-definitions/vpn_ipsec.xml.in13
-rw-r--r--op-mode-definitions/generate-psk.xml.in28
-rw-r--r--python/vyos/configdict.py495
-rw-r--r--python/vyos/frrender.py520
-rw-r--r--python/vyos/ifconfig/interface.py11
-rw-r--r--python/vyos/ifconfig/macvlan.py5
-rw-r--r--python/vyos/qos/base.py142
-rw-r--r--python/vyos/utils/process.py4
-rwxr-xr-xsmoketest/scripts/cli/test_qos.py66
-rwxr-xr-xsmoketest/scripts/cli/test_service_dns_dynamic.py9
-rwxr-xr-xsmoketest/scripts/cli/test_service_monitoring_zabbix-agent.py21
-rwxr-xr-xsrc/conf_mode/interfaces_bonding.py2
-rwxr-xr-xsrc/conf_mode/interfaces_ethernet.py2
-rwxr-xr-xsrc/conf_mode/policy.py2
-rwxr-xr-xsrc/conf_mode/protocols_babel.py2
-rwxr-xr-xsrc/conf_mode/protocols_bfd.py2
-rwxr-xr-xsrc/conf_mode/protocols_bgp.py2
-rwxr-xr-xsrc/conf_mode/protocols_eigrp.py2
-rwxr-xr-xsrc/conf_mode/protocols_isis.py2
-rwxr-xr-xsrc/conf_mode/protocols_mpls.py2
-rw-r--r--src/conf_mode/protocols_openfabric.py2
-rwxr-xr-xsrc/conf_mode/protocols_ospf.py2
-rwxr-xr-xsrc/conf_mode/protocols_ospfv3.py2
-rwxr-xr-xsrc/conf_mode/protocols_pim.py2
-rwxr-xr-xsrc/conf_mode/protocols_pim6.py2
-rwxr-xr-xsrc/conf_mode/protocols_rip.py2
-rwxr-xr-xsrc/conf_mode/protocols_ripng.py2
-rwxr-xr-xsrc/conf_mode/protocols_rpki.py2
-rwxr-xr-xsrc/conf_mode/protocols_segment-routing.py2
-rwxr-xr-xsrc/conf_mode/protocols_static.py2
-rwxr-xr-xsrc/conf_mode/service_monitoring_zabbix-agent.py23
-rwxr-xr-xsrc/conf_mode/system_ip.py2
-rwxr-xr-xsrc/conf_mode/system_ipv6.py2
-rwxr-xr-xsrc/conf_mode/vrf.py2
-rwxr-xr-xsrc/helpers/latest-image-url.py21
-rw-r--r--src/op_mode/generate_psk.py45
-rwxr-xr-xsrc/op_mode/image_installer.py28
-rwxr-xr-xsrc/services/vyos-configd3
-rw-r--r--src/validators/ether-type37
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)