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
6 changes: 6 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,12 @@ PHP 8.6 UPGRADE NOTES
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.
. Added stream socket context options so_rcvbuf and so_sndbuf that set the
socket receive and send buffer sizes in bytes (SO_RCVBUF and SO_SNDBUF) on
TCP and UDP sockets. The value must be an integer between 1 and 2147483647,
any other value throws a ValueError. The operating system may round, cap or
otherwise adjust the requested size, and may stop sizing that buffer
automatically, so the size read back can differ from the one requested.
. Allowed 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
94 changes: 94 additions & 0 deletions ext/standard/tests/network/so_rcvbuf_sndbuf.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
--TEST--
stream_socket_server() and stream_socket_client() SO_RCVBUF and SO_SNDBUF context options test
--EXTENSIONS--
sockets
--FILE--
<?php
function buffers($stream): array {
$sock = socket_import_stream($stream);
return [
socket_get_option($sock, SOL_SOCKET, SO_RCVBUF),
socket_get_option($sock, SOL_SOCKET, SO_SNDBUF),
];
}

// Shrinking is always honoured, while growing may be capped by the system maximum.
function context(int $rcvbuf, int $sndbuf) {
return stream_context_create(['socket' => [
'so_rcvbuf' => intdiv($rcvbuf, 4),
'so_sndbuf' => intdiv($sndbuf, 4),
]]);
}

function port($server): int {
return (int)substr(strrchr(stream_socket_get_name($server, false), ':'), 1);
}

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

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

[$rcvbuf, $sndbuf] = buffers($control);

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

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

echo "Listen buffers\n";
[$listen_rcvbuf, $listen_sndbuf] = buffers($server);
var_dump($listen_rcvbuf < $rcvbuf);
var_dump($listen_sndbuf < $sndbuf);

// A connection is compared against another connection: some systems size the
// receive buffer of a connected socket on their own.
$control_client = stream_socket_client("tcp://127.0.0.1:" . port($server), $errno, $errstr, 30);

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

$control_accepted = stream_socket_accept($server, 1);

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

[, $client_sndbuf] = buffers($control_client);

$client = stream_socket_client("tcp://127.0.0.1:" . port($server), $errno, $errstr, 30,
STREAM_CLIENT_CONNECT, context($client_sndbuf, $client_sndbuf));

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

$accepted = stream_socket_accept($server, 1);

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

echo "Client buffers\n";
[, $client_sndbuf2] = buffers($client);
var_dump($client_sndbuf2 < $client_sndbuf);

fclose($accepted);
fclose($control_accepted);
fclose($client);
fclose($control_client);
fclose($server);
fclose($control);

?>
--EXPECT--
Listen buffers
bool(true)
bool(true)
Client buffers
bool(true)
33 changes: 33 additions & 0 deletions ext/standard/tests/network/so_rcvbuf_sndbuf_error.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
--TEST--
SO_RCVBUF and SO_SNDBUF context options reject invalid values
--FILE--
<?php
foreach (['so_rcvbuf', 'so_sndbuf'] as $option) {
foreach ([0, -1, 'abc'] as $value) {
$context = stream_context_create(['socket' => [$option => $value]]);
try {
@stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr,
STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $context);
} catch (ValueError $e) {
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
}
}
}

$context = stream_context_create(['socket' => ['so_rcvbuf' => 0]]);
try {
@stream_socket_client("tcp://127.0.0.1:1", $errno, $errstr, 1,
STREAM_CLIENT_CONNECT, $context);
} catch (ValueError $e) {
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
}
Comment on lines +11 to +23

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
} catch (ValueError $e) {
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
}
}
}
$context = stream_context_create(['socket' => ['so_rcvbuf' => 0]]);
try {
@stream_socket_client("tcp://127.0.0.1:1", $errno, $errstr, 1,
STREAM_CLIENT_CONNECT, $context);
} catch (ValueError $e) {
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
}
} catch (Throwable $e) {
echo $e::class, ': ', $e->getMessage(), "\n";
}
}
}
$context = stream_context_create(['socket' => ['so_rcvbuf' => 0]]);
try {
@stream_socket_client("tcp://127.0.0.1:1", $errno, $errstr, 1,
STREAM_CLIENT_CONNECT, $context);
} catch (Throwable $e) {
echo $e::class, ': ', $e->getMessage(), "\n";
}


?>
--EXPECT--
ValueError: stream context option 'so_rcvbuf' must be between 1 and 2147483647
ValueError: stream context option 'so_rcvbuf' must be between 1 and 2147483647
ValueError: stream context option 'so_rcvbuf' must be between 1 and 2147483647
ValueError: stream context option 'so_sndbuf' must be between 1 and 2147483647
ValueError: stream context option 'so_sndbuf' must be between 1 and 2147483647
ValueError: stream context option 'so_sndbuf' must be between 1 and 2147483647
ValueError: stream context option 'so_rcvbuf' must be between 1 and 2147483647
47 changes: 47 additions & 0 deletions ext/standard/tests/network/so_rcvbuf_sndbuf_udp.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
--TEST--
stream_socket_server() SO_RCVBUF and SO_SNDBUF context options test with UDP
--EXTENSIONS--
sockets
--FILE--
<?php
function buffers($stream): array {
$sock = socket_import_stream($stream);
return [
socket_get_option($sock, SOL_SOCKET, SO_RCVBUF),
socket_get_option($sock, SOL_SOCKET, SO_SNDBUF),
];
}

