diff options
| -rw-r--r-- | Makefile | 1 | ||||
| -rw-r--r-- | interface-definitions/interfaces-vxlan.xml | 151 | ||||
| -rw-r--r-- | python/vyos/ifconfig.py | 67 | ||||
| -rwxr-xr-x | src/conf_mode/interface-dummy.py | 2 | ||||
| -rwxr-xr-x | src/conf_mode/interface-loopback.py | 5 | ||||
| -rwxr-xr-x | src/conf_mode/interface-vxlan.py | 208 | 
6 files changed, 430 insertions, 4 deletions
| @@ -13,6 +13,7 @@ interface_definitions:  	rm -f $(TMPL_DIR)/interfaces/node.def  	rm -f $(TMPL_DIR)/interfaces/bridge/node.tag/ip/node.def  	rm -f $(TMPL_DIR)/interfaces/bonding/node.tag/ip/node.def +	rm -f $(TMPL_DIR)/interfaces/vxlan/node.tag/ip/node.def  	rm -f $(TMPL_DIR)/protocols/node.def  	rm -f $(TMPL_DIR)/protocols/static/node.def  	rm -f $(TMPL_DIR)/system/node.def diff --git a/interface-definitions/interfaces-vxlan.xml b/interface-definitions/interfaces-vxlan.xml new file mode 100644 index 000000000..b06c2860c --- /dev/null +++ b/interface-definitions/interfaces-vxlan.xml @@ -0,0 +1,151 @@ +<?xml version="1.0"?> +<interfaceDefinition> +  <node name="interfaces"> +    <children> +      <tagNode name="vxlan" owner="${vyos_conf_scripts_dir}/interface-vxlan.py"> +        <properties> +          <help>Virtual extensible LAN interface (VXLAN)</help> +          <priority>460</priority> +          <constraint> +            <regex>vxlan[0-9]+$</regex> +          </constraint> +          <constraintErrorMessage>VXLAN interface must be named vxlanN</constraintErrorMessage> +          <valueHelp> +            <format>vxlanN</format> +            <description>VXLAN interface name</description> +          </valueHelp> +        </properties> +        <children> +          <leafNode name="address"> +            <properties> +              <help>IP address</help> +              <valueHelp> +                <format>ipv4net</format> +                <description>IPv4 address and prefix length</description> +              </valueHelp> +              <valueHelp> +                <format>ipv6net</format> +                <description>IPv6 address and prefix length</description> +              </valueHelp> +              <multi/> +              <constraint> +                <validator name="ip-cidr"/> +              </constraint> +            </properties> +          </leafNode> +          <leafNode name="description"> +            <properties> +              <help>Interface description</help> +              <constraint> +                <regex>^.{1,256}$</regex> +              </constraint> +              <constraintErrorMessage>Interface description too long (limit 256 characters)</constraintErrorMessage> +            </properties> +          </leafNode> +          <leafNode name="disable"> +            <properties> +              <help>Disable interface</help> +              <valueless/> +            </properties> +          </leafNode> +          <leafNode name="group"> +            <properties> +              <help>Multicast group address for VXLAN interface</help> +              <valueHelp> +                <format>ipv4</format> +                <description>Multicast group address</description> +              </valueHelp> +              <constraint> +                <validator name="ipv4-address"/> +              </constraint> +            </properties> +          </leafNode> +          <node name="ip"> +            <children> +              <leafNode name="arp-cache-timeout"> +                <properties> +                  <help>ARP cache entry timeout in seconds</help> +                  <valueHelp> +                    <format>1-86400</format> +                    <description>ARP cache entry timout in seconds (default 30)</description> +                  </valueHelp> +                  <constraint> +                    <validator name="numeric" argument="--range 1-86400"/> +                  </constraint> +                  <constraintErrorMessage>ARP cache entry timeout must be between 1 and 86400 seconds</constraintErrorMessage> +                </properties> +              </leafNode> +              <leafNode name="enable-proxy-arp"> +                <properties> +                  <help>Enable proxy-arp on this interface</help> +                  <valueless/> +                </properties> +              </leafNode> +            </children> +          </node> +          <leafNode name="link"> +            <properties> +              <help>Underlay device of VXLAN interface</help> +              <valueHelp> +                <format>interface</format> +                <description>Interface used for VXLAN underlay</description> +              </valueHelp> +              <completionHelp> +                <script>${vyos_completion_dir}/list_interfaces.py</script> +              </completionHelp> +            </properties> +          </leafNode> +          <leafNode name="mtu"> +            <properties> +              <help>Maximum Transmission Unit (MTU)</help> +              <valueHelp> +                <format>1450-9000</format> +                <description>Maximum Transmission Unit</description> +              </valueHelp> +              <constraint> +                <validator name="numeric" argument="--range 1450-9000"/> +              </constraint> +              <constraintErrorMessage>MTU must be between 1450 and 9000</constraintErrorMessage> +            </properties> +          </leafNode> +          <leafNode name="remote"> +            <properties> +              <help>Remote address of VXLAN tunnel</help> +              <valueHelp> +                <format>ipv4</format> +                <description>Remote address of VXLAN tunnel</description> +              </valueHelp> +              <constraint> +                <validator name="ipv4-address"/> +              </constraint> +            </properties> +          </leafNode> +          <leafNode name="port"> +            <properties> +              <help>Destination port of VXLAN tunnel (default: 8472)</help> +              <valueHelp> +                <format>1-65535</format> +                <description>Numeric IP port</description> +              </valueHelp> +              <constraint> +                <validator name="numeric" argument="--range 1-65535"/> +              </constraint> +            </properties> +          </leafNode> +          <leafNode name="vni"> +            <properties> +              <help>Virtual Network Identifier</help> +              <valueHelp> +                <format>0-16777214</format> +                <description>VXLAN virtual network identifier</description> +              </valueHelp> +              <constraint> +                <validator name="numeric" argument="--range 0-16777214"/> +              </constraint> +            </properties> +          </leafNode> +        </children> +      </tagNode> +    </children> +  </node> +</interfaceDefinition> diff --git a/python/vyos/ifconfig.py b/python/vyos/ifconfig.py index 7593f2c91..0479e3672 100644 --- a/python/vyos/ifconfig.py +++ b/python/vyos/ifconfig.py @@ -66,9 +66,6 @@ class Interface:          if not os.path.exists('/sys/class/net/{}'.format(ifname)) and not type:              raise Exception('interface "{}" not found'.format(self._ifname)) -        if os.path.isfile('/tmp/vyos.ifconfig.debug'): -            self._debug = True -          if not os.path.exists('/sys/class/net/{}'.format(self._ifname)):              cmd = 'ip link add dev {} type {}'.format(self._ifname, type)              self._cmd(cmd) @@ -1386,3 +1383,67 @@ class WireGuardIf(Interface):          cmd = "sudo wg set {0} peer {1} remove".format(              self._ifname, str(peerkey))          self._cmd(cmd) + + +class VXLANIf(Interface, ): +    """ +    The VXLAN protocol is a tunnelling protocol designed to solve the +    problem of limited VLAN IDs (4096) in IEEE 802.1q.  With VXLAN the +    size of the identifier is expanded to 24 bits (16777216). + +    VXLAN is described by IETF RFC 7348, and has been implemented by a +    number of vendors.  The protocol runs over UDP using a single +    destination port.  This document describes the Linux kernel tunnel +    device, there is also a separate implementation of VXLAN for +    Openvswitch. + +    Unlike most tunnels, a VXLAN is a 1 to N network, not just point to +    point. A VXLAN device can learn the IP address of the other endpoint +    either dynamically in a manner similar to a learning bridge, or make +    use of statically-configured forwarding entries. + +    For more information please refer to: +    https://www.kernel.org/doc/Documentation/networking/vxlan.txt +    """ +    def __init__(self, ifname, config=''): +        if config: +            self._ifname = ifname + +            if not os.path.exists('/sys/class/net/{}'.format(self._ifname)): +                # we assume that by default a multicast interface is created +                group = 'group {}'.format(config['group']) + +                # if remote host is specified we ignore the multicast address +                if config['remote']: +                    group = 'remote {}'.format(config['remote']) + +                # an underlay device is not always specified +                dev = '' +                if config['dev']: +                    dev = 'dev {}'.format(config['dev']) + +                cmd = 'ip link add {intf} type vxlan id {vni} {grp_rem} {dev} dstport {port}' \ +                       .format(intf=self._ifname, vni=config['vni'], grp_rem=group, dev=dev, port=config['port']) +                self._cmd(cmd) + +        super().__init__(ifname, type='vxlan') + +    @staticmethod +    def get_config(): +        """ +        VXLAN interfaces require a configuration when they are added using +        iproute2. This static method will provide the configuration dictionary +        used by this class. + +        Example: +        >> dict = VXLANIf().get_config() +        """ +        config = { +            'vni': 0, +            'dev': '', +            'group': '', +            'port': 8472, # The Linux implementation of VXLAN pre-dates +                          # the IANA's selection of a standard destination port +            'remote': '' +        } +        return config diff --git a/src/conf_mode/interface-dummy.py b/src/conf_mode/interface-dummy.py index 4a1179672..614fe08db 100755 --- a/src/conf_mode/interface-dummy.py +++ b/src/conf_mode/interface-dummy.py @@ -91,6 +91,8 @@ def apply(dummy):          du.ifalias = dummy['description']          # Configure interface address(es) +        # - not longer required addresses get removed first +        # - newly addresses will be added second          for addr in dummy['address_remove']:              du.del_addr(addr)          for addr in dummy['address']: diff --git a/src/conf_mode/interface-loopback.py b/src/conf_mode/interface-loopback.py index e2df37655..a1a807868 100755 --- a/src/conf_mode/interface-loopback.py +++ b/src/conf_mode/interface-loopback.py @@ -77,7 +77,10 @@ def apply(loopback):          # update interface description used e.g. within SNMP          # update interface description used e.g. within SNMP          lo.ifalias = loopback['description'] -        # configure interface address(es) + +        # Configure interface address(es) +        # - not longer required addresses get removed first +        # - newly addresses will be added second          for addr in loopback['address']:              lo.add_addr(addr) diff --git a/src/conf_mode/interface-vxlan.py b/src/conf_mode/interface-vxlan.py new file mode 100755 index 000000000..59022238e --- /dev/null +++ b/src/conf_mode/interface-vxlan.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019 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 os import environ +from sys import exit +from copy import deepcopy + +from vyos.configdict import list_diff +from vyos.config import Config +from vyos.ifconfig import VXLANIf, Interface +from vyos.interfaces import get_type_of_interface +from vyos import ConfigError +from netifaces import interfaces + +default_config_data = { +    'address': [], +    'address_remove': [], +    'deleted': False, +    'description': '', +    'disable': False, +    'group': '', +    'intf': '', +    'ip_arp_cache_tmo': 30, +    'ip_proxy_arp': 0, +    'link': '', +    'mtu': 1450, +    'remote': '', +    'remote_port': 8472 # The Linux implementation of VXLAN pre-dates +                        # the IANA's selection of a standard destination port +} + + +def get_config(): +    vxlan = deepcopy(default_config_data) +    conf = Config() + +    # determine tagNode instance +    try: +        vxlan['intf'] = environ['VYOS_TAGNODE_VALUE'] +    except KeyError as E: +        print("Interface not specified") + +    # Check if interface has been removed +    if not conf.exists('interfaces vxlan ' + vxlan['intf']): +        vxlan['deleted'] = True +        return vxlan + +    # set new configuration level +    conf.set_level('interfaces vxlan ' + vxlan['intf']) + +    # retrieve configured interface addresses +    if conf.exists('address'): +        vxlan['address'] = conf.return_values('address') + +    # Determine interface addresses (currently effective) - to determine which +    # address is no longer valid and needs to be removed from the interface +    eff_addr = conf.return_effective_values('address') +    act_addr = conf.return_values('address') +    vxlan['address_remove'] = list_diff(eff_addr, act_addr) + +    # retrieve interface description +    if conf.exists('description'): +        vxlan['description'] = conf.return_value('description') + +    # Disable this interface +    if conf.exists('disable'): +        vxlan['disable'] = True + +    # VXLAN multicast grou +    if conf.exists('group'): +        vxlan['group'] = conf.return_value('group') + +    # ARP cache entry timeout in seconds +    if conf.exists('ip arp-cache-timeout'): +        vxlan['ip_arp_cache_tmo'] = int(conf.return_value('ip arp-cache-timeout')) + +    # Enable proxy-arp on this interface +    if conf.exists('ip enable-proxy-arp'): +        vxlan['ip_proxy_arp'] = 1 + +    # VXLAN underlay interface +    if conf.exists('link'): +        vxlan['link'] = conf.return_value('link') + +    # Maximum Transmission Unit (MTU) +    if conf.exists('mtu'): +        vxlan['mtu'] = int(conf.return_value('mtu')) + +    # Remote address of VXLAN tunnel +    if conf.exists('remote'): +        vxlan['remote'] = conf.return_value('remote') + +    # Remote port of VXLAN tunnel +    if conf.exists('port'): +        vxlan['remote_port'] = int(conf.return_value('port')) + +    # Virtual Network Identifier +    if conf.exists('vni'): +        vxlan['vni'] = conf.return_value('vni') + +    return vxlan + + +def verify(vxlan): +    if vxlan['deleted']: +        # bail out early +        return None + +    if vxlan['mtu'] < 1500: +        print('WARNING: RFC7348 recommends VXLAN tunnels preserve a 1500 byte MTU') + +    if vxlan['group'] and not vxlan['link']: +        raise ConfigError('Multicast VXLAN requires an underlaying interface ') + +    if not (vxlan['group'] or vxlan['remote']): +        raise ConfigError('Group or remote must be configured') + +    if not vxlan['vni']: +        raise ConfigError('Must configure VNI for VXLAN') + +    if vxlan['link']: +        # VXLAN adds a 50 byte overhead - we need to check the underlaying MTU +        # if our configured MTU is at least 50 bytes less +        underlay_mtu = int(Interface(vxlan['link']).mtu) +        if underlay_mtu < (vxlan['mtu'] + 50): +            raise ConfigError('VXLAN has a 50 byte overhead, underlaying device ' \ +                              'MTU is to small ({})'.format(underlay_mtu)) + +    return None + + +def generate(vxlan): +    return None + + +def apply(vxlan): +    # Check if the VXLAN interface already exists +    if vxlan['intf'] in interfaces(): +        v = VXLANIf(vxlan['intf']) +        # VXLAN is super picky and the tunnel always needs to be recreated, +        # thus we can simply always delete it first. +        v.remove() + +    if not vxlan['deleted']: +        # VXLAN interface needs to be created on-block +        # instead of passing a ton of arguments, I just use a dict +        # that is managed by vyos.ifconfig +        conf = deepcopy(VXLANIf.get_config()) + +        # Assign VXLAN instance configuration parameters to config dict +        conf['vni'] = vxlan['vni'] +        conf['group'] = vxlan['group'] +        conf['dev'] = vxlan['link'] +        conf['remote'] = vxlan['remote'] +        conf['port'] = vxlan['remote_port'] + +        # Finally create the new interface +        v = VXLANIf(vxlan['intf'], config=conf) +        # update interface description used e.g. by SNMP +        v.ifalias = vxlan['description'] +        # Maximum Transfer Unit (MTU) +        v.mtu = vxlan['mtu'] + +        # configure ARP cache timeout in milliseconds +        v.arp_cache_tmp = vxlan['ip_arp_cache_tmo'] +        # Enable proxy-arp on this interface +        v.proxy_arp = vxlan['ip_proxy_arp'] + +        # Configure interface address(es) +        # - not longer required addresses get removed first +        # - newly addresses will be added second +        for addr in vxlan['address_remove']: +            v.del_addr(addr) +        for addr in vxlan['address']: +            v.add_addr(addr) + +        # As the bond interface is always disabled first when changing +        # parameters we will only re-enable the interface if it is not +        # administratively disabled +        if not vxlan['disable']: +            v.state='up' + +    return None + + +if __name__ == '__main__': +    try: +        c = get_config() +        verify(c) +        generate(c) +        apply(c) +    except ConfigError as e: +        print(e) +        exit(1) | 
