-
Notifications
You must be signed in to change notification settings - Fork 29.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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: #14605 PR-URL: #23654 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com>
- Loading branch information
1 parent
874371d
commit 9aec3e6
Showing
3 changed files
with
201 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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()), | ||
); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} |