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