From 180eae60f2bdbd282c3e4b28ba4e0ec80ab32529 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Tue, 14 Nov 2023 00:28:15 +0700 Subject: [PATCH] all: allowing config defined discover ptr endpoints The default gateway is usually the DNS server in normal home network setup for most users. However, there's case that it is not, causing discover ptr failed. This commit add discover_ptr_endpoints config parameter, so users can define what DNS nameservers will be used. --- config.go | 33 +++++++++++++++--------------- docs/config.md | 16 +++++++++++++++ internal/clientinfo/client_info.go | 21 +++++++++++++++++++ resolver.go | 19 ++++++++++++----- 4 files changed, 68 insertions(+), 21 deletions(-) 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 {