wire

package
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Aug 30, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package wire implements DNS message encoding and decoding from RFC 1035 sections 3 and 4, including name compression in both directions.

Decoding never trusts the header counts. Each count is an upper bound on a loop, and every read is bounded by the octets actually present, so a message claiming 65535 answers in 40 octets fails with a typed error rather than allocating.

Index

Constants

View Source
const (
	RcodeSuccess  uint8 = 0
	RcodeFormErr  uint8 = 1
	RcodeServFail uint8 = 2
	RcodeNXDomain uint8 = 3
	RcodeNotImp   uint8 = 4
	RcodeRefused  uint8 = 5
)

Response codes from RFC 1035 section 4.1.1.

View Source
const DefaultUDPSize uint16 = 1232

DefaultUDPSize is the requestor's UDP payload size this implementation advertises, in octets.

1232 is the current operational recommendation: it keeps a response inside the smallest MTU in common use once IPv6 and UDP headers are accounted for, so a large answer arrives whole rather than as IP fragments that middleboxes and firewalls routinely drop. Measured on this network, 1232 carries the full 828-octet com. NS response intact, while the same query without EDNS0 comes back capped at 509 octets with TC set.

Variables

View Source
var (
	ErrTruncated      = errors.New("wire: message truncated")
	ErrLabelType      = errors.New("wire: reserved label type")
	ErrLabelTooLong   = errors.New("wire: label exceeds 63 octets")
	ErrNameTooLong    = errors.New("wire: name exceeds 255 octets")
	ErrNameSyntax     = errors.New("wire: malformed name")
	ErrBadPointer     = errors.New("wire: compression pointer does not point strictly backwards")
	ErrTooManyRecords = errors.New("wire: section exceeds 65535 records")
	ErrRData          = errors.New("wire: malformed rdata")
)

Decode and encode failures. Every malformed message returns one of these, wrapped with the offset or field that failed, so callers and tests can match on the class of failure with errors.Is.

Functions

func RcodeName

func RcodeName(rcode uint8) string

RcodeName renders a response code the way dig prints it.

Here rather than beside the presentation code because there are now two consumers, the CLI output and the control socket, and two switches over the same six constants is one switch too many: the day a seventh is added, one of them gets it.

Types

type A

type A struct{ Addr netip.Addr }

A is an IPv4 host address, RFC 1035 section 3.4.1.

func (A) Type

func (A) Type() Type

type AAAA

type AAAA struct{ Addr netip.Addr }

AAAA is an IPv6 host address, RFC 3596.

func (AAAA) Type

func (AAAA) Type() Type

type CNAME

type CNAME struct{ Target Name }

CNAME is the canonical name of an alias, RFC 1035 section 3.3.1.

func (CNAME) Type

func (CNAME) Type() Type

type Class

type Class uint16

Class is a DNS class code.

const ClassIN Class = 1

ClassIN is the internet class, the only one this implementation serves.

type EDNS

type EDNS struct {
	// UDPSize is the largest response the sender will accept over UDP. Zero
	// packs as DefaultUDPSize, since advertising no capacity at all is never
	// what a caller means.
	UDPSize uint16

	// ExtRcode is the high 8 bits of the 12-bit extended RCODE. The low 4
	// bits stay in the message header, so the full code is
	// uint16(ExtRcode)<<4 | Rcode.
	ExtRcode uint8

	// Version is the EDNS version, 0 for RFC 6891.
	Version uint8

	// DO is the DNSSEC OK bit, RFC 3225.
	DO bool

	Options []Option
}

EDNS is the whole OPT pseudo-record, gathering the fields RFC 6891 scatters across the record's CLASS and TTL.

func (EDNS) RR

func (e EDNS) RR() RR

RR builds the OPT pseudo-record. The owner name is the root, which is the only name RFC 6891 permits.

type Header struct {
	ID                 uint16
	Response           bool
	Opcode             uint8
	Authoritative      bool
	Truncated          bool
	RecursionDesired   bool
	RecursionAvailable bool
	AuthenticData      bool
	CheckingDisabled   bool
	Rcode              uint8
}

Header is the 12-octet message header. The section counts are not stored, because they are a function of the sections themselves and a Header that can disagree with its own message is a bug waiting to happen.

type MX

type MX struct {
	Preference uint16
	Exchange   Name
}

MX is a mail exchange, RFC 1035 section 3.3.9.

func (MX) Type

func (MX) Type() Type

type Message

