Skip to content

GH-14111 main/streams: adding SO_LINGER to stream options. #14129

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
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
2 changes: 1 addition & 1 deletion ext/ftp/ftp.c
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ ftp_open(const char *host, short port, zend_long timeout_sec)

ftp->fd = php_network_connect_socket_to_host(host,
(unsigned short) (port ? port : 21), SOCK_STREAM,
0, &tv, NULL, NULL, NULL, 0, STREAM_SOCKOP_NONE);
0, &tv, NULL, NULL, NULL, 0, STREAM_SOCKOP_NONE, NULL);
if (ftp->fd == -1) {
goto bail;
}
Expand Down
38 changes: 38 additions & 0 deletions ext/standard/tests/network/gh14111.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
--TEST--
Testing linger `socket` option.
--FILE--
<?php
for ($i=0; $i<100; $i++) {
$port = rand(10000, 65000);
/* Setup socket server */
$server = @stream_socket_server("tcp://127.0.0.1:$port");
if ($server) {
break;
}
}
$client = stream_socket_client("tcp://127.0.0.1:$port");
$context = stream_context_create(['socket' => ['linger' => false]]);
$socket = stream_socket_client("tcp://127.0.0.1:$port", $errno, $errstr, 0, STREAM_CLIENT_CONNECT, $context);
var_dump($socket);
$context = stream_context_create(['socket' => ['linger' => PHP_INT_MAX + 1]]);
$socket = stream_socket_client("tcp://127.0.0.1:$port", $errno, $errstr, 0, STREAM_CLIENT_CONNECT, $context);
var_dump($socket);
$context = stream_context_create(['socket' => ['linger' => 5]]);
$socket = stream_socket_client("tcp://127.0.0.1:$port", $errno, $errstr, 1, STREAM_CLIENT_CONNECT, $context);
var_dump($socket);
stream_set_blocking($socket, true);
var_dump(stream_socket_sendto($socket, "data"));
$data = base64_decode("1oIBAAABAAAAAAAAB2V4YW1wbGUDb3JnAAABAAE=");
stream_set_blocking($socket, 0);
stream_socket_sendto($socket, $data);
stream_socket_shutdown($socket, STREAM_SHUT_RDWR);
stream_socket_shutdown($server, STREAM_SHUT_RDWR);
?>
--EXPECTF--
Warning: stream_socket_client(): Unable to connect to tcp://127.0.0.1:%d (Invalid `linger` value) in %s on line %d
bool(false)

Warning: stream_socket_client(): Unable to connect to tcp://127.0.0.1:%d (Invalid `linger` value) in %s on line %d
bool(false)
resource(%d) of type (stream)
int(4)
29 changes: 27 additions & 2 deletions main/network.c
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ static inline void sub_times(struct timeval a, struct timeval b, struct timeval
* */
/* {{{ php_network_bind_socket_to_local_addr */
php_socket_t php_network_bind_socket_to_local_addr(const char *host, unsigned port,
int socktype, long sockopts, zend_string **error_string, int *error_code
int socktype, long sockopts, void *option, zend_string **error_string, int *error_code
)
{
int num_addrs, n, err = 0;
Expand Down Expand Up @@ -470,6 +470,17 @@ php_socket_t php_network_bind_socket_to_local_addr(const char *host, unsigned po
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char*)&sockoptval, sizeof(sockoptval));
}
#endif
#ifdef PHP_SO_LINGER
if (sockopts & STREAM_SOCKOP_SO_LINGER) {
ZEND_ASSERT(option != NULL);
long linger = *(long *)option;
struct linger val = {
.l_onoff = (linger > 0),
.l_linger = (int)linger
};
setsockopt(sock, IPPROTO_TCP, PHP_SO_LINGER, (char*)&val, sizeof(val));
}
#endif

n = bind(sock, sa, socklen);

