mirror of
https://github.com/soypat/lneto.git
synced 2026-07-26 10:38:47 +00:00
836 lines
23 KiB
Go
836 lines
23 KiB
Go
package dns
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"math"
|
|
"net/netip"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/soypat/lneto"
|
|
"github.com/soypat/lneto/internal"
|
|
)
|
|
|
|
// Global parameters.
|
|
const (
|
|
// SizeHeader is the length (in bytes) of a DNS header.
|
|
// A header is comprised of 6 uint16s and no padding.
|
|
SizeHeader = 6 * 2
|
|
// The Internet supports name server access using TCP [RFC-9293] on server
|
|
// port 53 (decimal) as well as datagram access using UDP [RFC-768] on UDP port 53 (decimal).
|
|
ServerPort = 53
|
|
ClientPort = 53
|
|
// Messages carried by UDP are restricted to 512 bytes (not counting the IP
|
|
// or UDP headers). Longer messages are truncated and the TC bit is set in the header.
|
|
MaxSizeUDP = 512
|
|
)
|
|
|
|
// Message is a convenience type for decoding DNS messages and storing results in a single object.
|
|
// Message is designed for ease of memory reuse. All internal buffers in a Message are reused in methods:
|
|
// - [Message.Decode]: Limited in decode size by [Message.LimitResourceDecoding] which must be called beforehand.
|
|
// - [Message.CopyFrom]
|
|
// - [Message.AddQuestions]
|
|
type Message struct {
|
|
Questions []Question
|
|
Answers []Resource
|
|
Authorities []Resource
|
|
Additionals []Resource
|
|
}
|
|
|
|
type Question struct {
|
|
Name Name
|
|
Type Type
|
|
Class Class
|
|
}
|
|
|
|
type Resource struct {
|
|
header ResourceHeader
|
|
data []byte
|
|
}
|
|
|
|
// A ResourceHeader is the header of a DNS resource record. There are
|
|
// many types of DNS resource records, but they all share the same header.
|
|
type ResourceHeader struct {
|
|
Name Name
|
|
Type Type
|
|
Class Class
|
|
TTL uint32
|
|
Length uint16
|
|
}
|
|
|
|
// Name is a wire representation of a DNS name.
|
|
type Name struct {
|
|
data []byte
|
|
}
|
|
|
|
// EqualString checks if the name receiver matches the strname string (non-wire formatted) name.
|
|
func (n Name) EqualString(strname string) bool {
|
|
data := n.data
|
|
for len(data) > 0 {
|
|
labelLen := int(data[0])
|
|
if labelLen == 0 {
|
|
return strname == "" || strname == "."
|
|
}
|
|
if len(data) < 1+labelLen {
|
|
return false
|
|
}
|
|
label := data[1 : 1+labelLen]
|
|
var seg string
|
|
before, after, ok := strings.Cut(strname, ".")
|
|
if !ok {
|
|
seg, strname = strname, ""
|
|
} else {
|
|
seg, strname = before, after
|
|
}
|
|
if len(seg) != len(label) || seg != string(label) {
|
|
return false
|
|
}
|
|
data = data[1+labelLen:]
|
|
}
|
|
return false
|
|
}
|
|
|
|
// NamesEqual reports whether two DNS names are equal by comparing
|
|
// their wire-format representations directly. This is case-sensitive;
|
|
// for case-insensitive comparison use [NamesEqualFold].
|
|
func NamesEqual(a, b Name) bool {
|
|
return internal.BytesEqual(a.data, b.data)
|
|
}
|
|
|
|
type ZFlags uint16
|
|
|
|
func NewResource(name Name, typ Type, class Class, ttl uint32, data []byte) Resource {
|
|
return Resource{
|
|
header: ResourceHeader{
|
|
Name: name,
|
|
Type: typ,
|
|
Class: class,
|
|
TTL: ttl,
|
|
Length: uint16(len(data)),
|
|
},
|
|
data: data,
|
|
}
|
|
}
|
|
|
|
func (r *Resource) SetEDNS0(UDPlength uint16, rcode RCode, zflags ZFlags, data []byte) {
|
|
if len(data) > math.MaxUint16-2 || len(data)+8+2*SizeHeader > int(UDPlength) {
|
|
panic("too large data")
|
|
}
|
|
r.header = ResourceHeader{
|
|
Name: Name{data: rootDomain},
|
|
Type: TypeOPT,
|
|
Class: Class(UDPlength),
|
|
TTL: uint32(rcode)<<24 | 0<<16 | uint32(zflags),
|
|
Length: uint16(len(data)),
|
|
}
|
|
r.data = append(r.data[:0], data...)
|
|
}
|
|
|
|
// DecodeMessage decodes the DNS message into question, answer, authority and additional resources.
|
|
// It returns the number of bytes
|
|
// consumed from b (0 if no bytes were consumed) and any error encountered.
|
|
// If the message was not completely parsed due to LimitResourceDecoding,
|
|
// incompleteButOK is true and an error is returned, though the message is still usable.
|
|
//
|
|
// The slice memory is overwritten and capacity used as the limit of encoding.
|
|
// If the argument slice is nil it is skipped for decoding but does not prevent further decoding
|
|
// of other answers, authorities or additionals from being decoded.
|
|
func DecodeMessage(q *[]Question, answers, authorities, additionals *[]Resource, msg []byte) (_ uint16, incompleteButOK bool, err error) {
|
|
hdr, err := NewFrame(msg)
|
|
if err != nil {
|
|
return 0, false, err
|
|
}
|
|
qd := hdr.QDCount()
|
|
nq := int(qd)
|
|
off := uint16(SizeHeader)
|
|
// Return tooManyErr if found to flag to the caller that the message was
|
|
// decoded but contained too many resources to decode completely.
|
|
var tooManyErr error
|
|
switch {
|
|
case nq > caporzero(q):
|
|
tooManyErr = errTooManyQuestions
|
|
case int(hdr.ANCount()) > caporzero(answers):
|
|
tooManyErr = errTooManyAnswers
|
|
case int(hdr.NSCount()) > caporzero(authorities):
|
|
tooManyErr = errTooManyAuthorities
|
|
case int(hdr.ARCount()) > caporzero(additionals):
|
|
tooManyErr = errTooManyAdditionals
|
|
}
|
|
if q != nil {
|
|
if nq > cap(*q) {
|
|
nq = cap(*q)
|
|
}
|
|
*q = (*q)[:nq]
|
|
for i := 0; i < nq; i++ {
|
|
off, err = (*q)[i].Decode(msg, off)
|
|
if err != nil {
|
|
*q = (*q)[:i] // Trim non-decoded/failed questions.
|
|
return off, false, err
|
|
}
|
|
}
|
|
} else {
|
|
nq = 0 // No question slice provided, skip all questions below.
|
|
}
|
|
// Skip undecoded questions.
|
|
for i := 0; i < int(qd)-nq; i++ {
|
|
off, err = skipQuestion(msg, off)
|
|
if err != nil {
|
|
return off, false, err
|
|
}
|
|
}
|
|
off, err = decodeToCapResources(answers, msg, hdr.ANCount(), off)
|
|
if err != nil {
|
|
return off, false, err
|
|
}
|
|
off, err = decodeToCapResources(authorities, msg, hdr.NSCount(), off)
|
|
if err != nil {
|
|
return off, false, err
|
|
}
|
|
off, err = decodeToCapResources(additionals, msg, hdr.ARCount(), off)
|
|
if err != nil {
|
|
return off, false, err
|
|
}
|
|
return off, tooManyErr != nil, tooManyErr
|
|
}
|
|
|
|
// Decode decodes the DNS message in b into m. It is a convenience wrapper for [DecodeMessage].
|
|
func (m *Message) Decode(msg []byte) (_ uint16, incompleteButOK bool, err error) {
|
|
return DecodeMessage(&m.Questions, &m.Answers, &m.Authorities, &m.Additionals, msg)
|
|
}
|
|
|
|
func decodeToCapResources(dst *[]Resource, msg []byte, nrec, off uint16) (_ uint16, err error) {
|
|
originalRec := nrec
|
|
if dst != nil {
|
|
if nrec > uint16(cap(*dst)) {
|
|
nrec = uint16(cap(*dst)) // Decode up to cap. Caller will return an error flag.
|
|
}
|
|
*dst = (*dst)[:nrec]
|
|
for i := uint16(0); i < nrec; i++ {
|
|
off, err = (*dst)[i].Decode(msg, off)
|
|
if err != nil {
|
|
*dst = (*dst)[:i] // Trim non-decoded/failed resources.
|
|
return off, err
|
|
}
|
|
}
|
|
}
|
|
// Parse undecoded resources, effectively skipping them.
|
|
for i := uint16(0); i < originalRec-nrec; i++ {
|
|
off, err = skipResource(msg, off)
|
|
if err != nil {
|
|
return off, err
|
|
}
|
|
}
|
|
return off, nil
|
|
}
|
|
|
|
func skipQuestion(msg []byte, off uint16) (_ uint16, err error) {
|
|
off, err = skipName(msg, off)
|
|
if err != nil {
|
|
return off, err
|
|
}
|
|
if off+4 > uint16(len(msg)) {
|
|
return off, lneto.ErrTruncatedFrame
|
|
}
|
|
return off + 4, nil
|
|
}
|
|
|
|
func skipResource(msg []byte, off uint16) (_ uint16, err error) {
|
|
off, err = skipName(msg, off)
|
|
if err != nil {
|
|
return off, err
|
|
}
|
|
// | Name... | Type16 | Class16 | TTL32 | Length16 | Data... |
|
|
datalen := binary.BigEndian.Uint16(msg[off+8:])
|
|
off += datalen + 10
|
|
if off > uint16(len(msg)) {
|
|
return off, lneto.ErrTruncatedFrame
|
|
}
|
|
return off, nil
|
|
}
|
|
|
|
func skipName(msg []byte, off uint16) (uint16, error) {
|
|
return visitAllLabels(msg, off, func(b []byte) {}, allowCompression)
|
|
}
|
|
|
|
func (m *Message) AppendTo(buf []byte, txid uint16, flags HeaderFlags) (_ []byte, err error) {
|
|
nq := uint16(len(m.Questions))
|
|
nans := uint16(len(m.Answers))
|
|
nauth := uint16(len(m.Authorities))
|
|
nadd := uint16(len(m.Additionals))
|
|
buf = slices.Grow(buf, int(m.Len()))
|
|
// Set the buffer directly with header fields.
|
|
f, err := NewFrame(buf[len(buf) : len(buf)+SizeHeader])
|
|
if err != nil {
|
|
return buf, err
|
|
}
|
|
f.SetTxID(txid)
|
|
f.SetFlags(flags)
|
|
f.SetQDCount(nq)
|
|
f.SetANCount(nans)
|
|
f.SetNSCount(nauth)
|
|
f.SetARCount(nadd)
|
|
buf = buf[:len(buf)+SizeHeader]
|
|
for _, q := range m.Questions {
|
|
buf, err = q.appendTo(buf)
|
|
if err != nil {
|
|
return buf, err
|
|
}
|
|
}
|
|
for _, r := range m.Answers {
|
|
buf, err = r.appendTo(buf)
|
|
if err != nil {
|
|
return buf, err
|
|
}
|
|
}
|
|
for _, r := range m.Authorities {
|
|
buf, err = r.appendTo(buf)
|
|
if err != nil {
|
|
return buf, err
|
|
}
|
|
}
|
|
for _, r := range m.Additionals {
|
|
buf, err = r.appendTo(buf)
|
|
if err != nil {
|
|
return buf, err
|
|
}
|
|
}
|
|
return buf, nil
|
|
}
|
|
|
|
func (m *Message) WriteAnswers(dst []netip.Addr, host string) (n uint16, err error) {
|
|
for i := range m.Answers {
|
|
if int(n) >= len(dst) {
|
|
return n, lneto.ErrExhausted
|
|
}
|
|
ans := &m.Answers[i]
|
|
hdr := ans.Header()
|
|
if !hdr.Name.EqualString(host) {
|
|
continue
|
|
}
|
|
var ok bool
|
|
dst[n], ok = netip.AddrFromSlice(ans.RawData())
|
|
if !ok {
|
|
err = lneto.ErrInvalidAddr
|
|
} else {
|
|
n++
|
|
}
|
|
}
|
|
return n, err
|
|
}
|
|
|
|
func (m *Message) Len() uint16 {
|
|
return SizeHeader + m.lenResources()
|
|
}
|
|
|
|
func (m *Message) lenResources() (l uint16) {
|
|
for i := range m.Questions {
|
|
l += m.Questions[i].Len()
|
|
}
|
|
for i := range m.Answers {
|
|
l += m.Answers[i].Len()
|
|
}
|
|
for i := range m.Authorities {
|
|
l += m.Authorities[i].Len()
|
|
}
|
|
for i := range m.Additionals {
|
|
l += m.Additionals[i].Len()
|
|
}
|
|
return l
|
|
}
|
|
|
|
func (m *Message) AddQuestions(questions []Question) {
|
|
// This question slice handling here is done in spirit of DNSClient being owner of its own buffer.
|
|
// If this is not done we risk the Questions being edited by user and interfering with the DNS request.
|
|
qoff := len(m.Questions)
|
|
m.Questions = slices.Grow(m.Questions, len(questions))
|
|
m.Questions = m.Questions[:qoff+len(questions)]
|
|
for i := range questions {
|
|
m.Questions[qoff+i].CopyFrom(questions[i])
|
|
}
|
|
}
|
|
|
|
func (m *Message) AddAdditionals(rsc []Resource) {
|
|
aoff := len(m.Additionals)
|
|
m.Additionals = slices.Grow(m.Additionals, len(rsc))
|
|
m.Additionals = m.Additionals[:aoff+len(rsc)]
|
|
for i := range rsc {
|
|
m.Additionals[aoff+i].CopyFrom(rsc[i])
|
|
}
|
|
}
|
|
|
|
// LimitResourceDecoding sets the maximum number of resources that can be decoded
|
|
// by a subsequent call to [Message.Decode]. This is useful for limiting memory
|
|
// usage when decoding untrusted DNS messages.
|
|
//
|
|
// After calling LimitResourceDecoding, a call to Decode will:
|
|
// - Decode at most maxQ questions
|
|
// - Decode at most maxAns answers
|
|
// - Decode at most maxAuth authority records
|
|
// - Decode at most maxAdd additional records
|
|
//
|
|
// If the message contains more resources than the limits, Decode returns
|
|
// incompleteButOK=true along with an error indicating which resource type
|
|
// exceeded the limit. The message is still usable with the decoded resources.
|
|
//
|
|
// Call this method before Decode to set up the limits. The limits are based on
|
|
// slice capacity, which is set exactly to the specified values.
|
|
func (m *Message) LimitResourceDecoding(maxQ, maxAns, maxAuth, maxAdd uint16) {
|
|
internal.SliceReuse(&m.Questions, int(maxQ))
|
|
internal.SliceReuse(&m.Answers, int(maxAns))
|
|
internal.SliceReuse(&m.Authorities, int(maxAuth))
|
|
internal.SliceReuse(&m.Additionals, int(maxAdd))
|
|
}
|
|
|
|
func (m *Message) Reset() {
|
|
m.Questions = m.Questions[:0]
|
|
m.Answers = m.Answers[:0]
|
|
m.Authorities = m.Authorities[:0]
|
|
m.Additionals = m.Additionals[:0]
|
|
}
|
|
|
|
// String returns a string representation of the header.
|
|
func (h *ResourceHeader) String() string {
|
|
return h.Name.String() + " " + h.Type.String() + " " + h.Class.String() +
|
|
" ttl=" + strconv.FormatUint(uint64(h.TTL), 10) + " len=" + strconv.FormatUint(uint64(h.Length), 10)
|
|
}
|
|
|
|
func (r *Resource) Reset() {
|
|
r.header.Reset()
|
|
r.data = r.data[:0]
|
|
}
|
|
|
|
func (r *Resource) Header() ResourceHeader { return r.header }
|
|
|
|
func (r *Resource) RawData() []byte {
|
|
length := r.header.Length
|
|
if int(length) > len(r.data) {
|
|
length = uint16(len(r.data))
|
|
}
|
|
return r.data[:length]
|
|
}
|
|
|
|
func (q *Question) Reset() {
|
|
q.Name.Reset()
|
|
*q = Question{Name: q.Name} // Reuse Name's buffer.
|
|
}
|
|
|
|
// Len returns Question's length over-the-wire.
|
|
func (q *Question) Len() uint16 { return q.Name.Len() + 4 }
|
|
|
|
func (r *ResourceHeader) Reset() {
|
|
r.Name.Reset()
|
|
*r = ResourceHeader{Name: r.Name} // Reuse Name's buffer.
|
|
}
|
|
|
|
func (q *Question) Decode(msg []byte, off uint16) (uint16, error) {
|
|
off, err := q.Name.Decode(msg, off)
|
|
if err != nil {
|
|
return off, err
|
|
}
|
|
if off+4 > uint16(len(msg)) {
|
|
return off, errResourceLen
|
|
}
|
|
q.Type = Type(binary.BigEndian.Uint16(msg[off:]))
|
|
q.Class = Class(binary.BigEndian.Uint16(msg[off+2:]))
|
|
return off + 4, nil
|
|
}
|
|
|
|
func (q *Question) appendTo(buf []byte) (_ []byte, err error) {
|
|
buf, err = q.Name.AppendTo(buf)
|
|
if err != nil {
|
|
return buf, err
|
|
}
|
|
buf = append16(buf, uint16(q.Type))
|
|
buf = append16(buf, uint16(q.Class))
|
|
return buf, nil
|
|
}
|
|
|
|
// String returns a string representation of the Question with the Name in dotted format.
|
|
func (q *Question) String() string {
|
|
return q.Name.String() + " " + q.Type.String() + " " + q.Class.String()
|
|
}
|
|
|
|
func (r *Resource) Decode(b []byte, off uint16) (uint16, error) {
|
|
off, err := r.header.Decode(b, off)
|
|
if err != nil {
|
|
return off, err
|
|
}
|
|
if r.header.Length > uint16(len(b[off:])) {
|
|
return off, errResourceLen
|
|
}
|
|
r.data = append(r.data[:0], b[off:off+r.header.Length]...)
|
|
return off + r.header.Length, nil
|
|
}
|
|
|
|
func (r *Resource) appendTo(buf []byte) (_ []byte, err error) {
|
|
buf, err = r.header.appendTo(buf)
|
|
if err != nil {
|
|
return buf, err
|
|
}
|
|
buf = append(buf, r.data...)
|
|
return buf, nil
|
|
}
|
|
|
|
func (r *Resource) Len() uint16 {
|
|
return r.header.Name.Len() + 10 + uint16(len(r.data))
|
|
}
|
|
|
|
func (rhdr *ResourceHeader) Decode(msg []byte, off uint16) (uint16, error) {
|
|
off, err := rhdr.Name.Decode(msg, off)
|
|
if err != nil {
|
|
return off, err
|
|
}
|
|
if off+10 > uint16(len(msg)) {
|
|
return off, errResourceLen
|
|
}
|
|
rhdr.Type = Type(binary.BigEndian.Uint16(msg[off:])) // 2
|
|
rhdr.Class = Class(binary.BigEndian.Uint16(msg[off+2:])) // 4
|
|
rhdr.TTL = binary.BigEndian.Uint32(msg[off+4:]) // 8
|
|
rhdr.Length = binary.BigEndian.Uint16(msg[off+8:]) // 10
|
|
return off + 10, nil
|
|
}
|
|
|
|
func (rhdr *ResourceHeader) appendTo(buf []byte) (_ []byte, err error) {
|
|
buf, err = rhdr.Name.AppendTo(buf)
|
|
if err != nil {
|
|
return buf, err
|
|
}
|
|
buf = append16(buf, uint16(rhdr.Type))
|
|
buf = append16(buf, uint16(rhdr.Class))
|
|
buf = append32(buf, rhdr.TTL)
|
|
buf = append16(buf, rhdr.Length)
|
|
return buf, nil
|
|
}
|
|
|
|
func MustNewName(s string) Name {
|
|
name, err := NewName(s)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return name
|
|
}
|
|
|
|
var rootDomain = []byte{0}
|
|
|
|
// NewName parses a domain name and returns a new Name.
|
|
func NewName(domain string) (Name, error) {
|
|
if domain == "" {
|
|
return Name{}, errEmptyDomainName
|
|
}
|
|
if len(domain) == 1 && domain[0] == '.' {
|
|
return Name{data: append([]byte{}, rootDomain...)}, nil
|
|
}
|
|
var name Name
|
|
for len(domain) > 0 {
|
|
idx := strings.IndexByte(domain, '.')
|
|
done := idx < 0 || idx+1 > len(domain)
|
|
if done {
|
|
idx = len(domain)
|
|
}
|
|
if !name.CanAddLabel(domain[:idx]) {
|
|
return Name{}, errCantAddLabel
|
|
}
|
|
name.AddLabel(domain[:idx])
|
|
if done {
|
|
break
|
|
}
|
|
domain = domain[idx+1:]
|
|
}
|
|
return name, nil
|
|
}
|
|
|
|
// TrimLabels returns a Name sharing the same backing data with the first n labels removed.
|
|
// For example, trimming 1 label from "My Web._http._tcp.local" yields "_http._tcp.local".
|
|
// Returns an empty Name if n exceeds the number of labels.
|
|
func (n Name) TrimLabels(skip int) Name {
|
|
off := 0
|
|
for range skip {
|
|
if off >= len(n.data) {
|
|
return Name{}
|
|
}
|
|
off += 1 + int(n.data[off])
|
|
}
|
|
return Name{data: n.data[off:]}
|
|
}
|
|
|
|
// Len returns the length over-the-wire of the encoded Name.
|
|
func (n *Name) Len() uint16 {
|
|
if len(n.data) > math.MaxUint16 {
|
|
panic("size of DNS name data overflows 16bits")
|
|
}
|
|
return uint16(len(n.data))
|
|
}
|
|
|
|
func (n *Name) CopyFrom(ex Name) {
|
|
n.data = append(n.data[:0], ex.data...)
|
|
}
|
|
|
|
// AppendTo appends the Name to b in wire format and returns the resulting slice.
|
|
func (n *Name) AppendTo(b []byte) ([]byte, error) {
|
|
if len(n.data) == 0 {
|
|
return b, errInvalidName
|
|
}
|
|
return append(b, n.data...), nil
|
|
}
|
|
|
|
// String returns a string representation of the name in dotted format.
|
|
func (n *Name) String() string {
|
|
b := make([]byte, 0, len(n.data)+3)
|
|
return string(n.AppendDottedTo(b))
|
|
}
|
|
|
|
// AppendDottedTo appends the Name to b in dotted format and returns the resulting slice.
|
|
func (n *Name) AppendDottedTo(b []byte) []byte {
|
|
n.VisitLabels(func(label []byte) {
|
|
b = append(b, label...)
|
|
b = append(b, '.')
|
|
})
|
|
return b
|
|
}
|
|
|
|
// Decode resets internal Name buffer and reads raw wire data from buffer, returning any error encountered.
|
|
func (n *Name) Decode(b []byte, off uint16) (uint16, error) {
|
|
n.Reset()
|
|
off, err := visitAllLabels(b, off, n.vistAddLabel, allowCompression)
|
|
if err != nil {
|
|
n.Reset()
|
|
return off, err
|
|
}
|
|
n.data = append(n.data, 0) // Add terminator, off counts the terminator already in visitAllLabels.
|
|
return off, nil
|
|
}
|
|
|
|
// Reset resets the Name labels to be empty andatad reuses buffer.
|
|
func (n *Name) Reset() { n.data = n.data[:0] }
|
|
|
|
// CanAddLabel reports whether the label can be added to the name.
|
|
func (n *Name) CanAddLabel(label string) bool {
|
|
return len(label) != 0 && len(label) <= 63 && len(label)+len(n.data)+2 <= 255 && // Include len+terminator+label.
|
|
label[len(label)-1] != 0 && // We do not support implicitly zero-terminated labels.
|
|
strings.IndexByte(label, '.') < 0 // See issue golang/go#56246
|
|
}
|
|
|
|
// AddLabel adds a label to the name. If n.CanAddLabel(label) returns false, it panics.
|
|
func (n *Name) AddLabel(label string) {
|
|
if !n.CanAddLabel(label) {
|
|
panic(errCantAddLabel.Error())
|
|
}
|
|
if n.isTerminated() {
|
|
n.data = n.data[:len(n.data)-1] // Remove terminator if present to add another label.
|
|
}
|
|
n.data = append(n.data, byte(len(label)))
|
|
n.data = append(n.data, label...)
|
|
n.data = append(n.data, 0)
|
|
}
|
|
|
|
func (n *Name) vistAddLabel(label []byte) {
|
|
n.data = append(n.data, byte(len(label)))
|
|
n.data = append(n.data, label...)
|
|
}
|
|
|
|
func (n *Name) isTerminated() bool {
|
|
return len(n.data) > 0 && n.data[len(n.data)-1] == 0
|
|
}
|
|
|
|
func (n *Name) VisitLabels(fn func(label []byte)) error {
|
|
if len(n.data) > 255 {
|
|
return errNameTooLong
|
|
}
|
|
_, err := visitAllLabels(n.data, 0, fn, allowCompression)
|
|
return err
|
|
}
|
|
|
|
func append16(b []byte, v uint16) []byte {
|
|
binary.BigEndian.PutUint16(b[len(b):len(b)+2], v)
|
|
return b[:len(b)+2]
|
|
}
|
|
|
|
func append32(b []byte, v uint32) []byte {
|
|
binary.BigEndian.PutUint32(b[len(b):len(b)+4], v)
|
|
return b[:len(b)+4]
|
|
}
|
|
|
|
func visitAllLabels(msg []byte, off uint16, fn func(b []byte), allowCompression bool) (uint16, error) {
|
|
if len(msg) > math.MaxUint16 {
|
|
return off, errResTooLong
|
|
}
|
|
// ptr is the number of pointers followed.
|
|
var ptr uint8
|
|
// newOff is the offset where the next record will start. Pointers lead
|
|
// to data that belongs to other names and thus doesn't count towards to
|
|
// the usage of this name.
|
|
var newOff = off
|
|
|
|
for {
|
|
start, end, isPtr, err := NextLabel(msg[off:])
|
|
if err != nil {
|
|
return off, err
|
|
} else if start == end {
|
|
if ptr == 0 {
|
|
newOff = off + 1 // advance past the null terminator byte
|
|
}
|
|
break
|
|
} else if isPtr {
|
|
if !allowCompression {
|
|
return newOff, errCompressedSRV
|
|
}
|
|
if ptr == 0 {
|
|
newOff = off + 2 // next record follows the 2-byte pointer
|
|
}
|
|
off = start
|
|
if int(off) >= len(msg) {
|
|
return newOff, errInvalidPtr
|
|
} else if ptr++; ptr > 10 {
|
|
return newOff, errTooManyPtr
|
|
}
|
|
} else {
|
|
// Is normal label; start/end are relative to msg[off:].
|
|
fn(msg[off+start : off+end])
|
|
off += end
|
|
}
|
|
}
|
|
return newOff, nil
|
|
}
|
|
|
|
// NextLabel parses the first control byte of data and returns the position and extent of next DNS label.
|
|
//
|
|
// For a normal string label (RFC 1035 §3.1), isPointer==false and start/end are
|
|
// byte indices into data: data[start:end] holds the raw label bytes.
|
|
// A null terminator (c==0) signals the end of the name: start==end==1, err==nil.
|
|
//
|
|
// For a compression pointer (RFC 1035 §4.1.4), isPointer==true:
|
|
// - start is the absolute target offset within the full DNS message to jump to.
|
|
// - end==0 (sentinel; not a data range).
|
|
//
|
|
// Returns [lneto.ErrTruncatedFrame] if data is too short to read the full label or pointer.
|
|
// Returns errReserved for the 0x40 and 0x80 reserved prefix classes.
|
|
func NextLabel(data []byte) (start_RelOrAbs, endRel uint16, isAbsPointer bool, err error) {
|
|
// Default invalid values
|
|
start_RelOrAbs, endRel = 0, 0
|
|
if len(data) == 0 {
|
|
return start_RelOrAbs, endRel, false, lneto.ErrTruncatedFrame
|
|
}
|
|
c := uint16(data[0])
|
|
switch c & 0xc0 {
|
|
case 0:
|
|
start_RelOrAbs = 1
|
|
// String label segment.
|
|
if c == 0 {
|
|
return start_RelOrAbs, start_RelOrAbs, false, nil // Null terminator. String ended.
|
|
}
|
|
endRel = start_RelOrAbs + c
|
|
if int(endRel) > len(data) {
|
|
return start_RelOrAbs, endRel, false, lneto.ErrTruncatedFrame
|
|
}
|
|
// Reject names containing dots. See issue golang/go#56246
|
|
if bytes.IndexByte(data[start_RelOrAbs:endRel], '.') >= 0 {
|
|
return start_RelOrAbs, endRel, false, errInvalidName
|
|
}
|
|
// Correct label!
|
|
case 0xc0:
|
|
// Pointer. Start is absolute index in DNS message.
|
|
isAbsPointer = true
|
|
if len(data) < 2 {
|
|
return start_RelOrAbs, endRel, isAbsPointer, lneto.ErrTruncatedFrame // Need more data to fully read pointer.
|
|
}
|
|
c1 := uint16(data[1])
|
|
start_RelOrAbs = (c^0xC0)<<8 | c1
|
|
default:
|
|
err = errReserved
|
|
}
|
|
return start_RelOrAbs, endRel, isAbsPointer, err
|
|
}
|
|
|
|
func (dst *Message) CopyFrom(m Message) {
|
|
internal.SliceReuse(&dst.Questions, len(m.Questions))
|
|
internal.SliceReuse(&dst.Answers, len(m.Answers))
|
|
internal.SliceReuse(&dst.Authorities, len(m.Authorities))
|
|
internal.SliceReuse(&dst.Additionals, len(m.Additionals))
|
|
dst.Questions = dst.Questions[:len(m.Questions)]
|
|
dst.Answers = dst.Answers[:len(m.Answers)]
|
|
dst.Authorities = dst.Authorities[:len(m.Authorities)]
|
|
dst.Additionals = dst.Additionals[:len(m.Additionals)]
|
|
for i := range dst.Questions {
|
|
dst.Questions[i].CopyFrom(m.Questions[i])
|
|
}
|
|
for i := range dst.Answers {
|
|
dst.Answers[i].CopyFrom(m.Answers[i])
|
|
}
|
|
for i := range dst.Authorities {
|
|
dst.Authorities[i].CopyFrom(m.Authorities[i])
|
|
}
|
|
for i := range dst.Additionals {
|
|
dst.Additionals[i].CopyFrom(m.Additionals[i])
|
|
}
|
|
}
|
|
|
|
func (dst *Question) CopyFrom(q Question) {
|
|
dst.Name.CopyFrom(q.Name)
|
|
dst.Class = q.Class
|
|
dst.Type = q.Type
|
|
}
|
|
|
|
func (dst *Resource) CopyFrom(r Resource) {
|
|
dst.header.CopyFrom(r.header)
|
|
dst.data = append(dst.data[:0], r.data...)
|
|
}
|
|
|
|
// SetA sets an A (IPv4 address) resource record, reusing internal buffers.
|
|
func (r *Resource) SetA(name Name, class Class, ttl uint32, addr []byte) {
|
|
r.setHeader(name, TypeA, class, ttl)
|
|
r.data = append(r.data[:0], addr...)
|
|
r.header.Length = uint16(len(r.data))
|
|
}
|
|
|
|
// SetPTR sets a PTR (pointer) resource record, reusing internal buffers.
|
|
func (r *Resource) SetPTR(name Name, class Class, ttl uint32, target Name) {
|
|
r.setHeader(name, TypePTR, class, ttl)
|
|
r.data, _ = target.AppendTo(r.data[:0])
|
|
r.header.Length = uint16(len(r.data))
|
|
}
|
|
|
|
// SetSRV sets a SRV (service locator) resource record, reusing internal buffers.
|
|
func (r *Resource) SetSRV(name Name, class Class, ttl uint32, priority, weight, port uint16, target Name) {
|
|
r.setHeader(name, TypeSRV, class, ttl)
|
|
r.data = binary.BigEndian.AppendUint16(r.data[:0], priority)
|
|
r.data = binary.BigEndian.AppendUint16(r.data, weight)
|
|
r.data = binary.BigEndian.AppendUint16(r.data, port)
|
|
r.data, _ = target.AppendTo(r.data)
|
|
r.header.Length = uint16(len(r.data))
|
|
}
|
|
|
|
// SetTXT sets a TXT resource record, reusing internal buffers.
|
|
func (r *Resource) SetTXT(name Name, class Class, ttl uint32, txt []byte) {
|
|
r.setHeader(name, TypeTXT, class, ttl)
|
|
if len(txt) == 0 {
|
|
r.data = append(r.data[:0], 0)
|
|
} else {
|
|
r.data = append(r.data[:0], txt...)
|
|
}
|
|
r.header.Length = uint16(len(r.data))
|
|
}
|
|
|
|
func (r *Resource) setHeader(name Name, typ Type, class Class, ttl uint32) {
|
|
r.header.Name.CopyFrom(name)
|
|
r.header.Type = typ
|
|
r.header.Class = class
|
|
r.header.TTL = ttl
|
|
}
|
|
|
|
func (dst *ResourceHeader) CopyFrom(rh ResourceHeader) {
|
|
dst.Name.CopyFrom(rh.Name)
|
|
dst.Type = rh.Type
|
|
dst.Class = rh.Class
|
|
dst.TTL = rh.TTL
|
|
dst.Length = rh.Length
|
|
}
|
|
|
|
func caporzero[T any](v *[]T) int {
|
|
if v == nil {
|
|
return 0
|
|
}
|
|
return cap(*v)
|
|
}
|