Skip to content
Open
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
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
93 changes: 84 additions & 9 deletions main/network.c
Original file line number Diff line number Diff line change
Expand Up @@ -450,9 +450,9 @@ PHPAPI int php_network_connect_socket(php_socket_t sockfd,
/* Bind to a local IP address.
* Returns the bound socket, or -1 on failure.
* */
/* {{{ php_network_bind_socket_to_local_addr */
php_socket_t php_network_bind_socket_to_local_addr(const char *host, unsigned port,
int socktype, long sockopts, zend_string **error_string, int *error_code
php_socket_t php_network_bind_socket_to_local_addr_ex(const char *host, unsigned port,
int socktype, long sockopts, php_sockvals *sockvals, zend_string **error_string,
int *error_code
)
{
int num_addrs, n, err = 0;
Expand Down Expand Up @@ -531,6 +531,35 @@ php_socket_t php_network_bind_socket_to_local_addr(const char *host, unsigned po
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char*)&sockoptval, sizeof(sockoptval));
}
#endif
#ifdef SO_KEEPALIVE
if (sockopts & STREAM_SOCKOP_SO_KEEPALIVE) {
setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (char*)&sockoptval, sizeof(sockoptval));
}
#endif

/* Set socket values if provided */
if (sockvals != NULL) {
#if defined(TCP_KEEPIDLE)
if (sockvals->mask & PHP_SOCKVAL_TCP_KEEPIDLE) {
setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, (char*)&sockvals->keepalive.keepidle, sizeof(sockvals->keepalive.keepidle));
}
#elif defined(TCP_KEEPALIVE)
/* macOS uses TCP_KEEPALIVE instead of TCP_KEEPIDLE */
if (sockvals->mask & PHP_SOCKVAL_TCP_KEEPIDLE) {
setsockopt(sock, IPPROTO_TCP, TCP_KEEPALIVE, (char*)&sockvals->keepalive.keepidle, sizeof(sockvals->keepalive.keepidle));
}
#endif
#ifdef TCP_KEEPINTVL
if (sockvals->mask & PHP_SOCKVAL_TCP_KEEPINTVL) {
setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, (char*)&sockvals->keepalive.keepintvl, sizeof(sockvals->keepalive.keepintvl));
}
#endif
#ifdef TCP_KEEPCNT
if (sockvals->mask & PHP_SOCKVAL_TCP_KEEPCNT) {
setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, (char*)&sockvals->keepalive.keepcnt, sizeof(sockvals->keepalive.keepcnt));
}
#endif
}

n = bind(sock, sa, socklen);

Expand Down Expand Up @@ -558,7 +587,13 @@ php_socket_t php_network_bind_socket_to_local_addr(const char *host, unsigned po
return sock;

}
/* }}} */

php_socket_t php_network_bind_socket_to_local_addr(const char *host, unsigned port,
int socktype, long sockopts, zend_string **error_string, int *error_code
)
{
return php_network_bind_socket_to_local_addr_ex(host, port, socktype, sockopts, NULL, error_string, error_code);
}

PHPAPI zend_result php_network_parse_network_address_with_port(const char *addr, size_t addrlen, struct sockaddr *sa, socklen_t *sl)
{
Expand Down Expand Up @@ -822,11 +857,9 @@ PHPAPI php_socket_t php_network_accept_incoming(php_socket_t srvsock,
* enable non-blocking mode on the socket.
* Returns the connected (or connecting) socket, or -1 on failure.
* */

/* {{{ php_network_connect_socket_to_host */
php_socket_t php_network_connect_socket_to_host(const char *host, unsigned short port,
php_socket_t php_network_connect_socket_to_host_ex(const char *host, unsigned short port,
int socktype, int asynchronous, struct timeval *timeout, zend_string **error_string,
int *error_code, const char *bindto, unsigned short bindport, long sockopts
int *error_code, const char *bindto, unsigned short bindport, long sockopts, php_sockvals *sockvals
)
{
int num_addrs, n, fatal = 0;
Expand Down Expand Up @@ -950,6 +983,40 @@ php_socket_t php_network_connect_socket_to_host(const char *host, unsigned short
}
}
#endif

#ifdef SO_KEEPALIVE
{
int val = 1;
if (sockopts & STREAM_SOCKOP_SO_KEEPALIVE) {
setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (char*)&val, sizeof(val));
}
}
#endif

/* Set socket values if provided */
if (sockvals != NULL) {
#if defined(TCP_KEEPIDLE)
if (sockvals->mask & PHP_SOCKVAL_TCP_KEEPIDLE) {
setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, (char*)&sockvals->keepalive.keepidle, sizeof(sockvals->keepalive.keepidle));
}
#elif defined(TCP_KEEPALIVE)
/* macOS uses TCP_KEEPALIVE instead of TCP_KEEPIDLE */
if (sockvals->mask & PHP_SOCKVAL_TCP_KEEPIDLE) {
setsockopt(sock, IPPROTO_TCP, TCP_KEEPALIVE, (char*)&sockvals->keepalive.keepidle, sizeof(sockvals->keepalive.keepidle));
}
#endif
#ifdef TCP_KEEPINTVL
if (sockvals->mask & PHP_SOCKVAL_TCP_KEEPINTVL) {
setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, (char*)&sockvals->keepalive.keepintvl, sizeof(sockvals->keepalive.keepintvl));
}
#endif
#ifdef TCP_KEEPCNT
if (sockvals->mask & PHP_SOCKVAL_TCP_KEEPCNT) {
setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, (char*)&sockvals->keepalive.keepcnt, sizeof(sockvals->keepalive.keepcnt));
}
#endif
}

