tessl install tessl/golang-github-com-miekg--dns@1.1.1Complete DNS library for Go with full protocol control, DNSSEC support, and both client and server programming capabilities
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
}