mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 10:15:07 +08:00
SSL_read() returns at most one TLS record, and ClearOut() emitted a separate read for each one. Peers that use small records, such as nginx with its default 4 KiB ssl_buffer_size or servers doing dynamic record sizing, made every record pay for its own buffer allocation and call into JavaScript. While the next buffered record is application data, keep reading into the same 16 KiB buffer before emitting it, so reads never get larger than one full record already makes them. Batching stops before any other record type, so that e.g. a TLS 1.2 renegotiation is still processed after the data in front of it has been emitted. It also stops before records short enough to be empty, because SSL_read() skips empty records and carries on with the next one. If SSL_read() fails in the middle of a batch, the error is captured first and reported after the data. test-tls-max-send-fragment relied on 'data' chunks matching records, so it now checks the record sizes on the wire. Signed-off-by: Robert Nagy <ronagy@icloud.com> Assisted-by: claude:opus-5.5 PR-URL: https://github.com/nodejs/node/pull/66698 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
66 lines
2.4 KiB
JavaScript
66 lines
2.4 KiB
JavaScript
'use strict';
|
|
const common = require('../common');
|
|
const assert = require('assert');
|
|
if (!common.hasCrypto) common.skip('missing crypto');
|
|
const fixtures = require('../common/fixtures');
|
|
const tls = require('tls');
|
|
|
|
// In reality, this can be a HTTP CONNECT message, signaling the incoming
|
|
// data is TLS encrypted
|
|
const HEAD = 'XXXX';
|
|
|
|
const subserver = tls.createServer({
|
|
key: fixtures.readKey('agent1-key.pem'),
|
|
cert: fixtures.readKey('agent1-cert.pem'),
|
|
})
|
|
.on('secureConnection', common.mustCall(() => {
|
|
process.exit(0);
|
|
}));
|
|
|
|
const server = tls.createServer({
|
|
key: fixtures.readKey('agent1-key.pem'),
|
|
cert: fixtures.readKey('agent1-cert.pem'),
|
|
})
|
|
.listen(client)
|
|
.on('secureConnection', common.mustCall((serverTlsSock) => {
|
|
serverTlsSock.once('data', common.mustCall((chunk) => {
|
|
assert.strictEqual(chunk.toString('latin1', 0, HEAD.length), HEAD);
|
|
// The start of the inner handshake can arrive in the same chunk as
|
|
// HEAD. Put it back so that the inner TLSSocket, which reads whatever
|
|
// is buffered on the socket it wraps, receives it.
|
|
if (chunk.length > HEAD.length) {
|
|
serverTlsSock.pause();
|
|
serverTlsSock.unshift(chunk.subarray(HEAD.length));
|
|
}
|
|
subserver.emit('connection', serverTlsSock);
|
|
}));
|
|
}));
|
|
|
|
function client() {
|
|
const down = tls.connect({
|
|
host: '127.0.0.1',
|
|
port: server.address().port,
|
|
rejectUnauthorized: false
|
|
}).on('secureConnect', () => {
|
|
down.write(HEAD, common.mustSucceed());
|
|
|
|
// Sending tls data on a client TLSSocket with an active write led to a crash:
|
|
//
|
|
// node[16862]: ../src/crypto/crypto_tls.cc:963:virtual int node::crypto::TLSWrap::DoWrite(node::WriteWrap*,
|
|
// uv_buf_t*, size_t, uv_stream_t*): Assertion `!current_write_' failed.
|
|
// 1: 0xb090e0 node::Abort() [node]
|
|
// 2: 0xb0915e [node]
|
|
// 3: 0xca8413 node::crypto::TLSWrap::DoWrite(node::WriteWrap*, uv_buf_t*, unsigned long, uv_stream_s*) [node]
|
|
// 4: 0xcaa549 node::StreamBase::Write(uv_buf_t*, unsigned long, uv_stream_s*, v8::Local<v8::Object>) [node]
|
|
// 5: 0xca88d7 node::crypto::TLSWrap::EncOut() [node]
|
|
// 6: 0xd3df3e [node]
|
|
// 7: 0xd3f35f v8::internal::Builtin_HandleApiCall(int, unsigned long*, v8::internal::Isolate*) [node]
|
|
// 8: 0x15d9ef9 [node]
|
|
// Aborted
|
|
tls.connect({
|
|
socket: down,
|
|
rejectUnauthorized: false
|
|
});
|
|
});
|
|
}
|