Page MenuHomeVyOS Platform

interfaces-bridge.py
No OneTemporary

Size
14 KB
Referenced Files
None
Subscribers
None

interfaces-bridge.py

#!/usr/bin/env python3
#
# Copyright (C) 2019-2020 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 BridgeIf, Section
from vyos.ifconfig.stp import STP
from vyos.configdict import list_diff, interface_default_data
from vyos.validate import is_member, has_address_configured
from vyos.config import Config
from vyos.util import cmd, get_bridge_member_config
from vyos import ConfigError
from vyos import airbag
airbag.enable()
default_config_data = {
**interface_default_data,
'aging': 300,
'arp_cache_tmo': 30,
'deleted': False,
'forwarding_delay': 14,
'hello_time': 2,
'igmp_querier': 0,
'intf': '',
'max_age': 20,
'member': [],
'member_remove': [],
'priority': 32768,
'stp': 0
}
def get_config():
bridge = deepcopy(default_config_data)
conf = Config()
# determine tagNode instance
if 'VYOS_TAGNODE_VALUE' not in os.environ:
raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified')
bridge['intf'] = os.environ['VYOS_TAGNODE_VALUE']
# Check if bridge has been removed
if not conf.exists('interfaces bridge ' + bridge['intf']):
bridge['deleted'] = True
return bridge
# set new configuration level
conf.set_level('interfaces bridge ' + bridge['intf'])
# retrieve configured interface addresses
if conf.exists('address'):
bridge['address'] = conf.return_values('address')
# Determine interface addresses (currently effective) - to determine which
# address is no longer valid and needs to be removed
eff_addr = conf.return_effective_values('address')
bridge['address_remove'] = list_diff(eff_addr, bridge['address'])
# retrieve aging - how long addresses are retained
if conf.exists('aging'):
bridge['aging'] = int(conf.return_value('aging'))
# retrieve interface description
if conf.exists('description'):
bridge['description'] = conf.return_value('description')
# get DHCP client identifier
if conf.exists('dhcp-options client-id'):
bridge['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'):
bridge['dhcp_hostname'] = conf.return_value('dhcp-options host-name')
# DHCP client vendor identifier
if conf.exists('dhcp-options vendor-class-id'):
bridge['dhcp_vendor_class_id'] = conf.return_value('dhcp-options vendor-class-id')
# DHCPv6 only acquire config parameters, no address
if conf.exists('dhcpv6-options parameters-only'):
bridge['dhcpv6_prm_only'] = True
# DHCPv6 temporary IPv6 address
if conf.exists('dhcpv6-options temporary'):
bridge['dhcpv6_temporary'] = True
# Disable this bridge interface
if conf.exists('disable'):
bridge['disable'] = True
# Ignore link state changes
if conf.exists('disable-link-detect'):
bridge['disable_link_detect'] = 2
# Forwarding delay
if conf.exists('forwarding-delay'):
bridge['forwarding_delay'] = int(conf.return_value('forwarding-delay'))
# Hello packet advertisment interval
if conf.exists('hello-time'):
bridge['hello_time'] = int(conf.return_value('hello-time'))
# Enable Internet Group Management Protocol (IGMP) querier
if conf.exists('igmp querier'):
bridge['igmp_querier'] = 1
# ARP cache entry timeout in seconds
if conf.exists('ip arp-cache-timeout'):
bridge['arp_cache_tmo'] = int(conf.return_value('ip arp-cache-timeout'))
# ARP filter configuration
if conf.exists('ip disable-arp-filter'):
bridge['ip_disable_arp_filter'] = 0
# ARP enable accept
if conf.exists('ip enable-arp-accept'):
bridge['ip_enable_arp_accept'] = 1
# ARP enable announce
if conf.exists('ip enable-arp-announce'):
bridge['ip_enable_arp_announce'] = 1
# ARP enable ignore
if conf.exists('ip enable-arp-ignore'):
bridge['ip_enable_arp_ignore'] = 1
# Enable acquisition of IPv6 address using stateless autoconfig (SLAAC)
if conf.exists('ipv6 address autoconf'):
bridge['ipv6_autoconf'] = 1
# Get prefixes for IPv6 addressing based on MAC address (EUI-64)
if conf.exists('ipv6 address eui64'):
bridge['ipv6_eui64_prefix'] = conf.return_values('ipv6 address eui64')
# Determine currently effective EUI64 addresses - to determine which
# address is no longer valid and needs to be removed
eff_addr = conf.return_effective_values('ipv6 address eui64')
bridge['ipv6_eui64_prefix_remove'] = list_diff(eff_addr, bridge['ipv6_eui64_prefix'])
# Remove the default link-local address if set.
if conf.exists('ipv6 address no-default-link-local'):
bridge['ipv6_eui64_prefix_remove'].append('fe80::/64')
else:
# add the link-local by default to make IPv6 work
bridge['ipv6_eui64_prefix'].append('fe80::/64')
# Disable IPv6 forwarding on this interface
if conf.exists('ipv6 disable-forwarding'):
bridge['ipv6_forwarding'] = 0
# IPv6 Duplicate Address Detection (DAD) tries
if conf.exists('ipv6 dup-addr-detect-transmits'):
bridge['ipv6_dup_addr_detect'] = int(conf.return_value('ipv6 dup-addr-detect-transmits'))
# Media Access Control (MAC) address
if conf.exists('mac'):
bridge['mac'] = conf.return_value('mac')
# Find out if MAC has changed - if so, we need to delete all IPv6 EUI64 addresses
# before re-adding them
if ( bridge['mac'] and bridge['intf'] in Section.interfaces(section='bridge')
and bridge['mac'] != BridgeIf(bridge['intf'], create=False).get_mac() ):
bridge['ipv6_eui64_prefix_remove'] += bridge['ipv6_eui64_prefix']
# to make IPv6 SLAAC and DHCPv6 work with forwarding=1,
# accept_ra must be 2
if bridge['ipv6_autoconf'] or 'dhcpv6' in bridge['address']:
bridge['ipv6_accept_ra'] = 2
# Interval at which neighbor bridges are removed
if conf.exists('max-age'):
bridge['max_age'] = int(conf.return_value('max-age'))
# Determine bridge member interface (currently configured)
for intf in conf.list_nodes('member interface'):
# 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
eff_intf = conf.list_effective_nodes('member interface')
act_intf = conf.list_nodes('member interface')
bridge['member_remove'] = list_diff(eff_intf, act_intf)
# Priority for this bridge
if conf.exists('priority'):
bridge['priority'] = int(conf.return_value('priority'))
# Enable spanning tree protocol
if conf.exists('stp'):
bridge['stp'] = 1
# retrieve VRF instance
if conf.exists('vrf'):
bridge['vrf'] = conf.return_value('vrf')
return bridge
def verify(bridge):
if bridge['dhcpv6_prm_only'] and bridge['dhcpv6_temporary']:
raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!')
vrf_name = bridge['vrf']
if vrf_name and vrf_name not in interfaces():
raise ConfigError(f'VRF "{vrf_name}" does not exist')
conf = Config()
for intf in bridge['member']:
# the interface must exist prior adding it to a bridge
if intf['name'] not in interfaces():
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 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
def generate(bridge):
return None
def apply(bridge):
br = BridgeIf(bridge['intf'])
if bridge['deleted']:
# delete interface
br.remove()
else:
# enable interface
br.set_admin_state('up')
# set ageing time
br.set_ageing_time(bridge['aging'])
# set bridge forward delay
br.set_forward_delay(bridge['forwarding_delay'])
# set hello time
br.set_hello_time(bridge['hello_time'])
# configure ARP filter configuration
br.set_arp_filter(bridge['ip_disable_arp_filter'])
# configure ARP accept
br.set_arp_accept(bridge['ip_enable_arp_accept'])
# configure ARP announce
br.set_arp_announce(bridge['ip_enable_arp_announce'])
# configure ARP ignore
br.set_arp_ignore(bridge['ip_enable_arp_ignore'])
# IPv6 accept RA
br.set_ipv6_accept_ra(bridge['ipv6_accept_ra'])
# IPv6 address autoconfiguration
br.set_ipv6_autoconf(bridge['ipv6_autoconf'])
# IPv6 forwarding
br.set_ipv6_forwarding(bridge['ipv6_forwarding'])
# IPv6 Duplicate Address Detection (DAD) tries
br.set_ipv6_dad_messages(bridge['ipv6_dup_addr_detect'])
# set max message age
br.set_max_age(bridge['max_age'])
# set bridge priority
br.set_priority(bridge['priority'])
# turn stp on/off
br.set_stp(bridge['stp'])
# enable or disable IGMP querier
br.set_multicast_querier(bridge['igmp_querier'])
# update interface description used e.g. within SNMP
br.set_alias(bridge['description'])
if bridge['dhcp_client_id']:
br.dhcp.v4.options['client_id'] = bridge['dhcp_client_id']
if bridge['dhcp_hostname']:
br.dhcp.v4.options['hostname'] = bridge['dhcp_hostname']
if bridge['dhcp_vendor_class_id']:
br.dhcp.v4.options['vendor_class_id'] = bridge['dhcp_vendor_class_id']
if bridge['dhcpv6_prm_only']:
br.dhcp.v6.options['dhcpv6_prm_only'] = True
if bridge['dhcpv6_temporary']:
br.dhcp.v6.options['dhcpv6_temporary'] = True
if bridge['dhcpv6_pd_length']:
br.dhcp.v6.options['dhcpv6_pd_length'] = br['dhcpv6_pd_length']
if bridge['dhcpv6_pd_interfaces']:
br.dhcp.v6.options['dhcpv6_pd_interfaces'] = br['dhcpv6_pd_interfaces']
# assign/remove VRF
br.set_vrf(bridge['vrf'])
# Delete old IPv6 EUI64 addresses before changing MAC
# (adding members to a fresh bridge changes its MAC too)
for addr in bridge['ipv6_eui64_prefix_remove']:
br.del_ipv6_eui64_address(addr)
# remove interface from bridge
for intf in bridge['member_remove']:
br.del_port(intf)
# add interfaces to bridge
for member in bridge['member']:
# 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'])
# Change interface MAC address
if bridge['mac']:
br.set_mac(bridge['mac'])
# Add IPv6 EUI-based addresses (must be done after adding the
# 1st bridge member or setting its MAC)
for addr in bridge['ipv6_eui64_prefix']:
br.add_ipv6_eui64_address(addr)
# up/down interface
if bridge['disable']:
br.set_admin_state('down')
# Configure interface address(es)
# - not longer required addresses get removed first
# - newly addresses will be added second
for addr in bridge['address_remove']:
br.del_addr(addr)
for addr in bridge['address']:
br.add_addr(addr)
STPBridgeIf = STP.enable(BridgeIf)
# configure additional bridge member options
for member in bridge['member']:
i = STPBridgeIf(member['name'])
# configure ARP cache timeout
i.set_arp_cache_tmo(member['arp_cache_tmo'])
# ignore link state changes
i.set_link_detect(member['disable_link_detect'])
# set bridge port path cost
i.set_path_cost(member['cost'])
# set bridge port path priority
i.set_path_priority(member['priority'])
return None
if __name__ == '__main__':
try:
c = get_config()
verify(c)
generate(c)
apply(c)
except ConfigError as e:
print(e)
exit(1)

File Metadata

Mime Type
text/x-script.python
Expires
Sat, Oct 17, 12:50 AM (1 d, 7 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
4354804
Default Alt Text
interfaces-bridge.py (14 KB)

Event Timeline