diff options
-rw-r--r-- | Makefile | 1 | ||||
-rw-r--r-- | interface-definitions/interfaces-bonding.xml | 673 | ||||
-rw-r--r-- | python/vyos/ifconfig.py | 65 | ||||
-rwxr-xr-x | src/conf_mode/interface-bonding.py | 547 | ||||
-rwxr-xr-x | src/conf_mode/interface-bridge.py | 7 | ||||
-rwxr-xr-x | src/migration-scripts/interfaces/1-to-2 | 63 |
6 files changed, 1346 insertions, 10 deletions
@@ -12,6 +12,7 @@ interface_definitions: 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)/interfaces/bonding/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-bonding.xml b/interface-definitions/interfaces-bonding.xml new file mode 100644 index 000000000..d4c3bb704 --- /dev/null +++ b/interface-definitions/interfaces-bonding.xml @@ -0,0 +1,673 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="interfaces"> + <children> + <tagNode name="bonding" owner="${vyos_conf_scripts_dir}/interface-bonding.py"> + <properties> + <help>Bonding interface name</help> + <priority>315</priority> + <constraint> + <regex>bond[0-9]+$</regex> + </constraint> + <constraintErrorMessage>Bonding interface must be named bondN</constraintErrorMessage> + <valueHelp> + <format>bondN</format> + <description>Bonding 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="ip-cidr"/> + <regex>(dhcp|dhcpv6)</regex> + </constraint> + <multi/> + </properties> + </leafNode> + <node name="arp-monitor"> + <properties> + <help>ARP link monitoring parameters</help> + </properties> + <children> + <leafNode name="interval"> + <properties> + <help>ARP link monitoring interval</help> + <valueHelp> + <format>0-4294967295</format> + <description>Specifies the ARP link monitoring frequency in milliseconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + </properties> + </leafNode> + <leafNode name="target"> + <properties> + <help>IP address used for ARP monitoring</help> + <valueHelp> + <format>ipv4</format> + <description>Network Time Protocol (NTP) IPv4 address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + <multi/> + </properties> + </leafNode> + </children> + </node> + <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> + <valueless/> + </properties> + </leafNode> + <leafNode name="hash-policy"> + <properties> + <help>Bonding transmit hash policy</help> + <completionHelp> + <list>layer2 layer2+3 layer3+4</list> + </completionHelp> + <valueHelp> + <format>layer2</format> + <description>use MAC addresses to generate the hash (802.3ad, default)</description> + </valueHelp> + <valueHelp> + <format>layer2+3</format> + <description>combine MAC address and IP address to make hash</description> + </valueHelp> + <valueHelp> + <format>layer3+4</format> + <description>combine IP address and port to make hash</description> + </valueHelp> + <constraint> + <regex>(layer2\\+3|layer3\\+4|layer2)</regex> + </constraint> + <constraintErrorMessage>hash-policy must be layer2 layer2+3 or layer3+4</constraintErrorMessage> + </properties> + </leafNode> + <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> + <leafNode name="enable-proxy-arp"> + <properties> + <help>Enable proxy-arp on this interface</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="proxy-arp-pvlan"> + <properties> + <help>Enable private VLAN proxy ARP on this interface</help> + <valueless/> + </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="mode"> + <properties> + <help>Bonding mode</help> + <completionHelp> + <list>802.3ad active-backup broadcast round-robin transmit-load-balance adaptive-load-balance xor-hash</list> + </completionHelp> + <valueHelp> + <format>802.3ad</format> + <description>IEEE 802.3ad Dynamic link aggregation (Default)</description> + </valueHelp> + <valueHelp> + <format>active-backup</format> + <description>Fault tolerant: only one slave in the bond is active</description> + </valueHelp> + <valueHelp> + <format>broadcast</format> + <description>Fault tolerant: transmits everything on all slave interfaces</description> + </valueHelp> + <valueHelp> + <format>round-robin</format> + <description>Load balance: transmit packets in sequential order</description> + </valueHelp> + <valueHelp> + <format>transmit-load-balance</format> + <description>Load balance: adapts based on transmit load and speed</description> + </valueHelp> + <valueHelp> + <format>adaptive-load-balance</format> + <description>Load balance: adapts based on transmit and receive plus ARP</description> + </valueHelp> + <valueHelp> + <format>xor-hash</format> + <description>Distribute based on MAC address</description> + </valueHelp> + <constraint> + <regex>(802.3ad|active-backup|broadcast|round-robin|transmit-load-balance|adaptive-load-balance|xor-hash)</regex> + </constraint> + <constraintErrorMessage>mode must be 802.3ad, active-backup, broadcast, round-robin, transmit-load-balance, adaptive-load-balance, or xor</constraintErrorMessage> + </properties> + </leafNode> + <node name="member"> + <properties> + <help>Bridge member interfaces</help> + </properties> + <children> + <leafNode name="interface"> + <properties> + <help>Member interface name</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces.py --bondable</script> + </completionHelp> + <multi/> + </properties> + </leafNode> + </children> + </node> + <leafNode name="mtu"> + <properties> + <help>Maximum Transmission Unit (MTU)</help> + <valueHelp> + <format>68-9000</format> + <description>Maximum Transmission Unit</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 68-9000"/> + </constraint> + <constraintErrorMessage>MTU must be between 68 and 9000</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="primary"> + <properties> + <help>Primary device interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces.py --bondable</script> + </completionHelp> + </properties> + </leafNode> + <tagNode name="vif-s"> + <properties> + <help>QinQ TAG-S Virtual Local Area Network (VLAN) ID</help> + <constraint> + <validator name="numeric" argument="--range 0-4094"/> + </constraint> + <constraintErrorMessage>VLAN ID must be between 0 and 4094</constraintErrorMessage> + </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="ip-cidr"/> + <regex>(dhcp|dhcpv6)</regex> + </constraint> + <multi/> + </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> + <valueless/> + </properties> + </leafNode> + <leafNode name="ethertype"> + <properties> + <help>Set Ethertype</help> + <completionHelp> + <list>0x88A8 0x8100</list> + </completionHelp> + <valueHelp> + <format>0x88A8</format> + <description>802.1ad</description> + </valueHelp> + <valueHelp> + <format>0x8100</format> + <description>802.1q</description> + </valueHelp> + <constraint> + <regex>(0x88A8|0x8100)</regex> + </constraint> + <constraintErrorMessage>Ethertype must be 0x88A8 or 0x8100</constraintErrorMessage> + </properties> + </leafNode> + <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="mtu"> + <properties> + <help>Maximum Transmission Unit (MTU)</help> + <valueHelp> + <format>68-9000</format> + <description>Maximum Transmission Unit</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 68-9000"/> + </constraint> + <constraintErrorMessage>MTU must be between 68 and 9000</constraintErrorMessage> + </properties> + </leafNode> + <tagNode name="vif-c"> + <properties> + <help>QinQ TAG-C Virtual Local Area Network (VLAN) ID</help> + <constraint> + <validator name="numeric" argument="--range 0-4094"/> + </constraint> + <constraintErrorMessage>VLAN ID must be between 0 and 4094</constraintErrorMessage> + </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="ip-cidr"/> + <regex>(dhcp|dhcpv6)</regex> + </constraint> + <multi/> + </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> + <valueless/> + </properties> + </leafNode> + <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="mtu"> + <properties> + <help>Maximum Transmission Unit (MTU)</help> + <valueHelp> + <format>68-9000</format> + <description>Maximum Transmission Unit</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 68-9000"/> + </constraint> + <constraintErrorMessage>MTU must be between 68 and 9000</constraintErrorMessage> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </tagNode> + <tagNode name="vif"> + <properties> + <help>Virtual Local Area Network (VLAN) ID</help> + <constraint> + <validator name="numeric" argument="--range 0-4094"/> + </constraint> + <constraintErrorMessage>VLAN ID must be between 0 and 4094</constraintErrorMessage> + </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="ip-cidr"/> + <regex>(dhcp|dhcpv6)</regex> + </constraint> + <multi/> + </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> + <valueless/> + </properties> + </leafNode> + <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="mtu"> + <properties> + <help>Maximum Transmission Unit (MTU)</help> + <valueHelp> + <format>68-9000</format> + <description>Maximum Transmission Unit</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 68-9000"/> + </constraint> + <constraintErrorMessage>MTU must be between 68 and 9000</constraintErrorMessage> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/python/vyos/ifconfig.py b/python/vyos/ifconfig.py index 1d03e4e74..449923f09 100644 --- a/python/vyos/ifconfig.py +++ b/python/vyos/ifconfig.py @@ -66,7 +66,7 @@ class Interface: self._debug = True if not os.path.exists('/sys/class/net/{}'.format(self._ifname)): - cmd = 'ip link add dev "{}" type "{}"'.format(self._ifname, type) + cmd = 'ip link add dev {} type {}'.format(self._ifname, type) self._cmd(cmd) # per interface DHCP config files @@ -87,7 +87,9 @@ class Interface: def remove(self): """ - Remove system interface + Remove interface from operating system. Removing the interface + deconfigures all assigned IP addresses and clear possible DHCP(v6) + client processes. Example: >>> from vyos.ifconfig import Interface @@ -102,12 +104,12 @@ class Interface: # NOTE (Improvement): # after interface removal no other commands should be allowed # to be called and instead should raise an Exception: - cmd = 'ip link del dev "{}"'.format(self._ifname) + cmd = 'ip link del dev {}'.format(self._ifname) self._cmd(cmd) def _cmd(self, command): - self._debug_msg("{:<6} '{}'".format('cmd', command)) + self._debug_msg("cmd '{}'".format(command)) process = subprocess.Popen(command,stdout=subprocess.PIPE, shell=True) proc_stdout = process.communicate()[0].strip() @@ -124,7 +126,7 @@ class Interface: with open(filename, 'r') as f: value = f.read().rstrip('\n') - self._debug_msg("{:<6} '{}' < '{}'".format('read', value, filename)) + self._debug_msg("read '{}' < '{}'".format(value, filename)) return value @@ -132,7 +134,7 @@ class Interface: """ Provide a single primitive w/ error checking for writing to sysfs. """ - self._debug_msg("{:<6} '{}' > '{}'".format('write', value, filename)) + self._debug_msg("write '{}' > '{}'".format(value, filename)) with open(filename, 'w') as f: f.write(str(value)) @@ -214,7 +216,7 @@ class Interface: # Assemble command executed on system. Unfortunately there is no way # of altering the MAC address via sysfs - cmd = 'ip link set dev "{}" address "{}"'.format(self._ifname, mac) + cmd = 'ip link set dev {} address {}'.format(self._ifname, mac) self._cmd(cmd) @@ -358,7 +360,7 @@ class Interface: # Assemble command executed on system. Unfortunately there is no way # to up/down an interface via sysfs - cmd = 'ip link set dev "{}" "{}"'.format(self._ifname, state) + cmd = 'ip link set dev {} {}'.format(self._ifname, state) self._cmd(cmd) @property @@ -922,7 +924,7 @@ class BridgeIf(Interface): >>> BridgeIf('br0').add_port('eth0') >>> BridgeIf('br0').add_port('eth1') """ - cmd = 'ip link set dev "{}" master "{}"'.format(interface, self._ifname) + cmd = 'ip link set dev {} master {}'.format(interface, self._ifname) self._cmd(cmd) @@ -934,7 +936,7 @@ class BridgeIf(Interface): >>> from vyos.ifconfig import Interface >>> BridgeIf('br0').del_port('eth1') """ - cmd = 'ip link set dev "{}" nomaster'.format(interface) + cmd = 'ip link set dev {} nomaster'.format(interface) self._cmd(cmd) @@ -964,10 +966,53 @@ class BridgeIf(Interface): .format(self._ifname, interface), priority) + class EthernetIf(Interface): def __init__(self, ifname, type=None): super().__init__(ifname, type) + def add_vlan(self, vlan_id, ethertype=''): + """ + A virtual LAN (VLAN) is any broadcast domain that is partitioned and + isolated in a computer network at the data link layer (OSI layer 2). + Use this function to create a new VLAN interface on a given physical + interface. + + This function creates both 802.1q and 802.1ad (Q-in-Q) interfaces. Proto + parameter is used to indicate VLAN type. + + A new object of type EthernetIf is returned once the interface has been + created. + """ + vlan_ifname = self._ifname + '.' + str(vlan_id) + if not os.path.exists('/sys/class/net/{}'.format(vlan_ifname)): + self._vlan_id = int(vlan_id) + + if ethertype: + self._ethertype = ethertype + ethertype='proto {}'.format(ethertype) + + # create interface in the system + cmd = 'ip link add link {intf} name {intf}.{vlan} type vlan {proto} id {vlan}'.format(intf=self._ifname, vlan=self._vlan_id, proto=ethertype) + self._cmd(cmd) + + # return new object mapping to the newly created interface + # we can now work on this object for e.g. IP address setting + # or interface description and so on + return EthernetIf(vlan_ifname) + + + def del_vlan(self, vlan_id): + """ + Remove VLAN interface from operating system. Removing the interface + deconfigures all assigned IP addresses and clear possible DHCP(v6) + client processes. + """ + vlan_ifname = self._ifname + '.' + str(vlan_id) + tmp = EthernetIf(vlan_ifname) + tmp.remove() + + class BondIf(EthernetIf): """ The Linux bonding driver provides a method for aggregating multiple network diff --git a/src/conf_mode/interface-bonding.py b/src/conf_mode/interface-bonding.py new file mode 100755 index 000000000..03d28954d --- /dev/null +++ b/src/conf_mode/interface-bonding.py @@ -0,0 +1,547 @@ +#!/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 + +from copy import deepcopy +from sys import exit +from netifaces import interfaces + +from vyos.ifconfig import BondIf, EthernetIf +from vyos.config import Config +from vyos import ConfigError + +default_config_data = { + 'address': [], + 'address_remove': [], + 'arp_mon_intvl': 0, + 'arp_mon_tgt': [], + 'description': '', + 'deleted': False, + 'dhcp_client_id': '', + 'dhcp_hostname': '', + 'dhcpv6_prm_only': False, + 'dhcpv6_temporary': False, + 'disable': False, + 'disable_link_detect': 1, + 'hash_policy': 'layer2', + 'ip_arp_cache_tmo': 30, + 'ip_proxy_arp': 0, + 'ip_proxy_arp_pvlan': 0, + 'intf': '', + 'mac': '', + 'mode': '802.3ad', + 'member': [], + 'mtu': 1500, + 'primary': '', + 'vif_s': [], + 'vif_s_remove': [], + 'vif': [], + 'vif_remove': [] +} + +def diff(first, second): + second = set(second) + return [item for item in first if item not in second] + +def get_bond_mode(mode): + if mode == 'round-robin': + return 'balance-rr' + elif mode == 'active-backup': + return 'active-backup' + elif mode == 'xor-hash': + return 'balance-xor' + elif mode == 'broadcast': + return 'broadcast' + elif mode == '802.3ad': + return '802.3ad' + elif mode == 'transmit-load-balance': + return 'balance-tlb' + elif mode == 'adaptive-load-balance': + return 'balance-alb' + else: + raise ConfigError('invalid bond mode "{}"'.format(mode)) + +def get_ethertype(ethertype_val): + if ethertype_val == '0x88A8': + return '802.1ad' + elif ethertype_val == '0x8100': + return '802.1q' + else: + raise ConfigError('invalid ethertype "{}"'.format(ethertype_val)) + + +def vlan_to_dict(conf): + """ + Common used function which will extract VLAN related information from config + and represent the result as Python dictionary. + + Function call's itself recursively if a vif-s/vif-c pair is detected. + """ + vlan = { + 'id': conf.get_level().split()[-1], # get the '100' in 'interfaces bonding bond0 vif-s 100' + 'address': [], + 'address_remove': [], + 'description': '', + 'dhcp_client_id': '', + 'dhcp_hostname': '', + 'dhcpv6_prm_only': False, + 'dhcpv6_temporary': False, + 'disable': False, + 'disable_link_detect': 1, + 'mac': '', + 'mtu': 1500 + } + # retrieve configured interface addresses + if conf.exists('address'): + vlan['address'] = conf.return_values('address') + + # Determine interface addresses (currently effective) - to determine which + # address is no longer valid and needs to be removed from the bond + eff_addr = conf.return_effective_values('address') + act_addr = conf.return_values('address') + vlan['address_remove'] = diff(eff_addr, act_addr) + + # retrieve interface description + if conf.exists('description'): + vlan['description'] = conf.return_value('description') + + # get DHCP client identifier + if conf.exists('dhcp-options client-id'): + vlan['dhcp_client_id'] = conf.return_value('dhcp-options client-id') + + # DHCP client host name (overrides the system host name) + if conf.exists('dhcp-options host-name'): + vlan['dhcp_hostname'] = conf.return_value('dhcp-options host-name') + + # DHCPv6 only acquire config parameters, no address + if conf.exists('dhcpv6-options parameters-only'): + vlan['dhcpv6_prm_only'] = conf.return_value('dhcpv6-options parameters-only') + + # DHCPv6 temporary IPv6 address + if conf.exists('dhcpv6-options temporary'): + vlan['dhcpv6_temporary'] = conf.return_value('dhcpv6-options temporary') + + # ignore link state changes + if conf.exists('disable-link-detect'): + vlan['disable_link_detect'] = 2 + + # disable bond interface + if conf.exists('disable'): + vlan['disable'] = True + + # Media Access Control (MAC) address + if conf.exists('mac'): + vlan['mac'] = conf.return_value('mac') + + # Maximum Transmission Unit (MTU) + if conf.exists('mtu'): + vlan['mtu'] = int(conf.return_value('mtu')) + + # ethertype is mandatory on vif-s nodes and only exists here! + # check if this is a vif-s node at all: + if conf.get_level().split()[-2] == 'vif-s': + vlan['vif_c'] = [] + vlan['vif_c_remove'] = [] + + # ethertype uses a default of 0x88A8 + tmp = '0x88A8' + if conf.exists('ethertype'): + tmp = conf.return_value('ethertype') + vlan['ethertype'] = get_ethertype(tmp) + + # get vif-c interfaces (currently effective) - to determine which vif-c + # interface is no longer present and needs to be removed + eff_intf = conf.list_effective_nodes('vif-c') + act_intf = conf.list_nodes('vif-c') + vlan['vif_c_remove'] = diff(eff_intf, act_intf) + + # check if there is a Q-in-Q vlan customer interface + # and call this function recursively + if conf.exists('vif-c'): + cfg_level = conf.get_level() + # add new key (vif-c) to dictionary + for vif in conf.list_nodes('vif-c'): + # set config level to vif interface + conf.set_level(cfg_level + ' vif-c ' + vif) + vlan['vif_c'].append(vlan_to_dict(conf)) + + return vlan + + +def apply_vlan_config(vlan, config): + """ + Generic function to apply a VLAN configuration from a dictionary + to a VLAN interface + """ + + if type(vlan) != type(EthernetIf("lo")): + raise TypeError() + + # Configure interface address(es) + for addr in config['address_remove']: + vlan.del_addr(addr) + for addr in config['address']: + vlan.add_addr(addr) + + # update interface description used e.g. within SNMP + vlan.ifalias = config['description'] + # ignore link state changes + vlan.link_detect = config['disable_link_detect'] + # Maximum Transmission Unit (MTU) + vlan.mtu = config['mtu'] + # Change VLAN interface MAC address + if config['mac']: + vlan.mac = config['mac'] + + # enable/disable VLAN interface + if config['disable']: + vlan.state = 'down' + else: + vlan.state = 'up' + + +def get_config(): + # initialize kernel module if not loaded + if not os.path.isfile('/sys/class/net/bonding_masters'): + import syslog + syslog.syslog(syslog.LOG_NOTICE, "loading bonding kernel module") + if os.system('modprobe bonding max_bonds=0 miimon=250') != 0: + syslog.syslog(syslog.LOG_NOTICE, "failed loading bonding kernel module") + raise ConfigError("failed loading bonding kernel module") + + bond = deepcopy(default_config_data) + conf = Config() + + # determine tagNode instance + try: + bond['intf'] = os.environ['VYOS_TAGNODE_VALUE'] + except KeyError as E: + print("Interface not specified") + + # check if bond has been removed + cfg_base = 'interfaces bonding ' + bond['intf'] + if not conf.exists(cfg_base): + bond['deleted'] = True + return bond + + # set new configuration level + conf.set_level(cfg_base) + + # retrieve configured interface addresses + if conf.exists('address'): + bond['address'] = conf.return_values('address') + + # ARP link monitoring frequency in milliseconds + if conf.exists('arp-monitor interval'): + bond['arp_mon_intvl'] = int(conf.return_value('arp-monitor interval')) + + # IP address to use for ARP monitoring + if conf.exists('arp-monitor target'): + bond['arp_mon_tgt'] = conf.return_values('arp-monitor target') + + # retrieve interface description + if conf.exists('description'): + bond['description'] = conf.return_value('description') + else: + bond['description'] = bond['intf'] + + # get DHCP client identifier + if conf.exists('dhcp-options client-id'): + bond['dhcp_client_id'] = conf.return_value('dhcp-options client-id') + + # DHCP client host name (overrides the system host name) + if conf.exists('dhcp-options host-name'): + bond['dhcp_hostname'] = conf.return_value('dhcp-options host-name') + + # DHCPv6 only acquire config parameters, no address + if conf.exists('dhcpv6-options parameters-only'): + bond['dhcpv6_prm_only'] = conf.return_value('dhcpv6-options parameters-only') + + # DHCPv6 temporary IPv6 address + if conf.exists('dhcpv6-options temporary'): + bond['dhcpv6_temporary'] = conf.return_value('dhcpv6-options temporary') + + # ignore link state changes + if conf.exists('disable-link-detect'): + bond['disable_link_detect'] = 2 + + # disable bond interface + if conf.exists('disable'): + bond['disable'] = True + + # Bonding transmit hash policy + if conf.exists('hash-policy'): + bond['hash_policy'] = conf.return_value('hash-policy') + + # ARP cache entry timeout in seconds + if conf.exists('ip arp-cache-timeout'): + bond['ip_arp_cache_tmo'] = int(conf.return_value('ip arp-cache-timeout')) + + # Enable proxy-arp on this interface + if conf.exists('ip enable-proxy-arp'): + bond['ip_proxy_arp'] = 1 + + # Enable private VLAN proxy ARP on this interface + if conf.exists('ip proxy-arp-pvlan'): + bond['ip_proxy_arp_pvlan'] = 1 + + # Media Access Control (MAC) address + if conf.exists('mac'): + bond['mac'] = conf.return_value('mac') + + # Bonding mode + if conf.exists('mode'): + bond['mode'] = get_bond_mode(conf.return_value('mode')) + + # Maximum Transmission Unit (MTU) + if conf.exists('mtu'): + bond['mtu'] = int(conf.return_value('mtu')) + + # determine bond member interfaces (currently configured) + if conf.exists('member interface'): + bond['member'] = conf.return_values('member interface') + + # get interface addresses (currently effective) - to determine which + # address is no longer valid and needs to be removed from the bond + eff_addr = conf.return_effective_values('address') + act_addr = conf.return_values('address') + bond['address_remove'] = diff(eff_addr, act_addr) + + # Primary device interface + if conf.exists('primary'): + bond['primary'] = conf.return_value('primary') + + # re-set configuration level and retrieve vif-s interfaces + conf.set_level(cfg_base) + # get vif-s interfaces (currently effective) - to determine which vif-s + # interface is no longer present and needs to be removed + eff_intf = conf.list_effective_nodes('vif-s') + act_intf = conf.list_nodes('vif-s') + bond['vif_s_remove'] = diff(eff_intf, act_intf) + + if conf.exists('vif-s'): + for vif_s in conf.list_nodes('vif-s'): + # set config level to vif-s interface + conf.set_level(cfg_base + ' vif-s ' + vif_s) + bond['vif_s'].append(vlan_to_dict(conf)) + + # re-set configuration level and retrieve vif-s interfaces + conf.set_level(cfg_base) + # Determine vif interfaces (currently effective) - to determine which + # vif interface is no longer present and needs to be removed + eff_intf = conf.list_effective_nodes('vif') + act_intf = conf.list_nodes('vif') + bond['vif_remove'] = diff(eff_intf, act_intf) + + if conf.exists('vif'): + for vif in conf.list_nodes('vif'): + # set config level to vif interface + conf.set_level(cfg_base + ' vif ' + vif) + bond['vif'].append(vlan_to_dict(conf)) + + return bond + + +def verify(bond): + if len (bond['arp_mon_tgt']) > 16: + raise ConfigError('The maximum number of targets that can be specified is 16') + + if bond['primary']: + if bond['mode'] not in ['active-backup', 'balance-tlb', 'balance-alb']: + raise ConfigError('Mode dependency failed, primary not supported in this mode.'.format()) + + if bond['primary'] not in bond['member']: + raise ConfigError('Interface "{}" is not part of the bond'.format(bond['primary'])) + + for vif_s in bond['vif_s']: + for vif in bond['vif']: + if vif['id'] == vif_s['id']: + raise ConfigError('Can not use identical ID on vif and vif-s interface') + + + conf = Config() + for intf in bond['member']: + # we can not add disabled slave interfaces to our bond + if conf.exists('interfaces ethernet ' + intf + ' disable'): + raise ConfigError('can not add disabled interface {} to {}'.format(intf, bond['intf'])) + + # can not add interfaces with an assigned address to a bond + if conf.exists('interfaces ethernet ' + intf + ' address'): + raise ConfigError('can not add interface {} with an assigned address to {}'.format(intf, bond['intf'])) + + # bond members are not allowed to be bridge members, too + for bridge in conf.list_nodes('interfaces bridge'): + if conf.exists('interfaces bridge ' + bridge + ' member interface ' + intf): + raise ConfigError('can not add interface {} that is part of bridge {} to {}'.format(intf, bridge, bond['intf'])) + + # bond members are not allowed to be vrrp members, too + for vrrp in conf.list_nodes('high-availability vrrp group'): + if conf.exists('high-availability vrrp group ' + vrrp + ' interface ' + intf): + raise ConfigError('can not add interface {} which belongs to a VRRP group to {}'.format(intf, bond['intf'])) + + # bond members are not allowed to be underlaying psuedo-ethernet devices + for peth in conf.list_nodes('interfaces pseudo-ethernet'): + if conf.exists('interfaces pseudo-ethernet ' + peth + ' link ' + intf): + raise ConfigError('can not add interface {} used by pseudo-ethernet {} to {}'.format(intf, peth, bond['intf'])) + + if bond['primary']: + if bond['primary'] not in bond['member']: + raise ConfigError('primary interface must be a member interface of {}'.format(bond['intf'])) + + if bond['mode'] not in ['active-backup', 'balance-tlb', 'balance-alb']: + raise ConfigError('primary interface only works for mode active-backup, transmit-load-balance or adaptive-load-balance') + + if bond['arp_mon_intvl'] > 0: + if bond['mode'] in ['802.3ad', 'balance-tlb', 'balance-alb']: + raise ConfigError('ARP link monitoring does not work for mode 802.3ad, transmit-load-balance or adaptive-load-balance') + + return None + + +def generate(bond): + return None + + +def apply(bond): + b = BondIf(bond['intf']) + + if bond['deleted']: + # delete bonding interface + b.remove() + else: + # Some parameters can not be changed when the bond is up. + # Always disable the bond prior changing anything + b.state = 'down' + + # The bonding mode can not be changed when there are interfaces enslaved + # to this bond, thus we will free all interfaces from the bond first! + for intf in b.get_slaves(): + b.del_port(intf) + + # Configure interface address(es) + for addr in bond['address_remove']: + b.del_addr(addr) + for addr in bond['address']: + b.add_addr(addr) + + # ARP link monitoring frequency + b.arp_interval = bond['arp_mon_intvl'] + # reset miimon on arp-montior deletion + if bond['arp_mon_intvl'] == 0: + # reset miimon to default + b.bond_miimon = 250 + + # ARP monitor targets need to be synchronized between sysfs and CLI. + # Unfortunately an address can't be send twice to sysfs as this will + # result in the following exception: OSError: [Errno 22] Invalid argument. + # + # We remove ALL adresses prior adding new ones, this will remove addresses + # added manually by the user too - but as we are limited to 16 adresses + # from the kernel side this looks valid to me. We won't run into an error + # when a user added manual adresses which would result in having more + # then 16 adresses in total. + cur_addr = list(map(str, b.arp_ip_target.split())) + for addr in cur_addr: + b.arp_ip_target = '-' + addr + + # Add configured ARP target addresses + for addr in bond['arp_mon_tgt']: + b.arp_ip_target = '+' + addr + + # update interface description used e.g. within SNMP + b.ifalias = bond['description'] + + # + # missing DHCP/DHCPv6 options go here + # + + # ignore link state changes + b.link_detect = bond['disable_link_detect'] + # Bonding transmit hash policy + b.xmit_hash_policy = bond['hash_policy'] + # configure ARP cache timeout in milliseconds + b.arp_cache_tmp = bond['ip_arp_cache_tmo'] + # Enable proxy-arp on this interface + b.proxy_arp = bond['ip_proxy_arp'] + # Enable private VLAN proxy ARP on this interface + b.proxy_arp_pvlan = bond['ip_proxy_arp_pvlan'] + + # Change interface MAC address + if bond['mac']: + b.mac = bond['mac'] + + # Bonding policy + b.mode = bond['mode'] + # Maximum Transmission Unit (MTU) + b.mtu = bond['mtu'] + + # Primary device interface + if bond['primary']: + b.primary = bond['primary'] + + # Add (enslave) interfaces to bond + for intf in bond['member']: + b.add_port(intf) + + # remove no longer required service VLAN interfaces (vif-s) + for vif_s in bond['vif_s_remove']: + b.del_vlan(vif_s) + + # create service VLAN interfaces (vif-s) + for vif_s in bond['vif_s']: + s_vlan = b.add_vlan(vif_s['id'], ethertype=vif_s['ethertype']) + apply_vlan_config(s_vlan, vif_s) + + # remove no longer required client VLAN interfaces (vif-c) + # on lower service VLAN interface + for vif_c in vif_s['vif_c_remove']: + s_vlan.del_vlan(vif_c) + + # create client VLAN interfaces (vif-c) + # on lower service VLAN interface + for vif_c in vif_s['vif_c']: + c_vlan = s_vlan.add_vlan(vif_c['id']) + apply_vlan_config(c_vlan, vif_c) + + # remove no longer required VLAN interfaces (vif) + for vif in bond['vif_remove']: + b.del_vlan(vif) + + # create VLAN interfaces (vif) + for vif in bond['vif']: + vlan = b.add_vlan(vif['id']) + apply_vlan_config(vlan, vif) + + # As the bond interface is always disabled first when changing + # parameters we will only re-enable the interface if it is not + # administratively disabled + if not bond['disable']: + b.state = 'up' + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/interface-bridge.py b/src/conf_mode/interface-bridge.py index 6e48a1382..1d3587114 100755 --- a/src/conf_mode/interface-bridge.py +++ b/src/conf_mode/interface-bridge.py @@ -172,6 +172,13 @@ def verify(bridge): if intf['name'] not in interfaces(): raise ConfigError('Can not add non existing interface "{}" to bridge "{}"'.format(intf['name'], bridge['intf'])) + # bridge members are not allowed to be bond members, too + for intf in bridge['member']: + for bond in conf.list_nodes('interfaces bonding'): + if conf.exists('interfaces bonding ' + bond + ' member interface'): + if intf['name'] in conf.return_values('interfaces bonding ' + bond + ' member interface'): + raise ConfigError('Interface {} belongs to bond {}, can not add it to {}'.format(intf['name'], bond, bridge['intf'])) + return None def generate(bridge): diff --git a/src/migration-scripts/interfaces/1-to-2 b/src/migration-scripts/interfaces/1-to-2 new file mode 100755 index 000000000..050137318 --- /dev/null +++ b/src/migration-scripts/interfaces/1-to-2 @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 + +# Change syntax of bond interface +# - move interface based bond-group to actual bond (de-nest) +# https://phabricator.vyos.net/T1614 + +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', 'bonding'] + +if not config.exists(base): + # Nothing to do + sys.exit(0) +else: + # + # move interface based bond-group to actual bond (de-nest) + # + for intf in config.list_nodes(['interfaces', 'ethernet']): + # check if bond-group exists + if config.exists(['interfaces', 'ethernet', intf, 'bond-group']): + # get configured bond interface + bond = config.return_value(['interfaces', 'ethernet', intf, 'bond-group']) + # delete old interface asigned (nested) bond group + config.delete(['interfaces', 'ethernet', intf, 'bond-group']) + # create new bond member interface + config.set(base + [bond, 'member', 'interface'], value=intf, replace=False) + + # + # some combinations were allowed in the past from a CLI perspective + # but the kernel overwrote them - remove from CLI to not confuse the users. + # In addition new consitency checks are in place so users can't repeat the + # mistake. One of those nice issues is https://phabricator.vyos.net/T532 + for bond in config.list_nodes(base): + if config.exists(base + [bond, 'arp-monitor', 'interval']) and config.exists(base + [bond, 'mode']): + mode = config.return_value(base + [bond, 'mode']) + if mode in ['802.3ad', 'transmit-load-balance', 'adaptive-load-balance']: + intvl = int(config.return_value(base + [bond, 'arp-monitor', 'interval'])) + if intvl > 0: + # this is not allowed and the linux kernel replies with: + # option arp_interval: mode dependency failed, not supported in mode 802.3ad(4) + # option arp_interval: mode dependency failed, not supported in mode balance-alb(6) + # option arp_interval: mode dependency failed, not supported in mode balance-tlb(5) + # + # so we simply disable arp_interval by setting it to 0 and miimon will take care about the link + config.set(base + [bond, 'arp-monitor', 'interval'], value='0') + + 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) |