Commit 9582d8e6d74 for php.net
commit 9582d8e6d746befac8b3aa84a2cb503d64030716
Author: Jakub Zelenka <bukka@php.net>
Date: Mon Nov 3 22:22:01 2025 +0100
Add stream socket keepalive context options
This adds so_keepalive, tcp_keepidle, tcp_keepintvl and tcp_keepcnt
stream socket context options that are used to set their upper case
C macro variants by setsockopt function.
The test requires sockets extension and just tests that the values are
being set. This is because a real test would be slow and difficult to
show that those options really work due to how they work internally.
Closes GH-20381
diff --git a/NEWS b/NEWS
index 12740c65c3c..3b20cbb026c 100644
--- a/NEWS
+++ b/NEWS
@@ -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
diff --git a/UPGRADING b/UPGRADING
index d52827bf961..f9e50623519 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -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
diff --git a/ext/standard/tests/network/so_keepalive.phpt b/ext/standard/tests/network/so_keepalive.phpt
new file mode 100644
index 00000000000..a437d16694b
--- /dev/null
+++ b/ext/standard/tests/network/so_keepalive.phpt
@@ -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
diff --git a/main/network.c b/main/network.c
index 96953531c37..58f688a4fea 100644
--- a/main/network.c
+++ b/main/network.c
@@ -452,9 +452,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;
@@ -533,6 +533,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);
@@ -560,7 +589,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)
{
@@ -824,11 +859,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;
@@ -952,6 +985,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);
@@ -998,7 +1065,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
diff --git a/main/php_network.h b/main/php_network.h
index 45e1e190263..08d6bbc140c 100644
--- a/main/php_network.h
+++ b/main/php_network.h
@@ -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 */
@@ -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
@@ -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
);
diff --git a/main/streams/xp_socket.c b/main/streams/xp_socket.c
index db35a9b7952..969d364ffe1 100644
--- a/main/streams/xp_socket.c
+++ b/main/streams/xp_socket.c
@@ -16,7 +16,7 @@
#include "php.h"
#include "ext/standard/file.h"
-#include "streams/php_streams_int.h"
+#include "php_streams.h"
#include "php_network.h"
#if defined(PHP_WIN32) || defined(__riscos__)
@@ -48,8 +48,18 @@ static const php_stream_ops php_stream_udp_socket_ops;
#ifdef AF_UNIX
static const php_stream_ops php_stream_unix_socket_ops;
static const php_stream_ops php_stream_unixdg_socket_ops;
-#endif
+#define PHP_STREAM_XPORT_IS_UNIX_DG(stream) php_stream_is(stream, &php_stream_unixdg_socket_ops)
+#define PHP_STREAM_XPORT_IS_UNIX_ST(stream) php_stream_is(stream, &php_stream_unix_socket_ops)
+#define PHP_STREAM_XPORT_IS_UNIX(stream) \
+ (PHP_STREAM_XPORT_IS_UNIX_DG(stream) || PHP_STREAM_XPORT_IS_UNIX_ST(stream))
+#else
+#define PHP_STREAM_XPORT_IS_UNIX_DG(stream) false
+#define PHP_STREAM_XPORT_IS_UNIX_STD(stream) false
+#define PHP_STREAM_XPORT_IS_UNIX(stream) false
+#endif
+#define PHP_STREAM_XPORT_IS_UDP(stream) (php_stream_is(stream, &php_stream_udp_socket_ops))
+#define PHP_STREAM_XPORT_IS_TCP(stream) (!PHP_STREAM_XPORT_IS_UNIX(stream) && !PHP_STREAM_XPORT_IS_UDP(stream))
static int php_tcp_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam);
@@ -669,18 +679,19 @@ static inline int php_tcp_sockop_bind(php_stream *stream, php_netstream_data_t *
int portno, err;
long sockopts = STREAM_SOCKOP_NONE;
zval *tmpzval = NULL;
+ php_sockvals sockvals = {0};
#ifdef AF_UNIX
- if (stream->ops == &php_stream_unix_socket_ops || stream->ops == &php_stream_unixdg_socket_ops) {
+ if (PHP_STREAM_XPORT_IS_UNIX(stream)) {
struct sockaddr_un unix_addr;
- sock->socket = socket(PF_UNIX, stream->ops == &php_stream_unix_socket_ops ? SOCK_STREAM : SOCK_DGRAM, 0);
+ sock->socket = socket(PF_UNIX, PHP_STREAM_XPORT_IS_UNIX_ST(stream) ? SOCK_STREAM : SOCK_DGRAM, 0);
if (sock->socket == SOCK_ERR) {
if (xparam->want_errortext) {
char errstr[256];
xparam->outputs.error_text = strpprintf(0, "Failed to create unix%s socket %s",
- stream->ops == &php_stream_unix_socket_ops ? "" : "datagram",
+ PHP_STREAM_XPORT_IS_UNIX_ST(stream) ? "" : " datagram",
php_socket_strerror_s(errno, errstr, sizeof(errstr)));
}
return -1;
@@ -729,7 +740,7 @@ static inline int php_tcp_sockop_bind(php_stream *stream, php_netstream_data_t *
#endif
#ifdef SO_BROADCAST
- if (stream->ops == &php_stream_udp_socket_ops /* SO_BROADCAST is only applicable for UDP */
+ if (PHP_STREAM_XPORT_IS_UDP(stream) /* SO_BROADCAST is only applicable for UDP */
&& PHP_STREAM_CONTEXT(stream)
&& (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_broadcast")) != NULL
&& zend_is_true(tmpzval)
@@ -738,9 +749,53 @@ static inline int php_tcp_sockop_bind(php_stream *stream, php_netstream_data_t *
}
#endif
- sock->socket = php_network_bind_socket_to_local_addr(host, portno,
- stream->ops == &php_stream_udp_socket_ops ? SOCK_DGRAM : SOCK_STREAM,
+#ifdef SO_KEEPALIVE
+ if (PHP_STREAM_XPORT_IS_TCP(stream) /* SO_KEEPALIVE is only applicable for TCP */
+ && PHP_STREAM_CONTEXT(stream)
+ && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_keepalive")) != NULL
+ && zend_is_true(tmpzval)
+ ) {
+ sockopts |= STREAM_SOCKOP_SO_KEEPALIVE;
+ }
+#endif
+
+ /* Parse TCP keepalive parameters - only for TCP streams */
+ if (PHP_STREAM_XPORT_IS_TCP(stream)) {
+#if defined(TCP_KEEPIDLE) || defined(TCP_KEEPALIVE)
+ if (PHP_STREAM_CONTEXT(stream)
+ && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_keepidle")) != NULL
+ && Z_TYPE_P(tmpzval) == IS_LONG
+ ) {
+ sockvals.mask |= PHP_SOCKVAL_TCP_KEEPIDLE;
+ sockvals.keepalive.keepidle = (int)Z_LVAL_P(tmpzval);
+ }
+#endif
+
+#ifdef TCP_KEEPINTVL
+ if (PHP_STREAM_CONTEXT(stream)
+ && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_keepintvl")) != NULL
+ && Z_TYPE_P(tmpzval) == IS_LONG
+ ) {
+ sockvals.mask |= PHP_SOCKVAL_TCP_KEEPINTVL;
+ sockvals.keepalive.keepintvl = (int)Z_LVAL_P(tmpzval);
+ }
+#endif
+
+#ifdef TCP_KEEPCNT
+ if (PHP_STREAM_CONTEXT(stream)
+ && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_keepcnt")) != NULL
+ && Z_TYPE_P(tmpzval) == IS_LONG
+ ) {
+ sockvals.mask |= PHP_SOCKVAL_TCP_KEEPCNT;
+ sockvals.keepalive.keepcnt = (int)Z_LVAL_P(tmpzval);
+ }
+#endif
+ }
+
+ sock->socket = php_network_bind_socket_to_local_addr_ex(host, portno,
+ PHP_STREAM_XPORT_IS_UDP(stream) ? SOCK_DGRAM : SOCK_STREAM,
sockopts,
+ sockvals.mask ? &sockvals : NULL,
xparam->want_errortext ? &xparam->outputs.error_text : NULL,
&err
);
@@ -761,12 +816,13 @@ static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_
int ret;
zval *tmpzval = NULL;
long sockopts = STREAM_SOCKOP_NONE;
+ php_sockvals sockvals = {0};
#ifdef AF_UNIX
- if (stream->ops == &php_stream_unix_socket_ops || stream->ops == &php_stream_unixdg_socket_ops) {
+ if (PHP_STREAM_XPORT_IS_UNIX(stream)) {
struct sockaddr_un unix_addr;
- sock->socket = socket(PF_UNIX, stream->ops == &php_stream_unix_socket_ops ? SOCK_STREAM : SOCK_DGRAM, 0);
+ sock->socket = socket(PF_UNIX, PHP_STREAM_XPORT_IS_UNIX_ST(stream) ? SOCK_STREAM : SOCK_DGRAM, 0);
if (sock->socket == SOCK_ERR) {
if (xparam->want_errortext) {
@@ -807,7 +863,7 @@ static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_
}
#ifdef SO_BROADCAST
- if (stream->ops == &php_stream_udp_socket_ops /* SO_BROADCAST is only applicable for UDP */
+ if (PHP_STREAM_XPORT_IS_UDP(stream) /* SO_BROADCAST is only applicable for UDP */
&& PHP_STREAM_CONTEXT(stream)
&& (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_broadcast")) != NULL
&& zend_is_true(tmpzval)
@@ -816,11 +872,7 @@ static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_
}
#endif
- if (stream->ops != &php_stream_udp_socket_ops /* TCP_NODELAY is only applicable for TCP */
-#ifdef AF_UNIX
- && stream->ops != &php_stream_unix_socket_ops
- && stream->ops != &php_stream_unixdg_socket_ops
-#endif
+ if (PHP_STREAM_XPORT_IS_TCP(stream) /* TCP_NODELAY is only applicable for TCP */
&& PHP_STREAM_CONTEXT(stream)
&& (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_nodelay")) != NULL
&& zend_is_true(tmpzval)
@@ -828,19 +880,63 @@ static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_
sockopts |= STREAM_SOCKOP_TCP_NODELAY;
}
+#ifdef SO_KEEPALIVE
+ if (PHP_STREAM_XPORT_IS_TCP(stream) /* SO_KEEPALIVE is only applicable for TCP */
+ && PHP_STREAM_CONTEXT(stream)
+ && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_keepalive")) != NULL
+ && zend_is_true(tmpzval)
+ ) {
+ sockopts |= STREAM_SOCKOP_SO_KEEPALIVE;
+ }
+#endif
+
+ /* Parse TCP keepalive parameters - only for TCP streams */
+ if (PHP_STREAM_XPORT_IS_TCP(stream)) {
+#if defined(TCP_KEEPIDLE) || defined(TCP_KEEPALIVE)
+ if (PHP_STREAM_CONTEXT(stream)
+ && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_keepidle")) != NULL
+ && Z_TYPE_P(tmpzval) == IS_LONG
+ ) {
+ sockvals.mask |= PHP_SOCKVAL_TCP_KEEPIDLE;
+ sockvals.keepalive.keepidle = (int)Z_LVAL_P(tmpzval);
+ }
+#endif
+
+#ifdef TCP_KEEPINTVL
+ if (PHP_STREAM_CONTEXT(stream)
+ && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_keepintvl")) != NULL
+ && Z_TYPE_P(tmpzval) == IS_LONG
+ ) {
+ sockvals.mask |= PHP_SOCKVAL_TCP_KEEPINTVL;
+ sockvals.keepalive.keepintvl = (int)Z_LVAL_P(tmpzval);
+ }
+#endif
+
+#ifdef TCP_KEEPCNT
+ if (PHP_STREAM_CONTEXT(stream)
+ && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_keepcnt")) != NULL
+ && Z_TYPE_P(tmpzval) == IS_LONG
+ ) {
+ sockvals.mask |= PHP_SOCKVAL_TCP_KEEPCNT;
+ sockvals.keepalive.keepcnt = (int)Z_LVAL_P(tmpzval);
+ }
+#endif
+ }
+
/* Note: the test here for php_stream_udp_socket_ops is important, because we
* want the default to be TCP sockets so that the openssl extension can
* re-use this code. */
- sock->socket = php_network_connect_socket_to_host(host, portno,
- stream->ops == &php_stream_udp_socket_ops ? SOCK_DGRAM : SOCK_STREAM,
+ sock->socket = php_network_connect_socket_to_host_ex(host, portno,
+ PHP_STREAM_XPORT_IS_UDP(stream) ? SOCK_DGRAM : SOCK_STREAM,
xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC,
xparam->inputs.timeout,
xparam->want_errortext ? &xparam->outputs.error_text : NULL,
&err,
bindto,
bindport,
- sockopts
+ sockopts,
+ sockvals.mask ? &sockvals : NULL
);
ret = sock->socket == -1 ? -1 : 0;