mirror of
https://github.com/CumulusNetworks/ifupdown2.git
synced 2024-05-06 15:54:50 +00:00
Add a new attribute for B.A.T.M.A.N. advanced interfaces to control the B.A.T.M.A.N. advanced routing algorithm to be used when setting up new interfaces. As the routing algorithm must be set before an interface is created, it needs special handling and can't be implemented as a common attribute. D'oh. Signed-off-by: Maximilian Wilhelm <max@sdn.clinic> Tested-by: Annika Wickert <aw@awlnx.space>
398 lines
15 KiB
Python
398 lines
15 KiB
Python
#!/usr/bin/python
|
|
#
|
|
# Copyright 2016-2017 Maximilian Wilhelm <max@sdn.clinic>
|
|
# Author: Maximilian Wilhelm, max@sdn.clinic
|
|
#
|
|
|
|
try:
|
|
from ifupdown2.ifupdown.iface import *
|
|
from ifupdown2.ifupdown.utils import utils
|
|
from ifupdown2.ifupdownaddons.modulebase import moduleBase
|
|
from ifupdown2.ifupdownaddons.LinkUtils import LinkUtils
|
|
from ifupdown2.ifupdown.netlink import netlink
|
|
from ifupdown2.ifupdown.exceptions import moduleNotSupported
|
|
import ifupdown2.ifupdown.ifupdownflags as ifupdownflags
|
|
|
|
except:
|
|
from ifupdown.iface import *
|
|
from ifupdown.utils import utils
|
|
from ifupdownaddons.modulebase import moduleBase
|
|
from ifupdownaddons.LinkUtils import LinkUtils
|
|
from ifupdown.netlink import netlink
|
|
from ifupdown.exceptions import moduleNotSupported
|
|
import ifupdown.ifupdownflags as ifupdownflags
|
|
|
|
import logging
|
|
import re
|
|
import subprocess
|
|
import os
|
|
|
|
class batman_adv (moduleBase):
|
|
""" ifupdown2 addon module to configure B.A.T.M.A.N. advanced interfaces """
|
|
|
|
_modinfo = {
|
|
'mhelp' : 'batman_adv module configures B.A.T.M.A.N. advanced interfaces.' +
|
|
'Every B.A.T.M.A.N. advanced interface needs at least on ethernet ' +
|
|
'interface to be creatable. You can specify a space separated list' +
|
|
'of interfaces by using the "batma-ifaces" paramater. If this parameter' +
|
|
'is set for an interfaces this module will do the magic.',
|
|
|
|
'attrs' : {
|
|
'batman-ifaces' : {
|
|
'help' : 'Interfaces to be part of this B.A.T.M.A.N. advanced instance',
|
|
'validvals' : [ '<interface-list>' ],
|
|
'required' : True,
|
|
},
|
|
|
|
'batman-ifaces-ignore-regex' : {
|
|
'help' : 'Interfaces to ignore when verifying configuration (regexp)',
|
|
'required' : False,
|
|
},
|
|
|
|
'batman-distributed-arp-table' : {
|
|
'help' : 'B.A.T.M.A.N. distributed ARP table',
|
|
'validvals' : [ 'enabled', 'disabled' ],
|
|
'required' : False,
|
|
'batman-attr' : True,
|
|
},
|
|
|
|
'batman-gw-mode' : {
|
|
'help' : 'B.A.T.M.A.N. gateway mode',
|
|
'validvals' : [ 'off', 'client', 'server' ],
|
|
'required' : False,
|
|
'example' : [ 'batman-gw-mode client' ],
|
|
'batman-attr' : True,
|
|
},
|
|
|
|
'batman-hop-penalty' : {
|
|
'help' : 'B.A.T.M.A.N. hop penalty',
|
|
'validvals' : [ '<number>' ],
|
|
'required' : False,
|
|
'batman-attr' : True,
|
|
},
|
|
|
|
'batman-multicast-mode' : {
|
|
'help' : 'B.A.T.M.A.N. multicast mode',
|
|
'validvals' : [ 'enabled', 'disabled' ],
|
|
'required' : False,
|
|
'batman-attr' : True,
|
|
},
|
|
|
|
'batman-routing-algo' : {
|
|
'help' : 'B.A.T.M.A.N. routing algo',
|
|
'validvals' : [ 'BATMAN_IV', 'BATMAN_V' ],
|
|
'required' : False,
|
|
'batman-attr' : False,
|
|
},
|
|
}
|
|
}
|
|
|
|
_batman_attrs = {
|
|
}
|
|
|
|
|
|
def __init__ (self, *args, **kargs):
|
|
moduleBase.__init__ (self, *args, **kargs)
|
|
if not os.path.exists('/usr/sbin/batctl'):
|
|
raise moduleNotSupported('module init failed: no /usr/sbin/batctl found')
|
|
self.ipcmd = None
|
|
|
|
for longname, entry in self._modinfo['attrs'].items ():
|
|
if entry.get ('batman-attr', False) == False:
|
|
continue
|
|
|
|
attr = longname.replace ("batman-", "")
|
|
self._batman_attrs[attr] = {
|
|
'filename' : attr.replace ("-", "_"),
|
|
}
|
|
|
|
|
|
def _is_batman_device (self, ifaceobj):
|
|
if ifaceobj.get_attr_value_first ('batman-ifaces'):
|
|
return True
|
|
return False
|
|
|
|
|
|
def _get_batman_ifaces (self, ifaceobj ):
|
|
batman_ifaces = ifaceobj.get_attr_value_first ('batman-ifaces')
|
|
if batman_ifaces:
|
|
return sorted (batman_ifaces.split ())
|
|
return None
|
|
|
|
|
|
def _get_batman_ifaces_ignore_regex (self, ifaceobj):
|
|
ifaces_ignore_regex = ifaceobj.get_attr_value_first ('batman-ifaces-ignore-regex')
|
|
if ifaces_ignore_regex:
|
|
return re.compile (r"%s" % ifaces_ignore_regex)
|
|
return None
|
|
|
|
|
|
def _get_batman_attr (self, ifaceobj, attr):
|
|
if attr not in self._batman_attrs:
|
|
raise ValueError ("_get_batman_attr: Invalid or unsupported B.A.T.M.A.N. adv. attribute: %s" % attr)
|
|
|
|
value = ifaceobj.get_attr_value_first ('batman-%s' % attr)
|
|
if value:
|
|
return value
|
|
|
|
return None
|
|
|
|
|
|
def _read_current_batman_attr (self, ifaceobj, attr, dont_map = False):
|
|
# 'routing_algo' needs special handling, D'oh.
|
|
if dont_map:
|
|
attr_file_path = "/sys/class/net/%s/mesh/%s" % (ifaceobj.name, attr)
|
|
else:
|
|
if attr not in self._batman_attrs:
|
|
raise ValueError ("_read_current_batman_attr: Invalid or unsupported B.A.T.M.A.N. adv. attribute: %s" % attr)
|
|
|
|
attr_file_name = self._batman_attrs[attr]['filename']
|
|
attr_file_path = "/sys/class/net/%s/mesh/%s" % (ifaceobj.name, attr_file_name)
|
|
|
|
try:
|
|
return self.read_file_oneline(attr_file_path)
|
|
except IOError as i:
|
|
raise Exception ("_read_current_batman_attr (%s) %s" % (attr, i))
|
|
except ValueError:
|
|
raise Exception ("_read_current_batman_attr: Integer value expected, got: %s" % value)
|
|
|
|
|
|
def _set_batman_attr (self, ifaceobj, attr, value):
|
|
if attr not in self._batman_attrs:
|
|
raise ValueError ("_set_batman_attr: Invalid or unsupported B.A.T.M.A.N. adv. attribute: %s" % attr)
|
|
|
|
attr_file_name = self._batman_attrs[attr]['filename']
|
|
attr_file_path = "/sys/class/net/%s/mesh/%s" % (ifaceobj.name, attr_file_name)
|
|
try:
|
|
self.write_file(attr_file_path, "%s\n" % value)
|
|
except IOError as i:
|
|
raise Exception ("_set_batman_attr (%s): %s" % (attr, i))
|
|
|
|
|
|
def _batctl_if (self, bat_iface, mesh_iface, op):
|
|
if op not in [ 'add', 'del' ]:
|
|
raise Exception ("_batctl_if() called with invalid \"op\" value: %s" % op)
|
|
|
|
try:
|
|
self.logger.debug ("Running batctl -m %s if %s %s" % (bat_iface, op, mesh_iface))
|
|
utils.exec_commandl(["batctl", "-m", bat_iface, "if", op, mesh_iface])
|
|
except subprocess.CalledProcessError as c:
|
|
raise Exception ("Command \"batctl -m %s if %s %s\" failed: %s" % (bat_iface, op, mesh_iface, c.output))
|
|
except Exception as e:
|
|
raise Exception ("_batctl_if: %s" % e)
|
|
|
|
def _set_routing_algo (self, routing_algo):
|
|
if routing_algo not in ['BATMAN_IV', 'BATMAN_V']:
|
|
raise Exception ("_set_routing_algo() called with invalid \"routing_algo\" value: %s" % routing_algo)
|
|
|
|
try:
|
|
self.logger.debug ("Running batctl ra %s" % routing_algo)
|
|
batctl_output = subprocess.check_output (["batctl", "ra", routing_algo], stderr = subprocess.STDOUT)
|
|
except subprocess.CalledProcessError as c:
|
|
raise Exception ("Command \"batctl ra %s\" failed: %s" % (routing_algo, c.output))
|
|
except Exception as e:
|
|
raise Exception ("_set_routing_algo: %s" % e)
|
|
|
|
|
|
def _find_member_ifaces (self, ifaceobj, ignore = True):
|
|
members = []
|
|
iface_ignore_re = self._get_batman_ifaces_ignore_regex (ifaceobj)
|
|
self.logger.info("batman: executing: %s" % " ".join(["batctl", "-m", ifaceobj.name, "if"]))
|
|
batctl_fh = subprocess.Popen (["batctl", "-m", ifaceobj.name, "if"], bufsize = 4194304, stdout = subprocess.PIPE).stdout
|
|
for line in batctl_fh.readlines ():
|
|
iface = line.split (':')[0]
|
|
if iface_ignore_re and iface_ignore_re.match (iface) and ignore:
|
|
continue
|
|
|
|
members.append (iface)
|
|
|
|
return sorted (members)
|
|
|
|
|
|
def get_dependent_ifacenames (self, ifaceobj, ifaceobjs_all=None):
|
|
if not self._is_batman_device (ifaceobj):
|
|
return None
|
|
|
|
ifaceobj.link_kind |= ifaceLinkKind.BATMAN_ADV
|
|
batman_ifaces = self._get_batman_ifaces (ifaceobj)
|
|
if batman_ifaces:
|
|
return batman_ifaces
|
|
|
|
return [None]
|
|
|
|
|
|
def _up (self, ifaceobj):
|
|
if self._get_batman_ifaces (ifaceobj) == None:
|
|
raise Exception ('could not determine batman interfacaes')
|
|
|
|
# Verify existance of batman interfaces (should be present already)
|
|
batman_ifaces = []
|
|
for iface in self._get_batman_ifaces (ifaceobj):
|
|
if not self.ipcmd.link_exists (iface):
|
|
self.logger.warn ('batman iface %s not present' % iface)
|
|
continue
|
|
|
|
batman_ifaces.append (iface)
|
|
|
|
if len (batman_ifaces) == 0:
|
|
raise Exception ("None of the configured batman interfaces are available!")
|
|
|
|
routing_algo = ifaceobj.get_attr_value_first ('batman-routing-algo')
|
|
if routing_algo:
|
|
self._set_routing_algo (routing_algo)
|
|
|
|
|
|
if_ignore_re = self._get_batman_ifaces_ignore_regex (ifaceobj)
|
|
# Is the batman main interface already present?
|
|
if self.ipcmd.link_exists (ifaceobj.name):
|
|
# Verify which member interfaces are present
|
|
members = self._find_member_ifaces (ifaceobj)
|
|
for iface in members:
|
|
if iface not in batman_ifaces:
|
|
self._batctl_if (ifaceobj.name, iface, 'del')
|
|
for iface in batman_ifaces:
|
|
if iface not in members:
|
|
self._batctl_if (ifaceobj.name, iface, 'add')
|
|
|
|
# Batman interfaces no present, add member interfaces to create it
|
|
else:
|
|
for iface in batman_ifaces:
|
|
self._batctl_if (ifaceobj.name, iface, 'add')
|
|
|
|
# Check/set any B.A.T.M.A.N. adv. set within interface configuration
|
|
for attr in self._batman_attrs:
|
|
value_cfg = self._get_batman_attr (ifaceobj, attr)
|
|
if value_cfg and value_cfg != self._read_current_batman_attr (ifaceobj, attr):
|
|
self._set_batman_attr (ifaceobj, attr, value_cfg)
|
|
|
|
if ifaceobj.addr_method == 'manual':
|
|
netlink.link_set_updown(ifaceobj.name, "up")
|
|
|
|
|
|
|
|
def _down (self, ifaceobj):
|
|
if not ifupdownflags.flags.PERFMODE and not self.ipcmd.link_exists (ifaceobj.name):
|
|
return
|
|
|
|
members = self._find_member_ifaces (ifaceobj)
|
|
for iface in members:
|
|
self._batctl_if (ifaceobj.name, iface, 'del')
|
|
|
|
# The main interface will automagically vanish after the last member
|
|
# interface has been deleted.
|
|
|
|
|
|
def _query_check (self, ifaceobj, ifaceobjcurr):
|
|
if not self.ipcmd.link_exists (ifaceobj.name):
|
|
return
|
|
|
|
batman_ifaces_cfg = self._get_batman_ifaces (ifaceobj)
|
|
batman_ifaces_real = self._find_member_ifaces (ifaceobj, False)
|
|
# Produce list of all current interfaces, tag interfaces ignored by
|
|
# regex with () around the iface name.
|
|
batman_ifaces_real_tagged = []
|
|
iface_ignore_re_str = ifaceobj.get_attr_value_first ('batman-ifaces-ignore-regex')
|
|
iface_ignore_re = self._get_batman_ifaces_ignore_regex (ifaceobj)
|
|
|
|
# Assume everything's fine and wait for reality to prove us otherwise
|
|
ifaces_ok = 0
|
|
|
|
# Interfaces configured but not active?
|
|
for iface in batman_ifaces_cfg:
|
|
if iface not in batman_ifaces_real:
|
|
ifaces_ok = 1
|
|
|
|
# Interfaces active but not configured (or ignored)?
|
|
for iface in batman_ifaces_real:
|
|
if iface not in batman_ifaces_cfg:
|
|
if iface_ignore_re and iface_ignore_re.match (iface):
|
|
batman_ifaces_real_tagged.append ("(%s)" % iface)
|
|
continue
|
|
ifaces_ok = 1
|
|
else:
|
|
batman_ifaces_real_tagged.append (iface)
|
|
|
|
# Produce sorted list of active and ignored interfaces
|
|
ifaces_str = " ".join (batman_ifaces_real_tagged)
|
|
ifaceobjcurr.update_config_with_status ('batman-ifaces', ifaces_str, ifaces_ok)
|
|
ifaceobjcurr.update_config_with_status ('batman-ifaces-ignore-regex', iface_ignore_re_str, 0)
|
|
|
|
# Check any B.A.T.M.A.N. adv. set within interface configuration
|
|
for attr in self._batman_attrs:
|
|
value_cfg = self._get_batman_attr (ifaceobj, attr)
|
|
value_curr = self._read_current_batman_attr (ifaceobj, attr)
|
|
|
|
# Ignore this attribute if its'nt configured for this interface
|
|
if not value_cfg:
|
|
continue
|
|
|
|
value_ok = 0
|
|
if value_cfg != value_curr:
|
|
value_ok = 1
|
|
|
|
ifaceobjcurr.update_config_with_status ('batman-%s' % attr, value_curr, value_ok)
|
|
|
|
routing_algo = ifaceobj.get_attr_value_first ('batman-routing-algo')
|
|
if routing_algo:
|
|
value_curr = self._read_current_batman_attr (ifaceobj, "routing_algo", dont_map = True)
|
|
|
|
value_ok = 0
|
|
if routing_algo != value_curr:
|
|
value_ok = 1
|
|
|
|
ifaceobjcurr.update_config_with_status ('batman-routing-algo', value_curr, value_ok)
|
|
|
|
|
|
def _query_running (self, ifaceobjrunning):
|
|
if not self.ipcmd.link_exists (ifaceobjrunning.name):
|
|
return
|
|
|
|
# XXX Now what?
|
|
|
|
|
|
_run_ops = {'pre-up' : _up,
|
|
'post-down' : _down,
|
|
'query-checkcurr' : _query_check}
|
|
# XXX 'query-running' : _query_running}
|
|
|
|
|
|
def get_ops (self):
|
|
""" returns list of ops supported by this module """
|
|
return self._run_ops.keys ()
|
|
|
|
|
|
def _init_command_handlers (self):
|
|
if not self.ipcmd:
|
|
self.ipcmd = LinkUtils()
|
|
|
|
|
|
def run (self, ifaceobj, operation, query_ifaceobj = None, **extra_args):
|
|
""" run B.A.T.M.A.N. configuration on the interface object passed as argument
|
|
|
|
Args:
|
|
**ifaceobj** (object): iface object
|
|
|
|
**operation** (str): any of 'pre-up', 'post-down', 'query-checkcurr',
|
|
'query-running'
|
|
Kwargs:
|
|
**query_ifaceobj** (object): query check ifaceobject. This is only
|
|
valid when op is 'query-checkcurr'. It is an object same as
|
|
ifaceobj, but contains running attribute values and its config
|
|
status. The modules can use it to return queried running state
|
|
of interfaces. status is success if the running state is same
|
|
as user required state in ifaceobj. error otherwise.
|
|
"""
|
|
op_handler = self._run_ops.get (operation)
|
|
if not op_handler:
|
|
return
|
|
|
|
if (operation != 'query-running' and not self._is_batman_device (ifaceobj)):
|
|
return
|
|
|
|
self._init_command_handlers ()
|
|
|
|
if operation == 'query-checkcurr':
|
|
op_handler (self, ifaceobj, query_ifaceobj)
|
|
else:
|
|
op_handler (self, ifaceobj)
|