diff options
author | Christian Poessinger <christian@poessinger.com> | 2020-02-13 07:59:42 +0100 |
---|---|---|
committer | Christian Poessinger <christian@poessinger.com> | 2020-02-13 08:01:48 +0100 |
commit | e2016b7229e727002e2fada805d14941855f5e7d (patch) | |
tree | 41e5f4e2bdac5eebe8a0dfdd3eadcb6a39c88c97 | |
parent | c624e198974790e5d7fa9a158aa879fe75068090 (diff) | |
download | vyos-1x-e2016b7229e727002e2fada805d14941855f5e7d.tar.gz vyos-1x-e2016b7229e727002e2fada805d14941855f5e7d.zip |
macvlan: T1635: migrate pseudo-ethernet interface definition to XML/Python
-rw-r--r-- | interface-definitions/interfaces-pseudo-ethernet.xml.in | 82 | ||||
-rw-r--r-- | python/vyos/ifconfig.py | 38 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-pseudo-ethernet.py | 317 |
3 files changed, 437 insertions, 0 deletions
diff --git a/interface-definitions/interfaces-pseudo-ethernet.xml.in b/interface-definitions/interfaces-pseudo-ethernet.xml.in new file mode 100644 index 000000000..c2dea438a --- /dev/null +++ b/interface-definitions/interfaces-pseudo-ethernet.xml.in @@ -0,0 +1,82 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="interfaces"> + <children> + <tagNode name="pseudo-ethernet" owner="${vyos_conf_scripts_dir}/interfaces-pseudo-ethernet.py"> + <properties> + <help>Pseudo Ethernet</help> + <priority>319</priority> + <constraint> + <regex>peth[0-9]+$</regex> + </constraint> + <constraintErrorMessage>Pseudo Ethernet interface must be named pethN</constraintErrorMessage> + <valueHelp> + <format>pethN</format> + <description>Pseudo Ethernet interface name</description> + </valueHelp> + </properties> + <children> + #include <include/address-ipv4-ipv6-dhcp.xml.i> + #include <include/interface-description.xml.i> + #include <include/dhcp-dhcpv6-options.xml.i> + #include <include/interface-disable-link-detect.xml.i> + #include <include/interface-disable.xml.i> + <node name="ip"> + <children> + #include <include/interface-arp-cache-timeout.xml.i> + #include <include/interface-disable-arp-filter.xml.i> + #include <include/interface-enable-arp-accept.xml.i> + #include <include/interface-enable-arp-announce.xml.i> + #include <include/interface-enable-arp-ignore.xml.i> + #include <include/interface-enable-proxy-arp.xml.i> + #include <include/interface-proxy-arp-pvlan.xml.i> + </children> + </node> + <leafNode name="link"> + <properties> + <help>Lower link device</help> + <valueHelp> + <format>interface</format> + <description>Interface used for VXLAN underlay</description> + </valueHelp> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces.py -t ethernet</script> + </completionHelp> + </properties> + </leafNode> + #include <include/interface-mac.xml.i> + <leafNode name="mode"> + <properties> + <help>Receive mode (default: private)</help> + <completionHelp> + <list>private vepa bridge passthru</list> + </completionHelp> + <valueHelp> + <format>private</format> + <description>No communication with other pseudo-devices</description> + </valueHelp> + <valueHelp> + <format>vepa</format> + <description>Virtual Ethernet Port Aggregator reflective relay</description> + </valueHelp> + <valueHelp> + <format>bridge</format> + <description>Simple bridge between pseudo-devices</description> + </valueHelp> + <valueHelp> + <format>passthru</format> + <description>Promicious mode passthrough of underlying device</description> + </valueHelp> + <constraint> + <regex>(private|vepa|bridge|passthru)</regex> + </constraint> + <constraintErrorMessage>mode must be private, vepa, bridge or passthru</constraintErrorMessage> + </properties> + </leafNode> + #include <include/vif-s.xml.i> + #include <include/vif.xml.i> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/python/vyos/ifconfig.py b/python/vyos/ifconfig.py index 90b8fc169..52eab5717 100644 --- a/python/vyos/ifconfig.py +++ b/python/vyos/ifconfig.py @@ -1325,6 +1325,44 @@ class EthernetIf(VLANIf): cmd = '/sbin/ethtool -K {} ufo {}'.format(self._ifname, state) return self._cmd(cmd) +class MACVLANIf(VLANIf): + """ + Abstraction of a Linux MACvlan interface + """ + def __init__(self, ifname, config=''): + self._ifname = ifname + + if not os.path.exists('/sys/class/net/{}'.format(self._ifname)) and config: + cmd = 'ip link add {intf} link {link} type macvlan mode {mode}' \ + .format(intf=self._ifname, link=config['link'], mode=config['mode']) + self._cmd(cmd) + + super().__init__(ifname, type='macvlan') + + @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 = MACVLANIf().get_config() + """ + config = { + 'address': '', + 'link': 0, + 'mode': '' + } + return config + + def set_mode(self, mode): + """ + """ + + cmd = 'ip link set dev {} type macvlan mode {}'.format(self._ifname, mode) + return self._cmd(cmd) + class BondIf(VLANIf): """ diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py new file mode 100755 index 000000000..864e28936 --- /dev/null +++ b/src/conf_mode/interfaces-pseudo-ethernet.py @@ -0,0 +1,317 @@ +#!/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/>. + +import os + +from copy import deepcopy +from sys import exit +from netifaces import interfaces + +from vyos.ifconfig import MACVLANIf +from vyos.configdict import list_diff +from vyos.config import Config +from vyos import ConfigError + +default_config_data = { + 'address': [], + 'address_remove': [], + 'description': '', + 'deleted': False, + 'dhcp_client_id': '', + 'dhcp_hostname': '', + 'dhcp_vendor_class_id': '', + 'dhcpv6_prm_only': False, + 'dhcpv6_temporary': False, + 'disable': False, + 'disable_link_detect': 1, + 'ip_arp_cache_tmo': 30, + 'ip_disable_arp_filter': 1, + 'ip_enable_arp_accept': 0, + 'ip_enable_arp_announce': 0, + 'ip_enable_arp_ignore': 0, + 'ip_proxy_arp': 0, + 'ip_proxy_arp_pvlan': 0, + 'intf': '', + 'link': '', + 'link_changed': False, + 'mac': '', + 'mode': 'private', + 'vif_s': [], + 'vif_s_remove': [], + 'vif': [], + 'vif_remove': [] +} + +def get_config(): + peth = deepcopy(default_config_data) + conf = Config() + + # determine tagNode instance + try: + peth['intf'] = os.environ['VYOS_TAGNODE_VALUE'] + except KeyError as E: + print("Interface not specified") + + cfg_base = ['interfaces', 'pseudo-ethernet', peth['intf']] + + # Check if interface has been removed + if not conf.exists(cfg_base): + peth['deleted'] = True + return peth + + # set new configuration level + conf.set_level(cfg_base) + + # retrieve configured interface addresses + if conf.exists(['address']): + peth['address'] = conf.return_values(['address']) + + # get interface addresses (currently effective) - to determine which + # address is no longer valid and needs to be removed + eff_addr = conf.return_effective_values(['address']) + peth['address_remove'] = list_diff(eff_addr, peth['address']) + + # retrieve interface description + if conf.exists(['description']): + peth['description'] = conf.return_value(['description']) + + # get DHCP client identifier + if conf.exists(['dhcp-options', 'client-id']): + peth['dhcp_client_id'] = conf.return_value(['dhcp-options', 'client-id']) + + # DHCP client host name (overrides the system host name) + if conf.exists(['dhcp-options', 'host-name']): + peth['dhcp_hostname'] = conf.return_value(['dhcp-options', 'host-name']) + + # DHCP client vendor identifier + if conf.exists(['dhcp-options', 'vendor-class-id']): + peth['dhcp_vendor_class_id'] = conf.return_value(['dhcp-options', 'vendor-class-id']) + + # DHCPv6 only acquire config parameters, no address + if conf.exists(['dhcpv6-options parameters-only']): + peth['dhcpv6_prm_only'] = True + + # DHCPv6 temporary IPv6 address + if conf.exists(['dhcpv6-options temporary']): + peth['dhcpv6_temporary'] = True + + # disable interface + if conf.exists(['disable']): + peth['disable'] = True + + # ignore link state changes + if conf.exists(['disable-link-detect']): + peth['disable_link_detect'] = 2 + + # ARP cache entry timeout in seconds + if conf.exists(['ip', 'arp-cache-timeout']): + peth['ip_arp_cache_tmo'] = int(conf.return_value(['ip', 'arp-cache-timeout'])) + + # ARP filter configuration + if conf.exists(['ip', 'disable-arp-filter']): + peth['ip_disable_arp_filter'] = 0 + + # ARP enable accept + if conf.exists(['ip', 'enable-arp-accept']): + peth['ip_enable_arp_accept'] = 1 + + # ARP enable announce + if conf.exists(['ip', 'enable-arp-announce']): + peth['ip_enable_arp_announce'] = 1 + + # ARP enable ignore + if conf.exists(['ip', 'enable-arp-ignore']): + peth['ip_enable_arp_ignore'] = 1 + + # Enable proxy-arp on this interface + if conf.exists(['ip', 'enable-proxy-arp']): + peth['ip_proxy_arp'] = 1 + + # Enable private VLAN proxy ARP on this interface + if conf.exists(['ip', 'proxy-arp-pvlan']): + peth['ip_proxy_arp_pvlan'] = 1 + + # Lower link device + if conf.exists(['link']): + peth['link'] = conf.return_value(['link']) + tmp = conf.return_effective_value(['link']) + if tmp != peth['link']: + peth['link_changed'] = True + + # Media Access Control (MAC) address + if conf.exists(['mac']): + peth['mac'] = conf.return_value(['mac']) + + # MACvlan mode + if conf.exists(['mode']): + peth['mode'] = conf.return_value(['mode']) + + # re-set configuration level to parse new nodes + conf.set_level(cfg_base) + # get vif-s interfaces (currently effective) - to determine which vif-s + # interface is no longer present and needs to be removed + eff_intf = conf.list_effective_nodes('vif-s') + act_intf = conf.list_nodes('vif-s') + peth['vif_s_remove'] = list_diff(eff_intf, act_intf) + + if conf.exists('vif-s'): + for vif_s in conf.list_nodes('vif-s'): + # set config level to vif-s interface + conf.set_level(cfg_base + ['vif-s', vif_s]) + peth['vif_s'].append(vlan_to_dict(conf)) + + # re-set configuration level to parse new nodes + conf.set_level(cfg_base) + # Determine vif interfaces (currently effective) - to determine which + # vif interface is no longer present and needs to be removed + eff_intf = conf.list_effective_nodes('vif') + act_intf = conf.list_nodes('vif') + peth['vif_remove'] = list_diff(eff_intf, act_intf) + + if conf.exists('vif'): + for vif in conf.list_nodes('vif'): + # set config level to vif interface + conf.set_level(cfg_base + ['vif', vif]) + peth['vif'].append(vlan_to_dict(conf)) + + + return peth + +def verify(peth): + if peth['deleted']: + return None + + if not peth['link']: + raise ConfigError('Link device must be set for virtual ethernet {}'.format(peth['intf'])) + + return None + +def generate(peth): + return None + +def apply(peth): + + p = '' + if peth['deleted']: + # delete interface + p = MACVLANIf(peth['intf']) + p.remove() + return None + + elif peth['link_changed']: + # Check if MACVLAN interface already exists. Parameters like the + # underlaying link device can not be changed on the fly and the + # interface needs to be recreated from the bottom. + # + # link_changed also means - the interface was not present in the + # beginning and is newly created + if peth['intf'] in interfaces(): + p = MACVLANIf(peth['intf']) + p.remove() + + # MACVLAN 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(MACVLANIf.get_config()) + + # Assign MACVLAN instance configuration parameters to config dict + conf['link'] = peth['link'] + conf['mode'] = peth['mode'] + + # It is safe to "re-create" the interface always, there is a sanity check + # that the interface will only be create if its non existent + p = MACVLANIf(peth['intf'], config=conf) + else: + p = MACVLANIf(peth['intf']) + + # update interface description used e.g. within SNMP + p.set_alias(peth['description']) + + # get DHCP config dictionary and update values + opt = p.get_dhcp_options() + + if peth['dhcp_client_id']: + opt['client_id'] = peth['dhcp_client_id'] + + if peth['dhcp_hostname']: + opt['hostname'] = peth['dhcp_hostname'] + + if peth['dhcp_vendor_class_id']: + opt['vendor_class_id'] = peth['dhcp_vendor_class_id'] + + # store DHCP config dictionary - used later on when addresses are aquired + p.set_dhcp_options(opt) + + # get DHCPv6 config dictionary and update values + opt = p.get_dhcpv6_options() + + if peth['dhcpv6_prm_only']: + opt['dhcpv6_prm_only'] = True + + if peth['dhcpv6_temporary']: + opt['dhcpv6_temporary'] = True + + # store DHCPv6 config dictionary - used later on when addresses are aquired + p.set_dhcpv6_options(opt) + + # ignore link state changes + p.set_link_detect(peth['disable_link_detect']) + # configure ARP cache timeout in milliseconds + p.set_arp_cache_tmo(peth['ip_arp_cache_tmo']) + # configure ARP filter configuration + p.set_arp_filter(peth['ip_disable_arp_filter']) + # configure ARP accept + p.set_arp_accept(peth['ip_enable_arp_accept']) + # configure ARP announce + p.set_arp_announce(peth['ip_enable_arp_announce']) + # configure ARP ignore + p.set_arp_ignore(peth['ip_enable_arp_ignore']) + # Enable proxy-arp on this interface + p.set_proxy_arp(peth['ip_proxy_arp']) + # Enable private VLAN proxy ARP on this interface + p.set_proxy_arp_pvlan(peth['ip_proxy_arp_pvlan']) + + # Change interface MAC address + if peth['mac']: + p.set_mac(peth['mac']) + + # Change interface mode + p.set_mode(peth['mode']) + + # Enable/Disable interface + if peth['disable']: + p.set_state('down') + else: + p.set_state('up') + + # Configure interface address(es) + # - not longer required addresses get removed first + # - newly addresses will be added second + for addr in peth['address_remove']: + p.del_addr(addr) + for addr in peth['address']: + p.add_addr(addr) + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) |