type Message struct {
	Header     Header
	Questions  []Question
	Answers    []RR
	Authority  []RR
	Additional []RR
}

Message is a decoded DNS message.

func Unpack

func Unpack(msg []byte) (*Message, error)

Unpack decodes a DNS message. The returned RRs alias msg; see RR.

func (*Message) EDNS

func (m *Message) EDNS() (EDNS, bool, error)

EDNS returns the OPT pseudo-record from the additional section, reporting whether one was present.

RFC 6891 section 6.1.1 permits at most one, and a message carrying more is a format error rather than something to resolve by picking a winner.

func (*Message) Pack

func (m *Message) Pack() ([]byte, error)

Pack encodes a message, compressing names against those already written.

func (*Message) SetEDNS

func (m *Message) SetEDNS(e EDNS)

SetEDNS installs the OPT pseudo-record, replacing any already present.

type NS

type NS struct{ Host Name }

NS is an authoritative nameserver for a zone, RFC 1035 section 3.3.11.

func (NS) Type

func (NS) Type() Type

type Name

type Name string

Name is a domain name in presentation format, fully qualified with a trailing dot. The root is ".".

Case is preserved exactly as it arrived. Names match case-insensitively, which is what EqualFold is for, but decoding never folds case: the 0x20 defence depends on a response echoing the exact case that was sent.

Octets outside the printable ASCII range, along with '.' and '\\', are escaped in the RFC 1035 section 5.1 style, so the presentation form is unambiguous and two Names are byte-equal only when the wire names were byte-equal.

const Root Name = "."

Root is the zero-length name, encoded as a single zero octet.

func ParseName

func ParseName(s string) (Name, error)

ParseName converts a presentation-format name to a Name, accepting both "example.com" and "example.com." and canonicalising to the latter.

func (Name) EqualFold

func (n Name) EqualFold(other Name) bool

EqualFold reports whether two names are equal under DNS case-insensitive matching.

func (Name) Fold

func (n Name) Fold() Name

Fold returns n lowercased, for use as a map key where DNS case-insensitive matching is wanted but a comparison per candidate is too slow.

The result is a key and nothing else. It must never be stored in a record or written to the wire, because the 0x20 defence depends on the case that arrived surviving intact.

Lowercasing the presentation string is sound because that string is always printable ASCII: appendEscaped escapes every octet outside '!' to '~', so there is no multi-byte sequence for strings.ToLower to fold differently from the ASCII rule RFC 4343 specifies. Escaped octets are unaffected for the same reason, since a letter is printable and therefore never escaped in the first place.

func (Name) String

func (n Name) String() string

func (Name) Suffixes

func (n Name) Suffixes() []Name

Suffixes returns n followed by each of its parent names, from the name itself down to its final label. The root is not included, and neither the root nor a name that fails to parse yields anything.

For "a.b.example.com." the result is "a.b.example.com.", "b.example.com.", "example.com.", "com.". Each is a slice of n's own storage, so the walk allocates once for the slice and never for the names.

This exists because a suffix match over the raw string is wrong in the way that matters. Dots inside a label are escaped, so "evil\.com." is a single label whose bytes end in "com." and which must not be treated as living under com. Splitting on unescaped dots only, it yields itself and nothing else.

func (Name) Within

func (n Name) Within(zone Name) bool

Within reports whether n lies inside zone, either equal to it or below it. Every name is within the root.

This compares labels, not bytes, and the difference is a security one. A label may contain a dot, escaped as "\." in presentation form, so a suffix test over the string would read the single label "evil\.com" as ending in the label "com" and place it inside the com zone. That is exactly the input a bailiwick check exists to reject, so the slower comparison is the only correct one. A name that does not parse is within nothing, which keeps the failure closed rather than open.

type OPT

type OPT struct{ Options []Option }

OPT is the rdata of the EDNS0 pseudo-record, a bare sequence of options.

The rest of what EDNS0 carries lives outside the rdata, in fields the pseudo-record repurposes: CLASS holds the requestor's payload size and TTL holds the extended RCODE, the version and the DO bit. EDNS presents all of it as one value, and is what callers should use.

func (OPT) Type

func (OPT) Type() Type

type Option

type Option struct {
	Code uint16
	Data []byte
}

Option is one EDNS0 option, RFC 6891 section 6.1.2.

Data aliases the message buffer it was decoded from; see Unknown.

type PTR

type PTR struct{ Target Name }

PTR is a pointer to another name, RFC 1035 section 3.3.12. Unlike CNAME it triggers no further processing at the resolver.

