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
3 changes: 2 additions & 1 deletion NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,8 @@ PHP NEWS
. Added so_keepalive, tcp_keepidle, tcp_keepintvl and tcp_keepcnt stream
socket context options. (Jakub Zelenka)
. Added so_reuseaddr streams context socket option that allows disabling
address reuse. (Jakub Zelenka)
address resuse. (Jakub Zelenka)
. Added so_linger stream socket context option. (Jakub Zelenka)
. Fixed bug GH-20370 (User stream filters could violate typed property
constraints). (alexandre-daubois)
. Allowed filtered streams to be casted as fd for select. (Jakub Zelenka)
Expand Down
4 changes: 4 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,10 @@ PHP 8.6 UPGRADE NOTES
. Added stream socket context options so_keepalive, tcp_keepidle,
tcp_keepintvl and tcp_keepcnt that allow setting socket keepalive
options.
. Added stream socket context option so_linger that sets SO_LINGER on TCP
sockets. A positive value enables lingering for that many seconds, zero
or a negative value disables it. Values above 65535 are clamped as the
linger time is limited to an unsigned short on some platforms.
. Allowed casting casting filtered streams as file descriptor for select.
. Added the "write_seek_mode stream" filter parameter for the bz2, iconv,
zlib, and string stream filters. This parameter must be set via an
Expand Down
82 changes: 82 additions & 0 deletions ext/standard/tests/network/so_linger.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
--TEST--
stream_socket_server() and stream_socket_client() SO_LINGER context option test
--EXTENSIONS--
sockets
--FILE--
<?php
// Test server with SO_LINGER enabled
$server_context = stream_context_create([
'socket' => [
'so_linger' => 10,
]
]);

$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_LINGER enabled
$client_context = stream_context_create([
'socket' => [
'so_linger' => 8,
]
]);

$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');
}

// macOS expresses SO_LINGER in ticks, SO_LINGER_SEC in seconds.
$so_linger = defined('SO_LINGER_SEC') ? SO_LINGER_SEC : SO_LINGER;

// Verify the listening socket
$listen_sock = socket_import_stream($server);
$listen_linger = socket_get_option($listen_sock, SOL_SOCKET, $so_linger);
echo "Listen SO_LINGER\n";
var_dump($listen_linger['l_onoff'] > 0);
var_dump($listen_linger['l_linger']);

// Verify server side (accepted connection, inherits from the listening socket)
$server_sock = socket_import_stream($accepted);
$server_linger = socket_get_option($server_sock, SOL_SOCKET, $so_linger);
echo "Server SO_LINGER\n";
var_dump($server_linger['l_onoff'] > 0);
var_dump($server_linger['l_linger']);

// Verify client side
$client_sock = socket_import_stream($client);
$client_linger = socket_get_option($client_sock, SOL_SOCKET, $so_linger);
echo "Client SO_LINGER\n";
var_dump($client_linger['l_onoff'] > 0);
var_dump($client_linger['l_linger']);

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

