Repository: caddyserver/caddy (language: go)

Issue:
Does reverse_proxy handler support proxying H2C traffic to a UDS?
To proxy gRPC traffic to a gRPC inbound of Xray (similar to V2Ray), I used to let Xray listen on a port, like 4443, and let Caddy pick gRPC traffic with right Path (here it is `/example/TunMulti`) out, then proxy it to an upstream (here it is `:4443`) . The config file reads:

``` json
{
    "apps": {
        "http": {
            "servers": {
                "fall": {
                    "listen": [
                        "unix//dev/shm/fall.sock"
                    ],
                    "allow_h2c": true,
                    "routes": [
                        {
                            "handle": [
                                {
                                    "handler": "subroute",
                                    "routes": [
                                        {
                                            "match": [
                                                {
                                                    "protocol": "grpc",
                                                    "path": [
                                                        "/example/TunMulti"
                                                    ]
                                                }
                                            ],
                                            "handle": [
                                                {
                                                    "handler": "reverse_proxy",
                                                    "transport": {
                                                        "protocol": "http",
                                                        "versions": [
                                                            "h2c"
                                                        ]
                                                    },
                                                    "upstreams": [
                                                        {
                                                            "dial": ":4443"
                                                        }
                                                    ]
                                                }
                                            ]
                                        }
                                    ]
                                }
                            ]
                        }
                    ]
                }
            }
        }
    }
}
```

There is something else I need to explain:
Here a VLESS (a protocol) inbound of Xray is working behind :443 (not `:4443`), it decrypts all TLS traffic and foward everything non-VLESS to a fallback, which can be a port or a UDS (here it is `fall.sock`, on which Caddy is listening). The traffic flows through Caddy has to be H2C because it has been decrypted, and the upstream (`:4443` or `grpc.sock` in the following) is listened by a gRPC inbound of Xray.

Everything works fine until I try to replace `:4443` (a port) to `grpc.sock` (a UDS), that is, changing

``` json
"dial": ":4443"
```

to

``` json
"dial": "unix//dev/shm/grpc.sock"
```

A similar change is made in the config file of Xray. Such configs do not work, and Caddy reports some error like:

``` json
"msg": "dial tcp: lookup /dev/shm/grpc.sock: no such host"
```

My question is:
Does reverse_proxy handler support proxying H2C traffic to a UDS? If it does, I hope to get some help from you. Many thanks.

