]> git.proxmox.com Git - mirror_ifupdown2.git/blame - addons/addressvirtual.py
addons: bridge & mstpctl: avoid printing default and port attributes under vlan aware...
[mirror_ifupdown2.git] / addons / addressvirtual.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
7from ifupdown.iface import *
8from ifupdownaddons.modulebase import moduleBase
9from ifupdownaddons.iproute2 import iproute2
9e0be374 10import ifupdown.ifupdownconfig as ifupdownConfig
55072bd1 11import ifupdown.statemanager as statemanager
2864d6f3 12from ifupdown.netlink import netlink
fc5e1735
RP
13import ifupdown.ifupdownflags as ifupdownflags
14
c6370b56 15from ipaddr import IPNetwork, IPv4Network
15ef32ea
RP
16import logging
17import os
18import glob
19
20class addressvirtual(moduleBase):
21 """ ifupdown2 addon module to configure virtual addresses """
22
23 _modinfo = {'mhelp' : 'address module configures virtual addresses for ' +
24 'interfaces. It creates a macvlan interface for ' +
25 'every mac ip address-virtual line',
26 'attrs' : {
27 'address-virtual' :
28 { 'help' : 'bridge router virtual mac and ip',
c6370b56
DW
29 'validvals' : [('<mac>', IPv4Network), ],
30 'example' : ['address-virtual 00:11:22:33:44:01 11.0.1.1/24 11.0.1.2/24']}
15ef32ea
RP
31 }}
32
8e113d63 33
15ef32ea
RP
34 def __init__(self, *args, **kargs):
35 moduleBase.__init__(self, *args, **kargs)
36 self.ipcmd = None
8e113d63 37 self._bridge_fdb_query_cache = {}
15ef32ea
RP
38
39 def _is_supported(self, ifaceobj):
40 if ifaceobj.get_attr_value_first('address-virtual'):
41 return True
42 return False
43
aaef0a79
RP
44 def _get_macvlan_prefix(self, ifaceobj):
45 return '%s-v' %ifaceobj.name[0:13].replace('.', '-')
46
e1601369 47 def _add_addresses_to_bridge(self, ifaceobj, hwaddress):
8e113d63 48 # XXX: batch the addresses
e1601369
RP
49 if '.' in ifaceobj.name:
50 (bridgename, vlan) = ifaceobj.name.split('.')
51 if self.ipcmd.bridge_is_vlan_aware(bridgename):
52 [self.ipcmd.bridge_fdb_add(bridgename, addr,
53 vlan) for addr in hwaddress]
8e113d63
RP
54 elif self.ipcmd.is_bridge(ifaceobj.name):
55 [self.ipcmd.bridge_fdb_add(ifaceobj.name, addr)
56 for addr in hwaddress]
e1601369
RP
57
58 def _remove_addresses_from_bridge(self, ifaceobj, hwaddress):
8e113d63 59 # XXX: batch the addresses
e1601369 60 if '.' in ifaceobj.name:
55072bd1 61 (bridgename, vlan) = ifaceobj.name.split('.')
e1601369 62 if self.ipcmd.bridge_is_vlan_aware(bridgename):
55072bd1
ST
63 for addr in hwaddress:
64 try:
65 self.ipcmd.bridge_fdb_del(bridgename, addr, vlan)
66 except Exception, e:
67 self.logger.debug("%s: %s" %(ifaceobj.name, str(e)))
68 pass
8e113d63 69 elif self.ipcmd.is_bridge(ifaceobj.name):
55072bd1
ST
70 for addr in hwaddress:
71 try:
72 self.ipcmd.bridge_fdb_del(ifaceobj.name, addr)
73 except Exception, e:
74 self.logger.debug("%s: %s" %(ifaceobj.name, str(e)))
75 pass
8e113d63
RP
76
77 def _get_bridge_fdbs(self, bridgename, vlan):
78 fdbs = self._bridge_fdb_query_cache.get(bridgename)
79 if not fdbs:
80 fdbs = self.ipcmd.bridge_fdb_show_dev(bridgename)
81 if not fdbs:
82 return
83 self._bridge_fdb_query_cache[bridgename] = fdbs
84 return fdbs.get(vlan)
85
86 def _check_addresses_in_bridge(self, ifaceobj, hwaddress):
87 """ If the device is a bridge, make sure the addresses
88 are in the bridge """
89 if '.' in ifaceobj.name:
90 (bridgename, vlan) = ifaceobj.name.split('.')
91 if self.ipcmd.bridge_is_vlan_aware(bridgename):
92 fdb_addrs = self._get_bridge_fdbs(bridgename, vlan)
93 if not fdb_addrs or hwaddress not in fdb_addrs:
94 return False
95 return True
e1601369 96
00f6105d
RP
97 def _fix_connected_route(self, ifaceobj, vifacename, addr):
98 #
99 # XXX: Hack to make sure the primary address
100 # is the first in the routing table.
101 #
102 # We use `ip route get` on the vrr network to see which
103 # device the kernel returns. if it is the mac vlan device,
104 # flap the macvlan device to adjust the routing table entry.
105 #
106 # flapping the macvlan device makes sure the macvlan
107 # connected route goes through delete + add, hence adjusting
108 # the order in the routing table.
109 #
110 try:
111 self.logger.info('%s: checking route entry ...' %ifaceobj.name)
112 ip = IPNetwork(addr)
113 route_prefix = '%s/%d' %(ip.network, ip.prefixlen)
114
115 dev = self.ipcmd.ip_route_get_dev(route_prefix)
116 if dev and dev == vifacename:
117 self.logger.info('%s: preferred routing entry ' %ifaceobj.name +
118 'seems to be of the macvlan dev %s'
119 %vifacename +
120 ' .. flapping macvlan dev to fix entry.')
121 self.ipcmd.link_down(vifacename)
122 self.ipcmd.link_up(vifacename)
123 except Exception, e:
124 self.logger.debug('%s: fixing route entry failed (%s)'
125 %str(e))
126 pass
127
1b284018
RP
128 def _handle_vrf_slaves(self, macvlan_ifacename, ifaceobj):
129 vrfname = self.ipcmd.link_get_master(ifaceobj.name)
130 if vrfname:
131 self.ipcmd.link_set(macvlan_ifacename, 'master', vrfname)
132
55072bd1
ST
133 def _get_macs_from_old_config(self, ifaceobj=None):
134 """ This method returns a list of the mac addresses
135 in the address-virtual attribute for the bridge. """
136 maclist = []
137 saved_ifaceobjs = statemanager.statemanager_api.get_ifaceobjs(ifaceobj.name)
138 if not saved_ifaceobjs:
139 return maclist
140 # we need the old saved configs from the statemanager
141 for oldifaceobj in saved_ifaceobjs:
142 if not oldifaceobj.get_attr_value('address-virtual'):
143 continue
144 for av in oldifaceobj.get_attr_value('address-virtual'):
145 macip = av.split()
146 if len(macip) < 2:
147 self.logger.debug("%s: incorrect old address-virtual attrs '%s'"
148 %(oldifaceobj.name, av))
149 continue
150 maclist.append(macip[0])
151 return maclist
152
e1601369 153 def _apply_address_config(self, ifaceobj, address_virtual_list):
fc5e1735 154 purge_existing = False if ifupdownflags.flags.PERFMODE else True
15ef32ea 155
9e0be374
JF
156 lower_iface_mtu = update_mtu = None
157 if ifupdownConfig.config.get('adjust_logical_dev_mtu', '1') != '0':
158 if ifaceobj.lowerifaces and address_virtual_list:
159 update_mtu = True
160
e1601369 161 hwaddress = []
15ef32ea
RP
162 self.ipcmd.batch_start()
163 av_idx = 0
aaef0a79 164 macvlan_prefix = self._get_macvlan_prefix(ifaceobj)
15ef32ea
RP
165 for av in address_virtual_list:
166 av_attrs = av.split()
167 if len(av_attrs) < 2:
bf3eda91
RP
168 self.log_error("%s: incorrect address-virtual attrs '%s'"
169 %(ifaceobj.name, av), ifaceobj,
170 raise_error=False)
15ef32ea
RP
171 av_idx += 1
172 continue
173
4d3dc0f7
N
174 mac = av_attrs[0]
175 if not self.check_mac_address(ifaceobj, mac):
176 continue
15ef32ea
RP
177 # Create a macvlan device on this device and set the virtual
178 # router mac and ip on it
00f6105d 179 link_created = False
cb46a208 180 macvlan_ifacename = '%s%d' %(macvlan_prefix, av_idx)
e74d01e1 181 if not self.ipcmd.link_exists(macvlan_ifacename):
2864d6f3 182 netlink.link_add_macvlan(ifaceobj.name, macvlan_ifacename)
00f6105d 183 link_created = True
5df79763
ST
184 ips = av_attrs[1:]
185 if mac != 'None':
186 mac = mac.lower()
187 # customer could have used UPPERCASE for MAC
188 self.ipcmd.link_set_hwaddress(macvlan_ifacename, mac)
189 hwaddress.append(mac)
190 self.ipcmd.addr_add_multiple(macvlan_ifacename, ips,
15ef32ea 191 purge_existing)
9e0be374 192
00f6105d
RP
193 # If link existed before, flap the link
194 if not link_created:
195 self._fix_connected_route(ifaceobj, macvlan_ifacename,
5df79763 196 ips[0])
9e0be374
JF
197 if update_mtu:
198 lower_iface_mtu = self.ipcmd.link_get_mtu(ifaceobj.lowerifaces[0], refresh=True)
199 update_mtu = False
200
201 if lower_iface_mtu and lower_iface_mtu != self.ipcmd.link_get_mtu(macvlan_ifacename):
202 self.ipcmd.link_set_mtu(macvlan_ifacename, lower_iface_mtu)
203
1b284018
RP
204 # handle vrf slaves
205 if (ifaceobj.link_privflags & ifaceLinkPrivFlags.VRF_SLAVE):
206 self._handle_vrf_slaves(macvlan_ifacename, ifaceobj)
207
f8ad40ce
SE
208 # Disable IPv6 duplicate address detection on VRR interfaces
209 for key, sysval in { 'accept_dad' : '0', 'dad_transmits' : '0' }.iteritems():
210 syskey = 'net.ipv6.conf.%s.%s' % (macvlan_ifacename, key)
211 if self.sysctl_get(syskey) != sysval:
212 self.sysctl_set(syskey, sysval)
213
15ef32ea
RP
214 av_idx += 1
215 self.ipcmd.batch_commit()
216
55072bd1
ST
217 # check the statemanager for old configs.
218 # We need to remove only the previously configured FDB entries
219 oldmacs = self._get_macs_from_old_config(ifaceobj)
220 # get a list of fdbs in old that are not in new config meaning they should
221 # be removed since they are gone from the config
5df79763 222 removed_macs = [mac for mac in oldmacs if mac.lower() not in hwaddress]
55072bd1 223 self._remove_addresses_from_bridge(ifaceobj, removed_macs)
e1601369
RP
224 # if ifaceobj is a bridge and bridge is a vlan aware bridge
225 # add the vid to the bridge
226 self._add_addresses_to_bridge(ifaceobj, hwaddress)
227
228 def _remove_running_address_config(self, ifaceobj):
229 if not self.ipcmd.link_exists(ifaceobj.name):
15ef32ea 230 return
e1601369 231 hwaddress = []
15ef32ea 232 self.ipcmd.batch_start()
aaef0a79 233 macvlan_prefix = self._get_macvlan_prefix(ifaceobj)
55072bd1 234 for macvlan_ifacename in glob.glob("/sys/class/net/%s*" %macvlan_prefix):
e1601369
RP
235 macvlan_ifacename = os.path.basename(macvlan_ifacename)
236 if not self.ipcmd.link_exists(macvlan_ifacename):
237 continue
238 hwaddress.append(self.ipcmd.link_get_hwaddress(macvlan_ifacename))
15ef32ea 239 self.ipcmd.link_delete(os.path.basename(macvlan_ifacename))
e1601369
RP
240 # XXX: Also delete any fdb addresses. This requires, checking mac address
241 # on individual macvlan interfaces and deleting the vlan from that.
15ef32ea 242 self.ipcmd.batch_commit()
e1601369
RP
243 if any(hwaddress):
244 self._remove_addresses_from_bridge(ifaceobj, hwaddress)
15ef32ea 245
e1601369
RP
246 def _remove_address_config(self, ifaceobj, address_virtual_list=None):
247 if not address_virtual_list:
248 self._remove_running_address_config(ifaceobj)
249 return
250
251 if not self.ipcmd.link_exists(ifaceobj.name):
252 return
253 hwaddress = []
254 self.ipcmd.batch_start()
255 av_idx = 0
aaef0a79 256 macvlan_prefix = self._get_macvlan_prefix(ifaceobj)
e1601369
RP
257 for av in address_virtual_list:
258 av_attrs = av.split()
259 if len(av_attrs) < 2:
bf3eda91
RP
260 self.log_error("%s: incorrect address-virtual attrs '%s'"
261 %(ifaceobj.name, av), ifaceobj,
262 raise_error=False)
e1601369
RP
263 av_idx += 1
264 continue
265
266 # Delete the macvlan device on this device
cb46a208 267 macvlan_ifacename = '%s%d' %(macvlan_prefix, av_idx)
e1601369
RP
268 self.ipcmd.link_delete(os.path.basename(macvlan_ifacename))
269 if av_attrs[0] != 'None':
270 hwaddress.append(av_attrs[0])
271 av_idx += 1
272 self.ipcmd.batch_commit()
273 self._remove_addresses_from_bridge(ifaceobj, hwaddress)
15ef32ea 274
4d3dc0f7
N
275 def check_mac_address(self, ifaceobj, mac):
276 if mac == 'None':
277 return True
278 mac = mac.lower()
279 try:
586535e8 280 if int(mac.split(":")[0], 16) & 1 :
4d3dc0f7
N
281 self.logger.error("%s: Multicast bit is set in the virtual mac address '%s'" %(ifaceobj.name, mac))
282 return False
283 return True
284 except Exception, e:
285 return False
286
15ef32ea 287 def _up(self, ifaceobj):
15ef32ea
RP
288 address_virtual_list = ifaceobj.get_attr_value('address-virtual')
289 if not address_virtual_list:
290 # XXX: address virtual is not present. In which case,
00f6105d 291 # delete stale macvlan devices.
e1601369 292 self._remove_address_config(ifaceobj, address_virtual_list)
15ef32ea
RP
293 return
294
1b284018
RP
295 if (ifaceobj.upperifaces and
296 not ifaceobj.link_privflags & ifaceLinkPrivFlags.VRF_SLAVE):
f466af7a
JF
297 self.log_error('%s: invalid placement of address-virtual lines (must be configured under an interface with no upper interfaces or parent interfaces)'
298 % (ifaceobj.name), ifaceobj)
299 return
300
e1601369 301 if not self.ipcmd.link_exists(ifaceobj.name):
15ef32ea 302 return
e1601369 303 self._apply_address_config(ifaceobj, address_virtual_list)
15ef32ea
RP
304
305 def _down(self, ifaceobj):
15ef32ea 306 try:
cb46a208
RP
307 self._remove_address_config(ifaceobj,
308 ifaceobj.get_attr_value('address-virtual'))
15ef32ea
RP
309 except Exception, e:
310 self.log_warn(str(e))
311
312 def _query_check(self, ifaceobj, ifaceobjcurr):
313 address_virtual_list = ifaceobj.get_attr_value('address-virtual')
314 if not address_virtual_list:
315 return
cb46a208
RP
316 if not self.ipcmd.link_exists(ifaceobj.name):
317 return
15ef32ea 318 av_idx = 0
aaef0a79 319 macvlan_prefix = self._get_macvlan_prefix(ifaceobj)
15ef32ea
RP
320 for address_virtual in address_virtual_list:
321 av_attrs = address_virtual.split()
322 if len(av_attrs) < 2:
323 self.logger.warn("%s: incorrect address-virtual attrs '%s'"
324 %(ifaceobj.name, address_virtual))
325 av_idx += 1
326 continue
327
328 # Check if the macvlan device on this interface
cb46a208
RP
329 macvlan_ifacename = '%s%d' %(macvlan_prefix, av_idx)
330 if not self.ipcmd.link_exists(macvlan_ifacename):
331 ifaceobjcurr.update_config_with_status('address-virtual',
332 '', 1)
333 av_idx += 1
334 continue
335 # Check mac and ip address
336 rhwaddress = self.ipcmd.link_get_hwaddress(macvlan_ifacename)
337 raddrs = self.ipcmd.addr_get(macvlan_ifacename)
338 if not raddrs or not rhwaddress:
339 ifaceobjcurr.update_config_with_status('address-virtual', '', 1)
340 av_idx += 1
341 continue
cb46a208 342 raddrs = raddrs.keys()
b6688146
JF
343 try:
344 av_attrs[0] = ':'.join([i if len(i) == 2 else '0%s' % i
345 for i in av_attrs[0].split(':')])
346 except:
347 self.logger.info('%s: %s: invalid value for address-virtual (%s)'
348 % (ifaceobj.name,
349 macvlan_ifacename,
350 ' '.join(av_attrs)))
8e113d63
RP
351 if (rhwaddress == av_attrs[0] and raddrs == av_attrs[1:] and
352 self._check_addresses_in_bridge(ifaceobj, av_attrs[0])):
cb46a208 353 ifaceobjcurr.update_config_with_status('address-virtual',
15ef32ea 354 address_virtual, 0)
cb46a208
RP
355 else:
356 raddress_virtual = '%s %s' %(rhwaddress, ' '.join(raddrs))
357 ifaceobjcurr.update_config_with_status('address-virtual',
15ef32ea
RP
358 raddress_virtual, 1)
359 av_idx += 1
360 return
361
362 def _query_running(self, ifaceobjrunning):
aaef0a79 363 macvlan_prefix = self._get_macvlan_prefix(ifaceobjrunning)
8e113d63
RP
364 address_virtuals = glob.glob("/sys/class/net/%s*" %macvlan_prefix)
365 for av in address_virtuals:
366 macvlan_ifacename = os.path.basename(av)
367 rhwaddress = self.ipcmd.link_get_hwaddress(macvlan_ifacename)
368 raddress = self.ipcmd.addr_get(macvlan_ifacename)
369 if not raddress:
370 self.logger.warn('%s: no running addresses'
371 %ifaceobjrunning.name)
372 raddress = []
373 ifaceobjrunning.update_config('address-virtual',
374 '%s %s' %(rhwaddress, ''.join(raddress)))
15ef32ea
RP
375 return
376
377 _run_ops = {'up' : _up,
378 'down' : _down,
379 'query-checkcurr' : _query_check,
380 'query-running' : _query_running}
381
382 def get_ops(self):
383 """ returns list of ops supported by this module """
384 return self._run_ops.keys()
385
386 def _init_command_handlers(self):
387 if not self.ipcmd:
fc5e1735 388 self.ipcmd = iproute2()
15ef32ea 389
84ca006f 390 def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args):
15ef32ea
RP
391 """ run vlan configuration on the interface object passed as argument
392
393 Args:
394 **ifaceobj** (object): iface object
395
396 **operation** (str): any of 'pre-up', 'post-down', 'query-checkcurr',
397 'query-running'
398 Kwargs:
399 **query_ifaceobj** (object): query check ifaceobject. This is only
400 valid when op is 'query-checkcurr'. It is an object same as
401 ifaceobj, but contains running attribute values and its config
402 status. The modules can use it to return queried running state
403 of interfaces. status is success if the running state is same
404 as user required state in ifaceobj. error otherwise.
405 """
84ca006f
RP
406 if ifaceobj.type == ifaceType.BRIDGE_VLAN:
407 return
15ef32ea
RP
408 op_handler = self._run_ops.get(operation)
409 if not op_handler:
410 return
411 self._init_command_handlers()
412 if operation == 'query-checkcurr':
413 op_handler(self, ifaceobj, query_ifaceobj)
414 else:
415 op_handler(self, ifaceobj)