From d87015f3ca6dbe75b38b041fb5cc1f989afa2820 Mon Sep 17 00:00:00 2001 From: James Lucas Date: Mon, 15 Dec 2025 21:23:44 +1100 Subject: [PATCH 1/9] ext/sockets: adding Linux's TCP_USER_TIMEOUT constant. --- NEWS | 4 ++++ UPGRADING | 3 +++ ext/sockets/sockets.stub.php | 7 +++++++ ext/sockets/sockets_arginfo.h | 5 ++++- 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index e3c32a4e1648b..7e9381866da56 100644 --- a/NEWS +++ b/NEWS @@ -56,6 +56,10 @@ PHP NEWS . Soap::__setCookie() when cookie name is a digit is now not stored and represented as a string anymore but a int. (David Carlier) +- Sockets: + . Added the TCP_USER_TIMEOUT constant for Linux to set the maximum time in milliseconds + transmitted data can remain unacknowledged. (James Lucas) + - SPL: . DirectoryIterator key can now work better with filesystem supporting larger directory indexing. (David Carlier) diff --git a/UPGRADING b/UPGRADING index 4e511d094797e..b83312b6ade7d 100644 --- a/UPGRADING +++ b/UPGRADING @@ -90,6 +90,9 @@ PHP 8.6 UPGRADE NOTES 10. New Global Constants ======================================== +- Sockets: + . TCP_USER_TIMEOUT (Linux only). + ======================================== 11. Changes to INI File Handling ======================================== diff --git a/ext/sockets/sockets.stub.php b/ext/sockets/sockets.stub.php index 3df9b598a1e8f..04fb702807e0a 100644 --- a/ext/sockets/sockets.stub.php +++ b/ext/sockets/sockets.stub.php @@ -602,6 +602,13 @@ */ const TCP_SYNCNT = UNKNOWN; #endif +#ifdef TCP_USER_TIMEOUT +/** + * @var int + * @cvalue TCP_USER_TIMEOUT + */ +const TCP_USER_TIMEOUT = UNKNOWN; +#endif #ifdef SO_ZEROCOPY /** * @var int diff --git a/ext/sockets/sockets_arginfo.h b/ext/sockets/sockets_arginfo.h index edfc344ff8cc0..0145d01252881 100644 --- a/ext/sockets/sockets_arginfo.h +++ b/ext/sockets/sockets_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: f754368e28f6e45bf3a63a403e49f5659c29d2c6 */ + * Stub hash: 038081ca7bb98076d4b559d93b4c9300acc47160 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_socket_select, 0, 4, MAY_BE_LONG|MAY_BE_FALSE) ZEND_ARG_TYPE_INFO(1, read, IS_ARRAY, 1) @@ -527,6 +527,9 @@ static void register_sockets_symbols(int module_number) #if defined(TCP_SYNCNT) REGISTER_LONG_CONSTANT("TCP_SYNCNT", TCP_SYNCNT, CONST_PERSISTENT); #endif +#if defined(TCP_USER_TIMEOUT) + REGISTER_LONG_CONSTANT("TCP_USER_TIMEOUT", TCP_USER_TIMEOUT, CONST_PERSISTENT); +#endif #if defined(SO_ZEROCOPY) REGISTER_LONG_CONSTANT("SO_ZEROCOPY", SO_ZEROCOPY, CONST_PERSISTENT); #endif From 67ee0ad743164417dd4c2928a5479bac6742e62e Mon Sep 17 00:00:00 2001 From: James Lucas Date: Tue, 16 Dec 2025 10:40:05 +1100 Subject: [PATCH 2/9] TCP_USER_TIMEOUT expects unsigned integer --- ext/sockets/sockets.c | 16 +++++++++ .../socket_setoption_tcpusertimeout.phpt | 33 +++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 ext/sockets/tests/socket_setoption_tcpusertimeout.phpt diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 4a9332498c3cc..2effe73310370 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -2329,6 +2329,22 @@ PHP_FUNCTION(socket_set_option) } #endif +#if defined(TCP_USER_TIMEOUT) + case TCP_USER_TIMEOUT: { + ov = zval_get_long(arg4); + + // TCP_USER_TIMEOUT unsigned int + if (ov < 0 || ov > UINT_MAX) { + zend_argument_value_error(4, "must be of between 0 and %u", UINT_MAX); + RETURN_FALSE; + } + + optlen = sizeof(ov); + opt_ptr = &ov; + break; + } +#endif + #if defined(UDP_SEGMENT) case UDP_SEGMENT: { ov = zval_get_long(arg4); diff --git a/ext/sockets/tests/socket_setoption_tcpusertimeout.phpt b/ext/sockets/tests/socket_setoption_tcpusertimeout.phpt new file mode 100644 index 0000000000000..6e89196513405 --- /dev/null +++ b/ext/sockets/tests/socket_setoption_tcpusertimeout.phpt @@ -0,0 +1,33 @@ +--TEST-- +Test if socket_set_option() works, option:SO_SNDTIMEO +--EXTENSIONS-- +sockets +--SKIPIF-- + +--FILE-- +getMessage(), PHP_EOL; +} + +$timeout = 200; +$retval_2 = socket_set_option( $socket, SOL_TCP, TCP_USER_TIMEOUT, $timeout); +$retval_3 = socket_get_option( $socket, SOL_TCP, TCP_USER_TIMEOUT); +var_dump($retval_2); +var_dump($retval_3 === $timeout); +socket_close($socket); +?> +--EXPECT-- +socket_setopt(): Argument #4 ($value) must be of between 0 and %d +bool(true) +bool(true) From 1117658011ac101c059e3c3ba3b1986cce7245eb Mon Sep 17 00:00:00 2001 From: James Lucas Date: Tue, 16 Dec 2025 12:11:57 +1100 Subject: [PATCH 3/9] Fix test case --- ext/sockets/tests/socket_setoption_tcpusertimeout.phpt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/sockets/tests/socket_setoption_tcpusertimeout.phpt b/ext/sockets/tests/socket_setoption_tcpusertimeout.phpt index 6e89196513405..6b4649fe712bb 100644 --- a/ext/sockets/tests/socket_setoption_tcpusertimeout.phpt +++ b/ext/sockets/tests/socket_setoption_tcpusertimeout.phpt @@ -1,5 +1,5 @@ --TEST-- -Test if socket_set_option() works, option:SO_SNDTIMEO +Test if socket_set_option() works, option:TCP_USER_TIMEOUT --EXTENSIONS-- sockets --SKIPIF-- @@ -15,14 +15,14 @@ if (!$socket) { socket_set_block($socket); try { - socket_setopt($src, SOL_TCP, TCP_USER_TIMEOUT, -1); + socket_setopt($socket, SOL_TCP, TCP_USER_TIMEOUT, -1); } catch (\ValueError $e) { echo $e->getMessage(), PHP_EOL; } $timeout = 200; -$retval_2 = socket_set_option( $socket, SOL_TCP, TCP_USER_TIMEOUT, $timeout); -$retval_3 = socket_get_option( $socket, SOL_TCP, TCP_USER_TIMEOUT); +$retval_2 = socket_set_option($socket, SOL_TCP, TCP_USER_TIMEOUT, $timeout); +$retval_3 = socket_get_option($socket, SOL_TCP, TCP_USER_TIMEOUT); var_dump($retval_2); var_dump($retval_3 === $timeout); socket_close($socket); From 1cd17b1a5fc52fef85f00ee7bcb02fceac21919b Mon Sep 17 00:00:00 2001 From: James Lucas Date: Tue, 16 Dec 2025 12:37:37 +1100 Subject: [PATCH 4/9] Update testcase with UINT_MAX expected output --- ext/sockets/tests/socket_setoption_tcpusertimeout.phpt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/sockets/tests/socket_setoption_tcpusertimeout.phpt b/ext/sockets/tests/socket_setoption_tcpusertimeout.phpt index 6b4649fe712bb..b11b357a36cc5 100644 --- a/ext/sockets/tests/socket_setoption_tcpusertimeout.phpt +++ b/ext/sockets/tests/socket_setoption_tcpusertimeout.phpt @@ -28,6 +28,6 @@ var_dump($retval_3 === $timeout); socket_close($socket); ?> --EXPECT-- -socket_setopt(): Argument #4 ($value) must be of between 0 and %d +socket_setopt(): Argument #4 ($value) must be of between 0 and 4294967295 bool(true) bool(true) From a80425870b043a597ffb214eaef01bf31f38ac3e Mon Sep 17 00:00:00 2001 From: James Lucas Date: Tue, 16 Dec 2025 16:56:13 +1100 Subject: [PATCH 5/9] Use ZEND_LONG_UINT_OVFL --- ext/sockets/sockets.c | 4 ++-- ext/sockets/tests/socket_setoption_tcpusertimeout.phpt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 2effe73310370..4e6fda18f2a9f 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -2334,9 +2334,9 @@ PHP_FUNCTION(socket_set_option) ov = zval_get_long(arg4); // TCP_USER_TIMEOUT unsigned int - if (ov < 0 || ov > UINT_MAX) { + if (ZEND_LONG_UINT_OVFL(ov)) { zend_argument_value_error(4, "must be of between 0 and %u", UINT_MAX); - RETURN_FALSE; + RETURN_THROWS; } optlen = sizeof(ov); diff --git a/ext/sockets/tests/socket_setoption_tcpusertimeout.phpt b/ext/sockets/tests/socket_setoption_tcpusertimeout.phpt index b11b357a36cc5..2cd2b4d28ff22 100644 --- a/ext/sockets/tests/socket_setoption_tcpusertimeout.phpt +++ b/ext/sockets/tests/socket_setoption_tcpusertimeout.phpt @@ -27,7 +27,7 @@ var_dump($retval_2); var_dump($retval_3 === $timeout); socket_close($socket); ?> ---EXPECT-- -socket_setopt(): Argument #4 ($value) must be of between 0 and 4294967295 +--EXPECTF-- +socket_setopt(): Argument #4 ($value) must be of between 0 and %d bool(true) bool(true) From 840e5461cd2f28ef573d32111593ad53faadecca Mon Sep 17 00:00:00 2001 From: James Lucas Date: Tue, 16 Dec 2025 17:10:45 +1100 Subject: [PATCH 6/9] RETURN_THROWS(); --- ext/sockets/sockets.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 4e6fda18f2a9f..b46aabc662ee5 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -2336,7 +2336,7 @@ PHP_FUNCTION(socket_set_option) // TCP_USER_TIMEOUT unsigned int if (ZEND_LONG_UINT_OVFL(ov)) { zend_argument_value_error(4, "must be of between 0 and %u", UINT_MAX); - RETURN_THROWS; + RETURN_THROWS(); } optlen = sizeof(ov); From efb6560288f4780b6e026aeab769267d9e1b6441 Mon Sep 17 00:00:00 2001 From: James Lucas Date: Wed, 17 Dec 2025 07:40:54 +1100 Subject: [PATCH 7/9] Long to unsigned int --- ext/sockets/sockets.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index b46aabc662ee5..5bb9e2843bca3 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -1773,7 +1773,7 @@ PHP_FUNCTION(socket_sendto) RETURN_THROWS(); } - memset(&sll, 0, sizeof(sll)); + memset(&sll, 0, sizeof(sll)); sll.sll_family = AF_PACKET; sll.sll_ifindex = port; @@ -2331,7 +2331,7 @@ PHP_FUNCTION(socket_set_option) #if defined(TCP_USER_TIMEOUT) case TCP_USER_TIMEOUT: { - ov = zval_get_long(arg4); + zval timeout = zval_get_long(arg4); // TCP_USER_TIMEOUT unsigned int if (ZEND_LONG_UINT_OVFL(ov)) { @@ -2339,8 +2339,9 @@ PHP_FUNCTION(socket_set_option) RETURN_THROWS(); } - optlen = sizeof(ov); - opt_ptr = &ov; + unsigned int val = (unsigned int)timeout; + optlen = sizeof(val); + opt_ptr = &val; break; } #endif From 66a4ea49f5ba61c7ed9d26b3d9465361cd848888 Mon Sep 17 00:00:00 2001 From: James Lucas Date: Wed, 17 Dec 2025 07:48:32 +1100 Subject: [PATCH 8/9] long --- ext/sockets/sockets.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 5bb9e2843bca3..ab0ab68fc0428 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -2331,10 +2331,10 @@ PHP_FUNCTION(socket_set_option) #if defined(TCP_USER_TIMEOUT) case TCP_USER_TIMEOUT: { - zval timeout = zval_get_long(arg4); + zend_long timeout = zval_get_long(arg4); // TCP_USER_TIMEOUT unsigned int - if (ZEND_LONG_UINT_OVFL(ov)) { + if (ZEND_LONG_UINT_OVFL(timeout)) { zend_argument_value_error(4, "must be of between 0 and %u", UINT_MAX); RETURN_THROWS(); } From dab9d207f3206da24ef6e021e28d3c62ce673615 Mon Sep 17 00:00:00 2001 From: James Lucas Date: Wed, 17 Dec 2025 08:33:36 +1100 Subject: [PATCH 9/9] Revert to original check --- ext/sockets/sockets.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index ab0ab68fc0428..109b5e35ad604 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -2331,17 +2331,16 @@ PHP_FUNCTION(socket_set_option) #if defined(TCP_USER_TIMEOUT) case TCP_USER_TIMEOUT: { - zend_long timeout = zval_get_long(arg4); + ov = zval_get_long(arg4); // TCP_USER_TIMEOUT unsigned int - if (ZEND_LONG_UINT_OVFL(timeout)) { + if (ov < 0 || ov > UINT_MAX) { zend_argument_value_error(4, "must be of between 0 and %u", UINT_MAX); RETURN_THROWS(); } - unsigned int val = (unsigned int)timeout; - optlen = sizeof(val); - opt_ptr = &val; + optlen = sizeof(ov); + opt_ptr = &ov; break; } #endif