Skip to content

Commit 203e59e

Browse files
committed
streams: add so_rcvbuf and so_sndbuf socket context options
They set SO_RCVBUF and SO_SNDBUF in bytes on TCP and UDP sockets, applied before connect() and listen(). A value outside the 1 to INT_MAX range makes the stream creation fail. Close GH-23406
1 parent 01970ad commit 203e59e

8 files changed

Lines changed: 260 additions & 0 deletions

File tree

‎NEWS‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ PHP NEWS
8080
. Fixed writing to a dimension of the object returned by attributes() not
8181
creating the attribute. (Ilia Alshanetsky)
8282

83+
- Streams:
84+
. Added so_rcvbuf and so_sndbuf stream socket context options, setting the
85+
socket receive and send buffer sizes in bytes. (David Carlier)
86+
8387

8488
27 Aug 2026, PHP 8.6.0beta2
8589

‎UPGRADING‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,13 @@ PHP 8.6 UPGRADE NOTES
486486
sockets. A positive value enables lingering for that many seconds, zero
487487
or a negative value disables it. Values above 65535 are clamped as the
488488
linger time is limited to an unsigned short on some platforms.
489+
. Added stream socket context options so_rcvbuf and so_sndbuf that set the
490+
socket receive and send buffer sizes in bytes (SO_RCVBUF and SO_SNDBUF) on
491+
TCP and UDP sockets. The value must be an integer between 1 and 2147483647,
492+
any other value makes the stream creation fail. The operating system may
493+
round, cap or otherwise adjust the requested size, and may stop sizing that
494+
buffer automatically, so the size read back can differ from the one
495+
requested.
489496
. Allowed casting filtered streams as file descriptors for select.
490497
. Added the "write_seek_mode" filter parameter for the bz2, iconv,
491498
zlib, and string stream filters. This parameter must be set via an
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
--TEST--
2+
stream_socket_server() and stream_socket_client() SO_RCVBUF and SO_SNDBUF context options test
3+
--EXTENSIONS--
4+
sockets
5+
--FILE--
6+
<?php
7+
function buffers($stream): array {
8+
$sock = socket_import_stream($stream);
9+
return [
10+
socket_get_option($sock, SOL_SOCKET, SO_RCVBUF),
11+
socket_get_option($sock, SOL_SOCKET, SO_SNDBUF),
12+
];
13+
}
14+
15+
// Shrinking is always honoured, while growing may be capped by the system maximum.
16+
function context(int $rcvbuf, int $sndbuf) {
17+
return stream_context_create(['socket' => [
18+
'so_rcvbuf' => intdiv($rcvbuf, 4),
19+
'so_sndbuf' => intdiv($sndbuf, 4),
20+
]]);
21+
}
22+
23+
function port($server): int {
24+
return (int)substr(strrchr(stream_socket_get_name($server, false), ':'), 1);
25+
}
26+
27+
$control = stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr,
28+
STREAM_SERVER_BIND | STREAM_SERVER_LISTEN);
29+
30+
if (!$control) {
31+
die('Unable to create server');
32+
}
33+
34+
[$rcvbuf, $sndbuf] = buffers($control);
35+
36+
$server = stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr,
37+
STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, context($rcvbuf, $sndbuf));
38+
39+
if (!$server) {
40+
die('Unable to create server');
41+
}
42+
43+
echo "Listen buffers\n";
44+
[$listen_rcvbuf, $listen_sndbuf] = buffers($server);
45+
var_dump($listen_rcvbuf < $rcvbuf);
46+
var_dump($listen_sndbuf < $sndbuf);
47+
48+
// A connection is compared against another connection: some systems size the
49+
// receive buffer of a connected socket on their own.
50+
$control_client = stream_socket_client("tcp://127.0.0.1:" . port($server), $errno, $errstr, 30);
51+
52+
if (!$control_client) {
53+
die('Unable to create client');
54+
}
55+
56+
$control_accepted = stream_socket_accept($server, 1);
57+
58+
if (!$control_accepted) {
59+
die('Unable to accept connection');
60+
}
61+
62+
[, $client_sndbuf] = buffers($control_client);
63+
64+
$client = stream_socket_client("tcp://127.0.0.1:" . port($server), $errno, $errstr, 30,
65+
STREAM_CLIENT_CONNECT, context($client_sndbuf, $client_sndbuf));
66+
67+
if (!$client) {
68+
die('Unable to create client');
69+
}
70+
71+
$accepted = stream_socket_accept($server, 1);
72+
73+
if (!$accepted) {
74+
die('Unable to accept connection');
75+
}
76+
77+
echo "Client buffers\n";
78+
[, $client_sndbuf2] = buffers($client);
79+
var_dump($client_sndbuf2 < $client_sndbuf);
80+
81+
fclose($accepted);
82+
fclose($control_accepted);
83+
fclose($client);
84+
fclose($control_client);
85+
fclose($server);
86+
fclose($control);
87+
88+
?>
89+
--EXPECT--
90+
Listen buffers
91+
bool(true)
92+
bool(true)
93+
Client buffers
94+
bool(true)
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
--TEST--
2+
SO_RCVBUF and SO_SNDBUF context options reject invalid values
3+
--FILE--
4+
<?php
5+
foreach (['so_rcvbuf', 'so_sndbuf'] as $option) {
6+
foreach ([0, -1, 'abc'] as $value) {
7+
$context = stream_context_create(['socket' => [$option => $value]]);
8+
var_dump(@stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr,
9+
STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $context));
10+
echo $errstr, PHP_EOL;
11+
}
12+
}
13+
14+
$context = stream_context_create(['socket' => ['so_rcvbuf' => 0]]);
15+
var_dump(@stream_socket_client("tcp://127.0.0.1:1", $errno, $errstr, 1,
16+
STREAM_CLIENT_CONNECT, $context));
17+
echo $errstr, PHP_EOL;
18+
19+
?>
20+
--EXPECT--
21+
bool(false)
22+
so_rcvbuf context option must be between 1 and 2147483647
23+
bool(false)
24+
so_rcvbuf context option must be between 1 and 2147483647
25+
bool(false)
26+
so_rcvbuf context option must be between 1 and 2147483647
27+
bool(false)
28+
so_sndbuf context option must be between 1 and 2147483647
29+
bool(false)
30+
so_sndbuf context option must be between 1 and 2147483647
31+
bool(false)
32+
so_sndbuf context option must be between 1 and 2147483647
33+
bool(false)
34+
so_rcvbuf context option must be between 1 and 2147483647
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
--TEST--
2+
stream_socket_server() SO_RCVBUF and SO_SNDBUF context options test with UDP
3+
--EXTENSIONS--
4+
sockets
5+
--FILE--
6+
<?php
7+
function buffers($stream): array {
8+
$sock = socket_import_stream($stream);
9+
return [
10+
socket_get_option($sock, SOL_SOCKET, SO_RCVBUF),
11+
socket_get_option($sock, SOL_SOCKET, SO_SNDBUF),
12+
];
13+
}
14+
15+
$control = stream_socket_server("udp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND);
16+
17+
if (!$control) {
18+
die('Unable to create server');
19+
}
20+
21+
[$rcvbuf, $sndbuf] = buffers($control);
22+
23+
// Shrinking is always honoured, while growing may be capped by the system maximum.
24+
$context = stream_context_create(['socket' => [
25+
'so_rcvbuf' => intdiv($rcvbuf, 4),
26+
'so_sndbuf' => intdiv($sndbuf, 4),
27+
]]);
28+
29+
$server = stream_socket_server("udp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND, $context);
30+
31+
if (!$server) {
32+
die('Unable to create server');
33+
}
34+
35+
[$server_rcvbuf, $server_sndbuf] = buffers($server);
36+
echo "Server buffers\n";
37+
var_dump($server_rcvbuf < $rcvbuf);
38+
var_dump($server_sndbuf < $sndbuf);
39+
40+
fclose($server);
41+
fclose($control);
42+
43+
?>
44+
--EXPECT--
45+
Server buffers
46+
bool(true)
47+
bool(true)

‎main/network.c‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,20 @@ PHPAPI int php_network_connect_socket(php_socket_t sockfd,
443443
}
444444
/* }}} */
445445

446+
static void php_network_set_socket_buffers(php_socket_t sock, const php_sockvals *sockvals)
447+
{
448+
#ifdef SO_RCVBUF
449+
if (sockvals->mask & PHP_SOCKVAL_SO_RCVBUF) {
450+
setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char*)&sockvals->rcvbuf, sizeof(sockvals->rcvbuf));
451+
}
452+
#endif
453+
#ifdef SO_SNDBUF
454+
if (sockvals->mask & PHP_SOCKVAL_SO_SNDBUF) {
455+
setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char*)&sockvals->sndbuf, sizeof(sockvals->sndbuf));
456+
}
457+
#endif
458+
}
459+
446460
/* Bind to a local IP address.
447461
* Returns the bound socket, or -1 on failure.
448462
* */
@@ -573,6 +587,7 @@ php_socket_t php_network_bind_socket_to_local_addr_ex(const char *host, unsigned
573587
setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, (char*)&sockvals->keepalive.keepcnt, sizeof(sockvals->keepalive.keepcnt));
574588
}
575589
#endif
590+
php_network_set_socket_buffers(sock, sockvals);
576591
}
577592