n = php_network_connect_socket(sock, sa, socklen, asynchronous,
timeout ? &working_timeout : NULL,
error_string, error_code);
Expand Down Expand Up @@ -996,7 +1063,15 @@ php_socket_t php_network_connect_socket_to_host(const char *host, unsigned short

return sock;
}
/* }}} */

php_socket_t php_network_connect_socket_to_host(const char *host, unsigned short port,
int socktype, int asynchronous, struct timeval *timeout, zend_string **error_string,
int *error_code, const char *bindto, unsigned short bindport, long sockopts
)
{
return php_network_connect_socket_to_host_ex(host, port, socktype, asynchronous, timeout,
error_string, error_code, bindto, bindport, sockopts, NULL);
}

/* {{{ php_any_addr
* Fills any (wildcard) address into php_sockaddr_storage
Expand Down
23 changes: 23 additions & 0 deletions main/php_network.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ typedef int php_socket_t;
#define STREAM_SOCKOP_IPV6_V6ONLY_ENABLED (1 << 4)
#define STREAM_SOCKOP_TCP_NODELAY (1 << 5)
#define STREAM_SOCKOP_SO_REUSEADDR (1 << 6)
#define STREAM_SOCKOP_SO_KEEPALIVE (1 << 7)

/* uncomment this to debug poll(2) emulation on systems that have poll(2) */
/* #define PHP_USE_POLL_2_EMULATION 1 */
Expand Down Expand Up @@ -266,10 +267,28 @@ typedef struct {
} php_sockaddr_storage;
#endif

#define PHP_SOCKVAL_TCP_KEEPIDLE (1 << 0)
#define PHP_SOCKVAL_TCP_KEEPCNT (1 << 1)
#define PHP_SOCKVAL_TCP_KEEPINTVL (1 << 2)

typedef struct {
unsigned int mask;
struct {
int keepidle;
int keepcnt;
int keepintvl;
} keepalive;
} php_sockvals;

BEGIN_EXTERN_C()
PHPAPI int php_network_getaddresses(const char *host, int socktype, struct sockaddr ***sal, zend_string **error_string);
PHPAPI void php_network_freeaddresses(struct sockaddr **sal);

PHPAPI php_socket_t php_network_connect_socket_to_host_ex(const char *host, unsigned short port,
int socktype, int asynchronous, struct timeval *timeout, zend_string **error_string,
int *error_code, const char *bindto, unsigned short bindport, long sockopts, php_sockvals *sockvals
);

PHPAPI php_socket_t php_network_connect_socket_to_host(const char *host, unsigned short port,
int socktype, int asynchronous, struct timeval *timeout, zend_string **error_string,
int *error_code, const char *bindto, unsigned short bindport, long sockopts
Expand All @@ -286,6 +305,10 @@ PHPAPI int php_network_connect_socket(php_socket_t sockfd,
#define php_connect_nonb(sock, addr, addrlen, timeout) \
php_network_connect_socket((sock), (addr), (addrlen), 0, (timeout), NULL, NULL)

PHPAPI php_socket_t php_network_bind_socket_to_local_addr_ex(const char *host, unsigned port,
int socktype, long sockopts, php_sockvals *sockvals, zend_string **error_string, int *error_code
);

PHPAPI php_socket_t php_network_bind_socket_to_local_addr(const char *host, unsigned port,
int socktype, long sockopts, zend_string **error_string, int *error_code
);
Expand Down
Loading
Loading