#!/usr/bin/env python3 # # Copyright (C) 2019-2020 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.config import Config from vyos.configdict import list_diff, intf_to_dict, add_to_dict, interface_default_data from vyos.ifconfig import MACVLANIf, Section from vyos.ifconfig_vlan import apply_all_vlans, verify_vlan_config from vyos import ConfigError from vyos import airbag airbag.enable() default_config_data = { **interface_default_data, 'deleted': False, 'intf': '', 'ip_arp_cache_tmo': 30, 'ip_proxy_arp_pvlan': 0, 'source_interface': '', 'source_interface_changed': False, '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'] # Check if interface has been removed cfg_base = ['interfaces', 'pseudo-ethernet', peth['intf']] if not conf.exists(cfg_base): peth['deleted'] = True return peth # set new configuration level conf.set_level(cfg_base) peth, disabled = intf_to_dict(conf, default_config_data) # 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'])) # Enable private VLAN proxy ARP on this interface if conf.exists(['ip', 'proxy-arp-pvlan']): peth['ip_proxy_arp_pvlan'] = 1 # Physical interface if conf.exists(['source-interface']): peth['source_interface'] = conf.return_value(['source-interface']) tmp = conf.return_effective_value(['source-interface']) if tmp != peth['source_interface']: peth['source_interface_changed'] = True # MACvlan mode if conf.exists(['mode']): peth['mode'] = conf.return_value(['mode']) add_to_dict(conf, disabled, peth, 'vif', 'vif') add_to_dict(conf, disabled, peth, 'vif-s', 'vif_s') return peth def verify(peth): if peth['deleted']: if peth['is_bridge_member']: raise ConfigError(( f'Cannot delete interface "{peth["intf"]}" as it is a ' f'member of bridge "{peth["is_bridge_member"]}"!')) return None if not peth['source_interface']: raise ConfigError(( f'Link device must be set for pseudo-ethernet "{peth["intf"]}"')) if not peth['source_interface'] in interfaces(): raise ConfigError(( f'Pseudo-ethernet "{peth["intf"]}" link device does not exist')) if ( peth['is_bridge_member'] and ( peth['address'] or peth['ipv6_eui64_prefix'] or peth['ipv6_autoconf'] ) ): raise ConfigError(( f'Cannot assign address to interface "{peth["intf"]}" ' f'as it is a member of bridge "{peth["is_bridge_member"]}"!')) if peth['vrf']: if peth['vrf'] not in interfaces(): raise ConfigError(f'VRF "{peth["vrf"]}" does not exist') if peth['is_bridge_member']: raise ConfigError(( f'Interface "{peth["intf"]}" cannot be member of VRF ' f'"{peth["vrf"]}" and bridge {peth["is_bridge_member"]} ' f'at the same time!')) # use common function to verify VLAN configuration verify_vlan_config(peth) return None def generate(peth): return None def apply(peth): if peth['deleted']: # delete interface MACVLANIf(peth['intf']).remove() return None # Check if MACVLAN interface already exists. Parameters like the underlaying # source-interface device can not be changed on the fly and the interface # needs to be recreated from the bottom. if peth['intf'] in interfaces(): if peth['source_interface_changed']: MACVLANIf(peth['intf']).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['source_interface'] = peth['source_interface'] 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) # update interface description used e.g. within SNMP p.set_alias(peth['description']) if peth['dhcp_client_id']: p.dhcp.v4.options['client_id'] = peth['dhcp_client_id'] if peth['dhcp_hostname']: p.dhcp.v4.options['hostname'] = peth['dhcp_hostname'] if peth['dhcp_vendor_class_id']: p.dhcp.v4.options['vendor_class_id'] = peth['dhcp_vendor_class_id'] if peth['dhcpv6_prm_only']: p.dhcp.v6.options['dhcpv6_prm_only'] = True if peth['dhcpv6_temporary']: p.dhcp.v6.options['dhcpv6_temporary'] = True if peth['dhcpv6_pd_length']: p.dhcp.v6.options['dhcpv6_pd_length'] = peth['dhcpv6_pd_length'] if peth['dhcpv6_pd_interfaces']: p.dhcp.v6.options['dhcpv6_pd_interfaces'] = peth['dhcpv6_pd_interfaces'] # 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']) # IPv6 accept RA p.set_ipv6_accept_ra(peth['ipv6_accept_ra']) # IPv6 address autoconfiguration p.set_ipv6_autoconf(peth['ipv6_autoconf']) # IPv6 forwarding p.set_ipv6_forwarding(peth['ipv6_forwarding']) # IPv6 Duplicate Address Detection (DAD) tries p.set_ipv6_dad_messages(peth['ipv6_dup_addr_detect']) # assign/remove VRF (ONLY when not a member of a bridge, # otherwise 'nomaster' removes it from it) if not peth['is_bridge_member']: p.set_vrf(peth['vrf']) # Delete old IPv6 EUI64 addresses before changing MAC for addr in peth['ipv6_eui64_prefix_remove']: p.del_ipv6_eui64_address(addr) # Change interface MAC address if peth['mac']: p.set_mac(peth['mac']) # Add IPv6 EUI-based addresses for addr in peth['ipv6_eui64_prefix']: p.add_ipv6_eui64_address(addr) # 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) # re-add ourselves to any bridge we might have fallen out of if peth['is_bridge_member']: p.add_to_bridge(peth['is_bridge_member']) # apply all vlans to interface apply_all_vlans(p, peth) return None if __name__ == '__main__': try: c = get_config() verify(c) generate(c) apply(c) except ConfigError as e: print(e) exit(1)