Skip to content

Monitor DNS

Source · io.systemd.Resolve.Monitor · systemd v257+ · Root

resolvectl monitor in Go: subscribe to systemd-resolved's query stream and print every lookup as it completes. The subscription never ends on its own; Ctrl-C cancels the context, which ends the stream and closes the connection.

Run it

sudo go run ./examples/dns-monitor
Output (illustrative)
monitoring… (Ctrl-C to stop)
success    github.com                               answers=1
success    api.github.com                           answers=1
success    go.dev                                   answers=8
failure    nope.invalid                             answers=0
^C

The code

examples/dns-monitor/main.go
// Command dns-monitor streams every DNS query systemd-resolved answers, like
// `resolvectl monitor`. It runs until interrupted. Requires root.
//
//  sudo go run ./examples/dns-monitor
package main

import (
    "context"
    "fmt"
    "log"
    "os"
    "os/signal"
    "strings"

    "github.com/rossedman/sdvarlink/systemd/v257/resolvemonitor"
    "github.com/rossedman/sdvarlink/varlink"
)

func main() {
    // Cancelling the context ends the stream and closes the connection.
    ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
    defer stop()

    c, err := resolvemonitor.Dial(ctx)
    if err != nil {
        log.Fatal(err)
    }
    defer c.Close()

    for ev, err := range c.SubscribeQueryResults(ctx, resolvemonitor.SubscribeQueryResultsIn{}) {
        if ctx.Err() != nil {
            return
        }
        if err != nil {
            log.Fatal(err)
        }
        if varlink.Deref(ev.Ready) {
            fmt.Println("monitoring… (Ctrl-C to stop)")
            continue
        }
        var q []string
        for _, k := range ev.Question {
            q = append(q, k.Name)
        }
        fmt.Printf("%-10s %-40s answers=%d\n", varlink.Deref(ev.State), strings.Join(q, ","), len(ev.Answer))
    }
}

How it works

  • Requires-more. SubscribeQueryResults is annotated [Requires 'more' flag], so the generated method returns the iterator directly; there's no single-reply variant.
  • The first reply is a handshake with Ready: true, sent once the subscription is active.
  • Shutdown via context. signal.NotifyContext cancels on Ctrl-C. The loop checks ctx.Err() first so an interrupt exits cleanly instead of reporting the cancellation as a failure.
  • Root only. The monitor socket is restricted to privileged clients.

See also