#!/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 import sys import copy import subprocess import vyos.configinterface as VyIfconfig from vyos.config import Config from vyos import ConfigError default_config_data = { 'address': [], 'address_remove': [], 'aging': '300', 'br_name': '', 'description': '', 'deleted': False, 'dhcp_client_id': '', 'dhcp_hostname': '', 'dhcpv6_parameters_only': False, 'dhcpv6_temporary': False, 'disable': False, 'disable_link_detect': False, 'forwarding_delay': '15', 'hello_time': '2', 'igmp_querier': 0, 'arp_cache_timeout_ms': '30000', 'mac' : '', 'max_age': '20', 'member': [], 'member_remove': [], 'priority': '32768', 'stp': 'off' } def subprocess_cmd(command): process = subprocess.Popen(command,stdout=subprocess.PIPE, shell=True) proc_stdout = process.communicate()[0].strip() pass def diff(first, second): second = set(second) return [item for item in first if item not in second] def get_config(): bridge = copy.deepcopy(default_config_data) conf = Config() # determine tagNode instance try: bridge['br_name'] = os.environ['VYOS_TAGNODE_VALUE'] except KeyError as E: print("Interface not specified") # Check if bridge has been removed if not conf.exists('interfaces bridge ' + bridge['br_name']): bridge['deleted'] = True return bridge # set new configuration level conf.set_level('interfaces bridge ' + bridge['br_name']) # retrieve configured interface addresses if conf.exists('address'): bridge['address'] = conf.return_values('address') # retrieve aging - how long addresses are retained if conf.exists('aging'): bridge['aging'] = conf.return_value('aging') # retrieve interface description if conf.exists('description'): bridge['description'] = conf.return_value('description') # DHCP client identifier if conf.exists('dhcp-options client-id'): bridge['dhcp_client_id'] = conf.return_value('dhcp-options client-id') # DHCP client hostname if conf.exists('dhcp-options host-name'): bridge['dhcp_hostname'] = conf.return_value('dhcp-options host-name') # DHCPv6 acquire only config parameters, no address if conf.exists('dhcpv6-options parameters-only'): bridge['dhcpv6_parameters_only'] = True # DHCPv6 IPv6 "temporary" address if conf.exists('dhcpv6-options temporary'): bridge['dhcpv6_temporary'] = True # Disable this bridge interface if conf.exists('disable'): bridge['disable'] = True # Ignore link state changes if conf.exists('disable-link-detect'): bridge['disable_link_detect'] = True # Forwarding delay if conf.exists('forwarding-delay'): bridge['forwarding_delay'] = conf.return_value('forwarding-delay') # Hello packet advertisment interval if conf.exists('hello-time'): bridge['hello_time'] = conf.return_value('hello-time') # Enable Internet Group Management Protocol (IGMP) querier if conf.exists('igmp querier'): bridge['igmp_querier'] = 1 # ARP cache entry timeout in seconds if conf.exists('ip arp-cache-timeout'): tmp = 1000 * int(conf.return_value('ip arp-cache-timeout')) bridge['arp_cache_timeout_ms'] = str(tmp) # Media Access Control (MAC) address if conf.exists('mac'): bridge['mac'] = conf.return_value('mac') # Interval at which neighbor bridges are removed if conf.exists('max-age'): bridge['max_age'] = conf.return_value('max-age') # Determine bridge member interface (currently configured) for intf in conf.list_nodes('member interface'): iface = { 'name': intf, 'cost': '', 'priority': '' } if conf.exists('member interface {} cost'.format(intf)): iface['cost'] = conf.return_value('member interface {} cost'.format(intf)) if conf.exists('member interface {} priority'.format(intf)): iface['priority'] = conf.return_value('member interface {} priority'.format(intf)) bridge['member'].append(iface) # Determine bridge member interface (currently effective) - to determine which # interfaces is no longer assigend to the bridge and thus can be removed eff_intf = conf.list_effective_nodes('member interface') act_intf = conf.list_nodes('member interface') bridge['member_remove'] = diff(eff_intf, act_intf) # Determine interface addresses (currently effective) - to determine which # address is no longer valid and needs to be removed from the bridge eff_addr = conf.return_effective_values('address') act_addr = conf.return_values('address') bridge['address_remove'] = diff(eff_addr, act_addr) # Priority for this bridge if conf.exists('priority'): bridge['priority'] = conf.return_value('priority') # Enable spanning tree protocol if conf.exists('stp'): bridge['stp'] = 'on' return bridge def verify(bridge): if bridge is None: return None conf = Config() for br in conf.list_nodes('interfaces bridge'): # it makes no sense to verify ourself in this case if br == bridge['br_name']: continue for intf in bridge['member']: tmp = conf.list_nodes('interfaces bridge {} member interface'.format(br)) if intf['name'] in tmp: raise ConfigError('{} can be assigned to any one bridge only'.format(intf['name'])) return None def generate(bridge): if bridge is None: return None return None def apply(bridge): if bridge is None: return None cmd = '' if bridge['deleted']: # bridges need to be shutdown first cmd += 'ip link set dev "{}" down'.format(bridge['br_name']) cmd += ' && ' # delete bridge cmd += 'brctl delbr "{}"'.format(bridge['br_name']) subprocess_cmd(cmd) else: # create bridge if it does not exist if not os.path.exists("/sys/class/net/" + bridge['br_name']): # create bridge interface cmd += 'brctl addbr "{}"'.format(bridge['br_name']) cmd += ' && ' # activate "UP" the interface cmd += 'ip link set dev "{}" up'.format(bridge['br_name']) cmd += ' && ' # set ageing time cmd += 'brctl setageing "{}" "{}"'.format(bridge['br_name'], bridge['aging']) cmd += ' && ' # set bridge forward delay cmd += 'brctl setfd "{}" "{}"'.format(bridge['br_name'], bridge['forwarding_delay']) cmd += ' && ' # set hello time cmd += 'brctl sethello "{}" "{}"'.format(bridge['br_name'], bridge['hello_time']) cmd += ' && ' # set max message age cmd += 'brctl setmaxage "{}" "{}"'.format(bridge['br_name'], bridge['max_age']) cmd += ' && ' # set bridge priority cmd += 'brctl setbridgeprio "{}" "{}"'.format(bridge['br_name'], bridge['priority']) cmd += ' && ' # turn stp on/off cmd += 'brctl stp "{}" "{}"'.format(bridge['br_name'], bridge['stp']) for intf in bridge['member_remove']: # remove interface from bridge cmd += ' && ' cmd += 'brctl delif "{}" "{}"'.format(bridge['br_name'], intf) for intf in bridge['member']: # add interface to bridge # but only if it is not yet member of this bridge if not os.path.exists('/sys/devices/virtual/net/' + bridge['br_name'] + '/brif/' + intf['name']): cmd += ' && ' cmd += 'brctl addif "{}" "{}"'.format(bridge['br_name'], intf['name']) # set bridge port cost if intf['cost']: cmd += ' && ' cmd += 'brctl setpathcost "{}" "{}" "{}"'.format(bridge['br_name'], intf['name'], intf['cost']) # set bridge port priority if intf['priority']: cmd += ' && ' cmd += 'brctl setportprio "{}" "{}" "{}"'.format(bridge['br_name'], intf['name'], intf['priority']) subprocess_cmd(cmd) # Change interface MAC address if bridge['mac']: VyIfconfig.set_mac_address(bridge['br_name'], bridge['mac']) # update interface description used e.g. within SNMP VyIfconfig.set_description(bridge['br_name'], bridge['description']) # Ignore link state changes? VyIfconfig.set_link_detect(bridge['br_name'], bridge['disable_link_detect']) # enable or disable IGMP querier VyIfconfig.set_multicast_querier(bridge['br_name'], bridge['igmp_querier']) # ARP cache entry timeout in seconds VyIfconfig.set_arp_cache_timeout(bridge['br_name'], bridge['arp_cache_timeout_ms']) # Configure interface address(es) for addr in bridge['address_remove']: VyIfconfig.remove_interface_address(bridge['br_name'], addr) for addr in bridge['address']: VyIfconfig.add_interface_address(bridge['br_name'], addr) return None if __name__ == '__main__': try: c = get_config() verify(c) generate(c) apply(c) except ConfigError as e: print(e) sys.exit(1)