prober: support creating multiple probes in ForEachAddr

So that we can e.g. check TLS on multiple ports for a given IP.

Updates tailscale/corp#16367

Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
Change-Id: I81d840a4c88138de1cbb2032b917741c009470e6
pull/11625/head
Andrew Dunham 8 months ago
parent ac574d875c
commit 7d7d159824

@ -31,15 +31,15 @@ type ForEachAddrOpts struct {
} }
// ForEachAddr returns a Probe that resolves a given hostname into all // ForEachAddr returns a Probe that resolves a given hostname into all
// available IP addresses, and then calls a function to create a new Probe // available IP addresses, and then calls a function to create new Probes
// every time a new IP is discovered. The Probe returned will be closed if an // 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 // 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. // be used to healthcheck every IP address that a hostname resolves to.
func ForEachAddr(host string, newProbe func(netip.Addr) *Probe, opts ForEachAddrOpts) ProbeFunc { func ForEachAddr(host string, makeProbes func(netip.Addr) []*Probe, opts ForEachAddrOpts) ProbeFunc {
return makeForEachAddr(host, newProbe, opts).run return makeForEachAddr(host, makeProbes, opts).run
} }
func makeForEachAddr(host string, newProbe func(netip.Addr) *Probe, opts ForEachAddrOpts) *forEachAddrProbe { func makeForEachAddr(host string, makeProbes func(netip.Addr) []*Probe, opts ForEachAddrOpts) *forEachAddrProbe {
if opts.Logf == nil { if opts.Logf == nil {
opts.Logf = logger.Discard opts.Logf = logger.Discard
} }
@ -54,9 +54,9 @@ func makeForEachAddr(host string, newProbe func(netip.Addr) *Probe, opts ForEach
logf: opts.Logf, logf: opts.Logf,
host: host, host: host,
networks: opts.Networks, networks: opts.Networks,
newProbe: newProbe, makeProbes: makeProbes,
lookupNetIP: opts.LookupNetIP, lookupNetIP: opts.LookupNetIP,
probes: make(map[netip.Addr]*Probe), probes: make(map[netip.Addr][]*Probe),
} }
} }
@ -65,12 +65,12 @@ type forEachAddrProbe struct {
logf logger.Logf logf logger.Logf
host string host string
networks []string networks []string
newProbe func(netip.Addr) *Probe makeProbes func(netip.Addr) []*Probe
lookupNetIP func(context.Context, string, string) ([]netip.Addr, error) lookupNetIP func(context.Context, string, string) ([]netip.Addr, error)
// state // state
mu sync.Mutex // protects following mu sync.Mutex // protects following
probes map[netip.Addr]*Probe probes map[netip.Addr][]*Probe
} }
// run matches the ProbeFunc signature // run matches the ProbeFunc signature
@ -102,23 +102,25 @@ func (f *forEachAddrProbe) run(ctx context.Context) error {
} }
// Make a new probe, and add it to 'probes'; if the // Make a new probe, and add it to 'probes'; if the
// function returns nil, we skip it. // function returns an empty list, we skip it.
probe := f.newProbe(addr) probes := f.makeProbes(addr)
if probe == nil { if len(probes) == 0 {
continue continue
} }
f.logf("adding new probe for %v", addr) f.logf("adding %d new probes for %v", len(probes), addr)
f.probes[addr] = probe f.probes[addr] = probes
} }
// Remove probes that we didn't see during this address resolution. // Remove probes that we didn't see during this address resolution.
for addr, probe := range f.probes { for addr, probes := range f.probes {
if !sawIPs[addr] { if !sawIPs[addr] {
f.logf("removing probe for %v", addr) f.logf("removing %d probes for %v", len(probes), addr)
// This IP is no longer in the DNS record. Close and remove the probe. // This IP is no longer in the DNS record. Close and remove all probes
probe.Close() for _, probe := range probes {
probe.Close()
}
delete(f.probes, addr) delete(f.probes, addr)
} }
} }

@ -39,14 +39,15 @@ func ExampleForEachAddr() {
} }
// This function is called every time we discover a new IP address to check. // This function is called every time we discover a new IP address to check.
makeTLSProbe := func(addr netip.Addr) *prober.Probe { makeTLSProbe := func(addr netip.Addr) []*prober.Probe {
pf := prober.TLSWithIP(*hostname, netip.AddrPortFrom(addr, 443)) pf := prober.TLSWithIP(*hostname, netip.AddrPortFrom(addr, 443))
if *verbose { if *verbose {
logger := logger.WithPrefix(log.Printf, fmt.Sprintf("[tls %s]: ", addr)) logger := logger.WithPrefix(log.Printf, fmt.Sprintf("[tls %s]: ", addr))
pf = probeLogWrapper(logger, pf) pf = probeLogWrapper(logger, pf)
} }
return p.Run(fmt.Sprintf("website/%s/tls", addr), every30s, nil, pf) probe := p.Run(fmt.Sprintf("website/%s/tls", addr), every30s, nil, pf)
return []*prober.Probe{probe}
} }
// Determine whether to use IPv4 or IPv6 based on whether we can create // Determine whether to use IPv4 or IPv6 based on whether we can create

@ -48,7 +48,7 @@ func TestForEachAddr(t *testing.T) {
mu sync.Mutex // protects following mu sync.Mutex // protects following
registered []netip.Addr registered []netip.Addr
) )
newProbe := func(addr netip.Addr) *Probe { newProbe := func(addr netip.Addr) []*Probe {
// Called to register a new prober // Called to register a new prober
t.Logf("called to register new probe for %v", addr) t.Logf("called to register new probe for %v", addr)
@ -57,9 +57,10 @@ func TestForEachAddr(t *testing.T) {
registered = append(registered, addr) registered = append(registered, addr)
// Return a probe that does nothing; we don't care about what this does. // Return a probe that does nothing; we don't care about what this does.
return p.Run(fmt.Sprintf("website/%s", addr), probeInterval, nil, func(_ context.Context) error { probe := p.Run(fmt.Sprintf("website/%s", addr), probeInterval, nil, func(_ context.Context) error {
return nil return nil
}) })
return []*Probe{probe}
} }
fep := makeForEachAddr("tailscale.com", newProbe, opts) fep := makeForEachAddr("tailscale.com", newProbe, opts)

Loading…
Cancel
Save