578593
n = bind(sock, sa, socklen);
@@ -1077,6 +1092,7 @@ php_socket_t php_network_connect_socket_to_host_ex(const char *host, unsigned sh
10771092
setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, (char*)&sockvals->keepalive.keepcnt, sizeof(sockvals->keepalive.keepcnt));
10781093
}
10791094
#endif
1095+
php_network_set_socket_buffers(sock, sockvals);
10801096
}
10811097

10821098
n = php_network_connect_socket(sock, sa, socklen, asynchronous,

‎main/php_network.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,13 +270,17 @@ typedef struct {
270270
#define PHP_SOCKVAL_TCP_KEEPCNT (1 << 2)
271271
#define PHP_SOCKVAL_TCP_KEEPINTVL (1 << 3)
272272
#define PHP_SOCKVAL_SO_LINGER (1 << 4)
273+
#define PHP_SOCKVAL_SO_RCVBUF (1 << 5)
274+
#define PHP_SOCKVAL_SO_SNDBUF (1 << 6)
273275

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

276278
typedef struct {
277279
unsigned int mask;
278280
int tcp_nodelay;
279281
int linger;
282+
int rcvbuf;
283+
int sndbuf;
280284
struct {
281285
int keepidle;
282286
int keepcnt;

‎main/streams/xp_socket.c‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,50 @@ static inline char *parse_ip_address(php_stream_xport_param *xparam, int *portno
677677
return parse_ip_address_ex(xparam->inputs.name, xparam->inputs.namelen, portno, xparam->want_errortext, &xparam->outputs.error_text);
678678
}
679679

680+
static int php_sockop_parse_buffer_sizes(php_stream *stream, php_stream_xport_param *xparam,
681+
php_sockvals *sockvals)
682+
{
683+
zval *tmpzval;
684+
685+
if (!PHP_STREAM_CONTEXT(stream)) {
686+
return 0;
687+
}
688+
689+
#ifdef SO_RCVBUF
690+
if ((tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_rcvbuf")) != NULL) {
691+
zend_long bufsize = zval_get_long(tmpzval);
692+
693+
if (bufsize < 1 || bufsize > INT_MAX) {
694+
if (xparam->want_errortext) {
695+
xparam->outputs.error_text = strpprintf(0, "so_rcvbuf context option must be between 1 and %d", INT_MAX);
696+
}
697+
return -1;
698+
}
699+
700+
sockvals->mask |= PHP_SOCKVAL_SO_RCVBUF;
701+
sockvals->rcvbuf = (int) bufsize;
702+
}
703+
#endif
704+
705+
#ifdef SO_SNDBUF
706+
if ((tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_sndbuf")) != NULL) {
707+
zend_long bufsize = zval_get_long(tmpzval);
708+
709+
if (bufsize < 1 || bufsize > INT_MAX) {
710+
if (xparam->want_errortext) {
711+
xparam->outputs.error_text = strpprintf(0, "so_sndbuf context option must be between 1 and %d", INT_MAX);
712+
}
713+
return -1;
714+
}
715+
716+
sockvals->mask |= PHP_SOCKVAL_SO_SNDBUF;
717+
sockvals->sndbuf = (int) bufsize;
718+
}
719+
#endif
720+
721+
return 0;
722+
}
723+
680724
static inline int php_tcp_sockop_bind(php_stream *stream, php_netstream_data_t *sock,
681725
php_stream_xport_param *xparam)
682726
{
@@ -720,6 +764,11 @@ static inline int php_tcp_sockop_bind(php_stream *stream, php_netstream_data_t *
720764
return -1;
721765
}
722766

767+
if (php_sockop_parse_buffer_sizes(stream, xparam, &sockvals) == -1) {
768+
efree(host);
769+
return -1;
770+
}
771+
723772
#ifdef IPV6_V6ONLY
724773
if (PHP_STREAM_CONTEXT(stream)
725774
&& (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "ipv6_v6only")) != NULL
@@ -868,6 +917,11 @@ static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_
868917
return -1;
869918
}
870919

920+
if (php_sockop_parse_buffer_sizes(stream, xparam, &sockvals) == -1) {
921+
efree(host);
922+
return -1;
923+
}
924+
871925
if (PHP_STREAM_CONTEXT(stream) && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "bindto")) != NULL) {
872926
if (Z_TYPE_P(tmpzval) != IS_STRING) {
873927
if (xparam->want_errortext) {

0 commit comments

Comments
 (0)