diff --git a/cmd/cloudflared/tunnel/cmd.go b/cmd/cloudflared/tunnel/cmd.go index ae1e5f55..710a79fc 100644 --- a/cmd/cloudflared/tunnel/cmd.go +++ b/cmd/cloudflared/tunnel/cmd.go @@ -837,6 +837,13 @@ func configureProxyFlags(shouldHide bool) []cli.Flag { EnvVars: []string{"TUNNEL_NO_CHUNKED_ENCODING"}, Hidden: shouldHide, }), + altsrc.NewBoolFlag(&cli.BoolFlag{ + Name: ingress.Http2OriginFlag, + Usage: "Enables HTTP/2 origin servers.", + EnvVars: []string{"TUNNEL_ORIGIN_ENABLE_HTTP2"}, + Hidden: shouldHide, + Value: false, + }), } return append(flags, sshFlags(shouldHide)...) } diff --git a/config/configuration.go b/config/configuration.go index fdbf0aef..5112d1ec 100644 --- a/config/configuration.go +++ b/config/configuration.go @@ -227,6 +227,8 @@ type OriginRequestConfig struct { ProxyType *string `yaml:"proxyType" json:"proxyType,omitempty"` // IP rules for the proxy service IPRules []IngressIPRule `yaml:"ipRules" json:"ipRules,omitempty"` + // Attempt to connect to origin with HTTP/2 + Http2Origin *bool `yaml:"http2Origin" json:"http2Origin,omitempty"` } type IngressIPRule struct { diff --git a/config/configuration_test.go b/config/configuration_test.go index 96adb74b..b613b8d1 100644 --- a/config/configuration_test.go +++ b/config/configuration_test.go @@ -144,7 +144,8 @@ var rawJsonConfig = []byte(` "ports": [443, 4443], "allow": true } - ] + ], + "http2Origin": true } `) @@ -191,6 +192,7 @@ func assertConfig( assert.Equal(t, true, *config.NoTLSVerify) assert.Equal(t, uint(9000), *config.ProxyPort) assert.Equal(t, "socks", *config.ProxyType) + assert.Equal(t, true, *config.Http2Origin) privateV4 := "10.0.0.0/8" privateV6 := "fc00::/7" diff --git a/ingress/config.go b/ingress/config.go index 350edb0f..e6eed0bf 100644 --- a/ingress/config.go +++ b/ingress/config.go @@ -36,6 +36,7 @@ const ( NoChunkedEncodingFlag = "no-chunked-encoding" ProxyAddressFlag = "proxy-address" ProxyPortFlag = "proxy-port" + Http2OriginFlag = "http2-origin" ) const ( @@ -128,6 +129,7 @@ func originRequestFromSingeRule(c *cli.Context) OriginRequestConfig { var proxyAddress = defaultProxyAddress var proxyPort uint var proxyType string + var http2Origin bool if flag := ProxyConnectTimeoutFlag; c.IsSet(flag) { connectTimeout = config.CustomDuration{Duration: c.Duration(flag)} } @@ -171,9 +173,13 @@ func originRequestFromSingeRule(c *cli.Context) OriginRequestConfig { // Note TUN-3758 , we use Int because UInt is not supported with altsrc proxyPort = uint(c.Int(flag)) } + if flag := Http2OriginFlag; c.IsSet(flag) { + http2Origin = c.Bool(flag) + } if c.IsSet(Socks5Flag) { proxyType = socksProxy } + return OriginRequestConfig{ ConnectTimeout: connectTimeout, TLSTimeout: tlsTimeout, @@ -190,6 +196,7 @@ func originRequestFromSingeRule(c *cli.Context) OriginRequestConfig { ProxyAddress: proxyAddress, ProxyPort: proxyPort, ProxyType: proxyType, + Http2Origin: http2Origin, } } @@ -255,6 +262,9 @@ func originRequestFromConfig(c config.OriginRequestConfig) OriginRequestConfig { } } } + if c.Http2Origin != nil { + out.Http2Origin = *c.Http2Origin + } return out } @@ -298,6 +308,8 @@ type OriginRequestConfig struct { ProxyType string `yaml:"proxyType" json:"proxyType"` // IP rules for the proxy service IPRules []ipaccess.Rule `yaml:"ipRules" json:"ipRules"` + // Attempt to connect to origin with HTTP/2 + Http2Origin bool `yaml:"http2Origin" json:"http2Origin"` } func (defaults *OriginRequestConfig) setConnectTimeout(overrides config.OriginRequestConfig) { @@ -403,6 +415,12 @@ func (defaults *OriginRequestConfig) setIPRules(overrides config.OriginRequestCo } } +func (defaults *OriginRequestConfig) setHttp2Origin(overrides config.OriginRequestConfig) { + if val := overrides.Http2Origin; val != nil { + defaults.Http2Origin = *val + } +} + // SetConfig gets config for the requests that cloudflared sends to origins. // Each field has a setter method which sets a value for the field by trying to find: // 1. The user config for this rule @@ -428,6 +446,7 @@ func setConfig(defaults OriginRequestConfig, overrides config.OriginRequestConfi cfg.setProxyAddress(overrides) cfg.setProxyType(overrides) cfg.setIPRules(overrides) + cfg.setHttp2Origin(overrides) return cfg } @@ -475,6 +494,7 @@ func ConvertToRawOriginConfig(c OriginRequestConfig) config.OriginRequestConfig ProxyPort: zeroUIntToNil(c.ProxyPort), ProxyType: emptyStringToNil(c.ProxyType), IPRules: convertToRawIPRules(c.IPRules), + Http2Origin: defaultBoolToNil(c.Http2Origin), } } diff --git a/ingress/origin_service.go b/ingress/origin_service.go index 6f348bee..b45ec6a2 100644 --- a/ingress/origin_service.go +++ b/ingress/origin_service.go @@ -290,6 +290,7 @@ func newHTTPTransport(service OriginService, cfg OriginRequestConfig, log *zerol TLSHandshakeTimeout: cfg.TLSTimeout.Duration, ExpectContinueTimeout: 1 * time.Second, TLSClientConfig: &tls.Config{RootCAs: originCertPool, InsecureSkipVerify: cfg.NoTLSVerify}, + ForceAttemptHTTP2: cfg.Http2Origin, } if _, isHelloWorld := service.(*helloWorld); !isHelloWorld && cfg.OriginServerName != "" { httpTransport.TLSClientConfig.ServerName = cfg.OriginServerName diff --git a/ingress/rule_test.go b/ingress/rule_test.go index 7561709a..279d7911 100644 --- a/ingress/rule_test.go +++ b/ingress/rule_test.go @@ -182,25 +182,25 @@ func TestMarshalJSON(t *testing.T) { { name: "Nil", path: nil, - expected: `{"hostname":"example.com","path":null,"service":"https://localhost:8000","originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null}}`, + expected: `{"hostname":"example.com","path":null,"service":"https://localhost:8000","originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false}}`, want: true, }, { name: "Nil regex", path: &Regexp{Regexp: nil}, - expected: `{"hostname":"example.com","path":null,"service":"https://localhost:8000","originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null}}`, + expected: `{"hostname":"example.com","path":null,"service":"https://localhost:8000","originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false}}`, want: true, }, { name: "Empty", path: &Regexp{Regexp: regexp.MustCompile("")}, - expected: `{"hostname":"example.com","path":"","service":"https://localhost:8000","originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null}}`, + expected: `{"hostname":"example.com","path":"","service":"https://localhost:8000","originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false}}`, want: true, }, { name: "Basic", path: &Regexp{Regexp: regexp.MustCompile("/echo")}, - expected: `{"hostname":"example.com","path":"/echo","service":"https://localhost:8000","originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null}}`, + expected: `{"hostname":"example.com","path":"/echo","service":"https://localhost:8000","originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false}}`, want: true, }, }