|
|
|
@ -7,6 +7,7 @@ import ( |
|
|
|
"fmt" |
|
|
|
"net" |
|
|
|
"strings" |
|
|
|
"sync" |
|
|
|
"time" |
|
|
|
|
|
|
|
"github.com/go-log/log" |
|
|
|
@ -15,6 +16,8 @@ import ( |
|
|
|
var ( |
|
|
|
// DefaultResolverTimeout is the default timeout for name resolution.
|
|
|
|
DefaultResolverTimeout = 30 * time.Second |
|
|
|
// DefaultResolverTTL is the default cache TTL for name resolution.
|
|
|
|
DefaultResolverTTL = 60 * time.Second |
|
|
|
) |
|
|
|
|
|
|
|
// Resolver is a name resolver for domain name.
|
|
|
|
@ -32,7 +35,7 @@ type NameServer struct { |
|
|
|
Hostname string // for TLS handshake verification
|
|
|
|
} |
|
|
|
|
|
|
|
func (ns *NameServer) String() string { |
|
|
|
func (ns NameServer) String() string { |
|
|
|
addr := ns.Addr |
|
|
|
prot := ns.Protocol |
|
|
|
host := ns.Hostname |
|
|
|
@ -45,23 +48,39 @@ func (ns *NameServer) String() string { |
|
|
|
return fmt.Sprintf("%s/%s %s", addr, prot, host) |
|
|
|
} |
|
|
|
|
|
|
|
type resolverCacheItem struct { |
|
|
|
IPAddrs []net.IPAddr |
|
|
|
ts int64 |
|
|
|
} |
|
|
|
|
|
|
|
type resolver struct { |
|
|
|
Resolver *net.Resolver |
|
|
|
Servers []NameServer |
|
|
|
Timeout time.Duration |
|
|
|
TTL time.Duration |
|
|
|
mCache *sync.Map |
|
|
|
} |
|
|
|
|
|
|
|
// NewResolver create a new Resolver with the given name servers and resolution timeout.
|
|
|
|
func NewResolver(servers []NameServer, timeout time.Duration) Resolver { |
|
|
|
func NewResolver(servers []NameServer, timeout, ttl time.Duration) Resolver { |
|
|
|
r := &resolver{ |
|
|
|
Servers: servers, |
|
|
|
Timeout: timeout, |
|
|
|
TTL: ttl, |
|
|
|
mCache: &sync.Map{}, |
|
|
|
} |
|
|
|
r.init() |
|
|
|
return r |
|
|
|
} |
|
|
|
|
|
|
|
func (r *resolver) init() { |
|
|
|
if r.Timeout <= 0 { |
|
|
|
r.Timeout = DefaultResolverTimeout |
|
|
|
} |
|
|
|
if r.TTL == 0 { |
|
|
|
r.TTL = DefaultResolverTTL |
|
|
|
} |
|
|
|
|
|
|
|
r.Resolver = &net.Resolver{ |
|
|
|
PreferGo: true, |
|
|
|
Dial: func(ctx context.Context, network, address string) (conn net.Conn, err error) { |
|
|
|
@ -106,15 +125,53 @@ func (r *resolver) dial(ctx context.Context, ns NameServer) (net.Conn, error) { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
func (r *resolver) Resolve(name string) ([]net.IPAddr, error) { |
|
|
|
func (r *resolver) Resolve(name string) (addrs []net.IPAddr, err error) { |
|
|
|
timeout := r.Timeout |
|
|
|
if timeout <= 0 { |
|
|
|
timeout = DefaultResolverTimeout |
|
|
|
|
|
|
|
addrs = r.loadCache(name) |
|
|
|
if len(addrs) > 0 { |
|
|
|
if Debug { |
|
|
|
log.Logf("[resolver] cache hit: %s %v", name, addrs) |
|
|
|
} |
|
|
|
return |
|
|
|
} |
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), timeout) |
|
|
|
defer cancel() |
|
|
|
|
|
|
|
return r.Resolver.LookupIPAddr(ctx, name) |
|
|
|
addrs, err = r.Resolver.LookupIPAddr(ctx, name) |
|
|
|
r.storeCache(name, addrs) |
|
|
|
if len(addrs) > 0 && Debug { |
|
|
|
log.Logf("[resolver] %s %v", name, addrs) |
|
|
|
} |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
func (r *resolver) loadCache(name string) []net.IPAddr { |
|
|
|
ttl := r.TTL |
|
|
|
if ttl < 0 { |
|
|
|
return nil |
|
|
|
} |
|
|
|
|
|
|
|
if v, ok := r.mCache.Load(name); ok { |
|
|
|
item, _ := v.(*resolverCacheItem) |
|
|
|
if item == nil || time.Since(time.Unix(item.ts, 0)) > ttl { |
|
|
|
return nil |
|
|
|
} |
|
|
|
return item.IPAddrs |
|
|
|
} |
|
|
|
|
|
|
|
return nil |
|
|
|
} |
|
|
|
|
|
|
|
func (r *resolver) storeCache(name string, addrs []net.IPAddr) { |
|
|
|
ttl := r.TTL |
|
|
|
if ttl < 0 || name == "" || len(addrs) == 0 { |
|
|
|
return |
|
|
|
} |
|
|
|
r.mCache.Store(name, &resolverCacheItem{ |
|
|
|
IPAddrs: addrs, |
|
|
|
ts: time.Now().Unix(), |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
func (r *resolver) String() string { |
|
|
|
@ -124,6 +181,7 @@ func (r *resolver) String() string { |
|
|
|
|
|
|
|
b := &bytes.Buffer{} |
|
|
|
fmt.Fprintf(b, "timeout %v\n", r.Timeout) |
|
|
|
fmt.Fprintf(b, "ttl %v\n", r.TTL) |
|
|
|
for i := range r.Servers { |
|
|
|
fmt.Fprintln(b, r.Servers[i]) |
|
|
|
} |
|
|
|
|