From c078dac303d1e427f9612f7ff1996800f5076b47 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 24 Nov 2019 10:40:56 +0100 Subject: geneve: T1799: add Generic Network Virtualization Encapsulation --- interface-definitions/interfaces-geneve.xml | 92 +++++++++++++++++ python/vyos/ifconfig.py | 39 +++++++- src/conf_mode/interfaces-geneve.py | 150 ++++++++++++++++++++++++++++ 3 files changed, 280 insertions(+), 1 deletion(-) create mode 100644 interface-definitions/interfaces-geneve.xml create mode 100755 src/conf_mode/interfaces-geneve.py diff --git a/interface-definitions/interfaces-geneve.xml b/interface-definitions/interfaces-geneve.xml new file mode 100644 index 000000000..3a529c363 --- /dev/null +++ b/interface-definitions/interfaces-geneve.xml @@ -0,0 +1,92 @@ + + + + + + + Generic Network Virtualization Encapsulation (GENEVE) + 460 + + gnv[0-9]+$ + + GENEVE interface must be named gnvN + + gnvN + GENEVE 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 + + + + + + Maximum Transmission Unit (MTU) + + 1450-9000 + Maximum Transmission Unit + + + + + MTU must be between 1450 and 9000 + + + + + Remote address of GENEVE tunnel + + ipv4 + Remote address of GENEVE tunnel + + + + + + + + + Virtual Network Identifier + + 0-16777214 + GENEVE virtual network identifier + + + + + + + + + + + diff --git a/python/vyos/ifconfig.py b/python/vyos/ifconfig.py index 279d948b7..f487e6a5b 100644 --- a/python/vyos/ifconfig.py +++ b/python/vyos/ifconfig.py @@ -1609,7 +1609,7 @@ class WireGuardIf(Interface): 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 + 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 @@ -1668,3 +1668,40 @@ class VXLANIf(Interface, ): 'remote': '' } return config + +class GeneveIf(Interface, ): + """ + Geneve: Generic Network Virtualization Encapsulation + + For more information please refer to: + https://tools.ietf.org/html/draft-gross-geneve-00 + https://www.redhat.com/en/blog/what-geneve + 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=''): + if config: + self._ifname = ifname + + if not os.path.exists('/sys/class/net/{}'.format(self._ifname)): + cmd = 'ip link add name {} type geneve id {} remote {}' \ + .format(self._ifname, config['vni'], config['remote']) + self._cmd(cmd) + + super().__init__(ifname, type='geneve') + + @staticmethod + def get_config(): + """ + GENEVE interfaces require a configuration when they are added using + iproute2. This static method will provide the configuration dictionary + used by this class. + + Example: + >> dict = GeneveIf().get_config() + """ + config = { + 'vni': 0, + 'remote': '' + } + return config diff --git a/src/conf_mode/interfaces-geneve.py b/src/conf_mode/interfaces-geneve.py new file mode 100755 index 000000000..94f88ad6d --- /dev/null +++ b/src/conf_mode/interfaces-geneve.py @@ -0,0 +1,150 @@ +#!/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 . + +import os + +from sys import exit +from copy import deepcopy + +from vyos.configdict import list_diff +from vyos.config import Config +from vyos.ifconfig import GeneveIf, Interface +from vyos.interfaces import get_type_of_interface +from vyos import ConfigError +from netifaces import interfaces + +default_config_data = { + 'address': [], + 'deleted': False, + 'description': '', + 'disable': False, + 'intf': '', + 'mtu': 1450, + 'remote': '' +} + +def get_config(): + geneve = deepcopy(default_config_data) + conf = Config() + + # determine tagNode instance + try: + geneve['intf'] = os.environ['VYOS_TAGNODE_VALUE'] + except KeyError as E: + print("Interface not specified") + + # Check if interface has been removed + if not conf.exists('interfaces geneve ' + geneve['intf']): + geneve['deleted'] = True + return geneve + + # set new configuration level + conf.set_level('interfaces geneve ' + geneve['intf']) + + # retrieve configured interface addresses + if conf.exists('address'): + geneve['address'] = conf.return_values('address') + + # retrieve interface description + if conf.exists('description'): + geneve['description'] = conf.return_value('description') + + # Disable this interface + if conf.exists('disable'): + geneve['disable'] = True + + # Maximum Transmission Unit (MTU) + if conf.exists('mtu'): + geneve['mtu'] = int(conf.return_value('mtu')) + + # Remote address of GENEVE tunnel + if conf.exists('remote'): + geneve['remote'] = conf.return_value('remote') + + # Virtual Network Identifier + if conf.exists('vni'): + geneve['vni'] = conf.return_value('vni') + + return geneve + + +def verify(geneve): + if geneve['deleted']: + # bail out early + return None + + if not geneve['remote']: + raise ConfigError('GENEVE remote must be configured') + + if not geneve['vni']: + raise ConfigError('GENEVE VNI must be configured') + + return None + + +def generate(geneve): + return None + + +def apply(geneve): + # Check if GENEVE interface already exists + if geneve['intf'] in interfaces(): + v = GeneveIf(geneve['intf']) + # GENEVE is super picky and the tunnel always needs to be recreated, + # thus we can simply always delete it first. + v.remove() + + if not geneve['deleted']: + # GENEVE 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(GeneveIf.get_config()) + + # Assign GENEVE instance configuration parameters to config dict + conf['vni'] = geneve['vni'] + conf['remote'] = geneve['remote'] + + # Finally create the new interface + v = GeneveIf(geneve['intf'], config=conf) + # update interface description used e.g. by SNMP + v.set_alias(geneve['description']) + # Maximum Transfer Unit (MTU) + v.set_mtu(geneve['mtu']) + + # Configure interface address(es) - no need to implicitly delete the + # old addresses as they have already been removed by deleting the + # interface above + for addr in geneve['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 geneve['disable']: + v.set_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 From 9fc971e6945ed97a88a441053cae6a5f9b57ee00 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 24 Nov 2019 10:41:15 +0100 Subject: geneve: T1799: support bridging --- data/interface-types.json | 3 ++- src/completion/list_interfaces.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/data/interface-types.json b/data/interface-types.json index f1862b882..f174d3c39 100644 --- a/data/interface-types.json +++ b/data/interface-types.json @@ -14,5 +14,6 @@ "wireless": "wlan", "wirelessmodem": "wlm", "input": "ifb", - "pppoe": "pppoe" + "pppoe": "pppoe", + "geneve": "gnv" } diff --git a/src/completion/list_interfaces.py b/src/completion/list_interfaces.py index 47eeaf00c..f336968a6 100755 --- a/src/completion/list_interfaces.py +++ b/src/completion/list_interfaces.py @@ -36,8 +36,9 @@ elif args.bridgeable: wireless = vyos.interfaces.list_interfaces_of_type("wireless") tunnel = vyos.interfaces.list_interfaces_of_type("tunnel") wireless = vyos.interfaces.list_interfaces_of_type("wireless") + geneve = vyos.interfaces.list_interfaces_of_type("geneve") - interfaces = eth + bond + l2tpv3 + openvpn + vxlan + wireless + tunnel + interfaces = eth + bond + l2tpv3 + openvpn + vxlan + tunnel + wireless + geneve elif args.bondable: eth = vyos.interfaces.list_interfaces_of_type("ethernet") -- cgit v1.2.3 From 762e0922eec583c011c5fb834ad8a22971bc96b5 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 24 Nov 2019 10:51:54 +0100 Subject: geneve: T1799: add IPv4 routing parameters --- interface-definitions/interfaces-geneve.xml | 26 ++++++++++++++++++++++++++ src/conf_mode/interfaces-geneve.py | 15 +++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/interface-definitions/interfaces-geneve.xml b/interface-definitions/interfaces-geneve.xml index 3a529c363..4d3897330 100644 --- a/interface-definitions/interfaces-geneve.xml +++ b/interface-definitions/interfaces-geneve.xml @@ -48,6 +48,32 @@ + + + IPv4 routing parameters + + + + + 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 + + + + + Maximum Transmission Unit (MTU) diff --git a/src/conf_mode/interfaces-geneve.py b/src/conf_mode/interfaces-geneve.py index 94f88ad6d..b11ef3594 100755 --- a/src/conf_mode/interfaces-geneve.py +++ b/src/conf_mode/interfaces-geneve.py @@ -32,6 +32,8 @@ default_config_data = { 'description': '', 'disable': False, 'intf': '', + 'ip_arp_cache_tmo': 30, + 'ip_proxy_arp': 0, 'mtu': 1450, 'remote': '' } @@ -66,6 +68,14 @@ def get_config(): if conf.exists('disable'): geneve['disable'] = True + # ARP cache entry timeout in seconds + if conf.exists('ip arp-cache-timeout'): + geneve['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'): + geneve['ip_proxy_arp'] = 1 + # Maximum Transmission Unit (MTU) if conf.exists('mtu'): geneve['mtu'] = int(conf.return_value('mtu')) @@ -124,6 +134,11 @@ def apply(geneve): # Maximum Transfer Unit (MTU) v.set_mtu(geneve['mtu']) + # configure ARP cache timeout in milliseconds + v.set_arp_cache_tmo(geneve['ip_arp_cache_tmo']) + # Enable proxy-arp on this interface + v.set_proxy_arp(geneve['ip_proxy_arp']) + # Configure interface address(es) - no need to implicitly delete the # old addresses as they have already been removed by deleting the # interface above -- cgit v1.2.3 From 229b705e6fe0f8bdf314a22d71eb938c6c3669cb Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 24 Nov 2019 11:47:13 +0100 Subject: geneve: T1799: set minimum MTU size 1500 bytes --- interface-definitions/interfaces-geneve.xml | 4 ++-- src/conf_mode/interfaces-geneve.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/interface-definitions/interfaces-geneve.xml b/interface-definitions/interfaces-geneve.xml index 4d3897330..e65ce6826 100644 --- a/interface-definitions/interfaces-geneve.xml +++ b/interface-definitions/interfaces-geneve.xml @@ -82,9 +82,9 @@ Maximum Transmission Unit - + - MTU must be between 1450 and 9000 + MTU must be between 1500 and 9000 diff --git a/src/conf_mode/interfaces-geneve.py b/src/conf_mode/interfaces-geneve.py index b11ef3594..599be2780 100755 --- a/src/conf_mode/interfaces-geneve.py +++ b/src/conf_mode/interfaces-geneve.py @@ -34,7 +34,7 @@ default_config_data = { 'intf': '', 'ip_arp_cache_tmo': 30, 'ip_proxy_arp': 0, - 'mtu': 1450, + 'mtu': 1500, 'remote': '' } -- cgit v1.2.3 From 31054d01294e314358d35c8177b49cd85a41799f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 24 Nov 2019 12:21:16 +0100 Subject: vxlan: T1636: add misssing "vni" to default_config_data --- src/conf_mode/interfaces-vxlan.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index 1097ae4d0..30ff1755d 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -38,8 +38,9 @@ default_config_data = { 'link': '', 'mtu': 1450, 'remote': '', - 'remote_port': 8472 # The Linux implementation of VXLAN pre-dates - # the IANA's selection of a standard destination port + 'remote_port': 8472, # The Linux implementation of VXLAN pre-dates + # the IANA's selection of a standard destination port + 'vni': '' } def get_config(): -- cgit v1.2.3 From a3c2c690f900b6a967fdff6ddbc743dbda0137f6 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 24 Nov 2019 12:22:19 +0100 Subject: geneve: T1799: add misssing "vni" to default_config_data --- src/conf_mode/interfaces-geneve.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/conf_mode/interfaces-geneve.py b/src/conf_mode/interfaces-geneve.py index 599be2780..ba684b553 100755 --- a/src/conf_mode/interfaces-geneve.py +++ b/src/conf_mode/interfaces-geneve.py @@ -35,7 +35,8 @@ default_config_data = { 'ip_arp_cache_tmo': 30, 'ip_proxy_arp': 0, 'mtu': 1500, - 'remote': '' + 'remote': '', + 'vni': '' } def get_config(): -- cgit v1.2.3 From a6fde4aa02862f51cbd3618ade199c64915469d3 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 24 Nov 2019 20:27:25 +0100 Subject: bridge: T1673: add missing VLAN bridge member migration VLAN interfaces assigned to a bridge as member have not been migrated so far. This was the case for vif, vif-s and vif-c interfaces. The migration code has been generalized in migrate_bridge() so it is re-usable for regular interfaces, vif, vif-s and vif-c interfaces - all now use the same code. --- src/migration-scripts/interfaces/0-to-1 | 171 +++++++++++++++++++------------- 1 file changed, 101 insertions(+), 70 deletions(-) diff --git a/src/migration-scripts/interfaces/0-to-1 b/src/migration-scripts/interfaces/0-to-1 index 96e18b5d5..a67a4a325 100755 --- a/src/migration-scripts/interfaces/0-to-1 +++ b/src/migration-scripts/interfaces/0-to-1 @@ -6,78 +6,109 @@ # https://phabricator.vyos.net/T1556 import sys - from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) -base = ['interfaces', 'bridge'] - -if not config.exists(base): - # Nothing to do - sys.exit(0) -else: - # - # make stp and igmp-snooping nodes valueless - # - for br in config.list_nodes(base): - # STP: check if enabled - if config.exists(base + [br, 'stp']): - stp_val = config.return_value(base + [br, 'stp']) - # STP: delete node with old syntax - config.delete(base + [br, 'stp']) - # STP: set new node - if enabled - if stp_val == "true": - config.set(base + [br, 'stp'], value=None) - - # igmp-snooping: check if enabled - if config.exists(base + [br, 'igmp-snooping', 'querier']): - igmp_val = config.return_value(base + [br, 'igmp-snooping', 'querier']) - # igmp-snooping: delete node with old syntax - config.delete(base + [br, 'igmp-snooping', 'querier']) - # igmp-snooping: set new node - if enabled - if igmp_val == "enable": - config.set(base + [br, 'igmp', 'querier'], value=None) - - # - # move interface based bridge-group to actual bridge (de-nest) - # - bridge_types = ['bonding', 'ethernet', 'l2tpv3', 'openvpn', 'vxlan', 'wireless'] - for type in bridge_types: - if not config.exists(['interfaces', type]): - continue - - for intf in config.list_nodes(['interfaces', type]): - # check if bridge-group exists - if config.exists(['interfaces', type, intf, 'bridge-group']): - bridge = config.return_value(['interfaces', type, intf, 'bridge-group', 'bridge']) - - # create new bridge member interface - config.set(base + [bridge, 'member', 'interface', intf]) - # format as tag node to avoid loading problems - config.set_tag(base + [bridge, 'member', 'interface']) - - # cost: migrate if configured - if config.exists(['interfaces', type, intf, 'bridge-group', 'cost']): - cost = config.return_value(['interfaces', type, intf, 'bridge-group', 'cost']) - # set new node - config.set(base + [bridge, 'member', 'interface', intf, 'cost'], value=cost) - - if config.exists(['interfaces', type, intf, 'bridge-group', 'priority']): - priority = config.return_value(['interfaces', type, intf, 'bridge-group', 'priority']) - # set new node - config.set(base + [bridge, 'member', 'interface', intf, 'priority'], value=priority) - - # Delete the old bridge-group assigned to an interface - config.delete(['interfaces', type, intf, 'bridge-group']) +def migrate_bridge(config, tree, intf): + # check if bridge-group exists + tree_bridge = tree + ['bridge-group'] + if config.exists(tree_bridge): + bridge = config.return_value(tree_bridge + ['bridge']) + # create new bridge member interface + config.set(base + [bridge, 'member', 'interface', intf]) + # format as tag node to avoid loading problems + config.set_tag(base + [bridge, 'member', 'interface']) + + # cost: migrate if configured + tree_cost = tree + ['bridge-group', 'cost'] + if config.exists(tree_cost): + cost = config.return_value(tree_cost) + # set new node + config.set(base + [bridge, 'member', 'interface', intf, 'cost'], value=cost) + + # priority: migrate if configured + tree_priority = tree + ['bridge-group', 'priority'] + if config.exists(tree_priority): + priority = config.return_value(tree_priority) + # set new node + config.set(base + [bridge, 'member', 'interface', intf, 'priority'], value=priority) + + # Delete the old bridge-group assigned to an interface + config.delete(tree_bridge) + + +if __name__ == '__main__': + if (len(sys.argv) < 1): + print("Must specify file name!") + sys.exit(1) + + file_name = sys.argv[1] + + with open(file_name, 'r') as f: + config_file = f.read() + + config = ConfigTree(config_file) + base = ['interfaces', 'bridge'] + + if not config.exists(['interfaces', 'bridge']): + # Nothing to do + sys.exit(0) + else: + # + # make stp and igmp-snooping nodes valueless + # + for br in config.list_nodes(base): + # STP: check if enabled + if config.exists(base + [br, 'stp']): + stp_val = config.return_value(base + [br, 'stp']) + # STP: delete node with old syntax + config.delete(base + [br, 'stp']) + # STP: set new node - if enabled + if stp_val == "true": + config.set(base + [br, 'stp'], value=None) + + # igmp-snooping: check if enabled + if config.exists(base + [br, 'igmp-snooping', 'querier']): + igmp_val = config.return_value(base + [br, 'igmp-snooping', 'querier']) + # igmp-snooping: delete node with old syntax + config.delete(base + [br, 'igmp-snooping', 'querier']) + # igmp-snooping: set new node - if enabled + if igmp_val == "enable": + config.set(base + [br, 'igmp', 'querier'], value=None) + + # + # move interface based bridge-group to actual bridge (de-nest) + # + bridge_types = ['bonding', 'ethernet', 'l2tpv3', 'openvpn', 'vxlan', 'wireless'] + for type in bridge_types: + if not config.exists(['interfaces', type]): + continue + + for interface in config.list_nodes(['interfaces', type]): + # check if bridge-group exists + bridge_group = ['interfaces', type, interface] + if config.exists(bridge_group + ['bridge-group']): + migrate_bridge(config, bridge_group, interface) + + # We also need to migrate VLAN interfaces + vlan_base = ['interfaces', type, interface, 'vif'] + if config.exists(vlan_base): + for vlan in config.list_nodes(vlan_base): + intf = "{}.{}".format(interface, vlan) + migrate_bridge(config, vlan_base + [vlan], intf) + + # And then we have service VLANs (vif-s) interfaces + vlan_base = ['interfaces', type, interface, 'vif-s'] + if config.exists(vlan_base): + for vif_s in config.list_nodes(vlan_base): + intf = "{}.{}".format(interface, vif_s) + migrate_bridge(config, vlan_base + [vif_s], intf) + + # Every service VLAN can have multiple customer VLANs (vif-c) + vlan_c = ['interfaces', type, interface, 'vif-s', vif_s, 'vif-c'] + if config.exists(vlan_c): + for vif_c in config.list_nodes(vlan_c): + intf = "{}.{}.{}".format(interface, vif_s, vif_c) + migrate_bridge(config, vlan_c + [vif_c], intf) try: with open(file_name, 'w') as f: -- cgit v1.2.3 From 5238f0970219639e988bc31beb7db27b43f94e6e Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 24 Nov 2019 20:31:22 +0100 Subject: bridge: T1673: re-use "base" variable --- src/migration-scripts/interfaces/0-to-1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/migration-scripts/interfaces/0-to-1 b/src/migration-scripts/interfaces/0-to-1 index a67a4a325..ee4d6b82c 100755 --- a/src/migration-scripts/interfaces/0-to-1 +++ b/src/migration-scripts/interfaces/0-to-1 @@ -49,7 +49,7 @@ if __name__ == '__main__': config = ConfigTree(config_file) base = ['interfaces', 'bridge'] - if not config.exists(['interfaces', 'bridge']): + if not config.exists(base): # Nothing to do sys.exit(0) else: -- cgit v1.2.3