1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

Add so_reuseaddr stream socket context option

This is to allow disabling of SO_REUSEADDR that is enabled by default.

To achieve better compatibility on Windows SO_EXCLUSIVEADDRUSE is set
if so_reuseaddr is false.

Closes GH-19967
This commit is contained in:
Jakub Zelenka
2025-09-25 20:33:10 +02:00
parent f00a30102c
commit aead67d0bb
6 changed files with 101 additions and 3 deletions

4
NEWS
View File

@@ -33,6 +33,10 @@ PHP NEWS
. Fixed bug GH-19926 (reset internal pointer earlier while splicing array
while COW violation flag is still set). (alexandre-daubois)
- Streams:
. Added so_reuseaddr streams context socket option that allows disabling
address resuse.
- Zip:
. Fixed ZipArchive callback being called after executor has shut down.
(ilutov)

View File

@@ -36,6 +36,12 @@ PHP 8.6 UPGRADE NOTES
IntlNumberRangeFormatter::IDENTITY_FALLBACK_SINGLE_VALUE, IntlNumberRangeFormatter::IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE, IntlNumberRangeFormatter::IDENTITY_FALLBACK_APPROXIMATELY and
IntlNumberRangeFormatter::IDENTITY_FALLBACK_RANGE identity fallbacks.
It is supported from icu 63.
- Streams:
. Added stream socket context option so_reuseaddr that allows disabling
address reuse (SO_REUSEADDR) and explicitly uses SO_EXCLUSIVEADDRUSE on
Windows.
========================================
3. Changes in SAPI modules
========================================

View File

@@ -0,0 +1,73 @@
--TEST--
stream_socket_server() SO_REUSEADDR context option test
--FILE--
<?php
$is_win = substr(PHP_OS, 0, 3) == "WIN";
// Test default behavior (SO_REUSEADDR enabled)
$server1 = stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN);
if (!$server1) {
die('Unable to create server3');
}
$addr = stream_socket_get_name($server1, false);
$port = (int)substr(strrchr($addr, ':'), 1);
$client1 = stream_socket_client("tcp://127.0.0.1:$port");
$accepted = stream_socket_accept($server1, 1);
// Force real TCP connection with data
fwrite($client1, "test");
fread($accepted, 4);
fwrite($accepted, "response");
fread($client1, 8);
fclose($client1);
if (!$is_win) { // Windows would always succeed if server is closed
fclose($server1);
}
$server2 = @stream_socket_server("tcp://127.0.0.1:$port", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN);
if ($server2) {
echo "Default: Server restart succeeded\n";
fclose($server2);
} else {
echo "Default: Server restart failed\n";
}
// Test with SO_REUSEADDR explicitly disabled
$context = stream_context_create(['socket' => ['so_reuseaddr' => false]]);
$server3 = stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $context);
if (!$server3) {
die('Unable to create server3');
}
$addr = stream_socket_get_name($server3, false);
$port = (int)substr(strrchr($addr, ':'), 1);
$client3 = stream_socket_client("tcp://127.0.0.1:$port");
$accepted = stream_socket_accept($server3, 1);
// Force real TCP connection with data
fwrite($client3, "test");
fread($accepted, 4);
fwrite($accepted, "response");
fread($client3, 8);
// Client closes first (becomes active closer)
fclose($client3); // This enters TIME_WAIT
if (!$is_win) { // Windows would always succeed if server is closed
fclose($server3);
}
// Try immediate bind with SO_REUSEADDR disabled (should fail)
$server4 = @stream_socket_server("tcp://127.0.0.1:$port", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $context);
if ($server4) {
echo "Disabled: Server restart succeeded\n";
fclose($server4);
} else {
echo "Disabled: Server restart failed\n";
}
?>
--EXPECT--
Default: Server restart succeeded
Disabled: Server restart failed

View File

@@ -496,8 +496,13 @@ php_socket_t php_network_bind_socket_to_local_addr(const char *host, unsigned po
/* attempt to bind */
#ifdef SO_REUSEADDR
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&sockoptval, sizeof(sockoptval));
if (sockopts & STREAM_SOCKOP_SO_REUSEADDR) {
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&sockoptval, sizeof(sockoptval));
}
#ifdef PHP_WIN32
else {
setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (char*)&sockoptval, sizeof(sockoptval));
}
#endif
#ifdef IPV6_V6ONLY
if (sockopts & STREAM_SOCKOP_IPV6_V6ONLY) {

View File

@@ -123,7 +123,7 @@ typedef int php_socket_t;
#define STREAM_SOCKOP_IPV6_V6ONLY (1 << 3)
#define STREAM_SOCKOP_IPV6_V6ONLY_ENABLED (1 << 4)
#define STREAM_SOCKOP_TCP_NODELAY (1 << 5)
#define STREAM_SOCKOP_SO_REUSEADDR (1 << 6)
/* uncomment this to debug poll(2) emulation on systems that have poll(2) */
/* #define PHP_USE_POLL_2_EMULATION 1 */

View File

@@ -718,6 +718,16 @@ static inline int php_tcp_sockop_bind(php_stream *stream, php_netstream_data_t *
}
#endif
#ifdef SO_REUSEADDR
/* SO_REUSEADDR is enabled by default so this option is just to disable it if set to false. */
if (!PHP_STREAM_CONTEXT(stream)
|| (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_reuseaddr")) == NULL
|| zend_is_true(tmpzval)
) {
sockopts |= STREAM_SOCKOP_SO_REUSEADDR;
}
#endif
#ifdef SO_BROADCAST
if (stream->ops == &php_stream_udp_socket_ops /* SO_BROADCAST is only applicable for UDP */
&& PHP_STREAM_CONTEXT(stream)