package rfc2136 import ( "net" "time" "github.com/miekg/dns" ) // notifyTimeout caps how long any single NOTIFY send can block before // we give up. RFC 1996 §4 says the master MUST NOT block UPDATE // acknowledgement on NOTIFY delivery — the secondaries will fall back // to their own SOA refresh polling if NOTIFY is missed. 2s is plenty // for a healthy secondary to ack via UDP; a slow/blackholed target // just times out. const notifyTimeout = 2 * time.Second // defaultNotifyPort is appended to any target that doesn't already // specify host:port. NOTIFY is always-over-port-53 in practice. const defaultNotifyPort = "53" // sendNotify dispatches fire-and-forget DNS NOTIFY messages (RFC 1996) // to every configured secondary for the given zone. Each target gets // its own goroutine so a slow/blackholed secondary can't slow // propagation to its siblings. // // We do NOT wait for goroutines to finish — the UPDATE response goes // back to the client immediately. Whether secondaries ack or not, the // master's job is done; secondaries that miss the NOTIFY pick up the // new serial on their next refresh poll. // // Failures are logged at Debug level. NOTIFY is best-effort; logging // at Warning would flood the operator on every transient packet drop // for secondaries that are intermittently reachable. func sendNotify(zone string, targets []string) { if len(targets) == 0 { return } for _, t := range targets { go notifyOne(zone, t) } } // notifyOne sends one NOTIFY packet to `target` for `zone`. Target // can be "host" (default port 53), "host:port", or "[ipv6]:port". func notifyOne(zone, target string) { addr := target if _, _, err := net.SplitHostPort(addr); err != nil { addr = net.JoinHostPort(addr, defaultNotifyPort) } msg := new(dns.Msg) msg.SetNotify(dns.Fqdn(zone)) c := &dns.Client{Net: "udp", Timeout: notifyTimeout} _, _, err := c.Exchange(msg, addr) if err != nil { log.Debugf("NOTIFY %s → %s failed: %v", zone, addr, err) return } log.Debugf("NOTIFY %s → %s ok", zone, addr) }