mirror of
https://github.com/soypat/lneto.git
synced 2026-08-09 17:33:43 +00:00
662 lines
17 KiB
Go
662 lines
17 KiB
Go
package dns
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"math"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// 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
|
|
)
|
|
|
|
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
|
|
}
|
|
|
|
type Name struct {
|
|
data []byte
|
|
}
|
|
|
|
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...)
|
|
}
|
|
|
|
// Decode decodes the DNS message in b into m. 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.
|
|
func (m *Message) Decode(msg []byte) (_ uint16, incompleteButOK bool, err error) {
|
|
if len(msg) < SizeHeader {
|
|
return 0, false, errBaseLen
|
|
} else if len(msg) > math.MaxUint16 {
|
|
return 0, false, errResTooLong
|
|
}
|
|
m.Reset()
|
|
hdr, err := NewFrame(msg)
|
|
if err != nil {
|
|
return 0, false, err
|
|
}
|
|
nq := int(hdr.QDCount())
|
|
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 > cap(m.Questions):
|
|
tooManyErr = errTooManyQuestions
|
|
case hdr.ANCount() > uint16(cap(m.Answers)):
|
|
tooManyErr = errTooManyAnswers
|
|
case hdr.NSCount() > uint16(cap(m.Authorities)):
|
|
tooManyErr = errTooManyAuthorities
|
|
case hdr.ARCount() > uint16(cap(m.Additionals)):
|
|
tooManyErr = errTooManyAdditionals
|
|
}
|
|
if nq > cap(m.Questions) {
|
|
nq = cap(m.Questions)
|
|
}
|
|
m.Questions = m.Questions[:nq]
|
|
for i := 0; i < nq; i++ {
|
|
off, err = m.Questions[i].Decode(msg, off)
|
|
if err != nil {
|
|
m.Questions = m.Questions[:i] // Trim non-decoded/failed questions.
|
|
return off, false, err
|
|
}
|
|
}
|
|
// Skip undecoded questions.
|
|
qd := hdr.QDCount()
|
|
for i := 0; i < int(qd)-nq; i++ {
|
|
off, err = skipQuestion(msg, off)
|
|
if err != nil {
|
|
return off, false, err
|
|
}
|
|
}
|
|
|
|
off, err = decodeToCapResources(&m.Answers, msg, hdr.ANCount(), off)
|
|
if err != nil {
|
|
return off, false, err
|
|
}
|
|
off, err = decodeToCapResources(&m.Authorities, msg, hdr.NSCount(), off)
|
|
if err != nil {
|
|
return off, false, err
|
|
}
|
|
off, err = decodeToCapResources(&m.Additionals, msg, hdr.ARCount(), off)
|
|
if err != nil {
|
|
return off, false, err
|
|
}
|
|
return off, tooManyErr != nil, tooManyErr
|
|
}
|
|
|
|
func decodeToCapResources(dst *[]Resource, msg []byte, nrec, off uint16) (_ uint16, err error) {
|
|
originalRec := nrec
|
|
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, errBaseLen
|
|
}
|
|
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, errBaseLen
|
|
}
|
|
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))
|
|
var hdr [SizeHeader]byte
|
|
f, err := NewFrame(hdr[:])
|
|
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 = slices.Grow(buf, int(m.Len()))
|
|
buf = append(buf, hdr[:]...)
|
|
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) 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])
|
|
}
|
|
}
|
|
|
|
func (m *Message) LimitResourceDecoding(maxQ, maxAns, maxAuth, maxAdd uint16) {
|
|
m.Questions = slices.Grow(m.Questions, int(maxQ))
|
|
m.Answers = slices.Grow(m.Answers, int(maxQ))
|
|
m.Authorities = slices.Grow(m.Authorities, int(maxQ))
|
|
m.Additionals = slices.Grow(m.Additionals, int(maxQ))
|
|
}
|
|
|
|
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) 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
|
|
}
|
|
|
|
// 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) {
|
|
// currOff is the current working offset.
|
|
currOff := off
|
|
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
|
|
|
|
LOOP:
|
|
for {
|
|
if currOff >= uint16(len(msg)) {
|
|
return off, errBaseLen
|
|
}
|
|
c := uint16(msg[currOff])
|
|
currOff++
|
|
switch c & 0xc0 {
|
|
case 0x00: // String label (segment).
|
|
if c == 0x00 {
|
|
break LOOP // Nominal end of name, always ends with null terminator.
|
|
}
|
|
endOff := currOff + c
|
|
if endOff > uint16(len(msg)) {
|
|
return off, errCalcLen
|
|
}
|
|
|
|
// Reject names containing dots. See issue golang/go#56246
|
|
if bytes.IndexByte(msg[currOff:endOff], '.') >= 0 {
|
|
return off, errInvalidName
|
|
}
|
|
|
|
fn(msg[currOff:endOff])
|
|
currOff = endOff
|
|
|
|
case 0xc0: // Pointer.
|
|
// https://cs.opensource.google/go/x/net/+/refs/tags/v0.19.0:dns/dnsmessage/message.go;l=2078
|
|
if !allowCompression {
|
|
return off, errCompressedSRV
|
|
}
|
|
if currOff >= uint16(len(msg)) {
|
|
return off, errInvalidPtr
|
|
}
|
|
c1 := msg[currOff]
|
|
currOff++
|
|
if ptr == 0 {
|
|
newOff = currOff
|
|
}
|
|
// Don't follow too many pointers, maybe there's a loop.
|
|
if ptr++; ptr > 10 {
|
|
return off, errTooManyPtr
|
|
}
|
|
currOff = (c^0xC0)<<8 | uint16(c1)
|
|
default:
|
|
// Prefixes 0x80 and 0x40 are reserved.
|
|
return off, errReserved
|
|
}
|
|
}
|
|
if ptr == 0 {
|
|
newOff = currOff
|
|
}
|
|
return newOff, nil
|
|
}
|
|
|
|
func (dst *Message) CopyFrom(m Message) {
|
|
reuseGrowSlice(&dst.Questions, len(m.Questions))
|
|
reuseGrowSlice(&dst.Answers, len(m.Answers))
|
|
reuseGrowSlice(&dst.Authorities, len(m.Authorities))
|
|
reuseGrowSlice(&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...)
|
|
}
|
|
|
|
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 reuseGrowSlice[T any](dst *[]T, n int) {
|
|
if n == 0 {
|
|
return
|
|
}
|
|
*dst = slices.Grow(*dst, n)[:n]
|
|
}
|