summaryrefslogtreecommitdiff
path: root/src/conf_mode
diff options
context:
space:
mode:
Diffstat (limited to 'src/conf_mode')
-rwxr-xr-xsrc/conf_mode/interfaces-erspan.py114
-rwxr-xr-xsrc/conf_mode/interfaces-ethernet.py25
-rwxr-xr-xsrc/conf_mode/interfaces-pppoe.py10
-rwxr-xr-xsrc/conf_mode/interfaces-tunnel.py35
-rwxr-xr-xsrc/conf_mode/lldp.py9
-rwxr-xr-xsrc/conf_mode/protocols_bfd.py230
-rwxr-xr-xsrc/conf_mode/protocols_bgp.py75
-rwxr-xr-xsrc/conf_mode/protocols_ospf.py56
-rwxr-xr-xsrc/conf_mode/protocols_ospfv3.py41
-rwxr-xr-xsrc/conf_mode/protocols_rip.py327
-rwxr-xr-xsrc/conf_mode/protocols_ripng.py133
-rwxr-xr-xsrc/conf_mode/protocols_rpki.py110
-rwxr-xr-xsrc/conf_mode/protocols_static.py74
-rwxr-xr-xsrc/conf_mode/protocols_vrf.py72
-rwxr-xr-xsrc/conf_mode/service_console-server.py23
-rwxr-xr-xsrc/conf_mode/service_webproxy.py3
-rwxr-xr-xsrc/conf_mode/vrrp.py6
17 files changed, 773 insertions, 570 deletions
diff --git a/src/conf_mode/interfaces-erspan.py b/src/conf_mode/interfaces-erspan.py
new file mode 100755
index 000000000..2d65b834c
--- /dev/null
+++ b/src/conf_mode/interfaces-erspan.py
@@ -0,0 +1,114 @@
+#!/usr/bin/env python3
+#
+# 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
+# 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 copy import deepcopy
+from netifaces import interfaces
+
+from vyos.config import Config
+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_mtu_ipv6
+from vyos.configverify import verify_tunnel
+from vyos.ifconfig import Interface
+from vyos.ifconfig import ERSpanIf
+from vyos.ifconfig import ER6SpanIf
+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()
+
+def get_config(config=None):
+ """
+ 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', 'erspan']
+ erspan = get_interface_dict(conf, base)
+
+ tmp = leaf_node_changed(conf, ['encapsulation'])
+ if tmp:
+ erspan.update({'encapsulation_changed': {}})
+
+ return erspan
+
+def verify(erspan):
+ if 'deleted' in erspan:
+ return None
+
+ if 'encapsulation' not in erspan:
+ raise ConfigError('Unable to detect the following ERSPAN tunnel encapsulation'\
+ '{ifname}!'.format(**erspan))
+
+ verify_mtu_ipv6(erspan)
+ verify_tunnel(erspan)
+
+ key = dict_search('parameters.ip.key',erspan)
+ if key == None:
+ raise ConfigError('parameters.ip.key is mandatory for ERSPAN tunnel')
+
+
+def generate(erspan):
+ return None
+
+def apply(erspan):
+ if 'deleted' in erspan or 'encapsulation_changed' in erspan:
+ if erspan['ifname'] in interfaces():
+ tmp = Interface(erspan['ifname'])
+ tmp.remove()
+ if 'deleted' in erspan:
+ return None
+
+ dispatch = {
+ 'erspan': ERSpanIf,
+ 'ip6erspan': ER6SpanIf
+ }
+
+ # We need to re-map the tunnel encapsulation proto to a valid interface class
+ encap = erspan['encapsulation']
+ klass = dispatch[encap]
+
+ conf = deepcopy(erspan)
+
+ conf.update(klass.get_config())
+
+ del conf['ifname']
+
+ erspan_tunnel = klass(erspan['ifname'],**conf)
+ erspan_tunnel.change_options()
+ erspan_tunnel.update(erspan)
+
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ generate(c)
+ verify(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py
index e7f0cd6a5..e82a3e0f1 100755
--- a/src/conf_mode/interfaces-ethernet.py
+++ b/src/conf_mode/interfaces-ethernet.py
@@ -30,6 +30,7 @@ from vyos.configverify import verify_mtu
from vyos.configverify import verify_mtu_ipv6
from vyos.configverify import verify_vlan_config
from vyos.configverify import verify_vrf
+from vyos.ethtool import Ethtool
from vyos.ifconfig import EthernetIf
from vyos.template import render
from vyos.util import call
@@ -76,10 +77,32 @@ def verify(ethernet):
verify_mirror(ethernet)
# verify offloading capabilities
- if 'offload' in ethernet and 'rps' in ethernet['offload']:
+ if dict_search('offload.rps', ethernet) != None:
if not os.path.exists(f'/sys/class/net/{ifname}/queues/rx-0/rps_cpus'):
raise ConfigError('Interface does not suport RPS!')
+ driver = EthernetIf(ifname).get_driver_name()
+ # T3342 - Xen driver requires special treatment
+ if driver == 'vif':
+ if int(ethernet['mtu']) > 1500 and dict_search('offload.sg', ethernet) == None:
+ raise ConfigError('Xen netback drivers requires scatter-gatter offloading '\
+ 'for MTU size larger then 1500 bytes')
+
+ ethtool = Ethtool(ifname)
+ if 'ring_buffer' in ethernet:
+ max_rx = ethtool.get_rx_buffer()
+ max_tx = ethtool.get_tx_buffer()
+
+ rx = dict_search('ring_buffer.rx', ethernet)
+ if rx and int(rx) > int(max_rx):
+ raise ConfigError(f'Driver only supports a maximum RX ring-buffer '\
+ f'size of "{max_rx}" bytes!')
+
+ tx = dict_search('ring_buffer.tx', ethernet)
+ if tx and int(tx) > int(max_tx):
+ raise ConfigError(f'Driver only supports a maximum TX ring-buffer '\
+ f'size of "{max_tx}" bytes!')
+
# XDP requires multiple TX queues
if 'xdp' in ethernet:
queues = glob(f'/sys/class/net/{ifname}/queues/tx-*')
diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py
index f49792a7a..3675db73b 100755
--- a/src/conf_mode/interfaces-pppoe.py
+++ b/src/conf_mode/interfaces-pppoe.py
@@ -73,7 +73,7 @@ def generate(pppoe):
config_files = [config_pppoe, script_pppoe_pre_up, script_pppoe_ip_up,
script_pppoe_ip_down, script_pppoe_ipv6_up, config_wide_dhcp6c]
- if 'deleted' in pppoe:
+ if 'deleted' in pppoe or 'disable' in pppoe:
# stop DHCPv6-PD client
call(f'systemctl stop dhcp6c@{ifname}.service')
# Hang-up PPPoE connection
@@ -110,13 +110,11 @@ def generate(pppoe):
return None
def apply(pppoe):
- if 'deleted' in pppoe:
- # bail out early
+ if 'deleted' in pppoe or 'disable' in pppoe:
+ call('systemctl stop ppp@{ifname}.service'.format(**pppoe))
return None
- if 'disable' not in pppoe:
- # Dial PPPoE connection
- call('systemctl restart ppp@{ifname}.service'.format(**pppoe))
+ call('systemctl restart ppp@{ifname}.service'.format(**pppoe))
return None
diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py
index f03bc9d5d..87da214a8 100755
--- a/src/conf_mode/interfaces-tunnel.py
+++ b/src/conf_mode/interfaces-tunnel.py
@@ -29,6 +29,7 @@ from vyos.configverify import verify_bridge_delete
from vyos.configverify import verify_interface_exists
from vyos.configverify import verify_mtu_ipv6
from vyos.configverify import verify_vrf
+from vyos.configverify import verify_tunnel
from vyos.ifconfig import Interface
from vyos.ifconfig import GREIf
from vyos.ifconfig import GRETapIf
@@ -84,38 +85,7 @@ def verify(tunnel):
verify_mtu_ipv6(tunnel)
verify_address(tunnel)
verify_vrf(tunnel)
-
- 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')
-
- 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')
-
- 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')
+ verify_tunnel(tunnel)
if 'source_interface' in tunnel:
verify_interface_exists(tunnel['source_interface'])
@@ -170,7 +140,6 @@ def apply(tunnel):
'parameters.ip.ttl' : 'ttl',
'parameters.ip.tos' : 'tos',
'parameters.ip.key' : 'key',
- 'parameters.ipv6.encaplimit' : 'encaplimit'
}
# Add additional IPv6 options if tunnel is IPv6 aware
diff --git a/src/conf_mode/lldp.py b/src/conf_mode/lldp.py
index 6b645857a..082c3e128 100755
--- a/src/conf_mode/lldp.py
+++ b/src/conf_mode/lldp.py
@@ -21,7 +21,8 @@ from copy import deepcopy
from sys import exit
from vyos.config import Config
-from vyos.validate import is_addr_assigned,is_loopback_addr
+from vyos.validate import is_addr_assigned
+from vyos.validate import is_loopback_addr
from vyos.version import get_version_data
from vyos import ConfigError
from vyos.util import call
@@ -237,8 +238,10 @@ def apply(lldp):
else:
# LLDP service has been terminated
call('systemctl stop lldpd.service')
- os.unlink(config_file)
- os.unlink(vyos_config_file)
+ if os.path.isfile(config_file):
+ os.unlink(config_file)
+ if os.path.isfile(vyos_config_file):
+ os.unlink(vyos_config_file)
if __name__ == '__main__':
try:
diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py
index d1e551cad..a43eed504 100755
--- a/src/conf_mode/protocols_bfd.py
+++ b/src/conf_mode/protocols_bfd.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019-2020 VyOS maintainers and contributors
+# Copyright (C) 2019-2021 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -17,191 +17,97 @@
import os
from sys import exit
-from copy import deepcopy
from vyos.config import Config
+from vyos.configdict import dict_merge
from vyos.template import is_ipv6
-from vyos.template import render
+from vyos.template import render_to_string
from vyos.util import call
from vyos.validate import is_ipv6_link_local
+from vyos.xml import defaults
from vyos import ConfigError
+from vyos import frr
from vyos import airbag
airbag.enable()
-config_file = r'/tmp/bfd.frr'
-
-default_config_data = {
- 'new_peers': [],
- 'old_peers' : []
-}
-
-# get configuration for BFD peer from proposed or effective configuration
-def get_bfd_peer_config(peer, conf_mode="proposed"):
- conf = Config()
- conf.set_level('protocols bfd peer {0}'.format(peer))
-
- bfd_peer = {
- 'remote': peer,
- 'shutdown': False,
- 'src_if': '',
- 'src_addr': '',
- 'multiplier': '3',
- 'rx_interval': '300',
- 'tx_interval': '300',
- 'multihop': False,
- 'echo_interval': '',
- 'echo_mode': False,
- }
-
- # Check if individual peer is disabled
- if conf_mode == "effective" and conf.exists_effective('shutdown'):
- bfd_peer['shutdown'] = True
- if conf_mode == "proposed" and conf.exists('shutdown'):
- bfd_peer['shutdown'] = True
-
- # Check if peer has a local source interface configured
- if conf_mode == "effective" and conf.exists_effective('source interface'):
- bfd_peer['src_if'] = conf.return_effective_value('source interface')
- if conf_mode == "proposed" and conf.exists('source interface'):
- bfd_peer['src_if'] = conf.return_value('source interface')
-
- # Check if peer has a local source address configured - this is mandatory for IPv6
- if conf_mode == "effective" and conf.exists_effective('source address'):
- bfd_peer['src_addr'] = conf.return_effective_value('source address')
- if conf_mode == "proposed" and conf.exists('source address'):
- bfd_peer['src_addr'] = conf.return_value('source address')
-
- # Tell BFD daemon that we should expect packets with TTL less than 254
- # (because it will take more than one hop) and to listen on the multihop
- # port (4784)
- if conf_mode == "effective" and conf.exists_effective('multihop'):
- bfd_peer['multihop'] = True
- if conf_mode == "proposed" and conf.exists('multihop'):
- bfd_peer['multihop'] = True
-
- # Configures the minimum interval that this system is capable of receiving
- # control packets. The default value is 300 milliseconds.
- if conf_mode == "effective" and conf.exists_effective('interval receive'):
- bfd_peer['rx_interval'] = conf.return_effective_value('interval receive')
- if conf_mode == "proposed" and conf.exists('interval receive'):
- bfd_peer['rx_interval'] = conf.return_value('interval receive')
-
- # The minimum transmission interval (less jitter) that this system wants
- # to use to send BFD control packets.
- if conf_mode == "effective" and conf.exists_effective('interval transmit'):
- bfd_peer['tx_interval'] = conf.return_effective_value('interval transmit')
- if conf_mode == "proposed" and conf.exists('interval transmit'):
- bfd_peer['tx_interval'] = conf.return_value('interval transmit')
-
- # Configures the detection multiplier to determine packet loss. The remote
- # transmission interval will be multiplied by this value to determine the
- # connection loss detection timer. The default value is 3.
- if conf_mode == "effective" and conf.exists_effective('interval multiplier'):
- bfd_peer['multiplier'] = conf.return_effective_value('interval multiplier')
- if conf_mode == "proposed" and conf.exists('interval multiplier'):
- bfd_peer['multiplier'] = conf.return_value('interval multiplier')
-
- # Configures the minimal echo receive transmission interval that this system is capable of handling
- if conf_mode == "effective" and conf.exists_effective('interval echo-interval'):
- bfd_peer['echo_interval'] = conf.return_effective_value('interval echo-interval')
- if conf_mode == "proposed" and conf.exists('interval echo-interval'):
- bfd_peer['echo_interval'] = conf.return_value('interval echo-interval')
-
- # Enables or disables the echo transmission mode
- if conf_mode == "effective" and conf.exists_effective('echo-mode'):
- bfd_peer['echo_mode'] = True
- if conf_mode == "proposed" and conf.exists('echo-mode'):
- bfd_peer['echo_mode'] = True
-
- return bfd_peer
-
-def get_config():
- bfd = deepcopy(default_config_data)
- conf = Config()
- if not (conf.exists('protocols bfd') or conf.exists_effective('protocols bfd')):
- return None
+def get_config(config=None):
+ if config:
+ conf = config
else:
- conf.set_level('protocols bfd')
-
- # as we have to use vtysh to talk to FRR we also need to know
- # which peers are gone due to a config removal - thus we read in
- # all peers (active or to delete)
- for peer in conf.list_effective_nodes('peer'):
- bfd['old_peers'].append(get_bfd_peer_config(peer, "effective"))
-
- for peer in conf.list_nodes('peer'):
- bfd['new_peers'].append(get_bfd_peer_config(peer))
-
- # find deleted peers
- set_new_peers = set(conf.list_nodes('peer'))
- set_old_peers = set(conf.list_effective_nodes('peer'))
- bfd['deleted_peers'] = set_old_peers - set_new_peers
+ conf = Config()
+ base = ['protocols', 'bfd']
+ bfd = conf.get_config_dict(base, get_first_key=True)
+
+ # Bail out early if configuration tree does not exist
+ if not conf.exists(base):
+ return bfd
+
+ # We have gathered the dict representation of the CLI, but there are
+ # default options which we need to update into the dictionary retrived.
+ # XXX: T2665: we currently have no nice way for defaults under tag
+ # nodes, thus we load the defaults "by hand"
+ default_values = defaults(base + ['peer'])
+ if 'peer' in bfd:
+ for peer in bfd['peer']:
+ bfd['peer'][peer] = dict_merge(default_values, bfd['peer'][peer])
+
+ if 'profile' in bfd:
+ for profile in bfd['profile']:
+ bfd['profile'][profile] = dict_merge(default_values, bfd['profile'][profile])
return bfd
def verify(bfd):
- if bfd is None:
+ if not bfd:
return None
- # some variables to use later
- conf = Config()
-
- for peer in bfd['new_peers']:
- # IPv6 link local peers require an explicit local address/interface
- if is_ipv6_link_local(peer['remote']):
- if not (peer['src_if'] and peer['src_addr']):
- raise ConfigError('BFD IPv6 link-local peers require explicit local address and interface setting')
-
- # IPv6 peers require an explicit local address
- if is_ipv6(peer['remote']):
- if not peer['src_addr']:
- raise ConfigError('BFD IPv6 peers require explicit local address setting')
-
- # multihop require source address
- if peer['multihop'] and not peer['src_addr']:
- raise ConfigError('Multihop require source address')
-
- # multihop and echo-mode cannot be used together
- if peer['multihop'] and peer['echo_mode']:
- raise ConfigError('Multihop and echo-mode cannot be used together')
-
- # multihop doesn't accept interface names
- if peer['multihop'] and peer['src_if']:
- raise ConfigError('Multihop and source interface cannot be used together')
-
- # echo interval can be configured only with enabled echo-mode
- if peer['echo_interval'] != '' and not peer['echo_mode']:
- raise ConfigError('echo-interval can be configured only with enabled echo-mode')
-
- # check if we deleted peers are not used in configuration
- if conf.exists('protocols bgp'):
- bgp_as = conf.list_nodes('protocols bgp')[0]
-
- # check BGP neighbors
- for peer in bfd['deleted_peers']:
- if conf.exists('protocols bgp {0} neighbor {1} bfd'.format(bgp_as, peer)):
- raise ConfigError('Cannot delete BFD peer {0}: it is used in BGP configuration'.format(peer))
- if conf.exists('protocols bgp {0} neighbor {1} peer-group'.format(bgp_as, peer)):
- peer_group = conf.return_value('protocols bgp {0} neighbor {1} peer-group'.format(bgp_as, peer))
- if conf.exists('protocols bgp {0} peer-group {1} bfd'.format(bgp_as, peer_group)):
- raise ConfigError('Cannot delete BFD peer {0}: it belongs to BGP peer-group {1} with enabled BFD'.format(peer, peer_group))
+ if 'peer' in bfd:
+ for peer, peer_config in bfd['peer'].items():
+ # IPv6 link local peers require an explicit local address/interface
+ if is_ipv6_link_local(peer):
+ if 'source' not in peer_config or len(peer_config['source'] < 2):
+ raise ConfigError('BFD IPv6 link-local peers require explicit local address and interface setting')
+
+ # IPv6 peers require an explicit local address
+ if is_ipv6(peer):
+ if 'source' not in peer_config or 'address' not in peer_config['source']:
+ raise ConfigError('BFD IPv6 peers require explicit local address setting')
+
+ if 'multihop' in peer_config:
+ # multihop require source address
+ if 'source' not in peer_config or 'address' not in peer_config['source']:
+ raise ConfigError('BFD multihop require source address')
+
+ # multihop and echo-mode cannot be used together
+ if 'echo_mode' in peer_config:
+ raise ConfigError('Multihop and echo-mode cannot be used together')
+
+ # multihop doesn't accept interface names
+ if 'source' in peer_config and 'interface' in peer_config['source']:
+ raise ConfigError('Multihop and source interface cannot be used together')
return None
def generate(bfd):
- if bfd is None:
+ if not bfd:
+ bfd['new_frr_config'] = ''
return None
- render(config_file, 'frr/bfd.frr.tmpl', bfd)
- return None
+ bfd['new_frr_config'] = render_to_string('frr/bfd.frr.tmpl', bfd)
def apply(bfd):
- if bfd is None:
- return None
-
- call("vtysh -d bfdd -f " + config_file)
- if os.path.exists(config_file):
- os.remove(config_file)
+ # Save original configuration prior to starting any commit actions
+ frr_cfg = frr.FRRConfig()
+ frr_cfg.load_configuration()
+ frr_cfg.modify_section('^bfd', '')
+ frr_cfg.add_before(r'(ip prefix-list .*|route-map .*|line vty)', bfd['new_frr_config'])
+ frr_cfg.commit_configuration()
+
+ # If FRR config is blank, rerun the blank commit x times due to frr-reload
+ # behavior/bug not properly clearing out on one commit.
+ if bfd['new_frr_config'] == '':
+ for a in range(5):
+ frr_cfg.commit_configuration()
return None
diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py
index 41d89e03b..baf5c4159 100755
--- a/src/conf_mode/protocols_bgp.py
+++ b/src/conf_mode/protocols_bgp.py
@@ -20,7 +20,6 @@ from sys import exit
from vyos.config import Config
from vyos.configdict import dict_merge
-from vyos.template import render
from vyos.template import render_to_string
from vyos.util import call
from vyos.util import dict_search
@@ -29,17 +28,8 @@ from vyos import frr
from vyos import airbag
airbag.enable()
-config_file = r'/tmp/bgp.frr'
frr_daemon = 'bgpd'
-DEBUG = os.path.exists('/tmp/bgp.debug')
-if DEBUG:
- import logging
- lg = logging.getLogger("vyos.frr")
- lg.setLevel(logging.DEBUG)
- ch = logging.StreamHandler()
- lg.addHandler(ch)
-
def get_config(config=None):
if config:
conf = config
@@ -64,6 +54,26 @@ def get_config(config=None):
return bgp
+def verify_remote_as(peer_config, asn_config):
+ if 'remote_as' in peer_config:
+ return peer_config['remote_as']
+
+ if 'peer_group' in peer_config:
+ peer_group_name = peer_config['peer_group']
+ tmp = dict_search(f'peer_group.{peer_group_name}.remote_as', asn_config)
+ if tmp: return tmp
+
+ if 'interface' in peer_config:
+ if 'remote_as' in peer_config['interface']:
+ return peer_config['interface']['remote_as']
+
+ if 'peer_group' in peer_config['interface']:
+ peer_group_name = peer_config['interface']['peer_group']
+ tmp = dict_search(f'peer_group.{peer_group_name}.remote_as', asn_config)
+ if tmp: return tmp
+
+ return None
+
def verify(bgp):
if not bgp:
return None
@@ -89,20 +99,15 @@ def verify(bgp):
raise ConfigError(f'Specified peer-group "{peer_group}" for '\
f'neighbor "{neighbor}" does not exist!')
- # Some checks can/must only be done on a neighbor and nor a peer-group
+
+ # Some checks can/must only be done on a neighbor and not a peer-group
if neighbor == 'neighbor':
# remote-as must be either set explicitly for the neighbor
# or for the entire peer-group
- if 'interface' in peer_config:
- if 'remote_as' not in peer_config['interface']:
- if 'peer_group' not in peer_config['interface'] or 'remote_as' not in asn_config['peer_group'][ peer_config['interface']['peer_group'] ]:
- raise ConfigError('Remote AS must be set for neighbor or peer-group!')
-
- elif 'remote_as' not in peer_config:
- if 'peer_group' not in peer_config or 'remote_as' not in asn_config['peer_group'][ peer_config['peer_group'] ]:
- raise ConfigError('Remote AS must be set for neighbor or peer-group!')
+ if not verify_remote_as(peer_config, asn_config):
+ raise ConfigError(f'Neighbor "{peer}" remote-as must be set!')
- for afi in ['ipv4_unicast', 'ipv6_unicast']:
+ for afi in ['ipv4_unicast', 'ipv6_unicast', 'l2vpn_evpn']:
# Bail out early if address family is not configured
if 'address_family' not in peer_config or afi not in peer_config['address_family']:
continue
@@ -133,6 +138,15 @@ def verify(bgp):
if dict_search(f'policy.route_map.{route_map}', asn_config) == None:
raise ConfigError(f'route-map "{route_map}" used for "{tmp}" does not exist!')
+ if 'route_reflector_client' in afi_config:
+ if 'remote_as' in peer_config and asn != peer_config['remote_as']:
+ raise ConfigError('route-reflector-client only supported for iBGP peers')
+ else:
+ if 'peer_group' in peer_config:
+ peer_group_as = dict_search(f'peer_group.{peer_group}.remote_as', asn_config)
+ if peer_group_as != None and peer_group_as != asn:
+ raise ConfigError('route-reflector-client only supported for iBGP peers')
+
# Throw an error if a peer group is not configured for allow range
for prefix in dict_search('listen.range', asn_config) or []:
# we can not use dict_search() here as prefix contains dots ...
@@ -156,33 +170,15 @@ def generate(bgp):
asn = list(bgp.keys())[0]
bgp[asn]['asn'] = asn
- # 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 configuration prior to starting any commit actions
frr_cfg = frr.FRRConfig()
frr_cfg.load_configuration(frr_daemon)
- frr_cfg.modify_section(f'router bgp \S+', '')
+ frr_cfg.modify_section(f'^router bgp \d+$', '')
frr_cfg.add_before(r'(ip prefix-list .*|route-map .*|line vty)', bgp['new_frr_config'])
-
- # Debugging
- if DEBUG:
- from pprint import pprint
- print('')
- print('--------- DEBUGGING ----------')
- pprint(dir(frr_cfg))
- print('Existing config:\n')
- for line in frr_cfg.original_config:
- print(line)
- print(f'Replacement config:\n')
- print(f'{bgp["new_frr_config"]}')
- print(f'Modified config:\n')
- print(f'{frr_cfg}')
-
frr_cfg.commit_configuration(frr_daemon)
# If FRR config is blank, rerun the blank commit x times due to frr-reload
@@ -191,7 +187,6 @@ def apply(bgp):
for a in range(5):
frr_cfg.commit_configuration(frr_daemon)
-
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py
index 7c0ffbd27..6d9eb828b 100755
--- a/src/conf_mode/protocols_ospf.py
+++ b/src/conf_mode/protocols_ospf.py
@@ -21,7 +21,7 @@ from sys import exit
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.configverify import verify_route_maps
-from vyos.template import render
+from vyos.configverify import verify_interface_exists
from vyos.template import render_to_string
from vyos.util import call
from vyos.util import dict_search
@@ -31,17 +31,8 @@ from vyos import frr
from vyos import airbag
airbag.enable()
-config_file = r'/tmp/ospf.frr'
frr_daemon = 'ospfd'
-DEBUG = os.path.exists('/tmp/ospf.debug')
-if DEBUG:
- import logging
- lg = logging.getLogger("vyos.frr")
- lg.setLevel(logging.DEBUG)
- ch = logging.StreamHandler()
- lg.addHandler(ch)
-
def get_config(config=None):
if config:
conf = config
@@ -76,6 +67,7 @@ def get_config(config=None):
# clean them out and add them manually :(
del default_values['neighbor']
del default_values['area']['virtual_link']
+ del default_values['interface']
# merge in remaining default values
ospf = dict_merge(default_values, ospf)
@@ -94,6 +86,19 @@ def get_config(config=None):
ospf['area'][area]['virtual_link'][virtual_link] = dict_merge(
default_values, ospf['area'][area]['virtual_link'][virtual_link])
+ if 'interface' in ospf:
+ for interface in ospf['interface']:
+ # We need to reload the defaults on every pass b/c of
+ # hello-multiplier dependency on dead-interval
+ default_values = defaults(base + ['interface'])
+ # If hello-multiplier is set, we need to remove the default from
+ # dead-interval.
+ if 'hello_multiplier' in ospf['interface'][interface]:
+ del default_values['dead_interval']
+
+ ospf['interface'][interface] = dict_merge(default_values,
+ ospf['interface'][interface])
+
# We also need some additional information from the config, prefix-lists
# and route-maps for instance. They will be used in verify()
base = ['policy']
@@ -108,6 +113,16 @@ def verify(ospf):
return None
verify_route_maps(ospf)
+
+ if 'interface' in ospf:
+ for interface in ospf['interface']:
+ verify_interface_exists(interface)
+ # One can not use dead-interval and hello-multiplier at the same
+ # time. FRR will only activate the last option set via CLI.
+ if {'hello_multiplier', 'dead_interval'} <= set(ospf['interface'][interface]):
+ raise ConfigError(f'Can not use hello-multiplier and dead-interval ' \
+ f'concurrently for "{interface}"!')
+
return None
def generate(ospf):
@@ -115,33 +130,16 @@ def generate(ospf):
ospf['new_frr_config'] = ''
return None
- # render(config) not needed, its only for debug
- render(config_file, 'frr/ospf.frr.tmpl', ospf)
ospf['new_frr_config'] = render_to_string('frr/ospf.frr.tmpl', ospf)
-
return None
def apply(ospf):
# Save original configuration prior to starting any commit actions
frr_cfg = frr.FRRConfig()
frr_cfg.load_configuration(frr_daemon)
- frr_cfg.modify_section('router ospf', '')
+ frr_cfg.modify_section(r'^interface \S+', '')
+ frr_cfg.modify_section('^router ospf$', '')
frr_cfg.add_before(r'(ip prefix-list .*|route-map .*|line vty)', ospf['new_frr_config'])
-
- # Debugging
- if DEBUG:
- from pprint import pprint
- print('')
- print('--------- DEBUGGING ----------')
- pprint(dir(frr_cfg))
- print('Existing config:\n')
- for line in frr_cfg.original_config:
- print(line)
- print(f'Replacement config:\n')
- print(f'{ospf["new_frr_config"]}')
- print(f'Modified config:\n')
- print(f'{frr_cfg}')
-
frr_cfg.commit_configuration(frr_daemon)
# If FRR config is blank, rerun the blank commit x times due to frr-reload
diff --git a/src/conf_mode/protocols_ospfv3.py b/src/conf_mode/protocols_ospfv3.py
index 2fb600056..6f068b196 100755
--- a/src/conf_mode/protocols_ospfv3.py
+++ b/src/conf_mode/protocols_ospfv3.py
@@ -21,27 +21,17 @@ from sys import exit
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.configverify import verify_route_maps
-from vyos.template import render
from vyos.template import render_to_string
from vyos.util import call
-from vyos.util import dict_search
+from vyos.ifconfig import Interface
from vyos.xml import defaults
from vyos import ConfigError
from vyos import frr
from vyos import airbag
airbag.enable()
-config_file = r'/tmp/ospfv3.frr'
frr_daemon = 'ospf6d'
-DEBUG = os.path.exists('/tmp/ospfv3.debug')
-if DEBUG:
- import logging
- lg = logging.getLogger("vyos.frr")
- lg.setLevel(logging.DEBUG)
- ch = logging.StreamHandler()
- lg.addHandler(ch)
-
def get_config(config=None):
if config:
conf = config
@@ -68,6 +58,14 @@ def verify(ospfv3):
return None
verify_route_maps(ospfv3)
+
+ if 'interface' in ospfv3:
+ for ifname, if_config in ospfv3['interface'].items():
+ if 'ifmtu' in if_config:
+ mtu = Interface(ifname).get_mtu()
+ if int(if_config['ifmtu']) > int(mtu):
+ raise ConfigError(f'OSPFv3 ifmtu cannot go beyond physical MTU of "{mtu}"')
+
return None
def generate(ospfv3):
@@ -75,33 +73,16 @@ def generate(ospfv3):
ospfv3['new_frr_config'] = ''
return None
- # render(config) not needed, its only for debug
- render(config_file, 'frr/ospfv3.frr.tmpl', ospfv3)
ospfv3['new_frr_config'] = render_to_string('frr/ospfv3.frr.tmpl', ospfv3)
-
return None
def apply(ospfv3):
# Save original configuration prior to starting any commit actions
frr_cfg = frr.FRRConfig()
frr_cfg.load_configuration(frr_daemon)
- frr_cfg.modify_section('router ospf6', '')
+ frr_cfg.modify_section(r'^interface \S+', '')
+ frr_cfg.modify_section('^router ospf6$', '')
frr_cfg.add_before(r'(ip prefix-list .*|route-map .*|line vty)', ospfv3['new_frr_config'])
-
- # Debugging
- if DEBUG:
- from pprint import pprint
- print('')
- print('--------- DEBUGGING ----------')
- pprint(dir(frr_cfg))
- print('Existing config:\n')
- for line in frr_cfg.original_config:
- print(line)
- print(f'Replacement config:\n')
- print(f'{ospfv3["new_frr_config"]}')
- print(f'Modified config:\n')
- print(f'{frr_cfg}')
-
frr_cfg.commit_configuration(frr_daemon)
# If FRR config is blank, re-run the blank commit x times due to frr-reload
diff --git a/src/conf_mode/protocols_rip.py b/src/conf_mode/protocols_rip.py
index 8ddd705f2..6db5143c5 100755
--- a/src/conf_mode/protocols_rip.py
+++ b/src/conf_mode/protocols_rip.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020 VyOS maintainers and contributors
+# Copyright (C) 2021 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -18,15 +18,19 @@ import os
from sys import exit
-from vyos import ConfigError
from vyos.config import Config
+from vyos.configdict import dict_merge
+from vyos.configverify import verify_route_maps
from vyos.util import call
-from vyos.template import render
-
+from vyos.util import dict_search
+from vyos.xml import defaults
+from vyos.template import render_to_string
+from vyos import ConfigError
+from vyos import frr
from vyos import airbag
airbag.enable()
-config_file = r'/tmp/ripd.frr'
+frr_daemon = 'ripd'
def get_config(config=None):
if config:
@@ -34,277 +38,83 @@ def get_config(config=None):
else:
conf = Config()
base = ['protocols', 'rip']
- rip_conf = {
- 'rip_conf' : False,
- 'default_distance' : [],
- 'default_originate' : False,
- 'old_rip' : {
- 'default_metric' : [],
- 'distribute' : {},
- 'neighbors' : {},
- 'networks' : {},
- 'net_distance' : {},
- 'passive_iface' : {},
- 'redist' : {},
- 'route' : {},
- 'ifaces' : {},
- 'timer_garbage' : 120,
- 'timer_timeout' : 180,
- 'timer_update' : 30
- },
- 'rip' : {
- 'default_metric' : None,
- 'distribute' : {},
- 'neighbors' : {},
- 'networks' : {},
- 'net_distance' : {},
- 'passive_iface' : {},
- 'redist' : {},
- 'route' : {},
- 'ifaces' : {},
- 'timer_garbage' : 120,
- 'timer_timeout' : 180,
- 'timer_update' : 30
- }
- }
-
- if not (conf.exists(base) or conf.exists_effective(base)):
- return None
-
- if conf.exists(base):
- rip_conf['rip_conf'] = True
-
- conf.set_level(base)
-
- # Get default distance
- if conf.exists_effective('default-distance'):
- rip_conf['old_default_distance'] = conf.return_effective_value('default-distance')
-
- if conf.exists('default-distance'):
- rip_conf['default_distance'] = conf.return_value('default-distance')
-
- # Get default information originate (originate default route)
- if conf.exists_effective('default-information originate'):
- rip_conf['old_default_originate'] = True
-
- if conf.exists('default-information originate'):
- rip_conf['default_originate'] = True
-
- # Get default-metric
- if conf.exists_effective('default-metric'):
- rip_conf['old_rip']['default_metric'] = conf.return_effective_value('default-metric')
-
- if conf.exists('default-metric'):
- rip_conf['rip']['default_metric'] = conf.return_value('default-metric')
-
- # Get distribute list interface old_rip
- for dist_iface in conf.list_effective_nodes('distribute-list interface'):
- # Set level 'distribute-list interface ethX'
- conf.set_level(base + ['distribute-list', 'interface', dist_iface])
- rip_conf['rip']['distribute'].update({
- dist_iface : {
- 'iface_access_list_in': conf.return_effective_value('access-list in'.format(dist_iface)),
- 'iface_access_list_out': conf.return_effective_value('access-list out'.format(dist_iface)),
- 'iface_prefix_list_in': conf.return_effective_value('prefix-list in'.format(dist_iface)),
- 'iface_prefix_list_out': conf.return_effective_value('prefix-list out'.format(dist_iface))
- }
- })
-
- # Access-list in old_rip
- if conf.exists_effective('access-list in'.format(dist_iface)):
- rip_conf['old_rip']['iface_access_list_in'] = conf.return_effective_value('access-list in'.format(dist_iface))
- # Access-list out old_rip
- if conf.exists_effective('access-list out'.format(dist_iface)):
- rip_conf['old_rip']['iface_access_list_out'] = conf.return_effective_value('access-list out'.format(dist_iface))
- # Prefix-list in old_rip
- if conf.exists_effective('prefix-list in'.format(dist_iface)):
- rip_conf['old_rip']['iface_prefix_list_in'] = conf.return_effective_value('prefix-list in'.format(dist_iface))
- # Prefix-list out old_rip
- if conf.exists_effective('prefix-list out'.format(dist_iface)):
- rip_conf['old_rip']['iface_prefix_list_out'] = conf.return_effective_value('prefix-list out'.format(dist_iface))
-
- conf.set_level(base)
-
- # Get distribute list interface
- for dist_iface in conf.list_nodes('distribute-list interface'):
- # Set level 'distribute-list interface ethX'
- conf.set_level(base + ['distribute-list', 'interface', dist_iface])
- rip_conf['rip']['distribute'].update({
- dist_iface : {
- 'iface_access_list_in': conf.return_value('access-list in'.format(dist_iface)),
- 'iface_access_list_out': conf.return_value('access-list out'.format(dist_iface)),
- 'iface_prefix_list_in': conf.return_value('prefix-list in'.format(dist_iface)),
- 'iface_prefix_list_out': conf.return_value('prefix-list out'.format(dist_iface))
- }
- })
-
- # Access-list in
- if conf.exists('access-list in'.format(dist_iface)):
- rip_conf['rip']['iface_access_list_in'] = conf.return_value('access-list in'.format(dist_iface))
- # Access-list out
- if conf.exists('access-list out'.format(dist_iface)):
- rip_conf['rip']['iface_access_list_out'] = conf.return_value('access-list out'.format(dist_iface))
- # Prefix-list in
- if conf.exists('prefix-list in'.format(dist_iface)):
- rip_conf['rip']['iface_prefix_list_in'] = conf.return_value('prefix-list in'.format(dist_iface))
- # Prefix-list out
- if conf.exists('prefix-list out'.format(dist_iface)):
- rip_conf['rip']['iface_prefix_list_out'] = conf.return_value('prefix-list out'.format(dist_iface))
-
- conf.set_level(base + ['distribute-list'])
-
- # Get distribute list, access-list in
- if conf.exists_effective('access-list in'):
- rip_conf['old_rip']['dist_acl_in'] = conf.return_effective_value('access-list in')
-
- if conf.exists('access-list in'):
- rip_conf['rip']['dist_acl_in'] = conf.return_value('access-list in')
-
- # Get distribute list, access-list out
- if conf.exists_effective('access-list out'):
- rip_conf['old_rip']['dist_acl_out'] = conf.return_effective_value('access-list out')
-
- if conf.exists('access-list out'):
- rip_conf['rip']['dist_acl_out'] = conf.return_value('access-list out')
-
- # Get ditstribute list, prefix-list in
- if conf.exists_effective('prefix-list in'):
- rip_conf['old_rip']['dist_prfx_in'] = conf.return_effective_value('prefix-list in')
-
- if conf.exists('prefix-list in'):
- rip_conf['rip']['dist_prfx_in'] = conf.return_value('prefix-list in')
-
- # Get distribute list, prefix-list out
- if conf.exists_effective('prefix-list out'):
- rip_conf['old_rip']['dist_prfx_out'] = conf.return_effective_value('prefix-list out')
+ rip = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
- if conf.exists('prefix-list out'):
- rip_conf['rip']['dist_prfx_out'] = conf.return_value('prefix-list out')
+ # Bail out early if configuration tree does not exist
+ if not conf.exists(base):
+ return rip
- conf.set_level(base)
+ # 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)
+ # merge in remaining default values
+ rip = dict_merge(default_values, rip)
- # Get network Interfaces
- if conf.exists_effective('interface'):
- rip_conf['old_rip']['ifaces'] = conf.return_effective_values('interface')
+ # We also need some additional information from the config, prefix-lists
+ # and route-maps for instance. They will be used in verify()
+ base = ['policy']
+ tmp = conf.get_config_dict(base, key_mangling=('-', '_'))
+ # Merge policy dict into OSPF dict
+ rip = dict_merge(tmp, rip)
- if conf.exists('interface'):
- rip_conf['rip']['ifaces'] = conf.return_values('interface')
+ return rip
- # Get neighbors
- if conf.exists_effective('neighbor'):
- rip_conf['old_rip']['neighbors'] = conf.return_effective_values('neighbor')
-
- if conf.exists('neighbor'):
- rip_conf['rip']['neighbors'] = conf.return_values('neighbor')
-
- # Get networks
- if conf.exists_effective('network'):
- rip_conf['old_rip']['networks'] = conf.return_effective_values('network')
-
- if conf.exists('network'):
- rip_conf['rip']['networks'] = conf.return_values('network')
-
- # Get network-distance old_rip
- for net_dist in conf.list_effective_nodes('network-distance'):
- rip_conf['old_rip']['net_distance'].update({
- net_dist : {
- 'access_list' : conf.return_effective_value('network-distance {0} access-list'.format(net_dist)),
- 'distance' : conf.return_effective_value('network-distance {0} distance'.format(net_dist)),
- }
- })
-
- # Get network-distance
- for net_dist in conf.list_nodes('network-distance'):
- rip_conf['rip']['net_distance'].update({
- net_dist : {
- 'access_list' : conf.return_value('network-distance {0} access-list'.format(net_dist)),
- 'distance' : conf.return_value('network-distance {0} distance'.format(net_dist)),
- }
- })
-
- # Get passive-interface
- if conf.exists_effective('passive-interface'):
- rip_conf['old_rip']['passive_iface'] = conf.return_effective_values('passive-interface')
-
- if conf.exists('passive-interface'):
- rip_conf['rip']['passive_iface'] = conf.return_values('passive-interface')
-
- # Get redistribute for old_rip
- for protocol in conf.list_effective_nodes('redistribute'):
- rip_conf['old_rip']['redist'].update({
- protocol : {
- 'metric' : conf.return_effective_value('redistribute {0} metric'.format(protocol)),
- 'route_map' : conf.return_effective_value('redistribute {0} route-map'.format(protocol)),
- }
- })
-
- # Get redistribute
- for protocol in conf.list_nodes('redistribute'):
- rip_conf['rip']['redist'].update({
- protocol : {
- 'metric' : conf.return_value('redistribute {0} metric'.format(protocol)),
- 'route_map' : conf.return_value('redistribute {0} route-map'.format(protocol)),
- }
- })
-
- conf.set_level(base)
-
- # Get route
- if conf.exists_effective('route'):
- rip_conf['old_rip']['route'] = conf.return_effective_values('route')
-
- if conf.exists('route'):
- rip_conf['rip']['route'] = conf.return_values('route')
-
- # Get timers garbage
- if conf.exists_effective('timers garbage-collection'):
- rip_conf['old_rip']['timer_garbage'] = conf.return_effective_value('timers garbage-collection')
-
- if conf.exists('timers garbage-collection'):
- rip_conf['rip']['timer_garbage'] = conf.return_value('timers garbage-collection')
-
- # Get timers timeout
- if conf.exists_effective('timers timeout'):
- rip_conf['old_rip']['timer_timeout'] = conf.return_effective_value('timers timeout')
+def verify(rip):
+ if not rip:
+ return None
- if conf.exists('timers timeout'):
- rip_conf['rip']['timer_timeout'] = conf.return_value('timers timeout')
+ acl_in = dict_search('distribute_list.access_list.in', rip)
+ if acl_in and acl_in not in (dict_search('policy.access_list', rip) or []):
+ raise ConfigError(f'Inbound ACL "{acl_in}" does not exist!')
- # Get timers update
- if conf.exists_effective('timers update'):
- rip_conf['old_rip']['timer_update'] = conf.return_effective_value('timers update')
+ acl_out = dict_search('distribute_list.access_list.out', rip)
+ if acl_out and acl_out not in (dict_search('policy.access_list', rip) or []):
+ raise ConfigError(f'Outbound ACL "{acl_out}" does not exist!')
- if conf.exists('timers update'):
- rip_conf['rip']['timer_update'] = conf.return_value('timers update')
+ prefix_list_in = dict_search('distribute_list.prefix_list.in', rip)
+ if prefix_list_in and prefix_list_in.replace('-','_') not in (dict_search('policy.prefix_list', rip) or []):
+ raise ConfigError(f'Inbound prefix-list "{prefix_list_in}" does not exist!')
- return rip_conf
+ prefix_list_out = dict_search('distribute_list.prefix_list.out', rip)
+ if prefix_list_out and prefix_list_out.replace('-','_') not in (dict_search('policy.prefix_list', rip) or []):
+ raise ConfigError(f'Outbound prefix-list "{prefix_list_out}" does not exist!')
-def verify(rip):
- if rip is None:
- return None
+ if 'interface' in rip:
+ for interface, interface_options in rip['interface'].items():
+ if 'authentication' in interface_options:
+ if {'md5', 'plaintext_password'} <= set(interface_options['authentication']):
+ raise ConfigError('Can not use both md5 and plaintext-password at the same time!')
+ if 'split_horizon' in interface_options:
+ if {'disable', 'poison_reverse'} <= set(interface_options['split_horizon']):
+ raise ConfigError(f'You can not have "split-horizon poison-reverse" enabled ' \
+ f'with "split-horizon disable" for "{interface}"!')
- # Check for network. If network-distance acl is set and distance not set
- for net in rip['rip']['net_distance']:
- if not rip['rip']['net_distance'][net]['distance']:
- raise ConfigError(f"Must specify distance for network {net}")
+ verify_route_maps(rip)
def generate(rip):
- if rip is None:
+ if not rip:
+ rip['new_frr_config'] = ''
return None
- render(config_file, 'frr/rip.frr.tmpl', rip)
+ rip['new_frr_config'] = render_to_string('frr/rip.frr.tmpl', rip)
+
return None
def apply(rip):
- if rip is None:
- return None
-
- if os.path.exists(config_file):
- call(f'vtysh -d ripd -f {config_file}')
- os.remove(config_file)
- else:
- print("File {0} not found".format(config_file))
-
+ # Save original configuration prior to starting any commit actions
+ frr_cfg = frr.FRRConfig()
+ frr_cfg.load_configuration(frr_daemon)
+ frr_cfg.modify_section(r'key chain \S+', '')
+ frr_cfg.modify_section(r'interface \S+', '')
+ frr_cfg.modify_section('router rip', '')
+ frr_cfg.add_before(r'(ip prefix-list .*|route-map .*|line vty)', rip['new_frr_config'])
+ frr_cfg.commit_configuration(frr_daemon)
+
+ # If FRR config is blank, rerun the blank commit x times due to frr-reload
+ # behavior/bug not properly clearing out on one commit.
+ if rip['new_frr_config'] == '':
+ for a in range(5):
+ frr_cfg.commit_configuration(frr_daemon)
return None
@@ -317,4 +127,3 @@ if __name__ == '__main__':
except ConfigError as e:
print(e)
exit(1)
-
diff --git a/src/conf_mode/protocols_ripng.py b/src/conf_mode/protocols_ripng.py
new file mode 100755
index 000000000..8cc5de64a
--- /dev/null
+++ b/src/conf_mode/protocols_ripng.py
@@ -0,0 +1,133 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# 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.configverify import verify_route_maps
+from vyos.util import call
+from vyos.util import dict_search
+from vyos.xml import defaults
+from vyos.template import render_to_string
+from vyos import ConfigError
+from vyos import frr
+from vyos import airbag
+airbag.enable()
+
+frr_daemon = 'ripngd'
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+ base = ['protocols', 'ripng']
+ ripng = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+
+ # Bail out early if configuration tree does not exist
+ if not conf.exists(base):
+ return ripng
+
+ # 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)
+ # merge in remaining default values
+ ripng = dict_merge(default_values, ripng)
+
+ # We also need some additional information from the config, prefix-lists
+ # and route-maps for instance. They will be used in verify()
+ base = ['policy']
+ tmp = conf.get_config_dict(base, key_mangling=('-', '_'))
+ # Merge policy dict into OSPF dict
+ ripng = dict_merge(tmp, ripng)
+
+ import pprint
+ pprint.pprint(ripng)
+ return ripng
+
+def verify(ripng):
+ if not ripng:
+ return None
+
+ acl_in = dict_search('distribute_list.access_list.in', ripng)
+ if acl_in and acl_in not in (dict_search('policy.access_list6', ripng) or []):
+ raise ConfigError(f'Inbound access-list6 "{acl_in}" does not exist!')
+
+ acl_out = dict_search('distribute_list.access_list.out', ripng)
+ if acl_out and acl_out not in (dict_search('policy.access_list6', ripng) or []):
+ raise ConfigError(f'Outbound access-list6 "{acl_out}" does not exist!')
+
+ prefix_list_in = dict_search('distribute_list.prefix_list.in', ripng)
+ if prefix_list_in and prefix_list_in.replace('-','_') not in (dict_search('policy.prefix_list6', ripng) or []):
+ raise ConfigError(f'Inbound prefix-list6 "{prefix_list_in}" does not exist!')
+
+ prefix_list_out = dict_search('distribute_list.prefix_list.out', ripng)
+ if prefix_list_out and prefix_list_out.replace('-','_') not in (dict_search('policy.prefix_list6', ripng) or []):
+ raise ConfigError(f'Outbound prefix-list6 "{prefix_list_out}" does not exist!')
+
+ if 'interface' in ripng:
+ for interface, interface_options in ripng['interface'].items():
+ if 'authentication' in interface_options:
+ if {'md5', 'plaintext_password'} <= set(interface_options['authentication']):
+ raise ConfigError('Can not use both md5 and plaintext-password at the same time!')
+ if 'split_horizon' in interface_options:
+ if {'disable', 'poison_reverse'} <= set(interface_options['split_horizon']):
+ raise ConfigError(f'You can not have "split-horizon poison-reverse" enabled ' \
+ f'with "split-horizon disable" for "{interface}"!')
+
+ verify_route_maps(ripng)
+
+def generate(ripng):
+ if not ripng:
+ ripng['new_frr_config'] = ''
+ return None
+
+ ripng['new_frr_config'] = render_to_string('frr/ripng.frr.tmpl', ripng)
+ import pprint
+ pprint.pprint(ripng['new_frr_config'])
+
+ return None
+
+def apply(ripng):
+ # Save original configuration prior to starting any commit actions
+ frr_cfg = frr.FRRConfig()
+ frr_cfg.load_configuration(frr_daemon)
+ frr_cfg.modify_section(r'key chain \S+', '')
+ frr_cfg.modify_section(r'interface \S+', '')
+ frr_cfg.modify_section('router ripng', '')
+ frr_cfg.add_before(r'(ip prefix-list .*|route-map .*|line vty)', ripng['new_frr_config'])
+ frr_cfg.commit_configuration(frr_daemon)
+
+ # If FRR config is blank, rerun the blank commit x times due to frr-reload
+ # behavior/bug not properly clearing out on one commit.
+ if ripng['new_frr_config'] == '':
+ for a in range(5):
+ frr_cfg.commit_configuration(frr_daemon)
+
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/conf_mode/protocols_rpki.py b/src/conf_mode/protocols_rpki.py
new file mode 100755
index 000000000..75b870b05
--- /dev/null
+++ b/src/conf_mode/protocols_rpki.py
@@ -0,0 +1,110 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# 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.template import render_to_string
+from vyos.util import call
+from vyos.util import dict_search
+from vyos.xml import defaults
+from vyos import ConfigError
+from vyos import frr
+from vyos import airbag
+airbag.enable()
+
+frr_daemon = 'bgpd'
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+ base = ['protocols', 'rpki']
+
+ rpki = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+ if not conf.exists(base):
+ return rpki
+
+ # 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)
+ rpki = dict_merge(default_values, rpki)
+
+ return rpki
+
+def verify(rpki):
+ if not rpki:
+ return None
+
+ if 'cache' in rpki:
+ preferences = []
+ for peer, peer_config in rpki['cache'].items():
+ for mandatory in ['port', 'preference']:
+ if mandatory not in peer_config:
+ raise ConfigError(f'RPKI cache "{peer}" {mandatory} must be defined!')
+
+ if 'preference' in peer_config:
+ preference = peer_config['preference']
+ if preference in preferences:
+ raise ConfigError(f'RPKI cache with preference {preference} already configured!')
+ preferences.append(preference)
+
+ if 'ssh' in peer_config:
+ files = ['private_key_file', 'public_key_file', 'known_hosts_file']
+ for file in files:
+ if file not in peer_config['ssh']:
+ raise ConfigError('RPKI+SSH requires username, public/private ' \
+ 'keys and known-hosts file to be defined!')
+
+ filename = peer_config['ssh'][file]
+ if not os.path.exists(filename):
+ raise ConfigError(f'RPKI SSH {file.replace("-","-")} "{filename}" does not exist!')
+
+ return None
+
+def generate(rpki):
+ rpki['new_frr_config'] = render_to_string('frr/rpki.frr.tmpl', rpki)
+ return None
+
+def apply(rpki):
+ # Save original configuration prior to starting any commit actions
+ frr_cfg = frr.FRRConfig()
+ frr_cfg.load_configuration(frr_daemon)
+ frr_cfg.modify_section('rpki', '')
+ frr_cfg.add_before(r'(ip prefix-list .*|route-map .*|line vty)', rpki['new_frr_config'])
+ frr_cfg.commit_configuration(frr_daemon)
+
+ # If FRR config is blank, re-run the blank commit x times due to frr-reload
+ # behavior/bug not properly clearing out on one commit.
+ if rpki['new_frr_config'] == '':
+ for a in range(5):
+ frr_cfg.commit_configuration(frr_daemon)
+
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/conf_mode/protocols_static.py b/src/conf_mode/protocols_static.py
new file mode 100755
index 000000000..5d101b33e
--- /dev/null
+++ b/src/conf_mode/protocols_static.py
@@ -0,0 +1,74 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# 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.template import render_to_string
+from vyos.util import call
+from vyos.configverify import verify_route_maps
+from vyos import ConfigError
+from vyos import frr
+from vyos import airbag
+airbag.enable()
+
+frr_daemon = 'staticd'
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+ base = ['protocols', 'static']
+ static = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+ return static
+
+def verify(static):
+ verify_route_maps(static)
+ return None
+
+def generate(static):
+ static['new_frr_config'] = render_to_string('frr/static.frr.tmpl', static)
+ return None
+
+def apply(static):
+ # Save original configuration prior to starting any commit actions
+ frr_cfg = frr.FRRConfig()
+ frr_cfg.load_configuration(frr_daemon)
+ frr_cfg.modify_section(r'^ip route .*', '')
+ frr_cfg.modify_section(r'^ipv6 route .*', '')
+ frr_cfg.add_before(r'(interface .*|line vty)', static['new_frr_config'])
+ frr_cfg.commit_configuration(frr_daemon)
+
+ # If FRR config is blank, rerun the blank commit x times due to frr-reload
+ # behavior/bug not properly clearing out on one commit.
+ if static['new_frr_config'] == '':
+ for a in range(5):
+ frr_cfg.commit_configuration(frr_daemon)
+
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/conf_mode/protocols_vrf.py b/src/conf_mode/protocols_vrf.py
new file mode 100755
index 000000000..227e7d5e1
--- /dev/null
+++ b/src/conf_mode/protocols_vrf.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# 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.template import render_to_string
+from vyos.util import call
+from vyos import ConfigError
+from vyos import frr
+from vyos import airbag
+airbag.enable()
+
+frr_daemon = 'staticd'
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+ base = ['protocols', 'vrf']
+ vrf = conf.get_config_dict(base, key_mangling=('-', '_'))
+ return vrf
+
+def verify(vrf):
+
+ return None
+
+def generate(vrf):
+ vrf['new_frr_config'] = render_to_string('frr/vrf.frr.tmpl', vrf)
+ return None
+
+def apply(vrf):
+ # Save original configuration prior to starting any commit actions
+ frr_cfg = frr.FRRConfig()
+ frr_cfg.load_configuration(frr_daemon)
+ frr_cfg.modify_section(r'vrf \S+', '')
+ frr_cfg.add_before(r'(ip prefix-list .*|route-map .*|line vty)', vrf['new_frr_config'])
+ frr_cfg.commit_configuration(frr_daemon)
+
+ # If FRR config is blank, rerun the blank commit x times due to frr-reload
+ # behavior/bug not properly clearing out on one commit.
+ if vrf['new_frr_config'] == '':
+ for a in range(5):
+ frr_cfg.commit_configuration(frr_daemon)
+
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/conf_mode/service_console-server.py b/src/conf_mode/service_console-server.py
index 0e5fc75b0..6e94a19ae 100755
--- a/src/conf_mode/service_console-server.py
+++ b/src/conf_mode/service_console-server.py
@@ -25,7 +25,8 @@ from vyos.util import call
from vyos.xml import defaults
from vyos import ConfigError
-config_file = r'/run/conserver/conserver.cf'
+config_file = '/run/conserver/conserver.cf'
+dropbear_systemd_file = '/etc/systemd/system/dropbear@{port}.service.d/override.conf'
def get_config(config=None):
if config:
@@ -75,9 +76,22 @@ def generate(proxy):
return None
render(config_file, 'conserver/conserver.conf.tmpl', proxy)
+ if 'device' in proxy:
+ for device in proxy['device']:
+ if 'ssh' not in proxy['device'][device]:
+ continue
+
+ tmp = {
+ 'device' : device,
+ 'port' : proxy['device'][device]['ssh']['port'],
+ }
+ render(dropbear_systemd_file.format(**tmp),
+ 'conserver/dropbear@.service.tmpl', tmp)
+
return None
def apply(proxy):
+ call('systemctl daemon-reload')
call('systemctl stop dropbear@*.service conserver-server.service')
if not proxy:
@@ -89,9 +103,10 @@ def apply(proxy):
if 'device' in proxy:
for device in proxy['device']:
- if 'ssh' in proxy['device'][device]:
- port = proxy['device'][device]['ssh']['port']
- call(f'systemctl restart dropbear@{device}.service')
+ if 'ssh' not in proxy['device'][device]:
+ continue
+ port = proxy['device'][device]['ssh']['port']
+ call(f'systemctl restart dropbear@{port}.service')
return None
diff --git a/src/conf_mode/service_webproxy.py b/src/conf_mode/service_webproxy.py
index 8dfae348a..cbbd2e0bc 100755
--- a/src/conf_mode/service_webproxy.py
+++ b/src/conf_mode/service_webproxy.py
@@ -123,9 +123,6 @@ def verify(proxy):
ldap_auth = dict_search('authentication.method', proxy) == 'ldap'
for address, config in proxy['listen_address'].items():
- if not is_addr_assigned(address):
- raise ConfigError(
- f'listen-address "{address}" not assigned on any interface!')
if ldap_auth and 'disable_transparent' not in config:
raise ConfigError('Authentication can not be configured when ' \
'proxy is in transparent mode')
diff --git a/src/conf_mode/vrrp.py b/src/conf_mode/vrrp.py
index 4510dd3e7..680a80859 100755
--- a/src/conf_mode/vrrp.py
+++ b/src/conf_mode/vrrp.py
@@ -75,6 +75,7 @@ def get_config(config=None):
group["backup_script"] = config.return_value("transition-script backup")
group["fault_script"] = config.return_value("transition-script fault")
group["stop_script"] = config.return_value("transition-script stop")
+ group["script_mode_force"] = config.exists("transition-script mode-force")
if config.exists("no-preempt"):
group["preempt"] = False
@@ -183,6 +184,11 @@ def verify(data):
if isinstance(pa, IPv4Address):
raise ConfigError("VRRP group {0} uses IPv6 but its peer-address is IPv4".format(group["name"]))
+ # Warn the user about the deprecated mode-force option
+ if group['script_mode_force']:
+ print("""Warning: "transition-script mode-force" VRRP option is deprecated and will be removed in VyOS 1.4.""")
+ print("""It's no longer necessary, so you can safely remove it from your config now.""")
+
# Disallow same VRID on multiple interfaces
_groups = sorted(vrrp_groups, key=(lambda x: x["interface"]))
count = len(_groups) - 1