diff options
-rw-r--r-- | debian/control | 1 | ||||
-rw-r--r-- | interface-definitions/interfaces-bridge.xml | 4 | ||||
-rwxr-xr-x | src/conf_mode/interface-bridge.py | 238 |
3 files changed, 111 insertions, 132 deletions
diff --git a/debian/control b/debian/control index 12eb7c309..7b75ca111 100644 --- a/debian/control +++ b/debian/control @@ -29,6 +29,7 @@ Depends: python3, python3-vici (>= 5.7.2), python3-bottle, python3-zmq, + python3-pyroute2, ipaddrcheck, tcpdump, tshark, diff --git a/interface-definitions/interfaces-bridge.xml b/interface-definitions/interfaces-bridge.xml index adb525a46..e92a55d63 100644 --- a/interface-definitions/interfaces-bridge.xml +++ b/interface-definitions/interfaces-bridge.xml @@ -57,8 +57,7 @@ <description>Address aging time for bridge seconds (default 300)</description> </valueHelp> <constraint> - <validator name="numeric" argument="--range 0-0"/> - <validator name="numeric" argument="--range 10-1000000"/> + <validator name="numeric" argument="--range 0-0 --range 10-1000000"/> </constraint> </properties> </leafNode> @@ -117,6 +116,7 @@ <leafNode name="disable"> <properties> <help>Disable this bridge interface</help> + <valueless/> </properties> </leafNode> <leafNode name="forwarding-delay"> diff --git a/src/conf_mode/interface-bridge.py b/src/conf_mode/interface-bridge.py index c5c5bd4ac..d5ef85940 100755 --- a/src/conf_mode/interface-bridge.py +++ b/src/conf_mode/interface-bridge.py @@ -16,13 +16,10 @@ # # -import os -import sys -import copy -import subprocess - -import vyos.configinterface as VyIfconfig - +from os import environ +from copy import deepcopy +from sys import exit +from pyroute2 import IPDB from netifaces import interfaces from vyos.config import Config from vyos import ConfigError @@ -30,44 +27,42 @@ from vyos import ConfigError default_config_data = { 'address': [], 'address_remove': [], - 'aging': '300', - 'arp_cache_timeout_ms': '30000', + 'aging': 300, + 'arp_cache_timeout_ms': 30000, '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', + 'disable_link_detect': 1, + 'forwarding_delay': 14, + 'hello_time': 2, 'igmp_querier': 0, 'intf': '', 'mac' : '', - 'max_age': '20', + 'max_age': 20, 'member': [], 'member_remove': [], - 'priority': '32768', - 'stp': 'off' + 'priority': 32768, + 'stp': 0 } -def subprocess_cmd(command): - process = subprocess.Popen(command,stdout=subprocess.PIPE, shell=True) - proc_stdout = process.communicate()[0].strip() - pass +def freeze(d): + if isinstance(d, dict): + return frozenset((key, freeze(value)) for key, value in d.items()) + elif isinstance(d, list): + return tuple(freeze(value) for value in d) + return d 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) + bridge = deepcopy(default_config_data) conf = Config() # determine tagNode instance try: - bridge['intf'] = os.environ['VYOS_TAGNODE_VALUE'] + bridge['intf'] = environ['VYOS_TAGNODE_VALUE'] except KeyError as E: print("Interface not specified") @@ -85,27 +80,13 @@ def get_config(): # retrieve aging - how long addresses are retained if conf.exists('aging'): - bridge['aging'] = conf.return_value('aging') + bridge['aging'] = int(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 + else: + bridge['description'] = bridge['intf'] # Disable this bridge interface if conf.exists('disable'): @@ -113,15 +94,15 @@ def get_config(): # Ignore link state changes if conf.exists('disable-link-detect'): - bridge['disable_link_detect'] = True + bridge['disable_link_detect'] = 2 # Forwarding delay if conf.exists('forwarding-delay'): - bridge['forwarding_delay'] = conf.return_value('forwarding-delay') + bridge['forwarding_delay'] = int(conf.return_value('forwarding-delay')) # Hello packet advertisment interval if conf.exists('hello-time'): - bridge['hello_time'] = conf.return_value('hello-time') + bridge['hello_time'] = int(conf.return_value('hello-time')) # Enable Internet Group Management Protocol (IGMP) querier if conf.exists('igmp querier'): @@ -129,8 +110,7 @@ def get_config(): # 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) + bridge['arp_cache_timeout_ms'] = int(conf.return_value('ip arp-cache-timeout')) * 1000 # Media Access Control (MAC) address if conf.exists('mac'): @@ -138,21 +118,24 @@ def get_config(): # Interval at which neighbor bridges are removed if conf.exists('max-age'): - bridge['max_age'] = conf.return_value('max-age') + bridge['max_age'] = int(conf.return_value('max-age')) # Determine bridge member interface (currently configured) for intf in conf.list_nodes('member interface'): + # cost and priority initialized with linux defaults + # by reading /sys/devices/virtual/net/br0/brif/eth2/{path_cost,priority} + # after adding interface to bridge after reboot iface = { 'name': intf, - 'cost': '', - 'priority': '' + 'cost': 100, + 'priority': 32 } if conf.exists('member interface {} cost'.format(intf)): - iface['cost'] = conf.return_value('member interface {} cost'.format(intf)) + iface['cost'] = int(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)) + iface['priority'] = int(conf.return_value('member interface {} priority'.format(intf))) bridge['member'].append(iface) @@ -170,11 +153,11 @@ def get_config(): # Priority for this bridge if conf.exists('priority'): - bridge['priority'] = conf.return_value('priority') + bridge['priority'] = int(conf.return_value('priority')) # Enable spanning tree protocol if conf.exists('stp'): - bridge['stp'] = 'on' + bridge['stp'] = 1 return bridge @@ -201,94 +184,89 @@ def generate(bridge): return None def apply(bridge): - cmd = '' - if bridge['deleted']: - # bridges need to be shutdown first - cmd += 'ip link set dev "{}" down'.format(bridge['intf']) - cmd += ' && ' - # delete bridge - cmd += 'brctl delbr "{}"'.format(bridge['intf']) - subprocess_cmd(cmd) + ipdb = IPDB(mode='explicit') + brif = bridge['intf'] + if bridge['deleted']: + try: + # delete bridge interface + with ipdb.interfaces[ brif ] as br: + br.remove() + except: + pass else: - # create bridge if it does not exist - if not os.path.exists("/sys/class/net/" + bridge['intf']): - # create bridge interface - cmd += 'brctl addbr "{}"'.format(bridge['intf']) - cmd += ' && ' - # activate "UP" the interface - cmd += 'ip link set dev "{}" up'.format(bridge['intf']) - cmd += ' && ' - - # set ageing time - cmd += 'brctl setageing "{}" "{}"'.format(bridge['intf'], bridge['aging']) - cmd += ' && ' - - # set bridge forward delay - cmd += 'brctl setfd "{}" "{}"'.format(bridge['intf'], bridge['forwarding_delay']) - cmd += ' && ' - - # set hello time - cmd += 'brctl sethello "{}" "{}"'.format(bridge['intf'], bridge['hello_time']) - cmd += ' && ' - - # set max message age - cmd += 'brctl setmaxage "{}" "{}"'.format(bridge['intf'], bridge['max_age']) - cmd += ' && ' - + try: + # create bridge interface if it not already exists + ipdb.create(kind='bridge', ifname=brif).commit() + except: + pass + + # get handle in bridge interface + br = ipdb.interfaces[brif] + # begin() a transaction prior to make any change + br.begin() + # enable interface + br.up() + # set ageing time - - value is in centiseconds YES! centiseconds! + br.br_ageing_time = bridge['aging'] * 100 + # set bridge forward delay - value is in centiseconds YES! centiseconds! + br.br_forward_delay = bridge['forwarding_delay'] * 100 + # set hello time - value is in centiseconds YES! centiseconds! + br.br_hello_time = bridge['hello_time'] * 100 + # set max message age - value is in centiseconds YES! centiseconds! + br.br_max_age = bridge['max_age'] * 100 # set bridge priority - cmd += 'brctl setbridgeprio "{}" "{}"'.format(bridge['intf'], bridge['priority']) - cmd += ' && ' - + br.br_priority = bridge['priority'] # turn stp on/off - cmd += 'brctl stp "{}" "{}"'.format(bridge['intf'], bridge['stp']) - - for intf in bridge['member_remove']: - # remove interface from bridge - cmd += ' && ' - cmd += 'brctl delif "{}" "{}"'.format(bridge['intf'], 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['intf'] + '/brif/' + intf['name']): - cmd += ' && ' - cmd += 'brctl addif "{}" "{}"'.format(bridge['intf'], intf['name']) - - # set bridge port cost - if intf['cost']: - cmd += ' && ' - cmd += 'brctl setpathcost "{}" "{}" "{}"'.format(bridge['intf'], intf['name'], intf['cost']) - - # set bridge port priority - if intf['priority']: - cmd += ' && ' - cmd += 'brctl setportprio "{}" "{}" "{}"'.format(bridge['intf'], intf['name'], intf['priority']) - - subprocess_cmd(cmd) + br.br_stp_state = bridge['stp'] + # enable or disable IGMP querier + br.br_mcast_querier = bridge['igmp_querier'] + # update interface description used e.g. within SNMP + br.ifalias = bridge['description'] # Change interface MAC address if bridge['mac']: - VyIfconfig.set_mac_address(bridge['intf'], bridge['mac']) + br.set_address = bridge['mac'] - # update interface description used e.g. within SNMP - VyIfconfig.set_description(bridge['intf'], bridge['description']) - - # Ignore link state changes? - VyIfconfig.set_link_detect(bridge['intf'], bridge['disable_link_detect']) - - # enable or disable IGMP querier - VyIfconfig.set_multicast_querier(bridge['intf'], bridge['igmp_querier']) + # remove interface from bridge + for intf in bridge['member_remove']: + br.del_port( intf['name'] ) - # ARP cache entry timeout in seconds - VyIfconfig.set_arp_cache_timeout(bridge['intf'], bridge['arp_cache_timeout_ms']) + # configure bridge member interfaces + for member in bridge['member']: + # add interface + br.add_port(member['name']) # Configure interface address(es) for addr in bridge['address_remove']: - VyIfconfig.remove_interface_address(bridge['intf'], addr) - + br.del_ip(addr) for addr in bridge['address']: - VyIfconfig.add_interface_address(bridge['intf'], addr) + br.add_ip(addr) + + # up/down interface + if bridge['disable']: + br.down() + + # commit change son bridge interface + br.commit() + + # configure additional bridge member options + for member in bridge['member']: + # configure ARP cache timeout in milliseconds + with open('/proc/sys/net/ipv4/neigh/' + member['name'] + '/base_reachable_time_ms', 'w') as f: + f.write(str(bridge['arp_cache_timeout_ms'])) + # ignore link state changes + with open('/proc/sys/net/ipv4/conf/' + member['name'] + '/link_filter', 'w') as f: + f.write(str(bridge['disable_link_detect'])) + + # adjust member port stp attributes + member_if = ipdb.interfaces[ member['name'] ] + member_if.begin() + # set bridge port cost + member_if.brport_cost = member['cost'] + # set bridge port priority + member_if.brport_priority = member['priority'] + member_if.commit() return None @@ -300,4 +278,4 @@ if __name__ == '__main__': apply(c) except ConfigError as e: print(e) - sys.exit(1) + exit(1) |