summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--interface-definitions/interfaces-virtaul-ethernet.xml.in47
-rw-r--r--python/vyos/ifconfig/__init__.py1
-rw-r--r--python/vyos/ifconfig/veth.py54
-rw-r--r--python/vyos/util.py4
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_virtual_ethernet.py39
-rwxr-xr-xsrc/conf_mode/interfaces-virtual-ethernet.py117
-rwxr-xr-xsrc/services/vyos-http-api-server2
7 files changed, 262 insertions, 2 deletions
diff --git a/interface-definitions/interfaces-virtaul-ethernet.xml.in b/interface-definitions/interfaces-virtaul-ethernet.xml.in
new file mode 100644
index 000000000..1daa764d4
--- /dev/null
+++ b/interface-definitions/interfaces-virtaul-ethernet.xml.in
@@ -0,0 +1,47 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="interfaces">
+ <children>
+ <tagNode name="virtual-ethernet" owner="${vyos_conf_scripts_dir}/interfaces-virtual-ethernet.py">
+ <properties>
+ <help>Virtual Ethernet (veth) Interface</help>
+ <priority>300</priority>
+ <constraint>
+ <regex>veth[0-9]+</regex>
+ </constraint>
+ <constraintErrorMessage>Virutal Ethernet interface must be named vethN</constraintErrorMessage>
+ <valueHelp>
+ <format>vethN</format>
+ <description>Virtual Ethernet interface name</description>
+ </valueHelp>
+ </properties>
+ <children>
+ #include <include/interface/address-ipv4-ipv6-dhcp.xml.i>
+ #include <include/generic-description.xml.i>
+ #include <include/interface/dhcp-options.xml.i>
+ #include <include/interface/dhcpv6-options.xml.i>
+ #include <include/interface/disable.xml.i>
+ #include <include/interface/vif-s.xml.i>
+ #include <include/interface/vif.xml.i>
+ #include <include/interface/vrf.xml.i>
+ <leafNode name="peer-name">
+ <properties>
+ <help>Virtual ethernet peer interface name</help>
+ <completionHelp>
+ <path>interfaces virtual-ethernet</path>
+ </completionHelp>
+ <valueHelp>
+ <format>txt</format>
+ <description>Name of peer interface</description>
+ </valueHelp>
+ <constraint>
+ <regex>veth[0-9]+</regex>
+ </constraint>
+ <constraintErrorMessage>Virutal Ethernet interface must be named vethN</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/python/vyos/ifconfig/__init__.py b/python/vyos/ifconfig/__init__.py
index c5d83d24b..a18e315db 100644
--- a/python/vyos/ifconfig/__init__.py
+++ b/python/vyos/ifconfig/__init__.py
@@ -36,4 +36,5 @@ from vyos.ifconfig.pppoe import PPPoEIf
from vyos.ifconfig.wireless import WiFiIf
from vyos.ifconfig.l2tpv3 import L2TPv3If
from vyos.ifconfig.macsec import MACsecIf
+from vyos.ifconfig.veth import VethIf
from vyos.ifconfig.wwan import WWANIf
diff --git a/python/vyos/ifconfig/veth.py b/python/vyos/ifconfig/veth.py
new file mode 100644
index 000000000..aafbf226a
--- /dev/null
+++ b/python/vyos/ifconfig/veth.py
@@ -0,0 +1,54 @@
+# Copyright 2022 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/>.
+
+from vyos.ifconfig.interface import Interface
+
+
+@Interface.register
+class VethIf(Interface):
+ """
+ Abstraction of a Linux veth interface
+ """
+ iftype = 'veth'
+ definition = {
+ **Interface.definition,
+ **{
+ 'section': 'virtual-ethernet',
+ 'prefixes': ['veth', ],
+ 'bridgeable': True,
+ },
+ }
+
+ def _create(self):
+ """
+ Create veth interface in OS kernel. Interface is administrative
+ down by default.
+ """
+ # check before create, as we have 2 veth interfaces in our CLI
+ # interface virtual-ethernet veth0 peer-name 'veth1'
+ # interface virtual-ethernet veth1 peer-name 'veth0'
+ #
+ # but iproute2 creates the pair with one command:
+ # ip link add vet0 type veth peer name veth1
+ if self.exists(self.config['peer_name']):
+ return
+
+ # create virtual-ethernet interface
+ cmd = 'ip link add {ifname} type {type}'.format(**self.config)
+ cmd += f' peer name {self.config["peer_name"]}'
+ self._cmd(cmd)
+
+ # interface is always A/D down. It needs to be enabled explicitly
+ self.set_admin_state('down')
diff --git a/python/vyos/util.py b/python/vyos/util.py
index 4df046a36..03809fc83 100644
--- a/python/vyos/util.py
+++ b/python/vyos/util.py
@@ -349,9 +349,11 @@ def colon_separated_to_dict(data_string, uniquekeys=False):
l = l.strip()
if l:
match = re.match(key_value_re, l)
- if match:
+ if match and (len(match.groups()) == 2):
key = match.groups()[0].strip()
value = match.groups()[1].strip()
+ else:
+ raise ValueError(f"""Line "{l}" could not be parsed a colon-separated pair """, l)
if key in data.keys():
if uniquekeys:
raise ValueError("Data string has duplicate keys: {0}".format(key))
diff --git a/smoketest/scripts/cli/test_interfaces_virtual_ethernet.py b/smoketest/scripts/cli/test_interfaces_virtual_ethernet.py
new file mode 100755
index 000000000..b6f7db428
--- /dev/null
+++ b/smoketest/scripts/cli/test_interfaces_virtual_ethernet.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 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
+import unittest
+
+from vyos.ifconfig import Section
+from base_interfaces_test import BasicInterfaceTest
+
+class VEthInterfaceTest(BasicInterfaceTest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ cls._test_dhcp = True
+ cls._base_path = ['interfaces', 'virtual-ethernet']
+
+ cls._options = {
+ 'veth0': ['peer-name veth1'],
+ 'veth1': ['peer-name veth0'],
+ }
+
+ cls._interfaces = list(cls._options)
+ # call base-classes classmethod
+ super(VEthInterfaceTest, cls).setUpClass()
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/src/conf_mode/interfaces-virtual-ethernet.py b/src/conf_mode/interfaces-virtual-ethernet.py
new file mode 100755
index 000000000..21f8cdad3
--- /dev/null
+++ b/src/conf_mode/interfaces-virtual-ethernet.py
@@ -0,0 +1,117 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 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/>.
+
+from sys import exit
+
+from netifaces import interfaces
+from vyos import ConfigError
+from vyos import airbag
+from vyos.config import Config
+from vyos.configdict import get_interface_dict
+from vyos.configverify import verify_address
+from vyos.configverify import verify_bridge_delete
+from vyos.configverify import verify_vrf
+from vyos.ifconfig import VethIf
+
+airbag.enable()
+
+def get_config(config=None):
+ """
+ Retrive CLI config as dictionary. Dictionary can never be empty, as at
+ least the interface name will be added or a deleted flag
+ """
+ if config:
+ conf = config
+ else:
+ conf = Config()
+ base = ['interfaces', 'virtual-ethernet']
+ veth = get_interface_dict(conf, base)
+
+ # We need to know all other veth related interfaces as veth requires a 1:1
+ # mapping for the peer-names. The Linux kernel automatically creates both
+ # interfaces, the local one and the peer-name, but VyOS also needs a peer
+ # interfaces configrued on the CLI so we can assign proper IP addresses etc.
+ conf.set_level(base)
+ veth['other_interfaces'] = conf.get_config_dict([], key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
+
+ return veth
+
+
+def verify(veth):
+ if 'deleted' in veth:
+ verify_bridge_delete(veth)
+ # Prevent to delete veth interface which used for another "vethX peer-name"
+ for iface, iface_config in veth['other_interfaces'].items():
+ if veth['ifname'] in iface_config['peer_name']:
+ ifname = veth['ifname']
+ raise ConfigError(
+ f'Cannot delete "{ifname}" used for "interface {iface} peer-name"'
+ )
+ return None
+
+ verify_vrf(veth)
+ verify_address(veth)
+
+ if 'peer_name' not in veth:
+ raise ConfigError(f'Remote peer name must be set for "{veth["ifname"]}"!')
+
+ peer_name = veth['peer_name']
+ ifname = veth['ifname']
+
+ if veth['peer_name'] not in veth['other_interfaces']:
+ raise ConfigError(f'Used peer-name "{peer_name}" on interface "{ifname}" ' \
+ 'is not configured!')
+
+ if veth['other_interfaces'][peer_name]['peer_name'] != ifname:
+ raise ConfigError(
+ f'Configuration mismatch between "{ifname}" and "{peer_name}"!')
+
+ if peer_name == ifname:
+ raise ConfigError(
+ f'Peer-name "{peer_name}" cannot be the same as interface "{ifname}"!')
+
+ return None
+
+
+def generate(peth):
+ return None
+
+
+def apply(veth):
+ # Check if the Veth interface already exists
+ if 'rebuild_required' in veth or 'deleted' in veth:
+ if veth['ifname'] in interfaces():
+ p = VethIf(veth['ifname'])
+ p.remove()
+
+ if 'deleted' not in veth:
+ p = VethIf(**veth)
+ p.update(veth)
+
+ 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/services/vyos-http-api-server b/src/services/vyos-http-api-server
index ece3aa9b2..a5dccb3b4 100755
--- a/src/services/vyos-http-api-server
+++ b/src/services/vyos-http-api-server
@@ -480,7 +480,7 @@ def configure_op(data: Union[ConfigureModel, ConfigureListModel]):
return success(None)
@app.post("/retrieve")
-def retrieve_op(data: RetrieveModel):
+async def retrieve_op(data: RetrieveModel):
session = app.state.vyos_session
env = session.get_session_env()
config = vyos.config.Config(session_env=env)