diff options
24 files changed, 478 insertions, 52 deletions
diff --git a/data/configd-include.json b/data/configd-include.json index a762a6d4c..92d3863ce 100644 --- a/data/configd-include.json +++ b/data/configd-include.json @@ -53,6 +53,7 @@ "protocols_rip.py", "protocols_ripng.py", "protocols_rpki.py", +"protocols_segment_routing.py", "protocols_static.py", "protocols_static_multicast.py", "qos.py", diff --git a/data/templates/frr/bgpd.frr.j2 b/data/templates/frr/bgpd.frr.j2 index 6f81174ac..641dac44a 100644 --- a/data/templates/frr/bgpd.frr.j2 +++ b/data/templates/frr/bgpd.frr.j2 @@ -588,6 +588,14 @@ bgp route-reflector allow-outbound-policy {% if parameters.tcp_keepalive.idle is vyos_defined and parameters.tcp_keepalive.interval is vyos_defined and parameters.tcp_keepalive.probes is vyos_defined %} bgp tcp-keepalive {{ parameters.tcp_keepalive.idle }} {{ parameters.tcp_keepalive.interval }} {{ parameters.tcp_keepalive.probes }} {% endif %} +{% if srv6.locator is vyos_defined %} + segment-routing srv6 + locator {{ srv6.locator }} + exit +{% endif %} +{% if sid.vpn.per_vrf.export is vyos_defined %} + sid vpn per-vrf export {{ sid.vpn.per_vrf.export }} +{% endif %} {% if timers.keepalive is vyos_defined and timers.holdtime is vyos_defined %} timers bgp {{ timers.keepalive }} {{ timers.holdtime }} {% endif %} diff --git a/data/templates/frr/zebra.segment_routing.frr.j2 b/data/templates/frr/zebra.segment_routing.frr.j2 new file mode 100644 index 000000000..7b12fcdd0 --- /dev/null +++ b/data/templates/frr/zebra.segment_routing.frr.j2 @@ -0,0 +1,23 @@ +! +{% if srv6.locator is vyos_defined %} +segment-routing + srv6 + locators +{% for locator, locator_config in srv6.locator.items() %} + locator {{ locator }} +{% if locator_config.prefix is vyos_defined %} + prefix {{ locator_config.prefix }} block-len {{ locator_config.block_len }} node-len {{ locator_config.node_len }} func-bits {{ locator_config.func_bits }} +{% endif %} +{% if locator_config.behavior_usid is vyos_defined %} + behavior usid +{% endif %} + exit + ! +{% endfor %} + exit + ! +exit +! +exit +! +{% endif %} diff --git a/debian/control b/debian/control index 816d41944..08adc8a68 100644 --- a/debian/control +++ b/debian/control @@ -279,6 +279,8 @@ Depends: # For "run monitor traffic" tcpdump, # End "run monitor traffic" +# For "show hardware dmi" + dmidecode, # For "run show hardware storage smart" smartmontools, # For "run show hardware scsi" diff --git a/interface-definitions/include/bgp/protocol-common-config.xml.i b/interface-definitions/include/bgp/protocol-common-config.xml.i index 4e43298bc..c379ba95c 100644 --- a/interface-definitions/include/bgp/protocol-common-config.xml.i +++ b/interface-definitions/include/bgp/protocol-common-config.xml.i @@ -1639,6 +1639,66 @@ #include <include/port-number.xml.i> </children> </tagNode> +<node name="srv6"> + <properties> + <help>Segment-Routing SRv6 configuration</help> + </properties> + <children> + <leafNode name="locator"> + <properties> + <help>Specify SRv6 locator</help> + <valueHelp> + <format>txt</format> + <description>SRv6 locator name</description> + </valueHelp> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i> + </constraint> + </properties> + </leafNode> + </children> +</node> +<node name="sid"> + <properties> + <help>SID value for VRF</help> + </properties> + <children> + <node name="vpn"> + <properties> + <help>Between current VRF and VPN</help> + </properties> + <children> + <node name="per-vrf"> + <properties> + <help>SID per-VRF (both IPv4 and IPv6 address families)</help> + </properties> + <children> + <leafNode name="export"> + <properties> + <help>For routes leaked from current VRF to VPN</help> + <completionHelp> + <list>auto</list> + </completionHelp> + <valueHelp> + <format>u32:1-1048575</format> + <description>SID allocation index</description> + </valueHelp> + <valueHelp> + <format>auto</format> + <description>Automatically assign a label</description> + </valueHelp> + <constraint> + <regex>auto</regex> + <validator name="numeric" argument="--range 1-1048575"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + </children> +</node> <node name="timers"> <properties> <help>BGP protocol timers</help> diff --git a/interface-definitions/include/constraint/alpha-numeric-hyphen-underscore.xml.i b/interface-definitions/include/constraint/alpha-numeric-hyphen-underscore.xml.i index ba097c6b5..399f2e1da 100644 --- a/interface-definitions/include/constraint/alpha-numeric-hyphen-underscore.xml.i +++ b/interface-definitions/include/constraint/alpha-numeric-hyphen-underscore.xml.i @@ -1,3 +1,3 @@ -<!-- include start from include/constraint/alpha-numeric-hyphen-underscore.xml.i --> +<!-- include start from constraint/alpha-numeric-hyphen-underscore.xml.i --> <regex>[-_a-zA-Z0-9]+</regex> <!-- include end --> diff --git a/interface-definitions/include/constraint/dhcp-client-string-option.xml.i b/interface-definitions/include/constraint/dhcp-client-string-option.xml.i index 76e0e5466..88257a9bb 100644 --- a/interface-definitions/include/constraint/dhcp-client-string-option.xml.i +++ b/interface-definitions/include/constraint/dhcp-client-string-option.xml.i @@ -1,4 +1,4 @@ -<!-- include start from include/constraint/dhcp-client-string-option.xml.i --> +<!-- include start from constraint/dhcp-client-string-option.xml.i --> <regex>[-_a-zA-Z0-9\s]+</regex> <regex>([a-fA-F0-9][a-fA-F0-9]:){2,}[a-fA-F0-9][a-fA-F0-9]</regex> <!-- include end --> diff --git a/interface-definitions/protocols-segment-routing.xml.in b/interface-definitions/protocols-segment-routing.xml.in new file mode 100644 index 000000000..d461e9c5d --- /dev/null +++ b/interface-definitions/protocols-segment-routing.xml.in @@ -0,0 +1,89 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="protocols"> + <children> + <node name="segment-routing" owner="${vyos_conf_scripts_dir}/protocols_segment_routing.py"> + <properties> + <help>Segment Routing</help> + <priority>900</priority> + </properties> + <children> + <node name="srv6"> + <properties> + <help>Segment-Routing SRv6 configuration</help> + </properties> + <children> + <tagNode name="locator"> + <properties> + <help>Segment Routing SRv6 locator</help> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i> + </constraint> + </properties> + <children> + <leafNode name="behavior-usid"> + <properties> + <help>Set SRv6 behavior uSID</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="prefix"> + <properties> + <help>SRv6 locator prefix</help> + <valueHelp> + <format>ipv6net</format> + <description>SRv6 locator prefix</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + </leafNode> + <leafNode name="block-len"> + <properties> + <help>Configure SRv6 locator block length in bits</help> + <valueHelp> + <format>u32:16-64</format> + <description>Specify SRv6 locator block length in bits</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 16-64"/> + </constraint> + </properties> + <defaultValue>40</defaultValue> + </leafNode> + <leafNode name="func-bits"> + <properties> + <help>Configure SRv6 locator function length in bits</help> + <valueHelp> + <format>u32:0-64</format> + <description>Specify SRv6 locator function length in bits</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-64"/> + </constraint> + </properties> + <defaultValue>16</defaultValue> + </leafNode> + <leafNode name="node-len"> + <properties> + <help>Configure SRv6 locator node length in bits</help> + <valueHelp> + <format>u32:16-64</format> + <description>Configure SRv6 locator node length in bits</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 16-64"/> + </constraint> + </properties> + <defaultValue>24</defaultValue> + </leafNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-bgp.xml.in b/op-mode-definitions/show-bgp.xml.in index 3c212614c..8b1992432 100644 --- a/op-mode-definitions/show-bgp.xml.in +++ b/op-mode-definitions/show-bgp.xml.in @@ -100,6 +100,19 @@ </children> </tagNode> #include <include/vtysh-generic-wide.xml.i> + <node name="segment-routing"> + <properties> + <help>BGP Segment Routing</help> + </properties> + <children> + <leafNode name="srv6"> + <properties> + <help>BGP Segment Routing SRv6</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + </node> </children> </node> </children> diff --git a/op-mode-definitions/show-segment-routing.xml.in b/op-mode-definitions/show-segment-routing.xml.in new file mode 100644 index 000000000..ebdb51a61 --- /dev/null +++ b/op-mode-definitions/show-segment-routing.xml.in @@ -0,0 +1,27 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="segment-routing"> + <properties> + <help>Show Segment Routing</help> + </properties> + <children> + <node name="srv6"> + <properties> + <help>Segment Routing SRv6</help> + </properties> + <children> + <node name="locator"> + <properties> + <help>Locator Information</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </node> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/python/vyos/config_mgmt.py b/python/vyos/config_mgmt.py index fd0fa7a75..e57129ce5 100644 --- a/python/vyos/config_mgmt.py +++ b/python/vyos/config_mgmt.py @@ -386,15 +386,8 @@ Proceed ?''' _, _, netloc = url.netloc.rpartition("@") redacted_location = urlunsplit(url._replace(netloc=netloc)) print(f" {redacted_location}", end=" ", flush=True) - try: - upload(archive_config_file, f'{location}/{remote_file}', - source_host=source_address, raise_error=True) - print("OK") - except Exception as e: - print("FAILED!") - print() - print(indent(str(e), " > ")) - print() + upload(archive_config_file, f'{location}/{remote_file}', + source_host=source_address) # op-mode functions # diff --git a/python/vyos/remote.py b/python/vyos/remote.py index fec44b571..b1efcd10b 100644 --- a/python/vyos/remote.py +++ b/python/vyos/remote.py @@ -452,7 +452,7 @@ def upload(local_path, urlstring, progressbar=False, source_host='', source_port=0, timeout=10.0): try: progressbar = progressbar and is_interactive() - urlc(urlstring, progressbar, source_host, source_port, timeout).upload(local_path) + urlc(urlstring, progressbar, False, source_host, source_port, timeout).upload(local_path) except Exception as err: print_error(f'Unable to upload "{urlstring}": {err}') except KeyboardInterrupt: diff --git a/python/vyos/system/disk.py b/python/vyos/system/disk.py index f8e0fd1bf..b8a2c0f35 100644 --- a/python/vyos/system/disk.py +++ b/python/vyos/system/disk.py @@ -31,12 +31,17 @@ class DiskDetails: def disk_cleanup(drive_path: str) -> None: """Clean up disk partition table (MBR and GPT) + Remove partition and device signatures. Zeroize primary and secondary headers - first and last 17408 bytes (512 bytes * 34 LBA) on a drive Args: drive_path (str): path to a drive that needs to be cleaned """ + partitions: list[str] = partition_list(drive_path) + for partition in partitions: + run(f'wipefs -af {partition}') + run(f'wipefs -af {drive_path}') run(f'sgdisk -Z {drive_path}') diff --git a/python/vyos/system/raid.py b/python/vyos/system/raid.py index 13b99fa69..5b33d34da 100644 --- a/python/vyos/system/raid.py +++ b/python/vyos/system/raid.py @@ -19,7 +19,7 @@ from pathlib import Path from shutil import copy from dataclasses import dataclass -from vyos.utils.process import cmd +from vyos.utils.process import cmd, run from vyos.system import disk @@ -44,18 +44,11 @@ def raid_create(raid_members: list[str], """ raid_devices_num: int = len(raid_members) raid_members_str: str = ' '.join(raid_members) - if Path('/sys/firmware/efi').exists(): - for part in raid_members: - drive: str = disk.partition_parent(part) - command: str = f'sgdisk --typecode=3:A19D880F-05FC-4D3B-A006-743F0F84911E {drive}' - cmd(command) - else: - for part in raid_members: - drive: str = disk.partition_parent(part) - command: str = f'sgdisk --typecode=3:A19D880F-05FC-4D3B-A006-743F0F84911E {drive}' - cmd(command) for part in raid_members: - command: str = f'mdadm --zero-superblock {part}' + drive: str = disk.partition_parent(part) + # set partition type GUID for raid member; cf. + # https://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_type_GUIDs + command: str = f'sgdisk --typecode=3:A19D880F-05FC-4D3B-A006-743F0F84911E {drive}' cmd(command) command: str = f'mdadm --create /dev/{raid_name} -R --metadata=1.0 \ --raid-devices={raid_devices_num} --level={raid_level} \ @@ -72,6 +65,20 @@ def raid_create(raid_members: list[str], return raid +def clear(): + """Deactivate all RAID arrays""" + command: str = 'mdadm --examine --scan' + raid_config = cmd(command) + if not raid_config: + return + command: str = 'mdadm --run /dev/md?*' + run(command) + command: str = 'mdadm --assemble --scan --auto=yes --symlink=no' + run(command) + command: str = 'mdadm --stop --scan' + run(command) + + def update_initramfs() -> None: """Update initramfs""" mdadm_script = '/etc/initramfs-tools/scripts/local-top/mdadm' diff --git a/python/vyos/utils/io.py b/python/vyos/utils/io.py index 74099b502..0afaf695c 100644 --- a/python/vyos/utils/io.py +++ b/python/vyos/utils/io.py @@ -26,13 +26,18 @@ def print_error(str='', end='\n'): sys.stderr.write(end) sys.stderr.flush() -def ask_input(question, default='', numeric_only=False, valid_responses=[]): +def ask_input(question, default='', numeric_only=False, valid_responses=[], + no_echo=False): + from getpass import getpass question_out = question if default: question_out += f' (Default: {default})' response = '' while True: - response = input(question_out + ' ').strip() + if not no_echo: + response = input(question_out + ' ').strip() + else: + response = getpass(question_out + ' ').strip() if not response and default: return default if numeric_only: diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index 71e2142f9..97dab255e 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -1133,5 +1133,20 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' mpls bgp forwarding', frrconfig) self.cli_delete(['interfaces', 'ethernet', interface, 'vrf']) + def test_bgp_24_srv6_sid(self): + locator_name = 'VyOS_foo' + sid = 'auto' + + self.cli_set(base_path + ['srv6', 'locator', locator_name]) + self.cli_set(base_path + ['sid', 'vpn', 'per-vrf', 'export', sid]) + + self.cli_commit() + + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + self.assertIn(f'router bgp {ASN}', frrconfig) + self.assertIn(f' segment-routing srv6', frrconfig) + self.assertIn(f' locator {locator_name}', frrconfig) + self.assertIn(f' sid vpn per-vrf export {sid}', frrconfig) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_segment_routing.py b/smoketest/scripts/cli/test_protocols_segment_routing.py new file mode 100755 index 000000000..81d42b925 --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_segment_routing.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.utils.process import cmd +from vyos.utils.process import process_named_running + +base_path = ['protocols', 'segment-routing'] +PROCESS_NAME = 'zebra' + +class TestProtocolsSegmentRouting(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + # call base-classes classmethod + super(TestProtocolsSegmentRouting, cls).setUpClass() + # Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same + cls.daemon_pid = process_named_running(PROCESS_NAME) + # 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() + + # check process health and continuity + self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) + + def test_srv6(self): + locators = { + 'foo' : { 'prefix' : '2001:a::/64' }, + 'foo' : { 'prefix' : '2001:b::/64', 'usid' : {} }, + } + + for locator, locator_config in locators.items(): + self.cli_set(base_path + ['srv6', 'locator', locator, 'prefix', locator_config['prefix']]) + if 'usid' in locator_config: + self.cli_set(base_path + ['srv6', 'locator', locator, 'behavior-usid']) + + self.cli_commit() + + frrconfig = self.getFRRconfig(f'segment-routing', daemon='zebra') + self.assertIn(f'segment-routing', frrconfig) + self.assertIn(f' srv6', frrconfig) + self.assertIn(f' locators', frrconfig) + for locator, locator_config in locators.items(): + self.assertIn(f' locator {locator}', frrconfig) + self.assertIn(f' prefix {locator_config["prefix"]} block-len 40 node-len 24 func-bits 16', frrconfig) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_dns_dynamic.py b/smoketest/scripts/cli/test_service_dns_dynamic.py index 3c7303f32..ae46b18ba 100755 --- a/smoketest/scripts/cli/test_service_dns_dynamic.py +++ b/smoketest/scripts/cli/test_service_dns_dynamic.py @@ -17,8 +17,6 @@ import os import unittest import tempfile -import random -import string from base_vyostest_shim import VyOSUnitTestSHIM @@ -67,14 +65,12 @@ class TestServiceDDNS(VyOSUnitTestSHIM.TestCase): self.cli_set(name_path + [svc, 'address', interface]) self.cli_set(name_path + [svc, 'host-name', hostname]) self.cli_set(name_path + [svc, 'password', password]) - self.cli_set(name_path + [svc, 'zone', zone]) - self.cli_set(name_path + [svc, 'ttl', ttl]) for opt, value in details.items(): self.cli_set(name_path + [svc, opt, value]) - # 'zone' option is supported and required by 'cloudfare', but not 'freedns' and 'zoneedit' + # 'zone' option is supported by 'cloudfare' and 'zoneedit1', but not 'freedns' self.cli_set(name_path + [svc, 'zone', zone]) - if details['protocol'] == 'cloudflare': + if details['protocol'] in ['cloudflare', 'zoneedit1']: pass else: # exception is raised for unsupported ones @@ -292,7 +288,36 @@ class TestServiceDDNS(VyOSUnitTestSHIM.TestCase): self.assertIn(f'password=\'{password}\'', ddclient_conf) self.assertIn(f'{hostname}', ddclient_conf) - def test_07_dyndns_vrf(self): + def test_07_dyndns_dynamic_interface(self): + # Check if DDNS service can be configured and runs + svc_path = name_path + ['namecheap'] + proto = 'namecheap' + dyn_interface = 'pppoe587' + + self.cli_set(svc_path + ['address', dyn_interface]) + self.cli_set(svc_path + ['protocol', proto]) + self.cli_set(svc_path + ['server', server]) + self.cli_set(svc_path + ['username', username]) + self.cli_set(svc_path + ['password', password]) + self.cli_set(svc_path + ['host-name', hostname]) + + # Dynamic interface will raise a warning but still go through + # XXX: We should have idiomatic class "ConfigSessionWarning" wrapping + # "Warning" similar to "ConfigSessionError". + # with self.assertWarns(Warning): + # self.cli_commit() + self.cli_commit() + + # Check the generating config parameters + ddclient_conf = cmd(f'sudo cat {DDCLIENT_CONF}') + self.assertIn(f'ifv4={dyn_interface}', ddclient_conf) + self.assertIn(f'protocol={proto}', ddclient_conf) + self.assertIn(f'server={server}', ddclient_conf) + self.assertIn(f'login={username}', ddclient_conf) + self.assertIn(f'password=\'{password}\'', ddclient_conf) + self.assertIn(f'{hostname}', ddclient_conf) + + def test_08_dyndns_vrf(self): # Table number randomized, but should be within range 100-65535 vrf_table = '58710' vrf_name = f'vyos-test-{vrf_table}' diff --git a/src/conf_mode/dns_dynamic.py b/src/conf_mode/dns_dynamic.py index c4dcb76ed..809c650d9 100755 --- a/src/conf_mode/dns_dynamic.py +++ b/src/conf_mode/dns_dynamic.py @@ -30,6 +30,9 @@ airbag.enable() config_file = r'/run/ddclient/ddclient.conf' systemd_override = r'/run/systemd/system/ddclient.service.d/override.conf' +# Dynamic interfaces that might not exist when the configuration is loaded +dynamic_interfaces = ('pppoe', 'sstpc') + # Protocols that require zone zone_necessary = ['cloudflare', 'digitalocean', 'godaddy', 'hetzner', 'gandi', 'nfsn', 'nsupdate'] @@ -86,17 +89,19 @@ def verify(dyndns): if field not in config: raise ConfigError(f'"{field.replace("_", "-")}" {error_msg_req}') - # If dyndns address is an interface, ensure that it exists + # If dyndns address is an interface, ensure + # that the interface exists (or just warn if dynamic interface) # and that web-options are not set if config['address'] != 'web': # exclude check interface for dynamic interfaces - interface_filter = ('pppoe', 'sstpc') - if config['address'].startswith(interface_filter): - Warning(f'interface {config["address"]} does not exist!') + if config['address'].startswith(dynamic_interfaces): + Warning(f'Interface "{config["address"]}" does not exist yet and cannot ' + f'be used for Dynamic DNS service "{service}" until it is up!') else: verify_interface_exists(config['address']) if 'web_options' in config: - raise ConfigError(f'"web-options" is applicable only when using HTTP(S) web request to obtain the IP address') + raise ConfigError(f'"web-options" is applicable only when using HTTP(S) ' + f'web request to obtain the IP address') # RFC2136 uses 'key' instead of 'password' if config['protocol'] != 'nsupdate' and 'password' not in config: @@ -124,13 +129,16 @@ def verify(dyndns): if config['ip_version'] == 'both': if config['protocol'] not in dualstack_supported: - raise ConfigError(f'Both IPv4 and IPv6 at the same time {error_msg_uns} with protocol "{config["protocol"]}"') + raise ConfigError(f'Both IPv4 and IPv6 at the same time {error_msg_uns} ' + f'with protocol "{config["protocol"]}"') # dyndns2 protocol in ddclient honors dual stack only for dyn.com (dyndns.org) if config['protocol'] == 'dyndns2' and 'server' in config and config['server'] not in dyndns_dualstack_servers: - raise ConfigError(f'Both IPv4 and IPv6 at the same time {error_msg_uns} for "{config["server"]}" with protocol "{config["protocol"]}"') + raise ConfigError(f'Both IPv4 and IPv6 at the same time {error_msg_uns} ' + f'for "{config["server"]}" with protocol "{config["protocol"]}"') if {'wait_time', 'expiry_time'} <= config.keys() and int(config['expiry_time']) < int(config['wait_time']): - raise ConfigError(f'"expiry-time" must be greater than "wait-time" for Dynamic DNS service "{service}"') + raise ConfigError(f'"expiry-time" must be greater than "wait-time" for ' + f'Dynamic DNS service "{service}"') return None diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index 00015023c..557f0a9e9 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -93,6 +93,7 @@ def get_config(config=None): tmp = conf.get_config_dict(['policy']) # Merge policy dict into "regular" config dict bgp = dict_merge(tmp, bgp) + return bgp diff --git a/src/conf_mode/protocols_segment_routing.py b/src/conf_mode/protocols_segment_routing.py new file mode 100755 index 000000000..eb1653212 --- /dev/null +++ b/src/conf_mode/protocols_segment_routing.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit + +from vyos.config import Config +from vyos.template import render_to_string +from vyos import ConfigError +from vyos import frr +from vyos import airbag +airbag.enable() + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['protocols', 'segment-routing'] + sr = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) + + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + sr = conf.merge_defaults(sr, recursive=True) + + return sr + +def verify(static): + return None + +def generate(static): + if not static: + return None + + static['new_frr_config'] = render_to_string('frr/zebra.segment_routing.frr.j2', static) + return None + +def apply(static): + zebra_daemon = 'zebra' + + # Save original configuration prior to starting any commit actions + frr_cfg = frr.FRRConfig() + frr_cfg.load_configuration(zebra_daemon) + frr_cfg.modify_section(r'^segment-routing') + if 'new_frr_config' in static: + frr_cfg.add_before(frr.default_add_before, static['new_frr_config']) + frr_cfg.commit_configuration(zebra_daemon) + + return None + +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/op_mode/dhcp.py b/src/op_mode/dhcp.py index bd2c522ca..a9271ea79 100755 --- a/src/op_mode/dhcp.py +++ b/src/op_mode/dhcp.py @@ -102,11 +102,11 @@ def _get_raw_server_leases(family='inet', pool=None, sorted=None, state=[], orig if family == 'inet': data_lease['mac'] = lease['hwaddr'] - data_lease['start'] = lease['start_timestamp'] + data_lease['start'] = lease['start_timestamp'].timestamp() data_lease['hostname'] = lease['hostname'] if family == 'inet6': - data_lease['last_communication'] = lease['start_timestamp'] + data_lease['last_communication'] = lease['start_timestamp'].timestamp() data_lease['iaid_duid'] = _format_hex_string(lease['duid']) lease_types_long = {'0': 'non-temporary', '1': 'temporary', '2': 'prefix delegation'} data_lease['type'] = lease_types_long[lease['lease_type']] @@ -123,7 +123,7 @@ def _get_raw_server_leases(family='inet', pool=None, sorted=None, state=[], orig # Do not add old leases if data_lease['remaining'] != '' and data_lease['pool'] in pool and data_lease['state'] != 'free': - if not state or data_lease['state'] in state: + if not state or state == 'all' or data_lease['state'] in state: data.append(data_lease) # deduplicate @@ -151,7 +151,7 @@ def _get_formatted_server_leases(raw_data, family='inet'): ipaddr = lease.get('ip') hw_addr = lease.get('mac') state = lease.get('state') - start = lease.get('start').timestamp() + start = lease.get('start') start = _utc_to_local(start).strftime('%Y/%m/%d %H:%M:%S') end = lease.get('end') end = _utc_to_local(end).strftime('%Y/%m/%d %H:%M:%S') if end else '-' @@ -168,7 +168,7 @@ def _get_formatted_server_leases(raw_data, family='inet'): for lease in raw_data: ipaddr = lease.get('ip') state = lease.get('state') - start = lease.get('last_communication').timestamp() + start = lease.get('last_communication') start = _utc_to_local(start).strftime('%Y/%m/%d %H:%M:%S') end = lease.get('end') end = _utc_to_local(end).strftime('%Y/%m/%d %H:%M:%S') diff --git a/src/op_mode/image_installer.py b/src/op_mode/image_installer.py index 156adcad1..09501ef46 100755 --- a/src/op_mode/image_installer.py +++ b/src/op_mode/image_installer.py @@ -181,6 +181,7 @@ def create_partitions(target_disk: str, target_size: int, rootfs_size: int = available_size print(MSG_INFO_INSTALL_PARTITONING) + raid.clear() disk.disk_cleanup(target_disk) disk_details: disk.DiskDetails = disk.parttable_create(target_disk, rootfs_size) @@ -623,7 +624,8 @@ def install_image() -> None: print(MSG_WARN_IMAGE_NAME_WRONG) # ask for password - user_password: str = ask_input(MSG_INPUT_PASSWORD, default='vyos') + user_password: str = ask_input(MSG_INPUT_PASSWORD, default='vyos', + no_echo=True) # ask for default console console_type: str = ask_input(MSG_INPUT_CONSOLE_TYPE, diff --git a/src/validators/bgp-large-community-list b/src/validators/bgp-large-community-list index 80112dfdc..9ba5b27eb 100755 --- a/src/validators/bgp-large-community-list +++ b/src/validators/bgp-large-community-list @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2023 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 @@ -17,9 +17,8 @@ import re import sys -from vyos.template import is_ipv4 - pattern = '(.*):(.*):(.*)' +allowedChars = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '+', '*', '?', '^', '$', '(', ')', '[', ']', '{', '}', '|', '\\', ':', '-' } if __name__ == '__main__': if len(sys.argv) != 2: @@ -29,8 +28,7 @@ if __name__ == '__main__': if not len(value) == 3: sys.exit(1) - if not (re.match(pattern, sys.argv[1]) and - (is_ipv4(value[0]) or value[0].isdigit()) and (value[1].isdigit() or value[1] == '*')): + if not (re.match(pattern, sys.argv[1]) and set(sys.argv[1]).issubset(allowedChars)): sys.exit(1) sys.exit(0) |