/
resolver.go
145 lines (121 loc) · 3.8 KB
/
resolver.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package bootstrap
import (
"context"
"net"
"net/netip"
"slices"
"time"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
)
// Resolver resolves the hostnames to IP addresses. Note, that [net.Resolver]
// from standard library also implements this interface.
type Resolver interface {
// LookupNetIP looks up the IP addresses for the given host. network should
// be one of [NetworkIP], [NetworkIP4] or [NetworkIP6]. The response may be
// empty even if err is nil. All the addrs must be valid.
LookupNetIP(ctx context.Context, network Network, host string) (addrs []netip.Addr, err error)
}
// type check
var _ Resolver = &net.Resolver{}
// ParallelResolver is a slice of resolvers that are queried concurrently. The
// first successful response is returned.
type ParallelResolver []Resolver
// type check
var _ Resolver = ParallelResolver(nil)
// LookupNetIP implements the [Resolver] interface for ParallelResolver.
func (r ParallelResolver) LookupNetIP(
ctx context.Context,
network Network,
host string,
) (addrs []netip.Addr, err error) {
resolversNum := len(r)
switch resolversNum {
case 0:
return nil, ErrNoResolvers
case 1:
return lookup(ctx, r[0], network, host)
default:
// Go on.
}
// Size of channel must accommodate results of lookups from all resolvers,
// sending into channel will block otherwise.
ch := make(chan any, resolversNum)
for _, rslv := range r {
go lookupAsync(ctx, rslv, network, host, ch)
}
var errs []error
for range r {
switch result := <-ch; result := result.(type) {
case error:
errs = append(errs, result)
case []netip.Addr:
return result, nil
}
}
return nil, errors.Join(errs...)
}
// lookupAsync performs a lookup for ip of host with r and sends the result into
// resCh. It is intended to be used as a goroutine.
func lookupAsync(ctx context.Context, r Resolver, network, host string, resCh chan<- any) {
defer log.OnPanic("parallel lookup")
addrs, err := lookup(ctx, r, network, host)
if err != nil {
resCh <- err
} else {
resCh <- addrs
}
}
// lookup tries to lookup ip of host with r.
//
// TODO(e.burkov): Get rid of this function? It only wraps the actual lookup
// with dubious logging.
func lookup(ctx context.Context, r Resolver, network, host string) (addrs []netip.Addr, err error) {
start := time.Now()
addrs, err = r.LookupNetIP(ctx, network, host)
elapsed := time.Since(start)
if err != nil {
log.Debug("parallel lookup: lookup for %s failed in %s: %s", host, elapsed, err)
} else {
log.Debug("parallel lookup: lookup for %s succeeded in %s: %s", host, elapsed, addrs)
}
return addrs, err
}
// ConsequentResolver is a slice of resolvers that are queried in order until
// the first successful non-empty response, as opposed to just successful
// response requirement in [ParallelResolver].
type ConsequentResolver []Resolver
// type check
var _ Resolver = ConsequentResolver(nil)
// LookupNetIP implements the [Resolver] interface for ConsequentResolver.
func (resolvers ConsequentResolver) LookupNetIP(
ctx context.Context,
network Network,
host string,
) (addrs []netip.Addr, err error) {
if len(resolvers) == 0 {
return nil, ErrNoResolvers
}
var errs []error
for _, r := range resolvers {
addrs, err = r.LookupNetIP(ctx, network, host)
if err == nil && len(addrs) > 0 {
return addrs, nil
}
errs = append(errs, err)
}
return nil, errors.Join(errs...)
}
// StaticResolver is a resolver which always responds with an underlying slice
// of IP addresses regardless of host and network.
type StaticResolver []netip.Addr
// type check
var _ Resolver = StaticResolver(nil)
// LookupNetIP implements the [Resolver] interface for StaticResolver.
func (r StaticResolver) LookupNetIP(
_ context.Context,
_ Network,
_ string,
) (addrs []netip.Addr, err error) {
return slices.Clone(r), nil
}