diff options
author | Christian Poessinger <christian@poessinger.com> | 2019-08-05 12:29:16 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-08-05 12:29:16 +0200 |
commit | 2afd1163361ea2ad3e94f51eac882007d8f9b7cf (patch) | |
tree | 975f51ccf8c749257d631fab9198c4ed521c06b0 /src | |
parent | b5c1b646beb025bce40cf1a5fb647ab39070da58 (diff) | |
parent | f8cc906b8ef3427b3a8686777d5bc2e3acbe4b7e (diff) | |
download | vyos-1x-2afd1163361ea2ad3e94f51eac882007d8f9b7cf.tar.gz vyos-1x-2afd1163361ea2ad3e94f51eac882007d8f9b7cf.zip |
Merge pull request #96 from c-po/t1156-bridge
Diffstat (limited to 'src')
-rwxr-xr-x | src/completion/list_interfaces.py | 9 | ||||
-rwxr-xr-x | src/conf_mode/bridge_has_members.py | 85 | ||||
-rwxr-xr-x | src/conf_mode/interface-bridge.py | 306 | ||||
-rwxr-xr-x | src/conf_mode/interface-wireguard.py (renamed from src/conf_mode/wireguard.py) | 0 | ||||
-rwxr-xr-x | src/migration-scripts/interfaces/0-to-1 | 81 |
5 files changed, 396 insertions, 85 deletions
diff --git a/src/completion/list_interfaces.py b/src/completion/list_interfaces.py index a4968c52f..66432af19 100755 --- a/src/completion/list_interfaces.py +++ b/src/completion/list_interfaces.py @@ -10,6 +10,7 @@ parser = argparse.ArgumentParser() group = parser.add_mutually_exclusive_group() group.add_argument("-t", "--type", type=str, help="List interfaces of specific type") group.add_argument("-b", "--broadcast", action="store_true", help="List all broadcast interfaces") +group.add_argument("-br", "--bridgeable", action="store_true", help="List all bridgeable interfaces") args = parser.parse_args() @@ -25,6 +26,14 @@ elif args.broadcast: bridge = vyos.interfaces.list_interfaces_of_type("bridge") bond = vyos.interfaces.list_interfaces_of_type("bonding") interfaces = eth + bridge + bond +elif args.bridgeable: + eth = vyos.interfaces.list_interfaces_of_type("ethernet") + bond = vyos.interfaces.list_interfaces_of_type("bonding") + l2tpv3 = vyos.interfaces.list_interfaces_of_type("l2tpv3") + openvpn = vyos.interfaces.list_interfaces_of_type("openvpn") + vxlan = vyos.interfaces.list_interfaces_of_type("vxlan") + wireless = vyos.interfaces.list_interfaces_of_type("wireless") + interfaces = eth + bond + l2tpv3 + openvpn + vxlan + wireless else: interfaces = vyos.interfaces.list_interfaces() diff --git a/src/conf_mode/bridge_has_members.py b/src/conf_mode/bridge_has_members.py deleted file mode 100755 index 712a9cc46..000000000 --- a/src/conf_mode/bridge_has_members.py +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2018 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 sys - -import vyos.config - -if len(sys.argv) < 2: - print("Argument (bridge interface name) is required") - sys.exit(1) -else: - bridge = sys.argv[1] - -c = vyos.config.Config() - -members = [] - - -# Check in ethernet and bonding interfaces -for p in ["interfaces ethernet", "interfaces bonding"]: - intfs = c.list_nodes(p) - for i in intfs: - intf_bridge_path = "{0} {1} bridge-group bridge".format(p, i) - if c.exists(intf_bridge_path): - intf_bridge = c.return_value(intf_bridge_path) - if intf_bridge == bridge: - members.append(i) - # Walk VLANs - for v in c.list_nodes("{0} {1} vif".format(p, i)): - vif_bridge_path = "{0} {1} vif {2} bridge-group bridge".format(p, i, v) - if c.exists(vif_bridge_path): - vif_bridge = c.return_value(vif_bridge_path) - if vif_bridge == bridge: - members.append("{0}.{1}".format(i, v)) - # Walk QinQ interfaces - for vs in c.list_nodes("{0} {1} vif-s".format(p, i)): - vifs_bridge_path = "{0} {1} vif-s {2} bridge-group bridge".format(p, i, vs) - if c.exists(vifs_bridge_path): - vifs_bridge = c.return_value(vifs_bridge_path) - if vifs_bridge == bridge: - members.append("{0}.{1}".format(i, vs)) - for vc in c.list_nodes("{0} {1} vif-s {2} vif-c".format(p, i, vs)): - vifc_bridge_path = "{0} {1} vif-s {2} vif-c {3} bridge-group bridge".format(p, i, vs, vc) - if c.exists(vifc_bridge_path): - vifc_bridge = c.return_value(vifc_bridge_path) - if vifc_bridge == bridge: - members.append("{0}.{1}.{2}".format(i, vs, vc)) - -# Check tunnel interfaces -for t in c.list_nodes("interfaces tunnel"): - tunnel_bridge_path = "interfaces tunnel {0} parameters ip bridge-group bridge".format(t) - if c.exists(tunnel_bridge_path): - intf_bridge = c.return_value(tunnel_bridge_path) - if intf_bridge == bridge: - members.append(t) - -# Check OpenVPN interfaces -for o in c.list_nodes("interfaces openvpn"): - ovpn_bridge_path = "interfaces openvpn {0} bridge-group bridge".format(o) - if c.exists(ovpn_bridge_path): - intf_bridge = c.return_value(ovpn_bridge_path) - if intf_bridge == bridge: - members.append(o) - -if members: - print("Bridge {0} cannot be deleted because some interfaces are configured as its members".format(bridge)) - print("The following interfaces are members of {0}: {1}".format(bridge, " ".join(members))) - sys.exit(1) -else: - sys.exit(0) diff --git a/src/conf_mode/interface-bridge.py b/src/conf_mode/interface-bridge.py new file mode 100755 index 000000000..93eb3839c --- /dev/null +++ b/src/conf_mode/interface-bridge.py @@ -0,0 +1,306 @@ +#!/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) diff --git a/src/conf_mode/wireguard.py b/src/conf_mode/interface-wireguard.py index 8234fad0b..8234fad0b 100755 --- a/src/conf_mode/wireguard.py +++ b/src/conf_mode/interface-wireguard.py diff --git a/src/migration-scripts/interfaces/0-to-1 b/src/migration-scripts/interfaces/0-to-1 new file mode 100755 index 000000000..b8e190f2c --- /dev/null +++ b/src/migration-scripts/interfaces/0-to-1 @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 + +# Change syntax of bridge interface +# - move interface based bridge-group to actual bridge (de-nest) +# - make stp and igmp-snooping nodes valueless +# https://phabricator.vyos.net/T1556 + +import sys + +from vyos.configtree import ConfigTree + +if (len(sys.argv) < 1): + print("Must specify file name!") + sys.exit(1) + +file_name = sys.argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +config = ConfigTree(config_file) +base = ['interfaces', 'bridge'] + +# +# make stp and igmp-snooping nodes valueless +# +for br in config.list_nodes(base): + # STP: check if enabled + stp_val = config.return_value(base + [br, 'stp']) + # STP: delete node with old syntax + config.delete(base + [br, 'stp']) + # STP: set new node - if enabled + if stp_val == "true": + config.set(base + [br, 'stp'], value=None) + + # igmp-snooping: check if enabled + igmp_val = config.return_value(base + [br, 'igmp-snooping', 'querier']) + # igmp-snooping: delete node with old syntax + config.delete(base + [br, 'igmp-snooping', 'querier']) + # igmp-snooping: set new node - if enabled + if igmp_val == "enable": + config.set(base + [br, 'igmp', 'querier'], value=None) + +# +# move interface based bridge-group to actual bridge (de-nest) +# +bridge_types = ['bonding', 'ethernet', 'l2tpv3', 'openvpn', 'vxlan', 'wireless'] +for type in bridge_types: + if not config.exists(['interfaces', type]): + continue + + for intf in config.list_nodes(['interfaces', type]): + # check if bridge-group exists + if config.exists(['interfaces', type, intf, 'bridge-group']): + bridge = config.return_value(['interfaces', type, intf, 'bridge-group', 'bridge']) + + # create new bridge member interface + config.set(base + [bridge, 'member', 'interface', intf]) + # format as tag node to avoid loading problems + config.set_tag(base + [bridge, 'member', 'interface']) + + # cost: migrate if configured + if config.exists(['interfaces', type, intf, 'bridge-group', 'cost']): + cost = config.return_value(['interfaces', type, intf, 'bridge-group', 'cost']) + # set new node + config.set(base + [bridge, 'member', 'interface', intf, 'cost'], value=cost) + + if config.exists(['interfaces', type, intf, 'bridge-group', 'priority']): + priority = config.return_value(['interfaces', type, intf, 'bridge-group', 'priority']) + # set new node + config.set(base + [bridge, 'member', 'interface', intf, 'priority'], value=priority) + + # Delete the old bridge-group assigned to an interface + config.delete(['interfaces', type, intf, 'bridge-group']) + + try: + with open(file_name, 'w') as f: + f.write(config.to_string()) + except OSError as e: + print("Failed to save the modified config: {}".format(e)) + sys.exit(1) |