summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/openvpn/server.conf.tmpl2
-rw-r--r--python/vyos/configdict.py29
-rw-r--r--python/vyos/ifconfig/interface.py20
-rw-r--r--python/vyos/ifconfig/section.py21
-rw-r--r--python/vyos/ifconfig_vlan.py68
-rw-r--r--python/vyos/util.py71
-rw-r--r--python/vyos/validate.py66
-rwxr-xr-xsrc/conf_mode/interfaces-bonding.py121
-rwxr-xr-xsrc/conf_mode/interfaces-bridge.py83
-rwxr-xr-xsrc/conf_mode/interfaces-dummy.py36
-rwxr-xr-xsrc/conf_mode/interfaces-ethernet.py52
-rwxr-xr-xsrc/conf_mode/interfaces-geneve.py22
-rwxr-xr-xsrc/conf_mode/interfaces-l2tpv3.py28
-rwxr-xr-xsrc/conf_mode/interfaces-openvpn.py41
-rwxr-xr-xsrc/conf_mode/interfaces-pseudo-ethernet.py48
-rwxr-xr-xsrc/conf_mode/interfaces-tunnel.py43
-rwxr-xr-xsrc/conf_mode/interfaces-vxlan.py28
-rwxr-xr-xsrc/conf_mode/interfaces-wireguard.py53
-rwxr-xr-xsrc/conf_mode/interfaces-wireless.py43
-rwxr-xr-xsrc/conf_mode/interfaces-wirelessmodem.py37
20 files changed, 638 insertions, 274 deletions
diff --git a/data/templates/openvpn/server.conf.tmpl b/data/templates/openvpn/server.conf.tmpl
index 75ab602f8..401f8e04b 100644
--- a/data/templates/openvpn/server.conf.tmpl
+++ b/data/templates/openvpn/server.conf.tmpl
@@ -74,7 +74,7 @@ nobind
topology {% if server_topology == 'point-to-point' %}p2p{% else %}{{ server_topology }}{% endif %}
{%- endif %}
-{%- if bridge_member %}
+{%- if is_bridge_member %}
mode server
tls-server
{%- else %}
diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py
index e1b704a31..5ab831258 100644
--- a/python/vyos/configdict.py
+++ b/python/vyos/configdict.py
@@ -23,7 +23,8 @@ from copy import deepcopy
from vyos import ConfigError
from vyos.ifconfig import Interface
-
+from vyos.validate import is_member
+from vyos.util import ifname_from_config
def retrieve_config(path_hash, base_path, config):
"""
@@ -128,6 +129,7 @@ vlan_default = {
'ipv6_dup_addr_detect': 1,
'ingress_qos': '',
'ingress_qos_changed': False,
+ 'is_bridge_member': False,
'mac': '',
'mtu': 1500,
'vif_c': [],
@@ -197,6 +199,7 @@ def intf_to_dict(conf, default):
"""
intf = deepcopy(default)
+ intf['intf'] = ifname_from_config(conf)
# retrieve configured interface addresses
if conf.exists('address'):
@@ -263,15 +266,18 @@ def intf_to_dict(conf, default):
if conf.exists('ipv6 disable-forwarding'):
intf['ipv6_forwarding'] = 0
- # Media Access Control (MAC) address
- if conf.exists('mac'):
- intf['mac'] = conf.return_value('mac')
+ # check if interface is member of a bridge
+ intf['is_bridge_member'] = is_member(conf, intf['intf'], 'bridge')
# IPv6 Duplicate Address Detection (DAD) tries
if conf.exists('ipv6 dup-addr-detect-transmits'):
intf['ipv6_dup_addr_detect'] = int(
conf.return_value('ipv6 dup-addr-detect-transmits'))
+ # Media Access Control (MAC) address
+ if conf.exists('mac'):
+ intf['mac'] = conf.return_value('mac')
+
# Maximum Transmission Unit (MTU)
if conf.exists('mtu'):
intf['mtu'] = int(conf.return_value('mtu'))
@@ -338,14 +344,15 @@ def intf_to_dict(conf, default):
intf['ipv6_eui64_prefix'] = act_eui
intf['ipv6_eui64_prefix_remove'] = list_diff(eff_eui, act_eui)
- # Remove the default link-local address if set.
- if conf.exists('ipv6 address no-default-link-local'):
+ # Remove the default link-local address if set or if member of a bridge
+ if ( conf.exists('ipv6 address no-default-link-local')
+ or intf.get('is_bridge_member') ):
intf['ipv6_eui64_prefix_remove'].append('fe80::/64')
else:
# add the link-local by default to make IPv6 work
intf['ipv6_eui64_prefix'].append('fe80::/64')
- # Find out if MAC has changed
+ # If MAC has changed, remove and re-add all IPv6 EUI64 addresses
try:
interface = Interface(intf['intf'], create=False)
if intf['mac'] and intf['mac'] != interface.get_mac():
@@ -416,13 +423,13 @@ def add_to_dict(conf, disabled, ifdict, section, key):
def vlan_to_dict(conf, default=vlan_default):
vlan, disabled = intf_to_dict(conf, default)
- # get the '100' in 'interfaces bonding bond0 vif-s 100
- vlan['id'] = conf.get_level()[-1]
- current_level = conf.get_level()
+ level = conf.get_level()
+ # get the '100' in 'interfaces bonding bond0 vif-s 100'
+ vlan['id'] = level[-1]
# if this is a not within vif-s node, we are done
- if current_level[-2] != 'vif-s':
+ if level[-2] != 'vif-s':
return vlan
# ethertype is mandatory on vif-s nodes and only exists here!
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index 5b3da228e..7b42e3399 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -42,6 +42,7 @@ from vyos.ifconfig.control import Control
from vyos.ifconfig.dhcp import DHCP
from vyos.ifconfig.vrrp import VRRP
from vyos.ifconfig.operational import Operational
+from vyos.ifconfig import Section
class Interface(Control):
@@ -718,3 +719,22 @@ class Interface(Control):
# flush all addresses
self._cmd(f'ip addr flush dev "{self.ifname}"')
+
+ def add_to_bridge(self, br):
+ """
+ Adds the interface to the bridge with the passed port config.
+
+ Returns False if bridge doesn't exist.
+ """
+
+ # check if the bridge exists (on boot it doesn't)
+ if br not in Section.interfaces('bridge'):
+ return False
+
+ self.flush_addrs()
+ # add interface to bridge - use Section.klass to get BridgeIf class
+ Section.klass(br)(br, create=False).add_port(self.ifname)
+
+ # TODO: port config (STP)
+
+ return True
diff --git a/python/vyos/ifconfig/section.py b/python/vyos/ifconfig/section.py
index 092236fef..926c22e8a 100644
--- a/python/vyos/ifconfig/section.py
+++ b/python/vyos/ifconfig/section.py
@@ -54,7 +54,7 @@ class Section:
name = name.rstrip('0123456789')
name = name.rstrip('.')
if vlan:
- name = name.rstrip('0123456789')
+ name = name.rstrip('0123456789.')
return name
@classmethod
@@ -137,3 +137,22 @@ class Section:
eth, lo, vxlan, dum, ...
"""
return list(cls._prefixes.keys())
+
+ @classmethod
+ def get_config_path(cls, name):
+ """
+ get config path to interface with .vif or .vif-s.vif-c
+ example: eth0.1.2 -> 'ethernet eth0 vif-s 1 vif-c 2'
+ Returns False if interface name is invalid (not found in sections)
+ """
+ sect = cls.section(name)
+ if sect:
+ splinterface = name.split('.')
+ intfpath = f'{sect} {splinterface[0]}'
+ if len(splinterface) == 2:
+ intfpath += f' vif {splinterface[1]}'
+ elif len(splinterface) == 3:
+ intfpath += f' vif-s {splinterface[1]} vif-c {splinterface[2]}'
+ return intfpath
+ else:
+ return False
diff --git a/python/vyos/ifconfig_vlan.py b/python/vyos/ifconfig_vlan.py
index ee009f7f9..eb7a369ec 100644
--- a/python/vyos/ifconfig_vlan.py
+++ b/python/vyos/ifconfig_vlan.py
@@ -63,8 +63,10 @@ def apply_vlan_config(vlan, config):
# Maximum Transmission Unit (MTU)
vlan.set_mtu(config['mtu'])
- # assign/remove VRF
- vlan.set_vrf(config['vrf'])
+ # assign/remove VRF (ONLY when not a member of a bridge,
+ # otherwise 'nomaster' removes it from it)
+ if not config['is_bridge_member']:
+ vlan.set_vrf(config['vrf'])
# Delete old IPv6 EUI64 addresses before changing MAC
for addr in config['ipv6_eui64_prefix_remove']:
@@ -92,6 +94,10 @@ def apply_vlan_config(vlan, config):
for addr in config['address']:
vlan.add_addr(addr)
+ # re-add ourselves to any bridge we might have fallen out of
+ if config['is_bridge_member']:
+ vlan.add_to_bridge(config['is_bridge_member'])
+
def verify_vlan_config(config):
"""
Generic function to verify VLAN config consistency. Instead of re-
@@ -103,9 +109,22 @@ def verify_vlan_config(config):
if vif['dhcpv6_prm_only'] and vif['dhcpv6_temporary']:
raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!')
- vrf_name = vif['vrf']
- if vrf_name and vrf_name not in interfaces():
- raise ConfigError(f'VRF "{vrf_name}" does not exist')
+ if ( vif['is_bridge_member']
+ and ( vif['address']
+ or vif['ipv6_eui64_prefix']
+ or vif['ipv6_autoconf'] ) ):
+ raise ConfigError((
+ f'Cannot assign address to vif interface {vif["intf"]} '
+ f'which is a member of bridge {vif["is_bridge_member"]}'))
+
+ if vif['vrf']:
+ if vif['vrf'] not in interfaces():
+ raise ConfigError(f'VRF "{vif["vrf"]}" does not exist')
+
+ if vif['is_bridge_member']:
+ raise ConfigError((
+ f'vif {vif["intf"]} cannot be member of VRF {vif["vrf"]} '
+ f'and bridge {vif["is_bridge_member"]} at the same time!'))
# e.g. wireless interface has no vif_s support
# thus we bail out eraly.
@@ -121,17 +140,42 @@ def verify_vlan_config(config):
if vif_s['dhcpv6_prm_only'] and vif_s['dhcpv6_temporary']:
raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!')
- vrf_name = vif_s['vrf']
- if vrf_name and vrf_name not in interfaces():
- raise ConfigError(f'VRF "{vrf_name}" does not exist')
+ if ( vif_s['is_bridge_member']
+ and ( vif_s['address']
+ or vif_s['ipv6_eui64_prefix']
+ or vif_s['ipv6_autoconf'] ) ):
+ raise ConfigError((
+ f'Cannot assign address to vif-s interface {vif_s["intf"]} '
+ f'which is a member of bridge {vif_s["is_bridge_member"]}'))
+
+ if vif_s['vrf']:
+ if vif_s['vrf'] not in interfaces():
+ raise ConfigError(f'VRF "{vif_s["vrf"]}" does not exist')
+
+ if vif_s['is_bridge_member']:
+ raise ConfigError((
+ f'vif-s {vif_s["intf"]} cannot be member of VRF {vif_s["vrf"]} '
+ f'and bridge {vif_s["is_bridge_member"]} at the same time!'))
for vif_c in vif_s['vif_c']:
# DHCPv6 parameters-only and temporary address are mutually exclusive
if vif_c['dhcpv6_prm_only'] and vif_c['dhcpv6_temporary']:
raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!')
- vrf_name = vif_c['vrf']
- if vrf_name and vrf_name not in interfaces():
- raise ConfigError(f'VRF "{vrf_name}" does not exist')
-
+ if ( vif_c['is_bridge_member']
+ and ( vif_c['address']
+ or vif_c['ipv6_eui64_prefix']
+ or vif_c['ipv6_autoconf'] ) ):
+ raise ConfigError((
+ f'Cannot assign address to vif-c interface {vif_c["intf"]} '
+ f'which is a member of bridge {vif_c["is_bridge_member"]}'))
+
+ if vif_c['vrf']:
+ if vif_c['vrf'] not in interfaces():
+ raise ConfigError(f'VRF "{vif_c["vrf"]}" does not exist')
+
+ if vif_c['is_bridge_member']:
+ raise ConfigError((
+ f'vif-c {vif_c["intf"]} cannot be member of VRF {vif_c["vrf"]} '
+ f'and bridge {vif_c["is_bridge_member"]} at the same time!'))
diff --git a/python/vyos/util.py b/python/vyos/util.py
index 92b6f7992..e598e0ff3 100644
--- a/python/vyos/util.py
+++ b/python/vyos/util.py
@@ -498,3 +498,74 @@ def get_half_cpus():
if cpu > 1:
cpu /= 2
return int(cpu)
+
+def ifname_from_config(conf):
+ """
+ Gets interface name with VLANs from current config level.
+ Level must be at the interface whose name we want.
+
+ Example:
+ >>> from vyos.util import ifname_from_config
+ >>> from vyos.config import Config
+ >>> conf = Config()
+ >>> conf.set_level('interfaces ethernet eth0 vif-s 1 vif-c 2')
+ >>> ifname_from_config(conf)
+ 'eth0.1.2'
+ """
+ level = conf.get_level()
+
+ # vlans
+ if level[-2] == 'vif' or level[-2] == 'vif-s':
+ return level[-3] + '.' + level[-1]
+ if level[-2] == 'vif-c':
+ return level[-5] + '.' + level[-3] + '.' + level[-1]
+
+ # no vlans
+ return level[-1]
+
+def get_bridge_member_config(conf, br, intf):
+ """
+ Gets bridge port (member) configuration
+
+ Arguments:
+ conf: Config
+ br: bridge name
+ intf: interface name
+
+ Returns:
+ dict with the configuration
+ False if bridge or bridge port doesn't exist
+ """
+ old_level = conf.get_level()
+ conf.set_level([])
+
+ bridge = f'interfaces bridge {br}'
+ member = f'{bridge} member interface {intf}'
+ if not ( conf.exists(bridge) and conf.exists(member) ):
+ return False
+
+ # default bridge port configuration
+ # cost and priority initialized with linux defaults
+ # by reading /sys/devices/virtual/net/br0/brif/eth2/{path_cost,priority}
+ # after adding interface to bridge after reboot
+ memberconf = {
+ 'cost': 100,
+ 'priority': 32,
+ 'arp_cache_tmo': 30,
+ 'disable_link_detect': 1,
+ }
+
+ if conf.exists(f'{member} cost'):
+ memberconf['cost'] = int(conf.return_value(f'{member} cost'))
+
+ if conf.exists(f'{member} priority'):
+ memberconf['priority'] = int(conf.return_value(f'{member} priority'))
+
+ if conf.exists(f'{bridge} ip arp-cache-timeout'):
+ memberconf['arp_cache_tmo'] = int(conf.return_value(f'{bridge} ip arp-cache-timeout'))
+
+ if conf.exists(f'{bridge} disable-link-detect'):
+ memberconf['disable_link_detect'] = 2
+
+ conf.set_level(old_level)
+ return memberconf
diff --git a/python/vyos/validate.py b/python/vyos/validate.py
index 446f6e4ca..e083604c5 100644
--- a/python/vyos/validate.py
+++ b/python/vyos/validate.py
@@ -241,26 +241,66 @@ def assert_mac(m):
if octets[:5] == (0, 0, 94, 0, 1):
raise ValueError(f'{m} is a VRRP MAC address')
-def is_bridge_member(conf, interface):
+def is_member(conf, interface, intftype=None):
"""
- Checks if passed interfaces is part of a bridge device or not.
-
- Returns a tuple:
- None -> Interface not a bridge member
- Bridge -> Interface is a member of this bridge
+ Checks if passed interface is member of other interface of specified type.
+ intftype is optional, if not passed it will search all known types
+ (currently bridge and bonding)
+
+ Returns:
+ None -> Interface is not a member
+ interface name -> Interface is a member of this interface
+ False -> interface type cannot have members
"""
ret_val = None
- old_level = conf.get_level()
+
+ if intftype not in ['bonding', 'bridge', None]:
+ raise ValueError((
+ f'unknown interface type "{intftype}" or it cannot '
+ f'have member interfaces'))
+
+ intftype = ['bonding', 'bridge'] if intftype == None else [intftype]
# set config level to root
+ old_level = conf.get_level()
conf.set_level([])
- base = ['interfaces', 'bridge']
- for bridge in conf.list_nodes(base):
- members = conf.list_nodes(base + [bridge, 'member', 'interface'])
- if interface in members:
- ret_val = bridge
- break
+
+ for it in intftype:
+ base = 'interfaces ' + it
+ for intf in conf.list_nodes(base):
+ memberintf = f'{base} {intf} member interface'
+ if conf.is_tag(memberintf):
+ if interface in conf.list_nodes(memberintf):
+ ret_val = intf
+ break
+ elif conf.is_leaf(memberintf):
+ if ( conf.exists(memberintf) and
+ interface in conf.return_values(memberintf) ):
+ ret_val = intf
+ break
old_level = conf.set_level(old_level)
return ret_val
+def has_address_configured(conf, intf):
+ """
+ Checks if interface has an address configured.
+ Checks the following config nodes:
+ 'address', 'ipv6 address eui64', 'ipv6 address autoconf'
+
+ Returns True if interface has address configured, False if it doesn't.
+ """
+ from vyos.ifconfig import Section
+ ret = False
+
+ old_level = conf.get_level()
+ conf.set_level([])
+
+ intfpath = 'interfaces ' + Section.get_config_path(intf)
+ if ( conf.exists(f'{intfpath} address') or
+ conf.exists(f'{intfpath} ipv6 address autoconf') or
+ conf.exists(f'{intfpath} ipv6 address eui64') ):
+ ret = True
+
+ conf.set_level(old_level)
+ return ret
diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py
index a174e33e4..82b7c0f2a 100755
--- a/src/conf_mode/interfaces-bonding.py
+++ b/src/conf_mode/interfaces-bonding.py
@@ -20,12 +20,12 @@ from copy import deepcopy
from sys import exit
from netifaces import interfaces
-from vyos.ifconfig import BondIf, Section
+from vyos.ifconfig import BondIf
from vyos.ifconfig_vlan import apply_vlan_config, verify_vlan_config
from vyos.configdict import list_diff, intf_to_dict, add_to_dict
from vyos.config import Config
from vyos.util import call, cmd
-from vyos.validate import is_bridge_member
+from vyos.validate import is_member, has_address_configured
from vyos import ConfigError
default_config_data = {
@@ -111,15 +111,12 @@ def get_config():
bond = deepcopy(default_config_data)
bond['intf'] = ifname
bond['deleted'] = True
- # check if interface is member if a bridge
- bond['is_bridge_member'] = is_bridge_member(conf, ifname)
return bond
# set new configuration level
conf.set_level(cfg_base)
bond, disabled = intf_to_dict(conf, default_config_data)
- bond['intf'] = ifname
# ARP link monitoring frequency in milliseconds
if conf.exists('arp-monitor interval'):
@@ -175,22 +172,38 @@ def get_config():
def verify(bond):
if bond['deleted']:
if bond['is_bridge_member']:
- interface = bond['intf']
- bridge = bond['is_bridge_member']
- raise ConfigError(f'Interface "{interface}" can not be deleted as it belongs to bridge "{bridge}"!')
+ raise ConfigError((
+ f'Cannot delete interface "{bond["intf"]}" as it is a '
+ f'member of bridge "{bond["is_bridge_member"]}"!'))
+
return None
- if len (bond['arp_mon_tgt']) > 16:
- raise ConfigError('The maximum number of targets that can be specified is 16')
+ if len(bond['arp_mon_tgt']) > 16:
+ raise ConfigError('The maximum number of arp-monitor targets 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 mode "{}"!'.format(bond['mode']))
+ raise ConfigError((
+ 'Mode dependency failed, primary not supported in mode '
+ f'"{bond["mode"]}"!'))
+
+ if ( bond['is_bridge_member']
+ and ( bond['address']
+ or bond['ipv6_eui64_prefix']
+ or bond['ipv6_autoconf'] ) ):
+ raise ConfigError((
+ f'Cannot assign address to interface "{bond["intf"]}" '
+ f'as it is a member of bridge "{bond["is_bridge_member"]}"!'))
+
+ if bond['vrf']:
+ if bond['vrf'] not in interfaces():
+ raise ConfigError(f'VRF "{bond["vrf"]}" does not exist')
- vrf_name = bond['vrf']
- if vrf_name and vrf_name not in interfaces():
- raise ConfigError(f'VRF "{vrf_name}" does not exist')
+ if bond['is_bridge_member']:
+ raise ConfigError((
+ f'Interface "{bond["intf"]}" cannot be member of VRF '
+ f'"{bond["vrf"]}" and bridge {bond["is_bridge_member"]} '
+ f'at the same time!'))
# use common function to verify VLAN configuration
verify_vlan_config(bond)
@@ -199,51 +212,55 @@ def verify(bond):
for intf in bond['member']:
# check if member interface is "real"
if intf not in interfaces():
- raise ConfigError('interface {} does not exist!'.format(intf))
+ raise ConfigError(f'Interface {intf} does not exist!')
# a bonding member interface is only allowed to be assigned to one bond!
all_bonds = conf.list_nodes('interfaces bonding')
# We do not need to check our own bond
all_bonds.remove(bond['intf'])
for tmp in all_bonds:
- if conf.exists('interfaces bonding ' + tmp + ' member interface ' + intf):
- raise ConfigError('can not enslave interface {} which already ' \
- 'belongs to {}'.format(intf, tmp))
+ if conf.exists('interfaces bonding {tmp} member interface {intf}'):
+ raise ConfigError((
+ f'Cannot add interface "{intf}" to bond "{bond["intf"]}", '
+ f'it is already a member of bond "{tmp}"!'))
# can not add interfaces with an assigned address to a bond
- if conf.exists('interfaces ethernet ' + intf + ' address'):
- raise ConfigError('can not enslave interface {} which has an address ' \
- 'assigned'.format(intf))
-
- # bond members are not allowed to be bridge members, too
- for tmp in conf.list_nodes('interfaces bridge'):
- if conf.exists('interfaces bridge ' + tmp + ' member interface ' + intf):
- raise ConfigError('can not enslave interface {} which belongs to ' \
- 'bridge {}'.format(intf, tmp))
-
- # bond members are not allowed to be vrrp members, too
+ if has_address_configured(conf, intf):
+ raise ConfigError((
+ f'Cannot add interface "{intf}" to bond "{bond["intf"]}", '
+ f'it has an address assigned!'))
+
+ # bond members are not allowed to be bridge members
+ tmp = is_member(conf, intf, 'bridge')
+ if tmp:
+ raise ConfigError((
+ f'Cannot add interface "{intf}" to bond "{bond["intf"]}", '
+ f'it is already a member of bridge "{tmp}"!'))
+
+ # bond members are not allowed to be vrrp members
for tmp in conf.list_nodes('high-availability vrrp group'):
- if conf.exists('high-availability vrrp group ' + tmp + ' interface ' + intf):
- raise ConfigError('can not enslave interface {} which belongs to ' \
- 'VRRP group {}'.format(intf, tmp))
+ if conf.exists('high-availability vrrp group {tmp} interface {intf}'):
+ raise ConfigError((
+ f'Cannot add interface "{intf}" to bond "{bond["intf"]}", '
+ f'it is already a member of VRRP group "{tmp}"!'))
# bond members are not allowed to be underlaying psuedo-ethernet devices
for tmp in conf.list_nodes('interfaces pseudo-ethernet'):
- if conf.exists('interfaces pseudo-ethernet ' + tmp + ' link ' + intf):
- raise ConfigError('can not enslave interface {} which belongs to ' \
- 'pseudo-ethernet {}'.format(intf, tmp))
+ if conf.exists('interfaces pseudo-ethernet {tmp} link {intf}'):
+ raise ConfigError((
+ f'Cannot add interface "{intf}" to bond "{bond["intf"]}", '
+ f'it is already the link of pseudo-ethernet "{tmp}"!'))
# bond members are not allowed to be underlaying vxlan devices
for tmp in conf.list_nodes('interfaces vxlan'):
- if conf.exists('interfaces vxlan ' + tmp + ' link ' + intf):
- raise ConfigError('can not enslave interface {} which belongs to ' \
- 'vxlan {}'.format(intf, tmp))
-
+ if conf.exists('interfaces vxlan {tmp} link {intf}'):
+ raise ConfigError((
+ f'Cannot add interface "{intf}" to bond "{bond["intf"]}", '
+ f'it is already the link of VXLAN "{tmp}"!'))
if bond['primary']:
if bond['primary'] not in bond['member']:
- raise ConfigError('primary interface must be a member interface of {}' \
- .format(bond['intf']))
+ raise ConfigError(f'Bond "{bond["intf"]}" primary interface must be a member')
if bond['mode'] not in ['active-backup', 'balance-tlb', 'balance-alb']:
raise ConfigError('primary interface only works for mode active-backup, ' \
@@ -256,7 +273,6 @@ def verify(bond):
return None
-
def generate(bond):
return None
@@ -365,12 +381,9 @@ def apply(bond):
# Add (enslave) interfaces to bond
for intf in bond['member']:
- # flushes only children of Interfaces class (e.g. vlan are not)
- if intf in Section.interfaces():
- klass = Section.klass(intf, vlan=False)
- klass(intf, create=False).flush_addrs()
- # flushes also vlan interfaces
- call(f'ip addr flush dev "{intf}"')
+ # if we've come here we already verified the interface doesn't
+ # have addresses configured so just flush any remaining ones
+ cmd(f'ip addr flush dev "{intf}"')
b.add_port(intf)
# As the bond interface is always disabled first when changing
@@ -389,8 +402,14 @@ def apply(bond):
for addr in bond['address']:
b.add_addr(addr)
- # assign/remove VRF
- b.set_vrf(bond['vrf'])
+ # assign/remove VRF (ONLY when not a member of a bridge,
+ # otherwise 'nomaster' removes it from it)
+ if not bond['is_bridge_member']:
+ b.set_vrf(bond['vrf'])
+
+ # re-add ourselves to any bridge we might have fallen out of
+ if bond['is_bridge_member']:
+ b.add_to_bridge(bond['is_bridge_member'])
# remove no longer required service VLAN interfaces (vif-s)
for vif_s in bond['vif_s_remove']:
diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py
index 9d638653c..c43fae78b 100755
--- a/src/conf_mode/interfaces-bridge.py
+++ b/src/conf_mode/interfaces-bridge.py
@@ -23,8 +23,9 @@ from netifaces import interfaces
from vyos.ifconfig import BridgeIf, Section
from vyos.ifconfig.stp import STP
from vyos.configdict import list_diff
+from vyos.validate import is_member, has_address_configured
from vyos.config import Config
-from vyos.util import cmd
+from vyos.util import cmd, get_bridge_member_config
from vyos import ConfigError
default_config_data = {
@@ -202,22 +203,12 @@ def get_config():
# Determine bridge member interface (currently configured)
for intf in conf.list_nodes('member interface'):
- # cost and priority initialized with linux defaults
- # by reading /sys/devices/virtual/net/br0/brif/eth2/{path_cost,priority}
- # after adding interface to bridge after reboot
- iface = {
- 'name': intf,
- 'cost': 100,
- 'priority': 32
- }
-
- if conf.exists('member interface {} cost'.format(intf)):
- iface['cost'] = int(conf.return_value('member interface {} cost'.format(intf)))
-
- if conf.exists('member interface {} priority'.format(intf)):
- iface['priority'] = int(conf.return_value('member interface {} priority'.format(intf)))
-
- bridge['member'].append(iface)
+ # defaults are stored in util.py (they can't be here as all interface
+ # scripts use the function)
+ memberconf = get_bridge_member_config(conf, bridge['intf'], intf)
+ if memberconf:
+ memberconf['name'] = intf
+ bridge['member'].append(memberconf)
# Determine bridge member interface (currently effective) - to determine which
# interfaces is no longer assigend to the bridge and thus can be removed
@@ -248,30 +239,40 @@ def verify(bridge):
raise ConfigError(f'VRF "{vrf_name}" does not exist')
conf = Config()
- for br in conf.list_nodes('interfaces bridge'):
- # it makes no sense to verify ourself in this case
- if br == bridge['intf']:
- continue
-
- for intf in bridge['member']:
- tmp = conf.list_nodes('interfaces bridge {} member interface'.format(br))
- if intf['name'] in tmp:
- raise ConfigError('Interface "{}" belongs to bridge "{}" and can not be enslaved.'.format(intf['name'], bridge['intf']))
-
- # the interface must exist prior adding it to a bridge
for intf in bridge['member']:
+ # the interface must exist prior adding it to a bridge
if intf['name'] not in interfaces():
- raise ConfigError('Can not add non existing interface "{}" to bridge "{}"'.format(intf['name'], bridge['intf']))
+ raise ConfigError((
+ f'Cannot add nonexistent interface "{intf["name"]}" '
+ f'to bridge "{bridge["intf"]}"'))
if intf['name'] == 'lo':
raise ConfigError('Loopback interface "lo" can not be added to a bridge')
- # 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']))
+ # bridge members aren't allowed to be members of another bridge
+ for br in conf.list_nodes('interfaces bridge'):
+ # it makes no sense to verify ourself in this case
+ if br == bridge['intf']:
+ continue
+
+ tmp = conf.list_nodes(f'interfaces bridge {br} member interface')
+ if intf['name'] in tmp:
+ raise ConfigError((
+ f'Cannot add interface "{intf["name"]}" to bridge '
+ f'"{bridge["intf"]}", it is already a member of bridge "{br}"!'))
+
+ # bridge members are not allowed to be bond members
+ tmp = is_member(conf, intf['name'], 'bonding')
+ if tmp:
+ raise ConfigError((
+ f'Cannot add interface "{intf["name"]}" to bridge '
+ f'"{bridge["intf"]}", it is already a member of bond "{tmp}"!'))
+
+ # bridge members must not have an assigned address
+ if has_address_configured(conf, intf['name']):
+ raise ConfigError((
+ f'Cannot add interface "{intf["name"]}" to bridge '
+ f'"{bridge["intf"]}", it has an address assigned!'))
return None
@@ -347,12 +348,8 @@ def apply(bridge):
# add interfaces to bridge
for member in bridge['member']:
- # flushes address of only children of Interfaces class
- # (e.g. vlan are not)
- if member['name'] in Section.interfaces():
- klass = Section.klass(member['name'], vlan=False)
- klass(member['name'], create=False).flush_addrs()
- # flushes all interfaces
+ # if we've come here we already verified the interface doesn't
+ # have addresses configured so just flush any remaining ones
cmd(f'ip addr flush dev "{member["name"]}"')
br.add_port(member['name'])
@@ -382,9 +379,9 @@ def apply(bridge):
for member in bridge['member']:
i = STPBridgeIf(member['name'])
# configure ARP cache timeout
- i.set_arp_cache_tmo(bridge['arp_cache_tmo'])
+ i.set_arp_cache_tmo(member['arp_cache_tmo'])
# ignore link state changes
- i.set_link_detect(bridge['disable_link_detect'])
+ i.set_link_detect(member['disable_link_detect'])
# set bridge port path cost
i.set_path_cost(member['cost'])
# set bridge port path priority
diff --git a/src/conf_mode/interfaces-dummy.py b/src/conf_mode/interfaces-dummy.py
index 23eaa4ecb..4a77b0c1a 100755
--- a/src/conf_mode/interfaces-dummy.py
+++ b/src/conf_mode/interfaces-dummy.py
@@ -23,7 +23,7 @@ from netifaces import interfaces
from vyos.ifconfig import DummyIf
from vyos.configdict import list_diff
from vyos.config import Config
-from vyos.validate import is_bridge_member
+from vyos.validate import is_member
from vyos import ConfigError
default_config_data = {
@@ -47,11 +47,12 @@ def get_config():
dummy['intf'] = os.environ['VYOS_TAGNODE_VALUE']
+ # check if we are a member of any bridge
+ dummy['is_bridge_member'] = is_member(conf, dummy['intf'], 'bridge')
+
# Check if interface has been removed
if not conf.exists('interfaces dummy ' + dummy['intf']):
dummy['deleted'] = True
- # check if interface is member if a bridge
- dummy['is_bridge_member'] = is_bridge_member(conf, dummy['intf'])
return dummy
# set new configuration level
@@ -84,15 +85,26 @@ def get_config():
def verify(dummy):
if dummy['deleted']:
if dummy['is_bridge_member']:
- interface = dummy['intf']
- bridge = dummy['is_bridge_member']
- raise ConfigError(f'Interface "{interface}" can not be deleted as it belongs to bridge "{bridge}"!')
+ raise ConfigError((
+ f'Interface "{dummy["intf"]}" cannot be deleted as it is a '
+ f'member of bridge "{dummy["is_bridge_member"]}"!'))
return None
- vrf_name = dummy['vrf']
- if vrf_name and vrf_name not in interfaces():
- raise ConfigError(f'VRF "{vrf_name}" does not exist')
+ if dummy['vrf']:
+ if dummy['vrf'] not in interfaces():
+ raise ConfigError(f'VRF "{dummy["vrf"]}" does not exist')
+
+ if dummy['is_bridge_member']:
+ raise ConfigError((
+ f'Interface "{dummy["intf"]}" cannot be member of VRF '
+ f'"{dummy["vrf"]}" and bridge "{dummy["is_bridge_member"]}" '
+ f'at the same time!'))
+
+ if dummy['is_bridge_member'] and dummy['address']:
+ raise ConfigError((
+ f'Cannot assign address to interface "{dummy["intf"]}" '
+ f'as it is a member of bridge "{dummy["is_bridge_member"]}"!'))
return None
@@ -117,8 +129,10 @@ def apply(dummy):
for addr in dummy['address']:
d.add_addr(addr)
- # assign/remove VRF
- d.set_vrf(dummy['vrf'])
+ # assign/remove VRF (ONLY when not a member of a bridge,
+ # otherwise 'nomaster' removes it from it)
+ if not dummy['is_bridge_member']:
+ d.set_vrf(dummy['vrf'])
# disable interface on demand
if dummy['disable']:
diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py
index 3ddd394d7..9b9ae931c 100755
--- a/src/conf_mode/interfaces-ethernet.py
+++ b/src/conf_mode/interfaces-ethernet.py
@@ -20,9 +20,10 @@ from sys import exit
from copy import deepcopy
from netifaces import interfaces
-from vyos.ifconfig import EthernetIf, Section
+from vyos.ifconfig import EthernetIf
from vyos.ifconfig_vlan import apply_vlan_config, verify_vlan_config
from vyos.configdict import list_diff, intf_to_dict, add_to_dict
+from vyos.validate import is_member
from vyos.config import Config
from vyos import ConfigError
@@ -53,6 +54,8 @@ default_config_data = {
'ipv6_eui64_prefix_remove': [],
'ipv6_forwarding': 1,
'ipv6_dup_addr_detect': 1,
+ 'is_bridge_member': False,
+ 'is_bond_member': False,
'intf': '',
'mac': '',
'mtu': 1500,
@@ -92,7 +95,6 @@ def get_config():
conf.set_level(cfg_base)
eth, disabled = intf_to_dict(conf, default_config_data)
- eth['intf'] = ifname
# disable ethernet flow control (pause frames)
if conf.exists('disable-flow-control'):
@@ -114,6 +116,9 @@ def get_config():
if conf.exists('ip proxy-arp-pvlan'):
eth['ip_proxy_arp_pvlan'] = 1
+ # check if we are a member of any bond
+ eth['is_bond_member'] = is_member(conf, eth['intf'], 'bonding')
+
# GRO (generic receive offload)
if conf.exists('offload-options generic-receive'):
eth['offload_gro'] = conf.return_value('offload-options generic-receive')
@@ -138,6 +143,11 @@ def get_config():
if conf.exists('speed'):
eth['speed'] = conf.return_value('speed')
+ # remove default IPv6 link-local address if member of a bond
+ if eth['is_bond_member'] and 'fe80::/64' in eth['ipv6_eui64_prefix']:
+ eth['ipv6_eui64_prefix'].remove('fe80::/64')
+ eth['ipv6_eui64_prefix_remove'].append('fe80::/64')
+
add_to_dict(conf, disabled, eth, 'vif', 'vif')
add_to_dict(conf, disabled, eth, 'vif-s', 'vif_s')
@@ -162,18 +172,24 @@ def verify(eth):
if eth['dhcpv6_prm_only'] and eth['dhcpv6_temporary']:
raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!')
- vrf_name = eth['vrf']
- if vrf_name and vrf_name not in interfaces():
- raise ConfigError(f'VRF "{vrf_name}" does not exist')
+ memberof = eth['is_bridge_member'] if eth['is_bridge_member'] else eth['is_bond_member']
- conf = Config()
- # some options can not be changed when interface is enslaved to a bond
- for bond in conf.list_nodes('interfaces bonding'):
- if conf.exists('interfaces bonding ' + bond + ' member interface'):
- bond_member = conf.return_values('interfaces bonding ' + bond + ' member interface')
- if eth['intf'] in bond_member:
- if eth['address']:
- raise ConfigError(f"Can not assign address to interface {eth['intf']} which is a member of {bond}")
+ if ( memberof
+ and ( eth['address']
+ or eth['ipv6_eui64_prefix']
+ or eth['ipv6_autoconf'] ) ):
+ raise ConfigError((
+ f'Cannot assign address to interface "{eth["intf"]}" '
+ f'as it is a member of "{memberof}"!'))
+
+ if eth['vrf']:
+ if eth['vrf'] not in interfaces():
+ raise ConfigError(f'VRF "{eth["vrf"]}" does not exist')
+
+ if memberof:
+ raise ConfigError((
+ f'Interface "{eth["intf"]}" cannot be member of VRF "{eth["vrf"]}" '
+ f'and "{memberof}" at the same time!'))
# use common function to verify VLAN configuration
verify_vlan_config(eth)
@@ -281,8 +297,14 @@ def apply(eth):
for addr in eth['address']:
e.add_addr(addr)
- # assign/remove VRF
- e.set_vrf(eth['vrf'])
+ # assign/remove VRF (ONLY when not a member of a bridge or bond,
+ # otherwise 'nomaster' removes it from it)
+ if not ( eth['is_bridge_member'] or eth['is_bond_member'] ):
+ e.set_vrf(eth['vrf'])
+
+ # re-add ourselves to any bridge we might have fallen out of
+ if eth['is_bridge_member']:
+ e.add_to_bridge(eth['is_bridge_member'])
# remove no longer required service VLAN interfaces (vif-s)
for vif_s in eth['vif_s_remove']:
diff --git a/src/conf_mode/interfaces-geneve.py b/src/conf_mode/interfaces-geneve.py
index 708a64474..e4109a221 100755
--- a/src/conf_mode/interfaces-geneve.py
+++ b/src/conf_mode/interfaces-geneve.py
@@ -22,7 +22,7 @@ from netifaces import interfaces
from vyos.config import Config
from vyos.ifconfig import GeneveIf
-from vyos.validate import is_bridge_member
+from vyos.validate import is_member
from vyos import ConfigError
default_config_data = {
@@ -49,11 +49,12 @@ def get_config():
geneve['intf'] = os.environ['VYOS_TAGNODE_VALUE']
+ # check if interface is member if a bridge
+ geneve['is_bridge_member'] = is_member(conf, geneve['intf'], 'bridge')
+
# Check if interface has been removed
if not conf.exists('interfaces geneve ' + geneve['intf']):
geneve['deleted'] = True
- # check if interface is member if a bridge
- geneve['is_bridge_member'] = is_bridge_member(conf, geneve['intf'])
return geneve
# set new configuration level
@@ -97,12 +98,17 @@ def get_config():
def verify(geneve):
if geneve['deleted']:
if geneve['is_bridge_member']:
- interface = geneve['intf']
- bridge = geneve['is_bridge_member']
- raise ConfigError(f'Interface "{interface}" can not be deleted as it belongs to bridge "{bridge}"!')
+ raise ConfigError((
+ f'Cannot delete interface "{geneve["intf"]}" as it is a '
+ f'member of bridge "{geneve["is_bridge_member"]}"!'))
return None
+ if geneve['is_bridge_member'] and geneve['address']:
+ raise ConfigError((
+ f'Cannot assign address to interface "{geneve["intf"]}" '
+ f'as it is a member of bridge "{geneve["is_bridge_member"]}"!'))
+
if not geneve['remote']:
raise ConfigError('GENEVE remote must be configured')
@@ -158,6 +164,10 @@ def apply(geneve):
if not geneve['disable']:
g.set_admin_state('up')
+ # re-add ourselves to any bridge we might have fallen out of
+ if geneve['is_bridge_member']:
+ g.add_to_bridge(geneve['is_bridge_member'])
+
return None
diff --git a/src/conf_mode/interfaces-l2tpv3.py b/src/conf_mode/interfaces-l2tpv3.py
index 33cf62f70..26bb537e5 100755
--- a/src/conf_mode/interfaces-l2tpv3.py
+++ b/src/conf_mode/interfaces-l2tpv3.py
@@ -24,7 +24,7 @@ from vyos.config import Config
from vyos.ifconfig import L2TPv3If, Interface
from vyos import ConfigError
from vyos.util import call
-from vyos.validate import is_bridge_member, is_addr_assigned
+from vyos.validate import is_member, is_addr_assigned
default_config_data = {
'address': [],
@@ -66,12 +66,13 @@ def get_config():
l2tpv3['intf'] = os.environ['VYOS_TAGNODE_VALUE']
+ # check if interface is member of a bridge
+ l2tpv3['is_bridge_member'] = is_member(conf, l2tpv3['intf'], 'bridge')
+
# Check if interface has been removed
if not conf.exists('interfaces l2tpv3 ' + l2tpv3['intf']):
l2tpv3['deleted'] = True
interface = l2tpv3['intf']
- # check if interface is member if a bridge
- l2tpv3['is_bridge_member'] = is_bridge_member(conf, interface)
# to delete the l2tpv3 interface we need the current tunnel_id and session_id
if conf.exists_effective(f'interfaces l2tpv3 {interface} tunnel-id'):
@@ -118,7 +119,8 @@ def get_config():
l2tpv3['ipv6_eui64_prefix'] = conf.return_values('ipv6 address eui64')
# Remove the default link-local address if set.
- if not conf.exists('ipv6 address no-default-link-local'):
+ if not ( conf.exists('ipv6 address no-default-link-local') or
+ l2tpv3['is_bridge_member'] ):
# add the link-local by default to make IPv6 work
l2tpv3['ipv6_eui64_prefix'].append('fe80::/64')
@@ -166,9 +168,9 @@ def verify(l2tpv3):
if l2tpv3['deleted']:
if l2tpv3['is_bridge_member']:
- interface = l2tpv3['intf']
- bridge = l2tpv3['is_bridge_member']
- raise ConfigError(f'Interface "{interface}" can not be deleted as it belongs to bridge "{bridge}"!')
+ raise ConfigError((
+ f'Interface "{l2tpv3["intf"]}" cannot be deleted as it is a '
+ f'member of bridge "{l2tpv3["is_bridge_member"]}"!'))
return None
@@ -193,6 +195,14 @@ def verify(l2tpv3):
if not l2tpv3['peer_session_id']:
raise ConfigError(f'Must configure the l2tpv3 peer-session-id for {interface}')
+ if ( l2tpv3['is_bridge_member']
+ and ( l2tpv3['address']
+ or l2tpv3['ipv6_eui64_prefix']
+ or l2tpv3['ipv6_autoconf'] ) ):
+ raise ConfigError((
+ f'Cannot assign address to interface "{l2tpv3["intf"]}" '
+ f'as it is a member of bridge "{l2tpv3["is_bridge_member"]}"!'))
+
return None
@@ -254,6 +264,10 @@ def apply(l2tpv3):
if not l2tpv3['disable']:
l.set_admin_state('up')
+ # re-add ourselves to any bridge we might have fallen out of
+ if l2tpv3['is_bridge_member']:
+ l.add_to_bridge(l2tpv3['is_bridge_member'])
+
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py
index 029bc1d69..c0678764f 100755
--- a/src/conf_mode/interfaces-openvpn.py
+++ b/src/conf_mode/interfaces-openvpn.py
@@ -29,7 +29,7 @@ from vyos.configdict import list_diff
from vyos.ifconfig import VTunIf
from vyos.template import render
from vyos.util import call, chown, chmod_600, chmod_755
-from vyos.validate import is_addr_assigned, is_bridge_member, is_ipv4
+from vyos.validate import is_addr_assigned, is_member, is_ipv4
from vyos import ConfigError
user = 'openvpn'
@@ -41,7 +41,6 @@ default_config_data = {
'auth_pass': '',
'auth_user_pass_file': '',
'auth': False,
- 'bridge_member': [],
'compress_lzo': False,
'deleted': False,
'description': '',
@@ -199,21 +198,16 @@ def get_config():
openvpn['intf'] = os.environ['VYOS_TAGNODE_VALUE']
openvpn['auth_user_pass_file'] = f"/run/openvpn/{openvpn['intf']}.pw"
+ # check if interface is member of a bridge
+ openvpn['is_bridge_member'] = is_member(conf, openvpn['intf'], 'bridge')
+
# Check if interface instance has been removed
if not conf.exists('interfaces openvpn ' + openvpn['intf']):
openvpn['deleted'] = True
- # check if interface is member if a bridge
- openvpn['is_bridge_member'] = is_bridge_member(conf, openvpn['intf'])
return openvpn
- # Check if we belong to any bridge interface
- for bridge in conf.list_nodes('interfaces bridge'):
- for intf in conf.list_nodes('interfaces bridge {} member interface'.format(bridge)):
- if intf == openvpn['intf']:
- openvpn['bridge_member'].append(intf)
-
# bridged server should not have a pool by default (but can be specified manually)
- if openvpn['bridge_member']:
+ if openvpn['is_bridge_member']:
openvpn['server_pool'] = False
openvpn['server_ipv6_pool'] = False
@@ -597,7 +591,7 @@ def get_config():
default_server = getDefaultServer(server_network_v4, openvpn['server_topology'], openvpn['type'])
if default_server:
# server-bridge doesn't require a pool so don't set defaults for it
- if openvpn['server_pool'] and not openvpn['bridge_member']:
+ if openvpn['server_pool'] and not openvpn['is_bridge_member']:
if not openvpn['server_pool_start']:
openvpn['server_pool_start'] = default_server['pool_start']
@@ -635,22 +629,15 @@ def get_config():
def verify(openvpn):
if openvpn['deleted']:
if openvpn['is_bridge_member']:
- interface = openvpn['intf']
- bridge = openvpn['is_bridge_member']
- raise ConfigError(f'Interface "{interface}" can not be deleted as it belongs to bridge "{bridge}"!')
-
- return None
+ raise ConfigError((
+ f'Cannot delete interface "{openvpn["intf"]}" as it is a '
+ f'member of bridge "{openvpn["is_bridge_menber"]}"!'))
+ return None
if not openvpn['mode']:
raise ConfigError('Must specify OpenVPN operation mode')
- # Checks which need to be performed on interface rmeoval
- if openvpn['deleted']:
- # OpenVPN interface can not be deleted if it's still member of a bridge
- if openvpn['bridge_member']:
- raise ConfigError('Can not delete {} as it is a member interface of bridge {}!'.format(openvpn['intf'], bridge))
-
# Check if we have disabled ncp and at the same time specified ncp-ciphers
if openvpn['disable_ncp'] and openvpn['ncp_ciphers']:
raise ConfigError('Cannot specify both "encryption disable-ncp" and "encryption ncp-ciphers"')
@@ -680,9 +667,9 @@ def verify(openvpn):
if openvpn['ncp_ciphers']:
raise ConfigError('encryption ncp-ciphers cannot be specified in site-to-site mode, only server or client')
- if openvpn['mode'] == 'site-to-site' and not openvpn['bridge_member']:
+ if openvpn['mode'] == 'site-to-site' and not openvpn['is_bridge_member']:
if not (openvpn['local_address'] or openvpn['ipv6_local_address']):
- raise ConfigError('Must specify "local-address" or "bridge member interface"')
+ raise ConfigError('Must specify "local-address" or add interface to bridge')
if len(openvpn['local_address']) > 1 or len(openvpn['ipv6_local_address']) > 1:
raise ConfigError('Cannot specify more than 1 IPv4 and 1 IPv6 "local-address"')
@@ -761,8 +748,8 @@ def verify(openvpn):
raise ConfigError(f'Client "{client["name"]}" IP {client["ip"][0]} not in server subnet {subnet}')
else:
- if not openvpn['bridge_member']:
- raise ConfigError('Must specify "server subnet" or "bridge member interface" in server mode')
+ if not openvpn['is_bridge_member']:
+ raise ConfigError('Must specify "server subnet" or add interface to bridge in server mode')
if openvpn['server_pool']:
if not (openvpn['server_pool_start'] and openvpn['server_pool_stop']):
diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py
index f0f893b44..1d04d6dbe 100755
--- a/src/conf_mode/interfaces-pseudo-ethernet.py
+++ b/src/conf_mode/interfaces-pseudo-ethernet.py
@@ -21,10 +21,9 @@ from sys import exit
from netifaces import interfaces
from vyos.config import Config
-from vyos.configdict import list_diff, vlan_to_dict, intf_to_dict, add_to_dict
+from vyos.configdict import list_diff, intf_to_dict, add_to_dict
from vyos.ifconfig import MACVLANIf, Section
from vyos.ifconfig_vlan import apply_vlan_config, verify_vlan_config
-from vyos.validate import is_bridge_member
from vyos import ConfigError
default_config_data = {
@@ -77,15 +76,12 @@ def get_config():
if not conf.exists(cfg_base):
peth = deepcopy(default_config_data)
peth['deleted'] = True
- # check if interface is member if a bridge
- peth['is_bridge_member'] = is_bridge_member(conf, ifname)
return peth
# set new configuration level
conf.set_level(cfg_base)
peth, disabled = intf_to_dict(conf, default_config_data)
- peth['intf'] = ifname
# ARP cache entry timeout in seconds
if conf.exists(['ip', 'arp-cache-timeout']):
@@ -114,21 +110,37 @@ def get_config():
def verify(peth):
if peth['deleted']:
if peth['is_bridge_member']:
- interface = peth['intf']
- bridge = peth['is_bridge_member']
- raise ConfigError(f'Interface "{interface}" can not be deleted as it belongs to bridge "{bridge}"!')
+ raise ConfigError((
+ f'Cannot delete interface "{peth["intf"]}" as it is a '
+ f'member of bridge "{peth["is_bridge_member"]}"!'))
return None
if not peth['source_interface']:
- raise ConfigError('source-interface must be set for virtual ethernet {}'.format(peth['intf']))
+ raise ConfigError((
+ f'Link device must be set for pseudo-ethernet "{peth["intf"]}"'))
if not peth['source_interface'] in interfaces():
- raise ConfigError('Pseudo-ethernet source-interface does not exist')
+ raise ConfigError((
+ f'Pseudo-ethernet "{peth["intf"]}" link device does not exist')
- vrf_name = peth['vrf']
- if vrf_name and vrf_name not in interfaces():
- raise ConfigError(f'VRF "{vrf_name}" does not exist')
+ if ( peth['is_bridge_member']
+ and ( peth['address']
+ or peth['ipv6_eui64_prefix']
+ or peth['ipv6_autoconf'] ) ):
+ raise ConfigError((
+ f'Cannot assign address to interface "{peth["intf"]}" '
+ f'as it is a member of bridge "{peth["is_bridge_member"]}"!'))
+
+ if peth['vrf']:
+ if peth['vrf'] not in interfaces():
+ raise ConfigError(f'VRF "{peth["vrf"]}" does not exist')
+
+ if peth['is_bridge_member']:
+ raise ConfigError((
+ f'Interface "{peth["intf"]}" cannot be member of VRF '
+ f'"{peth["vrf"]}" and bridge {peth["is_bridge_member"]} '
+ f'at the same time!'))
# use common function to verify VLAN configuration
verify_vlan_config(peth)
@@ -203,8 +215,10 @@ def apply(peth):
# IPv6 Duplicate Address Detection (DAD) tries
p.set_ipv6_dad_messages(peth['ipv6_dup_addr_detect'])
- # assign/remove VRF
- p.set_vrf(peth['vrf'])
+ # assign/remove VRF (ONLY when not a member of a bridge,
+ # otherwise 'nomaster' removes it from it)
+ if not peth['is_bridge_member']:
+ p.set_vrf(peth['vrf'])
# Delete old IPv6 EUI64 addresses before changing MAC
for addr in peth['ipv6_eui64_prefix_remove']:
@@ -235,6 +249,10 @@ def apply(peth):
for addr in peth['address']:
p.add_addr(addr)
+ # re-add ourselves to any bridge we might have fallen out of
+ if peth['is_bridge_member']:
+ p.add_to_bridge(peth['is_bridge_member'])
+
# remove no longer required service VLAN interfaces (vif-s)
for vif_s in peth['vif_s_remove']:
p.del_vlan(vif_s)
diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py
index fc084814a..f4cd53981 100755
--- a/src/conf_mode/interfaces-tunnel.py
+++ b/src/conf_mode/interfaces-tunnel.py
@@ -25,7 +25,7 @@ from vyos.config import Config
from vyos.ifconfig import Interface, GREIf, GRETapIf, IPIPIf, IP6GREIf, IPIP6If, IP6IP6If, SitIf, Sit6RDIf
from vyos.ifconfig.afi import IP4, IP6
from vyos.configdict import list_diff
-from vyos.validate import is_ipv4, is_ipv6, is_bridge_member
+from vyos.validate import is_ipv4, is_ipv6, is_member
from vyos import ConfigError
from vyos.dicts import FixedDict
@@ -410,7 +410,7 @@ def get_config():
options['tunnel'] = {}
# check for bridges
- options['bridge'] = is_bridge_member(conf, ifname)
+ options['bridge'] = is_member(conf, ifname, 'bridge')
options['interfaces'] = interfaces()
for name in ct:
@@ -436,11 +436,14 @@ def verify(conf):
if changes['section'] == 'delete':
if ifname in options['nhrp']:
- raise ConfigError(f'Can not delete interface tunnel {iftype} {ifname}, it is used by nhrp')
+ raise ConfigError((
+ f'Cannot delete interface tunnel {iftype} {ifname}, '
+ 'it is used by NHRP'))
- bridge = options['bridge']
- if bridge:
- raise ConfigError(f'Interface "{ifname}" can not be deleted as it belongs to bridge "{bridge}"!')
+ if options['bridge']:
+ raise ConfigError((
+ f'Cannot delete interface "{options["ifname"]}" as it is a '
+ f'member of bridge "{options["bridge"]}"!'))
# done, bail out early
return None
@@ -525,10 +528,23 @@ def verify(conf):
print(f'Should not use IPv6 addresses on tunnel {iftype} {ifname}')
# vrf check
-
- vrf = options['vrf']
- if vrf and vrf not in options['interfaces']:
- raise ConfigError(f'VRF "{vrf}" does not exist')
+ if options['vrf']:
+ if options['vrf'] not in options['interfaces']:
+ raise ConfigError(f'VRF "{options["vrf"]}" does not exist')
+
+ if options['bridge']:
+ raise ConfigError((
+ f'Interface "{options["ifname"]}" cannot be member of VRF '
+ f'"{options["vrf"]}" and bridge {options["bridge"]} '
+ f'at the same time!'))
+
+ # bridge and address check
+ if ( options['bridge']
+ and ( options['addresses-add']
+ or options['ipv6_autoconf'] ) ):
+ raise ConfigError((
+ f'Cannot assign address to interface "{options["name"]}" '
+ f'as it is a member of bridge "{options["bridge"]}"!'))
# source-interface check
@@ -620,12 +636,17 @@ def apply(conf):
# set other interface properties
for option in ('alias', 'mtu', 'link_detect', 'multicast', 'allmulticast',
- 'vrf', 'ipv6_autoconf', 'ipv6_forwarding', 'ipv6_dad_transmits'):
+ 'ipv6_autoconf', 'ipv6_forwarding', 'ipv6_dad_transmits'):
if not options[option]:
# should never happen but better safe
continue
tunnel.set_interface(option, options[option])
+ # assign/remove VRF (ONLY when not a member of a bridge,
+ # otherwise 'nomaster' removes it from it)
+ if not options['bridge']:
+ tunnel.set_vrf(options['vrf'])
+
# Configure interface address(es)
for addr in options['addresses-del']:
tunnel.del_addr(addr)
diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py
index 74eae4281..fabfaa9df 100755
--- a/src/conf_mode/interfaces-vxlan.py
+++ b/src/conf_mode/interfaces-vxlan.py
@@ -22,7 +22,7 @@ from netifaces import interfaces
from vyos.config import Config
from vyos.ifconfig import VXLANIf, Interface
-from vyos.validate import is_bridge_member
+from vyos.validate import is_member
from vyos import ConfigError
default_config_data = {
@@ -62,11 +62,12 @@ def get_config():
vxlan['intf'] = os.environ['VYOS_TAGNODE_VALUE']
+ # check if interface is member if a bridge
+ vxlan['is_bridge_member'] = is_member(conf, vxlan['intf'], 'bridge')
+
# Check if interface has been removed
if not conf.exists('interfaces vxlan ' + vxlan['intf']):
vxlan['deleted'] = True
- # check if interface is member if a bridge
- vxlan['is_bridge_member'] = is_bridge_member(conf, vxlan['intf'])
return vxlan
# set new configuration level
@@ -121,7 +122,8 @@ def get_config():
vxlan['ipv6_eui64_prefix'] = conf.return_values('ipv6 address eui64')
# Remove the default link-local address if set.
- if not conf.exists('ipv6 address no-default-link-local'):
+ if not ( conf.exists('ipv6 address no-default-link-local')
+ or vxlan['is_bridge_member'] ):
# add the link-local by default to make IPv6 work
vxlan['ipv6_eui64_prefix'].append('fe80::/64')
@@ -163,9 +165,9 @@ def get_config():
def verify(vxlan):
if vxlan['deleted']:
if vxlan['is_bridge_member']:
- interface = vxlan['intf']
- bridge = vxlan['is_bridge_member']
- raise ConfigError(f'Interface "{interface}" can not be deleted as it belongs to bridge "{bridge}"!')
+ raise ConfigError((
+ f'Cannot delete interface "{vxlan["intf"]}" as it is a '
+ f'member of bridge "{vxlan["is_bridge_member"]}"!')
return None
@@ -193,6 +195,14 @@ def verify(vxlan):
raise ConfigError('VXLAN has a 50 byte overhead, underlaying device ' \
'MTU is to small ({})'.format(underlay_mtu))
+ if ( vxlan['is_bridge_member']
+ and ( vxlan['address']
+ or vxlan['ipv6_eui64_prefix']
+ or vxlan['ipv6_autoconf'] ) ):
+ raise ConfigError((
+ f'Cannot assign address to interface "{vxlan["intf"]}" '
+ f'as it is a member of bridge "{vxlan["is_bridge_member"]}"!'))
+
return None
@@ -264,6 +274,10 @@ def apply(vxlan):
if not vxlan['disable']:
v.set_admin_state('up')
+ # re-add ourselves to any bridge we might have fallen out of
+ if vxlan['is_bridge_member']:
+ v.add_to_bridge(vxlan['is_bridge_member'])
+
return None
diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py
index 01f84260d..820b0a724 100755
--- a/src/conf_mode/interfaces-wireguard.py
+++ b/src/conf_mode/interfaces-wireguard.py
@@ -25,7 +25,7 @@ from vyos.config import Config
from vyos.configdict import list_diff
from vyos.ifconfig import WireGuardIf
from vyos.util import chown, chmod_750, call
-from vyos.validate import is_bridge_member
+from vyos.validate import is_member
from vyos import ConfigError
kdir = r'/config/auth/wireguard'
@@ -38,8 +38,8 @@ default_config_data = {
'listen_port': '',
'deleted': False,
'disable': False,
- 'is_bridge_member': False,
'fwmark': 0,
+ 'is_bridge_member': False,
'mtu': 1420,
'peer': [],
'peer_remove': [], # stores public keys of peers to remove
@@ -78,11 +78,12 @@ def get_config():
wg = deepcopy(default_config_data)
wg['intf'] = os.environ['VYOS_TAGNODE_VALUE']
+ # check if interface is member if a bridge
+ wg['is_bridge_member'] = is_member(conf, wg['intf'], 'bridge')
+
# Check if interface has been removed
if not conf.exists(base + [wg['intf']]):
wg['deleted'] = True
- # check if interface is member if a bridge
- wg['is_bridge_member'] = is_bridge_member(conf, wg['intf'])
return wg
conf.set_level(base + [wg['intf']])
@@ -189,44 +190,52 @@ def get_config():
def verify(wg):
- interface = wg['intf']
-
if wg['deleted']:
if wg['is_bridge_member']:
- interface = wg['intf']
- bridge = wg['is_bridge_member']
- raise ConfigError(f'Interface "{interface}" can not be deleted as it belongs to bridge "{bridge}"!')
+ raise ConfigError((
+ f'Cannot delete interface "{wg["intf"]}" as it is a member '
+ f'of bridge "{wg["is_bridge_member"]}"!'))
return None
- vrf_name = wg['vrf']
- if vrf_name and vrf_name not in interfaces():
- raise ConfigError(f'VRF "{vrf_name}" does not exist')
+ if wg['is_bridge_member'] and wg['address']:
+ raise ConfigError((
+ f'Cannot assign address to interface "{wg["intf"]}" '
+ f'as it is a member of bridge "{wg["is_bridge_member"]}"!'))
+
+ if wg['vrf']:
+ if wg['vrf'] not in interfaces():
+ raise ConfigError(f'VRF "{wg["vrf"]}" does not exist')
+
+ if wg['is_bridge_member']:
+ raise ConfigError((
+ f'Interface "{wg["intf"]}" cannot be member of VRF '
+ f'"{wg["vrf"]}" and bridge {wg["is_bridge_member"]} '
+ f'at the same time!'))
if not os.path.exists(wg['pk']):
raise ConfigError('No keys found, generate them by executing:\n' \
'"run generate wireguard [keypair|named-keypairs]"')
if not wg['address']:
- raise ConfigError(f'IP address required for interface "{interface}"!')
+ raise ConfigError(f'IP address required for interface "{wg["intf"]}"!')
if not wg['peer']:
- raise ConfigError(f'Peer required for interface "{interface}"!')
+ raise ConfigError(f'Peer required for interface "{wg["intf"]}"!')
# run checks on individual configured WireGuard peer
for peer in wg['peer']:
- peer_name = peer['name']
if not peer['allowed-ips']:
- raise ConfigError(f'Peer allowed-ips required for peer "{peer_name}"!')
+ raise ConfigError(f'Peer allowed-ips required for peer "{peer["name"]}"!')
if not peer['pubkey']:
- raise ConfigError(f'Peer public-key required for peer "{peer_name}"!')
+ raise ConfigError(f'Peer public-key required for peer "{peer["name"]}"!')
if peer['address'] and not peer['port']:
- raise ConfigError(f'Peer "{peer_name}" port must be defined if address is defined!')
+ raise ConfigError(f'Peer "{peer["name"]}" port must be defined if address is defined!')
if not peer['address'] and peer['port']:
- raise ConfigError(f'Peer "{peer_name}" address must be defined if port is defined!')
+ raise ConfigError(f'Peer "{peer["name"]}" address must be defined if port is defined!')
def apply(wg):
@@ -252,8 +261,10 @@ def apply(wg):
# update interface description used e.g. within SNMP
w.set_alias(wg['description'])
- # assign/remove VRF
- w.set_vrf(wg['vrf'])
+ # assign/remove VRF (ONLY when not a member of a bridge,
+ # otherwise 'nomaster' removes it from it)
+ if not wg['is_bridge_member']:
+ w.set_vrf(wg['vrf'])
# remove peers
for pub_key in wg['peer_remove']:
diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py
index 148a7f6e0..4d61dc303 100755
--- a/src/conf_mode/interfaces-wireless.py
+++ b/src/conf_mode/interfaces-wireless.py
@@ -29,7 +29,7 @@ from vyos.ifconfig import WiFiIf, Section
from vyos.ifconfig_vlan import apply_vlan_config, verify_vlan_config
from vyos.template import render
from vyos.util import chown, call
-from vyos.validate import is_bridge_member
+from vyos.validate import is_member
from vyos import ConfigError
default_config_data = {
@@ -134,12 +134,13 @@ def get_config():
wifi['intf'] = os.environ['VYOS_TAGNODE_VALUE']
+ # check if interface is member if a bridge
+ wifi['is_bridge_member'] = is_member(conf, wifi['intf'], 'bridge')
+
# check if wireless interface has been removed
cfg_base = 'interfaces wireless ' + wifi['intf']
if not conf.exists(cfg_base):
wifi['deleted'] = True
- # check if interface is member if a bridge
- wifi['is_bridge_member'] = is_bridge_member(conf, wifi['intf'])
# we can not bail out early as wireless interface can not be removed
# Kernel will complain with: RTNETLINK answers: Operation not supported.
# Thus we need to remove individual settings
@@ -378,8 +379,8 @@ def get_config():
eff_addr = conf.return_effective_values('ipv6 address eui64')
wifi['ipv6_eui64_prefix_remove'] = list_diff(eff_addr, wifi['ipv6_eui64_prefix'])
- # Remove the default link-local address if set.
- if conf.exists('ipv6 address no-default-link-local'):
+ # Remove the default link-local address if set or if member of a bridge
+ if conf.exists('ipv6 address no-default-link-local') or wifi['is_bridge_member']:
wifi['ipv6_eui64_prefix_remove'].append('fe80::/64')
else:
# add the link-local by default to make IPv6 work
@@ -551,9 +552,9 @@ def get_config():
def verify(wifi):
if wifi['deleted']:
if wifi['is_bridge_member']:
- interface = wifi['intf']
- bridge = wifi['is_bridge_member']
- raise ConfigError(f'Interface "{interface}" can not be deleted as it belongs to bridge "{bridge}"!')
+ raise ConfigError((
+ f'Cannot delete interface "{wifi["intf"]}" as it is a '
+ f'member of bridge "{wifi["is_bridge_member"]}"!'))
return None
@@ -598,9 +599,23 @@ def verify(wifi):
if not radius['key']:
raise ConfigError('Misssing RADIUS shared secret key for server: {}'.format(radius['server']))
- vrf_name = wifi['vrf']
- if vrf_name and vrf_name not in interfaces():
- raise ConfigError(f'VRF "{vrf_name}" does not exist')
+ if ( wifi['is_bridge_member']
+ and ( wifi['address']
+ or wifi['ipv6_eui64_prefix']
+ or wifi['ipv6_autoconf'] ) ):
+ raise ConfigError((
+ f'Cannot assign address to interface "{wifi["intf"]}" '
+ f'as it is a member of bridge "{wifi["is_bridge_member"]}"!'))
+
+ if wifi['vrf']:
+ if wifi['vrf'] not in interfaces():
+ raise ConfigError(f'VRF "{wifi["vrf"]}" does not exist')
+
+ if wifi['is_bridge_member']:
+ raise ConfigError((
+ f'Interface "{wifi["intf"]}" cannot be member of VRF '
+ f'"{wifi["vrf"]}" and bridge {wifi["is_bridge_member"]} '
+ f'at the same time!'))
# use common function to verify VLAN configuration
verify_vlan_config(wifi)
@@ -691,8 +706,10 @@ def apply(wifi):
# Finally create the new interface
w = WiFiIf(interface, **conf)
- # assign/remove VRF
- w.set_vrf(wifi['vrf'])
+ # assign/remove VRF (ONLY when not a member of a bridge,
+ # otherwise 'nomaster' removes it from it)
+ if not wifi['is_bridge_member']:
+ w.set_vrf(wifi['vrf'])
# update interface description used e.g. within SNMP
w.set_alias(wifi['description'])
diff --git a/src/conf_mode/interfaces-wirelessmodem.py b/src/conf_mode/interfaces-wirelessmodem.py
index a3a2a2648..975e21d9f 100755
--- a/src/conf_mode/interfaces-wirelessmodem.py
+++ b/src/conf_mode/interfaces-wirelessmodem.py
@@ -21,9 +21,10 @@ from copy import deepcopy
from netifaces import interfaces
from vyos.config import Config
+from vyos.ifconfig import BridgeIf, Section
from vyos.template import render
from vyos.util import chown, chmod_755, cmd, call
-from vyos.validate import is_bridge_member
+from vyos.validate import is_member
from vyos import ConfigError
default_config_data = {
@@ -64,11 +65,12 @@ def get_config():
wwan['logfile'] = f"/var/log/vyatta/ppp_{wwan['intf']}.log"
wwan['chat_script'] = f"/etc/ppp/peers/chat.{wwan['intf']}"
+ # check if interface is member if a bridge
+ wwan['is_bridge_member'] = is_member(conf, wwan['intf'], 'bridge')
+
# Check if interface has been removed
if not conf.exists('interfaces wirelessmodem ' + wwan['intf']):
wwan['deleted'] = True
- # check if interface is member if a bridge
- wwan['is_bridge_member'] = is_bridge_member(conf, wwan['intf'])
return wwan
# set new configuration level
@@ -119,9 +121,9 @@ def get_config():
def verify(wwan):
if wwan['deleted']:
if wwan['is_bridge_member']:
- interface = wwan['intf']
- bridge = wwan['is_bridge_member']
- raise ConfigError(f'Interface "{interface}" can not be deleted as it belongs to bridge "{bridge}"!')
+ raise ConfigError((
+ f'Cannot delete interface "{wwan["intf"]}" as it is a '
+ f'member of bridge "{wwan["is_bridge_member"]}"!'))
return None
@@ -133,9 +135,20 @@ def verify(wwan):
if not os.path.exists(f"/dev/{wwan['device']}"):
raise ConfigError(f"Device {wwan['device']} does not exist")
- vrf_name = wwan['vrf']
- if vrf_name and vrf_name not in interfaces():
- raise ConfigError(f'VRF {vrf_name} does not exist')
+ if wwan['is_bridge_member'] and wwan['address']:
+ raise ConfigError((
+ f'Cannot assign address to interface "{wwan["intf"]}" '
+ f'as it is a member of bridge "{wwan["is_bridge_member"]}"!'))
+
+ if wwan['vrf']:
+ if wwan['vrf'] not in interfaces():
+ raise ConfigError(f'VRF "{wwan["vrf"]}" does not exist')
+
+ if wwan['is_bridge_member']:
+ raise ConfigError((
+ f'Interface "{wwan["intf"]}" cannot be member of VRF '
+ f'"{wwan["vrf"]}" and bridge {wwan["is_bridge_member"]} '
+ f'at the same time!'))
return None
@@ -193,6 +206,12 @@ def apply(wwan):
# make logfile owned by root / vyattacfg
chown(wwan['logfile'], 'root', 'vyattacfg')
+ # re-add ourselves to any bridge we might have fallen out of
+ # FIXME: wwan isn't under vyos.ifconfig so we can't call
+ # Interfaces.add_to_bridge() so STP settings won't get applied
+ if wwan['is_bridge_member'] in Section.interfaces('bridge'):
+ BridgeIf(wwan['is_bridge_member'], create=False).add_port(wwan['intf'])
+
return None
if __name__ == '__main__':