]>
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 | |
55072bd1 | 10 | import ifupdown.statemanager as statemanager |
e74d01e1 | 11 | import ifupdown.rtnetlink_api as rtnetlink_api |
00f6105d | 12 | from ipaddr import IPNetwork |
15ef32ea RP |
13 | import logging |
14 | import os | |
15 | import glob | |
16 | ||
17 | class 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) |