diff --git a/ext/standard/tests/network/so_reuseport.phpt b/ext/standard/tests/network/so_reuseport.phpt new file mode 100644 index 0000000000000..12a07bbd42364 --- /dev/null +++ b/ext/standard/tests/network/so_reuseport.phpt @@ -0,0 +1,136 @@ +--TEST-- +stream_socket_server() SO_REUSEPORT context option test +--SKIPIF-- + +--FILE-- + ['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 diff --git a/main/network.c b/main/network.c index 0dadf0bb4dc3a..6c43321cf2e8a 100644 --- a/main/network.c +++ b/main/network.c @@ -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