Description
- Version: v10.0.0-pre
- Platform: Linux newtons7 4.9.0-6-rt-amd64 deps: update openssl to 1.0.1j #1 SMP PREEMPT RT Debian 4.9.82-1+deb9u3 (2018-03-02) x86_64 GNU/Linux
- Subsystem: TLS
With the upcoming Node.js v10.0.0, performance of sockets created with createSecurePair()
is degraded, with several effects as unresponsive event loop, crashing connections and general sluggishness.
Steps To Reproduce
Create a proxy that receives encrypted TLS connections and resends to a plain socket server. It should be a regular socket server that uses tls.createSecurePair()
to decrypt data, and send it unencrypted to the socket server. Bombard it with a lot of connections and relatively small packets. Soon enough the proxy will hog down and become unresponsive.
client -> proxy -> server
It is best to create multiple workers to act as clients, since otherwise the main event loop will be too busy sending data.
Sample Script
Run the script combined-tls.js
:
git clone https://github.com/logtrust/tls-server-demo
cd tls-server-demo
npm install
# raise the number of available file descriptors to ~10k
ulimit -n 10240
node lib/combined-tls.js
Running the last line using Node.js v8 or v9 will produce a responsive server, that prints a trace every five seconds; using Node.js v10.0.0 pre will yield an unresponsive server that prints traces erratically, generates strange errors and takes ages to shut down after ^C'ing it.
See the README.md for runtime options such as modifying the number of incoming connections. The script uses the stdio
module only for parsing options, and strictly Node.js core modules after that.
Possible Explanation
After the great work by @addaleax in #17882, the unholy mess that was lib/_tls_legacy.js
was greatly simplified to use new tls.TLSSocket()
underneath. A crucial bit was not ported, though: the buffering capabilities of tls.SecurePair()
.
As a consequence, the new code in lib/internal/streams/duplexpair.js
sends events (either data
or readable
depending on the stream mode) as they are received and decrypted. When many small messages are sent to the TLSSocket
it will hog down. In contrast, the old code behaved like TCP: when heavily loaded it started buffering data and sending bigger and bigger packets to the cleartext
stream.
The sample code shows an average of 32 bytes per event with TLSSocket
(v10), the same exact value sent by the client per packet; while the original createSecurePair()
(v8 and v9) shows a bigger rate that depends on system load: I have seen anything from 60 to 300 bytes/event. The number of events in TLSSocket
is so high with v10 that attempting to resend everything from the proxy makes the event loop unresponsive.
A possible workaround is to buffer packets in the proxy server, but this is suboptimal as it means double-guessing what the stream is doing. Also, buffering in the proxy would minimize packet resend time, but we would still be processing a lot of unnecessary events.
Importance
This issue is critical for us since it affects our ability to ingest data using both secure and insecure connections to the same port.