TUN-8914: Add a new configuration to locally override the max-active-flows

## Summary

This commit introduces a new command line flag, `--max-active-flows`, which allows overriding the remote configuration for the maximum number of active flows.

The flag can be used with the `run` command, like `cloudflared tunnel --no-autoupdate run --token <TUNNEL_TOKEN> --max-active-flows 50000`, or set via an environment variable `TUNNEL_MAX_ACTIVE_FLOWS`.

Note that locally-set values always take precedence over remote settings, even if the tunnel is remotely managed.

Closes TUN-8914
This commit is contained in:
João "Pisco" Fernandes 2025-02-03 03:42:50 -08:00
parent 2feccd772c
commit b187879e69
5 changed files with 134 additions and 39 deletions

View File

@ -126,7 +126,7 @@ var (
routeFailMsg = fmt.Sprintf("failed to provision routing, please create it manually via Cloudflare dashboard or UI; "+ routeFailMsg = fmt.Sprintf("failed to provision routing, please create it manually via Cloudflare dashboard or UI; "+
"most likely you already have a conflicting record there. You can also rerun this command with --%s to overwrite "+ "most likely you already have a conflicting record there. You can also rerun this command with --%s to overwrite "+
"any existing DNS records for this hostname.", overwriteDNSFlag) "any existing DNS records for this hostname.", overwriteDNSFlag)
errDeprecatedClassicTunnel = fmt.Errorf("Classic tunnels have been deprecated, please use Named Tunnels. (https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/tunnel-guide/)") errDeprecatedClassicTunnel = errors.New("Classic tunnels have been deprecated, please use Named Tunnels. (https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/tunnel-guide/)")
// TODO: TUN-8756 the list below denotes the flags that do not possess any kind of sensitive information // TODO: TUN-8756 the list below denotes the flags that do not possess any kind of sensitive information
// however this approach is not maintainble in the long-term. // however this approach is not maintainble in the long-term.
nonSecretFlagsList = []string{ nonSecretFlagsList = []string{
@ -214,6 +214,7 @@ var (
"protocol", "protocol",
"overwrite-dns", "overwrite-dns",
"help", "help",
"max-active-flows",
} }
) )

View File

