1 /*
2  * External health-checks functions.
3  *
4  * Copyright 2000-2009,2020 Willy Tarreau <w@1wt.eu>
5  * Copyright 2014 Horms Solutions Ltd, Simon Horman <horms@verge.net.au>
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version
10  * 2 of the License, or (at your option) any later version.
11  *
12  */
13 
14 #include <sys/resource.h>
15 #include <sys/socket.h>
16 #include <sys/types.h>
17 #include <sys/wait.h>
18 #include <assert.h>
19 #include <ctype.h>
20 #include <errno.h>
21 #include <fcntl.h>
22 #include <signal.h>
23 #include <stdarg.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <time.h>
28 #include <unistd.h>
29 
30 #include <haproxy/api.h>
31 #include <haproxy/cfgparse.h>
32 #include <haproxy/check.h>
33 #include <haproxy/errors.h>
34 #include <haproxy/global.h>
35 #include <haproxy/list.h>
36 #include <haproxy/proxy.h>
37 #include <haproxy/server.h>
38 #include <haproxy/signal.h>
39 #include <haproxy/task.h>
40 #include <haproxy/thread.h>
41 #include <haproxy/time.h>
42 #include <haproxy/tools.h>
43 
44 
45 static struct list pid_list = LIST_HEAD_INIT(pid_list);
46 static struct pool_head *pool_head_pid_list;
47 __decl_spinlock(pid_list_lock);
48 
49 struct extcheck_env {
50 	char *name;	/* environment variable name */
51 	int vmaxlen;	/* value maximum length, used to determine the required memory allocation */
52 };
53 
54 /* environment variables memory requirement for different types of data */
55 #define EXTCHK_SIZE_EVAL_INIT 0                  /* size determined during the init phase,
56                                                   * such environment variables are not updatable. */
57 #define EXTCHK_SIZE_ULONG     20                 /* max string length for an unsigned long value */
58 #define EXTCHK_SIZE_UINT      11                 /* max string length for an unsigned int value */
59 #define EXTCHK_SIZE_ADDR      INET6_ADDRSTRLEN+1 /* max string length for an address */
60 
61 /* external checks environment variables */
62 enum {
63 	EXTCHK_PATH = 0,
64 
65 	/* Proxy specific environment variables */
66 	EXTCHK_HAPROXY_PROXY_NAME,	/* the backend name */
67 	EXTCHK_HAPROXY_PROXY_ID,	/* the backend id */
68 	EXTCHK_HAPROXY_PROXY_ADDR,	/* the first bind address if available (or empty) */
69 	EXTCHK_HAPROXY_PROXY_PORT,	/* the first bind port if available (or empty) */
70 
71 	/* Server specific environment variables */
72 	EXTCHK_HAPROXY_SERVER_NAME,	/* the server name */
73 	EXTCHK_HAPROXY_SERVER_ID,	/* the server id */
74 	EXTCHK_HAPROXY_SERVER_ADDR,	/* the server address */
75 	EXTCHK_HAPROXY_SERVER_PORT,	/* the server port if available (or empty) */
76 	EXTCHK_HAPROXY_SERVER_MAXCONN,	/* the server max connections */
77 	EXTCHK_HAPROXY_SERVER_CURCONN,	/* the current number of connections on the server */
78 
79 	EXTCHK_SIZE
80 };
81 
82 const struct extcheck_env extcheck_envs[EXTCHK_SIZE] = {
83 	[EXTCHK_PATH]                   = { "PATH",                   EXTCHK_SIZE_EVAL_INIT },
84 	[EXTCHK_HAPROXY_PROXY_NAME]     = { "HAPROXY_PROXY_NAME",     EXTCHK_SIZE_EVAL_INIT },
85 	[EXTCHK_HAPROXY_PROXY_ID]       = { "HAPROXY_PROXY_ID",       EXTCHK_SIZE_EVAL_INIT },
86 	[EXTCHK_HAPROXY_PROXY_ADDR]     = { "HAPROXY_PROXY_ADDR",     EXTCHK_SIZE_EVAL_INIT },
87 	[EXTCHK_HAPROXY_PROXY_PORT]     = { "HAPROXY_PROXY_PORT",     EXTCHK_SIZE_EVAL_INIT },
88 	[EXTCHK_HAPROXY_SERVER_NAME]    = { "HAPROXY_SERVER_NAME",    EXTCHK_SIZE_EVAL_INIT },
89 	[EXTCHK_HAPROXY_SERVER_ID]      = { "HAPROXY_SERVER_ID",      EXTCHK_SIZE_EVAL_INIT },
90 	[EXTCHK_HAPROXY_SERVER_ADDR]    = { "HAPROXY_SERVER_ADDR",    EXTCHK_SIZE_ADDR },
91 	[EXTCHK_HAPROXY_SERVER_PORT]    = { "HAPROXY_SERVER_PORT",    EXTCHK_SIZE_UINT },
92 	[EXTCHK_HAPROXY_SERVER_MAXCONN] = { "HAPROXY_SERVER_MAXCONN", EXTCHK_SIZE_EVAL_INIT },
93 	[EXTCHK_HAPROXY_SERVER_CURCONN] = { "HAPROXY_SERVER_CURCONN", EXTCHK_SIZE_ULONG },
94 };
95 
block_sigchld(void)96 void block_sigchld(void)
97 {
98 	sigset_t set;
99 	sigemptyset(&set);
100 	sigaddset(&set, SIGCHLD);
101 	assert(ha_sigmask(SIG_BLOCK, &set, NULL) == 0);
102 }
103 
unblock_sigchld(void)104 void unblock_sigchld(void)
105 {
106 	sigset_t set;
107 	sigemptyset(&set);
108 	sigaddset(&set, SIGCHLD);
109 	assert(ha_sigmask(SIG_UNBLOCK, &set, NULL) == 0);
110 }
111 
pid_list_add(pid_t pid,struct task * t)112 static struct pid_list *pid_list_add(pid_t pid, struct task *t)
113 {
114 	struct pid_list *elem;
115 	struct check *check = t->context;
116 
117 	elem = pool_alloc(pool_head_pid_list);
118 	if (!elem)
119 		return NULL;
120 	elem->pid = pid;
121 	elem->t = t;
122 	elem->exited = 0;
123 	check->curpid = elem;
124 	LIST_INIT(&elem->list);
125 
126 	HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
127 	LIST_ADD(&pid_list, &elem->list);
128 	HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
129 
130 	return elem;
131 }
132 
pid_list_del(struct pid_list * elem)133 static void pid_list_del(struct pid_list *elem)
134 {
135 	struct check *check;
136 
137 	if (!elem)
138 		return;
139 
140 	HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
141 	LIST_DEL(&elem->list);
142 	HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
143 
144 	if (!elem->exited)
145 		kill(elem->pid, SIGTERM);
146 
147 	check = elem->t->context;
148 	check->curpid = NULL;
149 	pool_free(pool_head_pid_list, elem);
150 }
151 
152 /* Called from inside SIGCHLD handler, SIGCHLD is blocked */
pid_list_expire(pid_t pid,int status)153 static void pid_list_expire(pid_t pid, int status)
154 {
155 	struct pid_list *elem;
156 
157 	HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
158 	list_for_each_entry(elem, &pid_list, list) {
159 		if (elem->pid == pid) {
160 			elem->t->expire = now_ms;
161 			elem->status = status;
162 			elem->exited = 1;
163 			task_wakeup(elem->t, TASK_WOKEN_IO);
164 			break;
165 		}
166 	}
167 	HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
168 }
169 
sigchld_handler(struct sig_handler * sh)170 static void sigchld_handler(struct sig_handler *sh)
171 {
172 	pid_t pid;
173 	int status;
174 
175 	while ((pid = waitpid(0, &status, WNOHANG)) > 0)
176 		pid_list_expire(pid, status);
177 }
178 
init_pid_list(void)179 int init_pid_list(void)
180 {
181 	if (pool_head_pid_list != NULL)
182 		/* Nothing to do */
183 		return 0;
184 
185 	if (!signal_register_fct(SIGCHLD, sigchld_handler, SIGCHLD)) {
186 		ha_alert("Failed to set signal handler for external health checks: %s. Aborting.\n",
187 			 strerror(errno));
188 		return 1;
189 	}
190 
191 	pool_head_pid_list = create_pool("pid_list", sizeof(struct pid_list), MEM_F_SHARED);
192 	if (pool_head_pid_list == NULL) {
193 		ha_alert("Failed to allocate memory pool for external health checks: %s. Aborting.\n",
194 			 strerror(errno));
195 		return 1;
196 	}
197 
198 	return 0;
199 }
200 
201 /* helper macro to set an environment variable and jump to a specific label on failure. */
202 #define EXTCHK_SETENV(check, envidx, value, fail) { if (extchk_setenv(check, envidx, value)) goto fail; }
203 
204 /*
205  * helper function to allocate enough memory to store an environment variable.
206  * It will also check that the environment variable is updatable, and silently
207  * fail if not.
208  */
extchk_setenv(struct check * check,int idx,const char * value)209 static int extchk_setenv(struct check *check, int idx, const char *value)
210 {
211 	int len, ret;
212 	char *envname;
213 	int vmaxlen;
214 
215 	if (idx < 0 || idx >= EXTCHK_SIZE) {
216 		ha_alert("Illegal environment variable index %d. Aborting.\n", idx);
217 		return 1;
218 	}
219 
220 	envname = extcheck_envs[idx].name;
221 	vmaxlen = extcheck_envs[idx].vmaxlen;
222 
223 	/* Check if the environment variable is already set, and silently reject
224 	 * the update if this one is not updatable. */
225 	if ((vmaxlen == EXTCHK_SIZE_EVAL_INIT) && (check->envp[idx]))
226 		return 0;
227 
228 	/* Instead of sending NOT_USED, sending an empty value is preferable */
229 	if (strcmp(value, "NOT_USED") == 0) {
230 		value = "";
231 	}
232 
233 	len = strlen(envname) + 1;
234 	if (vmaxlen == EXTCHK_SIZE_EVAL_INIT)
235 		len += strlen(value);
236 	else
237 		len += vmaxlen;
238 
239 	if (!check->envp[idx])
240 		check->envp[idx] = malloc(len + 1);
241 
242 	if (!check->envp[idx]) {
243 		ha_alert("Failed to allocate memory for the environment variable '%s'. Aborting.\n", envname);
244 		return 1;
245 	}
246 	ret = snprintf(check->envp[idx], len + 1, "%s=%s", envname, value);
247 	if (ret < 0) {
248 		ha_alert("Failed to store the environment variable '%s'. Reason : %s. Aborting.\n", envname, strerror(errno));
249 		return 1;
250 	}
251 	else if (ret > len) {
252 		ha_alert("Environment variable '%s' was truncated. Aborting.\n", envname);
253 		return 1;
254 	}
255 	return 0;
256 }
257 
prepare_external_check(struct check * check)258 int prepare_external_check(struct check *check)
259 {
260 	struct server *s = check->server;
261 	struct proxy *px = s->proxy;
262 	struct listener *listener = NULL, *l;
263 	int i;
264 	const char *path = px->check_path ? px->check_path : DEF_CHECK_PATH;
265 	char buf[256];
266 
267 	list_for_each_entry(l, &px->conf.listeners, by_fe)
268 		/* Use the first INET, INET6 or UNIX listener */
269 		if (l->rx.addr.ss_family == AF_INET ||
270 		    l->rx.addr.ss_family == AF_INET6 ||
271 		    l->rx.addr.ss_family == AF_UNIX) {
272 			listener = l;
273 			break;
274 		}
275 
276 	check->curpid = NULL;
277 	check->envp = calloc((EXTCHK_SIZE + 1), sizeof(*check->envp));
278 	if (!check->envp) {
279 		ha_alert("Failed to allocate memory for environment variables. Aborting\n");
280 		goto err;
281 	}
282 
283 	check->argv = calloc(6, sizeof(*check->argv));
284 	if (!check->argv) {
285 		ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
286 		goto err;
287 	}
288 
289 	check->argv[0] = px->check_command;
290 
291 	if (!listener) {
292 		check->argv[1] = strdup("NOT_USED");
293 		check->argv[2] = strdup("NOT_USED");
294 	}
295 	else if (listener->rx.addr.ss_family == AF_INET ||
296 	    listener->rx.addr.ss_family == AF_INET6) {
297 		addr_to_str(&listener->rx.addr, buf, sizeof(buf));
298 		check->argv[1] = strdup(buf);
299 		port_to_str(&listener->rx.addr, buf, sizeof(buf));
300 		check->argv[2] = strdup(buf);
301 	}
302 	else if (listener->rx.addr.ss_family == AF_UNIX) {
303 		const struct sockaddr_un *un;
304 
305 		un = (struct sockaddr_un *)&listener->rx.addr;
306 		check->argv[1] = strdup(un->sun_path);
307 		check->argv[2] = strdup("NOT_USED");
308 	}
309 	else {
310 		ha_alert("Starting [%s:%s] check: unsupported address family.\n", px->id, s->id);
311 		goto err;
312 	}
313 
314 	if (!check->argv[1] || !check->argv[2]) {
315 		ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
316 		goto err;
317 	}
318 
319 	check->argv[3] = calloc(EXTCHK_SIZE_ADDR, sizeof(*check->argv[3]));
320 	check->argv[4] = calloc(EXTCHK_SIZE_UINT, sizeof(*check->argv[4]));
321 	if (!check->argv[3] || !check->argv[4]) {
322 		ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
323 		goto err;
324 	}
325 
326 	addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
327 	if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
328 		snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
329 
330 	for (i = 0; i < 5; i++) {
331 		if (!check->argv[i]) {
332 			ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
333 			goto err;
334 		}
335 	}
336 
337 	EXTCHK_SETENV(check, EXTCHK_PATH, path, err);
338 	/* Add proxy environment variables */
339 	EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_NAME, px->id, err);
340 	EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ID, ultoa_r(px->uuid, buf, sizeof(buf)), err);
341 	EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ADDR, check->argv[1], err);
342 	EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_PORT, check->argv[2], err);
343 	/* Add server environment variables */
344 	EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_NAME, s->id, err);
345 	EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ID, ultoa_r(s->puid, buf, sizeof(buf)), err);
346 	EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3], err);
347 	EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4], err);
348 	EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_MAXCONN, ultoa_r(s->maxconn, buf, sizeof(buf)), err);
349 	EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)), err);
350 
351 	/* Ensure that we don't leave any hole in check->envp */
352 	for (i = 0; i < EXTCHK_SIZE; i++)
353 		if (!check->envp[i])
354 			EXTCHK_SETENV(check, i, "", err);
355 
356 	return 1;
357 err:
358 	if (check->envp) {
359 		for (i = 0; i < EXTCHK_SIZE; i++)
360 			free(check->envp[i]);
361 		free(check->envp);
362 		check->envp = NULL;
363 	}
364 
365 	if (check->argv) {
366 		for (i = 1; i < 5; i++)
367 			free(check->argv[i]);
368 		free(check->argv);
369 		check->argv = NULL;
370 	}
371 	return 0;
372 }
373 
374 /*
375  * establish a server health-check that makes use of a process.
376  *
377  * It can return one of :
378  *  - SF_ERR_NONE if everything's OK
379  *  - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
380  * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
381  *
382  * Blocks and then unblocks SIGCHLD
383  */
connect_proc_chk(struct task * t)384 static int connect_proc_chk(struct task *t)
385 {
386 	char buf[256];
387 	struct check *check = t->context;
388 	struct server *s = check->server;
389 	struct proxy *px = s->proxy;
390 	int status;
391 	pid_t pid;
392 
393 	status = SF_ERR_RESOURCE;
394 
395 	block_sigchld();
396 
397 	pid = fork();
398 	if (pid < 0) {
399 		ha_alert("Failed to fork process for external health check%s: %s. Aborting.\n",
400 			 (global.tune.options & GTUNE_INSECURE_FORK) ?
401 			 "" : " (likely caused by missing 'insecure-fork-wanted')",
402 			 strerror(errno));
403 		set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
404 		goto out;
405 	}
406 	if (pid == 0) {
407 		/* Child */
408 		extern char **environ;
409 		struct rlimit limit;
410 		int fd;
411 
412 		/* close all FDs. Keep stdin/stdout/stderr in verbose mode */
413 		fd = (global.mode & (MODE_QUIET|MODE_VERBOSE)) == MODE_QUIET ? 0 : 3;
414 
415 		my_closefrom(fd);
416 
417 		/* restore the initial FD limits */
418 		limit.rlim_cur = rlim_fd_cur_at_boot;
419 		limit.rlim_max = rlim_fd_max_at_boot;
420 		if (setrlimit(RLIMIT_NOFILE, &limit) == -1) {
421 			getrlimit(RLIMIT_NOFILE, &limit);
422 			ha_warning("External check: failed to restore initial FD limits (cur=%u max=%u), using cur=%u max=%u\n",
423 				   rlim_fd_cur_at_boot, rlim_fd_max_at_boot,
424 				   (unsigned int)limit.rlim_cur, (unsigned int)limit.rlim_max);
425 		}
426 
427 		environ = check->envp;
428 
429 		/* Update some environment variables and command args: curconn, server addr and server port */
430 		EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)), fail);
431 
432 		addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
433 		EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3], fail);
434 
435 		*check->argv[4] = 0;
436 		if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
437 			snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
438 		EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4], fail);
439 
440 		haproxy_unblock_signals();
441 		execvp(px->check_command, check->argv);
442 		ha_alert("Failed to exec process for external health check: %s. Aborting.\n",
443 			 strerror(errno));
444 	fail:
445 		exit(-1);
446 	}
447 
448 	/* Parent */
449 	if (check->result == CHK_RES_UNKNOWN) {
450 		if (pid_list_add(pid, t) != NULL) {
451 			t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
452 
453 			if (px->timeout.check && px->timeout.connect) {
454 				int t_con = tick_add(now_ms, px->timeout.connect);
455 				t->expire = tick_first(t->expire, t_con);
456 			}
457 			status = SF_ERR_NONE;
458 			goto out;
459 		}
460 		else {
461 			set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
462 		}
463 		kill(pid, SIGTERM); /* process creation error */
464 	}
465 	else
466 		set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
467 
468 out:
469 	unblock_sigchld();
470 	return status;
471 }
472 
473 /*
474  * manages a server health-check that uses an external process. Returns
475  * the time the task accepts to wait, or TIME_ETERNITY for infinity.
476  *
477  * Please do NOT place any return statement in this function and only leave
478  * via the out_unlock label.
479  */
process_chk_proc(struct task * t,void * context,unsigned short state)480 struct task *process_chk_proc(struct task *t, void *context, unsigned short state)
481 {
482 	struct check *check = context;
483 	struct server *s = check->server;
484 	int rv;
485 	int ret;
486 	int expired = tick_is_expired(t->expire, now_ms);
487 
488 	HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
489 	if (!(check->state & CHK_ST_INPROGRESS)) {
490 		/* no check currently running */
491 		if (!expired) /* woke up too early */
492 			goto out_unlock;
493 
494 		/* we don't send any health-checks when the proxy is
495 		 * stopped, the server should not be checked or the check
496 		 * is disabled.
497 		 */
498 		if (((check->state & (CHK_ST_ENABLED | CHK_ST_PAUSED)) != CHK_ST_ENABLED) ||
499 		    s->proxy->disabled)
500 			goto reschedule;
501 
502 		/* we'll initiate a new check */
503 		set_server_check_status(check, HCHK_STATUS_START, NULL);
504 
505 		check->state |= CHK_ST_INPROGRESS;
506 
507 		ret = connect_proc_chk(t);
508 		if (ret == SF_ERR_NONE) {
509 			/* the process was forked, we allow up to min(inter,
510 			 * timeout.connect) for it to report its status, but
511 			 * only when timeout.check is set as it may be to short
512 			 * for a full check otherwise.
513 			 */
514 			t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
515 
516 			if (s->proxy->timeout.check && s->proxy->timeout.connect) {
517 				int t_con = tick_add(now_ms, s->proxy->timeout.connect);
518 				t->expire = tick_first(t->expire, t_con);
519 			}
520 			task_set_affinity(t, tid_bit);
521 			goto reschedule;
522 		}
523 
524 		/* here, we failed to start the check */
525 
526 		check->state &= ~CHK_ST_INPROGRESS;
527 		check_notify_failure(check);
528 
529 		/* we allow up to min(inter, timeout.connect) for a connection
530 		 * to establish but only when timeout.check is set
531 		 * as it may be to short for a full check otherwise
532 		 */
533 		while (tick_is_expired(t->expire, now_ms)) {
534 			int t_con;
535 
536 			t_con = tick_add(t->expire, s->proxy->timeout.connect);
537 			t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
538 
539 			if (s->proxy->timeout.check)
540 				t->expire = tick_first(t->expire, t_con);
541 		}
542 	}
543 	else {
544 		/* there was a test running.
545 		 * First, let's check whether there was an uncaught error,
546 		 * which can happen on connect timeout or error.
547 		 */
548 		if (check->result == CHK_RES_UNKNOWN) {
549 			/* good connection is enough for pure TCP check */
550 			struct pid_list *elem = check->curpid;
551 			int status = HCHK_STATUS_UNKNOWN;
552 
553 			if (elem->exited) {
554 				status = elem->status; /* Save in case the process exits between use below */
555 				if (!WIFEXITED(status))
556 					check->code = -1;
557 				else
558 					check->code = WEXITSTATUS(status);
559 				if (!WIFEXITED(status) || WEXITSTATUS(status))
560 					status = HCHK_STATUS_PROCERR;
561 				else
562 					status = HCHK_STATUS_PROCOK;
563 			} else if (expired) {
564 				status = HCHK_STATUS_PROCTOUT;
565 				ha_warning("kill %d\n", (int)elem->pid);
566 				kill(elem->pid, SIGTERM);
567 			}
568 			set_server_check_status(check, status, NULL);
569 		}
570 
571 		if (check->result == CHK_RES_FAILED) {
572 			/* a failure or timeout detected */
573 			check_notify_failure(check);
574 		}
575 		else if (check->result == CHK_RES_CONDPASS) {
576 			/* check is OK but asks for stopping mode */
577 			check_notify_stopping(check);
578 		}
579 		else if (check->result == CHK_RES_PASSED) {
580 			/* a success was detected */
581 			check_notify_success(check);
582 		}
583 		task_set_affinity(t, 1);
584 		check->state &= ~CHK_ST_INPROGRESS;
585 
586 		pid_list_del(check->curpid);
587 
588 		rv = 0;
589 		if (global.spread_checks > 0) {
590 			rv = srv_getinter(check) * global.spread_checks / 100;
591 			rv -= (int) (2 * rv * (ha_random32() / 4294967295.0));
592 		}
593 		t->expire = tick_add(now_ms, MS_TO_TICKS(srv_getinter(check) + rv));
594 	}
595 
596  reschedule:
597 	while (tick_is_expired(t->expire, now_ms))
598 		t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
599 
600  out_unlock:
601 	HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
602 	return t;
603 }
604 
605 /* Parses the "external-check" proxy keyword */
proxy_parse_extcheck(char ** args,int section,struct proxy * curpx,struct proxy * defpx,const char * file,int line,char ** errmsg)606 int proxy_parse_extcheck(char **args, int section, struct proxy *curpx,
607                          struct proxy *defpx, const char *file, int line,
608                          char **errmsg)
609 {
610 	int cur_arg, ret = 0;
611 
612 	cur_arg = 1;
613 	if (!*(args[cur_arg])) {
614 		memprintf(errmsg, "missing argument after '%s'.\n", args[0]);
615 		goto error;
616 	}
617 
618 	if (strcmp(args[cur_arg], "command") == 0) {
619 		if (too_many_args(2, args, errmsg, NULL))
620 			goto error;
621 		if (!*(args[cur_arg+1])) {
622 			memprintf(errmsg, "missing argument after '%s'.", args[cur_arg]);
623 			goto error;
624 		}
625 		free(curpx->check_command);
626 		curpx->check_command = strdup(args[cur_arg+1]);
627 	}
628 	else if (strcmp(args[cur_arg], "path") == 0) {
629 		if (too_many_args(2, args, errmsg, NULL))
630 			goto error;
631 		if (!*(args[cur_arg+1])) {
632 			memprintf(errmsg, "missing argument after '%s'.", args[cur_arg]);
633 			goto error;
634 		}
635 		free(curpx->check_path);
636 		curpx->check_path = strdup(args[cur_arg+1]);
637 	}
638 	else {
639 		memprintf(errmsg, "'%s' only supports 'command' and 'path'. but got '%s'.",
640 			  args[0], args[1]);
641 		goto error;
642 	}
643 
644 	ret = (*errmsg != NULL); /* Handle warning */
645 	return ret;
646 
647 error:
648 	return -1;
649 }
650 
proxy_parse_external_check_opt(char ** args,int cur_arg,struct proxy * curpx,struct proxy * defpx,const char * file,int line)651 int proxy_parse_external_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
652 				   const char *file, int line)
653 {
654 	int err_code = 0;
655 
656 	curpx->options2 &= ~PR_O2_CHK_ANY;
657 	curpx->options2 |= PR_O2_EXT_CHK;
658 	if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
659 		goto out;
660 
661   out:
662 	return err_code;
663 }
664 
665 static struct cfg_kw_list cfg_kws = {ILH, {
666         { CFG_LISTEN, "external-check", proxy_parse_extcheck },
667         { 0, NULL, NULL },
668 }};
669 
670 INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
671