mirror of
https://github.com/caddyserver/caddy.git
synced 2026-08-04 06:53:36 +00:00
reverseproxy: replace placeholders specified for sni while using http3 (#7737)
Some checks failed
Tests / test (./cmd/caddy/caddy, ~1.26.0, macos-14, 0, 1.26, mac) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy, ~1.26.0, ubuntu-latest, 0, 1.26, linux) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy.exe, ~1.26.0, windows-latest, True, 1.26, windows) (push) Has been cancelled
Tests / test (s390x on IBM Z) (push) Has been cancelled
Tests / goreleaser-check (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, aix) (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, darwin) (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, dragonfly) (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, freebsd) (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, illumos) (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, linux) (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, netbsd) (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, openbsd) (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, solaris) (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, windows) (push) Has been cancelled
Lint / lint (push) Has been cancelled
Lint / lint-1 (push) Has been cancelled
Lint / lint-2 (push) Has been cancelled
Lint / govulncheck (push) Has been cancelled
Lint / dependency-review (push) Has been cancelled
OpenSSF Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Some checks failed
Tests / test (./cmd/caddy/caddy, ~1.26.0, macos-14, 0, 1.26, mac) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy, ~1.26.0, ubuntu-latest, 0, 1.26, linux) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy.exe, ~1.26.0, windows-latest, True, 1.26, windows) (push) Has been cancelled
Tests / test (s390x on IBM Z) (push) Has been cancelled
Tests / goreleaser-check (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, aix) (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, darwin) (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, dragonfly) (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, freebsd) (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, illumos) (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, linux) (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, netbsd) (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, openbsd) (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, solaris) (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, windows) (push) Has been cancelled
Lint / lint (push) Has been cancelled
Lint / lint-1 (push) Has been cancelled
Lint / lint-2 (push) Has been cancelled
Lint / govulncheck (push) Has been cancelled
Lint / dependency-review (push) Has been cancelled
OpenSSF Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
* reverseproxy: replace placeholders specified for sni while using http3 * add test for placeholder * reverseproxy: replace placeholders specified for sni while using http3 * add test for placeholder * reverseproxy: test HTTP/3 SNI host placeholder --------- Co-authored-by: Zen Dodd <mail@steadytao.com>
This commit is contained in:
parent
e2eee6a7fc
commit
fcc7860d03
2 changed files with 135 additions and 1 deletions
|
|
@ -793,3 +793,41 @@ func TestReverseProxyRetryMatchIsTransportError(t *testing.T) {
|
|||
// Transport error on broken upstream should be retried to good upstream
|
||||
tester.AssertGetResponse("http://localhost:9080/", 200, "ok")
|
||||
}
|
||||
|
||||
func TestReverseProxySNIPlaceHolder(t *testing.T) {
|
||||
configTemplate := `
|
||||
{
|
||||
skip_install_trust
|
||||
local_certs
|
||||
admin localhost:2999
|
||||
http_port 9080
|
||||
https_port 9443
|
||||
grace_period 1ns
|
||||
}
|
||||
localhost example.com {
|
||||
@proxied header X-Transport caddy
|
||||
respond @proxied {http.request.tls.server_name}
|
||||
reverse_proxy 127.0.0.1:9443 {
|
||||
header_up X-Transport caddy
|
||||
header_up Host {host}
|
||||
transport http {
|
||||
versions %s
|
||||
tls_server_name {header.X-SNI}
|
||||
tls_insecure_skip_verify
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
for _, versions := range []string{"1.1 2", "3"} {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(fmt.Sprintf(configTemplate, versions), "caddyfile")
|
||||
req, err := http.NewRequest("GET", "https://localhost:9443", nil)
|
||||
if err != nil {
|
||||
t.Errorf("failed to create request %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
req.Header.Set("X-SNI", "example.com")
|
||||
tester.AssertResponse(req, 200, "example.com")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/pires/go-proxyproto"
|
||||
"github.com/quic-go/quic-go"
|
||||
"github.com/quic-go/quic-go/http3"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
|
|
@ -161,7 +162,8 @@ type HTTPTransport struct {
|
|||
// `HTTPS_PROXY`, and `NO_PROXY` environment variables.
|
||||
NetworkProxyRaw json.RawMessage `json:"network_proxy,omitempty" caddy:"namespace=caddy.network_proxy inline_key=from"`
|
||||
|
||||
h3Transport *http3.Transport // TODO: EXPERIMENTAL (May 2024)
|
||||
h3Transport *http3.Transport // TODO: EXPERIMENTAL (May 2024)
|
||||
quicTransport *quic.Transport // used by h3Transport if sni placeholder is used, otherwise nil
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
|
|
@ -499,6 +501,25 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("making TLS client config for HTTP/3 transport: %v", err)
|
||||
}
|
||||
|
||||
if strings.Contains(h.TLS.ServerName, "{") {
|
||||
// copied from quic-go
|
||||
udpConn, err := net.ListenUDP("udp", nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("making udp socket for HTTP/3 transport: %v", err)
|
||||
}
|
||||
h.quicTransport = &quic.Transport{Conn: udpConn}
|
||||
h.h3Transport.Dial = func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error) {
|
||||
// tlsCfg is already cloned from h3Transport.TLSClientConfig
|
||||
repl := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||
tlsCfg.ServerName = repl.ReplaceAll(tlsCfg.ServerName, "")
|
||||
udpAddr, err := resolveUDPAddr(ctx, "udp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return h.quicTransport.DialEarly(ctx, udpAddr, tlsCfg, cfg)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if len(h.Versions) > 1 && slices.Contains(h.Versions, "3") {
|
||||
return nil, fmt.Errorf("if HTTP/3 is enabled to the upstream, no other HTTP versions are supported")
|
||||
|
|
@ -525,6 +546,71 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e
|
|||
return rt, nil
|
||||
}
|
||||
|
||||
// TODO: EXPERIMENTAL (May 2025)
|
||||
// copied from quic-go
|
||||
func resolveUDPAddr(ctx context.Context, network, addr string) (*net.UDPAddr, error) {
|
||||
host, portStr, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
port, err := net.LookupPort(network, portStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resolver := net.DefaultResolver
|
||||
ipAddrs, err := resolver.LookupIPAddr(ctx, host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addrs := addrList(ipAddrs)
|
||||
ip := addrs.forResolve(network, addr)
|
||||
return &net.UDPAddr{IP: ip.IP, Port: port, Zone: ip.Zone}, nil
|
||||
}
|
||||
|
||||
// TODO: EXPERIMENTAL (May 2025)
|
||||
// copied from quic-go
|
||||
// An addrList represents a list of network endpoint addresses.
|
||||
// Copy from [net.addrList] and change type from [net.Addr] to [net.IPAddr]
|
||||
type addrList []net.IPAddr
|
||||
|
||||
// isIPv4 reports whether addr contains an IPv4 address.
|
||||
func isIPv4(addr net.IPAddr) bool {
|
||||
return addr.IP.To4() != nil
|
||||
}
|
||||
|
||||
// isNotIPv4 reports whether addr does not contain an IPv4 address.
|
||||
func isNotIPv4(addr net.IPAddr) bool { return !isIPv4(addr) }
|
||||
|
||||
// forResolve returns the most appropriate address in address for
|
||||
// a call to ResolveTCPAddr, ResolveUDPAddr, or ResolveIPAddr.
|
||||
// IPv4 is preferred, unless addr contains an IPv6 literal.
|
||||
func (addrs addrList) forResolve(network, addr string) net.IPAddr {
|
||||
var want6 bool
|
||||
switch network {
|
||||
case "ip":
|
||||
// IPv6 literal (addr does NOT contain a port)
|
||||
want6 = strings.ContainsRune(addr, ':')
|
||||
case "tcp", "udp":
|
||||
// IPv6 literal. (addr contains a port, so look for '[')
|
||||
want6 = strings.ContainsRune(addr, '[')
|
||||
}
|
||||
if want6 {
|
||||
return addrs.first(isNotIPv4)
|
||||
}
|
||||
return addrs.first(isIPv4)
|
||||
}
|
||||
|
||||
// first returns the first address which satisfies strategy, or if
|
||||
// none do, then the first address of any kind.
|
||||
func (addrs addrList) first(strategy func(net.IPAddr) bool) net.IPAddr {
|
||||
for _, addr := range addrs {
|
||||
if strategy(addr) {
|
||||
return addr
|
||||
}
|
||||
}
|
||||
return addrs[0]
|
||||
}
|
||||
|
||||
// RequestHeaderOps implements TransportHeaderOpsProvider. It returns header
|
||||
// operations for requests when the transport's configuration indicates they
|
||||
// should be applied. In particular, when TLS is enabled for this transport,
|
||||
|
|
@ -623,6 +709,16 @@ func (h HTTPTransport) Cleanup() error {
|
|||
return nil
|
||||
}
|
||||
h.Transport.CloseIdleConnections()
|
||||
// h3 related cleanup, errors are ignored as nothing can be done.
|
||||
// TODO: log these errors if any
|
||||
if h.h3Transport != nil {
|
||||
h.h3Transport.CloseIdleConnections()
|
||||
_ = h.h3Transport.Close()
|
||||
if h.quicTransport != nil {
|
||||
_ = h.quicTransport.Close()
|
||||
_ = h.quicTransport.Conn.Close()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue