diff --git a/config.go b/config.go index 489a7fd..97a837e 100644 --- a/config.go +++ b/config.go @@ -167,22 +167,23 @@ func (c *Config) FirstUpstream() *UpstreamConfig { // ServiceConfig specifies the general ctrld config. type ServiceConfig struct { - LogLevel string `mapstructure:"log_level" toml:"log_level,omitempty"` - LogPath string `mapstructure:"log_path" toml:"log_path,omitempty"` - CacheEnable bool `mapstructure:"cache_enable" toml:"cache_enable,omitempty"` - CacheSize int `mapstructure:"cache_size" toml:"cache_size,omitempty"` - CacheTTLOverride int `mapstructure:"cache_ttl_override" toml:"cache_ttl_override,omitempty"` - CacheServeStale bool `mapstructure:"cache_serve_stale" toml:"cache_serve_stale,omitempty"` - MaxConcurrentRequests *int `mapstructure:"max_concurrent_requests" toml:"max_concurrent_requests,omitempty" validate:"omitempty,gte=0"` - DHCPLeaseFile string `mapstructure:"dhcp_lease_file_path" toml:"dhcp_lease_file_path" validate:"omitempty,file"` - DHCPLeaseFileFormat string `mapstructure:"dhcp_lease_file_format" toml:"dhcp_lease_file_format" validate:"required_unless=DHCPLeaseFile '',omitempty,oneof=dnsmasq isc-dhcp"` - DiscoverMDNS *bool `mapstructure:"discover_mdns" toml:"discover_mdns,omitempty"` - DiscoverARP *bool `mapstructure:"discover_arp" toml:"discover_dhcp,omitempty"` - DiscoverDHCP *bool `mapstructure:"discover_dhcp" toml:"discover_dhcp,omitempty"` - DiscoverPtr *bool `mapstructure:"discover_ptr" toml:"discover_ptr,omitempty"` - DiscoverHosts *bool `mapstructure:"discover_hosts" toml:"discover_hosts,omitempty"` - Daemon bool `mapstructure:"-" toml:"-"` - AllocateIP bool `mapstructure:"-" toml:"-"` + LogLevel string `mapstructure:"log_level" toml:"log_level,omitempty"` + LogPath string `mapstructure:"log_path" toml:"log_path,omitempty"` + CacheEnable bool `mapstructure:"cache_enable" toml:"cache_enable,omitempty"` + CacheSize int `mapstructure:"cache_size" toml:"cache_size,omitempty"` + CacheTTLOverride int `mapstructure:"cache_ttl_override" toml:"cache_ttl_override,omitempty"` + CacheServeStale bool `mapstructure:"cache_serve_stale" toml:"cache_serve_stale,omitempty"` + MaxConcurrentRequests *int `mapstructure:"max_concurrent_requests" toml:"max_concurrent_requests,omitempty" validate:"omitempty,gte=0"` + DHCPLeaseFile string `mapstructure:"dhcp_lease_file_path" toml:"dhcp_lease_file_path" validate:"omitempty,file"` + DHCPLeaseFileFormat string `mapstructure:"dhcp_lease_file_format" toml:"dhcp_lease_file_format" validate:"required_unless=DHCPLeaseFile '',omitempty,oneof=dnsmasq isc-dhcp"` + DiscoverMDNS *bool `mapstructure:"discover_mdns" toml:"discover_mdns,omitempty"` + DiscoverARP *bool `mapstructure:"discover_arp" toml:"discover_dhcp,omitempty"` + DiscoverDHCP *bool `mapstructure:"discover_dhcp" toml:"discover_dhcp,omitempty"` + DiscoverPtr *bool `mapstructure:"discover_ptr" toml:"discover_ptr,omitempty"` + DiscoverPtrEndpoints []string `mapstructure:"discover_ptr_endpoints" toml:"discover_ptr_endpoints,omitempty"` + DiscoverHosts *bool `mapstructure:"discover_hosts" toml:"discover_hosts,omitempty"` + Daemon bool `mapstructure:"-" toml:"-"` + AllocateIP bool `mapstructure:"-" toml:"-"` } // NetworkConfig specifies configuration for networks where ctrld will handle requests. diff --git a/docs/config.md b/docs/config.md index 29dff8d..57a794b 100644 --- a/docs/config.md +++ b/docs/config.md @@ -193,6 +193,22 @@ Perform LAN client discovery using PTR queries. - Required: no - Default: true +### discover_ptr_endpoints +List of DNS nameservers used for PTR discovery. + +Each entry can be either "ip" (default port 53) or "ip:port" pair. Invalid entry will be ignored. + +- Type: array of string +- Required: no +- Default: [] + +Example: + +```toml +[service] +discover_ptr_endpoints = ["192.168.1.1", "192.168.2.1:5354"] +``` + ### discover_hosts Perform LAN client discovery using hosts file. diff --git a/internal/clientinfo/client_info.go b/internal/clientinfo/client_info.go index f591174..ee1a14f 100644 --- a/internal/clientinfo/client_info.go +++ b/internal/clientinfo/client_info.go @@ -3,7 +3,9 @@ package clientinfo import ( "context" "fmt" + "net" "net/netip" + "strconv" "strings" "sync" "time" @@ -183,6 +185,25 @@ func (t *Table) init() { // PTR lookup. if t.discoverPTR() { t.ptr = &ptrDiscover{resolver: ctrld.NewPrivateResolver()} + if len(t.svcCfg.DiscoverPtrEndpoints) > 0 { + nss := make([]string, 0, len(t.svcCfg.DiscoverPtrEndpoints)) + for _, ns := range t.svcCfg.DiscoverPtrEndpoints { + host, port := ns, "53" + if h, p, err := net.SplitHostPort(ns); err == nil { + host, port = h, p + } + // Only use valid ip:port pair. + if _, portErr := strconv.Atoi(port); portErr == nil && port != "0" && net.ParseIP(host) != nil { + nss = append(nss, net.JoinHostPort(host, port)) + } else { + ctrld.ProxyLogger.Load().Warn().Msgf("ignoring invalid nameserver for ptr discover: %q", ns) + } + } + if len(nss) > 0 { + t.ptr.resolver = ctrld.NewResolverWithNameserver(nss) + ctrld.ProxyLogger.Load().Debug().Msgf("using nameservers %v for ptr discovery", nss) + } + } ctrld.ProxyLogger.Load().Debug().Msg("start ptr discovery") if err := t.ptr.refresh(); err != nil { ctrld.ProxyLogger.Load().Error().Err(err).Msg("could not init PTR discover") diff --git a/resolver.go b/resolver.go index f08263b..8fd26cf 100644 --- a/resolver.go +++ b/resolver.go @@ -78,8 +78,9 @@ type osResolverResult struct { err error } -// Resolve performs DNS resolvers using OS default nameservers. Nameserver is chosen from -// available nameservers with a roundrobin algorithm. +// Resolve resolves DNS queries using pre-configured nameservers. +// Query is sent to all nameservers concurrently, and the first +// success response will be returned. func (o *osResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, error) { numServers := len(o.nameservers) if numServers == 0 { @@ -269,11 +270,19 @@ func NewPrivateResolver() Resolver { } } nss = nss[:n] - if len(nss) == 0 { + return NewResolverWithNameserver(nss) +} + +// NewResolverWithNameserver returns an OS resolver which uses the given nameservers +// for resolving DNS queries. If nameservers is empty, a dummy resolver will be returned. +// +// Each nameserver must be form "host:port". It's the caller responsibility to ensure all +// nameservers are well formatted by using net.JoinHostPort function. +func NewResolverWithNameserver(nameservers []string) Resolver { + if len(nameservers) == 0 { return &dummyResolver{} } - resolver := &osResolver{nameservers: nss} - return resolver + return &osResolver{nameservers: nameservers} } func newDialer(dnsAddress string) *net.Dialer {