Expand Down Expand Up @@ -766,7 +777,8 @@ PHPAPI php_socket_t php_network_accept_incoming(php_socket_t srvsock,
/* {{{ php_network_connect_socket_to_host */
php_socket_t php_network_connect_socket_to_host(const char *host, unsigned short port,
int socktype, int asynchronous, struct timeval *timeout, zend_string **error_string,
int *error_code, const char *bindto, unsigned short bindport, long sockopts
int *error_code, const char *bindto, unsigned short bindport, long sockopts,
void *option
)
{
int num_addrs, n, fatal = 0;
Expand Down Expand Up @@ -896,6 +908,19 @@ php_socket_t php_network_connect_socket_to_host(const char *host, unsigned short
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char*)&val, sizeof(val));
}
}
#endif
#ifdef PHP_SO_LINGER
{
if (sockopts & STREAM_SOCKOP_SO_LINGER) {
ZEND_ASSERT(option != NULL);
long linger = *(long *)option;
struct linger val = {
.l_onoff = linger > 0,
.l_linger = (int)linger
};
setsockopt(sock, IPPROTO_TCP, PHP_SO_LINGER, (char*)&val, sizeof(val));
}
}
#endif
n = php_network_connect_socket(sock, sa, socklen, asynchronous,
timeout ? &working_timeout : NULL,
Expand Down
14 changes: 12 additions & 2 deletions main/php_network.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@
# define PHP_IS_TRANSIENT_ERROR(err) (err == EAGAIN)
#endif

#ifdef SO_LINGER
# ifdef SO_LINGER_SEC
# define PHP_SO_LINGER SO_LINGER_SEC
# else
# define PHP_SO_LINGER SO_LINGER
# endif
#endif

#ifdef PHP_WIN32
#define php_socket_errno() WSAGetLastError()
#else
Expand Down Expand Up @@ -116,6 +124,7 @@ typedef int php_socket_t;
#define STREAM_SOCKOP_IPV6_V6ONLY (1 << 3)
#define STREAM_SOCKOP_IPV6_V6ONLY_ENABLED (1 << 4)
#define STREAM_SOCKOP_TCP_NODELAY (1 << 5)
#define STREAM_SOCKOP_SO_LINGER (1 << 6)


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

PHPAPI php_socket_t php_network_connect_socket_to_host(const char *host, unsigned short port,
int socktype, int asynchronous, struct timeval *timeout, zend_string **error_string,
int *error_code, const char *bindto, unsigned short bindport, long sockopts
int *error_code, const char *bindto, unsigned short bindport, long sockopts,
void *option
);

PHPAPI int php_network_connect_socket(php_socket_t sockfd,
Expand All @@ -280,7 +290,7 @@ PHPAPI int php_network_connect_socket(php_socket_t sockfd,
php_network_connect_socket((sock), (addr), (addrlen), 0, (timeout), NULL, NULL)

PHPAPI php_socket_t php_network_bind_socket_to_local_addr(const char *host, unsigned port,
int socktype, long sockopts, zend_string **error_string, int *error_code
int socktype, long sockopts, void *option, zend_string **error_string, int *error_code
);

PHPAPI php_socket_t php_network_accept_incoming(php_socket_t srvsock,
Expand Down
95 changes: 88 additions & 7 deletions main/streams/xp_socket.c
Original file line number Diff line number Diff line change
Expand Up @@ -659,12 +659,42 @@ 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 long parse_linger(zval *arg, bool *failed)
{
zend_long lval;
*failed = false;
if (Z_TYPE_P(arg) == IS_STRING) {
zend_string *val = Z_STR_P(arg);
uint8_t r = is_numeric_string(ZSTR_VAL(val), ZSTR_LEN(val), &lval, NULL, false);

switch (r) {
case IS_LONG:
break;
default:
*failed = true;
return -1;
}
} else if (Z_TYPE_P(arg) == IS_LONG) {
lval = Z_LVAL_P(arg);
} else {
*failed = true;
return -1;
}

if (lval < 0 || lval > INT_MAX) {
*failed = true;
}
return (long)lval;
}

static inline int php_tcp_sockop_bind(php_stream *stream, php_netstream_data_t *sock,
php_stream_xport_param *xparam)
{
char *host = NULL;
int portno, err;
long sockopts = STREAM_SOCKOP_NONE;
long linger = -1;
void *option = NULL;
zval *tmpzval = NULL;

#ifdef AF_UNIX
Expand Down Expand Up @@ -724,9 +754,32 @@ static inline int php_tcp_sockop_bind(php_stream *stream, php_netstream_data_t *
}
#endif

#ifdef PHP_SO_LINGER
if (PHP_STREAM_CONTEXT(stream)
&& (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "linger")) != NULL) {
bool failed;
linger = parse_linger(tmpzval, &failed);

if (failed) {
if (xparam->want_errortext) {
xparam->outputs.error_text = strpprintf(0, "Invalid `linger` value");
}
if (host) {
efree(host);
}
return -1;
} else {
sockopts |= STREAM_SOCKOP_SO_LINGER;
}

option = &linger;
}
#endif

sock->socket = php_network_bind_socket_to_local_addr(host, portno,
stream->ops == &php_stream_udp_socket_ops ? SOCK_DGRAM : SOCK_STREAM,
sockopts,
option,
xparam->want_errortext ? &xparam->outputs.error_text : NULL,
&err
);
Expand All @@ -747,6 +800,8 @@ static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_
int ret;
zval *tmpzval = NULL;
long sockopts = STREAM_SOCKOP_NONE;
long linger = -1;
void *option = NULL;

#ifdef AF_UNIX
if (stream->ops == &php_stream_unix_socket_ops || stream->ops == &php_stream_unixdg_socket_ops) {
Expand Down Expand Up @@ -802,15 +857,40 @@ static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_
}
#endif

#ifdef PHP_SO_LINGER
if (PHP_STREAM_CONTEXT(stream)
&& (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "linger")) != NULL) {
bool failed;
linger = parse_linger(tmpzval, &failed);

if (failed) {
if (xparam->want_errortext) {
xparam->outputs.error_text = strpprintf(0, "Invalid `linger` value");
}
if (host) {
efree(host);
}
if (bindto) {
efree(bindto);
}
return -1;
} else {
sockopts |= STREAM_SOCKOP_SO_LINGER;
}

option = &linger;
}
#endif

if (stream->ops != &php_stream_udp_socket_ops /* TCP_NODELAY is only applicable for TCP */
#ifdef AF_UNIX
&& stream->ops != &php_stream_unix_socket_ops
&& stream->ops != &php_stream_unixdg_socket_ops
&& stream->ops != &php_stream_unix_socket_ops
&& stream->ops != &php_stream_unixdg_socket_ops
#endif
&& PHP_STREAM_CONTEXT(stream)
&& (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_nodelay")) != NULL
&& zend_is_true(tmpzval)
) {
&& PHP_STREAM_CONTEXT(stream)
&& (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_nodelay")) != NULL
&& zend_is_true(tmpzval)
) {
sockopts |= STREAM_SOCKOP_TCP_NODELAY;
}

Expand All @@ -826,7 +906,8 @@ static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_
&err,
bindto,
bindport,
sockopts
sockopts,
option
);

ret = sock->socket == -1 ? -1 : 0;
Expand Down
Loading