diff --git a/.chloggen/10952-allow-h2c-connections-otlphttp.yaml b/.chloggen/10952-allow-h2c-connections-otlphttp.yaml new file mode 100644 index 00000000000..8a8dd24240b --- /dev/null +++ b/.chloggen/10952-allow-h2c-connections-otlphttp.yaml @@ -0,0 +1,25 @@ +# Use this changelog template to create an entry for release notes. + +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: 'bug_fix' + +# The name of the component, or a single word describing the area of concern, (e.g. otlpreceiver) +component: otlpreceiver + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: "Allow H2C connections to otlp http receiver" + +# One or more tracking issues or pull requests related to the change +issues: [10952] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: + +# Optional: The change log or logs in which this entry should be included. +# e.g. '[user]' or '[user, api]' +# Include 'user' if the change is relevant to end users. +# Include 'api' if there is a change to a library API. +# Default: '[user]' +change_logs: [] diff --git a/config/confighttp/confighttp.go b/config/confighttp/confighttp.go index 3f70b64bf5e..1adbce46357 100644 --- a/config/confighttp/confighttp.go +++ b/config/confighttp/confighttp.go @@ -19,6 +19,7 @@ import ( "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.opentelemetry.io/otel" "golang.org/x/net/http2" + "golang.org/x/net/http2/h2c" "golang.org/x/net/publicsuffix" "go.opentelemetry.io/collector/component" @@ -105,6 +106,10 @@ type ClientConfig struct { HTTP2PingTimeout time.Duration `mapstructure:"http2_ping_timeout"` // Cookies configures the cookie management of the HTTP client. Cookies *CookiesConfig `mapstructure:"cookies"` + + // AllowH2CUpgrade enables clients to upgrade an existing + // HTTP/1.1 connection to an HTTP/2 connection without using TLS. + AllowH2CUpgrade bool `mapstructure:"allow_h2c_upgrade"` } // CookiesConfig defines the configuration of the HTTP client regarding cookies served by the server. @@ -327,6 +332,10 @@ type ServerConfig struct { // is zero, the value of ReadTimeout is used. If both are // zero, there is no timeout. IdleTimeout time.Duration `mapstructure:"idle_timeout"` + + // AllowH2CUpgrade enables clients to upgrade an existing + // HTTP/1.1 connection to an HTTP/2 connection without using TLS. + AllowH2CUpgrade bool `mapstructure:"allow_h2c_upgrade"` } // NewDefaultServerConfig returns ServerConfig type object with default values. @@ -340,6 +349,7 @@ func NewDefaultServerConfig() ServerConfig { WriteTimeout: 30 * time.Second, ReadHeaderTimeout: 1 * time.Minute, IdleTimeout: 1 * time.Minute, + AllowH2CUpgrade: false, } } @@ -419,6 +429,12 @@ func (hss *ServerConfig) ToServer(_ context.Context, host component.Host, settin o.apply(serverOpts) } + if hss.AllowH2CUpgrade { + settings.Logger.Warn("H2C Upgrade is enabled, this is not recommended for production environments") + h2s := &http2.Server{IdleTimeout: 0} + handler = h2c.NewHandler(handler, h2s) + } + if hss.MaxRequestBodySize <= 0 { hss.MaxRequestBodySize = defaultMaxRequestBodySize } diff --git a/config/confighttp/confighttp_test.go b/config/confighttp/confighttp_test.go index 7d16eb12ef3..ae8884b4346 100644 --- a/config/confighttp/confighttp_test.go +++ b/config/confighttp/confighttp_test.go @@ -6,6 +6,7 @@ package confighttp import ( "bytes" "context" + "crypto/tls" "errors" "fmt" "io" @@ -23,6 +24,7 @@ import ( "go.opentelemetry.io/otel/metric" "go.uber.org/zap" "go.uber.org/zap/zaptest/observer" + "golang.org/x/net/http2" "go.opentelemetry.io/collector/client" "go.opentelemetry.io/collector/component" @@ -564,6 +566,7 @@ func TestHttpReception(t *testing.T) { tlsClientCreds *configtls.ClientConfig hasError bool forceHTTP1 bool + allowh2c bool }{ { name: "noTLS", @@ -572,6 +575,14 @@ func TestHttpReception(t *testing.T) { Insecure: true, }, }, + { + name: "noTLS and AllowH2CUpgrade", + tlsServerCreds: nil, + tlsClientCreds: &configtls.ClientConfig{ + Insecure: true, + }, + allowh2c: true, + }, { name: "TLS", tlsServerCreds: &configtls.ServerConfig{ @@ -683,8 +694,9 @@ func TestHttpReception(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { hss := &ServerConfig{ - Endpoint: "localhost:0", - TLSSetting: tt.tlsServerCreds, + Endpoint: "localhost:0", + TLSSetting: tt.tlsServerCreds, + AllowH2CUpgrade: tt.allowh2c, } ln, err := hss.ToListener(context.Background()) require.NoError(t, err) @@ -703,11 +715,13 @@ func TestHttpReception(t *testing.T) { _ = s.Serve(ln) }() - prefix := "https://" expectedProto := "HTTP/2.0" + prefix := "https://" if tt.tlsClientCreds.Insecure { prefix = "http://" - expectedProto = "HTTP/1.1" + if !tt.allowh2c { + expectedProto = "HTTP/1.1" + } } hcs := &ClientConfig{ @@ -718,6 +732,17 @@ func TestHttpReception(t *testing.T) { client, errClient := hcs.ToClient(context.Background(), componenttest.NewNopHost(), nilProvidersSettings) require.NoError(t, errClient) + if tt.allowh2c { + client.Transport = &http2.Transport{ + AllowHTTP: true, + // Pretend we are dialing a TLS endpoint. (Note, we ignore the passed tls.Config) + DialTLS: func(netw, addr string, _ *tls.Config) (net.Conn, error) { + return net.Dial(netw, addr) + }, + } + defer client.Transport.(*http2.Transport).CloseIdleConnections() + } + if tt.forceHTTP1 { expectedProto = "HTTP/1.1" client.Transport.(*http.Transport).ForceAttemptHTTP2 = false