@ -38,7 +38,7 @@ const (
var ( var (
secretFlags = [2]*altsrc.StringFlag{credentialsContentsFlag, tunnelTokenFlag} secretFlags = [2]*altsrc.StringFlag{credentialsContentsFlag, tunnelTokenFlag}
configFlags = []string{"autoupdate-freq", "no-autoupdate", "retries", "protocol", "loglevel", "transport-loglevel", "origincert", "metrics", "metrics-update-freq", "edge-ip-version", "edge-bind-address"} configFlags = []string{"autoupdate-freq", "no-autoupdate", "retries", "protocol", "loglevel", "transport-loglevel", "origincert", "metrics", "metrics-update-freq", "edge-ip-version", "edge-bind-address", "max-active-flows"}
) )
func logClientOptions(c *cli.Context, log *zerolog.Logger) { func logClientOptions(c *cli.Context, log *zerolog.Logger) {

View File

@ -230,6 +230,11 @@ var (
Usage: "Network diagnostics won't be performed", Usage: "Network diagnostics won't be performed",
Value: false, Value: false,
} }
maxActiveFlowsFlag = &cli.Uint64Flag{
Name: "max-active-flows",
Usage: "Overrides the remote configuration for max active private network flows (TCP/UDP) that this cloudflared instance supports",
EnvVars: []string{"TUNNEL_MAX_ACTIVE_FLOWS"},
}
) )
func buildCreateCommand() *cli.Command { func buildCreateCommand() *cli.Command {
@ -705,6 +710,7 @@ func buildRunCommand() *cli.Command {
tunnelTokenFlag, tunnelTokenFlag,
icmpv4SrcFlag, icmpv4SrcFlag,
icmpv6SrcFlag, icmpv6SrcFlag,
maxActiveFlowsFlag,
} }
flags = append(flags, configureProxyFlags(false)...) flags = append(flags, configureProxyFlags(false)...)
return &cli.Command{ return &cli.Command{

View File

@ -4,16 +4,16 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"strconv"
"sync" "sync"
"sync/atomic" "sync/atomic"
"github.com/pkg/errors" pkgerrors "github.com/pkg/errors"
"github.com/rs/zerolog" "github.com/rs/zerolog"
cfdflow "github.com/cloudflare/cloudflared/flow"
"github.com/cloudflare/cloudflared/config" "github.com/cloudflare/cloudflared/config"
"github.com/cloudflare/cloudflared/connection" "github.com/cloudflare/cloudflared/connection"
cfdflow "github.com/cloudflare/cloudflared/flow"
"github.com/cloudflare/cloudflared/ingress" "github.com/cloudflare/cloudflared/ingress"
"github.com/cloudflare/cloudflared/proxy" "github.com/cloudflare/cloudflared/proxy"
"github.com/cloudflare/cloudflared/tunnelrpc/pogs" "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
@ -117,6 +117,30 @@ func (o *Orchestrator) UpdateConfig(version int32, config []byte) *pogs.UpdateCo
} }
} }
// overrideRemoteWarpRoutingWithLocalValues overrides the ingress.WarpRoutingConfig that comes from the remote with
// the local values if there is any.
func (o *Orchestrator) overrideRemoteWarpRoutingWithLocalValues(remoteWarpRouting *ingress.WarpRoutingConfig) error {
return o.overrideMaxActiveFlows(o.config.ConfigurationFlags["max-active-flows"], remoteWarpRouting)
}
// overrideMaxActiveFlows checks the local configuration flags, and if a value is found for the flags.MaxActiveFlows
// overrides the value that comes on the remote ingress.WarpRoutingConfig with the local value.
func (o *Orchestrator) overrideMaxActiveFlows(maxActiveFlowsLocalConfig string, remoteWarpRouting *ingress.WarpRoutingConfig) error {
// If max active flows isn't defined locally just use the remote value
if maxActiveFlowsLocalConfig == "" {
return nil
}
maxActiveFlowsLocalOverride, err := strconv.ParseUint(maxActiveFlowsLocalConfig, 10, 64)
if err != nil {
return pkgerrors.Wrapf(err, "failed to parse %s", "max-active-flows")
}
// Override the value that comes from the remote with the local value
remoteWarpRouting.MaxActiveFlows = maxActiveFlowsLocalOverride
return nil
}
// The caller is responsible to make sure there is no concurrent access // The caller is responsible to make sure there is no concurrent access
func (o *Orchestrator) updateIngress(ingressRules ingress.Ingress, warpRouting ingress.WarpRoutingConfig) error { func (o *Orchestrator) updateIngress(ingressRules ingress.Ingress, warpRouting ingress.WarpRoutingConfig) error {
select { select {
@ -125,6 +149,11 @@ func (o *Orchestrator) updateIngress(ingressRules ingress.Ingress, warpRouting i
default: default:
} }
// Overrides the local values, onto the remote values of the warp routing configuration
if err := o.overrideRemoteWarpRoutingWithLocalValues(&warpRouting); err != nil {
return pkgerrors.Wrap(err, "failed to merge local overrides into warp routing configuration")
}
// Assign the internal ingress rules to the parsed ingress // Assign the internal ingress rules to the parsed ingress
ingressRules.InternalRules = o.internalRules ingressRules.InternalRules = o.internalRules
@ -139,7 +168,7 @@ func (o *Orchestrator) updateIngress(ingressRules ingress.Ingress, warpRouting i
// The downside is minimized because none of the ingress.OriginService implementation have that requirement // The downside is minimized because none of the ingress.OriginService implementation have that requirement
proxyShutdownC := make(chan struct{}) proxyShutdownC := make(chan struct{})
if err := ingressRules.StartOrigins(o.log, proxyShutdownC); err != nil { if err := ingressRules.StartOrigins(o.log, proxyShutdownC); err != nil {
return errors.Wrap(err, "failed to start origin") return pkgerrors.Wrap(err, "failed to start origin")
} }
// Update the flow limit since the configuration might have changed // Update the flow limit since the configuration might have changed

View File

@ -16,6 +16,7 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
gows "github.com/gorilla/websocket" gows "github.com/gorilla/websocket"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/cloudflare/cloudflared/config" "github.com/cloudflare/cloudflared/config"
@ -106,25 +107,25 @@ func TestUpdateConfiguration(t *testing.T) {
require.Len(t, configV2.Ingress.Rules, 3) require.Len(t, configV2.Ingress.Rules, 3)
// originRequest of this ingress rule overrides global default // originRequest of this ingress rule overrides global default
require.Equal(t, config.CustomDuration{Duration: time.Second * 10}, configV2.Ingress.Rules[0].Config.ConnectTimeout) require.Equal(t, config.CustomDuration{Duration: time.Second * 10}, configV2.Ingress.Rules[0].Config.ConnectTimeout)
require.Equal(t, true, configV2.Ingress.Rules[0].Config.NoTLSVerify) require.True(t, configV2.Ingress.Rules[0].Config.NoTLSVerify)
// Inherited from global default // Inherited from global default
require.Equal(t, true, configV2.Ingress.Rules[0].Config.NoHappyEyeballs) require.True(t, configV2.Ingress.Rules[0].Config.NoHappyEyeballs)
// Validate ingress rule 1 // Validate ingress rule 1
require.Equal(t, "jira.tunnel.org", configV2.Ingress.Rules[1].Hostname) require.Equal(t, "jira.tunnel.org", configV2.Ingress.Rules[1].Hostname)
require.True(t, configV2.Ingress.Rules[1].Matches("jira.tunnel.org", "/users")) require.True(t, configV2.Ingress.Rules[1].Matches("jira.tunnel.org", "/users"))
require.Equal(t, "http://172.32.20.6:80", configV2.Ingress.Rules[1].Service.String()) require.Equal(t, "http://172.32.20.6:80", configV2.Ingress.Rules[1].Service.String())
// originRequest of this ingress rule overrides global default // originRequest of this ingress rule overrides global default
require.Equal(t, config.CustomDuration{Duration: time.Second * 30}, configV2.Ingress.Rules[1].Config.ConnectTimeout) require.Equal(t, config.CustomDuration{Duration: time.Second * 30}, configV2.Ingress.Rules[1].Config.ConnectTimeout)
require.Equal(t, true, configV2.Ingress.Rules[1].Config.NoTLSVerify) require.True(t, configV2.Ingress.Rules[1].Config.NoTLSVerify)
// Inherited from global default // Inherited from global default
require.Equal(t, true, configV2.Ingress.Rules[1].Config.NoHappyEyeballs) require.True(t, configV2.Ingress.Rules[1].Config.NoHappyEyeballs)
// Validate ingress rule 2, it's the catch-all rule // Validate ingress rule 2, it's the catch-all rule
require.True(t, configV2.Ingress.Rules[2].Matches("blogs.tunnel.io", "/2022/02/10")) require.True(t, configV2.Ingress.Rules[2].Matches("blogs.tunnel.io", "/2022/02/10"))
// Inherited from global default // Inherited from global default
require.Equal(t, config.CustomDuration{Duration: time.Second * 90}, configV2.Ingress.Rules[2].Config.ConnectTimeout) require.Equal(t, config.CustomDuration{Duration: time.Second * 90}, configV2.Ingress.Rules[2].Config.ConnectTimeout)
require.Equal(t, false, configV2.Ingress.Rules[2].Config.NoTLSVerify) require.False(t, configV2.Ingress.Rules[2].Config.NoTLSVerify)
require.Equal(t, true, configV2.Ingress.Rules[2].Config.NoHappyEyeballs) require.True(t, configV2.Ingress.Rules[2].Config.NoHappyEyeballs)
require.Equal(t, configV2.WarpRouting.ConnectTimeout.Duration, 10*time.Second) require.Equal(t, 10*time.Second, configV2.WarpRouting.ConnectTimeout.Duration)
originProxyV2, err := orchestrator.GetOriginProxy() originProxyV2, err := orchestrator.GetOriginProxy()
require.NoError(t, err) require.NoError(t, err)
@ -317,7 +318,7 @@ func TestConcurrentUpdateAndRead(t *testing.T) {
go func(i int, originProxy connection.OriginProxy) { go func(i int, originProxy connection.OriginProxy) {
defer wg.Done() defer wg.Done()
resp, err := proxyHTTP(originProxy, hostname) resp, err := proxyHTTP(originProxy, hostname)
require.NoError(t, err, "proxyHTTP %d failed %v", i, err) assert.NoError(t, err, "proxyHTTP %d failed %v", i, err)
defer resp.Body.Close() defer resp.Body.Close()
var warpRoutingDisabled bool var warpRoutingDisabled bool
@ -326,16 +327,16 @@ func TestConcurrentUpdateAndRead(t *testing.T) {
// v1 proxy, warp enabled // v1 proxy, warp enabled
case 200: case 200:
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
require.NoError(t, err) assert.NoError(t, err)
require.Equal(t, t.Name(), string(body)) assert.Equal(t, t.Name(), string(body))
warpRoutingDisabled = false warpRoutingDisabled = false
// v2 proxy, warp disabled // v2 proxy, warp disabled
case 204: case 204:
require.Greater(t, i, concurrentRequests/4) assert.Greater(t, i, concurrentRequests/4)
warpRoutingDisabled = true warpRoutingDisabled = true
// v3 proxy, warp enabled // v3 proxy, warp enabled
case 418: case 418:
require.Greater(t, i, concurrentRequests/2) assert.Greater(t, i, concurrentRequests/2)
warpRoutingDisabled = false warpRoutingDisabled = false
} }
@ -358,11 +359,10 @@ func TestConcurrentUpdateAndRead(t *testing.T) {
err = proxyTCP(ctx, originProxy, tcpOrigin.Addr().String(), w, pr) err = proxyTCP(ctx, originProxy, tcpOrigin.Addr().String(), w, pr)
if warpRoutingDisabled { if warpRoutingDisabled {
require.Error(t, err, "expect proxyTCP %d to return error", i) assert.Error(t, err, "expect proxyTCP %d to return error", i)
} else { } else {
require.NoError(t, err, "proxyTCP %d failed %v", i, err) assert.NoError(t, err, "proxyTCP %d failed %v", i, err)
} }
}(i, originProxy) }(i, originProxy)
if i == concurrentRequests/4 { if i == concurrentRequests/4 {
@ -388,6 +388,57 @@ func TestConcurrentUpdateAndRead(t *testing.T) {
wg.Wait() wg.Wait()
} }
// TestOverrideWarpRoutingConfigWithLocalValues tests that if a value is defined in the Config.ConfigurationFlags,
// it will override the value that comes from the remote result.
func TestOverrideWarpRoutingConfigWithLocalValues(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
assertMaxActiveFlows := func(orchestrator *Orchestrator, expectedValue uint64) {
configJson, err := orchestrator.GetConfigJSON()
require.NoError(t, err)
var result map[string]interface{}
err = json.Unmarshal(configJson, &result)
require.NoError(t, err)
warpRouting := result["warp-routing"].(map[string]interface{})
require.EqualValues(t, expectedValue, warpRouting["maxActiveFlows"])
}
remoteValue := uint64(100)
remoteIngress := ingress.Ingress{}
remoteWarpConfig := ingress.WarpRoutingConfig{
MaxActiveFlows: remoteValue,
}
remoteConfig := &Config{
Ingress: &remoteIngress,
WarpRouting: remoteWarpConfig,
ConfigurationFlags: map[string]string{},
}
orchestrator, err := NewOrchestrator(ctx, remoteConfig, testTags, []ingress.Rule{}, &testLogger)
require.NoError(t, err)
assertMaxActiveFlows(orchestrator, remoteValue)
// Add a local override for the maxActiveFlows
localValue := uint64(500)
remoteConfig.ConfigurationFlags["max-active-flows"] = fmt.Sprintf("%d", localValue)
// Force a configuration refresh
err = orchestrator.updateIngress(remoteIngress, remoteWarpConfig)
require.NoError(t, err)
// Check the value being used is the local one
assertMaxActiveFlows(orchestrator, localValue)
// Remove local override for the maxActiveFlows
delete(remoteConfig.ConfigurationFlags, "max-active-flows")
// Force a configuration refresh
err = orchestrator.updateIngress(remoteIngress, remoteWarpConfig)
require.NoError(t, err)
// Check the value being used is now the remote again
assertMaxActiveFlows(orchestrator, remoteValue)
}
func proxyHTTP(originProxy connection.OriginProxy, hostname string) (*http.Response, error) { func proxyHTTP(originProxy connection.OriginProxy, hostname string) (*http.Response, error) {
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s", hostname), nil) req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s", hostname), nil)
if err != nil { if err != nil {
@ -409,15 +460,16 @@ func proxyHTTP(originProxy connection.OriginProxy, hostname string) (*http.Respo
return w.Result(), nil return w.Result(), nil
} }
// nolint: testifylint // this is used inside go routines so it can't use `require.`
func tcpEyeball(t *testing.T, reqWriter io.WriteCloser, body string, respReadWriter *respReadWriteFlusher) { func tcpEyeball(t *testing.T, reqWriter io.WriteCloser, body string, respReadWriter *respReadWriteFlusher) {
writeN, err := reqWriter.Write([]byte(body)) writeN, err := reqWriter.Write([]byte(body))
require.NoError(t, err) assert.NoError(t, err)
readBuffer := make([]byte, writeN) readBuffer := make([]byte, writeN)
n, err := respReadWriter.Read(readBuffer) n, err := respReadWriter.Read(readBuffer)
require.NoError(t, err) assert.NoError(t, err)
require.Equal(t, body, string(readBuffer[:n])) assert.Equal(t, body, string(readBuffer[:n]))
require.Equal(t, writeN, n) assert.Equal(t, writeN, n)
} }
func proxyTCP(ctx context.Context, originProxy connection.OriginProxy, originAddr string, w http.ResponseWriter, reqBody io.ReadCloser) error { func proxyTCP(ctx context.Context, originProxy connection.OriginProxy, originAddr string, w http.ResponseWriter, reqBody io.ReadCloser) error {
@ -458,14 +510,15 @@ func serveTCPOrigin(t *testing.T, tcpOrigin net.Listener, wg *sync.WaitGroup) {
} }
} }
// nolint: testifylint // this is used inside go routines so it can't use `require.`
func echoTCP(t *testing.T, conn net.Conn) { func echoTCP(t *testing.T, conn net.Conn) {
readBuf := make([]byte, 1000) readBuf := make([]byte, 1000)
readN, err := conn.Read(readBuf) readN, err := conn.Read(readBuf)
require.NoError(t, err) assert.NoError(t, err)
writeN, err := conn.Write(readBuf[:readN]) writeN, err := conn.Write(readBuf[:readN])
require.NoError(t, err) assert.NoError(t, err)
require.Equal(t, readN, writeN) assert.Equal(t, readN, writeN)
} }
type validateHostHandler struct { type validateHostHandler struct {
@ -479,16 +532,17 @@ func (vhh *validateHostHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
return return
} }
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
w.Write([]byte(vhh.body)) _, _ = w.Write([]byte(vhh.body))
} }
// nolint: testifylint // this is used inside go routines so it can't use `require.`
func updateWithValidation(t *testing.T, orchestrator *Orchestrator, version int32, config []byte) { func updateWithValidation(t *testing.T, orchestrator *Orchestrator, version int32, config []byte) {
resp := orchestrator.UpdateConfig(version, config) resp := orchestrator.UpdateConfig(version, config)
require.NoError(t, resp.Err) assert.NoError(t, resp.Err)
require.Equal(t, version, resp.LastAppliedVersion) assert.Equal(t, version, resp.LastAppliedVersion)
} }
// TestClosePreviousProxies makes sure proxies started in the pervious configuration version are shutdown // TestClosePreviousProxies makes sure proxies started in the previous configuration version are shutdown
func TestClosePreviousProxies(t *testing.T) { func TestClosePreviousProxies(t *testing.T) {
var ( var (
hostname = "hello.tunnel1.org" hostname = "hello.tunnel1.org"
@ -532,6 +586,7 @@ func TestClosePreviousProxies(t *testing.T) {
originProxyV1, err := orchestrator.GetOriginProxy() originProxyV1, err := orchestrator.GetOriginProxy()
require.NoError(t, err) require.NoError(t, err)
// nolint: bodyclose
resp, err := proxyHTTP(originProxyV1, hostname) resp, err := proxyHTTP(originProxyV1, hostname)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode) require.Equal(t, http.StatusOK, resp.StatusCode)
@ -540,12 +595,14 @@ func TestClosePreviousProxies(t *testing.T) {
originProxyV2, err := orchestrator.GetOriginProxy() originProxyV2, err := orchestrator.GetOriginProxy()
require.NoError(t, err) require.NoError(t, err)
// nolint: bodyclose
resp, err = proxyHTTP(originProxyV2, hostname) resp, err = proxyHTTP(originProxyV2, hostname)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, http.StatusTeapot, resp.StatusCode) require.Equal(t, http.StatusTeapot, resp.StatusCode)
// The hello-world server in config v1 should have been stopped. We wait a bit since it's closed asynchronously. // The hello-world server in config v1 should have been stopped. We wait a bit since it's closed asynchronously.
time.Sleep(time.Millisecond * 10) time.Sleep(time.Millisecond * 10)
// nolint: bodyclose
resp, err = proxyHTTP(originProxyV1, hostname) resp, err = proxyHTTP(originProxyV1, hostname)
require.Error(t, err) require.Error(t, err)
require.Nil(t, resp) require.Nil(t, resp)
@ -557,6 +614,7 @@ func TestClosePreviousProxies(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.NotEqual(t, originProxyV1, originProxyV3) require.NotEqual(t, originProxyV1, originProxyV3)
// nolint: bodyclose
resp, err = proxyHTTP(originProxyV3, hostname) resp, err = proxyHTTP(originProxyV3, hostname)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode) require.Equal(t, http.StatusOK, resp.StatusCode)
@ -566,6 +624,7 @@ func TestClosePreviousProxies(t *testing.T) {
// Wait for proxies to shutdown // Wait for proxies to shutdown
time.Sleep(time.Millisecond * 10) time.Sleep(time.Millisecond * 10)
// nolint: bodyclose
resp, err = proxyHTTP(originProxyV3, hostname) resp, err = proxyHTTP(originProxyV3, hostname)
require.Error(t, err) require.Error(t, err)
require.Nil(t, resp) require.Nil(t, resp)
@ -622,7 +681,7 @@ func TestPersistentConnection(t *testing.T) {
go func() { go func() {
defer wg.Done() defer wg.Done()
conn, err := tcpOrigin.Accept() conn, err := tcpOrigin.Accept()
require.NoError(t, err) assert.NoError(t, err)
defer conn.Close() defer conn.Close()
// Expect 3 TCP messages // Expect 3 TCP messages
@ -630,26 +689,26 @@ func TestPersistentConnection(t *testing.T) {
echoTCP(t, conn) echoTCP(t, conn)
} }
}() }()
// Simulate cloudflared recieving a TCP connection // Simulate cloudflared receiving a TCP connection
go func() { go func() {
defer wg.Done() defer wg.Done()
require.NoError(t, proxyTCP(ctx, originProxy, tcpOrigin.Addr().String(), tcpRespReadWriter, tcpReqReader)) assert.NoError(t, proxyTCP(ctx, originProxy, tcpOrigin.Addr().String(), tcpRespReadWriter, tcpReqReader))
}() }()
// Simulate cloudflared recieving a WS connection // Simulate cloudflared receiving a WS connection
go func() { go func() {
defer wg.Done() defer wg.Done()
req, err := http.NewRequest(http.MethodGet, hostname, wsReqReader) req, err := http.NewRequest(http.MethodGet, hostname, wsReqReader)
require.NoError(t, err) assert.NoError(t, err)
// ProxyHTTP will add Connection, Upgrade and Sec-Websocket-Version headers // ProxyHTTP will add Connection, Upgrade and Sec-Websocket-Version headers
req.Header.Add("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ==") req.Header.Add("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ==")
log := zerolog.Nop() log := zerolog.Nop()
respWriter, err := connection.NewHTTP2RespWriter(req, wsRespReadWriter, connection.TypeWebsocket, &log) respWriter, err := connection.NewHTTP2RespWriter(req, wsRespReadWriter, connection.TypeWebsocket, &log)
require.NoError(t, err) assert.NoError(t, err)
err = originProxy.ProxyHTTP(respWriter, tracing.NewTracedHTTPRequest(req, 0, &log), true) err = originProxy.ProxyHTTP(respWriter, tracing.NewTracedHTTPRequest(req, 0, &log), true)
require.NoError(t, err) assert.NoError(t, err)
}() }()
// Simulate eyeball WS and TCP connections // Simulate eyeball WS and TCP connections