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
2 changes: 2 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ PHP NEWS
while COW violation flag is still set). (alexandre-daubois)

- Streams:
. Added so_keepalive, tcp_keepidle, tcp_keepintvl and tcp_keepcnt stream
socket context options.
. Added so_reuseaddr streams context socket option that allows disabling
address resuse.
. Fixed bug GH-20370 (User stream filters could violate typed property
Expand Down
3 changes: 3 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ PHP 8.6 UPGRADE NOTES
. Added stream socket context option so_reuseaddr that allows disabling
address reuse (SO_REUSEADDR) and explicitly uses SO_EXCLUSIVEADDRUSE on
Windows.
. Added stream socket context options so_keepalive, tcp_keepidle,
tcp_keepintvl and tcp_keepcnt that allow setting socket keepalive
options.

========================================
3. Changes in SAPI modules
Expand Down
21 changes: 14 additions & 7 deletions ext/openssl/xp_ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -2219,25 +2219,32 @@ static int php_openssl_sockop_stat(php_stream *stream, php_stream_statbuf *ssb)
static inline int php_openssl_tcp_sockop_accept(php_stream *stream, php_openssl_netstream_data_t *sock,
php_stream_xport_param *xparam STREAMS_DC) /* {{{ */
{
bool nodelay = false;
php_sockvals sockvals = {0};
zval *tmpzval = NULL;

xparam->outputs.client = NULL;

if (PHP_STREAM_CONTEXT(stream) &&
(tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_nodelay")) != NULL &&
zend_is_true(tmpzval)) {
nodelay = true;
if (PHP_STREAM_CONTEXT(stream)) {
tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_nodelay");
if (tmpzval != NULL && zend_is_true(tmpzval)) {
sockvals.mask |= PHP_SOCKVAL_TCP_NODELAY;
sockvals.tcp_nodelay = 1;
}
tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_keepidle");
if (tmpzval != NULL && Z_TYPE_P(tmpzval) == IS_LONG) {
sockvals.mask |= PHP_SOCKVAL_TCP_KEEPIDLE;
sockvals.keepalive.keepidle = Z_LVAL_P(tmpzval);
}
}

php_socket_t clisock = php_network_accept_incoming(sock->s.socket,
php_socket_t clisock = php_network_accept_incoming_ex(sock->s.socket,
xparam->want_textaddr ? &xparam->outputs.textaddr : NULL,
xparam->want_addr ? &xparam->outputs.addr : NULL,
xparam->want_addr ? &xparam->outputs.addrlen : NULL,
xparam->inputs.timeout,
xparam->want_errortext ? &xparam->outputs.error_text : NULL,
&xparam->outputs.error_code,
nodelay);
&sockvals);

if (clisock != SOCK_ERR) {
php_openssl_netstream_data_t *clisockdata = (php_openssl_netstream_data_t*) emalloc(sizeof(*clisockdata));
Expand Down
151 changes: 151 additions & 0 deletions ext/standard/tests/network/so_keepalive.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
--TEST--
stream_socket_server() and stream_socket_client() SO_KEEPALIVE context option test
--EXTENSIONS--
sockets
--SKIPIF--
<?php
if (!defined('TCP_KEEPIDLE') && !defined('TCP_KEEPALIVE')) {
die('skip TCP_KEEPIDLE/TCP_KEEPALIVE not available');
}
if (!defined('TCP_KEEPINTVL')) {
die('skip TCP_KEEPINTVL not available');
}
if (!defined('TCP_KEEPCNT')) {
die('skip TCP_KEEPCNT not available');
}
?>
--FILE--
<?php
// Test server with SO_KEEPALIVE enabled
$server_context = stream_context_create([
'socket' => [
'so_keepalive' => true,
'tcp_keepidle' => 60,
'tcp_keepintvl' => 10,
'tcp_keepcnt' => 5,
]
]);

$server = stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr,
STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $server_context);

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

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

// Test client with SO_KEEPALIVE enabled
$client_context = stream_context_create([
'socket' => [
'so_keepalive' => true,
'tcp_keepidle' => 30,
'tcp_keepintvl' => 5,
'tcp_keepcnt' => 3,
]
]);

$client = stream_socket_client("tcp://127.0.0.1:$port", $errno, $errstr, 30,
STREAM_CLIENT_CONNECT, $client_context);

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

$accepted = stream_socket_accept($server, 1);

if (!$accepted) {
die('Unable to accept connection');
}

// Verify server side (accepted connection)
$server_sock = socket_import_stream($accepted);
$server_keepalive = socket_get_option($server_sock, SOL_SOCKET, SO_KEEPALIVE);
echo "Server SO_KEEPALIVE: " . ($server_keepalive ? "enabled" : "disabled") . "\n";

if (defined('TCP_KEEPIDLE')) {
$server_idle = socket_get_option($server_sock, SOL_TCP, TCP_KEEPIDLE);
echo "Server TCP_KEEPIDLE: $server_idle\n";
} else {
$server_idle = socket_get_option($server_sock, SOL_TCP, TCP_KEEPALIVE);
echo "Server TCP_KEEPIDLE: $server_idle\n";
}

$server_intvl = socket_get_option($server_sock, SOL_TCP, TCP_KEEPINTVL);
echo "Server TCP_KEEPINTVL: $server_intvl\n";

$server_cnt = socket_get_option($server_sock, SOL_TCP, TCP_KEEPCNT);
echo "Server TCP_KEEPCNT: $server_cnt\n";

// Verify client side
$client_sock = socket_import_stream($client);
$client_keepalive = socket_get_option($client_sock, SOL_SOCKET, SO_KEEPALIVE);
echo "Client SO_KEEPALIVE: " . ($client_keepalive ? "enabled" : "disabled") . "\n";

if (defined('TCP_KEEPIDLE')) {
$client_idle = socket_get_option($client_sock, SOL_TCP, TCP_KEEPIDLE);
echo "Client TCP_KEEPIDLE: $client_idle\n";
} else {
$client_idle = socket_get_option($client_sock, SOL_TCP, TCP_KEEPALIVE);
echo "Client TCP_KEEPIDLE: $client_idle\n";
}

$client_intvl = socket_get_option($client_sock, SOL_TCP, TCP_KEEPINTVL);
echo "Client TCP_KEEPINTVL: $client_intvl\n";

$client_cnt = socket_get_option($client_sock, SOL_TCP, TCP_KEEPCNT);
echo "Client TCP_KEEPCNT: $client_cnt\n";

fclose($accepted);
fclose($client);
fclose($server);

// Test server with SO_KEEPALIVE disabled
$server2_context = stream_context_create(['socket' => ['so_keepalive' => false]]);
$server2 = stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr,
STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $server2_context);

$addr2 = stream_socket_get_name($server2, false);
$port2 = (int)substr(strrchr($addr2, ':'), 1);

$client2 = stream_socket_client("tcp://127.0.0.1:$port2");
$accepted2 = stream_socket_accept($server2, 1);

$server2_sock = socket_import_stream($accepted2);
$server2_keepalive = socket_get_option($server2_sock, SOL_SOCKET, SO_KEEPALIVE);
echo "Server disabled SO_KEEPALIVE: " . ($server2_keepalive ? "enabled" : "disabled") . "\n";

fclose($accepted2);
fclose($client2);
fclose($server2);

// Test client with SO_KEEPALIVE disabled
$server3 = stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr,
STREAM_SERVER_BIND | STREAM_SERVER_LISTEN);

$addr3 = stream_socket_get_name($server3, false);
$port3 = (int)substr(strrchr($addr3, ':'), 1);

$client3_context = stream_context_create(['socket' => ['so_keepalive' => false]]);
$client3 = stream_socket_client("tcp://127.0.0.1:$port3", $errno, $errstr, 30,
STREAM_CLIENT_CONNECT, $client3_context);

$client3_sock = socket_import_stream($client3);
$client3_keepalive = socket_get_option($client3_sock, SOL_SOCKET, SO_KEEPALIVE);
echo "Client disabled SO_KEEPALIVE: " . ($client3_keepalive ? "enabled" : "disabled") . "\n";

fclose($client3);
fclose($server3);
?>
--EXPECT--
Server SO_KEEPALIVE: enabled
Server TCP_KEEPIDLE: 60
Server TCP_KEEPINTVL: 10
Server TCP_KEEPCNT: 5
Client SO_KEEPALIVE: enabled
Client TCP_KEEPIDLE: 30
Client TCP_KEEPINTVL: 5
Client TCP_KEEPCNT: 3
Server disabled SO_KEEPALIVE: disabled
Client disabled SO_KEEPALIVE: disabled
Loading