Commit 87b2d5dd102 for php
commit 87b2d5dd10215a3751d68a35b8fca9f66fd987c1
Author: David Carlier <devnexen@gmail.com>
Date: Wed Sep 30 18:13:34 2026 +0100
streams: apply so_linger, so_rcvbuf and so_sndbuf to unix sockets
The unix and udg transports returned from the AF_UNIX branch before any
socket context option was read. The parsing happens before the transport
split now and the values are applied through the new
php_network_apply_sockvals().
Close GH-23584
diff --git a/NEWS b/NEWS
index e1bf6e4a3dd..124d6c51638 100644
--- a/NEWS
+++ b/NEWS
@@ -80,6 +80,10 @@ PHP NEWS
. Fixed bug GH-22051 (Inconsistent error reporting in SQLite3Result::reset()
and SQLite3Result::finalize()). (Ilia Alshanetsky)
+- Streams:
+ . Applied the so_linger, so_rcvbuf and so_sndbuf context options to unix
+ sockets. (David Carlier)
+
- Standard:
. Fixed bug #60110 (fclose(), file_put_contents(), copy() do not return false
properly). (Jakub Zelenka, Ilija Tovilo)
diff --git a/UPGRADING b/UPGRADING
index 10994928470..c46af00cfe2 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -33,6 +33,10 @@ PHP 8.7 UPGRADE NOTES
2. New Features
========================================
+- Streams:
+ . The so_linger, so_rcvbuf and so_sndbuf stream socket context options are
+ now also applied to unix and unix datagram sockets.
+
========================================
3. Changes in SAPI modules
========================================
diff --git a/ext/standard/tests/network/so_rcvbuf_sndbuf_udg.phpt b/ext/standard/tests/network/so_rcvbuf_sndbuf_udg.phpt
new file mode 100644
index 00000000000..8b7528d79a2
--- /dev/null
+++ b/ext/standard/tests/network/so_rcvbuf_sndbuf_udg.phpt
@@ -0,0 +1,58 @@
+--TEST--
+stream_socket_server() SO_RCVBUF and SO_SNDBUF context options test with unix datagram sockets
+--EXTENSIONS--
+sockets
+--SKIPIF--
+<?php
+if (array_search("udg", stream_get_transports()) === false) {
+ die('skip No support for UNIX domain sockets.');
+}
+?>
+--FILE--
+<?php
+function buffers($stream): array {
+ $sock = socket_import_stream($stream);
+ return [
+ socket_get_option($sock, SOL_SOCKET, SO_RCVBUF),
+ socket_get_option($sock, SOL_SOCKET, SO_SNDBUF),
+ ];
+}
+
+$control_path = sys_get_temp_dir() . '/' . uniqid('so_buf_control_') . '.sock';
+$server_path = sys_get_temp_dir() . '/' . uniqid('so_buf_server_') . '.sock';
+
+$control = stream_socket_server("udg://$control_path", $errno, $errstr, STREAM_SERVER_BIND);
+
+if (!$control) {
+ die('Unable to create server');
+}
+
+[$rcvbuf, $sndbuf] = buffers($control);
+
+// Shrinking is always honoured, while growing may be capped by the system maximum.
+$context = stream_context_create(['socket' => [
+ 'so_rcvbuf' => intdiv($rcvbuf, 4),
+ 'so_sndbuf' => intdiv($sndbuf, 4),
+]]);
+
+$server = stream_socket_server("udg://$server_path", $errno, $errstr, STREAM_SERVER_BIND, $context);
+
+if (!$server) {
+ die('Unable to create server');
+}
+
+[$server_rcvbuf, $server_sndbuf] = buffers($server);
+echo "Server buffers", PHP_EOL;
+var_dump($server_rcvbuf < $rcvbuf);
+var_dump($server_sndbuf < $sndbuf);
+
+fclose($server);
+fclose($control);
+unlink($server_path);
+unlink($control_path);
+
+?>
+--EXPECT--
+Server buffers
+bool(true)
+bool(true)
diff --git a/ext/standard/tests/network/so_rcvbuf_sndbuf_unix.phpt b/ext/standard/tests/network/so_rcvbuf_sndbuf_unix.phpt
new file mode 100644
index 00000000000..962fb4a1f6a
--- /dev/null
+++ b/ext/standard/tests/network/so_rcvbuf_sndbuf_unix.phpt
@@ -0,0 +1,108 @@
+--TEST--
+stream_socket_server() and stream_socket_client() SO_RCVBUF and SO_SNDBUF context options test with unix sockets
+--EXTENSIONS--
+sockets
+--SKIPIF--
+<?php
+if (array_search("unix", stream_get_transports()) === false) {
+ die('skip No support for UNIX domain sockets.');
+}
+?>
+--FILE--
+<?php
+function buffers($stream): array {
+ $sock = socket_import_stream($stream);
+ return [
+ socket_get_option($sock, SOL_SOCKET, SO_RCVBUF),
+ socket_get_option($sock, SOL_SOCKET, SO_SNDBUF),
+ ];
+}
+
+$control_path = sys_get_temp_dir() . '/' . uniqid('so_buf_control_') . '.sock';
+$server_path = sys_get_temp_dir() . '/' . uniqid('so_buf_server_') . '.sock';
+
+$control = stream_socket_server("unix://$control_path", $errno, $errstr,
+ STREAM_SERVER_BIND | STREAM_SERVER_LISTEN);
+
+if (!$control) {
+ die('Unable to create server');
+}
+
+$control_client = stream_socket_client("unix://$control_path", $errno, $errstr, 30);
+
+if (!$control_client) {
+ die('Unable to create client');
+}
+
+[$rcvbuf, $sndbuf] = buffers($control);
+[$client_rcvbuf, $client_sndbuf] = buffers($control_client);
+
+// Shrinking is always honoured, while growing may be capped by the system maximum.
+function context(int $rcvbuf, int $sndbuf) {
+ return stream_context_create(['socket' => [
+ 'so_rcvbuf' => intdiv($rcvbuf, 4),
+ 'so_sndbuf' => intdiv($sndbuf, 4),
+ ]]);
+}
+
+$server = stream_socket_server("unix://$server_path", $errno, $errstr,
+ STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, context($rcvbuf, $sndbuf));
+
+if (!$server) {
+ die('Unable to create server');
+}
+
+echo "Listen buffers", PHP_EOL;
+[$listen_rcvbuf, $listen_sndbuf] = buffers($server);
+var_dump($listen_rcvbuf < $rcvbuf);
+var_dump($listen_sndbuf < $sndbuf);
+
+$client = stream_socket_client("unix://$server_path", $errno, $errstr, 30,
+ STREAM_CLIENT_CONNECT, context($client_rcvbuf, $client_sndbuf));
+
+if (!$client) {
+ die('Unable to create client');
+}
+
+$accepted = stream_socket_accept($server, 1);
+
+if (!$accepted) {
+ die('Unable to accept connection');
+}
+
+echo "Client buffers", PHP_EOL;
+[$rcvbuf2, $sndbuf2] = buffers($client);
+var_dump($rcvbuf2 < $client_rcvbuf);
+var_dump($sndbuf2 < $client_sndbuf);
+
+$invalid = stream_context_create(['socket' => ['so_rcvbuf' => 0]]);
+
+echo "Invalid buffer size", PHP_EOL;
+var_dump(@stream_socket_server("unix://$server_path.invalid", $errno, $errstr,
+ STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $invalid));
+echo $errstr, PHP_EOL;
+var_dump(@stream_socket_client("unix://$server_path", $errno, $errstr, 30,
+ STREAM_CLIENT_CONNECT, $invalid));
+echo $errstr, PHP_EOL;
+
+fclose($accepted);
+fclose($client);
+fclose($server);
+fclose($control_client);
+fclose($control);
+unlink($server_path);
+unlink($control_path);
+
+?>
+--EXPECT--
+Listen buffers
+bool(true)
+bool(true)
+Client buffers
+bool(true)
+bool(true)
+Invalid buffer size
+bool(false)
+so_rcvbuf context option must be between 1 and 2147483647
+bool(false)
+so_rcvbuf context option must be between 1 and 2147483647
diff --git a/main/network.c b/main/network.c
index 01e4f2e778d..1eee7390a23 100644
--- a/main/network.c
+++ b/main/network.c
@@ -443,8 +443,42 @@ PHPAPI int php_network_connect_socket(php_socket_t sockfd,
}
/* }}} */
-static void php_network_set_socket_buffers(php_socket_t sock, const php_sockvals *sockvals)
+PHPAPI void php_network_apply_sockvals(php_socket_t sock, const php_sockvals *sockvals)
{
+#ifdef SO_LINGER
+ if (sockvals->mask & PHP_SOCKVAL_SO_LINGER) {
+ unsigned short secs = sockvals->linger > USHRT_MAX
+ ? USHRT_MAX : (unsigned short)sockvals->linger;
+ struct linger linger_val = {
+ .l_onoff = (sockvals->linger > 0),
+ .l_linger = sockvals->linger > 0 ? secs : 0
+ };
+#ifdef SO_LINGER_SEC
+ setsockopt(sock, SOL_SOCKET, SO_LINGER_SEC, (char*)&linger_val, sizeof(linger_val));
+#else
+ setsockopt(sock, SOL_SOCKET, SO_LINGER, (char*)&linger_val, sizeof(linger_val));
+#endif
+ }
+#endif
+#if defined(TCP_KEEPIDLE)
+ if (sockvals->mask & PHP_SOCKVAL_TCP_KEEPIDLE) {
+ setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, (char*)&sockvals->keepalive.keepidle, sizeof(sockvals->keepalive.keepidle));
+ }
+#elif defined(TCP_KEEPALIVE)
+ if (sockvals->mask & PHP_SOCKVAL_TCP_KEEPIDLE) {
+ setsockopt(sock, IPPROTO_TCP, TCP_KEEPALIVE, (char*)&sockvals->keepalive.keepidle, sizeof(sockvals->keepalive.keepidle));
+ }
+#endif
+#ifdef TCP_KEEPINTVL
+ if (sockvals->mask & PHP_SOCKVAL_TCP_KEEPINTVL) {
+ setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, (char*)&sockvals->keepalive.keepintvl, sizeof(sockvals->keepalive.keepintvl));
+ }
+#endif
+#ifdef TCP_KEEPCNT
+ if (sockvals->mask & PHP_SOCKVAL_TCP_KEEPCNT) {
+ setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, (char*)&sockvals->keepalive.keepcnt, sizeof(sockvals->keepalive.keepcnt));
+ }
+#endif
#ifdef SO_RCVBUF
if (sockvals->mask & PHP_SOCKVAL_SO_RCVBUF) {
setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char*)&sockvals->rcvbuf, sizeof(sockvals->rcvbuf));
@@ -547,47 +581,8 @@ php_socket_t php_network_bind_socket_to_local_addr_ex(const char *host, unsigned
}
#endif
- /* Set socket values if provided */
if (sockvals != NULL) {
-#ifdef SO_LINGER
- if (sockvals->mask & PHP_SOCKVAL_SO_LINGER) {
- /* l_linger is an unsigned short on Windows, so clamp rather than
- * truncate: a truncated value may still be in range and would then
- * be applied silently (e.g. 65536 becoming 0, an abortive close). */
- unsigned short secs = sockvals->linger > USHRT_MAX
- ? USHRT_MAX : (unsigned short)sockvals->linger;
- struct linger linger_val = {
- .l_onoff = (sockvals->linger > 0),
- .l_linger = sockvals->linger > 0 ? secs : 0
- };
-#ifdef SO_LINGER_SEC
- setsockopt(sock, SOL_SOCKET, SO_LINGER_SEC, (char*)&linger_val, sizeof(linger_val));
-#else
- setsockopt(sock, SOL_SOCKET, SO_LINGER, (char*)&linger_val, sizeof(linger_val));
-#endif
- }
-#endif
-#if defined(TCP_KEEPIDLE)
- if (sockvals->mask & PHP_SOCKVAL_TCP_KEEPIDLE) {
- setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, (char*)&sockvals->keepalive.keepidle, sizeof(sockvals->keepalive.keepidle));
- }
-#elif defined(TCP_KEEPALIVE)
- /* macOS uses TCP_KEEPALIVE instead of TCP_KEEPIDLE */
- if (sockvals->mask & PHP_SOCKVAL_TCP_KEEPIDLE) {
- setsockopt(sock, IPPROTO_TCP, TCP_KEEPALIVE, (char*)&sockvals->keepalive.keepidle, sizeof(sockvals->keepalive.keepidle));
- }
-#endif
-#ifdef TCP_KEEPINTVL
- if (sockvals->mask & PHP_SOCKVAL_TCP_KEEPINTVL) {
- setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, (char*)&sockvals->keepalive.keepintvl, sizeof(sockvals->keepalive.keepintvl));
- }
-#endif
-#ifdef TCP_KEEPCNT
- if (sockvals->mask & PHP_SOCKVAL_TCP_KEEPCNT) {
- setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, (char*)&sockvals->keepalive.keepcnt, sizeof(sockvals->keepalive.keepcnt));
- }
-#endif
- php_network_set_socket_buffers(sock, sockvals);
+ php_network_apply_sockvals(sock, sockvals);
}
n = bind(sock, sa, socklen);
@@ -1052,47 +1047,8 @@ php_socket_t php_network_connect_socket_to_host_ex(const char *host, unsigned sh
}
#endif
- /* Set socket values if provided */
if (sockvals != NULL) {
-#ifdef SO_LINGER
- if (sockvals->mask & PHP_SOCKVAL_SO_LINGER) {
- /* l_linger is an unsigned short on Windows, so clamp rather than
- * truncate: a truncated value may still be in range and would then
- * be applied silently (e.g. 65536 becoming 0, an abortive close). */
- unsigned short secs = sockvals->linger > USHRT_MAX
- ? USHRT_MAX : (unsigned short)sockvals->linger;
- struct linger linger_val = {
- .l_onoff = (sockvals->linger > 0),
- .l_linger = sockvals->linger > 0 ? secs : 0
- };
-#ifdef SO_LINGER_SEC
- setsockopt(sock, SOL_SOCKET, SO_LINGER_SEC, (char*)&linger_val, sizeof(linger_val));
-#else
- setsockopt(sock, SOL_SOCKET, SO_LINGER, (char*)&linger_val, sizeof(linger_val));
-#endif
- }
-#endif
-#if defined(TCP_KEEPIDLE)
- if (sockvals->mask & PHP_SOCKVAL_TCP_KEEPIDLE) {
- setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, (char*)&sockvals->keepalive.keepidle, sizeof(sockvals->keepalive.keepidle));
- }
-#elif defined(TCP_KEEPALIVE)
- /* macOS uses TCP_KEEPALIVE instead of TCP_KEEPIDLE */
- if (sockvals->mask & PHP_SOCKVAL_TCP_KEEPIDLE) {
- setsockopt(sock, IPPROTO_TCP, TCP_KEEPALIVE, (char*)&sockvals->keepalive.keepidle, sizeof(sockvals->keepalive.keepidle));
- }
-#endif
-#ifdef TCP_KEEPINTVL
- if (sockvals->mask & PHP_SOCKVAL_TCP_KEEPINTVL) {
- setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, (char*)&sockvals->keepalive.keepintvl, sizeof(sockvals->keepalive.keepintvl));
- }
-#endif
-#ifdef TCP_KEEPCNT
- if (sockvals->mask & PHP_SOCKVAL_TCP_KEEPCNT) {
- setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, (char*)&sockvals->keepalive.keepcnt, sizeof(sockvals->keepalive.keepcnt));
- }
-#endif
- php_network_set_socket_buffers(sock, sockvals);
+ php_network_apply_sockvals(sock, sockvals);
}
n = php_network_connect_socket(sock, sa, socklen, asynchronous,
diff --git a/main/php_network.h b/main/php_network.h
index c93a519911f..5c2cb0acede 100644
--- a/main/php_network.h
+++ b/main/php_network.h
@@ -292,6 +292,8 @@ BEGIN_EXTERN_C()
PHPAPI int php_network_getaddresses(const char *host, int socktype, struct sockaddr ***sal, zend_string **error_string);
PHPAPI void php_network_freeaddresses(struct sockaddr **sal);
+PHPAPI void php_network_apply_sockvals(php_socket_t sock, const php_sockvals *sockvals);
+
PHPAPI php_socket_t php_network_connect_socket_to_host_ex(const char *host, unsigned short port,
int socktype, int asynchronous, struct timeval *timeout, zend_string **error_string,
int *error_code, const char *bindto, unsigned short bindport, long sockopts, php_sockvals *sockvals
diff --git a/main/streams/xp_socket.c b/main/streams/xp_socket.c
index 3844414d804..4d022c48571 100644
--- a/main/streams/xp_socket.c
+++ b/main/streams/xp_socket.c
@@ -53,7 +53,7 @@ static const php_stream_ops php_stream_unixdg_socket_ops;
(PHP_STREAM_XPORT_IS_UNIX_DG(stream) || PHP_STREAM_XPORT_IS_UNIX_ST(stream))
#else
#define PHP_STREAM_XPORT_IS_UNIX_DG(stream) false
-#define PHP_STREAM_XPORT_IS_UNIX_STD(stream) false
+#define PHP_STREAM_XPORT_IS_UNIX_ST(stream) false
#define PHP_STREAM_XPORT_IS_UNIX(stream) false
#endif
#define PHP_STREAM_XPORT_IS_UDP(stream) (php_stream_is(stream, &php_stream_udp_socket_ops))
@@ -677,7 +677,7 @@ static inline char *parse_ip_address(php_stream_xport_param *xparam, int *portno
return parse_ip_address_ex(xparam->inputs.name, xparam->inputs.namelen, portno, xparam->want_errortext, &xparam->outputs.error_text);
}
-static int php_sockop_parse_buffer_sizes(php_stream *stream, php_stream_xport_param *xparam,
+static int php_sockop_parse_sockvals(php_stream *stream, php_stream_xport_param *xparam,
php_sockvals *sockvals)
{
zval *tmpzval;
@@ -686,6 +686,15 @@ static int php_sockop_parse_buffer_sizes(php_stream *stream, php_stream_xport_pa
return 0;
}
+#ifdef SO_LINGER
+ if (PHP_STREAM_XPORT_IS_TCP(stream)
+ && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_linger")) != NULL
+ ) {
+ sockvals->mask |= PHP_SOCKVAL_SO_LINGER;
+ sockvals->linger = (int)zval_get_long(tmpzval);
+ }
+#endif
+
#ifdef SO_RCVBUF
if ((tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_rcvbuf")) != NULL) {
zend_long bufsize = zval_get_long(tmpzval);
@@ -730,6 +739,10 @@ static inline int php_tcp_sockop_bind(php_stream *stream, php_netstream_data_t *
zval *tmpzval = NULL;
php_sockvals sockvals = {0};
+ if (php_sockop_parse_sockvals(stream, xparam, &sockvals) == -1) {
+ return -1;
+ }
+
#ifdef AF_UNIX
if (PHP_STREAM_XPORT_IS_UNIX(stream)) {
struct sockaddr_un unix_addr;
@@ -746,6 +759,8 @@ static inline int php_tcp_sockop_bind(php_stream *stream, php_netstream_data_t *
return -1;
}
+ php_network_apply_sockvals(sock->socket, &sockvals);
+
parse_unix_address(stream, xparam, &unix_addr);
int result = bind(sock->socket, (const struct sockaddr *)&unix_addr,
@@ -764,11 +779,6 @@ static inline int php_tcp_sockop_bind(php_stream *stream, php_netstream_data_t *
return -1;
}
- if (php_sockop_parse_buffer_sizes(stream, xparam, &sockvals) == -1) {
- efree(host);
- return -1;
- }
-
#ifdef IPV6_V6ONLY
if (PHP_STREAM_CONTEXT(stream)
&& (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "ipv6_v6only")) != NULL
@@ -808,16 +818,6 @@ static inline int php_tcp_sockop_bind(php_stream *stream, php_netstream_data_t *
}
#endif
-#ifdef SO_LINGER
- if (PHP_STREAM_XPORT_IS_TCP(stream)
- && PHP_STREAM_CONTEXT(stream)
- && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_linger")) != NULL
- ) {
- sockvals.mask |= PHP_SOCKVAL_SO_LINGER;
- sockvals.linger = (int)zval_get_long(tmpzval);
- }
-#endif
-
#ifdef SO_KEEPALIVE
if (PHP_STREAM_XPORT_IS_TCP(stream) /* SO_KEEPALIVE is only applicable for TCP */
&& PHP_STREAM_CONTEXT(stream)
@@ -884,6 +884,10 @@ static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_
long sockopts = STREAM_SOCKOP_NONE;
php_sockvals sockvals = {0};
+ if (php_sockop_parse_sockvals(stream, xparam, &sockvals) == -1) {
+ return -1;
+ }
+
#ifdef AF_UNIX
if (PHP_STREAM_XPORT_IS_UNIX(stream)) {
struct sockaddr_un unix_addr;
@@ -897,6 +901,8 @@ static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_
return -1;
}
+ php_network_apply_sockvals(sock->socket, &sockvals);
+
parse_unix_address(stream, xparam, &unix_addr);
ret = php_network_connect_socket(sock->socket,
@@ -917,11 +923,6 @@ static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_
return -1;
}
- if (php_sockop_parse_buffer_sizes(stream, xparam, &sockvals) == -1) {
- efree(host);
- return -1;
- }
-
if (PHP_STREAM_CONTEXT(stream) && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "bindto")) != NULL) {
if (Z_TYPE_P(tmpzval) != IS_STRING) {
if (xparam->want_errortext) {
@@ -951,16 +952,6 @@ static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_
sockopts |= STREAM_SOCKOP_TCP_NODELAY;
}
-#ifdef SO_LINGER
- if (PHP_STREAM_XPORT_IS_TCP(stream)
- && PHP_STREAM_CONTEXT(stream)
- && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_linger")) != NULL
- ) {
- sockvals.mask |= PHP_SOCKVAL_SO_LINGER;
- sockvals.linger = (int)zval_get_long(tmpzval);
- }
-#endif
-
#ifdef SO_KEEPALIVE
if (PHP_STREAM_XPORT_IS_TCP(stream) /* SO_KEEPALIVE is only applicable for TCP */
&& PHP_STREAM_CONTEXT(stream)