From a722194b5a8bf2edfdbdaacb776e13b6a552d728 Mon Sep 17 00:00:00 2001 From: Konrad Neitzel Date: Thu, 27 Aug 2026 22:00:02 +0200 Subject: [PATCH] Add CONTEXT.md, consumer README skeleton, and package Examples. Co-authored-by: Cursor --- CONTEXT.md | 25 ++++++++++++++++++++ README.md | 61 +++++++++++++++++++++++++++++++++++++++---------- example_test.go | 46 +++++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 12 deletions(-) create mode 100644 CONTEXT.md create mode 100644 example_test.go diff --git a/CONTEXT.md b/CONTEXT.md new file mode 100644 index 0000000..d7bf457 --- /dev/null +++ b/CONTEXT.md @@ -0,0 +1,25 @@ +# go-dns + +Shared Go Library for authoritative DNS against BIND-compatible nameservers: full zone transfer (AXFR) and RFC 2136 dynamic UPDATE with caller-supplied TSIG. ExApps and Services import `gitea.neitzel.de/konrad/go-dns`. + +## Language + +**Session**: +A `Client` bound to one nameserver address, one zone name, and one TSIG key. AXFR and UPDATE both use that Session; there is no anonymous or unsigned mode in this Library. +_Avoid_: resolver client, stub, recursive lookup API + +**Zone / RRSet / Record**: +A Zone is the in-memory result of an AXFR (origin, SOA serial, flat record list). An RRSet groups Records that share owner, class, and type. A Record is one resource record with presentation-format rdata. +_Avoid_: zone file on disk as the Library's primary type, miekg/dns RR as the public model + +**UPDATE**: +An RFC 2136 dynamic DNS change batch (add and/or delete Records) sent through the Session. The Library builds and signs the message; the nameserver applies it. +_Avoid_: nsupdate script files, unsigned updates + +**AXFR**: +A full zone transfer over TCP for the Session's zone, returning a Zone. Incremental transfer (IXFR) is out of scope. +_Avoid_: AXFR over UDP, treating AXFR as a recursive query + +**TSIG key**: +Caller-supplied key name, algorithm (e.g. hmac-sha256), and base64 secret used to authenticate AXFR and UPDATE. The Library does not generate keys or write BIND configuration. +_Avoid_: API token, shared AppAPI secret, auto-provisioned keys diff --git a/README.md b/README.md index 1c72ca8..4de8776 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,61 @@ # go-dns -Go library for authoritative DNS operations against BIND (and compatible) nameservers: +Shared Go library for authoritative DNS against BIND-compatible nameservers: full zone transfer (AXFR) and RFC 2136 dynamic UPDATE, authenticated with caller-supplied TSIG. -- **Dynamic updates** — `nsupdate`-style changes (RFC 2136) -- **Zone transfer** — full zone read via **AXFR** (IXFR out of scope for v1) -- **TSIG key authentication** (callers provide keys; generation out of scope) - -## Status - -Library: AXFR over TCP, RFC 2136 UPDATE (UDP with TCP fallback), caller-supplied TSIG, typed RR builders. ASCII names only. - -## Module +Import: `gitea.neitzel.de/konrad/go-dns` (package `godns`). ```bash go get gitea.neitzel.de/konrad/go-dns ``` -Module path: `gitea.neitzel.de/konrad/go-dns` — import as **`godns`**. +## Scope -## Documentation +**Included** +- **Session (`Client`)** — one nameserver + zone + TSIG key +- **AXFR** — full zone read over TCP +- **RFC 2136 UPDATE** — typed add/delete changes (UDP with TCP fallback) +- **TSIG** — caller-supplied key name, algorithm, and base64 secret +- **Typed RR builders** — A, AAAA, MX, TXT, CNAME, NS, SRV, PTR (ASCII names) +- **Zone / RRSet / Record** — in-memory zone model from AXFR + +**Excluded** + +- IXFR / incremental transfer +- TSIG key generation or BIND config writing (see docs for operator notes) +- DNSSEC signing / validation as a product feature +- Non-ASCII / IDN domain names (v1) + +## Usage + +```go +key := godns.TSIG{ + Name: "exapp-key.", + Algorithm: "hmac-sha256", + Secret: "base64-secret-from-bind", +} +client, err := godns.NewClient("ns.example:53", "example.com.", key) + +// AXFR +tr, err := client.AXFR(ctx) + +// UPDATE +rec, err := godns.A("www.example.com.", 300, net.ParseIP("192.0.2.10")) +_, err = client.Update(ctx, godns.Add(rec)) +``` + +Runnable package examples (construction only, no live nameserver): `go test -run Example`. + +## Domain language + +See [CONTEXT.md](./CONTEXT.md) for Session, Zone / RRSet / Record, UPDATE, AXFR, and TSIG key terminology. + +## Testing + +Unit tests cover message construction and record builders. Optional BIND integration tests live behind build tags / env (see `bind_integration_test.go`). + +## Related + +- **CheckDNS** — ExApp that consumes this library - [Project specification](docs/project-specification.md) - [BIND TSIG configuration](docs/bind-tsig-configuration.md) diff --git a/example_test.go b/example_test.go new file mode 100644 index 0000000..df87d28 --- /dev/null +++ b/example_test.go @@ -0,0 +1,46 @@ +package godns_test + +import ( + "fmt" + "net" + + godns "gitea.neitzel.de/konrad/go-dns" +) + +func ExampleNewClient() { + key := godns.TSIG{ + Name: "exapp-key.", + Algorithm: "hmac-sha256", + Secret: "dGVzdA==", // base64; construction only — no network call + } + client, err := godns.NewClient("127.0.0.1:53", "example.com.", key) + if err != nil { + fmt.Println("err:", err) + return + } + _ = client + fmt.Println("ok") + // Output: ok +} + +func ExampleAdd() { + rec, err := godns.A("www.example.com.", 300, net.ParseIP("192.0.2.10")) + if err != nil { + fmt.Println("err:", err) + return + } + change := godns.Add(rec) + fmt.Println(change.Action == godns.ActionAdd, change.Record.Type, change.Record.RData) + // Output: true A 192.0.2.10 +} + +func ExampleDelete() { + rec, err := godns.TXT("note.example.com.", 60, "retire") + if err != nil { + fmt.Println("err:", err) + return + } + change := godns.Delete(rec) + fmt.Println(change.Action == godns.ActionDelete, change.Record.Type) + // Output: true TXT +}