Skip to content

Commit 19e8ddc

Browse files
committed
GH-14111 main/streams: adding SO_LINGER to stream options.
1 parent f442cec commit 19e8ddc

File tree

5 files changed

+118
-6
lines changed

5 files changed

+118
-6
lines changed

ext/ftp/ftp.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ ftp_open(const char *host, short port, zend_long timeout_sec)
129129

130130
ftp->fd = php_network_connect_socket_to_host(host,
131131
(unsigned short) (port ? port : 21), SOCK_STREAM,
132-
0, &tv, NULL, NULL, NULL, 0, STREAM_SOCKOP_NONE);
132+
0, &tv, NULL, NULL, NULL, 0, STREAM_SOCKOP_NONE, -1);
133133
if (ftp->fd == -1) {
134134
goto bail;
135135
}
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
--TEST--
2+
Testing so_linger `socket` option.
3+
--SKIPIF--
4+
<?php
5+
if (getenv("SKIP_ONLINE_TESTS")) die('skip online test');
6+
if (!in_array('https', stream_get_wrappers())) die('skip: https wrapper is required');
7+
?>
8+
--FILE--
9+
<?php
10+
$context = stream_context_create(['socket' => ['so_linger' => false]]);
11+
var_dump(file_get_contents('https://httpbin.org/get', false, $context) !== false);
12+
$context = stream_context_create(['socket' => ['so_linger' => PHP_INT_MAX + 1]]);
13+
var_dump(file_get_contents('https://httpbin.org/get', false, $context) !== false);
14+
$context = stream_context_create(['socket' => ['so_linger' => 3]]);
15+
var_dump(file_get_contents('https://httpbin.org/get', false, $context) !== false);
16+
?>
17+
--EXPECTF--
18+
Warning: file_get_contents(https://httpbin.org/get): Failed to open stream: Invalid `so_linger` value in %s on line %d
19+
bool(false)
20+
21+
Warning: file_get_contents(https://httpbin.org/get): Failed to open stream: Invalid `so_linger` value in %s on line %d
22+
bool(false)
23+
bool(true)

main/network.c

+23-2
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ static inline void sub_times(struct timeval a, struct timeval b, struct timeval
402402
* */
403403
/* {{{ php_network_bind_socket_to_local_addr */
404404
php_socket_t php_network_bind_socket_to_local_addr(const char *host, unsigned port,
405-
int socktype, long sockopts, zend_string **error_string, int *error_code
405+
int socktype, long sockopts, long linger, zend_string **error_string, int *error_code
406406
)
407407
{
408408
int num_addrs, n, err = 0;
@@ -470,6 +470,15 @@ php_socket_t php_network_bind_socket_to_local_addr(const char *host, unsigned po
470470
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char*)&sockoptval, sizeof(sockoptval));
471471
}
472472
#endif
473+
#ifdef SO_LINGER
474+
if (sockopts & STREAM_SOCKOP_SO_LINGER) {
475+
struct linger val = {
476+
.l_onoff = (linger > 0),
477+
.l_linger = (int)linger
478+
};
479+
setsockopt(sock, IPPROTO_TCP, SO_LINGER, (char*)&val, sizeof(val));
480+
}
481+
#endif
473482

474483
n = bind(sock, sa, socklen);
475484

@@ -766,7 +775,8 @@ PHPAPI php_socket_t php_network_accept_incoming(php_socket_t srvsock,
766775
/* {{{ php_network_connect_socket_to_host */
767776
php_socket_t php_network_connect_socket_to_host(const char *host, unsigned short port,
768777
int socktype, int asynchronous, struct timeval *timeout, zend_string **error_string,
769-
int *error_code, const char *bindto, unsigned short bindport, long sockopts
778+
int *error_code, const char *bindto, unsigned short bindport, long sockopts,
779+
long linger
770780
)
771781
{
772782
int num_addrs, n, fatal = 0;
@@ -896,6 +906,17 @@ php_socket_t php_network_connect_socket_to_host(const char *host, unsigned short
896906
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char*)&val, sizeof(val));
897907
}
898908
}
909+
#endif
910+
#ifdef SO_LINGER
911+
{
912+
if (sockopts & STREAM_SOCKOP_SO_LINGER) {
913+
struct linger val = {
914+
.l_onoff = linger > 0,
915+
.l_linger = (int)linger
916+
};
917+
setsockopt(sock, IPPROTO_TCP, SO_LINGER, (char*)&val, sizeof(val));
918+
}
919+
}
899920
#endif
900921
n = php_network_connect_socket(sock, sa, socklen, asynchronous,
901922
timeout ? &working_timeout : NULL,

main/php_network.h

+4-2
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ typedef int php_socket_t;
116116
#define STREAM_SOCKOP_IPV6_V6ONLY (1 << 3)
117117
#define STREAM_SOCKOP_IPV6_V6ONLY_ENABLED (1 << 4)
118118
#define STREAM_SOCKOP_TCP_NODELAY (1 << 5)
119+
#define STREAM_SOCKOP_SO_LINGER (1 << 6)
119120

120121

121122
/* uncomment this to debug poll(2) emulation on systems that have poll(2) */
@@ -265,7 +266,8 @@ PHPAPI void php_network_freeaddresses(struct sockaddr **sal);
265266

266267
PHPAPI php_socket_t php_network_connect_socket_to_host(const char *host, unsigned short port,
267268
int socktype, int asynchronous, struct timeval *timeout, zend_string **error_string,
268-
int *error_code, const char *bindto, unsigned short bindport, long sockopts
269+
int *error_code, const char *bindto, unsigned short bindport, long sockopts,
270+
long linger
269271
);
270272

271273
PHPAPI int php_network_connect_socket(php_socket_t sockfd,
@@ -280,7 +282,7 @@ PHPAPI int php_network_connect_socket(php_socket_t sockfd,
280282
php_network_connect_socket((sock), (addr), (addrlen), 0, (timeout), NULL, NULL)
281283

282284
PHPAPI php_socket_t php_network_bind_socket_to_local_addr(const char *host, unsigned port,
283-
int socktype, long sockopts, zend_string **error_string, int *error_code
285+
int socktype, long sockopts, long linger, zend_string **error_string, int *error_code
284286
);
285287

286288
PHPAPI php_socket_t php_network_accept_incoming(php_socket_t srvsock,

main/streams/xp_socket.c

+67-1
Original file line numberDiff line numberDiff line change
@@ -654,12 +654,41 @@ static inline char *parse_ip_address(php_stream_xport_param *xparam, int *portno
654654
return parse_ip_address_ex(xparam->inputs.name, xparam->inputs.namelen, portno, xparam->want_errortext, &xparam->outputs.error_text);
655655
}
656656

657+
static long parse_linger(zval *arg, bool *failed)
658+
{
659+
zend_long lval;
660+
*failed = false;
661+
if (Z_TYPE_P(arg) == IS_STRING) {
662+
zend_string *val = Z_STR_P(arg);
663+
uint8_t r = is_numeric_string(ZSTR_VAL(val), ZSTR_LEN(val), &lval, NULL, false);
664+
665+
switch (r) {
666+
case IS_LONG:
667+
break;
668+
default:
669+
*failed = true;
670+
return -1;
671+
}
672+
} else if (Z_TYPE_P(arg) == IS_LONG) {
673+
lval = Z_LVAL_P(arg);
674+
} else {
675+
*failed = true;
676+
return -1;
677+
}
678+
679+
if (lval < 0 || lval > INT_MAX) {
680+
*failed = true;
681+
}
682+
return (long)lval;
683+
}
684+
657685
static inline int php_tcp_sockop_bind(php_stream *stream, php_netstream_data_t *sock,
658686
php_stream_xport_param *xparam)
659687
{
660688
char *host = NULL;
661689
int portno, err;
662690
long sockopts = STREAM_SOCKOP_NONE;
691+
long linger = -1;
663692
zval *tmpzval = NULL;
664693

665694
#ifdef AF_UNIX
@@ -719,9 +748,27 @@ static inline int php_tcp_sockop_bind(php_stream *stream, php_netstream_data_t *
719748
}
720749
#endif
721750

751+
#ifdef SO_LINGER
752+
if (PHP_STREAM_CONTEXT(stream)
753+
&& (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_linger")) != NULL) {
754+
bool failed;
755+
linger = parse_linger(tmpzval, &failed);
756+
757+
if (failed) {
758+
if (xparam->want_errortext) {
759+
xparam->outputs.error_text = strpprintf(0, "Invalid `so_linger` value");
760+
}
761+
return -1;
762+
} else {
763+
sockopts |= STREAM_SOCKOP_SO_LINGER;
764+
}
765+
}
766+
#endif
767+
722768
sock->socket = php_network_bind_socket_to_local_addr(host, portno,
723769
stream->ops == &php_stream_udp_socket_ops ? SOCK_DGRAM : SOCK_STREAM,
724770
sockopts,
771+
linger,
725772
xparam->want_errortext ? &xparam->outputs.error_text : NULL,
726773
&err
727774
);
@@ -742,6 +789,7 @@ static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_
742789
int ret;
743790
zval *tmpzval = NULL;
744791
long sockopts = STREAM_SOCKOP_NONE;
792+
long linger = -1;
745793

746794
#ifdef AF_UNIX
747795
if (stream->ops == &php_stream_unix_socket_ops || stream->ops == &php_stream_unixdg_socket_ops) {
@@ -797,6 +845,23 @@ static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_
797845
}
798846
#endif
799847

848+
#ifdef SO_LINGER
849+
if (PHP_STREAM_CONTEXT(stream)
850+
&& (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_linger")) != NULL) {
851+
bool failed;
852+
linger = parse_linger(tmpzval, &failed);
853+
854+
if (failed) {
855+
if (xparam->want_errortext) {
856+
xparam->outputs.error_text = strpprintf(0, "Invalid `so_linger` value");
857+
}
858+
return -1;
859+
} else {
860+
sockopts |= STREAM_SOCKOP_SO_LINGER;
861+
}
862+
}
863+
#endif
864+
800865
if (stream->ops != &php_stream_udp_socket_ops /* TCP_NODELAY is only applicable for TCP */
801866
#ifdef AF_UNIX
802867
&& stream->ops != &php_stream_unix_socket_ops
@@ -821,7 +886,8 @@ static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_
821886
&err,
822887
bindto,
823888
bindport,
824-
sockopts
889+
sockopts,
890+
linger
825891
);
826892

827893
ret = sock->socket == -1 ? -1 : 0;

0 commit comments

Comments
 (0)