]>
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 | try: | |
8 | from ipaddr import IPNetwork | |
9 | from sets import Set | |
10 | from ifupdown.iface import * | |
11 | from ifupdownaddons.modulebase import moduleBase | |
12 | from ifupdownaddons.iproute2 import iproute2 | |
13 | from ifupdownaddons.dhclient import dhclient | |
14 | except ImportError, e: | |
15 | raise ImportError (str(e) + "- required module not found") | |
16 | ||
17 | class address(moduleBase): | |
18 | """ ifupdown2 addon module to configure address, mtu, hwaddress, alias | |
19 | (description) on an interface """ | |
20 | ||
21 | _modinfo = {'mhelp' : 'address configuration module for interfaces', | |
22 | 'attrs': { | |
23 | 'address' : | |
24 | {'help' : 'ipv4 or ipv6 addresses', | |
25 | 'example' : ['address 10.0.12.3/24', | |
26 | 'address 2000:1000:1000:1000:3::5/128']}, | |
27 | 'netmask' : | |
28 | {'help': 'netmask', | |
29 | 'example' : ['netmask 255.255.255.0'], | |
30 | 'compat' : True}, | |
31 | 'broadcast' : | |
32 | {'help': 'broadcast address', | |
33 | 'example' : ['broadcast 10.0.1.255']}, | |
34 | 'scope' : | |
35 | {'help': 'scope', | |
36 | 'example' : ['scope host']}, | |
37 | 'preferred-lifetime' : | |
38 | {'help': 'preferred lifetime', | |
39 | 'example' : ['preferred-lifetime forever', | |
40 | 'preferred-lifetime 10']}, | |
41 | 'gateway' : | |
42 | {'help': 'default gateway', | |
43 | 'example' : ['gateway 255.255.255.0']}, | |
44 | 'mtu' : | |
45 | { 'help': 'interface mtu', | |
46 | 'example' : ['mtu 1600'], | |
47 | 'default' : '1500'}, | |
48 | 'hwaddress' : | |
49 | {'help' : 'hw address', | |
50 | 'example': ['hwaddress 44:38:39:00:27:b8']}, | |
51 | 'alias' : | |
52 | { 'help': 'description/alias', | |
394e68b5 RP |
53 | 'example' : ['alias testnetwork']}, |
54 | 'address-purge' : | |
55 | { 'help': 'purge existing addresses. By default ' + | |
56 | 'any existing ip addresses on an interface are ' + | |
57 | 'purged to match persistant addresses in the ' + | |
58 | 'interfaces file. Set this attribute to \'no\'' + | |
59 | 'if you want to preserve existing addresses', | |
60 | 'default' : 'yes', | |
61 | 'example' : ['address-purge yes/no']}}} | |
15ef32ea RP |
62 | |
63 | def __init__(self, *args, **kargs): | |
64 | moduleBase.__init__(self, *args, **kargs) | |
65 | self.ipcmd = None | |
8e113d63 | 66 | self._bridge_fdb_query_cache = {} |
15ef32ea | 67 | |
cb46a208 RP |
68 | def _add_address_to_bridge(self, ifaceobj, hwaddress): |
69 | if '.' in ifaceobj.name: | |
70 | (bridgename, vlan) = ifaceobj.name.split('.') | |
71 | if self.ipcmd.bridge_is_vlan_aware(bridgename): | |
72 | self.ipcmd.bridge_fdb_add(bridgename, hwaddress, | |
73 | vlan) | |
74 | ||
75 | def _remove_address_from_bridge(self, ifaceobj, hwaddress): | |
76 | if '.' in ifaceobj.name: | |
77 | (bridgename, vlan) = ifaceobj.name.split('.') | |
78 | if self.ipcmd.bridge_is_vlan_aware(bridgename): | |
79 | self.ipcmd.bridge_fdb_del(bridgename, hwaddress, | |
80 | vlan) | |
81 | ||
15ef32ea | 82 | def _inet_address_config(self, ifaceobj): |
394e68b5 RP |
83 | purge_addresses = ifaceobj.get_attr_value_first('address-purge') |
84 | if not purge_addresses: | |
85 | purge_addresses = 'yes' | |
15ef32ea RP |
86 | newaddrs = [] |
87 | addrs = ifaceobj.get_attr_value('address') | |
88 | if addrs: | |
89 | # If user address is not in CIDR notation, convert them to CIDR | |
90 | for addr_index in range(0, len(addrs)): | |
91 | addr = addrs[addr_index] | |
92 | if '/' in addr: | |
93 | newaddrs.append(addr) | |
94 | continue | |
95 | netmask = ifaceobj.get_attr_value_n('netmask', addr_index) | |
96 | if netmask: | |
97 | prefixlen = IPNetwork('%s' %addr + | |
98 | '/%s' %netmask).prefixlen | |
99 | newaddrs.append(addr + '/%s' %prefixlen) | |
100 | else: | |
101 | newaddrs.append(addr) | |
102 | ||
394e68b5 RP |
103 | if (not self.PERFMODE and |
104 | not (ifaceobj.flags & iface.HAS_SIBLINGS) and | |
105 | purge_addresses == 'yes'): | |
15ef32ea RP |
106 | # if perfmode is not set and also if iface has no sibling |
107 | # objects, purge addresses that are not present in the new | |
108 | # config | |
109 | runningaddrs = self.ipcmd.addr_get(ifaceobj.name, details=False) | |
110 | if newaddrs == runningaddrs: | |
111 | return | |
112 | try: | |
113 | # if primary address is not same, there is no need to keep any. | |
114 | # reset all addresses | |
115 | if (newaddrs and runningaddrs and | |
116 | (newaddrs[0] != runningaddrs[0])): | |
117 | self.ipcmd.del_addr_all(ifaceobj.name) | |
118 | else: | |
119 | self.ipcmd.del_addr_all(ifaceobj.name, newaddrs) | |
120 | except Exception, e: | |
121 | self.log_warn(str(e)) | |
122 | if not newaddrs: | |
123 | return | |
124 | for addr_index in range(0, len(newaddrs)): | |
125 | try: | |
126 | self.ipcmd.addr_add(ifaceobj.name, newaddrs[addr_index], | |
127 | ifaceobj.get_attr_value_n('broadcast', addr_index), | |
128 | ifaceobj.get_attr_value_n('pointopoint',addr_index), | |
129 | ifaceobj.get_attr_value_n('scope', addr_index), | |
130 | ifaceobj.get_attr_value_n('preferred-lifetime', addr_index)) | |
131 | except Exception, e: | |
132 | self.log_error(str(e)) | |
133 | ||
134 | def _up(self, ifaceobj): | |
135 | if not self.ipcmd.link_exists(ifaceobj.name): | |
136 | return | |
137 | try: | |
138 | # release any stale dhcp addresses if present | |
139 | if (not self.PERFMODE and | |
140 | not (ifaceobj.flags & iface.HAS_SIBLINGS)): | |
141 | # if not running in perf mode and ifaceobj does not have | |
142 | # any sibling iface objects, kill any stale dhclient | |
143 | # processes | |
144 | dhclientcmd = self.dhclient() | |
145 | if dhclient.is_running(ifaceobj.name): | |
146 | # release any dhcp leases | |
147 | dhclientcmd.release(ifaceobj.name) | |
148 | elif dhclient.is_running6(ifaceobj.name): | |
149 | dhclientcmd.release6(ifaceobj.name) | |
150 | except: | |
151 | pass | |
8e113d63 | 152 | |
15ef32ea RP |
153 | self.ipcmd.batch_start() |
154 | self._inet_address_config(ifaceobj) | |
155 | mtu = ifaceobj.get_attr_value_first('mtu') | |
156 | if mtu: | |
8e113d63 | 157 | self.ipcmd.link_set(ifaceobj.name, 'mtu', mtu) |
15ef32ea RP |
158 | alias = ifaceobj.get_attr_value_first('alias') |
159 | if alias: | |
8e113d63 | 160 | self.ipcmd.link_set_alias(ifaceobj.name, alias) |
cb46a208 RP |
161 | hwaddress = ifaceobj.get_attr_value_first('hwaddress') |
162 | if hwaddress: | |
163 | self.ipcmd.link_set(ifaceobj.name, 'address', hwaddress) | |
15ef32ea | 164 | self.ipcmd.batch_commit() |
cb46a208 RP |
165 | |
166 | # After all adds are successful, also add the hw address | |
167 | # to the bridge if required | |
168 | if hwaddress: | |
169 | self._add_address_to_bridge(ifaceobj, hwaddress) | |
170 | ||
15ef32ea RP |
171 | self.ipcmd.route_add_gateway(ifaceobj.name, |
172 | ifaceobj.get_attr_value_first('gateway')) | |
173 | ||
174 | def _down(self, ifaceobj): | |
175 | try: | |
176 | if not self.ipcmd.link_exists(ifaceobj.name): | |
177 | return | |
178 | self.ipcmd.route_del_gateway(ifaceobj.name, | |
179 | ifaceobj.get_attr_value_first('gateway'), | |
180 | ifaceobj.get_attr_value_first('metric')) | |
181 | self.ipcmd.del_addr_all(ifaceobj.name) | |
182 | mtu = ifaceobj.get_attr_value_first('mtu') | |
183 | if mtu: | |
184 | self.ipcmd.link_set(ifaceobj.name, 'mtu', | |
185 | self.get_mod_subattr('mtu', 'default')) | |
186 | alias = ifaceobj.get_attr_value_first('alias') | |
187 | if alias: | |
188 | self.ipcmd.link_set(ifaceobj.name, 'alias', "\'\'") | |
cb46a208 RP |
189 | hwaddress = ifaceobj.get_attr_value_first('hwaddress') |
190 | if hwaddress: | |
191 | # XXX Dont know what to reset the address to | |
192 | self._remove_address_from_bridge(ifaceobj, hwaddress) | |
15ef32ea RP |
193 | except Exception, e: |
194 | self.log_warn(str(e)) | |
195 | ||
196 | def _get_iface_addresses(self, ifaceobj): | |
197 | addrlist = ifaceobj.get_attr_value('address') | |
198 | outaddrlist = [] | |
199 | ||
200 | if not addrlist: return None | |
201 | for addrindex in range(0, len(addrlist)): | |
202 | addr = addrlist[addrindex] | |
203 | netmask = ifaceobj.get_attr_value_n('netmask', addrindex) | |
204 | if netmask: | |
205 | prefixlen = IPNetwork('%s' %addr + | |
206 | '/%s' %netmask).prefixlen | |
207 | addr = addr + '/%s' %prefixlen | |
208 | outaddrlist.append(addr) | |
209 | return outaddrlist | |
210 | ||
8e113d63 RP |
211 | def _get_bridge_fdbs(self, bridgename, vlan): |
212 | fdbs = self._bridge_fdb_query_cache.get(bridgename) | |
213 | if not fdbs: | |
214 | fdbs = self.ipcmd.bridge_fdb_show_dev(bridgename) | |
215 | if not fdbs: | |
216 | return | |
217 | self._bridge_fdb_query_cache[bridgename] = fdbs | |
218 | return fdbs.get(vlan) | |
219 | ||
220 | def _check_addresses_in_bridge(self, ifaceobj, hwaddress): | |
221 | """ If the device is a bridge, make sure the addresses | |
222 | are in the bridge """ | |
223 | if '.' in ifaceobj.name: | |
224 | (bridgename, vlan) = ifaceobj.name.split('.') | |
225 | if self.ipcmd.bridge_is_vlan_aware(bridgename): | |
226 | fdb_addrs = self._get_bridge_fdbs(bridgename, vlan) | |
227 | if not fdb_addrs or hwaddress not in fdb_addrs: | |
228 | return False | |
229 | return True | |
230 | ||
15ef32ea RP |
231 | def _query_check(self, ifaceobj, ifaceobjcurr): |
232 | runningaddrsdict = None | |
233 | if not self.ipcmd.link_exists(ifaceobj.name): | |
234 | self.logger.debug('iface %s not found' %ifaceobj.name) | |
235 | return | |
236 | self.query_n_update_ifaceobjcurr_attr(ifaceobj, ifaceobjcurr, | |
237 | 'mtu', self.ipcmd.link_get_mtu) | |
8e113d63 RP |
238 | hwaddress = ifaceobj.get_attr_value_first('hwaddress') |
239 | if hwaddress: | |
240 | rhwaddress = self.ipcmd.link_get_hwaddress(ifaceobj.name) | |
241 | if not rhwaddress or rhwaddress != hwaddress: | |
242 | ifaceobjcurr.update_config_with_status('hwaddress', rhwaddress, | |
243 | 1) | |
244 | elif not self._check_addresses_in_bridge(ifaceobj, hwaddress): | |
245 | # XXX: hw address is not in bridge | |
246 | ifaceobjcurr.update_config_with_status('hwaddress', rhwaddress, | |
247 | 1) | |
248 | ifaceobjcurr.status_str = 'bridge fdb error' | |
249 | else: | |
250 | ifaceobjcurr.update_config_with_status('hwaddress', rhwaddress, | |
251 | 0) | |
15ef32ea RP |
252 | self.query_n_update_ifaceobjcurr_attr(ifaceobj, ifaceobjcurr, |
253 | 'alias', self.ipcmd.link_get_alias) | |
254 | # compare addresses | |
255 | addrs = self._get_iface_addresses(ifaceobj) | |
256 | runningaddrsdict = self.ipcmd.addr_get(ifaceobj.name) | |
257 | ||
258 | # Set ifaceobjcurr method and family | |
259 | ifaceobjcurr.addr_method = ifaceobj.addr_method | |
260 | ifaceobjcurr.addr_family = ifaceobj.addr_family | |
261 | if not runningaddrsdict and not addrs: | |
262 | return | |
263 | runningaddrs = runningaddrsdict.keys() if runningaddrsdict else [] | |
264 | if runningaddrs != addrs: | |
265 | runningaddrsset = set(runningaddrs) if runningaddrs else set([]) | |
266 | addrsset = set(addrs) if addrs else set([]) | |
267 | if (ifaceobj.flags & iface.HAS_SIBLINGS): | |
268 | if not addrsset: | |
269 | return | |
270 | # only check for addresses present in running config | |
271 | addrsdiff = addrsset.difference(runningaddrsset) | |
272 | for addr in addrs: | |
273 | if addr in addrsdiff: | |
274 | ifaceobjcurr.update_config_with_status('address', | |
275 | addr, 1) | |
276 | else: | |
277 | ifaceobjcurr.update_config_with_status('address', | |
278 | addr, 0) | |
279 | else: | |
280 | addrsdiff = addrsset.symmetric_difference(runningaddrsset) | |
281 | for addr in addrsset.union(runningaddrsset): | |
282 | if addr in addrsdiff: | |
283 | ifaceobjcurr.update_config_with_status('address', | |
284 | addr, 1) | |
285 | else: | |
286 | ifaceobjcurr.update_config_with_status('address', | |
287 | addr, 0) | |
288 | elif addrs: | |
289 | [ifaceobjcurr.update_config_with_status('address', | |
290 | addr, 0) for addr in addrs] | |
291 | #XXXX Check broadcast address, scope, etc | |
292 | return | |
293 | ||
294 | def _query_running(self, ifaceobjrunning): | |
295 | if not self.ipcmd.link_exists(ifaceobjrunning.name): | |
296 | self.logger.debug('iface %s not found' %ifaceobjrunning.name) | |
297 | ifaceobjrunning.status = ifaceStatus.NOTFOUND | |
298 | return | |
299 | dhclientcmd = dhclient() | |
300 | if (dhclientcmd.is_running(ifaceobjrunning.name) or | |
301 | dhclientcmd.is_running6(ifaceobjrunning.name)): | |
302 | # If dhcp is configured on the interface, we skip it | |
303 | return | |
304 | isloopback = self.ipcmd.link_isloopback(ifaceobjrunning.name) | |
305 | if isloopback: | |
306 | default_addrs = ['127.0.0.1/8', '::1/128'] | |
307 | ifaceobjrunning.addr_family = 'inet' | |
308 | ifaceobjrunning.addr_method = 'loopback' | |
309 | else: | |
310 | default_addrs = [] | |
311 | runningaddrsdict = self.ipcmd.addr_get(ifaceobjrunning.name) | |
312 | if runningaddrsdict: | |
313 | [ifaceobjrunning.update_config('address', addr) | |
314 | for addr, addrattrs in runningaddrsdict.items() | |
315 | if addr not in default_addrs] | |
316 | mtu = self.ipcmd.link_get_mtu(ifaceobjrunning.name) | |
317 | if (mtu and | |
318 | (ifaceobjrunning.name == 'lo' and mtu != '16436') or | |
319 | (ifaceobjrunning.name != 'lo' and | |
320 | mtu != self.get_mod_subattr('mtu', 'default'))): | |
321 | ifaceobjrunning.update_config('mtu', mtu) | |
322 | alias = self.ipcmd.link_get_alias(ifaceobjrunning.name) | |
323 | if alias: | |
324 | ifaceobjrunning.update_config('alias', alias) | |
325 | ||
326 | _run_ops = {'up' : _up, | |
327 | 'down' : _down, | |
328 | 'query-checkcurr' : _query_check, | |
329 | 'query-running' : _query_running } | |
330 | ||
331 | def get_ops(self): | |
332 | """ returns list of ops supported by this module """ | |
333 | return self._run_ops.keys() | |
334 | ||
335 | def _init_command_handlers(self): | |
336 | if not self.ipcmd: | |
337 | self.ipcmd = iproute2(**self.get_flags()) | |
338 | ||
84ca006f | 339 | def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args): |
15ef32ea RP |
340 | """ run address configuration on the interface object passed as argument |
341 | ||
342 | Args: | |
343 | **ifaceobj** (object): iface object | |
344 | ||
345 | **operation** (str): any of 'up', 'down', 'query-checkcurr', | |
346 | 'query-running' | |
347 | Kwargs: | |
348 | query_ifaceobj (object): query check ifaceobject. This is only | |
349 | valid when op is 'query-checkcurr'. It is an object same as | |
350 | ifaceobj, but contains running attribute values and its config | |
351 | status. The modules can use it to return queried running state | |
352 | of interfaces. status is success if the running state is same | |
353 | as user required state in ifaceobj. error otherwise. | |
354 | """ | |
8e113d63 RP |
355 | if ifaceobj.type == ifaceType.BRIDGE_VLAN: |
356 | return | |
15ef32ea RP |
357 | op_handler = self._run_ops.get(operation) |
358 | if not op_handler: | |
359 | return | |
360 | if (operation != 'query-running' and ifaceobj.addr_family and | |
361 | ifaceobj.addr_family != 'inet' and | |
362 | ifaceobj.addr_family != 'inet6'): | |
363 | return | |
364 | if (operation != 'query-running' and ifaceobj.addr_method and | |
365 | ifaceobj.addr_method != 'static' and | |
366 | ifaceobj.addr_method != 'loopback'): | |
367 | return | |
368 | self._init_command_handlers() | |
369 | if operation == 'query-checkcurr': | |
370 | op_handler(self, ifaceobj, query_ifaceobj) | |
371 | else: | |
372 | op_handler(self, ifaceobj) |