summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/check-unused-imports.yml2
-rw-r--r--.gitignore2
-rw-r--r--Makefile2
-rw-r--r--data/templates/accel-ppp/ipoe.config.j22
-rw-r--r--data/templates/accel-ppp/pppoe.config.j22
-rwxr-xr-x[-rw-r--r--]data/templates/firewall/nftables.j26
-rw-r--r--data/templates/ids/suricata.j22
-rwxr-xr-x[-rw-r--r--]interface-definitions/firewall.xml.in2
-rw-r--r--interface-definitions/include/accel-ppp/vlan-mon.xml.i8
-rw-r--r--interface-definitions/include/dhcp/option-v6.xml.i12
-rw-r--r--interface-definitions/include/version/dhcpv6-server-version.xml.i2
-rw-r--r--interface-definitions/include/version/ipoe-server-version.xml.i2
-rw-r--r--interface-definitions/include/version/pppoe-server-version.xml.i2
-rw-r--r--interface-definitions/service_dhcpv6-server.xml.in22
-rw-r--r--interface-definitions/service_ipoe-server.xml.in1
-rw-r--r--interface-definitions/service_pppoe-server.xml.in1
-rw-r--r--op-mode-definitions/execute.xml.in8
-rw-r--r--python/vyos/config.py3
-rw-r--r--python/vyos/configsource.py2
-rw-r--r--python/vyos/configverify.py13
-rw-r--r--python/vyos/defaults.py3
-rwxr-xr-x[-rw-r--r--]python/vyos/firewall.py15
-rw-r--r--python/vyos/opmode.py2
-rwxr-xr-x[-rw-r--r--]python/vyos/template.py7
-rw-r--r--python/vyos/utils/file.py2
-rw-r--r--python/vyos/xml_ref/__init__.py23
-rwxr-xr-xpython/vyos/xml_ref/generate_op_cache.py174
-rw-r--r--python/vyos/xml_ref/op_definition.py49
-rwxr-xr-xsmoketest/scripts/cli/test_firewall.py22
-rwxr-xr-xsmoketest/scripts/cli/test_service_ipoe-server.py32
-rwxr-xr-xsmoketest/scripts/cli/test_service_pppoe-server.py7
-rwxr-xr-xsmoketest/scripts/cli/test_system_conntrack.py1
-rwxr-xr-xsrc/conf_mode/firewall.py2
-rwxr-xr-xsrc/conf_mode/interfaces_ethernet.py4
-rwxr-xr-xsrc/conf_mode/interfaces_wwan.py2
-rwxr-xr-xsrc/conf_mode/policy_local-route.py2
-rwxr-xr-xsrc/conf_mode/protocols_igmp-proxy.py2
-rwxr-xr-xsrc/conf_mode/protocols_isis.py2
-rwxr-xr-xsrc/conf_mode/protocols_mpls.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/qos.py2
-rwxr-xr-xsrc/conf_mode/service_broadcast-relay.py2
-rwxr-xr-xsrc/conf_mode/service_conntrack-sync.py2
-rwxr-xr-xsrc/conf_mode/service_dns_dynamic.py2
-rwxr-xr-xsrc/conf_mode/service_ipoe-server.py4
-rwxr-xr-xsrc/conf_mode/service_mdns_repeater.py2
-rwxr-xr-xsrc/conf_mode/service_ndp-proxy.py2
-rwxr-xr-xsrc/conf_mode/service_ntp.py2
-rwxr-xr-xsrc/conf_mode/service_pppoe-server.py7
-rwxr-xr-xsrc/conf_mode/service_salt-minion.py2
-rwxr-xr-xsrc/conf_mode/service_suricata.py2
-rwxr-xr-xsrc/conf_mode/system_flow-accounting.py2
-rwxr-xr-xsrc/conf_mode/system_option.py2
-rwxr-xr-xsrc/conf_mode/vpn_ipsec.py8
-rw-r--r--src/migration-scripts/dhcpv6-server/5-to-631
-rw-r--r--src/migration-scripts/ipoe-server/3-to-430
-rw-r--r--src/migration-scripts/pppoe-server/10-to-1130
-rwxr-xr-xsrc/op_mode/pki.py8
-rw-r--r--src/opt/vyatta/etc/shell/level/users/allowed-op1
-rw-r--r--src/opt/vyatta/etc/shell/level/users/allowed-op.in1
-rwxr-xr-xsrc/services/vyos-configd12
-rw-r--r--src/shim/vyshim.c24
65 files changed, 545 insertions, 90 deletions
diff --git a/.github/workflows/check-unused-imports.yml b/.github/workflows/check-unused-imports.yml
index 9fca5add6..d6dd61483 100644
--- a/.github/workflows/check-unused-imports.yml
+++ b/.github/workflows/check-unused-imports.yml
@@ -1,6 +1,6 @@
name: Check for unused imports using Pylint
on:
- pull_request:
+ pull_request_target:
branches:
- current
diff --git a/.gitignore b/.gitignore
index 01333d5b1..c597d9c84 100644
--- a/.gitignore
+++ b/.gitignore
@@ -145,6 +145,8 @@ data/component-versions.json
# vyos-1x XML cache
python/vyos/xml_ref/cache.py
python/vyos/xml_ref/pkg_cache/*_cache.py
+python/vyos/xml_ref/op_cache.py
+python/vyos/xml_ref/pkg_cache/*_op_cache.py
# autogenerated vyos-configd JSON definition
data/configd-include.json
diff --git a/Makefile b/Makefile
index 685c8f150..c83380be5 100644
--- a/Makefile
+++ b/Makefile
@@ -55,6 +55,8 @@ op_mode_definitions: $(op_xml_obj)
find $(BUILD_DIR)/op-mode-definitions/ -type f -name "*.xml" | xargs -I {} $(CURDIR)/scripts/build-command-op-templates {} $(CURDIR)/schema/op-mode-definition.rng $(OP_TMPL_DIR) || exit 1
+ $(CURDIR)/python/vyos/xml_ref/generate_op_cache.py --xml-dir $(BUILD_DIR)/op-mode-definitions || exit 1
+
# XXX: tcpdump, ping, traceroute and mtr must be able to recursivly call themselves as the
# options are provided from the scripts themselves
ln -s ../node.tag $(OP_TMPL_DIR)/ping/node.tag/node.tag/
diff --git a/data/templates/accel-ppp/ipoe.config.j2 b/data/templates/accel-ppp/ipoe.config.j2
index 9729b295e..81f63c53b 100644
--- a/data/templates/accel-ppp/ipoe.config.j2
+++ b/data/templates/accel-ppp/ipoe.config.j2
@@ -56,7 +56,7 @@ verbose=1
{% set relay = ',' ~ 'relay=' ~ iface_config.external_dhcp.dhcp_relay if iface_config.external_dhcp.dhcp_relay is vyos_defined else '' %}
{% set giaddr = ',' ~ 'giaddr=' ~ iface_config.external_dhcp.giaddr if iface_config.external_dhcp.giaddr is vyos_defined else '' %}
{{ tmp }},{{ shared }}mode={{ iface_config.mode | upper }},ifcfg=1,{{ range }}start=dhcpv4,ipv6=1{{ relay }}{{ giaddr }}
-{% if iface_config.vlan is vyos_defined %}
+{% if iface_config.vlan_mon is vyos_defined %}
vlan-mon={{ iface }},{{ iface_config.vlan | join(',') }}
{% endif %}
{% endfor %}
diff --git a/data/templates/accel-ppp/pppoe.config.j2 b/data/templates/accel-ppp/pppoe.config.j2
index 73ffe0963..beab46936 100644
--- a/data/templates/accel-ppp/pppoe.config.j2
+++ b/data/templates/accel-ppp/pppoe.config.j2
@@ -61,7 +61,9 @@ interface={{ iface }}
{% for vlan in iface_config.vlan %}
interface=re:^{{ iface }}\.{{ vlan | range_to_regex }}$
{% endfor %}
+{% if iface_config.vlan_mon is vyos_defined %}
vlan-mon={{ iface }},{{ iface_config.vlan | join(',') }}
+{% endif %}
{% endif %}
{% endfor %}
{% endif %}
diff --git a/data/templates/firewall/nftables.j2 b/data/templates/firewall/nftables.j2
index 82dcefac0..155b7f4d0 100644..100755
--- a/data/templates/firewall/nftables.j2
+++ b/data/templates/firewall/nftables.j2
@@ -135,7 +135,7 @@ table ip vyos_filter {
{% endif %}
{% endfor %}
{% endif %}
- {{ conf | nft_default_rule(name_text, 'ipv4') }}
+ {{ conf | nft_default_rule('NAM-' + name_text, 'ipv4') }}
}
{% endfor %}
{% endif %}
@@ -287,7 +287,7 @@ table ip6 vyos_filter {
{% endif %}
{% endfor %}
{% endif %}
- {{ conf | nft_default_rule(name_text, 'ipv6') }}
+ {{ conf | nft_default_rule('NAM-' + name_text, 'ipv6') }}
}
{% endfor %}
{% endif %}
@@ -416,7 +416,7 @@ table bridge vyos_filter {
{% endif %}
{% endfor %}
{% endif %}
- {{ conf | nft_default_rule(name_text, 'bri') }}
+ {{ conf | nft_default_rule('NAM-' + name_text, 'bri') }}
}
{% endfor %}
{% endif %}
diff --git a/data/templates/ids/suricata.j2 b/data/templates/ids/suricata.j2
index 585db93eb..d76994c47 100644
--- a/data/templates/ids/suricata.j2
+++ b/data/templates/ids/suricata.j2
@@ -79,7 +79,7 @@ af-packet:
{% for interface in suricata.interface %}
- interface: {{ interface }}
# Default clusterid. AF_PACKET will load balance packets based on flow.
- cluster-id: 99
+ cluster-id: {{ 100 - loop.index }}
# Default AF_PACKET cluster type. AF_PACKET can load balance per flow or per hash.
# This is only supported for Linux kernel > 3.1
# possible value are:
diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in
index 816dd1855..07c88f799 100644..100755
--- a/interface-definitions/firewall.xml.in
+++ b/interface-definitions/firewall.xml.in
@@ -2,7 +2,7 @@
<interfaceDefinition>
<node name="firewall" owner="${vyos_conf_scripts_dir}/firewall.py">
<properties>
- <priority>319</priority>
+ <priority>489</priority>
<help>Firewall</help>
</properties>
<children>
diff --git a/interface-definitions/include/accel-ppp/vlan-mon.xml.i b/interface-definitions/include/accel-ppp/vlan-mon.xml.i
new file mode 100644
index 000000000..d5bacb0d1
--- /dev/null
+++ b/interface-definitions/include/accel-ppp/vlan-mon.xml.i
@@ -0,0 +1,8 @@
+<!-- include start from accel-ppp/vlan-mon.xml.i -->
+<leafNode name="vlan-mon">
+ <properties>
+ <help>Automatically create VLAN interfaces</help>
+ <valueless/>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/dhcp/option-v6.xml.i b/interface-definitions/include/dhcp/option-v6.xml.i
index 1df0c3934..e1897f52d 100644
--- a/interface-definitions/include/dhcp/option-v6.xml.i
+++ b/interface-definitions/include/dhcp/option-v6.xml.i
@@ -78,6 +78,18 @@
<multi/>
</properties>
</leafNode>
+ <leafNode name="info-refresh-time">
+ <properties>
+ <help>Time (in seconds) that stateless clients should wait between refreshing the information they were given</help>
+ <valueHelp>
+ <format>u32:1-4294967295</format>
+ <description>DHCPv6 information refresh time</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-4294967295"/>
+ </constraint>
+ </properties>
+ </leafNode>
<node name="vendor-option">
<properties>
<help>Vendor Specific Options</help>
diff --git a/interface-definitions/include/version/dhcpv6-server-version.xml.i b/interface-definitions/include/version/dhcpv6-server-version.xml.i
index 1f30368a3..8b72a9c72 100644
--- a/interface-definitions/include/version/dhcpv6-server-version.xml.i
+++ b/interface-definitions/include/version/dhcpv6-server-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/dhcpv6-server-version.xml.i -->
-<syntaxVersion component='dhcpv6-server' version='5'></syntaxVersion>
+<syntaxVersion component='dhcpv6-server' version='6'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/include/version/ipoe-server-version.xml.i b/interface-definitions/include/version/ipoe-server-version.xml.i
index 659433382..b7718fc5e 100644
--- a/interface-definitions/include/version/ipoe-server-version.xml.i
+++ b/interface-definitions/include/version/ipoe-server-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/ipoe-server-version.xml.i -->
-<syntaxVersion component='ipoe-server' version='3'></syntaxVersion>
+<syntaxVersion component='ipoe-server' version='4'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/include/version/pppoe-server-version.xml.i b/interface-definitions/include/version/pppoe-server-version.xml.i
index 61de1277a..2e020faa3 100644
--- a/interface-definitions/include/version/pppoe-server-version.xml.i
+++ b/interface-definitions/include/version/pppoe-server-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/pppoe-server-version.xml.i -->
-<syntaxVersion component='pppoe-server' version='10'></syntaxVersion>
+<syntaxVersion component='pppoe-server' version='11'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/service_dhcpv6-server.xml.in b/interface-definitions/service_dhcpv6-server.xml.in
index daca7b43f..cf14388e8 100644
--- a/interface-definitions/service_dhcpv6-server.xml.in
+++ b/interface-definitions/service_dhcpv6-server.xml.in
@@ -63,27 +63,7 @@
</constraint>
</properties>
</leafNode>
- <node name="common-options">
- <properties>
- <help>Common options to distribute to all clients, including stateless clients</help>
- </properties>
- <children>
- <leafNode name="info-refresh-time">
- <properties>
- <help>Time (in seconds) that stateless clients should wait between refreshing the information they were given</help>
- <valueHelp>
- <format>u32:1-4294967295</format>
- <description>DHCPv6 information refresh time</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-4294967295"/>
- </constraint>
- </properties>
- </leafNode>
- #include <include/dhcp/domain-search.xml.i>
- #include <include/name-server-ipv6.xml.i>
- </children>
- </node>
+ #include <include/dhcp/option-v6.xml.i>
<tagNode name="subnet">
<properties>
<help>IPv6 DHCP subnet for this shared network</help>
diff --git a/interface-definitions/service_ipoe-server.xml.in b/interface-definitions/service_ipoe-server.xml.in
index c7542f0d0..25bc43cc6 100644
--- a/interface-definitions/service_ipoe-server.xml.in
+++ b/interface-definitions/service_ipoe-server.xml.in
@@ -175,6 +175,7 @@
</children>
</node>
#include <include/accel-ppp/vlan.xml.i>
+ #include <include/accel-ppp/vlan-mon.xml.i>
</children>
</tagNode>
#include <include/accel-ppp/client-ip-pool.xml.i>
diff --git a/interface-definitions/service_pppoe-server.xml.in b/interface-definitions/service_pppoe-server.xml.in
index 7cb1ec06e..93ec7ade9 100644
--- a/interface-definitions/service_pppoe-server.xml.in
+++ b/interface-definitions/service_pppoe-server.xml.in
@@ -64,6 +64,7 @@
</properties>
<children>
#include <include/accel-ppp/vlan.xml.i>
+ #include <include/accel-ppp/vlan-mon.xml.i>
</children>
</tagNode>
<leafNode name="service-name">
diff --git a/op-mode-definitions/execute.xml.in b/op-mode-definitions/execute.xml.in
new file mode 100644
index 000000000..66069c927
--- /dev/null
+++ b/op-mode-definitions/execute.xml.in
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="execute">
+ <properties>
+ <help>Initiate an operation</help>
+ </properties>
+ </node>
+</interfaceDefinition> \ No newline at end of file
diff --git a/python/vyos/config.py b/python/vyos/config.py
index b7ee606a9..1fab46761 100644
--- a/python/vyos/config.py
+++ b/python/vyos/config.py
@@ -344,6 +344,9 @@ class Config(object):
conf_dict['pki'] = pki_dict
+ interfaces_root = root_dict.get('interfaces', {})
+ setattr(conf_dict, 'interfaces_root', interfaces_root)
+
# save optional args for a call to get_config_defaults
setattr(conf_dict, '_dict_kwargs', kwargs)
diff --git a/python/vyos/configsource.py b/python/vyos/configsource.py
index f582bdfab..59e5ac8a1 100644
--- a/python/vyos/configsource.py
+++ b/python/vyos/configsource.py
@@ -204,6 +204,8 @@ class ConfigSourceSession(ConfigSource):
Returns:
True if called from a configuration session, False otherwise.
"""
+ if os.getenv('VYOS_CONFIGD', ''):
+ return False
try:
self._run(self._make_command('inSession', ''))
return True
diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py
index 4cb84194a..59b67300d 100644
--- a/python/vyos/configverify.py
+++ b/python/vyos/configverify.py
@@ -237,7 +237,7 @@ def verify_bridge_delete(config):
raise ConfigError(f'Interface "{interface}" cannot be deleted as it '
f'is a member of bridge "{bridge_name}"!')
-def verify_interface_exists(ifname, warning_only=False):
+def verify_interface_exists(config, ifname, state_required=False, warning_only=False):
"""
Common helper function used by interface implementations to perform
recurring validation if an interface actually exists. We first probe
@@ -245,15 +245,14 @@ def verify_interface_exists(ifname, warning_only=False):
it exists at the OS level.
"""
from vyos.base import Warning
- from vyos.configquery import ConfigTreeQuery
from vyos.utils.dict import dict_search_recursive
from vyos.utils.network import interface_exists
- # Check if interface is present in CLI config
- config = ConfigTreeQuery()
- tmp = config.get_config_dict(['interfaces'], get_first_key=True)
- if bool(list(dict_search_recursive(tmp, ifname))):
- return True
+ if not state_required:
+ # Check if interface is present in CLI config
+ tmp = getattr(config, 'interfaces_root', {})
+ if bool(list(dict_search_recursive(tmp, ifname))):
+ return True
# Interface not found on CLI, try Linux Kernel
if interface_exists(ifname):
diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py
index 25ee45391..dec619d3e 100644
--- a/python/vyos/defaults.py
+++ b/python/vyos/defaults.py
@@ -35,7 +35,8 @@ directories = {
'vyos_udev_dir' : '/run/udev/vyos',
'isc_dhclient_dir' : '/run/dhclient',
'dhcp6_client_dir' : '/run/dhcp6c',
- 'vyos_configdir' : '/opt/vyatta/config'
+ 'vyos_configdir' : '/opt/vyatta/config',
+ 'completion_dir' : f'{base_dir}/completion'
}
config_status = '/tmp/vyos-config-status'
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
index 3976a5580..f0cf3c924 100644..100755
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -167,10 +167,19 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name):
if address_mask:
operator = '!=' if exclude else '=='
operator = f'& {address_mask} {operator} '
- if is_ipv4(suffix):
- output.append(f'ip {prefix}addr {operator}{suffix}')
+
+ if suffix.find('-') != -1:
+ # Range
+ start, end = suffix.split('-')
+ if is_ipv4(start):
+ output.append(f'ip {prefix}addr {operator}{suffix}')
+ else:
+ output.append(f'ip6 {prefix}addr {operator}{suffix}')
else:
- output.append(f'ip6 {prefix}addr {operator}{suffix}')
+ if is_ipv4(suffix):
+ output.append(f'ip {prefix}addr {operator}{suffix}')
+ else:
+ output.append(f'ip6 {prefix}addr {operator}{suffix}')
if 'fqdn' in side_conf:
fqdn = side_conf['fqdn']
diff --git a/python/vyos/opmode.py b/python/vyos/opmode.py
index a6c64adfb..066c8058f 100644
--- a/python/vyos/opmode.py
+++ b/python/vyos/opmode.py
@@ -89,7 +89,7 @@ class InternalError(Error):
def _is_op_mode_function_name(name):
- if re.match(r"^(show|clear|reset|restart|add|update|delete|generate|set|renew|release)", name):
+ if re.match(r"^(show|clear|reset|restart|add|update|delete|generate|set|renew|release|execute)", name):
return True
else:
return False
diff --git a/python/vyos/template.py b/python/vyos/template.py
index 3507e0940..be9f781a6 100644..100755
--- a/python/vyos/template.py
+++ b/python/vyos/template.py
@@ -694,7 +694,8 @@ def conntrack_rule(rule_conf, rule_id, action, ipv6=False):
else:
for protocol, protocol_config in rule_conf['protocol'].items():
proto = protocol
- output.append(f'meta l4proto {proto}')
+ if proto != 'all':
+ output.append(f'meta l4proto {proto}')
tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags')
if tcp_flags and action != 'timeout':
@@ -922,8 +923,8 @@ def kea6_shared_network_json(shared_networks):
'subnet6': []
}
- if 'common_options' in config:
- network['option-data'] = kea6_parse_options(config['common_options'])
+ if 'option' in config:
+ network['option-data'] = kea6_parse_options(config['option'])
if 'interface' in config:
network['interface'] = config['interface']
diff --git a/python/vyos/utils/file.py b/python/vyos/utils/file.py
index c566f0334..eaebb57a3 100644
--- a/python/vyos/utils/file.py
+++ b/python/vyos/utils/file.py
@@ -51,7 +51,7 @@ def write_file(fname, data, defaultonfailure=None, user=None, group=None, mode=N
If directory of file is not present, it is auto-created.
"""
dirname = os.path.dirname(fname)
- if not os.path.isdir(dirname):
+ if dirname and not os.path.isdir(dirname):
os.makedirs(dirname, mode=0o755, exist_ok=False)
chown(dirname, user, group)
diff --git a/python/vyos/xml_ref/__init__.py b/python/vyos/xml_ref/__init__.py
index 2ba3da4e8..91ce394f7 100644
--- a/python/vyos/xml_ref/__init__.py
+++ b/python/vyos/xml_ref/__init__.py
@@ -15,6 +15,7 @@
from typing import Optional, Union, TYPE_CHECKING
from vyos.xml_ref import definition
+from vyos.xml_ref import op_definition
if TYPE_CHECKING:
from vyos.config import ConfigDict
@@ -87,3 +88,25 @@ def from_source(d: dict, path: list) -> bool:
def ext_dict_merge(source: dict, destination: Union[dict, 'ConfigDict']):
return definition.ext_dict_merge(source, destination)
+
+def load_op_reference(op_cache=[]):
+ if op_cache:
+ return op_cache[0]
+
+ op_xml = op_definition.OpXml()
+
+ try:
+ from vyos.xml_ref.op_cache import op_reference
+ except Exception:
+ raise ImportError('no xml op reference cache !!')
+
+ if not op_reference:
+ raise ValueError('empty xml op reference cache !!')
+
+ op_xml.define(op_reference)
+ op_cache.append(op_xml)
+
+ return op_xml
+
+def get_op_ref_path(path: list) -> list[op_definition.PathData]:
+ return load_op_reference()._get_op_ref_path(path)
diff --git a/python/vyos/xml_ref/generate_op_cache.py b/python/vyos/xml_ref/generate_op_cache.py
new file mode 100755
index 000000000..cd2ac890e
--- /dev/null
+++ b/python/vyos/xml_ref/generate_op_cache.py
@@ -0,0 +1,174 @@
+#!/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
+import sys
+import json
+import glob
+
+from argparse import ArgumentParser
+from os.path import join
+from os.path import abspath
+from os.path import dirname
+from xml.etree import ElementTree as ET
+from xml.etree.ElementTree import Element
+from typing import TypeAlias
+from typing import Optional
+
+_here = dirname(__file__)
+
+sys.path.append(join(_here, '..'))
+from defaults import directories
+
+from op_definition import NodeData
+from op_definition import PathData
+
+xml_op_cache_json = 'xml_op_cache.json'
+xml_op_tmp = join('/tmp', xml_op_cache_json)
+op_ref_cache = abspath(join(_here, 'op_cache.py'))
+
+OptElement: TypeAlias = Optional[Element]
+DEBUG = False
+
+
+def translate_exec(s: str) -> str:
+ s = s.replace('${vyos_op_scripts_dir}', directories['op_mode'])
+ s = s.replace('${vyos_libexec_dir}', directories['base'])
+ return s
+
+
+def translate_position(s: str, pos: list[str]) -> str:
+ pos = pos.copy()
+ pat: re.Pattern = re.compile(r'(?:\")?\${?([0-9]+)}?(?:\")?')
+ t: str = pat.sub(r'_place_holder_\1_', s)
+
+ # preferred to .format(*list) to avoid collisions with braces
+ for i, p in enumerate(pos):
+ t = t.replace(f'_place_holder_{i+1}_', p)
+
+ return t
+
+
+def translate_command(s: str, pos: list[str]) -> str:
+ s = translate_exec(s)
+ s = translate_position(s, pos)
+ return s
+
+
+def translate_op_script(s: str) -> str:
+ s = s.replace('${vyos_completion_dir}', directories['completion_dir'])
+ s = s.replace('${vyos_op_scripts_dir}', directories['op_mode'])
+ return s
+
+
+def insert_node(n: Element, l: list[PathData], path = None) -> None:
+ # pylint: disable=too-many-locals,too-many-branches
+ prop: OptElement = n.find('properties')
+ children: OptElement = n.find('children')
+ command: OptElement = n.find('command')
+ # name is not None as required by schema
+ name: str = n.get('name', 'schema_error')
+ node_type: str = n.tag
+ if path is None:
+ path = []
+
+ path.append(name)
+ if node_type == 'tagNode':
+ path.append(f'{name}-tag_value')
+
+ help_prop: OptElement = None if prop is None else prop.find('help')
+ help_text = None if help_prop is None else help_prop.text
+ command_text = None if command is None else command.text
+ if command_text is not None:
+ command_text = translate_command(command_text, path)
+
+ comp_help = None
+ if prop is not None:
+ che = prop.findall("completionHelp")
+ for c in che:
+ lists = c.findall("list")
+ paths = c.findall("path")
+ scripts = c.findall("script")
+
+ comp_help = {}
+ list_l = []
+ for i in lists:
+ list_l.append(i.text)
+ path_l = []
+ for i in paths:
+ path_str = re.sub(r'\s+', '/', i.text)
+ path_l.append(path_str)
+ script_l = []
+ for i in scripts:
+ script_str = translate_op_script(i.text)
+ script_l.append(script_str)
+
+ comp_help['list'] = list_l
+ comp_help['fs_path'] = path_l
+ comp_help['script'] = script_l
+
+ for d in l:
+ if name in list(d):
+ break
+ else:
+ d = {}
+ l.append(d)
+
+ inner_l = d.setdefault(name, [])
+
+ inner_d: PathData = {'node_data': NodeData(node_type=node_type,
+ help_text=help_text,
+ comp_help=comp_help,
+ command=command_text,
+ path=path)}
+ inner_l.append(inner_d)
+
+ if children is not None:
+ inner_nodes = children.iterfind("*")
+ for inner_n in inner_nodes:
+ inner_path = path[:]
+ insert_node(inner_n, inner_l, inner_path)
+
+
+def parse_file(file_path, l):
+ tree = ET.parse(file_path)
+ root = tree.getroot()
+ for n in root.iterfind("*"):
+ insert_node(n, l)
+
+
+def main():
+ parser = ArgumentParser(description='generate dict from xml defintions')
+ parser.add_argument('--xml-dir', type=str, required=True,
+ help='transcluded xml op-mode-definition file')
+
+ args = vars(parser.parse_args())
+
+ xml_dir = abspath(args['xml_dir'])
+
+ l = []
+
+ for fname in glob.glob(f'{xml_dir}/*.xml'):
+ parse_file(fname, l)
+
+ with open(xml_op_tmp, 'w') as f:
+ json.dump(l, f, indent=2)
+
+ with open(op_ref_cache, 'w') as f:
+ f.write(f'op_reference = {str(l)}')
+
+if __name__ == '__main__':
+ main()
diff --git a/python/vyos/xml_ref/op_definition.py b/python/vyos/xml_ref/op_definition.py
new file mode 100644
index 000000000..914f3a105
--- /dev/null
+++ b/python/vyos/xml_ref/op_definition.py
@@ -0,0 +1,49 @@
+# Copyright 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
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+from typing import TypedDict
+from typing import TypeAlias
+from typing import Optional
+from typing import Union
+
+
+class NodeData(TypedDict):
+ node_type: Optional[str]
+ help_text: Optional[str]
+ comp_help: Optional[dict[str, list]]
+ command: Optional[str]
+ path: Optional[list[str]]
+
+
+PathData: TypeAlias = dict[str, Union[NodeData|list['PathData']]]
+
+
+class OpXml:
+ def __init__(self):
+ self.op_ref = {}
+
+ def define(self, op_ref: list[PathData]) -> None:
+ self.op_ref = op_ref
+
+ def _get_op_ref_path(self, path: list[str]) -> list[PathData]:
+ def _get_path_list(path: list[str], l: list[PathData]) -> list[PathData]:
+ if not path:
+ return l
+ for d in l:
+ if path[0] in list(d):
+ return _get_path_list(path[1:], d[path[0]])
+ return []
+ l = self.op_ref
+ return _get_path_list(path, l)
diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
index dfc816a42..b8031eed0 100755
--- a/smoketest/scripts/cli/test_firewall.py
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -280,7 +280,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
['chain NAME_smoketest'],
['saddr 172.16.20.10', 'daddr 172.16.10.10', 'log prefix "[ipv4-NAM-smoketest-1-A]" log level debug', 'ip ttl 15', 'accept'],
['tcp flags syn / syn,ack', 'tcp dport 8888', 'log prefix "[ipv4-NAM-smoketest-2-R]" log level err', 'ip ttl > 102', 'reject'],
- ['log prefix "[ipv4-smoketest-default-D]"','smoketest default-action', 'drop']
+ ['log prefix "[ipv4-NAM-smoketest-default-D]"','smoketest default-action', 'drop']
]
self.verify_nftables(nftables_search, 'ip vyos_filter')
@@ -311,7 +311,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '7', 'dscp-exclude', '21-25'])
self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'default-action', 'drop'])
- self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'source', 'address', '198.51.100.1'])
+ self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'source', 'address', '198.51.100.1-198.51.100.50'])
self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'mark', '1010'])
self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'action', 'jump'])
self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'jump-target', name])
@@ -331,7 +331,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
nftables_search = [
['chain VYOS_FORWARD_filter'],
['type filter hook forward priority filter; policy accept;'],
- ['ip saddr 198.51.100.1', 'meta mark 0x000003f2', f'jump NAME_{name}'],
+ ['ip saddr 198.51.100.1-198.51.100.50', 'meta mark 0x000003f2', f'jump NAME_{name}'],
['FWD-filter default-action drop', 'drop'],
['chain VYOS_INPUT_filter'],
['type filter hook input priority filter; policy accept;'],
@@ -341,7 +341,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
[f'chain NAME_{name}'],
['ip length { 64, 512, 1024 }', 'ip dscp { 0x11, 0x34 }', f'log prefix "[ipv4-NAM-{name}-6-A]" log group 66 snaplen 6666 queue-threshold 32000', 'accept'],
['ip length 1-30000', 'ip length != 60000-65535', 'ip dscp 0x03-0x0b', 'ip dscp != 0x15-0x19', 'accept'],
- [f'log prefix "[ipv4-{name}-default-D]"', 'drop']
+ [f'log prefix "[ipv4-NAM-{name}-default-D]"', 'drop']
]
self.verify_nftables(nftables_search, 'ip vyos_filter')
@@ -455,7 +455,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_set(['firewall', 'ipv6', 'name', name, 'default-log'])
self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'action', 'accept'])
- self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'source', 'address', '2002::1'])
+ self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'source', 'address', '2002::1-2002::10'])
self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'destination', 'address', '2002::1:1'])
self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'log'])
self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'log-options', 'level', 'crit'])
@@ -510,8 +510,8 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
['tcp dport 23', 'drop'],
['PRE-raw default-action accept', 'accept'],
[f'chain NAME6_{name}'],
- ['saddr 2002::1', 'daddr 2002::1:1', 'log prefix "[ipv6-NAM-v6-smoketest-1-A]" log level crit', 'accept'],
- [f'"{name} default-action drop"', f'log prefix "[ipv6-{name}-default-D]"', 'drop'],
+ ['saddr 2002::1-2002::10', 'daddr 2002::1:1', 'log prefix "[ipv6-NAM-v6-smoketest-1-A]" log level crit', 'accept'],
+ [f'"NAM-{name} default-action drop"', f'log prefix "[ipv6-NAM-{name}-default-D]"', 'drop'],
['jump VYOS_STATE_POLICY6'],
['chain VYOS_STATE_POLICY6'],
['ct state established', 'accept'],
@@ -522,9 +522,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.verify_nftables(nftables_search, 'ip6 vyos_filter')
def test_ipv6_advanced(self):
- name = 'v6-smoketest-adv'
- name2 = 'v6-smoketest-adv2'
- interface = 'eth0'
+ name = 'v6-smoke-adv'
self.cli_set(['firewall', 'ipv6', 'name', name, 'default-action', 'drop'])
self.cli_set(['firewall', 'ipv6', 'name', name, 'default-log'])
@@ -559,7 +557,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
['ip6 saddr 2001:db8::/64', 'meta mark != 0x000019ff-0x00001e56', f'jump NAME6_{name}'],
[f'chain NAME6_{name}'],
['ip6 length { 65, 513, 1025 }', 'ip6 dscp { af21, 0x35 }', 'accept'],
- [f'log prefix "[ipv6-{name}-default-D]"', 'drop']
+ [f'log prefix "[ipv6-NAM-{name}-default-D]"', 'drop']
]
self.verify_nftables(nftables_search, 'ip6 vyos_filter')
@@ -686,7 +684,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
['ct state new', 'ct status dnat', 'accept'],
['ct state { established, new }', 'ct status snat', 'accept'],
['ct state related', 'ct helper { "ftp", "pptp" }', 'accept'],
- ['drop', f'comment "{name} default-action drop"']
+ ['drop', f'comment "NAM-{name} default-action drop"']
]
self.verify_nftables(nftables_search, 'ip vyos_filter')
diff --git a/smoketest/scripts/cli/test_service_ipoe-server.py b/smoketest/scripts/cli/test_service_ipoe-server.py
index 5f1cf9ad1..be03179bf 100755
--- a/smoketest/scripts/cli/test_service_ipoe-server.py
+++ b/smoketest/scripts/cli/test_service_ipoe-server.py
@@ -21,6 +21,7 @@ from collections import OrderedDict
from base_accel_ppp_test import BasicAccelPPPTest
from vyos.configsession import ConfigSessionError
from vyos.utils.process import cmd
+from vyos.template import range_to_regex
from configparser import ConfigParser
from configparser import RawConfigParser
@@ -228,6 +229,37 @@ delegate={delegate_1_prefix},{delegate_mask},name={pool_name}
delegate={delegate_2_prefix},{delegate_mask},name={pool_name}"""
self.assertIn(pool_config, config)
+ def test_ipoe_server_vlan(self):
+ vlans = ['100', '200', '300-310']
+
+ # Test configuration of local authentication for PPPoE server
+ self.basic_config()
+ # cannot use "client-subnet" option with "vlan" option
+ # have to delete it
+ self.delete(['interface', interface, 'client-subnet'])
+ self.cli_commit()
+
+ self.set(['interface', interface, 'vlan-mon'])
+
+ # cannot use option "vlan-mon" if no "vlan" set
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ for vlan in vlans:
+ self.set(['interface', interface, 'vlan', vlan])
+
+ # commit changes
+ self.cli_commit()
+
+ # Validate configuration values
+ conf = ConfigParser(allow_no_value=True, delimiters='=', strict=False)
+ conf.read(self._config_file)
+ tmp = range_to_regex(vlans)
+ self.assertIn(f're:^{interface}\.{tmp}$', conf['ipoe']['interface'])
+
+ tmp = ','.join(vlans)
+ self.assertIn(f'{interface},{tmp}', conf['ipoe']['vlan-mon'])
+
@unittest.skip("PPP is not a part of IPoE")
def test_accel_ppp_options(self):
pass
diff --git a/smoketest/scripts/cli/test_service_pppoe-server.py b/smoketest/scripts/cli/test_service_pppoe-server.py
index 34e45a81a..8add5ee6c 100755
--- a/smoketest/scripts/cli/test_service_pppoe-server.py
+++ b/smoketest/scripts/cli/test_service_pppoe-server.py
@@ -21,6 +21,7 @@ from base_accel_ppp_test import BasicAccelPPPTest
from configparser import ConfigParser
from vyos.utils.file import read_file
from vyos.template import range_to_regex
+from vyos.configsession import ConfigSessionError
local_if = ['interfaces', 'dummy', 'dum667']
ac_name = 'ACN'
@@ -133,6 +134,12 @@ class TestServicePPPoEServer(BasicAccelPPPTest.TestCase):
# Test configuration of local authentication for PPPoE server
self.basic_config()
+ self.set(['interface', interface, 'vlan-mon'])
+
+ # cannot use option "vlan-mon" if no "vlan" set
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
for vlan in vlans:
self.set(['interface', interface, 'vlan', vlan])
diff --git a/smoketest/scripts/cli/test_system_conntrack.py b/smoketest/scripts/cli/test_system_conntrack.py
index c07fdce77..72deb7525 100755
--- a/smoketest/scripts/cli/test_system_conntrack.py
+++ b/smoketest/scripts/cli/test_system_conntrack.py
@@ -209,6 +209,7 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '2', 'source', 'address', '192.0.2.1'])
self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '2', 'destination', 'group', 'address-group', address_group])
+ self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '2', 'protocol', 'all'])
self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '11', 'source', 'address', 'fe80::1'])
self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '11', 'destination', 'address', 'fe80::2'])
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
index b71ce7124..5638a9668 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -402,7 +402,7 @@ def verify(firewall):
raise ConfigError(f'Flowtable "{flowtable}" requires at least one interface')
for ifname in flowtable_conf['interface']:
- verify_interface_exists(ifname)
+ verify_interface_exists(firewall, ifname)
if dict_search_args(flowtable_conf, 'offload') == 'hardware':
interfaces = flowtable_conf['interface']
diff --git a/src/conf_mode/interfaces_ethernet.py b/src/conf_mode/interfaces_ethernet.py
index 54d0669cb..afc48ead8 100755
--- a/src/conf_mode/interfaces_ethernet.py
+++ b/src/conf_mode/interfaces_ethernet.py
@@ -310,7 +310,7 @@ def verify_bond_member(ethernet):
:type ethernet: dict
"""
ifname = ethernet['ifname']
- verify_interface_exists(ifname)
+ verify_interface_exists(ethernet, ifname)
verify_eapol(ethernet)
verify_mirror_redirect(ethernet)
ethtool = Ethtool(ifname)
@@ -327,7 +327,7 @@ def verify_ethernet(ethernet):
:type ethernet: dict
"""
ifname = ethernet['ifname']
- verify_interface_exists(ifname)
+ verify_interface_exists(ethernet, ifname)
verify_mtu(ethernet)
verify_mtu_ipv6(ethernet)
verify_dhcpv6(ethernet)
diff --git a/src/conf_mode/interfaces_wwan.py b/src/conf_mode/interfaces_wwan.py
index 2515dc838..230eb14d6 100755
--- a/src/conf_mode/interfaces_wwan.py
+++ b/src/conf_mode/interfaces_wwan.py
@@ -95,7 +95,7 @@ def verify(wwan):
if not 'apn' in wwan:
raise ConfigError(f'No APN configured for "{ifname}"!')
- verify_interface_exists(ifname)
+ verify_interface_exists(wwan, ifname)
verify_authentication(wwan)
verify_vrf(wwan)
verify_mirror_redirect(wwan)
diff --git a/src/conf_mode/policy_local-route.py b/src/conf_mode/policy_local-route.py
index f458f4e82..331fd972d 100755
--- a/src/conf_mode/policy_local-route.py
+++ b/src/conf_mode/policy_local-route.py
@@ -223,7 +223,7 @@ def verify(pbr):
if 'inbound_interface' in pbr_route['rule'][rule]:
interface = pbr_route['rule'][rule]['inbound_interface']
- verify_interface_exists(interface)
+ verify_interface_exists(pbr, interface)
return None
diff --git a/src/conf_mode/protocols_igmp-proxy.py b/src/conf_mode/protocols_igmp-proxy.py
index afcef0985..9a07adf05 100755
--- a/src/conf_mode/protocols_igmp-proxy.py
+++ b/src/conf_mode/protocols_igmp-proxy.py
@@ -65,7 +65,7 @@ def verify(igmp_proxy):
upstream = 0
for interface, config in igmp_proxy['interface'].items():
- verify_interface_exists(interface)
+ verify_interface_exists(igmp_proxy, interface)
if dict_search('role', config) == 'upstream':
upstream += 1
diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py
index 9cadfd081..ba2f3cf0d 100755
--- a/src/conf_mode/protocols_isis.py
+++ b/src/conf_mode/protocols_isis.py
@@ -102,7 +102,7 @@ def verify(isis):
raise ConfigError('Interface used for routing updates is mandatory!')
for interface in isis['interface']:
- verify_interface_exists(interface)
+ verify_interface_exists(isis, interface)
# Interface MTU must be >= configured lsp-mtu
mtu = Interface(interface).get_mtu()
area_mtu = isis['lsp_mtu']
diff --git a/src/conf_mode/protocols_mpls.py b/src/conf_mode/protocols_mpls.py
index 177a43444..ad164db9f 100755
--- a/src/conf_mode/protocols_mpls.py
+++ b/src/conf_mode/protocols_mpls.py
@@ -49,7 +49,7 @@ def verify(mpls):
if 'interface' in mpls:
for interface in mpls['interface']:
- verify_interface_exists(interface)
+ verify_interface_exists(mpls, interface)
# Checks to see if LDP is properly configured
if 'ldp' in mpls:
diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py
index 6fffe7e0d..7347c4faa 100755
--- a/src/conf_mode/protocols_ospf.py
+++ b/src/conf_mode/protocols_ospf.py
@@ -144,7 +144,7 @@ def verify(ospf):
if 'interface' in ospf:
for interface, interface_config in ospf['interface'].items():
- verify_interface_exists(interface)
+ verify_interface_exists(ospf, interface)
# One can not use dead-interval and hello-multiplier at the same
# time. FRR will only activate the last option set via CLI.
if {'hello_multiplier', 'dead_interval'} <= set(interface_config):
diff --git a/src/conf_mode/protocols_ospfv3.py b/src/conf_mode/protocols_ospfv3.py
index 1bb172293..60c2a9b16 100755
--- a/src/conf_mode/protocols_ospfv3.py
+++ b/src/conf_mode/protocols_ospfv3.py
@@ -127,7 +127,7 @@ def verify(ospfv3):
if 'interface' in ospfv3:
for interface, interface_config in ospfv3['interface'].items():
- verify_interface_exists(interface)
+ verify_interface_exists(ospfv3, interface)
if 'ifmtu' in interface_config:
mtu = Interface(interface).get_mtu()
if int(interface_config['ifmtu']) > int(mtu):
diff --git a/src/conf_mode/protocols_pim.py b/src/conf_mode/protocols_pim.py
index d450d11ca..79294a1f0 100755
--- a/src/conf_mode/protocols_pim.py
+++ b/src/conf_mode/protocols_pim.py
@@ -97,7 +97,7 @@ def verify(pim):
raise ConfigError('PIM require defined interfaces!')
for interface, interface_config in pim['interface'].items():
- verify_interface_exists(interface)
+ verify_interface_exists(pim, interface)
# Check join group in reserved net
if 'igmp' in interface_config and 'join' in interface_config['igmp']:
diff --git a/src/conf_mode/protocols_pim6.py b/src/conf_mode/protocols_pim6.py
index 2003a1014..581ffe238 100755
--- a/src/conf_mode/protocols_pim6.py
+++ b/src/conf_mode/protocols_pim6.py
@@ -63,7 +63,7 @@ def verify(pim6):
return
for interface, interface_config in pim6.get('interface', {}).items():
- verify_interface_exists(interface)
+ verify_interface_exists(pim6, interface)
if 'mld' in interface_config:
mld = interface_config['mld']
for group in mld.get('join', {}).keys():
diff --git a/src/conf_mode/qos.py b/src/conf_mode/qos.py
index 45248fb4a..7dfad3180 100755
--- a/src/conf_mode/qos.py
+++ b/src/conf_mode/qos.py
@@ -303,7 +303,7 @@ def apply(qos):
return None
for interface, interface_config in qos['interface'].items():
- if not verify_interface_exists(interface, warning_only=True):
+ if not verify_interface_exists(qos, interface, state_required=True, warning_only=True):
# When shaper is bound to a dialup (e.g. PPPoE) interface it is
# possible that it is yet not availbale when to QoS code runs.
# Skip the configuration and inform the user via warning_only=True
diff --git a/src/conf_mode/service_broadcast-relay.py b/src/conf_mode/service_broadcast-relay.py
index 31c552f5a..d35954718 100755
--- a/src/conf_mode/service_broadcast-relay.py
+++ b/src/conf_mode/service_broadcast-relay.py
@@ -59,7 +59,7 @@ def verify(relay):
raise ConfigError('At least two interfaces are required for UDP broadcast relay "{instance}"')
for interface in config.get('interface', []):
- verify_interface_exists(interface)
+ verify_interface_exists(relay, interface)
if not is_afi_configured(interface, AF_INET):
raise ConfigError(f'Interface "{interface}" has no IPv4 address configured!')
diff --git a/src/conf_mode/service_conntrack-sync.py b/src/conf_mode/service_conntrack-sync.py
index 4fb2ce27f..3a233a172 100755
--- a/src/conf_mode/service_conntrack-sync.py
+++ b/src/conf_mode/service_conntrack-sync.py
@@ -67,7 +67,7 @@ def verify(conntrack):
has_peer = False
for interface, interface_config in conntrack['interface'].items():
- verify_interface_exists(interface)
+ verify_interface_exists(conntrack, interface)
# Interface must not only exist, it must also carry an IP address
if len(get_ipv4(interface)) < 1:
raise ConfigError(f'Interface {interface} requires an IP address!')
diff --git a/src/conf_mode/service_dns_dynamic.py b/src/conf_mode/service_dns_dynamic.py
index a551a9891..5f5303856 100755
--- a/src/conf_mode/service_dns_dynamic.py
+++ b/src/conf_mode/service_dns_dynamic.py
@@ -104,7 +104,7 @@ def verify(dyndns):
Warning(f'Interface "{config["address"]["interface"]}" does not exist yet and '
f'cannot be used for Dynamic DNS service "{service}" until it is up!')
else:
- verify_interface_exists(config['address']['interface'])
+ verify_interface_exists(dyndns, config['address']['interface'])
if 'web' in config['address']:
# If 'skip' is specified, 'url' is required as well
diff --git a/src/conf_mode/service_ipoe-server.py b/src/conf_mode/service_ipoe-server.py
index 28b7fb03c..c7e3ef033 100755
--- a/src/conf_mode/service_ipoe-server.py
+++ b/src/conf_mode/service_ipoe-server.py
@@ -66,10 +66,12 @@ def verify(ipoe):
raise ConfigError('No IPoE interface configured')
for interface, iface_config in ipoe['interface'].items():
- verify_interface_exists(interface, warning_only=True)
+ verify_interface_exists(ipoe, interface, warning_only=True)
if 'client_subnet' in iface_config and 'vlan' in iface_config:
raise ConfigError('Option "client-subnet" and "vlan" are mutually exclusive, '
'use "client-ip-pool" instead!')
+ if 'vlan_mon' in iface_config and not 'vlan' in iface_config:
+ raise ConfigError('Option "vlan-mon" requires "vlan" to be set!')
verify_accel_ppp_authentication(ipoe, local_users=False)
verify_accel_ppp_ip_pool(ipoe)
diff --git a/src/conf_mode/service_mdns_repeater.py b/src/conf_mode/service_mdns_repeater.py
index 207da5e03..b0ece031c 100755
--- a/src/conf_mode/service_mdns_repeater.py
+++ b/src/conf_mode/service_mdns_repeater.py
@@ -65,7 +65,7 @@ def verify(mdns):
# For mdns-repeater to work it is essential that the interfaces has
# an IPv4 address assigned
for interface in mdns['interface']:
- verify_interface_exists(interface)
+ verify_interface_exists(mdns, interface)
if mdns['ip_version'] in ['ipv4', 'both'] and AF_INET not in ifaddresses(interface):
raise ConfigError('mDNS repeater requires an IPv4 address to be '
diff --git a/src/conf_mode/service_ndp-proxy.py b/src/conf_mode/service_ndp-proxy.py
index aa2374f4c..024ad79f2 100755
--- a/src/conf_mode/service_ndp-proxy.py
+++ b/src/conf_mode/service_ndp-proxy.py
@@ -50,7 +50,7 @@ def verify(ndpp):
if 'interface' in ndpp:
for interface, interface_config in ndpp['interface'].items():
- verify_interface_exists(interface)
+ verify_interface_exists(ndpp, interface)
if 'rule' in interface_config:
for rule, rule_config in interface_config['rule'].items():
diff --git a/src/conf_mode/service_ntp.py b/src/conf_mode/service_ntp.py
index f11690ee6..83880fd72 100755
--- a/src/conf_mode/service_ntp.py
+++ b/src/conf_mode/service_ntp.py
@@ -64,7 +64,7 @@ def verify(ntp):
if 'interface' in ntp:
# If ntpd should listen on a given interface, ensure it exists
interface = ntp['interface']
- verify_interface_exists(interface)
+ verify_interface_exists(ntp, interface)
# If we run in a VRF, our interface must belong to this VRF, too
if 'vrf' in ntp:
diff --git a/src/conf_mode/service_pppoe-server.py b/src/conf_mode/service_pppoe-server.py
index c95f976d3..ac697c509 100755
--- a/src/conf_mode/service_pppoe-server.py
+++ b/src/conf_mode/service_pppoe-server.py
@@ -121,8 +121,11 @@ def verify(pppoe):
raise ConfigError('At least one listen interface must be defined!')
# Check is interface exists in the system
- for interface in pppoe['interface']:
- verify_interface_exists(interface, warning_only=True)
+ for interface, interface_config in pppoe['interface'].items():
+ verify_interface_exists(pppoe, interface, warning_only=True)
+
+ if 'vlan_mon' in interface_config and not 'vlan' in interface_config:
+ raise ConfigError('Option "vlan-mon" requires "vlan" to be set!')
return None
diff --git a/src/conf_mode/service_salt-minion.py b/src/conf_mode/service_salt-minion.py
index a8fce8e01..edf74b0c0 100755
--- a/src/conf_mode/service_salt-minion.py
+++ b/src/conf_mode/service_salt-minion.py
@@ -70,7 +70,7 @@ def verify(salt):
Warning('Do not use sha1 hashing algorithm, upgrade to sha256 or later!')
if 'source_interface' in salt:
- verify_interface_exists(salt['source_interface'])
+ verify_interface_exists(salt, salt['source_interface'])
return None
diff --git a/src/conf_mode/service_suricata.py b/src/conf_mode/service_suricata.py
index 69b369e0b..1ce170145 100755
--- a/src/conf_mode/service_suricata.py
+++ b/src/conf_mode/service_suricata.py
@@ -59,7 +59,7 @@ def topological_sort(source):
temporary_marks.add(n)
for m in v.get('group', []):
- m = m.lstrip('!')
+ m = m.lstrip('!').replace('-', '_')
if m not in source:
raise ConfigError(f'Undefined referenced group "{m}"')
visit(m, source[m])
diff --git a/src/conf_mode/system_flow-accounting.py b/src/conf_mode/system_flow-accounting.py
index 2dacd92da..a12ee363d 100755
--- a/src/conf_mode/system_flow-accounting.py
+++ b/src/conf_mode/system_flow-accounting.py
@@ -183,7 +183,7 @@ def verify(flow_config):
# check that all configured interfaces exists in the system
for interface in flow_config['interface']:
- verify_interface_exists(interface, warning_only=True)
+ verify_interface_exists(flow_config, interface, warning_only=True)
# check sFlow configuration
if 'sflow' in flow_config:
diff --git a/src/conf_mode/system_option.py b/src/conf_mode/system_option.py
index 402510492..d1647e3a1 100755
--- a/src/conf_mode/system_option.py
+++ b/src/conf_mode/system_option.py
@@ -68,7 +68,7 @@ def verify(options):
if 'http_client' in options:
config = options['http_client']
if 'source_interface' in config:
- verify_interface_exists(config['source_interface'])
+ verify_interface_exists(options, config['source_interface'])
if {'source_address', 'source_interface'} <= set(config):
raise ConfigError('Can not define both HTTP source-interface and source-address')
diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py
index b3e05a814..ca0c3657f 100755
--- a/src/conf_mode/vpn_ipsec.py
+++ b/src/conf_mode/vpn_ipsec.py
@@ -210,9 +210,9 @@ def verify(ipsec):
for interface in ipsec['interface']:
# exclude check interface for dynamic interfaces
if tmp.match(interface):
- verify_interface_exists(interface, warning_only=True)
+ verify_interface_exists(ipsec, interface, warning_only=True)
else:
- verify_interface_exists(interface)
+ verify_interface_exists(ipsec, interface)
if 'l2tp' in ipsec:
if 'esp_group' in ipsec['l2tp']:
@@ -273,7 +273,7 @@ def verify(ipsec):
if 'dhcp_interface' in ra_conf:
dhcp_interface = ra_conf['dhcp_interface']
- verify_interface_exists(dhcp_interface)
+ verify_interface_exists(ipsec, dhcp_interface)
dhcp_base = directories['isc_dhclient_dir']
if not os.path.exists(f'{dhcp_base}/dhclient_{dhcp_interface}.conf'):
@@ -502,7 +502,7 @@ def verify(ipsec):
if 'dhcp_interface' in peer_conf:
dhcp_interface = peer_conf['dhcp_interface']
- verify_interface_exists(dhcp_interface)
+ verify_interface_exists(ipsec, dhcp_interface)
dhcp_base = directories['isc_dhclient_dir']
if not os.path.exists(f'{dhcp_base}/dhclient_{dhcp_interface}.conf'):
diff --git a/src/migration-scripts/dhcpv6-server/5-to-6 b/src/migration-scripts/dhcpv6-server/5-to-6
new file mode 100644
index 000000000..cad0a3538
--- /dev/null
+++ b/src/migration-scripts/dhcpv6-server/5-to-6
@@ -0,0 +1,31 @@
+# Copyright 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
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+# T6648: Rename "common-options" to "option" at shared-network level
+
+from vyos.configtree import ConfigTree
+
+base = ['service', 'dhcpv6-server', 'shared-network-name']
+
+def migrate(config: ConfigTree) -> None:
+ if not config.exists(base):
+ # Nothing to do
+ return
+
+ for network in config.list_nodes(base):
+ if not config.exists(base + [network, 'common-options']):
+ continue
+
+ config.rename(base + [network, 'common-options'], 'option')
diff --git a/src/migration-scripts/ipoe-server/3-to-4 b/src/migration-scripts/ipoe-server/3-to-4
new file mode 100644
index 000000000..3bad9756d
--- /dev/null
+++ b/src/migration-scripts/ipoe-server/3-to-4
@@ -0,0 +1,30 @@
+# Copyright 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
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+# Add the "vlan-mon" option to the configuration to prevent it
+# from disappearing from the configuration file
+
+from vyos.configtree import ConfigTree
+
+base = ['service', 'ipoe-server']
+
+def migrate(config: ConfigTree) -> None:
+ if not config.exists(base):
+ return
+
+ for interface in config.list_nodes(base + ['interface']):
+ base_path = base + ['interface', interface]
+ if config.exists(base_path + ['vlan']):
+ config.set(base_path + ['vlan-mon'])
diff --git a/src/migration-scripts/pppoe-server/10-to-11 b/src/migration-scripts/pppoe-server/10-to-11
new file mode 100644
index 000000000..6bc138b5c
--- /dev/null
+++ b/src/migration-scripts/pppoe-server/10-to-11
@@ -0,0 +1,30 @@
+# Copyright 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
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+# Add the "vlan-mon" option to the configuration to prevent it
+# from disappearing from the configuration file
+
+from vyos.configtree import ConfigTree
+
+base = ['service', 'pppoe-server']
+
+def migrate(config: ConfigTree) -> None:
+ if not config.exists(base):
+ return
+
+ for interface in config.list_nodes(base + ['interface']):
+ base_path = base + ['interface', interface]
+ if config.exists(base_path + ['vlan']):
+ config.set(base_path + ['vlan-mon'])
diff --git a/src/op_mode/pki.py b/src/op_mode/pki.py
index b1a42d6c3..ab613e5c4 100755
--- a/src/op_mode/pki.py
+++ b/src/op_mode/pki.py
@@ -316,7 +316,13 @@ def generate_certificate_request(private_key=None, key_type=None, return_request
default_values = get_default_values()
subject = {}
- subject['country'] = ask_input('Enter country code:', default=default_values['country'])
+ while True:
+ country = ask_input('Enter country code:', default=default_values['country'])
+ if len(country) != 2:
+ print("Country name must be a 2 character country code")
+ continue
+ subject['country'] = country
+ break
subject['state'] = ask_input('Enter state:', default=default_values['state'])
subject['locality'] = ask_input('Enter locality:', default=default_values['locality'])
subject['organization'] = ask_input('Enter organization name:', default=default_values['organization'])
diff --git a/src/opt/vyatta/etc/shell/level/users/allowed-op b/src/opt/vyatta/etc/shell/level/users/allowed-op
index 74c45af37..381fd26e5 100644
--- a/src/opt/vyatta/etc/shell/level/users/allowed-op
+++ b/src/opt/vyatta/etc/shell/level/users/allowed-op
@@ -6,6 +6,7 @@ clear
connect
delete
disconnect
+execute
exit
force
monitor
diff --git a/src/opt/vyatta/etc/shell/level/users/allowed-op.in b/src/opt/vyatta/etc/shell/level/users/allowed-op.in
index 1976904e4..9752f99a2 100644
--- a/src/opt/vyatta/etc/shell/level/users/allowed-op.in
+++ b/src/opt/vyatta/etc/shell/level/users/allowed-op.in
@@ -2,6 +2,7 @@ clear
connect
delete
disconnect
+execute
exit
force
monitor
diff --git a/src/services/vyos-configd b/src/services/vyos-configd
index a4b839a7f..d797e90cf 100755
--- a/src/services/vyos-configd
+++ b/src/services/vyos-configd
@@ -182,6 +182,12 @@ def initialization(socket):
sudo_user_string = socket.recv().decode("utf-8", "ignore")
resp = "sudo_user"
socket.send(resp.encode())
+ temp_config_dir_string = socket.recv().decode("utf-8", "ignore")
+ resp = "temp_config_dir"
+ socket.send(resp.encode())
+ changes_only_dir_string = socket.recv().decode("utf-8", "ignore")
+ resp = "changes_only_dir"
+ socket.send(resp.encode())
logger.debug(f"config session pid is {pid_string}")
logger.debug(f"config session sudo_user is {sudo_user_string}")
@@ -198,6 +204,10 @@ def initialization(socket):
session_mode = 'a'
os.environ['SUDO_USER'] = sudo_user_string
+ if temp_config_dir_string:
+ os.environ['VYATTA_TEMP_CONFIG_DIR'] = temp_config_dir_string
+ if changes_only_dir_string:
+ os.environ['VYATTA_CHANGES_ONLY_DIR'] = changes_only_dir_string
try:
configsource = ConfigSourceString(running_config_text=active_string,
@@ -267,6 +277,8 @@ if __name__ == '__main__':
cfg_group = grp.getgrnam(CFG_GROUP)
os.setgid(cfg_group.gr_gid)
+ os.environ['VYOS_CONFIGD'] = 't'
+
def sig_handler(signum, frame):
shutdown()
diff --git a/src/shim/vyshim.c b/src/shim/vyshim.c
index 4d836127d..a78f62a7b 100644
--- a/src/shim/vyshim.c
+++ b/src/shim/vyshim.c
@@ -185,6 +185,20 @@ int initialization(void* Requester)
}
debug_print("sudo_user is %s\n", sudo_user);
+ char *temp_config_dir = getenv("VYATTA_TEMP_CONFIG_DIR");
+ if (!temp_config_dir) {
+ char none[] = "";
+ temp_config_dir = none;
+ }
+ debug_print("temp_config_dir is %s\n", temp_config_dir);
+
+ char *changes_only_dir = getenv("VYATTA_CHANGES_ONLY_DIR");
+ if (!changes_only_dir) {
+ char none[] = "";
+ changes_only_dir = none;
+ }
+ debug_print("changes_only_dir is %s\n", changes_only_dir);
+
debug_print("Sending init announcement\n");
char *init_announce = mkjson(MKJSON_OBJ, 1,
MKJSON_STRING, "type", "init");
@@ -252,6 +266,16 @@ int initialization(void* Requester)
zmq_recv(Requester, buffer, 16, 0);
debug_print("Received sudo_user receipt\n");
+ debug_print("Sending config session temp_config_dir\n");
+ zmq_send(Requester, temp_config_dir, strlen(temp_config_dir), 0);
+ zmq_recv(Requester, buffer, 16, 0);
+ debug_print("Received temp_config_dir receipt\n");
+
+ debug_print("Sending config session changes_only_dir\n");
+ zmq_send(Requester, changes_only_dir, strlen(changes_only_dir), 0);
+ zmq_recv(Requester, buffer, 16, 0);
+ debug_print("Received changes_only_dir receipt\n");
+
return 0;
}