summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/conf_mode/dhcp_server.py11
-rwxr-xr-xsrc/conf_mode/dhcpv6_server.py11
-rwxr-xr-xsrc/conf_mode/dns_dynamic.py24
-rwxr-xr-xsrc/conf_mode/load-balancing-haproxy.py20
-rwxr-xr-xsrc/conf_mode/protocols_bgp.py24
-rwxr-xr-xsrc/conf_mode/protocols_segment_routing.py74
-rwxr-xr-xsrc/conf_mode/system_frr.py4
-rwxr-xr-xsrc/helpers/simple-download.py20
-rwxr-xr-xsrc/migration-scripts/dns-dynamic/0-to-129
-rwxr-xr-xsrc/migration-scripts/dns-dynamic/2-to-345
-rwxr-xr-xsrc/migration-scripts/nat/6-to-78
-rwxr-xr-xsrc/op_mode/dhcp.py10
-rwxr-xr-xsrc/op_mode/image_installer.py58
-rwxr-xr-xsrc/system/on-dhcp-event.sh24
-rwxr-xr-xsrc/validators/bgp-large-community-list8
15 files changed, 272 insertions, 98 deletions
diff --git a/src/conf_mode/dhcp_server.py b/src/conf_mode/dhcp_server.py
index 66f7c8057..958e90014 100755
--- a/src/conf_mode/dhcp_server.py
+++ b/src/conf_mode/dhcp_server.py
@@ -21,7 +21,6 @@ from ipaddress import ip_network
from netaddr import IPAddress
from netaddr import IPRange
from sys import exit
-from time import sleep
from vyos.config import Config
from vyos.pki import wrap_certificate
@@ -29,7 +28,6 @@ from vyos.pki import wrap_private_key
from vyos.template import render
from vyos.utils.dict import dict_search
from vyos.utils.dict import dict_search_args
-from vyos.utils.file import chmod_775
from vyos.utils.file import write_file
from vyos.utils.process import call
from vyos.utils.process import run
@@ -362,15 +360,6 @@ def apply(dhcp):
call(f'systemctl {action} {service}.service')
- # op-mode needs ctrl socket permission change
- i = 0
- while not os.path.exists(ctrl_socket):
- if i > 15:
- break
- i += 1
- sleep(1)
- chmod_775(ctrl_socket)
-
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/dhcpv6_server.py b/src/conf_mode/dhcpv6_server.py
index 73a708ff5..b01f510e5 100755
--- a/src/conf_mode/dhcpv6_server.py
+++ b/src/conf_mode/dhcpv6_server.py
@@ -19,13 +19,11 @@ import os
from ipaddress import ip_address
from ipaddress import ip_network
from sys import exit
-from time import sleep
from vyos.config import Config
from vyos.template import render
from vyos.template import is_ipv6
from vyos.utils.process import call
-from vyos.utils.file import chmod_775
from vyos.utils.file import write_file
from vyos.utils.dict import dict_search
from vyos.utils.network import is_subnet_connected
@@ -197,15 +195,6 @@ def apply(dhcpv6):
call(f'systemctl restart {service_name}')
- # op-mode needs ctrl socket permission change
- i = 0
- while not os.path.exists(ctrl_socket):
- if i > 15:
- break
- i += 1
- sleep(1)
- chmod_775(ctrl_socket)
-
return None
if __name__ == '__main__':
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/load-balancing-haproxy.py b/src/conf_mode/load-balancing-haproxy.py
index ec4311bb5..333ebc66c 100755
--- a/src/conf_mode/load-balancing-haproxy.py
+++ b/src/conf_mode/load-balancing-haproxy.py
@@ -108,17 +108,19 @@ def generate(lb):
if 'ssl' in front_config:
if 'certificate' in front_config['ssl']:
- cert_name = front_config['ssl']['certificate']
- pki_cert = lb['pki']['certificate'][cert_name]
- cert_file_path = os.path.join(load_balancing_dir, f'{cert_name}.pem')
- cert_key_path = os.path.join(load_balancing_dir, f'{cert_name}.pem.key')
+ cert_names = front_config['ssl']['certificate']
- with open(cert_file_path, 'w') as f:
- f.write(wrap_certificate(pki_cert['certificate']))
+ for cert_name in cert_names:
+ pki_cert = lb['pki']['certificate'][cert_name]
+ cert_file_path = os.path.join(load_balancing_dir, f'{cert_name}.pem')
+ cert_key_path = os.path.join(load_balancing_dir, f'{cert_name}.pem.key')
- if 'private' in pki_cert and 'key' in pki_cert['private']:
- with open(cert_key_path, 'w') as f:
- f.write(wrap_private_key(pki_cert['private']['key']))
+ with open(cert_file_path, 'w') as f:
+ f.write(wrap_certificate(pki_cert['certificate']))
+
+ if 'private' in pki_cert and 'key' in pki_cert['private']:
+ with open(cert_key_path, 'w') as f:
+ f.write(wrap_private_key(pki_cert['private']['key']))
if 'ca_certificate' in front_config['ssl']:
ca_name = front_config['ssl']['ca_certificate']
diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py
index 00015023c..bf807fa5f 100755
--- a/src/conf_mode/protocols_bgp.py
+++ b/src/conf_mode/protocols_bgp.py
@@ -30,6 +30,7 @@ from vyos.template import render_to_string
from vyos.utils.dict import dict_search
from vyos.utils.network import get_interface_vrf
from vyos.utils.network import is_addr_assigned
+from vyos.utils.process import process_named_running
from vyos import ConfigError
from vyos import frr
from vyos import airbag
@@ -49,8 +50,13 @@ def get_config(config=None):
# eqivalent of the C foo ? 'a' : 'b' statement
base = vrf and ['vrf', 'name', vrf, 'protocols', 'bgp'] or base_path
- bgp = conf.get_config_dict(base, key_mangling=('-', '_'),
- get_first_key=True, no_tag_node_value_mangle=True)
+ bgp = conf.get_config_dict(
+ base,
+ key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True,
+ with_recursive_defaults=True,
+ )
bgp['dependent_vrfs'] = conf.get_config_dict(['vrf', 'name'],
key_mangling=('-', '_'),
@@ -93,6 +99,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
@@ -246,6 +253,19 @@ def verify(bgp):
if 'system_as' not in bgp:
raise ConfigError('BGP system-as number must be defined!')
+ # Verify BMP
+ if 'bmp' in bgp:
+ # check bmp flag "bgpd -d -F traditional --daemon -A 127.0.0.1 -M rpki -M bmp"
+ if not process_named_running('bgpd', 'bmp'):
+ raise ConfigError(
+ f'"bmp" flag is not found in bgpd. Configure "set system frr bmp" and restart bgp process'
+ )
+ # check bmp target
+ if 'target' in bgp['bmp']:
+ for target, target_config in bgp['bmp']['target'].items():
+ if 'address' not in target_config:
+ raise ConfigError(f'BMP target "{target}" address must be defined!')
+
# Verify vrf on interface and bgp section
if 'interface' in bgp:
for interface in bgp['interface']:
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/conf_mode/system_frr.py b/src/conf_mode/system_frr.py
index 6727b63c2..07f291000 100755
--- a/src/conf_mode/system_frr.py
+++ b/src/conf_mode/system_frr.py
@@ -40,7 +40,9 @@ def get_config(config=None):
conf = Config()
base = ['system', 'frr']
- frr_config = conf.get_config_dict(base, get_first_key=True)
+ frr_config = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True,
+ with_recursive_defaults=True)
return frr_config
diff --git a/src/helpers/simple-download.py b/src/helpers/simple-download.py
new file mode 100755
index 000000000..501af75f5
--- /dev/null
+++ b/src/helpers/simple-download.py
@@ -0,0 +1,20 @@
+#!/usr/bin/env python3
+
+import sys
+from argparse import ArgumentParser
+from vyos.remote import download
+
+parser = ArgumentParser()
+parser.add_argument('--local-file', help='local file', required=True)
+parser.add_argument('--remote-path', help='remote path', required=True)
+
+args = parser.parse_args()
+
+try:
+ download(args.local_file, args.remote_path,
+ check_space=True, raise_error=True)
+except Exception as e:
+ print(e)
+ sys.exit(1)
+
+sys.exit()
diff --git a/src/migration-scripts/dns-dynamic/0-to-1 b/src/migration-scripts/dns-dynamic/0-to-1
index d80e8d44a..4f6083eab 100755
--- a/src/migration-scripts/dns-dynamic/0-to-1
+++ b/src/migration-scripts/dns-dynamic/0-to-1
@@ -81,20 +81,33 @@ for address in config.list_nodes(new_base_path):
config.rename(new_base_path + [address, 'service', svc_cfg, 'login'], 'username')
# Apply global 'ipv6-enable' to per <config> 'ip-version: ipv6'
if config.exists(new_base_path + [address, 'ipv6-enable']):
- config.set(new_base_path + [address, 'service', svc_cfg, 'ip-version'],
- value='ipv6', replace=False)
+ config.set(new_base_path + [address, 'service', svc_cfg, 'ip-version'], 'ipv6')
config.delete(new_base_path + [address, 'ipv6-enable'])
# Apply service protocol mapping upfront, they are not 'auto-detected' anymore
if svc_cfg in service_protocol_mapping:
config.set(new_base_path + [address, 'service', svc_cfg, 'protocol'],
- value=service_protocol_mapping.get(svc_cfg), replace=False)
+ service_protocol_mapping.get(svc_cfg))
- # Migrate "service dns dynamic interface <interface> use-web"
- # to "service dns dynamic address <address> web-options"
- # Also, rename <address> to 'web' literal for backward compatibility
+ # If use-web is set, then:
+ # Move "service dns dynamic address <address> <service|rfc2136> <service> ..."
+ # to "service dns dynamic address web <service|rfc2136> <service>-<address> ..."
+ # Move "service dns dynamic address web use-web ..."
+ # to "service dns dynamic address web web-options ..."
+ # Note: The config is named <service>-<address> to avoid name conflict with old entries
if config.exists(new_base_path + [address, 'use-web']):
- config.rename(new_base_path + [address], 'web')
- config.rename(new_base_path + ['web', 'use-web'], 'web-options')
+ for svc_type in ['rfc2136', 'service']:
+ if config.exists(new_base_path + [address, svc_type]):
+ config.set(new_base_path + ['web', svc_type])
+ config.set_tag(new_base_path + ['web', svc_type])
+ for svc_cfg in config.list_nodes(new_base_path + [address, svc_type]):
+ config.copy(new_base_path + [address, svc_type, svc_cfg],
+ new_base_path + ['web', svc_type, f'{svc_cfg}-{address}'])
+
+ # Multiple web-options were not supported, so copy only the first one
+ if not config.exists(new_base_path + ['web', 'web-options']):
+ config.copy(new_base_path + [address, 'use-web'], new_base_path + ['web', 'web-options'])
+
+ config.delete(new_base_path + [address])
try:
with open(file_name, 'w') as f:
diff --git a/src/migration-scripts/dns-dynamic/2-to-3 b/src/migration-scripts/dns-dynamic/2-to-3
index 187c2a895..e5910f7b4 100755
--- a/src/migration-scripts/dns-dynamic/2-to-3
+++ b/src/migration-scripts/dns-dynamic/2-to-3
@@ -21,10 +21,27 @@
# to "service dns dynamic name <service> address <interface> protocol 'nsupdate'"
# - migrate "service dns dynamic address <interface> service <service> ..."
# to "service dns dynamic name <service> address <interface> ..."
+# - normalize the all service names to conform with name constraints
import sys
+import re
+from unicodedata import normalize
from vyos.configtree import ConfigTree
+def normalize_name(name):
+ """Normalize service names to conform with name constraints.
+
+ This is necessary as part of migration because there were no constraints in
+ the old name format.
+ """
+ # Normalize unicode characters to ASCII (NFKD)
+ # Replace all separators with hypens, strip leading and trailing hyphens
+ name = normalize('NFKD', name).encode('ascii', 'ignore').decode()
+ name = re.sub(r'(\s|\W)+', '-', name).strip('-')
+
+ return name
+
+
if len(sys.argv) < 2:
print("Must specify file name!")
sys.exit(1)
@@ -64,22 +81,36 @@ for address in config.list_nodes(address_path):
for svc_type in ['service', 'rfc2136']:
if config.exists(address_path_tag + [svc_type]):
- # Move RFC2136 as service configuration, rename to avoid name conflict and set protocol to 'nsupdate'
+ # Set protocol to 'nsupdate' for RFC2136 configuration
if svc_type == 'rfc2136':
- for rfc_cfg_old in config.list_nodes(address_path_tag + ['rfc2136']):
- rfc_cfg_new = f'{rfc_cfg_old}-rfc2136'
- config.rename(address_path_tag + ['rfc2136', rfc_cfg_old], rfc_cfg_new)
- config.set(address_path_tag + ['rfc2136', rfc_cfg_new, 'protocol'], 'nsupdate')
+ for rfc_cfg in config.list_nodes(address_path_tag + ['rfc2136']):
+ config.set(address_path_tag + ['rfc2136', rfc_cfg, 'protocol'], 'nsupdate')
# Add address as config value in each service before moving the service path
- # And then copy the services from 'address <interface> service <service>' to 'name <service>'
+ # And then copy the services from 'address <interface> service <service>'
+ # to 'name (service|rfc2136)-<service>-<address>'
+ # Note: The new service is named (service|rfc2136)-<service>-<address>
+ # to avoid name conflict with old entries
for svc_cfg in config.list_nodes(address_path_tag + [svc_type]):
config.set(address_path_tag + [svc_type, svc_cfg, 'address'], address)
- config.copy(address_path_tag + [svc_type, svc_cfg], name_path + [svc_cfg])
+ config.copy(address_path_tag + [svc_type, svc_cfg],
+ name_path + ['-'.join([svc_type, svc_cfg, address])])
# Finally cleanup the old address path
config.delete(address_path)
+# Normalize the all service names to conform with name constraints
+index = 1
+for name in config.list_nodes(name_path):
+ new_name = normalize_name(name)
+ if new_name != name:
+ # Append index if there is still a name conflicts after normalization
+ # For example, "foo-?(" and "foo-!)" both normalize to "foo-"
+ if config.exists(name_path + [new_name]):
+ new_name = f'{new_name}-{index}'
+ index += 1
+ config.rename(name_path + [name], new_name)
+
try:
with open(file_name, 'w') as f:
f.write(config.to_string())
diff --git a/src/migration-scripts/nat/6-to-7 b/src/migration-scripts/nat/6-to-7
index b5f6328ef..a2e735394 100755
--- a/src/migration-scripts/nat/6-to-7
+++ b/src/migration-scripts/nat/6-to-7
@@ -21,6 +21,7 @@
# to
# 'set nat [source|destination] rule X [inbound-interface|outbound interface] name <iface>'
# 'set nat [source|destination] rule X [inbound-interface|outbound interface] group <iface_group>'
+# Also remove command if interface == any
from sys import argv,exit
from vyos.configtree import ConfigTree
@@ -56,8 +57,11 @@ for direction in ['source', 'destination']:
if config.exists(base + [iface]):
if config.exists(base + [iface, 'interface-name']):
tmp = config.return_value(base + [iface, 'interface-name'])
- config.delete(base + [iface, 'interface-name'])
- config.set(base + [iface, 'name'], value=tmp)
+ if tmp != 'any':
+ config.delete(base + [iface, 'interface-name'])
+ config.set(base + [iface, 'name'], value=tmp)
+ else:
+ config.delete(base + [iface])
try:
with open(file_name, 'w') as f:
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 b3e6e518c..6a8797aec 100755
--- a/src/op_mode/image_installer.py
+++ b/src/op_mode/image_installer.py
@@ -22,6 +22,7 @@ from pathlib import Path
from shutil import copy, chown, rmtree, copytree
from glob import glob
from sys import exit
+from os import environ
from time import sleep
from typing import Union
from urllib.parse import urlparse
@@ -83,6 +84,8 @@ DIR_KERNEL_SRC: str = '/boot/'
FILE_ROOTFS_SRC: str = '/usr/lib/live/mount/medium/live/filesystem.squashfs'
ISO_DOWNLOAD_PATH: str = '/tmp/vyos_installation.iso'
+external_download_script = '/usr/libexec/vyos/simple-download.py'
+
# default boot variables
DEFAULT_BOOT_VARS: dict[str, str] = {
'timeout': '5',
@@ -179,6 +182,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)
@@ -459,8 +463,23 @@ def validate_signature(file_path: str, sign_type: str) -> None:
else:
print('Signature is valid')
-
-def image_fetch(image_path: str, no_prompt: bool = False) -> Path:
+def download_file(local_file: str, remote_path: str, vrf: str,
+ username: str, password: str,
+ progressbar: bool = False, check_space: bool = False):
+ environ['REMOTE_USERNAME'] = username
+ environ['REMOTE_PASSWORD'] = password
+ if vrf is None:
+ download(local_file, remote_path, progressbar=progressbar,
+ check_space=check_space, raise_error=True)
+ else:
+ vrf_cmd = f'REMOTE_USERNAME={username} REMOTE_PASSWORD={password} \
+ ip vrf exec {vrf} {external_download_script} \
+ --local-file {local_file} --remote-path {remote_path}'
+ cmd(vrf_cmd)
+
+def image_fetch(image_path: str, vrf: str = None,
+ username: str = '', password: str = '',
+ no_prompt: bool = False) -> Path:
"""Fetch an ISO image
Args:
@@ -473,14 +492,17 @@ def image_fetch(image_path: str, no_prompt: bool = False) -> Path:
# check a type of path
if urlparse(image_path).scheme:
# download an image
- download(ISO_DOWNLOAD_PATH, image_path, True, True,
- raise_error=True)
+ download_file(ISO_DOWNLOAD_PATH, image_path, vrf,
+ username, password,
+ progressbar=True, check_space=True)
+
# download a signature
sign_file = (False, '')
for sign_type in ['minisig', 'asc']:
try:
- download(f'{ISO_DOWNLOAD_PATH}.{sign_type}',
- f'{image_path}.{sign_type}', raise_error=True)
+ download_file(f'{ISO_DOWNLOAD_PATH}.{sign_type}',
+ f'{image_path}.{sign_type}', vrf,
+ username, password)
sign_file = (True, sign_type)
break
except Exception:
@@ -501,8 +523,8 @@ def image_fetch(image_path: str, no_prompt: bool = False) -> Path:
return local_path
else:
raise FileNotFoundError
- except Exception:
- print(f'The image cannot be fetched from: {image_path}')
+ except Exception as e:
+ print(f'The image cannot be fetched from: {image_path} {e}')
exit(1)
@@ -611,7 +633,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,
@@ -730,7 +753,8 @@ def install_image() -> None:
@compat.grub_cfg_update
-def add_image(image_path: str, no_prompt: bool = False) -> None:
+def add_image(image_path: str, vrf: str = None, username: str = '',
+ password: str = '', no_prompt: bool = False) -> None:
"""Add a new image
Args:
@@ -740,7 +764,7 @@ def add_image(image_path: str, no_prompt: bool = False) -> None:
exit(MSG_ERR_LIVE)
# fetch an image
- iso_path: Path = image_fetch(image_path, no_prompt)
+ iso_path: Path = image_fetch(image_path, vrf, username, password, no_prompt)
try:
# mount an ISO
Path(DIR_ISO_MOUNT).mkdir(mode=0o755, parents=True)
@@ -840,10 +864,15 @@ def parse_arguments() -> Namespace:
choices=['install', 'add'],
required=True,
help='action to perform with an image')
+ parser.add_argument('--vrf',
+ help='vrf name for image download')
parser.add_argument('--no-prompt', action='store_true',
help='perform action non-interactively')
- parser.add_argument(
- '--image-path',
+ parser.add_argument('--username', default='',
+ help='username for image download')
+ parser.add_argument('--password', default='',
+ help='password for image download')
+ parser.add_argument('--image-path',
help='a path (HTTP or local file) to an image that needs to be installed'
)
# parser.add_argument('--image_new_name', help='a new name for image')
@@ -861,7 +890,8 @@ if __name__ == '__main__':
if args.action == 'install':
install_image()
if args.action == 'add':
- add_image(args.image_path, args.no_prompt)
+ add_image(args.image_path, args.vrf,
+ args.username, args.password, args.no_prompt)
exit()
diff --git a/src/system/on-dhcp-event.sh b/src/system/on-dhcp-event.sh
index 7b25bf338..03574bdc3 100755
--- a/src/system/on-dhcp-event.sh
+++ b/src/system/on-dhcp-event.sh
@@ -8,7 +8,7 @@
# Thanks to forum user "ruudboon" for multiple domain fix
# Thanks to forum user "chibby85" for expire patch and static-mapping
-if [ $# -lt 5 ]; then
+if [ $# -lt 1 ]; then
echo Invalid args
logger -s -t on-dhcp-event "Invalid args \"$@\""
exit 1
@@ -18,36 +18,30 @@ action=$1
client_name=$LEASE4_HOSTNAME
client_ip=$LEASE4_ADDRESS
client_mac=$LEASE4_HWADDR
-domain=$(echo "$client_name" | cut -d"." -f2-)
hostsd_client="/usr/bin/vyos-hostsd-client"
case "$action" in
- leases4_renew|lease4_recover) # add mapping for new lease
+ lease4_renew|lease4_recover) # add mapping for new/recovered lease address
if [ -z "$client_name" ]; then
logger -s -t on-dhcp-event "Client name was empty, using MAC \"$client_mac\" instead"
- client_name=$(echo "client-"$client_mac | tr : -)
+ client_name=$(echo "host-$client_mac" | tr : -)
fi
- if [ -z "$domain" ]; then
- client_fqdn_name=$client_name
- client_search_expr=$client_name
- else
- client_fqdn_name=$client_name.$domain
- client_search_expr="$client_name\\.$domain"
- fi
- $hostsd_client --add-hosts "$client_fqdn_name,$client_ip" --tag "dhcp-server-$client_ip" --apply
+ $hostsd_client --add-hosts "$client_name,$client_ip" --tag "dhcp-server-$client_ip" --apply
exit 0
;;
- lease4_release|lease4_expire) # delete mapping for released address)
+ lease4_release|lease4_expire|lease4_decline) # delete mapping for released/declined address
$hostsd_client --delete-hosts --tag "dhcp-server-$client_ip" --apply
exit 0
;;
+ leases4_committed) # nothing to do
+ exit 0
+ ;;
+
*)
logger -s -t on-dhcp-event "Invalid command \"$1\""
exit 1
;;
esac
-
-exit 0
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)