plugin/forward: add hostname resolution support for TO endpoints (#5646) (#7923)

Signed-off-by: Dmytro Alieksieiev <1865999+dragoangel@users.noreply.github.com>
This commit is contained in:
Dmytro Alieksieiev
2026-05-31 04:36:01 +02:00
committed by GitHub
parent 33c71b1554
commit ce0e5a6f39
6 changed files with 920 additions and 9 deletions

View File

@@ -4,6 +4,7 @@ import (
"crypto/tls"
"errors"
"fmt"
"net"
"path/filepath"
"strconv"
"strings"
@@ -32,8 +33,8 @@ func setup(c *caddy.Controller) error {
}
for i := range fs {
f := fs[i]
if f.Len() > max {
return plugin.Error("forward", fmt.Errorf("more than %d TOs configured: %d", max, f.Len()))
if len(f.toEntries) > max {
return plugin.Error("forward", fmt.Errorf("more than %d TOs configured: %d", max, len(f.toEntries)))
}
if i == len(fs)-1 {
@@ -146,11 +147,7 @@ func parseStanza(c *caddy.Controller) (*Forward, error) {
return f, c.ArgErr()
}
toHosts, err := parse.HostPortOrFile(to...)
if err != nil {
return f, err
}
// Parse block first to get resolver and other options before processing TO addresses.
for c.NextBlock() {
if err := parseBlock(c, f); err != nil {
return f, err
@@ -161,6 +158,22 @@ func parseStanza(c *caddy.Controller) (*Forward, error) {
return f, fmt.Errorf("max_age (%s) must not be less than expire (%s)", f.maxAge, f.expire)
}
// Classify TO addresses in order, preserving config ordering.
entries, err := classifyToAddrs(to)
if err != nil {
return f, err
}
f.toEntries = entries
// Expand hostnames and deduplicate globally (first-seen order wins).
toHosts, err := expandAndDedup(f.toEntries, f.resolver)
if err != nil {
return f, err
}
if len(toHosts) == 0 {
return f, fmt.Errorf("no valid upstream addresses found")
}
tlsServerNames := make([]string, len(toHosts))
perServerNameProxyCount := make(map[string]int)
transports := make([]string, len(toHosts))
@@ -424,6 +437,21 @@ func parseBlock(c *caddy.Controller, f *Forward) error {
f.failoverRcodes = append(f.failoverRcodes, rc)
}
case "resolver":
args := c.RemainingArgs()
if len(args) == 0 {
return c.ArgErr()
}
for _, arg := range args {
host := arg
if h, _, err := net.SplitHostPort(arg); err == nil {
host = h
}
if net.ParseIP(host) == nil {
return fmt.Errorf("resolver must be an IP address or IP:port: %q", arg)
}
}
f.resolver = args
default:
return c.Errf("unknown property '%s'", c.Val())
}