From 66fc5a343656509b8683d299f992836aab043bf7 Mon Sep 17 00:00:00 2001 From: Joe Watkins Date: Wed, 27 Apr 2016 22:56:15 +0100 Subject: [PATCH] Implement #51879 stream context socket option tcp_nodelay --- ext/openssl/xp_ssl.c | 50 +++++++-------- .../streams/stream_context_tcp_nodelay.phpt | 22 +++++++ .../stream_context_tcp_nodelay_fopen.phpt | 21 +++++++ .../stream_context_tcp_nodelay_server.phpt | 47 ++++++++++++++ main/network.c | 23 ++++++- main/php_network.h | 5 +- main/streams/xp_socket.c | 61 +++++++++++-------- 7 files changed, 177 insertions(+), 52 deletions(-) create mode 100644 ext/standard/tests/streams/stream_context_tcp_nodelay.phpt create mode 100644 ext/standard/tests/streams/stream_context_tcp_nodelay_fopen.phpt create mode 100644 ext/standard/tests/streams/stream_context_tcp_nodelay_server.phpt diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index 53536a1a77..6f542323d2 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -2199,39 +2199,39 @@ static inline int php_openssl_tcp_sockop_accept(php_stream *stream, php_openssl_ php_stream_xport_param *xparam STREAMS_DC) { int clisock; - + zend_bool nodelay = 0; + zval *tmpzval = NULL; + xparam->outputs.client = NULL; + if ((tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_nodelay")) != NULL && + zend_is_true(tmpzval)) { + nodelay = 1; + } + clisock = php_network_accept_incoming(sock->s.socket, - xparam->want_textaddr ? &xparam->outputs.textaddr : NULL, - xparam->want_addr ? &xparam->outputs.addr : NULL, - xparam->want_addr ? &xparam->outputs.addrlen : NULL, - xparam->inputs.timeout, - xparam->want_errortext ? &xparam->outputs.error_text : NULL, - &xparam->outputs.error_code - ); + xparam->want_textaddr ? &xparam->outputs.textaddr : NULL, + xparam->want_addr ? &xparam->outputs.addr : NULL, + xparam->want_addr ? &xparam->outputs.addrlen : NULL, + xparam->inputs.timeout, + xparam->want_errortext ? &xparam->outputs.error_text : NULL, + &xparam->outputs.error_code, + nodelay); if (clisock >= 0) { - php_openssl_netstream_data_t *clisockdata; + php_openssl_netstream_data_t *clisockdata = (php_openssl_netstream_data_t*) emalloc(sizeof(*clisockdata)); - clisockdata = emalloc(sizeof(*clisockdata)); + /* copy underlying tcp fields */ + memset(clisockdata, 0, sizeof(*clisockdata)); + memcpy(clisockdata, sock, sizeof(clisockdata->s)); - if (clisockdata == NULL) { - closesocket(clisock); - /* technically a fatal error */ - } else { - /* copy underlying tcp fields */ - memset(clisockdata, 0, sizeof(*clisockdata)); - memcpy(clisockdata, sock, sizeof(clisockdata->s)); - - clisockdata->s.socket = clisock; + clisockdata->s.socket = clisock; - xparam->outputs.client = php_stream_alloc_rel(stream->ops, clisockdata, NULL, "r+"); - if (xparam->outputs.client) { - xparam->outputs.client->ctx = stream->ctx; - if (stream->ctx) { - GC_REFCOUNT(stream->ctx)++; - } + xparam->outputs.client = php_stream_alloc_rel(stream->ops, clisockdata, NULL, "r+"); + if (xparam->outputs.client) { + xparam->outputs.client->ctx = stream->ctx; + if (stream->ctx) { + GC_REFCOUNT(stream->ctx)++; } } diff --git a/ext/standard/tests/streams/stream_context_tcp_nodelay.phpt b/ext/standard/tests/streams/stream_context_tcp_nodelay.phpt new file mode 100644 index 0000000000..bcb7a81b21 --- /dev/null +++ b/ext/standard/tests/streams/stream_context_tcp_nodelay.phpt @@ -0,0 +1,22 @@ +--TEST-- +stream context tcp_nodelay +--SKIPIF-- + +--FILE-- + [ + "tcp_nodelay" => true + ] +]); + +$stream = stream_socket_client( + "tcp://www.php.net:80", $errno, $errstr, 10, STREAM_CLIENT_CONNECT, $ctxt); + +$socket = + socket_import_stream($stream); + +var_dump(socket_get_option($socket, SOL_TCP, TCP_NODELAY)); +?> +--EXPECT-- +int(1) diff --git a/ext/standard/tests/streams/stream_context_tcp_nodelay_fopen.phpt b/ext/standard/tests/streams/stream_context_tcp_nodelay_fopen.phpt new file mode 100644 index 0000000000..2638935938 --- /dev/null +++ b/ext/standard/tests/streams/stream_context_tcp_nodelay_fopen.phpt @@ -0,0 +1,21 @@ +--TEST-- +stream context tcp_nodelay fopen +--SKIPIF-- + +--FILE-- + [ + "tcp_nodelay" => true + ] +]); + +$stream = fopen("http://www.php.net", "r", false, $ctxt); + +$socket = + @socket_import_stream($stream); + +var_dump(socket_get_option($socket, STREAM_IPPROTO_TCP, TCP_NODELAY)); +?> +--EXPECT-- +int(1) diff --git a/ext/standard/tests/streams/stream_context_tcp_nodelay_server.phpt b/ext/standard/tests/streams/stream_context_tcp_nodelay_server.phpt new file mode 100644 index 0000000000..0c4e687ffc --- /dev/null +++ b/ext/standard/tests/streams/stream_context_tcp_nodelay_server.phpt @@ -0,0 +1,47 @@ +--TEST-- +stream context tcp_nodelay server +--SKIPIF-- + +--FILE-- + [ + "tcp_nodelay" => true + ] + ]); + + $server = stream_socket_server( + "tcp://127.0.0.1:9099", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt); + + $client = stream_socket_accept($server); + + var_dump(socket_get_option( + socket_import_stream($server), + SOL_TCP, TCP_NODELAY)); + + var_dump(socket_get_option( + socket_import_stream($client), + SOL_TCP, TCP_NODELAY)); + + fclose($client); + fclose($server); +CODE; + +$clientCode = <<<'CODE' + $test = stream_socket_client( + "tcp://127.0.0.1:9099", $errno, $errstr, 10); + + sleep(1); + + fclose($test); +CODE; + +include sprintf( + "%s/../../../openssl/tests/ServerClientTestCase.inc", + dirname(__FILE__)); +ServerClientTestCase::getInstance()->run($serverCode, $clientCode); +?> +--EXPECT-- +int(0) +int(1) diff --git a/main/network.c b/main/network.c index e7f1b84281..e53dc66a8a 100644 --- a/main/network.c +++ b/main/network.c @@ -487,6 +487,11 @@ php_socket_t php_network_bind_socket_to_local_addr(const char *host, unsigned po setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char*)&sockoptval, sizeof(sockoptval)); } #endif +#ifdef TCP_NODELAY + if (sockopts & STREAM_SOCKOP_TCP_NODELAY) { + setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char*)&sockoptval, sizeof(sockoptval)); + } +#endif n = bind(sock, sa, socklen); @@ -726,7 +731,8 @@ PHPAPI php_socket_t php_network_accept_incoming(php_socket_t srvsock, socklen_t *addrlen, struct timeval *timeout, zend_string **error_string, - int *error_code + int *error_code, + int tcp_nodelay ) { php_socket_t clisock = -1; @@ -750,6 +756,11 @@ PHPAPI php_socket_t php_network_accept_incoming(php_socket_t srvsock, textaddr, addr, addrlen ); + if (tcp_nodelay) { +#ifdef TCP_NODELAY + setsockopt(clisock, IPPROTO_TCP, TCP_NODELAY, (char*)&tcp_nodelay, sizeof(tcp_nodelay)); +#endif + } } else { error = php_socket_errno(); } @@ -767,7 +778,6 @@ PHPAPI php_socket_t php_network_accept_incoming(php_socket_t srvsock, /* }}} */ - /* Connect to a remote host using an interruptible connect with optional timeout. * Optionally, the connect can be made asynchronously, which will implicitly * enable non-blocking mode on the socket. @@ -902,6 +912,15 @@ skip_bind: } } #endif + +#ifdef TCP_NODELAY + { + int val = 1; + if (sockopts & STREAM_SOCKOP_TCP_NODELAY) { + setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char*)&val, sizeof(val)); + } + } +#endif n = php_network_connect_socket(sock, sa, socklen, asynchronous, timeout ? &working_timeout : NULL, error_string, error_code); diff --git a/main/php_network.h b/main/php_network.h index 4ddae5cd4c..2c342b4668 100644 --- a/main/php_network.h +++ b/main/php_network.h @@ -28,6 +28,7 @@ #else # undef closesocket # define closesocket close +# include #endif #ifndef HAVE_SHUTDOWN @@ -115,6 +116,7 @@ typedef int php_socket_t; #define STREAM_SOCKOP_SO_BROADCAST (1 << 2) #define STREAM_SOCKOP_IPV6_V6ONLY (1 << 3) #define STREAM_SOCKOP_IPV6_V6ONLY_ENABLED (1 << 4) +#define STREAM_SOCKOP_TCP_NODELAY (1 << 5) /* uncomment this to debug poll(2) emulation on systems that have poll(2) */ @@ -265,7 +267,8 @@ PHPAPI php_socket_t php_network_accept_incoming(php_socket_t srvsock, socklen_t *addrlen, struct timeval *timeout, zend_string **error_string, - int *error_code + int *error_code, + int tcp_nodelay ); PHPAPI int php_network_get_sock_name(php_socket_t sock, diff --git a/main/streams/xp_socket.c b/main/streams/xp_socket.c index 7a21fbef44..cc76ace510 100644 --- a/main/streams/xp_socket.c +++ b/main/streams/xp_socket.c @@ -742,6 +742,18 @@ static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_ } #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 +#endif + && 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; + } + /* Note: the test here for php_stream_udp_socket_ops is important, because we * want the default to be TCP sockets so that the openssl extension can * re-use this code. */ @@ -783,36 +795,37 @@ static inline int php_tcp_sockop_accept(php_stream *stream, php_netstream_data_t php_stream_xport_param *xparam STREAMS_DC) { int clisock; + zend_bool nodelay = 0; + zval *tmpzval = NULL; xparam->outputs.client = NULL; + if ((NULL != PHP_STREAM_CONTEXT(stream)) && + (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_nodelay")) != NULL && + zend_is_true(tmpzval)) { + nodelay = 1; + } + clisock = php_network_accept_incoming(sock->socket, - xparam->want_textaddr ? &xparam->outputs.textaddr : NULL, - xparam->want_addr ? &xparam->outputs.addr : NULL, - xparam->want_addr ? &xparam->outputs.addrlen : NULL, - xparam->inputs.timeout, - xparam->want_errortext ? &xparam->outputs.error_text : NULL, - &xparam->outputs.error_code - ); + xparam->want_textaddr ? &xparam->outputs.textaddr : NULL, + xparam->want_addr ? &xparam->outputs.addr : NULL, + xparam->want_addr ? &xparam->outputs.addrlen : NULL, + xparam->inputs.timeout, + xparam->want_errortext ? &xparam->outputs.error_text : NULL, + &xparam->outputs.error_code, + nodelay); if (clisock >= 0) { - php_netstream_data_t *clisockdata; - - clisockdata = emalloc(sizeof(*clisockdata)); - - if (clisockdata == NULL) { - close(clisock); - /* technically a fatal error */ - } else { - memcpy(clisockdata, sock, sizeof(*clisockdata)); - clisockdata->socket = clisock; - - xparam->outputs.client = php_stream_alloc_rel(stream->ops, clisockdata, NULL, "r+"); - if (xparam->outputs.client) { - xparam->outputs.client->ctx = stream->ctx; - if (stream->ctx) { - GC_REFCOUNT(stream->ctx)++; - } + php_netstream_data_t *clisockdata = (php_netstream_data_t*) emalloc(sizeof(*clisockdata)); + + memcpy(clisockdata, sock, sizeof(*clisockdata)); + clisockdata->socket = clisock; + + xparam->outputs.client = php_stream_alloc_rel(stream->ops, clisockdata, NULL, "r+"); + if (xparam->outputs.client) { + xparam->outputs.client->ctx = stream->ctx; + if (stream->ctx) { + GC_REFCOUNT(stream->ctx)++; } } } -- 2.40.0