Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions cmd/cloudflared/tunnel/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -1065,8 +1065,9 @@ func configureProxyFlags(shouldHide bool) []cli.Flag {
Hidden: shouldHide,
}),
altsrc.NewBoolFlag(&cli.BoolFlag{
Name: ingress.Http2OriginFlag,
Usage: "Enables HTTP/2 origin servers.",
Name: ingress.Http2OriginFlag,
Usage: "Use HTTP/2 to connect to the origin. TLS origins negotiate HTTP/2. " +
"Cleartext HTTP and Unix origins use prior-knowledge h2c, which requires HTTP/2 support with no HTTP/1.1 fallback.",
EnvVars: []string{"TUNNEL_ORIGIN_ENABLE_HTTP2"},
Hidden: shouldHide,
Value: false,
Expand Down
6 changes: 4 additions & 2 deletions ingress/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,9 @@ 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
// Use HTTP/2 to connect to the origin. TLS origins negotiate HTTP/2.
// Cleartext HTTP and Unix origins use prior-knowledge h2c, which requires
// HTTP/2 support with no HTTP/1.1 fallback.
Http2Origin bool `yaml:"http2Origin" json:"http2Origin"`

// Access holds all access related configs
Expand Down Expand Up @@ -544,7 +546,7 @@ func ConvertToRawOriginConfig(c OriginRequestConfig) config.OriginRequestConfig
}

func convertToRawIPRules(ipRules []ipaccess.Rule) []config.IngressIPRule {
result := make([]config.IngressIPRule, 0)
result := make([]config.IngressIPRule, 0, len(ipRules))
for _, r := range ipRules {
cidr := r.StringCIDR()

Expand Down
35 changes: 29 additions & 6 deletions ingress/origin_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ type rawTCPService struct {
name string
dialer net.Dialer
writeTimeout time.Duration
logger *zerolog.Logger
}

func (o *rawTCPService) String() string {
Expand Down Expand Up @@ -233,10 +232,14 @@ func (o *helloWorld) start(
if err != nil {
return errors.Wrap(err, "Cannot start Hello World Server")
}
go hello.StartHelloWorldServer(log, helloListener, shutdownC)
go func() {
if err := hello.StartHelloWorldServer(log, helloListener, shutdownC); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Error().Err(err).Msg("Hello World server failed")
}
}()
o.server = helloListener

o.httpService.url = &url.URL{
o.url = &url.URL{
Scheme: "https",
Host: o.server.Addr().String(),
}
Expand Down Expand Up @@ -343,21 +346,42 @@ func (nrc *NopReadCloser) Close() error {
return nil
}

func isH2COrigin(service OriginService) bool {
switch service := service.(type) {
case *httpService:
return service.url != nil && service.url.Scheme == "http"
case *unixSocketPath:
return service.scheme == "http"
default:
return false
}
}

func newHTTPTransport(service OriginService, cfg OriginRequestConfig, log *zerolog.Logger) (*http.Transport, error) {
originCertPool, err := tlsconfig.LoadOriginCA(cfg.CAPool, log)
if err != nil {
return nil, errors.Wrap(err, "Error loading cert pool")
}

useH2C := cfg.Http2Origin && isH2COrigin(service)
httpTransport := http.Transport{
Proxy: http.ProxyFromEnvironment,
MaxIdleConns: cfg.KeepAliveConnections,
MaxIdleConnsPerHost: cfg.KeepAliveConnections,
IdleConnTimeout: cfg.KeepAliveTimeout.Duration,
TLSHandshakeTimeout: cfg.TLSTimeout.Duration,
ExpectContinueTimeout: 1 * time.Second,
TLSClientConfig: &tls.Config{RootCAs: originCertPool, InsecureSkipVerify: cfg.NoTLSVerify},
ForceAttemptHTTP2: cfg.Http2Origin,
TLSClientConfig: &tls.Config{
RootCAs: originCertPool,
InsecureSkipVerify: cfg.NoTLSVerify, //nolint:gosec // Disabling verification is an explicit origin setting.
},
ForceAttemptHTTP2: cfg.Http2Origin && !useH2C,
}
if useH2C {
// HTTP/1 must remain disabled for the transport to use prior-knowledge h2c.
protocols := new(http.Protocols)
protocols.SetUnencryptedHTTP2(true)
httpTransport.Protocols = protocols
}
if _, isHelloWorld := service.(*helloWorld); !isHelloWorld && cfg.OriginServerName != "" {
httpTransport.TLSClientConfig.ServerName = cfg.OriginServerName
Expand All @@ -374,7 +398,6 @@ func newHTTPTransport(service OriginService, cfg OriginRequestConfig, log *zerol
// DialContext depends on which kind of origin is being used.
dialContext := dialer.DialContext
switch service := service.(type) {

// If this origin is a unix socket, enforce network type "unix".
case *unixSocketPath:
httpTransport.DialContext = func(ctx context.Context, _, _ string) (net.Conn, error) {
Expand Down
68 changes: 68 additions & 0 deletions ingress/origin_service_posix_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//go:build !windows

package ingress

import (
"errors"
"net"
"net/http"
"os"
"testing"
"time"

"github.com/rs/zerolog"
"github.com/stretchr/testify/require"
)

func TestUnixSocketH2COrigin(t *testing.T) {
t.Parallel()

socketFile, err := os.CreateTemp("/tmp", "cloudflared-h2c-*.sock")
require.NoError(t, err)
socketPath := socketFile.Name()
require.NoError(t, socketFile.Close())
require.NoError(t, os.Remove(socketPath))

listener, err := net.Listen("unix", socketPath)
require.NoError(t, err)
t.Cleanup(func() {
err := os.Remove(socketPath)
if err != nil && !errors.Is(err, os.ErrNotExist) {
t.Errorf("failed to remove Unix socket: %v", err)
}
})

protocols := new(http.Protocols)
protocols.SetUnencryptedHTTP2(true)
server := &http.Server{
ReadHeaderTimeout: time.Second,
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Origin-Protocol", r.Proto)
}),
Protocols: protocols,
}
serveErr := make(chan error, 1)
go func() {
serveErr <- server.Serve(listener)
}()
t.Cleanup(func() {
require.NoError(t, server.Close())
require.ErrorIs(t, <-serveErr, http.ErrServerClosed)
})

log := zerolog.Nop()
service := &unixSocketPath{
path: socketPath,
scheme: "http",
}
require.NoError(t, service.start(&log, nil, OriginRequestConfig{Http2Origin: true}))
t.Cleanup(service.transport.CloseIdleConnections)

req, err := http.NewRequest(http.MethodGet, "http://localhost", nil)
require.NoError(t, err)
resp, err := service.RoundTrip(req)
require.NoError(t, err)
require.Equal(t, "HTTP/2.0", resp.Header.Get("X-Origin-Protocol"))
require.Equal(t, 2, resp.ProtoMajor)
require.NoError(t, resp.Body.Close())
}
Loading