2 * Copyright (C) 2017 Jonathan Rajotte <jonathan.rajotte-julien@efficios.com>
4 * SPDX-License-Identifier: GPL-2.0-only
9 #include <netinet/tcp.h>
11 #include <sys/socket.h>
14 #include <common/compat/getenv.h>
15 #include <common/time.h>
16 #include <common/defaults.h>
17 #include <common/config/session-config.h>
19 #include "tcp_keep_alive.h"
21 #define SOLARIS_IDLE_TIME_MIN_S 10
22 #define SOLARIS_IDLE_TIME_MAX_S 864000 /* 10 days */
23 #define SOLARIS_ABORT_THRESHOLD_MIN_S 1
24 #define SOLARIS_ABORT_THRESHOLD_MAX_S 480 /* 8 minutes */
26 /* Per-platform definitions of TCP socket options. */
27 #if defined (__linux__)
29 #define COMPAT_TCP_LEVEL SOL_TCP
30 #define COMPAT_TCP_ABORT_THRESHOLD 0 /* Does not exist on linux. */
31 #define COMPAT_TCP_KEEPIDLE TCP_KEEPIDLE
32 #define COMPAT_TCP_KEEPINTVL TCP_KEEPINTVL
33 #define COMPAT_TCP_KEEPCNT TCP_KEEPCNT
35 #elif defined (__sun__) /* ! defined (__linux__) */
37 #define COMPAT_TCP_LEVEL IPPROTO_TCP
39 #ifdef TCP_KEEPALIVE_THRESHOLD
40 #define COMPAT_TCP_KEEPIDLE TCP_KEEPALIVE_THRESHOLD
41 #else /* ! defined (TCP_KEEPALIVE_THRESHOLD) */
42 #define COMPAT_TCP_KEEPIDLE 0
43 #endif /* TCP_KEEPALIVE_THRESHOLD */
45 #ifdef TCP_KEEPALIVE_ABORT_THRESHOLD
46 #define COMPAT_TCP_ABORT_THRESHOLD TCP_KEEPALIVE_ABORT_THRESHOLD
47 #else /* ! defined (TCP_KEEPALIVE_ABORT_THRESHOLD) */
48 #define COMPAT_TCP_ABORT_THRESHOLD 0
49 #endif /* TCP_KEEPALIVE_ABORT_THRESHOLD */
51 #define COMPAT_TCP_KEEPINTVL 0 /* Does not exist on Solaris. */
52 #define COMPAT_TCP_KEEPCNT 0 /* Does not exist on Solaris. */
54 #else /* ! defined (__linux__) && ! defined (__sun__) */
56 #define COMPAT_TCP_LEVEL 0
57 #define COMPAT_TCP_ABORT_THRESHOLD 0
58 #define COMPAT_TCP_KEEPIDLE 0
59 #define COMPAT_TCP_KEEPINTVL 0
60 #define COMPAT_TCP_KEEPCNT 0
62 #endif /* ! defined (__linux__) && ! defined (__sun__) */
64 struct tcp_keep_alive_support
{
65 /* TCP keep-alive is supported by this platform. */
67 /* Overriding idle-time per socket is supported by this platform. */
68 bool idle_time_supported
;
70 * Overriding probe interval per socket is supported by this
73 bool probe_interval_supported
;
75 * Configuring max probe count per socket is supported by this
78 bool max_probe_count_supported
;
79 /* Overriding on a per-socket basis is supported by this platform. */
80 bool abort_threshold_supported
;
83 struct tcp_keep_alive_config
{
84 /* Maps to the LTTNG_RELAYD_TCP_KEEP_ALIVE_ENV environment variable. */
87 * Maps to the LTTNG_RELAYD_TCP_KEEP_ALIVE_IDLE_TIME_ENV environment
92 * Maps to the LTTNG_RELAYD_TCP_KEEP_ALIVE_PROBE_INTERVAL_ENV
93 * environment variable.
97 * Maps to the LTTNG_RELAYD_TCP_KEEP_ALIVE_MAX_PROBE_COUNT_ENV
98 * environment variable.
102 * Maps to the LTTNG_RELAYD_TCP_KEEP_ALIVE_ABORT_THRESHOLD_ENV
103 * environment variable.
108 static struct tcp_keep_alive_config config
= {
111 .probe_interval
= -1,
112 .max_probe_count
= -1,
113 .abort_threshold
= -1
116 static struct tcp_keep_alive_support support
= {
118 .idle_time_supported
= false,
119 .probe_interval_supported
= false,
120 .max_probe_count_supported
= false,
121 .abort_threshold_supported
= false
125 * Common parser for string to positive int conversion where the value must be
126 * in range [-1, INT_MAX].
128 * Returns -2 on invalid value.
131 int get_env_int(const char *env_var
,
139 tmp
= strtol(value
, &endptr
, 0);
141 ERR("%s cannot be parsed.", env_var
);
142 PERROR("errno for previous parsing failure");
147 if (endptr
== value
|| *endptr
!= '\0') {
148 ERR("%s is not a valid number", env_var
);
154 ERR("%s must be greater or equal to -1", env_var
);
159 ERR("%s is too big. Maximum value is %d", env_var
, INT_MAX
);
170 * Per-platform implementation of tcp_keep_alive_idle_time_modifier.
171 * Returns -2 on invalid value.
176 int convert_idle_time(int value
)
181 if (value
== -1 || value
== 0) {
182 /* Use system defaults */
188 ERR("Invalid tcp keep-alive idle time (%i)", value
);
194 * Additional constraints for Solaris 11.
195 * Minimum 10s, maximum 10 days. Defined by
196 * https://docs.oracle.com/cd/E23824_01/html/821-1475/tcp-7p.html#REFMAN7tcp-7p
198 if ((value
< SOLARIS_IDLE_TIME_MIN_S
||
199 value
> SOLARIS_IDLE_TIME_MAX_S
)) {
200 ERR("%s must be comprised between %d and %d inclusively on Solaris",
201 DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_IDLE_TIME_ENV
,
202 SOLARIS_IDLE_TIME_MIN_S
,
203 SOLARIS_IDLE_TIME_MAX_S
);
208 /* On Solaris idle time is given in milliseconds. */
209 tmp_ms
= ((unsigned int) value
) * MSEC_PER_SEC
;
210 if ((value
!= 0 && (tmp_ms
/ ((unsigned int) value
)) != MSEC_PER_SEC
)
211 || tmp_ms
> INT_MAX
) {
213 const int max_value
= INT_MAX
/ MSEC_PER_SEC
;
215 ERR("%s is too big: maximum supported value is %d",
216 DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_IDLE_TIME_ENV
,
222 /* tmp_ms is >= 0 and <= INT_MAX. Cast is safe. */
228 #else /* ! defined(__sun__) */
231 int convert_idle_time(int value
)
236 #endif /* ! defined(__sun__) */
238 /* Per-platform support of tcp_keep_alive functionality. */
239 #if defined (__linux__)
242 void tcp_keep_alive_init_support(struct tcp_keep_alive_support
*support
)
244 support
->supported
= true;
245 support
->idle_time_supported
= true;
246 support
->probe_interval_supported
= true;
247 support
->max_probe_count_supported
= true;
248 /* Solaris specific */
249 support
->abort_threshold_supported
= false;
252 #elif defined(__sun__) /* ! defined (__linux__) */
255 void tcp_keep_alive_init_support(struct tcp_keep_alive_support
*support
)
257 support
->supported
= true;
258 #ifdef TCP_KEEPALIVE_THRESHOLD
259 support
->idle_time_supported
= true;
261 support
->idle_time_supported
= false;;
262 #endif /* TCP_KEEPALIVE_THRESHOLD */
265 * Solaris does not support either tcp_keepalive_probes or
266 * tcp_keepalive_intvl.
267 * Inferring a value for TCP_KEEP_ALIVE_ABORT_THRESHOLD using
268 * (tcp_keepalive_probes * tcp_keepalive_intvl) could yield a good
269 * alternative, but Solaris does not detail the algorithm used (such as
270 * constant time retry like Linux).
272 * Ignore those settings on Solaris 11. We prefer exposing an
273 * environment variable only used on Solaris for the abort threshold.
275 support
->probe_interval_supported
= false;
276 support
->max_probe_count_supported
= false;
277 #ifdef TCP_KEEPALIVE_ABORT_THRESHOLD
278 support
->abort_threshold_supported
= true;
280 support
->abort_threshold_supported
= false;
281 #endif /* TCP_KEEPALIVE_THRESHOLD */
284 #else /* ! defined(__sun__) && ! defined(__linux__) */
286 /* Assume nothing is supported on other platforms. */
288 void tcp_keep_alive_init_support(struct tcp_keep_alive_support
*support
)
290 support
->supported
= false;
291 support
->idle_time_supported
= false;
292 support
->probe_interval_supported
= false;
293 support
->max_probe_count_supported
= false;
294 support
->abort_threshold_supported
= false;
297 #endif /* ! defined(__sun__) && ! defined(__linux__) */
302 * Solaris specific modifier for abort threshold.
303 * Return -2 on error.
306 int convert_abort_threshold(int value
)
312 /* Use system defaults */
318 ERR("Invalid tcp keep-alive abort threshold (%i)", value
);
324 * Additional constraints for Solaris 11.
326 * Between 0 and 8 minutes.
327 * https://docs.oracle.com/cd/E19120-01/open.solaris/819-2724/fsvdh/index.html
329 * Restrict from 1 seconds to 8 minutes sice the 0 value goes against
330 * the purpose of dead peers detection by never timing out when probing.
331 * It does NOT mean that the connection times out immediately.
333 if ((value
< SOLARIS_ABORT_THRESHOLD_MIN_S
|| value
> SOLARIS_ABORT_THRESHOLD_MAX_S
)) {
334 ERR("%s must be comprised between %d and %d inclusively on Solaris",
335 DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_ABORT_THRESHOLD_ENV
,
336 SOLARIS_ABORT_THRESHOLD_MIN_S
,
337 SOLARIS_ABORT_THRESHOLD_MAX_S
);
342 /* Abort threshold is given in milliseconds. */
343 tmp_ms
= ((unsigned int) value
) * MSEC_PER_SEC
;
344 if ((value
!= 0 && (tmp_ms
/ ((unsigned int) value
)) != MSEC_PER_SEC
)
345 || tmp_ms
> INT_MAX
) {
347 const int max_value
= INT_MAX
/ MSEC_PER_SEC
;
349 ERR("%s is too big: maximum supported value is %d",
350 DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_ABORT_THRESHOLD_ENV
,
356 /* tmp_ms is >= 0 and <= INT_MAX. Cast is safe. */
365 int convert_abort_threshold(int value
)
370 #endif /* defined (__sun__) */
373 * Retrieve settings from environment variables and warn for settings not
374 * supported by the platform.
377 int tcp_keep_alive_init_config(struct tcp_keep_alive_support
*support
,
378 struct tcp_keep_alive_config
*config
)
383 value
= lttng_secure_getenv(DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_ENV
);
384 if (!support
->supported
) {
386 WARN("Using per-socket TCP keep-alive mechanism is not supported by this platform. Ignoring the %s environment variable.",
387 DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_ENV
);
389 config
->enabled
= false;
391 ret
= config_parse_value(value
);
392 if (ret
< 0 || ret
> 1) {
393 ERR("Invalid value for %s", DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_ENV
);
397 config
->enabled
= ret
;
399 DBG("TCP keep-alive mechanism %s", config
->enabled
? "enabled": "disabled");
401 /* Get value for tcp_keepalive_time in seconds. */
402 value
= lttng_secure_getenv(DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_IDLE_TIME_ENV
);
403 if (!support
->idle_time_supported
&& value
) {
404 WARN("Overriding the TCP keep-alive idle time threshold per-socket is not supported by this platform. Ignoring the %s environment variable.",
405 DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_IDLE_TIME_ENV
);
406 config
->idle_time
= -1;
408 int idle_time_platform
;
409 int idle_time_seconds
;
411 idle_time_seconds
= get_env_int(
412 DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_IDLE_TIME_ENV
,
414 if (idle_time_seconds
< -1) {
419 idle_time_platform
= convert_idle_time(idle_time_seconds
);
420 if (idle_time_platform
< -1) {
425 config
->idle_time
= idle_time_platform
;
426 DBG("Overriding %s to %d",
427 DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_IDLE_TIME_ENV
,
431 /* Get value for tcp_keepalive_intvl in seconds. */
432 value
= lttng_secure_getenv(
433 DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_PROBE_INTERVAL_ENV
);
434 if (!support
->probe_interval_supported
&& value
) {
435 WARN("Overriding the TCP keep-alive probe interval time per-socket is not supported by this platform. Ignoring the %s environment variable.",
436 DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_PROBE_INTERVAL_ENV
);
437 config
->probe_interval
= -1;
441 probe_interval
= get_env_int(DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_PROBE_INTERVAL_ENV
,
443 if (probe_interval
< -1) {
448 config
->probe_interval
= probe_interval
;
449 DBG("Overriding %s to %d",
450 DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_PROBE_INTERVAL_ENV
,
451 config
->probe_interval
);
454 /* Get value for tcp_keepalive_probes. */
455 value
= lttng_secure_getenv(DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_MAX_PROBE_COUNT_ENV
);
456 if (!support
->max_probe_count_supported
&& value
) {
457 WARN("Overriding the TCP keep-alive maximum probe count per-socket is not supported by this platform. Ignoring the %s environment variable.",
458 DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_MAX_PROBE_COUNT_ENV
);
459 config
->max_probe_count
= -1;
463 max_probe_count
= get_env_int(DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_MAX_PROBE_COUNT_ENV
,
465 if (max_probe_count
< -1) {
470 config
->max_probe_count
= max_probe_count
;
471 DBG("Overriding %s to %d",
472 DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_MAX_PROBE_COUNT_ENV
,
473 config
->max_probe_count
);
476 /* Get value for tcp_keepalive_abort_interval. */
477 value
= lttng_secure_getenv(
478 DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_ABORT_THRESHOLD_ENV
);
479 if (!support
->abort_threshold_supported
&& value
) {
480 WARN("Overriding the TCP keep-alive abort threshold per-socket is not supported by this platform. Ignoring the %s environment variable.",
481 DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_ABORT_THRESHOLD_ENV
);
482 config
->abort_threshold
= -1;
484 int abort_threshold_platform
;
485 int abort_threshold_seconds
;
487 abort_threshold_seconds
= get_env_int(
488 DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_MAX_PROBE_COUNT_ENV
,
490 if (abort_threshold_seconds
< -1) {
495 abort_threshold_platform
= convert_abort_threshold(
496 abort_threshold_seconds
);
497 if (abort_threshold_platform
< -1) {
502 config
->abort_threshold
= abort_threshold_platform
;
503 DBG("Overriding %s to %d",
504 DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_ABORT_THRESHOLD_ENV
,
505 config
->abort_threshold
);
514 /* Initialize the TCP keep-alive configuration. */
515 __attribute__((constructor
)) static
516 void tcp_keep_alive_init(void)
518 tcp_keep_alive_init_support(&support
);
519 (void) tcp_keep_alive_init_config(&support
, &config
);
523 * Set the socket options regarding TCP keep-alive.
526 int socket_apply_keep_alive_config(int socket_fd
)
532 if (!support
.supported
|| !config
.enabled
) {
537 DBG("TCP keep-alive enabled for socket %d", socket_fd
);
538 ret
= setsockopt(socket_fd
, SOL_SOCKET
, SO_KEEPALIVE
, &val
,
541 PERROR("setsockopt so_keepalive");
545 /* TCP keep-alive idle time */
546 if (support
.idle_time_supported
&& config
.idle_time
> 0) {
547 DBG("TCP keep-alive keep idle: %d enabled for socket %d", config
.idle_time
, socket_fd
);
548 ret
= setsockopt(socket_fd
, COMPAT_TCP_LEVEL
, COMPAT_TCP_KEEPIDLE
, &config
.idle_time
,
549 sizeof(config
.idle_time
));
551 PERROR("setsockopt TCP_KEEPIDLE");
555 /* TCP keep-alive probe interval */
556 if (support
.probe_interval_supported
&& config
.probe_interval
> 0) {
557 DBG("TCP keep-alive probe_interval: %d enabled for socket %d", config
.probe_interval
, socket_fd
);
558 ret
= setsockopt(socket_fd
, COMPAT_TCP_LEVEL
, COMPAT_TCP_KEEPINTVL
, &config
.probe_interval
,
559 sizeof(config
.probe_interval
));
561 PERROR("setsockopt TCP_KEEPINTVL");
566 /* TCP keep-alive max probe count */
567 if (support
.max_probe_count_supported
&& config
.max_probe_count
> 0) {
568 DBG("TCP keep-alive max_probe: %d enabled for socket %d", config
.max_probe_count
, socket_fd
);
569 ret
= setsockopt(socket_fd
, COMPAT_TCP_LEVEL
, COMPAT_TCP_KEEPCNT
, &config
.max_probe_count
,
570 sizeof(config
.max_probe_count
));
572 PERROR("setsockopt TCP_KEEPCNT");
577 /* TCP keep-alive abort threshold */
578 if (support
.abort_threshold_supported
&& config
.abort_threshold
> 0) {
579 DBG("TCP keep-alive abort threshold: %d enabled for socket %d", config
.abort_threshold
, socket_fd
);
580 ret
= setsockopt(socket_fd
, COMPAT_TCP_LEVEL
, COMPAT_TCP_ABORT_THRESHOLD
, &config
.abort_threshold
,
581 sizeof(config
.max_probe_count
));
583 PERROR("setsockopt TCP_KEEPALIVE_ABORT_THRESHOLD");