diff options
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 @@ -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; } |