From 0fd8aae6e851b94123288ea67726ea68622c0c17 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Tue, 30 Dec 2025 16:53:22 +0100 Subject: [PATCH] Fix TCP_KEEPALIVE no inheriting for accepted sockets on MacOS --- ext/openssl/xp_ssl.c | 21 +++++++++++++------- main/network.c | 42 ++++++++++++++++++++++++++++++++++------ main/php_network.h | 20 ++++++++++++++++--- main/streams/xp_socket.c | 21 +++++++++++++------- 4 files changed, 81 insertions(+), 23 deletions(-) diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index 62929246a07..482715c7455 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -2219,25 +2219,32 @@ static int php_openssl_sockop_stat(php_stream *stream, php_stream_statbuf *ssb) static inline int php_openssl_tcp_sockop_accept(php_stream *stream, php_openssl_netstream_data_t *sock, php_stream_xport_param *xparam STREAMS_DC) /* {{{ */ { - bool nodelay = false; + php_sockvals sockvals = {0}; zval *tmpzval = NULL; xparam->outputs.client = NULL; - if (PHP_STREAM_CONTEXT(stream) && - (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_nodelay")) != NULL && - zend_is_true(tmpzval)) { - nodelay = true; + if (PHP_STREAM_CONTEXT(stream)) { + tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_nodelay"); + if (tmpzval != NULL && zend_is_true(tmpzval)) { + sockvals.mask |= PHP_SOCKVAL_TCP_NODELAY; + sockvals.tcp_nodelay = 1; + } + tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_keepidle"); + if (tmpzval != NULL && Z_TYPE_P(tmpzval) == IS_LONG) { + sockvals.mask |= PHP_SOCKVAL_TCP_KEEPIDLE; + sockvals.keepalive.keepidle = Z_LVAL_P(tmpzval); + } } - php_socket_t clisock = php_network_accept_incoming(sock->s.socket, + php_socket_t clisock = php_network_accept_incoming_ex(sock->s.socket, xparam->want_textaddr ? &xparam->outputs.textaddr : NULL, xparam->want_addr ? &xparam->outputs.addr : NULL, xparam->want_addr ? &xparam->outputs.addrlen : NULL, xparam->inputs.timeout, xparam->want_errortext ? &xparam->outputs.error_text : NULL, &xparam->outputs.error_code, - nodelay); + &sockvals); if (clisock != SOCK_ERR) { php_openssl_netstream_data_t *clisockdata = (php_openssl_netstream_data_t*) emalloc(sizeof(*clisockdata)); diff --git a/main/network.c b/main/network.c index 58f688a4fea..c4e2ab2e67d 100644 --- a/main/network.c +++ b/main/network.c @@ -801,15 +801,22 @@ PHPAPI int php_network_get_sock_name(php_socket_t sock, * version of the address will be emalloc'd and returned. * */ -/* {{{ php_network_accept_incoming */ -PHPAPI php_socket_t php_network_accept_incoming(php_socket_t srvsock, + /* Accept a client connection from a server socket, + * using an optional timeout. + * Returns the peer address in addr/addrlen (it will emalloc + * these, so be sure to efree the result). + * If you specify textaddr, a text-printable + * version of the address will be emalloc'd and returned. + * */ + +PHPAPI php_socket_t php_network_accept_incoming_ex(php_socket_t srvsock, zend_string **textaddr, struct sockaddr **addr, socklen_t *addrlen, struct timeval *timeout, zend_string **error_string, int *error_code, - int tcp_nodelay + php_sockvals *sockvals ) { php_socket_t clisock = -1; @@ -833,11 +840,19 @@ PHPAPI php_socket_t php_network_accept_incoming(php_socket_t srvsock, textaddr, addr, addrlen ); - if (tcp_nodelay) { #ifdef TCP_NODELAY + if (PHP_SOCKVAL_IS_SET(sockvals, PHP_SOCKVAL_TCP_NODELAY)) { + int tcp_nodelay = 1; setsockopt(clisock, IPPROTO_TCP, TCP_NODELAY, (char*)&tcp_nodelay, sizeof(tcp_nodelay)); -#endif } +#endif +#ifdef TCP_KEEPALIVE + /* MacOS does not inherit TCP_KEEPALIVE so it needs to be set */ + if (PHP_SOCKVAL_IS_SET(sockvals, PHP_SOCKVAL_TCP_KEEPIDLE)) { + setsockopt(clisock, IPPROTO_TCP, TCP_KEEPALIVE, + (char*)&sockvals->keepalive.keepidle, sizeof(sockvals->keepalive.keepidle)); + } +#endif } else { error = php_socket_errno(); } @@ -852,7 +867,22 @@ PHPAPI php_socket_t php_network_accept_incoming(php_socket_t srvsock, return clisock; } -/* }}} */ + +PHPAPI php_socket_t php_network_accept_incoming(php_socket_t srvsock, + zend_string **textaddr, + struct sockaddr **addr, + socklen_t *addrlen, + struct timeval *timeout, + zend_string **error_string, + int *error_code, + int tcp_nodelay + ) +{ + php_sockvals sockvals = { .mask = tcp_nodelay ? PHP_SOCKVAL_TCP_NODELAY : 0 }; + + return php_network_accept_incoming_ex(srvsock, textaddr, addr, addrlen, timeout, error_string, + error_code, &sockvals); +} /* Connect to a remote host using an interruptible connect with optional timeout. * Optionally, the connect can be made asynchronously, which will implicitly diff --git a/main/php_network.h b/main/php_network.h index 08d6bbc140c..db449ae35ff 100644 --- a/main/php_network.h +++ b/main/php_network.h @@ -267,12 +267,16 @@ typedef struct { } php_sockaddr_storage; #endif -#define PHP_SOCKVAL_TCP_KEEPIDLE (1 << 0) -#define PHP_SOCKVAL_TCP_KEEPCNT (1 << 1) -#define PHP_SOCKVAL_TCP_KEEPINTVL (1 << 2) +#define PHP_SOCKVAL_TCP_NODELAY (1 << 0) +#define PHP_SOCKVAL_TCP_KEEPIDLE (1 << 1) +#define PHP_SOCKVAL_TCP_KEEPCNT (1 << 2) +#define PHP_SOCKVAL_TCP_KEEPINTVL (1 << 3) + +#define PHP_SOCKVAL_IS_SET(sockvals, opt) (sockvals->mask & opt) typedef struct { unsigned int mask; + int tcp_nodelay; struct { int keepidle; int keepcnt; @@ -313,6 +317,16 @@ PHPAPI php_socket_t php_network_bind_socket_to_local_addr(const char *host, unsi int socktype, long sockopts, zend_string **error_string, int *error_code ); +PHPAPI php_socket_t php_network_accept_incoming_ex(php_socket_t srvsock, + zend_string **textaddr, + struct sockaddr **addr, + socklen_t *addrlen, + struct timeval *timeout, + zend_string **error_string, + int *error_code, + php_sockvals *sockvals + ); + PHPAPI php_socket_t php_network_accept_incoming(php_socket_t srvsock, zend_string **textaddr, struct sockaddr **addr, diff --git a/main/streams/xp_socket.c b/main/streams/xp_socket.c index 969d364ffe1..7a7f007f918 100644 --- a/main/streams/xp_socket.c +++ b/main/streams/xp_socket.c @@ -964,25 +964,32 @@ out: static inline int php_tcp_sockop_accept(php_stream *stream, php_netstream_data_t *sock, php_stream_xport_param *xparam STREAMS_DC) { - bool nodelay = 0; + php_sockvals sockvals = {0}; zval *tmpzval = NULL; xparam->outputs.client = NULL; - if ((NULL != PHP_STREAM_CONTEXT(stream)) && - (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_nodelay")) != NULL && - zend_is_true(tmpzval)) { - nodelay = 1; + if (PHP_STREAM_CONTEXT(stream)) { + tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_nodelay"); + if (tmpzval != NULL && zend_is_true(tmpzval)) { + sockvals.mask |= PHP_SOCKVAL_TCP_NODELAY; + sockvals.tcp_nodelay = 1; + } + tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_keepidle"); + if (tmpzval != NULL && Z_TYPE_P(tmpzval) == IS_LONG) { + sockvals.mask |= PHP_SOCKVAL_TCP_KEEPIDLE; + sockvals.keepalive.keepidle = Z_LVAL_P(tmpzval); + } } - php_socket_t clisock = php_network_accept_incoming(sock->socket, + php_socket_t clisock = php_network_accept_incoming_ex(sock->socket, xparam->want_textaddr ? &xparam->outputs.textaddr : NULL, xparam->want_addr ? &xparam->outputs.addr : NULL, xparam->want_addr ? &xparam->outputs.addrlen : NULL, xparam->inputs.timeout, xparam->want_errortext ? &xparam->outputs.error_text : NULL, &xparam->outputs.error_code, - nodelay); + &sockvals); if (clisock != SOCK_ERR) { php_netstream_data_t *clisockdata = (php_netstream_data_t*) emalloc(sizeof(*clisockdata));