Skip to content

Instantly share code, notes, and snippets.

@thomas-mangin thomas-mangin/t31.patch Secret
Created Feb 12, 2020

Embed
What would you like to do?
T31: patch-1 review
diff --git a/interface-definitions/include/interface-vrf.xml.i b/interface-definitions/include/interface-vrf.xml.i
new file mode 100644
index 0000000..7e880e6
--- /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/include/vif-s.xml.i b/interface-definitions/include/vif-s.xml.i
index 2120aa3..c147066 100644
--- a/interface-definitions/include/vif-s.xml.i
+++ b/interface-definitions/include/vif-s.xml.i
@@ -8,6 +8,7 @@
</properties>
<children>
#include <include/address-ipv4-ipv6-dhcp.xml.i>
+ #include <include/interface-vrf.xml.i>
#include <include/interface-description.xml.i>
#include <include/dhcp-dhcpv6-options.xml.i>
#include <include/interface-disable-link-detect.xml.i>
diff --git a/interface-definitions/include/vif.xml.i b/interface-definitions/include/vif.xml.i
index 85e9018..1f65b42 100644
--- a/interface-definitions/include/vif.xml.i
+++ b/interface-definitions/include/vif.xml.i
@@ -12,6 +12,7 @@
</properties>
<children>
#include <include/address-ipv4-ipv6-dhcp.xml.i>
+ #include <include/interface-vrf.xml.i>
#include <include/interface-description.xml.i>
#include <include/dhcp-dhcpv6-options.xml.i>
#include <include/interface-disable-link-detect.xml.i>
diff --git a/interface-definitions/interfaces-bonding.xml.in b/interface-definitions/interfaces-bonding.xml.in
index 586a843..786be1e 100644
--- a/interface-definitions/interfaces-bonding.xml.in
+++ b/interface-definitions/interfaces-bonding.xml.in
@@ -17,6 +17,7 @@
</properties>
<children>
#include <include/address-ipv4-ipv6-dhcp.xml.i>
+ #include <include/interface-vrf.xml.i>
<node name="arp-monitor">
<properties>
<help>ARP link monitoring parameters</help>
@@ -156,6 +157,7 @@
</leafNode>
#include <include/vif-s.xml.i>
#include <include/vif.xml.i>
+ #include <include/interface-vrf.xml.i>
</children>
</tagNode>
</children>
diff --git a/interface-definitions/interfaces-bridge.xml.in b/interface-definitions/interfaces-bridge.xml.in
index e8285b1..4c43b42 100644
--- a/interface-definitions/interfaces-bridge.xml.in
+++ b/interface-definitions/interfaces-bridge.xml.in
@@ -17,6 +17,7 @@
</properties>
<children>
#include <include/address-ipv4-ipv6-dhcp.xml.i>
+ #include <include/interface-vrf.xml.i>
<leafNode name="aging">
<properties>
<help>MAC address aging interval</help>
diff --git a/interface-definitions/interfaces-dummy.xml.in b/interface-definitions/interfaces-dummy.xml.in
index 39809a6..326939e 100644
--- a/interface-definitions/interfaces-dummy.xml.in
+++ b/interface-definitions/interfaces-dummy.xml.in
@@ -17,6 +17,7 @@
</properties>
<children>
#include <include/address-ipv4-ipv6.xml.i>
+ #include <include/interface-vrf.xml.i>
#include <include/interface-description.xml.i>
#include <include/interface-disable.xml.i>
</children>
diff --git a/interface-definitions/interfaces-ethernet.xml.in b/interface-definitions/interfaces-ethernet.xml.in
index 8f5d735..f236790 100644
--- a/interface-definitions/interfaces-ethernet.xml.in
+++ b/interface-definitions/interfaces-ethernet.xml.in
@@ -21,6 +21,7 @@
</properties>
<children>
#include <include/address-ipv4-ipv6-dhcp.xml.i>
+ #include <include/interface-vrf.xml.i>
#include <include/interface-description.xml.i>
#include <include/dhcp-dhcpv6-options.xml.i>
<leafNode name="disable-flow-control">
diff --git a/interface-definitions/interfaces-geneve.xml.in b/interface-definitions/interfaces-geneve.xml.in
index a6406ff..302eada 100644
--- a/interface-definitions/interfaces-geneve.xml.in
+++ b/interface-definitions/interfaces-geneve.xml.in
@@ -17,6 +17,7 @@
</properties>
<children>
#include <include/address-ipv4-ipv6.xml.i>
+ #include <include/interface-vrf.xml.i>
#include <include/interface-description.xml.i>
#include <include/interface-disable.xml.i>
<node name="ip">
diff --git a/interface-definitions/interfaces-l2tpv3.xml.in b/interface-definitions/interfaces-l2tpv3.xml.in
index a408e58..bc5b7c0 100644
--- a/interface-definitions/interfaces-l2tpv3.xml.in
+++ b/interface-definitions/interfaces-l2tpv3.xml.in
@@ -17,6 +17,7 @@
</properties>
<children>
#include <include/address-ipv4-ipv6.xml.i>
+ #include <include/interface-vrf.xml.i>
#include <include/interface-description.xml.i>
<leafNode name="destination-port">
<properties>
diff --git a/interface-definitions/interfaces-loopback.xml.in b/interface-definitions/interfaces-loopback.xml.in
index ddbfad7..2152bb7 100644
--- a/interface-definitions/interfaces-loopback.xml.in
+++ b/interface-definitions/interfaces-loopback.xml.in
@@ -17,6 +17,7 @@
</properties>
<children>
#include <include/address-ipv4-ipv6.xml.i>
+ #include <include/interface-vrf.xml.i>
#include <include/interface-description.xml.i>
</children>
</tagNode>
diff --git a/interface-definitions/interfaces-openvpn.xml.in b/interface-definitions/interfaces-openvpn.xml.in
index bc1a159..5de7bb7 100644
--- a/interface-definitions/interfaces-openvpn.xml.in
+++ b/interface-definitions/interfaces-openvpn.xml.in
@@ -34,6 +34,7 @@
</children>
</node>
#include <include/interface-description.xml.i>
+ #include <include/interface-vrf.xml.i>
<leafNode name="device-type">
<properties>
<help>OpenVPN interface device-type</help>
diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in
index 16cb2c1..147cbab 100644
--- a/interface-definitions/interfaces-vxlan.xml.in
+++ b/interface-definitions/interfaces-vxlan.xml.in
@@ -17,6 +17,7 @@
</properties>
<children>
#include <include/address-ipv4-ipv6.xml.i>
+ #include <include/interface-vrf.xml.i>
#include <include/interface-description.xml.i>
#include <include/interface-disable.xml.i>
<leafNode name="group">
diff --git a/interface-definitions/interfaces-wireguard.xml.in b/interface-definitions/interfaces-wireguard.xml.in
index dd4a73e..9f8ac9a 100644
--- a/interface-definitions/interfaces-wireguard.xml.in
+++ b/interface-definitions/interfaces-wireguard.xml.in
@@ -17,6 +17,7 @@
</properties>
<children>
#include <include/address-ipv4-ipv6.xml.i>
+ #include <include/interface-vrf.xml.i>
#include <include/interface-description.xml.i>
#include <include/interface-disable.xml.i>
<leafNode name="port">
diff --git a/interface-definitions/interfaces-wireless.xml.in b/interface-definitions/interfaces-wireless.xml.in
index d6b2579..6cc6f70 100644
--- a/interface-definitions/interfaces-wireless.xml.in
+++ b/interface-definitions/interfaces-wireless.xml.in
@@ -17,6 +17,7 @@
</properties>
<children>
#include <include/address-ipv4-ipv6-dhcp.xml.i>
+ #include <include/interface-vrf.xml.i>
<node name="capabilities">
<properties>
<help>HT and VHT capabilities for your card</help>
@@ -754,6 +755,7 @@
</properties>
</leafNode>
#include <include/vif.xml.i>
+ #include <include/interface-vrf.xml.i>
</children>
</tagNode>
</children>
diff --git a/interface-definitions/ssh.xml.in b/interface-definitions/ssh.xml.in
index ea42021..2258c18 100644
--- a/interface-definitions/ssh.xml.in
+++ b/interface-definitions/ssh.xml.in
@@ -9,6 +9,7 @@
<priority>500</priority>
</properties>
<children>
+ #include <include/interface-vrf.xml.i>
<node name="access-control">
<properties>
<help>SSH user/group access controls. Directives are processed
diff --git a/interface-definitions/vrf.xml.in b/interface-definitions/vrf.xml.in
new file mode 100644
index 0000000..e270e8b
--- /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 0000000..fb2fddd
--- /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/config.py b/python/vyos/config.py
index 2342f70..88dd67e 100644
--- a/python/vyos/config.py
+++ b/python/vyos/config.py
@@ -262,7 +262,7 @@ class Config(object):
Args: path (str list): Configuration tree path, can be empty
Returns: a dict representation of the config
"""
- res = self.show_config(self._make_path(path), effective=effective)
+ res = self.show_config(self._make_path(path), default='', effective=effective)
config_tree = vyos.configtree.ConfigTree(res)
config_dict = json.loads(config_tree.to_json())
return config_dict
diff --git a/python/vyos/ifconfig.py b/python/vyos/ifconfig.py
index 90b8fc1..3d6f48a 100644
--- a/python/vyos/ifconfig.py
+++ b/python/vyos/ifconfig.py
@@ -66,7 +66,7 @@ interface "{{ intf }}" {
"""
class Interface:
- def __init__(self, ifname, type=None):
+ def __init__(self, ifname, type=None, vrf=''):
"""
This is the base interface class which supports basic IP/MAC address
operations as well as DHCP(v6). Other interface which represent e.g.
@@ -91,6 +91,15 @@ class Interface:
cmd = 'ip link add dev {} type {}'.format(self._ifname, type)
self._cmd(cmd)
+ if vrf:
+ cmd = 'ip link set {} master {}'.format(self._ifname,vrf)
+ res = self._cmd(cmd)
+ if res: print(res)
+ else:
+ cmd = 'ip link set dev {} nomaster'.format(self._ifname)
+ res = self._cmd(cmd)
+ if res: print(res)
+
# per interface DHCP config files
self._dhcp_cfg_file = dhclient_base + self._ifname + '.conf'
self._dhcp_pid_file = dhclient_base + self._ifname + '.pid'
@@ -812,8 +821,8 @@ class LoopbackIf(Interface):
uses to communicate with itself.
"""
- def __init__(self, ifname):
- super().__init__(ifname, type='loopback')
+ def __init__(self, ifname, vrf=''):
+ super().__init__(ifname, type='loopback', vrf=vrf)
def remove(self):
"""
@@ -839,8 +848,8 @@ class DummyIf(Interface):
packets through without actually transmitting them.
"""
- def __init__(self, ifname):
- super().__init__(ifname, type='dummy')
+ def __init__(self, ifname, vrf=''):
+ super().__init__(ifname, type='dummy', vrf=vrf)
class STPIf(Interface):
@@ -848,8 +857,8 @@ class STPIf(Interface):
A spanning-tree capable interface. This applies only to bridge port member
interfaces!
"""
- def __init__(self, ifname):
- super().__init__(ifname)
+ def __init__(self, ifname, vrf=''):
+ super().__init__(ifname, vrf=vrf)
def set_path_cost(self, cost):
"""
@@ -895,8 +904,8 @@ class BridgeIf(Interface):
The Linux bridge code implements a subset of the ANSI/IEEE 802.1d standard.
"""
- def __init__(self, ifname):
- super().__init__(ifname, type='bridge')
+ def __init__(self, ifname, vrf=''):
+ super().__init__(ifname, type='bridge', vrf=vrf)
def set_ageing_time(self, time):
"""
@@ -1022,8 +1031,8 @@ class VLANIf(Interface):
This class handels the creation and removal of a VLAN interface. It serves
as base class for BondIf and EthernetIf.
"""
- def __init__(self, ifname, type=None):
- super().__init__(ifname, type)
+ def __init__(self, ifname, type=None, vrf =''):
+ super().__init__(ifname, type, vrf=vrf)
def remove(self):
"""
@@ -1060,7 +1069,7 @@ class VLANIf(Interface):
super().remove()
- def add_vlan(self, vlan_id, ethertype='', ingress_qos='', egress_qos=''):
+ def add_vlan(self, vlan_id, ethertype='', ingress_qos='', egress_qos='', vrf=''):
"""
A virtual LAN (VLAN) is any broadcast domain that is partitioned and
isolated in a computer network at the data link layer (OSI layer 2).
@@ -1110,7 +1119,7 @@ class VLANIf(Interface):
# return new object mapping to the newly created interface
# we can now work on this object for e.g. IP address setting
# or interface description and so on
- return VLANIf(vlan_ifname)
+ return VLANIf(vlan_ifname, vrf=vrf)
def del_vlan(self, vlan_id):
@@ -1132,8 +1141,8 @@ class EthernetIf(VLANIf):
"""
Abstraction of a Linux Ethernet Interface
"""
- def __init__(self, ifname):
- super().__init__(ifname)
+ def __init__(self, ifname, vrf=''):
+ super().__init__(ifname, vrf=vrf)
def get_driver_name(self):
"""
@@ -1334,8 +1343,8 @@ class BondIf(VLANIf):
either hot standby or load balancing services. Additionally, link integrity
monitoring may be performed.
"""
- def __init__(self, ifname):
- super().__init__(ifname, type='bond')
+ def __init__(self, ifname, vrf=''):
+ super().__init__(ifname, type='bond', vrf=vrf)
def remove(self):
"""
@@ -1567,8 +1576,8 @@ class WireGuardIf(Interface):
'allowed-ips': [], 'pubkey': None, 'fwmark': 0, 'psk': '/dev/null'}
"""
- def __init__(self, ifname):
- super().__init__(ifname, type='wireguard')
+ def __init__(self, ifname, vrf=''):
+ super().__init__(ifname, type='wireguard', vrf=vrf)
self.config = {
'port': 0,
@@ -1708,7 +1717,7 @@ class VXLANIf(Interface):
For more information please refer to:
https://www.kernel.org/doc/Documentation/networking/vxlan.txt
"""
- def __init__(self, ifname, config=''):
+ def __init__(self, ifname, config='',vrf=''):
if config:
self._ifname = ifname
@@ -1729,7 +1738,7 @@ class VXLANIf(Interface):
.format(intf=self._ifname, vni=config['vni'], grp_rem=group, dev=dev, port=config['port'])
self._cmd(cmd)
- super().__init__(ifname, type='vxlan')
+ super().__init__(ifname, type='vxlan', vrf=vrf)
@staticmethod
def get_config():
@@ -1761,7 +1770,7 @@ class GeneveIf(Interface):
https://developers.redhat.com/blog/2019/05/17/an-introduction-to-linux-virtual-interfaces-tunnels/#geneve
https://lwn.net/Articles/644938/
"""
- def __init__(self, ifname, config=''):
+ def __init__(self, ifname, config='', vrf=''):
if config:
self._ifname = ifname
if not os.path.exists('/sys/class/net/{}'.format(self._ifname)):
@@ -1772,7 +1781,7 @@ class GeneveIf(Interface):
# interface is always A/D down. It needs to be enabled explicitly
self.set_state('down')
- super().__init__(ifname, type='geneve')
+ super().__init__(ifname, type='geneve', vrf=vrf)
@staticmethod
def get_config():
@@ -1798,7 +1807,7 @@ class L2TPv3If(Interface):
either hot standby or load balancing services. Additionally, link integrity
monitoring may be performed.
"""
- def __init__(self, ifname, config=''):
+ def __init__(self, ifname, config='',vrf=''):
self._config = {}
if config:
self._ifname = ifname
@@ -1824,7 +1833,7 @@ class L2TPv3If(Interface):
# interface is always A/D down. It needs to be enabled explicitly
self.set_state('down')
- super().__init__(ifname, type='l2tp')
+ super().__init__(ifname, type='l2tp',vrf=vrf)
def remove(self):
"""
diff --git a/python/vyos/vrf.py b/python/vyos/vrf.py
new file mode 100644
index 0000000..99e4cb7
--- /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 0000000..210b3c9
--- /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/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py
index ac4f213..2244589 100755
--- a/src/conf_mode/interfaces-bonding.py
+++ b/src/conf_mode/interfaces-bonding.py
@@ -58,7 +58,8 @@ default_config_data = {
'vif_s': [],
'vif_s_remove': [],
'vif': [],
- 'vif_remove': []
+ 'vif_remove': [],
+ 'vrf': '',
}
@@ -244,6 +245,12 @@ def get_config():
conf.set_level(cfg_base + ' vif ' + vif)
bond['vif'].append(vlan_to_dict(conf))
+ # re-set configuration level to parse new nodes
+ conf.set_level(cfg_base)
+ # interface vrf
+ if conf.exists('vrf'):
+ bond['vrf'] = conf.return_value('vrf')
+
return bond
@@ -343,12 +350,12 @@ def generate(bond):
return None
def apply(bond):
- b = BondIf(bond['intf'])
-
if bond['deleted']:
+ b = BondIf(bond['intf'])
# delete interface
b.remove()
else:
+ b = BondIf(bond['intf'], vrf=bond['vrf'])
# ARP link monitoring frequency, reset miimon when arp-montior is inactive
# this is done inside BondIf automatically
b.set_arp_interval(bond['arp_mon_intvl'])
@@ -469,7 +476,7 @@ def apply(bond):
# create service VLAN interfaces (vif-s)
for vif_s in bond['vif_s']:
- s_vlan = b.add_vlan(vif_s['id'], ethertype=vif_s['ethertype'])
+ s_vlan = b.add_vlan(vif_s['id'], ethertype=vif_s['ethertype'], vrf=bond['vrf'])
apply_vlan_config(s_vlan, vif_s)
# remove no longer required client VLAN interfaces (vif-c)
@@ -480,7 +487,7 @@ def apply(bond):
# create client VLAN interfaces (vif-c)
# on lower service VLAN interface
for vif_c in vif_s['vif_c']:
- c_vlan = s_vlan.add_vlan(vif_c['id'])
+ c_vlan = s_vlan.add_vlan(vif_c['id'], vrf=bond['vrf'])
apply_vlan_config(c_vlan, vif_c)
# remove no longer required VLAN interfaces (vif)
@@ -489,7 +496,7 @@ def apply(bond):
# create VLAN interfaces (vif)
for vif in bond['vif']:
- vlan = b.add_vlan(vif['id'])
+ vlan = b.add_vlan(vif['id'], vrf=bond['vrf'])
apply_vlan_config(vlan, vif)
return None
diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py
index a3213f3..ffbb5a4 100755
--- a/src/conf_mode/interfaces-bridge.py
+++ b/src/conf_mode/interfaces-bridge.py
@@ -52,7 +52,8 @@ default_config_data = {
'member': [],
'member_remove': [],
'priority': 32768,
- 'stp': 0
+ 'stp': 0,
+ 'vrf': '',
}
def get_config():
@@ -191,6 +192,10 @@ def get_config():
if conf.exists('stp'):
bridge['stp'] = 1
+ # interface vrf
+ if conf.exists('vrf'):
+ bridge['vrf'] = conf.return_value('vrf')
+
return bridge
def verify(bridge):
@@ -226,12 +231,12 @@ def generate(bridge):
return None
def apply(bridge):
- br = BridgeIf(bridge['intf'])
-
if bridge['deleted']:
+ br = BridgeIf(bridge['intf'])
# delete interface
br.remove()
else:
+ br = BridgeIf(bridge['intf'], vrf=bridge['vrf'])
# enable interface
br.set_state('up')
# set ageing time
@@ -312,7 +317,7 @@ def apply(bridge):
# configure additional bridge member options
for member in bridge['member']:
- i = STPIf(member['name'])
+ i = STPIf(member['name'], vrf=bridge['vrf'])
# configure ARP cache timeout
i.set_arp_cache_tmo(bridge['arp_cache_tmo'])
# ignore link state changes
diff --git a/src/conf_mode/interfaces-dummy.py b/src/conf_mode/interfaces-dummy.py
index eb0145f..01ec61e 100755
--- a/src/conf_mode/interfaces-dummy.py
+++ b/src/conf_mode/interfaces-dummy.py
@@ -30,7 +30,8 @@ default_config_data = {
'deleted': False,
'description': '',
'disable': False,
- 'intf': ''
+ 'intf': '',
+ 'vrf': '',
}
def get_config():
@@ -69,6 +70,10 @@ def get_config():
act_addr = conf.return_values('address')
dummy['address_remove'] = list_diff(eff_addr, act_addr)
+ # interface vrf
+ if conf.exists('vrf'):
+ dummy['vrf'] = conf.return_value('vrf')
+
return dummy
def verify(dummy):
@@ -78,12 +83,12 @@ def generate(dummy):
return None
def apply(dummy):
- d = DummyIf(dummy['intf'])
-
# Remove dummy interface
if dummy['deleted']:
+ d = DummyIf(dummy['intf'])
d.remove()
else:
+ d = DummyIf(dummy['intf'], dummy['vrf'])
# update interface description used e.g. within SNMP
d.set_alias(dummy['description'])
diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py
index e4f6e5f..874b95a 100755
--- a/src/conf_mode/interfaces-ethernet.py
+++ b/src/conf_mode/interfaces-ethernet.py
@@ -59,7 +59,8 @@ default_config_data = {
'vif_s': [],
'vif_s_remove': [],
'vif': [],
- 'vif_remove': []
+ 'vif_remove': [],
+ 'vrf': '',
}
def get_config():
@@ -225,6 +226,12 @@ def get_config():
conf.set_level(cfg_base + ['vif', vif])
eth['vif'].append(vlan_to_dict(conf))
+ # re-set configuration level to parse new nodes
+ conf.set_level(cfg_base)
+ # interface vrf
+ if conf.exists('vrf'):
+ eth['vrf'] = conf.return_value('vrf')
+
return eth
@@ -271,11 +278,12 @@ def generate(eth):
return None
def apply(eth):
- e = EthernetIf(eth['intf'])
if eth['deleted']:
+ e = EthernetIf(eth['intf'])
# delete interface
e.remove()
else:
+ e = EthernetIf(eth['intf'], eth['vrf'])
# update interface description used e.g. within SNMP
e.set_alias(eth['description'])
@@ -373,7 +381,8 @@ def apply(eth):
# create service VLAN interfaces (vif-s)
for vif_s in eth['vif_s']:
- s_vlan = e.add_vlan(vif_s['id'], ethertype=vif_s['ethertype'])
+ s_vlan = e.add_vlan(
+ vif_s['id'], ethertype=vif_s['ethertype'], vrf=eth['vrf'])
apply_vlan_config(s_vlan, vif_s)
# remove no longer required client VLAN interfaces (vif-c)
@@ -384,7 +393,7 @@ def apply(eth):
# create client VLAN interfaces (vif-c)
# on lower service VLAN interface
for vif_c in vif_s['vif_c']:
- c_vlan = s_vlan.add_vlan(vif_c['id'])
+ c_vlan = s_vlan.add_vlan(vif_c['id'], vrf=eth['vrf'])
apply_vlan_config(c_vlan, vif_c)
# remove no longer required VLAN interfaces (vif)
@@ -403,7 +412,7 @@ def apply(eth):
except:
pass
- vlan = e.add_vlan(vif['id'], ingress_qos=vif['ingress_qos'], egress_qos=vif['egress_qos'])
+ vlan = e.add_vlan(vif['id'], ingress_qos=vif['ingress_qos'], egress_qos=vif['egress_qos'], vrf=eth['vrf'])
apply_vlan_config(vlan, vif)
return None
diff --git a/src/conf_mode/interfaces-geneve.py b/src/conf_mode/interfaces-geneve.py
index b0c3816..5ba85a9 100755
--- a/src/conf_mode/interfaces-geneve.py
+++ b/src/conf_mode/interfaces-geneve.py
@@ -34,7 +34,8 @@ default_config_data = {
'ip_proxy_arp': 0,
'mtu': 1500,
'remote': '',
- 'vni': ''
+ 'vni': '',
+ 'vrf': '',
}
def get_config():
@@ -87,6 +88,10 @@ def get_config():
if conf.exists('vni'):
geneve['vni'] = conf.return_value('vni')
+ # interface vrf
+ if conf.exists('vrf'):
+ geneve['vrf'] = conf.return_value('vrf')
+
return geneve
@@ -127,7 +132,7 @@ def apply(geneve):
conf['remote'] = geneve['remote']
# Finally create the new interface
- g = GeneveIf(geneve['intf'], config=conf)
+ g = GeneveIf(geneve['intf'], config=conf, vrf=geneve['vrf'])
# update interface description used e.g. by SNMP
g.set_alias(geneve['description'])
# Maximum Transfer Unit (MTU)
diff --git a/src/conf_mode/interfaces-l2tpv3.py b/src/conf_mode/interfaces-l2tpv3.py
index ae49dad..b4da968 100755
--- a/src/conf_mode/interfaces-l2tpv3.py
+++ b/src/conf_mode/interfaces-l2tpv3.py
@@ -39,7 +39,8 @@ default_config_data = {
'remote_address': '',
'remote_port': 5000,
'session_id': '',
- 'tunnel_id': ''
+ 'tunnel_id': '',
+ 'vrf': '',
}
def get_config():
@@ -122,6 +123,10 @@ def get_config():
if conf.exists('tunnel-id'):
l2tpv3['tunnel_id'] = conf.return_value('tunnel-id')
+ # interface vrf
+ if conf.exists('vrf'):
+ l2tpv3['vrf'] = conf.return_value('vrf')
+
return l2tpv3
@@ -208,7 +213,7 @@ def apply(l2tpv3):
conf['peer_session_id'] = l2tpv3['peer_session_id']
# Finally create the new interface
- l = L2TPv3If(l2tpv3['intf'], config=conf)
+ l = L2TPv3If(l2tpv3['intf'], config=conf, vrf=l2tpv3['vrf'])
# update interface description used e.g. by SNMP
l.set_alias(l2tpv3['description'])
# Maximum Transfer Unit (MTU)
diff --git a/src/conf_mode/interfaces-loopback.py b/src/conf_mode/interfaces-loopback.py
index 10722d1..ca4edc5 100755
--- a/src/conf_mode/interfaces-loopback.py
+++ b/src/conf_mode/interfaces-loopback.py
@@ -29,6 +29,7 @@ default_config_data = {
'address_remove': [],
'deleted': False,
'description': '',
+ 'vrf': '',
}
@@ -63,6 +64,10 @@ def get_config():
act_addr = conf.return_values('address')
loopback['address_remove'] = list_diff(eff_addr, act_addr)
+ # interface vrf
+ if conf.exists('vrf'):
+ loopback['vrf'] = conf.return_value('vrf')
+
return loopback
def verify(loopback):
@@ -72,10 +77,11 @@ def generate(loopback):
return None
def apply(loopback):
- l = LoopbackIf(loopback['intf'])
if loopback['deleted']:
+ l = LoopbackIf(loopback['intf'])
l.remove()
else:
+ l = LoopbackIf(loopback['intf'], vrf=loopback['vrf'])
# update interface description used e.g. within SNMP
l.set_alias(loopback['description'])
diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py
index 3a7bc66..20fffd4 100755
--- a/src/conf_mode/interfaces-openvpn.py
+++ b/src/conf_mode/interfaces-openvpn.py
@@ -324,6 +324,7 @@ default_config_data = {
'type': 'tun',
'uid': user,
'gid': group,
+ 'vrf': '',
}
def subprocess_cmd(command):
@@ -649,6 +650,10 @@ def get_config():
if conf.exists('use-lzo-compression'):
openvpn['compress_lzo'] = True
+ # interface vrf
+ if conf.exists('vrf'):
+ openvpn['vrf'] = conf.return_value('vrf')
+
return openvpn
def verify(openvpn):
@@ -991,14 +996,14 @@ def apply(openvpn):
try:
# we need to catch the exception if the interface is not up due to
# reason stated above
- Interface(openvpn['intf']).set_alias(openvpn['description'])
+ Interface(openvpn['intf'], vrf=openvpn['vrf']).set_alias(openvpn['description'])
except:
pass
# TAP interface needs to be brought up explicitly
if openvpn['type'] == 'tap':
if not openvpn['disable']:
- Interface(openvpn['intf']).set_state('up')
+ Interface(openvpn['intf'], vrf=openvpn['vrf']).set_state('up')
return None
diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py
index efdc21f..70b29a5 100755
--- a/src/conf_mode/interfaces-vxlan.py
+++ b/src/conf_mode/interfaces-vxlan.py
@@ -42,7 +42,8 @@ default_config_data = {
'remote': '',
'remote_port': 8472, # The Linux implementation of VXLAN pre-dates
# the IANA's selection of a standard destination port
- 'vni': ''
+ 'vni': '',
+ 'vrf': '',
}
def get_config():
@@ -123,6 +124,10 @@ def get_config():
if conf.exists('vni'):
vxlan['vni'] = conf.return_value('vni')
+ # interface vrf
+ if conf.exists('vrf'):
+ vxlan['vrf'] = conf.return_value('vrf')
+
return vxlan
@@ -180,7 +185,7 @@ def apply(vxlan):
conf['port'] = vxlan['remote_port']
# Finally create the new interface
- v = VXLANIf(vxlan['intf'], config=conf)
+ v = VXLANIf(vxlan['intf'], config=conf, vrf=vxlan['vrf'])
# update interface description used e.g. by SNMP
v.set_alias(vxlan['description'])
# Maximum Transfer Unit (MTU)
diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py
index ff12a51..39d1b1f 100755
--- a/src/conf_mode/interfaces-wireguard.py
+++ b/src/conf_mode/interfaces-wireguard.py
@@ -68,7 +68,8 @@ def get_config():
'mtu': 1420,
'peer': {},
'peer_remove': [],
- 'pk': '{}/default/private.key'.format(kdir)
+ 'pk': '{}/default/private.key'.format(kdir),
+ 'vrf': '',
}
if os.getenv('VYOS_TAGNODE_VALUE'):
@@ -171,6 +172,11 @@ def get_config():
else:
peer_key = c.return_value(['peer', p, 'pubkey'])
wg['peer_remove'].append(peer_key)
+
+ # interface vrf
+ if c.exists('vrf'):
+ wg['vrf'] = c.return_value('vrf')
+
return wg
@@ -210,7 +216,7 @@ def apply(c):
return None
# init wg class
- intfc = WireGuardIf(c['intfc'])
+ intfc = WireGuardIf(c['intfc'], vrf=c['vrf'])
# single interface removal
if c['delete']:
diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py
index 162aaf4..835f28f 100755
--- a/src/conf_mode/interfaces-wireless.py
+++ b/src/conf_mode/interfaces-wireless.py
@@ -836,7 +836,8 @@ default_config_data = {
'ssid' : '',
'type' : 'monitor',
'vif': [],
- 'vif_remove': []
+ 'vif_remove': [],
+ 'vrf': '',
}
def get_conf_file(conf_type, intf):
@@ -1254,6 +1255,10 @@ def get_config():
if conf.exists('wifi-regulatory-domain'):
wifi['country_code'] = conf.return_value('wifi-regulatory-domain')
+ # interface vrf
+ if conf.exists('vrf'):
+ wifi['vrf'] = conf.return_value('vrf')
+
return wifi
@@ -1345,11 +1350,12 @@ def generate(wifi):
return None
def apply(wifi):
- w = EthernetIf(wifi['intf'])
if wifi['deleted']:
+ w = EthernetIf(wifi['intf']])
# delete interface
w.remove()
else:
+ w = EthernetIf(wifi['intf'], wifi['vrf'])
# Some parts e.g. MAC address can't be changed when interface is up
w.set_state('down')
@@ -1430,7 +1436,7 @@ def apply(wifi):
except:
pass
- vlan = e.add_vlan(vif['id'])
+ vlan = e.add_vlan(vif['id'], vrf=wifi['vrf'])
apply_vlan_config(vlan, vif)
# Physical interface is now configured. Proceed by starting hostapd or
diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py
new file mode 100755
index 0000000..9896c7c
--- /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 0000000..49a833f
--- /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)
+
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.