summaryrefslogtreecommitdiff
path: root/src/conf_mode
diff options
context:
space:
mode:
Diffstat (limited to 'src/conf_mode')
-rwxr-xr-xsrc/conf_mode/container.py7
-rwxr-xr-xsrc/conf_mode/firewall.py10
-rwxr-xr-xsrc/conf_mode/interfaces_bridge.py2
-rwxr-xr-xsrc/conf_mode/interfaces_pseudo-ethernet.py2
-rwxr-xr-xsrc/conf_mode/interfaces_virtual-ethernet.py2
-rwxr-xr-xsrc/conf_mode/interfaces_vti.py2
-rwxr-xr-xsrc/conf_mode/interfaces_wireguard.py2
-rwxr-xr-xsrc/conf_mode/interfaces_wwan.py2
-rw-r--r--src/conf_mode/load-balancing_haproxy.py37
-rwxr-xr-xsrc/conf_mode/nat66.py4
-rwxr-xr-xsrc/conf_mode/pki.py94
-rwxr-xr-xsrc/conf_mode/policy.py16
-rwxr-xr-xsrc/conf_mode/protocols_bgp.py19
-rwxr-xr-xsrc/conf_mode/service_https.py14
-rwxr-xr-xsrc/conf_mode/system_option.py49
-rwxr-xr-xsrc/conf_mode/vpn_ipsec.py2
-rwxr-xr-xsrc/conf_mode/vpn_openconnect.py2
17 files changed, 215 insertions, 51 deletions
diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py
index 18d660a4e..94882fc14 100755
--- a/src/conf_mode/container.py
+++ b/src/conf_mode/container.py
@@ -324,6 +324,11 @@ def generate_run_arguments(name, container_config):
cap = cap.upper().replace('-', '_')
capabilities += f' --cap-add={cap}'
+ # Grant root capabilities to the container
+ privileged = ''
+ if 'privileged' in container_config:
+ privileged = '--privileged'
+
# Add a host device to the container /dev/x:/dev/x
device = ''
if 'device' in container_config:
@@ -402,7 +407,7 @@ def generate_run_arguments(name, container_config):
for ns in container_config['name_server']:
name_server += f'--dns {ns}'
- container_base_cmd = f'--detach --interactive --tty --replace {capabilities} --cpus {cpu_quota} {sysctl_opt} ' \
+ container_base_cmd = f'--detach --interactive --tty --replace {capabilities} {privileged} --cpus {cpu_quota} {sysctl_opt} ' \
f'--memory {memory}m --shm-size {shared_memory}m --memory-swap 0 --restart {restart} ' \
f'--name {name} {hostname} {device} {port} {name_server} {volume} {tmpfs} {env_opt} {label} {uid} {host_pid}'
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
index 14438dae7..348eaeba3 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -439,6 +439,16 @@ def verify(firewall):
for ifname in interfaces:
verify_hardware_offload(ifname)
+ if 'offload' in firewall.get('global_options', {}).get('state_policy', {}):
+ offload_path = firewall['global_options']['state_policy']['offload']
+ if 'offload_target' not in offload_path:
+ raise ConfigError('offload-target must be specified')
+
+ offload_target = offload_path['offload_target']
+
+ if not dict_search_args(firewall, 'flowtable', offload_target):
+ raise ConfigError(f'Invalid offload-target. Flowtable "{offload_target}" does not exist on the system')
+
if 'group' in firewall:
for group_type in nested_group_types:
if group_type in firewall['group']:
diff --git a/src/conf_mode/interfaces_bridge.py b/src/conf_mode/interfaces_bridge.py
index aff93af2a..95dcc543e 100755
--- a/src/conf_mode/interfaces_bridge.py
+++ b/src/conf_mode/interfaces_bridge.py
@@ -25,6 +25,7 @@ from vyos.configdict import has_vlan_subinterface_configured
from vyos.configverify import verify_dhcpv6
from vyos.configverify import verify_mirror_redirect
from vyos.configverify import verify_vrf
+from vyos.configverify import verify_mtu_ipv6
from vyos.ifconfig import BridgeIf
from vyos.configdict import has_address_configured
from vyos.configdict import has_vrf_configured
@@ -136,6 +137,7 @@ def verify(bridge):
verify_dhcpv6(bridge)
verify_vrf(bridge)
+ verify_mtu_ipv6(bridge)
verify_mirror_redirect(bridge)
ifname = bridge['ifname']
diff --git a/src/conf_mode/interfaces_pseudo-ethernet.py b/src/conf_mode/interfaces_pseudo-ethernet.py
index 446beffd3..b066fd542 100755
--- a/src/conf_mode/interfaces_pseudo-ethernet.py
+++ b/src/conf_mode/interfaces_pseudo-ethernet.py
@@ -27,6 +27,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_mtu_ipv6
from vyos.configverify import verify_mirror_redirect
from vyos.ifconfig import MACVLANIf
from vyos.utils.network import interface_exists
@@ -71,6 +72,7 @@ def verify(peth):
verify_vrf(peth)
verify_address(peth)
verify_mtu_parent(peth, peth['parent'])
+ verify_mtu_ipv6(peth)
verify_mirror_redirect(peth)
# use common function to verify VLAN configuration
verify_vlan_config(peth)
diff --git a/src/conf_mode/interfaces_virtual-ethernet.py b/src/conf_mode/interfaces_virtual-ethernet.py
index cb6104f59..59ce474fc 100755
--- a/src/conf_mode/interfaces_virtual-ethernet.py
+++ b/src/conf_mode/interfaces_virtual-ethernet.py
@@ -23,6 +23,7 @@ from vyos.configdict import get_interface_dict
from vyos.configverify import verify_address
from vyos.configverify import verify_bridge_delete
from vyos.configverify import verify_vrf
+from vyos.configverify import verify_mtu_ipv6
from vyos.ifconfig import VethIf
from vyos.utils.network import interface_exists
airbag.enable()
@@ -62,6 +63,7 @@ def verify(veth):
return None
verify_vrf(veth)
+ verify_mtu_ipv6(veth)
verify_address(veth)
if 'peer_name' not in veth:
diff --git a/src/conf_mode/interfaces_vti.py b/src/conf_mode/interfaces_vti.py
index 20629c6c1..915bde066 100755
--- a/src/conf_mode/interfaces_vti.py
+++ b/src/conf_mode/interfaces_vti.py
@@ -20,6 +20,7 @@ from vyos.config import Config
from vyos.configdict import get_interface_dict
from vyos.configverify import verify_mirror_redirect
from vyos.configverify import verify_vrf
+from vyos.configverify import verify_mtu_ipv6
from vyos.ifconfig import VTIIf
from vyos import ConfigError
from vyos import airbag
@@ -40,6 +41,7 @@ def get_config(config=None):
def verify(vti):
verify_vrf(vti)
+ verify_mtu_ipv6(vti)
verify_mirror_redirect(vti)
return None
diff --git a/src/conf_mode/interfaces_wireguard.py b/src/conf_mode/interfaces_wireguard.py
index f2f29da01..770667df1 100755
--- a/src/conf_mode/interfaces_wireguard.py
+++ b/src/conf_mode/interfaces_wireguard.py
@@ -99,7 +99,7 @@ def verify(wireguard):
if 'port' in wireguard and 'port_changed' in wireguard:
listen_port = int(wireguard['port'])
- if check_port_availability('0.0.0.0', listen_port, 'udp') is not True:
+ if check_port_availability(None, listen_port, protocol='udp') is not True:
raise ConfigError(f'UDP port {listen_port} is busy or unavailable and '
'cannot be used for the interface!')
diff --git a/src/conf_mode/interfaces_wwan.py b/src/conf_mode/interfaces_wwan.py
index 230eb14d6..ddbebfb4a 100755
--- a/src/conf_mode/interfaces_wwan.py
+++ b/src/conf_mode/interfaces_wwan.py
@@ -26,6 +26,7 @@ from vyos.configverify import verify_authentication
from vyos.configverify import verify_interface_exists
from vyos.configverify import verify_mirror_redirect
from vyos.configverify import verify_vrf
+from vyos.configverify import verify_mtu_ipv6
from vyos.ifconfig import WWANIf
from vyos.utils.dict import dict_search
from vyos.utils.process import cmd
@@ -98,6 +99,7 @@ def verify(wwan):
verify_interface_exists(wwan, ifname)
verify_authentication(wwan)
verify_vrf(wwan)
+ verify_mtu_ipv6(wwan)
verify_mirror_redirect(wwan)
return None
diff --git a/src/conf_mode/load-balancing_haproxy.py b/src/conf_mode/load-balancing_haproxy.py
index 5fd1beec9..504a90596 100644
--- a/src/conf_mode/load-balancing_haproxy.py
+++ b/src/conf_mode/load-balancing_haproxy.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2023-2024 VyOS maintainers and contributors
+# Copyright (C) 2023-2025 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,6 +19,7 @@ import os
from sys import exit
from shutil import rmtree
+from vyos.defaults import systemd_services
from vyos.config import Config
from vyos.configverify import verify_pki_certificate
from vyos.configverify import verify_pki_ca_certificate
@@ -39,7 +40,6 @@ airbag.enable()
load_balancing_dir = '/run/haproxy'
load_balancing_conf_file = f'{load_balancing_dir}/haproxy.cfg'
-systemd_service = 'haproxy.service'
systemd_override = '/run/systemd/system/haproxy.service.d/10-override.conf'
def get_config(config=None):
@@ -65,18 +65,18 @@ def verify(lb):
return None
if 'backend' not in lb or 'service' not in lb:
- raise ConfigError(f'"service" and "backend" must be configured!')
+ raise ConfigError('Both "service" and "backend" must be configured!')
for front, front_config in lb['service'].items():
if 'port' not in front_config:
raise ConfigError(f'"{front} service port" must be configured!')
# Check if bind address:port are used by another service
- tmp_address = front_config.get('address', '0.0.0.0')
+ tmp_address = front_config.get('address', None)
tmp_port = front_config['port']
if check_port_availability(tmp_address, int(tmp_port), 'tcp') is not True and \
not is_listen_port_bind_service(int(tmp_port), 'haproxy'):
- raise ConfigError(f'"TCP" port "{tmp_port}" is used by another service')
+ raise ConfigError(f'TCP port "{tmp_port}" is used by another service')
if 'http_compression' in front_config:
if front_config['mode'] != 'http':
@@ -85,16 +85,19 @@ def verify(lb):
raise ConfigError(f'service {front} must have at least one mime-type configured to use'
f'http_compression!')
+ for cert in dict_search('ssl.certificate', front_config) or []:
+ verify_pki_certificate(lb, cert)
+
for back, back_config in lb['backend'].items():
if 'http_check' in back_config:
http_check = back_config['http_check']
if 'expect' in http_check and 'status' in http_check['expect'] and 'string' in http_check['expect']:
- raise ConfigError(f'"expect status" and "expect string" can not be configured together!')
+ raise ConfigError('"expect status" and "expect string" can not be configured together!')
if 'health_check' in back_config:
if back_config['mode'] != 'tcp':
raise ConfigError(f'backend "{back}" can only be configured with {back_config["health_check"]} ' +
- f'health-check whilst in TCP mode!')
+ 'health-check whilst in TCP mode!')
if 'http_check' in back_config:
raise ConfigError(f'backend "{back}" cannot be configured with both http-check and health-check!')
@@ -112,20 +115,15 @@ def verify(lb):
if {'no_verify', 'ca_certificate'} <= set(back_config['ssl']):
raise ConfigError(f'backend {back} cannot have both ssl options no-verify and ca-certificate set!')
+ tmp = dict_search('ssl.ca_certificate', back_config)
+ if tmp: verify_pki_ca_certificate(lb, tmp)
+
# Check if http-response-headers are configured in any frontend/backend where mode != http
for group in ['service', 'backend']:
for config_name, config in lb[group].items():
if 'http_response_headers' in config and config['mode'] != 'http':
raise ConfigError(f'{group} {config_name} must be set to http mode to use http_response_headers!')
- for front, front_config in lb['service'].items():
- for cert in dict_search('ssl.certificate', front_config) or []:
- verify_pki_certificate(lb, cert)
-
- for back, back_config in lb['backend'].items():
- tmp = dict_search('ssl.ca_certificate', back_config)
- if tmp: verify_pki_ca_certificate(lb, tmp)
-
def generate(lb):
if not lb:
@@ -193,12 +191,11 @@ def generate(lb):
return None
def apply(lb):
+ action = 'stop'
+ if lb:
+ action = 'reload-or-restart'
call('systemctl daemon-reload')
- if not lb:
- call(f'systemctl stop {systemd_service}')
- else:
- call(f'systemctl reload-or-restart {systemd_service}')
-
+ call(f'systemctl {action} {systemd_services["haproxy"]}')
return None
diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py
index 95dfae3a5..c65950c9e 100755
--- a/src/conf_mode/nat66.py
+++ b/src/conf_mode/nat66.py
@@ -92,6 +92,10 @@ def verify(nat):
if prefix != None:
if not is_ipv6(prefix):
raise ConfigError(f'{err_msg} source-prefix not specified')
+
+ if 'destination' in config and 'group' in config['destination']:
+ if len({'address_group', 'network_group', 'domain_group'} & set(config['destination']['group'])) > 1:
+ raise ConfigError('Only one address-group, network-group or domain-group can be specified')
if dict_search('destination.rule', nat):
for rule, config in dict_search('destination.rule', nat).items():
diff --git a/src/conf_mode/pki.py b/src/conf_mode/pki.py
index 724f97555..869518dd9 100755
--- a/src/conf_mode/pki.py
+++ b/src/conf_mode/pki.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021-2024 VyOS maintainers and contributors
+# Copyright (C) 2021-2025 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,6 +19,7 @@ import os
from sys import argv
from sys import exit
+from vyos.base import Message
from vyos.config import Config
from vyos.config import config_dict_merge
from vyos.configdep import set_dependents
@@ -27,6 +28,8 @@ from vyos.configdict import node_changed
from vyos.configdiff import Diff
from vyos.configdiff import get_config_diff
from vyos.defaults import directories
+from vyos.defaults import internal_ports
+from vyos.defaults import systemd_services
from vyos.pki import encode_certificate
from vyos.pki import is_ca_certificate
from vyos.pki import load_certificate
@@ -42,9 +45,11 @@ from vyos.utils.dict import dict_search
from vyos.utils.dict import dict_search_args
from vyos.utils.dict import dict_search_recursive
from vyos.utils.file import read_file
+from vyos.utils.network import check_port_availability
from vyos.utils.process import call
from vyos.utils.process import cmd
from vyos.utils.process import is_systemd_service_active
+from vyos.utils.process import is_systemd_service_running
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -128,8 +133,20 @@ def certbot_request(name: str, config: dict, dry_run: bool=True):
f'--standalone --agree-tos --no-eff-email --expand --server {config["url"]} '\
f'--email {config["email"]} --key-type rsa --rsa-key-size {config["rsa_key_size"]} '\
f'{domains}'
+
+ listen_address = None
if 'listen_address' in config:
- tmp += f' --http-01-address {config["listen_address"]}'
+ listen_address = config['listen_address']
+
+ # When ACME is used behind a reverse proxy, we always bind to localhost
+ # whatever the CLI listen-address is configured for.
+ if ('haproxy' in dict_search('used_by', config) and
+ is_systemd_service_running(systemd_services['haproxy']) and
+ not check_port_availability(listen_address, 80)):
+ tmp += f' --http-01-address 127.0.0.1 --http-01-port {internal_ports["certbot_haproxy"]}'
+ elif listen_address:
+ tmp += f' --http-01-address {listen_address}'
+
# verify() does not need to actually request a cert but only test for plausability
if dry_run:
tmp += ' --dry-run'
@@ -150,14 +167,18 @@ def get_config(config=None):
if len(argv) > 1 and argv[1] == 'certbot_renew':
pki['certbot_renew'] = {}
- changed_keys = ['ca', 'certificate', 'dh', 'key-pair', 'openssh', 'openvpn']
+ # Walk through the list of sync_translate mapping and build a list
+ # which is later used to check if the node was changed in the CLI config
+ changed_keys = []
+ for value in sync_translate.values():
+ if value not in changed_keys:
+ changed_keys.append(value)
+ # Check for changes to said given keys in the CLI config
for key in changed_keys:
tmp = node_changed(conf, base + [key], recursive=True, expand_nodes=Diff.DELETE | Diff.ADD)
-
if 'changed' not in pki:
pki.update({'changed':{}})
-
pki['changed'].update({key.replace('-', '_') : tmp})
# We only merge on the defaults of there is a configuration at all
@@ -219,8 +240,8 @@ def get_config(config=None):
continue
path = search['path']
- path_str = ' '.join(path + found_path)
- #print(f'PKI: Updating config: {path_str} {item_name}')
+ path_str = ' '.join(path + found_path).replace('_','-')
+ Message(f'Updating configuration: "{path_str} {item_name}"')
if path[0] == 'interfaces':
ifname = found_path[0]
@@ -230,6 +251,24 @@ def get_config(config=None):
if not D.node_changed_presence(path):
set_dependents(path[1], conf)
+ # Check PKI certificates if they are auto-generated by ACME. If they are,
+ # traverse the current configuration and determine the service where the
+ # certificate is used by.
+ # Required to check if we might need to run certbot behing a reverse proxy.
+ if 'certificate' in pki:
+ for name, cert_config in pki['certificate'].items():
+ if 'acme' not in cert_config:
+ continue
+ if not dict_search('system.load_balancing.haproxy', pki):
+ continue
+ used_by = []
+ for cert_list, _ in dict_search_recursive(
+ pki['system']['load_balancing']['haproxy'], 'certificate'):
+ if name in cert_list:
+ used_by.append('haproxy')
+ if used_by:
+ pki['certificate'][name]['acme'].update({'used_by': used_by})
+
return pki
def is_valid_certificate(raw_data):
@@ -321,6 +360,15 @@ def verify(pki):
raise ConfigError(f'An email address is required to request '\
f'certificate for "{name}" via ACME!')
+ listen_address = None
+ if 'listen_address' in cert_conf['acme']:
+ listen_address = cert_conf['acme']['listen_address']
+
+ if 'used_by' not in cert_conf['acme']:
+ if not check_port_availability(listen_address, 80):
+ raise ConfigError('Port 80 is already in use and not available '\
+ f'to provide ACME challenge for "{name}"!')
+
if 'certbot_renew' not in pki:
# Only run the ACME command if something on this entity changed,
# as this is time intensive
@@ -374,27 +422,35 @@ def verify(pki):
for search in sync_search:
for key in search['keys']:
changed_key = sync_translate[key]
-
if changed_key not in pki['changed']:
continue
-
for item_name in pki['changed'][changed_key]:
node_present = False
if changed_key == 'openvpn':
node_present = dict_search_args(pki, 'openvpn', 'shared_secret', item_name)
else:
node_present = dict_search_args(pki, changed_key, item_name)
+ # If the node is still present, we can skip the check
+ # as we are not deleting it
+ if node_present:
+ continue
- if not node_present:
- search_dict = dict_search_args(pki['system'], *search['path'])
-
- if not search_dict:
- continue
+ search_dict = dict_search_args(pki['system'], *search['path'])
+ if not search_dict:
+ continue
- for found_name, found_path in dict_search_recursive(search_dict, key):
- if found_name == item_name:
- path_str = " ".join(search['path'] + found_path)
- raise ConfigError(f'PKI object "{item_name}" still in use by "{path_str}"')
+ for found_name, found_path in dict_search_recursive(search_dict, key):
+ # Check if the name matches either by string compare, or beeing
+ # part of a list
+ if ((isinstance(found_name, str) and found_name == item_name) or
+ (isinstance(found_name, list) and item_name in found_name)):
+ # We do not support _ in CLI paths - this is only a convenience
+ # as we mangle all - to _, now it's time to reverse this!
+ path_str = ' '.join(search['path'] + found_path).replace('_','-')
+ object = changed_key.replace('_','-')
+ tmp = f'Embedded PKI {object} with name "{item_name}" is still '\
+ f'in use by CLI path "{path_str}"'
+ raise ConfigError(tmp)
return None
@@ -490,7 +546,7 @@ def generate(pki):
if not ca_cert_present:
tmp = dict_search_args(pki, 'ca', f'{autochain_prefix}{cert}', 'certificate')
if not bool(tmp) or tmp != cert_chain_base64:
- print(f'Adding/replacing automatically imported CA certificate for "{cert}" ...')
+ Message(f'Add/replace automatically imported CA certificate for "{cert}"...')
add_cli_node(['pki', 'ca', f'{autochain_prefix}{cert}', 'certificate'], value=cert_chain_base64)
return None
diff --git a/src/conf_mode/policy.py b/src/conf_mode/policy.py
index a90e33e81..ec9005890 100755
--- a/src/conf_mode/policy.py
+++ b/src/conf_mode/policy.py
@@ -14,6 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+import re
from sys import exit
from vyos.config import Config
@@ -24,9 +25,20 @@ from vyos.frrender import get_frrender_dict
from vyos.utils.dict import dict_search
from vyos.utils.process import is_systemd_service_running
from vyos import ConfigError
+from vyos.base import Warning
from vyos import airbag
airbag.enable()
+# Sanity checks for large-community-list regex:
+# * Require complete 3-tuples, no blank members. Catch missed & doubled colons.
+# * Permit appropriate community separators (whitespace, underscore)
+# * Permit common regex between tuples while requiring at least one separator
+# (eg, "1:1:1_.*_4:4:4", matching "1:1:1 4:4:4" and "1:1:1 2:2:2 4:4:4",
+# but not "1:1:13 24:4:4")
+# Best practice: stick with basic patterns, mind your wildcards and whitespace.
+# Regex that doesn't match this pattern will be allowed with a warning.
+large_community_regex_pattern = r'([^: _]+):([^: _]+):([^: _]+)([ _]([^:]+):([^: _]+):([^: _]+))*'
+
def community_action_compatibility(actions: dict) -> bool:
"""
Check compatibility of values in community and large community sections
@@ -147,6 +159,10 @@ def verify(config_dict):
if 'regex' not in rule_config:
raise ConfigError(f'A regex {mandatory_error}')
+ if policy_type == 'large_community_list':
+ if not re.fullmatch(large_community_regex_pattern, rule_config['regex']):
+ Warning(f'"policy large-community-list {instance} rule {rule} regex" does not follow expected form and may not match as expected.')
+
if policy_type in ['prefix_list', 'prefix_list6']:
if 'prefix' not in rule_config:
raise ConfigError(f'A prefix {mandatory_error}')
diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py
index 53e83c3b4..e29f3358a 100755
--- a/src/conf_mode/protocols_bgp.py
+++ b/src/conf_mode/protocols_bgp.py
@@ -413,15 +413,19 @@ def verify(config_dict):
verify_route_map(afi_config['route_map'][tmp], bgp)
if 'route_reflector_client' in afi_config:
- peer_group_as = peer_config.get('remote_as')
+ peer_as = peer_config.get('remote_as')
- if peer_group_as is None or (peer_group_as != 'internal' and peer_group_as != bgp['system_as']):
+ if peer_as is not None and (peer_as != 'internal' and peer_as != bgp['system_as']):
raise ConfigError('route-reflector-client only supported for iBGP peers')
else:
+ # Check into the peer group for the remote as, if we are in a peer group, check in peer itself
if 'peer_group' in peer_config:
peer_group_as = dict_search(f'peer_group.{peer_group}.remote_as', bgp)
- if peer_group_as is None or (peer_group_as != 'internal' and peer_group_as != bgp['system_as']):
- raise ConfigError('route-reflector-client only supported for iBGP peers')
+ elif neighbor == 'peer_group':
+ peer_group_as = peer_config.get('remote_as')
+
+ if peer_group_as is None or (peer_group_as != 'internal' and peer_group_as != bgp['system_as']):
+ raise ConfigError('route-reflector-client only supported for iBGP peers')
# T5833 not all AFIs are supported for VRF
if 'vrf' in bgp and 'address_family' in peer_config:
@@ -523,11 +527,15 @@ def verify(config_dict):
raise ConfigError(
'Please unconfigure import vrf commands before using vpn commands in dependent VRFs!')
+ # Verify if the route-map exists
+ if dict_search('route_map.vrf.import', afi_config) is not None:
+ verify_route_map(afi_config['route_map']['vrf']['import'], bgp)
+
if (dict_search('route_map.vrf.import', afi_config) is not None
or dict_search('import.vrf', afi_config) is not None):
# FRR error: please unconfigure vpn to vrf commands before
# using import vrf commands
- if ('vpn' in afi_config['import']
+ if (dict_search('import.vpn', afi_config) is not None
or dict_search('export.vpn', afi_config) is not None):
raise ConfigError('Please unconfigure VPN to VRF commands before '\
'using "import vrf" commands!')
@@ -537,7 +545,6 @@ def verify(config_dict):
raise ConfigError('Please unconfigure route-map VPN to VRF commands before '\
'using "import vrf" commands!')
-
# Verify that the export/import route-maps do exist
for export_import in ['export', 'import']:
tmp = dict_search(f'route_map.vpn.{export_import}', afi_config)
diff --git a/src/conf_mode/service_https.py b/src/conf_mode/service_https.py
index 9e58b4c72..2123823f4 100755
--- a/src/conf_mode/service_https.py
+++ b/src/conf_mode/service_https.py
@@ -28,6 +28,7 @@ from vyos.configverify import verify_vrf
from vyos.configverify import verify_pki_certificate
from vyos.configverify import verify_pki_ca_certificate
from vyos.configverify import verify_pki_dh_parameters
+from vyos.configdiff import get_config_diff
from vyos.defaults import api_config_state
from vyos.pki import wrap_certificate
from vyos.pki import wrap_private_key
@@ -79,6 +80,14 @@ def get_config(config=None):
# merge CLI and default dictionary
https = config_dict_merge(default_values, https)
+
+ # some settings affecting nginx will require a restart:
+ # for example, a reload will not suffice when binding the listen address
+ # after nginx has started and dropped privileges; add flag here
+ diff = get_config_diff(conf)
+ children_changed = diff.node_changed_children(base)
+ https['nginx_restart_required'] = bool(set(children_changed) != set(['api']))
+
return https
def verify(https):
@@ -208,7 +217,10 @@ def apply(https):
elif is_systemd_service_active(http_api_service_name):
call(f'systemctl stop {http_api_service_name}')
- call(f'systemctl reload-or-restart {https_service_name}')
+ if https['nginx_restart_required']:
+ call(f'systemctl restart {https_service_name}')
+ else:
+ call(f'systemctl reload-or-restart {https_service_name}')
if __name__ == '__main__':
try:
diff --git a/src/conf_mode/system_option.py b/src/conf_mode/system_option.py
index b45a9d8a6..5acad6599 100755
--- a/src/conf_mode/system_option.py
+++ b/src/conf_mode/system_option.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019-2024 VyOS maintainers and contributors
+# Copyright (C) 2019-2025 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
@@ -127,6 +127,9 @@ def generate(options):
# occurance is used for having the appropriate options passed to GRUB
# when re-configuring options on the CLI.
cmdline_options = []
+ kernel_opts = options.get('kernel', {})
+ k_cpu_opts = kernel_opts.get('cpu', {})
+ k_memory_opts = kernel_opts.get('memory', {})
if 'kernel' in options:
if 'disable_mitigations' in options['kernel']:
cmdline_options.append('mitigations=off')
@@ -136,6 +139,50 @@ def generate(options):
mode = options['kernel']['amd_pstate_driver']
cmdline_options.append(
f'initcall_blacklist=acpi_cpufreq_init amd_pstate={mode}')
+ if 'quiet' in options['kernel']:
+ cmdline_options.append('quiet')
+
+ if 'disable_hpet' in kernel_opts:
+ cmdline_options.append('hpet=disable')
+
+ if 'disable_mce' in kernel_opts:
+ cmdline_options.append('mce=off')
+
+ if 'disable_softlockup' in kernel_opts:
+ cmdline_options.append('nosoftlockup')
+
+ # CPU options
+ isol_cpus = k_cpu_opts.get('isolate_cpus')
+ if isol_cpus:
+ cmdline_options.append(f'isolcpus={isol_cpus}')
+
+ nohz_full = k_cpu_opts.get('nohz_full')
+ if nohz_full:
+ cmdline_options.append(f'nohz_full={nohz_full}')
+
+ rcu_nocbs = k_cpu_opts.get('rcu_no_cbs')
+ if rcu_nocbs:
+ cmdline_options.append(f'rcu_nocbs={rcu_nocbs}')
+
+ if 'disable_nmi_watchdog' in k_cpu_opts:
+ cmdline_options.append('nmi_watchdog=0')
+
+ # Memory options
+ if 'disable_numa_balancing' in k_memory_opts:
+ cmdline_options.append('numa_balancing=disable')
+
+ default_hp_size = k_memory_opts.get('default_hugepage_size')
+ if default_hp_size:
+ cmdline_options.append(f'default_hugepagesz={default_hp_size}')
+
+ hp_sizes = k_memory_opts.get('hugepage_size')
+ if hp_sizes:
+ for size, settings in hp_sizes.items():
+ cmdline_options.append(f'hugepagesz={size}')
+ count = settings.get('hugepage_count')
+ if count:
+ cmdline_options.append(f'hugepages={count}')
+
grub_util.update_kernel_cmdline_options(' '.join(cmdline_options))
return None
diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py
index 2754314f7..ac25cd671 100755
--- a/src/conf_mode/vpn_ipsec.py
+++ b/src/conf_mode/vpn_ipsec.py
@@ -727,7 +727,7 @@ def generate(ipsec):
for remote_prefix in remote_prefixes:
local_net = ipaddress.ip_network(local_prefix)
remote_net = ipaddress.ip_network(remote_prefix)
- if local_net.overlaps(remote_net):
+ if local_net.subnet_of(remote_net):
if passthrough is None:
passthrough = []
passthrough.append(local_prefix)
diff --git a/src/conf_mode/vpn_openconnect.py b/src/conf_mode/vpn_openconnect.py
index 42785134f..0346c7819 100755
--- a/src/conf_mode/vpn_openconnect.py
+++ b/src/conf_mode/vpn_openconnect.py
@@ -93,7 +93,7 @@ def verify(ocserv):
"radius" in ocserv["authentication"]["mode"]):
raise ConfigError('OpenConnect authentication modes are mutually-exclusive, remove either local or radius from your configuration')
if "radius" in ocserv["authentication"]["mode"]:
- if not ocserv["authentication"]['radius']['server']:
+ if 'server' not in ocserv['authentication']['radius']:
raise ConfigError('OpenConnect authentication mode radius requires at least one RADIUS server')
if "local" in ocserv["authentication"]["mode"]:
if not ocserv.get("authentication", {}).get("local_users"):