#!/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.ifconfig_vlan import apply_vlan_config, verify_vlan_config from vyos.configdict import list_diff, vlan_to_dict 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': [], 'vrf': '' } def get_config(): peth = deepcopy(default_config_data) conf = Config() # determine tagNode instance if 'VYOS_TAGNODE_VALUE' not in os.environ: raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') peth['intf'] = os.environ['VYOS_TAGNODE_VALUE'] 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']) # retrieve VRF instance if conf.exists('vrf'): peth['vrf'] = conf.return_value('vrf') # 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'])) if not peth['link'] in interfaces(): raise ConfigError('Pseudo-ethernet source interface does not exist') vrf_name = peth['vrf'] if vrf_name and vrf_name not in interfaces(): raise ConfigError(f'VRF "{vrf_name}" does not exist') # use common function to verify VLAN configuration verify_vlan_config(peth) 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'], **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']) # assign/remove VRF p.set_vrf(peth['vrf']) # 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_admin_state('down') else: p.set_admin_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) # remove no longer required service VLAN interfaces (vif-s) for vif_s in peth['vif_s_remove']: p.del_vlan(vif_s) # create service VLAN interfaces (vif-s) for vif_s in peth['vif_s']: s_vlan = p.add_vlan(vif_s['id'], ethertype=vif_s['ethertype']) apply_vlan_config(s_vlan, vif_s) # remove no longer required client VLAN interfaces (vif-c) # on lower service VLAN interface for vif_c in vif_s['vif_c_remove']: s_vlan.del_vlan(vif_c) # create client VLAN interfaces (vif-c) # on lower service VLAN interface for vif_c in vif_s['vif_c']: c_vlan = s_vlan.add_vlan(vif_c['id']) apply_vlan_config(c_vlan, vif_c) # remove no longer required VLAN interfaces (vif) for vif in peth['vif_remove']: p.del_vlan(vif) # create VLAN interfaces (vif) for vif in peth['vif']: vlan = p.add_vlan(vif['id']) apply_vlan_config(vlan, vif) return None if __name__ == '__main__': try: c = get_config() verify(c) generate(c) apply(c) except ConfigError as e: print(e) exit(1)