summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/conf_mode/firewall.py12
-rwxr-xr-xsrc/conf_mode/protocols_bgp.py2
-rwxr-xr-xsrc/conf_mode/protocols_isis.py16
-rwxr-xr-xsrc/conf_mode/protocols_ospf.py7
-rwxr-xr-xsrc/conf_mode/service_dhcp-server.py48
-rwxr-xr-xsrc/conf_mode/service_router-advert.py18
-rwxr-xr-xsrc/conf_mode/system_conntrack.py43
-rwxr-xr-xsrc/etc/ipsec.d/vti-up-down4
-rwxr-xr-xsrc/helpers/priority.py42
-rwxr-xr-xsrc/helpers/vyos_config_sync.py72
-rwxr-xr-xsrc/migration-scripts/dhcp-server/10-to-1148
-rwxr-xr-xsrc/migration-scripts/dhcpv6-server/4-to-57
-rwxr-xr-xsrc/migration-scripts/policy/1-to-218
-rwxr-xr-xsrc/migration-scripts/policy/3-to-48
-rwxr-xr-xsrc/op_mode/conntrack.py3
-rwxr-xr-xsrc/op_mode/dhcp.py32
-rwxr-xr-xsrc/op_mode/image_installer.py4
-rwxr-xr-xsrc/services/vyos-http-api-server2
-rw-r--r--src/system/grub_update.py4
-rw-r--r--src/systemd/dhclient@.service1
-rw-r--r--src/systemd/vyos-grub-update.service4
21 files changed, 290 insertions, 105 deletions
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
index 3c27655b0..810437dda 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -268,6 +268,18 @@ def verify_rule(firewall, rule_conf, ipv6):
if 'port' in side_conf and dict_search_args(side_conf, 'group', 'port_group'):
raise ConfigError(f'{side} port-group and port cannot both be defined')
+ if 'add_address_to_group' in rule_conf:
+ for type in ['destination_address', 'source_address']:
+ if type in rule_conf['add_address_to_group']:
+ if 'address_group' not in rule_conf['add_address_to_group'][type]:
+ raise ConfigError(f'Dynamic address group must be defined.')
+ else:
+ target = rule_conf['add_address_to_group'][type]['address_group']
+ fwall_group = 'ipv6_address_group' if ipv6 else 'address_group'
+ group_obj = dict_search_args(firewall, 'group', 'dynamic_group', fwall_group, target)
+ if group_obj is None:
+ raise ConfigError(f'Invalid dynamic address group on firewall rule')
+
if 'log_options' in rule_conf:
if 'log' not in rule_conf:
raise ConfigError('log-options defined, but log is not enable')
diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py
index d90dfe45b..f1c59cbde 100755
--- a/src/conf_mode/protocols_bgp.py
+++ b/src/conf_mode/protocols_bgp.py
@@ -457,6 +457,8 @@ def verify(bgp):
peer_group_as = dict_search(f'peer_group.{peer_group}.remote_as', bgp)
if peer_group_as != None and peer_group_as != 'internal' and peer_group_as != bgp['system_as']:
raise ConfigError('route-reflector-client only supported for iBGP peers')
+ else:
+ raise ConfigError('route-reflector-client only supported for iBGP peers')
# Throw an error if a peer group is not configured for allow range
for prefix in dict_search('listen.range', bgp) or []:
diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py
index 8d594bb68..6c9925b80 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-2022 VyOS maintainers and contributors
+# Copyright (C) 2020-2024 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -155,12 +155,12 @@ def verify(isis):
for proto, proto_config in isis['redistribute'][afi].items():
if 'level_1' not in proto_config and 'level_2' not in proto_config:
raise ConfigError(f'Redistribute level-1 or level-2 should be specified in ' \
- f'"protocols isis {process} redistribute {afi} {proto}"!')
+ f'"protocols isis redistribute {afi} {proto}"!')
for redistr_level, redistr_config in proto_config.items():
if proc_level and proc_level != 'level_1_2' and proc_level != redistr_level:
- raise ConfigError(f'"protocols isis {process} redistribute {afi} {proto} {redistr_level}" ' \
- f'can not be used with \"protocols isis {process} level {proc_level}\"')
+ raise ConfigError(f'"protocols isis redistribute {afi} {proto} {redistr_level}" ' \
+ f'can not be used with \"protocols isis level {proc_level}\"!')
# Segment routing checks
if dict_search('segment_routing.global_block', isis):
@@ -220,8 +220,8 @@ def verify(isis):
if ("explicit_null" in prefix_config['index']) and ("no_php_flag" in prefix_config['index']):
raise ConfigError(f'Segment routing prefix {prefix} cannot have both explicit-null '\
f'and no-php-flag configured at the same time.')
-
- # Check for index ranges being larger than the segment routing global block
+
+ # Check for index ranges being larger than the segment routing global block
if dict_search('segment_routing.global_block', 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)
@@ -233,7 +233,7 @@ def verify(isis):
if int(index_size) > int(g_label_difference):
raise ConfigError(f'Segment routing prefix {prefix} cannot have an '\
f'index base size larger than the SRGB label base.')
-
+
# Check for LFA tiebreaker index duplication
if dict_search('fast_reroute.lfa.local.tiebreaker', isis):
comparison_dictionary = {}
@@ -311,4 +311,4 @@ if __name__ == '__main__':
apply(c)
except ConfigError as e:
print(e)
- exit(1) \ No newline at end of file
+ exit(1)
diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py
index 695842795..6fffe7e0d 100755
--- a/src/conf_mode/protocols_ospf.py
+++ b/src/conf_mode/protocols_ospf.py
@@ -127,6 +127,7 @@ def verify(ospf):
# Validate if configured Access-list exists
if 'area' in ospf:
+ networks = []
for area, area_config in ospf['area'].items():
if 'import_list' in area_config:
acl_import = area_config['import_list']
@@ -135,6 +136,12 @@ def verify(ospf):
acl_export = area_config['export_list']
if acl_export: verify_access_list(acl_export, ospf)
+ if 'network' in area_config:
+ for network in area_config['network']:
+ if network in networks:
+ raise ConfigError(f'Network "{network}" already defined in different area!')
+ networks.append(network)
+
if 'interface' in ospf:
for interface, interface_config in ospf['interface'].items():
verify_interface_exists(interface)
diff --git a/src/conf_mode/service_dhcp-server.py b/src/conf_mode/service_dhcp-server.py
index 91ea354b6..ba3d69b07 100755
--- a/src/conf_mode/service_dhcp-server.py
+++ b/src/conf_mode/service_dhcp-server.py
@@ -143,7 +143,7 @@ def get_config(config=None):
dhcp['shared_network_name'][network]['subnet'][subnet].update(
{'range' : new_range_dict})
- if dict_search('failover.certificate', dhcp):
+ if dict_search('high_availability.certificate', dhcp):
dhcp['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True)
return dhcp
@@ -286,34 +286,34 @@ def verify(dhcp):
if (shared_networks - disabled_shared_networks) < 1:
raise ConfigError(f'At least one shared network must be active!')
- if 'failover' in dhcp:
+ if 'high_availability' in dhcp:
for key in ['name', 'remote', 'source_address', 'status']:
- if key not in dhcp['failover']:
+ if key not in dhcp['high_availability']:
tmp = key.replace('_', '-')
- raise ConfigError(f'DHCP failover requires "{tmp}" to be specified!')
+ raise ConfigError(f'DHCP high-availability requires "{tmp}" to be specified!')
- if len({'certificate', 'ca_certificate'} & set(dhcp['failover'])) == 1:
- raise ConfigError(f'DHCP secured failover requires both certificate and CA certificate')
+ if len({'certificate', 'ca_certificate'} & set(dhcp['high_availability'])) == 1:
+ raise ConfigError(f'DHCP secured high-availability requires both certificate and CA certificate')
- if 'certificate' in dhcp['failover']:
- cert_name = dhcp['failover']['certificate']
+ if 'certificate' in dhcp['high_availability']:
+ cert_name = dhcp['high_availability']['certificate']
if cert_name not in dhcp['pki']['certificate']:
- raise ConfigError(f'Invalid certificate specified for DHCP failover')
+ raise ConfigError(f'Invalid certificate specified for DHCP high-availability')
if not dict_search_args(dhcp['pki']['certificate'], cert_name, 'certificate'):
- raise ConfigError(f'Invalid certificate specified for DHCP failover')
+ raise ConfigError(f'Invalid certificate specified for DHCP high-availability')
if not dict_search_args(dhcp['pki']['certificate'], cert_name, 'private', 'key'):
- raise ConfigError(f'Missing private key on certificate specified for DHCP failover')
+ raise ConfigError(f'Missing private key on certificate specified for DHCP high-availability')
- if 'ca_certificate' in dhcp['failover']:
- ca_cert_name = dhcp['failover']['ca_certificate']
+ if 'ca_certificate' in dhcp['high_availability']:
+ ca_cert_name = dhcp['high_availability']['ca_certificate']
if ca_cert_name not in dhcp['pki']['ca']:
- raise ConfigError(f'Invalid CA certificate specified for DHCP failover')
+ raise ConfigError(f'Invalid CA certificate specified for DHCP high-availability')
if not dict_search_args(dhcp['pki']['ca'], ca_cert_name, 'certificate'):
- raise ConfigError(f'Invalid CA certificate specified for DHCP failover')
+ raise ConfigError(f'Invalid CA certificate specified for DHCP high-availability')
for address in (dict_search('listen_address', dhcp) or []):
if is_addr_assigned(address):
@@ -359,23 +359,23 @@ def generate(dhcp):
if os.path.exists(f):
os.unlink(f)
- if 'failover' in dhcp:
- if 'certificate' in dhcp['failover']:
- cert_name = dhcp['failover']['certificate']
+ if 'high_availability' in dhcp:
+ if 'certificate' in dhcp['high_availability']:
+ cert_name = dhcp['high_availability']['certificate']
cert_data = dhcp['pki']['certificate'][cert_name]['certificate']
key_data = dhcp['pki']['certificate'][cert_name]['private']['key']
write_file(cert_file, wrap_certificate(cert_data), user=user_group, mode=0o600)
write_file(cert_key_file, wrap_private_key(key_data), user=user_group, mode=0o600)
- dhcp['failover']['cert_file'] = cert_file
- dhcp['failover']['cert_key_file'] = cert_key_file
+ dhcp['high_availability']['cert_file'] = cert_file
+ dhcp['high_availability']['cert_key_file'] = cert_key_file
- if 'ca_certificate' in dhcp['failover']:
- ca_cert_name = dhcp['failover']['ca_certificate']
+ if 'ca_certificate' in dhcp['high_availability']:
+ ca_cert_name = dhcp['high_availability']['ca_certificate']
ca_cert_data = dhcp['pki']['ca'][ca_cert_name]['certificate']
write_file(ca_cert_file, wrap_certificate(ca_cert_data), user=user_group, mode=0o600)
- dhcp['failover']['ca_cert_file'] = ca_cert_file
+ dhcp['high_availability']['ca_cert_file'] = ca_cert_file
render(systemd_override, 'dhcp-server/10-override.conf.j2', dhcp)
@@ -402,7 +402,7 @@ def apply(dhcp):
if service == 'kea-dhcp-ddns-server' and 'dynamic_dns_update' not in dhcp:
action = 'stop'
- if service == 'kea-ctrl-agent' and 'failover' not in dhcp:
+ if service == 'kea-ctrl-agent' and 'high_availability' not in dhcp:
action = 'stop'
call(f'systemctl {action} {service}.service')
diff --git a/src/conf_mode/service_router-advert.py b/src/conf_mode/service_router-advert.py
index dbb47de4e..88d767bb8 100755
--- a/src/conf_mode/service_router-advert.py
+++ b/src/conf_mode/service_router-advert.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2022 VyOS maintainers and contributors
+# Copyright (C) 2018-2024 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -17,6 +17,8 @@
import os
from sys import exit
+from ipaddress import IPv6Network
+
from vyos.base import Warning
from vyos.config import Config
from vyos.template import render
@@ -47,7 +49,9 @@ def verify(rtradv):
return None
for interface, interface_config in rtradv['interface'].items():
- if 'prefix' in interface:
+ interval_max = int(interface_config['interval']['max'])
+
+ if 'prefix' in interface_config:
for prefix, prefix_config in interface_config['prefix'].items():
valid_lifetime = prefix_config['valid_lifetime']
if valid_lifetime == 'infinity':
@@ -60,6 +64,15 @@ def verify(rtradv):
if not (int(valid_lifetime) >= int(preferred_lifetime)):
raise ConfigError('Prefix valid-lifetime must be greater then or equal to preferred-lifetime')
+ if 'nat64prefix' in interface_config:
+ nat64_supported_lengths = [32, 40, 48, 56, 64, 96]
+ for prefix, prefix_config in interface_config['nat64prefix'].items():
+ if IPv6Network(prefix).prefixlen not in nat64_supported_lengths:
+ raise ConfigError(f'Invalid NAT64 prefix length for "{prefix}", can only be one of: /' + ', /'.join(nat64_supported_lengths))
+
+ if int(prefix_config['valid_lifetime']) < interval_max:
+ raise ConfigError(f'NAT64 valid-lifetime must not be smaller then "interval max" which is "{interval_max}"!')
+
if 'name_server' in interface_config:
if len(interface_config['name_server']) > 3:
raise ConfigError('No more then 3 IPv6 name-servers supported!')
@@ -72,7 +85,6 @@ def verify(rtradv):
# ensure stale RDNSS info gets removed in a timely fashion, this
# should not be greater than 2*MaxRtrAdvInterval.
lifetime = int(interface_config['name_server_lifetime'])
- interval_max = int(interface_config['interval']['max'])
if lifetime > 0:
if lifetime < int(interval_max):
raise ConfigError(f'RDNSS lifetime must be at least "{interval_max}" seconds!')
diff --git a/src/conf_mode/system_conntrack.py b/src/conf_mode/system_conntrack.py
index e075bc928..3d42389f6 100755
--- a/src/conf_mode/system_conntrack.py
+++ b/src/conf_mode/system_conntrack.py
@@ -42,33 +42,38 @@ nftables_ct_file = r'/run/nftables-ct.conf'
module_map = {
'ftp': {
'ko': ['nf_nat_ftp', 'nf_conntrack_ftp'],
- 'nftables': ['ct helper set "ftp_tcp" tcp dport {21} return']
+ 'nftables': ['tcp dport {21} ct helper set "ftp_tcp" return']
},
'h323': {
'ko': ['nf_nat_h323', 'nf_conntrack_h323'],
- 'nftables': ['ct helper set "ras_udp" udp dport {1719} return',
- 'ct helper set "q931_tcp" tcp dport {1720} return']
+ 'nftables': ['udp dport {1719} ct helper set "ras_udp" return',
+ 'tcp dport {1720} ct helper set "q931_tcp" return']
},
'nfs': {
- 'nftables': ['ct helper set "rpc_tcp" tcp dport {111} return',
- 'ct helper set "rpc_udp" udp dport {111} return']
+ 'nftables': ['tcp dport {111} ct helper set "rpc_tcp" return',
+ 'udp dport {111} ct helper set "rpc_udp" return']
},
'pptp': {
'ko': ['nf_nat_pptp', 'nf_conntrack_pptp'],
- 'nftables': ['ct helper set "pptp_tcp" tcp dport {1723} return'],
+ 'nftables': ['tcp dport {1723} ct helper set "pptp_tcp" return'],
'ipv4': True
},
+ 'rtsp': {
+ 'ko': ['nf_nat_rtsp', 'nf_conntrack_rtsp'],
+ 'nftables': ['tcp dport {554} ct helper set "rtsp_tcp" return'],
+ 'ipv4': True
+ },
'sip': {
'ko': ['nf_nat_sip', 'nf_conntrack_sip'],
- 'nftables': ['ct helper set "sip_tcp" tcp dport {5060,5061} return',
- 'ct helper set "sip_udp" udp dport {5060,5061} return']
+ 'nftables': ['tcp dport {5060,5061} ct helper set "sip_tcp" return',
+ 'udp dport {5060,5061} ct helper set "sip_udp" return']
},
'sqlnet': {
- 'nftables': ['ct helper set "tns_tcp" tcp dport {1521,1525,1536} return']
+ 'nftables': ['tcp dport {1521,1525,1536} ct helper set "tns_tcp" return']
},
'tftp': {
'ko': ['nf_nat_tftp', 'nf_conntrack_tftp'],
- 'nftables': ['ct helper set "tftp_udp" udp dport {69} return']
+ 'nftables': ['udp dport {69} ct helper set "tftp_udp" return']
},
}
@@ -180,12 +185,16 @@ def generate(conntrack):
conntrack['ipv4_firewall_action'] = 'return'
conntrack['ipv6_firewall_action'] = 'return'
- for rules, path in dict_search_recursive(conntrack['firewall'], 'rule'):
- if any(('state' in rule_conf or 'connection_status' in rule_conf or 'offload_target' in rule_conf) for rule_conf in rules.values()):
- if path[0] == 'ipv4':
- conntrack['ipv4_firewall_action'] = 'accept'
- elif path[0] == 'ipv6':
- conntrack['ipv6_firewall_action'] = 'accept'
+ if dict_search_args(conntrack['firewall'], 'global_options', 'state_policy') != None:
+ conntrack['ipv4_firewall_action'] = 'accept'
+ conntrack['ipv6_firewall_action'] = 'accept'
+ else:
+ for rules, path in dict_search_recursive(conntrack['firewall'], 'rule'):
+ if any(('state' in rule_conf or 'connection_status' in rule_conf or 'offload_target' in rule_conf) for rule_conf in rules.values()):
+ if path[0] == 'ipv4':
+ conntrack['ipv4_firewall_action'] = 'accept'
+ elif path[0] == 'ipv6':
+ conntrack['ipv6_firewall_action'] = 'accept'
render(conntrack_config, 'conntrack/vyos_nf_conntrack.conf.j2', conntrack)
render(sysctl_file, 'conntrack/sysctl.conf.j2', conntrack)
@@ -195,7 +204,7 @@ def generate(conntrack):
def apply(conntrack):
# Depending on the enable/disable state of the ALG (Application Layer Gateway)
# modules we need to either insmod or rmmod the helpers.
-
+
add_modules = []
rm_modules = []
diff --git a/src/etc/ipsec.d/vti-up-down b/src/etc/ipsec.d/vti-up-down
index 441b316c2..01e9543c9 100755
--- a/src/etc/ipsec.d/vti-up-down
+++ b/src/etc/ipsec.d/vti-up-down
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021-2023 VyOS maintainers and contributors
+# Copyright (C) 2021-2024 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -57,7 +57,9 @@ if __name__ == '__main__':
if 'disable' not in vti:
tmp = VTIIf(interface)
tmp.update(vti)
+ call(f'sudo ip link set {interface} up')
else:
+ call(f'sudo ip link set {interface} down')
syslog(f'Interface {interface} is admin down ...')
elif verb in ['down-client', 'down-host']:
if vti_link_up:
diff --git a/src/helpers/priority.py b/src/helpers/priority.py
new file mode 100755
index 000000000..04186104c
--- /dev/null
+++ b/src/helpers/priority.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2024 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+import sys
+from argparse import ArgumentParser
+from tabulate import tabulate
+
+from vyos.priority import get_priority_data
+
+if __name__ == '__main__':
+ parser = ArgumentParser()
+ parser.add_argument('--legacy-format', action='store_true',
+ help="format output for comparison with legacy 'priority.pl'")
+ args = parser.parse_args()
+
+ prio_list = get_priority_data()
+ if args.legacy_format:
+ for p in prio_list:
+ print(f'{p[2]} {"/".join(p[0])}')
+ sys.exit(0)
+
+ l = []
+ for p in prio_list:
+ l.append((p[2], p[1], p[0]))
+ headers = ['priority', 'owner', 'path']
+ out = tabulate(l, headers, numalign='right')
+ print(out)
diff --git a/src/helpers/vyos_config_sync.py b/src/helpers/vyos_config_sync.py
index 7cfa8fe88..77f7cd810 100755
--- a/src/helpers/vyos_config_sync.py
+++ b/src/helpers/vyos_config_sync.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2023 VyOS maintainers and contributors
+# Copyright (C) 2023-2024 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -60,19 +60,20 @@ def post_request(url: str,
return response
-def retrieve_config(section: str = None) -> Optional[Dict[str, Any]]:
+
+def retrieve_config(section: Optional[List[str]] = None) -> Optional[Dict[str, Any]]:
"""Retrieves the configuration from the local server.
Args:
- section: str: The section of the configuration to retrieve. Default is None.
+ section: List[str]: The section of the configuration to retrieve.
+ Default is None.
Returns:
- Optional[Dict[str, Any]]: The retrieved configuration as a dictionary, or None if an error occurred.
+ Optional[Dict[str, Any]]: The retrieved configuration as a
+ dictionary, or None if an error occurred.
"""
if section is None:
section = []
- else:
- section = section.split()
conf = Config()
config = conf.get_config_dict(section, get_first_key=True)
@@ -84,25 +85,21 @@ def retrieve_config(section: str = None) -> Optional[Dict[str, Any]]:
def set_remote_config(
address: str,
key: str,
- op: str,
- path: str = None,
- section: Optional[str] = None) -> Optional[Dict[str, Any]]:
+ commands: List[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
"""Loads the VyOS configuration in JSON format to a remote host.
Args:
address (str): The address of the remote host.
key (str): The key to use for loading the configuration.
- path (Optional[str]): The path of the configuration. Default is None.
- section (Optional[str]): The section of the configuration to load. Default is None.
+ commands (list): List of set/load commands for request, given as:
+ [{'op': str, 'path': list[str], 'section': dict},
+ ...]
Returns:
- Optional[Dict[str, Any]]: The response from the remote host as a dictionary, or None if an error occurred.
+ Optional[Dict[str, Any]]: The response from the remote host as a
+ dictionary, or None if a RequestException occurred.
"""
- if path is None:
- path = []
- else:
- path = path.split()
headers = {'Content-Type': 'application/json'}
# Disable the InsecureRequestWarning
@@ -110,9 +107,7 @@ def set_remote_config(
url = f'https://{address}/configure-section'
data = json.dumps({
- 'op': mode,
- 'path': path,
- 'section': section,
+ 'commands': commands,
'key': key
})
@@ -125,19 +120,18 @@ def set_remote_config(
return None
-def is_section_revised(section: str) -> bool:
+def is_section_revised(section: List[str]) -> bool:
from vyos.config_mgmt import is_node_revised
- return is_node_revised([section])
+ return is_node_revised(section)
def config_sync(secondary_address: str,
secondary_key: str,
- sections: List[str],
+ sections: List[list[str]],
mode: str):
"""Retrieve a config section from primary router in JSON format and send it to
secondary router
"""
- # Config sync only if sections changed
if not any(map(is_section_revised, sections)):
return
@@ -146,21 +140,25 @@ def config_sync(secondary_address: str,
)
# Sync sections ("nat", "firewall", etc)
+ commands = []
for section in sections:
config_json = retrieve_config(section=section)
# Check if config path deesn't exist, for example "set nat"
# we set empty value for config_json data
# As we cannot send to the remote host section "nat None" config
if not config_json:
- config_json = ""
+ config_json = {}
logger.debug(
f"Retrieved config for section '{section}': {config_json}")
- set_config = set_remote_config(address=secondary_address,
- key=secondary_key,
- op=mode,
- path=section,
- section=config_json)
- logger.debug(f"Set config for section '{section}': {set_config}")
+
+ d = {'op': mode, 'path': section, 'section': config_json}
+ commands.append(d)
+
+ set_config = set_remote_config(address=secondary_address,
+ key=secondary_key,
+ commands=commands)
+
+ logger.debug(f"Set config for sections '{sections}': {set_config}")
if __name__ == '__main__':
@@ -188,5 +186,17 @@ if __name__ == '__main__':
"Missing required configuration data for config synchronization.")
exit(0)
+ # Generate list_sections of sections/subsections
+ # [
+ # ['interfaces', 'pseudo-ethernet'], ['interfaces', 'virtual-ethernet'], ['nat'], ['nat66']
+ # ]
+ list_sections = []
+ for section, subsections in sections.items():
+ if subsections:
+ for subsection in subsections:
+ list_sections.append([section, subsection])
+ else:
+ list_sections.append([section])
+
config_sync(secondary_address, secondary_key,
- sections, mode)
+ list_sections, mode)
diff --git a/src/migration-scripts/dhcp-server/10-to-11 b/src/migration-scripts/dhcp-server/10-to-11
new file mode 100755
index 000000000..a0dc96ad0
--- /dev/null
+++ b/src/migration-scripts/dhcp-server/10-to-11
@@ -0,0 +1,48 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2024 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# T6171: rename "service dhcp-server failover" to "service dhcp-server high-availability"
+
+from sys import argv
+from sys import exit
+
+from vyos.configtree import ConfigTree
+
+if len(argv) < 2:
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+base = ['service', 'dhcp-server']
+config = ConfigTree(config_file)
+
+if not config.exists(base):
+ # Nothing to do
+ exit(0)
+
+if config.exists(base + ['failover']):
+ config.rename(base + ['failover'],'high-availability')
+
+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) \ No newline at end of file
diff --git a/src/migration-scripts/dhcpv6-server/4-to-5 b/src/migration-scripts/dhcpv6-server/4-to-5
index ae506b9c5..55fda91b3 100755
--- a/src/migration-scripts/dhcpv6-server/4-to-5
+++ b/src/migration-scripts/dhcpv6-server/4-to-5
@@ -42,8 +42,11 @@ def find_subnet_interface(subnet):
def check_addr(if_path):
if config.exists(if_path + ['address']):
for addr in config.return_values(if_path + ['address']):
- if ip_network(addr, strict=False) == subnet_net:
- return True
+ try:
+ if ip_network(addr, strict=False) == subnet_net:
+ return True
+ except:
+ pass # interface address was probably "dhcp" or other magic string
return None
for iftype in config.list_nodes(['interfaces']):
diff --git a/src/migration-scripts/policy/1-to-2 b/src/migration-scripts/policy/1-to-2
index c70490ce9..c7a983bba 100755
--- a/src/migration-scripts/policy/1-to-2
+++ b/src/migration-scripts/policy/1-to-2
@@ -32,23 +32,23 @@ file_name = argv[1]
with open(file_name, 'r') as f:
config_file = f.read()
-base = ['policy', 'ipv6-route']
+base = ['policy']
config = ConfigTree(config_file)
if not config.exists(base):
# Nothing to do
exit(0)
-config.rename(base, 'route6')
-config.set_tag(['policy', 'route6'])
+if config.exists(base + ['ipv6-route']):
+ config.rename(base + ['ipv6-route'],'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(base + [route]):
+ for name in config.list_nodes(base + [route]):
+ if config.exists(base + [route, name, 'rule']):
+ for rule in config.list_nodes(base + [route, name, 'rule']):
+ rule_tcp_flags = base + [route, name, 'rule', rule, 'tcp', 'flags']
if config.exists(rule_tcp_flags):
tmp = config.return_value(rule_tcp_flags)
diff --git a/src/migration-scripts/policy/3-to-4 b/src/migration-scripts/policy/3-to-4
index 1ebb248b0..476fa3af2 100755
--- a/src/migration-scripts/policy/3-to-4
+++ b/src/migration-scripts/policy/3-to-4
@@ -51,7 +51,7 @@ def community_migrate(config: ConfigTree, rule: list[str]) -> bool:
:rtype: bool
"""
community_list = list((config.return_value(rule)).split(" "))
-
+ config.delete(rule)
if 'none' in community_list:
config.set(rule + ['none'])
return False
@@ -61,10 +61,8 @@ def community_migrate(config: ConfigTree, rule: list[str]) -> bool:
community_action = 'add'
community_list.remove('additive')
for community in community_list:
- if len(community):
- config.set(rule + [community_action], value=community,
- replace=False)
- config.delete(rule)
+ config.set(rule + [community_action], value=community,
+ replace=False)
if community_action == 'replace':
return False
else:
diff --git a/src/op_mode/conntrack.py b/src/op_mode/conntrack.py
index cf8adf795..6ea213bec 100755
--- a/src/op_mode/conntrack.py
+++ b/src/op_mode/conntrack.py
@@ -112,7 +112,8 @@ def get_formatted_output(dict_data):
proto = meta['layer4']['protoname']
if direction == 'independent':
conn_id = meta['id']
- timeout = meta['timeout']
+ # T6138 flowtable offload conntrack entries without 'timeout'
+ timeout = meta.get('timeout', 'n/a')
orig_src = f'{orig_src}:{orig_sport}' if orig_sport else orig_src
orig_dst = f'{orig_dst}:{orig_dport}' if orig_dport else orig_dst
reply_src = f'{reply_src}:{reply_sport}' if reply_sport else reply_src
diff --git a/src/op_mode/dhcp.py b/src/op_mode/dhcp.py
index d27e1baf7..a2f947400 100755
--- a/src/op_mode/dhcp.py
+++ b/src/op_mode/dhcp.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2022-2023 VyOS maintainers and contributors
+# Copyright (C) 2022-2024 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -33,6 +33,7 @@ from vyos.kea import kea_get_leases
from vyos.kea import kea_get_pool_from_subnet_id
from vyos.kea import kea_delete_lease
from vyos.utils.process import is_systemd_service_running
+from vyos.utils.process import call
time_string = "%a %b %d %H:%M:%S %Z %Y"
@@ -309,6 +310,25 @@ def _verify(func):
return func(*args, **kwargs)
return _wrapper
+def _verify_client(func):
+ """Decorator checks if interface is configured as DHCP client"""
+ from functools import wraps
+ from vyos.ifconfig import Section
+
+ @wraps(func)
+ def _wrapper(*args, **kwargs):
+ config = ConfigTreeQuery()
+ family = kwargs.get('family')
+ v = 'v6' if family == 'inet6' else ''
+ interface = kwargs.get('interface')
+ interface_path = Section.get_config_path(interface)
+ unconf_message = f'DHCP{v} client not configured on interface {interface}!'
+
+ # Check if config does not exist
+ if not config.exists(f'interfaces {interface_path} address dhcp{v}'):
+ raise vyos.opmode.UnconfiguredSubsystem(unconf_message)
+ return func(*args, **kwargs)
+ return _wrapper
@_verify
def show_pool_statistics(raw: bool, family: ArgFamily, pool: typing.Optional[str]):
@@ -474,6 +494,16 @@ def show_client_leases(raw: bool, family: ArgFamily, interface: typing.Optional[
else:
return _get_formatted_client_leases(lease_data, family=family)
+@_verify_client
+def renew_client_lease(raw: bool, family: ArgFamily, interface: str):
+ if not raw:
+ v = 'v6' if family == 'inet6' else ''
+ print(f'Restarting DHCP{v} client on interface {interface}...')
+ if family == 'inet6':
+ call(f'systemctl restart dhcp6c@{interface}.service')
+ else:
+ call(f'systemctl restart dhclient@{interface}.service')
+
if __name__ == '__main__':
try:
res = vyos.opmode.run(sys.modules[__name__])
diff --git a/src/op_mode/image_installer.py b/src/op_mode/image_installer.py
index 85ebd19ba..b0567305a 100755
--- a/src/op_mode/image_installer.py
+++ b/src/op_mode/image_installer.py
@@ -786,6 +786,10 @@ def install_image() -> None:
grub.install(install_target.name, f'{DIR_DST_ROOT}/boot/',
f'{DIR_DST_ROOT}/boot/efi')
+ # sort inodes (to make GRUB read config files in alphabetical order)
+ grub.sort_inodes(f'{DIR_DST_ROOT}/{grub.GRUB_DIR_VYOS}')
+ grub.sort_inodes(f'{DIR_DST_ROOT}/{grub.GRUB_DIR_VYOS_VERS}')
+
# umount filesystems and remove temporary files
if is_raid_install(install_target):
cleanup([install_target.name],
diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server
index a7b14a1a3..77870a84c 100755
--- a/src/services/vyos-http-api-server
+++ b/src/services/vyos-http-api-server
@@ -463,7 +463,7 @@ def _configure_op(data: Union[ConfigureModel, ConfigureListModel,
endpoint = request.url.path
# Allow users to pass just one command
- if not isinstance(data, ConfigureListModel):
+ if not isinstance(data, (ConfigureListModel, ConfigSectionListModel)):
data = [data]
else:
data = data.commands
diff --git a/src/system/grub_update.py b/src/system/grub_update.py
index 5a7d8eb72..5a0534195 100644
--- a/src/system/grub_update.py
+++ b/src/system/grub_update.py
@@ -105,4 +105,8 @@ if __name__ == '__main__':
else:
render(grub_cfg_main, grub.TMPL_GRUB_MAIN, {})
+ # sort inodes (to make GRUB read config files in alphabetical order)
+ grub.sort_inodes(f'{root_dir}/{grub.GRUB_DIR_VYOS}')
+ grub.sort_inodes(f'{root_dir}/{grub.GRUB_DIR_VYOS_VERS}')
+
exit(0)
diff --git a/src/systemd/dhclient@.service b/src/systemd/dhclient@.service
index 099f7ed52..d430d8868 100644
--- a/src/systemd/dhclient@.service
+++ b/src/systemd/dhclient@.service
@@ -3,6 +3,7 @@ Description=DHCP client on %i
Documentation=man:dhclient(8)
StartLimitIntervalSec=0
After=vyos-router.service
+ConditionPathExists=/run/dhclient/dhclient_%i.conf
[Service]
Type=exec
diff --git a/src/systemd/vyos-grub-update.service b/src/systemd/vyos-grub-update.service
index 522b13a33..7b67ae1b8 100644
--- a/src/systemd/vyos-grub-update.service
+++ b/src/systemd/vyos-grub-update.service
@@ -6,9 +6,9 @@ Before=vyos-router.service
[Service]
Type=oneshot
ExecStart=/usr/libexec/vyos/system/grub_update.py
-TimeoutSec=5
+TimeoutSec=60
KillMode=process
StandardOutput=journal+console
[Install]
-WantedBy=vyos-router.service \ No newline at end of file
+WantedBy=vyos-router.service