summaryrefslogtreecommitdiff
path: root/src/conf_mode
diff options
context:
space:
mode:
Diffstat (limited to 'src/conf_mode')
-rwxr-xr-xsrc/conf_mode/conntrack.py135
-rwxr-xr-xsrc/conf_mode/dns_dynamic.py2
-rwxr-xr-xsrc/conf_mode/firewall.py67
-rwxr-xr-xsrc/conf_mode/flow_accounting_conf.py2
-rwxr-xr-xsrc/conf_mode/high-availability.py2
-rwxr-xr-xsrc/conf_mode/interfaces-openvpn.py13
-rwxr-xr-xsrc/conf_mode/load-balancing-wan.py5
-rwxr-xr-xsrc/conf_mode/nat.py105
-rwxr-xr-xsrc/conf_mode/nat66.py46
-rwxr-xr-xsrc/conf_mode/policy-local-route.py103
-rwxr-xr-xsrc/conf_mode/service_aws_glb.py76
-rwxr-xr-xsrc/conf_mode/service_mdns-repeater.py24
-rwxr-xr-xsrc/conf_mode/snmp.py5
-rwxr-xr-xsrc/conf_mode/system-ip.py28
-rwxr-xr-xsrc/conf_mode/system-ipv6.py25
-rwxr-xr-xsrc/conf_mode/system_frr.py35
-rwxr-xr-xsrc/conf_mode/vpn_ipsec.py15
17 files changed, 394 insertions, 294 deletions
diff --git a/src/conf_mode/conntrack.py b/src/conf_mode/conntrack.py
index a0de914bc..2c5fa335e 100755
--- a/src/conf_mode/conntrack.py
+++ b/src/conf_mode/conntrack.py
@@ -20,11 +20,11 @@ import re
from sys import exit
from vyos.config import Config
-from vyos.firewall import find_nftables_rule
-from vyos.firewall import remove_nftables_rule
+from vyos.configdep import set_dependents, call_dependents
from vyos.utils.process import process_named_running
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.process import cmd
from vyos.utils.process import rc_cmd
from vyos.utils.process import run
@@ -40,27 +40,35 @@ nftables_ct_file = r'/run/nftables-ct.conf'
# Every ALG (Application Layer Gateway) consists of either a Kernel Object
# also called a Kernel Module/Driver or some rules present in iptables
module_map = {
- 'ftp' : {
- 'ko' : ['nf_nat_ftp', 'nf_conntrack_ftp'],
+ 'ftp': {
+ 'ko': ['nf_nat_ftp', 'nf_conntrack_ftp'],
+ 'nftables': ['ct helper set "ftp_tcp" tcp dport {21} return']
},
- 'h323' : {
- 'ko' : ['nf_nat_h323', 'nf_conntrack_h323'],
+ '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']
},
- 'nfs' : {
- 'nftables' : ['ct helper set "rpc_tcp" tcp dport "{111}" return',
- 'ct helper set "rpc_udp" udp dport "{111}" return']
+ 'nfs': {
+ 'nftables': ['ct helper set "rpc_tcp" tcp dport {111} return',
+ 'ct helper set "rpc_udp" udp dport {111} return']
},
- 'pptp' : {
- 'ko' : ['nf_nat_pptp', 'nf_conntrack_pptp'],
+ 'pptp': {
+ 'ko': ['nf_nat_pptp', 'nf_conntrack_pptp'],
+ 'nftables': ['ct helper set "pptp_tcp" tcp dport {1723} return'],
+ 'ipv4': True
},
- 'sip' : {
- 'ko' : ['nf_nat_sip', 'nf_conntrack_sip'],
+ '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']
},
- 'sqlnet' : {
- 'nftables' : ['ct helper set "tns_tcp" tcp dport "{1521,1525,1536}" return']
+ 'sqlnet': {
+ 'nftables': ['ct helper set "tns_tcp" tcp dport {1521,1525,1536} return']
},
- 'tftp' : {
- 'ko' : ['nf_nat_tftp', 'nf_conntrack_tftp'],
+ 'tftp': {
+ 'ko': ['nf_nat_tftp', 'nf_conntrack_tftp'],
+ 'nftables': ['ct helper set "tftp_udp" udp dport {69} return']
},
}
@@ -71,11 +79,6 @@ valid_groups = [
'port_group'
]
-def resync_conntrackd():
- tmp = run('/usr/libexec/vyos/conf_mode/conntrack_sync.py')
- if tmp > 0:
- print('ERROR: error restarting conntrackd!')
-
def get_config(config=None):
if config:
conf = config
@@ -87,10 +90,20 @@ def get_config(config=None):
get_first_key=True,
with_recursive_defaults=True)
- conntrack['firewall_group'] = conf.get_config_dict(['firewall', 'group'], key_mangling=('-', '_'),
+ conntrack['firewall'] = conf.get_config_dict(['firewall'], key_mangling=('-', '_'),
get_first_key=True,
no_tag_node_value_mangle=True)
+ conntrack['ipv4_nat_action'] = 'accept' if conf.exists(['nat']) else 'return'
+ conntrack['ipv6_nat_action'] = 'accept' if conf.exists(['nat66']) else 'return'
+ conntrack['wlb_action'] = 'accept' if conf.exists(['load-balancing', 'wan']) else 'return'
+ conntrack['wlb_local_action'] = conf.exists(['load-balancing', 'wan', 'enable-local-traffic'])
+
+ conntrack['module_map'] = module_map
+
+ if conf.exists(['service', 'conntrack-sync']):
+ set_dependents('conntrack_sync', conf)
+
return conntrack
def verify(conntrack):
@@ -104,6 +117,17 @@ def verify(conntrack):
if 'protocol' not in rule_config or rule_config['protocol'] not in ['tcp', 'udp']:
raise ConfigError(f'Port requires tcp or udp as protocol in rule {rule}')
+ tcp_flags = dict_search_args(rule_config, 'tcp', 'flags')
+ if tcp_flags:
+ if dict_search_args(rule_config, 'protocol') != 'tcp':
+ raise ConfigError('Protocol must be tcp when specifying tcp flags')
+
+ not_flags = dict_search_args(rule_config, 'tcp', 'flags', 'not')
+ if not_flags:
+ duplicates = [flag for flag in tcp_flags if flag in not_flags]
+ if duplicates:
+ raise ConfigError(f'Cannot match a tcp flag as set and not set')
+
for side in ['destination', 'source']:
if side in rule_config:
side_conf = rule_config[side]
@@ -127,7 +151,7 @@ def verify(conntrack):
if inet == 'ipv6':
group = f'ipv6_{group}'
- group_obj = dict_search_args(conntrack['firewall_group'], group, group_name)
+ group_obj = dict_search_args(conntrack['firewall'], 'group', group, group_name)
if group_obj is None:
raise ConfigError(f'Invalid {error_group} "{group_name}" on ignore rule')
@@ -138,56 +162,57 @@ def verify(conntrack):
return None
def generate(conntrack):
+ if not os.path.exists(nftables_ct_file):
+ conntrack['first_install'] = True
+
+ # Determine if conntrack is needed
+ 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'
+
render(conntrack_config, 'conntrack/vyos_nf_conntrack.conf.j2', conntrack)
render(sysctl_file, 'conntrack/sysctl.conf.j2', conntrack)
render(nftables_ct_file, 'conntrack/nftables-ct.j2', conntrack)
return None
-def find_nftables_ct_rule(table, chain, rule):
- helper_search = re.search('ct helper set "(\w+)"', rule)
- if helper_search:
- rule = helper_search[1]
- return find_nftables_rule(table, chain, [rule])
-
-def find_remove_rule(table, chain, rule):
- handle = find_nftables_ct_rule(table, chain, rule)
- if handle:
- remove_nftables_rule(table, chain, handle)
-
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 = []
+
for module, module_config in module_map.items():
- if dict_search(f'modules.{module}', conntrack) is None:
+ if dict_search_args(conntrack, 'modules', module) is None:
if 'ko' in module_config:
- for mod in module_config['ko']:
- # Only remove the module if it's loaded
- if os.path.exists(f'/sys/module/{mod}'):
- cmd(f'rmmod {mod}')
- if 'nftables' in module_config:
- for rule in module_config['nftables']:
- find_remove_rule('raw', 'VYOS_CT_HELPER', rule)
- find_remove_rule('ip6 raw', 'VYOS_CT_HELPER', rule)
+ unloaded = [mod for mod in module_config['ko'] if os.path.exists(f'/sys/module/{mod}')]
+ rm_modules.extend(unloaded)
else:
if 'ko' in module_config:
- for mod in module_config['ko']:
- cmd(f'modprobe {mod}')
- if 'nftables' in module_config:
- for rule in module_config['nftables']:
- if not find_nftables_ct_rule('raw', 'VYOS_CT_HELPER', rule):
- cmd(f'nft insert rule raw VYOS_CT_HELPER {rule}')
+ add_modules.extend(module_config['ko'])
- if not find_nftables_ct_rule('ip6 raw', 'VYOS_CT_HELPER', rule):
- cmd(f'nft insert rule ip6 raw VYOS_CT_HELPER {rule}')
+ # Add modules before nftables uses them
+ if add_modules:
+ module_str = ' '.join(add_modules)
+ cmd(f'modprobe -a {module_str}')
# Load new nftables ruleset
install_result, output = rc_cmd(f'nft -f {nftables_ct_file}')
if install_result == 1:
raise ConfigError(f'Failed to apply configuration: {output}')
- if process_named_running('conntrackd'):
- # Reload conntrack-sync daemon to fetch new sysctl values
- resync_conntrackd()
+ # Remove modules after nftables stops using them
+ if rm_modules:
+ module_str = ' '.join(rm_modules)
+ cmd(f'rmmod {module_str}')
+
+ call_dependents()
# We silently ignore all errors
# See: https://bugzilla.redhat.com/show_bug.cgi?id=1264080
diff --git a/src/conf_mode/dns_dynamic.py b/src/conf_mode/dns_dynamic.py
index ab80defe8..4b1aed742 100755
--- a/src/conf_mode/dns_dynamic.py
+++ b/src/conf_mode/dns_dynamic.py
@@ -104,7 +104,7 @@ def generate(dyndns):
if not dyndns or 'address' not in dyndns:
return None
- render(config_file, 'dns-dynamic/ddclient.conf.j2', dyndns)
+ render(config_file, 'dns-dynamic/ddclient.conf.j2', dyndns, permission=0o600)
render(systemd_override, 'dns-dynamic/override.conf.j2', dyndns)
return None
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
index c3b1ee015..f6480ab0a 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021-2022 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
@@ -26,7 +26,8 @@ from vyos.config import Config
from vyos.configdict import node_changed
from vyos.configdiff import get_config_diff, Diff
from vyos.configdep import set_dependents, call_dependents
-# from vyos.configverify import verify_interface_exists
+from vyos.configverify import verify_interface_exists
+from vyos.ethtool import Ethtool
from vyos.firewall import fqdn_config_parse
from vyos.firewall import geoip_update
from vyos.template import render
@@ -38,6 +39,7 @@ from vyos.utils.process import process_named_running
from vyos.utils.process import rc_cmd
from vyos import ConfigError
from vyos import airbag
+
airbag.enable()
nat_conf_script = 'nat.py'
@@ -100,7 +102,7 @@ def geoip_updated(conf, firewall):
elif (path[0] == 'ipv6'):
set_name = f'GEOIP_CC6_{path[1]}_{path[2]}_{path[4]}'
out['ipv6_name'].append(set_name)
-
+
updated = True
if 'delete' in node_diff:
@@ -140,6 +142,8 @@ def get_config(config=None):
fqdn_config_parse(firewall)
+ set_dependents('conntrack', conf)
+
return firewall
def verify_rule(firewall, rule_conf, ipv6):
@@ -160,6 +164,25 @@ def verify_rule(firewall, rule_conf, ipv6):
if target not in dict_search_args(firewall, 'ipv6', 'name'):
raise ConfigError(f'Invalid jump-target. Firewall ipv6 name {target} does not exist on the system')
+ if rule_conf['action'] == 'offload':
+ if 'offload_target' not in rule_conf:
+ raise ConfigError('Action set to offload, but no offload-target specified')
+
+ offload_target = rule_conf['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 rule_conf['action'] != 'synproxy' and 'synproxy' in rule_conf:
+ raise ConfigError('"synproxy" option allowed only for action synproxy')
+ if rule_conf['action'] == 'synproxy':
+ if 'state' in rule_conf:
+ raise ConfigError('For action "synproxy" state cannot be defined')
+ if not rule_conf.get('synproxy', {}).get('tcp'):
+ raise ConfigError('synproxy TCP MSS is not defined')
+ if rule_conf.get('protocol', {}) != 'tcp':
+ raise ConfigError('For action "synproxy" the protocol must be set to TCP')
+
if 'queue_options' in rule_conf:
if 'queue' not in rule_conf['action']:
raise ConfigError('queue-options defined, but action queue needed and it is not defined')
@@ -279,7 +302,31 @@ def verify_nested_group(group_name, group, groups, seen):
if 'include' in groups[g]:
verify_nested_group(g, groups[g], groups, seen)
+def verify_hardware_offload(ifname):
+ ethtool = Ethtool(ifname)
+ enabled, fixed = ethtool.get_hw_tc_offload()
+
+ if not enabled and fixed:
+ raise ConfigError(f'Interface "{ifname}" does not support hardware offload')
+
+ if not enabled:
+ raise ConfigError(f'Interface "{ifname}" requires "offload hw-tc-offload"')
+
def verify(firewall):
+ if 'flowtable' in firewall:
+ for flowtable, flowtable_conf in firewall['flowtable'].items():
+ if 'interface' not in flowtable_conf:
+ raise ConfigError(f'Flowtable "{flowtable}" requires at least one interface')
+
+ for ifname in flowtable_conf['interface']:
+ verify_interface_exists(ifname)
+
+ if dict_search_args(flowtable_conf, 'offload') == 'hardware':
+ interfaces = flowtable_conf['interface']
+
+ for ifname in interfaces:
+ verify_hardware_offload(ifname)
+
if 'group' in firewall:
for group_type in nested_group_types:
if group_type in firewall['group']:
@@ -333,17 +380,6 @@ def generate(firewall):
if not os.path.exists(nftables_conf):
firewall['first_install'] = True
- # Determine if conntrack is needed
- firewall['ipv4_conntrack_action'] = 'return'
- firewall['ipv6_conntrack_action'] = 'return'
-
- for rules, path in dict_search_recursive(firewall, 'rule'):
- if any(('state' in rule_conf or 'connection_status' in rule_conf) for rule_conf in rules.values()):
- if path[0] == 'ipv4':
- firewall['ipv4_conntrack_action'] = 'accept'
- elif path[0] == 'ipv6':
- firewall['ipv6_conntrack_action'] = 'accept'
-
render(nftables_conf, 'firewall/nftables.j2', firewall)
return None
@@ -373,8 +409,7 @@ def apply(firewall):
apply_sysfs(firewall)
- if firewall['group_resync']:
- call_dependents()
+ call_dependents()
# T970 Enable a resolver (systemd daemon) that checks
# domain-group/fqdn addresses and update entries for domains by timeout
diff --git a/src/conf_mode/flow_accounting_conf.py b/src/conf_mode/flow_accounting_conf.py
index 71acd69fa..81ee39df1 100755
--- a/src/conf_mode/flow_accounting_conf.py
+++ b/src/conf_mode/flow_accounting_conf.py
@@ -37,7 +37,7 @@ uacctd_conf_path = '/run/pmacct/uacctd.conf'
systemd_service = 'uacctd.service'
systemd_override = f'/run/systemd/system/{systemd_service}.d/override.conf'
nftables_nflog_table = 'raw'
-nftables_nflog_chain = 'VYOS_CT_PREROUTING_HOOK'
+nftables_nflog_chain = 'VYOS_PREROUTING_HOOK'
egress_nftables_nflog_table = 'inet mangle'
egress_nftables_nflog_chain = 'FORWARD'
diff --git a/src/conf_mode/high-availability.py b/src/conf_mode/high-availability.py
index 70f43ab52..b3b27b14e 100755
--- a/src/conf_mode/high-availability.py
+++ b/src/conf_mode/high-availability.py
@@ -59,7 +59,7 @@ def get_config(config=None):
if conf.exists(conntrack_path):
ha['conntrack_sync_group'] = conf.return_value(conntrack_path)
- if leaf_node_changed(conf, base + ['vrrp', 'disable-snmp']):
+ if leaf_node_changed(conf, base + ['vrrp', 'snmp']):
ha.update({'restart_required': {}})
return ha
diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py
index 1d0feb56f..bdeb44837 100755
--- a/src/conf_mode/interfaces-openvpn.py
+++ b/src/conf_mode/interfaces-openvpn.py
@@ -30,6 +30,7 @@ from netifaces import interfaces
from secrets import SystemRandom
from shutil import rmtree
+from vyos.base import DeprecationWarning
from vyos.config import Config
from vyos.configdict import get_interface_dict
from vyos.configdict import is_node_changed
@@ -165,6 +166,11 @@ def verify_pki(openvpn):
if shared_secret_key not in pki['openvpn']['shared_secret']:
raise ConfigError(f'Invalid shared-secret on openvpn interface {interface}')
+ # If PSK settings are correct, warn about its deprecation
+ DeprecationWarning("OpenVPN shared-secret support will be removed in future VyOS versions.\n\
+ Please migrate your site-to-site tunnels to TLS.\n\
+ You can use self-signed certificates with peer fingerprint verification, consult the documentation for details.")
+
if tls:
if (mode in ['server', 'client']) and ('ca_certificate' not in tls):
raise ConfigError(f'Must specify "tls ca-certificate" on openvpn interface {interface},\
@@ -344,9 +350,6 @@ def verify(openvpn):
if v6_subnets > 1:
raise ConfigError('Cannot specify more than 1 IPv6 server subnet')
- if v6_subnets > 0 and v4_subnets == 0:
- raise ConfigError('IPv6 server requires an IPv4 server subnet')
-
for subnet in tmp:
if is_ipv4(subnet):
subnet = IPv4Network(subnet)
@@ -388,6 +391,10 @@ def verify(openvpn):
for v4PoolNet in v4PoolNets:
if IPv4Address(client['ip'][0]) in v4PoolNet:
print(f'Warning: Client "{client["name"]}" IP {client["ip"][0]} is in server IP pool, it is not reserved for this client.')
+ # configuring a client_ip_pool will set 'server ... nopool' which is currently incompatible with 'server-ipv6' (probably to be fixed upstream)
+ for subnet in (dict_search('server.subnet', openvpn) or []):
+ if is_ipv6(subnet):
+ raise ConfigError(f'Setting client-ip-pool is incompatible having an IPv6 server subnet.')
for subnet in (dict_search('server.subnet', openvpn) or []):
if is_ipv6(subnet):
diff --git a/src/conf_mode/load-balancing-wan.py b/src/conf_mode/load-balancing-wan.py
index ad9c80d72..5da0b906b 100755
--- a/src/conf_mode/load-balancing-wan.py
+++ b/src/conf_mode/load-balancing-wan.py
@@ -21,6 +21,7 @@ from shutil import rmtree
from vyos.base import Warning
from vyos.config import Config
+from vyos.configdep import set_dependents, call_dependents
from vyos.utils.process import cmd
from vyos.template import render
from vyos import ConfigError
@@ -49,6 +50,8 @@ def get_config(config=None):
if lb.from_defaults(['rule', rule, 'limit']):
del lb['rule'][rule]['limit']
+ set_dependents('conntrack', conf)
+
return lb
@@ -132,6 +135,8 @@ def apply(lb):
cmd('sudo sysctl -w net.netfilter.nf_conntrack_acct=1')
cmd(f'systemctl restart {systemd_service}')
+ call_dependents()
+
return None
diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py
index 08e96f10b..52a7a71fd 100755
--- a/src/conf_mode/nat.py
+++ b/src/conf_mode/nat.py
@@ -18,13 +18,12 @@ import jmespath
import json
import os
-from distutils.version import LooseVersion
-from platform import release as kernel_version
from sys import exit
from netifaces import interfaces
from vyos.base import Warning
from vyos.config import Config
+from vyos.configdep import set_dependents, call_dependents
from vyos.template import render
from vyos.template import is_ip_network
from vyos.utils.kernel import check_kmod
@@ -38,10 +37,7 @@ from vyos import ConfigError
from vyos import airbag
airbag.enable()
-if LooseVersion(kernel_version()) > LooseVersion('5.1'):
- k_mod = ['nft_nat', 'nft_chain_nat']
-else:
- k_mod = ['nft_nat', 'nft_chain_nat_ipv4']
+k_mod = ['nft_nat', 'nft_chain_nat']
nftables_nat_config = '/run/nftables_nat.conf'
nftables_static_nat_conf = '/run/nftables_static-nat-rules.nft'
@@ -53,18 +49,27 @@ valid_groups = [
'port_group'
]
-def get_handler(json, chain, target):
- """ Get nftable rule handler number of given chain/target combination.
- Handler is required when adding NAT/Conntrack helper targets """
- for x in json:
- if x['chain'] != chain:
- continue
- if x['target'] != target:
- continue
- return x['handle']
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
- return None
+ base = ['nat']
+ nat = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True,
+ with_recursive_defaults=True)
+
+ set_dependents('conntrack', conf)
+
+ if not conf.exists(base):
+ nat['deleted'] = ''
+ return nat
+
+ nat['firewall_group'] = conf.get_config_dict(['firewall', 'group'], key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True)
+ return nat
def verify_rule(config, err_msg, groups_dict):
""" Common verify steps used for both source and destination NAT """
@@ -136,70 +141,18 @@ def verify_rule(config, err_msg, groups_dict):
if count != 100:
Warning(f'Sum of weight for nat load balance rule is not 100. You may get unexpected behaviour')
-def get_config(config=None):
- if config:
- conf = config
- else:
- conf = Config()
-
- base = ['nat']
- nat = conf.get_config_dict(base, key_mangling=('-', '_'),
- get_first_key=True,
- with_recursive_defaults=True)
-
- # read in current nftable (once) for further processing
- tmp = cmd('nft -j list table raw')
- nftable_json = json.loads(tmp)
-
- # condense the full JSON table into a list with only relevand informations
- pattern = 'nftables[?rule].rule[?expr[].jump].{chain: chain, handle: handle, target: expr[].jump.target | [0]}'
- condensed_json = jmespath.search(pattern, nftable_json)
-
- if not conf.exists(base):
- if get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_HELPER'):
- nat['helper_functions'] = 'remove'
-
- # Retrieve current table handler positions
- nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_HELPER')
- nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK')
- nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYOS_CT_HELPER')
- nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'NAT_CONNTRACK')
- nat['deleted'] = ''
- return nat
-
- nat['firewall_group'] = conf.get_config_dict(['firewall', 'group'], key_mangling=('-', '_'), get_first_key=True,
- no_tag_node_value_mangle=True)
-
- # check if NAT connection tracking helpers need to be set up - this has to
- # be done only once
- if not get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK'):
- nat['helper_functions'] = 'add'
-
- # Retrieve current table handler positions
- nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_IGNORE')
- nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_PREROUTING_HOOK')
- nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYOS_CT_IGNORE')
- nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'VYOS_CT_OUTPUT_HOOK')
-
- return nat
-
def verify(nat):
if not nat or 'deleted' in nat:
# no need to verify the CLI as NAT is going to be deactivated
return None
- if 'helper_functions' in nat:
- if not (nat['pre_ct_ignore'] or nat['pre_ct_conntrack'] or nat['out_ct_ignore'] or nat['out_ct_conntrack']):
- raise Exception('could not determine nftable ruleset handlers')
-
if dict_search('source.rule', nat):
for rule, config in dict_search('source.rule', nat).items():
err_msg = f'Source NAT configuration error in rule {rule}:'
- if 'outbound_interface' not in config:
- raise ConfigError(f'{err_msg} outbound-interface not specified')
- if config['outbound_interface'] not in 'any' and config['outbound_interface'] not in interfaces():
- Warning(f'rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system')
+ if 'outbound_interface' in config:
+ if config['outbound_interface'] not in 'any' and config['outbound_interface'] not in interfaces():
+ Warning(f'rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system')
if not dict_search('translation.address', config) and not dict_search('translation.port', config):
if 'exclude' not in config and 'backend' not in config['load_balance']:
@@ -218,11 +171,9 @@ def verify(nat):
for rule, config in dict_search('destination.rule', nat).items():
err_msg = f'Destination NAT configuration error in rule {rule}:'
- if 'inbound_interface' not in config:
- raise ConfigError(f'{err_msg}\n' \
- 'inbound-interface not specified')
- elif config['inbound_interface'] not in 'any' and config['inbound_interface'] not in interfaces():
- Warning(f'rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system')
+ if 'inbound_interface' in config:
+ if config['inbound_interface'] not in 'any' and config['inbound_interface'] not in interfaces():
+ Warning(f'rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system')
if not dict_search('translation.address', config) and not dict_search('translation.port', config) and 'redirect' not in config['translation']:
if 'exclude' not in config and 'backend' not in config['load_balance']:
@@ -270,6 +221,8 @@ def apply(nat):
os.unlink(nftables_nat_config)
os.unlink(nftables_static_nat_conf)
+ call_dependents()
+
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py
index 4c12618bc..46d796bc8 100755
--- a/src/conf_mode/nat66.py
+++ b/src/conf_mode/nat66.py
@@ -23,6 +23,7 @@ from netifaces import interfaces
from vyos.base import Warning
from vyos.config import Config
+from vyos.configdep import set_dependents, call_dependents
from vyos.template import render
from vyos.utils.process import cmd
from vyos.utils.kernel import check_kmod
@@ -37,18 +38,6 @@ k_mod = ['nft_nat', 'nft_chain_nat']
nftables_nat66_config = '/run/nftables_nat66.nft'
ndppd_config = '/run/ndppd/ndppd.conf'
-def get_handler(json, chain, target):
- """ Get nftable rule handler number of given chain/target combination.
- Handler is required when adding NAT66/Conntrack helper targets """
- for x in json:
- if x['chain'] != chain:
- continue
- if x['target'] != target:
- continue
- return x['handle']
-
- return None
-
def get_config(config=None):
if config:
conf = config
@@ -58,35 +47,10 @@ def get_config(config=None):
base = ['nat66']
nat = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
- # read in current nftable (once) for further processing
- tmp = cmd('nft -j list table ip6 raw')
- nftable_json = json.loads(tmp)
-
- # condense the full JSON table into a list with only relevand informations
- pattern = 'nftables[?rule].rule[?expr[].jump].{chain: chain, handle: handle, target: expr[].jump.target | [0]}'
- condensed_json = jmespath.search(pattern, nftable_json)
+ set_dependents('conntrack', conf)
if not conf.exists(base):
- nat['helper_functions'] = 'remove'
- nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_HELPER')
- nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK')
- nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYOS_CT_HELPER')
- nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'NAT_CONNTRACK')
nat['deleted'] = ''
- return nat
-
- # check if NAT66 connection tracking helpers need to be set up - this has to
- # be done only once
- if not get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK'):
- nat['helper_functions'] = 'add'
-
- # Retrieve current table handler positions
- nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_IGNORE')
- nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_PREROUTING_HOOK')
- nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYOS_CT_IGNORE')
- nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'VYOS_CT_OUTPUT_HOOK')
- else:
- nat['helper_functions'] = 'has'
return nat
@@ -95,10 +59,6 @@ def verify(nat):
# no need to verify the CLI as NAT66 is going to be deactivated
return None
- if 'helper_functions' in nat and nat['helper_functions'] != 'has':
- if not (nat['pre_ct_conntrack'] or nat['out_ct_conntrack']):
- raise Exception('could not determine nftable ruleset handlers')
-
if dict_search('source.rule', nat):
for rule, config in dict_search('source.rule', nat).items():
err_msg = f'Source NAT66 configuration error in rule {rule}:'
@@ -155,6 +115,8 @@ def apply(nat):
else:
cmd('systemctl restart ndppd')
+ call_dependents()
+
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/policy-local-route.py b/src/conf_mode/policy-local-route.py
index 79526f82a..d3c307cdc 100755
--- a/src/conf_mode/policy-local-route.py
+++ b/src/conf_mode/policy-local-route.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020-2021 VyOS maintainers and contributors
+# Copyright (C) 2020-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
@@ -16,6 +16,7 @@
import os
+from itertools import product
from sys import exit
from netifaces import interfaces
@@ -54,6 +55,7 @@ def get_config(config=None):
fwmk = leaf_node_changed(conf, base_rule + [rule, 'fwmark'])
iif = leaf_node_changed(conf, base_rule + [rule, 'inbound-interface'])
dst = leaf_node_changed(conf, base_rule + [rule, 'destination'])
+ proto = leaf_node_changed(conf, base_rule + [rule, 'protocol'])
rule_def = {}
if src:
rule_def = dict_merge({'source' : src}, rule_def)
@@ -63,6 +65,8 @@ def get_config(config=None):
rule_def = dict_merge({'inbound_interface' : iif}, rule_def)
if dst:
rule_def = dict_merge({'destination' : dst}, rule_def)
+ if proto:
+ rule_def = dict_merge({'protocol' : proto}, rule_def)
dict = dict_merge({dict_id : {rule : rule_def}}, dict)
pbr.update(dict)
@@ -78,6 +82,7 @@ def get_config(config=None):
fwmk = leaf_node_changed(conf, base_rule + [rule, 'fwmark'])
iif = leaf_node_changed(conf, base_rule + [rule, 'inbound-interface'])
dst = leaf_node_changed(conf, base_rule + [rule, 'destination'])
+ proto = leaf_node_changed(conf, base_rule + [rule, 'protocol'])
# keep track of changes in configuration
# otherwise we might remove an existing node although nothing else has changed
changed = False
@@ -119,6 +124,13 @@ def get_config(config=None):
changed = True
if len(dst) > 0:
rule_def = dict_merge({'destination' : dst}, rule_def)
+ if proto is None:
+ if 'protocol' in rule_config:
+ rule_def = dict_merge({'protocol': rule_config['protocol']}, rule_def)
+ else:
+ changed = True
+ if len(proto) > 0:
+ rule_def = dict_merge({'protocol' : proto}, rule_def)
if changed:
dict = dict_merge({dict_id : {rule : rule_def}}, dict)
pbr.update(dict)
@@ -137,18 +149,22 @@ def verify(pbr):
pbr_route = pbr[route]
if 'rule' in pbr_route:
for rule in pbr_route['rule']:
- if 'source' not in pbr_route['rule'][rule] \
- and 'destination' not in pbr_route['rule'][rule] \
- and 'fwmark' not in pbr_route['rule'][rule] \
- and 'inbound_interface' not in pbr_route['rule'][rule]:
- raise ConfigError('Source or destination address or fwmark or inbound-interface is required!')
- else:
- if 'set' not in pbr_route['rule'][rule] or 'table' not in pbr_route['rule'][rule]['set']:
- raise ConfigError('Table set is required!')
- if 'inbound_interface' in pbr_route['rule'][rule]:
- interface = pbr_route['rule'][rule]['inbound_interface']
- if interface not in interfaces():
- raise ConfigError(f'Interface "{interface}" does not exist')
+ if (
+ 'source' not in pbr_route['rule'][rule] and
+ 'destination' not in pbr_route['rule'][rule] and
+ 'fwmark' not in pbr_route['rule'][rule] and
+ 'inbound_interface' not in pbr_route['rule'][rule] and
+ 'protocol' not in pbr_route['rule'][rule]
+ ):
+ raise ConfigError('Source or destination address or fwmark or inbound-interface or protocol is required!')
+
+ if 'set' not in pbr_route['rule'][rule] or 'table' not in pbr_route['rule'][rule]['set']:
+ raise ConfigError('Table set is required!')
+
+ if 'inbound_interface' in pbr_route['rule'][rule]:
+ interface = pbr_route['rule'][rule]['inbound_interface']
+ if interface not in interfaces():
+ raise ConfigError(f'Interface "{interface}" does not exist')
return None
@@ -166,20 +182,22 @@ def apply(pbr):
for rule_rm in ['rule_remove', 'rule6_remove']:
if rule_rm in pbr:
v6 = " -6" if rule_rm == 'rule6_remove' else ""
+
for rule, rule_config in pbr[rule_rm].items():
- rule_config['source'] = rule_config['source'] if 'source' in rule_config else ['']
- for src in rule_config['source']:
+ source = rule_config.get('source', [''])
+ destination = rule_config.get('destination', [''])
+ fwmark = rule_config.get('fwmark', [''])
+ inbound_interface = rule_config.get('inbound_interface', [''])
+ protocol = rule_config.get('protocol', [''])
+
+ for src, dst, fwmk, iif, proto in product(source, destination, fwmark, inbound_interface, protocol):
f_src = '' if src == '' else f' from {src} '
- rule_config['destination'] = rule_config['destination'] if 'destination' in rule_config else ['']
- for dst in rule_config['destination']:
- f_dst = '' if dst == '' else f' to {dst} '
- rule_config['fwmark'] = rule_config['fwmark'] if 'fwmark' in rule_config else ['']
- for fwmk in rule_config['fwmark']:
- f_fwmk = '' if fwmk == '' else f' fwmark {fwmk} '
- rule_config['inbound_interface'] = rule_config['inbound_interface'] if 'inbound_interface' in rule_config else ['']
- for iif in rule_config['inbound_interface']:
- f_iif = '' if iif == '' else f' iif {iif} '
- call(f'ip{v6} rule del prio {rule} {f_src}{f_dst}{f_fwmk}{f_iif}')
+ f_dst = '' if dst == '' else f' to {dst} '
+ f_fwmk = '' if fwmk == '' else f' fwmark {fwmk} '
+ f_iif = '' if iif == '' else f' iif {iif} '
+ f_proto = '' if proto == '' else f' ipproto {proto} '
+
+ call(f'ip{v6} rule del prio {rule} {f_src}{f_dst}{f_fwmk}{f_iif}')
# Generate new config
for route in ['local_route', 'local_route6']:
@@ -187,27 +205,26 @@ def apply(pbr):
continue
v6 = " -6" if route == 'local_route6' else ""
-
pbr_route = pbr[route]
+
if 'rule' in pbr_route:
for rule, rule_config in pbr_route['rule'].items():
- table = rule_config['set']['table']
-
- rule_config['source'] = rule_config['source'] if 'source' in rule_config else ['all']
- for src in rule_config['source'] or ['all']:
- f_src = '' if src == '' else f' from {src} '
- rule_config['destination'] = rule_config['destination'] if 'destination' in rule_config else ['all']
- for dst in rule_config['destination']:
- f_dst = '' if dst == '' else f' to {dst} '
- f_fwmk = ''
- if 'fwmark' in rule_config:
- fwmk = rule_config['fwmark']
- f_fwmk = f' fwmark {fwmk} '
- f_iif = ''
- if 'inbound_interface' in rule_config:
- iif = rule_config['inbound_interface']
- f_iif = f' iif {iif} '
- call(f'ip{v6} rule add prio {rule} {f_src}{f_dst}{f_fwmk}{f_iif} lookup {table}')
+ table = rule_config['set'].get('table', '')
+ source = rule_config.get('source', ['all'])
+ destination = rule_config.get('destination', ['all'])
+ fwmark = rule_config.get('fwmark', '')
+ inbound_interface = rule_config.get('inbound_interface', '')
+ protocol = rule_config.get('protocol', '')
+
+ for src in source:
+ f_src = f' from {src} ' if src else ''
+ for dst in destination:
+ f_dst = f' to {dst} ' if dst else ''
+ f_fwmk = f' fwmark {fwmark} ' if fwmark else ''
+ f_iif = f' iif {inbound_interface} ' if inbound_interface else ''
+ f_proto = f' ipproto {protocol} ' if protocol else ''
+
+ call(f'ip{v6} rule add prio {rule}{f_src}{f_dst}{f_proto}{f_fwmk}{f_iif} lookup {table}')
return None
diff --git a/src/conf_mode/service_aws_glb.py b/src/conf_mode/service_aws_glb.py
new file mode 100755
index 000000000..d1ed5a07b
--- /dev/null
+++ b/src/conf_mode/service_aws_glb.py
@@ -0,0 +1,76 @@
+#!/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/>.
+
+from sys import exit
+
+from vyos.config import Config
+from vyos.template import render
+from vyos.utils.process import call
+from vyos import ConfigError
+from vyos import airbag
+airbag.enable()
+
+systemd_service = 'aws-gwlbtun.service'
+systemd_override = '/run/systemd/system/aws-gwlbtun.service.d/10-override.conf'
+
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+ base = ['service', 'aws', 'glb']
+ if not conf.exists(base):
+ return None
+
+ glb = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
+
+ return glb
+
+
+def verify(glb):
+ # bail out early - looks like removal from running config
+ if not glb:
+ return None
+
+
+def generate(glb):
+ if not glb:
+ return None
+
+ render(systemd_override, 'aws/override_aws_gwlbtun.conf.j2', glb)
+
+
+def apply(glb):
+ call('systemctl daemon-reload')
+ if not glb:
+ call(f'systemctl stop {systemd_service}')
+ else:
+ call(f'systemctl restart {systemd_service}')
+ 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/service_mdns-repeater.py b/src/conf_mode/service_mdns-repeater.py
index a2c90b537..6909731ff 100755
--- a/src/conf_mode/service_mdns-repeater.py
+++ b/src/conf_mode/service_mdns-repeater.py
@@ -18,7 +18,7 @@ import os
from json import loads
from sys import exit
-from netifaces import ifaddresses, interfaces, AF_INET
+from netifaces import ifaddresses, interfaces, AF_INET, AF_INET6
from vyos.config import Config
from vyos.ifconfig.vrrp import VRRP
@@ -36,18 +36,22 @@ def get_config(config=None):
conf = config
else:
conf = Config()
+
base = ['service', 'mdns', 'repeater']
- mdns = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+ if not conf.exists(base):
+ return None
+
+ mdns = conf.get_config_dict(base, key_mangling=('-', '_'),
+ no_tag_node_value_mangle=True,
+ get_first_key=True,
+ with_recursive_defaults=True)
if mdns:
mdns['vrrp_exists'] = conf.exists('high-availability vrrp')
return mdns
def verify(mdns):
- if not mdns:
- return None
-
- if 'disable' in mdns:
+ if not mdns or 'disable' in mdns:
return None
# We need at least two interfaces to repeat mDNS advertisments
@@ -60,10 +64,14 @@ def verify(mdns):
if interface not in interfaces():
raise ConfigError(f'Interface "{interface}" does not exist!')
- if AF_INET not in ifaddresses(interface):
+ if mdns['ip_version'] in ['ipv4', 'both'] and AF_INET not in ifaddresses(interface):
raise ConfigError('mDNS repeater requires an IPv4 address to be '
f'configured on interface "{interface}"')
+ if mdns['ip_version'] in ['ipv6', 'both'] and AF_INET6 not in ifaddresses(interface):
+ raise ConfigError('mDNS repeater requires an IPv6 address to be '
+ f'configured on interface "{interface}"')
+
return None
# Get VRRP states from interfaces, returns only interfaces where state is MASTER
@@ -92,7 +100,7 @@ def generate(mdns):
if len(mdns['interface']) < 2:
return None
- render(config_file, 'mdns-repeater/avahi-daemon.j2', mdns)
+ render(config_file, 'mdns-repeater/avahi-daemon.conf.j2', mdns)
return None
def apply(mdns):
diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py
index 7882f8510..d2ed5414f 100755
--- a/src/conf_mode/snmp.py
+++ b/src/conf_mode/snmp.py
@@ -253,9 +253,8 @@ def apply(snmp):
# Enable AgentX in FRR
# This should be done for each daemon individually because common command
# works only if all the daemons started with SNMP support
- frr_daemons_list = [
- 'bgpd', 'ospf6d', 'ospfd', 'ripd', 'ripngd', 'isisd', 'ldpd', 'zebra'
- ]
+ # Following daemons from FRR 9.0/stable have SNMP module compiled in VyOS
+ frr_daemons_list = ['zebra', 'bgpd', 'ospf6d', 'ospfd', 'ripd', 'isisd', 'ldpd']
for frr_daemon in frr_daemons_list:
call(
f'vtysh -c "configure terminal" -d {frr_daemon} -c "agentx" >/dev/null'
diff --git a/src/conf_mode/system-ip.py b/src/conf_mode/system-ip.py
index 5e4e5ec28..7612e2c0d 100755
--- a/src/conf_mode/system-ip.py
+++ b/src/conf_mode/system-ip.py
@@ -20,10 +20,12 @@ from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.configverify import verify_route_map
from vyos.template import render_to_string
-from vyos.utils.process import call
from vyos.utils.dict import dict_search
from vyos.utils.file import write_file
+from vyos.utils.process import call
+from vyos.utils.process import is_systemd_service_active
from vyos.utils.system import sysctl_write
+
from vyos import ConfigError
from vyos import frr
from vyos import airbag
@@ -115,16 +117,20 @@ def apply(opt):
value = '48' if (tmp is None) else tmp
sysctl_write('net.ipv4.tcp_mtu_probe_floor', value)
- zebra_daemon = 'zebra'
- # Save original configuration prior to starting any commit actions
- frr_cfg = frr.FRRConfig()
-
- # The route-map used for the FIB (zebra) is part of the zebra daemon
- frr_cfg.load_configuration(zebra_daemon)
- frr_cfg.modify_section(r'ip protocol \w+ route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)')
- if 'frr_zebra_config' in opt:
- frr_cfg.add_before(frr.default_add_before, opt['frr_zebra_config'])
- frr_cfg.commit_configuration(zebra_daemon)
+ # During startup of vyos-router that brings up FRR, the service is not yet
+ # running when this script is called first. Skip this part and wait for initial
+ # commit of the configuration to trigger this statement
+ if is_systemd_service_active('frr.service'):
+ zebra_daemon = 'zebra'
+ # Save original configuration prior to starting any commit actions
+ frr_cfg = frr.FRRConfig()
+
+ # The route-map used for the FIB (zebra) is part of the zebra daemon
+ frr_cfg.load_configuration(zebra_daemon)
+ frr_cfg.modify_section(r'ip protocol \w+ route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)')
+ if 'frr_zebra_config' in opt:
+ frr_cfg.add_before(frr.default_add_before, opt['frr_zebra_config'])
+ frr_cfg.commit_configuration(zebra_daemon)
if __name__ == '__main__':
try:
diff --git a/src/conf_mode/system-ipv6.py b/src/conf_mode/system-ipv6.py
index e40ed38e2..90a1a8087 100755
--- a/src/conf_mode/system-ipv6.py
+++ b/src/conf_mode/system-ipv6.py
@@ -22,8 +22,9 @@ from vyos.configdict import dict_merge
from vyos.configverify import verify_route_map
from vyos.template import render_to_string
from vyos.utils.dict import dict_search
-from vyos.utils.system import sysctl_write
from vyos.utils.file import write_file
+from vyos.utils.process import is_systemd_service_active
+from vyos.utils.system import sysctl_write
from vyos import ConfigError
from vyos import frr
from vyos import airbag
@@ -93,16 +94,20 @@ def apply(opt):
if name == 'accept_dad':
write_file(os.path.join(root, name), value)
- zebra_daemon = 'zebra'
- # Save original configuration prior to starting any commit actions
- frr_cfg = frr.FRRConfig()
+ # During startup of vyos-router that brings up FRR, the service is not yet
+ # running when this script is called first. Skip this part and wait for initial
+ # commit of the configuration to trigger this statement
+ if is_systemd_service_active('frr.service'):
+ zebra_daemon = 'zebra'
+ # Save original configuration prior to starting any commit actions
+ frr_cfg = frr.FRRConfig()
- # The route-map used for the FIB (zebra) is part of the zebra daemon
- frr_cfg.load_configuration(zebra_daemon)
- frr_cfg.modify_section(r'ipv6 protocol \w+ route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)')
- if 'frr_zebra_config' in opt:
- frr_cfg.add_before(frr.default_add_before, opt['frr_zebra_config'])
- frr_cfg.commit_configuration(zebra_daemon)
+ # The route-map used for the FIB (zebra) is part of the zebra daemon
+ frr_cfg.load_configuration(zebra_daemon)
+ frr_cfg.modify_section(r'ipv6 protocol \w+ route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)')
+ if 'frr_zebra_config' in opt:
+ frr_cfg.add_before(frr.default_add_before, opt['frr_zebra_config'])
+ frr_cfg.commit_configuration(zebra_daemon)
if __name__ == '__main__':
try:
diff --git a/src/conf_mode/system_frr.py b/src/conf_mode/system_frr.py
index fb252238a..6727b63c2 100755
--- a/src/conf_mode/system_frr.py
+++ b/src/conf_mode/system_frr.py
@@ -18,21 +18,20 @@ from pathlib import Path
from sys import exit
from vyos import ConfigError
-from vyos import airbag
+from vyos.base import Warning
from vyos.config import Config
from vyos.logger import syslog
from vyos.template import render_to_string
+from vyos.utils.boot import boot_configuration_complete
from vyos.utils.file import read_file
from vyos.utils.file import write_file
-from vyos.utils.process import run
+from vyos.utils.process import call
+
+from vyos import airbag
airbag.enable()
# path to daemons config and config status files
config_file = '/etc/frr/daemons'
-vyos_status_file = '/tmp/vyos-config-status'
-# path to watchfrr for FRR control
-watchfrr = '/usr/lib/frr/watchfrr.sh'
-
def get_config(config=None):
if config:
@@ -45,12 +44,10 @@ def get_config(config=None):
return frr_config
-
def verify(frr_config):
# Nothing to verify here
pass
-
def generate(frr_config):
# read daemons config file
daemons_config_current = read_file(config_file)
@@ -62,25 +59,19 @@ def generate(frr_config):
write_file(config_file, daemons_config_new)
frr_config['config_file_changed'] = True
-
def apply(frr_config):
- # check if this is initial commit during boot or intiated by CLI
- # if the file exists, this must be CLI commit
- commit_type_cli = Path(vyos_status_file).exists()
# display warning to user
- if commit_type_cli and frr_config.get('config_file_changed'):
+ if boot_configuration_complete() and frr_config.get('config_file_changed'):
# Since FRR restart is not safe thing, better to give
# control over this to users
- print('''
- You need to reboot a router (preferred) or restart FRR
- to apply changes in modules settings
- ''')
- # restart FRR automatically. DUring the initial boot this should be
- # safe in most cases
- if not commit_type_cli and frr_config.get('config_file_changed'):
- syslog.warning('Restarting FRR to apply changes in modules')
- run(f'{watchfrr} restart')
+ Warning('You need to reboot the router (preferred) or restart '\
+ 'FRR to apply changes in modules settings')
+ # restart FRR automatically
+ # During initial boot this should be safe in most cases
+ if not boot_configuration_complete() and frr_config.get('config_file_changed'):
+ syslog.warning('Restarting FRR to apply changes in modules')
+ call(f'systemctl restart frr.service')
if __name__ == '__main__':
try:
diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py
index fa271cbdb..9e9385ddb 100755
--- a/src/conf_mode/vpn_ipsec.py
+++ b/src/conf_mode/vpn_ipsec.py
@@ -29,7 +29,10 @@ from vyos.configdict import leaf_node_changed
from vyos.configverify import verify_interface_exists
from vyos.defaults import directories
from vyos.ifconfig import Interface
+from vyos.pki import encode_certificate
from vyos.pki import encode_public_key
+from vyos.pki import find_chain
+from vyos.pki import load_certificate
from vyos.pki import load_private_key
from vyos.pki import wrap_certificate
from vyos.pki import wrap_crl
@@ -431,15 +434,23 @@ def generate_pki_files_x509(pki, x509_conf):
ca_cert_name = x509_conf['ca_certificate']
ca_cert_data = dict_search_args(pki, 'ca', ca_cert_name, 'certificate')
ca_cert_crls = dict_search_args(pki, 'ca', ca_cert_name, 'crl') or []
+ ca_index = 1
crl_index = 1
+ ca_cert = load_certificate(ca_cert_data)
+ pki_ca_certs = [load_certificate(ca['certificate']) for ca in pki['ca'].values()]
+
+ ca_cert_chain = find_chain(ca_cert, pki_ca_certs)
+
cert_name = x509_conf['certificate']
cert_data = dict_search_args(pki, 'certificate', cert_name, 'certificate')
key_data = dict_search_args(pki, 'certificate', cert_name, 'private', 'key')
protected = 'passphrase' in x509_conf
- with open(os.path.join(CA_PATH, f'{ca_cert_name}.pem'), 'w') as f:
- f.write(wrap_certificate(ca_cert_data))
+ for ca_cert_obj in ca_cert_chain:
+ with open(os.path.join(CA_PATH, f'{ca_cert_name}_{ca_index}.pem'), 'w') as f:
+ f.write(encode_certificate(ca_cert_obj))
+ ca_index += 1
for crl in ca_cert_crls:
with open(os.path.join(CRL_PATH, f'{ca_cert_name}_{crl_index}.pem'), 'w') as f: