Files
ctrld/internal/clientinfo/ptr_lookup.go
T
Cuong Manh Le 4614b98e94 internal/clientinfo: emit error once if ptr discovery failed
So it won't spam ctrld log unnecessary, prevent confusion. While at it,
also change the log level from Warn to Info, since this error is not
actionable by the user.
2023-11-09 00:30:56 +07:00

118 lines
2.6 KiB
Go

package clientinfo
import (
"context"
"sync"
"sync/atomic"
"time"
"github.com/miekg/dns"
"tailscale.com/logtail/backoff"
"github.com/Control-D-Inc/ctrld"
)
type ptrDiscover struct {
hostname sync.Map // ip => hostname
resolver ctrld.Resolver
serverDown atomic.Bool
}
func (p *ptrDiscover) refresh() error {
p.hostname.Range(func(key, value any) bool {
ip := key.(string)
if name := p.lookupHostname(ip); name != "" {
p.hostname.Store(ip, name)
}
return true
})
return nil
}
func (p *ptrDiscover) LookupHostnameByIP(ip string) string {
if val, ok := p.hostname.Load(ip); ok {
return val.(string)
}
return p.lookupHostname(ip)
}
func (p *ptrDiscover) LookupHostnameByMac(mac string) string {
return ""
}
func (p *ptrDiscover) String() string {
return "ptr"
}
func (p *ptrDiscover) List() []string {
if p == nil {
return nil
}
var ips []string
p.hostname.Range(func(key, value any) bool {
ips = append(ips, key.(string))
return true
})
return ips
}
func (p *ptrDiscover) lookupHostnameFromCache(ip string) string {
if val, ok := p.hostname.Load(ip); ok {
return val.(string)
}
return ""
}
func (p *ptrDiscover) lookupHostname(ip string) string {
// If nameserver is down, do nothing.
if p.serverDown.Load() {
return ""
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
msg := new(dns.Msg)
addr, err := dns.ReverseAddr(ip)
if err != nil {
ctrld.ProxyLogger.Load().Info().Str("discovery", "ptr").Err(err).Msg("invalid ip address")
return ""
}
msg.SetQuestion(addr, dns.TypePTR)
ans, err := p.resolver.Resolve(ctx, msg)
if err != nil {
if p.serverDown.CompareAndSwap(false, true) {
ctrld.ProxyLogger.Load().Info().Str("discovery", "ptr").Err(err).Msg("could not perform PTR lookup")
go p.checkServer()
}
return ""
}
for _, rr := range ans.Answer {
if ptr, ok := rr.(*dns.PTR); ok {
hostname := normalizeHostname(ptr.Ptr)
p.hostname.Store(ip, hostname)
return hostname
}
}
return ""
}
// checkServer monitors if the resolver can reach its nameserver. When the nameserver
// is reachable, set p.serverDown to false, so p.lookupHostname can continue working.
func (p *ptrDiscover) checkServer() {
bo := backoff.NewBackoff("ptrDiscover", func(format string, args ...any) {}, time.Minute*5)
m := new(dns.Msg)
m.SetQuestion(".", dns.TypeNS)
ping := func() error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
_, err := p.resolver.Resolve(ctx, m)
return err
}
for {
if err := ping(); err != nil {
bo.BackOff(context.Background(), err)
continue
}
break
}
p.serverDown.Store(false)
}