diff --git a/docs/examples/batman_adv/configure_batman_adv.sh b/docs/examples/batman_adv/configure_batman_adv.sh new file mode 100755 index 0000000..eabfea8 --- /dev/null +++ b/docs/examples/batman_adv/configure_batman_adv.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash + +echo "Installing batman-adv module on debian/ubuntu" +echo "" +echo "Batman is a Layer2-Mesh protocol which uses Ethernet devices (like eth*, +vlans, etc.) to communicate with peers and provides access to the L2-mesh via +a batX interface. You can only create a batman instance if at least one batman- +-iface (read: an interface where the mesh protocol is spoken on) is present and +added to the batman-mesh-instance." +echo "More info: https://en.wikipedia.org/wiki/B.A.T.M.A.N." +echo "" + +echo "installing batctl: apt-get install batctl" +apt-get install batctl +echo "" +echo "" + +echo "loading batman-adv module: modprobe batman-adv" +modprobe batman-adv +echo "" + +echo "usefull commands: +$ batctl if add \$IFACE +$ batctl -m bat0 if add \$IFACE" +echo "please read: man batctl" +echo "" +echo "" + +echo "configuration example: +$ cat /etc/network/interfaces + +auto bat0 +iface bat0 + batman-ifaces \$IFACE [\$IFACES...] + batman-ifaces-ignore-regex .*_nodes + batman-hop-penalty 23 + address 192.0.2.42/24 +$ +$ +$ ifreload -a +$ ifquery -a -c +auto bat0 +iface bat0 [pass] + batman-ifaces tap0 tap1 [pass] + batman-ifaces-ignore-regex .*_nodes [pass] + batman-hop-penalty 23 [pass] + address 192.0.2.42/24 [pass] + +$" diff --git a/docs/examples/batman_adv/interfaces_batman b/docs/examples/batman_adv/interfaces_batman new file mode 100644 index 0000000..14ced43 --- /dev/null +++ b/docs/examples/batman_adv/interfaces_batman @@ -0,0 +1,12 @@ +auto eth0 +iface eth0 inet dhcp + +auto lo +iface lo inet + +auto bat0 +iface bat0 + batman-ifaces tap0 tap1 + batman-ifaces-ignore-regex .*_nodes + batman-hop-penalty 23 + address 192.0.2.42/24 \ No newline at end of file diff --git a/etc/network/ifupdown2/addons.conf b/etc/network/ifupdown2/addons.conf index 4afbedf..99f6b7d 100644 --- a/etc/network/ifupdown2/addons.conf +++ b/etc/network/ifupdown2/addons.conf @@ -1,6 +1,7 @@ pre-up,link pre-up,ppp pre-up,bond +pre-up,batman_adv pre-up,vlan pre-up,vxlan pre-up,clagd @@ -34,6 +35,7 @@ post-down,bridge post-down,vxlan post-down,vlan post-down,bond +post-down,batman_adv post-down,usercmds post-down,link post-down,tunnel diff --git a/ifupdown2/addons/batman_adv.py b/ifupdown2/addons/batman_adv.py new file mode 100644 index 0000000..0fda862 --- /dev/null +++ b/ifupdown2/addons/batman_adv.py @@ -0,0 +1,344 @@ +#!/usr/bin/python +# +# Copyright 2016-2017 Maximilian Wilhelm +# Author: Maximilian Wilhelm, max@sdn.clinic +# + +from ifupdown.iface import * +from ifupdownaddons.modulebase import moduleBase +from ifupdownaddons.iproute2 import iproute2 +from ifupdown.netlink import netlink +import ifupdown.ifupdownflags as ifupdownflags +import logging +import re +import subprocess + + +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' : [ '' ], + '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' : [ '' ], + '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_attrs = { + } + + + def __init__ (self, *args, **kargs): + moduleBase.__init__ (self, *args, **kargs) + 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): + 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: + with open (attr_file_path, "r") as fh: + return fh.readline ().strip () + 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: + with open (attr_file_path, "w") as fh: + fh.write ("%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)) + batctl_output = subprocess.check_output (["batctl", "-m", bat_iface, "if", op, mesh_iface], stderr = subprocess.STDOUT) + 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 _find_member_ifaces (self, ifaceobj, ignore = True): + members = [] + iface_ignore_re = self._get_batman_ifaces_ignore_regex (ifaceobj) + 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!") + + 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) + + + 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 = iproute2() + + + 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) diff --git a/ifupdown2/ifupdown/iface.py b/ifupdown2/ifupdown/iface.py index 75cc37a..bb09535 100644 --- a/ifupdown2/ifupdown/iface.py +++ b/ifupdown2/ifupdown/iface.py @@ -47,15 +47,16 @@ class ifaceLinkKind(): bond have an ifaceobj.role attribute of SLAVE and the bridge or bond itself has ifaceobj.role of MASTER. """ - UNKNOWN = 0x000000 - BRIDGE = 0x000001 - BOND = 0x000010 - VLAN = 0x000100 - VXLAN = 0x001000 - VRF = 0x010000 + UNKNOWN = 0x0000000 + BRIDGE = 0x0000001 + BOND = 0x0000010 + VLAN = 0x0000100 + VXLAN = 0x0001000 + VRF = 0x0010000 + BATMAN_ADV = 0x0100000 # to indicate logical interface created by an external entity. # the 'kind' of which ifupdown2 does not really understand - OTHER = 0x100000 + OTHER = 0x1000000 @classmethod def to_str(cls, kind):