// Copyright 2025 The Gorilla WebSocket Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package websocket import ( "bytes" "context" "crypto/rand" "crypto/tls" "crypto/x509" "errors" "io" "net" "net/http" "net/http/httptest" "net/url" "strings" "sync/atomic" "testing" ) // These test cases use a websocket client (Dialer)/proxy/websocket server (Upgrader) // to validate the cases where a proxy is an intermediary between a websocket client // and server. The test cases usually 1) create a websocket server which echoes any // data received back to the client, 2) a basic duplex streaming proxy, and 3) a // websocket client which sends random data to the server through the proxy, // validating any subsequent data received is the same as the data sent. The various // permutations include the proxy and backend schemes (HTTP or HTTPS), as well as // the custom dial functions (e.g NetDialContext, NetDial) set on the Dialer. const ( subprotocolV1 = "subprotocol-version-1" subprotocolV2 = "subprotocol-version-2" ) // Permutation 1 // // Backend: HTTP // Proxy: HTTP func TestHTTPProxyAndBackend(t *testing.T) { websocketTLS := false proxyTLS := false // Start the websocket server, which echoes data back to sender. websocketServer, websocketURL, err := newWebsocketServer(websocketTLS) defer websocketServer.Close() if err != nil { t.Fatalf("error starting websocket server: %v", err) } // Start the proxy server. proxyServer, proxyServerURL, err := newProxyServer(proxyTLS) defer proxyServer.Close() if err != nil { t.Fatalf("error starting proxy server: %v", err) } // Dial the websocket server through the proxy server. dialer := Dialer{ Proxy: http.ProxyURL(proxyServerURL), Subprotocols: []string{subprotocolV1}, } wsClient, _, err := dialer.Dial(websocketURL.String(), nil) if err != nil { t.Fatalf("websocket dial error: %v", err) } // Send, receive, and validate random data over websocket connection. sendReceiveData(t, wsClient) // Validate the proxy server was called. if e, a := int64(1), proxyServer.numCalls(); e != a { t.Errorf("proxy not called") } } // Permutation 2 // // Backend: HTTP // Proxy: HTTP // DialFn: NetDial (dials proxy) func TestHTTPProxyWithNetDial(t *testing.T) { websocketTLS := false proxyTLS := false // Start the websocket server, which echoes data back to sender. websocketServer, websocketURL, err := newWebsocketServer(websocketTLS) defer websocketServer.Close() if err != nil { t.Fatalf("error starting websocket server: %v", err) } // Start the proxy server. proxyServer, proxyServerURL, err := newProxyServer(proxyTLS) defer proxyServer.Close() if err != nil { t.Fatalf("error starting proxy server: %v", err) } // Dial the websocket server through the proxy server. var netDialCalled atomic.Int64 dialer := Dialer{ NetDial: func(network, addr string) (net.Conn, error) { netDialCalled.Add(1) return (&net.Dialer{}).DialContext(context.Background(), network, addr) }, Proxy: http.ProxyURL(proxyServerURL), Subprotocols: []string{subprotocolV1}, } wsClient, _, err := dialer.Dial(websocketURL.String(), nil) if err != nil { t.Fatalf("websocket dial error: %v", err) } // Send, receive, and validate random data over websocket connection. sendReceiveData(t, wsClient) if e, a := int64(1), netDialCalled.Load(); e != a { t.Errorf("netDial not called") } // Validate the proxy server was called. if e, a := int64(1), proxyServer.numCalls(); e != a { t.Errorf("proxy not called") } } // Permutation 3 // // Backend: HTTP // Proxy: HTTP // DialFn: NetDialContext (dials proxy) func TestHTTPProxyWithNetDialContext(t *testing.T) { websocketTLS := false proxyTLS := false // Start the websocket server, which echoes data back to sender. websocketServer, websocketURL, err := newWebsocketServer(websocketTLS) defer websocketServer.Close() if err != nil { t.Fatalf("error starting websocket server: %v", err) } // Start the proxy server. proxyServer, proxyServerURL, err := newProxyServer(proxyTLS) defer proxyServer.Close() if err != nil { t.Fatalf("error starting proxy server: %v", err) } // Dial the websocket server through the proxy server. var netDialCalled atomic.Int64 dialer := Dialer{ NetDialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { netDialCalled.Add(1) return (&net.Dialer{}).DialContext(ctx, network, addr) }, Proxy: http.ProxyURL(proxyServerURL), Subprotocols: []string{subprotocolV1}, } wsClient, _, err := dialer.Dial(websocketURL.String(), nil) if err != nil { t.Fatalf("websocket dial error: %v", err) } // Send, receive, and validate random data over websocket connection. sendReceiveData(t, wsClient) if e, a := int64(1), netDialCalled.Load(); e != a { t.Errorf("netDial not called") } // Validate the proxy server was called. if e, a := int64(1), proxyServer.numCalls(); e != a { t.Errorf("proxy not called") } } // Permutation 4 // // Backend: HTTPS // Proxy: HTTP // DialFn: NetDialTLSConfig (set but *ignored*) // TLS Config: set (used for backend TLS) func TestHTTPProxyWithHTTPSBackend(t *testing.T) { websocketTLS := true proxyTLS := false // Start the websocket server, which echoes data back to sender. websocketServer, websocketURL, err := newWebsocketServer(websocketTLS) defer websocketServer.Close() if err != nil { t.Fatalf("error starting websocket server: %v", err) } // Start the proxy server. proxyServer, proxyServerURL, err := newProxyServer(proxyTLS) defer proxyServer.Close() if err != nil { t.Fatalf("error starting proxy server: %v", err) } var netDialTLSCalled atomic.Int64 dialer := Dialer{ Proxy: http.ProxyURL(proxyServerURL), // This function should be ignored, because an HTTP proxy exists // and the backend TLS handshake should use TLSClientConfig. NetDialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) { netDialTLSCalled.Add(1) return (&net.Dialer{}).DialContext(ctx, network, addr) }, // Used for the backend server TLS handshake. TLSClientConfig: tlsConfig(websocketTLS, proxyTLS), Subprotocols: []string{subprotocolV1}, } wsClient, _, err := dialer.Dial(websocketURL.String(), nil) if err != nil { t.Fatalf("websocket dial error: %v", err) } // Send, receive, and validate random data over websocket connection. sendReceiveData(t, wsClient) if numTLSDials := netDialTLSCalled.Load(); numTLSDials > 0 { t.Errorf("NetDialTLS should have been ignored") } // Validate the proxy server was called. if e, a := int64(1), proxyServer.numCalls(); e != a { t.Errorf("proxy not called") } } // Permutation 5 // // Backend: HTTPS // Proxy: HTTPS // TLS Config: set (used for both proxy and backend TLS) func TestHTTPSProxyAndBackend(t *testing.T) { websocketTLS := true proxyTLS := true // Start the websocket server, which echoes data back to sender. websocketServer, websocketURL, err := newWebsocketServer(websocketTLS) defer websocketServer.Close() if err != nil { t.Fatalf("error starting websocket server: %v", err) } // Start the proxy server. proxyServer, proxyServerURL, err := newProxyServer(proxyTLS) defer proxyServer.Close() if err != nil { t.Fatalf("error starting proxy server: %v", err) } dialer := Dialer{ Proxy: http.ProxyURL(proxyServerURL), TLSClientConfig: tlsConfig(websocketTLS, proxyTLS), Subprotocols: []string{subprotocolV1}, } wsClient, _, err := dialer.Dial(websocketURL.String(), nil) if err != nil { t.Fatalf("websocket dial error: %v", err) } // Send, receive, and validate random data over websocket connection. sendReceiveData(t, wsClient) // Validate the proxy server was called. if e, a := int64(1), proxyServer.numCalls(); e != a { t.Errorf("proxy not called") } } // Permutation 6 // // Backend: HTTPS // Proxy: HTTPS // DialFn: NetDial (used to dial proxy) // TLS Config: set (used for both proxy and backend TLS) func TestHTTPSProxyUsingNetDial(t *testing.T) { websocketTLS := true proxyTLS := true // Start the websocket server, which echoes data back to sender. websocketServer, websocketURL, err := newWebsocketServer(websocketTLS) defer websocketServer.Close() if err != nil { t.Fatalf("error starting websocket server: %v", err) } // Start the proxy server. proxyServer, proxyServerURL, err := newProxyServer(proxyTLS) defer proxyServer.Close() if err != nil { t.Fatalf("error starting proxy server: %v", err) } var netDialCalled atomic.Int64 dialer := Dialer{ NetDial: func(network, addr string) (net.Conn, error) { netDialCalled.Add(1) return (&net.Dialer{}).DialContext(context.Background(), network, addr) }, Proxy: http.ProxyURL(proxyServerURL), TLSClientConfig: tlsConfig(websocketTLS, proxyTLS), Subprotocols: []string{subprotocolV1}, } wsClient, _, err := dialer.Dial(websocketURL.String(), nil) if err != nil { t.Fatalf("websocket dial error: %v", err) } // Send, receive, and validate random data over websocket connection. sendReceiveData(t, wsClient) if e, a := int64(1), netDialCalled.Load(); e != a { t.Errorf("netDial not called") } // Validate the proxy server was called. if e, a := int64(1), proxyServer.numCalls(); e != a { t.Errorf("proxy not called") } } // Permutation 7 // // Backend: HTTPS // Proxy: HTTPS // DialFn: NetDialContext (used to dial proxy) // TLS Config: set (used for both proxy and backend TLS) func TestHTTPSProxyUsingNetDialContext(t *testing.T) { websocketTLS := true proxyTLS := true // Start the websocket server, which echoes data back to sender. websocketServer, websocketURL, err := newWebsocketServer(websocketTLS) defer websocketServer.Close() if err != nil { t.Fatalf("error starting websocket server: %v", err) } // Start the proxy server. proxyServer, proxyServerURL, err := newProxyServer(proxyTLS) defer proxyServer.Close() if err != nil { t.Fatalf("error starting proxy server: %v", err) } var netDialCalled atomic.Int64 dialer := Dialer{ NetDialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { netDialCalled.Add(1) return (&net.Dialer{}).DialContext(ctx, network, addr) }, Proxy: http.ProxyURL(proxyServerURL), TLSClientConfig: tlsConfig(websocketTLS, proxyTLS), Subprotocols: []string{subprotocolV1}, } wsClient, _, err := dialer.Dial(websocketURL.String(), nil) if err != nil { t.Fatalf("websocket dial error: %v", err) } // Send, receive, and validate random data over websocket connection. sendReceiveData(t, wsClient) if e, a := int64(1), netDialCalled.Load(); e != a { t.Errorf("netDial not called") } // Validate the proxy server was called. if e, a := int64(1), proxyServer.numCalls(); e != a { t.Errorf("proxy not called") } } // Permutation 8 // // Backend: HTTPS // Proxy: HTTPS // DialFn: NetDialTLSContext (used for proxy TLS) // TLS Config: set (used for backend TLS) func TestHTTPSProxyUsingNetDialTLSContext(t *testing.T) { websocketTLS := true proxyTLS := true // Start the websocket server, which echoes data back to sender. websocketServer, websocketURL, err := newWebsocketServer(websocketTLS) defer websocketServer.Close() if err != nil { t.Fatalf("error starting websocket server: %v", err) } // Start the proxy server. proxyServer, proxyServerURL, err := newProxyServer(proxyTLS) defer proxyServer.Close() if err != nil { t.Fatalf("error starting proxy server: %v", err) } // Configure the proxy dialing function which dials the proxy and // performs the TLS handshake. var proxyDialCalled atomic.Int64 proxyCerts := x509.NewCertPool() proxyCerts.AppendCertsFromPEM(proxyServerCert) proxyTLSConfig := &tls.Config{RootCAs: proxyCerts} proxyDial := func(ctx context.Context, network, addr string) (net.Conn, error) { proxyDialCalled.Add(1) return tls.Dial(network, addr, proxyTLSConfig) } // Configure the backend webscocket TLS configuration (handshake occurs // over the previously created proxy connection). websocketCerts := x509.NewCertPool() websocketCerts.AppendCertsFromPEM(websocketServerCert) websocketTLSConfig := &tls.Config{RootCAs: websocketCerts} dialer := Dialer{ Proxy: http.ProxyURL(proxyServerURL), // Dial and TLS handshake function to proxy. NetDialTLSContext: proxyDial, // Used for second TLS handshake to backend server over previously // established proxy connection. TLSClientConfig: websocketTLSConfig, Subprotocols: []string{subprotocolV1}, } wsClient, _, err := dialer.Dial(websocketURL.String(), nil) if err != nil { t.Fatalf("websocket dial error: %v", err) } // Send, receive, and validate random data over websocket connection. sendReceiveData(t, wsClient) if e, a := int64(1), proxyDialCalled.Load(); e != a { t.Errorf("netDial not called") } // Validate the proxy server was called. if e, a := int64(1), proxyServer.numCalls(); e != a { t.Errorf("proxy not called") } } // Permutation 9 // // Backend: HTTP // Proxy: HTTPS // TLS Config: set (used for proxy TLS) func TestHTTPSProxyHTTPBackend(t *testing.T) { websocketTLS := false proxyTLS := true // Start the websocket server, which echoes data back to sender. websocketServer, websocketURL, err := newWebsocketServer(websocketTLS) defer websocketServer.Close() if err != nil { t.Fatalf("error starting websocket server: %v", err) } // Start the proxy server. proxyServer, proxyServerURL, err := newProxyServer(proxyTLS) defer proxyServer.Close() if err != nil { t.Fatalf("error starting proxy server: %v", err) } dialer := Dialer{ Proxy: http.ProxyURL(proxyServerURL), TLSClientConfig: tlsConfig(websocketTLS, proxyTLS), Subprotocols: []string{subprotocolV1}, } wsClient, _, err := dialer.Dial(websocketURL.String(), nil) if err != nil { t.Fatalf("websocket dial error: %v", err) } // Send, receive, and validate random data over websocket connection. sendReceiveData(t, wsClient) // Validate the proxy server was called. if e, a := int64(1), proxyServer.numCalls(); e != a { t.Errorf("proxy not called") } } // Permutation 10 // // Backend: HTTP // Proxy: HTTPS // DialFn: NetDialTLSContext (used for proxy TLS) // TLS Config: set (ignored) func TestHTTPSProxyUsingNetDialTLSContextWithHTTPBackend(t *testing.T) { websocketTLS := false proxyTLS := true // Start the websocket server, which echoes data back to sender. websocketServer, websocketURL, err := newWebsocketServer(websocketTLS) defer websocketServer.Close() if err != nil { t.Fatalf("error starting websocket server: %v", err) } // Start the proxy server. proxyServer, proxyServerURL, err := newProxyServer(proxyTLS) defer proxyServer.Close() if err != nil { t.Fatalf("error starting proxy server: %v", err) } var proxyDialCalled atomic.Int64 dialer := Dialer{ NetDialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) { proxyDialCalled.Add(1) return tls.Dial(network, addr, tlsConfig(websocketTLS, proxyTLS)) }, Proxy: http.ProxyURL(proxyServerURL), TLSClientConfig: &tls.Config{}, // Misconfigured, but ignored. Subprotocols: []string{subprotocolV1}, } wsClient, _, err := dialer.Dial(websocketURL.String(), nil) if err != nil { t.Fatalf("websocket dial error: %v", err) } // Send, receive, and validate random data over websocket connection. sendReceiveData(t, wsClient) if e, a := int64(1), proxyDialCalled.Load(); e != a { t.Errorf("netDial not called") } // Validate the proxy server was called. if e, a := int64(1), proxyServer.numCalls(); e != a { t.Errorf("proxy not called") } } func TestTLSValidationErrors(t *testing.T) { // Both websocket and proxy servers are started with TLS. websocketTLS := true proxyTLS := true websocketServer, websocketURL, err := newWebsocketServer(websocketTLS) defer websocketServer.Close() if err != nil { t.Fatalf("error starting websocket server: %v", err) } proxyServer, proxyServerURL, err := newProxyServer(proxyTLS) defer proxyServer.Close() if err != nil { t.Fatalf("error starting proxy server: %v", err) } // Dialer without proxy CA cert fails TLS verification. tlsError := "tls: failed to verify certificate" dialer := Dialer{ Proxy: http.ProxyURL(proxyServerURL), TLSClientConfig: tlsConfig(true, false), Subprotocols: []string{subprotocolV1}, } _, _, err = dialer.Dial(websocketURL.String(), nil) if err == nil { t.Errorf("expected proxy TLS verification error did not arrive") } else if !strings.Contains(err.Error(), tlsError) { t.Errorf("expected proxy TLS error (%s), got (%s)", err.Error(), tlsError) } // Validate the proxy handler was *NOT* called (because proxy // server TLS validation failed). if e, a := int64(0), proxyServer.numCalls(); e != a { t.Errorf("proxy should not have been called") } // Dialer without websocket CA cert fails TLS verification. dialer = Dialer{ Proxy: http.ProxyURL(proxyServerURL), TLSClientConfig: tlsConfig(false, true), Subprotocols: []string{subprotocolV1}, } _, _, err = dialer.Dial(websocketURL.String(), nil) if err == nil { t.Errorf("expected websocket TLS verification error did not arrive") } else if !strings.Contains(err.Error(), tlsError) { t.Errorf("expected websocket TLS error (%s), got (%s)", err.Error(), tlsError) } // Validate the proxy server *was* called (but subsequent // websocket server failed TLS validation). if e, a := int64(1), proxyServer.numCalls(); e != a { t.Errorf("proxy have been called") } } func TestProxyFnErrorIsPropagated(t *testing.T) { websocketServer, websocketURL, err := newWebsocketServer(false) defer websocketServer.Close() if err != nil { t.Fatalf("error starting websocket server: %v", err) } // Create a Dialer where Proxy function always returns an error. proxyURLError := errors.New("proxy URL generation error") dialer := Dialer{ Proxy: func(r *http.Request) (*url.URL, error) { return nil, proxyURLError }, Subprotocols: []string{subprotocolV1}, } // Proxy URL generation error should halt request and be propagated. _, _, err = dialer.Dial(websocketURL.String(), nil) if err == nil { t.Fatalf("expected websocket dial error, received none") } else if !errors.Is(proxyURLError, err) { t.Fatalf("expected error (%s), got (%s)", proxyURLError, err) } } func TestProxyFnNilMeansNoProxy(t *testing.T) { // Both websocket and proxy servers are started. websocketTLS := false proxyTLS := false websocketServer, websocketURL, err := newWebsocketServer(websocketTLS) defer websocketServer.Close() if err != nil { t.Fatalf("error starting websocket server: %v", err) } proxyServer, _, err := newProxyServer(proxyTLS) defer proxyServer.Close() if err != nil { t.Fatalf("error starting proxy server: %v", err) } // Dialer created with Proxy URL generation function returning nil // proxy URL, which continues with backend server connection without // proxying. dialer := Dialer{ Proxy: func(r *http.Request) (*url.URL, error) { return nil, nil }, Subprotocols: []string{subprotocolV1}, } wsClient, _, err := dialer.Dial(websocketURL.String(), nil) if err != nil { t.Fatalf("websocket dial error: %v", err) } sendReceiveData(t, wsClient) // Validate the proxy handler was *NOT* called (because proxy // URL generation returned nil). if e, a := int64(0), proxyServer.numCalls(); e != a { t.Errorf("proxy should not have been called") } } // "counter" interface can be implemented by a server to keep track // of the number of times a handler was called, as well as "Close". type counter interface { increment() numCalls() int64 closer } type closer interface { Close() } // testServer implements "counter" interface. type testServer struct { server *httptest.Server numHandled atomic.Int64 } func (ts *testServer) numCalls() int64 { return ts.numHandled.Load() } func (ts *testServer) increment() { ts.numHandled.Add(1) } func (ts *testServer) Close() { if ts.server != nil { ts.server.Close() } } // websocketEchoHandler upgrades the connection associated with the request, and // echoes binary messages read off the websocket connection back to the client. var websocketEchoHandler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { upgrader := Upgrader{ CheckOrigin: func(r *http.Request) bool { return true // Accepting all requests }, Subprotocols: []string{ subprotocolV1, subprotocolV2, }, } wsConn, err := upgrader.Upgrade(w, req, nil) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } defer wsConn.Close() for { writer, err := wsConn.NextWriter(BinaryMessage) if err != nil { break } messageType, reader, err := wsConn.NextReader() if err != nil { break } if messageType != BinaryMessage { http.Error(w, "websocket reader not binary message type", http.StatusInternalServerError) } _, err = io.Copy(writer, reader) if err != nil { http.Error(w, "websocket server io copy error", http.StatusInternalServerError) } } }) // Returns a test backend websocket server as well as the URL pointing // to the server, or an error if one occurred. Sets up a TLS endpoint // on the server if the passed "tlsServer" is true. // func newWebsocketServer(tlsServer bool) (*httptest.Server, *url.URL, error) { func newWebsocketServer(tlsServer bool) (closer, *url.URL, error) { // Start the websocket server, which echoes data back to sender. websocketServer := httptest.NewUnstartedServer(websocketEchoHandler) if tlsServer { websocketKeyPair, err := tls.X509KeyPair(websocketServerCert, websocketServerKey) if err != nil { return nil, nil, err } websocketServer.TLS = &tls.Config{ Certificates: []tls.Certificate{websocketKeyPair}, } websocketServer.StartTLS() } else { websocketServer.Start() } websocketURL, err := url.Parse(websocketServer.URL) if err != nil { return nil, nil, err } if tlsServer { websocketURL.Scheme = "wss" } else { websocketURL.Scheme = "ws" } return websocketServer, websocketURL, nil } // proxyHandler creates a full duplex streaming connection between the client // (hijacking the http request connection), and an "upstream" dialed connection // to the "Host". Creates two goroutines to copy between connections in each direction. var proxyHandler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { // Validate the CONNECT method. if req.Method != http.MethodConnect { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } // Dial upstream server. upstream, err := (&net.Dialer{}).DialContext(req.Context(), "tcp", req.URL.Host) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } defer upstream.Close() // Return 200 OK to client. w.WriteHeader(http.StatusOK) // Hijack client connection. client, _, err := w.(http.Hijacker).Hijack() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } defer client.Close() // Create duplex streaming between client and upstream connections. done := make(chan struct{}, 2) go func() { _, _ = io.Copy(upstream, client) done <- struct{}{} }() go func() { _, _ = io.Copy(client, upstream) done <- struct{}{} }() <-done }) // Returns a new test HTTP server, as well as the URL to that server, or // an error if one occurred. numProxyCalls keeps track of the number of // times the proxy handler was called with this server. func newProxyServer(tlsServer bool) (counter, *url.URL, error) { // Start the proxy server, keeping track of how many times the handler is called. ts := &testServer{} proxyServer := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { ts.increment() proxyHandler.ServeHTTP(w, req) })) if tlsServer { proxyKeyPair, err := tls.X509KeyPair(proxyServerCert, proxyServerKey) if err != nil { return nil, nil, err } proxyServer.TLS = &tls.Config{ Certificates: []tls.Certificate{proxyKeyPair}, } proxyServer.StartTLS() } else { proxyServer.Start() } proxyURL, err := url.Parse(proxyServer.URL) if err != nil { return nil, nil, err } return ts, proxyURL, nil } // Returns the TLS config with the RootCAs cert pool set. If // neither websocket nor proxy server uses TLS, returns nil. func tlsConfig(websocketTLS bool, proxyTLS bool) *tls.Config { if !websocketTLS && !proxyTLS { return nil } certPool := x509.NewCertPool() tlsConfig := &tls.Config{ RootCAs: certPool, } if websocketTLS { tlsConfig.RootCAs.AppendCertsFromPEM(websocketServerCert) } if proxyTLS { tlsConfig.RootCAs.AppendCertsFromPEM(proxyServerCert) } return tlsConfig } // Sends, receives, and validates random data sent and received // over the passed websocket connection. const randomDataSize = 128 * 1024 func sendReceiveData(t *testing.T, wsConn *Conn) { // Create the random data. randomData := make([]byte, randomDataSize) if _, err := rand.Read(randomData); err != nil { t.Errorf("unexpected error reading random data: %v", err) } // Send the random data. err := wsConn.WriteMessage(BinaryMessage, randomData) if err != nil { t.Errorf("websocket write error: %v", err) } // Read from the websocket connection, and validate the // read data is the same as the previously sent data. _, received, err := wsConn.ReadMessage() if !bytes.Equal(randomData, received) { t.Errorf("unexpected data received: %d bytes sent, %d bytes received", len(received), len(randomData)) } } // proxyServerCert was generated from crypto/tls/generate_cert.go with the following command: // // go run generate_cert.go --rsa-bits 2048 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h // // proxyServerCert is a self-signed. var proxyServerCert = []byte(`-----BEGIN CERTIFICATE----- MIIDGTCCAgGgAwIBAgIRALL5AZcefF4kkYV1SEG6YrMwDQYJKoZIhvcNAQELBQAw EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2 MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzCCASIwDQYJKoZIhvcNAQEBBQADggEP ADCCAQoCggEBALQ/FHcyVwdFHxARbbD2KBtDUT7Eni+8ioNdjtGcmtXqBv45EC1C JOqqGJTroFGJ6Q9kQIZ9FqH5IJR2fOOJD9kOTueG4Vt1JY1rj1Kbpjefu8XleZ5L SBwIWVnN/lEsEbuKmj7N2gLt5AH3zMZiBI1mg1u9Z5ZZHYbCiTpBrwsq6cTlvR9g dyo1YkM5hRESCzsrL0aUByoo0qRMD8ZsgANJwgsiO0/M6idbxDwv1BnGwGmRYvOE Hxpy3v0Jg7GJYrvnpnifJTs4nw91N5X9pXxR7FFzi/6HTYDWRljvTb0w6XciKYAz bWZ0+cJr5F7wB7ovlbm7HrQIR7z7EIIu2d8CAwEAAaNoMGYwDgYDVR0PAQH/BAQD AgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8wLgYDVR0R BCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZI hvcNAQELBQADggEBAFPPWopNEJtIA2VFAQcqN6uJK+JVFOnjGRoCrM6Xgzdm0wxY XCGjsxY5dl+V7KzdGqu858rCaq5osEBqypBpYAnS9C38VyCDA1vPS1PsN8SYv48z DyBwj+7R2qar0ADBhnhWxvYO9M72lN/wuCqFKYMeFSnJdQLv3AsrrHe9lYqOa36s 8wxSwVTFTYXBzljPEnSaaJMPqFD8JXaZK1ryJPkO5OsCNQNGtatNiWAf3DcmwHAT MGYMzP0u4nw47aRz9shB8w+taPKHx2BVwE1m/yp3nHVioOjXqA1fwRQVGclCJSH1 D2iq3hWVHRENgjTjANBPICLo9AZ4JfN6PH19mnU= -----END CERTIFICATE-----`) // proxyServerKey is the private key for proxyServerCert. var proxyServerKey = []byte(`-----BEGIN RSA PRIVATE KEY----- MIIEogIBAAKCAQEAtD8UdzJXB0UfEBFtsPYoG0NRPsSeL7yKg12O0Zya1eoG/jkQ LUIk6qoYlOugUYnpD2RAhn0WofkglHZ844kP2Q5O54bhW3UljWuPUpumN5+7xeV5 nktIHAhZWc3+USwRu4qaPs3aAu3kAffMxmIEjWaDW71nllkdhsKJOkGvCyrpxOW9 H2B3KjViQzmFERILOysvRpQHKijSpEwPxmyAA0nCCyI7T8zqJ1vEPC/UGcbAaZFi 84QfGnLe/QmDsYliu+emeJ8lOzifD3U3lf2lfFHsUXOL/odNgNZGWO9NvTDpdyIp gDNtZnT5wmvkXvAHui+VubsetAhHvPsQgi7Z3wIDAQABAoIBAGmw93IxjYCQ0ncc kSKMJNZfsdtJdaxuNRZ0nNNirhQzR2h403iGaZlEpmdkhzxozsWcto1l+gh+SdFk bTUK4MUZM8FlgO2dEqkLYh5BcMT7ICMZvSfJ4v21E5eqR68XVUqQKoQbNvQyxFk3 EddeEGdNrkb0GDK8DKlBlzAW5ep4gjG85wSTjR+J+muUv3R0BgLBFSuQnIDM/IMB LWqsja/QbtB7yppe7jL5u8UCFdZG8BBKT9fcvFIu5PRLO3MO0uOI7LTc8+W1Xm23 uv+j3SY0+v+6POjK0UlJFFi/wkSPTFIfrQO1qFBkTDQHhQ6q/7GnILYYOiGbIRg2 NNuP52ECgYEAzXEoy50wSYh8xfFaBuxbm3ruuG2W49jgop7ZfoFrPWwOQKAZS441 VIwV4+e5IcA6KkuYbtGSdTYqK1SMkgnUyD/VevwAqH5TJoEIGu0pDuKGwVuwqioZ frCIAV5GllKyUJ55VZNbRr2vY2fCsWbaCSCHETn6C16DNuTCe5C0JBECgYEA4JqY 5GpNbMG8fOt4H7hU0Fbm2yd6SHJcQ3/9iimef7xG6ajxsYrIhg1ft+3IPHMjVI0+ 9brwHDnWg4bOOx/VO4VJBt6Dm/F33bndnZRkuIjfSNpLM51P+EnRdaFVHOJHwKqx uF69kihifCAG7YATgCveeXImzBUSyZUz9UrETu8CgYARNBimdFNG1RcdvEg9rC0/ p9u1tfecvNySwZqU7WF9kz7eSonTueTdX521qAHowaAdSpdJMGODTTXaywm6cPhQ jIfj9JZZhbqQzt1O4+08Qdvm9TamCUB5S28YLjza+bHU7nBaqixKkDfPqzCyilpX yVGGL8SwjwmN3zop/sQXAQKBgC0JMsESQ6YcDsRpnrOVjYQc+LtW5iEitTdfsaID iGGKihmOI7B66IxgoCHMTws39wycKdSyADVYr5e97xpR3rrJlgQHmBIrz+Iow7Q2 LiAGaec8xjl6QK/DdXmFuQBKqyKJ14rljFODP4QuE9WJid94bGqjpf3j99ltznZP 4J8HAoGAJb4eb4lu4UGwifDzqfAPzLGCoi0fE1/hSx34lfuLcc1G+LEu9YDKoOVJ 9suOh0b5K/bfEy9KrVMBBriduvdaERSD8S3pkIQaitIz0B029AbE4FLFf9lKQpP2 KR8NJEkK99Vh/tew6jAMll70xFrE7aF8VLXJVE7w4sQzuvHxl9Q= -----END RSA PRIVATE KEY----- `) // websocketServerCert is self-signed. var websocketServerCert = []byte(`-----BEGIN CERTIFICATE----- MIIDOTCCAiGgAwIBAgIQYSN1VY/favsLUo+B7gJ5tTANBgkqhkiG9w0BAQsFADAS MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A MIIBCgKCAQEApBlintjkL1fO1Sk2pzNvl862CtTwU7/Jy6EZqWzI17wEbPn4sbSD bHhfDlPl2nmw3hVkc6LNK+eqzm2GX/ai4tgMiaH7kyyNit1K3g7y7GISMf9poWIa POJhid2wmhKHbEtHECSdQ5c/jEN1UVzB4go5LO7MEEVo9kyQ+yBqS6gISyFmfaT4 qOsPJBir33bBpptSend1JSXaRTXqRa1p+oudw2ILa4U7KfuKK3emp21m5/HYAuSf CV4WqqDoDiBPMpsQ0kPEPugWZKFeF3qanmqFFvptYx+zJbOznWYY2D3idWsvcg6q VLPEB19oXaVBV0HXPFtObm5m1jCpl8FI1wIDAQABo4GIMIGFMA4GA1UdDwEB/wQE AwICpDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud DgQWBBQcSkjqA9rgos1daegNj49BpRCA0jAuBgNVHREEJzAlggtleGFtcGxlLmNv bYcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAnk9i 9rogNTi9B1pn+Fbk3WALKdEjv/uyePsTnwdyvswVbeYbQweU9TrhYT2+eXbMA5kY 7TaQm46idRqxCKMgc3Ip3DADJdm8cJX9p2ExU4fKdkPc1KD/J+4QHHx1W2Ml5S2o foOo6j1F0UdZP/rBj0UumEZp32qW+4DhVV/QQjUB8J0gaDC7yZBMdyMIeClR0RqE YfZdCJbQHqtTwBXN+imQUHPGmksYkRDpFRvw/4crpcMIE04mVVd99nOpFCQnK61t 9US1y17VW1lYpkqlCS+rkcAtor4Z5naSf9/oLGCxEAwyW0pwHGO6MXtMxvB/JD20 hJdlz1I7wlSfF4MiRQ== -----END CERTIFICATE-----`) // websocketServerKey is the private key for websocketServerCert. var websocketServerKey = []byte(`-----BEGIN PRIVATE KEY----- MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCkGWKe2OQvV87V KTanM2+XzrYK1PBTv8nLoRmpbMjXvARs+fixtINseF8OU+XaebDeFWRzos0r56rO bYZf9qLi2AyJofuTLI2K3UreDvLsYhIx/2mhYho84mGJ3bCaEodsS0cQJJ1Dlz+M Q3VRXMHiCjks7swQRWj2TJD7IGpLqAhLIWZ9pPio6w8kGKvfdsGmm1J6d3UlJdpF NepFrWn6i53DYgtrhTsp+4ord6anbWbn8dgC5J8JXhaqoOgOIE8ymxDSQ8Q+6BZk oV4XepqeaoUW+m1jH7Mls7OdZhjYPeJ1ay9yDqpUs8QHX2hdpUFXQdc8W05ubmbW MKmXwUjXAgMBAAECggEAE6BkTDgH//rnkP/Ej/Y17Zkv6qxnMLe/4evwZB7PsrBu cxOUAWUOpvA1UO215bh87+2XvcDbUISnyC1kpKDyAGGeC5llER2DXE11VokWgtvZ Q0OXavw5w83A+WVGFFdiUmXP0l10CxEm7OwQjFz6D21GQ1qC65tG9NZZghTxbFTe iZKqgWqyHsaAWLOuDQbj1FTEBMFrY8f9RbclSh0luPZnzGc4BVI/t34jKPZBpH2N NCkr8aB7MMHGhrNZFHAu/KAvq8UBrDTX+O8ERMwcwQWB4nne2+GOTN0MdcAUc72i GryzIa8TgO+TpQOYoZ4NPnzFrsa+m3G2Tug3vbt62QKBgQDOPfM4/5/x/h/ggxQn aRvEOC+8ldeqEOS1VTGiuDKJMWXrNkG+d+AsxfNP4k0QVNrpEAZSYcf0gnS9Odcl luEsi/yPZDDnPg/cS+Z3336VKsggly7BWFs1Ct/9I+ZfSCl88TkVpIfeCBC34XEb 0mFUq/RdLqXj/mVLbBfr+H8cEwKBgQDLsJUm8lkWFAPJ8UMto8xeUMGk44VukYwx +oI6KhplFntiI0C1Dd9wrxyCjySlJcc0NFt6IPN84d7pI9LQSbiKXQ1jMvsBzd4G EMtG8SHpIY/mMU+KzWLHYVFS0FA4PvXXvPRNLOXas7hbALZdLshVKd7aDlkQAb5C KWFHeIFwrQKBgA8r5Xl67HQrwoKMge4IQF+l1nUj/LJo/boNI1KaBDWtaZbs7dcq EFaa1TQ6LHsYEuZ0JFLpGIF3G0lUOOxt9fCF97VApIxON3J4LuMAkNo+RGyJUoos isETJLkFbAv0TgD/6bga21fM9hXgwqZOSpSk9ZvpM5DbBO6QbA4SwJ77AoGAX7h1 /z14XAW/2hDE7xfAnLn6plA9jj5b0cjVlhvfF44/IVlLuUnxrPS9wyUdpXZhbMkG DBicFB3ZMVqiYTuju3ILLojwqGJkahlOTeJXe0VIaHbX2HS4bNXw76fxat07jsy/ Sd1Fj0dR5YIqMRQhFNR+Y57Gf90x2cm0a2/X9GkCgYANawYx9bNfcX0HMVG7vktK 6/80omnoBM0JUxA+V7DxS8kr9Cj2Y/kcS+VHb4yyoSkDgnsSdnCr1ZTctcj828MJ 8AUwskAtEjPkHRXEgRRnEl2oJGD1TT5iwBNnuPAQDXwzkGCRYBnlfZNbILbOoSUz m+VDcqT5XzcRADa/TLlEXA== -----END PRIVATE KEY----- `)