diff options
-rw-r--r-- | Makefile | 1 | ||||
-rw-r--r-- | interface-definitions/interfaces-bridge.xml | 245 | ||||
-rw-r--r-- | python/vyos/configinterface.py | 77 | ||||
-rwxr-xr-x | src/conf_mode/interface-bridge.py | 227 |
4 files changed, 550 insertions, 0 deletions
@@ -11,6 +11,7 @@ interface_definitions: # XXX: delete top level node.def's that now live in other packages rm -f $(TMPL_DIR)/firewall/node.def rm -f $(TMPL_DIR)/interfaces/node.def + rm -f $(TMPL_DIR)/interfaces/bridge/node.tag/ip/node.def rm -f $(TMPL_DIR)/protocols/node.def rm -f $(TMPL_DIR)/protocols/static/node.def rm -f $(TMPL_DIR)/system/node.def diff --git a/interface-definitions/interfaces-bridge.xml b/interface-definitions/interfaces-bridge.xml new file mode 100644 index 000000000..a5f2df5b5 --- /dev/null +++ b/interface-definitions/interfaces-bridge.xml @@ -0,0 +1,245 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="interfaces"> + <children> + <tagNode name="bridge" owner="${vyos_conf_scripts_dir}/interface-bridge.py"> + <properties> + <help>Bridge interface name</help> + <priority>310</priority> + <constraint> + <regex>^br[0-9]+$</regex> + </constraint> + <constraintErrorMessage>Bridge interface must be named brN</constraintErrorMessage> + <valueHelp> + <format>brN</format> + <description>Bridge interface name</description> + </valueHelp> + </properties> + <children> + <leafNode name="address"> + <properties> + <help>IP address</help> + <priority>320</priority> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 address and prefix length</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 address and prefix length</description> + </valueHelp> + <valueHelp> + <format>dhcp</format> + <description>Dynamic Host Configuration Protocol</description> + </valueHelp> + <valueHelp> + <format>dhcpv6</format> + <description>Dynamic Host Configuration Protocol for IPv6</description> + </valueHelp> + <multi/> + </properties> + </leafNode> + <leafNode name="aging"> + <properties> + <help>Interval addresses are retained</help> + <valueHelp> + <format>0</format> + <description>Disable retaining address in bridge (always flood)</description> + </valueHelp> + <valueHelp> + <format>10-1000000</format> + <description>Address aging time for bridge seconds (default 300)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-1000000"/> + </constraint> + </properties> + </leafNode> + <leafNode name="description"> + <properties> + <help>Interface description</help> + <constraint> + <regex>^.{1,256}$</regex> + </constraint> + <constraintErrorMessage>Interface description too long (limit 256 characters)</constraintErrorMessage> + </properties> + </leafNode> + <node name="dhcp-options"> + <properties> + <help>DHCP options</help> + </properties> + <children> + <leafNode name="client-id"> + <properties> + <help>DHCP client identifier</help> + </properties> + </leafNode> + <leafNode name="host-name"> + <properties> + <help>DHCP client host name (overrides the system host name)</help> + </properties> + </leafNode> + </children> + </node> + <node name="dhcpv6-options"> + <properties> + <help>DHCPv6 options</help> + <priority>319</priority> + </properties> + <children> + <leafNode name="parameters-only"> + <properties> + <help>Acquire only config parameters, no address</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="temporary"> + <properties> + <help>IPv6 "temporary" address</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <leafNode name="disable-link-detect"> + <properties> + <help>Ignore link state changes</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="disable"> + <properties> + <help>Disable this bridge interface</help> + </properties> + </leafNode> + <leafNode name="forwarding-delay"> + <properties> + <help>Forwarding delay</help> + <valueHelp> + <format>0-200</format> + <description>Spanning Tree Protocol forwarding delay in seconds (default 15)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-200"/> + </constraint> + <constraintErrorMessage>Forwarding delay must be between 0 and 200 seconds</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="hello-time"> + <properties> + <help>Hello packet advertisment interval</help> + <valueHelp> + <format>1-10</format> + <description>Spanning Tree Protocol hello advertisement interval in seconds (default 2)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-10"/> + </constraint> + <constraintErrorMessage>Bridge Hello interval must be between 1 and 10 seconds</constraintErrorMessage> + </properties> + </leafNode> + <node name="igmp-snooping"> + <properties> + <help>IGMP snooping settings</help> + </properties> + <children> + <leafNode name="querier"> + <properties> + <help>Enable or disable IGMP querier</help> + <completionHelp> + <list>enable disable</list> + </completionHelp> + <valueHelp> + <format>enable</format> + <description>Enable IGMP querier</description> + </valueHelp> + <valueHelp> + <format>disable</format> + <description>Disable IGMP querier</description> + </valueHelp> + <constraint> + <regex>(enable|disable)</regex> + </constraint> + </properties> + </leafNode> + </children> + </node> + <node name="ip"> + <children> + <leafNode name="arp-cache-timeout"> + <properties> + <help>ARP cache entry timeout in seconds</help> + <valueHelp> + <format>1-86400</format> + <description>ARP cache entry timout in seconds (default 30)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-86400"/> + </constraint> + <constraintErrorMessage>Bridge max aging value must be between 6 and 86400 seconds</constraintErrorMessage> + </properties> + </leafNode> + </children> + </node> + <leafNode name="mac"> + <properties> + <help>Media Access Control (MAC) address</help> + <valueHelp> + <format>h:h:h:h:h:h</format> + <description>Hardware (MAC) address</description> + </valueHelp> + <constraint> + <validator name="mac-address"/> + </constraint> + </properties> + </leafNode> + <leafNode name="max-age"> + <properties> + <help>Interval at which neighbor bridges are removed</help> + <valueHelp> + <format>1-40</format> + <description>Bridge maximum aging time in seconds (default 20)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-40"/> + </constraint> + <constraintErrorMessage>Bridge max aging value must be between 1 and 40 seconds</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="priority"> + <properties> + <help>Priority for this bridge</help> + <valueHelp> + <format>0-65535</format> + <description>Bridge priority (default 32768)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-65535"/> + </constraint> + <constraintErrorMessage>Bridge priority must be between 0 and 65535 (multiples of 4096)</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="stp"> + <properties> + <help>Enable spanning tree protocol</help> + <completionHelp> + <list>true false</list> + </completionHelp> + <valueHelp> + <format>true</format> + <description>Enable Spanning Tree Protocol</description> + </valueHelp> + <valueHelp> + <format>false</format> + <description>Disable Spanning Tree Protocol</description> + </valueHelp> + <constraint> + <regex>(true|false)</regex> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/python/vyos/configinterface.py b/python/vyos/configinterface.py new file mode 100644 index 000000000..b0d766b9c --- /dev/null +++ b/python/vyos/configinterface.py @@ -0,0 +1,77 @@ +# Copyright 2019 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import os + +def set_description(intf, desc): + """ + Sets the interface secription reported usually by SNMP + """ + with open('/sys/class/net/' + intf + '/ifalias', 'w') as f: + f.write(desc) + + +def set_arp_cache_timeout(intf, tmoMS): + """ + Configure the ARP cache entry timeout in milliseconds + """ + with open('/proc/sys/net/ipv4/neigh/' + intf + '/base_reachable_time_ms', 'w') as f: + f.write(tmoMS) + +def set_multicast_querier(intf, enable): + """ + Sets whether the bridge actively runs a multicast querier or not. When a + bridge receives a 'multicast host membership' query from another network host, + that host is tracked based on the time that the query was received plus the + multicast query interval time. + + use enable=1 to enable or enable=0 to disable + """ + + if int(enable) >= 0 and int(enable) <= 1: + with open('/sys/devices/virtual/net/' + intf + '/bridge/multicast_querier', 'w') as f: + f.write(str(enable)) + else: + raise ValueError("malformed configuration string on interface {}: enable={}".format(intf, enable)) + +def set_link_detect(intf, enable): + """ + 0 - Allow packets to be received for the address on this interface + even if interface is disabled or no carrier. + + 1 - Ignore packets received if interface associated with the incoming + address is down. + + 2 - Ignore packets received if interface associated with the incoming + address is down or has no carrier. + + Kernel Source: Documentation/networking/ip-sysctl.txt + """ + + # Note can't use sysctl it is broken for vif name because of dots + # link_filter values: + # 0 - always receive + # 1 - ignore receive if admin_down + # 2 - ignore receive if admin_down or link down + + with open('/proc/sys/net/ipv4/conf/' + intf + '/link_filter', 'w') as f: + if enable == True or enable == 1: + f.write('2') + if os.path.isfile('/usr/bin/vtysh'): + os.system('/usr/bin/vtysh -c "configure terminal" -c "interface {}" -c "link-detect"'.format(intf)) + else: + f.write('1') + if os.path.isfile('/usr/bin/vtysh'): + os.system('/usr/bin/vtysh -c "configure terminal" -c "interface {}" -c "no link-detect"'.format(intf)) diff --git a/src/conf_mode/interface-bridge.py b/src/conf_mode/interface-bridge.py new file mode 100755 index 000000000..f7f70b15d --- /dev/null +++ b/src/conf_mode/interface-bridge.py @@ -0,0 +1,227 @@ +#!/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': [], + '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', + 'priority': '32768', + 'stp': 'off' +} + +def subprocess_cmd(command): + process = subprocess.Popen(command,stdout=subprocess.PIPE, shell=True) + proc_stdout = process.communicate()[0].strip() + print(proc_stdout) + +def get_config(): + bridge = copy.deepcopy(default_config_data) + conf = Config() + + # determine tagNode instance + try: + bridge['br_name'] = os.environ['VYOS_TAGNODE_VALUE'] + print("Executing script for interface: " + bridge['br_name']) + 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 or disable IGMP querier + if conf.exists('igmp-snooping querier'): + tmp = conf.return_value('igmp-snooping querier') + if tmp == "enable": + 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') + + # Priority for this bridge + if conf.exists('priority'): + bridge['priority'] = conf.return_value('priority') + + # Enable spanning tree protocol + if conf.exists('stp'): + tmp = conf.return_value('stp') + if tmp == "true": + bridge['stp'] = 'on' + + return bridge + +def verify(bridge): + if bridge is None: + return None + + return None + +def generate(bridge): + if bridge is None: + return None + + return None + +def apply(bridge): + if bridge is None: + return None + + if bridge['deleted']: + # bridges need to be shutdown first + os.system("ip link set dev {0} down".format(bridge['br_name'])) + # delete bridge + os.system("brctl delbr {0}".format(bridge['br_name'])) + else: + # create bridge if it does not exist + if not os.path.exists("/sys/class/net/" + bridge['br_name']): + os.system("brctl addbr {0}".format(bridge['br_name'])) + + # assemble bridge configuration + # configuration is passed via subprocess to brctl + cmd = '' + + # set ageing time + cmd += 'brctl setageing {0} {1}'.format(bridge['br_name'], bridge['aging']) + cmd += ' && ' + + # set bridge forward delay + cmd += 'brctl setfd {0} {1}'.format(bridge['br_name'], bridge['forwarding_delay']) + cmd += ' && ' + + # set hello time + cmd += 'brctl sethello {0} {1}'.format(bridge['br_name'], bridge['hello_time']) + cmd += ' && ' + + # set max message age + cmd += 'brctl setmaxage {0} {1}'.format(bridge['br_name'], bridge['max_age']) + cmd += ' && ' + + # set bridge priority + cmd += 'brctl setbridgeprio {0} {1}'.format(bridge['br_name'], bridge['priority']) + cmd += ' && ' + + # turn stp on/off + cmd += 'brctl stp {0} {1}'.format(bridge['br_name'], bridge['stp']) + + subprocess_cmd(cmd) + + # 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']) + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + sys.exit(1) |