Help us shape the future of Go! Take the 2026 Go Developer Survey

masterfile

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 28, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

README

masterfile

CI Go Reference

A Go library for parsing, editing and formatting DNS zone files (RFC 1035 "master files") without losing anything a human put there: comments in every position, blank lines, directives and the written form of values.

It is built for programs that edit zone files other people also edit by hand and that a name server (BIND, Knot, CoreDNS) then loads:

  • Parse keeps every entry and comment and never panics on any input.
  • ResolveOwners gives records stable identities (owner relative to the apex, type, values, effective TTL) so they can be found, compared, replaced and removed regardless of position or $ORIGIN lines.
  • NewRecord builds records safely from parts that may come from network clients: a value can never add a second record, a directive or a comment.
  • Format writes one canonical, aligned layout that is idempotent.
  • Verify proves with an independent parser (miekg/dns) that the formatted text holds exactly the intended records and comments before it replaces the original.
f, err := masterfile.Parse(data)
apex, _ := f.Origin("")
err = f.ResolveOwners(apex)

rec, err := masterfile.NewRecord("www", 0, dns.TypeA, "192.0.2.1")
f.Entries = append(f.Entries, rec)

var plain, out bytes.Buffer
err = masterfile.WritePlain(&plain, apex, f.Entries)
err = masterfile.Format(&out, apex, f.Entries, true) // bumps the SOA serial
err = masterfile.Verify(apex, plain.Bytes(), out.Bytes())

The only dependency is github.com/miekg/dns for the RR type tables and the reference parser. Go 1.27 or later.

See docs/spec.md for the full specification and docs/DESIGN.md for implementation decisions.

License

Apache-2.0, see LICENSE.

Documentation

Overview

Package masterfile parses, edits and writes DNS zone files (RFC 1035 "master files") while keeping everything a human put there: comments in every position, blank lines, directives and the written form of values.

The package is a plain library over byte slices. It never reads or writes files itself and has no scheduling or network code. Its one third-party dependency is github.com/miekg/dns, used for the RR type and class name tables and as the independent reference parser in Verify.

Pipeline

A typical edit goes through these steps:

  1. Parse the text into a File of entries (blank lines, comments, directives and records).
  2. File.Origin finds the apex (the owner of the first SOA).
  3. File.ResolveOwners makes every record's owner explicit and relative to the apex, so entries can be found, moved, compared and replaced without regard to their position or to $ORIGIN lines.
  4. Edit the entry list: NewRecord builds records safely from parts that may come from network clients; Entry.Equal and EntriesEqual detect changes.
  5. Format writes the entries back in one canonical, aligned layout.
  6. Verify proves with the reference parser that the formatted text holds exactly the records and user comments of the plain rendering produced by WritePlain, before the result replaces the original.

Reformat runs steps 1, 2, 3 and 5 in one call for formatter commands.

Index

Constants

This section is empty.

Variables

View Source
var ErrInvalidRecord = errors.New("invalid record")

ErrInvalidRecord is wrapped by every error of NewRecord.

View Source
var ErrUnsafeWrite = errors.New("unsafe write")

ErrUnsafeWrite is wrapped by every error of Verify.

Functions

func Absolute

func Absolute(name, origin string) string

Absolute resolves name against origin: "@" is the origin, an absolute name is itself, and any other name is appended to the origin. With an empty origin a relative name is taken as absolute (a trailing dot is added), which is what the reference parser does; "@" then resolves to "".

func Comments

func Comments(data []byte) ([]string, error)

Comments parses data with this package's parser and returns every comment in file order. It is exported for tests.

func EntriesEqual

func EntriesEqual(a, b []Entry) bool

EntriesEqual compares two entry lists by meaning: same length, pairwise Entry.Equal ignoring explicit TTLs, and equal effective TTLs for records (both unknown is equal; known versus unknown is not). It is the test for whether an edit changed anything.

func Format added in v1.0.0

func Format(w io.Writer, apex string, entries []Entry, bumpSerial bool) error

Format writes entries whose owners have been resolved with File.ResolveOwners against apex in the canonical layout: aligned owner, TTL, class, type and value columns; owners relative to the $ORIGIN in force and blank when repeated; TTLs equal to the $TTL in force dropped; SOA and multi-string TXT records multi-line; long DNSKEY, DS and RRSIG data split into 55-byte pieces; comments kept in place. With bumpSerial the first SOA's serial is replaced by Increase of itself. The output is idempotent: formatting it again (without a bump) reproduces it byte for byte. Write errors are returned.

func Fqdn

func Fqdn(name string) string

Fqdn returns name with a trailing dot added if it has none. An empty name becomes the root ".".

func Increase

func Increase(serial string) string

Increase returns the serial to write after a change: a Unix time serial becomes max(now, serial+1); a date serial is incremented by one with no date logic ("2024123199" becomes "2024123200"). Anything else is returned unchanged. Two bumps within one second still increase.

