]> git.proxmox.com Git - mirror_ifupdown2.git/blame - ifupdown2/addons/batman_adv.py
addons: batman_adv: Add support to set B.A.T.M.A.N. advanced routing_algo
[mirror_ifupdown2.git] / ifupdown2 / addons / batman_adv.py
CommitLineData
b1a2d241
MW
1#!/usr/bin/python
2#
3# Copyright 2016-2017 Maximilian Wilhelm <max@sdn.clinic>
4# Author: Maximilian Wilhelm, max@sdn.clinic
5#
6
8d8cd4f4
JF
7try:
8 from ifupdown2.ifupdown.iface import *
9 from ifupdown2.ifupdown.utils import utils
10 from ifupdown2.ifupdownaddons.modulebase import moduleBase
11 from ifupdown2.ifupdownaddons.LinkUtils import LinkUtils
12 from ifupdown2.ifupdown.netlink import netlink
3e074210 13 from ifupdown2.ifupdown.exceptions import moduleNotSupported
8d8cd4f4 14 import ifupdown2.ifupdown.ifupdownflags as ifupdownflags
3e074210 15
8d8cd4f4
JF
16except:
17 from ifupdown.iface import *
18 from ifupdown.utils import utils
19 from ifupdownaddons.modulebase import moduleBase
20 from ifupdownaddons.LinkUtils import LinkUtils
21 from ifupdown.netlink import netlink
3e074210 22 from ifupdown.exceptions import moduleNotSupported
8d8cd4f4
JF
23 import ifupdown.ifupdownflags as ifupdownflags
24
b1a2d241
MW
25import logging
26import re
27import subprocess
3e074210 28import os
b1a2d241
MW
29
30class batman_adv (moduleBase):
31 """ ifupdown2 addon module to configure B.A.T.M.A.N. advanced interfaces """
32
33 _modinfo = {
34 'mhelp' : 'batman_adv module configures B.A.T.M.A.N. advanced interfaces.' +
35 'Every B.A.T.M.A.N. advanced interface needs at least on ethernet ' +
36 'interface to be creatable. You can specify a space separated list' +
37 'of interfaces by using the "batma-ifaces" paramater. If this parameter' +
38 'is set for an interfaces this module will do the magic.',
39
40 'attrs' : {
41 'batman-ifaces' : {
42 'help' : 'Interfaces to be part of this B.A.T.M.A.N. advanced instance',
43 'validvals' : [ '<interface-list>' ],
44 'required' : True,
45 },
46
47 'batman-ifaces-ignore-regex' : {
48 'help' : 'Interfaces to ignore when verifying configuration (regexp)',
49 'required' : False,
50 },
51
52 'batman-distributed-arp-table' : {
53 'help' : 'B.A.T.M.A.N. distributed ARP table',
54 'validvals' : [ 'enabled', 'disabled' ],
55 'required' : False,
56 'batman-attr' : True,
57 },
58
59 'batman-gw-mode' : {
60 'help' : 'B.A.T.M.A.N. gateway mode',
61 'validvals' : [ 'off', 'client', 'server' ],
62 'required' : False,
63 'example' : [ 'batman-gw-mode client' ],
64 'batman-attr' : True,
65 },
66
67 'batman-hop-penalty' : {
68 'help' : 'B.A.T.M.A.N. hop penalty',
69 'validvals' : [ '<number>' ],
70 'required' : False,
71 'batman-attr' : True,
72 },
73
74 'batman-multicast-mode' : {
75 'help' : 'B.A.T.M.A.N. multicast mode',
76 'validvals' : [ 'enabled', 'disabled' ],
77 'required' : False,
78 'batman-attr' : True,
79 },
ff1f1df9
MW
80
81 'batman-routing-algo' : {
82 'help' : 'B.A.T.M.A.N. routing algo',
83 'validvals' : [ 'BATMAN_IV', 'BATMAN_V' ],
84 'required' : False,
85 'batman-attr' : False,
86 },
b1a2d241
MW
87 }
88 }
89
90 _batman_attrs = {
91 }
92
93
94 def __init__ (self, *args, **kargs):
95 moduleBase.__init__ (self, *args, **kargs)
3e074210
AD
96 if not os.path.exists('/usr/sbin/batctl'):
97 raise moduleNotSupported('module init failed: no /usr/sbin/batctl found')
b1a2d241
MW
98 self.ipcmd = None
99
100 for longname, entry in self._modinfo['attrs'].items ():
101 if entry.get ('batman-attr', False) == False:
102 continue
103
104 attr = longname.replace ("batman-", "")
105 self._batman_attrs[attr] = {
106 'filename' : attr.replace ("-", "_"),
107 }
108
109
110 def _is_batman_device (self, ifaceobj):
111 if ifaceobj.get_attr_value_first ('batman-ifaces'):
112 return True
113 return False
114
115
116 def _get_batman_ifaces (self, ifaceobj ):
117 batman_ifaces = ifaceobj.get_attr_value_first ('batman-ifaces')
118 if batman_ifaces:
119 return sorted (batman_ifaces.split ())
120 return None
121
122
123 def _get_batman_ifaces_ignore_regex (self, ifaceobj):
124 ifaces_ignore_regex = ifaceobj.get_attr_value_first ('batman-ifaces-ignore-regex')
125 if ifaces_ignore_regex:
126 return re.compile (r"%s" % ifaces_ignore_regex)
127 return None
128
129
130 def _get_batman_attr (self, ifaceobj, attr):
131 if attr not in self._batman_attrs:
132 raise ValueError ("_get_batman_attr: Invalid or unsupported B.A.T.M.A.N. adv. attribute: %s" % attr)
133
134 value = ifaceobj.get_attr_value_first ('batman-%s' % attr)
135 if value:
136 return value
137
138 return None
139
140
ff1f1df9
MW
141 def _read_current_batman_attr (self, ifaceobj, attr, dont_map = False):
142 # 'routing_algo' needs special handling, D'oh.
143 if dont_map:
144 attr_file_path = "/sys/class/net/%s/mesh/%s" % (ifaceobj.name, attr)
145 else:
146 if attr not in self._batman_attrs:
147 raise ValueError ("_read_current_batman_attr: Invalid or unsupported B.A.T.M.A.N. adv. attribute: %s" % attr)
148
149 attr_file_name = self._batman_attrs[attr]['filename']
150 attr_file_path = "/sys/class/net/%s/mesh/%s" % (ifaceobj.name, attr_file_name)
b1a2d241 151
b1a2d241 152 try:
8d8cd4f4 153 return self.read_file_oneline(attr_file_path)
b1a2d241
MW
154 except IOError as i:
155 raise Exception ("_read_current_batman_attr (%s) %s" % (attr, i))
156 except ValueError:
157 raise Exception ("_read_current_batman_attr: Integer value expected, got: %s" % value)
158
159
160 def _set_batman_attr (self, ifaceobj, attr, value):
161 if attr not in self._batman_attrs:
162 raise ValueError ("_set_batman_attr: Invalid or unsupported B.A.T.M.A.N. adv. attribute: %s" % attr)
163
164 attr_file_name = self._batman_attrs[attr]['filename']
165 attr_file_path = "/sys/class/net/%s/mesh/%s" % (ifaceobj.name, attr_file_name)
166 try:
8d8cd4f4 167 self.write_file(attr_file_path, "%s\n" % value)
b1a2d241
MW
168 except IOError as i:
169 raise Exception ("_set_batman_attr (%s): %s" % (attr, i))
170
171
172 def _batctl_if (self, bat_iface, mesh_iface, op):
173 if op not in [ 'add', 'del' ]:
174 raise Exception ("_batctl_if() called with invalid \"op\" value: %s" % op)
175
176 try:
177 self.logger.debug ("Running batctl -m %s if %s %s" % (bat_iface, op, mesh_iface))
8d8cd4f4 178 utils.exec_commandl(["batctl", "-m", bat_iface, "if", op, mesh_iface])
b1a2d241
MW
179 except subprocess.CalledProcessError as c:
180 raise Exception ("Command \"batctl -m %s if %s %s\" failed: %s" % (bat_iface, op, mesh_iface, c.output))
181 except Exception as e:
182 raise Exception ("_batctl_if: %s" % e)
183
ff1f1df9
MW
184 def _set_routing_algo (self, routing_algo):
185 if routing_algo not in ['BATMAN_IV', 'BATMAN_V']:
186 raise Exception ("_set_routing_algo() called with invalid \"routing_algo\" value: %s" % routing_algo)
187
188 try:
189 self.logger.debug ("Running batctl ra %s" % routing_algo)
190 batctl_output = subprocess.check_output (["batctl", "ra", routing_algo], stderr = subprocess.STDOUT)
191 except subprocess.CalledProcessError as c:
192 raise Exception ("Command \"batctl ra %s\" failed: %s" % (routing_algo, c.output))
193 except Exception as e:
194 raise Exception ("_set_routing_algo: %s" % e)
195
b1a2d241
MW
196
197 def _find_member_ifaces (self, ifaceobj, ignore = True):
198 members = []
199 iface_ignore_re = self._get_batman_ifaces_ignore_regex (ifaceobj)
8d8cd4f4 200 self.logger.info("batman: executing: %s" % " ".join(["batctl", "-m", ifaceobj.name, "if"]))
b1a2d241
MW
201 batctl_fh = subprocess.Popen (["batctl", "-m", ifaceobj.name, "if"], bufsize = 4194304, stdout = subprocess.PIPE).stdout
202 for line in batctl_fh.readlines ():
203 iface = line.split (':')[0]
204 if iface_ignore_re and iface_ignore_re.match (iface) and ignore:
205 continue
206
207 members.append (iface)
208
209 return sorted (members)
210
211
212 def get_dependent_ifacenames (self, ifaceobj, ifaceobjs_all=None):
213 if not self._is_batman_device (ifaceobj):
214 return None
215
216 ifaceobj.link_kind |= ifaceLinkKind.BATMAN_ADV
217 batman_ifaces = self._get_batman_ifaces (ifaceobj)
218 if batman_ifaces:
219 return batman_ifaces
220
221 return [None]
222
223
224 def _up (self, ifaceobj):
225 if self._get_batman_ifaces (ifaceobj) == None:
226 raise Exception ('could not determine batman interfacaes')
227
228 # Verify existance of batman interfaces (should be present already)
229 batman_ifaces = []
230 for iface in self._get_batman_ifaces (ifaceobj):
231 if not self.ipcmd.link_exists (iface):
232 self.logger.warn ('batman iface %s not present' % iface)
233 continue
234
235 batman_ifaces.append (iface)
236
237 if len (batman_ifaces) == 0:
238 raise Exception ("None of the configured batman interfaces are available!")
239
ff1f1df9
MW
240 routing_algo = ifaceobj.get_attr_value_first ('batman-routing-algo')
241 if routing_algo:
242 self._set_routing_algo (routing_algo)
243
244
b1a2d241
MW
245 if_ignore_re = self._get_batman_ifaces_ignore_regex (ifaceobj)
246 # Is the batman main interface already present?
247 if self.ipcmd.link_exists (ifaceobj.name):
248 # Verify which member interfaces are present
249 members = self._find_member_ifaces (ifaceobj)
250 for iface in members:
251 if iface not in batman_ifaces:
252 self._batctl_if (ifaceobj.name, iface, 'del')
253 for iface in batman_ifaces:
254 if iface not in members:
255 self._batctl_if (ifaceobj.name, iface, 'add')
256
257 # Batman interfaces no present, add member interfaces to create it
258 else:
259 for iface in batman_ifaces:
260 self._batctl_if (ifaceobj.name, iface, 'add')
261
262 # Check/set any B.A.T.M.A.N. adv. set within interface configuration
263 for attr in self._batman_attrs:
264 value_cfg = self._get_batman_attr (ifaceobj, attr)
265 if value_cfg and value_cfg != self._read_current_batman_attr (ifaceobj, attr):
266 self._set_batman_attr (ifaceobj, attr, value_cfg)
267
268 if ifaceobj.addr_method == 'manual':
269 netlink.link_set_updown(ifaceobj.name, "up")
270
271
272
273 def _down (self, ifaceobj):
274 if not ifupdownflags.flags.PERFMODE and not self.ipcmd.link_exists (ifaceobj.name):
275 return
276
277 members = self._find_member_ifaces (ifaceobj)
278 for iface in members:
279 self._batctl_if (ifaceobj.name, iface, 'del')
280
281 # The main interface will automagically vanish after the last member
282 # interface has been deleted.
283
284
285 def _query_check (self, ifaceobj, ifaceobjcurr):
286 if not self.ipcmd.link_exists (ifaceobj.name):
287 return
288
289 batman_ifaces_cfg = self._get_batman_ifaces (ifaceobj)
290 batman_ifaces_real = self._find_member_ifaces (ifaceobj, False)
291 # Produce list of all current interfaces, tag interfaces ignored by
292 # regex with () around the iface name.
293 batman_ifaces_real_tagged = []
294 iface_ignore_re_str = ifaceobj.get_attr_value_first ('batman-ifaces-ignore-regex')
295 iface_ignore_re = self._get_batman_ifaces_ignore_regex (ifaceobj)
296
297 # Assume everything's fine and wait for reality to prove us otherwise
298 ifaces_ok = 0
299
300 # Interfaces configured but not active?
301 for iface in batman_ifaces_cfg:
302 if iface not in batman_ifaces_real:
303 ifaces_ok = 1
304
305 # Interfaces active but not configured (or ignored)?
306 for iface in batman_ifaces_real:
307 if iface not in batman_ifaces_cfg:
308 if iface_ignore_re and iface_ignore_re.match (iface):
309 batman_ifaces_real_tagged.append ("(%s)" % iface)
310 continue
311 ifaces_ok = 1
312 else:
313 batman_ifaces_real_tagged.append (iface)
314
315 # Produce sorted list of active and ignored interfaces
316 ifaces_str = " ".join (batman_ifaces_real_tagged)
317 ifaceobjcurr.update_config_with_status ('batman-ifaces', ifaces_str, ifaces_ok)
318 ifaceobjcurr.update_config_with_status ('batman-ifaces-ignore-regex', iface_ignore_re_str, 0)
319
320 # Check any B.A.T.M.A.N. adv. set within interface configuration
321 for attr in self._batman_attrs:
322 value_cfg = self._get_batman_attr (ifaceobj, attr)
323 value_curr = self._read_current_batman_attr (ifaceobj, attr)
324
325 # Ignore this attribute if its'nt configured for this interface
326 if not value_cfg:
327 continue
328
329 value_ok = 0
330 if value_cfg != value_curr:
331 value_ok = 1
332
333 ifaceobjcurr.update_config_with_status ('batman-%s' % attr, value_curr, value_ok)
334
ff1f1df9
MW
335 routing_algo = ifaceobj.get_attr_value_first ('batman-routing-algo')
336 if routing_algo:
337 value_curr = self._read_current_batman_attr (ifaceobj, "routing_algo", dont_map = True)
338
339 value_ok = 0
340 if routing_algo != value_curr:
341 value_ok = 1
342
343 ifaceobjcurr.update_config_with_status ('batman-routing-algo', value_curr, value_ok)
344
b1a2d241
MW
345
346 def _query_running (self, ifaceobjrunning):
347 if not self.ipcmd.link_exists (ifaceobjrunning.name):
348 return
349
350 # XXX Now what?
351
352
353 _run_ops = {'pre-up' : _up,
354 'post-down' : _down,
355 'query-checkcurr' : _query_check}
356# XXX 'query-running' : _query_running}
357
358
359 def get_ops (self):
360 """ returns list of ops supported by this module """
361 return self._run_ops.keys ()
362
363
364 def _init_command_handlers (self):
365 if not self.ipcmd:
8d8cd4f4 366 self.ipcmd = LinkUtils()
b1a2d241
MW
367
368
369 def run (self, ifaceobj, operation, query_ifaceobj = None, **extra_args):
370 """ run B.A.T.M.A.N. configuration on the interface object passed as argument
371
372 Args:
373 **ifaceobj** (object): iface object
374
375 **operation** (str): any of 'pre-up', 'post-down', 'query-checkcurr',
376 'query-running'
377 Kwargs:
378 **query_ifaceobj** (object): query check ifaceobject. This is only
379 valid when op is 'query-checkcurr'. It is an object same as
380 ifaceobj, but contains running attribute values and its config
381 status. The modules can use it to return queried running state
382 of interfaces. status is success if the running state is same
383 as user required state in ifaceobj. error otherwise.
384 """
385 op_handler = self._run_ops.get (operation)
386 if not op_handler:
387 return
388
389 if (operation != 'query-running' and not self._is_batman_device (ifaceobj)):
390 return
391
392 self._init_command_handlers ()
393
394 if operation == 'query-checkcurr':
395 op_handler (self, ifaceobj, query_ifaceobj)
396 else:
397 op_handler (self, ifaceobj)