]> git.proxmox.com Git - mirror_ifupdown2.git/blob - ifupdown2/addons/dhcp.py
addons: dhcp: move policy log info to debug
[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, **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, **handler_kwargs)
105 retry = self.dhclient_check(ifname, family, ip_config_before, retry, handler_kwargs.get("cmd_prefix"))
106
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)
109
110 if diff:
111 self.logger.info(
112 "%s: dhclient: new address%s detected: %s"
113 % (ifname, "es" if len(diff) > 1 else "", ", ".join(diff))
114 )
115 return -1
116 else:
117 if retry > 0:
118 self.logger.error(
119 "%s: dhclient: couldn't detect new ip address, retrying %s more times..."
120 % (ifname, retry)
121 )
122 self.dhclientcmd.stop(ifname)
123 else:
124 self.logger.error("%s: dhclient: timeout failed to detect new ip addresses" % ifname)
125 return -1
126 retry -= 1
127 return retry
128
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)
133
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)
139
140 if ifaceobj.link_privflags & ifaceLinkPrivFlags.KEEP_LINK_DOWN:
141 self.logger.info("%s: skipping dhcp configuration: link-down yes" % ifaceobj.name)
142 return
143
144 try:
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')
151 try:
152 timeout = int(dhcp6_ll_wait)+1
153 except Exception:
154 timeout = 10
155 pass
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')
165
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)
170 else:
171 # First release any existing dhclient processes
172 try:
173 if not ifupdownflags.flags.PERFMODE:
174 self.dhclientcmd.stop(ifaceobj.name)
175 except Exception:
176 pass
177
178 self.dhclient_start_and_check(
179 ifaceobj.name,
180 "inet",
181 self.dhclientcmd.start,
182 wait=wait,
183 cmd_prefix=dhclient_cmd_prefix
184 )
185
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)
190 else:
191 accept_ra = ifaceobj.get_attr_value_first('accept_ra')
192 if 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')
197 if autoconf:
198 # XXX: Validate value
199 self.sysctl_set('net.ipv6.conf.%s' %ifaceobj.name +
200 '.autoconf', autoconf)
201 try:
202 self.dhclientcmd.stop6(ifaceobj.name, duid=dhcp6_duid)
203 except Exception:
204 pass
205 #add delay before starting IPv6 dhclient to
206 #make sure the configured interface/link is up.
207 if timeout > 1:
208 time.sleep(1)
209 while timeout:
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)
213 if r:
214 self.dhclientcmd.start6(ifaceobj.name,
215 wait=wait,
216 cmd_prefix=dhclient_cmd_prefix, duid=dhcp6_duid)
217 return
218 timeout -= 1
219 if timeout:
220 time.sleep(1)
221 except Exception as e:
222 self.logger.error("%s: %s" % (ifaceobj.name, str(e)))
223 ifaceobj.set_status(ifaceStatus.ERROR)
224
225 def _down_stale_dhcp_config(self, ifaceobj, family, dhclientX_running):
226 addr_family = ifaceobj.addr_family
227 try:
228 if not family in ifaceobj.addr_family and dhclientX_running:
229 ifaceobj.addr_family = [family]
230 self._dhcp_down(ifaceobj)
231 except Exception:
232 pass
233 finally:
234 ifaceobj.addr_family = addr_family
235
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)
250
251 def _down(self, ifaceobj):
252 self._dhcp_down(ifaceobj)
253 self.netlink.link_down(ifaceobj.name)
254
255 def _query_check(self, ifaceobj, ifaceobjcurr):
256 status = ifaceStatus.SUCCESS
257 dhcp_running = False
258
259 dhcp_v4 = self.dhclientcmd.is_running(ifaceobjcurr.name)
260 dhcp_v6 = self.dhclientcmd.is_running6(ifaceobjcurr.name)
261
262 if dhcp_v4:
263 dhcp_running = True
264 if 'inet' not in ifaceobj.addr_family and not dhcp_v6:
265 status = ifaceStatus.ERROR
266 ifaceobjcurr.addr_method = 'dhcp'
267 if dhcp_v6:
268 dhcp_running = True
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
273 if not dhcp_running:
274 ifaceobjcurr.addr_family = []
275 status = ifaceStatus.ERROR
276 ifaceobjcurr.status = status
277
278 def _query_running(self, ifaceobjrunning):
279 if not self.cache.link_exists(ifaceobjrunning.name):
280 return
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'
287
288 _run_ops = {'up' : _up,
289 'down' : _down,
290 'pre-down' : _down,
291 'query-checkcurr' : _query_check,
292 'query-running' : _query_running }
293
294 def get_ops(self):
295 """ returns list of ops supported by this module """
296 return list(self._run_ops.keys())
297
298 def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args):
299 """ run dhcp configuration on the interface object passed as argument
300
301 Args:
302 **ifaceobj** (object): iface object
303
304 **operation** (str): any of 'up', 'down', 'query-checkcurr',
305 'query-running'
306
307 Kwargs:
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.
314 """
315 op_handler = self._run_ops.get(operation)
316 if not op_handler:
317 return
318 try:
319 if (operation != 'query-running' and
320 (ifaceobj.addr_method != 'dhcp' and
321 ifaceobj.addr_method != 'dhcp6')):
322 return
323 except Exception:
324 return
325 if not self.is_dhcp_allowed_on(ifaceobj, syntax_check=False):
326 return
327
328 log_manager = LogManager.get_instance()
329
330 syslog_log_level = logging.INFO
331 disable_syslog_on_exit = None
332
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
340 else:
341 # enabling syslog
342 log_manager.enable_syslog()
343 # syslog will be disabled once we are done
344 disable_syslog_on_exit = True
345
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)
349
350 self.logger.info("%s: enabling syslog for dhcp configuration" % ifaceobj.name)
351
352 try:
353 if operation == 'query-checkcurr':
354 op_handler(self, ifaceobj, query_ifaceobj)
355 else:
356 op_handler(self, ifaceobj)
357 finally:
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)