P.S. I tried `"h2c_unix"` mentioned [here](https://github.com/caddyserver/caddy/issues/3218#issuecomment-610055329), but seems not working.


Discussion / hints:
`"versions"` needs to contain both `"h2c"` and `"2"` to work properly. The `"h2c"` part is a hack, but `"2"` is necessary so the codepath for the H2 transport (which is used for regular HTTPS) to work.
Sorry to forget mentioning, I tried to contain both of them, too, but still not working.

Edit: It's important to point out that, when Caddy dials to a port, even only "h2c" is contained, it still works fine.
Are you able to build Caddy and add some debug lines? I'm not sure I'll be able to replicate the issue myself right now. (tldr checkout the repo, `cd cmd/caddy` then run `go build`, which will output a `caddy` binary you can use)

First, add just below this chunk of code, a log statement:

https://github.com/caddyserver/caddy/blob/5333c3528bd2badf1676efcced322d151e3706c8/modules/caddyhttp/reverseproxy/reverseproxy.go#L430-L436

Put on the following line:
```go
fmt.Printf("\ndialInfo: %+v\n", dialInfo)
```

This should show what the dial info has been parsed as.

Then later it gets used by the dialer:

https://github.com/caddyserver/caddy/blob/5333c3528bd2badf1676efcced322d151e3706c8/modules/caddyhttp/reverseproxy/httptransport.go#L132-L136

You can add a log line here as well (on the line before `net.Dial()`) to make sure the network and address still look right.

```go
fmt.Printf("\ndialNet: %+v\ndialAddr: %+v\n", network, address)
```
OK, thank you. I'm a newbie though, I'll take a try when my computer at hand later.
Reading the code more closely, I think the `// TODO: no context, thus potentially wrong dial info` is the issue, essentially.

Go's http2 package doesn't provide a `DialTLSContext` closure that would be what we need to pass through the network the way we do for for the http v1 transport:

https://github.com/caddyserver/caddy/blob/5333c3528bd2badf1676efcced322d151e3706c8/modules/caddyhttp/reverseproxy/httptransport.go#L196-L210

So I think this is more of an upstream issue at this point, Go doesn't really give us the facilities to do this correctly. We could hack in some stuff to pipe in the network, but it would be messy and temporary, and probably not work in all cases.
I opened a bug report upstream to see if we can resolve this TODO. But I guess for now, you'll have to stick with TCP 😞 
I see (I'm not a pro as you guys so I can only understand your point roughly :), and thank you all the same. Earlier I have been told that Caddy2 does not support h2c to UDS elsewhere (a repo in Chinese), and I think I need to confirm it (essentially, in your word) so I wrote this issue. Does it mean that I do not need to debug as you told any more? And again, thank you for your kindly reply, wish Caddy being more powerful with your continuously development.
> Does it mean that I do not need to debug as you told any more?

Yeah, I think I understand the way things are wired up enough that it's not necessary anymore.
OK, thank you!
I just noticed that https://github.com/golang/net/pull/123 might fix this issue, so may I ask in advance that, if I want to set the reverse proxy to some h2c UDS in Caddyfile within one line later, should the address "h2c://unix//path/to/the.sock" work? Thank you.
`h2c://` is a Caddyfile-only shortcut for setting the versions to `"versions": ["h2c", "2"]`. It seems like you're using JSON, so you wouldn't use that. But yes, the upstream dial address would be `unix//path/to/sock`
You're right. Recently I have switched to Caddyfile for its readability. I just feel a little unsure about the somehow strange prefix "h2c://unix//" (so many slashes) so I need a certain answer from a pro. Thank you!
Recently I've read about golang release notes.
In https://go.dev/doc/go1.18 , it's seems DialTLS & DialTLSContext is supported by golang :
```
net/http
    On WebAssembly targets, the Dial, DialContext, DialTLS and DialTLSContext method fields in Transport will now be correctly used, if specified, for making HTTP requests. 
```

Then I've build a new caddy by using xcaddy
```
xcaddy build HEAD --with github.com/mastercactapus/caddy2-proxyprotocol --with github.com/caddy-dns/cloudflare --with github.com/mholt/caddy-l4
``` 
so caddy version is now v2.5.3-0.20220803170451-1960a0dc117d h1:kIBsNVyxn9SPJ44s6UlljPK7pWjxgCU3LVKPz0+GyVY=

but reverse proxy dial by using UDS still call error , logs here :
`
Aug 03 01:49:12 server-01 caddy[559725]:         DEBUG        http.log.error.log1        dial tcp: lookup /dev/shm/grpc: no such host        {"request": {"remote_ip": "remote ip", "remote_port": "61676", "proto": "HTTP/2.0", "method": "POST", "host": "server-01:443", "uri": "/grpc/TunMulti", "headers": {"Content-Type": ["application/grpc"], "User-Agent": ["grpc-go/1.45.0"], "Te": ["trailers"]}}, "duration": 0.000145173, "status": 502, "err_id": "0if2af75g", "err_trace": "reverseproxy.statusError (reverseproxy.go:1201)"}
`
`
Aug 03 01:49:12 server-01 caddy[559725]:         ERROR        http.log.access.log1        handled request        {"request": {"remote_ip": "remote ip", "remote_port": "61676", "proto": "HTTP/2.0", "method": "POST", "host": "server-01:443", "uri": "/grpc/TunMulti", "headers": {"Content-Type": ["application/grpc"], "User-Agent": ["grpc-go/1.45.0"], "Te": ["trailers"]}}, "user_id": "", "duration": 0.000145173, "size": 7021, "status": 502, "resp_headers": {"Accept-Ranges": ["bytes"], "Content-Length": ["7021"], "Server": ["Caddy"], "Etag": [""], "Content-Type": ["text/html; charset=utf-8"]}}
`

configurations here
```
          { "handle":[{
              "handler":"reverse_proxy",
              "flush_interval":-1,
              "upstreams":[{"dial":"unix//dev/shm/grpc"}],
              "transport":{"protocol":"http","versions":["h2c"]}}],
            "match":[{"header":{"Content-Type":["application/grpc"]},"path":["/grpc/*"]}]},
```

I've read https://github.com/caddyserver/caddy/raw/a379fa4c6c5d58aa6b812bb21a879ce0944c1ccb/modules/caddyhttp/reverseproxy/httptransport.go
```
	if sliceContains(h.Versions, "h2c") {
		// crafting our own http2.Transport doesn't allow us to utilize
		// most of the customizations/preferences on the http.Transport,
		// because, for some reason, only http2.ConfigureTransport()
		// is allowed to set the unexported field that refers to a base
		// http.Transport config; oh well
		h2t := &http2.Transport{
			// kind of a hack, but for plaintext/H2C requests, pretend to dial TLS
			DialTLS: func(network, addr string, _ *tls.Config) (net.Conn, error) {
				// TODO: no context, thus potentially wrong dial info
				return net.Dial(network, addr)
			},
			AllowHTTP: true,
		}
		if h.Compression != nil {
			h2t.DisableCompression = !*h.Compression
		}
		h.h2cTransport = h2t
	}
```
and https://pkg.go.dev/net/http
```
	// DialTLS specifies an optional dial function for creating
	// TLS connections for non-proxied HTTPS requests.
	//
	// Deprecated: Use DialTLSContext instead, which allows the transport
	// to cancel dials as soon as they are no longer needed.
	// If both are set, DialTLSContext takes priority.
	DialTLS func(network, addr string) (net.Conn, error)
```
which seems caddy already using DialTLS for reverse proxy in h2c mode, so what can I do to make it works ?

@PaTTeeL no, we still don't have it. We need https://github.com/golang/net/pull/123 to be merged. We need `DialTLSContext` in `http2.Transport`, not `http.Transport`.
> @PaTTeeL no, we still don't have it. We need [golang/net#123](https://github.com/golang/net/pull/123) to be merged. We need `DialTLSContext` in `http2.Transport`, not `http.Transport`.

got it, many thanks for replying.

Fix patch:
diff --git a/go.mod b/go.mod
index 87fdc60e09f..4c668ca2853 100644
--- a/go.mod
+++ b/go.mod
@@ -30,7 +30,7 @@ require (
 	go.opentelemetry.io/otel/sdk v1.4.0
 	go.uber.org/zap v1.21.0
 	golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29
-	golang.org/x/net v0.0.0-20220630215102-69896b714898
+	golang.org/x/net v0.0.0-20220812165438-1d4ff48094d1
 	golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
 	google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21
 	gopkg.in/natefinch/lumberjack.v2 v2.0.0
@@ -111,7 +111,6 @@ require (
 	go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 // indirect
 	go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.4.0 // indirect
 	go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.4.0 // indirect
-	go.opentelemetry.io/otel/internal/metric v0.27.0 // indirect
 	go.opentelemetry.io/otel/metric v0.31.0 // indirect
 	go.opentelemetry.io/otel/trace v1.9.0 // indirect
 	go.opentelemetry.io/proto/otlp v0.12.0 // indirect
@@ -121,7 +120,7 @@ require (
 	go.uber.org/atomic v1.9.0 // indirect
 	go.uber.org/multierr v1.6.0 // indirect
 	golang.org/x/mod v0.4.2 // indirect
-	golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e
+	golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10
 	golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b // indirect
 	golang.org/x/tools v0.1.7 // indirect
 	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
diff --git a/go.sum b/go.sum
index 4ab23b566b0..0c22859c066 100644
--- a/go.sum
+++ b/go.sum
@@ -234,8 +234,6 @@ github.com/evanphx/json-patch/v5 v5.5.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2Vvl
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
 github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
-github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o=
-github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
 github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
 github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
 github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
@@ -272,7 +270,6 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4=
 github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
-github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs=
 github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
 github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
 github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -352,8 +349,8 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
 github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
 github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -841,8 +838,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
 github.com/tailscale/tscert v0.0.0-20220316030059-54bbcb9f74e2 h1:xwMw7LFhV9dbvot9A7NLClP9udqbjrQlIwWMH8e7uiQ=
 github.com/tailscale/tscert v0.0.0-20220316030059-54bbcb9f74e2/go.mod h1:hL4gB6APAasMR2NNi/JHzqKkxW3EPQlFgLEq9PMi2t0=
 github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
@@ -894,11 +891,8 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
 go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
 go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.29.0 h1:SLme4Porm+UwX0DdHMxlwRt7FzPSE0sys81bet2o0pU=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.29.0/go.mod h1:tLYsuf2v8fZreBVwp9gVMhefZlLFZaUiNVSq8QxXRII=
 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.34.0 h1:9NkMW03wwEzPtP/KciZ4Ozu/Uz5ZA7kfqXJIObnrjGU=
 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.34.0/go.mod h1:548ZsYzmT4PL4zWKRd8q/N4z0Wxzn/ZxUE+lkEpwWQA=
-go.opentelemetry.io/otel v1.4.0 h1:7ESuKPq6zpjRaY5nvVDGiuwK7VAJ8MwkKnmNJ9whNZ4=
 go.opentelemetry.io/otel v1.4.0/go.mod h1:jeAqMFKy2uLIxCtKxoFj0FAL5zAPKQagc3+GtBWakzk=
 go.opentelemetry.io/otel v1.9.0 h1:8WZNQFIB2a71LnANS9JeyidJKKGOOremcUtb/OtHISw=
 go.opentelemetry.io/otel v1.9.0/go.mod h1:np4EoPGzoPs3O67xUVNoPPcmSvsfOxNlNA4F4AC+0Eo=
@@ -908,15 +902,10 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.4.0 h1:lRpP10E8oTGVmY1nVXcw
 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.4.0/go.mod h1:3oS+j2WUoJVyj6/BzQN/52G17lNJDulngsOxDm1w2PY=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.4.0 h1:buSx4AMC/0Z232slPhicN/fU5KIlj0bMngct5pcZhkI=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.4.0/go.mod h1:ew1NcwkHo0QFT3uTm3m2IVZMkZdVIpbOYNPasgWwpdk=
-go.opentelemetry.io/otel/internal/metric v0.27.0 h1:9dAVGAfFiiEq5NVB9FUJ5et+btbDQAUIJehJ+ikyryk=
-go.opentelemetry.io/otel/internal/metric v0.27.0/go.mod h1:n1CVxRqKqYZtqyTh9U/onvKapPGv7y/rpyOTI+LFNzw=
-go.opentelemetry.io/otel/metric v0.27.0 h1:HhJPsGhJoKRSegPQILFbODU56NS/L1UE4fS1sC5kIwQ=
-go.opentelemetry.io/otel/metric v0.27.0/go.mod h1:raXDJ7uP2/Jc0nVZWQjJtzoyssOYWu/+pjZqRzfvZ7g=
 go.opentelemetry.io/otel/metric v0.31.0 h1:6SiklT+gfWAwWUR0meEMxQBtihpiEs4c+vL9spDTqUs=
 go.opentelemetry.io/otel/metric v0.31.0/go.mod h1:ohmwj9KTSIeBnDBm/ZwH2PSZxZzoOaG2xZeekTRzL5A=
 go.opentelemetry.io/otel/sdk v1.4.0 h1:LJE4SW3jd4lQTESnlpQZcBhQ3oci0U2MLR5uhicfTHQ=
 go.opentelemetry.io/otel/sdk v1.4.0/go.mod h1:71GJPNJh4Qju6zJuYl1CrYtXbrgfau/M9UAggqiy1UE=
-go.opentelemetry.io/otel/trace v1.4.0 h1:4OOUrPZdVFQkbzl/JSdvGCWIdw5ONXXxzHlaLlWppmo=
 go.opentelemetry.io/otel/trace v1.4.0/go.mod h1:uc3eRsqDfWs9R7b92xbQbU42/eTNz4N+gLP8qJCi4aE=
 go.opentelemetry.io/otel/trace v1.9.0 h1:oZaCNJUjWcg60VXWee8lJKlqhPbXAPB51URuR47pQYc=
 go.opentelemetry.io/otel/trace v1.9.0/go.mod h1:2737Q0MuG8q1uILYm2YYVkAyLtOofiTNGg6VODnOiPo=
@@ -1077,8 +1066,9 @@ golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qx
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220403103023-749bd193bc2b/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.0.0-20220630215102-69896b714898 h1:K7wO6V1IrczY9QOQ2WkVpw4JQSwCd52UsxVEirZUfiw=
 golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.0.0-20220812165438-1d4ff48094d1 h1:mx1QvUwXKGgh+3SB51PH4G1TouzL84rLG0CtpdX+TTg=
+golang.org/x/net v0.0.0-20220812165438-1d4ff48094d1/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -1208,8 +1198,9 @@ golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e h1:CsOuNlbOuf0mzxJIefr6Q4uAUetRUwZE4qt7VfzP+xo=
 golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
+golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
diff --git a/modules/caddyhttp/reverseproxy/httptransport.go b/modules/caddyhttp/reverseproxy/httptransport.go
index ef72b8869bb..e9c7ddd0aae 100644
--- a/modules/caddyhttp/reverseproxy/httptransport.go
+++ b/modules/caddyhttp/reverseproxy/httptransport.go
@@ -128,28 +128,6 @@ func (h *HTTPTransport) Provision(ctx caddy.Context) error {
 	}
 	h.Transport = rt
 
-	// if h2c is enabled, configure its transport (std lib http.Transport
-	// does not "HTTP/2 over cleartext TCP")
-	if sliceContains(h.Versions, "h2c") {
-		// crafting our own http2.Transport doesn't allow us to utilize
-		// most of the customizations/preferences on the http.Transport,
-		// because, for some reason, only http2.ConfigureTransport()
-		// is allowed to set the unexported field that refers to a base
-		// http.Transport config; oh well
-		h2t := &http2.Transport{
-			// kind of a hack, but for plaintext/H2C requests, pretend to dial TLS
-			DialTLS: func(network, addr string, _ *tls.Config) (net.Conn, error) {
-				// TODO: no context, thus potentially wrong dial info
-				return net.Dial(network, addr)
-			},
-			AllowHTTP: true,
-		}
-		if h.Compression != nil {
-			h2t.DisableCompression = !*h.Compression
-		}
-		h.h2cTransport = h2t
-	}
-
 	return nil
 }
 
@@ -194,35 +172,38 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e
 		}
 	}
 
-	rt := &http.Transport{
-		DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
-			// the proper dialing information should be embedded into the request's context
-			if dialInfo, ok := GetDialInfo(ctx); ok {
-				network = dialInfo.Network
-				address = dialInfo.Address
-			}
+	// Set up the dialer to pull the correct information from the context
+	dialContext := func(ctx context.Context, network, address string) (net.Conn, error) {
+		// the proper dialing information should be embedded into the request's context
+		if dialInfo, ok := GetDialInfo(ctx); ok {
+			network = dialInfo.Network
+			address = dialInfo.Address
+		}
 
-			conn, err := dialer.DialContext(ctx, network, address)
-			if err != nil {
-				// identify this error as one that occurred during
-				// dialing, which can be important when trying to
-				// decide whether to retry a request
-				return nil, DialError{err}
-			}
+		conn, err := dialer.DialContext(ctx, network, address)
+		if err != nil {
+			// identify this error as one that occurred during
+			// dialing, which can be important when trying to
+			// decide whether to retry a request
+			return nil, DialError{err}
+		}
 
-			// if read/write timeouts are configured and this is a TCP connection, enforce the timeouts
-			// by wrapping the connection with our own type
-			if tcpConn, ok := conn.(*net.TCPConn); ok && (h.ReadTimeout > 0 || h.WriteTimeout > 0) {
-				conn = &tcpRWTimeoutConn{
-					TCPConn:      tcpConn,
-					readTimeout:  time.Duration(h.ReadTimeout),
-					writeTimeout: time.Duration(h.WriteTimeout),
-					logger:       caddyCtx.Logger(h),
-				}
+		// if read/write timeouts are configured and this is a TCP connection, enforce the timeouts
+		// by wrapping the connection with our own type
+		if tcpConn, ok := conn.(*net.TCPConn); ok && (h.ReadTimeout > 0 || h.WriteTimeout > 0) {
+			conn = &tcpRWTimeoutConn{
+				TCPConn:      tcpConn,
+				readTimeout:  time.Duration(h.ReadTimeout),
+				writeTimeout: time.Duration(h.WriteTimeout),
+				logger:       caddyCtx.Logger(h),
 			}
+		}
 
-			return conn, nil
-		},
+		return conn, nil
+	}
+
+	rt := &http.Transport{
+		DialContext:            dialContext,
 		MaxConnsPerHost:        h.MaxConnsPerHost,
 		ResponseHeaderTimeout:  time.Duration(h.ResponseHeaderTimeout),
 		ExpectContinueTimeout:  time.Duration(h.ExpectContinueTimeout),
@@ -260,6 +241,27 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e
 		}
 	}
 
+	// if h2c is enabled, configure its transport (std lib http.Transport
+	// does not "HTTP/2 over cleartext TCP")
+	if sliceContains(h.Versions, "h2c") {
+		// crafting our own http2.Transport doesn't allow us to utilize
+		// most of the customizations/preferences on the http.Transport,
+		// because, for some reason, only http2.ConfigureTransport()
+		// is allowed to set the unexported field that refers to a base
+		// http.Transport config; oh well
+		h2t := &http2.Transport{
+			// kind of a hack, but for plaintext/H2C requests, pretend to dial TLS
+			DialTLSContext: func(ctx context.Context, network, address string, _ *tls.Config) (net.Conn, error) {
+				return dialContext(ctx, network, address)
+			},
+			AllowHTTP: true,
+		}
+		if h.Compression != nil {
+			h2t.DisableCompression = !*h.Compression
+		}
+		h.h2cTransport = h2t
+	}
+
 	return rt, nil
 }
 


Test patch: