]> git.proxmox.com Git - mirror_ifupdown2.git/blame_incremental - addons/address.py
debian: avoid zipping of generate_interfaces.py file
[mirror_ifupdown2.git] / addons / address.py
... / ...
CommitLineData
1#!/usr/bin/python
2#
3# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
4# Author: Roopa Prabhu, roopa@cumulusnetworks.com
5#
6
7import os
8
9try:
10 from ipaddr import IPNetwork
11 from sets import Set
12 from ifupdown.iface import *
13 from ifupdownaddons.modulebase import moduleBase
14 from ifupdownaddons.iproute2 import iproute2
15 from ifupdownaddons.dhclient import dhclient
16 import ifupdown.policymanager as policymanager
17 import ifupdown.rtnetlink_api as rtnetlink_api
18 import ifupdown.ifupdownconfig as ifupdownConfig
19 import ifupdown.ifupdownflags as ifupdownflags
20except ImportError, e:
21 raise ImportError (str(e) + "- required module not found")
22
23class address(moduleBase):
24 """ ifupdown2 addon module to configure address, mtu, hwaddress, alias
25 (description) on an interface """
26
27 _modinfo = {'mhelp' : 'address configuration module for interfaces',
28 'attrs': {
29 'address' :
30 {'help' : 'ipv4 or ipv6 addresses',
31 'example' : ['address 10.0.12.3/24',
32 'address 2000:1000:1000:1000:3::5/128']},
33 'netmask' :
34 {'help': 'netmask',
35 'example' : ['netmask 255.255.255.0'],
36 'compat' : True},
37 'broadcast' :
38 {'help': 'broadcast address',
39 'example' : ['broadcast 10.0.1.255']},
40 'scope' :
41 {'help': 'scope',
42 'example' : ['scope host']},
43 'preferred-lifetime' :
44 {'help': 'preferred lifetime',
45 'example' : ['preferred-lifetime forever',
46 'preferred-lifetime 10']},
47 'gateway' :
48 {'help': 'default gateway',
49 'example' : ['gateway 255.255.255.0']},
50 'mtu' :
51 { 'help': 'interface mtu',
52 'example' : ['mtu 1600'],
53 'default' : '1500'},
54 'hwaddress' :
55 {'help' : 'hw address',
56 'example': ['hwaddress 44:38:39:00:27:b8']},
57 'alias' :
58 { 'help': 'description/alias',
59 'example' : ['alias testnetwork']},
60 'address-purge' :
61 { 'help': 'purge existing addresses. By default ' +
62 'any existing ip addresses on an interface are ' +
63 'purged to match persistant addresses in the ' +
64 'interfaces file. Set this attribute to \'no\'' +
65 'if you want to preserve existing addresses',
66 'default' : 'yes',
67 'example' : ['address-purge yes/no']},
68 'clagd-vxlan-anycast-ip' :
69 { 'help' : 'Anycast local IP address for ' +
70 'dual connected VxLANs',
71 'example' : ['clagd-vxlan-anycast-ip 36.0.0.11']}}}
72
73 def __init__(self, *args, **kargs):
74 moduleBase.__init__(self, *args, **kargs)
75 self.ipcmd = None
76 self._bridge_fdb_query_cache = {}
77 self.default_mtu = policymanager.policymanager_api.get_attr_default(module_name=self.__class__.__name__, attr='mtu')
78
79 def _address_valid(self, addrs):
80 if not addrs:
81 return False
82 if any(map(lambda a: True if a[:7] != '0.0.0.0'
83 else False, addrs)):
84 return True
85 return False
86
87 def _get_hwaddress(self, ifaceobj):
88 hwaddress = ifaceobj.get_attr_value_first('hwaddress')
89 if hwaddress and hwaddress.startswith("ether"):
90 hwaddress = hwaddress[5:].strip()
91 return hwaddress
92
93 def _process_bridge(self, ifaceobj, up):
94 hwaddress = self._get_hwaddress(ifaceobj)
95 addrs = ifaceobj.get_attr_value_first('address')
96 is_vlan_dev_on_vlan_aware_bridge = False
97 is_bridge = self.ipcmd.is_bridge(ifaceobj.name)
98 if not is_bridge:
99 if '.' in ifaceobj.name:
100 (bridgename, vlan) = ifaceobj.name.split('.')
101 is_vlan_dev_on_vlan_aware_bridge = self.ipcmd.bridge_is_vlan_aware(bridgename)
102 if ((is_bridge and not self.ipcmd.bridge_is_vlan_aware(ifaceobj.name))
103 or is_vlan_dev_on_vlan_aware_bridge):
104 if self._address_valid(addrs):
105 if up:
106 self.write_file('/proc/sys/net/ipv4/conf/%s' %ifaceobj.name +
107 '/arp_accept', '1')
108 else:
109 self.write_file('/proc/sys/net/ipv4/conf/%s' %ifaceobj.name +
110 '/arp_accept', '0')
111 if hwaddress and is_vlan_dev_on_vlan_aware_bridge:
112 if up:
113 self.ipcmd.bridge_fdb_add(bridgename, hwaddress, vlan)
114 else:
115 self.ipcmd.bridge_fdb_del(bridgename, hwaddress, vlan)
116
117 def _get_anycast_addr(self, ifaceobjlist):
118 for ifaceobj in ifaceobjlist:
119 anycast_addr = ifaceobj.get_attr_value_first('clagd-vxlan-anycast-ip')
120 if anycast_addr:
121 anycast_addr = anycast_addr+'/32'
122 return anycast_addr
123 return None
124
125 def _inet_address_convert_to_cidr(self, ifaceobjlist):
126 newaddrs = []
127 newaddr_attrs = {}
128
129 for ifaceobj in ifaceobjlist:
130 addrs = ifaceobj.get_attr_value('address')
131 if not addrs:
132 continue
133
134 if (((ifaceobj.role & ifaceRole.SLAVE) and
135 not (ifaceobj.link_privflags & ifaceLinkPrivFlags.VRF_SLAVE)) or
136 ((ifaceobj.link_kind & ifaceLinkKind.BRIDGE) and
137 (ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_VLAN_AWARE))):
138 # we must not configure an IP address if the interface is
139 # enslaved or is a VLAN AWARE BRIDGE
140 self.logger.info('%s: ignoring ip address. Interface is '
141 'enslaved or a vlan aware bridge and cannot'
142 ' have an IP Address' %(ifaceobj.name))
143 return (False, newaddrs, newaddr_attrs)
144 # If user address is not in CIDR notation, convert them to CIDR
145 for addr_index in range(0, len(addrs)):
146 addr = addrs[addr_index]
147 if '/' in addr:
148 newaddrs.append(addr)
149 continue
150 newaddr = addr
151 netmask = ifaceobj.get_attr_value_n('netmask', addr_index)
152 if netmask:
153 prefixlen = IPNetwork('%s' %addr +
154 '/%s' %netmask).prefixlen
155 newaddr = addr + '/%s' %prefixlen
156 newaddrs.append(newaddr)
157
158 attrs = {}
159 for a in ['broadcast', 'pointopoint', 'scope',
160 'preferred-lifetime']:
161 aval = ifaceobj.get_attr_value_n(a, addr_index)
162 if aval:
163 attrs[a] = aval
164
165 if attrs:
166 newaddr_attrs[newaddr]= attrs
167 return (True, newaddrs, newaddr_attrs)
168
169 def _inet_address_list_config(self, ifaceobj, newaddrs, newaddr_attrs):
170 for addr_index in range(0, len(newaddrs)):
171 try:
172 if newaddr_attrs:
173 self.ipcmd.addr_add(ifaceobj.name, newaddrs[addr_index],
174 newaddr_attrs.get(newaddrs[addr_index],
175 {}).get('broadcast'),
176 newaddr_attrs.get(newaddrs[addr_index],
177 {}).get('pointopoint'),
178 newaddr_attrs.get(newaddrs[addr_index],
179 {}).get('scope'),
180 newaddr_attrs.get(newaddrs[addr_index],
181 {}).get('preferred-lifetime'))
182 else:
183 self.ipcmd.addr_add(ifaceobj.name, newaddrs[addr_index])
184 except Exception, e:
185 self.log_error(str(e), ifaceobj)
186
187 def _inet_address_config(self, ifaceobj, ifaceobj_getfunc=None,
188 force_reapply=False):
189 squash_addr_config = (True if \
190 ifupdownConfig.config.get('addr_config_squash', \
191 '0') == '1' else False)
192
193 if (squash_addr_config and
194 not (ifaceobj.flags & ifaceobj.YOUNGEST_SIBLING)):
195 return
196
197 purge_addresses = ifaceobj.get_attr_value_first('address-purge')
198 if not purge_addresses:
199 purge_addresses = 'yes'
200
201 if squash_addr_config and ifaceobj.flags & iface.HAS_SIBLINGS:
202 ifaceobjlist = ifaceobj_getfunc(ifaceobj.name)
203 else:
204 ifaceobjlist = [ifaceobj]
205
206 (addr_supported, newaddrs, newaddr_attrs) = self._inet_address_convert_to_cidr(ifaceobjlist)
207 if not addr_supported:
208 return
209 if (not squash_addr_config and (ifaceobj.flags & iface.HAS_SIBLINGS)):
210 # if youngest sibling and squash addr is not set
211 # print a warning that addresses will not be purged
212 if (ifaceobj.flags & iface.YOUNGEST_SIBLING):
213 self.logger.warn('%s: interface has multiple ' %ifaceobj.name +
214 'iface stanzas, skip purging existing addresses')
215 purge_addresses = 'no'
216
217 if not ifupdownflags.flags.PERFMODE and purge_addresses == 'yes':
218 # if perfmode is not set and purge addresses is not set to 'no'
219 # lets purge addresses not in the config
220 runningaddrs = self.ipcmd.addr_get(ifaceobj.name, details=False)
221
222 # if anycast address is configured on 'lo' and is in running config
223 # add it to newaddrs so that ifreload doesn't wipe it out
224 anycast_addr = self._get_anycast_addr(ifaceobjlist)
225
226 if runningaddrs and anycast_addr and anycast_addr in runningaddrs:
227 newaddrs.append(anycast_addr)
228 if newaddrs == runningaddrs:
229 if force_reapply:
230 self._inet_address_list_config(ifaceobj, newaddrs, newaddr_attrs)
231 return
232 try:
233 # if primary address is not same, there is no need to keep any.
234 # reset all addresses
235 if (newaddrs and runningaddrs and
236 (newaddrs[0] != runningaddrs[0])):
237 self.ipcmd.del_addr_all(ifaceobj.name)
238 else:
239 self.ipcmd.del_addr_all(ifaceobj.name, newaddrs)
240 except Exception, e:
241 self.log_warn(str(e))
242 if not newaddrs:
243 return
244 self._inet_address_list_config(ifaceobj, newaddrs, newaddr_attrs)
245
246 def _up(self, ifaceobj, ifaceobj_getfunc=None):
247 if not self.ipcmd.link_exists(ifaceobj.name):
248 return
249 addr_method = ifaceobj.addr_method
250 force_reapply = False
251 try:
252 # release any stale dhcp addresses if present
253 if (addr_method != "dhcp" and not ifupdownflags.flags.PERFMODE and
254 not (ifaceobj.flags & iface.HAS_SIBLINGS)):
255 # if not running in perf mode and ifaceobj does not have
256 # any sibling iface objects, kill any stale dhclient
257 # processes
258 dhclientcmd = dhclient()
259 if dhclientcmd.is_running(ifaceobj.name):
260 # release any dhcp leases
261 dhclientcmd.release(ifaceobj.name)
262 force_reapply = True
263 elif dhclientcmd.is_running6(ifaceobj.name):
264 dhclientcmd.release6(ifaceobj.name)
265 force_reapply = True
266 except:
267 pass
268
269 self.ipcmd.batch_start()
270 if addr_method != "dhcp":
271 self._inet_address_config(ifaceobj, ifaceobj_getfunc,
272 force_reapply)
273 mtu = ifaceobj.get_attr_value_first('mtu')
274 if mtu:
275 self.ipcmd.link_set(ifaceobj.name, 'mtu', mtu)
276 elif (not ifaceobj.link_kind and
277 not (ifaceobj.link_privflags & ifaceLinkPrivFlags.BOND_SLAVE) and
278 self.default_mtu):
279 # logical devices like bridges and vlan devices rely on mtu
280 # from their lower devices. ie mtu travels from
281 # lower devices to upper devices. For bonds mtu travels from
282 # upper to lower devices. running mtu depends on upper and
283 # lower device mtu. With all this implicit mtu
284 # config by the kernel in play, we try to be cautious here
285 # on which devices we want to reset mtu to default.
286 # essentially only physical interfaces which are not bond slaves
287 running_mtu = self.ipcmd.link_get_mtu(ifaceobj.name)
288 if running_mtu != self.default_mtu:
289 self.ipcmd.link_set(ifaceobj.name, 'mtu', self.default_mtu)
290
291 alias = ifaceobj.get_attr_value_first('alias')
292 if alias:
293 self.ipcmd.link_set_alias(ifaceobj.name, alias)
294 self.ipcmd.batch_commit()
295
296 hwaddress = self._get_hwaddress(ifaceobj)
297 if hwaddress:
298 running_hwaddress = None
299 if not ifupdownflags.flags.PERFMODE: # system is clean
300 running_hwaddress = self.ipcmd.link_get_hwaddress(ifaceobj.name)
301 if hwaddress != running_hwaddress:
302 slave_down = False
303 rtnetlink_api.rtnl_api.link_set(ifaceobj.name, "down")
304 if ifaceobj.link_kind & ifaceLinkKind.BOND:
305 # if bond, down all the slaves
306 if ifaceobj.lowerifaces:
307 for l in ifaceobj.lowerifaces:
308 rtnetlink_api.rtnl_api.link_set(l, "down")
309 slave_down = True
310 try:
311 self.ipcmd.link_set(ifaceobj.name, 'address', hwaddress)
312 finally:
313 rtnetlink_api.rtnl_api.link_set(ifaceobj.name, "up")
314 if slave_down:
315 for l in ifaceobj.lowerifaces:
316 rtnetlink_api.rtnl_api.link_set(l, "up")
317
318 try:
319 # Handle special things on a bridge
320 self._process_bridge(ifaceobj, True)
321 except Exception, e:
322 self.log_error('%s: %s' %(ifaceobj.name, str(e)), ifaceobj)
323 pass
324
325 if addr_method != "dhcp":
326 self.ipcmd.route_add_gateway(ifaceobj.name,
327 ifaceobj.get_attr_value_first('gateway'),
328 ifaceobj.get_attr_value_first('vrf'))
329
330 def _down(self, ifaceobj, ifaceobj_getfunc=None):
331 try:
332 if not self.ipcmd.link_exists(ifaceobj.name):
333 return
334 addr_method = ifaceobj.addr_method
335 if addr_method != "dhcp":
336 self.ipcmd.route_del_gateway(ifaceobj.name,
337 ifaceobj.get_attr_value_first('gateway'),
338 ifaceobj.get_attr_value_first('vrf'),
339 ifaceobj.get_attr_value_first('metric'))
340 if ifaceobj.get_attr_value_first('address-purge')=='no':
341 addrlist = ifaceobj.get_attr_value('address')
342 for addr in addrlist:
343 self.ipcmd.addr_del(ifaceobj.name, addr)
344 #self.ipcmd.addr_del(ifaceobj.name, ifaceobj.get_attr_value('address')[0])
345 else:
346 self.ipcmd.del_addr_all(ifaceobj.name)
347 mtu = ifaceobj.get_attr_value_first('mtu')
348 if (not ifaceobj.link_kind and mtu and
349 self.default_mtu and (mtu != self.default_mtu)):
350 self.ipcmd.link_set(ifaceobj.name, 'mtu', self.default_mtu)
351 alias = ifaceobj.get_attr_value_first('alias')
352 if alias:
353 filename = '/sys/class/net/%s/ifalias' %ifaceobj.name
354 self.logger.info('executing echo "" > %s' %filename)
355 os.system('echo "" > %s' %filename)
356 # XXX hwaddress reset cannot happen because we dont know last
357 # address.
358
359 # Handle special things on a bridge
360 self._process_bridge(ifaceobj, False)
361 except Exception, e:
362 self.logger.debug('%s : %s' %(ifaceobj.name, str(e)))
363 pass
364
365 def _get_iface_addresses(self, ifaceobj):
366 addrlist = ifaceobj.get_attr_value('address')
367 outaddrlist = []
368
369 if not addrlist: return None
370 for addrindex in range(0, len(addrlist)):
371 addr = addrlist[addrindex]
372 netmask = ifaceobj.get_attr_value_n('netmask', addrindex)
373 if netmask:
374 prefixlen = IPNetwork('%s' %addr +
375 '/%s' %netmask).prefixlen
376 addr = addr + '/%s' %prefixlen
377 outaddrlist.append(addr)
378 return outaddrlist
379
380 def _get_bridge_fdbs(self, bridgename, vlan):
381 fdbs = self._bridge_fdb_query_cache.get(bridgename)
382 if not fdbs:
383 fdbs = self.ipcmd.bridge_fdb_show_dev(bridgename)
384 if not fdbs:
385 return
386 self._bridge_fdb_query_cache[bridgename] = fdbs
387 return fdbs.get(vlan)
388
389 def _check_addresses_in_bridge(self, ifaceobj, hwaddress):
390 """ If the device is a bridge, make sure the addresses
391 are in the bridge """
392 if '.' in ifaceobj.name:
393 (bridgename, vlan) = ifaceobj.name.split('.')
394 if self.ipcmd.bridge_is_vlan_aware(bridgename):
395 fdb_addrs = self._get_bridge_fdbs(bridgename, vlan)
396 if not fdb_addrs or hwaddress not in fdb_addrs:
397 return False
398 return True
399
400 def _query_check(self, ifaceobj, ifaceobjcurr, ifaceobj_getfunc=None):
401 runningaddrsdict = None
402 if not self.ipcmd.link_exists(ifaceobj.name):
403 self.logger.debug('iface %s not found' %ifaceobj.name)
404 return
405 addr_method = ifaceobj.addr_method
406 self.query_n_update_ifaceobjcurr_attr(ifaceobj, ifaceobjcurr,
407 'mtu', self.ipcmd.link_get_mtu)
408 hwaddress = self._get_hwaddress(ifaceobj)
409 if hwaddress:
410 rhwaddress = self.ipcmd.link_get_hwaddress(ifaceobj.name)
411 if not rhwaddress or rhwaddress != hwaddress:
412 ifaceobjcurr.update_config_with_status('hwaddress', rhwaddress,
413 1)
414 elif not self._check_addresses_in_bridge(ifaceobj, hwaddress):
415 # XXX: hw address is not in bridge
416 ifaceobjcurr.update_config_with_status('hwaddress', rhwaddress,
417 1)
418 ifaceobjcurr.status_str = 'bridge fdb error'
419 else:
420 ifaceobjcurr.update_config_with_status('hwaddress', rhwaddress,
421 0)
422 self.query_n_update_ifaceobjcurr_attr(ifaceobj, ifaceobjcurr,
423 'alias', self.ipcmd.link_get_alias)
424 # compare addresses
425 if addr_method == 'dhcp':
426 return
427 addrs = self._get_iface_addresses(ifaceobj)
428 runningaddrsdict = self.ipcmd.addr_get(ifaceobj.name)
429 # if anycast address is configured on 'lo' and is in running config
430 # add it to addrs so that query_check doesn't fail
431 anycast_addr = ifaceobj.get_attr_value_first('clagd-vxlan-anycast-ip')
432 if anycast_addr:
433 anycast_addr = anycast_addr+'/32'
434 if runningaddrsdict and anycast_addr and runningaddrsdict.get(anycast_addr):
435 addrs.append(anycast_addr)
436
437 # Set ifaceobjcurr method and family
438 ifaceobjcurr.addr_method = ifaceobj.addr_method
439 ifaceobjcurr.addr_family = ifaceobj.addr_family
440 if not runningaddrsdict and not addrs:
441 return
442 runningaddrs = runningaddrsdict.keys() if runningaddrsdict else []
443 if runningaddrs != addrs:
444 runningaddrsset = set(runningaddrs) if runningaddrs else set([])
445 addrsset = set(addrs) if addrs else set([])
446 if (ifaceobj.flags & iface.HAS_SIBLINGS):
447 if not addrsset:
448 return
449 # only check for addresses present in running config
450 addrsdiff = addrsset.difference(runningaddrsset)
451 for addr in addrs:
452 if addr in addrsdiff:
453 ifaceobjcurr.update_config_with_status('address',
454 addr, 1)
455 else:
456 ifaceobjcurr.update_config_with_status('address',
457 addr, 0)
458 else:
459 addrsdiff = addrsset.symmetric_difference(runningaddrsset)
460 for addr in addrsset.union(runningaddrsset):
461 if addr in addrsdiff:
462 ifaceobjcurr.update_config_with_status('address',
463 addr, 1)
464 else:
465 ifaceobjcurr.update_config_with_status('address',
466 addr, 0)
467 elif addrs:
468 [ifaceobjcurr.update_config_with_status('address',
469 addr, 0) for addr in addrs]
470 #XXXX Check broadcast address, scope, etc
471 return
472
473 def _query_running(self, ifaceobjrunning, ifaceobj_getfunc=None):
474 if not self.ipcmd.link_exists(ifaceobjrunning.name):
475 self.logger.debug('iface %s not found' %ifaceobjrunning.name)
476 return
477 dhclientcmd = dhclient()
478 if (dhclientcmd.is_running(ifaceobjrunning.name) or
479 dhclientcmd.is_running6(ifaceobjrunning.name)):
480 # If dhcp is configured on the interface, we skip it
481 return
482 isloopback = self.ipcmd.link_isloopback(ifaceobjrunning.name)
483 if isloopback:
484 default_addrs = ['127.0.0.1/8', '::1/128']
485 ifaceobjrunning.addr_family = 'inet'
486 ifaceobjrunning.addr_method = 'loopback'
487 else:
488 default_addrs = []
489 runningaddrsdict = self.ipcmd.addr_get(ifaceobjrunning.name)
490 if runningaddrsdict:
491 [ifaceobjrunning.update_config('address', addr)
492 for addr, addrattrs in runningaddrsdict.items()
493 if addr not in default_addrs]
494 mtu = self.ipcmd.link_get_mtu(ifaceobjrunning.name)
495 if (mtu and
496 (ifaceobjrunning.name == 'lo' and mtu != '16436') or
497 (ifaceobjrunning.name != 'lo' and
498 mtu != self.get_mod_subattr('mtu', 'default'))):
499 ifaceobjrunning.update_config('mtu', mtu)
500 alias = self.ipcmd.link_get_alias(ifaceobjrunning.name)
501 if alias:
502 ifaceobjrunning.update_config('alias', alias)
503
504 _run_ops = {'up' : _up,
505 'down' : _down,
506 'query-checkcurr' : _query_check,
507 'query-running' : _query_running }
508
509 def get_ops(self):
510 """ returns list of ops supported by this module """
511 return self._run_ops.keys()
512
513 def _init_command_handlers(self):
514 if not self.ipcmd:
515 self.ipcmd = iproute2()
516
517 def run(self, ifaceobj, operation, query_ifaceobj=None, ifaceobj_getfunc=None):
518 """ run address configuration on the interface object passed as argument
519
520 Args:
521 **ifaceobj** (object): iface object
522
523 **operation** (str): any of 'up', 'down', 'query-checkcurr',
524 'query-running'
525 Kwargs:
526 query_ifaceobj (object): query check ifaceobject. This is only
527 valid when op is 'query-checkcurr'. It is an object same as
528 ifaceobj, but contains running attribute values and its config
529 status. The modules can use it to return queried running state
530 of interfaces. status is success if the running state is same
531 as user required state in ifaceobj. error otherwise.
532 """
533 if ifaceobj.type == ifaceType.BRIDGE_VLAN:
534 return
535 op_handler = self._run_ops.get(operation)
536 if not op_handler:
537 return
538 self._init_command_handlers()
539 if operation == 'query-checkcurr':
540 op_handler(self, ifaceobj, query_ifaceobj,
541 ifaceobj_getfunc=ifaceobj_getfunc)
542 else:
543 op_handler(self, ifaceobj,
544 ifaceobj_getfunc=ifaceobj_getfunc)