Core: enable TCP keepalive for macOS and accepted sockets.

For TCP keepalive mechanism, SO_KEEPALIVE is inherited by a accepted
socket from the listening socket on most UNIX-like OS's, whereas
TCP_KEEP* options are only inherited on Linux, FreeBSD, or DragonFlyBSD.

This PR does two things:

1. Set TCP keepalive options on accepted sockets in addition to the
listening socket when the OS is not one of Linux, FreeBSD, or DragonFlyBSD.

2. Enable TCP keepalive on macOS by detecting and setting TCP_KEEPALIVE
along with TCP_KEEPINTVL and TCP_KEEPCNT on the accepted sockets.

Fixes #336

https://man7.org/linux/man-pages/man7/tcp.7.html
https://man.freebsd.org/cgi/man.cgi?query=tcp
https://groups.google.com/g/erlang-programming/c/vCI3PCZuj9k/m/PKnFAL5ZBQAJ
This commit is contained in:
Andy Pan 2024-11-19 22:34:17 +08:00
parent f3542500b6
commit 744629c1eb
8 changed files with 155 additions and 57 deletions

View File

@ -118,3 +118,24 @@ ngx_feature_libs=
ngx_feature_test="int32_t lock = 0;
if (!OSAtomicCompareAndSwap32Barrier(0, 1, &lock)) return 1"
. auto/feature
# TCP keepalive
#
# Darwin way to set TCP keepalive tunables, appeared in OS X Mavericks 10.9.
# Tunable values are set on the accepted socket.
NGX_TCP_KEEPALIVE_CHECKED=YES
ngx_feature="TCP_KEEPALIVE"
ngx_feature_name="NGX_HAVE_KEEPALIVE_TUNABLE"
ngx_feature_run=yes
ngx_feature_incs="#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>"
ngx_feature_path=
ngx_feature_libs=
ngx_feature_test="setsockopt(0, IPPROTO_TCP, TCP_KEEPALIVE, NULL, 0);
setsockopt(0, IPPROTO_TCP, TCP_KEEPINTVL, NULL, 0);
setsockopt(0, IPPROTO_TCP, TCP_KEEPCNT, NULL, 0)"
. auto/feature

View File

@ -90,12 +90,14 @@ EVENT_DEPS="src/event/ngx_event.h \
src/event/ngx_event_posted.h \
src/event/ngx_event_connect.h \
src/event/ngx_event_pipe.h \
src/event/ngx_event_tcp.h \
src/event/ngx_event_udp.h"
EVENT_SRCS="src/event/ngx_event.c \
src/event/ngx_event_timer.c \
src/event/ngx_event_posted.c \
src/event/ngx_event_accept.c \
src/event/ngx_event_tcp.c \
src/event/ngx_event_udp.c \
src/event/ngx_event_connect.c \
src/event/ngx_event_pipe.c"

View File

@ -508,18 +508,20 @@ ngx_feature_test="setsockopt(0, IPPROTO_TCP, TCP_DEFER_ACCEPT, NULL, 0)"
. auto/feature
ngx_feature="TCP_KEEPIDLE"
ngx_feature_name="NGX_HAVE_KEEPALIVE_TUNABLE"
ngx_feature_run=no
ngx_feature_incs="#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>"
ngx_feature_path=
ngx_feature_libs=
ngx_feature_test="setsockopt(0, IPPROTO_TCP, TCP_KEEPIDLE, NULL, 0);
setsockopt(0, IPPROTO_TCP, TCP_KEEPINTVL, NULL, 0);
setsockopt(0, IPPROTO_TCP, TCP_KEEPCNT, NULL, 0)"
. auto/feature
if [ -z "$NGX_TCP_KEEPALIVE_CHECKED" ]; then
ngx_feature="TCP_KEEPIDLE"
ngx_feature_name="NGX_HAVE_KEEPALIVE_TUNABLE"
ngx_feature_run=no
ngx_feature_incs="#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>"
ngx_feature_path=
ngx_feature_libs=
ngx_feature_test="setsockopt(0, IPPROTO_TCP, TCP_KEEPIDLE, NULL, 0);
setsockopt(0, IPPROTO_TCP, TCP_KEEPINTVL, NULL, 0);
setsockopt(0, IPPROTO_TCP, TCP_KEEPCNT, NULL, 0)"
. auto/feature
fi
ngx_feature="TCP_FASTOPEN"

View File

