summaryrefslogtreecommitdiff
path: root/src/conf_mode
diff options
context:
space:
mode:
Diffstat (limited to 'src/conf_mode')
-rwxr-xr-xsrc/conf_mode/container.py6
-rwxr-xr-xsrc/conf_mode/dns_forwarding.py22
-rwxr-xr-xsrc/conf_mode/high-availability.py13
-rwxr-xr-xsrc/conf_mode/load-balancing-wan.py132
-rwxr-xr-xsrc/conf_mode/protocols_bgp.py38
-rwxr-xr-xsrc/conf_mode/protocols_isis.py2
-rwxr-xr-xsrc/conf_mode/protocols_ospf.py2
-rwxr-xr-xsrc/conf_mode/protocols_ospfv3.py2
-rwxr-xr-xsrc/conf_mode/service_pppoe-server.py5
-rwxr-xr-xsrc/conf_mode/system-syslog.py324
-rwxr-xr-xsrc/conf_mode/vpn_l2tp.py6
-rwxr-xr-xsrc/conf_mode/vpn_pptp.py4
-rwxr-xr-xsrc/conf_mode/vrf.py14
-rw-r--r--src/conf_mode/vrf_vni.py104
14 files changed, 406 insertions, 268 deletions
diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py
index 4b7ab3444..aceb27fb0 100755
--- a/src/conf_mode/container.py
+++ b/src/conf_mode/container.py
@@ -376,11 +376,11 @@ def generate(container):
'name': network,
'id' : sha256(f'{network}'.encode()).hexdigest(),
'driver': 'bridge',
- 'network_interface': f'podman-{network}',
+ 'network_interface': f'pod-{network}',
'subnets': [],
'ipv6_enabled': False,
'internal': False,
- 'dns_enabled': False,
+ 'dns_enabled': True,
'ipam_options': {
'driver': 'host-local'
}
@@ -479,7 +479,7 @@ def apply(container):
# the network interface in advance
if 'network' in container:
for network, network_config in container['network'].items():
- network_name = f'podman-{network}'
+ network_name = f'pod-{network}'
# T5147: Networks are started only as soon as there is a consumer.
# If only a network is created in the first place, no need to assign
# it to a VRF as there's no consumer, yet.
diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py
index 36c1098fe..0d86c6a52 100755
--- a/src/conf_mode/dns_forwarding.py
+++ b/src/conf_mode/dns_forwarding.py
@@ -99,7 +99,7 @@ def get_config(config=None):
recorddata = zonedata['records']
- for rtype in [ 'a', 'aaaa', 'cname', 'mx', 'ptr', 'txt', 'spf', 'srv', 'naptr' ]:
+ for rtype in [ 'a', 'aaaa', 'cname', 'mx', 'ns', 'ptr', 'txt', 'spf', 'srv', 'naptr' ]:
if rtype not in recorddata:
continue
for subnode in recorddata[rtype]:
@@ -113,7 +113,7 @@ def get_config(config=None):
rdata = dict_merge(rdefaults, rdata)
if not 'address' in rdata:
- dns['authoritative_zone_errors'].append('{}.{}: at least one address is required'.format(subnode, node))
+ dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one address is required')
continue
if subnode == 'any':
@@ -126,12 +126,12 @@ def get_config(config=None):
'ttl': rdata['ttl'],
'value': address
})
- elif rtype in ['cname', 'ptr']:
+ elif rtype in ['cname', 'ptr', 'ns']:
rdefaults = defaults(base + ['authoritative-domain', 'records', rtype]) # T2665
rdata = dict_merge(rdefaults, rdata)
if not 'target' in rdata:
- dns['authoritative_zone_errors'].append('{}.{}: target is required'.format(subnode, node))
+ dns['authoritative_zone_errors'].append(f'{subnode}.{node}: target is required')
continue
zone['records'].append({
@@ -146,7 +146,7 @@ def get_config(config=None):
rdata = dict_merge(rdefaults, rdata)
if not 'server' in rdata:
- dns['authoritative_zone_errors'].append('{}.{}: at least one server is required'.format(subnode, node))
+ dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one server is required')
continue
for servername in rdata['server']:
@@ -164,7 +164,7 @@ def get_config(config=None):
rdata = dict_merge(rdefaults, rdata)
if not 'value' in rdata:
- dns['authoritative_zone_errors'].append('{}.{}: at least one value is required'.format(subnode, node))
+ dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one value is required')
continue
for value in rdata['value']:
@@ -179,7 +179,7 @@ def get_config(config=None):
rdata = dict_merge(rdefaults, rdata)
if not 'value' in rdata:
- dns['authoritative_zone_errors'].append('{}.{}: value is required'.format(subnode, node))
+ dns['authoritative_zone_errors'].append(f'{subnode}.{node}: value is required')
continue
zone['records'].append({
@@ -194,7 +194,7 @@ def get_config(config=None):
rdata = dict_merge(rdefaults, rdata)
if not 'entry' in rdata:
- dns['authoritative_zone_errors'].append('{}.{}: at least one entry is required'.format(subnode, node))
+ dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one entry is required')
continue
for entryno in rdata['entry']:
@@ -203,11 +203,11 @@ def get_config(config=None):
entrydata = dict_merge(entrydefaults, entrydata)
if not 'hostname' in entrydata:
- dns['authoritative_zone_errors'].append('{}.{}: hostname is required for entry {}'.format(subnode, node, entryno))
+ dns['authoritative_zone_errors'].append(f'{subnode}.{node}: hostname is required for entry {entryno}')
continue
if not 'port' in entrydata:
- dns['authoritative_zone_errors'].append('{}.{}: port is required for entry {}'.format(subnode, node, entryno))
+ dns['authoritative_zone_errors'].append(f'{subnode}.{node}: port is required for entry {entryno}')
continue
zone['records'].append({
@@ -223,7 +223,7 @@ def get_config(config=None):
if not 'rule' in rdata:
- dns['authoritative_zone_errors'].append('{}.{}: at least one rule is required'.format(subnode, node))
+ dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one rule is required')
continue
for ruleno in rdata['rule']:
diff --git a/src/conf_mode/high-availability.py b/src/conf_mode/high-availability.py
index 79e407efd..7a63f5b4b 100755
--- a/src/conf_mode/high-availability.py
+++ b/src/conf_mode/high-availability.py
@@ -86,7 +86,7 @@ def get_config(config=None):
return ha
def verify(ha):
- if not ha:
+ if not ha or 'disable' in ha:
return None
used_vrid_if = []
@@ -106,6 +106,13 @@ def verify(ha):
if not {'password', 'type'} <= set(group_config['authentication']):
raise ConfigError(f'Authentication requires both type and passwortd to be set in VRRP group "{group}"')
+ if 'health_check' in group_config:
+ from vyos.utils.dict import check_mutually_exclusive_options
+ try:
+ check_mutually_exclusive_options(group_config["health_check"], ["script", "ping"], required=True)
+ except ValueError as e:
+ raise ConfigError(f'Health check config is incorrect in VRRP group "{group}": {e}')
+
# Keepalived doesn't allow mixing IPv4 and IPv6 in one group, so we mirror that restriction
# We also need to make sure VRID is not used twice on the same interface with the
# same address family.
@@ -175,7 +182,7 @@ def verify(ha):
def generate(ha):
- if not ha:
+ if not ha or 'disable' in ha:
return None
render(VRRP.location['config'], 'high-availability/keepalived.conf.j2', ha)
@@ -183,7 +190,7 @@ def generate(ha):
def apply(ha):
service_name = 'keepalived.service'
- if not ha:
+ if not ha or 'disable' in ha:
call(f'systemctl stop {service_name}')
return None
diff --git a/src/conf_mode/load-balancing-wan.py b/src/conf_mode/load-balancing-wan.py
index 11840249f..7086aaf8b 100755
--- a/src/conf_mode/load-balancing-wan.py
+++ b/src/conf_mode/load-balancing-wan.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2022 VyOS maintainers and contributors
+# 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
@@ -14,17 +14,25 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+import os
from sys import exit
+from shutil import rmtree
+from vyos.base import Warning
from vyos.config import Config
-from vyos.configdict import node_changed
-from vyos.util import call
+from vyos.configdict import dict_merge
+from vyos.util import cmd
+from vyos.template import render
+from vyos.xml import defaults
from vyos import ConfigError
-from pprint import pprint
from vyos import airbag
airbag.enable()
+load_balancing_dir = '/run/load-balance'
+load_balancing_conf_file = f'{load_balancing_dir}/wlb.conf'
+systemd_service = 'vyos-wan-load-balance.service'
+
def get_config(config=None):
if config:
@@ -33,27 +41,135 @@ def get_config(config=None):
conf = Config()
base = ['load-balancing', 'wan']
- lb = conf.get_config_dict(base, get_first_key=True,
- no_tag_node_value_mangle=True)
+ lb = conf.get_config_dict(base,
+ get_first_key=True,
+ key_mangling=('-', '_'),
+ no_tag_node_value_mangle=True)
+
+ # We have gathered the dict representation of the CLI, but there are default
+ # options which we need to update into the dictionary retrived.
+ default_values = defaults(base)
+ # lb base default values can not be merged here - remove and add them later
+ if 'interface_health' in default_values:
+ del default_values['interface_health']
+ if 'rule' in default_values:
+ del default_values['rule']
+ lb = dict_merge(default_values, lb)
+
+ if 'interface_health' in lb:
+ for iface in lb.get('interface_health'):
+ default_values_iface = defaults(base + ['interface-health'])
+ if 'test' in default_values_iface:
+ del default_values_iface['test']
+ lb['interface_health'][iface] = dict_merge(
+ default_values_iface, lb['interface_health'][iface])
+ if 'test' in lb['interface_health'][iface]:
+ for node_test in lb['interface_health'][iface]['test']:
+ default_values_test = defaults(base +
+ ['interface-health', 'test'])
+ lb['interface_health'][iface]['test'][node_test] = dict_merge(
+ default_values_test,
+ lb['interface_health'][iface]['test'][node_test])
+
+ if 'rule' in lb:
+ for rule in lb.get('rule'):
+ default_values_rule = defaults(base + ['rule'])
+ if 'interface' in default_values_rule:
+ del default_values_rule['interface']
+ lb['rule'][rule] = dict_merge(default_values_rule, lb['rule'][rule])
+ if not conf.exists(base + ['rule', rule, 'limit']):
+ del lb['rule'][rule]['limit']
+ if 'interface' in lb['rule'][rule]:
+ for iface in lb['rule'][rule]['interface']:
+ default_values_rule_iface = defaults(base + ['rule', 'interface'])
+ lb['rule'][rule]['interface'][iface] = dict_merge(default_values_rule_iface, lb['rule'][rule]['interface'][iface])
- pprint(lb)
return lb
+
def verify(lb):
- return None
+ if not lb:
+ return None
+
+ if 'interface_health' not in lb:
+ raise ConfigError(
+ 'A valid WAN load-balance configuration requires an interface with a nexthop!'
+ )
+
+ for interface, interface_config in lb['interface_health'].items():
+ if 'nexthop' not in interface_config:
+ raise ConfigError(
+ f'interface-health {interface} nexthop must be specified!')
+
+ if 'test' in interface_config:
+ for test_rule, test_config in interface_config['test'].items():
+ if 'type' in test_config:
+ if test_config['type'] == 'user-defined' and 'test_script' not in test_config:
+ raise ConfigError(
+ f'test {test_rule} script must be defined for test-script!'
+ )
+
+ if 'rule' not in lb:
+ Warning(
+ 'At least one rule with an (outbound) interface must be defined for WAN load balancing to be active!'
+ )
+ else:
+ for rule, rule_config in lb['rule'].items():
+ if 'inbound_interface' not in rule_config:
+ raise ConfigError(f'rule {rule} inbound-interface must be specified!')
+ if {'failover', 'exclude'} <= set(rule_config):
+ raise ConfigError(f'rule {rule} failover cannot be configured with exclude!')
+ if {'limit', 'exclude'} <= set(rule_config):
+ raise ConfigError(f'rule {rule} limit cannot be used with exclude!')
+ if 'interface' not in rule_config:
+ if 'exclude' not in rule_config:
+ Warning(
+ f'rule {rule} will be inactive because no (outbound) interfaces have been defined for this rule'
+ )
+ for direction in {'source', 'destination'}:
+ if direction in rule_config:
+ if 'protocol' in rule_config and 'port' in rule_config[
+ direction]:
+ if rule_config['protocol'] not in {'tcp', 'udp'}:
+ raise ConfigError('ports can only be specified when protocol is "tcp" or "udp"')
def generate(lb):
if not lb:
+ # Delete /run/load-balance/wlb.conf
+ if os.path.isfile(load_balancing_conf_file):
+ os.unlink(load_balancing_conf_file)
+ # Delete old directories
+ if os.path.isdir(load_balancing_dir):
+ rmtree(load_balancing_dir, ignore_errors=True)
+ if os.path.exists('/var/run/load-balance/wlb.out'):
+ os.unlink('/var/run/load-balance/wlb.out')
+
return None
+ # Create load-balance dir
+ if not os.path.isdir(load_balancing_dir):
+ os.mkdir(load_balancing_dir)
+
+ render(load_balancing_conf_file, 'load-balancing/wlb.conf.j2', lb)
+
return None
def apply(lb):
+ if not lb:
+ try:
+ cmd(f'systemctl stop {systemd_service}')
+ except Exception as e:
+ print(f"Error message: {e}")
+
+ else:
+ cmd('sudo sysctl -w net.netfilter.nf_conntrack_acct=1')
+ cmd(f'systemctl restart {systemd_service}')
return None
+
if __name__ == '__main__':
try:
c = get_config()
diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py
index 66505e58d..b23584bdb 100755
--- a/src/conf_mode/protocols_bgp.py
+++ b/src/conf_mode/protocols_bgp.py
@@ -50,16 +50,24 @@ def get_config(config=None):
bgp = conf.get_config_dict(base, key_mangling=('-', '_'),
get_first_key=True, no_tag_node_value_mangle=True)
- # Assign the name of our VRF context. This MUST be done before the return
- # statement below, else on deletion we will delete the default instance
- # instead of the VRF instance.
- if vrf: bgp.update({'vrf' : vrf})
-
bgp['dependent_vrfs'] = conf.get_config_dict(['vrf', 'name'],
key_mangling=('-', '_'),
get_first_key=True,
no_tag_node_value_mangle=True)
+ # Assign the name of our VRF context. This MUST be done before the return
+ # statement below, else on deletion we will delete the default instance
+ # instead of the VRF instance.
+ if vrf:
+ bgp.update({'vrf' : vrf})
+ # We can not delete the BGP VRF instance if there is a L3VNI configured
+ tmp = ['vrf', 'name', vrf, 'vni']
+ if conf.exists(tmp):
+ bgp.update({'vni' : conf.return_value(tmp)})
+ # We can safely delete ourself from the dependent vrf list
+ if vrf in bgp['dependent_vrfs']:
+ del bgp['dependent_vrfs'][vrf]
+
bgp['dependent_vrfs'].update({'default': {'protocols': {
'bgp': conf.get_config_dict(base_path, key_mangling=('-', '_'),
get_first_key=True,
@@ -202,9 +210,13 @@ def verify(bgp):
if 'vrf' in bgp:
# Cannot delete vrf if it exists in import vrf list in other vrfs
for tmp_afi in ['ipv4_unicast', 'ipv6_unicast']:
- if verify_vrf_as_import(bgp['vrf'],tmp_afi,bgp['dependent_vrfs']):
- raise ConfigError(f'Cannot delete vrf {bgp["vrf"]} instance, ' \
- 'Please unconfigure import vrf commands!')
+ if verify_vrf_as_import(bgp['vrf'], tmp_afi, bgp['dependent_vrfs']):
+ raise ConfigError(f'Cannot delete VRF instance "{bgp["vrf"]}", ' \
+ 'unconfigure "import vrf" commands!')
+ # We can not delete the BGP instance if a L3VNI instance exists
+ if 'vni' in bgp:
+ raise ConfigError(f'Cannot delete VRF instance "{bgp["vrf"]}", ' \
+ f'unconfigure VNI "{bgp["vni"]}" first!')
else:
# We are running in the default VRF context, thus we can not delete
# our main BGP instance if there are dependent BGP VRF instances.
@@ -429,7 +441,6 @@ def verify(bgp):
f'{afi} administrative distance {key}!')
if afi in ['ipv4_unicast', 'ipv6_unicast']:
-
vrf_name = bgp['vrf'] if dict_search('vrf', bgp) else 'default'
# Verify if currant VRF contains rd and route-target options
# and does not exist in import list in other VRFs
@@ -478,6 +489,15 @@ def verify(bgp):
tmp = dict_search(f'route_map.vpn.{export_import}', afi_config)
if tmp: verify_route_map(tmp, bgp)
+ # Checks only required for L2VPN EVPN
+ if afi in ['l2vpn_evpn']:
+ if 'vni' in afi_config:
+ for vni, vni_config in afi_config['vni'].items():
+ if 'rd' in vni_config and 'advertise_all_vni' not in afi_config:
+ raise ConfigError('BGP EVPN "rd" requires "advertise-all-vni" to be set!')
+ if 'route_target' in vni_config and 'advertise_all_vni' not in afi_config:
+ raise ConfigError('BGP EVPN "route-target" requires "advertise-all-vni" to be set!')
+
return None
def generate(bgp):
diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py
index af2937db8..ecca87db0 100755
--- a/src/conf_mode/protocols_isis.py
+++ b/src/conf_mode/protocols_isis.py
@@ -129,7 +129,7 @@ def verify(isis):
vrf = isis['vrf']
tmp = get_interface_config(interface)
if 'master' not in tmp or tmp['master'] != vrf:
- raise ConfigError(f'Interface {interface} is not a member of VRF {vrf}!')
+ raise ConfigError(f'Interface "{interface}" is not a member of VRF "{vrf}"!')
# If md5 and plaintext-password set at the same time
for password in ['area_password', 'domain_password']:
diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py
index fbb876123..b73483470 100755
--- a/src/conf_mode/protocols_ospf.py
+++ b/src/conf_mode/protocols_ospf.py
@@ -196,7 +196,7 @@ def verify(ospf):
vrf = ospf['vrf']
tmp = get_interface_config(interface)
if 'master' not in tmp or tmp['master'] != vrf:
- raise ConfigError(f'Interface {interface} is not a member of VRF {vrf}!')
+ raise ConfigError(f'Interface "{interface}" is not a member of VRF "{vrf}"!')
# Segment routing checks
if dict_search('segment_routing.global_block', ospf):
diff --git a/src/conf_mode/protocols_ospfv3.py b/src/conf_mode/protocols_ospfv3.py
index ee1fdd399..cb21bd83c 100755
--- a/src/conf_mode/protocols_ospfv3.py
+++ b/src/conf_mode/protocols_ospfv3.py
@@ -138,7 +138,7 @@ def verify(ospfv3):
vrf = ospfv3['vrf']
tmp = get_interface_config(interface)
if 'master' not in tmp or tmp['master'] != vrf:
- raise ConfigError(f'Interface {interface} is not a member of VRF {vrf}!')
+ raise ConfigError(f'Interface "{interface}" is not a member of VRF "{vrf}"!')
return None
diff --git a/src/conf_mode/service_pppoe-server.py b/src/conf_mode/service_pppoe-server.py
index 600ba4e92..adeefaa37 100755
--- a/src/conf_mode/service_pppoe-server.py
+++ b/src/conf_mode/service_pppoe-server.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2022 VyOS maintainers and contributors
+# Copyright (C) 2018-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
@@ -71,8 +71,9 @@ def verify(pppoe):
# local ippool and gateway settings config checks
if not (dict_search('client_ip_pool.subnet', pppoe) or
+ (dict_search('client_ip_pool.name', pppoe) or
(dict_search('client_ip_pool.start', pppoe) and
- dict_search('client_ip_pool.stop', pppoe))):
+ dict_search('client_ip_pool.stop', pppoe)))):
print('Warning: No PPPoE client pool defined')
if dict_search('authentication.radius.dynamic_author.server', pppoe):
diff --git a/src/conf_mode/system-syslog.py b/src/conf_mode/system-syslog.py
index 20132456c..e646fb0ae 100755
--- a/src/conf_mode/system-syslog.py
+++ b/src/conf_mode/system-syslog.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2020 VyOS maintainers and contributors
+# Copyright (C) 2018-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
@@ -15,253 +15,129 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
-import re
-from pathlib import Path
from sys import exit
from vyos.config import Config
-from vyos import ConfigError
-from vyos.util import run
+from vyos.configdict import dict_merge
+from vyos.configdict import is_node_changed
+from vyos.configverify import verify_vrf
+from vyos.util import call
from vyos.template import render
-
+from vyos.xml import defaults
+from vyos import ConfigError
from vyos import airbag
airbag.enable()
+rsyslog_conf = '/etc/rsyslog.d/00-vyos.conf'
+logrotate_conf = '/etc/logrotate.d/vyos-rsyslog'
+systemd_override = r'/run/systemd/system/rsyslog.service.d/override.conf'
+
def get_config(config=None):
if config:
- c = config
+ conf = config
else:
- c = Config()
- if not c.exists('system syslog'):
+ conf = Config()
+ base = ['system', 'syslog']
+ if not conf.exists(base):
return None
- c.set_level('system syslog')
-
- config_data = {
- 'files': {},
- 'console': {},
- 'hosts': {},
- 'user': {}
- }
-
- #
- # /etc/rsyslog.d/vyos-rsyslog.conf
- # 'set system syslog global'
- #
- config_data['files'].update(
- {
- 'global': {
- 'log-file': '/var/log/messages',
- 'selectors': '*.notice;local7.debug',
- 'max-files': '5',
- 'preserver_fqdn': False
- }
- }
- )
-
- if c.exists('global marker'):
- config_data['files']['global']['marker'] = True
- if c.exists('global marker interval'):
- config_data['files']['global'][
- 'marker-interval'] = c.return_value('global marker interval')
- if c.exists('global facility'):
- config_data['files']['global'][
- 'selectors'] = generate_selectors(c, 'global facility')
- if c.exists('global archive size'):
- config_data['files']['global']['max-size'] = int(
- c.return_value('global archive size')) * 1024
- if c.exists('global archive file'):
- config_data['files']['global'][
- 'max-files'] = c.return_value('global archive file')
- if c.exists('global preserve-fqdn'):
- config_data['files']['global']['preserver_fqdn'] = True
-
- #
- # set system syslog file
- #
-
- if c.exists('file'):
- filenames = c.list_nodes('file')
- for filename in filenames:
- config_data['files'].update(
- {
- filename: {
- 'log-file': '/var/log/user/' + filename,
- 'max-files': '5',
- 'action-on-max-size': '/usr/sbin/logrotate /etc/logrotate.d/vyos-rsyslog-generated-' + filename,
- 'selectors': '*.err',
- 'max-size': 262144
- }
- }
- )
-
- if c.exists('file ' + filename + ' facility'):
- config_data['files'][filename]['selectors'] = generate_selectors(
- c, 'file ' + filename + ' facility')
- if c.exists('file ' + filename + ' archive size'):
- config_data['files'][filename]['max-size'] = int(
- c.return_value('file ' + filename + ' archive size')) * 1024
- if c.exists('file ' + filename + ' archive files'):
- config_data['files'][filename]['max-files'] = c.return_value(
- 'file ' + filename + ' archive files')
-
- # set system syslog console
- if c.exists('console'):
- config_data['console'] = {
- '/dev/console': {
- 'selectors': '*.err'
- }
- }
-
- for f in c.list_nodes('console facility'):
- if c.exists('console facility ' + f + ' level'):
- config_data['console'] = {
- '/dev/console': {
- 'selectors': generate_selectors(c, 'console facility')
- }
- }
- # set system syslog host
- if c.exists('host'):
- rhosts = c.list_nodes('host')
- proto = 'udp'
- for rhost in rhosts:
- for fac in c.list_nodes('host ' + rhost + ' facility'):
- if c.exists('host ' + rhost + ' facility ' + fac + ' protocol'):
- proto = c.return_value(
- 'host ' + rhost + ' facility ' + fac + ' protocol')
- else:
- proto = 'udp'
-
- config_data['hosts'].update(
- {
- rhost: {
- 'selectors': generate_selectors(c, 'host ' + rhost + ' facility'),
- 'proto': proto
- }
- }
- )
- if c.exists('host ' + rhost + ' port'):
- config_data['hosts'][rhost][
- 'port'] = c.return_value(['host', rhost, 'port'])
-
- # set system syslog host x.x.x.x format octet-counted
- if c.exists('host ' + rhost + ' format octet-counted'):
- config_data['hosts'][rhost]['oct_count'] = True
- else:
- config_data['hosts'][rhost]['oct_count'] = False
-
- # set system syslog user
- if c.exists('user'):
- usrs = c.list_nodes('user')
- for usr in usrs:
- config_data['user'].update(
- {
- usr: {
- 'selectors': generate_selectors(c, 'user ' + usr + ' facility')
- }
- }
- )
-
- return config_data
-
-
-def generate_selectors(c, config_node):
-# protocols and security are being mapped here
-# for backward compatibility with old configs
-# security and protocol mappings can be removed later
- nodes = c.list_nodes(config_node)
- selectors = ""
- for node in nodes:
- lvl = c.return_value(config_node + ' ' + node + ' level')
- if lvl == None:
- lvl = "err"
- if lvl == 'all':
- lvl = '*'
- if node == 'all' and node != nodes[-1]:
- selectors += "*." + lvl + ";"
- elif node == 'all':
- selectors += "*." + lvl
- elif node != nodes[-1]:
- if node == 'protocols':
- node = 'local7'
- if node == 'security':
- node = 'auth'
- selectors += node + "." + lvl + ";"
- else:
- if node == 'protocols':
- node = 'local7'
- if node == 'security':
- node = 'auth'
- selectors += node + "." + lvl
- return selectors
-
-
-def generate(c):
- if c == None:
+ syslog = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True, no_tag_node_value_mangle=True)
+
+ syslog.update({ 'logrotate' : logrotate_conf })
+ tmp = is_node_changed(conf, base + ['vrf'])
+ if tmp: syslog.update({'restart_required': {}})
+
+ # We have gathered the dict representation of the CLI, but there are default
+ # options which we need to update into the dictionary retrived.
+ default_values = defaults(base)
+ # XXX: some syslog default values can not be merged here (originating from
+ # a tagNode - remove and add them later per individual tagNode instance
+ if 'console' in default_values:
+ del default_values['console']
+ for entity in ['global', 'user', 'host', 'file']:
+ if entity in default_values:
+ del default_values[entity]
+
+ syslog = dict_merge(default_values, syslog)
+
+ # XXX: add defaults for "console" tree
+ if 'console' in syslog and 'facility' in syslog['console']:
+ default_values = defaults(base + ['console', 'facility'])
+ for facility in syslog['console']['facility']:
+ syslog['console']['facility'][facility] = dict_merge(default_values,
+ syslog['console']['facility'][facility])
+
+ # XXX: add defaults for "host" tree
+ if 'host' in syslog:
+ default_values_host = defaults(base + ['host'])
+ if 'facility' in default_values_host:
+ del default_values_host['facility']
+ default_values_facility = defaults(base + ['host', 'facility'])
+
+ for host, host_config in syslog['host'].items():
+ syslog['host'][host] = dict_merge(default_values_host, syslog['host'][host])
+ if 'facility' in host_config:
+ for facility in host_config['facility']:
+ syslog['host'][host]['facility'][facility] = dict_merge(default_values_facility,
+ syslog['host'][host]['facility'][facility])
+
+ # XXX: add defaults for "user" tree
+ if 'user' in syslog:
+ default_values = defaults(base + ['user', 'facility'])
+ for user, user_config in syslog['user'].items():
+ if 'facility' in user_config:
+ for facility in user_config['facility']:
+ syslog['user'][user]['facility'][facility] = dict_merge(default_values,
+ syslog['user'][user]['facility'][facility])
+
+ # XXX: add defaults for "file" tree
+ if 'file' in syslog:
+ default_values = defaults(base + ['file'])
+ for file, file_config in syslog['file'].items():
+ for facility in file_config['facility']:
+ syslog['file'][file]['facility'][facility] = dict_merge(default_values,
+ syslog['file'][file]['facility'][facility])
+
+ return syslog
+
+def verify(syslog):
+ if not syslog:
return None
- conf = '/etc/rsyslog.d/vyos-rsyslog.conf'
- render(conf, 'syslog/rsyslog.conf.j2', c)
-
- # cleanup current logrotate config files
- logrotate_files = Path('/etc/logrotate.d/').glob('vyos-rsyslog-generated-*')
- for file in logrotate_files:
- file.unlink()
+ verify_vrf(syslog)
- # eventually write for each file its own logrotate file, since size is
- # defined it shouldn't matter
- for filename, fileconfig in c.get('files', {}).items():
- if fileconfig['log-file'].startswith('/var/log/user/'):
- conf = '/etc/logrotate.d/vyos-rsyslog-generated-' + filename
- render(conf, 'syslog/logrotate.j2', { 'config_render': fileconfig })
+def generate(syslog):
+ if not syslog:
+ if os.path.exists(rsyslog_conf):
+ os.path.unlink(rsyslog_conf)
+ if os.path.exists(logrotate_conf):
+ os.path.unlink(logrotate_conf)
-
-def verify(c):
- if c == None:
return None
- # may be obsolete
- # /etc/rsyslog.conf is generated somewhere and copied over the original (exists in /opt/vyatta/etc/rsyslog.conf)
- # it interferes with the global logging, to make sure we are using a single base, template is enforced here
- #
- if not os.path.islink('/etc/rsyslog.conf'):
- os.remove('/etc/rsyslog.conf')
- os.symlink(
- '/usr/share/vyos/templates/rsyslog/rsyslog.conf', '/etc/rsyslog.conf')
+ render(rsyslog_conf, 'rsyslog/rsyslog.conf.j2', syslog)
+ render(systemd_override, 'rsyslog/override.conf.j2', syslog)
+ render(logrotate_conf, 'rsyslog/logrotate.j2', syslog)
- # /var/log/vyos-rsyslog were the old files, we may want to clean those up, but currently there
- # is a chance that someone still needs it, so I don't automatically remove
- # them
- #
+ # Reload systemd manager configuration
+ call('systemctl daemon-reload')
+ return None
- if c == None:
+def apply(syslog):
+ systemd_service = 'syslog.service'
+ if not syslog:
+ call(f'systemctl stop {systemd_service}')
return None
- fac = [
- '*', 'auth', 'authpriv', 'cron', 'daemon', 'kern', 'lpr', 'mail', 'mark', 'news', 'protocols', 'security',
- 'syslog', 'user', 'uucp', 'local0', 'local1', 'local2', 'local3', 'local4', 'local5', 'local6', 'local7']
- lvl = ['emerg', 'alert', 'crit', 'err',
- 'warning', 'notice', 'info', 'debug', '*']
-
- for conf in c:
- if c[conf]:
- for item in c[conf]:
- for s in c[conf][item]['selectors'].split(";"):
- f = re.sub("\..*$", "", s)
- if f not in fac:
- raise ConfigError(
- 'Invalid facility ' + s + ' set in ' + conf + ' ' + item)
- l = re.sub("^.+\.", "", s)
- if l not in lvl:
- raise ConfigError(
- 'Invalid logging level ' + s + ' set in ' + conf + ' ' + item)
-
+ # we need to restart the service if e.g. the VRF name changed
+ systemd_action = 'reload-or-restart'
+ if 'restart_required' in syslog:
+ systemd_action = 'restart'
-def apply(c):
- if not c:
- return run('systemctl stop syslog.service')
- return run('systemctl restart syslog.service')
+ call(f'systemctl {systemd_action} {systemd_service}')
+ return None
if __name__ == '__main__':
try:
diff --git a/src/conf_mode/vpn_l2tp.py b/src/conf_mode/vpn_l2tp.py
index 65623c2b1..ffac3b023 100755
--- a/src/conf_mode/vpn_l2tp.py
+++ b/src/conf_mode/vpn_l2tp.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019-2020 VyOS maintainers and contributors
+# Copyright (C) 2019-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
@@ -63,6 +63,7 @@ default_config_data = {
'ppp_ipv6_peer_intf_id': None,
'radius_server': [],
'radius_acct_inter_jitter': '',
+ 'radius_acct_interim_interval': None,
'radius_acct_tmo': '3',
'radius_max_try': '3',
'radius_timeout': '3',
@@ -190,6 +191,9 @@ def get_config(config=None):
# advanced radius-setting
conf.set_level(base_path + ['authentication', 'radius'])
+ if conf.exists(['accounting-interim-interval']):
+ l2tp['radius_acct_interim_interval'] = conf.return_value(['accounting-interim-interval'])
+
if conf.exists(['acct-interim-jitter']):
l2tp['radius_acct_inter_jitter'] = conf.return_value(['acct-interim-jitter'])
diff --git a/src/conf_mode/vpn_pptp.py b/src/conf_mode/vpn_pptp.py
index 986a19972..b9d18110a 100755
--- a/src/conf_mode/vpn_pptp.py
+++ b/src/conf_mode/vpn_pptp.py
@@ -37,6 +37,7 @@ default_pptp = {
'local_users' : [],
'radius_server' : [],
'radius_acct_inter_jitter': '',
+ 'radius_acct_interim_interval': None,
'radius_acct_tmo' : '30',
'radius_max_try' : '3',
'radius_timeout' : '30',
@@ -145,6 +146,9 @@ def get_config(config=None):
# advanced radius-setting
conf.set_level(base_path + ['authentication', 'radius'])
+ if conf.exists(['accounting-interim-interval']):
+ pptp['radius_acct_interim_interval'] = conf.return_value(['accounting-interim-interval'])
+
if conf.exists(['acct-interim-jitter']):
pptp['radius_acct_inter_jitter'] = conf.return_value(['acct-interim-jitter'])
diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py
index a7ef4cb5c..0b983293e 100755
--- a/src/conf_mode/vrf.py
+++ b/src/conf_mode/vrf.py
@@ -108,6 +108,12 @@ def get_config(config=None):
# vyos.configverify.verify_common_route_maps() for more information.
tmp = {'policy' : {'route-map' : conf.get_config_dict(['policy', 'route-map'],
get_first_key=True)}}
+
+ # L3VNI setup is done via vrf_vni.py as it must be de-configured (on node
+ # deletetion prior to the BGP process. Tell the Jinja2 template no VNI
+ # setup is needed
+ vrf.update({'no_vni' : ''})
+
# Merge policy dict into "regular" config dict
vrf = dict_merge(tmp, vrf)
return vrf
@@ -124,8 +130,8 @@ def verify(vrf):
f'static routes installed!')
if 'name' in vrf:
- reserved_names = ["add", "all", "broadcast", "default", "delete", "dev", "get", "inet", "mtu", "link", "type",
- "vrf"]
+ reserved_names = ["add", "all", "broadcast", "default", "delete", "dev",
+ "get", "inet", "mtu", "link", "type", "vrf"]
table_ids = []
for name, vrf_config in vrf['name'].items():
# Reserved VRF names
@@ -142,8 +148,8 @@ def verify(vrf):
if tmp and tmp != vrf_config['table']:
raise ConfigError(f'VRF "{name}" table id modification not possible!')
- # VRf routing table ID must be unique on the system
- if vrf_config['table'] in table_ids:
+ # VRF routing table ID must be unique on the system
+ if 'table' in vrf_config and vrf_config['table'] in table_ids:
raise ConfigError(f'VRF "{name}" table id is not unique!')
table_ids.append(vrf_config['table'])
diff --git a/src/conf_mode/vrf_vni.py b/src/conf_mode/vrf_vni.py
new file mode 100644
index 000000000..9f33536e5
--- /dev/null
+++ b/src/conf_mode/vrf_vni.py
@@ -0,0 +1,104 @@
+#!/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 argv
+from sys import exit
+
+from vyos.config import Config
+from vyos.template import render_to_string
+from vyos.util import dict_search
+from vyos import ConfigError
+from vyos import frr
+from vyos import airbag
+airbag.enable()
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+
+ vrf_name = None
+ if len(argv) > 1:
+ vrf_name = argv[1]
+ else:
+ return None
+
+ # Using duplicate L3VNIs makes no sense - it's also forbidden in FRR,
+ # thus VyOS CLI must deny this, too. Instead of getting only the dict for
+ # the requested VRF and den comparing it with depenent VRfs to not have any
+ # duplicate we will just grad ALL VRFs by default but only render/apply
+ # the configuration for the requested VRF - that makes the code easier and
+ # hopefully less error prone
+ vrf = conf.get_config_dict(['vrf'], key_mangling=('-', '_'),
+ no_tag_node_value_mangle=True,
+ get_first_key=True)
+
+ # Store name of VRF we are interested in for FRR config rendering
+ vrf.update({'only_vrf' : vrf_name})
+
+ return vrf
+
+def verify(vrf):
+ if not vrf:
+ return
+
+ if len(argv) < 2:
+ raise ConfigError('VRF parameter not specified when valling vrf_vni.py')
+
+ if 'name' in vrf:
+ vni_ids = []
+ for name, vrf_config in vrf['name'].items():
+ # VRF VNI (Virtual Network Identifier) must be unique on the system
+ if 'vni' in vrf_config:
+ if vrf_config['vni'] in vni_ids:
+ raise ConfigError(f'VRF "{name}" VNI is not unique!')
+ vni_ids.append(vrf_config['vni'])
+
+ return None
+
+def generate(vrf):
+ if not vrf:
+ return
+
+ vrf['new_frr_config'] = render_to_string('frr/zebra.vrf.route-map.frr.j2', vrf)
+ return None
+
+def apply(vrf):
+ frr_daemon = 'zebra'
+
+ # add configuration to FRR
+ frr_cfg = frr.FRRConfig()
+ frr_cfg.load_configuration(frr_daemon)
+ # There is only one VRF inside the dict as we read only one in get_config()
+ if vrf and 'only_vrf' in vrf:
+ vrf_name = vrf['only_vrf']
+ frr_cfg.modify_section(f'^vrf {vrf_name}', stop_pattern='^exit-vrf', remove_stop_mark=True)
+ if vrf and 'new_frr_config' in vrf:
+ frr_cfg.add_before(frr.default_add_before, vrf['new_frr_config'])
+ frr_cfg.commit_configuration(frr_daemon)
+
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)