?>
--EXPECT--
Listen SO_LINGER
bool(true)
int(10)
Server SO_LINGER
bool(true)
int(10)
Client SO_LINGER
bool(true)
int(8)
36 changes: 36 additions & 0 deletions main/network.c
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,24 @@ php_socket_t php_network_bind_socket_to_local_addr_ex(const char *host, unsigned

/* Set socket values if provided */
if (sockvals != NULL) {
#ifdef SO_LINGER
if (sockvals->mask & PHP_SOCKVAL_SO_LINGER) {
/* l_linger is an unsigned short on Windows, so clamp rather than
* truncate: a truncated value may still be in range and would then
* be applied silently (e.g. 65536 becoming 0, an abortive close). */
unsigned short secs = sockvals->linger > USHRT_MAX
? USHRT_MAX : (unsigned short)sockvals->linger;
struct linger linger_val = {
Comment thread
bukka marked this conversation as resolved.
.l_onoff = (sockvals->linger > 0),
.l_linger = sockvals->linger > 0 ? secs : 0
};
#ifdef SO_LINGER_SEC
setsockopt(sock, SOL_SOCKET, SO_LINGER_SEC, (char*)&linger_val, sizeof(linger_val));
#else
setsockopt(sock, SOL_SOCKET, SO_LINGER, (char*)&linger_val, sizeof(linger_val));
#endif
}
#endif
#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));
Expand Down Expand Up @@ -1021,6 +1039,24 @@ php_socket_t php_network_connect_socket_to_host_ex(const char *host, unsigned sh

/* Set socket values if provided */
if (sockvals != NULL) {
#ifdef SO_LINGER
if (sockvals->mask & PHP_SOCKVAL_SO_LINGER) {
/* l_linger is an unsigned short on Windows, so clamp rather than
* truncate: a truncated value may still be in range and would then
* be applied silently (e.g. 65536 becoming 0, an abortive close). */
unsigned short secs = sockvals->linger > USHRT_MAX
? USHRT_MAX : (unsigned short)sockvals->linger;
struct linger linger_val = {
.l_onoff = (sockvals->linger > 0),
.l_linger = sockvals->linger > 0 ? secs : 0
};
#ifdef SO_LINGER_SEC
setsockopt(sock, SOL_SOCKET, SO_LINGER_SEC, (char*)&linger_val, sizeof(linger_val));
#else
setsockopt(sock, SOL_SOCKET, SO_LINGER, (char*)&linger_val, sizeof(linger_val));
#endif
}
#endif
#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));
Expand Down
2 changes: 2 additions & 0 deletions main/php_network.h
Original file line number Diff line number Diff line change
Expand Up @@ -269,12 +269,14 @@ typedef struct {
#define PHP_SOCKVAL_TCP_KEEPIDLE (1 << 1)
#define PHP_SOCKVAL_TCP_KEEPCNT (1 << 2)
#define PHP_SOCKVAL_TCP_KEEPINTVL (1 << 3)
#define PHP_SOCKVAL_SO_LINGER (1 << 4)

#define PHP_SOCKVAL_IS_SET(sockvals, opt) ((sockvals)->mask & (opt))

typedef struct {
unsigned int mask;
int tcp_nodelay;
int linger;
struct {
int keepidle;
int keepcnt;
Expand Down
20 changes: 20 additions & 0 deletions main/streams/xp_socket.c
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,16 @@ static inline int php_tcp_sockop_bind(php_stream *stream, php_netstream_data_t *
}
#endif

#ifdef SO_LINGER
if (PHP_STREAM_XPORT_IS_TCP(stream)
&& PHP_STREAM_CONTEXT(stream)
&& (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_linger")) != NULL
) {
sockvals.mask |= PHP_SOCKVAL_SO_LINGER;
sockvals.linger = (int)zval_get_long(tmpzval);
}
#endif

#ifdef SO_KEEPALIVE
if (PHP_STREAM_XPORT_IS_TCP(stream) /* SO_KEEPALIVE is only applicable for TCP */
&& PHP_STREAM_CONTEXT(stream)
Expand Down Expand Up @@ -887,6 +897,16 @@ static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_
sockopts |= STREAM_SOCKOP_TCP_NODELAY;
}

#ifdef SO_LINGER
if (PHP_STREAM_XPORT_IS_TCP(stream)
&& PHP_STREAM_CONTEXT(stream)
&& (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_linger")) != NULL
) {
sockvals.mask |= PHP_SOCKVAL_SO_LINGER;
sockvals.linger = (int)zval_get_long(tmpzval);
}
#endif

#ifdef SO_KEEPALIVE
if (PHP_STREAM_XPORT_IS_TCP(stream) /* SO_KEEPALIVE is only applicable for TCP */
&& PHP_STREAM_CONTEXT(stream)
Expand Down
Loading