$control = stream_socket_server("udp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND);

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

[$rcvbuf, $sndbuf] = buffers($control);

// Shrinking is always honoured, while growing may be capped by the system maximum.
$context = stream_context_create(['socket' => [
'so_rcvbuf' => intdiv($rcvbuf, 4),
'so_sndbuf' => intdiv($sndbuf, 4),
]]);

$server = stream_socket_server("udp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND, $context);

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

[$server_rcvbuf, $server_sndbuf] = buffers($server);
echo "Server buffers\n";
var_dump($server_rcvbuf < $rcvbuf);
var_dump($server_sndbuf < $sndbuf);

fclose($server);
fclose($control);

?>
--EXPECT--
Server buffers
bool(true)
bool(true)
16 changes: 16 additions & 0 deletions main/network.c
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,20 @@ PHPAPI int php_network_connect_socket(php_socket_t sockfd,
}
/* }}} */

static void php_network_set_socket_buffers(php_socket_t sock, const php_sockvals *sockvals)
{
#ifdef SO_RCVBUF
if (sockvals->mask & PHP_SOCKVAL_SO_RCVBUF) {
setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char*)&sockvals->rcvbuf, sizeof(sockvals->rcvbuf));
}
#endif
#ifdef SO_SNDBUF
if (sockvals->mask & PHP_SOCKVAL_SO_SNDBUF) {
setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char*)&sockvals->sndbuf, sizeof(sockvals->sndbuf));
}
#endif
}

/* Bind to a local IP address.
* Returns the bound socket, or -1 on failure.
* */
Expand Down Expand Up @@ -573,6 +587,7 @@ php_socket_t php_network_bind_socket_to_local_addr_ex(const char *host, unsigned
setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, (char*)&sockvals->keepalive.keepcnt, sizeof(sockvals->keepalive.keepcnt));
}
#endif
php_network_set_socket_buffers(sock, sockvals);
}

n = bind(sock, sa, socklen);
Expand Down Expand Up @@ -1077,6 +1092,7 @@ php_socket_t php_network_connect_socket_to_host_ex(const char *host, unsigned sh
setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, (char*)&sockvals->keepalive.keepcnt, sizeof(sockvals->keepalive.keepcnt));
}
#endif
php_network_set_socket_buffers(sock, sockvals);
}

n = php_network_connect_socket(sock, sa, socklen, asynchronous,
Expand Down
4 changes: 4 additions & 0 deletions main/php_network.h
Original file line number Diff line number Diff line change
Expand Up @@ -270,13 +270,17 @@ typedef struct {
#define PHP_SOCKVAL_TCP_KEEPCNT (1 << 2)
#define PHP_SOCKVAL_TCP_KEEPINTVL (1 << 3)
#define PHP_SOCKVAL_SO_LINGER (1 << 4)
#define PHP_SOCKVAL_SO_RCVBUF (1 << 5)
#define PHP_SOCKVAL_SO_SNDBUF (1 << 6)

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

typedef struct {
unsigned int mask;
int tcp_nodelay;
int linger;
int rcvbuf;
int sndbuf;
struct {
int keepidle;
int keepcnt;
Expand Down
49 changes: 49 additions & 0 deletions main/streams/xp_socket.c
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,45 @@ static inline char *parse_ip_address(php_stream_xport_param *xparam, int *portno
return parse_ip_address_ex(xparam->inputs.name, xparam->inputs.namelen, portno, xparam->want_errortext, &xparam->outputs.error_text);
}

static int php_sockop_parse_buffer_sizes(php_stream *stream, php_sockvals *sockvals)
{
zval *tmpzval;

if (!PHP_STREAM_CONTEXT(stream)) {
return 0;
}

#ifdef SO_RCVBUF
if ((tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_rcvbuf")) != NULL) {
zend_long bufsize = zval_get_long(tmpzval);

if (bufsize < 1 || bufsize > INT_MAX) {
zend_value_error("stream context option 'so_rcvbuf' must be between 1 and %d", INT_MAX);
return -1;
}

sockvals->mask |= PHP_SOCKVAL_SO_RCVBUF;
sockvals->rcvbuf = (int) bufsize;
}
#endif

#ifdef SO_SNDBUF
if ((tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_sndbuf")) != NULL) {
zend_long bufsize = zval_get_long(tmpzval);

if (bufsize < 1 || bufsize > INT_MAX) {
zend_value_error("stream context option 'so_sndbuf' must be between 1 and %d", INT_MAX);
return -1;
}

sockvals->mask |= PHP_SOCKVAL_SO_SNDBUF;
sockvals->sndbuf = (int) bufsize;
}
#endif

return 0;
}

static inline int php_tcp_sockop_bind(php_stream *stream, php_netstream_data_t *sock,
php_stream_xport_param *xparam)
{
Expand Down Expand Up @@ -720,6 +759,11 @@ static inline int php_tcp_sockop_bind(php_stream *stream, php_netstream_data_t *
return -1;
}

if (php_sockop_parse_buffer_sizes(stream, &sockvals) == -1) {
efree(host);
return -1;
}

#ifdef IPV6_V6ONLY
if (PHP_STREAM_CONTEXT(stream)
&& (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "ipv6_v6only")) != NULL
Expand Down Expand Up @@ -868,6 +912,11 @@ static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_
return -1;
}

if (php_sockop_parse_buffer_sizes(stream, &sockvals) == -1) {
efree(host);
return -1;
}

if (PHP_STREAM_CONTEXT(stream) && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "bindto")) != NULL) {
if (Z_TYPE_P(tmpzval) != IS_STRING) {
if (xparam->want_errortext) {
Expand Down
Loading