镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Cannot reuse an estabilished TLS socket for a HTTP/2 session #35475

Description

@szmarczak
  • 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 error

Additional information

This is similar to #33343

Activity

  1. szmarczak commented on Oct 3, 2020

    @szmarczak
    MemberAuthor

    What does Protocol error mean? It could be anything.

  2. szmarczak commented on Oct 3, 2020

    @szmarczak
    MemberAuthor

    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?

  3. added
    http2Issues and PRs related to the http2 subsystem.
    on Oct 4, 2020
  4. szmarczak commented on Oct 5, 2020

    @szmarczak
    MemberAuthor

    I think that the socket must have no data buffered.

    Putting console.log(socket.readableLength); after the await tells us that there is some data to be read. But if we lower the timeout to 100ms then there's no queued data to be read and it passes.

  5. szmarczak commented on Oct 5, 2020

    @szmarczak
    MemberAuthor
    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!');
        });
    });
  6. mmomtchev commented on Oct 16, 2020

    @mmomtchev
    Contributor

    @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 problem

    When 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?

  7. mutza97 commented on Oct 25, 2020

    @mutza97
    • 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 error
    

    Additional information

    This is similar to #33343

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    http2Issues and PRs related to the http2 subsystem.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions