Documentation
¶
Overview ¶
Package dnsmsg implements the DNS message format defined by RFC 1035 and its many successors.
It is the foundation of the GatewayDNS engine: every other package speaks in terms of the types declared here. The package is deliberately self-contained and has no dependencies outside the Go standard library, so that it can be vendored, audited, or reused on its own.
Design ¶
The package models the wire format faithfully rather than presenting a convenience abstraction over it. A Message contains a Header and four sections; resource records are RR values whose payload is an RData implementation selected by Type. Nothing is hidden, because a DNS proxy has to be able to forward records it does not itself understand.
Unknown record types are preserved verbatim as Unknown per RFC 3597, so a GatewayDNS server can relay record types that did not exist when it was compiled.
Names ¶
Name stores the uncompressed wire encoding of a domain name in an immutable string. This makes names comparable, usable as map keys, and cheap to write back to the wire (a single copy) at the cost of converting to presentation format on demand. Because a caching resolver reads names far more often than it prints them, this trade is the right way round.
Names compare case-insensitively per RFC 4343 but preserve the case they were received in, which is what makes DNS-0x20 query randomisation possible.
Errors ¶
Two error types divide every failure by whose fault it is, because a server answers the two cases differently. SyntaxError means the input did not parse: drop the packet, answer FORMERR, move on. EncodeError means the Message the program itself assembled cannot be put on the wire: that is a bug, and no retry fixes it. Test with IsDecodeError and IsEncodeError rather than by type assertion. The sentinel errors do not carry that distinction — most of them, ErrInvalidName among them, describe a condition reachable from either direction — so errors.Is answers "what went wrong" and the predicates answer "who broke it".
Allocation behaviour ¶
Encoding appends into a caller-supplied buffer and allocates nothing else. The Encoder, including its compression table, is pooled internally, and compression keys alias the Name's own immutable string. Measured with testing.AllocsPerRun into a warm 4 KiB buffer: zero allocations per Pack for a query, for a query carrying an OPT record, and for a ten-record response. The only growth left is the caller's buffer, which stops growing once it has seen its largest message.
Decoding allocates one string per domain name and one value per RDATA payload, and nothing else once a Message has been reused: section slices keep their capacity across Message.Reset. Pair Decoder.Reset with Message.Decode to reuse the decoder too — that is the only entry point that accepts a caller-owned Decoder, and it is worth using because a Decoder embeds a 255-octet name buffer. Measured: a 33-octet single-question query costs 2 allocations through Message.Unpack and 1 through Decode; an 81-octet response with one question and three A records costs 8 and 7 respectively — four names and three payloads, plus the Decoder that Decode does not allocate.
Names, not records, dominate. See UnpackOptions.MaxNameOctets for what that means when the sender is hostile.
Concurrency ¶
Name, Type, Class, RCode and Opcode are immutable value types and are safe for concurrent use. Message, RR, Decoder and Encoder are not safe for concurrent mutation; a message that has been fully decoded may be read concurrently provided nothing writes to it. Registry is safe for concurrent use.
Robustness ¶
Every decoding path is bounded. Compression pointers must point strictly backwards, names are capped at 255 octets and 127 labels, input is capped at MaxMessageSize, and each record's payload is confined to its declared RDLENGTH. The decoder is continuously fuzz-tested; see FuzzUnpack.
Bounded is not the same as cheap. Decompression can turn a two-octet pointer into a 255-octet name, so a hostile 64 KiB message can cost roughly 48 times its own size in heap. That ratio is a property of RFC 1035 compression rather than of this implementation — a conforming message can reach a similar figure — so it is not capped by default; UnpackOptions.MaxNameOctets caps it for deployments that know their own traffic.
Index ¶
- Constants
- Variables
- func IsDecodeError(err error) bool
- func IsEncodeError(err error) bool
- type A
- type AAAA
- type CAA
- type CDNSKEY
- type CDS
- type CERT
- type CNAME
- type CSYNC
- type Class
- type DHCID
- type DLV
- type DNAME
- type DNSKEY
- type DS
- type DecodeFunc
- type Decoder
- func (d *Decoder) Bytes(n int) ([]byte, error)
- func (d *Decoder) CharacterString() (string, error)
- func (d *Decoder) Header() (Header, [4]uint16, error)
- func (d *Decoder) IPv4() (netip.Addr, error)
- func (d *Decoder) IPv6() (netip.Addr, error)
- func (d *Decoder) Name() (Name, error)
- func (d *Decoder) NameUncompressed() (Name, error)
- func (d *Decoder) Offset() int
- func (d *Decoder) Question() (Question, error)
- func (d *Decoder) RR() (RR, error)
- func (d *Decoder) Remaining() int
- func (d *Decoder) Reset(msg []byte, opts *UnpackOptions)
- func (d *Decoder) Skip(n int) error
- func (d *Decoder) Uint8() (uint8, error)
- func (d *Decoder) Uint16() (uint16, error)
- func (d *Decoder) Uint32() (uint32, error)
- func (d *Decoder) Uint48() (uint64, error)
- type EDNS
- type EDNSClientSubnet
- type EDNSCookie
- type EDNSExtendedError
- type EDNSNSID
- type EDNSOption
- type EDNSOptionCode
- type EDNSPadding
- type EDNSTCPKeepalive
- type EDNSUnknown
- type EUI48
- type EUI64
- type EncodeError
- type Encoder
- func (e *Encoder) AppendAddr(dst []byte, a netip.Addr) []byte
- func (e *Encoder) AppendCharacterString(dst []byte, s string) []byte
- func (e *Encoder) AppendName(dst []byte, name Name) []byte
- func (e *Encoder) AppendNameUncompressed(dst []byte, name Name) []byte
- func (e *Encoder) AppendUint8(dst []byte, v uint8) []byte
- func (e *Encoder) AppendUint16(dst []byte, v uint16) []byte
- func (e *Encoder) AppendUint32(dst []byte, v uint32) []byte
- func (e *Encoder) AppendUint48(dst []byte, v uint64) []byte
- func (e *Encoder) Reset(base int, compress bool)
- type ExtendedErrorCode
- type HINFO
- type HTTPS
- type Header
- type KX
- type L32
- type L64
- type LOC
- type LP
- type MINFO
- type MX
- type Message
- func (m *Message) AppendPack(dst []byte, opts *PackOptions) ([]byte, error)
- func (m *Message) Copy() *Message
- func (m *Message) Decode(d *Decoder) error
- func (m *Message) EDNS() (*EDNS, bool)
- func (m *Message) Len() (int, error)
- func (m *Message) Pack() ([]byte, error)
- func (m *Message) Question() (Question, bool)
- func (m *Message) Reset()
- func (m *Message) Section(s Section) []RR
- func (m *Message) SetEDNS(e *EDNS)
- func (m *Message) SetEDNSDefaults(do bool)
- func (m *Message) SetExtendedError(code ExtendedErrorCode, text string)
- func (m *Message) SetQuestion(name Name, qtype Type, qclass Class) *Message
- func (m *Message) SetRCode(rc RCode) *Message
- func (m *Message) SetReply(req *Message) *Message
- func (m *Message) String() string
- func (m *Message) UDPSize() uint16
- func (m *Message) Unpack(b []byte, opts *UnpackOptions) error
- type NAPTR
- type NID
- type NS
- type NSEC
- type NSEC3
- type NSEC3PARAM
- type NULL
- type Name
- func (n Name) AppendWire(dst []byte) []byte
- func (n Name) Canonical() Name
- func (n Name) Compare(other Name) int
- func (n Name) Equal(other Name) bool
- func (n Name) IsCanonical() bool
- func (n Name) IsRoot() bool
- func (n Name) IsSubDomainOf(parent Name) bool
- func (n Name) IsZero() bool
- func (n Name) Label(i int) []byte
- func (n Name) LabelCount() int
- func (n Name) LabelString(i int) (string, bool)
- func (n Name) Labels() []string
- func (n Name) MarshalText() ([]byte, error)
- func (n Name) Parent() (Name, bool)
- func (n Name) Prepend(label []byte) (Name, error)
- func (n Name) String() string
- func (n *Name) UnmarshalText(text []byte) error
- func (n Name) WireLen() int
- type OPENPGPKEY
- type OPT
- type Opcode
- type PTR
- type PackOptions
- type Question
- type RCode
- type RData
- type RP
- type RR
- type RRSIG
- type Registry
- type SMIMEA
- type SOA
- type SPF
- type SRV
- type SSHFP
- type SVCB
- type Section
- type SvcParam
- type SvcParamKey
- type SvcParamValueALPN
- type SvcParamValueDoHPath
- type SvcParamValueECH
- type SvcParamValueIPv4Hint
- type SvcParamValueIPv6Hint
- type SvcParamValueMandatory
- type SvcParamValueNoDefaultALPN
- type SvcParamValueOHTTP
- type SvcParamValuePort
- type SvcParamValueUnknown
- type SyntaxError
- type TLSA
- type TXT
- type Type
- type URI
- type Unknown
- type UnpackOptions
- type Validator
- type ZONEMD
Examples ¶
Constants ¶
const ( // HeaderLen is the fixed size of a DNS message header. HeaderLen = 12 // MaxLabelLen is the largest permitted single label, in octets. MaxLabelLen = 63 // MaxNameWireLen is the largest permitted wire encoding of a domain name, // including all length octets and the terminating root label. MaxNameWireLen = 255 // MaxLabels is the largest number of labels a name may contain, excluding // the root. It follows from MaxNameWireLen with single-octet labels. MaxLabels = 127 // MaxCharStringLen is the largest permitted <character-string>. MaxCharStringLen = 255 // MinUDPSize is the payload size every DNS implementation must accept over // UDP without EDNS (RFC 1035 section 4.2.1). MinUDPSize = 512 // DefaultUDPSize is the advertised EDNS(0) payload size GatewayDNS uses by // default. 1232 avoids IPv6 fragmentation, which is widely dropped, and is // the value recommended by the DNS Flag Day 2020 consensus. DefaultUDPSize = 1232 // MaxMessageSize is the largest message expressible over TCP, bounded by // the two-octet length prefix of RFC 1035 section 4.2.2. MaxMessageSize = 65535 )
Protocol limits fixed by RFC 1035 and RFC 6891.
const EDNS0Version uint8 = 0
EDNS0Version is the only version of EDNS defined.
Variables ¶
var ( // ErrTruncated reports that the input ended before a complete structure // could be read. It does not refer to the header TC bit. ErrTruncated = errors.New("dnsmsg: unexpected end of message") // ErrBadPointer reports a malformed or hostile compression pointer: one // that does not point strictly backwards, or that exceeds the pointer // budget. Enforcing backwards-only pointers makes decompression loops // impossible by construction. ErrBadPointer = errors.New("dnsmsg: invalid compression pointer") // ErrBadLabel reports a label whose two high bits are 0b01 or 0b10, which // RFC 1035 reserves and RFC 6891 did not allocate. ErrBadLabel = errors.New("dnsmsg: reserved label type") // ErrNameTooLong reports a domain name whose wire encoding exceeds // MaxNameWireLen octets. ErrNameTooLong = errors.New("dnsmsg: domain name exceeds 255 octets") // ErrLabelTooLong reports a label longer than MaxLabelLen octets. ErrLabelTooLong = errors.New("dnsmsg: label exceeds 63 octets") // ErrEmptyLabel reports an empty label in a position other than the root, // which would make the name ambiguous. ErrEmptyLabel = errors.New("dnsmsg: empty label") // ErrBadEscape reports a malformed backslash escape in presentation format. ErrBadEscape = errors.New("dnsmsg: invalid escape sequence") // ErrInvalidName reports a name that is structurally unusable, such as the // zero Name. ErrInvalidName = errors.New("dnsmsg: invalid domain name") // ErrRDataLength reports an RDATA payload whose contents do not consume // exactly RDLENGTH octets. Trailing or missing bytes indicate either a // corrupt message or a decoder bug, and are never safe to ignore. ErrRDataLength = errors.New("dnsmsg: RDATA length mismatch") // ErrTypeMismatch reports an RR whose Type field disagrees with the type // reported by its RData. ErrTypeMismatch = errors.New("dnsmsg: RR type does not match RData type") // ErrBufferTooSmall reports that a message could not be encoded within the // requested size limit even after truncation. ErrBufferTooSmall = errors.New("dnsmsg: message does not fit in size limit") // ErrNoOPT reports an attempt to encode an extended RCODE (> 15) or other // EDNS-only state on a message that carries no OPT record. ErrNoOPT = errors.New("dnsmsg: extended RCODE requires an OPT record") // ErrRCodeRange reports a response code above 4095, which the twelve bits // available across the header and the OPT record cannot express. It is // returned rather than silently masking the value, because a masked RCODE // means a different response than the caller asked for. ErrRCodeRange = errors.New("dnsmsg: response code exceeds 12 bits") // ErrMultipleOPT reports a message carrying more than one OPT record, which // RFC 6891 forbids. ErrMultipleOPT = errors.New("dnsmsg: message contains more than one OPT record") // ErrBadOPT reports a structurally invalid OPT record, such as one whose // owner name is not the root. ErrBadOPT = errors.New("dnsmsg: malformed OPT record") // ErrSectionOverflow reports a section count that cannot be satisfied by // the remaining input. It is returned eagerly so that a tiny hostile // message cannot cause a large allocation. ErrSectionOverflow = errors.New("dnsmsg: section count exceeds remaining message") // ErrCharStringTooLong reports a <character-string> longer than 255 octets. ErrCharStringTooLong = errors.New("dnsmsg: character-string exceeds 255 octets") // ErrAddressFamily reports an address whose family does not match the record // type holding it, such as an IPv6 address in an A record. Encoding one // would produce a record of the wrong RDLENGTH that no conforming resolver // will accept, so it is refused rather than written. ErrAddressFamily = errors.New("dnsmsg: address family does not match record type") // ErrNameBudget reports a message whose decompressed domain names exceed // [UnpackOptions.MaxNameOctets]. It is a resource decision rather than a // format violation: the message may be perfectly well formed and simply // more expensive than the operator agreed to spend on it. It is reported as // a decode failure because the response is the same — stop reading this // packet — but it is the one such failure that does not justify FORMERR, // since nothing about the message is malformed. ErrNameBudget = errors.New("dnsmsg: decompressed names exceed the configured budget") // ErrMessageTooLong reports an input buffer larger than [MaxMessageSize]. // No DNS message can exceed that, because RFC 1035 section 4.2.2 frames a // message with a two-octet length. Rejecting the buffer up front keeps the // decoder's cost bounded by a constant a caller cannot raise by mistake. ErrMessageTooLong = errors.New("dnsmsg: message exceeds 65535 octets") )
Sentinel errors returned by this package. Callers should test with errors.Is rather than comparing directly, because errors are frequently wrapped with positional context.
Most sentinels describe a condition that can arise on either side of the wire: ErrInvalidName means a peer sent a nonsense name when it comes out of Unpack and means the local program built one when it comes out of Message.AppendPack. The sentinel therefore does not tell a server what to do. The wrapping type does: see IsDecodeError and IsEncodeError.
Functions ¶
func IsDecodeError ¶
IsDecodeError reports whether err describes input this package could not parse, which is the check a server makes to decide that a packet is the peer's fault.
It is the condition under which the correct response is to stop processing the message and, if the header was intelligible enough to reply at all, return FORMERR. Prefer it to a type assertion on SyntaxError: this package reserves the right to report malformed input with additional types, and a predicate keeps that from becoming a breaking change.
func IsEncodeError ¶
IsEncodeError reports whether err describes a Message the local program built that cannot be represented on the wire.
It is never caused by the network, so a server should treat it as it treats a panic-worthy bug: log it with the message that produced it and answer SERVFAIL. Retrying, or forwarding the failure upstream as FORMERR, only hides which side is broken.
Whole-message failures that belong to no single record — ErrSectionOverflow, ErrRCodeRange, ErrNoOPT and ErrBufferTooSmall — are returned bare by Message.AppendPack and are encode failures too; match them with errors.Is.
Types ¶
type A ¶
A is an IPv4 host address (RFC 1035 section 3.4.1).
func (*A) AppendWire ¶
AppendWire implements RData.
func (*A) Validate ¶
Validate implements Validator. An A record's RDATA is exactly four octets, so an address that is not IPv4 — including the zero netip.Addr — cannot be encoded. Refusing it here turns a silently malformed record into an error at the point the mistake was made.
type AAAA ¶
AAAA is an IPv6 host address (RFC 3596).
func (*AAAA) AppendWire ¶
AppendWire implements RData.
type CAA ¶
type CAA struct {
// Flag is the flags octet. Only the issuer critical bit (0x80) is defined;
// see [CAA.Critical].
Flag uint8
// Tag is the property name, one to fifteen alphanumeric octets, compared
// case-insensitively by consumers.
Tag string
// Value is the property value, occupying the remainder of the RDATA. It is
// octets rather than text because RFC 8659 leaves its interpretation to the
// property named by Tag.
Value []byte
}
CAA authorises certificate issuance for a domain (RFC 8659).
CAA is consulted by certificate authorities at issuance time, which makes it the one record type in this package whose misparsing has a direct security consequence: a tag that differs from "issue" only by an unexpected octet must not be mistaken for it. The decoder therefore enforces the RFC 8659 section 4.1.1 tag grammar rather than accepting whatever the wire carries.
func (*CAA) AppendWire ¶
AppendWire implements RData.
func (*CAA) Critical ¶
Critical reports whether the issuer critical flag is set, which requires a certificate authority that does not understand CAA.Tag to refuse issuance rather than ignore the property (RFC 8659 section 4.1.1).
type CDNSKEY ¶
CDNSKEY is a Child DNSKEY record (RFC 7344): the key a child zone wants its parent to build a DS from. Its wire format is identical to DNSKEY.
RFC 8078 section 4 gives one special case worth knowing: a CDNSKEY whose entire RDATA is a single zero octet is the child signalling that it wishes to go insecure. That decodes here as Flags 0, Protocol 0, Algorithm 0 with an empty key only if RDLENGTH is 4; the true "delete" form is shorter than the fixed header and so is preserved as Unknown, which is the honest outcome for a payload that is not a key at all.
func (*CDNSKEY) AppendWire ¶
AppendWire implements RData.
func (*CDNSKEY) KeyTag ¶
KeyTag returns the RFC 4034 Appendix B key tag, computed exactly as for DNSKEY.KeyTag. It is provided because building the DS record a CDNSKEY asks for requires the tag, which is the whole purpose of the type.
type CDS ¶
CDS is a Child DS record (RFC 7344): the DS the child zone would like its parent to publish. It is identical to DS on the wire and differs only in who is authoritative for it and what a parent is expected to do about it.
func (*CDS) AppendWire ¶
AppendWire implements RData. CDS shares DS's field layout exactly, so the conversion is free and the encoding cannot drift between the two types.
type CERT ¶
type CERT struct {
// CertType selects what Certificate holds: PKIX, SPKI, PGP, an URL form, and
// so on. It is not named Type because [RData] requires a Type method and Go
// forbids a struct having both a field and a method of one name.
CertType uint16
// KeyTag is the key tag of the associated key, computed as in
// [DNSKEY.KeyTag], or zero when the certificate type has no key tag.
KeyTag uint16
// Algorithm is the DNSSEC algorithm number of the associated key, or zero
// when there is none.
Algorithm uint8
// Certificate is the certificate or CRL itself.
Certificate []byte
}
CERT carries a certificate or certificate revocation list (RFC 4398).
func (*CERT) AppendWire ¶
AppendWire implements RData.
func (*CERT) String ¶
String implements RData. The certificate type is printed numerically rather than by mnemonic: RFC 4398 section 2.1 permits either, and a number cannot go stale as the IANA registry grows.
type CNAME ¶
type CNAME struct {
Target Name
}
CNAME is the canonical name for an alias (RFC 1035 section 3.3.1).
func (*CNAME) AppendWire ¶
AppendWire implements RData.
type CSYNC ¶
type CSYNC struct {
// Serial is the SOA serial the child must have reached before the parent
// acts, which is what stops a stale view of the child from being published.
Serial uint32
// Flags are the processing flags of RFC 7477 section 2.1.2: bit 0
// (immediate) and bit 1 (soaminimum).
Flags uint16
// TypeBitMap lists the record types to synchronise, in ascending order.
TypeBitMap []Type
}
CSYNC signals which records a child zone wants copied into its parent (RFC 7477).
func (*CSYNC) AppendWire ¶
AppendWire implements RData.
func (*CSYNC) String ¶
String implements RData, per RFC 7477 section 2.2: the serial, the flags as a decimal number, then the type mnemonics.
type Class ¶
type Class uint16
Class is a resource record class as defined by RFC 1035 section 3.2.4.
const ( ClassINET Class = 1 // the Internet; the only class in practical use ClassCSNET Class = 2 // CSNET, obsolete ClassCHAOS Class = 3 // CHAOS, used for server version probes ClassHESIOD Class = 4 // Hesiod ClassNONE Class = 254 // RFC 2136 prerequisite ClassANY Class = 255 // QCLASS *, RFC 1035 )
Resource record classes.
func ParseClass ¶
ParseClass returns the Class named by s, accepting mnemonics and the RFC 3597 "CLASSnnn" generic form.
type DHCID ¶
type DHCID struct {
Digest []byte
}
DHCID binds a DHCP client to a name so that only that client may update it (RFC 4701).
The payload is kept opaque. Its internal structure — an identifier type code, a digest type code and a SHA-256 digest — is meaningful only to the DHCP server that computed it, and RFC 4701 section 3.5 defines the presentation format as base64 of the whole field, so decomposing it would buy nothing and risk rejecting a digest type this package has not heard of.
func (*DHCID) AppendWire ¶
AppendWire implements RData.
func (*DHCID) String ¶
String implements RData, as base64 per RFC 4701 section 3.5.
type DLV ¶
DLV is a DNSSEC Lookaside Validation record (RFC 4431). Lookaside validation was decommissioned by RFC 8749, but the type is still published and still forwarded, so it decodes rather than falling through to Unknown.
func (*DLV) AppendWire ¶
AppendWire implements RData.
type DNAME ¶
type DNAME struct {
Target Name
}
DNAME redirects an entire subtree (RFC 6672). Unlike CNAME its target is not compressible, because DNAME postdates RFC 1035.
func (*DNAME) AppendWire ¶
AppendWire implements RData.
type DNSKEY ¶
type DNSKEY struct {
// Flags carries the Zone Key bit (0x0100) and the Secure Entry Point bit
// (0x0001); every other bit is reserved and must be preserved on the wire.
Flags uint16
// Protocol must be 3 (RFC 4034 section 2.1.2). A different value makes the
// record unusable for validation but is still carried verbatim, because a
// forwarder's job is to deliver what the authority published.
Protocol uint8
// Algorithm is the DNSSEC algorithm number, which determines how PublicKey
// is to be interpreted.
Algorithm uint8
// PublicKey is the algorithm-specific key material.
PublicKey []byte
}
DNSKEY holds a zone's public key (RFC 4034 section 2).
func (*DNSKEY) AppendWire ¶
AppendWire implements RData.
func (*DNSKEY) KeyTag ¶
KeyTag returns the RFC 4034 Appendix B key tag of this DNSKEY.
The tag is what lets a validator pair a DNSKEY with the DS or RRSIG that references it without parsing the key material, which matters because a resolver must be able to discard irrelevant keys before it does any cryptography. It is a one's-complement checksum over the RDATA, not an identifier: two distinct keys in the same zone may share a tag, so a validator must try every key whose tag matches rather than assuming the first is right.
The generic algorithm is used unconditionally. RFC 4034 Appendix B.1 defines a different computation for algorithm 1 (RSA/MD5), but RFC 6944 moved that algorithm to MUST NOT and RFC 8624 completed its removal, so a key it applies to is not one this engine would validate against in any case.
func (*DNSKEY) String ¶
String implements RData. The key is base64, as RFC 4034 section 2.2 requires; only the digest-bearing types print hex.
type DS ¶
type DS struct {
// KeyTag identifies the DNSKEY this record covers. It is a checksum, not an
// identifier: collisions are permitted and a validator must be prepared to
// try every DNSKEY with a matching tag. See [DNSKEY.KeyTag].
KeyTag uint16
// Algorithm is the DNSSEC algorithm number of the covered DNSKEY.
Algorithm uint8
// DigestType selects the digest algorithm from the IANA "Delegation Signer
// (DS) Resource Record (RR) Type Digest Algorithms" registry.
DigestType uint8
// Digest is the digest of the covered DNSKEY, whose length is fixed by
// DigestType. It is deliberately not validated against DigestType here: a
// forwarder must relay a delegation it cannot itself evaluate.
Digest []byte
}
DS is a Delegation Signer record (RFC 4034 section 5). It publishes a digest of a child zone's DNSKEY in the parent zone, which is the single link that makes the DNSSEC chain of trust traversable across a delegation.
func (*DS) AppendWire ¶
AppendWire implements RData.
type DecodeFunc ¶
DecodeFunc decodes the RDATA of one record. The decoder is bounded to exactly the record's RDLENGTH octets: reading past the end returns ErrTruncated, and leaving octets unread is reported as ErrRDataLength by the caller. An implementation may therefore read greedily with Decoder.Remaining and trust the bound.
type Decoder ¶
type Decoder struct {
// contains filtered or unexported fields
}
Decoder reads DNS wire format from a message buffer.
Decoder is exported because DecodeFunc implementations need it, so third-party record types get the same primitives the built-in ones use. It holds a reference to the whole message, which name decompression requires.
A Decoder is not safe for concurrent use. It may be reused across messages via Decoder.Reset, which is how a server avoids per-query allocation.
func NewDecoder ¶
func NewDecoder(msg []byte, opts *UnpackOptions) *Decoder
NewDecoder returns a Decoder reading msg. The buffer is retained, not copied, and must not be modified while the Decoder or any Name it produced is in use — Name copies its bytes, so in practice only the Decoder is affected.
func (*Decoder) Bytes ¶
Bytes reads exactly n octets and returns a copy. Returning a copy rather than a sub-slice is deliberate: decoded messages routinely outlive the buffer they came from, because that buffer is returned to a pool.
func (*Decoder) CharacterString ¶
CharacterString reads a length-prefixed <character-string> as defined by RFC 1035 section 3.3.
func (*Decoder) Header ¶
Header reads the twelve-octet header and returns it along with the four section counts.
func (*Decoder) Name ¶
Name reads a domain name, following compression pointers.
Two rules make decompression safe without tracking visited offsets. A pointer must target an offset strictly lower than the one it appears at, so a cycle cannot form; and the number of hops is capped by [maxPointerHops], so a single name costs a bounded number of redirections. Both are enforced here.
Neither rule bounds how much memory a whole message of pointers expands to. UnpackOptions.MaxNameOctets, charged here as each name completes, is the only mechanism that does.
func (*Decoder) NameUncompressed ¶
NameUncompressed reads a domain name, rejecting compression pointers with ErrBadPointer.
This is the correct reader for a name inside the RDATA of any record type defined after RFC 1035, and the reason is round-trip fidelity rather than pedantry. RFC 3597 section 4 forbids senders from compressing those names, and this package's encoder honours that. If the decoder silently expanded a pointer that a non-conforming sender emitted, the record would re-encode longer than it arrived — a different RDLENGTH over different octets. For an RRSIG or an NSEC that is covered by a signature, the answer would then go bogus downstream with nothing to explain why. Refusing the record instead turns a silent corruption into a visible format error.
func (*Decoder) Offset ¶
Offset returns the current read position, counted from the start of the message. It is the offset reported in a SyntaxError.
func (*Decoder) RR ¶
RR reads one resource record, dispatching the RDATA to the registered decoder for its type. Types without a decoder become Unknown.
func (*Decoder) Remaining ¶
Remaining returns how many octets may still be read at the current bound. Inside a DecodeFunc the bound is the record's RDLENGTH, so this is exactly the number of RDATA octets left.
func (*Decoder) Reset ¶
func (d *Decoder) Reset(msg []byte, opts *UnpackOptions)
Reset prepares d to read a new message, retaining its scratch storage.
Every per-message budget is reset here, so a pooled Decoder cannot carry one query's spending into the next.
type EDNS ¶
type EDNS struct {
// UDPSize is the requestor's advertised payload size.
UDPSize uint16
// Version is the EDNS version; only [EDNS0Version] is defined.
Version uint8
// DO is the DNSSEC OK bit.
DO bool
// Flags holds the remaining fifteen flag bits, all currently reserved.
Flags uint16
// Options are the EDNS options carried in the OPT RDATA.
Options []EDNSOption
}
EDNS is a decoded view of a message's OPT record, with the fields the wire format overloads into CLASS and TTL broken out.
There is deliberately no extended-RCODE field. Header.RCode holds the full twelve-bit code, and the encoder writes the OPT TTL's high octet from it on every message that carries an OPT record; a second copy here could only ever disagree with the first, and the loser of that disagreement would be whichever one the caller happened to set.
type EDNSClientSubnet ¶
type EDNSClientSubnet struct {
// Prefix is the client network. Its bit length is the source prefix length
// sent on the wire, and a decoded Prefix is always already masked to it.
Prefix netip.Prefix
// ScopeLen is the scope prefix length: zero in a query, and in a response
// the number of significant bits the answer actually depends on.
ScopeLen uint8
}
EDNSClientSubnet carries the client's network to the upstream resolver (RFC 7871).
GatewayDNS treats ECS as a privacy control rather than a performance optimisation: forwarding it discloses the client's network to every upstream. The resolver's default is to strip it, and the policy engine decides when to synthesise one.
An option that arrives with address bits set beyond SOURCE PREFIX-LENGTH is rejected, not normalised. RFC 7871 section 6 requires those bits to be zero, so such an option is malformed rather than merely untidy, and the two alternatives are both worse for a forwarding proxy. Masking it silently means the option this gateway emits differs from the one it received, which breaks the echo RFC 7871 section 7.3 requires of a response — a strict client comparing the returned option against the one it sent would reject a perfectly good answer. Relaying the stray bits verbatim means disclosing more of the client's address than the prefix length claims, which is precisely the disclosure the option's length field exists to bound. Refusing is the only choice that neither rewrites the sender's octets nor leaks them, and it makes the round trip exact by construction: every option that decodes is already masked, so re-encoding it reproduces the input byte for byte.
func (*EDNSClientSubnet) AppendWire ¶
func (o *EDNSClientSubnet) AppendWire(dst []byte) []byte
AppendWire implements EDNSOption. Only the significant octets of the address are sent, as RFC 7871 section 6 requires; trailing bits within the last octet are zeroed.
That zeroing is a backstop for an option built in memory, where a caller can easily assign a whole client address alongside a shorter prefix length and disclose the part it meant to withhold. It never alters a forwarded option, because decoding rejects an address carrying those bits in the first place.
func (*EDNSClientSubnet) Code ¶
func (o *EDNSClientSubnet) Code() EDNSOptionCode
Code implements EDNSOption.
func (*EDNSClientSubnet) Copy ¶
func (o *EDNSClientSubnet) Copy() EDNSOption
Copy implements EDNSOption.
func (*EDNSClientSubnet) String ¶
func (o *EDNSClientSubnet) String() string
String implements EDNSOption.
type EDNSCookie ¶
type EDNSCookie struct {
// Client is the eight-octet client cookie.
Client [8]byte
// Server is the 8 to 32 octet server cookie, empty in an initial query.
Server []byte
}
EDNSCookie is a DNS cookie (RFC 7873), used to make off-path spoofing and reflection amplification harder.
func (*EDNSCookie) AppendWire ¶
func (o *EDNSCookie) AppendWire(dst []byte) []byte
AppendWire implements EDNSOption.
func (*EDNSCookie) Copy ¶
func (o *EDNSCookie) Copy() EDNSOption
Copy implements EDNSOption. An initial query carries no server cookie, and [copyBytes] keeps that absence nil rather than turning it into an empty slice that encodes the same but compares differently.
type EDNSExtendedError ¶
type EDNSExtendedError struct {
InfoCode ExtendedErrorCode
ExtraText string
}
EDNSExtendedError explains *why* a response failed (RFC 8914).
This is the mechanism by which a blocked query can be reported honestly. A filtered response carries ExtendedErrorBlocked, ExtendedErrorCensored or ExtendedErrorFiltered together with human-readable text, so that a client can distinguish policy from failure. The policy engine sets it on every synthesised block.
func (*EDNSExtendedError) AppendWire ¶
func (o *EDNSExtendedError) AppendWire(dst []byte) []byte
AppendWire implements EDNSOption.
func (*EDNSExtendedError) Code ¶
func (o *EDNSExtendedError) Code() EDNSOptionCode
Code implements EDNSOption.
func (*EDNSExtendedError) Copy ¶
func (o *EDNSExtendedError) Copy() EDNSOption
Copy implements EDNSOption.
func (*EDNSExtendedError) String ¶
func (o *EDNSExtendedError) String() string
String implements EDNSOption.
type EDNSNSID ¶
type EDNSNSID struct {
ID []byte
}
EDNSNSID identifies the responding server instance (RFC 5001).
func (*EDNSNSID) AppendWire ¶
AppendWire implements EDNSOption.
type EDNSOption ¶
type EDNSOption interface {
// Code returns the option code.
Code() EDNSOptionCode
// AppendWire appends the option body, excluding the code and length.
AppendWire(dst []byte) []byte
// String returns a human-readable rendering of the option body.
String() string
// Copy returns a deep copy.
Copy() EDNSOption
}
EDNSOption is a single EDNS(0) option.
As with RData, the interface is fully exported so that an application can implement an option this package does not know about and have it encode and print correctly. Unrecognised options decode to EDNSUnknown and round-trip verbatim.
type EDNSOptionCode ¶
type EDNSOptionCode uint16
EDNSOptionCode identifies an EDNS(0) option.
const ( EDNSCodeLLQ EDNSOptionCode = 1 EDNSCodeNSID EDNSOptionCode = 3 EDNSCodeDAU EDNSOptionCode = 5 EDNSCodeDHU EDNSOptionCode = 6 EDNSCodeN3U EDNSOptionCode = 7 EDNSCodeClientSubnet EDNSOptionCode = 8 EDNSCodeExpire EDNSOptionCode = 9 EDNSCodeCookie EDNSOptionCode = 10 EDNSCodeTCPKeepalive EDNSOptionCode = 11 EDNSCodePadding EDNSOptionCode = 12 EDNSCodeChain EDNSOptionCode = 13 EDNSCodeKeyTag EDNSOptionCode = 14 EDNSCodeExtendedError EDNSOptionCode = 15 EDNSCodeClientTag EDNSOptionCode = 16 EDNSCodeServerTag EDNSOptionCode = 17 EDNSCodeReportChannel EDNSOptionCode = 18 EDNSCodeZoneVersion EDNSOptionCode = 19 )
EDNS(0) option codes from the IANA registry.
func (EDNSOptionCode) String ¶
func (c EDNSOptionCode) String() string
String returns the mnemonic for c, or "OPT-CODE-n".
type EDNSPadding ¶
type EDNSPadding struct {
// Payload is the padding octets exactly as they appeared on the wire. It is
// owned by the option and must not be modified by callers. Its length is
// the padding length.
Payload []byte
}
EDNSPadding pads a message to a uniform size (RFC 7830), so that an observer of an encrypted transport cannot infer the query from its length.
The octets are stored rather than regenerated. RFC 7830 section 3 says a sender SHOULD pad with zeroes and a receiver MUST NOT reject anything else, so re-emitting zeroes would be legal — but this option lives inside the OPT record, which is inside whatever a TSIG or SIG(0) MAC covers. A proxy that quietly rewrites octets it was asked to relay invalidates a signature it is supposed to be transparent to, and the failure surfaces at a validator with nothing to point at. Keeping what arrived costs one slice and removes the whole class of unexplainable verification errors; use NewEDNSPadding for the ordinary all-zero case.
func NewEDNSPadding ¶
func NewEDNSPadding(n int) *EDNSPadding
NewEDNSPadding returns a padding option of n zero octets, the form RFC 7830 section 3 asks a sender to produce. A non-positive n gives an empty option, which is still well formed and is what a padding computation naturally yields when the message already sits on a block boundary.
func (*EDNSPadding) AppendWire ¶
func (o *EDNSPadding) AppendWire(dst []byte) []byte
AppendWire implements EDNSOption.
func (*EDNSPadding) Copy ¶
func (o *EDNSPadding) Copy() EDNSOption
Copy implements EDNSOption. [copyBytes] is used rather than an unconditional make so that an absent payload stays nil and a copy compares equal to its original under reflect.DeepEqual.
func (*EDNSPadding) String ¶
func (o *EDNSPadding) String() string
String implements EDNSOption. Non-zero content is called out rather than dumped: it is legal but unusual, and it is the first thing worth knowing when a signature over the OPT record stops verifying.
type EDNSTCPKeepalive ¶
type EDNSTCPKeepalive struct {
// Timeout is the idle timeout in units of 100 milliseconds. It is absent in
// a client's request, which HasTimeout reports.
Timeout uint16
// HasTimeout distinguishes an absent timeout from a zero one, which
// instructs the client to close immediately.
HasTimeout bool
}
EDNSTCPKeepalive negotiates an idle timeout for a stateful transport (RFC 7828).
func (*EDNSTCPKeepalive) AppendWire ¶
func (o *EDNSTCPKeepalive) AppendWire(dst []byte) []byte
AppendWire implements EDNSOption.
func (*EDNSTCPKeepalive) Code ¶
func (o *EDNSTCPKeepalive) Code() EDNSOptionCode
Code implements EDNSOption.
func (*EDNSTCPKeepalive) Copy ¶
func (o *EDNSTCPKeepalive) Copy() EDNSOption
Copy implements EDNSOption.
func (*EDNSTCPKeepalive) String ¶
func (o *EDNSTCPKeepalive) String() string
String implements EDNSOption.
type EDNSUnknown ¶
type EDNSUnknown struct {
OptCode EDNSOptionCode
Payload []byte
}
EDNSUnknown preserves an option this package does not decode.
func (*EDNSUnknown) AppendWire ¶
func (o *EDNSUnknown) AppendWire(dst []byte) []byte
AppendWire implements EDNSOption.
func (*EDNSUnknown) Copy ¶
func (o *EDNSUnknown) Copy() EDNSOption
Copy implements EDNSOption. [copyBytes] preserves a nil payload as nil, so that a copy compares equal to its source under reflect.DeepEqual as well as encoding identically.
type EUI48 ¶
type EUI48 struct {
Address [6]byte
}
EUI48 carries a 48-bit Extended Unique Identifier, typically an Ethernet MAC address (RFC 7043 section 3).
RFC 7043 section 5 warns that publishing one links a name to a physical interface and so to a device's movements; GatewayDNS decodes it because a forwarder must, not because publishing it is advisable.
func (*EUI48) AppendWire ¶
AppendWire implements RData.
func (*EUI48) Copy ¶
Copy implements RData. The address is a fixed-size array, so the value is self-contained and may be shared.
func (*EUI48) String ¶
String implements RData, using the dash-separated lower-case hexadecimal form of RFC 7043 section 3.2.
type EUI64 ¶
type EUI64 struct {
Address [8]byte
}
EUI64 carries a 64-bit Extended Unique Identifier (RFC 7043 section 4). The privacy caveat on EUI48 applies equally.
func (*EUI64) AppendWire ¶
AppendWire implements RData.
func (*EUI64) String ¶
String implements RData, using the dash-separated lower-case hexadecimal form of RFC 7043 section 4.2.
type EncodeError ¶
type EncodeError struct {
// Section is the section holding the offending record.
Section Section
// Index is its position within that section.
Index int
// Err is the underlying cause.
Err error
}
EncodeError describes a Message that cannot be expressed on the wire, identifying the record at fault.
It is a separate type from SyntaxError rather than a flag on it because the two demand opposite responses and the distinction has to survive being passed through layers of a server as a bare error. A SyntaxError is a fact about a packet: drop it, answer FORMERR, count it, carry on. An EncodeError is a fact about the local program: the message it assembled is not representable, no retry will change that, and the right response is to fail the request loudly and fix the code. Reporting both as one type is what forces a server author to pick the wrong one of those for half their errors.
There is no Offset field. The octet position within a buffer the caller never sees says nothing about which line of code built the bad value, whereas the section and index locate it in the caller's own Message.
func (*EncodeError) Error ¶
func (e *EncodeError) Error() string
Error implements the error interface.
func (*EncodeError) Unwrap ¶
func (e *EncodeError) Unwrap() error
Unwrap returns the underlying cause so that errors.Is works through the positional annotation.
type Encoder ¶
type Encoder struct {
// contains filtered or unexported fields
}
Encoder writes DNS wire format.
Encoder is exported because RData.AppendWire receives one, giving third-party record types access to the same name-writing primitives the built-ins use. It is not safe for concurrent use.
func NewEncoder ¶
NewEncoder returns an Encoder that writes into a buffer whose current length is base, compressing names when compress is true.
Message.AppendPack manages an Encoder for you, so most callers never need this. It exists for code that encodes an RData payload on its own — most often to produce the uncompressed canonical form a signature is computed over, for which base is 0 and compress is false.
func (*Encoder) AppendAddr ¶
AppendAddr appends an IP address in its natural width: four octets for IPv4, sixteen for IPv6. An IPv4-mapped IPv6 address is written as IPv4, which is what an A record requires.
func (*Encoder) AppendCharacterString ¶
AppendCharacterString appends a length-prefixed <character-string>.
A string longer than MaxCharStringLen cannot be expressed, and is truncated here because this method has no way to report an error. That is a last-resort backstop, not the intended path: an RData implementation holding a <character-string> should implement Validator so the encoder rejects the record before reaching this point. Every built-in type that writes one does, and TestRDataCharacterStringWritersImplementValidator keeps it that way.
func (*Encoder) AppendName ¶
AppendName appends name to dst, using compression where possible.
Only use this for the owner name of a record and for RDATA of the record types RFC 1035 defined with compressible names: NS, CNAME, SOA, PTR, MX and their obsolete siblings. RFC 3597 section 4 forbids compression anywhere else, because a receiver that does not implement the type cannot know a name is there and so cannot expand it. Use Encoder.AppendNameUncompressed for every other type.
func (*Encoder) AppendNameUncompressed ¶
AppendNameUncompressed appends name to dst without compression, and without registering it as a compression target. This is the correct choice for the RDATA of every record type defined after RFC 1035.
func (*Encoder) AppendUint8 ¶
AppendUint8 appends a single octet.
func (*Encoder) AppendUint16 ¶
AppendUint16 appends a big-endian 16-bit integer.
func (*Encoder) AppendUint32 ¶
AppendUint32 appends a big-endian 32-bit integer.
func (*Encoder) AppendUint48 ¶
AppendUint48 appends a big-endian 48-bit integer.
type ExtendedErrorCode ¶
type ExtendedErrorCode uint16
ExtendedErrorCode is an EDNS Extended DNS Error info code (RFC 8914).
const ( ExtendedErrorOther ExtendedErrorCode = 0 ExtendedErrorUnsupportedDNSKEYAlg ExtendedErrorCode = 1 ExtendedErrorUnsupportedDSDigest ExtendedErrorCode = 2 ExtendedErrorStaleAnswer ExtendedErrorCode = 3 ExtendedErrorForgedAnswer ExtendedErrorCode = 4 ExtendedErrorDNSSECIndeterminate ExtendedErrorCode = 5 ExtendedErrorDNSSECBogus ExtendedErrorCode = 6 ExtendedErrorSignatureExpired ExtendedErrorCode = 7 ExtendedErrorSignatureNotYetValid ExtendedErrorCode = 8 ExtendedErrorDNSKEYMissing ExtendedErrorCode = 9 ExtendedErrorRRSIGsMissing ExtendedErrorCode = 10 ExtendedErrorNoZoneKeyBitSet ExtendedErrorCode = 11 ExtendedErrorNSECMissing ExtendedErrorCode = 12 ExtendedErrorCachedError ExtendedErrorCode = 13 ExtendedErrorNotReady ExtendedErrorCode = 14 ExtendedErrorBlocked ExtendedErrorCode = 15 ExtendedErrorCensored ExtendedErrorCode = 16 ExtendedErrorFiltered ExtendedErrorCode = 17 ExtendedErrorProhibited ExtendedErrorCode = 18 ExtendedErrorStaleNXDOMAIN ExtendedErrorCode = 19 ExtendedErrorNotAuthoritative ExtendedErrorCode = 20 ExtendedErrorNotSupported ExtendedErrorCode = 21 ExtendedErrorNoReachableAuthority ExtendedErrorCode = 22 ExtendedErrorNetworkError ExtendedErrorCode = 23 ExtendedErrorInvalidData ExtendedErrorCode = 24 ExtendedErrorSignatureExpiredBeforeVal ExtendedErrorCode = 25 ExtendedErrorTooEarly ExtendedErrorCode = 26 )
Extended DNS Error info codes.
func (ExtendedErrorCode) String ¶
func (c ExtendedErrorCode) String() string
String returns the registry name for c, or "EDE-n".
type HINFO ¶
type HINFO struct {
// CPU is the hardware type, drawn from the machine names of RFC 1010.
CPU string
// OS is the operating system name.
OS string
}
HINFO describes a host's hardware and operating system (RFC 1035 section 3.3.2).
Publishing it is now discouraged as an information leak, but RFC 8482 gave it a second life: it is the record type a server returns in place of an ANY response, so a resolver still has to decode it.
func (*HINFO) AppendWire ¶
AppendWire implements RData.
type HTTPS ¶
type HTTPS struct {
SVCB
}
HTTPS is the HTTP-specific service binding record (RFC 9460 section 9).
It shares SVCB's wire format and presentation format exactly and differs only in its type code and in the defaults a client applies to a record that omits "alpn". Embedding SVCB rather than duplicating the codec is what keeps the two implementations from drifting apart, which for a record that carries Encrypted Client Hello would be a security bug rather than an inconvenience.
A promoted method needs an override here exactly when it names the concrete type. HTTPS.Type and HTTPS.Copy both do, and both would silently turn an HTTPS record into an SVCB one without it. SVCB.Validate does not: it reads only the embedded fields, which are the whole of an HTTPS record's state, so *HTTPS satisfies Validator through the promotion and the encoder holds an HTTPS record to exactly the rules it holds an SVCB one to.
func (*HTTPS) Copy ¶
Copy implements RData.
Like HTTPS.Type, this override must exist: the promoted SVCB.Copy returns an *SVCB, so a cache or a rewriting proxy that stores Copy's result would silently turn every HTTPS record into an SVCB record with the same contents and a different type code — a corruption with no error and no log line anywhere. Any test of this file should assert both that (&HTTPS{}).Copy().Type() == TypeHTTPS and that the concrete type of the result is *HTTPS.
func (*HTTPS) Type ¶
Type implements RData.
This override is load-bearing. Without it the method promoted from the embedded SVCB would report TypeSVCB, and Message.AppendPack would reject every HTTPS record with ErrTypeMismatch.
type Header ¶
type Header struct {
// ID correlates a response with its query.
ID uint16
// Response is the QR bit: false for a query, true for a response.
Response bool
// Opcode is the kind of query being made.
Opcode Opcode
// Authoritative is the AA bit, set by a server authoritative for the name.
Authoritative bool
// Truncated is the TC bit, set when the message was cut short because it
// exceeded the transport's size limit.
Truncated bool
// RecursionDesired is the RD bit, copied from query to response.
RecursionDesired bool
// RecursionAvailable is the RA bit, set by a server willing to recurse.
RecursionAvailable bool
// Zero is the reserved Z bit. It must be false in conforming messages, but
// is preserved so that malformed traffic round-trips for diagnosis.
Zero bool
// AuthenticData is the AD bit of RFC 4035: the server verified DNSSEC.
AuthenticData bool
// CheckingDisabled is the CD bit of RFC 4035: the client will do its own
// DNSSEC validation and wants unvalidated data.
CheckingDisabled bool
// RCode is the *full* response code, and the only place this package stores
// one.
//
// RFC 6891 section 6.1.3 splits the twelve-bit code across two fields: the
// low four bits sit in the header, the upper eight in the TTL of the OPT
// record. The decoder merges them, so this field always holds the effective
// value and callers never have to reassemble it. The encoder performs the
// reverse split and writes that TTL octet unconditionally, including when it
// is zero — so an extended code left over from a decoded message cannot
// survive a change made here, and setting the OPT record's TTL by hand has
// no effect on the response code.
//
// Encoding reports [ErrNoOPT] for a value above 15 with no OPT record to
// carry the upper bits, and [ErrRCodeRange] for a value above 4095, which no
// DNS message can express.
RCode RCode
}
Header is the fixed twelve-octet preamble of a DNS message, decoded into named fields.
The flag bits are modelled as booleans rather than as a packed word because almost every use is a read or write of one specific bit; a Flags type would force every caller through masking helpers to gain nothing.
type KX ¶
type KX struct {
// Preference orders exchangers; lower is tried first, as in MX.
Preference uint16
// Exchanger is the host that acts as the key exchanger.
Exchanger Name
}
KX names a key exchanger for a domain (RFC 2230).
func (*KX) AppendWire ¶
AppendWire implements RData. RFC 2230 section 3.1 states outright that the exchanger name is not compressed, which RFC 3597 section 4 later generalised.
type L32 ¶
type L32 struct {
// Preference orders candidate locators; lower is preferred.
Preference uint16
// Locator32 is the IPv4 routing prefix the node is reachable through. It is
// held as a [netip.Addr] because RFC 6742 section 2.2.2 presents it as a
// dotted quad, not as an opaque 32-bit integer.
Locator32 netip.Addr
}
L32 maps a name to an ILNP 32-bit Locator (RFC 6742 section 2.2).
func (*L32) AppendWire ¶
AppendWire implements RData.
type L64 ¶
type L64 struct {
// Preference orders candidate locators; lower is preferred.
Preference uint16
// Locator64 is the 64-bit locator, the routing half of an ILNP address.
Locator64 uint64
}
L64 maps a name to an ILNP 64-bit Locator (RFC 6742 section 2.3).
func (*L64) AppendWire ¶
AppendWire implements RData.
func (*L64) String ¶
String implements RData, writing the locator as four colon-separated groups of four lower-case hexadecimal digits (RFC 6742 section 2.3.2).
type LOC ¶
type LOC struct {
// Version must be zero. RFC 1876 section 2 requires implementations to
// check it and assume nothing about other versions, so the decoder rejects
// them rather than guessing at the layout.
Version uint8
// Size is the diameter of a sphere enclosing the entity, in the
// base-and-exponent encoding described by [LOC.SizeCentimetres].
Size uint8
// HorizPre is the horizontal precision, same encoding as Size.
HorizPre uint8
// VertPre is the vertical precision, same encoding as Size.
VertPre uint8
// Latitude is thousandths of an arcsecond, biased by 2^31 so that the
// equator is 2^31 and larger values are north.
Latitude uint32
// Longitude is thousandths of an arcsecond, biased by 2^31 so that the
// prime meridian is 2^31 and larger values are east.
Longitude uint32
// Altitude is centimetres above a datum 100000 metres below the WGS 84
// reference spheroid, which is what makes the field unsigned.
Altitude uint32
}
LOC expresses a host's physical location (RFC 1876).
The fields are stored exactly as the wire carries them rather than as latitude and longitude in degrees, because the encoding is not value-preserving in floating point: thousandths of an arcsecond and the base-and-exponent precision octets both round badly through a float64, and a record must re-encode to the octets it arrived as. LOC.String does the conversion for presentation only.
func (*LOC) AppendWire ¶
AppendWire implements RData.
func (*LOC) HorizPreCentimetres ¶
HorizPreCentimetres returns LOC.HorizPre in centimetres, using the same encoding as LOC.SizeCentimetres.
func (*LOC) SizeCentimetres ¶
SizeCentimetres returns LOC.Size in centimetres. The octet is a mantissa in its high nibble and a power of ten in its low nibble, each in the range zero to nine, so the representable values are 0 through 9e9 centimetres (RFC 1876 section 2).
func (*LOC) String ¶
String implements RData, producing the RFC 1876 section 3 presentation format: degrees, minutes and seconds with a hemisphere letter for each coordinate, then altitude, size, horizontal precision and vertical precision in metres. Minutes and seconds are zero-padded to two digits and the metre values carry two decimals, matching the output every deployed implementation derives from BIND's loc_ntoa, so that captured records compare directly.
func (*LOC) VertPreCentimetres ¶
VertPreCentimetres returns LOC.VertPre in centimetres, using the same encoding as LOC.SizeCentimetres.
type LP ¶
type LP struct {
// Preference orders candidate pointers; lower is preferred.
Preference uint16
// FQDN is the name to query for L32 and L64 records. RFC 6742 section 2.4.1
// requires it to differ from the owner name.
FQDN Name
}
LP names a node whose L32 and L64 records supply locators for this name (RFC 6742 section 2.4).
func (*LP) AppendWire ¶
AppendWire implements RData. RFC 6742 section 2.4.1 forbids compressing the name, in line with RFC 3597 section 4.
type MINFO ¶
type MINFO struct {
// RMailbox receives mail about the list itself, such as subscription
// requests. The root name means the information is unavailable.
RMailbox Name
// EMailbox receives error reports for the list.
EMailbox Name
}
MINFO names the mailboxes responsible for a mailing list (RFC 1035 section 3.3.7).
MINFO is the exception to this file's compression rule. It is one of the types RFC 1035 defines, so it falls inside the "well-known" set that RFC 3597 section 4 still permits compression for, and both of its names are written through Encoder.AppendName. Every other name-bearing type here is written uncompressed.
func (*MINFO) AppendWire ¶
AppendWire implements RData. See the type comment for why these two names, alone in this file, are compressed.
type MX ¶
MX names a mail exchange (RFC 1035 section 3.3.9).
func (*MX) AppendWire ¶
AppendWire implements RData.
type Message ¶
Message is a decoded DNS message.
A Message is a plain struct with exported fields: constructing one by hand is the normal way to build a synthetic response, and every field is meant to be written directly. Nothing is validated until the message is encoded.
func Unpack ¶
Unpack decodes a complete DNS message from b using the default options.
The returned Message does not alias b: names and payloads are copied, so b may be returned to a pool immediately.
Example ¶
ExampleUnpack decodes a response and walks the answer section with a type switch on RData, which is how a caller gets at the fields of a record without the package having to expose a union type.
package main
import (
"encoding/hex"
"fmt"
"log"
"github.com/daboss2003/dns/dnsmsg"
)
func main() {
// A real response to "www.example.com. IN A": a CNAME to example.com.
// followed by that name's address record. The two c0-prefixed octets are
// compression pointers, which Unpack expands transparently.
wire, err := hex.DecodeString(
"f00d8180000100020000000003777777076578616d706c6503636f6d0000010001" +
"c00c0005000100000e100002c010c010000100010000012c00045db8d822")
if err != nil {
log.Fatal(err)
}
m, err := dnsmsg.Unpack(wire)
if err != nil {
log.Fatal(err)
}
q, _ := m.Question()
fmt.Println("id:", m.ID, "status:", m.RCode, "question:", q.Name, q.Type)
for _, rr := range m.Answers {
switch rd := rr.Data.(type) {
case *dnsmsg.A:
fmt.Println(rr.Name, rr.TTL, "A", rd.Addr)
case *dnsmsg.CNAME:
fmt.Println(rr.Name, rr.TTL, "CNAME", rd.Target)
default:
// Unknown lands here, carrying the RDATA verbatim, so a forwarder
// can relay a type it was never taught.
fmt.Println(rr.Name, rr.TTL, rr.Type, rd)
}
}
}
Output: id: 61453 status: NOERROR question: www.example.com. A www.example.com. 3600 CNAME example.com. example.com. 300 A 93.184.216.34
func UnpackWith ¶
func UnpackWith(b []byte, opts *UnpackOptions) (*Message, error)
UnpackWith decodes a complete DNS message using the supplied options.
func (*Message) AppendPack ¶
func (m *Message) AppendPack(dst []byte, opts *PackOptions) ([]byte, error)
AppendPack encodes m, appending to dst, and returns the extended buffer.
Appending rather than allocating is what makes a low-allocation server possible: the caller keeps one buffer per connection and passes dst[:0] each time. The encoder itself is pooled internally, so a steady-state Pack into a warm buffer allocates nothing beyond the buffer's own growth.
When the encoded message would exceed PackOptions.MaxSize it is truncated and the TC bit is set in the encoded output. m itself is never modified.
Encoding stops as soon as the limit is reached, so truncating a large message down to a UDP payload costs time proportional to the payload, not to the message. That matters because truncation is not a rare path: it is what every oversized answer over UDP does.
Example ¶
ExampleMessage_AppendPack shows the buffer reuse that makes an allocation-free server possible: one buffer is kept for the lifetime of the connection and handed to every encode as dst[:0]. Because the append has spare capacity it never reallocates, so encoding a message costs no allocation at all after the first.
package main
import (
"fmt"
"log"
"github.com/daboss2003/dns/dnsmsg"
)
func main() {
buf := make([]byte, 0, 512)
var m dnsmsg.Message
for _, name := range []string{"a.example.", "bb.example."} {
m.SetQuestion(dnsmsg.MustParseName(name), dnsmsg.TypeA, dnsmsg.ClassINET)
m.ID = 1
out, err := m.AppendPack(buf[:0], nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(name, "encoded:", len(out), "octets, buffer capacity:", cap(out))
// Keep whatever capacity the encode ended up with for the next round.
buf = out
}
}
Output: a.example. encoded: 27 octets, buffer capacity: 512 bb.example. encoded: 28 octets, buffer capacity: 512
func (*Message) Copy ¶
Copy returns a deep copy of m.
The cache stores messages that are handed to many concurrent readers, so it must never hand out a value that another goroutine could mutate. Copy is what makes that safe. Name and most RData implementations are immutable and are shared rather than duplicated; implementations holding mutable slices duplicate them in their Copy method.
func (*Message) Decode ¶
Decode reads one complete message from d into m, replacing m's contents.
This is the pooled entry point. Message.Unpack allocates a Decoder per call, and a Decoder is not small — it embeds a 255-octet name scratch buffer. A server that keeps one Decoder and one Message per worker calls Decoder.Reset then Decode, and decodes a query for the cost of its names alone. The two Reset methods exist for exactly this pairing.
d must be positioned at the start of a message, which is what Decoder.Reset guarantees. The Message does not alias d's buffer.
func (*Message) EDNS ¶
EDNS returns a decoded view of the message's OPT record, and whether one is present. The result is a copy; mutating it does not affect the message.
func (*Message) Len ¶
Len returns the number of octets m occupies when encoded with the default options. It encodes into a scratch buffer, so prefer capturing the length from Message.AppendPack when the bytes are wanted anyway.
The default options carry the MaxMessageSize ceiling that PackOptions.MaxSize describes, so a message too large to express on the wire reports the size of its *truncated* encoding rather than its true size, with a nil error. That is not an evasion: a message above 65535 octets has no encoded size, because it has no encoding. Callers that need to distinguish "this is how big it is" from "it does not fit" should pack with PackOptions.NoTruncate and check for ErrBufferTooSmall.
func (*Message) Question ¶
Question returns the first question, and whether one is present. Practically every DNS message carries exactly one question; this accessor keeps callers from indexing an empty slice on malformed input.
func (*Message) Reset ¶
func (m *Message) Reset()
Reset clears m for reuse while retaining the capacity of its section slices. It is the basis for pooling messages across queries: a server handling 10,000 queries per second should not allocate four slices per query.
Reset owns the whole capacity of each section slice, not just the part within its length, and zeroes all of it. A section must therefore never be assigned a sub-slice of an array something else still reads.
func (*Message) Section ¶
Section returns the records of the named section. It returns nil for SectionQuestion, which holds questions rather than records.
func (*Message) SetEDNS ¶
SetEDNS installs or replaces the message's OPT record. A nil argument removes it.
The extended RCODE is not written here, and cannot be: Header.RCode is the only source of truth for it, and the encoder writes the OPT TTL's high octet from that field unconditionally. Setting a twelve-bit RCode on the message and calling SetEDNS in either order therefore produces the same wire bytes.
func (*Message) SetEDNSDefaults ¶
SetEDNSDefaults installs an OPT record advertising DefaultUDPSize with the DNSSEC OK bit set as requested and no options. It is the one-line form of Message.SetEDNS for the common case.
func (*Message) SetExtendedError ¶
func (m *Message) SetExtendedError(code ExtendedErrorCode, text string)
SetExtendedError attaches an EDE option to the message, creating an OPT record if necessary and replacing any EDE already present. It is how the policy engine reports that an answer was synthesised rather than resolved.
func (*Message) SetQuestion ¶
SetQuestion resets m and configures it as a recursive query for the given name, type and class. The ID is left zero; the caller assigns one, because only the transport knows what identifiers are already outstanding.
Example ¶
ExampleMessage_SetQuestion builds a recursive query and encodes it. The ID is left for the caller to assign, because only the transport knows which identifiers are already outstanding on a connection.
package main
import (
"fmt"
"log"
"github.com/daboss2003/dns/dnsmsg"
)
func main() {
var m dnsmsg.Message
m.SetQuestion(dnsmsg.MustParseName("example.com"), dnsmsg.TypeMX, dnsmsg.ClassINET)
m.ID = 0x1234
q, ok := m.Question()
fmt.Println(ok, q.Name, q.Class, q.Type)
fmt.Println("opcode:", m.Opcode, "rd:", m.RecursionDesired)
wire, err := m.Pack()
if err != nil {
log.Fatal(err)
}
fmt.Printf("% x\n", wire)
}
Output: true example.com. IN MX opcode: QUERY rd: true 12 34 01 00 00 01 00 00 00 00 00 00 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 00 0f 00 01
func (*Message) SetReply ¶
SetReply resets m and configures it as a response to req: the ID, opcode, RD bit and question section are copied, and QR and RA are set. Existing contents of m are discarded.
The OPT record is deliberately not copied. Echoing a client's EDNS options back verbatim leaks state and can reflect options the responder does not implement; the caller decides what EDNS to emit via Message.SetEDNS.
func (*Message) String ¶
String renders m in the multi-section format produced by dig, which makes captured messages directly comparable against a reference implementation.
func (*Message) UDPSize ¶
UDPSize returns the payload size the message advertises, or MinUDPSize when it carries no OPT record. This is the value a server must respect when deciding whether to truncate a UDP response.
func (*Message) Unpack ¶
func (m *Message) Unpack(b []byte, opts *UnpackOptions) error
Unpack decodes b into m, replacing its contents.
Decoding into an existing Message is what allows a server to reuse section slices across queries. Calling Message.Reset is unnecessary; Unpack resets m itself. To reuse the Decoder as well, use Message.Decode.
type NAPTR ¶
type NAPTR struct {
// Order is the position of this rule in the sequence; lower is applied
// first, and the order is significant even when Preference is not.
Order uint16
// Preference breaks ties within one Order value.
Preference uint16
// Flags controls how the result of the rewrite is used, for example "u" for
// a terminal URI or "s" for an SRV lookup.
Flags string
// Service names the resolution service the rule applies to.
Service string
// Regexp is the substitution expression applied to the input, empty when
// Replacement is used instead.
Regexp string
// Replacement is the next name to query, used when Regexp is empty. The two
// are mutually exclusive.
Replacement Name
}
NAPTR is a naming authority pointer, the rewrite rule at the heart of DDDS (RFC 3403).
func (*NAPTR) AppendWire ¶
AppendWire implements RData. NAPTR postdates RFC 1035, so RFC 3597 section 4 forbids compressing its replacement name.
type NID ¶
type NID struct {
// Preference orders candidate identifiers; lower is preferred.
Preference uint16
// NodeID is the 64-bit node identifier.
NodeID uint64
}
NID maps a name to an ILNP Node Identifier (RFC 6742 section 2.1).
func (*NID) AppendWire ¶
AppendWire implements RData.
func (*NID) String ¶
String implements RData. The identifier is written as four colon-separated groups of four hexadecimal digits, the IPv6-like grouping of RFC 6742 section 2.1.2, in lower case throughout.
type NS ¶
type NS struct {
Target Name
}
NS names an authoritative name server (RFC 1035 section 3.3.11).
func (*NS) AppendWire ¶
AppendWire implements RData. NS predates RFC 3597, so its name is compressible.
type NSEC ¶
type NSEC struct {
// NextDomain is the next owner name in the zone's canonical ordering. In the
// last NSEC of a zone it wraps around to the apex.
NextDomain Name
// TypeBitMap lists the types present at the owner name, which is what turns
// an NSEC into a proof that a particular type does not exist there.
TypeBitMap []Type
}
NSEC proves that a name does not exist by naming the next one that does (RFC 4034 section 4).
func (*NSEC) AppendWire ¶
AppendWire implements RData. RFC 4034 section 4.1.1 forbids compressing the next domain name, for the same reason RRSIG's signer name is uncompressed: the octets are covered by a signature.
type NSEC3 ¶
type NSEC3 struct {
// Hash is the hash algorithm; only 1 (SHA-1) is defined.
Hash uint8
// Flags carries the Opt-Out bit (0x01); the rest are reserved.
Flags uint8
// Iterations is the number of additional hash iterations. RFC 9276 advises
// zero, and a resolver should treat large values as a denial-of-service
// vector rather than a security feature.
Iterations uint16
// Salt is prefixed on the wire by a one-octet length. An empty salt is legal
// and encodes as a length of zero, which is distinct from a salt of zeroes
// and presents as "-".
Salt []byte
// NextHashedOwner is the next hashed owner name in the chain, also carried
// with a one-octet length prefix. It is unmodified binary, not base32: the
// base32hex spelling exists only in presentation format.
NextHashedOwner []byte
// TypeBitMap lists the types present at the original, unhashed owner name.
TypeBitMap []Type
}
NSEC3 proves non-existence over hashed owner names (RFC 5155 section 3).
Hashing the names is what stops an NSEC chain from being walked to enumerate a zone; the cost is the iteration count and salt below, which a resolver must bound because they are attacker-influenced work multipliers.
func (*NSEC3) AppendWire ¶
AppendWire implements RData. Both variable-length fields are truncated to 255 octets, the most their length prefix can express; a longer value could not be decoded back and so must not be written.
func (*NSEC3) String ¶
String implements RData. The next hashed owner is base32hex without padding per RFC 5155 section 3.3, which sorts in the same order as the binary hashes and so keeps a printed chain readable in sequence.
type NSEC3PARAM ¶
NSEC3PARAM tells an authoritative server which NSEC3 parameters to use when it answers from the zone (RFC 5155 section 4). It is the same header as NSEC3 with the chain fields removed.
func (*NSEC3PARAM) AppendWire ¶
func (r *NSEC3PARAM) AppendWire(dst []byte, e *Encoder) []byte
AppendWire implements RData.
type NULL ¶
type NULL struct {
Data []byte
}
NULL is an experimental record that may hold anything at all (RFC 1035 section 3.3.10).
It is registered so that a NULL record has a named Go type rather than arriving as Unknown, which keeps policy rules and logs able to talk about it. The contents stay opaque, because by definition they have no structure.
func (*NULL) AppendWire ¶
AppendWire implements RData.
func (*NULL) String ¶
String implements RData. RFC 1035 section 3.3.10 forbids NULL in a master file, so there is no native presentation format and the RFC 3597 section 5 generic encoding is used instead.
type Name ¶
type Name struct {
// contains filtered or unexported fields
}
Name is a fully qualified DNS domain name.
A Name stores the *uncompressed wire encoding* of the name — a sequence of length-prefixed labels terminated by a zero octet — in an immutable string. Three properties follow from that representation, and together they are why it was chosen over presentation format:
- Names are comparable with == and usable directly as map keys, which the cache and the policy engine both rely on.
- Writing a name to the wire is a single copy, with no re-encoding, on the hottest path in the server.
- The representation is exact. Names containing dots, non-ASCII octets or unprintable bytes survive a round trip unchanged, because they are never converted to text unless the caller asks.
The cost is that Name.String must build presentation format on demand. A resolver compares names orders of magnitude more often than it prints them, so this is the correct side of the trade.
Names compare case-insensitively over ASCII, as required by RFC 4343, while preserving the case in which they were received. That is what makes DNS-0x20 query randomisation possible: the query and response names must match case-insensitively but carry meaningful case.
The zero Name is invalid. Use ParseName, MustParseName, NameFromLabels or Root. Because Name wraps a string, the zero value is safe to compare and to pass around; only encoding it is an error.
func MustParseName ¶
MustParseName is like ParseName but panics on error. It is intended for package-level variables and tests, where a malformed constant is a programmer error rather than a runtime condition.
func NameFromLabels ¶
NameFromLabels builds a Name from raw label octets, applying no escape processing. Each label is used exactly as given, so a label may contain dots, backslashes or any other octet. This is the correct constructor when labels come from a structured source rather than from text.
func NameFromWire ¶
NameFromWire validates the uncompressed wire encoding in b and returns the corresponding Name, copying the input. Compression pointers are rejected; only the Decoder resolves those, because only it has the whole message.
func ParseName ¶
ParseName converts a domain name from presentation format, as defined by RFC 1035 section 5.1, to a Name.
The trailing dot is optional: "example.com" and "example.com." both parse to the same absolute name, since this package has no notion of a relative name or an origin to resolve one against. The empty string and "." both denote the root.
Backslash escapes are honoured: `\.` and `\\` are literal characters, and `\DDD` is the decimal byte value DDD. Any octet may be expressed this way, which is what allows names that are not valid host names to be represented.
ParseName reports failure as an error where ParseType and ParseClass report it as a bool, and the difference is deliberate rather than an oversight. A name can be rejected for four distinguishable reasons — ErrEmptyLabel, ErrLabelTooLong, ErrNameTooLong, ErrBadEscape — and an operator staring at a rejected configuration line needs to be told which; Name.UnmarshalText also has to produce one. A type or class mnemonic has exactly one failure mode, "no such mnemonic", so an error value there would carry no information the bool does not, at the cost of the `if t, ok :=` idiom. The rule for any Parse function added later is that one: an error when the causes are worth distinguishing, a bool when there is only ever one.
Example ¶
ExampleParseName shows presentation format going in and coming back out, including the escape mechanism that lets a label hold an octet — here a dot — that the presentation syntax otherwise reserves.
package main
import (
"fmt"
"log"
"github.com/daboss2003/dns/dnsmsg"
)
func main() {
n, err := dnsmsg.ParseName("www.Example.COM")
if err != nil {
log.Fatal(err)
}
// The trailing dot is optional on input and always present on output:
// every Name is absolute, because this package has no notion of an origin
// to resolve a relative name against.
fmt.Println(n)
fmt.Println(n.Canonical())
fmt.Println("labels:", n.LabelCount(), "wire octets:", n.WireLen())
// A label may contain any octet at all. `\.` is a literal dot inside one
// label rather than a label separator, so this name has three labels and
// not four.
odd := dnsmsg.MustParseName(`weird\.label.example.com.`)
fmt.Println("labels:", odd.LabelCount(), "first:", odd.Labels()[0])
}
Output: www.Example.COM. www.example.com. labels: 3 wire octets: 17 labels: 3 first: weird\.label
func (Name) AppendWire ¶
AppendWire appends the uncompressed wire encoding of n to dst and returns the extended buffer. It panics on the zero Name, which has no encoding; callers decoding untrusted input should check Name.IsZero first.
func (Name) Canonical ¶
Canonical returns n with all ASCII letters lower-cased, the form defined by RFC 4034 section 6.2. Canonical names are what the cache and the policy engine key on, so that lookups are case-insensitive without paying for a case-insensitive comparison at every level.
Canonical does not allocate when n is already canonical, which is the common case for names produced by this package's own machinery.
func (Name) Compare ¶
Compare orders names by the canonical DNS name order of RFC 4034 section 6.1: labels are compared right to left, case-insensitively, as unsigned octets. It returns a negative number, zero, or a positive number as n sorts before, equal to, or after other.
The zero Name sorts before every valid name, including the root. It has no labels, so an order defined purely over label sequences would put it exactly where the root is — and an index built on Compare would then answer a lookup for an uninitialised Name with the root's entry. Ordering it first keeps the invariant that matters instead: Compare returns zero exactly when Name.Equal reports true, so Compare, Equal and Name.IsZero tell one story. Two zero Names compare equal, which is what a total order requires.
This is the ordering DNSSEC denial-of-existence proofs are expressed in, and the ordering the policy engine uses to make longest-suffix matching deterministic.
func (Name) Equal ¶
Equal reports whether n and other denote the same domain name, comparing labels case-insensitively over ASCII as RFC 4343 requires.
Equal is the correct comparison for DNS semantics. The == operator compares names byte for byte and so distinguishes "Example.COM." from "example.com."; that is occasionally what you want (verifying a DNS-0x20 echo, for instance) but it is not name equality.
Example ¶
ExampleName_Equal contrasts Equal with ==.
Both comparisons are useful and they are not interchangeable. Equal is DNS name equality, which RFC 4343 defines as case-insensitive over ASCII. The == operator compares the wire encoding byte for byte, which is what you want when the case itself carries information — verifying a DNS-0x20 echo, for instance — and is a bug anywhere else.
package main
import (
"fmt"
"github.com/daboss2003/dns/dnsmsg"
)
func main() {
a := dnsmsg.MustParseName("Example.COM.")
b := dnsmsg.MustParseName("example.com.")
fmt.Println("a == b: ", a == b)
fmt.Println("a.Equal(b): ", a.Equal(b))
// Canonical lower-cases the name, which reconciles the two: canonical names
// may safely be compared with == and used as map keys, and that is exactly
// what the cache and the policy engine do.
fmt.Println("canonical ==:", a.Canonical() == b.Canonical())
seen := make(map[dnsmsg.Name]int)
seen[a.Canonical()]++
seen[b.Canonical()]++
fmt.Println("map entries: ", len(seen))
// Case is preserved rather than folded away, so the original spelling is
// still there to be echoed back or checked.
fmt.Println("a still reads:", a)
}
Output: a == b: false a.Equal(b): true canonical ==: true map entries: 1 a still reads: Example.COM.
func (Name) IsCanonical ¶
IsCanonical reports whether n contains no upper-case ASCII letters, and so is already in the canonical form used for cache keys and DNSSEC.
func (Name) IsSubDomainOf ¶
IsSubDomainOf reports whether n is equal to or hierarchically below parent. Every name, including the root itself, is a subdomain of the root.
func (Name) IsZero ¶
IsZero reports whether n is the zero Name, which carries no encoding at all and is distinct from the root name.
func (Name) Label ¶
Label returns the i'th label of n as raw octets, counting from zero at the leftmost label. It returns nil if i is out of range.
The result is a fresh copy that the caller owns and may modify freely: a Name stores its encoding in a string, and converting a string to a []byte always copies, so Label costs one allocation per call. There is no way to hand out the internal storage as a slice without making Names mutable, which would cost them their comparability. Use Name.LabelString where the octets are only read.
func (Name) LabelCount ¶
LabelCount returns the number of labels in n, not counting the root. The root name has zero labels; "example.com." has two.
func (Name) LabelString ¶
LabelString returns the i'th label of n as raw octets in a string, counting from zero at the leftmost label, and reports whether i was in range.
It is the allocation-free form of Name.Label: the result shares n's immutable storage, which is only safe because a string cannot be written through. Prefer it on hot paths — a longest-suffix policy walk touches every label of every query — and use Label only when the octets must be modified.
The octets are returned exactly as stored, with no presentation-format escaping; Name.Labels applies that.
func (Name) Labels ¶
Labels returns the labels of n in presentation format, outermost first, with escaping applied. The root label is not included. It allocates; prefer Name.LabelCount or Name.Parent on hot paths.
func (Name) MarshalText ¶
MarshalText implements encoding.TextMarshaler, emitting presentation format. This makes Name usable directly in JSON configuration and structured log records.
func (Name) Parent ¶
Parent returns n with its leftmost label removed, and reports whether such a name exists. The root has no parent. Parent does not allocate: the result shares storage with n.
func (Name) Prepend ¶
Prepend returns a new Name with label prepended to n, for example turning "example.com." into "_tcp.example.com.". The label is used verbatim, with no escape processing.
func (Name) String ¶
String returns n in presentation format, always with a trailing dot. Octets that are not printable ASCII, along with '.' and '\\', are escaped as described in RFC 1035 section 5.1. The zero Name renders as "<invalid>".
func (*Name) UnmarshalText ¶
UnmarshalText implements encoding.TextUnmarshaler.
type OPENPGPKEY ¶
type OPENPGPKEY struct {
// PublicKey is an OpenPGP transferable public key in binary form — not the
// ASCII-armoured form, which RFC 7929 section 2.3 explicitly excludes.
PublicKey []byte
}
OPENPGPKEY publishes an OpenPGP public key for the email address encoded in the owner name (RFC 7929). The RDATA is nothing but the key.
func (*OPENPGPKEY) AppendWire ¶
func (r *OPENPGPKEY) AppendWire(dst []byte, _ *Encoder) []byte
AppendWire implements RData.
type OPT ¶
type OPT struct {
Options []EDNSOption
}
OPT is the RDATA of an EDNS(0) pseudo-record: a sequence of options.
The payload size, version, extended RCODE and flags are not stored here. They live in the enclosing RR's Class and TTL fields, where the wire format puts them; use Message.EDNS and Message.SetEDNS to read and write them without doing the bit manipulation by hand.
func (*OPT) AppendWire ¶
AppendWire implements RData.
func (*OPT) Copy ¶
Copy implements RData. An absent option list stays nil rather than becoming an empty slice, per the RData.Copy contract: the two encode identically but compare unequal, and an OPT record with no options is the common case.
func (*OPT) Option ¶
func (r *OPT) Option(code EDNSOptionCode) (EDNSOption, bool)
Option returns the first option with the given code, and whether one was present.
type Opcode ¶
type Opcode uint8
Opcode identifies the kind of query in a DNS message header.
const ( OpcodeQuery Opcode = 0 // standard query, RFC 1035 OpcodeIQuery Opcode = 1 // inverse query, obsoleted by RFC 3425 OpcodeStatus Opcode = 2 // server status request, RFC 1035 OpcodeNotify Opcode = 4 // zone change notification, RFC 1996 OpcodeUpdate Opcode = 5 // dynamic update, RFC 2136 OpcodeDSO Opcode = 6 // stateful operations, RFC 8490 )
Opcodes.
type PTR ¶
type PTR struct {
Target Name
}
PTR is a domain name pointer, used for reverse lookups (RFC 1035 section 3.3.12).
func (*PTR) AppendWire ¶
AppendWire implements RData.
type PackOptions ¶
type PackOptions struct {
// MaxSize limits the encoded message, in octets.
//
// Zero means [MaxMessageSize], which is the ceiling the two-octet TCP length
// prefix of RFC 1035 section 4.2.2 imposes on every DNS message regardless of
// transport. There is deliberately no "unlimited" setting: a message that
// cannot be expressed in 65535 octets cannot be sent, so producing one would
// only defer the failure to the transport.
//
// When the message does not fit, it is truncated per RFC 2181 section 9:
// records are dropped from the end, the OPT record is retained as RFC 6891
// section 7 requires, and the TC bit is set if any answer or authority
// record was dropped. If the header, question section and OPT record alone
// exceed MaxSize, [ErrBufferTooSmall] is returned.
MaxSize int
// NoCompression disables domain name compression entirely. Required when
// producing the canonical form for a signature, and useful when a peer is
// known to mishandle pointers.
NoCompression bool
// NoTruncate makes an over-size message an error rather than truncating it.
NoTruncate bool
}
PackOptions configures message encoding. The zero value means: compress names, truncate to the protocol maximum if necessary.
type Question ¶
Question is an entry in the question section.
type RCode ¶
type RCode uint16
RCode is a DNS response code. Values above 15 are only expressible on the wire when the message carries an OPT record, whose TTL field supplies the upper eight bits (RFC 6891 section 6.1.3).
const ( RCodeSuccess RCode = 0 // NOERROR RCodeFormatError RCode = 1 // FORMERR RCodeServerFailure RCode = 2 // SERVFAIL RCodeNameError RCode = 3 // NXDOMAIN RCodeNotImplemented RCode = 4 // NOTIMP RCodeRefused RCode = 5 // REFUSED RCodeYXDomain RCode = 6 // name exists when it should not RCodeYXRRSet RCode = 7 // RR set exists when it should not RCodeNXRRSet RCode = 8 // RR set that should exist does not RCodeNotAuth RCode = 9 // server not authoritative / not authorized RCodeNotZone RCode = 10 // name not contained in zone RCodeDSOTypeNI RCode = 11 // DSO-TYPE not implemented RCodeBadVers RCode = 16 // bad OPT version / TSIG signature failure RCodeBadKey RCode = 17 RCodeBadTime RCode = 18 RCodeBadMode RCode = 19 RCodeBadName RCode = 20 RCodeBadAlg RCode = 21 RCodeBadTrunc RCode = 22 RCodeBadCookie RCode = 23 )
Response codes.
type RData ¶
type RData interface {
// Type returns the record type this payload encodes. It must match the
// Type field of any RR carrying it.
Type() Type
// AppendWire appends the RDATA to dst, excluding the two-octet RDLENGTH
// prefix, and returns the extended buffer.
//
// The [Encoder] supplies itself so that implementations of the handful of
// record types where RFC 1035 permits name compression can use
// [Encoder.AppendName]. Every other type must use
// [Encoder.AppendNameUncompressed]: RFC 3597 section 4 forbids compressing
// names in record types defined after RFC 1035, because a receiver that
// does not know the type cannot find the names to decompress them.
AppendWire(dst []byte, e *Encoder) []byte
// String returns the RDATA in master-file presentation format, without the
// owner name, TTL, class or type.
String() string
// Copy returns a deep copy. Implementations that hold only immutable values
// may return themselves.
//
// A copy is required to satisfy reflect.DeepEqual against its source, which
// means a nil slice must be copied as nil rather than as an empty non-nil
// slice. The two encode identically, so the distinction looks cosmetic, but
// it is not: a zero-length RDATA field decodes to nil, and [Message.Copy] is
// the documented mechanism by which a cache hands one message to many
// readers. A copy that compared unequal to the record it was made from would
// give any cache that de-duplicates or revalidates by DeepEqual a false
// negative — on exactly the empty-field records that arrive most often.
// [copyBytes], [copyStrings] and [copyTypes] implement the rule.
Copy() RData
}
RData is the type-specific payload of a resource record.
The interface is exported and complete: a caller outside this package can implement a record type GatewayDNS has never heard of, register it with a Registry, and have it decode, encode and print exactly like a built-in. That is the extension point the plugin architecture depends on, and it is why none of these methods are unexported.
Implementations must be safe for concurrent reading once constructed. The built-in implementations are value types or hold slices they never mutate.
An implementation holding a length-prefixed or count-prefixed field should also satisfy Validator, the optional interface the encoder consults before writing a record. RData.AppendWire returns only a buffer, so it is the only way to refuse a value that has no faithful wire form instead of truncating it into a different record that still parses.
type RP ¶
type RP struct {
// Mailbox is the responsible party's address with '@' written as a dot. The
// root name means no mailbox is published.
Mailbox Name
// TXTDomain owns TXT records with further contact information, or is the
// root when there are none.
TXTDomain Name
}
RP names the person responsible for a host (RFC 1183 section 2.2).
Although RP reads like an RFC 1035 record and carries two plain domain names, it was defined by RFC 1183. RFC 3597 section 4 draws the compression boundary at "the RR types defined in RFC 1035", so RP falls outside it and both names are written in full. Compressing them would corrupt the record for any receiver that treats RP as an unknown type.
func (*RP) AppendWire ¶
AppendWire implements RData. Both names are uncompressed; see the type comment.
type RR ¶
RR is a resource record: an owner name, a type, a class, a time to live, and a type-specific payload.
Type is stored explicitly rather than derived from Data because the wire format stores it explicitly, and because keeping them separate makes the Unknown passthrough and meta types representable without special cases. The encoder rejects records whose Type disagrees with Data.Type.
An OPT record is an RR like any other, but two of its fields do not mean what their names say: RFC 6891 section 6.1.2 puts the requestor's UDP payload size in Class, and the extended RCODE, the EDNS version and the DO bit in TTL. The encoder owns the extended-RCODE octet of that TTL and overwrites it from Header.RCode on every pack, so a response code set here is discarded; set it on the header. For the rest, prefer Message.EDNS and Message.SetEDNS over packing the fields by hand.
func (RR) Copy ¶
Copy returns a deep copy of rr, duplicating the RDATA payload.
Assigning an RR is not enough: RData implementations holding slices share them until RData.Copy duplicates them, while immutable ones return themselves. Copy is the per-record half of Message.Copy, exported so that every cache built on this package does not reimplement that contract, each getting it slightly differently wrong.
func (RR) Equal ¶
Equal reports whether rr and other are the same record. Owner names are compared case-insensitively over ASCII as RFC 4343 requires; the payloads are compared by their uncompressed wire encoding.
Comparing the encoding rather than the Go values makes the answer depend on the record instead of on how it was decoded: a type with no registered decoder arrives as Unknown and still compares equal to the same record decoded by a registry that knows the type. It also avoids putting an Equal method on RData, which would close that interface to the third-party implementations it exists for.
The TTL participates, because Equal is equality of the struct as written. The RFC 2181 section 5 notion of record identity — the one an RRset and duplicate suppression use — deliberately excludes the TTL; a caller wanting that predicate should compare the fields it cares about directly.
func (RR) String ¶
String renders the record in master-file presentation format.
The rendering is one-way: this package has no master-file parser, so RR deliberately does not implement encoding.TextMarshaler the way Name does. A marshaller with no matching unmarshaller round-trips nothing, and would invite configuration formats that can be written but not read back.
type RRSIG ¶
type RRSIG struct {
// TypeCovered is the type of the RRset this signature authenticates.
TypeCovered Type
// Algorithm is the DNSSEC algorithm number of the signing key.
Algorithm uint8
// Labels is the number of labels in the original owner name, excluding the
// root and any leading wildcard. A validator compares it against the query
// name to detect that an answer was synthesised from a wildcard.
Labels uint8
// OrigTTL is the TTL of the covered RRset as it appears in the zone, which
// must be restored before the signature is verified because caching will
// have decremented the TTL in transit.
OrigTTL uint32
// Expiration is the end of the signature's validity, in seconds since the
// Unix epoch.
Expiration uint32
// Inception is the start of the signature's validity, in seconds since the
// Unix epoch.
Inception uint32
// KeyTag identifies the DNSKEY that produced Signature. See [DNSKEY.KeyTag].
KeyTag uint16
// SignerName is the owner name of the DNSKEY that produced Signature, and
// must be the zone the covered RRset lives in.
SignerName Name
// Signature is the algorithm-specific signature over the canonical form of
// the covered RRset.
Signature []byte
}
RRSIG holds the signature over one RRset (RFC 4034 section 3).
func (*RRSIG) AppendWire ¶
AppendWire implements RData. The signer name is written uncompressed: RFC 4034 section 3.1.7 requires it, because a validator recomputes these octets to check the signature and a compression pointer would make them depend on the rest of the message.
func (*RRSIG) String ¶
String implements RData. Timestamps use the YYYYMMDDHHmmSS UTC form of RFC 4034 section 3.2; the alternative bare-integer form is legal input but is not what any deployed tool emits.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry maps record types to the code that decodes them.
A Registry is the seam that keeps this package open to extension. Anything not registered decodes to Unknown and round-trips byte for byte, so an unregistered type is a loss of introspection, never a loss of data.
DefaultRegistry holds the built-in types and is what the decoder uses when no registry is supplied. Applications that want to add a private record type without perturbing global state should build their own with NewRegistry and pass it through UnpackOptions.Registry; applications shipping a plugin that should apply process-wide may register into the default.
The zero Registry is valid and empty; Registry.Register allocates its map on first use. A caller who declares a Registry as a field of a configuration struct, or as a package-level var, therefore gets a working registry without having to remember NewRegistry — which matters because every other method already worked on the zero value, so a missing constructor call would otherwise surface only as a panic on the first registration.
A Registry is safe for concurrent use.
Example ¶
ExampleRegistry registers a record type this package has never heard of.
The two Unpack calls decode the identical octets: the difference is only whether a decoder was available for the type. Without one the record still survives intact as Unknown, which is what lets a gateway forward traffic it does not understand.
package main
import (
"fmt"
"log"
"github.com/daboss2003/dns/dnsmsg"
)
// exampleTagType is a private-use record type. RFC 6895 section 3.1 reserves
// 65280 through 65534 for local experiments, which is where a type that will
// never be seen outside one deployment belongs.
const exampleTagType dnsmsg.Type = 65280
// exampleTag is a made-up record whose RDATA is a single <character-string>.
type exampleTag struct {
Tag string
}
func (r *exampleTag) Type() dnsmsg.Type { return exampleTagType }
func (r *exampleTag) AppendWire(dst []byte, e *dnsmsg.Encoder) []byte {
return e.AppendCharacterString(dst, r.Tag)
}
func (r *exampleTag) String() string { return `"` + r.Tag + `"` }
func (r *exampleTag) Copy() dnsmsg.RData { return r }
// decodeExampleTag is the DecodeFunc for exampleTag. The Decoder is already
// bounded to this record's RDLENGTH, so no length arithmetic is needed and none
// should be attempted.
func decodeExampleTag(d *dnsmsg.Decoder) (dnsmsg.RData, error) {
s, err := d.CharacterString()
if err != nil {
return nil, err
}
return &exampleTag{Tag: s}, nil
}
func main() {
reg := dnsmsg.DefaultRegistry().Clone()
reg.MustRegister(exampleTagType, decodeExampleTag)
var m dnsmsg.Message
m.Response = true
m.Answers = append(m.Answers, dnsmsg.RR{
Name: dnsmsg.MustParseName("host.example."),
Type: exampleTagType,
Class: dnsmsg.ClassINET,
TTL: 60,
Data: &exampleTag{Tag: "rack-17"},
})
wire, err := m.Pack()
if err != nil {
log.Fatal(err)
}
// The default registry has no decoder for 65280, so the payload is kept
// verbatim and prints in the RFC 3597 generic form.
opaque, err := dnsmsg.Unpack(wire)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%T %s\n", opaque.Answers[0].Data, opaque.Answers[0].Data)
// With the cloned registry the same octets become a Go value with fields.
decoded, err := dnsmsg.UnpackWith(wire, &dnsmsg.UnpackOptions{Registry: reg})
if err != nil {
log.Fatal(err)
}
fmt.Printf("%T %s\n", decoded.Answers[0].Data, decoded.Answers[0].Data)
fmt.Println("tag:", decoded.Answers[0].Data.(*exampleTag).Tag)
}
Output: *dnsmsg.Unknown \# 8 077261636B2D3137 *dnsmsg_test.exampleTag "rack-17" tag: rack-17
func DefaultRegistry ¶
func DefaultRegistry() *Registry
DefaultRegistry returns the registry used when UnpackOptions.Registry is nil. It contains every record type this package implements natively.
The registry is built on first use rather than during package initialisation, because the built-in set is assembled by init functions across several files and Go does not order package-level variable initialisation against them.
func NewRegistry ¶
func NewRegistry() *Registry
NewRegistry returns an empty Registry. Types it does not know decode to Unknown; call Registry.RegisterBuiltins to seed it with the record types this package implements.
func (*Registry) Clone ¶
Clone returns an independent copy of r, which is the safe way to derive a registry that adds a type to the built-in set without mutating it.
func (*Registry) Lookup ¶
func (r *Registry) Lookup(t Type) DecodeFunc
Lookup returns the decoder registered for t, or nil.
func (*Registry) MustRegister ¶
func (r *Registry) MustRegister(t Type, fn DecodeFunc)
MustRegister is like Registry.Register but panics on error. It is intended for package initialisation, where a bad registration is a programmer error.
func (*Registry) Register ¶
func (r *Registry) Register(t Type, fn DecodeFunc) error
Register associates a decoder with a record type, replacing any existing entry. It returns an error for the query-only types, which never carry stored RDATA, and for TypeOPT, whose payload is not free to reinterpret.
OPT is refused because its RDATA is not private to the record: Message.EDNS, Message.UDPSize, Message.SetExtendedError and the RFC 6891 section 6.1.3 extended-RCODE split all reach into the decoded *OPT value. A replacement decoder returns some other type, those accessors stop matching, and EDNS quietly stops working for every message in the process — with no error at the point of the mistake to explain it. Since DefaultRegistry is process-wide, one plugin could do that to an entire binary.
TSIG and TKEY are deliberately *not* refused, even though Type.IsMeta reports true for them. They are pseudo-records in the sense that they must not be cached, but they do carry real RDATA on the wire and this package does not implement them, so registering a decoder for one is exactly the extension the Registry exists to allow.
func (*Registry) RegisterBuiltins ¶
func (r *Registry) RegisterBuiltins()
RegisterBuiltins adds every record type implemented by this package to r, replacing any conflicting entries. Use it to seed a registry created with NewRegistry that should extend, rather than replace, the standard set.
It goes through the internal path rather than Registry.Register because the built-in set includes OPT, which Register refuses to third parties for reasons that do not apply to this package's own decoder.
type SMIMEA ¶
SMIMEA associates an S/MIME certificate with an email address (RFC 8162). It is TLSA's wire format applied to a different owner-name convention, and is a separate type only so that the two cannot be confused.
func (*SMIMEA) AppendWire ¶
AppendWire implements RData.
type SOA ¶
type SOA struct {
NS Name // primary name server
Mailbox Name // responsible party, with '@' expressed as a dot
Serial uint32 // zone version
Refresh uint32 // seconds before a secondary should re-check
Retry uint32 // seconds before retrying a failed refresh
Expire uint32 // seconds before a secondary stops answering
Minimum uint32 // negative caching TTL, per RFC 2308
}
SOA marks the start of a zone of authority (RFC 1035 section 3.3.13).
Its Minimum field is what RFC 2308 redefines as the negative caching TTL, and is the value the resolver's negative cache reads.
func (*SOA) AppendWire ¶
AppendWire implements RData.
type SPF ¶
type SPF struct {
Strings []string
}
SPF is the deprecated Sender Policy Framework record (RFC 4408, obsoleted by RFC 7208). It shares TXT's wire format and is retained so that records still published in the wild decode meaningfully.
func (*SPF) AppendWire ¶
AppendWire implements RData.
type SRV ¶
SRV locates the service named by the owner name (RFC 2782).
func (*SRV) AppendWire ¶
AppendWire implements RData. RFC 2782 states explicitly that the target is not compressed.
type SSHFP ¶
type SSHFP struct {
// Algorithm is the SSH public key algorithm.
Algorithm uint8
// FPType is the fingerprint type — the "fingerprint type" field of RFC 4255
// section 3.1.2. It is not named Type because [RData] requires a Type method
// and Go forbids a struct having both a field and a method of one name.
FPType uint8
// FingerPrint is the raw fingerprint, whose length follows from FPType.
FingerPrint []byte
}
SSHFP publishes a fingerprint of an SSH host key (RFC 4255), so that a client can authenticate a host on first contact instead of trusting it blindly.
func (*SSHFP) AppendWire ¶
AppendWire implements RData.
type SVCB ¶
type SVCB struct {
// Priority is the SvcPriority field. Zero selects AliasMode.
Priority uint16
// Target is the TargetName field. RFC 9460 section 2.2 forbids compressing
// it, so it is always written out in full.
Target Name
// Params are the SvcParams, in ascending key order.
Params []SvcParam
}
SVCB is a service binding record (RFC 9460 section 2).
A record is in one of two modes, distinguished by Priority. Priority zero is AliasMode: the record is a pure delegation to Target and, per RFC 9460 section 2.4.2, carries no parameters at all. Any other priority is ServiceMode: Target is the endpoint to connect to and Params describe how. The distinction is not decoration — a client that treats an AliasMode record as ServiceMode will connect to the wrong host — so the decoder enforces it.
Params are held in the ascending key order RFC 9460 section 2.2 mandates. Decoding guarantees that order; encoding and printing restore it if a caller built the record by hand, without mutating the caller's slice.
func (*SVCB) AppendWire ¶
AppendWire implements RData.
The target name is written uncompressed, as RFC 9460 section 2.2 requires and RFC 3597 section 4 requires of every type defined after RFC 1035: a receiver that does not implement SVCB cannot know a name is in there and so could never expand a pointer.
Parameters are emitted in ascending key order regardless of the order they are held in, because an unsorted list is invalid on the wire and a peer is entitled to reject the whole message over it. Nothing else is repaired here: this method cannot report an error, so every rule a re-ordering cannot fix lives in SVCB.Validate, which the encoder calls first.
func (*SVCB) Copy ¶
Copy implements RData. The parameter list is duplicated because a caller may append to it, and each parameter is copied because some of them own slices.
func (*SVCB) IsAlias ¶
IsAlias reports whether r is in AliasMode, that is, whether it delegates to Target rather than describing an endpoint (RFC 9460 section 2.4.2).
func (*SVCB) Param ¶
func (r *SVCB) Param(key SvcParamKey) (SvcParam, bool)
Param returns the parameter with the given key, and whether one is present. It is the accessor the ECH and ALPN consumers actually want; the alternative is for every caller to re-implement the same linear scan.
func (*SVCB) String ¶
String implements RData, producing the RFC 9460 section 2.1 presentation form: the priority, the target, then each parameter in ascending key order.
func (*SVCB) Validate ¶
Validate implements Validator, enforcing the RFC 9460 rules that SVCB.AppendWire cannot repair on its own: a real target name, an AliasMode record carrying no parameters (section 2.4.2), unique parameter keys (section 2.2), and a well-formed "mandatory" list (section 8).
The encoder calls it before writing the record, which is what stops this package emitting octets its own decoder — and every conforming peer — rejects. Advisory validation was not enough: a record built by hand is published once and then fails at whichever hop parses it, far from the code that built it.
Ordering is deliberately not checked. AppendWire and String sort on the way out, so a hand-built record whose parameters merely arrived in the wrong order is normalised rather than refused; only a defect that survives sorting is an error.
type Section ¶
type Section uint8
Section identifies one of the four sections of a DNS message.
type SvcParam ¶
type SvcParam interface {
// Key returns the parameter's key. It must match the key the parameter is
// stored under.
Key() SvcParamKey
// AppendWire appends the parameter value to dst, excluding the key and the
// two-octet length prefix, and returns the extended buffer. No [Encoder] is
// supplied because no parameter value may contain a domain name.
AppendWire(dst []byte) []byte
// String returns the whole parameter in RFC 9460 section 2.1 presentation
// format, key included: "alpn=h2,h3", "port=8443", or for a parameter whose
// value is necessarily empty the bare key alone, "no-default-alpn". The key
// is part of the rendering rather than being prefixed by the caller because
// only the parameter itself knows whether an "=" belongs there.
String() string
// Copy returns a deep copy. Implementations holding only immutable values
// may return themselves.
Copy() SvcParam
}
SvcParam is a single service parameter.
As with RData and EDNSOption, the interface is fully exported so that an application can implement a parameter this package does not know about and have it encode and print correctly alongside the built-in ones. Anything unrecognised decodes to SvcParamValueUnknown and round-trips verbatim, so an unimplemented key is a loss of introspection and never a loss of data.
Implementations must be safe for concurrent reading once constructed.
type SvcParamKey ¶
type SvcParamKey uint16
SvcParamKey identifies a service parameter (RFC 9460 section 14.3.2).
const ( SvcParamMandatory SvcParamKey = 0 // RFC 9460 section 8 SvcParamALPN SvcParamKey = 1 // RFC 9460 section 7.1 SvcParamNoDefaultALPN SvcParamKey = 2 // RFC 9460 section 7.1 SvcParamPort SvcParamKey = 3 // RFC 9460 section 7.2 SvcParamIPv4Hint SvcParamKey = 4 // RFC 9460 section 7.3 SvcParamECH SvcParamKey = 5 // RFC 9460 section 14.3.2 SvcParamIPv6Hint SvcParamKey = 6 // RFC 9460 section 7.3 SvcParamDoHPath SvcParamKey = 7 // RFC 9461 section 5 SvcParamOHTTP SvcParamKey = 8 // RFC 9540 section 4 SvcParamTLSSupportedGroups SvcParamKey = 9 // draft-ietf-tls-key-share-prediction )
Service parameter keys from the IANA "Service Parameter Keys (SvcParamKeys)" registry.
func (SvcParamKey) String ¶
func (k SvcParamKey) String() string
String returns the registered name for k, or the generic "keyNNNNN" form of RFC 9460 section 14.3, written without leading zeros.
type SvcParamValueALPN ¶
type SvcParamValueALPN struct {
// Protocols are the alpn-ids, in the order the record lists them. The order
// is preserved because RFC 9460 does not define one and the operator's
// preference is the only meaning it can carry.
Protocols []string
}
SvcParamValueALPN lists the application protocols the endpoint supports (RFC 9460 section 7.1), using the identifiers of the IANA ALPN registry.
This is how a browser learns that an origin speaks HTTP/3 without first connecting over HTTP/2 and reading an Alt-Svc header, so dropping or reordering it costs a round trip on every cold connection.
func (*SvcParamValueALPN) AppendWire ¶
func (p *SvcParamValueALPN) AppendWire(dst []byte) []byte
AppendWire implements SvcParam. Each identifier is written as a one-octet length prefix followed by its octets.
func (*SvcParamValueALPN) Copy ¶
func (p *SvcParamValueALPN) Copy() SvcParam
Copy implements SvcParam.
func (*SvcParamValueALPN) Key ¶
func (p *SvcParamValueALPN) Key() SvcParamKey
Key implements SvcParam.
func (*SvcParamValueALPN) String ¶
func (p *SvcParamValueALPN) String() string
String implements SvcParam.
type SvcParamValueDoHPath ¶
type SvcParamValueDoHPath struct {
// Template is the URI template, held as received. RFC 9461 requires it to
// be UTF-8 and to contain a "dns" variable; both are the consumer's checks
// to make, because this package must forward a template it cannot use.
Template string
}
SvcParamValueDoHPath is the URI template of a DNS-over-HTTPS endpoint (RFC 9461 section 5). It is what makes a resolver discoverable over DoH without the client hard-coding a path.
func (*SvcParamValueDoHPath) AppendWire ¶
func (p *SvcParamValueDoHPath) AppendWire(dst []byte) []byte
AppendWire implements SvcParam.
func (*SvcParamValueDoHPath) Copy ¶
func (p *SvcParamValueDoHPath) Copy() SvcParam
Copy implements SvcParam. A string is immutable, so the value is shared.
func (*SvcParamValueDoHPath) Key ¶
func (p *SvcParamValueDoHPath) Key() SvcParamKey
Key implements SvcParam.
func (*SvcParamValueDoHPath) String ¶
func (p *SvcParamValueDoHPath) String() string
String implements SvcParam. The template is quoted because it routinely contains the "{?dns}" expansion, whose braces would otherwise run into whatever parameter follows it.
type SvcParamValueECH ¶
type SvcParamValueECH struct {
// Config is the ECHConfigList, exactly as it appeared on the wire.
Config []byte
}
SvcParamValueECH carries an ECHConfigList for Encrypted Client Hello (RFC 9460 section 14.3.2).
The contents are treated as opaque on purpose. ECHConfigList is versioned by the TLS working group and evolves independently of DNS; a resolver that parsed it would have to be updated in lockstep with TLS or start rejecting valid records, and a rejected ech parameter is a silent downgrade of the client's privacy. Passing the octets through untouched is both simpler and safer.
func (*SvcParamValueECH) AppendWire ¶
func (p *SvcParamValueECH) AppendWire(dst []byte) []byte
AppendWire implements SvcParam.
func (*SvcParamValueECH) Copy ¶
func (p *SvcParamValueECH) Copy() SvcParam
Copy implements SvcParam.
func (*SvcParamValueECH) Key ¶
func (p *SvcParamValueECH) Key() SvcParamKey
Key implements SvcParam.
func (*SvcParamValueECH) String ¶
func (p *SvcParamValueECH) String() string
String implements SvcParam. RFC 9460 section 14.3.2 defines the presentation value as the base64 encoding of the ECHConfigList.
type SvcParamValueIPv4Hint ¶
SvcParamValueIPv4Hint carries IPv4 addresses for the endpoint (RFC 9460 section 7.3), letting a client start connecting without a second lookup.
The addresses are a hint, not an authority: RFC 9460 section 7.3 is explicit that a client must still resolve the target name and must not cache these as A records. This package therefore keeps them inside the parameter rather than exposing them anywhere a cache might mistake them for answers.
func (*SvcParamValueIPv4Hint) AppendWire ¶
func (p *SvcParamValueIPv4Hint) AppendWire(dst []byte) []byte
AppendWire implements SvcParam.
func (*SvcParamValueIPv4Hint) Copy ¶
func (p *SvcParamValueIPv4Hint) Copy() SvcParam
Copy implements SvcParam.
func (*SvcParamValueIPv4Hint) Key ¶
func (p *SvcParamValueIPv4Hint) Key() SvcParamKey
Key implements SvcParam.
func (*SvcParamValueIPv4Hint) String ¶
func (p *SvcParamValueIPv4Hint) String() string
String implements SvcParam.
type SvcParamValueIPv6Hint ¶
SvcParamValueIPv6Hint carries IPv6 addresses for the endpoint (RFC 9460 section 7.3). The same "hint, not an answer" rule applies as for SvcParamValueIPv4Hint.
func (*SvcParamValueIPv6Hint) AppendWire ¶
func (p *SvcParamValueIPv6Hint) AppendWire(dst []byte) []byte
AppendWire implements SvcParam.
The sixteen-octet form is written unconditionally. Encoder.AppendAddr would be wrong here: it narrows an IPv4-mapped address to four octets, which is right for an A record and would silently corrupt an ipv6hint entry such as ::ffff:192.0.2.1 into a four-octet field the receiver cannot parse.
func (*SvcParamValueIPv6Hint) Copy ¶
func (p *SvcParamValueIPv6Hint) Copy() SvcParam
Copy implements SvcParam.
func (*SvcParamValueIPv6Hint) Key ¶
func (p *SvcParamValueIPv6Hint) Key() SvcParamKey
Key implements SvcParam.
func (*SvcParamValueIPv6Hint) String ¶
func (p *SvcParamValueIPv6Hint) String() string
String implements SvcParam.
type SvcParamValueMandatory ¶
type SvcParamValueMandatory struct {
// Keys are the mandatory keys, without duplicates. Order is not required
// here: [SvcParamValueMandatory.AppendWire] sorts on the way out, because
// only the wire form has to be ascending.
Keys []SvcParamKey
}
SvcParamValueMandatory lists the keys a client must understand before it may use the record (RFC 9460 section 8).
The list is the mechanism by which a service operator can refuse to be downgraded: if a client does not implement every listed key it must treat the record as unusable rather than connect without the protection the key provides. That makes its rules worth enforcing strictly, and the ones that span the whole record — no self-reference, no duplicates, and no key the record does not actually carry — are checked by SVCB.Validate on both the encode and the decode path.
func (*SvcParamValueMandatory) AppendWire ¶
func (p *SvcParamValueMandatory) AppendWire(dst []byte) []byte
AppendWire implements SvcParam. The keys are sorted on the way out for the same reason the parameter list itself is: an unsorted list is invalid.
func (*SvcParamValueMandatory) Copy ¶
func (p *SvcParamValueMandatory) Copy() SvcParam
Copy implements SvcParam.
func (*SvcParamValueMandatory) Key ¶
func (p *SvcParamValueMandatory) Key() SvcParamKey
Key implements SvcParam.
func (*SvcParamValueMandatory) String ¶
func (p *SvcParamValueMandatory) String() string
String implements SvcParam.
type SvcParamValueNoDefaultALPN ¶
type SvcParamValueNoDefaultALPN struct{}
SvcParamValueNoDefaultALPN suppresses the protocol a client would otherwise assume for the scheme (RFC 9460 section 7.1). Its presence is the entire signal, so it has no value and no fields.
func (*SvcParamValueNoDefaultALPN) AppendWire ¶
func (p *SvcParamValueNoDefaultALPN) AppendWire(dst []byte) []byte
AppendWire implements SvcParam. The value is always empty.
func (*SvcParamValueNoDefaultALPN) Copy ¶
func (p *SvcParamValueNoDefaultALPN) Copy() SvcParam
Copy implements SvcParam. The value carries no state, so it is shared.
func (*SvcParamValueNoDefaultALPN) Key ¶
func (p *SvcParamValueNoDefaultALPN) Key() SvcParamKey
Key implements SvcParam.
func (*SvcParamValueNoDefaultALPN) String ¶
func (p *SvcParamValueNoDefaultALPN) String() string
String implements SvcParam. A parameter with a necessarily empty value is written as a bare key, with no "=".
type SvcParamValueOHTTP ¶
type SvcParamValueOHTTP struct{}
SvcParamValueOHTTP advertises that the endpoint is an Oblivious HTTP relay or gateway (RFC 9540 section 4). As with no-default-alpn, presence is the whole signal and the value is empty.
func (*SvcParamValueOHTTP) AppendWire ¶
func (p *SvcParamValueOHTTP) AppendWire(dst []byte) []byte
AppendWire implements SvcParam. The value is always empty.
func (*SvcParamValueOHTTP) Copy ¶
func (p *SvcParamValueOHTTP) Copy() SvcParam
Copy implements SvcParam. The value carries no state, so it is shared.
func (*SvcParamValueOHTTP) Key ¶
func (p *SvcParamValueOHTTP) Key() SvcParamKey
Key implements SvcParam.
func (*SvcParamValueOHTTP) String ¶
func (p *SvcParamValueOHTTP) String() string
String implements SvcParam, as a bare key with no "=".
type SvcParamValuePort ¶
type SvcParamValuePort struct {
Port uint16
}
SvcParamValuePort is the TCP or UDP port of the endpoint (RFC 9460 section 7.2), overriding the scheme's default.
func (*SvcParamValuePort) AppendWire ¶
func (p *SvcParamValuePort) AppendWire(dst []byte) []byte
AppendWire implements SvcParam.
func (*SvcParamValuePort) Copy ¶
func (p *SvcParamValuePort) Copy() SvcParam
Copy implements SvcParam. The value is immutable, so it is shared.
func (*SvcParamValuePort) Key ¶
func (p *SvcParamValuePort) Key() SvcParamKey
Key implements SvcParam.
func (*SvcParamValuePort) String ¶
func (p *SvcParamValuePort) String() string
String implements SvcParam.
type SvcParamValueUnknown ¶
type SvcParamValueUnknown struct {
// ParamKey is the numeric key.
ParamKey SvcParamKey
// Value is the raw parameter value. It is owned by the parameter and must
// not be modified by callers.
Value []byte
}
SvcParamValueUnknown preserves a parameter this package does not model, in the spirit of RFC 3597: the octets are kept verbatim so a forwarding resolver relays parameters that did not exist when it was compiled.
It is also what carries registered-but-unmodelled keys such as tls-supported-groups, which print under their registered name because SvcParamKey.String knows it even though no structure is imposed on the value.
func (*SvcParamValueUnknown) AppendWire ¶
func (p *SvcParamValueUnknown) AppendWire(dst []byte) []byte
AppendWire implements SvcParam.
func (*SvcParamValueUnknown) Copy ¶
func (p *SvcParamValueUnknown) Copy() SvcParam
Copy implements SvcParam.
func (*SvcParamValueUnknown) Key ¶
func (p *SvcParamValueUnknown) Key() SvcParamKey
Key implements SvcParam.
func (*SvcParamValueUnknown) String ¶
func (p *SvcParamValueUnknown) String() string
String implements SvcParam, using the generic form of RFC 9460 section 14.3: the key name followed by the value as a quoted, escaped string.
type SyntaxError ¶
type SyntaxError struct {
// Struct names the structure being decoded. It is "header", or a section
// name and index: "question[0]", "answer[2]", "authority[0]",
// "additional[1]". Per-record detail is not folded in here, because a
// caller that wants to know which record type failed already has the
// section and index with which to find it.
Struct string
// Offset is the octet offset into the message at which decoding failed.
Offset int
// Err is the underlying cause.
Err error
}
SyntaxError describes a failure to decode a message: the input did not conform to the wire format, annotated with the structure and offset at which that became apparent. The offset makes hostile input reproducible in a test without needing the original packet.
A SyntaxError always means the *input* was at fault, never the local program. See IsDecodeError.
func (*SyntaxError) Error ¶
func (e *SyntaxError) Error() string
Error implements the error interface.
func (*SyntaxError) Unwrap ¶
func (e *SyntaxError) Unwrap() error
Unwrap returns the underlying cause so that errors.Is works through the positional annotation.
type TLSA ¶
type TLSA struct {
// Usage says how the association constrains the peer's certificate chain:
// PKIX-TA, PKIX-EE, DANE-TA or DANE-EE.
Usage uint8
// Selector says whether Certificate matches the full certificate or only its
// SubjectPublicKeyInfo.
Selector uint8
// MatchingType says whether Certificate is the selected data verbatim or a
// digest of it.
MatchingType uint8
// Certificate is the certificate association data.
Certificate []byte
}
TLSA associates a TLS certificate or public key with a service (RFC 6698, DANE).
func (*TLSA) AppendWire ¶
AppendWire implements RData.
func (*TLSA) String ¶
String implements RData. RFC 6698 section 2.2 specifies hex, not base64, even though the data is usually a certificate.
type TXT ¶
type TXT struct {
Strings []string
}
TXT holds one or more <character-string>s (RFC 1035 section 3.3.14).
The strings are kept separate rather than concatenated because the split is semantically meaningful: DKIM and other consumers reassemble it themselves, and a record must round-trip exactly.
func (*TXT) AppendWire ¶
AppendWire implements RData.
func (*TXT) Copy ¶
Copy implements RData. A nil Strings survives as nil rather than becoming an empty slice, so a decoded record and its copy stay reflect.DeepEqual; callers that compare records to detect change depend on that.
func (*TXT) String ¶
String implements RData, quoting and escaping each string as master-file format requires.
func (*TXT) Validate ¶
Validate implements Validator. Each string is length-prefixed by a single octet, so one longer than MaxCharStringLen cannot be encoded; writing it anyway would truncate it into a different record that still parses, which is the worst possible failure mode.
type Type ¶
type Type uint16
Type is a resource record type as defined by the IANA "Resource Record (RR) TYPEs" registry.
const ( TypeNone Type = 0 TypeA Type = 1 TypeNS Type = 2 TypeMD Type = 3 TypeMF Type = 4 TypeCNAME Type = 5 TypeSOA Type = 6 TypeMB Type = 7 TypeMG Type = 8 TypeMR Type = 9 TypeNULL Type = 10 TypeWKS Type = 11 TypePTR Type = 12 TypeHINFO Type = 13 TypeMINFO Type = 14 TypeMX Type = 15 TypeTXT Type = 16 TypeRP Type = 17 TypeAFSDB Type = 18 TypeX25 Type = 19 TypeISDN Type = 20 TypeRT Type = 21 TypeNSAP Type = 22 TypeNSAPPTR Type = 23 TypeSIG Type = 24 TypeKEY Type = 25 TypePX Type = 26 TypeGPOS Type = 27 TypeAAAA Type = 28 TypeLOC Type = 29 TypeNXT Type = 30 TypeEID Type = 31 TypeNIMLOC Type = 32 TypeSRV Type = 33 TypeATMA Type = 34 TypeNAPTR Type = 35 TypeKX Type = 36 TypeCERT Type = 37 TypeA6 Type = 38 TypeDNAME Type = 39 TypeSINK Type = 40 TypeOPT Type = 41 TypeAPL Type = 42 TypeDS Type = 43 TypeSSHFP Type = 44 TypeIPSECKEY Type = 45 TypeRRSIG Type = 46 TypeNSEC Type = 47 TypeDNSKEY Type = 48 TypeDHCID Type = 49 TypeNSEC3 Type = 50 TypeNSEC3PARAM Type = 51 TypeTLSA Type = 52 TypeSMIMEA Type = 53 TypeHIP Type = 55 TypeNINFO Type = 56 TypeRKEY Type = 57 TypeTALINK Type = 58 TypeCDS Type = 59 TypeCDNSKEY Type = 60 TypeOPENPGPKEY Type = 61 TypeCSYNC Type = 62 TypeZONEMD Type = 63 TypeSVCB Type = 64 TypeHTTPS Type = 65 TypeDSYNC Type = 66 TypeSPF Type = 99 TypeNID Type = 104 TypeL32 Type = 105 TypeL64 Type = 106 TypeLP Type = 107 TypeEUI48 Type = 108 TypeEUI64 Type = 109 TypeNXNAME Type = 128 TypeTKEY Type = 249 TypeTSIG Type = 250 TypeIXFR Type = 251 TypeAXFR Type = 252 TypeMAILB Type = 253 TypeMAILA Type = 254 TypeANY Type = 255 TypeURI Type = 256 TypeCAA Type = 257 TypeAVC Type = 258 TypeAMTRELAY Type = 260 TypeRESINFO Type = 261 TypeTA Type = 32768 TypeDLV Type = 32769 )
Resource record types. Types that GatewayDNS does not decode natively are still listed so that policy rules and logs can name them; they decode to Unknown and round-trip byte for byte.
func ParseType ¶
ParseType returns the Type named by s. It accepts both mnemonics such as "AAAA" (case-insensitively) and the RFC 3597 "TYPEnnn" generic form.
type URI ¶
type URI struct {
// Priority selects among alternatives; lower is preferred, as in SRV.
Priority uint16
// Weight distributes load among equal priorities.
Weight uint16
// Target is the URI itself. Unlike almost every other textual DNS field it
// is not a <character-string>: RFC 7553 section 4.5 stores it as the bare
// remainder of the RDATA, so that a URI may exceed 255 octets.
Target string
}
URI publishes a URI for the service named by the owner name (RFC 7553).
func (*URI) AppendWire ¶
AppendWire implements RData.
type Unknown ¶
type Unknown struct {
// RRType is the numeric type of the record.
RRType Type
// Payload is the raw RDATA. It is owned by the Unknown and must not be
// modified by callers.
Payload []byte
}
Unknown is the RFC 3597 representation of a record whose type has no registered decoder. The payload is retained verbatim so that a forwarding resolver relays record types that did not exist when it was compiled.
Because the octets are opaque, any domain names inside them cannot be located, and so are never compressed on output and never decompressed on input. This is exactly what RFC 3597 section 4 requires.
func (*Unknown) AppendWire ¶
AppendWire implements RData.
type UnpackOptions ¶
type UnpackOptions struct {
// Registry supplies RDATA decoders. When nil, [DefaultRegistry] is used.
Registry *Registry
// IgnoreTruncatedSections accepts a message whose section counts exceed the
// records actually present, returning what could be decoded.
//
// This is off by default because silently accepting a short message hides
// corruption. It exists because some middleboxes emit responses whose
// ARCOUNT includes an OPT record they then strip, and a resolver in the
// field is better off using such a response than failing the query.
IgnoreTruncatedSections bool
// MaxRecords caps the total number of entries decoded across all four
// sections, questions included. Zero means the limit implied by the message
// size, which is already tight because every record costs at least eleven
// octets and every question at least five.
//
// Questions count because this is a work bound, not a taxonomy: a crafted
// message can carry thousands of them, and a question costs the same name
// decompression a record does. An operator who sets MaxRecords to bound
// per-query work would not expect the one section an attacker can fill most
// cheaply to be exempt.
MaxRecords int
// MaxNameOctets caps the total decompressed domain-name octets a single
// message may produce. Zero means no budget.
//
// It is off by default because no safe default exists. Compression lets a
// two-octet pointer stand for a 255-octet name, so the ratio of decoded name
// octets to message octets is large for hostile and legitimate input alike:
// measured, a pointer-chain construction reaches 17x while a conforming
// message of 400 NS records sharing a long owner name reaches 26x. Any
// default low enough to blunt the first would drop the second.
//
// Set it when the deployment's own traffic is known — a resolver serving
// ordinary names sits near 2x, so 8*len(msg) is generous there — and accept
// that the cost of the knob is rejecting the unusual-but-valid message with
// [ErrNameBudget].
MaxNameOctets int
}
UnpackOptions configures message decoding. The zero value is the default behaviour and is what Unpack uses.
type Validator ¶
type Validator interface {
// Validate reports whether the value can be encoded losslessly.
Validate() error
}
Validator is an optional interface an RData implementation may satisfy to have the encoder reject values that cannot be represented on the wire.
RData.AppendWire cannot report an error — it returns only a buffer — so without this hook a value that overflows a length-prefixed field would be silently truncated into a different record. Implementations holding any length-prefixed or count-prefixed field should implement Validator; the encoder calls it before writing each record and fails the whole message if it returns an error.
type ZONEMD ¶
type ZONEMD struct {
// Serial must equal the SOA serial of the zone the digest was computed over,
// which is what binds the digest to a specific version of the zone.
Serial uint32
// Scheme selects the collation scheme; only 1 (SIMPLE) is defined.
Scheme uint8
// Hash selects the digest algorithm.
Hash uint8
// Digest is the zone digest.
Digest []byte
}
ZONEMD carries a message digest over the whole zone (RFC 8976), letting a recipient verify a zone obtained by any transport — including one that is not itself authenticated — against the apex signature.
func (*ZONEMD) AppendWire ¶
AppendWire implements RData.