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