3 # Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved.
4 # Author: Roopa Prabhu, roopa@cumulusnetworks.com
12 from ifupdown2
.lib
.addon
import Addon
13 from ifupdown2
.lib
.log
import LogManager
15 import ifupdown2
.ifupdown
.policymanager
as policymanager
16 import ifupdown2
.ifupdown
.ifupdownflags
as ifupdownflags
18 from ifupdown2
.ifupdown
.iface
import *
19 from ifupdown2
.ifupdown
.utils
import utils
21 from ifupdown2
.ifupdownaddons
.dhclient
import dhclient
22 from ifupdown2
.ifupdownaddons
.modulebase
import moduleBase
23 except (ImportError, ModuleNotFoundError
):
24 from lib
.addon
import Addon
25 from lib
.log
import LogManager
27 import ifupdown
.policymanager
as policymanager
28 import ifupdown
.ifupdownflags
as ifupdownflags
30 from ifupdown
.iface
import *
31 from ifupdown
.utils
import utils
33 from ifupdownaddons
.dhclient
import dhclient
34 from ifupdownaddons
.modulebase
import moduleBase
37 class dhcp(Addon
, moduleBase
):
38 """ ifupdown2 addon module to configure dhcp on interface """
40 # by default we won't perform any dhcp retry
41 # this can be changed by setting the module global
42 # policy: dhclient_retry_on_failure
43 DHCLIENT_RETRY_ON_FAILURE
= 0
45 def __init__(self
, *args
, **kargs
):
47 moduleBase
.__init
__(self
, *args
, **kargs
)
48 self
.dhclientcmd
= dhclient(**kargs
)
49 vrf_id
= self
._get
_vrf
_context
()
50 if vrf_id
and vrf_id
== 'mgmt':
51 self
.mgmt_vrf_context
= True
53 self
.mgmt_vrf_context
= False
54 self
.logger
.info('mgmt vrf_context = %s' %self
.mgmt_vrf_context
)
57 self
.dhclient_retry_on_failure
= int(
58 policymanager
.policymanager_api
.get_module_globals(
59 module_name
=self
.__class
__.__name
__,
60 attr
="dhclient_retry_on_failure"
64 self
.dhclient_retry_on_failure
= self
.DHCLIENT_RETRY_ON_FAILURE
66 if self
.dhclient_retry_on_failure
< 0:
67 self
.dhclient_retry_on_failure
= 0
69 self
.logger
.debug("dhclient: dhclient_retry_on_failure set to %s" % self
.dhclient_retry_on_failure
)
71 def syntax_check(self
, ifaceobj
, ifaceobj_getfunc
):
72 return self
.is_dhcp_allowed_on(ifaceobj
, syntax_check
=True)
74 def is_dhcp_allowed_on(self
, ifaceobj
, syntax_check
):
75 if ifaceobj
.addr_method
and 'dhcp' in ifaceobj
.addr_method
:
76 return utils
.is_addr_ip_allowed_on(ifaceobj
, syntax_check
=True)
79 def get_current_ip_configured(self
, ifname
, family
):
82 a
= utils
.exec_commandl(["ip", "-o", "addr", "show", ifname
]).split("\n")
85 family_index
= entry
.find(family
)
90 tmp
= entry
[entry
.find(family
) + len(family
) + 1:]
91 ip
= tmp
[:tmp
.find(" ")]
99 def dhclient_start_and_check(self
, ifname
, family
, handler
, wait
=True, **handler_kwargs
):
100 ip_config_before
= self
.get_current_ip_configured(ifname
, family
)
101 retry
= self
.dhclient_retry_on_failure
104 handler(ifname
, wait
=wait
, **handler_kwargs
)
106 # In most case, the client won't have the time to find anything
107 # with the wait=False param.
109 retry
= self
.dhclient_check(ifname
, family
, ip_config_before
, retry
, handler_kwargs
.get("cmd_prefix"))
111 def dhclient_check(self
, ifname
, family
, ip_config_before
, retry
, dhclient_cmd_prefix
):
112 diff
= self
.get_current_ip_configured(ifname
, family
).difference(ip_config_before
)
116 "%s: dhclient: new address%s detected: %s"
117 % (ifname
, "es" if len(diff
) > 1 else "", ", ".join(diff
))
123 "%s: dhclient: couldn't detect new ip address, retrying %s more times..."
126 self
.dhclientcmd
.stop(ifname
)
128 self
.logger
.error("%s: dhclient: timeout failed to detect new ip addresses" % ifname
)
133 def _up(self
, ifaceobj
):
134 # if dhclient is already running do not stop and start it
135 dhclient4_running
= self
.dhclientcmd
.is_running(ifaceobj
.name
)
136 dhclient6_running
= self
.dhclientcmd
.is_running6(ifaceobj
.name
)
138 # today if we have an interface with both inet and inet6, if we
139 # remove the inet or inet6 or both then execute ifreload, we need
140 # to release/kill the appropriate dhclient(4/6) if they are running
141 self
._down
_stale
_dhcp
_config
(ifaceobj
, 'inet', dhclient4_running
)
142 self
._down
_stale
_dhcp
_config
(ifaceobj
, 'inet6', dhclient6_running
)
144 if ifaceobj
.link_privflags
& ifaceLinkPrivFlags
.KEEP_LINK_DOWN
:
145 self
.logger
.info("%s: skipping dhcp configuration: link-down yes" % ifaceobj
.name
)
149 dhclient_cmd_prefix
= None
150 dhcp_wait
= policymanager
.policymanager_api
.get_attr_default(
151 module_name
=self
.__class
__.__name
__, attr
='dhcp-wait')
152 wait
= str(dhcp_wait
).lower() != "no"
153 dhcp6_ll_wait
= policymanager
.policymanager_api
.get_iface_default(module_name
=self
.__class
__.__name
__, \
154 ifname
=ifaceobj
.name
, attr
='dhcp6-ll-wait')
156 timeout
= int(dhcp6_ll_wait
)+1
160 dhcp6_duid
= policymanager
.policymanager_api
.get_iface_default(module_name
=self
.__class
__.__name
__, \
161 ifname
=ifaceobj
.name
, attr
='dhcp6-duid')
162 vrf
= ifaceobj
.get_attr_value_first('vrf')
163 if (vrf
and self
.vrf_exec_cmd_prefix
and
164 self
.cache
.link_exists(vrf
)):
165 dhclient_cmd_prefix
= '%s %s' %(self
.vrf_exec_cmd_prefix
, vrf
)
166 elif self
.mgmt_vrf_context
:
167 dhclient_cmd_prefix
= '%s %s' %(self
.vrf_exec_cmd_prefix
, 'default')
168 self
.logger
.info('detected mgmt vrf context starting dhclient in default vrf context')
170 if 'inet' in ifaceobj
.addr_family
:
171 if dhclient4_running
:
172 self
.logger
.info('dhclient4 already running on %s. '
173 'Not restarting.' % ifaceobj
.name
)
175 # First release any existing dhclient processes
177 if not ifupdownflags
.flags
.PERFMODE
:
178 self
.dhclientcmd
.stop(ifaceobj
.name
)
182 self
.dhclient_start_and_check(
185 self
.dhclientcmd
.start
,
187 cmd_prefix
=dhclient_cmd_prefix
190 if 'inet6' in ifaceobj
.addr_family
:
191 if dhclient6_running
:
192 self
.logger
.info('dhclient6 already running on %s. '
193 'Not restarting.' % ifaceobj
.name
)
195 accept_ra
= ifaceobj
.get_attr_value_first('accept_ra')
197 # XXX: Validate value
198 self
.sysctl_set('net.ipv6.conf.%s' %ifaceobj
.name
+
199 '.accept_ra', accept_ra
)
200 autoconf
= ifaceobj
.get_attr_value_first('autoconf')
202 # XXX: Validate value
203 self
.sysctl_set('net.ipv6.conf.%s' %ifaceobj
.name
+
204 '.autoconf', autoconf
)
206 self
.dhclientcmd
.stop6(ifaceobj
.name
, duid
=dhcp6_duid
)
209 #add delay before starting IPv6 dhclient to
210 #make sure the configured interface/link is up.
214 addr_output
= utils
.exec_command('%s -6 addr show %s'
215 %(utils
.ip_cmd
, ifaceobj
.name
))
216 r
= re
.search('inet6 .* scope link', addr_output
)
218 self
.dhclientcmd
.start6(ifaceobj
.name
,
220 cmd_prefix
=dhclient_cmd_prefix
, duid
=dhcp6_duid
)
225 except Exception as e
:
226 self
.logger
.error("%s: %s" % (ifaceobj
.name
, str(e
)))
227 ifaceobj
.set_status(ifaceStatus
.ERROR
)
229 def _down_stale_dhcp_config(self
, ifaceobj
, family
, dhclientX_running
):
230 addr_family
= ifaceobj
.addr_family
232 if not family
in ifaceobj
.addr_family
and dhclientX_running
:
233 ifaceobj
.addr_family
= [family
]
234 self
._dhcp
_down
(ifaceobj
)
238 ifaceobj
.addr_family
= addr_family
240 def _dhcp_down(self
, ifaceobj
):
241 dhclient_cmd_prefix
= None
242 vrf
= ifaceobj
.get_attr_value_first('vrf')
243 if (vrf
and self
.vrf_exec_cmd_prefix
and
244 self
.cache
.link_exists(vrf
)):
245 dhclient_cmd_prefix
= '%s %s' %(self
.vrf_exec_cmd_prefix
, vrf
)
246 dhcp6_duid
= policymanager
.policymanager_api
.get_iface_default(module_name
=self
.__class
__.__name
__, \
247 ifname
=ifaceobj
.name
, attr
='dhcp6-duid')
248 if 'inet6' in ifaceobj
.addr_family
:
249 self
.dhclientcmd
.release6(ifaceobj
.name
, dhclient_cmd_prefix
, duid
=dhcp6_duid
)
250 self
.cache
.force_address_flush_family(ifaceobj
.name
, socket
.AF_INET6
)
251 if 'inet' in ifaceobj
.addr_family
:
252 self
.dhclientcmd
.release(ifaceobj
.name
, dhclient_cmd_prefix
)
253 self
.cache
.force_address_flush_family(ifaceobj
.name
, socket
.AF_INET
)
255 def _down(self
, ifaceobj
):
256 self
._dhcp
_down
(ifaceobj
)
257 self
.netlink
.link_down(ifaceobj
.name
)
259 def _query_check(self
, ifaceobj
, ifaceobjcurr
):
260 status
= ifaceStatus
.SUCCESS
263 dhcp_v4
= self
.dhclientcmd
.is_running(ifaceobjcurr
.name
)
264 dhcp_v6
= self
.dhclientcmd
.is_running6(ifaceobjcurr
.name
)
268 if 'inet' not in ifaceobj
.addr_family
and not dhcp_v6
:
269 status
= ifaceStatus
.ERROR
270 ifaceobjcurr
.addr_method
= 'dhcp'
273 if 'inet6' not in ifaceobj
.addr_family
and not dhcp_v4
:
274 status
= ifaceStatus
.ERROR
275 ifaceobjcurr
.addr_method
= 'dhcp'
276 ifaceobjcurr
.addr_family
= ifaceobj
.addr_family
278 ifaceobjcurr
.addr_family
= []
279 status
= ifaceStatus
.ERROR
280 ifaceobjcurr
.status
= status
282 def _query_running(self
, ifaceobjrunning
):
283 if not self
.cache
.link_exists(ifaceobjrunning
.name
):
285 if self
.dhclientcmd
.is_running(ifaceobjrunning
.name
):
286 ifaceobjrunning
.addr_family
.append('inet')
287 ifaceobjrunning
.addr_method
= 'dhcp'
288 if self
.dhclientcmd
.is_running6(ifaceobjrunning
.name
):
289 ifaceobjrunning
.addr_family
.append('inet6')
290 ifaceobjrunning
.addr_method
= 'dhcp6'
292 _run_ops
= {'up' : _up
,
295 'query-checkcurr' : _query_check
,
296 'query-running' : _query_running
}
299 """ returns list of ops supported by this module """
300 return list(self
._run
_ops
.keys())
302 def run(self
, ifaceobj
, operation
, query_ifaceobj
=None, **extra_args
):
303 """ run dhcp configuration on the interface object passed as argument
306 **ifaceobj** (object): iface object
308 **operation** (str): any of 'up', 'down', 'query-checkcurr',
312 **query_ifaceobj** (object): query check ifaceobject. This is only
313 valid when op is 'query-checkcurr'. It is an object same as
314 ifaceobj, but contains running attribute values and its config
315 status. The modules can use it to return queried running state
316 of interfaces. status is success if the running state is same
317 as user required state in ifaceobj. error otherwise.
319 op_handler
= self
._run
_ops
.get(operation
)
323 if (operation
!= 'query-running' and
324 (ifaceobj
.addr_method
!= 'dhcp' and
325 ifaceobj
.addr_method
!= 'dhcp6')):
329 if not self
.is_dhcp_allowed_on(ifaceobj
, syntax_check
=False):
332 log_manager
= LogManager
.get_instance()
334 syslog_log_level
= logging
.INFO
335 disable_syslog_on_exit
= None
337 if operation
in ["up", "down"]:
338 # if syslog is already enabled we shouldn't disable it
339 if log_manager
.is_syslog_enabled():
340 # save current syslog level
341 syslog_log_level
= log_manager
.get_syslog_log_level()
342 # prevent syslog from being disabled on exit
343 disable_syslog_on_exit
= False
346 log_manager
.enable_syslog()
347 # syslog will be disabled once we are done
348 disable_syslog_on_exit
= True
350 # update the current syslog handler log level if higher than INFO
351 if syslog_log_level
>= logging
.INFO
:
352 log_manager
.set_level_syslog(logging
.INFO
)
354 self
.logger
.info("%s: enabling syslog for dhcp configuration" % ifaceobj
.name
)
357 if operation
== 'query-checkcurr':
358 op_handler(self
, ifaceobj
, query_ifaceobj
)
360 op_handler(self
, ifaceobj
)
362 # disable syslog handler or re-set the proper log-level
363 if disable_syslog_on_exit
is True:
364 log_manager
.get_instance().disable_syslog()
365 elif disable_syslog_on_exit
is False:
366 log_manager
.set_level_syslog(syslog_log_level
)