3 # Copyright 2014 Cumulus Networks, Inc. All rights reserved.
4 # Author: Roopa Prabhu, roopa@cumulusnetworks.com
8 from ipaddr
import IPNetwork
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")
17 class address(moduleBase
):
18 """ ifupdown2 addon module to configure address, mtu, hwaddress, alias
19 (description) on an interface """
21 _modinfo
= {'mhelp' : 'address configuration module for interfaces',
24 {'help' : 'ipv4 or ipv6 addresses',
25 'example' : ['address 10.0.12.3/24',
26 'address 2000:1000:1000:1000:3::5/128']},
29 'example' : ['netmask 255.255.255.0'],
32 {'help': 'broadcast address',
33 'example' : ['broadcast 10.0.1.255']},
36 'example' : ['scope host']},
37 'preferred-lifetime' :
38 {'help': 'preferred lifetime',
39 'example' : ['preferred-lifetime forever',
40 'preferred-lifetime 10']},
42 {'help': 'default gateway',
43 'example' : ['gateway 255.255.255.0']},
45 { 'help': 'interface mtu',
46 'example' : ['mtu 1600'],
49 {'help' : 'hw address',
50 'example': ['hwaddress 44:38:39:00:27:b8']},
52 { 'help': 'description/alias',
53 'example' : ['alias testnetwork']},
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',
61 'example' : ['address-purge yes/no']}}}
63 def __init__(self
, *args
, **kargs
):
64 moduleBase
.__init
__(self
, *args
, **kargs
)
66 self
._bridge
_fdb
_query
_cache
= {}
68 def _address_valid(self
, addrs
):
71 if any(map(lambda a
: True if a
[:7] != '0.0.0.0'
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
)
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
):
89 self
.write_file('/proc/sys/net/ipv4/conf/%s' %ifaceobj
.name
+
92 self
.write_file('/proc/sys/net/ipv4/conf/%s' %ifaceobj
.name
+
94 if hwaddress
and is_vlan_dev_on_vlan_aware_bridge
:
96 self
.ipcmd
.bridge_fdb_add(bridgename
, hwaddress
, vlan
)
98 self
.ipcmd
.bridge_fdb_del(bridgename
, hwaddress
, vlan
)
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'
105 addrs
= ifaceobj
.get_attr_value('address')
107 # If user address is not in CIDR notation, convert them to CIDR
108 for addr_index
in range(0, len(addrs
)):
109 addr
= addrs
[addr_index
]
111 newaddrs
.append(addr
)
113 netmask
= ifaceobj
.get_attr_value_n('netmask', addr_index
)
115 prefixlen
= IPNetwork('%s' %addr
+
116 '/%s' %netmask
).prefixlen
117 newaddrs
.append(addr
+ '/%s' %prefixlen
)
119 newaddrs
.append(addr
)
121 if (not self
.PERFMODE
and
122 not (ifaceobj
.flags
& iface
.HAS_SIBLINGS
) and
123 purge_addresses
== 'yes'):
124 # if perfmode is not set and also if iface has no sibling
125 # objects, purge addresses that are not present in the new
127 runningaddrs
= self
.ipcmd
.addr_get(ifaceobj
.name
, details
=False)
128 if newaddrs
== runningaddrs
:
131 # if primary address is not same, there is no need to keep any.
132 # reset all addresses
133 if (newaddrs
and runningaddrs
and
134 (newaddrs
[0] != runningaddrs
[0])):
135 self
.ipcmd
.del_addr_all(ifaceobj
.name
)
137 self
.ipcmd
.del_addr_all(ifaceobj
.name
, newaddrs
)
139 self
.log_warn(str(e
))
142 for addr_index
in range(0, len(newaddrs
)):
144 self
.ipcmd
.addr_add(ifaceobj
.name
, newaddrs
[addr_index
],
145 ifaceobj
.get_attr_value_n('broadcast', addr_index
),
146 ifaceobj
.get_attr_value_n('pointopoint',addr_index
),
147 ifaceobj
.get_attr_value_n('scope', addr_index
),
148 ifaceobj
.get_attr_value_n('preferred-lifetime', addr_index
))
150 self
.log_error(str(e
))
152 def _up(self
, ifaceobj
):
153 if not self
.ipcmd
.link_exists(ifaceobj
.name
):
155 addr_method
= ifaceobj
.addr_method
157 # release any stale dhcp addresses if present
158 if (addr_method
!= "dhcp" and not self
.PERFMODE
and
159 not (ifaceobj
.flags
& iface
.HAS_SIBLINGS
)):
160 # if not running in perf mode and ifaceobj does not have
161 # any sibling iface objects, kill any stale dhclient
163 dhclientcmd
= dhclient()
164 if dhclient
.is_running(ifaceobj
.name
):
165 # release any dhcp leases
166 dhclientcmd
.release(ifaceobj
.name
)
167 elif dhclient
.is_running6(ifaceobj
.name
):
168 dhclientcmd
.release6(ifaceobj
.name
)
172 self
.ipcmd
.batch_start()
173 if addr_method
!= "dhcp":
174 self
._inet
_address
_config
(ifaceobj
)
175 mtu
= ifaceobj
.get_attr_value_first('mtu')
177 self
.ipcmd
.link_set(ifaceobj
.name
, 'mtu', mtu
)
178 alias
= ifaceobj
.get_attr_value_first('alias')
180 self
.ipcmd
.link_set_alias(ifaceobj
.name
, alias
)
181 hwaddress
= ifaceobj
.get_attr_value_first('hwaddress')
183 self
.ipcmd
.link_set(ifaceobj
.name
, 'address', hwaddress
)
184 self
.ipcmd
.batch_commit()
187 # Handle special things on a bridge
188 self
._process
_bridge
(ifaceobj
, True)
190 self
.log_warn('%s: %s' %(ifaceobj
.name
, str(e
)))
193 if addr_method
!= "dhcp":
194 self
.ipcmd
.route_add_gateway(ifaceobj
.name
,
195 ifaceobj
.get_attr_value_first('gateway'))
197 def _down(self
, ifaceobj
):
199 if not self
.ipcmd
.link_exists(ifaceobj
.name
):
201 addr_method
= ifaceobj
.addr_method
202 if addr_method
!= "dhcp":
203 self
.ipcmd
.route_del_gateway(ifaceobj
.name
,
204 ifaceobj
.get_attr_value_first('gateway'),
205 ifaceobj
.get_attr_value_first('metric'))
206 self
.ipcmd
.del_addr_all(ifaceobj
.name
)
207 alias
= ifaceobj
.get_attr_value_first('alias')
209 self
.ipcmd
.link_set(ifaceobj
.name
, 'alias', "\'\'")
210 # XXX hwaddress reset cannot happen because we dont know last
213 # Handle special things on a bridge
214 self
._process
_bridge
(ifaceobj
, False)
216 self
.logger
.debug('%s : %s' %(ifaceobj
.name
, str(e
)))
219 def _get_iface_addresses(self
, ifaceobj
):
220 addrlist
= ifaceobj
.get_attr_value('address')
223 if not addrlist
: return None
224 for addrindex
in range(0, len(addrlist
)):
225 addr
= addrlist
[addrindex
]
226 netmask
= ifaceobj
.get_attr_value_n('netmask', addrindex
)
228 prefixlen
= IPNetwork('%s' %addr
+
229 '/%s' %netmask
).prefixlen
230 addr
= addr
+ '/%s' %prefixlen
231 outaddrlist
.append(addr
)
234 def _get_bridge_fdbs(self
, bridgename
, vlan
):
235 fdbs
= self
._bridge
_fdb
_query
_cache
.get(bridgename
)
237 fdbs
= self
.ipcmd
.bridge_fdb_show_dev(bridgename
)
240 self
._bridge
_fdb
_query
_cache
[bridgename
] = fdbs
241 return fdbs
.get(vlan
)
243 def _check_addresses_in_bridge(self
, ifaceobj
, hwaddress
):
244 """ If the device is a bridge, make sure the addresses
245 are in the bridge """
246 if '.' in ifaceobj
.name
:
247 (bridgename
, vlan
) = ifaceobj
.name
.split('.')
248 if self
.ipcmd
.bridge_is_vlan_aware(bridgename
):
249 fdb_addrs
= self
._get
_bridge
_fdbs
(bridgename
, vlan
)
250 if not fdb_addrs
or hwaddress
not in fdb_addrs
:
254 def _query_check(self
, ifaceobj
, ifaceobjcurr
):
255 runningaddrsdict
= None
256 if not self
.ipcmd
.link_exists(ifaceobj
.name
):
257 self
.logger
.debug('iface %s not found' %ifaceobj
.name
)
259 addr_method
= ifaceobj
.addr_method
260 self
.query_n_update_ifaceobjcurr_attr(ifaceobj
, ifaceobjcurr
,
261 'mtu', self
.ipcmd
.link_get_mtu
)
262 hwaddress
= ifaceobj
.get_attr_value_first('hwaddress')
264 rhwaddress
= self
.ipcmd
.link_get_hwaddress(ifaceobj
.name
)
265 if not rhwaddress
or rhwaddress
!= hwaddress
:
266 ifaceobjcurr
.update_config_with_status('hwaddress', rhwaddress
,
268 elif not self
._check
_addresses
_in
_bridge
(ifaceobj
, hwaddress
):
269 # XXX: hw address is not in bridge
270 ifaceobjcurr
.update_config_with_status('hwaddress', rhwaddress
,
272 ifaceobjcurr
.status_str
= 'bridge fdb error'
274 ifaceobjcurr
.update_config_with_status('hwaddress', rhwaddress
,
276 self
.query_n_update_ifaceobjcurr_attr(ifaceobj
, ifaceobjcurr
,
277 'alias', self
.ipcmd
.link_get_alias
)
279 if addr_method
== 'dhcp':
281 addrs
= self
._get
_iface
_addresses
(ifaceobj
)
282 runningaddrsdict
= self
.ipcmd
.addr_get(ifaceobj
.name
)
284 # Set ifaceobjcurr method and family
285 ifaceobjcurr
.addr_method
= ifaceobj
.addr_method
286 ifaceobjcurr
.addr_family
= ifaceobj
.addr_family
287 if not runningaddrsdict
and not addrs
:
289 runningaddrs
= runningaddrsdict
.keys() if runningaddrsdict
else []
290 if runningaddrs
!= addrs
:
291 runningaddrsset
= set(runningaddrs
) if runningaddrs
else set([])
292 addrsset
= set(addrs
) if addrs
else set([])
293 if (ifaceobj
.flags
& iface
.HAS_SIBLINGS
):
296 # only check for addresses present in running config
297 addrsdiff
= addrsset
.difference(runningaddrsset
)
299 if addr
in addrsdiff
:
300 ifaceobjcurr
.update_config_with_status('address',
303 ifaceobjcurr
.update_config_with_status('address',
306 addrsdiff
= addrsset
.symmetric_difference(runningaddrsset
)
307 for addr
in addrsset
.union(runningaddrsset
):
308 if addr
in addrsdiff
:
309 ifaceobjcurr
.update_config_with_status('address',
312 ifaceobjcurr
.update_config_with_status('address',
315 [ifaceobjcurr
.update_config_with_status('address',
316 addr
, 0) for addr
in addrs
]
317 #XXXX Check broadcast address, scope, etc
320 def _query_running(self
, ifaceobjrunning
):
321 if not self
.ipcmd
.link_exists(ifaceobjrunning
.name
):
322 self
.logger
.debug('iface %s not found' %ifaceobjrunning
.name
)
324 dhclientcmd
= dhclient()
325 if (dhclientcmd
.is_running(ifaceobjrunning
.name
) or
326 dhclientcmd
.is_running6(ifaceobjrunning
.name
)):
327 # If dhcp is configured on the interface, we skip it
329 isloopback
= self
.ipcmd
.link_isloopback(ifaceobjrunning
.name
)
331 default_addrs
= ['127.0.0.1/8', '::1/128']
332 ifaceobjrunning
.addr_family
= 'inet'
333 ifaceobjrunning
.addr_method
= 'loopback'
336 runningaddrsdict
= self
.ipcmd
.addr_get(ifaceobjrunning
.name
)
338 [ifaceobjrunning
.update_config('address', addr
)
339 for addr
, addrattrs
in runningaddrsdict
.items()
340 if addr
not in default_addrs
]
341 mtu
= self
.ipcmd
.link_get_mtu(ifaceobjrunning
.name
)
343 (ifaceobjrunning
.name
== 'lo' and mtu
!= '16436') or
344 (ifaceobjrunning
.name
!= 'lo' and
345 mtu
!= self
.get_mod_subattr('mtu', 'default'))):
346 ifaceobjrunning
.update_config('mtu', mtu
)
347 alias
= self
.ipcmd
.link_get_alias(ifaceobjrunning
.name
)
349 ifaceobjrunning
.update_config('alias', alias
)
351 _run_ops
= {'up' : _up
,
353 'query-checkcurr' : _query_check
,
354 'query-running' : _query_running
}
357 """ returns list of ops supported by this module """
358 return self
._run
_ops
.keys()
360 def _init_command_handlers(self
):
362 self
.ipcmd
= iproute2(**self
.get_flags())
364 def run(self
, ifaceobj
, operation
, query_ifaceobj
=None, **extra_args
):
365 """ run address configuration on the interface object passed as argument
368 **ifaceobj** (object): iface object
370 **operation** (str): any of 'up', 'down', 'query-checkcurr',
373 query_ifaceobj (object): query check ifaceobject. This is only
374 valid when op is 'query-checkcurr'. It is an object same as
375 ifaceobj, but contains running attribute values and its config
376 status. The modules can use it to return queried running state
377 of interfaces. status is success if the running state is same
378 as user required state in ifaceobj. error otherwise.
380 if ifaceobj
.type == ifaceType
.BRIDGE_VLAN
:
382 op_handler
= self
._run
_ops
.get(operation
)
385 self
._init
_command
_handlers
()
386 if operation
== 'query-checkcurr':
387 op_handler(self
, ifaceobj
, query_ifaceobj
)
389 op_handler(self
, ifaceobj
)