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