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 __read_mostly;
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_INSERT(&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_DELETE(&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 ha_free(&check->envp);
362 }
363
364 if (check->argv) {
365 for (i = 1; i < 5; i++)
366 free(check->argv[i]);
367 ha_free(&check->argv);
368 }
369 return 0;
370 }
371
372 /*
373 * establish a server health-check that makes use of a process.
374 *
375 * It can return one of :
376 * - SF_ERR_NONE if everything's OK
377 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
378 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
379 *
380 * Blocks and then unblocks SIGCHLD
381 */
connect_proc_chk(struct task * t)382 static int connect_proc_chk(struct task *t)
383 {
384 char buf[256];
385 struct check *check = t->context;
386 struct server *s = check->server;
387 struct proxy *px = s->proxy;
388 int status;
389 pid_t pid;
390
391 status = SF_ERR_RESOURCE;
392
393 block_sigchld();
394
395 pid = fork();
396 if (pid < 0) {
397 ha_alert("Failed to fork process for external health check%s: %s. Aborting.\n",
398 (global.tune.options & GTUNE_INSECURE_FORK) ?
399 "" : " (likely caused by missing 'insecure-fork-wanted')",
400 strerror(errno));
401 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
402 goto out;
403 }
404 if (pid == 0) {
405 /* Child */
406 extern char **environ;
407 struct rlimit limit;
408 int fd;
409
410 /* close all FDs. Keep stdin/stdout/stderr in verbose mode */
411 fd = (global.mode & (MODE_QUIET|MODE_VERBOSE)) == MODE_QUIET ? 0 : 3;
412
413 my_closefrom(fd);
414
415 /* restore the initial FD limits */
416 limit.rlim_cur = rlim_fd_cur_at_boot;
417 limit.rlim_max = rlim_fd_max_at_boot;
418 if (setrlimit(RLIMIT_NOFILE, &limit) == -1) {
419 getrlimit(RLIMIT_NOFILE, &limit);
420 ha_warning("External check: failed to restore initial FD limits (cur=%u max=%u), using cur=%u max=%u\n",
421 rlim_fd_cur_at_boot, rlim_fd_max_at_boot,
422 (unsigned int)limit.rlim_cur, (unsigned int)limit.rlim_max);
423 }
424
425 environ = check->envp;
426
427 /* Update some environment variables and command args: curconn, server addr and server port */
428 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)), fail);
429
430 addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
431 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3], fail);
432
433 *check->argv[4] = 0;
434 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
435 snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
436 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4], fail);
437
438 haproxy_unblock_signals();
439 execvp(px->check_command, check->argv);
440 ha_alert("Failed to exec process for external health check: %s. Aborting.\n",
441 strerror(errno));
442 fail:
443 exit(-1);
444 }
445
446 /* Parent */
447 if (check->result == CHK_RES_UNKNOWN) {
448 if (pid_list_add(pid, t) != NULL) {
449 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
450
451 if (px->timeout.check && px->timeout.connect) {
452 int t_con = tick_add(now_ms, px->timeout.connect);
453 t->expire = tick_first(t->expire, t_con);
454 }
455 status = SF_ERR_NONE;
456 goto out;
457 }
458 else {
459 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
460 }
461 kill(pid, SIGTERM); /* process creation error */
462 }
463 else
464 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
465
466 out:
467 unblock_sigchld();
468 return status;
469 }
470
471 /*
472 * manages a server health-check that uses an external process. Returns
473 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
474 *
475 * Please do NOT place any return statement in this function and only leave
476 * via the out_unlock label.
477 */
process_chk_proc(struct task * t,void * context,unsigned int state)478 struct task *process_chk_proc(struct task *t, void *context, unsigned int state)
479 {
480 struct check *check = context;
481 struct server *s = check->server;
482 int rv;
483 int ret;
484 int expired = tick_is_expired(t->expire, now_ms);
485
486 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
487 if (!(check->state & CHK_ST_INPROGRESS)) {
488 /* no check currently running */
489 if (!expired) /* woke up too early */
490 goto out_unlock;
491
492 /* we don't send any health-checks when the proxy is
493 * stopped, the server should not be checked or the check
494 * is disabled.
495 */
496 if (((check->state & (CHK_ST_ENABLED | CHK_ST_PAUSED)) != CHK_ST_ENABLED) ||
497 s->proxy->disabled)
498 goto reschedule;
499
500 /* we'll initiate a new check */
501 set_server_check_status(check, HCHK_STATUS_START, NULL);
502
503 check->state |= CHK_ST_INPROGRESS;
504
505 ret = connect_proc_chk(t);
506 if (ret == SF_ERR_NONE) {
507 /* the process was forked, we allow up to min(inter,
508 * timeout.connect) for it to report its status, but
509 * only when timeout.check is set as it may be to short
510 * for a full check otherwise.
511 */
512 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
513
514 if (s->proxy->timeout.check && s->proxy->timeout.connect) {
515 int t_con = tick_add(now_ms, s->proxy->timeout.connect);
516 t->expire = tick_first(t->expire, t_con);
517 }
518 task_set_affinity(t, tid_bit);
519 goto reschedule;
520 }
521
522 /* here, we failed to start the check */
523
524 check->state &= ~CHK_ST_INPROGRESS;
525 check_notify_failure(check);
526
527 /* we allow up to min(inter, timeout.connect) for a connection
528 * to establish but only when timeout.check is set
529 * as it may be to short for a full check otherwise
530 */
531 while (tick_is_expired(t->expire, now_ms)) {
532 int t_con;
533
534 t_con = tick_add(t->expire, s->proxy->timeout.connect);
535 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
536
537 if (s->proxy->timeout.check)
538 t->expire = tick_first(t->expire, t_con);
539 }
540 }
541 else {
542 /* there was a test running.
543 * First, let's check whether there was an uncaught error,
544 * which can happen on connect timeout or error.
545 */
546 if (check->result == CHK_RES_UNKNOWN) {
547 /* good connection is enough for pure TCP check */
548 struct pid_list *elem = check->curpid;
549 int status = HCHK_STATUS_UNKNOWN;
550
551 if (elem->exited) {
552 status = elem->status; /* Save in case the process exits between use below */
553 if (!WIFEXITED(status))
554 check->code = -1;
555 else
556 check->code = WEXITSTATUS(status);
557 if (!WIFEXITED(status) || WEXITSTATUS(status))
558 status = HCHK_STATUS_PROCERR;
559 else
560 status = HCHK_STATUS_PROCOK;
561 } else if (expired) {
562 status = HCHK_STATUS_PROCTOUT;
563 ha_warning("kill %d\n", (int)elem->pid);
564 kill(elem->pid, SIGTERM);
565 }
566 set_server_check_status(check, status, NULL);
567 }
568
569 if (check->result == CHK_RES_FAILED) {
570 /* a failure or timeout detected */
571 check_notify_failure(check);
572 }
573 else if (check->result == CHK_RES_CONDPASS) {
574 /* check is OK but asks for stopping mode */
575 check_notify_stopping(check);
576 }
577 else if (check->result == CHK_RES_PASSED) {
578 /* a success was detected */
579 check_notify_success(check);
580 }
581 task_set_affinity(t, 1);
582 check->state &= ~CHK_ST_INPROGRESS;
583
584 pid_list_del(check->curpid);
585
586 rv = 0;
587 if (global.spread_checks > 0) {
588 rv = srv_getinter(check) * global.spread_checks / 100;
589 rv -= (int) (2 * rv * (ha_random32() / 4294967295.0));
590 }
591 t->expire = tick_add(now_ms, MS_TO_TICKS(srv_getinter(check) + rv));
592 }
593
594 reschedule:
595 while (tick_is_expired(t->expire, now_ms))
596 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
597
598 out_unlock:
599 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
600 return t;
601 }
602
603 /* Parses the "external-check" proxy keyword */
proxy_parse_extcheck(char ** args,int section,struct proxy * curpx,const struct proxy * defpx,const char * file,int line,char ** errmsg)604 int proxy_parse_extcheck(char **args, int section, struct proxy *curpx,
605 const struct proxy *defpx, const char *file, int line,
606 char **errmsg)
607 {
608 int cur_arg, ret = 0;
609
610 cur_arg = 1;
611 if (!*(args[cur_arg])) {
612 memprintf(errmsg, "missing argument after '%s'.\n", args[0]);
613 goto error;
614 }
615
616 if (strcmp(args[cur_arg], "command") == 0) {
617 if (too_many_args(2, args, errmsg, NULL))
618 goto error;
619 if (!*(args[cur_arg+1])) {
620 memprintf(errmsg, "missing argument after '%s'.", args[cur_arg]);
621 goto error;
622 }
623 free(curpx->check_command);
624 curpx->check_command = strdup(args[cur_arg+1]);
625 }
626 else if (strcmp(args[cur_arg], "path") == 0) {
627 if (too_many_args(2, args, errmsg, NULL))
628 goto error;
629 if (!*(args[cur_arg+1])) {
630 memprintf(errmsg, "missing argument after '%s'.", args[cur_arg]);
631 goto error;
632 }
633 free(curpx->check_path);
634 curpx->check_path = strdup(args[cur_arg+1]);
635 }
636 else {
637 memprintf(errmsg, "'%s' only supports 'command' and 'path'. but got '%s'.",
638 args[0], args[1]);
639 goto error;
640 }
641
642 ret = (*errmsg != NULL); /* Handle warning */
643 return ret;
644
645 error:
646 return -1;
647 }
648
proxy_parse_external_check_opt(char ** args,int cur_arg,struct proxy * curpx,const struct proxy * defpx,const char * file,int line)649 int proxy_parse_external_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
650 const char *file, int line)
651 {
652 int err_code = 0;
653
654 curpx->options2 &= ~PR_O2_CHK_ANY;
655 curpx->options2 |= PR_O2_EXT_CHK;
656 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
657 goto out;
658
659 out:
660 return err_code;
661 }
662
663 static struct cfg_kw_list cfg_kws = {ILH, {
664 { CFG_LISTEN, "external-check", proxy_parse_extcheck },
665 { 0, NULL, NULL },
666 }};
667
668 INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
669