tls: close StreamWrap and its stream correctly
When sockets of the "net" module destroyed, they will call `this._handle.close()` which will also emit EOF if not emitted before. This feature makes sockets on the other side emit "end" and "close" even though we haven't called `end()`. As `stream` of `StreamWrap` are likely to be instances of `net.Socket`, calling `destroy()` manually will avoid issues that don't properly close wrapped connections. Fixes: https://github.com/nodejs/node/issues/14605 PR-URL: https://github.com/nodejs/node/pull/23654 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
parent
beb0f03e78
commit
517955a474
@ -68,6 +68,12 @@ class JSStreamWrap extends Socket {
|
|||||||
if (this._handle)
|
if (this._handle)
|
||||||
this._handle.emitEOF();
|
this._handle.emitEOF();
|
||||||
});
|
});
|
||||||
|
// Some `Stream` don't pass `hasError` parameters when closed.
|
||||||
|
stream.once('close', () => {
|
||||||
|
// Errors emitted from `stream` have also been emitted to this instance
|
||||||
|
// so that we don't pass errors to `destroy()` again.
|
||||||
|
this.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
super({ handle, manualStart: true });
|
super({ handle, manualStart: true });
|
||||||
this.stream = stream;
|
this.stream = stream;
|
||||||
@ -188,6 +194,14 @@ class JSStreamWrap extends Socket {
|
|||||||
doClose(cb) {
|
doClose(cb) {
|
||||||
const handle = this._handle;
|
const handle = this._handle;
|
||||||
|
|
||||||
|
// When sockets of the "net" module destroyed, they will call
|
||||||
|
// `this._handle.close()` which will also emit EOF if not emitted before.
|
||||||
|
// This feature makes sockets on the other side emit "end" and "close"
|
||||||
|
// even though we haven't called `end()`. As `stream` are likely to be
|
||||||
|
// instances of `net.Socket`, calling `stream.destroy()` manually will
|
||||||
|
// avoid issues that don't properly close wrapped connections.
|
||||||
|
this.stream.destroy();
|
||||||
|
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
// Should be already set by net.js
|
// Should be already set by net.js
|
||||||
assert.strictEqual(this._handle, null);
|
assert.strictEqual(this._handle, null);
|
||||||
|
69
test/parallel/test-tls-destroy-stream.js
Normal file
69
test/parallel/test-tls-destroy-stream.js
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
if (!common.hasCrypto) common.skip('missing crypto');
|
||||||
|
|
||||||
|
const fixtures = require('../common/fixtures');
|
||||||
|
const makeDuplexPair = require('../common/duplexpair');
|
||||||
|
const net = require('net');
|
||||||
|
const assert = require('assert');
|
||||||
|
const tls = require('tls');
|
||||||
|
|
||||||
|
// This test ensures that an instance of StreamWrap should emit "end" and
|
||||||
|
// "close" when the socket on the other side call `destroy()` instead of
|
||||||
|
// `end()`.
|
||||||
|
// Refs: https://github.com/nodejs/node/issues/14605
|
||||||
|
const CONTENT = 'Hello World';
|
||||||
|
const tlsServer = tls.createServer(
|
||||||
|
{
|
||||||
|
key: fixtures.readSync('test_key.pem'),
|
||||||
|
cert: fixtures.readSync('test_cert.pem'),
|
||||||
|
ca: [fixtures.readSync('test_ca.pem')],
|
||||||
|
},
|
||||||
|
(socket) => {
|
||||||
|
socket.on('error', common.mustNotCall());
|
||||||
|
socket.on('close', common.mustCall());
|
||||||
|
socket.write(CONTENT);
|
||||||
|
socket.destroy();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const server = net.createServer((conn) => {
|
||||||
|
conn.on('error', common.mustNotCall());
|
||||||
|
// Assume that we want to use data to determine what to do with connections.
|
||||||
|
conn.once('data', common.mustCall((chunk) => {
|
||||||
|
const { clientSide, serverSide } = makeDuplexPair();
|
||||||
|
serverSide.on('close', common.mustCall(() => {
|
||||||
|
conn.destroy();
|
||||||
|
}));
|
||||||
|
clientSide.pipe(conn);
|
||||||
|
conn.pipe(clientSide);
|
||||||
|
|
||||||
|
conn.on('close', common.mustCall(() => {
|
||||||
|
clientSide.destroy();
|
||||||
|
}));
|
||||||
|
clientSide.on('close', common.mustCall(() => {
|
||||||
|
conn.destroy();
|
||||||
|
}));
|
||||||
|
|
||||||
|
process.nextTick(() => {
|
||||||
|
conn.unshift(chunk);
|
||||||
|
});
|
||||||
|
|
||||||
|
tlsServer.emit('connection', serverSide);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(0, () => {
|
||||||
|
const port = server.address().port;
|
||||||
|
const conn = tls.connect({ port, rejectUnauthorized: false }, () => {
|
||||||
|
conn.on('data', common.mustCall((data) => {
|
||||||
|
assert.strictEqual(data.toString('utf8'), CONTENT);
|
||||||
|
}));
|
||||||
|
conn.on('error', common.mustNotCall());
|
||||||
|
conn.on(
|
||||||
|
'close',
|
||||||
|
common.mustCall(() => server.close()),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
118
test/parallel/test-wrap-js-stream-destroy.js
Normal file
118
test/parallel/test-wrap-js-stream-destroy.js
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
const StreamWrap = require('_stream_wrap');
|
||||||
|
const net = require('net');
|
||||||
|
|
||||||
|
// This test ensures that when we directly call `socket.destroy()` without
|
||||||
|
// having called `socket.end()` on an instance of streamWrap, it will
|
||||||
|
// still emit EOF which makes the socket on the other side emit "end" and
|
||||||
|
// "close" events, and vice versa.
|
||||||
|
{
|
||||||
|
let port;
|
||||||
|
const server = net.createServer((socket) => {
|
||||||
|
socket.on('error', common.mustNotCall());
|
||||||
|
socket.on('end', common.mustNotCall());
|
||||||
|
socket.on('close', common.mustCall());
|
||||||
|
socket.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(() => {
|
||||||
|
port = server.address().port;
|
||||||
|
createSocket();
|
||||||
|
});
|
||||||
|
|
||||||
|
function createSocket() {
|
||||||
|
let streamWrap;
|
||||||
|
const socket = new net.connect({
|
||||||
|
port,
|
||||||
|
}, () => {
|
||||||
|
socket.on('error', common.mustNotCall());
|
||||||
|
socket.on('end', common.mustCall());
|
||||||
|
socket.on('close', common.mustCall());
|
||||||
|
|
||||||
|
streamWrap.on('error', common.mustNotCall());
|
||||||
|
// The "end" events will be emitted which is as same as
|
||||||
|
// the same situation for an instance of `net.Socket` without
|
||||||
|
// `StreamWrap`.
|
||||||
|
streamWrap.on('end', common.mustCall());
|
||||||
|
// Destroying a socket in the server side should emit EOF and cause
|
||||||
|
// the corresponding client-side socket closed.
|
||||||
|
streamWrap.on('close', common.mustCall(() => {
|
||||||
|
server.close();
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
streamWrap = new StreamWrap(socket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy the streamWrap and test again.
|
||||||
|
{
|
||||||
|
let port;
|
||||||
|
const server = net.createServer((socket) => {
|
||||||
|
socket.on('error', common.mustNotCall());
|
||||||
|
socket.on('end', common.mustCall());
|
||||||
|
socket.on('close', common.mustCall(() => {
|
||||||
|
server.close();
|
||||||
|
}));
|
||||||
|
// Do not `socket.end()` and directly `socket.destroy()`.
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(() => {
|
||||||
|
port = server.address().port;
|
||||||
|
createSocket();
|
||||||
|
});
|
||||||
|
|
||||||
|
function createSocket() {
|
||||||
|
let streamWrap;
|
||||||
|
const socket = new net.connect({
|
||||||
|
port,
|
||||||
|
}, () => {
|
||||||
|
socket.on('error', common.mustNotCall());
|
||||||
|
socket.on('end', common.mustNotCall());
|
||||||
|
socket.on('close', common.mustCall());
|
||||||
|
|
||||||
|
streamWrap.on('error', common.mustNotCall());
|
||||||
|
streamWrap.on('end', common.mustNotCall());
|
||||||
|
// Destroying a socket in the server side should emit EOF and cause
|
||||||
|
// the corresponding client-side socket closed.
|
||||||
|
streamWrap.on('close', common.mustCall());
|
||||||
|
streamWrap.destroy();
|
||||||
|
});
|
||||||
|
streamWrap = new StreamWrap(socket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy the client socket and test again.
|
||||||
|
{
|
||||||
|
let port;
|
||||||
|
const server = net.createServer((socket) => {
|
||||||
|
socket.on('error', common.mustNotCall());
|
||||||
|
socket.on('end', common.mustCall());
|
||||||
|
socket.on('close', common.mustCall(() => {
|
||||||
|
server.close();
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(() => {
|
||||||
|
port = server.address().port;
|
||||||
|
createSocket();
|
||||||
|
});
|
||||||
|
|
||||||
|
function createSocket() {
|
||||||
|
let streamWrap;
|
||||||
|
const socket = new net.connect({
|
||||||
|
port,
|
||||||
|
}, () => {
|
||||||
|
socket.on('error', common.mustNotCall());
|
||||||
|
socket.on('end', common.mustNotCall());
|
||||||
|
socket.on('close', common.mustCall());
|
||||||
|
|
||||||
|
streamWrap.on('error', common.mustNotCall());
|
||||||
|
streamWrap.on('end', common.mustNotCall());
|
||||||
|
streamWrap.on('close', common.mustCall());
|
||||||
|
socket.destroy();
|
||||||
|
});
|
||||||
|
streamWrap = new StreamWrap(socket);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user