Document every exported symbol and how callers use the library.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+245
@@ -0,0 +1,245 @@
|
||||
# API
|
||||
|
||||
Generated from the package comment and every exported declaration. Do not edit.
|
||||
Regenerate with `UPDATE_API_DOCS=1 go test -run TestAPIDoc -count=1`.
|
||||
|
||||
## Package godns
|
||||
|
||||
Package godns is a library for authoritative DNS against BIND-compatible nameservers: full zone transfer (AXFR) over TCP and RFC 2136 dynamic UPDATE, authenticated with caller-supplied TSIG. Domain names are ASCII only in v1.
|
||||
|
||||
## Types
|
||||
|
||||
### Action
|
||||
|
||||
```go
|
||||
type Action int
|
||||
```
|
||||
|
||||
Action is one RFC 2136 UPDATE operation.
|
||||
|
||||
#### ActionAdd
|
||||
|
||||
#### ActionDelete
|
||||
|
||||
```go
|
||||
const (
|
||||
// ActionAdd inserts a record.
|
||||
ActionAdd Action = iota
|
||||
// ActionDelete removes one record that matches owner, type, and rdata.
|
||||
ActionDelete
|
||||
)
|
||||
```
|
||||
|
||||
### Change
|
||||
|
||||
```go
|
||||
type Change struct {
|
||||
Action Action
|
||||
Record Record
|
||||
}
|
||||
```
|
||||
|
||||
Change is one add or delete of one record inside an Update batch.
|
||||
|
||||
#### Add
|
||||
|
||||
```go
|
||||
func Add(rec Record) Change
|
||||
```
|
||||
|
||||
Add builds an insert change for rec.
|
||||
|
||||
#### Delete
|
||||
|
||||
```go
|
||||
func Delete(rec Record) Change
|
||||
```
|
||||
|
||||
Delete builds a delete of one matching record (owner, type, and rdata).
|
||||
|
||||
### Client
|
||||
|
||||
```go
|
||||
type Client struct {
|
||||
// contains filtered or unexported fields
|
||||
}
|
||||
```
|
||||
|
||||
Client is one session: one nameserver, one zone, and one TSIG key. AXFR and Update both use that key. There is no unsigned mode.
|
||||
|
||||
#### NewClient
|
||||
|
||||
```go
|
||||
func NewClient(server, zone string, key TSIG) (*Client, error)
|
||||
```
|
||||
|
||||
NewClient starts a session for one nameserver, zone, and TSIG key. server is a host or host:port. A missing port becomes 53. An IPv6 host must be written in brackets. It returns an error when server, zone, or any TSIG field is empty, when zone or the key name is not ASCII, or when server is a bare IPv6 address.
|
||||
|
||||
#### Client.AXFR
|
||||
|
||||
```go
|
||||
func (c *Client) AXFR(ctx context.Context) (*TransferResult, error)
|
||||
```
|
||||
|
||||
AXFR reads the full zone over TCP, signed with the session TSIG key. It returns an error when ctx is done, the transfer fails, a record owner is not ASCII, or the stream contains no SOA. The closing SOA of the transfer is not appended again.
|
||||
|
||||
#### Client.Update
|
||||
|
||||
```go
|
||||
func (c *Client) Update(ctx context.Context, changes ...Change) (*UpdateResult, error)
|
||||
```
|
||||
|
||||
Update sends an RFC 2136 UPDATE for changes, signed with the session TSIG key. It tries UDP, then TCP when the UDP exchange fails or the response is truncated. No changes returns success and does not contact the nameserver. It returns an error when ctx is done, a record cannot be encoded, the exchange fails, the nameserver rcode is not NOERROR, or Action is neither ActionAdd nor ActionDelete.
|
||||
|
||||
### RRSet
|
||||
|
||||
```go
|
||||
type RRSet struct {
|
||||
Owner string
|
||||
Class string
|
||||
Type string
|
||||
Records []Record
|
||||
}
|
||||
```
|
||||
|
||||
RRSet groups records that share owner, class, and type.
|
||||
|
||||
### Record
|
||||
|
||||
```go
|
||||
type Record struct {
|
||||
Owner string
|
||||
Class string
|
||||
Type string
|
||||
TTL uint32
|
||||
RData string
|
||||
}
|
||||
```
|
||||
|
||||
Record is one resource record. Owner is a trailing-dot FQDN. Class and Type are presentation names such as IN and A. RData is the presentation-format rdata, without the owner, TTL, class, or type.
|
||||
|
||||
#### A
|
||||
|
||||
```go
|
||||
func A(owner string, ttl uint32, ip net.IP) (Record, error)
|
||||
```
|
||||
|
||||
A builds an IN A record. It returns an error when ip is not IPv4, or when owner is empty or not ASCII. Owner is stored as a trailing-dot FQDN.
|
||||
|
||||
#### AAAA
|
||||
|
||||
```go
|
||||
func AAAA(owner string, ttl uint32, ip net.IP) (Record, error)
|
||||
```
|
||||
|
||||
AAAA builds an IN AAAA record. It returns an error when ip is missing, is IPv4, or when owner is empty or not ASCII. Owner is stored as a trailing-dot FQDN.
|
||||
|
||||
#### CAA
|
||||
|
||||
```go
|
||||
func CAA(owner string, ttl uint32, flag uint8, tag, value string) (Record, error)
|
||||
```
|
||||
|
||||
CAA builds an IN CAA record (RFC 8659) with flag, tag, and value. It returns an error when owner is empty or not ASCII.
|
||||
|
||||
#### CNAME
|
||||
|
||||
```go
|
||||
func CNAME(owner string, ttl uint32, target string) (Record, error)
|
||||
```
|
||||
|
||||
CNAME builds an IN CNAME record pointing at target. It returns an error when owner or target is empty or not ASCII.
|
||||
|
||||
#### MX
|
||||
|
||||
```go
|
||||
func MX(owner string, ttl uint32, pref uint16, target string) (Record, error)
|
||||
```
|
||||
|
||||
MX builds an IN MX record for preference pref and exchanger target. It returns an error when owner or target is empty or not ASCII.
|
||||
|
||||
#### NS
|
||||
|
||||
```go
|
||||
func NS(owner string, ttl uint32, target string) (Record, error)
|
||||
```
|
||||
|
||||
NS builds an IN NS record pointing at target. It returns an error when owner or target is empty or not ASCII.
|
||||
|
||||
#### PTR
|
||||
|
||||
```go
|
||||
func PTR(owner string, ttl uint32, target string) (Record, error)
|
||||
```
|
||||
|
||||
PTR builds an IN PTR record pointing at target. It returns an error when owner or target is empty or not ASCII.
|
||||
|
||||
#### SRV
|
||||
|
||||
```go
|
||||
func SRV(owner string, ttl uint32, prio, weight, port uint16, target string) (Record, error)
|
||||
```
|
||||
|
||||
SRV builds an IN SRV record. It returns an error when owner or target is empty or not ASCII.
|
||||
|
||||
#### TXT
|
||||
|
||||
```go
|
||||
func TXT(owner string, ttl uint32, text string) (Record, error)
|
||||
```
|
||||
|
||||
TXT builds an IN TXT record whose rdata is text. It returns an error when owner is empty or not ASCII.
|
||||
|
||||
### TSIG
|
||||
|
||||
```go
|
||||
type TSIG struct {
|
||||
Name string
|
||||
Algorithm string
|
||||
Secret string
|
||||
}
|
||||
```
|
||||
|
||||
TSIG is caller-supplied key material. The library does not generate keys or write BIND configuration. Name is the key name presented to the nameserver. Algorithm is a BIND name such as hmac-sha256 (also hmac-md5, hmac-sha1, hmac-sha224, hmac-sha384, and hmac-sha512). Secret is the base64 secret.
|
||||
|
||||
### TransferResult
|
||||
|
||||
```go
|
||||
type TransferResult struct {
|
||||
Zone Zone
|
||||
Serial uint32
|
||||
}
|
||||
```
|
||||
|
||||
TransferResult is a completed AXFR. Serial is the SOA serial and is the same value stored on Zone.
|
||||
|
||||
### UpdateResult
|
||||
|
||||
```go
|
||||
type UpdateResult struct {
|
||||
Rcode string
|
||||
}
|
||||
```
|
||||
|
||||
UpdateResult is a successful UPDATE response. Rcode is the nameserver rcode text, which is NOERROR on success.
|
||||
|
||||
### Zone
|
||||
|
||||
```go
|
||||
type Zone struct {
|
||||
Origin string
|
||||
Serial uint32
|
||||
Records []Record
|
||||
}
|
||||
```
|
||||
|
||||
Zone is an in-memory zone: origin, SOA serial, and a flat record list. AXFR returns one. Callers can also assemble a Zone and call RRSets.
|
||||
|
||||
#### Zone.RRSets
|
||||
|
||||
```go
|
||||
func (z Zone) RRSets() []RRSet
|
||||
```
|
||||
|
||||
RRSets groups Zone records by owner, class, and type, in first-seen order.
|
||||
|
||||
@@ -144,6 +144,6 @@ Ensure `db.example.com` exists with a valid SOA and NS records before loading th
|
||||
|
||||
## Related documentation
|
||||
|
||||
- [Guide](guide.md) — how callers use AXFR, UPDATE, and TSIG
|
||||
- [API](api.md) — exported symbols
|
||||
- [Project specification](project-specification.md) — library scope, TSIG model, AXFR-only transfers
|
||||
- [Change 00001 spec](changes/00001-setup-project/spec.md) — full DNS operation catalog and BIND guide requirements
|
||||
- [Product backlog](backlog.md) — future work (e.g. IXFR)
|
||||
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
# go-dns guide
|
||||
|
||||
How to use the library. Symbol signatures and doc comments are in [API](api.md). Words for the domain are in [CONTEXT.md](../CONTEXT.md). Operators who configure BIND follow [BIND TSIG configuration](bind-tsig-configuration.md).
|
||||
|
||||
## Session
|
||||
|
||||
### Purpose
|
||||
|
||||
A session is a `Client` bound to one nameserver address, one zone name, and one TSIG key. AXFR and UPDATE both use that session. There is no unsigned mode.
|
||||
|
||||
### When to use it
|
||||
|
||||
Create one client per nameserver, zone, and key. A second key, even for the same zone, is a second client.
|
||||
|
||||
### Call sequence
|
||||
|
||||
1. Fill a `TSIG` with the key name, algorithm, and base64 secret.
|
||||
2. Call `NewClient` with the server, the zone, and that key.
|
||||
3. Call `AXFR` or `Update` on the returned client.
|
||||
|
||||
`server` is a host or `host:port`. A missing port becomes 53. An IPv6 host must be written in brackets, such as `[2001:db8::1]:53`.
|
||||
|
||||
### Errors
|
||||
|
||||
`NewClient` returns an error when the server, zone, or any TSIG field is empty, when the zone or the key name is not ASCII, or when the server is a bare IPv6 address. It does not contact the nameserver.
|
||||
|
||||
### Example
|
||||
|
||||
```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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = client
|
||||
```
|
||||
|
||||
## AXFR
|
||||
|
||||
### Purpose
|
||||
|
||||
AXFR reads the full zone over TCP and returns it as a `Zone` plus the SOA serial.
|
||||
|
||||
### When to use it
|
||||
|
||||
Use AXFR when the caller needs the nameserver's current records for the session's zone. Incremental transfer (IXFR) is not available.
|
||||
|
||||
### Call sequence
|
||||
|
||||
1. Start a session.
|
||||
2. Call `Client.AXFR` with a context.
|
||||
3. Read `TransferResult.Zone` and `TransferResult.Serial`. `Serial` is the same value stored on the zone.
|
||||
|
||||
The request is signed with the session TSIG key. The closing SOA of the transfer is not appended a second time.
|
||||
|
||||
### Errors
|
||||
|
||||
`AXFR` returns an error when the context is done, the transfer fails, a record owner is not ASCII, or the stream contains no SOA. Transport errors are wrapped with the server address.
|
||||
|
||||
### Example
|
||||
|
||||
```go
|
||||
tr, err := client.AXFR(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
zone := tr.Zone
|
||||
_ = zone.Serial
|
||||
```
|
||||
|
||||
## UPDATE
|
||||
|
||||
### Purpose
|
||||
|
||||
UPDATE sends one RFC 2136 batch that adds records, deletes records, or does both. The nameserver applies the batch.
|
||||
|
||||
### When to use it
|
||||
|
||||
Use UPDATE to change the session's zone. Each change is one record, built with `Add` or `Delete`.
|
||||
|
||||
### Call sequence
|
||||
|
||||
1. Build records with the typed builders.
|
||||
2. Wrap each record in `Add` or `Delete`.
|
||||
3. Call `Client.Update` with a context and the changes.
|
||||
4. On success, `UpdateResult.Rcode` is `NOERROR`.
|
||||
|
||||
The client tries UDP first. It retries on TCP when the UDP exchange fails or the response is truncated. An empty change list returns success and does not contact the nameserver. `Delete` removes one record that matches owner, type, and rdata. It does not delete a whole RRset by type alone.
|
||||
|
||||
### Errors
|
||||
|
||||
`Update` returns an error when the context is done, a record cannot be encoded, the exchange fails, or the nameserver rcode is not `NOERROR`. An `Action` other than `ActionAdd` or `ActionDelete` is rejected before the exchange. A refused or badly signed update comes back as an rcode error such as `REFUSED` or `BADSIG`.
|
||||
|
||||
### Example
|
||||
|
||||
```go
|
||||
rec, err := godns.A("www.example.com.", 300, net.ParseIP("192.0.2.10"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result, err := client.Update(ctx, godns.Add(rec))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = result.Rcode
|
||||
```
|
||||
|
||||
## TSIG
|
||||
|
||||
### Purpose
|
||||
|
||||
TSIG is the caller-supplied key that signs AXFR and UPDATE. The library checks the fields and sends them. It does not generate keys or write BIND configuration.
|
||||
|
||||
### When to use it
|
||||
|
||||
Fill a `TSIG` before `NewClient`. The same value is used for every call on that client.
|
||||
|
||||
### Call sequence
|
||||
|
||||
1. Take the key name, algorithm, and base64 secret from the operator's BIND key.
|
||||
2. Store them on `TSIG`.
|
||||
3. Pass the value to `NewClient`.
|
||||
|
||||
Algorithms the client maps to known TSIG names are `hmac-md5`, `hmac-sha1`, `hmac-sha224`, `hmac-sha256`, `hmac-sha384`, and `hmac-sha512`. Any other non-empty algorithm is sent as a trailing-dot name. Prefer `hmac-sha256` for new keys. How to create the key on BIND is in [BIND TSIG configuration](bind-tsig-configuration.md).
|
||||
|
||||
### Errors
|
||||
|
||||
A missing name, algorithm, or secret fails in `NewClient`, as does a non-ASCII key name. A wrong secret does not fail at construction. The nameserver reports it on AXFR or UPDATE, usually as `BADSIG`.
|
||||
|
||||
### Example
|
||||
|
||||
```go
|
||||
key := godns.TSIG{
|
||||
Name: "go-dns-key.",
|
||||
Algorithm: "hmac-sha256",
|
||||
Secret: os.Getenv("TSIG_SECRET"),
|
||||
}
|
||||
client, err := godns.NewClient("192.0.2.53", "example.com", key)
|
||||
```
|
||||
|
||||
## Typed RR builders
|
||||
|
||||
### Purpose
|
||||
|
||||
The builders turn an owner, a TTL, and typed fields into one `Record` of class `IN`. The owner is stored as a trailing-dot FQDN. `RData` is presentation-format rdata.
|
||||
|
||||
### When to use it
|
||||
|
||||
Use a builder when the caller has an address, a target name, or text and wants a record for UPDATE or for an in-memory zone. The builders are `A`, `AAAA`, `MX`, `TXT`, `CNAME`, `NS`, `SRV`, `PTR`, and `CAA`.
|
||||
|
||||
### Call sequence
|
||||
|
||||
1. Call the builder for the record type.
|
||||
2. Pass the `Record` to `Add` or `Delete`, or store it on a `Zone`.
|
||||
|
||||
`A` takes an IPv4 `net.IP`. `AAAA` takes an IPv6 `net.IP`. Name targets (`MX`, `CNAME`, `NS`, `SRV`, `PTR`) are ASCII domain names. `CAA` takes a flag, a tag, and a value (RFC 8659).
|
||||
|
||||
### Errors
|
||||
|
||||
A builder returns an error when the owner is empty or not ASCII. `A` returns an error when the address is not IPv4. `AAAA` returns an error when the address is missing or is IPv4. Builders that take a target name return an error when that name is empty or not ASCII.
|
||||
|
||||
### Example
|
||||
|
||||
```go
|
||||
a, err := godns.A("www.example.com.", 300, net.ParseIP("192.0.2.10"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mx, err := godns.MX("example.com.", 300, 10, "mail.example.com.")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = client.Update(ctx, godns.Add(a), godns.Add(mx))
|
||||
```
|
||||
|
||||
## Zone, RRSet, and Record
|
||||
|
||||
### Purpose
|
||||
|
||||
`Record` is one resource record: owner, class, type, TTL, and presentation-format rdata. `Zone` is an origin, an SOA serial, and a flat record list. `RRSet` groups records that share owner, class, and type.
|
||||
|
||||
### When to use it
|
||||
|
||||
AXFR returns a `Zone`. Call `Zone.RRSets` when the caller wants records grouped. A caller can also assemble a `Zone` from builder output and group it the same way. Grouping does not contact the nameserver.
|
||||
|
||||
### Call sequence
|
||||
|
||||
1. Take `TransferResult.Zone` from AXFR, or fill `Zone.Origin`, `Zone.Serial`, and `Zone.Records`.
|
||||
2. Call `RRSets` to get groups in first-seen order.
|
||||
3. Read each group's `Owner`, `Class`, `Type`, and `Records`.
|
||||
|
||||
### Errors
|
||||
|
||||
`RRSets` does not return an error. It copies the records already on the zone. Bad names are reported earlier, by AXFR or by a builder.
|
||||
|
||||
### Example
|
||||
|
||||
```go
|
||||
tr, err := client.AXFR(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, set := range tr.Zone.RRSets() {
|
||||
fmt.Println(set.Owner, set.Type, len(set.Records))
|
||||
}
|
||||
```
|
||||
@@ -4,18 +4,18 @@
|
||||
|
||||
**go-dns** is a Go library for authoritative DNS operations against **BIND** (and compatible servers):
|
||||
|
||||
- **Dynamic updates** — same semantics as `nsupdate` ([RFC 2136](https://www.rfc-editor.org/rfc/rfc2136))
|
||||
- **Dynamic updates** — RFC 2136 add and delete of records ([RFC 2136](https://www.rfc-editor.org/rfc/rfc2136)); prerequisite sections are out of scope
|
||||
- **Zone transfer** — full **AXFR** only ([RFC 5936](https://www.rfc-editor.org/rfc/rfc5936)); IXFR is out of scope for v1
|
||||
- **TSIG authentication** — callers supply existing shared keys; key generation is out of scope
|
||||
|
||||
Callers perform updates and full zone reads in Go with typed **zone data structures** and **typed RR builders** instead of shelling out to `nsupdate` or `dig axfr`.
|
||||
Callers perform updates and full zone reads in Go with an in-memory zone (`Record` rdata is presentation format) and **typed RR builders** instead of shelling out to `nsupdate` or `dig axfr`.
|
||||
|
||||
## Module metadata
|
||||
|
||||
| Item | Value |
|
||||
|------|-------|
|
||||
| Module path | `gitea.neitzel.de/konrad/go-dns` |
|
||||
| Go version | Workspace `go.work` (currently 1.26.4) |
|
||||
| Go version | `go 1.27.0` in this module's `go.mod` |
|
||||
| Public package | **`godns`** (`package godns` at module root) |
|
||||
| Git remote | `ssh://git@gitea.neitzel.de:2222/konrad/go-dns.git` |
|
||||
|
||||
@@ -34,21 +34,21 @@ This is a **Library** in the ExApp Workspace. Workspace Go rules live in `.curso
|
||||
## Supported operation surface
|
||||
|
||||
1. **Session & TSIG key** — server, zone, transport, existing key (name, algorithm, secret)
|
||||
2. **Dynamic update** — add, delete, prerequisites, batch UPDATE; typed RR builders
|
||||
2. **Dynamic update** — add, delete, and batch UPDATE; typed RR builders (`A`, `AAAA`, `MX`, `TXT`, `CNAME`, `NS`, `SRV`, `PTR`, `CAA`)
|
||||
3. **Zone transfer** — **AXFR** only; parse stream into zone data
|
||||
4. **Zone data model** — `Record`, `RRSet`, `Zone`, `UpdateResult`, `TransferResult`
|
||||
5. **BIND guide** — [bind-tsig-configuration.md](bind-tsig-configuration.md) (`allow-update` + `allow-transfer` for a TSIG key)
|
||||
|
||||
## API style
|
||||
|
||||
- **Typed builders** per RR type (e.g. A, AAAA, MX, SRV) — not a generic `(type, rdata string)` API
|
||||
- Exact method names follow in later Workspace Issues after the catalog is agreed
|
||||
- **Typed builders** per RR type (`A`, `AAAA`, `MX`, `TXT`, `CNAME`, `NS`, `SRV`, `PTR`, `CAA`) — not a generic `(type, rdata string)` API
|
||||
- How to call them: [guide.md](guide.md). Every exported symbol: [api.md](api.md)
|
||||
|
||||
## Zone data model (logical)
|
||||
|
||||
| Type | Purpose |
|
||||
|------|---------|
|
||||
| **Record** | Single RR: owner, class, type, TTL, typed rdata |
|
||||
| **Record** | Single RR: owner, class, type, TTL, presentation-format rdata |
|
||||
| **RRSet** | Group of RRs with same owner/class/type |
|
||||
| **Zone** | Origin + full record set + SOA/serial (complete zone in memory) |
|
||||
| **UpdateResult** | UPDATE response status |
|
||||
@@ -58,21 +58,24 @@ This is a **Library** in the ExApp Workspace. Workspace Go rules live in `.curso
|
||||
|
||||
Operators configure BIND so a TSIG key may **update** and **transfer** a zone (`allow-update`, `allow-transfer`). Step-by-step instructions, security notes, and a minimal `named.conf` example:
|
||||
|
||||
**[docs/bind-tsig-configuration.md](bind-tsig-configuration.md)**
|
||||
**[bind-tsig-configuration.md](bind-tsig-configuration.md)**
|
||||
|
||||
## Tests
|
||||
|
||||
| Command | Purpose |
|
||||
|---------|---------|
|
||||
| `go test ./...` | Unit tests (encoding, parsing, zone model, typed builders) |
|
||||
| `go test ./...` | Unit tests (encoding, parsing, zone model, typed builders). Fails when `docs/api.md` is stale |
|
||||
| `UPDATE_API_DOCS=1 go test -run TestAPIDoc -count=1` | Rewrite `docs/api.md` after an exported API change |
|
||||
| `go test -tags=integration ./...` | **BIND testcontainer** (Docker required) |
|
||||
|
||||
Follow the Workspace `.cursor/rules/go-testing.mdc`. This Library's integration image is BIND.
|
||||
|
||||
## Package layout (planned)
|
||||
## Package layout
|
||||
|
||||
```
|
||||
package godns # public API at module root
|
||||
internal/ # RFC 2136, AXFR, TSIG, wire format
|
||||
docs/ # Library spec and BIND TSIG guide
|
||||
docs/guide.md # consumer guide
|
||||
docs/api.md # generated exported API
|
||||
docs/project-specification.md
|
||||
docs/bind-tsig-configuration.md
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user