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
, **handler_kwargs
):
100 ip_config_before
= self
.get_current_ip_configured(ifname
, family
)
101 retry
= self
.dhclient_retry_on_failure
104 handler(ifname
, **handler_kwargs
)
105 retry
= self
.dhclient_check(ifname
, family
, ip_config_before
, retry
, handler_kwargs
.get("cmd_prefix"))
107 def dhclient_check(self
, ifname
, family
, ip_config_before
, retry
, dhclient_cmd_prefix
):
108 diff
= self
.get_current_ip_configured(ifname
, family
).difference(ip_config_before
)
112 "%s: dhclient: new address%s detected: %s"
113 % (ifname
, "es" if len(diff
) > 1 else "", ", ".join(diff
))
119 "%s: dhclient: couldn't detect new ip address, retrying %s more times..."
122 self
.dhclientcmd
.stop(ifname
)
124 self
.logger
.error("%s: dhclient: timeout failed to detect new ip addresses" % ifname
)
129 def _up(self
, ifaceobj
):
130 # if dhclient is already running do not stop and start it
131 dhclient4_running
= self
.dhclientcmd
.is_running(ifaceobj
.name
)
132 dhclient6_running
= self
.dhclientcmd
.is_running6(ifaceobj
.name
)
134 # today if we have an interface with both inet and inet6, if we
135 # remove the inet or inet6 or both then execute ifreload, we need
136 # to release/kill the appropriate dhclient(4/6) if they are running
137 self
._down
_stale
_dhcp
_config
(ifaceobj
, 'inet', dhclient4_running
)
138 self
._down
_stale
_dhcp
_config
(ifaceobj
, 'inet6', dhclient6_running
)
140 if ifaceobj
.link_privflags
& ifaceLinkPrivFlags
.KEEP_LINK_DOWN
:
141 self
.logger
.info("%s: skipping dhcp configuration: link-down yes" % ifaceobj
.name
)
145 dhclient_cmd_prefix
= None
146 dhcp_wait
= policymanager
.policymanager_api
.get_attr_default(
147 module_name
=self
.__class
__.__name
__, attr
='dhcp-wait')
148 wait
= not str(dhcp_wait
).lower() == "no"
149 dhcp6_ll_wait
= policymanager
.policymanager_api
.get_iface_default(module_name
=self
.__class
__.__name
__, \
150 ifname
=ifaceobj
.name
, attr
='dhcp6-ll-wait')
152 timeout
= int(dhcp6_ll_wait
)+1
156 dhcp6_duid
= policymanager
.policymanager_api
.get_iface_default(module_name
=self
.__class
__.__name
__, \
157 ifname
=ifaceobj
.name
, attr
='dhcp6-duid')
158 vrf
= ifaceobj
.get_attr_value_first('vrf')
159 if (vrf
and self
.vrf_exec_cmd_prefix
and
160 self
.cache
.link_exists(vrf
)):
161 dhclient_cmd_prefix
= '%s %s' %(self
.vrf_exec_cmd_prefix
, vrf
)
162 elif self
.mgmt_vrf_context
:
163 dhclient_cmd_prefix
= '%s %s' %(self
.vrf_exec_cmd_prefix
, 'default')
164 self
.logger
.info('detected mgmt vrf context starting dhclient in default vrf context')
166 if 'inet' in ifaceobj
.addr_family
:
167 if dhclient4_running
:
168 self
.logger
.info('dhclient4 already running on %s. '
169 'Not restarting.' % ifaceobj
.name
)
171 # First release any existing dhclient processes
173 if not ifupdownflags
.flags
.PERFMODE
:
174 self
.dhclientcmd
.stop(ifaceobj
.name
)
178 self
.dhclient_start_and_check(
181 self
.dhclientcmd
.start
,
183 cmd_prefix
=dhclient_cmd_prefix
186 if 'inet6' in ifaceobj
.addr_family
:
187 if dhclient6_running
:
188 self
.logger
.info('dhclient6 already running on %s. '
189 'Not restarting.' % ifaceobj
.name
)
191 accept_ra
= ifaceobj
.get_attr_value_first('accept_ra')
193 # XXX: Validate value
194 self
.sysctl_set('net.ipv6.conf.%s' %ifaceobj
.name
+
195 '.accept_ra', accept_ra
)
196 autoconf
= ifaceobj
.get_attr_value_first('autoconf')
198 # XXX: Validate value
199 self
.sysctl_set('net.ipv6.conf.%s' %ifaceobj
.name
+
200 '.autoconf', autoconf
)
202 self
.dhclientcmd
.stop6(ifaceobj
.name
, duid
=dhcp6_duid
)
205 #add delay before starting IPv6 dhclient to
206 #make sure the configured interface/link is up.
210 addr_output
= utils
.exec_command('%s -6 addr show %s'
211 %(utils
.ip_cmd
, ifaceobj
.name
))
212 r
= re
.search('inet6 .* scope link', addr_output
)
214 self
.dhclientcmd
.start6(ifaceobj
.name
,
216 cmd_prefix
=dhclient_cmd_prefix
, duid
=dhcp6_duid
)
221 except Exception as e
:
222 self
.logger
.error("%s: %s" % (ifaceobj
.name
, str(e
)))
223 ifaceobj
.set_status(ifaceStatus
.ERROR
)
225 def _down_stale_dhcp_config(self
, ifaceobj
, family
, dhclientX_running
):
226 addr_family
= ifaceobj
.addr_family
228 if not family
in ifaceobj
.addr_family
and dhclientX_running
:
229 ifaceobj
.addr_family
= [family
]
230 self
._dhcp
_down
(ifaceobj
)
234 ifaceobj
.addr_family
= addr_family
236 def _dhcp_down(self
, ifaceobj
):
237 dhclient_cmd_prefix
= None
238 vrf
= ifaceobj
.get_attr_value_first('vrf')
239 if (vrf
and self
.vrf_exec_cmd_prefix
and
240 self
.cache
.link_exists(vrf
)):
241 dhclient_cmd_prefix
= '%s %s' %(self
.vrf_exec_cmd_prefix
, vrf
)
242 dhcp6_duid
= policymanager
.policymanager_api
.get_iface_default(module_name
=self
.__class
__.__name
__, \
243 ifname
=ifaceobj
.name
, attr
='dhcp6-duid')
244 if 'inet6' in ifaceobj
.addr_family
:
245 self
.dhclientcmd
.release6(ifaceobj
.name
, dhclient_cmd_prefix
, duid
=dhcp6_duid
)
246 self
.cache
.force_address_flush_family(ifaceobj
.name
, socket
.AF_INET6
)
247 if 'inet' in ifaceobj
.addr_family
:
248 self
.dhclientcmd
.release(ifaceobj
.name
, dhclient_cmd_prefix
)
249 self
.cache
.force_address_flush_family(ifaceobj
.name
, socket
.AF_INET
)
251 def _down(self
, ifaceobj
):
252 self
._dhcp
_down
(ifaceobj
)
253 self
.netlink
.link_down(ifaceobj
.name
)
255 def _query_check(self
, ifaceobj
, ifaceobjcurr
):
256 status
= ifaceStatus
.SUCCESS
259 dhcp_v4
= self
.dhclientcmd
.is_running(ifaceobjcurr
.name
)
260 dhcp_v6
= self
.dhclientcmd
.is_running6(ifaceobjcurr
.name
)
264 if 'inet' not in ifaceobj
.addr_family
and not dhcp_v6
:
265 status
= ifaceStatus
.ERROR
266 ifaceobjcurr
.addr_method
= 'dhcp'
269 if 'inet6' not in ifaceobj
.addr_family
and not dhcp_v4
:
270 status
= ifaceStatus
.ERROR
271 ifaceobjcurr
.addr_method
= 'dhcp'
272 ifaceobjcurr
.addr_family
= ifaceobj
.addr_family
274 ifaceobjcurr
.addr_family
= []
275 status
= ifaceStatus
.ERROR
276 ifaceobjcurr
.status
= status
278 def _query_running(self
, ifaceobjrunning
):
279 if not self
.cache
.link_exists(ifaceobjrunning
.name
):
281 if self
.dhclientcmd
.is_running(ifaceobjrunning
.name
):
282 ifaceobjrunning
.addr_family
.append('inet')
283 ifaceobjrunning
.addr_method
= 'dhcp'
284 if self
.dhclientcmd
.is_running6(ifaceobjrunning
.name
):
285 ifaceobjrunning
.addr_family
.append('inet6')
286 ifaceobjrunning
.addr_method
= 'dhcp6'
288 _run_ops
= {'up' : _up
,
291 'query-checkcurr' : _query_check
,
292 'query-running' : _query_running
}
295 """ returns list of ops supported by this module """
296 return list(self
._run
_ops
.keys())
298 def run(self
, ifaceobj
, operation
, query_ifaceobj
=None, **extra_args
):
299 """ run dhcp configuration on the interface object passed as argument
302 **ifaceobj** (object): iface object
304 **operation** (str): any of 'up', 'down', 'query-checkcurr',
308 **query_ifaceobj** (object): query check ifaceobject. This is only
309 valid when op is 'query-checkcurr'. It is an object same as
310 ifaceobj, but contains running attribute values and its config
311 status. The modules can use it to return queried running state
312 of interfaces. status is success if the running state is same
313 as user required state in ifaceobj. error otherwise.
315 op_handler
= self
._run
_ops
.get(operation
)
319 if (operation
!= 'query-running' and
320 (ifaceobj
.addr_method
!= 'dhcp' and
321 ifaceobj
.addr_method
!= 'dhcp6')):
325 if not self
.is_dhcp_allowed_on(ifaceobj
, syntax_check
=False):
328 log_manager
= LogManager
.get_instance()
330 syslog_log_level
= logging
.INFO
331 disable_syslog_on_exit
= None
333 if operation
in ["up", "down"]:
334 # if syslog is already enabled we shouldn't disable it
335 if log_manager
.is_syslog_enabled():
336 # save current syslog level
337 syslog_log_level
= log_manager
.get_syslog_log_level()
338 # prevent syslog from being disabled on exit
339 disable_syslog_on_exit
= False
342 log_manager
.enable_syslog()
343 # syslog will be disabled once we are done
344 disable_syslog_on_exit
= True
346 # update the current syslog handler log level if higher than INFO
347 if syslog_log_level
>= logging
.INFO
:
348 log_manager
.set_level_syslog(logging
.INFO
)
350 self
.logger
.info("%s: enabling syslog for dhcp configuration" % ifaceobj
.name
)
353 if operation
== 'query-checkcurr':
354 op_handler(self
, ifaceobj
, query_ifaceobj
)
356 op_handler(self
, ifaceobj
)
358 # disable syslog handler or re-set the proper log-level
359 if disable_syslog_on_exit
is True:
360 log_manager
.get_instance().disable_syslog()
361 elif disable_syslog_on_exit
is False:
362 log_manager
.set_level_syslog(syslog_log_level
)