summaryrefslogtreecommitdiff
path: root/src/conf_mode
diff options
context:
space:
mode:
Diffstat (limited to 'src/conf_mode')
-rwxr-xr-xsrc/conf_mode/bcast_relay.py2
-rwxr-xr-xsrc/conf_mode/dhcp_relay.py79
-rwxr-xr-xsrc/conf_mode/dhcpv6_relay.py94
-rwxr-xr-xsrc/conf_mode/dns_forwarding.py4
-rwxr-xr-xsrc/conf_mode/dynamic_dns.py4
-rwxr-xr-xsrc/conf_mode/https.py2
-rwxr-xr-xsrc/conf_mode/igmp_proxy.py94
-rwxr-xr-xsrc/conf_mode/interfaces-bridge.py16
-rwxr-xr-xsrc/conf_mode/interfaces-openvpn.py30
-rwxr-xr-xsrc/conf_mode/interfaces-pppoe.py22
-rwxr-xr-xsrc/conf_mode/interfaces-tunnel.py781
-rwxr-xr-xsrc/conf_mode/interfaces-wireguard.py3
-rwxr-xr-xsrc/conf_mode/interfaces-wireless.py7
-rwxr-xr-xsrc/conf_mode/interfaces-wirelessmodem.py10
-rwxr-xr-xsrc/conf_mode/ipsec-settings.py8
-rwxr-xr-xsrc/conf_mode/nat.py218
-rwxr-xr-xsrc/conf_mode/ntp.py4
-rwxr-xr-xsrc/conf_mode/policy-local-route.py110
-rwxr-xr-xsrc/conf_mode/protocols_bgp.py66
-rwxr-xr-xsrc/conf_mode/protocols_igmp.py28
-rwxr-xr-xsrc/conf_mode/protocols_isis.py7
-rwxr-xr-xsrc/conf_mode/protocols_mpls.py407
-rwxr-xr-xsrc/conf_mode/protocols_pim.py28
-rwxr-xr-xsrc/conf_mode/service_ids_fastnetmon.py6
-rwxr-xr-xsrc/conf_mode/service_ipoe-server.py2
-rwxr-xr-xsrc/conf_mode/service_pppoe-server.py4
-rwxr-xr-xsrc/conf_mode/service_router-advert.py2
-rwxr-xr-xsrc/conf_mode/ssh.py4
-rwxr-xr-xsrc/conf_mode/system-login.py2
-rwxr-xr-xsrc/conf_mode/system-option.py (renamed from src/conf_mode/system-options.py)17
-rwxr-xr-xsrc/conf_mode/system-syslog.py4
-rwxr-xr-xsrc/conf_mode/system_lcd.py4
-rwxr-xr-xsrc/conf_mode/tftp_server.py2
-rwxr-xr-xsrc/conf_mode/vpn_l2tp.py2
-rwxr-xr-xsrc/conf_mode/vpn_openconnect.py17
-rwxr-xr-xsrc/conf_mode/vpn_pptp.py4
-rwxr-xr-xsrc/conf_mode/vpn_sstp.py4
37 files changed, 680 insertions, 1418 deletions
diff --git a/src/conf_mode/bcast_relay.py b/src/conf_mode/bcast_relay.py
index 78daeb6be..d93a2a8f4 100755
--- a/src/conf_mode/bcast_relay.py
+++ b/src/conf_mode/bcast_relay.py
@@ -79,7 +79,7 @@ def generate(relay):
config['instance'] = instance
render(config_file_base + instance, 'bcast-relay/udp-broadcast-relay.tmpl',
- config, trim_blocks=True)
+ config)
return None
diff --git a/src/conf_mode/dhcp_relay.py b/src/conf_mode/dhcp_relay.py
index 352865b9d..6352e0b4a 100755
--- a/src/conf_mode/dhcp_relay.py
+++ b/src/conf_mode/dhcp_relay.py
@@ -19,81 +19,43 @@ import os
from sys import exit
from vyos.config import Config
+from vyos.configdict import dict_merge
from vyos.template import render
from vyos.util import call
+from vyos.util import dict_search
+from vyos.xml import defaults
from vyos import ConfigError
-
from vyos import airbag
airbag.enable()
-config_file = r'/run/dhcp-relay/dhcp.conf'
-
-default_config_data = {
- 'interface': [],
- 'server': [],
- 'options': [],
- 'hop_count': '10',
- 'relay_agent_packets': 'forward'
-}
+config_file = r'/run/dhcp-relay/dhcrelay.conf'
def get_config(config=None):
- relay = default_config_data
if config:
conf = config
else:
conf = Config()
- if not conf.exists(['service', 'dhcp-relay']):
+ base = ['service', 'dhcp-relay']
+ if not conf.exists(base):
return None
- else:
- conf.set_level(['service', 'dhcp-relay'])
-
- # Network interfaces to listen on
- if conf.exists(['interface']):
- relay['interface'] = conf.return_values(['interface'])
-
- # Servers equal to the address of the DHCP server(s)
- if conf.exists(['server']):
- relay['server'] = conf.return_values(['server'])
-
- conf.set_level(['service', 'dhcp-relay', 'relay-options'])
-
- if conf.exists(['hop-count']):
- count = '-c ' + conf.return_value(['hop-count'])
- relay['options'].append(count)
-
- # Specify the maximum packet size to send to a DHCPv4/BOOTP server.
- # This might be done to allow sufficient space for addition of relay agent
- # options while still fitting into the Ethernet MTU size.
- #
- # Available in DHCPv4 mode only:
- if conf.exists(['max-size']):
- size = '-A ' + conf.return_value(['max-size'])
- relay['options'].append(size)
-
- # Control the handling of incoming DHCPv4 packets which already contain
- # relay agent options. If such a packet does not have giaddr set in its
- # header, the DHCP standard requires that the packet be discarded. However,
- # if giaddr is set, the relay agent may handle the situation in four ways:
- # It may append its own set of relay options to the packet, leaving the
- # supplied option field intact; it may replace the existing agent option
- # field; it may forward the packet unchanged; or, it may discard it.
- #
- # Available in DHCPv4 mode only:
- if conf.exists(['relay-agents-packets']):
- pkt = '-a -m ' + conf.return_value(['relay-agents-packets'])
- relay['options'].append(pkt)
+
+ relay = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=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)
+ relay = dict_merge(default_values, relay)
return relay
def verify(relay):
# bail out early - looks like removal from running config
- if relay is None:
+ if not relay:
return None
- if 'lo' in relay['interface']:
+ if 'lo' in (dict_search('interface', relay) or []):
raise ConfigError('DHCP relay does not support the loopback interface.')
- if len(relay['server']) == 0:
+ if 'server' not in relay :
raise ConfigError('No DHCP relay server(s) configured.\n' \
'At least one DHCP relay server required.')
@@ -104,17 +66,18 @@ def generate(relay):
if not relay:
return None
- render(config_file, 'dhcp-relay/config.tmpl', relay)
+ render(config_file, 'dhcp-relay/dhcrelay.conf.tmpl', relay)
return None
def apply(relay):
- if relay:
- call('systemctl restart isc-dhcp-relay.service')
- else:
- # DHCP relay support is removed in the commit
+ # bail out early - looks like removal from running config
+ if not relay:
call('systemctl stop isc-dhcp-relay.service')
if os.path.exists(config_file):
os.unlink(config_file)
+ return None
+
+ call('systemctl restart isc-dhcp-relay.service')
return None
diff --git a/src/conf_mode/dhcpv6_relay.py b/src/conf_mode/dhcpv6_relay.py
index d4212b8be..cf8a26674 100755
--- a/src/conf_mode/dhcpv6_relay.py
+++ b/src/conf_mode/dhcpv6_relay.py
@@ -17,90 +17,84 @@
import os
from sys import exit
-from copy import deepcopy
from vyos.config import Config
-from vyos import ConfigError
-from vyos.util import call
+from vyos.configdict import dict_merge
+from vyos.ifconfig import Interface
from vyos.template import render
-
+from vyos.util import call
+from vyos.util import dict_search
+from vyos.validate import is_ipv6_link_local
+from vyos.xml import defaults
+from vyos import ConfigError
from vyos import airbag
airbag.enable()
-config_file = r'/run/dhcp-relay/dhcpv6.conf'
-
-default_config_data = {
- 'listen_addr': [],
- 'upstream_addr': [],
- 'options': [],
-}
+config_file = '/run/dhcp-relay/dhcrelay6.conf'
def get_config(config=None):
- relay = deepcopy(default_config_data)
if config:
conf = config
else:
conf = Config()
- if not conf.exists('service dhcpv6-relay'):
+ base = ['service', 'dhcpv6-relay']
+ if not conf.exists(base):
return None
- else:
- conf.set_level('service dhcpv6-relay')
-
- # Network interfaces/address to listen on for DHCPv6 query(s)
- if conf.exists('listen-interface'):
- interfaces = conf.list_nodes('listen-interface')
- for intf in interfaces:
- if conf.exists('listen-interface {0} address'.format(intf)):
- addr = conf.return_value('listen-interface {0} address'.format(intf))
- listen = addr + '%' + intf
- relay['listen_addr'].append(listen)
-
- # Upstream interface/address for remote DHCPv6 server
- if conf.exists('upstream-interface'):
- interfaces = conf.list_nodes('upstream-interface')
- for intf in interfaces:
- addresses = conf.return_values('upstream-interface {0} address'.format(intf))
- for addr in addresses:
- server = addr + '%' + intf
- relay['upstream_addr'].append(server)
-
- # Maximum hop count. When forwarding packets, dhcrelay discards packets
- # which have reached a hop count of COUNT. Default is 10. Maximum is 255.
- if conf.exists('max-hop-count'):
- count = '-c ' + conf.return_value('max-hop-count')
- relay['options'].append(count)
-
- if conf.exists('use-interface-id-option'):
- relay['options'].append('-I')
+
+ relay = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=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)
+ relay = dict_merge(default_values, relay)
return relay
def verify(relay):
# bail out early - looks like removal from running config
- if relay is None:
+ if not relay:
return None
- if len(relay['listen_addr']) == 0 or len(relay['upstream_addr']) == 0:
- raise ConfigError('Must set at least one listen and upstream interface addresses.')
+ if 'upstream_interface' not in relay:
+ raise ConfigError('At least one upstream interface required!')
+ for interface, config in relay['upstream_interface'].items():
+ if 'address' not in config:
+ raise ConfigError('DHCPv6 server required for upstream ' \
+ f'interface {interface}!')
+
+ if 'listen_interface' not in relay:
+ raise ConfigError('At least one listen interface required!')
+
+ # DHCPv6 relay requires at least one global unicat address assigned to the
+ # interface
+ for interface in relay['listen_interface']:
+ has_global = False
+ for addr in Interface(interface).get_addr():
+ if not is_ipv6_link_local(addr.split('/')[0]):
+ has_global = True
+ if not has_global:
+ raise ConfigError(f'Interface {interface} does not have global '\
+ 'IPv6 address assigned!')
return None
def generate(relay):
# bail out early - looks like removal from running config
- if relay is None:
+ if not relay:
return None
- render(config_file, 'dhcpv6-relay/config.tmpl', relay)
+ render(config_file, 'dhcp-relay/dhcrelay6.conf.tmpl', relay)
return None
def apply(relay):
- if relay is not None:
- call('systemctl restart isc-dhcp-relay6.service')
- else:
+ # bail out early - looks like removal from running config
+ if not relay:
# DHCPv6 relay support is removed in the commit
call('systemctl stop isc-dhcp-relay6.service')
if os.path.exists(config_file):
os.unlink(config_file)
+ return None
+
+ call('systemctl restart isc-dhcp-relay6.service')
return None
diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py
index ef52cbfd3..c44e6c974 100755
--- a/src/conf_mode/dns_forwarding.py
+++ b/src/conf_mode/dns_forwarding.py
@@ -114,10 +114,10 @@ def generate(dns):
return None
render(pdns_rec_config_file, 'dns-forwarding/recursor.conf.tmpl',
- dns, trim_blocks=True, user=pdns_rec_user, group=pdns_rec_group)
+ dns, user=pdns_rec_user, group=pdns_rec_group)
render(pdns_rec_lua_conf_file, 'dns-forwarding/recursor.conf.lua.tmpl',
- dns, trim_blocks=True, user=pdns_rec_user, group=pdns_rec_group)
+ dns, user=pdns_rec_user, group=pdns_rec_group)
# if vyos-hostsd didn't create its files yet, create them (empty)
for file in [pdns_rec_hostsd_lua_conf_file, pdns_rec_hostsd_zones_file]:
diff --git a/src/conf_mode/dynamic_dns.py b/src/conf_mode/dynamic_dns.py
index 93e995b78..6d39c6644 100755
--- a/src/conf_mode/dynamic_dns.py
+++ b/src/conf_mode/dynamic_dns.py
@@ -131,7 +131,9 @@ def generate(dyndns):
if not dyndns:
return None
- render(config_file, 'dynamic-dns/ddclient.conf.tmpl', dyndns, trim_blocks=True, permission=0o600)
+ render(config_file, 'dynamic-dns/ddclient.conf.tmpl', dyndns,
+ permission=0o600)
+
return None
def apply(dyndns):
diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py
index de228f0f8..a6e2d9c8c 100755
--- a/src/conf_mode/https.py
+++ b/src/conf_mode/https.py
@@ -159,7 +159,7 @@ def generate(https):
if 'server_block_list' not in https or not https['server_block_list']:
https['server_block_list'] = [default_server_block]
- render(config_file, 'https/nginx.default.tmpl', https, trim_blocks=True)
+ render(config_file, 'https/nginx.default.tmpl', https)
return None
diff --git a/src/conf_mode/igmp_proxy.py b/src/conf_mode/igmp_proxy.py
index 754f46566..fb030c9f3 100755
--- a/src/conf_mode/igmp_proxy.py
+++ b/src/conf_mode/igmp_proxy.py
@@ -17,90 +17,65 @@
import os
from sys import exit
-from copy import deepcopy
-
from netifaces import interfaces
+
from vyos.config import Config
-from vyos import ConfigError
-from vyos.util import call
+from vyos.configdict import dict_merge
from vyos.template import render
-
+from vyos.util import call
+from vyos.util import dict_search
+from vyos.xml import defaults
+from vyos import ConfigError
from vyos import airbag
airbag.enable()
config_file = r'/etc/igmpproxy.conf'
-default_config_data = {
- 'disable': False,
- 'disable_quickleave': False,
- 'interfaces': [],
-}
-
def get_config(config=None):
- igmp_proxy = deepcopy(default_config_data)
if config:
conf = config
else:
conf = Config()
- base = ['protocols', 'igmp-proxy']
- if not conf.exists(base):
- return None
- else:
- conf.set_level(base)
-
- # Network interfaces to listen on
- if conf.exists(['disable']):
- igmp_proxy['disable'] = True
-
- # Option to disable "quickleave"
- if conf.exists(['disable-quickleave']):
- igmp_proxy['disable_quickleave'] = True
- for intf in conf.list_nodes(['interface']):
- conf.set_level(base + ['interface', intf])
- interface = {
- 'name': intf,
- 'alt_subnet': [],
- 'role': 'downstream',
- 'threshold': '1',
- 'whitelist': []
- }
-
- if conf.exists(['alt-subnet']):
- interface['alt_subnet'] = conf.return_values(['alt-subnet'])
+ base = ['protocols', 'igmp-proxy']
+ igmp_proxy = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
- if conf.exists(['role']):
- interface['role'] = conf.return_value(['role'])
+ if 'interface' in igmp_proxy:
+ # T2665: we must add the tagNode defaults individually until this is
+ # moved to the base class
+ default_values = defaults(base + ['interface'])
+ for interface in igmp_proxy['interface']:
+ igmp_proxy['interface'][interface] = dict_merge(default_values,
+ igmp_proxy['interface'][interface])
- if conf.exists(['threshold']):
- interface['threshold'] = conf.return_value(['threshold'])
- if conf.exists(['whitelist']):
- interface['whitelist'] = conf.return_values(['whitelist'])
+ if conf.exists(['protocols', 'igmp']):
+ igmp_proxy.update({'igmp_configured': ''})
- # Append interface configuration to global configuration list
- igmp_proxy['interfaces'].append(interface)
+ if conf.exists(['protocols', 'pim']):
+ igmp_proxy.update({'pim_configured': ''})
return igmp_proxy
def verify(igmp_proxy):
# bail out early - looks like removal from running config
- if igmp_proxy is None:
+ if not igmp_proxy or 'disable' in igmp_proxy:
return None
- # bail out early - service is disabled
- if igmp_proxy['disable']:
- return None
+ if 'igmp_configured' in igmp_proxy or 'pim_configured' in igmp_proxy:
+ raise ConfigError('Can not configure both IGMP proxy and PIM '\
+ 'at the same time')
# at least two interfaces are required, one upstream and one downstream
- if len(igmp_proxy['interfaces']) < 2:
- raise ConfigError('Must define an upstream and at least 1 downstream interface!')
+ if 'interface' not in igmp_proxy or len(igmp_proxy['interface']) < 2:
+ raise ConfigError('Must define exactly one upstream and at least one ' \
+ 'downstream interface!')
upstream = 0
- for interface in igmp_proxy['interfaces']:
- if interface['name'] not in interfaces():
- raise ConfigError('Interface "{}" does not exist'.format(interface['name']))
- if "upstream" == interface['role']:
+ for interface, config in igmp_proxy['interface'].items():
+ if interface not in interfaces():
+ raise ConfigError(f'Interface "{interface}" does not exist')
+ if dict_search('role', config) == 'upstream':
upstream += 1
if upstream == 0:
@@ -112,19 +87,20 @@ def verify(igmp_proxy):
def generate(igmp_proxy):
# bail out early - looks like removal from running config
- if igmp_proxy is None:
+ if not igmp_proxy:
return None
# bail out early - service is disabled, but inform user
- if igmp_proxy['disable']:
- print('Warning: IGMP Proxy will be deactivated because it is disabled')
+ if 'disable' in igmp_proxy:
+ print('WARNING: IGMP Proxy will be deactivated because it is disabled')
return None
render(config_file, 'igmp-proxy/igmpproxy.conf.tmpl', igmp_proxy)
+
return None
def apply(igmp_proxy):
- if igmp_proxy is None or igmp_proxy['disable']:
+ if not igmp_proxy or 'disable' in igmp_proxy:
# IGMP Proxy support is removed in the commit
call('systemctl stop igmpproxy.service')
if os.path.exists(config_file):
diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py
index 076bdb63e..7af3e3d7c 100755
--- a/src/conf_mode/interfaces-bridge.py
+++ b/src/conf_mode/interfaces-bridge.py
@@ -123,12 +123,12 @@ def get_config(config=None):
# VLAN-aware bridge members must not have VLAN interface configuration
if 'native_vlan' in interface_config:
- if 'disable' not in interface_config['native_vlan']:
- vlan_aware = True
+ vlan_aware = True
if 'allowed_vlan' in interface_config:
vlan_aware = True
+
if vlan_aware:
tmp = has_vlan_subinterface_configured(conf,interface)
if tmp:
@@ -142,6 +142,8 @@ def verify(bridge):
verify_dhcpv6(bridge)
verify_vrf(bridge)
+
+ vlan_aware = False
if dict_search('member.interface', bridge):
for interface, interface_config in bridge['member']['interface'].items():
@@ -168,6 +170,16 @@ def verify(bridge):
if 'has_vlan' in interface_config:
raise ConfigError(error_msg + 'it has an VLAN subinterface assigned!')
+ # VLAN-aware bridge members must not have VLAN interface configuration
+ if 'native_vlan' in interface_config:
+ vlan_aware = True
+
+ if 'allowed_vlan' in interface_config:
+ vlan_aware = True
+
+ if vlan_aware and 'wlan' in interface:
+ raise ConfigError(error_msg + 'VLAN aware cannot be set!')
+
if 'allowed_vlan' in interface_config:
for vlan in interface_config['allowed_vlan']:
if re.search('[0-9]{1,4}-[0-9]{1,4}', vlan):
diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py
index c23e79948..25920f893 100755
--- a/src/conf_mode/interfaces-openvpn.py
+++ b/src/conf_mode/interfaces-openvpn.py
@@ -120,7 +120,7 @@ def verify(openvpn):
# OpenVPN site-to-site - VERIFY
#
elif openvpn['mode'] == 'site-to-site':
- if not 'local_address' in openvpn:
+ if 'local_address' not in openvpn and 'is_bridge_member' not in openvpn:
raise ConfigError('Must specify "local-address" or add interface to bridge')
if len([addr for addr in openvpn['local_address'] if is_ipv4(addr)]) > 1:
@@ -166,15 +166,16 @@ def verify(openvpn):
if dict_search('remote_host', openvpn) in dict_search('remote_address', openvpn):
raise ConfigError('"remote-address" and "remote-host" can not be the same')
-
- if 'local_address' in openvpn:
+ if openvpn['device_type'] == 'tap':
# we can only have one local_address, this is ensured above
v4addr = None
for laddr in openvpn['local_address']:
- if is_ipv4(laddr): v4addr = laddr
+ if is_ipv4(laddr):
+ v4addr = laddr
+ break
- if 'remote_address' not in openvpn and (v4addr not in openvpn['local_address'] or 'subnet_mask' not in openvpn['local_address'][v4addr]):
- raise ConfigError('IPv4 "local-address" requires IPv4 "remote-address" or IPv4 "local-address subnet"')
+ if v4addr in openvpn['local_address'] and 'subnet_mask' not in openvpn['local_address'][v4addr]:
+ raise ConfigError('Must specify IPv4 "subnet-mask" for local-address')
if dict_search('encryption.ncp_ciphers', openvpn):
raise ConfigError('NCP ciphers can only be used in client or server mode')
@@ -464,12 +465,9 @@ def generate(openvpn):
if tmp: fix_permissions.append(tmp)
# Generate User/Password authentication file
- if 'auth' in openvpn:
- with open(openvpn['auth_user_pass_file'], 'w') as f:
- f.write('{}\n{}'.format(openvpn['auth_user'], openvpn['auth_pass']))
- # also change permission on auth file
- fix_permissions.append(openvpn['auth_user_pass_file'])
-
+ if 'authentication' in openvpn:
+ render(openvpn['auth_user_pass_file'], 'openvpn/auth.pw.tmpl', openvpn,
+ user=user, group=group, permission=0o600)
else:
# delete old auth file if present
if os.path.isfile(openvpn['auth_user_pass_file']):
@@ -483,17 +481,13 @@ def generate(openvpn):
# Our client need's to know its subnet mask ...
client_config['server_subnet'] = dict_search('server.subnet', openvpn)
- import pprint
- pprint.pprint(client_config)
-
render(client_file, 'openvpn/client.conf.tmpl', client_config,
- trim_blocks=True, user=user, group=group)
+ user=user, group=group)
# we need to support quoting of raw parameters from OpenVPN CLI
# see https://phabricator.vyos.net/T1632
render(cfg_file.format(**openvpn), 'openvpn/server.conf.tmpl', openvpn,
- trim_blocks=True, formater=lambda _: _.replace("&quot;", '"'),
- user=user, group=group)
+ formater=lambda _: _.replace("&quot;", '"'), user=user, group=group)
# Fixup file permissions
for file in fix_permissions:
diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py
index ee3b142c8..c31e49574 100755
--- a/src/conf_mode/interfaces-pppoe.py
+++ b/src/conf_mode/interfaces-pppoe.py
@@ -93,25 +93,25 @@ def generate(pppoe):
return None
# Create PPP configuration files
- render(config_pppoe, 'pppoe/peer.tmpl',
- pppoe, trim_blocks=True, permission=0o755)
+ render(config_pppoe, 'pppoe/peer.tmpl', pppoe, permission=0o755)
+
# Create script for ip-pre-up.d
- render(script_pppoe_pre_up, 'pppoe/ip-pre-up.script.tmpl',
- pppoe, trim_blocks=True, permission=0o755)
+ render(script_pppoe_pre_up, 'pppoe/ip-pre-up.script.tmpl', pppoe,
+ permission=0o755)
# Create script for ip-up.d
- render(script_pppoe_ip_up, 'pppoe/ip-up.script.tmpl',
- pppoe, trim_blocks=True, permission=0o755)
+ render(script_pppoe_ip_up, 'pppoe/ip-up.script.tmpl', pppoe,
+ permission=0o755)
# Create script for ip-down.d
- render(script_pppoe_ip_down, 'pppoe/ip-down.script.tmpl',
- pppoe, trim_blocks=True, permission=0o755)
+ render(script_pppoe_ip_down, 'pppoe/ip-down.script.tmpl', pppoe,
+ permission=0o755)
# Create script for ipv6-up.d
- render(script_pppoe_ipv6_up, 'pppoe/ipv6-up.script.tmpl',
- pppoe, trim_blocks=True, permission=0o755)
+ render(script_pppoe_ipv6_up, 'pppoe/ipv6-up.script.tmpl', pppoe,
+ permission=0o755)
if 'dhcpv6_options' in pppoe and 'pd' in pppoe['dhcpv6_options']:
# ipv6.tmpl relies on ifname - this should be made consitent in the
# future better then double key-ing the same value
- render(config_wide_dhcp6c, 'dhcp-client/ipv6.tmpl', pppoe, trim_blocks=True)
+ render(config_wide_dhcp6c, 'dhcp-client/ipv6.tmpl', pppoe)
return None
diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py
index f1217b62d..1a7e9a96d 100755
--- a/src/conf_mode/interfaces-tunnel.py
+++ b/src/conf_mode/interfaces-tunnel.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019 VyOS maintainers and contributors
+# Copyright (C) 2018-2020 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,354 +15,124 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
-import netifaces
from sys import exit
-from copy import deepcopy
from netifaces import interfaces
from vyos.config import Config
-from vyos.configdict import is_member
-from vyos.configdict import list_diff
-from vyos.dicts import FixedDict
-from vyos.ifconfig import Interface, GREIf, GRETapIf, IPIPIf, IP6GREIf, IPIP6If, IP6IP6If, SitIf, Sit6RDIf
-from vyos.ifconfig.afi import IP4, IP6
+from vyos.configdict import dict_merge
+from vyos.configdict import get_interface_dict
+from vyos.configdict import node_changed
+from vyos.configdict import leaf_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.ifconfig import Interface
+from vyos.ifconfig import GREIf
+from vyos.ifconfig import GRETapIf
+from vyos.ifconfig import IPIPIf
+from vyos.ifconfig import IP6GREIf
+from vyos.ifconfig import IPIP6If
+from vyos.ifconfig import IP6IP6If
+from vyos.ifconfig import SitIf
+from vyos.ifconfig import Sit6RDIf
from vyos.template import is_ipv4
from vyos.template import is_ipv6
+from vyos.util import dict_search
from vyos import ConfigError
-
-
from vyos import airbag
airbag.enable()
-
-class ConfigurationState(object):
+def get_config(config=None):
"""
- The current API require a dict to be generated by get_config()
- which is then consumed by verify(), generate() and apply()
-
- ConfiguartionState is an helper class wrapping Config and providing
- an common API to this dictionary structure
-
- Its to_api() function return a dictionary containing three fields,
- each a dict, called options, changes, actions.
-
- options:
-
- contains the configuration options for the dict and its value
- {'options': {'commment': 'test'}} will be set if
- 'set interface dummy dum1 description test' was used and
- the key 'commment' is used to index the description info.
-
- changes:
-
- per key, let us know how the data was modified using one of the action
- a special key called 'section' is used to indicate what happened to the
- section. for example:
-
- 'set interface dummy dum1 description test' when no interface was setup
- will result in the following changes
- {'changes': {'section': 'create', 'comment': 'create'}}
-
- on an existing interface, depending if there was a description
- 'set interface dummy dum1 description test' will result in one of
- {'changes': {'comment': 'create'}} (not present before)
- {'changes': {'comment': 'static'}} (unchanged)
- {'changes': {'comment': 'modify'}} (changed from half)
-
- and 'delete interface dummy dummy1 description' will result in:
- {'changes': {'comment': 'delete'}}
-
- actions:
-
- for each action list the configuration key which were changes
- in our example if we added the 'description' and added an IP we would have
- {'actions': { 'create': ['comment'], 'modify': ['addresses-add']}}
-
- the actions are:
- 'create': it did not exist previously and was created
- 'modify': it did exist previously but its content changed
- 'static': it did exist and did not change
- 'delete': it was present but was removed from the configuration
- 'absent': it was not and is not present
- which for each field represent how it was modified since the last commit
+ Retrive CLI config as dictionary. Dictionary can never be empty, as at least
+ the interface name will be added or a deleted flag
"""
+ if config:
+ conf = config
+ else:
+ conf = Config()
+ base = ['interfaces', 'tunnel']
+ tunnel = get_interface_dict(conf, base)
- def __init__(self, configuration, section, default):
- """
- initialise the class for a given configuration path:
-
- >>> conf = ConfigurationState(conf, 'interfaces ethernet eth1')
- all further references to get_value(s) and get_effective(s)
- will be for this part of the configuration (eth1)
- """
- self._conf = configuration
-
- self.default = deepcopy(default)
- self.options = FixedDict(**default)
- self.actions = {
- 'create': [], # the key did not exist and was added
- 'static': [], # the key exists and its value was not modfied
- 'modify': [], # the key exists and its value was modified
- 'absent': [], # the key is not present
- 'delete': [], # the key was present and was deleted
- }
- self.changes = {}
- if not self._conf.exists(section):
- self.changes['section'] = 'delete'
- elif self._conf.exists_effective(section):
- self.changes['section'] = 'modify'
- else:
- self.changes['section'] = 'create'
-
- self.set_level(section)
-
- def set_level(self, lpath):
- self.section = lpath
- self._conf.set_level(lpath)
-
- def _act(self, section):
- """
- Returns for a given configuration field determine what happened to it
-
- 'create': it did not exist previously and was created
- 'modify': it did exist previously but its content changed
- 'static': it did exist and did not change
- 'delete': it was present but was removed from the configuration
- 'absent': it was not and is not present
- """
- if self._conf.exists(section):
- if self._conf.exists_effective(section):
- if self._conf.return_value(section) != self._conf.return_effective_value(section):
- return 'modify'
- return 'static'
- return 'create'
- else:
- if self._conf.exists_effective(section):
- return 'delete'
- return 'absent'
-
- def _action(self, name, key):
- action = self._act(key)
- self.changes[name] = action
- self.actions[action].append(name)
- return action
-
- def _get(self, name, key, default, getter):
- value = getter(key)
- if not value:
- if default:
- self.options[name] = default
- return
- self.options[name] = self.default[name]
- return
- self.options[name] = value
-
- def get_value(self, name, key, default=None):
- """
- >>> conf.get_value('comment', 'description')
- will place the string of 'interface dummy description test'
- into the dictionnary entry 'comment' using Config.return_value
- (the data in the configuration to apply)
- """
- if self._action(name, key) in ('delete', 'absent'):
- return
- return self._get(name, key, default, self._conf.return_value)
-
- def get_values(self, name, key, default=None):
- """
- >>> conf.get_values('addresses', 'address')
- will place a list of the new IP present in 'interface dummy dum1 address'
- into the dictionnary entry "-add" (here 'addresses-add') using
- Config.return_values and will add the the one which were removed in into
- the entry "-del" (here addresses-del')
- """
- add_name = f'{name}-add'
-
- if self._action(add_name, key) in ('delete', 'absent'):
- return
-
- self._get(add_name, key, default, self._conf.return_values)
-
- # get the effective values to determine which data is no longer valid
- self.options['addresses-del'] = list_diff(
- self._conf.return_effective_values('address'),
- self.options['addresses-add']
- )
-
- def get_effective(self, name, key, default=None):
- """
- >>> conf.get_value('comment', 'description')
- will place the string of 'interface dummy description test'
- into the dictionnary entry 'comment' using Config.return_effective_value
- (the data in the configuration to apply)
- """
- self._action(name, key)
- return self._get(name, key, default, self._conf.return_effective_value)
-
- def get_effectives(self, name, key, default=None):
- """
- >>> conf.get_effectives('addresses-add', 'address')
- will place a list made of the IP present in 'interface ethernet eth1 address'
- into the dictionnary entry 'addresses-add' using Config.return_effectives_value
- (the data in the un-modified configuration)
- """
- self._action(name, key)
- return self._get(name, key, default, self._conf.return_effectives_value)
+ # Wireguard is "special" the default MTU is 1420 - update accordingly
+ # as the config_level is already st in get_interface_dict() - we can use []
+ tmp = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True)
+ if 'mtu' not in tmp:
+ tunnel['mtu'] = '1476'
- def load(self, mapping):
- """
- load will take a dictionary defining how we wish the configuration
- to be parsed and apply this definition to set the data.
+ tmp = leaf_node_changed(conf, ['encapsulation'])
+ if tmp: tunnel.update({'encapsulation_changed': {}})
- >>> mapping = {
- 'addresses-add' : ('address', True, None),
- 'comment' : ('description', False, 'auto'),
- }
- >>> conf.load(mapping)
+ # We must check if our interface is configured to be a DMVPN member
+ nhrp_base = ['protocols', 'nhrp', 'tunnel']
+ conf.set_level(nhrp_base)
+ nhrp = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True)
+ if nhrp: tunnel.update({'nhrp' : list(nhrp.keys())})
- mapping is a dictionary where each key represents the name we wish
- to have (such as 'addresses-add'), with a list a content representing
- how the data should be parsed:
- - the configuration section name
- such as 'address' under 'interface ethernet eth1'
- - boolean indicating if this data can have multiple values
- for 'address', True, as multiple IPs can be set
- for 'description', False, as it is a single string
- - default represent the default value if absent from the configuration
- 'None' indicate that no default should be set if the configuration
- does not have the configuration section
+ return tunnel
- """
- for local_name, (config_name, multiple, default) in mapping.items():
- if multiple:
- self.get_values(local_name, config_name, default)
- else:
- self.get_value(local_name, config_name, default)
+def verify(tunnel):
+ if 'deleted' in tunnel:
+ verify_bridge_delete(tunnel)
- def remove_default(self,*options):
- """
- remove all the values which were not changed from the default
- """
- for option in options:
- if not self._conf.exists(option):
- del self.options[option]
- continue
+ if 'nhrp' in tunnel and tunnel['ifname'] in tunnel['nhrp']:
+ raise ConfigError('Tunnel used for NHRP, it can not be deleted!')
- if self._conf.return_value(option) == self.default[option]:
- del self.options[option]
- continue
+ return None
- if self._conf.return_values(option) == self.default[option]:
- del self.options[option]
- continue
+ if 'encapsulation' not in tunnel:
+ raise ConfigError('Must configure the tunnel encapsulation for '\
+ '{ifname}!'.format(**tunnel))
- def as_dict(self, lpath):
- l = self._conf.get_level()
- self._conf.set_level([])
- d = self._conf.get_config_dict(lpath)
- # XXX: that not what I would have expected from get_config_dict
- if lpath:
- d = d[lpath[-1]]
- # XXX: it should have provided me the content and not the key
- self._conf.set_level(l)
- return d
+ verify_mtu_ipv6(tunnel)
+ verify_address(tunnel)
+ verify_vrf(tunnel)
- def to_api(self):
- """
- provide a dictionary with the generated data for the configuration
- options: the configuration value for the key
- changes: per key how they changed from the previous configuration
- actions: per changes all the options which were changed
- """
- # as we have to use a dict() for the API for verify and apply the options
- return {
- 'options': self.options,
- 'changes': self.changes,
- 'actions': self.actions,
- }
+ if 'local_ip' not in tunnel and 'dhcp_interface' not in tunnel:
+ raise ConfigError('local-ip is mandatory for tunnel')
+ if 'remote_ip' not in tunnel and tunnel['encapsulation'] != 'gre':
+ raise ConfigError('remote-ip is mandatory for tunnel')
-default_config_data = {
- # interface definition
- 'vrf': '',
- 'addresses-add': [],
- 'addresses-del': [],
- 'state': 'up',
- 'dhcp-interface': '',
- 'link_detect': 1,
- 'ip': False,
- 'ipv6': False,
- 'nhrp': [],
- 'arp_filter': 1,
- 'arp_accept': 0,
- 'arp_announce': 0,
- 'arp_ignore': 0,
- 'ipv6_accept_ra': 1,
- 'ipv6_autoconf': 0,
- 'ipv6_forwarding': 1,
- 'ipv6_dad_transmits': 1,
- # internal
- 'interfaces': [],
- 'tunnel': {},
- 'bridge': '',
- # the following names are exactly matching the name
- # for the ip command and must not be changed
- 'ifname': '',
- 'type': '',
- 'alias': '',
- 'mtu': '1476',
- 'local': '',
- 'remote': '',
- 'dev': '',
- 'multicast': 'disable',
- 'allmulticast': 'disable',
- 'ttl': '255',
- 'tos': 'inherit',
- 'key': '',
- 'encaplimit': '4',
- 'flowlabel': 'inherit',
- 'hoplimit': '64',
- 'tclass': 'inherit',
- '6rd-prefix': '',
- '6rd-relay-prefix': '',
-}
+ if {'local_ip', 'dhcp_interface'} <= set(tunnel):
+ raise ConfigError('Can not use both local-ip and dhcp-interface')
+ if tunnel['encapsulation'] in ['ipip6', 'ip6ip6', 'ip6gre']:
+ error_ipv6 = 'Encapsulation mode requires IPv6'
+ if 'local_ip' in tunnel and not is_ipv6(tunnel['local_ip']):
+ raise ConfigError(f'{error_ipv6} local-ip')
-# dict name -> config name, multiple values, default
-mapping = {
- 'type': ('encapsulation', False, None),
- 'alias': ('description', False, None),
- 'mtu': ('mtu', False, None),
- 'local': ('local-ip', False, None),
- 'remote': ('remote-ip', False, None),
- 'multicast': ('multicast', False, None),
- 'dev': ('source-interface', False, None),
- 'ttl': ('parameters ip ttl', False, None),
- 'tos': ('parameters ip tos', False, None),
- 'key': ('parameters ip key', False, None),
- 'encaplimit': ('parameters ipv6 encaplimit', False, None),
- 'flowlabel': ('parameters ipv6 flowlabel', False, None),
- 'hoplimit': ('parameters ipv6 hoplimit', False, None),
- 'tclass': ('parameters ipv6 tclass', False, None),
- '6rd-prefix': ('6rd-prefix', False, None),
- '6rd-relay-prefix': ('6rd-relay-prefix', False, None),
- 'dhcp-interface': ('dhcp-interface', False, None),
- 'state': ('disable', False, 'down'),
- 'link_detect': ('disable-link-detect', False, 2),
- 'vrf': ('vrf', False, None),
- 'addresses': ('address', True, None),
- 'arp_filter': ('ip disable-arp-filter', False, 0),
- 'arp_accept': ('ip enable-arp-accept', False, 1),
- 'arp_announce': ('ip enable-arp-announce', False, 1),
- 'arp_ignore': ('ip enable-arp-ignore', False, 1),
- 'ipv6_autoconf': ('ipv6 address autoconf', False, 1),
- 'ipv6_forwarding': ('ipv6 disable-forwarding', False, 0),
- 'ipv6_dad_transmits:': ('ipv6 dup-addr-detect-transmits', False, None)
-}
+ if 'remote_ip' in tunnel and not is_ipv6(tunnel['remote_ip']):
+ raise ConfigError(f'{error_ipv6} remote-ip')
+ else:
+ error_ipv4 = 'Encapsulation mode requires IPv4'
+ if 'local_ip' in tunnel and not is_ipv4(tunnel['local_ip']):
+ raise ConfigError(f'{error_ipv4} local-ip')
+
+ if 'remote_ip' in tunnel and not is_ipv4(tunnel['remote_ip']):
+ raise ConfigError(f'{error_ipv4} remote-ip')
+
+ if tunnel['encapsulation'] in ['sit', 'gre-bridge']:
+ if 'source_interface' in tunnel:
+ raise ConfigError('Option source-interface can not be used with ' \
+ 'encapsulation "sit" or "gre-bridge"')
+ elif tunnel['encapsulation'] == 'gre':
+ if 'local_ip' in tunnel and is_ipv6(tunnel['local_ip']):
+ raise ConfigError('Can not use local IPv6 address is for mGRE tunnels')
+
+def generate(tunnel):
+ return None
+def apply(tunnel):
+ if 'deleted' in tunnel or 'encapsulation_changed' in tunnel:
+ if tunnel['ifname'] in interfaces():
+ tmp = Interface(tunnel['ifname'])
+ tmp.remove()
+ if 'deleted' in tunnel:
+ return None
-def get_class (options):
dispatch = {
'gre': GREIf,
'gre-bridge': GRETapIf,
@@ -373,353 +143,52 @@ def get_class (options):
'sit': SitIf,
}
- kls = dispatch[options['type']]
- if options['type'] == 'gre' and not options['remote'] \
- and not options['key'] and not options['multicast']:
- # will use GreTapIf on GreIf deletion but it does not matter
- return GRETapIf
- elif options['type'] == 'sit' and options['6rd-prefix']:
- # will use SitIf on Sit6RDIf deletion but it does not matter
- return Sit6RDIf
- return kls
-
-def get_interface_ip (ifname):
- if not ifname:
- return ''
- try:
- addrs = Interface(ifname).get_addr()
- if addrs:
- return addrs[0].split('/')[0]
- except Exception:
- return ''
-
-def get_afi (ip):
- return IP6 if is_ipv6(ip) else IP4
-
-def ip_proto (afi):
- return 6 if afi == IP6 else 4
-
-
-def get_config(config=None):
- ifname = os.environ.get('VYOS_TAGNODE_VALUE','')
- if not ifname:
- raise ConfigError('Interface not specified')
-
- if config:
- config = config
- else:
- config = Config()
-
- conf = ConfigurationState(config, ['interfaces', 'tunnel ', ifname], default_config_data)
- options = conf.options
- changes = conf.changes
- options['ifname'] = ifname
-
- if changes['section'] == 'delete':
- conf.get_effective('type', mapping['type'][0])
- config.set_level(['protocols', 'nhrp', 'tunnel'])
- options['nhrp'] = config.list_nodes('')
- return conf.to_api()
-
- # load all the configuration option according to the mapping
- conf.load(mapping)
-
- # remove default value if not set and not required
- afi_local = get_afi(options['local'])
- if afi_local == IP6:
- conf.remove_default('ttl', 'tos', 'key')
- if afi_local == IP4:
- conf.remove_default('encaplimit', 'flowlabel', 'hoplimit', 'tclass')
-
- # if the local-ip is not set, pick one from the interface !
- # hopefully there is only one, otherwise it will not be very deterministic
- # at time of writing the code currently returns ipv4 before ipv6 in the list
-
- # XXX: There is no way to trigger an update of the interface source IP if
- # XXX: the underlying interface IP address does change, I believe this
- # XXX: limit/issue is present in vyatta too
-
- if not options['local'] and options['dhcp-interface']:
- # XXX: This behaviour changes from vyatta which would return 127.0.0.1 if
- # XXX: the interface was not DHCP. As there is no easy way to find if an
- # XXX: interface is using DHCP, and using this feature to get 127.0.0.1
- # XXX: makes little sense, I feel the change in behaviour is acceptable
- picked = get_interface_ip(options['dhcp-interface'])
- if picked == '':
- picked = '127.0.0.1'
- print('Could not get an IP address from {dhcp-interface} using 127.0.0.1 instead')
- options['local'] = picked
- options['dhcp-interface'] = ''
-
- # to make IPv6 SLAAC and DHCPv6 work with forwarding=1,
- # accept_ra must be 2
- if options['ipv6_autoconf'] or 'dhcpv6' in options['addresses-add']:
- options['ipv6_accept_ra'] = 2
-
- # allmulticast fate is linked to multicast
- options['allmulticast'] = options['multicast']
-
- # check that per encapsulation all local-remote pairs are unique
- ct = conf.as_dict(['interfaces', 'tunnel'])
- options['tunnel'] = {}
-
- # check for bridges
- tmp = is_member(config, ifname, 'bridge')
- if tmp: options['bridge'] = next(iter(tmp))
- options['interfaces'] = interfaces()
-
- for name in ct:
- tunnel = ct[name]
- encap = tunnel.get('encapsulation', '')
- local = tunnel.get('local-ip', '')
- if not local:
- local = get_interface_ip(tunnel.get('dhcp-interface', ''))
- remote = tunnel.get('remote-ip', '<unset>')
- pair = f'{local}-{remote}'
- options['tunnel'][encap][pair] = options['tunnel'].setdefault(encap, {}).get(pair, 0) + 1
-
- return conf.to_api()
-
-
-def verify(conf):
- options = conf['options']
- changes = conf['changes']
- actions = conf['actions']
-
- ifname = options['ifname']
- iftype = options['type']
-
- if changes['section'] == 'delete':
- if ifname in options['nhrp']:
- raise ConfigError((
- f'Cannot delete interface tunnel {iftype} {ifname}, '
- 'it is used by NHRP'))
-
- if options['bridge']:
- raise ConfigError((
- f'Cannot delete interface "{options["ifname"]}" as it is a '
- f'member of bridge "{options["bridge"]}"!'))
-
- # done, bail out early
- return None
-
- # tunnel encapsulation checks
-
- if not iftype:
- raise ConfigError(f'Must provide an "encapsulation" for tunnel {iftype} {ifname}')
-
- if changes['type'] in ('modify', 'delete'):
- # TODO: we could now deal with encapsulation modification by deleting / recreating
- raise ConfigError(f'Encapsulation can only be set at tunnel creation for tunnel {iftype} {ifname}')
-
- if iftype != 'sit' and options['6rd-prefix']:
- # XXX: should be able to remove this and let the definition catch it
- print(f'6RD can only be configured for sit interfaces not tunnel {iftype} {ifname}')
-
- # what are the tunnel options we can set / modified / deleted
-
- kls = get_class(options)
- valid = kls.updates + ['alias', 'addresses-add', 'addresses-del', 'vrf', 'state']
- valid += ['arp_filter', 'arp_accept', 'arp_announce', 'arp_ignore']
- valid += ['ipv6_accept_ra', 'ipv6_autoconf', 'ipv6_forwarding', 'ipv6_dad_transmits']
-
- if changes['section'] == 'create':
- valid.extend(['type',])
- valid.extend([o for o in kls.options if o not in kls.updates])
-
- for create in actions['create']:
- if create not in valid:
- raise ConfigError(f'Can not set "{create}" for tunnel {iftype} {ifname} at tunnel creation')
-
- for modify in actions['modify']:
- if modify not in valid:
- raise ConfigError(f'Can not modify "{modify}" for tunnel {iftype} {ifname}. it must be set at tunnel creation')
-
- for delete in actions['delete']:
- if delete in kls.required:
- raise ConfigError(f'Can not remove "{delete}", it is an mandatory option for tunnel {iftype} {ifname}')
-
- # tunnel information
-
- tun_local = options['local']
- afi_local = get_afi(tun_local)
- tun_remote = options['remote'] or tun_local
- afi_remote = get_afi(tun_remote)
- tun_ismgre = iftype == 'gre' and not options['remote']
- tun_is6rd = iftype == 'sit' and options['6rd-prefix']
- tun_dev = options['dev']
-
- # incompatible options
-
- if not tun_local and not options['dhcp-interface'] and not tun_is6rd:
- raise ConfigError(f'Must configure either local-ip or dhcp-interface for tunnel {iftype} {ifname}')
-
- if tun_local and options['dhcp-interface']:
- raise ConfigError(f'Must configure only one of local-ip or dhcp-interface for tunnel {iftype} {ifname}')
-
- if tun_dev and iftype in ('gre-bridge', 'sit'):
- raise ConfigError(f'source interface can not be used with {iftype} {ifname}')
-
- # tunnel endpoint
-
- if afi_local != afi_remote:
- raise ConfigError(f'IPv4/IPv6 mismatch between local-ip and remote-ip for tunnel {iftype} {ifname}')
-
- if afi_local != kls.tunnel:
- version = 4 if tun_local == IP4 else 6
- raise ConfigError(f'Invalid IPv{version} local-ip for tunnel {iftype} {ifname}')
-
- ipv4_count = len([ip for ip in options['addresses-add'] if is_ipv4(ip)])
- ipv6_count = len([ip for ip in options['addresses-add'] if is_ipv6(ip)])
-
- if tun_ismgre and afi_local == IP6:
- raise ConfigError(f'Using an IPv6 address is forbidden for mGRE tunnels such as tunnel {iftype} {ifname}')
-
- # check address family use
- # checks are not enforced (but ip command failing) for backward compatibility
-
- if ipv4_count and not IP4 in kls.ip:
- print(f'Should not use IPv4 addresses on tunnel {iftype} {ifname}')
-
- if ipv6_count and not IP6 in kls.ip:
- print(f'Should not use IPv6 addresses on tunnel {iftype} {ifname}')
-
- # vrf check
- if options['vrf']:
- if options['vrf'] not in options['interfaces']:
- raise ConfigError(f'VRF "{options["vrf"]}" does not exist')
-
- if options['bridge']:
- raise ConfigError((
- f'Interface "{options["ifname"]}" cannot be member of VRF '
- f'"{options["vrf"]}" and bridge {options["bridge"]} '
- f'at the same time!'))
-
- # bridge and address check
- if ( options['bridge']
- and ( options['addresses-add']
- or options['ipv6_autoconf'] ) ):
- raise ConfigError((
- f'Cannot assign address to interface "{options["name"]}" '
- f'as it is a member of bridge "{options["bridge"]}"!'))
-
- # source-interface check
-
- if tun_dev and tun_dev not in options['interfaces']:
- raise ConfigError(f'device "{tun_dev}" does not exist')
-
- # tunnel encapsulation check
-
- convert = {
- (6, 4, 'gre'): 'ip6gre',
- (6, 6, 'gre'): 'ip6gre',
- (4, 6, 'ipip'): 'ipip6',
- (6, 6, 'ipip'): 'ip6ip6',
+ # We need to re-map the tunnel encapsulation proto to a valid interface class
+ encap = tunnel['encapsulation']
+ klass = dispatch[encap]
+
+ # This is a special type of interface which needs additional parameters
+ # when created using iproute2. Instead of passing a ton of arguments,
+ # use a dictionary provided by the interface class which holds all the
+ # options necessary.
+ conf = klass.get_config()
+
+ # Copy/re-assign our dictionary values to values understood by the
+ # derived _Tunnel classes
+ mapping = {
+ # this : get_config()
+ 'local_ip' : 'local',
+ 'remote_ip' : 'remote',
+ 'source_interface' : 'dev',
+ 'parameters.ip.ttl' : 'ttl',
+ 'parameters.ip.tos' : 'tos',
+ 'parameters.ip.key' : 'key',
+ 'parameters.ipv6.encaplimit' : 'encaplimit'
}
- iprotos = []
- if ipv4_count:
- iprotos.append(4)
- if ipv6_count:
- iprotos.append(6)
-
- for iproto in iprotos:
- replace = convert.get((kls.tunnel, iproto, iftype), '')
- if replace:
- raise ConfigError(
- f'Using IPv6 address in local-ip or remote-ip is not possible with "encapsulation {iftype}". ' +
- f'Use "encapsulation {replace}" for tunnel {iftype} {ifname} instead.'
- )
-
- # tunnel options
-
- incompatible = []
- if afi_local == IP6:
- incompatible.extend(['ttl', 'tos', 'key',])
- if afi_local == IP4:
- incompatible.extend(['encaplimit', 'flowlabel', 'hoplimit', 'tclass'])
-
- for option in incompatible:
- if option in options:
- # TODO: raise converted to print as not enforced by vyatta
- # raise ConfigError(f'{option} is not valid for tunnel {iftype} {ifname}')
- print(f'Using "{option}" is invalid for tunnel {iftype} {ifname}')
-
- # duplicate tunnel pairs
-
- pair = '{}-{}'.format(options['local'], options['remote'])
- if options['tunnel'].get(iftype, {}).get(pair, 0) > 1:
- raise ConfigError(f'More than one tunnel configured for with the same encapulation and IPs for tunnel {iftype} {ifname}')
+ # Add additional IPv6 options if tunnel is IPv6 aware
+ if tunnel['encapsulation'] in ['ipip6', 'ip6ip6', 'ip6gre']:
+ mappingv6 = {
+ # this : get_config()
+ 'parameters.ipv6.encaplimit' : 'encaplimit'
+ }
+ mapping.update(mappingv6)
- return None
+ for our_key, their_key in mapping.items():
+ if dict_search(our_key, tunnel) and their_key in conf:
+ conf[their_key] = dict_search(our_key, tunnel)
+ tun = klass(tunnel['ifname'], **conf)
+ tun.change_options()
+ tun.update(tunnel)
-def generate(gre):
return None
-def apply(conf):
- options = conf['options']
- changes = conf['changes']
- actions = conf['actions']
- kls = get_class(options)
-
- # extract ifname as otherwise it is duplicated on the interface creation
- ifname = options.pop('ifname')
-
- # only the valid keys for creation of a Interface
- config = dict((k, options[k]) for k in kls.options if options[k])
-
- # setup or create the tunnel interface if it does not exist
- tunnel = kls(ifname, **config)
-
- if changes['section'] == 'delete':
- tunnel.remove()
- # The perl code was calling/opt/vyatta/sbin/vyatta-tunnel-cleanup
- # which identified tunnels type which were not used anymore to remove them
- # (ie: gre0, gretap0, etc.) The perl code did however nothing
- # This feature is also not implemented yet
- return
-
- # A GRE interface without remote will be mGRE
- # if the interface does not suppor the option, it skips the change
- for option in tunnel.updates:
- if changes['section'] in 'create' and option in tunnel.options:
- # it was setup at creation
- continue
- if not options[option]:
- # remote can be set to '' and it would generate an invalide command
- continue
- tunnel.set_interface(option, options[option])
-
- # set other interface properties
- for option in ('alias', 'mtu', 'link_detect', 'multicast', 'allmulticast',
- 'arp_accept', 'arp_filter', 'arp_announce', 'arp_ignore',
- 'ipv6_accept_ra', 'ipv6_autoconf', 'ipv6_forwarding', 'ipv6_dad_transmits'):
- if not options[option]:
- # should never happen but better safe
- continue
- tunnel.set_interface(option, options[option])
-
- # assign/remove VRF (ONLY when not a member of a bridge,
- # otherwise 'nomaster' removes it from it)
- if not options['bridge']:
- tunnel.set_vrf(options['vrf'])
-
- # Configure interface address(es)
- for addr in options['addresses-del']:
- tunnel.del_addr(addr)
- for addr in options['addresses-add']:
- tunnel.add_addr(addr)
-
- # now bring it up (or not)
- tunnel.set_admin_state(options['state'])
-
-
if __name__ == '__main__':
try:
c = get_config()
- verify(c)
generate(c)
+ verify(c)
apply(c)
except ConfigError as e:
print(e)
diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py
index 9bda35d0a..7cfc76aa0 100755
--- a/src/conf_mode/interfaces-wireguard.py
+++ b/src/conf_mode/interfaces-wireguard.py
@@ -80,9 +80,6 @@ def verify(wireguard):
raise ConfigError('Wireguard private-key not found! Execute: ' \
'"run generate wireguard [default-keypair|named-keypairs]"')
- if 'address' not in wireguard:
- raise ConfigError('IP address required!')
-
if 'peer' not in wireguard:
raise ConfigError('At least one Wireguard peer is required!')
diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py
index 5d723bbfd..b25fcd4e0 100755
--- a/src/conf_mode/interfaces-wireless.py
+++ b/src/conf_mode/interfaces-wireless.py
@@ -241,10 +241,12 @@ def generate(wifi):
# render appropriate new config files depending on access-point or station mode
if wifi['type'] == 'access-point':
- render(hostapd_conf.format(**wifi), 'wifi/hostapd.conf.tmpl', wifi, trim_blocks=True)
+ render(hostapd_conf.format(**wifi), 'wifi/hostapd.conf.tmpl',
+ wifi)
elif wifi['type'] == 'station':
- render(wpa_suppl_conf.format(**wifi), 'wifi/wpa_supplicant.conf.tmpl', wifi, trim_blocks=True)
+ render(wpa_suppl_conf.format(**wifi), 'wifi/wpa_supplicant.conf.tmpl',
+ wifi)
return None
@@ -261,7 +263,6 @@ def apply(wifi):
# Assign WiFi instance configuration parameters to config dict
conf['phy'] = wifi['physical_device']
- conf['wds'] = 'on' if 'wds' in wifi else 'off'
# Finally create the new interface
w = WiFiIf(interface, **conf)
diff --git a/src/conf_mode/interfaces-wirelessmodem.py b/src/conf_mode/interfaces-wirelessmodem.py
index bce3405d0..976953b31 100755
--- a/src/conf_mode/interfaces-wirelessmodem.py
+++ b/src/conf_mode/interfaces-wirelessmodem.py
@@ -91,21 +91,21 @@ def generate(wwan):
wwan['device'] = find_device_file(wwan['device'])
# Create PPP configuration files
- render(config_wwan, 'wwan/peer.tmpl', wwan, trim_blocks=True)
+ render(config_wwan, 'wwan/peer.tmpl', wwan)
# Create PPP chat script
- render(config_wwan_chat, 'wwan/chat.tmpl', wwan, trim_blocks=True)
+ render(config_wwan_chat, 'wwan/chat.tmpl', wwan)
# generated script file must be executable
# Create script for ip-pre-up.d
render(script_wwan_pre_up, 'wwan/ip-pre-up.script.tmpl',
- wwan, trim_blocks=True, permission=0o755)
+ wwan, permission=0o755)
# Create script for ip-up.d
render(script_wwan_ip_up, 'wwan/ip-up.script.tmpl',
- wwan, trim_blocks=True, permission=0o755)
+ wwan, permission=0o755)
# Create script for ip-down.d
render(script_wwan_ip_down, 'wwan/ip-down.script.tmpl',
- wwan, trim_blocks=True, permission=0o755)
+ wwan, permission=0o755)
return None
diff --git a/src/conf_mode/ipsec-settings.py b/src/conf_mode/ipsec-settings.py
index 11a5b7aaa..a65e8b567 100755
--- a/src/conf_mode/ipsec-settings.py
+++ b/src/conf_mode/ipsec-settings.py
@@ -170,12 +170,12 @@ def verify(data):
raise ConfigError("L2TP VPN configuration error: \"vpn ipsec ipsec-interfaces\" must be specified.")
def generate(data):
- render(charon_conf_file, 'ipsec/charon.tmpl', data, trim_blocks=True)
+ render(charon_conf_file, 'ipsec/charon.tmpl', data)
if data["ipsec_l2tp"]:
remove_confs(delim_ipsec_l2tp_begin, delim_ipsec_l2tp_end, ipsec_secrets_file)
# old_umask = os.umask(0o077)
- # render(ipsec_secrets_file, 'ipsec/ipsec.secrets.tmpl', data, trim_blocks=True)
+ # render(ipsec_secrets_file, 'ipsec/ipsec.secrets.tmpl', data)
# os.umask(old_umask)
## Use this method while IPSec CLI handler won't be overwritten to python
write_ipsec_secrets(data)
@@ -186,12 +186,12 @@ def generate(data):
if not os.path.exists(ipsec_ra_conn_dir):
os.makedirs(ipsec_ra_conn_dir)
- render(ipsec_ra_conn_file, 'ipsec/remote-access.tmpl', data, trim_blocks=True)
+ render(ipsec_ra_conn_file, 'ipsec/remote-access.tmpl', data)
os.umask(old_umask)
remove_confs(delim_ipsec_l2tp_begin, delim_ipsec_l2tp_end, ipsec_conf_file)
# old_umask = os.umask(0o077)
- # render(ipsec_conf_file, 'ipsec/ipsec.conf.tmpl', data, trim_blocks=True)
+ # render(ipsec_conf_file, 'ipsec/ipsec.conf.tmpl', data)
# os.umask(old_umask)
## Use this method while IPSec CLI handler won't be overwritten to python
write_ipsec_conf(data)
diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py
index b66cd370a..f5c023b81 100755
--- a/src/conf_mode/nat.py
+++ b/src/conf_mode/nat.py
@@ -18,18 +18,19 @@ import jmespath
import json
import os
-from copy import deepcopy
from distutils.version import LooseVersion
from platform import release as kernel_version
from sys import exit
from netifaces import interfaces
from vyos.config import Config
+from vyos.configdict import dict_merge
from vyos.template import render
-from vyos.util import call
from vyos.util import cmd
from vyos.util import check_kmod
+from vyos.util import dict_search
from vyos.validate import is_addr_assigned
+from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
@@ -40,17 +41,6 @@ if LooseVersion(kernel_version()) > LooseVersion('5.1'):
else:
k_mod = ['nft_nat', 'nft_chain_nat_ipv4']
-default_config_data = {
- 'deleted': False,
- 'destination': [],
- 'helper_functions': None,
- 'pre_ct_helper': '',
- 'pre_ct_conntrack': '',
- 'out_ct_helper': '',
- 'out_ct_conntrack': '',
- 'source': []
-}
-
iptables_nat_config = '/tmp/vyos-nat-rules.nft'
def get_handler(json, chain, target):
@@ -66,114 +56,43 @@ def get_handler(json, chain, target):
return None
-def verify_rule(rule, err_msg):
+def verify_rule(config, err_msg):
""" Common verify steps used for both source and destination NAT """
- if rule['translation_port'] or rule['dest_port'] or rule['source_port']:
- if rule['protocol'] not in ['tcp', 'udp', 'tcp_udp']:
- proto = rule['protocol']
- raise ConfigError(f'{err_msg} ports can only be specified when protocol is "tcp", "udp" or "tcp_udp" (currently "{proto}")')
- if '/' in rule['translation_address']:
+ if (dict_search('translation.port', config) != None or
+ dict_search('destination.port', config) != None or
+ dict_search('source.port', config)):
+
+ if config['protocol'] not in ['tcp', 'udp', 'tcp_udp']:
+ raise ConfigError(f'{err_msg}\n' \
+ 'ports can only be specified when protocol is '\
+ 'either tcp, udp or tcp_udp!')
+
+ if '/' in (dict_search('translation.address', config) or []):
raise ConfigError(f'{err_msg}\n' \
'Cannot use ports with an IPv4net type translation address as it\n' \
'statically maps a whole network of addresses onto another\n' \
'network of addresses')
-
-def parse_configuration(conf, source_dest):
- """ Common wrapper to read in both NAT source and destination CLI """
- ruleset = []
- base_level = ['nat', source_dest]
- conf.set_level(base_level)
- for number in conf.list_nodes(['rule']):
- rule = {
- 'description': '',
- 'dest_address': '',
- 'dest_port': '',
- 'disabled': False,
- 'exclude': False,
- 'interface_in': '',
- 'interface_out': '',
- 'log': False,
- 'protocol': 'all',
- 'number': number,
- 'source_address': '',
- 'source_prefix': '',
- 'source_port': '',
- 'translation_address': '',
- 'translation_prefix': '',
- 'translation_port': ''
- }
- conf.set_level(base_level + ['rule', number])
-
- if conf.exists(['description']):
- rule['description'] = conf.return_value(['description'])
-
- if conf.exists(['destination', 'address']):
- tmp = conf.return_value(['destination', 'address'])
- if tmp.startswith('!'):
- tmp = tmp.replace('!', '!=')
- rule['dest_address'] = tmp
-
- if conf.exists(['destination', 'port']):
- tmp = conf.return_value(['destination', 'port'])
- if tmp.startswith('!'):
- tmp = tmp.replace('!', '!=')
- rule['dest_port'] = tmp
-
- if conf.exists(['disable']):
- rule['disabled'] = True
-
- if conf.exists(['exclude']):
- rule['exclude'] = True
-
- if conf.exists(['inbound-interface']):
- rule['interface_in'] = conf.return_value(['inbound-interface'])
-
- if conf.exists(['outbound-interface']):
- rule['interface_out'] = conf.return_value(['outbound-interface'])
-
- if conf.exists(['log']):
- rule['log'] = True
-
- if conf.exists(['protocol']):
- rule['protocol'] = conf.return_value(['protocol'])
-
- if conf.exists(['source', 'address']):
- tmp = conf.return_value(['source', 'address'])
- if tmp.startswith('!'):
- tmp = tmp.replace('!', '!=')
- rule['source_address'] = tmp
-
- if conf.exists(['source', 'prefix']):
- rule['source_prefix'] = conf.return_value(['source', 'prefix'])
-
- if conf.exists(['source', 'port']):
- tmp = conf.return_value(['source', 'port'])
- if tmp.startswith('!'):
- tmp = tmp.replace('!', '!=')
- rule['source_port'] = tmp
-
- if conf.exists(['translation', 'address']):
- rule['translation_address'] = conf.return_value(['translation', 'address'])
-
- if conf.exists(['translation', 'prefix']):
- rule['translation_prefix'] = conf.return_value(['translation', 'prefix'])
-
- if conf.exists(['translation', 'port']):
- rule['translation_port'] = conf.return_value(['translation', 'port'])
-
- ruleset.append(rule)
-
- return ruleset
-
def get_config(config=None):
- nat = deepcopy(default_config_data)
if config:
conf = config
else:
conf = Config()
+ base = ['nat']
+ nat = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+
+ # T2665: we must add the tagNode defaults individually until this is
+ # moved to the base class
+ for direction in ['source', 'destination']:
+ if direction in nat:
+ default_values = defaults(base + [direction, 'rule'])
+ for rule in nat[direction]['rule']:
+ nat[direction]['rule'][rule] = dict_merge(default_values,
+ nat[direction]['rule'][rule])
+
+
# read in current nftable (once) for further processing
tmp = cmd('nft -j list table raw')
nftable_json = json.loads(tmp)
@@ -182,7 +101,7 @@ def get_config(config=None):
pattern = 'nftables[?rule].rule[?expr[].jump].{chain: chain, handle: handle, target: expr[].jump.target | [0]}'
condensed_json = jmespath.search(pattern, nftable_json)
- if not conf.exists(['nat']):
+ if not conf.exists(base):
nat['helper_functions'] = 'remove'
# Retrieve current table handler positions
@@ -190,9 +109,7 @@ def get_config(config=None):
nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK')
nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYATTA_CT_HELPER')
nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'NAT_CONNTRACK')
-
- nat['deleted'] = True
-
+ nat['deleted'] = ''
return nat
# check if NAT connection tracking helpers need to be set up - this has to
@@ -206,19 +123,10 @@ def get_config(config=None):
nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYATTA_CT_IGNORE')
nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'VYATTA_CT_OUTPUT_HOOK')
- # set config level for parsing in NAT configuration
- conf.set_level(['nat'])
-
- # use a common wrapper function to read in the source / destination
- # tree from the config - thus we do not need to replicate almost the
- # same code :-)
- for tgt in ['source', 'destination', 'nptv6']:
- nat[tgt] = parse_configuration(conf, tgt)
-
return nat
def verify(nat):
- if nat['deleted']:
+ if not nat or 'deleted' in nat:
# no need to verify the CLI as NAT is going to be deactivated
return None
@@ -226,49 +134,55 @@ def verify(nat):
if not (nat['pre_ct_ignore'] or nat['pre_ct_conntrack'] or nat['out_ct_ignore'] or nat['out_ct_conntrack']):
raise Exception('could not determine nftable ruleset handlers')
- for rule in nat['source']:
- interface = rule['interface_out']
- err_msg = f'Source NAT configuration error in rule "{rule["number"]}":'
-
- if interface and interface not in 'any' and interface not in interfaces():
- print(f'Warning: rule "{rule["number"]}" interface "{interface}" does not exist on this system')
+ if dict_search('source.rule', nat):
+ for rule, config in dict_search('source.rule', nat).items():
+ err_msg = f'Source NAT configuration error in rule {rule}:'
+ if 'outbound_interface' not in config:
+ raise ConfigError(f'{err_msg}\n' \
+ 'outbound-interface not specified')
+ else:
+ if config['outbound_interface'] not in 'any' and config['outbound_interface'] not in interfaces():
+ print(f'WARNING: rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system')
- if not rule['interface_out']:
- raise ConfigError(f'{err_msg} outbound-interface not specified')
- if rule['translation_address']:
- addr = rule['translation_address']
- if addr != 'masquerade':
- for ip in addr.split('-'):
- if not is_addr_assigned(ip):
- print(f'Warning: IP address {ip} does not exist on the system!')
+ addr = dict_search('translation.address', config)
+ if addr != None:
+ if addr != 'masquerade':
+ for ip in addr.split('-'):
+ if not is_addr_assigned(ip):
+ print(f'WARNING: IP address {ip} does not exist on the system!')
+ elif 'exclude' not in config:
+ raise ConfigError(f'{err_msg}\n' \
+ 'translation address not specified')
- elif not rule['exclude']:
- raise ConfigError(f'{err_msg} translation address not specified')
+ # common rule verification
+ verify_rule(config, err_msg)
- # common rule verification
- verify_rule(rule, err_msg)
- for rule in nat['destination']:
- interface = rule['interface_in']
- err_msg = f'Destination NAT configuration error in rule "{rule["number"]}":'
+ if dict_search('destination.rule', nat):
+ for rule, config in dict_search('destination.rule', nat).items():
+ err_msg = f'Destination NAT configuration error in rule {rule}:'
- if interface and interface not in 'any' and interface not in interfaces():
- print(f'Warning: rule "{rule["number"]}" interface "{interface}" does not exist on this system')
+ if 'inbound_interface' not in config:
+ raise ConfigError(f'{err_msg}\n' \
+ 'inbound-interface not specified')
+ else:
+ if config['inbound_interface'] not in 'any' and config['inbound_interface'] not in interfaces():
+ print(f'WARNING: rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system')
- if not rule['interface_in']:
- raise ConfigError(f'{err_msg} inbound-interface not specified')
- if not rule['translation_address'] and not rule['exclude']:
- raise ConfigError(f'{err_msg} translation address not specified')
+ if dict_search('translation.address', config) == None and 'exclude' not in config:
+ raise ConfigError(f'{err_msg}\n' \
+ 'translation address not specified')
- # common rule verification
- verify_rule(rule, err_msg)
+ # common rule verification
+ verify_rule(config, err_msg)
return None
def generate(nat):
- render(iptables_nat_config, 'firewall/nftables-nat.tmpl', nat, trim_blocks=True, permission=0o755)
+ render(iptables_nat_config, 'firewall/nftables-nat.tmpl', nat,
+ permission=0o755)
return None
def apply(nat):
diff --git a/src/conf_mode/ntp.py b/src/conf_mode/ntp.py
index d6453ec83..b102b3e9e 100755
--- a/src/conf_mode/ntp.py
+++ b/src/conf_mode/ntp.py
@@ -53,8 +53,8 @@ def generate(ntp):
if not ntp:
return None
- render(config_file, 'ntp/ntp.conf.tmpl', ntp, trim_blocks=True)
- render(systemd_override, 'ntp/override.conf.tmpl', ntp, trim_blocks=True)
+ render(config_file, 'ntp/ntp.conf.tmpl', ntp)
+ render(systemd_override, 'ntp/override.conf.tmpl', ntp)
return None
diff --git a/src/conf_mode/policy-local-route.py b/src/conf_mode/policy-local-route.py
new file mode 100755
index 000000000..c4024dce4
--- /dev/null
+++ b/src/conf_mode/policy-local-route.py
@@ -0,0 +1,110 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 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 os
+
+from sys import exit
+
+from vyos.config import Config
+from vyos.configdict import dict_merge
+from vyos.configdict import node_changed
+from vyos.configdict import leaf_node_changed
+from vyos.template import render
+from vyos.util import call
+from vyos import ConfigError
+from vyos import airbag
+airbag.enable()
+
+
+def get_config(config=None):
+
+ if config:
+ conf = config
+ else:
+ conf = Config()
+ base = ['policy', 'local-route']
+ pbr = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+
+ # delete policy local-route
+ dict = {}
+ tmp = node_changed(conf, ['policy', 'local-route', 'rule'])
+ if tmp:
+ for rule in (tmp or []):
+ src = leaf_node_changed(conf, ['policy', 'local-route', 'rule', rule, 'source'])
+ if src:
+ dict = dict_merge({'rule_remove' : {rule : {'source' : src}}}, dict)
+ pbr.update(dict)
+
+ # delete policy local-route rule x source x.x.x.x
+ if 'rule' in pbr:
+ for rule in pbr['rule']:
+ src = leaf_node_changed(conf, ['policy', 'local-route', 'rule', rule, 'source'])
+ if src:
+ dict = dict_merge({'rule_remove' : {rule : {'source' : src}}}, dict)
+ pbr.update(dict)
+
+ return pbr
+
+def verify(pbr):
+ # bail out early - looks like removal from running config
+ if not pbr:
+ return None
+
+ if 'rule' in pbr:
+ for rule in pbr['rule']:
+ if 'source' not in pbr['rule'][rule]:
+ raise ConfigError('Source address required!')
+ else:
+ if 'set' not in pbr['rule'][rule] or 'table' not in pbr['rule'][rule]['set']:
+ raise ConfigError('Table set is required!')
+
+ return None
+
+def generate(pbr):
+ if not pbr:
+ return None
+
+ return None
+
+def apply(pbr):
+ if not pbr:
+ return None
+
+ # Delete old rule if needed
+ if 'rule_remove' in pbr:
+ for rule in pbr['rule_remove']:
+ for src in pbr['rule_remove'][rule]['source']:
+ call(f'ip rule del prio {rule} from {src}')
+
+ # Generate new config
+ if 'rule' in pbr:
+ for rule in pbr['rule']:
+ table = pbr['rule'][rule]['set']['table']
+ if pbr['rule'][rule]['source']:
+ for src in pbr['rule'][rule]['source']:
+ call(f'ip rule add prio {rule} from {src} lookup {table}')
+
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py
index 654874232..642738b09 100755
--- a/src/conf_mode/protocols_bgp.py
+++ b/src/conf_mode/protocols_bgp.py
@@ -14,16 +14,16 @@
# 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 vyos.config import Config
from vyos.util import call
+from vyos.util import dict_search
from vyos.template import render
from vyos.template import render_to_string
+from vyos import ConfigError
from vyos import frr
-from vyos import ConfigError, airbag
+from vyos import airbag
airbag.enable()
config_file = r'/tmp/bgp.frr'
@@ -31,8 +31,10 @@ config_file = r'/tmp/bgp.frr'
def get_config():
conf = Config()
base = ['protocols', 'nbgp']
- bgp = conf.get_config_dict(base, key_mangling=('-', '_'))
+ bgp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+ # XXX: any reason we can not move this into the FRR template?
+ # we shall not call vtysh directly, especially not in get_config()
if not conf.exists(base):
bgp = {}
call('vtysh -c \"conf t\" -c \"no ip protocol bgp\" ')
@@ -40,9 +42,6 @@ def get_config():
if not conf.exists(base + ['route-map']):
call('vtysh -c \"conf t\" -c \"no ip protocol bgp\" ')
- from pprint import pprint
- pprint(bgp)
-
return bgp
def verify(bgp):
@@ -50,9 +49,23 @@ def verify(bgp):
return None
# Check if declared more than one ASN
- for asn in bgp['nbgp'].items():
- if len(bgp['nbgp']) > 1:
- raise ConfigError('Only one bgp ASN process can be definded')
+ if len(bgp) > 1:
+ raise ConfigError('Only one BGP AS can be defined!')
+
+ for asn, asn_config in bgp.items():
+ # Common verification for both peer-group and neighbor statements
+ for neigh in ['neighbor', 'peer_group']:
+ # bail out early if there is no neighbor or peer-group statement
+ # this also saves one indention level
+ if neigh not in asn_config:
+ continue
+
+ for neighbor, config in asn_config[neigh].items():
+ if 'remote_as' not in config and 'peer_group' not in config:
+ raise ConfigError(f'BGP remote-as must be specified for "{neighbor}"!')
+
+ if 'remote_as' in config and 'peer_group' in config:
+ raise ConfigError(f'BGP peer-group member "{neighbor}" cannot override remote-as of peer-group!')
return None
@@ -61,33 +74,40 @@ def generate(bgp):
bgp['new_frr_config'] = ''
return None
- # render(config) not needed, its only for debug
- render(config_file, 'frr/bgp.frr.tmpl', bgp)
+ # only one BGP AS is supported, so we can directly send the first key
+ # of the config dict
+ asn = list(bgp.keys())[0]
+ bgp[asn]['asn'] = asn
- bgp['new_frr_config'] = render_to_string('frr/bgp.frr.tmpl', bgp)
+ # render(config) not needed, its only for debug
+ render(config_file, 'frr/bgp.frr.tmpl', bgp[asn])
+ bgp['new_frr_config'] = render_to_string('frr/bgp.frr.tmpl', bgp[asn])
return None
def apply(bgp):
- # Save original configration prior to starting any commit actions
- bgp['original_config'] = frr.get_configuration(daemon='bgpd')
- bgp['modified_config'] = frr.replace_section(bgp['original_config'], bgp['new_frr_config'], from_re='router bgp .*')
+ # Save original configuration prior to starting any commit actions
+ frr_cfg = {}
+ frr_cfg['original_config'] = frr.get_configuration(daemon='bgpd')
+ frr_cfg['modified_config'] = frr.replace_section(frr_cfg['original_config'], bgp['new_frr_config'], from_re='router bgp .*')
# Debugging
+ print('')
print('--------- DEBUGGING ----------')
- print(f'Existing config:\n{bgp["original_config"]}\n\n')
+ print(f'Existing config:\n{frr_cfg["original_config"]}\n\n')
print(f'Replacement config:\n{bgp["new_frr_config"]}\n\n')
- print(f'Modified config:\n{bgp["modified_config"]}\n\n')
+ print(f'Modified config:\n{frr_cfg["modified_config"]}\n\n')
- # Frr Mark configuration will test for syntax errors and exception out if any syntax errors are detected
- frr.mark_configuration(bgp['modified_config'])
+ # FRR mark configuration will test for syntax errors and throws an
+ # exception if any syntax errors is detected
+ frr.mark_configuration(frr_cfg['modified_config'])
- # Commit the resulting new configuration to frr, this will render an frr.CommitError() Exception on fail
- frr.reload_configuration(bgp['modified_config'], daemon='bgpd')
+ # Commit resulting configuration to FRR, this will throw CommitError
+ # on failure
+ frr.reload_configuration(frr_cfg['modified_config'], daemon='bgpd')
return None
-
if __name__ == '__main__':
try:
c = get_config()
diff --git a/src/conf_mode/protocols_igmp.py b/src/conf_mode/protocols_igmp.py
index 6f4fc784d..8606e7364 100755
--- a/src/conf_mode/protocols_igmp.py
+++ b/src/conf_mode/protocols_igmp.py
@@ -21,8 +21,9 @@ from sys import exit
from vyos import ConfigError
from vyos.config import Config
-from vyos.util import call
+from vyos.util import call, process_named_running
from vyos.template import render
+from signal import SIGTERM
from vyos import airbag
airbag.enable()
@@ -36,12 +37,20 @@ def get_config(config=None):
conf = Config()
igmp_conf = {
'igmp_conf' : False,
+ 'pim_conf' : False,
+ 'igmp_proxy_conf' : False,
'old_ifaces' : {},
'ifaces' : {}
}
if not (conf.exists('protocols igmp') or conf.exists_effective('protocols igmp')):
return None
+ if conf.exists('protocols igmp-proxy'):
+ igmp_conf['igmp_proxy_conf'] = True
+
+ if conf.exists('protocols pim'):
+ igmp_conf['pim_conf'] = True
+
if conf.exists('protocols igmp'):
igmp_conf['igmp_conf'] = True
@@ -79,6 +88,10 @@ def verify(igmp):
return None
if igmp['igmp_conf']:
+ # Check conflict with IGMP-Proxy
+ if igmp['igmp_proxy_conf']:
+ raise ConfigError(f"IGMP proxy and PIM cannot be both configured at the same time")
+
# Check interfaces
if not igmp['ifaces']:
raise ConfigError(f"IGMP require defined interfaces!")
@@ -99,9 +112,16 @@ def apply(igmp):
if igmp is None:
return None
- if os.path.exists(config_file):
- call(f'vtysh -d pimd -f {config_file}')
- os.remove(config_file)
+ pim_pid = process_named_running('pimd')
+ if igmp['igmp_conf'] or igmp['pim_conf']:
+ if not pim_pid:
+ call(f'pimd -d -F traditional --daemon -A 127.0.0.1')
+
+ if os.path.exists(config_file):
+ call(f'vtysh -d pimd -f {config_file}')
+ os.remove(config_file)
+ elif pim_pid:
+ os.kill(int(pim_pid), SIGTERM)
return None
diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py
index b0b8d705b..bd372a7b3 100755
--- a/src/conf_mode/protocols_isis.py
+++ b/src/conf_mode/protocols_isis.py
@@ -107,16 +107,15 @@ def generate(isis):
isis[process]['process'] = process
# render(config) not needed, its only for debug
- render(config_file, 'frr/isis.frr.tmpl', isis[process], trim_blocks=True)
+ render(config_file, 'frr/isis.frr.tmpl', isis[process])
isis['new_frr_config'] = render_to_string('frr/isis.frr.tmpl',
- isis[process], trim_blocks=True)
+ isis[process])
return None
def apply(isis):
-
- # Save original configration prior to starting any commit actions
+ # Save original configuration prior to starting any commit actions
frr_cfg = {}
frr_cfg['original_config'] = frr.get_configuration(daemon='isisd')
frr_cfg['modified_config'] = frr.replace_section(frr_cfg['original_config'], isis['new_frr_config'], from_re='interface .*')
diff --git a/src/conf_mode/protocols_mpls.py b/src/conf_mode/protocols_mpls.py
index 84948baf4..791b18110 100755
--- a/src/conf_mode/protocols_mpls.py
+++ b/src/conf_mode/protocols_mpls.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019-2020 VyOS maintainers and contributors
+# Copyright (C) 2020 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -16,351 +16,124 @@
import os
+from sys import exit
+
from vyos.config import Config
-from vyos import ConfigError
+from vyos.configdict import node_changed
+from vyos.template import render_to_string
from vyos.util import call
-from vyos.template import render
-
+from vyos.util import dict_search
+from vyos import ConfigError
+from vyos import frr
from vyos import airbag
airbag.enable()
config_file = r'/tmp/ldpd.frr'
-def sysctl(name, value):
- call('sysctl -wq {}={}'.format(name, value))
-
def get_config(config=None):
if config:
conf = config
else:
conf = Config()
- mpls_conf = {
- 'router_id' : None,
- 'mpls_ldp' : False,
- 'old_parameters' : {
- 'no_ttl_propagation' : False,
- 'maximum_ttl' : None
- },
- 'parameters' : {
- 'no_ttl_propagation' : False,
- 'maximum_ttl' : None
- },
- 'old_ldp' : {
- 'interfaces' : [],
- 'neighbors' : {},
- 'd_transp_ipv4' : None,
- 'd_transp_ipv6' : None,
- 'hello_ipv4_holdtime' : None,
- 'hello_ipv4_interval' : None,
- 'hello_ipv6_holdtime' : None,
- 'hello_ipv6_interval' : None,
- 'ses_ipv4_hold' : None,
- 'ses_ipv6_hold' : None,
- 'export_ipv4_exp' : False,
- 'export_ipv6_exp' : False,
- 'cisco_interop_tlv' : False,
- 'transport_prefer_ipv4' : False,
- 'target_ipv4_addresses' : [],
- 'target_ipv6_addresses' : [],
- 'target_ipv4_enable' : False,
- 'target_ipv6_enable' : False,
- 'target_ipv4_hello_int' : None,
- 'target_ipv6_hello_int' : None,
- 'target_ipv4_hello_hold' : None,
- 'target_ipv6_hello_hold' : None
- },
- 'ldp' : {
- 'interfaces' : [],
- 'neighbors' : {},
- 'd_transp_ipv4' : None,
- 'd_transp_ipv6' : None,
- 'hello_ipv4_holdtime' : None,
- 'hello_ipv4_interval' : None,
- 'hello_ipv6_holdtime' : None,
- 'hello_ipv6_interval' : None,
- 'ses_ipv4_hold' : None,
- 'ses_ipv6_hold' : None,
- 'export_ipv4_exp' : False,
- 'export_ipv6_exp' : False,
- 'cisco_interop_tlv' : False,
- 'transport_prefer_ipv4' : False,
- 'target_ipv4_addresses' : [],
- 'target_ipv6_addresses' : [],
- 'target_ipv4_enable' : False,
- 'target_ipv6_enable' : False,
- 'target_ipv4_hello_int' : None,
- 'target_ipv6_hello_int' : None,
- 'target_ipv4_hello_hold' : None,
- 'target_ipv6_hello_hold' : None
- }
- }
- if not (conf.exists('protocols mpls') or conf.exists_effective('protocols mpls')):
- return None
-
- # If LDP is defined then enable LDP portion of code
- if conf.exists('protocols mpls ldp'):
- mpls_conf['mpls_ldp'] = True
-
- # Set to MPLS hierarchy configuration level
- conf.set_level('protocols mpls')
-
- # Get no_ttl_propagation
- if conf.exists_effective('parameters no-propagate-ttl'):
- mpls_conf['old_parameters']['no_ttl_propagation'] = True
-
- if conf.exists('parameters no-propagate-ttl'):
- mpls_conf['parameters']['no_ttl_propagation'] = True
-
- # Get maximum_ttl
- if conf.exists_effective('parameters maximum-ttl'):
- mpls_conf['old_parameters']['maximum_ttl'] = conf.return_effective_value('parameters maximum-ttl')
-
- if conf.exists('parameters maximum-ttl'):
- mpls_conf['parameters']['maximum_ttl'] = conf.return_value('parameters maximum-ttl')
-
- # Set to LDP hierarchy configuration level
- conf.set_level('protocols mpls ldp')
-
- # Get router-id
- if conf.exists_effective('router-id'):
- mpls_conf['old_router_id'] = conf.return_effective_value('router-id')
-
- if conf.exists('router-id'):
- mpls_conf['router_id'] = conf.return_value('router-id')
-
- # Get hello-ipv4-holdtime
- if conf.exists_effective('discovery hello-ipv4-holdtime'):
- mpls_conf['old_ldp']['hello_ipv4_holdtime'] = conf.return_effective_value('discovery hello-ipv4-holdtime')
-
- if conf.exists('discovery hello-ipv4-holdtime'):
- mpls_conf['ldp']['hello_ipv4_holdtime'] = conf.return_value('discovery hello-ipv4-holdtime')
-
- # Get hello-ipv4-interval
- if conf.exists_effective('discovery hello-ipv4-interval'):
- mpls_conf['old_ldp']['hello_ipv4_interval'] = conf.return_effective_value('discovery hello-ipv4-interval')
-
- if conf.exists('discovery hello-ipv4-interval'):
- mpls_conf['ldp']['hello_ipv4_interval'] = conf.return_value('discovery hello-ipv4-interval')
-
- # Get hello-ipv6-holdtime
- if conf.exists_effective('discovery hello-ipv6-holdtime'):
- mpls_conf['old_ldp']['hello_ipv6_holdtime'] = conf.return_effective_value('discovery hello-ipv6-holdtime')
-
- if conf.exists('discovery hello-ipv6-holdtime'):
- mpls_conf['ldp']['hello_ipv6_holdtime'] = conf.return_value('discovery hello-ipv6-holdtime')
-
- # Get hello-ipv6-interval
- if conf.exists_effective('discovery hello-ipv6-interval'):
- mpls_conf['old_ldp']['hello_ipv6_interval'] = conf.return_effective_value('discovery hello-ipv6-interval')
-
- if conf.exists('discovery hello-ipv6-interval'):
- mpls_conf['ldp']['hello_ipv6_interval'] = conf.return_value('discovery hello-ipv6-interval')
-
- # Get session-ipv4-holdtime
- if conf.exists_effective('discovery session-ipv4-holdtime'):
- mpls_conf['old_ldp']['ses_ipv4_hold'] = conf.return_effective_value('discovery session-ipv4-holdtime')
-
- if conf.exists('discovery session-ipv4-holdtime'):
- mpls_conf['ldp']['ses_ipv4_hold'] = conf.return_value('discovery session-ipv4-holdtime')
-
- # Get session-ipv6-holdtime
- if conf.exists_effective('discovery session-ipv6-holdtime'):
- mpls_conf['old_ldp']['ses_ipv6_hold'] = conf.return_effective_value('discovery session-ipv6-holdtime')
-
- if conf.exists('discovery session-ipv6-holdtime'):
- mpls_conf['ldp']['ses_ipv6_hold'] = conf.return_value('discovery session-ipv6-holdtime')
-
- # Get discovery transport-ipv4-address
- if conf.exists_effective('discovery transport-ipv4-address'):
- mpls_conf['old_ldp']['d_transp_ipv4'] = conf.return_effective_value('discovery transport-ipv4-address')
-
- if conf.exists('discovery transport-ipv4-address'):
- mpls_conf['ldp']['d_transp_ipv4'] = conf.return_value('discovery transport-ipv4-address')
-
- # Get discovery transport-ipv6-address
- if conf.exists_effective('discovery transport-ipv6-address'):
- mpls_conf['old_ldp']['d_transp_ipv6'] = conf.return_effective_value('discovery transport-ipv6-address')
-
- if conf.exists('discovery transport-ipv6-address'):
- mpls_conf['ldp']['d_transp_ipv6'] = conf.return_value('discovery transport-ipv6-address')
-
- # Get export ipv4 explicit-null
- if conf.exists_effective('export ipv4 explicit-null'):
- mpls_conf['old_ldp']['export_ipv4_exp'] = True
-
- if conf.exists('export ipv4 explicit-null'):
- mpls_conf['ldp']['export_ipv4_exp'] = True
-
- # Get export ipv6 explicit-null
- if conf.exists_effective('export ipv6 explicit-null'):
- mpls_conf['old_ldp']['export_ipv6_exp'] = True
-
- if conf.exists('export ipv6 explicit-null'):
- mpls_conf['ldp']['export_ipv6_exp'] = True
-
- # Get target_ipv4_addresses
- if conf.exists_effective('targeted-neighbor ipv4 address'):
- mpls_conf['old_ldp']['target_ipv4_addresses'] = conf.return_effective_values('targeted-neighbor ipv4 address')
+ base = ['protocols', 'mpls']
- if conf.exists('targeted-neighbor ipv4 address'):
- mpls_conf['ldp']['target_ipv4_addresses'] = conf.return_values('targeted-neighbor ipv4 address')
-
- # Get target_ipv4_enable
- if conf.exists_effective('targeted-neighbor ipv4 enable'):
- mpls_conf['old_ldp']['target_ipv4_enable'] = True
-
- if conf.exists('targeted-neighbor ipv4 enable'):
- mpls_conf['ldp']['target_ipv4_enable'] = True
-
- # Get target_ipv4_hello_int
- if conf.exists_effective('targeted-neighbor ipv4 hello-interval'):
- mpls_conf['old_ldp']['target_ipv4_hello_int'] = conf.return_effective_value('targeted-neighbor ipv4 hello-interval')
-
- if conf.exists('targeted-neighbor ipv4 hello-interval'):
- mpls_conf['ldp']['target_ipv4_hello_int'] = conf.return_value('targeted-neighbor ipv4 hello-interval')
-
- # Get target_ipv4_hello_hold
- if conf.exists_effective('targeted-neighbor ipv4 hello-holdtime'):
- mpls_conf['old_ldp']['target_ipv4_hello_hold'] = conf.return_effective_value('targeted-neighbor ipv4 hello-holdtime')
-
- if conf.exists('targeted-neighbor ipv4 hello-holdtime'):
- mpls_conf['ldp']['target_ipv4_hello_hold'] = conf.return_value('targeted-neighbor ipv4 hello-holdtime')
-
- # Get target_ipv6_addresses
- if conf.exists_effective('targeted-neighbor ipv6 address'):
- mpls_conf['old_ldp']['target_ipv6_addresses'] = conf.return_effective_values('targeted-neighbor ipv6 address')
-
- if conf.exists('targeted-neighbor ipv6 address'):
- mpls_conf['ldp']['target_ipv6_addresses'] = conf.return_values('targeted-neighbor ipv6 address')
-
- # Get target_ipv6_enable
- if conf.exists_effective('targeted-neighbor ipv6 enable'):
- mpls_conf['old_ldp']['target_ipv6_enable'] = True
-
- if conf.exists('targeted-neighbor ipv6 enable'):
- mpls_conf['ldp']['target_ipv6_enable'] = True
-
- # Get target_ipv6_hello_int
- if conf.exists_effective('targeted-neighbor ipv6 hello-interval'):
- mpls_conf['old_ldp']['target_ipv6_hello_int'] = conf.return_effective_value('targeted-neighbor ipv6 hello-interval')
-
- if conf.exists('targeted-neighbor ipv6 hello-interval'):
- mpls_conf['ldp']['target_ipv6_hello_int'] = conf.return_value('targeted-neighbor ipv6 hello-interval')
-
- # Get target_ipv6_hello_hold
- if conf.exists_effective('targeted-neighbor ipv6 hello-holdtime'):
- mpls_conf['old_ldp']['target_ipv6_hello_hold'] = conf.return_effective_value('targeted-neighbor ipv6 hello-holdtime')
-
- if conf.exists('targeted-neighbor ipv6 hello-holdtime'):
- mpls_conf['ldp']['target_ipv6_hello_hold'] = conf.return_value('targeted-neighbor ipv6 hello-holdtime')
-
- # Get parameters cisco-interop-tlv
- if conf.exists_effective('parameters cisco-interop-tlv'):
- mpls_conf['old_ldp']['cisco_interop_tlv'] = True
-
- if conf.exists('parameters cisco-interop-tlv'):
- mpls_conf['ldp']['cisco_interop_tlv'] = True
-
- # Get parameters transport-prefer-ipv4
- if conf.exists_effective('parameters transport-prefer-ipv4'):
- mpls_conf['old_ldp']['transport_prefer_ipv4'] = True
-
- if conf.exists('parameters transport-prefer-ipv4'):
- mpls_conf['ldp']['transport_prefer_ipv4'] = True
-
- # Get interfaces
- if conf.exists_effective('interface'):
- mpls_conf['old_ldp']['interfaces'] = conf.return_effective_values('interface')
-
- if conf.exists('interface'):
- mpls_conf['ldp']['interfaces'] = conf.return_values('interface')
-
- # Get neighbors
- for neighbor in conf.list_effective_nodes('neighbor'):
- mpls_conf['old_ldp']['neighbors'].update({
- neighbor : {
- 'password' : conf.return_effective_value('neighbor {0} password'.format(neighbor), default=''),
- 'ttl_security' : conf.return_effective_value('neighbor {0} ttl-security'.format(neighbor), default=''),
- 'session_holdtime' : conf.return_effective_value('neighbor {0} session-holdtime'.format(neighbor), default='')
- }
- })
-
- for neighbor in conf.list_nodes('neighbor'):
- mpls_conf['ldp']['neighbors'].update({
- neighbor : {
- 'password' : conf.return_value('neighbor {0} password'.format(neighbor), default=''),
- 'ttl_security' : conf.return_value('neighbor {0} ttl-security'.format(neighbor), default=''),
- 'session_holdtime' : conf.return_value('neighbor {0} session-holdtime'.format(neighbor), default='')
- }
- })
-
- return mpls_conf
-
-def operate_mpls_on_intfc(interfaces, action):
- rp_filter = 0
- if action == 1:
- rp_filter = 2
- for iface in interfaces:
- sysctl('net.mpls.conf.{0}.input'.format(iface), action)
- # Operate rp filter
- sysctl('net.ipv4.conf.{0}.rp_filter'.format(iface), rp_filter)
+ mpls = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+ return mpls
def verify(mpls):
- if mpls is None:
+ # If no config, then just bail out early.
+ if not mpls:
return None
- if mpls['mpls_ldp']:
- # Require router-id
- if not mpls['router_id']:
- raise ConfigError(f"MPLS ldp router-id is mandatory!")
+ # Checks to see if LDP is properly configured
+ if 'ldp' in mpls:
+ # If router ID not defined
+ if 'router_id' not in mpls['ldp']:
+ raise ConfigError('Router ID missing. An LDP router id is mandatory!')
- # Require discovery transport-address
- if not mpls['ldp']['d_transp_ipv4'] and not mpls['ldp']['d_transp_ipv6']:
- raise ConfigError(f"MPLS ldp discovery transport address is mandatory!")
+ # If interface not set
+ if 'interface' not in mpls['ldp']:
+ raise ConfigError('LDP interfaces are missing. An LDP interface is mandatory!')
- # Require interface
- if not mpls['ldp']['interfaces']:
- raise ConfigError(f"MPLS ldp interface is mandatory!")
+ # If transport addresses are not set
+ if not dict_search('ldp.discovery.transport_ipv4_address', mpls) and \
+ not dict_search('ldp.discovery.transport_ipv6_address', mpls):
+ raise ConfigError('LDP transport address missing!')
+
+ return None
def generate(mpls):
- if mpls is None:
+ # If there's no MPLS config generated, create dictionary key with no value.
+ if not mpls:
+ mpls['new_frr_config'] = ''
return None
- render(config_file, 'frr/ldpd.frr.tmpl', mpls)
+ mpls['new_frr_config'] = render_to_string('frr/ldpd.frr.tmpl', mpls)
return None
def apply(mpls):
- if mpls is None:
- return None
-
- # Set number of entries in the platform label table
- if mpls['mpls_ldp']:
- sysctl('net.mpls.platform_labels', '1048575')
+ # Define dictionary that will load FRR config
+ frr_cfg = {}
+ # Save original configuration prior to starting any commit actions
+ frr_cfg['original_config'] = frr.get_configuration(daemon='ldpd')
+ frr_cfg['modified_config'] = frr.replace_section(frr_cfg['original_config'], mpls['new_frr_config'], from_re='mpls.*')
+
+ # If FRR config is blank, rerun the blank commit three times due to frr-reload
+ # behavior/bug not properly clearing out on one commit.
+ if mpls['new_frr_config'] == '':
+ for x in range(3):
+ frr.reload_configuration(frr_cfg['modified_config'], daemon='ldpd')
+ elif not 'ldp' in mpls:
+ for x in range(3):
+ frr.reload_configuration(frr_cfg['modified_config'], daemon='ldpd')
else:
- sysctl('net.mpls.platform_labels', '0')
-
- # Choose whether to copy IP TTL to MPLS header TTL
- if mpls['parameters']['no_ttl_propagation']:
- sysctl('net.mpls.ip_ttl_propagate', '0')
+ # FRR mark configuration will test for syntax errors and throws an
+ # exception if any syntax errors is detected
+ frr.mark_configuration(frr_cfg['modified_config'])
+
+ # Commit resulting configuration to FRR, this will throw CommitError
+ # on failure
+ frr.reload_configuration(frr_cfg['modified_config'], daemon='ldpd')
+
+ # Set number of entries in the platform label tables
+ labels = '0'
+ if 'interface' in mpls:
+ labels = '1048575'
+ call(f'sysctl -wq net.mpls.platform_labels={labels}')
+
+ # Check for changes in global MPLS options
+ if 'parameters' in mpls:
+ # Choose whether to copy IP TTL to MPLS header TTL
+ if 'no_propagate_ttl' in mpls['parameters']:
+ call('sysctl -wq net.mpls.ip_ttl_propagate=0')
+ # Choose whether to limit maximum MPLS header TTL
+ if 'maximum_ttl' in mpls['parameters']:
+ ttl = mpls['parameters']['maximum_ttl']
+ call(f'sysctl -wq net.mpls.default_ttl={ttl}')
else:
- sysctl('net.mpls.ip_ttl_propagate', '1')
-
- # Choose whether to limit maximum MPLS header TTL
- if mpls['parameters']['maximum_ttl']:
- sysctl('net.mpls.default_ttl', '%s' %(mpls['parameters']['maximum_ttl']))
+ # Set default global MPLS options if not defined.
+ call('sysctl -wq net.mpls.ip_ttl_propagate=1')
+ call('sysctl -wq net.mpls.default_ttl=255')
+
+ # Enable and disable MPLS processing on interfaces per configuration
+ if 'interface' in mpls:
+ system_interfaces = []
+ system_interfaces.append(((os.popen('sysctl net.mpls.conf').read()).split('\n')))
+ del system_interfaces[0][-1]
+ for configured_interface in mpls['interface']:
+ for system_interface in system_interfaces[0]:
+ if configured_interface in system_interface:
+ call(f'sysctl -wq net.mpls.conf.{configured_interface}.input=1')
+ elif system_interface.endswith(' = 1'):
+ system_interface = system_interface.replace(' = 1', '=0')
+ call(f'sysctl -wq {system_interface}')
else:
- sysctl('net.mpls.default_ttl', '255')
-
- # Allow mpls on interfaces
- operate_mpls_on_intfc(mpls['ldp']['interfaces'], 1)
-
- # Disable mpls on deleted interfaces
- diactive_ifaces = set(mpls['old_ldp']['interfaces']).difference(mpls['ldp']['interfaces'])
- operate_mpls_on_intfc(diactive_ifaces, 0)
-
- if os.path.exists(config_file):
- call(f'vtysh -d ldpd -f {config_file}')
- os.remove(config_file)
+ # If MPLS interfaces are not configured, set MPLS processing disabled
+ system_interfaces = []
+ system_interfaces.append(((os.popen('sysctl net.mpls.conf').read()).replace(" = 1", "=0")).split('\n'))
+ del system_interfaces[0][-1]
+ for interface in (system_interfaces[0]):
+ call(f'sysctl -wq {interface}')
return None
diff --git a/src/conf_mode/protocols_pim.py b/src/conf_mode/protocols_pim.py
index 6d333e19a..8a9f034d5 100755
--- a/src/conf_mode/protocols_pim.py
+++ b/src/conf_mode/protocols_pim.py
@@ -21,8 +21,9 @@ from sys import exit
from vyos.config import Config
from vyos import ConfigError
-from vyos.util import call
+from vyos.util import call, process_named_running
from vyos.template import render
+from signal import SIGTERM
from vyos import airbag
airbag.enable()
@@ -36,6 +37,8 @@ def get_config(config=None):
conf = Config()
pim_conf = {
'pim_conf' : False,
+ 'igmp_conf' : False,
+ 'igmp_proxy_conf' : False,
'old_pim' : {
'ifaces' : {},
'rp' : {}
@@ -48,6 +51,12 @@ def get_config(config=None):
if not (conf.exists('protocols pim') or conf.exists_effective('protocols pim')):
return None
+ if conf.exists('protocols igmp-proxy'):
+ pim_conf['igmp_proxy_conf'] = True
+
+ if conf.exists('protocols igmp'):
+ pim_conf['igmp_conf'] = True
+
if conf.exists('protocols pim'):
pim_conf['pim_conf'] = True
@@ -92,6 +101,10 @@ def verify(pim):
return None
if pim['pim_conf']:
+ # Check conflict with IGMP-Proxy
+ if pim['igmp_proxy_conf']:
+ raise ConfigError(f"IGMP proxy and PIM cannot be both configured at the same time")
+
# Check interfaces
if not pim['pim']['ifaces']:
raise ConfigError(f"PIM require defined interfaces!")
@@ -126,9 +139,16 @@ def apply(pim):
if pim is None:
return None
- if os.path.exists(config_file):
- call("vtysh -d pimd -f " + config_file)
- os.remove(config_file)
+ pim_pid = process_named_running('pimd')
+ if pim['igmp_conf'] or pim['pim_conf']:
+ if not pim_pid:
+ call(f'pimd -d -F traditional --daemon -A 127.0.0.1')
+
+ if os.path.exists(config_file):
+ call("vtysh -d pimd -f " + config_file)
+ os.remove(config_file)
+ elif pim_pid:
+ os.kill(int(pim_pid), SIGTERM)
return None
diff --git a/src/conf_mode/service_ids_fastnetmon.py b/src/conf_mode/service_ids_fastnetmon.py
index 27d0ee60c..67edeb630 100755
--- a/src/conf_mode/service_ids_fastnetmon.py
+++ b/src/conf_mode/service_ids_fastnetmon.py
@@ -56,7 +56,7 @@ def verify(fastnetmon):
if not os.access(fastnetmon["alert_script"], os.X_OK):
raise ConfigError('Script {0} does not have permissions for execution'.format(fastnetmon["alert_script"]))
else:
- raise ConfigError('File {0} does not exists!'.format(fastnetmon["alert_script"]))
+ raise ConfigError('File {0} does not exists!'.format(fastnetmon["alert_script"]))
def generate(fastnetmon):
if not fastnetmon:
@@ -67,8 +67,8 @@ def generate(fastnetmon):
return
- render(config_file, 'ids/fastnetmon.tmpl', fastnetmon, trim_blocks=True)
- render(networks_list, 'ids/fastnetmon_networks_list.tmpl', fastnetmon, trim_blocks=True)
+ render(config_file, 'ids/fastnetmon.tmpl', fastnetmon)
+ render(networks_list, 'ids/fastnetmon_networks_list.tmpl', fastnetmon)
return None
diff --git a/src/conf_mode/service_ipoe-server.py b/src/conf_mode/service_ipoe-server.py
index 68c554360..f676fdbbe 100755
--- a/src/conf_mode/service_ipoe-server.py
+++ b/src/conf_mode/service_ipoe-server.py
@@ -283,7 +283,7 @@ def generate(ipoe):
if not ipoe:
return None
- render(ipoe_conf, 'accel-ppp/ipoe.config.tmpl', ipoe, trim_blocks=True)
+ render(ipoe_conf, 'accel-ppp/ipoe.config.tmpl', ipoe)
if ipoe['auth_mode'] == 'local':
render(ipoe_chap_secrets, 'accel-ppp/chap-secrets.ipoe.tmpl', ipoe)
diff --git a/src/conf_mode/service_pppoe-server.py b/src/conf_mode/service_pppoe-server.py
index 2260b3fe1..9fbd531da 100755
--- a/src/conf_mode/service_pppoe-server.py
+++ b/src/conf_mode/service_pppoe-server.py
@@ -73,11 +73,11 @@ def generate(pppoe):
if not pppoe:
return None
- render(pppoe_conf, 'accel-ppp/pppoe.config.tmpl', pppoe, trim_blocks=True)
+ render(pppoe_conf, 'accel-ppp/pppoe.config.tmpl', pppoe)
if dict_search('authentication.mode', pppoe) == 'local':
render(pppoe_chap_secrets, 'accel-ppp/chap-secrets.config_dict.tmpl',
- pppoe, trim_blocks=True, permission=0o640)
+ pppoe, permission=0o640)
else:
if os.path.exists(pppoe_chap_secrets):
os.unlink(pppoe_chap_secrets)
diff --git a/src/conf_mode/service_router-advert.py b/src/conf_mode/service_router-advert.py
index 687d7068f..65eb11ce3 100755
--- a/src/conf_mode/service_router-advert.py
+++ b/src/conf_mode/service_router-advert.py
@@ -93,7 +93,7 @@ def generate(rtradv):
if not rtradv:
return None
- render(config_file, 'router-advert/radvd.conf.tmpl', rtradv, trim_blocks=True, permission=0o644)
+ render(config_file, 'router-advert/radvd.conf.tmpl', rtradv, permission=0o644)
return None
def apply(rtradv):
diff --git a/src/conf_mode/ssh.py b/src/conf_mode/ssh.py
index e07745963..8f99053d2 100755
--- a/src/conf_mode/ssh.py
+++ b/src/conf_mode/ssh.py
@@ -66,8 +66,8 @@ def generate(ssh):
return None
- render(config_file, 'ssh/sshd_config.tmpl', ssh, trim_blocks=True)
- render(systemd_override, 'ssh/override.conf.tmpl', ssh, trim_blocks=True)
+ render(config_file, 'ssh/sshd_config.tmpl', ssh)
+ render(systemd_override, 'ssh/override.conf.tmpl', ssh)
return None
diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py
index 2c0bbd4f7..39bad717d 100755
--- a/src/conf_mode/system-login.py
+++ b/src/conf_mode/system-login.py
@@ -256,7 +256,7 @@ def generate(login):
if len(login['radius_server']) > 0:
render(radius_config_file, 'system-login/pam_radius_auth.conf.tmpl',
- login, trim_blocks=True)
+ login)
uid = getpwnam('root').pw_uid
gid = getpwnam('root').pw_gid
diff --git a/src/conf_mode/system-options.py b/src/conf_mode/system-option.py
index 1061b90ac..910c14474 100755
--- a/src/conf_mode/system-options.py
+++ b/src/conf_mode/system-option.py
@@ -39,7 +39,7 @@ def get_config(config=None):
conf = config
else:
conf = Config()
- base = ['system', 'options']
+ base = ['system', 'option']
options = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
# We have gathered the dict representation of the CLI, but there are default
@@ -73,13 +73,13 @@ def verify(options):
return None
def generate(options):
- render(curlrc_config, 'system/curlrc.tmpl', options, trim_blocks=True)
- render(ssh_config, 'system/ssh_config.tmpl', options, trim_blocks=True)
+ render(curlrc_config, 'system/curlrc.tmpl', options)
+ render(ssh_config, 'system/ssh_config.tmpl', options)
return None
def apply(options):
# System bootup beep
- if 'beep_if_fully_booted' in options:
+ if 'startup_beep' in options:
cmd('systemctl enable vyos-beep.service')
else:
cmd('systemctl disable vyos-beep.service')
@@ -87,10 +87,10 @@ def apply(options):
# Ctrl-Alt-Delete action
if os.path.exists(systemd_action_file):
os.unlink(systemd_action_file)
- if 'ctrl_alt_del_action' in options:
- if options['ctrl_alt_del_action'] == 'reboot':
+ if 'ctrl_alt_del' in options:
+ if options['ctrl_alt_del'] == 'reboot':
os.symlink('/lib/systemd/system/reboot.target', systemd_action_file)
- elif options['ctrl_alt_del_action'] == 'poweroff':
+ elif options['ctrl_alt_del'] == 'poweroff':
os.symlink('/lib/systemd/system/poweroff.target', systemd_action_file)
# Configure HTTP client
@@ -123,7 +123,7 @@ def apply(options):
# Keyboard layout - there will be always the default key inside the dict
# but we check for key existence anyway
if 'keyboard_layout' in options:
- cmd('loadkeys -C /dev/console {keyboard_layout}'.format(**options))
+ cmd('loadkeys {keyboard_layout}'.format(**options))
if __name__ == '__main__':
try:
@@ -134,4 +134,3 @@ if __name__ == '__main__':
except ConfigError as e:
print(e)
exit(1)
-
diff --git a/src/conf_mode/system-syslog.py b/src/conf_mode/system-syslog.py
index b1daf7a82..3d8a51cd8 100755
--- a/src/conf_mode/system-syslog.py
+++ b/src/conf_mode/system-syslog.py
@@ -203,12 +203,12 @@ def generate(c):
return None
conf = '/etc/rsyslog.d/vyos-rsyslog.conf'
- render(conf, 'syslog/rsyslog.conf.tmpl', c, trim_blocks=True)
+ render(conf, 'syslog/rsyslog.conf.tmpl', c)
# eventually write for each file its own logrotate file, since size is
# defined it shouldn't matter
conf = '/etc/logrotate.d/vyos-rsyslog'
- render(conf, 'syslog/logrotate.tmpl', c, trim_blocks=True)
+ render(conf, 'syslog/logrotate.tmpl', c)
def verify(c):
diff --git a/src/conf_mode/system_lcd.py b/src/conf_mode/system_lcd.py
index a540d1b9e..b5ce32beb 100755
--- a/src/conf_mode/system_lcd.py
+++ b/src/conf_mode/system_lcd.py
@@ -61,9 +61,9 @@ def generate(lcd):
lcd['device'] = find_device_file(lcd['device'])
# Render config file for daemon LCDd
- render(lcdd_conf, 'lcd/LCDd.conf.tmpl', lcd, trim_blocks=True)
+ render(lcdd_conf, 'lcd/LCDd.conf.tmpl', lcd)
# Render config file for client lcdproc
- render(lcdproc_conf, 'lcd/lcdproc.conf.tmpl', lcd, trim_blocks=True)
+ render(lcdproc_conf, 'lcd/lcdproc.conf.tmpl', lcd)
return None
diff --git a/src/conf_mode/tftp_server.py b/src/conf_mode/tftp_server.py
index 56e195b6a..2409eec1f 100755
--- a/src/conf_mode/tftp_server.py
+++ b/src/conf_mode/tftp_server.py
@@ -92,7 +92,7 @@ def generate(tftpd):
config['listen_address'] = f'[{address}]:{port} -6'
file = config_file + str(idx)
- render(file, 'tftp-server/default.tmpl', config, trim_blocks=True)
+ render(file, 'tftp-server/default.tmpl', config)
idx = idx + 1
return None
diff --git a/src/conf_mode/vpn_l2tp.py b/src/conf_mode/vpn_l2tp.py
index 80eb8daf2..e970d2ef5 100755
--- a/src/conf_mode/vpn_l2tp.py
+++ b/src/conf_mode/vpn_l2tp.py
@@ -357,7 +357,7 @@ def generate(l2tp):
if not l2tp:
return None
- render(l2tp_conf, 'accel-ppp/l2tp.config.tmpl', l2tp, trim_blocks=True)
+ render(l2tp_conf, 'accel-ppp/l2tp.config.tmpl', l2tp)
if l2tp['auth_mode'] == 'local':
render(l2tp_chap_secrets, 'accel-ppp/chap-secrets.tmpl', l2tp)
diff --git a/src/conf_mode/vpn_openconnect.py b/src/conf_mode/vpn_openconnect.py
index af8604972..b2aa13c0d 100755
--- a/src/conf_mode/vpn_openconnect.py
+++ b/src/conf_mode/vpn_openconnect.py
@@ -34,12 +34,10 @@ ocserv_passwd = cfg_dir + '/ocpasswd'
radius_cfg = cfg_dir + '/radiusclient.conf'
radius_servers = cfg_dir + '/radius_servers'
-
# Generate hash from user cleartext password
def get_hash(password):
return crypt(password, mksalt(METHOD_SHA512))
-
def get_config():
conf = Config()
base = ['vpn', 'openconnect']
@@ -47,10 +45,12 @@ def get_config():
return None
ocserv = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=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)
ocserv = dict_merge(default_values, ocserv)
- return ocserv
+ return ocserv
def verify(ocserv):
if ocserv is None:
@@ -88,7 +88,7 @@ def verify(ocserv):
ocserv["network_settings"]["push_route"].remove("0.0.0.0/0")
ocserv["network_settings"]["push_route"].append("default")
else:
- ocserv["network_settings"]["push_route"] = "default"
+ ocserv["network_settings"]["push_route"] = "default"
else:
raise ConfigError('openconnect network settings required')
@@ -99,19 +99,18 @@ def generate(ocserv):
if "radius" in ocserv["authentication"]["mode"]:
# Render radius client configuration
- render(radius_cfg, 'ocserv/radius_conf.tmpl', ocserv["authentication"]["radius"], trim_blocks=True)
+ render(radius_cfg, 'ocserv/radius_conf.tmpl', ocserv["authentication"]["radius"])
# Render radius servers
- render(radius_servers, 'ocserv/radius_servers.tmpl', ocserv["authentication"]["radius"], trim_blocks=True)
+ render(radius_servers, 'ocserv/radius_servers.tmpl', ocserv["authentication"]["radius"])
else:
if "local_users" in ocserv["authentication"]:
for user in ocserv["authentication"]["local_users"]["username"]:
ocserv["authentication"]["local_users"]["username"][user]["hash"] = get_hash(ocserv["authentication"]["local_users"]["username"][user]["password"])
# Render local users
- render(ocserv_passwd, 'ocserv/ocserv_passwd.tmpl', ocserv["authentication"]["local_users"], trim_blocks=True)
+ render(ocserv_passwd, 'ocserv/ocserv_passwd.tmpl', ocserv["authentication"]["local_users"])
# Render config
- render(ocserv_conf, 'ocserv/ocserv_config.tmpl', ocserv, trim_blocks=True)
-
+ render(ocserv_conf, 'ocserv/ocserv_config.tmpl', ocserv)
def apply(ocserv):
diff --git a/src/conf_mode/vpn_pptp.py b/src/conf_mode/vpn_pptp.py
index 3125ee9d0..30abe4782 100755
--- a/src/conf_mode/vpn_pptp.py
+++ b/src/conf_mode/vpn_pptp.py
@@ -264,10 +264,10 @@ def generate(pptp):
if not pptp:
return None
- render(pptp_conf, 'accel-ppp/pptp.config.tmpl', pptp, trim_blocks=True)
+ render(pptp_conf, 'accel-ppp/pptp.config.tmpl', pptp)
if pptp['local_users']:
- render(pptp_chap_secrets, 'accel-ppp/chap-secrets.tmpl', pptp, trim_blocks=True)
+ render(pptp_chap_secrets, 'accel-ppp/chap-secrets.tmpl', pptp)
os.chmod(pptp_chap_secrets, S_IRUSR | S_IWUSR | S_IRGRP)
else:
if os.path.exists(pptp_chap_secrets):
diff --git a/src/conf_mode/vpn_sstp.py b/src/conf_mode/vpn_sstp.py
index 1b2b80ce5..47367f125 100755
--- a/src/conf_mode/vpn_sstp.py
+++ b/src/conf_mode/vpn_sstp.py
@@ -82,11 +82,11 @@ def generate(sstp):
return None
# accel-cmd reload doesn't work so any change results in a restart of the daemon
- render(sstp_conf, 'accel-ppp/sstp.config.tmpl', sstp, trim_blocks=True)
+ render(sstp_conf, 'accel-ppp/sstp.config.tmpl', sstp)
if dict_search('authentication.mode', sstp) == 'local':
render(sstp_chap_secrets, 'accel-ppp/chap-secrets.config_dict.tmpl',
- sstp, trim_blocks=True, permission=0o640)
+ sstp, permission=0o640)
else:
if os.path.exists(sstp_chap_secrets):
os.unlink(sstp_chap_secrets)