Page MenuHomeVyOS Platform

interfaces-bonding.py
No OneTemporary

Size
18 KB
Referenced Files
None
Subscribers
None

interfaces-bonding.py

#!/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
from vyos.ifconfig_vlan import apply_vlan_config
from vyos.configdict import list_diff, vlan_to_dict
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': '',
'dhcp_vendor_class_id': '',
'dhcpv6_prm_only': False,
'dhcpv6_temporary': False,
'disable': False,
'disable_link_detect': 1,
'hash_policy': 'layer2',
'ip_arp_cache_tmo': 30,
'ip_disable_arp_filter': 1,
'ip_enable_arp_accept': 0,
'ip_enable_arp_announce': 0,
'ip_enable_arp_ignore': 0,
'ip_proxy_arp': 0,
'ip_proxy_arp_pvlan': 0,
'intf': '',
'mac': '',
'mode': '802.3ad',
'member': [],
'shutdown_required': False,
'mtu': 1500,
'primary': '',
'vif_s': [],
'vif_s_remove': [],
'vif': [],
'vif_remove': []
}
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_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')
# get interface addresses (currently effective) - to determine which
# address is no longer valid and needs to be removed
eff_addr = conf.return_effective_values('address')
bond['address_remove'] = list_diff(eff_addr, bond['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')
# 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')
# DHCP client vendor identifier
if conf.exists('dhcp-options vendor-class-id'):
bond['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'):
bond['dhcpv6_prm_only'] = True
# DHCPv6 temporary IPv6 address
if conf.exists('dhcpv6-options temporary'):
bond['dhcpv6_temporary'] = True
# 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'))
# ARP filter configuration
if conf.exists('ip disable-arp-filter'):
bond['ip_disable_arp_filter'] = 0
# ARP enable accept
if conf.exists('ip enable-arp-accept'):
bond['ip_enable_arp_accept'] = 1
# ARP enable announce
if conf.exists('ip enable-arp-announce'):
bond['ip_enable_arp_announce'] = 1
# ARP enable ignore
if conf.exists('ip enable-arp-ignore'):
bond['ip_enable_arp_ignore'] = 1
# 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'):
act_mode = conf.return_value('mode')
eff_mode = conf.return_effective_value('mode')
if not (act_mode == eff_mode):
bond['shutdown_required'] = True
bond['mode'] = get_bond_mode(act_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')
eff_member = conf.return_effective_values('member interface')
if not (bond['member'] == eff_member):
bond['shutdown_required'] = True
# Primary device interface
if conf.exists('primary'):
bond['primary'] = conf.return_value('primary')
# re-set configuration level to parse new nodes
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'] = list_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 to parse new nodes
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'] = list_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']))
# DHCPv6 parameters-only and temporary address are mutually exclusive
for vif_s in bond['vif_s']:
if vif_s['dhcpv6_prm_only'] and vif_s['dhcpv6_temporary']:
raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!')
for vif_c in vif_s['vif_c']:
if vif_c['dhcpv6_prm_only'] and vif_c['dhcpv6_temporary']:
raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!')
for vif in bond['vif']:
if vif['dhcpv6_prm_only'] and vif['dhcpv6_temporary']:
raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!')
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']:
# 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))
# 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
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))
# 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))
# 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 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 interface
b.remove()
else:
# ARP link monitoring frequency, reset miimon when arp-montior is inactive
# this is done inside BondIf automatically
b.set_arp_interval(bond['arp_mon_intvl'])
# 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.
arp_tgt_addr = list(map(str, b.get_arp_ip_target().split()))
for addr in arp_tgt_addr:
b.set_arp_ip_target('-' + addr)
# Add configured ARP target addresses
for addr in bond['arp_mon_tgt']:
b.set_arp_ip_target('+' + addr)
# update interface description used e.g. within SNMP
b.set_alias(bond['description'])
# get DHCP config dictionary and update values
opt = b.get_dhcp_options()
if bond['dhcp_client_id']:
opt['client_id'] = bond['dhcp_client_id']
if bond['dhcp_hostname']:
opt['hostname'] = bond['dhcp_hostname']
if bond['dhcp_vendor_class_id']:
opt['vendor_class_id'] = bond['dhcp_vendor_class_id']
# store DHCP config dictionary - used later on when addresses are aquired
b.set_dhcp_options(opt)
# get DHCPv6 config dictionary and update values
opt = b.get_dhcpv6_options()
if bond['dhcpv6_prm_only']:
opt['dhcpv6_prm_only'] = True
if bond['dhcpv6_temporary']:
opt['dhcpv6_temporary'] = True
# store DHCPv6 config dictionary - used later on when addresses are required
b.set_dhcpv6_options(opt)
# ignore link state changes
b.set_link_detect(bond['disable_link_detect'])
# Bonding transmit hash policy
b.set_hash_policy(bond['hash_policy'])
# configure ARP cache timeout in milliseconds
b.set_arp_cache_tmo(bond['ip_arp_cache_tmo'])
# configure ARP filter configuration
b.set_arp_filter(bond['ip_disable_arp_filter'])
# configure ARP accept
b.set_arp_accept(bond['ip_enable_arp_accept'])
# configure ARP announce
b.set_arp_announce(bond['ip_enable_arp_announce'])
# configure ARP ignore
b.set_arp_ignore(bond['ip_enable_arp_ignore'])
# Enable proxy-arp on this interface
b.set_proxy_arp(bond['ip_proxy_arp'])
# Enable private VLAN proxy ARP on this interface
b.set_proxy_arp_pvlan(bond['ip_proxy_arp_pvlan'])
# Change interface MAC address
if bond['mac']:
b.set_mac(bond['mac'])
# Maximum Transmission Unit (MTU)
b.set_mtu(bond['mtu'])
# Primary device interface
if bond['primary']:
b.set_primary(bond['primary'])
# Some parameters can not be changed when the bond is up.
if bond['shutdown_required']:
# Disable bond prior changing of certain properties
b.set_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)
# Bonding policy/mode
b.set_mode(bond['mode'])
# Add (enslave) interfaces to bond
for intf in bond['member']:
b.add_port(intf)
# 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.set_state('up')
else:
b.set_state('down')
# Configure interface address(es)
# - not longer required addresses get removed first
# - newly addresses will be added second
for addr in bond['address_remove']:
b.del_addr(addr)
for addr in bond['address']:
b.add_addr(addr)
# 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)
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
Tue, Dec 9, 10:51 PM (1 d, 4 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3069282
Default Alt Text
interfaces-bonding.py (18 KB)

Event Timeline