blob: c667b1635cc0feb7e1392b872707895f0d282e2b [file] [log] [blame]
Willy Tarreaubcc67332020-06-05 15:31:31 +02001/*
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>
Willy Tarreaubcc67332020-06-05 15:31:31 +020021#include <signal.h>
22#include <stdarg.h>
23#include <stdio.h>
24#include <stdlib.h>
25#include <string.h>
26#include <time.h>
27#include <unistd.h>
28
29#include <haproxy/api.h>
30#include <haproxy/cfgparse.h>
31#include <haproxy/check.h>
Willy Tarreau36979d92020-06-05 17:27:29 +020032#include <haproxy/errors.h>
Willy Tarreaubcc67332020-06-05 15:31:31 +020033#include <haproxy/global.h>
34#include <haproxy/list.h>
Willy Tarreaubcc67332020-06-05 15:31:31 +020035#include <haproxy/proxy.h>
36#include <haproxy/server.h>
37#include <haproxy/signal.h>
Willy Tarreau397ad412021-10-06 09:11:54 +020038#include <haproxy/stream-t.h>
Willy Tarreaubcc67332020-06-05 15:31:31 +020039#include <haproxy/task.h>
40#include <haproxy/thread.h>
Willy Tarreaubcc67332020-06-05 15:31:31 +020041#include <haproxy/tools.h>
42
43
44static struct list pid_list = LIST_HEAD_INIT(pid_list);
Willy Tarreauff882702021-04-10 17:23:00 +020045static struct pool_head *pool_head_pid_list __read_mostly;
Willy Tarreaubcc67332020-06-05 15:31:31 +020046__decl_spinlock(pid_list_lock);
47
48struct extcheck_env {
49 char *name; /* environment variable name */
50 int vmaxlen; /* value maximum length, used to determine the required memory allocation */
51};
52
53/* environment variables memory requirement for different types of data */
54#define EXTCHK_SIZE_EVAL_INIT 0 /* size determined during the init phase,
55 * such environment variables are not updatable. */
56#define EXTCHK_SIZE_ULONG 20 /* max string length for an unsigned long value */
57#define EXTCHK_SIZE_UINT 11 /* max string length for an unsigned int value */
Willy Tarreaucef08c22022-04-14 19:51:02 +020058#define EXTCHK_SIZE_ADDR 256 /* max string length for an IPv4/IPv6/UNIX address */
Willy Tarreaubcc67332020-06-05 15:31:31 +020059
60/* external checks environment variables */
61enum {
62 EXTCHK_PATH = 0,
63
64 /* Proxy specific environment variables */
65 EXTCHK_HAPROXY_PROXY_NAME, /* the backend name */
66 EXTCHK_HAPROXY_PROXY_ID, /* the backend id */
67 EXTCHK_HAPROXY_PROXY_ADDR, /* the first bind address if available (or empty) */
68 EXTCHK_HAPROXY_PROXY_PORT, /* the first bind port if available (or empty) */
69
70 /* Server specific environment variables */
71 EXTCHK_HAPROXY_SERVER_NAME, /* the server name */
72 EXTCHK_HAPROXY_SERVER_ID, /* the server id */
73 EXTCHK_HAPROXY_SERVER_ADDR, /* the server address */
74 EXTCHK_HAPROXY_SERVER_PORT, /* the server port if available (or empty) */
75 EXTCHK_HAPROXY_SERVER_MAXCONN, /* the server max connections */
76 EXTCHK_HAPROXY_SERVER_CURCONN, /* the current number of connections on the server */
Willy Tarreau973cf902022-05-13 15:58:35 +020077 EXTCHK_HAPROXY_SERVER_SSL, /* "1" if the server supports SSL, otherwise zero */
78 EXTCHK_HAPROXY_SERVER_PROTO, /* the server's configured proto, if any */
Willy Tarreaubcc67332020-06-05 15:31:31 +020079
80 EXTCHK_SIZE
81};
82
83const struct extcheck_env extcheck_envs[EXTCHK_SIZE] = {
84 [EXTCHK_PATH] = { "PATH", EXTCHK_SIZE_EVAL_INIT },
85 [EXTCHK_HAPROXY_PROXY_NAME] = { "HAPROXY_PROXY_NAME", EXTCHK_SIZE_EVAL_INIT },
86 [EXTCHK_HAPROXY_PROXY_ID] = { "HAPROXY_PROXY_ID", EXTCHK_SIZE_EVAL_INIT },
87 [EXTCHK_HAPROXY_PROXY_ADDR] = { "HAPROXY_PROXY_ADDR", EXTCHK_SIZE_EVAL_INIT },
88 [EXTCHK_HAPROXY_PROXY_PORT] = { "HAPROXY_PROXY_PORT", EXTCHK_SIZE_EVAL_INIT },
89 [EXTCHK_HAPROXY_SERVER_NAME] = { "HAPROXY_SERVER_NAME", EXTCHK_SIZE_EVAL_INIT },
90 [EXTCHK_HAPROXY_SERVER_ID] = { "HAPROXY_SERVER_ID", EXTCHK_SIZE_EVAL_INIT },
91 [EXTCHK_HAPROXY_SERVER_ADDR] = { "HAPROXY_SERVER_ADDR", EXTCHK_SIZE_ADDR },
92 [EXTCHK_HAPROXY_SERVER_PORT] = { "HAPROXY_SERVER_PORT", EXTCHK_SIZE_UINT },
93 [EXTCHK_HAPROXY_SERVER_MAXCONN] = { "HAPROXY_SERVER_MAXCONN", EXTCHK_SIZE_EVAL_INIT },
94 [EXTCHK_HAPROXY_SERVER_CURCONN] = { "HAPROXY_SERVER_CURCONN", EXTCHK_SIZE_ULONG },
Willy Tarreau973cf902022-05-13 15:58:35 +020095 [EXTCHK_HAPROXY_SERVER_SSL] = { "HAPROXY_SERVER_SSL", EXTCHK_SIZE_UINT },
96 [EXTCHK_HAPROXY_SERVER_PROTO] = { "HAPROXY_SERVER_PROTO", EXTCHK_SIZE_EVAL_INIT },
Willy Tarreaubcc67332020-06-05 15:31:31 +020097};
98
99void block_sigchld(void)
100{
101 sigset_t set;
102 sigemptyset(&set);
103 sigaddset(&set, SIGCHLD);
104 assert(ha_sigmask(SIG_BLOCK, &set, NULL) == 0);
105}
106
107void unblock_sigchld(void)
108{
109 sigset_t set;
110 sigemptyset(&set);
111 sigaddset(&set, SIGCHLD);
112 assert(ha_sigmask(SIG_UNBLOCK, &set, NULL) == 0);
113}
114
115static struct pid_list *pid_list_add(pid_t pid, struct task *t)
116{
117 struct pid_list *elem;
118 struct check *check = t->context;
119
120 elem = pool_alloc(pool_head_pid_list);
121 if (!elem)
122 return NULL;
123 elem->pid = pid;
124 elem->t = t;
125 elem->exited = 0;
126 check->curpid = elem;
127 LIST_INIT(&elem->list);
128
129 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
Willy Tarreau2b718102021-04-21 07:32:39 +0200130 LIST_INSERT(&pid_list, &elem->list);
Willy Tarreaubcc67332020-06-05 15:31:31 +0200131 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
132
133 return elem;
134}
135
136static void pid_list_del(struct pid_list *elem)
137{
138 struct check *check;
139
140 if (!elem)
141 return;
142
143 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
Willy Tarreau2b718102021-04-21 07:32:39 +0200144 LIST_DELETE(&elem->list);
Willy Tarreaubcc67332020-06-05 15:31:31 +0200145 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
146
147 if (!elem->exited)
148 kill(elem->pid, SIGTERM);
149
150 check = elem->t->context;
151 check->curpid = NULL;
152 pool_free(pool_head_pid_list, elem);
153}
154
155/* Called from inside SIGCHLD handler, SIGCHLD is blocked */
156static void pid_list_expire(pid_t pid, int status)
157{
158 struct pid_list *elem;
159
160 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
161 list_for_each_entry(elem, &pid_list, list) {
162 if (elem->pid == pid) {
163 elem->t->expire = now_ms;
164 elem->status = status;
165 elem->exited = 1;
166 task_wakeup(elem->t, TASK_WOKEN_IO);
167 break;
168 }
169 }
170 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
171}
172
173static void sigchld_handler(struct sig_handler *sh)
174{
175 pid_t pid;
176 int status;
177
178 while ((pid = waitpid(0, &status, WNOHANG)) > 0)
179 pid_list_expire(pid, status);
180}
181
182int init_pid_list(void)
183{
184 if (pool_head_pid_list != NULL)
185 /* Nothing to do */
186 return 0;
187
188 if (!signal_register_fct(SIGCHLD, sigchld_handler, SIGCHLD)) {
189 ha_alert("Failed to set signal handler for external health checks: %s. Aborting.\n",
190 strerror(errno));
191 return 1;
192 }
193
194 pool_head_pid_list = create_pool("pid_list", sizeof(struct pid_list), MEM_F_SHARED);
195 if (pool_head_pid_list == NULL) {
196 ha_alert("Failed to allocate memory pool for external health checks: %s. Aborting.\n",
197 strerror(errno));
198 return 1;
199 }
200
201 return 0;
202}
203
204/* helper macro to set an environment variable and jump to a specific label on failure. */
205#define EXTCHK_SETENV(check, envidx, value, fail) { if (extchk_setenv(check, envidx, value)) goto fail; }
206
207/*
208 * helper function to allocate enough memory to store an environment variable.
209 * It will also check that the environment variable is updatable, and silently
210 * fail if not.
211 */
212static int extchk_setenv(struct check *check, int idx, const char *value)
213{
214 int len, ret;
215 char *envname;
216 int vmaxlen;
217
218 if (idx < 0 || idx >= EXTCHK_SIZE) {
219 ha_alert("Illegal environment variable index %d. Aborting.\n", idx);
220 return 1;
221 }
222
223 envname = extcheck_envs[idx].name;
224 vmaxlen = extcheck_envs[idx].vmaxlen;
225
226 /* Check if the environment variable is already set, and silently reject
227 * the update if this one is not updatable. */
228 if ((vmaxlen == EXTCHK_SIZE_EVAL_INIT) && (check->envp[idx]))
229 return 0;
230
231 /* Instead of sending NOT_USED, sending an empty value is preferable */
232 if (strcmp(value, "NOT_USED") == 0) {
233 value = "";
234 }
235
236 len = strlen(envname) + 1;
237 if (vmaxlen == EXTCHK_SIZE_EVAL_INIT)
238 len += strlen(value);
239 else
240 len += vmaxlen;
241
242 if (!check->envp[idx])
243 check->envp[idx] = malloc(len + 1);
244
245 if (!check->envp[idx]) {
246 ha_alert("Failed to allocate memory for the environment variable '%s'. Aborting.\n", envname);
247 return 1;
248 }
249 ret = snprintf(check->envp[idx], len + 1, "%s=%s", envname, value);
250 if (ret < 0) {
251 ha_alert("Failed to store the environment variable '%s'. Reason : %s. Aborting.\n", envname, strerror(errno));
252 return 1;
253 }
254 else if (ret > len) {
255 ha_alert("Environment variable '%s' was truncated. Aborting.\n", envname);
256 return 1;
257 }
258 return 0;
259}
260
261int prepare_external_check(struct check *check)
262{
263 struct server *s = check->server;
264 struct proxy *px = s->proxy;
265 struct listener *listener = NULL, *l;
266 int i;
267 const char *path = px->check_path ? px->check_path : DEF_CHECK_PATH;
268 char buf[256];
Willy Tarreau973cf902022-05-13 15:58:35 +0200269 const char *svmode = NULL;
Willy Tarreaubcc67332020-06-05 15:31:31 +0200270
271 list_for_each_entry(l, &px->conf.listeners, by_fe)
272 /* Use the first INET, INET6 or UNIX listener */
Willy Tarreau37159062020-08-27 07:48:42 +0200273 if (l->rx.addr.ss_family == AF_INET ||
274 l->rx.addr.ss_family == AF_INET6 ||
275 l->rx.addr.ss_family == AF_UNIX) {
Willy Tarreaubcc67332020-06-05 15:31:31 +0200276 listener = l;
277 break;
278 }
279
280 check->curpid = NULL;
Tim Duesterhuse52b6e52020-09-12 20:26:43 +0200281 check->envp = calloc((EXTCHK_SIZE + 1), sizeof(*check->envp));
Willy Tarreaubcc67332020-06-05 15:31:31 +0200282 if (!check->envp) {
283 ha_alert("Failed to allocate memory for environment variables. Aborting\n");
284 goto err;
285 }
286
Tim Duesterhuse52b6e52020-09-12 20:26:43 +0200287 check->argv = calloc(6, sizeof(*check->argv));
Willy Tarreaubcc67332020-06-05 15:31:31 +0200288 if (!check->argv) {
289 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
290 goto err;
291 }
292
293 check->argv[0] = px->check_command;
294
295 if (!listener) {
296 check->argv[1] = strdup("NOT_USED");
297 check->argv[2] = strdup("NOT_USED");
298 }
Willy Tarreau37159062020-08-27 07:48:42 +0200299 else if (listener->rx.addr.ss_family == AF_INET ||
300 listener->rx.addr.ss_family == AF_INET6) {
301 addr_to_str(&listener->rx.addr, buf, sizeof(buf));
Willy Tarreaubcc67332020-06-05 15:31:31 +0200302 check->argv[1] = strdup(buf);
Willy Tarreau37159062020-08-27 07:48:42 +0200303 port_to_str(&listener->rx.addr, buf, sizeof(buf));
Willy Tarreaubcc67332020-06-05 15:31:31 +0200304 check->argv[2] = strdup(buf);
305 }
Willy Tarreau37159062020-08-27 07:48:42 +0200306 else if (listener->rx.addr.ss_family == AF_UNIX) {
Willy Tarreaubcc67332020-06-05 15:31:31 +0200307 const struct sockaddr_un *un;
308
Willy Tarreau37159062020-08-27 07:48:42 +0200309 un = (struct sockaddr_un *)&listener->rx.addr;
Willy Tarreaubcc67332020-06-05 15:31:31 +0200310 check->argv[1] = strdup(un->sun_path);
311 check->argv[2] = strdup("NOT_USED");
312 }
313 else {
314 ha_alert("Starting [%s:%s] check: unsupported address family.\n", px->id, s->id);
315 goto err;
316 }
317
Willy Tarreauc7edc982022-04-14 19:49:50 +0200318 /* args 3 and 4 are the address, they're replaced on each check */
Willy Tarreaubcc67332020-06-05 15:31:31 +0200319 check->argv[3] = calloc(EXTCHK_SIZE_ADDR, sizeof(*check->argv[3]));
320 check->argv[4] = calloc(EXTCHK_SIZE_UINT, sizeof(*check->argv[4]));
Willy Tarreaubcc67332020-06-05 15:31:31 +0200321
322 for (i = 0; i < 5; i++) {
323 if (!check->argv[i]) {
324 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
325 goto err;
326 }
327 }
328
329 EXTCHK_SETENV(check, EXTCHK_PATH, path, err);
330 /* Add proxy environment variables */
331 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_NAME, px->id, err);
332 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ID, ultoa_r(px->uuid, buf, sizeof(buf)), err);
333 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ADDR, check->argv[1], err);
334 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_PORT, check->argv[2], err);
335 /* Add server environment variables */
336 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_NAME, s->id, err);
337 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ID, ultoa_r(s->puid, buf, sizeof(buf)), err);
338 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3], err);
339 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4], err);
340 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_MAXCONN, ultoa_r(s->maxconn, buf, sizeof(buf)), err);
341 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)), err);
Willy Tarreau973cf902022-05-13 15:58:35 +0200342 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_SSL, s->use_ssl ? "1" : "0", err);
343
344 switch (px->mode) {
345 case PR_MODE_CLI: svmode = "cli"; break;
346 case PR_MODE_SYSLOG: svmode = "syslog"; break;
347 case PR_MODE_PEERS: svmode = "peers"; break;
348 case PR_MODE_HTTP: svmode = (s->mux_proto) ? s->mux_proto->token.ptr : "h1"; break;
349 case PR_MODE_TCP: svmode = "tcp"; break;
350 /* all valid cases must be enumerated above, below is to avoid a warning */
351 case PR_MODES: svmode = "?"; break;
352 }
353 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_PROTO, svmode, err);
Willy Tarreaubcc67332020-06-05 15:31:31 +0200354
355 /* Ensure that we don't leave any hole in check->envp */
356 for (i = 0; i < EXTCHK_SIZE; i++)
357 if (!check->envp[i])
358 EXTCHK_SETENV(check, i, "", err);
359
360 return 1;
361err:
362 if (check->envp) {
363 for (i = 0; i < EXTCHK_SIZE; i++)
364 free(check->envp[i]);
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100365 ha_free(&check->envp);
Willy Tarreaubcc67332020-06-05 15:31:31 +0200366 }
367
368 if (check->argv) {
369 for (i = 1; i < 5; i++)
370 free(check->argv[i]);
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100371 ha_free(&check->argv);
Willy Tarreaubcc67332020-06-05 15:31:31 +0200372 }
373 return 0;
374}
375
376/*
377 * establish a server health-check that makes use of a process.
378 *
379 * It can return one of :
380 * - SF_ERR_NONE if everything's OK
381 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
382 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
383 *
384 * Blocks and then unblocks SIGCHLD
385 */
386static int connect_proc_chk(struct task *t)
387{
388 char buf[256];
389 struct check *check = t->context;
390 struct server *s = check->server;
391 struct proxy *px = s->proxy;
392 int status;
393 pid_t pid;
394
395 status = SF_ERR_RESOURCE;
396
397 block_sigchld();
398
399 pid = fork();
400 if (pid < 0) {
401 ha_alert("Failed to fork process for external health check%s: %s. Aborting.\n",
402 (global.tune.options & GTUNE_INSECURE_FORK) ?
403 "" : " (likely caused by missing 'insecure-fork-wanted')",
404 strerror(errno));
405 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
406 goto out;
407 }
408 if (pid == 0) {
409 /* Child */
410 extern char **environ;
411 struct rlimit limit;
412 int fd;
413
414 /* close all FDs. Keep stdin/stdout/stderr in verbose mode */
415 fd = (global.mode & (MODE_QUIET|MODE_VERBOSE)) == MODE_QUIET ? 0 : 3;
416
417 my_closefrom(fd);
418
419 /* restore the initial FD limits */
420 limit.rlim_cur = rlim_fd_cur_at_boot;
421 limit.rlim_max = rlim_fd_max_at_boot;
Willy Tarreauc06557c2022-09-22 16:12:08 +0200422 if (raise_rlim_nofile(NULL, &limit) != 0) {
Willy Tarreaubcc67332020-06-05 15:31:31 +0200423 getrlimit(RLIMIT_NOFILE, &limit);
424 ha_warning("External check: failed to restore initial FD limits (cur=%u max=%u), using cur=%u max=%u\n",
425 rlim_fd_cur_at_boot, rlim_fd_max_at_boot,
426 (unsigned int)limit.rlim_cur, (unsigned int)limit.rlim_max);
427 }
428
Willy Tarreau39dcd1f2023-11-23 16:48:48 +0100429 if (global.external_check < 2) {
430 /* fresh new env for each check */
431 environ = check->envp;
432 }
Willy Tarreaubcc67332020-06-05 15:31:31 +0200433
434 /* Update some environment variables and command args: curconn, server addr and server port */
Willy Tarreaub3250a22020-10-24 13:07:39 +0200435 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)), fail);
Willy Tarreaubcc67332020-06-05 15:31:31 +0200436
Willy Tarreaucef08c22022-04-14 19:51:02 +0200437 if (s->addr.ss_family == AF_UNIX) {
438 const struct sockaddr_un *un = (struct sockaddr_un *)&s->addr;
439 strlcpy2(check->argv[3], un->sun_path, EXTCHK_SIZE_ADDR);
440 memcpy(check->argv[4], "NOT_USED", 9);
441 } else {
442 addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
443 *check->argv[4] = 0; // just in case the address family changed
444 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
445 snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
446 }
Willy Tarreaubcc67332020-06-05 15:31:31 +0200447
Willy Tarreaucef08c22022-04-14 19:51:02 +0200448 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3], fail);
Willy Tarreaub3250a22020-10-24 13:07:39 +0200449 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4], fail);
Willy Tarreaubcc67332020-06-05 15:31:31 +0200450
Willy Tarreau39dcd1f2023-11-23 16:48:48 +0100451 if (global.external_check >= 2) {
452 /* environment is preserved, let's merge new vars */
453 int i;
454
455 for (i = 0; check->envp[i] && *check->envp[i]; i++) {
456 char *delim = strchr(check->envp[i], '=');
457 if (!delim)
458 continue;
459 *(delim++) = 0;
460 if (setenv(check->envp[i], delim, 1) != 0)
461 goto fail;
462 }
463 }
Willy Tarreaubcc67332020-06-05 15:31:31 +0200464 haproxy_unblock_signals();
465 execvp(px->check_command, check->argv);
466 ha_alert("Failed to exec process for external health check: %s. Aborting.\n",
467 strerror(errno));
Willy Tarreaub3250a22020-10-24 13:07:39 +0200468 fail:
Willy Tarreaubcc67332020-06-05 15:31:31 +0200469 exit(-1);
470 }
471
472 /* Parent */
473 if (check->result == CHK_RES_UNKNOWN) {
474 if (pid_list_add(pid, t) != NULL) {
475 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
476
477 if (px->timeout.check && px->timeout.connect) {
478 int t_con = tick_add(now_ms, px->timeout.connect);
479 t->expire = tick_first(t->expire, t_con);
480 }
481 status = SF_ERR_NONE;
482 goto out;
483 }
484 else {
485 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
486 }
487 kill(pid, SIGTERM); /* process creation error */
488 }
489 else
490 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
491
492out:
493 unblock_sigchld();
494 return status;
495}
496
497/*
498 * manages a server health-check that uses an external process. Returns
499 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
500 *
501 * Please do NOT place any return statement in this function and only leave
502 * via the out_unlock label.
503 */
Willy Tarreau144f84a2021-03-02 16:09:26 +0100504struct task *process_chk_proc(struct task *t, void *context, unsigned int state)
Willy Tarreaubcc67332020-06-05 15:31:31 +0200505{
506 struct check *check = context;
507 struct server *s = check->server;
508 int rv;
509 int ret;
510 int expired = tick_is_expired(t->expire, now_ms);
511
512 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
513 if (!(check->state & CHK_ST_INPROGRESS)) {
514 /* no check currently running */
515 if (!expired) /* woke up too early */
516 goto out_unlock;
517
518 /* we don't send any health-checks when the proxy is
519 * stopped, the server should not be checked or the check
520 * is disabled.
521 */
522 if (((check->state & (CHK_ST_ENABLED | CHK_ST_PAUSED)) != CHK_ST_ENABLED) ||
Christopher Fauletdfd10ab2021-10-06 14:24:19 +0200523 (s->proxy->flags & (PR_FL_DISABLED|PR_FL_STOPPED)))
Willy Tarreaubcc67332020-06-05 15:31:31 +0200524 goto reschedule;
525
526 /* we'll initiate a new check */
527 set_server_check_status(check, HCHK_STATUS_START, NULL);
528
529 check->state |= CHK_ST_INPROGRESS;
530
531 ret = connect_proc_chk(t);
532 if (ret == SF_ERR_NONE) {
533 /* the process was forked, we allow up to min(inter,
534 * timeout.connect) for it to report its status, but
535 * only when timeout.check is set as it may be to short
536 * for a full check otherwise.
537 */
538 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
539
540 if (s->proxy->timeout.check && s->proxy->timeout.connect) {
541 int t_con = tick_add(now_ms, s->proxy->timeout.connect);
542 t->expire = tick_first(t->expire, t_con);
543 }
Willy Tarreaueed39112022-06-15 17:20:16 +0200544 task_set_thread(t, tid);
Willy Tarreaubcc67332020-06-05 15:31:31 +0200545 goto reschedule;
546 }
547
548 /* here, we failed to start the check */
549
550 check->state &= ~CHK_ST_INPROGRESS;
551 check_notify_failure(check);
552
553 /* we allow up to min(inter, timeout.connect) for a connection
554 * to establish but only when timeout.check is set
555 * as it may be to short for a full check otherwise
556 */
557 while (tick_is_expired(t->expire, now_ms)) {
558 int t_con;
559
560 t_con = tick_add(t->expire, s->proxy->timeout.connect);
561 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
562
563 if (s->proxy->timeout.check)
564 t->expire = tick_first(t->expire, t_con);
565 }
566 }
567 else {
568 /* there was a test running.
569 * First, let's check whether there was an uncaught error,
570 * which can happen on connect timeout or error.
571 */
572 if (check->result == CHK_RES_UNKNOWN) {
573 /* good connection is enough for pure TCP check */
574 struct pid_list *elem = check->curpid;
575 int status = HCHK_STATUS_UNKNOWN;
576
577 if (elem->exited) {
578 status = elem->status; /* Save in case the process exits between use below */
579 if (!WIFEXITED(status))
580 check->code = -1;
581 else
582 check->code = WEXITSTATUS(status);
583 if (!WIFEXITED(status) || WEXITSTATUS(status))
584 status = HCHK_STATUS_PROCERR;
585 else
586 status = HCHK_STATUS_PROCOK;
587 } else if (expired) {
588 status = HCHK_STATUS_PROCTOUT;
589 ha_warning("kill %d\n", (int)elem->pid);
590 kill(elem->pid, SIGTERM);
591 }
592 set_server_check_status(check, status, NULL);
593 }
594
595 if (check->result == CHK_RES_FAILED) {
596 /* a failure or timeout detected */
597 check_notify_failure(check);
598 }
599 else if (check->result == CHK_RES_CONDPASS) {
600 /* check is OK but asks for stopping mode */
601 check_notify_stopping(check);
602 }
603 else if (check->result == CHK_RES_PASSED) {
604 /* a success was detected */
605 check_notify_success(check);
606 }
Willy Tarreaueed39112022-06-15 17:20:16 +0200607 task_set_thread(t, 0);
Willy Tarreaubcc67332020-06-05 15:31:31 +0200608 check->state &= ~CHK_ST_INPROGRESS;
609
610 pid_list_del(check->curpid);
611
612 rv = 0;
613 if (global.spread_checks > 0) {
614 rv = srv_getinter(check) * global.spread_checks / 100;
Willy Tarreaua840b4a2022-10-12 21:48:17 +0200615 rv -= (int) (2 * rv * (statistical_prng() / 4294967295.0));
Willy Tarreaubcc67332020-06-05 15:31:31 +0200616 }
617 t->expire = tick_add(now_ms, MS_TO_TICKS(srv_getinter(check) + rv));
618 }
619
620 reschedule:
621 while (tick_is_expired(t->expire, now_ms))
622 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
623
624 out_unlock:
625 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
626 return t;
627}
628
629/* Parses the "external-check" proxy keyword */
630int proxy_parse_extcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +0100631 const struct proxy *defpx, const char *file, int line,
Willy Tarreaubcc67332020-06-05 15:31:31 +0200632 char **errmsg)
633{
634 int cur_arg, ret = 0;
635
636 cur_arg = 1;
637 if (!*(args[cur_arg])) {
638 memprintf(errmsg, "missing argument after '%s'.\n", args[0]);
639 goto error;
640 }
641
642 if (strcmp(args[cur_arg], "command") == 0) {
643 if (too_many_args(2, args, errmsg, NULL))
644 goto error;
645 if (!*(args[cur_arg+1])) {
646 memprintf(errmsg, "missing argument after '%s'.", args[cur_arg]);
647 goto error;
648 }
649 free(curpx->check_command);
650 curpx->check_command = strdup(args[cur_arg+1]);
651 }
652 else if (strcmp(args[cur_arg], "path") == 0) {
653 if (too_many_args(2, args, errmsg, NULL))
654 goto error;
655 if (!*(args[cur_arg+1])) {
656 memprintf(errmsg, "missing argument after '%s'.", args[cur_arg]);
657 goto error;
658 }
659 free(curpx->check_path);
660 curpx->check_path = strdup(args[cur_arg+1]);
661 }
662 else {
663 memprintf(errmsg, "'%s' only supports 'command' and 'path'. but got '%s'.",
664 args[0], args[1]);
665 goto error;
666 }
667
668 ret = (*errmsg != NULL); /* Handle warning */
669 return ret;
670
671error:
672 return -1;
673}
674
Willy Tarreau220fd702021-02-12 12:07:38 +0100675int proxy_parse_external_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Willy Tarreaubcc67332020-06-05 15:31:31 +0200676 const char *file, int line)
677{
678 int err_code = 0;
679
680 curpx->options2 &= ~PR_O2_CHK_ANY;
681 curpx->options2 |= PR_O2_EXT_CHK;
682 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
683 goto out;
684
685 out:
686 return err_code;
687}
688
689static struct cfg_kw_list cfg_kws = {ILH, {
690 { CFG_LISTEN, "external-check", proxy_parse_extcheck },
691 { 0, NULL, NULL },
692}};
693
694INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);