@ -764,52 +764,14 @@ ngx_configure_listening_sockets(ngx_cycle_t *cycle)
}
#if (NGX_HAVE_KEEPALIVE_TUNABLE)
if (ls[i].keepidle) {
value = ls[i].keepidle;
#if (NGX_KEEPALIVE_FACTOR)
value *= NGX_KEEPALIVE_FACTOR;
#endif
if (setsockopt(ls[i].fd, IPPROTO_TCP, TCP_KEEPIDLE,
(const void *) &value, sizeof(int))
== -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
"setsockopt(TCP_KEEPIDLE, %d) %V failed, ignored",
value, &ls[i].addr_text);
}
if (ngx_tcp_keepalive(ls[i].fd, ls[i].keepidle,
ls[i].keepintvl, ls[i].keepcnt) == -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
"ngx_tcp_keepalive(%d, %d, %d, %d) %V failed, ignored",
ls[i].fd, ls[i].keepidle, ls[i].keepintvl, ls[i].keepcnt,
&ls[i].addr_text);
}
if (ls[i].keepintvl) {
value = ls[i].keepintvl;
#if (NGX_KEEPALIVE_FACTOR)
value *= NGX_KEEPALIVE_FACTOR;
#endif
if (setsockopt(ls[i].fd, IPPROTO_TCP, TCP_KEEPINTVL,
(const void *) &value, sizeof(int))
== -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
"setsockopt(TCP_KEEPINTVL, %d) %V failed, ignored",
value, &ls[i].addr_text);
}
}
if (ls[i].keepcnt) {
if (setsockopt(ls[i].fd, IPPROTO_TCP, TCP_KEEPCNT,
(const void *) &ls[i].keepcnt, sizeof(int))
== -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
"setsockopt(TCP_KEEPCNT, %d) %V failed, ignored",
ls[i].keepcnt, &ls[i].addr_text);
}
}
#endif
#if (NGX_HAVE_SETFIB)

View File

@ -523,6 +523,7 @@ ngx_int_t ngx_send_lowat(ngx_connection_t *c, size_t lowat);
#include <ngx_event_timer.h>
#include <ngx_event_posted.h>
#include <ngx_event_tcp.h>
#include <ngx_event_udp.h>
#if (NGX_WIN32)

View File

@ -156,6 +156,26 @@ ngx_event_accept(ngx_event_t *ev)
(void) ngx_atomic_fetch_add(ngx_stat_active, 1);
#endif
#if (NGX_HAVE_KEEPALIVE_TUNABLE) && \
!defined(__DragonFly__) && \
!defined(__FreeBSD__) && \
!defined(__linux__)
/*
* TCP keepalive options are not inherited from the listening socket
* on platforms other than Linux, FreeBSD, or DragonFlyBSD.
* We therefore need to set them on the accepted socket explicitly.
*/
if (ngx_tcp_keepalive(s, ls->keepidle,
ls->keepintvl, ls->keepcnt) == -1)
{
ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_socket_errno,
"ngx_tcp_keepalive(%d, %d, %d, %d) %V failed, ignored",
ls->fd, ls->keepidle, ls->keepintvl, ls->keepcnt,
&ls->addr_text);
}
#endif
c->pool = ngx_create_pool(ls->pool_size, ev->log);
if (c->pool == NULL) {
ngx_close_accepted_connection(c);

73
src/event/ngx_event_tcp.c Normal file
View File

@ -0,0 +1,73 @@
/*
* Copyright (C) Andy Pan
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
int
ngx_tcp_keepalive(ngx_socket_t s, int idle, int interval, int count)
{
#if (NGX_HAVE_KEEPALIVE_TUNABLE)
int sockval;
if (idle < 1 || interval < 1 || count < 1) {
return NGX_ERROR;
}
sockval = idle;
#if (NGX_KEEPALIVE_FACTOR)
sockval *= NGX_KEEPALIVE_FACTOR;
#endif
#ifdef TCP_KEEPIDLE
if (setsockopt(s, IPPROTO_TCP, TCP_KEEPIDLE,
(const void *) &sockval, sizeof(int))
== -1)
{
return NGX_ERROR;
}
#elif defined(TCP_KEEPALIVE)
/* Darwin/macOS uses TCP_KEEPALIVE in place of TCP_KEEPIDLE. */
if (setsockopt(s, IPPROTO_TCP, TCP_KEEPALIVE,
(const void *) &sockval, sizeof(int))
== -1)
{
return NGX_ERROR;
}
#endif
sockval = interval;
#if (NGX_KEEPALIVE_FACTOR)
sockval *= NGX_KEEPALIVE_FACTOR;
#endif
if (setsockopt(s, IPPROTO_TCP, TCP_KEEPINTVL,
(const void *) &sockval, sizeof(int))
== -1)
{
return NGX_ERROR;
}
sockval = count;
if (setsockopt(s, IPPROTO_TCP, TCP_KEEPCNT,
(const void *) &sockval, sizeof(int))
== -1)
{
return NGX_ERROR;
}
return NGX_OK;
#else /* !(NGX_HAVE_KEEPALIVE_TUNABLE) */
return NGX_ERROR;
#endif
}

17
src/event/ngx_event_tcp.h Normal file
View File

@ -0,0 +1,17 @@
/*
* Copyright (C) Andy Pan
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_TCP_H_INCLUDED_
#define _NGX_EVENT_TCP_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
int ngx_tcp_keepalive(ngx_socket_t s, int idle, int interval, int count);
#endif /* _NGX_EVENT_TCP_H_INCLUDED_ */