From 14750f65db2d8e11e738075db2328ef855d06b1d Mon Sep 17 00:00:00 2001 From: jack9603301 Date: Tue, 23 Mar 2021 14:40:30 +0800 Subject: upnpd: T3420: Add miniupnpd-nftables package --- debian/control | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 08c04b439..6575c1db8 100644 --- a/debian/control +++ b/debian/control @@ -168,7 +168,9 @@ Depends: wide-dhcpv6-client, wireguard-tools, wireless-regdb, - wpasupplicant (>= 0.6.7) + wpasupplicant (>= 0.6.7), + ndppd, + miniupnpd-nftables Description: VyOS configuration scripts and data VyOS configuration scripts, interface definitions, and everything -- cgit v1.2.3 From 600d0c76750a0430ae5594ea89a0340c7f0d9785 Mon Sep 17 00:00:00 2001 From: jack9603301 Date: Thu, 25 Mar 2021 19:11:46 +0800 Subject: upnpd: T3420: Add the UPnP command line --- interface-definitions/service_upnp.xml.in | 246 ++++++++++++++++++++++++++++++ 1 file changed, 246 insertions(+) create mode 100644 interface-definitions/service_upnp.xml.in diff --git a/interface-definitions/service_upnp.xml.in b/interface-definitions/service_upnp.xml.in new file mode 100644 index 000000000..8d0a14d4e --- /dev/null +++ b/interface-definitions/service_upnp.xml.in @@ -0,0 +1,246 @@ + + + + + + + Universal Plug and Play (UPnP) service + 900 + + + + + Name of this service + + txt + Friendly name + + + + + + WAN network interface (REQUIRE) + + + + + + + + + + + WAN network IP + + ipv4 + IPv4 address + + + ipv6 + IPv6 address + + + + + + + + + + + Enable NAT-PMP support + + + + + + Enable Secure Mode + + + + + + Presentation Url + + txt + Presentation Url + + + + + + PCP-base lifetime Option + + + + + Max lifetime time + + + + + + + + Min lifetime time + + + + + + + + + + Local IP addresses for service to listen on + + + + + + <interface> + Monitor interface address + + + ipv4 + IP address to listen for incoming connections + + + ipv4-prefix + IP prefix to listen for incoming connections + + + ipv6 + IP address to listen for incoming connections + + + ipv6-prefix + IP prefix to listen for incoming connections + + + + + + + + + + + + + + Enable STUN probe support (can be used with NAT 1:1 support for WAN interfaces) + + + + + The STUN server address + + txt + The STUN server host address + + + stun.stunprotocol.org + stunprotocol + + + stun.sipgate.net + sipgate + + + stun.xten.com + xten + + + txt + other STUN Server + + + + + + The STUN server port + + txt + The STUN server port + + + + + + + + UPnP Rule + + + + + + + + Disable Rule + + + + + + Port range (REQUIRE) + + <port> + single port + + + <portN>-<portM> + Port range (use '-' as delimiter) + + + + + + + + + Port range (REQUIRE) + + <port> + single port + + + <portN>-<portM> + Port range (use '-' as delimiter) + + + + + + + + + The IP to which this rule applies (REQUIRE) + + ipv4 + The IPv4 to which this rule applies + + + + + + + + + Actions against the rule (REQUIRE) + + allow deny + + + ^(allow|deny)$ + + + + + + + + + + -- cgit v1.2.3 From b57b048623d0c336ed7e4b9293cab32ed82324e3 Mon Sep 17 00:00:00 2001 From: jack9603301 Date: Thu, 25 Mar 2021 19:20:49 +0800 Subject: upnpd: T3420: Implement features --- data/configd-include.json | 1 + data/templates/firewall/upnpd.conf.tmpl | 172 +++++++++++++++++++++++++++++ smoketest/scripts/cli/test_service_upnp.py | 71 ++++++++++++ src/conf_mode/service_upnp.py | 155 ++++++++++++++++++++++++++ src/systemd/miniupnpd.service | 13 +++ 5 files changed, 412 insertions(+) create mode 100644 data/templates/firewall/upnpd.conf.tmpl create mode 100755 smoketest/scripts/cli/test_service_upnp.py create mode 100755 src/conf_mode/service_upnp.py create mode 100644 src/systemd/miniupnpd.service diff --git a/data/configd-include.json b/data/configd-include.json index 6893aaa86..6f00b8492 100644 --- a/data/configd-include.json +++ b/data/configd-include.json @@ -53,6 +53,7 @@ "service_mdns-repeater.py", "service_pppoe-server.py", "service_router-advert.py", +"service_upnp.py", "ssh.py", "system-ip.py", "system-ipv6.py", diff --git a/data/templates/firewall/upnpd.conf.tmpl b/data/templates/firewall/upnpd.conf.tmpl new file mode 100644 index 000000000..39cb21373 --- /dev/null +++ b/data/templates/firewall/upnpd.conf.tmpl @@ -0,0 +1,172 @@ +# This is the UPNP configuration file + +# WAN network interface +ext_ifname={{ wan_interface }} +{% if wan_ip is defined %} +# If the WAN interface has several IP addresses, you +# can specify the one to use below +{% for addr in wan_ip %} +ext_ip={{ addr }} +{% endfor %} +{% endif %} + +# LAN network interfaces IPs / networks +{% if listen is defined %} +# There can be multiple listening IPs for SSDP traffic, in that case +# use multiple 'listening_ip=...' lines, one for each network interface. +# It can be IP address or network interface name (ie. "eth0") +# It is mandatory to use the network interface name in order to enable IPv6 +# HTTP is available on all interfaces. +# When MULTIPLE_EXTERNAL_IP is enabled, the external IP +# address associated with the subnet follows. For example: +# listening_ip=192.168.0.1/24 88.22.44.13 +{% for addr in listen %} +{% if addr | is_ipv4 %} +listening_ip={{ addr }} +{% elif addr | is_ipv6 %} +ipv6_listening_ip={{ addr }} +{% else %} +listening_ip={{ addr }} +{% endif %} +{% endfor %} +{% endif %} + +# CAUTION: mixing up WAN and LAN interfaces may introduce security risks! +# Be sure to assign the correct interfaces to LAN and WAN and consider +# implementing UPnP permission rules at the bottom of this configuration file + +# Port for HTTP (descriptions and SOAP) traffic. Set to 0 for autoselect. +#http_port=0 +# Port for HTTPS. Set to 0 for autoselect (default) +#https_port=0 + +# Path to the UNIX socket used to communicate with MiniSSDPd +# If running, MiniSSDPd will manage M-SEARCH answering. +# default is /var/run/minissdpd.sock +#minissdpdsocket=/var/run/minissdpd.sock + +{% if nat_pmp is defined %} +# Enable NAT-PMP support (default is no) +enable_natpmp=yes +{% endif %} + +# Enable UPNP support (default is yes) +enable_upnp=yes + +{% if pcp_lifetime is defined %} +# PCP +# Configure the minimum and maximum lifetime of a port mapping in seconds +# 120s and 86400s (24h) are suggested values from PCP-base +{% if pcp_lifetime.max is defined %} +max_lifetime={{ pcp_lifetime.max }} +{% endif %} +{% if pcp_lifetime.min is defined %} +min_lifetime={{ pcp_lifetime.min }} +{% endif %} +{% endif %} + + +# To enable the next few runtime options, see compile time +# ENABLE_MANUFACTURER_INFO_CONFIGURATION (config.h) + +{% if friendly_name is defined %} +# Name of this service, default is "`uname -s` router" +friendly_name= {{ friendly_name }} +{% endif %} + +# Manufacturer name, default is "`uname -s`" +manufacturer_name=VyOS + +# Manufacturer URL, default is URL of OS vendor +manufacturer_url=https://vyos.io/ + +# Model name, default is "`uname -s` router" +model_name=VyOS Router Model + +# Model description, default is "`uname -s` router" +model_description=Vyos open source enterprise router/firewall operating system + +# Model URL, default is URL of OS vendor +model_url=https://vyos.io/ + +{% if secure_mode is defined %} +# Secure Mode, UPnP clients can only add mappings to their own IP +secure_mode=yes +{% else %} +# Secure Mode, UPnP clients can only add mappings to their own IP +secure_mode=no +{% endif %} + +{% if presentation_url is defined %} +# Default presentation URL is HTTP address on port 80 +# If set to an empty string, no presentationURL element will appear +# in the XML description of the device, which prevents MS Windows +# from displaying an icon in the "Network Connections" panel. +#presentation_url= {{ presentation_url }} +{% endif %} + +# Report system uptime instead of daemon uptime +system_uptime=yes + +# Unused rules cleaning. +# never remove any rule before this threshold for the number +# of redirections is exceeded. default to 20 +clean_ruleset_threshold=10 +# Clean process work interval in seconds. default to 0 (disabled). +# a 600 seconds (10 minutes) interval makes sense +clean_ruleset_interval=600 + +# Anchor name in pf (default is miniupnpd) +anchor=VyOS + +uuid={{ uuid }} + +# Lease file location +lease_file=/config/upnp.leases + +# Daemon's serial and model number when reporting to clients +# (in XML description) +#serial=12345678 +#model_number=1 + +{% if rules is defined %} +# UPnP permission rules +# (allow|deny) (external port range) IP/mask (internal port range) +# A port range is - or if there is only +# one port in the range. +# IP/mask format must be nnn.nnn.nnn.nnn/nn +# It is advised to only allow redirection of port >= 1024 +# and end the rule set with "deny 0-65535 0.0.0.0/0 0-65535" +# The following default ruleset allows specific LAN side IP addresses +# to request only ephemeral ports. It is recommended that users +# modify the IP ranges to match their own internal networks, and +# also consider implementing network-specific restrictions +# CAUTION: failure to enforce any rules may permit insecure requests to be made! +{% for rule, config in rules.items() %} +{% if config.disable is defined %} +{{ config.action}} {{ config.external_port_range }} {{ config.ip }} {{ config.internal_port_range }} +{% endif %} +{% endfor %} +{% endif %} + +{% if stun is defined %} +# WAN interface must have public IP address. Otherwise it is behind NAT +# and port forwarding is impossible. In some cases WAN interface can be +# behind unrestricted NAT 1:1 when all incoming traffic is NAT-ed and +# routed to WAN interfaces without any filtering. In this cases miniupnpd +# needs to know public IP address and it can be learnt by asking external +# server via STUN protocol. Following option enable retrieving external +# public IP address from STUN server and detection of NAT type. You need +# to specify also external STUN server in stun_host option below. +# This option is disabled by default. +ext_perform_stun=yes +# Specify STUN server, either hostname or IP address +# Some public STUN servers: +# stun.stunprotocol.org +# stun.sipgate.net +# stun.xten.com +# stun.l.google.com (on non standard port 19302) +ext_stun_host={{ stun.host }} +# Specify STUN UDP port, by default it is standard port 3478. +ext_stun_port={{ stun.port }} +{% endif %} diff --git a/smoketest/scripts/cli/test_service_upnp.py b/smoketest/scripts/cli/test_service_upnp.py new file mode 100755 index 000000000..9fbbdaff9 --- /dev/null +++ b/smoketest/scripts/cli/test_service_upnp.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 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 . + +import re +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSession +from vyos.util import read_file +from vyos.util import process_named_running + +UPNP_CONF = '/run/upnp/miniupnp.conf' +interface = 'eth0' +base_path = ['service', 'upnp'] +address_base = ['interfaces', 'ethernet', interface, 'address'] + +class TestServiceUPnP(VyOSUnitTestSHIM.TestCase): + def tearDown(self): + self.cli_delete(address_base) + self.cli_delete(base_path) + self.cli_commit() + + def test_ipv4_base(self): + self.cli_set(address_base + ['100.64.0.1/24']) + self.cli_set(base_path + ['nat-pmp']) + self.cli_set(base_path + ['wan-interface', interface]) + self.cli_set(base_path + ['listen', interface]) + self.cli_commit() + + config = read_file(UPNP_CONF) + self.assertIn(f'ext_ifname={interface}', config) + self.assertIn(f'listening_ip={interface}', config) + self.assertIn(f'enable_natpmp=yes', config) + self.assertIn(f'enable_upnp=yes', config) + + # Check for running process + self.assertTrue(process_named_running('miniupnpd')) + + def test_ipv6_base(self): + self.cli_set(address_base + ['2001:db8::1/64']) + self.cli_set(base_path + ['nat-pmp']) + self.cli_set(base_path + ['wan-interface', interface]) + self.cli_set(base_path + ['listen', interface]) + self.cli_set(base_path + ['listen', '2001:db8::1']) + self.cli_commit() + + config = read_file(UPNP_CONF) + self.assertIn(f'ext_ifname={interface}', config) + self.assertIn(f'listening_ip={interface}', config) + self.assertIn(f'enable_natpmp=yes', config) + self.assertIn(f'enable_upnp=yes', config) + + # Check for running process + self.assertTrue(process_named_running('miniupnpd')) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/src/conf_mode/service_upnp.py b/src/conf_mode/service_upnp.py new file mode 100755 index 000000000..8bf6f43b4 --- /dev/null +++ b/src/conf_mode/service_upnp.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 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 . + +import os + +from sys import exit +import uuid +import netifaces +from ipaddress import IPv4Network +from ipaddress import IPv6Network + +from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.configdict import dict_search +from vyos.configdict import get_interface_dict +from vyos.configverify import verify_vrf +from vyos.util import call +from vyos.template import render +from vyos.template import is_ipv4 +from vyos.template import is_ipv6 +from vyos.xml import defaults +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +config_file = r'/run/upnp/miniupnp.conf' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'upnp'] + upnpd = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + + if not upnpd: + return None + + if dict_search('rule', upnpd): + default_member_values = defaults(base + ['rule']) + for rule,rule_config in upnpd['rule'].items(): + upnpd['rule'][rule] = dict_merge(default_member_values, upnpd['rule'][rule]) + + uuidgen = uuid.uuid1() + upnpd.update({'uuid': uuidgen}) + + return upnpd + +def get_all_interface_addr(prefix, filter_dev, filter_family): + list_addr = [] + interfaces = netifaces.interfaces() + + for interface in interfaces: + if filter_dev and interface in filter_dev: + continue + addrs = netifaces.ifaddresses(interface) + if netifaces.AF_INET in addrs.keys(): + if netifaces.AF_INET in filter_family: + for addr in addrs[netifaces.AF_INET]: + if prefix: + # we need to manually assemble a list of IPv4 address/prefix + prefix = '/' + \ + str(IPv4Network('0.0.0.0/' + addr['netmask']).prefixlen) + list_addr.append(addr['addr'] + prefix) + else: + list_addr.append(addr['addr']) + if netifaces.AF_INET6 in addrs.keys(): + if netifaces.AF_INET6 in filter_family: + for addr in addrs[netifaces.AF_INET6]: + if prefix: + # we need to manually assemble a list of IPv4 address/prefix + bits = bin(int(addr['netmask'].replace(':', ''), 16)).count('1') + prefix = '/' + str(bits) + list_addr.append(addr['addr'] + prefix) + else: + list_addr.append(addr['addr']) + + return list_addr + +def verify(upnpd): + if not upnpd: + return None + + if 'wan_interface' not in upnpd: + raise ConfigError('To enable UPNP, you must have the "wan-interface" option!') + + if dict_search('rules', upnpd): + for rule,rule_config in upnpd['rule'].items(): + for option in ['external_port_range', 'internal_port_range', 'ip', 'action']: + if option not in rule_config: + raise ConfigError(f'A UPNP rule must have an "{option}" option!') + + if dict_search('stun', upnpd): + for option in ['host', 'port']: + if option not in upnpd['stun']: + raise ConfigError(f'A UPNP stun support must have an "{option}" option!') + + # Check the validity of the IP address + listen_dev = [] + system_addrs_cidr = get_all_interface_addr(True, [], [netifaces.AF_INET, netifaces.AF_INET6]) + system_addrs = get_all_interface_addr(False, [], [netifaces.AF_INET, netifaces.AF_INET6]) + for listen_if_or_addr in upnpd['listen']: + if listen_if_or_addr not in netifaces.interfaces(): + listen_dev.append(listen_if_or_addr) + if (listen_if_or_addr not in system_addrs) and (listen_if_or_addr not in system_addrs_cidr) and (listen_if_or_addr not in netifaces.interfaces()): + if is_ipv4(listen_if_or_addr) and IPv4Network(listen_if_or_addr).is_multicast: + raise ConfigError(f'The address "{listen_if_or_addr}" is an address that is not allowed to listen on. It is not an interface address nor a multicast address!') + if is_ipv6(listen_if_or_addr) and IPv6Network(listen_if_or_addr).is_multicast: + raise ConfigError(f'The address "{listen_if_or_addr}" is an address that is not allowed to listen on. It is not an interface address nor a multicast address!') + + system_listening_dev_addrs_cidr = get_all_interface_addr(True, listen_dev, [netifaces.AF_INET6]) + system_listening_dev_addrs = get_all_interface_addr(False, listen_dev, [netifaces.AF_INET6]) + for listen_if_or_addr in upnpd['listen']: + if listen_if_or_addr not in netifaces.interfaces() and (listen_if_or_addr not in system_listening_dev_addrs_cidr) and (listen_if_or_addr not in system_listening_dev_addrs) and is_ipv6(listen_if_or_addr) and (not IPv6Network(listen_if_or_addr).is_multicast): + raise ConfigError(f'{listen_if_or_addr} must listen on the interface of the network card') + +def generate(upnpd): + if not upnpd: + return None + + if os.path.isfile(config_file): + os.unlink(config_file) + + render(config_file, 'firewall/upnpd.conf.tmpl', upnpd) + +def apply(upnpd): + if not upnpd: + # Stop the UPNP service + call('systemctl stop miniupnpd.service') + else: + # Start the UPNP service + call('systemctl restart miniupnpd.service') + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/systemd/miniupnpd.service b/src/systemd/miniupnpd.service new file mode 100644 index 000000000..51cb2eed8 --- /dev/null +++ b/src/systemd/miniupnpd.service @@ -0,0 +1,13 @@ +[Unit] +Description=UPnP service +ConditionPathExists=/run/upnp/miniupnp.conf +After=vyos-router.service +StartLimitIntervalSec=0 + +[Service] +WorkingDirectory=/run/upnp +Type=simple +ExecStart=/usr/sbin/miniupnpd -d -f /run/upnp/miniupnp.conf +PrivateTmp=yes +PIDFile=/run/miniupnpd.pid +Restart=on-failure -- cgit v1.2.3 From c7cdb87fa09a7e51e13de9939de6f6cc8a6a25fc Mon Sep 17 00:00:00 2001 From: jack9603301 Date: Sat, 13 Nov 2021 18:43:02 +0800 Subject: upnpd: T3420: Fix IPv6 errors --- src/conf_mode/service_upnp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/conf_mode/service_upnp.py b/src/conf_mode/service_upnp.py index 8bf6f43b4..638296f45 100755 --- a/src/conf_mode/service_upnp.py +++ b/src/conf_mode/service_upnp.py @@ -82,7 +82,7 @@ def get_all_interface_addr(prefix, filter_dev, filter_family): for addr in addrs[netifaces.AF_INET6]: if prefix: # we need to manually assemble a list of IPv4 address/prefix - bits = bin(int(addr['netmask'].replace(':', ''), 16)).count('1') + bits = bin(int(addr['netmask'].replace(':', '').split('/')[0], 16)).count('1') prefix = '/' + str(bits) list_addr.append(addr['addr'] + prefix) else: -- cgit v1.2.3 From fb464f0b7654add88e1b95e7862296eb711d65c3 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Sat, 8 Jan 2022 11:09:17 +0000 Subject: keepalived: T4150: Fix template option conntrack_sync_group conntrack_sync_group option not under 'vrrp' section but part of high-avalability dictionary --- data/templates/high-availability/keepalived.conf.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/high-availability/keepalived.conf.tmpl b/data/templates/high-availability/keepalived.conf.tmpl index afd5f5383..bb54752fc 100644 --- a/data/templates/high-availability/keepalived.conf.tmpl +++ b/data/templates/high-availability/keepalived.conf.tmpl @@ -103,7 +103,7 @@ vrrp_sync_group {{ name }} { {% endif %} {% endfor %} {% endif %} -{% if vrrp.conntrack_sync_group is defined and vrrp.conntrack_sync_group == name %} +{% if conntrack_sync_group is defined and conntrack_sync_group == name %} {% set vyos_helper = "/usr/libexec/vyos/vyos-vrrp-conntracksync.sh" %} notify_master "{{ vyos_helper }} master {{ name }}" notify_backup "{{ vyos_helper }} backup {{ name }}" -- cgit v1.2.3 From 66d59d9e393c435fa82717d0d918955bae59ba43 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Sat, 8 Jan 2022 13:51:59 +0000 Subject: vrrp: T1972: Ability to set IP address on not vrrp interface Ability to set virtual_address on not vrrp-listen interface Add ability don't track primary vrrp interface "exclude-vrrp-interface" Add ability to set tracking (state UP/Down) on desired interfaces For example eth0 is used for vrrp and we want to track another eth1 interface that not belong to any vrrp-group --- .../high-availability/keepalived.conf.tmpl | 16 ++++++++-- interface-definitions/high-availability.xml.in | 37 ++++++++++++++++++++-- smoketest/scripts/cli/test_ha_vrrp.py | 30 ++++++++++++++++++ 3 files changed, 77 insertions(+), 6 deletions(-) diff --git a/data/templates/high-availability/keepalived.conf.tmpl b/data/templates/high-availability/keepalived.conf.tmpl index afd5f5383..da598cd1f 100644 --- a/data/templates/high-availability/keepalived.conf.tmpl +++ b/data/templates/high-availability/keepalived.conf.tmpl @@ -28,6 +28,9 @@ vrrp_instance {{ name }} { virtual_router_id {{ group_config.vrid }} priority {{ group_config.priority }} advert_int {{ group_config.advertise_interval }} +{% if group_config.track is defined and group_config.track.exclude_vrrp_interface is defined %} + dont_track_primary +{% endif %} {% if group_config.no_preempt is not defined and group_config.preempt_delay is defined and group_config.preempt_delay is not none %} preempt_delay {{ group_config.preempt_delay }} {% elif group_config.no_preempt is defined %} @@ -61,8 +64,8 @@ vrrp_instance {{ name }} { {% endif %} {% if group_config.address is defined and group_config.address is not none %} virtual_ipaddress { -{% for addr in group_config.address %} - {{ addr }} +{% for addr, addr_config in group_config.address.items() %} + {{ addr }}{{ ' dev ' + addr_config.interface if addr_config.interface is defined }} {% endfor %} } {% endif %} @@ -73,6 +76,13 @@ vrrp_instance {{ name }} { {% endfor %} } {% endif %} +{% if group_config.track is defined and group_config.track.interface is defined and group_config.track.interface is not none %} + track_interface { +{% for interface in group_config.track.interface %} + {{ interface }} +{% endfor %} + } +{% endif %} {% if group_config.health_check is defined and group_config.health_check.script is defined and group_config.health_check.script is not none %} track_script { healthcheck_{{ name }} @@ -113,8 +123,8 @@ vrrp_sync_group {{ name }} { {% endfor %} {% endif %} -# Virtual-server configuration {% if virtual_server is defined and virtual_server is not none %} +# Virtual-server configuration {% for vserver, vserver_config in virtual_server.items() %} virtual_server {{ vserver }} {{ vserver_config.port }} { delay_loop {{ vserver_config.delay_loop }} diff --git a/interface-definitions/high-availability.xml.in b/interface-definitions/high-availability.xml.in index f46343c76..ee1d70484 100644 --- a/interface-definitions/high-availability.xml.in +++ b/interface-definitions/high-availability.xml.in @@ -177,8 +177,37 @@ + + + Track settings + + + + + + Disable track state of main interface + + + + + Interface name state check + + + + + txt + Interface name + + + + + + + + + #include - + Virtual IP address @@ -193,9 +222,11 @@ - - + + #include + + Virtual address (If you need additional IPv4 and IPv6 in same group) diff --git a/smoketest/scripts/cli/test_ha_vrrp.py b/smoketest/scripts/cli/test_ha_vrrp.py index a37ce7e64..68905e447 100755 --- a/smoketest/scripts/cli/test_ha_vrrp.py +++ b/smoketest/scripts/cli/test_ha_vrrp.py @@ -166,5 +166,35 @@ class TestVRRP(VyOSUnitTestSHIM.TestCase): for group in groups: self.assertIn(f'{group}', config) + def test_04_exclude_vrrp_interface(self): + group = 'VyOS-WAN' + none_vrrp_interface = 'eth2' + vlan_id = '24' + vip = '100.64.24.1/24' + vip_dev = '192.0.2.2/24' + vrid = '150' + group_base = base_path + ['vrrp', 'group', group] + + self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', '100.64.24.11/24']) + self.cli_set(group_base + ['interface', f'{vrrp_interface}.{vlan_id}']) + self.cli_set(group_base + ['address', vip]) + self.cli_set(group_base + ['address', vip_dev, 'interface', none_vrrp_interface]) + self.cli_set(group_base + ['track', 'exclude-vrrp-interface']) + self.cli_set(group_base + ['track', 'interface', none_vrrp_interface]) + self.cli_set(group_base + ['vrid', vrid]) + + # commit changes + self.cli_commit() + + config = getConfig(f'vrrp_instance {group}') + + self.assertIn(f'interface {vrrp_interface}.{vlan_id}', config) + self.assertIn(f'virtual_router_id {vrid}', config) + self.assertIn(f'dont_track_primary', config) + self.assertIn(f' {vip}', config) + self.assertIn(f' {vip_dev} dev {none_vrrp_interface}', config) + self.assertIn(f'track_interface', config) + self.assertIn(f' {none_vrrp_interface}', config) + if __name__ == '__main__': unittest.main(verbosity=2) -- cgit v1.2.3 From d997874deb61bcdac1cc5e8f2a882347fc83f2d8 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Sun, 9 Jan 2022 12:34:04 +0000 Subject: nhrp: T4152: Fix template holding-time for nhrp Add missed 'holding-time' option for shortcut-target address --- data/templates/nhrp/opennhrp.conf.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/nhrp/opennhrp.conf.tmpl b/data/templates/nhrp/opennhrp.conf.tmpl index 948327198..e9e9f692a 100644 --- a/data/templates/nhrp/opennhrp.conf.tmpl +++ b/data/templates/nhrp/opennhrp.conf.tmpl @@ -33,7 +33,7 @@ interface {{ name }} #{{ type }} {{ profile_name }} {% endfor %} {% if tunnel_conf.shortcut_target is defined and tunnel_conf.shortcut_target is not none %} {% for target, shortcut_conf in tunnel_conf.shortcut_target.items() %} - shortcut-target {{ target }} {{ shortcut_conf.holding_time if shortcut_conf.holding_time is defined else '' }} + shortcut-target {{ target }}{{ ' holding-time ' + shortcut_conf.holding_time if shortcut_conf.holding_time is defined }} {% endfor %} {% endif %} -- cgit v1.2.3 From 7c1ea983c455a08b66b948a08b1cf11b7bff967d Mon Sep 17 00:00:00 2001 From: Georg Date: Sun, 9 Jan 2022 22:13:56 +0100 Subject: T4157: Add jinja2 to test-requirements.txt Signed-off-by: Georg --- test-requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/test-requirements.txt b/test-requirements.txt index 9348520b5..a475e0a16 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -3,3 +3,4 @@ lxml pylint nose coverage +jinja2 -- cgit v1.2.3 From deb9bfa02863ea28104f36558ed4e90caba792e3 Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Sun, 9 Jan 2022 23:35:30 +0100 Subject: policy: T4155: Fix using incorrect table variable --- python/vyos/firewall.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py index 8b7402b7e..414ec89c1 100644 --- a/python/vyos/firewall.py +++ b/python/vyos/firewall.py @@ -209,7 +209,7 @@ def parse_policy_set(set_conf, def_suffix): table = set_conf['table'] if table == 'main': table = '254' - mark = 0x7FFFFFFF - int(set_conf['table']) + mark = 0x7FFFFFFF - int(table) out.append(f'meta mark set {mark}') if 'tcp_mss' in set_conf: mss = set_conf['tcp_mss'] -- cgit v1.2.3 From 8dfde277c90ca27551fed9476fd30d28b90797ef Mon Sep 17 00:00:00 2001 From: Nicolas Fort Date: Mon, 10 Jan 2022 17:57:45 +0000 Subject: policy: T4161: Set correct description for local-preference --- interface-definitions/policy.xml.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface-definitions/policy.xml.in b/interface-definitions/policy.xml.in index 225f9a6f9..61c5ab90a 100644 --- a/interface-definitions/policy.xml.in +++ b/interface-definitions/policy.xml.in @@ -793,7 +793,7 @@ - local-preference_help + Local Preference u32:0-4294967295 Local Preference -- cgit v1.2.3 From 67ab8154685638b373b139aaf9a936cbcb83a84f Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Sun, 9 Jan 2022 23:36:31 +0100 Subject: firewall: 4149: Fix verify steps being bypassed when base node is removed --- src/conf_mode/firewall.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index 75382034f..0b4c0854f 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -104,9 +104,6 @@ def get_config(config=None): conf = Config() base = ['firewall'] - if not conf.exists(base): - return {} - firewall = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) @@ -169,10 +166,6 @@ def verify_rule(firewall, rule_conf, ipv6): raise ConfigError('Protocol must be tcp, udp, or tcp_udp when specifying a port or port-group') def verify(firewall): - # bail out early - looks like removal from running config - if not firewall: - return None - if 'config_trap' in firewall and firewall['config_trap'] == 'enable': if not firewall['trap_targets']: raise ConfigError(f'Firewall config-trap enabled but "service snmp trap-target" is not defined') -- cgit v1.2.3 From a5ad98b2307af974dd498a84caec94fa613f7491 Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Mon, 10 Jan 2022 01:00:12 +0100 Subject: firewall: validators: T2199: Improve port validation --- interface-definitions/firewall.xml.in | 3 ++ interface-definitions/include/firewall/port.xml.i | 5 ++- src/validators/port-multi | 43 +++++++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100755 src/validators/port-multi diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in index 78a48a522..07a36093f 100644 --- a/interface-definitions/firewall.xml.in +++ b/interface-definitions/firewall.xml.in @@ -182,6 +182,9 @@ Numbered port range (e.g. 1001-1050) + + + diff --git a/interface-definitions/include/firewall/port.xml.i b/interface-definitions/include/firewall/port.xml.i index 59d92978b..3bacafff8 100644 --- a/interface-definitions/include/firewall/port.xml.i +++ b/interface-definitions/include/firewall/port.xml.i @@ -16,8 +16,11 @@ - \n\n Multiple destination ports can be specified as a comma-separated list.\n The whole list can also be negated using '!'.\n For example: '!22,telnet,http,123,1001-1005' + \n\n Multiple destination ports can be specified as a comma-separated list.\n For example: 'telnet,http,123,1001-1005' + + + diff --git a/src/validators/port-multi b/src/validators/port-multi new file mode 100755 index 000000000..763d34e57 --- /dev/null +++ b/src/validators/port-multi @@ -0,0 +1,43 @@ +#!/usr/bin/python3 + +import sys +import re + +from vyos.util import read_file + +services_file = '/etc/services' + +def get_services(): + names = [] + service_data = read_file(services_file, "") + for line in service_data.split("\n"): + if not line or line[0] == '#': + continue + names.append(line.split(None, 1)[0]) + return names + +if __name__ == '__main__': + if len(sys.argv)>1: + ports = sys.argv[1].split(",") + services = get_services() + + for port in ports: + if re.match('^[0-9]{1,5}-[0-9]{1,5}$', port): + port_1, port_2 = port.split('-') + if int(port_1) not in range(1, 65535) or int(port_2) not in range(1, 65535): + print(f'Error: {port} is not a valid port range') + sys.exit(1) + if int(port_1) > int(port_2): + print(f'Error: {port} is not a valid port range') + sys.exit(1) + elif port.isnumeric(): + if int(port) not in range(1, 65535): + print(f'Error: {port} is not a valid port') + sys.exit(1) + elif port not in services: + print(f'Error: {port} is not a valid service name') + sys.exit(1) + else: + sys.exit(2) + + sys.exit(0) -- cgit v1.2.3 From da370b63b266254d9a7a7ae15274a9a70bcf5417 Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Sun, 9 Jan 2022 23:37:12 +0100 Subject: validators: T4148: Add text output when validators fail --- src/validators/ipv4-range | 13 +++++++++---- src/validators/port-range | 2 ++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/validators/ipv4-range b/src/validators/ipv4-range index cc59039f1..6492bfc52 100755 --- a/src/validators/ipv4-range +++ b/src/validators/ipv4-range @@ -7,6 +7,11 @@ ip2dec () { printf '%d\n' "$((a * 256 ** 3 + b * 256 ** 2 + c * 256 + d))" } +error_exit() { + echo "Error: $1 is not a valid IPv4 address range" + exit 1 +} + # Only run this if there is a hypen present in $1 if [[ "$1" =~ "-" ]]; then # This only works with real bash (<<<) - split IP addresses into array with @@ -15,21 +20,21 @@ if [[ "$1" =~ "-" ]]; then ipaddrcheck --is-ipv4-single ${strarr[0]} if [ $? -gt 0 ]; then - exit 1 + error_exit $1 fi ipaddrcheck --is-ipv4-single ${strarr[1]} if [ $? -gt 0 ]; then - exit 1 + error_exit $1 fi start=$(ip2dec ${strarr[0]}) stop=$(ip2dec ${strarr[1]}) if [ $start -ge $stop ]; then - exit 1 + error_exit $1 fi exit 0 fi -exit 1 +error_exit $1 diff --git a/src/validators/port-range b/src/validators/port-range index abf0b09d5..6e68c8733 100755 --- a/src/validators/port-range +++ b/src/validators/port-range @@ -9,9 +9,11 @@ if __name__ == '__main__': if re.search('[0-9]{1,5}-[0-9]{1,5}', port_range): for tmp in port_range.split('-'): if int(tmp) not in range(1, 65535): + print(f'Error: {port_range} is not a valid port range') sys.exit(1) else: if int(port_range) not in range(1, 65535): + print(f'Error: {port_range} is not a valid port') sys.exit(1) else: sys.exit(2) -- cgit v1.2.3 From 0a0e7d789e7e482b65cbca47bff1dcb427891a88 Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Sun, 9 Jan 2022 23:45:04 +0100 Subject: validators: Stricter checking on port-range validator --- src/validators/port-range | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/validators/port-range b/src/validators/port-range index 6e68c8733..657a21e20 100755 --- a/src/validators/port-range +++ b/src/validators/port-range @@ -3,18 +3,21 @@ import sys import re +def error(port_range): + print(f'Error: {port_range} is not a valid port or port range') + sys.exit(1) + if __name__ == '__main__': if len(sys.argv)>1: port_range = sys.argv[1] - if re.search('[0-9]{1,5}-[0-9]{1,5}', port_range): - for tmp in port_range.split('-'): - if int(tmp) not in range(1, 65535): - print(f'Error: {port_range} is not a valid port range') - sys.exit(1) - else: - if int(port_range) not in range(1, 65535): - print(f'Error: {port_range} is not a valid port') - sys.exit(1) + if re.match('^[0-9]{1,5}-[0-9]{1,5}$', port_range): + port_1, port_2 = port_range.split('-') + if int(port_1) not in range(1, 65535) or int(port_2) not in range(1, 65535): + error(port_range) + if int(port_1) > int(port_2): + error(port_range) + elif not port_range.isnumeric() or int(port_range) not in range(1, 65535): + error(port_range) else: sys.exit(2) -- cgit v1.2.3 From 05b5d09ca70c5cc868f2108df4bcd3fcf6a7d865 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 9 Jan 2022 20:54:39 +0100 Subject: conntrack: T3579: migrate "conntrack ignore" tree to vyos-1x and nftables --- data/templates/conntrack/nftables-ct-ignore.tmpl | 40 ++++ .../include/conntrack/log-common.xml.i | 20 ++ interface-definitions/system-conntrack.xml.in | 205 +++++++++++++++++++++ src/conf_mode/conntrack.py | 12 ++ 4 files changed, 277 insertions(+) create mode 100644 data/templates/conntrack/nftables-ct-ignore.tmpl create mode 100644 interface-definitions/include/conntrack/log-common.xml.i diff --git a/data/templates/conntrack/nftables-ct-ignore.tmpl b/data/templates/conntrack/nftables-ct-ignore.tmpl new file mode 100644 index 000000000..59c1cb1d2 --- /dev/null +++ b/data/templates/conntrack/nftables-ct-ignore.tmpl @@ -0,0 +1,40 @@ +#!/usr/sbin/nft -f + +# we first flush the chains content and then render the new statements from CLI +# if applicable +{% set nft_ct_ignore_name = 'VYOS_CT_IGNORE' %} +flush chain raw {{ nft_ct_ignore_name }} +table raw { + chain {{ nft_ct_ignore_name }} { +{% if ignore is defined and ignore.rule is defined and ignore.rule is not none %} +{% for rule, rule_config in ignore.rule.items() %} + # rule-{{ rule }} {{ '- ' ~ rule_config.description if rule_config.description is defined and rule_config.description is not none }} +{% set nft_command = '' %} +{% if rule_config.inbound_interface is defined and rule_config.inbound_interface is not none %} +{% set nft_command = nft_command ~ ' iifname ' ~ rule_config.inbound_interface %} +{% endif %} +{% if rule_config.protocol is defined and rule_config.protocol is not none %} +{% set nft_command = nft_command ~ ' ip protocol ' ~ rule_config.protocol %} +{% endif %} +{% if rule_config.destination is defined and rule_config.destination is not none %} +{% if rule_config.destination.address is defined and rule_config.destination.address is not none %} +{% set nft_command = nft_command ~ ' ip daddr ' ~ rule_config.destination.address %} +{% endif %} +{% if rule_config.destination.port is defined and rule_config.destination.port is not none %} +{% set nft_command = nft_command ~ ' ' ~ rule_config.protocol ~ ' dport { ' ~ rule_config.destination.port ~ ' }' %} +{% endif %} +{% endif %} +{% if rule_config.source is defined and rule_config.source is not none %} +{% if rule_config.source.address is defined and rule_config.source.address is not none %} +{% set nft_command = nft_command ~ ' ip saddr ' ~ rule_config.source.address %} +{% endif %} +{% if rule_config.source.port is defined and rule_config.source.port is not none %} +{% set nft_command = nft_command ~ ' ' ~ rule_config.protocol ~ ' sport { ' ~ rule_config.source.port ~ ' }' %} +{% endif %} +{% endif %} + {{ nft_command }} counter return comment ignore-{{ rule }} +{% endfor %} +{% endif %} + return + } +} diff --git a/interface-definitions/include/conntrack/log-common.xml.i b/interface-definitions/include/conntrack/log-common.xml.i new file mode 100644 index 000000000..38799f8f4 --- /dev/null +++ b/interface-definitions/include/conntrack/log-common.xml.i @@ -0,0 +1,20 @@ + + + + Log connection deletion + + + + + + Log connection creation + + + + + + Log connection updates + + + + diff --git a/interface-definitions/system-conntrack.xml.in b/interface-definitions/system-conntrack.xml.in index daa4177c9..88f96a078 100644 --- a/interface-definitions/system-conntrack.xml.in +++ b/interface-definitions/system-conntrack.xml.in @@ -35,6 +35,128 @@ 32768 + + + Customized rules to ignore selective connection tracking + + + + + Rule number + + u32:1-999999 + Number of conntrack ignore rule + + + + + Ignore rule number must be between 1 and 999999 + + + #include + + + Destination parameters + + + #include + #include + + + + + Interface to ignore connections tracking on + + any + + + + + #include + + + Protocol to match (protocol name, number, or "all") + + + all tcp_udp + + + all + All IP protocols + + + tcp_udp + Both TCP and UDP + + + u32:0-255 + IP protocol number + + + <protocol> + IP protocol name + + + !<protocol> + IP protocol name + + + + + + + + + Source parameters + + + #include + #include + + + + + + + + + Log connection tracking events per protocol + + + + + Log connection tracking events for ICMP + + + #include + + + + + Log connection tracking events for all protocols other than TCP, UDP and ICMP + + + #include + + + + + Log connection tracking events for TCP + + + #include + + + + + Log connection tracking events for UDP + + + #include + + + + Connection tracking modules @@ -155,6 +277,89 @@ Connection timeout options + + + Define custom timeouts per connection + + + + + Rule number + + u32:1-999999 + Number of conntrack rule + + + + + Ignore rule number must be between 1 and 999999 + + + #include + + + Destination parameters + + + #include + #include + + + + + Interface to ignore connections tracking on + + any + + + + + #include + + + Protocol to match (protocol name, number, or "all") + + + all tcp_udp + + + all + All IP protocols + + + tcp_udp + Both TCP and UDP + + + u32:0-255 + IP protocol number + + + <protocol> + IP protocol name + + + !<protocol> + IP protocol name + + + + + + + + + Source parameters + + + #include + #include + + + + + + ICMP timeout in seconds diff --git a/src/conf_mode/conntrack.py b/src/conf_mode/conntrack.py index c65ef9540..3cb0dd1e2 100755 --- a/src/conf_mode/conntrack.py +++ b/src/conf_mode/conntrack.py @@ -35,6 +35,7 @@ airbag.enable() conntrack_config = r'/etc/modprobe.d/vyatta_nf_conntrack.conf' sysctl_file = r'/run/sysctl/10-vyos-conntrack.conf' +nftables_ct_ignore_file = r'/run/nftables-ct-ignore.conf' # Every ALG (Application Layer Gateway) consists of either a Kernel Object # also called a Kernel Module/Driver or some rules present in iptables @@ -86,11 +87,19 @@ def get_config(config=None): return conntrack def verify(conntrack): + if dict_search('ignore.rule', conntrack) != None: + for rule, rule_config in conntrack['ignore']['rule'].items(): + if dict_search('destination.port', rule_config) or \ + dict_search('source.port', rule_config): + if 'protocol' not in rule_config or rule_config['protocol'] not in ['tcp', 'udp']: + raise ConfigError(f'Port requires tcp or udp as protocol in rule {rule}') + return None def generate(conntrack): render(conntrack_config, 'conntrack/vyos_nf_conntrack.conf.tmpl', conntrack) render(sysctl_file, 'conntrack/sysctl.conf.tmpl', conntrack) + render(nftables_ct_ignore_file, 'conntrack/nftables-ct-ignore.tmpl', conntrack) return None @@ -127,6 +136,9 @@ def apply(conntrack): if not find_nftables_ct_rule(rule): cmd(f'nft insert rule ip raw VYOS_CT_HELPER {rule}') + # Load new nftables ruleset + cmd(f'nft -f {nftables_ct_ignore_file}') + if process_named_running('conntrackd'): # Reload conntrack-sync daemon to fetch new sysctl values resync_conntrackd() -- cgit v1.2.3 From 062762154ae1e91394d5dc98385aa07dc200671c Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 10 Jan 2022 21:42:23 +0100 Subject: conntrack: T3579: use "notrack" over "return" in nft statements --- data/templates/conntrack/nftables-ct-ignore.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/conntrack/nftables-ct-ignore.tmpl b/data/templates/conntrack/nftables-ct-ignore.tmpl index 59c1cb1d2..4ec133680 100644 --- a/data/templates/conntrack/nftables-ct-ignore.tmpl +++ b/data/templates/conntrack/nftables-ct-ignore.tmpl @@ -32,7 +32,7 @@ table raw { {% set nft_command = nft_command ~ ' ' ~ rule_config.protocol ~ ' sport { ' ~ rule_config.source.port ~ ' }' %} {% endif %} {% endif %} - {{ nft_command }} counter return comment ignore-{{ rule }} + {{ nft_command }} counter notrack comment ignore-{{ rule }} {% endfor %} {% endif %} return -- cgit v1.2.3 From fd1b1ff19b0ff852d796e979ab3b596651686f2f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 10 Jan 2022 22:26:39 +0100 Subject: conntrack: T3579: make the timeout tree re-usable as XML include --- .../conntrack/timeout-common-protocols.xml.i | 172 +++++++++++++++++ interface-definitions/system-conntrack.xml.in | 207 +-------------------- 2 files changed, 179 insertions(+), 200 deletions(-) create mode 100644 interface-definitions/include/conntrack/timeout-common-protocols.xml.i diff --git a/interface-definitions/include/conntrack/timeout-common-protocols.xml.i b/interface-definitions/include/conntrack/timeout-common-protocols.xml.i new file mode 100644 index 000000000..2676d846e --- /dev/null +++ b/interface-definitions/include/conntrack/timeout-common-protocols.xml.i @@ -0,0 +1,172 @@ + + + + ICMP timeout in seconds + + u32:1-21474836 + ICMP timeout in seconds + + + + + + 30 + + + + Generic connection timeout in seconds + + u32:1-21474836 + Generic connection timeout in seconds + + + + + + 600 + + + + TCP connection timeout options + + + + + TCP CLOSE-WAIT timeout in seconds + + u32:1-21474836 + TCP CLOSE-WAIT timeout in seconds + + + + + + 60 + + + + TCP CLOSE timeout in seconds + + u32:1-21474836 + TCP CLOSE timeout in seconds + + + + + + 10 + + + + TCP ESTABLISHED timeout in seconds + + u32:1-21474836 + TCP ESTABLISHED timeout in seconds + + + + + + 432000 + + + + TCP FIN-WAIT timeout in seconds + + u32:1-21474836 + TCP FIN-WAIT timeout in seconds + + + + + + 120 + + + + TCP LAST-ACK timeout in seconds + + u32:1-21474836 + TCP LAST-ACK timeout in seconds + + + + + + 30 + + + + TCP SYN-RECEIVED timeout in seconds + + u32:1-21474836 + TCP SYN-RECEIVED timeout in seconds + + + + + + 60 + + + + TCP SYN-SENT timeout in seconds + + u32:1-21474836 + TCP SYN-SENT timeout in seconds + + + + + + 120 + + + + TCP TIME-WAIT timeout in seconds + + u32:1-21474836 + TCP TIME-WAIT timeout in seconds + + + + + + 120 + + + + + + UDP timeout options + + + + + UDP generic timeout in seconds + + u32:1-21474836 + UDP generic timeout in seconds + + + + + + 30 + + + + UDP stream timeout in seconds + + u32:1-21474836 + UDP stream timeout in seconds + + + + + + 180 + + + + diff --git a/interface-definitions/system-conntrack.xml.in b/interface-definitions/system-conntrack.xml.in index 88f96a078..65edab839 100644 --- a/interface-definitions/system-conntrack.xml.in +++ b/interface-definitions/system-conntrack.xml.in @@ -315,38 +315,14 @@ #include - + - Protocol to match (protocol name, number, or "all") - - - all tcp_udp - - - all - All IP protocols - - - tcp_udp - Both TCP and UDP - - - u32:0-255 - IP protocol number - - - <protocol> - IP protocol name - - - !<protocol> - IP protocol name - - - - + Customize protocol specific timers, one protocol configuration per rule - + + #include + + Source parameters @@ -360,176 +336,7 @@ - - - ICMP timeout in seconds - - u32:1-21474836 - ICMP timeout in seconds - - - - - - 30 - - - - Generic connection timeout in seconds - - u32:1-21474836 - Generic connection timeout in seconds - - - - - - 600 - - - - TCP connection timeout options - - - - - TCP CLOSE-WAIT timeout in seconds - - u32:1-21474836 - TCP CLOSE-WAIT timeout in seconds - - - - - - 60 - - - - TCP CLOSE timeout in seconds - - u32:1-21474836 - TCP CLOSE timeout in seconds - - - - - - 10 - - - - TCP ESTABLISHED timeout in seconds - - u32:1-21474836 - TCP ESTABLISHED timeout in seconds - - - - - - 432000 - - - - TCP FIN-WAIT timeout in seconds - - u32:1-21474836 - TCP FIN-WAIT timeout in seconds - - - - - - 120 - - - - TCP LAST-ACK timeout in seconds - - u32:1-21474836 - TCP LAST-ACK timeout in seconds - - - - - - 30 - - - - TCP SYN-RECEIVED timeout in seconds - - u32:1-21474836 - TCP SYN-RECEIVED timeout in seconds - - - - - - 60 - - - - TCP SYN-SENT timeout in seconds - - u32:1-21474836 - TCP SYN-SENT timeout in seconds - - - - - - 120 - - - - TCP TIME-WAIT timeout in seconds - - u32:1-21474836 - TCP TIME-WAIT timeout in seconds - - - - - - 120 - - - - - - UDP timeout options - - - - - UDP generic timeout in seconds - - u32:1-21474836 - UDP generic timeout in seconds - - - - - - 30 - - - - UDP stream timeout in seconds - - u32:1-21474836 - UDP stream timeout in seconds - - - - - - 180 - - - + #include -- cgit v1.2.3 From 9bc2f5db25c74f7a4c10c10cf0bbdc2f1879c2db Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 10 Jan 2022 23:05:59 +0100 Subject: conntrack: T3579: prepare for "conntrack timeout custom rule" CLI commands --- data/templates/conntrack/nftables-ct-ignore.tmpl | 40 ------------------ data/templates/conntrack/nftables-ct.tmpl | 52 ++++++++++++++++++++++++ src/conf_mode/conntrack.py | 10 +++-- 3 files changed, 59 insertions(+), 43 deletions(-) delete mode 100644 data/templates/conntrack/nftables-ct-ignore.tmpl create mode 100644 data/templates/conntrack/nftables-ct.tmpl diff --git a/data/templates/conntrack/nftables-ct-ignore.tmpl b/data/templates/conntrack/nftables-ct-ignore.tmpl deleted file mode 100644 index 4ec133680..000000000 --- a/data/templates/conntrack/nftables-ct-ignore.tmpl +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/sbin/nft -f - -# we first flush the chains content and then render the new statements from CLI -# if applicable -{% set nft_ct_ignore_name = 'VYOS_CT_IGNORE' %} -flush chain raw {{ nft_ct_ignore_name }} -table raw { - chain {{ nft_ct_ignore_name }} { -{% if ignore is defined and ignore.rule is defined and ignore.rule is not none %} -{% for rule, rule_config in ignore.rule.items() %} - # rule-{{ rule }} {{ '- ' ~ rule_config.description if rule_config.description is defined and rule_config.description is not none }} -{% set nft_command = '' %} -{% if rule_config.inbound_interface is defined and rule_config.inbound_interface is not none %} -{% set nft_command = nft_command ~ ' iifname ' ~ rule_config.inbound_interface %} -{% endif %} -{% if rule_config.protocol is defined and rule_config.protocol is not none %} -{% set nft_command = nft_command ~ ' ip protocol ' ~ rule_config.protocol %} -{% endif %} -{% if rule_config.destination is defined and rule_config.destination is not none %} -{% if rule_config.destination.address is defined and rule_config.destination.address is not none %} -{% set nft_command = nft_command ~ ' ip daddr ' ~ rule_config.destination.address %} -{% endif %} -{% if rule_config.destination.port is defined and rule_config.destination.port is not none %} -{% set nft_command = nft_command ~ ' ' ~ rule_config.protocol ~ ' dport { ' ~ rule_config.destination.port ~ ' }' %} -{% endif %} -{% endif %} -{% if rule_config.source is defined and rule_config.source is not none %} -{% if rule_config.source.address is defined and rule_config.source.address is not none %} -{% set nft_command = nft_command ~ ' ip saddr ' ~ rule_config.source.address %} -{% endif %} -{% if rule_config.source.port is defined and rule_config.source.port is not none %} -{% set nft_command = nft_command ~ ' ' ~ rule_config.protocol ~ ' sport { ' ~ rule_config.source.port ~ ' }' %} -{% endif %} -{% endif %} - {{ nft_command }} counter notrack comment ignore-{{ rule }} -{% endfor %} -{% endif %} - return - } -} diff --git a/data/templates/conntrack/nftables-ct.tmpl b/data/templates/conntrack/nftables-ct.tmpl new file mode 100644 index 000000000..c0fe5297d --- /dev/null +++ b/data/templates/conntrack/nftables-ct.tmpl @@ -0,0 +1,52 @@ +#!/usr/sbin/nft -f + +{% set nft_ct_ignore_name = 'VYOS_CT_IGNORE' %} +{% set nft_ct_timeout_name = 'VYOS_CT_TIMEOUT' %} + +# we first flush all chains and render the content from scratch - this makes +# any delta check obsolete +flush chain raw {{ nft_ct_ignore_name }} +flush chain raw {{ nft_ct_timeout_name }} + +table raw { + chain {{ nft_ct_ignore_name }} { +{% if ignore is defined and ignore.rule is defined and ignore.rule is not none %} +{% for rule, rule_config in ignore.rule.items() %} + # rule-{{ rule }} {{ '- ' ~ rule_config.description if rule_config.description is defined and rule_config.description is not none }} +{% set nft_command = '' %} +{% if rule_config.inbound_interface is defined and rule_config.inbound_interface is not none %} +{% set nft_command = nft_command ~ ' iifname ' ~ rule_config.inbound_interface %} +{% endif %} +{% if rule_config.protocol is defined and rule_config.protocol is not none %} +{% set nft_command = nft_command ~ ' ip protocol ' ~ rule_config.protocol %} +{% endif %} +{% if rule_config.destination is defined and rule_config.destination is not none %} +{% if rule_config.destination.address is defined and rule_config.destination.address is not none %} +{% set nft_command = nft_command ~ ' ip daddr ' ~ rule_config.destination.address %} +{% endif %} +{% if rule_config.destination.port is defined and rule_config.destination.port is not none %} +{% set nft_command = nft_command ~ ' ' ~ rule_config.protocol ~ ' dport { ' ~ rule_config.destination.port ~ ' }' %} +{% endif %} +{% endif %} +{% if rule_config.source is defined and rule_config.source is not none %} +{% if rule_config.source.address is defined and rule_config.source.address is not none %} +{% set nft_command = nft_command ~ ' ip saddr ' ~ rule_config.source.address %} +{% endif %} +{% if rule_config.source.port is defined and rule_config.source.port is not none %} +{% set nft_command = nft_command ~ ' ' ~ rule_config.protocol ~ ' sport { ' ~ rule_config.source.port ~ ' }' %} +{% endif %} +{% endif %} + {{ nft_command }} counter notrack comment ignore-{{ rule }} +{% endfor %} +{% endif %} + return + } + chain {{ nft_ct_timeout_name }} { +{% if timeout is defined and timeout.custom is defined and timeout.custom.rule is defined and timeout.custom.rule is not none %} +{% for rule, rule_config in timeout.custom.rule.items() %} + # rule-{{ rule }} {{ '- ' ~ rule_config.description if rule_config.description is defined and rule_config.description is not none }} +{% endfor %} +{% endif %} + return + } +} diff --git a/src/conf_mode/conntrack.py b/src/conf_mode/conntrack.py index 3cb0dd1e2..b9eb8071d 100755 --- a/src/conf_mode/conntrack.py +++ b/src/conf_mode/conntrack.py @@ -35,7 +35,7 @@ airbag.enable() conntrack_config = r'/etc/modprobe.d/vyatta_nf_conntrack.conf' sysctl_file = r'/run/sysctl/10-vyos-conntrack.conf' -nftables_ct_ignore_file = r'/run/nftables-ct-ignore.conf' +nftables_ct_file = r'/run/nftables-ct.conf' # Every ALG (Application Layer Gateway) consists of either a Kernel Object # also called a Kernel Module/Driver or some rules present in iptables @@ -82,6 +82,10 @@ def get_config(config=None): # We have gathered the dict representation of the CLI, but there are default # options which we need to update into the dictionary retrived. default_values = defaults(base) + # XXX: T2665: we can not safely rely on the defaults() when there are + # tagNodes in place, it is better to blend in the defaults manually. + if 'timeout' in default_values and 'custom' in default_values['timeout']: + del default_values['timeout']['custom'] conntrack = dict_merge(default_values, conntrack) return conntrack @@ -99,7 +103,7 @@ def verify(conntrack): def generate(conntrack): render(conntrack_config, 'conntrack/vyos_nf_conntrack.conf.tmpl', conntrack) render(sysctl_file, 'conntrack/sysctl.conf.tmpl', conntrack) - render(nftables_ct_ignore_file, 'conntrack/nftables-ct-ignore.tmpl', conntrack) + render(nftables_ct_file, 'conntrack/nftables-ct.tmpl', conntrack) return None @@ -137,7 +141,7 @@ def apply(conntrack): cmd(f'nft insert rule ip raw VYOS_CT_HELPER {rule}') # Load new nftables ruleset - cmd(f'nft -f {nftables_ct_ignore_file}') + cmd(f'nft -f {nftables_ct_file}') if process_named_running('conntrackd'): # Reload conntrack-sync daemon to fetch new sysctl values -- cgit v1.2.3 From 76d912d63ca4d15d9efe118184c405cf8273cbcf Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 10 Jan 2022 23:17:32 +0100 Subject: conntrack: T3579: dry-run newly generated config before install Before installing a new conntrack policy into the OS Kernel, the new policy should be verified by nftables if it can be loaded at all or if it will fail to load. There is no need to load a "bad" configuration if we can pre-test it. --- src/conf_mode/conntrack.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/conf_mode/conntrack.py b/src/conf_mode/conntrack.py index b9eb8071d..aabf2bdf5 100755 --- a/src/conf_mode/conntrack.py +++ b/src/conf_mode/conntrack.py @@ -105,6 +105,13 @@ def generate(conntrack): render(sysctl_file, 'conntrack/sysctl.conf.tmpl', conntrack) render(nftables_ct_file, 'conntrack/nftables-ct.tmpl', conntrack) + # dry-run newly generated configuration + tmp = run(f'nft -c -f {nftables_ct_file}') + if tmp > 0: + if os.path.exists(nftables_ct_file): + os.unlink(nftables_ct_file) + raise ConfigError('Configuration file errors encountered!') + return None def find_nftables_ct_rule(rule): -- cgit v1.2.3 From bb76e8d7f16355b140a60feafbbed67774788343 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 10 Jan 2022 23:28:22 +0100 Subject: nat: T2199: dry-run newly generated config before install Before installing a new conntrack policy into the OS Kernel, the new policy should be verified by nftables if it can be loaded at all or if it will fail to load. There is no need to load a "bad" configuration if we can pre-test it. --- src/conf_mode/nat.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index 96f8f6fb6..9f319fc8a 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2021 VyOS maintainers and contributors +# Copyright (C) 2020-2022 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 @@ -28,6 +28,7 @@ from vyos.configdict import dict_merge from vyos.template import render from vyos.template import is_ip_network from vyos.util import cmd +from vyos.util import run from vyos.util import check_kmod from vyos.util import dict_search from vyos.validate import is_addr_assigned @@ -179,12 +180,19 @@ def verify(nat): return None def generate(nat): - render(nftables_nat_config, 'firewall/nftables-nat.tmpl', nat, - permission=0o755) + render(nftables_nat_config, 'firewall/nftables-nat.tmpl', nat) + + # dry-run newly generated configuration + tmp = run(f'nft -c -f {nftables_nat_config}') + if tmp > 0: + if os.path.exists(nftables_ct_file): + os.unlink(nftables_ct_file) + raise ConfigError('Configuration file errors encountered!') + return None def apply(nat): - cmd(f'{nftables_nat_config}') + cmd(f'nft -f {nftables_nat_config}') if os.path.isfile(nftables_nat_config): os.unlink(nftables_nat_config) -- cgit v1.2.3 From cb797395a4df6b0b7a192e9e1f25427f921fac06 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Mon, 10 Jan 2022 11:34:04 -0600 Subject: frr: T4166: move log debug setting to init function for vyos-configd frr.py debugging is set True if the file '/tmp/vyos.frr.debug' exists; this check needs to be called within an init function, as frr.py will have already been loaded by vyos-configd before the /tmp/*.debug files are created by vyos-router, or by call to 'touch'. --- python/vyos/frr.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/python/vyos/frr.py b/python/vyos/frr.py index a8f115d9a..cbba19ab7 100644 --- a/python/vyos/frr.py +++ b/python/vyos/frr.py @@ -73,15 +73,15 @@ from vyos.util import cmd import logging from logging.handlers import SysLogHandler import os +import sys + LOG = logging.getLogger(__name__) +DEBUG = False -DEBUG = os.path.exists('/tmp/vyos.frr.debug') -if DEBUG: - LOG.setLevel(logging.DEBUG) - ch = SysLogHandler(address='/dev/log') - ch2 = logging.StreamHandler() - LOG.addHandler(ch) - LOG.addHandler(ch2) +ch = SysLogHandler(address='/dev/log') +ch2 = logging.StreamHandler(stream=sys.stdout) +LOG.addHandler(ch) +LOG.addHandler(ch2) _frr_daemons = ['zebra', 'bgpd', 'fabricd', 'isisd', 'ospf6d', 'ospfd', 'pbrd', 'pimd', 'ripd', 'ripngd', 'sharpd', 'staticd', 'vrrpd', 'ldpd', @@ -121,6 +121,12 @@ class ConfigSectionNotFound(FrrError): """ pass +def init_debugging(): + global DEBUG + + DEBUG = os.path.exists('/tmp/vyos.frr.debug') + if DEBUG: + LOG.setLevel(logging.DEBUG) def get_configuration(daemon=None, marked=False): """ Get current running FRR configuration @@ -424,6 +430,8 @@ class FRRConfig: Using this overwrites the current loaded config objects and replaces the original loaded config ''' + init_debugging() + self.imported_config = get_configuration(daemon=daemon) if daemon: LOG.debug(f'load_configuration: Configuration loaded from FRR daemon {daemon}') -- cgit v1.2.3 From 142c976ca4b37fbae9ec44487d146b2a8319391c Mon Sep 17 00:00:00 2001 From: Mathew Inkson <627767+imathew@users.noreply.github.com> Date: Tue, 11 Jan 2022 14:23:20 +1100 Subject: containers: T2216: bugfix host networking on image upgrade The bug was partially fixed with this commit: https://github.com/vyos/vyos-1x/commit/358f0b481d8620cad4954e3fe418054b9a8c3ecd The earlier commit introduced a startup retry (up to 10 times) to allow the OS to settle before the container is started. However, it only applies if host networking is NOT used. This change applies the same for containers where host networking is employed. Since the retry portion of the code (written in the earlier commit) is now referenced twice, it has been moved to its own function. --- src/conf_mode/containers.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/conf_mode/containers.py b/src/conf_mode/containers.py index 2e14e0b25..26c50cab6 100755 --- a/src/conf_mode/containers.py +++ b/src/conf_mode/containers.py @@ -298,7 +298,7 @@ def apply(container): f'--memory {memory}m --memory-swap 0 --restart {restart} ' \ f'--name {name} {port} {volume} {env_opt}' if 'allow_host_networks' in container_config: - _cmd(f'{container_base_cmd} --net host {image}') + run(f'{container_base_cmd} --net host {image}') else: for network in container_config['network']: ipparam = '' @@ -306,19 +306,25 @@ def apply(container): address = container_config['network'][network]['address'] ipparam = f'--ip {address}' - counter = 0 - while True: - if counter >= 10: - break - try: - _cmd(f'{container_base_cmd} --net {network} {ipparam} {image}') - break - except: - counter = counter +1 - sleep(0.5) + run(f'{container_base_cmd} --net {network} {ipparam} {image}') return None +def run(container_cmd): + counter = 0 + while True: + if counter >= 10: + break + try: + _cmd(container_cmd) + break + except: + counter = counter +1 + sleep(0.5) + + return None + + if __name__ == '__main__': try: c = get_config() -- cgit v1.2.3 From e89f48269e96f0b558dd3d427c4cc89abd585c3f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 11 Jan 2022 10:26:47 +0100 Subject: policy: T2199: add missing rule constraints --- interface-definitions/policy-route.xml.in | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/interface-definitions/policy-route.xml.in b/interface-definitions/policy-route.xml.in index ed726d1e4..ee456a82b 100644 --- a/interface-definitions/policy-route.xml.in +++ b/interface-definitions/policy-route.xml.in @@ -4,7 +4,7 @@ - IPv6 policy route rule set name + Policy route rule set name for IPv6 201 @@ -12,7 +12,15 @@ #include - Rule number (1-9999) + Policy rule number + + u32:1-999999 + Number of policy rule + + + + + Policy rule number must be between 1 and 999999 @@ -42,7 +50,7 @@ - Policy route rule set name + Policy route rule set name for IPv4 201 @@ -50,7 +58,15 @@ #include - Rule number (1-9999) + Policy rule number + + u32:1-999999 + Number of policy rule + + + + + Policy rule number must be between 1 and 999999 -- cgit v1.2.3 From 54675c2cc9aa9c1315478107cce14e5ba23d865e Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 11 Jan 2022 10:27:13 +0100 Subject: policy: T4170: rename "policy ipv6-route" -> "policy route6" In order to have a consistent looking CLI we should rename this CLI node. There is: * access-list and access-list6 (policy) * prefix-list and prefix-list6 (policy) * route and route6 (static routes) --- data/templates/firewall/nftables-policy.tmpl | 6 ++-- interface-definitions/policy-route.xml.in | 2 +- src/conf_mode/policy-route.py | 4 +-- src/migration-scripts/policy/1-to-2 | 49 ++++++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 6 deletions(-) create mode 100755 src/migration-scripts/policy/1-to-2 diff --git a/data/templates/firewall/nftables-policy.tmpl b/data/templates/firewall/nftables-policy.tmpl index aa6bb6fc1..ecc7e0fbd 100644 --- a/data/templates/firewall/nftables-policy.tmpl +++ b/data/templates/firewall/nftables-policy.tmpl @@ -9,7 +9,7 @@ table ip mangle { type filter hook postrouting priority -150; policy accept; } {% endif %} -{% if route is defined -%} +{% if route is defined and route is not none -%} {% for route_text, conf in route.items() %} chain VYOS_PBR_{{ route_text }} { {% if conf.rule is defined %} @@ -36,8 +36,8 @@ table ip6 mangle { type filter hook postrouting priority -150; policy accept; } {% endif %} -{% if ipv6_route is defined %} -{% for route_text, conf in ipv6_route.items() %} +{% if route6 is defined and route6 is not none %} +{% for route_text, conf in route6.items() %} chain VYOS_PBR6_{{ route_text }} { {% if conf.rule is defined %} {% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not defined %} diff --git a/interface-definitions/policy-route.xml.in b/interface-definitions/policy-route.xml.in index ee456a82b..4ce953b52 100644 --- a/interface-definitions/policy-route.xml.in +++ b/interface-definitions/policy-route.xml.in @@ -2,7 +2,7 @@ - + Policy route rule set name for IPv6 201 diff --git a/src/conf_mode/policy-route.py b/src/conf_mode/policy-route.py index d098be68d..9edab4b47 100755 --- a/src/conf_mode/policy-route.py +++ b/src/conf_mode/policy-route.py @@ -51,7 +51,7 @@ def verify(policy): if not policy: return None - for route in ['route', 'ipv6_route']: + for route in ['route', 'route6']: if route in policy: for name, pol_conf in policy[route].items(): if 'rule' in pol_conf: @@ -98,7 +98,7 @@ def generate(policy): return None def apply_table_marks(policy): - for route in ['route', 'ipv6_route']: + for route in ['route', 'route6']: if route in policy: for name, pol_conf in policy[route].items(): if 'rule' in pol_conf: diff --git a/src/migration-scripts/policy/1-to-2 b/src/migration-scripts/policy/1-to-2 new file mode 100755 index 000000000..3e46227de --- /dev/null +++ b/src/migration-scripts/policy/1-to-2 @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 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 . + +# T4170: rename "policy ipv6-route" to "policy route6" to match common +# IPv4/IPv6 schema + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree + +if (len(argv) < 1): + print("Must specify file name!") + exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +base = ['policy', 'ipv6-route'] +config = ConfigTree(config_file) + +if not config.exists(base): + # Nothing to do + exit(0) + +config.rename(base, 'route6') +config.set_tag(['policy', 'route6']) + +try: + with open(file_name, 'w') as f: + f.write(config.to_string()) +except OSError as e: + print(f'Failed to save the modified config: {e}') + exit(1) -- cgit v1.2.3 From d5775339f9d1ee33c04f5d923684cd254b400364 Mon Sep 17 00:00:00 2001 From: erkin Date: Tue, 11 Jan 2022 12:02:32 +0300 Subject: remote: T3950: Gracefully handle chained exceptions --- python/vyos/remote.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/python/vyos/remote.py b/python/vyos/remote.py index aa62ac60d..66044fa52 100644 --- a/python/vyos/remote.py +++ b/python/vyos/remote.py @@ -83,8 +83,7 @@ def check_storage(path, size): directory = path if os.path.isdir(path) else (os.path.dirname(os.path.expanduser(path)) or os.getcwd()) # `size` can be None or 0 to indicate unknown size. if not size: - print_error('Warning: Cannot determine size of remote file.') - print_error('Bravely continuing regardless.') + print_error('Warning: Cannot determine size of remote file. Bravely continuing regardless.') return if size < 1024 * 1024: @@ -227,7 +226,7 @@ class HttpC: r.raise_for_status() # If the request got redirected, keep the last URL we ended up with. final_urlstring = r.url - if r.history: + if r.history and self.progressbar: print_error('Redirecting to ' + final_urlstring) # Check for the prospective file size. try: @@ -317,11 +316,12 @@ def friendly_download(local_path, urlstring, source_host='', source_port=0): sys.exit(1) except: import traceback + print_error(f'Failed to download {urlstring}.') # There are a myriad different reasons a download could fail. # SSH errors, FTP errors, I/O errors, HTTP errors (403, 404...) # We omit the scary stack trace but print the error nevertheless. - print_error(f'Failed to download {urlstring}.') - traceback.print_exception(*sys.exc_info()[:2], None) + exc_type, exc_value, exc_traceback = sys.exc_info() + traceback.print_exception(exc_type, exc_value, None, 0, None, False) sys.exit(1) else: print_error('Download complete.') -- cgit v1.2.3 From f16525175deb69ae3b9193573550992b4d5fd951 Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Mon, 10 Jan 2022 23:14:28 +0100 Subject: firewall: policy: T4159: T4164: Fix empty firewall groups, create separate file for group definitions. --- data/templates/firewall/nftables-defines.tmpl | 27 +++++++++++++++++++ data/templates/firewall/nftables-policy.tmpl | 2 ++ data/templates/firewall/nftables.tmpl | 38 +-------------------------- src/conf_mode/firewall.py | 2 ++ 4 files changed, 32 insertions(+), 37 deletions(-) create mode 100644 data/templates/firewall/nftables-defines.tmpl diff --git a/data/templates/firewall/nftables-defines.tmpl b/data/templates/firewall/nftables-defines.tmpl new file mode 100644 index 000000000..3578a9dc5 --- /dev/null +++ b/data/templates/firewall/nftables-defines.tmpl @@ -0,0 +1,27 @@ +{% if group is defined %} +{% if group.address_group is defined %} +{% for group_name, group_conf in group.address_group.items() %} +define A_{{ group_name }} = { {{ group_conf.address | join(",") }} } +{% endfor %} +{% endif %} +{% if group.ipv6_address_group is defined %} +{% for group_name, group_conf in group.ipv6_address_group.items() %} +define A6_{{ group_name }} = { {{ group_conf.address | join(",") }} } +{% endfor %} +{% endif %} +{% if group.network_group is defined %} +{% for group_name, group_conf in group.network_group.items() %} +define N_{{ group_name }} = { {{ group_conf.network | join(",") }} } +{% endfor %} +{% endif %} +{% if group.ipv6_network_group is defined %} +{% for group_name, group_conf in group.ipv6_network_group.items() %} +define N6_{{ group_name }} = { {{ group_conf.network | join(",") }} } +{% endfor %} +{% endif %} +{% if group.port_group is defined %} +{% for group_name, group_conf in group.port_group.items() %} +define P_{{ group_name }} = { {{ group_conf.port | join(",") }} } +{% endfor %} +{% endif %} +{% endif %} \ No newline at end of file diff --git a/data/templates/firewall/nftables-policy.tmpl b/data/templates/firewall/nftables-policy.tmpl index ecc7e0fbd..668ec7388 100644 --- a/data/templates/firewall/nftables-policy.tmpl +++ b/data/templates/firewall/nftables-policy.tmpl @@ -1,5 +1,7 @@ #!/usr/sbin/nft -f +include "/run/nftables_defines.conf" + table ip mangle { {% if first_install is defined %} chain VYOS_PBR_PREROUTING { diff --git a/data/templates/firewall/nftables.tmpl b/data/templates/firewall/nftables.tmpl index 68e83de64..e8fa4e306 100644 --- a/data/templates/firewall/nftables.tmpl +++ b/data/templates/firewall/nftables.tmpl @@ -6,43 +6,7 @@ {% endfor %} {% endif %} -{% if group is defined %} -{% if group.address_group is defined %} -{% for group_name, group_conf in group.address_group.items() %} -define A_{{ group_name }} = { - {{ group_conf.address | join(",") }} -} -{% endfor %} -{% endif %} -{% if group.ipv6_address_group is defined %} -{% for group_name, group_conf in group.ipv6_address_group.items() %} -define A6_{{ group_name }} = { - {{ group_conf.address | join(",") }} -} -{% endfor %} -{% endif %} -{% if group.network_group is defined %} -{% for group_name, group_conf in group.network_group.items() %} -define N_{{ group_name }} = { - {{ group_conf.network | join(",") }} -} -{% endfor %} -{% endif %} -{% if group.ipv6_network_group is defined %} -{% for group_name, group_conf in group.ipv6_network_group.items() %} -define N6_{{ group_name }} = { - {{ group_conf.network | join(",") }} -} -{% endfor %} -{% endif %} -{% if group.port_group is defined %} -{% for group_name, group_conf in group.port_group.items() %} -define P_{{ group_name }} = { - {{ group_conf.port | join(",") }} -} -{% endfor %} -{% endif %} -{% endif %} +include "/run/nftables_defines.conf" table ip filter { {% if first_install is defined %} diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index 0b4c0854f..06e6a1ed4 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -34,6 +34,7 @@ from vyos import airbag airbag.enable() nftables_conf = '/run/nftables.conf' +nftables_defines_conf = '/run/nftables_defines.conf' sysfs_config = { 'all_ping': {'sysfs': '/proc/sys/net/ipv4/icmp_echo_ignore_all', 'enable': '0', 'disable': '1'}, @@ -236,6 +237,7 @@ def generate(firewall): firewall['cleanup_commands'] = cleanup_commands(firewall) render(nftables_conf, 'firewall/nftables.tmpl', firewall) + render(nftables_defines_conf, 'firewall/nftables-defines.tmpl', firewall) return None def apply_sysfs(firewall): -- cgit v1.2.3 From f97144259335102c3d96b232cbb0af4970120d62 Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Mon, 10 Jan 2022 23:24:50 +0100 Subject: validators: T4144: Add error messages to the majority of IP validators --- src/validators/ip-address | 7 +++++++ src/validators/ip-cidr | 7 +++++++ src/validators/ip-host | 7 +++++++ src/validators/ip-prefix | 7 +++++++ src/validators/ip-protocol | 1 + src/validators/ipv4 | 7 +++++++ src/validators/ipv4-address | 7 +++++++ src/validators/ipv4-host | 7 +++++++ src/validators/ipv4-multicast | 7 +++++++ src/validators/ipv4-prefix | 7 +++++++ src/validators/ipv6 | 7 +++++++ src/validators/ipv6-address | 7 +++++++ src/validators/ipv6-host | 7 +++++++ src/validators/ipv6-multicast | 7 +++++++ src/validators/ipv6-prefix | 7 +++++++ src/validators/ipv6-range | 1 + 16 files changed, 100 insertions(+) diff --git a/src/validators/ip-address b/src/validators/ip-address index 51fb72c85..11d6df09e 100755 --- a/src/validators/ip-address +++ b/src/validators/ip-address @@ -1,3 +1,10 @@ #!/bin/sh ipaddrcheck --is-any-single $1 + +if [ $? -gt 0 ]; then + echo "Error: $1 is not a valid IP address" + exit 1 +fi + +exit 0 \ No newline at end of file diff --git a/src/validators/ip-cidr b/src/validators/ip-cidr index 987bf84ca..60d2ac295 100755 --- a/src/validators/ip-cidr +++ b/src/validators/ip-cidr @@ -1,3 +1,10 @@ #!/bin/sh ipaddrcheck --is-any-cidr $1 + +if [ $? -gt 0 ]; then + echo "Error: $1 is not a valid IP CIDR" + exit 1 +fi + +exit 0 \ No newline at end of file diff --git a/src/validators/ip-host b/src/validators/ip-host index f2906e8cf..77c578fa2 100755 --- a/src/validators/ip-host +++ b/src/validators/ip-host @@ -1,3 +1,10 @@ #!/bin/sh ipaddrcheck --is-any-host $1 + +if [ $? -gt 0 ]; then + echo "Error: $1 is not a valid IP host" + exit 1 +fi + +exit 0 \ No newline at end of file diff --git a/src/validators/ip-prefix b/src/validators/ip-prefix index e58aad395..e5a64fea8 100755 --- a/src/validators/ip-prefix +++ b/src/validators/ip-prefix @@ -1,3 +1,10 @@ #!/bin/sh ipaddrcheck --is-any-net $1 + +if [ $? -gt 0 ]; then + echo "Error: $1 is not a valid IP prefix" + exit 1 +fi + +exit 0 \ No newline at end of file diff --git a/src/validators/ip-protocol b/src/validators/ip-protocol index 7898fa6d0..c4c882502 100755 --- a/src/validators/ip-protocol +++ b/src/validators/ip-protocol @@ -38,4 +38,5 @@ if __name__ == '__main__': if re.match(pattern, input): exit(0) + print(f'Error: {input} is not a valid IP protocol') exit(1) diff --git a/src/validators/ipv4 b/src/validators/ipv4 index 53face090..8676d5800 100755 --- a/src/validators/ipv4 +++ b/src/validators/ipv4 @@ -1,3 +1,10 @@ #!/bin/sh ipaddrcheck --is-ipv4 $1 + +if [ $? -gt 0 ]; then + echo "Error: $1 is not IPv4" + exit 1 +fi + +exit 0 \ No newline at end of file diff --git a/src/validators/ipv4-address b/src/validators/ipv4-address index 872a7645a..058db088b 100755 --- a/src/validators/ipv4-address +++ b/src/validators/ipv4-address @@ -1,3 +1,10 @@ #!/bin/sh ipaddrcheck --is-ipv4-single $1 + +if [ $? -gt 0 ]; then + echo "Error: $1 is not a valid IPv4 address" + exit 1 +fi + +exit 0 \ No newline at end of file diff --git a/src/validators/ipv4-host b/src/validators/ipv4-host index f42feffa4..74b8c36a7 100755 --- a/src/validators/ipv4-host +++ b/src/validators/ipv4-host @@ -1,3 +1,10 @@ #!/bin/sh ipaddrcheck --is-ipv4-host $1 + +if [ $? -gt 0 ]; then + echo "Error: $1 is not a valid IPv4 host" + exit 1 +fi + +exit 0 \ No newline at end of file diff --git a/src/validators/ipv4-multicast b/src/validators/ipv4-multicast index 5465c728d..3f28c51db 100755 --- a/src/validators/ipv4-multicast +++ b/src/validators/ipv4-multicast @@ -1,3 +1,10 @@ #!/bin/sh ipaddrcheck --is-ipv4-multicast $1 && ipaddrcheck --is-ipv4-single $1 + +if [ $? -gt 0 ]; then + echo "Error: $1 is not a valid IPv4 multicast address" + exit 1 +fi + +exit 0 \ No newline at end of file diff --git a/src/validators/ipv4-prefix b/src/validators/ipv4-prefix index 8ec8a2c45..7e1e0e8dd 100755 --- a/src/validators/ipv4-prefix +++ b/src/validators/ipv4-prefix @@ -1,3 +1,10 @@ #!/bin/sh ipaddrcheck --is-ipv4-net $1 + +if [ $? -gt 0 ]; then + echo "Error: $1 is not a valid IPv4 prefix" + exit 1 +fi + +exit 0 \ No newline at end of file diff --git a/src/validators/ipv6 b/src/validators/ipv6 index f18d4a63e..4ae130eb5 100755 --- a/src/validators/ipv6 +++ b/src/validators/ipv6 @@ -1,3 +1,10 @@ #!/bin/sh ipaddrcheck --is-ipv6 $1 + +if [ $? -gt 0 ]; then + echo "Error: $1 is not IPv6" + exit 1 +fi + +exit 0 \ No newline at end of file diff --git a/src/validators/ipv6-address b/src/validators/ipv6-address index e5d68d756..1fca77668 100755 --- a/src/validators/ipv6-address +++ b/src/validators/ipv6-address @@ -1,3 +1,10 @@ #!/bin/sh ipaddrcheck --is-ipv6-single $1 + +if [ $? -gt 0 ]; then + echo "Error: $1 is not a valid IPv6 address" + exit 1 +fi + +exit 0 \ No newline at end of file diff --git a/src/validators/ipv6-host b/src/validators/ipv6-host index f7a745077..7085809a9 100755 --- a/src/validators/ipv6-host +++ b/src/validators/ipv6-host @@ -1,3 +1,10 @@ #!/bin/sh ipaddrcheck --is-ipv6-host $1 + +if [ $? -gt 0 ]; then + echo "Error: $1 is not a valid IPv6 host" + exit 1 +fi + +exit 0 \ No newline at end of file diff --git a/src/validators/ipv6-multicast b/src/validators/ipv6-multicast index 5afc437e5..5aa7d734a 100755 --- a/src/validators/ipv6-multicast +++ b/src/validators/ipv6-multicast @@ -1,3 +1,10 @@ #!/bin/sh ipaddrcheck --is-ipv6-multicast $1 && ipaddrcheck --is-ipv6-single $1 + +if [ $? -gt 0 ]; then + echo "Error: $1 is not a valid IPv6 multicast address" + exit 1 +fi + +exit 0 \ No newline at end of file diff --git a/src/validators/ipv6-prefix b/src/validators/ipv6-prefix index e43616350..890dda723 100755 --- a/src/validators/ipv6-prefix +++ b/src/validators/ipv6-prefix @@ -1,3 +1,10 @@ #!/bin/sh ipaddrcheck --is-ipv6-net $1 + +if [ $? -gt 0 ]; then + echo "Error: $1 is not a valid IPv6 prefix" + exit 1 +fi + +exit 0 \ No newline at end of file diff --git a/src/validators/ipv6-range b/src/validators/ipv6-range index 033b6461b..a3c401281 100755 --- a/src/validators/ipv6-range +++ b/src/validators/ipv6-range @@ -11,6 +11,7 @@ if __name__ == '__main__': if re.search('([a-f0-9:]+:+)+[a-f0-9]+-([a-f0-9:]+:+)+[a-f0-9]+', ipv6_range): for tmp in ipv6_range.split('-'): if not is_ipv6(tmp): + print(f'Error: {ipv6_range} is not a valid IPv6 range') sys.exit(1) sys.exit(0) -- cgit v1.2.3 From 6e23345a693c5ccaaae7694dd60a91315a911631 Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Mon, 10 Jan 2022 23:26:39 +0100 Subject: firewall: T2199: Add ipv6-range support to IPv6 address group --- interface-definitions/firewall.xml.in | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in index 07a36093f..f428efe58 100644 --- a/interface-definitions/firewall.xml.in +++ b/interface-definitions/firewall.xml.in @@ -109,8 +109,13 @@ ipv6 IPv6 address to match + + ipv6range + IPv6 range to match (e.g. 2002::1-2002::ff) + + -- cgit v1.2.3 From 5334ca6fc758c3e9c86dfcb3d4ace5d54ab8878a Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Mon, 10 Jan 2022 23:50:25 +0100 Subject: firewall: op-mode: T4131: Display `show firewall group` reference and member items sorted and one per line --- src/op_mode/firewall.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/op_mode/firewall.py b/src/op_mode/firewall.py index cf70890a6..030a9b19a 100755 --- a/src/op_mode/firewall.py +++ b/src/op_mode/firewall.py @@ -15,6 +15,7 @@ # along with this program. If not, see . import argparse +import ipaddress import json import re import tabulate @@ -266,13 +267,15 @@ def show_firewall_group(name=None): continue references = find_references(group_type, group_name) - row = [group_name, group_type, ', '.join(references)] + row = [group_name, group_type, '\n'.join(references) or 'N/A'] if 'address' in group_conf: - row.append(", ".join(group_conf['address'])) + row.append("\n".join(sorted(group_conf['address'], key=ipaddress.ip_address))) elif 'network' in group_conf: - row.append(", ".join(group_conf['network'])) + row.append("\n".join(sorted(group_conf['network'], key=ipaddress.ip_network))) elif 'port' in group_conf: - row.append(", ".join(group_conf['port'])) + row.append("\n".join(sorted(group_conf['port']))) + else: + row.append('N/A') rows.append(row) if rows: @@ -302,7 +305,7 @@ def show_summary(): for name, name_conf in firewall['ipv6_name'].items(): description = name_conf.get('description', '') interfaces = ", ".join(name_conf['interface']) - v6_out.append([name, description, interfaces]) + v6_out.append([name, description, interfaces or 'N/A']) if v6_out: print('\nIPv6 name:\n') -- cgit v1.2.3 From 1292a69a5fe9fe931676a475e011dece578233df Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Tue, 11 Jan 2022 00:20:36 +0100 Subject: firewall: policy: T2199: Reload policy route script if `firewall group` node is changed --- src/conf_mode/firewall.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index 06e6a1ed4..bca5afb2e 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -22,6 +22,7 @@ from sys import exit from vyos.config import Config from vyos.configdict import dict_merge +from vyos.configdict import node_changed from vyos.configdiff import get_config_diff, Diff from vyos.template import render from vyos.util import cmd @@ -33,6 +34,8 @@ from vyos import ConfigError from vyos import airbag airbag.enable() +policy_route_conf_script = '/usr/libexec/vyos/conf_mode/policy-route.py' + nftables_conf = '/run/nftables.conf' nftables_defines_conf = '/run/nftables_defines.conf' @@ -111,6 +114,7 @@ def get_config(config=None): default_values = defaults(base) firewall = dict_merge(default_values, firewall) + firewall['policy_resync'] = bool('group' in firewall or node_changed(conf, base + ['group'])) firewall['interfaces'] = get_firewall_interfaces(conf) if 'config_trap' in firewall and firewall['config_trap'] == 'enable': @@ -119,6 +123,7 @@ def get_config(config=None): firewall['trap_targets'] = conf.get_config_dict(['service', 'snmp', 'trap-target'], key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) + return firewall def verify_rule(firewall, rule_conf, ipv6): @@ -301,6 +306,12 @@ def state_policy_rule_exists(): search_str = cmd(f'nft list chain ip filter VYOS_FW_FORWARD') return 'VYOS_STATE_POLICY' in search_str +def resync_policy_route(): + # Update policy route as firewall groups were updated + tmp = run(policy_route_conf_script) + if tmp > 0: + print('Warning: Failed to re-apply policy route configuration') + def apply(firewall): if 'first_install' in firewall: run('nfct helper add rpc inet tcp') @@ -320,6 +331,9 @@ def apply(firewall): apply_sysfs(firewall) + if firewall['policy_resync']: + resync_policy_route() + post_apply_trap(firewall) return None -- cgit v1.2.3 From e389729f4de84ce3f32e1a0cdb471c919d7d7807 Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Tue, 11 Jan 2022 00:28:37 +0100 Subject: firewall: T4159: Add warning when an empty group is applied to a rule --- src/conf_mode/firewall.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index bca5afb2e..7b491a325 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -153,17 +153,16 @@ def verify_rule(firewall, rule_conf, ipv6): for group in valid_groups: if group in side_conf['group']: group_name = side_conf['group'][group] - fw_group = f'ipv6_{group}' if ipv6 and group in ['address_group', 'network_group'] else group + error_group = fw_group.replace("_", "-") + group_obj = dict_search_args(firewall, 'group', fw_group, group_name) - if not dict_search_args(firewall, 'group', fw_group): - error_group = fw_group.replace("_", "-") - raise ConfigError(f'Group defined in rule but {error_group} is not configured') - - if group_name not in firewall['group'][fw_group]: - error_group = group.replace("_", "-") + if group_obj is None: raise ConfigError(f'Invalid {error_group} "{group_name}" on firewall rule') + if not group_obj: + print(f'WARNING: {error_group} "{group_name}" has no members') + if 'port' in side_conf or dict_search_args(side_conf, 'group', 'port_group'): if 'protocol' not in rule_conf: raise ConfigError('Protocol must be defined if specifying a port or port-group') -- cgit v1.2.3 From 29efbf51efea559773f61703f11a77a8aee6de36 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 11 Jan 2022 12:01:23 +0100 Subject: migrator: interfaces: T4171: bugfix ConfigTreeError Migrating 1.2.8 -> 1.4-rolling-202201110811 vyos-router[970]: Waiting for NICs to settle down: settled in 0sec.. vyos-router[1085]: Started watchfrr. vyos-router[970]: Mounting VyOS Config...done. vyos-router[970]: Starting VyOS router: migrate vyos-router[1490]: Traceback (most recent call last): vyos-router[1490]: File "/opt/vyatta/etc/config-migrate/migrate/interfaces/5-to-6", line 112, in vyos-router[1490]: for if_type in config.list_nodes(['interfaces']): vyos-router[1490]: File "/usr/lib/python3/dist-packages/vyos/configtree.py", line 236, in list_nodes vyos-router[1490]: raise ConfigTreeError("Path [{}] doesn't exist".format(path_str)) vyos-router[1490]: vyos.configtree.ConfigTreeError: Path [b'interfaces'] doesn't exist vyos-router[1455]: Migration script error: /opt/vyatta/etc/config-migrate/migrate/interfaces/5-to-6: Command '['/opt/vyatta/etc/config-migrate/migrate/interfaces/5-to-6', '/opt/vyatta/etc/config/config.boot']' returned non-zero exit status 1.. vyos-router[970]: configure. vyos-config[979]: Configuration success --- src/migration-scripts/interfaces/5-to-6 | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/migration-scripts/interfaces/5-to-6 b/src/migration-scripts/interfaces/5-to-6 index ae79c1d1b..dc8d9554f 100755 --- a/src/migration-scripts/interfaces/5-to-6 +++ b/src/migration-scripts/interfaces/5-to-6 @@ -107,10 +107,15 @@ if __name__ == '__main__': config_file = f.read() config = ConfigTree(config_file) + base = ['interfaces'] + + if not config.exists(base): + # Nothing to do + exit(0) # list all individual interface types like dummy, ethernet and so on - for if_type in config.list_nodes(['interfaces']): - base_if_type = ['interfaces', if_type] + for if_type in config.list_nodes(base): + base_if_type = base + [if_type] # for every individual interface we need to check if there is an # ipv6 ra configured ... and also for every VIF (VLAN) interface -- cgit v1.2.3 From 1b8f421727eed9849b5a179e0415df3fb06f0297 Mon Sep 17 00:00:00 2001 From: Nicolas Fort Date: Tue, 11 Jan 2022 12:20:32 +0000 Subject: ike-group: T4162: Correct helper description for ikev2-reauth --- interface-definitions/vpn_ipsec.xml.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface-definitions/vpn_ipsec.xml.in b/interface-definitions/vpn_ipsec.xml.in index 0c2205410..afa3d52a0 100644 --- a/interface-definitions/vpn_ipsec.xml.in +++ b/interface-definitions/vpn_ipsec.xml.in @@ -286,7 +286,7 @@ - ikev2-reauth_help + Re-authentication of the remote peer during an IKE re-key. IKEv2 option only yes no -- cgit v1.2.3 From 6cf5767524b8519f86981943ab71ff288bf77d67 Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Tue, 11 Jan 2022 01:10:59 +0100 Subject: policy: T2199: Refactor policy route script for better error handling * Migrates all policy route references from `ipv6-route` to `route6` * Update test config `dialup-router-medium-vpn` to test migration of `ipv6-route` to `route6` --- data/templates/firewall/nftables-policy.tmpl | 6 + .../include/interface/interface-policy-vif-c.xml.i | 4 +- .../include/interface/interface-policy-vif.xml.i | 4 +- .../include/interface/interface-policy.xml.i | 4 +- smoketest/configs/dialup-router-medium-vpn | 24 +++ smoketest/scripts/cli/test_policy_route.py | 28 +++- src/conf_mode/policy-route-interface.py | 8 +- src/conf_mode/policy-route.py | 169 +++++++++++++++------ src/migration-scripts/policy/1-to-2 | 18 +++ src/op_mode/policy_route.py | 10 +- 10 files changed, 213 insertions(+), 62 deletions(-) diff --git a/data/templates/firewall/nftables-policy.tmpl b/data/templates/firewall/nftables-policy.tmpl index 668ec7388..484b6f203 100644 --- a/data/templates/firewall/nftables-policy.tmpl +++ b/data/templates/firewall/nftables-policy.tmpl @@ -1,5 +1,11 @@ #!/usr/sbin/nft -f +{% if cleanup_commands is defined %} +{% for command in cleanup_commands %} +{{ command }} +{% endfor %} +{% endif %} + include "/run/nftables_defines.conf" table ip mangle { diff --git a/interface-definitions/include/interface/interface-policy-vif-c.xml.i b/interface-definitions/include/interface/interface-policy-vif-c.xml.i index 5dad6422b..866fcd5c0 100644 --- a/interface-definitions/include/interface/interface-policy-vif-c.xml.i +++ b/interface-definitions/include/interface/interface-policy-vif-c.xml.i @@ -13,11 +13,11 @@ - + IPv6 policy route ruleset for interface - policy ipv6-route + policy route6 diff --git a/interface-definitions/include/interface/interface-policy-vif.xml.i b/interface-definitions/include/interface/interface-policy-vif.xml.i index 5ee80ae13..83510fe59 100644 --- a/interface-definitions/include/interface/interface-policy-vif.xml.i +++ b/interface-definitions/include/interface/interface-policy-vif.xml.i @@ -13,11 +13,11 @@ - + IPv6 policy route ruleset for interface - policy ipv6-route + policy route6 diff --git a/interface-definitions/include/interface/interface-policy.xml.i b/interface-definitions/include/interface/interface-policy.xml.i index 06f025af1..42a8fd009 100644 --- a/interface-definitions/include/interface/interface-policy.xml.i +++ b/interface-definitions/include/interface/interface-policy.xml.i @@ -13,11 +13,11 @@ - + IPv6 policy route ruleset for interface - policy ipv6-route + policy route6 diff --git a/smoketest/configs/dialup-router-medium-vpn b/smoketest/configs/dialup-router-medium-vpn index af7c075e4..7ca540b66 100644 --- a/smoketest/configs/dialup-router-medium-vpn +++ b/smoketest/configs/dialup-router-medium-vpn @@ -83,6 +83,7 @@ interfaces { } policy { route LAN-POLICY-BASED-ROUTING + ipv6-route LAN6-POLICY-BASED-ROUTING } smp-affinity auto speed auto @@ -383,6 +384,29 @@ nat { } } policy { + ipv6-route LAN6-POLICY-BASED-ROUTING { + rule 10 { + destination { + } + disable + set { + table 10 + } + source { + address 2002::1 + } + } + rule 20 { + destination { + } + set { + table 100 + } + source { + address 2008::f + } + } + } prefix-list user2-routes { rule 1 { action permit diff --git a/smoketest/scripts/cli/test_policy_route.py b/smoketest/scripts/cli/test_policy_route.py index 70a234187..4463a2255 100755 --- a/smoketest/scripts/cli/test_policy_route.py +++ b/smoketest/scripts/cli/test_policy_route.py @@ -31,8 +31,9 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): def tearDown(self): self.cli_delete(['interfaces', 'ethernet', 'eth0']) + self.cli_delete(['protocols', 'static']) self.cli_delete(['policy', 'route']) - self.cli_delete(['policy', 'ipv6-route']) + self.cli_delete(['policy', 'route6']) self.cli_commit() def test_pbr_mark(self): @@ -65,13 +66,19 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'protocol', 'tcp_udp']) self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'destination', 'port', '8888']) self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'set', 'table', table_id]) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '1', 'protocol', 'tcp_udp']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '1', 'destination', 'port', '8888']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '1', 'set', 'table', table_id]) self.cli_set(['interfaces', 'ethernet', 'eth0', 'policy', 'route', 'smoketest']) + self.cli_set(['interfaces', 'ethernet', 'eth0', 'policy', 'route6', 'smoketest6']) self.cli_commit() mark_hex = "{0:#010x}".format(table_mark_offset - int(table_id)) + # IPv4 + nftables_search = [ ['iifname "eth0"', 'jump VYOS_PBR_smoketest'], ['meta l4proto { tcp, udp }', 'th dport { 8888 }', 'meta mark set ' + mark_hex] @@ -87,6 +94,25 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): break self.assertTrue(matched) + # IPv6 + + nftables6_search = [ + ['iifname "eth0"', 'jump VYOS_PBR6_smoketest'], + ['meta l4proto { tcp, udp }', 'th dport { 8888 }', 'meta mark set ' + mark_hex] + ] + + nftables6_output = cmd('sudo nft list table ip6 mangle') + + for search in nftables6_search: + matched = False + for line in nftables6_output.split("\n"): + if all(item in line for item in search): + matched = True + break + self.assertTrue(matched) + + # IP rule fwmark -> table + ip_rule_search = [ ['fwmark ' + hex(table_mark_offset - int(table_id)), 'lookup ' + table_id] ] diff --git a/src/conf_mode/policy-route-interface.py b/src/conf_mode/policy-route-interface.py index e81135a74..1108aebe6 100755 --- a/src/conf_mode/policy-route-interface.py +++ b/src/conf_mode/policy-route-interface.py @@ -52,7 +52,7 @@ def verify(if_policy): if not if_policy: return None - for route in ['route', 'ipv6_route']: + for route in ['route', 'route6']: if route in if_policy: if route not in if_policy['policy']: raise ConfigError('Policy route not configured') @@ -71,7 +71,7 @@ def cleanup_rule(table, chain, ifname, new_name=None): results = cmd(f'nft -a list chain {table} {chain}').split("\n") retval = None for line in results: - if f'oifname "{ifname}"' in line: + if f'ifname "{ifname}"' in line: if new_name and f'jump {new_name}' in line: # new_name is used to clear rules for any previously referenced chains # returns true when rule exists and doesn't need to be created @@ -98,8 +98,8 @@ def apply(if_policy): else: cleanup_rule('ip mangle', route_chain, ifname) - if 'ipv6_route' in if_policy: - name = 'VYOS_PBR6_' + if_policy['ipv6_route'] + if 'route6' in if_policy: + name = 'VYOS_PBR6_' + if_policy['route6'] rule_exists = cleanup_rule('ip6 mangle', ipv6_route_chain, ifname, name) if not rule_exists: diff --git a/src/conf_mode/policy-route.py b/src/conf_mode/policy-route.py index 9edab4b47..c5904309f 100755 --- a/src/conf_mode/policy-route.py +++ b/src/conf_mode/policy-route.py @@ -31,6 +31,35 @@ airbag.enable() mark_offset = 0x7FFFFFFF nftables_conf = '/run/nftables_policy.conf' +preserve_chains = [ + 'VYOS_PBR_PREROUTING', + 'VYOS_PBR_POSTROUTING', + 'VYOS_PBR6_PREROUTING', + 'VYOS_PBR6_POSTROUTING' +] + +valid_groups = [ + 'address_group', + 'network_group', + 'port_group' +] + +def get_policy_interfaces(conf): + out = {} + interfaces = conf.get_config_dict(['interfaces'], key_mangling=('-', '_'), get_first_key=True, + no_tag_node_value_mangle=True) + def find_interfaces(iftype_conf, output={}, prefix=''): + for ifname, if_conf in iftype_conf.items(): + if 'policy' in if_conf: + output[prefix + ifname] = if_conf['policy'] + for vif in ['vif', 'vif_s', 'vif_c']: + if vif in if_conf: + output.update(find_interfaces(if_conf[vif], output, f'{prefix}{ifname}.')) + return output + for iftype, iftype_conf in interfaces.items(): + out.update(find_interfaces(iftype_conf)) + return out + def get_config(config=None): if config: conf = config @@ -38,61 +67,117 @@ def get_config(config=None): conf = Config() base = ['policy'] - if not conf.exists(base + ['route']) and not conf.exists(base + ['ipv6-route']): - return None - policy = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) + policy['firewall_group'] = conf.get_config_dict(['firewall', 'group'], key_mangling=('-', '_'), get_first_key=True, + no_tag_node_value_mangle=True) + policy['interfaces'] = get_policy_interfaces(conf) + return policy -def verify(policy): - # bail out early - looks like removal from running config - if not policy: - return None +def verify_rule(policy, rule_conf, ipv6): + icmp = 'icmp' if not ipv6 else 'icmpv6' + if icmp in rule_conf: + icmp_defined = False + if 'type_name' in rule_conf[icmp]: + icmp_defined = True + if 'code' in rule_conf[icmp] or 'type' in rule_conf[icmp]: + raise ConfigError(f'{name} rule {rule_id}: Cannot use ICMP type/code with ICMP type-name') + if 'code' in rule_conf[icmp]: + icmp_defined = True + if 'type' not in rule_conf[icmp]: + raise ConfigError(f'{name} rule {rule_id}: ICMP code can only be defined if ICMP type is defined') + if 'type' in rule_conf[icmp]: + icmp_defined = True + + if icmp_defined and 'protocol' not in rule_conf or rule_conf['protocol'] != icmp: + raise ConfigError(f'{name} rule {rule_id}: ICMP type/code or type-name can only be defined if protocol is ICMP') + if 'set' in rule_conf: + if 'tcp_mss' in rule_conf['set']: + tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags') + if not tcp_flags or 'SYN' not in tcp_flags.split(","): + raise ConfigError(f'{name} rule {rule_id}: TCP SYN flag must be set to modify TCP-MSS') + if 'tcp' in rule_conf: + if 'flags' in rule_conf['tcp']: + if 'protocol' not in rule_conf or rule_conf['protocol'] != 'tcp': + raise ConfigError(f'{name} rule {rule_id}: TCP flags can only be set if protocol is set to TCP') + + for side in ['destination', 'source']: + if side in rule_conf: + side_conf = rule_conf[side] + + if 'group' in side_conf: + if {'address_group', 'network_group'} <= set(side_conf['group']): + raise ConfigError('Only one address-group or network-group can be specified') + + for group in valid_groups: + if group in side_conf['group']: + group_name = side_conf['group'][group] + fw_group = f'ipv6_{group}' if ipv6 and group in ['address_group', 'network_group'] else group + error_group = fw_group.replace("_", "-") + group_obj = dict_search_args(policy['firewall_group'], fw_group, group_name) + + if group_obj is None: + raise ConfigError(f'Invalid {error_group} "{group_name}" on policy route rule') + + if not group_obj: + print(f'WARNING: {error_group} "{group_name}" has no members') + + if 'port' in side_conf or dict_search_args(side_conf, 'group', 'port_group'): + if 'protocol' not in rule_conf: + raise ConfigError('Protocol must be defined if specifying a port or port-group') + + if rule_conf['protocol'] not in ['tcp', 'udp', 'tcp_udp']: + raise ConfigError('Protocol must be tcp, udp, or tcp_udp when specifying a port or port-group') +def verify(policy): for route in ['route', 'route6']: + ipv6 = route == 'route6' if route in policy: for name, pol_conf in policy[route].items(): if 'rule' in pol_conf: - for rule_id, rule_conf in pol_conf.items(): - icmp = 'icmp' if route == 'route' else 'icmpv6' - if icmp in rule_conf: - icmp_defined = False - if 'type_name' in rule_conf[icmp]: - icmp_defined = True - if 'code' in rule_conf[icmp] or 'type' in rule_conf[icmp]: - raise ConfigError(f'{name} rule {rule_id}: Cannot use ICMP type/code with ICMP type-name') - if 'code' in rule_conf[icmp]: - icmp_defined = True - if 'type' not in rule_conf[icmp]: - raise ConfigError(f'{name} rule {rule_id}: ICMP code can only be defined if ICMP type is defined') - if 'type' in rule_conf[icmp]: - icmp_defined = True - - if icmp_defined and 'protocol' not in rule_conf or rule_conf['protocol'] != icmp: - raise ConfigError(f'{name} rule {rule_id}: ICMP type/code or type-name can only be defined if protocol is ICMP') - if 'set' in rule_conf: - if 'tcp_mss' in rule_conf['set']: - tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags') - if not tcp_flags or 'SYN' not in tcp_flags.split(","): - raise ConfigError(f'{name} rule {rule_id}: TCP SYN flag must be set to modify TCP-MSS') - if 'tcp' in rule_conf: - if 'flags' in rule_conf['tcp']: - if 'protocol' not in rule_conf or rule_conf['protocol'] != 'tcp': - raise ConfigError(f'{name} rule {rule_id}: TCP flags can only be set if protocol is set to TCP') + for rule_id, rule_conf in pol_conf['rule'].items(): + verify_rule(policy, rule_conf, ipv6) + + for ifname, if_policy in policy['interfaces'].items(): + name = dict_search_args(if_policy, 'route') + ipv6_name = dict_search_args(if_policy, 'route6') + if name and not dict_search_args(policy, 'route', name): + raise ConfigError(f'Policy route "{name}" is still referenced on interface {ifname}') + + if ipv6_name and not dict_search_args(policy, 'route6', ipv6_name): + raise ConfigError(f'Policy route6 "{ipv6_name}" is still referenced on interface {ifname}') return None -def generate(policy): - if not policy: - if os.path.exists(nftables_conf): - os.unlink(nftables_conf) - return None +def cleanup_commands(policy): + commands = [] + for table in ['ip mangle', 'ip6 mangle']: + json_str = cmd(f'nft -j list table {table}') + obj = loads(json_str) + if 'nftables' not in obj: + continue + for item in obj['nftables']: + if 'chain' in item: + chain = item['chain']['name'] + if not chain.startswith("VYOS_PBR"): + continue + if chain not in preserve_chains: + if table == 'ip mangle' and dict_search_args(policy, 'route', chain.replace("VYOS_PBR_", "", 1)): + commands.append(f'flush chain {table} {chain}') + elif table == 'ip6 mangle' and dict_search_args(policy, 'route6', chain.replace("VYOS_PBR6_", "", 1)): + commands.append(f'flush chain {table} {chain}') + else: + commands.append(f'delete chain {table} {chain}') + return commands +def generate(policy): if not os.path.exists(nftables_conf): policy['first_install'] = True + else: + policy['cleanup_commands'] = cleanup_commands(policy) render(nftables_conf, 'firewall/nftables-policy.tmpl', policy) return None @@ -124,14 +209,6 @@ def cleanup_table_marks(): cmd(f'ip rule del fwmark {fwmark} table {table}') def apply(policy): - if not policy or 'first_install' not in policy: - run(f'nft flush table ip mangle') - run(f'nft flush table ip6 mangle') - - if not policy: - cleanup_table_marks() - return None - install_result = run(f'nft -f {nftables_conf}') if install_result == 1: raise ConfigError('Failed to apply policy based routing') diff --git a/src/migration-scripts/policy/1-to-2 b/src/migration-scripts/policy/1-to-2 index 3e46227de..7ffceef22 100755 --- a/src/migration-scripts/policy/1-to-2 +++ b/src/migration-scripts/policy/1-to-2 @@ -41,6 +41,24 @@ if not config.exists(base): config.rename(base, 'route6') config.set_tag(['policy', 'route6']) +if config.exists(['interfaces']): + def if_policy_rename(config, path): + if config.exists(path + ['policy', 'ipv6-route']): + config.rename(path + ['policy', 'ipv6-route'], 'route6') + + for if_type in config.list_nodes(['interfaces']): + for ifname in config.list_nodes(['interfaces', if_type]): + if_path = ['interfaces', if_type, ifname] + if_policy_rename(config, if_path) + + for vif_type in ['vif', 'vif-s']: + if config.exists(if_path + [vif_type]): + for vifname in config.list_nodes(if_path + [vif_type]): + if_policy_rename(config, if_path + [vif_type, vifname]) + + if config.exists(if_path + [vif_type, vifname, 'vif-c']): + for vifcname in config.list_nodes(if_path + [vif_type, vifname, 'vif-c']): + if_policy_rename(config, if_path + [vif_type, vifname, 'vif-c', vifcname]) try: with open(file_name, 'w') as f: f.write(config.to_string()) diff --git a/src/op_mode/policy_route.py b/src/op_mode/policy_route.py index e0b4ac514..95a7eadac 100755 --- a/src/op_mode/policy_route.py +++ b/src/op_mode/policy_route.py @@ -26,7 +26,7 @@ def get_policy_interfaces(conf, policy, name=None, ipv6=False): interfaces = conf.get_config_dict(['interfaces'], key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) - routes = ['route', 'ipv6_route'] + routes = ['route', 'route6'] def parse_if(ifname, if_conf): if 'policy' in if_conf: @@ -52,7 +52,7 @@ def get_policy_interfaces(conf, policy, name=None, ipv6=False): def get_config_policy(conf, name=None, ipv6=False, interfaces=True): config_path = ['policy'] if name: - config_path += ['ipv6-route' if ipv6 else 'route', name] + config_path += ['route6' if ipv6 else 'route', name] policy = conf.get_config_dict(config_path, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) @@ -64,7 +64,7 @@ def get_config_policy(conf, name=None, ipv6=False, interfaces=True): for route_name, route_conf in policy['route'].items(): route_conf['interface'] = [] - if 'ipv6_route' in policy: + if 'route6' in policy: for route_name, route_conf in policy['ipv6_route'].items(): route_conf['interface'] = [] @@ -151,8 +151,8 @@ def show_policy(ipv6=False): for route, route_conf in policy['route'].items(): output_policy_route(route, route_conf, ipv6=False) - if ipv6 and 'ipv6_route' in policy: - for route, route_conf in policy['ipv6_route'].items(): + if ipv6 and 'route6' in policy: + for route, route_conf in policy['route6'].items(): output_policy_route(route, route_conf, ipv6=True) def show_policy_name(name, ipv6=False): -- cgit v1.2.3 From 201257fe60afc40d101d162cc08e2878dfa3467b Mon Sep 17 00:00:00 2001 From: zsdc Date: Tue, 4 Jan 2022 15:38:29 +0200 Subject: ipsec: T1925: Fixed `show vpn ipsec sa` output After the a1aaf4fb9c0e4111670ef3dd491796fa35a2311f commit, only single (latest) CHILD_SA for each connection can be displayed in the `show vpn ipsec sa` output. This commit backs the proper behavior for the command and adds a little optimization to the formatter to make it easier. --- src/op_mode/show_ipsec_sa.py | 186 +++++++++++++++++++++---------------------- 1 file changed, 92 insertions(+), 94 deletions(-) diff --git a/src/op_mode/show_ipsec_sa.py b/src/op_mode/show_ipsec_sa.py index e72f0f965..5b8f00dba 100755 --- a/src/op_mode/show_ipsec_sa.py +++ b/src/op_mode/show_ipsec_sa.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019 VyOS maintainers and contributors +# Copyright (C) 2022 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 @@ -14,119 +14,117 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import re -import sys +from re import split as re_split +from sys import exit -import vici -import tabulate -import hurry.filesize +from hurry import filesize +from tabulate import tabulate +from vici import Session as vici_session + +from vyos.util import seconds_to_human -import vyos.util def convert(text): return int(text) if text.isdigit() else text.lower() + def alphanum_key(key): - return [convert(c) for c in re.split('([0-9]+)', str(key))] + return [convert(c) for c in re_split('([0-9]+)', str(key))] -def format_output(conns, sas): + +def format_output(sas): sa_data = [] - for peer, parent_conn in conns.items(): - if peer not in sas: - continue - - parent_sa = sas[peer] - child_sas = parent_sa['child-sas'] - installed_sas = {v['name'].decode(): v for k, v in child_sas.items() if v["state"] == b"INSTALLED"} - - # parent_sa["state"] = IKE state, child_sas["state"] = ESP state - state = 'down' - uptime = 'N/A' - - if parent_sa["state"] == b"ESTABLISHED" and installed_sas: - state = "up" - - remote_host = parent_sa["remote-host"].decode() - remote_id = parent_sa["remote-id"].decode() - - if remote_host == remote_id: - remote_id = "N/A" - - # The counters can only be obtained from the child SAs - for child_conn in parent_conn['children']: - if child_conn not in installed_sas: - data = [child_conn, "down", "N/A", "N/A", "N/A", "N/A", "N/A", "N/A"] - sa_data.append(data) - continue - - isa = installed_sas[child_conn] - csa_name = isa['name'] - csa_name = csa_name.decode() - - bytes_in = hurry.filesize.size(int(isa["bytes-in"].decode())) - bytes_out = hurry.filesize.size(int(isa["bytes-out"].decode())) - bytes_str = "{0}/{1}".format(bytes_in, bytes_out) - - pkts_in = hurry.filesize.size(int(isa["packets-in"].decode()), system=hurry.filesize.si) - pkts_out = hurry.filesize.size(int(isa["packets-out"].decode()), system=hurry.filesize.si) - pkts_str = "{0}/{1}".format(pkts_in, pkts_out) - # Remove B from <1K values - pkts_str = re.sub(r'B', r'', pkts_str) - - uptime = vyos.util.seconds_to_human(isa['install-time'].decode()) - - enc = isa["encr-alg"].decode() - if "encr-keysize" in isa: - key_size = isa["encr-keysize"].decode() - else: - key_size = "" - if "integ-alg" in isa: - hash = isa["integ-alg"].decode() - else: - hash = "" - if "dh-group" in isa: - dh_group = isa["dh-group"].decode() - else: - dh_group = "" - - proposal = enc - if key_size: - proposal = "{0}_{1}".format(proposal, key_size) - if hash: - proposal = "{0}/{1}".format(proposal, hash) - if dh_group: - proposal = "{0}/{1}".format(proposal, dh_group) - - data = [csa_name, state, uptime, bytes_str, pkts_str, remote_host, remote_id, proposal] - sa_data.append(data) + for sa in sas: + for parent_sa in sa.values(): + # create an item for each child-sa + for child_sa in parent_sa.get('child-sas', {}).values(): + # prepare a list for output data + sa_out_name = sa_out_state = sa_out_uptime = sa_out_bytes = sa_out_packets = sa_out_remote_addr = sa_out_remote_id = sa_out_proposal = 'N/A' + + # collect raw data + sa_name = child_sa.get('name') + sa_state = child_sa.get('state') + sa_uptime = child_sa.get('install-time') + sa_bytes_in = child_sa.get('bytes-in') + sa_bytes_out = child_sa.get('bytes-out') + sa_packets_in = child_sa.get('packets-in') + sa_packets_out = child_sa.get('packets-out') + sa_remote_addr = parent_sa.get('remote-host') + sa_remote_id = parent_sa.get('remote-id') + sa_proposal_encr_alg = child_sa.get('encr-alg') + sa_proposal_integ_alg = child_sa.get('integ-alg') + sa_proposal_encr_keysize = child_sa.get('encr-keysize') + sa_proposal_dh_group = child_sa.get('dh-group') + + # format data to display + if sa_name: + sa_out_name = sa_name.decode() + if sa_state: + if sa_state == b'INSTALLED': + sa_out_state = 'up' + else: + sa_out_state = 'down' + if sa_uptime: + sa_out_uptime = seconds_to_human(sa_uptime.decode()) + if sa_bytes_in and sa_bytes_out: + bytes_in = filesize.size(int(sa_bytes_in.decode())) + bytes_out = filesize.size(int(sa_bytes_out.decode())) + sa_out_bytes = f'{bytes_in}/{bytes_out}' + if sa_packets_in and sa_packets_out: + packets_in = filesize.size(int(sa_packets_in.decode()), + system=filesize.si) + packets_out = filesize.size(int(sa_packets_out.decode()), + system=filesize.si) + sa_out_packets = f'{packets_in}/{packets_out}' + if sa_remote_addr: + sa_out_remote_addr = sa_remote_addr.decode() + if sa_remote_id: + sa_out_remote_id = sa_remote_id.decode() + # format proposal + if sa_proposal_encr_alg: + sa_out_proposal = sa_proposal_encr_alg.decode() + if sa_proposal_encr_keysize: + sa_proposal_encr_keysize_str = sa_proposal_encr_keysize.decode() + sa_out_proposal = f'{sa_out_proposal}_{sa_proposal_encr_keysize_str}' + if sa_proposal_integ_alg: + sa_proposal_integ_alg_str = sa_proposal_integ_alg.decode() + sa_out_proposal = f'{sa_out_proposal}/{sa_proposal_integ_alg_str}' + if sa_proposal_dh_group: + sa_proposal_dh_group_str = sa_proposal_dh_group.decode() + sa_out_proposal = f'{sa_out_proposal}/{sa_proposal_dh_group_str}' + + # add a new item to output data + sa_data.append([ + sa_out_name, sa_out_state, sa_out_uptime, sa_out_bytes, + sa_out_packets, sa_out_remote_addr, sa_out_remote_id, + sa_out_proposal + ]) + + # return output data return sa_data + if __name__ == '__main__': try: - session = vici.Session() - conns = {} - sas = {} + session = vici_session() + sas = list(session.list_sas()) - for conn in session.list_conns(): - for key in conn: - conns[key] = conn[key] - - for sa in session.list_sas(): - for key in sa: - sas[key] = sa[key] - - headers = ["Connection", "State", "Uptime", "Bytes In/Out", "Packets In/Out", "Remote address", "Remote ID", "Proposal"] - sa_data = format_output(conns, sas) + sa_data = format_output(sas) sa_data = sorted(sa_data, key=alphanum_key) - output = tabulate.tabulate(sa_data, headers) + + headers = [ + "Connection", "State", "Uptime", "Bytes In/Out", "Packets In/Out", + "Remote address", "Remote ID", "Proposal" + ] + output = tabulate(sa_data, headers) print(output) except PermissionError: print("You do not have a permission to connect to the IPsec daemon") - sys.exit(1) + exit(1) except ConnectionRefusedError: print("IPsec is not runing") - sys.exit(1) + exit(1) except Exception as e: print("An error occured: {0}".format(e)) - sys.exit(1) + exit(1) -- cgit v1.2.3 From 30edcba594ebc07412c903a8e7896235cbc3d11f Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Tue, 11 Jan 2022 19:12:00 +0100 Subject: policy: T2199: Update op-mode syntax to `route6` --- op-mode-definitions/policy-route.xml.in | 8 ++++---- src/op_mode/policy_route.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/op-mode-definitions/policy-route.xml.in b/op-mode-definitions/policy-route.xml.in index c998e5487..bd4a61dc9 100644 --- a/op-mode-definitions/policy-route.xml.in +++ b/op-mode-definitions/policy-route.xml.in @@ -84,17 +84,17 @@ Show policy information - + Show IPv6 policy chain sudo ${vyos_op_scripts_dir}/policy_route.py --action show_all --ipv6 - + Show IPv6 policy chains - policy ipv6-route + policy route6 @@ -102,7 +102,7 @@ Show summary of IPv6 policy rules - policy ipv6-route ${COMP_WORDS[4]} rule + policy route6 ${COMP_WORDS[4]} rule sudo ${vyos_op_scripts_dir}/policy_route.py --action show --name $4 --rule $6 --ipv6 diff --git a/src/op_mode/policy_route.py b/src/op_mode/policy_route.py index 95a7eadac..5be40082f 100755 --- a/src/op_mode/policy_route.py +++ b/src/op_mode/policy_route.py @@ -65,7 +65,7 @@ def get_config_policy(conf, name=None, ipv6=False, interfaces=True): route_conf['interface'] = [] if 'route6' in policy: - for route_name, route_conf in policy['ipv6_route'].items(): + for route_name, route_conf in policy['route6'].items(): route_conf['interface'] = [] get_policy_interfaces(conf, policy, name, ipv6) -- cgit v1.2.3 From 4793e2fc0baf09c8ef128147106acb8bb69ba02b Mon Sep 17 00:00:00 2001 From: Bᴇʀɴᴅ Sᴄʜᴏʀɢᴇʀs Date: Tue, 11 Jan 2022 20:41:20 +0100 Subject: firewall: validators: T4174: Correct upper port range boundary --- src/validators/port-multi | 4 ++-- src/validators/port-range | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/validators/port-multi b/src/validators/port-multi index 763d34e57..017ea78fb 100755 --- a/src/validators/port-multi +++ b/src/validators/port-multi @@ -24,14 +24,14 @@ if __name__ == '__main__': for port in ports: if re.match('^[0-9]{1,5}-[0-9]{1,5}$', port): port_1, port_2 = port.split('-') - if int(port_1) not in range(1, 65535) or int(port_2) not in range(1, 65535): + if int(port_1) not in range(1, 65536) or int(port_2) not in range(1, 65536): print(f'Error: {port} is not a valid port range') sys.exit(1) if int(port_1) > int(port_2): print(f'Error: {port} is not a valid port range') sys.exit(1) elif port.isnumeric(): - if int(port) not in range(1, 65535): + if int(port) not in range(1, 65536): print(f'Error: {port} is not a valid port') sys.exit(1) elif port not in services: diff --git a/src/validators/port-range b/src/validators/port-range index 657a21e20..6c01048f0 100755 --- a/src/validators/port-range +++ b/src/validators/port-range @@ -12,11 +12,11 @@ if __name__ == '__main__': port_range = sys.argv[1] if re.match('^[0-9]{1,5}-[0-9]{1,5}$', port_range): port_1, port_2 = port_range.split('-') - if int(port_1) not in range(1, 65535) or int(port_2) not in range(1, 65535): + if int(port_1) not in range(1, 65536) or int(port_2) not in range(1, 65536): error(port_range) if int(port_1) > int(port_2): error(port_range) - elif not port_range.isnumeric() or int(port_range) not in range(1, 65535): + elif not port_range.isnumeric() or int(port_range) not in range(1, 65536): error(port_range) else: sys.exit(2) -- cgit v1.2.3 From 391ce22b76190309f81e048ebffab778b0fdee1d Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 11 Jan 2022 20:59:54 +0100 Subject: migrator: interfaces: T4171: bugfix ConfigTreeError --- src/migration-scripts/interfaces/11-to-12 | 2 ++ src/migration-scripts/interfaces/12-to-13 | 2 ++ src/migration-scripts/interfaces/22-to-23 | 2 ++ src/migration-scripts/interfaces/9-to-10 | 2 ++ 4 files changed, 8 insertions(+) diff --git a/src/migration-scripts/interfaces/11-to-12 b/src/migration-scripts/interfaces/11-to-12 index 0dad24642..ca4ebe741 100755 --- a/src/migration-scripts/interfaces/11-to-12 +++ b/src/migration-scripts/interfaces/11-to-12 @@ -31,6 +31,8 @@ if __name__ == '__main__': config_file = f.read() config = ConfigTree(config_file) + if not config.exists(['interfaces']): + exit(0) for type in config.list_nodes(['interfaces']): for interface in config.list_nodes(['interfaces', type]): diff --git a/src/migration-scripts/interfaces/12-to-13 b/src/migration-scripts/interfaces/12-to-13 index f866ca9a6..fbd7e6f47 100755 --- a/src/migration-scripts/interfaces/12-to-13 +++ b/src/migration-scripts/interfaces/12-to-13 @@ -33,6 +33,8 @@ if __name__ == '__main__': config_file = f.read() config = ConfigTree(config_file) + if not config.exists(['interfaces']): + exit(0) # # T2903 diff --git a/src/migration-scripts/interfaces/22-to-23 b/src/migration-scripts/interfaces/22-to-23 index 06e07572f..734aa1fd8 100755 --- a/src/migration-scripts/interfaces/22-to-23 +++ b/src/migration-scripts/interfaces/22-to-23 @@ -84,6 +84,8 @@ if __name__ == '__main__': config_file = f.read() config = ConfigTree(config_file) + if not config.exists(['interfaces']): + exit(0) # # Migrate "interface ethernet eth0 ip ospf" to "protocols ospf interface eth0" diff --git a/src/migration-scripts/interfaces/9-to-10 b/src/migration-scripts/interfaces/9-to-10 index 4aa2c42b5..c6311a24d 100755 --- a/src/migration-scripts/interfaces/9-to-10 +++ b/src/migration-scripts/interfaces/9-to-10 @@ -32,6 +32,8 @@ if __name__ == '__main__': config_file = f.read() config = ConfigTree(config_file) + if not config.exists(['interfaces']): + exit(0) for intf_type in config.list_nodes(['interfaces']): for intf in config.list_nodes(['interfaces', intf_type]): -- cgit v1.2.3 From a132ba993e786994a3b129c72fb0024931339619 Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Wed, 12 Jan 2022 00:59:53 +0100 Subject: firewall: T4160: Fix support for inverse matches --- python/vyos/firewall.py | 35 ++++++++++++++++++++++++++++------- src/validators/port-multi | 2 ++ 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py index 414ec89c1..66dc8bc40 100644 --- a/python/vyos/firewall.py +++ b/python/vyos/firewall.py @@ -45,13 +45,19 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name): if 'state' in rule_conf and rule_conf['state']: states = ",".join([s for s, v in rule_conf['state'].items() if v == 'enable']) - output.append(f'ct state {{{states}}}') + + if states: + output.append(f'ct state {{{states}}}') if 'protocol' in rule_conf and rule_conf['protocol'] != 'all': proto = rule_conf['protocol'] + operator = '' + if proto[0] == '!': + operator = '!=' + proto = proto[1:] if proto == 'tcp_udp': proto = '{tcp, udp}' - output.append('meta l4proto ' + proto) + output.append(f'meta l4proto {operator} {proto}') for side in ['destination', 'source']: if side in rule_conf: @@ -59,7 +65,10 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name): side_conf = rule_conf[side] if 'address' in side_conf: - output.append(f'{ip_name} {prefix}addr {side_conf["address"]}') + suffix = side_conf['address'] + if suffix[0] == '!': + suffix = f'!= {suffix[1:]}' + output.append(f'{ip_name} {prefix}addr {suffix}') if 'mac_address' in side_conf: suffix = side_conf["mac_address"] @@ -69,15 +78,27 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name): if 'port' in side_conf: proto = rule_conf['protocol'] - port = side_conf["port"] + port = side_conf['port'].split(',') - if isinstance(port, list): - port = ",".join(port) + ports = [] + negated_ports = [] + + for p in port: + if p[0] == '!': + negated_ports.append(p[1:]) + else: + ports.append(p) if proto == 'tcp_udp': proto = 'th' - output.append(f'{proto} {prefix}port {{{port}}}') + if ports: + ports_str = ','.join(ports) + output.append(f'{proto} {prefix}port {{{ports_str}}}') + + if negated_ports: + negated_ports_str = ','.join(negated_ports) + output.append(f'{proto} {prefix}port != {{{negated_ports_str}}}') if 'group' in side_conf: group = side_conf['group'] diff --git a/src/validators/port-multi b/src/validators/port-multi index 017ea78fb..cef371563 100755 --- a/src/validators/port-multi +++ b/src/validators/port-multi @@ -22,6 +22,8 @@ if __name__ == '__main__': services = get_services() for port in ports: + if port and port[0] == '!': + port = port[1:] if re.match('^[0-9]{1,5}-[0-9]{1,5}$', port): port_1, port_2 = port.split('-') if int(port_1) not in range(1, 65536) or int(port_2) not in range(1, 65536): -- cgit v1.2.3 From f57eef6751a32d7cfd59485e980855f10e8e1d07 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Thu, 13 Jan 2022 00:49:08 +0000 Subject: monitoring: T3872: Rewrite input filter custom_script Rewrite and improve the custom input filter telegraf script "show_interfaces_input_filter.py" to more readable and clear format Fix bug when it failed with configured tunnel "tunX" interfaces --- .../custom_scripts/show_interfaces_input_filter.py | 123 ++++++++++++++------- 1 file changed, 82 insertions(+), 41 deletions(-) diff --git a/src/etc/telegraf/custom_scripts/show_interfaces_input_filter.py b/src/etc/telegraf/custom_scripts/show_interfaces_input_filter.py index 0f5e366cd..0c7474156 100755 --- a/src/etc/telegraf/custom_scripts/show_interfaces_input_filter.py +++ b/src/etc/telegraf/custom_scripts/show_interfaces_input_filter.py @@ -1,47 +1,88 @@ #!/usr/bin/env python3 -import subprocess +from vyos.ifconfig import Section +from vyos.ifconfig import Interface + import time -def status_to_int(status): - switcher={ - 'u':'0', - 'D':'1', - 'A':'2' - } - return switcher.get(status,"") - -def description_check(line): - desc=" ".join(line[3:]) - if desc == "": +def get_interfaces(type='', vlan=True): + """ + Get interfaces: + ['dum0', 'eth0', 'eth1', 'eth1.5', 'lo', 'tun0'] + """ + interfaces = [] + ifaces = Section.interfaces(type) + for iface in ifaces: + if vlan == False and '.' in iface: + continue + interfaces.append(iface) + + return interfaces + +def get_interface_addresses(iface, link_local_v6=False): + """ + Get IP and IPv6 addresses from interface in one string + By default don't get IPv6 link-local addresses + If interface doesn't have address, return "-" + """ + addresses = [] + addrs = Interface(iface).get_addr() + + for addr in addrs: + if link_local_v6 == False: + if addr.startswith('fe80::'): + continue + addresses.append(addr) + + if not addresses: + return "-" + + return (" ".join(addresses)) + +def get_interface_description(iface): + """ + Get interface description + If none return "empty" + """ + description = Interface(iface).get_alias() + + if not description: return "empty" + + return description + +def get_interface_admin_state(iface): + """ + Interface administrative state + up => 0, down => 2 + """ + state = Interface(iface).get_admin_state() + if state == 'up': + admin_state = 0 + if state == 'down': + admin_state = 2 + + return admin_state + +def get_interface_oper_state(iface): + """ + Interface operational state + up => 0, down => 1 + """ + state = Interface(iface).operational.get_state() + if state == 'down': + oper_state = 1 else: - return desc - -def gen_ip_list(index,interfaces): - line=interfaces[index].split() - ip_list=line[1] - if index < len(interfaces): - index += 1 - while len(interfaces[index].split())==1: - ip = interfaces[index].split() - ip_list = ip_list + " " + ip[0] - index += 1 - if index == len(interfaces): - break - return ip_list - -interfaces = subprocess.check_output("/usr/libexec/vyos/op_mode/show_interfaces.py --action=show-brief", shell=True).decode('utf-8').splitlines() -del interfaces[:3] -lines_count=len(interfaces) -index=0 -while index1: - print(f'show_interfaces,interface={line[0]} ' - f'ip_addresses="{gen_ip_list(index,interfaces)}",' - f'state={status_to_int(line[2][0])}i,' - f'link={status_to_int(line[2][2])}i,' - f'description="{description_check(line)}" ' - f'{str(int(time.time()))}000000000') - index += 1 + oper_state = 0 + + return oper_state + +interfaces = get_interfaces() + +for iface in interfaces: + print(f'show_interfaces,interface={iface} ' + f'ip_addresses="{get_interface_addresses(iface)}",' + f'state={get_interface_admin_state(iface)}i,' + f'link={get_interface_oper_state(iface)}i,' + f'description="{get_interface_description(iface)}" ' + f'{str(int(time.time()))}000000000') -- cgit v1.2.3 From 140adbe8344563d8f907fba6d76d8ce1472236ad Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Thu, 13 Jan 2022 00:58:51 +0000 Subject: monitoring: T3872: Add just required interfaces for ethtool Telegraf ethtool input filter expected ethX interfaces and not other interfaces like vlans/tunnels/dummy Add "interface_include" option to telegraf template. --- data/templates/monitoring/telegraf.tmpl | 1 + src/conf_mode/service_monitoring_telegraf.py | 25 +++++++++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/data/templates/monitoring/telegraf.tmpl b/data/templates/monitoring/telegraf.tmpl index 62fa4df7a..afc04aa6d 100644 --- a/data/templates/monitoring/telegraf.tmpl +++ b/data/templates/monitoring/telegraf.tmpl @@ -41,6 +41,7 @@ files = ["ip_conntrack_count","ip_conntrack_max","nf_conntrack_count","nf_conntrack_max"] dirs = ["/proc/sys/net/ipv4/netfilter","/proc/sys/net/netfilter"] [[inputs.ethtool]] + interface_include = {{ interfaces_ethernet }} [[inputs.iptables]] use_sudo = false table = "filter" diff --git a/src/conf_mode/service_monitoring_telegraf.py b/src/conf_mode/service_monitoring_telegraf.py index a1e7a7286..8a972b9fe 100755 --- a/src/conf_mode/service_monitoring_telegraf.py +++ b/src/conf_mode/service_monitoring_telegraf.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2022 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 @@ -22,6 +22,7 @@ from shutil import rmtree from vyos.config import Config from vyos.configdict import dict_merge +from vyos.ifconfig import Section from vyos.template import render from vyos.util import call from vyos.util import chown @@ -42,6 +43,24 @@ systemd_telegraf_override_dir = '/etc/systemd/system/vyos-telegraf.service.d' systemd_override = f'{systemd_telegraf_override_dir}/10-override.conf' +def get_interfaces(type='', vlan=True): + """ + Get interfaces + get_interfaces() + ['dum0', 'eth0', 'eth1', 'eth1.5', 'lo', 'tun0'] + + get_interfaces("dummy") + ['dum0'] + """ + interfaces = [] + ifaces = Section.interfaces(type) + for iface in ifaces: + if vlan == False and '.' in iface: + continue + interfaces.append(iface) + + return interfaces + def get_nft_filter_chains(): """ Get nft chains for table filter @@ -57,6 +76,7 @@ def get_nft_filter_chains(): return chain_list + def get_config(config=None): if config: @@ -75,8 +95,9 @@ def get_config(config=None): default_values = defaults(base) monitoring = dict_merge(default_values, monitoring) - monitoring['nft_chains'] = get_nft_filter_chains() monitoring['custom_scripts_dir'] = custom_scripts_dir + monitoring['interfaces_ethernet'] = get_interfaces('ethernet', vlan=False) + monitoring['nft_chains'] = get_nft_filter_chains() return monitoring -- cgit v1.2.3 From f12d8b5a575f4b454426fe11f65b5add966ca53c Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Thu, 13 Jan 2022 09:46:32 +0000 Subject: strip-private: T4177: Fix for hiding private data token/url/bucket Add URL, token and bucket hidind data when is used function "strip-private" --- src/helpers/strip-private.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/helpers/strip-private.py b/src/helpers/strip-private.py index e4e1fe11d..eb584edaf 100755 --- a/src/helpers/strip-private.py +++ b/src/helpers/strip-private.py @@ -1,6 +1,6 @@ #!/usr/bin/python3 -# Copyright 2021 VyOS maintainers and contributors +# Copyright 2021-2022 VyOS maintainers and contributors # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -111,6 +111,10 @@ if __name__ == "__main__": (True, re.compile(r'public-keys \S+'), 'public-keys xxxx@xxx.xxx'), (True, re.compile(r'type \'ssh-(rsa|dss)\''), 'type ssh-xxx'), (True, re.compile(r' key \S+'), ' key xxxxxx'), + # Strip bucket + (True, re.compile(r' bucket \S+'), ' bucket xxxxxx'), + # Strip tokens + (True, re.compile(r' token \S+'), ' token xxxxxx'), # Strip OpenVPN secrets (True, re.compile(r'(shared-secret-key-file|ca-cert-file|cert-file|dh-file|key-file|client) (\S+)'), r'\1 xxxxxx'), # Strip IPSEC secrets @@ -123,8 +127,8 @@ if __name__ == "__main__": # Strip MAC addresses (args.mac, re.compile(r'([0-9a-fA-F]{2}\:){5}([0-9a-fA-F]{2}((\:{0,1})){3})'), r'xx:xx:xx:xx:xx:\2'), - # Strip host-name, domain-name, and domain-search - (args.hostname, re.compile(r'(host-name|domain-name|domain-search) \S+'), r'\1 xxxxxx'), + # Strip host-name, domain-name, domain-search and url + (args.hostname, re.compile(r'(host-name|domain-name|domain-search|url) \S+'), r'\1 xxxxxx'), # Strip user-names (args.username, re.compile(r'(user|username|user-id) \S+'), r'\1 xxxxxx'), -- cgit v1.2.3 From eae32ec9ae9fa246ba8be58d61f95398ad7557be Mon Sep 17 00:00:00 2001 From: fett0 Date: Thu, 13 Jan 2022 20:23:15 +0000 Subject: Firewall: T4181: Set correct description for ipv6-network-group --- interface-definitions/firewall.xml.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in index f428efe58..fd98ae138 100644 --- a/interface-definitions/firewall.xml.in +++ b/interface-definitions/firewall.xml.in @@ -125,7 +125,7 @@ - Network-group member + Firewall ipv6-network-group #include -- cgit v1.2.3 From 6cdeb472d924ec66fe74e17559e942ab5f55d45f Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Thu, 13 Jan 2022 20:11:57 +0000 Subject: vrrp: T4182: Check if VRRP configured in op mode There is a situation when service keepalived is active but there a no any "vrrp" configuration. In that case "show vrrp" hangs up because it expect data from keepalived daemon which can't get Check if "vrrp" exists in configuration and only then check if pid is active --- src/op_mode/vrrp.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/op_mode/vrrp.py b/src/op_mode/vrrp.py index 2c1db20bf..dab146d28 100755 --- a/src/op_mode/vrrp.py +++ b/src/op_mode/vrrp.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018 VyOS maintainers and contributors +# Copyright (C) 2018-2022 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 @@ -23,6 +23,7 @@ import tabulate import vyos.util +from vyos.configquery import ConfigTreeQuery from vyos.ifconfig.vrrp import VRRP from vyos.ifconfig.vrrp import VRRPError, VRRPNoData @@ -35,7 +36,17 @@ group.add_argument("-d", "--data", action="store_true", help="Print detailed VRR args = parser.parse_args() +def is_configured(): + """ Check if VRRP is configured """ + config = ConfigTreeQuery() + if not config.exists(['high-availability', 'vrrp', 'group']): + return False + return True + # Exit early if VRRP is dead or not configured +if is_configured() == False: + print('VRRP not configured!') + exit(0) if not VRRP.is_running(): print('VRRP is not running') sys.exit(0) -- cgit v1.2.3 From d63cabb18649149084bc3d385feaa20ddbb2f870 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Thu, 13 Jan 2022 19:10:38 +0000 Subject: op-mode: T4179: Add op-mode CLI show virtual-server --- op-mode-definitions/show-virtual-server.xml.in | 13 ++++++++++ src/op_mode/show_virtual_server.py | 33 ++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 op-mode-definitions/show-virtual-server.xml.in create mode 100755 src/op_mode/show_virtual_server.py diff --git a/op-mode-definitions/show-virtual-server.xml.in b/op-mode-definitions/show-virtual-server.xml.in new file mode 100644 index 000000000..5dbd3c759 --- /dev/null +++ b/op-mode-definitions/show-virtual-server.xml.in @@ -0,0 +1,13 @@ + + + + + + + Show virtual server information + + ${vyos_op_scripts_dir}/show_virtual_server.py + + + + diff --git a/src/op_mode/show_virtual_server.py b/src/op_mode/show_virtual_server.py new file mode 100755 index 000000000..377180dec --- /dev/null +++ b/src/op_mode/show_virtual_server.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 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 . + +from vyos.configquery import ConfigTreeQuery +from vyos.util import call + +def is_configured(): + """ Check if high-availability virtual-server is configured """ + config = ConfigTreeQuery() + if not config.exists(['high-availability', 'virtual-server']): + return False + return True + +if __name__ == '__main__': + + if is_configured() == False: + print('Virtual server not configured!') + exit(0) + + call('sudo ipvsadm --list --numeric') -- cgit v1.2.3 From 0d4079ca3a3de889d521ed66218bd0015daf042d Mon Sep 17 00:00:00 2001 From: Henning Surmeier Date: Sat, 8 Jan 2022 12:44:12 +0100 Subject: policy: T4151: Add policy ipv6-local-route Adds support for `ip -6 rule` policy based routing. Also, extends the existing ipv4 implemenation with a `destination` key, which is translated as `ip rule add to x.x.x.x/x` rules. https://phabricator.vyos.net/T4151 --- interface-definitions/policy-local-route.xml.in | 109 +++++++++- smoketest/scripts/cli/test_policy.py | 258 +++++++++++++++++++++++- src/conf_mode/policy-local-route.py | 145 +++++++------ 3 files changed, 443 insertions(+), 69 deletions(-) diff --git a/interface-definitions/policy-local-route.xml.in b/interface-definitions/policy-local-route.xml.in index 86445b65d..11b1e04d9 100644 --- a/interface-definitions/policy-local-route.xml.in +++ b/interface-definitions/policy-local-route.xml.in @@ -14,7 +14,7 @@ u32:1-32765 - Local-route rule number (1-219) + Local-route rule number (1-32765) @@ -70,6 +70,113 @@ + + + Destination address or prefix + + ipv4 + Address to match against + + + ipv4net + Prefix to match against + + + + + + + + + + + + + + + IPv6 policy route of local traffic + + + + + IPv6 policy local-route rule set number + + + u32:1-32765 + Local-route rule number (1-32765) + + + + + + + + + Packet modifications + + + + + Routing table to forward packet with + + u32:1-200 + Table number + + + main + + + + + + + + Match fwmark value + + u32:1-2147483647 + Address to match against + + + + + + + + + Source address or prefix + + ipv4 + Address to match against + + + ipv4net + Prefix to match against + + + + + + + + + + + Destination address or prefix + + ipv6 + Address to match against + + + ipv6net + Prefix to match against + + + + + + + + diff --git a/smoketest/scripts/cli/test_policy.py b/smoketest/scripts/cli/test_policy.py index 5844e1ec1..9f9c11fb5 100755 --- a/smoketest/scripts/cli/test_policy.py +++ b/smoketest/scripts/cli/test_policy.py @@ -1143,10 +1143,8 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): 50: from 203.0.113.2 lookup 23 """ tmp = cmd('ip rule show prio 50') - original = original.split() - tmp = tmp.split() - self.assertEqual(tmp, original) + self.assertEqual(sort_ip(tmp), sort_ip(original)) # Test set table for fwmark def test_fwmark_table_id(self): @@ -1168,10 +1166,31 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): 101: from all fwmark 0x18 lookup 154 """ tmp = cmd('ip rule show prio 101') - original = original.split() - tmp = tmp.split() - self.assertEqual(tmp, original) + self.assertEqual(sort_ip(tmp), sort_ip(original)) + + # Test set table for destination + def test_destination_table_id(self): + path = base_path + ['local-route'] + + dst = '203.0.113.1' + rule = '102' + table = '154' + + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'destination', dst]) + + self.cli_commit() + + # Check generated configuration + + # Expected values + original = """ + 102: from all to 203.0.113.1 lookup 154 + """ + tmp = cmd('ip rule show prio 102') + + self.assertEqual(sort_ip(tmp), sort_ip(original)) # Test set table for sources with fwmark def test_fwmark_sources_table_id(self): @@ -1196,10 +1215,231 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): 100: from 203.0.113.12 fwmark 0x17 lookup 150 """ tmp = cmd('ip rule show prio 100') - original = original.split() - tmp = tmp.split() - self.assertEqual(tmp, original) + self.assertEqual(sort_ip(tmp), sort_ip(original)) + + # Test set table for sources and destinations with fwmark + def test_fwmark_sources_destination_table_id(self): + path = base_path + ['local-route'] + + sources = ['203.0.113.11', '203.0.113.12'] + destinations = ['203.0.113.13', '203.0.113.15'] + fwmk = '23' + rule = '103' + table = '150' + for src in sources: + for dst in destinations: + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'source', src]) + self.cli_set(path + ['rule', rule, 'destination', dst]) + self.cli_set(path + ['rule', rule, 'fwmark', fwmk]) + + self.cli_commit() + + # Check generated configuration + + # Expected values + original = """ + 103: from 203.0.113.11 to 203.0.113.13 fwmark 0x17 lookup 150 + 103: from 203.0.113.11 to 203.0.113.15 fwmark 0x17 lookup 150 + 103: from 203.0.113.12 to 203.0.113.13 fwmark 0x17 lookup 150 + 103: from 203.0.113.12 to 203.0.113.15 fwmark 0x17 lookup 150 + """ + tmp = cmd('ip rule show prio 103') + + self.assertEqual(sort_ip(tmp), sort_ip(original)) + + # Test set table ipv6 for some sources ipv6 + def test_ipv6_table_id(self): + path = base_path + ['local-route6'] + + sources = ['2001:db8:123::/48', '2001:db8:126::/48'] + rule = '50' + table = '23' + for src in sources: + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'source', src]) + + self.cli_commit() + + # Check generated configuration + + # Expected values + original = """ + 50: from 2001:db8:123::/48 lookup 23 + 50: from 2001:db8:126::/48 lookup 23 + """ + tmp = cmd('ip -6 rule show prio 50') + + self.assertEqual(sort_ip(tmp), sort_ip(original)) + + # Test set table for fwmark ipv6 + def test_fwmark_ipv6_table_id(self): + path = base_path + ['local-route6'] + + fwmk = '24' + rule = '100' + table = '154' + + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'fwmark', fwmk]) + + self.cli_commit() + + # Check generated configuration + + # Expected values + original = """ + 100: from all fwmark 0x18 lookup 154 + """ + tmp = cmd('ip -6 rule show prio 100') + + self.assertEqual(sort_ip(tmp), sort_ip(original)) + + # Test set table for destination ipv6 + def test_destination_ipv6_table_id(self): + path = base_path + ['local-route6'] + + dst = '2001:db8:1337::/126' + rule = '101' + table = '154' + + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'destination', dst]) + + self.cli_commit() + + # Check generated configuration + + # Expected values + original = """ + 101: from all to 2001:db8:1337::/126 lookup 154 + """ + tmp = cmd('ip -6 rule show prio 101') + + self.assertEqual(sort_ip(tmp), sort_ip(original)) + + # Test set table for sources with fwmark ipv6 + def test_fwmark_sources_ipv6_table_id(self): + path = base_path + ['local-route6'] + + sources = ['2001:db8:1338::/126', '2001:db8:1339::/126'] + fwmk = '23' + rule = '102' + table = '150' + for src in sources: + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'source', src]) + self.cli_set(path + ['rule', rule, 'fwmark', fwmk]) + + self.cli_commit() + + # Check generated configuration + + # Expected values + original = """ + 102: from 2001:db8:1338::/126 fwmark 0x17 lookup 150 + 102: from 2001:db8:1339::/126 fwmark 0x17 lookup 150 + """ + tmp = cmd('ip -6 rule show prio 102') + + self.assertEqual(sort_ip(tmp), sort_ip(original)) + + # Test set table for sources and destinations with fwmark ipv6 + def test_fwmark_sources_destination_ipv6_table_id(self): + path = base_path + ['local-route6'] + + sources = ['2001:db8:1338::/126', '2001:db8:1339::/56'] + destinations = ['2001:db8:13::/48', '2001:db8:16::/48'] + fwmk = '23' + rule = '103' + table = '150' + for src in sources: + for dst in destinations: + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'source', src]) + self.cli_set(path + ['rule', rule, 'destination', dst]) + self.cli_set(path + ['rule', rule, 'fwmark', fwmk]) + + self.cli_commit() + + # Check generated configuration + + # Expected values + original = """ + 103: from 2001:db8:1338::/126 to 2001:db8:13::/48 fwmark 0x17 lookup 150 + 103: from 2001:db8:1338::/126 to 2001:db8:16::/48 fwmark 0x17 lookup 150 + 103: from 2001:db8:1339::/56 to 2001:db8:13::/48 fwmark 0x17 lookup 150 + 103: from 2001:db8:1339::/56 to 2001:db8:16::/48 fwmark 0x17 lookup 150 + """ + tmp = cmd('ip -6 rule show prio 103') + + self.assertEqual(sort_ip(tmp), sort_ip(original)) + + # Test delete table for sources and destination with fwmark ipv4/ipv6 + def test_delete_ipv4_ipv6_table_id(self): + path = base_path + ['local-route'] + path_v6 = base_path + ['local-route6'] + + sources = ['203.0.113.1/24', '203.0.114.5'] + destinations = ['203.0.112.1/24', '203.0.116.5'] + sources_v6 = ['2001:db8:1338::/126', '2001:db8:1339::/56'] + destinations_v6 = ['2001:db8:13::/48', '2001:db8:16::/48'] + fwmk = '23' + rule = '103' + table = '150' + for src in sources: + for dst in destinations: + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'source', src]) + self.cli_set(path + ['rule', rule, 'destination', dst]) + self.cli_set(path + ['rule', rule, 'fwmark', fwmk]) + + for src in sources_v6: + for dst in destinations_v6: + self.cli_set(path_v6 + ['rule', rule, 'set', 'table', table]) + self.cli_set(path_v6 + ['rule', rule, 'source', src]) + self.cli_set(path_v6 + ['rule', rule, 'destination', dst]) + self.cli_set(path_v6 + ['rule', rule, 'fwmark', fwmk]) + + self.cli_commit() + + # Check generated configuration + + # Expected values + original = """ + 103: from 203.0.113.1/24 to 203.0.112.1/24 fwmark 0x17 lookup 150 + 103: from 203.0.113.1/24 to 203.0.116.5 fwmark 0x17 lookup 150 + 103: from 203.0.114.5 to 203.0.112.1/24 fwmark 0x17 lookup 150 + 103: from 203.0.114.5 to 203.0.116.5 fwmark 0x17 lookup 150 + """ + original_v6 = """ + 103: from 20016 to 2001:db8:13::/48 fwmark 0x17 lookup 150 + 103: from 2001:db8:1338::/126 to 2001:db8:16::/48 fwmark 0x17 lookup 150 + 103: from 2001:db8:1339::/56 to 2001:db8:13::/48 fwmark 0x17 lookup 150 + 103: from 2001:db8:1339::/56 to 2001:db8:16::/48 fwmark 0x17 lookup 150 + """ + tmp = cmd('ip rule show prio 103') + tmp_v6 = cmd('ip -6 rule show prio 103') + + self.assertEqual(sort_ip(tmp), sort_ip(original)) + self.assertEqual(sort_ip(tmp_v6), sort_ip(original_v6)) + + self.cli_delete(path) + self.cli_delete(path_v6) + self.cli_commit() + + tmp = cmd('ip rule show prio 103') + tmp_v6 = cmd('ip -6 rule show prio 103') + + original = [''] + original_v6 = [''] + + self.assertEqual(sort_ip(tmp), original) + self.assertEqual(sort_ip(tmp_v6), original_v6) + +def sort_ip(output): + return output.splitlines().sort() if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/conf_mode/policy-local-route.py b/src/conf_mode/policy-local-route.py index 539189442..2541603e2 100755 --- a/src/conf_mode/policy-local-route.py +++ b/src/conf_mode/policy-local-route.py @@ -35,34 +35,53 @@ def get_config(config=None): conf = config else: conf = Config() - base = ['policy', 'local-route'] + base = ['policy'] + pbr = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - # delete policy local-route - dict = {} - tmp = node_changed(conf, ['policy', 'local-route', 'rule'], key_mangling=('-', '_')) - if tmp: - for rule in (tmp or []): - src = leaf_node_changed(conf, ['policy', 'local-route', 'rule', rule, 'source']) - fwmk = leaf_node_changed(conf, ['policy', 'local-route', 'rule', rule, 'fwmark']) - if src: - dict = dict_merge({'rule_remove' : {rule : {'source' : src}}}, dict) - pbr.update(dict) - if fwmk: - dict = dict_merge({'rule_remove' : {rule : {'fwmark' : fwmk}}}, dict) + for route in ['local_route', 'local_route6']: + dict_id = 'rule_remove' if route == 'local_route' else 'rule6_remove' + route_key = 'local-route' if route == 'local_route' else 'local-route6' + base_rule = base + [route_key, 'rule'] + + # delete policy local-route + dict = {} + tmp = node_changed(conf, base_rule, key_mangling=('-', '_')) + if tmp: + for rule in (tmp or []): + src = leaf_node_changed(conf, base_rule + [rule, 'source']) + fwmk = leaf_node_changed(conf, base_rule + [rule, 'fwmark']) + dst = leaf_node_changed(conf, base_rule + [rule, 'destination']) + rule_def = {} + if src: + rule_def = dict_merge({'source' : src}, rule_def) + if fwmk: + rule_def = dict_merge({'fwmark' : fwmk}, rule_def) + if dst: + rule_def = dict_merge({'destination' : dst}, rule_def) + dict = dict_merge({dict_id : {rule : rule_def}}, dict) pbr.update(dict) - # delete policy local-route rule x source x.x.x.x - # delete policy local-route rule x fwmark x - if 'rule' in pbr: - for rule in pbr['rule']: - src = leaf_node_changed(conf, ['policy', 'local-route', 'rule', rule, 'source']) - fwmk = leaf_node_changed(conf, ['policy', 'local-route', 'rule', rule, 'fwmark']) - if src: - dict = dict_merge({'rule_remove' : {rule : {'source' : src}}}, dict) - pbr.update(dict) - if fwmk: - dict = dict_merge({'rule_remove' : {rule : {'fwmark' : fwmk}}}, dict) + if not route in pbr: + continue + + # delete policy local-route rule x source x.x.x.x + # delete policy local-route rule x fwmark x + # delete policy local-route rule x destination x.x.x.x + if 'rule' in pbr[route]: + for rule in pbr[route]['rule']: + src = leaf_node_changed(conf, base_rule + [rule, 'source']) + fwmk = leaf_node_changed(conf, base_rule + [rule, 'fwmark']) + dst = leaf_node_changed(conf, base_rule + [rule, 'destination']) + + rule_def = {} + if src: + rule_def = dict_merge({'source' : src}, rule_def) + if fwmk: + rule_def = dict_merge({'fwmark' : fwmk}, rule_def) + if dst: + rule_def = dict_merge({'destination' : dst}, rule_def) + dict = dict_merge({dict_id : {rule : rule_def}}, dict) pbr.update(dict) return pbr @@ -72,13 +91,18 @@ def verify(pbr): if not pbr: return None - if 'rule' in pbr: - for rule in pbr['rule']: - if 'source' not in pbr['rule'][rule] and 'fwmark' not in pbr['rule'][rule]: - raise ConfigError('Source address or fwmark is required!') - else: - if 'set' not in pbr['rule'][rule] or 'table' not in pbr['rule'][rule]['set']: - raise ConfigError('Table set is required!') + for route in ['local_route', 'local_route6']: + if not route in pbr: + continue + + pbr_route = pbr[route] + if 'rule' in pbr_route: + for rule in pbr_route['rule']: + if 'source' not in pbr_route['rule'][rule] and 'destination' not in pbr_route['rule'][rule] and 'fwmark' not in pbr_route['rule'][rule]: + raise ConfigError('Source or destination address or fwmark is required!') + else: + if 'set' not in pbr_route['rule'][rule] or 'table' not in pbr_route['rule'][rule]['set']: + raise ConfigError('Table set is required!') return None @@ -93,36 +117,39 @@ def apply(pbr): return None # Delete old rule if needed - if 'rule_remove' in pbr: - for rule in pbr['rule_remove']: - if 'source' in pbr['rule_remove'][rule]: - for src in pbr['rule_remove'][rule]['source']: - call(f'ip rule del prio {rule} from {src}') - if 'fwmark' in pbr['rule_remove'][rule]: - for fwmk in pbr['rule_remove'][rule]['fwmark']: - call(f'ip rule del prio {rule} from all fwmark {fwmk}') + for rule_rm in ['rule_remove', 'rule6_remove']: + if rule_rm in pbr: + v6 = " -6" if rule_rm == 'rule6_remove' else "" + for rule, rule_config in pbr[rule_rm].items(): + for src in (rule_config['source'] or ['']): + f_src = '' if src == '' else f' from {src} ' + for dst in (rule_config['destination'] or ['']): + f_dst = '' if dst == '' else f' to {dst} ' + for fwmk in (rule_config['fwmark'] or ['']): + f_fwmk = '' if fwmk == '' else f' fwmark {fwmk} ' + call(f'ip{v6} rule del prio {rule} {f_src}{f_dst}{f_fwmk}') # Generate new config - if 'rule' in pbr: - for rule in pbr['rule']: - table = pbr['rule'][rule]['set']['table'] - # Only source in the rule - # set policy local-route rule 100 source '203.0.113.1' - if 'source' in pbr['rule'][rule] and not 'fwmark' in pbr['rule'][rule]: - for src in pbr['rule'][rule]['source']: - call(f'ip rule add prio {rule} from {src} lookup {table}') - # Only fwmark in the rule - # set policy local-route rule 101 fwmark '23' - if 'fwmark' in pbr['rule'][rule] and not 'source' in pbr['rule'][rule]: - fwmk = pbr['rule'][rule]['fwmark'] - call(f'ip rule add prio {rule} from all fwmark {fwmk} lookup {table}') - # Source and fwmark in the rule - # set policy local-route rule 100 source '203.0.113.1' - # set policy local-route rule 100 fwmark '23' - if 'source' in pbr['rule'][rule] and 'fwmark' in pbr['rule'][rule]: - fwmk = pbr['rule'][rule]['fwmark'] - for src in pbr['rule'][rule]['source']: - call(f'ip rule add prio {rule} from {src} fwmark {fwmk} lookup {table}') + for route in ['local_route', 'local_route6']: + if not route in pbr: + continue + + v6 = " -6" if route == 'local_route6' else "" + + pbr_route = pbr[route] + if 'rule' in pbr_route: + for rule, rule_config in pbr_route['rule'].items(): + table = rule_config['set']['table'] + + for src in (rule_config['source'] or ['all']): + f_src = '' if src == '' else f' from {src} ' + for dst in (rule_config['destination'] or ['all']): + f_dst = '' if dst == '' else f' to {dst} ' + f_fwmk = '' + if 'fwmark' in rule_config: + fwmk = rule_config['fwmark'] + f_fwmk = f' fwmark {fwmk} ' + call(f'ip{v6} rule add prio {rule} {f_src}{f_dst}{f_fwmk} lookup {table}') return None -- cgit v1.2.3 From df5a862beb84145dfc8434efde7d7fee783199cf Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Thu, 13 Jan 2022 12:58:37 +0100 Subject: firewall: T4178: Use lowercase for TCP flags and add an validator --- .../include/firewall/common-rule.xml.i | 34 ++++++++++++++++++++-- .../include/policy/route-common-rule-ipv6.xml.i | 34 ++++++++++++++++++++-- .../include/policy/route-common-rule.xml.i | 34 ++++++++++++++++++++-- python/vyos/firewall.py | 7 ++--- src/conf_mode/firewall.py | 3 ++ src/conf_mode/policy-route.py | 10 +++---- src/validators/tcp-flag | 19 ++++++++++++ 7 files changed, 126 insertions(+), 15 deletions(-) create mode 100755 src/validators/tcp-flag diff --git a/interface-definitions/include/firewall/common-rule.xml.i b/interface-definitions/include/firewall/common-rule.xml.i index 92950cc68..6e8203c88 100644 --- a/interface-definitions/include/firewall/common-rule.xml.i +++ b/interface-definitions/include/firewall/common-rule.xml.i @@ -274,12 +274,42 @@ TCP flags to match txt - TCP flags to match + Multiple comma-separated flags + + + syn + Syncronise flag + + + ack + Acknowledge flag + + + fin + Finish flag + + + rst + Reset flag + + + urg + Urgent flag + + + psh + Push flag - \n\n Allowed values for TCP flags : SYN ACK FIN RST URG PSH ALL\n When specifying more than one flag, flags should be comma-separated.\n For example : value of 'SYN,!ACK,!FIN,!RST' will only match packets with\n the SYN flag set, and the ACK, FIN and RST flags unset + \n When specifying more than one flag, flags should be comma-separated.\n For example: value of 'SYN,!ACK,!FIN,!RST' will only match packets with\n the SYN flag set, and the ACK, FIN and RST flags unset + + syn ack fin rst urg psh + + + + diff --git a/interface-definitions/include/policy/route-common-rule-ipv6.xml.i b/interface-definitions/include/policy/route-common-rule-ipv6.xml.i index 2d6adcd1d..b8fee4b7b 100644 --- a/interface-definitions/include/policy/route-common-rule-ipv6.xml.i +++ b/interface-definitions/include/policy/route-common-rule-ipv6.xml.i @@ -330,12 +330,42 @@ TCP flags to match txt - TCP flags to match + Multiple comma-separated flags + + + syn + Syncronise flag + + + ack + Acknowledge flag + + + fin + Finish flag + + + rst + Reset flag + + + urg + Urgent flag + + + psh + Push flag - \n\n Allowed values for TCP flags : SYN ACK FIN RST URG PSH ALL\n When specifying more than one flag, flags should be comma-separated.\n For example : value of 'SYN,!ACK,!FIN,!RST' will only match packets with\n the SYN flag set, and the ACK, FIN and RST flags unset + \n When specifying more than one flag, flags should be comma-separated.\n For example: value of 'SYN,!ACK,!FIN,!RST' will only match packets with\n the SYN flag set, and the ACK, FIN and RST flags unset + + syn ack fin rst urg psh + + + + diff --git a/interface-definitions/include/policy/route-common-rule.xml.i b/interface-definitions/include/policy/route-common-rule.xml.i index c4deefd2a..17b47474d 100644 --- a/interface-definitions/include/policy/route-common-rule.xml.i +++ b/interface-definitions/include/policy/route-common-rule.xml.i @@ -330,12 +330,42 @@ TCP flags to match txt - TCP flags to match + Multiple comma-separated flags + + + syn + Syncronise flag + + + ack + Acknowledge flag + + + fin + Finish flag + + + rst + Reset flag + + + urg + Urgent flag + + + psh + Push flag - \n\n Allowed values for TCP flags : SYN ACK FIN RST URG PSH ALL\n When specifying more than one flag, flags should be comma-separated.\n For example : value of 'SYN,!ACK,!FIN,!RST' will only match packets with\n the SYN flag set, and the ACK, FIN and RST flags unset + \n When specifying more than one flag, flags should be comma-separated.\n For example: value of 'SYN,!ACK,!FIN,!RST' will only match packets with\n the SYN flag set, and the ACK, FIN and RST flags unset + + syn ack fin rst urg psh + + + + diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py index 66dc8bc40..acde9f913 100644 --- a/python/vyos/firewall.py +++ b/python/vyos/firewall.py @@ -171,7 +171,6 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name): if tcp_flags: output.append(parse_tcp_flags(tcp_flags)) - output.append('counter') if 'set' in rule_conf: @@ -190,10 +189,10 @@ def parse_tcp_flags(flags): include = [] for flag in flags.split(","): if flag[0] == '!': - flag = flag[1:] + flag = flag[1:].lower() else: - include.append(flag) - all_flags.append(flag) + include.append(flag.lower()) + all_flags.append(flag.lower()) return f'tcp flags & ({"|".join(all_flags)}) == {"|".join(include)}' def parse_time(time): diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index 7b491a325..853470fd8 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -142,6 +142,9 @@ def verify_rule(firewall, rule_conf, ipv6): if not {'count', 'time'} <= set(rule_conf['recent']): raise ConfigError('Recent "count" and "time" values must be defined') + if dict_search_args(rule_conf, 'tcp', 'flags') and dict_search_args(rule_conf, 'protocol') != 'tcp': + raise ConfigError('Protocol must be tcp when specifying tcp flags') + for side in ['destination', 'source']: if side in rule_conf: side_conf = rule_conf[side] diff --git a/src/conf_mode/policy-route.py b/src/conf_mode/policy-route.py index c5904309f..30597ef4e 100755 --- a/src/conf_mode/policy-route.py +++ b/src/conf_mode/policy-route.py @@ -76,7 +76,7 @@ def get_config(config=None): return policy -def verify_rule(policy, rule_conf, ipv6): +def verify_rule(policy, name, rule_conf, ipv6): icmp = 'icmp' if not ipv6 else 'icmpv6' if icmp in rule_conf: icmp_defined = False @@ -93,14 +93,14 @@ def verify_rule(policy, rule_conf, ipv6): if icmp_defined and 'protocol' not in rule_conf or rule_conf['protocol'] != icmp: raise ConfigError(f'{name} rule {rule_id}: ICMP type/code or type-name can only be defined if protocol is ICMP') + if 'set' in rule_conf: if 'tcp_mss' in rule_conf['set']: tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags') if not tcp_flags or 'SYN' not in tcp_flags.split(","): raise ConfigError(f'{name} rule {rule_id}: TCP SYN flag must be set to modify TCP-MSS') - if 'tcp' in rule_conf: - if 'flags' in rule_conf['tcp']: - if 'protocol' not in rule_conf or rule_conf['protocol'] != 'tcp': + + if dict_search_args(rule_conf, 'tcp', 'flags') and dict_search_args(rule_conf, 'protocol') != 'tcp': raise ConfigError(f'{name} rule {rule_id}: TCP flags can only be set if protocol is set to TCP') for side in ['destination', 'source']: @@ -138,7 +138,7 @@ def verify(policy): for name, pol_conf in policy[route].items(): if 'rule' in pol_conf: for rule_id, rule_conf in pol_conf['rule'].items(): - verify_rule(policy, rule_conf, ipv6) + verify_rule(policy, name, rule_conf, ipv6) for ifname, if_policy in policy['interfaces'].items(): name = dict_search_args(if_policy, 'route') diff --git a/src/validators/tcp-flag b/src/validators/tcp-flag new file mode 100755 index 000000000..86ebec189 --- /dev/null +++ b/src/validators/tcp-flag @@ -0,0 +1,19 @@ +#!/usr/bin/python3 + +import sys +import re + +if __name__ == '__main__': + if len(sys.argv)>1: + flags = sys.argv[1].split(",") + + for flag in flags: + if flag and flag[0] == '!': + flag = flag[1:] + if flag.lower() not in ['syn', 'ack', 'rst', 'fin', 'urg', 'psh']: + print(f'Error: {flag} is not a valid TCP flag') + sys.exit(1) + else: + sys.exit(2) + + sys.exit(0) -- cgit v1.2.3 From dbdb736c851245036106800728a33695c0914607 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Fri, 14 Jan 2022 20:53:40 +0000 Subject: wireguard: T4183: Allow to set peer IPv6 link-local address --- interface-definitions/interfaces-wireguard.xml.in | 1 + 1 file changed, 1 insertion(+) diff --git a/interface-definitions/interfaces-wireguard.xml.in b/interface-definitions/interfaces-wireguard.xml.in index 7a7c9c1d9..1b4b4a816 100644 --- a/interface-definitions/interfaces-wireguard.xml.in +++ b/interface-definitions/interfaces-wireguard.xml.in @@ -101,6 +101,7 @@ + -- cgit v1.2.3 From 40f0e78dd94691d54ffd4d2e270ed071e2d2513a Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Sat, 15 Jan 2022 15:33:06 +0000 Subject: ntp: T4184: Fix allow-clients address NTP-server with option "allow-clients address x.x.x.x" should accept requests only from clients addresses which declared in configuration if this option exists Add "restrict default ignore" to fix it, in another case it responce to any address --- data/templates/ntp/ntpd.conf.tmpl | 1 + 1 file changed, 1 insertion(+) diff --git a/data/templates/ntp/ntpd.conf.tmpl b/data/templates/ntp/ntpd.conf.tmpl index 38e68f24f..e7afcc16b 100644 --- a/data/templates/ntp/ntpd.conf.tmpl +++ b/data/templates/ntp/ntpd.conf.tmpl @@ -27,6 +27,7 @@ restrict -6 ::1 {% if allow_clients is defined and allow_clients.address is defined %} # Allowed clients configuration +restrict default ignore {% for address in allow_clients.address %} restrict {{ address|address_from_cidr }} mask {{ address|netmask_from_cidr }} nomodify notrap nopeer {% endfor %} -- cgit v1.2.3 From ba9dc4c2ff89a7a71b84bc84db20e89f604919f2 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 15 Jan 2022 17:41:10 +0100 Subject: smoketest: ntp: re-organize testcases Drop the overcomplex function get_config_value() to search for NTPd configuration values. Rather assemble the required string and probe for its presence in the configuration like we do on most other smoketests. --- smoketest/scripts/cli/test_system_ntp.py | 58 ++++++++++++++------------------ 1 file changed, 25 insertions(+), 33 deletions(-) diff --git a/smoketest/scripts/cli/test_system_ntp.py b/smoketest/scripts/cli/test_system_ntp.py index e8cc64463..c69144d6c 100755 --- a/smoketest/scripts/cli/test_system_ntp.py +++ b/smoketest/scripts/cli/test_system_ntp.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2020 VyOS maintainers and contributors +# Copyright (C) 2019-2022 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 @@ -14,7 +14,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import re import unittest from base_vyostest_shim import VyOSUnitTestSHIM @@ -29,17 +28,14 @@ PROCESS_NAME = 'ntpd' NTP_CONF = '/run/ntpd/ntpd.conf' base_path = ['system', 'ntp'] -def get_config_value(key): - tmp = read_file(NTP_CONF) - tmp = re.findall(r'\n?{}\s+(.*)'.format(key), tmp) - # remove possible trailing whitespaces - return [item.strip() for item in tmp] - class TestSystemNTP(VyOSUnitTestSHIM.TestCase): - def setUp(self): + @classmethod + def setUpClass(cls): + super(cls, cls).setUpClass() + # ensure we can also run this test on a live system - so lets clean # out the current configuration :) - self.cli_delete(base_path) + cls.cli_delete(cls, base_path) def tearDown(self): self.cli_delete(base_path) @@ -47,35 +43,38 @@ class TestSystemNTP(VyOSUnitTestSHIM.TestCase): self.assertFalse(process_named_running(PROCESS_NAME)) - def test_ntp_options(self): + def test_01_ntp_options(self): # Test basic NTP support with multiple servers and their options servers = ['192.0.2.1', '192.0.2.2'] options = ['noselect', 'preempt', 'prefer'] - ntp_pool = 'pool.vyos.io' + pools = ['pool.vyos.io'] for server in servers: for option in options: self.cli_set(base_path + ['server', server, option]) # Test NTP pool - self.cli_set(base_path + ['server', ntp_pool, 'pool']) + for pool in pools: + self.cli_set(base_path + ['server', pool, 'pool']) # commit changes self.cli_commit() # Check generated configuration - tmp = get_config_value('server') - for server in servers: - test = f'{server} iburst ' + ' '.join(options) - self.assertTrue(test in tmp) + config = read_file(NTP_CONF) + self.assertIn('driftfile /var/lib/ntp/ntp.drift', config) + self.assertIn('restrict default noquery nopeer notrap nomodify', config) + self.assertIn('restrict source nomodify notrap noquery', config) + self.assertIn('restrict 127.0.0.1', config) + self.assertIn('restrict -6 ::1', config) - tmp = get_config_value('pool') - self.assertTrue(f'{ntp_pool} iburst' in tmp) + for server in servers: + self.assertIn(f'server {server} iburst ' + ' '.join(options), config) - # Check for running process - self.assertTrue(process_named_running(PROCESS_NAME)) + for pool in pools: + self.assertIn(f'pool {pool} iburst', config) - def test_ntp_clients(self): + def test_02_ntp_clients(self): # Test the allowed-networks statement listen_address = ['127.0.0.1', '::1'] for listen in listen_address: @@ -96,23 +95,16 @@ class TestSystemNTP(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Check generated client address configuration + config = read_file(NTP_CONF) for network in networks: network_address = address_from_cidr(network) network_netmask = netmask_from_cidr(network) - - tmp = get_config_value(f'restrict {network_address}')[0] - test = f'mask {network_netmask} nomodify notrap nopeer' - self.assertTrue(tmp in test) + self.assertIn(f'restrict {network_address} mask {network_netmask} nomodify notrap nopeer', config) # Check listen address - tmp = get_config_value('interface') - test = ['ignore wildcard'] + self.assertIn('interface ignore wildcard', config) for listen in listen_address: - test.append(f'listen {listen}') - self.assertEqual(tmp, test) - - # Check for running process - self.assertTrue(process_named_running(PROCESS_NAME)) + self.assertIn(f'interface listen {listen}', config) if __name__ == '__main__': unittest.main(verbosity=2) -- cgit v1.2.3 From 3ef881fcc3aada5846e3dd9ec20054c0e7261f46 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 15 Jan 2022 17:42:56 +0100 Subject: smoketest: ntp: T4184: check for "restrict default ignore" presencex --- smoketest/scripts/cli/test_system_ntp.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/smoketest/scripts/cli/test_system_ntp.py b/smoketest/scripts/cli/test_system_ntp.py index c69144d6c..c8cf04b7d 100755 --- a/smoketest/scripts/cli/test_system_ntp.py +++ b/smoketest/scripts/cli/test_system_ntp.py @@ -96,6 +96,8 @@ class TestSystemNTP(VyOSUnitTestSHIM.TestCase): # Check generated client address configuration config = read_file(NTP_CONF) + self.assertIn('restrict default ignore', config) + for network in networks: network_address = address_from_cidr(network) network_netmask = netmask_from_cidr(network) -- cgit v1.2.3 From 3399e0df679f197270f85200ecbba637e65e57f3 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 16 Jan 2022 11:15:21 +0100 Subject: bgp: T3741: remove unnecessary exit() in migration script 1 -> 2 --- src/migration-scripts/bgp/1-to-2 | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/migration-scripts/bgp/1-to-2 b/src/migration-scripts/bgp/1-to-2 index 4c6d5ceb8..4aa24bf3c 100755 --- a/src/migration-scripts/bgp/1-to-2 +++ b/src/migration-scripts/bgp/1-to-2 @@ -52,8 +52,6 @@ if config.exists(base + ['parameters', 'default', 'no-ipv4-unicast']): if len(config.list_nodes(base + ['parameters'])) == 0: config.delete(base + ['parameters']) - exit(0) - # As we now install a new default option into BGP we need to migrate all # existing BGP neighbors and restore the old behavior if config.exists(base + ['neighbor']): -- cgit v1.2.3 From b8039c9888bdca112f4d23d9ee99726ce6d79707 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 16 Jan 2022 11:16:04 +0100 Subject: dns-forwarding: T1595: remove unnecessary nesting in migration script 1 -> 2 --- src/migration-scripts/dns-forwarding/1-to-2 | 83 ++++++++++++++--------------- 1 file changed, 40 insertions(+), 43 deletions(-) diff --git a/src/migration-scripts/dns-forwarding/1-to-2 b/src/migration-scripts/dns-forwarding/1-to-2 index ba10c26f2..a8c930be7 100755 --- a/src/migration-scripts/dns-forwarding/1-to-2 +++ b/src/migration-scripts/dns-forwarding/1-to-2 @@ -16,7 +16,7 @@ # # This migration script will remove the deprecated 'listen-on' statement -# from the dns forwarding service and will add the corresponding +# from the dns forwarding service and will add the corresponding # listen-address nodes instead. This is required as PowerDNS can only listen # on interface addresses and not on interface names. @@ -37,53 +37,50 @@ with open(file_name, 'r') as f: config = ConfigTree(config_file) base = ['service', 'dns', 'forwarding'] -if not config.exists(base): +if not config.exists(base + ['listen-on']): # Nothing to do exit(0) -if config.exists(base + ['listen-on']): - listen_intf = config.return_values(base + ['listen-on']) - # Delete node with abandoned command - config.delete(base + ['listen-on']) +listen_intf = config.return_values(base + ['listen-on']) +# Delete node with abandoned command +config.delete(base + ['listen-on']) - # retrieve interface addresses for every configured listen-on interface - listen_addr = [] - for intf in listen_intf: - # we need to evaluate the interface section before manipulating the 'intf' variable - section = Interface.section(intf) - if not section: - raise ValueError(f'Invalid interface name {intf}') +# retrieve interface addresses for every configured listen-on interface +listen_addr = [] +for intf in listen_intf: + # we need to evaluate the interface section before manipulating the 'intf' variable + section = Interface.section(intf) + if not section: + raise ValueError(f'Invalid interface name {intf}') - # we need to treat vif and vif-s interfaces differently, - # both "real interfaces" use dots for vlan identifiers - those - # need to be exchanged with vif and vif-s identifiers - if intf.count('.') == 1: - # this is a regular VLAN interface - intf = intf.split('.')[0] + ' vif ' + intf.split('.')[1] - elif intf.count('.') == 2: - # this is a QinQ VLAN interface - intf = intf.split('.')[0] + ' vif-s ' + intf.split('.')[1] + ' vif-c ' + intf.split('.')[2] - - # retrieve corresponding interface addresses in CIDR format - # those need to be converted in pure IP addresses without network information - path = ['interfaces', section, intf, 'address'] - try: - for addr in config.return_values(path): - listen_addr.append( ip_interface(addr).ip ) - except: - # Some interface types do not use "address" option (e.g. OpenVPN) - # and may not even have a fixed address - print("Could not retrieve the address of the interface {} from the config".format(intf)) - print("You will need to update your DNS forwarding configuration manually") - - for addr in listen_addr: - config.set(base + ['listen-address'], value=addr, replace=False) + # we need to treat vif and vif-s interfaces differently, + # both "real interfaces" use dots for vlan identifiers - those + # need to be exchanged with vif and vif-s identifiers + if intf.count('.') == 1: + # this is a regular VLAN interface + intf = intf.split('.')[0] + ' vif ' + intf.split('.')[1] + elif intf.count('.') == 2: + # this is a QinQ VLAN interface + intf = intf.split('.')[0] + ' vif-s ' + intf.split('.')[1] + ' vif-c ' + intf.split('.')[2] + # retrieve corresponding interface addresses in CIDR format + # those need to be converted in pure IP addresses without network information + path = ['interfaces', section, intf, 'address'] try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) + for addr in config.return_values(path): + listen_addr.append( ip_interface(addr).ip ) + except: + # Some interface types do not use "address" option (e.g. OpenVPN) + # and may not even have a fixed address + print("Could not retrieve the address of the interface {} from the config".format(intf)) + print("You will need to update your DNS forwarding configuration manually") -exit(0) +for addr in listen_addr: + config.set(base + ['listen-address'], value=addr, replace=False) + +try: + with open(file_name, 'w') as f: + f.write(config.to_string()) +except OSError as e: + print(f'Failed to save the modified config: {e}') + exit(1) -- cgit v1.2.3 From 9f52a4f4ea9990bcf3b8c27a472fb47653bbeb0d Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 16 Jan 2022 18:17:20 +0100 Subject: Revert "migrator: interfaces: T4171: bugfix ConfigTreeError" This reverts commit 391ce22b76190309f81e048ebffab778b0fdee1d. --- src/migration-scripts/interfaces/11-to-12 | 2 -- src/migration-scripts/interfaces/12-to-13 | 2 -- src/migration-scripts/interfaces/22-to-23 | 2 -- src/migration-scripts/interfaces/9-to-10 | 2 -- 4 files changed, 8 deletions(-) diff --git a/src/migration-scripts/interfaces/11-to-12 b/src/migration-scripts/interfaces/11-to-12 index ca4ebe741..0dad24642 100755 --- a/src/migration-scripts/interfaces/11-to-12 +++ b/src/migration-scripts/interfaces/11-to-12 @@ -31,8 +31,6 @@ if __name__ == '__main__': config_file = f.read() config = ConfigTree(config_file) - if not config.exists(['interfaces']): - exit(0) for type in config.list_nodes(['interfaces']): for interface in config.list_nodes(['interfaces', type]): diff --git a/src/migration-scripts/interfaces/12-to-13 b/src/migration-scripts/interfaces/12-to-13 index fbd7e6f47..f866ca9a6 100755 --- a/src/migration-scripts/interfaces/12-to-13 +++ b/src/migration-scripts/interfaces/12-to-13 @@ -33,8 +33,6 @@ if __name__ == '__main__': config_file = f.read() config = ConfigTree(config_file) - if not config.exists(['interfaces']): - exit(0) # # T2903 diff --git a/src/migration-scripts/interfaces/22-to-23 b/src/migration-scripts/interfaces/22-to-23 index 734aa1fd8..06e07572f 100755 --- a/src/migration-scripts/interfaces/22-to-23 +++ b/src/migration-scripts/interfaces/22-to-23 @@ -84,8 +84,6 @@ if __name__ == '__main__': config_file = f.read() config = ConfigTree(config_file) - if not config.exists(['interfaces']): - exit(0) # # Migrate "interface ethernet eth0 ip ospf" to "protocols ospf interface eth0" diff --git a/src/migration-scripts/interfaces/9-to-10 b/src/migration-scripts/interfaces/9-to-10 index c6311a24d..4aa2c42b5 100755 --- a/src/migration-scripts/interfaces/9-to-10 +++ b/src/migration-scripts/interfaces/9-to-10 @@ -32,8 +32,6 @@ if __name__ == '__main__': config_file = f.read() config = ConfigTree(config_file) - if not config.exists(['interfaces']): - exit(0) for intf_type in config.list_nodes(['interfaces']): for intf in config.list_nodes(['interfaces', intf_type]): -- cgit v1.2.3 From 7e731c0ef503334eaab2bfd723163a9749d64da2 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 16 Jan 2022 18:17:24 +0100 Subject: Revert "migrator: interfaces: T4171: bugfix ConfigTreeError" This reverts commit 29efbf51efea559773f61703f11a77a8aee6de36. --- src/migration-scripts/interfaces/5-to-6 | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/migration-scripts/interfaces/5-to-6 b/src/migration-scripts/interfaces/5-to-6 index dc8d9554f..ae79c1d1b 100755 --- a/src/migration-scripts/interfaces/5-to-6 +++ b/src/migration-scripts/interfaces/5-to-6 @@ -107,15 +107,10 @@ if __name__ == '__main__': config_file = f.read() config = ConfigTree(config_file) - base = ['interfaces'] - - if not config.exists(base): - # Nothing to do - exit(0) # list all individual interface types like dummy, ethernet and so on - for if_type in config.list_nodes(base): - base_if_type = base + [if_type] + for if_type in config.list_nodes(['interfaces']): + base_if_type = ['interfaces', if_type] # for every individual interface we need to check if there is an # ipv6 ra configured ... and also for every VIF (VLAN) interface -- cgit v1.2.3 From 64668771d5f14fc4b68fff382d166238c164bdde Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Sat, 15 Jan 2022 12:48:48 +0100 Subject: firewall: policy: T4178: Migrate and refactor tcp flags * Add support for ECN and CWR flags --- .../include/firewall/common-rule.xml.i | 51 +-------- .../include/firewall/tcp-flags.xml.i | 119 +++++++++++++++++++++ .../include/policy/route-common-rule-ipv6.xml.i | 51 +-------- .../include/policy/route-common-rule.xml.i | 51 +-------- python/vyos/firewall.py | 10 +- smoketest/configs/dialup-router-medium-vpn | 9 ++ smoketest/scripts/cli/test_firewall.py | 16 +-- smoketest/scripts/cli/test_policy_route.py | 6 +- src/conf_mode/firewall.py | 12 ++- src/conf_mode/policy-route.py | 14 ++- src/migration-scripts/firewall/6-to-7 | 21 ++++ src/migration-scripts/policy/1-to-2 | 19 ++++ src/validators/tcp-flag | 14 ++- 13 files changed, 213 insertions(+), 180 deletions(-) create mode 100644 interface-definitions/include/firewall/tcp-flags.xml.i diff --git a/interface-definitions/include/firewall/common-rule.xml.i b/interface-definitions/include/firewall/common-rule.xml.i index 6e8203c88..5ffbd639c 100644 --- a/interface-definitions/include/firewall/common-rule.xml.i +++ b/interface-definitions/include/firewall/common-rule.xml.i @@ -264,56 +264,7 @@ - - - TCP flags to match - - - - - TCP flags to match - - txt - Multiple comma-separated flags - - - syn - Syncronise flag - - - ack - Acknowledge flag - - - fin - Finish flag - - - rst - Reset flag - - - urg - Urgent flag - - - psh - Push flag - - - - \n When specifying more than one flag, flags should be comma-separated.\n For example: value of 'SYN,!ACK,!FIN,!RST' will only match packets with\n the SYN flag set, and the ACK, FIN and RST flags unset - - - syn ack fin rst urg psh - - - - - - - - +#include Time to match rule diff --git a/interface-definitions/include/firewall/tcp-flags.xml.i b/interface-definitions/include/firewall/tcp-flags.xml.i new file mode 100644 index 000000000..b99896687 --- /dev/null +++ b/interface-definitions/include/firewall/tcp-flags.xml.i @@ -0,0 +1,119 @@ + + + + TCP flags to match + + + + + TCP flags to match + + + + + Synchronise flag + + + + + + Acknowledge flag + + + + + + Finish flag + + + + + + Reset flag + + + + + + Urgent flag + + + + + + Push flag + + + + + + Explicit Congestion Notification flag + + + + + + Congestion Window Reduced flag + + + + + + Match flags not set + + + + + Synchronise flag + + + + + + Acknowledge flag + + + + + + Finish flag + + + + + + Reset flag + + + + + + Urgent flag + + + + + + Push flag + + + + + + Explicit Congestion Notification flag + + + + + + Congestion Window Reduced flag + + + + + + + + + + diff --git a/interface-definitions/include/policy/route-common-rule-ipv6.xml.i b/interface-definitions/include/policy/route-common-rule-ipv6.xml.i index b8fee4b7b..735edbd48 100644 --- a/interface-definitions/include/policy/route-common-rule-ipv6.xml.i +++ b/interface-definitions/include/policy/route-common-rule-ipv6.xml.i @@ -320,56 +320,7 @@ - - - TCP flags to match - - - - - TCP flags to match - - txt - Multiple comma-separated flags - - - syn - Syncronise flag - - - ack - Acknowledge flag - - - fin - Finish flag - - - rst - Reset flag - - - urg - Urgent flag - - - psh - Push flag - - - - \n When specifying more than one flag, flags should be comma-separated.\n For example: value of 'SYN,!ACK,!FIN,!RST' will only match packets with\n the SYN flag set, and the ACK, FIN and RST flags unset - - - syn ack fin rst urg psh - - - - - - - - +#include Time to match rule diff --git a/interface-definitions/include/policy/route-common-rule.xml.i b/interface-definitions/include/policy/route-common-rule.xml.i index 17b47474d..4452f78fc 100644 --- a/interface-definitions/include/policy/route-common-rule.xml.i +++ b/interface-definitions/include/policy/route-common-rule.xml.i @@ -320,56 +320,7 @@ - - - TCP flags to match - - - - - TCP flags to match - - txt - Multiple comma-separated flags - - - syn - Syncronise flag - - - ack - Acknowledge flag - - - fin - Finish flag - - - rst - Reset flag - - - urg - Urgent flag - - - psh - Push flag - - - - \n When specifying more than one flag, flags should be comma-separated.\n For example: value of 'SYN,!ACK,!FIN,!RST' will only match packets with\n the SYN flag set, and the ACK, FIN and RST flags unset - - - syn ack fin rst urg psh - - - - - - - - +#include Time to match rule diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py index acde9f913..ad84393df 100644 --- a/python/vyos/firewall.py +++ b/python/vyos/firewall.py @@ -185,14 +185,8 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name): return " ".join(output) def parse_tcp_flags(flags): - all_flags = [] - include = [] - for flag in flags.split(","): - if flag[0] == '!': - flag = flag[1:].lower() - else: - include.append(flag.lower()) - all_flags.append(flag.lower()) + include = [flag for flag in flags if flag != 'not'] + all_flags = include + [flag for flag in flags['not']] if 'not' in flags else [] return f'tcp flags & ({"|".join(all_flags)}) == {"|".join(include)}' def parse_time(time): diff --git a/smoketest/configs/dialup-router-medium-vpn b/smoketest/configs/dialup-router-medium-vpn index 7ca540b66..63d955738 100644 --- a/smoketest/configs/dialup-router-medium-vpn +++ b/smoketest/configs/dialup-router-medium-vpn @@ -6,6 +6,15 @@ firewall { ipv6-src-route disable ip-src-route disable log-martians enable + name test_tcp_flags { + rule 1 { + action drop + protocol tcp + tcp { + flags SYN,ACK,!RST,!FIN + } + } + } options { interface vtun0 { adjust-mss 1380 diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py index 2b3b354ba..c70743a9f 100755 --- a/smoketest/scripts/cli/test_firewall.py +++ b/smoketest/scripts/cli/test_firewall.py @@ -53,7 +53,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'source', 'group', 'network-group', 'smoketest_network']) self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'destination', 'address', '172.16.10.10']) self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'destination', 'group', 'port-group', 'smoketest_port']) - self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'protocol', 'tcp']) + self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'protocol', 'tcp_udp']) self.cli_set(['interfaces', 'ethernet', 'eth0', 'firewall', 'in', 'name', 'smoketest']) @@ -61,7 +61,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): nftables_search = [ ['iifname "eth0"', 'jump smoketest'], - ['ip saddr { 172.16.99.0/24 }', 'ip daddr 172.16.10.10', 'tcp dport { 53, 123 }', 'return'], + ['ip saddr { 172.16.99.0/24 }', 'ip daddr 172.16.10.10', 'th dport { 53, 123 }', 'return'], ] nftables_output = cmd('sudo nft list table ip filter') @@ -72,7 +72,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): if all(item in line for item in search): matched = True break - self.assertTrue(matched) + self.assertTrue(matched, msg=search) def test_basic_rules(self): self.cli_set(['firewall', 'name', 'smoketest', 'default-action', 'drop']) @@ -80,8 +80,10 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'source', 'address', '172.16.20.10']) self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'destination', 'address', '172.16.10.10']) self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'action', 'reject']) - self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'protocol', 'tcp_udp']) + self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'protocol', 'tcp']) self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'destination', 'port', '8888']) + self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'tcp', 'flags', 'syn']) + self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'tcp', 'flags', 'not', 'ack']) self.cli_set(['interfaces', 'ethernet', 'eth0', 'firewall', 'in', 'name', 'smoketest']) @@ -90,7 +92,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): nftables_search = [ ['iifname "eth0"', 'jump smoketest'], ['saddr 172.16.20.10', 'daddr 172.16.10.10', 'return'], - ['meta l4proto { tcp, udp }', 'th dport { 8888 }', 'reject'], + ['tcp flags & (syn | ack) == syn', 'tcp dport { 8888 }', 'reject'], ['smoketest default-action', 'drop'] ] @@ -102,7 +104,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): if all(item in line for item in search): matched = True break - self.assertTrue(matched) + self.assertTrue(matched, msg=search) def test_basic_rules_ipv6(self): self.cli_set(['firewall', 'ipv6-name', 'v6-smoketest', 'default-action', 'drop']) @@ -132,7 +134,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): if all(item in line for item in search): matched = True break - self.assertTrue(matched) + self.assertTrue(matched, msg=search) def test_state_policy(self): self.cli_set(['firewall', 'state-policy', 'established', 'action', 'accept']) diff --git a/smoketest/scripts/cli/test_policy_route.py b/smoketest/scripts/cli/test_policy_route.py index 4463a2255..9035f0832 100755 --- a/smoketest/scripts/cli/test_policy_route.py +++ b/smoketest/scripts/cli/test_policy_route.py @@ -63,8 +63,10 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): self.assertTrue(matched) def test_pbr_table(self): - self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'protocol', 'tcp_udp']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'protocol', 'tcp']) self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'destination', 'port', '8888']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'tcp', 'flags', 'syn']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'tcp', 'flags', 'not', 'ack']) self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'set', 'table', table_id]) self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '1', 'protocol', 'tcp_udp']) self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '1', 'destination', 'port', '8888']) @@ -81,7 +83,7 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): nftables_search = [ ['iifname "eth0"', 'jump VYOS_PBR_smoketest'], - ['meta l4proto { tcp, udp }', 'th dport { 8888 }', 'meta mark set ' + mark_hex] + ['tcp flags & (syn | ack) == syn', 'tcp dport { 8888 }', 'meta mark set ' + mark_hex] ] nftables_output = cmd('sudo nft list table ip mangle') diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index 853470fd8..906d477b0 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -142,8 +142,16 @@ def verify_rule(firewall, rule_conf, ipv6): if not {'count', 'time'} <= set(rule_conf['recent']): raise ConfigError('Recent "count" and "time" values must be defined') - if dict_search_args(rule_conf, 'tcp', 'flags') and dict_search_args(rule_conf, 'protocol') != 'tcp': - raise ConfigError('Protocol must be tcp when specifying tcp flags') + tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags') + if tcp_flags: + if dict_search_args(rule_conf, 'protocol') != 'tcp': + raise ConfigError('Protocol must be tcp when specifying tcp flags') + + not_flags = dict_search_args(rule_conf, 'tcp', 'flags', 'not') + if not_flags: + duplicates = [flag for flag in tcp_flags if flag in not_flags] + if duplicates: + raise ConfigError(f'Cannot match a tcp flag as set and not set') for side in ['destination', 'source']: if side in rule_conf: diff --git a/src/conf_mode/policy-route.py b/src/conf_mode/policy-route.py index 30597ef4e..eb13788dd 100755 --- a/src/conf_mode/policy-route.py +++ b/src/conf_mode/policy-route.py @@ -97,11 +97,19 @@ def verify_rule(policy, name, rule_conf, ipv6): if 'set' in rule_conf: if 'tcp_mss' in rule_conf['set']: tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags') - if not tcp_flags or 'SYN' not in tcp_flags.split(","): + if not tcp_flags or 'syn' not in tcp_flags: raise ConfigError(f'{name} rule {rule_id}: TCP SYN flag must be set to modify TCP-MSS') - if dict_search_args(rule_conf, 'tcp', 'flags') and dict_search_args(rule_conf, 'protocol') != 'tcp': - raise ConfigError(f'{name} rule {rule_id}: TCP flags can only be set if protocol is set to TCP') + tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags') + if tcp_flags: + if dict_search_args(rule_conf, 'protocol') != 'tcp': + raise ConfigError('Protocol must be tcp when specifying tcp flags') + + not_flags = dict_search_args(rule_conf, 'tcp', 'flags', 'not') + if not_flags: + duplicates = [flag for flag in tcp_flags if flag in not_flags] + if duplicates: + raise ConfigError(f'Cannot match a tcp flag as set and not set') for side in ['destination', 'source']: if side in rule_conf: diff --git a/src/migration-scripts/firewall/6-to-7 b/src/migration-scripts/firewall/6-to-7 index 4a4097d56..bc0b19325 100755 --- a/src/migration-scripts/firewall/6-to-7 +++ b/src/migration-scripts/firewall/6-to-7 @@ -17,6 +17,7 @@ # T2199: Remove unavailable nodes due to XML/Python implementation using nftables # monthdays: nftables does not have a monthdays equivalent # utc: nftables userspace uses localtime and calculates the UTC offset automatically +# T4178: Update tcp flags to use multi value node from sys import argv from sys import exit @@ -45,6 +46,7 @@ if config.exists(base + ['name']): if config.exists(base + ['name', name, 'rule']): for rule in config.list_nodes(base + ['name', name, 'rule']): rule_time = base + ['name', name, 'rule', rule, 'time'] + rule_tcp_flags = base + ['name', name, 'rule', rule, 'tcp', 'flags'] if config.exists(rule_time + ['monthdays']): config.delete(rule_time + ['monthdays']) @@ -52,11 +54,21 @@ if config.exists(base + ['name']): if config.exists(rule_time + ['utc']): config.delete(rule_time + ['utc']) + if config.exists(rule_tcp_flags): + tmp = config.return_value(rule_tcp_flags) + config.delete(rule_tcp_flags) + for flag in tmp.split(","): + if flag[0] == '!': + config.set(rule_tcp_flags + ['not', flag[1:].lower()]) + else: + config.set(rule_tcp_flags + [flag.lower()]) + if config.exists(base + ['ipv6-name']): for name in config.list_nodes(base + ['ipv6-name']): if config.exists(base + ['ipv6-name', name, 'rule']): for rule in config.list_nodes(base + ['ipv6-name', name, 'rule']): rule_time = base + ['ipv6-name', name, 'rule', rule, 'time'] + rule_tcp_flags = base + ['ipv6-name', name, 'rule', rule, 'tcp', 'flags'] if config.exists(rule_time + ['monthdays']): config.delete(rule_time + ['monthdays']) @@ -64,6 +76,15 @@ if config.exists(base + ['ipv6-name']): if config.exists(rule_time + ['utc']): config.delete(rule_time + ['utc']) + if config.exists(rule_tcp_flags): + tmp = config.return_value(rule_tcp_flags) + config.delete(rule_tcp_flags) + for flag in tmp.split(","): + if flag[0] == '!': + config.set(rule_tcp_flags + ['not', flag[1:].lower()]) + else: + config.set(rule_tcp_flags + [flag.lower()]) + try: with open(file_name, 'w') as f: f.write(config.to_string()) diff --git a/src/migration-scripts/policy/1-to-2 b/src/migration-scripts/policy/1-to-2 index 7ffceef22..eebbf9d41 100755 --- a/src/migration-scripts/policy/1-to-2 +++ b/src/migration-scripts/policy/1-to-2 @@ -16,6 +16,7 @@ # T4170: rename "policy ipv6-route" to "policy route6" to match common # IPv4/IPv6 schema +# T4178: Update tcp flags to use multi value node from sys import argv from sys import exit @@ -41,6 +42,24 @@ if not config.exists(base): config.rename(base, 'route6') config.set_tag(['policy', 'route6']) +for route in ['route', 'route6']: + route_path = ['policy', route] + if config.exists(route_path): + for name in config.list_nodes(route_path): + if config.exists(route_path + [name, 'rule']): + for rule in config.list_nodes(route_path + [name, 'rule']): + rule_tcp_flags = route_path + [name, 'rule', rule, 'tcp', 'flags'] + + if config.exists(rule_tcp_flags): + tmp = config.return_value(rule_tcp_flags) + config.delete(rule_tcp_flags) + for flag in tmp.split(","): + for flag in tmp.split(","): + if flag[0] == '!': + config.set(rule_tcp_flags + ['not', flag[1:].lower()]) + else: + config.set(rule_tcp_flags + [flag.lower()]) + if config.exists(['interfaces']): def if_policy_rename(config, path): if config.exists(path + ['policy', 'ipv6-route']): diff --git a/src/validators/tcp-flag b/src/validators/tcp-flag index 86ebec189..1496b904a 100755 --- a/src/validators/tcp-flag +++ b/src/validators/tcp-flag @@ -5,14 +5,12 @@ import re if __name__ == '__main__': if len(sys.argv)>1: - flags = sys.argv[1].split(",") - - for flag in flags: - if flag and flag[0] == '!': - flag = flag[1:] - if flag.lower() not in ['syn', 'ack', 'rst', 'fin', 'urg', 'psh']: - print(f'Error: {flag} is not a valid TCP flag') - sys.exit(1) + flag = sys.argv[1] + if flag and flag[0] == '!': + flag = flag[1:] + if flag not in ['syn', 'ack', 'rst', 'fin', 'urg', 'psh', 'ecn', 'cwr']: + print(f'Error: {flag} is not a valid TCP flag') + sys.exit(1) else: sys.exit(2) -- cgit v1.2.3 From ab4dd3b7a65d10a936071ee1bf0c5c2c884bb1f5 Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Sun, 16 Jan 2022 22:38:43 +0100 Subject: zone-policy: T3873: Fix intra-zone-filtering return to zone default-action --- data/templates/zone_policy/nftables.tmpl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/data/templates/zone_policy/nftables.tmpl b/data/templates/zone_policy/nftables.tmpl index fae6e8c4f..e59208a0d 100644 --- a/data/templates/zone_policy/nftables.tmpl +++ b/data/templates/zone_policy/nftables.tmpl @@ -29,6 +29,9 @@ table ip filter { {% else %} chain VZONE_{{ zone_name }} { iifname { {{ zone_conf.interface | join(",") }} } counter {{ zone_conf | nft_intra_zone_action(ipv6=False) }} +{% if zone_conf.intra_zone_filtering is defined %} + iifname { {{ zone_conf.interface | join(",") }} } counter return +{% endif %} {% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall.name is defined %} {% if zone[from_zone].local_zone is not defined %} iifname { {{ zone[from_zone].interface | join(",") }} } counter jump {{ from_conf.firewall.name }} @@ -63,6 +66,9 @@ table ip6 filter { {% else %} chain VZONE6_{{ zone_name }} { iifname { {{ zone_conf.interface | join(",") }} } counter {{ zone_conf | nft_intra_zone_action(ipv6=True) }} +{% if zone_conf.intra_zone_filtering is defined %} + iifname { {{ zone_conf.interface | join(",") }} } counter return +{% endif %} {% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall.ipv6_name is defined %} {% if zone[from_zone].local_zone is not defined %} iifname { {{ zone[from_zone].interface | join(",") }} } counter jump {{ from_conf.firewall.ipv6_name }} -- cgit v1.2.3 From 53c2b62dda5bcd1f605a8b9ea438f0f76e366e36 Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Mon, 17 Jan 2022 16:46:03 +0100 Subject: firewall: T2199: Fix `port-range` validator to accept service names --- src/validators/port-range | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/validators/port-range b/src/validators/port-range index 6c01048f0..5468000a7 100755 --- a/src/validators/port-range +++ b/src/validators/port-range @@ -3,6 +3,19 @@ import sys import re +from vyos.util import read_file + +services_file = '/etc/services' + +def get_services(): + names = [] + service_data = read_file(services_file, "") + for line in service_data.split("\n"): + if not line or line[0] == '#': + continue + names.append(line.split(None, 1)[0]) + return names + def error(port_range): print(f'Error: {port_range} is not a valid port or port range') sys.exit(1) @@ -16,8 +29,11 @@ if __name__ == '__main__': error(port_range) if int(port_1) > int(port_2): error(port_range) - elif not port_range.isnumeric() or int(port_range) not in range(1, 65536): - error(port_range) + elif port_range.isnumeric() and int(port_range) not in range(1, 65536): + error(port_range) + elif not port_range.isnumeric() and port_range not in get_services(): + print(f'Error: {port_range} is not a valid service name') + sys.exit(1) else: sys.exit(2) -- cgit v1.2.3 From 385b72da4845e5c247aaeae9469ca04da216a4cb Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 17 Jan 2022 19:32:44 +0100 Subject: bgp: T3741: bugfix migrator - exit() was called without saving --- src/migration-scripts/bgp/1-to-2 | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/migration-scripts/bgp/1-to-2 b/src/migration-scripts/bgp/1-to-2 index 4aa24bf3c..e2d3fcd33 100755 --- a/src/migration-scripts/bgp/1-to-2 +++ b/src/migration-scripts/bgp/1-to-2 @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2022 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 @@ -20,7 +20,6 @@ from sys import argv from sys import exit from vyos.configtree import ConfigTree -from vyos.template import is_ipv4 if (len(argv) < 1): print("Must specify file name!") @@ -51,21 +50,21 @@ if config.exists(base + ['parameters', 'default', 'no-ipv4-unicast']): # Check if the "default" node is now empty, if so - remove it if len(config.list_nodes(base + ['parameters'])) == 0: config.delete(base + ['parameters']) +else: + # As we now install a new default option into BGP we need to migrate all + # existing BGP neighbors and restore the old behavior + if config.exists(base + ['neighbor']): + for neighbor in config.list_nodes(base + ['neighbor']): + peer_group = base + ['neighbor', neighbor, 'peer-group'] + if config.exists(peer_group): + peer_group_name = config.return_value(peer_group) + # peer group enables old behavior for neighbor - bail out + if config.exists(base + ['peer-group', peer_group_name, 'address-family', 'ipv4-unicast']): + continue -# As we now install a new default option into BGP we need to migrate all -# existing BGP neighbors and restore the old behavior -if config.exists(base + ['neighbor']): - for neighbor in config.list_nodes(base + ['neighbor']): - peer_group = base + ['neighbor', neighbor, 'peer-group'] - if config.exists(peer_group): - peer_group_name = config.return_value(peer_group) - # peer group enables old behavior for neighbor - bail out - if config.exists(base + ['peer-group', peer_group_name, 'address-family', 'ipv4-unicast']): - continue - - afi_ipv4 = base + ['neighbor', neighbor, 'address-family', 'ipv4-unicast'] - if not config.exists(afi_ipv4): - config.set(afi_ipv4) + afi_ipv4 = base + ['neighbor', neighbor, 'address-family', 'ipv4-unicast'] + if not config.exists(afi_ipv4): + config.set(afi_ipv4) try: with open(file_name, 'w') as f: -- cgit v1.2.3 From a7e14cba820fcb4a0f448c05d4480e00d26130ee Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Tue, 18 Jan 2022 18:45:25 +0100 Subject: firewall: T4188: Create default conntrack `FW_CONNTRACK` chain This chain was missing from the XML/Python rewrite thus all traffic fell through to the `notrack` rule. --- data/templates/firewall/nftables.tmpl | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/data/templates/firewall/nftables.tmpl b/data/templates/firewall/nftables.tmpl index e8fa4e306..81b2c0b98 100644 --- a/data/templates/firewall/nftables.tmpl +++ b/data/templates/firewall/nftables.tmpl @@ -175,6 +175,7 @@ table raw { counter jump VYOS_CT_IGNORE counter jump VYOS_CT_TIMEOUT counter jump VYOS_CT_PREROUTING_HOOK + counter jump FW_CONNTRACK notrack } @@ -183,6 +184,7 @@ table raw { counter jump VYOS_CT_IGNORE counter jump VYOS_CT_TIMEOUT counter jump VYOS_CT_OUTPUT_HOOK + counter jump FW_CONNTRACK notrack } @@ -220,6 +222,10 @@ table raw { chain VYOS_CT_OUTPUT_HOOK { return } + + chain FW_CONNTRACK { + accept + } } table ip6 raw { @@ -230,12 +236,14 @@ table ip6 raw { chain PREROUTING { type filter hook prerouting priority -300; policy accept; counter jump VYOS_CT_PREROUTING_HOOK + counter jump FW_CONNTRACK notrack } chain OUTPUT { type filter hook output priority -300; policy accept; counter jump VYOS_CT_OUTPUT_HOOK + counter jump FW_CONNTRACK notrack } @@ -246,5 +254,9 @@ table ip6 raw { chain VYOS_CT_OUTPUT_HOOK { return } + + chain FW_CONNTRACK { + accept + } } {% endif %} -- cgit v1.2.3 From 0a5a78621b2b28f06af1f40c10ee8bb880f860a0 Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Tue, 18 Jan 2022 15:29:03 +0100 Subject: firewall: T3560: Add support for MAC address groups --- data/templates/firewall/nftables-defines.tmpl | 5 ++++ interface-definitions/firewall.xml.in | 21 +++++++++++++++++ .../include/firewall/common-rule.xml.i | 3 +++ .../include/firewall/mac-group.xml.i | 10 ++++++++ .../firewall/source-destination-group-ipv6.xml.i | 1 + .../firewall/source-destination-group.xml.i | 1 + .../include/policy/route-common-rule-ipv6.xml.i | 3 +++ .../include/policy/route-common-rule.xml.i | 3 +++ python/vyos/firewall.py | 3 +++ smoketest/scripts/cli/test_firewall.py | 4 ++++ src/op_mode/firewall.py | 2 ++ src/validators/mac-address-firewall | 27 ++++++++++++++++++++++ 12 files changed, 83 insertions(+) create mode 100644 interface-definitions/include/firewall/mac-group.xml.i create mode 100755 src/validators/mac-address-firewall diff --git a/data/templates/firewall/nftables-defines.tmpl b/data/templates/firewall/nftables-defines.tmpl index 3578a9dc5..d9eb7c199 100644 --- a/data/templates/firewall/nftables-defines.tmpl +++ b/data/templates/firewall/nftables-defines.tmpl @@ -9,6 +9,11 @@ define A_{{ group_name }} = { {{ group_conf.address | join(",") }} } define A6_{{ group_name }} = { {{ group_conf.address | join(",") }} } {% endfor %} {% endif %} +{% if group.mac_group is defined %} +{% for group_name, group_conf in group.mac_group.items() %} +define M_{{ group_name }} = { {{ group_conf.mac_address | join(",") }} } +{% endfor %} +{% endif %} {% if group.network_group is defined %} {% for group_name, group_conf in group.network_group.items() %} define N_{{ group_name }} = { {{ group_conf.network | join(",") }} } diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in index fd98ae138..987ccaca6 100644 --- a/interface-definitions/firewall.xml.in +++ b/interface-definitions/firewall.xml.in @@ -144,6 +144,27 @@ + + + Firewall mac-group + + + #include + + + Mac-group member + + <MAC address> + MAC address to match + + + + + + + + + Firewall network-group diff --git a/interface-definitions/include/firewall/common-rule.xml.i b/interface-definitions/include/firewall/common-rule.xml.i index 5ffbd639c..521fe54f2 100644 --- a/interface-definitions/include/firewall/common-rule.xml.i +++ b/interface-definitions/include/firewall/common-rule.xml.i @@ -176,6 +176,9 @@ !<MAC address> Match everything except the specified MAC address + + + #include diff --git a/interface-definitions/include/firewall/mac-group.xml.i b/interface-definitions/include/firewall/mac-group.xml.i new file mode 100644 index 000000000..dbce3fc88 --- /dev/null +++ b/interface-definitions/include/firewall/mac-group.xml.i @@ -0,0 +1,10 @@ + + + + Group of MAC addresses + + firewall group mac-group + + + + \ No newline at end of file diff --git a/interface-definitions/include/firewall/source-destination-group-ipv6.xml.i b/interface-definitions/include/firewall/source-destination-group-ipv6.xml.i index 7815b78d4..c2cc7edb3 100644 --- a/interface-definitions/include/firewall/source-destination-group-ipv6.xml.i +++ b/interface-definitions/include/firewall/source-destination-group-ipv6.xml.i @@ -12,6 +12,7 @@ + #include Group of networks diff --git a/interface-definitions/include/firewall/source-destination-group.xml.i b/interface-definitions/include/firewall/source-destination-group.xml.i index 9a9bed0fe..ab11e89e9 100644 --- a/interface-definitions/include/firewall/source-destination-group.xml.i +++ b/interface-definitions/include/firewall/source-destination-group.xml.i @@ -12,6 +12,7 @@ + #include Group of networks diff --git a/interface-definitions/include/policy/route-common-rule-ipv6.xml.i b/interface-definitions/include/policy/route-common-rule-ipv6.xml.i index 735edbd48..406125e55 100644 --- a/interface-definitions/include/policy/route-common-rule-ipv6.xml.i +++ b/interface-definitions/include/policy/route-common-rule-ipv6.xml.i @@ -232,6 +232,9 @@ !<MAC address> Match everything except the specified MAC address + + + #include diff --git a/interface-definitions/include/policy/route-common-rule.xml.i b/interface-definitions/include/policy/route-common-rule.xml.i index 4452f78fc..33c4ba77c 100644 --- a/interface-definitions/include/policy/route-common-rule.xml.i +++ b/interface-definitions/include/policy/route-common-rule.xml.i @@ -232,6 +232,9 @@ !<MAC address> Match everything except the specified MAC address + + + #include diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py index ad84393df..2ab78ff18 100644 --- a/python/vyos/firewall.py +++ b/python/vyos/firewall.py @@ -108,6 +108,9 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name): elif 'network_group' in group: group_name = group['network_group'] output.append(f'{ip_name} {prefix}addr $N{def_suffix}_{group_name}') + if 'mac_group' in group: + group_name = group['mac_group'] + output.append(f'ether {prefix}addr $M_{group_name}') if 'port_group' in group: proto = rule_conf['protocol'] group_name = group['port_group'] diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py index c70743a9f..6b74e6c92 100755 --- a/smoketest/scripts/cli/test_firewall.py +++ b/smoketest/scripts/cli/test_firewall.py @@ -46,6 +46,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.cli_commit() def test_groups(self): + self.cli_set(['firewall', 'group', 'mac-group', 'smoketest_mac', 'mac-address', '00:01:02:03:04:05']) self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network', 'network', '172.16.99.0/24']) self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port', 'port', '53']) self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port', 'port', '123']) @@ -54,6 +55,8 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'destination', 'address', '172.16.10.10']) self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'destination', 'group', 'port-group', 'smoketest_port']) self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'protocol', 'tcp_udp']) + self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'action', 'accept']) + self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'source', 'group', 'mac-group', 'smoketest_mac']) self.cli_set(['interfaces', 'ethernet', 'eth0', 'firewall', 'in', 'name', 'smoketest']) @@ -62,6 +65,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): nftables_search = [ ['iifname "eth0"', 'jump smoketest'], ['ip saddr { 172.16.99.0/24 }', 'ip daddr 172.16.10.10', 'th dport { 53, 123 }', 'return'], + ['ether saddr { 00:01:02:03:04:05 }', 'return'] ] nftables_output = cmd('sudo nft list table ip filter') diff --git a/src/op_mode/firewall.py b/src/op_mode/firewall.py index 030a9b19a..b6bb5b802 100755 --- a/src/op_mode/firewall.py +++ b/src/op_mode/firewall.py @@ -272,6 +272,8 @@ def show_firewall_group(name=None): row.append("\n".join(sorted(group_conf['address'], key=ipaddress.ip_address))) elif 'network' in group_conf: row.append("\n".join(sorted(group_conf['network'], key=ipaddress.ip_network))) + elif 'mac_address' in group_conf: + row.append("\n".join(sorted(group_conf['mac_address']))) elif 'port' in group_conf: row.append("\n".join(sorted(group_conf['port']))) else: diff --git a/src/validators/mac-address-firewall b/src/validators/mac-address-firewall new file mode 100755 index 000000000..70551f86d --- /dev/null +++ b/src/validators/mac-address-firewall @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2022 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 . + +import re +import sys + +pattern = "^!?([0-9A-Fa-f]{2}:){5}([0-9A-Fa-f]{2})$" + +if __name__ == '__main__': + if len(sys.argv) != 2: + sys.exit(1) + if not re.match(pattern, sys.argv[1]): + sys.exit(1) + sys.exit(0) -- cgit v1.2.3 From 081fc4466f200bf358fdd78b755a8732518f7df4 Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Tue, 18 Jan 2022 12:16:23 +0100 Subject: firewall: policy: T1292: Clean up any rules required to delete a chain --- src/conf_mode/firewall.py | 12 ++++++++++++ src/conf_mode/policy-route.py | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index 906d477b0..ae46801c6 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -15,6 +15,7 @@ # along with this program. If not, see . import os +import re from glob import glob from json import loads @@ -212,6 +213,16 @@ def verify(firewall): return None +def cleanup_rule(table, jump_chain): + commands = [] + results = cmd(f'nft -a list table {table}').split("\n") + for line in results: + if f'jump {jump_chain}' in line: + handle_search = re.search('handle (\d+)', line) + if handle_search: + commands.append(f'delete rule {table} {chain} handle {handle_search[1]}') + return commands + def cleanup_commands(firewall): commands = [] for table in ['ip filter', 'ip6 filter']: @@ -234,6 +245,7 @@ def cleanup_commands(firewall): elif table == 'ip6 filter' and dict_search_args(firewall, 'ipv6_name', chain): commands.append(f'flush chain {table} {chain}') else: + commands += cleanup_rule(table, chain) commands.append(f'delete chain {table} {chain}') elif 'rule' in item: rule = item['rule'] diff --git a/src/conf_mode/policy-route.py b/src/conf_mode/policy-route.py index eb13788dd..ee5197af0 100755 --- a/src/conf_mode/policy-route.py +++ b/src/conf_mode/policy-route.py @@ -15,6 +15,7 @@ # along with this program. If not, see . import os +import re from json import loads from sys import exit @@ -160,6 +161,16 @@ def verify(policy): return None +def cleanup_rule(table, jump_chain): + commands = [] + results = cmd(f'nft -a list table {table}').split("\n") + for line in results: + if f'jump {jump_chain}' in line: + handle_search = re.search('handle (\d+)', line) + if handle_search: + commands.append(f'delete rule {table} {chain} handle {handle_search[1]}') + return commands + def cleanup_commands(policy): commands = [] for table in ['ip mangle', 'ip6 mangle']: @@ -178,6 +189,7 @@ def cleanup_commands(policy): elif table == 'ip6 mangle' and dict_search_args(policy, 'route6', chain.replace("VYOS_PBR6_", "", 1)): commands.append(f'flush chain {table} {chain}') else: + commands += cleanup_rule(table, chain) commands.append(f'delete chain {table} {chain}') return commands -- cgit v1.2.3 From f96a4fcd5d0cc4e43dd8163a81dd7ca66355c6b4 Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Tue, 18 Jan 2022 12:30:34 +0100 Subject: firewall: T2199: Raise ConfigError if deleted node is used in zone-policy --- src/conf_mode/firewall.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index ae46801c6..82223d60b 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -102,6 +102,35 @@ def get_firewall_interfaces(conf): out.update(find_interfaces(iftype_conf)) return out +def get_firewall_zones(conf): + used_v4 = [] + used_v6 = [] + zone_policy = conf.get_config_dict(['zone-policy'], key_mangling=('-', '_'), get_first_key=True, + no_tag_node_value_mangle=True) + + if 'zone' in zone_policy: + for zone, zone_conf in zone_policy['zone'].items(): + if 'from' in zone_conf: + for from_zone, from_conf in zone_conf['from'].items(): + name = dict_search_args(from_conf, 'firewall', 'name') + if name: + used_v4.append(name) + + ipv6_name = dict_search_args(from_conf, 'firewall', 'ipv6_name') + if ipv6_name: + used_v6.append(ipv6_name) + + if 'intra_zone_filtering' in zone_conf: + name = dict_search_args(zone_conf, 'intra_zone_filtering', 'firewall', 'name') + if name: + used_v4.append(name) + + ipv6_name = dict_search_args(zone_conf, 'intra_zone_filtering', 'firewall', 'ipv6_name') + if ipv6_name: + used_v6.append(ipv6_name) + + return {'name': used_v4, 'ipv6_name': used_v6} + def get_config(config=None): if config: conf = config @@ -117,6 +146,7 @@ def get_config(config=None): firewall['policy_resync'] = bool('group' in firewall or node_changed(conf, base + ['group'])) firewall['interfaces'] = get_firewall_interfaces(conf) + firewall['zone_policy'] = get_firewall_zones(conf) if 'config_trap' in firewall and firewall['config_trap'] == 'enable': diff = get_config_diff(conf) @@ -211,6 +241,11 @@ def verify(firewall): if ipv6_name and not dict_search_args(firewall, 'ipv6_name', ipv6_name): raise ConfigError(f'Firewall ipv6-name "{ipv6_name}" is still referenced on interface {ifname}') + for fw_name, used_names in firewall['zone_policy'].items(): + for name in used_names: + if not dict_search_args(firewall, fw_name, name): + raise ConfigError(f'Firewall {fw_name.replace("_", "-")} "{name}" is still referenced in zone-policy') + return None def cleanup_rule(table, jump_chain): -- cgit v1.2.3 From c31f085b5d87847320a239580f1fe3f1478541c0 Mon Sep 17 00:00:00 2001 From: fett0 Date: Wed, 19 Jan 2022 16:02:15 +0000 Subject: OSPF : T4195: ability to set maximum paths for OSPF --- data/templates/frr/ospfd.frr.tmpl | 3 +++ .../include/ospf/protocol-common-config.xml.i | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/data/templates/frr/ospfd.frr.tmpl b/data/templates/frr/ospfd.frr.tmpl index af66baf53..a6618b6af 100644 --- a/data/templates/frr/ospfd.frr.tmpl +++ b/data/templates/frr/ospfd.frr.tmpl @@ -126,6 +126,9 @@ router ospf {{ 'vrf ' + vrf if vrf is defined and vrf is not none }} {% if default_metric is defined and default_metric is not none %} default-metric {{ default_metric }} {% endif %} +{% if maximum_paths is defined and maximum_paths is not none %} + maximum-paths {{ maximum_paths }} +{% endif %} {% if distance is defined and distance is not none %} {% if distance.global is defined and distance.global is not none %} distance {{ distance.global }} diff --git a/interface-definitions/include/ospf/protocol-common-config.xml.i b/interface-definitions/include/ospf/protocol-common-config.xml.i index 688e78034..e783f4bec 100644 --- a/interface-definitions/include/ospf/protocol-common-config.xml.i +++ b/interface-definitions/include/ospf/protocol-common-config.xml.i @@ -289,6 +289,18 @@ + + + Maximum multiple paths (ECMP) + + u32:1-64 + Maximum multiple paths (ECMP) + + + + + + Administrative distance -- cgit v1.2.3 From d1d0150b6a40252700181530ca87c5699a4bd0b4 Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Thu, 20 Jan 2022 00:27:53 +0100 Subject: firewall: T2199: Add log prefix to match legacy perl behaviour Example syslog: [FWNAME-default-D] ... * Also clean-up firewall default-action --- data/templates/firewall/nftables-policy.tmpl | 10 ++-------- data/templates/firewall/nftables.tmpl | 14 ++------------ python/vyos/firewall.py | 3 ++- python/vyos/template.py | 13 +++++++++++++ 4 files changed, 19 insertions(+), 21 deletions(-) diff --git a/data/templates/firewall/nftables-policy.tmpl b/data/templates/firewall/nftables-policy.tmpl index 484b6f203..905ffcd09 100644 --- a/data/templates/firewall/nftables-policy.tmpl +++ b/data/templates/firewall/nftables-policy.tmpl @@ -25,11 +25,7 @@ table ip mangle { {{ rule_conf | nft_rule(route_text, rule_id, 'ip') }} {% endfor %} {% endif %} -{% if conf.default_action is defined %} - counter {{ conf.default_action | nft_action }} comment "{{ name_text }} default-action {{ conf.default_action }}" -{% else %} - counter return -{% endif %} + {{ conf | nft_default_rule(route_text) }} } {% endfor %} {%- endif %} @@ -52,9 +48,7 @@ table ip6 mangle { {{ rule_conf | nft_rule(route_text, rule_id, 'ip6') }} {% endfor %} {% endif %} -{% if conf.default_action is defined %} - counter {{ conf.default_action | nft_action }} comment "{{ name_text }} default-action {{ conf.default_action }}" -{% endif %} + {{ conf | nft_default_rule(route_text) }} } {% endfor %} {% endif %} diff --git a/data/templates/firewall/nftables.tmpl b/data/templates/firewall/nftables.tmpl index 81b2c0b98..33c821e84 100644 --- a/data/templates/firewall/nftables.tmpl +++ b/data/templates/firewall/nftables.tmpl @@ -32,18 +32,13 @@ table ip filter { {% endif %} {% if name is defined %} {% for name_text, conf in name.items() %} -{% set default_log = 'log' if 'enable_default_log' in conf else '' %} chain {{ name_text }} { {% if conf.rule is defined %} {% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not defined %} {{ rule_conf | nft_rule(name_text, rule_id) }} {% endfor %} {% endif %} -{% if conf.default_action is defined %} - counter {{ default_log }} {{ conf.default_action | nft_action }} comment "{{ name_text }} default-action {{ conf.default_action }}" -{% else %} - return -{% endif %} + {{ conf | nft_default_rule(name_text) }} } {% endfor %} {% endif %} @@ -87,18 +82,13 @@ table ip6 filter { {% endif %} {% if ipv6_name is defined %} {% for name_text, conf in ipv6_name.items() %} -{% set default_log = 'log' if 'enable_default_log' in conf else '' %} chain {{ name_text }} { {% if conf.rule is defined %} {% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not defined %} {{ rule_conf | nft_rule(name_text, rule_id, 'ip6') }} {% endfor %} {% endif %} -{% if conf.default_action is defined %} - counter {{ default_log }} {{ conf.default_action | nft_action }} comment "{{ name_text }} default-action {{ conf.default_action }}" -{% else %} - return -{% endif %} + {{ conf | nft_default_rule(name_text) }} } {% endfor %} {% endif %} diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py index 2ab78ff18..808e90e38 100644 --- a/python/vyos/firewall.py +++ b/python/vyos/firewall.py @@ -121,7 +121,8 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name): output.append(f'{proto} {prefix}port $P_{group_name}') if 'log' in rule_conf and rule_conf['log'] == 'enable': - output.append('log') + action = rule_conf['action'] if 'action' in rule_conf else 'accept' + output.append(f'log prefix "[{fw_name[:19]}-{rule_id}-{action[:1].upper()}] "') if 'hop_limit' in rule_conf: operators = {'eq': '==', 'gt': '>', 'lt': '<'} diff --git a/python/vyos/template.py b/python/vyos/template.py index 6f65c6c98..633b28ade 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -516,6 +516,19 @@ def nft_rule(rule_conf, fw_name, rule_id, ip_name='ip'): from vyos.firewall import parse_rule return parse_rule(rule_conf, fw_name, rule_id, ip_name) +@register_filter('nft_default_rule') +def nft_default_rule(fw_conf, fw_name): + output = ['counter'] + default_action = fw_conf.get('default_action', 'accept') + + if 'enable_default_log' in fw_conf: + action_suffix = default_action[:1].upper() + output.append(f'log prefix "[{fw_name[:19]}-default-{action_suffix}] "') + + output.append(nft_action(default_action)) + output.append(f'comment "{fw_name} default-action {default_action}"') + return " ".join(output) + @register_filter('nft_state_policy') def nft_state_policy(conf, state, ipv6=False): out = [f'ct state {state}'] -- cgit v1.2.3 From a41826759ae79907eee673414c2de35807fb6b3a Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Thu, 20 Jan 2022 08:32:03 -0600 Subject: interface-names: T3871: use tempfile during virtual migration Use tempfile to avoid race conditions during virtual migration. --- src/helpers/vyos_net_name | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/helpers/vyos_net_name b/src/helpers/vyos_net_name index afeef8f2d..1798e92db 100755 --- a/src/helpers/vyos_net_name +++ b/src/helpers/vyos_net_name @@ -20,12 +20,14 @@ import os import re import time import logging +import tempfile import threading from sys import argv from vyos.configtree import ConfigTree from vyos.defaults import directories from vyos.util import cmd, boot_configuration_complete +from vyos.migrator import VirtualMigrator vyos_udev_dir = directories['vyos_udev_dir'] vyos_log_dir = '/run/udev/log' @@ -139,14 +141,20 @@ def get_configfile_interfaces() -> dict: try: config = ConfigTree(config_file) except Exception: - logging.debug(f"updating component version string syntax") try: - # this will update the component version string in place, for - # updates 1.2 --> 1.3/1.4 - os.system(f'/usr/libexec/vyos/run-config-migration.py {config_path} --virtual --set-vintage=vyos') - with open(config_path) as f: - config_file = f.read() + logging.debug(f"updating component version string syntax") + # this will update the component version string syntax, + # required for updates 1.2 --> 1.3/1.4 + with tempfile.NamedTemporaryFile() as fp: + with open(fp.name, 'w') as fd: + fd.write(config_file) + virtual_migration = VirtualMigrator(fp.name) + virtual_migration.run() + with open(fp.name) as fd: + config_file = fd.read() + config = ConfigTree(config_file) + except Exception as e: logging.critical(f"ConfigTree error: {e}") @@ -246,4 +254,3 @@ if not boot_configuration_complete(): else: logging.debug("boot configuration complete") lock.release() - -- cgit v1.2.3 From 28a92e75cf931c54423e2441ebf85ab3629e59b4 Mon Sep 17 00:00:00 2001 From: goodNETnick Date: Thu, 20 Jan 2022 06:58:17 -0500 Subject: DHCP: T4196: fix client-prefix-length parameter --- data/templates/dhcp-server/dhcpd.conf.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/dhcp-server/dhcpd.conf.tmpl b/data/templates/dhcp-server/dhcpd.conf.tmpl index 233e2cc53..da2f28ced 100644 --- a/data/templates/dhcp-server/dhcpd.conf.tmpl +++ b/data/templates/dhcp-server/dhcpd.conf.tmpl @@ -165,7 +165,7 @@ shared-network {{ network | replace('_','-') }} { option wpad-url "{{ subnet_config.wpad_url }}"; {% endif %} {% if subnet_config.client_prefix_length is defined and subnet_config.client_prefix_length is not none %} - option subnet-mask {{ subnet_config.client_prefix_length }}; + option subnet-mask {{ ('0.0.0.0/' ~ subnet_config.client_prefix_length) | netmask_from_cidr }}; {% endif %} {% if subnet_config.lease is defined and subnet_config.lease is not none %} default-lease-time {{ subnet_config.lease }}; -- cgit v1.2.3 From 2e4bceee568d4f132d0667a3ca51d05154f375d5 Mon Sep 17 00:00:00 2001 From: Henning Surmeier Date: Fri, 21 Jan 2022 13:27:18 +0100 Subject: policy: T4151: Bugfix policy ipv6-local-route --- smoketest/scripts/cli/test_policy.py | 8 ++++---- src/conf_mode/policy-local-route.py | 15 ++++++++++----- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/smoketest/scripts/cli/test_policy.py b/smoketest/scripts/cli/test_policy.py index 9f9c11fb5..d055762f4 100755 --- a/smoketest/scripts/cli/test_policy.py +++ b/smoketest/scripts/cli/test_policy.py @@ -1381,8 +1381,8 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): path = base_path + ['local-route'] path_v6 = base_path + ['local-route6'] - sources = ['203.0.113.1/24', '203.0.114.5'] - destinations = ['203.0.112.1/24', '203.0.116.5'] + sources = ['203.0.113.0/24', '203.0.114.5'] + destinations = ['203.0.112.0/24', '203.0.116.5'] sources_v6 = ['2001:db8:1338::/126', '2001:db8:1339::/56'] destinations_v6 = ['2001:db8:13::/48', '2001:db8:16::/48'] fwmk = '23' @@ -1432,8 +1432,8 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): tmp = cmd('ip rule show prio 103') tmp_v6 = cmd('ip -6 rule show prio 103') - original = [''] - original_v6 = [''] + original = None + original_v6 = None self.assertEqual(sort_ip(tmp), original) self.assertEqual(sort_ip(tmp_v6), original_v6) diff --git a/src/conf_mode/policy-local-route.py b/src/conf_mode/policy-local-route.py index 2541603e2..6dabb37ae 100755 --- a/src/conf_mode/policy-local-route.py +++ b/src/conf_mode/policy-local-route.py @@ -121,11 +121,14 @@ def apply(pbr): if rule_rm in pbr: v6 = " -6" if rule_rm == 'rule6_remove' else "" for rule, rule_config in pbr[rule_rm].items(): - for src in (rule_config['source'] or ['']): + rule_config['source'] = rule_config['source'] if 'source' in rule_config else [''] + for src in rule_config['source']: f_src = '' if src == '' else f' from {src} ' - for dst in (rule_config['destination'] or ['']): + rule_config['destination'] = rule_config['destination'] if 'destination' in rule_config else [''] + for dst in rule_config['destination']: f_dst = '' if dst == '' else f' to {dst} ' - for fwmk in (rule_config['fwmark'] or ['']): + rule_config['fwmark'] = rule_config['fwmark'] if 'fwmark' in rule_config else [''] + for fwmk in rule_config['fwmark']: f_fwmk = '' if fwmk == '' else f' fwmark {fwmk} ' call(f'ip{v6} rule del prio {rule} {f_src}{f_dst}{f_fwmk}') @@ -141,9 +144,11 @@ def apply(pbr): for rule, rule_config in pbr_route['rule'].items(): table = rule_config['set']['table'] - for src in (rule_config['source'] or ['all']): + rule_config['source'] = rule_config['source'] if 'source' in rule_config else ['all'] + for src in rule_config['source'] or ['all']: f_src = '' if src == '' else f' from {src} ' - for dst in (rule_config['destination'] or ['all']): + rule_config['destination'] = rule_config['destination'] if 'destination' in rule_config else ['all'] + for dst in rule_config['destination']: f_dst = '' if dst == '' else f' to {dst} ' f_fwmk = '' if 'fwmark' in rule_config: -- cgit v1.2.3 From 958c887f9c014ccc5e881b751d4eb82c62b4cb9f Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Fri, 21 Jan 2022 13:09:32 +0100 Subject: firewall: T4130: Use correct table to check for state policy rule --- src/conf_mode/firewall-interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/conf_mode/firewall-interface.py b/src/conf_mode/firewall-interface.py index b0df9dff4..a7442ecbd 100755 --- a/src/conf_mode/firewall-interface.py +++ b/src/conf_mode/firewall-interface.py @@ -150,7 +150,7 @@ def apply(if_firewall): rule_action = 'insert' rule_prefix = '' - handle = state_policy_handle('ip filter', chain) + handle = state_policy_handle('ip6 filter', ipv6_chain) if handle: rule_action = 'add' rule_prefix = f'position {handle}' -- cgit v1.2.3 From 4f8f49c9945aea96e24e984de36a7ebb03916984 Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Fri, 21 Jan 2022 14:56:53 +0100 Subject: firewall: T4186: ICMP/v6 migrations --- src/migration-scripts/firewall/6-to-7 | 97 +++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/src/migration-scripts/firewall/6-to-7 b/src/migration-scripts/firewall/6-to-7 index bc0b19325..cc3a9b559 100755 --- a/src/migration-scripts/firewall/6-to-7 +++ b/src/migration-scripts/firewall/6-to-7 @@ -17,8 +17,11 @@ # T2199: Remove unavailable nodes due to XML/Python implementation using nftables # monthdays: nftables does not have a monthdays equivalent # utc: nftables userspace uses localtime and calculates the UTC offset automatically +# icmp/v6: migrate previously available `type-name` to valid type/code # T4178: Update tcp flags to use multi value node +import re + from sys import argv from sys import exit @@ -41,12 +44,67 @@ if not config.exists(base): # Nothing to do exit(0) +icmp_remove = ['any'] +icmp_translations = { + 'ping': 'echo-request', + 'pong': 'echo-reply', + 'ttl-exceeded': 'time-exceeded', + # Network Unreachable + 'network-unreachable': [3, 0], + 'host-unreachable': [3, 1], + 'protocol-unreachable': [3, 2], + 'port-unreachable': [3, 3], + 'fragmentation-needed': [3, 4], + 'source-route-failed': [3, 5], + 'network-unknown': [3, 6], + 'host-unknown': [3, 7], + 'network-prohibited': [3, 9], + 'host-prohibited': [3, 10], + 'TOS-network-unreachable': [3, 11], + 'TOS-host-unreachable': [3, 12], + 'communication-prohibited': [3, 13], + 'host-precedence-violation': [3, 14], + 'precedence-cutoff': [3, 15], + # Redirect + 'network-redirect': [5, 0], + 'host-redirect': [5, 1], + 'TOS-network-redirect': [5, 2], + 'TOS host-redirect': [5, 3], + # Time Exceeded + 'ttl-zero-during-transit': [11, 0], + 'ttl-zero-during-reassembly': [11, 1], + # Parameter Problem + 'ip-header-bad': [12, 0], + 'required-option-missing': [12, 1] +} + +icmpv6_remove = [] +icmpv6_translations = { + 'ping': 'echo-request', + 'pong': 'echo-reply', + # Destination Unreachable + 'no-route': [1, 0], + 'communication-prohibited': [1, 1], + 'address-unreachble': [1, 3], + 'port-unreachable': [1, 4], + # Redirect + 'redirect': 'nd-redirect', + # Time Exceeded + 'ttl-zero-during-transit': [3, 0], + 'ttl-zero-during-reassembly': [3, 1], + # Parameter Problem + 'bad-header': [4, 0], + 'unknown-header-type': [4, 1], + 'unknown-option': [4, 2] +} + if config.exists(base + ['name']): for name in config.list_nodes(base + ['name']): if config.exists(base + ['name', name, 'rule']): for rule in config.list_nodes(base + ['name', name, 'rule']): rule_time = base + ['name', name, 'rule', rule, 'time'] rule_tcp_flags = base + ['name', name, 'rule', rule, 'tcp', 'flags'] + rule_icmp = base + ['name', name, 'rule', rule, 'icmp'] if config.exists(rule_time + ['monthdays']): config.delete(rule_time + ['monthdays']) @@ -63,12 +121,26 @@ if config.exists(base + ['name']): else: config.set(rule_tcp_flags + [flag.lower()]) + if config.exists(rule_icmp + ['type-name']): + tmp = config.return_value(rule_icmp + ['type-name']) + if tmp in icmp_remove: + config.delete(rule_icmp + ['type-name']) + elif tmp in icmp_translations: + translate = icmp_translations[tmp] + if isinstance(translate, str): + config.set(rule_icmp + ['type-name'], value=translate) + elif isinstance(translate, list): + config.delete(rule_icmp + ['type-name']) + config.set(rule_icmp + ['type'], value=translate[0]) + config.set(rule_icmp + ['code'], value=translate[1]) + if config.exists(base + ['ipv6-name']): for name in config.list_nodes(base + ['ipv6-name']): if config.exists(base + ['ipv6-name', name, 'rule']): for rule in config.list_nodes(base + ['ipv6-name', name, 'rule']): rule_time = base + ['ipv6-name', name, 'rule', rule, 'time'] rule_tcp_flags = base + ['ipv6-name', name, 'rule', rule, 'tcp', 'flags'] + rule_icmp = base + ['ipv6-name', name, 'rule', rule, 'icmpv6'] if config.exists(rule_time + ['monthdays']): config.delete(rule_time + ['monthdays']) @@ -85,6 +157,31 @@ if config.exists(base + ['ipv6-name']): else: config.set(rule_tcp_flags + [flag.lower()]) + if config.exists(base + ['ipv6-name', name, 'rule', rule, 'protocol']): + tmp = config.return_value(base + ['ipv6-name', name, 'rule', rule, 'protocol']) + if tmp == 'icmpv6': + config.set(base + ['ipv6-name', name, 'rule', rule, 'protocol'], value='ipv6-icmp') + + if config.exists(rule_icmp + ['type']): + tmp = config.return_value(rule_icmp + ['type']) + type_code_match = re.match(r'^(\d+)/(\d+)$', tmp) + + if type_code_match: + config.set(rule_icmp + ['type'], value=type_code_match[1]) + config.set(rule_icmp + ['code'], value=type_code_match[2]) + elif tmp in icmpv6_remove: + config.delete(rule_icmp + ['type']) + elif tmp in icmpv6_translations: + translate = icmpv6_translations[tmp] + if isinstance(translate, str): + config.delete(rule_icmp + ['type']) + config.set(rule_icmp + ['type-name'], value=translate) + elif isinstance(translate, list): + config.set(rule_icmp + ['type'], value=translate[0]) + config.set(rule_icmp + ['code'], value=translate[1]) + else: + config.rename(rule_icmp + ['type'], 'type-name') + try: with open(file_name, 'w') as f: f.write(config.to_string()) -- cgit v1.2.3 From e31493c32d0e95ef14c627d0bf181efbb81ef062 Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Fri, 21 Jan 2022 18:15:50 +0100 Subject: firewall: T2199: Verify correct ICMP protocol for ipv4/ipv6 --- src/conf_mode/firewall.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index 82223d60b..358b938e3 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -184,6 +184,12 @@ def verify_rule(firewall, rule_conf, ipv6): if duplicates: raise ConfigError(f'Cannot match a tcp flag as set and not set') + if 'protocol' in rule_conf: + if rule_conf['protocol'] == 'icmp' and ipv6: + raise ConfigError(f'Cannot match IPv4 ICMP protocol on IPv6, use ipv6-icmp') + if rule_conf['protocol'] == 'ipv6-icmp' and not ipv6: + raise ConfigError(f'Cannot match IPv6 ICMP protocol on IPv4, use icmp') + for side in ['destination', 'source']: if side in rule_conf: side_conf = rule_conf[side] -- cgit v1.2.3 From 3e4f2f577746608de6944d18d2b827811c81f70c Mon Sep 17 00:00:00 2001 From: Nicolas Fort Date: Sun, 16 Jan 2022 15:13:22 +0000 Subject: Firewall: T4186: Correct icmp type-name options for firewall rules --- .../include/firewall/icmp-type-name.xml.i | 142 +++------------------ 1 file changed, 21 insertions(+), 121 deletions(-) diff --git a/interface-definitions/include/firewall/icmp-type-name.xml.i b/interface-definitions/include/firewall/icmp-type-name.xml.i index b45fb619b..585b387e2 100644 --- a/interface-definitions/include/firewall/icmp-type-name.xml.i +++ b/interface-definitions/include/firewall/icmp-type-name.xml.i @@ -3,170 +3,70 @@ ICMP type-name - any echo-reply pong destination-unreachable network-unreachable host-unreachable protocol-unreachable port-unreachable fragmentation-needed source-route-failed network-unknown host-unknown network-prohibited host-prohibited TOS-network-unreachable TOS-host-unreachable communication-prohibited host-precedence-violation precedence-cutoff source-quench redirect network-redirect host-redirect TOS-network-redirect TOS host-redirect echo-request ping router-advertisement router-solicitation time-exceeded ttl-exceeded ttl-zero-during-transit ttl-zero-during-reassembly parameter-problem ip-header-bad required-option-missing timestamp-request timestamp-reply address-mask-request address-mask-reply + echo-reply destination-unreachable source-quench redirect echo-request router-advertisement router-solicitation time-exceeded parameter-problem timestamp-request timestamp-reply info-request info-reply address-mask-request address-mask-reply - - any - Any ICMP type/code - echo-reply - ICMP type/code name - - - pong - ICMP type/code name + ICMP type 0: echo-reply destination-unreachable - ICMP type/code name - - - network-unreachable - ICMP type/code name - - - host-unreachable - ICMP type/code name - - - protocol-unreachable - ICMP type/code name - - - port-unreachable - ICMP type/code name - - - fragmentation-needed - ICMP type/code name - - - source-route-failed - ICMP type/code name - - - network-unknown - ICMP type/code name - - - host-unknown - ICMP type/code name - - - network-prohibited - ICMP type/code name - - - host-prohibited - ICMP type/code name - - - TOS-network-unreachable - ICMP type/code name - - - TOS-host-unreachable - ICMP type/code name - - - communication-prohibited - ICMP type/code name - - - host-precedence-violation - ICMP type/code name - - - precedence-cutoff - ICMP type/code name + ICMP type 3: destination-unreachable source-quench - ICMP type/code name + ICMP type 4: source-quench redirect - ICMP type/code name - - - network-redirect - ICMP type/code name - - - host-redirect - ICMP type/code name - - - TOS-network-redirect - ICMP type/code name - - - TOS host-redirect - ICMP type/code name + ICMP type 5: redirect echo-request - ICMP type/code name - - - ping - ICMP type/code name + ICMP type 8: echo-request router-advertisement - ICMP type/code name + ICMP type 9: router-advertisement router-solicitation - ICMP type/code name + ICMP type 10: router-solicitation time-exceeded - ICMP type/code name - - - ttl-exceeded - ICMP type/code name - - - ttl-zero-during-transit - ICMP type/code name - - - ttl-zero-during-reassembly - ICMP type/code name + ICMP type 11: time-exceeded parameter-problem - ICMP type/code name + ICMP type 12: parameter-problem - ip-header-bad - ICMP type/code name + timestamp-request + ICMP type 13: timestamp-request - required-option-missing - ICMP type/code name + timestamp-reply + ICMP type 14: timestamp-reply - timestamp-request - ICMP type/code name + info-request + ICMP type 15: info-request - timestamp-reply - ICMP type/code name + info-reply + ICMP type 16: info-reply address-mask-request - ICMP type/code name + ICMP type 17: address-mask-request address-mask-reply - ICMP type/code name + ICMP type 18: address-mask-replye - ^(any|echo-reply|pong|destination-unreachable|network-unreachable|host-unreachable|protocol-unreachable|port-unreachable|fragmentation-needed|source-route-failed|network-unknown|host-unknown|network-prohibited|host-prohibited|TOS-network-unreachable|TOS-host-unreachable|communication-prohibited|host-precedence-violation|precedence-cutoff|source-quench|redirect|network-redirect|host-redirect|TOS-network-redirect|TOS host-redirect|echo-request|ping|router-advertisement|router-solicitation|time-exceeded|ttl-exceeded|ttl-zero-during-transit|ttl-zero-during-reassembly|parameter-problem|ip-header-bad|required-option-missing|timestamp-request|timestamp-reply|address-mask-request|address-mask-reply)$ + ^(echo-reply|destination-unreachable|source-quench|redirect|echo-request|router-advertisement|router-solicitation|time-exceeded|parameter-problem|timestamp-request|timestamp-reply|info-request|info-reply|address-mask-request|address-mask-reply)$ -- cgit v1.2.3 From d0cfd9758bab25c14a4389488f1f8dcef01ecd45 Mon Sep 17 00:00:00 2001 From: Nicolas Fort Date: Sun, 16 Jan 2022 15:35:23 +0000 Subject: Firewall: T4186: typo correction on address-mask-reply description --- interface-definitions/include/firewall/icmp-type-name.xml.i | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface-definitions/include/firewall/icmp-type-name.xml.i b/interface-definitions/include/firewall/icmp-type-name.xml.i index 585b387e2..f57def3e1 100644 --- a/interface-definitions/include/firewall/icmp-type-name.xml.i +++ b/interface-definitions/include/firewall/icmp-type-name.xml.i @@ -63,7 +63,7 @@ address-mask-reply - ICMP type 18: address-mask-replye + ICMP type 18: address-mask-reply ^(echo-reply|destination-unreachable|source-quench|redirect|echo-request|router-advertisement|router-solicitation|time-exceeded|parameter-problem|timestamp-request|timestamp-reply|info-request|info-reply|address-mask-request|address-mask-reply)$ -- cgit v1.2.3 From 3e55af0ccdf01a7707bd81d7b329f57848e6cd2f Mon Sep 17 00:00:00 2001 From: Nicolas Fort Date: Fri, 21 Jan 2022 16:58:50 +0000 Subject: Firewall: T4186: Adding icmpv6 corrections, in corcondancy of what was done for icmp --- interface-definitions/firewall.xml.in | 181 ++------------------- .../include/firewall/icmpv6-type-name.xml.i | 73 +++++++++ 2 files changed, 88 insertions(+), 166 deletions(-) create mode 100644 interface-definitions/include/firewall/icmpv6-type-name.xml.i diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in index 987ccaca6..f38bcfd9c 100644 --- a/interface-definitions/firewall.xml.in +++ b/interface-definitions/firewall.xml.in @@ -329,182 +329,31 @@ ICMPv6 type and code information - + - ICMP type-name - - any echo-reply pong destination-unreachable network-unreachable host-unreachable protocol-unreachable port-unreachable fragmentation-needed source-route-failed network-unknown host-unknown network-prohibited host-prohibited TOS-network-unreachable TOS-host-unreachable communication-prohibited host-precedence-violation precedence-cutoff source-quench redirect network-redirect host-redirect TOS-network-redirect TOS host-redirect echo-request ping router-advertisement router-solicitation time-exceeded ttl-exceeded ttl-zero-during-transit ttl-zero-during-reassembly parameter-problem ip-header-bad required-option-missing timestamp-request timestamp-reply address-mask-request address-mask-reply packet-too-big - - - any - Any ICMP type/code - - - echo-reply - ICMP type/code name - - - pong - ICMP type/code name - - - destination-unreachable - ICMP type/code name - - - network-unreachable - ICMP type/code name - + ICMPv6 code (0-255) - host-unreachable - ICMP type/code name - - - protocol-unreachable - ICMP type/code name - - - port-unreachable - ICMP type/code name - - - fragmentation-needed - ICMP type/code name - - - source-route-failed - ICMP type/code name - - - network-unknown - ICMP type/code name - - - host-unknown - ICMP type/code name - - - network-prohibited - ICMP type/code name - - - host-prohibited - ICMP type/code name - - - TOS-network-unreachable - ICMP type/code name - - - TOS-host-unreachable - ICMP type/code name - - - communication-prohibited - ICMP type/code name - - - host-precedence-violation - ICMP type/code name - - - precedence-cutoff - ICMP type/code name - - - source-quench - ICMP type/code name - - - redirect - ICMP type/code name - - - network-redirect - ICMP type/code name - - - host-redirect - ICMP type/code name - - - TOS-network-redirect - ICMP type/code name - - - TOS host-redirect - ICMP type/code name - - - echo-request - ICMP type/code name - - - ping - ICMP type/code name - - - router-advertisement - ICMP type/code name - - - router-solicitation - ICMP type/code name - - - time-exceeded - ICMP type/code name - - - ttl-exceeded - ICMP type/code name - - - ttl-zero-during-transit - ICMP type/code name - - - ttl-zero-during-reassembly - ICMP type/code name - - - parameter-problem - ICMP type/code name - - - ip-header-bad - ICMP type/code name - - - required-option-missing - ICMP type/code name - - - timestamp-request - ICMP type/code name - - - timestamp-reply - ICMP type/code name - - - address-mask-request - ICMP type/code name - - - address-mask-reply - ICMP type/code name + u32:0-255 + ICMPv6 code (0-255) + + + + + + + + ICMPv6 type (0-255) - packet-too-big - ICMP type/code name + u32:0-255 + ICMPv6 type (0-255) - ^(any|echo-reply|pong|destination-unreachable|network-unreachable|host-unreachable|protocol-unreachable|port-unreachable|fragmentation-needed|source-route-failed|network-unknown|host-unknown|network-prohibited|host-prohibited|TOS-network-unreachable|TOS-host-unreachable|communication-prohibited|host-precedence-violation|precedence-cutoff|source-quench|redirect|network-redirect|host-redirect|TOS-network-redirect|TOS host-redirect|echo-request|ping|router-advertisement|router-solicitation|time-exceeded|ttl-exceeded|ttl-zero-during-transit|ttl-zero-during-reassembly|parameter-problem|ip-header-bad|required-option-missing|timestamp-request|timestamp-reply|address-mask-request|address-mask-reply|packet-too-big)$ + #include diff --git a/interface-definitions/include/firewall/icmpv6-type-name.xml.i b/interface-definitions/include/firewall/icmpv6-type-name.xml.i new file mode 100644 index 000000000..b13cf02c4 --- /dev/null +++ b/interface-definitions/include/firewall/icmpv6-type-name.xml.i @@ -0,0 +1,73 @@ + + + + ICMPv6 type-name + + destination-unreachable packet-too-big time-exceeded echo-request echo-reply mld-listener-query mld-listener-report mld-listener-reduction nd-router-solicit nd-router-advert nd-neighbor-solicit nd-neighbor-advert nd-redirect parameter-problem router-renumbering + + + destination-unreachable + ICMPv6 type 1: destination-unreachable + + + packet-too-big + ICMPv6 type 2: packet-too-big + + + time-exceeded + ICMPv6 type 3: time-exceeded + + + echo-request + ICMPv6 type 128: echo-request + + + echo-reply + ICMPv6 type 129: echo-reply + + + mld-listener-query + ICMPv6 type 130: mld-listener-query + + + mld-listener-report + ICMPv6 type 131: mld-listener-report + + + mld-listener-reduction + ICMPv6 type 132: mld-listener-reduction + + + nd-router-solicit + ICMPv6 type 133: nd-router-solicit + + + nd-router-advert + ICMPv6 type 134: nd-router-advert + + + nd-neighbor-solicit + ICMPv6 type 135: nd-neighbor-solicit + + + nd-neighbor-advert + ICMPv6 type 136: nd-neighbor-advert + + + nd-redirect + ICMPv6 type 137: nd-redirect + + + parameter-problem + ICMPv6 type 4: parameter-problem + + + router-renumbering + ICMPv6 type 138: router-renumbering + + + ^(destination-unreachable|packet-too-big|time-exceeded|echo-request|echo-reply|mld-listener-query|mld-listener-report|mld-listener-reduction|nd-router-solicit|nd-router-advert|nd-neighbor-solicit|nd-neighbor-advert|nd-redirect|parameter-problem|router-renumbering)$ + + + + -- cgit v1.2.3 From 60d3e93c33df598224df8f960efee92b035c60f4 Mon Sep 17 00:00:00 2001 From: Nicolas Fort Date: Sat, 22 Jan 2022 14:02:38 +0000 Subject: bandwidth-test: T4153: Fixed bandwidth-test initiate, which was not working with ipv4 --- src/op_mode/monitor_bandwidth_test.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/op_mode/monitor_bandwidth_test.sh b/src/op_mode/monitor_bandwidth_test.sh index 900223bca..a6ad0b42c 100755 --- a/src/op_mode/monitor_bandwidth_test.sh +++ b/src/op_mode/monitor_bandwidth_test.sh @@ -24,6 +24,9 @@ elif [[ $(dig $1 AAAA +short | grep -v '\.$' | wc -l) -gt 0 ]]; then # Set address family to IPv6 when FQDN has at least one AAAA record OPT="-V" +else + # It's not IPv6, no option needed + OPT="" fi /usr/bin/iperf $OPT -c $1 $2 -- cgit v1.2.3 From 10fb7f4c6d07d409040eccc71a0320dd457b9c23 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Tue, 25 Jan 2022 08:47:53 +0000 Subject: sshd: T4205: Hide extra version suffix "Debian" Disable distribution-specified extra version suffix is included during initial protocol handshake SSH-2.0-OpenSSH_8.4p1 Debian-5 => SSH-2.0-OpenSSH_8.4p1 --- data/templates/ssh/sshd_config.tmpl | 1 + 1 file changed, 1 insertion(+) diff --git a/data/templates/ssh/sshd_config.tmpl b/data/templates/ssh/sshd_config.tmpl index 2f2b78a66..670cf85a1 100644 --- a/data/templates/ssh/sshd_config.tmpl +++ b/data/templates/ssh/sshd_config.tmpl @@ -29,6 +29,7 @@ UsePAM yes PermitRootLogin no PidFile /run/sshd/sshd.pid AddressFamily any +DebianBanner no # # User configurable section -- cgit v1.2.3 From ceb52d57e96510fd7cf19ca06ba9d0160e8b81ca Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Tue, 25 Jan 2022 09:20:45 +0000 Subject: monitoring: T3872: Delete iptables input plugin as we use nft Telegraf inputs iptables plugin incompatible with nftables As it tries to get statistics from "iptables -L -n -v" which doesnt display required data in 1.4 as we don't use iptables anymore --- data/templates/monitoring/telegraf.tmpl | 5 ----- 1 file changed, 5 deletions(-) diff --git a/data/templates/monitoring/telegraf.tmpl b/data/templates/monitoring/telegraf.tmpl index afc04aa6d..f05396d91 100644 --- a/data/templates/monitoring/telegraf.tmpl +++ b/data/templates/monitoring/telegraf.tmpl @@ -42,11 +42,6 @@ dirs = ["/proc/sys/net/ipv4/netfilter","/proc/sys/net/netfilter"] [[inputs.ethtool]] interface_include = {{ interfaces_ethernet }} -[[inputs.iptables]] - use_sudo = false - table = "filter" - chains = {{ nft_chains }} - use_lock = true [[inputs.ntpq]] dns_lookup = true [[inputs.internal]] -- cgit v1.2.3 From 5dafe255d6e9cb7747f331b8ecec36b5ca5ce33d Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Tue, 25 Jan 2022 16:15:57 +0000 Subject: policy: T4194: Add prefix-list duplication checks Prefix-list should not be duplicatied as FRR doesn't accept it One option when it can be duplicated when it uses "le" or "ge" --- src/conf_mode/policy.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/conf_mode/policy.py b/src/conf_mode/policy.py index e251396c7..6b1d3bf1a 100755 --- a/src/conf_mode/policy.py +++ b/src/conf_mode/policy.py @@ -87,6 +87,7 @@ def verify(policy): # human readable instance name (hypen instead of underscore) policy_hr = policy_type.replace('_', '-') + entries = [] for rule, rule_config in instance_config['rule'].items(): mandatory_error = f'must be specified for "{policy_hr} {instance} rule {rule}"!' if 'action' not in rule_config: @@ -113,6 +114,11 @@ def verify(policy): if 'prefix' not in rule_config: raise ConfigError(f'A prefix {mandatory_error}') + # Check prefix duplicates + if rule_config['prefix'] in entries and ('ge' not in rule_config and 'le' not in rule_config): + raise ConfigError(f'Prefix {rule_config["prefix"]} is duplicated!') + entries.append(rule_config['prefix']) + # route-maps tend to be a bit more complex so they get their own verify() section if 'route_map' in policy: -- cgit v1.2.3 From 1d65ce9558b7c814295474a7cdf648866b612ff6 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Tue, 25 Jan 2022 19:09:08 +0000 Subject: nat: T4138: Add port-range validation for NAT Add port-validators for NAT rules that prevent to set incorrect port-ranges (21-5) and incorrect ports (70000) --- interface-definitions/include/nat-port.xml.i | 7 +++++++ interface-definitions/include/nat-translation-port.xml.i | 3 +++ 2 files changed, 10 insertions(+) diff --git a/interface-definitions/include/nat-port.xml.i b/interface-definitions/include/nat-port.xml.i index 7aabc33c3..5f762cfb3 100644 --- a/interface-definitions/include/nat-port.xml.i +++ b/interface-definitions/include/nat-port.xml.i @@ -2,6 +2,10 @@ Port number + + txt + Named port (any name in /etc/services, e.g., http) + u32:1-65535 Numeric IP port @@ -14,6 +18,9 @@ \n\nMultiple destination ports can be specified as a comma-separated list.\nThe whole list can also be negated using '!'.\nFor example: '!22,telnet,http,123,1001-1005' + + + diff --git a/interface-definitions/include/nat-translation-port.xml.i b/interface-definitions/include/nat-translation-port.xml.i index 6e507353c..6f17df3d9 100644 --- a/interface-definitions/include/nat-translation-port.xml.i +++ b/interface-definitions/include/nat-translation-port.xml.i @@ -10,6 +10,9 @@ range Numbered port range (e.g., 1001-1005) + + + -- cgit v1.2.3 From 3523da8e4c87c96ae6fede6a5bc0fd2e96dcabc9 Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Wed, 26 Jan 2022 16:25:03 +0100 Subject: pki: T4212: Catch `install_into_config` errors and output for manual command entry --- python/vyos/util.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/python/vyos/util.py b/python/vyos/util.py index 954c6670d..571d43754 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -952,14 +952,23 @@ def install_into_config(conf, config_paths, override_prompt=True): return None count = 0 + failed = [] for path in config_paths: if override_prompt and conf.exists(path) and not conf.is_multi(path): if not ask_yes_no(f'Config node "{node}" already exists. Do you want to overwrite it?'): continue - cmd(f'/opt/vyatta/sbin/my_set {path}') - count += 1 + try: + cmd(f'/opt/vyatta/sbin/my_set {path}') + count += 1 + except: + failed.append(path) + + if failed: + print(f'Failed to install {len(failed)} value(s). Commands to manually install:') + for path in failed: + print(f'set {path}') if count > 0: print(f'{count} value(s) installed. Use "compare" to see the pending changes, and "commit" to apply.') -- cgit v1.2.3 From dcabea5919e299cdee9db7469b451356743cc7ff Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Thu, 27 Jan 2022 13:12:39 +0100 Subject: firewall: T4178: Fix tcp flags output when `not` isn't used --- python/vyos/firewall.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py index 808e90e38..4993d855e 100644 --- a/python/vyos/firewall.py +++ b/python/vyos/firewall.py @@ -190,8 +190,8 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name): def parse_tcp_flags(flags): include = [flag for flag in flags if flag != 'not'] - all_flags = include + [flag for flag in flags['not']] if 'not' in flags else [] - return f'tcp flags & ({"|".join(all_flags)}) == {"|".join(include)}' + exclude = flags['not'].keys() if 'not' in flags else [] + return f'tcp flags & ({"|".join(include + exclude)}) == {"|".join(include)}' def parse_time(time): out = [] -- cgit v1.2.3 From 25e97e0b0224f3f8f1bffb77b36955d6fa129dd3 Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Thu, 27 Jan 2022 18:21:16 +0100 Subject: policy: T4213: Fix rule creation/deletion for IPv6 policy routes --- src/conf_mode/policy-route.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/conf_mode/policy-route.py b/src/conf_mode/policy-route.py index ee5197af0..7dcab4b58 100755 --- a/src/conf_mode/policy-route.py +++ b/src/conf_mode/policy-route.py @@ -205,6 +205,7 @@ def generate(policy): def apply_table_marks(policy): for route in ['route', 'route6']: if route in policy: + cmd_str = 'ip' if route == 'route' else 'ip -6' for name, pol_conf in policy[route].items(): if 'rule' in pol_conf: for rule_id, rule_conf in pol_conf['rule'].items(): @@ -213,20 +214,21 @@ def apply_table_marks(policy): if set_table == 'main': set_table = '254' table_mark = mark_offset - int(set_table) - cmd(f'ip rule add fwmark {table_mark} table {set_table}') + cmd(f'{cmd_str} rule add pref {set_table} fwmark {table_mark} table {set_table}') def cleanup_table_marks(): - json_rules = cmd('ip -j -N rule list') - rules = loads(json_rules) - for rule in rules: - if 'fwmark' not in rule or 'table' not in rule: - continue - fwmark = rule['fwmark'] - table = int(rule['table']) - if fwmark[:2] == '0x': - fwmark = int(fwmark, 16) - if (int(fwmark) == (mark_offset - table)): - cmd(f'ip rule del fwmark {fwmark} table {table}') + for cmd_str in ['ip', 'ip -6']: + json_rules = cmd(f'{cmd_str} -j -N rule list') + rules = loads(json_rules) + for rule in rules: + if 'fwmark' not in rule or 'table' not in rule: + continue + fwmark = rule['fwmark'] + table = int(rule['table']) + if fwmark[:2] == '0x': + fwmark = int(fwmark, 16) + if (int(fwmark) == (mark_offset - table)): + cmd(f'{cmd_str} rule del fwmark {fwmark} table {table}') def apply(policy): install_result = run(f'nft -f {nftables_conf}') -- cgit v1.2.3 From c501ae0fdc5de3c24c998891f78c8bb05ffb35c7 Mon Sep 17 00:00:00 2001 From: Henning Surmeier Date: Fri, 28 Jan 2022 17:28:32 +0100 Subject: policy: T4151: remove all previous rules on edit --- smoketest/scripts/cli/test_policy.py | 37 ++++++++++++++++++++++++++++ src/conf_mode/policy-local-route.py | 47 +++++++++++++++++++++++++++++------- 2 files changed, 75 insertions(+), 9 deletions(-) diff --git a/smoketest/scripts/cli/test_policy.py b/smoketest/scripts/cli/test_policy.py index d055762f4..6dac28a0f 100755 --- a/smoketest/scripts/cli/test_policy.py +++ b/smoketest/scripts/cli/test_policy.py @@ -1438,6 +1438,43 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): self.assertEqual(sort_ip(tmp), original) self.assertEqual(sort_ip(tmp_v6), original_v6) + # Test multiple commits ipv4 + def test_multiple_commit_ipv4_table_id(self): + path = base_path + ['local-route'] + + sources = ['192.0.2.1', '192.0.2.2'] + destination = '203.0.113.25' + rule = '105' + table = '151' + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + for src in sources: + self.cli_set(path + ['rule', rule, 'source', src]) + + self.cli_commit() + + # Check generated configuration + # Expected values + original_first = """ + 105: from 192.0.2.1 lookup 151 + 105: from 192.0.2.2 lookup 151 + """ + tmp = cmd('ip rule show prio 105') + + self.assertEqual(sort_ip(tmp), sort_ip(original_first)) + + # Create second commit with added destination + self.cli_set(path + ['rule', rule, 'destination', destination]) + self.cli_commit() + + original_second = """ + 105: from 192.0.2.1 to 203.0.113.25 lookup 151 + 105: from 192.0.2.2 to 203.0.113.25 lookup 151 + """ + tmp = cmd('ip rule show prio 105') + + self.assertEqual(sort_ip(tmp), sort_ip(original_second)) + + def sort_ip(output): return output.splitlines().sort() diff --git a/src/conf_mode/policy-local-route.py b/src/conf_mode/policy-local-route.py index 6dabb37ae..71183c6ba 100755 --- a/src/conf_mode/policy-local-route.py +++ b/src/conf_mode/policy-local-route.py @@ -69,20 +69,47 @@ def get_config(config=None): # delete policy local-route rule x fwmark x # delete policy local-route rule x destination x.x.x.x if 'rule' in pbr[route]: - for rule in pbr[route]['rule']: + for rule, rule_config in pbr[route]['rule'].items(): src = leaf_node_changed(conf, base_rule + [rule, 'source']) fwmk = leaf_node_changed(conf, base_rule + [rule, 'fwmark']) dst = leaf_node_changed(conf, base_rule + [rule, 'destination']) + # keep track of changes in configuration + # otherwise we might remove an existing node although nothing else has changed + changed = False rule_def = {} - if src: - rule_def = dict_merge({'source' : src}, rule_def) - if fwmk: - rule_def = dict_merge({'fwmark' : fwmk}, rule_def) - if dst: - rule_def = dict_merge({'destination' : dst}, rule_def) - dict = dict_merge({dict_id : {rule : rule_def}}, dict) - pbr.update(dict) + # src is None if there are no changes to src + if src is None: + # if src hasn't changed, include it in the removal selector + # if a new selector is added, we have to remove all previous rules without this selector + # to make sure we remove all previous rules with this source(s), it will be included + if 'source' in rule_config: + rule_def = dict_merge({'source': rule_config['source']}, rule_def) + else: + # if src is not None, it's previous content will be returned + # this can be an empty array if it's just being set, or the previous value + # either way, something has to be changed and we only want to remove previous values + changed = True + # set the old value for removal if it's not empty + if len(src) > 0: + rule_def = dict_merge({'source' : src}, rule_def) + if fwmk is None: + if 'fwmark' in rule_config: + rule_def = dict_merge({'fwmark': rule_config['fwmark']}, rule_def) + else: + changed = True + if len(fwmk) > 0: + rule_def = dict_merge({'fwmark' : fwmk}, rule_def) + if dst is None: + if 'destination' in rule_config: + rule_def = dict_merge({'destination': rule_config['destination']}, rule_def) + else: + changed = True + if len(dst) > 0: + rule_def = dict_merge({'destination' : dst}, rule_def) + if changed: + dict = dict_merge({dict_id : {rule : rule_def}}, dict) + pbr.update(dict) return pbr @@ -116,6 +143,8 @@ def apply(pbr): if not pbr: return None + print(pbr) + # Delete old rule if needed for rule_rm in ['rule_remove', 'rule6_remove']: if rule_rm in pbr: -- cgit v1.2.3 From 137c9b8b4c01ceb041e50d539e2198fe876b6362 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 28 Jan 2022 22:14:18 +0100 Subject: firewall: T4217: install protocol tcp_udp if port group does not use a protocol --- src/migration-scripts/firewall/6-to-7 | 168 +++++++++++++++++++--------------- 1 file changed, 92 insertions(+), 76 deletions(-) diff --git a/src/migration-scripts/firewall/6-to-7 b/src/migration-scripts/firewall/6-to-7 index cc3a9b559..efc901530 100755 --- a/src/migration-scripts/firewall/6-to-7 +++ b/src/migration-scripts/firewall/6-to-7 @@ -100,87 +100,103 @@ icmpv6_translations = { if config.exists(base + ['name']): for name in config.list_nodes(base + ['name']): - if config.exists(base + ['name', name, 'rule']): - for rule in config.list_nodes(base + ['name', name, 'rule']): - rule_time = base + ['name', name, 'rule', rule, 'time'] - rule_tcp_flags = base + ['name', name, 'rule', rule, 'tcp', 'flags'] - rule_icmp = base + ['name', name, 'rule', rule, 'icmp'] - - if config.exists(rule_time + ['monthdays']): - config.delete(rule_time + ['monthdays']) - - if config.exists(rule_time + ['utc']): - config.delete(rule_time + ['utc']) - - if config.exists(rule_tcp_flags): - tmp = config.return_value(rule_tcp_flags) - config.delete(rule_tcp_flags) - for flag in tmp.split(","): - if flag[0] == '!': - config.set(rule_tcp_flags + ['not', flag[1:].lower()]) - else: - config.set(rule_tcp_flags + [flag.lower()]) - - if config.exists(rule_icmp + ['type-name']): - tmp = config.return_value(rule_icmp + ['type-name']) - if tmp in icmp_remove: + if not config.exists(base + ['name', name, 'rule']): + continue + + for rule in config.list_nodes(base + ['name', name, 'rule']): + rule_time = base + ['name', name, 'rule', rule, 'time'] + rule_tcp_flags = base + ['name', name, 'rule', rule, 'tcp', 'flags'] + rule_icmp = base + ['name', name, 'rule', rule, 'icmp'] + + if config.exists(rule_time + ['monthdays']): + config.delete(rule_time + ['monthdays']) + + if config.exists(rule_time + ['utc']): + config.delete(rule_time + ['utc']) + + if config.exists(rule_tcp_flags): + tmp = config.return_value(rule_tcp_flags) + config.delete(rule_tcp_flags) + for flag in tmp.split(","): + if flag[0] == '!': + config.set(rule_tcp_flags + ['not', flag[1:].lower()]) + else: + config.set(rule_tcp_flags + [flag.lower()]) + + if config.exists(rule_icmp + ['type-name']): + tmp = config.return_value(rule_icmp + ['type-name']) + if tmp in icmp_remove: + config.delete(rule_icmp + ['type-name']) + elif tmp in icmp_translations: + translate = icmp_translations[tmp] + if isinstance(translate, str): + config.set(rule_icmp + ['type-name'], value=translate) + elif isinstance(translate, list): config.delete(rule_icmp + ['type-name']) - elif tmp in icmp_translations: - translate = icmp_translations[tmp] - if isinstance(translate, str): - config.set(rule_icmp + ['type-name'], value=translate) - elif isinstance(translate, list): - config.delete(rule_icmp + ['type-name']) - config.set(rule_icmp + ['type'], value=translate[0]) - config.set(rule_icmp + ['code'], value=translate[1]) + config.set(rule_icmp + ['type'], value=translate[0]) + config.set(rule_icmp + ['code'], value=translate[1]) + + for src_dst in ['destination', 'source']: + pg_base = base + ['name', name, 'rule', rule, src_dst, 'group', 'port-group'] + proto_base = base + ['name', name, 'rule', rule, 'protocol'] + if config.exists(pg_base) and not config.exists(proto_base): + config.set(proto_base, value='tcp_udp') if config.exists(base + ['ipv6-name']): for name in config.list_nodes(base + ['ipv6-name']): - if config.exists(base + ['ipv6-name', name, 'rule']): - for rule in config.list_nodes(base + ['ipv6-name', name, 'rule']): - rule_time = base + ['ipv6-name', name, 'rule', rule, 'time'] - rule_tcp_flags = base + ['ipv6-name', name, 'rule', rule, 'tcp', 'flags'] - rule_icmp = base + ['ipv6-name', name, 'rule', rule, 'icmpv6'] - - if config.exists(rule_time + ['monthdays']): - config.delete(rule_time + ['monthdays']) - - if config.exists(rule_time + ['utc']): - config.delete(rule_time + ['utc']) - - if config.exists(rule_tcp_flags): - tmp = config.return_value(rule_tcp_flags) - config.delete(rule_tcp_flags) - for flag in tmp.split(","): - if flag[0] == '!': - config.set(rule_tcp_flags + ['not', flag[1:].lower()]) - else: - config.set(rule_tcp_flags + [flag.lower()]) - - if config.exists(base + ['ipv6-name', name, 'rule', rule, 'protocol']): - tmp = config.return_value(base + ['ipv6-name', name, 'rule', rule, 'protocol']) - if tmp == 'icmpv6': - config.set(base + ['ipv6-name', name, 'rule', rule, 'protocol'], value='ipv6-icmp') - - if config.exists(rule_icmp + ['type']): - tmp = config.return_value(rule_icmp + ['type']) - type_code_match = re.match(r'^(\d+)/(\d+)$', tmp) - - if type_code_match: - config.set(rule_icmp + ['type'], value=type_code_match[1]) - config.set(rule_icmp + ['code'], value=type_code_match[2]) - elif tmp in icmpv6_remove: - config.delete(rule_icmp + ['type']) - elif tmp in icmpv6_translations: - translate = icmpv6_translations[tmp] - if isinstance(translate, str): - config.delete(rule_icmp + ['type']) - config.set(rule_icmp + ['type-name'], value=translate) - elif isinstance(translate, list): - config.set(rule_icmp + ['type'], value=translate[0]) - config.set(rule_icmp + ['code'], value=translate[1]) + if not config.exists(base + ['ipv6-name', name, 'rule']): + continue + + for rule in config.list_nodes(base + ['ipv6-name', name, 'rule']): + rule_time = base + ['ipv6-name', name, 'rule', rule, 'time'] + rule_tcp_flags = base + ['ipv6-name', name, 'rule', rule, 'tcp', 'flags'] + rule_icmp = base + ['ipv6-name', name, 'rule', rule, 'icmpv6'] + + if config.exists(rule_time + ['monthdays']): + config.delete(rule_time + ['monthdays']) + + if config.exists(rule_time + ['utc']): + config.delete(rule_time + ['utc']) + + if config.exists(rule_tcp_flags): + tmp = config.return_value(rule_tcp_flags) + config.delete(rule_tcp_flags) + for flag in tmp.split(","): + if flag[0] == '!': + config.set(rule_tcp_flags + ['not', flag[1:].lower()]) else: - config.rename(rule_icmp + ['type'], 'type-name') + config.set(rule_tcp_flags + [flag.lower()]) + + if config.exists(base + ['ipv6-name', name, 'rule', rule, 'protocol']): + tmp = config.return_value(base + ['ipv6-name', name, 'rule', rule, 'protocol']) + if tmp == 'icmpv6': + config.set(base + ['ipv6-name', name, 'rule', rule, 'protocol'], value='ipv6-icmp') + + if config.exists(rule_icmp + ['type']): + tmp = config.return_value(rule_icmp + ['type']) + type_code_match = re.match(r'^(\d+)/(\d+)$', tmp) + + if type_code_match: + config.set(rule_icmp + ['type'], value=type_code_match[1]) + config.set(rule_icmp + ['code'], value=type_code_match[2]) + elif tmp in icmpv6_remove: + config.delete(rule_icmp + ['type']) + elif tmp in icmpv6_translations: + translate = icmpv6_translations[tmp] + if isinstance(translate, str): + config.delete(rule_icmp + ['type']) + config.set(rule_icmp + ['type-name'], value=translate) + elif isinstance(translate, list): + config.set(rule_icmp + ['type'], value=translate[0]) + config.set(rule_icmp + ['code'], value=translate[1]) + else: + config.rename(rule_icmp + ['type'], 'type-name') + + for src_dst in ['destination', 'source']: + pg_base = base + ['ipv6-name', name, 'rule', rule, src_dst, 'group', 'port-group'] + proto_base = base + ['ipv6-name', name, 'rule', rule, 'protocol'] + if config.exists(pg_base) and not config.exists(proto_base): + config.set(proto_base, value='tcp_udp') try: with open(file_name, 'w') as f: -- cgit v1.2.3 From 78b247b724f74bdabab0706aaa7f5b00e5809bc1 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 28 Jan 2022 22:15:29 +0100 Subject: dhclient: T3392: remove /usr/sbin prefix from iproute2 ip command --- src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper | 16 ++++++++-------- src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper b/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper index 74a7e83bf..9d5505758 100644 --- a/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper +++ b/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper @@ -4,7 +4,7 @@ IF_METRIC=${IF_METRIC:-210} # Check if interface is inside a VRF -VRF_OPTION=$(/usr/sbin/ip -j -d link show ${interface} | awk '{if(match($0, /.*"master":"(\w+)".*"info_slave_kind":"vrf"/, IFACE_DETAILS)) printf("vrf %s", IFACE_DETAILS[1])}') +VRF_OPTION=$(ip -j -d link show ${interface} | awk '{if(match($0, /.*"master":"(\w+)".*"info_slave_kind":"vrf"/, IFACE_DETAILS)) printf("vrf %s", IFACE_DETAILS[1])}') # get status of FRR function frr_alive () { @@ -66,9 +66,9 @@ function iptovtysh () { # delete the same route from kernel before adding new one function delroute () { logmsg info "Checking if the route presented in kernel: $@ $VRF_OPTION" - if /usr/sbin/ip route show $@ $VRF_OPTION | grep -qx "$1 " ; then - logmsg info "Deleting IP route: \"/usr/sbin/ip route del $@ $VRF_OPTION\"" - /usr/sbin/ip route del $@ $VRF_OPTION + if ip route show $@ $VRF_OPTION | grep -qx "$1 " ; then + logmsg info "Deleting IP route: \"ip route del $@ $VRF_OPTION\"" + ip route del $@ $VRF_OPTION fi } @@ -76,8 +76,8 @@ function delroute () { function ip () { # pass comand to system `ip` if this is not related to routes change if [ "$2" != "route" ] ; then - logmsg info "Passing command to /usr/sbin/ip: \"$@\"" - /usr/sbin/ip $@ + logmsg info "Passing command to iproute2: \"$@\"" + ip $@ else # if we want to work with routes, try to use FRR first if frr_alive ; then @@ -87,8 +87,8 @@ function ip () { vtysh -c "conf t" -c "$VTYSH_CMD" else # add ip route to kernel - logmsg info "Modifying routes in kernel: \"/usr/sbin/ip $@\"" - /usr/sbin/ip $@ $VRF_OPTION + logmsg info "Modifying routes in kernel: \"ip $@\"" + ip $@ $VRF_OPTION fi fi } diff --git a/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup b/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup index ad6a1d5eb..a6989441b 100644 --- a/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup +++ b/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup @@ -1,7 +1,7 @@ ## ## VyOS cleanup ## -# NOTE: here we use 'ip' wrapper, therefore a route will be actually deleted via /usr/sbin/ip or vtysh, according to the system state +# NOTE: here we use 'ip' wrapper, therefore a route will be actually deleted via ip or vtysh, according to the system state hostsd_client="/usr/bin/vyos-hostsd-client" hostsd_changes= # check vyos-hostsd status -- cgit v1.2.3 From 1c828cc5a1dcbad6c8d94142de64ba9a529c14a7 Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Sat, 29 Jan 2022 01:46:50 +0100 Subject: firewall: T4178: Fix dict_keys issue with tcp flags --- python/vyos/firewall.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py index 4993d855e..a2e133217 100644 --- a/python/vyos/firewall.py +++ b/python/vyos/firewall.py @@ -190,7 +190,7 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name): def parse_tcp_flags(flags): include = [flag for flag in flags if flag != 'not'] - exclude = flags['not'].keys() if 'not' in flags else [] + exclude = list(flags['not']) if 'not' in flags else [] return f'tcp flags & ({"|".join(include + exclude)}) == {"|".join(include)}' def parse_time(time): -- cgit v1.2.3 From 87d93efc27d8e7a0ddd1dffe33eb7ada6e83fabc Mon Sep 17 00:00:00 2001 From: Henning Surmeier Date: Sat, 29 Jan 2022 13:15:15 +0100 Subject: policy: T4151: bugfix smoketest .sort() is an inplace operation and return None... --- smoketest/scripts/cli/test_policy.py | 55 +++++++----------------------------- 1 file changed, 10 insertions(+), 45 deletions(-) diff --git a/smoketest/scripts/cli/test_policy.py b/smoketest/scripts/cli/test_policy.py index 6dac28a0f..73d93c986 100755 --- a/smoketest/scripts/cli/test_policy.py +++ b/smoketest/scripts/cli/test_policy.py @@ -1135,9 +1135,6 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): self.cli_commit() - # Check generated configuration - - # Expected values original = """ 50: from 203.0.113.1 lookup 23 50: from 203.0.113.2 lookup 23 @@ -1159,9 +1156,6 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): self.cli_commit() - # Check generated configuration - - # Expected values original = """ 101: from all fwmark 0x18 lookup 154 """ @@ -1182,9 +1176,6 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): self.cli_commit() - # Check generated configuration - - # Expected values original = """ 102: from all to 203.0.113.1 lookup 154 """ @@ -1207,9 +1198,6 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): self.cli_commit() - # Check generated configuration - - # Expected values original = """ 100: from 203.0.113.11 fwmark 0x17 lookup 150 100: from 203.0.113.12 fwmark 0x17 lookup 150 @@ -1236,9 +1224,6 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): self.cli_commit() - # Check generated configuration - - # Expected values original = """ 103: from 203.0.113.11 to 203.0.113.13 fwmark 0x17 lookup 150 103: from 203.0.113.11 to 203.0.113.15 fwmark 0x17 lookup 150 @@ -1262,9 +1247,6 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): self.cli_commit() - # Check generated configuration - - # Expected values original = """ 50: from 2001:db8:123::/48 lookup 23 50: from 2001:db8:126::/48 lookup 23 @@ -1286,9 +1268,6 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): self.cli_commit() - # Check generated configuration - - # Expected values original = """ 100: from all fwmark 0x18 lookup 154 """ @@ -1309,9 +1288,6 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): self.cli_commit() - # Check generated configuration - - # Expected values original = """ 101: from all to 2001:db8:1337::/126 lookup 154 """ @@ -1334,9 +1310,6 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): self.cli_commit() - # Check generated configuration - - # Expected values original = """ 102: from 2001:db8:1338::/126 fwmark 0x17 lookup 150 102: from 2001:db8:1339::/126 fwmark 0x17 lookup 150 @@ -1363,9 +1336,6 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): self.cli_commit() - # Check generated configuration - - # Expected values original = """ 103: from 2001:db8:1338::/126 to 2001:db8:13::/48 fwmark 0x17 lookup 150 103: from 2001:db8:1338::/126 to 2001:db8:16::/48 fwmark 0x17 lookup 150 @@ -1404,20 +1374,17 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): self.cli_commit() - # Check generated configuration - - # Expected values original = """ - 103: from 203.0.113.1/24 to 203.0.112.1/24 fwmark 0x17 lookup 150 - 103: from 203.0.113.1/24 to 203.0.116.5 fwmark 0x17 lookup 150 - 103: from 203.0.114.5 to 203.0.112.1/24 fwmark 0x17 lookup 150 + 103: from 203.0.113.0/24 to 203.0.116.5 fwmark 0x17 lookup 150 + 103: from 203.0.114.5 to 203.0.112.0/24 fwmark 0x17 lookup 150 103: from 203.0.114.5 to 203.0.116.5 fwmark 0x17 lookup 150 + 103: from 203.0.113.0/24 to 203.0.112.0/24 fwmark 0x17 lookup 150 """ original_v6 = """ - 103: from 20016 to 2001:db8:13::/48 fwmark 0x17 lookup 150 103: from 2001:db8:1338::/126 to 2001:db8:16::/48 fwmark 0x17 lookup 150 103: from 2001:db8:1339::/56 to 2001:db8:13::/48 fwmark 0x17 lookup 150 103: from 2001:db8:1339::/56 to 2001:db8:16::/48 fwmark 0x17 lookup 150 + 103: from 2001:db8:1338::/126 to 2001:db8:13::/48 fwmark 0x17 lookup 150 """ tmp = cmd('ip rule show prio 103') tmp_v6 = cmd('ip -6 rule show prio 103') @@ -1432,11 +1399,8 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): tmp = cmd('ip rule show prio 103') tmp_v6 = cmd('ip -6 rule show prio 103') - original = None - original_v6 = None - - self.assertEqual(sort_ip(tmp), original) - self.assertEqual(sort_ip(tmp_v6), original_v6) + self.assertEqual(sort_ip(tmp), []) + self.assertEqual(sort_ip(tmp_v6), []) # Test multiple commits ipv4 def test_multiple_commit_ipv4_table_id(self): @@ -1452,8 +1416,6 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): self.cli_commit() - # Check generated configuration - # Expected values original_first = """ 105: from 192.0.2.1 lookup 151 105: from 192.0.2.2 lookup 151 @@ -1476,7 +1438,10 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): def sort_ip(output): - return output.splitlines().sort() + o = '\n'.join([' '.join(line.strip().split()) for line in output.strip().splitlines()]) + o = o.splitlines() + o.sort() + return o if __name__ == '__main__': unittest.main(verbosity=2) -- cgit v1.2.3 From ed67750b94e8bc779ec0e2cf6d568a3f7292de13 Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Sat, 29 Jan 2022 13:18:28 +0100 Subject: firewall: T4218: Adds a prefix to all user defined chains --- data/templates/firewall/nftables.tmpl | 4 ++-- data/templates/zone_policy/nftables.tmpl | 12 ++++++------ python/vyos/template.py | 3 ++- smoketest/scripts/cli/test_firewall.py | 6 +++--- smoketest/scripts/cli/test_zone_policy.py | 4 ++-- src/conf_mode/firewall-interface.py | 11 +++++++---- src/conf_mode/firewall.py | 7 +++++-- src/op_mode/firewall.py | 3 ++- 8 files changed, 29 insertions(+), 21 deletions(-) diff --git a/data/templates/firewall/nftables.tmpl b/data/templates/firewall/nftables.tmpl index 33c821e84..468a5a32f 100644 --- a/data/templates/firewall/nftables.tmpl +++ b/data/templates/firewall/nftables.tmpl @@ -32,7 +32,7 @@ table ip filter { {% endif %} {% if name is defined %} {% for name_text, conf in name.items() %} - chain {{ name_text }} { + chain NAME_{{ name_text }} { {% if conf.rule is defined %} {% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not defined %} {{ rule_conf | nft_rule(name_text, rule_id) }} @@ -82,7 +82,7 @@ table ip6 filter { {% endif %} {% if ipv6_name is defined %} {% for name_text, conf in ipv6_name.items() %} - chain {{ name_text }} { + chain NAME6_{{ name_text }} { {% if conf.rule is defined %} {% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not defined %} {{ rule_conf | nft_rule(name_text, rule_id, 'ip6') }} diff --git a/data/templates/zone_policy/nftables.tmpl b/data/templates/zone_policy/nftables.tmpl index e59208a0d..093da6bd8 100644 --- a/data/templates/zone_policy/nftables.tmpl +++ b/data/templates/zone_policy/nftables.tmpl @@ -13,7 +13,7 @@ table ip filter { chain VZONE_{{ zone_name }}_IN { iifname lo counter return {% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall.name is defined %} - iifname { {{ zone[from_zone].interface | join(",") }} } counter jump {{ from_conf.firewall.name }} + iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME_{{ from_conf.firewall.name }} iifname { {{ zone[from_zone].interface | join(",") }} } counter return {% endfor %} counter {{ zone_conf.default_action if zone_conf.default_action is defined else 'drop' }} @@ -21,7 +21,7 @@ table ip filter { chain VZONE_{{ zone_name }}_OUT { oifname lo counter return {% for from_zone, from_conf in zone_conf.from_local.items() if from_conf.firewall.name is defined %} - oifname { {{ zone[from_zone].interface | join(",") }} } counter jump {{ from_conf.firewall.name }} + oifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME_{{ from_conf.firewall.name }} oifname { {{ zone[from_zone].interface | join(",") }} } counter return {% endfor %} counter {{ zone_conf.default_action if zone_conf.default_action is defined else 'drop' }} @@ -34,7 +34,7 @@ table ip filter { {% endif %} {% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall.name is defined %} {% if zone[from_zone].local_zone is not defined %} - iifname { {{ zone[from_zone].interface | join(",") }} } counter jump {{ from_conf.firewall.name }} + iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME_{{ from_conf.firewall.name }} iifname { {{ zone[from_zone].interface | join(",") }} } counter return {% endif %} {% endfor %} @@ -50,7 +50,7 @@ table ip6 filter { chain VZONE6_{{ zone_name }}_IN { iifname lo counter return {% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall.ipv6_name is defined %} - iifname { {{ zone[from_zone].interface | join(",") }} } counter jump {{ from_conf.firewall.ipv6_name }} + iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME6_{{ from_conf.firewall.ipv6_name }} iifname { {{ zone[from_zone].interface | join(",") }} } counter return {% endfor %} counter {{ zone_conf.default_action if zone_conf.default_action is defined else 'drop' }} @@ -58,7 +58,7 @@ table ip6 filter { chain VZONE6_{{ zone_name }}_OUT { oifname lo counter return {% for from_zone, from_conf in zone_conf.from_local.items() if from_conf.firewall.ipv6_name is defined %} - oifname { {{ zone[from_zone].interface | join(",") }} } counter jump {{ from_conf.firewall.ipv6_name }} + oifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME6_{{ from_conf.firewall.ipv6_name }} oifname { {{ zone[from_zone].interface | join(",") }} } counter return {% endfor %} counter {{ zone_conf.default_action if zone_conf.default_action is defined else 'drop' }} @@ -71,7 +71,7 @@ table ip6 filter { {% endif %} {% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall.ipv6_name is defined %} {% if zone[from_zone].local_zone is not defined %} - iifname { {{ zone[from_zone].interface | join(",") }} } counter jump {{ from_conf.firewall.ipv6_name }} + iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME6_{{ from_conf.firewall.ipv6_name }} iifname { {{ zone[from_zone].interface | join(",") }} } counter return {% endif %} {% endfor %} diff --git a/python/vyos/template.py b/python/vyos/template.py index 633b28ade..3675aef5d 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -548,6 +548,7 @@ def nft_intra_zone_action(zone_conf, ipv6=False): if 'intra_zone_filtering' in zone_conf: intra_zone = zone_conf['intra_zone_filtering'] fw_name = 'ipv6_name' if ipv6 else 'name' + name_prefix = 'NAME6_' if ipv6 else 'NAME_' if 'action' in intra_zone: if intra_zone['action'] == 'accept': @@ -555,5 +556,5 @@ def nft_intra_zone_action(zone_conf, ipv6=False): return intra_zone['action'] elif dict_search_args(intra_zone, 'firewall', fw_name): name = dict_search_args(intra_zone, 'firewall', fw_name) - return f'jump {name}' + return f'jump {name_prefix}{name}' return 'return' diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py index 6b74e6c92..ecc0c29a0 100755 --- a/smoketest/scripts/cli/test_firewall.py +++ b/smoketest/scripts/cli/test_firewall.py @@ -63,7 +63,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.cli_commit() nftables_search = [ - ['iifname "eth0"', 'jump smoketest'], + ['iifname "eth0"', 'jump NAME_smoketest'], ['ip saddr { 172.16.99.0/24 }', 'ip daddr 172.16.10.10', 'th dport { 53, 123 }', 'return'], ['ether saddr { 00:01:02:03:04:05 }', 'return'] ] @@ -94,7 +94,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.cli_commit() nftables_search = [ - ['iifname "eth0"', 'jump smoketest'], + ['iifname "eth0"', 'jump NAME_smoketest'], ['saddr 172.16.20.10', 'daddr 172.16.10.10', 'return'], ['tcp flags & (syn | ack) == syn', 'tcp dport { 8888 }', 'reject'], ['smoketest default-action', 'drop'] @@ -124,7 +124,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.cli_commit() nftables_search = [ - ['iifname "eth0"', 'jump v6-smoketest'], + ['iifname "eth0"', 'jump NAME6_v6-smoketest'], ['saddr 2002::1', 'daddr 2002::1:1', 'return'], ['meta l4proto { tcp, udp }', 'th dport { 8888 }', 'reject'], ['smoketest default-action', 'drop'] diff --git a/smoketest/scripts/cli/test_zone_policy.py b/smoketest/scripts/cli/test_zone_policy.py index c0af6164b..00dfe0182 100755 --- a/smoketest/scripts/cli/test_zone_policy.py +++ b/smoketest/scripts/cli/test_zone_policy.py @@ -44,8 +44,8 @@ class TestZonePolicy(VyOSUnitTestSHIM.TestCase): ['oifname { "eth0" }', 'jump VZONE_smoketest-eth0'], ['jump VZONE_smoketest-local_IN'], ['jump VZONE_smoketest-local_OUT'], - ['iifname { "eth0" }', 'jump smoketest'], - ['oifname { "eth0" }', 'jump smoketest'] + ['iifname { "eth0" }', 'jump NAME_smoketest'], + ['oifname { "eth0" }', 'jump NAME_smoketest'] ] nftables_output = cmd('sudo nft list table ip filter') diff --git a/src/conf_mode/firewall-interface.py b/src/conf_mode/firewall-interface.py index a7442ecbd..9a5d278e9 100755 --- a/src/conf_mode/firewall-interface.py +++ b/src/conf_mode/firewall-interface.py @@ -31,6 +31,9 @@ from vyos import ConfigError from vyos import airbag airbag.enable() +NAME_PREFIX = 'NAME_' +NAME6_PREFIX = 'NAME6_' + NFT_CHAINS = { 'in': 'VYOS_FW_FORWARD', 'out': 'VYOS_FW_FORWARD', @@ -127,7 +130,7 @@ def apply(if_firewall): name = dict_search_args(if_firewall, direction, 'name') if name: - rule_exists = cleanup_rule('ip filter', chain, if_prefix, ifname, name) + rule_exists = cleanup_rule('ip filter', chain, if_prefix, ifname, f'{NAME_PREFIX}{name}') if not rule_exists: rule_action = 'insert' @@ -138,13 +141,13 @@ def apply(if_firewall): rule_action = 'add' rule_prefix = f'position {handle}' - run(f'nft {rule_action} rule ip filter {chain} {rule_prefix} {if_prefix}ifname {ifname} counter jump {name}') + run(f'nft {rule_action} rule ip filter {chain} {rule_prefix} {if_prefix}ifname {ifname} counter jump {NAME_PREFIX}{name}') else: cleanup_rule('ip filter', chain, if_prefix, ifname) ipv6_name = dict_search_args(if_firewall, direction, 'ipv6_name') if ipv6_name: - rule_exists = cleanup_rule('ip6 filter', ipv6_chain, if_prefix, ifname, ipv6_name) + rule_exists = cleanup_rule('ip6 filter', ipv6_chain, if_prefix, ifname, f'{NAME6_PREFIX}{ipv6_name}') if not rule_exists: rule_action = 'insert' @@ -155,7 +158,7 @@ def apply(if_firewall): rule_action = 'add' rule_prefix = f'position {handle}' - run(f'nft {rule_action} rule ip6 filter {ipv6_chain} {rule_prefix} {if_prefix}ifname {ifname} counter jump {ipv6_name}') + run(f'nft {rule_action} rule ip6 filter {ipv6_chain} {rule_prefix} {if_prefix}ifname {ifname} counter jump {NAME6_PREFIX}{ipv6_name}') else: cleanup_rule('ip6 filter', ipv6_chain, if_prefix, ifname) diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index 358b938e3..5b6c57d04 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -54,6 +54,9 @@ sysfs_config = { 'twa_hazards_protection': {'sysfs': '/proc/sys/net/ipv4/tcp_rfc1337'} } +NAME_PREFIX = 'NAME_' +NAME6_PREFIX = 'NAME6_' + preserve_chains = [ 'INPUT', 'FORWARD', @@ -281,9 +284,9 @@ def cleanup_commands(firewall): else: commands.append(f'flush chain {table} {chain}') elif chain not in preserve_chains and not chain.startswith("VZONE"): - if table == 'ip filter' and dict_search_args(firewall, 'name', chain): + if table == 'ip filter' and dict_search_args(firewall, 'name', chain.replace(NAME_PREFIX, "", 1)): commands.append(f'flush chain {table} {chain}') - elif table == 'ip6 filter' and dict_search_args(firewall, 'ipv6_name', chain): + elif table == 'ip6 filter' and dict_search_args(firewall, 'ipv6_name', chain.replace(NAME6_PREFIX, "", 1)): commands.append(f'flush chain {table} {chain}') else: commands += cleanup_rule(table, chain) diff --git a/src/op_mode/firewall.py b/src/op_mode/firewall.py index b6bb5b802..3146fc357 100755 --- a/src/op_mode/firewall.py +++ b/src/op_mode/firewall.py @@ -88,7 +88,8 @@ def get_config_firewall(conf, name=None, ipv6=False, interfaces=True): def get_nftables_details(name, ipv6=False): suffix = '6' if ipv6 else '' - command = f'sudo nft list chain ip{suffix} filter {name}' + name_prefix = 'NAME6_' if ipv6 else 'NAME_' + command = f'sudo nft list chain ip{suffix} filter {name_prefix}{name}' try: results = cmd(command) except: -- cgit v1.2.3 From 985a9e8536cb7f049e82dd1c7333ecced34563fa Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Sat, 29 Jan 2022 23:34:05 +0100 Subject: firewall: T4216: Add support for negated firewall groups --- python/vyos/firewall.py | 25 +++++++++++++++++++++---- src/conf_mode/firewall.py | 4 ++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py index a2e133217..a74fd922a 100644 --- a/python/vyos/firewall.py +++ b/python/vyos/firewall.py @@ -104,13 +104,25 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name): group = side_conf['group'] if 'address_group' in group: group_name = group['address_group'] - output.append(f'{ip_name} {prefix}addr $A{def_suffix}_{group_name}') + operator = '' + if group_name[0] == '!': + operator = '!=' + group_name = group_name[1:] + output.append(f'{ip_name} {prefix}addr {operator} $A{def_suffix}_{group_name}') elif 'network_group' in group: group_name = group['network_group'] - output.append(f'{ip_name} {prefix}addr $N{def_suffix}_{group_name}') + operator = '' + if group_name[0] == '!': + operator = '!=' + group_name = group_name[1:] + output.append(f'{ip_name} {prefix}addr {operator} $N{def_suffix}_{group_name}') if 'mac_group' in group: group_name = group['mac_group'] - output.append(f'ether {prefix}addr $M_{group_name}') + operator = '' + if group_name[0] == '!': + operator = '!=' + group_name = group_name[1:] + output.append(f'ether {prefix}addr {operator} $M_{group_name}') if 'port_group' in group: proto = rule_conf['protocol'] group_name = group['port_group'] @@ -118,7 +130,12 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name): if proto == 'tcp_udp': proto = 'th' - output.append(f'{proto} {prefix}port $P_{group_name}') + operator = '' + if group_name[0] == '!': + operator = '!=' + group_name = group_name[1:] + + output.append(f'{proto} {prefix}port {operator} $P_{group_name}') if 'log' in rule_conf and rule_conf['log'] == 'enable': action = rule_conf['action'] if 'action' in rule_conf else 'accept' diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index 5b6c57d04..064b2d5a3 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -204,6 +204,10 @@ def verify_rule(firewall, rule_conf, ipv6): for group in valid_groups: if group in side_conf['group']: group_name = side_conf['group'][group] + + if group_name and group_name[0] == '!': + group_name = group_name[1:] + fw_group = f'ipv6_{group}' if ipv6 and group in ['address_group', 'network_group'] else group error_group = fw_group.replace("_", "-") group_obj = dict_search_args(firewall, 'group', fw_group, group_name) -- cgit v1.2.3 From 8532f2c391e895d7cd4c10b6d83d1e26973202a3 Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Sun, 30 Jan 2022 00:07:35 +0100 Subject: policy: T4213: Fix duplicate commands from multiple rules with single table --- src/conf_mode/policy-route.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/conf_mode/policy-route.py b/src/conf_mode/policy-route.py index 7dcab4b58..82f668acf 100755 --- a/src/conf_mode/policy-route.py +++ b/src/conf_mode/policy-route.py @@ -206,6 +206,7 @@ def apply_table_marks(policy): for route in ['route', 'route6']: if route in policy: cmd_str = 'ip' if route == 'route' else 'ip -6' + tables = [] for name, pol_conf in policy[route].items(): if 'rule' in pol_conf: for rule_id, rule_conf in pol_conf['rule'].items(): @@ -213,6 +214,9 @@ def apply_table_marks(policy): if set_table: if set_table == 'main': set_table = '254' + if set_table in tables: + continue + tables.append(set_table) table_mark = mark_offset - int(set_table) cmd(f'{cmd_str} rule add pref {set_table} fwmark {table_mark} table {set_table}') -- cgit v1.2.3 From c6c562eca6ff469f603697f7f1d9319b2a5504a3 Mon Sep 17 00:00:00 2001 From: Henning Surmeier Date: Fri, 28 Jan 2022 23:55:06 +0100 Subject: policy: T4219: add local-route(6) incoming-interface --- .../include/interface/inbound-interface.xml.i | 10 ++++ interface-definitions/policy-local-route.xml.in | 2 + smoketest/scripts/cli/test_policy.py | 53 +++++++++++++++++++++- src/conf_mode/policy-local-route.py | 34 ++++++++++++-- 4 files changed, 94 insertions(+), 5 deletions(-) create mode 100644 interface-definitions/include/interface/inbound-interface.xml.i diff --git a/interface-definitions/include/interface/inbound-interface.xml.i b/interface-definitions/include/interface/inbound-interface.xml.i new file mode 100644 index 000000000..5a8d47280 --- /dev/null +++ b/interface-definitions/include/interface/inbound-interface.xml.i @@ -0,0 +1,10 @@ + + + + Inbound Interface + + + + + + diff --git a/interface-definitions/policy-local-route.xml.in b/interface-definitions/policy-local-route.xml.in index 11b1e04d9..573a7963f 100644 --- a/interface-definitions/policy-local-route.xml.in +++ b/interface-definitions/policy-local-route.xml.in @@ -88,6 +88,7 @@ + #include @@ -177,6 +178,7 @@ + #include diff --git a/smoketest/scripts/cli/test_policy.py b/smoketest/scripts/cli/test_policy.py index 73d93c986..491f1766d 100755 --- a/smoketest/scripts/cli/test_policy.py +++ b/smoketest/scripts/cli/test_policy.py @@ -1206,6 +1206,32 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): self.assertEqual(sort_ip(tmp), sort_ip(original)) + # Test set table for sources with iif + def test_iif_sources_table_id(self): + path = base_path + ['local-route'] + + sources = ['203.0.113.11', '203.0.113.12'] + iif = 'lo' + rule = '100' + table = '150' + + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'inbound-interface', iif]) + for src in sources: + self.cli_set(path + ['rule', rule, 'source', src]) + + self.cli_commit() + + # Check generated configuration + # Expected values + original = """ + 100: from 203.0.113.11 iif lo lookup 150 + 100: from 203.0.113.12 iif lo lookup 150 + """ + tmp = cmd('ip rule show prio 100') + + self.assertEqual(sort_ip(tmp), sort_ip(original)) + # Test set table for sources and destinations with fwmark def test_fwmark_sources_destination_table_id(self): path = base_path + ['local-route'] @@ -1318,6 +1344,31 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): self.assertEqual(sort_ip(tmp), sort_ip(original)) + # Test set table for sources with iif ipv6 + def test_iif_sources_ipv6_table_id(self): + path = base_path + ['local-route6'] + + sources = ['2001:db8:1338::/126', '2001:db8:1339::/126'] + iif = 'lo' + rule = '102' + table = '150' + for src in sources: + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'source', src]) + self.cli_set(path + ['rule', rule, 'inbound-interface', iif]) + + self.cli_commit() + + # Check generated configuration + # Expected values + original = """ + 102: from 2001:db8:1338::/126 iif lo lookup 150 + 102: from 2001:db8:1339::/126 iif lo lookup 150 + """ + tmp = cmd('ip -6 rule show prio 102') + + self.assertEqual(sort_ip(tmp), sort_ip(original)) + # Test set table for sources and destinations with fwmark ipv6 def test_fwmark_sources_destination_ipv6_table_id(self): path = base_path + ['local-route6'] @@ -1384,7 +1435,7 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): 103: from 2001:db8:1338::/126 to 2001:db8:16::/48 fwmark 0x17 lookup 150 103: from 2001:db8:1339::/56 to 2001:db8:13::/48 fwmark 0x17 lookup 150 103: from 2001:db8:1339::/56 to 2001:db8:16::/48 fwmark 0x17 lookup 150 - 103: from 2001:db8:1338::/126 to 2001:db8:13::/48 fwmark 0x17 lookup 150 + 103: from 2001:db8:1338::/126 to 2001:db8:13::/48 fwmark 0x17 lookup 150 """ tmp = cmd('ip rule show prio 103') tmp_v6 = cmd('ip -6 rule show prio 103') diff --git a/src/conf_mode/policy-local-route.py b/src/conf_mode/policy-local-route.py index 71183c6ba..0990039c1 100755 --- a/src/conf_mode/policy-local-route.py +++ b/src/conf_mode/policy-local-route.py @@ -18,6 +18,7 @@ import os from sys import exit +from netifaces import interfaces from vyos.config import Config from vyos.configdict import dict_merge from vyos.configdict import node_changed @@ -51,12 +52,15 @@ def get_config(config=None): for rule in (tmp or []): src = leaf_node_changed(conf, base_rule + [rule, 'source']) fwmk = leaf_node_changed(conf, base_rule + [rule, 'fwmark']) + iif = leaf_node_changed(conf, base_rule + [rule, 'inbound-interface']) dst = leaf_node_changed(conf, base_rule + [rule, 'destination']) rule_def = {} if src: rule_def = dict_merge({'source' : src}, rule_def) if fwmk: rule_def = dict_merge({'fwmark' : fwmk}, rule_def) + if iif: + rule_def = dict_merge({'inbound_interface' : iif}, rule_def) if dst: rule_def = dict_merge({'destination' : dst}, rule_def) dict = dict_merge({dict_id : {rule : rule_def}}, dict) @@ -72,6 +76,7 @@ def get_config(config=None): for rule, rule_config in pbr[route]['rule'].items(): src = leaf_node_changed(conf, base_rule + [rule, 'source']) fwmk = leaf_node_changed(conf, base_rule + [rule, 'fwmark']) + iif = leaf_node_changed(conf, base_rule + [rule, 'inbound-interface']) dst = leaf_node_changed(conf, base_rule + [rule, 'destination']) # keep track of changes in configuration # otherwise we might remove an existing node although nothing else has changed @@ -100,6 +105,13 @@ def get_config(config=None): changed = True if len(fwmk) > 0: rule_def = dict_merge({'fwmark' : fwmk}, rule_def) + if iif is None: + if 'inbound_interface' in rule_config: + rule_def = dict_merge({'inbound_interface': rule_config['inbound_interface']}, rule_def) + else: + changed = True + if len(iif) > 0: + rule_def = dict_merge({'inbound_interface' : iif}, rule_def) if dst is None: if 'destination' in rule_config: rule_def = dict_merge({'destination': rule_config['destination']}, rule_def) @@ -125,11 +137,18 @@ def verify(pbr): pbr_route = pbr[route] if 'rule' in pbr_route: for rule in pbr_route['rule']: - if 'source' not in pbr_route['rule'][rule] and 'destination' not in pbr_route['rule'][rule] and 'fwmark' not in pbr_route['rule'][rule]: - raise ConfigError('Source or destination address or fwmark is required!') + if 'source' not in pbr_route['rule'][rule] \ + and 'destination' not in pbr_route['rule'][rule] \ + and 'fwmark' not in pbr_route['rule'][rule] \ + and 'inbound_interface' not in pbr_route['rule'][rule]: + raise ConfigError('Source or destination address or fwmark or inbound-interface is required!') else: if 'set' not in pbr_route['rule'][rule] or 'table' not in pbr_route['rule'][rule]['set']: raise ConfigError('Table set is required!') + if 'inbound_interface' in pbr_route['rule'][rule]: + interface = pbr_route['rule'][rule]['inbound_interface'] + if interface not in interfaces(): + raise ConfigError(f'Interface "{interface}" does not exist') return None @@ -159,7 +178,10 @@ def apply(pbr): rule_config['fwmark'] = rule_config['fwmark'] if 'fwmark' in rule_config else [''] for fwmk in rule_config['fwmark']: f_fwmk = '' if fwmk == '' else f' fwmark {fwmk} ' - call(f'ip{v6} rule del prio {rule} {f_src}{f_dst}{f_fwmk}') + rule_config['inbound_interface'] = rule_config['inbound_interface'] if 'inbound_interface' in rule_config else [''] + for iif in rule_config['inbound_interface']: + f_iif = '' if iif == '' else f' iif {iif} ' + call(f'ip{v6} rule del prio {rule} {f_src}{f_dst}{f_fwmk}{f_iif}') # Generate new config for route in ['local_route', 'local_route6']: @@ -183,7 +205,11 @@ def apply(pbr): if 'fwmark' in rule_config: fwmk = rule_config['fwmark'] f_fwmk = f' fwmark {fwmk} ' - call(f'ip{v6} rule add prio {rule} {f_src}{f_dst}{f_fwmk} lookup {table}') + f_iif = '' + if 'inbound_interface' in rule_config: + iif = rule_config['inbound_interface'] + f_iif = f' iif {iif} ' + call(f'ip{v6} rule add prio {rule} {f_src}{f_dst}{f_fwmk}{f_iif} lookup {table}') return None -- cgit v1.2.3 From fafd25143d46220c537de8ef514d5954129528eb Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Sun, 30 Jan 2022 00:39:12 +0100 Subject: firewall: T2199: Add constraint for tagnode names --- interface-definitions/firewall.xml.in | 24 ++++++++++++++++++++++++ interface-definitions/policy-route.xml.in | 6 ++++++ interface-definitions/zone-policy.xml.in | 3 +++ 3 files changed, 33 insertions(+) diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in index f38bcfd9c..f2aca4b3a 100644 --- a/interface-definitions/firewall.xml.in +++ b/interface-definitions/firewall.xml.in @@ -74,6 +74,9 @@ Firewall address-group + + ^[a-zA-Z0-9][\w\-\.]*$ + @@ -100,6 +103,9 @@ Firewall ipv6-address-group + + ^[a-zA-Z0-9][\w\-\.]*$ + @@ -126,6 +132,9 @@ Firewall ipv6-network-group + + ^[a-zA-Z0-9][\w\-\.]*$ + #include @@ -147,6 +156,9 @@ Firewall mac-group + + ^[a-zA-Z0-9][\w\-\.]*$ + #include @@ -168,6 +180,9 @@ Firewall network-group + + ^[a-zA-Z0-9][\w\-\.]*$ + #include @@ -189,6 +204,9 @@ Firewall port-group + + ^[a-zA-Z0-9][\w\-\.]*$ + #include @@ -240,6 +258,9 @@ IPv6 firewall rule-set name + + ^[a-zA-Z0-9][\w\-\.]*$ + #include @@ -423,6 +444,9 @@ IPv4 firewall rule-set name + + ^[a-zA-Z0-9][\w\-\.]*$ + #include diff --git a/interface-definitions/policy-route.xml.in b/interface-definitions/policy-route.xml.in index 4ce953b52..a1c3b50de 100644 --- a/interface-definitions/policy-route.xml.in +++ b/interface-definitions/policy-route.xml.in @@ -5,6 +5,9 @@ Policy route rule set name for IPv6 + + ^[a-zA-Z0-9][\w\-\.]*$ + 201 @@ -51,6 +54,9 @@ Policy route rule set name for IPv4 + + ^[a-zA-Z0-9][\w\-\.]*$ + 201 diff --git a/interface-definitions/zone-policy.xml.in b/interface-definitions/zone-policy.xml.in index dd64c7c16..69ee031c7 100644 --- a/interface-definitions/zone-policy.xml.in +++ b/interface-definitions/zone-policy.xml.in @@ -13,6 +13,9 @@ txt Zone name + + ^[a-zA-Z0-9][\w\-\.]*$ + #include -- cgit v1.2.3 From ff2cc45f8ba6d7ad1bc75ef384643692a54f31cc Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Sun, 30 Jan 2022 22:52:49 +0100 Subject: firewall: T2199: Fix errors when referencing an empty chain --- src/conf_mode/firewall.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index 064b2d5a3..9dec2143e 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -73,6 +73,9 @@ preserve_chains = [ 'VYOS_FRAG6_MARK' ] +nft_iface_chains = ['VYOS_FW_FORWARD', 'VYOS_FW_OUTPUT', 'VYOS_FW_LOCAL'] +nft6_iface_chains = ['VYOS_FW6_FORWARD', 'VYOS_FW6_OUTPUT', 'VYOS_FW6_LOCAL'] + valid_groups = [ 'address_group', 'network_group', @@ -248,27 +251,29 @@ def verify(firewall): name = dict_search_args(if_firewall, direction, 'name') ipv6_name = dict_search_args(if_firewall, direction, 'ipv6_name') - if name and not dict_search_args(firewall, 'name', name): + if name and dict_search_args(firewall, 'name', name) == None: raise ConfigError(f'Firewall name "{name}" is still referenced on interface {ifname}') - if ipv6_name and not dict_search_args(firewall, 'ipv6_name', ipv6_name): + if ipv6_name and dict_search_args(firewall, 'ipv6_name', ipv6_name) == None: raise ConfigError(f'Firewall ipv6-name "{ipv6_name}" is still referenced on interface {ifname}') for fw_name, used_names in firewall['zone_policy'].items(): for name in used_names: - if not dict_search_args(firewall, fw_name, name): + if dict_search_args(firewall, fw_name, name) == None: raise ConfigError(f'Firewall {fw_name.replace("_", "-")} "{name}" is still referenced in zone-policy') return None def cleanup_rule(table, jump_chain): commands = [] - results = cmd(f'nft -a list table {table}').split("\n") - for line in results: - if f'jump {jump_chain}' in line: - handle_search = re.search('handle (\d+)', line) - if handle_search: - commands.append(f'delete rule {table} {chain} handle {handle_search[1]}') + chains = nft_iface_chains if table == 'ip filter' else nft6_iface_chains + for chain in chains: + results = cmd(f'nft -a list chain {table} {chain}').split("\n") + for line in results: + if f'jump {jump_chain}' in line: + handle_search = re.search('handle (\d+)', line) + if handle_search: + commands.append(f'delete rule {table} {chain} handle {handle_search[1]}') return commands def cleanup_commands(firewall): @@ -288,9 +293,9 @@ def cleanup_commands(firewall): else: commands.append(f'flush chain {table} {chain}') elif chain not in preserve_chains and not chain.startswith("VZONE"): - if table == 'ip filter' and dict_search_args(firewall, 'name', chain.replace(NAME_PREFIX, "", 1)): + if table == 'ip filter' and dict_search_args(firewall, 'name', chain.replace(NAME_PREFIX, "", 1)) != None: commands.append(f'flush chain {table} {chain}') - elif table == 'ip6 filter' and dict_search_args(firewall, 'ipv6_name', chain.replace(NAME6_PREFIX, "", 1)): + elif table == 'ip6 filter' and dict_search_args(firewall, 'ipv6_name', chain.replace(NAME6_PREFIX, "", 1)) != None: commands.append(f'flush chain {table} {chain}') else: commands += cleanup_rule(table, chain) -- cgit v1.2.3 From 84d790b65e1356f1c82b2a0e498f834abbe08653 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Mon, 31 Jan 2022 07:52:57 -0500 Subject: T4221: add force_to_list Jinja2 filter --- python/vyos/template.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/python/vyos/template.py b/python/vyos/template.py index 633b28ade..4d081b4c2 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -127,6 +127,14 @@ def render( ################################## # Custom template filters follow # ################################## +@register_filter('force_to_list') +def force_to_list(value): + """ Convert scalars to single-item lists and leave lists untouched """ + if isinstance(value, list): + return value + else: + return [value] + @register_filter('ip_from_cidr') def ip_from_cidr(prefix): """ Take an IPv4/IPv6 CIDR host and strip cidr mask. -- cgit v1.2.3 From 3dc698f18bc83149ab9044bcf18d60a9332685d2 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 31 Jan 2022 21:56:46 +0100 Subject: smoketest: upnpd: T3420: refine code and re-use paths --- smoketest/scripts/cli/test_service_upnp.py | 68 ++++++++++++++++++++++-------- 1 file changed, 51 insertions(+), 17 deletions(-) diff --git a/smoketest/scripts/cli/test_service_upnp.py b/smoketest/scripts/cli/test_service_upnp.py index 9fbbdaff9..c3e9b600f 100755 --- a/smoketest/scripts/cli/test_service_upnp.py +++ b/smoketest/scripts/cli/test_service_upnp.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2022 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 @@ -20,52 +20,86 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError +from vyos.template import ip_from_cidr from vyos.util import read_file from vyos.util import process_named_running UPNP_CONF = '/run/upnp/miniupnp.conf' +DAEMON = 'miniupnpd' interface = 'eth0' base_path = ['service', 'upnp'] address_base = ['interfaces', 'ethernet', interface, 'address'] +ipv4_addr = '100.64.0.1/24' +ipv6_addr = '2001:db8::1/64' + class TestServiceUPnP(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(cls, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + cls.cli_set(cls, address_base + [ipv4_addr]) + cls.cli_set(cls, address_base + [ipv6_addr]) + + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, address_base) + cls._session.commit() + + super(cls, cls).tearDownClass() + def tearDown(self): - self.cli_delete(address_base) + # Check for running process + self.assertTrue(process_named_running(DAEMON)) + self.cli_delete(base_path) self.cli_commit() - + + # Check for running process + self.assertFalse(process_named_running(DAEMON)) + def test_ipv4_base(self): - self.cli_set(address_base + ['100.64.0.1/24']) self.cli_set(base_path + ['nat-pmp']) - self.cli_set(base_path + ['wan-interface', interface]) self.cli_set(base_path + ['listen', interface]) + + # check validate() - WAN interface is mandatory + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['wan-interface', interface]) + self.cli_commit() - + config = read_file(UPNP_CONF) self.assertIn(f'ext_ifname={interface}', config) self.assertIn(f'listening_ip={interface}', config) self.assertIn(f'enable_natpmp=yes', config) self.assertIn(f'enable_upnp=yes', config) - - # Check for running process - self.assertTrue(process_named_running('miniupnpd')) - + def test_ipv6_base(self): - self.cli_set(address_base + ['2001:db8::1/64']) + v6_addr = ip_from_cidr(ipv6_addr) + self.cli_set(base_path + ['nat-pmp']) - self.cli_set(base_path + ['wan-interface', interface]) self.cli_set(base_path + ['listen', interface]) - self.cli_set(base_path + ['listen', '2001:db8::1']) + self.cli_set(base_path + ['listen', v6_addr]) + + # check validate() - WAN interface is mandatory + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['wan-interface', interface]) + self.cli_commit() - + config = read_file(UPNP_CONF) self.assertIn(f'ext_ifname={interface}', config) self.assertIn(f'listening_ip={interface}', config) + self.assertIn(f'ipv6_listening_ip={v6_addr}', config) self.assertIn(f'enable_natpmp=yes', config) self.assertIn(f'enable_upnp=yes', config) - - # Check for running process - self.assertTrue(process_named_running('miniupnpd')) if __name__ == '__main__': unittest.main(verbosity=2) -- cgit v1.2.3 From 2ac8376ca1b79ba05bcf83d77c8e9d903e9b50f0 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 31 Jan 2022 21:57:08 +0100 Subject: upnpd: T3420: use proper include directives --- interface-definitions/service_upnp.xml.in | 44 ++++++++----------------------- 1 file changed, 11 insertions(+), 33 deletions(-) diff --git a/interface-definitions/service_upnp.xml.in b/interface-definitions/service_upnp.xml.in index 8d0a14d4e..7cfe1f02e 100644 --- a/interface-definitions/service_upnp.xml.in +++ b/interface-definitions/service_upnp.xml.in @@ -19,7 +19,7 @@ - WAN network interface (REQUIRE) + WAN network interface @@ -139,49 +139,27 @@ txt The STUN server host address - - stun.stunprotocol.org - stunprotocol - - - stun.sipgate.net - sipgate - - - stun.xten.com - xten - - - txt - other STUN Server - - - - - - The STUN server port - - txt - The STUN server port - + + + + #include - + UPnP Rule + + u32:0-65535 + Rule number + - - - Disable Rule - - - + #include Port range (REQUIRE) -- cgit v1.2.3 From 494ca8ffa043fd90c83cd052ff7da170646cec05 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 31 Jan 2022 21:57:24 +0100 Subject: upnpd: T3420: code cleanup --- src/conf_mode/service_upnp.py | 44 ++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/src/conf_mode/service_upnp.py b/src/conf_mode/service_upnp.py index 638296f45..d21b31990 100755 --- a/src/conf_mode/service_upnp.py +++ b/src/conf_mode/service_upnp.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2022 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -24,7 +24,6 @@ from ipaddress import IPv6Network from vyos.config import Config from vyos.configdict import dict_merge -from vyos.configdict import dict_search from vyos.configdict import get_interface_dict from vyos.configverify import verify_vrf from vyos.util import call @@ -43,17 +42,18 @@ def get_config(config=None): conf = config else: conf = Config() + base = ['service', 'upnp'] upnpd = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - + if not upnpd: return None - - if dict_search('rule', upnpd): + + if 'rule' in upnpd: default_member_values = defaults(base + ['rule']) for rule,rule_config in upnpd['rule'].items(): upnpd['rule'][rule] = dict_merge(default_member_values, upnpd['rule'][rule]) - + uuidgen = uuid.uuid1() upnpd.update({'uuid': uuidgen}) @@ -62,7 +62,7 @@ def get_config(config=None): def get_all_interface_addr(prefix, filter_dev, filter_family): list_addr = [] interfaces = netifaces.interfaces() - + for interface in interfaces: if filter_dev and interface in filter_dev: continue @@ -87,27 +87,28 @@ def get_all_interface_addr(prefix, filter_dev, filter_family): list_addr.append(addr['addr'] + prefix) else: list_addr.append(addr['addr']) - + return list_addr def verify(upnpd): if not upnpd: return None - + if 'wan_interface' not in upnpd: raise ConfigError('To enable UPNP, you must have the "wan-interface" option!') - - if dict_search('rules', upnpd): - for rule,rule_config in upnpd['rule'].items(): + + if 'rule' in upnpd: + for rule, rule_config in upnpd['rule'].items(): for option in ['external_port_range', 'internal_port_range', 'ip', 'action']: if option not in rule_config: - raise ConfigError(f'A UPNP rule must have an "{option}" option!') - - if dict_search('stun', upnpd): + tmp = option.replace('_', '-') + raise ConfigError(f'Every UPNP rule requires "{tmp}" to be set!') + + if 'stun' in upnpd: for option in ['host', 'port']: if option not in upnpd['stun']: raise ConfigError(f'A UPNP stun support must have an "{option}" option!') - + # Check the validity of the IP address listen_dev = [] system_addrs_cidr = get_all_interface_addr(True, [], [netifaces.AF_INET, netifaces.AF_INET6]) @@ -120,7 +121,7 @@ def verify(upnpd): raise ConfigError(f'The address "{listen_if_or_addr}" is an address that is not allowed to listen on. It is not an interface address nor a multicast address!') if is_ipv6(listen_if_or_addr) and IPv6Network(listen_if_or_addr).is_multicast: raise ConfigError(f'The address "{listen_if_or_addr}" is an address that is not allowed to listen on. It is not an interface address nor a multicast address!') - + system_listening_dev_addrs_cidr = get_all_interface_addr(True, listen_dev, [netifaces.AF_INET6]) system_listening_dev_addrs = get_all_interface_addr(False, listen_dev, [netifaces.AF_INET6]) for listen_if_or_addr in upnpd['listen']: @@ -130,19 +131,20 @@ def verify(upnpd): def generate(upnpd): if not upnpd: return None - + if os.path.isfile(config_file): os.unlink(config_file) - + render(config_file, 'firewall/upnpd.conf.tmpl', upnpd) def apply(upnpd): + systemd_service_name = 'miniupnpd.service' if not upnpd: # Stop the UPNP service - call('systemctl stop miniupnpd.service') + call(f'systemctl stop {systemd_service_name}') else: # Start the UPNP service - call('systemctl restart miniupnpd.service') + call(f'systemctl restart {systemd_service_name}') if __name__ == '__main__': try: -- cgit v1.2.3 From bf549b34e7daab7e843176bc1c8a8d03148f3840 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 1 Feb 2022 08:00:58 +0100 Subject: Revert "dhclient: T3392: remove /usr/sbin prefix from iproute2 ip command" This reverts commit 78b247b724f74bdabab0706aaa7f5b00e5809bc1. --- src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper | 16 ++++++++-------- src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper b/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper index 9d5505758..74a7e83bf 100644 --- a/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper +++ b/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper @@ -4,7 +4,7 @@ IF_METRIC=${IF_METRIC:-210} # Check if interface is inside a VRF -VRF_OPTION=$(ip -j -d link show ${interface} | awk '{if(match($0, /.*"master":"(\w+)".*"info_slave_kind":"vrf"/, IFACE_DETAILS)) printf("vrf %s", IFACE_DETAILS[1])}') +VRF_OPTION=$(/usr/sbin/ip -j -d link show ${interface} | awk '{if(match($0, /.*"master":"(\w+)".*"info_slave_kind":"vrf"/, IFACE_DETAILS)) printf("vrf %s", IFACE_DETAILS[1])}') # get status of FRR function frr_alive () { @@ -66,9 +66,9 @@ function iptovtysh () { # delete the same route from kernel before adding new one function delroute () { logmsg info "Checking if the route presented in kernel: $@ $VRF_OPTION" - if ip route show $@ $VRF_OPTION | grep -qx "$1 " ; then - logmsg info "Deleting IP route: \"ip route del $@ $VRF_OPTION\"" - ip route del $@ $VRF_OPTION + if /usr/sbin/ip route show $@ $VRF_OPTION | grep -qx "$1 " ; then + logmsg info "Deleting IP route: \"/usr/sbin/ip route del $@ $VRF_OPTION\"" + /usr/sbin/ip route del $@ $VRF_OPTION fi } @@ -76,8 +76,8 @@ function delroute () { function ip () { # pass comand to system `ip` if this is not related to routes change if [ "$2" != "route" ] ; then - logmsg info "Passing command to iproute2: \"$@\"" - ip $@ + logmsg info "Passing command to /usr/sbin/ip: \"$@\"" + /usr/sbin/ip $@ else # if we want to work with routes, try to use FRR first if frr_alive ; then @@ -87,8 +87,8 @@ function ip () { vtysh -c "conf t" -c "$VTYSH_CMD" else # add ip route to kernel - logmsg info "Modifying routes in kernel: \"ip $@\"" - ip $@ $VRF_OPTION + logmsg info "Modifying routes in kernel: \"/usr/sbin/ip $@\"" + /usr/sbin/ip $@ $VRF_OPTION fi fi } diff --git a/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup b/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup index a6989441b..ad6a1d5eb 100644 --- a/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup +++ b/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup @@ -1,7 +1,7 @@ ## ## VyOS cleanup ## -# NOTE: here we use 'ip' wrapper, therefore a route will be actually deleted via ip or vtysh, according to the system state +# NOTE: here we use 'ip' wrapper, therefore a route will be actually deleted via /usr/sbin/ip or vtysh, according to the system state hostsd_client="/usr/bin/vyos-hostsd-client" hostsd_changes= # check vyos-hostsd status -- cgit v1.2.3 From d331da9949060870f0543ac841e533d73e02c079 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Wed, 2 Feb 2022 17:58:42 +0000 Subject: monitoring: T3872: Fix template input plugin for running services Add required capability for input scripts which collect statistics of running services --- data/templates/monitoring/override.conf.tmpl | 2 +- data/templates/monitoring/telegraf.tmpl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/templates/monitoring/override.conf.tmpl b/data/templates/monitoring/override.conf.tmpl index 63f6d7391..f8f150791 100644 --- a/data/templates/monitoring/override.conf.tmpl +++ b/data/templates/monitoring/override.conf.tmpl @@ -3,5 +3,5 @@ After=vyos-router.service ConditionPathExists=/run/telegraf/vyos-telegraf.conf [Service] Environment=INFLUX_TOKEN={{ authentication.token }} -CapabilityBoundingSet=CAP_NET_RAW CAP_NET_ADMIN +CapabilityBoundingSet=CAP_NET_RAW CAP_NET_ADMIN CAP_SYS_ADMIN AmbientCapabilities=CAP_NET_RAW CAP_NET_ADMIN diff --git a/data/templates/monitoring/telegraf.tmpl b/data/templates/monitoring/telegraf.tmpl index f05396d91..465b58c80 100644 --- a/data/templates/monitoring/telegraf.tmpl +++ b/data/templates/monitoring/telegraf.tmpl @@ -53,7 +53,7 @@ [[inputs.exec]] commands = [ "{{ custom_scripts_dir }}/show_interfaces_input_filter.py", - "cat /tmp/vyos_services_input_filter" + "{{ custom_scripts_dir }}/vyos_services_input_filter.py" ] timeout = "10s" data_format = "influx" -- cgit v1.2.3 From 9f7f1ebb15a2dce507693830517bc1c0c2b6815e Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Thu, 3 Feb 2022 00:30:52 +0100 Subject: firewall: T4178: Fix only inverse matching on tcp flags --- python/vyos/firewall.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py index a74fd922a..c1217b420 100644 --- a/python/vyos/firewall.py +++ b/python/vyos/firewall.py @@ -208,7 +208,7 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name): def parse_tcp_flags(flags): include = [flag for flag in flags if flag != 'not'] exclude = list(flags['not']) if 'not' in flags else [] - return f'tcp flags & ({"|".join(include + exclude)}) == {"|".join(include)}' + return f'tcp flags & ({"|".join(include + exclude)}) == {"|".join(include) if include else "0x0"}' def parse_time(time): out = [] -- cgit v1.2.3 From b10baca3c8663e7e56eb9abfb3c03ce576c34f1f Mon Sep 17 00:00:00 2001 From: srividya0208 Date: Thu, 3 Feb 2022 12:05:53 -0500 Subject: T4227:Bridge: Typo in completion help of hello-time option There is spelling mistake in "advertisement" of hello-time option's completion help --- interface-definitions/interfaces-bridge.xml.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface-definitions/interfaces-bridge.xml.in b/interface-definitions/interfaces-bridge.xml.in index 0856615be..89a6d2303 100644 --- a/interface-definitions/interfaces-bridge.xml.in +++ b/interface-definitions/interfaces-bridge.xml.in @@ -59,7 +59,7 @@ - Hello packet advertisment interval + Hello packet advertisement interval u32:1-10 Spanning Tree Protocol hello advertisement interval in seconds (default 2) -- cgit v1.2.3 From 22f0794a9f195e69e277d48f031fe934febe9408 Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Thu, 27 Jan 2022 16:58:36 +0100 Subject: firewall: T4209: Fix support for rule `recent` matches --- data/templates/firewall/nftables.tmpl | 22 ++++++++++++++++++++++ .../include/firewall/common-rule.xml.i | 19 +++++++++++++++---- python/vyos/firewall.py | 4 +--- src/conf_mode/firewall.py | 6 +++++- src/migration-scripts/firewall/6-to-7 | 20 ++++++++++++++++++++ 5 files changed, 63 insertions(+), 8 deletions(-) diff --git a/data/templates/firewall/nftables.tmpl b/data/templates/firewall/nftables.tmpl index 468a5a32f..0cc977cf9 100644 --- a/data/templates/firewall/nftables.tmpl +++ b/data/templates/firewall/nftables.tmpl @@ -31,16 +31,27 @@ table ip filter { } {% endif %} {% if name is defined %} +{% set ns = namespace(sets=[]) %} {% for name_text, conf in name.items() %} chain NAME_{{ name_text }} { {% if conf.rule is defined %} {% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not defined %} {{ rule_conf | nft_rule(name_text, rule_id) }} +{% if rule_conf.recent is defined %} +{% set ns.sets = ns.sets + [name_text + '_' + rule_id] %} +{% endif %} {% endfor %} {% endif %} {{ conf | nft_default_rule(name_text) }} } {% endfor %} +{% for set_name in ns.sets %} + set RECENT_{{ set_name }} { + type ipv4_addr + size 65535 + flags dynamic + } +{% endfor %} {% endif %} {% if state_policy is defined %} chain VYOS_STATE_POLICY { @@ -81,16 +92,27 @@ table ip6 filter { } {% endif %} {% if ipv6_name is defined %} +{% set ns = namespace(sets=[]) %} {% for name_text, conf in ipv6_name.items() %} chain NAME6_{{ name_text }} { {% if conf.rule is defined %} {% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not defined %} {{ rule_conf | nft_rule(name_text, rule_id, 'ip6') }} +{% if rule_conf.recent is defined %} +{% set ns.sets = ns.sets + [name_text + '_' + rule_id] %} +{% endif %} {% endfor %} {% endif %} {{ conf | nft_default_rule(name_text) }} } {% endfor %} +{% for set_name in ns.sets %} + set RECENT6_{{ set_name }} { + type ipv6_addr + size 65535 + flags dynamic + } +{% endfor %} {% endif %} {% if state_policy is defined %} chain VYOS_STATE_POLICY6 { diff --git a/interface-definitions/include/firewall/common-rule.xml.i b/interface-definitions/include/firewall/common-rule.xml.i index 521fe54f2..353804990 100644 --- a/interface-definitions/include/firewall/common-rule.xml.i +++ b/interface-definitions/include/firewall/common-rule.xml.i @@ -146,13 +146,24 @@ - Source addresses seen in the last N seconds + Source addresses seen in the last second/minute/hour + + second minute hour + - u32:0-4294967295 - Source addresses seen in the last N seconds + second + Source addresses seen COUNT times in the last second + + + minute + Source addresses seen COUNT times in the last minute + + + hour + Source addresses seen COUNT times in the last hour - + ^(second|minute|hour)$ diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py index c1217b420..55ce318e7 100644 --- a/python/vyos/firewall.py +++ b/python/vyos/firewall.py @@ -181,9 +181,7 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name): if 'recent' in rule_conf: count = rule_conf['recent']['count'] time = rule_conf['recent']['time'] - # output.append(f'meter {fw_name}_{rule_id} {{ ip saddr and 255.255.255.255 limit rate over {count}/{time} burst {count} packets }}') - # Waiting on input from nftables developers due to - # bug with above line and atomic chain flushing. + output.append(f'add @RECENT{def_suffix}_{fw_name}_{rule_id} {{ {ip_name} saddr limit rate over {count}/{time} burst {count} packets }}') if 'time' in rule_conf: output.append(parse_time(rule_conf['time'])) diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index 9dec2143e..41df1b84a 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -278,6 +278,7 @@ def cleanup_rule(table, jump_chain): def cleanup_commands(firewall): commands = [] + commands_end = [] for table in ['ip filter', 'ip6 filter']: state_chain = 'VYOS_STATE_POLICY' if table == 'ip filter' else 'VYOS_STATE_POLICY6' json_str = cmd(f'nft -j list table {table}') @@ -308,7 +309,10 @@ def cleanup_commands(firewall): chain = rule['chain'] handle = rule['handle'] commands.append(f'delete rule {table} {chain} handle {handle}') - return commands + elif 'set' in item: + set_name = item['set']['name'] + commands_end.append(f'delete set {table} {set_name}') + return commands + commands_end def generate(firewall): if not os.path.exists(nftables_conf): diff --git a/src/migration-scripts/firewall/6-to-7 b/src/migration-scripts/firewall/6-to-7 index efc901530..5f4cff90d 100755 --- a/src/migration-scripts/firewall/6-to-7 +++ b/src/migration-scripts/firewall/6-to-7 @@ -104,6 +104,7 @@ if config.exists(base + ['name']): continue for rule in config.list_nodes(base + ['name', name, 'rule']): + rule_recent = base + ['name', name, 'rule', rule, 'recent'] rule_time = base + ['name', name, 'rule', rule, 'time'] rule_tcp_flags = base + ['name', name, 'rule', rule, 'tcp', 'flags'] rule_icmp = base + ['name', name, 'rule', rule, 'icmp'] @@ -114,6 +115,15 @@ if config.exists(base + ['name']): if config.exists(rule_time + ['utc']): config.delete(rule_time + ['utc']) + if config.exists(rule_recent + ['time']): + tmp = int(config.return_value(rule_recent + ['time'])) + unit = 'minute' + if tmp > 600: + unit = 'hour' + elif tmp < 10: + unit = 'second' + config.set(rule_recent + ['time'], value=unit) + if config.exists(rule_tcp_flags): tmp = config.return_value(rule_tcp_flags) config.delete(rule_tcp_flags) @@ -148,6 +158,7 @@ if config.exists(base + ['ipv6-name']): continue for rule in config.list_nodes(base + ['ipv6-name', name, 'rule']): + rule_recent = base + ['ipv6-name', name, 'rule', rule, 'recent'] rule_time = base + ['ipv6-name', name, 'rule', rule, 'time'] rule_tcp_flags = base + ['ipv6-name', name, 'rule', rule, 'tcp', 'flags'] rule_icmp = base + ['ipv6-name', name, 'rule', rule, 'icmpv6'] @@ -158,6 +169,15 @@ if config.exists(base + ['ipv6-name']): if config.exists(rule_time + ['utc']): config.delete(rule_time + ['utc']) + if config.exists(rule_recent + ['time']): + tmp = int(config.return_value(rule_recent + ['time'])) + unit = 'minute' + if tmp > 600: + unit = 'hour' + elif tmp < 10: + unit = 'second' + config.set(rule_recent + ['time'], value=unit) + if config.exists(rule_tcp_flags): tmp = config.return_value(rule_tcp_flags) config.delete(rule_tcp_flags) -- cgit v1.2.3 From 5444eeda0fab496da5bef7b233c443ba79b100ee Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Fri, 4 Feb 2022 08:45:18 +0000 Subject: policy: T4151: Delete unexpected print added in commit c501ae0f --- src/conf_mode/policy-local-route.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/conf_mode/policy-local-route.py b/src/conf_mode/policy-local-route.py index 0990039c1..3f834f55c 100755 --- a/src/conf_mode/policy-local-route.py +++ b/src/conf_mode/policy-local-route.py @@ -162,8 +162,6 @@ def apply(pbr): if not pbr: return None - print(pbr) - # Delete old rule if needed for rule_rm in ['rule_remove', 'rule6_remove']: if rule_rm in pbr: -- cgit v1.2.3 From 11a900e706db59459314622050ced7d4117f090b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 5 Feb 2022 20:13:04 +0100 Subject: vrrp: T4226: transition-script does not work for groups containing a hypen (-) --- src/system/keepalived-fifo.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/system/keepalived-fifo.py b/src/system/keepalived-fifo.py index b1fe7e43f..a8df232ae 100755 --- a/src/system/keepalived-fifo.py +++ b/src/system/keepalived-fifo.py @@ -71,7 +71,8 @@ class KeepalivedFifo: # Read VRRP configuration directly from CLI self.vrrp_config_dict = conf.get_config_dict(base, - key_mangling=('-', '_'), get_first_key=True) + key_mangling=('-', '_'), get_first_key=True, + no_tag_node_value_mangle=True) logger.debug(f'Loaded configuration: {self.vrrp_config_dict}') except Exception as err: -- cgit v1.2.3 From 5e7e96380b314587bbd8bd584848d39caef86f3f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 6 Feb 2022 13:54:15 +0100 Subject: config: T4228: is_member() must return all instances not only the last one --- python/vyos/configdict.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index d974a7565..e7f515ea9 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -196,7 +196,7 @@ def is_member(conf, interface, intftype=None): interface name -> Interface is a member of this interface False -> interface type cannot have members """ - ret_val = None + ret_val = {} intftypes = ['bonding', 'bridge'] if intftype not in intftypes + [None]: @@ -216,8 +216,8 @@ def is_member(conf, interface, intftype=None): member = base + [intf, 'member', 'interface', interface] if conf.exists(member): tmp = conf.get_config_dict(member, key_mangling=('-', '_'), - get_first_key=True) - ret_val = {intf : tmp} + get_first_key=True, no_tag_node_value_mangle=True) + ret_val.update({intf : tmp}) old_level = conf.set_level(old_level) return ret_val -- cgit v1.2.3 From b4185f8356d69476292906ebe32daf1c4867601a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 6 Feb 2022 20:56:37 +0100 Subject: smoketest: bond: T4228: verify bond member is only used once --- smoketest/scripts/cli/test_interfaces_bonding.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/smoketest/scripts/cli/test_interfaces_bonding.py b/smoketest/scripts/cli/test_interfaces_bonding.py index 86000553e..1d9a887bd 100755 --- a/smoketest/scripts/cli/test_interfaces_bonding.py +++ b/smoketest/scripts/cli/test_interfaces_bonding.py @@ -36,7 +36,6 @@ class BondingInterfaceTest(BasicInterfaceTest.TestCase): cls._test_vlan = True cls._test_qinq = True cls._base_path = ['interfaces', 'bonding'] - cls._interfaces = ['bond0'] cls._mirror_interfaces = ['dum21354'] cls._members = [] @@ -52,6 +51,7 @@ class BondingInterfaceTest(BasicInterfaceTest.TestCase): cls._options['bond0'] = [] for member in cls._members: cls._options['bond0'].append(f'member interface {member}') + cls._interfaces = list(cls._options) # call base-classes classmethod super(cls, cls).setUpClass() @@ -150,5 +150,19 @@ class BondingInterfaceTest(BasicInterfaceTest.TestCase): defined_policy = read_file(f'/sys/class/net/{interface}/bonding/xmit_hash_policy').split() self.assertEqual(defined_policy[0], hash_policy) + def test_bonding_multi_use_member(self): + # Define available bonding hash policies + for interface in ['bond10', 'bond20']: + for member in self._members: + self.cli_set(self._base_path + [interface, 'member', 'interface', member]) + + # check validate() - can not use the same member interfaces multiple times + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_delete(self._base_path + ['bond20']) + + self.cli_commit() + if __name__ == '__main__': unittest.main(verbosity=2) -- cgit v1.2.3 From 20090e7df2cc74763e5917c3b97b2262fbd909fc Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Mon, 7 Feb 2022 11:40:22 +0000 Subject: dhcp: T3600: Fix DHCP static table dhcp-interface route Static table dhcp-interface route required table in template Without table this route will be placed to table 'main' by default --- data/templates/frr/static_routes_macro.j2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/frr/static_routes_macro.j2 b/data/templates/frr/static_routes_macro.j2 index 3b432b49b..86c7470ca 100644 --- a/data/templates/frr/static_routes_macro.j2 +++ b/data/templates/frr/static_routes_macro.j2 @@ -5,7 +5,7 @@ {% if prefix_config.dhcp_interface is defined and prefix_config.dhcp_interface is not none %} {% set next_hop = prefix_config.dhcp_interface | get_dhcp_router %} {% if next_hop is defined and next_hop is not none %} -{{ ip_ipv6 }} route {{ prefix }} {{ next_hop }} {{ prefix_config.dhcp_interface }} +{{ ip_ipv6 }} route {{ prefix }} {{ next_hop }} {{ prefix_config.dhcp_interface }} {{ 'table ' + table if table is defined and table is not none }} {% endif %} {% endif %} {% if prefix_config.interface is defined and prefix_config.interface is not none %} -- cgit v1.2.3 From d96bab4e6da517f07133667834cd6f8bcfb5160f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 7 Feb 2022 22:27:51 +0100 Subject: xml: ssh: T4233: sync regex for allow/deny usernames to "system login" --- interface-definitions/include/ssh-user.xml.i | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface-definitions/include/ssh-user.xml.i b/interface-definitions/include/ssh-user.xml.i index 677602dd8..17ba05a90 100644 --- a/interface-definitions/include/ssh-user.xml.i +++ b/interface-definitions/include/ssh-user.xml.i @@ -3,9 +3,9 @@ Allow specific users to login - [a-z_][a-z0-9_-]{1,31}[$]? + ^[-_a-zA-Z0-9.]{1,100} - illegal characters or more than 32 characters + Illegal characters or more than 100 characters -- cgit v1.2.3 From 4ddfe9b7e72e4f1e1fc8e70c5239bf09644e6d9b Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Tue, 8 Feb 2022 08:38:12 +0000 Subject: monitoring: T3872: Add input filter for firewall InfluxDB2 Input filter for firewall allows to get bytes/counters from nftables in format, required for InfluxDB2 --- data/templates/monitoring/telegraf.tmpl | 3 +- .../custom_scripts/show_firewall_input_filter.py | 73 ++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) create mode 100755 src/etc/telegraf/custom_scripts/show_firewall_input_filter.py diff --git a/data/templates/monitoring/telegraf.tmpl b/data/templates/monitoring/telegraf.tmpl index 465b58c80..d3145a500 100644 --- a/data/templates/monitoring/telegraf.tmpl +++ b/data/templates/monitoring/telegraf.tmpl @@ -17,7 +17,7 @@ [[outputs.influxdb_v2]] urls = ["{{ url }}:{{ port }}"] insecure_skip_verify = true - token = "{{ authentication.token }}" + token = "$INFLUX_TOKEN" organization = "{{ authentication.organization }}" bucket = "{{ bucket }}" [[inputs.cpu]] @@ -52,6 +52,7 @@ syslog_standard = "RFC3164" [[inputs.exec]] commands = [ + "{{ custom_scripts_dir }}/show_firewall_input_filter.py", "{{ custom_scripts_dir }}/show_interfaces_input_filter.py", "{{ custom_scripts_dir }}/vyos_services_input_filter.py" ] diff --git a/src/etc/telegraf/custom_scripts/show_firewall_input_filter.py b/src/etc/telegraf/custom_scripts/show_firewall_input_filter.py new file mode 100755 index 000000000..bf4bfd05d --- /dev/null +++ b/src/etc/telegraf/custom_scripts/show_firewall_input_filter.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 + +import json +import re +import time + +from vyos.util import cmd + + +def get_nft_filter_chains(): + """ + Get list of nft chains for table filter + """ + nft = cmd('/usr/sbin/nft --json list table ip filter') + nft = json.loads(nft) + chain_list = [] + + for output in nft['nftables']: + if 'chain' in output: + chain = output['chain']['name'] + chain_list.append(chain) + + return chain_list + + +def get_nftables_details(name): + """ + Get dict, counters packets and bytes for chain + """ + command = f'/usr/sbin/nft list chain ip filter {name}' + try: + results = cmd(command) + except: + return {} + + # Trick to remove 'NAME_' from chain name in the comment + # It was added to any chain T4218 + # counter packets 0 bytes 0 return comment "FOO default-action accept" + comment_name = name.replace("NAME_", "") + out = {} + for line in results.split('\n'): + comment_search = re.search(rf'{comment_name}[\- ](\d+|default-action)', line) + if not comment_search: + continue + + rule = {} + rule_id = comment_search[1] + counter_search = re.search(r'counter packets (\d+) bytes (\d+)', line) + if counter_search: + rule['packets'] = counter_search[1] + rule['bytes'] = counter_search[2] + + rule['conditions'] = re.sub(r'(\b(counter packets \d+ bytes \d+|drop|reject|return|log)\b|comment "[\w\-]+")', '', line).strip() + out[rule_id] = rule + return out + + +def get_nft_telegraf(name): + """ + Get data for telegraf in influxDB format + """ + for rule, rule_config in get_nftables_details(name).items(): + print(f'nftables,table=filter,chain={name},' + f'ruleid={rule} ' + f'pkts={rule_config["packets"]}i,' + f'bytes={rule_config["bytes"]}i ' + f'{str(int(time.time()))}000000000') + + +chains = get_nft_filter_chains() + +for chain in chains: + get_nft_telegraf(chain) -- cgit v1.2.3 From 2ee94418ce24429dbf6a52c2a327ed08a1935958 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Tue, 8 Feb 2022 21:13:46 -0600 Subject: configtree: T4235: encapsulate config tree diff function --- python/vyos/configtree.py | 65 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 13 deletions(-) diff --git a/python/vyos/configtree.py b/python/vyos/configtree.py index d8ffaca99..866f24e47 100644 --- a/python/vyos/configtree.py +++ b/python/vyos/configtree.py @@ -15,8 +15,9 @@ import re import json -from ctypes import cdll, c_char_p, c_void_p, c_int +from ctypes import cdll, c_char_p, c_void_p, c_int, POINTER +LIBPATH = '/usr/lib/libvyosconfig.so.0' def escape_backslash(string: str) -> str: """Escape single backslashes in string that are not in escape sequence""" @@ -42,7 +43,9 @@ class ConfigTreeError(Exception): class ConfigTree(object): - def __init__(self, config_string, libpath='/usr/lib/libvyosconfig.so.0'): + def __init__(self, config_string=None, address=None, libpath=LIBPATH): + if config_string is None and address is None: + raise TypeError("ConfigTree() requires one of 'config_string' or 'address'") self.__config = None self.__lib = cdll.LoadLibrary(libpath) @@ -60,7 +63,7 @@ class ConfigTree(object): self.__to_string.restype = c_char_p self.__to_commands = self.__lib.to_commands - self.__to_commands.argtypes = [c_void_p] + self.__to_commands.argtypes = [c_void_p, c_char_p] self.__to_commands.restype = c_char_p self.__to_json = self.__lib.to_json @@ -126,15 +129,19 @@ class ConfigTree(object): self.__destroy = self.__lib.destroy self.__destroy.argtypes = [c_void_p] - config_section, version_section = extract_version(config_string) - config_section = escape_backslash(config_section) - config = self.__from_string(config_section.encode()) - if config is None: - msg = self.__get_error().decode() - raise ValueError("Failed to parse config: {0}".format(msg)) + if address is None: + config_section, version_section = extract_version(config_string) + config_section = escape_backslash(config_section) + config = self.__from_string(config_section.encode()) + if config is None: + msg = self.__get_error().decode() + raise ValueError("Failed to parse config: {0}".format(msg)) + else: + self.__config = config + self.__version = version_section else: - self.__config = config - self.__version = version_section + self.__config = address + self.__version = '' def __del__(self): if self.__config is not None: @@ -143,13 +150,16 @@ class ConfigTree(object): def __str__(self): return self.to_string() + def _get_config(self): + return self.__config + def to_string(self): config_string = self.__to_string(self.__config).decode() config_string = "{0}\n{1}".format(config_string, self.__version) return config_string - def to_commands(self): - return self.__to_commands(self.__config).decode() + def to_commands(self, op="set"): + return self.__to_commands(self.__config, op.encode()).decode() def to_json(self): return self.__to_json(self.__config).decode() @@ -281,3 +291,32 @@ class ConfigTree(object): else: raise ConfigTreeError("Path [{}] doesn't exist".format(path_str)) +class Diff: + def __init__(self, left, right, path=[], libpath=LIBPATH): + if not (isinstance(left, ConfigTree) and isinstance(right, ConfigTree)): + raise TypeError("Arguments must be instances of ConfigTree") + if path: + if not left.exists(path): + raise ConfigTreeError(f"Path {path} doesn't exist in lhs tree") + if not right.exists(path): + raise ConfigTreeError(f"Path {path} doesn't exist in rhs tree") + self.left = left + self.right = right + + check_path(path) + path_str = " ".join(map(str, path)).encode() + df = cdll.LoadLibrary(libpath).diffs + df.restype = POINTER(c_void_p * 3) + res = list(df(path_str, left._get_config(), right._get_config()).contents) + self._diff = {'add': ConfigTree(address=res[0]), + 'del': ConfigTree(address=res[1]), + 'int': ConfigTree(address=res[2]) } + + self.add = self._diff['add'] + self.delete = self._diff['del'] + self.inter = self._diff['int'] + + def to_commands(self): + add = self.add.to_commands() + delete = self.delete.to_commands(op="delete") + return delete + "\n" + add -- cgit v1.2.3 From 4ecfd5d87c33aea770878a012f3b4956deafd762 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Tue, 8 Feb 2022 10:58:20 +0000 Subject: openvpn: T4230: Delete checks if local-host address assigned OpenVPN can't start if it depends on VRRP virtual-address as virtual-address is not yet assigned by HA (openvpn and ha in one commit) as we have checks "if address assigned" It depends on commit priorities: 460 interfaces/openvpn 800 high-availability Replace check if local-host address assigned from raise ConfigError to print (just notification) Allow to bind OpenVPN service to nonlocal address --- src/conf_mode/interfaces-openvpn.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 3b8fae710..0f6114b4a 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -47,6 +47,7 @@ from vyos.template import is_ipv4 from vyos.template import is_ipv6 from vyos.util import call from vyos.util import chown +from vyos.util import cmd from vyos.util import dict_search from vyos.util import dict_search_args from vyos.util import makedir @@ -423,8 +424,8 @@ def verify(openvpn): # verify specified IP address is present on any interface on this system if 'local_host' in openvpn: if not is_addr_assigned(openvpn['local_host']): - raise ConfigError('local-host IP address "{local_host}" not assigned' \ - ' to any interface'.format(**openvpn)) + print('local-host IP address "{local_host}" not assigned' \ + ' to any interface'.format(**openvpn)) # TCP active if openvpn['protocol'] == 'tcp-active': @@ -647,6 +648,13 @@ def apply(openvpn): return None + # verify specified IP address is present on any interface on this system + # Allow to bind service to nonlocal address, if it virtaual-vrrp address + # or if address will be assign later + if 'local_host' in openvpn: + if not is_addr_assigned(openvpn['local_host']): + cmd('sysctl -w net.ipv4.ip_nonlocal_bind=1') + # No matching OpenVPN process running - maybe it got killed or none # existed - nevertheless, spawn new OpenVPN process call(f'systemctl reload-or-restart openvpn@{interface}.service') -- cgit v1.2.3 From 230ac0a202acd7ae9ad9bccb9e777ee5a0e0b7b7 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Wed, 9 Feb 2022 16:07:55 +0000 Subject: openvpn: T3686: Fix for check local-address in script and tmpl Local-address should be checked/executed only if it exists in the openvpn configuration, dictionary, jinja2 template --- data/templates/openvpn/server.conf.tmpl | 10 ++++++---- src/conf_mode/interfaces-openvpn.py | 13 +++++++------ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/data/templates/openvpn/server.conf.tmpl b/data/templates/openvpn/server.conf.tmpl index 7a0470d0e..fb7ad9e16 100644 --- a/data/templates/openvpn/server.conf.tmpl +++ b/data/templates/openvpn/server.conf.tmpl @@ -141,11 +141,13 @@ ping {{ keep_alive.interval }} ping-restart {{ keep_alive.failure_count }} {% if device_type == 'tap' %} -{% for laddr, laddr_conf in local_address.items() if laddr | is_ipv4 %} -{% if laddr_conf is defined and laddr_conf.subnet_mask is defined and laddr_conf.subnet_mask is not none %} +{% if local_address is defined and local_address is not none %} +{% for laddr, laddr_conf in local_address.items() if laddr | is_ipv4 %} +{% if laddr_conf is defined and laddr_conf.subnet_mask is defined and laddr_conf.subnet_mask is not none %} ifconfig {{ laddr }} {{ laddr_conf.subnet_mask }} -{% endif %} -{% endfor %} +{% endif %} +{% endfor %} +{% endif %} {% else %} {% for laddr in local_address if laddr | is_ipv4 %} {% for raddr in remote_address if raddr | is_ipv4 %} diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 3b8fae710..242fae9fb 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2021 VyOS maintainers and contributors +# Copyright (C) 2019-2022 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 @@ -225,11 +225,12 @@ def verify(openvpn): if 'local_address' not in openvpn and 'is_bridge_member' not in openvpn: raise ConfigError('Must specify "local-address" or add interface to bridge') - if len([addr for addr in openvpn['local_address'] if is_ipv4(addr)]) > 1: - raise ConfigError('Only one IPv4 local-address can be specified') + if 'local_address' in openvpn: + if len([addr for addr in openvpn['local_address'] if is_ipv4(addr)]) > 1: + raise ConfigError('Only one IPv4 local-address can be specified') - if len([addr for addr in openvpn['local_address'] if is_ipv6(addr)]) > 1: - raise ConfigError('Only one IPv6 local-address can be specified') + if len([addr for addr in openvpn['local_address'] if is_ipv6(addr)]) > 1: + raise ConfigError('Only one IPv6 local-address can be specified') if openvpn['device_type'] == 'tun': if 'remote_address' not in openvpn: @@ -268,7 +269,7 @@ def verify(openvpn): if dict_search('remote_host', openvpn) in dict_search('remote_address', openvpn): raise ConfigError('"remote-address" and "remote-host" can not be the same') - if openvpn['device_type'] == 'tap': + if openvpn['device_type'] == 'tap' and 'local_address' in openvpn: # we can only have one local_address, this is ensured above v4addr = None for laddr in openvpn['local_address']: -- cgit v1.2.3 From 19f65290529ac642da419ac77003ddaa70d0cc67 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Thu, 10 Feb 2022 11:13:33 +0000 Subject: smoketest: T3872: Fix token check for monitoring test As INFLUX_TOKEN is present in override.conf.tmpl environment we expect variable "$INFLUX_TOKEN" in the telegraf template and config but not value of the token --- smoketest/scripts/cli/test_service_monitoring_telegraf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smoketest/scripts/cli/test_service_monitoring_telegraf.py b/smoketest/scripts/cli/test_service_monitoring_telegraf.py index b857926e2..09937513e 100755 --- a/smoketest/scripts/cli/test_service_monitoring_telegraf.py +++ b/smoketest/scripts/cli/test_service_monitoring_telegraf.py @@ -54,7 +54,7 @@ class TestMonitoringTelegraf(VyOSUnitTestSHIM.TestCase): # Check telegraf config self.assertIn(f'organization = "{org}"', config) - self.assertIn(token, config) + self.assertIn(f' token = "$INFLUX_TOKEN"', config) self.assertIn(f'urls = ["{url}:{port}"]', config) self.assertIn(f'bucket = "{bucket}"', config) -- cgit v1.2.3 From 7f7be911b749b6c65ac3c6e57192e9e4ce2dcd24 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Thu, 10 Feb 2022 22:05:19 +0000 Subject: openvpn: T4236: Add generator for ovpn configurations in op-mode This generator generates client .ovpn files with required initial configuration It gets information from interface vtun, pki ca and certificates --- .../generate-openvpn-config-client.xml.in | 58 +++++++++ src/op_mode/generate_ovpn_client_file.py | 145 +++++++++++++++++++++ 2 files changed, 203 insertions(+) create mode 100644 op-mode-definitions/generate-openvpn-config-client.xml.in create mode 100755 src/op_mode/generate_ovpn_client_file.py diff --git a/op-mode-definitions/generate-openvpn-config-client.xml.in b/op-mode-definitions/generate-openvpn-config-client.xml.in new file mode 100644 index 000000000..4f9f31bfe --- /dev/null +++ b/op-mode-definitions/generate-openvpn-config-client.xml.in @@ -0,0 +1,58 @@ + + + + + + + Generate OpenVPN client configuration ovpn file + + + + + Generate Client config + + + + + Local interface used for connection + + + + + + + + CA certificate + + pki ca + + + + + + Cerificate used by client + + pki certificate + + + + + + Certificate key used by client + + sudo ${vyos_op_scripts_dir}/generate_ovpn_client_file.py --interface "$5" --ca "$7" --cert "$9" --key "${11}" + + + sudo ${vyos_op_scripts_dir}/generate_ovpn_client_file.py --interface "$5" --ca "$7" --cert "$9" + + + + + + + + + + + + diff --git a/src/op_mode/generate_ovpn_client_file.py b/src/op_mode/generate_ovpn_client_file.py new file mode 100755 index 000000000..29db41e37 --- /dev/null +++ b/src/op_mode/generate_ovpn_client_file.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 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 . + +import argparse +import os + +from jinja2 import Template + +from vyos.configquery import ConfigTreeQuery +from vyos.ifconfig import Section +from vyos.util import cmd + + +client_config = """ + +client +nobind +remote {{ remote_host }} {{ port }} +remote-cert-tls server +proto {{ 'tcp-client' if protocol == 'tcp-active' else 'udp' }} +dev {{ device }} +dev-type {{ device }} +persist-key +persist-tun +verb 3 + +# Encryption options +{% if encryption is defined and encryption is not none %} +{% if encryption.cipher is defined and encryption.cipher is not none %} +cipher {{ encryption.cipher }} +{% if encryption.cipher == 'bf128' %} +keysize 128 +{% elif encryption.cipher == 'bf256' %} +keysize 256 +{% endif %} +{% endif %} +{% if encryption.ncp_ciphers is defined and encryption.ncp_ciphers is not none %} +data-ciphers {{ encryption.ncp_ciphers }} +{% endif %} +{% endif %} + +{% if hash is defined and hash is not none %} +auth {{ hash }} +{% endif %} +keysize 256 +comp-lzo {{ '' if use_lzo_compression is defined else 'no' }} + + +-----BEGIN CERTIFICATE----- +{{ ca }} +-----END CERTIFICATE----- + + + + +-----BEGIN CERTIFICATE----- +{{ cert }} +-----END CERTIFICATE----- + + + + +-----BEGIN PRIVATE KEY----- +{{ key }} +-----END PRIVATE KEY----- + + + +""" + +config = ConfigTreeQuery() +base = ['interfaces', 'openvpn'] + +if not config.exists(base): + print('OpenVPN not configured') + exit(0) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument("-i", "--interface", type=str, help='OpenVPN interface the client is connecting to', required=True) + parser.add_argument("-a", "--ca", type=str, help='OpenVPN CA cerificate', required=True) + parser.add_argument("-c", "--cert", type=str, help='OpenVPN client cerificate', required=True) + parser.add_argument("-k", "--key", type=str, help='OpenVPN client cerificate key', action="store") + args = parser.parse_args() + + interface = args.interface + ca = args.ca + cert = args.cert + key = args.key + if not key: + key = args.cert + + if interface not in Section.interfaces('openvpn'): + exit(f'OpenVPN interface "{interface}" does not exist!') + + if not config.exists(['pki', 'ca', ca, 'certificate']): + exit(f'OpenVPN CA certificate "{ca}" does not exist!') + + if not config.exists(['pki', 'certificate', cert, 'certificate']): + exit(f'OpenVPN certificate "{cert}" does not exist!') + + if not config.exists(['pki', 'certificate', cert, 'private', 'key']): + exit(f'OpenVPN certificate key "{key}" does not exist!') + + ca = config.value(['pki', 'ca', ca, 'certificate']) + cert = config.value(['pki', 'certificate', cert, 'certificate']) + key = config.value(['pki', 'certificate', key, 'private', 'key']) + remote_host = config.value(base + [interface, 'local-host']) + + ovpn_conf = config.get_config_dict(base + [interface], key_mangling=('-', '_'), get_first_key=True) + + port = '1194' if 'local_port' not in ovpn_conf else ovpn_conf['local_port'] + proto = 'udp' if 'protocol' not in ovpn_conf else ovpn_conf['protocol'] + device = 'tun' if 'device_type' not in ovpn_conf else ovpn_conf['device_type'] + + config = { + 'interface' : interface, + 'ca' : ca, + 'cert' : cert, + 'key' : key, + 'device' : device, + 'port' : port, + 'proto' : proto, + 'remote_host' : remote_host, + 'address' : [], + } + +# Clear out terminal first +print('\x1b[2J\x1b[H') +client = Template(client_config, trim_blocks=True).render(config) +print(client) -- cgit v1.2.3 From 27daf4a6cd4928be41ed08330ccc1b7f04ad2638 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 12 Feb 2022 08:44:43 +0100 Subject: policy: T2199: bugfix verify_rule() on negated groups Related to #1215 --- src/conf_mode/policy-route.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/conf_mode/policy-route.py b/src/conf_mode/policy-route.py index 82f668acf..3d1d7d8c5 100755 --- a/src/conf_mode/policy-route.py +++ b/src/conf_mode/policy-route.py @@ -123,6 +123,10 @@ def verify_rule(policy, name, rule_conf, ipv6): for group in valid_groups: if group in side_conf['group']: group_name = side_conf['group'][group] + + if group_name.startswith('!'): + group_name = group_name[1:] + fw_group = f'ipv6_{group}' if ipv6 and group in ['address_group', 'network_group'] else group error_group = fw_group.replace("_", "-") group_obj = dict_search_args(policy['firewall_group'], fw_group, group_name) -- cgit v1.2.3 From b40315b3c5051888f499961e63410e14c5d1bad7 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 13 Feb 2022 20:04:33 +0100 Subject: vyos.util: T4191: add new sysctl() helper function --- python/vyos/util.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/python/vyos/util.py b/python/vyos/util.py index 571d43754..1767ff9d3 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -997,3 +997,12 @@ def boot_configuration_complete() -> bool: if os.path.isfile(config_status): return True return False + +def sysctl(name, value): + """ Change value via sysctl() - return True if changed, False otherwise """ + tmp = cmd(f'sysctl {name}') + # last list index contains the actual value - only write if value differs + if tmp.split()[-1] != str(value): + call(f'sysctl -wq {name}={value}') + return True + return False -- cgit v1.2.3 From 2cec431e5caf9df85640f707cd6dc3077c17c238 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 13 Feb 2022 20:29:25 +0100 Subject: vrf: T4191: bugfix for "ip rule" when VRFs are created We always mangled and worked on the "ip rule" singleton even when nothing needed to be changed. This resulted in a VRF hickup when the same VRF was added and removed multiple times. set interfaces ethernet eth1 vrf foo set vrf name foo table '1000' commit delete interfaces ethernet eth1 vrf delete vrf commit set interfaces ethernet eth1 vrf foo set vrf name foo table '1000' commit broke reachability on eth1 - a reboot was required. This change will now only alter the ip rule tables once when VRF instances are created for the first time and will not touch the Kernel "ip rule" representation afterwards. --- src/conf_mode/vrf.py | 108 ++++++++++++++++++++++++++------------------------- 1 file changed, 56 insertions(+), 52 deletions(-) diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py index 38c0c4463..cfe0f4d8e 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2021 VyOS maintainers and contributors +# Copyright (C) 2020-2022 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 @@ -29,6 +29,7 @@ from vyos.util import dict_search from vyos.util import get_interface_config from vyos.util import popen from vyos.util import run +from vyos.util import sysctl from vyos import ConfigError from vyos import frr from vyos import airbag @@ -37,10 +38,16 @@ airbag.enable() config_file = '/etc/iproute2/rt_tables.d/vyos-vrf.conf' nft_vrf_config = '/tmp/nftables-vrf-zones' -def list_rules(): - command = 'ip -j -4 rule show' - answer = loads(cmd(command)) - return [_ for _ in answer if _] +def has_rule(af : str, priority : int, table : str): + """ Check if a given ip rule exists """ + if af not in ['-4', '-6']: + raise ValueError() + command = f'ip -j {af} rule show' + for tmp in loads(cmd(command)): + if {'priority', 'table'} <= set(tmp): + if tmp['priority'] == priority and tmp['table'] == table: + return True + return False def vrf_interfaces(c, match): matched = [] @@ -69,7 +76,6 @@ def vrf_routing(c, match): c.set_level(old_level) return matched - def get_config(config=None): if config: conf = config @@ -148,13 +154,11 @@ def apply(vrf): bind_all = '0' if 'bind-to-all' in vrf: bind_all = '1' - call(f'sysctl -wq net.ipv4.tcp_l3mdev_accept={bind_all}') - call(f'sysctl -wq net.ipv4.udp_l3mdev_accept={bind_all}') + sysctl('net.ipv4.tcp_l3mdev_accept', bind_all) + sysctl('net.ipv4.udp_l3mdev_accept', bind_all) for tmp in (dict_search('vrf_remove', vrf) or []): if os.path.isdir(f'/sys/class/net/{tmp}'): - call(f'ip -4 route del vrf {tmp} unreachable default metric 4278198272') - call(f'ip -6 route del vrf {tmp} unreachable default metric 4278198272') call(f'ip link delete dev {tmp}') # Remove nftables conntrack zone map item nft_del_element = f'delete element inet vrf_zones ct_iface_map {{ "{tmp}" }}' @@ -165,31 +169,59 @@ def apply(vrf): # check if table already exists _, err = popen('nft list table inet vrf_zones') # If not, create a table - if err: - if os.path.exists(nft_vrf_config): - cmd(f'nft -f {nft_vrf_config}') - os.unlink(nft_vrf_config) + if err and os.path.exists(nft_vrf_config): + cmd(f'nft -f {nft_vrf_config}') + os.unlink(nft_vrf_config) + + # Linux routing uses rules to find tables - routing targets are then + # looked up in those tables. If the lookup got a matching route, the + # process ends. + # + # TL;DR; first table with a matching entry wins! + # + # You can see your routing table lookup rules using "ip rule", sadly the + # local lookup is hit before any VRF lookup. Pinging an addresses from the + # VRF will usually find a hit in the local table, and never reach the VRF + # routing table - this is usually not what you want. Thus we will + # re-arrange the tables and move the local lookup further down once VRFs + # are enabled. + # + # Thanks to https://stbuehler.de/blog/article/2020/02/29/using_vrf__virtual_routing_and_forwarding__on_linux.html + + for afi in ['-4', '-6']: + # move lookup local to pref 32765 (from 0) + if not has_rule(afi, 32765, 'local'): + call(f'ip {afi} rule add pref 32765 table local') + if has_rule(afi, 0, 'local'): + call(f'ip {afi} rule del pref 0') + # make sure that in VRFs after failed lookup in the VRF specific table + # nothing else is reached + if not has_rule(afi, 1000, 'l3mdev'): + # this should be added by the kernel when a VRF is created + # add it here for completeness + call(f'ip {afi} rule add pref 1000 l3mdev protocol kernel') + + # add another rule with an unreachable target which only triggers in VRF context + # if a route could not be reached + if not has_rule(afi, 2000, 'l3mdev'): + call(f'ip {afi} rule add pref 2000 l3mdev unreachable') for name, config in vrf['name'].items(): table = config['table'] - if not os.path.isdir(f'/sys/class/net/{name}'): # For each VRF apart from your default context create a VRF # interface with a separate routing table call(f'ip link add {name} type vrf table {table}') - # The kernel Documentation/networking/vrf.txt also recommends - # adding unreachable routes to the VRF routing tables so that routes - # afterwards are taken. - call(f'ip -4 route add vrf {name} unreachable default metric 4278198272') - call(f'ip -6 route add vrf {name} unreachable default metric 4278198272') - # We also should add proper loopback IP addresses to the newly - # created VRFs for services bound to the loopback address (SNMP, NTP) - call(f'ip -4 addr add 127.0.0.1/8 dev {name}') - call(f'ip -6 addr add ::1/128 dev {name}') # set VRF description for e.g. SNMP monitoring vrf_if = Interface(name) + # We also should add proper loopback IP addresses to the newly + # created VRFs for services bound to the loopback address (SNMP, NTP) + vrf_if.add_addr('127.0.0.1/8') + vrf_if.add_addr('::1/128') + # add VRF description if available vrf_if.set_alias(config.get('description', '')) + # Enable/Disable of an interface must always be done at the end of the # derived class to make use of the ref-counting set_admin_state() # function. We will only enable the interface if 'up' was called as @@ -203,37 +235,9 @@ def apply(vrf): nft_add_element = f'add element inet vrf_zones ct_iface_map {{ "{name}" : {table} }}' cmd(f'nft {nft_add_element}') - # Linux routing uses rules to find tables - routing targets are then - # looked up in those tables. If the lookup got a matching route, the - # process ends. - # - # TL;DR; first table with a matching entry wins! - # - # You can see your routing table lookup rules using "ip rule", sadly the - # local lookup is hit before any VRF lookup. Pinging an addresses from the - # VRF will usually find a hit in the local table, and never reach the VRF - # routing table - this is usually not what you want. Thus we will - # re-arrange the tables and move the local lookup furhter down once VRFs - # are enabled. - - # get current preference on local table - local_pref = [r.get('priority') for r in list_rules() if r.get('table') == 'local'][0] - - # change preference when VRFs are enabled and local lookup table is default - if not local_pref and 'name' in vrf: - for af in ['-4', '-6']: - call(f'ip {af} rule add pref 32765 table local') - call(f'ip {af} rule del pref 0') # return to default lookup preference when no VRF is configured if 'name' not in vrf: - for af in ['-4', '-6']: - call(f'ip {af} rule add pref 0 table local') - call(f'ip {af} rule del pref 32765') - - # clean out l3mdev-table rule if present - if 1000 in [r.get('priority') for r in list_rules() if r.get('priority') == 1000]: - call(f'ip {af} rule del pref 1000') # Remove VRF zones table from nftables tmp = run('nft list table inet vrf_zones') if tmp == 0: -- cgit v1.2.3 From 812d9770619b968b04961aebf3944fde13df491b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 13 Feb 2022 20:32:10 +0100 Subject: ethernet: T4242: speed/duplex can never be switched back to auto/auto --- python/vyos/ethtool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/vyos/ethtool.py b/python/vyos/ethtool.py index e45b0f041..80d44eee6 100644 --- a/python/vyos/ethtool.py +++ b/python/vyos/ethtool.py @@ -41,7 +41,7 @@ class Ethtool: # '100' : {'full': '', 'half': ''}, # '1000': {'full': ''} # } - _speed_duplex = { } + _speed_duplex = {'auto': {'auto': ''}} _ring_buffers = { } _ring_buffers_max = { } _driver_name = None -- cgit v1.2.3 From 6f1326d6b68f6dcb83843374c876407ef2922bd1 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 14 Feb 2022 19:07:29 +0100 Subject: tunnel: T4154: verify() no more then one GRE tunnel is used w/o "ip key" per interface It is impossible for the OS kernel to distinguish multiple GRE tunnels when no "gre key" is configured when sourcing tunnels from the same interface. --- src/conf_mode/interfaces-tunnel.py | 39 +++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index 30f57ec0c..c9267e749 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -103,19 +103,22 @@ def verify(tunnel): raise ConfigError('Tunnel parameters ip key must be set!') if tunnel['encapsulation'] in ['gre', 'gretap']: - if dict_search('parameters.ip.key', tunnel) != None: - # Check pairs tunnel source-address/encapsulation/key with exists tunnels. - # Prevent the same key for 2 tunnels with same source-address/encap. T2920 - for tunnel_if in Section.interfaces('tunnel'): - # It makes no sense to run the test for re-used GRE keys on our - # own interface we are currently working on - if tunnel['ifname'] == tunnel_if: - continue - tunnel_cfg = get_interface_config(tunnel_if) - # no match on encapsulation - bail out - if dict_search('linkinfo.info_kind', tunnel_cfg) != tunnel['encapsulation']: - continue - new_source_address = dict_search('source_address', tunnel) + # Check pairs tunnel source-address/encapsulation/key with exists tunnels. + # Prevent the same key for 2 tunnels with same source-address/encap. T2920 + for tunnel_if in Section.interfaces('tunnel'): + # It makes no sense to run the test against our own interface we + # are currently configuring + if tunnel['ifname'] == tunnel_if: + continue + + tunnel_cfg = get_interface_config(tunnel_if) + # no match on encapsulation - bail out + if dict_search('linkinfo.info_kind', tunnel_cfg) != tunnel['encapsulation']: + continue + + new_source_address = dict_search('source_address', tunnel) + new_source_interface = dict_search('source_interface', tunnel) + if dict_search('parameters.ip.key', tunnel) != None: # Convert tunnel key to ip key, format "ip -j link show" # 1 => 0.0.0.1, 999 => 0.0.3.231 orig_new_key = dict_search('parameters.ip.key', tunnel) @@ -125,6 +128,16 @@ def verify(tunnel): dict_search('linkinfo.info_data.ikey', tunnel_cfg) == new_key: raise ConfigError(f'Key "{orig_new_key}" for source-address "{new_source_address}" ' \ f'is already used for tunnel "{tunnel_if}"!') + else: + # If no IP GRE key is used we can not have more then one GRE tunnel + # bound to any one interface/IP address. This will result in a OS + # PermissionError: add tunnel "gre0" failed: File exists + if (dict_search('address', tunnel_cfg) == new_source_address or + (dict_search('address', tunnel_cfg) == '0.0.0.0' and + dict_search('link', tunnel_cfg) == new_source_interface)): + raise ConfigError(f'Missing required "ip key" parameter when \ + running more then one GRE based tunnel on the \ + same source-interface/source-address') # Keys are not allowed with ipip and sit tunnels if tunnel['encapsulation'] in ['ipip', 'sit']: -- cgit v1.2.3 From 122c7a53575f67759f157e02eca776f799658dc1 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 14 Feb 2022 19:09:09 +0100 Subject: tunnel: T4154: import cleanup --- src/conf_mode/interfaces-tunnel.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index c9267e749..4c1204b4e 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018-2021 VyOS maintainers and contributors +# Copyright (C) 2018-2022 yOS 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 @@ -21,9 +21,7 @@ from netifaces import interfaces from ipaddress import IPv4Address from vyos.config import Config -from vyos.configdict import dict_merge from vyos.configdict import get_interface_dict -from vyos.configdict import node_changed from vyos.configdict import leaf_node_changed from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete @@ -34,8 +32,6 @@ from vyos.configverify import verify_tunnel from vyos.ifconfig import Interface from vyos.ifconfig import Section from vyos.ifconfig import TunnelIf -from vyos.template import is_ipv4 -from vyos.template import is_ipv6 from vyos.util import get_interface_config from vyos.util import dict_search from vyos import ConfigError -- cgit v1.2.3 From e00edb0072ceb07b92be826984154afeb6c567d3 Mon Sep 17 00:00:00 2001 From: Andrew Gunnerson Date: Mon, 14 Feb 2022 17:02:13 -0500 Subject: pki: eapol: T4244: Fix KeyError when CA cert name differs from client cert name This commit fixes a small typo where the client cert name was being used to index the CA configuration dict. Signed-off-by: Andrew Gunnerson --- python/vyos/configverify.py | 2 +- src/conf_mode/interfaces-ethernet.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 365a28feb..18fb7f9f7 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -173,7 +173,7 @@ def verify_eapol(config): if ca_cert_name not in config['pki']['ca']: raise ConfigError('Invalid CA certificate specified for EAPoL') - ca_cert = config['pki']['ca'][cert_name] + ca_cert = config['pki']['ca'][ca_cert_name] if 'certificate' not in ca_cert: raise ConfigError('Invalid CA certificate specified for EAPoL') diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index e7250fb49..ab8d58f81 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -165,7 +165,7 @@ def generate(ethernet): if 'ca_certificate' in ethernet['eapol']: ca_cert_file_path = os.path.join(cfg_dir, f'{ifname}_ca.pem') ca_cert_name = ethernet['eapol']['ca_certificate'] - pki_ca_cert = ethernet['pki']['ca'][cert_name] + pki_ca_cert = ethernet['pki']['ca'][ca_cert_name] write_file(ca_cert_file_path, wrap_certificate(pki_ca_cert['certificate'])) -- cgit v1.2.3 From 283688fe52bd762d1fadff635de58a87df3da629 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Fri, 11 Feb 2022 13:17:05 +0000 Subject: conntrack-sync: T4237: Fix checks for listen-address list to str Verify section conntrack_sync.py funciton 'is_addr_assigned' should checks address as string not as list (cherry picked from commit c41c51e4ed7ceb293161014a73bdd350162c3300) --- src/conf_mode/conntrack_sync.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/conf_mode/conntrack_sync.py b/src/conf_mode/conntrack_sync.py index 8f9837c2b..34d1f7398 100755 --- a/src/conf_mode/conntrack_sync.py +++ b/src/conf_mode/conntrack_sync.py @@ -93,9 +93,9 @@ def verify(conntrack): raise ConfigError('Can not configure expect-sync "all" with other protocols!') if 'listen_address' in conntrack: - address = conntrack['listen_address'] - if not is_addr_assigned(address): - raise ConfigError(f'Specified listen-address {address} not assigned to any interface!') + for address in conntrack['listen_address']: + if not is_addr_assigned(address): + raise ConfigError(f'Specified listen-address {address} not assigned to any interface!') vrrp_group = dict_search('failover_mechanism.vrrp.sync_group', conntrack) if vrrp_group == None: -- cgit v1.2.3 From 1ceaed55a629c92cf42baccdef4106e8d0e4914e Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 16 Feb 2022 22:16:01 +0100 Subject: wireless: T4240: bugfix interface bridging VLAN isolation can not be "set" when interface is of type wifi. --- python/vyos/ifconfig/bridge.py | 12 +++++++----- python/vyos/ifconfig/wireless.py | 6 +++--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py index 27073b266..ffd9c590f 100644 --- a/python/vyos/ifconfig/bridge.py +++ b/python/vyos/ifconfig/bridge.py @@ -298,7 +298,6 @@ class BridgeIf(Interface): tmp = dict_search('member.interface', config) if tmp: - for interface, interface_config in tmp.items(): # if interface does yet not exist bail out early and # add it later @@ -316,10 +315,13 @@ class BridgeIf(Interface): # enslave interface port to bridge self.add_port(interface) - # always set private-vlan/port isolation - tmp = dict_search('isolated', interface_config) - value = 'on' if (tmp != None) else 'off' - lower.set_port_isolation(value) + if not interface.startswith('wlan'): + # always set private-vlan/port isolation - this can not be + # done when lower link is a wifi link, as it will trigger: + # RTNETLINK answers: Operation not supported + tmp = dict_search('isolated', interface_config) + value = 'on' if (tmp != None) else 'off' + lower.set_port_isolation(value) # set bridge port path cost if 'cost' in interface_config: diff --git a/python/vyos/ifconfig/wireless.py b/python/vyos/ifconfig/wireless.py index 748b6e02d..88eaa772b 100644 --- a/python/vyos/ifconfig/wireless.py +++ b/python/vyos/ifconfig/wireless.py @@ -49,10 +49,10 @@ class WiFiIf(Interface): on any interface. """ # We can not call add_to_bridge() until wpa_supplicant is running, thus - # we will remove the key from the config dict and react to this specal - # case in thie derived class. + # we will remove the key from the config dict and react to this special + # case in this derived class. # re-add ourselves to any bridge we might have fallen out of - bridge_member = '' + bridge_member = None if 'is_bridge_member' in config: bridge_member = config['is_bridge_member'] del config['is_bridge_member'] -- cgit v1.2.3 From f076f9f4cf6e6b7d89eada9f5d59bacea0f3af72 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 16 Feb 2022 22:17:48 +0100 Subject: policy: T2425: add completion helper script when referencing IP addresses --- interface-definitions/policy.xml.in | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/interface-definitions/policy.xml.in b/interface-definitions/policy.xml.in index 61c5ab90a..9767285dd 100644 --- a/interface-definitions/policy.xml.in +++ b/interface-definitions/policy.xml.in @@ -1113,6 +1113,9 @@ Nexthop IP address + + + ipv4 IP address @@ -1130,6 +1133,9 @@ Nexthop IPv6 global address + + + ipv6 IPv6 address and prefix length @@ -1142,6 +1148,9 @@ Nexthop IPv6 local address + + + ipv6 IPv6 address and prefix length @@ -1268,6 +1277,9 @@ Source address for route + + + ipv4 IPv4 address -- cgit v1.2.3 From 3795fdba8edf8e81298370d6cd8d81a779ae2997 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 16 Feb 2022 11:31:23 -0600 Subject: xml: T3474: add component version include files Add the include files containing the syntaxVersion element defining the version of the respective component; these files are included by the top level file 'xml-component-versions.xml.in'. Processing of these elements was previously added to the python xml lib in commit 40f5359d. This will replace the use of 'curver_DATA' in vyatta-cfg-system and other legacy packages. --- .../include/version/bgp-version.xml.i | 3 ++ .../include/version/broadcast-relay-version.xml.i | 3 ++ .../include/version/cluster-version.xml.i | 3 ++ .../version/config-management-version.xml.i | 3 ++ .../include/version/conntrack-sync-version.xml.i | 3 ++ .../include/version/conntrack-version.xml.i | 3 ++ .../include/version/dhcp-relay-version.xml.i | 3 ++ .../include/version/dhcp-server-version.xml.i | 3 ++ .../include/version/dhcpv6-server-version.xml.i | 3 ++ .../include/version/dns-forwarding-version.xml.i | 3 ++ .../include/version/firewall-version.xml.i | 3 ++ .../include/version/flow-accounting-version.xml.i | 3 ++ .../include/version/https-version.xml.i | 3 ++ .../include/version/interfaces-version.xml.i | 3 ++ .../include/version/ipoe-server-version.xml.i | 3 ++ .../include/version/ipsec-version.xml.i | 3 ++ .../include/version/isis-version.xml.i | 3 ++ .../include/version/l2tp-version.xml.i | 3 ++ .../include/version/lldp-version.xml.i | 3 ++ .../include/version/mdns-version.xml.i | 3 ++ .../include/version/nat-version.xml.i | 3 ++ .../include/version/nat66-version.xml.i | 3 ++ .../include/version/ntp-version.xml.i | 3 ++ .../include/version/openconnect-version.xml.i | 3 ++ .../include/version/ospf-version.xml.i | 3 ++ .../include/version/policy-version.xml.i | 3 ++ .../include/version/pppoe-server-version.xml.i | 3 ++ .../include/version/pptp-version.xml.i | 3 ++ .../include/version/qos-version.xml.i | 3 ++ .../include/version/quagga-version.xml.i | 3 ++ .../include/version/rpki-version.xml.i | 3 ++ .../include/version/salt-version.xml.i | 3 ++ .../include/version/snmp-version.xml.i | 3 ++ .../include/version/ssh-version.xml.i | 3 ++ .../include/version/sstp-version.xml.i | 3 ++ .../include/version/system-version.xml.i | 3 ++ .../include/version/vrf-version.xml.i | 3 ++ .../include/version/vrrp-version.xml.i | 3 ++ .../include/version/vyos-accel-ppp-version.xml.i | 3 ++ .../include/version/wanloadbalance-version.xml.i | 3 ++ .../include/version/webproxy-version.xml.i | 3 ++ interface-definitions/xml-component-version.xml.in | 44 ++++++++++++++++++++++ python/vyos/xml/__init__.py | 4 +- python/vyos/xml/definition.py | 9 +++-- 44 files changed, 174 insertions(+), 6 deletions(-) create mode 100644 interface-definitions/include/version/bgp-version.xml.i create mode 100644 interface-definitions/include/version/broadcast-relay-version.xml.i create mode 100644 interface-definitions/include/version/cluster-version.xml.i create mode 100644 interface-definitions/include/version/config-management-version.xml.i create mode 100644 interface-definitions/include/version/conntrack-sync-version.xml.i create mode 100644 interface-definitions/include/version/conntrack-version.xml.i create mode 100644 interface-definitions/include/version/dhcp-relay-version.xml.i create mode 100644 interface-definitions/include/version/dhcp-server-version.xml.i create mode 100644 interface-definitions/include/version/dhcpv6-server-version.xml.i create mode 100644 interface-definitions/include/version/dns-forwarding-version.xml.i create mode 100644 interface-definitions/include/version/firewall-version.xml.i create mode 100644 interface-definitions/include/version/flow-accounting-version.xml.i create mode 100644 interface-definitions/include/version/https-version.xml.i create mode 100644 interface-definitions/include/version/interfaces-version.xml.i create mode 100644 interface-definitions/include/version/ipoe-server-version.xml.i create mode 100644 interface-definitions/include/version/ipsec-version.xml.i create mode 100644 interface-definitions/include/version/isis-version.xml.i create mode 100644 interface-definitions/include/version/l2tp-version.xml.i create mode 100644 interface-definitions/include/version/lldp-version.xml.i create mode 100644 interface-definitions/include/version/mdns-version.xml.i create mode 100644 interface-definitions/include/version/nat-version.xml.i create mode 100644 interface-definitions/include/version/nat66-version.xml.i create mode 100644 interface-definitions/include/version/ntp-version.xml.i create mode 100644 interface-definitions/include/version/openconnect-version.xml.i create mode 100644 interface-definitions/include/version/ospf-version.xml.i create mode 100644 interface-definitions/include/version/policy-version.xml.i create mode 100644 interface-definitions/include/version/pppoe-server-version.xml.i create mode 100644 interface-definitions/include/version/pptp-version.xml.i create mode 100644 interface-definitions/include/version/qos-version.xml.i create mode 100644 interface-definitions/include/version/quagga-version.xml.i create mode 100644 interface-definitions/include/version/rpki-version.xml.i create mode 100644 interface-definitions/include/version/salt-version.xml.i create mode 100644 interface-definitions/include/version/snmp-version.xml.i create mode 100644 interface-definitions/include/version/ssh-version.xml.i create mode 100644 interface-definitions/include/version/sstp-version.xml.i create mode 100644 interface-definitions/include/version/system-version.xml.i create mode 100644 interface-definitions/include/version/vrf-version.xml.i create mode 100644 interface-definitions/include/version/vrrp-version.xml.i create mode 100644 interface-definitions/include/version/vyos-accel-ppp-version.xml.i create mode 100644 interface-definitions/include/version/wanloadbalance-version.xml.i create mode 100644 interface-definitions/include/version/webproxy-version.xml.i create mode 100644 interface-definitions/xml-component-version.xml.in diff --git a/interface-definitions/include/version/bgp-version.xml.i b/interface-definitions/include/version/bgp-version.xml.i new file mode 100644 index 000000000..15bc5abd4 --- /dev/null +++ b/interface-definitions/include/version/bgp-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/broadcast-relay-version.xml.i b/interface-definitions/include/version/broadcast-relay-version.xml.i new file mode 100644 index 000000000..98481f446 --- /dev/null +++ b/interface-definitions/include/version/broadcast-relay-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/cluster-version.xml.i b/interface-definitions/include/version/cluster-version.xml.i new file mode 100644 index 000000000..621996df4 --- /dev/null +++ b/interface-definitions/include/version/cluster-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/config-management-version.xml.i b/interface-definitions/include/version/config-management-version.xml.i new file mode 100644 index 000000000..695ba09ab --- /dev/null +++ b/interface-definitions/include/version/config-management-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/conntrack-sync-version.xml.i b/interface-definitions/include/version/conntrack-sync-version.xml.i new file mode 100644 index 000000000..f040c29f6 --- /dev/null +++ b/interface-definitions/include/version/conntrack-sync-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/conntrack-version.xml.i b/interface-definitions/include/version/conntrack-version.xml.i new file mode 100644 index 000000000..696f76362 --- /dev/null +++ b/interface-definitions/include/version/conntrack-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/dhcp-relay-version.xml.i b/interface-definitions/include/version/dhcp-relay-version.xml.i new file mode 100644 index 000000000..75f5d5486 --- /dev/null +++ b/interface-definitions/include/version/dhcp-relay-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/dhcp-server-version.xml.i b/interface-definitions/include/version/dhcp-server-version.xml.i new file mode 100644 index 000000000..330cb7d1b --- /dev/null +++ b/interface-definitions/include/version/dhcp-server-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/dhcpv6-server-version.xml.i b/interface-definitions/include/version/dhcpv6-server-version.xml.i new file mode 100644 index 000000000..4b2cf40aa --- /dev/null +++ b/interface-definitions/include/version/dhcpv6-server-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/dns-forwarding-version.xml.i b/interface-definitions/include/version/dns-forwarding-version.xml.i new file mode 100644 index 000000000..fe817940a --- /dev/null +++ b/interface-definitions/include/version/dns-forwarding-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/firewall-version.xml.i b/interface-definitions/include/version/firewall-version.xml.i new file mode 100644 index 000000000..059a89f24 --- /dev/null +++ b/interface-definitions/include/version/firewall-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/flow-accounting-version.xml.i b/interface-definitions/include/version/flow-accounting-version.xml.i new file mode 100644 index 000000000..5b01fe4b5 --- /dev/null +++ b/interface-definitions/include/version/flow-accounting-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/https-version.xml.i b/interface-definitions/include/version/https-version.xml.i new file mode 100644 index 000000000..586083649 --- /dev/null +++ b/interface-definitions/include/version/https-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/interfaces-version.xml.i b/interface-definitions/include/version/interfaces-version.xml.i new file mode 100644 index 000000000..b97971531 --- /dev/null +++ b/interface-definitions/include/version/interfaces-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/ipoe-server-version.xml.i b/interface-definitions/include/version/ipoe-server-version.xml.i new file mode 100644 index 000000000..00d2544e6 --- /dev/null +++ b/interface-definitions/include/version/ipoe-server-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/ipsec-version.xml.i b/interface-definitions/include/version/ipsec-version.xml.i new file mode 100644 index 000000000..fcdd6c702 --- /dev/null +++ b/interface-definitions/include/version/ipsec-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/isis-version.xml.i b/interface-definitions/include/version/isis-version.xml.i new file mode 100644 index 000000000..4a8fef39c --- /dev/null +++ b/interface-definitions/include/version/isis-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/l2tp-version.xml.i b/interface-definitions/include/version/l2tp-version.xml.i new file mode 100644 index 000000000..86114d676 --- /dev/null +++ b/interface-definitions/include/version/l2tp-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/lldp-version.xml.i b/interface-definitions/include/version/lldp-version.xml.i new file mode 100644 index 000000000..0deb73279 --- /dev/null +++ b/interface-definitions/include/version/lldp-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/mdns-version.xml.i b/interface-definitions/include/version/mdns-version.xml.i new file mode 100644 index 000000000..b200a68b4 --- /dev/null +++ b/interface-definitions/include/version/mdns-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/nat-version.xml.i b/interface-definitions/include/version/nat-version.xml.i new file mode 100644 index 000000000..027216a07 --- /dev/null +++ b/interface-definitions/include/version/nat-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/nat66-version.xml.i b/interface-definitions/include/version/nat66-version.xml.i new file mode 100644 index 000000000..7b7123dcc --- /dev/null +++ b/interface-definitions/include/version/nat66-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/ntp-version.xml.i b/interface-definitions/include/version/ntp-version.xml.i new file mode 100644 index 000000000..cc4ff9a1c --- /dev/null +++ b/interface-definitions/include/version/ntp-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/openconnect-version.xml.i b/interface-definitions/include/version/openconnect-version.xml.i new file mode 100644 index 000000000..d7d35b321 --- /dev/null +++ b/interface-definitions/include/version/openconnect-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/ospf-version.xml.i b/interface-definitions/include/version/ospf-version.xml.i new file mode 100644 index 000000000..755965daa --- /dev/null +++ b/interface-definitions/include/version/ospf-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/policy-version.xml.i b/interface-definitions/include/version/policy-version.xml.i new file mode 100644 index 000000000..6d0c80518 --- /dev/null +++ b/interface-definitions/include/version/policy-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/pppoe-server-version.xml.i b/interface-definitions/include/version/pppoe-server-version.xml.i new file mode 100644 index 000000000..ec81487f8 --- /dev/null +++ b/interface-definitions/include/version/pppoe-server-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/pptp-version.xml.i b/interface-definitions/include/version/pptp-version.xml.i new file mode 100644 index 000000000..0296c44e9 --- /dev/null +++ b/interface-definitions/include/version/pptp-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/qos-version.xml.i b/interface-definitions/include/version/qos-version.xml.i new file mode 100644 index 000000000..e4d139349 --- /dev/null +++ b/interface-definitions/include/version/qos-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/quagga-version.xml.i b/interface-definitions/include/version/quagga-version.xml.i new file mode 100644 index 000000000..bb8ad7f82 --- /dev/null +++ b/interface-definitions/include/version/quagga-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/rpki-version.xml.i b/interface-definitions/include/version/rpki-version.xml.i new file mode 100644 index 000000000..2fff259a8 --- /dev/null +++ b/interface-definitions/include/version/rpki-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/salt-version.xml.i b/interface-definitions/include/version/salt-version.xml.i new file mode 100644 index 000000000..fe4684050 --- /dev/null +++ b/interface-definitions/include/version/salt-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/snmp-version.xml.i b/interface-definitions/include/version/snmp-version.xml.i new file mode 100644 index 000000000..0416288f0 --- /dev/null +++ b/interface-definitions/include/version/snmp-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/ssh-version.xml.i b/interface-definitions/include/version/ssh-version.xml.i new file mode 100644 index 000000000..0f25caf98 --- /dev/null +++ b/interface-definitions/include/version/ssh-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/sstp-version.xml.i b/interface-definitions/include/version/sstp-version.xml.i new file mode 100644 index 000000000..79b43a3e7 --- /dev/null +++ b/interface-definitions/include/version/sstp-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/system-version.xml.i b/interface-definitions/include/version/system-version.xml.i new file mode 100644 index 000000000..fb4629bf1 --- /dev/null +++ b/interface-definitions/include/version/system-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/vrf-version.xml.i b/interface-definitions/include/version/vrf-version.xml.i new file mode 100644 index 000000000..9d7ff35fe --- /dev/null +++ b/interface-definitions/include/version/vrf-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/vrrp-version.xml.i b/interface-definitions/include/version/vrrp-version.xml.i new file mode 100644 index 000000000..626dd6cbc --- /dev/null +++ b/interface-definitions/include/version/vrrp-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/vyos-accel-ppp-version.xml.i b/interface-definitions/include/version/vyos-accel-ppp-version.xml.i new file mode 100644 index 000000000..e5a4e1613 --- /dev/null +++ b/interface-definitions/include/version/vyos-accel-ppp-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/wanloadbalance-version.xml.i b/interface-definitions/include/version/wanloadbalance-version.xml.i new file mode 100644 index 000000000..59f8729cc --- /dev/null +++ b/interface-definitions/include/version/wanloadbalance-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/include/version/webproxy-version.xml.i b/interface-definitions/include/version/webproxy-version.xml.i new file mode 100644 index 000000000..42dbf3f8b --- /dev/null +++ b/interface-definitions/include/version/webproxy-version.xml.i @@ -0,0 +1,3 @@ + + + diff --git a/interface-definitions/xml-component-version.xml.in b/interface-definitions/xml-component-version.xml.in new file mode 100644 index 000000000..b7f063a6c --- /dev/null +++ b/interface-definitions/xml-component-version.xml.in @@ -0,0 +1,44 @@ + + + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + diff --git a/python/vyos/xml/__init__.py b/python/vyos/xml/__init__.py index e0eacb2d1..6db446a40 100644 --- a/python/vyos/xml/__init__.py +++ b/python/vyos/xml/__init__.py @@ -46,8 +46,8 @@ def is_tag(lpath): def is_leaf(lpath, flat=True): return load_configuration().is_leaf(lpath, flat) -def component_versions(): - return load_configuration().component_versions() +def component_version(): + return load_configuration().component_version() def defaults(lpath, flat=False): return load_configuration().defaults(lpath, flat) diff --git a/python/vyos/xml/definition.py b/python/vyos/xml/definition.py index 5e0d5282c..bc3892b42 100644 --- a/python/vyos/xml/definition.py +++ b/python/vyos/xml/definition.py @@ -249,10 +249,11 @@ class XML(dict): # @lru_cache(maxsize=100) # XXX: need to use cachetool instead - for later - def component_versions(self) -> dict: - sort_component = sorted(self[kw.component_version].items(), - key = lambda kv: kv[0]) - return dict(sort_component) + def component_version(self) -> dict: + d = {} + for k in sorted(self[kw.component_version]): + d[k] = int(self[kw.component_version][k]) + return d def defaults(self, lpath, flat): d = self[kw.default] -- cgit v1.2.3 From d9a1f8deceec729371004377692d477f5e554cb1 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 16 Feb 2022 15:29:02 -0600 Subject: xml: T3474: add smoketest to check xml component versions are maintained Add smoketest to catch updates to a component version in legacy curver_DATA that is not present in xml syntaxVersion. --- python/vyos/systemversions.py | 7 +++++ smoketest/scripts/cli/test_component_version.py | 36 +++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100755 smoketest/scripts/cli/test_component_version.py diff --git a/python/vyos/systemversions.py b/python/vyos/systemversions.py index 9b3f4f413..f2da76d4f 100644 --- a/python/vyos/systemversions.py +++ b/python/vyos/systemversions.py @@ -17,7 +17,10 @@ import os import re import sys import vyos.defaults +from vyos.xml import component_version +# legacy version, reading from the file names in +# /opt/vyatta/etc/config-migrate/current def get_system_versions(): """ Get component versions from running system; critical failure if @@ -37,3 +40,7 @@ def get_system_versions(): system_versions[pair[0]] = int(pair[1]) return system_versions + +# read from xml cache +def get_system_component_version(): + return component_version() diff --git a/smoketest/scripts/cli/test_component_version.py b/smoketest/scripts/cli/test_component_version.py new file mode 100755 index 000000000..777379bdd --- /dev/null +++ b/smoketest/scripts/cli/test_component_version.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 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 . + +import unittest + +from vyos.systemversions import get_system_versions, get_system_component_version + +# After T3474, component versions should be updated in the files in +# vyos-1x/interface-definitions/include/version/ +# This test verifies that the legacy version in curver_DATA does not exceed +# that in the xml cache. +class TestComponentVersion(unittest.TestCase): + def setUp(self): + self.legacy_d = get_system_versions() + self.xml_d = get_system_component_version() + + def test_component_version(self): + self.assertTrue(set(self.legacy_d).issubset(set(self.xml_d))) + for k, v in self.legacy_d.items(): + self.assertTrue(v <= self.xml_d[k]) + +if __name__ == '__main__': + unittest.main(verbosity=2) -- cgit v1.2.3 From 425f8f16caa7e9e5101be3dc8ab32e60db274d7f Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 16 Feb 2022 20:11:31 -0600 Subject: xml: T3474: get component version dictionary from xml cache, not legacy --- python/vyos/migrator.py | 2 +- src/helpers/system-versions-foot.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python/vyos/migrator.py b/python/vyos/migrator.py index 4574bb6d1..a2e0daabd 100644 --- a/python/vyos/migrator.py +++ b/python/vyos/migrator.py @@ -195,7 +195,7 @@ class Migrator(object): # This will force calling all migration scripts: cfg_versions = {} - sys_versions = systemversions.get_system_versions() + sys_versions = systemversions.get_system_component_version() # save system component versions in json file for easy reference self.save_json_record(sys_versions) diff --git a/src/helpers/system-versions-foot.py b/src/helpers/system-versions-foot.py index c33e41d79..2aa687221 100755 --- a/src/helpers/system-versions-foot.py +++ b/src/helpers/system-versions-foot.py @@ -21,7 +21,7 @@ import vyos.systemversions as systemversions import vyos.defaults import vyos.version -sys_versions = systemversions.get_system_versions() +sys_versions = systemversions.get_system_component_version() component_string = formatversions.format_versions_string(sys_versions) -- cgit v1.2.3 From 1cbcbf40b7721849f9696c05fac65db010a66b7c Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 17 Feb 2022 20:58:02 +0100 Subject: openvpn: T4230: globally enable ip_nonlocal_bind --- src/conf_mode/interfaces-openvpn.py | 7 ------- src/etc/sysctl.d/33-vyos-nonlocal-bind.conf | 8 ++++++++ 2 files changed, 8 insertions(+), 7 deletions(-) create mode 100644 src/etc/sysctl.d/33-vyos-nonlocal-bind.conf diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 329399274..29a25eedc 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -649,13 +649,6 @@ def apply(openvpn): return None - # verify specified IP address is present on any interface on this system - # Allow to bind service to nonlocal address, if it virtaual-vrrp address - # or if address will be assign later - if 'local_host' in openvpn: - if not is_addr_assigned(openvpn['local_host']): - cmd('sysctl -w net.ipv4.ip_nonlocal_bind=1') - # No matching OpenVPN process running - maybe it got killed or none # existed - nevertheless, spawn new OpenVPN process call(f'systemctl reload-or-restart openvpn@{interface}.service') diff --git a/src/etc/sysctl.d/33-vyos-nonlocal-bind.conf b/src/etc/sysctl.d/33-vyos-nonlocal-bind.conf new file mode 100644 index 000000000..aa81b5336 --- /dev/null +++ b/src/etc/sysctl.d/33-vyos-nonlocal-bind.conf @@ -0,0 +1,8 @@ +### Added by vyos-1x ### +# +# ip_nonlocal_bind - BOOLEAN +# If set, allows processes to bind() to non-local IP addresses, +# which can be quite useful - but may break some applications. +# Default: 0 +net.ipv4.ip_nonlocal_bind = 1 +net.ipv6.ip_nonlocal_bind = 1 -- cgit v1.2.3 From 9e626ce7bad2bd846826822a3622fedf2d937e09 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 17 Feb 2022 21:10:16 +0100 Subject: vyos.configverify: T4255: fix unexpected print of dictionary instead of key --- python/vyos/configverify.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 18fb7f9f7..fab88bc72 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -224,9 +224,10 @@ def verify_bridge_delete(config): when interface also is part of a bridge. """ if 'is_bridge_member' in config: - raise ConfigError( - 'Interface "{ifname}" cannot be deleted as it is a ' - 'member of bridge "{is_bridge_member}"!'.format(**config)) + interface = config['ifname'] + for bridge in config['is_bridge_member']: + raise ConfigError(f'Interface "{interface}" cannot be deleted as it ' + f'is a member of bridge "{bridge}"!') def verify_interface_exists(ifname): """ -- cgit v1.2.3 From 3d1b34bf715e594aa4a013d409bfcc5a4c4ad99c Mon Sep 17 00:00:00 2001 From: Andrew Gunnerson Date: Wed, 16 Feb 2022 17:46:06 -0500 Subject: pki: eapol: T4245: Add full CA and client cert chains to wpa_supplicant PEM files This commit updates the eapol code so that it writes the full certificate chains for both the specified CA and the client certificate to `_ca.pem` and `_cert.pem`, respectively. The full CA chain is necessary for validating the incoming server certificate when it is signed by an intermediate CA and the intermediate CA cert is not included in the EAP-TLS ServerHello. In this scenario, wpa_supplicant needs to have both the intermediate CA and the root CA in its `ca_file`. Similarly, the full client certificate chain is needed when the ISP expects/requires that the client (wpa_supplicant) sends the client cert + the intermediate CA (or even + the root CA) as part of the EAP-TLS ClientHello. Signed-off-by: Andrew Gunnerson --- python/vyos/pki.py | 26 ++++++ smoketest/scripts/cli/test_interfaces_ethernet.py | 108 +++++++++++++++++----- src/conf_mode/interfaces-ethernet.py | 18 +++- 3 files changed, 125 insertions(+), 27 deletions(-) diff --git a/python/vyos/pki.py b/python/vyos/pki.py index 68ad73bf2..0b916eaae 100644 --- a/python/vyos/pki.py +++ b/python/vyos/pki.py @@ -331,3 +331,29 @@ def verify_certificate(cert, ca_cert): return True except InvalidSignature: return False + +# Certificate chain + +def find_parent(cert, ca_certs): + for ca_cert in ca_certs: + if verify_certificate(cert, ca_cert): + return ca_cert + return None + +def find_chain(cert, ca_certs): + remaining = ca_certs.copy() + chain = [cert] + + while remaining: + parent = find_parent(chain[-1], remaining) + if parent is None: + # No parent in the list of remaining certificates or there's a circular dependency + break + elif parent == chain[-1]: + # Self-signed: must be root CA (end of chain) + break + else: + remaining.remove(parent) + chain.append(parent) + + return chain diff --git a/smoketest/scripts/cli/test_interfaces_ethernet.py b/smoketest/scripts/cli/test_interfaces_ethernet.py index 6d80e4c96..ae3d5ed96 100755 --- a/smoketest/scripts/cli/test_interfaces_ethernet.py +++ b/smoketest/scripts/cli/test_interfaces_ethernet.py @@ -21,29 +21,73 @@ import unittest from base_interfaces_test import BasicInterfaceTest from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section +from vyos.pki import CERT_BEGIN from vyos.util import cmd from vyos.util import process_named_running from vyos.util import read_file -cert_data = """ -MIICFDCCAbugAwIBAgIUfMbIsB/ozMXijYgUYG80T1ry+mcwCgYIKoZIzj0EAwIw -WTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNv -bWUtQ2l0eTENMAsGA1UECgwEVnlPUzESMBAGA1UEAwwJVnlPUyBUZXN0MB4XDTIx -MDcyMDEyNDUxMloXDTI2MDcxOTEyNDUxMlowWTELMAkGA1UEBhMCR0IxEzARBgNV -BAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNvbWUtQ2l0eTENMAsGA1UECgwEVnlP -UzESMBAGA1UEAwwJVnlPUyBUZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE -01HrLcNttqq4/PtoMua8rMWEkOdBu7vP94xzDO7A8C92ls1v86eePy4QllKCzIw3 -QxBIoCuH2peGRfWgPRdFsKNhMF8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E -BAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMB0GA1UdDgQWBBSu -+JnU5ZC4mkuEpqg2+Mk4K79oeDAKBggqhkjOPQQDAgNHADBEAiBEFdzQ/Bc3Lftz -ngrY605UhA6UprHhAogKgROv7iR4QgIgEFUxTtW3xXJcnUPWhhUFhyZoqfn8dE93 -+dm/LDnp7C0= +server_ca_root_cert_data = """ +MIIBcTCCARagAwIBAgIUDcAf1oIQV+6WRaW7NPcSnECQ/lUwCgYIKoZIzj0EAwIw +HjEcMBoGA1UEAwwTVnlPUyBzZXJ2ZXIgcm9vdCBDQTAeFw0yMjAyMTcxOTQxMjBa +Fw0zMjAyMTUxOTQxMjBaMB4xHDAaBgNVBAMME1Z5T1Mgc2VydmVyIHJvb3QgQ0Ew +WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQ0y24GzKQf4aM2Ir12tI9yITOIzAUj +ZXyJeCmYI6uAnyAMqc4Q4NKyfq3nBi4XP87cs1jlC1P2BZ8MsjL5MdGWozIwMDAP +BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRwC/YaieMEnjhYa7K3Flw/o0SFuzAK +BggqhkjOPQQDAgNJADBGAiEAh3qEj8vScsjAdBy5shXzXDVVOKWCPTdGrPKnu8UW +a2cCIQDlDgkzWmn5ujc5ATKz1fj+Se/aeqwh4QyoWCVTFLIxhQ== """ -key_data = """ -MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPLpD0Ohhoq0g4nhx -2KMIuze7ucKUt/lBEB2wc03IxXyhRANCAATTUestw222qrj8+2gy5rysxYSQ50G7 -u8/3jHMM7sDwL3aWzW/zp54/LhCWUoLMjDdDEEigK4fal4ZF9aA9F0Ww +server_ca_intermediate_cert_data = """ +MIIBmTCCAT+gAwIBAgIUNzrtHzLmi3QpPK57tUgCnJZhXXQwCgYIKoZIzj0EAwIw +HjEcMBoGA1UEAwwTVnlPUyBzZXJ2ZXIgcm9vdCBDQTAeFw0yMjAyMTcxOTQxMjFa +Fw0zMjAyMTUxOTQxMjFaMCYxJDAiBgNVBAMMG1Z5T1Mgc2VydmVyIGludGVybWVk +aWF0ZSBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABEl2nJ1CzoqPV6hWII2m +eGN/uieU6wDMECTk/LgG8CCCSYb488dibUiFN/1UFsmoLIdIhkx/6MUCYh62m8U2 +WNujUzBRMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMV3YwH88I5gFsFUibbQ +kMR0ECPsMB8GA1UdIwQYMBaAFHAL9hqJ4wSeOFhrsrcWXD+jRIW7MAoGCCqGSM49 +BAMCA0gAMEUCIQC/ahujD9dp5pMMCd3SZddqGC9cXtOwMN0JR3e5CxP13AIgIMQm +jMYrinFoInxmX64HfshYqnUY8608nK9D2BNPOHo= +""" + +client_ca_root_cert_data = """ +MIIBcDCCARagAwIBAgIUZmoW2xVdwkZSvglnkCq0AHKa6zIwCgYIKoZIzj0EAwIw +HjEcMBoGA1UEAwwTVnlPUyBjbGllbnQgcm9vdCBDQTAeFw0yMjAyMTcxOTQxMjFa +Fw0zMjAyMTUxOTQxMjFaMB4xHDAaBgNVBAMME1Z5T1MgY2xpZW50IHJvb3QgQ0Ew +WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATUpKXzQk2NOVKDN4VULk2yw4mOKPvn +mg947+VY7lbpfOfAUD0QRg95qZWCw899eKnXp/U4TkAVrmEKhUb6OJTFozIwMDAP +BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTXu6xGWUl25X3sBtrhm3BJSICIATAK +BggqhkjOPQQDAgNIADBFAiEAnTzEwuTI9bz2Oae3LZbjP6f/f50KFJtjLZFDbQz7 +DpYCIDNRHV8zBUibC+zg5PqMpQBKd/oPfNU76nEv6xkp/ijO +""" + +client_ca_intermediate_cert_data = """ +MIIBmDCCAT+gAwIBAgIUJEMdotgqA7wU4XXJvEzDulUAGqgwCgYIKoZIzj0EAwIw +HjEcMBoGA1UEAwwTVnlPUyBjbGllbnQgcm9vdCBDQTAeFw0yMjAyMTcxOTQxMjJa +Fw0zMjAyMTUxOTQxMjJaMCYxJDAiBgNVBAMMG1Z5T1MgY2xpZW50IGludGVybWVk +aWF0ZSBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGyIVIi217s9j3O+WQ2b +6R65/Z0ZjQpELxPjBRc0CA0GFCo+pI5EvwI+jNFArvTAJ5+ZdEWUJ1DQhBKDDQdI +avCjUzBRMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOUS8oNJjChB1Rb9Blcl +ETvziHJ9MB8GA1UdIwQYMBaAFNe7rEZZSXblfewG2uGbcElIgIgBMAoGCCqGSM49 +BAMCA0cAMEQCIArhaxWgRsAUbEeNHD/ULtstLHxw/P97qPUSROLQld53AiBjgiiz +9pDfISmpekZYz6bIDWRIR0cXUToZEMFNzNMrQg== +""" + +client_cert_data = """ +MIIBmTCCAUCgAwIBAgIUV5T77XdE/tV82Tk4Vzhp5BIFFm0wCgYIKoZIzj0EAwIw +JjEkMCIGA1UEAwwbVnlPUyBjbGllbnQgaW50ZXJtZWRpYXRlIENBMB4XDTIyMDIx +NzE5NDEyMloXDTMyMDIxNTE5NDEyMlowIjEgMB4GA1UEAwwXVnlPUyBjbGllbnQg +Y2VydGlmaWNhdGUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARuyynqfc/qJj5e +KJ03oOH8X4Z8spDeAPO9WYckMM0ldPj+9kU607szFzPwjaPWzPdgyIWz3hcN8yAh +CIhytmJao1AwTjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTIFKrxZ+PqOhYSUqnl +TGCUmM7wTjAfBgNVHSMEGDAWgBTlEvKDSYwoQdUW/QZXJRE784hyfTAKBggqhkjO +PQQDAgNHADBEAiAvO8/jvz05xqmP3OXD53XhfxDLMIxzN4KPoCkFqvjlhQIgIHq2 +/geVx3rAOtSps56q/jiDouN/aw01TdpmGKVAa9U= +""" + +client_key_data = """ +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgxaxAQsJwjoOCByQE ++qSYKtKtJzbdbOnTsKNSrfgkFH6hRANCAARuyynqfc/qJj5eKJ03oOH8X4Z8spDe +APO9WYckMM0ldPj+9kU607szFzPwjaPWzPdgyIWz3hcN8yAhCIhytmJa """ def get_wpa_supplicant_value(interface, key): @@ -51,6 +95,10 @@ def get_wpa_supplicant_value(interface, key): tmp = re.findall(r'\n?{}=(.*)'.format(key), tmp) return tmp[0] +def get_certificate_count(interface, cert_type): + tmp = read_file(f'/run/wpa_supplicant/{interface}_{cert_type}.pem') + return tmp.count(CERT_BEGIN) + class EthernetInterfaceTest(BasicInterfaceTest.TestCase): @classmethod def setUpClass(cls): @@ -165,16 +213,23 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase): self.cli_commit() def test_eapol_support(self): - ca_name = 'eapol' - cert_name = 'eapol' + ca_certs = { + 'eapol-server-ca-root': server_ca_root_cert_data, + 'eapol-server-ca-intermediate': server_ca_intermediate_cert_data, + 'eapol-client-ca-root': client_ca_root_cert_data, + 'eapol-client-ca-intermediate': client_ca_intermediate_cert_data, + } + cert_name = 'eapol-client' - self.cli_set(['pki', 'ca', ca_name, 'certificate', cert_data.replace('\n','')]) - self.cli_set(['pki', 'certificate', cert_name, 'certificate', cert_data.replace('\n','')]) - self.cli_set(['pki', 'certificate', cert_name, 'private', 'key', key_data.replace('\n','')]) + for name, data in ca_certs.items(): + self.cli_set(['pki', 'ca', name, 'certificate', data.replace('\n','')]) + + self.cli_set(['pki', 'certificate', cert_name, 'certificate', client_cert_data.replace('\n','')]) + self.cli_set(['pki', 'certificate', cert_name, 'private', 'key', client_key_data.replace('\n','')]) for interface in self._interfaces: # Enable EAPoL - self.cli_set(self._base_path + [interface, 'eapol', 'ca-certificate', ca_name]) + self.cli_set(self._base_path + [interface, 'eapol', 'ca-certificate', 'eapol-server-ca-intermediate']) self.cli_set(self._base_path + [interface, 'eapol', 'certificate', cert_name]) self.cli_commit() @@ -206,7 +261,12 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase): tmp = get_wpa_supplicant_value(interface, 'identity') self.assertEqual(f'"{mac}"', tmp) - self.cli_delete(['pki', 'ca', ca_name]) + # Check certificate files have the full chain + self.assertEqual(get_certificate_count(interface, 'ca'), 2) + self.assertEqual(get_certificate_count(interface, 'cert'), 3) + + for name in ca_certs: + self.cli_delete(['pki', 'ca', name]) self.cli_delete(['pki', 'certificate', cert_name]) if __name__ == '__main__': diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index ab8d58f81..2a8a126f2 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -32,7 +32,9 @@ from vyos.configverify import verify_vlan_config from vyos.configverify import verify_vrf from vyos.ethtool import Ethtool from vyos.ifconfig import EthernetIf -from vyos.pki import wrap_certificate +from vyos.pki import find_chain +from vyos.pki import encode_certificate +from vyos.pki import load_certificate from vyos.pki import wrap_private_key from vyos.template import render from vyos.util import call @@ -159,7 +161,14 @@ def generate(ethernet): cert_name = ethernet['eapol']['certificate'] pki_cert = ethernet['pki']['certificate'][cert_name] - write_file(cert_file_path, wrap_certificate(pki_cert['certificate'])) + loaded_pki_cert = load_certificate(pki_cert['certificate']) + loaded_ca_certs = {load_certificate(c['certificate']) + for c in ethernet['pki']['ca'].values()} + + cert_full_chain = find_chain(loaded_pki_cert, loaded_ca_certs) + + write_file(cert_file_path, + '\n'.join(encode_certificate(c) for c in cert_full_chain)) write_file(cert_key_path, wrap_private_key(pki_cert['private']['key'])) if 'ca_certificate' in ethernet['eapol']: @@ -167,8 +176,11 @@ def generate(ethernet): ca_cert_name = ethernet['eapol']['ca_certificate'] pki_ca_cert = ethernet['pki']['ca'][ca_cert_name] + loaded_ca_cert = load_certificate(pki_ca_cert['certificate']) + ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs) + write_file(ca_cert_file_path, - wrap_certificate(pki_ca_cert['certificate'])) + '\n'.join(encode_certificate(c) for c in ca_full_chain)) else: # delete configuration on interface removal if os.path.isfile(wpa_suppl_conf.format(**ethernet)): -- cgit v1.2.3 From 5fc9ef9e31eb566a601f8a150c69b183a4331564 Mon Sep 17 00:00:00 2001 From: fett0 Date: Fri, 18 Feb 2022 21:08:21 +0000 Subject: DHCP : T4258: Set correct port for dhcp-failover --- data/templates/dhcp-server/dhcpd.conf.tmpl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/templates/dhcp-server/dhcpd.conf.tmpl b/data/templates/dhcp-server/dhcpd.conf.tmpl index da2f28ced..dbd864b5e 100644 --- a/data/templates/dhcp-server/dhcpd.conf.tmpl +++ b/data/templates/dhcp-server/dhcpd.conf.tmpl @@ -42,9 +42,9 @@ failover peer "{{ failover.name }}" { secondary; {% endif %} address {{ failover.source_address }}; - port 520; + port 647; peer address {{ failover.remote }}; - peer port 520; + peer port 647; max-response-delay 30; max-unacked-updates 10; load balance max seconds 3; -- cgit v1.2.3 From 29ba813fb65b8b292105cdae4f8f71fcce6350a1 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 19 Feb 2022 13:09:17 +0100 Subject: smoketest: T4258: dhcp: bugfix failover ports Commit 5fc9ef9e ("DHCP : T4258: Set correct port for dhcp-failover") changed how the failover port is rendered into the ISC DHCPd configuration - adjustment of the smoketests was missed out. --- smoketest/scripts/cli/test_service_dhcp-server.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/smoketest/scripts/cli/test_service_dhcp-server.py b/smoketest/scripts/cli/test_service_dhcp-server.py index 14666db15..9adb9c042 100755 --- a/smoketest/scripts/cli/test_service_dhcp-server.py +++ b/smoketest/scripts/cli/test_service_dhcp-server.py @@ -461,12 +461,11 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): self.assertIn(f'mclt 1800;', config) self.assertIn(f'mclt 1800;', config) self.assertIn(f'split 128;', config) - self.assertIn(f'port 520;', config) - self.assertIn(f'peer port 520;', config) + self.assertIn(f'port 647;', config) + self.assertIn(f'peer port 647;', config) self.assertIn(f'max-response-delay 30;', config) self.assertIn(f'max-unacked-updates 10;', config) self.assertIn(f'load balance max seconds 3;', config) - self.assertIn(f'peer port 520;', config) self.assertIn(f'address {failover_local};', config) self.assertIn(f'peer address {failover_remote};', config) -- cgit v1.2.3 From f6c2b5e4762e7713c5868bebf8e482ce732e3302 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Thu, 17 Feb 2022 21:18:37 +0000 Subject: vpn: T4254: Add cisco_flexvpn and install_virtual_ip_on options Ability to set Cisco FlexVPN vendor ID payload: charon.cisco_flexvpn charon.install_virtual_ip_on swanctl.connections..vips = x.x.x.x, z.z.z.z set vpn ipsec options flexvpn set vpn ipsec options virtual-ip set vpn ipsec options interface tunX set vpn ipsec site-to-site peer x.x.x.x virtual-address x.x.x.x --- data/templates/ipsec/charon.tmpl | 11 +++++ data/templates/ipsec/swanctl/peer.tmpl | 3 ++ interface-definitions/vpn_ipsec.xml.in | 27 +++++++++++++ smoketest/scripts/cli/test_vpn_ipsec.py | 71 +++++++++++++++++++++++++++++++++ 4 files changed, 112 insertions(+) diff --git a/data/templates/ipsec/charon.tmpl b/data/templates/ipsec/charon.tmpl index 4d710921e..b9b020dcd 100644 --- a/data/templates/ipsec/charon.tmpl +++ b/data/templates/ipsec/charon.tmpl @@ -20,6 +20,17 @@ charon { # Send Cisco Unity vendor ID payload (IKEv1 only). # cisco_unity = no + # Cisco FlexVPN +{% if options is defined %} + cisco_flexvpn = {{ 'yes' if options.flexvpn is defined else 'no' }} +{% if options.virtual_ip is defined %} + install_virtual_ip = yes +{% endif %} +{% if options.interface is defined and options.interface is not none %} + install_virtual_ip_on = {{ options.interface }} +{% endif %} +{% endif %} + # Close the IKE_SA if setup of the CHILD_SA along with IKE_AUTH failed. # close_ike_on_child_failure = no diff --git a/data/templates/ipsec/swanctl/peer.tmpl b/data/templates/ipsec/swanctl/peer.tmpl index c6b71f2a1..f4e28d818 100644 --- a/data/templates/ipsec/swanctl/peer.tmpl +++ b/data/templates/ipsec/swanctl/peer.tmpl @@ -5,6 +5,9 @@ peer_{{ name }} { proposals = {{ ike | get_esp_ike_cipher | join(',') }} version = {{ ike.key_exchange[4:] if ike is defined and ike.key_exchange is defined else "0" }} +{% if peer_conf.virtual_address is defined and peer_conf.virtual_address is not none %} + vips = {{ peer_conf.virtual_address | join(', ') }} +{% endif %} local_addrs = {{ peer_conf.local_address if peer_conf.local_address != 'any' else '0.0.0.0/0' }} # dhcp:{{ peer_conf.dhcp_interface if 'dhcp_interface' in peer_conf else 'no' }} remote_addrs = {{ peer if peer not in ['any', '0.0.0.0'] and peer[0:1] != '@' else '0.0.0.0/0' }} {% if peer_conf.authentication is defined and peer_conf.authentication.mode is defined and peer_conf.authentication.mode == 'x509' %} diff --git a/interface-definitions/vpn_ipsec.xml.in b/interface-definitions/vpn_ipsec.xml.in index afa3d52a0..f7297a6e2 100644 --- a/interface-definitions/vpn_ipsec.xml.in +++ b/interface-definitions/vpn_ipsec.xml.in @@ -622,6 +622,19 @@ + + + Allow FlexVPN vendor ID payload (IKEv2 only) + + + + #include + + + Allow install virtual-ip addresses + + + @@ -1087,6 +1100,20 @@ + + + Initiator request virtual-address from peer + + ipv4 + Request IPv4 address from peer + + + ipv6 + Request IPv6 address from peer + + + + Virtual tunnel interface [REQUIRED] diff --git a/smoketest/scripts/cli/test_vpn_ipsec.py b/smoketest/scripts/cli/test_vpn_ipsec.py index 1433c7329..2c3e55a57 100755 --- a/smoketest/scripts/cli/test_vpn_ipsec.py +++ b/smoketest/scripts/cli/test_vpn_ipsec.py @@ -28,6 +28,7 @@ vti_path = ['interfaces', 'vti'] nhrp_path = ['protocols', 'nhrp'] base_path = ['vpn', 'ipsec'] +charon_file = '/etc/strongswan.d/charon.conf' dhcp_waiting_file = '/tmp/ipsec_dhcp_waiting' swanctl_file = '/etc/swanctl/swanctl.conf' @@ -416,5 +417,75 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): # There is only one VTI test so no need to delete this globally in tearDown() self.cli_delete(vti_path) + + def test_06_flex_vpn_vips(self): + local_address = '192.0.2.5' + local_id = 'vyos-r1' + remote_id = 'vyos-r2' + peer_base_path = base_path + ['site-to-site', 'peer', peer_ip] + + self.cli_set(tunnel_path + ['tun1', 'encapsulation', 'gre']) + self.cli_set(tunnel_path + ['tun1', 'source-address', local_address]) + + self.cli_set(base_path + ['interface', interface]) + self.cli_set(base_path + ['options', 'flexvpn']) + self.cli_set(base_path + ['options', 'interface', 'tun1']) + self.cli_set(base_path + ['ike-group', ike_group, 'ikev2-reauth', 'no']) + self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev2']) + + self.cli_set(peer_base_path + ['authentication', 'id', local_id]) + self.cli_set(peer_base_path + ['authentication', 'mode', 'pre-shared-secret']) + self.cli_set(peer_base_path + ['authentication', 'pre-shared-secret', secret]) + self.cli_set(peer_base_path + ['authentication', 'remote-id', remote_id]) + self.cli_set(peer_base_path + ['connection-type', 'initiate']) + self.cli_set(peer_base_path + ['ike-group', ike_group]) + self.cli_set(peer_base_path + ['default-esp-group', esp_group]) + self.cli_set(peer_base_path + ['local-address', local_address]) + self.cli_set(peer_base_path + ['tunnel', '1', 'protocol', 'gre']) + + self.cli_set(peer_base_path + ['virtual-address', '203.0.113.55']) + self.cli_set(peer_base_path + ['virtual-address', '203.0.113.56']) + + self.cli_commit() + + # Verify strongSwan configuration + swanctl_conf = read_file(swanctl_file) + swanctl_conf_lines = [ + f'version = 2', + f'vips = 203.0.113.55, 203.0.113.56', + f'life_time = 3600s', # default value + f'local_addrs = {local_address} # dhcp:no', + f'remote_addrs = {peer_ip}', + f'peer_{peer_ip.replace(".","-")}_tunnel_1', + f'mode = tunnel', + ] + + for line in swanctl_conf_lines: + self.assertIn(line, swanctl_conf) + + swanctl_secrets_lines = [ + f'id-local = {local_address} # dhcp:no', + f'id-remote = {peer_ip}', + f'id-localid = {local_id}', + f'id-remoteid = {remote_id}', + f'secret = "{secret}"', + ] + + for line in swanctl_secrets_lines: + self.assertIn(line, swanctl_conf) + + # Verify charon configuration + charon_conf = read_file(charon_file) + charon_conf_lines = [ + f'# Cisco FlexVPN', + f'cisco_flexvpn = yes', + f'install_virtual_ip = yes', + f'install_virtual_ip_on = tun1', + ] + + for line in charon_conf_lines: + self.assertIn(line, charon_conf) + + if __name__ == '__main__': unittest.main(verbosity=2) -- cgit v1.2.3 From cf36ced75094a519682875e0e73571824f34b6ec Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Sat, 19 Feb 2022 18:06:27 +0000 Subject: containers: T4249: Allow to connect host device to the container Ability to attach host devices to the container It can be disk, USB device or any device from the directory /dev set container name alp01 device disk source '/dev/vdb1' set container name alp01 device disk destination '/dev/mydisk' --- interface-definitions/containers.xml.in | 25 +++++++++++++++++++++++++ src/conf_mode/containers.py | 22 +++++++++++++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/interface-definitions/containers.xml.in b/interface-definitions/containers.xml.in index 30c7110b8..07686b16e 100644 --- a/interface-definitions/containers.xml.in +++ b/interface-definitions/containers.xml.in @@ -58,6 +58,31 @@ #include + + + Add a host device to the container + + + + + Source device (Example: "/dev/x") + + txt + Source device + + + + + + Destination container device (Example: "/dev/x") + + txt + Destination container device + + + + + #include diff --git a/src/conf_mode/containers.py b/src/conf_mode/containers.py index 26c50cab6..516671844 100755 --- a/src/conf_mode/containers.py +++ b/src/conf_mode/containers.py @@ -122,6 +122,18 @@ def verify(container): raise ConfigError(f'IP address "{address}" can not be used for a container, '\ 'reserved for the container engine!') + if 'device' in container_config: + for dev, dev_config in container_config['device'].items(): + if 'source' not in dev_config: + raise ConfigError(f'Device "{dev}" has no source path configured!') + + if 'destination' not in dev_config: + raise ConfigError(f'Device "{dev}" has no destination path configured!') + + source = dev_config['source'] + if not os.path.exists(source): + raise ConfigError(f'Device "{dev}" source path "{source}" does not exist!') + if 'environment' in container_config: for var, cfg in container_config['environment'].items(): if 'value' not in cfg: @@ -266,6 +278,14 @@ def apply(container): c = c.replace('-', '_') cap_add += f' --cap-add={c}' + # Add a host device to the container /dev/x:/dev/x + device = '' + if 'device' in container_config: + for dev, dev_config in container_config['device'].items(): + source_dev = dev_config['source'] + dest_dev = dev_config['destination'] + device += f' --device={source_dev}:{dest_dev}' + # Check/set environment options "-e foo=bar" env_opt = '' if 'environment' in container_config: @@ -296,7 +316,7 @@ def apply(container): container_base_cmd = f'podman run --detach --interactive --tty --replace {cap_add} ' \ f'--memory {memory}m --memory-swap 0 --restart {restart} ' \ - f'--name {name} {port} {volume} {env_opt}' + f'--name {name} {device} {port} {volume} {env_opt}' if 'allow_host_networks' in container_config: run(f'{container_base_cmd} --net host {image}') else: -- cgit v1.2.3 From 3a1a7c40a13ee9f5561823a79876d88d3f5bf053 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 20 Feb 2022 10:36:33 +0100 Subject: interface: T4203: prevent DHCP client restart if not necessary In the past whenever a change happened to any interface and it was configured as a DHCP client, VyOS always had a breif outage as DHCP released the old lease and re-aquired a new one - bad! This commit changes the behavior that DHCP client is only restarted if any one of the possible options one can set for DHCP client under the "dhcp-options" node is altered. --- python/vyos/configdict.py | 38 +++++++++++++++++++++++++++++++++++++- python/vyos/ifconfig/interface.py | 26 ++++++++++++++------------ 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index e7f515ea9..efeb6dc1f 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -423,6 +423,15 @@ def get_interface_dict(config, base, ifname=''): bond = is_member(config, ifname, 'bonding') if bond: dict.update({'is_bond_member' : bond}) + # Check if any DHCP options changed which require a client restat + for leaf_node in ['client-id', 'default-route-distance', 'host-name', + 'no-default-route', 'vendor-class-id']: + dhcp = leaf_node_changed(config, ['dhcp-options', leaf_node]) + if dhcp: + dict.update({'dhcp_options_old' : dhcp}) + # one option is suffiecient to set 'dhcp_options_old' key + break + # Some interfaces come with a source_interface which must also not be part # of any other bond or bridge interface as it is exclusivly assigned as the # Kernels "lower" interface to this new "virtual/upper" interface. @@ -470,6 +479,15 @@ def get_interface_dict(config, base, ifname=''): bridge = is_member(config, f'{ifname}.{vif}', 'bridge') if bridge: dict['vif'][vif].update({'is_bridge_member' : bridge}) + # Check if any DHCP options changed which require a client restat + for leaf_node in ['client-id', 'default-route-distance', 'host-name', + 'no-default-route', 'vendor-class-id']: + dhcp = leaf_node_changed(config, ['vif', vif, 'dhcp-options', leaf_node]) + if dhcp: + dict['vif'][vif].update({'dhcp_options_old' : dhcp}) + # one option is suffiecient to set 'dhcp_options_old' key + break + for vif_s, vif_s_config in dict.get('vif_s', {}).items(): default_vif_s_values = defaults(base + ['vif-s']) # XXX: T2665: we only wan't the vif-s defaults - do not care about vif-c @@ -495,6 +513,15 @@ def get_interface_dict(config, base, ifname=''): bridge = is_member(config, f'{ifname}.{vif_s}', 'bridge') if bridge: dict['vif_s'][vif_s].update({'is_bridge_member' : bridge}) + # Check if any DHCP options changed which require a client restat + for leaf_node in ['client-id', 'default-route-distance', 'host-name', + 'no-default-route', 'vendor-class-id']: + dhcp = leaf_node_changed(config, ['vif-s', vif_s, 'dhcp-options', leaf_node]) + if dhcp: + dict['vif_s'][vif_s].update({'dhcp_options_old' : dhcp}) + # one option is suffiecient to set 'dhcp_options_old' key + break + for vif_c, vif_c_config in vif_s_config.get('vif_c', {}).items(): default_vif_c_values = defaults(base + ['vif-s', 'vif-c']) @@ -521,6 +548,16 @@ def get_interface_dict(config, base, ifname=''): if bridge: dict['vif_s'][vif_s]['vif_c'][vif_c].update( {'is_bridge_member' : bridge}) + # Check if any DHCP options changed which require a client restat + for leaf_node in ['client-id', 'default-route-distance', 'host-name', + 'no-default-route', 'vendor-class-id']: + dhcp = leaf_node_changed(config, ['vif-s', vif_s, 'vif-c', vif_c, + 'dhcp-options', leaf_node]) + if dhcp: + dict['vif_s'][vif_s]['vif_c'][vif_c].update({'dhcp_options_old' : dhcp}) + # one option is suffiecient to set 'dhcp_options_old' key + break + # Check vif, vif-s/vif-c VLAN interfaces for removal dict = get_removed_vlans(config, dict) return dict @@ -545,7 +582,6 @@ def get_vlan_ids(interface): return vlan_ids - def get_accel_dict(config, base, chap_secrets): """ Common utility function to retrieve and mangle the Accel-PPP configuration diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 91c7f0c33..cf1887bf6 100755 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -1228,12 +1228,11 @@ class Interface(Control): options_file = f'{config_base}_{ifname}.options' pid_file = f'{config_base}_{ifname}.pid' lease_file = f'{config_base}_{ifname}.leases' - - # Stop client with old config files to get the right IF_METRIC. systemd_service = f'dhclient@{ifname}.service' - if is_systemd_service_active(systemd_service): - self._cmd(f'systemctl stop {systemd_service}') + # 'up' check is mandatory b/c even if the interface is A/D, as soon as + # the DHCP client is started the interface will be placed in u/u state. + # This is not what we intended to do when disabling an interface. if enable and 'disable' not in self._config: if dict_search('dhcp_options.host_name', self._config) == None: # read configured system hostname. @@ -1244,16 +1243,19 @@ class Interface(Control): tmp = {'dhcp_options' : { 'host_name' : hostname}} self._config = dict_merge(tmp, self._config) - render(options_file, 'dhcp-client/daemon-options.tmpl', - self._config) - render(config_file, 'dhcp-client/ipv4.tmpl', - self._config) + render(options_file, 'dhcp-client/daemon-options.tmpl', self._config) + render(config_file, 'dhcp-client/ipv4.tmpl', self._config) - # 'up' check is mandatory b/c even if the interface is A/D, as soon as - # the DHCP client is started the interface will be placed in u/u state. - # This is not what we intended to do when disabling an interface. - return self._cmd(f'systemctl restart {systemd_service}') + # When the DHCP client is restarted a brief outage will occur, as + # the old lease is released a new one is acquired (T4203). We will + # only restart DHCP client if it's option changed, or if it's not + # running, but it should be running (e.g. on system startup) + if 'dhcp_options_old' in self._config or not is_systemd_service_active(systemd_service): + return self._cmd(f'systemctl restart {systemd_service}') + return None else: + if is_systemd_service_active(systemd_service): + self._cmd(f'systemctl stop {systemd_service}') # cleanup old config files for file in [config_file, options_file, pid_file, lease_file]: if os.path.isfile(file): -- cgit v1.2.3 From b693f929b63c0c847d9a3c6ee9160845ef501be1 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 20 Feb 2022 10:40:38 +0100 Subject: static: T4203: obey interface dhcp default route distance Commit 05aa22dc ("protocols: static: T3680: do not delete DHCP received routes") added a bug whenever a static route is modified - the DHCP interface will always end up with metric 210 - if there was a default route over a DHCP interface. --- data/templates/frr/staticd.frr.tmpl | 4 +- .../include/interface/dhcp-options.xml.i | 3 +- python/vyos/configdict.py | 54 ++++++++++++++++++---- 3 files changed, 48 insertions(+), 13 deletions(-) diff --git a/data/templates/frr/staticd.frr.tmpl b/data/templates/frr/staticd.frr.tmpl index bfe959c1d..5d833228a 100644 --- a/data/templates/frr/staticd.frr.tmpl +++ b/data/templates/frr/staticd.frr.tmpl @@ -17,10 +17,10 @@ vrf {{ vrf }} {% endif %} {# IPv4 default routes from DHCP interfaces #} {% if dhcp is defined and dhcp is not none %} -{% for interface in dhcp %} +{% for interface, interface_config in dhcp.items() %} {% set next_hop = interface | get_dhcp_router %} {% if next_hop is defined and next_hop is not none %} -{{ ip_prefix }} route 0.0.0.0/0 {{ next_hop }} {{ interface }} tag 210 210 +{{ ip_prefix }} route 0.0.0.0/0 {{ next_hop }} {{ interface }} tag 210 {{ interface_config.distance }} {% endif %} {% endfor %} {% endif %} diff --git a/interface-definitions/include/interface/dhcp-options.xml.i b/interface-definitions/include/interface/dhcp-options.xml.i index b65b0802a..f62b06640 100644 --- a/interface-definitions/include/interface/dhcp-options.xml.i +++ b/interface-definitions/include/interface/dhcp-options.xml.i @@ -30,12 +30,13 @@ Distance for the default route from DHCP server u32:1-255 - Distance for the default route from DHCP server (default 210) + Distance for the default route from DHCP server (default: 210) + 210 diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index efeb6dc1f..f2ec93520 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -319,34 +319,42 @@ def is_source_interface(conf, interface, intftype=None): def get_dhcp_interfaces(conf, vrf=None): """ Common helper functions to retrieve all interfaces from current CLI sessions that have DHCP configured. """ - dhcp_interfaces = [] + dhcp_interfaces = {} dict = conf.get_config_dict(['interfaces'], get_first_key=True) if not dict: return dhcp_interfaces def check_dhcp(config, ifname): - out = [] + tmp = {} if 'address' in config and 'dhcp' in config['address']: + options = {} + if 'dhcp_options' in config and 'default_route_distance' in config['dhcp_options']: + options.update({'distance' : config['dhcp_options']['default_route_distance']}) if 'vrf' in config: - if vrf is config['vrf']: out.append(ifname) - else: out.append(ifname) - return out + if vrf is config['vrf']: tmp.update({ifname : options}) + else: tmp.update({ifname : options}) + return tmp for section, interface in dict.items(): - for ifname, ifconfig in interface.items(): + for ifname in interface: + # we already have a dict representation of the config from get_config_dict(), + # but with the extended information from get_interface_dict() we also + # get the DHCP client default-route-distance default option if not specified. + ifconfig = get_interface_dict(conf, ['interfaces', section], ifname) + tmp = check_dhcp(ifconfig, ifname) - dhcp_interfaces.extend(tmp) + dhcp_interfaces.update(tmp) # check per VLAN interfaces for vif, vif_config in ifconfig.get('vif', {}).items(): tmp = check_dhcp(vif_config, f'{ifname}.{vif}') - dhcp_interfaces.extend(tmp) + dhcp_interfaces.update(tmp) # check QinQ VLAN interfaces for vif_s, vif_s_config in ifconfig.get('vif-s', {}).items(): tmp = check_dhcp(vif_s_config, f'{ifname}.{vif_s}') - dhcp_interfaces.extend(tmp) + dhcp_interfaces.update(tmp) for vif_c, vif_c_config in vif_s_config.get('vif-c', {}).items(): tmp = check_dhcp(vif_c_config, f'{ifname}.{vif_s}.{vif_c}') - dhcp_interfaces.extend(tmp) + dhcp_interfaces.update(tmp) return dhcp_interfaces @@ -405,6 +413,12 @@ def get_interface_dict(config, base, ifname=''): if 'deleted' not in dict: dict = dict_merge(default_values, dict) + # If interface does not request an IPv4 DHCP address there is no need + # to keep the dhcp-options key + if 'address' not in dict or 'dhcp' not in dict['address']: + if 'dhcp_options' in dict: + del dict['dhcp_options'] + # XXX: T2665: blend in proper DHCPv6-PD default values dict = T2665_set_dhcpv6pd_defaults(dict) @@ -475,6 +489,12 @@ def get_interface_dict(config, base, ifname=''): # XXX: T2665: blend in proper DHCPv6-PD default values dict['vif'][vif] = T2665_set_dhcpv6pd_defaults(dict['vif'][vif]) + # If interface does not request an IPv4 DHCP address there is no need + # to keep the dhcp-options key + if 'address' not in dict['vif'][vif] or 'dhcp' not in dict['vif'][vif]['address']: + if 'dhcp_options' in dict['vif'][vif]: + del dict['vif'][vif]['dhcp_options'] + # Check if we are a member of a bridge device bridge = is_member(config, f'{ifname}.{vif}', 'bridge') if bridge: dict['vif'][vif].update({'is_bridge_member' : bridge}) @@ -509,6 +529,13 @@ def get_interface_dict(config, base, ifname=''): # XXX: T2665: blend in proper DHCPv6-PD default values dict['vif_s'][vif_s] = T2665_set_dhcpv6pd_defaults(dict['vif_s'][vif_s]) + # If interface does not request an IPv4 DHCP address there is no need + # to keep the dhcp-options key + if 'address' not in dict['vif_s'][vif_s] or 'dhcp' not in \ + dict['vif_s'][vif_s]['address']: + if 'dhcp_options' in dict['vif_s'][vif_s]: + del dict['vif_s'][vif_s]['dhcp_options'] + # Check if we are a member of a bridge device bridge = is_member(config, f'{ifname}.{vif_s}', 'bridge') if bridge: dict['vif_s'][vif_s].update({'is_bridge_member' : bridge}) @@ -543,6 +570,13 @@ def get_interface_dict(config, base, ifname=''): dict['vif_s'][vif_s]['vif_c'][vif_c] = T2665_set_dhcpv6pd_defaults( dict['vif_s'][vif_s]['vif_c'][vif_c]) + # If interface does not request an IPv4 DHCP address there is no need + # to keep the dhcp-options key + if 'address' not in dict['vif_s'][vif_s]['vif_c'][vif_c] or 'dhcp' \ + not in dict['vif_s'][vif_s]['vif_c'][vif_c]['address']: + if 'dhcp_options' in dict['vif_s'][vif_s]['vif_c'][vif_c]: + del dict['vif_s'][vif_s]['vif_c'][vif_c]['dhcp_options'] + # Check if we are a member of a bridge device bridge = is_member(config, f'{ifname}.{vif_s}.{vif_c}', 'bridge') if bridge: dict['vif_s'][vif_s]['vif_c'][vif_c].update( -- cgit v1.2.3 From 5d14a04b6ffbd592e8257d98d71da5acb1bb45a9 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 20 Feb 2022 18:50:29 +0100 Subject: smoketest: dhcp: T4203: move testcase to base class We do not only provide DHCP functionality to ethernet interfaces, it's a common feature so the testcase should be made available for multiple interface types. --- smoketest/scripts/cli/base_interfaces_test.py | 28 +++++++++++++++++++++- smoketest/scripts/cli/test_interfaces_bonding.py | 3 ++- smoketest/scripts/cli/test_interfaces_bridge.py | 3 ++- smoketest/scripts/cli/test_interfaces_ethernet.py | 21 ++-------------- smoketest/scripts/cli/test_interfaces_macsec.py | 3 ++- .../scripts/cli/test_interfaces_pseudo_ethernet.py | 3 ++- 6 files changed, 37 insertions(+), 24 deletions(-) diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py index 9de961249..013c78837 100644 --- a/smoketest/scripts/cli/base_interfaces_test.py +++ b/smoketest/scripts/cli/base_interfaces_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2021 VyOS maintainers and contributors +# Copyright (C) 2019-2022 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 @@ -56,6 +56,7 @@ def is_mirrored_to(interface, mirror_if, qdisc): class BasicInterfaceTest: class TestCase(VyOSUnitTestSHIM.TestCase): + _test_dhcp = False _test_ip = False _test_mtu = False _test_vlan = False @@ -96,6 +97,31 @@ class BasicInterfaceTest: for intf in self._interfaces: self.assertNotIn(intf, interfaces()) + # No daemon that was started during a test should remain running + for daemon in ['dhcp6c', 'dhclient']: + self.assertFalse(process_named_running(daemon)) + + def test_dhcp_disable_interface(self): + if not self._test_dhcp: + self.skipTest('not supported') + + # When interface is configured as admin down, it must be admin down + # even when dhcpc starts on the given interface + for interface in self._interfaces: + self.cli_set(self._base_path + [interface, 'disable']) + + # Also enable DHCP (ISC DHCP always places interface in admin up + # state so we check that we do not start DHCP client. + # https://phabricator.vyos.net/T2767 + self.cli_set(self._base_path + [interface, 'address', 'dhcp']) + + self.cli_commit() + + # Validate interface state + for interface in self._interfaces: + flags = read_file(f'/sys/class/net/{interface}/flags') + self.assertEqual(int(flags, 16) & 1, 0) + def test_span_mirror(self): if not self._mirror_interfaces: self.skipTest('not supported') diff --git a/smoketest/scripts/cli/test_interfaces_bonding.py b/smoketest/scripts/cli/test_interfaces_bonding.py index 1d9a887bd..4f2fe979a 100755 --- a/smoketest/scripts/cli/test_interfaces_bonding.py +++ b/smoketest/scripts/cli/test_interfaces_bonding.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-2022 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 @@ -28,6 +28,7 @@ from vyos.util import read_file class BondingInterfaceTest(BasicInterfaceTest.TestCase): @classmethod def setUpClass(cls): + cls._test_dhcp = True cls._test_ip = True cls._test_ipv6 = True cls._test_ipv6_pd = True diff --git a/smoketest/scripts/cli/test_interfaces_bridge.py b/smoketest/scripts/cli/test_interfaces_bridge.py index 4f7e03298..f2e111425 100755 --- a/smoketest/scripts/cli/test_interfaces_bridge.py +++ b/smoketest/scripts/cli/test_interfaces_bridge.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2021 VyOS maintainers and contributors +# Copyright (C) 2020-2022 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 @@ -31,6 +31,7 @@ from vyos.validate import is_intf_addr_assigned class BridgeInterfaceTest(BasicInterfaceTest.TestCase): @classmethod def setUpClass(cls): + cls._test_dhcp = True cls._test_ip = True cls._test_ipv6 = True cls._test_ipv6_pd = True diff --git a/smoketest/scripts/cli/test_interfaces_ethernet.py b/smoketest/scripts/cli/test_interfaces_ethernet.py index ae3d5ed96..ee7649af8 100755 --- a/smoketest/scripts/cli/test_interfaces_ethernet.py +++ b/smoketest/scripts/cli/test_interfaces_ethernet.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2021 VyOS maintainers and contributors +# Copyright (C) 2020-2022 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 @@ -102,6 +102,7 @@ def get_certificate_count(interface, cert_type): class EthernetInterfaceTest(BasicInterfaceTest.TestCase): @classmethod def setUpClass(cls): + cls._test_dhcp = True cls._test_ip = True cls._test_ipv6 = True cls._test_ipv6_pd = True @@ -146,24 +147,6 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase): self.cli_commit() - def test_dhcp_disable_interface(self): - # When interface is configured as admin down, it must be admin down - # even when dhcpc starts on the given interface - for interface in self._interfaces: - self.cli_set(self._base_path + [interface, 'disable']) - - # Also enable DHCP (ISC DHCP always places interface in admin up - # state so we check that we do not start DHCP client. - # https://phabricator.vyos.net/T2767 - self.cli_set(self._base_path + [interface, 'address', 'dhcp']) - - self.cli_commit() - - # Validate interface state - for interface in self._interfaces: - flags = read_file(f'/sys/class/net/{interface}/flags') - self.assertEqual(int(flags, 16) & 1, 0) - def test_offloading_rps(self): # enable RPS on all available CPUs, RPS works woth a CPU bitmask, # where each bit represents a CPU (core/thread). The formula below diff --git a/smoketest/scripts/cli/test_interfaces_macsec.py b/smoketest/scripts/cli/test_interfaces_macsec.py index e4280a5b7..5b10bfa44 100755 --- a/smoketest/scripts/cli/test_interfaces_macsec.py +++ b/smoketest/scripts/cli/test_interfaces_macsec.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2021 VyOS maintainers and contributors +# Copyright (C) 2020-2022 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 @@ -40,6 +40,7 @@ def get_cipher(interface): class MACsecInterfaceTest(BasicInterfaceTest.TestCase): @classmethod def setUpClass(cls): + cls._test_dhcp = True cls._test_ip = True cls._test_ipv6 = True cls._base_path = ['interfaces', 'macsec'] diff --git a/smoketest/scripts/cli/test_interfaces_pseudo_ethernet.py b/smoketest/scripts/cli/test_interfaces_pseudo_ethernet.py index ae899cddd..adcadc5eb 100755 --- a/smoketest/scripts/cli/test_interfaces_pseudo_ethernet.py +++ b/smoketest/scripts/cli/test_interfaces_pseudo_ethernet.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2021 VyOS maintainers and contributors +# Copyright (C) 2020-2022 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 @@ -23,6 +23,7 @@ from base_interfaces_test import BasicInterfaceTest class PEthInterfaceTest(BasicInterfaceTest.TestCase): @classmethod def setUpClass(cls): + cls._test_dhcp = True cls._test_ip = True cls._test_ipv6 = True cls._test_ipv6_pd = True -- cgit v1.2.3 From 6bf5a0b0dd489a480dce6030e1c61d29e77fa107 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Sun, 20 Feb 2022 18:30:44 +0000 Subject: ipsec: T1856: Ability to set SA life bytes and packets set vpn ipsec esp-group grp-ESP life-bytes '100000' set vpn ipsec esp-group grp-ESP life-packets '2000000' --- data/templates/ipsec/swanctl/peer.tmpl | 12 ++++++++++++ interface-definitions/vpn_ipsec.xml.in | 24 ++++++++++++++++++++++++ smoketest/scripts/cli/test_vpn_ipsec.py | 7 +++++++ 3 files changed, 43 insertions(+) diff --git a/data/templates/ipsec/swanctl/peer.tmpl b/data/templates/ipsec/swanctl/peer.tmpl index c6b71f2a1..481ea7224 100644 --- a/data/templates/ipsec/swanctl/peer.tmpl +++ b/data/templates/ipsec/swanctl/peer.tmpl @@ -57,6 +57,12 @@ {% set vti_esp = esp_group[ peer_conf.vti.esp_group ] if peer_conf.vti.esp_group is defined else esp_group[ peer_conf.default_esp_group ] %} peer_{{ name }}_vti { esp_proposals = {{ vti_esp | get_esp_ike_cipher(ike) | join(',') }} +{% if vti_esp.life_bytes is defined and vti_esp.life_bytes is not none %} + life_bytes = {{ vti_esp.life_bytes }} +{% endif %} +{% if vti_esp.life_packets is defined and vti_esp.life_packets is not none %} + life_packets = {{ vti_esp.life_packets }} +{% endif %} life_time = {{ vti_esp.lifetime }}s local_ts = 0.0.0.0/0,::/0 remote_ts = 0.0.0.0/0,::/0 @@ -91,6 +97,12 @@ {% set remote_suffix = '[{0}/{1}]'.format(proto, remote_port) if proto or remote_port else '' %} peer_{{ name }}_tunnel_{{ tunnel_id }} { esp_proposals = {{ tunnel_esp | get_esp_ike_cipher(ike) | join(',') }} +{% if tunnel_esp.life_bytes is defined and tunnel_esp.life_bytes is not none %} + life_bytes = {{ tunnel_esp.life_bytes }} +{% endif %} +{% if tunnel_esp.life_packets is defined and tunnel_esp.life_packets is not none %} + life_packets = {{ tunnel_esp.life_packets }} +{% endif %} life_time = {{ tunnel_esp.lifetime }}s {% if tunnel_esp.mode is not defined or tunnel_esp.mode == 'tunnel' %} {% if tunnel_conf.local is defined and tunnel_conf.local.prefix is defined %} diff --git a/interface-definitions/vpn_ipsec.xml.in b/interface-definitions/vpn_ipsec.xml.in index afa3d52a0..af92eec31 100644 --- a/interface-definitions/vpn_ipsec.xml.in +++ b/interface-definitions/vpn_ipsec.xml.in @@ -55,6 +55,30 @@ 3600 + + + ESP life in bytes + + u32:1024-26843545600000 + ESP life in bytes + + + + + + + + + ESP life in packets + + u32:1000-26843545600000 + ESP life in packets + + + + + + ESP mode diff --git a/smoketest/scripts/cli/test_vpn_ipsec.py b/smoketest/scripts/cli/test_vpn_ipsec.py index 1433c7329..14079c905 100755 --- a/smoketest/scripts/cli/test_vpn_ipsec.py +++ b/smoketest/scripts/cli/test_vpn_ipsec.py @@ -171,8 +171,13 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): # Site to site local_address = '192.0.2.10' priority = '20' + life_bytes = '100000' + life_packets = '2000000' peer_base_path = base_path + ['site-to-site', 'peer', peer_ip] + self.cli_set(base_path + ['esp-group', esp_group, 'life-bytes', life_bytes]) + self.cli_set(base_path + ['esp-group', esp_group, 'life-packets', life_packets]) + self.cli_set(peer_base_path + ['authentication', 'mode', 'pre-shared-secret']) self.cli_set(peer_base_path + ['authentication', 'pre-shared-secret', secret]) self.cli_set(peer_base_path + ['ike-group', ike_group]) @@ -197,6 +202,8 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): swanctl_conf_lines = [ f'version = 2', f'auth = psk', + f'life_bytes = {life_bytes}', + f'life_packets = {life_packets}', f'rekey_time = 28800s', # default value f'proposals = aes128-sha1-modp1024', f'esp_proposals = aes128-sha1-modp1024', -- cgit v1.2.3 From 5ae566086c5c190d52b15f64454abcae9c8a1d46 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 20 Feb 2022 20:07:34 +0100 Subject: smoketest: dhcp: T4203: set missing interface options if present Commit 5d14a04b ("smoketest: dhcp: T4203: move testcase to base class") added global support in the test case framework for DHCP tests. Some interfaces (e.g. MACsec) require additional options to be passed before the test can be launched. In the MACsec case this includes a source interface, or encryption ciphers. --- smoketest/scripts/cli/base_interfaces_test.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py index 013c78837..ba5acf5d6 100644 --- a/smoketest/scripts/cli/base_interfaces_test.py +++ b/smoketest/scripts/cli/base_interfaces_test.py @@ -109,6 +109,10 @@ class BasicInterfaceTest: # even when dhcpc starts on the given interface for interface in self._interfaces: self.cli_set(self._base_path + [interface, 'disable']) + for option in self._options.get(interface, []): + self.cli_set(self._base_path + [interface] + option.split()) + + self.cli_set(self._base_path + [interface, 'disable']) # Also enable DHCP (ISC DHCP always places interface in admin up # state so we check that we do not start DHCP client. -- cgit v1.2.3 From 529af7898d062b42ac33e15bfdc62c14184e098f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 20 Feb 2022 20:18:02 +0100 Subject: macsec: T4261: add dhcp client support --- interface-definitions/interfaces-macsec.xml.in | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/interface-definitions/interfaces-macsec.xml.in b/interface-definitions/interfaces-macsec.xml.in index d69a093af..598935e51 100644 --- a/interface-definitions/interfaces-macsec.xml.in +++ b/interface-definitions/interfaces-macsec.xml.in @@ -16,7 +16,9 @@ - #include + #include + #include + #include #include #include #include -- cgit v1.2.3 From f23040a0f7d425550350f91410272196f842308e Mon Sep 17 00:00:00 2001 From: srividya0208 Date: Sun, 20 Feb 2022 15:06:21 -0500 Subject: T4115:Reboot:Options "in" and "at" are not working When reboot is executed with "in" option it only accepts minutes till 99 value and does not accept greater values and "at" is also working same like in option where as it should work with exact timings. --- op-mode-definitions/reboot.xml.in | 4 ++-- src/op_mode/powerctrl.py | 25 +++++++++++++++++++++---- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/op-mode-definitions/reboot.xml.in b/op-mode-definitions/reboot.xml.in index 2c8daec5d..6414742d9 100644 --- a/op-mode-definitions/reboot.xml.in +++ b/op-mode-definitions/reboot.xml.in @@ -25,7 +25,7 @@ <Minutes> - sudo ${vyos_op_scripts_dir}/powerctrl.py --yes --reboot $3 $4 + sudo ${vyos_op_scripts_dir}/powerctrl.py --yes --reboot_in $3 $4 @@ -40,7 +40,7 @@ Reboot at a specific date - <DDMMYYYY> <DD/MM/YYYY> <DD.MM.YYYY> <DD:MM:YYYY> + <DD/MM/YYYY> <DD.MM.YYYY> <DD:MM:YYYY> sudo ${vyos_op_scripts_dir}/powerctrl.py --yes --reboot $3 $5 diff --git a/src/op_mode/powerctrl.py b/src/op_mode/powerctrl.py index 679b03c0b..fd4f86d88 100755 --- a/src/op_mode/powerctrl.py +++ b/src/op_mode/powerctrl.py @@ -33,10 +33,12 @@ def utc2local(datetime): def parse_time(s): try: - if re.match(r'^\d{1,2}$', s): - if (int(s) > 59): + if re.match(r'^\d{1,9999}$', s): + if (int(s) > 59) and (int(s) < 1440): s = str(int(s)//60) + ":" + str(int(s)%60) return datetime.strptime(s, "%H:%M").time() + if (int(s) >= 1440): + return s.split() else: return datetime.strptime(s, "%M").time() else: @@ -141,7 +143,7 @@ def execute_shutdown(time, reboot=True, ask=True): cmd(f'/usr/bin/wall "{wall_msg}"') else: if not ts: - exit(f'Invalid time "{time[0]}". The valid format is HH:MM') + exit(f'Invalid time "{time[0]}". Uses 24 Hour Clock format') else: exit(f'Invalid date "{time[1]}". A valid format is YYYY-MM-DD [HH:MM]') else: @@ -172,7 +174,12 @@ def main(): action.add_argument("--reboot", "-r", help="Reboot the system", nargs="*", - metavar="Minutes|HH:MM") + metavar="HH:MM") + + action.add_argument("--reboot_in", "-i", + help="Reboot the system", + nargs="*", + metavar="Minutes") action.add_argument("--poweroff", "-p", help="Poweroff the system", @@ -190,7 +197,17 @@ def main(): try: if args.reboot is not None: + for r in args.reboot: + if ':' not in r and '/' not in r and '.' not in r: + print("Incorrect format! Use HH:MM") + exit(1) execute_shutdown(args.reboot, reboot=True, ask=args.yes) + if args.reboot_in is not None: + for i in args.reboot_in: + if ':' in i: + print("Incorrect format! Use Minutes") + exit(1) + execute_shutdown(args.reboot_in, reboot=True, ask=args.yes) if args.poweroff is not None: execute_shutdown(args.poweroff, reboot=False, ask=args.yes) if args.cancel: -- cgit v1.2.3 From 4ec6262629393bd8a88951970c367a5cc3d57a42 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Sun, 20 Feb 2022 20:32:06 +0000 Subject: ipsec: T3948: Add CLI site-to-site peer connection-type none set vpn ipsec site-to-site peer 192.0.2.14 connection-type none --- data/templates/ipsec/swanctl/peer.tmpl | 4 ++++ interface-definitions/vpn_ipsec.xml.in | 8 ++++++-- smoketest/scripts/cli/test_vpn_ipsec.py | 2 ++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/data/templates/ipsec/swanctl/peer.tmpl b/data/templates/ipsec/swanctl/peer.tmpl index f4e28d818..673dc3375 100644 --- a/data/templates/ipsec/swanctl/peer.tmpl +++ b/data/templates/ipsec/swanctl/peer.tmpl @@ -77,6 +77,8 @@ start_action = start {% elif peer_conf.connection_type == 'respond' %} start_action = trap +{% elif peer_conf.connection_type == 'none' %} + start_action = none {% endif %} {% if ike.dead_peer_detection is defined %} {% set dpd_translate = {'clear': 'clear', 'hold': 'trap', 'restart': 'start'} %} @@ -119,6 +121,8 @@ start_action = start {% elif peer_conf.connection_type == 'respond' %} start_action = trap +{% elif peer_conf.connection_type == 'none' %} + start_action = none {% endif %} {% if ike.dead_peer_detection is defined %} {% set dpd_translate = {'clear': 'clear', 'hold': 'trap', 'restart': 'start'} %} diff --git a/interface-definitions/vpn_ipsec.xml.in b/interface-definitions/vpn_ipsec.xml.in index f7297a6e2..7b5074112 100644 --- a/interface-definitions/vpn_ipsec.xml.in +++ b/interface-definitions/vpn_ipsec.xml.in @@ -978,7 +978,7 @@ Connection type - initiate respond + initiate respond none initiate @@ -988,8 +988,12 @@ respond Bring the connection up only if traffic is detected + + none + Load the connection only + - ^(initiate|respond)$ + ^(initiate|respond|none)$ diff --git a/smoketest/scripts/cli/test_vpn_ipsec.py b/smoketest/scripts/cli/test_vpn_ipsec.py index 2c3e55a57..699d854bb 100755 --- a/smoketest/scripts/cli/test_vpn_ipsec.py +++ b/smoketest/scripts/cli/test_vpn_ipsec.py @@ -238,6 +238,7 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): peer_base_path = base_path + ['site-to-site', 'peer', peer_ip] self.cli_set(peer_base_path + ['authentication', 'mode', 'pre-shared-secret']) self.cli_set(peer_base_path + ['authentication', 'pre-shared-secret', secret]) + self.cli_set(peer_base_path + ['connection-type', 'none']) self.cli_set(peer_base_path + ['ike-group', ike_group]) self.cli_set(peer_base_path + ['default-esp-group', esp_group]) self.cli_set(peer_base_path + ['local-address', local_address]) @@ -266,6 +267,7 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): f'mode = tunnel', f'local_ts = 172.16.10.0/24,172.16.11.0/24', f'remote_ts = 172.17.10.0/24,172.17.11.0/24', + f'start_action = none', f'if_id_in = {if_id}', # will be 11 for vti10 - shifted by one f'if_id_out = {if_id}', f'updown = "/etc/ipsec.d/vti-up-down {vti}"' -- cgit v1.2.3 From 0ecddff7cffa8900d351d5c15e32420f9d780c0b Mon Sep 17 00:00:00 2001 From: Andreas Date: Wed, 29 Dec 2021 18:02:06 +0100 Subject: vxlan: T4120: add ability to set multiple remotes (PR #1127) VXLAN does support using multiple remotes but VyOS does not. Add the ability to set multiple remotes and add their flood lists using "bridge" command. --- .../include/interface/tunnel-remote.xml.i | 2 +- .../include/interface/tunnel-remotes.xml.i | 19 ++++++++++++ interface-definitions/interfaces-vxlan.xml.in | 2 +- python/vyos/ifconfig/vxlan.py | 7 +++++ smoketest/scripts/cli/test_interfaces_vxlan.py | 2 ++ src/conf_mode/interfaces-vxlan.py | 34 ++++++++++++++++++++++ 6 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 interface-definitions/include/interface/tunnel-remotes.xml.i diff --git a/interface-definitions/include/interface/tunnel-remote.xml.i b/interface-definitions/include/interface/tunnel-remote.xml.i index 1ba9b0382..2a8891b85 100644 --- a/interface-definitions/include/interface/tunnel-remote.xml.i +++ b/interface-definitions/include/interface/tunnel-remote.xml.i @@ -1,4 +1,4 @@ - + Tunnel remote address diff --git a/interface-definitions/include/interface/tunnel-remotes.xml.i b/interface-definitions/include/interface/tunnel-remotes.xml.i new file mode 100644 index 000000000..ae8481898 --- /dev/null +++ b/interface-definitions/include/interface/tunnel-remotes.xml.i @@ -0,0 +1,19 @@ + + + + Tunnel remote address + + ipv4 + Tunnel remote IPv4 address + + + ipv6 + Tunnel remote IPv6 address + + + + + + + + diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in index 4c3c3ac71..559067ea5 100644 --- a/interface-definitions/interfaces-vxlan.xml.in +++ b/interface-definitions/interfaces-vxlan.xml.in @@ -98,7 +98,7 @@ #include #include - #include + #include #include #include diff --git a/python/vyos/ifconfig/vxlan.py b/python/vyos/ifconfig/vxlan.py index 0c5282db4..87b5e40b8 100644 --- a/python/vyos/ifconfig/vxlan.py +++ b/python/vyos/ifconfig/vxlan.py @@ -82,3 +82,10 @@ class VXLANIf(Interface): self._cmd(cmd.format(**self.config)) # interface is always A/D down. It needs to be enabled explicitly self.set_admin_state('down') + + other_remotes = self.config.get('other_remotes') + if other_remotes: + for rem in other_remotes: + self.config['rem'] = rem + cmd2 = 'bridge fdb append to 00:00:00:00:00:00 dst {rem} port {port} dev {ifname}' + self._cmd(cmd2.format(**self.config)) diff --git a/smoketest/scripts/cli/test_interfaces_vxlan.py b/smoketest/scripts/cli/test_interfaces_vxlan.py index 9278adadd..12fc463ba 100755 --- a/smoketest/scripts/cli/test_interfaces_vxlan.py +++ b/smoketest/scripts/cli/test_interfaces_vxlan.py @@ -33,6 +33,8 @@ class VXLANInterfaceTest(BasicInterfaceTest.TestCase): 'vxlan10': ['vni 10', 'remote 127.0.0.2'], 'vxlan20': ['vni 20', 'group 239.1.1.1', 'source-interface eth0'], 'vxlan30': ['vni 30', 'remote 2001:db8:2000::1', 'source-address 2001:db8:1000::1', 'parameters ipv6 flowlabel 0x1000'], + 'vxlan40': ['vni 40', 'remote 127.0.0.2', 'remote 127.0.0.3'], + 'vxlan50': ['vni 50', 'remote 2001:db8:2000::1', 'remote 2001:db8:2000::2', 'parameters ipv6 flowlabel 0x1000'], } cls._interfaces = list(cls._options) # call base-classes classmethod diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index 1f097c4e3..092f249df 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -58,6 +58,13 @@ def get_config(config=None): if len(vxlan['other_tunnels']) == 0: del vxlan['other_tunnels'] + # leave first remote in dict and put the other ones (if they exists) to "other_remotes" + remotes = vxlan.get('remote') + if remotes: + vxlan['remote'] = remotes[0] + if len(remotes) > 1: + del remotes[0] + vxlan['other_remotes'] = remotes return vxlan def verify(vxlan): @@ -108,6 +115,33 @@ def verify(vxlan): raise ConfigError(f'Underlaying device MTU is to small ({lower_mtu} '\ f'bytes) for VXLAN overhead ({vxlan_overhead} bytes!)') + # Check for mixed IPv4 and IPv6 addresses + protocol = None + if 'source_address' in vxlan: + if is_ipv6(vxlan['source_address']): + protocol = 'ipv6' + else: + protocol = 'ipv4' + if 'remote' in vxlan: + if is_ipv6(vxlan['remote']): + if protocol == 'ipv4': + raise ConfigError('IPv4 and IPV6 cannot be mixed') + protocol = 'ipv6' + else: + if protocol == 'ipv6': + raise ConfigError('IPv4 and IPV6 cannot be mixed') + protocol = 'ipv4' + if 'other_remotes' in vxlan: + for rem in vxlan['other_remotes']: + if is_ipv6(rem): + if protocol == 'ipv4': + raise ConfigError('IPv4 and IPV6 cannot be mixed') + protocol = 'ipv6' + else: + if protocol == 'ipv6': + raise ConfigError('IPv4 and IPV6 cannot be mixed') + protocol = 'ipv4' + verify_mtu_ipv6(vxlan) verify_address(vxlan) return None -- cgit v1.2.3 From d418cd36027aef5993122ec62419e8c66fe7a1ed Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 20 Feb 2022 22:06:49 +0100 Subject: vxlan: T4120: rename tunnel-remotes.xml.i -> tunnel-remote-multi.xml.i --- .../include/interface/tunnel-remote-multi.xml.i | 19 +++++++++++++++++++ .../include/interface/tunnel-remotes.xml.i | 19 ------------------- interface-definitions/interfaces-vxlan.xml.in | 2 +- 3 files changed, 20 insertions(+), 20 deletions(-) create mode 100644 interface-definitions/include/interface/tunnel-remote-multi.xml.i delete mode 100644 interface-definitions/include/interface/tunnel-remotes.xml.i diff --git a/interface-definitions/include/interface/tunnel-remote-multi.xml.i b/interface-definitions/include/interface/tunnel-remote-multi.xml.i new file mode 100644 index 000000000..f672087a4 --- /dev/null +++ b/interface-definitions/include/interface/tunnel-remote-multi.xml.i @@ -0,0 +1,19 @@ + + + + Tunnel remote address + + ipv4 + Tunnel remote IPv4 address + + + ipv6 + Tunnel remote IPv6 address + + + + + + + + diff --git a/interface-definitions/include/interface/tunnel-remotes.xml.i b/interface-definitions/include/interface/tunnel-remotes.xml.i deleted file mode 100644 index ae8481898..000000000 --- a/interface-definitions/include/interface/tunnel-remotes.xml.i +++ /dev/null @@ -1,19 +0,0 @@ - - - - Tunnel remote address - - ipv4 - Tunnel remote IPv4 address - - - ipv6 - Tunnel remote IPv6 address - - - - - - - - diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in index 559067ea5..0546b4199 100644 --- a/interface-definitions/interfaces-vxlan.xml.in +++ b/interface-definitions/interfaces-vxlan.xml.in @@ -98,7 +98,7 @@ #include #include - #include + #include #include #include -- cgit v1.2.3 From 25b2f2a8057260ad0d2c59823618d7c9f0fba707 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 20 Feb 2022 22:10:37 +0100 Subject: bridge: remove unreferenced import -> leaf_node_changed --- src/conf_mode/interfaces-bridge.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py index 4d3ebc587..f4dba9d4a 100755 --- a/src/conf_mode/interfaces-bridge.py +++ b/src/conf_mode/interfaces-bridge.py @@ -22,7 +22,6 @@ from netifaces import interfaces from vyos.config import Config from vyos.configdict import get_interface_dict from vyos.configdict import node_changed -from vyos.configdict import leaf_node_changed from vyos.configdict import is_member from vyos.configdict import is_source_interface from vyos.configdict import has_vlan_subinterface_configured -- cgit v1.2.3 From 9b7d0744409b4fc9433a1dc1e83e8c984567a2d0 Mon Sep 17 00:00:00 2001 From: srividya0208 Date: Mon, 21 Feb 2022 09:20:23 -0500 Subject: vpn_ipsec: T3656: modified completion help for key-exchange In latest releases, default IKE version is removed, which allows the connection to be IKEv1 or IKEv2. The completion help shows IKEv1 as default so removed it. --- interface-definitions/vpn_ipsec.xml.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface-definitions/vpn_ipsec.xml.in b/interface-definitions/vpn_ipsec.xml.in index f7297a6e2..5ec7480cc 100644 --- a/interface-definitions/vpn_ipsec.xml.in +++ b/interface-definitions/vpn_ipsec.xml.in @@ -311,7 +311,7 @@ ikev1 - Use IKEv1 for key exchange [DEFAULT] + Use IKEv1 for key exchange ikev2 -- cgit v1.2.3 From a3b7e985911eeaccac4fa229563b78c5a64e7e90 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Mon, 21 Feb 2022 08:06:40 -0500 Subject: T2719: initial batch of standardized structure op mode scripts --- src/op_mode/cpu_summary.py | 36 +++++++++++++++++--------- src/op_mode/show_cpu.py | 63 ++++++++++++++++++++++++++------------------- src/op_mode/show_ram.py | 19 +++++++++----- src/op_mode/show_uptime.py | 27 ++++++++++++++----- src/op_mode/show_version.py | 22 +++++++++++----- 5 files changed, 109 insertions(+), 58 deletions(-) diff --git a/src/op_mode/cpu_summary.py b/src/op_mode/cpu_summary.py index cfd321522..3bdf5a718 100755 --- a/src/op_mode/cpu_summary.py +++ b/src/op_mode/cpu_summary.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018 VyOS maintainers and contributors +# Copyright (C) 2018-2022 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -19,18 +19,30 @@ from vyos.util import colon_separated_to_dict FILE_NAME = '/proc/cpuinfo' -with open(FILE_NAME, 'r') as f: - data_raw = f.read() +def get_raw_data(): + with open(FILE_NAME, 'r') as f: + data_raw = f.read() -data = colon_separated_to_dict(data_raw) + data = colon_separated_to_dict(data_raw) -# Accumulate all data in a dict for future support for machine-readable output -cpu_data = {} -cpu_data['cpu_number'] = len(data['processor']) -cpu_data['models'] = list(set(data['model name'])) + # Accumulate all data in a dict for future support for machine-readable output + cpu_data = {} + cpu_data['cpu_number'] = len(data['processor']) + cpu_data['models'] = list(set(data['model name'])) -# Strip extra whitespace from CPU model names, /proc/cpuinfo is prone to that -cpu_data['models'] = map(lambda s: re.sub(r'\s+', ' ', s), cpu_data['models']) + # Strip extra whitespace from CPU model names, /proc/cpuinfo is prone to that + cpu_data['models'] = list(map(lambda s: re.sub(r'\s+', ' ', s), cpu_data['models'])) + + return cpu_data + +def get_formatted_output(): + cpu_data = get_raw_data() + + out = "CPU(s): {0}\n".format(cpu_data['cpu_number']) + out += "CPU model(s): {0}".format(",".join(cpu_data['models'])) + + return out + +if __name__ == '__main__': + print(get_formatted_output()) -print("CPU(s): {0}".format(cpu_data['cpu_number'])) -print("CPU model(s): {0}".format(",".join(cpu_data['models']))) diff --git a/src/op_mode/show_cpu.py b/src/op_mode/show_cpu.py index 0040e950d..9973d9789 100755 --- a/src/op_mode/show_cpu.py +++ b/src/op_mode/show_cpu.py @@ -21,7 +21,7 @@ from sys import exit from vyos.util import popen, DEVNULL OUT_TMPL_SRC = """ -{% if cpu %} +{%- if cpu -%} {% if 'vendor' in cpu %}CPU Vendor: {{cpu.vendor}}{% endif %} {% if 'model' in cpu %}Model: {{cpu.model}}{% endif %} {% if 'cpus' in cpu %}Total CPUs: {{cpu.cpus}}{% endif %} @@ -31,31 +31,42 @@ OUT_TMPL_SRC = """ {% if 'mhz' in cpu %}Current MHz: {{cpu.mhz}}{% endif %} {% if 'mhz_min' in cpu %}Minimum MHz: {{cpu.mhz_min}}{% endif %} {% if 'mhz_max' in cpu %}Maximum MHz: {{cpu.mhz_max}}{% endif %} -{% endif %} +{%- endif -%} """ -cpu = {} -cpu_json, code = popen('lscpu -J', stderr=DEVNULL) - -if code == 0: - cpu_info = json.loads(cpu_json) - if len(cpu_info) > 0 and 'lscpu' in cpu_info: - for prop in cpu_info['lscpu']: - if (prop['field'].find('Thread(s)') > -1): cpu['threads'] = prop['data'] - if (prop['field'].find('Core(s)')) > -1: cpu['cores'] = prop['data'] - if (prop['field'].find('Socket(s)')) > -1: cpu['sockets'] = prop['data'] - if (prop['field'].find('CPU(s):')) > -1: cpu['cpus'] = prop['data'] - if (prop['field'].find('CPU MHz')) > -1: cpu['mhz'] = prop['data'] - if (prop['field'].find('CPU min MHz')) > -1: cpu['mhz_min'] = prop['data'] - if (prop['field'].find('CPU max MHz')) > -1: cpu['mhz_max'] = prop['data'] - if (prop['field'].find('Vendor ID')) > -1: cpu['vendor'] = prop['data'] - if (prop['field'].find('Model name')) > -1: cpu['model'] = prop['data'] - -if len(cpu) > 0: - tmp = { 'cpu':cpu } +def get_raw_data(): + cpu = {} + cpu_json, code = popen('lscpu -J', stderr=DEVNULL) + + if code == 0: + cpu_info = json.loads(cpu_json) + if len(cpu_info) > 0 and 'lscpu' in cpu_info: + for prop in cpu_info['lscpu']: + if (prop['field'].find('Thread(s)') > -1): cpu['threads'] = prop['data'] + if (prop['field'].find('Core(s)')) > -1: cpu['cores'] = prop['data'] + if (prop['field'].find('Socket(s)')) > -1: cpu['sockets'] = prop['data'] + if (prop['field'].find('CPU(s):')) > -1: cpu['cpus'] = prop['data'] + if (prop['field'].find('CPU MHz')) > -1: cpu['mhz'] = prop['data'] + if (prop['field'].find('CPU min MHz')) > -1: cpu['mhz_min'] = prop['data'] + if (prop['field'].find('CPU max MHz')) > -1: cpu['mhz_max'] = prop['data'] + if (prop['field'].find('Vendor ID')) > -1: cpu['vendor'] = prop['data'] + if (prop['field'].find('Model name')) > -1: cpu['model'] = prop['data'] + + return cpu + +def get_formatted_output(): + cpu = get_raw_data() + + tmp = {'cpu':cpu} tmpl = Template(OUT_TMPL_SRC) - print(tmpl.render(tmp)) - exit(0) -else: - print('CPU information could not be determined\n') - exit(1) + return tmpl.render(tmp) + +if __name__ == '__main__': + cpu = get_raw_data() + + if len(cpu) > 0: + print(get_formatted_output()) + else: + print('CPU information could not be determined\n') + exit(1) + diff --git a/src/op_mode/show_ram.py b/src/op_mode/show_ram.py index 5818ec132..2b0be3965 100755 --- a/src/op_mode/show_ram.py +++ b/src/op_mode/show_ram.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2022 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 @@ -55,10 +55,17 @@ def get_system_memory_human(): return mem -if __name__ == '__main__': - mem = get_system_memory_human() +def get_raw_data(): + return get_system_memory_human() + +def get_formatted_output(): + mem = get_raw_data() - print("Total: {}".format(mem["total"])) - print("Free: {}".format(mem["free"])) - print("Used: {}".format(mem["used"])) + out = "Total: {}\n".format(mem["total"]) + out += "Free: {}\n".format(mem["free"]) + out += "Used: {}".format(mem["used"]) + return out + +if __name__ == '__main__': + print(get_formatted_output()) diff --git a/src/op_mode/show_uptime.py b/src/op_mode/show_uptime.py index c3dea52e6..1b5e33fa9 100755 --- a/src/op_mode/show_uptime.py +++ b/src/op_mode/show_uptime.py @@ -37,14 +37,27 @@ def get_load_averages(): return res -if __name__ == '__main__': +def get_raw_data(): from vyos.util import seconds_to_human - print("Uptime: {}\n".format(seconds_to_human(get_uptime_seconds()))) + res = {} + res["uptime_seconds"] = get_uptime_seconds() + res["uptime"] = seconds_to_human(get_uptime_seconds()) + res["load_average"] = get_load_averages() + + return res - avgs = get_load_averages() +def get_formatted_output(): + data = get_raw_data() - print("Load averages:") - print("1 minute: {:.02f}%".format(avgs[1]*100)) - print("5 minutes: {:.02f}%".format(avgs[5]*100)) - print("15 minutes: {:.02f}%".format(avgs[15]*100)) + out = "Uptime: {}\n\n".format(data["uptime"]) + avgs = data["load_average"] + out += "Load averages:\n" + out += "1 minute: {:.02f}%\n".format(avgs[1]*100) + out += "5 minutes: {:.02f}%\n".format(avgs[5]*100) + out += "15 minutes: {:.02f}%\n".format(avgs[15]*100) + + return out + +if __name__ == '__main__': + print(get_formatted_output()) diff --git a/src/op_mode/show_version.py b/src/op_mode/show_version.py index 7962e1e7b..b82ab6eca 100755 --- a/src/op_mode/show_version.py +++ b/src/op_mode/show_version.py @@ -26,10 +26,6 @@ from jinja2 import Template from sys import exit from vyos.util import call -parser = argparse.ArgumentParser() -parser.add_argument("-f", "--funny", action="store_true", help="Add something funny to the output") -parser.add_argument("-j", "--json", action="store_true", help="Produce JSON output") - version_output_tmpl = """ Version: VyOS {{version}} Release train: {{release_train}} @@ -51,7 +47,20 @@ Hardware UUID: {{hardware_uuid}} Copyright: VyOS maintainers and contributors """ +def get_raw_data(): + version_data = vyos.version.get_full_version_data() + return version_data + +def get_formatted_output(): + version_data = get_raw_data() + tmpl = Template(version_output_tmpl) + return tmpl.render(version_data) + if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument("-f", "--funny", action="store_true", help="Add something funny to the output") + parser.add_argument("-j", "--json", action="store_true", help="Produce JSON output") + args = parser.parse_args() version_data = vyos.version.get_full_version_data() @@ -60,9 +69,8 @@ if __name__ == '__main__': import json print(json.dumps(version_data)) exit(0) - - tmpl = Template(version_output_tmpl) - print(tmpl.render(version_data)) + else: + print(get_formatted_output()) if args.funny: print(vyos.limericks.get_random()) -- cgit v1.2.3 From 3a605ad020d8d20b08a72cb1284f6e590d1fd7b5 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 21 Feb 2022 18:23:55 +0100 Subject: vxlan: T4120: code cleanup for multiple remotes --- python/vyos/ifconfig/vxlan.py | 24 +++++++++++++++++------- src/conf_mode/interfaces-vxlan.py | 38 ++++++++++---------------------------- 2 files changed, 27 insertions(+), 35 deletions(-) diff --git a/python/vyos/ifconfig/vxlan.py b/python/vyos/ifconfig/vxlan.py index 87b5e40b8..516a19f24 100644 --- a/python/vyos/ifconfig/vxlan.py +++ b/python/vyos/ifconfig/vxlan.py @@ -1,4 +1,4 @@ -# Copyright 2019-2021 VyOS maintainers and contributors +# Copyright 2019-2022 VyOS maintainers and contributors # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -68,6 +68,16 @@ class VXLANIf(Interface): 'vni' : 'id', } + # IPv6 flowlabels can only be used on IPv6 tunnels, thus we need to + # ensure that at least the first remote IP address is passed to the + # tunnel creation command. Subsequent tunnel remote addresses can later + # be added to the FDB + remote_list = None + if 'remote' in self.config: + # skip first element as this is already configured as remote + remote_list = self.config['remote'][1:] + self.config['remote'] = self.config['remote'][0] + cmd = 'ip link add {ifname} type {type} dstport {port}' for vyos_key, iproute2_key in mapping.items(): # dict_search will return an empty dict "{}" for valueless nodes like @@ -83,9 +93,9 @@ class VXLANIf(Interface): # interface is always A/D down. It needs to be enabled explicitly self.set_admin_state('down') - other_remotes = self.config.get('other_remotes') - if other_remotes: - for rem in other_remotes: - self.config['rem'] = rem - cmd2 = 'bridge fdb append to 00:00:00:00:00:00 dst {rem} port {port} dev {ifname}' - self._cmd(cmd2.format(**self.config)) + # VXLAN tunnel is always recreated on any change - see interfaces-vxlan.py + if remote_list: + for remote in remote_list: + cmd = f'bridge fdb append to 00:00:00:00:00:00 dst {remote} ' \ + 'port {port} dev {ifname}' + self._cmd(cmd.format(**self.config)) diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index 092f249df..85604508e 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2020 VyOS maintainers and contributors +# Copyright (C) 2019-2022 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 @@ -34,8 +34,8 @@ airbag.enable() def get_config(config=None): """ - Retrive CLI config as dictionary. Dictionary can never be empty, as at least the - interface name will be added or a deleted flag + Retrive CLI config as dictionary. Dictionary can never be empty, as at least + the interface name will be added or a deleted flag """ if config: conf = config @@ -58,13 +58,6 @@ def get_config(config=None): if len(vxlan['other_tunnels']) == 0: del vxlan['other_tunnels'] - # leave first remote in dict and put the other ones (if they exists) to "other_remotes" - remotes = vxlan.get('remote') - if remotes: - vxlan['remote'] = remotes[0] - if len(remotes) > 1: - del remotes[0] - vxlan['other_remotes'] = remotes return vxlan def verify(vxlan): @@ -77,8 +70,7 @@ def verify(vxlan): if 'group' in vxlan: if 'source_interface' not in vxlan: - raise ConfigError('Multicast VXLAN requires an underlaying interface ') - + raise ConfigError('Multicast VXLAN requires an underlaying interface') verify_source_interface(vxlan) if not any(tmp in ['group', 'remote', 'source_address'] for tmp in vxlan): @@ -122,35 +114,26 @@ def verify(vxlan): protocol = 'ipv6' else: protocol = 'ipv4' + if 'remote' in vxlan: - if is_ipv6(vxlan['remote']): - if protocol == 'ipv4': - raise ConfigError('IPv4 and IPV6 cannot be mixed') - protocol = 'ipv6' - else: - if protocol == 'ipv6': - raise ConfigError('IPv4 and IPV6 cannot be mixed') - protocol = 'ipv4' - if 'other_remotes' in vxlan: - for rem in vxlan['other_remotes']: - if is_ipv6(rem): + error_msg = 'Can not mix both IPv4 and IPv6 for VXLAN underlay' + for remote in vxlan['remote']: + if is_ipv6(remote): if protocol == 'ipv4': - raise ConfigError('IPv4 and IPV6 cannot be mixed') + raise ConfigError(error_msg) protocol = 'ipv6' else: if protocol == 'ipv6': - raise ConfigError('IPv4 and IPV6 cannot be mixed') + raise ConfigError(error_msg) protocol = 'ipv4' verify_mtu_ipv6(vxlan) verify_address(vxlan) return None - def generate(vxlan): return None - def apply(vxlan): # Check if the VXLAN interface already exists if vxlan['ifname'] in interfaces(): @@ -166,7 +149,6 @@ def apply(vxlan): return None - if __name__ == '__main__': try: c = get_config() -- cgit v1.2.3 From c3661c8d5d7e8f5c1d040cadf134e87f0d77e28e Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 21 Feb 2022 18:25:05 +0100 Subject: smoketest: vxlan: T4120: verify support for multiple remote addresses --- python/vyos/util.py | 8 ++++++ smoketest/scripts/cli/test_interfaces_vxlan.py | 34 ++++++++++++++++++-------- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/python/vyos/util.py b/python/vyos/util.py index 1767ff9d3..4526375df 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -774,6 +774,14 @@ def dict_search_recursive(dict_object, key): for x in dict_search_recursive(j, key): yield x +def get_bridge_fdb(interface): + """ Returns the forwarding database entries for a given interface """ + if not os.path.exists(f'/sys/class/net/{interface}'): + return None + from json import loads + tmp = loads(cmd(f'bridge -j fdb show dev {interface}')) + return tmp + def get_interface_config(interface): """ Returns the used encapsulation protocol for given interface. If interface does not exist, None is returned. diff --git a/smoketest/scripts/cli/test_interfaces_vxlan.py b/smoketest/scripts/cli/test_interfaces_vxlan.py index 12fc463ba..f34b99ea4 100755 --- a/smoketest/scripts/cli/test_interfaces_vxlan.py +++ b/smoketest/scripts/cli/test_interfaces_vxlan.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2021 VyOS maintainers and contributors +# Copyright (C) 2020-2022 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -18,8 +18,9 @@ import unittest from vyos.configsession import ConfigSessionError from vyos.ifconfig import Interface +from vyos.util import get_bridge_fdb from vyos.util import get_interface_config - +from vyos.template import is_ipv6 from base_interfaces_test import BasicInterfaceTest class VXLANInterfaceTest(BasicInterfaceTest.TestCase): @@ -57,21 +58,34 @@ class VXLANInterfaceTest(BasicInterfaceTest.TestCase): ttl = 20 for interface in self._interfaces: options = get_interface_config(interface) + bridge = get_bridge_fdb(interface) vni = options['linkinfo']['info_data']['id'] self.assertIn(f'vni {vni}', self._options[interface]) - if any('link' in s for s in self._options[interface]): + if any('source-interface' in s for s in self._options[interface]): link = options['linkinfo']['info_data']['link'] self.assertIn(f'source-interface {link}', self._options[interface]) - if any('local6' in s for s in self._options[interface]): - remote = options['linkinfo']['info_data']['local6'] - self.assertIn(f'source-address {local6}', self._options[interface]) - - if any('remote6' in s for s in self._options[interface]): - remote = options['linkinfo']['info_data']['remote6'] - self.assertIn(f'remote {remote}', self._options[interface]) + # Verify source-address setting was properly configured on the Kernel + if any('source-address' in s for s in self._options[interface]): + for s in self._options[interface]: + if 'source-address' in s: + address = s.split()[-1] + if is_ipv6(address): + tmp = options['linkinfo']['info_data']['local6'] + else: + tmp = options['linkinfo']['info_data']['local'] + self.assertIn(f'source-address {tmp}', self._options[interface]) + + # Verify remote setting was properly configured on the Kernel + if any('remote' in s for s in self._options[interface]): + for s in self._options[interface]: + if 'remote' in s: + for fdb in bridge: + if 'mac' in fdb and fdb['mac'] == '00:00:00:00:00:00': + remote = fdb['dst'] + self.assertIn(f'remote {remote}', self._options[interface]) if any('group' in s for s in self._options[interface]): group = options['linkinfo']['info_data']['group'] -- cgit v1.2.3 From 149f704a172fb14f16d0ba00ef237b972539492f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 22 Feb 2022 10:29:32 +0100 Subject: vyos.configdict: T4263: leaf_node_changed() must also honor valueLess CLI nodes If a valueLess node is added or removed from the CLI, a call to leaf_node_changed() will not detect it. If node is valueLess, on change old or new (depending on addition or deletion) will be {} and is treated as None. Add handler for this special case where old or new is an instance of a dictionary but empty. --- python/vyos/configdict.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index f2ec93520..aea22c0c9 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -117,6 +117,12 @@ def leaf_node_changed(conf, path): D.set_level(conf.get_level()) (new, old) = D.get_value_diff(path) if new != old: + if isinstance(old, dict): + # valueLess nodes return {} if node is deleted + return True + if old is None and isinstance(new, dict): + # valueLess nodes return {} if node was added + return True if old is None: return [] if isinstance(old, str): -- cgit v1.2.3 From 2373b232849c847717cbdcfac7390d8376e227ca Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 22 Feb 2022 10:31:32 +0100 Subject: vxlan: T4264: interface is destroyed and rebuild on description change When changing "general" parameters like: - interface IP address - MTU - description the interface is destroyed and recreated ... this should not happen! --- src/conf_mode/interfaces-vxlan.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index 85604508e..29b16af89 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -21,6 +21,7 @@ from netifaces import interfaces from vyos.config import Config from vyos.configdict import get_interface_dict +from vyos.configdict import leaf_node_changed from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_mtu_ipv6 @@ -44,6 +45,16 @@ def get_config(config=None): base = ['interfaces', 'vxlan'] vxlan = get_interface_dict(conf, base) + # VXLAN interfaces are picky and require recreation if certain parameters + # change. But a VXLAN interface should - of course - not be re-created if + # it's description or IP address is adjusted. Feels somehow logic doesn't it? + for cli_option in ['external', 'gpe', 'group', 'port', 'remote', + 'source-address', 'source-interface', 'vni', + 'parameters ip dont-fragment', 'parameters ip tos', + 'parameters ip ttl']: + if leaf_node_changed(conf, cli_option.split()): + vxlan.update({'rebuild_required': {}}) + # We need to verify that no other VXLAN tunnel is configured when external # mode is in use - Linux Kernel limitation conf.set_level(base) @@ -136,11 +147,12 @@ def generate(vxlan): def apply(vxlan): # Check if the VXLAN interface already exists - if vxlan['ifname'] in interfaces(): - v = VXLANIf(vxlan['ifname']) - # VXLAN is super picky and the tunnel always needs to be recreated, - # thus we can simply always delete it first. - v.remove() + if 'rebuild_required' in vxlan or 'delete' in vxlan: + if vxlan['ifname'] in interfaces(): + v = VXLANIf(vxlan['ifname']) + # VXLAN is super picky and the tunnel always needs to be recreated, + # thus we can simply always delete it first. + v.remove() if 'deleted' not in vxlan: # Finally create the new interface -- cgit v1.2.3 From e64d45717940aa4fb4a072065bdfa04f884d00cc Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 23 Feb 2022 18:14:11 +0100 Subject: tunnel: T4267: "parameters ip key" on GRE not required for different remotes --- smoketest/scripts/cli/test_interfaces_tunnel.py | 98 +++++++++++++++++++++++-- src/conf_mode/interfaces-tunnel.py | 82 ++++++++++++--------- 2 files changed, 139 insertions(+), 41 deletions(-) diff --git a/smoketest/scripts/cli/test_interfaces_tunnel.py b/smoketest/scripts/cli/test_interfaces_tunnel.py index fc2e254d6..b2c045b56 100755 --- a/smoketest/scripts/cli/test_interfaces_tunnel.py +++ b/smoketest/scripts/cli/test_interfaces_tunnel.py @@ -44,14 +44,14 @@ class TunnelInterfaceTest(BasicInterfaceTest.TestCase): # call base-classes classmethod super(cls, cls).setUpClass() - def setUp(self): - super().setUp() - self.cli_set(['interfaces', 'dummy', source_if, 'address', self.local_v4 + '/32']) - self.cli_set(['interfaces', 'dummy', source_if, 'address', self.local_v6 + '/128']) + # create some test interfaces + cls.cli_set(cls, ['interfaces', 'dummy', source_if, 'address', cls.local_v4 + '/32']) + cls.cli_set(cls, ['interfaces', 'dummy', source_if, 'address', cls.local_v6 + '/128']) - def tearDown(self): - self.cli_delete(['interfaces', 'dummy', source_if]) - super().tearDown() + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, ['interfaces', 'dummy', source_if]) + super().tearDownClass() def test_ipv4_encapsulations(self): # When running tests ensure that for certain encapsulation types the @@ -312,5 +312,89 @@ class TunnelInterfaceTest(BasicInterfaceTest.TestCase): conf = get_interface_config(interface) self.assertEqual(new_remote, conf['linkinfo']['info_data']['remote']) + def test_tunnel_src_any_gre_key(self): + interface = f'tun1280' + encapsulation = 'gre' + src_addr = '0.0.0.0' + key = '127' + + self.cli_set(self._base_path + [interface, 'encapsulation', encapsulation]) + self.cli_set(self._base_path + [interface, 'source-address', src_addr]) + # GRE key must be supplied with a 0.0.0.0 source address + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(self._base_path + [interface, 'parameters', 'ip', 'key', key]) + + self.cli_commit() + + def test_multiple_gre_tunnel_same_remote(self): + tunnels = { + 'tun10' : { + 'encapsulation' : 'gre', + 'source_interface' : source_if, + 'remote' : '1.2.3.4', + }, + 'tun20' : { + 'encapsulation' : 'gre', + 'source_interface' : source_if, + 'remote' : '1.2.3.4', + }, + } + + for tunnel, tunnel_config in tunnels.items(): + self.cli_set(self._base_path + [tunnel, 'encapsulation', tunnel_config['encapsulation']]) + if 'source_interface' in tunnel_config: + self.cli_set(self._base_path + [tunnel, 'source-interface', tunnel_config['source_interface']]) + if 'remote' in tunnel_config: + self.cli_set(self._base_path + [tunnel, 'remote', tunnel_config['remote']]) + + # GRE key must be supplied when two or more tunnels are formed to the same desitnation + with self.assertRaises(ConfigSessionError): + self.cli_commit() + for tunnel, tunnel_config in tunnels.items(): + self.cli_set(self._base_path + [tunnel, 'parameters', 'ip', 'key', tunnel.lstrip('tun')]) + + self.cli_commit() + + for tunnel, tunnel_config in tunnels.items(): + conf = get_interface_config(tunnel) + ip_key = tunnel.lstrip('tun') + + self.assertEqual(tunnel_config['source_interface'], conf['link']) + self.assertEqual(tunnel_config['encapsulation'], conf['linkinfo']['info_kind']) + self.assertEqual(tunnel_config['remote'], conf['linkinfo']['info_data']['remote']) + self.assertEqual(f'0.0.0.{ip_key}', conf['linkinfo']['info_data']['ikey']) + self.assertEqual(f'0.0.0.{ip_key}', conf['linkinfo']['info_data']['okey']) + + def test_multiple_gre_tunnel_different_remote(self): + tunnels = { + 'tun10' : { + 'encapsulation' : 'gre', + 'source_interface' : source_if, + 'remote' : '1.2.3.4', + }, + 'tun20' : { + 'encapsulation' : 'gre', + 'source_interface' : source_if, + 'remote' : '1.2.3.5', + }, + } + + for tunnel, tunnel_config in tunnels.items(): + self.cli_set(self._base_path + [tunnel, 'encapsulation', tunnel_config['encapsulation']]) + if 'source_interface' in tunnel_config: + self.cli_set(self._base_path + [tunnel, 'source-interface', tunnel_config['source_interface']]) + if 'remote' in tunnel_config: + self.cli_set(self._base_path + [tunnel, 'remote', tunnel_config['remote']]) + + self.cli_commit() + + for tunnel, tunnel_config in tunnels.items(): + conf = get_interface_config(tunnel) + + self.assertEqual(tunnel_config['source_interface'], conf['link']) + self.assertEqual(tunnel_config['encapsulation'], conf['linkinfo']['info_kind']) + self.assertEqual(tunnel_config['remote'], conf['linkinfo']['info_data']['remote']) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index 4c1204b4e..433764b8a 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -18,7 +18,6 @@ import os from sys import exit from netifaces import interfaces -from ipaddress import IPv4Address from vyos.config import Config from vyos.configdict import get_interface_dict @@ -50,8 +49,24 @@ def get_config(config=None): base = ['interfaces', 'tunnel'] tunnel = get_interface_dict(conf, base) - tmp = leaf_node_changed(conf, ['encapsulation']) - if tmp: tunnel.update({'encapsulation_changed': {}}) + if 'deleted' not in tunnel: + tmp = leaf_node_changed(conf, ['encapsulation']) + if tmp: tunnel.update({'encapsulation_changed': {}}) + + # We also need to inspect other configured tunnels as there are Kernel + # restrictions where we need to comply. E.g. GRE tunnel key can't be used + # twice, or with multiple GRE tunnels to the same location we must specify + # a GRE key + conf.set_level(base) + tunnel['other_tunnels'] = conf.get_config_dict([], key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + # delete our own instance from this dict + ifname = tunnel['ifname'] + del tunnel['other_tunnels'][ifname] + # if only one tunnel is present on the system, no need to keep this key + if len(tunnel['other_tunnels']) == 0: + del tunnel['other_tunnels'] # We must check if our interface is configured to be a DMVPN member nhrp_base = ['protocols', 'nhrp', 'tunnel'] @@ -92,48 +107,47 @@ def verify(tunnel): if 'direction' not in tunnel['parameters']['erspan']: raise ConfigError('ERSPAN version 2 requires direction to be set!') - # If tunnel source address any and key not set + # If tunnel source is any and gre key is not set + interface = tunnel['ifname'] if tunnel['encapsulation'] in ['gre'] and \ dict_search('source_address', tunnel) == '0.0.0.0' and \ dict_search('parameters.ip.key', tunnel) == None: - raise ConfigError('Tunnel parameters ip key must be set!') + raise ConfigError(f'"parameters ip key" must be set for {interface} when '\ + 'encapsulation is GRE!') - if tunnel['encapsulation'] in ['gre', 'gretap']: + gre_encapsulations = ['gre', 'gretap'] + if tunnel['encapsulation'] in gre_encapsulations and 'other_tunnels' in tunnel: # Check pairs tunnel source-address/encapsulation/key with exists tunnels. # Prevent the same key for 2 tunnels with same source-address/encap. T2920 - for tunnel_if in Section.interfaces('tunnel'): - # It makes no sense to run the test against our own interface we - # are currently configuring - if tunnel['ifname'] == tunnel_if: - continue - - tunnel_cfg = get_interface_config(tunnel_if) + for o_tunnel, o_tunnel_conf in tunnel['other_tunnels'].items(): # no match on encapsulation - bail out - if dict_search('linkinfo.info_kind', tunnel_cfg) != tunnel['encapsulation']: + our_encapsulation = tunnel['encapsulation'] + their_encapsulation = o_tunnel_conf['encapsulation'] + if our_encapsulation in gre_encapsulations and their_encapsulation \ + not in gre_encapsulations: continue - new_source_address = dict_search('source_address', tunnel) - new_source_interface = dict_search('source_interface', tunnel) - if dict_search('parameters.ip.key', tunnel) != None: - # Convert tunnel key to ip key, format "ip -j link show" - # 1 => 0.0.0.1, 999 => 0.0.3.231 - orig_new_key = dict_search('parameters.ip.key', tunnel) - new_key = IPv4Address(int(orig_new_key)) - new_key = str(new_key) - if dict_search('address', tunnel_cfg) == new_source_address and \ - dict_search('linkinfo.info_data.ikey', tunnel_cfg) == new_key: - raise ConfigError(f'Key "{orig_new_key}" for source-address "{new_source_address}" ' \ + our_address = dict_search('source_address', tunnel) + our_key = dict_search('parameters.ip.key', tunnel) + their_address = dict_search('source_address', o_tunnel_conf) + their_key = dict_search('parameters.ip.key', o_tunnel_conf) + if our_key != None: + if their_address == our_address and their_key == our_key: + raise ConfigError(f'Key "{our_key}" for source-address "{our_address}" ' \ f'is already used for tunnel "{tunnel_if}"!') else: - # If no IP GRE key is used we can not have more then one GRE tunnel - # bound to any one interface/IP address. This will result in a OS - # PermissionError: add tunnel "gre0" failed: File exists - if (dict_search('address', tunnel_cfg) == new_source_address or - (dict_search('address', tunnel_cfg) == '0.0.0.0' and - dict_search('link', tunnel_cfg) == new_source_interface)): - raise ConfigError(f'Missing required "ip key" parameter when \ - running more then one GRE based tunnel on the \ - same source-interface/source-address') + our_source_if = dict_search('source_interface', tunnel) + their_source_if = dict_search('source_interface', o_tunnel_conf) + our_remote = dict_search('remote', tunnel) + their_remote = dict_search('remote', o_tunnel_conf) + # If no IP GRE key is defined we can not have more then one GRE tunnel + # bound to any one interface/IP address and the same remote. This will + # result in a OS PermissionError: add tunnel "gre0" failed: File exists + if (their_address == our_address or our_source_if == their_source_if) and \ + our_remote == their_remote: + raise ConfigError(f'Missing required "ip key" parameter when '\ + 'running more then one GRE based tunnel on the '\ + 'same source-interface/source-address') # Keys are not allowed with ipip and sit tunnels if tunnel['encapsulation'] in ['ipip', 'sit']: -- cgit v1.2.3 From 53517de05e9566c35218d1f07cacb1bff98a46d9 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 23 Feb 2022 18:14:58 +0100 Subject: smoketest: tunnel: indention fixup --- smoketest/scripts/cli/test_interfaces_tunnel.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/smoketest/scripts/cli/test_interfaces_tunnel.py b/smoketest/scripts/cli/test_interfaces_tunnel.py index b2c045b56..99c25c374 100755 --- a/smoketest/scripts/cli/test_interfaces_tunnel.py +++ b/smoketest/scripts/cli/test_interfaces_tunnel.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2021 VyOS maintainers and contributors +# Copyright (C) 2020-2022 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 @@ -202,7 +202,7 @@ class TunnelInterfaceTest(BasicInterfaceTest.TestCase): self.assertEqual(encapsulation, conf['linkinfo']['info_kind']) self.assertEqual(self.local_v4, conf['linkinfo']['info_data']['local']) self.assertEqual(remote_ip4, conf['linkinfo']['info_data']['remote']) - self.assertEqual(64, conf['linkinfo']['info_data']['ttl']) + self.assertEqual(64, conf['linkinfo']['info_data']['ttl']) # Change remote ip address (inc host by 2 new_remote = inc_ip(remote_ip4, 2) @@ -239,7 +239,7 @@ class TunnelInterfaceTest(BasicInterfaceTest.TestCase): self.assertEqual(encapsulation, conf['linkinfo']['info_kind']) self.assertEqual(self.local_v4, conf['linkinfo']['info_data']['local']) self.assertEqual(remote_ip4, conf['linkinfo']['info_data']['remote']) - self.assertEqual(64, conf['linkinfo']['info_data']['ttl']) + self.assertEqual(64, conf['linkinfo']['info_data']['ttl']) self.assertEqual(f'0.0.0.{ip_key}', conf['linkinfo']['info_data']['ikey']) self.assertEqual(f'0.0.0.{ip_key}', conf['linkinfo']['info_data']['okey']) self.assertEqual(int(idx), conf['linkinfo']['info_data']['erspan_index']) @@ -295,7 +295,7 @@ class TunnelInterfaceTest(BasicInterfaceTest.TestCase): self.assertEqual(encapsulation, conf['linkinfo']['info_kind']) self.assertEqual(self.local_v6, conf['linkinfo']['info_data']['local']) self.assertEqual(remote_ip6, conf['linkinfo']['info_data']['remote']) - self.assertEqual(64, conf['linkinfo']['info_data']['ttl']) + self.assertEqual(64, conf['linkinfo']['info_data']['ttl']) self.assertEqual(f'0.0.0.{ip_key}', conf['linkinfo']['info_data']['ikey']) self.assertEqual(f'0.0.0.{ip_key}', conf['linkinfo']['info_data']['okey']) self.assertEqual(erspan_ver, conf['linkinfo']['info_data']['erspan_ver']) -- cgit v1.2.3 From a68c9238111c6caee78bb28f8054b8f0cfa0e374 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 24 Feb 2022 22:47:12 +0100 Subject: scripts: T4269: node.def generator should automatically add default values Since introducing the XML node it was common, but redundant, practice to also add a help string indicating which value would be used as default if the node is unset. This makes no sense b/c it's duplicated code/value/characters and prone to error. The node.def scripts should be extended to automatically render the appropriate default value into the CLI help string. For e.g. SSH the current PoC renders: $ cat templates-cfg/service/ssh/port/node.def multi: type: txt help: Port for SSH service (default: 22) val_help: u32:1-65535; Numeric IP port ... Not all subsystems are already migrated to get_config_dict() and make use of the defaults() call - those subsystems need to be migrated, first before the new default is added to the CLI help. --- interface-definitions/containers.xml.in | 6 ++-- interface-definitions/dhcp-relay.xml.in | 6 ++-- interface-definitions/dhcp-server.xml.in | 2 +- interface-definitions/dhcpv6-relay.xml.in | 2 +- interface-definitions/dns-domain-name.xml.in | 1 + interface-definitions/dns-forwarding.xml.in | 6 ++-- interface-definitions/flow-accounting-conf.xml.in | 26 +++++++++--------- interface-definitions/high-availability.xml.in | 16 +++++------ interface-definitions/igmp-proxy.xml.in | 8 +++--- .../include/accel-ppp/client-ipv6-pool.xml.i | 2 +- .../include/accel-ppp/radius-additions.xml.i | 6 ++-- interface-definitions/include/bfd/common.xml.i | 6 ++-- .../include/bgp/protocol-common-config.xml.i | 2 +- .../include/bgp/timers-keepalive.xml.i | 2 +- .../include/firewall/name-default-action.xml.i | 2 +- .../include/interface/arp-cache-timeout.xml.i | 2 +- .../include/interface/dhcp-options.xml.i | 2 +- .../include/interface/dhcpv6-options.xml.i | 4 +-- .../include/nat-translation-options.xml.i | 4 +-- interface-definitions/include/ospf/auto-cost.xml.i | 2 +- .../include/ospf/interface-common.xml.i | 2 +- interface-definitions/include/ospf/intervals.xml.i | 8 +++--- .../include/ospf/metric-type.xml.i | 2 +- .../include/ospf/protocol-common-config.xml.i | 18 ++++++------ .../include/ospfv3/protocol-common-config.xml.i | 2 +- .../include/radius-server-port.xml.i | 2 +- interface-definitions/include/rip/rip-timers.xml.i | 6 ++-- .../include/snmp/access-mode.xml.i | 2 +- .../include/snmp/authentication-type.xml.i | 2 +- .../include/snmp/privacy-type.xml.i | 2 +- interface-definitions/include/snmp/protocol.xml.i | 2 +- .../include/vpn-ipsec-encryption.xml.i | 2 +- interface-definitions/include/vpn-ipsec-hash.xml.i | 2 +- interface-definitions/interfaces-bonding.xml.in | 6 ++-- interface-definitions/interfaces-bridge.xml.in | 10 +++---- interface-definitions/interfaces-ethernet.xml.in | 4 +-- interface-definitions/interfaces-l2tpv3.xml.in | 6 ++-- interface-definitions/interfaces-macsec.xml.in | 4 +-- interface-definitions/interfaces-openvpn.xml.in | 22 +++++++-------- interface-definitions/interfaces-pppoe.xml.in | 2 +- interface-definitions/interfaces-tunnel.xml.in | 4 +-- interface-definitions/interfaces-wireless.xml.in | 10 +++---- interface-definitions/protocols-rpki.xml.in | 2 +- .../service_console-server.xml.in | 6 ++-- .../service_monitoring_telegraf.xml.in | 6 ++-- interface-definitions/service_router-advert.xml.in | 14 +++++----- interface-definitions/service_webproxy.xml.in | 26 ++++++++++-------- interface-definitions/snmp.xml.in | 6 ++-- interface-definitions/ssh.xml.in | 2 +- interface-definitions/system-ip.xml.in | 2 +- interface-definitions/system-login.xml.in | 4 +-- interface-definitions/system-logs.xml.in | 8 +++--- interface-definitions/vpn_ipsec.xml.in | 32 +++++++++++----------- interface-definitions/vpn_l2tp.xml.in | 10 +++---- interface-definitions/vpn_openconnect.xml.in | 12 ++++---- interface-definitions/zone-policy.xml.in | 6 ++-- scripts/build-command-templates | 17 +++++++++--- 57 files changed, 197 insertions(+), 183 deletions(-) diff --git a/interface-definitions/containers.xml.in b/interface-definitions/containers.xml.in index 07686b16e..9cd2b0902 100644 --- a/interface-definitions/containers.xml.in +++ b/interface-definitions/containers.xml.in @@ -111,7 +111,7 @@ - Constrain the memory available to a container (default: 512MB) + Constrain the memory available to a container u32:0 Unlimited @@ -212,7 +212,7 @@ on-failure - Restart containers when they exit with a non-zero exit code, retrying indefinitely (default) + Restart containers when they exit with a non-zero exit code, retrying indefinitely always @@ -283,7 +283,7 @@ - Add registry (default docker.io) + Add registry docker.io diff --git a/interface-definitions/dhcp-relay.xml.in b/interface-definitions/dhcp-relay.xml.in index 483e776a7..a5643add6 100644 --- a/interface-definitions/dhcp-relay.xml.in +++ b/interface-definitions/dhcp-relay.xml.in @@ -20,7 +20,7 @@ Policy to discard packets that have reached specified hop-count u32:1-255 - Hop count (default: 10) + Hop count @@ -34,7 +34,7 @@ Maximum packet size to send to a DHCPv4/BOOTP server u32:64-1400 - Maximum packet size (default: 576) + Maximum packet size @@ -44,7 +44,7 @@ - Policy to handle incoming DHCPv4 packets which already contain relay agent options (default: forward) + Policy to handle incoming DHCPv4 packets which already contain relay agent options append replace forward discard diff --git a/interface-definitions/dhcp-server.xml.in b/interface-definitions/dhcp-server.xml.in index d1ed579e9..312dcd2a0 100644 --- a/interface-definitions/dhcp-server.xml.in +++ b/interface-definitions/dhcp-server.xml.in @@ -198,7 +198,7 @@ - Lease timeout in seconds (default: 86400) + Lease timeout in seconds u32 DHCP lease time in seconds diff --git a/interface-definitions/dhcpv6-relay.xml.in b/interface-definitions/dhcpv6-relay.xml.in index 7162cf353..5abcbe804 100644 --- a/interface-definitions/dhcpv6-relay.xml.in +++ b/interface-definitions/dhcpv6-relay.xml.in @@ -36,7 +36,7 @@ Maximum hop count for which requests will be processed u32:1-255 - Hop count (default: 10) + Hop count diff --git a/interface-definitions/dns-domain-name.xml.in b/interface-definitions/dns-domain-name.xml.in index 005a55ab3..7ae537d00 100644 --- a/interface-definitions/dns-domain-name.xml.in +++ b/interface-definitions/dns-domain-name.xml.in @@ -29,6 +29,7 @@ + System host name (default: vyos) diff --git a/interface-definitions/dns-forwarding.xml.in b/interface-definitions/dns-forwarding.xml.in index 4faf604ad..a2e809da8 100644 --- a/interface-definitions/dns-forwarding.xml.in +++ b/interface-definitions/dns-forwarding.xml.in @@ -16,7 +16,7 @@ - DNS forwarding cache size (default: 10000) + DNS forwarding cache size u32:0-2147483647 DNS forwarding cache size @@ -38,7 +38,7 @@ - DNSSEC mode (default: process-no-validate) + DNSSEC mode off process-no-validate process log-fail validate @@ -587,7 +587,7 @@ #include - Maximum amount of time negative entries are cached (default: 3600) + Maximum amount of time negative entries are cached u32:0-7200 Seconds to cache NXDOMAIN entries diff --git a/interface-definitions/flow-accounting-conf.xml.in b/interface-definitions/flow-accounting-conf.xml.in index 1b57d706c..05cf5e170 100644 --- a/interface-definitions/flow-accounting-conf.xml.in +++ b/interface-definitions/flow-accounting-conf.xml.in @@ -14,7 +14,7 @@ Buffer size u32 - Buffer size in MiB (default: 10) + Buffer size in MiB @@ -27,7 +27,7 @@ Specifies the maximum number of bytes to capture for each packet u32:128-750 - Packet length in bytes (default: 128) + Packet length in bytes @@ -209,7 +209,7 @@ 9 - NetFlow version 9 (default) + NetFlow version 9 10 @@ -240,7 +240,7 @@ NetFlow port number u32:1025-65535 - NetFlow port number (default: 2055) + NetFlow port number @@ -260,7 +260,7 @@ Expiry scan interval u32:0-2147483647 - Expiry scan interval (default: 60) + Expiry scan interval @@ -273,7 +273,7 @@ Generic flow timeout value u32:0-2147483647 - Generic flow timeout in seconds (default: 3600) + Generic flow timeout in seconds @@ -286,7 +286,7 @@ ICMP timeout value u32:0-2147483647 - ICMP timeout in seconds (default: 300) + ICMP timeout in seconds @@ -299,7 +299,7 @@ Max active timeout value u32:0-2147483647 - Max active timeout in seconds (default: 604800) + Max active timeout in seconds @@ -312,7 +312,7 @@ TCP finish timeout value u32:0-2147483647 - TCP FIN timeout in seconds (default: 300) + TCP FIN timeout in seconds @@ -325,7 +325,7 @@ TCP generic timeout value u32:0-2147483647 - TCP generic timeout in seconds (default: 3600) + TCP generic timeout in seconds @@ -338,7 +338,7 @@ TCP reset timeout value u32:0-2147483647 - TCP RST timeout in seconds (default: 120) + TCP RST timeout in seconds @@ -351,7 +351,7 @@ UDP timeout value u32:0-2147483647 - UDP timeout in seconds (default: 300) + UDP timeout in seconds @@ -418,7 +418,7 @@ sFlow port number u32:1025-65535 - sFlow port number (default: 6343) + sFlow port number diff --git a/interface-definitions/high-availability.xml.in b/interface-definitions/high-availability.xml.in index ee1d70484..662052e12 100644 --- a/interface-definitions/high-availability.xml.in +++ b/interface-definitions/high-availability.xml.in @@ -22,7 +22,7 @@ Advertise interval u32:1-255 - Advertise interval in seconds (default: 1) + Advertise interval in seconds @@ -79,7 +79,7 @@ - Health check failure count required for transition to fault (default: 3) + Health check failure count required for transition to fault @@ -88,7 +88,7 @@ - Health check execution interval in seconds (default: 60) + Health check execution interval in seconds @@ -160,7 +160,7 @@ - Router priority (default: 100) + Router priority u32:1-255 Router priority @@ -333,7 +333,7 @@ Interval between health-checks (in seconds) u32:1-600 - Interval in seconds (default: 10) + Interval in seconds @@ -343,7 +343,7 @@ - Forwarding method (default: NAT) + Forwarding method direct nat tunnel @@ -371,7 +371,7 @@ Timeout for persistent connections u32:1-86400 - Timeout for persistent connections (default: 300) + Timeout for persistent connections @@ -381,7 +381,7 @@ - Protocol for port checks (default: TCP) + Protocol for port checks tcp udp diff --git a/interface-definitions/igmp-proxy.xml.in b/interface-definitions/igmp-proxy.xml.in index 91c912d8b..c7ab60929 100644 --- a/interface-definitions/igmp-proxy.xml.in +++ b/interface-definitions/igmp-proxy.xml.in @@ -39,7 +39,7 @@ - IGMP interface role (default: downstream) + IGMP interface role upstream downstream disabled @@ -49,7 +49,7 @@ downstream - Downstream interface(s) (default) + Downstream interface(s) disabled @@ -63,10 +63,10 @@ - TTL threshold (default: 1) + TTL threshold u32:1-255 - TTL threshold for the interfaces (default: 1) + TTL threshold for the interfaces diff --git a/interface-definitions/include/accel-ppp/client-ipv6-pool.xml.i b/interface-definitions/include/accel-ppp/client-ipv6-pool.xml.i index a692f2335..01cf0e040 100644 --- a/interface-definitions/include/accel-ppp/client-ipv6-pool.xml.i +++ b/interface-definitions/include/accel-ppp/client-ipv6-pool.xml.i @@ -21,7 +21,7 @@ Prefix length used for individual client u32:48-128 - Client prefix length (default: 64) + Client prefix length diff --git a/interface-definitions/include/accel-ppp/radius-additions.xml.i b/interface-definitions/include/accel-ppp/radius-additions.xml.i index 258ece2b5..441c9dda5 100644 --- a/interface-definitions/include/accel-ppp/radius-additions.xml.i +++ b/interface-definitions/include/accel-ppp/radius-additions.xml.i @@ -21,7 +21,7 @@ Accounting port u32:1-65535 - Numeric IP port (default: 1813) + Numeric IP port @@ -62,7 +62,7 @@ - Timeout for Interim-Update packets, terminate session afterwards (default 3 seconds) + Timeout for Interim-Update packets, terminate session afterwards u32:0-60 Timeout in seconds, 0 to keep active @@ -126,7 +126,7 @@ - Port for Dynamic Authorization Extension server (DM/CoA) (default: 1700) + Port for Dynamic Authorization Extension server (DM/CoA) u32:1-65535 TCP port diff --git a/interface-definitions/include/bfd/common.xml.i b/interface-definitions/include/bfd/common.xml.i index e52221441..126ab9b9a 100644 --- a/interface-definitions/include/bfd/common.xml.i +++ b/interface-definitions/include/bfd/common.xml.i @@ -15,7 +15,7 @@ Minimum interval of receiving control packets u32:10-60000 - Interval in milliseconds (default: 300) + Interval in milliseconds @@ -28,7 +28,7 @@ Minimum interval of transmitting control packets u32:10-60000 - Interval in milliseconds (default: 300) + Interval in milliseconds @@ -41,7 +41,7 @@ Multiplier to determine packet loss u32:2-255 - Remote transmission interval will be multiplied by this value (default: 3) + Remote transmission interval will be multiplied by this value diff --git a/interface-definitions/include/bgp/protocol-common-config.xml.i b/interface-definitions/include/bgp/protocol-common-config.xml.i index 8214d0779..38337b032 100644 --- a/interface-definitions/include/bgp/protocol-common-config.xml.i +++ b/interface-definitions/include/bgp/protocol-common-config.xml.i @@ -1191,7 +1191,7 @@ Set period to rescan BGP table to check if condition is met u32:5-240 - Period to rerun the conditional advertisement scanner process (default: 60) + Period to rerun the conditional advertisement scanner process diff --git a/interface-definitions/include/bgp/timers-keepalive.xml.i b/interface-definitions/include/bgp/timers-keepalive.xml.i index b2771e326..b23f96ec8 100644 --- a/interface-definitions/include/bgp/timers-keepalive.xml.i +++ b/interface-definitions/include/bgp/timers-keepalive.xml.i @@ -4,7 +4,7 @@ BGP keepalive interval for this neighbor u32:1-65535 - Keepalive interval in seconds (default 60) + Keepalive interval in seconds diff --git a/interface-definitions/include/firewall/name-default-action.xml.i b/interface-definitions/include/firewall/name-default-action.xml.i index 1b61b076f..8470a29a9 100644 --- a/interface-definitions/include/firewall/name-default-action.xml.i +++ b/interface-definitions/include/firewall/name-default-action.xml.i @@ -7,7 +7,7 @@ drop - Drop if no prior rules are hit (default) + Drop if no prior rules are hit reject diff --git a/interface-definitions/include/interface/arp-cache-timeout.xml.i b/interface-definitions/include/interface/arp-cache-timeout.xml.i index cb01d0525..06d7ffe96 100644 --- a/interface-definitions/include/interface/arp-cache-timeout.xml.i +++ b/interface-definitions/include/interface/arp-cache-timeout.xml.i @@ -4,7 +4,7 @@ ARP cache entry timeout in seconds u32:1-86400 - ARP cache entry timout in seconds (default 30) + ARP cache entry timout in seconds diff --git a/interface-definitions/include/interface/dhcp-options.xml.i b/interface-definitions/include/interface/dhcp-options.xml.i index f62b06640..098d02919 100644 --- a/interface-definitions/include/interface/dhcp-options.xml.i +++ b/interface-definitions/include/interface/dhcp-options.xml.i @@ -30,7 +30,7 @@ Distance for the default route from DHCP server u32:1-255 - Distance for the default route from DHCP server (default: 210) + Distance for the default route from DHCP server diff --git a/interface-definitions/include/interface/dhcpv6-options.xml.i b/interface-definitions/include/interface/dhcpv6-options.xml.i index d1abf4a90..08e4f5e0a 100644 --- a/interface-definitions/include/interface/dhcpv6-options.xml.i +++ b/interface-definitions/include/interface/dhcpv6-options.xml.i @@ -57,10 +57,10 @@ - Local interface address assigned to interface + Local interface address assigned to interface (default: EUI-64) >0 - Used to form IPv6 interface address (default: EUI-64) + Used to form IPv6 interface address diff --git a/interface-definitions/include/nat-translation-options.xml.i b/interface-definitions/include/nat-translation-options.xml.i index df2f76397..f1539757b 100644 --- a/interface-definitions/include/nat-translation-options.xml.i +++ b/interface-definitions/include/nat-translation-options.xml.i @@ -16,7 +16,7 @@ random - Random source or destination address allocation for each connection (default) + Random source or destination address allocation for each connection ^(persistent|random)$ @@ -39,7 +39,7 @@ none - Do not apply port randomization (default) + Do not apply port randomization ^(random|fully-random|none)$ diff --git a/interface-definitions/include/ospf/auto-cost.xml.i b/interface-definitions/include/ospf/auto-cost.xml.i index 3e6cc8232..da6483a00 100644 --- a/interface-definitions/include/ospf/auto-cost.xml.i +++ b/interface-definitions/include/ospf/auto-cost.xml.i @@ -6,7 +6,7 @@ - Reference bandwidth method to assign cost (default: 100) + Reference bandwidth method to assign cost u32:1-4294967 Reference bandwidth cost in Mbits/sec diff --git a/interface-definitions/include/ospf/interface-common.xml.i b/interface-definitions/include/ospf/interface-common.xml.i index 738651594..9c8b94f0b 100644 --- a/interface-definitions/include/ospf/interface-common.xml.i +++ b/interface-definitions/include/ospf/interface-common.xml.i @@ -20,7 +20,7 @@ - Router priority (default: 1) + Router priority u32:0-255 OSPF router priority cost diff --git a/interface-definitions/include/ospf/intervals.xml.i b/interface-definitions/include/ospf/intervals.xml.i index fad1a6305..9f6e5df69 100644 --- a/interface-definitions/include/ospf/intervals.xml.i +++ b/interface-definitions/include/ospf/intervals.xml.i @@ -1,7 +1,7 @@ - Interval after which a neighbor is declared dead (default: 40) + Interval after which a neighbor is declared dead u32:1-65535 Neighbor dead interval (seconds) @@ -14,7 +14,7 @@ - Interval between hello packets (default: 10) + Interval between hello packets u32:1-65535 Hello interval (seconds) @@ -27,7 +27,7 @@ - Interval between retransmitting lost link state advertisements (default: 5) + Interval between retransmitting lost link state advertisements u32:1-65535 Retransmit interval (seconds) @@ -40,7 +40,7 @@ - Link state transmit delay (default: 1) + Link state transmit delay u32:1-65535 Link state transmit delay (seconds) diff --git a/interface-definitions/include/ospf/metric-type.xml.i b/interface-definitions/include/ospf/metric-type.xml.i index ef9fd8ac0..de55c7645 100644 --- a/interface-definitions/include/ospf/metric-type.xml.i +++ b/interface-definitions/include/ospf/metric-type.xml.i @@ -1,7 +1,7 @@ - OSPF metric type for default routes (default: 2) + OSPF metric type for default routes u32:1-2 Set OSPF External Type 1/2 metrics diff --git a/interface-definitions/include/ospf/protocol-common-config.xml.i b/interface-definitions/include/ospf/protocol-common-config.xml.i index e783f4bec..088bee2de 100644 --- a/interface-definitions/include/ospf/protocol-common-config.xml.i +++ b/interface-definitions/include/ospf/protocol-common-config.xml.i @@ -106,7 +106,7 @@ - Configure NSSA-ABR (default: candidate) + Configure NSSA-ABR always candidate never @@ -116,7 +116,7 @@ candidate - Translate for election (default) + Translate for election never @@ -502,7 +502,7 @@ - Dead neighbor polling interval (default: 60) + Dead neighbor polling interval u32:1-65535 Seconds between dead neighbor polling interval @@ -515,7 +515,7 @@ - Neighbor priority in seconds (default: 0) + Neighbor priority in seconds u32:0-255 Neighbor priority @@ -535,13 +535,13 @@ - OSPF ABR type (default: cisco) + OSPF ABR type cisco ibm shortcut standard cisco - Cisco ABR type (default) + Cisco ABR type ibm @@ -712,7 +712,7 @@ - Delay from the first change received to SPF calculation (default: 200) + Delay from the first change received to SPF calculation u32:0-600000 Delay in milliseconds @@ -725,7 +725,7 @@ - Initial hold time between consecutive SPF calculations (default: 1000) + Initial hold time between consecutive SPF calculations u32:0-600000 Initial hold time in milliseconds @@ -738,7 +738,7 @@ - Maximum hold time (default: 10000) + Maximum hold time u32:0-600000 Max hold time in milliseconds diff --git a/interface-definitions/include/ospfv3/protocol-common-config.xml.i b/interface-definitions/include/ospfv3/protocol-common-config.xml.i index 5d08debda..792c873c8 100644 --- a/interface-definitions/include/ospfv3/protocol-common-config.xml.i +++ b/interface-definitions/include/ospfv3/protocol-common-config.xml.i @@ -158,7 +158,7 @@ - Instance Id (default: 0) + Instance ID u32:0-255 Instance Id diff --git a/interface-definitions/include/radius-server-port.xml.i b/interface-definitions/include/radius-server-port.xml.i index 4e5d906bc..c6b691a0f 100644 --- a/interface-definitions/include/radius-server-port.xml.i +++ b/interface-definitions/include/radius-server-port.xml.i @@ -4,7 +4,7 @@ Authentication port u32:1-65535 - Numeric IP port (default: 1812) + Numeric IP port diff --git a/interface-definitions/include/rip/rip-timers.xml.i b/interface-definitions/include/rip/rip-timers.xml.i index 3aaaf8e65..129d9ed23 100644 --- a/interface-definitions/include/rip/rip-timers.xml.i +++ b/interface-definitions/include/rip/rip-timers.xml.i @@ -9,7 +9,7 @@ Garbage collection timer u32:5-2147483647 - Garbage colletion time (default 120) + Garbage colletion time @@ -22,7 +22,7 @@ Routing information timeout timer u32:5-2147483647 - Routing information timeout timer (default 180) + Routing information timeout timer @@ -35,7 +35,7 @@ Routing table update timer u32:5-2147483647 - Routing table update timer in seconds (default 30) + Routing table update timer in seconds diff --git a/interface-definitions/include/snmp/access-mode.xml.i b/interface-definitions/include/snmp/access-mode.xml.i index 1fce2364e..71c766774 100644 --- a/interface-definitions/include/snmp/access-mode.xml.i +++ b/interface-definitions/include/snmp/access-mode.xml.i @@ -7,7 +7,7 @@ ro - Read-Only (default) + Read-Only rw diff --git a/interface-definitions/include/snmp/authentication-type.xml.i b/interface-definitions/include/snmp/authentication-type.xml.i index 2a545864a..ca0bb10a6 100644 --- a/interface-definitions/include/snmp/authentication-type.xml.i +++ b/interface-definitions/include/snmp/authentication-type.xml.i @@ -7,7 +7,7 @@ md5 - Message Digest 5 (default) + Message Digest 5 sha diff --git a/interface-definitions/include/snmp/privacy-type.xml.i b/interface-definitions/include/snmp/privacy-type.xml.i index 47a1e632e..94029a6c6 100644 --- a/interface-definitions/include/snmp/privacy-type.xml.i +++ b/interface-definitions/include/snmp/privacy-type.xml.i @@ -7,7 +7,7 @@ des - Data Encryption Standard (default) + Data Encryption Standard aes diff --git a/interface-definitions/include/snmp/protocol.xml.i b/interface-definitions/include/snmp/protocol.xml.i index 335736724..ebdeef87e 100644 --- a/interface-definitions/include/snmp/protocol.xml.i +++ b/interface-definitions/include/snmp/protocol.xml.i @@ -7,7 +7,7 @@ udp - Listen protocol UDP (default) + Listen protocol UDP tcp diff --git a/interface-definitions/include/vpn-ipsec-encryption.xml.i b/interface-definitions/include/vpn-ipsec-encryption.xml.i index 9ef2f7c90..faa264d2f 100644 --- a/interface-definitions/include/vpn-ipsec-encryption.xml.i +++ b/interface-definitions/include/vpn-ipsec-encryption.xml.i @@ -11,7 +11,7 @@ aes128 - 128 bit AES-CBC (default) + 128 bit AES-CBC aes192 diff --git a/interface-definitions/include/vpn-ipsec-hash.xml.i b/interface-definitions/include/vpn-ipsec-hash.xml.i index 5a06b290e..b3ef4fb7a 100644 --- a/interface-definitions/include/vpn-ipsec-hash.xml.i +++ b/interface-definitions/include/vpn-ipsec-hash.xml.i @@ -15,7 +15,7 @@ sha1 - SHA1 HMAC (default) + SHA1 HMAC sha1_160 diff --git a/interface-definitions/interfaces-bonding.xml.in b/interface-definitions/interfaces-bonding.xml.in index 723041ca5..b98f4b960 100644 --- a/interface-definitions/interfaces-bonding.xml.in +++ b/interface-definitions/interfaces-bonding.xml.in @@ -66,7 +66,7 @@ layer2 - use MAC addresses to generate the hash (802.3ad, default) + use MAC addresses to generate the hash layer2+3 @@ -115,7 +115,7 @@ slow - Request partner to transmit LACPDUs every 30 seconds (default) + Request partner to transmit LACPDUs every 30 seconds fast @@ -135,7 +135,7 @@ 802.3ad - IEEE 802.3ad Dynamic link aggregation (Default) + IEEE 802.3ad Dynamic link aggregation active-backup diff --git a/interface-definitions/interfaces-bridge.xml.in b/interface-definitions/interfaces-bridge.xml.in index 89a6d2303..fabfb917a 100644 --- a/interface-definitions/interfaces-bridge.xml.in +++ b/interface-definitions/interfaces-bridge.xml.in @@ -26,7 +26,7 @@ u32:10-1000000 - MAC address aging time in seconds (default: 300) + MAC address aging time in seconds @@ -48,7 +48,7 @@ Forwarding delay u32:0-200 - Spanning Tree Protocol forwarding delay in seconds (default 15) + Spanning Tree Protocol forwarding delay in seconds @@ -62,7 +62,7 @@ Hello packet advertisement interval u32:1-10 - Spanning Tree Protocol hello advertisement interval in seconds (default 2) + Spanning Tree Protocol hello advertisement interval in seconds @@ -99,7 +99,7 @@ Interval at which neighbor bridges are removed u32:1-40 - Bridge maximum aging time in seconds (default 20) + Bridge maximum aging time in seconds @@ -195,7 +195,7 @@ Priority for this bridge u32:0-65535 - Bridge priority (default 32768) + Bridge priority diff --git a/interface-definitions/interfaces-ethernet.xml.in b/interface-definitions/interfaces-ethernet.xml.in index 9e113cb71..be7bddfa4 100644 --- a/interface-definitions/interfaces-ethernet.xml.in +++ b/interface-definitions/interfaces-ethernet.xml.in @@ -41,7 +41,7 @@ auto - Auto negotiation (default) + Auto negotiation half @@ -110,7 +110,7 @@ - Link speed (default: auto) + Link speed auto 10 100 1000 2500 5000 10000 25000 40000 50000 100000 diff --git a/interface-definitions/interfaces-l2tpv3.xml.in b/interface-definitions/interfaces-l2tpv3.xml.in index 85d4ab992..ba9bcb0a2 100644 --- a/interface-definitions/interfaces-l2tpv3.xml.in +++ b/interface-definitions/interfaces-l2tpv3.xml.in @@ -20,7 +20,7 @@ #include - UDP destination port for L2TPv3 tunnel (default: 5000) + UDP destination port for L2TPv3 tunnel u32:1-65535 Numeric IP port @@ -36,7 +36,7 @@ #include - Encapsulation type (default: UDP) + Encapsulation type udp ip @@ -102,7 +102,7 @@ - UDP source port for L2TPv3 tunnel (default: 5000) + UDP source port for L2TPv3 tunnel u32:1-65535 Numeric IP port diff --git a/interface-definitions/interfaces-macsec.xml.in b/interface-definitions/interfaces-macsec.xml.in index 598935e51..7206e57b1 100644 --- a/interface-definitions/interfaces-macsec.xml.in +++ b/interface-definitions/interfaces-macsec.xml.in @@ -36,7 +36,7 @@ gcm-aes-128 - Galois/Counter Mode of AES cipher with 128-bit key (default) + Galois/Counter Mode of AES cipher with 128-bit key gcm-aes-256 @@ -84,7 +84,7 @@ - Priority of MACsec Key Agreement protocol (MKA) actor (default: 255) + Priority of MACsec Key Agreement protocol (MKA) actor u32:0-255 MACsec Key Agreement protocol (MKA) priority diff --git a/interface-definitions/interfaces-openvpn.xml.in b/interface-definitions/interfaces-openvpn.xml.in index 16d91145f..eb574eb52 100644 --- a/interface-definitions/interfaces-openvpn.xml.in +++ b/interface-definitions/interfaces-openvpn.xml.in @@ -38,7 +38,7 @@ #include - OpenVPN interface device-type (default: tun) + OpenVPN interface device-type tun tap @@ -206,7 +206,7 @@ - Maximum number of keepalive packet failures (default: 60) + Maximum number of keepalive packet failures u32:0-1000 Maximum number of keepalive packet failures @@ -219,7 +219,7 @@ - Keepalive packet interval in seconds (default: 10) + Keepalive packet interval in seconds u32:0-600 Keepalive packet interval (seconds) @@ -613,13 +613,13 @@ - Topology for clients (default: net30) + Topology for clients net30 point-to-point subnet net30 - net30 topology (default) + net30 topology point-to-point @@ -647,7 +647,7 @@ - Maximum allowed clock slop in seconds (default: 180) + Maximum allowed clock slop in seconds 1-65535 Seconds @@ -660,7 +660,7 @@ - Time drift in seconds (default: 0) + Time drift in seconds 1-65535 Seconds @@ -673,7 +673,7 @@ - Step value for totp in seconds (default: 30) + Step value for totp in seconds 1-65535 Seconds @@ -686,7 +686,7 @@ - Number of digits to use for totp hash (default: 6) + Number of digits to use for totp hash 1-65535 Seconds @@ -699,7 +699,7 @@ - Expect password as result of a challenge response protocol (default: enabled) + Expect password as result of a challenge response protocol disable enable @@ -709,7 +709,7 @@ enable - Enable chalenge-response (default) + Enable chalenge-response ^(disable|enable)$ diff --git a/interface-definitions/interfaces-pppoe.xml.in b/interface-definitions/interfaces-pppoe.xml.in index 80a890940..ed0e45840 100644 --- a/interface-definitions/interfaces-pppoe.xml.in +++ b/interface-definitions/interfaces-pppoe.xml.in @@ -23,7 +23,7 @@ #include - Default route insertion behaviour (default: auto) + Default route insertion behaviour auto none force diff --git a/interface-definitions/interfaces-tunnel.xml.in b/interface-definitions/interfaces-tunnel.xml.in index fd69fd177..eb1708aaa 100644 --- a/interface-definitions/interfaces-tunnel.xml.in +++ b/interface-definitions/interfaces-tunnel.xml.in @@ -241,7 +241,7 @@ u32:0-255 - Encapsulation limit (default: 4) + Encapsulation limit none @@ -261,7 +261,7 @@ Hoplimit u32:0-255 - Hop limit (default: 64) + Hop limit diff --git a/interface-definitions/interfaces-wireless.xml.in b/interface-definitions/interfaces-wireless.xml.in index a2d1439a3..5b79ac671 100644 --- a/interface-definitions/interfaces-wireless.xml.in +++ b/interface-definitions/interfaces-wireless.xml.in @@ -291,7 +291,7 @@ 0 - 20 or 40 MHz channel width (default) + 20 or 40 MHz channel width 1 @@ -431,7 +431,7 @@ - Wireless radio channel (default: 0) + Wireless radio channel 0 Automatic Channel Selection (ACS) @@ -515,7 +515,7 @@ disabled - no MFP (hostapd default) + no MFP optional @@ -546,7 +546,7 @@ g - 802.11g - 54 Mbits/sec (default) + 802.11g - 54 Mbits/sec n @@ -564,7 +564,7 @@ - Wireless physical device (default: phy0) + Wireless physical device diff --git a/interface-definitions/protocols-rpki.xml.in b/interface-definitions/protocols-rpki.xml.in index a73d0aae4..68762ff9a 100644 --- a/interface-definitions/protocols-rpki.xml.in +++ b/interface-definitions/protocols-rpki.xml.in @@ -82,7 +82,7 @@ - RPKI cache polling period (default: 300) + RPKI cache polling period u32:1-86400 Polling period in seconds diff --git a/interface-definitions/service_console-server.xml.in b/interface-definitions/service_console-server.xml.in index 28aa7ea71..549edb813 100644 --- a/interface-definitions/service_console-server.xml.in +++ b/interface-definitions/service_console-server.xml.in @@ -41,7 +41,7 @@ - Serial port data bits (default: 8) + Serial port data bits 7 8 @@ -53,7 +53,7 @@ - Serial port stop bits (default: 1) + Serial port stop bits 1 2 @@ -65,7 +65,7 @@ - Parity setting (default: none) + Parity setting even odd none diff --git a/interface-definitions/service_monitoring_telegraf.xml.in b/interface-definitions/service_monitoring_telegraf.xml.in index 0db9052ff..f0a94d6a9 100644 --- a/interface-definitions/service_monitoring_telegraf.xml.in +++ b/interface-definitions/service_monitoring_telegraf.xml.in @@ -44,19 +44,19 @@ - Remote bucket, by default (main) + Remote bucket main - Source parameters for monitoring (default: all) + Source parameters for monitoring all hardware-utilization logs network system telegraf all - All parameters (default) + All parameters hardware-utilization diff --git a/interface-definitions/service_router-advert.xml.in b/interface-definitions/service_router-advert.xml.in index 0f4009f5c..ce1da85aa 100644 --- a/interface-definitions/service_router-advert.xml.in +++ b/interface-definitions/service_router-advert.xml.in @@ -18,7 +18,7 @@ - Set Hop Count field of the IP header for outgoing packets (default: 64) + Set Hop Count field of the IP header for outgoing packets u32:0 Unspecified (by this router) @@ -63,7 +63,7 @@ medium - Default router has medium preference (default) + Default router has medium preference high @@ -108,7 +108,7 @@ - Maximum interval between unsolicited multicast RAs (default: 600) + Maximum interval between unsolicited multicast RAs u32:4-1800 Maximum interval in seconds @@ -156,7 +156,7 @@ - Time in seconds that the route will remain valid (default: 1800 seconds) + Time in seconds that the route will remain valid infinity @@ -187,7 +187,7 @@ medium - Route has medium preference (default) + Route has medium preference high @@ -234,7 +234,7 @@ - Time in seconds that the prefix will remain preferred (default 4 hours) + Time in seconds that the prefix will remain preferred infinity @@ -255,7 +255,7 @@ - Time in seconds that the prefix will remain valid (default: 30 days) + Time in seconds that the prefix will remain valid infinity diff --git a/interface-definitions/service_webproxy.xml.in b/interface-definitions/service_webproxy.xml.in index 03f504ac7..92e5ca37b 100644 --- a/interface-definitions/service_webproxy.xml.in +++ b/interface-definitions/service_webproxy.xml.in @@ -28,7 +28,7 @@ - Number of authentication helper processes (default: 5) + Number of authentication helper processes n Number of authentication helper processes @@ -41,7 +41,7 @@ - Authenticated session time to live in minutes (default: 60) + Authenticated session time to live in minutes n Authenticated session timeout @@ -105,7 +105,7 @@ - LDAP protocol version (default: 3) + LDAP protocol version 2 3 @@ -177,7 +177,7 @@ - Default Proxy Port (default: 3128) + Default Proxy Port u32:1025-65535 Default port number @@ -190,7 +190,11 @@ - Cache peer ICP port (default: disabled) + Cache peer ICP port + + u32:0 + Cache peer disabled + u32:1-65535 Cache peer ICP port @@ -203,7 +207,7 @@ - Cache peer options (default: "no-query default") + Cache peer options txt Cache peer options @@ -239,7 +243,7 @@ - Disk cache size in MB (default: 100) + Disk cache size in MB u32 Disk cache size in MB @@ -253,7 +257,7 @@ - Default Proxy Port (default: 3128) + Default Proxy Port u32:1025-65535 Default port number @@ -296,7 +300,7 @@ - Default Proxy Port (default: 3128) + Default Proxy Port u32:1025-65535 Default port number @@ -399,7 +403,7 @@ - Hour of day for database update [REQUIRED] + Hour of day for database update u32:0-23 Hour for database update @@ -414,7 +418,7 @@ - Redirect URL for filtered websites (default: block.vyos.net) + Redirect URL for filtered websites url URL for redirect diff --git a/interface-definitions/snmp.xml.in b/interface-definitions/snmp.xml.in index 67d3aef9a..b9e0f4cc5 100644 --- a/interface-definitions/snmp.xml.in +++ b/interface-definitions/snmp.xml.in @@ -26,7 +26,7 @@ ro - Read-Only (default) + Read-Only rw @@ -226,7 +226,7 @@ auth - Messages are authenticated but not encrypted (authNoPriv, default) + Messages are authenticated but not encrypted (authNoPriv) priv @@ -329,7 +329,7 @@ inform trap - inform (default) + inform Use INFORM diff --git a/interface-definitions/ssh.xml.in b/interface-definitions/ssh.xml.in index e3b9d16e1..187e5f8e8 100644 --- a/interface-definitions/ssh.xml.in +++ b/interface-definitions/ssh.xml.in @@ -105,7 +105,7 @@ ^(quiet|fatal|error|info|verbose)$ - INFO + info diff --git a/interface-definitions/system-ip.xml.in b/interface-definitions/system-ip.xml.in index 86fbe5701..1fa63d517 100644 --- a/interface-definitions/system-ip.xml.in +++ b/interface-definitions/system-ip.xml.in @@ -15,7 +15,7 @@ - Maximum number of entries to keep in the ARP cache (default: 8192) + Maximum number of entries to keep in the ARP cache 1024 2048 4096 8192 16384 32768 diff --git a/interface-definitions/system-login.xml.in b/interface-definitions/system-login.xml.in index 4bfe82268..a5519ee88 100644 --- a/interface-definitions/system-login.xml.in +++ b/interface-definitions/system-login.xml.in @@ -124,7 +124,7 @@ Session timeout u32:1-30 - Session timeout in seconds (default: 2) + Session timeout in seconds @@ -138,7 +138,7 @@ Server priority u32:1-255 - Server priority (default: 255) + Server priority diff --git a/interface-definitions/system-logs.xml.in b/interface-definitions/system-logs.xml.in index 8b6c7c399..1caa7abb6 100644 --- a/interface-definitions/system-logs.xml.in +++ b/interface-definitions/system-logs.xml.in @@ -23,7 +23,7 @@ Size of a single log file that triggers rotation u32:1-1024 - Size in MB (default: 10) + Size in MB @@ -37,7 +37,7 @@ Count of rotations before old logs will be deleted u32:1-100 - Rotations (default: 10) + Rotations @@ -58,7 +58,7 @@ Size of a single log file that triggers rotation u32:1-1024 - Size in MB (default: 1) + Size in MB @@ -72,7 +72,7 @@ Count of rotations before old logs will be deleted u32:1-100 - Rotations (default: 10) + Rotations diff --git a/interface-definitions/vpn_ipsec.xml.in b/interface-definitions/vpn_ipsec.xml.in index dae76218f..147bb99ba 100644 --- a/interface-definitions/vpn_ipsec.xml.in +++ b/interface-definitions/vpn_ipsec.xml.in @@ -30,7 +30,7 @@ disable - Disable ESP compression (default) + Disable ESP compression enable @@ -47,7 +47,7 @@ ESP lifetime u32:30-86400 - ESP lifetime in seconds (default: 3600) + ESP lifetime in seconds @@ -87,7 +87,7 @@ tunnel - Tunnel mode (default) + Tunnel mode transport @@ -107,7 +107,7 @@ enable - Inherit Diffie-Hellman group from the IKE group (default) + Inherit Diffie-Hellman group from the IKE group dh-group1 @@ -235,7 +235,7 @@ none - Do nothing (default) + Do nothing hold @@ -267,7 +267,7 @@ hold - Attempt to re-negotiate the connection when matching traffic is seen (default) + Attempt to re-negotiate the connection when matching traffic is seen clear @@ -287,7 +287,7 @@ Keep-alive interval u32:2-86400 - Keep-alive interval in seconds (default: 30) + Keep-alive interval in seconds @@ -299,7 +299,7 @@ Dead Peer Detection keep-alive timeout (IKEv1 only) u32:2-86400 - Keep-alive timeout in seconds (default 120) + Keep-alive timeout in seconds @@ -310,7 +310,7 @@ - Re-authentication of the remote peer during an IKE re-key. IKEv2 option only + Re-authentication of the remote peer during an IKE re-key - IKEv2 only yes no @@ -320,7 +320,7 @@ no - Disable remote host re-authenticaton during an IKE rekey. (default) + Disable remote host re-authenticaton during an IKE rekey ^(yes|no)$ @@ -351,7 +351,7 @@ IKE lifetime u32:30-86400 - IKE lifetime in seconds (default: 28800) + IKE lifetime in seconds @@ -367,7 +367,7 @@ enable - Enable MOBIKE (default for IKEv2) + Enable MOBIKE disable @@ -386,7 +386,7 @@ main - Use the main mode (recommended, default) + Use the main mode (recommended) aggressive @@ -533,7 +533,7 @@ strongSwan logging Level 0 - Very basic auditing logs e.g. SA up/SA down (default) + Very basic auditing logs e.g. SA up/SA down 1 @@ -791,7 +791,7 @@ u32:1-86400 - Timeout in seconds (default: 28800) + Timeout in seconds @@ -1067,7 +1067,7 @@ inherit - Inherit the reauth configuration form your IKE-group (default) + Inherit the reauth configuration form your IKE-group ^(yes|no|inherit)$ diff --git a/interface-definitions/vpn_l2tp.xml.in b/interface-definitions/vpn_l2tp.xml.in index 6a88756a7..9ca7b1fad 100644 --- a/interface-definitions/vpn_l2tp.xml.in +++ b/interface-definitions/vpn_l2tp.xml.in @@ -88,7 +88,7 @@ IKE lifetime u32:30-86400 - IKE lifetime in seconds (default 3600) + IKE lifetime in seconds @@ -101,7 +101,7 @@ ESP lifetime u32:30-86400 - IKE lifetime in seconds (default 3600) + IKE lifetime in seconds @@ -135,7 +135,7 @@ PPP idle timeout u32:30-86400 - PPP idle timeout in seconds (default 1800) + PPP idle timeout in seconds @@ -206,7 +206,7 @@ - Timeout to wait reply for Interim-Update packets. (default 3 seconds) + Timeout to wait reply for Interim-Update packets @@ -244,7 +244,7 @@ - Specifies which radius attribute contains rate information. (default is Filter-Id) + Specifies which radius attribute contains rate information diff --git a/interface-definitions/vpn_openconnect.xml.in b/interface-definitions/vpn_openconnect.xml.in index 0db5e79d0..3fc34bacc 100644 --- a/interface-definitions/vpn_openconnect.xml.in +++ b/interface-definitions/vpn_openconnect.xml.in @@ -41,7 +41,7 @@ Session timeout u32:1-30 - Session timeout in seconds (default: 2) + Session timeout in seconds @@ -61,10 +61,10 @@ - tcp port number to accept connections (default: 443) + tcp port number to accept connections u32:1-65535 - Numeric IP port (default: 443) + Numeric IP port @@ -74,10 +74,10 @@ - udp port number to accept connections (default: 443) + udp port number to accept connections u32:1-65535 - Numeric IP port (default: 443) + Numeric IP port @@ -160,7 +160,7 @@ Prefix length used for individual client u32:48-128 - Client prefix length (default: 64) + Client prefix length diff --git a/interface-definitions/zone-policy.xml.in b/interface-definitions/zone-policy.xml.in index 69ee031c7..b898c3ecd 100644 --- a/interface-definitions/zone-policy.xml.in +++ b/interface-definitions/zone-policy.xml.in @@ -27,7 +27,7 @@ drop - Drop silently (default) + Drop silently reject @@ -97,7 +97,7 @@ accept - Accept traffic (default) + Accept traffic drop @@ -138,7 +138,7 @@ Zone to be local-zone - + diff --git a/scripts/build-command-templates b/scripts/build-command-templates index d8abb0a13..876f5877c 100755 --- a/scripts/build-command-templates +++ b/scripts/build-command-templates @@ -117,7 +117,7 @@ def collect_validators(ve): return regex_args + " " + validator_args -def get_properties(p): +def get_properties(p, default=None): props = {} if p is None: @@ -125,7 +125,12 @@ def get_properties(p): # Get the help string try: - props["help"] = p.find("help").text + help = p.find("help").text + if default != None: + # DNS forwarding for instance has multiple defaults - specified as whitespace separated list + tmp = ', '.join(default.text.split()) + help += f' (default: {tmp})' + props["help"] = help except: pass @@ -134,7 +139,11 @@ def get_properties(p): vhe = p.findall("valueHelp") vh = [] for v in vhe: - vh.append( (v.find("format").text, v.find("description").text) ) + format = v.find("format").text + description = v.find("description").text + if default != None and default.text == format: + description += f' (default)' + vh.append( (format, description) ) props["val_help"] = vh except: props["val_help"] = [] @@ -271,7 +280,7 @@ def process_node(n, tmpl_dir): print("Name of the node: {0}. Created directory: {1}\n".format(name, "/".join(my_tmpl_dir)), end="") os.makedirs(make_path(my_tmpl_dir), exist_ok=True) - props = get_properties(props_elem) + props = get_properties(props_elem, n.find("defaultValue")) if owner: props["owner"] = owner # Type should not be set for non-tag, non-leaf nodes -- cgit v1.2.3 From faa63999ca1fe11cc25e8a241e75a451a53ffa26 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 25 Feb 2022 21:46:18 +0100 Subject: dhcp-relay: T3095: add missing max-size default value --- interface-definitions/dhcp-relay.xml.in | 1 + 1 file changed, 1 insertion(+) diff --git a/interface-definitions/dhcp-relay.xml.in b/interface-definitions/dhcp-relay.xml.in index a5643add6..339941e65 100644 --- a/interface-definitions/dhcp-relay.xml.in +++ b/interface-definitions/dhcp-relay.xml.in @@ -41,6 +41,7 @@ max-size must be a value between 64 and 1400 + 576 -- cgit v1.2.3 From 15eff1682613ad20f83c46fded866b132a1fb814 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 25 Feb 2022 21:47:26 +0100 Subject: smoketest: webproxy: use setUpClass() over setUp() --- smoketest/scripts/cli/test_service_webproxy.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/smoketest/scripts/cli/test_service_webproxy.py b/smoketest/scripts/cli/test_service_webproxy.py index 8a1a03ce7..ebbd9fe55 100755 --- a/smoketest/scripts/cli/test_service_webproxy.py +++ b/smoketest/scripts/cli/test_service_webproxy.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-2022 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 @@ -30,11 +30,19 @@ listen_if = 'dum3632' listen_ip = '192.0.2.1' class TestServiceWebProxy(VyOSUnitTestSHIM.TestCase): - def setUp(self): - self.cli_set(['interfaces', 'dummy', listen_if, 'address', listen_ip + '/32']) + @classmethod + def setUpClass(cls): + # call base-classes classmethod + super(cls, cls).setUpClass() + # create a test interfaces + cls.cli_set(cls, ['interfaces', 'dummy', listen_if, 'address', listen_ip + '/32']) + + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, ['interfaces', 'dummy', listen_if]) + super().tearDownClass() def tearDown(self): - self.cli_delete(['interfaces', 'dummy', listen_if]) self.cli_delete(base_path) self.cli_commit() -- cgit v1.2.3 From be60d39332b753f5fe35101efe3463eebea2cb9d Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 25 Feb 2022 21:50:09 +0100 Subject: wireless: ifconfig: T2653: add missing defaultValue for mgmt-frame-protection --- interface-definitions/interfaces-wireless.xml.in | 1 + 1 file changed, 1 insertion(+) diff --git a/interface-definitions/interfaces-wireless.xml.in b/interface-definitions/interfaces-wireless.xml.in index 5b79ac671..9db9fd757 100644 --- a/interface-definitions/interfaces-wireless.xml.in +++ b/interface-definitions/interfaces-wireless.xml.in @@ -529,6 +529,7 @@ ^(disabled|optional|required)$ + disabled -- cgit v1.2.3 From 8cb7c2288585b81f29111e7fdc36c34e62b7db13 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 25 Feb 2022 21:52:57 +0100 Subject: xml: webproxy: add comment about explicitly not set defaultValue --- interface-definitions/service_webproxy.xml.in | 1 + 1 file changed, 1 insertion(+) diff --git a/interface-definitions/service_webproxy.xml.in b/interface-definitions/service_webproxy.xml.in index 92e5ca37b..89c4c3910 100644 --- a/interface-definitions/service_webproxy.xml.in +++ b/interface-definitions/service_webproxy.xml.in @@ -309,6 +309,7 @@ + -- cgit v1.2.3 From 0ec8927476e7d654d52df4c803a6694be0b1e9e2 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 25 Feb 2022 21:54:25 +0100 Subject: monitoring: T3872: re-use "port" building block from port-number.xml.i --- interface-definitions/service_monitoring_telegraf.xml.in | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/interface-definitions/service_monitoring_telegraf.xml.in b/interface-definitions/service_monitoring_telegraf.xml.in index f0a94d6a9..7db9de9f8 100644 --- a/interface-definitions/service_monitoring_telegraf.xml.in +++ b/interface-definitions/service_monitoring_telegraf.xml.in @@ -98,10 +98,8 @@ Incorrect URL format. + #include - - Remote port (default: 8086) - 8086 -- cgit v1.2.3 From ae51162283826e1a510aed1609778eb0223c8462 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 25 Feb 2022 21:57:09 +0100 Subject: vpn: ipsec: T3093: add missing defaultValue entries --- interface-definitions/include/vpn-ipsec-encryption.xml.i | 1 + interface-definitions/include/vpn-ipsec-hash.xml.i | 1 + interface-definitions/vpn_ipsec.xml.in | 4 ++++ 3 files changed, 6 insertions(+) diff --git a/interface-definitions/include/vpn-ipsec-encryption.xml.i b/interface-definitions/include/vpn-ipsec-encryption.xml.i index faa264d2f..eb0678aa9 100644 --- a/interface-definitions/include/vpn-ipsec-encryption.xml.i +++ b/interface-definitions/include/vpn-ipsec-encryption.xml.i @@ -229,5 +229,6 @@ ^(null|aes128|aes192|aes256|aes128ctr|aes192ctr|aes256ctr|aes128ccm64|aes192ccm64|aes256ccm64|aes128ccm96|aes192ccm96|aes256ccm96|aes128ccm128|aes192ccm128|aes256ccm128|aes128gcm64|aes192gcm64|aes256gcm64|aes128gcm96|aes192gcm96|aes256gcm96|aes128gcm128|aes192gcm128|aes256gcm128|aes128gmac|aes192gmac|aes256gmac|3des|blowfish128|blowfish192|blowfish256|camellia128|camellia192|camellia256|camellia128ctr|camellia192ctr|camellia256ctr|camellia128ccm64|camellia192ccm64|camellia256ccm64|camellia128ccm96|camellia192ccm96|camellia256ccm96|camellia128ccm128|camellia192ccm128|camellia256ccm128|serpent128|serpent192|serpent256|twofish128|twofish192|twofish256|cast128|chacha20poly1305)$ + aes128 diff --git a/interface-definitions/include/vpn-ipsec-hash.xml.i b/interface-definitions/include/vpn-ipsec-hash.xml.i index b3ef4fb7a..d6259574a 100644 --- a/interface-definitions/include/vpn-ipsec-hash.xml.i +++ b/interface-definitions/include/vpn-ipsec-hash.xml.i @@ -61,5 +61,6 @@ ^(md5|md5_128|sha1|sha1_160|sha256|sha256_96|sha384|sha512|aesxcbc|aescmac|aes128gmac|aes192gmac|aes256gmac)$ + sha1 diff --git a/interface-definitions/vpn_ipsec.xml.in b/interface-definitions/vpn_ipsec.xml.in index 147bb99ba..885bac979 100644 --- a/interface-definitions/vpn_ipsec.xml.in +++ b/interface-definitions/vpn_ipsec.xml.in @@ -293,6 +293,7 @@ + 30 @@ -305,6 +306,7 @@ + 120 @@ -377,6 +379,7 @@ ^(enable|disable)$ + enable @@ -396,6 +399,7 @@ ^(main|aggressive)$ + main -- cgit v1.2.3 From 0daf168d3d7583984431de2ef97682ff4c986f74 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 25 Feb 2022 22:30:34 +0100 Subject: zone-policy: T2199: bugfix defaultValue usage Instead of hardcoding the default behavior inside the Jinaj2 template, all defaults are required to be specified inside teh XML definition. This is required to automatically render the appropriate CLI tab completion commands. --- data/templates/zone_policy/nftables.tmpl | 12 ++++++------ interface-definitions/zone-policy.xml.in | 1 + src/conf_mode/zone_policy.py | 24 ++++++++++++++++++------ 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/data/templates/zone_policy/nftables.tmpl b/data/templates/zone_policy/nftables.tmpl index 093da6bd8..4a6bd2772 100644 --- a/data/templates/zone_policy/nftables.tmpl +++ b/data/templates/zone_policy/nftables.tmpl @@ -16,7 +16,7 @@ table ip filter { iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME_{{ from_conf.firewall.name }} iifname { {{ zone[from_zone].interface | join(",") }} } counter return {% endfor %} - counter {{ zone_conf.default_action if zone_conf.default_action is defined else 'drop' }} + counter {{ zone_conf.default_action }} } chain VZONE_{{ zone_name }}_OUT { oifname lo counter return @@ -24,7 +24,7 @@ table ip filter { oifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME_{{ from_conf.firewall.name }} oifname { {{ zone[from_zone].interface | join(",") }} } counter return {% endfor %} - counter {{ zone_conf.default_action if zone_conf.default_action is defined else 'drop' }} + counter {{ zone_conf.default_action }} } {% else %} chain VZONE_{{ zone_name }} { @@ -38,7 +38,7 @@ table ip filter { iifname { {{ zone[from_zone].interface | join(",") }} } counter return {% endif %} {% endfor %} - counter {{ zone_conf.default_action if zone_conf.default_action is defined else 'drop' }} + counter {{ zone_conf.default_action }} } {% endif %} {% endfor %} @@ -53,7 +53,7 @@ table ip6 filter { iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME6_{{ from_conf.firewall.ipv6_name }} iifname { {{ zone[from_zone].interface | join(",") }} } counter return {% endfor %} - counter {{ zone_conf.default_action if zone_conf.default_action is defined else 'drop' }} + counter {{ zone_conf.default_action }} } chain VZONE6_{{ zone_name }}_OUT { oifname lo counter return @@ -61,7 +61,7 @@ table ip6 filter { oifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME6_{{ from_conf.firewall.ipv6_name }} oifname { {{ zone[from_zone].interface | join(",") }} } counter return {% endfor %} - counter {{ zone_conf.default_action if zone_conf.default_action is defined else 'drop' }} + counter {{ zone_conf.default_action }} } {% else %} chain VZONE6_{{ zone_name }} { @@ -75,7 +75,7 @@ table ip6 filter { iifname { {{ zone[from_zone].interface | join(",") }} } counter return {% endif %} {% endfor %} - counter {{ zone_conf.default_action if zone_conf.default_action is defined else 'drop' }} + counter {{ zone_conf.default_action }} } {% endif %} {% endfor %} diff --git a/interface-definitions/zone-policy.xml.in b/interface-definitions/zone-policy.xml.in index b898c3ecd..eac63fa6b 100644 --- a/interface-definitions/zone-policy.xml.in +++ b/interface-definitions/zone-policy.xml.in @@ -37,6 +37,7 @@ ^(drop|reject)$ + drop diff --git a/src/conf_mode/zone_policy.py b/src/conf_mode/zone_policy.py index 683f8f034..dc0617353 100755 --- a/src/conf_mode/zone_policy.py +++ b/src/conf_mode/zone_policy.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2022 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 @@ -20,10 +20,12 @@ from json import loads from sys import exit from vyos.config import Config +from vyos.configdict import dict_merge from vyos.template import render from vyos.util import cmd from vyos.util import dict_search_args from vyos.util import run +from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -36,12 +38,22 @@ def get_config(config=None): else: conf = Config() base = ['zone-policy'] - zone_policy = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, - no_tag_node_value_mangle=True) + zone_policy = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) - if zone_policy: - zone_policy['firewall'] = conf.get_config_dict(['firewall'], key_mangling=('-', '_'), get_first_key=True, - no_tag_node_value_mangle=True) + zone_policy['firewall'] = conf.get_config_dict(['firewall'], + key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + + if 'zone' in zone_policy: + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + default_values = defaults(base + ['zone']) + for zone in zone_policy['zone']: + zone_policy['zone'][zone] = dict_merge(default_values, + zone_policy['zone'][zone]) return zone_policy -- cgit v1.2.3 From d25e07fd2e965b1193ad4d625167e890b0e36f6a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 25 Feb 2022 22:31:45 +0100 Subject: smoketest: zone-policy: use setUpClass() over setUp() --- smoketest/scripts/cli/test_zone_policy.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/smoketest/scripts/cli/test_zone_policy.py b/smoketest/scripts/cli/test_zone_policy.py index 00dfe0182..6e34f3179 100755 --- a/smoketest/scripts/cli/test_zone_policy.py +++ b/smoketest/scripts/cli/test_zone_policy.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2022 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 @@ -21,12 +21,18 @@ from base_vyostest_shim import VyOSUnitTestSHIM from vyos.util import cmd class TestZonePolicy(VyOSUnitTestSHIM.TestCase): - def setUp(self): - self.cli_set(['firewall', 'name', 'smoketest', 'default-action', 'drop']) + @classmethod + def setUpClass(cls): + super(cls, cls).setUpClass() + cls.cli_set(cls, ['firewall', 'name', 'smoketest', 'default-action', 'drop']) + + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, ['firewall']) + super(cls, cls).tearDownClass() def tearDown(self): self.cli_delete(['zone-policy']) - self.cli_delete(['firewall']) self.cli_commit() def test_basic_zone(self): -- cgit v1.2.3 From e1c5f629fa310251e0516ac59fb5429b9e83d7fa Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 25 Feb 2022 22:33:16 +0100 Subject: nat: T1083: use defaultValue from XML when handling translations --- interface-definitions/include/nat-translation-options.xml.i | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/interface-definitions/include/nat-translation-options.xml.i b/interface-definitions/include/nat-translation-options.xml.i index f1539757b..925f90106 100644 --- a/interface-definitions/include/nat-translation-options.xml.i +++ b/interface-definitions/include/nat-translation-options.xml.i @@ -22,7 +22,8 @@ ^(persistent|random)$ - + random + Port mapping options @@ -45,7 +46,8 @@ ^(random|fully-random|none)$ - + none + -- cgit v1.2.3 From 291558023bbd1bdbef6e00c4eec173cf5c9575d8 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 26 Feb 2022 16:30:10 +0100 Subject: lldp: T4272: migrate to get_config_dict() --- data/templates/lldp/lldpd.tmpl | 3 +- data/templates/lldp/vyos.conf.tmpl | 35 +++--- interface-definitions/lldp.xml.in | 22 ++-- src/conf_mode/lldp.py | 235 ++++++++++--------------------------- 4 files changed, 94 insertions(+), 201 deletions(-) diff --git a/data/templates/lldp/lldpd.tmpl b/data/templates/lldp/lldpd.tmpl index 3db955b48..819e70c84 100644 --- a/data/templates/lldp/lldpd.tmpl +++ b/data/templates/lldp/lldpd.tmpl @@ -1,3 +1,2 @@ ### Autogenerated by lldp.py ### -DAEMON_ARGS="-M 4{% if options.snmp %} -x{% endif %}{% if options.cdp %} -c{% endif %}{% if options.edp %} -e{% endif %}{% if options.fdp %} -f{% endif %}{% if options.sonmp %} -s{% endif %}" - +DAEMON_ARGS="-M 4{% if snmp is defined and snmp.enable is defined %} -x{% endif %}{% if legacy_protocols is defined and legacy_protocols.cdp is defined %} -c{% endif %}{% if legacy_protocols is defined and legacy_protocols.edp is defined %} -e{% endif %}{% if legacy_protocols is defined and legacy_protocols.fdp is defined %} -f{% endif %}{% if legacy_protocols is defined and legacy_protocols.sonmp is defined %} -s{% endif %}" diff --git a/data/templates/lldp/vyos.conf.tmpl b/data/templates/lldp/vyos.conf.tmpl index 07bbaf604..592dcf61f 100644 --- a/data/templates/lldp/vyos.conf.tmpl +++ b/data/templates/lldp/vyos.conf.tmpl @@ -1,20 +1,25 @@ ### Autogenerated by lldp.py ### configure system platform VyOS -configure system description "VyOS {{ options.description }}" -{% if options.listen_on %} -configure system interface pattern "{{ ( options.listen_on | select('equalto','all') | map('replace','all','*') | list + options.listen_on | select('equalto','!all') | map('replace','!all','!*') | list + options.listen_on | reject('equalto','all') | reject('equalto','!all') | list ) | unique | join(",") }}" +configure system description "VyOS {{ version }}" +{% if interface is defined and interface is not none %} +{% set tmp = [] %} +{% for iface, iface_options in interface.items() if not iface_options.disable %} +{% if iface == 'all' %} +{% set iface = '*' %} +{% endif %} +{% set _ = tmp.append(iface) %} +{% if iface_options.location is defined and iface_options.location is not none %} +{% if iface_options.location.elin is defined and iface_options.location.elin is not none %} +configure ports {{ iface }} med location elin "{{ iface_options.location.elin }}" +{% endif %} +{% if iface_options.location is defined and iface_options.location.coordinate_based is not none and iface_options.location.coordinate_based is not none %} +configure ports {{ iface }} med location coordinate latitude "{{ iface_options.location.coordinate_based.latitude }}" longitude "{{ iface_options.location.coordinate_based.longitude }}" altitude "{{ iface_options.location.coordinate_based.altitude }}m" datum "{{ iface_options.location.coordinate_based.datum }}" +{% endif %} +{% endif %} +{% endfor %} +configure system interface pattern "{{ tmp | join(",") }}" {% endif %} -{% if options.mgmt_addr %} -configure system ip management pattern {{ options.mgmt_addr | join(",") }} +{% if management_address is defined and management_address is not none %} +configure system ip management pattern {{ management_address | join(",") }} {% endif %} -{% for loc in location %} -{% if loc.elin %} -configure ports {{ loc.name }} med location elin "{{ loc.elin }}" -{% endif %} -{% if loc.coordinate_based %} -configure ports {{ loc.name }} med location coordinate {% if loc.coordinate_based.latitude %}latitude {{ loc.coordinate_based.latitude }}{% endif %} {% if loc.coordinate_based.longitude %}longitude {{ loc.coordinate_based.longitude }}{% endif %} {% if loc.coordinate_based.altitude %}altitude {{ loc.coordinate_based.altitude }} m{% endif %} {% if loc.coordinate_based.datum %}datum {{ loc.coordinate_based.datum }}{% endif %} -{% endif %} - - -{% endfor %} diff --git a/interface-definitions/lldp.xml.in b/interface-definitions/lldp.xml.in index 32ef0ad14..b9ffe234c 100644 --- a/interface-definitions/lldp.xml.in +++ b/interface-definitions/lldp.xml.in @@ -28,7 +28,7 @@ #include - LLDP-MED location data [REQUIRED] + LLDP-MED location data @@ -39,6 +39,10 @@ Altitude in meters + + 0 + No altitude + [+-]<meters> Altitude in meters @@ -48,13 +52,14 @@ + 0 Coordinate datum type WGS84 - WGS84 (default) + WGS84 NAD83 @@ -69,33 +74,34 @@ Datum should be WGS84, NAD83, or MLLW - ^(WGS84|NAD83|MLLW)$ + (WGS84|NAD83|MLLW) + WGS84 - Latitude [REQUIRED] + Latitude <latitude> Latitude (example "37.524449N") Latitude should be a number followed by S or N - (\d+)(\.\d+)?[nNsS]$ + (\d+)(\.\d+)?[nNsS] - Longitude [REQUIRED] + Longitude <longitude> Longitude (example "122.267255W") Longiture should be a number followed by E or W - (\d+)(\.\d+)?[eEwW]$ + (\d+)(\.\d+)?[eEwW] @@ -109,7 +115,7 @@ Emergency Call Service ELIN number (between 10-25 numbers) - [0-9]{10,25}$ + [0-9]{10,25} ELIN number must be between 10-25 numbers diff --git a/src/conf_mode/lldp.py b/src/conf_mode/lldp.py index 082c3e128..db8328259 100755 --- a/src/conf_mode/lldp.py +++ b/src/conf_mode/lldp.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2017-2020 VyOS maintainers and contributors +# Copyright (C) 2017-2022 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -15,19 +15,19 @@ # along with this program. If not, see . import os -import re -from copy import deepcopy from sys import exit from vyos.config import Config +from vyos.configdict import dict_merge from vyos.validate import is_addr_assigned from vyos.validate import is_loopback_addr from vyos.version import get_version_data -from vyos import ConfigError from vyos.util import call +from vyos.util import dict_search +from vyos.xml import defaults from vyos.template import render - +from vyos import ConfigError from vyos import airbag airbag.enable() @@ -35,178 +35,73 @@ config_file = "/etc/default/lldpd" vyos_config_file = "/etc/lldpd.d/01-vyos.conf" base = ['service', 'lldp'] -default_config_data = { - "options": '', - "interface_list": '', - "location": '' -} - -def get_options(config): - options = {} - config.set_level(base) - - options['listen_vlan'] = config.exists('listen-vlan') - options['mgmt_addr'] = [] - for addr in config.return_values('management-address'): - if is_addr_assigned(addr) and not is_loopback_addr(addr): - options['mgmt_addr'].append(addr) - else: - message = 'WARNING: LLDP management address {0} invalid - '.format(addr) - if is_loopback_addr(addr): - message += '(loopback address).' - else: - message += 'address not found.' - print(message) - - snmp = config.exists('snmp enable') - options["snmp"] = snmp - if snmp: - config.set_level('') - options["sys_snmp"] = config.exists('service snmp') - config.set_level(base) - - config.set_level(base + ['legacy-protocols']) - options['cdp'] = config.exists('cdp') - options['edp'] = config.exists('edp') - options['fdp'] = config.exists('fdp') - options['sonmp'] = config.exists('sonmp') - - # start with an unknown version information - version_data = get_version_data() - options['description'] = version_data['version'] - options['listen_on'] = [] - - return options - -def get_interface_list(config): - config.set_level(base) - intfs_names = config.list_nodes(['interface']) - if len(intfs_names) < 0: - return 0 - - interface_list = [] - for name in intfs_names: - config.set_level(base + ['interface', name]) - disable = config.exists(['disable']) - intf = { - 'name': name, - 'disable': disable - } - interface_list.append(intf) - return interface_list - - -def get_location_intf(config, name): - path = base + ['interface', name] - config.set_level(path) - - config.set_level(path + ['location']) - elin = '' - coordinate_based = {} - - if config.exists('elin'): - elin = config.return_value('elin') - - if config.exists('coordinate-based'): - config.set_level(path + ['location', 'coordinate-based']) - - coordinate_based['latitude'] = config.return_value(['latitude']) - coordinate_based['longitude'] = config.return_value(['longitude']) - - coordinate_based['altitude'] = '0' - if config.exists(['altitude']): - coordinate_based['altitude'] = config.return_value(['altitude']) - - coordinate_based['datum'] = 'WGS84' - if config.exists(['datum']): - coordinate_based['datum'] = config.return_value(['datum']) - - intf = { - 'name': name, - 'elin': elin, - 'coordinate_based': coordinate_based - - } - return intf - - -def get_location(config): - config.set_level(base) - intfs_names = config.list_nodes(['interface']) - if len(intfs_names) < 0: - return 0 - - if config.exists('disable'): - return 0 - - intfs_location = [] - for name in intfs_names: - intf = get_location_intf(config, name) - intfs_location.append(intf) - - return intfs_location - - def get_config(config=None): - lldp = deepcopy(default_config_data) if config: conf = config else: conf = Config() + if not conf.exists(base): - return None - else: - lldp['options'] = get_options(conf) - lldp['interface_list'] = get_interface_list(conf) - lldp['location'] = get_location(conf) + return {} - return lldp + lldp = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, no_tag_node_value_mangle=True) + if conf.exists(['service', 'snmp']): + lldp['system_snmp_enabled'] = '' + + version_data = get_version_data() + lldp['version'] = version_data['version'] + + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + # location coordinates have a default value + if 'interface' in lldp: + for interface, interface_config in lldp['interface'].items(): + default_values = defaults(base + ['interface']) + if dict_search('location.coordinate_based', interface_config) == None: + # no location specified - no need to add defaults + del default_values['location']['coordinate_based']['datum'] + del default_values['location']['coordinate_based']['altitude'] + + # cleanup default_values dictionary from inner to outer + # this might feel overkill here, but it does support easy extension + # in the future with additional default values + if len(default_values['location']['coordinate_based']) == 0: + del default_values['location']['coordinate_based'] + if len(default_values['location']) == 0: + del default_values['location'] + + lldp['interface'][interface] = dict_merge(default_values, + lldp['interface'][interface]) + + return lldp def verify(lldp): # bail out early - looks like removal from running config if lldp is None: return - # check location - for location in lldp['location']: - # check coordinate-based - if len(location['coordinate_based']) > 0: - # check longitude and latitude - if not location['coordinate_based']['longitude']: - raise ConfigError('Must define longitude for interface {0}'.format(location['name'])) - - if not location['coordinate_based']['latitude']: - raise ConfigError('Must define latitude for interface {0}'.format(location['name'])) - - if not re.match(r'^(\d+)(\.\d+)?[nNsS]$', location['coordinate_based']['latitude']): - raise ConfigError('Invalid location for interface {0}:\n' \ - 'latitude should be a number followed by S or N'.format(location['name'])) - - if not re.match(r'^(\d+)(\.\d+)?[eEwW]$', location['coordinate_based']['longitude']): - raise ConfigError('Invalid location for interface {0}:\n' \ - 'longitude should be a number followed by E or W'.format(location['name'])) - - # check altitude and datum if exist - if location['coordinate_based']['altitude']: - if not re.match(r'^[-+0-9\.]+$', location['coordinate_based']['altitude']): - raise ConfigError('Invalid location for interface {0}:\n' \ - 'altitude should be a positive or negative number'.format(location['name'])) - - if location['coordinate_based']['datum']: - if not re.match(r'^(WGS84|NAD83|MLLW)$', location['coordinate_based']['datum']): - raise ConfigError("Invalid location for interface {0}:\n' \ - 'datum should be WGS84, NAD83, or MLLW".format(location['name'])) - - # check elin - elif location['elin']: - if not re.match(r'^[0-9]{10,25}$', location['elin']): - raise ConfigError('Invalid location for interface {0}:\n' \ - 'ELIN number must be between 10-25 numbers'.format(location['name'])) + if 'management_address' in lldp: + for address in lldp['management_address']: + message = f'WARNING: LLDP management address "{address}" is invalid' + if is_loopback_addr(address): + print(f'{message} - loopback address') + elif not is_addr_assigned(address): + print(f'{message} - not assigned to any interface') + + if 'interface' in lldp: + for interface, interface_config in lldp['interface'].items(): + # bail out early if no location info present in interface config + if 'location' not in interface_config: + continue + if 'coordinate_based' in interface_config['location']: + if not {'latitude', 'latitude'} <= set(interface_config['location']['coordinate_based']): + raise ConfigError(f'Must define both longitude and latitude for "{interface}" location!') # check options - if lldp['options']['snmp']: - if not lldp['options']['sys_snmp']: + if 'snmp' in lldp and 'enable' in lldp['snmp']: + if 'system_snmp_enabled' not in lldp: raise ConfigError('SNMP must be configured to enable LLDP SNMP') @@ -215,29 +110,17 @@ def generate(lldp): if lldp is None: return - # generate listen on interfaces - for intf in lldp['interface_list']: - tmp = '' - # add exclamation mark if interface is disabled - if intf['disable']: - tmp = '!' - - tmp += intf['name'] - lldp['options']['listen_on'].append(tmp) - - # generate /etc/default/lldpd render(config_file, 'lldp/lldpd.tmpl', lldp) - # generate /etc/lldpd.d/01-vyos.conf render(vyos_config_file, 'lldp/vyos.conf.tmpl', lldp) - def apply(lldp): + systemd_service = 'lldpd.service' if lldp: # start/restart lldp service - call('systemctl restart lldpd.service') + call(f'systemctl restart {systemd_service}') else: # LLDP service has been terminated - call('systemctl stop lldpd.service') + call(f'systemctl stop {systemd_service}') if os.path.isfile(config_file): os.unlink(config_file) if os.path.isfile(vyos_config_file): -- cgit v1.2.3 From 3b72f115807d84a08000d8ddbbadb521e671e697 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 26 Feb 2022 22:38:52 +0100 Subject: smoketest: lldp: add testcase (cherry picked from commit 2fd5eea801bb524c12217c26d98c44a819b2086e) --- smoketest/scripts/cli/test_service_lldp.py | 127 +++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100755 smoketest/scripts/cli/test_service_lldp.py diff --git a/smoketest/scripts/cli/test_service_lldp.py b/smoketest/scripts/cli/test_service_lldp.py new file mode 100755 index 000000000..64fdd9d1b --- /dev/null +++ b/smoketest/scripts/cli/test_service_lldp.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 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 . + +import re +import os +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.ifconfig import Section +from vyos.util import cmd +from vyos.util import process_named_running +from vyos.util import read_file +from vyos.version import get_version_data + +PROCESS_NAME = 'lldpd' +LLDPD_CONF = '/etc/lldpd.d/01-vyos.conf' +base_path = ['service', 'lldp'] +mgmt_if = 'dum83513' +mgmt_addr = ['1.2.3.4', '1.2.3.5'] + +class TestServiceLLDP(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + # call base-classes classmethod + super(cls, cls).setUpClass() + + # create a test interfaces + for addr in mgmt_addr: + cls.cli_set(cls, ['interfaces', 'dummy', mgmt_if, 'address', addr + '/32']) + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, ['interfaces', 'dummy', mgmt_if]) + super().tearDownClass() + + def tearDown(self): + # service must be running after it was configured + self.assertTrue(process_named_running(PROCESS_NAME)) + + # delete/stop LLDP service + self.cli_delete(base_path) + self.cli_commit() + + # service is no longer allowed to run after it was removed + self.assertFalse(process_named_running(PROCESS_NAME)) + + def test_01_lldp_basic(self): + self.cli_set(base_path) + self.cli_commit() + + config = read_file(LLDPD_CONF) + version_data = get_version_data() + version = version_data['version'] + self.assertIn(f'configure system platform VyOS', config) + self.assertIn(f'configure system description "VyOS {version}"', config) + + def test_02_lldp_mgmt_address(self): + for addr in mgmt_addr: + self.cli_set(base_path + ['management-address', addr]) + self.cli_commit() + + config = read_file(LLDPD_CONF) + self.assertIn(f'configure system ip management pattern {",".join(mgmt_addr)}', config) + + def test_03_lldp_interfaces(self): + for interface in Section.interfaces('ethernet'): + if not '.' in interface: + self.cli_set(base_path + ['interface', interface]) + + # commit changes + self.cli_commit() + + # verify configuration + config = read_file(LLDPD_CONF) + + interface_list = [] + for interface in Section.interfaces('ethernet'): + if not '.' in interface: + interface_list.append(interface) + tmp = ','.join(interface_list) + self.assertIn(f'configure system interface pattern "{tmp}"', config) + + def test_04_lldp_all_interfaces(self): + self.cli_set(base_path + ['interface', 'all']) + # commit changes + self.cli_commit() + + # verify configuration + config = read_file(LLDPD_CONF) + self.assertIn(f'configure system interface pattern "*"', config) + + def test_05_lldp_location(self): + interface = 'eth0' + elin = '1234567890' + self.cli_set(base_path + ['interface', interface, 'location', 'elin', elin]) + + # commit changes + self.cli_commit() + + # verify configuration + config = read_file(LLDPD_CONF) + + self.assertIn(f'configure ports {interface} med location elin "{elin}"', config) + self.assertIn(f'configure system interface pattern "{interface}"', config) + + +if __name__ == '__main__': + unittest.main(verbosity=2) -- cgit v1.2.3 From 17602c2d63aacc972d4e2f6f21aeeded243d4fa1 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 26 Feb 2022 23:06:37 +0100 Subject: lldp: T4272: minor bugfix in Jinja2 template for location --- data/templates/lldp/vyos.conf.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/lldp/vyos.conf.tmpl b/data/templates/lldp/vyos.conf.tmpl index 592dcf61f..14395a223 100644 --- a/data/templates/lldp/vyos.conf.tmpl +++ b/data/templates/lldp/vyos.conf.tmpl @@ -13,7 +13,7 @@ configure system description "VyOS {{ version }}" {% if iface_options.location.elin is defined and iface_options.location.elin is not none %} configure ports {{ iface }} med location elin "{{ iface_options.location.elin }}" {% endif %} -{% if iface_options.location is defined and iface_options.location.coordinate_based is not none and iface_options.location.coordinate_based is not none %} +{% if iface_options.location is defined and iface_options.location.coordinate_based is defined and iface_options.location.coordinate_based is not none %} configure ports {{ iface }} med location coordinate latitude "{{ iface_options.location.coordinate_based.latitude }}" longitude "{{ iface_options.location.coordinate_based.longitude }}" altitude "{{ iface_options.location.coordinate_based.altitude }}m" datum "{{ iface_options.location.coordinate_based.datum }}" {% endif %} {% endif %} -- cgit v1.2.3 From 61fa1c95164e4222e79b078b1a796f41397e0ee3 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 28 Feb 2022 14:28:55 +0100 Subject: ssh: T4273: bugfix cipher and key-exchange multi nodes After hardning the regex validator to be preceeded with ^ and ending with $ it was no longer possible to have a comma separated list as SSH ciphers. The migrations cript is altered to migrate the previous comma separated list to individual multi node entries - cipher and key-exchange always had been multinodes - so this just re-arranges some values and does not break CLI compatibility --- interface-definitions/ssh.xml.in | 8 ++-- smoketest/configs/basic-vyos | 88 ++++++++++++++++++++++++++++++++++++++++ src/migration-scripts/ssh/1-to-2 | 50 +++++++++++++++++------ 3 files changed, 130 insertions(+), 16 deletions(-) create mode 100644 smoketest/configs/basic-vyos diff --git a/interface-definitions/ssh.xml.in b/interface-definitions/ssh.xml.in index 187e5f8e8..8edbad110 100644 --- a/interface-definitions/ssh.xml.in +++ b/interface-definitions/ssh.xml.in @@ -44,7 +44,7 @@ 3des-cbc aes128-cbc aes192-cbc aes256-cbc rijndael-cbc@lysator.liu.se aes128-ctr aes192-ctr aes256-ctr aes128-gcm@openssh.com aes256-gcm@openssh.com chacha20-poly1305@openssh.com - ^(3des-cbc|aes128-cbc|aes192-cbc|aes256-cbc|rijndael-cbc@lysator.liu.se|aes128-ctr|aes192-ctr|aes256-ctr|aes128-gcm@openssh.com|aes256-gcm@openssh.com|chacha20-poly1305@openssh.com)$ + (3des-cbc|aes128-cbc|aes192-cbc|aes256-cbc|rijndael-cbc@lysator.liu.se|aes128-ctr|aes192-ctr|aes256-ctr|aes128-gcm@openssh.com|aes256-gcm@openssh.com|chacha20-poly1305@openssh.com) @@ -70,7 +70,7 @@ - ^(diffie-hellman-group1-sha1|diffie-hellman-group14-sha1|diffie-hellman-group14-sha256|diffie-hellman-group16-sha512|diffie-hellman-group18-sha512|diffie-hellman-group-exchange-sha1|diffie-hellman-group-exchange-sha256|ecdh-sha2-nistp256|ecdh-sha2-nistp384|ecdh-sha2-nistp521|curve25519-sha256|curve25519-sha256@libssh.org)$ + (diffie-hellman-group1-sha1|diffie-hellman-group14-sha1|diffie-hellman-group14-sha256|diffie-hellman-group16-sha512|diffie-hellman-group18-sha512|diffie-hellman-group-exchange-sha1|diffie-hellman-group-exchange-sha256|ecdh-sha2-nistp256|ecdh-sha2-nistp384|ecdh-sha2-nistp521|curve25519-sha256|curve25519-sha256@libssh.org) @@ -102,7 +102,7 @@ enable logging of failed login attempts - ^(quiet|fatal|error|info|verbose)$ + (quiet|fatal|error|info|verbose) info @@ -115,7 +115,7 @@ hmac-sha1 hmac-sha1-96 hmac-sha2-256 hmac-sha2-512 hmac-md5 hmac-md5-96 umac-64@openssh.com umac-128@openssh.com hmac-sha1-etm@openssh.com hmac-sha1-96-etm@openssh.com hmac-sha2-256-etm@openssh.com hmac-sha2-512-etm@openssh.com hmac-md5-etm@openssh.com hmac-md5-96-etm@openssh.com umac-64-etm@openssh.com umac-128-etm@openssh.com - ^(hmac-sha1|hmac-sha1-96|hmac-sha2-256|hmac-sha2-512|hmac-md5|hmac-md5-96|umac-64@openssh.com|umac-128@openssh.com|hmac-sha1-etm@openssh.com|hmac-sha1-96-etm@openssh.com|hmac-sha2-256-etm@openssh.com|hmac-sha2-512-etm@openssh.com|hmac-md5-etm@openssh.com|hmac-md5-96-etm@openssh.com|umac-64-etm@openssh.com|umac-128-etm@openssh.com)$ + (hmac-sha1|hmac-sha1-96|hmac-sha2-256|hmac-sha2-512|hmac-md5|hmac-md5-96|umac-64@openssh.com|umac-128@openssh.com|hmac-sha1-etm@openssh.com|hmac-sha1-96-etm@openssh.com|hmac-sha2-256-etm@openssh.com|hmac-sha2-512-etm@openssh.com|hmac-md5-etm@openssh.com|hmac-md5-96-etm@openssh.com|umac-64-etm@openssh.com|umac-128-etm@openssh.com) diff --git a/smoketest/configs/basic-vyos b/smoketest/configs/basic-vyos new file mode 100644 index 000000000..493feed5b --- /dev/null +++ b/smoketest/configs/basic-vyos @@ -0,0 +1,88 @@ +interfaces { + ethernet eth0 { + address 192.168.0.1/24 + duplex auto + smp-affinity auto + speed auto + } + ethernet eth1 { + address 100.64.0.0/31 + duplex auto + smp-affinity auto + speed auto + } + loopback lo { + } +} +protocols { + static { + route 0.0.0.0/0 { + next-hop 100.64.0.1 { + } + } + } +} +service { + dhcp-server { + shared-network-name LAN { + authoritative + subnet 192.168.0.0/24 { + default-router 192.168.0.1 + dns-server 192.168.0.1 + domain-name vyos.net + domain-search vyos.net + range LANDynamic { + start 192.168.0.20 + stop 192.168.0.240 + } + } + } + } + dns { + forwarding { + allow-from 192.168.0.0/16 + cache-size 10000 + dnssec off + listen-address 192.168.0.1 + } + } + ssh { + ciphers aes128-ctr,aes192-ctr,aes256-ctr + ciphers chacha20-poly1305@openssh.com,rijndael-cbc@lysator.liu.se + listen-address 192.168.0.1 + key-exchange curve25519-sha256@libssh.org + key-exchange diffie-hellman-group1-sha1,diffie-hellman-group-exchange-sha1,diffie-hellman-group-exchange-sha256 + port 22 + } +} +system { + config-management { + commit-revisions 100 + } + console { + device ttyS0 { + speed 115200 + } + } + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0 + plaintext-password "" + } + } + } + name-server 192.168.0.1 + syslog { + global { + facility all { + level info + } + } + } + time-zone Europe/Berlin +} +/* Warning: Do not remove the following line. */ +/* === vyatta-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack-sync@1:conntrack@1:dhcp-relay@2:dhcp-server@5:dns-forwarding@1:firewall@5:ipsec@5:l2tp@1:mdns@1:nat@4:ntp@1:pptp@1:qos@1:quagga@6:snmp@1:ssh@1:system@9:vrrp@2:wanloadbalance@3:webgui@1:webproxy@1:webproxy@2:zone-policy@1" === */ +/* Release version: 1.2.6 */ diff --git a/src/migration-scripts/ssh/1-to-2 b/src/migration-scripts/ssh/1-to-2 index bc8815753..31c40df16 100755 --- a/src/migration-scripts/ssh/1-to-2 +++ b/src/migration-scripts/ssh/1-to-2 @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-2022 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 @@ -30,26 +30,52 @@ file_name = argv[1] with open(file_name, 'r') as f: config_file = f.read() -base = ['service', 'ssh', 'loglevel'] +base = ['service', 'ssh'] config = ConfigTree(config_file) if not config.exists(base): # Nothing to do exit(0) -else: - # red in configured loglevel and convert it to lower case - tmp = config.return_value(base).lower() +path_loglevel = base + ['loglevel'] +if config.exists(path_loglevel): + # red in configured loglevel and convert it to lower case + tmp = config.return_value(path_loglevel).lower() # VyOS 1.2 had no proper value validation on the CLI thus the # user could use any arbitrary values - sanitize them if tmp not in ['quiet', 'fatal', 'error', 'info', 'verbose']: tmp = 'info' + config.set(path_loglevel, value=tmp) + +# T4273: migrate ssh cipher list to multi node +path_ciphers = base + ['ciphers'] +if config.exists(path_ciphers): + tmp = [] + # get curtrent cipher list - comma delimited + for cipher in config.return_values(path_ciphers): + tmp.extend(cipher.split(',')) + # delete old cipher suite representation + config.delete(path_ciphers) - config.set(base, value=tmp) + for cipher in tmp: + config.set(path_ciphers, value=cipher, replace=False) - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) +# T4273: migrate ssh key-exchange list to multi node +path_kex = base + ['key-exchange'] +if config.exists(path_kex): + tmp = [] + # get curtrent cipher list - comma delimited + for kex in config.return_values(path_kex): + tmp.extend(kex.split(',')) + # delete old cipher suite representation + config.delete(path_kex) + + for kex in tmp: + config.set(path_kex, value=kex, replace=False) + +try: + with open(file_name, 'w') as f: + f.write(config.to_string()) +except OSError as e: + print("Failed to save the modified config: {}".format(e)) + exit(1) -- cgit v1.2.3 From 257345cd152c23a465332dea4af034244007aaa7 Mon Sep 17 00:00:00 2001 From: RageLtMan Date: Mon, 28 Feb 2022 08:32:30 -0500 Subject: open-connect: T4274: extend RADIUS authentication timeout RADIUS authentication can be handled by a variety of mechanisms, including proxy for 2FA systems requiring user interaction with a separate device, token acquisition, or other time-consuming action. Given the delays required for certain 2FA implementations, a thirty second timeout can range from onerous to untenable. Accomodate the 2FA time requirements by extending the hard-coded RADIUS time limit from 30 seconds to 240. Co-authored-by: RageLtMan --- interface-definitions/vpn_openconnect.xml.in | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/interface-definitions/vpn_openconnect.xml.in b/interface-definitions/vpn_openconnect.xml.in index 3fc34bacc..f418f5d75 100644 --- a/interface-definitions/vpn_openconnect.xml.in +++ b/interface-definitions/vpn_openconnect.xml.in @@ -40,13 +40,13 @@ Session timeout - u32:1-30 - Session timeout in seconds + u32:1-240 + Session timeout in seconds (default: 2) - + - Timeout must be between 1 and 30 seconds + Timeout must be between 1 and 240 seconds 2 -- cgit v1.2.3 From a37f0db280ffc662e1b51ec2ae479ff6318b0b3c Mon Sep 17 00:00:00 2001 From: srividya0208 Date: Mon, 28 Feb 2022 12:04:31 -0500 Subject: ipsec prefix: T4275: Incorrect val_help for local/remote prefix It accepts network as the input value but the completion help is showing ip address --- interface-definitions/vpn_ipsec.xml.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface-definitions/vpn_ipsec.xml.in b/interface-definitions/vpn_ipsec.xml.in index 885bac979..0ad69c637 100644 --- a/interface-definitions/vpn_ipsec.xml.in +++ b/interface-definitions/vpn_ipsec.xml.in @@ -879,11 +879,11 @@ Local IPv4 or IPv6 pool prefix exclusions - ipv4 + ipv4net Local IPv4 pool prefix exclusion - ipv6 + ipv6net Local IPv6 pool prefix exclusion -- cgit v1.2.3 From ff7e2cd622cf3679cd9265b2cb766395a1830f50 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Fri, 25 Feb 2022 16:05:03 -0600 Subject: configtree: T4235: add utility get_subtree --- python/vyos/configtree.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/python/vyos/configtree.py b/python/vyos/configtree.py index 866f24e47..0ec15cf40 100644 --- a/python/vyos/configtree.py +++ b/python/vyos/configtree.py @@ -126,6 +126,10 @@ class ConfigTree(object): self.__set_tag.argtypes = [c_void_p, c_char_p] self.__set_tag.restype = c_int + self.__get_subtree = self.__lib.get_subtree + self.__get_subtree.argtypes = [c_void_p, c_char_p] + self.__get_subtree.restype = c_void_p + self.__destroy = self.__lib.destroy self.__destroy.argtypes = [c_void_p] @@ -291,6 +295,14 @@ class ConfigTree(object): else: raise ConfigTreeError("Path [{}] doesn't exist".format(path_str)) + def get_subtree(self, path, with_node=False): + check_path(path) + path_str = " ".join(map(str, path)).encode() + + res = self.__get_subtree(self.__config, path_str, with_node) + subt = ConfigTree(address=res) + return subt + class Diff: def __init__(self, left, right, path=[], libpath=LIBPATH): if not (isinstance(left, ConfigTree) and isinstance(right, ConfigTree)): -- cgit v1.2.3 From 3d28528ff84b4e874faf80028709bd08b2956933 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Fri, 25 Feb 2022 16:10:12 -0600 Subject: configtree: T4235: simplification of diff_tree class The return value of diff_tree is now a single config_tree, with initial children of names: ["add", "delete", "inter"] containing the config sub-trees of added paths; deleted paths; and intersection, respectively. The simplifies dumping to json, and checking existence of paths, hence, of node changes. --- python/vyos/configtree.py | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/python/vyos/configtree.py b/python/vyos/configtree.py index 0ec15cf40..f5e697137 100644 --- a/python/vyos/configtree.py +++ b/python/vyos/configtree.py @@ -15,7 +15,7 @@ import re import json -from ctypes import cdll, c_char_p, c_void_p, c_int, POINTER +from ctypes import cdll, c_char_p, c_void_p, c_int LIBPATH = '/usr/lib/libvyosconfig.so.0' @@ -303,7 +303,7 @@ class ConfigTree(object): subt = ConfigTree(address=res) return subt -class Diff: +class DiffTree: def __init__(self, left, right, path=[], libpath=LIBPATH): if not (isinstance(left, ConfigTree) and isinstance(right, ConfigTree)): raise TypeError("Arguments must be instances of ConfigTree") @@ -312,21 +312,28 @@ class Diff: raise ConfigTreeError(f"Path {path} doesn't exist in lhs tree") if not right.exists(path): raise ConfigTreeError(f"Path {path} doesn't exist in rhs tree") + self.left = left self.right = right + self.__lib = cdll.LoadLibrary(libpath) + + self.__diff_tree = self.__lib.diff_tree + self.__diff_tree.argtypes = [c_char_p, c_void_p, c_void_p] + self.__diff_tree.restype = c_void_p + check_path(path) path_str = " ".join(map(str, path)).encode() - df = cdll.LoadLibrary(libpath).diffs - df.restype = POINTER(c_void_p * 3) - res = list(df(path_str, left._get_config(), right._get_config()).contents) - self._diff = {'add': ConfigTree(address=res[0]), - 'del': ConfigTree(address=res[1]), - 'int': ConfigTree(address=res[2]) } - - self.add = self._diff['add'] - self.delete = self._diff['del'] - self.inter = self._diff['int'] + res = self.__diff_tree(path_str, left._get_config(), right._get_config()) + + # full diff config_tree and python dict representation + self.full = ConfigTree(address=res) + self.dict = json.loads(self.full.to_json()) + + # config_tree sub-trees + self.add = self.full.get_subtree(['add']) + self.delete = self.full.get_subtree(['delete']) + self.inter = self.full.get_subtree(['inter']) def to_commands(self): add = self.add.to_commands() -- cgit v1.2.3 From 4625fd41f99ddf77c104a657cd90a1ddf5449dd8 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Fri, 25 Feb 2022 16:12:29 -0600 Subject: configtree: T4235: allow empty arguments --- python/vyos/configtree.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/python/vyos/configtree.py b/python/vyos/configtree.py index f5e697137..5ba829a4c 100644 --- a/python/vyos/configtree.py +++ b/python/vyos/configtree.py @@ -305,6 +305,10 @@ class ConfigTree(object): class DiffTree: def __init__(self, left, right, path=[], libpath=LIBPATH): + if left is None: + left = ConfigTree(config_string='\n') + if right is None: + right = ConfigTree(config_string='\n') if not (isinstance(left, ConfigTree) and isinstance(right, ConfigTree)): raise TypeError("Arguments must be instances of ConfigTree") if path: -- cgit v1.2.3 From 193cbd15ba39a41614c63b997e6a62254589158a Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Sun, 27 Feb 2022 10:05:40 -0600 Subject: configtree: T4235: distinguish sub(-tract) tree from delete tree The DiffTree class maintains both the 'sub'(-tract) configtree, containing all paths in the LHS of the comparison that are not in the RHS, and the 'delete' configtree: the delete tree is the minimal subtree containing only the first node of a path not present in the RHS. It is the delete tree that is needed to produce 'delete' commands for config mode, whereas the 'sub' tree contains full information, needed for recursively detecting changes to a node. --- python/vyos/configtree.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/python/vyos/configtree.py b/python/vyos/configtree.py index 5ba829a4c..e9cdb69e4 100644 --- a/python/vyos/configtree.py +++ b/python/vyos/configtree.py @@ -326,8 +326,13 @@ class DiffTree: self.__diff_tree.argtypes = [c_char_p, c_void_p, c_void_p] self.__diff_tree.restype = c_void_p + self.__trim_tree = self.__lib.trim_tree + self.__trim_tree.argtypes = [c_void_p, c_void_p] + self.__trim_tree.restype = c_void_p + check_path(path) path_str = " ".join(map(str, path)).encode() + res = self.__diff_tree(path_str, left._get_config(), right._get_config()) # full diff config_tree and python dict representation @@ -336,9 +341,14 @@ class DiffTree: # config_tree sub-trees self.add = self.full.get_subtree(['add']) - self.delete = self.full.get_subtree(['delete']) + self.sub = self.full.get_subtree(['sub']) self.inter = self.full.get_subtree(['inter']) + # trim sub(-tract) tree to get delete tree for commands + ref = self.right.get_subtree(path, with_node=True) if path else self.right + res = self.__trim_tree(self.sub._get_config(), ref._get_config()) + self.delete = ConfigTree(address=res) + def to_commands(self): add = self.add.to_commands() delete = self.delete.to_commands(op="delete") -- cgit v1.2.3 From 42c011224e5aef3c27f9de6b5a74e594a404131e Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 1 Mar 2022 19:09:12 +0100 Subject: flow-accounting: T4277: support sending flow-data via VRF interface It should be possible to send the gathered data via a VRF bound interface to the collector. This is somehow related to T3981 but it's the opposite side of the netflow process. set system flow-accounting vrf --- data/templates/netflow/uacctd.conf.tmpl | 74 ---------------------- data/templates/pmacct/override.conf.tmpl | 17 +++++ data/templates/pmacct/uacctd.conf.tmpl | 74 ++++++++++++++++++++++ interface-definitions/flow-accounting-conf.xml.in | 1 + .../scripts/cli/test_system_flow-accounting.py | 5 +- src/conf_mode/flow_accounting_conf.py | 14 ++-- .../systemd/system/uacctd.service.d/override.conf | 14 ---- 7 files changed, 106 insertions(+), 93 deletions(-) delete mode 100644 data/templates/netflow/uacctd.conf.tmpl create mode 100644 data/templates/pmacct/override.conf.tmpl create mode 100644 data/templates/pmacct/uacctd.conf.tmpl delete mode 100644 src/etc/systemd/system/uacctd.service.d/override.conf diff --git a/data/templates/netflow/uacctd.conf.tmpl b/data/templates/netflow/uacctd.conf.tmpl deleted file mode 100644 index f81002dc1..000000000 --- a/data/templates/netflow/uacctd.conf.tmpl +++ /dev/null @@ -1,74 +0,0 @@ -# Genereated from VyOS configuration -daemonize: true -promisc: false -pidfile: /run/pmacct/uacctd.pid -uacctd_group: 2 -uacctd_nl_size: 2097152 -snaplen: {{ packet_length }} -aggregate: in_iface{{ ',out_iface' if enable_egress is defined }},src_mac,dst_mac,vlan,src_host,dst_host,src_port,dst_port,proto,tos,flows -{% set pipe_size = buffer_size | int *1024 *1024 %} -plugin_pipe_size: {{ pipe_size }} -{# We need an integer division (//) without any remainder or fraction #} -plugin_buffer_size: {{ pipe_size // 1000 }} -{% if syslog_facility is defined and syslog_facility is not none %} -syslog: {{ syslog_facility }} -{% endif %} -{% if disable_imt is not defined %} -imt_path: /tmp/uacctd.pipe -imt_mem_pools_number: 169 -{% endif %} - -{% set plugin = [] %} -{% if disable_imt is not defined %} -{% set plugin = ['memory'] %} -{% endif %} -{% if netflow is defined and netflow.server is defined and netflow.server is not none %} -{% for server in netflow.server %} -{% set plugin = plugin.append('nfprobe[nf_' ~ server ~ ']') %} -{% endfor %} -{% endif %} -{% if sflow is defined and sflow.server is defined and sflow.server is not none %} -{% for server in sflow.server %} -{% set plugin = plugin.append('sfprobe[sf_' ~ server ~ ']') %} -{% endfor %} -{% endif %} -plugins: {{ plugin | join(',') }} - -{% if netflow is defined and netflow.server is defined and netflow.server is not none %} -# NetFlow servers -{% for server, server_config in netflow.server.items() %} -nfprobe_receiver[nf_{{ server }}]: {{ server }}:{{ server_config.port }} -nfprobe_version[nf_{{ server }}]: {{ netflow.version }} -{% if netflow.engine_id is defined and netflow.engine_id is not none %} -nfprobe_engine[nf_{{ server }}]: {{ netflow.engine_id }} -{% endif %} -{% if netflow.max_flows is defined and netflow.max_flows is not none %} -nfprobe_maxflows[nf_{{ server }}]: {{ netflow.max_flows }} -{% endif %} -{% if netflow.sampling_rate is defined and netflow.sampling_rate is not none %} -sampling_rate[nf_{{ server }}]: {{ netflow.sampling_rate }} -{% endif %} -{% if netflow.source_address is defined and netflow.source_address is not none %} -nfprobe_source_ip[nf_{{ server }}]: {{ netflow.source_address }} -{% endif %} -{% if netflow.timeout is defined and netflow.timeout is not none %} -nfprobe_timeouts[nf_{{ server }}]: expint={{ netflow.timeout.expiry_interval }}:general={{ netflow.timeout.flow_generic }}:icmp={{ netflow.timeout.icmp }}:maxlife={{ netflow.timeout.max_active_life }}:tcp.fin={{ netflow.timeout.tcp_fin }}:tcp={{ netflow.timeout.tcp_generic }}:tcp.rst={{ netflow.timeout.tcp_rst }}:udp={{ netflow.timeout.udp }} -{% endif %} - -{% endfor %} -{% endif %} - -{% if sflow is defined and sflow.server is defined and sflow.server is not none %} -# sFlow servers -{% for server, server_config in sflow.server.items() %} -sfprobe_receiver[sf_{{ server }}]: {{ server }}:{{ server_config.port }} -sfprobe_agentip[sf_{{ server }}]: {{ sflow.agent_address }} -{% if sflow.sampling_rate is defined and sflow.sampling_rate is not none %} -sampling_rate[sf_{{ server }}]: {{ sflow.sampling_rate }} -{% endif %} -{% if sflow.source_address is defined and sflow.source_address is not none %} -sfprobe_source_ip[sf_{{ server }}]: {{ sflow.source_address }} -{% endif %} - -{% endfor %} -{% endif %} diff --git a/data/templates/pmacct/override.conf.tmpl b/data/templates/pmacct/override.conf.tmpl new file mode 100644 index 000000000..216927666 --- /dev/null +++ b/data/templates/pmacct/override.conf.tmpl @@ -0,0 +1,17 @@ +{% set vrf_command = 'ip vrf exec ' + vrf + ' ' if vrf is defined else '' %} +[Unit] +After= +After=vyos-router.service +ConditionPathExists= +ConditionPathExists=/run/pmacct/uacctd.conf + +[Service] +EnvironmentFile= +ExecStart= +ExecStart={{vrf_command}}/usr/sbin/uacctd -f /run/pmacct/uacctd.conf +WorkingDirectory= +WorkingDirectory=/run/pmacct +PIDFile= +PIDFile=/run/pmacct/uacctd.pid +Restart=always +RestartSec=10 diff --git a/data/templates/pmacct/uacctd.conf.tmpl b/data/templates/pmacct/uacctd.conf.tmpl new file mode 100644 index 000000000..b58f7c796 --- /dev/null +++ b/data/templates/pmacct/uacctd.conf.tmpl @@ -0,0 +1,74 @@ +# Genereated from VyOS configuration +daemonize: true +promisc: false +pidfile: /run/pmacct/uacctd.pid +uacctd_group: 2 +uacctd_nl_size: 2097152 +snaplen: {{ packet_length }} +aggregate: in_iface{{ ',out_iface' if enable_egress is defined }},src_mac,dst_mac,vlan,src_host,dst_host,src_port,dst_port,proto,tos,flows +{% set pipe_size = buffer_size | int *1024 *1024 %} +plugin_pipe_size: {{ pipe_size }} +{# We need an integer division (//) without any remainder or fraction #} +plugin_buffer_size: {{ pipe_size // 1000 }} +{% if syslog_facility is defined and syslog_facility is not none %} +syslog: {{ syslog_facility }} +{% endif %} +{% if disable_imt is not defined %} +imt_path: /tmp/uacctd.pipe +imt_mem_pools_number: 169 +{% endif %} + +{% set plugin = [] %} +{% if netflow is defined and netflow.server is defined and netflow.server is not none %} +{% for server in netflow.server %} +{% set _ = plugin.append('nfprobe[nf_' ~ server ~ ']') %} +{% endfor %} +{% endif %} +{% if sflow is defined and sflow.server is defined and sflow.server is not none %} +{% for server in sflow.server %} +{% set _ = plugin.append('sfprobe[sf_' ~ server ~ ']') %} +{% endfor %} +{% endif %} +{% if disable_imt is not defined %} +{% set _ = plugin.append('memory') %} +{% endif %} +plugins: {{ plugin | join(',') }} + +{% if netflow is defined and netflow.server is defined and netflow.server is not none %} +# NetFlow servers +{% for server, server_config in netflow.server.items() %} +nfprobe_receiver[nf_{{ server }}]: {{ server }}:{{ server_config.port }} +nfprobe_version[nf_{{ server }}]: {{ netflow.version }} +{% if netflow.engine_id is defined and netflow.engine_id is not none %} +nfprobe_engine[nf_{{ server }}]: {{ netflow.engine_id }} +{% endif %} +{% if netflow.max_flows is defined and netflow.max_flows is not none %} +nfprobe_maxflows[nf_{{ server }}]: {{ netflow.max_flows }} +{% endif %} +{% if netflow.sampling_rate is defined and netflow.sampling_rate is not none %} +sampling_rate[nf_{{ server }}]: {{ netflow.sampling_rate }} +{% endif %} +{% if netflow.source_address is defined and netflow.source_address is not none %} +nfprobe_source_ip[nf_{{ server }}]: {{ netflow.source_address }} +{% endif %} +{% if netflow.timeout is defined and netflow.timeout is not none %} +nfprobe_timeouts[nf_{{ server }}]: expint={{ netflow.timeout.expiry_interval }}:general={{ netflow.timeout.flow_generic }}:icmp={{ netflow.timeout.icmp }}:maxlife={{ netflow.timeout.max_active_life }}:tcp.fin={{ netflow.timeout.tcp_fin }}:tcp={{ netflow.timeout.tcp_generic }}:tcp.rst={{ netflow.timeout.tcp_rst }}:udp={{ netflow.timeout.udp }} +{% endif %} + +{% endfor %} +{% endif %} + +{% if sflow is defined and sflow.server is defined and sflow.server is not none %} +# sFlow servers +{% for server, server_config in sflow.server.items() %} +sfprobe_receiver[sf_{{ server }}]: {{ server }}:{{ server_config.port }} +sfprobe_agentip[sf_{{ server }}]: {{ sflow.agent_address }} +{% if sflow.sampling_rate is defined and sflow.sampling_rate is not none %} +sampling_rate[sf_{{ server }}]: {{ sflow.sampling_rate }} +{% endif %} +{% if sflow.source_address is defined and sflow.source_address is not none %} +sfprobe_source_ip[sf_{{ server }}]: {{ sflow.source_address }} +{% endif %} + +{% endfor %} +{% endif %} diff --git a/interface-definitions/flow-accounting-conf.xml.in b/interface-definitions/flow-accounting-conf.xml.in index 05cf5e170..133e45c72 100644 --- a/interface-definitions/flow-accounting-conf.xml.in +++ b/interface-definitions/flow-accounting-conf.xml.in @@ -431,6 +431,7 @@ #include + #include diff --git a/smoketest/scripts/cli/test_system_flow-accounting.py b/smoketest/scripts/cli/test_system_flow-accounting.py index 857df1be6..84f17bcb0 100755 --- a/smoketest/scripts/cli/test_system_flow-accounting.py +++ b/smoketest/scripts/cli/test_system_flow-accounting.py @@ -39,6 +39,9 @@ class TestSystemFlowAccounting(VyOSUnitTestSHIM.TestCase): cls.cli_delete(cls, base_path) def tearDown(self): + # after service removal process must no longer run + self.assertTrue(process_named_running(PROCESS_NAME)) + self.cli_delete(base_path) self.cli_commit() @@ -213,9 +216,9 @@ class TestSystemFlowAccounting(VyOSUnitTestSHIM.TestCase): uacctd = read_file(uacctd_conf) tmp = [] - tmp.append('memory') for server, server_config in netflow_server.items(): tmp.append(f'nfprobe[nf_{server}]') + tmp.append('memory') self.assertIn('plugins: ' + ','.join(tmp), uacctd) for server, server_config in netflow_server.items(): diff --git a/src/conf_mode/flow_accounting_conf.py b/src/conf_mode/flow_accounting_conf.py index 975f19acf..25bf54790 100755 --- a/src/conf_mode/flow_accounting_conf.py +++ b/src/conf_mode/flow_accounting_conf.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018-2021 VyOS maintainers and contributors +# Copyright (C) 2018-2022 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 @@ -27,6 +27,7 @@ from vyos.configdict import dict_merge from vyos.ifconfig import Section from vyos.ifconfig import Interface from vyos.template import render +from vyos.util import call from vyos.util import cmd from vyos.validate import is_addr_assigned from vyos.xml import defaults @@ -35,6 +36,8 @@ from vyos import airbag airbag.enable() uacctd_conf_path = '/run/pmacct/uacctd.conf' +systemd_service = 'uacctd.service' +systemd_override = f'/etc/systemd/system/{systemd_service}.d/override.conf' nftables_nflog_table = 'raw' nftables_nflog_chain = 'VYOS_CT_PREROUTING_HOOK' egress_nftables_nflog_table = 'inet mangle' @@ -236,7 +239,10 @@ def generate(flow_config): if not flow_config: return None - render(uacctd_conf_path, 'netflow/uacctd.conf.tmpl', flow_config) + render(uacctd_conf_path, 'pmacct/uacctd.conf.tmpl', flow_config) + render(systemd_override, 'pmacct/override.conf.tmpl', flow_config) + # Reload systemd manager configuration + call('systemctl daemon-reload') def apply(flow_config): action = 'restart' @@ -246,13 +252,13 @@ def apply(flow_config): _nftables_config([], 'egress') # Stop flow-accounting daemon and remove configuration file - cmd('systemctl stop uacctd.service') + call(f'systemctl stop {systemd_service}') if os.path.exists(uacctd_conf_path): os.unlink(uacctd_conf_path) return # Start/reload flow-accounting daemon - cmd(f'systemctl restart uacctd.service') + call(f'systemctl restart {systemd_service}') # configure nftables rules for defined interfaces if 'interface' in flow_config: diff --git a/src/etc/systemd/system/uacctd.service.d/override.conf b/src/etc/systemd/system/uacctd.service.d/override.conf deleted file mode 100644 index 38bcce515..000000000 --- a/src/etc/systemd/system/uacctd.service.d/override.conf +++ /dev/null @@ -1,14 +0,0 @@ -[Unit] -After= -After=vyos-router.service -ConditionPathExists= -ConditionPathExists=/run/pmacct/uacctd.conf - -[Service] -EnvironmentFile= -ExecStart= -ExecStart=/usr/sbin/uacctd -f /run/pmacct/uacctd.conf -WorkingDirectory= -WorkingDirectory=/run/pmacct -PIDFile= -PIDFile=/run/pmacct/uacctd.pid -- cgit v1.2.3 From e5d04b20be0ef270d20f1d5ac9203b3a03649135 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 2 Mar 2022 07:59:47 -0600 Subject: configdiff: T4260: add support for diff_tree class Add support for the configtree diff algorithm. A new function ConfigDiff().is_node_changed(path) -> bool is added to recursively detect changes in the tree below the node at path; existing functions take the keyword argument 'recursive: bool' to apply the algorithm in place of the existing, non-recursive, comparison. --- python/vyos/configdiff.py | 84 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 80 insertions(+), 4 deletions(-) diff --git a/python/vyos/configdiff.py b/python/vyos/configdiff.py index 4ad7443d7..9185575df 100644 --- a/python/vyos/configdiff.py +++ b/python/vyos/configdiff.py @@ -16,6 +16,7 @@ from enum import IntFlag, auto from vyos.config import Config +from vyos.configtree import DiffTree from vyos.configdict import dict_merge from vyos.configdict import list_diff from vyos.util import get_sub_dict, mangle_dict_keys @@ -38,6 +39,8 @@ class Diff(IntFlag): ADD = auto() STABLE = auto() +ALL = Diff.MERGE | Diff.DELETE | Diff.ADD | Diff.STABLE + requires_effective = [enum_to_key(Diff.DELETE)] target_defaults = [enum_to_key(Diff.MERGE)] @@ -75,19 +78,24 @@ def get_config_diff(config, key_mangling=None): isinstance(key_mangling[1], str)): raise ValueError("key_mangling must be a tuple of two strings") - return ConfigDiff(config, key_mangling) + diff_t = DiffTree(config._running_config, config._session_config) + + return ConfigDiff(config, key_mangling, diff_tree=diff_t) class ConfigDiff(object): """ The class of config changes as represented by comparison between the session config dict and the effective config dict. """ - def __init__(self, config, key_mangling=None): + def __init__(self, config, key_mangling=None, diff_tree=None): self._level = config.get_level() self._session_config_dict = config.get_cached_root_dict(effective=False) self._effective_config_dict = config.get_cached_root_dict(effective=True) self._key_mangling = key_mangling + self._diff_tree = diff_tree + self._diff_dict = diff_tree.dict if diff_tree else {} + # mirrored from Config; allow path arguments relative to level def _make_path(self, path): if isinstance(path, str): @@ -136,6 +144,15 @@ class ConfigDiff(object): self._key_mangling[1]) return config_dict + def is_node_changed(self, path=[]): + if self._diff_tree is None: + raise NotImplementedError("diff_tree class not available") + + if (self._diff_tree.add.exists(self._make_path(path)) or + self._diff_tree.sub.exists(self._make_path(path))): + return True + return False + def get_child_nodes_diff_str(self, path=[]): ret = {'add': {}, 'change': {}, 'delete': {}} @@ -164,7 +181,8 @@ class ConfigDiff(object): return ret - def get_child_nodes_diff(self, path=[], expand_nodes=Diff(0), no_defaults=False): + def get_child_nodes_diff(self, path=[], expand_nodes=Diff(0), no_defaults=False, + recursive=False): """ Args: path (str|list): config path @@ -174,6 +192,8 @@ class ConfigDiff(object): value no_detaults=False: if expand_nodes & Diff.MERGE, do not merge default values to ret['merge'] + recursive: if true, use config_tree diff algorithm provided by + diff_tree class Returns: dict of lists, representing differences between session and effective config, under path @@ -184,6 +204,34 @@ class ConfigDiff(object): """ session_dict = get_sub_dict(self._session_config_dict, self._make_path(path), get_first_key=True) + + if recursive: + if self._diff_tree is None: + raise NotImplementedError("diff_tree class not available") + else: + add = get_sub_dict(self._diff_tree.dict, ['add'], get_first_key=True) + sub = get_sub_dict(self._diff_tree.dict, ['sub'], get_first_key=True) + inter = get_sub_dict(self._diff_tree.dict, ['inter'], get_first_key=True) + ret = {} + ret[enum_to_key(Diff.MERGE)] = session_dict + ret[enum_to_key(Diff.DELETE)] = get_sub_dict(sub, self._make_path(path), + get_first_key=True) + ret[enum_to_key(Diff.ADD)] = get_sub_dict(add, self._make_path(path), + get_first_key=True) + ret[enum_to_key(Diff.STABLE)] = get_sub_dict(inter, self._make_path(path), + get_first_key=True) + for e in Diff: + k = enum_to_key(e) + if not (e & expand_nodes): + ret[k] = list(ret[k]) + else: + if self._key_mangling: + ret[k] = self._mangle_dict_keys(ret[k]) + if k in target_defaults and not no_defaults: + default_values = defaults(self._make_path(path)) + ret[k] = dict_merge(default_values, ret[k]) + return ret + effective_dict = get_sub_dict(self._effective_config_dict, self._make_path(path), get_first_key=True) @@ -209,7 +257,8 @@ class ConfigDiff(object): return ret - def get_node_diff(self, path=[], expand_nodes=Diff(0), no_defaults=False): + def get_node_diff(self, path=[], expand_nodes=Diff(0), no_defaults=False, + recursive=False): """ Args: path (str|list): config path @@ -219,6 +268,8 @@ class ConfigDiff(object): value no_detaults=False: if expand_nodes & Diff.MERGE, do not merge default values to ret['merge'] + recursive: if true, use config_tree diff algorithm provided by + diff_tree class Returns: dict of lists, representing differences between session and effective config, at path @@ -228,6 +279,31 @@ class ConfigDiff(object): dict['stable'] = config values in both session and effective """ session_dict = get_sub_dict(self._session_config_dict, self._make_path(path)) + + if recursive: + if self._diff_tree is None: + raise NotImplementedError("diff_tree class not available") + else: + add = get_sub_dict(self._diff_tree.dict, ['add'], get_first_key=True) + sub = get_sub_dict(self._diff_tree.dict, ['sub'], get_first_key=True) + inter = get_sub_dict(self._diff_tree.dict, ['inter'], get_first_key=True) + ret = {} + ret[enum_to_key(Diff.MERGE)] = session_dict + ret[enum_to_key(Diff.DELETE)] = get_sub_dict(sub, self._make_path(path)) + ret[enum_to_key(Diff.ADD)] = get_sub_dict(add, self._make_path(path)) + ret[enum_to_key(Diff.STABLE)] = get_sub_dict(inter, self._make_path(path)) + for e in Diff: + k = enum_to_key(e) + if not (e & expand_nodes): + ret[k] = list(ret[k]) + else: + if self._key_mangling: + ret[k] = self._mangle_dict_keys(ret[k]) + if k in target_defaults and not no_defaults: + default_values = defaults(self._make_path(path)) + ret[k] = dict_merge(default_values, ret[k]) + return ret + effective_dict = get_sub_dict(self._effective_config_dict, self._make_path(path)) ret = _key_sets_from_dicts(session_dict, effective_dict) -- cgit v1.2.3 From 0e23fc10581cde7b31101990566f59eded826233 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 3 Mar 2022 18:15:32 +0100 Subject: interface: T4203: switch to new recursive node_changed() implementation --- python/vyos/configdict.py | 42 ++++++++++----------------------------- python/vyos/ifconfig/interface.py | 4 ++-- 2 files changed, 13 insertions(+), 33 deletions(-) diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index aea22c0c9..b8f428c1c 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -136,7 +136,7 @@ def leaf_node_changed(conf, path): return None -def node_changed(conf, path, key_mangling=None): +def node_changed(conf, path, key_mangling=None, recursive=False): """ Check if a leaf node was altered. If it has been altered - values has been changed, or it was added/removed, we will return the old value. If nothing @@ -146,7 +146,7 @@ def node_changed(conf, path, key_mangling=None): D = get_config_diff(conf, key_mangling) D.set_level(conf.get_level()) # get_child_nodes() will return dict_keys(), mangle this into a list with PEP448 - keys = D.get_child_nodes_diff(path, expand_nodes=Diff.DELETE)['delete'].keys() + keys = D.get_child_nodes_diff(path, expand_nodes=Diff.DELETE, recursive=recursive)['delete'].keys() return list(keys) def get_removed_vlans(conf, dict): @@ -444,13 +444,8 @@ def get_interface_dict(config, base, ifname=''): if bond: dict.update({'is_bond_member' : bond}) # Check if any DHCP options changed which require a client restat - for leaf_node in ['client-id', 'default-route-distance', 'host-name', - 'no-default-route', 'vendor-class-id']: - dhcp = leaf_node_changed(config, ['dhcp-options', leaf_node]) - if dhcp: - dict.update({'dhcp_options_old' : dhcp}) - # one option is suffiecient to set 'dhcp_options_old' key - break + dhcp = node_changed(config, ['dhcp-options'], recursive=True) + if dhcp: dict.update({'dhcp_options_changed' : ''}) # Some interfaces come with a source_interface which must also not be part # of any other bond or bridge interface as it is exclusivly assigned as the @@ -506,13 +501,8 @@ def get_interface_dict(config, base, ifname=''): if bridge: dict['vif'][vif].update({'is_bridge_member' : bridge}) # Check if any DHCP options changed which require a client restat - for leaf_node in ['client-id', 'default-route-distance', 'host-name', - 'no-default-route', 'vendor-class-id']: - dhcp = leaf_node_changed(config, ['vif', vif, 'dhcp-options', leaf_node]) - if dhcp: - dict['vif'][vif].update({'dhcp_options_old' : dhcp}) - # one option is suffiecient to set 'dhcp_options_old' key - break + dhcp = node_changed(config, ['vif', vif, 'dhcp-options'], recursive=True) + if dhcp: dict['vif'][vif].update({'dhcp_options_changed' : ''}) for vif_s, vif_s_config in dict.get('vif_s', {}).items(): default_vif_s_values = defaults(base + ['vif-s']) @@ -547,13 +537,8 @@ def get_interface_dict(config, base, ifname=''): if bridge: dict['vif_s'][vif_s].update({'is_bridge_member' : bridge}) # Check if any DHCP options changed which require a client restat - for leaf_node in ['client-id', 'default-route-distance', 'host-name', - 'no-default-route', 'vendor-class-id']: - dhcp = leaf_node_changed(config, ['vif-s', vif_s, 'dhcp-options', leaf_node]) - if dhcp: - dict['vif_s'][vif_s].update({'dhcp_options_old' : dhcp}) - # one option is suffiecient to set 'dhcp_options_old' key - break + dhcp = node_changed(config, ['vif-s', vif_s, 'dhcp-options'], recursive=True) + if dhcp: dict['vif_s'][vif_s].update({'dhcp_options_changed' : ''}) for vif_c, vif_c_config in vif_s_config.get('vif_c', {}).items(): default_vif_c_values = defaults(base + ['vif-s', 'vif-c']) @@ -589,14 +574,9 @@ def get_interface_dict(config, base, ifname=''): {'is_bridge_member' : bridge}) # Check if any DHCP options changed which require a client restat - for leaf_node in ['client-id', 'default-route-distance', 'host-name', - 'no-default-route', 'vendor-class-id']: - dhcp = leaf_node_changed(config, ['vif-s', vif_s, 'vif-c', vif_c, - 'dhcp-options', leaf_node]) - if dhcp: - dict['vif_s'][vif_s]['vif_c'][vif_c].update({'dhcp_options_old' : dhcp}) - # one option is suffiecient to set 'dhcp_options_old' key - break + dhcp = leaf_node_changed(config, ['vif-s', vif_s, 'vif-c', vif_c, + 'dhcp-options'], recursive=True) + if dhcp: dict['vif_s'][vif_s]['vif_c'][vif_c].update({'dhcp_options_changed' : ''}) # Check vif, vif-s/vif-c VLAN interfaces for removal dict = get_removed_vlans(config, dict) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index cf1887bf6..8c5ff576e 100755 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -1,4 +1,4 @@ -# Copyright 2019-2021 VyOS maintainers and contributors +# Copyright 2019-2022 VyOS maintainers and contributors # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -1250,7 +1250,7 @@ class Interface(Control): # the old lease is released a new one is acquired (T4203). We will # only restart DHCP client if it's option changed, or if it's not # running, but it should be running (e.g. on system startup) - if 'dhcp_options_old' in self._config or not is_systemd_service_active(systemd_service): + if 'dhcp_options_changed' in self._config or not is_systemd_service_active(systemd_service): return self._cmd(f'systemctl restart {systemd_service}') return None else: -- cgit v1.2.3 From 9d2fa6f85847bbb59af42809c50e4547542e4845 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 3 Mar 2022 19:10:07 +0100 Subject: static: T4283: fix help string for route/route6 --- interface-definitions/include/static/static-route.xml.i | 2 +- interface-definitions/include/static/static-route6.xml.i | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/interface-definitions/include/static/static-route.xml.i b/interface-definitions/include/static/static-route.xml.i index 21babc015..903915066 100644 --- a/interface-definitions/include/static/static-route.xml.i +++ b/interface-definitions/include/static/static-route.xml.i @@ -1,7 +1,7 @@ - VRF static IPv4 route + Static IPv4 route ipv4net IPv4 static route diff --git a/interface-definitions/include/static/static-route6.xml.i b/interface-definitions/include/static/static-route6.xml.i index 0ea995588..e705c45fa 100644 --- a/interface-definitions/include/static/static-route6.xml.i +++ b/interface-definitions/include/static/static-route6.xml.i @@ -1,7 +1,7 @@ - VRF static IPv6 route + Static IPv6 route ipv6net IPv6 static route -- cgit v1.2.3 From e3f86ce0d65fe8fe0c5eebebdfd3ab3723e2e539 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 3 Mar 2022 19:10:38 +0100 Subject: static: T4283: create re-usable XML interface definitions for blackhole --- .../include/static/static-route-blackhole.xml.i | 3 ++- .../include/static/static-route-tag.xml.i | 14 ++++++++++++++ .../include/static/static-route.xml.i | 21 +-------------------- .../include/static/static-route6.xml.i | 21 +-------------------- 4 files changed, 18 insertions(+), 41 deletions(-) create mode 100644 interface-definitions/include/static/static-route-tag.xml.i diff --git a/interface-definitions/include/static/static-route-blackhole.xml.i b/interface-definitions/include/static/static-route-blackhole.xml.i index f2ad23e69..487f775f5 100644 --- a/interface-definitions/include/static/static-route-blackhole.xml.i +++ b/interface-definitions/include/static/static-route-blackhole.xml.i @@ -1,10 +1,11 @@ - Silently discard packets when matched + Silently discard pkts when matched #include + #include diff --git a/interface-definitions/include/static/static-route-tag.xml.i b/interface-definitions/include/static/static-route-tag.xml.i new file mode 100644 index 000000000..24bfa732e --- /dev/null +++ b/interface-definitions/include/static/static-route-tag.xml.i @@ -0,0 +1,14 @@ + + + + Tag value for this route + + u32:1-4294967295 + Tag value for this route + + + + + + + diff --git a/interface-definitions/include/static/static-route.xml.i b/interface-definitions/include/static/static-route.xml.i index 903915066..8433703a5 100644 --- a/interface-definitions/include/static/static-route.xml.i +++ b/interface-definitions/include/static/static-route.xml.i @@ -11,26 +11,7 @@ - - - Silently discard pkts when matched - - - #include - - - Tag value for this route - - u32:1-4294967295 - Tag value for this route - - - - - - - - + #include #include diff --git a/interface-definitions/include/static/static-route6.xml.i b/interface-definitions/include/static/static-route6.xml.i index e705c45fa..124b2b062 100644 --- a/interface-definitions/include/static/static-route6.xml.i +++ b/interface-definitions/include/static/static-route6.xml.i @@ -11,26 +11,7 @@ - - - Silently discard pkts when matched - - - #include - - - Tag value for this route - - u32:1-4294967295 - Tag value for this route - - - - - - - - + #include IPv6 gateway interface name -- cgit v1.2.3 From bb78f3a9ad28f62896a536719783011794deb64c Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 3 Mar 2022 20:23:09 +0100 Subject: static: T4283: support "reject" routes - emit an ICMP unreachable when matched --- data/templates/frr/static_routes_macro.j2 | 3 ++ .../include/static/static-route-reject.xml.i | 12 +++++ .../include/static/static-route.xml.i | 1 + .../include/static/static-route6.xml.i | 1 + smoketest/scripts/cli/test_protocols_static.py | 57 +++++++++++++++++++--- src/conf_mode/protocols_static.py | 4 ++ 6 files changed, 70 insertions(+), 8 deletions(-) create mode 100644 interface-definitions/include/static/static-route-reject.xml.i diff --git a/data/templates/frr/static_routes_macro.j2 b/data/templates/frr/static_routes_macro.j2 index 86c7470ca..8359357b7 100644 --- a/data/templates/frr/static_routes_macro.j2 +++ b/data/templates/frr/static_routes_macro.j2 @@ -2,6 +2,9 @@ {% if prefix_config.blackhole is defined %} {{ ip_ipv6 }} route {{ prefix }} blackhole {{ prefix_config.blackhole.distance if prefix_config.blackhole.distance is defined }} {{ 'tag ' + prefix_config.blackhole.tag if prefix_config.blackhole.tag is defined }} {{ 'table ' + table if table is defined and table is not none }} {% endif %} +{% if prefix_config.reject is defined %} +{{ ip_ipv6 }} route {{ prefix }} reject {{ prefix_config.reject.distance if prefix_config.reject.distance is defined }} {{ 'tag ' + prefix_config.reject.tag if prefix_config.reject.tag is defined }} {{ 'table ' + table if table is defined and table is not none }} +{% endif %} {% if prefix_config.dhcp_interface is defined and prefix_config.dhcp_interface is not none %} {% set next_hop = prefix_config.dhcp_interface | get_dhcp_router %} {% if next_hop is defined and next_hop is not none %} diff --git a/interface-definitions/include/static/static-route-reject.xml.i b/interface-definitions/include/static/static-route-reject.xml.i new file mode 100644 index 000000000..81d4f9afd --- /dev/null +++ b/interface-definitions/include/static/static-route-reject.xml.i @@ -0,0 +1,12 @@ + + + + Emit an ICMP unreachable when matched + + + #include + #include + + + + diff --git a/interface-definitions/include/static/static-route.xml.i b/interface-definitions/include/static/static-route.xml.i index 8433703a5..2de5dc58f 100644 --- a/interface-definitions/include/static/static-route.xml.i +++ b/interface-definitions/include/static/static-route.xml.i @@ -12,6 +12,7 @@ #include + #include #include diff --git a/interface-definitions/include/static/static-route6.xml.i b/interface-definitions/include/static/static-route6.xml.i index 124b2b062..35feef41c 100644 --- a/interface-definitions/include/static/static-route6.xml.i +++ b/interface-definitions/include/static/static-route6.xml.i @@ -12,6 +12,7 @@ #include + #include IPv6 gateway interface name diff --git a/smoketest/scripts/cli/test_protocols_static.py b/smoketest/scripts/cli/test_protocols_static.py index 4c4eb5a7c..3ef9c76d8 100755 --- a/smoketest/scripts/cli/test_protocols_static.py +++ b/smoketest/scripts/cli/test_protocols_static.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2022 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 @@ -52,9 +52,16 @@ routes = { }, 'blackhole' : { 'distance' : '90' }, }, - '100.64.0.0/10' : { + '100.64.0.0/16' : { 'blackhole' : { }, }, + '100.65.0.0/16' : { + 'reject' : { 'distance' : '10', 'tag' : '200' }, + }, + '100.66.0.0/16' : { + 'blackhole' : { }, + 'reject' : { 'distance' : '10', 'tag' : '200' }, + }, '2001:db8:100::/40' : { 'next_hop' : { '2001:db8::1' : { 'distance' : '10' }, @@ -74,6 +81,9 @@ routes = { }, 'blackhole' : { 'distance' : '250', 'tag' : '500' }, }, + '2001:db8:300::/40' : { + 'reject' : { 'distance' : '250', 'tag' : '500' }, + }, '2001:db8::/32' : { 'blackhole' : { 'distance' : '200', 'tag' : '600' }, }, @@ -82,9 +92,15 @@ routes = { tables = ['80', '81', '82'] class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase): - def setUp(self): - # This is our "target" VRF when leaking routes: - self.cli_set(['vrf', 'name', 'black', 'table', '43210']) + @classmethod + def setUpClass(cls): + super(cls, cls).setUpClass() + cls.cli_set(cls, ['vrf', 'name', 'black', 'table', '43210']) + + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, ['vrf']) + super(cls, cls).tearDownClass() def tearDown(self): for route, route_config in routes.items(): @@ -135,6 +151,20 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase): if 'tag' in route_config['blackhole']: self.cli_set(base + ['blackhole', 'tag', route_config['blackhole']['tag']]) + if 'reject' in route_config: + self.cli_set(base + ['reject']) + if 'distance' in route_config['reject']: + self.cli_set(base + ['reject', 'distance', route_config['reject']['distance']]) + if 'tag' in route_config['reject']: + self.cli_set(base + ['reject', 'tag', route_config['reject']['tag']]) + + if {'blackhole', 'reject'} <= set(route_config): + # Can not use blackhole and reject at the same time + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base + ['blackhole']) + self.cli_delete(base + ['reject']) + # commit changes self.cli_commit() @@ -177,6 +207,11 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase): else: self.assertIn(tmp, frrconfig) + if {'blackhole', 'reject'} <= set(route_config): + # Can not use blackhole and reject at the same time + # Config error validated above - skip this route + continue + if 'blackhole' in route_config: tmp = f'{ip_ipv6} route {route} blackhole' if 'tag' in route_config['blackhole']: @@ -186,6 +221,15 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase): self.assertIn(tmp, frrconfig) + if 'reject' in route_config: + tmp = f'{ip_ipv6} route {route} reject' + if 'tag' in route_config['reject']: + tmp += ' tag ' + route_config['reject']['tag'] + if 'distance' in route_config['reject']: + tmp += ' ' + route_config['reject']['distance'] + + self.assertIn(tmp, frrconfig) + def test_02_static_table(self): for table in tables: for route, route_config in routes.items(): @@ -389,11 +433,8 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase): self.assertIn(tmp, frrconfig) - self.cli_delete(['vrf']) - def test_04_static_zebra_route_map(self): # Implemented because of T3328 - self.debug = True route_map = 'foo-static-in' self.cli_set(['policy', 'route-map', route_map, 'rule', '10', 'action', 'permit']) diff --git a/src/conf_mode/protocols_static.py b/src/conf_mode/protocols_static.py index c1e427b16..f0ec48de4 100755 --- a/src/conf_mode/protocols_static.py +++ b/src/conf_mode/protocols_static.py @@ -82,6 +82,10 @@ def verify(static): for interface, interface_config in prefix_options[type].items(): verify_vrf(interface_config) + if {'blackhole', 'reject'} <= set(prefix_options): + raise ConfigError(f'Can not use both blackhole and reject for '\ + 'prefix "{prefix}"!') + return None def generate(static): -- cgit v1.2.3 From a929d58f333c39272580a48b2fc467118105bbcf Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 4 Mar 2022 06:48:43 +0100 Subject: interface: T4203: bugfix Q-in-Q interface parsing Commit 0e23fc10 ("interface: T4203: switch to new recursive node_changed() implementation") switched to a new implementation to retrieve nested changes under a CLI node. Unfortunately the new API was not called - instead the old one was used. --- python/vyos/configdict.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index b8f428c1c..551c27b67 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -574,8 +574,7 @@ def get_interface_dict(config, base, ifname=''): {'is_bridge_member' : bridge}) # Check if any DHCP options changed which require a client restat - dhcp = leaf_node_changed(config, ['vif-s', vif_s, 'vif-c', vif_c, - 'dhcp-options'], recursive=True) + dhcp = node_changed(config, ['vif-s', vif_s, 'vif-c', vif_c, 'dhcp-options'], recursive=True) if dhcp: dict['vif_s'][vif_s]['vif_c'][vif_c].update({'dhcp_options_changed' : ''}) # Check vif, vif-s/vif-c VLAN interfaces for removal -- cgit v1.2.3 From c06861440cd21ff7c668b35ed1039f5fac4101b9 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 4 Mar 2022 20:08:27 +0100 Subject: op-mode: lldp: T3999: bugfix cap' referenced before assignment --- src/op_mode/lldp_op.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/op_mode/lldp_op.py b/src/op_mode/lldp_op.py index b9ebc991a..17f6bf552 100755 --- a/src/op_mode/lldp_op.py +++ b/src/op_mode/lldp_op.py @@ -54,6 +54,7 @@ def parse_data(data, interface): for local_if, values in neighbor.items(): if interface is not None and local_if != interface: continue + cap = '' for chassis, c_value in values.get('chassis', {}).items(): # bail out early if no capabilities found if 'capability' not in c_value: @@ -62,7 +63,6 @@ def parse_data(data, interface): if isinstance(capabilities, dict): capabilities = [capabilities] - cap = '' for capability in capabilities: if capability['enabled']: if capability['type'] == 'Router': -- cgit v1.2.3 From 2c94c3ec72a559de405b29b4399250db3085717e Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 5 Mar 2022 09:40:32 +0100 Subject: conntrackd: T4259: prevent startup of multiple daemon instances --- debian/vyos-1x.preinst | 2 +- src/etc/logrotate.d/conntrackd | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 src/etc/logrotate.d/conntrackd diff --git a/debian/vyos-1x.preinst b/debian/vyos-1x.preinst index 45440bf64..71750b3a1 100644 --- a/debian/vyos-1x.preinst +++ b/debian/vyos-1x.preinst @@ -1,4 +1,4 @@ dpkg-divert --package vyos-1x --add --rename /etc/securetty dpkg-divert --package vyos-1x --add --rename /etc/security/capability.conf dpkg-divert --package vyos-1x --add --rename /lib/systemd/system/lcdproc.service - +dpkg-divert --package vyos-1x --add --rename /etc/logrotate.d/conntrackd diff --git a/src/etc/logrotate.d/conntrackd b/src/etc/logrotate.d/conntrackd new file mode 100644 index 000000000..b0b09dec1 --- /dev/null +++ b/src/etc/logrotate.d/conntrackd @@ -0,0 +1,9 @@ +/var/log/conntrackd-stats.log { + weekly + rotate 2 + missingok + + postrotate + systemctl restart conntrackd.service > /dev/null + endscript +} -- cgit v1.2.3 From aa8080d316dbeb4d26bf67f6d67efeda43b2bc07 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 5 Mar 2022 20:51:13 +0100 Subject: conntrackd: T4259: fix daemon configuration path --- debian/vyos-1x.postinst | 9 +++++++++ src/helpers/vyos-vrrp-conntracksync.sh | 8 +++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/debian/vyos-1x.postinst b/debian/vyos-1x.postinst index 4b4c4c13e..ffd631b1f 100644 --- a/debian/vyos-1x.postinst +++ b/debian/vyos-1x.postinst @@ -83,3 +83,12 @@ fi if [ -L /etc/init.d/README ]; then rm -f /etc/init.d/README fi + +# Remove unwanted daemon files from /etc +# conntackd +DELETE="/etc/logrotate.d/conntrackd.distrib /etc/init.d/conntrackd /etc/default/conntrackd" +for file in $DELETE; do + if [ -f ${file} ]; then + rm -f ${file} + fi +done diff --git a/src/helpers/vyos-vrrp-conntracksync.sh b/src/helpers/vyos-vrrp-conntracksync.sh index 4501aa63e..0cc718938 100755 --- a/src/helpers/vyos-vrrp-conntracksync.sh +++ b/src/helpers/vyos-vrrp-conntracksync.sh @@ -14,12 +14,10 @@ # Modified by : Mohit Mehta # Slight modifications were made to this script for running with Vyatta # The original script came from 0.9.14 debian conntrack-tools package -# -# CONNTRACKD_BIN=/usr/sbin/conntrackd CONNTRACKD_LOCK=/var/lock/conntrack.lock -CONNTRACKD_CONFIG=/etc/conntrackd/conntrackd.conf +CONNTRACKD_CONFIG=/run/conntrackd/conntrackd.conf FACILITY=daemon LEVEL=notice TAG=conntrack-tools @@ -29,6 +27,10 @@ FAILOVER_STATE="/var/run/vyatta-conntrackd-failover-state" $LOGCMD "vyatta-vrrp-conntracksync invoked at `date`" +if ! systemctl is-active --quiet conntrackd.service; then + echo "conntrackd service not running" + exit 1 +fi if [ ! -e $FAILOVER_STATE ]; then mkdir -p /var/run -- cgit v1.2.3 From 1073df8c3aa2a56af861155290a77a59bf5739bf Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 5 Mar 2022 20:52:00 +0100 Subject: flow-accounting: T4277: delete Debian common configs --- debian/vyos-1x.postinst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/debian/vyos-1x.postinst b/debian/vyos-1x.postinst index ffd631b1f..1a4c830cc 100644 --- a/debian/vyos-1x.postinst +++ b/debian/vyos-1x.postinst @@ -86,7 +86,8 @@ fi # Remove unwanted daemon files from /etc # conntackd -DELETE="/etc/logrotate.d/conntrackd.distrib /etc/init.d/conntrackd /etc/default/conntrackd" +DELETE="/etc/logrotate.d/conntrackd.distrib /etc/init.d/conntrackd /etc/default/conntrackd + /etc/default/pmacctd /etc/pmacct" for file in $DELETE; do if [ -f ${file} ]; then rm -f ${file} -- cgit v1.2.3 From 1d0d4e83d8413c1b389be763cadd5d150d4be982 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 6 Mar 2022 09:58:22 +0100 Subject: smoketest: config: add "recent" firewall rule to dialup-router --- smoketest/configs/dialup-router-complex | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/smoketest/configs/dialup-router-complex b/smoketest/configs/dialup-router-complex index fef79ea56..1b62deb5c 100644 --- a/smoketest/configs/dialup-router-complex +++ b/smoketest/configs/dialup-router-complex @@ -267,6 +267,22 @@ firewall { } protocol udp } + rule 800 { + action drop + description "SSH anti brute force" + destination { + port ssh + } + log enable + protocol tcp + recent { + count 4 + time 60 + } + state { + new enable + } + } } name DMZ-WAN { default-action accept -- cgit v1.2.3 From 27404f71c85187403b3ae1b73b95e6347e07ea97 Mon Sep 17 00:00:00 2001 From: srividya0208 Date: Mon, 7 Mar 2022 04:44:08 -0500 Subject: ipsec prefix: T4275: Fix for prefix val_help of remote-access and s2s vpn It accepts network as the input value but the completion help is showing ip address, continuation of previous commit --- interface-definitions/include/ipsec/local-traffic-selector.xml.i | 4 ++-- interface-definitions/vpn_ipsec.xml.in | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/interface-definitions/include/ipsec/local-traffic-selector.xml.i b/interface-definitions/include/ipsec/local-traffic-selector.xml.i index d30a6d11a..9ae67f583 100644 --- a/interface-definitions/include/ipsec/local-traffic-selector.xml.i +++ b/interface-definitions/include/ipsec/local-traffic-selector.xml.i @@ -9,11 +9,11 @@ Local IPv4 or IPv6 prefix - ipv4 + ipv4net Local IPv4 prefix - ipv6 + ipv6net Local IPv6 prefix diff --git a/interface-definitions/vpn_ipsec.xml.in b/interface-definitions/vpn_ipsec.xml.in index 0ad69c637..d8c06a310 100644 --- a/interface-definitions/vpn_ipsec.xml.in +++ b/interface-definitions/vpn_ipsec.xml.in @@ -897,11 +897,11 @@ Local IPv4 or IPv6 pool prefix - ipv4 + ipv4net Local IPv4 pool prefix - ipv6 + ipv6net Local IPv6 pool prefix @@ -1114,11 +1114,11 @@ Remote IPv4 or IPv6 prefix - ipv4 + ipv4net Remote IPv4 prefix - ipv6 + ipv6net Remote IPv6 prefix -- cgit v1.2.3 From ebb524702e1cd60a74b00727b7bd24d375648c78 Mon Sep 17 00:00:00 2001 From: zsdc Date: Mon, 7 Mar 2022 18:20:53 +0200 Subject: logrotate: T4250: Fixed logrotate config generation * Removed `/var/log/auth.log` and `/var/log/messages` from `/etc/logrotate.d/rsyslog`, because they conflict with VyOS-controlled items what leads to service error. * Removed generation config file for `/var/log/messages` from `system-syslog.py` - this should be done from `syslom logs` now. * Generate each logfile from `system syslog file` to a dedicated logrotate config file. * Fixed logrotate config file names in `/etc/rsyslog.d/vyos-rsyslog.conf`. * Added default logrotate settins for `/var/log/messages` --- data/templates/syslog/logrotate.tmpl | 9 ++++----- debian/vyos-1x.postinst | 4 ++++ src/conf_mode/system-syslog.py | 14 +++++++++++--- src/etc/logrotate.d/vyos-rsyslog | 12 ++++++++++++ 4 files changed, 31 insertions(+), 8 deletions(-) create mode 100644 src/etc/logrotate.d/vyos-rsyslog diff --git a/data/templates/syslog/logrotate.tmpl b/data/templates/syslog/logrotate.tmpl index f758265e4..c1b951e8b 100644 --- a/data/templates/syslog/logrotate.tmpl +++ b/data/templates/syslog/logrotate.tmpl @@ -1,12 +1,11 @@ -{% for file in files %} -{{files[file]['log-file']}} { +{{ config_render['log-file'] }} { missingok notifempty create - rotate {{files[file]['max-files']}} - size={{files[file]['max-size']//1024}}k + rotate {{ config_render['max-files'] }} + size={{ config_render['max-size'] // 1024 }}k postrotate invoke-rc.d rsyslog rotate > /dev/null endscript } -{% endfor %} + diff --git a/debian/vyos-1x.postinst b/debian/vyos-1x.postinst index 1a4c830cc..1ca6687a3 100644 --- a/debian/vyos-1x.postinst +++ b/debian/vyos-1x.postinst @@ -93,3 +93,7 @@ for file in $DELETE; do rm -f ${file} fi done + +# Remove logrotate items controlled via CLI and VyOS defaults +sed -i '/^\/var\/log\/messages$/d' /etc/logrotate.d/rsyslog +sed -i '/^\/var\/log\/auth.log$/d' /etc/logrotate.d/rsyslog diff --git a/src/conf_mode/system-syslog.py b/src/conf_mode/system-syslog.py index 3d8a51cd8..309b4bdb0 100755 --- a/src/conf_mode/system-syslog.py +++ b/src/conf_mode/system-syslog.py @@ -17,6 +17,7 @@ import os import re +from pathlib import Path from sys import exit from vyos.config import Config @@ -89,7 +90,7 @@ def get_config(config=None): filename: { 'log-file': '/var/log/user/' + filename, 'max-files': '5', - 'action-on-max-size': '/usr/sbin/logrotate /etc/logrotate.d/' + filename, + 'action-on-max-size': '/usr/sbin/logrotate /etc/logrotate.d/vyos-rsyslog-generated-' + filename, 'selectors': '*.err', 'max-size': 262144 } @@ -205,10 +206,17 @@ def generate(c): conf = '/etc/rsyslog.d/vyos-rsyslog.conf' render(conf, 'syslog/rsyslog.conf.tmpl', c) + # cleanup current logrotate config files + logrotate_files = Path('/etc/logrotate.d/').glob('vyos-rsyslog-generated-*') + for file in logrotate_files: + file.unlink() + # eventually write for each file its own logrotate file, since size is # defined it shouldn't matter - conf = '/etc/logrotate.d/vyos-rsyslog' - render(conf, 'syslog/logrotate.tmpl', c) + for filename, fileconfig in c.get('files', {}).items(): + if fileconfig['log-file'].startswith('/var/log/user/'): + conf = '/etc/logrotate.d/vyos-rsyslog-generated-' + filename + render(conf, 'syslog/logrotate.tmpl', { 'config_render': fileconfig }) def verify(c): diff --git a/src/etc/logrotate.d/vyos-rsyslog b/src/etc/logrotate.d/vyos-rsyslog new file mode 100644 index 000000000..3c087b94e --- /dev/null +++ b/src/etc/logrotate.d/vyos-rsyslog @@ -0,0 +1,12 @@ +/var/log/messages { + create + missingok + nomail + notifempty + rotate 10 + size 1M + postrotate + # inform rsyslog service about rotation + /usr/lib/rsyslog/rsyslog-rotate + endscript +} -- cgit v1.2.3 From 534f677d36285863decb2cdff179687b4fd690cb Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Tue, 8 Mar 2022 11:46:18 -0600 Subject: component_version: T4291: consolidate read/write functions --- python/vyos/component_version.py | 174 ++++++++++++++++++++++++ python/vyos/component_versions.py | 57 -------- python/vyos/formatversions.py | 109 --------------- python/vyos/migrator.py | 26 ++-- python/vyos/systemversions.py | 46 ------- smoketest/scripts/cli/test_component_version.py | 6 +- src/helpers/system-versions-foot.py | 19 +-- 7 files changed, 189 insertions(+), 248 deletions(-) create mode 100644 python/vyos/component_version.py delete mode 100644 python/vyos/component_versions.py delete mode 100644 python/vyos/formatversions.py delete mode 100644 python/vyos/systemversions.py diff --git a/python/vyos/component_version.py b/python/vyos/component_version.py new file mode 100644 index 000000000..b1554828d --- /dev/null +++ b/python/vyos/component_version.py @@ -0,0 +1,174 @@ +# Copyright 2022 VyOS maintainers and contributors +# +# 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 . + +""" +Functions for reading/writing component versions. + +The config file version string has the following form: + +VyOS 1.3/1.4: + +// Warning: Do not remove the following line. +// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@3:conntrack-sync@2:dhcp-relay@2:dhcp-server@6:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@22:ipoe-server@1:ipsec@5:isis@1:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@8:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@21:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" +// Release version: 1.3.0 + +VyOS 1.2: + +/* Warning: Do not remove the following line. */ +/* === vyatta-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack-sync@1:conntrack@1:dhcp-relay@2:dhcp-server@5:dns-forwarding@1:firewall@5:ipsec@5:l2tp@1:mdns@1:nat@4:ntp@1:pppoe-server@2:pptp@1:qos@1:quagga@7:snmp@1:ssh@1:system@10:vrrp@2:wanloadbalance@3:webgui@1:webproxy@2:zone-policy@1" === */ +/* Release version: 1.2.8 */ + +""" + +import os +import re +import sys +import fileinput +from vyos.xml import component_version +from vyos.version import get_version + +def from_string(string_line, vintage='vyos'): + """ + Get component version dictionary from string. + Return empty dictionary if string contains no config information + or raise error if component version string malformed. + """ + version_dict = {} + + if vintage == 'vyos': + if re.match(r'// vyos-config-version:.+', string_line): + if not re.match(r'// vyos-config-version:\s+"([\w,-]+@\d+:)+([\w,-]+@\d+)"\s*', string_line): + raise ValueError(f"malformed configuration string: {string_line}") + + for pair in re.findall(r'([\w,-]+)@(\d+)', string_line): + version_dict[pair[0]] = int(pair[1]) + + elif vintage == 'vyatta': + if re.match(r'/\* === vyatta-config-version:.+=== \*/$', string_line): + if not re.match(r'/\* === vyatta-config-version:\s+"([\w,-]+@\d+:)+([\w,-]+@\d+)"\s+=== \*/$', string_line): + raise ValueError(f"malformed configuration string: {string_line}") + + for pair in re.findall(r'([\w,-]+)@(\d+)', string_line): + version_dict[pair[0]] = int(pair[1]) + else: + raise ValueError("Unknown config string vintage") + + return version_dict + +def from_file(config_file_name='/opt/vyatta/etc/config/config.boot', + vintage='vyos'): + """ + Get component version dictionary parsing config file line by line + """ + f = open(config_file_name, 'r') + for line_in_config in f: + version_dict = from_string(line_in_config, vintage=vintage) + if version_dict: + return version_dict + + # no version information + return {} + +def from_system(): + """ + Get system component version dict. + """ + return component_version() + +def legacy_from_system(): + """ + legacy function; imported as-is. + + Get component versions from running system; critical failure if + unable to read migration directory. + """ + import vyos.defaults + + system_versions = {} + + try: + version_info = os.listdir(vyos.defaults.directories['current']) + except OSError as err: + print("OS error: {}".format(err)) + sys.exit(1) + + for info in version_info: + if re.match(r'[\w,-]+@\d+', info): + pair = info.split('@') + system_versions[pair[0]] = int(pair[1]) + + return system_versions + +def format_string(ver: dict) -> str: + """ + Version dict to string. + """ + keys = list(ver) + keys.sort() + l = [] + for k in keys: + v = ver[k] + l.append(f'{k}@{v}') + sep = ':' + return sep.join(l) + +def system_footer(vintage='vyos') -> str: + """ + Version footer as string. + """ + ver_str = format_string(from_system()) + release = get_version() + if vintage == 'vyos': + ret_str = (f'// Warning: Do not remove the following line.\n' + + f'// vyos-config-version: "{ver_str}"\n' + + f'// Release version: {release}\n') + elif vintage == 'vyatta': + ret_str = (f'/* Warning: Do not remove the following line. */\n' + + f'/* === vyatta-config-version: "{ver_str}" === */\n' + + f'/* Release version: {release} */\n') + else: + raise ValueError("Unknown config string vintage") + + return ret_str + +def write_footer(file_name, vintage='vyos'): + """ + Write version footer to file. + """ + footer = system_footer(vintage=vintage) + if file_name: + with open(file_name, 'a') as f: + f.write(footer) + else: + sys.stdout.write(footer) + +def remove_footer(file_name): + """ + Remove old version footer. + """ + for line in fileinput.input(file_name, inplace=True): + if re.match(r'/\* Warning:.+ \*/$', line): + continue + if re.match(r'/\* === vyatta-config-version:.+=== \*/$', line): + continue + if re.match(r'/\* Release version:.+ \*/$', line): + continue + if re.match('// vyos-config-version:.+', line): + continue + if re.match('// Warning:.+', line): + continue + if re.match('// Release version:.+', line): + continue + sys.stdout.write(line) diff --git a/python/vyos/component_versions.py b/python/vyos/component_versions.py deleted file mode 100644 index 90b458aae..000000000 --- a/python/vyos/component_versions.py +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright 2017 VyOS maintainers and contributors -# -# 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 . - -""" -The version data looks like: - -/* Warning: Do not remove the following line. */ -/* === vyatta-config-version: -"cluster@1:config-management@1:conntrack-sync@1:conntrack@1:dhcp-relay@1:dhcp-server@4:firewall@5:ipsec@4:nat@4:qos@1:quagga@2:system@8:vrrp@1:wanloadbalance@3:webgui@1:webproxy@1:zone-policy@1" -=== */ -/* Release version: 1.2.0-rolling+201806131737 */ -""" - -import re - -def get_component_version(string_line): - """ - Get component version dictionary from string - return empty dictionary if string contains no config information - or raise error if component version string malformed - """ - return_value = {} - if re.match(r'/\* === vyatta-config-version:.+=== \*/$', string_line): - - if not re.match(r'/\* === vyatta-config-version:\s+"([\w,-]+@\d+:)+([\w,-]+@\d+)"\s+=== \*/$', string_line): - raise ValueError("malformed configuration string: " + str(string_line)) - - for pair in re.findall(r'([\w,-]+)@(\d+)', string_line): - if pair[0] in return_value.keys(): - raise ValueError("duplicate unit name: \"" + str(pair[0]) + "\" in string: \"" + string_line + "\"") - return_value[pair[0]] = int(pair[1]) - - return return_value - - -def get_component_versions_from_file(config_file_name='/opt/vyatta/etc/config/config.boot'): - """ - Get component version dictionary parsing config file line by line - """ - f = open(config_file_name, 'r') - for line_in_config in f: - component_version = get_component_version(line_in_config) - if component_version: - return component_version - raise ValueError("no config string in file:", config_file_name) diff --git a/python/vyos/formatversions.py b/python/vyos/formatversions.py deleted file mode 100644 index 29117a5d3..000000000 --- a/python/vyos/formatversions.py +++ /dev/null @@ -1,109 +0,0 @@ -# Copyright 2019 VyOS maintainers and contributors -# -# 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 . - -import sys -import os -import re -import fileinput - -def read_vyatta_versions(config_file): - config_file_versions = {} - - with open(config_file, 'r') as config_file_handle: - for config_line in config_file_handle: - if re.match(r'/\* === vyatta-config-version:.+=== \*/$', config_line): - if not re.match(r'/\* === vyatta-config-version:\s+"([\w,-]+@\d+:)+([\w,-]+@\d+)"\s+=== \*/$', config_line): - raise ValueError("malformed configuration string: " - "{}".format(config_line)) - - for pair in re.findall(r'([\w,-]+)@(\d+)', config_line): - config_file_versions[pair[0]] = int(pair[1]) - - - return config_file_versions - -def read_vyos_versions(config_file): - config_file_versions = {} - - with open(config_file, 'r') as config_file_handle: - for config_line in config_file_handle: - if re.match(r'// vyos-config-version:.+', config_line): - if not re.match(r'// vyos-config-version:\s+"([\w,-]+@\d+:)+([\w,-]+@\d+)"\s*', config_line): - raise ValueError("malformed configuration string: " - "{}".format(config_line)) - - for pair in re.findall(r'([\w,-]+)@(\d+)', config_line): - config_file_versions[pair[0]] = int(pair[1]) - - return config_file_versions - -def remove_versions(config_file): - """ - Remove old version string. - """ - for line in fileinput.input(config_file, inplace=True): - if re.match(r'/\* Warning:.+ \*/$', line): - continue - if re.match(r'/\* === vyatta-config-version:.+=== \*/$', line): - continue - if re.match(r'/\* Release version:.+ \*/$', line): - continue - if re.match('// vyos-config-version:.+', line): - continue - if re.match('// Warning:.+', line): - continue - if re.match('// Release version:.+', line): - continue - sys.stdout.write(line) - -def format_versions_string(config_versions): - cfg_keys = list(config_versions.keys()) - cfg_keys.sort() - - component_version_strings = [] - - for key in cfg_keys: - cfg_vers = config_versions[key] - component_version_strings.append('{}@{}'.format(key, cfg_vers)) - - separator = ":" - component_version_string = separator.join(component_version_strings) - - return component_version_string - -def write_vyatta_versions_foot(config_file, component_version_string, - os_version_string): - if config_file: - with open(config_file, 'a') as config_file_handle: - config_file_handle.write('/* Warning: Do not remove the following line. */\n') - config_file_handle.write('/* === vyatta-config-version: "{}" === */\n'.format(component_version_string)) - config_file_handle.write('/* Release version: {} */\n'.format(os_version_string)) - else: - sys.stdout.write('/* Warning: Do not remove the following line. */\n') - sys.stdout.write('/* === vyatta-config-version: "{}" === */\n'.format(component_version_string)) - sys.stdout.write('/* Release version: {} */\n'.format(os_version_string)) - -def write_vyos_versions_foot(config_file, component_version_string, - os_version_string): - if config_file: - with open(config_file, 'a') as config_file_handle: - config_file_handle.write('// Warning: Do not remove the following line.\n') - config_file_handle.write('// vyos-config-version: "{}"\n'.format(component_version_string)) - config_file_handle.write('// Release version: {}\n'.format(os_version_string)) - else: - sys.stdout.write('// Warning: Do not remove the following line.\n') - sys.stdout.write('// vyos-config-version: "{}"\n'.format(component_version_string)) - sys.stdout.write('// Release version: {}\n'.format(os_version_string)) - diff --git a/python/vyos/migrator.py b/python/vyos/migrator.py index a2e0daabd..266a2e58e 100644 --- a/python/vyos/migrator.py +++ b/python/vyos/migrator.py @@ -17,10 +17,8 @@ import sys import os import json import subprocess -import vyos.version import vyos.defaults -import vyos.systemversions as systemversions -import vyos.formatversions as formatversions +import vyos.component_version as component_version class MigratorError(Exception): pass @@ -42,13 +40,13 @@ class Migrator(object): cfg_file = self._config_file component_versions = {} - cfg_versions = formatversions.read_vyatta_versions(cfg_file) + cfg_versions = component_version.from_file(cfg_file, vintage='vyatta') if cfg_versions: self._config_file_vintage = 'vyatta' component_versions = cfg_versions - cfg_versions = formatversions.read_vyos_versions(cfg_file) + cfg_versions = component_version.from_file(cfg_file, vintage='vyos') if cfg_versions: self._config_file_vintage = 'vyos' @@ -152,19 +150,11 @@ class Migrator(object): """ Write new versions string. """ - versions_string = formatversions.format_versions_string(cfg_versions) - - os_version_string = vyos.version.get_version() - if self._config_file_vintage == 'vyatta': - formatversions.write_vyatta_versions_foot(self._config_file, - versions_string, - os_version_string) + component_version.write_footer(self._config_file, vintage='vyatta') if self._config_file_vintage == 'vyos': - formatversions.write_vyos_versions_foot(self._config_file, - versions_string, - os_version_string) + component_version.write_footer(self._config_file, vintage='vyos') def save_json_record(self, component_versions: dict): """ @@ -195,7 +185,7 @@ class Migrator(object): # This will force calling all migration scripts: cfg_versions = {} - sys_versions = systemversions.get_system_component_version() + sys_versions = component_version.from_system() # save system component versions in json file for easy reference self.save_json_record(sys_versions) @@ -211,7 +201,7 @@ class Migrator(object): if not self._changed: return - formatversions.remove_versions(cfg_file) + component_version.remove_footer(cfg_file) self.write_config_file_versions(rev_versions) @@ -232,7 +222,7 @@ class VirtualMigrator(Migrator): if not self._changed: return - formatversions.remove_versions(cfg_file) + component_version.remove_footer(cfg_file) self.write_config_file_versions(cfg_versions) diff --git a/python/vyos/systemversions.py b/python/vyos/systemversions.py deleted file mode 100644 index f2da76d4f..000000000 --- a/python/vyos/systemversions.py +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright 2019 VyOS maintainers and contributors -# -# 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 . - -import os -import re -import sys -import vyos.defaults -from vyos.xml import component_version - -# legacy version, reading from the file names in -# /opt/vyatta/etc/config-migrate/current -def get_system_versions(): - """ - Get component versions from running system; critical failure if - unable to read migration directory. - """ - system_versions = {} - - try: - version_info = os.listdir(vyos.defaults.directories['current']) - except OSError as err: - print("OS error: {}".format(err)) - sys.exit(1) - - for info in version_info: - if re.match(r'[\w,-]+@\d+', info): - pair = info.split('@') - system_versions[pair[0]] = int(pair[1]) - - return system_versions - -# read from xml cache -def get_system_component_version(): - return component_version() diff --git a/smoketest/scripts/cli/test_component_version.py b/smoketest/scripts/cli/test_component_version.py index 777379bdd..21cc1c761 100755 --- a/smoketest/scripts/cli/test_component_version.py +++ b/smoketest/scripts/cli/test_component_version.py @@ -16,7 +16,7 @@ import unittest -from vyos.systemversions import get_system_versions, get_system_component_version +from vyos.component_version import legacy_from_system, from_system # After T3474, component versions should be updated in the files in # vyos-1x/interface-definitions/include/version/ @@ -24,8 +24,8 @@ from vyos.systemversions import get_system_versions, get_system_component_versio # that in the xml cache. class TestComponentVersion(unittest.TestCase): def setUp(self): - self.legacy_d = get_system_versions() - self.xml_d = get_system_component_version() + self.legacy_d = legacy_from_system() + self.xml_d = from_system() def test_component_version(self): self.assertTrue(set(self.legacy_d).issubset(set(self.xml_d))) diff --git a/src/helpers/system-versions-foot.py b/src/helpers/system-versions-foot.py index 2aa687221..b44408542 100755 --- a/src/helpers/system-versions-foot.py +++ b/src/helpers/system-versions-foot.py @@ -16,24 +16,13 @@ # along with this library. If not, see . import sys -import vyos.formatversions as formatversions -import vyos.systemversions as systemversions import vyos.defaults -import vyos.version - -sys_versions = systemversions.get_system_component_version() - -component_string = formatversions.format_versions_string(sys_versions) - -os_version_string = vyos.version.get_version() +from vyos.component_version import write_footer sys.stdout.write("\n\n") if vyos.defaults.cfg_vintage == 'vyos': - formatversions.write_vyos_versions_foot(None, component_string, - os_version_string) + write_footer(None, vintage='vyos') elif vyos.defaults.cfg_vintage == 'vyatta': - formatversions.write_vyatta_versions_foot(None, component_string, - os_version_string) + write_footer(None, vintage='vyatta') else: - formatversions.write_vyatta_versions_foot(None, component_string, - os_version_string) + write_footer(None, vintage='vyatta') -- cgit v1.2.3 From c4d389488970c8510200cac96a67182e9333b891 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Tue, 8 Mar 2022 11:48:29 -0600 Subject: save-config: T4292: rewrite vyatta-save-config.pl to Python --- python/vyos/config.py | 5 ++++ python/vyos/configsession.py | 2 +- src/helpers/vyos-save-config.py | 54 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100755 src/helpers/vyos-save-config.py diff --git a/python/vyos/config.py b/python/vyos/config.py index a5c1ad122..829167820 100644 --- a/python/vyos/config.py +++ b/python/vyos/config.py @@ -93,6 +93,11 @@ class Config(object): (self._running_config, self._session_config) = self._config_source.get_configtree_tuple() + def get_config_tree(self, effective=False): + if effective: + return self._running_config + return self._session_config + def _make_path(self, path): # Backwards-compatibility stuff: original implementation used string paths # libvyosconfig paths are lists, but since node names cannot contain whitespace, diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py index f28ad09c5..a7eda6e61 100644 --- a/python/vyos/configsession.py +++ b/python/vyos/configsession.py @@ -28,7 +28,7 @@ DISCARD = '/opt/vyatta/sbin/my_discard' SHOW_CONFIG = ['/bin/cli-shell-api', 'showConfig'] LOAD_CONFIG = ['/bin/cli-shell-api', 'loadFile'] MIGRATE_LOAD_CONFIG = ['/usr/libexec/vyos/vyos-load-config.py'] -SAVE_CONFIG = ['/opt/vyatta/sbin/vyatta-save-config.pl'] +SAVE_CONFIG = ['/usr/libexec/vyos/vyos-save-config.py'] INSTALL_IMAGE = ['/opt/vyatta/sbin/install-image', '--url'] REMOVE_IMAGE = ['/opt/vyatta/bin/vyatta-boot-image.pl', '--del'] GENERATE = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'generate'] diff --git a/src/helpers/vyos-save-config.py b/src/helpers/vyos-save-config.py new file mode 100755 index 000000000..628d510dd --- /dev/null +++ b/src/helpers/vyos-save-config.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 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 . +# +# +import os +import re +import sys +from tempfile import NamedTemporaryFile + +from vyos.config import Config +from vyos.remote import urlc +from vyos.component_version import system_footer + +DEFAULT_CONFIG_FILE = '/opt/vyatta/etc/config/config.boot' +remote_save = None + +if len(sys.argv) > 1: + save_file = sys.argv[1] +else: + save_file = DEFAULT_CONFIG_FILE + +if re.match(r'\w+:/', save_file): + try: + remote_save = urlc(save_file) + except ValueError as e: + sys.exit(e) + +config = Config() +ct = config.get_config_tree(effective=True) + +write_file = save_file if remote_save is None else NamedTemporaryFile(delete=False).name +with open(write_file, 'w') as f: + f.write(ct.to_string()) + f.write("\n") + f.write(system_footer()) + +if remote_save is not None: + try: + remote_save.upload(write_file) + finally: + os.remove(write_file) -- cgit v1.2.3 From 2a4b45ba7fa4dabf7e592f499cfb06a7ae38cdea Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 9 Mar 2022 11:00:10 -0600 Subject: load-config: T4295: use config_tree instead of legacy loadFile --- src/helpers/vyos-load-config.py | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/src/helpers/vyos-load-config.py b/src/helpers/vyos-load-config.py index e579e81b2..08f28b24e 100755 --- a/src/helpers/vyos-load-config.py +++ b/src/helpers/vyos-load-config.py @@ -29,15 +29,40 @@ import gzip import tempfile import vyos.defaults import vyos.remote -from vyos.configsource import ConfigSourceSession, VyOSError +from vyos.configsource import ConfigSourceSession +from vyos.configtree import ConfigTree, DiffTree from vyos.migrator import Migrator, VirtualMigrator, MigratorError +from vyos.util import cmd, DEVNULL class LoadConfig(ConfigSourceSession): - """A subclass for calling 'loadFile'. + """A subclass for loading a config file. This does not belong in configsource.py, and only has a single caller. """ - def load_config(self, path): - return self._run(['/bin/cli-shell-api','loadFile',path]) + def load_config(self, file_path): + try: + with open(file_path) as f: + config_file = f.read() + load_ct = ConfigTree(config_file) + except (OSError, ValueError) as e: + print(e) + return + + eff_ct, _ = self.get_configtree_tuple() + diff = DiffTree(eff_ct, load_ct) + commands = diff.to_commands() + # on an empty set of 'add' or 'delete' commands, to_commands + # returns '\n'; prune below + command_list = commands.splitlines() + command_list = [c for c in command_list if c] + + if not command_list: + return + for op in command_list: + try: + cmd(f'/opt/vyatta/sbin/my_{op}', shell=True, stderr=DEVNULL) + except OSError as e: + print(e) + return file_name = sys.argv[1] if len(sys.argv) > 1 else 'config.boot' configdir = vyos.defaults.directories['config'] @@ -93,10 +118,7 @@ with tempfile.NamedTemporaryFile() as fp: except MigratorError as err: sys.exit('{}'.format(err)) - try: - config.load_config(fp.name) - except VyOSError as err: - sys.exit('{}'.format(err)) + config.load_config(fp.name) if config.session_changed(): print("Load complete. Use 'commit' to make changes effective.") -- cgit v1.2.3 From 7549c847c3df9155c4315efcccfaf798af9fb402 Mon Sep 17 00:00:00 2001 From: Paul Lettington Date: Wed, 9 Mar 2022 14:24:16 +0000 Subject: policy: T2493 ip-next-hop unchanged & peer-address Also add ipv6-next-hop peer-address --- data/templates/frr/policy.frr.tmpl | 3 +++ interface-definitions/policy.xml.in | 16 ++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/data/templates/frr/policy.frr.tmpl b/data/templates/frr/policy.frr.tmpl index d3d3957a5..97eb15331 100644 --- a/data/templates/frr/policy.frr.tmpl +++ b/data/templates/frr/policy.frr.tmpl @@ -276,6 +276,9 @@ route-map {{ route_map }} {{ rule_config.action }} {{ rule }} {% if rule_config.set.ipv6_next_hop is defined and rule_config.set.ipv6_next_hop.local is defined and rule_config.set.ipv6_next_hop.local is not none %} set ipv6 next-hop local {{ rule_config.set.ipv6_next_hop.local }} {% endif %} +{% if rule_config.set.ipv6_next_hop is defined and rule_config.set.ipv6_next_hop.peer_address is defined %} + set ipv6 next-hop peer-address +{% endif %} {% if rule_config.set.ipv6_next_hop is defined and rule_config.set.ipv6_next_hop.prefer_global is defined %} set ipv6 next-hop prefer-global {% endif %} diff --git a/interface-definitions/policy.xml.in b/interface-definitions/policy.xml.in index 9767285dd..5e037b558 100644 --- a/interface-definitions/policy.xml.in +++ b/interface-definitions/policy.xml.in @@ -1115,13 +1115,23 @@ Nexthop IP address + unchanged peer-address ipv4 IP address + + unchanged + Set the BGP nexthop address as unchanged + + + peer-address + Set the BGP nexthop address to the address of the peer + + ^(unchanged|peer-address)$ @@ -1160,6 +1170,12 @@ + + + Use peer address (for BGP only) + + + Prefer global address as the nexthop -- cgit v1.2.3 From fde48b4d303b471f8c56b81bc7fcef0a028bb60a Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Thu, 10 Mar 2022 09:34:00 -0600 Subject: Revert "load-config: T4295: use config_tree instead of legacy loadFile" This reverts commit 2a4b45ba7fa4dabf7e592f499cfb06a7ae38cdea. Revert while investigating failure in vyos-configtest. --- src/helpers/vyos-load-config.py | 38 ++++++++------------------------------ 1 file changed, 8 insertions(+), 30 deletions(-) diff --git a/src/helpers/vyos-load-config.py b/src/helpers/vyos-load-config.py index 08f28b24e..e579e81b2 100755 --- a/src/helpers/vyos-load-config.py +++ b/src/helpers/vyos-load-config.py @@ -29,40 +29,15 @@ import gzip import tempfile import vyos.defaults import vyos.remote -from vyos.configsource import ConfigSourceSession -from vyos.configtree import ConfigTree, DiffTree +from vyos.configsource import ConfigSourceSession, VyOSError from vyos.migrator import Migrator, VirtualMigrator, MigratorError -from vyos.util import cmd, DEVNULL class LoadConfig(ConfigSourceSession): - """A subclass for loading a config file. + """A subclass for calling 'loadFile'. This does not belong in configsource.py, and only has a single caller. """ - def load_config(self, file_path): - try: - with open(file_path) as f: - config_file = f.read() - load_ct = ConfigTree(config_file) - except (OSError, ValueError) as e: - print(e) - return - - eff_ct, _ = self.get_configtree_tuple() - diff = DiffTree(eff_ct, load_ct) - commands = diff.to_commands() - # on an empty set of 'add' or 'delete' commands, to_commands - # returns '\n'; prune below - command_list = commands.splitlines() - command_list = [c for c in command_list if c] - - if not command_list: - return - for op in command_list: - try: - cmd(f'/opt/vyatta/sbin/my_{op}', shell=True, stderr=DEVNULL) - except OSError as e: - print(e) - return + def load_config(self, path): + return self._run(['/bin/cli-shell-api','loadFile',path]) file_name = sys.argv[1] if len(sys.argv) > 1 else 'config.boot' configdir = vyos.defaults.directories['config'] @@ -118,7 +93,10 @@ with tempfile.NamedTemporaryFile() as fp: except MigratorError as err: sys.exit('{}'.format(err)) - config.load_config(fp.name) + try: + config.load_config(fp.name) + except VyOSError as err: + sys.exit('{}'.format(err)) if config.session_changed(): print("Load complete. Use 'commit' to make changes effective.") -- cgit v1.2.3 From ef4870e66f8c1cd6df5fba2abbfdde0eac152e0a Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Thu, 10 Mar 2022 10:07:19 -0600 Subject: Revert "save-config: T4292: rewrite vyatta-save-config.pl to Python" This reverts commit c4d389488970c8510200cac96a67182e9333b891. Revert while investigating failure in vyos-configtest. --- python/vyos/config.py | 5 ---- python/vyos/configsession.py | 2 +- src/helpers/vyos-save-config.py | 54 ----------------------------------------- 3 files changed, 1 insertion(+), 60 deletions(-) delete mode 100755 src/helpers/vyos-save-config.py diff --git a/python/vyos/config.py b/python/vyos/config.py index 829167820..a5c1ad122 100644 --- a/python/vyos/config.py +++ b/python/vyos/config.py @@ -93,11 +93,6 @@ class Config(object): (self._running_config, self._session_config) = self._config_source.get_configtree_tuple() - def get_config_tree(self, effective=False): - if effective: - return self._running_config - return self._session_config - def _make_path(self, path): # Backwards-compatibility stuff: original implementation used string paths # libvyosconfig paths are lists, but since node names cannot contain whitespace, diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py index a7eda6e61..f28ad09c5 100644 --- a/python/vyos/configsession.py +++ b/python/vyos/configsession.py @@ -28,7 +28,7 @@ DISCARD = '/opt/vyatta/sbin/my_discard' SHOW_CONFIG = ['/bin/cli-shell-api', 'showConfig'] LOAD_CONFIG = ['/bin/cli-shell-api', 'loadFile'] MIGRATE_LOAD_CONFIG = ['/usr/libexec/vyos/vyos-load-config.py'] -SAVE_CONFIG = ['/usr/libexec/vyos/vyos-save-config.py'] +SAVE_CONFIG = ['/opt/vyatta/sbin/vyatta-save-config.pl'] INSTALL_IMAGE = ['/opt/vyatta/sbin/install-image', '--url'] REMOVE_IMAGE = ['/opt/vyatta/bin/vyatta-boot-image.pl', '--del'] GENERATE = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'generate'] diff --git a/src/helpers/vyos-save-config.py b/src/helpers/vyos-save-config.py deleted file mode 100755 index 628d510dd..000000000 --- a/src/helpers/vyos-save-config.py +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2022 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 . -# -# -import os -import re -import sys -from tempfile import NamedTemporaryFile - -from vyos.config import Config -from vyos.remote import urlc -from vyos.component_version import system_footer - -DEFAULT_CONFIG_FILE = '/opt/vyatta/etc/config/config.boot' -remote_save = None - -if len(sys.argv) > 1: - save_file = sys.argv[1] -else: - save_file = DEFAULT_CONFIG_FILE - -if re.match(r'\w+:/', save_file): - try: - remote_save = urlc(save_file) - except ValueError as e: - sys.exit(e) - -config = Config() -ct = config.get_config_tree(effective=True) - -write_file = save_file if remote_save is None else NamedTemporaryFile(delete=False).name -with open(write_file, 'w') as f: - f.write(ct.to_string()) - f.write("\n") - f.write(system_footer()) - -if remote_save is not None: - try: - remote_save.upload(write_file) - finally: - os.remove(write_file) -- cgit v1.2.3 From c1e04cea0817db07b22a8bd8e6b2f2c0a1e682f4 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Thu, 10 Mar 2022 10:07:31 -0600 Subject: Revert "component_version: T4291: consolidate read/write functions" This reverts commit 534f677d36285863decb2cdff179687b4fd690cb. Revert while investigating failure in vyos-configtest. --- python/vyos/component_version.py | 174 ------------------------ python/vyos/component_versions.py | 57 ++++++++ python/vyos/formatversions.py | 109 +++++++++++++++ python/vyos/migrator.py | 26 ++-- python/vyos/systemversions.py | 46 +++++++ smoketest/scripts/cli/test_component_version.py | 6 +- src/helpers/system-versions-foot.py | 19 ++- 7 files changed, 248 insertions(+), 189 deletions(-) delete mode 100644 python/vyos/component_version.py create mode 100644 python/vyos/component_versions.py create mode 100644 python/vyos/formatversions.py create mode 100644 python/vyos/systemversions.py diff --git a/python/vyos/component_version.py b/python/vyos/component_version.py deleted file mode 100644 index b1554828d..000000000 --- a/python/vyos/component_version.py +++ /dev/null @@ -1,174 +0,0 @@ -# Copyright 2022 VyOS maintainers and contributors -# -# 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 . - -""" -Functions for reading/writing component versions. - -The config file version string has the following form: - -VyOS 1.3/1.4: - -// Warning: Do not remove the following line. -// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@3:conntrack-sync@2:dhcp-relay@2:dhcp-server@6:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@22:ipoe-server@1:ipsec@5:isis@1:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@8:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@21:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" -// Release version: 1.3.0 - -VyOS 1.2: - -/* Warning: Do not remove the following line. */ -/* === vyatta-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack-sync@1:conntrack@1:dhcp-relay@2:dhcp-server@5:dns-forwarding@1:firewall@5:ipsec@5:l2tp@1:mdns@1:nat@4:ntp@1:pppoe-server@2:pptp@1:qos@1:quagga@7:snmp@1:ssh@1:system@10:vrrp@2:wanloadbalance@3:webgui@1:webproxy@2:zone-policy@1" === */ -/* Release version: 1.2.8 */ - -""" - -import os -import re -import sys -import fileinput -from vyos.xml import component_version -from vyos.version import get_version - -def from_string(string_line, vintage='vyos'): - """ - Get component version dictionary from string. - Return empty dictionary if string contains no config information - or raise error if component version string malformed. - """ - version_dict = {} - - if vintage == 'vyos': - if re.match(r'// vyos-config-version:.+', string_line): - if not re.match(r'// vyos-config-version:\s+"([\w,-]+@\d+:)+([\w,-]+@\d+)"\s*', string_line): - raise ValueError(f"malformed configuration string: {string_line}") - - for pair in re.findall(r'([\w,-]+)@(\d+)', string_line): - version_dict[pair[0]] = int(pair[1]) - - elif vintage == 'vyatta': - if re.match(r'/\* === vyatta-config-version:.+=== \*/$', string_line): - if not re.match(r'/\* === vyatta-config-version:\s+"([\w,-]+@\d+:)+([\w,-]+@\d+)"\s+=== \*/$', string_line): - raise ValueError(f"malformed configuration string: {string_line}") - - for pair in re.findall(r'([\w,-]+)@(\d+)', string_line): - version_dict[pair[0]] = int(pair[1]) - else: - raise ValueError("Unknown config string vintage") - - return version_dict - -def from_file(config_file_name='/opt/vyatta/etc/config/config.boot', - vintage='vyos'): - """ - Get component version dictionary parsing config file line by line - """ - f = open(config_file_name, 'r') - for line_in_config in f: - version_dict = from_string(line_in_config, vintage=vintage) - if version_dict: - return version_dict - - # no version information - return {} - -def from_system(): - """ - Get system component version dict. - """ - return component_version() - -def legacy_from_system(): - """ - legacy function; imported as-is. - - Get component versions from running system; critical failure if - unable to read migration directory. - """ - import vyos.defaults - - system_versions = {} - - try: - version_info = os.listdir(vyos.defaults.directories['current']) - except OSError as err: - print("OS error: {}".format(err)) - sys.exit(1) - - for info in version_info: - if re.match(r'[\w,-]+@\d+', info): - pair = info.split('@') - system_versions[pair[0]] = int(pair[1]) - - return system_versions - -def format_string(ver: dict) -> str: - """ - Version dict to string. - """ - keys = list(ver) - keys.sort() - l = [] - for k in keys: - v = ver[k] - l.append(f'{k}@{v}') - sep = ':' - return sep.join(l) - -def system_footer(vintage='vyos') -> str: - """ - Version footer as string. - """ - ver_str = format_string(from_system()) - release = get_version() - if vintage == 'vyos': - ret_str = (f'// Warning: Do not remove the following line.\n' - + f'// vyos-config-version: "{ver_str}"\n' - + f'// Release version: {release}\n') - elif vintage == 'vyatta': - ret_str = (f'/* Warning: Do not remove the following line. */\n' - + f'/* === vyatta-config-version: "{ver_str}" === */\n' - + f'/* Release version: {release} */\n') - else: - raise ValueError("Unknown config string vintage") - - return ret_str - -def write_footer(file_name, vintage='vyos'): - """ - Write version footer to file. - """ - footer = system_footer(vintage=vintage) - if file_name: - with open(file_name, 'a') as f: - f.write(footer) - else: - sys.stdout.write(footer) - -def remove_footer(file_name): - """ - Remove old version footer. - """ - for line in fileinput.input(file_name, inplace=True): - if re.match(r'/\* Warning:.+ \*/$', line): - continue - if re.match(r'/\* === vyatta-config-version:.+=== \*/$', line): - continue - if re.match(r'/\* Release version:.+ \*/$', line): - continue - if re.match('// vyos-config-version:.+', line): - continue - if re.match('// Warning:.+', line): - continue - if re.match('// Release version:.+', line): - continue - sys.stdout.write(line) diff --git a/python/vyos/component_versions.py b/python/vyos/component_versions.py new file mode 100644 index 000000000..90b458aae --- /dev/null +++ b/python/vyos/component_versions.py @@ -0,0 +1,57 @@ +# Copyright 2017 VyOS maintainers and contributors +# +# 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 . + +""" +The version data looks like: + +/* Warning: Do not remove the following line. */ +/* === vyatta-config-version: +"cluster@1:config-management@1:conntrack-sync@1:conntrack@1:dhcp-relay@1:dhcp-server@4:firewall@5:ipsec@4:nat@4:qos@1:quagga@2:system@8:vrrp@1:wanloadbalance@3:webgui@1:webproxy@1:zone-policy@1" +=== */ +/* Release version: 1.2.0-rolling+201806131737 */ +""" + +import re + +def get_component_version(string_line): + """ + Get component version dictionary from string + return empty dictionary if string contains no config information + or raise error if component version string malformed + """ + return_value = {} + if re.match(r'/\* === vyatta-config-version:.+=== \*/$', string_line): + + if not re.match(r'/\* === vyatta-config-version:\s+"([\w,-]+@\d+:)+([\w,-]+@\d+)"\s+=== \*/$', string_line): + raise ValueError("malformed configuration string: " + str(string_line)) + + for pair in re.findall(r'([\w,-]+)@(\d+)', string_line): + if pair[0] in return_value.keys(): + raise ValueError("duplicate unit name: \"" + str(pair[0]) + "\" in string: \"" + string_line + "\"") + return_value[pair[0]] = int(pair[1]) + + return return_value + + +def get_component_versions_from_file(config_file_name='/opt/vyatta/etc/config/config.boot'): + """ + Get component version dictionary parsing config file line by line + """ + f = open(config_file_name, 'r') + for line_in_config in f: + component_version = get_component_version(line_in_config) + if component_version: + return component_version + raise ValueError("no config string in file:", config_file_name) diff --git a/python/vyos/formatversions.py b/python/vyos/formatversions.py new file mode 100644 index 000000000..29117a5d3 --- /dev/null +++ b/python/vyos/formatversions.py @@ -0,0 +1,109 @@ +# Copyright 2019 VyOS maintainers and contributors +# +# 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 . + +import sys +import os +import re +import fileinput + +def read_vyatta_versions(config_file): + config_file_versions = {} + + with open(config_file, 'r') as config_file_handle: + for config_line in config_file_handle: + if re.match(r'/\* === vyatta-config-version:.+=== \*/$', config_line): + if not re.match(r'/\* === vyatta-config-version:\s+"([\w,-]+@\d+:)+([\w,-]+@\d+)"\s+=== \*/$', config_line): + raise ValueError("malformed configuration string: " + "{}".format(config_line)) + + for pair in re.findall(r'([\w,-]+)@(\d+)', config_line): + config_file_versions[pair[0]] = int(pair[1]) + + + return config_file_versions + +def read_vyos_versions(config_file): + config_file_versions = {} + + with open(config_file, 'r') as config_file_handle: + for config_line in config_file_handle: + if re.match(r'// vyos-config-version:.+', config_line): + if not re.match(r'// vyos-config-version:\s+"([\w,-]+@\d+:)+([\w,-]+@\d+)"\s*', config_line): + raise ValueError("malformed configuration string: " + "{}".format(config_line)) + + for pair in re.findall(r'([\w,-]+)@(\d+)', config_line): + config_file_versions[pair[0]] = int(pair[1]) + + return config_file_versions + +def remove_versions(config_file): + """ + Remove old version string. + """ + for line in fileinput.input(config_file, inplace=True): + if re.match(r'/\* Warning:.+ \*/$', line): + continue + if re.match(r'/\* === vyatta-config-version:.+=== \*/$', line): + continue + if re.match(r'/\* Release version:.+ \*/$', line): + continue + if re.match('// vyos-config-version:.+', line): + continue + if re.match('// Warning:.+', line): + continue + if re.match('// Release version:.+', line): + continue + sys.stdout.write(line) + +def format_versions_string(config_versions): + cfg_keys = list(config_versions.keys()) + cfg_keys.sort() + + component_version_strings = [] + + for key in cfg_keys: + cfg_vers = config_versions[key] + component_version_strings.append('{}@{}'.format(key, cfg_vers)) + + separator = ":" + component_version_string = separator.join(component_version_strings) + + return component_version_string + +def write_vyatta_versions_foot(config_file, component_version_string, + os_version_string): + if config_file: + with open(config_file, 'a') as config_file_handle: + config_file_handle.write('/* Warning: Do not remove the following line. */\n') + config_file_handle.write('/* === vyatta-config-version: "{}" === */\n'.format(component_version_string)) + config_file_handle.write('/* Release version: {} */\n'.format(os_version_string)) + else: + sys.stdout.write('/* Warning: Do not remove the following line. */\n') + sys.stdout.write('/* === vyatta-config-version: "{}" === */\n'.format(component_version_string)) + sys.stdout.write('/* Release version: {} */\n'.format(os_version_string)) + +def write_vyos_versions_foot(config_file, component_version_string, + os_version_string): + if config_file: + with open(config_file, 'a') as config_file_handle: + config_file_handle.write('// Warning: Do not remove the following line.\n') + config_file_handle.write('// vyos-config-version: "{}"\n'.format(component_version_string)) + config_file_handle.write('// Release version: {}\n'.format(os_version_string)) + else: + sys.stdout.write('// Warning: Do not remove the following line.\n') + sys.stdout.write('// vyos-config-version: "{}"\n'.format(component_version_string)) + sys.stdout.write('// Release version: {}\n'.format(os_version_string)) + diff --git a/python/vyos/migrator.py b/python/vyos/migrator.py index 266a2e58e..a2e0daabd 100644 --- a/python/vyos/migrator.py +++ b/python/vyos/migrator.py @@ -17,8 +17,10 @@ import sys import os import json import subprocess +import vyos.version import vyos.defaults -import vyos.component_version as component_version +import vyos.systemversions as systemversions +import vyos.formatversions as formatversions class MigratorError(Exception): pass @@ -40,13 +42,13 @@ class Migrator(object): cfg_file = self._config_file component_versions = {} - cfg_versions = component_version.from_file(cfg_file, vintage='vyatta') + cfg_versions = formatversions.read_vyatta_versions(cfg_file) if cfg_versions: self._config_file_vintage = 'vyatta' component_versions = cfg_versions - cfg_versions = component_version.from_file(cfg_file, vintage='vyos') + cfg_versions = formatversions.read_vyos_versions(cfg_file) if cfg_versions: self._config_file_vintage = 'vyos' @@ -150,11 +152,19 @@ class Migrator(object): """ Write new versions string. """ + versions_string = formatversions.format_versions_string(cfg_versions) + + os_version_string = vyos.version.get_version() + if self._config_file_vintage == 'vyatta': - component_version.write_footer(self._config_file, vintage='vyatta') + formatversions.write_vyatta_versions_foot(self._config_file, + versions_string, + os_version_string) if self._config_file_vintage == 'vyos': - component_version.write_footer(self._config_file, vintage='vyos') + formatversions.write_vyos_versions_foot(self._config_file, + versions_string, + os_version_string) def save_json_record(self, component_versions: dict): """ @@ -185,7 +195,7 @@ class Migrator(object): # This will force calling all migration scripts: cfg_versions = {} - sys_versions = component_version.from_system() + sys_versions = systemversions.get_system_component_version() # save system component versions in json file for easy reference self.save_json_record(sys_versions) @@ -201,7 +211,7 @@ class Migrator(object): if not self._changed: return - component_version.remove_footer(cfg_file) + formatversions.remove_versions(cfg_file) self.write_config_file_versions(rev_versions) @@ -222,7 +232,7 @@ class VirtualMigrator(Migrator): if not self._changed: return - component_version.remove_footer(cfg_file) + formatversions.remove_versions(cfg_file) self.write_config_file_versions(cfg_versions) diff --git a/python/vyos/systemversions.py b/python/vyos/systemversions.py new file mode 100644 index 000000000..f2da76d4f --- /dev/null +++ b/python/vyos/systemversions.py @@ -0,0 +1,46 @@ +# Copyright 2019 VyOS maintainers and contributors +# +# 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 . + +import os +import re +import sys +import vyos.defaults +from vyos.xml import component_version + +# legacy version, reading from the file names in +# /opt/vyatta/etc/config-migrate/current +def get_system_versions(): + """ + Get component versions from running system; critical failure if + unable to read migration directory. + """ + system_versions = {} + + try: + version_info = os.listdir(vyos.defaults.directories['current']) + except OSError as err: + print("OS error: {}".format(err)) + sys.exit(1) + + for info in version_info: + if re.match(r'[\w,-]+@\d+', info): + pair = info.split('@') + system_versions[pair[0]] = int(pair[1]) + + return system_versions + +# read from xml cache +def get_system_component_version(): + return component_version() diff --git a/smoketest/scripts/cli/test_component_version.py b/smoketest/scripts/cli/test_component_version.py index 21cc1c761..777379bdd 100755 --- a/smoketest/scripts/cli/test_component_version.py +++ b/smoketest/scripts/cli/test_component_version.py @@ -16,7 +16,7 @@ import unittest -from vyos.component_version import legacy_from_system, from_system +from vyos.systemversions import get_system_versions, get_system_component_version # After T3474, component versions should be updated in the files in # vyos-1x/interface-definitions/include/version/ @@ -24,8 +24,8 @@ from vyos.component_version import legacy_from_system, from_system # that in the xml cache. class TestComponentVersion(unittest.TestCase): def setUp(self): - self.legacy_d = legacy_from_system() - self.xml_d = from_system() + self.legacy_d = get_system_versions() + self.xml_d = get_system_component_version() def test_component_version(self): self.assertTrue(set(self.legacy_d).issubset(set(self.xml_d))) diff --git a/src/helpers/system-versions-foot.py b/src/helpers/system-versions-foot.py index b44408542..2aa687221 100755 --- a/src/helpers/system-versions-foot.py +++ b/src/helpers/system-versions-foot.py @@ -16,13 +16,24 @@ # along with this library. If not, see . import sys +import vyos.formatversions as formatversions +import vyos.systemversions as systemversions import vyos.defaults -from vyos.component_version import write_footer +import vyos.version + +sys_versions = systemversions.get_system_component_version() + +component_string = formatversions.format_versions_string(sys_versions) + +os_version_string = vyos.version.get_version() sys.stdout.write("\n\n") if vyos.defaults.cfg_vintage == 'vyos': - write_footer(None, vintage='vyos') + formatversions.write_vyos_versions_foot(None, component_string, + os_version_string) elif vyos.defaults.cfg_vintage == 'vyatta': - write_footer(None, vintage='vyatta') + formatversions.write_vyatta_versions_foot(None, component_string, + os_version_string) else: - write_footer(None, vintage='vyatta') + formatversions.write_vyatta_versions_foot(None, component_string, + os_version_string) -- cgit v1.2.3 From 2894b52454311f8e011bed910704064be7471275 Mon Sep 17 00:00:00 2001 From: Daniel Berlin Date: Fri, 11 Mar 2022 11:01:04 -0500 Subject: [Ethtool] T4297: Update drivers supporting speed/flow/duplex The iavf, ice, and i40e drivers do not support speed, flow, or duplex control using ethtool. As a result, interface configuration changes fail to commit when using those drivers. This patch fixes that by correctly marking those drivers as not supporting those controls. --- python/vyos/ethtool.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/python/vyos/ethtool.py b/python/vyos/ethtool.py index 80d44eee6..672ee2cc9 100644 --- a/python/vyos/ethtool.py +++ b/python/vyos/ethtool.py @@ -18,6 +18,9 @@ import re from vyos.util import popen +# These drivers do not support using ethtool to change the speed, duplex, or flow control settings +_drivers_without_speed_duplex_flow = ['vmxnet3', 'virtio_net', 'xen_netfront', 'iavf', 'ice', 'i40e'] + class Ethtool: """ Class is used to retrive and cache information about an ethernet adapter @@ -188,7 +191,7 @@ class Ethtool: if duplex not in ['auto', 'full', 'half']: raise ValueError(f'Value "{duplex}" for duplex is invalid!') - if self.get_driver_name() in ['vmxnet3', 'virtio_net', 'xen_netfront']: + if self.get_driver_name() in _drivers_without_speed_duplex_flow: return False if speed in self._speed_duplex: @@ -198,7 +201,7 @@ class Ethtool: def check_flow_control(self): """ Check if the NIC supports flow-control """ - if self.get_driver_name() in ['vmxnet3', 'virtio_net', 'xen_netfront']: + if self.get_driver_name() in _drivers_without_speed_duplex_flow: return False return self._flow_control -- cgit v1.2.3 From b1d4be53cd133e9a63f8e29e400f1d7bf18b8384 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Fri, 11 Mar 2022 18:11:44 +0000 Subject: bgp: T4265: Add op-mode for bgp flowspec routes --- .../include/bgp/afi-ipv4-ipv6-flowspec.xml.i | 25 ++++++++++++++++++++++ .../include/bgp/show-bgp-common.xml.i | 1 + 2 files changed, 26 insertions(+) create mode 100644 op-mode-definitions/include/bgp/afi-ipv4-ipv6-flowspec.xml.i diff --git a/op-mode-definitions/include/bgp/afi-ipv4-ipv6-flowspec.xml.i b/op-mode-definitions/include/bgp/afi-ipv4-ipv6-flowspec.xml.i new file mode 100644 index 000000000..34228fdd1 --- /dev/null +++ b/op-mode-definitions/include/bgp/afi-ipv4-ipv6-flowspec.xml.i @@ -0,0 +1,25 @@ + + + + Network in the BGP routing table to display + + <x.x.x.x> <x.x.x.x/x> <h:h:h:h:h:h:h:h> <h:h:h:h:h:h:h:h/x> + + + + #include + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + + + Flowspec Address Family modifier + + + #include + #include + #include + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + diff --git a/op-mode-definitions/include/bgp/show-bgp-common.xml.i b/op-mode-definitions/include/bgp/show-bgp-common.xml.i index e81b26b3e..c9a112fca 100644 --- a/op-mode-definitions/include/bgp/show-bgp-common.xml.i +++ b/op-mode-definitions/include/bgp/show-bgp-common.xml.i @@ -20,6 +20,7 @@ #include #include + #include #include ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ -- cgit v1.2.3 From ff0e43807789f3c5c228683eaeb5fc4fbb8f75ce Mon Sep 17 00:00:00 2001 From: Nicolas Fort Date: Sat, 12 Mar 2022 15:10:52 +0000 Subject: Firewall: T4286: Correct ipv6-range validator --- src/validators/ipv6-range | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/validators/ipv6-range b/src/validators/ipv6-range index a3c401281..7080860c4 100755 --- a/src/validators/ipv6-range +++ b/src/validators/ipv6-range @@ -1,17 +1,20 @@ -#!/usr/bin/python3 +#!/usr/bin/env python3 -import sys -import re -from vyos.template import is_ipv6 +from ipaddress import IPv6Address +from sys import argv, exit if __name__ == '__main__': - if len(sys.argv)>1: - ipv6_range = sys.argv[1] - # Regex for ipv6-ipv6 https://regexr.com/ - if re.search('([a-f0-9:]+:+)+[a-f0-9]+-([a-f0-9:]+:+)+[a-f0-9]+', ipv6_range): - for tmp in ipv6_range.split('-'): - if not is_ipv6(tmp): - print(f'Error: {ipv6_range} is not a valid IPv6 range') - sys.exit(1) - - sys.exit(0) + if len(argv) > 1: + # try to pass validation and raise an error if failed + try: + ipv6_range = argv[1] + range_left = ipv6_range.split('-')[0] + range_right = ipv6_range.split('-')[1] + if not IPv6Address(range_left) < IPv6Address(range_right): + raise ValueError(f'left element {range_left} must be less than right element {range_right}') + except Exception as err: + print(f'Error: {ipv6_range} is not a valid IPv6 range: {err}') + exit(1) + else: + print('Error: an IPv6 range argument must be provided') + exit(1) -- cgit v1.2.3 From a7a7e38049d4601d55dd032b7d3aecf96c7e8781 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Sun, 13 Mar 2022 12:48:46 +0000 Subject: bgp: T4290: Add verify source-interface for none ip neighbor When we use neighbor as interface we must not use option 'source-interface' for example: neighbor eth0 source-interface eth0 Such option can be used for IP/IPv6 neighbors --- src/conf_mode/protocols_bgp.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index d8704727c..9e59177a8 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -166,6 +166,8 @@ def verify(bgp): raise ConfigError(f'peer-group must be set under the interface node of "{peer}"') if 'remote_as' in peer_config: raise ConfigError(f'remote-as must be set under the interface node of "{peer}"') + if 'source_interface' in peer_config['interface']: + raise ConfigError(f'"source-interface" option not allowed for neighbor "{peer}"') for afi in ['ipv4_unicast', 'ipv4_multicast', 'ipv4_labeled_unicast', 'ipv4_flowspec', 'ipv6_unicast', 'ipv6_multicast', 'ipv6_labeled_unicast', 'ipv6_flowspec', -- cgit v1.2.3 From df4b544c29974e36b52fc42bcbf617f50738a4a6 Mon Sep 17 00:00:00 2001 From: zsdc Date: Tue, 15 Mar 2022 18:29:12 +0200 Subject: bonding: T4301: Fixed arp-monitor option In verify function for arp-monitor option was used by mistake an extra conversion for incoming data before comparing items. This commit removed these unnecessary conversions and makes the option operable. --- src/conf_mode/interfaces-bonding.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index 431d65f1f..d5be21949 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -132,10 +132,10 @@ def verify(bond): return None if 'arp_monitor' in bond: - if 'target' in bond['arp_monitor'] and len(int(bond['arp_monitor']['target'])) > 16: + if 'target' in bond['arp_monitor'] and len(bond['arp_monitor']['target']) > 16: raise ConfigError('The maximum number of arp-monitor targets is 16') - if 'interval' in bond['arp_monitor'] and len(int(bond['arp_monitor']['interval'])) > 0: + if 'interval' in bond['arp_monitor'] and int(bond['arp_monitor']['interval']) > 0: if bond['mode'] in ['802.3ad', 'balance-tlb', 'balance-alb']: raise ConfigError('ARP link monitoring does not work for mode 802.3ad, ' \ 'transmit-load-balance or adaptive-load-balance') -- cgit v1.2.3 From fd9cb1574f2ef9bca648c040074820635ad301b1 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 15 Mar 2022 21:07:11 +0100 Subject: frr: T4302: upgrade to version 8.2 --- data/templates/frr/policy.frr.tmpl | 2 +- smoketest/scripts/cli/test_policy.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/templates/frr/policy.frr.tmpl b/data/templates/frr/policy.frr.tmpl index 97eb15331..60e15f4fd 100644 --- a/data/templates/frr/policy.frr.tmpl +++ b/data/templates/frr/policy.frr.tmpl @@ -204,7 +204,7 @@ route-map {{ route_map }} {{ rule_config.action }} {{ rule }} match ipv6 address prefix-list {{ rule_config.match.ipv6.address.prefix_list }} {% endif %} {% if rule_config.match.ipv6 is defined and rule_config.match.ipv6.nexthop is defined and rule_config.match.ipv6.nexthop is not none %} - match ipv6 next-hop {{ rule_config.match.ipv6.nexthop }} + match ipv6 next-hop address {{ rule_config.match.ipv6.nexthop }} {% endif %} {% if rule_config.match.large_community is defined and rule_config.match.large_community.large_community_list is defined and rule_config.match.large_community.large_community_list is not none %} match large-community {{ rule_config.match.large_community.large_community_list }} diff --git a/smoketest/scripts/cli/test_policy.py b/smoketest/scripts/cli/test_policy.py index 491f1766d..0acd41903 100755 --- a/smoketest/scripts/cli/test_policy.py +++ b/smoketest/scripts/cli/test_policy.py @@ -1030,7 +1030,7 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): tmp = f'match ipv6 address prefix-list {rule_config["match"]["ipv6-address-pfx"]}' self.assertIn(tmp, config) if 'ipv6-nexthop' in rule_config['match']: - tmp = f'match ipv6 next-hop {rule_config["match"]["ipv6-nexthop"]}' + tmp = f'match ipv6 next-hop address {rule_config["match"]["ipv6-nexthop"]}' self.assertIn(tmp, config) if 'large-community' in rule_config['match']: tmp = f'match large-community {rule_config["match"]["large-community"]}' -- cgit v1.2.3 From a5ae8f831fef4d7f9778f25c1c60e75d50c190ac Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 16 Mar 2022 07:38:13 +0100 Subject: smoketest: remove failfast=True from certian tests --- smoketest/scripts/cli/test_nat66.py | 2 +- smoketest/scripts/cli/test_protocols_mpls.py | 2 +- smoketest/scripts/cli/test_protocols_ospfv3.py | 2 +- smoketest/scripts/cli/test_system_login.py | 2 +- smoketest/scripts/cli/test_system_logs.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/smoketest/scripts/cli/test_nat66.py b/smoketest/scripts/cli/test_nat66.py index 8afe0da26..6b7b49792 100755 --- a/smoketest/scripts/cli/test_nat66.py +++ b/smoketest/scripts/cli/test_nat66.py @@ -185,4 +185,4 @@ class TestNAT66(VyOSUnitTestSHIM.TestCase): self.cli_commit() if __name__ == '__main__': - unittest.main(verbosity=2, failfast=True) + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_mpls.py b/smoketest/scripts/cli/test_protocols_mpls.py index 13d38d01b..b60f0725a 100755 --- a/smoketest/scripts/cli/test_protocols_mpls.py +++ b/smoketest/scripts/cli/test_protocols_mpls.py @@ -114,4 +114,4 @@ class TestProtocolsMPLS(VyOSUnitTestSHIM.TestCase): self.assertIn(f' interface {interface}', afiv4_config) if __name__ == '__main__': - unittest.main(verbosity=2, failfast=True) + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_ospfv3.py b/smoketest/scripts/cli/test_protocols_ospfv3.py index 1327fd910..114733002 100755 --- a/smoketest/scripts/cli/test_protocols_ospfv3.py +++ b/smoketest/scripts/cli/test_protocols_ospfv3.py @@ -278,4 +278,4 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.cli_delete(['interfaces', 'ethernet', vrf_iface, 'vrf']) if __name__ == '__main__': - unittest.main(verbosity=2, failfast=True) + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_system_login.py b/smoketest/scripts/cli/test_system_login.py index 69a06eeac..095300de3 100755 --- a/smoketest/scripts/cli/test_system_login.py +++ b/smoketest/scripts/cli/test_system_login.py @@ -235,4 +235,4 @@ class TestSystemLogin(VyOSUnitTestSHIM.TestCase): self.assertTrue(tmp) if __name__ == '__main__': - unittest.main(verbosity=2, failfast=True) + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_system_logs.py b/smoketest/scripts/cli/test_system_logs.py index 0c11c4663..92fa9c3d9 100755 --- a/smoketest/scripts/cli/test_system_logs.py +++ b/smoketest/scripts/cli/test_system_logs.py @@ -114,4 +114,4 @@ class TestSystemLogs(VyOSUnitTestSHIM.TestCase): if __name__ == '__main__': - unittest.main(verbosity=2, failfast=True) + unittest.main(verbosity=2) -- cgit v1.2.3 From 71805191d1e663af47ac1c2c11f7861d84677525 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 16 Mar 2022 20:32:28 +0100 Subject: frr: T4302: fix Jinja2 template to match new FRR syntax According to a wrong bug [1] there is no longer a vrf suffix available for interfaces. This got changed in [2] which no longer print vrf name for interface config when using vrf-lite. 1: https://github.com/FRRouting/frr/issues/10805 2: https://github.com/FRRouting/frr/pull/10411 --- data/templates/frr/isisd.frr.tmpl | 2 +- data/templates/frr/ospf6d.frr.tmpl | 2 +- data/templates/frr/ospfd.frr.tmpl | 2 +- smoketest/scripts/cli/test_protocols_ospfv3.py | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/data/templates/frr/isisd.frr.tmpl b/data/templates/frr/isisd.frr.tmpl index b1e3f825b..c68dda443 100644 --- a/data/templates/frr/isisd.frr.tmpl +++ b/data/templates/frr/isisd.frr.tmpl @@ -1,7 +1,7 @@ ! {% if interface is defined and interface is not none %} {% for iface, iface_config in interface.items() %} -interface {{ iface }} {{ 'vrf ' + vrf if vrf is defined and vrf is not none }} +interface {{ iface }} ip router isis VyOS ipv6 router isis VyOS {% if iface_config.bfd is defined %} diff --git a/data/templates/frr/ospf6d.frr.tmpl b/data/templates/frr/ospf6d.frr.tmpl index 8279e5abb..a73c6cac3 100644 --- a/data/templates/frr/ospf6d.frr.tmpl +++ b/data/templates/frr/ospf6d.frr.tmpl @@ -1,7 +1,7 @@ ! {% if interface is defined and interface is not none %} {% for iface, iface_config in interface.items() %} -interface {{ iface }} {{ 'vrf ' + vrf if vrf is defined and vrf is not none }} +interface {{ iface }} {% if iface_config.area is defined and iface_config.area is not none %} ipv6 ospf6 area {{ iface_config.area }} {% endif %} diff --git a/data/templates/frr/ospfd.frr.tmpl b/data/templates/frr/ospfd.frr.tmpl index a6618b6af..12213f162 100644 --- a/data/templates/frr/ospfd.frr.tmpl +++ b/data/templates/frr/ospfd.frr.tmpl @@ -1,7 +1,7 @@ ! {% if interface is defined and interface is not none %} {% for iface, iface_config in interface.items() %} -interface {{ iface }} {{ 'vrf ' + vrf if vrf is defined and vrf is not none }} +interface {{ iface }} {% if iface_config.authentication is defined and iface_config.authentication is not none %} {% if iface_config.authentication.plaintext_password is defined and iface_config.authentication.plaintext_password is not none %} ip ospf authentication-key {{ iface_config.authentication.plaintext_password }} diff --git a/smoketest/scripts/cli/test_protocols_ospfv3.py b/smoketest/scripts/cli/test_protocols_ospfv3.py index 114733002..2fc694fd7 100755 --- a/smoketest/scripts/cli/test_protocols_ospfv3.py +++ b/smoketest/scripts/cli/test_protocols_ospfv3.py @@ -265,8 +265,8 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.assertIn(f'router ospf6', frrconfig) self.assertIn(f' ospf6 router-id {router_id}', frrconfig) - frrconfig = self.getFRRconfig(f'interface {vrf_iface} vrf {vrf}') - self.assertIn(f'interface {vrf_iface} vrf {vrf}', frrconfig) + frrconfig = self.getFRRconfig(f'interface {vrf_iface}') + self.assertIn(f'interface {vrf_iface}', frrconfig) self.assertIn(f' ipv6 ospf6 bfd', frrconfig) frrconfig = self.getFRRconfig(f'router ospf6 vrf {vrf}') -- cgit v1.2.3 From c29c6d3d654c7280fdd4ea9fa66b5e84ef267285 Mon Sep 17 00:00:00 2001 From: fett0 Date: Thu, 17 Mar 2022 17:35:02 +0000 Subject: OSPF : T4304: Set import/export filter inter-area prefix --- data/templates/frr/ospfd.frr.tmpl | 6 +++++ .../include/ospf/protocol-common-config.xml.i | 30 ++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/data/templates/frr/ospfd.frr.tmpl b/data/templates/frr/ospfd.frr.tmpl index 12213f162..59d936b55 100644 --- a/data/templates/frr/ospfd.frr.tmpl +++ b/data/templates/frr/ospfd.frr.tmpl @@ -97,6 +97,12 @@ router ospf {{ 'vrf ' + vrf if vrf is defined and vrf is not none }} {% endif %} {% endfor %} {% endif %} +{% if area_config.export_list is defined and area_config.export_list is not none %} + area {{ area_id }} export-list {{ area_config.export_list }} +{% endif %} +{% if area_config.import_list is defined and area_config.import_list is not none %} + area {{ area_id }} import-list {{ area_config.import_list }} +{% endif %} {% if area_config.shortcut is defined and area_config.shortcut is not none %} area {{ area_id }} shortcut {{ area_config.shortcut }} {% endif %} diff --git a/interface-definitions/include/ospf/protocol-common-config.xml.i b/interface-definitions/include/ospf/protocol-common-config.xml.i index 088bee2de..3a3372e47 100644 --- a/interface-definitions/include/ospf/protocol-common-config.xml.i +++ b/interface-definitions/include/ospf/protocol-common-config.xml.i @@ -256,6 +256,36 @@ + + + Set the filter for networks announced to other areas + + policy access-list + + + u32 + Access-list number + + + + + + + + + Set the filter for networks from other areas announced + + policy access-list + + + u32 + Access-list number + + + + + + Virtual link -- cgit v1.2.3 From 91d19038f9e31657e660a88cbfc1443e454177ef Mon Sep 17 00:00:00 2001 From: fett0 Date: Fri, 18 Mar 2022 13:00:04 +0000 Subject: OSPF : T4304: add check access-list is defined --- src/conf_mode/protocols_ospf.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py index 4895cde6f..26d491838 100755 --- a/src/conf_mode/protocols_ospf.py +++ b/src/conf_mode/protocols_ospf.py @@ -25,6 +25,7 @@ from vyos.configdict import node_changed from vyos.configverify import verify_common_route_maps from vyos.configverify import verify_route_map from vyos.configverify import verify_interface_exists +from vyos.configverify import verify_access_list from vyos.template import render_to_string from vyos.util import dict_search from vyos.util import get_interface_config @@ -159,6 +160,16 @@ def verify(ospf): route_map_name = dict_search('default_information.originate.route_map', ospf) if route_map_name: verify_route_map(route_map_name, ospf) + # Validate if configured Access-list exists + if 'area' in ospf: + for area, area_config in ospf['area'].items(): + if 'import_list' in area_config: + acl_import = area_config['import_list'] + if acl_import: verify_access_list(acl_import, ospf) + if 'export_list' in area_config: + acl_export = area_config['export_list'] + if acl_export: verify_access_list(acl_export, ospf) + if 'interface' in ospf: for interface, interface_config in ospf['interface'].items(): verify_interface_exists(interface) -- cgit v1.2.3 From 496d2a5fd8c3bcbd0e7102c88eaf66d432cbb678 Mon Sep 17 00:00:00 2001 From: fett0 Date: Sat, 19 Mar 2022 18:50:03 +0000 Subject: smoketest: Verify export-list rule to ospf-area --- smoketest/scripts/cli/test_protocols_ospf.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/smoketest/scripts/cli/test_protocols_ospf.py b/smoketest/scripts/cli/test_protocols_ospf.py index ee58b0fe2..5d8e9cff2 100755 --- a/smoketest/scripts/cli/test_protocols_ospf.py +++ b/smoketest/scripts/cli/test_protocols_ospf.py @@ -368,6 +368,30 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.cli_delete(['vrf', 'name', vrf]) self.cli_delete(['interfaces', 'ethernet', vrf_iface, 'vrf']) + def test_ospf_13_export_list(self): + # Verify explort-list works on ospf-area + acl = '100' + seq = '10' + area = '0.0.0.10' + network = '10.0.0.0/8' + + + self.cli_set(['policy', 'access-list', acl, 'rule', seq, 'action', 'permit']) + self.cli_set(['policy', 'access-list', acl, 'rule', seq, 'source', 'any']) + self.cli_set(['policy', 'access-list', acl, 'rule', seq, 'destination', 'any']) + self.cli_set(base_path + ['area', area, 'network', network]) + self.cli_set(base_path + ['area', area, 'export-list', acl]) + + # commit changes + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf') + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # default + self.assertIn(f' network {network} area {area}', frrconfig) + self.assertIn(f' area {area} export-list {acl}', frrconfig) + if __name__ == '__main__': logging.basicConfig(stream=sys.stderr, level=logging.DEBUG) unittest.main(verbosity=2) -- cgit v1.2.3 From 18483a2f7d18aecb40b2003cec9a9dae6bcfaa24 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 21 Mar 2022 18:52:12 +0100 Subject: mirror: T3089: add verify_mirror() also for bond and bridge interfaces --- src/conf_mode/interfaces-bonding.py | 2 ++ src/conf_mode/interfaces-bridge.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index d5be21949..bb53cd6c2 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -28,6 +28,7 @@ from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_dhcpv6 from vyos.configverify import verify_source_interface +from vyos.configverify import verify_mirror from vyos.configverify import verify_mtu_ipv6 from vyos.configverify import verify_vlan_config from vyos.configverify import verify_vrf @@ -149,6 +150,7 @@ def verify(bond): verify_address(bond) verify_dhcpv6(bond) verify_vrf(bond) + verify_mirror(bond) # use common function to verify VLAN configuration verify_vlan_config(bond) diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py index f4dba9d4a..9f840cb58 100755 --- a/src/conf_mode/interfaces-bridge.py +++ b/src/conf_mode/interfaces-bridge.py @@ -27,6 +27,7 @@ from vyos.configdict import is_source_interface from vyos.configdict import has_vlan_subinterface_configured from vyos.configdict import dict_merge from vyos.configverify import verify_dhcpv6 +from vyos.configverify import verify_mirror from vyos.configverify import verify_vrf from vyos.ifconfig import BridgeIf from vyos.validate import has_address_configured @@ -105,6 +106,7 @@ def verify(bridge): verify_dhcpv6(bridge) verify_vrf(bridge) + verify_mirror(bridge) ifname = bridge['ifname'] -- cgit v1.2.3 From 3584691b35f35e40a1bfc22c34da031141fd0dfa Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 21 Mar 2022 21:41:41 +0100 Subject: qos: T4284: initial XML interface definitions for rewrite --- Makefile | 6 + data/configd-include.json | 1 + .../include/interface/redirect.xml.i | 17 + .../include/interface/traffic-policy.xml.i | 43 ++ .../include/interface/vif-s.xml.i | 4 + interface-definitions/include/interface/vif.xml.i | 4 +- interface-definitions/include/qos/bandwidth.xml.i | 15 + interface-definitions/include/qos/burst.xml.i | 16 + .../include/qos/codel-quantum.xml.i | 16 + interface-definitions/include/qos/dscp.xml.i | 143 ++++ interface-definitions/include/qos/flows.xml.i | 16 + interface-definitions/include/qos/hfsc-d.xml.i | 15 + interface-definitions/include/qos/hfsc-m1.xml.i | 32 + interface-definitions/include/qos/hfsc-m2.xml.i | 32 + interface-definitions/include/qos/interval.xml.i | 16 + interface-definitions/include/qos/match.xml.i | 221 +++++++ interface-definitions/include/qos/max-length.xml.i | 15 + .../include/qos/queue-limit-1-4294967295.xml.i | 15 + .../include/qos/queue-limit-2-10999.xml.i | 16 + interface-definitions/include/qos/queue-type.xml.i | 30 + interface-definitions/include/qos/set-dscp.xml.i | 63 ++ interface-definitions/include/qos/target.xml.i | 16 + interface-definitions/include/qos/tcp-flags.xml.i | 21 + interface-definitions/interfaces-bonding.xml.in | 2 + interface-definitions/interfaces-bridge.xml.in | 2 + interface-definitions/interfaces-dummy.xml.in | 2 + interface-definitions/interfaces-ethernet.xml.in | 2 + interface-definitions/interfaces-geneve.xml.in | 2 + interface-definitions/interfaces-input.xml.in | 30 + interface-definitions/interfaces-l2tpv3.xml.in | 1 + interface-definitions/interfaces-loopback.xml.in | 2 + interface-definitions/interfaces-macsec.xml.in | 2 + interface-definitions/interfaces-openvpn.xml.in | 2 + interface-definitions/interfaces-pppoe.xml.in | 4 +- .../interfaces-pseudo-ethernet.xml.in | 2 + interface-definitions/interfaces-tunnel.xml.in | 4 +- interface-definitions/interfaces-vti.xml.in | 2 + interface-definitions/interfaces-vxlan.xml.in | 2 + interface-definitions/interfaces-wireguard.xml.in | 4 +- interface-definitions/interfaces-wireless.xml.in | 2 + interface-definitions/interfaces-wwan.xml.in | 4 +- interface-definitions/qos.xml.in | 721 +++++++++++++++++++++ python/vyos/configverify.py | 16 + src/conf_mode/interfaces-bonding.py | 4 +- src/conf_mode/interfaces-bridge.py | 2 + src/conf_mode/interfaces-dummy.py | 2 + src/conf_mode/interfaces-ethernet.py | 2 + src/conf_mode/interfaces-geneve.py | 2 + src/conf_mode/interfaces-l2tpv3.py | 2 + src/conf_mode/interfaces-loopback.py | 2 + src/conf_mode/interfaces-macsec.py | 2 + src/conf_mode/interfaces-pppoe.py | 2 + src/conf_mode/interfaces-pseudo-ethernet.py | 2 + src/conf_mode/interfaces-tunnel.py | 2 + src/conf_mode/interfaces-vti.py | 2 + src/conf_mode/interfaces-vxlan.py | 2 + src/conf_mode/interfaces-wireguard.py | 2 + src/conf_mode/interfaces-wireless.py | 2 + src/conf_mode/interfaces-wwan.py | 2 + src/conf_mode/qos.py | 90 +++ 60 files changed, 1699 insertions(+), 6 deletions(-) create mode 100644 interface-definitions/include/interface/redirect.xml.i create mode 100644 interface-definitions/include/interface/traffic-policy.xml.i create mode 100644 interface-definitions/include/qos/bandwidth.xml.i create mode 100644 interface-definitions/include/qos/burst.xml.i create mode 100644 interface-definitions/include/qos/codel-quantum.xml.i create mode 100644 interface-definitions/include/qos/dscp.xml.i create mode 100644 interface-definitions/include/qos/flows.xml.i create mode 100644 interface-definitions/include/qos/hfsc-d.xml.i create mode 100644 interface-definitions/include/qos/hfsc-m1.xml.i create mode 100644 interface-definitions/include/qos/hfsc-m2.xml.i create mode 100644 interface-definitions/include/qos/interval.xml.i create mode 100644 interface-definitions/include/qos/match.xml.i create mode 100644 interface-definitions/include/qos/max-length.xml.i create mode 100644 interface-definitions/include/qos/queue-limit-1-4294967295.xml.i create mode 100644 interface-definitions/include/qos/queue-limit-2-10999.xml.i create mode 100644 interface-definitions/include/qos/queue-type.xml.i create mode 100644 interface-definitions/include/qos/set-dscp.xml.i create mode 100644 interface-definitions/include/qos/target.xml.i create mode 100644 interface-definitions/include/qos/tcp-flags.xml.i create mode 100644 interface-definitions/interfaces-input.xml.in create mode 100644 interface-definitions/qos.xml.in create mode 100755 src/conf_mode/qos.py diff --git a/Makefile b/Makefile index 29744b323..431f3a8c2 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,12 @@ interface_definitions: $(config_xml_obj) # XXX: delete top level node.def's that now live in other packages # IPSec VPN EAP-RADIUS does not support source-address rm -rf $(TMPL_DIR)/vpn/ipsec/remote-access/radius/source-address + + # T4284 neq QoS implementation is not yet live + find $(TMPL_DIR)/interfaces -name traffic-policy -type d -exec rm -rf {} \; + find $(TMPL_DIR)/interfaces -name redirect -type d -exec rm -rf {} \; + rm -rf $(TMPL_DIR)/interfaces/input + # XXX: test if there are empty node.def files - this is not allowed as these # could mask help strings or mandatory priority statements find $(TMPL_DIR) -name node.def -type f -empty -exec false {} + || sh -c 'echo "There are empty node.def files! Check your interface definitions." && exit 1' diff --git a/data/configd-include.json b/data/configd-include.json index c85ab0725..b77d48001 100644 --- a/data/configd-include.json +++ b/data/configd-include.json @@ -48,6 +48,7 @@ "protocols_ripng.py", "protocols_static.py", "protocols_static_multicast.py", +"qos.py", "salt-minion.py", "service_console-server.py", "service_ids_fastnetmon.py", diff --git a/interface-definitions/include/interface/redirect.xml.i b/interface-definitions/include/interface/redirect.xml.i new file mode 100644 index 000000000..3be9ee16b --- /dev/null +++ b/interface-definitions/include/interface/redirect.xml.i @@ -0,0 +1,17 @@ + + + + Incoming packet redirection destination + + + + + txt + Interface name + + + + + + + diff --git a/interface-definitions/include/interface/traffic-policy.xml.i b/interface-definitions/include/interface/traffic-policy.xml.i new file mode 100644 index 000000000..cd60b62a5 --- /dev/null +++ b/interface-definitions/include/interface/traffic-policy.xml.i @@ -0,0 +1,43 @@ + + + + Traffic-policy for interface + + + + + Ingress traffic policy for interface + + traffic-policy drop-tail + traffic-policy fair-queue + traffic-policy fq-codel + traffic-policy limiter + traffic-policy network-emulator + traffic-policy priority-queue + traffic-policy random-detect + traffic-policy rate-control + traffic-policy round-robin + traffic-policy shaper + traffic-policy shaper-hfsc + + + txt + Policy name + + + + + + Egress traffic policy for interface + + traffic-policy + + + txt + Policy name + + + + + + \ No newline at end of file diff --git a/interface-definitions/include/interface/vif-s.xml.i b/interface-definitions/include/interface/vif-s.xml.i index f1a61ff64..59a47b5ff 100644 --- a/interface-definitions/include/interface/vif-s.xml.i +++ b/interface-definitions/include/interface/vif-s.xml.i @@ -64,11 +64,15 @@ #include #include #include + #include + #include #include #include #include + #include + #include #include diff --git a/interface-definitions/include/interface/vif.xml.i b/interface-definitions/include/interface/vif.xml.i index 11ba7e2f8..8a1475711 100644 --- a/interface-definitions/include/interface/vif.xml.i +++ b/interface-definitions/include/interface/vif.xml.i @@ -18,7 +18,6 @@ #include #include #include - #include #include #include @@ -51,6 +50,9 @@ #include #include #include + #include + #include + #include diff --git a/interface-definitions/include/qos/bandwidth.xml.i b/interface-definitions/include/qos/bandwidth.xml.i new file mode 100644 index 000000000..82af22f42 --- /dev/null +++ b/interface-definitions/include/qos/bandwidth.xml.i @@ -0,0 +1,15 @@ + + + + Traffic-limit used for this class + + <number> + Rate in kbit (kilobit per second) + + + <number><suffix> + Rate with scaling suffix (mbit, mbps, ...) + + + + diff --git a/interface-definitions/include/qos/burst.xml.i b/interface-definitions/include/qos/burst.xml.i new file mode 100644 index 000000000..761618027 --- /dev/null +++ b/interface-definitions/include/qos/burst.xml.i @@ -0,0 +1,16 @@ + + + + Burst size for this class + + <number> + Bytes + + + <number><suffix> + Bytes with scaling suffix (kb, mb, gb) + + + 15k + + diff --git a/interface-definitions/include/qos/codel-quantum.xml.i b/interface-definitions/include/qos/codel-quantum.xml.i new file mode 100644 index 000000000..bc24630b6 --- /dev/null +++ b/interface-definitions/include/qos/codel-quantum.xml.i @@ -0,0 +1,16 @@ + + + + Deficit in the fair queuing algorithm + + u32:0-1048576 + Number of bytes used as 'deficit' + + + + + Interval must be in range 0 to 1048576 + + 1514 + + diff --git a/interface-definitions/include/qos/dscp.xml.i b/interface-definitions/include/qos/dscp.xml.i new file mode 100644 index 000000000..bb90850ac --- /dev/null +++ b/interface-definitions/include/qos/dscp.xml.i @@ -0,0 +1,143 @@ + + + + Match on Differentiated Services Codepoint (DSCP) + + default reliability throughput lowdelay priority immediate flash flash-override critical internet network AF11 AF12 AF13 AF21 AF22 AF23 AF31 AF32 AF33 AF41 AF42 AF43 CS1 CS2 CS3 CS4 CS5 CS6 CS7 EF + + + u32:0-63 + Differentiated Services Codepoint (DSCP) value + + + default + match DSCP (000000) + + + reliability + match DSCP (000001) + + + throughput + match DSCP (000010) + + + lowdelay + match DSCP (000100) + + + priority + match DSCP (001000) + + + immediate + match DSCP (010000) + + + flash + match DSCP (011000) + + + flash-override + match DSCP (100000) + + + critical + match DSCP (101000) + + + internet + match DSCP (110000) + + + network + match DSCP (111000) + + + AF11 + High-throughput data + + + AF12 + High-throughput data + + + AF13 + High-throughput data + + + AF21 + Low-latency data + + + AF22 + Low-latency data + + + AF23 + Low-latency data + + + AF31 + Multimedia streaming + + + AF32 + Multimedia streaming + + + AF33 + Multimedia streaming + + + AF41 + Multimedia conferencing + + + AF42 + Multimedia conferencing + + + AF43 + Multimedia conferencing + + + CS1 + Low-priority data + + + CS2 + OAM + + + CS3 + Broadcast video + + + CS4 + Real-time interactive + + + CS5 + Signaling + + + CS6 + Network control + + + CS7 + + + + EF + Expedited Forwarding + + + + (default|reliability|throughput|lowdelay|priority|immediate|flash|flash-override|critical|internet|network|AF11|AF12|AF13|AF21|AF22|AF23|AF31|AF32|AF33|AF41|AF42|AF43|CS1|CS2|CS3|CS4|CS5|CS6|CS7|EF) + + Priority must be between 0 and 63 + + + diff --git a/interface-definitions/include/qos/flows.xml.i b/interface-definitions/include/qos/flows.xml.i new file mode 100644 index 000000000..a7d7c6422 --- /dev/null +++ b/interface-definitions/include/qos/flows.xml.i @@ -0,0 +1,16 @@ + + + + Number of flows into which the incoming packets are classified + + u32:1-65536 + Number of flows + + + + + Interval must be in range 1 to 65536 + + 1024 + + diff --git a/interface-definitions/include/qos/hfsc-d.xml.i b/interface-definitions/include/qos/hfsc-d.xml.i new file mode 100644 index 000000000..2a513509c --- /dev/null +++ b/interface-definitions/include/qos/hfsc-d.xml.i @@ -0,0 +1,15 @@ + + + + Service curve delay + + <number> + Time in milliseconds + + + + + Priority must be between 0 and 65535 + + + diff --git a/interface-definitions/include/qos/hfsc-m1.xml.i b/interface-definitions/include/qos/hfsc-m1.xml.i new file mode 100644 index 000000000..749d01f57 --- /dev/null +++ b/interface-definitions/include/qos/hfsc-m1.xml.i @@ -0,0 +1,32 @@ + + + + Linkshare m1 parameter for class traffic + + <number> + Rate in kbit (kilobit per second) + + + <number>%% + Percentage of overall rate + + + <number>bit + bit(1), kbit(10^3), mbit(10^6), gbit, tbit + + + <number>ibit + kibit(1024), mibit(1024^2), gibit(1024^3), tbit(1024^4) + + + <number>ibps + kibps(1024*8), mibps(1024^2*8), gibps, tibps - Byte/sec + + + <number>bps + bps(8),kbps(8*10^3),mbps(8*10^6), gbps, tbps - Byte/sec + + + 100% + + diff --git a/interface-definitions/include/qos/hfsc-m2.xml.i b/interface-definitions/include/qos/hfsc-m2.xml.i new file mode 100644 index 000000000..24e8f5d63 --- /dev/null +++ b/interface-definitions/include/qos/hfsc-m2.xml.i @@ -0,0 +1,32 @@ + + + + Linkshare m2 parameter for class traffic + + <number> + Rate in kbit (kilobit per second) + + + <number>%% + Percentage of overall rate + + + <number>bit + bit(1), kbit(10^3), mbit(10^6), gbit, tbit + + + <number>ibit + kibit(1024), mibit(1024^2), gibit(1024^3), tbit(1024^4) + + + <number>ibps + kibps(1024*8), mibps(1024^2*8), gibps, tibps - Byte/sec + + + <number>bps + bps(8),kbps(8*10^3),mbps(8*10^6), gbps, tbps - Byte/sec + + + 100% + + diff --git a/interface-definitions/include/qos/interval.xml.i b/interface-definitions/include/qos/interval.xml.i new file mode 100644 index 000000000..41896ac9c --- /dev/null +++ b/interface-definitions/include/qos/interval.xml.i @@ -0,0 +1,16 @@ + + + + Interval used to measure the delay + + u32 + Interval in milliseconds + + + + + Interval must be in range 0 to 4294967295 + + 100 + + diff --git a/interface-definitions/include/qos/match.xml.i b/interface-definitions/include/qos/match.xml.i new file mode 100644 index 000000000..7d89e4460 --- /dev/null +++ b/interface-definitions/include/qos/match.xml.i @@ -0,0 +1,221 @@ + + + + Class matching rule name + + [^-].* + + Match queue name cannot start with hyphen (-) + + + #include + + + Ethernet header match + + + + + Ethernet destination address for this match + + macaddr + MAC address to match + + + + + + + + + Ethernet protocol for this match + + + all 802.1Q 802_2 802_3 aarp aoe arp atalk dec ip ipv6 ipx lat localtalk rarp snap x25 + + + u32:0-65535 + Ethernet protocol number + + + txt + Ethernet protocol name + + + all + Any protocol + + + ip + Internet IP (IPv4) + + + ipv6 + Internet IP (IPv6) + + + arp + Address Resolution Protocol + + + atalk + Appletalk + + + ipx + Novell Internet Packet Exchange + + + 802.1Q + 802.1Q VLAN tag + + + + + + + + + Ethernet source address for this match + + macaddr + MAC address to match + + + + + + + + + #include + + + Match IP protocol header + + + + + Match on destination port or address + + + + + IPv4 destination address for this match + + ipv4net + IPv4 address and prefix length + + + + + + + #include + + + #include + #include + #include + + + Match on source port or address + + + + + IPv4 source address for this match + + ipv4net + IPv4 address and prefix length + + + + + + + #include + + + #include + + + + + Match IPv6 protocol header + + + + + Match on destination port or address + + + + + IPv6 destination address for this match + + ipv6net + IPv6 address and prefix length + + + + + + + #include + + + #include + #include + #include + + + Match on source port or address + + + + + IPv6 source address for this match + + ipv6net + IPv6 address and prefix length + + + + + + + #include + + + #include + + + + + Match on mark applied by firewall + + txt + FW mark to match + + + + + + + + + Virtual Local Area Network (VLAN) ID for this match + + u32:0-4095 + Virtual Local Area Network (VLAN) tag + + + + + VLAN ID must be between 0 and 4095 + + + + + diff --git a/interface-definitions/include/qos/max-length.xml.i b/interface-definitions/include/qos/max-length.xml.i new file mode 100644 index 000000000..4cc20f8c4 --- /dev/null +++ b/interface-definitions/include/qos/max-length.xml.i @@ -0,0 +1,15 @@ + + + + Maximum packet length (ipv4) + + u32:0-65535 + Maximum packet/payload length + + + + + Maximum IPv4 total packet length is 65535 + + + diff --git a/interface-definitions/include/qos/queue-limit-1-4294967295.xml.i b/interface-definitions/include/qos/queue-limit-1-4294967295.xml.i new file mode 100644 index 000000000..2f2d44631 --- /dev/null +++ b/interface-definitions/include/qos/queue-limit-1-4294967295.xml.i @@ -0,0 +1,15 @@ + + + + Maximum queue size + + u32:1-4294967295 + Queue size in packets + + + + + Queue limit must be greater than zero + + + diff --git a/interface-definitions/include/qos/queue-limit-2-10999.xml.i b/interface-definitions/include/qos/queue-limit-2-10999.xml.i new file mode 100644 index 000000000..7a9c8266b --- /dev/null +++ b/interface-definitions/include/qos/queue-limit-2-10999.xml.i @@ -0,0 +1,16 @@ + + + + Upper limit of the queue + + u32:2-10999 + Queue size in packets + + + + + Queue limit must greater than 1 and less than 11000 + + 10240 + + diff --git a/interface-definitions/include/qos/queue-type.xml.i b/interface-definitions/include/qos/queue-type.xml.i new file mode 100644 index 000000000..634f61024 --- /dev/null +++ b/interface-definitions/include/qos/queue-type.xml.i @@ -0,0 +1,30 @@ + + + + Queue type for default traffic + + fq-codel fair-queue drop-tail random-detect + + + fq-codel + Fair Queue Codel + + + fair-queue + Stochastic Fair Queue (SFQ) + + + drop-tail + First-In-First-Out (FIFO) + + + random-detect + Random Early Detection (RED) + + + (fq-codel|fair-queue|drop-tail|random-detect) + + + drop-tail + + diff --git a/interface-definitions/include/qos/set-dscp.xml.i b/interface-definitions/include/qos/set-dscp.xml.i new file mode 100644 index 000000000..55c0ea44d --- /dev/null +++ b/interface-definitions/include/qos/set-dscp.xml.i @@ -0,0 +1,63 @@ + + + + Change the Differentiated Services (DiffServ) field in the IP header + + default reliability throughput lowdelay priority immediate flash flash-override critical internet network + + + u32:0-63 + Priority order for bandwidth pool + + + default + match DSCP (000000) + + + reliability + match DSCP (000001) + + + throughput + match DSCP (000010) + + + lowdelay + match DSCP (000100) + + + priority + match DSCP (001000) + + + immediate + match DSCP (010000) + + + flash + match DSCP (011000) + + + flash-override + match DSCP (100000) + + + critical + match DSCP (101000) + + + internet + match DSCP (110000) + + + network + match DSCP (111000) + + + + (default|reliability|throughput|lowdelay|priority|immediate|flash|flash-override|critical|internet|network) + + Priority must be between 0 and 63 + + + diff --git a/interface-definitions/include/qos/target.xml.i b/interface-definitions/include/qos/target.xml.i new file mode 100644 index 000000000..bf6342ac9 --- /dev/null +++ b/interface-definitions/include/qos/target.xml.i @@ -0,0 +1,16 @@ + + + + Acceptable minimum standing/persistent queue delay + + u32 + Queue delay in milliseconds + + + + + Delay must be in range 0 to 4294967295 + + 5 + + diff --git a/interface-definitions/include/qos/tcp-flags.xml.i b/interface-definitions/include/qos/tcp-flags.xml.i new file mode 100644 index 000000000..81d70d1f3 --- /dev/null +++ b/interface-definitions/include/qos/tcp-flags.xml.i @@ -0,0 +1,21 @@ + + + + TCP Flags matching + + + + + Match TCP ACK + + + + + + Match TCP SYN + + + + + + diff --git a/interface-definitions/interfaces-bonding.xml.in b/interface-definitions/interfaces-bonding.xml.in index b98f4b960..20ece5137 100644 --- a/interface-definitions/interfaces-bonding.xml.in +++ b/interface-definitions/interfaces-bonding.xml.in @@ -207,6 +207,8 @@ + #include + #include #include #include #include diff --git a/interface-definitions/interfaces-bridge.xml.in b/interface-definitions/interfaces-bridge.xml.in index fabfb917a..6957067cd 100644 --- a/interface-definitions/interfaces-bridge.xml.in +++ b/interface-definitions/interfaces-bridge.xml.in @@ -210,6 +210,8 @@ + #include + #include #include diff --git a/interface-definitions/interfaces-dummy.xml.in b/interface-definitions/interfaces-dummy.xml.in index 3bca8b950..109ed1b50 100644 --- a/interface-definitions/interfaces-dummy.xml.in +++ b/interface-definitions/interfaces-dummy.xml.in @@ -30,6 +30,8 @@ #include + #include + #include #include diff --git a/interface-definitions/interfaces-ethernet.xml.in b/interface-definitions/interfaces-ethernet.xml.in index be7bddfa4..7d28912c0 100644 --- a/interface-definitions/interfaces-ethernet.xml.in +++ b/interface-definitions/interfaces-ethernet.xml.in @@ -196,6 +196,8 @@ + #include + #include #include #include #include diff --git a/interface-definitions/interfaces-geneve.xml.in b/interface-definitions/interfaces-geneve.xml.in index dd4d324d4..aa5809e60 100644 --- a/interface-definitions/interfaces-geneve.xml.in +++ b/interface-definitions/interfaces-geneve.xml.in @@ -50,6 +50,8 @@ + #include + #include #include #include diff --git a/interface-definitions/interfaces-input.xml.in b/interface-definitions/interfaces-input.xml.in new file mode 100644 index 000000000..f2eb01c58 --- /dev/null +++ b/interface-definitions/interfaces-input.xml.in @@ -0,0 +1,30 @@ + + + + + + + Input Functional Block (IFB) interface name + + 310 + + ifb[0-9]+ + + Input interface must be named ifbN + + ifbN + Input interface name + + + + #include + #include + #include + #include + #include + #include + + + + + diff --git a/interface-definitions/interfaces-l2tpv3.xml.in b/interface-definitions/interfaces-l2tpv3.xml.in index ba9bcb0a2..124863653 100644 --- a/interface-definitions/interfaces-l2tpv3.xml.in +++ b/interface-definitions/interfaces-l2tpv3.xml.in @@ -125,6 +125,7 @@ + #include #include diff --git a/interface-definitions/interfaces-loopback.xml.in b/interface-definitions/interfaces-loopback.xml.in index 7be15ab89..ffffc0220 100644 --- a/interface-definitions/interfaces-loopback.xml.in +++ b/interface-definitions/interfaces-loopback.xml.in @@ -26,6 +26,8 @@ #include + #include + #include diff --git a/interface-definitions/interfaces-macsec.xml.in b/interface-definitions/interfaces-macsec.xml.in index 7206e57b1..311e95c2f 100644 --- a/interface-definitions/interfaces-macsec.xml.in +++ b/interface-definitions/interfaces-macsec.xml.in @@ -122,6 +122,8 @@ 1460 #include + #include + #include #include diff --git a/interface-definitions/interfaces-openvpn.xml.in b/interface-definitions/interfaces-openvpn.xml.in index eb574eb52..73e30e590 100644 --- a/interface-definitions/interfaces-openvpn.xml.in +++ b/interface-definitions/interfaces-openvpn.xml.in @@ -816,6 +816,8 @@ + #include + #include #include diff --git a/interface-definitions/interfaces-pppoe.xml.in b/interface-definitions/interfaces-pppoe.xml.in index ed0e45840..1d888236e 100644 --- a/interface-definitions/interfaces-pppoe.xml.in +++ b/interface-definitions/interfaces-pppoe.xml.in @@ -49,7 +49,6 @@ #include #include #include - #include Delay before disconnecting idle session (in seconds) @@ -134,6 +133,9 @@ Service name must be alphanumeric only + #include + #include + #include diff --git a/interface-definitions/interfaces-pseudo-ethernet.xml.in b/interface-definitions/interfaces-pseudo-ethernet.xml.in index bf7055f8d..7baeac537 100644 --- a/interface-definitions/interfaces-pseudo-ethernet.xml.in +++ b/interface-definitions/interfaces-pseudo-ethernet.xml.in @@ -59,6 +59,8 @@ private #include + #include + #include #include #include diff --git a/interface-definitions/interfaces-tunnel.xml.in b/interface-definitions/interfaces-tunnel.xml.in index eb1708aaa..bc9297c86 100644 --- a/interface-definitions/interfaces-tunnel.xml.in +++ b/interface-definitions/interfaces-tunnel.xml.in @@ -20,7 +20,6 @@ #include #include #include - #include #include 1476 @@ -288,6 +287,9 @@ + #include + #include + #include diff --git a/interface-definitions/interfaces-vti.xml.in b/interface-definitions/interfaces-vti.xml.in index f03c7476d..538194c2b 100644 --- a/interface-definitions/interfaces-vti.xml.in +++ b/interface-definitions/interfaces-vti.xml.in @@ -34,6 +34,8 @@ #include #include #include + #include + #include #include #include #include diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in index 0546b4199..18abf9f20 100644 --- a/interface-definitions/interfaces-vxlan.xml.in +++ b/interface-definitions/interfaces-vxlan.xml.in @@ -99,6 +99,8 @@ #include #include #include + #include + #include #include #include diff --git a/interface-definitions/interfaces-wireguard.xml.in b/interface-definitions/interfaces-wireguard.xml.in index 1b4b4a816..2f130c6f2 100644 --- a/interface-definitions/interfaces-wireguard.xml.in +++ b/interface-definitions/interfaces-wireguard.xml.in @@ -19,7 +19,6 @@ #include #include #include - #include #include #include #include @@ -120,6 +119,9 @@ + #include + #include + #include diff --git a/interface-definitions/interfaces-wireless.xml.in b/interface-definitions/interfaces-wireless.xml.in index 9db9fd757..eebe8f841 100644 --- a/interface-definitions/interfaces-wireless.xml.in +++ b/interface-definitions/interfaces-wireless.xml.in @@ -778,6 +778,8 @@ monitor + #include + #include #include #include diff --git a/interface-definitions/interfaces-wwan.xml.in b/interface-definitions/interfaces-wwan.xml.in index 03554feed..7007a67ae 100644 --- a/interface-definitions/interfaces-wwan.xml.in +++ b/interface-definitions/interfaces-wwan.xml.in @@ -30,7 +30,6 @@ #include #include #include - #include #include #include @@ -41,6 +40,9 @@ #include #include #include + #include + #include + #include diff --git a/interface-definitions/qos.xml.in b/interface-definitions/qos.xml.in new file mode 100644 index 000000000..d4468543c --- /dev/null +++ b/interface-definitions/qos.xml.in @@ -0,0 +1,721 @@ + + + + + Quality of Service (QOS) policy type + 900 + + + + + Packet limited First In, First Out queue + + txt + Policy name + + + [[:alnum:]][-_[:alnum:]]* + + Only alpha-numeric policy name allowed + + + #include + #include + + + + + Stochastic Fairness Queueing + + txt + Policy name + + + [[:alnum:]][-_[:alnum:]]* + + Only alpha-numeric policy name allowed + + + #include + + + Interval in seconds for queue algorithm perturbation + + u32:0 + No perturbation + + + u32:1-127 + Interval in seconds for queue algorithm perturbation (advised: 10) + + + + + Interval must be in range 0 to 127 + + 0 + + + + Upper limit of the SFQ + + u32:2-127 + Queue size in packets + + + + + Queue limit must greater than 1 and less than 128 + + 127 + + + + + + Fair Queuing Controlled Delay + + txt + Policy name + + + [[:alnum:]][-_[:alnum:]]* + + Only alpha-numeric policy name allowed + + + #include + #include + #include + #include + #include + #include + + + + + Traffic input limiting policy + + txt + Policy name + + + [[:alnum:]][-_[:alnum:]]* + + Only alpha-numeric policy name allowed + + + + + Class ID + + u32:1-4090 + Class Identifier + + + + + Class identifier must be between 1 and 4090 + + + #include + #include + #include + #include + + + Priority for rule evaluation + + u32:0-20 + Priority for match rule evaluation + + + + + Priority must be between 0 and 20 + + 20 + + + + + + Default policy + + + #include + #include + + + #include + + + + + Network emulator policy + + txt + Policy name + + + [[:alnum:]][-_[:alnum:]]* + + Only alpha-numeric policy name allowed + + + #include + #include + #include + + + Adds delay to packets outgoing to chosen network interface + + <number> + Time in milliseconds + + + + + Priority must be between 0 and 65535 + + + + + Introducing error in a random position for chosen percent of packets + + <number> + Percentage of packets affected + + + + + Priority must be between 0 and 100 + + + + + Add independent loss probability to the packets outgoing to chosen network interface + + <number> + Percentage of packets affected + + + + + Must be between 0 and 100 + + + + + Add independent loss probability to the packets outgoing to chosen network interface + + <number> + Percentage of packets affected + + + + + Must be between 0 and 100 + + + + + Packet reordering percentage + + <number> + Percentage of packets affected + + + + + Must be between 0 and 100 + + + #include + + + + + Priority queuing based policy + + txt + Policy name + + + [[:alnum:]][-_[:alnum:]]* + + Only alpha-numeric policy name allowed + + + + + Class Handle + + u32:1-7 + Priority + + + + + Class handle must be between 1 and 7 + + + #include + #include + #include + #include + #include + #include + #include + #include + + + + + Default policy + + + #include + #include + #include + #include + #include + #include + #include + + + #include + + + + + Priority queuing based policy + + txt + Policy name + + + [[:alnum:]][-_[:alnum:]]* + + Only alpha-numeric policy name allowed + + + #include + + auto + + #include + + + IP precedence + + u32:0-7 + IP precedence value + + + + + IP precedence value must be between 0 and 7 + + + #include + + + Average packet size (bytes) + + u32:16-10240 + Average packet size in bytes + + + + + Average packet size must be between 16 and 10240 + + 1024 + + + + Mark probability for this precedence + + <number> + Numeric value (1/N) + + + + + Mark probability must be greater than 0 + + + + + Maximum threshold for random detection + + u32:0-4096 + Maximum Threshold in packets + + + + + Threshold must be between 0 and 4096 + + + + + Minimum threshold for random detection + + u32:0-4096 + Maximum Threshold in packets + + + + + Threshold must be between 0 and 4096 + + + + + + + + + Rate limiting policy (Token Bucket Filter) + + txt + Policy name + + + [[:alnum:]][-_[:alnum:]]* + + Only alpha-numeric policy name allowed + + + #include + #include + #include + + + Maximum latency + + <number> + Time in milliseconds + + + + + Threshold must be between 0 and 4096 + + 50 + + + + + + Round-Robin based policy + + txt + Policy name + + + [[:alnum:]][-_[:alnum:]]* + + Only alpha-numeric policy name allowed + + + #include + + + Class ID + + u32:1-4095 + Class Identifier + + + + + Class identifier must be between 1 and 4095 + + + #include + #include + #include + #include + #include + + + Packet scheduling quantum + + u32:1-4294967295 + Packet scheduling quantum (bytes) + + + + + Quantum must be in range 1 to 4294967295 + + + #include + #include + #include + + + + + + + Hierarchical Fair Service Curve's policy + + txt + Policy name + + + [[:alnum:]][-_[:alnum:]]* + + Only alpha-numeric policy name allowed + + + #include + + auto + + #include + + + Class ID + + u32:1-4095 + Class Identifier + + + + + Class identifier must be between 1 and 4095 + + + #include + + + Linkshare class settings + + + #include + #include + #include + + + #include + + + Realtime class settings + + + #include + #include + #include + + + + + Upperlimit class settings + + + #include + #include + #include + + + + + + + Default policy + + + + + Linkshare class settings + + + #include + #include + #include + + + + + Realtime class settings + + + #include + #include + #include + + + + + Upperlimit class settings + + + #include + #include + #include + + + + + + + + + Traffic shaping based policy (Hierarchy Token Bucket) + + txt + Policy name + + + [[:alnum:]][-_[:alnum:]]* + + Only alpha-numeric policy name allowed + + + #include + + auto + + + + Class ID + + u32:2-4095 + Class Identifier + + + + + Class identifier must be between 2 and 4095 + + + #include + + 100% + + #include + + + Bandwidth limit for this class + + <number> + Rate in kbit (kilobit per second) + + + <number>%% + Percentage of overall rate + + + <number>bit + bit(1), kbit(10^3), mbit(10^6), gbit, tbit + + + <number>ibit + kibit(1024), mibit(1024^2), gibit(1024^3), tbit(1024^4) + + + <number>ibps + kibps(1024*8), mibps(1024^2*8), gibps, tibps - Byte/sec + + + <number>bps + bps(8),kbps(8*10^3),mbps(8*10^6), gbps, tbps - Byte/sec + + + + #include + #include + #include + #include + #include + + + Priority for usage of excess bandwidth + + u32:0-7 + Priority order for bandwidth pool + + + + + Priority must be between 0 and 7 + + 20 + + #include + #include + #include + #include + + + #include + + + Default policy + + + #include + #include + + + Bandwidth limit for this class + + <number> + Rate in kbit (kilobit per second) + + + <number>%% + Percentage of overall rate + + + <number>bit + bit(1), kbit(10^3), mbit(10^6), gbit, tbit + + + <number>ibit + kibit(1024), mibit(1024^2), gibit(1024^3), tbit(1024^4) + + + <number>ibps + kibps(1024*8), mibps(1024^2*8), gibps, tibps - Byte/sec + + + <number>bps + bps(8),kbps(8*10^3),mbps(8*10^6), gbps, tbps - Byte/sec + + + + #include + #include + #include + #include + + + Priority for usage of excess bandwidth + + u32:0-7 + Priority order for bandwidth pool + + + + + Priority must be between 0 and 7 + + 20 + + #include + #include + #include + #include + + + + + + + diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index fab88bc72..7f1258575 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -191,6 +191,19 @@ def verify_mirror(config): raise ConfigError(f'Can not mirror "{direction}" traffic back ' \ 'the originating interface!') +def verify_redirect(config): + """ + Common helper function used by interface implementations to perform + recurring validation of the redirect interface configuration. + + It makes no sense to mirror and redirect traffic at the same time! + """ + if {'mirror', 'redirect'} <= set(config): + raise ConfigError('Can not do both redirect and mirror') + + if dict_search('traffic_policy.in', config) != None: + raise ConfigError('Can not use ingress policy and redirect') + def verify_authentication(config): """ Common helper function used by interface implementations to perform @@ -315,6 +328,7 @@ def verify_vlan_config(config): verify_dhcpv6(vlan) verify_address(vlan) verify_vrf(vlan) + verify_redirect(vlan) verify_mtu_parent(vlan, config) # 802.1ad (Q-in-Q) VLANs @@ -323,6 +337,7 @@ def verify_vlan_config(config): verify_dhcpv6(s_vlan) verify_address(s_vlan) verify_vrf(s_vlan) + verify_redirect(s_vlan) verify_mtu_parent(s_vlan, config) for c_vlan in s_vlan.get('vif_c', {}): @@ -330,6 +345,7 @@ def verify_vlan_config(config): verify_dhcpv6(c_vlan) verify_address(c_vlan) verify_vrf(c_vlan) + verify_redirect(c_vlan) verify_mtu_parent(c_vlan, config) verify_mtu_parent(c_vlan, s_vlan) diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index bb53cd6c2..661dc2298 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -27,9 +27,10 @@ from vyos.configdict import is_source_interface from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_dhcpv6 -from vyos.configverify import verify_source_interface from vyos.configverify import verify_mirror from vyos.configverify import verify_mtu_ipv6 +from vyos.configverify import verify_redirect +from vyos.configverify import verify_source_interface from vyos.configverify import verify_vlan_config from vyos.configverify import verify_vrf from vyos.ifconfig import BondIf @@ -151,6 +152,7 @@ def verify(bond): verify_dhcpv6(bond) verify_vrf(bond) verify_mirror(bond) + verify_redirect(bond) # use common function to verify VLAN configuration verify_vlan_config(bond) diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py index 9f840cb58..e16c0e9f4 100755 --- a/src/conf_mode/interfaces-bridge.py +++ b/src/conf_mode/interfaces-bridge.py @@ -28,6 +28,7 @@ from vyos.configdict import has_vlan_subinterface_configured from vyos.configdict import dict_merge from vyos.configverify import verify_dhcpv6 from vyos.configverify import verify_mirror +from vyos.configverify import verify_redirect from vyos.configverify import verify_vrf from vyos.ifconfig import BridgeIf from vyos.validate import has_address_configured @@ -107,6 +108,7 @@ def verify(bridge): verify_dhcpv6(bridge) verify_vrf(bridge) verify_mirror(bridge) + verify_redirect(bridge) ifname = bridge['ifname'] diff --git a/src/conf_mode/interfaces-dummy.py b/src/conf_mode/interfaces-dummy.py index 55c783f38..4072c4452 100755 --- a/src/conf_mode/interfaces-dummy.py +++ b/src/conf_mode/interfaces-dummy.py @@ -21,6 +21,7 @@ from vyos.configdict import get_interface_dict from vyos.configverify import verify_vrf from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_redirect from vyos.ifconfig import DummyIf from vyos import ConfigError from vyos import airbag @@ -46,6 +47,7 @@ def verify(dummy): verify_vrf(dummy) verify_address(dummy) + verify_redirect(dummy) return None diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 2a8a126f2..3eeddf190 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -28,6 +28,7 @@ from vyos.configverify import verify_interface_exists from vyos.configverify import verify_mirror from vyos.configverify import verify_mtu from vyos.configverify import verify_mtu_ipv6 +from vyos.configverify import verify_redirect from vyos.configverify import verify_vlan_config from vyos.configverify import verify_vrf from vyos.ethtool import Ethtool @@ -84,6 +85,7 @@ def verify(ethernet): verify_vrf(ethernet) verify_eapol(ethernet) verify_mirror(ethernet) + verify_redirect(ethernet) ethtool = Ethtool(ifname) # No need to check speed and duplex keys as both have default values. diff --git a/src/conf_mode/interfaces-geneve.py b/src/conf_mode/interfaces-geneve.py index 2a63b60aa..a94b5e1f7 100755 --- a/src/conf_mode/interfaces-geneve.py +++ b/src/conf_mode/interfaces-geneve.py @@ -24,6 +24,7 @@ from vyos.configdict import get_interface_dict from vyos.configverify import verify_address from vyos.configverify import verify_mtu_ipv6 from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_redirect from vyos.ifconfig import GeneveIf from vyos import ConfigError @@ -50,6 +51,7 @@ def verify(geneve): verify_mtu_ipv6(geneve) verify_address(geneve) + verify_redirect(geneve) if 'remote' not in geneve: raise ConfigError('Remote side must be configured') diff --git a/src/conf_mode/interfaces-l2tpv3.py b/src/conf_mode/interfaces-l2tpv3.py index 9b6ddd5aa..5ea7159dc 100755 --- a/src/conf_mode/interfaces-l2tpv3.py +++ b/src/conf_mode/interfaces-l2tpv3.py @@ -25,6 +25,7 @@ from vyos.configdict import leaf_node_changed from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_mtu_ipv6 +from vyos.configverify import verify_redirect from vyos.ifconfig import L2TPv3If from vyos.util import check_kmod from vyos.validate import is_addr_assigned @@ -76,6 +77,7 @@ def verify(l2tpv3): verify_mtu_ipv6(l2tpv3) verify_address(l2tpv3) + verify_redirect(l2tpv3) return None def generate(l2tpv3): diff --git a/src/conf_mode/interfaces-loopback.py b/src/conf_mode/interfaces-loopback.py index 193334443..e6a851113 100755 --- a/src/conf_mode/interfaces-loopback.py +++ b/src/conf_mode/interfaces-loopback.py @@ -20,6 +20,7 @@ from sys import exit from vyos.config import Config from vyos.configdict import get_interface_dict +from vyos.configverify import verify_redirect from vyos.ifconfig import LoopbackIf from vyos import ConfigError from vyos import airbag @@ -39,6 +40,7 @@ def get_config(config=None): return loopback def verify(loopback): + verify_redirect(loopback) return None def generate(loopback): diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py index eab69f36e..6a29fdb11 100755 --- a/src/conf_mode/interfaces-macsec.py +++ b/src/conf_mode/interfaces-macsec.py @@ -29,6 +29,7 @@ from vyos.configverify import verify_vrf from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_mtu_ipv6 +from vyos.configverify import verify_redirect from vyos.configverify import verify_source_interface from vyos import ConfigError from vyos import airbag @@ -66,6 +67,7 @@ def verify(macsec): verify_vrf(macsec) verify_mtu_ipv6(macsec) verify_address(macsec) + verify_redirect(macsec) if not (('security' in macsec) and ('cipher' in macsec['security'])): diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py index 584adc75e..9962e0a08 100755 --- a/src/conf_mode/interfaces-pppoe.py +++ b/src/conf_mode/interfaces-pppoe.py @@ -28,6 +28,7 @@ from vyos.configverify import verify_source_interface from vyos.configverify import verify_interface_exists from vyos.configverify import verify_vrf from vyos.configverify import verify_mtu_ipv6 +from vyos.configverify import verify_redirect from vyos.ifconfig import PPPoEIf from vyos.template import render from vyos.util import call @@ -85,6 +86,7 @@ def verify(pppoe): verify_authentication(pppoe) verify_vrf(pppoe) verify_mtu_ipv6(pppoe) + verify_redirect(pppoe) if {'connect_on_demand', 'vrf'} <= set(pppoe): raise ConfigError('On-demand dialing and VRF can not be used at the same time') diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py index 945a2ea9c..f57e41cc4 100755 --- a/src/conf_mode/interfaces-pseudo-ethernet.py +++ b/src/conf_mode/interfaces-pseudo-ethernet.py @@ -25,6 +25,7 @@ from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_source_interface from vyos.configverify import verify_vlan_config from vyos.configverify import verify_mtu_parent +from vyos.configverify import verify_redirect from vyos.ifconfig import MACVLANIf from vyos import ConfigError @@ -60,6 +61,7 @@ def verify(peth): verify_vrf(peth) verify_address(peth) verify_mtu_parent(peth, peth['parent']) + verify_redirect(peth) # use common function to verify VLAN configuration verify_vlan_config(peth) diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index 433764b8a..005fae5eb 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -26,6 +26,7 @@ from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_interface_exists from vyos.configverify import verify_mtu_ipv6 +from vyos.configverify import verify_redirect from vyos.configverify import verify_vrf from vyos.configverify import verify_tunnel from vyos.ifconfig import Interface @@ -157,6 +158,7 @@ def verify(tunnel): verify_mtu_ipv6(tunnel) verify_address(tunnel) verify_vrf(tunnel) + verify_redirect(tunnel) if 'source_interface' in tunnel: verify_interface_exists(tunnel['source_interface']) diff --git a/src/conf_mode/interfaces-vti.py b/src/conf_mode/interfaces-vti.py index 57950ffea..30e13536f 100755 --- a/src/conf_mode/interfaces-vti.py +++ b/src/conf_mode/interfaces-vti.py @@ -19,6 +19,7 @@ from sys import exit from vyos.config import Config from vyos.configdict import get_interface_dict +from vyos.configverify import verify_redirect from vyos.ifconfig import VTIIf from vyos.util import dict_search from vyos import ConfigError @@ -39,6 +40,7 @@ def get_config(config=None): return vti def verify(vti): + verify_redirect(vti) return None def generate(vti): diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index 29b16af89..a29836efd 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -25,6 +25,7 @@ from vyos.configdict import leaf_node_changed from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_mtu_ipv6 +from vyos.configverify import verify_redirect from vyos.configverify import verify_source_interface from vyos.ifconfig import Interface from vyos.ifconfig import VXLANIf @@ -140,6 +141,7 @@ def verify(vxlan): verify_mtu_ipv6(vxlan) verify_address(vxlan) + verify_redirect(vxlan) return None def generate(vxlan): diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py index da64dd076..dc0fe7b9c 100755 --- a/src/conf_mode/interfaces-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.py @@ -28,6 +28,7 @@ from vyos.configverify import verify_vrf from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_mtu_ipv6 +from vyos.configverify import verify_redirect from vyos.ifconfig import WireGuardIf from vyos.util import check_kmod from vyos.util import check_port_availability @@ -70,6 +71,7 @@ def verify(wireguard): verify_mtu_ipv6(wireguard) verify_address(wireguard) verify_vrf(wireguard) + verify_redirect(wireguard) if 'private_key' not in wireguard: raise ConfigError('Wireguard private-key not defined') diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py index af35b5f03..fdf9e3988 100755 --- a/src/conf_mode/interfaces-wireless.py +++ b/src/conf_mode/interfaces-wireless.py @@ -27,6 +27,7 @@ from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_dhcpv6 from vyos.configverify import verify_source_interface +from vyos.configverify import verify_redirect from vyos.configverify import verify_vlan_config from vyos.configverify import verify_vrf from vyos.ifconfig import WiFiIf @@ -189,6 +190,7 @@ def verify(wifi): verify_address(wifi) verify_vrf(wifi) + verify_redirect(wifi) # use common function to verify VLAN configuration verify_vlan_config(wifi) diff --git a/src/conf_mode/interfaces-wwan.py b/src/conf_mode/interfaces-wwan.py index a4b033374..367a50e82 100755 --- a/src/conf_mode/interfaces-wwan.py +++ b/src/conf_mode/interfaces-wwan.py @@ -23,6 +23,7 @@ from vyos.config import Config from vyos.configdict import get_interface_dict from vyos.configverify import verify_authentication from vyos.configverify import verify_interface_exists +from vyos.configverify import verify_redirect from vyos.configverify import verify_vrf from vyos.ifconfig import WWANIf from vyos.util import cmd @@ -77,6 +78,7 @@ def verify(wwan): verify_interface_exists(ifname) verify_authentication(wwan) verify_vrf(wwan) + verify_redirect(wwan) return None diff --git a/src/conf_mode/qos.py b/src/conf_mode/qos.py new file mode 100755 index 000000000..cf447d4b5 --- /dev/null +++ b/src/conf_mode/qos.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 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 . + +from sys import exit + +from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.xml import defaults +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['traffic-policy'] + if not conf.exists(base): + return None + + qos = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + + for traffic_policy in ['drop-tail', 'fair-queue', 'fq-codel', 'limiter', + 'network-emulator', 'priority-queue', 'random-detect', + 'rate-control', 'round-robin', 'shaper', 'shaper-hfsc']: + traffic_policy_us = traffic_policy.replace('-','_') + # Individual policy type not present on CLI - no need to blend in + # any default values + if traffic_policy_us not in qos: + continue + + default_values = defaults(base + [traffic_policy_us]) + + # class is another tag node which requires individual handling + class_default_values = defaults(base + [traffic_policy_us, 'class']) + if 'class' in default_values: + del default_values['class'] + + for policy, policy_config in qos[traffic_policy_us].items(): + qos[traffic_policy_us][policy] = dict_merge( + default_values, qos[traffic_policy_us][policy]) + + if 'class' in policy_config: + for policy_class in policy_config['class']: + qos[traffic_policy_us][policy]['class'][policy_class] = dict_merge( + class_default_values, qos[traffic_policy_us][policy]['class'][policy_class]) + + import pprint + pprint.pprint(qos) + return qos + +def verify(qos): + if not qos: + return None + + # network policy emulator + # reorder rerquires delay to be set + + raise ConfigError('123') + return None + +def generate(qos): + return None + +def apply(qos): + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) -- cgit v1.2.3 From dae259c5dbb6146f584251fe9a39a1a56cf926be Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 22 Mar 2022 07:11:00 +0100 Subject: qos: T4284: delete traffic-policy CLI path via Makefile Implementation is still work in progress, as such the CLI XML definitions are published but the Python code does not work. In this case we must ensure XML backed node.def files are deleted after generation so they do not conflict with the current vyatta-cfg-qos implementation. --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 431f3a8c2..54f3892ba 100644 --- a/Makefile +++ b/Makefile @@ -33,6 +33,7 @@ interface_definitions: $(config_xml_obj) # T4284 neq QoS implementation is not yet live find $(TMPL_DIR)/interfaces -name traffic-policy -type d -exec rm -rf {} \; find $(TMPL_DIR)/interfaces -name redirect -type d -exec rm -rf {} \; + rm -rf $(TMPL_DIR)/traffic-policy rm -rf $(TMPL_DIR)/interfaces/input # XXX: test if there are empty node.def files - this is not allowed as these -- cgit v1.2.3 From c37829f1e902b84a5bc3bc5618ee97ae1ba0dd86 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Tue, 22 Mar 2022 04:50:34 -0400 Subject: T4313: handle exceptions in the "generate public-key-command" script --- src/op_mode/generate_public_key_command.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/op_mode/generate_public_key_command.py b/src/op_mode/generate_public_key_command.py index 7a7b6c923..f071ae350 100755 --- a/src/op_mode/generate_public_key_command.py +++ b/src/op_mode/generate_public_key_command.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2022 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 @@ -29,8 +29,12 @@ def get_key(path): key_string = vyos.remote.get_remote_config(path) return key_string.split() -username = sys.argv[1] -algorithm, key, identifier = get_key(sys.argv[2]) +try: + username = sys.argv[1] + algorithm, key, identifier = get_key(sys.argv[2]) +except Exception as e: + print("Failed to retrieve the public key: {}".format(e)) + sys.exit(1) print('# To add this key as an embedded key, run the following commands:') print('configure') @@ -39,3 +43,4 @@ print(f'set system login user {username} authentication public-keys {identifier} print('commit') print('save') print('exit') + -- cgit v1.2.3 From 34bb64b361b7887def8f507eab1c99fff44165cf Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 23 Mar 2022 09:04:36 +0100 Subject: l2tpv3: T1923: remove duplicate mtu include --- interface-definitions/interfaces-l2tpv3.xml.in | 1 - 1 file changed, 1 deletion(-) diff --git a/interface-definitions/interfaces-l2tpv3.xml.in b/interface-definitions/interfaces-l2tpv3.xml.in index 124863653..680170b0f 100644 --- a/interface-definitions/interfaces-l2tpv3.xml.in +++ b/interface-definitions/interfaces-l2tpv3.xml.in @@ -86,7 +86,6 @@ - #include #include -- cgit v1.2.3 From d193e5cb9040bfca5011400acef601e8c7111346 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 23 Mar 2022 08:42:40 -0500 Subject: bgp: T4314: add missing check to migration script --- src/migration-scripts/bgp/0-to-1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/migration-scripts/bgp/0-to-1 b/src/migration-scripts/bgp/0-to-1 index b1d5a6514..5e9dffe1f 100755 --- a/src/migration-scripts/bgp/0-to-1 +++ b/src/migration-scripts/bgp/0-to-1 @@ -33,7 +33,7 @@ with open(file_name, 'r') as f: base = ['protocols', 'bgp'] config = ConfigTree(config_file) -if not config.exists(base): +if not config.exists(base) or not config.is_tag(base): # Nothing to do exit(0) -- cgit v1.2.3 From 78a4676f787e5e37f67afd5c2453ce06e3f0f9e9 Mon Sep 17 00:00:00 2001 From: srividya0208 Date: Fri, 18 Mar 2022 08:39:14 -0400 Subject: ike-group: T4288 : close-action is missing in swanctl.conf close-action parameter is missing in the swanctl.conf file --- data/templates/ipsec/swanctl/peer.tmpl | 6 +++-- interface-definitions/vpn_ipsec.xml.in | 8 ++---- src/migration-scripts/ipsec/8-to-9 | 49 ++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 8 deletions(-) create mode 100755 src/migration-scripts/ipsec/8-to-9 diff --git a/data/templates/ipsec/swanctl/peer.tmpl b/data/templates/ipsec/swanctl/peer.tmpl index 562e8fdd5..a622cbf74 100644 --- a/data/templates/ipsec/swanctl/peer.tmpl +++ b/data/templates/ipsec/swanctl/peer.tmpl @@ -87,9 +87,10 @@ start_action = none {% endif %} {% if ike.dead_peer_detection is defined %} -{% set dpd_translate = {'clear': 'clear', 'hold': 'trap', 'restart': 'start'} %} +{% set dpd_translate = {'clear': 'clear', 'hold': 'trap', 'restart': 'restart'} %} dpd_action = {{ dpd_translate[ike.dead_peer_detection.action] }} {% endif %} + close_action = {{ {'none': 'none', 'hold': 'trap', 'restart': 'start'}[ike.close_action] }} } {% elif peer_conf.tunnel is defined %} {% for tunnel_id, tunnel_conf in peer_conf.tunnel.items() if tunnel_conf.disable is not defined %} @@ -137,9 +138,10 @@ start_action = none {% endif %} {% if ike.dead_peer_detection is defined %} -{% set dpd_translate = {'clear': 'clear', 'hold': 'trap', 'restart': 'start'} %} +{% set dpd_translate = {'clear': 'clear', 'hold': 'trap', 'restart': 'restart'} %} dpd_action = {{ dpd_translate[ike.dead_peer_detection.action] }} {% endif %} + close_action = {{ {'none': 'none', 'hold': 'trap', 'restart': 'start'}[ike.close_action] }} {% if peer_conf.vti is defined and peer_conf.vti.bind is defined %} updown = "/etc/ipsec.d/vti-up-down {{ peer_conf.vti.bind }}" {# The key defaults to 0 and will match any policies which similarly do not have a lookup key configuration. #} diff --git a/interface-definitions/vpn_ipsec.xml.in b/interface-definitions/vpn_ipsec.xml.in index d8c06a310..a86951ce8 100644 --- a/interface-definitions/vpn_ipsec.xml.in +++ b/interface-definitions/vpn_ipsec.xml.in @@ -231,7 +231,7 @@ Action to take if a child SA is unexpectedly closed - none hold clear restart + none hold restart none @@ -241,16 +241,12 @@ hold Attempt to re-negotiate when matching traffic is seen - - clear - Remove the connection immediately - restart Attempt to re-negotiate the connection immediately - ^(none|hold|clear|restart)$ + ^(none|hold|restart)$ diff --git a/src/migration-scripts/ipsec/8-to-9 b/src/migration-scripts/ipsec/8-to-9 new file mode 100755 index 000000000..209cd8ac9 --- /dev/null +++ b/src/migration-scripts/ipsec/8-to-9 @@ -0,0 +1,49 @@ + +#!/usr/bin/env python3 +# +# Copyright (C) 2021 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 . + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree + +if (len(argv) < 1): + print("Must specify file name!") + exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +base = ['vpn', 'ipsec', 'ike-group'] +config = ConfigTree(config_file) + +if not config.exists(base): + # Nothing to do + exit(0) +else: + for ike_group in config.list_nodes(base): + base_closeaction = base + [ike_group, 'close-action'] + if config.exists(base_closeaction) and config.return_value(base_closeaction) == 'clear': + config.set(base_closeaction, 'none', replace=True) + +try: + with open(file_name, 'w') as f: + f.write(config.to_string()) +except OSError as e: + print(f'Failed to save the modified config: {e}') + exit(1) -- cgit v1.2.3 From 999b1e50dfdea8694174e82d22b2438cb1bf5e28 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 24 Mar 2022 17:42:59 +0100 Subject: openvpn: T4294: force service restart on openvpn-option node change --- src/conf_mode/interfaces-openvpn.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 29a25eedc..c30c0bdd0 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -32,6 +32,7 @@ from shutil import rmtree from vyos.config import Config from vyos.configdict import get_interface_dict +from vyos.configdict import leaf_node_changed from vyos.configverify import verify_vrf from vyos.configverify import verify_bridge_delete from vyos.ifconfig import VTunIf @@ -88,6 +89,9 @@ def get_config(config=None): if 'deleted' not in openvpn: openvpn['pki'] = tmp_pki + tmp = leaf_node_changed(conf, ['openvpn-option']) + if tmp: openvpn['restart_required'] = '' + # We have to get the dict using 'get_config_dict' instead of 'get_interface_dict' # as 'get_interface_dict' merges the defaults in, so we can not check for defaults in there. tmp = conf.get_config_dict(base + [openvpn['ifname']], get_first_key=True) @@ -651,7 +655,10 @@ def apply(openvpn): # No matching OpenVPN process running - maybe it got killed or none # existed - nevertheless, spawn new OpenVPN process - call(f'systemctl reload-or-restart openvpn@{interface}.service') + action = 'reload-or-restart' + if 'restart_required' in openvpn: + action = 'restart' + call(f'systemctl {action} openvpn@{interface}.service') o = VTunIf(**openvpn) o.update(openvpn) -- cgit v1.2.3 From cb499ee9d467d31f9a75a76f668c9ca0d8a3484f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 24 Mar 2022 18:28:49 +0100 Subject: ipsec: T4288: drop leading empty line to detect runtime environment --- src/migration-scripts/ipsec/8-to-9 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/migration-scripts/ipsec/8-to-9 b/src/migration-scripts/ipsec/8-to-9 index 209cd8ac9..eb44b6216 100755 --- a/src/migration-scripts/ipsec/8-to-9 +++ b/src/migration-scripts/ipsec/8-to-9 @@ -1,7 +1,6 @@ - #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2022 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 -- cgit v1.2.3 From e700bd3e22e080525e70ce560c0e48d41a80a9d2 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 24 Mar 2022 18:42:40 +0100 Subject: ipsec: T4288: bump config version 8 -> 9 --- interface-definitions/include/version/ipsec-version.xml.i | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface-definitions/include/version/ipsec-version.xml.i b/interface-definitions/include/version/ipsec-version.xml.i index fcdd6c702..59295cc91 100644 --- a/interface-definitions/include/version/ipsec-version.xml.i +++ b/interface-definitions/include/version/ipsec-version.xml.i @@ -1,3 +1,3 @@ - + -- cgit v1.2.3 From 87ccafd06b897b63f847e6b47cf72b951b0ed223 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 25 Mar 2022 18:53:12 +0100 Subject: T4319: "system ip(v6)" must run before any interface operation --- interface-definitions/system-ip.xml.in | 3 ++- interface-definitions/system-ipv6.xml.in | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/interface-definitions/system-ip.xml.in b/interface-definitions/system-ip.xml.in index 1fa63d517..b43100418 100644 --- a/interface-definitions/system-ip.xml.in +++ b/interface-definitions/system-ip.xml.in @@ -5,7 +5,8 @@ IPv4 Settings - 400 + + 290 diff --git a/interface-definitions/system-ipv6.xml.in b/interface-definitions/system-ipv6.xml.in index 5ee7adf54..ff1080544 100644 --- a/interface-definitions/system-ipv6.xml.in +++ b/interface-definitions/system-ipv6.xml.in @@ -5,6 +5,7 @@ IPv6 Settings + 290 -- cgit v1.2.3 From eaf4b60c9e7fa094d17b87b29bebaf81182ee7a1 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 25 Mar 2022 18:53:50 +0100 Subject: xml: T4319: use common building block for table-size CLI option --- interface-definitions/include/arp-ndp-table-size.xml.i | 14 ++++++++++++++ interface-definitions/system-ip.xml.in | 13 +------------ interface-definitions/system-ipv6.xml.in | 14 ++------------ 3 files changed, 17 insertions(+), 24 deletions(-) create mode 100644 interface-definitions/include/arp-ndp-table-size.xml.i diff --git a/interface-definitions/include/arp-ndp-table-size.xml.i b/interface-definitions/include/arp-ndp-table-size.xml.i new file mode 100644 index 000000000..dec86e91a --- /dev/null +++ b/interface-definitions/include/arp-ndp-table-size.xml.i @@ -0,0 +1,14 @@ + + + + Maximum number of entries to keep in the cache + + 1024 2048 4096 8192 16384 32768 + + + (1024|2048|4096|8192|16384|32768) + + + 8192 + + diff --git a/interface-definitions/system-ip.xml.in b/interface-definitions/system-ip.xml.in index b43100418..21d70694b 100644 --- a/interface-definitions/system-ip.xml.in +++ b/interface-definitions/system-ip.xml.in @@ -14,18 +14,7 @@ Parameters for ARP cache - - - Maximum number of entries to keep in the ARP cache - - 1024 2048 4096 8192 16384 32768 - - - ^(1024|2048|4096|8192|16384|32768)$ - - - 8192 - + #include diff --git a/interface-definitions/system-ipv6.xml.in b/interface-definitions/system-ipv6.xml.in index ff1080544..af4dcdb0f 100644 --- a/interface-definitions/system-ipv6.xml.in +++ b/interface-definitions/system-ipv6.xml.in @@ -36,20 +36,10 @@ - Parameters for Neighbor cache + Parameters for neighbor discovery cache - - - Maximum number of entries to keep in the Neighbor cache - - 1024 2048 4096 8192 16384 32768 - - - ^(1024|2048|4096|8192|16384|32768)$ - - - + #include -- cgit v1.2.3 From 52cb6185a4a51ffa92f10e0ded55a943bc21bc60 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 25 Mar 2022 18:54:44 +0100 Subject: vyos.util: T4319: provide generic sysctl_read() helper --- python/vyos/util.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/python/vyos/util.py b/python/vyos/util.py index 4526375df..da39ee8d1 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -1006,11 +1006,16 @@ def boot_configuration_complete() -> bool: return True return False +def sysctl_read(name): + """ Read and return current value of sysctl() option """ + tmp = cmd(f'sysctl {name}') + return tmp.split()[-1] + def sysctl(name, value): """ Change value via sysctl() - return True if changed, False otherwise """ tmp = cmd(f'sysctl {name}') # last list index contains the actual value - only write if value differs - if tmp.split()[-1] != str(value): + if sysctl_read(name) != str(value): call(f'sysctl -wq {name}={value}') return True return False -- cgit v1.2.3 From 1b16a4eab926462c0d2752d698bedf28c995058d Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 25 Mar 2022 18:55:32 +0100 Subject: system: T4319: align ipv6 settings with ipv4 by using get_config_dict() --- src/conf_mode/system-ip.py | 43 +++++++++--------- src/conf_mode/system-ipv6.py | 103 +++++++++++++++++++------------------------ 2 files changed, 66 insertions(+), 80 deletions(-) diff --git a/src/conf_mode/system-ip.py b/src/conf_mode/system-ip.py index 32cb2f036..8b97725ac 100755 --- a/src/conf_mode/system-ip.py +++ b/src/conf_mode/system-ip.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2020 VyOS maintainers and contributors +# Copyright (C) 2019-2022 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 @@ -20,14 +20,13 @@ from vyos.config import Config from vyos.configdict import dict_merge from vyos.util import call from vyos.util import dict_search +from vyos.util import sysctl +from vyos.util import write_file from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() -def sysctl(name, value): - call(f'sysctl -wq {name}={value}') - def get_config(config=None): if config: conf = config @@ -50,29 +49,29 @@ def generate(opt): pass def apply(opt): + # Apply ARP threshold values + # table_size has a default value - thus the key always exists size = int(dict_search('arp.table_size', opt)) - if size: - # apply ARP threshold values - sysctl('net.ipv4.neigh.default.gc_thresh3', str(size)) - sysctl('net.ipv4.neigh.default.gc_thresh2', str(size // 2)) - sysctl('net.ipv4.neigh.default.gc_thresh1', str(size // 8)) + # Amount upon reaching which the records begin to be cleared immediately + sysctl('net.ipv4.neigh.default.gc_thresh3', size) + # Amount after which the records begin to be cleaned after 5 seconds + sysctl('net.ipv4.neigh.default.gc_thresh2', size // 2) + # Minimum number of stored records is indicated which is not cleared + sysctl('net.ipv4.neigh.default.gc_thresh1', size // 8) # enable/disable IPv4 forwarding - tmp = '1' - if 'disable_forwarding' in opt: - tmp = '0' - sysctl('net.ipv4.conf.all.forwarding', tmp) + tmp = dict_search('disable_forwarding', opt) + value = '0' if (tmp != None) else '1' + write_file('/proc/sys/net/ipv4/conf/all/forwarding', value) - tmp = '0' - # configure multipath - dict_search() returns an empty dict if key was found - if isinstance(dict_search('multipath.ignore_unreachable_nexthops', opt), dict): - tmp = '1' - sysctl('net.ipv4.fib_multipath_use_neigh', tmp) + # configure multipath + tmp = dict_search('multipath.ignore_unreachable_nexthops', opt) + value = '1' if (tmp != None) else '0' + sysctl('net.ipv4.fib_multipath_use_neigh', value) - tmp = '0' - if isinstance(dict_search('multipath.layer4_hashing', opt), dict): - tmp = '1' - sysctl('net.ipv4.fib_multipath_hash_policy', tmp) + tmp = dict_search('multipath.layer4_hashing', opt) + value = '1' if (tmp != None) else '0' + sysctl('net.ipv4.fib_multipath_hash_policy', value) if __name__ == '__main__': try: diff --git a/src/conf_mode/system-ipv6.py b/src/conf_mode/system-ipv6.py index f70ec2631..8195beaa6 100755 --- a/src/conf_mode/system-ipv6.py +++ b/src/conf_mode/system-ipv6.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019 VyOS maintainers and contributors +# Copyright (C) 2019-2022 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -15,95 +15,82 @@ # along with this program. If not, see . import os -import sys from sys import exit -from copy import deepcopy from vyos.config import Config -from vyos import ConfigError +from vyos.configdict import dict_merge +from vyos.configdict import leaf_node_changed from vyos.util import call - +from vyos.util import dict_search +from vyos.util import sysctl +from vyos.util import write_file +from vyos.xml import defaults +from vyos import ConfigError from vyos import airbag airbag.enable() -ipv6_disable_file = '/etc/modprobe.d/vyos_disable_ipv6.conf' - -default_config_data = { - 'reboot_message': False, - 'ipv6_forward': '1', - 'disable_addr_assignment': False, - 'mp_layer4_hashing': '0', - 'neighbor_cache': 8192, - 'strict_dad': '1' - -} - -def sysctl(name, value): - call('sysctl -wq {}={}'.format(name, value)) - def get_config(config=None): - ip_opt = deepcopy(default_config_data) if config: conf = config else: conf = Config() - conf.set_level('system ipv6') - if conf.exists(''): - ip_opt['disable_addr_assignment'] = conf.exists('disable') - if conf.exists_effective('disable') != conf.exists('disable'): - ip_opt['reboot_message'] = True - - if conf.exists('disable-forwarding'): - ip_opt['ipv6_forward'] = '0' + base = ['system', 'ipv6'] - if conf.exists('multipath layer4-hashing'): - ip_opt['mp_layer4_hashing'] = '1' + opt = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - if conf.exists('neighbor table-size'): - ip_opt['neighbor_cache'] = int(conf.return_value('neighbor table-size')) + tmp = leaf_node_changed(conf, base + ['disable']) + if tmp: opt['reboot_required'] = {} - if conf.exists('strict-dad'): - ip_opt['strict_dad'] = 2 + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + default_values = defaults(base) + opt = dict_merge(default_values, opt) - return ip_opt + return opt -def verify(ip_opt): +def verify(opt): pass -def generate(ip_opt): +def generate(opt): pass -def apply(ip_opt): - # disable IPv6 address assignment - if ip_opt['disable_addr_assignment']: - with open(ipv6_disable_file, 'w') as f: - f.write('options ipv6 disable_ipv6=1') - else: - if os.path.exists(ipv6_disable_file): - os.unlink(ipv6_disable_file) +def apply(opt): + # disable IPv6 globally + tmp = dict_search('disable', opt) + value = '1' if (tmp != None) else '0' + sysctl('net.ipv6.conf.all.disable_ipv6', value) - if ip_opt['reboot_message']: + if 'reboot_required' in opt: print('Changing IPv6 disable parameter will only take affect\n' \ 'when the system is rebooted.') # configure multipath - sysctl('net.ipv6.fib_multipath_hash_policy', ip_opt['mp_layer4_hashing']) - - # apply neighbor table threshold values - sysctl('net.ipv6.neigh.default.gc_thresh3', ip_opt['neighbor_cache']) - sysctl('net.ipv6.neigh.default.gc_thresh2', ip_opt['neighbor_cache'] // 2) - sysctl('net.ipv6.neigh.default.gc_thresh1', ip_opt['neighbor_cache'] // 8) + tmp = dict_search('multipath.layer4_hashing', opt) + value = '1' if (tmp != None) else '0' + sysctl('net.ipv6.fib_multipath_hash_policy', value) + + # Apply ND threshold values + # table_size has a default value - thus the key always exists + size = int(dict_search('neighbor.table_size', opt)) + # Amount upon reaching which the records begin to be cleared immediately + sysctl('net.ipv6.neigh.default.gc_thresh3', size) + # Amount after which the records begin to be cleaned after 5 seconds + sysctl('net.ipv6.neigh.default.gc_thresh2', size // 2) + # Minimum number of stored records is indicated which is not cleared + sysctl('net.ipv6.neigh.default.gc_thresh1', size // 8) # enable/disable IPv6 forwarding - with open('/proc/sys/net/ipv6/conf/all/forwarding', 'w') as f: - f.write(ip_opt['ipv6_forward']) + tmp = dict_search('disable_forwarding', opt) + value = '0' if (tmp != None) else '1' + write_file('/proc/sys/net/ipv6/conf/all/forwarding', value) # configure IPv6 strict-dad + tmp = dict_search('strict_dad', opt) + value = '2' if (tmp != None) else '1' for root, dirs, files in os.walk('/proc/sys/net/ipv6/conf'): for name in files: - if name == "accept_dad": - with open(os.path.join(root, name), 'w') as f: - f.write(str(ip_opt['strict_dad'])) + if name == 'accept_dad': + write_file(os.path.join(root, name), value) if __name__ == '__main__': try: -- cgit v1.2.3 From f8b3d8999cbea988ce8e7d303957558308ddc1bc Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 25 Mar 2022 18:56:06 +0100 Subject: ipv6: T4319: do not configure IPv6 related settings if it's disabled --- python/vyos/ifconfig/interface.py | 93 ++++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 45 deletions(-) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 8c5ff576e..8ce851cdb 100755 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -39,6 +39,7 @@ from vyos.util import read_file from vyos.util import get_interface_config from vyos.util import get_interface_namespace from vyos.util import is_systemd_service_active +from vyos.util import sysctl_read from vyos.template import is_ipv4 from vyos.template import is_ipv6 from vyos.validate import is_intf_addr_assigned @@ -1448,11 +1449,6 @@ class Interface(Control): value = tmp if (tmp != None) else '0' self.set_tcp_ipv4_mss(value) - # Configure MSS value for IPv6 TCP connections - tmp = dict_search('ipv6.adjust_mss', config) - value = tmp if (tmp != None) else '0' - self.set_tcp_ipv6_mss(value) - # Configure ARP cache timeout in milliseconds - has default value tmp = dict_search('ip.arp_cache_timeout', config) value = tmp if (tmp != None) else '30' @@ -1498,47 +1494,54 @@ class Interface(Control): value = tmp if (tmp != None) else '0' self.set_ipv4_source_validation(value) - # IPv6 forwarding - tmp = dict_search('ipv6.disable_forwarding', config) - value = '0' if (tmp != None) else '1' - self.set_ipv6_forwarding(value) - - # IPv6 router advertisements - tmp = dict_search('ipv6.address.autoconf', config) - value = '2' if (tmp != None) else '1' - if 'dhcpv6' in new_addr: - value = '2' - self.set_ipv6_accept_ra(value) - - # IPv6 address autoconfiguration - tmp = dict_search('ipv6.address.autoconf', config) - value = '1' if (tmp != None) else '0' - self.set_ipv6_autoconf(value) - - # IPv6 Duplicate Address Detection (DAD) tries - tmp = dict_search('ipv6.dup_addr_detect_transmits', config) - value = tmp if (tmp != None) else '1' - self.set_ipv6_dad_messages(value) - - # MTU - Maximum Transfer Unit - if 'mtu' in config: - self.set_mtu(config.get('mtu')) - - # Delete old IPv6 EUI64 addresses before changing MAC - for addr in (dict_search('ipv6.address.eui64_old', config) or []): - self.del_ipv6_eui64_address(addr) - - # Manage IPv6 link-local addresses - if dict_search('ipv6.address.no_default_link_local', config) != None: - self.del_ipv6_eui64_address('fe80::/64') - else: - self.add_ipv6_eui64_address('fe80::/64') + # Only change IPv6 parameters if IPv6 was not explicitly disabled + if sysctl_read('net.ipv6.conf.all.disable_ipv6') == '0': + # Configure MSS value for IPv6 TCP connections + tmp = dict_search('ipv6.adjust_mss', config) + value = tmp if (tmp != None) else '0' + self.set_tcp_ipv6_mss(value) + + # IPv6 forwarding + tmp = dict_search('ipv6.disable_forwarding', config) + value = '0' if (tmp != None) else '1' + self.set_ipv6_forwarding(value) + + # IPv6 router advertisements + tmp = dict_search('ipv6.address.autoconf', config) + value = '2' if (tmp != None) else '1' + if 'dhcpv6' in new_addr: + value = '2' + self.set_ipv6_accept_ra(value) + + # IPv6 address autoconfiguration + tmp = dict_search('ipv6.address.autoconf', config) + value = '1' if (tmp != None) else '0' + self.set_ipv6_autoconf(value) + + # IPv6 Duplicate Address Detection (DAD) tries + tmp = dict_search('ipv6.dup_addr_detect_transmits', config) + value = tmp if (tmp != None) else '1' + self.set_ipv6_dad_messages(value) + + # MTU - Maximum Transfer Unit + if 'mtu' in config: + self.set_mtu(config.get('mtu')) + + # Delete old IPv6 EUI64 addresses before changing MAC + for addr in (dict_search('ipv6.address.eui64_old', config) or []): + self.del_ipv6_eui64_address(addr) + + # Manage IPv6 link-local addresses + if dict_search('ipv6.address.no_default_link_local', config) != None: + self.del_ipv6_eui64_address('fe80::/64') + else: + self.add_ipv6_eui64_address('fe80::/64') - # Add IPv6 EUI-based addresses - tmp = dict_search('ipv6.address.eui64', config) - if tmp: - for addr in tmp: - self.add_ipv6_eui64_address(addr) + # Add IPv6 EUI-based addresses + tmp = dict_search('ipv6.address.eui64', config) + if tmp: + for addr in tmp: + self.add_ipv6_eui64_address(addr) # re-add ourselves to any bridge we might have fallen out of if 'is_bridge_member' in config: -- cgit v1.2.3 From 364009e4317fb5c6732635726b511613aa2ed519 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 25 Mar 2022 19:00:36 +0100 Subject: vyos.util: T4319: rename sysctl() -> sysctl_write() --- python/vyos/util.py | 2 +- src/conf_mode/system-ip.py | 12 ++++++------ src/conf_mode/system-ipv6.py | 12 ++++++------ src/conf_mode/vrf.py | 6 +++--- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/python/vyos/util.py b/python/vyos/util.py index da39ee8d1..f46775490 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -1011,7 +1011,7 @@ def sysctl_read(name): tmp = cmd(f'sysctl {name}') return tmp.split()[-1] -def sysctl(name, value): +def sysctl_write(name, value): """ Change value via sysctl() - return True if changed, False otherwise """ tmp = cmd(f'sysctl {name}') # last list index contains the actual value - only write if value differs diff --git a/src/conf_mode/system-ip.py b/src/conf_mode/system-ip.py index 8b97725ac..05fc3a97a 100755 --- a/src/conf_mode/system-ip.py +++ b/src/conf_mode/system-ip.py @@ -20,7 +20,7 @@ from vyos.config import Config from vyos.configdict import dict_merge from vyos.util import call from vyos.util import dict_search -from vyos.util import sysctl +from vyos.util import sysctl_write from vyos.util import write_file from vyos.xml import defaults from vyos import ConfigError @@ -53,11 +53,11 @@ def apply(opt): # table_size has a default value - thus the key always exists size = int(dict_search('arp.table_size', opt)) # Amount upon reaching which the records begin to be cleared immediately - sysctl('net.ipv4.neigh.default.gc_thresh3', size) + sysctl_write('net.ipv4.neigh.default.gc_thresh3', size) # Amount after which the records begin to be cleaned after 5 seconds - sysctl('net.ipv4.neigh.default.gc_thresh2', size // 2) + sysctl_write('net.ipv4.neigh.default.gc_thresh2', size // 2) # Minimum number of stored records is indicated which is not cleared - sysctl('net.ipv4.neigh.default.gc_thresh1', size // 8) + sysctl_write('net.ipv4.neigh.default.gc_thresh1', size // 8) # enable/disable IPv4 forwarding tmp = dict_search('disable_forwarding', opt) @@ -67,11 +67,11 @@ def apply(opt): # configure multipath tmp = dict_search('multipath.ignore_unreachable_nexthops', opt) value = '1' if (tmp != None) else '0' - sysctl('net.ipv4.fib_multipath_use_neigh', value) + sysctl_write('net.ipv4.fib_multipath_use_neigh', value) tmp = dict_search('multipath.layer4_hashing', opt) value = '1' if (tmp != None) else '0' - sysctl('net.ipv4.fib_multipath_hash_policy', value) + sysctl_write('net.ipv4.fib_multipath_hash_policy', value) if __name__ == '__main__': try: diff --git a/src/conf_mode/system-ipv6.py b/src/conf_mode/system-ipv6.py index 8195beaa6..7fb2dd1cf 100755 --- a/src/conf_mode/system-ipv6.py +++ b/src/conf_mode/system-ipv6.py @@ -22,7 +22,7 @@ from vyos.configdict import dict_merge from vyos.configdict import leaf_node_changed from vyos.util import call from vyos.util import dict_search -from vyos.util import sysctl +from vyos.util import sysctl_write from vyos.util import write_file from vyos.xml import defaults from vyos import ConfigError @@ -58,7 +58,7 @@ def apply(opt): # disable IPv6 globally tmp = dict_search('disable', opt) value = '1' if (tmp != None) else '0' - sysctl('net.ipv6.conf.all.disable_ipv6', value) + sysctl_write('net.ipv6.conf.all.disable_ipv6', value) if 'reboot_required' in opt: print('Changing IPv6 disable parameter will only take affect\n' \ @@ -67,17 +67,17 @@ def apply(opt): # configure multipath tmp = dict_search('multipath.layer4_hashing', opt) value = '1' if (tmp != None) else '0' - sysctl('net.ipv6.fib_multipath_hash_policy', value) + sysctl_write('net.ipv6.fib_multipath_hash_policy', value) # Apply ND threshold values # table_size has a default value - thus the key always exists size = int(dict_search('neighbor.table_size', opt)) # Amount upon reaching which the records begin to be cleared immediately - sysctl('net.ipv6.neigh.default.gc_thresh3', size) + sysctl_write('net.ipv6.neigh.default.gc_thresh3', size) # Amount after which the records begin to be cleaned after 5 seconds - sysctl('net.ipv6.neigh.default.gc_thresh2', size // 2) + sysctl_write('net.ipv6.neigh.default.gc_thresh2', size // 2) # Minimum number of stored records is indicated which is not cleared - sysctl('net.ipv6.neigh.default.gc_thresh1', size // 8) + sysctl_write('net.ipv6.neigh.default.gc_thresh1', size // 8) # enable/disable IPv6 forwarding tmp = dict_search('disable_forwarding', opt) diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py index cfe0f4d8e..6a521a0dd 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -29,7 +29,7 @@ from vyos.util import dict_search from vyos.util import get_interface_config from vyos.util import popen from vyos.util import run -from vyos.util import sysctl +from vyos.util import sysctl_write from vyos import ConfigError from vyos import frr from vyos import airbag @@ -154,8 +154,8 @@ def apply(vrf): bind_all = '0' if 'bind-to-all' in vrf: bind_all = '1' - sysctl('net.ipv4.tcp_l3mdev_accept', bind_all) - sysctl('net.ipv4.udp_l3mdev_accept', bind_all) + sysctl_write('net.ipv4.tcp_l3mdev_accept', bind_all) + sysctl_write('net.ipv4.udp_l3mdev_accept', bind_all) for tmp in (dict_search('vrf_remove', vrf) or []): if os.path.isdir(f'/sys/class/net/{tmp}'): -- cgit v1.2.3 From cabe0c06e2312cc872d3e22d91611a3ccecefdb0 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 25 Mar 2022 19:08:56 +0100 Subject: mpls: T915: use vyos.util.sysctl_write() helper function --- src/conf_mode/protocols_mpls.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/conf_mode/protocols_mpls.py b/src/conf_mode/protocols_mpls.py index 0b0c7d07b..933e23065 100755 --- a/src/conf_mode/protocols_mpls.py +++ b/src/conf_mode/protocols_mpls.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-2022 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 @@ -20,11 +20,10 @@ from sys import exit from glob import glob from vyos.config import Config -from vyos.configdict import node_changed from vyos.template import render_to_string -from vyos.util import call from vyos.util import dict_search from vyos.util import read_file +from vyos.util import sysctl_write from vyos import ConfigError from vyos import frr from vyos import airbag @@ -89,21 +88,21 @@ def apply(mpls): labels = '0' if 'interface' in mpls: labels = '1048575' - call(f'sysctl -wq net.mpls.platform_labels={labels}') + sysctl_write('net.mpls.platform_labels', labels) # Check for changes in global MPLS options if 'parameters' in mpls: # Choose whether to copy IP TTL to MPLS header TTL if 'no_propagate_ttl' in mpls['parameters']: - call('sysctl -wq net.mpls.ip_ttl_propagate=0') + sysctl_write('net.mpls.ip_ttl_propagate', 0) # Choose whether to limit maximum MPLS header TTL if 'maximum_ttl' in mpls['parameters']: ttl = mpls['parameters']['maximum_ttl'] - call(f'sysctl -wq net.mpls.default_ttl={ttl}') + sysctl_write('net.mpls.default_ttl', ttl) else: # Set default global MPLS options if not defined. - call('sysctl -wq net.mpls.ip_ttl_propagate=1') - call('sysctl -wq net.mpls.default_ttl=255') + sysctl_write('net.mpls.ip_ttl_propagate', 1) + sysctl_write('net.mpls.default_ttl', 255) # Enable and disable MPLS processing on interfaces per configuration if 'interface' in mpls: @@ -117,11 +116,11 @@ def apply(mpls): if '1' in interface_state: if system_interface not in mpls['interface']: system_interface = system_interface.replace('.', '/') - call(f'sysctl -wq net.mpls.conf.{system_interface}.input=0') + sysctl_write(f'net.mpls.conf.{system_interface}.input', 0) elif '0' in interface_state: if system_interface in mpls['interface']: system_interface = system_interface.replace('.', '/') - call(f'sysctl -wq net.mpls.conf.{system_interface}.input=1') + sysctl_write(f'net.mpls.conf.{system_interface}.input', 1) else: system_interfaces = [] # If MPLS interfaces are not configured, set MPLS processing disabled @@ -129,7 +128,7 @@ def apply(mpls): system_interfaces.append(os.path.basename(interface)) for system_interface in system_interfaces: system_interface = system_interface.replace('.', '/') - call(f'sysctl -wq net.mpls.conf.{system_interface}.input=0') + sysctl_write(f'net.mpls.conf.{system_interface}.input', 0) return None -- cgit v1.2.3 From 625ea99e69a319c88fa67125438fdc457eaaaed3 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 25 Mar 2022 19:09:08 +0100 Subject: smoketest: mpls: disable debug output --- smoketest/scripts/cli/test_protocols_mpls.py | 1 - 1 file changed, 1 deletion(-) diff --git a/smoketest/scripts/cli/test_protocols_mpls.py b/smoketest/scripts/cli/test_protocols_mpls.py index b60f0725a..c6751cc42 100755 --- a/smoketest/scripts/cli/test_protocols_mpls.py +++ b/smoketest/scripts/cli/test_protocols_mpls.py @@ -81,7 +81,6 @@ class TestProtocolsMPLS(VyOSUnitTestSHIM.TestCase): self.assertTrue(process_named_running(PROCESS_NAME)) def test_mpls_basic(self): - self.debug = True router_id = '1.2.3.4' transport_ipv4_addr = '5.6.7.8' interfaces = Section.interfaces('ethernet') -- cgit v1.2.3 From 6249c2abe7e40a6671549d318ee9b0fbd622b991 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 25 Mar 2022 19:38:48 +0100 Subject: smoketest: ipv6: fix testcase after using new sysctl interface --- smoketest/scripts/cli/test_system_ipv6.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/smoketest/scripts/cli/test_system_ipv6.py b/smoketest/scripts/cli/test_system_ipv6.py index 1325d4b39..3112d2e46 100755 --- a/smoketest/scripts/cli/test_system_ipv6.py +++ b/smoketest/scripts/cli/test_system_ipv6.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2022 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 @@ -22,7 +22,7 @@ from vyos.util import read_file base_path = ['system', 'ipv6'] file_forwarding = '/proc/sys/net/ipv6/conf/all/forwarding' -file_disable = '/etc/modprobe.d/vyos_disable_ipv6.conf' +file_disable = '/proc/sys/net/ipv6/conf/all/disable_ipv6' file_dad = '/proc/sys/net/ipv6/conf/all/accept_dad' file_multipath = '/proc/sys/net/ipv6/fib_multipath_hash_policy' @@ -48,7 +48,7 @@ class TestSystemIPv6(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify configuration file - self.assertEqual(read_file(file_disable), 'options ipv6 disable_ipv6=1') + self.assertEqual(read_file(file_disable), '1') def test_system_ipv6_strict_dad(self): # This defaults to 1 -- cgit v1.2.3 From 1bfe09f90b9eca58f00cfae880e13e88c6dae894 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 26 Mar 2022 09:24:36 +0100 Subject: vyos.validate: T4321: make is_intf_addr_assigned() VRF aware --- python/vyos/validate.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/python/vyos/validate.py b/python/vyos/validate.py index 0dad2a6cb..6dfa1139f 100644 --- a/python/vyos/validate.py +++ b/python/vyos/validate.py @@ -43,19 +43,15 @@ def _are_same_ip(one, two): s_two = AF_INET if is_ipv4(two) else AF_INET6 return inet_pton(f_one, one) == inet_pton(f_one, two) -def is_intf_addr_assigned(intf, addr): - if '/' in addr: - ip,mask = addr.split('/') - return _is_intf_addr_assigned(intf, ip, mask) - return _is_intf_addr_assigned(intf, addr) - -def _is_intf_addr_assigned(intf, address, netmask=None): +def is_intf_addr_assigned(intf, address, vrf=None) -> bool: """ Verify if the given IPv4/IPv6 address is assigned to specific interface. It can check both a single IP address (e.g. 192.0.2.1 or a assigned CIDR address 192.0.2.1/24. """ from vyos.template import is_ipv4 + from vyos.util import get_interface_config + from netifaces import ifaddresses from netifaces import AF_INET from netifaces import AF_INET6 @@ -72,10 +68,19 @@ def _is_intf_addr_assigned(intf, address, netmask=None): print(e) return False + # Check if interface belongs to requested VRF. If interfaces does not + # belong to requested VRF - bail out early + tmp = get_interface_config(intf) + if 'master' in tmp and tmp['master'] != vrf: + return False + # determine IP version (AF_INET or AF_INET6) depending on passed address addr_type = AF_INET if is_ipv4(address) else AF_INET6 # Check every IP address on this interface for a match + netmask = None + if '/' in address: + address, netmask = address.split('/') for ip in ifaces.get(addr_type,[]): # ip can have the interface name in the 'addr' field, we need to remove it # {'addr': 'fe80::a00:27ff:fec5:f821%eth2', 'netmask': 'ffff:ffff:ffff:ffff::'} @@ -99,13 +104,13 @@ def _is_intf_addr_assigned(intf, address, netmask=None): return False -def is_addr_assigned(addr): +def is_addr_assigned(addr, vrf=None) -> bool: """ Verify if the given IPv4/IPv6 address is assigned to any interface """ from netifaces import interfaces for intf in interfaces(): - tmp = is_intf_addr_assigned(intf, addr) + tmp = is_intf_addr_assigned(intf, addr, vrf) if tmp == True: return True -- cgit v1.2.3 From 772d05156aaa75c904fe340cfce024da00f187f4 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 26 Mar 2022 09:25:02 +0100 Subject: bgp: T4321: check neighbor IP addresses against VRF context --- src/conf_mode/protocols_bgp.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index 9e59177a8..64b113873 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -159,8 +159,14 @@ def verify(bgp): # Only checks for ipv4 and ipv6 neighbors # Check if neighbor address is assigned as system interface address - if is_ip(peer) and is_addr_assigned(peer): - raise ConfigError(f'Can not configure a local address as neighbor "{peer}"') + vrf = None + vrf_error_msg = f' in default VRF!' + if 'vrf' in bgp: + vrf = bgp['vrf'] + vrf_error_msg = f' in VRF "{vrf}"!' + + if is_ip(peer) and is_addr_assigned(peer, vrf): + raise ConfigError(f'Can not configure local address as neighbor "{peer}"{vrf_error_msg}') elif is_interface(peer): if 'peer_group' in peer_config: raise ConfigError(f'peer-group must be set under the interface node of "{peer}"') -- cgit v1.2.3 From 2cfbb51731eb24c80bd4d697e370dde4972400a5 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 26 Mar 2022 09:40:44 +0100 Subject: vyos.validate: T4321: make is_addr_assigned() VRF aware Commit 1bfe09f9 ("vyos.validate: T4321: make is_intf_addr_assigned() VRF aware") added VRF support for an interface bound function. As an interface can only be bound to one VRF check makes less sense. This commit moves the VRF awareness from is_intf_addr_assigned() to is_addr_assigned() so we check the VRF assignment even prior of calling is_intf_addr_assigned() and fail fast. --- python/vyos/validate.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/python/vyos/validate.py b/python/vyos/validate.py index 6dfa1139f..e005da0e4 100644 --- a/python/vyos/validate.py +++ b/python/vyos/validate.py @@ -43,14 +43,13 @@ def _are_same_ip(one, two): s_two = AF_INET if is_ipv4(two) else AF_INET6 return inet_pton(f_one, one) == inet_pton(f_one, two) -def is_intf_addr_assigned(intf, address, vrf=None) -> bool: +def is_intf_addr_assigned(intf, address) -> bool: """ Verify if the given IPv4/IPv6 address is assigned to specific interface. It can check both a single IP address (e.g. 192.0.2.1 or a assigned CIDR address 192.0.2.1/24. """ from vyos.template import is_ipv4 - from vyos.util import get_interface_config from netifaces import ifaddresses from netifaces import AF_INET @@ -68,12 +67,6 @@ def is_intf_addr_assigned(intf, address, vrf=None) -> bool: print(e) return False - # Check if interface belongs to requested VRF. If interfaces does not - # belong to requested VRF - bail out early - tmp = get_interface_config(intf) - if 'master' in tmp and tmp['master'] != vrf: - return False - # determine IP version (AF_INET or AF_INET6) depending on passed address addr_type = AF_INET if is_ipv4(address) else AF_INET6 @@ -104,14 +97,20 @@ def is_intf_addr_assigned(intf, address, vrf=None) -> bool: return False -def is_addr_assigned(addr, vrf=None) -> bool: - """ - Verify if the given IPv4/IPv6 address is assigned to any interface - """ +def is_addr_assigned(ip_address, vrf=None) -> bool: + """ Verify if the given IPv4/IPv6 address is assigned to any interfac """ from netifaces import interfaces - for intf in interfaces(): - tmp = is_intf_addr_assigned(intf, addr, vrf) - if tmp == True: + from vyos.util import get_interface_config + from vyos.util import dict_search + for interface in interfaces(): + # Check if interface belongs to the requested VRF, if this is not the + # case there is no need to proceed with this data set - continue loop + # with next element + tmp = get_interface_config(interface) + if dict_search('master', tmp) != vrf: + continue + + if is_intf_addr_assigned(interface, ip_address): return True return False -- cgit v1.2.3 From 9d3acc2b55f2d1c563f1941e59c98c159211dc58 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Sun, 27 Mar 2022 01:30:21 -0500 Subject: graphql: T3993: add unsettable gql option; this is not exposed by CLI --- python/vyos/defaults.py | 1 + src/conf_mode/http-api.py | 9 +++++++++ src/services/vyos-http-api-server | 4 +++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py index c77b695bd..fcb6a7fbc 100644 --- a/python/vyos/defaults.py +++ b/python/vyos/defaults.py @@ -48,6 +48,7 @@ api_data = { 'port' : '8080', 'socket' : False, 'strict' : False, + 'gql' : False, 'debug' : False, 'api_keys' : [ {"id": "testapp", "key": "qwerty"} ] } diff --git a/src/conf_mode/http-api.py b/src/conf_mode/http-api.py index b5f5e919f..00f3d4f7f 100755 --- a/src/conf_mode/http-api.py +++ b/src/conf_mode/http-api.py @@ -66,6 +66,15 @@ def get_config(config=None): if conf.exists('debug'): http_api['debug'] = True + # this node is not available by CLI by default, and is reserved for + # the graphql tools. One can enable it for testing, with the warning + # that this will open an unauthenticated server. To do so + # mkdir /opt/vyatta/share/vyatta-cfg/templates/service/https/api/gql + # touch /opt/vyatta/share/vyatta-cfg/templates/service/https/api/gql/node.def + # and configure; editing the config alone is insufficient. + if conf.exists('gql'): + http_api['gql'] = True + if conf.exists('socket'): http_api['socket'] = True diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server index 06871f1d6..1000d8b72 100755 --- a/src/services/vyos-http-api-server +++ b/src/services/vyos-http-api-server @@ -648,10 +648,12 @@ if __name__ == '__main__': app.state.vyos_keys = server_config['api_keys'] app.state.vyos_debug = server_config['debug'] + app.state.vyos_gql = server_config['gql'] app.state.vyos_strict = server_config['strict'] app.state.vyos_origins = server_config.get('cors', {}).get('origins', []) - graphql_init(app) + if app.state.vyos_gql: + graphql_init(app) try: if not server_config['socket']: -- cgit v1.2.3 From 60f093464692f08c1c32c9e31513a6ae98636617 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Mon, 28 Mar 2022 10:46:30 +0300 Subject: Revert "openvpn: T4230: globally enable ip_nonlocal_bind" This reverts commit 1cbcbf40b7721849f9696c05fac65db010a66b7c. --- src/conf_mode/interfaces-openvpn.py | 7 +++++++ src/etc/sysctl.d/33-vyos-nonlocal-bind.conf | 8 -------- 2 files changed, 7 insertions(+), 8 deletions(-) delete mode 100644 src/etc/sysctl.d/33-vyos-nonlocal-bind.conf diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index c30c0bdd0..8f9c0b3f1 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -653,6 +653,13 @@ def apply(openvpn): return None + # verify specified IP address is present on any interface on this system + # Allow to bind service to nonlocal address, if it virtaual-vrrp address + # or if address will be assign later + if 'local_host' in openvpn: + if not is_addr_assigned(openvpn['local_host']): + cmd('sysctl -w net.ipv4.ip_nonlocal_bind=1') + # No matching OpenVPN process running - maybe it got killed or none # existed - nevertheless, spawn new OpenVPN process action = 'reload-or-restart' diff --git a/src/etc/sysctl.d/33-vyos-nonlocal-bind.conf b/src/etc/sysctl.d/33-vyos-nonlocal-bind.conf deleted file mode 100644 index aa81b5336..000000000 --- a/src/etc/sysctl.d/33-vyos-nonlocal-bind.conf +++ /dev/null @@ -1,8 +0,0 @@ -### Added by vyos-1x ### -# -# ip_nonlocal_bind - BOOLEAN -# If set, allows processes to bind() to non-local IP addresses, -# which can be quite useful - but may break some applications. -# Default: 0 -net.ipv4.ip_nonlocal_bind = 1 -net.ipv6.ip_nonlocal_bind = 1 -- cgit v1.2.3 From c82db71fa2ce0aa0b1768c51b78e790ee291836f Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Wed, 30 Mar 2022 09:16:20 -0400 Subject: T4319: do not try to add ::1/128 to lo if IPv6 is disabled --- python/vyos/ifconfig/loopback.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/python/vyos/ifconfig/loopback.py b/python/vyos/ifconfig/loopback.py index 192c12f5c..de554ef44 100644 --- a/python/vyos/ifconfig/loopback.py +++ b/python/vyos/ifconfig/loopback.py @@ -1,4 +1,4 @@ -# Copyright 2019-2021 VyOS maintainers and contributors +# Copyright 2019-2022 VyOS maintainers and contributors # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -13,6 +13,8 @@ # You should have received a copy of the GNU Lesser General Public # License along with this library. If not, see . +import vyos.util + from vyos.ifconfig.interface import Interface @Interface.register @@ -23,7 +25,6 @@ class LoopbackIf(Interface): """ _persistent_addresses = ['127.0.0.1/8', '::1/128'] iftype = 'loopback' - definition = { **Interface.definition, **{ @@ -32,6 +33,9 @@ class LoopbackIf(Interface): 'bridgeable': True, } } + + name = 'loopback' + def remove(self): """ Loopback interface can not be deleted from operating system. We can @@ -59,7 +63,10 @@ class LoopbackIf(Interface): addr = config.get('address', []) # We must ensure that the loopback addresses are never deleted from the system - addr += self._persistent_addresses + addr += ['127.0.0.1/8'] + + if (vyos.util.sysctl_read('net.ipv6.conf.all.disable_ipv6') == '0'): + addr += ['::1/128'] # Update IP address entry in our dictionary config.update({'address' : addr}) -- cgit v1.2.3 From bafb1973d906707cb571385e994a949d0d90b645 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 30 Mar 2022 19:16:52 +0200 Subject: vyos.ifconfig: make add_addr() method more reader firendly --- python/vyos/ifconfig/interface.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 8ce851cdb..4fda1c0a9 100755 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -1089,8 +1089,11 @@ class Interface(Control): elif addr == 'dhcpv6': self.set_dhcpv6(True) elif not is_intf_addr_assigned(self.ifname, addr): - self._cmd(f'ip addr add "{addr}" ' - f'{"brd + " if addr_is_v4 else ""}dev "{self.ifname}"') + tmp = f'ip addr add {addr} dev {self.ifname}' + # Add broadcast address for IPv4 + if is_ipv4(addr): tmp += ' brd +' + + self._cmd(tmp) else: return False -- cgit v1.2.3 From df0fbfeedce0f163e9d10be21d58ad4dc797a28a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 30 Mar 2022 19:18:09 +0200 Subject: vyos.util: T4319: add is_ipv6_enabled() helper function --- python/vyos/ifconfig/interface.py | 4 ++-- python/vyos/ifconfig/loopback.py | 13 +++++-------- python/vyos/util.py | 4 ++++ src/tests/test_util.py | 15 ++++++++++++++- 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 4fda1c0a9..22265cc3c 100755 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -39,7 +39,7 @@ from vyos.util import read_file from vyos.util import get_interface_config from vyos.util import get_interface_namespace from vyos.util import is_systemd_service_active -from vyos.util import sysctl_read +from vyos.util import is_ipv6_enabled from vyos.template import is_ipv4 from vyos.template import is_ipv6 from vyos.validate import is_intf_addr_assigned @@ -1498,7 +1498,7 @@ class Interface(Control): self.set_ipv4_source_validation(value) # Only change IPv6 parameters if IPv6 was not explicitly disabled - if sysctl_read('net.ipv6.conf.all.disable_ipv6') == '0': + if is_ipv6_enabled(): # Configure MSS value for IPv6 TCP connections tmp = dict_search('ipv6.adjust_mss', config) value = tmp if (tmp != None) else '0' diff --git a/python/vyos/ifconfig/loopback.py b/python/vyos/ifconfig/loopback.py index de554ef44..30c890fdf 100644 --- a/python/vyos/ifconfig/loopback.py +++ b/python/vyos/ifconfig/loopback.py @@ -13,9 +13,8 @@ # You should have received a copy of the GNU Lesser General Public # License along with this library. If not, see . -import vyos.util - from vyos.ifconfig.interface import Interface +from vyos.util import is_ipv6_enabled @Interface.register class LoopbackIf(Interface): @@ -34,8 +33,6 @@ class LoopbackIf(Interface): } } - name = 'loopback' - def remove(self): """ Loopback interface can not be deleted from operating system. We can @@ -62,11 +59,11 @@ class LoopbackIf(Interface): on any interface. """ addr = config.get('address', []) - # We must ensure that the loopback addresses are never deleted from the system - addr += ['127.0.0.1/8'] - if (vyos.util.sysctl_read('net.ipv6.conf.all.disable_ipv6') == '0'): - addr += ['::1/128'] + # We must ensure that the loopback addresses are never deleted from the system + addr.append('127.0.0.1/8') + if is_ipv6_enabled(): + addr.append('::1/128') # Update IP address entry in our dictionary config.update({'address' : addr}) diff --git a/python/vyos/util.py b/python/vyos/util.py index f46775490..f3f323c34 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -1019,3 +1019,7 @@ def sysctl_write(name, value): call(f'sysctl -wq {name}={value}') return True return False + +def is_ipv6_enabled() -> bool: + """ Check if IPv6 support on the system is enabled or not """ + return (sysctl_read('net.ipv6.conf.all.disable_ipv6') == '0') diff --git a/src/tests/test_util.py b/src/tests/test_util.py index 9bd27adc0..91890262c 100644 --- a/src/tests/test_util.py +++ b/src/tests/test_util.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2021 VyOS maintainers and contributors +# Copyright (C) 2020-2022 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 @@ -23,3 +23,16 @@ class TestVyOSUtil(TestCase): expected_data = {"foo_bar": {"baz_quux": None}} new_data = mangle_dict_keys(data, '-', '_') self.assertEqual(new_data, expected_data) + + def test_sysctl_read(self): + self.assertEqual(sysctl_read('net.ipv4.conf.lo.forwarding'), '1') + + def test_ipv6_enabled(self): + tmp = sysctl_read('net.ipv6.conf.all.disable_ipv6') + # We need to test for both variants as this depends on how the + # Docker container is started (with or without IPv6 support) - so we + # will simply check both cases to not make the users life miserable. + if tmp == '0': + self.assertTrue(is_ipv6_enabled()) + else: + self.assertFalse(is_ipv6_enabled()) -- cgit v1.2.3 From c33a96f6f0f0259808992b246b1a550fcf9a454a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 30 Mar 2022 19:19:24 +0200 Subject: vrf: T4319: do not add IPv6 localhost address if IPv6 is disabled --- src/conf_mode/vrf.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py index 6a521a0dd..c3e2d8efd 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -30,6 +30,7 @@ from vyos.util import get_interface_config from vyos.util import popen from vyos.util import run from vyos.util import sysctl_write +from vyos.util import is_ipv6_enabled from vyos import ConfigError from vyos import frr from vyos import airbag @@ -215,10 +216,11 @@ def apply(vrf): # set VRF description for e.g. SNMP monitoring vrf_if = Interface(name) - # We also should add proper loopback IP addresses to the newly - # created VRFs for services bound to the loopback address (SNMP, NTP) + # We also should add proper loopback IP addresses to the newly added + # VRF for services bound to the loopback address (SNMP, NTP) vrf_if.add_addr('127.0.0.1/8') - vrf_if.add_addr('::1/128') + if is_ipv6_enabled(): + vrf_if.add_addr('::1/128') # add VRF description if available vrf_if.set_alias(config.get('description', '')) -- cgit v1.2.3 From 60f009defadb9d36bf84def1e839cb11a0b3d619 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 30 Mar 2022 19:19:54 +0200 Subject: vyos.ifconfig: T4319: add_addr() should not add IPv6 address if it's disabled --- python/vyos/ifconfig/interface.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 22265cc3c..6b9a4d03e 100755 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -1083,6 +1083,10 @@ class Interface(Control): addr_is_v4 = is_ipv4(addr) + # Failsave - do not add IPv6 address if IPv6 is disabled + if is_ipv6(addr) and not is_ipv6_enabled(): + return False + # add to interface if addr == 'dhcp': self.set_dhcp(True) -- cgit v1.2.3 From a33b737b753843501c86eee744aef75137d2b64e Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 30 Mar 2022 21:57:13 +0200 Subject: smoketest: T4319: verify correct assignment of loopback IP addresses --- smoketest/scripts/cli/test_system_ipv6.py | 19 +++++++++++ smoketest/scripts/cli/test_vrf.py | 54 +++++++++++++++++++++++++++---- 2 files changed, 67 insertions(+), 6 deletions(-) diff --git a/smoketest/scripts/cli/test_system_ipv6.py b/smoketest/scripts/cli/test_system_ipv6.py index 3112d2e46..6fe58701b 100755 --- a/smoketest/scripts/cli/test_system_ipv6.py +++ b/smoketest/scripts/cli/test_system_ipv6.py @@ -17,7 +17,11 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.template import is_ipv4 from vyos.util import read_file +from vyos.util import is_ipv6_enabled +from vyos.validate import is_intf_addr_assigned base_path = ['system', 'ipv6'] @@ -42,6 +46,14 @@ class TestSystemIPv6(VyOSUnitTestSHIM.TestCase): self.assertEqual(read_file(file_forwarding), '0') def test_system_ipv6_disable(self): + # Verify previous "enable" state + self.assertEqual(read_file(file_disable), '0') + self.assertTrue(is_ipv6_enabled()) + + loopbacks = ['127.0.0.1', '::1'] + for addr in loopbacks: + self.assertTrue(is_intf_addr_assigned('lo', addr)) + # Do not assign any IPv6 address on interfaces, this requires a reboot # which can not be tested, but we can read the config file :) self.cli_set(base_path + ['disable']) @@ -49,6 +61,13 @@ class TestSystemIPv6(VyOSUnitTestSHIM.TestCase): # Verify configuration file self.assertEqual(read_file(file_disable), '1') + self.assertFalse(is_ipv6_enabled()) + + for addr in loopbacks: + if is_ipv4(addr): + self.assertTrue(is_intf_addr_assigned('lo', addr)) + else: + self.assertFalse(is_intf_addr_assigned('lo', addr)) def test_system_ipv6_strict_dad(self): # This defaults to 1 diff --git a/smoketest/scripts/cli/test_vrf.py b/smoketest/scripts/cli/test_vrf.py index 5ffa9c086..5daea589c 100755 --- a/smoketest/scripts/cli/test_vrf.py +++ b/smoketest/scripts/cli/test_vrf.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2021 VyOS maintainers and contributors +# Copyright (C) 2020-2022 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 @@ -25,9 +25,10 @@ from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError from vyos.ifconfig import Interface from vyos.ifconfig import Section -from vyos.template import is_ipv6 +from vyos.template import is_ipv4 from vyos.util import cmd from vyos.util import read_file +from vyos.util import get_interface_config from vyos.validate import is_intf_addr_assigned base_path = ['vrf'] @@ -105,10 +106,13 @@ class VRFTest(VyOSUnitTestSHIM.TestCase): frrconfig = self.getFRRconfig(f'vrf {vrf}') self.assertIn(f' vni {table}', frrconfig) + tmp = get_interface_config(vrf) + self.assertEqual(int(table), tmp['linkinfo']['info_data']['table']) + # Increment table ID for the next run table = str(int(table) + 1) - def test_vrf_loopback_ips(self): + def test_vrf_loopbacks_ips(self): table = '2000' for vrf in vrfs: base = base_path + ['name', vrf] @@ -119,10 +123,48 @@ class VRFTest(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify VRF configuration + loopbacks = ['127.0.0.1', '::1'] for vrf in vrfs: - self.assertTrue(vrf in interfaces()) - self.assertTrue(is_intf_addr_assigned(vrf, '127.0.0.1')) - self.assertTrue(is_intf_addr_assigned(vrf, '::1')) + # Ensure VRF was created + self.assertIn(vrf, interfaces()) + # Test for proper loopback IP assignment + for addr in loopbacks: + self.assertTrue(is_intf_addr_assigned(vrf, addr)) + + def test_vrf_loopbacks_no_ipv6(self): + table = '2002' + for vrf in vrfs: + base = base_path + ['name', vrf] + self.cli_set(base + ['table', str(table)]) + table = str(int(table) + 1) + + # Globally disable IPv6 - this will remove all IPv6 interface addresses + self.cli_set(['system', 'ipv6', 'disable']) + + # commit changes + self.cli_commit() + + # Verify VRF configuration + table = '2002' + loopbacks = ['127.0.0.1', '::1'] + for vrf in vrfs: + # Ensure VRF was created + self.assertIn(vrf, interfaces()) + + # Verify VRF table ID + tmp = get_interface_config(vrf) + self.assertEqual(int(table), tmp['linkinfo']['info_data']['table']) + + # Test for proper loopback IP assignment + for addr in loopbacks: + if is_ipv4(addr): + self.assertTrue(is_intf_addr_assigned(vrf, addr)) + else: + self.assertFalse(is_intf_addr_assigned(vrf, addr)) + + table = str(int(table) + 1) + + self.cli_delete(['system', 'ipv6']) def test_vrf_bind_all(self): table = '2000' -- cgit v1.2.3 From 7d4160f5e2ef1b0c7d5443850fa5b694b940547a Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Thu, 31 Mar 2022 13:08:05 +0000 Subject: bgp: T4326: Add bgp parameter no-suppress-duplicates Add new bgp parameter 'no-suppress-duplicates' set protocols bgp parameters no-suppress-duplicates --- data/templates/frr/bgpd.frr.tmpl | 3 +++ interface-definitions/include/bgp/protocol-common-config.xml.i | 6 ++++++ smoketest/scripts/cli/test_protocols_bgp.py | 2 ++ 3 files changed, 11 insertions(+) diff --git a/data/templates/frr/bgpd.frr.tmpl b/data/templates/frr/bgpd.frr.tmpl index 45e0544b7..0bc0fd36e 100644 --- a/data/templates/frr/bgpd.frr.tmpl +++ b/data/templates/frr/bgpd.frr.tmpl @@ -545,6 +545,9 @@ router bgp {{ local_as }} {{ 'vrf ' ~ vrf if vrf is defined and vrf is not none {% if parameters.no_fast_external_failover is defined %} no bgp fast-external-failover {% endif %} +{% if parameters.no_suppress_duplicates is defined %} + no bgp suppress-duplicates +{% endif %} {% if parameters.reject_as_sets is defined %} bgp reject-as-sets {% endif %} diff --git a/interface-definitions/include/bgp/protocol-common-config.xml.i b/interface-definitions/include/bgp/protocol-common-config.xml.i index 38337b032..b59ff0287 100644 --- a/interface-definitions/include/bgp/protocol-common-config.xml.i +++ b/interface-definitions/include/bgp/protocol-common-config.xml.i @@ -1430,6 +1430,12 @@ + + + Disable suppress duplicate updates if the route actually not changed + + + Reject routes with AS_SET or AS_CONFED_SET flag diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index d7230baf4..db1587ba7 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -274,6 +274,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['parameters', 'conditional-advertisement', 'timer', cond_adv_timer]) self.cli_set(base_path + ['parameters', 'fast-convergence']) self.cli_set(base_path + ['parameters', 'minimum-holdtime', min_hold_time]) + self.cli_set(base_path + ['parameters', 'no-suppress-duplicates']) self.cli_set(base_path + ['parameters', 'reject-as-sets']) self.cli_set(base_path + ['parameters', 'shutdown']) self.cli_set(base_path + ['parameters', 'suppress-fib-pending']) @@ -305,6 +306,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' bgp shutdown', frrconfig) self.assertIn(f' bgp suppress-fib-pending', frrconfig) self.assertNotIn(f'bgp ebgp-requires-policy', frrconfig) + self.assertIn(f' no bgp suppress-duplicates', frrconfig) afiv4_config = self.getFRRconfig(' address-family ipv4 unicast') self.assertIn(f' maximum-paths {max_path_v4}', afiv4_config) -- cgit v1.2.3 From d14102856387fb74de16f6305de7482443d561d0 Mon Sep 17 00:00:00 2001 From: srividya0208 Date: Mon, 28 Mar 2022 13:42:11 -0400 Subject: logging: T4308: log options for routing protocol It will be more convenient if there is a seperate option to debug the protocol --- op-mode-definitions/show-log.xml.in | 73 +++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in index 4c0a7913b..2d75f119d 100644 --- a/op-mode-definitions/show-log.xml.in +++ b/op-mode-definitions/show-log.xml.in @@ -212,6 +212,79 @@ + + + Show log for Routing Protocols + + + + + Show log for OSPF + + journalctl -b /usr/lib/frr/ospfd + + + + Show log for OSPF for IPv6 + + journalctl -b /usr/lib/frr/ospf6d + + + + Show log for BGP + + journalctl -b /usr/lib/frr/bgpd + + + + Show log for RIP + + journalctl -b /usr/lib/frr/ripd + + + + Show log for RIPng + + journalctl -b /usr/lib/frr/ripngd + + + + Show log for static route + + journalctl -b /usr/lib/frr/staticd + + + + Show log for Multicast protocol + + journalctl -b /usr/lib/frr/pimd + + + + Show log for ISIS + + journalctl -b /usr/lib/frr/isisd + + + + Show log for NHRP + + journalctl -b /usr/lib/frr/nhrpd + + + + Show log for BFD + + journalctl -b /usr/lib/frr/bfdd + + + + Show log for MPLS + + journalctl -b /usr/lib/frr/ldpd + + + Show log for Simple Network Monitoring Protocol (SNMP) -- cgit v1.2.3 From 53e20097d227ebf4bdb4dc6c85427ec9c5ec3982 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 31 Mar 2022 21:38:17 +0200 Subject: vyos.ifconfig: T4330: bugfix changing MTU when IPv6 is disabled Commit f8b3d8999c ("ipv6: T4319: do not configure IPv6 related settings if it's disabled") moved the MTU configuration part under the code path which is only run if IPv6 is enabled on the system. This prevented MTU changes on IPv6 disabled systems. --- python/vyos/ifconfig/interface.py | 8 ++++---- smoketest/scripts/cli/test_system_ipv6.py | 12 ++++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 6b9a4d03e..f39da90e4 100755 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -1530,10 +1530,6 @@ class Interface(Control): value = tmp if (tmp != None) else '1' self.set_ipv6_dad_messages(value) - # MTU - Maximum Transfer Unit - if 'mtu' in config: - self.set_mtu(config.get('mtu')) - # Delete old IPv6 EUI64 addresses before changing MAC for addr in (dict_search('ipv6.address.eui64_old', config) or []): self.del_ipv6_eui64_address(addr) @@ -1550,6 +1546,10 @@ class Interface(Control): for addr in tmp: self.add_ipv6_eui64_address(addr) + # MTU - Maximum Transfer Unit + if 'mtu' in config: + self.set_mtu(config.get('mtu')) + # re-add ourselves to any bridge we might have fallen out of if 'is_bridge_member' in config: bridge_dict = config.get('is_bridge_member') diff --git a/smoketest/scripts/cli/test_system_ipv6.py b/smoketest/scripts/cli/test_system_ipv6.py index 6fe58701b..837d1dc12 100755 --- a/smoketest/scripts/cli/test_system_ipv6.py +++ b/smoketest/scripts/cli/test_system_ipv6.py @@ -21,6 +21,7 @@ from base_vyostest_shim import VyOSUnitTestSHIM from vyos.template import is_ipv4 from vyos.util import read_file from vyos.util import is_ipv6_enabled +from vyos.util import get_interface_config from vyos.validate import is_intf_addr_assigned base_path = ['system', 'ipv6'] @@ -69,6 +70,17 @@ class TestSystemIPv6(VyOSUnitTestSHIM.TestCase): else: self.assertFalse(is_intf_addr_assigned('lo', addr)) + # T4330: Verify MTU can be changed with IPv6 disabled + mtu = '1600' + eth_if = 'eth0' + self.cli_set(['interfaces', 'ethernet', eth_if, 'mtu', mtu]) + self.cli_commit() + + tmp = get_interface_config(eth_if) + self.assertEqual(tmp['mtu'], int(mtu)) + + self.cli_delete(['interfaces', 'ethernet', eth_if, 'mtu']) + def test_system_ipv6_strict_dad(self): # This defaults to 1 self.assertEqual(read_file(file_dad), '1') -- cgit v1.2.3 From 601ab19fd8c81a998b3c966cc83b85ed60ac5ae0 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 31 Mar 2022 21:38:17 +0200 Subject: vyos.ifconfig: T4330: MTU must be configured prior to any IPv6 option change This extends the fix from 53e20097 ("vyos.ifconfig: T4330: bugfix changing MTU when IPv6 is disabled") by ordering the execution in a way the Kernel does not complain. --- python/vyos/ifconfig/interface.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index f39da90e4..d6906a061 100755 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -1501,6 +1501,13 @@ class Interface(Control): value = tmp if (tmp != None) else '0' self.set_ipv4_source_validation(value) + # MTU - Maximum Transfer Unit has a default value. It must ALWAYS be set + # before mangling any IPv6 option. If MTU is less then 1280 IPv6 will be + # automatically disabled by the kernel. Also MTU must be increased before + # configuring any IPv6 address on the interface. + if 'mtu' in config: + self.set_mtu(config.get('mtu')) + # Only change IPv6 parameters if IPv6 was not explicitly disabled if is_ipv6_enabled(): # Configure MSS value for IPv6 TCP connections @@ -1546,10 +1553,6 @@ class Interface(Control): for addr in tmp: self.add_ipv6_eui64_address(addr) - # MTU - Maximum Transfer Unit - if 'mtu' in config: - self.set_mtu(config.get('mtu')) - # re-add ourselves to any bridge we might have fallen out of if 'is_bridge_member' in config: bridge_dict = config.get('is_bridge_member') -- cgit v1.2.3 From 37426103b9d0c522ddf62029b931a56197f2a00a Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Fri, 1 Apr 2022 09:46:37 +0000 Subject: policy: T4329: Fix regex for extcommunity rt Fix regex to allow to set several marks for extcommunity rt set extcommunity rt '65:24 65:23 192.168.2.1:11 192.168.0.2:222' --- interface-definitions/policy.xml.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface-definitions/policy.xml.in b/interface-definitions/policy.xml.in index 5e037b558..1a4781397 100644 --- a/interface-definitions/policy.xml.in +++ b/interface-definitions/policy.xml.in @@ -1086,7 +1086,7 @@ Based on a router-id IP address - ^((?:[0-9]{1,3}\.){3}[0-9]{1,3}|\d+):\d+$ + ^(((\b(?:(?:2(?:[0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9])\.){3}(?:(?:2([0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9]))\b)|(\d+)):(\d+) ?)+$ Should be in form: ASN:NN or IPADDR:NN where ASN is autonomous system number -- cgit v1.2.3 From 4e09be0cd9ad18a8b2af5789a9bf2814b8702c61 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 1 Apr 2022 21:24:47 +0200 Subject: op-mode: logging: T4308: use full option name (--boot) --- op-mode-definitions/show-log.xml.in | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in index 2d75f119d..15bbc7f42 100644 --- a/op-mode-definitions/show-log.xml.in +++ b/op-mode-definitions/show-log.xml.in @@ -221,67 +221,67 @@ Show log for OSPF - journalctl -b /usr/lib/frr/ospfd + journalctl --boot /usr/lib/frr/ospfd Show log for OSPF for IPv6 - journalctl -b /usr/lib/frr/ospf6d + journalctl --boot /usr/lib/frr/ospf6d Show log for BGP - journalctl -b /usr/lib/frr/bgpd + journalctl --boot /usr/lib/frr/bgpd Show log for RIP - journalctl -b /usr/lib/frr/ripd + journalctl --boot /usr/lib/frr/ripd Show log for RIPng - journalctl -b /usr/lib/frr/ripngd + journalctl --boot /usr/lib/frr/ripngd Show log for static route - journalctl -b /usr/lib/frr/staticd + journalctl --boot /usr/lib/frr/staticd Show log for Multicast protocol - journalctl -b /usr/lib/frr/pimd + journalctl --boot /usr/lib/frr/pimd Show log for ISIS - journalctl -b /usr/lib/frr/isisd + journalctl --boot /usr/lib/frr/isisd Show log for NHRP - journalctl -b /usr/lib/frr/nhrpd + journalctl --boot /usr/lib/frr/nhrpd Show log for BFD - journalctl -b /usr/lib/frr/bfdd + journalctl --boot /usr/lib/frr/bfdd Show log for MPLS - journalctl -b /usr/lib/frr/ldpd + journalctl --boot /usr/lib/frr/ldpd -- cgit v1.2.3 From ff908b7d766ddfc0db5d728b531f0a9db89f1890 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 1 Apr 2022 21:25:11 +0200 Subject: op-mode: monitor: T4308: support following FRR daemon logs --- op-mode-definitions/monitor-log.xml.in | 73 ++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/op-mode-definitions/monitor-log.xml.in b/op-mode-definitions/monitor-log.xml.in index 352c84ff1..cbdf76fc3 100644 --- a/op-mode-definitions/monitor-log.xml.in +++ b/op-mode-definitions/monitor-log.xml.in @@ -14,6 +14,79 @@ grc tail --follow=name /var/log/messages + + + Monitor log for Routing Protocols + + + + + Monitor log for OSPF + + journalctl --follow --boot /usr/lib/frr/ospfd + + + + Monitor log for OSPF for IPv6 + + journalctl --follow --boot /usr/lib/frr/ospf6d + + + + Monitor log for BGP + + journalctl --follow --boot /usr/lib/frr/bgpd + + + + Monitor log for RIP + + journalctl --follow --boot /usr/lib/frr/ripd + + + + Monitor log for RIPng + + journalctl --follow --boot /usr/lib/frr/ripngd + + + + Monitor log for static route + + journalctl --follow --boot /usr/lib/frr/staticd + + + + Monitor log for Multicast protocol + + journalctl --follow --boot /usr/lib/frr/pimd + + + + Monitor log for ISIS + + journalctl --follow --boot /usr/lib/frr/isisd + + + + Monitor log for NHRP + + journalctl --follow --boot /usr/lib/frr/nhrpd + + + + Monitor log for BFD + + journalctl --follow --boot /usr/lib/frr/bfdd + + + + Monitor log for MPLS + + journalctl --follow --boot /usr/lib/frr/ldpd + + + -- cgit v1.2.3 From adcd022a0eaf3400c028e077fb063e58064ae154 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 1 Apr 2022 21:32:11 +0200 Subject: smoketest: bgp: verify graceful-restart options per neighbor --- smoketest/scripts/cli/test_protocols_bgp.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index db1587ba7..7237df95b 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -239,6 +239,12 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): if 'non_exist_map' in peer_config: base = f'{base} non-exist-map {peer_config["non_exist_map"]}' self.assertIn(base, frrconfig) + if 'graceful_rst' in peer_config: + self.assertIn(f' neighbor {peer} graceful-restart', frrconfig) + if 'graceful_rst_no' in peer_config: + self.assertIn(f' neighbor {peer} graceful-restart-disable', frrconfig) + if 'graceful_rst_hlp' in peer_config: + self.assertIn(f' neighbor {peer} graceful-restart-helper', frrconfig) def test_bgp_01_simple(self): router_id = '127.0.0.1' @@ -380,6 +386,12 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['neighbor', peer, 'address-family', afi, 'addpath-tx-all']) if 'addpath_per_as' in peer_config: self.cli_set(base_path + ['neighbor', peer, 'address-family', afi, 'addpath-tx-per-as']) + if 'graceful_rst' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'graceful-restart', 'enable']) + if 'graceful_rst_no' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'graceful-restart', 'disable']) + if 'graceful_rst_hlp' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'graceful-restart', 'restart-helper']) # Conditional advertisement if 'advertise_map' in peer_config: @@ -462,6 +474,12 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['peer-group', peer_group, 'address-family', 'ipv4-unicast', 'addpath-tx-all']) if 'addpath_per_as' in config: self.cli_set(base_path + ['peer-group', peer_group, 'address-family', 'ipv4-unicast', 'addpath-tx-per-as']) + if 'graceful_rst' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'graceful-restart', 'enable']) + if 'graceful_rst_no' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'graceful-restart', 'disable']) + if 'graceful_rst_hlp' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'graceful-restart', 'restart-helper']) # Conditional advertisement if 'advertise_map' in config: -- cgit v1.2.3 From 57dcafbda88e3005196405f561aa73a04344112c Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 1 Apr 2022 21:33:13 +0200 Subject: bgp: T4332: addpath-tx-per-as requires BGP deterministic-med paramtere to be set --- smoketest/scripts/cli/test_protocols_bgp.py | 2 ++ src/conf_mode/protocols_bgp.py | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index 7237df95b..fd3c27cdf 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -326,6 +326,8 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): def test_bgp_02_neighbors(self): # Test out individual neighbor configuration items, not all of them are # also available to a peer-group! + self.cli_set(base_path + ['parameters', 'deterministic-med']) + for peer, peer_config in neighbor_config.items(): afi = 'ipv4-unicast' if is_ipv6(peer): diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index 64b113873..dace53d37 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2021 VyOS maintainers and contributors +# Copyright (C) 2020-2022 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 @@ -213,6 +213,11 @@ def verify(bgp): if 'non_exist_map' in afi_config['conditionally_advertise']: verify_route_map(afi_config['conditionally_advertise']['non_exist_map'], bgp) + # T4332: bgp deterministic-med cannot be disabled while addpath-tx-bestpath-per-AS is in use + if 'addpath_tx_per_as' in afi_config: + if dict_search('parameters.deterministic_med', bgp) == None: + raise ConfigError('addpath-tx-per-as requires BGP deterministic-med paramtere to be set!') + # Validate if configured Prefix list exists if 'prefix_list' in afi_config: for tmp in ['import', 'export']: -- cgit v1.2.3 From 3ce13fe3c5c4161825fb36174d0da18668dca80c Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 1 Apr 2022 21:33:31 +0200 Subject: smoketest: bgp: adjust test parameter indention --- smoketest/scripts/cli/test_protocols_bgp.py | 177 +++++++++++++++------------- 1 file changed, 92 insertions(+), 85 deletions(-) diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index fd3c27cdf..f1db5350a 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2022 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 @@ -36,112 +36,118 @@ bfd_profile = 'foo-bar-baz' neighbor_config = { '192.0.2.1' : { - 'bfd' : '', - 'cap_dynamic' : '', - 'cap_ext_next' : '', - 'remote_as' : '100', - 'adv_interv' : '400', - 'passive' : '', - 'password' : 'VyOS-Secure123', - 'shutdown' : '', - 'cap_over' : '', - 'ttl_security' : '5', - 'local_as' : '300', - 'route_map_in' : route_map_in, - 'route_map_out': route_map_out, + 'bfd' : '', + 'cap_dynamic' : '', + 'cap_ext_next' : '', + 'remote_as' : '100', + 'adv_interv' : '400', + 'passive' : '', + 'password' : 'VyOS-Secure123', + 'shutdown' : '', + 'cap_over' : '', + 'ttl_security' : '5', + 'local_as' : '300', + 'route_map_in' : route_map_in, + 'route_map_out' : route_map_out, 'no_send_comm_ext' : '', - 'addpath_all' : '', + 'addpath_all' : '', }, '192.0.2.2' : { - 'bfd_profile' : bfd_profile, - 'remote_as' : '200', - 'shutdown' : '', - 'no_cap_nego' : '', - 'port' : '667', - 'cap_strict' : '', - 'advertise_map': route_map_in, - 'non_exist_map': route_map_out, - 'pfx_list_in' : prefix_list_in, - 'pfx_list_out' : prefix_list_out, + 'bfd_profile' : bfd_profile, + 'remote_as' : '200', + 'shutdown' : '', + 'no_cap_nego' : '', + 'port' : '667', + 'cap_strict' : '', + 'advertise_map' : route_map_in, + 'non_exist_map' : route_map_out, + 'pfx_list_in' : prefix_list_in, + 'pfx_list_out' : prefix_list_out, 'no_send_comm_std' : '', }, '192.0.2.3' : { - 'advertise_map': route_map_in, - 'description' : 'foo bar baz', - 'remote_as' : '200', - 'passive' : '', - 'multi_hop' : '5', - 'update_src' : 'lo', - 'peer_group' : 'foo', + 'advertise_map' : route_map_in, + 'description' : 'foo bar baz', + 'remote_as' : '200', + 'passive' : '', + 'multi_hop' : '5', + 'update_src' : 'lo', + 'peer_group' : 'foo', + 'graceful_rst' : '', }, '2001:db8::1' : { - 'advertise_map': route_map_in, - 'exist_map' : route_map_out, - 'cap_dynamic' : '', - 'cap_ext_next' : '', - 'remote_as' : '123', - 'adv_interv' : '400', - 'passive' : '', - 'password' : 'VyOS-Secure123', - 'shutdown' : '', - 'cap_over' : '', - 'ttl_security' : '5', - 'local_as' : '300', - 'solo' : '', - 'route_map_in' : route_map_in, - 'route_map_out': route_map_out, + 'advertise_map' : route_map_in, + 'exist_map' : route_map_out, + 'cap_dynamic' : '', + 'cap_ext_next' : '', + 'remote_as' : '123', + 'adv_interv' : '400', + 'passive' : '', + 'password' : 'VyOS-Secure123', + 'shutdown' : '', + 'cap_over' : '', + 'ttl_security' : '5', + 'local_as' : '300', + 'solo' : '', + 'route_map_in' : route_map_in, + 'route_map_out' : route_map_out, 'no_send_comm_std' : '', 'addpath_per_as' : '', - 'peer_group' : 'foo-bar', + 'peer_group' : 'foo-bar', }, '2001:db8::2' : { - 'remote_as' : '456', - 'shutdown' : '', - 'no_cap_nego' : '', - 'port' : '667', - 'cap_strict' : '', - 'pfx_list_in' : prefix_list_in6, - 'pfx_list_out' : prefix_list_out6, + 'remote_as' : '456', + 'shutdown' : '', + 'no_cap_nego' : '', + 'port' : '667', + 'cap_strict' : '', + 'pfx_list_in' : prefix_list_in6, + 'pfx_list_out' : prefix_list_out6, 'no_send_comm_ext' : '', - 'peer_group' : 'foo-bar_baz', + 'peer_group' : 'foo-bar_baz', + 'graceful_rst_hlp' : '' }, } peer_group_config = { 'foo' : { - 'advertise_map': route_map_in, - 'exist_map' : route_map_out, - 'bfd' : '', - 'remote_as' : '100', - 'passive' : '', - 'password' : 'VyOS-Secure123', - 'shutdown' : '', - 'cap_over' : '', - 'ttl_security': '5', + 'advertise_map' : route_map_in, + 'exist_map' : route_map_out, + 'bfd' : '', + 'remote_as' : '100', + 'passive' : '', + 'password' : 'VyOS-Secure123', + 'shutdown' : '', + 'cap_over' : '', + 'ttl_security' : '5', + }, + 'bar' : { + 'remote_as' : '111', + 'graceful_rst_no' : '' }, 'foo-bar' : { - 'advertise_map': route_map_in, - 'description' : 'foo peer bar group', - 'remote_as' : '200', - 'shutdown' : '', - 'no_cap_nego' : '', - 'local_as' : '300', - 'pfx_list_in' : prefix_list_in, - 'pfx_list_out' : prefix_list_out, + 'advertise_map' : route_map_in, + 'description' : 'foo peer bar group', + 'remote_as' : '200', + 'shutdown' : '', + 'no_cap_nego' : '', + 'local_as' : '300', + 'pfx_list_in' : prefix_list_in, + 'pfx_list_out' : prefix_list_out, 'no_send_comm_ext' : '', }, 'foo-bar_baz' : { - 'advertise_map': route_map_in, - 'non_exist_map': route_map_out, - 'bfd_profile' : bfd_profile, - 'cap_dynamic' : '', - 'cap_ext_next' : '', - 'remote_as' : '200', - 'passive' : '', - 'multi_hop' : '5', - 'update_src' : 'lo', - 'route_map_in' : route_map_in, - 'route_map_out': route_map_out, + 'advertise_map' : route_map_in, + 'non_exist_map' : route_map_out, + 'bfd_profile' : bfd_profile, + 'cap_dynamic' : '', + 'cap_ext_next' : '', + 'remote_as' : '200', + 'passive' : '', + 'multi_hop' : '5', + 'update_src' : 'lo', + 'route_map_in' : route_map_in, + 'route_map_out' : route_map_out, }, } @@ -501,6 +507,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): if 'peer_group' in peer_config: self.cli_set(base_path + ['neighbor', peer, 'peer-group', peer_config['peer_group']]) + # commit changes self.cli_commit() @@ -876,4 +883,4 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' exit-address-family', afi_config) if __name__ == '__main__': - unittest.main(verbosity=2) + unittest.main(verbosity=2, failfast=True) -- cgit v1.2.3 From 1684773712b92e70130b618937700b472da22fce Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 1 Apr 2022 21:56:53 +0200 Subject: vyos.template: T4333: add Jinja2 plugin to test if a variable is defined and not none We have a lot of boiler plate template code like {% if config.interface is defined and config.interface.remote_as is defined and config.interface.remote_as is not none %} ... {% endif %} This can be stripped down using a custom test to: {% if config.interface.remote_as is vyos_defined %} ... {% endif %} In addition the new vyos_defined test supports comparison {% if foo.bar.baz is vyos_defined('zoo') %} ... {% endif %} So the above will only evaluate to true if the variable foo.bar.baz is defined and its content is zoo This is inspired from https://github.com/aristanetworks/ansible-avd/ which make heavy use of it. All new templates should be written in this new style. --- python/vyos/template.py | 77 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/python/vyos/template.py b/python/vyos/template.py index dabf53692..132f5ddde 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -18,7 +18,7 @@ import os from jinja2 import Environment from jinja2 import FileSystemLoader - +from jinja2 import ChainableUndefined from vyos.defaults import directories from vyos.util import chmod from vyos.util import chown @@ -27,6 +27,7 @@ from vyos.util import makedir # Holds template filters registered via register_filter() _FILTERS = {} +_TESTS = {} # reuse Environments with identical settings to improve performance @functools.lru_cache(maxsize=2) @@ -42,8 +43,10 @@ def _get_environment(location=None): cache_size=100, loader=loc_loader, trim_blocks=True, + undefined=ChainableUndefined, ) env.filters.update(_FILTERS) + env.tests.update(_TESTS) return env @@ -67,6 +70,26 @@ def register_filter(name, func=None): _FILTERS[name] = func return func +def register_test(name, func=None): + """Register a function to be available as test in templates under given name. + + It can also be used as a decorator, see below in this module for examples. + + :raise RuntimeError: + when trying to register a test after a template has been rendered already + :raise ValueError: when trying to register a name which was taken already + """ + if func is None: + return functools.partial(register_test, name) + if _get_environment.cache_info().currsize: + raise RuntimeError( + "Tests can only be registered before rendering the first template" + ) + if name in _TESTS: + raise ValueError(f"A test with name {name!r} was registered already") + _TESTS[name] = func + return func + def render_to_string(template, content, formater=None, location=None): """Render a template from the template directory, raise on any errors. @@ -566,3 +589,55 @@ def nft_intra_zone_action(zone_conf, ipv6=False): name = dict_search_args(intra_zone, 'firewall', fw_name) return f'jump {name_prefix}{name}' return 'return' + +@register_test('vyos_defined') +def vyos_defined(value, test_value=None, var_type=None): + """ + Jinja2 plugin to test if a variable is defined and not none - vyos_defined + will test value if defined and is not none and return true or false. + + If test_value is supplied, the value must also pass == test_value to return true. + If var_type is supplied, the value must also be of the specified class/type + + Examples: + 1. Test if var is defined and not none: + {% if foo is vyos_defined %} + ... + {% endif %} + + 2. Test if variable is defined, not none and has value "something" + {% if bar is vyos_defined("something") %} + ... + {% endif %} + + Parameters + ---------- + value : any + Value to test from ansible + test_value : any, optional + Value to test in addition of defined and not none, by default None + var_type : ['float', 'int', 'str', 'list', 'dict', 'tuple', 'bool'], optional + Type or Class to test for + + Returns + ------- + boolean + True if variable matches criteria, False in other cases. + + Implementation inspired and re-used from https://github.com/aristanetworks/ansible-avd/ + """ + + from jinja2 import Undefined + + if isinstance(value, Undefined) or value is None: + # Invalid value - return false + return False + elif test_value is not None and value != test_value: + # Valid value but not matching the optional argument + return False + elif str(var_type).lower() in ['float', 'int', 'str', 'list', 'dict', 'tuple', 'bool'] and str(var_type).lower() != type(value).__name__: + # Invalid class - return false + return False + else: + # Valid value and is matching optional argument if provided - return true + return True -- cgit v1.2.3 From c6fcab97ed5c73529d043ac6a2540ab7d06759f0 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 1 Apr 2022 21:58:11 +0200 Subject: bgp: T4333: migrate to new vyos_defined Jinja2 test --- data/templates/frr/bgpd.frr.tmpl | 440 ++++++++++++++++++--------------------- 1 file changed, 204 insertions(+), 236 deletions(-) diff --git a/data/templates/frr/bgpd.frr.tmpl b/data/templates/frr/bgpd.frr.tmpl index 0bc0fd36e..8baa128a7 100644 --- a/data/templates/frr/bgpd.frr.tmpl +++ b/data/templates/frr/bgpd.frr.tmpl @@ -3,113 +3,113 @@ {% macro bgp_neighbor(neighbor, config, peer_group=false) %} {% if peer_group == true %} neighbor {{ neighbor }} peer-group -{% elif config.peer_group is defined and config.peer_group is not none %} +{% elif config.peer_group is vyos_defined %} neighbor {{ neighbor }} peer-group {{ config.peer_group }} {% endif %} -{% if config.remote_as is defined and config.remote_as is not none %} +{% if config.remote_as is vyos_defined %} neighbor {{ neighbor }} remote-as {{ config.remote_as }} {% endif %} -{% if config.interface is defined and config.interface.remote_as is defined and config.interface.remote_as is not none %} +{% if config.interface.remote_as is vyos_defined %} neighbor {{ neighbor }} interface remote-as {{ config.interface.remote_as }} {% endif %} -{% if config.advertisement_interval is defined and config.advertisement_interval is not none %} +{% if config.advertisement_interval is vyos_defined %} neighbor {{ neighbor }} advertisement-interval {{ config.advertisement_interval }} {% endif %} -{% if config.bfd is defined %} +{% if config.bfd is vyos_defined %} neighbor {{ neighbor }} bfd -{% if config.bfd.check_control_plane_failure is defined %} +{% if config.bfd.check_control_plane_failure is vyos_defined %} neighbor {{ neighbor }} bfd check-control-plane-failure {% endif %} -{% if config.bfd.profile is defined and config.bfd.profile is not none %} +{% if config.bfd.profile is vyos_defined %} neighbor {{ neighbor }} bfd profile {{ config.bfd.profile }} {% endif %} {% endif %} -{% if config.capability is defined and config.capability is not none %} -{% if config.capability.dynamic is defined %} +{% if config.capability is vyos_defined %} +{% if config.capability.dynamic is vyos_defined %} neighbor {{ neighbor }} capability dynamic {% endif %} -{% if config.capability.extended_nexthop is defined %} +{% if config.capability.extended_nexthop is vyos_defined %} neighbor {{ neighbor }} capability extended-nexthop {% endif %} {% endif %} -{% if config.description is defined and config.description is not none %} +{% if config.description is vyos_defined %} neighbor {{ neighbor }} description {{ config.description }} {% endif %} -{% if config.disable_capability_negotiation is defined %} +{% if config.disable_capability_negotiation is vyos_defined %} neighbor {{ neighbor }} dont-capability-negotiate {% endif %} -{% if config.ebgp_multihop is defined and config.ebgp_multihop is not none %} +{% if config.ebgp_multihop is vyos_defined %} neighbor {{ neighbor }} ebgp-multihop {{ config.ebgp_multihop }} {% endif %} -{% if config.graceful_restart is defined and config.graceful_restart is not none %} -{% if config.graceful_restart == 'enable' %} +{% if config.graceful_restart is vyos_defined %} +{% if config.graceful_restart is vyos_defined('enable') %} {% set graceful_restart = 'graceful-restart' %} -{% elif config.graceful_restart == 'disable' %} +{% elif config.graceful_restart is vyos_defined('disable') %} {% set graceful_restart = 'graceful-restart-disable' %} -{% elif config.graceful_restart == 'restart-helper' %} +{% elif config.graceful_restart is vyos_defined('restart-helper') %} {% set graceful_restart = 'graceful-restart-helper' %} {% endif %} neighbor {{ neighbor }} {{ graceful_restart }} {% endif %} -{% if config.local_as is defined and config.local_as is not none %} +{% if config.local_as is vyos_defined %} {% for local_as, local_as_config in config.local_as.items() %} {# There can be only one local-as value, this is checked in the Python code #} - neighbor {{ neighbor }} local-as {{ local_as }} {{ 'no-prepend' if local_as_config.no_prepend is defined }} {{ 'replace-as' if local_as_config.no_prepend is defined and local_as_config.no_prepend.replace_as is defined }} + neighbor {{ neighbor }} local-as {{ local_as }} {{ 'no-prepend' if local_as_config.no_prepend is vyos_defined }} {{ 'replace-as' if local_as_config.no_prepend is vyos_defined and local_as_config.no_prepend.replace_as is vyos_defined }} {% endfor %} {% endif %} -{% if config.override_capability is defined %} +{% if config.override_capability is vyos_defined %} neighbor {{ neighbor }} override-capability {% endif %} -{% if config.passive is defined %} +{% if config.passive is vyos_defined %} neighbor {{ neighbor }} passive {% endif %} -{% if config.password is defined and config.password is not none %} +{% if config.password is vyos_defined %} neighbor {{ neighbor }} password {{ config.password }} {% endif %} -{% if config.port is defined and config.port is not none %} +{% if config.port is vyos_defined %} neighbor {{ neighbor }} port {{ config.port }} {% endif %} -{% if config.shutdown is defined %} +{% if config.shutdown is vyos_defined %} neighbor {{ neighbor }} shutdown {% endif %} -{% if config.solo is defined %} +{% if config.solo is vyos_defined %} neighbor {{ neighbor }} solo {% endif %} -{% if config.strict_capability_match is defined %} +{% if config.strict_capability_match is vyos_defined %} neighbor {{ neighbor }} strict-capability-match {% endif %} -{% if config.ttl_security is defined and config.ttl_security.hops is defined and config.ttl_security.hops is not none %} +{% if config.ttl_security.hops is vyos_defined %} neighbor {{ neighbor }} ttl-security hops {{ config.ttl_security.hops }} {% endif %} -{% if config.timers is defined %} -{% if config.timers.connect is defined and config.timers.connect is not none %} +{% if config.timers is vyos_defined %} +{% if config.timers.connect is vyos_defined %} neighbor {{ neighbor }} timers connect {{ config.timers.connect }} {% endif %} -{% if config.timers.holdtime is defined and config.timers.keepalive is defined and config.timers.holdtime is not none and config.timers.keepalive is not none %} +{% if config.timers.keepalive is vyos_defined and config.timers.holdtime is vyos_defined %} neighbor {{ neighbor }} timers {{ config.timers.keepalive }} {{ config.timers.holdtime }} {% endif %} {% endif %} -{% if config.update_source is defined and config.update_source is not none %} +{% if config.update_source is vyos_defined %} neighbor {{ neighbor }} update-source {{ config.update_source }} {% endif %} -{% if config.interface is defined and config.interface is not none %} -{% if config.interface.peer_group is defined and config.interface.peer_group is not none %} +{% if config.interface is vyos_defined %} +{% if config.interface.peer_group is vyos_defined %} neighbor {{ neighbor }} interface peer-group {{ config.interface.peer_group }} {% endif %} -{% if config.interface.source_interface is defined and config.interface.source_interface is not none %} +{% if config.interface.source_interface is vyos_defined %} neighbor {{ neighbor }} interface {{ config.interface.source_interface }} {% endif %} -{% if config.interface.v6only is defined and config.interface.v6only is not none %} -{% if config.interface.v6only.peer_group is defined and config.interface.v6only.peer_group is not none %} +{% if config.interface.v6only is vyos_defined %} +{% if config.interface.v6only.peer_group is vyos_defined %} neighbor {{ neighbor }} interface v6only peer-group {{ config.interface.v6only.peer_group }} {% endif %} -{% if config.interface.v6only.remote_as is defined and config.interface.v6only.remote_as is not none %} +{% if config.interface.v6only.remote_as is vyos_defined %} neighbor {{ neighbor }} interface v6only remote-as {{ config.interface.v6only.remote_as }} {% endif %} {% endif %} {% endif %} ! -{% if config.address_family is defined and config.address_family is not none %} +{% if config.address_family is vyos_defined %} {% for afi, afi_config in config.address_family.items() %} {% if afi == 'ipv4_unicast' %} address-family ipv4 unicast @@ -134,104 +134,96 @@ {% elif afi == 'l2vpn_evpn' %} address-family l2vpn evpn {% endif %} -{% if afi_config.addpath_tx_all is defined %} +{% if afi_config.addpath_tx_all is vyos_defined %} neighbor {{ neighbor }} addpath-tx-all-paths {% endif %} -{% if afi_config.addpath_tx_per_as is defined %} +{% if afi_config.addpath_tx_per_as is vyos_defined %} neighbor {{ neighbor }} addpath-tx-bestpath-per-AS {% endif %} -{% if afi_config.allowas_in is defined and afi_config.allowas_in is not none %} - neighbor {{ neighbor }} allowas-in {{ afi_config.allowas_in.number if afi_config.allowas_in.number is defined }} +{% if afi_config.allowas_in is vyos_defined %} + neighbor {{ neighbor }} allowas-in {{ afi_config.allowas_in.number if afi_config.allowas_in.number is vyos_defined }} {% endif %} -{% if afi_config.as_override is defined %} +{% if afi_config.as_override is vyos_defined %} neighbor {{ neighbor }} as-override {% endif %} -{% if afi_config.conditionally_advertise is defined and afi_config.conditionally_advertise is not none %} -{% if afi_config.conditionally_advertise.advertise_map is defined and afi_config.conditionally_advertise.advertise_map is not none %} +{% if afi_config.conditionally_advertise is vyos_defined %} +{% if afi_config.conditionally_advertise.advertise_map is vyos_defined %} {% set exist_non_exist_map = 'exist-map' %} -{% if afi_config.conditionally_advertise.exist_map is defined and afi_config.conditionally_advertise.exist_map is not none %} +{% if afi_config.conditionally_advertise.exist_map is vyos_defined %} {% set exist_non_exist_map = 'exist-map ' ~ afi_config.conditionally_advertise.exist_map %} -{% elif afi_config.conditionally_advertise.non_exist_map is defined and afi_config.conditionally_advertise.non_exist_map is not none %} +{% elif afi_config.conditionally_advertise.non_exist_map is vyos_defined %} {% set exist_non_exist_map = 'non-exist-map ' ~ afi_config.conditionally_advertise.non_exist_map %} {% endif %} neighbor {{ neighbor }} advertise-map {{ afi_config.conditionally_advertise.advertise_map }} {{ exist_non_exist_map }} {% endif %} {% endif %} -{% if afi_config.remove_private_as is defined %} +{% if afi_config.remove_private_as is vyos_defined %} neighbor {{ neighbor }} remove-private-AS {% endif %} -{% if afi_config.route_reflector_client is defined %} +{% if afi_config.route_reflector_client is vyos_defined %} neighbor {{ neighbor }} route-reflector-client {% endif %} -{% if afi_config.weight is defined and afi_config.weight is not none %} +{% if afi_config.weight is vyos_defined %} neighbor {{ neighbor }} weight {{ afi_config.weight }} {% endif %} -{% if afi_config.attribute_unchanged is defined and afi_config.attribute_unchanged is not none %} - neighbor {{ neighbor }} attribute-unchanged {{ 'as-path ' if afi_config.attribute_unchanged.as_path is defined }}{{ 'med ' if afi_config.attribute_unchanged.med is defined }}{{ 'next-hop ' if afi_config.attribute_unchanged.next_hop is defined }} +{% if afi_config.attribute_unchanged is vyos_defined %} + neighbor {{ neighbor }} attribute-unchanged {{ 'as-path ' if afi_config.attribute_unchanged.as_path is vyos_defined }}{{ 'med ' if afi_config.attribute_unchanged.med is vyos_defined }}{{ 'next-hop ' if afi_config.attribute_unchanged.next_hop is vyos_defined }} {% endif %} -{% if afi_config.capability is defined and afi_config.capability.orf is defined and afi_config.capability.orf.prefix_list is defined and afi_config.capability.orf.prefix_list.send is defined %} +{% if afi_config.capability.orf.prefix_list.send is vyos_defined %} neighbor {{ neighbor }} capability orf prefix-list send {% endif %} -{% if afi_config.capability is defined and afi_config.capability.orf is defined and afi_config.capability.orf.prefix_list is defined and afi_config.capability.orf.prefix_list.receive is defined %} +{% if afi_config.capability.orf.prefix_list.receive is vyos_defined %} neighbor {{ neighbor }} capability orf prefix-list receive {% endif %} -{% if afi_config.default_originate is defined %} - neighbor {{ neighbor }} default-originate {{ 'route-map ' ~ afi_config.default_originate.route_map if afi_config.default_originate.route_map is defined }} +{% if afi_config.default_originate is vyos_defined %} + neighbor {{ neighbor }} default-originate {{ 'route-map ' ~ afi_config.default_originate.route_map if afi_config.default_originate.route_map is vyos_defined }} {% endif %} -{% if afi_config.distribute_list is defined and afi_config.distribute_list is not none %} -{% if afi_config.distribute_list.export is defined and afi_config.distribute_list.export is not none %} +{% if afi_config.distribute_list.export is vyos_defined %} neighbor {{ neighbor }} distribute-list {{ afi_config.distribute_list.export }} out -{% endif %} -{% if afi_config.distribute_list.import is defined and afi_config.distribute_list.import is not none %} +{% endif %} +{% if afi_config.distribute_list.import is vyos_defined %} neighbor {{ neighbor }} distribute-list {{ afi_config.distribute_list.import }} in -{% endif %} {% endif %} -{% if afi_config.filter_list is defined and afi_config.filter_list is not none %} -{% if afi_config.filter_list.export is defined and afi_config.filter_list.export is not none %} +{% if afi_config.filter_list.export is vyos_defined %} neighbor {{ neighbor }} filter-list {{ afi_config.filter_list.export }} out -{% endif %} -{% if afi_config.filter_list.import is defined and afi_config.filter_list.import is not none %} +{% endif %} +{% if afi_config.filter_list.import is vyos_defined %} neighbor {{ neighbor }} filter-list {{ afi_config.filter_list.import }} in -{% endif %} {% endif %} -{% if afi_config.maximum_prefix is defined and afi_config.maximum_prefix is not none %} +{% if afi_config.maximum_prefix is vyos_defined %} neighbor {{ neighbor }} maximum-prefix {{ afi_config.maximum_prefix }} {% endif %} -{% if afi_config.maximum_prefix_out is defined and afi_config.maximum_prefix_out is not none %} +{% if afi_config.maximum_prefix_out is vyos_defined %} neighbor {{ neighbor }} maximum-prefix-out {{ afi_config.maximum_prefix_out }} {% endif %} -{% if afi_config.nexthop_self is defined %} - neighbor {{ neighbor }} next-hop-self {{ 'force' if afi_config.nexthop_self.force is defined }} +{% if afi_config.nexthop_self is vyos_defined %} + neighbor {{ neighbor }} next-hop-self {{ 'force' if afi_config.nexthop_self.force is vyos_defined }} {% endif %} -{% if afi_config.route_server_client is defined %} +{% if afi_config.route_server_client is vyos_defined %} neighbor {{ neighbor }} route-server-client {% endif %} -{% if afi_config.route_map is defined and afi_config.route_map is not none %} -{% if afi_config.route_map.export is defined and afi_config.route_map.export is not none %} +{% if afi_config.route_map.export is vyos_defined %} neighbor {{ neighbor }} route-map {{ afi_config.route_map.export }} out -{% endif %} -{% if afi_config.route_map.import is defined and afi_config.route_map.import is not none %} +{% endif %} +{% if afi_config.route_map.import is vyos_defined %} neighbor {{ neighbor }} route-map {{ afi_config.route_map.import }} in -{% endif %} {% endif %} -{% if afi_config.prefix_list is defined and afi_config.prefix_list is not none %} -{% if afi_config.prefix_list.export is defined and afi_config.prefix_list.export is not none %} +{% if afi_config.prefix_list.export is vyos_defined %} neighbor {{ neighbor }} prefix-list {{ afi_config.prefix_list.export }} out -{% endif %} -{% if afi_config.prefix_list.import is defined and afi_config.prefix_list.import is not none %} +{% endif %} +{% if afi_config.prefix_list.import is vyos_defined %} neighbor {{ neighbor }} prefix-list {{ afi_config.prefix_list.import }} in -{% endif %} {% endif %} -{% if afi_config.soft_reconfiguration is defined and afi_config.soft_reconfiguration.inbound is defined %} +{% if afi_config.soft_reconfiguration.inbound is vyos_defined %} neighbor {{ neighbor }} soft-reconfiguration inbound {% endif %} -{% if afi_config.unsuppress_map is defined and afi_config.unsuppress_map is not none %} +{% if afi_config.unsuppress_map is vyos_defined %} neighbor {{ neighbor }} unsuppress-map {{ afi_config.unsuppress_map }} {% endif %} -{% if afi_config.disable_send_community is defined and afi_config.disable_send_community.extended is defined %} +{% if afi_config.disable_send_community.extended is vyos_defined %} no neighbor {{ neighbor }} send-community extended {% endif %} -{% if afi_config.disable_send_community is defined and afi_config.disable_send_community.standard is defined %} +{% if afi_config.disable_send_community.standard is vyos_defined %} no neighbor {{ neighbor }} send-community standard {% endif %} neighbor {{ neighbor }} activate @@ -241,8 +233,8 @@ {% endif %} {% endmacro %} ! -router bgp {{ local_as }} {{ 'vrf ' ~ vrf if vrf is defined and vrf is not none }} -{% if parameters is defined and parameters.ebgp_requires_policy is defined %} +router bgp {{ local_as }} {{ 'vrf ' ~ vrf if vrf is vyos_defined }} +{% if parameters.ebgp_requires_policy is vyos_defined %} bgp ebgp-requires-policy {% else %} no bgp ebgp-requires-policy @@ -251,7 +243,7 @@ router bgp {{ local_as }} {{ 'vrf ' ~ vrf if vrf is defined and vrf is not none no bgp default ipv4-unicast {# Workaround for T2100 until we have decided about a migration script #} no bgp network import-check -{% if address_family is defined and address_family is not none %} +{% if address_family is vyos_defined %} {% for afi, afi_config in address_family.items() %} ! {% if afi == 'ipv4_unicast' %} @@ -276,25 +268,25 @@ router bgp {{ local_as }} {{ 'vrf ' ~ vrf if vrf is defined and vrf is not none address-family ipv6 flowspec {% elif afi == 'l2vpn_evpn' %} address-family l2vpn evpn -{% if afi_config.rd is defined and afi_config.rd is not none %} +{% if afi_config.rd is vyos_defined %} rd {{ afi_config.rd }} {% endif %} {% endif %} -{% if afi_config.aggregate_address is defined and afi_config.aggregate_address is not none %} +{% if afi_config.aggregate_address is vyos_defined %} {% for aggregate, aggregate_config in afi_config.aggregate_address.items() %} - aggregate-address {{ aggregate }}{{ ' as-set' if aggregate_config.as_set is defined }}{{ ' summary-only' if aggregate_config.summary_only is defined }} -{% if aggregate_config.route_map is defined and aggregate_config.route_map is not none %} + aggregate-address {{ aggregate }}{{ ' as-set' if aggregate_config.as_set is vyos_defined }}{{ ' summary-only' if aggregate_config.summary_only is vyos_defined }} +{% if aggregate_config.route_map is vyos_defined %} aggregate-address {{ aggregate }} route-map {{ aggregate_config.route_map }} {% endif %} {% endfor %} {% endif %} -{% if afi_config.maximum_paths is defined and afi_config.maximum_paths.ebgp is defined and afi_config.maximum_paths.ebgp is not none %} +{% if afi_config.maximum_paths.ebgp is vyos_defined %} maximum-paths {{ afi_config.maximum_paths.ebgp }} {% endif %} -{% if afi_config.maximum_paths is defined and afi_config.maximum_paths.ibgp is defined and afi_config.maximum_paths.ibgp is not none %} +{% if afi_config.maximum_paths.ibgp is vyos_defined %} maximum-paths ibgp {{ afi_config.maximum_paths.ibgp }} {% endif %} -{% if afi_config.redistribute is defined and afi_config.redistribute is not none %} +{% if afi_config.redistribute is vyos_defined %} {% for protocol in afi_config.redistribute %} {% if protocol == 'table' %} redistribute table {{ afi_config.redistribute[protocol].table }} @@ -303,135 +295,123 @@ router bgp {{ local_as }} {{ 'vrf ' ~ vrf if vrf is defined and vrf is not none {% if protocol == 'ospfv3' %} {% set redistribution_protocol = 'ospf6' %} {% endif %} - redistribute {{ redistribution_protocol }}{% if afi_config.redistribute[protocol].metric is defined %} metric {{ afi_config.redistribute[protocol].metric }}{% endif %}{% if afi_config.redistribute[protocol].route_map is defined %} route-map {{ afi_config.redistribute[protocol].route_map }}{% endif %} + redistribute {{ redistribution_protocol }}{% if afi_config.redistribute[protocol].metric is vyos_defined %} metric {{ afi_config.redistribute[protocol].metric }}{% endif %}{% if afi_config.redistribute[protocol].route_map is vyos_defined %} route-map {{ afi_config.redistribute[protocol].route_map }}{% endif %} {####### we need this blank line!! #######} {% endif %} {% endfor %} {% endif %} -{% if afi_config.network is defined and afi_config.network is not none %} +{% if afi_config.network is vyos_defined %} {% for network in afi_config.network %} - network {{ network }}{% if afi_config.network[network].route_map is defined %} route-map {{ afi_config.network[network].route_map }}{% endif %}{% if afi_config.network[network].backdoor is defined %} backdoor{% endif %}{% if afi_config.network[network].rd is defined and afi_config.network[network].label is defined%} rd {{ afi_config.network[network].rd }} label {{ afi_config.network[network].label }}{% endif %} + network {{ network }}{% if afi_config.network[network].route_map is vyos_defined %} route-map {{ afi_config.network[network].route_map }}{% endif %}{% if afi_config.network[network].backdoor is vyos_defined %} backdoor{% endif %}{% if afi_config.network[network].rd is vyos_defined and afi_config.network[network].label is vyos_defined %} rd {{ afi_config.network[network].rd }} label {{ afi_config.network[network].label }}{% endif %} {####### we need this blank line!! #######} {% endfor %} {% endif %} -{% if afi_config.advertise is defined and afi_config.advertise is not none %} +{% if afi_config.advertise is vyos_defined %} {% for adv_afi, adv_afi_config in afi_config.advertise.items() %} -{% if adv_afi_config.unicast is defined and adv_afi_config.unicast is not none %} - advertise {{ adv_afi }} unicast {{ 'route-map ' ~ adv_afi_config.unicast.route_map if adv_afi_config.unicast.route_map is defined }} +{% if adv_afi_config.unicast is vyos_defined %} + advertise {{ adv_afi }} unicast {{ 'route-map ' ~ adv_afi_config.unicast.route_map if adv_afi_config.unicast.route_map is vyos_defined }} {% endif %} {% endfor %} {% endif %} -{% if afi_config.distance is defined and afi_config.distance is not none %} -{% if afi_config.distance is defined and afi_config.distance.external is defined and afi_config.distance.internal is defined and afi_config.distance.local is defined %} +{% if afi_config.distance.external is vyos_defined and afi_config.distance.internal is vyos_defined and afi_config.distance.local is vyos_defined %} distance bgp {{ afi_config.distance.external }} {{ afi_config.distance.internal }} {{ afi_config.distance.local }} -{% endif %} -{% if afi_config.distance.prefix is defined and afi_config.distance.prefix is not none %} -{% for prefix in afi_config.distance.prefix %} +{% endif %} +{% if afi_config.distance.prefix is vyos_defined %} +{% for prefix in afi_config.distance.prefix %} distance {{ afi_config.distance.prefix[prefix].distance }} {{ prefix }} -{% endfor %} -{% endif %} +{% endfor %} {% endif %} -{% if afi_config.export is defined and afi_config.export.vpn is defined %} +{% if afi_config.export.vpn is vyos_defined %} export vpn {% endif %} -{% if afi_config.import is defined and afi_config.import is not none %} -{% if afi_config.import.vpn is defined %} +{% if afi_config.import.vpn is vyos_defined %} import vpn -{% endif %} -{% if afi_config.import.vrf is defined and afi_config.import.vrf is not none %} -{% for vrf in afi_config.import.vrf %} +{% endif %} +{% if afi_config.import.vrf is vyos_defined %} +{% for vrf in afi_config.import.vrf %} import vrf {{ vrf }} -{% endfor %} -{% endif %} +{% endfor %} {% endif %} -{% if afi_config.label is defined and afi_config.label.vpn is defined and afi_config.label.vpn.export is defined and afi_config.label.vpn.export is not none %} +{% if afi_config.label.vpn.export is vyos_defined %} label vpn export {{ afi_config.label.vpn.export }} {% endif %} -{% if afi_config.local_install is defined and afi_config.local_install is not none %} +{% if afi_config.local_install is vyos_defined %} {% for interface in afi_config.local_install.interface %} local-install {{ interface }} {% endfor %} {% endif %} -{% if afi_config.advertise_all_vni is defined %} +{% if afi_config.advertise_all_vni is vyos_defined %} advertise-all-vni {% endif %} -{% if afi_config.advertise_default_gw is defined %} +{% if afi_config.advertise_default_gw is vyos_defined %} advertise-default-gw {% endif %} -{% if afi_config.advertise_pip is defined and afi_config.advertise_pip is not none %} +{% if afi_config.advertise_pip is vyos_defined %} advertise-pip ip {{ afi_config.advertise_pip }} {% endif %} -{% if afi_config.advertise_svi_ip is defined %} +{% if afi_config.advertise_svi_ip is vyos_defined %} advertise-svi-ip {% endif %} -{% if afi_config.rt_auto_derive is defined %} +{% if afi_config.rt_auto_derive is vyos_defined %} autort rfc8365-compatible {% endif %} -{% if afi_config.flooding is defined and afi_config.flooding.disable is defined %} +{% if afi_config.flooding.disable is vyos_defined %} flooding disable {% endif %} -{% if afi_config.flooding is defined and afi_config.flooding.head_end_replication is defined %} +{% if afi_config.flooding.head_end_replication is vyos_defined %} flooding head-end-replication {% endif %} -{% if afi_config.rd is defined and afi_config.rd.vpn is defined and afi_config.rd.vpn.export is defined %} +{% if afi_config.rd.vpn.export is vyos_defined %} rd vpn export {{ afi_config.rd.vpn.export }} {% endif %} -{% if afi_config.route_target is defined and afi_config.route_target is not none %} -{% if afi_config.route_target.vpn is defined and afi_config.route_target.vpn is not none %} -{% if afi_config.route_target.vpn.both is defined and afi_config.route_target.vpn.both is not none %} +{% if afi_config.route_target.vpn.both is vyos_defined %} route-target vpn both {{ afi_config.route_target.vpn.both }} -{% else %} -{% if afi_config.route_target.vpn.export is defined and afi_config.route_target.vpn.export is not none %} +{% else %} +{% if afi_config.route_target.vpn.export is vyos_defined %} route-target vpn export {{ afi_config.route_target.vpn.export }} -{% endif %} -{% if afi_config.route_target.vpn.import is defined and afi_config.route_target.vpn.import is not none %} +{% endif %} +{% if afi_config.route_target.vpn.import is vyos_defined %} route-target vpn import {{ afi_config.route_target.vpn.import }} -{% endif %} -{% endif %} {% endif %} -{% if afi_config.route_target.both is defined and afi_config.route_target.both is not none %} +{% endif %} +{% if afi_config.route_target.both is vyos_defined %} route-target both {{ afi_config.route_target.both }} -{% else %} -{% if afi_config.route_target.export is defined and afi_config.route_target.export is not none %} +{% else %} +{% if afi_config.route_target.export is vyos_defined %} route-target export {{ afi_config.route_target.export }} -{% endif %} -{% if afi_config.route_target.import is defined and afi_config.route_target.import is not none %} +{% endif %} +{% if afi_config.route_target.import is vyos_defined %} route-target import {{ afi_config.route_target.import }} -{% endif %} {% endif %} {% endif %} -{% if afi_config.route_map is defined and afi_config.route_map.vpn is defined and afi_config.route_map.vpn is not none %} -{% if afi_config.route_map.vpn.export is defined and afi_config.route_map.vpn.export is not none %} +{% if afi_config.route_map.vpn.export is vyos_defined %} route-map vpn export {{ afi_config.route_map.vpn.export }} -{% endif %} -{% if afi_config.route_map.vpn.import is defined and afi_config.route_map.vpn.import is not none %} +{% endif %} +{% if afi_config.route_map.vpn.import is vyos_defined %} route-map vpn import {{ afi_config.route_map.vpn.import }} -{% endif %} {% endif %} -{% if afi_config.vni is defined and afi_config.vni is not none %} +{% if afi_config.vni is vyos_defined %} {% for vni, vni_config in afi_config.vni.items() %} vni {{ vni }} -{% if vni_config.advertise_default_gw is defined %} +{% if vni_config.advertise_default_gw is vyos_defined %} advertise-default-gw {% endif %} -{% if vni_config.advertise_svi_ip is defined %} +{% if vni_config.advertise_svi_ip is vyos_defined %} advertise-svi-ip {% endif %} -{% if vni_config.rd is defined and vni_config.rd is not none %} +{% if vni_config.rd is vyos_defined %} rd {{ vni_config.rd }} {% endif %} -{% if vni_config.route_target is defined and vni_config.route_target is not none %} -{% if vni_config.route_target.both is defined and vni_config.route_target.both is not none %} +{% if vni_config.route_target.both is vyos_defined %} route-target both {{ vni_config.route_target.both }} -{% endif %} -{% if vni_config.route_target.export is defined and vni_config.route_target.export is not none %} +{% endif %} +{% if vni_config.route_target.export is vyos_defined %} route-target export {{ vni_config.route_target.export }} -{% endif %} -{% if vni_config.route_target.import is defined and vni_config.route_target.import is not none %} +{% endif %} +{% if vni_config.route_target.import is vyos_defined %} route-target import {{ vni_config.route_target.import }} -{% endif %} {% endif %} exit-vni {% endfor %} @@ -440,128 +420,116 @@ router bgp {{ local_as }} {{ 'vrf ' ~ vrf if vrf is defined and vrf is not none {% endfor %} {% endif %} ! -{% if peer_group is defined and peer_group is not none %} +{% if peer_group is vyos_defined %} {% for peer, config in peer_group.items() %} {{ bgp_neighbor(peer, config, true) }} {% endfor %} {% endif %} ! -{% if neighbor is defined and neighbor is not none %} +{% if neighbor is vyos_defined %} {% for peer, config in neighbor.items() %} {{ bgp_neighbor(peer, config) }} {% endfor %} {% endif %} ! -{% if listen is defined %} -{% if listen.limit is defined and listen.limit is not none %} +{% if listen.limit is vyos_defined %} bgp listen limit {{ listen.limit }} -{% endif %} +{% endif %} +{% if listen.range is vyos_defined %} {% for prefix, options in listen.range.items() %} -{% if options.peer_group is defined and options.peer_group is not none %} +{% if options.peer_group is vyos_defined %} bgp listen range {{ prefix }} peer-group {{ options.peer_group }} {% endif %} {% endfor %} {% endif %} -{% if parameters is defined %} -{% if parameters.always_compare_med is defined %} +{% if parameters.always_compare_med is vyos_defined %} bgp always-compare-med -{% endif %} -{% if parameters.bestpath is defined and parameters.bestpath is not none %} -{% if parameters.bestpath.as_path is defined and parameters.bestpath.as_path is not none %} -{% for option in parameters.bestpath.as_path %} +{% endif %} +{% if parameters.bestpath.as_path is vyos_defined %} +{% for option in parameters.bestpath.as_path %} {# replace is required for multipath-relax option #} bgp bestpath as-path {{ option|replace('_', '-') }} -{% endfor %} -{% endif %} -{% if parameters.bestpath.bandwidth is defined and parameters.bestpath.bandwidth is not none %} +{% endfor %} +{% endif %} +{% if parameters.bestpath.bandwidth is vyos_defined %} bgp bestpath bandwidth {{ parameters.bestpath.bandwidth }} -{% endif %} -{% if parameters.bestpath.compare_routerid is defined %} +{% endif %} +{% if parameters.bestpath.compare_routerid is vyos_defined %} bgp bestpath compare-routerid -{% endif %} -{% if parameters.bestpath.med is defined and parameters.bestpath.med is not none %} - bgp bestpath med {{ 'confed' if parameters.bestpath.med.confed is defined }} {{ 'missing-as-worst' if parameters.bestpath.med.missing_as_worst is defined }} -{% endif %} -{% endif %} -{% if parameters.cluster_id is defined and parameters.cluster_id is not none %} +{% endif %} +{% if parameters.bestpath.med is vyos_defined %} + bgp bestpath med {{ 'confed' if parameters.bestpath.med.confed is vyos_defined }} {{ 'missing-as-worst' if parameters.bestpath.med.missing_as_worst is vyos_defined }} +{% endif %} +{% if parameters.cluster_id is vyos_defined %} bgp cluster-id {{ parameters.cluster_id }} -{% endif %} -{% if parameters.conditional_advertisement is defined and parameters.conditional_advertisement is not none %} -{% if parameters.conditional_advertisement.timer is defined and parameters.conditional_advertisement.timer is not none %} +{% endif %} +{% if parameters.conditional_advertisement.timer is vyos_defined %} bgp conditional-advertisement timer {{ parameters.conditional_advertisement.timer }} -{% endif %} -{% endif %} -{% if parameters.confederation is defined and parameters.confederation is not none %} -{% if parameters.confederation.identifier is defined and parameters.confederation.identifier is not none %} +{% endif %} +{% if parameters.confederation.identifier is vyos_defined %} bgp confederation identifier {{ parameters.confederation.identifier }} -{% endif %} -{% if parameters.confederation.peers is defined and parameters.confederation.peers is not none %} +{% endif %} +{% if parameters.confederation.peers is vyos_defined %} bgp confederation peers {{ parameters.confederation.peers | join(' ') }} -{% endif %} -{% endif %} -{% if parameters.dampening is defined and parameters.dampening is defined and parameters.dampening.half_life is defined and parameters.dampening.half_life is not none %} +{% endif %} +{% if parameters.dampening.half_life is vyos_defined %} {# Doesn't work in current FRR configuration; vtysh (bgp dampening 16 751 2001 61) #} - bgp dampening {{ parameters.dampening.half_life }} {{ parameters.dampening.re_use if parameters.dampening.re_use is defined }} {{ parameters.dampening.start_suppress_time if parameters.dampening.start_suppress_time is defined }} {{ parameters.dampening.max_suppress_time if parameters.dampening.max_suppress_time is defined }} -{% endif %} -{% if parameters.default is defined and parameters.default is not none %} -{% if parameters.default.local_pref is defined and parameters.default.local_pref is not none %} + bgp dampening {{ parameters.dampening.half_life }} {{ parameters.dampening.re_use if parameters.dampening.re_use is vyos_defined }} {{ parameters.dampening.start_suppress_time if parameters.dampening.start_suppress_time is vyos_defined }} {{ parameters.dampening.max_suppress_time if parameters.dampening.max_suppress_time is vyos_defined }} +{% endif %} +{% if parameters.default.local_pref is vyos_defined %} bgp default local-preference {{ parameters.default.local_pref }} -{% endif %} -{% endif %} -{% if parameters.deterministic_med is defined %} +{% endif %} +{% if parameters.deterministic_med is vyos_defined %} bgp deterministic-med -{% endif %} -{% if parameters.distance is defined and parameters.distance is not none %} -{% if parameters.distance.global is defined and parameters.distance.global.external is defined and parameters.distance.global.internal is defined and parameters.distance.global.local is defined %} +{% endif %} +{% if parameters.distance.global.external is vyos_defined and parameters.distance.global.internal is vyos_defined and parameters.distance.global.local is vyos_defined %} distance bgp {{ parameters.distance.global.external }} {{ parameters.distance.global.internal }} {{ parameters.distance.global.local }} -{% endif %} -{% if parameters.distance.prefix is defined and parameters.distance.prefix is not none %} -{% for prefix in parameters.distance.prefix %} +{% endif %} +{% if parameters.distance.prefix is vyos_defined %} +{% for prefix in parameters.distance.prefix %} distance {{ parameters.distance.prefix[prefix].distance }} {{ prefix }} -{% endfor %} -{% endif %} -{% endif %} -{% if parameters.fast_convergence is defined %} +{% endfor %} +{% endif %} +{% if parameters.fast_convergence is vyos_defined %} bgp fast-convergence -{% endif %} -{% if parameters.graceful_restart is defined %} - bgp graceful-restart {{ 'stalepath-time ' ~ parameters.graceful_restart.stalepath_time if parameters.graceful_restart.stalepath_time is defined }} -{% endif %} -{% if parameters.graceful_shutdown is defined %} +{% endif %} +{% if parameters.graceful_restart is vyos_defined %} + bgp graceful-restart {{ 'stalepath-time ' ~ parameters.graceful_restart.stalepath_time if parameters.graceful_restart.stalepath_time is vyos_defined }} +{% endif %} +{% if parameters.graceful_shutdown is vyos_defined %} bgp graceful-shutdown -{% endif %} -{% if parameters.log_neighbor_changes is defined %} +{% endif %} +{% if parameters.log_neighbor_changes is vyos_defined %} bgp log-neighbor-changes -{% endif %} -{% if parameters.minimum_holdtime is defined and parameters.minimum_holdtime is not none %} +{% endif %} +{% if parameters.minimum_holdtime is vyos_defined %} bgp minimum-holdtime {{ parameters.minimum_holdtime }} -{% endif %} -{% if parameters.network_import_check is defined %} +{% endif %} +{% if parameters.network_import_check is vyos_defined %} bgp network import-check -{% endif %} -{% if parameters.no_client_to_client_reflection is defined %} +{% endif %} +{% if parameters.no_client_to_client_reflection is vyos_defined %} no bgp client-to-client reflection -{% endif %} -{% if parameters.no_fast_external_failover is defined %} +{% endif %} +{% if parameters.no_fast_external_failover is vyos_defined %} no bgp fast-external-failover -{% endif %} -{% if parameters.no_suppress_duplicates is defined %} +{% endif %} +{% if parameters.no_suppress_duplicates is vyos_defined %} no bgp suppress-duplicates -{% endif %} -{% if parameters.reject_as_sets is defined %} +{% endif %} +{% if parameters.reject_as_sets is vyos_defined %} bgp reject-as-sets -{% endif %} -{% if parameters.router_id is defined and parameters.router_id is not none %} +{% endif %} +{% if parameters.router_id is vyos_defined and parameters.router_id is not none %} bgp router-id {{ parameters.router_id }} -{% endif %} -{% if parameters.shutdown is defined %} +{% endif %} +{% if parameters.shutdown is vyos_defined %} bgp shutdown -{% endif %} -{% if parameters.suppress_fib_pending is defined %} +{% endif %} +{% if parameters.suppress_fib_pending is vyos_defined %} bgp suppress-fib-pending -{% endif %} {% endif %} -{% if timers is defined and timers.keepalive is defined and timers.holdtime is defined %} +{% if timers.keepalive is vyos_defined and timers.holdtime is vyos_defined %} timers bgp {{ timers.keepalive }} {{ timers.holdtime }} {% endif %} exit -- cgit v1.2.3 From 15c94a8706622927850eba8c22fcff2df32978b4 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 1 Apr 2022 22:30:53 +0200 Subject: wwan: T4324: is_wwan_connected() must verify if ModemManager is running --- python/vyos/util.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/python/vyos/util.py b/python/vyos/util.py index f3f323c34..0bf6b699e 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -989,6 +989,11 @@ def is_wwan_connected(interface): if not interface.startswith('wwan'): raise ValueError(f'Specified interface "{interface}" is not a WWAN interface') + # ModemManager is required for connection(s) - if service is not running, + # there won't be any connection at all! + if not is_systemd_service_active('ModemManager.service'): + return False + modem = interface.lstrip('wwan') tmp = cmd(f'mmcli --modem {modem} --output-json') -- cgit v1.2.3 From 5faeacd1111a83e5859b98ccc4193cb6017cdba8 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 2 Apr 2022 14:38:11 +0200 Subject: wwan: T4324: cronjob is setup via interfaces-wwan.py - drop dedicated cron file --- debian/vyos-1x.install | 1 - src/etc/cron.d/check-wwan | 1 - 2 files changed, 2 deletions(-) delete mode 100644 src/etc/cron.d/check-wwan diff --git a/debian/vyos-1x.install b/debian/vyos-1x.install index 63dff43a5..493c896eb 100644 --- a/debian/vyos-1x.install +++ b/debian/vyos-1x.install @@ -1,4 +1,3 @@ -etc/cron.d etc/cron.hourly etc/dhcp etc/ipsec.d diff --git a/src/etc/cron.d/check-wwan b/src/etc/cron.d/check-wwan deleted file mode 100644 index 28190776f..000000000 --- a/src/etc/cron.d/check-wwan +++ /dev/null @@ -1 +0,0 @@ -*/5 * * * * root /usr/libexec/vyos/vyos-check-wwan.py -- cgit v1.2.3 From c58a03ad76b2a0680a33fcfec3ab7a3545374abb Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 2 Apr 2022 14:39:09 +0200 Subject: wwan: T4324: properly start/stop ModemManager and cron helper on interface add/removal --- src/conf_mode/interfaces-wwan.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/conf_mode/interfaces-wwan.py b/src/conf_mode/interfaces-wwan.py index 367a50e82..ec01d3cc5 100755 --- a/src/conf_mode/interfaces-wwan.py +++ b/src/conf_mode/interfaces-wwan.py @@ -37,7 +37,7 @@ from vyos import airbag airbag.enable() service_name = 'ModemManager.service' -cron_script = '/etc/cron.d/wwan' +cron_script = '/etc/cron.d/vyos-wwan' def get_config(config=None): """ @@ -58,8 +58,8 @@ def get_config(config=None): get_first_key=True, no_tag_node_value_mangle=True) - # This if-clause is just to be sure - it will always evaluate to true ifname = wwan['ifname'] + # This if-clause is just to be sure - it will always evaluate to true if ifname in wwan['other_interfaces']: del wwan['other_interfaces'][ifname] if len(wwan['other_interfaces']) == 0: @@ -84,13 +84,25 @@ def verify(wwan): def generate(wwan): if 'deleted' in wwan: + # We are the last WWAN interface - there are no other ones remaining + # thus the cronjob needs to go away, too + if 'other_interfaces' not in wwan: + if os.path.exists(cron_script): + os.unlink(cron_script) return None + # Install cron triggered helper script to re-dial WWAN interfaces on + # disconnect - e.g. happens during RF signal loss. The script watches every + # WWAN interface - so there is only one instance. if not os.path.exists(cron_script): write_file(cron_script, '*/5 * * * * root /usr/libexec/vyos/vyos-check-wwan.py') + return None def apply(wwan): + # ModemManager is required to dial WWAN connections - one instance is + # required to serve all modems. Activate ModemManager on first invocation + # of any WWAN interface. if not is_systemd_service_active(service_name): cmd(f'systemctl start {service_name}') @@ -113,7 +125,8 @@ def apply(wwan): if 'deleted' in wwan or 'disable' in wwan: w.remove() - # There are no other WWAN interfaces - stop the daemon + # We are the last WWAN interface - there are no other WWAN interfaces + # remaining, thus we can stop ModemManager and free resources. if 'other_interfaces' not in wwan: cmd(f'systemctl stop {service_name}') # Clean CRON helper script which is used for to re-connect when @@ -141,9 +154,6 @@ def apply(wwan): call(command, stdout=DEVNULL) w.update(wwan) - if 'other_interfaces' not in wwan and 'deleted' in wwan: - cmd(f'systemctl start {service_name}') - return None if __name__ == '__main__': -- cgit v1.2.3 From cf8a8c0def7e56f1cc47c6c786c72671c32e060b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 2 Apr 2022 14:43:32 +0200 Subject: udp-broadcast-relay: T4333: migrate to new vyos_defined Jinja2 test --- data/templates/bcast-relay/udp-broadcast-relay.tmpl | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/data/templates/bcast-relay/udp-broadcast-relay.tmpl b/data/templates/bcast-relay/udp-broadcast-relay.tmpl index 73e9acad4..7b2b9b1a2 100644 --- a/data/templates/bcast-relay/udp-broadcast-relay.tmpl +++ b/data/templates/bcast-relay/udp-broadcast-relay.tmpl @@ -1,7 +1,5 @@ ### Autogenerated by bcast_relay.py ### # UDP broadcast relay configuration for instance {{ id }} -{% if description %} -# Comment: {{ description }} -{% endif %} -DAEMON_ARGS="{{ '-s ' + address if address is defined }} {{ instance }} {{ port }} {{ interface | join(' ') }}" +{{ '# ' ~ description if description is vyos_defined }} +DAEMON_ARGS="{{ '-s ' ~ address if address is defined }} {{ instance }} {{ port }} {{ interface | join(' ') }}" -- cgit v1.2.3 From d49cd1085f8f2725cf86fd1dc731c1d8a34db796 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 3 Apr 2022 09:16:33 +0200 Subject: conntrack: T4333: migrate to new vyos_defined Jinja2 test --- data/templates/conntrack/nftables-ct.tmpl | 32 ++++++++++++++----------------- data/templates/conntrack/sysctl.conf.tmpl | 2 +- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/data/templates/conntrack/nftables-ct.tmpl b/data/templates/conntrack/nftables-ct.tmpl index c0fe5297d..cebc1a54e 100644 --- a/data/templates/conntrack/nftables-ct.tmpl +++ b/data/templates/conntrack/nftables-ct.tmpl @@ -10,31 +10,27 @@ flush chain raw {{ nft_ct_timeout_name }} table raw { chain {{ nft_ct_ignore_name }} { -{% if ignore is defined and ignore.rule is defined and ignore.rule is not none %} +{% if ignore.rule is vyos_defined %} {% for rule, rule_config in ignore.rule.items() %} # rule-{{ rule }} {{ '- ' ~ rule_config.description if rule_config.description is defined and rule_config.description is not none }} {% set nft_command = '' %} -{% if rule_config.inbound_interface is defined and rule_config.inbound_interface is not none %} +{% if rule_config.inbound_interface is vyos_defined %} {% set nft_command = nft_command ~ ' iifname ' ~ rule_config.inbound_interface %} {% endif %} -{% if rule_config.protocol is defined and rule_config.protocol is not none %} +{% if rule_config.protocol is vyos_defined %} {% set nft_command = nft_command ~ ' ip protocol ' ~ rule_config.protocol %} {% endif %} -{% if rule_config.destination is defined and rule_config.destination is not none %} -{% if rule_config.destination.address is defined and rule_config.destination.address is not none %} -{% set nft_command = nft_command ~ ' ip daddr ' ~ rule_config.destination.address %} -{% endif %} -{% if rule_config.destination.port is defined and rule_config.destination.port is not none %} -{% set nft_command = nft_command ~ ' ' ~ rule_config.protocol ~ ' dport { ' ~ rule_config.destination.port ~ ' }' %} -{% endif %} +{% if rule_config.destination.address is vyos_defined %} +{% set nft_command = nft_command ~ ' ip daddr ' ~ rule_config.destination.address %} {% endif %} -{% if rule_config.source is defined and rule_config.source is not none %} -{% if rule_config.source.address is defined and rule_config.source.address is not none %} -{% set nft_command = nft_command ~ ' ip saddr ' ~ rule_config.source.address %} -{% endif %} -{% if rule_config.source.port is defined and rule_config.source.port is not none %} -{% set nft_command = nft_command ~ ' ' ~ rule_config.protocol ~ ' sport { ' ~ rule_config.source.port ~ ' }' %} -{% endif %} +{% if rule_config.destination.port is vyos_defined %} +{% set nft_command = nft_command ~ ' ' ~ rule_config.protocol ~ ' dport { ' ~ rule_config.destination.port ~ ' }' %} +{% endif %} +{% if rule_config.source.address is vyos_defined %} +{% set nft_command = nft_command ~ ' ip saddr ' ~ rule_config.source.address %} +{% endif %} +{% if rule_config.source.port is vyos_defined %} +{% set nft_command = nft_command ~ ' ' ~ rule_config.protocol ~ ' sport { ' ~ rule_config.source.port ~ ' }' %} {% endif %} {{ nft_command }} counter notrack comment ignore-{{ rule }} {% endfor %} @@ -42,7 +38,7 @@ table raw { return } chain {{ nft_ct_timeout_name }} { -{% if timeout is defined and timeout.custom is defined and timeout.custom.rule is defined and timeout.custom.rule is not none %} +{% if timeout.custom.rule is vyos_defined %} {% for rule, rule_config in timeout.custom.rule.items() %} # rule-{{ rule }} {{ '- ' ~ rule_config.description if rule_config.description is defined and rule_config.description is not none }} {% endfor %} diff --git a/data/templates/conntrack/sysctl.conf.tmpl b/data/templates/conntrack/sysctl.conf.tmpl index 9e97c3286..075402c04 100644 --- a/data/templates/conntrack/sysctl.conf.tmpl +++ b/data/templates/conntrack/sysctl.conf.tmpl @@ -6,7 +6,7 @@ net.netfilter.nf_conntrack_max = {{ table_size }} net.ipv4.tcp_max_syn_backlog = {{ tcp.half_open_connections }} -net.netfilter.nf_conntrack_tcp_loose = {{ '1' if tcp.loose == 'enable' else '0' }} +net.netfilter.nf_conntrack_tcp_loose = {{ '1' if tcp.loose is vyos_defined('enable') else '0' }} net.netfilter.nf_conntrack_tcp_max_retrans = {{ tcp.max_retrans }} net.netfilter.nf_conntrack_icmp_timeout = {{ timeout.icmp }} -- cgit v1.2.3 From 9cde173dcfab281b0b3ab9f5d55c19d4c6d35d45 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 3 Apr 2022 11:34:44 +0200 Subject: bfd: T4333: migrate to new vyos_defined Jinja2 test --- data/templates/frr/bfdd.frr.tmpl | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/data/templates/frr/bfdd.frr.tmpl b/data/templates/frr/bfdd.frr.tmpl index 439f79d67..ac55d4634 100644 --- a/data/templates/frr/bfdd.frr.tmpl +++ b/data/templates/frr/bfdd.frr.tmpl @@ -1,22 +1,22 @@ -{% if profile is defined or peer is defined %} +{% if profile is vyos_defined or peer is vyos_defined %} bfd -{% if profile is defined and profile is not none %} +{% if profile is vyos_defined %} {% for profile_name, profile_config in profile.items() %} profile {{ profile_name }} detect-multiplier {{ profile_config.interval.multiplier }} receive-interval {{ profile_config.interval.receive }} transmit-interval {{ profile_config.interval.transmit }} -{% if profile_config.interval.echo_interval is defined and profile_config.interval.echo_interval is not none %} +{% if profile_config.interval.echo_interval is vyos_defined %} echo transmit-interval {{ profile_config.interval.echo_interval }} echo receive-interval {{ profile_config.interval.echo_interval }} {% endif %} -{% if profile_config.echo_mode is defined %} +{% if profile_config.echo_mode is vyos_defined %} echo-mode {% endif %} -{% if profile_config.passive is defined %} +{% if profile_config.passive is vyos_defined %} passive-mode {% endif %} -{% if profile_config.shutdown is defined %} +{% if profile_config.shutdown is vyos_defined %} shutdown {% else %} no shutdown @@ -25,26 +25,26 @@ bfd ! {% endfor %} {% endif %} -{% if peer is defined and peer is not none %} +{% if peer is vyos_defined %} {% for peer_name, peer_config in peer.items() %} - peer {{ peer_name }}{{ ' multihop' if peer_config.multihop is defined }}{{ ' local-address ' + peer_config.source.address if peer_config.source is defined and peer_config.source.address is defined }}{{ ' interface ' + peer_config.source.interface if peer_config.source is defined and peer_config.source.interface is defined }} {{ ' vrf ' + peer_config.vrf if peer_config.vrf is defined and peer_config.vrf is not none }} + peer {{ peer_name }}{{ ' multihop' if peer_config.multihop is vyos_defined }}{{ ' local-address ' + peer_config.source.address if peer_config.source.address is vyos_defined }}{{ ' interface ' + peer_config.source.interface if peer_config.source.interface is vyos_defined }} {{ ' vrf ' + peer_config.vrf if peer_config.vrf is vyos_defined }} detect-multiplier {{ peer_config.interval.multiplier }} receive-interval {{ peer_config.interval.receive }} transmit-interval {{ peer_config.interval.transmit }} -{% if peer_config.interval.echo_interval is defined and peer_config.interval.echo_interval is not none %} +{% if peer_config.interval.echo_interval is vyos_defined %} echo transmit-interval {{ peer_config.interval.echo_interval }} echo receive-interval {{ peer_config.interval.echo_interval }} {% endif %} -{% if peer_config.echo_mode is defined %} +{% if peer_config.echo_mode is vyos_defined %} echo-mode {% endif %} -{% if peer_config.passive is defined %} +{% if peer_config.passive is vyos_defined %} passive-mode {% endif %} -{% if peer_config.profile is defined and peer_config.profile is not none %} +{% if peer_config.profile is vyos_defined %} profile {{ peer_config.profile }} {% endif %} -{% if peer_config.shutdown is defined %} +{% if peer_config.shutdown is vyos_defined %} shutdown {% else %} no shutdown -- cgit v1.2.3 From 6f5e5119330e7b75ed5b20bbf337ec9f4a54ec04 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 3 Apr 2022 12:11:11 +0200 Subject: isis: T4333: migrate to new vyos_defined Jinja2 test --- data/templates/frr/isisd.frr.tmpl | 166 ++++++++++++++++++-------------------- 1 file changed, 78 insertions(+), 88 deletions(-) diff --git a/data/templates/frr/isisd.frr.tmpl b/data/templates/frr/isisd.frr.tmpl index c68dda443..2bf3a3b8a 100644 --- a/data/templates/frr/isisd.frr.tmpl +++ b/data/templates/frr/isisd.frr.tmpl @@ -1,46 +1,46 @@ ! -{% if interface is defined and interface is not none %} +{% if interface is vyos_defined %} {% for iface, iface_config in interface.items() %} interface {{ iface }} ip router isis VyOS ipv6 router isis VyOS -{% if iface_config.bfd is defined %} +{% if iface_config.bfd is vyos_defined %} isis bfd -{% if iface_config.bfd.profile is defined and iface_config.bfd.profile is not none %} +{% if iface_config.bfd.profile is vyos_defined %} isis bfd profile {{ iface_config.bfd.profile }} {% endif %} {% endif %} -{% if iface_config.network is defined and iface_config.network.point_to_point is defined %} +{% if iface_config.network.point_to_point is vyos_defined %} isis network point-to-point {% endif %} -{% if iface_config.circuit_type is defined %} +{% if iface_config.circuit_type is vyos_defined %} isis circuit-type {{ iface_config.circuit_type }} {% endif %} -{% if iface_config.hello_interval is defined and iface_config.hello_interval is not none %} +{% if iface_config.hello_interval is vyos_defined %} isis hello-interval {{ iface_config.hello_interval }} {% endif %} -{% if iface_config.hello_multiplier is defined and iface_config.hello_multiplier is not none %} +{% if iface_config.hello_multiplier is vyos_defined %} isis hello-multiplier {{ iface_config.hello_multiplier }} {% endif %} -{% if iface_config.hello_padding is defined %} +{% if iface_config.hello_padding is vyos_defined %} isis hello padding {% endif %} -{% if iface_config.metric is defined and iface_config.metric is not none %} +{% if iface_config.metric is vyos_defined %} isis metric {{ iface_config.metric }} {% endif %} -{% if iface_config.passive is defined %} +{% if iface_config.passive is vyos_defined %} isis passive {% endif %} -{% if iface_config.password is defined and iface_config.password.plaintext_password is defined and iface_config.password.plaintext_password is not none %} +{% if iface_config.password.plaintext_password is vyos_defined %} isis password clear {{ iface_config.password.plaintext_password }} {% endif %} -{% if iface_config.priority is defined and iface_config.priority is not none %} +{% if iface_config.priority is vyos_defined %} isis priority {{ iface_config.priority }} {% endif %} -{% if iface_config.psnp_interval is defined and iface_config.psnp_interval is not none %} +{% if iface_config.psnp_interval is vyos_defined %} isis psnp-interval {{ iface_config.psnp_interval }} {% endif %} -{% if iface_config.no_three_way_handshake is defined %} +{% if iface_config.no_three_way_handshake is vyos_defined %} no isis three-way-handshake {% endif %} exit @@ -48,98 +48,94 @@ exit {% endfor %} {% endif %} ! -router isis VyOS {{ 'vrf ' + vrf if vrf is defined and vrf is not none }} +router isis VyOS {{ 'vrf ' + vrf if vrf is vyos_defined }} net {{ net }} -{% if dynamic_hostname is defined %} +{% if dynamic_hostname is vyos_defined %} hostname dynamic {% endif %} -{% if purge_originator is defined %} +{% if purge_originator is vyos_defined %} purge-originator {% endif %} -{% if set_attached_bit is defined %} +{% if set_attached_bit is vyos_defined %} set-attached-bit {% endif %} -{% if set_overload_bit is defined %} +{% if set_overload_bit is vyos_defined %} set-overload-bit {% endif %} -{% if domain_password is defined and domain_password is not none %} -{% if domain_password.md5 is defined and domain_password.md5 is not none %} +{% if domain_password.md5 is vyos_defined %} domain-password md5 {{ domain_password.plaintext_password }} -{% elif domain_password.plaintext_password is defined and domain_password.plaintext_password is not none %} +{% elif domain_password.plaintext_password is vyos_defined %} domain-password clear {{ domain_password.plaintext_password }} -{% endif %} {% endif %} -{% if log_adjacency_changes is defined %} +{% if log_adjacency_changes is vyos_defined %} log-adjacency-changes {% endif %} -{% if lsp_gen_interval is defined and lsp_gen_interval is not none %} +{% if lsp_gen_interval is vyos_defined %} lsp-gen-interval {{ lsp_gen_interval }} {% endif %} -{% if lsp_mtu is defined and lsp_mtu is not none %} +{% if lsp_mtu is vyos_defined %} lsp-mtu {{ lsp_mtu }} {% endif %} -{% if lsp_refresh_interval is defined and lsp_refresh_interval is not none %} +{% if lsp_refresh_interval is vyos_defined %} lsp-refresh-interval {{ lsp_refresh_interval }} {% endif %} -{% if max_lsp_lifetime is defined and max_lsp_lifetime is not none %} +{% if max_lsp_lifetime is vyos_defined %} max-lsp-lifetime {{ max_lsp_lifetime }} {% endif %} -{% if spf_interval is defined and spf_interval is not none %} +{% if spf_interval is vyos_defined %} spf-interval {{ spf_interval }} {% endif %} -{% if traffic_engineering is defined and traffic_engineering is not none %} -{% if traffic_engineering.enable is defined %} +{% if traffic_engineering.enable is vyos_defined %} mpls-te on -{% endif %} -{% if traffic_engineering.address is defined %} +{% endif %} +{% if traffic_engineering.address is vyos_defined %} mpls-te router-address {{ traffic_engineering.address }} +{% endif %} +{% if traffic_engineering.inter_as is vyos_defined %} +{% set level = '' %} +{% if traffic_engineering.inter_as.level_1 is vyos_defined %} +{% set level = ' level-1' %} {% endif %} -{% if traffic_engineering.inter_as is defined %} -{% if traffic_engineering.inter_as.level_1 is defined %} - mpls-te inter-as level-1 -{% endif %} -{% if traffic_engineering.inter_as.level_1_2 is defined %} - mpls-te inter-as level-1-2 -{% endif %} -{% if traffic_engineering.inter_as.level_2 is defined %} - mpls-te inter-as level-2-only -{% endif %} -{% else %} - mpls-te inter-as +{% if traffic_engineering.inter_as.level_1_2 is vyos_defined %} +{% set level = ' level-1-2' %} +{% endif %} +{% if traffic_engineering.inter_as.level_2 is vyos_defined %} +{% set level = ' level-2-only' %} {% endif %} + mpls-te inter-as{{ level }} {% endif %} -{% if segment_routing is defined %} -{% if segment_routing.enable is defined %} +{% if segment_routing is vyos_defined %} +{% if segment_routing.enable is vyos_defined %} segment-routing on {% endif %} -{% if segment_routing.maximum_label_depth is defined %} +{% if segment_routing.maximum_label_depth is vyos_defined %} segment-routing node-msd {{ segment_routing.maximum_label_depth }} {% endif %} -{% if segment_routing.global_block is defined %} +{% if segment_routing.global_block is vyos_defined %} segment-routing global-block {{ segment_routing.global_block.low_label_value }} {{ segment_routing.global_block.high_label_value }} {% endif %} -{% if segment_routing.local_block is defined %} +{% if segment_routing.local_block is vyos_defined %} segment-routing local-block {{ segment_routing.global_block.low_label_value }} {{ segment_routing.local_block.high_label_value }} {% endif %} -{% if segment_routing.prefix is defined %} +{% if segment_routing.prefix is vyos_defined %} {% for prefixes in segment_routing.prefix %} -{% if segment_routing.prefix[prefixes].absolute is defined %} -{% if segment_routing.prefix[prefixes].absolute.value is defined %} +{% if segment_routing.prefix[prefixes].absolute is vyos_defined %} +{% if segment_routing.prefix[prefixes].absolute.value is vyos_defined %} segment-routing prefix {{ prefixes }} absolute {{ segment_routing.prefix[prefixes].absolute.value }} -{% if segment_routing.prefix[prefixes].absolute.explicit_null is defined %} +{% if segment_routing.prefix[prefixes].absolute.explicit_null is vyos_defined %} segment-routing prefix {{ prefixes }} absolute {{ segment_routing.prefix[prefixes].absolute.value }} explicit-null {% endif %} -{% if segment_routing.prefix[prefixes].absolute.no_php_flag is defined %} +{% if segment_routing.prefix[prefixes].absolute.no_php_flag is vyos_defined %} segment-routing prefix {{ prefixes }} absolute {{ segment_routing.prefix[prefixes].absolute.value }} no-php-flag {% endif %} {% endif %} -{% if segment_routing.prefix[prefixes].index is defined %} -{% if segment_routing.prefix[prefixes].index.value is defined %} +{% if segment_routing.prefix[prefixes].index is vyos_defined %} +{% if segment_routing.prefix[prefixes].index.value is vyos_defined %} segment-routing prefix {{ prefixes }} index {{ segment_routing.prefix[prefixes].index.value }} -{% if segment_routing.prefix[prefixes].index.explicit_null is defined %} +{% if segment_routing.prefix[prefixes].index.explicit_null is vyos_defined %} segment-routing prefix {{ prefixes }} index {{ segment_routing.prefix[prefixes].index.value }} explicit-null {% endif %} -{% if segment_routing.prefix[prefixes].index.no_php_flag is defined %} +{% if segment_routing.prefix[prefixes].index.no_php_flag is vyos_defined %} segment-routing prefix {{ prefixes }} index {{ segment_routing.prefix[prefixes].index.value }} no-php-flag {% endif %} {% endif %} @@ -148,57 +144,51 @@ router isis VyOS {{ 'vrf ' + vrf if vrf is defined and vrf is not none }} {% endfor %} {% endif %} {% endif %} -{% if spf_delay_ietf is defined and spf_delay_ietf.init_delay is defined and spf_delay_ietf.init_delay is not none %} +{% if spf_delay_ietf.init_delay is vyos_defined %} spf-delay-ietf init-delay {{ spf_delay_ietf.init_delay }} short-delay {{ spf_delay_ietf.short_delay }} long-delay {{ spf_delay_ietf.long_delay }} holddown {{ spf_delay_ietf.holddown }} time-to-learn {{ spf_delay_ietf.time_to_learn }} {% endif %} -{% if area_password is defined and area_password is not none %} -{% if area_password.md5 is defined and area_password.md5 is not none %} +{% if area_password.md5 is vyos_defined %} area-password md5 {{ area_password.md5 }} -{% elif area_password.plaintext_password is defined and area_password.plaintext_password is not none %} +{% elif area_password.plaintext_password is vyos_defined %} area-password clear {{ area_password.plaintext_password }} -{% endif %} {% endif %} -{% if default_information is defined and default_information.originate is defined and default_information.originate is not none %} +{% if default_information.originate is vyos_defined %} {% for afi, afi_config in default_information.originate.items() %} {% for level, level_config in afi_config.items() %} default-information originate {{ afi }} {{ level | replace('_', '-') }} {{ 'always' if level_config.always is defined }} {{ 'route-map ' ~ level_config.route_map if level_config.route_map is defined }} {{ 'metric ' ~ level_config.metric if level_config.metric is defined }} {% endfor %} {% endfor %} {% endif %} -{% if redistribute is defined %} -{% if redistribute.ipv4 is defined and redistribute.ipv4 is not none %} -{% for protocol, protocol_options in redistribute.ipv4.items() %} -{% for level, level_config in protocol_options.items() %} -{% if level_config.metric is defined and level_config.metric is not none %} +{% if redistribute.ipv4 is vyos_defined %} +{% for protocol, protocol_options in redistribute.ipv4.items() %} +{% for level, level_config in protocol_options.items() %} +{% if level_config.metric is vyos_defined %} redistribute ipv4 {{ protocol }} {{ level | replace('_', '-') }} metric {{ level_config.metric }} -{% elif level_config.route_map is defined and level_config.route_map is not none %} +{% elif level_config.route_map is vyos_defined %} redistribute ipv4 {{ protocol }} {{ level | replace('_', '-') }} route-map {{ level_config.route_map }} -{% else %} +{% else %} redistribute ipv4 {{ protocol }} {{ level | replace('_', '-') }} -{% endif %} -{% endfor %} +{% endif %} {% endfor %} -{% endif %} -{% if redistribute.ipv6 is defined and redistribute.ipv6 is not none %} -{% for protocol, protocol_options in redistribute.ipv6.items() %} -{% for level, level_config in protocol_options.items() %} -{% if level_config.metric is defined and level_config.metric is not none %} +{% endfor %} +{% endif %} +{% if redistribute.ipv6 is vyos_defined %} +{% for protocol, protocol_options in redistribute.ipv6.items() %} +{% for level, level_config in protocol_options.items() %} +{% if level_config.metric is defined and level_config.metric is not none %} redistribute ipv6 {{ protocol }} {{ level | replace('_', '-') }} metric {{ level_config.metric }} -{% elif level_config.route_map is defined and level_config.route_map is not none %} +{% elif level_config.route_map is defined and level_config.route_map is not none %} redistribute ipv6 {{ protocol }} {{ level | replace('_', '-') }} route-map {{ level_config.route_map }} -{% else %} +{% else %} redistribute ipv6 {{ protocol }} {{ level | replace('_', '-') }} -{% endif %} -{% endfor %} +{% endif %} {% endfor %} -{% endif %} +{% endfor %} {% endif %} -{% if level is defined and level is not none %} -{% if level == 'level-2' %} +{% if level is vyos_defined('level-2') %} is-type level-2-only -{% else %} +{% elif level is vyos_defined %} is-type {{ level }} -{% endif %} {% endif %} exit ! \ No newline at end of file -- cgit v1.2.3 From a6c936997611de85dc73152297679d0b53095713 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 3 Apr 2022 12:11:29 +0200 Subject: isis: T4336: add support for MD5 authentication password on a circuit --- data/templates/frr/isisd.frr.tmpl | 4 +- interface-definitions/include/isis/password.xml.i | 20 ++++++++ .../include/isis/protocol-common-config.xml.i | 54 +++------------------- 3 files changed, 29 insertions(+), 49 deletions(-) create mode 100644 interface-definitions/include/isis/password.xml.i diff --git a/data/templates/frr/isisd.frr.tmpl b/data/templates/frr/isisd.frr.tmpl index 2bf3a3b8a..d8545bea7 100644 --- a/data/templates/frr/isisd.frr.tmpl +++ b/data/templates/frr/isisd.frr.tmpl @@ -31,7 +31,9 @@ interface {{ iface }} {% if iface_config.passive is vyos_defined %} isis passive {% endif %} -{% if iface_config.password.plaintext_password is vyos_defined %} +{% if iface_config.password.md5 is vyos_defined %} + isis password md5 {{ iface_config.password.md5 }} +{% elif iface_config.password.plaintext_password is vyos_defined %} isis password clear {{ iface_config.password.plaintext_password }} {% endif %} {% if iface_config.priority is vyos_defined %} diff --git a/interface-definitions/include/isis/password.xml.i b/interface-definitions/include/isis/password.xml.i new file mode 100644 index 000000000..27c3b0fa0 --- /dev/null +++ b/interface-definitions/include/isis/password.xml.i @@ -0,0 +1,20 @@ + + + + Plain-text authentication type + + txt + Circuit password + + + + + + MD5 authentication type + + txt + Level-wide password + + + + diff --git a/interface-definitions/include/isis/protocol-common-config.xml.i b/interface-definitions/include/isis/protocol-common-config.xml.i index 8ffa14a19..e0145f7a4 100644 --- a/interface-definitions/include/isis/protocol-common-config.xml.i +++ b/interface-definitions/include/isis/protocol-common-config.xml.i @@ -4,24 +4,7 @@ Configure the authentication password for an area - - - Plain-text authentication type - - txt - Level-wide password - - - - - - MD5 authentication type - - txt - Level-wide password - - - + #include @@ -59,24 +42,7 @@ Set the authentication password for a routing domain - - - Plain-text authentication type - - txt - Level-wide password - - - - - - MD5 authentication type - - txt - Level-wide password - - - + #include @@ -104,7 +70,7 @@ Act as an area router - ^(level-1|level-1-2|level-2)$ + (level-1|level-1-2|level-2) @@ -182,7 +148,7 @@ Use new style of TLVs to carry wider metric - ^(narrow|transition|wide)$ + (narrow|transition|wide) @@ -668,7 +634,7 @@ Level-2 only adjacencies are formed - ^(level-1|level-1-2|level-2-only)$ + (level-1|level-1-2|level-2-only) @@ -722,15 +688,7 @@ Configure the authentication password for a circuit - - - Plain-text authentication type - - txt - Circuit password - - - + #include -- cgit v1.2.3 From 16a88f6b86e4ab920178701f6b3c02e893f337e8 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 3 Apr 2022 12:12:21 +0200 Subject: smoketest: isis: extend testcase to verify 'is-type level-2-only' can be set --- smoketest/scripts/cli/test_protocols_isis.py | 29 ++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/smoketest/scripts/cli/test_protocols_isis.py b/smoketest/scripts/cli/test_protocols_isis.py index 7f51c7178..b83c751bc 100755 --- a/smoketest/scripts/cli/test_protocols_isis.py +++ b/smoketest/scripts/cli/test_protocols_isis.py @@ -71,13 +71,13 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify all changes - tmp = self.getFRRconfig(f'router isis {domain}') + tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd') self.assertIn(f' net {net}', tmp) self.assertIn(f' log-adjacency-changes', tmp) self.assertIn(f' redistribute ipv4 connected level-2 route-map {route_map}', tmp) for interface in self._interfaces: - tmp = self.getFRRconfig(f'interface {interface}') + tmp = self.getFRRconfig(f'interface {interface}', daemon='isisd') self.assertIn(f' ip router isis {domain}', tmp) self.assertIn(f' ipv6 router isis {domain}', tmp) @@ -104,11 +104,11 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR isisd configuration - tmp = self.getFRRconfig(f'router isis {domain}') + tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd') self.assertIn(f'router isis {domain}', tmp) self.assertIn(f' net {net}', tmp) - tmp = self.getFRRconfig(f'router isis {domain} vrf {vrf}') + tmp = self.getFRRconfig(f'router isis {domain} vrf {vrf}', daemon='isisd') self.assertIn(f'router isis {domain} vrf {vrf}', tmp) self.assertIn(f' net {net}', tmp) @@ -124,22 +124,26 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): self.isis_base_config() self.cli_set(base_path + ['redistribute', 'ipv4', 'connected', 'level-2', 'route-map', route_map]) self.cli_set(base_path + ['route-map', route_map]) + self.cli_set(base_path + ['level', 'level-2']) # commit changes self.cli_commit() # Verify FRR configuration zebra_route_map = f'ip protocol isis route-map {route_map}' - frrconfig = self.getFRRconfig(zebra_route_map) + frrconfig = self.getFRRconfig(zebra_route_map, daemon='zebra') self.assertIn(zebra_route_map, frrconfig) + tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd') + self.assertIn(' is-type level-2-only', tmp) + # Remove the route-map again self.cli_delete(base_path + ['route-map']) # commit changes self.cli_commit() # Verify FRR configuration - frrconfig = self.getFRRconfig(zebra_route_map) + frrconfig = self.getFRRconfig(zebra_route_map, daemon='zebra') self.assertNotIn(zebra_route_map, frrconfig) self.cli_delete(['policy', 'route-map', route_map]) @@ -159,7 +163,7 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify all changes - tmp = self.getFRRconfig(f'router isis {domain}') + tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd') self.assertIn(f' net {net}', tmp) for afi in ['ipv4', 'ipv6']: @@ -172,6 +176,8 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): password = 'foo' self.isis_base_config() + for interface in self._interfaces: + self.cli_set(base_path + ['interface', interface, 'password', 'plaintext-password', f'{password}-{interface}']) self.cli_set(base_path + ['area-password', 'plaintext-password', password]) self.cli_set(base_path + ['area-password', 'md5', password]) @@ -192,11 +198,14 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify all changes - tmp = self.getFRRconfig(f'router isis {domain}') + tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd') self.assertIn(f' net {net}', tmp) self.assertIn(f' domain-password clear {password}', tmp) self.assertIn(f' area-password clear {password}', tmp) + for interface in self._interfaces: + tmp = self.getFRRconfig(f'interface {interface}', daemon='isisd') + self.assertIn(f' isis password clear {password}-{interface}', tmp) def test_isis_06_spf_delay_bfd(self): network = 'point-to-point' @@ -237,12 +246,12 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify all changes - tmp = self.getFRRconfig(f'router isis {domain}') + tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd') self.assertIn(f' net {net}', tmp) self.assertIn(f' spf-delay-ietf init-delay {init_delay} short-delay {short_delay} long-delay {long_delay} holddown {holddown} time-to-learn {time_to_learn}', tmp) for interface in self._interfaces: - tmp = self.getFRRconfig(f'interface {interface}') + tmp = self.getFRRconfig(f'interface {interface}', daemon='isisd') self.assertIn(f' ip router isis {domain}', tmp) self.assertIn(f' ipv6 router isis {domain}', tmp) self.assertIn(f' isis network {network}', tmp) -- cgit v1.2.3 From de9e6122e0b2930718136965e56d72c007efc6d7 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 3 Apr 2022 13:20:02 +0200 Subject: isis: T4333: fix remaining "is defined" instances --- data/templates/frr/isisd.frr.tmpl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/templates/frr/isisd.frr.tmpl b/data/templates/frr/isisd.frr.tmpl index d8545bea7..33adac64e 100644 --- a/data/templates/frr/isisd.frr.tmpl +++ b/data/templates/frr/isisd.frr.tmpl @@ -157,7 +157,7 @@ router isis VyOS {{ 'vrf ' + vrf if vrf is vyos_defined }} {% if default_information.originate is vyos_defined %} {% for afi, afi_config in default_information.originate.items() %} {% for level, level_config in afi_config.items() %} - default-information originate {{ afi }} {{ level | replace('_', '-') }} {{ 'always' if level_config.always is defined }} {{ 'route-map ' ~ level_config.route_map if level_config.route_map is defined }} {{ 'metric ' ~ level_config.metric if level_config.metric is defined }} + default-information originate {{ afi }} {{ level | replace('_', '-') }} {{ 'always' if level_config.always is vyos_defined }} {{ 'route-map ' ~ level_config.route_map if level_config.route_map is vyos_defined }} {{ 'metric ' ~ level_config.metric if level_config.metric is vyos_defined }} {% endfor %} {% endfor %} {% endif %} @@ -177,9 +177,9 @@ router isis VyOS {{ 'vrf ' + vrf if vrf is vyos_defined }} {% if redistribute.ipv6 is vyos_defined %} {% for protocol, protocol_options in redistribute.ipv6.items() %} {% for level, level_config in protocol_options.items() %} -{% if level_config.metric is defined and level_config.metric is not none %} +{% if level_config.metric is vyos_defined %} redistribute ipv6 {{ protocol }} {{ level | replace('_', '-') }} metric {{ level_config.metric }} -{% elif level_config.route_map is defined and level_config.route_map is not none %} +{% elif level_config.route_map is vyos_defined %} redistribute ipv6 {{ protocol }} {{ level | replace('_', '-') }} route-map {{ level_config.route_map }} {% else %} redistribute ipv6 {{ protocol }} {{ level | replace('_', '-') }} -- cgit v1.2.3 From abdd80c7387e0b819aba5e74777695421fcb70bf Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 3 Apr 2022 13:27:44 +0200 Subject: xml: isis: T3236: create common high-low label value include block --- .../include/isis/high-low-label-value.xml.i | 26 +++++++++++ .../include/isis/protocol-common-config.xml.i | 50 +--------------------- 2 files changed, 28 insertions(+), 48 deletions(-) create mode 100644 interface-definitions/include/isis/high-low-label-value.xml.i diff --git a/interface-definitions/include/isis/high-low-label-value.xml.i b/interface-definitions/include/isis/high-low-label-value.xml.i new file mode 100644 index 000000000..adc28417d --- /dev/null +++ b/interface-definitions/include/isis/high-low-label-value.xml.i @@ -0,0 +1,26 @@ + + + + MPLS label lower bound + + u32:16-1048575 + Label value + + + + + + + + + MPLS label upper bound + + u32:16-1048575 + Label value + + + + + + + diff --git a/interface-definitions/include/isis/protocol-common-config.xml.i b/interface-definitions/include/isis/protocol-common-config.xml.i index e0145f7a4..af9d87a0d 100644 --- a/interface-definitions/include/isis/protocol-common-config.xml.i +++ b/interface-definitions/include/isis/protocol-common-config.xml.i @@ -244,30 +244,7 @@ Global block label range - - - The lower bound of the global block - - u32:16-1048575 - MPLS label value - - - - - - - - - The upper bound of the global block - - u32:16-1048575 - MPLS label value - - - - - - + #include -- cgit v1.2.3 From e66c5a56905a7370be0e14cd34aee608c7e7b54a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 3 Apr 2022 13:28:11 +0200 Subject: smoketest: isis: support running on live systems --- smoketest/scripts/cli/test_protocols_isis.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/smoketest/scripts/cli/test_protocols_isis.py b/smoketest/scripts/cli/test_protocols_isis.py index b83c751bc..11c765793 100755 --- a/smoketest/scripts/cli/test_protocols_isis.py +++ b/smoketest/scripts/cli/test_protocols_isis.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2022 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 @@ -35,6 +35,10 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): # call base-classes classmethod super(cls, cls).setUpClass() + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + def tearDown(self): self.cli_delete(base_path) self.cli_commit() -- cgit v1.2.3 From 7d3ae5fc3ba113b67281c9605f3a8a71b924efe2 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 3 Apr 2022 14:11:06 +0200 Subject: isis: T3156: add segment routing local-block for ISIS --- data/templates/frr/isisd.frr.tmpl | 7 ++-- .../include/isis/protocol-common-config.xml.i | 6 +-- src/conf_mode/protocols_isis.py | 44 ++++++++++++++-------- 3 files changed, 34 insertions(+), 23 deletions(-) diff --git a/data/templates/frr/isisd.frr.tmpl b/data/templates/frr/isisd.frr.tmpl index 33adac64e..238541903 100644 --- a/data/templates/frr/isisd.frr.tmpl +++ b/data/templates/frr/isisd.frr.tmpl @@ -114,10 +114,11 @@ router isis VyOS {{ 'vrf ' + vrf if vrf is vyos_defined }} segment-routing node-msd {{ segment_routing.maximum_label_depth }} {% endif %} {% if segment_routing.global_block is vyos_defined %} +{% if segment_routing.local_block is vyos_defined %} + segment-routing global-block {{ segment_routing.global_block.low_label_value }} {{ segment_routing.global_block.high_label_value }} local-block {{ segment_routing.local_block.low_label_value }} {{ segment_routing.local_block.high_label_value }} +{% else %} segment-routing global-block {{ segment_routing.global_block.low_label_value }} {{ segment_routing.global_block.high_label_value }} -{% endif %} -{% if segment_routing.local_block is vyos_defined %} - segment-routing local-block {{ segment_routing.global_block.low_label_value }} {{ segment_routing.local_block.high_label_value }} +{% endif %} {% endif %} {% if segment_routing.prefix is vyos_defined %} {% for prefixes in segment_routing.prefix %} diff --git a/interface-definitions/include/isis/protocol-common-config.xml.i b/interface-definitions/include/isis/protocol-common-config.xml.i index af9d87a0d..75a0355d4 100644 --- a/interface-definitions/include/isis/protocol-common-config.xml.i +++ b/interface-definitions/include/isis/protocol-common-config.xml.i @@ -241,22 +241,20 @@ - Global block label range + Segment Routing Global Block label range #include - Maximum MPLS labels allowed for this router diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py index 9b4b215de..f2501e38a 100755 --- a/src/conf_mode/protocols_isis.py +++ b/src/conf_mode/protocols_isis.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2021 VyOS maintainers and contributors +# Copyright (C) 2020-2022 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 @@ -169,28 +169,40 @@ def verify(isis): # Segment routing checks if dict_search('segment_routing.global_block', isis): - high_label_value = dict_search('segment_routing.global_block.high_label_value', isis) - low_label_value = dict_search('segment_routing.global_block.low_label_value', isis) + g_high_label_value = dict_search('segment_routing.global_block.high_label_value', isis) + g_low_label_value = dict_search('segment_routing.global_block.low_label_value', isis) - # If segment routing global block high value is blank, throw error - if (low_label_value and not high_label_value) or (high_label_value and not low_label_value): - raise ConfigError('Segment routing global block requires both low and high value!') + # If segment routing global block high or low value is blank, throw error + if not (g_low_label_value or g_high_label_value): + raise ConfigError('Segment routing global-block requires both low and high value!') # If segment routing global block low value is higher than the high value, throw error - if int(low_label_value) > int(high_label_value): - raise ConfigError('Segment routing global block low value must be lower than high value') + if int(g_low_label_value) > int(g_high_label_value): + raise ConfigError('Segment routing global-block low value must be lower than high value') if dict_search('segment_routing.local_block', isis): - high_label_value = dict_search('segment_routing.local_block.high_label_value', isis) - low_label_value = dict_search('segment_routing.local_block.low_label_value', isis) + if dict_search('segment_routing.global_block', isis) == None: + raise ConfigError('Segment routing local-block requires global-block to be configured!') - # If segment routing local block high value is blank, throw error - if (low_label_value and not high_label_value) or (high_label_value and not low_label_value): - raise ConfigError('Segment routing local block requires both high and low value!') + l_high_label_value = dict_search('segment_routing.local_block.high_label_value', isis) + l_low_label_value = dict_search('segment_routing.local_block.low_label_value', isis) - # If segment routing local block low value is higher than the high value, throw error - if int(low_label_value) > int(high_label_value): - raise ConfigError('Segment routing local block low value must be lower than high value') + # If segment routing local-block high or low value is blank, throw error + if not (l_low_label_value or l_high_label_value): + raise ConfigError('Segment routing local-block requires both high and low value!') + + # If segment routing local-block low value is higher than the high value, throw error + if int(l_low_label_value) > int(l_high_label_value): + raise ConfigError('Segment routing local-block low value must be lower than high value') + + # local-block most live outside global block + global_range = range(int(g_low_label_value), int(g_high_label_value) +1) + local_range = range(int(l_low_label_value), int(l_high_label_value) +1) + + # Check for overlapping ranges + if list(set(global_range) & set(local_range)): + raise ConfigError(f'Segment-Routing Global Block ({g_low_label_value}/{g_high_label_value}) '\ + f'conflicts with Local Block ({l_low_label_value}/{l_high_label_value})!') return None -- cgit v1.2.3 From 8fb6e715d32e7eff77e413d8577059dd55b24c0a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 3 Apr 2022 14:21:35 +0200 Subject: mpls: T4333: migrate to new vyos_defined Jinja2 test --- data/templates/frr/ldpd.frr.tmpl | 253 +++++++++++++++++---------------------- 1 file changed, 110 insertions(+), 143 deletions(-) diff --git a/data/templates/frr/ldpd.frr.tmpl b/data/templates/frr/ldpd.frr.tmpl index 537ea4025..5a67b5cdf 100644 --- a/data/templates/frr/ldpd.frr.tmpl +++ b/data/templates/frr/ldpd.frr.tmpl @@ -1,190 +1,157 @@ ! -{% if ldp is defined %} +{% if ldp is vyos_defined %} mpls ldp -{% if ldp.router_id is defined %} +{% if ldp.router_id is vyos_defined %} router-id {{ ldp.router_id }} -{% endif %} -{% if ldp.parameters is defined %} -{% if ldp.parameters.cisco_interop_tlv is defined %} +{% endif %} +{% if ldp.parameters.cisco_interop_tlv is vyos_defined %} dual-stack cisco-interop -{% endif %} -{% if ldp.parameters.transport_prefer_ipv4 is defined%} +{% endif %} +{% if ldp.parameters.transport_prefer_ipv4 is vyos_defined %} dual-stack transport-connection prefer ipv4 -{% endif %} -{% if ldp.parameters.ordered_control is defined%} +{% endif %} +{% if ldp.parameters.ordered_control is vyos_defined %} ordered-control -{% endif %} -{% endif %} -{% if ldp.neighbor is defined %} -{% for neighbors in ldp.neighbor %} -{% if ldp.neighbor[neighbors].password is defined %} - neighbor {{ neighbors }} password {{ ldp.neighbor[neighbors].password }} -{% endif %} -{% if ldp.neighbor[neighbors].ttl_security is defined %} -{% if 'disable' in ldp.neighbor[neighbors].ttl_security %} +{% endif %} +{% if ldp.neighbor is vyos_defined %} +{% for neighbor, neighbor_config in ldp.neighbor %} +{% if neighbor_config.password is vyos_defined %} + neighbor {{ neighbors }} password {{ neighbor_config.password }} +{% endif %} +{% if neighbor_config.ttl_security is vyos_defined %} +{% if neighbor_config.ttl_security.disable is vyos_defined%} neighbor {{ neighbors }} ttl-security disable -{% else %} - neighbor {{ neighbors }} ttl-security hops {{ ldp.neighbor[neighbors].ttl_security }} -{% endif %} -{% endif %} -{% if ldp.neighbor[neighbors].session_holdtime is defined %} - neighbor {{ neighbors }} session holdtime {{ ldp.neighbor[neighbors].session_holdtime }} -{% endif %} -{% endfor %} -{% endif %} +{% else %} + neighbor {{ neighbors }} ttl-security hops {{ neighbor_config.ttl_security }} +{% endif %} +{% endif %} +{% if neighbor_config.session_holdtime is vyos_defined %} + neighbor {{ neighbors }} session holdtime {{ neighbor_config.session_holdtime }} +{% endif %} +{% endfor %} +{% endif %} ! -{% if ldp.discovery is defined %} -{% if ldp.discovery.transport_ipv4_address is defined %} +{% if ldp.discovery.transport_ipv4_address is vyos_defined %} address-family ipv4 -{% if ldp.allocation is defined %} -{% if ldp.allocation.ipv4 is defined %} -{% if ldp.allocation.ipv4.access_list is defined %} +{% if ldp.allocation.ipv4.access_list is vyos_defined %} label local allocate for {{ ldp.allocation.ipv4.access_list }} -{% endif %} -{% endif %} -{% else %} +{% else %} label local allocate host-routes -{% endif %} -{% if ldp.discovery.transport_ipv4_address is defined %} +{% endif %} +{% if ldp.discovery.transport_ipv4_address is vyos_defined %} discovery transport-address {{ ldp.discovery.transport_ipv4_address }} -{% endif %} -{% if ldp.discovery.hello_ipv4_holdtime is defined %} +{% endif %} +{% if ldp.discovery.hello_ipv4_holdtime is vyos_defined %} discovery hello holdtime {{ ldp.discovery.hello_ipv4_holdtime }} -{% endif %} -{% if ldp.discovery.hello_ipv4_interval is defined %} +{% endif %} +{% if ldp.discovery.hello_ipv4_interval is vyos_defined %} discovery hello interval {{ ldp.discovery.hello_ipv4_interval }} -{% endif %} -{% if ldp.discovery.session_ipv4_holdtime is defined %} +{% endif %} +{% if ldp.discovery.session_ipv4_holdtime is vyos_defined %} session holdtime {{ ldp.discovery.session_ipv4_holdtime }} -{% endif %} -{% if ldp.import is defined %} -{% if ldp.import.ipv4 is defined %} -{% if ldp.import.ipv4.import_filter is defined %} -{% if ldp.import.ipv4.import_filter.filter_access_list is defined %} -{% if ldp.import.ipv4.import_filter.neighbor_access_list is defined %} +{% endif %} +{% if ldp.import.ipv4.import_filter.filter_access_list is vyos_defined %} +{% if ldp.import.ipv4.import_filter.neighbor_access_list is vyos_defined %} label remote accept for {{ ldp.import.ipv4.import_filter.filter_access_list }} from {{ ldp.import.ipv4.import_filter.neighbor_access_list }} -{% else %} +{% else %} label remote accept for {{ ldp.import.ipv4.import_filter.filter_access_list }} -{% endif %} -{% endif %} -{% endif %} -{% endif %} -{% endif %} -{% if ldp.export is defined %} -{% if ldp.export.ipv4 is defined %} -{% if ldp.export.ipv4.explicit_null is defined %} +{% endif %} +{% endif %} +{% if ldp.export.ipv4.explicit_null is vyos_defined %} label local advertise explicit-null -{% endif %} -{% if ldp.export.ipv4.export_filter is defined %} -{% if ldp.export.ipv4.export_filter.filter_access_list is defined %} -{% if ldp.export.ipv4.export_filter.neighbor_access_list is defined %} +{% endif %} +{% if ldp.export.ipv4.export_filter.filter_access_list is vyos_defined %} +{% if ldp.export.ipv4.export_filter.neighbor_access_list is vyos_defined %} label local advertise for {{ ldp.export.ipv4.export_filter.filter_access_list }} to {{ ldp.export.ipv4.export_filter.neighbor_access_list }} -{% else %} +{% else %} label local advertise for {{ ldp.export.ipv4.export_filter.filter_access_list }} -{% endif %} -{% endif %} -{% endif %} -{% endif %} -{% endif %} -{% if ldp.targeted_neighbor is defined %} -{% if ldp.targeted_neighbor.ipv4.enable is defined %} +{% endif %} +{% endif %} +{% if ldp.targeted_neighbor is vyos_defined %} +{% if ldp.targeted_neighbor.ipv4.enable is vyos_defined %} discovery targeted-hello accept -{% endif %} -{% if ldp.targeted_neighbor.ipv4.hello_holdtime is defined %} +{% endif %} +{% if ldp.targeted_neighbor.ipv4.hello_holdtime is vyos_defined %} discovery targeted-hello holdtime {{ ldp.targeted_neighbor.ipv4.hello_holdtime }} -{% endif %} -{% if ldp.targeted_neighbor.ipv4.hello_interval is defined %} +{% endif %} +{% if ldp.targeted_neighbor.ipv4.hello_interval is vyos_defined %} discovery targeted-hello interval {{ ldp.targeted_neighbor.ipv4.hello_interval }} -{% endif %} -{% for addresses in ldp.targeted_neighbor.ipv4.address %} - neighbor {{addresses}} targeted -{% endfor %} -{% endif %} -{% for interfaces in ldp.interface %} - interface {{interfaces}} -{% endfor %} +{% endif %} +{% for addresses in ldp.targeted_neighbor.ipv4.address %} + neighbor {{ addresses }} targeted +{% endfor %} +{% endif %} +{% if ldp.interface is vyos_defined %} +{% for interface in ldp.interface %} + interface {{ interface }} + exit +{% endfor %} +{% endif %} exit-address-family -{% else %} +{% else %} no address-family ipv4 -{% endif %} -{% endif %} +{% endif %} ! -{% if ldp.discovery is defined %} -{% if ldp.discovery.transport_ipv6_address is defined %} +{% if ldp.discovery.transport_ipv6_address is vyos_defined %} address-family ipv6 -{% if ldp.allocation is defined %} -{% if ldp.allocation.ipv6 is defined %} -{% if ldp.allocation.ipv6.access_list6 is defined %} +{% if ldp.allocation.ipv6.access_list6 is vyos_defined %} label local allocate for {{ ldp.allocation.ipv6.access_list6 }} -{% endif %} -{% endif %} -{% else %} +{% else %} label local allocate host-routes -{% endif %} -{% if ldp.discovery.transport_ipv6_address is defined %} +{% endif %} +{% if ldp.discovery.transport_ipv6_address is vyos_defined %} discovery transport-address {{ ldp.discovery.transport_ipv6_address }} -{% endif %} -{% if ldp.discovery.hello_ipv6_holdtime is defined %} +{% endif %} +{% if ldp.discovery.hello_ipv6_holdtime is vyos_defined %} discovery hello holdtime {{ ldp.discovery.hello_ipv6_holdtime }} -{% endif %} -{% if ldp.discovery.hello_ipv6_interval is defined %} +{% endif %} +{% if ldp.discovery.hello_ipv6_interval is vyos_defined %} discovery hello interval {{ ldp.discovery.hello_ipv6_interval }} -{% endif %} -{% if ldp.discovery.session_ipv6_holdtime is defined %} +{% endif %} +{% if ldp.discovery.session_ipv6_holdtime is vyos_defined %} session holdtime {{ ldp.discovery.session_ipv6_holdtime }} -{% endif %} -{% if ldp.import is defined %} -{% if ldp.import.ipv6 is defined %} -{% if ldp.import.ipv6.import_filter is defined %} -{% if ldp.import.ipv6.import_filter.filter_access_list6 is defined %} -{% if ldp.import.ipv6.import_filter.neighbor_access_list6 is defined %} +{% endif %} +{% if ldp.import.ipv6.import_filter.filter_access_list6 is vyos_defined %} +{% if ldp.import.ipv6.import_filter.neighbor_access_list6 is vyos_defined %} label remote accept for {{ ldp.import.ipv6.import_filter.filter_access_list6 }} from {{ ldp.import.ipv6.import_filter.neighbor_access_list6 }} -{% else %} +{% else %} label remote accept for {{ ldp.import.ipv6.import_filter.filter_access_list6 }} -{% endif %} -{% endif %} -{% endif %} -{% endif %} -{% endif %} -{% if ldp.export is defined %} -{% if ldp.export.ipv6 is defined %} -{% if ldp.export.ipv6.explicit_null is defined %} +{% endif %} +{% endif %} +{% if ldp.export.ipv6.explicit_null is vyos_defined %} label local advertise explicit-null -{% endif %} -{% if ldp.export.ipv6.export_filter is defined %} -{% if ldp.export.ipv6.export_filter.filter_access_list6 is defined %} -{% if ldp.export.ipv6.export_filter.neighbor_access_list6 is defined %} +{% endif %} +{% if ldp.export.ipv6.export_filter.filter_access_list6 is vyos_defined %} +{% if ldp.export.ipv6.export_filter.neighbor_access_list6 is vyos_defined %} label local advertise for {{ ldp.export.ipv6.export_filter.filter_access_list6 }} to {{ ldp.export.ipv6.export_filter.neighbor_access_list6 }} -{% else %} +{% else %} label local advertise for {{ ldp.export.ipv6.export_filter.filter_access_list6 }} -{% endif %} -{% endif %} -{% endif %} -{% endif %} -{% endif %} -{% if ldp.targeted_neighbor is defined %} -{% if ldp.targeted_neighbor.ipv6.enable is defined %} +{% endif %} +{% endif %} +{% if ldp.targeted_neighbor is vyos_defined %} +{% if ldp.targeted_neighbor.ipv6.enable is vyos_defined %} discovery targeted-hello accept -{% endif %} -{% if ldp.targeted_neighbor.ipv6.hello_holdtime is defined %} +{% endif %} +{% if ldp.targeted_neighbor.ipv6.hello_holdtime is vyos_defined %} discovery targeted-hello holdtime {{ ldp.targeted_neighbor.ipv6.hello_holdtime }} -{% endif %} -{% if ldp.targeted_neighbor.ipv6.hello_interval is defined %} +{% endif %} +{% if ldp.targeted_neighbor.ipv6.hello_interval is vyos_defined %} discovery targeted-hello interval {{ ldp.targeted_neighbor.ipv6.hello_interval }} -{% endif %} -{% for addresses in ldp.targeted_neighbor.ipv6.address %} - neighbor {{addresses}} targeted -{% endfor %} -{% endif %} -{% for interfaces in ldp.interface %} - interface {{interfaces}} -{% endfor %} +{% endif %} +{% for addresses in ldp.targeted_neighbor.ipv6.address %} + neighbor {{ addresses }} targeted +{% endfor %} +{% endif %} +{% if ldp.interface is vyos_defined %} +{% for interface in ldp.interface %} + interface {{ interface }} +{% endfor %} +{% endif %} exit-address-family -{% else %} +{% else %} no address-family ipv6 -{% endif %} +{% endif %} ! -{% endif %} exit {% endif %} ! -- cgit v1.2.3 From c3afde482f020b9bde983c7e1a34387016e6969a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 3 Apr 2022 14:58:59 +0200 Subject: smoketest: ospf(v3): ensure we can also run this test on a live system --- smoketest/scripts/cli/test_protocols_ospf.py | 6 +++++- smoketest/scripts/cli/test_protocols_ospfv3.py | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/smoketest/scripts/cli/test_protocols_ospf.py b/smoketest/scripts/cli/test_protocols_ospf.py index 5d8e9cff2..e433d06d0 100755 --- a/smoketest/scripts/cli/test_protocols_ospf.py +++ b/smoketest/scripts/cli/test_protocols_ospf.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2022 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 @@ -40,6 +40,10 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): cls.cli_set(cls, ['policy', 'route-map', route_map, 'rule', '10', 'action', 'permit']) cls.cli_set(cls, ['policy', 'route-map', route_map, 'rule', '20', 'action', 'permit']) + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + @classmethod def tearDownClass(cls): cls.cli_delete(cls, ['policy', 'route-map', route_map]) diff --git a/smoketest/scripts/cli/test_protocols_ospfv3.py b/smoketest/scripts/cli/test_protocols_ospfv3.py index 2fc694fd7..537e59f01 100755 --- a/smoketest/scripts/cli/test_protocols_ospfv3.py +++ b/smoketest/scripts/cli/test_protocols_ospfv3.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2022 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 @@ -38,6 +38,10 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): cls.cli_set(cls, ['policy', 'route-map', route_map, 'rule', '10', 'action', 'permit']) cls.cli_set(cls, ['policy', 'route-map', route_map, 'rule', '20', 'action', 'permit']) + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + @classmethod def tearDownClass(cls): cls.cli_delete(cls, ['policy', 'route-map', route_map]) -- cgit v1.2.3 From 63e7f444c68519ecb86964668e20f6e431076d3a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 3 Apr 2022 20:44:42 +0200 Subject: ospfv3: T4333: migrate to new vyos_defined Jinja2 test --- data/templates/frr/ospf6d.frr.tmpl | 74 ++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 39 deletions(-) diff --git a/data/templates/frr/ospf6d.frr.tmpl b/data/templates/frr/ospf6d.frr.tmpl index a73c6cac3..325f05213 100644 --- a/data/templates/frr/ospf6d.frr.tmpl +++ b/data/templates/frr/ospf6d.frr.tmpl @@ -1,47 +1,47 @@ ! -{% if interface is defined and interface is not none %} +{% if interface is vyos_defined %} {% for iface, iface_config in interface.items() %} interface {{ iface }} -{% if iface_config.area is defined and iface_config.area is not none %} +{% if iface_config.area is vyos_defined %} ipv6 ospf6 area {{ iface_config.area }} {% endif %} -{% if iface_config.cost is defined and iface_config.cost is not none %} +{% if iface_config.cost is vyos_defined %} ipv6 ospf6 cost {{ iface_config.cost }} {% endif %} -{% if iface_config.priority is defined and iface_config.priority is not none %} +{% if iface_config.priority is vyos_defined %} ipv6 ospf6 priority {{ iface_config.priority }} {% endif %} -{% if iface_config.hello_interval is defined and iface_config.hello_interval is not none %} +{% if iface_config.hello_interval is vyos_defined %} ipv6 ospf6 hello-interval {{ iface_config.hello_interval }} {% endif %} -{% if iface_config.retransmit_interval is defined and iface_config.retransmit_interval is not none %} +{% if iface_config.retransmit_interval is vyos_defined %} ipv6 ospf6 retransmit-interval {{ iface_config.retransmit_interval }} {% endif %} -{% if iface_config.transmit_delay is defined and iface_config.transmit_delay is not none %} +{% if iface_config.transmit_delay is vyos_defined %} ipv6 ospf6 transmit-delay {{ iface_config.transmit_delay }} {% endif %} -{% if iface_config.dead_interval is defined and iface_config.dead_interval is not none %} +{% if iface_config.dead_interval is vyos_defined %} ipv6 ospf6 dead-interval {{ iface_config.dead_interval }} {% endif %} -{% if iface_config.bfd is defined %} +{% if iface_config.bfd is vyos_defined %} ipv6 ospf6 bfd -{% if iface_config.bfd.profile is defined and iface_config.bfd.profile is not none %} +{% endif %} +{% if iface_config.bfd.profile is vyos_defined %} ipv6 ospf6 bfd profile {{ iface_config.bfd.profile }} -{% endif %} {% endif %} -{% if iface_config.mtu_ignore is defined %} +{% if iface_config.mtu_ignore is vyos_defined %} ipv6 ospf6 mtu-ignore {% endif %} -{% if iface_config.ifmtu is defined and iface_config.ifmtu is not none %} +{% if iface_config.ifmtu is vyos_defined %} ipv6 ospf6 ifmtu {{ iface_config.ifmtu }} {% endif %} -{% if iface_config.network is defined and iface_config.network is not none %} +{% if iface_config.network is vyos_defined %} ipv6 ospf6 network {{ iface_config.network }} {% endif %} -{% if iface_config.instance_id is defined and iface_config.instance_id is not none %} +{% if iface_config.instance_id is vyos_defined %} ipv6 ospf6 instance-id {{ iface_config.instance_id }} {% endif %} -{% if iface_config.passive is defined %} +{% if iface_config.passive is vyos_defined %} ipv6 ospf6 passive {% endif %} exit @@ -49,50 +49,46 @@ exit {% endfor %} {% endif %} ! -router ospf6 {{ 'vrf ' + vrf if vrf is defined and vrf is not none }} -{% if area is defined and area is not none %} +router ospf6 {{ 'vrf ' ~ vrf if vrf is vyos_defined }} +{% if area is vyos_defined %} {% for area_id, area_config in area.items() %} -{% if area_config.area_type is defined and area_config.area_type is not none %} +{% if area_config.area_type is vyos_defined %} {% for type, type_config in area_config.area_type.items() %} - area {{ area_id }} {{ type }} {{ 'default-information-originate' if type_config.default_information_originate is defined }} {{ 'no-summary' if type_config.no_summary is defined }} + area {{ area_id }} {{ type }} {{ 'default-information-originate' if type_config.default_information_originate is vyos_defined }} {{ 'no-summary' if type_config.no_summary is vyos_defined }} {% endfor %} {% endif %} -{% if area_config.range is defined and area_config.range is not none %} +{% if area_config.range is vyos_defined %} {% for prefix, prefix_config in area_config.range.items() %} - area {{ area_id }} range {{ prefix }} {{ 'advertise' if prefix_config.advertise is defined }} {{ 'not-advertise' if prefix_config.not_advertise is defined }} + area {{ area_id }} range {{ prefix }} {{ 'advertise' if prefix_config.advertise is vyos_defined }} {{ 'not-advertise' if prefix_config.not_advertise is vyos_defined }} {% endfor %} {% endif %} -{% if area_config.export_list is defined and area_config.export_list is not none %} +{% if area_config.export_list is vyos_defined %} area {{ area_id }} export-list {{ area_config.export_list }} {% endif %} -{% if area_config.import_list is defined and area_config.import_list is not none %} +{% if area_config.import_list is vyos_defined %} area {{ area_id }} import-list {{ area_config.import_list }} {% endif %} {% endfor %} {% endif %} auto-cost reference-bandwidth {{ auto_cost.reference_bandwidth }} -{% if default_information is defined and default_information.originate is defined and default_information.originate is not none %} - default-information originate {{ 'always' if default_information.originate.always is defined }} {{ 'metric ' + default_information.originate.metric if default_information.originate.metric is defined }} {{ 'metric-type ' + default_information.originate.metric_type if default_information.originate.metric_type is defined }} {{ 'route-map ' + default_information.originate.route_map if default_information.originate.route_map is defined }} +{% if default_information.originate is vyos_defined %} + default-information originate {{ 'always' if default_information.originate.always is vyos_defined }} {{ 'metric ' + default_information.originate.metric if default_information.originate.metric is vyos_defined }} {{ 'metric-type ' + default_information.originate.metric_type if default_information.originate.metric_type is vyos_defined }} {{ 'route-map ' + default_information.originate.route_map if default_information.originate.route_map is vyos_defined }} {% endif %} -{% if distance is defined and distance is not none %} -{% if distance.global is defined and distance.global is not none %} +{% if distance.global is vyos_defined %} distance {{ distance.global }} -{% endif %} -{% if distance.ospfv3 is defined and distance.ospfv3 is not none %} - distance ospf6 {{ 'intra-area ' + distance.ospfv3.intra_area if distance.ospfv3.intra_area is defined }} {{ 'inter-area ' + distance.ospfv3.inter_area if distance.ospfv3.inter_area is defined }} {{ 'external ' + distance.ospfv3.external if distance.ospfv3.external is defined }} -{% endif %} {% endif %} -{% if log_adjacency_changes is defined %} - log-adjacency-changes {{ "detail" if log_adjacency_changes.detail is defined }} +{% if distance.ospfv3 is vyos_defined %} + distance ospf6 {{ 'intra-area ' + distance.ospfv3.intra_area if distance.ospfv3.intra_area is vyos_defined }} {{ 'inter-area ' + distance.ospfv3.inter_area if distance.ospfv3.inter_area is vyos_defined }} {{ 'external ' + distance.ospfv3.external if distance.ospfv3.external is vyos_defined }} +{% endif %} +{% if log_adjacency_changes is vyos_defined %} + log-adjacency-changes {{ "detail" if log_adjacency_changes.detail is vyos_defined }} {% endif %} -{% if parameters is defined and parameters is not none %} -{% if parameters.router_id is defined and parameters.router_id is not none %} +{% if parameters.router_id is vyos_defined %} ospf6 router-id {{ parameters.router_id }} -{% endif %} {% endif %} -{% if redistribute is defined and redistribute is not none %} +{% if redistribute is vyos_defined %} {% for protocol, options in redistribute.items() %} - redistribute {{ protocol }} {{ 'route-map ' + options.route_map if options.route_map is defined }} + redistribute {{ protocol }} {{ 'route-map ' + options.route_map if options.route_map is vyos_defined }} {% endfor %} {% endif %} exit -- cgit v1.2.3 From 075d5fa41e0d4ad7dca7b3def4b5bd2ccb4a631a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 3 Apr 2022 20:45:08 +0200 Subject: smoketest: ospfv3: T4302: adjust to FRR 8.2 CLI --- smoketest/scripts/cli/test_protocols_ospfv3.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/smoketest/scripts/cli/test_protocols_ospfv3.py b/smoketest/scripts/cli/test_protocols_ospfv3.py index 537e59f01..f8dfe07d2 100755 --- a/smoketest/scripts/cli/test_protocols_ospfv3.py +++ b/smoketest/scripts/cli/test_protocols_ospfv3.py @@ -74,7 +74,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6') + frrconfig = self.getFRRconfig('router ospf6', daemon='ospf6d') self.assertIn(f'router ospf6', frrconfig) self.assertIn(f' area {default_area} range {prefix}', frrconfig) self.assertIn(f' ospf6 router-id {router_id}', frrconfig) @@ -82,7 +82,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.assertIn(f' area {default_area} export-list {acl_name}', frrconfig) for interface in interfaces: - if_config = self.getFRRconfig(f'interface {interface}') + if_config = self.getFRRconfig(f'interface {interface}', daemon='ospf6d') self.assertIn(f'ipv6 ospf6 area {default_area}', if_config) self.cli_delete(['policy', 'access-list6', acl_name]) @@ -103,7 +103,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6') + frrconfig = self.getFRRconfig('router ospf6', daemon='ospf6d') self.assertIn(f'router ospf6', frrconfig) self.assertIn(f' distance {dist_global}', frrconfig) self.assertIn(f' distance ospf6 intra-area {dist_intra_area} inter-area {dist_inter_area} external {dist_external}', frrconfig) @@ -123,7 +123,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6') + frrconfig = self.getFRRconfig('router ospf6', daemon='ospf6d') self.assertIn(f'router ospf6', frrconfig) for protocol in redistribute: self.assertIn(f' redistribute {protocol} route-map {route_map}', frrconfig) @@ -154,13 +154,13 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6') + frrconfig = self.getFRRconfig('router ospf6', daemon='ospf6d') self.assertIn(f'router ospf6', frrconfig) cost = '100' priority = '10' for interface in interfaces: - if_config = self.getFRRconfig(f'interface {interface}') + if_config = self.getFRRconfig(f'interface {interface}', daemon='ospf6d') self.assertIn(f'interface {interface}', if_config) self.assertIn(f' ipv6 ospf6 bfd', if_config) self.assertIn(f' ipv6 ospf6 bfd profile {bfd_profile}', if_config) @@ -184,7 +184,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6') + frrconfig = self.getFRRconfig('router ospf6', daemon='ospf6d') self.assertIn(f'router ospf6', frrconfig) self.assertIn(f' area {area_stub} stub', frrconfig) self.assertIn(f' area {area_stub_nosum} stub no-summary', frrconfig) @@ -210,7 +210,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6') + frrconfig = self.getFRRconfig('router ospf6', daemon='ospf6d') self.assertIn(f'router ospf6', frrconfig) self.assertIn(f' area {area_nssa} nssa', frrconfig) self.assertIn(f' area {area_nssa_nosum} nssa default-information-originate no-summary', frrconfig) @@ -230,7 +230,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6') + frrconfig = self.getFRRconfig('router ospf6', daemon='ospf6d') self.assertIn(f'router ospf6', frrconfig) self.assertIn(f' default-information originate metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig) @@ -239,7 +239,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6') + frrconfig = self.getFRRconfig('router ospf6', daemon='ospf6d') self.assertIn(f' default-information originate always metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig) @@ -265,15 +265,15 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6') + frrconfig = self.getFRRconfig('router ospf6', daemon='ospf6d') self.assertIn(f'router ospf6', frrconfig) self.assertIn(f' ospf6 router-id {router_id}', frrconfig) - frrconfig = self.getFRRconfig(f'interface {vrf_iface}') + frrconfig = self.getFRRconfig(f'interface {vrf_iface}', end='', daemon='ospf6d') self.assertIn(f'interface {vrf_iface}', frrconfig) self.assertIn(f' ipv6 ospf6 bfd', frrconfig) - frrconfig = self.getFRRconfig(f'router ospf6 vrf {vrf}') + frrconfig = self.getFRRconfig(f'router ospf6 vrf {vrf}', daemon='ospf6d') self.assertIn(f'router ospf6 vrf {vrf}', frrconfig) self.assertIn(f' ospf6 router-id {router_id_vrf}', frrconfig) -- cgit v1.2.3 From 5cff7b0de2693a2fcc4cf776e947a1de999104a8 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 3 Apr 2022 20:45:40 +0200 Subject: ospf: T4333: migrate to new vyos_defined Jinja2 test --- data/templates/frr/ospfd.frr.tmpl | 154 ++++++++++++++++++-------------------- 1 file changed, 72 insertions(+), 82 deletions(-) diff --git a/data/templates/frr/ospfd.frr.tmpl b/data/templates/frr/ospfd.frr.tmpl index 59d936b55..7c6738181 100644 --- a/data/templates/frr/ospfd.frr.tmpl +++ b/data/templates/frr/ospfd.frr.tmpl @@ -1,121 +1,117 @@ ! -{% if interface is defined and interface is not none %} +{% if interface is vyos_defined %} {% for iface, iface_config in interface.items() %} interface {{ iface }} -{% if iface_config.authentication is defined and iface_config.authentication is not none %} -{% if iface_config.authentication.plaintext_password is defined and iface_config.authentication.plaintext_password is not none %} +{% if iface_config.authentication.plaintext_password is vyos_defined %} ip ospf authentication-key {{ iface_config.authentication.plaintext_password }} -{% elif iface_config.authentication.md5 is defined %} +{% elif iface_config.authentication.md5 is vyos_defined %} ip ospf authentication message-digest -{% if iface_config.authentication.md5.key_id is defined and iface_config.authentication.md5.key_id is not none %} -{% for key, key_config in iface_config.authentication.md5.key_id.items() %} +{% if iface_config.authentication.md5.key_id is vyos_defined %} +{% for key, key_config in iface_config.authentication.md5.key_id.items() %} ip ospf message-digest-key {{ key }} md5 {{ key_config.md5_key }} -{% endfor %} -{% endif %} +{% endfor %} {% endif %} {% endif %} -{% if iface_config.area is defined and iface_config.area is not none %} +{% if iface_config.area is vyos_defined %} ip ospf area {{ iface_config.area }} {% endif %} -{% if iface_config.bandwidth is defined and iface_config.bandwidth is not none %} +{% if iface_config.bandwidth is vyos_defined %} bandwidth {{ iface_config.bandwidth }} {% endif %} -{% if iface_config.cost is defined and iface_config.cost is not none %} +{% if iface_config.cost is vyos_defined %} ip ospf cost {{ iface_config.cost }} {% endif %} -{% if iface_config.priority is defined and iface_config.priority is not none %} +{% if iface_config.priority is vyos_defined %} ip ospf priority {{ iface_config.priority }} {% endif %} -{% if iface_config.hello_interval is defined and iface_config.hello_interval is not none %} +{% if iface_config.hello_interval is vyos_defined %} ip ospf hello-interval {{ iface_config.hello_interval }} {% endif %} -{% if iface_config.retransmit_interval is defined and iface_config.retransmit_interval is not none %} +{% if iface_config.retransmit_interval is vyos_defined %} ip ospf retransmit-interval {{ iface_config.retransmit_interval }} {% endif %} -{% if iface_config.transmit_delay is defined and iface_config.transmit_delay is not none %} +{% if iface_config.transmit_delay is vyos_defined %} ip ospf transmit-delay {{ iface_config.transmit_delay }} {% endif %} -{% if iface_config.dead_interval is defined and iface_config.dead_interval is not none %} +{% if iface_config.dead_interval is vyos_defined %} ip ospf dead-interval {{ iface_config.dead_interval }} -{% elif iface_config.hello_multiplier is defined and iface_config.hello_multiplier is not none %} +{% elif iface_config.hello_multiplier is vyos_defined %} ip ospf dead-interval minimal hello-multiplier {{ iface_config.hello_multiplier }} {% endif %} -{% if iface_config.bfd is defined %} +{% if iface_config.bfd is vyos_defined %} ip ospf bfd -{% if iface_config.bfd.profile is defined and iface_config.bfd.profile is not none %} +{% endif %} +{% if iface_config.bfd.profile is vyos_defined %} ip ospf bfd profile {{ iface_config.bfd.profile }} -{% endif %} {% endif %} -{% if iface_config.mtu_ignore is defined %} +{% if iface_config.mtu_ignore is vyos_defined %} ip ospf mtu-ignore {% endif %} -{% if iface_config.network is defined and iface_config.network is not none %} +{% if iface_config.network is vyos_defined %} ip ospf network {{ iface_config.network }} {% endif %} -{% if iface_config.passive is defined %} - {{ 'no ' if iface_config.passive.disable is defined }}ip ospf passive +{% if iface_config.passive is vyos_defined %} + {{ 'no ' if iface_config.passive.disable is vyos_defined }}ip ospf passive {% endif %} exit ! {% endfor %} {% endif %} ! -router ospf {{ 'vrf ' + vrf if vrf is defined and vrf is not none }} -{% if access_list is defined and access_list is not none %} +router ospf {{ 'vrf ' ~ vrf if vrf is vyos_defined }} +{% if access_list is vyos_defined %} {% for acl, acl_config in access_list.items() %} -{% for protocol in acl_config.export if acl_config.export is defined %} +{% for protocol in acl_config.export if acl_config.export is vyos_defined %} distribute-list {{ acl }} out {{ protocol }} {% endfor %} {% endfor %} {% endif %} -{% if area is defined and area is not none %} +{% if area is vyos_defined %} {% for area_id, area_config in area.items() %} -{% if area_config.area_type is defined and area_config.area_type is not none %} +{% if area_config.area_type is vyos_defined %} {% for type, type_config in area_config.area_type.items() if type != 'normal' %} - area {{ area_id }} {{ type }} {{ 'no-summary' if type_config.no_summary is defined }} -{% if type_config.default_cost is defined and type_config.default_cost is not none %} + area {{ area_id }} {{ type }} {{ 'no-summary' if type_config.no_summary is vyos_defined }} +{% if type_config.default_cost is vyos_defined %} area {{ area_id }} default-cost {{ type_config.default_cost }} {% endif %} {% endfor %} {% endif %} -{% if area_config.authentication is defined and area_config.authentication is not none %} - area {{ area_id }} authentication {{ 'message-digest' if area_config.authentication == 'md5' }} +{% if area_config.authentication is vyos_defined %} + area {{ area_id }} authentication {{ 'message-digest' if area_config.authentication is vyos_defined('md5') }} {% endif %} -{% for network in area_config.network if area_config.network is defined %} +{% for network in area_config.network if area_config.network is vyos_defined %} network {{ network }} area {{ area_id }} {% endfor %} -{% if area_config.range is defined and area_config.range is not none %} +{% if area_config.range is vyos_defined %} {% for range, range_config in area_config.range.items() %} -{% if range_config.cost is defined and range_config.cost is not none %} +{% if range_config.cost is vyos_defined %} area {{ area_id }} range {{ range }} cost {{ range_config.cost }} {% endif %} -{% if range_config.not_advertise is defined %} +{% if range_config.not_advertise is vyos_defined %} area {{ area_id }} range {{ range }} not-advertise {% endif %} -{% if range_config.substitute is defined and range_config.substitute is not none %} +{% if range_config.substitute is vyos_defined %} area {{ area_id }} range {{ range }} substitute {{ range_config.substitute }} {% endif %} {% endfor %} {% endif %} -{% if area_config.export_list is defined and area_config.export_list is not none %} +{% if area_config.export_list is vyos_defined %} area {{ area_id }} export-list {{ area_config.export_list }} {% endif %} -{% if area_config.import_list is defined and area_config.import_list is not none %} +{% if area_config.import_list is vyos_defined %} area {{ area_id }} import-list {{ area_config.import_list }} {% endif %} -{% if area_config.shortcut is defined and area_config.shortcut is not none %} +{% if area_config.shortcut is vyos_defined %} area {{ area_id }} shortcut {{ area_config.shortcut }} {% endif %} -{% if area_config.virtual_link is defined and area_config.virtual_link is not none %} +{% if area_config.virtual_link is vyos_defined %} {% for link, link_config in area_config.virtual_link.items() %} -{% if link_config.authentication is defined and link_config.authentication is not none %} -{% if link_config.authentication.plaintext_password is defined and link_config.authentication.plaintext_password is not none %} +{% if link_config.authentication.plaintext_password is vyos_defined %} area {{ area_id }} virtual-link {{ link }} authentication-key {{ link_config.authentication.plaintext_password }} -{% elif link_config.authentication.md5 is defined and link_config.authentication.md5.key_id is defined and link_config.authentication.md5.key_id is not none %} -{% for key, key_config in link_config.authentication.md5.key_id.items() %} +{% elif link_config.authentication.md5.key_id is vyos_defined %} +{% for key, key_config in link_config.authentication.md5.key_id.items() %} area {{ area_id }} virtual-link {{ link }} message-digest-key {{ key }} md5 {{ key_config.md5_key }} -{% endfor %} -{% endif %} +{% endfor %} {% endif %} {# The following values are default values #} area {{ area_id }} virtual-link {{ link }} hello-interval {{ link_config.hello_interval }} retransmit-interval {{ link_config.retransmit_interval }} transmit-delay {{ link_config.transmit_delay }} dead-interval {{ link_config.dead_interval }} @@ -123,75 +119,69 @@ router ospf {{ 'vrf ' + vrf if vrf is defined and vrf is not none }} {% endif %} {% endfor %} {% endif %} -{% if auto_cost is defined and auto_cost.reference_bandwidth is defined and auto_cost.reference_bandwidth is not none %} +{% if auto_cost.reference_bandwidth is vyos_defined %} auto-cost reference-bandwidth {{ auto_cost.reference_bandwidth }} {% endif %} -{% if default_information is defined and default_information.originate is defined and default_information.originate is not none %} - default-information originate {{ 'always' if default_information.originate.always is defined }} {{ 'metric ' + default_information.originate.metric if default_information.originate.metric is defined }} {{ 'metric-type ' + default_information.originate.metric_type if default_information.originate.metric_type is defined }} {{ 'route-map ' + default_information.originate.route_map if default_information.originate.route_map is defined }} +{% if default_information.originate is vyos_defined %} + default-information originate {{ 'always' if default_information.originate.always is vyos_defined }} {{ 'metric ' + default_information.originate.metric if default_information.originate.metric is vyos_defined }} {{ 'metric-type ' + default_information.originate.metric_type if default_information.originate.metric_type is vyos_defined }} {{ 'route-map ' + default_information.originate.route_map if default_information.originate.route_map is vyos_defined }} {% endif %} -{% if default_metric is defined and default_metric is not none %} +{% if default_metric is vyos_defined %} default-metric {{ default_metric }} {% endif %} -{% if maximum_paths is defined and maximum_paths is not none %} +{% if maximum_paths is vyos_defined %} maximum-paths {{ maximum_paths }} {% endif %} -{% if distance is defined and distance is not none %} -{% if distance.global is defined and distance.global is not none %} +{% if distance.global is vyos_defined %} distance {{ distance.global }} -{% endif %} -{% if distance.ospf is defined and distance.ospf is not none %} - distance ospf {{ 'intra-area ' + distance.ospf.intra_area if distance.ospf.intra_area is defined }} {{ 'inter-area ' + distance.ospf.inter_area if distance.ospf.inter_area is defined }} {{ 'external ' + distance.ospf.external if distance.ospf.external is defined }} -{% endif %} {% endif %} -{% if log_adjacency_changes is defined %} - log-adjacency-changes {{ "detail" if log_adjacency_changes.detail is defined }} +{% if distance.ospf is vyos_defined %} + distance ospf {{ 'intra-area ' + distance.ospf.intra_area if distance.ospf.intra_area is vyos_defined }} {{ 'inter-area ' + distance.ospf.inter_area if distance.ospf.inter_area is vyos_defined }} {{ 'external ' + distance.ospf.external if distance.ospf.external is vyos_defined }} {% endif %} -{% if max_metric is defined and max_metric.router_lsa is defined and max_metric.router_lsa is not none %} -{% if max_metric.router_lsa.administrative is defined %} +{% if log_adjacency_changes is vyos_defined %} + log-adjacency-changes {{ "detail" if log_adjacency_changes.detail is vyos_defined }} +{% endif %} +{% if max_metric.router_lsa.administrative is vyos_defined %} max-metric router-lsa administrative -{% endif %} -{% if max_metric.router_lsa.on_shutdown is defined and max_metric.router_lsa.on_shutdown is not none %} +{% endif %} +{% if max_metric.router_lsa.on_shutdown is vyos_defined %} max-metric router-lsa on-shutdown {{ max_metric.router_lsa.on_shutdown }} -{% endif %} -{% if max_metric.router_lsa.on_startup is defined and max_metric.router_lsa.on_startup is not none %} +{% endif %} +{% if max_metric.router_lsa.on_startup is vyos_defined %} max-metric router-lsa on-startup {{ max_metric.router_lsa.on_startup }} -{% endif %} {% endif %} -{% if mpls_te is defined and mpls_te.enable is defined %} +{% if mpls_te.enable is vyos_defined %} mpls-te on mpls-te router-address {{ mpls_te.router_address }} {% endif %} -{% if neighbor is defined and neighbor is not none%} +{% if neighbor is vyos_defined %} {% for address, address_config in neighbor.items() %} - neighbor {{ address }} {{ 'priority ' + address_config.priority if address_config.priority is defined }} {{ 'poll-interval ' + address_config.poll_interval if address_config.poll_interval is defined }} + neighbor {{ address }} {{ 'priority ' + address_config.priority if address_config.priority is vyos_defined }} {{ 'poll-interval ' + address_config.poll_interval if address_config.poll_interval is vyos_defined }} {% endfor %} {% endif %} -{% if parameters is defined and parameters is not none %} -{% if parameters.abr_type is defined and parameters.abr_type is not none %} +{% if parameters.abr_type is vyos_defined %} ospf abr-type {{ parameters.abr_type }} -{% endif %} -{% if parameters.router_id is defined and parameters.router_id is not none %} +{% endif %} +{% if parameters.router_id is vyos_defined %} ospf router-id {{ parameters.router_id }} -{% endif %} {% endif %} -{% if passive_interface is defined and passive_interface.default is defined %} +{% if passive_interface.default is vyos_defined %} passive-interface default {% endif %} -{% if redistribute is defined and redistribute is not none %} +{% if redistribute is vyos_defined %} {% for protocol, protocols_options in redistribute.items() %} {% if protocol == 'table' %} {% for table, table_options in protocols_options.items() %} - redistribute {{ protocol }} {{ table }} {{ 'metric ' + table_options.metric if table_options.metric is defined }} {{ 'metric-type ' + table_options.metric_type if table_options.metric_type is defined }} {{ 'route-map ' + table_options.route_map if table_options.route_map is defined }} + redistribute {{ protocol }} {{ table }} {{ 'metric ' + table_options.metric if table_options.metric is vyos_defined }} {{ 'metric-type ' + table_options.metric_type if table_options.metric_type is vyos_defined }} {{ 'route-map ' + table_options.route_map if table_options.route_map is vyos_defined }} {% endfor %} {% else %} - redistribute {{ protocol }} {{ 'metric ' + protocols_options.metric if protocols_options.metric is defined }} {{ 'metric-type ' + protocols_options.metric_type if protocols_options.metric_type is defined }} {{ 'route-map ' + protocols_options.route_map if protocols_options.route_map is defined }} + redistribute {{ protocol }} {{ 'metric ' + protocols_options.metric if protocols_options.metric is vyos_defined }} {{ 'metric-type ' + protocols_options.metric_type if protocols_options.metric_type is vyos_defined }} {{ 'route-map ' + protocols_options.route_map if protocols_options.route_map is vyos_defined }} {% endif %} {% endfor %} {% endif %} -{% if refresh is defined and refresh.timers is defined and refresh.timers is not none %} +{% if refresh.timers is vyos_defined %} refresh timer {{ refresh.timers }} {% endif %} -{% if timers is defined and timers.throttle is defined and timers.throttle.spf is defined and timers.throttle.spf is not none %} +{% if timers.throttle.spf.delay is vyos_defined and timers.throttle.spf.initial_holdtime is vyos_defined and timers.throttle.spf.max_holdtime is vyos_defined %} {# Timer values have default values #} timers throttle spf {{ timers.throttle.spf.delay }} {{ timers.throttle.spf.initial_holdtime }} {{ timers.throttle.spf.max_holdtime }} {% endif %} -- cgit v1.2.3 From 96e1fd6f574e0f90e4e420787cd66d1a0f546cb4 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 3 Apr 2022 21:39:42 +0200 Subject: policy: T4333: migrate to new vyos_defined Jinja2 test --- data/templates/frr/policy.frr.tmpl | 196 ++++++++++++++++++------------------- 1 file changed, 97 insertions(+), 99 deletions(-) diff --git a/data/templates/frr/policy.frr.tmpl b/data/templates/frr/policy.frr.tmpl index 60e15f4fd..814dbf761 100644 --- a/data/templates/frr/policy.frr.tmpl +++ b/data/templates/frr/policy.frr.tmpl @@ -1,18 +1,18 @@ -{% if access_list is defined and access_list is not none %} +{% if access_list is vyos_defined %} {% for acl, acl_config in access_list.items() | natural_sort %} -{% if acl_config.description is defined and acl_config.description is not none %} +{% if acl_config.description is vyos_defined %} access-list {{ acl }} remark {{ acl_config.description }} {% endif %} -{% if acl_config.rule is defined and acl_config.rule is not none %} +{% if acl_config.rule is vyos_defined %} {% for rule, rule_config in acl_config.rule.items() | natural_sort %} {% set ip = '' %} {% set src = '' %} {% set src_mask = '' %} -{% if rule_config.source is defined and rule_config.source.any is defined %} +{% if rule_config.source.any is vyos_defined %} {% set src = 'any' %} -{% elif rule_config.source is defined and rule_config.source.host is defined and rule_config.source.host is not none %} -{% set src = 'host ' + rule_config.source.host %} -{% elif rule_config.source is defined and rule_config.source.network is defined and rule_config.source.network is not none %} +{% elif rule_config.source.host is vyos_defined %} +{% set src = 'host ' ~ rule_config.source.host %} +{% elif rule_config.source.network is vyos_defined %} {% set src = rule_config.source.network %} {% set src_mask = rule_config.source.inverse_mask %} {% endif %} @@ -21,11 +21,11 @@ access-list {{ acl }} remark {{ acl_config.description }} {% if (acl|int >= 100 and acl|int <= 199) or (acl|int >= 2000 and acl|int <= 2699) %} {% set ip = 'ip' %} {% set dst = 'any' %} -{% if rule_config.destination is defined and rule_config.destination.any is defined %} +{% if rule_config.destination.any is vyos_defined %} {% set dst = 'any' %} -{% elif rule_config.destination is defined and rule_config.destination.host is defined and rule_config.destination.host is not none %} -{% set dst = 'host ' + rule_config.destination.host %} -{% elif rule_config.destination is defined and rule_config.destination.network is defined and rule_config.destination.network is not none %} +{% elif rule_config.destination.host is vyos_defined %} +{% set dst = 'host ' ~ rule_config.destination.host %} +{% elif rule_config.destination.network is vyos_defined %} {% set dst = rule_config.destination.network %} {% set dst_mask = rule_config.destination.inverse_mask %} {% endif %} @@ -36,28 +36,28 @@ access-list {{ acl }} seq {{ rule }} {{ rule_config.action }} {{ ip }} {{ src }} {% endfor %} {% endif %} ! -{% if access_list6 is defined and access_list6 is not none %} +{% if access_list6 is vyos_defined %} {% for acl, acl_config in access_list6.items() | natural_sort %} -{% if acl_config.description is defined and acl_config.description is not none %} +{% if acl_config.description is vyos_defined %} ipv6 access-list {{ acl }} remark {{ acl_config.description }} {% endif %} -{% if acl_config.rule is defined and acl_config.rule is not none %} +{% if acl_config.rule is vyos_defined %} {% for rule, rule_config in acl_config.rule.items() | natural_sort %} {% set src = '' %} -{% if rule_config.source is defined and rule_config.source.any is defined %} +{% if rule_config.source.any is vyos_defined %} {% set src = 'any' %} -{% elif rule_config.source is defined and rule_config.source.network is defined and rule_config.source.network is not none %} +{% elif rule_config.source.network is vyos_defined %} {% set src = rule_config.source.network %} {% endif %} -ipv6 access-list {{ acl }} seq {{ rule }} {{ rule_config.action }} {{ src }} {{ 'exact-match' if rule_config.source.exact_match is defined }} +ipv6 access-list {{ acl }} seq {{ rule }} {{ rule_config.action }} {{ src }} {{ 'exact-match' if rule_config.source.exact_match is vyos_defined }} {% endfor %} {% endif %} {% endfor %} {% endif %} ! -{% if as_path_list is defined and as_path_list is not none %} +{% if as_path_list is vyos_defined %} {% for acl, acl_config in as_path_list.items() | natural_sort %} -{% if acl_config.rule is defined and acl_config.rule is not none %} +{% if acl_config.rule is vyos_defined %} {% for rule, rule_config in acl_config.rule.items() | natural_sort %} bgp as-path access-list {{ acl }} seq {{ rule }} {{ rule_config.action }} {{ rule_config.regex }} {% endfor %} @@ -65,9 +65,9 @@ bgp as-path access-list {{ acl }} seq {{ rule }} {{ rule_config.action }} {{ rul {% endfor %} {% endif %} ! -{% if community_list is defined and community_list is not none %} +{% if community_list is vyos_defined %} {% for list, list_config in community_list.items() | natural_sort %} -{% if list_config.rule is defined and list_config.rule is not none %} +{% if list_config.rule is vyos_defined %} {% for rule, rule_config in list_config.rule.items() | natural_sort %} {# by default, if casting to int fails it returns 0 #} {% if list|int != 0 %} @@ -80,9 +80,9 @@ bgp community-list expanded {{ list }} seq {{ rule }} {{ rule_config.action }} { {% endfor %} {% endif %} ! -{% if extcommunity_list is defined and extcommunity_list is not none %} +{% if extcommunity_list is vyos_defined %} {% for list, list_config in extcommunity_list.items() | natural_sort %} -{% if list_config.rule is defined and list_config.rule is not none %} +{% if list_config.rule is vyos_defined %} {% for rule, rule_config in list_config.rule.items() | natural_sort %} {# by default, if casting to int fails it returns 0 #} {% if list|int != 0 %} @@ -95,9 +95,9 @@ bgp extcommunity-list expanded {{ list }} seq {{ rule }} {{ rule_config.action } {% endfor %} {% endif %} ! -{% if large_community_list is defined and large_community_list is not none %} +{% if large_community_list is vyos_defined %} {% for list, list_config in large_community_list.items() | natural_sort %} -{% if list_config.rule is defined and list_config.rule is not none %} +{% if list_config.rule is vyos_defined %} {% for rule, rule_config in list_config.rule.items() | natural_sort %} {# by default, if casting to int fails it returns 0 #} {% if list|int != 0 %} @@ -110,209 +110,207 @@ bgp large-community-list expanded {{ list }} seq {{ rule }} {{ rule_config.actio {% endfor %} {% endif %} ! -{% if prefix_list is defined and prefix_list is not none %} +{% if prefix_list is vyos_defined %} {% for prefix_list, prefix_list_config in prefix_list.items() | natural_sort %} -{% if prefix_list_config.description is defined and prefix_list_config.description is not none %} +{% if prefix_list_config.description is vyos_defined %} ip prefix-list {{ prefix_list }} description {{ prefix_list_config.description }} {% endif %} -{% if prefix_list_config.rule is defined and prefix_list_config.rule is not none %} +{% if prefix_list_config.rule is vyos_defined %} {% for rule, rule_config in prefix_list_config.rule.items() | natural_sort %} -{% if rule_config.prefix is defined and rule_config.prefix is not none %} -ip prefix-list {{ prefix_list }} seq {{ rule }} {{ rule_config.action }} {{ rule_config.prefix }} {{ 'ge ' + rule_config.ge if rule_config.ge is defined }} {{ 'le ' + rule_config.le if rule_config.le is defined }} +{% if rule_config.prefix is vyos_defined %} +ip prefix-list {{ prefix_list }} seq {{ rule }} {{ rule_config.action }} {{ rule_config.prefix }} {{ 'ge ' ~ rule_config.ge if rule_config.ge is vyos_defined }} {{ 'le ' ~ rule_config.le if rule_config.le is vyos_defined }} {% endif %} {% endfor %} {% endif %} {% endfor %} {% endif %} ! -{% if prefix_list6 is defined and prefix_list6 is not none %} +{% if prefix_list6 is vyos_defined %} {% for prefix_list, prefix_list_config in prefix_list6.items() | natural_sort %} -{% if prefix_list_config.description is defined and prefix_list_config.description is not none %} +{% if prefix_list_config.description is vyos_defined %} ipv6 prefix-list {{ prefix_list }} description {{ prefix_list_config.description }} {% endif %} -{% if prefix_list_config.rule is defined and prefix_list_config.rule is not none %} +{% if prefix_list_config.rule is vyos_defined %} {% for rule, rule_config in prefix_list_config.rule.items() | natural_sort %} -{% if rule_config.prefix is defined and rule_config.prefix is not none %} -ipv6 prefix-list {{ prefix_list }} seq {{ rule }} {{ rule_config.action }} {{ rule_config.prefix }} {{ 'ge ' + rule_config.ge if rule_config.ge is defined }} {{ 'le ' + rule_config.le if rule_config.le is defined }} +{% if rule_config.prefix is vyos_defined %} +ipv6 prefix-list {{ prefix_list }} seq {{ rule }} {{ rule_config.action }} {{ rule_config.prefix }} {{ 'ge ' ~ rule_config.ge if rule_config.ge is vyos_defined }} {{ 'le ' ~ rule_config.le if rule_config.le is vyos_defined }} {% endif %} {% endfor %} {% endif %} {% endfor %} {% endif %} ! -{% if route_map is defined and route_map is not none %} +{% if route_map is vyos_defined %} {% for route_map, route_map_config in route_map.items() | natural_sort %} -{% if route_map_config.rule is defined and route_map_config.rule is not none %} +{% if route_map_config.rule is vyos_defined %} {% for rule, rule_config in route_map_config.rule.items() | natural_sort %} route-map {{ route_map }} {{ rule_config.action }} {{ rule }} -{% if rule_config.call is defined and rule_config.call is not none %} +{% if rule_config.call is vyos_defined %} call {{ rule_config.call }} {% endif %} -{% if rule_config.continue is defined and rule_config.continue is not none %} +{% if rule_config.continue is vyos_defined %} on-match goto {{ rule_config.continue }} {% endif %} -{% if rule_config.description is defined and rule_config.description is not none %} +{% if rule_config.description is vyos_defined %} description {{ rule_config.description }} {% endif %} -{% if rule_config.match is defined and rule_config.match is not none %} -{% if rule_config.match.as_path is defined and rule_config.match.as_path is not none %} +{% if rule_config.match is vyos_defined %} +{% if rule_config.match.as_path is vyos_defined %} match as-path {{ rule_config.match.as_path }} {% endif %} -{% if rule_config.match.community is defined and rule_config.match.community.community_list is defined and rule_config.match.community.community_list is not none %} - match community {{ rule_config.match.community.community_list }} {{ 'exact-match' if rule_config.match.community.exact_match is defined }} +{% if rule_config.match.community.community_list is vyos_defined %} + match community {{ rule_config.match.community.community_list }} {{ 'exact-match' if rule_config.match.community.exact_match is vyos_defined }} {% endif %} -{% if rule_config.match.extcommunity is defined and rule_config.match.extcommunity is not none %} +{% if rule_config.match.extcommunity is vyos_defined %} match extcommunity {{ rule_config.match.extcommunity }} {% endif %} -{% if rule_config.match.evpn is defined and rule_config.match.evpn.default_route is defined %} +{% if rule_config.match.evpn.default_route is vyos_defined %} match evpn default-route {% endif %} -{% if rule_config.match.evpn is defined and rule_config.match.evpn.rd is defined and rule_config.match.evpn.rd is not none %} +{% if rule_config.match.evpn.rd is vyos_defined %} match evpn rd {{ rule_config.match.evpn.rd }} {% endif %} -{% if rule_config.match.evpn is defined and rule_config.match.evpn.route_type is defined and rule_config.match.evpn.route_type is not none %} +{% if rule_config.match.evpn.route_type is vyos_defined %} match evpn route-type {{ rule_config.match.evpn.route_type }} {% endif %} -{% if rule_config.match.evpn is defined and rule_config.match.evpn.vni is defined and rule_config.match.evpn.vni is not none %} +{% if rule_config.match.evpn.vni is vyos_defined %} match evpn vni {{ rule_config.match.evpn.vni }} {% endif %} -{% if rule_config.match.interface is defined and rule_config.match.interface is not none %} +{% if rule_config.match.interface is vyos_defined %} match interface {{ rule_config.match.interface }} {% endif %} -{% if rule_config.match.ip is defined and rule_config.match.ip.address is defined and rule_config.match.ip.address.access_list is defined and rule_config.match.ip.address.access_list is not none %} +{% if rule_config.match.ip.address.access_list is vyos_defined %} match ip address {{ rule_config.match.ip.address.access_list }} {% endif %} -{% if rule_config.match.ip is defined and rule_config.match.ip.address is defined and rule_config.match.ip.address.prefix_list is defined and rule_config.match.ip.address.prefix_list is not none %} +{% if rule_config.match.ip.address.prefix_list is vyos_defined %} match ip address prefix-list {{ rule_config.match.ip.address.prefix_list }} {% endif %} -{% if rule_config.match.ip is defined and rule_config.match.ip.nexthop is defined and rule_config.match.ip.nexthop.access_list is defined and rule_config.match.ip.nexthop.access_list is not none %} +{% if rule_config.match.ip.nexthop.access_list is vyos_defined %} match ip next-hop {{ rule_config.match.ip.nexthop.access_list }} {% endif %} -{% if rule_config.match.ip is defined and rule_config.match.ip.nexthop is defined and rule_config.match.ip.nexthop.prefix_list is defined and rule_config.match.ip.nexthop.prefix_list is not none %} +{% if rule_config.match.ip.nexthop.prefix_list is vyos_defined %} match ip next-hop prefix-list {{ rule_config.match.ip.nexthop.prefix_list }} {% endif %} -{% if rule_config.match.ip is defined and rule_config.match.ip.route_source is defined and rule_config.match.ip.route_source.access_list is defined and rule_config.match.ip.route_source.access_list is not none %} +{% if rule_config.match.ip.route_source.access_list is vyos_defined %} match ip route-source {{ rule_config.match.ip.route_source.access_list }} {% endif %} -{% if rule_config.match.ip is defined and rule_config.match.ip.route_source is defined and rule_config.match.ip.route_source.prefix_list is defined and rule_config.match.ip.route_source.prefix_list is not none %} +{% if rule_config.match.ip.route_source.prefix_list is vyos_defined %} match ip route-source prefix-list {{ rule_config.match.ip.route_source.prefix_list }} {% endif %} -{% if rule_config.match.ipv6 is defined and rule_config.match.ipv6.address is defined and rule_config.match.ipv6.address.access_list is defined and rule_config.match.ipv6.address.access_list is not none %} +{% if rule_config.match.ipv6.address.access_list is vyos_defined %} match ipv6 address {{ rule_config.match.ipv6.address.access_list }} {% endif %} -{% if rule_config.match.ipv6 is defined and rule_config.match.ipv6.address is defined and rule_config.match.ipv6.address.prefix_list is defined and rule_config.match.ipv6.address.prefix_list is not none %} +{% if rule_config.match.ipv6.address.prefix_list is vyos_defined %} match ipv6 address prefix-list {{ rule_config.match.ipv6.address.prefix_list }} {% endif %} -{% if rule_config.match.ipv6 is defined and rule_config.match.ipv6.nexthop is defined and rule_config.match.ipv6.nexthop is not none %} +{% if rule_config.match.ipv6.nexthop is vyos_defined %} match ipv6 next-hop address {{ rule_config.match.ipv6.nexthop }} {% endif %} -{% if rule_config.match.large_community is defined and rule_config.match.large_community.large_community_list is defined and rule_config.match.large_community.large_community_list is not none %} +{% if rule_config.match.large_community.large_community_list is vyos_defined %} match large-community {{ rule_config.match.large_community.large_community_list }} {% endif %} -{% if rule_config.match.local_preference is defined and rule_config.match.local_preference is not none %} +{% if rule_config.match.local_preference is vyos_defined %} match local-preference {{ rule_config.match.local_preference }} {% endif %} -{% if rule_config.match.metric is defined and rule_config.match.metric is not none %} +{% if rule_config.match.metric is vyos_defined %} match metric {{ rule_config.match.metric }} {% endif %} -{% if rule_config.match.origin is defined and rule_config.match.origin is not none %} +{% if rule_config.match.origin is vyos_defined %} match origin {{ rule_config.match.origin }} {% endif %} -{% if rule_config.match.peer is defined and rule_config.match.peer is not none %} +{% if rule_config.match.peer is vyos_defined %} match peer {{ rule_config.match.peer }} {% endif %} -{% if rule_config.match.rpki is defined and rule_config.match.rpki is not none %} +{% if rule_config.match.rpki is vyos_defined %} match rpki {{ rule_config.match.rpki }} {% endif %} -{% if rule_config.match.tag is defined and rule_config.match.tag is not none %} +{% if rule_config.match.tag is vyos_defined %} match tag {{ rule_config.match.tag }} {% endif %} {% endif %} -{% if rule_config.on_match is defined and rule_config.on_match is not none %} -{% if rule_config.on_match.next is defined %} +{% if rule_config.on_match.next is vyos_defined %} on-match next -{% endif %} -{% if rule_config.on_match.goto is defined and rule_config.on_match.goto is not none %} +{% endif %} +{% if rule_config.on_match.goto is vyos_defined %} on-match goto {{ rule_config.on_match.goto }} -{% endif %} {% endif %} -{% if rule_config.set is defined and rule_config.set is not none %} -{% if rule_config.set.aggregator is defined and rule_config.set.aggregator.as is defined and rule_config.set.aggregator.ip is defined %} +{% if rule_config.set is vyos_defined %} +{% if rule_config.set.aggregator.as is vyos_defined and rule_config.set.aggregator.ip is vyos_defined %} set aggregator as {{ rule_config.set.aggregator.as }} {{ rule_config.set.aggregator.ip }} {% endif %} -{% if rule_config.set.as_path_exclude is defined and rule_config.set.as_path_exclude is not none %} +{% if rule_config.set.as_path_exclude is vyos_defined %} set as-path exclude {{ rule_config.set.as_path_exclude }} {% endif %} -{% if rule_config.set.as_path_prepend is defined and rule_config.set.as_path_prepend is not none %} +{% if rule_config.set.as_path_prepend is vyos_defined %} set as-path prepend {{ rule_config.set.as_path_prepend }} {% endif %} -{% if rule_config.set.atomic_aggregate is defined %} +{% if rule_config.set.atomic_aggregate is vyos_defined %} set atomic-aggregate {% endif %} -{% if rule_config.set.comm_list is defined and rule_config.set.comm_list.comm_list is defined and rule_config.set.comm_list.comm_list is not none %} - set comm-list {{ rule_config.set.comm_list.comm_list }} {{ 'delete' if rule_config.set.comm_list.delete is defined }} +{% if rule_config.set.comm_list.comm_list is vyos_defined %} + set comm-list {{ rule_config.set.comm_list.comm_list }} {{ 'delete' if rule_config.set.comm_list.delete is vyos_defined }} {% endif %} -{% if rule_config.set.community is defined and rule_config.set.community is not none %} +{% if rule_config.set.community is vyos_defined %} set community {{ rule_config.set.community }} {% endif %} -{% if rule_config.set.distance is defined and rule_config.set.distance is not none %} +{% if rule_config.set.distance is vyos_defined %} set distance {{ rule_config.set.distance }} {% endif %} -{% if rule_config.set.extcommunity is defined and rule_config.set.extcommunity.bandwidth is defined and rule_config.set.extcommunity.bandwidth is not none %} +{% if rule_config.set.extcommunity.bandwidth is vyos_defined %} set extcommunity bandwidth {{ rule_config.set.extcommunity.bandwidth }} {% endif %} -{% if rule_config.set.extcommunity is defined and rule_config.set.extcommunity.rt is defined and rule_config.set.extcommunity.rt is not none %} +{% if rule_config.set.extcommunity.rt is vyos_defined %} set extcommunity rt {{ rule_config.set.extcommunity.rt }} {% endif %} -{% if rule_config.set.extcommunity is defined and rule_config.set.extcommunity.soo is defined and rule_config.set.extcommunity.soo is not none %} +{% if rule_config.set.extcommunity.soo is vyos_defined %} set extcommunity soo {{ rule_config.set.extcommunity.soo }} {% endif %} -{% if rule_config.set.ip_next_hop is defined and rule_config.set.ip_next_hop is not none %} +{% if rule_config.set.ip_next_hop is vyos_defined %} set ip next-hop {{ rule_config.set.ip_next_hop }} {% endif %} -{% if rule_config.set.ipv6_next_hop is defined and rule_config.set.ipv6_next_hop.global is defined and rule_config.set.ipv6_next_hop.global is not none %} +{% if rule_config.set.ipv6_next_hop.global is vyos_defined %} set ipv6 next-hop global {{ rule_config.set.ipv6_next_hop.global }} {% endif %} -{% if rule_config.set.ipv6_next_hop is defined and rule_config.set.ipv6_next_hop.local is defined and rule_config.set.ipv6_next_hop.local is not none %} +{% if rule_config.set.ipv6_next_hop.local is vyos_defined %} set ipv6 next-hop local {{ rule_config.set.ipv6_next_hop.local }} {% endif %} -{% if rule_config.set.ipv6_next_hop is defined and rule_config.set.ipv6_next_hop.peer_address is defined %} +{% if rule_config.set.ipv6_next_hop.peer_address is vyos_defined %} set ipv6 next-hop peer-address {% endif %} -{% if rule_config.set.ipv6_next_hop is defined and rule_config.set.ipv6_next_hop.prefer_global is defined %} +{% if rule_config.set.ipv6_next_hop.prefer_global is vyos_defined %} set ipv6 next-hop prefer-global {% endif %} -{% if rule_config.set.large_community is defined and rule_config.set.large_community is not none %} +{% if rule_config.set.large_community is vyos_defined %} set large-community {{ rule_config.set.large_community }} {% endif %} -{% if rule_config.set.large_comm_list_delete is defined and rule_config.set.large_comm_list_delete is not none %} +{% if rule_config.set.large_comm_list_delete is vyos_defined %} set large-comm-list {{ rule_config.set.large_comm_list_delete }} delete {% endif %} -{% if rule_config.set.local_preference is defined and rule_config.set.local_preference is not none %} +{% if rule_config.set.local_preference is vyos_defined %} set local-preference {{ rule_config.set.local_preference }} {% endif %} -{% if rule_config.set.metric is defined and rule_config.set.metric is not none %} +{% if rule_config.set.metric is vyos_defined %} set metric {{ rule_config.set.metric }} {% endif %} -{% if rule_config.set.metric_type is defined and rule_config.set.metric_type is not none %} +{% if rule_config.set.metric_type is vyos_defined %} set metric-type {{ rule_config.set.metric_type }} {% endif %} -{% if rule_config.set.origin is defined and rule_config.set.origin is not none %} +{% if rule_config.set.origin is vyos_defined %} set origin {{ rule_config.set.origin }} {% endif %} -{% if rule_config.set.originator_id is defined and rule_config.set.originator_id is not none %} +{% if rule_config.set.originator_id is vyos_defined %} set originator-id {{ rule_config.set.originator_id }} {% endif %} -{% if rule_config.set.src is defined and rule_config.set.src is not none %} +{% if rule_config.set.src is vyos_defined %} set src {{ rule_config.set.src }} {% endif %} -{% if rule_config.set.table is defined and rule_config.set.table is not none %} +{% if rule_config.set.table is vyos_defined %} set table {{ rule_config.set.table }} {% endif %} -{% if rule_config.set.tag is defined and rule_config.set.tag is not none %} +{% if rule_config.set.tag is vyos_defined %} set tag {{ rule_config.set.tag }} {% endif %} -{% if rule_config.set.weight is defined and rule_config.set.weight is not none %} +{% if rule_config.set.weight is vyos_defined %} set weight {{ rule_config.set.weight }} {% endif %} {% endif %} -- cgit v1.2.3 From cb57b6e136a3caa38d45f50829e5f2cc011c93b5 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 3 Apr 2022 21:40:28 +0200 Subject: smoketest: ospfv3: remove obsolete "end=''" statement in VRF interface This is actually no longer required in FRR 8.2.2 --- smoketest/scripts/cli/test_protocols_ospfv3.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smoketest/scripts/cli/test_protocols_ospfv3.py b/smoketest/scripts/cli/test_protocols_ospfv3.py index f8dfe07d2..944190089 100755 --- a/smoketest/scripts/cli/test_protocols_ospfv3.py +++ b/smoketest/scripts/cli/test_protocols_ospfv3.py @@ -269,7 +269,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.assertIn(f'router ospf6', frrconfig) self.assertIn(f' ospf6 router-id {router_id}', frrconfig) - frrconfig = self.getFRRconfig(f'interface {vrf_iface}', end='', daemon='ospf6d') + frrconfig = self.getFRRconfig(f'interface {vrf_iface}', daemon='ospf6d') self.assertIn(f'interface {vrf_iface}', frrconfig) self.assertIn(f' ipv6 ospf6 bfd', frrconfig) -- cgit v1.2.3 From 807d054d8abb39e2a104115eac047e697356c721 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 3 Apr 2022 21:41:11 +0200 Subject: rip(ng): T4333: migrate to new vyos_defined Jinja2 test --- data/templates/frr/rip_ripng.frr.j2 | 18 +++++------ data/templates/frr/ripd.frr.tmpl | 62 ++++++++++++++++--------------------- data/templates/frr/ripngd.frr.tmpl | 46 ++++++++++++--------------- 3 files changed, 55 insertions(+), 71 deletions(-) diff --git a/data/templates/frr/rip_ripng.frr.j2 b/data/templates/frr/rip_ripng.frr.j2 index de180ee6b..3732371b2 100644 --- a/data/templates/frr/rip_ripng.frr.j2 +++ b/data/templates/frr/rip_ripng.frr.j2 @@ -1,36 +1,36 @@ -{% if default_information is defined and default_information.originate is defined %} +{% if default_information is vyos_defined %} default-information originate {% endif %} -{% if default_metric is defined and default_metric is not none %} +{% if default_metric is vyos_defined %} default-metric {{ default_metric }} {% endif %} -{% if passive_interface is defined and passive_interface is not none %} +{% if passive_interface is vyos_defined %} {% for interface in passive_interface %} passive-interface {{ interface }} {% endfor %} {% endif %} -{% if network is defined and network is not none %} +{% if network is vyos_defined %} {% for prefix in network %} network {{ prefix }} {% endfor %} {% endif %} -{% if interface is defined and interface is not none %} +{% if interface is vyos_defined %} {% for ifname in interface %} network {{ ifname }} {% endfor %} {% endif %} -{% if route is defined and route is not none %} +{% if route is vyos_defined %} {% for prefix in route %} route {{ prefix }} {% endfor %} {% endif %} {# timers have default values #} timers basic {{ timers['update'] }} {{ timers.timeout }} {{ timers.garbage_collection }} -{% if redistribute is defined and redistribute is not none %} +{% if redistribute is vyos_defined %} {% for protocol, protocol_config in redistribute.items() %} -{% if protocol == 'ospfv3' %} +{% if protocol is vyos_defined('ospfv3') %} {% set protocol = 'ospf6' %} {% endif %} - redistribute {{ protocol }} {{ 'metric ' + protocol_config.metric if protocol_config.metric is defined }} {{ 'route-map ' + protocol_config.route_map if protocol_config.route_map is defined }} + redistribute {{ protocol }} {{ 'metric ' ~ protocol_config.metric if protocol_config.metric is vyos_defined }} {{ 'route-map ' ~ protocol_config.route_map if protocol_config.route_map is vyos_defined }} {% endfor %} {% endif %} diff --git a/data/templates/frr/ripd.frr.tmpl b/data/templates/frr/ripd.frr.tmpl index c44bb6d27..2dbb93052 100644 --- a/data/templates/frr/ripd.frr.tmpl +++ b/data/templates/frr/ripd.frr.tmpl @@ -1,11 +1,11 @@ {# RIP key-chain definition #} -{% if interface is defined and interface is not none %} +{% if interface is vyos_defined %} {% for iface, iface_config in interface.items() %} -{% if iface_config.authentication is defined and iface_config.authentication.md5 is defined and iface_config.authentication.md5 is not none %} +{% if iface_config.authentication.md5 is vyos_defined %} key chain {{ iface }}-rip {% for key_id, key_options in iface_config.authentication.md5.items() %} key {{ key_id }} -{% if key_options.password is defined and key_options.password is not none %} +{% if key_options.password is vyos_defined %} key-string {{ key_options.password }} {% endif %} exit @@ -16,20 +16,20 @@ exit {% endif %} ! {# Interface specific configuration #} -{% if interface is defined and interface is not none %} +{% if interface is vyos_defined %} {% for iface, iface_config in interface.items() %} interface {{ iface }} -{% if iface_config.authentication is defined and iface_config.authentication.plaintext_password is defined and iface_config.authentication.plaintext_password is not none %} +{% if iface_config.authentication.plaintext_password is vyos_defined %} ip rip authentication mode text ip rip authentication string {{ iface_config.authentication.plaintext_password }} -{% elif iface_config.authentication is defined and iface_config.authentication.md5 is defined and iface_config.authentication.md5 is not none %} +{% elif iface_config.authentication.md5 is vyos_defined %} ip rip authentication key-chain {{ iface }}-rip ip rip authentication mode md5 {% endif %} -{% if iface_config.split_horizon is defined and iface_config.split_horizon.disable is defined %} +{% if iface_config.split_horizon.disable is vyos_defined %} no ip rip split-horizon {% endif %} -{% if iface_config.split_horizon is defined and iface_config.split_horizon.poison_reverse is defined %} +{% if iface_config.split_horizon.poison_reverse is vyos_defined %} ip rip split-horizon poisoned-reverse {% endif %} exit @@ -38,63 +38,55 @@ exit {% endif %} ! router rip -{% if default_distance is defined and default_distance is not none %} +{% if default_distance is vyos_defined %} distance {{ default_distance }} {% endif %} -{% if network_distance is defined and network_distance is not none %} +{% if network_distance is vyos_defined %} {% for network, network_config in network_distance.items() %} -{% if network_config.distance is defined and network_config.distance is not none %} +{% if network_config.distance is vyos_defined %} distance {{ network_config.distance }} {{ network }} {% endif %} {% endfor %} {% endif %} -{% if neighbor is defined and neighbor is not none %} +{% if neighbor is vyos_defined %} {% for address in neighbor %} neighbor {{ address }} {% endfor %} {% endif %} -{% if distribute_list is defined and distribute_list is not none %} -{% if distribute_list.access_list is defined and distribute_list.access_list is not none %} -{% if distribute_list.access_list.in is defined and distribute_list.access_list.in is not none %} +{% if distribute_list is vyos_defined %} +{% if distribute_list.access_list.in is vyos_defined %} distribute-list {{ distribute_list.access_list.in }} in -{% endif %} -{% if distribute_list.access_list.out is defined and distribute_list.access_list.out is not none %} +{% endif %} +{% if distribute_list.access_list.out is vyos_defined %} distribute-list {{ distribute_list.access_list.out }} out -{% endif %} {% endif %} -{% if distribute_list.interface is defined and distribute_list.interface is not none %} +{% if distribute_list.interface is vyos_defined %} {% for interface, interface_config in distribute_list.interface.items() %} -{% if interface_config.access_list is defined and interface_config.access_list is not none %} -{% if interface_config.access_list.in is defined and interface_config.access_list.in is not none %} +{% if interface_config.access_list.in is vyos_defined %} distribute-list {{ interface_config.access_list.in }} in {{ interface }} -{% endif %} -{% if interface_config.access_list.out is defined and interface_config.access_list.out is not none %} +{% endif %} +{% if interface_config.access_list.out is vyos_defined %} distribute-list {{ interface_config.access_list.out }} out {{ interface }} -{% endif %} {% endif %} -{% if interface_config.prefix_list is defined and interface_config.prefix_list is not none %} -{% if interface_config.prefix_list.in is defined and interface_config.prefix_list.in is not none %} +{% if interface_config.prefix_list.in is vyos_defined %} distribute-list prefix {{ interface_config.prefix_list.in }} in {{ interface }} -{% endif %} -{% if interface_config.prefix_list.out is defined and interface_config.prefix_list.out is not none %} +{% endif %} +{% if interface_config.prefix_list.out is vyos_defined %} distribute-list prefix {{ interface_config.prefix_list.out }} out {{ interface }} -{% endif %} {% endif %} {% endfor %} {% endif %} -{% if distribute_list.prefix_list is defined and distribute_list.prefix_list is not none %} -{% if distribute_list.prefix_list.in is defined and distribute_list.prefix_list.in is not none %} +{% if distribute_list.prefix_list.in is vyos_defined %} distribute-list prefix {{ distribute_list.prefix_list.in }} in -{% endif %} -{% if distribute_list.prefix_list.out is defined and distribute_list.prefix_list.out is not none %} +{% endif %} +{% if distribute_list.prefix_list.out is vyos_defined %} distribute-list prefix {{ distribute_list.prefix_list.out }} out -{% endif %} {% endif %} {% endif %} {% include 'frr/rip_ripng.frr.j2' %} exit ! -{% if route_map is defined and route_map is not none %} +{% if route_map is vyos_defined %} ip protocol rip route-map {{ route_map }} {% endif %} ! diff --git a/data/templates/frr/ripngd.frr.tmpl b/data/templates/frr/ripngd.frr.tmpl index ca7b9b5fb..06c61dd48 100644 --- a/data/templates/frr/ripngd.frr.tmpl +++ b/data/templates/frr/ripngd.frr.tmpl @@ -1,11 +1,11 @@ {# Interface specific configuration #} -{% if interface is defined and interface is not none %} +{% if interface is vyos_defined %} {% for iface, iface_config in interface.items() %} interface {{ iface }} -{% if iface_config.split_horizon is defined and iface_config.split_horizon.disable is defined %} +{% if iface_config.split_horizon.disable is vyos_defined %} no ipv6 rip split-horizon {% endif %} -{% if iface_config.split_horizon is defined and iface_config.split_horizon.poison_reverse is defined %} +{% if iface_config.split_horizon.poison_reverse is vyos_defined %} ipv6 rip split-horizon poisoned-reverse {% endif %} exit @@ -13,53 +13,45 @@ exit {% endif %} ! router ripng -{% if aggregate_address is defined and aggregate_address is not none %} +{% if aggregate_address is vyos_defined %} {% for prefix in aggregate_address %} aggregate-address {{ prefix }} {% endfor %} {% endif %} -{% if distribute_list is defined and distribute_list is not none %} -{% if distribute_list.access_list is defined and distribute_list.access_list is not none %} -{% if distribute_list.access_list.in is defined and distribute_list.access_list.in is not none %} +{% if distribute_list is vyos_defined %} +{% if distribute_list.access_list.in is vyos_defined %} ipv6 distribute-list {{ distribute_list.access_list.in }} in -{% endif %} -{% if distribute_list.access_list.out is defined and distribute_list.access_list.out is not none %} +{% endif %} +{% if distribute_list.access_list.out is vyos_defined %} ipv6 distribute-list {{ distribute_list.access_list.out }} out -{% endif %} {% endif %} -{% if distribute_list.interface is defined and distribute_list.interface is not none %} +{% if distribute_list.interface is vyos_defined %} {% for interface, interface_config in distribute_list.interface.items() %} -{% if interface_config.access_list is defined and interface_config.access_list is not none %} -{% if interface_config.access_list.in is defined and interface_config.access_list.in is not none %} +{% if interface_config.access_list.in is vyos_defined %} ipv6 distribute-list {{ interface_config.access_list.in }} in {{ interface }} -{% endif %} -{% if interface_config.access_list.out is defined and interface_config.access_list.out is not none %} +{% endif %} +{% if interface_config.access_list.out is vyos_defined %} ipv6 distribute-list {{ interface_config.access_list.out }} out {{ interface }} -{% endif %} {% endif %} -{% if interface_config.prefix_list is defined and interface_config.prefix_list is not none %} -{% if interface_config.prefix_list.in is defined and interface_config.prefix_list.in is not none %} +{% if interface_config.prefix_list.in is vyos_defined %} ipv6 distribute-list prefix {{ interface_config.prefix_list.in }} in {{ interface }} -{% endif %} -{% if interface_config.prefix_list.out is defined and interface_config.prefix_list.out is not none %} +{% endif %} +{% if interface_config.prefix_list.out is vyos_defined %} ipv6 distribute-list prefix {{ interface_config.prefix_list.out }} out {{ interface }} -{% endif %} {% endif %} {% endfor %} {% endif %} -{% if distribute_list.prefix_list is defined and distribute_list.prefix_list is not none %} -{% if distribute_list.prefix_list.in is defined and distribute_list.prefix_list.in is not none %} +{% if distribute_list.prefix_list.in is vyos_defined %} ipv6 distribute-list prefix {{ distribute_list.prefix_list.in }} in -{% endif %} -{% if distribute_list.prefix_list.out is defined and distribute_list.prefix_list.out is not none %} +{% endif %} +{% if distribute_list.prefix_list.out is vyos_defined %} ipv6 distribute-list prefix {{ distribute_list.prefix_list.out }} out -{% endif %} {% endif %} {% endif %} {% include 'frr/rip_ripng.frr.j2' %} exit ! -{% if route_map is defined and route_map is not none %} +{% if route_map is vyos_defined %} ipv6 protocol ripng route-map {{ route_map }} {% endif %} ! -- cgit v1.2.3 From 76abe709cd90b13b2af8d8b5ab8234b417a81c99 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 3 Apr 2022 21:41:41 +0200 Subject: rpki: T4333: migrate to new vyos_defined Jinja2 test --- data/templates/frr/rpki.frr.tmpl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/templates/frr/rpki.frr.tmpl b/data/templates/frr/rpki.frr.tmpl index 7f9823f6b..3f4fd3236 100644 --- a/data/templates/frr/rpki.frr.tmpl +++ b/data/templates/frr/rpki.frr.tmpl @@ -1,17 +1,17 @@ ! {# as FRR does not support deleting the entire rpki section we leave it in place even when it's empty #} rpki -{% if cache is defined and cache is not none %} +{% if cache is vyos_defined %} {% for peer, peer_config in cache.items() %} {# port is mandatory and preference uses a default value #} -{% if peer_config.ssh is defined and peer_config.ssh.username is defined and peer_config.ssh.username is not none %} +{% if peer_config.ssh.username is vyos_defined %} rpki cache {{ peer | replace('_', '-') }} {{ peer_config.port }} {{ peer_config.ssh.username }} {{ peer_config.ssh.private_key_file }} {{ peer_config.ssh.public_key_file }} {{ peer_config.ssh.known_hosts_file }} preference {{ peer_config.preference }} {% else %} rpki cache {{ peer | replace('_', '-') }} {{ peer_config.port }} preference {{ peer_config.preference }} {% endif %} {% endfor %} {% endif %} -{% if polling_period is defined and polling_period is not none %} +{% if polling_period is vyos_defined %} rpki polling_period {{ polling_period }} {% endif %} exit -- cgit v1.2.3 From f2a5b57223c607dea615cc09f9cb24e0910c8a08 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 3 Apr 2022 21:42:37 +0200 Subject: vrf/vni: T4333: migrate to new vyos_defined Jinja2 test --- data/templates/frr/vrf-vni.frr.tmpl | 4 ++-- data/templates/frr/vrf.route-map.frr.tmpl | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/templates/frr/vrf-vni.frr.tmpl b/data/templates/frr/vrf-vni.frr.tmpl index 299c9719e..916b5d05d 100644 --- a/data/templates/frr/vrf-vni.frr.tmpl +++ b/data/templates/frr/vrf-vni.frr.tmpl @@ -1,7 +1,7 @@ -{% if name is defined and name is not none %} +{% if name is vyos_defined %} {% for vrf, vrf_config in name.items() %} vrf {{ vrf }} -{% if vrf_config.vni is defined and vrf_config.vni is not none %} +{% if vrf_config.vni is vyos_defined %} vni {{ vrf_config.vni }} {% endif %} exit-vrf diff --git a/data/templates/frr/vrf.route-map.frr.tmpl b/data/templates/frr/vrf.route-map.frr.tmpl index cb0e07616..5e0c56a7b 100644 --- a/data/templates/frr/vrf.route-map.frr.tmpl +++ b/data/templates/frr/vrf.route-map.frr.tmpl @@ -1,10 +1,10 @@ ! -{% if vrf is defined and vrf is not none and route_map is defined and route_map is not none %} +{% if vrf is vyos_defined and route_map is vyos_defined %} vrf {{ vrf }} ip protocol {{ protocol }} route-map {{ route_map }} exit-vrf ! -{% elif route_map is defined and route_map is not none %} +{% elif route_map is vyos_defined %} ip protocol {{ protocol }} route-map {{ route_map }} {% endif %} ! -- cgit v1.2.3 From e02d8145e4f126203ff00949b7af6119658f7123 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 3 Apr 2022 21:43:10 +0200 Subject: static: T4333: migrate to new vyos_defined Jinja2 test --- data/templates/frr/static_routes_macro.j2 | 22 +++++++++++----------- data/templates/frr/staticd.frr.tmpl | 20 ++++++++++---------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/data/templates/frr/static_routes_macro.j2 b/data/templates/frr/static_routes_macro.j2 index 8359357b7..0b242a868 100644 --- a/data/templates/frr/static_routes_macro.j2 +++ b/data/templates/frr/static_routes_macro.j2 @@ -1,24 +1,24 @@ {% macro static_routes(ip_ipv6, prefix, prefix_config, table=None) %} -{% if prefix_config.blackhole is defined %} -{{ ip_ipv6 }} route {{ prefix }} blackhole {{ prefix_config.blackhole.distance if prefix_config.blackhole.distance is defined }} {{ 'tag ' + prefix_config.blackhole.tag if prefix_config.blackhole.tag is defined }} {{ 'table ' + table if table is defined and table is not none }} +{% if prefix_config.blackhole is vyos_defined %} +{{ ip_ipv6 }} route {{ prefix }} blackhole {{ prefix_config.blackhole.distance if prefix_config.blackhole.distance is vyos_defined }} {{ 'tag ' ~ prefix_config.blackhole.tag if prefix_config.blackhole.tag is vyos_defined }} {{ 'table ' ~ table if table is vyos_defined and table is not none }} {% endif %} -{% if prefix_config.reject is defined %} -{{ ip_ipv6 }} route {{ prefix }} reject {{ prefix_config.reject.distance if prefix_config.reject.distance is defined }} {{ 'tag ' + prefix_config.reject.tag if prefix_config.reject.tag is defined }} {{ 'table ' + table if table is defined and table is not none }} +{% if prefix_config.reject is vyos_defined %} +{{ ip_ipv6 }} route {{ prefix }} reject {{ prefix_config.reject.distance if prefix_config.reject.distance is vyos_defined }} {{ 'tag ' ~ prefix_config.reject.tag if prefix_config.reject.tag is vyos_defined }} {{ 'table ' ~ table if table is vyos_defined }} {% endif %} -{% if prefix_config.dhcp_interface is defined and prefix_config.dhcp_interface is not none %} +{% if prefix_config.dhcp_interface is vyos_defined %} {% set next_hop = prefix_config.dhcp_interface | get_dhcp_router %} -{% if next_hop is defined and next_hop is not none %} -{{ ip_ipv6 }} route {{ prefix }} {{ next_hop }} {{ prefix_config.dhcp_interface }} {{ 'table ' + table if table is defined and table is not none }} +{% if next_hop is vyos_defined %} +{{ ip_ipv6 }} route {{ prefix }} {{ next_hop }} {{ prefix_config.dhcp_interface }} {{ 'table ' ~ table if table is vyos_defined }} {% endif %} {% endif %} -{% if prefix_config.interface is defined and prefix_config.interface is not none %} +{% if prefix_config.interface is vyos_defined %} {% for interface, interface_config in prefix_config.interface.items() if interface_config.disable is not defined %} -{{ ip_ipv6 }} route {{ prefix }} {{ interface }} {{ interface_config.distance if interface_config.distance is defined }} {{ 'nexthop-vrf ' + interface_config.vrf if interface_config.vrf is defined }} {{ 'table ' + table if table is defined and table is not none }} +{{ ip_ipv6 }} route {{ prefix }} {{ interface }} {{ interface_config.distance if interface_config.distance is vyos_defined }} {{ 'nexthop-vrf ' ~ interface_config.vrf if interface_config.vrf is vyos_defined }} {{ 'table ' ~ table if table is vyos_defined }} {% endfor %} {% endif %} -{% if prefix_config.next_hop is defined and prefix_config.next_hop is not none %} +{% if prefix_config.next_hop is vyos_defined and prefix_config.next_hop is not none %} {% for next_hop, next_hop_config in prefix_config.next_hop.items() if next_hop_config.disable is not defined %} -{{ ip_ipv6 }} route {{ prefix }} {{ next_hop }} {{ next_hop_config.interface if next_hop_config.interface is defined }} {{ next_hop_config.distance if next_hop_config.distance is defined }} {{ 'nexthop-vrf ' + next_hop_config.vrf if next_hop_config.vrf is defined }} {{ 'table ' + table if table is defined and table is not none }} +{{ ip_ipv6 }} route {{ prefix }} {{ next_hop }} {{ next_hop_config.interface if next_hop_config.interface is vyos_defined }} {{ next_hop_config.distance if next_hop_config.distance is vyos_defined }} {{ 'nexthop-vrf ' ~ next_hop_config.vrf if next_hop_config.vrf is vyos_defined }} {{ 'table ' ~ table if table is vyos_defined}} {% endfor %} {% endif %} {% endmacro %} diff --git a/data/templates/frr/staticd.frr.tmpl b/data/templates/frr/staticd.frr.tmpl index 5d833228a..c7138b12b 100644 --- a/data/templates/frr/staticd.frr.tmpl +++ b/data/templates/frr/staticd.frr.tmpl @@ -2,7 +2,7 @@ ! {% set ip_prefix = 'ip' %} {% set ipv6_prefix = 'ipv6' %} -{% if vrf is defined and vrf is not none %} +{% if vrf is vyos_defined %} {# We need to add an additional whitespace in front of the prefix #} {# when VRFs are in use, thus we use a variable for prefix handling #} {% set ip_prefix = ' ip' %} @@ -10,40 +10,40 @@ vrf {{ vrf }} {% endif %} {# IPv4 routing #} -{% if route is defined and route is not none %} +{% if route is vyos_defined %} {% for prefix, prefix_config in route.items() %} {{ static_routes(ip_prefix, prefix, prefix_config) }} {%- endfor -%} {% endif %} {# IPv4 default routes from DHCP interfaces #} -{% if dhcp is defined and dhcp is not none %} +{% if dhcp is vyos_defined %} {% for interface, interface_config in dhcp.items() %} {% set next_hop = interface | get_dhcp_router %} -{% if next_hop is defined and next_hop is not none %} +{% if next_hop is vyos_defined %} {{ ip_prefix }} route 0.0.0.0/0 {{ next_hop }} {{ interface }} tag 210 {{ interface_config.distance }} {% endif %} {% endfor %} {% endif %} {# IPv6 routing #} -{% if route6 is defined and route6 is not none %} +{% if route6 is vyos_defined %} {% for prefix, prefix_config in route6.items() %} {{ static_routes(ipv6_prefix, prefix, prefix_config) }} {%- endfor -%} {% endif %} -{% if vrf is defined and vrf is not none %} +{% if vrf is vyos_defined %} exit-vrf {% endif %} ! {# Policy route tables #} -{% if table is defined and table is not none %} +{% if table is vyos_defined %} {% for table_id, table_config in table.items() %} -{% if table_config.route is defined and table_config.route is not none %} +{% if table_config.route is vyos_defined %} {% for prefix, prefix_config in table_config.route.items() %} {{ static_routes('ip', prefix, prefix_config, table_id) }} {%- endfor -%} {% endif %} ! -{% if table_config.route6 is defined and table_config.route6 is not none %} +{% if table_config.route6 is vyos_defined %} {% for prefix, prefix_config in table_config.route6.items() %} {{ static_routes('ipv6', prefix, prefix_config, table_id) }} {%- endfor -%} @@ -52,7 +52,7 @@ vrf {{ vrf }} {% endfor %} {% endif %} ! -{% if route_map is defined and route_map is not none %} +{% if route_map is vyos_defined %} ip protocol static route-map {{ route_map }} ! {% endif %} -- cgit v1.2.3 From dbd922397dcfe6df3f0e766787d9aee69410dd58 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Mon, 4 Apr 2022 10:12:08 +0000 Subject: ipoe: T2580: Add pools and gateway options Add new feature to allow to use named pools Can be used also with Radius attribute 'Framed-Pool' set service ipoe-server client-ip-pool name POOL1 gateway-address '192.0.2.1' set service ipoe-server client-ip-pool name POOL1 subnet '192.0.2.0/24' --- data/templates/accel-ppp/ipoe.config.tmpl | 28 +++++++++++++++++++--- .../accel-ppp/client-ip-pool-subnet-single.xml.i | 15 ++++++++++++ interface-definitions/service_ipoe-server.xml.in | 16 +++++++++++++ src/conf_mode/service_ipoe-server.py | 23 ++++++++++++++---- 4 files changed, 74 insertions(+), 8 deletions(-) create mode 100644 interface-definitions/include/accel-ppp/client-ip-pool-subnet-single.xml.i diff --git a/data/templates/accel-ppp/ipoe.config.tmpl b/data/templates/accel-ppp/ipoe.config.tmpl index 1cf2ab0be..92c2d5715 100644 --- a/data/templates/accel-ppp/ipoe.config.tmpl +++ b/data/templates/accel-ppp/ipoe.config.tmpl @@ -25,11 +25,21 @@ level=5 verbose=1 {% for interface in interfaces %} {% if interface.vlan_mon %} -interface=re:{{ interface.name }}\.\d+,{% else %}interface={{ interface.name }},{% endif %}shared={{ interface.shared }},mode={{ interface.mode }},ifcfg={{ interface.ifcfg }},range={{ interface.range }},start={{ interface.sess_start }},ipv6=1 +interface=re:{{ interface.name }}\.\d+,{% else %}interface={{ interface.name }},{% endif %}shared={{ interface.shared }},mode={{ interface.mode }},ifcfg={{ interface.ifcfg }}{{ ',range=' + interface.range if interface.range is defined and interface.range is not none }},start={{ interface.sess_start }},ipv6=1 {% endfor %} -{% if auth_mode == 'noauth' %} +{% if auth_mode == 'noauth' %} noauth=1 -{% elif auth_mode == 'local' %} +{% if client_named_ip_pool %} +{% for pool in client_named_ip_pool %} +{% if pool.subnet is defined %} +ip-pool={{ pool.name }} +{% endif %} +{% if pool.gateway_address is defined %} +gw-ip-address={{ pool.gateway_address }}/{{ pool.subnet.split('/')[1] }} +{% endif %} +{% endfor%} +{% endif %} +{% elif auth_mode == 'local' %} username=ifname password=csid {% endif %} @@ -61,6 +71,18 @@ verbose=1 [ipv6-dhcp] verbose=1 +{% if client_named_ip_pool %} +[ip-pool] +{% for pool in client_named_ip_pool %} +{% if pool.subnet is defined %} +{{ pool.subnet }},name={{ pool.name }} +{% endif %} +{% if pool.gateway_address is defined %} +gw-ip-address={{ pool.gateway_address }}/{{ pool.subnet.split('/')[1] }} +{% endif %} +{% endfor%} +{% endif %} + {% if client_ipv6_pool %} [ipv6-pool] {% for p in client_ipv6_pool %} diff --git a/interface-definitions/include/accel-ppp/client-ip-pool-subnet-single.xml.i b/interface-definitions/include/accel-ppp/client-ip-pool-subnet-single.xml.i new file mode 100644 index 000000000..e5918b765 --- /dev/null +++ b/interface-definitions/include/accel-ppp/client-ip-pool-subnet-single.xml.i @@ -0,0 +1,15 @@ + + + + Client IP subnet (CIDR notation) + + ipv4net + IPv4 address and prefix length + + + + + Not a valid CIDR formatted prefix + + + diff --git a/interface-definitions/service_ipoe-server.xml.in b/interface-definitions/service_ipoe-server.xml.in index b19acab56..1325ba10d 100644 --- a/interface-definitions/service_ipoe-server.xml.in +++ b/interface-definitions/service_ipoe-server.xml.in @@ -112,6 +112,22 @@ #include + + + Client IP pools and gateway setting + + + + + Pool name + + + #include + #include + + + + #include diff --git a/src/conf_mode/service_ipoe-server.py b/src/conf_mode/service_ipoe-server.py index f676fdbbe..2ebee8018 100755 --- a/src/conf_mode/service_ipoe-server.py +++ b/src/conf_mode/service_ipoe-server.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018-2020 VyOS maintainers and contributors +# Copyright (C) 2018-2022 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 @@ -41,6 +41,7 @@ default_config_data = { 'interfaces': [], 'dnsv4': [], 'dnsv6': [], + 'client_named_ip_pool': [], 'client_ipv6_pool': [], 'client_ipv6_delegate_prefix': [], 'radius_server': [], @@ -219,6 +220,22 @@ def get_config(config=None): conf.set_level(base_path) + # Named client-ip-pool + if conf.exists(['client-ip-pool', 'name']): + for name in conf.list_nodes(['client-ip-pool', 'name']): + tmp = { + 'name': name, + 'gateway_address': '', + 'subnet': '' + } + + if conf.exists(['client-ip-pool', 'name', name, 'gateway-address']): + tmp['gateway_address'] += conf.return_value(['client-ip-pool', 'name', name, 'gateway-address']) + if conf.exists(['client-ip-pool', 'name', name, 'subnet']): + tmp['subnet'] += conf.return_value(['client-ip-pool', 'name', name, 'subnet']) + + ipoe['client_named_ip_pool'].append(tmp) + if conf.exists(['client-ipv6-pool', 'prefix']): for prefix in conf.list_nodes(['client-ipv6-pool', 'prefix']): tmp = { @@ -254,10 +271,6 @@ def verify(ipoe): if not ipoe['interfaces']: raise ConfigError('No IPoE interface configured') - for interface in ipoe['interfaces']: - if not interface['range']: - raise ConfigError(f'No IPoE client subnet defined on interface "{ interface }"') - if len(ipoe['dnsv4']) > 2: raise ConfigError('Not more then two IPv4 DNS name-servers can be configured') -- cgit v1.2.3 From 76a049c7d30f3e64989b9697d65d15bfd3005316 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 4 Apr 2022 20:24:38 +0200 Subject: wwan: T4338: changing interface description should not trigger reconnect Changing the WWAN interface description will trigger an interface reconnect. Reconnects should only be triggered in changes to the connection parameters like bond interfaces. --- src/conf_mode/interfaces-wwan.py | 75 ++++++++++++++++++++++++++++------------ 1 file changed, 52 insertions(+), 23 deletions(-) diff --git a/src/conf_mode/interfaces-wwan.py b/src/conf_mode/interfaces-wwan.py index ec01d3cc5..d5e259c74 100755 --- a/src/conf_mode/interfaces-wwan.py +++ b/src/conf_mode/interfaces-wwan.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2021 VyOS maintainers and contributors +# Copyright (C) 2020-2022 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 @@ -21,6 +21,7 @@ from time import sleep from vyos.config import Config from vyos.configdict import get_interface_dict +from vyos.configdict import leaf_node_changed from vyos.configverify import verify_authentication from vyos.configverify import verify_interface_exists from vyos.configverify import verify_redirect @@ -51,6 +52,32 @@ def get_config(config=None): base = ['interfaces', 'wwan'] wwan = get_interface_dict(conf, base) + # We should only terminate the WWAN session if critical parameters change. + # All parameters that can be changed on-the-fly (like interface description) + # should not lead to a reconnect! + tmp = leaf_node_changed(conf, ['address']) + if tmp: wwan.update({'shutdown_required': {}}) + + tmp = leaf_node_changed(conf, ['apn']) + if tmp: wwan.update({'shutdown_required': {}}) + + tmp = leaf_node_changed(conf, ['disable']) + if tmp: wwan.update({'shutdown_required': {}}) + + tmp = leaf_node_changed(conf, ['vrf']) + # leaf_node_changed() returns a list, as VRF is a non-multi node, there + # will be only one list element + if tmp: wwan.update({'vrf_old': tmp[0]}) + + tmp = leaf_node_changed(conf, ['authentication', 'user']) + if tmp: wwan.update({'shutdown_required': {}}) + + tmp = leaf_node_changed(conf, ['authentication', 'password']) + if tmp: wwan.update({'shutdown_required': {}}) + + tmp = leaf_node_changed(conf, ['ipv6', 'address', 'autoconf']) + if tmp: wwan.update({'shutdown_required': {}}) + # We need to know the amount of other WWAN interfaces as ModemManager needs # to be started or stopped. conf.set_level(base) @@ -115,11 +142,12 @@ def apply(wwan): break sleep(0.250) - # we only need the modem number. wwan0 -> 0, wwan1 -> 1 - modem = wwan['ifname'].lstrip('wwan') - base_cmd = f'mmcli --modem {modem}' - # Number of bearers is limited - always disconnect first - cmd(f'{base_cmd} --simple-disconnect') + if 'shutdown_required' in wwan: + # we only need the modem number. wwan0 -> 0, wwan1 -> 1 + modem = wwan['ifname'].lstrip('wwan') + base_cmd = f'mmcli --modem {modem}' + # Number of bearers is limited - always disconnect first + cmd(f'{base_cmd} --simple-disconnect') w = WWANIf(wwan['ifname']) if 'deleted' in wwan or 'disable' in wwan: @@ -136,24 +164,25 @@ def apply(wwan): return None - ip_type = 'ipv4' - slaac = dict_search('ipv6.address.autoconf', wwan) != None - if 'address' in wwan: - if 'dhcp' in wwan['address'] and ('dhcpv6' in wwan['address'] or slaac): - ip_type = 'ipv4v6' - elif 'dhcpv6' in wwan['address'] or slaac: - ip_type = 'ipv6' - elif 'dhcp' in wwan['address']: - ip_type = 'ipv4' - - options = f'ip-type={ip_type},apn=' + wwan['apn'] - if 'authentication' in wwan: - options += ',user={user},password={password}'.format(**wwan['authentication']) - - command = f'{base_cmd} --simple-connect="{options}"' - call(command, stdout=DEVNULL) - w.update(wwan) + if 'shutdown_required' in wwan: + ip_type = 'ipv4' + slaac = dict_search('ipv6.address.autoconf', wwan) != None + if 'address' in wwan: + if 'dhcp' in wwan['address'] and ('dhcpv6' in wwan['address'] or slaac): + ip_type = 'ipv4v6' + elif 'dhcpv6' in wwan['address'] or slaac: + ip_type = 'ipv6' + elif 'dhcp' in wwan['address']: + ip_type = 'ipv4' + + options = f'ip-type={ip_type},apn=' + wwan['apn'] + if 'authentication' in wwan: + options += ',user={user},password={password}'.format(**wwan['authentication']) + command = f'{base_cmd} --simple-connect="{options}"' + call(command, stdout=DEVNULL) + + w.update(wwan) return None if __name__ == '__main__': -- cgit v1.2.3 From 671abc96ac607226e208ac94b87a33851c144945 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 4 Apr 2022 20:26:32 +0200 Subject: wwan: T4339: tab-completion results in "No such file or directory" --- interface-definitions/interfaces-wwan.xml.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface-definitions/interfaces-wwan.xml.in b/interface-definitions/interfaces-wwan.xml.in index 7007a67ae..c46bc58a7 100644 --- a/interface-definitions/interfaces-wwan.xml.in +++ b/interface-definitions/interfaces-wwan.xml.in @@ -7,7 +7,7 @@ Wireless Modem (WWAN) Interface 350 - + ^wwan[0-9]+$ -- cgit v1.2.3 From 175b0a082808955adba811f18424a126e798dd32 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 4 Apr 2022 20:26:54 +0200 Subject: wifi: T4339: tab-completion results in "No such file or directory" --- interface-definitions/interfaces-wireless.xml.in | 3 +++ 1 file changed, 3 insertions(+) diff --git a/interface-definitions/interfaces-wireless.xml.in b/interface-definitions/interfaces-wireless.xml.in index eebe8f841..ef56c208a 100644 --- a/interface-definitions/interfaces-wireless.xml.in +++ b/interface-definitions/interfaces-wireless.xml.in @@ -6,6 +6,9 @@ Wireless (WiFi/WLAN) Network Interface 318 + + + ^wlan[0-9]+$ -- cgit v1.2.3 From e66c45ce7a664ecb26d21a215975777bef0fcd71 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 4 Apr 2022 21:05:12 +0200 Subject: smoketest: ssh: verify login of valid and invalid test user In order to test for proper system authentication and security setup a new testcase is added which performs an SSH login and command execution with a predefined user. The result (output of uname -a) must match the output if the command is run natively. We also try to login as an invalid user - this is not allowed to work. --- smoketest/scripts/cli/test_service_ssh.py | 49 +++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/smoketest/scripts/cli/test_service_ssh.py b/smoketest/scripts/cli/test_service_ssh.py index a54c03919..8c23b1b62 100755 --- a/smoketest/scripts/cli/test_service_ssh.py +++ b/smoketest/scripts/cli/test_service_ssh.py @@ -15,9 +15,12 @@ # along with this program. If not, see . import os +import paramiko import re import unittest +from pwd import getpwall + from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError @@ -167,5 +170,51 @@ class TestServiceSSH(VyOSUnitTestSHIM.TestCase): # delete VRF self.cli_delete(['vrf', 'name', vrf]) + def test_ssh_login(self): + # Perform SSH login and command execution with a predefined user. The + # result (output of uname -a) must match the output if the command is + # run natively. + # + # We also try to login as an invalid user - this is not allowed to work. + + def ssh_send_cmd(command, username, password, host='localhost'): + """ SSH command execution helper """ + # Try to login via SSH + ssh_client = paramiko.SSHClient() + ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh_client.connect(hostname='localhost', username=username, password=password) + _, stdout, stderr = ssh_client.exec_command(command) + output = stdout.read().decode().strip() + error = stderr.read().decode().strip() + ssh_client.close() + return output, error + + test_user = 'ssh_test' + test_pass = 'v2i57DZs8idUwMN3VC92' + test_command = 'uname -a' + + self.cli_set(base_path) + self.cli_set(['system', 'login', 'user', test_user, 'authentication', 'plaintext-password', test_pass]) + + # commit changes + self.cli_commit() + + # Login with proper credentials + output, error = ssh_send_cmd(test_command, test_user, test_pass) + # verify login + self.assertFalse(error) + self.assertEqual(output, cmd(test_command)) + + # Login with invalid credentials + with self.assertRaises(paramiko.ssh_exception.AuthenticationException): + output, error = ssh_send_cmd(test_command, 'invalid_user', 'invalid_password') + + self.cli_delete(['system', 'login', 'user', test_user]) + self.cli_commit() + + # After deletion the test user is not allowed to remain in /etc/passwd + usernames = [x[0] for x in getpwall()] + self.assertNotIn(test_user, usernames) + if __name__ == '__main__': unittest.main(verbosity=2) -- cgit v1.2.3 From 4bb0adf535e12dc962a0ebea0f5a96f612a76a5d Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 4 Apr 2022 22:17:29 +0200 Subject: smoketest: ssh: verify SSH service is stopped on removal --- smoketest/scripts/cli/test_service_ssh.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/smoketest/scripts/cli/test_service_ssh.py b/smoketest/scripts/cli/test_service_ssh.py index 8c23b1b62..9ed263655 100755 --- a/smoketest/scripts/cli/test_service_ssh.py +++ b/smoketest/scripts/cli/test_service_ssh.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2020 VyOS maintainers and contributors +# Copyright (C) 2019-2022 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 @@ -25,6 +25,7 @@ from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError from vyos.util import cmd +from vyos.util import is_systemd_service_running from vyos.util import process_named_running from vyos.util import read_file @@ -52,6 +53,9 @@ class TestServiceSSH(VyOSUnitTestSHIM.TestCase): cls.cli_delete(cls, base_path) def tearDown(self): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + # delete testing SSH config self.cli_delete(base_path) self.cli_commit() @@ -60,6 +64,11 @@ class TestServiceSSH(VyOSUnitTestSHIM.TestCase): self.assertTrue(os.path.isfile(key_dsa)) self.assertTrue(os.path.isfile(key_ed25519)) + # Established SSH connections remains running after service is stopped. + # We can not use process_named_running here - we rather need to check + # that the systemd service is no longer running + self.assertFalse(is_systemd_service_running(PROCESS_NAME)) + def test_ssh_default(self): # Check if SSH service runs with default settings - used for checking # behavior of in XML definition @@ -72,9 +81,6 @@ class TestServiceSSH(VyOSUnitTestSHIM.TestCase): port = get_config_value('Port')[0] self.assertEqual('22', port) - # Check for running process - self.assertTrue(process_named_running(PROCESS_NAME)) - def test_ssh_single_listen_address(self): # Check if SSH service can be configured and runs self.cli_set(base_path + ['port', '1234']) @@ -111,9 +117,6 @@ class TestServiceSSH(VyOSUnitTestSHIM.TestCase): keepalive = get_config_value('ClientAliveInterval')[0] self.assertTrue("100" in keepalive) - # Check for running process - self.assertTrue(process_named_running(PROCESS_NAME)) - def test_ssh_multiple_listen_addresses(self): # Check if SSH service can be configured and runs with multiple # listen ports and listen-addresses @@ -138,9 +141,6 @@ class TestServiceSSH(VyOSUnitTestSHIM.TestCase): for address in addresses: self.assertIn(address, tmp) - # Check for running process - self.assertTrue(process_named_running(PROCESS_NAME)) - def test_ssh_vrf(self): # Check if SSH service can be bound to given VRF port = '22' @@ -160,9 +160,6 @@ class TestServiceSSH(VyOSUnitTestSHIM.TestCase): tmp = get_config_value('Port') self.assertIn(port, tmp) - # Check for running process - self.assertTrue(process_named_running(PROCESS_NAME)) - # Check for process in VRF tmp = cmd(f'ip vrf pids {vrf}') self.assertIn(PROCESS_NAME, tmp) -- cgit v1.2.3 From efa753bc661d04967237e7ec3d72d3757230aaf9 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 4 Apr 2022 22:21:25 +0200 Subject: login: T4341: disable user account prior to deletion --- src/conf_mode/system-login.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py index 4dd7f936d..9b8f194fb 100755 --- a/src/conf_mode/system-login.py +++ b/src/conf_mode/system-login.py @@ -250,6 +250,9 @@ def apply(login): if 'rm_users' in login: for user in login['rm_users']: try: + # Disable user to prevent re-login + call(f'usermod -s /sbin/nologin {user}') + # Logout user if he is still logged in if user in list(set([tmp[0] for tmp in users()])): print(f'{user} is logged in, forcing logout!') -- cgit v1.2.3 From 6a04ff2840dfcfcad7a1cb93baf210370fa8871e Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 4 Apr 2022 22:29:26 +0200 Subject: smoketest: login: verify test accounts are properly deleted --- smoketest/scripts/cli/test_system_login.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/smoketest/scripts/cli/test_system_login.py b/smoketest/scripts/cli/test_system_login.py index 095300de3..1131b6f93 100755 --- a/smoketest/scripts/cli/test_system_login.py +++ b/smoketest/scripts/cli/test_system_login.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2020 VyOS maintainers and contributors +# Copyright (C) 2019-2022 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 @@ -23,6 +23,7 @@ from base_vyostest_shim import VyOSUnitTestSHIM from distutils.version import LooseVersion from platform import release as kernel_version from subprocess import Popen, PIPE +from pwd import getpwall from vyos.configsession import ConfigSessionError from vyos.util import cmd @@ -52,6 +53,11 @@ class TestSystemLogin(VyOSUnitTestSHIM.TestCase): self.cli_commit() + # After deletion, a user is not allowed to remain in /etc/passwd + usernames = [x[0] for x in getpwall()] + for user in users: + self.assertNotIn(user, usernames) + def test_add_linux_system_user(self): # We are not allowed to re-use a username already taken by the Linux # base system -- cgit v1.2.3 From 796178f69ce09e28ab9f20c7b5e1ce97ef00a1ff Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 4 Apr 2022 23:01:38 +0200 Subject: login: T4341: busy wait on userdel(8) until the account was deleted successfully --- src/conf_mode/system-login.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py index 9b8f194fb..c9c6aa187 100755 --- a/src/conf_mode/system-login.py +++ b/src/conf_mode/system-login.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2021 VyOS maintainers and contributors +# Copyright (C) 2020-2022 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 @@ -23,6 +23,7 @@ from pwd import getpwall from pwd import getpwnam from spwd import getspnam from sys import exit +from time import sleep from vyos.config import Config from vyos.configdict import dict_merge @@ -31,6 +32,7 @@ from vyos.template import render from vyos.template import is_ipv4 from vyos.util import cmd from vyos.util import call +from vyos.util import run from vyos.util import DEVNULL from vyos.util import dict_search from vyos.xml import defaults @@ -256,10 +258,16 @@ def apply(login): # Logout user if he is still logged in if user in list(set([tmp[0] for tmp in users()])): print(f'{user} is logged in, forcing logout!') - call(f'pkill -HUP -u {user}') - - # Remove user account but leave home directory to be safe - call(f'userdel --remove {user}', stderr=DEVNULL) + # re-run command until user is logged out + while run(f'pkill -HUP -u {user}'): + sleep(0.250) + + # Remove user account but leave home directory in place. Re-run + # command until user is removed - userdel might return 8 as + # SSH sessions are not all yet properly cleaned away, thus we + # simply re-run the command until the account wen't away + while run(f'userdel --remove {user}', stderr=DEVNULL): + sleep(0.250) except Exception as e: raise ConfigError(f'Deleting user "{user}" raised exception: {e}') -- cgit v1.2.3 From 806ff50bf1a970d731c2227f9d2cd2342b8a1b4e Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 5 Apr 2022 09:00:42 +0200 Subject: dns: forwarding: T3804: bugfix DHCP name-servers used for recursion Commit 2ecf7a9f9c ('name-server: T3804: merge "system name-servers-dhcp" into "system name-server"') missed out an old dictionary key "system_name_server_dhcp" and thus system nameservers configured via DHCP did not get used for the DNS forwar recursor. --- src/conf_mode/dns_forwarding.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index 23a16df63..98dc87ccd 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018-2020 VyOS maintainers and contributors +# Copyright (C) 2018-2022 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 @@ -16,6 +16,7 @@ import os +from netifaces import interfaces from sys import exit from glob import glob @@ -65,10 +66,6 @@ def get_config(config=None): if conf.exists(base_nameservers): dns.update({'system_name_server': conf.return_values(base_nameservers)}) - base_nameservers_dhcp = ['system', 'name-servers-dhcp'] - if conf.exists(base_nameservers_dhcp): - dns.update({'system_name_server_dhcp': conf.return_values(base_nameservers_dhcp)}) - if 'authoritative_domain' in dns: dns['authoritative_zones'] = [] dns['authoritative_zone_errors'] = [] @@ -339,10 +336,15 @@ def apply(dns): hc.delete_name_server_tags_recursor(['system']) # add dhcp nameserver tags for configured interfaces - if 'system_name_server_dhcp' in dns: - for interface in dns['system_name_server_dhcp']: - hc.add_name_server_tags_recursor(['dhcp-' + interface, - 'dhcpv6-' + interface ]) + if 'system_name_server' in dns: + for interface in dns['system_name_server']: + # system_name_server key contains both IP addresses and interface + # names (DHCP) to use DNS servers. We need to check if the + # value is an interface name - only if this is the case, add the + # interface based DNS forwarder. + if interface in interfaces(): + hc.add_name_server_tags_recursor(['dhcp-' + interface, + 'dhcpv6-' + interface ]) # hostsd will generate the forward-zones file # the list and keys() are required as get returns a dict, not list -- cgit v1.2.3 From d6e22b28887c7a3f7d2f8b955c2e90bcadaeeeba Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Tue, 5 Apr 2022 11:20:54 +0000 Subject: interfaces: T4331: Fix assign link-local static IPv6 addr to vrf If we have link-local static address and vrf, for example: set interfaces ethernet eth2 address 'fe80::5200:ff:fe55:222/64' set interfaces ethernet eth2 vrf 'foo' This IPv6 address was assigned before vrf, as result after attaching the intreface to vrf we lose this static linklocal address DEBUG/IFCONFIG cmd 'ip addr add fe80::5200:ff:fe55:222/64 dev eth2' DEBUG/IFCONFIG cmd 'ip link set dev eth2 master foo' DEBUG/IFCONFIG cmd 'ip addr add fe80::5208:ff:fe13:2/64 dev eth2' This commit fixes this, the address is assigned after vrf assign --- python/vyos/ifconfig/interface.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index d6906a061..585a605e4 100755 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -1434,9 +1434,6 @@ class Interface(Control): else: self.del_addr(addr) - for addr in new_addr: - self.add_addr(addr) - # start DHCPv6 client when only PD was configured if dhcpv6pd: self.set_dhcpv6(True) @@ -1451,6 +1448,10 @@ class Interface(Control): # checked before self.set_vrf(config.get('vrf', '')) + # Add this section after vrf T4331 + for addr in new_addr: + self.add_addr(addr) + # Configure MSS value for IPv4 TCP connections tmp = dict_search('ip.adjust_mss', config) value = tmp if (tmp != None) else '0' -- cgit v1.2.3 From 8b7a0155af1291438fb533933bf93e82f960c99b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 6 Apr 2022 08:47:10 +0200 Subject: dns: forwarding: T3804: fix warning message about "system name-server" --- src/conf_mode/dns_forwarding.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index 98dc87ccd..fa9b21f20 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -269,9 +269,8 @@ def verify(dns): raise ConfigError('Invalid authoritative records have been defined') if 'system' in dns: - if not ('system_name_server' in dns or 'system_name_server_dhcp' in dns): - print("Warning: No 'system name-server' or 'system " \ - "name-servers-dhcp' configured") + if not 'system_name_server' in dns: + print('Warning: No "system name-server" configured') return None -- cgit v1.2.3 From 5b57c536b5f599deea2106aad7aea95c465bc0c0 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 6 Apr 2022 09:40:19 +0200 Subject: smoketest: vrf: T4331: IPv6 link-local addresses not configured for interface in VRF --- smoketest/scripts/cli/test_vrf.py | 48 +++++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/smoketest/scripts/cli/test_vrf.py b/smoketest/scripts/cli/test_vrf.py index 5daea589c..d6dd68f8b 100755 --- a/smoketest/scripts/cli/test_vrf.py +++ b/smoketest/scripts/cli/test_vrf.py @@ -216,11 +216,11 @@ class VRFTest(VyOSUnitTestSHIM.TestCase): # commit changes self.cli_commit() - # Verify & cleanup + # Verify VRF assignmant for interface in self._interfaces: - # os.readlink resolves to: '../../../../../virtual/net/foovrf' - tmp = os.readlink(f'/sys/class/net/{interface}/master').split('/')[-1] - self.assertEqual(tmp, vrf) + tmp = get_interface_config(interface) + self.assertEqual(vrf, tmp['master']) + # cleanup section = Section.section(interface) self.cli_delete(['interfaces', section, interface, 'vrf']) @@ -262,5 +262,45 @@ class VRFTest(VyOSUnitTestSHIM.TestCase): # Increment table ID for the next run table = str(int(table) + 1) + def test_vrf_link_local_ip_addresses(self): + # Testcase for issue T4331 + table = '100' + vrf = 'orange' + interface = 'dum9998' + addresses = ['192.0.2.1/26', '2001:db8:9998::1/64', 'fe80::1/64'] + + for address in addresses: + self.cli_set(['interfaces', 'dummy', interface, 'address', address]) + + # Create dummy interfaces + self.cli_commit() + + # ... and verify IP addresses got assigned + for address in addresses: + self.assertTrue(is_intf_addr_assigned(interface, address)) + + # Move interface to VRF + self.cli_set(base_path + ['name', vrf, 'table', table]) + self.cli_set(['interfaces', 'dummy', interface, 'vrf', vrf]) + + # Apply VRF config + self.cli_commit() + # Ensure VRF got created + self.assertIn(vrf, interfaces()) + # ... and IP addresses are still assigned + for address in addresses: + self.assertTrue(is_intf_addr_assigned(interface, address)) + # Verify VRF table ID + tmp = get_interface_config(vrf) + self.assertEqual(int(table), tmp['linkinfo']['info_data']['table']) + + # Verify interface is assigned to VRF + tmp = get_interface_config(interface) + self.assertEqual(vrf, tmp['master']) + + # Delete Interface + self.cli_delete(['interfaces', 'dummy', interface]) + self.cli_commit() + if __name__ == '__main__': unittest.main(verbosity=2) -- cgit v1.2.3 From 2e4b965cb235e242e1ee1ca0f0193ccc5b6d4140 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 6 Apr 2022 11:24:33 +0200 Subject: smoketest: http: verify nginx config file --- smoketest/scripts/cli/test_service_https.py | 33 ++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/smoketest/scripts/cli/test_service_https.py b/smoketest/scripts/cli/test_service_https.py index 8e69efd9c..287a2e143 100755 --- a/smoketest/scripts/cli/test_service_https.py +++ b/smoketest/scripts/cli/test_service_https.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2020 VyOS maintainers and contributors +# Copyright (C) 2019-2022 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 @@ -22,8 +22,27 @@ from vyos.util import run base_path = ['service', 'https'] pki_base = ['pki'] -cert_data = 'MIICFDCCAbugAwIBAgIUfMbIsB/ozMXijYgUYG80T1ry+mcwCgYIKoZIzj0EAwIwWTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNvbWUtQ2l0eTENMAsGA1UECgwEVnlPUzESMBAGA1UEAwwJVnlPUyBUZXN0MB4XDTIxMDcyMDEyNDUxMloXDTI2MDcxOTEyNDUxMlowWTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNvbWUtQ2l0eTENMAsGA1UECgwEVnlPUzESMBAGA1UEAwwJVnlPUyBUZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE01HrLcNttqq4/PtoMua8rMWEkOdBu7vP94xzDO7A8C92ls1v86eePy4QllKCzIw3QxBIoCuH2peGRfWgPRdFsKNhMF8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMB0GA1UdDgQWBBSu+JnU5ZC4mkuEpqg2+Mk4K79oeDAKBggqhkjOPQQDAgNHADBEAiBEFdzQ/Bc3LftzngrY605UhA6UprHhAogKgROv7iR4QgIgEFUxTtW3xXJcnUPWhhUFhyZoqfn8dE93+dm/LDnp7C0=' -key_data = 'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPLpD0Ohhoq0g4nhx2KMIuze7ucKUt/lBEB2wc03IxXyhRANCAATTUestw222qrj8+2gy5rysxYSQ50G7u8/3jHMM7sDwL3aWzW/zp54/LhCWUoLMjDdDEEigK4fal4ZF9aA9F0Ww' + +cert_data = """ +MIICFDCCAbugAwIBAgIUfMbIsB/ozMXijYgUYG80T1ry+mcwCgYIKoZIzj0EAwIw +WTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNv +bWUtQ2l0eTENMAsGA1UECgwEVnlPUzESMBAGA1UEAwwJVnlPUyBUZXN0MB4XDTIx +MDcyMDEyNDUxMloXDTI2MDcxOTEyNDUxMlowWTELMAkGA1UEBhMCR0IxEzARBgNV +BAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNvbWUtQ2l0eTENMAsGA1UECgwEVnlP +UzESMBAGA1UEAwwJVnlPUyBUZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE +01HrLcNttqq4/PtoMua8rMWEkOdBu7vP94xzDO7A8C92ls1v86eePy4QllKCzIw3 +QxBIoCuH2peGRfWgPRdFsKNhMF8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E +BAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMB0GA1UdDgQWBBSu ++JnU5ZC4mkuEpqg2+Mk4K79oeDAKBggqhkjOPQQDAgNHADBEAiBEFdzQ/Bc3Lftz +ngrY605UhA6UprHhAogKgROv7iR4QgIgEFUxTtW3xXJcnUPWhhUFhyZoqfn8dE93 ++dm/LDnp7C0= +""" + +key_data = """ +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPLpD0Ohhoq0g4nhx +2KMIuze7ucKUt/lBEB2wc03IxXyhRANCAATTUestw222qrj8+2gy5rysxYSQ50G7 +u8/3jHMM7sDwL3aWzW/zp54/LhCWUoLMjDdDEEigK4fal4ZF9aA9F0Ww +""" class TestHTTPSService(VyOSUnitTestSHIM.TestCase): def setUp(self): @@ -61,9 +80,13 @@ class TestHTTPSService(VyOSUnitTestSHIM.TestCase): ret = run('sudo /usr/sbin/nginx -t') self.assertEqual(ret, 0) + nginx_config = read_file('/etc/nginx/sites-enabled/default') + self.assertIn(f'listen {address}:{port} ssl;', nginx_config) + self.assertIn(f'ssl_protocols TLSv1.2 TLSv1.3;', nginx_config) + def test_certificate(self): - self.cli_set(pki_base + ['certificate', 'test_https', 'certificate', cert_data]) - self.cli_set(pki_base + ['certificate', 'test_https', 'private', 'key', key_data]) + self.cli_set(pki_base + ['certificate', 'test_https', 'certificate', cert_data.replace('\n','')]) + self.cli_set(pki_base + ['certificate', 'test_https', 'private', 'key', key_data.replace('\n','')]) self.cli_set(base_path + ['certificates', 'certificate', 'test_https']) -- cgit v1.2.3 From d321a62815aa09ef3a200f1e621d33870b7de627 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 6 Apr 2022 11:25:03 +0200 Subject: smoketest: http: test API authentication --- smoketest/scripts/cli/test_service_https.py | 39 ++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/smoketest/scripts/cli/test_service_https.py b/smoketest/scripts/cli/test_service_https.py index 287a2e143..2901cafa8 100755 --- a/smoketest/scripts/cli/test_service_https.py +++ b/smoketest/scripts/cli/test_service_https.py @@ -15,12 +15,17 @@ # along with this program. If not, see . import unittest +import urllib3 + +from requests import request from base_vyostest_shim import VyOSUnitTestSHIM +from vyos.util import read_file from vyos.util import run -base_path = ['service', 'https'] +urllib3.disable_warnings() +base_path = ['service', 'https'] pki_base = ['pki'] cert_data = """ @@ -95,5 +100,37 @@ class TestHTTPSService(VyOSUnitTestSHIM.TestCase): ret = run('sudo /usr/sbin/nginx -t') self.assertEqual(ret, 0) + def test_api_auth(self): + vhost_id = 'example' + address = '127.0.0.1' + port = '443' + name = 'localhost' + + key = 'MySuperSecretVyOS' + self.cli_set(base_path + ['api', 'keys', 'id', 'key-01', 'key', key]) + + test_path = base_path + ['virtual-host', vhost_id] + self.cli_set(test_path + ['listen-address', address]) + self.cli_set(test_path + ['listen-port', port]) + self.cli_set(test_path + ['server-name', name]) + + self.cli_commit() + + nginx_config = read_file('/etc/nginx/sites-enabled/default') + self.assertIn(f'listen {address}:{port} ssl;', nginx_config) + self.assertIn(f'ssl_protocols TLSv1.2 TLSv1.3;', nginx_config) + + url = f'https://{address}/retrieve' + payload = {'data': '{"op": "showConfig", "path": []}', 'key': f'{key}'} + headers = {} + r = request('POST', url, verify=False, headers=headers, data=payload) + # Must get HTTP code 200 on success + self.assertEqual(r.status_code, 200) + + payload_invalid = {'data': '{"op": "showConfig", "path": []}', 'key': 'invalid'} + r = request('POST', url, verify=False, headers=headers, data=payload_invalid) + # Must get HTTP code 401 on invalid key (Unauthorized) + self.assertEqual(r.status_code, 401) + if __name__ == '__main__': unittest.main(verbosity=2) -- cgit v1.2.3 From 8626a7b8602ea67e1286f054509a4d0dd69f363e Mon Sep 17 00:00:00 2001 From: srividya0208 Date: Wed, 6 Apr 2022 06:25:28 -0400 Subject: op-comm: ospf : error for ospf neighbor address command Error received when executed the sh ip ospf neighbor address 33.33.33.33 % Unknown command: sh ip ospf neighbor address 33.33.33.33 --- op-mode-definitions/include/ospf-common.xml.i | 9 --------- 1 file changed, 9 deletions(-) diff --git a/op-mode-definitions/include/ospf-common.xml.i b/op-mode-definitions/include/ospf-common.xml.i index 0edc3c37f..23769c8ba 100644 --- a/op-mode-definitions/include/ospf-common.xml.i +++ b/op-mode-definitions/include/ospf-common.xml.i @@ -523,15 +523,6 @@ ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ - - - Show IPv4 OSPF neighbor information for specified IP address - - <x.x.x.x> - - - ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ - Show detailed IPv4 OSPF neighbor information -- cgit v1.2.3 From c514cea0ad94a00838530cd07f87723be372ea8f Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Tue, 5 Apr 2022 20:40:45 +0200 Subject: firewall: T4345: Fix incorrect rule limit rate syntax --- interface-definitions/include/firewall/common-rule.xml.i | 6 +++--- python/vyos/firewall.py | 2 +- smoketest/configs/dialup-router-complex | 3 +++ smoketest/scripts/cli/test_firewall.py | 5 +++++ src/conf_mode/firewall.py | 6 ++++++ 5 files changed, 18 insertions(+), 4 deletions(-) diff --git a/interface-definitions/include/firewall/common-rule.xml.i b/interface-definitions/include/firewall/common-rule.xml.i index 353804990..cd80b7e28 100644 --- a/interface-definitions/include/firewall/common-rule.xml.i +++ b/interface-definitions/include/firewall/common-rule.xml.i @@ -66,11 +66,11 @@ Maximum average matching rate - u32:0-4294967295 - Maximum average matching rate + txt + integer/unit (Example: 5/minute) - + ^\d+/(second|minute|hour|day)$ diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py index 55ce318e7..ff8623592 100644 --- a/python/vyos/firewall.py +++ b/python/vyos/firewall.py @@ -174,7 +174,7 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name): if 'limit' in rule_conf: if 'rate' in rule_conf['limit']: - output.append(f'limit rate {rule_conf["limit"]["rate"]}/second') + output.append(f'limit rate {rule_conf["limit"]["rate"]}') if 'burst' in rule_conf['limit']: output.append(f'burst {rule_conf["limit"]["burst"]} packets') diff --git a/smoketest/configs/dialup-router-complex b/smoketest/configs/dialup-router-complex index 1b62deb5c..ac5ff5e99 100644 --- a/smoketest/configs/dialup-router-complex +++ b/smoketest/configs/dialup-router-complex @@ -498,6 +498,9 @@ firewall { destination { port 110,995 } + limit { + rate "10/minute" + } protocol tcp } rule 123 { diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py index ecc0c29a0..16b020e07 100755 --- a/smoketest/scripts/cli/test_firewall.py +++ b/smoketest/scripts/cli/test_firewall.py @@ -88,6 +88,10 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'destination', 'port', '8888']) self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'tcp', 'flags', 'syn']) self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'tcp', 'flags', 'not', 'ack']) + self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'action', 'accept']) + self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'protocol', 'tcp']) + self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'destination', 'port', '22']) + self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'limit', 'rate', '5/minute']) self.cli_set(['interfaces', 'ethernet', 'eth0', 'firewall', 'in', 'name', 'smoketest']) @@ -97,6 +101,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): ['iifname "eth0"', 'jump NAME_smoketest'], ['saddr 172.16.20.10', 'daddr 172.16.10.10', 'return'], ['tcp flags & (syn | ack) == syn', 'tcp dport { 8888 }', 'reject'], + ['tcp dport { 22 }', 'limit rate 5/minute', 'return'], ['smoketest default-action', 'drop'] ] diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index 41df1b84a..f33198a49 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -171,6 +171,12 @@ def verify_rule(firewall, rule_conf, ipv6): if {'match_frag', 'match_non_frag'} <= set(rule_conf['fragment']): raise ConfigError('Cannot specify both "match-frag" and "match-non-frag"') + if 'limit' in rule_conf: + if 'rate' in rule_conf['limit']: + rate_int = re.sub(r'\D', '', rule_conf['limit']['rate']) + if int(rate_int) < 1: + raise ConfigError('Limit rate integer cannot be less than 1') + if 'ipsec' in rule_conf: if {'match_ipsec', 'match_non_ipsec'} <= set(rule_conf['ipsec']): raise ConfigError('Cannot specify both "match-ipsec" and "match-non-ipsec"') -- cgit v1.2.3 From 25069b949eb97c1308ec927b53ac0c2d9d118467 Mon Sep 17 00:00:00 2001 From: Bracken Date: Wed, 6 Apr 2022 20:17:58 +0100 Subject: dns: forwarding: T4343: add CLI option for PowerDNS network-timeout Makes the powerdns `network-timeout` setting configurable via: `service dns forwarding timeout`. The powerdns default is 1500ms, VyOS now explicitly sets the same default value or the configured value so that the setting can have a readily apparent default in the help, rather than the user having to know it's powerdns. --- data/templates/dns-forwarding/recursor.conf.tmpl | 3 +++ interface-definitions/dns-forwarding.xml.in | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/data/templates/dns-forwarding/recursor.conf.tmpl b/data/templates/dns-forwarding/recursor.conf.tmpl index 02efe903b..d4ec80a3a 100644 --- a/data/templates/dns-forwarding/recursor.conf.tmpl +++ b/data/templates/dns-forwarding/recursor.conf.tmpl @@ -19,6 +19,9 @@ max-cache-entries={{ cache_size }} # negative TTL for NXDOMAIN max-negative-ttl={{ negative_ttl }} +# timeout +network-timeout={{ timeout }} + # ignore-hosts-file export-etc-hosts={{ 'no' if ignore_hosts_file is defined else 'yes' }} diff --git a/interface-definitions/dns-forwarding.xml.in b/interface-definitions/dns-forwarding.xml.in index a2e809da8..08501a4b5 100644 --- a/interface-definitions/dns-forwarding.xml.in +++ b/interface-definitions/dns-forwarding.xml.in @@ -598,6 +598,19 @@ 3600 + + + Number of milliseconds to wait for a remote authoritative server to respond + + u32:10-60000 + Network timeout in milliseconds + + + + + + 1500 + #include -- cgit v1.2.3 From 37c6d9fae5172b0342f94212e6483b3aa8fcd673 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 23 Mar 2022 10:07:41 +0100 Subject: qos: T4284: support mirror and redirect on all interface types --- interface-definitions/interfaces-dummy.xml.in | 1 + interface-definitions/interfaces-geneve.xml.in | 1 + interface-definitions/interfaces-l2tpv3.xml.in | 1 + interface-definitions/interfaces-loopback.xml.in | 1 + interface-definitions/interfaces-macsec.xml.in | 1 + interface-definitions/interfaces-openvpn.xml.in | 1 + interface-definitions/interfaces-pppoe.xml.in | 1 + .../interfaces-pseudo-ethernet.xml.in | 1 + interface-definitions/interfaces-tunnel.xml.in | 1 + interface-definitions/interfaces-vti.xml.in | 1 + interface-definitions/interfaces-vxlan.xml.in | 1 + interface-definitions/interfaces-wireguard.xml.in | 1 + interface-definitions/interfaces-wireless.xml.in | 1 + interface-definitions/interfaces-wwan.xml.in | 1 + python/vyos/configverify.py | 27 +++---- python/vyos/ifconfig/interface.py | 84 ++++++++++++---------- src/conf_mode/interfaces-bonding.py | 6 +- src/conf_mode/interfaces-bridge.py | 6 +- src/conf_mode/interfaces-dummy.py | 4 +- src/conf_mode/interfaces-ethernet.py | 6 +- src/conf_mode/interfaces-geneve.py | 4 +- src/conf_mode/interfaces-l2tpv3.py | 4 +- src/conf_mode/interfaces-loopback.py | 4 +- src/conf_mode/interfaces-macsec.py | 4 +- src/conf_mode/interfaces-openvpn.py | 2 + src/conf_mode/interfaces-pppoe.py | 4 +- src/conf_mode/interfaces-pseudo-ethernet.py | 4 +- src/conf_mode/interfaces-tunnel.py | 4 +- src/conf_mode/interfaces-vti.py | 4 +- src/conf_mode/interfaces-vxlan.py | 4 +- src/conf_mode/interfaces-wireguard.py | 4 +- src/conf_mode/interfaces-wireless.py | 4 +- src/conf_mode/interfaces-wwan.py | 4 +- 33 files changed, 107 insertions(+), 90 deletions(-) diff --git a/interface-definitions/interfaces-dummy.xml.in b/interface-definitions/interfaces-dummy.xml.in index 109ed1b50..988d87502 100644 --- a/interface-definitions/interfaces-dummy.xml.in +++ b/interface-definitions/interfaces-dummy.xml.in @@ -29,6 +29,7 @@ #include + #include #include #include #include diff --git a/interface-definitions/interfaces-geneve.xml.in b/interface-definitions/interfaces-geneve.xml.in index aa5809e60..5f2c6bc05 100644 --- a/interface-definitions/interfaces-geneve.xml.in +++ b/interface-definitions/interfaces-geneve.xml.in @@ -50,6 +50,7 @@ + #include #include #include #include diff --git a/interface-definitions/interfaces-l2tpv3.xml.in b/interface-definitions/interfaces-l2tpv3.xml.in index 680170b0f..0dcabf7a0 100644 --- a/interface-definitions/interfaces-l2tpv3.xml.in +++ b/interface-definitions/interfaces-l2tpv3.xml.in @@ -58,6 +58,7 @@ #include #include #include + #include #include 1488 diff --git a/interface-definitions/interfaces-loopback.xml.in b/interface-definitions/interfaces-loopback.xml.in index ffffc0220..1e093d95b 100644 --- a/interface-definitions/interfaces-loopback.xml.in +++ b/interface-definitions/interfaces-loopback.xml.in @@ -26,6 +26,7 @@ #include + #include #include #include diff --git a/interface-definitions/interfaces-macsec.xml.in b/interface-definitions/interfaces-macsec.xml.in index 311e95c2f..fbdd1562a 100644 --- a/interface-definitions/interfaces-macsec.xml.in +++ b/interface-definitions/interfaces-macsec.xml.in @@ -23,6 +23,7 @@ #include #include #include + #include Security/Encryption Settings diff --git a/interface-definitions/interfaces-openvpn.xml.in b/interface-definitions/interfaces-openvpn.xml.in index 73e30e590..761f8bcad 100644 --- a/interface-definitions/interfaces-openvpn.xml.in +++ b/interface-definitions/interfaces-openvpn.xml.in @@ -168,6 +168,7 @@ #include + #include Hashing Algorithm diff --git a/interface-definitions/interfaces-pppoe.xml.in b/interface-definitions/interfaces-pppoe.xml.in index 1d888236e..adf5f4040 100644 --- a/interface-definitions/interfaces-pppoe.xml.in +++ b/interface-definitions/interfaces-pppoe.xml.in @@ -102,6 +102,7 @@ + #include #include 1492 diff --git a/interface-definitions/interfaces-pseudo-ethernet.xml.in b/interface-definitions/interfaces-pseudo-ethernet.xml.in index 7baeac537..aed2052f5 100644 --- a/interface-definitions/interfaces-pseudo-ethernet.xml.in +++ b/interface-definitions/interfaces-pseudo-ethernet.xml.in @@ -27,6 +27,7 @@ #include #include #include + #include #include #include diff --git a/interface-definitions/interfaces-tunnel.xml.in b/interface-definitions/interfaces-tunnel.xml.in index bc9297c86..b31f22552 100644 --- a/interface-definitions/interfaces-tunnel.xml.in +++ b/interface-definitions/interfaces-tunnel.xml.in @@ -107,6 +107,7 @@ Invalid encapsulation, must be one of: erspan, gre, gretap, ip6erspan, ip6gre, ip6gretap, ipip, sit, ipip6 or ip6ip6 + #include Multicast operation over tunnel diff --git a/interface-definitions/interfaces-vti.xml.in b/interface-definitions/interfaces-vti.xml.in index 538194c2b..d66fc952e 100644 --- a/interface-definitions/interfaces-vti.xml.in +++ b/interface-definitions/interfaces-vti.xml.in @@ -34,6 +34,7 @@ #include #include #include + #include #include #include #include diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in index 18abf9f20..b1a2dfaec 100644 --- a/interface-definitions/interfaces-vxlan.xml.in +++ b/interface-definitions/interfaces-vxlan.xml.in @@ -53,6 +53,7 @@ #include #include #include + #include #include #include diff --git a/interface-definitions/interfaces-wireguard.xml.in b/interface-definitions/interfaces-wireguard.xml.in index 2f130c6f2..51565cfe6 100644 --- a/interface-definitions/interfaces-wireguard.xml.in +++ b/interface-definitions/interfaces-wireguard.xml.in @@ -23,6 +23,7 @@ #include #include #include + #include 1420 diff --git a/interface-definitions/interfaces-wireless.xml.in b/interface-definitions/interfaces-wireless.xml.in index ef56c208a..a16a7841e 100644 --- a/interface-definitions/interfaces-wireless.xml.in +++ b/interface-definitions/interfaces-wireless.xml.in @@ -566,6 +566,7 @@ g + #include Wireless physical device diff --git a/interface-definitions/interfaces-wwan.xml.in b/interface-definitions/interfaces-wwan.xml.in index c46bc58a7..33bc0cb3d 100644 --- a/interface-definitions/interfaces-wwan.xml.in +++ b/interface-definitions/interfaces-wwan.xml.in @@ -31,6 +31,7 @@ #include #include #include + #include #include 1430 diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 7f1258575..df2c5775a 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -178,31 +178,26 @@ def verify_eapol(config): if 'certificate' not in ca_cert: raise ConfigError('Invalid CA certificate specified for EAPoL') -def verify_mirror(config): +def verify_mirror_redirect(config): """ Common helper function used by interface implementations to perform - recurring validation of mirror interface configuration. + recurring validation of mirror and redirect interface configuration via tc(8) It makes no sense to mirror traffic back at yourself! """ + if {'mirror', 'redirect'} <= set(config): + raise ConfigError('Mirror and redirect can not be enabled at the same time!') + if 'mirror' in config: for direction, mirror_interface in config['mirror'].items(): if mirror_interface == config['ifname']: raise ConfigError(f'Can not mirror "{direction}" traffic back ' \ 'the originating interface!') -def verify_redirect(config): - """ - Common helper function used by interface implementations to perform - recurring validation of the redirect interface configuration. - - It makes no sense to mirror and redirect traffic at the same time! - """ - if {'mirror', 'redirect'} <= set(config): - raise ConfigError('Can not do both redirect and mirror') - if dict_search('traffic_policy.in', config) != None: - raise ConfigError('Can not use ingress policy and redirect') + # XXX: support combination of limiting and redirect/mirror - this is an + # artificial limitation + raise ConfigError('Can not use ingress policy tigether with mirror or redirect!') def verify_authentication(config): """ @@ -328,7 +323,7 @@ def verify_vlan_config(config): verify_dhcpv6(vlan) verify_address(vlan) verify_vrf(vlan) - verify_redirect(vlan) + verify_mirror_redirect(vlan) verify_mtu_parent(vlan, config) # 802.1ad (Q-in-Q) VLANs @@ -337,7 +332,7 @@ def verify_vlan_config(config): verify_dhcpv6(s_vlan) verify_address(s_vlan) verify_vrf(s_vlan) - verify_redirect(s_vlan) + verify_mirror_redirect(s_vlan) verify_mtu_parent(s_vlan, config) for c_vlan in s_vlan.get('vif_c', {}): @@ -345,7 +340,7 @@ def verify_vlan_config(config): verify_dhcpv6(c_vlan) verify_address(c_vlan) verify_vrf(c_vlan) - verify_redirect(c_vlan) + verify_mirror_redirect(c_vlan) verify_mtu_parent(c_vlan, config) verify_mtu_parent(c_vlan, s_vlan) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 585a605e4..76164ca32 100755 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -1294,48 +1294,60 @@ class Interface(Control): if os.path.isfile(config_file): os.remove(config_file) - def set_mirror(self): + def set_mirror_redirect(self): # Please refer to the document for details # - https://man7.org/linux/man-pages/man8/tc.8.html # - https://man7.org/linux/man-pages/man8/tc-mirred.8.html # Depening if we are the source or the target interface of the port # mirror we need to setup some variables. source_if = self._config['ifname'] - config = self._config.get('mirror', None) + mirror_config = None + if 'mirror' in self._config: + mirror_config = self._config['mirror'] if 'is_mirror_intf' in self._config: source_if = next(iter(self._config['is_mirror_intf'])) - config = self._config['is_mirror_intf'][source_if].get('mirror', None) - - # Check configuration stored by old perl code before delete T3782/T4056 - if not 'redirect' in self._config and not 'traffic_policy' in self._config: - # Please do not clear the 'set $? = 0 '. It's meant to force a return of 0 - # Remove existing mirroring rules - delete_tc_cmd = f'tc qdisc del dev {source_if} handle ffff: ingress 2> /dev/null;' - delete_tc_cmd += f'tc qdisc del dev {source_if} handle 1: root prio 2> /dev/null;' - delete_tc_cmd += 'set $?=0' - self._popen(delete_tc_cmd) - - # Bail out early if nothing needs to be configured - if not config: - return - - for direction, mirror_if in config.items(): - if mirror_if not in interfaces(): - continue - - if direction == 'ingress': - handle = 'ffff: ingress' - parent = 'ffff:' - elif direction == 'egress': - handle = '1: root prio' - parent = '1:' - - # Mirror egress traffic - mirror_cmd = f'tc qdisc add dev {source_if} handle {handle}; ' - # Export the mirrored traffic to the interface - mirror_cmd += f'tc filter add dev {source_if} parent {parent} protocol all prio 10 u32 match u32 0 0 flowid 1:1 action mirred egress mirror dev {mirror_if}' - self._popen(mirror_cmd) + mirror_config = self._config['is_mirror_intf'][source_if].get('mirror', None) + + redirect_config = None + + # clear existing ingess - ignore errors (e.g. "Error: Cannot find specified + # qdisc on specified device") - we simply cleanup all stuff here + self._popen(f'tc qdisc del dev {source_if} parent ffff: 2>/dev/null'); + self._popen(f'tc qdisc del dev {source_if} parent 1: 2>/dev/null'); + + # Apply interface mirror policy + if mirror_config: + for direction, target_if in mirror_config.items(): + if target_if not in interfaces(): + continue + + if direction == 'ingress': + handle = 'ffff: ingress' + parent = 'ffff:' + elif direction == 'egress': + handle = '1: root prio' + parent = '1:' + + # Mirror egress traffic + mirror_cmd = f'tc qdisc add dev {source_if} handle {handle}; ' + # Export the mirrored traffic to the interface + mirror_cmd += f'tc filter add dev {source_if} parent {parent} protocol '\ + f'all prio 10 u32 match u32 0 0 flowid 1:1 action mirred '\ + f'egress mirror dev {target_if}' + _, err = self._popen(mirror_cmd) + if err: print('tc qdisc(filter for mirror port failed') + + # Apply interface traffic redirection policy + elif 'redirect' in self._config: + _, err = self._popen(f'tc qdisc add dev {source_if} handle ffff: ingress') + if err: print(f'tc qdisc add for redirect failed!') + + target_if = self._config['redirect'] + _, err = self._popen(f'tc filter add dev {source_if} parent ffff: protocol '\ + f'all prio 10 u32 match u32 0 0 flowid 1:1 action mirred '\ + f'egress redirect dev {target_if}') + if err: print('tc filter add for redirect failed') def set_xdp(self, state): """ @@ -1562,8 +1574,8 @@ class Interface(Control): # eXpress Data Path - highly experimental self.set_xdp('xdp' in config) - # configure port mirror - self.set_mirror() + # configure interface mirror or redirection target + self.set_mirror_redirect() # Enable/Disable of an interface must always be done at the end of the # derived class to make use of the ref-counting set_admin_state() @@ -1723,5 +1735,5 @@ class VLANIf(Interface): return super().set_admin_state(state) - def set_mirror(self): + def set_mirror_redirect(self): return diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index 661dc2298..ad5a0f499 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -27,9 +27,8 @@ from vyos.configdict import is_source_interface from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_dhcpv6 -from vyos.configverify import verify_mirror +from vyos.configverify import verify_mirror_redirect from vyos.configverify import verify_mtu_ipv6 -from vyos.configverify import verify_redirect from vyos.configverify import verify_source_interface from vyos.configverify import verify_vlan_config from vyos.configverify import verify_vrf @@ -151,8 +150,7 @@ def verify(bond): verify_address(bond) verify_dhcpv6(bond) verify_vrf(bond) - verify_mirror(bond) - verify_redirect(bond) + verify_mirror_redirect(bond) # use common function to verify VLAN configuration verify_vlan_config(bond) diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py index e16c0e9f4..b1f7e6d7c 100755 --- a/src/conf_mode/interfaces-bridge.py +++ b/src/conf_mode/interfaces-bridge.py @@ -27,8 +27,7 @@ from vyos.configdict import is_source_interface from vyos.configdict import has_vlan_subinterface_configured from vyos.configdict import dict_merge from vyos.configverify import verify_dhcpv6 -from vyos.configverify import verify_mirror -from vyos.configverify import verify_redirect +from vyos.configverify import verify_mirror_redirect from vyos.configverify import verify_vrf from vyos.ifconfig import BridgeIf from vyos.validate import has_address_configured @@ -107,8 +106,7 @@ def verify(bridge): verify_dhcpv6(bridge) verify_vrf(bridge) - verify_mirror(bridge) - verify_redirect(bridge) + verify_mirror_redirect(bridge) ifname = bridge['ifname'] diff --git a/src/conf_mode/interfaces-dummy.py b/src/conf_mode/interfaces-dummy.py index 4072c4452..4a1eb7b93 100755 --- a/src/conf_mode/interfaces-dummy.py +++ b/src/conf_mode/interfaces-dummy.py @@ -21,7 +21,7 @@ from vyos.configdict import get_interface_dict from vyos.configverify import verify_vrf from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete -from vyos.configverify import verify_redirect +from vyos.configverify import verify_mirror_redirect from vyos.ifconfig import DummyIf from vyos import ConfigError from vyos import airbag @@ -47,7 +47,7 @@ def verify(dummy): verify_vrf(dummy) verify_address(dummy) - verify_redirect(dummy) + verify_mirror_redirect(dummy) return None diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 3eeddf190..6aea7a80e 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -25,10 +25,9 @@ from vyos.configverify import verify_address from vyos.configverify import verify_dhcpv6 from vyos.configverify import verify_eapol from vyos.configverify import verify_interface_exists -from vyos.configverify import verify_mirror +from vyos.configverify import verify_mirror_redirect from vyos.configverify import verify_mtu from vyos.configverify import verify_mtu_ipv6 -from vyos.configverify import verify_redirect from vyos.configverify import verify_vlan_config from vyos.configverify import verify_vrf from vyos.ethtool import Ethtool @@ -84,8 +83,7 @@ def verify(ethernet): verify_address(ethernet) verify_vrf(ethernet) verify_eapol(ethernet) - verify_mirror(ethernet) - verify_redirect(ethernet) + verify_mirror_redirect(ethernet) ethtool = Ethtool(ifname) # No need to check speed and duplex keys as both have default values. diff --git a/src/conf_mode/interfaces-geneve.py b/src/conf_mode/interfaces-geneve.py index a94b5e1f7..3a668226b 100755 --- a/src/conf_mode/interfaces-geneve.py +++ b/src/conf_mode/interfaces-geneve.py @@ -24,7 +24,7 @@ from vyos.configdict import get_interface_dict from vyos.configverify import verify_address from vyos.configverify import verify_mtu_ipv6 from vyos.configverify import verify_bridge_delete -from vyos.configverify import verify_redirect +from vyos.configverify import verify_mirror_redirect from vyos.ifconfig import GeneveIf from vyos import ConfigError @@ -51,7 +51,7 @@ def verify(geneve): verify_mtu_ipv6(geneve) verify_address(geneve) - verify_redirect(geneve) + verify_mirror_redirect(geneve) if 'remote' not in geneve: raise ConfigError('Remote side must be configured') diff --git a/src/conf_mode/interfaces-l2tpv3.py b/src/conf_mode/interfaces-l2tpv3.py index 5ea7159dc..22256bf4f 100755 --- a/src/conf_mode/interfaces-l2tpv3.py +++ b/src/conf_mode/interfaces-l2tpv3.py @@ -25,7 +25,7 @@ from vyos.configdict import leaf_node_changed from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_mtu_ipv6 -from vyos.configverify import verify_redirect +from vyos.configverify import verify_mirror_redirect from vyos.ifconfig import L2TPv3If from vyos.util import check_kmod from vyos.validate import is_addr_assigned @@ -77,7 +77,7 @@ def verify(l2tpv3): verify_mtu_ipv6(l2tpv3) verify_address(l2tpv3) - verify_redirect(l2tpv3) + verify_mirror_redirect(l2tpv3) return None def generate(l2tpv3): diff --git a/src/conf_mode/interfaces-loopback.py b/src/conf_mode/interfaces-loopback.py index e6a851113..e4bc15bb5 100755 --- a/src/conf_mode/interfaces-loopback.py +++ b/src/conf_mode/interfaces-loopback.py @@ -20,7 +20,7 @@ from sys import exit from vyos.config import Config from vyos.configdict import get_interface_dict -from vyos.configverify import verify_redirect +from vyos.configverify import verify_mirror_redirect from vyos.ifconfig import LoopbackIf from vyos import ConfigError from vyos import airbag @@ -40,7 +40,7 @@ def get_config(config=None): return loopback def verify(loopback): - verify_redirect(loopback) + verify_mirror_redirect(loopback) return None def generate(loopback): diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py index 6a29fdb11..96fc1c41c 100755 --- a/src/conf_mode/interfaces-macsec.py +++ b/src/conf_mode/interfaces-macsec.py @@ -29,7 +29,7 @@ from vyos.configverify import verify_vrf from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_mtu_ipv6 -from vyos.configverify import verify_redirect +from vyos.configverify import verify_mirror_redirect from vyos.configverify import verify_source_interface from vyos import ConfigError from vyos import airbag @@ -67,7 +67,7 @@ def verify(macsec): verify_vrf(macsec) verify_mtu_ipv6(macsec) verify_address(macsec) - verify_redirect(macsec) + verify_mirror_redirect(macsec) if not (('security' in macsec) and ('cipher' in macsec['security'])): diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 8f9c0b3f1..83d1c6d9b 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -35,6 +35,7 @@ from vyos.configdict import get_interface_dict from vyos.configdict import leaf_node_changed from vyos.configverify import verify_vrf from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_mirror_redirect from vyos.ifconfig import VTunIf from vyos.pki import load_dh_parameters from vyos.pki import load_private_key @@ -495,6 +496,7 @@ def verify(openvpn): raise ConfigError('Username for authentication is missing') verify_vrf(openvpn) + verify_mirror_redirect(openvpn) return None diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py index 9962e0a08..bfb1fadd5 100755 --- a/src/conf_mode/interfaces-pppoe.py +++ b/src/conf_mode/interfaces-pppoe.py @@ -28,7 +28,7 @@ from vyos.configverify import verify_source_interface from vyos.configverify import verify_interface_exists from vyos.configverify import verify_vrf from vyos.configverify import verify_mtu_ipv6 -from vyos.configverify import verify_redirect +from vyos.configverify import verify_mirror_redirect from vyos.ifconfig import PPPoEIf from vyos.template import render from vyos.util import call @@ -86,7 +86,7 @@ def verify(pppoe): verify_authentication(pppoe) verify_vrf(pppoe) verify_mtu_ipv6(pppoe) - verify_redirect(pppoe) + verify_mirror_redirect(pppoe) if {'connect_on_demand', 'vrf'} <= set(pppoe): raise ConfigError('On-demand dialing and VRF can not be used at the same time') diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py index f57e41cc4..f2c85554f 100755 --- a/src/conf_mode/interfaces-pseudo-ethernet.py +++ b/src/conf_mode/interfaces-pseudo-ethernet.py @@ -25,7 +25,7 @@ from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_source_interface from vyos.configverify import verify_vlan_config from vyos.configverify import verify_mtu_parent -from vyos.configverify import verify_redirect +from vyos.configverify import verify_mirror_redirect from vyos.ifconfig import MACVLANIf from vyos import ConfigError @@ -61,7 +61,7 @@ def verify(peth): verify_vrf(peth) verify_address(peth) verify_mtu_parent(peth, peth['parent']) - verify_redirect(peth) + verify_mirror_redirect(peth) # use common function to verify VLAN configuration verify_vlan_config(peth) diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index 005fae5eb..f4668d976 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -26,7 +26,7 @@ from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_interface_exists from vyos.configverify import verify_mtu_ipv6 -from vyos.configverify import verify_redirect +from vyos.configverify import verify_mirror_redirect from vyos.configverify import verify_vrf from vyos.configverify import verify_tunnel from vyos.ifconfig import Interface @@ -158,7 +158,7 @@ def verify(tunnel): verify_mtu_ipv6(tunnel) verify_address(tunnel) verify_vrf(tunnel) - verify_redirect(tunnel) + verify_mirror_redirect(tunnel) if 'source_interface' in tunnel: verify_interface_exists(tunnel['source_interface']) diff --git a/src/conf_mode/interfaces-vti.py b/src/conf_mode/interfaces-vti.py index 30e13536f..f06fdff1b 100755 --- a/src/conf_mode/interfaces-vti.py +++ b/src/conf_mode/interfaces-vti.py @@ -19,7 +19,7 @@ from sys import exit from vyos.config import Config from vyos.configdict import get_interface_dict -from vyos.configverify import verify_redirect +from vyos.configverify import verify_mirror_redirect from vyos.ifconfig import VTIIf from vyos.util import dict_search from vyos import ConfigError @@ -40,7 +40,7 @@ def get_config(config=None): return vti def verify(vti): - verify_redirect(vti) + verify_mirror_redirect(vti) return None def generate(vti): diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index a29836efd..0a9b51cac 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -25,7 +25,7 @@ from vyos.configdict import leaf_node_changed from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_mtu_ipv6 -from vyos.configverify import verify_redirect +from vyos.configverify import verify_mirror_redirect from vyos.configverify import verify_source_interface from vyos.ifconfig import Interface from vyos.ifconfig import VXLANIf @@ -141,7 +141,7 @@ def verify(vxlan): verify_mtu_ipv6(vxlan) verify_address(vxlan) - verify_redirect(vxlan) + verify_mirror_redirect(vxlan) return None def generate(vxlan): diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py index dc0fe7b9c..b404375d6 100755 --- a/src/conf_mode/interfaces-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.py @@ -28,7 +28,7 @@ from vyos.configverify import verify_vrf from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_mtu_ipv6 -from vyos.configverify import verify_redirect +from vyos.configverify import verify_mirror_redirect from vyos.ifconfig import WireGuardIf from vyos.util import check_kmod from vyos.util import check_port_availability @@ -71,7 +71,7 @@ def verify(wireguard): verify_mtu_ipv6(wireguard) verify_address(wireguard) verify_vrf(wireguard) - verify_redirect(wireguard) + verify_mirror_redirect(wireguard) if 'private_key' not in wireguard: raise ConfigError('Wireguard private-key not defined') diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py index fdf9e3988..500952df1 100755 --- a/src/conf_mode/interfaces-wireless.py +++ b/src/conf_mode/interfaces-wireless.py @@ -27,7 +27,7 @@ from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_dhcpv6 from vyos.configverify import verify_source_interface -from vyos.configverify import verify_redirect +from vyos.configverify import verify_mirror_redirect from vyos.configverify import verify_vlan_config from vyos.configverify import verify_vrf from vyos.ifconfig import WiFiIf @@ -190,7 +190,7 @@ def verify(wifi): verify_address(wifi) verify_vrf(wifi) - verify_redirect(wifi) + verify_mirror_redirect(wifi) # use common function to verify VLAN configuration verify_vlan_config(wifi) diff --git a/src/conf_mode/interfaces-wwan.py b/src/conf_mode/interfaces-wwan.py index d5e259c74..9a33039a3 100755 --- a/src/conf_mode/interfaces-wwan.py +++ b/src/conf_mode/interfaces-wwan.py @@ -24,7 +24,7 @@ from vyos.configdict import get_interface_dict from vyos.configdict import leaf_node_changed from vyos.configverify import verify_authentication from vyos.configverify import verify_interface_exists -from vyos.configverify import verify_redirect +from vyos.configverify import verify_mirror_redirect from vyos.configverify import verify_vrf from vyos.ifconfig import WWANIf from vyos.util import cmd @@ -105,7 +105,7 @@ def verify(wwan): verify_interface_exists(ifname) verify_authentication(wwan) verify_vrf(wwan) - verify_redirect(wwan) + verify_mirror_redirect(wwan) return None -- cgit v1.2.3 From 4ecf558f53d1740b5ddb0de1f7effbaf0f44ff5f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 23 Mar 2022 10:40:06 +0100 Subject: qos: T4284: support mirror and redirect on vlan subinterfaces --- interface-definitions/include/interface/vif-s.xml.i | 2 ++ interface-definitions/include/interface/vif.xml.i | 1 + python/vyos/configverify.py | 19 +++++++++++++------ python/vyos/ifconfig/interface.py | 3 --- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/interface-definitions/include/interface/vif-s.xml.i b/interface-definitions/include/interface/vif-s.xml.i index 59a47b5ff..40a87e3d3 100644 --- a/interface-definitions/include/interface/vif-s.xml.i +++ b/interface-definitions/include/interface/vif-s.xml.i @@ -44,6 +44,7 @@ #include #include #include + #include #include @@ -63,6 +64,7 @@ #include #include #include + #include #include #include #include diff --git a/interface-definitions/include/interface/vif.xml.i b/interface-definitions/include/interface/vif.xml.i index 8a1475711..615101664 100644 --- a/interface-definitions/include/interface/vif.xml.i +++ b/interface-definitions/include/interface/vif.xml.i @@ -49,6 +49,7 @@ #include #include #include + #include #include #include #include diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index df2c5775a..9f2771854 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -317,9 +317,12 @@ def verify_vlan_config(config): if duplicate: raise ConfigError(f'Duplicate VLAN id "{duplicate[0]}" used for vif and vif-s interfaces!') + parent_ifname = config['ifname'] # 802.1q VLANs - for vlan in config.get('vif', {}): - vlan = config['vif'][vlan] + for vlan_id in config.get('vif', {}): + vlan = config['vif'][vlan_id] + vlan['ifname'] = f'{parent_ifname}.{vlan_id}' + verify_dhcpv6(vlan) verify_address(vlan) verify_vrf(vlan) @@ -327,16 +330,20 @@ def verify_vlan_config(config): verify_mtu_parent(vlan, config) # 802.1ad (Q-in-Q) VLANs - for s_vlan in config.get('vif_s', {}): - s_vlan = config['vif_s'][s_vlan] + for s_vlan_id in config.get('vif_s', {}): + s_vlan = config['vif_s'][s_vlan_id] + s_vlan['ifname'] = f'{parent_ifname}.{s_vlan_id}' + verify_dhcpv6(s_vlan) verify_address(s_vlan) verify_vrf(s_vlan) verify_mirror_redirect(s_vlan) verify_mtu_parent(s_vlan, config) - for c_vlan in s_vlan.get('vif_c', {}): - c_vlan = s_vlan['vif_c'][c_vlan] + for c_vlan_id in s_vlan.get('vif_c', {}): + c_vlan = s_vlan['vif_c'][c_vlan_id] + c_vlan['ifname'] = f'{parent_ifname}.{s_vlan_id}.{c_vlan_id}' + verify_dhcpv6(c_vlan) verify_address(c_vlan) verify_vrf(c_vlan) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 76164ca32..1464b2969 100755 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -1734,6 +1734,3 @@ class VLANIf(Interface): return None return super().set_admin_state(state) - - def set_mirror_redirect(self): - return -- cgit v1.2.3 From 7581a5c6cbbc3f0e38ac69028b814252805d5c98 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 23 Mar 2022 10:58:02 +0100 Subject: qos: T4284: verify mirror/redirect target interface exists --- python/vyos/configverify.py | 13 ++++++++++++- python/vyos/ifconfig/interface.py | 4 ---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 9f2771854..1062d51ee 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -185,15 +185,26 @@ def verify_mirror_redirect(config): It makes no sense to mirror traffic back at yourself! """ + import os if {'mirror', 'redirect'} <= set(config): raise ConfigError('Mirror and redirect can not be enabled at the same time!') if 'mirror' in config: for direction, mirror_interface in config['mirror'].items(): + if not os.path.exists(f'/sys/class/net/{mirror_interface}'): + raise ConfigError(f'Requested mirror interface "{mirror_interface}" '\ + 'does not exist!') + if mirror_interface == config['ifname']: - raise ConfigError(f'Can not mirror "{direction}" traffic back ' \ + raise ConfigError(f'Can not mirror "{direction}" traffic back '\ 'the originating interface!') + if 'redirect' in config: + redirect_ifname = config['redirect'] + if not os.path.exists(f'/sys/class/net/{redirect_ifname}'): + raise ConfigError(f'Requested redirect interface "{redirect_ifname}" '\ + 'does not exist!') + if dict_search('traffic_policy.in', config) != None: # XXX: support combination of limiting and redirect/mirror - this is an # artificial limitation diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 1464b2969..5b2760386 100755 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -13,7 +13,6 @@ # You should have received a copy of the GNU Lesser General Public # License along with this library. If not, see . -from netifaces import interfaces import os import re import json @@ -1319,9 +1318,6 @@ class Interface(Control): # Apply interface mirror policy if mirror_config: for direction, target_if in mirror_config.items(): - if target_if not in interfaces(): - continue - if direction == 'ingress': handle = 'ffff: ingress' parent = 'ffff:' -- cgit v1.2.3 From 0bf386cee9b09d2e1a220330d3662c6ca2642645 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 6 Apr 2022 20:09:31 +0200 Subject: qos: T4284: rename "traffic-policy" node to "qos policy" "set traffic-policy" now becomes "set qos policy" "set interface ethernet eth0 traffic-policy" now bvecomes "set qos interface eth0" --- Makefile | 3 +- .../include/interface/traffic-policy.xml.i | 43 - .../include/interface/vif-s.xml.i | 2 - interface-definitions/include/interface/vif.xml.i | 1 - interface-definitions/interfaces-bonding.xml.in | 1 - interface-definitions/interfaces-bridge.xml.in | 1 - interface-definitions/interfaces-dummy.xml.in | 1 - interface-definitions/interfaces-ethernet.xml.in | 1 - interface-definitions/interfaces-geneve.xml.in | 1 - interface-definitions/interfaces-input.xml.in | 1 - interface-definitions/interfaces-l2tpv3.xml.in | 1 - interface-definitions/interfaces-loopback.xml.in | 1 - interface-definitions/interfaces-macsec.xml.in | 1 - interface-definitions/interfaces-openvpn.xml.in | 1 - interface-definitions/interfaces-pppoe.xml.in | 1 - .../interfaces-pseudo-ethernet.xml.in | 1 - interface-definitions/interfaces-tunnel.xml.in | 1 - interface-definitions/interfaces-vti.xml.in | 1 - interface-definitions/interfaces-vxlan.xml.in | 1 - interface-definitions/interfaces-wireguard.xml.in | 1 - interface-definitions/interfaces-wireless.xml.in | 1 - interface-definitions/interfaces-wwan.xml.in | 1 - interface-definitions/qos.xml.in | 1148 +++++++++++--------- src/conf_mode/qos.py | 47 +- 24 files changed, 631 insertions(+), 631 deletions(-) delete mode 100644 interface-definitions/include/interface/traffic-policy.xml.i diff --git a/Makefile b/Makefile index 54f3892ba..dc1301100 100644 --- a/Makefile +++ b/Makefile @@ -31,9 +31,8 @@ interface_definitions: $(config_xml_obj) rm -rf $(TMPL_DIR)/vpn/ipsec/remote-access/radius/source-address # T4284 neq QoS implementation is not yet live - find $(TMPL_DIR)/interfaces -name traffic-policy -type d -exec rm -rf {} \; find $(TMPL_DIR)/interfaces -name redirect -type d -exec rm -rf {} \; - rm -rf $(TMPL_DIR)/traffic-policy + rm -rf $(TMPL_DIR)/qos rm -rf $(TMPL_DIR)/interfaces/input # XXX: test if there are empty node.def files - this is not allowed as these diff --git a/interface-definitions/include/interface/traffic-policy.xml.i b/interface-definitions/include/interface/traffic-policy.xml.i deleted file mode 100644 index cd60b62a5..000000000 --- a/interface-definitions/include/interface/traffic-policy.xml.i +++ /dev/null @@ -1,43 +0,0 @@ - - - - Traffic-policy for interface - - - - - Ingress traffic policy for interface - - traffic-policy drop-tail - traffic-policy fair-queue - traffic-policy fq-codel - traffic-policy limiter - traffic-policy network-emulator - traffic-policy priority-queue - traffic-policy random-detect - traffic-policy rate-control - traffic-policy round-robin - traffic-policy shaper - traffic-policy shaper-hfsc - - - txt - Policy name - - - - - - Egress traffic policy for interface - - traffic-policy - - - txt - Policy name - - - - - - \ No newline at end of file diff --git a/interface-definitions/include/interface/vif-s.xml.i b/interface-definitions/include/interface/vif-s.xml.i index 40a87e3d3..3b305618e 100644 --- a/interface-definitions/include/interface/vif-s.xml.i +++ b/interface-definitions/include/interface/vif-s.xml.i @@ -67,14 +67,12 @@ #include #include #include - #include #include #include #include #include - #include #include diff --git a/interface-definitions/include/interface/vif.xml.i b/interface-definitions/include/interface/vif.xml.i index 615101664..4e7f9b3c2 100644 --- a/interface-definitions/include/interface/vif.xml.i +++ b/interface-definitions/include/interface/vif.xml.i @@ -52,7 +52,6 @@ #include #include #include - #include #include diff --git a/interface-definitions/interfaces-bonding.xml.in b/interface-definitions/interfaces-bonding.xml.in index 20ece5137..5ae67a672 100644 --- a/interface-definitions/interfaces-bonding.xml.in +++ b/interface-definitions/interfaces-bonding.xml.in @@ -208,7 +208,6 @@ #include - #include #include #include #include diff --git a/interface-definitions/interfaces-bridge.xml.in b/interface-definitions/interfaces-bridge.xml.in index 6957067cd..be4c92583 100644 --- a/interface-definitions/interfaces-bridge.xml.in +++ b/interface-definitions/interfaces-bridge.xml.in @@ -211,7 +211,6 @@ #include - #include #include diff --git a/interface-definitions/interfaces-dummy.xml.in b/interface-definitions/interfaces-dummy.xml.in index 988d87502..7f9ae90e5 100644 --- a/interface-definitions/interfaces-dummy.xml.in +++ b/interface-definitions/interfaces-dummy.xml.in @@ -32,7 +32,6 @@ #include #include #include - #include #include diff --git a/interface-definitions/interfaces-ethernet.xml.in b/interface-definitions/interfaces-ethernet.xml.in index 7d28912c0..7fa07e9ec 100644 --- a/interface-definitions/interfaces-ethernet.xml.in +++ b/interface-definitions/interfaces-ethernet.xml.in @@ -197,7 +197,6 @@ #include - #include #include #include #include diff --git a/interface-definitions/interfaces-geneve.xml.in b/interface-definitions/interfaces-geneve.xml.in index 5f2c6bc05..fa5a78be5 100644 --- a/interface-definitions/interfaces-geneve.xml.in +++ b/interface-definitions/interfaces-geneve.xml.in @@ -52,7 +52,6 @@ #include #include - #include #include #include diff --git a/interface-definitions/interfaces-input.xml.in b/interface-definitions/interfaces-input.xml.in index f2eb01c58..2164bfa4e 100644 --- a/interface-definitions/interfaces-input.xml.in +++ b/interface-definitions/interfaces-input.xml.in @@ -22,7 +22,6 @@ #include #include #include - #include diff --git a/interface-definitions/interfaces-l2tpv3.xml.in b/interface-definitions/interfaces-l2tpv3.xml.in index 0dcabf7a0..1f23a89a5 100644 --- a/interface-definitions/interfaces-l2tpv3.xml.in +++ b/interface-definitions/interfaces-l2tpv3.xml.in @@ -125,7 +125,6 @@ - #include #include diff --git a/interface-definitions/interfaces-loopback.xml.in b/interface-definitions/interfaces-loopback.xml.in index 1e093d95b..7ac0545c6 100644 --- a/interface-definitions/interfaces-loopback.xml.in +++ b/interface-definitions/interfaces-loopback.xml.in @@ -28,7 +28,6 @@ #include #include - #include diff --git a/interface-definitions/interfaces-macsec.xml.in b/interface-definitions/interfaces-macsec.xml.in index fbdd1562a..cb3c489aa 100644 --- a/interface-definitions/interfaces-macsec.xml.in +++ b/interface-definitions/interfaces-macsec.xml.in @@ -124,7 +124,6 @@ #include #include - #include #include diff --git a/interface-definitions/interfaces-openvpn.xml.in b/interface-definitions/interfaces-openvpn.xml.in index 761f8bcad..c917b9312 100644 --- a/interface-definitions/interfaces-openvpn.xml.in +++ b/interface-definitions/interfaces-openvpn.xml.in @@ -818,7 +818,6 @@ #include - #include #include diff --git a/interface-definitions/interfaces-pppoe.xml.in b/interface-definitions/interfaces-pppoe.xml.in index adf5f4040..3a0b7a40c 100644 --- a/interface-definitions/interfaces-pppoe.xml.in +++ b/interface-definitions/interfaces-pppoe.xml.in @@ -135,7 +135,6 @@ #include - #include #include diff --git a/interface-definitions/interfaces-pseudo-ethernet.xml.in b/interface-definitions/interfaces-pseudo-ethernet.xml.in index aed2052f5..5f5e9fdef 100644 --- a/interface-definitions/interfaces-pseudo-ethernet.xml.in +++ b/interface-definitions/interfaces-pseudo-ethernet.xml.in @@ -61,7 +61,6 @@ #include #include - #include #include #include diff --git a/interface-definitions/interfaces-tunnel.xml.in b/interface-definitions/interfaces-tunnel.xml.in index b31f22552..42ec62775 100644 --- a/interface-definitions/interfaces-tunnel.xml.in +++ b/interface-definitions/interfaces-tunnel.xml.in @@ -290,7 +290,6 @@ #include #include - #include diff --git a/interface-definitions/interfaces-vti.xml.in b/interface-definitions/interfaces-vti.xml.in index d66fc952e..5893e4c4c 100644 --- a/interface-definitions/interfaces-vti.xml.in +++ b/interface-definitions/interfaces-vti.xml.in @@ -36,7 +36,6 @@ #include #include #include - #include #include #include #include diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in index b1a2dfaec..9747b1816 100644 --- a/interface-definitions/interfaces-vxlan.xml.in +++ b/interface-definitions/interfaces-vxlan.xml.in @@ -101,7 +101,6 @@ #include #include #include - #include #include #include diff --git a/interface-definitions/interfaces-wireguard.xml.in b/interface-definitions/interfaces-wireguard.xml.in index 51565cfe6..eb0892f07 100644 --- a/interface-definitions/interfaces-wireguard.xml.in +++ b/interface-definitions/interfaces-wireguard.xml.in @@ -121,7 +121,6 @@ #include - #include #include diff --git a/interface-definitions/interfaces-wireless.xml.in b/interface-definitions/interfaces-wireless.xml.in index a16a7841e..db01657eb 100644 --- a/interface-definitions/interfaces-wireless.xml.in +++ b/interface-definitions/interfaces-wireless.xml.in @@ -783,7 +783,6 @@ monitor #include - #include #include #include diff --git a/interface-definitions/interfaces-wwan.xml.in b/interface-definitions/interfaces-wwan.xml.in index 33bc0cb3d..3cb1645c4 100644 --- a/interface-definitions/interfaces-wwan.xml.in +++ b/interface-definitions/interfaces-wwan.xml.in @@ -42,7 +42,6 @@ #include #include #include - #include #include diff --git a/interface-definitions/qos.xml.in b/interface-definitions/qos.xml.in index d4468543c..e8f575a1e 100644 --- a/interface-definitions/qos.xml.in +++ b/interface-definitions/qos.xml.in @@ -1,721 +1,789 @@ - + - Quality of Service (QOS) policy type - 900 + Quality of Service (QoS) - + - Packet limited First In, First Out queue + Interface to apply QoS policy + + + txt - Policy name + Interface name - [[:alnum:]][-_[:alnum:]]* + - Only alpha-numeric policy name allowed - #include - #include - - - - - Stochastic Fairness Queueing - - txt - Policy name - - - [[:alnum:]][-_[:alnum:]]* - - Only alpha-numeric policy name allowed - - - #include - + - Interval in seconds for queue algorithm perturbation - - u32:0 - No perturbation - + Interface ingress traffic policy + + traffic-policy drop-tail + traffic-policy fair-queue + traffic-policy fq-codel + traffic-policy limiter + traffic-policy network-emulator + traffic-policy priority-queue + traffic-policy random-detect + traffic-policy rate-control + traffic-policy round-robin + traffic-policy shaper + traffic-policy shaper-hfsc + - u32:1-127 - Interval in seconds for queue algorithm perturbation (advised: 10) + txt + QoS Policy name - - - - Interval must be in range 0 to 127 - 0 - + - Upper limit of the SFQ + Interface egress traffic policy + + traffic-policy drop-tail + traffic-policy fair-queue + traffic-policy fq-codel + traffic-policy limiter + traffic-policy network-emulator + traffic-policy priority-queue + traffic-policy random-detect + traffic-policy rate-control + traffic-policy round-robin + traffic-policy shaper + traffic-policy shaper-hfsc + - u32:2-127 - Queue size in packets + txt + QoS Policy name - - - - Queue limit must greater than 1 and less than 128 - 127 - + - Fair Queuing Controlled Delay - - txt - Policy name - - - [[:alnum:]][-_[:alnum:]]* - - Only alpha-numeric policy name allowed + Service Policy definitions + 900 - #include - #include - #include - #include - #include - #include - - - - - Traffic input limiting policy - - txt - Policy name - - - [[:alnum:]][-_[:alnum:]]* - - Only alpha-numeric policy name allowed - - - + - Class ID + Packet limited First In, First Out queue - u32:1-4090 - Class Identifier + txt + Policy name - + [[:alnum:]][-_[:alnum:]]* - Class identifier must be between 1 and 4090 + Only alpha-numeric policy name allowed + + + #include + #include + + + + + Stochastic Fairness Queueing + + txt + Policy name + + + [[:alnum:]][-_[:alnum:]]* + + Only alpha-numeric policy name allowed - #include - #include #include - #include - + + + Interval in seconds for queue algorithm perturbation + + u32:0 + No perturbation + + + u32:1-127 + Interval in seconds for queue algorithm perturbation (advised: 10) + + + + + Interval must be in range 0 to 127 + + 0 + + - Priority for rule evaluation + Upper limit of the SFQ - u32:0-20 - Priority for match rule evaluation + u32:2-127 + Queue size in packets - + - Priority must be between 0 and 20 + Queue limit must greater than 1 and less than 128 - 20 + 127 - - - Default policy - - - #include - #include - - - #include - - - - - Network emulator policy - - txt - Policy name - - - [[:alnum:]][-_[:alnum:]]* - - Only alpha-numeric policy name allowed - - - #include - #include - #include - - - Adds delay to packets outgoing to chosen network interface - - <number> - Time in milliseconds - - - - - Priority must be between 0 and 65535 - - - - - Introducing error in a random position for chosen percent of packets - - <number> - Percentage of packets affected - - - - - Priority must be between 0 and 100 - - - - - Add independent loss probability to the packets outgoing to chosen network interface - - <number> - Percentage of packets affected - - - - - Must be between 0 and 100 - - - + - Add independent loss probability to the packets outgoing to chosen network interface + Fair Queuing Controlled Delay - <number> - Percentage of packets affected + txt + Policy name - + [[:alnum:]][-_[:alnum:]]* - Must be between 0 and 100 - - - - - Packet reordering percentage - - <number> - Percentage of packets affected - - - - - Must be between 0 and 100 - - - #include - - - - - Priority queuing based policy - - txt - Policy name - - - [[:alnum:]][-_[:alnum:]]* - - Only alpha-numeric policy name allowed - - - - - Class Handle - - u32:1-7 - Priority - - - - - Class handle must be between 1 and 7 + Only alpha-numeric policy name allowed #include #include #include #include - #include #include #include - #include - + - Default policy + Traffic input limiting policy + + txt + Policy name + + + [[:alnum:]][-_[:alnum:]]* + + Only alpha-numeric policy name allowed + + + Class ID + + u32:1-4090 + Class Identifier + + + + + Class identifier must be between 1 and 4090 + + + #include + #include + #include + #include + + + Priority for rule evaluation + + u32:0-20 + Priority for match rule evaluation + + + + + Priority must be between 0 and 20 + + 20 + + + + + + Default policy + + + #include + #include + + #include - #include - #include - #include - #include - #include - #include - - #include - - - - - Priority queuing based policy - - txt - Policy name - - - [[:alnum:]][-_[:alnum:]]* - - Only alpha-numeric policy name allowed - - - #include - - auto - - #include - + + - IP precedence + Network emulator policy - u32:0-7 - IP precedence value + txt + Policy name - + [[:alnum:]][-_[:alnum:]]* - IP precedence value must be between 0 and 7 + Only alpha-numeric policy name allowed - #include - + #include + #include + #include + - Average packet size (bytes) + Adds delay to packets outgoing to chosen network interface - u32:16-10240 - Average packet size in bytes + <number> + Time in milliseconds - + - Average packet size must be between 16 and 10240 + Priority must be between 0 and 65535 - 1024 - + - Mark probability for this precedence + Introducing error in a random position for chosen percent of packets <number> - Numeric value (1/N) + Percentage of packets affected - + - Mark probability must be greater than 0 + Priority must be between 0 and 100 - + - Maximum threshold for random detection + Add independent loss probability to the packets outgoing to chosen network interface - u32:0-4096 - Maximum Threshold in packets + <number> + Percentage of packets affected - + - Threshold must be between 0 and 4096 + Must be between 0 and 100 - + - Minimum threshold for random detection + Add independent loss probability to the packets outgoing to chosen network interface - u32:0-4096 - Maximum Threshold in packets + <number> + Percentage of packets affected - + - Threshold must be between 0 and 4096 + Must be between 0 and 100 - - - - - - - Rate limiting policy (Token Bucket Filter) - - txt - Policy name - - - [[:alnum:]][-_[:alnum:]]* - - Only alpha-numeric policy name allowed - - - #include - #include - #include - - - Maximum latency - - <number> - Time in milliseconds - - - - - Threshold must be between 0 and 4096 - - 50 - - - - - - Round-Robin based policy - - txt - Policy name - - - [[:alnum:]][-_[:alnum:]]* - - Only alpha-numeric policy name allowed - - - #include - - - Class ID - - u32:1-4095 - Class Identifier - - - - - Class identifier must be between 1 and 4095 - - - #include - #include - #include - #include - #include - + - Packet scheduling quantum + Packet reordering percentage - u32:1-4294967295 - Packet scheduling quantum (bytes) + <number> + Percentage of packets affected - + - Quantum must be in range 1 to 4294967295 + Must be between 0 and 100 #include - #include - #include - - - - - Hierarchical Fair Service Curve's policy - - txt - Policy name - - - [[:alnum:]][-_[:alnum:]]* - - Only alpha-numeric policy name allowed - - - #include - - auto - - #include - + - Class ID + Priority queuing based policy - u32:1-4095 - Class Identifier + txt + Policy name - + [[:alnum:]][-_[:alnum:]]* - Class identifier must be between 1 and 4095 + Only alpha-numeric policy name allowed - #include - + - Linkshare class settings - - - #include - #include - #include - - - #include - - - Realtime class settings + Class Handle + + u32:1-7 + Priority + + + + + Class handle must be between 1 and 7 - #include - #include - #include + #include + #include + #include + #include + #include + #include + #include + #include - - + + - Upperlimit class settings + Default policy - #include - #include - #include + #include + #include + #include + #include + #include + #include + #include + #include - + - Default policy + Priority queuing based policy + + txt + Policy name + + + [[:alnum:]][-_[:alnum:]]* + + Only alpha-numeric policy name allowed - - - Linkshare class settings - - - #include - #include - #include - - - - - Realtime class settings - - - #include - #include - #include - - - + #include + + auto + + #include + - Upperlimit class settings + IP precedence + + u32:0-7 + IP precedence value + + + + + IP precedence value must be between 0 and 7 - #include - #include - #include + #include + + + Average packet size (bytes) + + u32:16-10240 + Average packet size in bytes + + + + + Average packet size must be between 16 and 10240 + + 1024 + + + + Mark probability for this precedence + + <number> + Numeric value (1/N) + + + + + Mark probability must be greater than 0 + + + + + Maximum threshold for random detection + + u32:0-4096 + Maximum Threshold in packets + + + + + Threshold must be between 0 and 4096 + + + + + Minimum threshold for random detection + + u32:0-4096 + Maximum Threshold in packets + + + + + Threshold must be between 0 and 4096 + + - + - - - - - - Traffic shaping based policy (Hierarchy Token Bucket) - - txt - Policy name - - - [[:alnum:]][-_[:alnum:]]* - - Only alpha-numeric policy name allowed - - - #include - - auto - - + + - Class ID + Rate limiting policy (Token Bucket Filter) - u32:2-4095 - Class Identifier + txt + Policy name - + [[:alnum:]][-_[:alnum:]]* - Class identifier must be between 2 and 4095 + Only alpha-numeric policy name allowed #include - - 100% - + #include #include - + - Bandwidth limit for this class + Maximum latency <number> - Rate in kbit (kilobit per second) - - - <number>%% - Percentage of overall rate - - - <number>bit - bit(1), kbit(10^3), mbit(10^6), gbit, tbit - - - <number>ibit - kibit(1024), mibit(1024^2), gibit(1024^3), tbit(1024^4) - - - <number>ibps - kibps(1024*8), mibps(1024^2*8), gibps, tibps - Byte/sec - - - <number>bps - bps(8),kbps(8*10^3),mbps(8*10^6), gbps, tbps - Byte/sec + Time in milliseconds + + + + Threshold must be between 0 and 4096 + 50 - #include + + + + + Round-Robin based policy + + txt + Policy name + + + [[:alnum:]][-_[:alnum:]]* + + Only alpha-numeric policy name allowed + + #include - #include - #include - #include - + - Priority for usage of excess bandwidth + Class ID - u32:0-7 - Priority order for bandwidth pool + u32:1-4095 + Class Identifier - + - Priority must be between 0 and 7 + Class identifier must be between 1 and 4095 - 20 - - #include - #include - #include - #include + + #include + #include + #include + #include + #include + + + Packet scheduling quantum + + u32:1-4294967295 + Packet scheduling quantum (bytes) + + + + + Quantum must be in range 1 to 4294967295 + + + #include + #include + #include + + - #include - + - Default policy + Hierarchical Fair Service Curve's policy + + txt + Policy name + + + [[:alnum:]][-_[:alnum:]]* + + Only alpha-numeric policy name allowed #include - #include - + + auto + + #include + - Bandwidth limit for this class - - <number> - Rate in kbit (kilobit per second) - - - <number>%% - Percentage of overall rate - + Class ID - <number>bit - bit(1), kbit(10^3), mbit(10^6), gbit, tbit - - - <number>ibit - kibit(1024), mibit(1024^2), gibit(1024^3), tbit(1024^4) - - - <number>ibps - kibps(1024*8), mibps(1024^2*8), gibps, tibps - Byte/sec - - - <number>bps - bps(8),kbps(8*10^3),mbps(8*10^6), gbps, tbps - Byte/sec + u32:1-4095 + Class Identifier + + + + Class identifier must be between 1 and 4095 + + + #include + + + Linkshare class settings + + + #include + #include + #include + + + #include + + + Realtime class settings + + + #include + #include + #include + + + + + Upperlimit class settings + + + #include + #include + #include + + + + + + + Default policy + + + + Linkshare class settings + + + #include + #include + #include + + + + + Realtime class settings + + + #include + #include + #include + + + + + Upperlimit class settings + + + #include + #include + #include + + + + + + + + + Traffic shaping based policy (Hierarchy Token Bucket) + + txt + Policy name + + + [[:alnum:]][-_[:alnum:]]* + + Only alpha-numeric policy name allowed + + + #include + + auto - #include - #include - #include - #include - + - Priority for usage of excess bandwidth + Class ID - u32:0-7 - Priority order for bandwidth pool + u32:2-4095 + Class Identifier - + - Priority must be between 0 and 7 + Class identifier must be between 2 and 4095 - 20 - - #include - #include - #include - #include + + #include + + 100% + + #include + + + Bandwidth limit for this class + + <number> + Rate in kbit (kilobit per second) + + + <number>%% + Percentage of overall rate + + + <number>bit + bit(1), kbit(10^3), mbit(10^6), gbit, tbit + + + <number>ibit + kibit(1024), mibit(1024^2), gibit(1024^3), tbit(1024^4) + + + <number>ibps + kibps(1024*8), mibps(1024^2*8), gibps, tibps - Byte/sec + + + <number>bps + bps(8),kbps(8*10^3),mbps(8*10^6), gbps, tbps - Byte/sec + + + + #include + #include + #include + #include + #include + + + Priority for usage of excess bandwidth + + u32:0-7 + Priority order for bandwidth pool + + + + + Priority must be between 0 and 7 + + 20 + + #include + #include + #include + #include + + + #include + + + Default policy + + + #include + #include + + + Bandwidth limit for this class + + <number> + Rate in kbit (kilobit per second) + + + <number>%% + Percentage of overall rate + + + <number>bit + bit(1), kbit(10^3), mbit(10^6), gbit, tbit + + + <number>ibit + kibit(1024), mibit(1024^2), gibit(1024^3), tbit(1024^4) + + + <number>ibps + kibps(1024*8), mibps(1024^2*8), gibps, tibps - Byte/sec + + + <number>bps + bps(8),kbps(8*10^3),mbps(8*10^6), gbps, tbps - Byte/sec + + + + #include + #include + #include + #include + + + Priority for usage of excess bandwidth + + u32:0-7 + Priority order for bandwidth pool + + + + + Priority must be between 0 and 7 + + 20 + + #include + #include + #include + #include + + - + - + diff --git a/src/conf_mode/qos.py b/src/conf_mode/qos.py index cf447d4b5..dbe3be225 100755 --- a/src/conf_mode/qos.py +++ b/src/conf_mode/qos.py @@ -28,36 +28,33 @@ def get_config(config=None): conf = config else: conf = Config() - base = ['traffic-policy'] + base = ['qos'] if not conf.exists(base): return None qos = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - for traffic_policy in ['drop-tail', 'fair-queue', 'fq-codel', 'limiter', - 'network-emulator', 'priority-queue', 'random-detect', - 'rate-control', 'round-robin', 'shaper', 'shaper-hfsc']: - traffic_policy_us = traffic_policy.replace('-','_') - # Individual policy type not present on CLI - no need to blend in - # any default values - if traffic_policy_us not in qos: - continue - - default_values = defaults(base + [traffic_policy_us]) - - # class is another tag node which requires individual handling - class_default_values = defaults(base + [traffic_policy_us, 'class']) - if 'class' in default_values: - del default_values['class'] - - for policy, policy_config in qos[traffic_policy_us].items(): - qos[traffic_policy_us][policy] = dict_merge( - default_values, qos[traffic_policy_us][policy]) - - if 'class' in policy_config: - for policy_class in policy_config['class']: - qos[traffic_policy_us][policy]['class'][policy_class] = dict_merge( - class_default_values, qos[traffic_policy_us][policy]['class'][policy_class]) + if 'policy' in qos: + for policy in qos['policy']: + # CLI mangles - to _ for better Jinja2 compatibility - do we need + # Jinja2 here? + policy = policy.replace('-','_') + + default_values = defaults(base + ['policy', policy]) + + # class is another tag node which requires individual handling + class_default_values = defaults(base + ['policy', policy, 'class']) + if 'class' in default_values: + del default_values['class'] + + for p_name, p_config in qos['policy'][policy].items(): + qos['policy'][policy][p_name] = dict_merge( + default_values, qos['policy'][policy][p_name]) + + if 'class' in p_config: + for p_class in p_config['class']: + qos['policy'][policy][p_name]['class'][p_class] = dict_merge( + class_default_values, qos['policy'][policy][p_name]['class'][p_class]) import pprint pprint.pprint(qos) -- cgit v1.2.3 From 957f73ed8c2c22afd5e56adc36b4d032b3f1a5f1 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 7 Apr 2022 18:34:41 +0200 Subject: vyos.base: T4346: add common DeprecationWarning() class --- python/vyos/base.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/python/vyos/base.py b/python/vyos/base.py index c78045548..fd22eaccd 100644 --- a/python/vyos/base.py +++ b/python/vyos/base.py @@ -15,6 +15,12 @@ from textwrap import fill +class DeprecationWarning(): + def __init__(self, message): + # Reformat the message and trim it to 72 characters in length + message = fill(message, width=72) + print(f'\nDEPRECATION WARNING: {message}\n') + class ConfigError(Exception): def __init__(self, message): # Reformat the message and trim it to 72 characters in length -- cgit v1.2.3 From 0f7e5371e702d4e2389f6fa6dfbda11bc9da6257 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 7 Apr 2022 18:35:10 +0200 Subject: ipv6: T4346: deprecate CLI command to disable IPv6 address family --- src/conf_mode/system-ipv6.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/conf_mode/system-ipv6.py b/src/conf_mode/system-ipv6.py index 7fb2dd1cf..e6bcc12ad 100755 --- a/src/conf_mode/system-ipv6.py +++ b/src/conf_mode/system-ipv6.py @@ -17,6 +17,7 @@ import os from sys import exit +from vyos.base import DeprecationWarning from vyos.config import Config from vyos.configdict import dict_merge from vyos.configdict import leaf_node_changed @@ -49,6 +50,9 @@ def get_config(config=None): return opt def verify(opt): + if 'disable' in opt: + DeprecationWarning('VyOS 1.4 (sagitta) will remove the CLI command to '\ + 'disable IPv6 address family in the Linux Kernel!') pass def generate(opt): -- cgit v1.2.3 From 440a7a1c965be39ca0b13b4ea5985dd9c95fabef Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 7 Apr 2022 19:07:52 +0200 Subject: ipv6: T4346: delete (migrate) CLI command to disable IPv6 address family --- .../include/version/system-version.xml.i | 2 +- interface-definitions/system-ipv6.xml.in | 6 -- python/vyos/ifconfig/interface.py | 91 ++++++++++------------ python/vyos/ifconfig/loopback.py | 12 ++- python/vyos/util.py | 4 - smoketest/configs/ipv6-disable | 83 ++++++++++++++++++++ smoketest/scripts/cli/test_system_ipv6.py | 36 --------- src/conf_mode/system-ipv6.py | 18 ----- src/conf_mode/vrf.py | 4 +- src/migration-scripts/system/22-to-23 | 50 ++++++++++++ src/tests/test_util.py | 10 --- 11 files changed, 181 insertions(+), 135 deletions(-) create mode 100644 smoketest/configs/ipv6-disable create mode 100755 src/migration-scripts/system/22-to-23 diff --git a/interface-definitions/include/version/system-version.xml.i b/interface-definitions/include/version/system-version.xml.i index fb4629bf1..19591256d 100644 --- a/interface-definitions/include/version/system-version.xml.i +++ b/interface-definitions/include/version/system-version.xml.i @@ -1,3 +1,3 @@ - + diff --git a/interface-definitions/system-ipv6.xml.in b/interface-definitions/system-ipv6.xml.in index af4dcdb0f..63260d00c 100644 --- a/interface-definitions/system-ipv6.xml.in +++ b/interface-definitions/system-ipv6.xml.in @@ -15,12 +15,6 @@ - - - Disable assignment of IPv6 addresses on all interfaces - - - IPv6 multipath settings diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 5b2760386..6b0f08fd4 100755 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -38,7 +38,6 @@ from vyos.util import read_file from vyos.util import get_interface_config from vyos.util import get_interface_namespace from vyos.util import is_systemd_service_active -from vyos.util import is_ipv6_enabled from vyos.template import is_ipv4 from vyos.template import is_ipv6 from vyos.validate import is_intf_addr_assigned @@ -1080,12 +1079,6 @@ class Interface(Control): if addr in self._addr: return False - addr_is_v4 = is_ipv4(addr) - - # Failsave - do not add IPv6 address if IPv6 is disabled - if is_ipv6(addr) and not is_ipv6_enabled(): - return False - # add to interface if addr == 'dhcp': self.set_dhcp(True) @@ -1517,50 +1510,48 @@ class Interface(Control): if 'mtu' in config: self.set_mtu(config.get('mtu')) - # Only change IPv6 parameters if IPv6 was not explicitly disabled - if is_ipv6_enabled(): - # Configure MSS value for IPv6 TCP connections - tmp = dict_search('ipv6.adjust_mss', config) - value = tmp if (tmp != None) else '0' - self.set_tcp_ipv6_mss(value) - - # IPv6 forwarding - tmp = dict_search('ipv6.disable_forwarding', config) - value = '0' if (tmp != None) else '1' - self.set_ipv6_forwarding(value) - - # IPv6 router advertisements - tmp = dict_search('ipv6.address.autoconf', config) - value = '2' if (tmp != None) else '1' - if 'dhcpv6' in new_addr: - value = '2' - self.set_ipv6_accept_ra(value) - - # IPv6 address autoconfiguration - tmp = dict_search('ipv6.address.autoconf', config) - value = '1' if (tmp != None) else '0' - self.set_ipv6_autoconf(value) - - # IPv6 Duplicate Address Detection (DAD) tries - tmp = dict_search('ipv6.dup_addr_detect_transmits', config) - value = tmp if (tmp != None) else '1' - self.set_ipv6_dad_messages(value) - - # Delete old IPv6 EUI64 addresses before changing MAC - for addr in (dict_search('ipv6.address.eui64_old', config) or []): - self.del_ipv6_eui64_address(addr) - - # Manage IPv6 link-local addresses - if dict_search('ipv6.address.no_default_link_local', config) != None: - self.del_ipv6_eui64_address('fe80::/64') - else: - self.add_ipv6_eui64_address('fe80::/64') + # Configure MSS value for IPv6 TCP connections + tmp = dict_search('ipv6.adjust_mss', config) + value = tmp if (tmp != None) else '0' + self.set_tcp_ipv6_mss(value) + + # IPv6 forwarding + tmp = dict_search('ipv6.disable_forwarding', config) + value = '0' if (tmp != None) else '1' + self.set_ipv6_forwarding(value) + + # IPv6 router advertisements + tmp = dict_search('ipv6.address.autoconf', config) + value = '2' if (tmp != None) else '1' + if 'dhcpv6' in new_addr: + value = '2' + self.set_ipv6_accept_ra(value) + + # IPv6 address autoconfiguration + tmp = dict_search('ipv6.address.autoconf', config) + value = '1' if (tmp != None) else '0' + self.set_ipv6_autoconf(value) + + # IPv6 Duplicate Address Detection (DAD) tries + tmp = dict_search('ipv6.dup_addr_detect_transmits', config) + value = tmp if (tmp != None) else '1' + self.set_ipv6_dad_messages(value) + + # Delete old IPv6 EUI64 addresses before changing MAC + for addr in (dict_search('ipv6.address.eui64_old', config) or []): + self.del_ipv6_eui64_address(addr) + + # Manage IPv6 link-local addresses + if dict_search('ipv6.address.no_default_link_local', config) != None: + self.del_ipv6_eui64_address('fe80::/64') + else: + self.add_ipv6_eui64_address('fe80::/64') - # Add IPv6 EUI-based addresses - tmp = dict_search('ipv6.address.eui64', config) - if tmp: - for addr in tmp: - self.add_ipv6_eui64_address(addr) + # Add IPv6 EUI-based addresses + tmp = dict_search('ipv6.address.eui64', config) + if tmp: + for addr in tmp: + self.add_ipv6_eui64_address(addr) # re-add ourselves to any bridge we might have fallen out of if 'is_bridge_member' in config: diff --git a/python/vyos/ifconfig/loopback.py b/python/vyos/ifconfig/loopback.py index 30c890fdf..b3babfadc 100644 --- a/python/vyos/ifconfig/loopback.py +++ b/python/vyos/ifconfig/loopback.py @@ -14,7 +14,6 @@ # License along with this library. If not, see . from vyos.ifconfig.interface import Interface -from vyos.util import is_ipv6_enabled @Interface.register class LoopbackIf(Interface): @@ -58,15 +57,14 @@ class LoopbackIf(Interface): interface setup code and provide a single point of entry when workin on any interface. """ - addr = config.get('address', []) - + address = config.get('address', []) # We must ensure that the loopback addresses are never deleted from the system - addr.append('127.0.0.1/8') - if is_ipv6_enabled(): - addr.append('::1/128') + for tmp in self._persistent_addresses: + if tmp not in address: + address.append(tmp) # Update IP address entry in our dictionary - config.update({'address' : addr}) + config.update({'address' : address}) # call base class super().update(config) diff --git a/python/vyos/util.py b/python/vyos/util.py index 0bf6b699e..de55e108b 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -1024,7 +1024,3 @@ def sysctl_write(name, value): call(f'sysctl -wq {name}={value}') return True return False - -def is_ipv6_enabled() -> bool: - """ Check if IPv6 support on the system is enabled or not """ - return (sysctl_read('net.ipv6.conf.all.disable_ipv6') == '0') diff --git a/smoketest/configs/ipv6-disable b/smoketest/configs/ipv6-disable new file mode 100644 index 000000000..da41e9020 --- /dev/null +++ b/smoketest/configs/ipv6-disable @@ -0,0 +1,83 @@ +interfaces { + ethernet eth0 { + duplex auto + smp-affinity auto + speed auto + vif 201 { + address 172.18.201.10/24 + } + vif 202 { + address 172.18.202.10/24 + } + vif 203 { + address 172.18.203.10/24 + } + vif 204 { + address 172.18.204.10/24 + } + } +} +protocols { + static { + route 0.0.0.0/0 { + next-hop 172.18.201.254 { + distance 10 + } + next-hop 172.18.202.254 { + distance 20 + } + next-hop 172.18.203.254 { + distance 30 + } + next-hop 172.18.204.254 { + distance 40 + } + } + } +} +system { + config-management { + commit-revisions 200 + } + console { + device ttyS0 { + speed 115200 + } + } + domain-name vyos.net + host-name vyos + ipv6 { + disable + } + login { + user vyos { + authentication { + encrypted-password $6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/ + plaintext-password "" + } + level admin + } + } + name-server 172.16.254.20 + name-server 172.16.254.30 + ntp { + server 172.16.254.20 { + } + server 172.16.254.30 { + } + } + syslog { + global { + facility all { + level info + } + facility protocols { + level debug + } + } + } +} + +/* Warning: Do not remove the following line. */ +/* === vyatta-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack-sync@1:conntrack@1:dhcp-relay@2:dhcp-server@5:dns-forwarding@1:firewall@5:ipsec@5:l2tp@1:mdns@1:nat@4:ntp@1:pptp@1:qos@1:quagga@6:snmp@1:ssh@1:system@9:vrrp@2:wanloadbalance@3:webgui@1:webproxy@1:webproxy@2:zone-policy@1" === */ +/* Release version: 1.2.6 */ diff --git a/smoketest/scripts/cli/test_system_ipv6.py b/smoketest/scripts/cli/test_system_ipv6.py index 837d1dc12..c8aea9100 100755 --- a/smoketest/scripts/cli/test_system_ipv6.py +++ b/smoketest/scripts/cli/test_system_ipv6.py @@ -20,7 +20,6 @@ from base_vyostest_shim import VyOSUnitTestSHIM from vyos.template import is_ipv4 from vyos.util import read_file -from vyos.util import is_ipv6_enabled from vyos.util import get_interface_config from vyos.validate import is_intf_addr_assigned @@ -46,41 +45,6 @@ class TestSystemIPv6(VyOSUnitTestSHIM.TestCase): self.assertEqual(read_file(file_forwarding), '0') - def test_system_ipv6_disable(self): - # Verify previous "enable" state - self.assertEqual(read_file(file_disable), '0') - self.assertTrue(is_ipv6_enabled()) - - loopbacks = ['127.0.0.1', '::1'] - for addr in loopbacks: - self.assertTrue(is_intf_addr_assigned('lo', addr)) - - # Do not assign any IPv6 address on interfaces, this requires a reboot - # which can not be tested, but we can read the config file :) - self.cli_set(base_path + ['disable']) - self.cli_commit() - - # Verify configuration file - self.assertEqual(read_file(file_disable), '1') - self.assertFalse(is_ipv6_enabled()) - - for addr in loopbacks: - if is_ipv4(addr): - self.assertTrue(is_intf_addr_assigned('lo', addr)) - else: - self.assertFalse(is_intf_addr_assigned('lo', addr)) - - # T4330: Verify MTU can be changed with IPv6 disabled - mtu = '1600' - eth_if = 'eth0' - self.cli_set(['interfaces', 'ethernet', eth_if, 'mtu', mtu]) - self.cli_commit() - - tmp = get_interface_config(eth_if) - self.assertEqual(tmp['mtu'], int(mtu)) - - self.cli_delete(['interfaces', 'ethernet', eth_if, 'mtu']) - def test_system_ipv6_strict_dad(self): # This defaults to 1 self.assertEqual(read_file(file_dad), '1') diff --git a/src/conf_mode/system-ipv6.py b/src/conf_mode/system-ipv6.py index e6bcc12ad..26aacf46b 100755 --- a/src/conf_mode/system-ipv6.py +++ b/src/conf_mode/system-ipv6.py @@ -17,11 +17,8 @@ import os from sys import exit -from vyos.base import DeprecationWarning from vyos.config import Config from vyos.configdict import dict_merge -from vyos.configdict import leaf_node_changed -from vyos.util import call from vyos.util import dict_search from vyos.util import sysctl_write from vyos.util import write_file @@ -39,9 +36,6 @@ def get_config(config=None): opt = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - tmp = leaf_node_changed(conf, base + ['disable']) - if tmp: opt['reboot_required'] = {} - # We have gathered the dict representation of the CLI, but there are default # options which we need to update into the dictionary retrived. default_values = defaults(base) @@ -50,24 +44,12 @@ def get_config(config=None): return opt def verify(opt): - if 'disable' in opt: - DeprecationWarning('VyOS 1.4 (sagitta) will remove the CLI command to '\ - 'disable IPv6 address family in the Linux Kernel!') pass def generate(opt): pass def apply(opt): - # disable IPv6 globally - tmp = dict_search('disable', opt) - value = '1' if (tmp != None) else '0' - sysctl_write('net.ipv6.conf.all.disable_ipv6', value) - - if 'reboot_required' in opt: - print('Changing IPv6 disable parameter will only take affect\n' \ - 'when the system is rebooted.') - # configure multipath tmp = dict_search('multipath.layer4_hashing', opt) value = '1' if (tmp != None) else '0' diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py index c3e2d8efd..f79c8a21e 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -30,7 +30,6 @@ from vyos.util import get_interface_config from vyos.util import popen from vyos.util import run from vyos.util import sysctl_write -from vyos.util import is_ipv6_enabled from vyos import ConfigError from vyos import frr from vyos import airbag @@ -219,8 +218,7 @@ def apply(vrf): # We also should add proper loopback IP addresses to the newly added # VRF for services bound to the loopback address (SNMP, NTP) vrf_if.add_addr('127.0.0.1/8') - if is_ipv6_enabled(): - vrf_if.add_addr('::1/128') + vrf_if.add_addr('::1/128') # add VRF description if available vrf_if.set_alias(config.get('description', '')) diff --git a/src/migration-scripts/system/22-to-23 b/src/migration-scripts/system/22-to-23 new file mode 100755 index 000000000..7f832e48a --- /dev/null +++ b/src/migration-scripts/system/22-to-23 @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 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 . + +import os + +from sys import exit, argv +from vyos.configtree import ConfigTree + +if (len(argv) < 1): + print("Must specify file name!") + exit(1) + +file_name = argv[1] +with open(file_name, 'r') as f: + config_file = f.read() + +base = ['system', 'ipv6'] +config = ConfigTree(config_file) + +if not config.exists(base): + # Nothing to do + exit(0) + +# T4346: drop support to disbale IPv6 address family within the OS Kernel +if config.exists(base + ['disable']): + config.delete(base + ['disable']) + # IPv6 address family disable was the only CLI option set - we can cleanup + # the entire tree + if len(config.list_nodes(base)) == 0: + config.delete(base) + +try: + with open(file_name, 'w') as f: + f.write(config.to_string()) +except OSError as e: + print(f'Failed to save the modified config: {e}') + exit(1) diff --git a/src/tests/test_util.py b/src/tests/test_util.py index 91890262c..8ac9a500a 100644 --- a/src/tests/test_util.py +++ b/src/tests/test_util.py @@ -26,13 +26,3 @@ class TestVyOSUtil(TestCase): def test_sysctl_read(self): self.assertEqual(sysctl_read('net.ipv4.conf.lo.forwarding'), '1') - - def test_ipv6_enabled(self): - tmp = sysctl_read('net.ipv6.conf.all.disable_ipv6') - # We need to test for both variants as this depends on how the - # Docker container is started (with or without IPv6 support) - so we - # will simply check both cases to not make the users life miserable. - if tmp == '0': - self.assertTrue(is_ipv6_enabled()) - else: - self.assertFalse(is_ipv6_enabled()) -- cgit v1.2.3 From 44c67e54ef6ecdf4d7b62e765ccfa4e724c14316 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 7 Apr 2022 20:43:39 +0200 Subject: policy: T4194: simplify prefix-list duplication checks Commit 5dafe255d ("policy: T4194: Add prefix-list duplication checks") added first support for FRR prefix-list duplication checks. FRR does not allow to specify the same profix list rule multiple times. vyos(config)# ip prefix-list foo seq 10 permit 192.0.2.0/24 vyos(config)# ip prefix-list foo seq 20 permit 192.0.2.0/24 % Configuration failed. Error type: validation Error description: duplicated prefix list value: 192.0.2.0/24 There is a VyOS verify() function which simply probed for the prefix, action, le and ge settings - but as Python has excellent support when comparing data, this can be as simple as a dictionary comparison using "==". --- smoketest/scripts/cli/test_policy.py | 34 ++++++++++++++++++++++++++++++++++ src/conf_mode/policy.py | 9 ++++----- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/smoketest/scripts/cli/test_policy.py b/smoketest/scripts/cli/test_policy.py index 0acd41903..b232a2241 100755 --- a/smoketest/scripts/cli/test_policy.py +++ b/smoketest/scripts/cli/test_policy.py @@ -665,6 +665,40 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): self.assertIn(tmp, config) + def test_prefix_list_duplicates(self): + # FRR does not allow to specify the same profix list rule multiple times + # + # vyos(config)# ip prefix-list foo seq 10 permit 192.0.2.0/24 + # vyos(config)# ip prefix-list foo seq 20 permit 192.0.2.0/24 + # % Configuration failed. + # Error type: validation + # Error description: duplicated prefix list value: 192.0.2.0/24 + + # There is also a VyOS verify() function to test this + + prefix = '100.64.0.0/10' + prefix_list = 'duplicates' + test_range = range(20, 25) + path = base_path + ['prefix-list', prefix_list] + + for rule in test_range: + self.cli_set(path + ['rule', str(rule), 'action', 'permit']) + self.cli_set(path + ['rule', str(rule), 'prefix', prefix]) + + # Duplicate prefixes + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + for rule in test_range: + self.cli_set(path + ['rule', str(rule), 'le', str(rule)]) + + self.cli_commit() + + config = self.getFRRconfig('ip prefix-list', end='') + for rule in test_range: + tmp = f'ip prefix-list {prefix_list} seq {rule} permit {prefix} le {rule}' + self.assertIn(tmp, config) + def test_route_map(self): access_list = '50' as_path_list = '100' diff --git a/src/conf_mode/policy.py b/src/conf_mode/policy.py index 6b1d3bf1a..9d8fcfa36 100755 --- a/src/conf_mode/policy.py +++ b/src/conf_mode/policy.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2022 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 @@ -114,10 +114,9 @@ def verify(policy): if 'prefix' not in rule_config: raise ConfigError(f'A prefix {mandatory_error}') - # Check prefix duplicates - if rule_config['prefix'] in entries and ('ge' not in rule_config and 'le' not in rule_config): - raise ConfigError(f'Prefix {rule_config["prefix"]} is duplicated!') - entries.append(rule_config['prefix']) + if rule_config in entries: + raise ConfigError(f'Rule "{rule}" contains a duplicate prefix definition!') + entries.append(rule_config) # route-maps tend to be a bit more complex so they get their own verify() section -- cgit v1.2.3 From 4c89134c632de11da52564f432483011a1cd0034 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Thu, 7 Apr 2022 11:58:29 -0500 Subject: http api: T4347: return complete and consistent error codes --- data/templates/https/nginx.default.tmpl | 13 ------------- src/services/vyos-http-api-server | 2 +- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/data/templates/https/nginx.default.tmpl b/data/templates/https/nginx.default.tmpl index e8511bd62..a51505270 100644 --- a/data/templates/https/nginx.default.tmpl +++ b/data/templates/https/nginx.default.tmpl @@ -53,19 +53,6 @@ server { } error_page 497 =301 https://$host:{{ server.port }}$request_uri; - error_page 501 502 503 =200 @50*_json; - -{% if api_set %} - location @50*_json { - default_type application/json; - return 200 '{"error": "service https api unavailable at this proxy address: set service https api-restrict virtual-host"}'; - } -{% else %} - location @50*_json { - default_type application/json; - return 200 '{"error": "Start service in configuration mode: set service https api"}'; - } -{% endif %} } diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server index 1000d8b72..c1b595412 100755 --- a/src/services/vyos-http-api-server +++ b/src/services/vyos-http-api-server @@ -352,7 +352,7 @@ class MultipartRoute(APIRoute): return error(e.status_code, e.detail) except Exception as e: if request.ERR_MISSING_KEY: - return error(422, "Valid API key is required") + return error(401, "Valid API key is required") if request.ERR_MISSING_DATA: return error(422, "Non-empty data field is required") if request.ERR_NOT_JSON: -- cgit v1.2.3 From 143621114e715eec996a2368011d7f559e89fa07 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Thu, 7 Apr 2022 14:57:44 -0500 Subject: smoketest: http: bind http api to unix domain socket --- smoketest/scripts/cli/test_service_https.py | 1 + 1 file changed, 1 insertion(+) diff --git a/smoketest/scripts/cli/test_service_https.py b/smoketest/scripts/cli/test_service_https.py index 2901cafa8..921344255 100755 --- a/smoketest/scripts/cli/test_service_https.py +++ b/smoketest/scripts/cli/test_service_https.py @@ -106,6 +106,7 @@ class TestHTTPSService(VyOSUnitTestSHIM.TestCase): port = '443' name = 'localhost' + self.cli_set(base_path + ['api', 'socket']) key = 'MySuperSecretVyOS' self.cli_set(base_path + ['api', 'keys', 'id', 'key-01', 'key', key]) -- cgit v1.2.3 From 84bfac85a711d22d97e1ee39a5233738e14a7750 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Thu, 7 Apr 2022 15:04:55 -0500 Subject: smoketest: http: add check for missing key --- smoketest/scripts/cli/test_service_https.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/smoketest/scripts/cli/test_service_https.py b/smoketest/scripts/cli/test_service_https.py index 921344255..9413d22d1 100755 --- a/smoketest/scripts/cli/test_service_https.py +++ b/smoketest/scripts/cli/test_service_https.py @@ -133,5 +133,10 @@ class TestHTTPSService(VyOSUnitTestSHIM.TestCase): # Must get HTTP code 401 on invalid key (Unauthorized) self.assertEqual(r.status_code, 401) + payload_no_key = {'data': '{"op": "showConfig", "path": []}'} + r = request('POST', url, verify=False, headers=headers, data=payload_no_key) + # Must get HTTP code 401 on missing key (Unauthorized) + self.assertEqual(r.status_code, 401) + if __name__ == '__main__': unittest.main(verbosity=2) -- cgit v1.2.3 From 57c0789f47e02090cf9a627028f6c29a1ff89c59 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 8 Apr 2022 11:27:26 +0200 Subject: smoketest: vrf: T4346: IPv6 address family can no longer be disabled in the Kernel --- smoketest/scripts/cli/test_vrf.py | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/smoketest/scripts/cli/test_vrf.py b/smoketest/scripts/cli/test_vrf.py index d6dd68f8b..c591d6cf5 100755 --- a/smoketest/scripts/cli/test_vrf.py +++ b/smoketest/scripts/cli/test_vrf.py @@ -131,41 +131,6 @@ class VRFTest(VyOSUnitTestSHIM.TestCase): for addr in loopbacks: self.assertTrue(is_intf_addr_assigned(vrf, addr)) - def test_vrf_loopbacks_no_ipv6(self): - table = '2002' - for vrf in vrfs: - base = base_path + ['name', vrf] - self.cli_set(base + ['table', str(table)]) - table = str(int(table) + 1) - - # Globally disable IPv6 - this will remove all IPv6 interface addresses - self.cli_set(['system', 'ipv6', 'disable']) - - # commit changes - self.cli_commit() - - # Verify VRF configuration - table = '2002' - loopbacks = ['127.0.0.1', '::1'] - for vrf in vrfs: - # Ensure VRF was created - self.assertIn(vrf, interfaces()) - - # Verify VRF table ID - tmp = get_interface_config(vrf) - self.assertEqual(int(table), tmp['linkinfo']['info_data']['table']) - - # Test for proper loopback IP assignment - for addr in loopbacks: - if is_ipv4(addr): - self.assertTrue(is_intf_addr_assigned(vrf, addr)) - else: - self.assertFalse(is_intf_addr_assigned(vrf, addr)) - - table = str(int(table) + 1) - - self.cli_delete(['system', 'ipv6']) - def test_vrf_bind_all(self): table = '2000' for vrf in vrfs: -- cgit v1.2.3 From d8f778456761124683443400a9182a39714268f4 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 8 Apr 2022 11:34:36 +0200 Subject: container: T4333: migrate to new vyos_defined Jinja2 test --- data/templates/containers/registry.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/containers/registry.tmpl b/data/templates/containers/registry.tmpl index 0347de673..0cbd9ecc2 100644 --- a/data/templates/containers/registry.tmpl +++ b/data/templates/containers/registry.tmpl @@ -1,5 +1,5 @@ ### Autogenerated by /usr/libexec/vyos/conf_mode/containers.py ### -{% if registry is defined and registry is not none %} +{% if registry is vyos_defined %} unqualified-search-registries = {{ registry }} {% endif %} -- cgit v1.2.3 From 654dbc9aa3b0d27ec4f3faefff6cbd85fc3e1d1a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 8 Apr 2022 14:48:17 +0200 Subject: dhcp(v6): T4333: migrate to new vyos_defined Jinja2 test --- data/templates/dhcp-client/daemon-options.tmpl | 2 +- data/templates/dhcp-client/ipv4.tmpl | 8 +- data/templates/dhcp-client/ipv6.tmpl | 34 ++++----- data/templates/dhcp-relay/dhcrelay.conf.tmpl | 2 +- data/templates/dhcp-relay/dhcrelay6.conf.tmpl | 10 +-- data/templates/dhcp-server/dhcpd.conf.tmpl | 102 ++++++++++++------------- data/templates/dhcp-server/dhcpdv6.conf.tmpl | 66 ++++++++-------- 7 files changed, 112 insertions(+), 112 deletions(-) diff --git a/data/templates/dhcp-client/daemon-options.tmpl b/data/templates/dhcp-client/daemon-options.tmpl index 40629dca1..5b3bff73f 100644 --- a/data/templates/dhcp-client/daemon-options.tmpl +++ b/data/templates/dhcp-client/daemon-options.tmpl @@ -1,4 +1,4 @@ ### Autogenerated by interface.py ### -DHCLIENT_OPTS="-nw -cf /var/lib/dhcp/dhclient_{{ifname}}.conf -pf /var/lib/dhcp/dhclient_{{ifname}}.pid -lf /var/lib/dhcp/dhclient_{{ifname}}.leases{{" -e IF_METRIC=" ~ dhcp_options.default_route_distance if dhcp_options.default_route_distance is defined and dhcp_options.default_route_distance is not none}} {{ifname}}" +DHCLIENT_OPTS="-nw -cf /var/lib/dhcp/dhclient_{{ ifname }}.conf -pf /var/lib/dhcp/dhclient_{{ ifname }}.pid -lf /var/lib/dhcp/dhclient_{{ ifname }}.leases{{" -e IF_METRIC=" ~ dhcp_options.default_route_distance if dhcp_options.default_route_distance is vyos_defined }} {{ ifname }}" diff --git a/data/templates/dhcp-client/ipv4.tmpl b/data/templates/dhcp-client/ipv4.tmpl index b3e74c22b..83fb93dc1 100644 --- a/data/templates/dhcp-client/ipv4.tmpl +++ b/data/templates/dhcp-client/ipv4.tmpl @@ -7,7 +7,7 @@ initial-interval 2; interface "{{ ifname }}" { send host-name "{{ dhcp_options.host_name }}"; -{% if dhcp_options.client_id is defined and dhcp_options.client_id is not none %} +{% if dhcp_options.client_id is vyos_defined %} {% set client_id = dhcp_options.client_id %} {# Use HEX representation of client-id as it is send in MAC-address style using hex characters. If not HEX, use double quotes ASCII format #} {% if not dhcp_options.client_id.split(':') | length >= 5 %} @@ -15,18 +15,18 @@ interface "{{ ifname }}" { {% endif %} send dhcp-client-identifier {{ client_id }}; {% endif %} -{% if dhcp_options.vendor_class_id is defined and dhcp_options.vendor_class_id is not none %} +{% if dhcp_options.vendor_class_id is vyos_defined %} send vendor-class-identifier "{{ dhcp_options.vendor_class_id }}"; {% endif %} # The request statement causes the client to request that any server responding to the # client send the client its values for the specified options. - request subnet-mask, broadcast-address,{{ " routers," if dhcp_options.no_default_route is not defined }} domain-name-servers, + request subnet-mask, broadcast-address,{{ " routers," if dhcp_options.no_default_route is not vyos_defined }} domain-name-servers, rfc3442-classless-static-routes, domain-name, interface-mtu; # The require statement lists options that must be sent in order for an offer to be # accepted. Offers that do not contain all the listed options will be ignored! require subnet-mask; -{% if dhcp_options.reject is defined and dhcp_options.reject is not none %} +{% if dhcp_options.reject is vyos_defined %} # Block addresses coming from theses dhcp servers if configured. reject {{ dhcp_options.reject | join(', ') }}; {% endif %} diff --git a/data/templates/dhcp-client/ipv6.tmpl b/data/templates/dhcp-client/ipv6.tmpl index c292664e9..085cfe5a9 100644 --- a/data/templates/dhcp-client/ipv6.tmpl +++ b/data/templates/dhcp-client/ipv6.tmpl @@ -2,54 +2,54 @@ # man https://www.unix.com/man-page/debian/5/dhcp6c.conf/ interface {{ ifname }} { -{% if dhcpv6_options is defined and dhcpv6_options.duid is defined and dhcpv6_options.duid is not none %} +{% if dhcpv6_options.duid is vyos_defined %} send client-id {{ dhcpv6_options.duid }}; {% endif %} -{% if address is defined and 'dhcpv6' in address %} +{% if address is vyos_defined and 'dhcpv6' in address %} request domain-name-servers; request domain-name; -{% if dhcpv6_options is defined and dhcpv6_options.parameters_only is defined %} +{% if dhcpv6_options.parameters_only is vyos_defined %} information-only; {% endif %} -{% if dhcpv6_options is not defined or dhcpv6_options.temporary is not defined %} +{% if dhcpv6_options.temporary is not vyos_defined %} send ia-na 0; # non-temporary address {% endif %} -{% if dhcpv6_options is defined and dhcpv6_options.rapid_commit is defined %} +{% if dhcpv6_options.rapid_commit is vyos_defined %} send rapid-commit; # wait for immediate reply instead of advertisements {% endif %} {% endif %} -{% if dhcpv6_options is defined and dhcpv6_options.pd is defined %} +{% if dhcpv6_options.pd is vyos_defined %} {% for pd in dhcpv6_options.pd %} send ia-pd {{ pd }}; # prefix delegation #{{ pd }} {% endfor %} {% endif %} }; -{% if address is defined and 'dhcpv6' in address %} -{% if dhcpv6_options is not defined or dhcpv6_options.temporary is not defined %} +{% if address is vyos_defined and 'dhcpv6' in address %} +{% if dhcpv6_options.temporary is not vyos_defined %} id-assoc na 0 { # Identity association for non temporary address }; {% endif %} {% endif %} -{% if dhcpv6_options is defined and dhcpv6_options.pd is defined %} -{% for pd in dhcpv6_options.pd %} +{% if dhcpv6_options.pd is vyos_defined %} +{% for pd, pd_config in dhcpv6_options.pd.items() %} id-assoc pd {{ pd }} { {# length got a default value #} - prefix ::/{{ dhcpv6_options.pd[pd].length }} infinity; -{% set sla_len = 64 - dhcpv6_options.pd[pd].length|int %} + prefix ::/{{ pd_config.length }} infinity; +{% set sla_len = 64 - pd_config.length|int %} {% set count = namespace(value=0) %} -{% for interface in dhcpv6_options.pd[pd].interface if dhcpv6_options.pd[pd].interface is defined %} +{% for interface, interface_config in pd_config.interface.items() if pd_config.interface is vyos_defined %} prefix-interface {{ interface }} { sla-len {{ sla_len }}; -{% if dhcpv6_options.pd[pd].interface[interface].sla_id is defined and dhcpv6_options.pd[pd].interface[interface].sla_id is not none %} - sla-id {{ dhcpv6_options.pd[pd].interface[interface].sla_id }}; +{% if interface_config.sla_id is vyos_defined %} + sla-id {{ interface_config.sla_id }}; {% else %} sla-id {{ count.value }}; {% endif %} -{% if dhcpv6_options.pd[pd].interface[interface].address is defined and dhcpv6_options.pd[pd].interface[interface].address is not none %} - ifid {{ dhcpv6_options.pd[pd].interface[interface].address }}; +{% if interface_config.address is vyos_defined %} + ifid {{ interface_config.address }}; {% endif %} }; {% set count.value = count.value + 1 %} diff --git a/data/templates/dhcp-relay/dhcrelay.conf.tmpl b/data/templates/dhcp-relay/dhcrelay.conf.tmpl index a9d17ed9a..11710bd8e 100644 --- a/data/templates/dhcp-relay/dhcrelay.conf.tmpl +++ b/data/templates/dhcp-relay/dhcrelay.conf.tmpl @@ -1,6 +1,6 @@ ### Autogenerated by dhcp_relay.py ### -{% set max_size = '-A ' + relay_options.max_size if relay_options.max_size is defined and relay_options.max_size is not none %} +{% set max_size = '-A ' ~ relay_options.max_size if relay_options.max_size is vyos_defined %} {# hop_count and relay_agents_packets is a default option, thus it is always present #} OPTIONS="-c {{ relay_options.hop_count }} -a -m {{ relay_options.relay_agents_packets }} {{ max_size }} -i {{ interface | join(' -i ') }} {{ server | join(' ') }}" diff --git a/data/templates/dhcp-relay/dhcrelay6.conf.tmpl b/data/templates/dhcp-relay/dhcrelay6.conf.tmpl index 58c216b7c..1fd5de18c 100644 --- a/data/templates/dhcp-relay/dhcrelay6.conf.tmpl +++ b/data/templates/dhcp-relay/dhcrelay6.conf.tmpl @@ -4,18 +4,18 @@ {% set upstream = namespace(value='') %} {% for interface, config in upstream_interface.items() %} {% for address in config.address %} -{% set upstream.value = upstream.value + '-u ' + address + '%' + interface + ' ' %} +{% set upstream.value = upstream.value ~ '-u ' ~ address ~ '%' ~ interface ~ ' ' %} {% endfor %} {% endfor %} {# listen_interface is mandatory so it's always present #} {% set listen = namespace(value='') %} {% for interface, config in listen_interface.items() %} -{% if config.address is defined and config.address is not none %} -{% set listen.value = listen.value + '-l ' + config.address + '%' + interface + ' ' %} +{% if config.address is vyos_defined %} +{% set listen.value = listen.value ~ '-l ' ~ config.address ~ '%' ~ interface ~ ' ' %} {% else %} -{% set listen.value = listen.value + '-l ' + interface + ' ' %} +{% set listen.value = listen.value ~ '-l ' ~ interface ~ ' ' %} {% endif %} {% endfor %} -OPTIONS="{{ listen.value }} {{ upstream.value }} -c {{ max_hop_count }} {{ '-I' if use_interface_id_option is defined }}" +OPTIONS="{{ listen.value }} {{ upstream.value }} -c {{ max_hop_count }} {{ '-I' if use_interface_id_option is vyos_defined }}" diff --git a/data/templates/dhcp-server/dhcpd.conf.tmpl b/data/templates/dhcp-server/dhcpd.conf.tmpl index dbd864b5e..b16d82f5c 100644 --- a/data/templates/dhcp-server/dhcpd.conf.tmpl +++ b/data/templates/dhcp-server/dhcpd.conf.tmpl @@ -4,7 +4,7 @@ # https://www.isc.org/wp-content/uploads/2017/08/dhcp43options.html # # log-facility local7; -{% if hostfile_update is defined %} +{% if hostfile_update is vyos_defined %} on release { set ClientName = pick-first-value(host-decl-name, option fqdn.hostname, option host-name); set ClientIp = binary-to-ascii(10, 8, ".",leased-address); @@ -17,13 +17,13 @@ on expiry { } {% endif %} -{{ 'use-host-decl-names on;' if host_decl_name is defined }} -ddns-update-style {{ 'interim' if dynamic_dns_update is defined else 'none' }}; +{{ 'use-host-decl-names on;' if host_decl_name is vyos_defined }} +ddns-update-style {{ 'interim' if dynamic_dns_update is vyos_defined else 'none' }}; option rfc3442-static-route code 121 = array of integer 8; option windows-static-route code 249 = array of integer 8; option wpad-url code 252 = text; -{% if global_parameters is defined and global_parameters is not none %} +{% if global_parameters is vyos_defined %} # The following {{ global_parameters | length }} line(s) have been added as # global-parameters in the CLI and have not been validated !!! {% for parameter in global_parameters %} @@ -31,7 +31,7 @@ option wpad-url code 252 = text; {% endfor %} {% endif %} -{% if failover is defined and failover is not none %} +{% if failover is vyos_defined %} # DHCP failover configuration failover peer "{{ failover.name }}" { {% if failover.status == 'primary' %} @@ -50,7 +50,7 @@ failover peer "{{ failover.name }}" { load balance max seconds 3; } {% endif %} -{% if listen_address is defined and listen_address is not none %} +{% if listen_address is vyos_defined %} # DHCP server serving relay subnet, we need a connector to the real world {% for address in listen_address %} @@ -60,70 +60,70 @@ subnet {{ address | network_from_ipv4 }} netmask {{ address | netmask_from_ipv4 {% endif %} # Shared network configration(s) -{% if shared_network_name is defined and shared_network_name is not none %} -{% for network, network_config in shared_network_name.items() if network_config.disable is not defined %} +{% if shared_network_name is vyos_defined %} +{% for network, network_config in shared_network_name.items() if network_config.disable is not vyos_defined %} shared-network {{ network | replace('_','-') }} { -{% if network_config.authoritative is defined %} +{% if network_config.authoritative is vyos_defined %} authoritative; {% endif %} -{% if network_config.name_server is defined and network_config.name_server is not none %} +{% if network_config.name_server is vyos_defined %} option domain-name-servers {{ network_config.name_server | join(', ') }}; {% endif %} -{% if network_config.domain_name is defined and network_config.domain_name is not none %} +{% if network_config.domain_name is vyos_defined %} option domain-name "{{ network_config.domain_name }}"; {% endif %} -{% if network_config.domain_search is defined and network_config.domain_search is not none %} +{% if network_config.domain_search is vyos_defined %} option domain-search "{{ network_config.domain_search | join('", "') }}"; {% endif %} -{% if network_config.ntp_server is defined and network_config.ntp_server is not none %} +{% if network_config.ntp_server is vyos_defined %} option ntp-servers {{ network_config.ntp_server | join(', ') }}; {% endif %} -{% if network_config.ping_check is defined %} +{% if network_config.ping_check is vyos_defined %} ping-check true; {% endif %} -{% if network_config.shared_network_parameters is defined and network_config.shared_network_parameters is not none %} +{% if network_config.shared_network_parameters is vyos_defined %} # The following {{ network_config.shared_network_parameters | length }} line(s) # were added as shared-network-parameters in the CLI and have not been validated {% for parameter in network_config.shared_network_parameters %} {{ parameter }} {% endfor %} {% endif %} -{% if network_config.subnet is defined and network_config.subnet is not none %} +{% if network_config.subnet is vyos_defined %} {% for subnet, subnet_config in network_config.subnet.items() %} -{% if subnet_config.description is defined and subnet_config.description is not none %} +{% if subnet_config.description is vyos_defined %} # {{ subnet_config.description }} {% endif %} subnet {{ subnet | address_from_cidr }} netmask {{ subnet | netmask_from_cidr }} { -{% if subnet_config.name_server is defined and subnet_config.name_server is not none %} +{% if subnet_config.name_server is vyos_defined %} option domain-name-servers {{ subnet_config.name_server | join(', ') }}; {% endif %} -{% if subnet_config.domain_name is defined and subnet_config.domain_name is not none %} +{% if subnet_config.domain_name is vyos_defined %} option domain-name "{{ subnet_config.domain_name }}"; {% endif %} -{% if subnet_config.domain_search is defined and subnet_config.domain_search is not none %} +{% if subnet_config.domain_search is vyos_defined %} option domain-search "{{ subnet_config.domain_search | join('", "') }}"; {% endif %} -{% if subnet_config.ntp_server is defined and subnet_config.ntp_server is not none %} +{% if subnet_config.ntp_server is vyos_defined %} option ntp-servers {{ subnet_config.ntp_server | join(', ') }}; {% endif %} -{% if subnet_config.pop_server is defined and subnet_config.pop_server is not none %} +{% if subnet_config.pop_server is vyos_defined %} option pop-server {{ subnet_config.pop_server | join(', ') }}; {% endif %} -{% if subnet_config.smtp_server is defined and subnet_config.smtp_server is not none %} +{% if subnet_config.smtp_server is vyos_defined %} option smtp-server {{ subnet_config.smtp_server | join(', ') }}; {% endif %} -{% if subnet_config.time_server is defined and subnet_config.time_server is not none %} +{% if subnet_config.time_server is vyos_defined %} option time-servers {{ subnet_config.time_server | join(', ') }}; {% endif %} -{% if subnet_config.wins_server is defined and subnet_config.wins_server is not none %} +{% if subnet_config.wins_server is vyos_defined %} option netbios-name-servers {{ subnet_config.wins_server | join(', ') }}; {% endif %} -{% if subnet_config.static_route is defined and subnet_config.static_route is not none %} +{% if subnet_config.static_route is vyos_defined %} {% set static_default_route = '' %} -{% if subnet_config.default_router and subnet_config.default_router is not none %} -{% set static_default_route = ', ' + '0.0.0.0/0' | isc_static_route(subnet_config.default_router) %} +{% if subnet_config.default_router is vyos_defined %} +{% set static_default_route = ', ' ~ '0.0.0.0/0' | isc_static_route(subnet_config.default_router) %} {% endif %} -{% if subnet_config.static_route is defined and subnet_config.static_route is not none %} +{% if subnet_config.static_route is vyos_defined %} {% set rfc3442_routes = [] %} {% for route, route_options in subnet_config.static_route.items() %} {% set rfc3442_routes = rfc3442_routes.append(route | isc_static_route(route_options.next_hop)) %} @@ -132,56 +132,56 @@ shared-network {{ network | replace('_','-') }} { option windows-static-route {{ rfc3442_routes | join(', ') }}; {% endif %} {% endif %} -{% if subnet_config.ip_forwarding is defined %} +{% if subnet_config.ip_forwarding is vyos_defined %} option ip-forwarding true; {% endif %} -{% if subnet_config.default_router and subnet_config.default_router is not none %} +{% if subnet_config.default_router is vyos_defined %} option routers {{ subnet_config.default_router }}; {% endif %} -{% if subnet_config.server_identifier is defined and subnet_config.server_identifier is not none %} +{% if subnet_config.server_identifier is vyos_defined %} option dhcp-server-identifier {{ subnet_config.server_identifier }}; {% endif %} -{% if subnet_config.subnet_parameters is defined and subnet_config.subnet_parameters is not none %} +{% if subnet_config.subnet_parameters is vyos_defined %} # The following {{ subnet_config.subnet_parameters | length }} line(s) were added as # subnet-parameters in the CLI and have not been validated!!! {% for parameter in subnet_config.subnet_parameters %} {{ parameter }} {% endfor %} {% endif %} -{% if subnet_config.tftp_server_name is defined and subnet_config.tftp_server_name is not none %} +{% if subnet_config.tftp_server_name is vyos_defined %} option tftp-server-name "{{ subnet_config.tftp_server_name }}"; {% endif %} -{% if subnet_config.bootfile_name is defined and subnet_config.bootfile_name is not none %} +{% if subnet_config.bootfile_name is vyos_defined %} option bootfile-name "{{ subnet_config.bootfile_name }}"; filename "{{ subnet_config.bootfile_name }}"; {% endif %} -{% if subnet_config.bootfile_server is defined and subnet_config.bootfile_server is not none %} +{% if subnet_config.bootfile_server is vyos_defined %} next-server {{ subnet_config.bootfile_server }}; {% endif %} -{% if subnet_config.time_offset is defined and subnet_config.time_offset is not none %} +{% if subnet_config.time_offset is vyos_defined %} option time-offset {{ subnet_config.time_offset }}; {% endif %} -{% if subnet_config.wpad_url is defined and subnet_config.wpad_url is not none %} +{% if subnet_config.wpad_url is vyos_defined %} option wpad-url "{{ subnet_config.wpad_url }}"; {% endif %} -{% if subnet_config.client_prefix_length is defined and subnet_config.client_prefix_length is not none %} +{% if subnet_config.client_prefix_length is vyos_defined %} option subnet-mask {{ ('0.0.0.0/' ~ subnet_config.client_prefix_length) | netmask_from_cidr }}; {% endif %} -{% if subnet_config.lease is defined and subnet_config.lease is not none %} +{% if subnet_config.lease is vyos_defined %} default-lease-time {{ subnet_config.lease }}; max-lease-time {{ subnet_config.lease }}; {% endif %} -{% if network_config.ping_check is not defined and subnet_config.ping_check is defined %} +{% if network_config.ping_check is not vyos_defined and subnet_config.ping_check is vyos_defined %} ping-check true; {% endif %} -{% if subnet_config.static_mapping is defined and subnet_config.static_mapping is not none %} -{% for host, host_config in subnet_config.static_mapping.items() if host_config.disable is not defined %} - host {{ host | replace('_','-') if host_decl_name is defined else network | replace('_','-') + '_' + host | replace('_','-') }} { -{% if host_config.ip_address is defined and host_config.ip_address is not none %} +{% if subnet_config.static_mapping is vyos_defined %} +{% for host, host_config in subnet_config.static_mapping.items() if host_config.disable is not vyos_defined %} + host {{ host | replace('_','-') if host_decl_name is vyos_defined else network | replace('_','-') ~ '_' ~ host | replace('_','-') }} { +{% if host_config.ip_address is vyos_defined %} fixed-address {{ host_config.ip_address }}; {% endif %} hardware ethernet {{ host_config.mac_address }}; -{% if host_config.static_mapping_parameters is defined and host_config.static_mapping_parameters is not none %} +{% if host_config.static_mapping_parameters is vyos_defined %} # The following {{ host_config.static_mapping_parameters | length }} line(s) were added # as static-mapping-parameters in the CLI and have not been validated {% for parameter in host_config.static_mapping_parameters %} @@ -191,20 +191,20 @@ shared-network {{ network | replace('_','-') }} { } {% endfor %} {% endif %} -{% if subnet_config.range is defined and subnet_config.range is not none %} +{% if subnet_config.range is vyos_defined %} {# pool configuration can only be used if there follows a range option #} pool { {% endif %} -{% if subnet_config.enable_failover is defined %} +{% if subnet_config.enable_failover is vyos_defined %} failover peer "{{ failover.name }}"; deny dynamic bootp clients; {% endif %} -{% if subnet_config.range is defined and subnet_config.range is not none %} +{% if subnet_config.range is vyos_defined %} {% for range, range_options in subnet_config.range.items() %} range {{ range_options.start }} {{ range_options.stop }}; {% endfor %} {% endif %} -{% if subnet_config.range is defined and subnet_config.range is not none %} +{% if subnet_config.range is vyos_defined %} {# pool configuration can only be used if there follows a range option #} } {% endif %} @@ -213,7 +213,7 @@ shared-network {{ network | replace('_','-') }} { {% endif %} on commit { set shared-networkname = "{{ network | replace('_','-') }}"; -{% if hostfile_update is defined %} +{% if hostfile_update is vyos_defined %} set ClientIp = binary-to-ascii(10, 8, ".", leased-address); set ClientMac = binary-to-ascii(16, 8, ":", substring(hardware, 1, 6)); set ClientName = pick-first-value(host-decl-name, option fqdn.hostname, option host-name, "empty_hostname"); diff --git a/data/templates/dhcp-server/dhcpdv6.conf.tmpl b/data/templates/dhcp-server/dhcpdv6.conf.tmpl index 45d629928..c6a4f4c92 100644 --- a/data/templates/dhcp-server/dhcpdv6.conf.tmpl +++ b/data/templates/dhcp-server/dhcpdv6.conf.tmpl @@ -4,74 +4,74 @@ # https://www.isc.org/wp-content/uploads/2017/08/dhcp43options.html log-facility local7; -{% if preference is defined and preference is not none %} +{% if preference is vyos_defined %} option dhcp6.preference {{ preference }}; {% endif %} -{% if global_parameters is defined and global_parameters.name_server is defined and global_parameters.name_server is not none %} +{% if global_parameters.name_server is vyos_defined %} option dhcp6.name-servers {{ global_parameters.name_server | join(', ') }}; {% endif %} # Shared network configration(s) -{% if shared_network_name is defined and shared_network_name is not none %} -{% for network, network_config in shared_network_name.items() if network_config.disable is not defined %} +{% if shared_network_name is vyos_defined %} +{% for network, network_config in shared_network_name.items() if network_config.disable is not vyos_defined %} shared-network {{ network | replace('_','-') }} { -{% if network_config.common_options is defined and network_config.common_options is not none %} -{% if network_config.common_options.info_refresh_time is defined and network_config.common_options.info_refresh_time is not none %} +{% if network_config.common_options is vyos_defined %} +{% if network_config.common_options.info_refresh_time is vyos_defined %} option dhcp6.info-refresh-time {{ network_config.common_options.info_refresh_time }}; {% endif %} -{% if network_config.common_options.domain_search is defined and network_config.common_options.domain_search is not none %} +{% if network_config.common_options.domain_search is vyos_defined %} option dhcp6.domain-search "{{ network_config.common_options.domain_search | join('", "') }}"; {% endif %} -{% if network_config.common_options.name_server is defined and network_config.common_options.name_server is not none %} +{% if network_config.common_options.name_server is vyos_defined %} option dhcp6.name-servers {{ network_config.common_options.name_server | join(', ') }}; {% endif %} {% endif %} -{% if network_config.subnet is defined and network_config.subnet is not none %} +{% if network_config.subnet is vyos_defined %} {% for subnet, subnet_config in network_config.subnet.items() %} subnet6 {{ subnet }} { -{% if subnet_config.address_range is defined and subnet_config.address_range is not none %} -{% if subnet_config.address_range.prefix is defined and subnet_config.address_range.prefix is not none %} +{% if subnet_config.address_range is vyos_defined %} +{% if subnet_config.address_range.prefix is vyos_defined %} {% for prefix, prefix_config in subnet_config.address_range.prefix.items() %} - range6 {{ prefix }} {{ "temporary" if prefix_config.temporary is defined }}; + range6 {{ prefix }} {{ "temporary" if prefix_config.temporary is vyos_defined }}; {% endfor %} {% endif %} -{% if subnet_config.address_range.start is defined and subnet_config.address_range.start is not none %} +{% if subnet_config.address_range.start is vyos_defined %} {% for address, address_config in subnet_config.address_range.start.items() %} range6 {{ address }} {{ address_config.stop }}; {% endfor %} {% endif %} {% endif %} -{% if subnet_config.domain_search is defined and subnet_config.domain_search is not none %} +{% if subnet_config.domain_search is vyos_defined %} option dhcp6.domain-search "{{ subnet_config.domain_search | join('", "') }}"; {% endif %} -{% if subnet_config.lease_time is defined and subnet_config.lease_time is not none %} -{% if subnet_config.lease_time.default is defined and subnet_config.lease_time.default is not none %} +{% if subnet_config.lease_time is vyos_defined %} +{% if subnet_config.lease_time.default is vyos_defined %} default-lease-time {{ subnet_config.lease_time.default }}; {% endif %} -{% if subnet_config.lease_time.maximum is defined and subnet_config.lease_time.maximum is not none %} +{% if subnet_config.lease_time.maximum is vyos_defined %} max-lease-time {{ subnet_config.lease_time.maximum }}; {% endif %} -{% if subnet_config.lease_time.minimum is defined and subnet_config.lease_time.minimum is not none %} +{% if subnet_config.lease_time.minimum is vyos_defined %} min-lease-time {{ subnet_config.lease_time.minimum }}; {% endif %} {% endif %} -{% if subnet_config.name_server is defined and subnet_config.name_server is not none %} +{% if subnet_config.name_server is vyos_defined %} option dhcp6.name-servers {{ subnet_config.name_server | join(', ') }}; {% endif %} -{% if subnet_config.nis_domain is defined and subnet_config.nis_domain is not none %} +{% if subnet_config.nis_domain is vyos_defined %} option dhcp6.nis-domain-name "{{ subnet_config.nis_domain }}"; {% endif %} -{% if subnet_config.nis_server is defined and subnet_config.nis_server is not none %} +{% if subnet_config.nis_server is vyos_defined %} option dhcp6.nis-servers {{ subnet_config.nis_server | join(', ') }}; {% endif %} -{% if subnet_config.nisplus_domain is defined and subnet_config.nisplus_domain is not none %} +{% if subnet_config.nisplus_domain is vyos_defined %} option dhcp6.nisp-domain-name "{{ subnet_config.nisplus_domain }}"; {% endif %} -{% if subnet_config.nisplus_server is defined and subnet_config.nisplus_server is not none %} +{% if subnet_config.nisplus_server is vyos_defined %} option dhcp6.nisp-servers {{ subnet_config.nisplus_server | join(', ') }}; {% endif %} -{% if subnet_config.sip_server is defined and subnet_config.sip_server is not none %} +{% if subnet_config.sip_server is vyos_defined %} {% set server_ip = [] %} {% set server_fqdn = [] %} {% for address in subnet_config.sip_server %} @@ -81,33 +81,33 @@ shared-network {{ network | replace('_','-') }} { {% set server_fqdn = server_fqdn.append(address) %} {% endif %} {% endfor %} -{% if server_ip is defined and server_ip | length > 0 %} +{% if server_ip is vyos_defined and server_ip | length > 0 %} option dhcp6.sip-servers-addresses {{ server_ip | join(', ') }}; {% endif %} -{% if server_fqdn is defined and server_fqdn | length > 0 %} +{% if server_fqdn is vyos_defined and server_fqdn | length > 0 %} option dhcp6.sip-servers-names "{{ server_fqdn | join('", "') }}"; {% endif %} {% endif %} -{% if subnet_config.sntp_server is defined and subnet_config.sntp_server is not none %} +{% if subnet_config.sntp_server is vyos_defined %} option dhcp6.sntp-servers {{ subnet_config.sntp_server | join(', ') }}; {% endif %} -{% if subnet_config.prefix_delegation is defined and subnet_config.prefix_delegation.start is defined and subnet_config.prefix_delegation.start is not none %} +{% if subnet_config.prefix_delegation.start is vyos_defined %} {% for prefix, prefix_config in subnet_config.prefix_delegation.start.items() %} prefix6 {{ prefix }} {{ prefix_config.stop }} /{{ prefix_config.prefix_length }}; {% endfor %} {% endif %} -{% if subnet_config.static_mapping is defined and subnet_config.static_mapping is not none %} +{% if subnet_config.static_mapping is vyos_defined %} # begin configuration of static client mappings -{% for host, host_config in subnet_config.static_mapping.items() if host_config.disable is not defined %} +{% for host, host_config in subnet_config.static_mapping.items() if host_config.disable is not vyos_defined %} host {{ network | replace('_','-') }}_{{ host | replace('_','-') }} { -{% if host_config.identifier is defined and host_config.identifier is not none %} +{% if host_config.identifier is vyos_defined %} host-identifier option dhcp6.client-id {{ host_config.identifier }}; {% endif %} -{% if host_config.ipv6_address is defined and host_config.ipv6_address is not none %} +{% if host_config.ipv6_address is vyos_defined %} fixed-address6 {{ host_config.ipv6_address }}; {% endif %} -{% if host_config.ipv6_prefix is defined and host_config.ipv6_prefix is not none %} +{% if host_config.ipv6_prefix is vyos_defined %} fixed-prefix6 {{ host_config.ipv6_prefix }}; {% endif %} } -- cgit v1.2.3