Skip to content

Commit 233e9d7

Browse files
committed
GH-14111 main/streams: adding SO_LINGER to stream options.
1 parent 520787b commit 233e9d7

File tree

5 files changed

+118
-6
lines changed

5 files changed

+118
-6
lines changed

ext/ftp/ftp.c

Lines changed: 1 addition & 1 deletion
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
}
Lines changed: 23 additions & 0 deletions
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

Lines changed: 23 additions & 2 deletions
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

Lines changed: 4 additions & 2 deletions
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

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -659,12 +659,41 @@ static inline char *parse_ip_address(php_stream_xport_param *xparam, int *portno
659659
return parse_ip_address_ex(xparam->inputs.name, xparam->inputs.namelen, portno, xparam->want_errortext, &xparam->outputs.error_text);
660660
}
661661

662+
static long parse_linger(zval *arg, bool *failed)
663+
{
664+
zend_long lval;
665+
*failed = false;
666+
if (Z_TYPE_P(arg) == IS_STRING) {
667+
zend_string *val = Z_STR_P(arg);
668+
uint8_t r = is_numeric_string(ZSTR_VAL(val), ZSTR_LEN(val), &lval, NULL, false);
669+
670+
switch (r) {
671+
case IS_LONG:
672+
break;
673+
default:
674+
*failed = true;
675+
return -1;
676+
}
677+
} else if (Z_TYPE_P(arg) == IS_LONG) {
678+
lval = Z_LVAL_P(arg);
679+
} else {
680+
*failed = true;
681+
return -1;
682+
}
683+
684+
if (lval < 0 || lval > INT_MAX) {
685+
*failed = true;
686+
}
687+
return (long)lval;
688+
}
689+
662690
static inline int php_tcp_sockop_bind(php_stream *stream, php_netstream_data_t *sock,
663691
php_stream_xport_param *xparam)
664692
{
665693
char *host = NULL;
666694
int portno, err;
667695
long sockopts = STREAM_SOCKOP_NONE;
696+
long linger = -1;
668697
zval *tmpzval = NULL;
669698

670699
#ifdef AF_UNIX
@@ -724,9 +753,27 @@ static inline int php_tcp_sockop_bind(php_stream *stream, php_netstream_data_t *
724753
}
725754
#endif
726755

756+
#ifdef SO_LINGER
757+
if (PHP_STREAM_CONTEXT(stream)
758+
&& (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_linger")) != NULL) {
759+
bool failed;
760+
linger = parse_linger(tmpzval, &failed);
761+
762+
if (failed) {
763+
if (xparam->want_errortext) {
764+
xparam->outputs.error_text = strpprintf(0, "Invalid `so_linger` value");
765+
}
766+
return -1;
767+
} else {
768+
sockopts |= STREAM_SOCKOP_SO_LINGER;
769+
}
770+
}
771+
#endif
772+
727773
sock->socket = php_network_bind_socket_to_local_addr(host, portno,
728774
stream->ops == &php_stream_udp_socket_ops ? SOCK_DGRAM : SOCK_STREAM,
729775
sockopts,
776+
linger,
730777
xparam->want_errortext ? &xparam->outputs.error_text : NULL,
731778
&err
732779
);
@@ -747,6 +794,7 @@ static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_
747794
int ret;
748795
zval *tmpzval = NULL;
749796
long sockopts = STREAM_SOCKOP_NONE;
797+
long linger = -1;
750798

751799
#ifdef AF_UNIX
752800
if (stream->ops == &php_stream_unix_socket_ops || stream->ops == &php_stream_unixdg_socket_ops) {
@@ -802,6 +850,23 @@ static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_
802850
}
803851
#endif
804852

853+
#ifdef SO_LINGER
854+
if (PHP_STREAM_CONTEXT(stream)
855+
&& (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_linger")) != NULL) {
856+
bool failed;
857+
linger = parse_linger(tmpzval, &failed);
858+
859+
if (failed) {
860+
if (xparam->want_errortext) {
861+
xparam->outputs.error_text = strpprintf(0, "Invalid `so_linger` value");
862+
}
863+
return -1;
864+
} else {
865+
sockopts |= STREAM_SOCKOP_SO_LINGER;
866+
}
867+
}
868+
#endif
869+
805870
if (stream->ops != &php_stream_udp_socket_ops /* TCP_NODELAY is only applicable for TCP */
806871
#ifdef AF_UNIX
807872
&& stream->ops != &php_stream_unix_socket_ops
@@ -826,7 +891,8 @@ static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_
826891
&err,
827892
bindto,
828893
bindport,
829-
sockopts
894+
sockopts,
895+
linger
830896
);
831897

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

0 commit comments

Comments
 (0)