20 KiB
Initial project setup and DNS operation catalog
Goal
Bootstrap go-dns as a new Go module in the workspace and define the operation surface the library will support:
- Dynamic DNS updates against BIND-style authoritative nameservers (RFC 2136,
nsupdatesemantics) - Zone transfer — full zone read via AXFR only (IXFR deferred — see backlog.md)
- TSIG key authentication for all of the above (callers supply existing keys; key generation is out of scope)
- Zone data structures to parse transfer/update responses and hold complete zone contents in memory
- BIND configuration guide describing how a TSIG key must be configured on the server for updates and zone transfers
This change establishes project identity, documentation layout, and an agreed catalog of supported operations and data models. It does not implement the library API or wire protocol yet.
Background
Authoritative DNS on BIND is commonly manipulated in two ways over the wire:
- Dynamic update — add, change, or remove resource records without editing zone files (
nsupdate, RFC 2136). - Zone transfer — read the full zone via AXFR (RFC 5936). Incremental transfer (IXFR) is out of scope for the initial library; see backlog.md.
Both paths are authenticated on production servers with TSIG shared secrets (RFC 2845, RFC 8945): the client signs requests; BIND verifies the signature and applies ACLs (allow-update, allow-transfer) for that key.
go-dns will be an importable Go library so other projects (automation, controllers, internal tools) can perform updates and zone transfers programmatically — with typed data structures, validation, and testability — instead of shelling out to nsupdate or dig axfr.
Sibling workspace projects (go-ollama, etc.) follow: module path gitea.neitzel.de/konrad/<project>, project spec under docs/, and .cursor/rules/project-specification.mdc.
DNS operations — supported interface
The following catalog describes what the library interface must be able to express. How (exact Go type names, function signatures, error shapes) is deferred to later changes. Wording follows RFC 2136, RFC 5936, and common BIND 9 / nsupdate usage.
1. Session, authentication, and transport
Operations that establish where, how, and as whom requests are sent.
| Operation | Description |
|---|---|
| Set server | Target authoritative nameserver (hostname or IP, optional port; default 53). |
| Set zone | Origin zone name (UPDATE question section; AXFR QNAME). |
| Configure TSIG key | Authenticate with an existing shared secret: key name, algorithm (e.g. hmac-sha256), and secret (Base64). Callers provide keys; the library does not generate keys. |
| Choose transport | UDP first for UPDATE where applicable; automatic TCP fallback on truncation (TC bit) or oversize; AXFR always uses TCP. |
| Timeout / context | Caller-controlled deadline and cancellation (maps to Go context.Context). |
Authentication model:
- TSIG is mandatory for the documented happy path (updates and zone transfers).
- The same key may be used for both UPDATE and AXFR when BIND ACLs permit (see BIND configuration below).
- Out of scope: key generation (
tsig-keygen,rndc tsig-keygen, etc.), GSS-TSIG, SIG(0), unauthenticated UPDATE or AXFR.
2. Update actions (zone mutations)
Operations that modify RRsets in the zone — the Update section of RFC 2136.
| Operation | Description | nsupdate analogue |
|---|---|---|
| Add RR | Insert one or more RRs at a name. If the RRset exists, new RRs are merged in; duplicates may be rejected or ignored per server policy. | add name [ttl] [class] type rdata |
| Delete RR (exact) | Remove RRs matching name, class, type, and exact rdata. Other RRs at the same name/type remain. | delete name [ttl] [class] type rdata |
| Delete RRset | Remove all RRs with the given name, class, and type (rdata wildcard). | delete name [ttl] [class] type |
| Delete all at name | Remove all RRs at a name (any type), optionally scoped by class. | delete name / update delete name |
| Replace RRset | Convenience composition: delete an RRset (or exact match) then add new RRs — same net effect as delete-then-add in one UPDATE. | two-step delete + add in one send |
| Set TTL on add | Specify TTL when adding; omit to use server default where applicable. | TTL field on add |
| Set class | Default IN (Internet); allow other classes only if explicitly required later. | [class] on add/delete |
Each row may appear multiple times in a single UPDATE message before submission.
3. Prerequisites (conditional updates)
Operations in the Prerequisite section — the update fails entirely if any prerequisite is not met (no partial apply).
| Operation | Description | nsupdate analogue |
|---|---|---|
| Name in use | The name must already have at least one RR (any type). | prereq nxdomain inverse / requires patterns |
| Name not in use | The name must have no RRs (NXDOMAIN or empty owner). | prereq nxdomain |
| RRset exists | An RRset with given name, class, and type must exist. Optional: full set of rdata values must match. | prereq yxrrset |
| RRset not exists | No RRset for name/class/type, or no RRs matching given rdata set. | prereq nxrrset |
| RR exists | A specific RR (name, class, type, rdata) must exist. | prereq yxrrset with rdata |
| RR not exists | That specific RR must not exist. | prereq nxrrset with rdata |
Prerequisites are optional but recommended for idempotent automation (compare-and-swap style updates).
4. Resource record types and typed builders
The interface MUST use typed builders per RR type — not a generic (type, rdata string) API. Each supported type has a dedicated Go type (or builder) with validated fields (e.g. ARecord with net.IP, MXRecord with priority + host, SRVRecord with priority/weight/port/target).
Initial target set:
| Category | Types |
|---|---|
| Address | A, AAAA |
| Names | CNAME, PTR, NS |
| Text / metadata | TXT, CAA |
| Mail / service | MX, SRV |
| DNSSEC-related (dynamic) | CDS, CDNSKEY (where policy allows) |
| Other | HINFO, NAPTR, TLSA, SSHFP |
For RR types returned in AXFR but not yet given a typed builder, Record may hold a typed unknown wrapper (type number + validated wire rdata bytes or presentation parse) until a builder is added — callers must not rely on unparsed strings for updates.
Per-type field validation (e.g. hostname vs IP) is implemented with the typed builders in a later change; this catalog requires typed expression only, not free-form rdata strings.
SOA serial manipulation is not a direct client operation; BIND increments the SOA serial automatically on successful updates.
5. Update submission and batching
| Operation | Description |
|---|---|
| Build update message | Collect prerequisites and update actions into one RFC 2136 UPDATE. |
| Send update | Transmit the TSIG-signed message and receive the response. |
| Batch multiple changes | One send applies many add/delete/prerequisite operations atomically (all succeed or none). |
| Interpret update response | Map server RCODE to success or typed errors (e.g. REFUSED, NOTAUTH, NXDOMAIN, YXDOMAIN, NOTZONE, BADSIG). |
There is no “show” or local-only preview in the protocol; local validation before send is a library concern, not a server operation.
6. Zone transfer (AXFR)
Operations to read the full zone from the authoritative server (distinct from RFC 2136 UPDATE).
| Operation | Description | Typical use |
|---|---|---|
| AXFR (full transfer) | Download all RRs in the zone in one TSIG-signed transfer session over TCP. | Sync, backup, inspect zone contents |
| Parse transfer stream | Decode multi-message AXFR response into structured zone data (see Zone data model). | Populate in-memory zone |
| Interpret transfer errors | REFUSED (ACL), NOTAUTH (TSIG/ACL), BADSIG, FORMERR, etc. | Caller error handling |
Constraints:
- AXFR always uses TCP.
- Transfers require
allow-transfer(or equivalent policy) for the TSIG key on the server — see BIND configuration.
Record types in transferred zones include SOA, NS, and all RR types present in the zone — not limited to the update-target set in section 4.
7. Zone data model
The library MUST define concrete data types (implemented in Go in a later change) to hold parsed zone contents and operation results. Logical model:
Record (single RR)
| Field | Description |
|---|---|
| Owner | Domain name (FQDN, normalized) |
| Class | DNS class (default IN) |
| Type | RR type (A, AAAA, SOA, … or generic numeric type) |
| TTL | Time to live in seconds |
| RData | Type-specific value matching the RR type (typed builder / sum type — not a raw presentation string) |
RRSet (optional grouping)
| Field | Description |
|---|---|
| Owner, Class, Type | RRset key |
| Records | One or more Record values sharing owner/class/type (multiple RData for same RRset) |
| TTL | RRset TTL (consistent across members in a well-formed zone) |
Zone
| Field | Description |
|---|---|
| Origin | Zone apex name (e.g. example.com.) |
| Records | All RRs from transfer or constructed locally — complete zone contents |
| SOA | SOA RR extracted for convenience (serial, refresh, retry, expire, minimum TTL, MNAME, RNAME) |
| Serial | SOA serial number (from SOA RR after transfer) |
Access patterns the types must support (behavioral requirements, not API names):
- Iterate all records; lookup by owner name; lookup by owner + type.
- After AXFR, populate
Zonewith the full current zone. - After UPDATE, optionally reflect mutations in an in-memory
Zoneif the caller maintains one (library may offer helpers). - Export to a stable sorted text representation (zone-file-like) for debugging/tests (exact format in plan).
Transfer / update result wrappers
| Type (logical) | Holds |
|---|---|
| UpdateResult | Success/failure, RCODE, optional server message |
| TransferResult | Resulting Zone, transfer kind (AXFR), ending serial (SOA after transfer) |
8. Read-only / auxiliary (library-side)
| Operation | Description |
|---|---|
| Validate rdata | Client-side syntax check before send (optional helper). |
| Normalize name | FQDN vs relative-to-zone, trailing dot; ASCII hostnames only in v1 (reject non-ASCII / Unicode labels; IDNA/punycode deferred). |
| Dry run / encode only | Produce wire-format or human-readable message without sending (testing and debugging). |
| Zone diff | Compare two Zone values (optional helper for automation; detail in plan). |
Out of scope: recursive resolver lookups (A/AAAA for unrelated names), catalog zones, BIND rndc control channel.
9. BIND configuration for TSIG keys
The project MUST ship a BIND configuration guide (English) describing how to configure BIND 9 so a single TSIG key can perform dynamic updates and full zone transfers (AXFR). Target location: docs/bind-tsig-configuration.md (created in implementation plan; content requirements defined here).
The guide MUST cover at minimum:
- Define the key —
key "name" { algorithm ...; secret "..."; };innamed.confor an included file. State explicitly that key generation is out of library scope; operators usetsig-keygenorrndc tsig-keygen(or copy from existing secret management). - Reference the key in the zone — for the target
zone "example.com" { ... }block:allow-update { key "name"; };— permits RFC 2136 UPDATE signed with that key.allow-transfer { key "name"; };— permits AXFR to clients signing with that key.
- Optional stricter policy — mention
update-policy(grant rules per name/type) as an alternative or supplement to broadallow-update. - Reload / validation —
named-checkconf,rndc reload, and verifying withnsupdate -yanddig -y/ TSIG-signed AXFR. - Security notes — restrict key to needed zones; do not expose AXFR to the public Internet; separate keys for read vs write if policy requires (library supports different keys per client instance even if the guide shows one key for simplicity).
- Minimal complete example — one
named.conffragment:key+zonewithallow-updateandallow-transferfor the same key.
Example fragment (illustrative; the guide expands this):
key "go-dns-key" {
algorithm hmac-sha256;
secret "base64secret==";
};
zone "example.com" {
type master;
file "/var/lib/bind/db.example.com";
allow-update { key "go-dns-key"; };
allow-transfer { key "go-dns-key"; };
};
10. Explicit non-goals (initial version)
| Excluded | Reason |
|---|---|
| TSIG key generation | Operator/tooling concern (tsig-keygen, secret store) |
| DNSSEC inline signing workflow | Complex policy; may revisit later |
| Unauthenticated UPDATE or AXFR | Security risk; TSIG is the default path |
subprocess wrapper around nsupdate / dig |
Library implements the protocol directly |
| Non-BIND servers | Best-effort compatibility possible, but BIND 9 is the reference |
| IDNA / Unicode domain names | ASCII-only in v1; punycode support deferred |
| IXFR (incremental zone transfer) | Full AXFR only in v1; see backlog.md |
Scope
- Register the project in workspace
go.work - Initialize Go module
gitea.neitzel.de/konrad/go-dns - Add project Cursor rule and
docs/project-specification.md(summary + pointer to this catalog) - Add
README.md,.gitignore,docs/backlog.md, anddocs/changes/layout - Document the full DNS operation catalog (update, transfer, data model, BIND guide requirements) as the agreed interface surface
- Ensure
go work syncsucceeds with the new module (no application code required in this phase)
Out of scope
- Go API implementation (types, interfaces, function bodies)
- RFC 2136 / AXFR wire encoding and TSIG signing implementation
- Unit or integration tests for update or transfer logic
- CLI binary, HTTP service, or Docker deployment (production compose — not required for library; BIND testcontainer is used for integration tests only)
git init, Gitea remote, and first push in this specify phase — deferred to implementation plan (see Resolved decisions)- Detailed per-record-type rdata validator implementation (typed builder shapes are decided here; code in a later change)
- TSIG key generation — operators create keys with BIND tooling or secret management; library accepts key name, algorithm, secret only
- Writing
docs/bind-tsig-configuration.mdin this phase (requirements for it are in this spec; file is created during implementation)
Requirements
- Module path MUST be
gitea.neitzel.de/konrad/go-dnsper workspace infrastructure rules. - Go version in
go.modMUST match workspacego.work(currently1.26.4). - Project MUST be listed in root
go.workunderuse (...). .cursor/rules/project-specification.mdcMUST exist withalwaysApply: trueand referencedocs/project-specification.md.docs/project-specification.mdMUST summarize purpose, BIND target, RFC 2136 + AXFR, TSIG authentication, zone data model, and point to this operation catalog.- This
spec.mdMUST contain the full operation catalog (sections 1–10): session/auth, updates, prerequisites, RR types, update submission, AXFR, zone data model, auxiliary helpers, BIND configuration guide requirements, and non-goals. - This
spec.mdMUST require TSIG key authentication for updates and transfers; key generation MUST be listed as out of scope. - This
spec.mdMUST define logical typesRecord,RRSet,Zone,UpdateResult, andTransferResultfor holding zone data and responses. README.mdMUST be in English and describe the project intent (updates + zone transfer) and link to this spec..gitignoreMUST exclude common Go artifacts and local secret config paths.docs/changes/MUST exist for future change efforts.- Public Go package name MUST be
godns(importgitea.neitzel.de/konrad/go-dns,package godnsat module root). - RR construction MUST use typed builders per type; a generic rdata string API MUST NOT be the primary interface.
- Integration tests (future change) MUST use a BIND testcontainer via testcontainers-go; tag
integrationper workspace rules. - UPDATE transport MUST automatically fall back to TCP when UDP response is truncated or unsuitable.
- Domain names MUST be ASCII-only in v1; reject Unicode labels at validation time.
docs/backlog.mdMUST exist with IXFR support listed as a future backlog item.
Resolved decisions
| Question | Decision |
|---|---|
| Root package name | godns — package godns at module root |
| RData API | Typed builders per RR type (A, AAAA, MX, …); no generic rdata string as primary API |
| Zone transfer | AXFR only in v1; IXFR tracked in backlog.md |
| Integration testing | BIND testcontainer (testcontainers-go); go test -tags=integration ./... |
| TCP fallback | Automatic on UDP truncation / oversize for UPDATE |
| IDNA / Unicode | ASCII only initially; IDNA deferred |
| Git repository | git init + Gitea remote during implementation of this change (plan phase), not in specify phase |
Remote URL per workspace infrastructure: ssh://git@gitea.neitzel.de:2222/konrad/go-dns.git
Constraints
- Follow workspace change-driven development: library implementation happens only after this spec and an approved
plan.md. - Documentation and comments in English.
- Library-first design (importable package), not a mandatory daemon or CLI.
- TSIG is the primary authentication mechanism; do not design for unsigned updates or transfers as the happy path.
- Zone transfers use TCP; UPDATE uses UDP with mandatory automatic TCP fallback on truncation.
- Domain names: ASCII-only validation in v1.
- Integration tests: BIND testcontainer only (no optional external nameserver config required for CI).
- Match conventions of existing workspace Go libraries (e.g.
go-ollama) where applicable.
Acceptance criteria
- AC-1:
grep -q go-dns /Users/konrad/Projects/go/go.workandgo work syncfrom the workspace root completes without error. - AC-2:
go-dns/go.moddeclaresmodule gitea.neitzel.de/konrad/go-dnsandgo 1.26.4. - AC-3:
go-dns/.cursor/rules/project-specification.mdcexists, hasalwaysApply: true, and referencesdocs/project-specification.md. - AC-4:
go-dns/docs/project-specification.mddocuments module path, BIND target, RFC 2136 + AXFR, TSIG auth, zone data model, and references this spec. - AC-5:
go-dns/README.mdexists, is in English, mentions updates and AXFR zone transfer, and links to this spec. - AC-6: This
spec.mdincludes sections 1–10: updates, AXFR (no IXFR), zone data model (Record,RRSet,Zone, result types), BIND configuration guide requirements, and non-goals including no key generation and no IXFR. - AC-7: This
spec.mdspecifies thatdocs/bind-tsig-configuration.mdwill documentallow-updateandallow-transferfor a TSIG key with a minimalnamed.confexample. - AC-8:
go-dns/docs/backlog.mdexists with an IXFR backlog entry. - AC-9:
go-dns/docs/changes/00001-setup-project/contains thisspec.md(and laterplan.md,verification.md,changes.mdper workflow). - AC-10: Resolved decisions documents
godns, typed builders, AXFR-only transfer, BIND testcontainer, automatic TCP fallback, ASCII-only names, and git setup in implementation.