Skip to content

[bug] Can't establish websocket connection when using NetDialTLSContext and Proxy together #779

@sleeyax

Description

@sleeyax

Describe the bug
I can't connect to a websocket server over a HTTP proxy when I set both NetDialTLSContext and Proxy fields on the websocket.Dialer struct. See the code snippet below for more details, it's best explained by code.

Versions
Go version: go1.18.1 linux/amd64.
Package version: v1.5.0.

Steps to Reproduce

  1. Get a HTTP proxy to connect to. You could install a local HTTP debugging proxy like Burp Suite, Charles Proxy, MITMProxy, ... on your development machine for testing.
  2. Execute the code snippet below and observe that no websocket connection can be established over the proxy you configured.

Expected behavior
The websocket dialer should be able to establish a websocket connection over a HTTP proxy while also applying a custom TLS connection without issues.

Code Snippets
The following code results in an error like websocket: bad handshake or unexpected EOF (depends on the proxy):

package main

import (
	"context"
	"crypto/tls"
	"github.com/gorilla/websocket"
	"log"
	"net"
	"net/http"
	"net/url"
)

const (
	// Local or remote HTTP proxy.
	proxyUrl = "http://127.0.0.1:8888"

	// Websocket endpoint for testing
	websocketUrl = "wss://echo.websocket.events/"
)

func main() {
	p, _ := url.Parse(proxyUrl)

	dialer := websocket.Dialer{
		Proxy:             http.ProxyURL(p),
		EnableCompression: true,
		NetDialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
			// TCP dial
			netConn, err := net.Dial(network, addr)
			if err != nil {
				return nil, err
			}

			// 'NetDialTLSContext' also gets called during the proxy CONNECT for some reason (at this point 'network' equals "TCP" and 'addr' equals "127.0.0.1:8888")
			// The HTTP proxy doesn't support HTTPS however, so I return the established TCP connection early.
			// If I don't do this check, the connection hangs forever (tested with several proxies).
			// This feels kinda hacky though, not sure if this is the correct approach...
			if p.Host == addr {
				return netConn, err
			}

			// Example TLS handshake
			tlsConn := tls.Client(netConn, &tls.Config{ServerName: "echo.websocket.events", InsecureSkipVerify: true})
			if err = tlsConn.Handshake(); err != nil {
				return nil, err
			}

			return tlsConn, nil
		},
	}

	conn, _, err := dialer.Dial(websocketUrl, nil)
	if err != nil {
		log.Fatalln(err)
	}

	log.Println(conn.LocalAddr())
	// ...
}

When I check the request in burp, it looks like this:

image

As highlighted in red, the host URL became http://echo.websocket.events:443/, which obviously should just be https://echo.websocket.events/. I've been trying to debug why this is happening exactly all day but can't seem to find the culprit so I need help.

If I remove NetDialTLSContext and just specify TLSClientConfig everything works as expected. Same result with the Proxy field removed. The issue only occurs when both of these fields are set.

Am I missing something obvious?

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    Status

    🏗 In progress

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions