]> git.proxmox.com Git - mirror_ifupdown2.git/blame - addons/addressvirtual.py
addons: bridge: syntax check for illegal subintf as member of VLAN aware bridge
[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' :
482b2fab 28 { 'help' : 'bridge router virtual mac and ips',
2c592263 29 'validvals' : ['<mac-ip/prefixlen-list>',],
c6370b56 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 197 if update_mtu:
0493bac6 198 lower_iface_mtu = self.ipcmd.link_get_mtu(ifaceobj.name, refresh=True)
9e0be374
JF
199 update_mtu = False
200
201 if lower_iface_mtu and lower_iface_mtu != self.ipcmd.link_get_mtu(macvlan_ifacename):
0493bac6
JF
202 try:
203 self.ipcmd.link_set_mtu(macvlan_ifacename,
204 lower_iface_mtu)
205 except Exception as e:
206 self.logger.info('%s: failed to set mtu %s: %s' %
207 (macvlan_ifacename, lower_iface_mtu, e))
9e0be374 208
dc3f4c45
RP
209 # set macvlan device to up in anycase.
210 # since we auto create them here..we are responsible
211 # to bring them up here in the case they were brought down
212 # by some other entity in the system.
213 netlink.link_set_updown(macvlan_ifacename, "up")
214
1b284018
RP
215 # handle vrf slaves
216 if (ifaceobj.link_privflags & ifaceLinkPrivFlags.VRF_SLAVE):
217 self._handle_vrf_slaves(macvlan_ifacename, ifaceobj)
218
f8ad40ce
SE
219 # Disable IPv6 duplicate address detection on VRR interfaces
220 for key, sysval in { 'accept_dad' : '0', 'dad_transmits' : '0' }.iteritems():
221 syskey = 'net.ipv6.conf.%s.%s' % (macvlan_ifacename, key)
222 if self.sysctl_get(syskey) != sysval:
223 self.sysctl_set(syskey, sysval)
224
15ef32ea
RP
225 av_idx += 1
226 self.ipcmd.batch_commit()
227
55072bd1
ST
228 # check the statemanager for old configs.
229 # We need to remove only the previously configured FDB entries
230 oldmacs = self._get_macs_from_old_config(ifaceobj)
231 # get a list of fdbs in old that are not in new config meaning they should
232 # be removed since they are gone from the config
5df79763 233 removed_macs = [mac for mac in oldmacs if mac.lower() not in hwaddress]
55072bd1 234 self._remove_addresses_from_bridge(ifaceobj, removed_macs)
e1601369
RP
235 # if ifaceobj is a bridge and bridge is a vlan aware bridge
236 # add the vid to the bridge
237 self._add_addresses_to_bridge(ifaceobj, hwaddress)
238
239 def _remove_running_address_config(self, ifaceobj):
240 if not self.ipcmd.link_exists(ifaceobj.name):
15ef32ea 241 return
e1601369 242 hwaddress = []
15ef32ea 243 self.ipcmd.batch_start()
aaef0a79 244 macvlan_prefix = self._get_macvlan_prefix(ifaceobj)
55072bd1 245 for macvlan_ifacename in glob.glob("/sys/class/net/%s*" %macvlan_prefix):
e1601369
RP
246 macvlan_ifacename = os.path.basename(macvlan_ifacename)
247 if not self.ipcmd.link_exists(macvlan_ifacename):
248 continue
249 hwaddress.append(self.ipcmd.link_get_hwaddress(macvlan_ifacename))
15ef32ea 250 self.ipcmd.link_delete(os.path.basename(macvlan_ifacename))
e1601369
RP
251 # XXX: Also delete any fdb addresses. This requires, checking mac address
252 # on individual macvlan interfaces and deleting the vlan from that.
15ef32ea 253 self.ipcmd.batch_commit()
e1601369
RP
254 if any(hwaddress):
255 self._remove_addresses_from_bridge(ifaceobj, hwaddress)
15ef32ea 256
e1601369
RP
257 def _remove_address_config(self, ifaceobj, address_virtual_list=None):
258 if not address_virtual_list:
259 self._remove_running_address_config(ifaceobj)
260 return
261
262 if not self.ipcmd.link_exists(ifaceobj.name):
263 return
264 hwaddress = []
265 self.ipcmd.batch_start()
266 av_idx = 0
aaef0a79 267 macvlan_prefix = self._get_macvlan_prefix(ifaceobj)
e1601369
RP
268 for av in address_virtual_list:
269 av_attrs = av.split()
270 if len(av_attrs) < 2:
bf3eda91
RP
271 self.log_error("%s: incorrect address-virtual attrs '%s'"
272 %(ifaceobj.name, av), ifaceobj,
273 raise_error=False)
e1601369
RP
274 av_idx += 1
275 continue
276
277 # Delete the macvlan device on this device
cb46a208 278 macvlan_ifacename = '%s%d' %(macvlan_prefix, av_idx)
e1601369
RP
279 self.ipcmd.link_delete(os.path.basename(macvlan_ifacename))
280 if av_attrs[0] != 'None':
281 hwaddress.append(av_attrs[0])
282 av_idx += 1
283 self.ipcmd.batch_commit()
284 self._remove_addresses_from_bridge(ifaceobj, hwaddress)
15ef32ea 285
4d3dc0f7
N
286 def check_mac_address(self, ifaceobj, mac):
287 if mac == 'None':
288 return True
289 mac = mac.lower()
290 try:
586535e8 291 if int(mac.split(":")[0], 16) & 1 :
4d3dc0f7
N
292 self.logger.error("%s: Multicast bit is set in the virtual mac address '%s'" %(ifaceobj.name, mac))
293 return False
294 return True
295 except Exception, e:
296 return False
297
15ef32ea 298 def _up(self, ifaceobj):
15ef32ea
RP
299 address_virtual_list = ifaceobj.get_attr_value('address-virtual')
300 if not address_virtual_list:
301 # XXX: address virtual is not present. In which case,
00f6105d 302 # delete stale macvlan devices.
e1601369 303 self._remove_address_config(ifaceobj, address_virtual_list)
15ef32ea
RP
304 return
305
1b284018
RP
306 if (ifaceobj.upperifaces and
307 not ifaceobj.link_privflags & ifaceLinkPrivFlags.VRF_SLAVE):
f466af7a
JF
308 self.log_error('%s: invalid placement of address-virtual lines (must be configured under an interface with no upper interfaces or parent interfaces)'
309 % (ifaceobj.name), ifaceobj)
310 return
311
e1601369 312 if not self.ipcmd.link_exists(ifaceobj.name):
15ef32ea 313 return
e1601369 314 self._apply_address_config(ifaceobj, address_virtual_list)
15ef32ea
RP
315
316 def _down(self, ifaceobj):
15ef32ea 317 try:
cb46a208
RP
318 self._remove_address_config(ifaceobj,
319 ifaceobj.get_attr_value('address-virtual'))
15ef32ea
RP
320 except Exception, e:
321 self.log_warn(str(e))
322
323 def _query_check(self, ifaceobj, ifaceobjcurr):
324 address_virtual_list = ifaceobj.get_attr_value('address-virtual')
325 if not address_virtual_list:
326 return
cb46a208
RP
327 if not self.ipcmd.link_exists(ifaceobj.name):
328 return
15ef32ea 329 av_idx = 0
aaef0a79 330 macvlan_prefix = self._get_macvlan_prefix(ifaceobj)
15ef32ea
RP
331 for address_virtual in address_virtual_list:
332 av_attrs = address_virtual.split()
333 if len(av_attrs) < 2:
334 self.logger.warn("%s: incorrect address-virtual attrs '%s'"
335 %(ifaceobj.name, address_virtual))
336 av_idx += 1
337 continue
338
339 # Check if the macvlan device on this interface
cb46a208
RP
340 macvlan_ifacename = '%s%d' %(macvlan_prefix, av_idx)
341 if not self.ipcmd.link_exists(macvlan_ifacename):
342 ifaceobjcurr.update_config_with_status('address-virtual',
343 '', 1)
344 av_idx += 1
345 continue
346 # Check mac and ip address
347 rhwaddress = self.ipcmd.link_get_hwaddress(macvlan_ifacename)
348 raddrs = self.ipcmd.addr_get(macvlan_ifacename)
349 if not raddrs or not rhwaddress:
350 ifaceobjcurr.update_config_with_status('address-virtual', '', 1)
351 av_idx += 1
352 continue
cb46a208 353 raddrs = raddrs.keys()
b6688146
JF
354 try:
355 av_attrs[0] = ':'.join([i if len(i) == 2 else '0%s' % i
356 for i in av_attrs[0].split(':')])
357 except:
358 self.logger.info('%s: %s: invalid value for address-virtual (%s)'
359 % (ifaceobj.name,
360 macvlan_ifacename,
361 ' '.join(av_attrs)))
8e113d63
RP
362 if (rhwaddress == av_attrs[0] and raddrs == av_attrs[1:] and
363 self._check_addresses_in_bridge(ifaceobj, av_attrs[0])):
cb46a208 364 ifaceobjcurr.update_config_with_status('address-virtual',
15ef32ea 365 address_virtual, 0)
cb46a208
RP
366 else:
367 raddress_virtual = '%s %s' %(rhwaddress, ' '.join(raddrs))
368 ifaceobjcurr.update_config_with_status('address-virtual',
15ef32ea
RP
369 raddress_virtual, 1)
370 av_idx += 1
371 return
372
373 def _query_running(self, ifaceobjrunning):
aaef0a79 374 macvlan_prefix = self._get_macvlan_prefix(ifaceobjrunning)
8e113d63
RP
375 address_virtuals = glob.glob("/sys/class/net/%s*" %macvlan_prefix)
376 for av in address_virtuals:
377 macvlan_ifacename = os.path.basename(av)
378 rhwaddress = self.ipcmd.link_get_hwaddress(macvlan_ifacename)
379 raddress = self.ipcmd.addr_get(macvlan_ifacename)
380 if not raddress:
381 self.logger.warn('%s: no running addresses'
382 %ifaceobjrunning.name)
383 raddress = []
384 ifaceobjrunning.update_config('address-virtual',
385 '%s %s' %(rhwaddress, ''.join(raddress)))
15ef32ea
RP
386 return
387
388 _run_ops = {'up' : _up,
389 'down' : _down,
390 'query-checkcurr' : _query_check,
391 'query-running' : _query_running}
392
393 def get_ops(self):
394 """ returns list of ops supported by this module """
395 return self._run_ops.keys()
396
397 def _init_command_handlers(self):
398 if not self.ipcmd:
fc5e1735 399 self.ipcmd = iproute2()
15ef32ea 400
84ca006f 401 def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args):
15ef32ea
RP
402 """ run vlan configuration on the interface object passed as argument
403
404 Args:
405 **ifaceobj** (object): iface object
406
407 **operation** (str): any of 'pre-up', 'post-down', 'query-checkcurr',
408 'query-running'
409 Kwargs:
410 **query_ifaceobj** (object): query check ifaceobject. This is only
411 valid when op is 'query-checkcurr'. It is an object same as
412 ifaceobj, but contains running attribute values and its config
413 status. The modules can use it to return queried running state
414 of interfaces. status is success if the running state is same
415 as user required state in ifaceobj. error otherwise.
416 """
84ca006f
RP
417 if ifaceobj.type == ifaceType.BRIDGE_VLAN:
418 return
15ef32ea
RP
419 op_handler = self._run_ops.get(operation)
420 if not op_handler:
421 return
422 self._init_command_handlers()
423 if operation == 'query-checkcurr':
424 op_handler(self, ifaceobj, query_ifaceobj)
425 else:
426 op_handler(self, ifaceobj)