or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

client.mdconstants.mddnssec.mddynamic-updates.mdedns0.mdindex.mdmessaging.mdrr-types.mdserver.mdtsig.mdutilities.mdzone-parsing.mdzone-transfer.md
tile.json

dynamic-updates.mddocs/

DNS Dynamic Updates

DNS Dynamic Update protocol (RFC 2136) for modifying zone data on authoritative servers.

Core Concepts

Dynamic updates use DNS messages with:

  • Question section: Zone to update
  • Answer section: Prerequisites (conditions that must be met)
  • Authority section: Update operations (additions/deletions)
  • Additional section: Additional data (often TSIG for authentication)

Message Setup

// SetUpdate prepares message for dynamic update
func (dns *Msg) SetUpdate(zone string) *Msg

Prerequisite Methods

Prerequisites check conditions before applying updates. They use the Answer section.

Name Checks

// 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)

RRset Checks

// 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 Methods

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)

Usage Examples

Basic Record Addition

// 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])
}

Secure Update with TSIG

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")
}

Conditional Update with Prerequisites

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")

Deleting Records

// 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")

Deleting All Records of a Type

// 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")

Deleting All Records for a Name

// 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")

Check Name Does Not Exist

// 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")
}

Check Name 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")
}

Check RRset Exists

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")
}

Atomic Replace

// 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")

Multiple Operations

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")

Server-Side Update Handling

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)
})

Response Codes

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
)

Handling Response Codes

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])
}

Security Considerations

Always Use Authentication

// 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 Access Control

// 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
	}
})

Common Patterns

DNS-based Service Registration

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
}

Related Topics

  • DNS Messaging - Message construction
  • TSIG Authentication - Securing updates
  • Client Operations - Sending updates
  • Server Operations - Handling updates
  • Resource Record Types - RR types for updates