forked from tailscale/tailscale
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdns.go
132 lines (115 loc) · 3.55 KB
/
dns.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
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package prober
import (
"context"
"fmt"
"net"
"net/netip"
"sync"
"tailscale.com/types/logger"
)
// ForEachAddrOpts contains options for ForEachAddr. The zero value for all
// fields is valid unless stated otherwise.
type ForEachAddrOpts struct {
// Logf is the logger to use for logging. If nil, no logging is done.
Logf logger.Logf
// Networks is the list of networks to resolve; if non-empty, it should
// contain at least one of "ip", "ip4", or "ip6".
//
// If empty, "ip" is assumed.
Networks []string
// LookupNetIP is the function to use to resolve the hostname to one or
// more IP addresses.
//
// If nil, net.DefaultResolver.LookupNetIP is used.
LookupNetIP func(context.Context, string, string) ([]netip.Addr, error)
}
// ForEachAddr returns a Probe that resolves a given hostname into all
// available IP addresses, and then calls a function to create new Probes
// every time a new IP is discovered. The Probes returned will be closed if an
// IP address is no longer in the DNS record for the given hostname. This can
// be used to healthcheck every IP address that a hostname resolves to.
func ForEachAddr(host string, makeProbes func(netip.Addr) []*Probe, opts ForEachAddrOpts) ProbeClass {
feap := makeForEachAddr(host, makeProbes, opts)
return ProbeClass{
Probe: feap.run,
Class: "dns_each_addr",
}
}
func makeForEachAddr(host string, makeProbes func(netip.Addr) []*Probe, opts ForEachAddrOpts) *forEachAddrProbe {
if opts.Logf == nil {
opts.Logf = logger.Discard
}
if len(opts.Networks) == 0 {
opts.Networks = []string{"ip"}
}
if opts.LookupNetIP == nil {
opts.LookupNetIP = net.DefaultResolver.LookupNetIP
}
return &forEachAddrProbe{
logf: opts.Logf,
host: host,
networks: opts.Networks,
makeProbes: makeProbes,
lookupNetIP: opts.LookupNetIP,
probes: make(map[netip.Addr][]*Probe),
}
}
type forEachAddrProbe struct {
// inputs; immutable
logf logger.Logf
host string
networks []string
makeProbes func(netip.Addr) []*Probe
lookupNetIP func(context.Context, string, string) ([]netip.Addr, error)
// state
mu sync.Mutex // protects following
probes map[netip.Addr][]*Probe
}
// run matches the ProbeFunc signature
func (f *forEachAddrProbe) run(ctx context.Context) error {
var addrs []netip.Addr
for _, network := range f.networks {
naddrs, err := f.lookupNetIP(ctx, network, f.host)
if err != nil {
return fmt.Errorf("resolving %s addr for %q: %w", network, f.host, err)
}
addrs = append(addrs, naddrs...)
}
if len(addrs) == 0 {
return fmt.Errorf("no addrs for %q", f.host)
}
// For each address, create a new probe if it doesn't already
// exist in our probe map.
f.mu.Lock()
defer f.mu.Unlock()
sawIPs := make(map[netip.Addr]bool)
for _, addr := range addrs {
sawIPs[addr] = true
if _, ok := f.probes[addr]; ok {
// Nothing to create
continue
}
// Make a new probe, and add it to 'probes'; if the
// function returns an empty list, we skip it.
probes := f.makeProbes(addr)
if len(probes) == 0 {
continue
}
f.logf("adding %d new probes for %v", len(probes), addr)
f.probes[addr] = probes
}
// Remove probes that we didn't see during this address resolution.
for addr, probes := range f.probes {
if !sawIPs[addr] {
f.logf("removing %d probes for %v", len(probes), addr)
// This IP is no longer in the DNS record. Close and remove all probes
for _, probe := range probes {
probe.Close()
}
delete(f.probes, addr)
}
}
return nil
}