]> git.proxmox.com Git - mirror_ifupdown2.git/blob - addons/addressvirtual.py
addons: addressvirtual: fix handling of admin state of macvlan interfaces
[mirror_ifupdown2.git] / addons / addressvirtual.py
1 #!/usr/bin/python
2 #
3 # Copyright 2014 Cumulus Networks, Inc. All rights reserved.
4 # Author: Roopa Prabhu, roopa@cumulusnetworks.com
5 #
6
7 from ifupdown.iface import *
8 from ifupdownaddons.modulebase import moduleBase
9 from ifupdownaddons.iproute2 import iproute2
10 import ifupdown.ifupdownconfig as ifupdownConfig
11 import ifupdown.statemanager as statemanager
12 from ifupdown.netlink import netlink
13 import ifupdown.ifupdownflags as ifupdownflags
14
15 from ipaddr import IPNetwork, IPv4Network
16 import logging
17 import os
18 import glob
19
20 class 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 ips',
29 'validvals' : ['<mac-ip/prefixlen-list>',],
30 'example' : ['address-virtual 00:11:22:33:44:01 11.0.1.1/24 11.0.1.2/24']}
31 }}
32
33
34 def __init__(self, *args, **kargs):
35 moduleBase.__init__(self, *args, **kargs)
36 self.ipcmd = None
37 self._bridge_fdb_query_cache = {}
38
39 def _is_supported(self, ifaceobj):
40 if ifaceobj.get_attr_value_first('address-virtual'):
41 return True
42 return False
43
44 def _get_macvlan_prefix(self, ifaceobj):
45 return '%s-v' %ifaceobj.name[0:13].replace('.', '-')
46
47 def _add_addresses_to_bridge(self, ifaceobj, hwaddress):
48 # XXX: batch the addresses
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]
54 elif self.ipcmd.is_bridge(ifaceobj.name):
55 [self.ipcmd.bridge_fdb_add(ifaceobj.name, addr)
56 for addr in hwaddress]
57
58 def _remove_addresses_from_bridge(self, ifaceobj, hwaddress):
59 # XXX: batch the addresses
60 if '.' in ifaceobj.name:
61 (bridgename, vlan) = ifaceobj.name.split('.')
62 if self.ipcmd.bridge_is_vlan_aware(bridgename):
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
69 elif self.ipcmd.is_bridge(ifaceobj.name):
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
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
96
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
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
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
153 def _apply_address_config(self, ifaceobj, address_virtual_list):
154 purge_existing = False if ifupdownflags.flags.PERFMODE else True
155
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
161 hwaddress = []
162 self.ipcmd.batch_start()
163 av_idx = 0
164 macvlan_prefix = self._get_macvlan_prefix(ifaceobj)
165 for av in address_virtual_list:
166 av_attrs = av.split()
167 if len(av_attrs) < 2:
168 self.log_error("%s: incorrect address-virtual attrs '%s'"
169 %(ifaceobj.name, av), ifaceobj,
170 raise_error=False)
171 av_idx += 1
172 continue
173
174 mac = av_attrs[0]
175 if not self.check_mac_address(ifaceobj, mac):
176 continue
177 # Create a macvlan device on this device and set the virtual
178 # router mac and ip on it
179 link_created = False
180 macvlan_ifacename = '%s%d' %(macvlan_prefix, av_idx)
181 if not self.ipcmd.link_exists(macvlan_ifacename):
182 netlink.link_add_macvlan(ifaceobj.name, macvlan_ifacename)
183 link_created = True
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,
191 purge_existing)
192
193 # If link existed before, flap the link
194 if not link_created:
195 self._fix_connected_route(ifaceobj, macvlan_ifacename,
196 ips[0])
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
204 # set macvlan device to up in anycase.
205 # since we auto create them here..we are responsible
206 # to bring them up here in the case they were brought down
207 # by some other entity in the system.
208 netlink.link_set_updown(macvlan_ifacename, "up")
209
210 # handle vrf slaves
211 if (ifaceobj.link_privflags & ifaceLinkPrivFlags.VRF_SLAVE):
212 self._handle_vrf_slaves(macvlan_ifacename, ifaceobj)
213
214 # Disable IPv6 duplicate address detection on VRR interfaces
215 for key, sysval in { 'accept_dad' : '0', 'dad_transmits' : '0' }.iteritems():
216 syskey = 'net.ipv6.conf.%s.%s' % (macvlan_ifacename, key)
217 if self.sysctl_get(syskey) != sysval:
218 self.sysctl_set(syskey, sysval)
219
220 av_idx += 1
221 self.ipcmd.batch_commit()
222
223 # check the statemanager for old configs.
224 # We need to remove only the previously configured FDB entries
225 oldmacs = self._get_macs_from_old_config(ifaceobj)
226 # get a list of fdbs in old that are not in new config meaning they should
227 # be removed since they are gone from the config
228 removed_macs = [mac for mac in oldmacs if mac.lower() not in hwaddress]
229 self._remove_addresses_from_bridge(ifaceobj, removed_macs)
230 # if ifaceobj is a bridge and bridge is a vlan aware bridge
231 # add the vid to the bridge
232 self._add_addresses_to_bridge(ifaceobj, hwaddress)
233
234 def _remove_running_address_config(self, ifaceobj):
235 if not self.ipcmd.link_exists(ifaceobj.name):
236 return
237 hwaddress = []
238 self.ipcmd.batch_start()
239 macvlan_prefix = self._get_macvlan_prefix(ifaceobj)
240 for macvlan_ifacename in glob.glob("/sys/class/net/%s*" %macvlan_prefix):
241 macvlan_ifacename = os.path.basename(macvlan_ifacename)
242 if not self.ipcmd.link_exists(macvlan_ifacename):
243 continue
244 hwaddress.append(self.ipcmd.link_get_hwaddress(macvlan_ifacename))
245 self.ipcmd.link_delete(os.path.basename(macvlan_ifacename))
246 # XXX: Also delete any fdb addresses. This requires, checking mac address
247 # on individual macvlan interfaces and deleting the vlan from that.
248 self.ipcmd.batch_commit()
249 if any(hwaddress):
250 self._remove_addresses_from_bridge(ifaceobj, hwaddress)
251
252 def _remove_address_config(self, ifaceobj, address_virtual_list=None):
253 if not address_virtual_list:
254 self._remove_running_address_config(ifaceobj)
255 return
256
257 if not self.ipcmd.link_exists(ifaceobj.name):
258 return
259 hwaddress = []
260 self.ipcmd.batch_start()
261 av_idx = 0
262 macvlan_prefix = self._get_macvlan_prefix(ifaceobj)
263 for av in address_virtual_list:
264 av_attrs = av.split()
265 if len(av_attrs) < 2:
266 self.log_error("%s: incorrect address-virtual attrs '%s'"
267 %(ifaceobj.name, av), ifaceobj,
268 raise_error=False)
269 av_idx += 1
270 continue
271
272 # Delete the macvlan device on this device
273 macvlan_ifacename = '%s%d' %(macvlan_prefix, av_idx)
274 self.ipcmd.link_delete(os.path.basename(macvlan_ifacename))
275 if av_attrs[0] != 'None':
276 hwaddress.append(av_attrs[0])
277 av_idx += 1
278 self.ipcmd.batch_commit()
279 self._remove_addresses_from_bridge(ifaceobj, hwaddress)
280
281 def check_mac_address(self, ifaceobj, mac):
282 if mac == 'None':
283 return True
284 mac = mac.lower()
285 try:
286 if int(mac.split(":")[0], 16) & 1 :
287 self.logger.error("%s: Multicast bit is set in the virtual mac address '%s'" %(ifaceobj.name, mac))
288 return False
289 return True
290 except Exception, e:
291 return False
292
293 def _up(self, ifaceobj):
294 address_virtual_list = ifaceobj.get_attr_value('address-virtual')
295 if not address_virtual_list:
296 # XXX: address virtual is not present. In which case,
297 # delete stale macvlan devices.
298 self._remove_address_config(ifaceobj, address_virtual_list)
299 return
300
301 if (ifaceobj.upperifaces and
302 not ifaceobj.link_privflags & ifaceLinkPrivFlags.VRF_SLAVE):
303 self.log_error('%s: invalid placement of address-virtual lines (must be configured under an interface with no upper interfaces or parent interfaces)'
304 % (ifaceobj.name), ifaceobj)
305 return
306
307 if not self.ipcmd.link_exists(ifaceobj.name):
308 return
309 self._apply_address_config(ifaceobj, address_virtual_list)
310
311 def _down(self, ifaceobj):
312 try:
313 self._remove_address_config(ifaceobj,
314 ifaceobj.get_attr_value('address-virtual'))
315 except Exception, e:
316 self.log_warn(str(e))
317
318 def _query_check(self, ifaceobj, ifaceobjcurr):
319 address_virtual_list = ifaceobj.get_attr_value('address-virtual')
320 if not address_virtual_list:
321 return
322 if not self.ipcmd.link_exists(ifaceobj.name):
323 return
324 av_idx = 0
325 macvlan_prefix = self._get_macvlan_prefix(ifaceobj)
326 for address_virtual in address_virtual_list:
327 av_attrs = address_virtual.split()
328 if len(av_attrs) < 2:
329 self.logger.warn("%s: incorrect address-virtual attrs '%s'"
330 %(ifaceobj.name, address_virtual))
331 av_idx += 1
332 continue
333
334 # Check if the macvlan device on this interface
335 macvlan_ifacename = '%s%d' %(macvlan_prefix, av_idx)
336 if not self.ipcmd.link_exists(macvlan_ifacename):
337 ifaceobjcurr.update_config_with_status('address-virtual',
338 '', 1)
339 av_idx += 1
340 continue
341 # Check mac and ip address
342 rhwaddress = self.ipcmd.link_get_hwaddress(macvlan_ifacename)
343 raddrs = self.ipcmd.addr_get(macvlan_ifacename)
344 if not raddrs or not rhwaddress:
345 ifaceobjcurr.update_config_with_status('address-virtual', '', 1)
346 av_idx += 1
347 continue
348 raddrs = raddrs.keys()
349 try:
350 av_attrs[0] = ':'.join([i if len(i) == 2 else '0%s' % i
351 for i in av_attrs[0].split(':')])
352 except:
353 self.logger.info('%s: %s: invalid value for address-virtual (%s)'
354 % (ifaceobj.name,
355 macvlan_ifacename,
356 ' '.join(av_attrs)))
357 if (rhwaddress == av_attrs[0] and raddrs == av_attrs[1:] and
358 self._check_addresses_in_bridge(ifaceobj, av_attrs[0])):
359 ifaceobjcurr.update_config_with_status('address-virtual',
360 address_virtual, 0)
361 else:
362 raddress_virtual = '%s %s' %(rhwaddress, ' '.join(raddrs))
363 ifaceobjcurr.update_config_with_status('address-virtual',
364 raddress_virtual, 1)
365 av_idx += 1
366 return
367
368 def _query_running(self, ifaceobjrunning):
369 macvlan_prefix = self._get_macvlan_prefix(ifaceobjrunning)
370 address_virtuals = glob.glob("/sys/class/net/%s*" %macvlan_prefix)
371 for av in address_virtuals:
372 macvlan_ifacename = os.path.basename(av)
373 rhwaddress = self.ipcmd.link_get_hwaddress(macvlan_ifacename)
374 raddress = self.ipcmd.addr_get(macvlan_ifacename)
375 if not raddress:
376 self.logger.warn('%s: no running addresses'
377 %ifaceobjrunning.name)
378 raddress = []
379 ifaceobjrunning.update_config('address-virtual',
380 '%s %s' %(rhwaddress, ''.join(raddress)))
381 return
382
383 _run_ops = {'up' : _up,
384 'down' : _down,
385 'query-checkcurr' : _query_check,
386 'query-running' : _query_running}
387
388 def get_ops(self):
389 """ returns list of ops supported by this module """
390 return self._run_ops.keys()
391
392 def _init_command_handlers(self):
393 if not self.ipcmd:
394 self.ipcmd = iproute2()
395
396 def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args):
397 """ run vlan configuration on the interface object passed as argument
398
399 Args:
400 **ifaceobj** (object): iface object
401
402 **operation** (str): any of 'pre-up', 'post-down', 'query-checkcurr',
403 'query-running'
404 Kwargs:
405 **query_ifaceobj** (object): query check ifaceobject. This is only
406 valid when op is 'query-checkcurr'. It is an object same as
407 ifaceobj, but contains running attribute values and its config
408 status. The modules can use it to return queried running state
409 of interfaces. status is success if the running state is same
410 as user required state in ifaceobj. error otherwise.
411 """
412 if ifaceobj.type == ifaceType.BRIDGE_VLAN:
413 return
414 op_handler = self._run_ops.get(operation)
415 if not op_handler:
416 return
417 self._init_command_handlers()
418 if operation == 'query-checkcurr':
419 op_handler(self, ifaceobj, query_ifaceobj)
420 else:
421 op_handler(self, ifaceobj)