Skip to content

Instantly share code, notes, and snippets.

@mfojtik
Created January 26, 2023 16:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mfojtik/8fa9a571c3c5835d7f9f6288d09b390e to your computer and use it in GitHub Desktop.
Save mfojtik/8fa9a571c3c5835d7f9f6288d09b390e to your computer and use it in GitHub Desktop.
package dnsprobe
import (
"context"
"errors"
"fmt"
"net"
"reflect"
"sort"
"sync"
"time"
)
type Controller struct {
service string
lastDurations []time.Duration
lastEntries []string
lastError error
lastErrorTimestamp time.Time
lastSuccess time.Time
resolveTimeout time.Duration
probeInterval time.Duration
resolver net.Resolver
}
func (c *Controller) startProbe(ctx context.Context) {
var wg sync.WaitGroup
for {
wg.Add(1)
go func(ctx context.Context) {
defer wg.Done()
now := time.Now()
var finishedDuration time.Duration
var lastEntries []string
done := make(chan error)
go func() {
defer func() {
finishedDuration = -time.Until(now)
}()
entries, err := c.resolver.LookupHost(ctx, c.service)
if err != nil {
done <- err
}
sort.Strings(entries)
lastEntries = entries
close(done)
}()
select {
case <-ctx.Done():
return
case err := <-done:
if err != nil {
fmt.Printf("ERROR: DNS resolving failed for %q: %v\n", c.service, err)
c.lastError = err
c.lastErrorTimestamp = time.Now()
return
}
case <-time.After(c.resolveTimeout):
fmt.Printf("ERROR: DNS resolving timed out for %q after %s\n", c.service, c.resolveTimeout)
c.lastError = errors.New("resolving timeout")
}
c.lastDurations = append(c.lastDurations, finishedDuration)
if len(c.lastDurations) > 60 {
c.lastDurations = append(c.lastDurations[:0], c.lastDurations[1:]...)
}
if !reflect.DeepEqual(c.lastEntries, lastEntries) && len(c.lastEntries) > 0 {
fmt.Printf("WARNING: DNS change detected for %q: was %#v now it is %#v\n", c.service, c.lastEntries, lastEntries)
}
c.lastSuccess = time.Now()
c.lastEntries = lastEntries
}(ctx)
select {
case <-ctx.Done():
return
default:
wg.Wait()
time.Sleep(c.probeInterval)
}
}
}
func New(serviceName string, probeInterval, resolveTimeout time.Duration) *Controller {
resolver := net.DefaultResolver
resolver.StrictErrors = true
resolver.PreferGo = true
return &Controller{
resolver: *resolver,
service: serviceName,
probeInterval: probeInterval,
resolveTimeout: resolveTimeout,
}
}
func (c *Controller) Run(ctx context.Context) {
go c.startProbe(ctx)
<-ctx.Done()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment