summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Mangin <thomas.mangin@exa.net.uk>2020-03-03 20:01:56 +0100
committerChristian Poessinger <christian@poessinger.com>2020-03-04 21:43:59 +0100
commit665d1c5bdb24aa0aef79405dc2f2962b930fb9b3 (patch)
tree467499a4ce63308ca6d1e349b13f1e807ba4703e
parentdac5ad318b00853591121078bce5c849db23c810 (diff)
downloadvyos-1x-665d1c5bdb24aa0aef79405dc2f2962b930fb9b3.tar.gz
vyos-1x-665d1c5bdb24aa0aef79405dc2f2962b930fb9b3.zip
vrf: T31: initial support for a VRF backend in XML/Python
This is a work in progress to complete T31 whoever thought it was less than 1 hour of work was ..... optimistic. Only VRF vreation and show is supported right now. No interface can be bound to any one VRF.
-rw-r--r--interface-definitions/include/interface-vrf.xml.i12
-rw-r--r--interface-definitions/vrf.xml.in58
-rw-r--r--op-mode-definitions/show-vrf.xml24
-rw-r--r--python/vyos/vrf.py23
-rwxr-xr-xsrc/completion/list_vrf.py27
-rwxr-xr-xsrc/conf_mode/vrf.py186
-rwxr-xr-xsrc/validators/interface-name29
7 files changed, 359 insertions, 0 deletions
diff --git a/interface-definitions/include/interface-vrf.xml.i b/interface-definitions/include/interface-vrf.xml.i
new file mode 100644
index 000000000..7e880e6ee
--- /dev/null
+++ b/interface-definitions/include/interface-vrf.xml.i
@@ -0,0 +1,12 @@
+<leafNode name="vrf">
+ <properties>
+ <help>VRF instance name</help>
+ <completionHelp>
+ <path>vrf name</path>
+ </completionHelp>
+ <constraint>
+ <validator name="interface-name"/>
+ </constraint>
+ <constraintErrorMessage>VRF name not allowed or to long</constraintErrorMessage>
+ </properties>
+</leafNode>
diff --git a/interface-definitions/vrf.xml.in b/interface-definitions/vrf.xml.in
new file mode 100644
index 000000000..e270e8b90
--- /dev/null
+++ b/interface-definitions/vrf.xml.in
@@ -0,0 +1,58 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="vrf" owner="${vyos_conf_scripts_dir}/vrf.py">
+ <properties>
+ <help>VRF configuration</help>
+ <!-- must be before any interface creation -->
+ <priority>210</priority>
+ </properties>
+ <children>
+ <node name="disable-bind-to-all">
+ <properties>
+ <help>Disable services running on the default VRF from other VRF (ssh, bgp, ...)</help>
+ </properties>
+ <children>
+ <leafNode name="ipv4">
+ <properties>
+ <valueless/>
+ <help>Enable binding across all VRF domains for IPv4</help>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <tagNode name="name">
+ <properties>
+ <help>Virtual Routing and Forwarding</help>
+ <constraint>
+ <validator name="interface-name"/>
+ </constraint>
+ <constraintErrorMessage>VRF name not allowed or to long</constraintErrorMessage>
+ <valueHelp>
+ <format>name</format>
+ <description>the vrf name must not contain '/' and be 16 characters or less</description>
+ </valueHelp>
+ </properties>
+ <children>
+ <leafNode name="table">
+ <properties>
+ <help>The routing table to associate to this VRF</help>
+ <constraint>
+ <validator name="numeric" argument="--range 1-2147483647"/>
+ </constraint>
+ <constraintErrorMessage>Invalid kernel table number</constraintErrorMessage>
+ <valueHelp>
+ <format>number</format>
+ <description>the VRF must be a number between 1 and 2^31-1</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="description">
+ <properties>
+ <help>Description of the VRF role</help>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+</interfaceDefinition> \ No newline at end of file
diff --git a/op-mode-definitions/show-vrf.xml b/op-mode-definitions/show-vrf.xml
new file mode 100644
index 000000000..fb2fddd49
--- /dev/null
+++ b/op-mode-definitions/show-vrf.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="show">
+ <children>
+ <node name="vrf">
+ <properties>
+ <help>Show VRF information</help>
+ </properties>
+ <command>${vyos_completion_dir}/list_vrf.py -e</command>
+ <children>
+ <tagNode name="name">
+ <properties>
+ <help>Show VRF information for an interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_vrf.py</script>
+ </completionHelp>
+ </properties>
+ <command>${vyos_completion_dir}/list_vrf.py -e "$4"</command>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/python/vyos/vrf.py b/python/vyos/vrf.py
new file mode 100644
index 000000000..99e4cb7d1
--- /dev/null
+++ b/python/vyos/vrf.py
@@ -0,0 +1,23 @@
+# Copyright 2020 VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+import json
+import subprocess
+
+
+def list_vrfs():
+ command = 'ip -j -br link show type vrf'
+ answer = json.loads(subprocess.check_output(command.split()).decode())
+ return [_ for _ in answer if _]
diff --git a/src/completion/list_vrf.py b/src/completion/list_vrf.py
new file mode 100755
index 000000000..210b3c9a4
--- /dev/null
+++ b/src/completion/list_vrf.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python3
+
+import argparse
+import vyos.vrf
+
+parser = argparse.ArgumentParser()
+group = parser.add_mutually_exclusive_group()
+group.add_argument("-e", "--extensive", action="store_true",
+ help="provide detailed vrf informatio")
+parser.add_argument('interface', metavar='I', type=str, nargs='?',
+ help='interface to display')
+
+args = parser.parse_args()
+
+if args.extensive:
+ print('{:16} {:7} {:17} {}'.format('interface', 'state', 'mac', 'flags'))
+ print('{:16} {:7} {:17} {}'.format('---------', '-----', '---', '-----'))
+ for vrf in vyos.vrf.list_vrfs():
+ name = vrf['ifname']
+ if args.interface and name != args.interface:
+ continue
+ state = vrf['operstate'].lower()
+ mac = vrf['address'].lower()
+ info = ','.join([_.lower() for _ in vrf['flags']])
+ print(f'{name:16} {state:7} {mac:17} {info}')
+else:
+ print(" ".join([vrf['ifname'] for vrf in vyos.vrf.list_vrfs()]))
diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py
new file mode 100755
index 000000000..9896c7c85
--- /dev/null
+++ b/src/conf_mode/vrf.py
@@ -0,0 +1,186 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+
+from sys import exit
+from copy import deepcopy
+from vyos.config import Config
+from vyos import ConfigError
+from vyos import vrf
+
+
+# https://github.com/torvalds/linux/blob/master/Documentation/networking/vrf.txt
+
+
+def sysctl(name, value):
+ os.system('sysctl -wq {}={}'.format(name, value))
+
+def interfaces_with_vrf (match, effective):
+ matched = []
+ config = Config()
+ section = config.get_config_dict('interfaces', effective)
+ for type in section:
+ interfaces = section[type]
+ for name in interfaces:
+ interface = interfaces[name]
+ if 'vrf' in interface:
+ v = interface.get('vrf', '')
+ if v == match:
+ matched.append(name)
+ return matched
+
+def get_config():
+ command = {
+ 'bind':{},
+ 'vrf':[],
+ 'int': {}, # per vrf name list of interfaces which will have it
+ }
+
+ config = Config()
+
+ old = {}
+ new = {}
+
+ if config.exists_effective('vrf'):
+ old = deepcopy(config.get_config_dict('vrf', True))
+
+ if config.exists('vrf'):
+ new = deepcopy(config.get_config_dict('vrf', False))
+
+ integer = lambda _: '1' if _ else '0'
+ command['bind']['ipv4'] = integer('ipv4' not in new.get('disable-bind-to-all', {}))
+ command['bind']['ipv6'] = integer('ipv6' not in new.get('disable-bind-to-all', {}))
+
+ old_names = old.get('name', [])
+ new_names = new.get('name', [])
+ all_names = list(set(old_names) | set(new_names))
+ del_names = list(set(old_names).difference(new_names))
+ mod_names = list(set(old_names).intersection(new_names))
+ add_names = list(set(new_names).difference(old_names))
+
+ for name in all_names:
+ v = {
+ 'name': name,
+ 'action': 'miss',
+ 'table': -1,
+ 'check': -1,
+ }
+
+ if name in new_names:
+ v['table'] = new.get('name', {}).get(name, {}).get('table', -1)
+ v['check'] = old.get('name', {}).get(name, {}).get('table', -1)
+
+ if name in add_names:
+ v['action'] = 'add'
+ elif name in del_names:
+ v['action'] = 'delete'
+ elif name in mod_names:
+ if v['table'] != -1:
+ if v['check'] == -1:
+ v['action'] = 'add'
+ else:
+ v['action'] = 'modify'
+
+ command['vrf'].append(v)
+
+ for v in vrf.list_vrfs():
+ name = v['ifname']
+ command['int'][name] = interfaces_with_vrf(name,False)
+
+ return command
+
+
+def verify(command):
+ for v in command['vrf']:
+ action = v['action']
+ name = v['name']
+ if action == 'modify' and v['table'] != v['check']:
+ raise ConfigError(f'set vrf name {name}: modification of vrf table is not supported yet')
+ if action == 'delete' and name in command['int']:
+ interface = ', '.join(command['int'][name])
+ if interface:
+ raise ConfigError(f'delete vrf name {name}: can not delete vrf as it is used on {interface}')
+
+ return command
+
+
+def generate(command):
+ return command
+
+
+def apply(command):
+ # set the default VRF global behaviour
+ sysctl('net.ipv4.tcp_l3mdev_accept', command['bind']['ipv4'])
+ sysctl('net.ipv4.udp_l3mdev_accept', command['bind']['ipv4'])
+
+ errors = []
+ for v in command['vrf']:
+ name = v['name']
+ action = v['action']
+ table = v['table']
+
+ errors.append(f'could not {action} vrf {name}')
+
+ if action == 'miss':
+ continue
+
+ if action == 'delete':
+ if os.system(f'sudo ip link delete dev {name}'):
+ continue
+ errors.pop()
+ continue
+
+ if action == 'modify':
+ # > uname -a
+ # Linux vyos 4.19.101-amd64-vyos #1 SMP Sun Feb 2 10:18:07 UTC 2020 x86_64 GNU/Linux
+ # > ip link add my-vrf type vrf table 100
+ # > ip link set my-vrf type vrf table 200
+ # RTNETLINK answers: Operation not supported
+ # so require to remove vrf and change all existing the interfaces
+
+ if os.system(f'sudo ip link delete dev {name}'):
+ continue
+ action = 'add'
+
+ if action == 'add':
+ commands = [
+ f'sudo ip link add {name} type vrf table {table}',
+ f'sudo ip link set dev {name} up',
+ f'sudo ip -4 rule add oif {name} lookup {table}',
+ f'sudo ip -4 rule add iif {name} lookup {table}',
+ f'sudo ip -6 rule add oif {name} lookup {table}',
+ f'sudo ip -6 rule add iif {name} lookup {table}',
+ ]
+
+ for command in commands:
+ if os.system(command):
+ errors[-1] += ' ('+command+')'
+ continue
+ errors.pop()
+
+ if errors:
+ raise ConfigError(', '.join(errors))
+
+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/validators/interface-name b/src/validators/interface-name
new file mode 100755
index 000000000..49a833f39
--- /dev/null
+++ b/src/validators/interface-name
@@ -0,0 +1,29 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2018 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 sys
+import re
+
+if len(sys.argv) == 2:
+ # https://unix.stackexchange.com/questions/451368/allowed-chars-in-linux-network-interface-names
+ pattern = "^([^/\s]{1,16}$)$"
+ if re.match(pattern, sys.argv[1]):
+ sys.exit(0)
+ else:
+ sys.exit(1)
+