From c38097eb62dfe5eb309d90752e3ce611999a48d1 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 6 Sep 2019 12:28:51 +0200 Subject: dummy: loopback: T1580: T1601: synchronize comments --- src/conf_mode/interface-dummy.py | 2 ++ src/conf_mode/interface-loopback.py | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) 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) -- cgit v1.2.3 From dcde45826501302fd5fc2fbfcc1c376c2d51ea3a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 5 Sep 2019 19:35:22 +0200 Subject: Python/ifconfig: T1557: vxlan: initial support via VXLANIf --- interface-definitions/interfaces-vxlan.xml | 102 +++++++++++++++++++++++++++++ python/vyos/ifconfig.py | 56 +++++++++++++++- 2 files changed, 155 insertions(+), 3 deletions(-) create mode 100644 interface-definitions/interfaces-vxlan.xml diff --git a/interface-definitions/interfaces-vxlan.xml b/interface-definitions/interfaces-vxlan.xml new file mode 100644 index 000000000..35a43f92c --- /dev/null +++ b/interface-definitions/interfaces-vxlan.xml @@ -0,0 +1,102 @@ + + + + + + + Virtual extensible LAN interface (VXLAN) + 460 + + vxlan[0-9]+$ + + VXLAN interface must be named vxlanN + + vxlanN + VXLAN interface name + + + + + + IP address + + ipv4net + IPv4 address and prefix length + + + ipv6net + IPv6 address and prefix length + + + + + + + + + + Interface description + + ^.{1,256}$ + + Interface description too long (limit 256 characters) + + + + + Disable interface + + + + + + Multicast group address for VXLAN interface + + ipv4 + Multicast group address + + + + + + + + + + + ARP cache entry timeout in seconds + + 1-86400 + ARP cache entry timout in seconds (default 30) + + + + + ARP cache entry timeout must be between 1 and 86400 seconds + + + + + Enable proxy-arp on this interface + + + + + + + + Underlay device of VXLAN interface + + interface + Interface used for VXLAN underlay + + + + + + + + + + + diff --git a/python/vyos/ifconfig.py b/python/vyos/ifconfig.py index 7593f2c91..bc22478a6 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,56 @@ 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: + 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 dev {intf} type vxlan id {vni} {group} {dev} {port}' + .format(intf=self._ifname, config['vni'], group=group, dev=dev, port=config['port']) + self._cmd(cmd) + + super().__init__(ifname, type='vxlan') + + + @staticmethod + def 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': '', + 'ttl': 16 + } -- cgit v1.2.3 From 98aafc8f704ef54b6ece514c038b6aea414df734 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 5 Sep 2019 19:35:43 +0200 Subject: vxlan: T1636: initial rewrite with XML and Python Tested using: Site 1 (VyOS 1.2.2) ------------------- set interfaces vxlan vxlan100 address '10.10.10.2/24' set interfaces vxlan vxlan100 remote '172.18.201.10' set interfaces vxlan vxlan100 vni '100' Site 2 (rewrite) ---------------- set interfaces vxlan vxlan100 address '10.10.10.1/24' set interfaces vxlan vxlan100 description 'VyOS VXLAN' set interfaces vxlan vxlan100 remote '172.18.202.10' set interfaces vxlan vxlan100 vni '100' --- Makefile | 1 + interface-definitions/interfaces-vxlan.xml | 49 +++++++ python/vyos/ifconfig.py | 37 +++-- src/conf_mode/interface-vxlan.py | 208 +++++++++++++++++++++++++++++ 4 files changed, 282 insertions(+), 13 deletions(-) create mode 100755 src/conf_mode/interface-vxlan.py diff --git a/Makefile b/Makefile index 03b4712fc..d7b3f047d 100644 --- a/Makefile +++ b/Makefile @@ -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 index 35a43f92c..b06c2860c 100644 --- a/interface-definitions/interfaces-vxlan.xml +++ b/interface-definitions/interfaces-vxlan.xml @@ -95,6 +95,55 @@ + + + Maximum Transmission Unit (MTU) + + 1450-9000 + Maximum Transmission Unit + + + + + MTU must be between 1450 and 9000 + + + + + Remote address of VXLAN tunnel + + ipv4 + Remote address of VXLAN tunnel + + + + + + + + + Destination port of VXLAN tunnel (default: 8472) + + 1-65535 + Numeric IP port + + + + + + + + + Virtual Network Identifier + + 0-16777214 + VXLAN virtual network identifier + + + + + + diff --git a/python/vyos/ifconfig.py b/python/vyos/ifconfig.py index bc22478a6..0479e3672 100644 --- a/python/vyos/ifconfig.py +++ b/python/vyos/ifconfig.py @@ -1407,32 +1407,43 @@ class VXLANIf(Interface, ): """ 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']) + dev = 'dev {}'.format(config['dev']) - cmd = 'ip link add dev {intf} type vxlan id {vni} {group} {dev} {port}' - .format(intf=self._ifname, config['vni'], group=group, dev=dev, port=config['port']) + 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. - @staticmethod - def get_config(): - config = { - 'vni': 0, - 'dev': '', - 'group': '', - 'port': 8472 # The Linux implementation of VXLAN pre-dates + 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': '', - 'ttl': 16 - } + 'remote': '' + } + return config 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 . +# + +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) -- cgit v1.2.3