]>
Commit | Line | Data |
---|---|---|
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 | ||
7 | from ifupdown.iface import * | |
8 | from ifupdownaddons.modulebase import moduleBase | |
9 | from ifupdownaddons.iproute2 import iproute2 | |
9e0be374 | 10 | import ifupdown.ifupdownconfig as ifupdownConfig |
55072bd1 | 11 | import ifupdown.statemanager as statemanager |
2864d6f3 | 12 | from ifupdown.netlink import netlink |
fc5e1735 RP |
13 | import ifupdown.ifupdownflags as ifupdownflags |
14 | ||
c6370b56 | 15 | from ipaddr import IPNetwork, IPv4Network |
15ef32ea RP |
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' : | |
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) |