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 | |
| 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
| -rw-r--r-- | Makefile | 1 | ||||
| -rw-r--r-- | interface-definitions/interfaces-bridge.xml | 269 | ||||
| -rw-r--r-- | interface-definitions/interfaces-wireguard.xml | 2 | ||||
| -rw-r--r-- | python/vyos/configinterface.py | 153 | ||||
| -rw-r--r-- | python/vyos/validate.py | 78 | ||||
| -rwxr-xr-x | scripts/build-command-templates | 2 | ||||
| -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 | 
11 files changed, 879 insertions, 107 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..93f374c80 --- /dev/null +++ b/interface-definitions/interfaces-bridge.xml @@ -0,0 +1,269 @@ +<?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> +              <completionHelp> +                <list>dhcp dhcpv6</list> +              </completionHelp> +              <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> +              <constraint> +                <validator name="cidr"/> +                <regex>(dhcp|dhcpv6)</regex> +              </constraint> +              <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-0"/> +                <validator name="numeric" argument="--range 10-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"> +            <properties> +              <help>Internet Group Management Protocol (IGMP) settings</help> +            </properties> +            <children> +              <leafNode name="querier"> +                <properties> +                  <help>Enable IGMP querier</help> +                  <valueless/> +                </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> +          <node name="member"> +            <properties> +              <help>Bridge member interfaces</help> +            </properties> +            <children> +              <tagNode name="interface"> +                <properties> +                  <help>Member interface name</help> +                  <completionHelp> +                    <script>${vyos_completion_dir}/list_interfaces.py --bridgeable</script> +                  </completionHelp> +                </properties> +                <children> +                  <leafNode name="cost"> +                    <properties> +                      <help>Bridge port cost</help> +                      <valueHelp> +                        <format>1-65535</format> +                        <description>Path cost value for Spanning Tree Protocol</description> +                      </valueHelp> +                      <constraint> +                        <validator name="numeric" argument="--range 1-65535"/> +                      </constraint> +                      <constraintErrorMessage>Path cost value must be between 1 and 65535</constraintErrorMessage> +                    </properties> +                  </leafNode> +                  <leafNode name="priority"> +                    <properties> +                      <help>Bridge port priority</help> +                      <valueHelp> +                        <format>0-63</format> +                        <description>Bridge port priority</description> +                      </valueHelp> +                      <constraint> +                        <validator name="numeric" argument="--range 1-63"/> +                      </constraint> +                      <constraintErrorMessage>Port priority value must be between 0 and 63</constraintErrorMessage> +                    </properties> +                  </leafNode> +                </children> +              </tagNode> +            </children> +          </node> +          <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> +              <valueless/> +            </properties> +          </leafNode> +        </children> +      </tagNode> +    </children> +  </node> +</interfaceDefinition> diff --git a/interface-definitions/interfaces-wireguard.xml b/interface-definitions/interfaces-wireguard.xml index 9cfcd32ee..c0102ea54 100644 --- a/interface-definitions/interfaces-wireguard.xml +++ b/interface-definitions/interfaces-wireguard.xml @@ -2,7 +2,7 @@  <interfaceDefinition>    <node name="interfaces">      <children> -      <tagNode name="wireguard" owner="${vyos_conf_scripts_dir}/wireguard.py"> +      <tagNode name="wireguard" owner="${vyos_conf_scripts_dir}/interface-wireguard.py">          <properties>            <help>WireGuard interface name</help>            <priority>459</priority> <!-- subsequent ones may be removed, just make sure ethernet ifs are present --> diff --git a/python/vyos/configinterface.py b/python/vyos/configinterface.py new file mode 100644 index 000000000..0f5b0842c --- /dev/null +++ b/python/vyos/configinterface.py @@ -0,0 +1,153 @@ +# 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 +import vyos.validate + +def validate_mac_address(addr): +    # a mac address consits out of 6 octets +    octets = len(addr.split(':')) +    if octets != 6: +        raise ValueError('wrong number of MAC octets: {} '.format(octets)) + +    # validate against the first mac address byte if it's a multicast address +    if int(addr.split(':')[0]) & 1: +        raise ValueError('{} is a multicast MAC address'.format(addr)) + +    # overall mac address is not allowed to be 00:00:00:00:00:00 +    if sum(int(i, 16) for i in addr.split(':')) == 0: +        raise ValueError('00:00:00:00:00:00 is not a valid MAC address') + +    # check for VRRP mac address +    if addr.split(':')[0] == '0' and addr.split(':')[1] == '0' and addr.split(':')[2] == '94' and addr.split(':')[3] == '0' and addr.split(':')[4] == '1': +        raise ValueError('{} is a VRRP MAC address') + +    pass + +def set_mac_address(intf, addr): +    """ +    Configure interface mac address using iproute2 command +    """ +    validate_mac_address(addr) + +    os.system('ip link set {} address {}'.format(intf, addr)) +    pass + +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) + +    pass + +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) + +    pass + +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)) + +    pass + +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)) + +    pass + +def add_interface_address(intf, addr): +    """ +    Configure an interface IPv4/IPv6 address +    """ +    if addr == "dhcp": +        os.system('/opt/vyatta/sbin/vyatta-interfaces.pl --dev="{}" --dhcp=start'.format(intf)) +    elif addr == "dhcpv6": +        os.system('/opt/vyatta/sbin/vyatta-dhcpv6-client.pl --start -ifname "{}"'.format(intf)) +    elif vyos.validate.is_ipv4(addr): +        if not vyos.validate.is_intf_addr_assigned(intf, addr): +            print("Assigning {} to {}".format(addr, intf)) +            os.system('sudo ip -4 addr add "{}" broadcast + dev "{}"'.format(addr, intf)) +    elif vyos.validate.is_ipv6(addr): +        if not vyos.validate.is_intf_addr_assigned(intf, addr): +            print("Assigning {} to {}".format(addr, intf)) +            os.system('sudo ip -6 addr add "{}" dev "{}"'.format(addr, intf)) +    else: +        raise ConfigError('{} is not a valid interface address'.format(addr)) + +    pass + +def remove_interface_address(intf, addr): +    """ +    Remove IPv4/IPv6 address from given interface +    """ + +    if addr == "dhcp": +        os.system('/opt/vyatta/sbin/vyatta-interfaces.pl --dev="{}" --dhcp=stop'.format(intf)) +    elif addr == "dhcpv6": +        os.system('/opt/vyatta/sbin/vyatta-dhcpv6-client.pl --stop -ifname "{}"'.format(intf)) +    elif vyos.validate.is_ipv4(addr): +        os.system('ip -4 addr del "{}" dev "{}"'.format(addr, intf)) +    elif vyos.validate.is_ipv6(addr): +        os.system('ip -6 addr del "{}" dev "{}"'.format(addr, intf)) +    else: +        raise ConfigError('{} is not a valid interface address'.format(addr)) + +    pass diff --git a/python/vyos/validate.py b/python/vyos/validate.py index 8def0a510..97a401423 100644 --- a/python/vyos/validate.py +++ b/python/vyos/validate.py @@ -18,32 +18,33 @@ import ipaddress  def is_ipv4(addr):      """ -    Check addr if it is an IPv4 address/network. - -    Return True/False +    Check addr if it is an IPv4 address/network. Returns True/False      """ -    if ipaddress.ip_network(addr).version == 4: + +    # With the below statement we can check for IPv4 networks and host +    # addresses at the same time +    if ipaddress.ip_address(addr.split(r'/')[0]).version == 4:          return True      else:          return False  def is_ipv6(addr):      """ -    Check addr if it is an IPv6 address/network. - -    Return True/False +    Check addr if it is an IPv6 address/network. Returns True/False      """ -    if ipaddress.ip_network(addr).version == 6: + +    # With the below statement we can check for IPv4 networks and host +    # addresses at the same time +    if ipaddress.ip_network(addr.split(r'/')[0]).version == 6:          return True      else:          return False -def is_addr_assigned(addr): +def is_intf_addr_assigned(intf, addr):      """ -    Verify if the given IPv4/IPv6 address is assigned to any interface on this -    system. - -    Return True/False +    Verify if the given IPv4/IPv6 address is assigned to specific interface. +    It can check both a single IP address (e.g. 192.0.2.1 or a assigned CIDR +    address 192.0.2.1/24.      """      # determine IP version (AF_INET or AF_INET6) depending on passed address @@ -51,15 +52,52 @@ def is_addr_assigned(addr):      if is_ipv6(addr):          addr_type = netifaces.AF_INET6 -    for interface in netifaces.interfaces(): -        # check if the requested address type is configured at all -        if addr_type in netifaces.ifaddresses(interface).keys(): -            # Check every IP address on this interface for a match -            for ip in netifaces.ifaddresses(interface)[addr_type]: -                # Check if it matches to the address requested -                if ip['addr'] == addr: +    # check if the requested address type is configured at all +    try: +        netifaces.ifaddresses(intf) +    except ValueError as e: +        print(e) +        return False + +    if addr_type in netifaces.ifaddresses(intf).keys(): +        # Check every IP address on this interface for a match +        for ip in netifaces.ifaddresses(intf)[addr_type]: +            # Check if it matches to the address requested +            # If passed address contains a '/' indicating a normalized IP +            # address we have to take this into account, too +            if r'/' in addr: +                prefixlen = '' +                if is_ipv6(addr): +                     # Note that currently expanded netmasks are not supported. That means +                     # 2001:db00::0/24 is a valid argument while 2001:db00::0/ffff:ff00:: not. +                     # see https://docs.python.org/3/library/ipaddress.html +                     bits =  bin( int(ip['netmask'].replace(':',''), 16) ).count('1') +                     prefixlen = '/' + str(bits) + +                else: +                     prefixlen = '/' + str(ipaddress.IPv4Network('0.0.0.0/' + ip['netmask']).prefixlen) + +                # construct temporary variable holding IPv6 address and netmask +                # in CIDR notation +                tmp = ip['addr'] + prefixlen +                if addr == tmp:                      return True +            elif ip['addr'] == addr: +                    return True + +    return False + +def is_addr_assigned(addr): +    """ +    Verify if the given IPv4/IPv6 address is assigned to any interface +    """ + +    for intf in netifaces.interfaces(): +        tmp = is_intf_addr_assigned(intf, addr) +        if tmp == True: +            return True +      return False  def is_subnet_connected(subnet, primary=False): diff --git a/scripts/build-command-templates b/scripts/build-command-templates index a7312f77b..ba80eadb2 100755 --- a/scripts/build-command-templates +++ b/scripts/build-command-templates @@ -227,7 +227,7 @@ def make_node_def(props):      if "owner" in props:          if "tag" in props: -            node_def += "end: sudo sh -c \"VALUE='$VAR(@)' {0}\"\n".format(props["owner"]) +            node_def += "end: sudo sh -c \"VYOS_TAGNODE_VALUE='$VAR(@)' {0}\"\n".format(props["owner"])          else:              node_def += "end: sudo sh -c \"{0}\"\n".format(props["owner"]) 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)  | 
