Repository navigation
Cannot reuse an estabilished TLS socket for a HTTP/2 session #35475
Description
Activity
What does
Protocol errormean? It could be anything.Ah, because the server sends us data and I don't catch that...Actually no because there's nothing to read directly from the socket... What's happening?- addedhttp2Issues and PRs related to the http2 subsystem.Issues and PRs related to the http2 subsystem.
on Oct 4, 2020 I think that the socket must have no data buffered.
Putting
console.log(socket.readableLength);after theawaittells us that there is some data to be read. But if we lower the timeout to100ms then there's no queued data to be read and it passes.Dunno... Wrapping `socket` like this makes things work
const http2 = require('http2'); const tls = require('tls'); const stream = require('stream'); const options = { ALPNProtocols: ['h2'], host: 'nghttp2.org', servername: 'nghttp2.org', port: 443 }; const socket = tls.connect(options, async () => { console.log('Connected!'); await new Promise(resolve => setTimeout(resolve, 1000)); const proxy = new stream.Duplex({ read(...args) { const x = socket.read(); if (x !== null) { this.push(x); } }, final(...args) { socket.end(...args); }, write(...args) { socket.write(...args); }, destroy(...args) { socket.destroy(...args); } }); const session = http2.connect('https://nghttp2.org', { createConnection: () => proxy }); session.once('remoteSettings', () => { console.log('Received remote settings!'); }); });
@szmarczak your analysis is correct, the problem is the data awaiting in the socket on the JS side
wrapping the socket makes the stream pass through JS which solves the problemWhen the TLS code establishes the connections, it asks for HTTP2 via APLN. The remote server sends immediately its SETTINGS frame which is received by the TLS code and kept in its buffer.
Then HTTP2 client attaches its Listener and sends its client SETTINGS frame and waits for a read callback.
The first read callback will be for the second frame of the server and this triggers a 505, "Remote peer returned unexpected data while we expected SETTINGS frame. Perhaps, peer does not support HTTP/2 properly." which is transformed into a PROTOCOL_ERROR on the user side.Now, the question is how is this supposed to work 😃
The TLS code has this:// Socket already has some buffered data - emulate receiving it if (socket && socket.readableLength) { let buf; while ((buf = socket.read()) !== null) tlsSocket._handle.receive(buf); }
and this:
void TLSWrap::Receive(const FunctionCallbackInfo<Value>& args) { TLSWrap* wrap; ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); ArrayBufferViewContents<char> buffer(args[0]); const char* data = buffer.data(); size_t len = buffer.length(); Debug(wrap, "Receiving %zu bytes injected from JS", len);
The HTTP2 code doesn't seem to support injection from JS - or I was unable to find it?
@jasnell?- added 2 commits that reference this issue
on Oct 16, 2020 - Version: 14.12.0
- Platform:
Linux solus 5.6.19-158.current #1 SMP PREEMPT Sun Jul 26 14:17:01 UTC 2020 x86_64 GNU/Linux - Subsystem: http2
What steps will reproduce the bug?
const http2 = require('http2'); const tls = require('tls'); const options = { ALPNProtocols: ['h2'], host: 'nghttp2.org', servername: 'nghttp2.org', port: 443 }; const socket = tls.connect(options, async () => { console.log('Connected!'); await new Promise(resolve => setTimeout(resolve, 1000)); const session = http2.connect('https://nghttp2.org', { createConnection: () => socket }); session.once('remoteSettings', () => { console.log('Received remote settings!'); }); });
How often does it reproduce? Is there a required condition?
Always.
What is the expected behavior?
Connected! Received remote settings!What do you see instead?
Connected! NghttpError: Protocol errorAdditional information
This is similar to #33343
- added a commit that references this issue
on May 22, 2026
Linux solus 5.6.19-158.current #1 SMP PREEMPT Sun Jul 26 14:17:01 UTC 2020 x86_64 GNU/LinuxWhat steps will reproduce the bug?
How often does it reproduce? Is there a required condition?
Always.
What is the expected behavior?
What do you see instead?
Additional information
This is similar to #33343