]>
Commit | Line | Data |
---|---|---|
f82758bf 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', | |
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']}}} | |
62 | ||
63 | def __init__(self, *args, **kargs): | |
64 | moduleBase.__init__(self, *args, **kargs) | |
65 | self.ipcmd = None | |
66 | self._bridge_fdb_query_cache = {} | |
67 | ||
68 | def _address_valid(self, addrs): | |
69 | if not addrs: | |
70 | return False | |
71 | if any(map(lambda a: True if a[:7] != '0.0.0.0' | |
72 | else False, addrs)): | |
73 | return True | |
74 | return False | |
75 | ||
76 | def _process_bridge(self, ifaceobj, up): | |
77 | hwaddress = ifaceobj.get_attr_value_first('hwaddress') | |
78 | addrs = ifaceobj.get_attr_value_first('address') | |
79 | is_vlan_dev_on_vlan_aware_bridge = False | |
80 | is_bridge = self.ipcmd.is_bridge(ifaceobj.name) | |
81 | if not is_bridge: | |
82 | if '.' in ifaceobj.name: | |
83 | (bridgename, vlan) = ifaceobj.name.split('.') | |
84 | is_vlan_dev_on_vlan_aware_bridge = self.ipcmd.bridge_is_vlan_aware(bridgename) | |
85 | if ((is_bridge and not self.ipcmd.bridge_is_vlan_aware(ifaceobj.name)) | |
86 | or is_vlan_dev_on_vlan_aware_bridge): | |
87 | if self._address_valid(addrs): | |
88 | if up: | |
89 | self.write_file('/proc/sys/net/ipv4/conf/%s' %ifaceobj.name + | |
90 | '/arp_accept', '1') | |
91 | else: | |
92 | self.write_file('/proc/sys/net/ipv4/conf/%s' %ifaceobj.name + | |
93 | '/arp_accept', '0') | |
94 | if hwaddress and is_vlan_dev_on_vlan_aware_bridge: | |
95 | if up: | |
96 | self.ipcmd.bridge_fdb_add(bridgename, hwaddress, vlan) | |
97 | else: | |
98 | self.ipcmd.bridge_fdb_del(bridgename, hwaddress, vlan) | |
99 | ||
100 | def _inet_address_config(self, ifaceobj): | |
101 | purge_addresses = ifaceobj.get_attr_value_first('address-purge') | |
102 | if not purge_addresses: | |
103 | purge_addresses = 'yes' | |
104 | newaddrs = [] | |
105 | addrs = ifaceobj.get_attr_value('address') | |
106 | if addrs: | |
4fc71247 ST |
107 | if ifaceobj.role & ifaceRole.SLAVE: |
108 | # we must not configure an IP address if the interface is enslaved | |
109 | self.log_warn('interface %s is enslaved and cannot have an IP Address' % \ | |
110 | (ifaceobj.name)) | |
111 | return | |
f82758bf RP |
112 | # If user address is not in CIDR notation, convert them to CIDR |
113 | for addr_index in range(0, len(addrs)): | |
114 | addr = addrs[addr_index] | |
115 | if '/' in addr: | |
116 | newaddrs.append(addr) | |
117 | continue | |
118 | netmask = ifaceobj.get_attr_value_n('netmask', addr_index) | |
119 | if netmask: | |
120 | prefixlen = IPNetwork('%s' %addr + | |
121 | '/%s' %netmask).prefixlen | |
122 | newaddrs.append(addr + '/%s' %prefixlen) | |
123 | else: | |
124 | newaddrs.append(addr) | |
125 | ||
126 | if (not self.PERFMODE and | |
127 | not (ifaceobj.flags & iface.HAS_SIBLINGS) and | |
128 | purge_addresses == 'yes'): | |
129 | # if perfmode is not set and also if iface has no sibling | |
130 | # objects, purge addresses that are not present in the new | |
131 | # config | |
132 | runningaddrs = self.ipcmd.addr_get(ifaceobj.name, details=False) | |
133 | if newaddrs == runningaddrs: | |
134 | return | |
135 | try: | |
136 | # if primary address is not same, there is no need to keep any. | |
137 | # reset all addresses | |
138 | if (newaddrs and runningaddrs and | |
139 | (newaddrs[0] != runningaddrs[0])): | |
140 | self.ipcmd.del_addr_all(ifaceobj.name) | |
141 | else: | |
142 | self.ipcmd.del_addr_all(ifaceobj.name, newaddrs) | |
143 | except Exception, e: | |
144 | self.log_warn(str(e)) | |
145 | if not newaddrs: | |
146 | return | |
147 | for addr_index in range(0, len(newaddrs)): | |
148 | try: | |
149 | self.ipcmd.addr_add(ifaceobj.name, newaddrs[addr_index], | |
150 | ifaceobj.get_attr_value_n('broadcast', addr_index), | |
151 | ifaceobj.get_attr_value_n('pointopoint',addr_index), | |
152 | ifaceobj.get_attr_value_n('scope', addr_index), | |
153 | ifaceobj.get_attr_value_n('preferred-lifetime', addr_index)) | |
154 | except Exception, e: | |
155 | self.log_error(str(e)) | |
156 | ||
157 | def _up(self, ifaceobj): | |
158 | if not self.ipcmd.link_exists(ifaceobj.name): | |
159 | return | |
160 | addr_method = ifaceobj.addr_method | |
161 | try: | |
162 | # release any stale dhcp addresses if present | |
163 | if (addr_method != "dhcp" and not self.PERFMODE and | |
164 | not (ifaceobj.flags & iface.HAS_SIBLINGS)): | |
165 | # if not running in perf mode and ifaceobj does not have | |
166 | # any sibling iface objects, kill any stale dhclient | |
167 | # processes | |
168 | dhclientcmd = dhclient() | |
169 | if dhclient.is_running(ifaceobj.name): | |
170 | # release any dhcp leases | |
171 | dhclientcmd.release(ifaceobj.name) | |
172 | elif dhclient.is_running6(ifaceobj.name): | |
173 | dhclientcmd.release6(ifaceobj.name) | |
174 | except: | |
175 | pass | |
176 | ||
177 | self.ipcmd.batch_start() | |
178 | if addr_method != "dhcp": | |
179 | self._inet_address_config(ifaceobj) | |
180 | mtu = ifaceobj.get_attr_value_first('mtu') | |
181 | if mtu: | |
182 | self.ipcmd.link_set(ifaceobj.name, 'mtu', mtu) | |
183 | alias = ifaceobj.get_attr_value_first('alias') | |
184 | if alias: | |
185 | self.ipcmd.link_set_alias(ifaceobj.name, alias) | |
186 | hwaddress = ifaceobj.get_attr_value_first('hwaddress') | |
187 | if hwaddress: | |
188 | self.ipcmd.link_set(ifaceobj.name, 'address', hwaddress) | |
189 | self.ipcmd.batch_commit() | |
190 | ||
191 | try: | |
192 | # Handle special things on a bridge | |
193 | self._process_bridge(ifaceobj, True) | |
194 | except Exception, e: | |
195 | self.log_warn('%s: %s' %(ifaceobj.name, str(e))) | |
196 | pass | |
197 | ||
198 | if addr_method != "dhcp": | |
199 | self.ipcmd.route_add_gateway(ifaceobj.name, | |
200 | ifaceobj.get_attr_value_first('gateway')) | |
201 | ||
202 | def _down(self, ifaceobj): | |
203 | try: | |
204 | if not self.ipcmd.link_exists(ifaceobj.name): | |
205 | return | |
206 | addr_method = ifaceobj.addr_method | |
207 | if addr_method != "dhcp": | |
208 | self.ipcmd.route_del_gateway(ifaceobj.name, | |
209 | ifaceobj.get_attr_value_first('gateway'), | |
210 | ifaceobj.get_attr_value_first('metric')) | |
211 | self.ipcmd.del_addr_all(ifaceobj.name) | |
f82758bf RP |
212 | alias = ifaceobj.get_attr_value_first('alias') |
213 | if alias: | |
214 | self.ipcmd.link_set(ifaceobj.name, 'alias', "\'\'") | |
215 | # XXX hwaddress reset cannot happen because we dont know last | |
216 | # address. | |
217 | ||
218 | # Handle special things on a bridge | |
219 | self._process_bridge(ifaceobj, False) | |
220 | except Exception, e: | |
221 | self.logger.debug('%s : %s' %(ifaceobj.name, str(e))) | |
222 | pass | |
223 | ||
224 | def _get_iface_addresses(self, ifaceobj): | |
225 | addrlist = ifaceobj.get_attr_value('address') | |
226 | outaddrlist = [] | |
227 | ||
228 | if not addrlist: return None | |
229 | for addrindex in range(0, len(addrlist)): | |
230 | addr = addrlist[addrindex] | |
231 | netmask = ifaceobj.get_attr_value_n('netmask', addrindex) | |
232 | if netmask: | |
233 | prefixlen = IPNetwork('%s' %addr + | |
234 | '/%s' %netmask).prefixlen | |
235 | addr = addr + '/%s' %prefixlen | |
236 | outaddrlist.append(addr) | |
237 | return outaddrlist | |
238 | ||
239 | def _get_bridge_fdbs(self, bridgename, vlan): | |
240 | fdbs = self._bridge_fdb_query_cache.get(bridgename) | |
241 | if not fdbs: | |
242 | fdbs = self.ipcmd.bridge_fdb_show_dev(bridgename) | |
243 | if not fdbs: | |
244 | return | |
245 | self._bridge_fdb_query_cache[bridgename] = fdbs | |
246 | return fdbs.get(vlan) | |
247 | ||
248 | def _check_addresses_in_bridge(self, ifaceobj, hwaddress): | |
249 | """ If the device is a bridge, make sure the addresses | |
250 | are in the bridge """ | |
251 | if '.' in ifaceobj.name: | |
252 | (bridgename, vlan) = ifaceobj.name.split('.') | |
253 | if self.ipcmd.bridge_is_vlan_aware(bridgename): | |
254 | fdb_addrs = self._get_bridge_fdbs(bridgename, vlan) | |
255 | if not fdb_addrs or hwaddress not in fdb_addrs: | |
256 | return False | |
257 | return True | |
258 | ||
259 | def _query_check(self, ifaceobj, ifaceobjcurr): | |
260 | runningaddrsdict = None | |
261 | if not self.ipcmd.link_exists(ifaceobj.name): | |
262 | self.logger.debug('iface %s not found' %ifaceobj.name) | |
263 | return | |
264 | addr_method = ifaceobj.addr_method | |
265 | self.query_n_update_ifaceobjcurr_attr(ifaceobj, ifaceobjcurr, | |
266 | 'mtu', self.ipcmd.link_get_mtu) | |
267 | hwaddress = ifaceobj.get_attr_value_first('hwaddress') | |
268 | if hwaddress: | |
269 | rhwaddress = self.ipcmd.link_get_hwaddress(ifaceobj.name) | |
270 | if not rhwaddress or rhwaddress != hwaddress: | |
271 | ifaceobjcurr.update_config_with_status('hwaddress', rhwaddress, | |
272 | 1) | |
273 | elif not self._check_addresses_in_bridge(ifaceobj, hwaddress): | |
274 | # XXX: hw address is not in bridge | |
275 | ifaceobjcurr.update_config_with_status('hwaddress', rhwaddress, | |
276 | 1) | |
277 | ifaceobjcurr.status_str = 'bridge fdb error' | |
278 | else: | |
279 | ifaceobjcurr.update_config_with_status('hwaddress', rhwaddress, | |
280 | 0) | |
281 | self.query_n_update_ifaceobjcurr_attr(ifaceobj, ifaceobjcurr, | |
282 | 'alias', self.ipcmd.link_get_alias) | |
283 | # compare addresses | |
284 | if addr_method == 'dhcp': | |
285 | return | |
286 | addrs = self._get_iface_addresses(ifaceobj) | |
287 | runningaddrsdict = self.ipcmd.addr_get(ifaceobj.name) | |
288 | ||
289 | # Set ifaceobjcurr method and family | |
290 | ifaceobjcurr.addr_method = ifaceobj.addr_method | |
291 | ifaceobjcurr.addr_family = ifaceobj.addr_family | |
292 | if not runningaddrsdict and not addrs: | |
293 | return | |
294 | runningaddrs = runningaddrsdict.keys() if runningaddrsdict else [] | |
295 | if runningaddrs != addrs: | |
296 | runningaddrsset = set(runningaddrs) if runningaddrs else set([]) | |
297 | addrsset = set(addrs) if addrs else set([]) | |
298 | if (ifaceobj.flags & iface.HAS_SIBLINGS): | |
299 | if not addrsset: | |
300 | return | |
301 | # only check for addresses present in running config | |
302 | addrsdiff = addrsset.difference(runningaddrsset) | |
303 | for addr in addrs: | |
304 | if addr in addrsdiff: | |
305 | ifaceobjcurr.update_config_with_status('address', | |
306 | addr, 1) | |
307 | else: | |
308 | ifaceobjcurr.update_config_with_status('address', | |
309 | addr, 0) | |
310 | else: | |
311 | addrsdiff = addrsset.symmetric_difference(runningaddrsset) | |
312 | for addr in addrsset.union(runningaddrsset): | |
313 | if addr in addrsdiff: | |
314 | ifaceobjcurr.update_config_with_status('address', | |
315 | addr, 1) | |
316 | else: | |
317 | ifaceobjcurr.update_config_with_status('address', | |
318 | addr, 0) | |
319 | elif addrs: | |
320 | [ifaceobjcurr.update_config_with_status('address', | |
321 | addr, 0) for addr in addrs] | |
322 | #XXXX Check broadcast address, scope, etc | |
323 | return | |
324 | ||
325 | def _query_running(self, ifaceobjrunning): | |
326 | if not self.ipcmd.link_exists(ifaceobjrunning.name): | |
327 | self.logger.debug('iface %s not found' %ifaceobjrunning.name) | |
328 | return | |
329 | dhclientcmd = dhclient() | |
330 | if (dhclientcmd.is_running(ifaceobjrunning.name) or | |
331 | dhclientcmd.is_running6(ifaceobjrunning.name)): | |
332 | # If dhcp is configured on the interface, we skip it | |
333 | return | |
334 | isloopback = self.ipcmd.link_isloopback(ifaceobjrunning.name) | |
335 | if isloopback: | |
336 | default_addrs = ['127.0.0.1/8', '::1/128'] | |
337 | ifaceobjrunning.addr_family = 'inet' | |
338 | ifaceobjrunning.addr_method = 'loopback' | |
339 | else: | |
340 | default_addrs = [] | |
341 | runningaddrsdict = self.ipcmd.addr_get(ifaceobjrunning.name) | |
342 | if runningaddrsdict: | |
343 | [ifaceobjrunning.update_config('address', addr) | |
344 | for addr, addrattrs in runningaddrsdict.items() | |
345 | if addr not in default_addrs] | |
346 | mtu = self.ipcmd.link_get_mtu(ifaceobjrunning.name) | |
347 | if (mtu and | |
348 | (ifaceobjrunning.name == 'lo' and mtu != '16436') or | |
349 | (ifaceobjrunning.name != 'lo' and | |
350 | mtu != self.get_mod_subattr('mtu', 'default'))): | |
351 | ifaceobjrunning.update_config('mtu', mtu) | |
352 | alias = self.ipcmd.link_get_alias(ifaceobjrunning.name) | |
353 | if alias: | |
354 | ifaceobjrunning.update_config('alias', alias) | |
355 | ||
356 | _run_ops = {'up' : _up, | |
357 | 'down' : _down, | |
358 | 'query-checkcurr' : _query_check, | |
359 | 'query-running' : _query_running } | |
360 | ||
361 | def get_ops(self): | |
362 | """ returns list of ops supported by this module """ | |
363 | return self._run_ops.keys() | |
364 | ||
365 | def _init_command_handlers(self): | |
366 | if not self.ipcmd: | |
367 | self.ipcmd = iproute2(**self.get_flags()) | |
368 | ||
369 | def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args): | |
370 | """ run address configuration on the interface object passed as argument | |
371 | ||
372 | Args: | |
373 | **ifaceobj** (object): iface object | |
374 | ||
375 | **operation** (str): any of 'up', 'down', 'query-checkcurr', | |
376 | 'query-running' | |
377 | Kwargs: | |
378 | query_ifaceobj (object): query check ifaceobject. This is only | |
379 | valid when op is 'query-checkcurr'. It is an object same as | |
380 | ifaceobj, but contains running attribute values and its config | |
381 | status. The modules can use it to return queried running state | |
382 | of interfaces. status is success if the running state is same | |
383 | as user required state in ifaceobj. error otherwise. | |
384 | """ | |
385 | if ifaceobj.type == ifaceType.BRIDGE_VLAN: | |
386 | return | |
387 | op_handler = self._run_ops.get(operation) | |
388 | if not op_handler: | |
389 | return | |
390 | self._init_command_handlers() | |
391 | if operation == 'query-checkcurr': | |
392 | op_handler(self, ifaceobj, query_ifaceobj) | |
393 | else: | |
394 | op_handler(self, ifaceobj) |