summaryrefslogtreecommitdiff
path: root/src/conf_mode
diff options
context:
space:
mode:
Diffstat (limited to 'src/conf_mode')
-rwxr-xr-xsrc/conf_mode/interfaces-tunnel.py4
-rwxr-xr-xsrc/conf_mode/nat66.py9
-rwxr-xr-xsrc/conf_mode/policy-lists.py117
-rwxr-xr-xsrc/conf_mode/protocols_bgp.py57
-rwxr-xr-xsrc/conf_mode/protocols_isis.py246
-rwxr-xr-xsrc/conf_mode/protocols_ospf.py66
-rwxr-xr-xsrc/conf_mode/protocols_static.py44
-rwxr-xr-xsrc/conf_mode/protocols_vrf.py72
-rwxr-xr-xsrc/conf_mode/service_console-server.py32
-rwxr-xr-xsrc/conf_mode/vrf.py38
10 files changed, 456 insertions, 229 deletions
diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py
index b63312750..cab94a5b0 100755
--- a/src/conf_mode/interfaces-tunnel.py
+++ b/src/conf_mode/interfaces-tunnel.py
@@ -34,7 +34,7 @@ from vyos.ifconfig import Interface
from vyos.ifconfig import TunnelIf
from vyos.template import is_ipv4
from vyos.template import is_ipv6
-from vyos.util import get_json_iface_options
+from vyos.util import get_interface_config
from vyos.util import dict_search
from vyos import ConfigError
from vyos import airbag
@@ -103,7 +103,7 @@ def apply(tunnel):
# There is no other solution to destroy and recreate the tunnel.
encap = ''
remote = ''
- tmp = get_json_iface_options(interface)
+ tmp = get_interface_config(interface)
if tmp:
encap = dict_search('linkinfo.info_kind', tmp)
remote = dict_search('linkinfo.info_data.remote', tmp)
diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py
index ce1db316c..e2bd6417d 100755
--- a/src/conf_mode/nat66.py
+++ b/src/conf_mode/nat66.py
@@ -28,7 +28,6 @@ from vyos.util import cmd
from vyos.util import check_kmod
from vyos.util import dict_search
from vyos.template import is_ipv6
-from vyos.template import is_ip_network
from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
@@ -80,8 +79,10 @@ def get_config(config=None):
if not conf.exists(base):
nat['helper_functions'] = 'remove'
+ nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYATTA_CT_HELPER')
nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK')
- nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT','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'] = ''
return nat
@@ -91,8 +92,10 @@ def get_config(config=None):
nat['helper_functions'] = 'add'
# Retrieve current table handler positions
+ nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYATTA_CT_IGNORE')
nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'VYATTA_CT_PREROUTING_HOOK')
- nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT','VYATTA_CT_OUTPUT_HOOK')
+ 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')
else:
nat['helper_functions'] = 'has'
diff --git a/src/conf_mode/policy-lists.py b/src/conf_mode/policy-lists.py
new file mode 100755
index 000000000..94a020e7b
--- /dev/null
+++ b/src/conf_mode/policy-lists.py
@@ -0,0 +1,117 @@
+#!/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
+from vyos.template import render_to_string
+from vyos.util import call
+from vyos.util import dict_search
+from vyos import ConfigError
+from vyos import frr
+from vyos import airbag
+from pprint import pprint
+airbag.enable()
+
+config_file = r'/tmp/policy.frr'
+frr_daemon = 'zebra'
+
+DEBUG = os.path.exists('/tmp/policy.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
+ else:
+ conf = Config()
+ base = ['npolicy']
+ policy = conf.get_config_dict(base, key_mangling=('-', '_'))
+
+ # Bail out early if configuration tree does not exist
+ if not conf.exists(base):
+ return policy
+
+ pprint(policy)
+ exit(1)
+ return policy
+
+def verify(policy):
+ if not policy:
+ return None
+
+ return None
+
+def generate(policy):
+ if not policy:
+ policy['new_frr_config'] = ''
+ return None
+
+ # render(config) not needed, its only for debug
+ # render(config_file, 'frr/policy.frr.tmpl', policy)
+ # policy['new_frr_config'] = render_to_string('frr/policy.frr.tmpl')
+
+ return None
+
+def apply(policy):
+ # Save original configuration prior to starting any commit actions
+ # frr_cfg = frr.FRRConfig()
+ # frr_cfg.load_configuration(frr_daemon)
+ # frr_cfg.modify_section(f'ip', '')
+ # frr_cfg.add_before(r'(line vty)', policy['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'{policy["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
+ # behavior/bug not properly clearing out on one commit.
+ # if policy['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_bgp.py b/src/conf_mode/protocols_bgp.py
index 7dede74a1..d5cc169c2 100755
--- a/src/conf_mode/protocols_bgp.py
+++ b/src/conf_mode/protocols_bgp.py
@@ -17,6 +17,7 @@
import os
from sys import exit
+from sys import argv
from vyos.config import Config
from vyos.configdict import dict_merge
@@ -37,11 +38,24 @@ def get_config(config=None):
conf = config
else:
conf = Config()
- base = ['protocols', 'bgp']
+
+ vrf = None
+ if len(argv) > 1:
+ vrf = argv[1]
+
+ base_path = ['protocols', 'bgp']
+
+ # eqivalent of the C foo ? 'a' : 'b' statement
+ base = vrf and ['vrf', 'name', vrf, 'protocols', 'bgp'] or base_path
bgp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
- # Bail out early if configuration tree does not exist
+ # Assign the name of our VRF context. This MUST be done before the return
+ # statement below, else on deletion we will delete the default instance
+ # instead of the VRF instance.
+ if vrf: bgp.update({'vrf' : vrf})
+
if not conf.exists(base):
+ bgp.update({'deleted' : ''})
return bgp
# We also need some additional information from the config,
@@ -80,11 +94,20 @@ def verify(bgp):
if not bgp:
return None
- # Check if declared more than one ASN
- if len(bgp) > 1:
- raise ConfigError('Only one BGP AS number can be defined!')
+ # FRR bgpd only supports one Autonomous System Number, verify this!
+ asn = 0
+ for key in bgp:
+ if key.isnumeric():
+ asn +=1
+ if asn > 1:
+ raise ConfigError('Only one BGP AS number can be defined!')
for asn, asn_config in bgp.items():
+ # Workaround for https://phabricator.vyos.net/T1711
+ # We also have a vrf, and deleted key now - so we can only veriy "numbers"
+ if not asn.isnumeric():
+ continue
+
# Common verification for both peer-group and neighbor statements
for neighbor in ['neighbor', 'peer_group']:
# bail out early if there is no neighbor or peer-group statement
@@ -166,16 +189,18 @@ def verify(bgp):
# we can not use dict_search() here as prefix contains dots ...
if 'peer_group' not in asn_config['listen']['range'][prefix]:
raise ConfigError(f'Listen range for prefix "{prefix}" has no peer group configured.')
- else:
- peer_group = asn_config['listen']['range'][prefix]['peer_group']
- # the peer group must also exist
- if not dict_search(f'peer_group.{peer_group}', asn_config):
- raise ConfigError(f'Peer-group "{peer_group}" for listen range "{prefix}" does not exist!')
+
+ peer_group = asn_config['listen']['range'][prefix]['peer_group']
+ if 'peer_group' not in asn_config or peer_group not in asn_config['peer_group']:
+ raise ConfigError(f'Peer-group "{peer_group}" for listen range "{prefix}" does not exist!')
+
+ if not verify_remote_as(asn_config['listen']['range'][prefix], asn_config):
+ raise ConfigError(f'Peer-group "{peer_group}" requires remote-as to be set!')
return None
def generate(bgp):
- if not bgp:
+ if not bgp or 'deleted' in bgp:
bgp['new_frr_config'] = ''
return None
@@ -183,6 +208,8 @@ def generate(bgp):
# of the config dict
asn = list(bgp.keys())[0]
bgp[asn]['asn'] = asn
+ if 'vrf' in bgp:
+ bgp[asn]['vrf'] = bgp['vrf']
bgp['new_frr_config'] = render_to_string('frr/bgp.frr.tmpl', bgp[asn])
return None
@@ -191,7 +218,13 @@ 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 \d+$', '')
+
+ if 'vrf' in bgp:
+ vrf = bgp['vrf']
+ frr_cfg.modify_section(f'^router bgp \d+ vrf {vrf}$', '')
+ else:
+ frr_cfg.modify_section('^router bgp \d+$', '')
+
frr_cfg.add_before(r'(ip prefix-list .*|route-map .*|line vty)', bgp['new_frr_config'])
frr_cfg.commit_configuration(frr_daemon)
diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py
index b7afad473..bcd9960ed 100755
--- a/src/conf_mode/protocols_isis.py
+++ b/src/conf_mode/protocols_isis.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020 VyOS maintainers and contributors
+# Copyright (C) 2020-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,143 +17,197 @@
import os
from sys import exit
+from sys import argv
from vyos.config import Config
+from vyos.configdict import dict_merge
from vyos.configdict import node_changed
-from vyos import ConfigError
+from vyos.configverify import verify_interface_exists
from vyos.util import call
from vyos.util import dict_search
-from vyos.template import render
+from vyos.util import get_interface_config
from vyos.template import render_to_string
+from vyos import ConfigError
from vyos import frr
from vyos import airbag
airbag.enable()
+frr_daemon = 'isisd'
+
def get_config(config=None):
if config:
conf = config
else:
conf = Config()
- base = ['protocols', 'isis']
- isis = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+ vrf = None
+ if len(argv) > 1:
+ vrf = argv[1]
+
+ base_path = ['protocols', 'isis']
+
+ # eqivalent of the C foo ? 'a' : 'b' statement
+ base = vrf and ['vrf', 'name', vrf, 'protocols', 'isis'] or base_path
+ isis = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True)
+
+ # Assign the name of our VRF context. This MUST be done before the return
+ # statement below, else on deletion we will delete the default instance
+ # instead of the VRF instance.
+ if vrf: isis['vrf'] = vrf
+
+ # As we no re-use this Python handler for both VRF and non VRF instances for
+ # IS-IS we need to find out if any interfaces changed so properly adjust
+ # the FRR configuration and not by acctident change interfaces from a
+ # different VRF.
+ interfaces_removed = node_changed(conf, base + ['interface'])
+ if interfaces_removed:
+ isis['interface_removed'] = list(interfaces_removed)
+
+ # Bail out early if configuration tree does not exist
+ if not conf.exists(base):
+ isis.update({'deleted' : ''})
+ return isis
+
+ # 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
+ isis = dict_merge(tmp, isis)
return isis
def verify(isis):
# bail out early - looks like removal from running config
- if not isis:
+ if not isis or 'deleted' in isis:
return None
- for process, isis_config in isis.items():
- # If more then one isis process is defined (Frr only supports one)
- # http://docs.frrouting.org/en/latest/isisd.html#isis-router
- if len(isis) > 1:
- raise ConfigError('Only one isis process can be defined')
-
- # If network entity title (net) not defined
- if 'net' not in isis_config:
- raise ConfigError('ISIS net format iso is mandatory!')
-
- # If interface not set
- if 'interface' not in isis_config:
- raise ConfigError('ISIS interface is mandatory!')
-
- # If md5 and plaintext-password set at the same time
- if 'area_password' in isis_config:
- if {'md5', 'plaintext_password'} <= set(isis_config['encryption']):
- raise ConfigError('Can not use both md5 and plaintext-password for ISIS area-password!')
-
- # If one param from delay set, but not set others
- if 'spf_delay_ietf' in isis_config:
- required_timers = ['holddown', 'init_delay', 'long_delay', 'short_delay', 'time_to_learn']
- exist_timers = []
- for elm_timer in required_timers:
- if elm_timer in isis_config['spf_delay_ietf']:
- exist_timers.append(elm_timer)
-
- exist_timers = set(required_timers).difference(set(exist_timers))
- if len(exist_timers) > 0:
- raise ConfigError('All types of delay must be specified: ' + ', '.join(exist_timers).replace('_', '-'))
-
- # If Redistribute set, but level don't set
- if 'redistribute' in isis_config:
- proc_level = isis_config.get('level','').replace('-','_')
- for proto, proto_config in isis_config.get('redistribute', {}).get('ipv4', {}).items():
+ if 'net' not in isis:
+ raise ConfigError('Network entity is mandatory!')
+
+ # last byte in IS-IS area address must be 0
+ tmp = isis['net'].split('.')
+ if int(tmp[-1]) != 0:
+ raise ConfigError('Last byte of IS-IS network entity title must always be 0!')
+
+ # If interface not set
+ if 'interface' not in isis:
+ raise ConfigError('Interface used for routing updates is mandatory!')
+
+ for interface in isis['interface']:
+ verify_interface_exists(interface)
+ if 'vrf' in isis:
+ # If interface specific options are set, we must ensure that the
+ # interface is bound to our requesting VRF. Due to the VyOS
+ # priorities the interface is bound to the VRF after creation of
+ # the VRF itself, and before any routing protocol is configured.
+ vrf = isis['vrf']
+ tmp = get_interface_config(interface)
+ if 'master' not in tmp or tmp['master'] != vrf:
+ raise ConfigError(f'Interface {interface} is not a member of VRF {vrf}!')
+
+ # If md5 and plaintext-password set at the same time
+ if 'area_password' in isis:
+ if {'md5', 'plaintext_password'} <= set(isis['encryption']):
+ raise ConfigError('Can not use both md5 and plaintext-password for ISIS area-password!')
+
+ # If one param from delay set, but not set others
+ if 'spf_delay_ietf' in isis:
+ required_timers = ['holddown', 'init_delay', 'long_delay', 'short_delay', 'time_to_learn']
+ exist_timers = []
+ for elm_timer in required_timers:
+ if elm_timer in isis['spf_delay_ietf']:
+ exist_timers.append(elm_timer)
+
+ exist_timers = set(required_timers).difference(set(exist_timers))
+ if len(exist_timers) > 0:
+ raise ConfigError('All types of delay must be specified: ' + ', '.join(exist_timers).replace('_', '-'))
+
+ # If Redistribute set, but level don't set
+ if 'redistribute' in isis:
+ proc_level = isis.get('level','').replace('-','_')
+ for afi in ['ipv4']:
+ if afi not in isis['redistribute']:
+ continue
+
+ for proto, proto_config in isis['redistribute'][afi].items():
if 'level_1' not in proto_config and 'level_2' not in proto_config:
- raise ConfigError('Redistribute level-1 or level-2 should be specified in \"protocols isis {} redistribute ipv4 {}\"'.format(process, proto))
- for redistribute_level in proto_config.keys():
- if proc_level and proc_level != 'level_1_2' and proc_level != redistribute_level:
- raise ConfigError('\"protocols isis {0} redistribute ipv4 {2} {3}\" cannot be used with \"protocols isis {0} level {1}\"'.format(process, proc_level, proto, redistribute_level))
-
- # Segment routing checks
- if dict_search('segment_routing', isis_config):
- if dict_search('segment_routing.global_block', isis_config):
- high_label_value = dict_search('segment_routing.global_block.high_label_value', isis_config)
- low_label_value = dict_search('segment_routing.global_block.low_label_value', isis_config)
- # If segment routing global block high value is blank, throw error
- if low_label_value and not high_label_value:
- raise ConfigError('Segment routing global block high value must not be left blank')
- # If segment routing global block low value is blank, throw error
- if high_label_value and not low_label_value:
- raise ConfigError('Segment routing global block low value must not be left blank')
- # If segment routing global block low value is higher than the high value, throw error
- if int(low_label_value) > int(high_label_value):
- raise ConfigError('Segment routing global block low value must be lower than high value')
-
- if dict_search('segment_routing.local_block', isis_config):
- high_label_value = dict_search('segment_routing.local_block.high_label_value', isis_config)
- low_label_value = dict_search('segment_routing.local_block.low_label_value', isis_config)
- # If segment routing local block high value is blank, throw error
- if low_label_value and not high_label_value:
- raise ConfigError('Segment routing local block high value must not be left blank')
- # If segment routing local block low value is blank, throw error
- if high_label_value and not low_label_value:
- raise ConfigError('Segment routing local block low value must not be left blank')
- # If segment routing local block low value is higher than the high value, throw error
- if int(low_label_value) > int(high_label_value):
- raise ConfigError('Segment routing local block low value must be lower than high value')
+ raise ConfigError(f'Redistribute level-1 or level-2 should be specified in ' \
+ f'"protocols isis {process} redistribute {afi} {proto}"!')
+
+ for redistr_level, redistr_config in proto_config.items():
+ if proc_level and proc_level != 'level_1_2' and proc_level != redistr_level:
+ raise ConfigError(f'"protocols isis {process} redistribute {afi} {proto} {redistr_level}" ' \
+ f'can not be used with \"protocols isis {process} level {proc_level}\"')
+
+ if 'route_map' in redistr_config:
+ name = redistr_config['route_map']
+ tmp = name.replace('-', '_')
+ if dict_search(f'policy.route_map.{tmp}', isis) == None:
+ raise ConfigError(f'Route-map {name} does not exist!')
+
+ # Segment routing checks
+ if dict_search('segment_routing.global_block', isis):
+ high_label_value = dict_search('segment_routing.global_block.high_label_value', isis)
+ low_label_value = dict_search('segment_routing.global_block.low_label_value', isis)
+
+ # If segment routing global block high value is blank, throw error
+ if (low_label_value and not high_label_value) or (high_label_value and not low_label_value):
+ raise ConfigError('Segment routing global block requires both low and high value!')
+
+ # If segment routing global block low value is higher than the high value, throw error
+ if int(low_label_value) > int(high_label_value):
+ raise ConfigError('Segment routing global block low value must be lower than high value')
+
+ if dict_search('segment_routing.local_block', isis):
+ high_label_value = dict_search('segment_routing.local_block.high_label_value', isis)
+ low_label_value = dict_search('segment_routing.local_block.low_label_value', isis)
+
+ # If segment routing local block high value is blank, throw error
+ if (low_label_value and not high_label_value) or (high_label_value and not low_label_value):
+ raise ConfigError('Segment routing local block requires both high and low value!')
+
+ # If segment routing local block low value is higher than the high value, throw error
+ if int(low_label_value) > int(high_label_value):
+ raise ConfigError('Segment routing local block low value must be lower than high value')
return None
def generate(isis):
- if not isis:
+ if not isis or 'deleted' in isis:
isis['new_frr_config'] = ''
return None
- # only one ISIS process is supported, so we can directly send the first key
- # of the config dict
- process = list(isis.keys())[0]
- isis[process]['process'] = process
-
- isis['new_frr_config'] = render_to_string('frr/isis.frr.tmpl',
- isis[process])
-
+ isis['new_frr_config'] = render_to_string('frr/isis.frr.tmpl', isis)
return None
def apply(isis):
# Save original configuration prior to starting any commit actions
frr_cfg = frr.FRRConfig()
- frr_cfg.load_configuration(daemon='isisd')
- frr_cfg.modify_section(r'interface \S+', '')
- frr_cfg.modify_section(f'router isis \S+', '')
+ frr_cfg.load_configuration(frr_daemon)
+
+ # Generate empty helper string which can be ammended to FRR commands,
+ # it will be either empty (default VRF) or contain the "vrf <name" statement
+ vrf = ''
+ if 'vrf' in isis:
+ vrf = 'vrf ' + isis['vrf']
+
+ frr_cfg.modify_section(f'^router isis VyOS {vrf}', '')
+ for key in ['interface', 'interface_removed']:
+ if key not in isis:
+ continue
+ for interface in isis[key]:
+ frr_cfg.modify_section(f'^interface {interface}{vrf}$', '')
+
frr_cfg.add_before(r'(ip prefix-list .*|route-map .*|line vty)', isis['new_frr_config'])
- frr_cfg.commit_configuration(daemon='isisd')
+ 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 isis['new_frr_config'] == '':
for a in range(5):
- frr_cfg.commit_configuration(daemon='isisd')
-
- # Debugging
- '''
- print('')
- print('--------- DEBUGGING ----------')
- print(f'Existing config:\n{frr_cfg["original_config"]}\n\n')
- print(f'Replacement config:\n{isis["new_frr_config"]}\n\n')
- print(f'Modified config:\n{frr_cfg["modified_config"]}\n\n')
- '''
+ frr_cfg.commit_configuration(frr_daemon)
return None
diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py
index aefe7c23e..a655eaeca 100755
--- a/src/conf_mode/protocols_ospf.py
+++ b/src/conf_mode/protocols_ospf.py
@@ -17,14 +17,17 @@
import os
from sys import exit
+from sys import argv
from vyos.config import Config
from vyos.configdict import dict_merge
+from vyos.configdict import node_changed
from vyos.configverify import verify_route_maps
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
+from vyos.util import get_interface_config
from vyos.xml import defaults
from vyos import ConfigError
from vyos import frr
@@ -38,16 +41,42 @@ def get_config(config=None):
conf = config
else:
conf = Config()
- base = ['protocols', 'ospf']
- ospf = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+
+ vrf = None
+ if len(argv) > 1:
+ vrf = argv[1]
+
+ base_path = ['protocols', 'ospf']
+
+ # eqivalent of the C foo ? 'a' : 'b' statement
+ base = vrf and ['vrf', 'name', vrf, 'protocols', 'ospf'] or base_path
+ ospf = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True)
+
+ # Assign the name of our VRF context. This MUST be done before the return
+ # statement below, else on deletion we will delete the default instance
+ # instead of the VRF instance.
+ if vrf: ospf['vrf'] = vrf
+
+ # As we no re-use this Python handler for both VRF and non VRF instances for
+ # OSPF we need to find out if any interfaces changed so properly adjust
+ # the FRR configuration and not by acctident change interfaces from a
+ # different VRF.
+ interfaces_removed = node_changed(conf, base + ['interface'])
+ if interfaces_removed:
+ ospf['interface_removed'] = list(interfaces_removed)
# Bail out early if configuration tree does not exist
if not conf.exists(base):
+ ospf.update({'deleted' : ''})
return ospf
# We have gathered the dict representation of the CLI, but there are default
# options which we need to update into the dictionary retrived.
- default_values = defaults(base)
+ # XXX: Note that we can not call defaults(base), as defaults does not work
+ # on an instance of a tag node. As we use the exact same CLI definition for
+ # both the non-vrf and vrf version this is absolutely safe!
+ default_values = defaults(base_path)
# We have to cleanup the default dict, as default values could enable features
# which are not explicitly enabled on the CLI. Example: default-information
@@ -63,6 +92,7 @@ def get_config(config=None):
for protocol in ['bgp', 'connected', 'isis', 'kernel', 'rip', 'static']:
if dict_search(f'redistribute.{protocol}', ospf) is None:
del default_values['redistribute'][protocol]
+
# XXX: T2665: we currently have no nice way for defaults under tag nodes,
# clean them out and add them manually :(
del default_values['neighbor']
@@ -121,12 +151,22 @@ def verify(ospf):
# 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}"!')
+ f'concurrently for {interface}!')
+
+ if 'vrf' in ospf:
+ # If interface specific options are set, we must ensure that the
+ # interface is bound to our requesting VRF. Due to the VyOS
+ # priorities the interface is bound to the VRF after creation of
+ # the VRF itself, and before any routing protocol is configured.
+ vrf = ospf['vrf']
+ tmp = get_interface_config(interface)
+ if 'master' not in tmp or tmp['master'] != vrf:
+ raise ConfigError(f'Interface {interface} is not a member of VRF {vrf}!')
return None
def generate(ospf):
- if not ospf:
+ if not ospf or 'deleted' in ospf:
ospf['new_frr_config'] = ''
return None
@@ -137,8 +177,20 @@ 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(r'^interface \S+', '')
- frr_cfg.modify_section('^router ospf$', '')
+
+ # Generate empty helper string which can be ammended to FRR commands,
+ # it will be either empty (default VRF) or contain the "vrf <name" statement
+ vrf = ''
+ if 'vrf' in ospf:
+ vrf = ' vrf ' + ospf['vrf']
+
+ frr_cfg.modify_section(f'^router ospf{vrf}$', '')
+ for key in ['interface', 'interface_removed']:
+ if key not in ospf:
+ continue
+ for interface in ospf[key]:
+ frr_cfg.modify_section(f'^interface {interface}{vrf}$', '')
+
frr_cfg.add_before(r'(ip prefix-list .*|route-map .*|line vty)', ospf['new_frr_config'])
frr_cfg.commit_configuration(frr_daemon)
diff --git a/src/conf_mode/protocols_static.py b/src/conf_mode/protocols_static.py
index 5d101b33e..3314baf47 100755
--- a/src/conf_mode/protocols_static.py
+++ b/src/conf_mode/protocols_static.py
@@ -17,11 +17,13 @@
import os
from sys import exit
+from sys import argv
from vyos.config import Config
+from vyos.configverify import verify_route_maps
+from vyos.configverify import verify_vrf
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
@@ -34,12 +36,40 @@ def get_config(config=None):
conf = config
else:
conf = Config()
- base = ['protocols', 'static']
+
+ vrf = None
+ if len(argv) > 1:
+ vrf = argv[1]
+
+ base_path = ['protocols', 'static']
+ # eqivalent of the C foo ? 'a' : 'b' statement
+ base = vrf and ['vrf', 'name', vrf, 'protocols', 'static'] or base_path
static = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+
+ # Assign the name of our VRF context
+ if vrf: static['vrf'] = vrf
+
return static
def verify(static):
verify_route_maps(static)
+
+ for route in ['route', 'route6']:
+ # if there is no route(6) key in the dictionary we can immediately
+ # bail out early
+ if route not in static:
+ continue
+
+ # When leaking routes to other VRFs we must ensure that the destination
+ # VRF exists
+ for prefix, prefix_options in static[route].items():
+ # both the interface and next-hop CLI node can have a VRF subnode,
+ # thus we check this using a for loop
+ for type in ['interface', 'next_hop']:
+ if type in prefix_options:
+ for interface, interface_config in prefix_options[type].items():
+ verify_vrf(interface_config)
+
return None
def generate(static):
@@ -50,8 +80,14 @@ 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 .*', '')
+
+ if 'vrf' in static:
+ vrf = static['vrf']
+ frr_cfg.modify_section(f'^vrf {vrf}$', '')
+ else:
+ 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)
diff --git a/src/conf_mode/protocols_vrf.py b/src/conf_mode/protocols_vrf.py
deleted file mode 100755
index 227e7d5e1..000000000
--- a/src/conf_mode/protocols_vrf.py
+++ /dev/null
@@ -1,72 +0,0 @@
-#!/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 6e94a19ae..51050e702 100755
--- a/src/conf_mode/service_console-server.py
+++ b/src/conf_mode/service_console-server.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2020 VyOS maintainers and contributors
+# Copyright (C) 2018-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,6 +17,7 @@
import os
from sys import exit
+from psutil import process_iter
from vyos.config import Config
from vyos.configdict import dict_merge
@@ -60,14 +61,19 @@ def verify(proxy):
if not proxy:
return None
+ processes = process_iter(['name', 'cmdline'])
if 'device' in proxy:
- for device in proxy['device']:
- if 'speed' not in proxy['device'][device]:
- raise ConfigError(f'Serial port speed must be defined for "{device}"!')
+ for device, device_config in proxy['device'].items():
+ for process in processes:
+ if 'agetty' in process.name() and device in process.cmdline():
+ raise ConfigError(f'Port "{device}" already provides a '\
+ 'console used by "system console"!')
+
+ if 'speed' not in device_config:
+ raise ConfigError(f'Port "{device}" requires speed to be set!')
- if 'ssh' in proxy['device'][device]:
- if 'port' not in proxy['device'][device]['ssh']:
- raise ConfigError(f'SSH port must be defined for "{device}"!')
+ if 'ssh' in device_config and 'port' not in device_config['ssh']:
+ raise ConfigError(f'Port "{device}" requires SSH port to be set!')
return None
@@ -77,13 +83,13 @@ def generate(proxy):
render(config_file, 'conserver/conserver.conf.tmpl', proxy)
if 'device' in proxy:
- for device in proxy['device']:
- if 'ssh' not in proxy['device'][device]:
+ for device, device_config in proxy['device'].items():
+ if 'ssh' not in device_config:
continue
tmp = {
'device' : device,
- 'port' : proxy['device'][device]['ssh']['port'],
+ 'port' : device_config['ssh']['port'],
}
render(dropbear_systemd_file.format(**tmp),
'conserver/dropbear@.service.tmpl', tmp)
@@ -102,10 +108,10 @@ def apply(proxy):
call('systemctl restart conserver-server.service')
if 'device' in proxy:
- for device in proxy['device']:
- if 'ssh' not in proxy['device'][device]:
+ for device, device_config in proxy['device'].items():
+ if 'ssh' not in device_config:
continue
- port = proxy['device'][device]['ssh']['port']
+ port = device_config['ssh']['port']
call(f'systemctl restart dropbear@{port}.service')
return None
diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py
index 6c6e219a5..414e514c5 100755
--- a/src/conf_mode/vrf.py
+++ b/src/conf_mode/vrf.py
@@ -23,17 +23,16 @@ from vyos.config import Config
from vyos.configdict import node_changed
from vyos.ifconfig import Interface
from vyos.template import render
+from vyos.util import call
from vyos.util import cmd
from vyos.util import dict_search
+from vyos.util import get_interface_config
from vyos import ConfigError
from vyos import airbag
airbag.enable()
config_file = r'/etc/iproute2/rt_tables.d/vyos-vrf.conf'
-def _cmd(command):
- cmd(command, raising=ConfigError, message='Error changing VRF')
-
def list_rules():
command = 'ip -j -4 rule show'
answer = loads(cmd(command))
@@ -111,8 +110,7 @@ def verify(vrf):
# routing table id can't be changed - OS restriction
if os.path.isdir(f'/sys/class/net/{name}'):
- tmp = loads(cmd(f'ip -j -d link show {name}'))[0]
- tmp = str(dict_search('linkinfo.info_data.table', tmp))
+ tmp = str(dict_search('linkinfo.info_data.table', get_interface_config(name)))
if tmp and tmp != config['table']:
raise ConfigError(f'VRF "{name}" table id modification not possible!')
@@ -140,14 +138,14 @@ def apply(vrf):
bind_all = '0'
if 'bind_to_all' in vrf:
bind_all = '1'
- _cmd(f'sysctl -wq net.ipv4.tcp_l3mdev_accept={bind_all}')
- _cmd(f'sysctl -wq net.ipv4.udp_l3mdev_accept={bind_all}')
+ call(f'sysctl -wq net.ipv4.tcp_l3mdev_accept={bind_all}')
+ call(f'sysctl -wq net.ipv4.udp_l3mdev_accept={bind_all}')
for tmp in (dict_search('vrf_remove', vrf) or []):
if os.path.isdir(f'/sys/class/net/{tmp}'):
- _cmd(f'ip -4 route del vrf {tmp} unreachable default metric 4278198272')
- _cmd(f'ip -6 route del vrf {tmp} unreachable default metric 4278198272')
- _cmd(f'ip link delete dev {tmp}')
+ call(f'ip -4 route del vrf {tmp} unreachable default metric 4278198272')
+ call(f'ip -6 route del vrf {tmp} unreachable default metric 4278198272')
+ call(f'ip link delete dev {tmp}')
if 'name' in vrf:
for name, config in vrf['name'].items():
@@ -156,16 +154,16 @@ def apply(vrf):
if not os.path.isdir(f'/sys/class/net/{name}'):
# For each VRF apart from your default context create a VRF
# interface with a separate routing table
- _cmd(f'ip link add {name} type vrf table {table}')
+ call(f'ip link add {name} type vrf table {table}')
# The kernel Documentation/networking/vrf.txt also recommends
# adding unreachable routes to the VRF routing tables so that routes
# afterwards are taken.
- _cmd(f'ip -4 route add vrf {name} unreachable default metric 4278198272')
- _cmd(f'ip -6 route add vrf {name} unreachable default metric 4278198272')
+ call(f'ip -4 route add vrf {name} unreachable default metric 4278198272')
+ call(f'ip -6 route add vrf {name} unreachable default metric 4278198272')
# We also should add proper loopback IP addresses to the newly
# created VRFs for services bound to the loopback address (SNMP, NTP)
- _cmd(f'ip -4 addr add 127.0.0.1/8 dev {name}')
- _cmd(f'ip -6 addr add ::1/128 dev {name}')
+ call(f'ip -4 addr add 127.0.0.1/8 dev {name}')
+ call(f'ip -6 addr add ::1/128 dev {name}')
# set VRF description for e.g. SNMP monitoring
vrf_if = Interface(name)
@@ -199,18 +197,18 @@ def apply(vrf):
# change preference when VRFs are enabled and local lookup table is default
if not local_pref and 'name' in vrf:
for af in ['-4', '-6']:
- _cmd(f'ip {af} rule add pref 32765 table local')
- _cmd(f'ip {af} rule del pref 0')
+ call(f'ip {af} rule add pref 32765 table local')
+ call(f'ip {af} rule del pref 0')
# return to default lookup preference when no VRF is configured
if 'name' not in vrf:
for af in ['-4', '-6']:
- _cmd(f'ip {af} rule add pref 0 table local')
- _cmd(f'ip {af} rule del pref 32765')
+ call(f'ip {af} rule add pref 0 table local')
+ call(f'ip {af} rule del pref 32765')
# clean out l3mdev-table rule if present
if 1000 in [r.get('priority') for r in list_rules() if r.get('priority') == 1000]:
- _cmd(f'ip {af} rule del pref 1000')
+ call(f'ip {af} rule del pref 1000')
return None