func NameEqual

func NameEqual(a, b string) bool

NameEqual compares two names case-insensitively for ASCII letters only (RFC 4343); every other byte must match exactly. No normalisation is applied: "www" and "www." differ.

func Quote

func Quote(s string) string

Quote renders s as a zone file quoted string: it is wrapped in double quotes, '"' and '\' are prefixed with a backslash, and bytes below 0x20 and byte 0x7F are written as "\DDD". All other bytes, including UTF-8 and bytes above 0x7F, are copied. Parsing the result yields exactly s.

func Records

func Records(apex string, data []byte) (map[string]int, error)

Records parses data with the reference parser (miekg/dns, origin apex, $INCLUDE disallowed) and returns the multiset of its records as rr.String() lower-cased with the SOA serial zeroed, mapped to their counts. It is exported for tests.

func Reformat

func Reformat(data []byte, originHint string, w io.Writer, bumpSerial bool) error

Reformat parses data, finds the apex with File.Origin (falling back to the root "." when none is known), resolves owners and writes the canonical layout to w. It is Parse, Origin, ResolveOwners and Format in one call, for formatter commands and tests.

func Relative

func Relative(name, origin string) string

Relative returns the part of the absolute name under the absolute origin: the origin itself gives "@", "www.example.org." under "example.org." gives "www". A name that is "@", not absolute, or not under the origin is returned unchanged. The origin must match on a label boundary: an escaped dot is not a boundary, and "myexample.org." is not under "example.org.". Under the root origin "." an absolute name loses its trailing dot, unless that would leave another one (".." stays as it is).

func StringToTTL

func StringToTTL(s string) (uint32, bool)

StringToTTL parses a TTL: a decimal number of seconds, or a BIND duration made of <digits><unit> parts with units s, m, h, d and w in any case, optionally ending in bare digits (seconds), such as "1h30m", "2W" or "90". The value must fit in 32 bits. It reports false for anything else, including an empty string.

func UserComment

func UserComment(c string) string

UserComment returns the user-written part of a serial comment generated by this package: "; serial <rendering> ; note" gives "; note" and a comment holding only the generated text gives "". Any other comment is returned unchanged. Verification uses it so that regenerated renderings do not count as lost comments.

func Verify

func Verify(apex string, plain, formatted []byte) error

Verify proves that formatted is a safe replacement for plain (both renderings of the same entries, see WritePlain and Format): both must parse with the reference parser, hold the same multiset of records (SOA serial ignored), and every user comment of plain (see UserComment) must occur in a comment of formatted. Errors wrap ErrUnsafeWrite and list lost ("-") and added ("+") records.

func WritePlain added in v1.0.0

func WritePlain(w io.Writer, apex string, entries []Entry) error

WritePlain writes the entries without alignment: fields separated by one space, owners relative to the $ORIGIN in force, values as written, multi-line only when a comment position requires it, comments in place. It is deliberately simple and is the reference that Verify checks Format against. Its exact spacing is not part of the contract.

Types

type Entry

type Entry struct {
	// contains filtered or unexported fields
}

Entry is one zone file entry: a blank line, a comment line, a directive or a record. Entries are immutable values: methods that change an entry return a new one, so copies never alias each other and can be inserted into slices and compared freely. The zero Entry is a blank line.

func NewRecord

func NewRecord(owner string, ttl uint32, rtype uint16, rdata string) (Entry, error)

NewRecord builds a Record entry "owner [ttl] IN TYPE rdata" from parts, without rendering text and parsing it back. A ttl of 0 means no explicit TTL. This is a security boundary for values coming from network clients: the result is always exactly one record, never a directive, a comment or a second record.

  • owner must tokenise as exactly one unquoted item and must not start with "$".
  • For TXT and SPF, rdata is one raw character-string that becomes one quoted field (see Quote); it is not split at 255 bytes.
  • For other types, rdata must tokenise into one or more items and quoted strings. Line terminators, ";", "(", ")", NUL and unterminated quotes are rejected. Quoted fields stay quoted as written.
  • rtype must be in the type table.

Errors wrap ErrInvalidRecord.

func (Entry) Args

func (e Entry) Args() []Field

Args returns a copy of a Directive entry's arguments.

func (Entry) Class

func (e Entry) Class() string

Class returns a Record entry's class, upper-cased, or "" when absent.

func (Entry) Comment

func (e Entry) Comment() string

Comment returns the text of a Comment entry including the leading ";".

func (Entry) Comments

func (e Entry) Comments() []string

Comments returns every comment attached to the entry in file order: the comment of a Comment entry, or head, per-field and tail comments.

func (Entry) Content

func (e Entry) Content() string

Content returns the record's content in the PowerDNS form: for TXT and SPF the concatenation of the decoded strings, for every other type the decoded fields joined by one space.

func (Entry) Equal

func (e Entry) Equal(o Entry) bool

