]> git.proxmox.com Git - mirror_ifupdown2.git/blob - ifupdown2/addons/dhcp.py
sonarlink: use opposite operator != instead of 'not'
[mirror_ifupdown2.git] / ifupdown2 / addons / dhcp.py
1 #!/usr/bin/env python3
2 #
3 # Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved.
4 # Author: Roopa Prabhu, roopa@cumulusnetworks.com
5 #
6
7 import re
8 import time
9 import socket
10
11 try:
12 from ifupdown2.lib.addon import Addon
13 from ifupdown2.lib.log import LogManager
14
15 import ifupdown2.ifupdown.policymanager as policymanager
16 import ifupdown2.ifupdown.ifupdownflags as ifupdownflags
17
18 from ifupdown2.ifupdown.iface import *
19 from ifupdown2.ifupdown.utils import utils
20
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
26
27 import ifupdown.policymanager as policymanager
28 import ifupdown.ifupdownflags as ifupdownflags
29
30 from ifupdown.iface import *
31 from ifupdown.utils import utils
32
33 from ifupdownaddons.dhclient import dhclient
34 from ifupdownaddons.modulebase import moduleBase
35
36
37 class dhcp(Addon, moduleBase):
38 """ ifupdown2 addon module to configure dhcp on interface """
39
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
44
45 def __init__(self, *args, **kargs):
46 Addon.__init__(self)
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
52 else:
53 self.mgmt_vrf_context = False
54 self.logger.info('mgmt vrf_context = %s' %self.mgmt_vrf_context)
55
56 try:
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"
61 )
62 )
63 except Exception:
64 self.dhclient_retry_on_failure = self.DHCLIENT_RETRY_ON_FAILURE
65
66 if self.dhclient_retry_on_failure < 0:
67 self.dhclient_retry_on_failure = 0
68
69 self.logger.debug("dhclient: dhclient_retry_on_failure set to %s" % self.dhclient_retry_on_failure)
70
71 def syntax_check(self, ifaceobj, ifaceobj_getfunc):
72 return self.is_dhcp_allowed_on(ifaceobj, syntax_check=True)
73
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)
77 return True
78
79 def get_current_ip_configured(self, ifname, family):
80 ips = set()
81 try:
82 a = utils.exec_commandl(["ip", "-o", "addr", "show", ifname]).split("\n")
83
84 for entry in a:
85 family_index = entry.find(family)
86
87 if family_index < 0:
88 continue
89
90 tmp = entry[entry.find(family) + len(family) + 1:]
91 ip = tmp[:tmp.find(" ")]
92
93 if ip:
94 ips.add(ip)
95 except Exception:
96 pass
97 return ips
98
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
102
103 while retry >= 0:
104 handler(ifname, wait=wait, **handler_kwargs)
105 if not wait:
106 # In most case, the client won't have the time to find anything
107 # with the wait=False param.
108 return
109 retry = self.dhclient_check(ifname, family, ip_config_before, retry, handler_kwargs.get("cmd_prefix"))
110
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)
113
114 if diff:
115 self.logger.info(
116 "%s: dhclient: new address%s detected: %s"
117 % (ifname, "es" if len(diff) > 1 else "", ", ".join(diff))
118 )
119 return -1
120 else:
121 if retry > 0:
122 self.logger.error(
123 "%s: dhclient: couldn't detect new ip address, retrying %s more times..."
124 % (ifname, retry)
125 )
126 self.dhclientcmd.stop(ifname)
127 else:
128 self.logger.error("%s: dhclient: timeout failed to detect new ip addresses" % ifname)
129 return -1
130 retry -= 1
131 return retry
132
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)
137
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)
143
144 if ifaceobj.link_privflags & ifaceLinkPrivFlags.KEEP_LINK_DOWN:
145 self.logger.info("%s: skipping dhcp configuration: link-down yes" % ifaceobj.name)
146 return
147
148 try:
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')
155 try:
156 timeout = int(dhcp6_ll_wait)+1
157 except Exception:
158 timeout = 10
159 pass
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')
169
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)
174 else:
175 # First release any existing dhclient processes
176 try:
177 if not ifupdownflags.flags.PERFMODE:
178 self.dhclientcmd.stop(ifaceobj.name)
179 except Exception:
180 pass
181
182 self.dhclient_start_and_check(
183 ifaceobj.name,
184 "inet",
185 self.dhclientcmd.start,
186 wait=wait,
187 cmd_prefix=dhclient_cmd_prefix
188 )
189
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)
194 else:
195 accept_ra = ifaceobj.get_attr_value_first('accept_ra')
196 if 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')
201 if autoconf:
202 # XXX: Validate value
203 self.sysctl_set('net.ipv6.conf.%s' %ifaceobj.name +
204 '.autoconf', autoconf)
205 try:
206 self.dhclientcmd.stop6(ifaceobj.name, duid=dhcp6_duid)
207 except Exception:
208 pass
209 #add delay before starting IPv6 dhclient to
210 #make sure the configured interface/link is up.
211 if timeout > 1:
212 time.sleep(1)
213 while timeout:
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)
217 if r:
218 self.dhclientcmd.start6(ifaceobj.name,
219 wait=wait,
220 cmd_prefix=dhclient_cmd_prefix, duid=dhcp6_duid)
221 return
222 timeout -= 1
223 if timeout:
224 time.sleep(1)
225 except Exception as e:
226 self.logger.error("%s: %s" % (ifaceobj.name, str(e)))
227 ifaceobj.set_status(ifaceStatus.ERROR)
228
229 def _down_stale_dhcp_config(self, ifaceobj, family, dhclientX_running):
230 addr_family = ifaceobj.addr_family
231 try:
232 if not family in ifaceobj.addr_family and dhclientX_running:
233 ifaceobj.addr_family = [family]
234 self._dhcp_down(ifaceobj)
235 except Exception:
236 pass
237 finally:
238 ifaceobj.addr_family = addr_family
239
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)
254
255 def _down(self, ifaceobj):
256 self._dhcp_down(ifaceobj)
257 self.netlink.link_down(ifaceobj.name)
258
259 def _query_check(self, ifaceobj, ifaceobjcurr):
260 status = ifaceStatus.SUCCESS
261 dhcp_running = False
262
263 dhcp_v4 = self.dhclientcmd.is_running(ifaceobjcurr.name)
264 dhcp_v6 = self.dhclientcmd.is_running6(ifaceobjcurr.name)
265
266 if dhcp_v4:
267 dhcp_running = True
268 if 'inet' not in ifaceobj.addr_family and not dhcp_v6:
269 status = ifaceStatus.ERROR
270 ifaceobjcurr.addr_method = 'dhcp'
271 if dhcp_v6:
272 dhcp_running = True
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
277 if not dhcp_running:
278 ifaceobjcurr.addr_family = []
279 status = ifaceStatus.ERROR
280 ifaceobjcurr.status = status
281
282 def _query_running(self, ifaceobjrunning):
283 if not self.cache.link_exists(ifaceobjrunning.name):
284 return
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'
291
292 _run_ops = {'up' : _up,
293 'down' : _down,
294 'pre-down' : _down,
295 'query-checkcurr' : _query_check,
296 'query-running' : _query_running }
297
298 def get_ops(self):
299 """ returns list of ops supported by this module """
300 return list(self._run_ops.keys())
301
302 def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args):
303 """ run dhcp configuration on the interface object passed as argument
304
305 Args:
306 **ifaceobj** (object): iface object
307
308 **operation** (str): any of 'up', 'down', 'query-checkcurr',
309 'query-running'
310
311 Kwargs:
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.
318 """
319 op_handler = self._run_ops.get(operation)
320 if not op_handler:
321 return
322 try:
323 if (operation != 'query-running' and
324 (ifaceobj.addr_method != 'dhcp' and
325 ifaceobj.addr_method != 'dhcp6')):
326 return
327 except Exception:
328 return
329 if not self.is_dhcp_allowed_on(ifaceobj, syntax_check=False):
330 return
331
332 log_manager = LogManager.get_instance()
333
334 syslog_log_level = logging.INFO
335 disable_syslog_on_exit = None
336
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
344 else:
345 # enabling syslog
346 log_manager.enable_syslog()
347 # syslog will be disabled once we are done
348 disable_syslog_on_exit = True
349
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)
353
354 self.logger.info("%s: enabling syslog for dhcp configuration" % ifaceobj.name)
355
356 try:
357 if operation == 'query-checkcurr':
358 op_handler(self, ifaceobj, query_ifaceobj)
359 else:
360 op_handler(self, ifaceobj)
361 finally:
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)