summaryrefslogtreecommitdiff
path: root/src/conf_mode
diff options
context:
space:
mode:
Diffstat (limited to 'src/conf_mode')
-rwxr-xr-xsrc/conf_mode/container.py11
-rwxr-xr-xsrc/conf_mode/dns_forwarding.py9
-rwxr-xr-xsrc/conf_mode/firewall-interface.py11
-rwxr-xr-xsrc/conf_mode/firewall.py215
-rwxr-xr-xsrc/conf_mode/interfaces-bonding.py23
-rwxr-xr-xsrc/conf_mode/interfaces-bridge.py14
-rwxr-xr-xsrc/conf_mode/interfaces-ethernet.py2
-rwxr-xr-xsrc/conf_mode/interfaces-geneve.py2
-rwxr-xr-xsrc/conf_mode/interfaces-l2tpv3.py2
-rwxr-xr-xsrc/conf_mode/interfaces-macsec.py73
-rwxr-xr-xsrc/conf_mode/interfaces-openvpn.py45
-rwxr-xr-xsrc/conf_mode/interfaces-pseudo-ethernet.py1
-rwxr-xr-xsrc/conf_mode/interfaces-tunnel.py2
-rwxr-xr-xsrc/conf_mode/interfaces-vxlan.py2
-rwxr-xr-xsrc/conf_mode/interfaces-wireguard.py2
-rwxr-xr-xsrc/conf_mode/interfaces-wireless.py2
-rwxr-xr-xsrc/conf_mode/ntp.py20
-rwxr-xr-xsrc/conf_mode/policy-route-interface.py12
-rwxr-xr-xsrc/conf_mode/policy-route.py72
-rwxr-xr-xsrc/conf_mode/protocols_bgp.py15
-rwxr-xr-xsrc/conf_mode/protocols_nhrp.py7
-rwxr-xr-xsrc/conf_mode/service_event_handler.py91
-rwxr-xr-xsrc/conf_mode/service_ipoe-server.py14
-rwxr-xr-xsrc/conf_mode/service_monitoring_telegraf.py22
-rwxr-xr-xsrc/conf_mode/service_router-advert.py34
-rwxr-xr-xsrc/conf_mode/system-ip.py5
-rwxr-xr-xsrc/conf_mode/system-login.py2
-rwxr-xr-xsrc/conf_mode/system-syslog.py2
-rwxr-xr-xsrc/conf_mode/vpn_sstp.py8
-rwxr-xr-xsrc/conf_mode/vrf.py6
-rwxr-xr-xsrc/conf_mode/zone_policy.py2
31 files changed, 570 insertions, 158 deletions
diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py
index 2110fd9e0..ac3dc536b 100755
--- a/src/conf_mode/container.py
+++ b/src/conf_mode/container.py
@@ -90,10 +90,10 @@ def get_config(config=None):
container['name'][name] = dict_merge(default_values, container['name'][name])
# Delete container network, delete containers
- tmp = node_changed(conf, base + ['container', 'network'])
+ tmp = node_changed(conf, base + ['network'])
if tmp: container.update({'network_remove' : tmp})
- tmp = node_changed(conf, base + ['container', 'name'])
+ tmp = node_changed(conf, base + ['name'])
if tmp: container.update({'container_remove' : tmp})
return container
@@ -132,7 +132,7 @@ def verify(container):
# Check if the specified container network exists
network_name = list(container_config['network'])[0]
- if network_name not in container['network']:
+ if network_name not in container.get('network', {}):
raise ConfigError(f'Container network "{network_name}" does not exist!')
if 'address' in container_config['network'][network_name]:
@@ -270,12 +270,13 @@ def apply(container):
# Option "--force" allows to delete containers with any status
if 'container_remove' in container:
for name in container['container_remove']:
- call(f'podman stop {name}')
+ call(f'podman stop --time 3 {name}')
call(f'podman rm --force {name}')
# Delete old networks if needed
if 'network_remove' in container:
for network in container['network_remove']:
+ call(f'podman network rm {network}')
tmp = f'/etc/cni/net.d/{network}.conflist'
if os.path.exists(tmp):
os.unlink(tmp)
@@ -294,7 +295,7 @@ def apply(container):
# check if there is a container by that name running
tmp = _cmd('podman ps -a --format "{{.Names}}"')
if name in tmp:
- _cmd(f'podman stop {name}')
+ _cmd(f'podman stop --time 3 {name}')
_cmd(f'podman rm --force {name}')
continue
diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py
index f1c2d1f43..d0d87d73e 100755
--- a/src/conf_mode/dns_forwarding.py
+++ b/src/conf_mode/dns_forwarding.py
@@ -98,6 +98,9 @@ def get_config(config=None):
dns['authoritative_zone_errors'].append('{}.{}: at least one address is required'.format(subnode, node))
continue
+ if subnode == 'any':
+ subnode = '*'
+
for address in rdata['address']:
zone['records'].append({
'name': subnode,
@@ -263,6 +266,12 @@ def verify(dns):
if 'server' not in dns['domain'][domain]:
raise ConfigError(f'No server configured for domain {domain}!')
+ if 'dns64_prefix' in dns:
+ dns_prefix = dns['dns64_prefix'].split('/')[1]
+ # RFC 6147 requires prefix /96
+ if int(dns_prefix) != 96:
+ raise ConfigError('DNS 6to4 prefix must be of length /96')
+
if ('authoritative_zone_errors' in dns) and dns['authoritative_zone_errors']:
for error in dns['authoritative_zone_errors']:
print(error)
diff --git a/src/conf_mode/firewall-interface.py b/src/conf_mode/firewall-interface.py
index 9a5d278e9..ab1c69259 100755
--- a/src/conf_mode/firewall-interface.py
+++ b/src/conf_mode/firewall-interface.py
@@ -64,6 +64,11 @@ def get_config(config=None):
return if_firewall
+def verify_chain(table, chain):
+ # Verify firewall applied
+ code = run(f'nft list chain {table} {chain}')
+ return code == 0
+
def verify(if_firewall):
# bail out early - looks like removal from running config
if not if_firewall:
@@ -80,6 +85,9 @@ def verify(if_firewall):
if name not in if_firewall['firewall']['name']:
raise ConfigError(f'Invalid firewall name "{name}"')
+ if not verify_chain('ip filter', f'{NAME_PREFIX}{name}'):
+ raise ConfigError('Firewall did not apply')
+
if 'ipv6_name' in if_firewall[direction]:
name = if_firewall[direction]['ipv6_name']
@@ -89,6 +97,9 @@ def verify(if_firewall):
if name not in if_firewall['firewall']['ipv6_name']:
raise ConfigError(f'Invalid firewall ipv6-name "{name}"')
+ if not verify_chain('ip6 filter', f'{NAME6_PREFIX}{name}'):
+ raise ConfigError('Firewall did not apply')
+
return None
def generate(if_firewall):
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
index 6924bf555..07eca722f 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021 VyOS maintainers and contributors
+# Copyright (C) 2021-2022 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,9 +26,17 @@ from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.configdict import node_changed
from vyos.configdiff import get_config_diff, Diff
+from vyos.firewall import geoip_update
+from vyos.firewall import get_ips_domains_dict
+from vyos.firewall import nft_add_set_elements
+from vyos.firewall import nft_flush_set
+from vyos.firewall import nft_init_set
+from vyos.firewall import nft_update_set_elements
from vyos.template import render
+from vyos.util import call
from vyos.util import cmd
from vyos.util import dict_search_args
+from vyos.util import dict_search_recursive
from vyos.util import process_named_running
from vyos.util import run
from vyos.xml import defaults
@@ -79,10 +87,26 @@ nft6_iface_chains = ['VYOS_FW6_FORWARD', 'VYOS_FW6_OUTPUT', 'VYOS_FW6_LOCAL']
valid_groups = [
'address_group',
+ 'domain_group',
'network_group',
'port_group'
]
+nested_group_types = [
+ 'address_group', 'network_group', 'mac_group',
+ 'port_group', 'ipv6_address_group', 'ipv6_network_group'
+]
+
+group_set_prefix = {
+ 'A_': 'address_group',
+ 'A6_': 'ipv6_address_group',
+ 'D_': 'domain_group',
+ 'M_': 'mac_group',
+ 'N_': 'network_group',
+ 'N6_': 'ipv6_network_group',
+ 'P_': 'port_group'
+}
+
snmp_change_type = {
'unknown': 0,
'add': 1,
@@ -138,6 +162,40 @@ def get_firewall_zones(conf):
return {'name': used_v4, 'ipv6_name': used_v6}
+def geoip_updated(conf, firewall):
+ diff = get_config_diff(conf)
+ node_diff = diff.get_child_nodes_diff(['firewall'], expand_nodes=Diff.DELETE, recursive=True)
+
+ out = {
+ 'name': [],
+ 'ipv6_name': [],
+ 'deleted_name': [],
+ 'deleted_ipv6_name': []
+ }
+ updated = False
+
+ for key, path in dict_search_recursive(firewall, 'geoip'):
+ set_name = f'GEOIP_CC_{path[1]}_{path[3]}'
+ if path[0] == 'name':
+ out['name'].append(set_name)
+ elif path[0] == 'ipv6_name':
+ out['ipv6_name'].append(set_name)
+ updated = True
+
+ if 'delete' in node_diff:
+ for key, path in dict_search_recursive(node_diff['delete'], 'geoip'):
+ set_name = f'GEOIP_CC_{path[1]}_{path[3]}'
+ if path[0] == 'name':
+ out['deleted_name'].append(set_name)
+ elif path[0] == 'ipv6-name':
+ out['deleted_ipv6_name'].append(set_name)
+ updated = True
+
+ if updated:
+ return out
+
+ return False
+
def get_config(config=None):
if config:
conf = config
@@ -162,6 +220,8 @@ def get_config(config=None):
key_mangling=('-', '_'), get_first_key=True,
no_tag_node_value_mangle=True)
+ firewall['geoip_updated'] = geoip_updated(conf, firewall)
+
return firewall
def verify_rule(firewall, rule_conf, ipv6):
@@ -207,6 +267,16 @@ def verify_rule(firewall, rule_conf, ipv6):
if side in rule_conf:
side_conf = rule_conf[side]
+ if dict_search_args(side_conf, 'geoip', 'country_code'):
+ if 'address' in side_conf:
+ raise ConfigError('Address and GeoIP cannot both be defined')
+
+ if dict_search_args(side_conf, 'group', 'address_group'):
+ raise ConfigError('Address-group and GeoIP cannot both be defined')
+
+ if dict_search_args(side_conf, 'group', 'network_group'):
+ raise ConfigError('Network-group and GeoIP cannot both be defined')
+
if 'group' in side_conf:
if {'address_group', 'network_group'} <= set(side_conf['group']):
raise ConfigError('Only one address-group or network-group can be specified')
@@ -235,11 +305,34 @@ def verify_rule(firewall, rule_conf, ipv6):
if rule_conf['protocol'] not in ['tcp', 'udp', 'tcp_udp']:
raise ConfigError('Protocol must be tcp, udp, or tcp_udp when specifying a port or port-group')
+def verify_nested_group(group_name, group, groups, seen):
+ if 'include' not in group:
+ return
+
+ for g in group['include']:
+ if g not in groups:
+ raise ConfigError(f'Nested group "{g}" does not exist')
+
+ if g in seen:
+ raise ConfigError(f'Group "{group_name}" has a circular reference')
+
+ seen.append(g)
+
+ if 'include' in groups[g]:
+ verify_nested_group(g, groups[g], groups, seen)
+
def verify(firewall):
if 'config_trap' in firewall and firewall['config_trap'] == 'enable':
if not firewall['trap_targets']:
raise ConfigError(f'Firewall config-trap enabled but "service snmp trap-target" is not defined')
+ if 'group' in firewall:
+ for group_type in nested_group_types:
+ if group_type in firewall['group']:
+ groups = firewall['group'][group_type]
+ for group_name, group in groups.items():
+ verify_nested_group(group_name, group, groups, [])
+
for name in ['name', 'ipv6_name']:
if name in firewall:
for name_id, name_conf in firewall[name].items():
@@ -271,55 +364,75 @@ def verify(firewall):
return None
-def cleanup_rule(table, jump_chain):
- commands = []
- chains = nft_iface_chains if table == 'ip filter' else nft6_iface_chains
- for chain in chains:
- results = cmd(f'nft -a list chain {table} {chain}').split("\n")
- for line in results:
- if f'jump {jump_chain}' in line:
- handle_search = re.search('handle (\d+)', line)
- if handle_search:
- commands.append(f'delete rule {table} {chain} handle {handle_search[1]}')
- return commands
-
def cleanup_commands(firewall):
commands = []
- commands_end = []
+ commands_chains = []
+ commands_sets = []
for table in ['ip filter', 'ip6 filter']:
+ name_node = 'name' if table == 'ip filter' else 'ipv6_name'
+ chain_prefix = NAME_PREFIX if table == 'ip filter' else NAME6_PREFIX
state_chain = 'VYOS_STATE_POLICY' if table == 'ip filter' else 'VYOS_STATE_POLICY6'
- json_str = cmd(f'nft -j list table {table}')
+ iface_chains = nft_iface_chains if table == 'ip filter' else nft6_iface_chains
+
+ geoip_list = []
+ if firewall['geoip_updated']:
+ geoip_key = 'deleted_ipv6_name' if table == 'ip6 filter' else 'deleted_name'
+ geoip_list = dict_search_args(firewall, 'geoip_updated', geoip_key) or []
+
+ json_str = cmd(f'nft -t -j list table {table}')
obj = loads(json_str)
+
if 'nftables' not in obj:
continue
+
for item in obj['nftables']:
if 'chain' in item:
chain = item['chain']['name']
- if chain in ['VYOS_STATE_POLICY', 'VYOS_STATE_POLICY6']:
- if 'state_policy' not in firewall:
- commands.append(f'delete chain {table} {chain}')
- else:
- commands.append(f'flush chain {table} {chain}')
- elif chain not in preserve_chains and not chain.startswith("VZONE"):
- if table == 'ip filter' and dict_search_args(firewall, 'name', chain.replace(NAME_PREFIX, "", 1)) != None:
- commands.append(f'flush chain {table} {chain}')
- elif table == 'ip6 filter' and dict_search_args(firewall, 'ipv6_name', chain.replace(NAME6_PREFIX, "", 1)) != None:
- commands.append(f'flush chain {table} {chain}')
- else:
- commands += cleanup_rule(table, chain)
- commands.append(f'delete chain {table} {chain}')
- elif 'rule' in item:
+ if chain in preserve_chains or chain.startswith("VZONE"):
+ continue
+
+ if chain == state_chain:
+ command = 'delete' if 'state_policy' not in firewall else 'flush'
+ commands_chains.append(f'{command} chain {table} {chain}')
+ elif dict_search_args(firewall, name_node, chain.replace(chain_prefix, "", 1)) != None:
+ commands.append(f'flush chain {table} {chain}')
+ else:
+ commands_chains.append(f'delete chain {table} {chain}')
+
+ if 'rule' in item:
rule = item['rule']
- if rule['chain'] in ['VYOS_FW_FORWARD', 'VYOS_FW_OUTPUT', 'VYOS_FW_LOCAL', 'VYOS_FW6_FORWARD', 'VYOS_FW6_OUTPUT', 'VYOS_FW6_LOCAL']:
- if 'expr' in rule and any([True for expr in rule['expr'] if dict_search_args(expr, 'jump', 'target') == state_chain]):
- if 'state_policy' not in firewall:
- chain = rule['chain']
- handle = rule['handle']
+ chain = rule['chain']
+ handle = rule['handle']
+
+ if chain in iface_chains:
+ target, _ = next(dict_search_recursive(rule['expr'], 'target'))
+
+ if target == state_chain and 'state_policy' not in firewall:
+ commands.append(f'delete rule {table} {chain} handle {handle}')
+
+ if target.startswith(chain_prefix):
+ if dict_search_args(firewall, name_node, target.replace(chain_prefix, "", 1)) == None:
commands.append(f'delete rule {table} {chain} handle {handle}')
- elif 'set' in item:
+
+ if 'set' in item:
set_name = item['set']['name']
- commands_end.append(f'delete set {table} {set_name}')
- return commands + commands_end
+
+ if set_name.startswith('GEOIP_CC_') and set_name in geoip_list:
+ commands_sets.append(f'delete set {table} {set_name}')
+ continue
+
+ if set_name.startswith("RECENT_"):
+ commands_sets.append(f'delete set {table} {set_name}')
+ continue
+
+ for prefix, group_type in group_set_prefix.items():
+ if set_name.startswith(prefix):
+ group_name = set_name.replace(prefix, "", 1)
+ if dict_search_args(firewall, 'group', group_type, group_name) != None:
+ commands_sets.append(f'flush set {table} {set_name}')
+ else:
+ commands_sets.append(f'delete set {table} {set_name}')
+ return commands + commands_chains + commands_sets
def generate(firewall):
if not os.path.exists(nftables_conf):
@@ -328,7 +441,6 @@ def generate(firewall):
firewall['cleanup_commands'] = cleanup_commands(firewall)
render(nftables_conf, 'firewall/nftables.j2', firewall)
- render(nftables_defines_conf, 'firewall/nftables-defines.j2', firewall)
return None
def apply_sysfs(firewall):
@@ -408,6 +520,27 @@ def apply(firewall):
if install_result == 1:
raise ConfigError('Failed to apply firewall')
+ # set fireall group domain-group xxx
+ if 'group' in firewall:
+ if 'domain_group' in firewall['group']:
+ # T970 Enable a resolver (systemd daemon) that checks
+ # domain-group addresses and update entries for domains by timeout
+ # If router loaded without internet connection or for synchronization
+ call('systemctl restart vyos-domain-group-resolve.service')
+ for group, group_config in firewall['group']['domain_group'].items():
+ domains = []
+ if group_config.get('address') is not None:
+ for address in group_config.get('address'):
+ domains.append(address)
+ # Add elements to domain-group, try to resolve domain => ip
+ # and add elements to nft set
+ ip_dict = get_ips_domains_dict(domains)
+ elements = sum(ip_dict.values(), [])
+ nft_init_set(f'D_{group}')
+ nft_add_set_elements(f'D_{group}', elements)
+ else:
+ call('systemctl stop vyos-domain-group-resolve.service')
+
if 'state_policy' in firewall and not state_policy_rule_exists():
for chain in ['VYOS_FW_FORWARD', 'VYOS_FW_OUTPUT', 'VYOS_FW_LOCAL']:
cmd(f'nft insert rule ip filter {chain} jump VYOS_STATE_POLICY')
@@ -420,6 +553,12 @@ def apply(firewall):
if firewall['policy_resync']:
resync_policy_route()
+ if firewall['geoip_updated']:
+ # Call helper script to Update set contents
+ if 'name' in firewall['geoip_updated'] or 'ipv6_name' in firewall['geoip_updated']:
+ print('Updating GeoIP. Please wait...')
+ geoip_update(firewall)
+
post_apply_trap(firewall)
return None
diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py
index 4167594e3..7e146f446 100755
--- a/src/conf_mode/interfaces-bonding.py
+++ b/src/conf_mode/interfaces-bonding.py
@@ -36,6 +36,7 @@ from vyos.ifconfig import BondIf
from vyos.ifconfig import Section
from vyos.util import dict_search
from vyos.validate import has_address_configured
+from vyos.validate import has_vrf_configured
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -109,20 +110,26 @@ def get_config(config=None):
for interface, interface_config in bond['member']['interface'].items():
# Check if member interface is already member of another bridge
tmp = is_member(conf, interface, 'bridge')
- if tmp: interface_config.update({'is_bridge_member' : tmp})
+ if tmp: bond['member']['interface'][interface].update({'is_bridge_member' : tmp})
# Check if member interface is already member of a bond
tmp = is_member(conf, interface, 'bonding')
- if tmp and bond['ifname'] not in tmp:
- interface_config.update({'is_bond_member' : tmp})
+ for tmp in is_member(conf, interface, 'bonding'):
+ if bond['ifname'] == tmp:
+ continue
+ bond['member']['interface'][interface].update({'is_bond_member' : tmp})
# Check if member interface is used as source-interface on another interface
tmp = is_source_interface(conf, interface)
- if tmp: interface_config.update({'is_source_interface' : tmp})
+ if tmp: bond['member']['interface'][interface].update({'is_source_interface' : tmp})
# bond members must not have an assigned address
tmp = has_address_configured(conf, interface)
- if tmp: interface_config.update({'has_address' : ''})
+ if tmp: bond['member']['interface'][interface].update({'has_address' : {}})
+
+ # bond members must not have a VRF attached
+ tmp = has_vrf_configured(conf, interface)
+ if tmp: bond['member']['interface'][interface].update({'has_vrf' : {}})
return bond
@@ -167,11 +174,11 @@ def verify(bond):
raise ConfigError(error_msg + 'it does not exist!')
if 'is_bridge_member' in interface_config:
- tmp = next(iter(interface_config['is_bridge_member']))
+ tmp = interface_config['is_bridge_member']
raise ConfigError(error_msg + f'it is already a member of bridge "{tmp}"!')
if 'is_bond_member' in interface_config:
- tmp = next(iter(interface_config['is_bond_member']))
+ tmp = interface_config['is_bond_member']
raise ConfigError(error_msg + f'it is already a member of bond "{tmp}"!')
if 'is_source_interface' in interface_config:
@@ -181,6 +188,8 @@ def verify(bond):
if 'has_address' in interface_config:
raise ConfigError(error_msg + 'it has an address assigned!')
+ if 'has_vrf' in interface_config:
+ raise ConfigError(error_msg + 'it has a VRF assigned!')
if 'primary' in bond:
if bond['primary'] not in bond['member']['interface']:
diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py
index 38ae727c1..cd0d9003b 100755
--- a/src/conf_mode/interfaces-bridge.py
+++ b/src/conf_mode/interfaces-bridge.py
@@ -31,6 +31,7 @@ from vyos.configverify import verify_mirror_redirect
from vyos.configverify import verify_vrf
from vyos.ifconfig import BridgeIf
from vyos.validate import has_address_configured
+from vyos.validate import has_vrf_configured
from vyos.xml import defaults
from vyos.util import cmd
@@ -93,6 +94,10 @@ def get_config(config=None):
tmp = has_address_configured(conf, interface)
if tmp: bridge['member']['interface'][interface].update({'has_address' : ''})
+ # Bridge members must not have a VRF attached
+ tmp = has_vrf_configured(conf, interface)
+ if tmp: bridge['member']['interface'][interface].update({'has_vrf' : ''})
+
# VLAN-aware bridge members must not have VLAN interface configuration
tmp = has_vlan_subinterface_configured(conf,interface)
if 'enable_vlan' in bridge and tmp:
@@ -118,11 +123,11 @@ def verify(bridge):
raise ConfigError('Loopback interface "lo" can not be added to a bridge')
if 'is_bridge_member' in interface_config:
- tmp = next(iter(interface_config['is_bridge_member']))
+ tmp = interface_config['is_bridge_member']
raise ConfigError(error_msg + f'it is already a member of bridge "{tmp}"!')
if 'is_bond_member' in interface_config:
- tmp = next(iter(interface_config['is_bond_member']))
+ tmp = interface_config['is_bond_member']
raise ConfigError(error_msg + f'it is already a member of bond "{tmp}"!')
if 'is_source_interface' in interface_config:
@@ -132,9 +137,12 @@ def verify(bridge):
if 'has_address' in interface_config:
raise ConfigError(error_msg + 'it has an address assigned!')
+ if 'has_vrf' in interface_config:
+ raise ConfigError(error_msg + 'it has a VRF assigned!')
+
if 'enable_vlan' in bridge:
if 'has_vlan' in interface_config:
- raise ConfigError(error_msg + 'it has an VLAN subinterface assigned!')
+ raise ConfigError(error_msg + 'it has VLAN subinterface(s) assigned!')
if 'wlan' in interface:
raise ConfigError(error_msg + 'VLAN aware cannot be set!')
diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py
index fec4456fb..30e7a2af7 100755
--- a/src/conf_mode/interfaces-ethernet.py
+++ b/src/conf_mode/interfaces-ethernet.py
@@ -31,6 +31,7 @@ from vyos.configverify import verify_mtu
from vyos.configverify import verify_mtu_ipv6
from vyos.configverify import verify_vlan_config
from vyos.configverify import verify_vrf
+from vyos.configverify import verify_bond_bridge_member
from vyos.ethtool import Ethtool
from vyos.ifconfig import EthernetIf
from vyos.pki import find_chain
@@ -83,6 +84,7 @@ def verify(ethernet):
verify_dhcpv6(ethernet)
verify_address(ethernet)
verify_vrf(ethernet)
+ verify_bond_bridge_member(ethernet)
verify_eapol(ethernet)
verify_mirror_redirect(ethernet)
diff --git a/src/conf_mode/interfaces-geneve.py b/src/conf_mode/interfaces-geneve.py
index b9cf2fa3c..08cc3a48d 100755
--- a/src/conf_mode/interfaces-geneve.py
+++ b/src/conf_mode/interfaces-geneve.py
@@ -27,6 +27,7 @@ from vyos.configverify import verify_address
from vyos.configverify import verify_mtu_ipv6
from vyos.configverify import verify_bridge_delete
from vyos.configverify import verify_mirror_redirect
+from vyos.configverify import verify_bond_bridge_member
from vyos.ifconfig import GeneveIf
from vyos import ConfigError
@@ -64,6 +65,7 @@ def verify(geneve):
verify_mtu_ipv6(geneve)
verify_address(geneve)
+ verify_bond_bridge_member(geneve)
verify_mirror_redirect(geneve)
if 'remote' not in geneve:
diff --git a/src/conf_mode/interfaces-l2tpv3.py b/src/conf_mode/interfaces-l2tpv3.py
index 6a486f969..ca321e01d 100755
--- a/src/conf_mode/interfaces-l2tpv3.py
+++ b/src/conf_mode/interfaces-l2tpv3.py
@@ -26,6 +26,7 @@ from vyos.configverify import verify_address
from vyos.configverify import verify_bridge_delete
from vyos.configverify import verify_mtu_ipv6
from vyos.configverify import verify_mirror_redirect
+from vyos.configverify import verify_bond_bridge_member
from vyos.ifconfig import L2TPv3If
from vyos.util import check_kmod
from vyos.validate import is_addr_assigned
@@ -77,6 +78,7 @@ def verify(l2tpv3):
verify_mtu_ipv6(l2tpv3)
verify_address(l2tpv3)
+ verify_bond_bridge_member(l2tpv3)
verify_mirror_redirect(l2tpv3)
return None
diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py
index 279dd119b..03a010086 100755
--- a/src/conf_mode/interfaces-macsec.py
+++ b/src/conf_mode/interfaces-macsec.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020 VyOS maintainers and contributors
+# Copyright (C) 2020-2022 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
@@ -21,16 +21,20 @@ from sys import exit
from vyos.config import Config
from vyos.configdict import get_interface_dict
-from vyos.ifconfig import MACsecIf
-from vyos.ifconfig import Interface
-from vyos.template import render
-from vyos.util import call
+from vyos.configdict import is_node_changed
from vyos.configverify import verify_vrf
from vyos.configverify import verify_address
from vyos.configverify import verify_bridge_delete
from vyos.configverify import verify_mtu_ipv6
from vyos.configverify import verify_mirror_redirect
from vyos.configverify import verify_source_interface
+from vyos.configverify import verify_bond_bridge_member
+from vyos.ifconfig import MACsecIf
+from vyos.ifconfig import Interface
+from vyos.template import render
+from vyos.util import call
+from vyos.util import dict_search
+from vyos.util import is_systemd_service_running
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -55,6 +59,12 @@ def get_config(config=None):
source_interface = conf.return_effective_value(['source-interface'])
macsec.update({'source_interface': source_interface})
+ if is_node_changed(conf, base + [ifname, 'security']):
+ macsec.update({'shutdown_required': {}})
+
+ if is_node_changed(conf, base + [ifname, 'source_interface']):
+ macsec.update({'shutdown_required': {}})
+
return macsec
@@ -67,22 +77,15 @@ def verify(macsec):
verify_vrf(macsec)
verify_mtu_ipv6(macsec)
verify_address(macsec)
+ verify_bond_bridge_member(macsec)
verify_mirror_redirect(macsec)
- if not (('security' in macsec) and
- ('cipher' in macsec['security'])):
- raise ConfigError(
- 'Cipher suite must be set for MACsec "{ifname}"'.format(**macsec))
+ if dict_search('security.cipher', macsec) == None:
+ raise ConfigError('Cipher suite must be set for MACsec "{ifname}"'.format(**macsec))
- if (('security' in macsec) and
- ('encrypt' in macsec['security'])):
- tmp = macsec.get('security')
-
- if not (('mka' in tmp) and
- ('cak' in tmp['mka']) and
- ('ckn' in tmp['mka'])):
- raise ConfigError('Missing mandatory MACsec security '
- 'keys as encryption is enabled!')
+ if dict_search('security.encrypt', macsec) != None:
+ if dict_search('security.mka.cak', macsec) == None or dict_search('security.mka.ckn', macsec) == None:
+ raise ConfigError('Missing mandatory MACsec security keys as encryption is enabled!')
if 'source_interface' in macsec:
# MACsec adds a 40 byte overhead (32 byte MACsec + 8 bytes VLAN 802.1ad
@@ -97,33 +100,35 @@ def verify(macsec):
def generate(macsec):
- render(wpa_suppl_conf.format(**macsec),
- 'macsec/wpa_supplicant.conf.j2', macsec)
+ render(wpa_suppl_conf.format(**macsec), 'macsec/wpa_supplicant.conf.j2', macsec)
return None
def apply(macsec):
- # Remove macsec interface
- if 'deleted' in macsec:
- call('systemctl stop wpa_supplicant-macsec@{source_interface}'
- .format(**macsec))
+ systemd_service = 'wpa_supplicant-macsec@{source_interface}'.format(**macsec)
+
+ # Remove macsec interface on deletion or mandatory parameter change
+ if 'deleted' in macsec or 'shutdown_required' in macsec:
+ call(f'systemctl stop {systemd_service}')
if macsec['ifname'] in interfaces():
tmp = MACsecIf(macsec['ifname'])
tmp.remove()
- # delete configuration on interface removal
- if os.path.isfile(wpa_suppl_conf.format(**macsec)):
- os.unlink(wpa_suppl_conf.format(**macsec))
+ if 'deleted' in macsec:
+ # delete configuration on interface removal
+ if os.path.isfile(wpa_suppl_conf.format(**macsec)):
+ os.unlink(wpa_suppl_conf.format(**macsec))
- else:
- # It is safe to "re-create" the interface always, there is a sanity
- # check that the interface will only be create if its non existent
- i = MACsecIf(**macsec)
- i.update(macsec)
+ return None
+
+ # It is safe to "re-create" the interface always, there is a sanity
+ # check that the interface will only be create if its non existent
+ i = MACsecIf(**macsec)
+ i.update(macsec)
- call('systemctl restart wpa_supplicant-macsec@{source_interface}'
- .format(**macsec))
+ if not is_systemd_service_running(systemd_service) or 'shutdown_required' in macsec:
+ call(f'systemctl reload-or-restart {systemd_service}')
return None
diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py
index 4750ca3e8..ef745d737 100755
--- a/src/conf_mode/interfaces-openvpn.py
+++ b/src/conf_mode/interfaces-openvpn.py
@@ -36,9 +36,12 @@ from vyos.configdict import is_node_changed
from vyos.configverify import verify_vrf
from vyos.configverify import verify_bridge_delete
from vyos.configverify import verify_mirror_redirect
+from vyos.configverify import verify_bond_bridge_member
from vyos.ifconfig import VTunIf
from vyos.pki import load_dh_parameters
from vyos.pki import load_private_key
+from vyos.pki import sort_ca_chain
+from vyos.pki import verify_ca_chain
from vyos.pki import wrap_certificate
from vyos.pki import wrap_crl
from vyos.pki import wrap_dh_parameters
@@ -148,8 +151,14 @@ def verify_pki(openvpn):
if 'ca_certificate' not in tls:
raise ConfigError(f'Must specify "tls ca-certificate" on openvpn interface {interface}')
- if tls['ca_certificate'] not in pki['ca']:
- raise ConfigError(f'Invalid CA certificate on openvpn interface {interface}')
+ for ca_name in tls['ca_certificate']:
+ if ca_name not in pki['ca']:
+ raise ConfigError(f'Invalid CA certificate on openvpn interface {interface}')
+
+ if len(tls['ca_certificate']) > 1:
+ sorted_chain = sort_ca_chain(tls['ca_certificate'], pki['ca'])
+ if not verify_ca_chain(sorted_chain, pki['ca']):
+ raise ConfigError(f'CA certificates are not a valid chain')
if mode != 'client' and 'auth_key' not in tls:
if 'certificate' not in tls:
@@ -495,6 +504,7 @@ def verify(openvpn):
raise ConfigError('Username for authentication is missing')
verify_vrf(openvpn)
+ verify_bond_bridge_member(openvpn)
verify_mirror_redirect(openvpn)
return None
@@ -516,21 +526,28 @@ def generate_pki_files(openvpn):
if tls:
if 'ca_certificate' in tls:
- cert_name = tls['ca_certificate']
- pki_ca = pki['ca'][cert_name]
+ cert_path = os.path.join(cfg_dir, f'{interface}_ca.pem')
+ crl_path = os.path.join(cfg_dir, f'{interface}_crl.pem')
- if 'certificate' in pki_ca:
- cert_path = os.path.join(cfg_dir, f'{interface}_ca.pem')
- write_file(cert_path, wrap_certificate(pki_ca['certificate']),
- user=user, group=group, mode=0o600)
+ if os.path.exists(cert_path):
+ os.unlink(cert_path)
+
+ if os.path.exists(crl_path):
+ os.unlink(crl_path)
+
+ for cert_name in sort_ca_chain(tls['ca_certificate'], pki['ca']):
+ pki_ca = pki['ca'][cert_name]
+
+ if 'certificate' in pki_ca:
+ write_file(cert_path, wrap_certificate(pki_ca['certificate']) + "\n",
+ user=user, group=group, mode=0o600, append=True)
- if 'crl' in pki_ca:
- for crl in pki_ca['crl']:
- crl_path = os.path.join(cfg_dir, f'{interface}_crl.pem')
- write_file(crl_path, wrap_crl(crl), user=user, group=group,
- mode=0o600)
+ if 'crl' in pki_ca:
+ for crl in pki_ca['crl']:
+ write_file(crl_path, wrap_crl(crl) + "\n", user=user, group=group,
+ mode=0o600, append=True)
- openvpn['tls']['crl'] = True
+ openvpn['tls']['crl'] = True
if 'certificate' in tls:
cert_name = tls['certificate']
diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py
index 1cd3fe276..f26a50a0e 100755
--- a/src/conf_mode/interfaces-pseudo-ethernet.py
+++ b/src/conf_mode/interfaces-pseudo-ethernet.py
@@ -26,6 +26,7 @@ from vyos.configverify import verify_source_interface
from vyos.configverify import verify_vlan_config
from vyos.configverify import verify_mtu_parent
from vyos.configverify import verify_mirror_redirect
+from vyos.configverify import verify_bond_bridge_member
from vyos.ifconfig import MACVLANIf
from vyos import ConfigError
diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py
index eff7f373c..acef1fda7 100755
--- a/src/conf_mode/interfaces-tunnel.py
+++ b/src/conf_mode/interfaces-tunnel.py
@@ -29,6 +29,7 @@ from vyos.configverify import verify_mtu_ipv6
from vyos.configverify import verify_mirror_redirect
from vyos.configverify import verify_vrf
from vyos.configverify import verify_tunnel
+from vyos.configverify import verify_bond_bridge_member
from vyos.ifconfig import Interface
from vyos.ifconfig import Section
from vyos.ifconfig import TunnelIf
@@ -158,6 +159,7 @@ def verify(tunnel):
verify_mtu_ipv6(tunnel)
verify_address(tunnel)
verify_vrf(tunnel)
+ verify_bond_bridge_member(tunnel)
verify_mirror_redirect(tunnel)
if 'source_interface' in tunnel:
diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py
index f44d754ba..bf0f6840d 100755
--- a/src/conf_mode/interfaces-vxlan.py
+++ b/src/conf_mode/interfaces-vxlan.py
@@ -29,6 +29,7 @@ from vyos.configverify import verify_bridge_delete
from vyos.configverify import verify_mtu_ipv6
from vyos.configverify import verify_mirror_redirect
from vyos.configverify import verify_source_interface
+from vyos.configverify import verify_bond_bridge_member
from vyos.ifconfig import Interface
from vyos.ifconfig import VXLANIf
from vyos.template import is_ipv6
@@ -144,6 +145,7 @@ def verify(vxlan):
verify_mtu_ipv6(vxlan)
verify_address(vxlan)
+ verify_bond_bridge_member(vxlan)
verify_mirror_redirect(vxlan)
return None
diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py
index 180ffa507..61bab2feb 100755
--- a/src/conf_mode/interfaces-wireguard.py
+++ b/src/conf_mode/interfaces-wireguard.py
@@ -29,6 +29,7 @@ from vyos.configverify import verify_address
from vyos.configverify import verify_bridge_delete
from vyos.configverify import verify_mtu_ipv6
from vyos.configverify import verify_mirror_redirect
+from vyos.configverify import verify_bond_bridge_member
from vyos.ifconfig import WireGuardIf
from vyos.util import check_kmod
from vyos.util import check_port_availability
@@ -71,6 +72,7 @@ def verify(wireguard):
verify_mtu_ipv6(wireguard)
verify_address(wireguard)
verify_vrf(wireguard)
+ verify_bond_bridge_member(wireguard)
verify_mirror_redirect(wireguard)
if 'private_key' not in wireguard:
diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py
index d34297063..dd798b5a2 100755
--- a/src/conf_mode/interfaces-wireless.py
+++ b/src/conf_mode/interfaces-wireless.py
@@ -30,6 +30,7 @@ from vyos.configverify import verify_source_interface
from vyos.configverify import verify_mirror_redirect
from vyos.configverify import verify_vlan_config
from vyos.configverify import verify_vrf
+from vyos.configverify import verify_bond_bridge_member
from vyos.ifconfig import WiFiIf
from vyos.template import render
from vyos.util import call
@@ -194,6 +195,7 @@ def verify(wifi):
verify_address(wifi)
verify_vrf(wifi)
+ verify_bond_bridge_member(wifi)
verify_mirror_redirect(wifi)
# use common function to verify VLAN configuration
diff --git a/src/conf_mode/ntp.py b/src/conf_mode/ntp.py
index 0d6ec9ace..5490a794d 100755
--- a/src/conf_mode/ntp.py
+++ b/src/conf_mode/ntp.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2021 VyOS maintainers and contributors
+# Copyright (C) 2018-2022 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
@@ -18,9 +18,11 @@ import os
from vyos.config import Config
from vyos.configverify import verify_vrf
-from vyos import ConfigError
+from vyos.configverify import verify_interface_exists
from vyos.util import call
+from vyos.util import get_interface_config
from vyos.template import render
+from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -49,6 +51,20 @@ def verify(ntp):
raise ConfigError('NTP server not configured')
verify_vrf(ntp)
+
+ if 'interface' in ntp:
+ # If ntpd should listen on a given interface, ensure it exists
+ for interface in ntp['interface']:
+ verify_interface_exists(interface)
+
+ # If we run in a VRF, our interface must belong to this VRF, too
+ if 'vrf' in ntp:
+ tmp = get_interface_config(interface)
+ vrf_name = ntp['vrf']
+ if 'master' not in tmp or tmp['master'] != vrf_name:
+ raise ConfigError(f'NTP runs in VRF "{vrf_name}" - "{interface}" '\
+ f'does not belong to this VRF!')
+
return None
def generate(ntp):
diff --git a/src/conf_mode/policy-route-interface.py b/src/conf_mode/policy-route-interface.py
index 1108aebe6..58c5fd93d 100755
--- a/src/conf_mode/policy-route-interface.py
+++ b/src/conf_mode/policy-route-interface.py
@@ -24,6 +24,7 @@ from vyos.config import Config
from vyos.ifconfig import Section
from vyos.template import render
from vyos.util import cmd
+from vyos.util import run
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -47,6 +48,11 @@ def get_config(config=None):
return if_policy
+def verify_chain(table, chain):
+ # Verify policy route applied
+ code = run(f'nft list chain {table} {chain}')
+ return code == 0
+
def verify(if_policy):
# bail out early - looks like removal from running config
if not if_policy:
@@ -62,6 +68,12 @@ def verify(if_policy):
if route_name not in if_policy['policy'][route]:
raise ConfigError(f'Invalid policy route name "{name}"')
+ nft_prefix = 'VYOS_PBR6_' if route == 'route6' else 'VYOS_PBR_'
+ nft_table = 'ip6 mangle' if route == 'route6' else 'ip mangle'
+
+ if not verify_chain(nft_table, nft_prefix + route_name):
+ raise ConfigError('Policy route did not apply')
+
return None
def generate(if_policy):
diff --git a/src/conf_mode/policy-route.py b/src/conf_mode/policy-route.py
index 5de341beb..9fddbd2c6 100755
--- a/src/conf_mode/policy-route.py
+++ b/src/conf_mode/policy-route.py
@@ -25,6 +25,7 @@ from vyos.config import Config
from vyos.template import render
from vyos.util import cmd
from vyos.util import dict_search_args
+from vyos.util import dict_search_recursive
from vyos.util import run
from vyos import ConfigError
from vyos import airbag
@@ -33,6 +34,9 @@ airbag.enable()
mark_offset = 0x7FFFFFFF
nftables_conf = '/run/nftables_policy.conf'
+ROUTE_PREFIX = 'VYOS_PBR_'
+ROUTE6_PREFIX = 'VYOS_PBR6_'
+
preserve_chains = [
'VYOS_PBR_PREROUTING',
'VYOS_PBR_POSTROUTING',
@@ -46,6 +50,16 @@ valid_groups = [
'port_group'
]
+group_set_prefix = {
+ 'A_': 'address_group',
+ 'A6_': 'ipv6_address_group',
+# 'D_': 'domain_group',
+ 'M_': 'mac_group',
+ 'N_': 'network_group',
+ 'N6_': 'ipv6_network_group',
+ 'P_': 'port_group'
+}
+
def get_policy_interfaces(conf):
out = {}
interfaces = conf.get_config_dict(['interfaces'], key_mangling=('-', '_'), get_first_key=True,
@@ -166,37 +180,55 @@ def verify(policy):
return None
-def cleanup_rule(table, jump_chain):
- commands = []
- results = cmd(f'nft -a list table {table}').split("\n")
- for line in results:
- if f'jump {jump_chain}' in line:
- handle_search = re.search('handle (\d+)', line)
- if handle_search:
- commands.append(f'delete rule {table} {chain} handle {handle_search[1]}')
- return commands
-
def cleanup_commands(policy):
commands = []
+ commands_chains = []
+ commands_sets = []
for table in ['ip mangle', 'ip6 mangle']:
- json_str = cmd(f'nft -j list table {table}')
+ route_node = 'route' if table == 'ip mangle' else 'route6'
+ chain_prefix = ROUTE_PREFIX if table == 'ip mangle' else ROUTE6_PREFIX
+
+ json_str = cmd(f'nft -t -j list table {table}')
obj = loads(json_str)
if 'nftables' not in obj:
continue
for item in obj['nftables']:
if 'chain' in item:
chain = item['chain']['name']
- if not chain.startswith("VYOS_PBR"):
+ if chain in preserve_chains or not chain.startswith("VYOS_PBR"):
continue
+
+ if dict_search_args(policy, route_node, chain.replace(chain_prefix, "", 1)) != None:
+ commands.append(f'flush chain {table} {chain}')
+ else:
+ commands_chains.append(f'delete chain {table} {chain}')
+
+ if 'rule' in item:
+ rule = item['rule']
+ chain = rule['chain']
+ handle = rule['handle']
+
if chain not in preserve_chains:
- if table == 'ip mangle' and dict_search_args(policy, 'route', chain.replace("VYOS_PBR_", "", 1)):
- commands.append(f'flush chain {table} {chain}')
- elif table == 'ip6 mangle' and dict_search_args(policy, 'route6', chain.replace("VYOS_PBR6_", "", 1)):
- commands.append(f'flush chain {table} {chain}')
- else:
- commands += cleanup_rule(table, chain)
- commands.append(f'delete chain {table} {chain}')
- return commands
+ continue
+
+ target, _ = next(dict_search_recursive(rule['expr'], 'target'))
+
+ if target.startswith(chain_prefix):
+ if dict_search_args(policy, route_node, target.replace(chain_prefix, "", 1)) == None:
+ commands.append(f'delete rule {table} {chain} handle {handle}')
+
+ if 'set' in item:
+ set_name = item['set']['name']
+
+ for prefix, group_type in group_set_prefix.items():
+ if set_name.startswith(prefix):
+ group_name = set_name.replace(prefix, "", 1)
+ if dict_search_args(policy, 'firewall_group', group_type, group_name) != None:
+ commands_sets.append(f'flush set {table} {set_name}')
+ else:
+ commands_sets.append(f'delete set {table} {set_name}')
+
+ return commands + commands_chains + commands_sets
def generate(policy):
if not os.path.exists(nftables_conf):
diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py
index cd46cbcb4..5aa643476 100755
--- a/src/conf_mode/protocols_bgp.py
+++ b/src/conf_mode/protocols_bgp.py
@@ -19,6 +19,7 @@ import os
from sys import exit
from sys import argv
+from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.configverify import verify_prefix_list
@@ -100,6 +101,17 @@ def verify_remote_as(peer_config, bgp_config):
return None
+def verify_afi(peer_config, bgp_config):
+ if 'address_family' in peer_config:
+ return True
+
+ if 'peer_group' in peer_config:
+ peer_group_name = peer_config['peer_group']
+ tmp = dict_search(f'peer_group.{peer_group_name}.address_family', bgp_config)
+ if tmp: return True
+
+ return False
+
def verify(bgp):
if not bgp or 'deleted' in bgp:
if 'dependent_vrfs' in bgp:
@@ -164,6 +176,9 @@ def verify(bgp):
if not verify_remote_as(peer_config, bgp):
raise ConfigError(f'Neighbor "{peer}" remote-as must be set!')
+ if not verify_afi(peer_config, bgp):
+ Warning(f'BGP neighbor "{peer}" requires address-family!')
+
# Peer-group member cannot override remote-as of peer-group
if 'peer_group' in peer_config:
peer_group = peer_config['peer_group']
diff --git a/src/conf_mode/protocols_nhrp.py b/src/conf_mode/protocols_nhrp.py
index 56939955d..b247ce2ab 100755
--- a/src/conf_mode/protocols_nhrp.py
+++ b/src/conf_mode/protocols_nhrp.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021-2022 VyOS maintainers and contributors
+# Copyright (C) 2021 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
@@ -81,11 +81,6 @@ def verify(nhrp):
for map_name, map_conf in nhrp_conf['dynamic_map'].items():
if 'nbma_domain_name' not in map_conf:
raise ConfigError(f'nbma-domain-name missing on dynamic-map {map_name} on tunnel {name}')
-
- if 'cisco_authentication' in nhrp_conf:
- if len(nhrp_conf['cisco_authentication']) > 8:
- raise ConfigError('Maximum length of the secret is 8 characters!')
-
return None
def generate(nhrp):
diff --git a/src/conf_mode/service_event_handler.py b/src/conf_mode/service_event_handler.py
new file mode 100755
index 000000000..5440d1056
--- /dev/null
+++ b/src/conf_mode/service_event_handler.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 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 json
+from pathlib import Path
+
+from vyos.config import Config
+from vyos.util import call, dict_search
+from vyos import ConfigError
+from vyos import airbag
+
+airbag.enable()
+
+service_name = 'vyos-event-handler'
+service_conf = Path(f'/run/{service_name}.conf')
+
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+
+ base = ['service', 'event-handler', 'event']
+ config = conf.get_config_dict(base,
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
+
+ return config
+
+
+def verify(config):
+ # bail out early - looks like removal from running config
+ if not config:
+ return None
+
+ for name, event_config in config.items():
+ if not dict_search('filter.pattern', event_config) or not dict_search(
+ 'script.path', event_config):
+ raise ConfigError(
+ 'Event-handler: both pattern and script path items are mandatory'
+ )
+
+ if dict_search('script.environment.message', event_config):
+ raise ConfigError(
+ 'Event-handler: "message" environment variable is reserved for log message text'
+ )
+
+
+def generate(config):
+ if not config:
+ # Remove old config and return
+ service_conf.unlink(missing_ok=True)
+ return None
+
+ # Write configuration file
+ conf_json = json.dumps(config, indent=4)
+ service_conf.write_text(conf_json)
+
+ return None
+
+
+def apply(config):
+ if config:
+ call(f'systemctl restart {service_name}.service')
+ else:
+ call(f'systemctl stop {service_name}.service')
+
+
+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_ipoe-server.py b/src/conf_mode/service_ipoe-server.py
index 559d1bcd5..61f484129 100755
--- a/src/conf_mode/service_ipoe-server.py
+++ b/src/conf_mode/service_ipoe-server.py
@@ -53,6 +53,8 @@ default_config_data = {
'radius_nas_ip': '',
'radius_source_address': '',
'radius_shaper_attr': '',
+ 'radius_shaper_enable': False,
+ 'radius_shaper_multiplier': '',
'radius_shaper_vendor': '',
'radius_dynamic_author': '',
'thread_cnt': get_half_cpus()
@@ -196,6 +198,18 @@ def get_config(config=None):
if conf.exists(['nas-ip-address']):
ipoe['radius_nas_ip'] = conf.return_value(['nas-ip-address'])
+ if conf.exists(['rate-limit', 'attribute']):
+ ipoe['radius_shaper_attr'] = conf.return_value(['rate-limit', 'attribute'])
+
+ if conf.exists(['rate-limit', 'enable']):
+ ipoe['radius_shaper_enable'] = True
+
+ if conf.exists(['rate-limit', 'multiplier']):
+ ipoe['radius_shaper_multiplier'] = conf.return_value(['rate-limit', 'multiplier'])
+
+ if conf.exists(['rate-limit', 'vendor']):
+ ipoe['radius_shaper_vendor'] = conf.return_value(['rate-limit', 'vendor'])
+
if conf.exists(['source-address']):
ipoe['radius_source_address'] = conf.return_value(['source-address'])
diff --git a/src/conf_mode/service_monitoring_telegraf.py b/src/conf_mode/service_monitoring_telegraf.py
index daf75d740..62f5e1ddf 100755
--- a/src/conf_mode/service_monitoring_telegraf.py
+++ b/src/conf_mode/service_monitoring_telegraf.py
@@ -99,10 +99,6 @@ def get_config(config=None):
monitoring['interfaces_ethernet'] = get_interfaces('ethernet', vlan=False)
monitoring['nft_chains'] = get_nft_filter_chains()
- if 'authentication' in monitoring or \
- 'url' in monitoring:
- monitoring['influxdb_configured'] = True
-
# Redefine azure group-metrics 'single-table' and 'table-per-metric'
if 'azure_data_explorer' in monitoring:
if 'single-table' in monitoring['azure_data_explorer']['group_metrics']:
@@ -119,6 +115,9 @@ def get_config(config=None):
# Ignore default XML values if config doesn't exists
# Delete key from dict
+ if not conf.exists(base + ['influxdb']):
+ del monitoring['influxdb']
+
if not conf.exists(base + ['prometheus-client']):
del monitoring['prometheus_client']
@@ -132,14 +131,15 @@ def verify(monitoring):
if not monitoring:
return None
- if 'influxdb_configured' in monitoring:
- if 'authentication' not in monitoring or \
- 'organization' not in monitoring['authentication'] or \
- 'token' not in monitoring['authentication']:
- raise ConfigError(f'Authentication "organization and token" are mandatory!')
+ # Verify influxdb
+ if 'influxdb' in monitoring:
+ if 'authentication' not in monitoring['influxdb'] or \
+ 'organization' not in monitoring['influxdb']['authentication'] or \
+ 'token' not in monitoring['influxdb']['authentication']:
+ raise ConfigError(f'influxdb authentication "organization and token" are mandatory!')
- if 'url' not in monitoring:
- raise ConfigError(f'Monitoring "url" is mandatory!')
+ if 'url' not in monitoring['influxdb']:
+ raise ConfigError(f'Monitoring influxdb "url" is mandatory!')
# Verify azure-data-explorer
if 'azure_data_explorer' in monitoring:
diff --git a/src/conf_mode/service_router-advert.py b/src/conf_mode/service_router-advert.py
index 71b758399..ff7caaa84 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-2021 VyOS maintainers and contributors
+# Copyright (C) 2018-2022 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,7 +17,7 @@
import os
from sys import exit
-
+from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.template import render
@@ -79,22 +79,35 @@ def verify(rtradv):
if 'interface' not in rtradv:
return None
- for interface in rtradv['interface']:
- interface = rtradv['interface'][interface]
+ for interface, interface_config in rtradv['interface'].items():
if 'prefix' in interface:
- for prefix in interface['prefix']:
- prefix = interface['prefix'][prefix]
- valid_lifetime = prefix['valid_lifetime']
+ for prefix, prefix_config in interface_config['prefix'].items():
+ valid_lifetime = prefix_config['valid_lifetime']
if valid_lifetime == 'infinity':
valid_lifetime = 4294967295
- preferred_lifetime = prefix['preferred_lifetime']
+ preferred_lifetime = prefix_config['preferred_lifetime']
if preferred_lifetime == 'infinity':
preferred_lifetime = 4294967295
if not (int(valid_lifetime) > int(preferred_lifetime)):
raise ConfigError('Prefix valid-lifetime must be greater then preferred-lifetime')
+ if 'name_server_lifetime' in interface_config:
+ # man page states:
+ # The maximum duration how long the RDNSS entries are used for name
+ # resolution. A value of 0 means the nameserver must no longer be
+ # used. The value, if not 0, must be at least MaxRtrAdvInterval. To
+ # 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!')
+ if lifetime > 2* interval_max:
+ Warning(f'RDNSS lifetime should not exceed "{2 * interval_max}" which is two times "interval max"!')
+
return None
def generate(rtradv):
@@ -105,15 +118,16 @@ def generate(rtradv):
return None
def apply(rtradv):
+ systemd_service = 'radvd.service'
if not rtradv:
# bail out early - looks like removal from running config
- call('systemctl stop radvd.service')
+ call(f'systemctl stop {systemd_service}')
if os.path.exists(config_file):
os.unlink(config_file)
return None
- call('systemctl restart radvd.service')
+ call(f'systemctl reload-or-restart {systemd_service}')
return None
diff --git a/src/conf_mode/system-ip.py b/src/conf_mode/system-ip.py
index 05fc3a97a..0c5063ed3 100755
--- a/src/conf_mode/system-ip.py
+++ b/src/conf_mode/system-ip.py
@@ -64,6 +64,11 @@ def apply(opt):
value = '0' if (tmp != None) else '1'
write_file('/proc/sys/net/ipv4/conf/all/forwarding', value)
+ # enable/disable IPv4 directed broadcast forwarding
+ tmp = dict_search('disable_directed_broadcast', opt)
+ value = '0' if (tmp != None) else '1'
+ write_file('/proc/sys/net/ipv4/conf/all/bc_forwarding', value)
+
# configure multipath
tmp = dict_search('multipath.ignore_unreachable_nexthops', opt)
value = '1' if (tmp != None) else '0'
diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py
index c717286ae..3dcbc995c 100755
--- a/src/conf_mode/system-login.py
+++ b/src/conf_mode/system-login.py
@@ -231,7 +231,7 @@ def apply(login):
if tmp: command += f" --home '{tmp}'"
else: command += f" --home '/home/{user}'"
- command += f' --groups frrvty,vyattacfg,sudo,adm,dip,disk {user}'
+ command += f' --groups frr,frrvty,vyattacfg,sudo,adm,dip,disk {user}'
try:
cmd(command)
diff --git a/src/conf_mode/system-syslog.py b/src/conf_mode/system-syslog.py
index a9d3bbe31..20132456c 100755
--- a/src/conf_mode/system-syslog.py
+++ b/src/conf_mode/system-syslog.py
@@ -52,8 +52,6 @@ def get_config(config=None):
{
'global': {
'log-file': '/var/log/messages',
- 'max-size': 262144,
- 'action-on-max-size': '/usr/sbin/logrotate /etc/logrotate.d/vyos-rsyslog',
'selectors': '*.notice;local7.debug',
'max-files': '5',
'preserver_fqdn': False
diff --git a/src/conf_mode/vpn_sstp.py b/src/conf_mode/vpn_sstp.py
index db53463cf..23e5162ba 100755
--- a/src/conf_mode/vpn_sstp.py
+++ b/src/conf_mode/vpn_sstp.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2020 VyOS maintainers and contributors
+# Copyright (C) 2018-2022 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
@@ -20,6 +20,7 @@ from sys import exit
from vyos.config import Config
from vyos.configdict import get_accel_dict
+from vyos.configdict import dict_merge
from vyos.configverify import verify_accel_ppp_base_service
from vyos.pki import wrap_certificate
from vyos.pki import wrap_private_key
@@ -50,10 +51,10 @@ def get_config(config=None):
# retrieve common dictionary keys
sstp = get_accel_dict(conf, base, sstp_chap_secrets)
-
if sstp:
sstp['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'),
- get_first_key=True, no_tag_node_value_mangle=True)
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
return sstp
@@ -121,7 +122,6 @@ def generate(sstp):
ca_cert_name = sstp['ssl']['ca_certificate']
pki_ca = sstp['pki']['ca'][ca_cert_name]
-
write_file(cert_file_path, wrap_certificate(pki_cert['certificate']))
write_file(cert_key_path, wrap_private_key(pki_cert['private']['key']))
write_file(ca_cert_file_path, wrap_certificate(pki_ca['certificate']))
diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py
index 972d0289b..1b4156895 100755
--- a/src/conf_mode/vrf.py
+++ b/src/conf_mode/vrf.py
@@ -113,8 +113,14 @@ 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"]
table_ids = []
for name, config in vrf['name'].items():
+ # Reserved VRF names
+ if name in reserved_names:
+ raise ConfigError(f'VRF name "{name}" is reserved and connot be used!')
+
# table id is mandatory
if 'table' not in config:
raise ConfigError(f'VRF "{name}" table id is mandatory!')
diff --git a/src/conf_mode/zone_policy.py b/src/conf_mode/zone_policy.py
index 070a4deea..a52c52706 100755
--- a/src/conf_mode/zone_policy.py
+++ b/src/conf_mode/zone_policy.py
@@ -155,7 +155,7 @@ def get_local_from(zone_policy, local_zone_name):
def cleanup_commands():
commands = []
for table in ['ip filter', 'ip6 filter']:
- json_str = cmd(f'nft -j list table {table}')
+ json_str = cmd(f'nft -t -j list table {table}')
obj = loads(json_str)
if 'nftables' not in obj:
continue