func (PTR) Type

func (PTR) Type() Type

type Question

type Question struct {
	Name  Name
	Type  Type
	Class Class
}

Question is an entry in the question section.

type RData

type RData interface {
	// Type reports the record type whose RDATA this is.
	Type() Type
	// contains filtered or unexported methods
}

RData is the parsed contents of a resource record's RDATA field. Every RR carries one. A type this package does not model decodes as Unknown, which keeps the octets verbatim so that an unfamiliar record still round-trips.

pack is unexported, so the set of implementations is closed to this package. Encoding a record means knowing whether the names inside it may be replaced by a compression pointer, and that is a property of the record type rather than something a caller should be able to supply.

type RR

type RR struct {
	Name  Name
	Type  Type
	Class Class
	TTL   int32
	Data  RData
}

RR is a resource record. The rdata is parsed into a per-type value from rdata.go, and a type this package does not model keeps its octets as Unknown, so an unfamiliar record still round-trips.

Type is kept alongside Data rather than derived from it, so that reading the type of a record never depends on the rdata being present. Pack rejects a record whose two disagree.

type SOA

type SOA struct {
	Primary Name // MNAME, the authoritative server for the zone
	Mailbox Name // RNAME, the maintainer's mailbox with '.' standing in for '@'
	Serial  uint32
	Refresh uint32
	Retry   uint32
	Expire  uint32
	Minimum uint32 // also the negative caching TTL, RFC 2308 section 4
}

SOA marks the start of a zone of authority, RFC 1035 section 3.3.13.

func (SOA) Type

func (SOA) Type() Type

type SRV

type SRV struct {
	Priority uint16
	Weight   uint16
	Port     uint16
	Target   Name
}

SRV locates the host serving a protocol, RFC 2782.

func (SRV) Type

func (SRV) Type() Type

type Span

type Span struct {
	Offset  int
	Length  int
	Section string // header, question, answer, authority, additional, or trailing
	Field   string // ID, flags, QNAME, TTL, RDATA, and so on
	Detail  string // the decoded value, in the terms of the field
}

Span is one field of a message, in the order the decoder read it.

Spans are contiguous and cover the whole buffer: the offsets of any two neighbours meet exactly, and the last one ends at the final octet. That is the property that makes an annotated dump worth anything, because a region nobody can name is a region the decoder was not really reading.

func Annotate

func Annotate(msg []byte) ([]Span, error)

Annotate walks a raw message and returns a span per field.

It reads with the same decoder the resolver reads with, field by field and in the same order, so an annotation is a record of what the parser did rather than a second opinion about what the bytes mean. A message that fails to decode returns the spans read so far along with the error, because the field the parser stopped on is usually the whole answer to why.

func (Span) End

func (s Span) End() int

End is the offset one past the last octet of the span.

type TXT

type TXT struct{ Strings []string }

TXT is one or more character-strings, RFC 1035 section 3.3.14.

The strings stay separate. One TXT record can carry several, each capped at 255 octets, and joining them destroys a boundary that SPF and DKIM both depend on.

func (TXT) Type

func (TXT) Type() Type

type Type

type Type uint16

Type is a DNS resource record type code.

const (
	TypeA     Type = 1
	TypeNS    Type = 2
	TypeCNAME Type = 5
	TypeSOA   Type = 6
	TypePTR   Type = 12
	TypeMX    Type = 15
	TypeTXT   Type = 16
	TypeAAAA  Type = 28
	TypeSRV   Type = 33
	TypeOPT   Type = 41
	TypeANY   Type = 255
)

Record types carried by this implementation. Types outside this set decode successfully with opaque RDATA, because a resolver that rejects an unfamiliar type is broken.

func ParseType

func ParseType(s string) (Type, error)

ParseType is the inverse of Type.String. It accepts the mnemonics above, case-insensitively, and the RFC 3597 "TYPE65535" form for everything else, so a type this package does not model can still be asked for by number.

func (Type) String

func (t Type) String() string

type Unknown

type Unknown struct {
	Kind Type
	Data []byte
}

Unknown is the rdata of a type this package does not model, held verbatim because a resolver that rejects an unfamiliar record is broken.

Data aliases the message buffer it was decoded from, so a caller that keeps the record beyond the life of that buffer must copy it first. Every other RData implementation owns its contents, apart from Option.Data.

func (Unknown) Type

func (u Unknown) Type() Type

Jump to

Keyboard shortcuts

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