plugin/forward: Add http(2) host/authority header and TO server resolution (#8233)

This commit is contained in:
llucas
2026-07-22 19:07:09 -07:00
committed by GitHub
parent 56f7c09311
commit 73d1eacf87
9 changed files with 83 additions and 32 deletions

View File

@@ -16,8 +16,8 @@ import (
// hostEntry represents a hostname-based TO address that needs DNS resolution.
type hostEntry struct {
hostname string // the hostname to resolve (e.g., "rbldnsd.rbldnsd.svc.cluster.local")
port string // port (e.g., "53", "853")
transport string // "dns" or "tls"
port string // port (e.g., "53", "443", "853")
transport string // "dns", "tls", or "https"
zone string // TLS server name zone (from %zone syntax)
}
@@ -67,15 +67,18 @@ func parseAsHostEntry(h string) (hostEntry, bool) {
cleanH, zone := splitZone(h)
trans, host := parse.Transport(cleanH)
// Only dns and tls transports are supported for hostname resolution
if trans != transport.DNS && trans != transport.TLS {
// Only dns, tls, and https transports are supported for hostname resolution
if trans != transport.DNS && trans != transport.TLS && trans != transport.HTTPS {
return hostEntry{}, false
}
hostname := host
port := transport.Port
if trans == transport.TLS {
switch trans {
case transport.TLS:
port = transport.TLSPort
case transport.HTTPS:
port = transport.HTTPSPort
}
// Check if there's a port
@@ -161,14 +164,14 @@ func formatResolvedAddr(ip, port, trans, zone string) string {
isIPv6 := strings.Contains(ip, ":")
switch trans {
case transport.TLS:
case transport.TLS, transport.HTTPS:
if zone != "" {
if isIPv6 {
return transport.TLS + "://[" + ip + "%" + zone + "]:" + port
return trans + "://[" + ip + "%" + zone + "]:" + port
}
return transport.TLS + "://" + ip + "%" + zone + ":" + port
return trans + "://" + ip + "%" + zone + ":" + port
}
return transport.TLS + "://" + net.JoinHostPort(ip, port)
return trans + "://" + net.JoinHostPort(ip, port)
default: // transport.DNS
return net.JoinHostPort(ip, port)
}

View File

@@ -79,6 +79,11 @@ func TestClassifyToAddrs(t *testing.T) {
input: []string{"tls://dns.example.com"},
wantDynamic: 1,
},
{
name: "HTTPS hostname",
input: []string{"https://dns.example.com"},
wantDynamic: 1,
},
{
name: "k8s service name",
input: []string{"rbldnsd.rbldnsd.svc.cluster.local"},
@@ -166,12 +171,15 @@ func TestParseAsHostEntry(t *testing.T) {
{"tls://dns.example.com", true, "dns.example.com", "853", transport.TLS, ""},
{"tls://dns.example.com:8853", true, "dns.example.com", "8853", transport.TLS, ""},
{"tls://dns.example.com%servername.example.com", true, "dns.example.com", "853", transport.TLS, "servername.example.com"},
{"https://dns.example.com", true, "dns.example.com", "443", transport.HTTPS, ""},
{"https://dns.example.com:8443", true, "dns.example.com", "8443", transport.HTTPS, ""},
{"https://dns.example.com%servername.example.com", true, "dns.example.com", "443", transport.HTTPS, "servername.example.com"},
{"rbldnsd.rbldnsd.svc.cluster.local", true, "rbldnsd.rbldnsd.svc.cluster.local", "53", transport.DNS, ""},
// Should fail for IPs
{"127.0.0.1", false, "", "", "", ""},
{"::1", false, "", "", "", ""},
// Should fail for unsupported transports
{"https://example.com", false, "", "", "", ""},
{"grpc://example.com", false, "", "", "", ""},
// Should fail for empty
{"", false, "", "", "", ""},
}
@@ -209,9 +217,13 @@ func TestFormatResolvedAddr(t *testing.T) {
{"10.0.0.1", "53", transport.DNS, "", "10.0.0.1:53"},
{"10.0.0.1", "853", transport.TLS, "", "tls://10.0.0.1:853"},
{"10.0.0.1", "853", transport.TLS, "example.com", "tls://10.0.0.1%example.com:853"},
{"10.0.0.1", "443", transport.HTTPS, "", "https://10.0.0.1:443"},
{"10.0.0.1", "443", transport.HTTPS, "example.com", "https://10.0.0.1%example.com:443"},
{"::1", "53", transport.DNS, "", "[::1]:53"},
{"::1", "853", transport.TLS, "", "tls://[::1]:853"},
{"::1", "853", transport.TLS, "example.com", "tls://[::1%example.com]:853"},
{"::1", "443", transport.HTTPS, "", "https://[::1]:443"},
{"::1", "443", transport.HTTPS, "example.com", "https://[::1%example.com]:443"},
}
for _, tc := range tests {
@@ -576,6 +588,10 @@ func TestExpandAndDedupTLS(t *testing.T) {
{static: false, entry: hostEntry{hostname: "dns2.example.com", port: "853", transport: "tls"}},
{static: true, addrs: []string{"tls://149.112.112.112:853"}},
{static: true, addrs: []string{"tls://9.9.9.10:853"}},
{static: false, entry: hostEntry{hostname: "dns1.example.com", port: "443", transport: "https"}},
{static: false, entry: hostEntry{hostname: "dns2.example.com", port: "443", transport: "https"}},
{static: true, addrs: []string{"https://149.112.112.112:443"}},
{static: true, addrs: []string{"https://9.9.9.10:443"}},
}
result, err := expandAndDedup(entries, []string{s.Addr})
@@ -583,7 +599,7 @@ func TestExpandAndDedupTLS(t *testing.T) {
t.Fatalf("unexpected error: %v", err)
}
expected := []string{"9.9.9.9:853", "149.112.112.112:853", "9.9.9.10:853"}
expected := []string{"9.9.9.9:853", "149.112.112.112:853", "9.9.9.10:853", "9.9.9.9:443", "149.112.112.112:443", "9.9.9.10:443"}
if len(result) != len(expected) {
t.Fatalf("expected %d addresses after dedup, got %d: %v", len(expected), len(result), result)
}

View File

@@ -224,20 +224,8 @@ func parseStanza(c *caddy.Controller) (*Forward, error) {
f.tlsConfig.ClientSessionCache = tls.NewLRUClientSessionCache(len(f.proxies))
for i := range f.proxies {
// Only set this for proxies that need it.
if transports[i] == transport.TLS {
if tlsConfig, ok := perServerNameTlsConfig[tlsServerNames[i]]; ok {
f.proxies[i].SetTLSConfig(tlsConfig)
} else {
f.proxies[i].SetTLSConfig(f.tlsConfig)
}
}
if transports[i] == transport.HTTPS {
httpTransport := http.DefaultTransport.(*http.Transport).Clone()
httpTransport.TLSClientConfig = f.tlsConfig
httpTransport.MaxIdleConns = f.maxIdleConns
httpTransport.MaxIdleConnsPerHost = f.maxIdleConns
c := http.Client{
Transport: httpTransport,
@@ -248,6 +236,15 @@ func parseStanza(c *caddy.Controller) (*Forward, error) {
f.proxies[i].SetDOHRequestOptions(f.dohMethod)
}
// Only set this for proxies that need it.
if transports[i] == transport.TLS {
if tlsConfig, ok := perServerNameTlsConfig[tlsServerNames[i]]; ok {
f.proxies[i].SetTLSConfig(tlsConfig)
} else {
f.proxies[i].SetTLSConfig(f.tlsConfig)
}
}
f.proxies[i].SetExpire(f.expire)
f.proxies[i].SetMaxAge(f.maxAge)
f.proxies[i].SetMaxIdleConns(f.maxIdleConns)

View File

@@ -143,14 +143,22 @@ func TestSplitZone(t *testing.T) {
"tls://127.0.0.1%example.net:854", "tls://127.0.0.1:854", "example.net",
}, {
"tls://127.0.0.1%example.net", "tls://127.0.0.1", "example.net",
}, {
"https://127.0.0.1%example.net:443", "https://127.0.0.1:443", "example.net",
}, {
"https://127.0.0.1%example.net", "https://127.0.0.1", "example.net",
}, {
"tls://127.0.0.1:854", "tls://127.0.0.1:854", "",
}, {
"https://127.0.0.1:443", "https://127.0.0.1:443", "",
}, {
"dns://127.0.0.1", "dns://127.0.0.1", "",
}, {
"foo%bar:baz", "foo:baz", "bar",
}, {
"tls://[::1%example.net]:853", "tls://[::1]:853", "example.net",
}, {
"https://[::1%example.net]:443", "https://[::1]:443", "example.net",
},
}
for i, test := range tests {