git » dnss » next » tree

[next] / tests / dnsprobe.go

//go:build ignore

// dnsprobe sends DNS queries to a server at a regular interval, and logs the
// result of each one. It's used by outage.sh to measure how dnss behaves
// during and after network outages.
//
// Each query is for a different name, so the answers can't come from a cache.
//
// It prints one line per query, as they complete:
//
//	<send time, in unix milliseconds> <latency in milliseconds> <result>
//
// The result is "ok" if we got a NOERROR or NXDOMAIN reply, or a short
// description of the failure otherwise.
package main

import (
	"errors"
	"flag"
	"fmt"
	"math/rand/v2"
	"net"
	"os"
	"sync"
	"time"

	"github.com/miekg/dns"
)

var (
	server   = flag.String("server", "127.0.0.1:1053", "DNS server to query")
	interval = flag.Duration("interval", 200*time.Millisecond,
		"time between queries")
	timeout = flag.Duration("timeout", 2*time.Second, "timeout for each query")
	domain  = flag.String("domain", "probe.test.", "domain to query under")
)

var outMu sync.Mutex

func main() {
	flag.Parse()

	c := &dns.Client{
		Net:     "udp",
		Timeout: *timeout,
	}

	// Use a random prefix, so names don't repeat across runs either.
	prefix := rand.Uint32()

	ticker := time.NewTicker(*interval)
	for i := 0; ; i++ {
		go probe(c, fmt.Sprintf("%x-%d.%s", prefix, i, *domain))
		<-ticker.C
	}
}

func probe(c *dns.Client, name string) {
	m := &dns.Msg{}
	m.SetQuestion(name, dns.TypeA)

	start := time.Now()
	r, _, err := c.Exchange(m, *server)
	latency := time.Since(start)

	result := "ok"
	var netErr net.Error
	switch {
	case errors.As(err, &netErr) && netErr.Timeout():
		result = "timeout"
	case err != nil:
		result = "error: " + err.Error()
	case r.Rcode != dns.RcodeSuccess && r.Rcode != dns.RcodeNameError:
		result = dns.RcodeToString[r.Rcode]
	}

	outMu.Lock()
	fmt.Fprintf(os.Stdout, "%d %d %s\n",
		start.UnixMilli(), latency.Milliseconds(), result)
	outMu.Unlock()
}