Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions ext/standard/tests/network/so_reuseport.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
--TEST--
stream_socket_server() SO_REUSEPORT context option test
--SKIPIF--
<?php
if (substr(PHP_OS, 0, 3) == "WIN") {
die('skip SO_REUSEPORT not available on Windows');
}
?>
--FILE--
<?php
// SO_REUSEPORT enabled - should allow multiple servers on same port
$context1 = stream_context_create(['socket' => ['so_reuseport' => true]]);
$server1 = stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr,
STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $context1);

if (!$server1) {
die('Unable to create server1');
}

$addr = stream_socket_get_name($server1, false);
$port = (int)substr(strrchr($addr, ':'), 1);

// Establish actual connection on server1
$client1 = stream_socket_client("tcp://127.0.0.1:$port");
$accepted1 = stream_socket_accept($server1, 1);

// Force real TCP connection with data
fwrite($client1, "test");
fread($accepted1, 4);
fwrite($accepted1, "response");
fread($client1, 8);

// Try to bind second server to SAME port with SO_REUSEPORT (while server1 still active)
$context2 = stream_context_create(['socket' => ['so_reuseport' => true]]);
$server2 = @stream_socket_server("tcp://127.0.0.1:$port", $errno, $errstr,
STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $context2);

if ($server2) {
echo "SO_REUSEPORT enabled: Multiple servers succeeded\n";

// Verify server2 can also accept connections
$client2 = stream_socket_client("tcp://127.0.0.1:$port");
$accepted2_on_srv1 = @stream_socket_accept($server1, 0.1);
$accepted2_on_srv2 = @stream_socket_accept($server2, 0.1);

if ($accepted2_on_srv1 || $accepted2_on_srv2) {
echo "SO_REUSEPORT enabled: Connections accepted\n";
}

if ($accepted2_on_srv1) fclose($accepted2_on_srv1);
if ($accepted2_on_srv2) fclose($accepted2_on_srv2);
if ($client2) fclose($client2);

fclose($server2);
} else {
echo "SO_REUSEPORT enabled: Multiple servers failed\n";
}

fclose($accepted1);
fclose($client1);
fclose($server1);

// SO_REUSEPORT disabled (default) - should NOT allow multiple servers
$server3 = stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr,
STREAM_SERVER_BIND | STREAM_SERVER_LISTEN);

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");
$accepted3 = stream_socket_accept($server3, 1);

fwrite($client3, "test");
fread($accepted3, 4);
fwrite($accepted3, "response");
fread($client3, 8);

// Try to bind second server WITHOUT SO_REUSEPORT (while server3 still active)
$server4 = @stream_socket_server("tcp://127.0.0.1:$port", $errno, $errstr,
STREAM_SERVER_BIND | STREAM_SERVER_LISTEN);

if ($server4) {
echo "SO_REUSEPORT disabled: Multiple servers succeeded\n";
fclose($server4);
} else {
echo "SO_REUSEPORT disabled: Multiple servers failed\n";
}

fclose($accepted3);
fclose($client3);
fclose($server3);

// SO_REUSEPORT explicitly disabled
$context3 = stream_context_create(['socket' => ['so_reuseport' => false]]);
$server5 = stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr,
STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $context3);

if (!$server5) {
die('Unable to create server5');
}

$addr = stream_socket_get_name($server5, false);
$port = (int)substr(strrchr($addr, ':'), 1);

$client5 = stream_socket_client("tcp://127.0.0.1:$port");
$accepted5 = stream_socket_accept($server5, 1);

fwrite($client5, "test");
fread($accepted5, 4);
fwrite($accepted5, "response");
fread($client5, 8);

$context4 = stream_context_create(['socket' => ['so_reuseport' => false]]);
$server6 = @stream_socket_server("tcp://127.0.0.1:$port", $errno, $errstr,
STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $context4);

if ($server6) {
echo "SO_REUSEPORT explicitly disabled: Multiple servers succeeded\n";
fclose($server6);
} else {
echo "SO_REUSEPORT explicitly disabled: Multiple servers failed\n";
}

fclose($accepted5);
fclose($client5);
fclose($server5);
?>
--EXPECT--
SO_REUSEPORT enabled: Multiple servers succeeded
SO_REUSEPORT enabled: Connections accepted
SO_REUSEPORT disabled: Multiple servers failed
SO_REUSEPORT explicitly disabled: Multiple servers failed
6 changes: 6 additions & 0 deletions main/network.c
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,13 @@ php_socket_t php_network_bind_socket_to_local_addr(const char *host, unsigned po
#endif
#ifdef SO_REUSEPORT
if (sockopts & STREAM_SOCKOP_SO_REUSEPORT) {
# ifdef SO_REUSEPORT_LB
/* Historically, SO_REUSEPORT on FreeBSD predates Linux version, however does not
* involve load balancing grouping thus SO_REUSEPORT_LB is the genuine equivalent.*/
setsockopt(sock, SOL_SOCKET, SO_REUSEPORT_LB, (char*)&sockoptval, sizeof(sockoptval));
# else
setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, (char*)&sockoptval, sizeof(sockoptval));
# endif
}
#endif
#ifdef SO_BROADCAST
Expand Down