]> git.proxmox.com Git - mirror_ifupdown2.git/blob - addons/addressvirtual.py
attribute syntax check using validvals/validrange and keywords
[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-ipaddr/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 # handle vrf slaves
205 if (ifaceobj.link_privflags & ifaceLinkPrivFlags.VRF_SLAVE):
206 self._handle_vrf_slaves(macvlan_ifacename, ifaceobj)
207
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
214 av_idx += 1
215 self.ipcmd.batch_commit()
216
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
222 removed_macs = [mac for mac in oldmacs if mac.lower() not in hwaddress]
223 self._remove_addresses_from_bridge(ifaceobj, removed_macs)
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):
230 return
231 hwaddress = []
232 self.ipcmd.batch_start()
233 macvlan_prefix = self._get_macvlan_prefix(ifaceobj)
234 for macvlan_ifacename in glob.glob("/sys/class/net/%s*" %macvlan_prefix):
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))
239 self.ipcmd.link_delete(os.path.basename(macvlan_ifacename))
240 # XXX: Also delete any fdb addresses. This requires, checking mac address
241 # on individual macvlan interfaces and deleting the vlan from that.
242 self.ipcmd.batch_commit()
243 if any(hwaddress):
244 self._remove_addresses_from_bridge(ifaceobj, hwaddress)
245
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
256 macvlan_prefix = self._get_macvlan_prefix(ifaceobj)
257 for av in address_virtual_list:
258 av_attrs = av.split()
259 if len(av_attrs) < 2:
260 self.log_error("%s: incorrect address-virtual attrs '%s'"
261 %(ifaceobj.name, av), ifaceobj,
262 raise_error=False)
263 av_idx += 1
264 continue
265
266 # Delete the macvlan device on this device
267 macvlan_ifacename = '%s%d' %(macvlan_prefix, av_idx)
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)
274
275 def check_mac_address(self, ifaceobj, mac):
276 if mac == 'None':
277 return True
278 mac = mac.lower()
279 try:
280 if int(mac.split(":")[0], 16) & 1 :
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
287 def _up(self, ifaceobj):
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,
291 # delete stale macvlan devices.
292 self._remove_address_config(ifaceobj, address_virtual_list)
293 return
294
295 if (ifaceobj.upperifaces and
296 not ifaceobj.link_privflags & ifaceLinkPrivFlags.VRF_SLAVE):
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
301 if not self.ipcmd.link_exists(ifaceobj.name):
302 return
303 self._apply_address_config(ifaceobj, address_virtual_list)
304
305 def _down(self, ifaceobj):
306 try:
307 self._remove_address_config(ifaceobj,
308 ifaceobj.get_attr_value('address-virtual'))
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
316 if not self.ipcmd.link_exists(ifaceobj.name):
317 return
318 av_idx = 0
319 macvlan_prefix = self._get_macvlan_prefix(ifaceobj)
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
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
342 raddrs = raddrs.keys()
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)))
351 if (rhwaddress == av_attrs[0] and raddrs == av_attrs[1:] and
352 self._check_addresses_in_bridge(ifaceobj, av_attrs[0])):
353 ifaceobjcurr.update_config_with_status('address-virtual',
354 address_virtual, 0)
355 else:
356 raddress_virtual = '%s %s' %(rhwaddress, ' '.join(raddrs))
357 ifaceobjcurr.update_config_with_status('address-virtual',
358 raddress_virtual, 1)
359 av_idx += 1
360 return
361
362 def _query_running(self, ifaceobjrunning):
363 macvlan_prefix = self._get_macvlan_prefix(ifaceobjrunning)
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)))
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:
388 self.ipcmd = iproute2()
389
390 def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args):
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 """
406 if ifaceobj.type == ifaceType.BRIDGE_VLAN:
407 return
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)