DNS Dynamic Update protocol (RFC 2136) for modifying zone data on authoritative servers.
Dynamic updates use DNS messages with:
// SetUpdate prepares message for dynamic update
func (dns *Msg) SetUpdate(zone string) *MsgPrerequisites check conditions before applying updates. They use the Answer section.
// NameUsed checks that name exists (any RR type)
// RFC 2136 section 2.4.4
func (u *Msg) NameUsed(rr []RR)
// NameNotUsed checks that name does not exist
// RFC 2136 section 2.4.5
func (u *Msg) NameNotUsed(rr []RR)// RRsetUsed checks that RRset exists (value independent)
// RFC 2136 section 2.4.1
func (u *Msg) RRsetUsed(rr []RR)
// RRsetNotUsed checks that RRset does not exist
// RFC 2136 section 2.4.3
func (u *Msg) RRsetNotUsed(rr []RR)
// Used checks that specific RR exists (value dependent)
// RFC 2136 section 2.4.2
func (u *Msg) Used(rr []RR)Update operations modify zone data. They use the Authority section.
// Insert adds complete RRset to zone
// RFC 2136 section 2.5.1
func (u *Msg) Insert(rr []RR)
// Remove deletes specific RRs from RRset
// RFC 2136 section 2.5.4
func (u *Msg) Remove(rr []RR)
// RemoveRRset deletes entire RRset
// RFC 2136 section 2.5.2
func (u *Msg) RemoveRRset(rr []RR)
// RemoveName deletes all RRsets for a name
// RFC 2136 section 2.5.3
func (u *Msg) RemoveName(rr []RR)// Create update message
m := new(dns.Msg)
m.SetUpdate("example.com.")
// Add A record
rr := &dns.A{
Hdr: dns.RR_Header{
Name: "new.example.com.",
Rrtype: dns.TypeA,
Class: dns.ClassINET,
Ttl: 3600,
},
A: net.ParseIP("192.0.2.1"),
}
m.Insert([]dns.RR{rr})
// Send update
c := new(dns.Client)
r, _, err := c.Exchange(m, "ns1.example.com:53")
if err != nil {
log.Fatal(err)
}
if r.Rcode != dns.RcodeSuccess {
log.Printf("Update failed: %s", dns.RcodeToString[r.Rcode])
}m := new(dns.Msg)
m.SetUpdate("example.com.")
// Add record
rr := &dns.A{
Hdr: dns.RR_Header{
Name: "secure.example.com.",
Rrtype: dns.TypeA,
Class: dns.ClassINET,
Ttl: 3600,
},
A: net.ParseIP("192.0.2.1"),
}
m.Insert([]dns.RR{rr})
// Add TSIG authentication
m.SetTsig("update-key.", dns.HmacSHA256, 300, time.Now().Unix())
// Send with TSIG secret
c := &dns.Client{
TsigSecret: map[string]string{
"update-key.": "base64secret==",
},
}
r, _, err := c.Exchange(m, "ns1.example.com:53")
if err != nil {
log.Fatal(err)
}
if r.Rcode != dns.RcodeSuccess {
log.Fatal("Update failed")
}m := new(dns.Msg)
m.SetUpdate("example.com.")
// Prerequisite: Check that old record exists
oldRR := &dns.A{
Hdr: dns.RR_Header{
Name: "host.example.com.",
Rrtype: dns.TypeA,
Class: dns.ClassINET,
},
A: net.ParseIP("192.0.2.1"),
}
m.Used([]dns.RR{oldRR})
// Update: Remove old record and add new one
m.Remove([]dns.RR{oldRR})
newRR := &dns.A{
Hdr: dns.RR_Header{
Name: "host.example.com.",
Rrtype: dns.TypeA,
Class: dns.ClassINET,
Ttl: 3600,
},
A: net.ParseIP("192.0.2.2"),
}
m.Insert([]dns.RR{newRR})
c := new(dns.Client)
r, _, err := c.Exchange(m, "ns1.example.com:53")// Delete specific RR
m := new(dns.Msg)
m.SetUpdate("example.com.")
rr := &dns.A{
Hdr: dns.RR_Header{
Name: "old.example.com.",
Rrtype: dns.TypeA,
Class: dns.ClassINET,
},
A: net.ParseIP("192.0.2.1"),
}
m.Remove([]dns.RR{rr})
c := new(dns.Client)
c.Exchange(m, "ns1.example.com:53")// Delete entire A RRset
m := new(dns.Msg)
m.SetUpdate("example.com.")
rr := &dns.A{
Hdr: dns.RR_Header{
Name: "host.example.com.",
Rrtype: dns.TypeA,
Class: dns.ClassINET,
},
}
m.RemoveRRset([]dns.RR{rr})
c := new(dns.Client)
c.Exchange(m, "ns1.example.com:53")// Delete all RRsets for a name
m := new(dns.Msg)
m.SetUpdate("example.com.")
rr := &dns.A{
Hdr: dns.RR_Header{
Name: "remove.example.com.",
},
}
m.RemoveName([]dns.RR{rr})
c := new(dns.Client)
c.Exchange(m, "ns1.example.com:53")// Only add if name doesn't exist
m := new(dns.Msg)
m.SetUpdate("example.com.")
// Prerequisite: name must not exist
prereqRR := &dns.A{
Hdr: dns.RR_Header{
Name: "new.example.com.",
},
}
m.NameNotUsed([]dns.RR{prereqRR})
// Add record
addRR := &dns.A{
Hdr: dns.RR_Header{
Name: "new.example.com.",
Rrtype: dns.TypeA,
Class: dns.ClassINET,
Ttl: 3600,
},
A: net.ParseIP("192.0.2.1"),
}
m.Insert([]dns.RR{addRR})
c := new(dns.Client)
r, _, err := c.Exchange(m, "ns1.example.com:53")
if r.Rcode == dns.RcodeYXDomain {
log.Println("Name already exists")
}// Only update if name exists
m := new(dns.Msg)
m.SetUpdate("example.com.")
// Prerequisite: name must exist
prereqRR := &dns.A{
Hdr: dns.RR_Header{
Name: "existing.example.com.",
},
}
m.NameUsed([]dns.RR{prereqRR})
// Add another record type
addRR := &dns.AAAA{
Hdr: dns.RR_Header{
Name: "existing.example.com.",
Rrtype: dns.TypeAAAA,
Class: dns.ClassINET,
Ttl: 3600,
},
AAAA: net.ParseIP("2001:db8::1"),
}
m.Insert([]dns.RR{addRR})
c := new(dns.Client)
r, _, err := c.Exchange(m, "ns1.example.com:53")
if r.Rcode == dns.RcodeNXDomain {
log.Println("Name does not exist")
}m := new(dns.Msg)
m.SetUpdate("example.com.")
// Prerequisite: check that A RRset exists (any value)
prereqRR := &dns.A{
Hdr: dns.RR_Header{
Name: "host.example.com.",
Rrtype: dns.TypeA,
},
}
m.RRsetUsed([]dns.RR{prereqRR})
// Add MX record
addRR := &dns.MX{
Hdr: dns.RR_Header{
Name: "example.com.",
Rrtype: dns.TypeMX,
Class: dns.ClassINET,
Ttl: 3600,
},
Preference: 10,
Mx: "host.example.com.",
}
m.Insert([]dns.RR{addRR})
c := new(dns.Client)
r, _, err := c.Exchange(m, "ns1.example.com:53")
if r.Rcode == dns.RcodeNXRrset {
log.Println("A RRset does not exist")
}// Atomically replace entire RRset
m := new(dns.Msg)
m.SetUpdate("example.com.")
// Remove entire A RRset
removeRR := &dns.A{
Hdr: dns.RR_Header{
Name: "host.example.com.",
Rrtype: dns.TypeA,
},
}
m.RemoveRRset([]dns.RR{removeRR})
// Add new A records
newRRs := []dns.RR{
&dns.A{
Hdr: dns.RR_Header{
Name: "host.example.com.",
Rrtype: dns.TypeA,
Class: dns.ClassINET,
Ttl: 3600,
},
A: net.ParseIP("192.0.2.10"),
},
&dns.A{
Hdr: dns.RR_Header{
Name: "host.example.com.",
Rrtype: dns.TypeA,
Class: dns.ClassINET,
Ttl: 3600,
},
A: net.ParseIP("192.0.2.11"),
},
}
m.Insert(newRRs)
c := new(dns.Client)
c.Exchange(m, "ns1.example.com:53")m := new(dns.Msg)
m.SetUpdate("example.com.")
// Add multiple records in one update
records := []dns.RR{
&dns.A{
Hdr: dns.RR_Header{
Name: "www.example.com.",
Rrtype: dns.TypeA,
Class: dns.ClassINET,
Ttl: 3600,
},
A: net.ParseIP("192.0.2.1"),
},
&dns.AAAA{
Hdr: dns.RR_Header{
Name: "www.example.com.",
Rrtype: dns.TypeAAAA,
Class: dns.ClassINET,
Ttl: 3600,
},
AAAA: net.ParseIP("2001:db8::1"),
},
&dns.MX{
Hdr: dns.RR_Header{
Name: "example.com.",
Rrtype: dns.TypeMX,
Class: dns.ClassINET,
Ttl: 3600,
},
Preference: 10,
Mx: "mail.example.com.",
},
}
m.Insert(records)
c := new(dns.Client)
c.Exchange(m, "ns1.example.com:53")dns.HandleFunc("example.com.", func(w dns.ResponseWriter, r *dns.Msg) {
if r.Opcode != dns.OpcodeUpdate {
return
}
// Check TSIG authentication
if r.IsTsig() == nil || w.TsigStatus() != nil {
m := new(dns.Msg)
m.SetRcode(r, dns.RcodeNotAuth)
w.WriteMsg(m)
return
}
// Validate prerequisites
for _, prereq := range r.Answer {
if !checkPrerequisite(prereq) {
m := new(dns.Msg)
m.SetRcode(r, dns.RcodeYXDomain) // Or RcodeNXDomain, RcodeYXRrset, RcodeNXRrset
w.WriteMsg(m)
return
}
}
// Apply updates
for _, update := range r.Ns {
applyUpdate(update)
}
// Success response
m := new(dns.Msg)
m.SetReply(r)
w.WriteMsg(m)
})const (
RcodeSuccess = 0 // Update succeeded
RcodeFormatError = 1 // Format error
RcodeServerFailure = 2 // Server failure
RcodeNameError = 3 // Name does not exist (NXDOMAIN)
RcodeNotImplemented = 4 // Not implemented
RcodeRefused = 5 // Query refused
RcodeYXDomain = 6 // Name exists when it should not
RcodeYXRrset = 7 // RRset exists when it should not
RcodeNXRrset = 8 // RRset does not exist when it should
RcodeNotAuth = 9 // Server not authoritative for zone
RcodeNotZone = 10 // Name not contained in zone
)r, _, err := c.Exchange(m, "ns1.example.com:53")
if err != nil {
log.Fatal(err)
}
switch r.Rcode {
case dns.RcodeSuccess:
log.Println("Update successful")
case dns.RcodeNotAuth:
log.Println("Not authorized")
case dns.RcodeYXDomain:
log.Println("Name exists (prerequisite failed)")
case dns.RcodeNXDomain:
log.Println("Name does not exist (prerequisite failed)")
case dns.RcodeYXRrset:
log.Println("RRset exists (prerequisite failed)")
case dns.RcodeNXRrset:
log.Println("RRset does not exist (prerequisite failed)")
case dns.RcodeRefused:
log.Println("Update refused")
default:
log.Printf("Update failed: %s", dns.RcodeToString[r.Rcode])
}// Dynamic updates should always be authenticated with TSIG
m.SetTsig("update-key.", dns.HmacSHA256, 300, time.Now().Unix())
c := &dns.Client{
TsigSecret: map[string]string{
"update-key.": "base64secret==",
},
}// Server should verify TSIG and check permissions
dns.HandleFunc(".", func(w dns.ResponseWriter, r *dns.Msg) {
if r.Opcode == dns.OpcodeUpdate {
// Check TSIG
if r.IsTsig() == nil || w.TsigStatus() != nil {
m := new(dns.Msg)
m.SetRcode(r, dns.RcodeNotAuth)
w.WriteMsg(m)
return
}
// Check zone authorization
if !isAuthorized(r.Question[0].Name, r.IsTsig().Hdr.Name) {
m := new(dns.Msg)
m.SetRcode(r, dns.RcodeRefused)
w.WriteMsg(m)
return
}
// Process update
}
})func RegisterService(service, ip string, port uint16) error {
m := new(dns.Msg)
m.SetUpdate("services.example.com.")
// Add A record
a := &dns.A{
Hdr: dns.RR_Header{
Name: service + ".services.example.com.",
Rrtype: dns.TypeA,
Class: dns.ClassINET,
Ttl: 60, // Short TTL for dynamic services
},
A: net.ParseIP(ip),
}
// Add SRV record
srv := &dns.SRV{
Hdr: dns.RR_Header{
Name: "_http._tcp." + service + ".services.example.com.",
Rrtype: dns.TypeSRV,
Class: dns.ClassINET,
Ttl: 60,
},
Priority: 10,
Weight: 10,
Port: port,
Target: service + ".services.example.com.",
}
m.Insert([]dns.RR{a, srv})
m.SetTsig("service-key.", dns.HmacSHA256, 300, time.Now().Unix())
c := &dns.Client{
TsigSecret: map[string]string{
"service-key.": "secret==",
},
}
r, _, err := c.Exchange(m, "ns.example.com:53")
if err != nil {
return err
}
if r.Rcode != dns.RcodeSuccess {
return fmt.Errorf("update failed: %s", dns.RcodeToString[r.Rcode])
}
return nil
}