Equal compares two entries literally: same kind, and for records the same owner (ASCII case-insensitive), class, type, decoded values and the same explicit TTL in seconds (or both absent). Comments and the written form of values are ignored. Directives compare by name and decoded arguments; comment entries by text.

func (Entry) Fields

func (e Entry) Fields() []Field

Fields returns a copy of a Record entry's rdata fields.

func (Entry) Head

func (e Entry) Head() []string

Head returns a copy of the comments between "(" and the first field.

func (Entry) Kind

func (e Entry) Kind() Kind

Kind returns the entry kind.

func (Entry) Line

func (e Entry) Line() int

Line returns the 1-based line of the entry's first line in the parsed text, or 0 for entries built by hand.

func (Entry) Name

func (e Entry) Name() string

Name returns a Directive entry's name, upper-cased, such as "$ORIGIN".

func (Entry) Owner

func (e Entry) Owner() string

Owner returns a Record entry's owner as written (or as set by File.ResolveOwners); "" when the owner is inherited and unresolved.

func (Entry) RType

func (e Entry) RType() uint16

RType returns a Record entry's numeric type.

func (Entry) RawValues

func (e Entry) RawValues() []string

RawValues returns a Record entry's rdata fields as written, or a Directive entry's arguments as written.

func (Entry) TTL

func (e Entry) TTL() (TTL, bool)

TTL returns a Record entry's explicit TTL and whether it has one.

func (Entry) Tail

func (e Entry) Tail() []string

Tail returns a copy of the comments after the last field or at the end of the entry's line.

func (Entry) Type

func (e Entry) Type() string

Type returns a Record entry's type name, upper-cased ("A", "TYPE65280").

func (Entry) Values

func (e Entry) Values() []string

Values returns a Record entry's rdata fields decoded (escapes resolved, quotes removed), or a Directive entry's decoded arguments.

func (Entry) WithComments

func (e Entry) WithComments(comments ...string) Entry

WithComments returns a copy of the entry with the comments appended to its last line as one comment joined by single spaces. Comments without a leading ";" get one. Empty comments are ignored.

func (Entry) WithOwner

func (e Entry) WithOwner(name string) Entry

WithOwner returns a copy of a Record entry with its owner replaced; an empty name clears the owner so it is inherited. It is the caller's job to pass a single owner item (see NewRecord).

type Field

type Field struct {
	Raw      string   // as written, including quotes and escapes
	Value    string   // decoded: escapes resolved, quotes removed
	Quoted   bool     // written as a quoted string
	Comments []string // comments following this field, before the next
}

Field is one rdata field or directive argument.

type File added in v1.0.0

type File struct {
	Entries []Entry
}

File is a parsed zone file: a flat list of entries in file order. Trailing bytes that formed no entry are dropped; nothing else is lost.

func Parse

func Parse(data []byte) (*File, error)

Parse parses zone file text. The result keeps every entry in order with its comments; owners are as written (see File.ResolveOwners). Errors are *ParseError values naming the line. Parse never panics on any input and performs no I/O.

func (*File) Origin added in v1.0.0

func (f *File) Origin(hint string) (string, *Entry)

Origin returns the apex of the file: the owner of the first SOA record, resolved against the $ORIGIN in force (the hint before any $ORIGIN), together with that record. Without an SOA the apex is the first $ORIGIN, else the hint made absolute, else ""; the record is then nil. A bare "@" SOA owner with no origin at all yields "".

func (*File) Records added in v1.0.0

func (f *File) Records() iter.Seq2[int, Entry]

Records iterates over the Record entries of the file with their index in Entries.

func (*File) ResolveOwners added in v1.0.0

func (f *File) ResolveOwners(apex string) error

ResolveOwners rewrites every record so that its owner is explicit and relative to apex: "@" for the apex, "www" for www.<apex>, the absolute name for anything outside the apex. Inherited (blank) owners are filled from the previous record; a record with nothing to inherit is left ownerless. Afterwards an entry's meaning no longer depends on its position or on $ORIGIN lines. It fails when "@" is used with no origin known.

type Kind

type Kind int

Kind classifies an entry.

const (
	Blank     Kind = iota // an empty line (runs are collapsed into one)
	Comment               // a line holding only a comment
	Directive             // $ORIGIN, $TTL, $INCLUDE or $GENERATE
	Record                // a resource record
)

Entry kinds.

func (Kind) String

func (k Kind) String() string

String returns the kind's name.

type ParseError

type ParseError struct {
	Line int
	Col  int
	Msg  string
}

ParseError describes a syntax error in zone file text. Line and Col are 1-based; Col counts bytes from the start of the line.

func (*ParseError) Error

func (e *ParseError) Error() string

Error implements the error interface as "line L: msg".

type TTL

type TTL struct {
	Raw     string // as written, e.g. "1h" or "3600"
	Seconds uint32
}

TTL is a time-to-live as written in a zone file together with its value in seconds.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL