summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/conf_mode/protocols_ospf.py16
-rwxr-xr-xsrc/conf_mode/protocols_static.py20
-rwxr-xr-xsrc/conf_mode/vrf.py38
-rwxr-xr-xsrc/services/vyos-configd41
-rwxr-xr-xsrc/validators/ipv6-eui64-prefix16
5 files changed, 92 insertions, 39 deletions
diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py
index ef2aeda7f..30246594a 100755
--- a/src/conf_mode/protocols_ospf.py
+++ b/src/conf_mode/protocols_ospf.py
@@ -57,6 +57,14 @@ def get_config(config=None):
# 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' : ''})
@@ -119,14 +127,6 @@ def get_config(config=None):
ospf['interface'][interface] = dict_merge(default_values,
ospf['interface'][interface])
- # 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)
-
# We also need some additional information from the config, prefix-lists
# and route-maps for instance. They will be used in verify()
base = ['policy']
diff --git a/src/conf_mode/protocols_static.py b/src/conf_mode/protocols_static.py
index 51b4acfc8..3314baf47 100755
--- a/src/conf_mode/protocols_static.py
+++ b/src/conf_mode/protocols_static.py
@@ -20,9 +20,10 @@ 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
@@ -52,6 +53,23 @@ def get_config(config=None):
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):
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
diff --git a/src/services/vyos-configd b/src/services/vyos-configd
index 1e60e53df..8547b5f56 100755
--- a/src/services/vyos-configd
+++ b/src/services/vyos-configd
@@ -25,7 +25,7 @@ import logging
import signal
import importlib.util
import zmq
-from contextlib import redirect_stdout, redirect_stderr
+from contextlib import contextmanager
from vyos.defaults import directories
from vyos.configsource import ConfigSourceString, ConfigSourceError
@@ -108,20 +108,40 @@ conf_mode_scripts = dict(zip(imports, modules))
exclude_set = {key_name_from_file_name(f) for f in filenames if f not in include}
include_set = {key_name_from_file_name(f) for f in filenames if f in include}
+@contextmanager
+def stdout_redirected(filename, mode):
+ saved_stdout_fd = None
+ destination_file = None
+ try:
+ sys.stdout.flush()
+ saved_stdout_fd = os.dup(sys.stdout.fileno())
+ destination_file = open(filename, mode)
+ os.dup2(destination_file.fileno(), sys.stdout.fileno())
+ yield
+ finally:
+ if saved_stdout_fd is not None:
+ os.dup2(saved_stdout_fd, sys.stdout.fileno())
+ os.close(saved_stdout_fd)
+ if destination_file is not None:
+ destination_file.close()
+
+def explicit_print(path, mode, msg):
+ try:
+ with open(path, mode) as f:
+ f.write(f"\n{msg}\n\n")
+ except OSError:
+ logger.critical("error explicit_print")
def run_script(script, config) -> int:
config.set_level([])
try:
- with open(session_out, session_mode) as f, redirect_stdout(f):
- with redirect_stderr(f):
- c = script.get_config(config)
- script.verify(c)
- script.generate(c)
- script.apply(c)
+ c = script.get_config(config)
+ script.verify(c)
+ script.generate(c)
+ script.apply(c)
except ConfigError as e:
logger.critical(e)
- with open(session_out, session_mode) as f, redirect_stdout(f):
- print(f"{e}\n")
+ explicit_print(session_out, session_mode, str(e))
return R_ERROR_COMMIT
except Exception as e:
logger.critical(e)
@@ -201,7 +221,8 @@ def process_node_data(config, data) -> int:
if script_name in exclude_set:
return R_PASS
- result = run_script(conf_mode_scripts[script_name], config)
+ with stdout_redirected(session_out, session_mode):
+ result = run_script(conf_mode_scripts[script_name], config)
return result
diff --git a/src/validators/ipv6-eui64-prefix b/src/validators/ipv6-eui64-prefix
new file mode 100755
index 000000000..d7f262633
--- /dev/null
+++ b/src/validators/ipv6-eui64-prefix
@@ -0,0 +1,16 @@
+#!/usr/bin/env python3
+
+# Validator used to check if given IPv6 prefix is of size /64 required by EUI64
+
+from sys import argv
+from sys import exit
+
+if __name__ == '__main__':
+ if len(argv) != 2:
+ exit(1)
+
+ prefix = argv[1]
+ if prefix.split('/')[1] == '64':
+ exit(0)
+
+ exit(1)