diff options
author | Christian Poessinger <christian@poessinger.com> | 2019-09-05 19:35:43 +0200 |
---|---|---|
committer | Christian Poessinger <christian@poessinger.com> | 2019-09-06 12:56:56 +0200 |
commit | 98aafc8f704ef54b6ece514c038b6aea414df734 (patch) | |
tree | 747dd4507c513dd4f5a233294e6ce82b3096d6b8 | |
parent | dcde45826501302fd5fc2fbfcc1c376c2d51ea3a (diff) | |
download | vyos-1x-98aafc8f704ef54b6ece514c038b6aea414df734.tar.gz vyos-1x-98aafc8f704ef54b6ece514c038b6aea414df734.zip |
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'
-rw-r--r-- | Makefile | 1 | ||||
-rw-r--r-- | interface-definitions/interfaces-vxlan.xml | 49 | ||||
-rw-r--r-- | python/vyos/ifconfig.py | 37 | ||||
-rwxr-xr-x | src/conf_mode/interface-vxlan.py | 208 |
4 files changed, 282 insertions, 13 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 index 35a43f92c..b06c2860c 100644 --- a/interface-definitions/interfaces-vxlan.xml +++ b/interface-definitions/interfaces-vxlan.xml @@ -95,6 +95,55 @@ </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> 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 <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) |