]>
Commit | Line | Data |
---|---|---|
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 |
7 | try: |
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 |
16 | except: |
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 |
25 | import logging |
26 | import re | |
27 | import subprocess | |
3e074210 | 28 | import os |
b1a2d241 MW |
29 | |
30 | class 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) |