restructure package, rename module/repo

This commit is contained in:
soypat
2025-01-07 11:34:35 -03:00
parent 73f0b547b3
commit 9abf0c11b9
30 changed files with 20 additions and 18 deletions
+233
View File
@@ -0,0 +1,233 @@
package dns
import (
"encoding/binary"
"errors"
)
//go:generate stringer -type=Type,Class,RCode,OpCode -linecomment -output stringers.go .
// common errors. Taken from golang.org/x/net/dns/dnsmessage module.
var (
errNameTooLong = errors.New("DNS name exceeds maximum length")
errNoNullTerm = errors.New("DNS name missing null terminator")
errCalcLen = errors.New("DNS calculated name label length exceeds remaining buffer length")
errCantAddLabel = errors.New("long/empty/zterm/escape DNS label or not enough space")
errBaseLen = errors.New("insufficient data for base length type")
errReserved = errors.New("segment prefix is reserved")
errTooManyPtr = errors.New("too many pointers (>10)")
errInvalidPtr = errors.New("invalid pointer")
errInvalidName = errors.New("invalid dns name")
errNilResouceBody = errors.New("nil resource body")
errResourceLen = errors.New("insufficient data for resource body length")
errSegTooLong = errors.New("segment length too long")
errZeroSegLen = errors.New("zero length segment")
errResTooLong = errors.New("resource length too long")
errTooManyQuestions = errors.New("too many Questions")
errTooManyAnswers = errors.New("too many Answers")
errTooManyAuthorities = errors.New("too many Authorities")
errTooManyAdditionals = errors.New("too many Additionals")
errNonCanonicalName = errors.New("name is not in canonical format (it must end with a .)")
errStringTooLong = errors.New("character string exceeds maximum length (255)")
errCompressedSRV = errors.New("compressed name in SRV resource data")
)
// Frame encapsulates the raw data of a DNS packet
// and provides methods for manipulating, validating and
// retrieving fields and payload data. See [RFC1035].
//
// [RFC1035]: https://tools.ietf.org/html/rfc1035
type Frame struct {
buf []byte
}
func NewFrame(buf []byte) Frame {
return Frame{buf: buf}
}
func (frm Frame) TxID() uint16 {
return binary.BigEndian.Uint16(frm.buf[0:2])
}
func (frm Frame) SetTxID(txid uint16) {
binary.BigEndian.PutUint16(frm.buf[0:2], txid)
}
func (frm Frame) Flags() HeaderFlags {
return HeaderFlags(binary.BigEndian.Uint16(frm.buf[2:4]))
}
func (frm Frame) SetFlags(flags HeaderFlags) {
binary.BigEndian.PutUint16(frm.buf[2:4], uint16(flags))
}
// QDCount returns number of entries in the question section.
func (frm Frame) QDCount() uint16 {
return binary.BigEndian.Uint16(frm.buf[4:6])
}
func (frm Frame) SetQDCount(qdCount uint16) {
binary.BigEndian.PutUint16(frm.buf[4:6], qdCount)
}
// ANCount returns number of resource records in the answer section.
func (frm Frame) ANCount() uint16 {
return binary.BigEndian.Uint16(frm.buf[6:8])
}
func (frm Frame) SetANCount(anCount uint16) {
binary.BigEndian.PutUint16(frm.buf[6:8], anCount)
}
// NSCount returns number of name server resource records in the authority records section.
func (frm Frame) NSCount() uint16 {
return binary.BigEndian.Uint16(frm.buf[8:10])
}
func (frm Frame) SetNSCount(nsCount uint16) {
binary.BigEndian.PutUint16(frm.buf[8:10], nsCount)
}
// ARCount returns number of resource records in the additional records section.
func (frm Frame) ARCount() uint16 {
return binary.BigEndian.Uint16(frm.buf[10:12])
}
func (frm Frame) SetARCount(arCount uint16) {
binary.BigEndian.PutUint16(frm.buf[10:12], arCount)
}
// ClearHeader zeros out the fixed(non-variable) header contents.
func (frm Frame) ClearHeader() {
for i := range frm.buf[:SizeHeader] {
frm.buf[i] = 0
}
}
// HeaderFlags gathers the flags in bits 16..31 of the header.
type HeaderFlags uint16
// NewClientHeaderFlags creates the header flags for a client request.
func NewClientHeaderFlags(op OpCode, enableRecursion bool) HeaderFlags {
return HeaderFlags(op&0b1111)<<11 | HeaderFlags(b2u8(enableRecursion))<<8
}
// IsResponse returns QR bit which specifies whether this message is a query (0), or a response (1).
func (flags HeaderFlags) IsResponse() bool { return flags&(1<<15) != 0 }
// OpCode returns the 4-bit opcode.
func (flags HeaderFlags) OpCode() OpCode { return OpCode(flags>>11) & 0b1111 }
// IsAuthorativeAnswer returns AA bit which specifies that the responding name server is an authority for the domain name in question section.
func (flags HeaderFlags) IsAuthorativeAnswer() bool { return flags&(1<<10) != 0 }
// IsTruncated returns TC bit which specifies that this message was truncated due to length greater than that permitted on the transmission channel.
func (flags HeaderFlags) IsTruncated() bool { return flags&(1<<9) != 0 }
// IsRecursionDesired returns RD bit which specifies whether recursive query support is desired by the client. Is optionally set by client.
func (flags HeaderFlags) IsRecursionDesired() bool { return flags&(1<<8) != 0 }
// IsRecursionAvailable returns RA bit which specifies whether recursive query support is available by the server.
func (flags HeaderFlags) IsRecursionAvailable() bool { return flags&(1<<7) != 0 }
// ResponseCode returns the 4-bit response code set as part of responses.
func (flags HeaderFlags) ResponseCode() RCode { return RCode(flags & 0b1111) }
func (flags HeaderFlags) String() string {
buf := make([]byte, 0, 16)
return string(flags.appendF(buf))
}
func (flags HeaderFlags) appendF(buf []byte) []byte {
writeBit := func(b bool, s string) {
if b {
buf = append(buf, s...)
buf = append(buf, ' ')
}
}
writeBit(flags.IsResponse(), "QR")
writeBit(flags.IsAuthorativeAnswer(), "AA")
writeBit(flags.IsTruncated(), "TC")
writeBit(flags.IsRecursionDesired(), "RD")
writeBit(flags.IsRecursionAvailable(), "RA")
buf = append(buf, flags.OpCode().String()...)
buf = append(buf, ' ')
buf = append(buf, flags.ResponseCode().String()...)
return buf
}
const allowCompression = true
// Types taken from golang.org/x/net/dns/dnsmessage package. See https://pkg.go.dev/golang.org/x/net/dns/dnsmessage.
// Type is a type of DNS request and response.
type Type uint16
const (
// ResourceHeader.Type and Question.Type
TypeA Type = 1 // A
TypeNS Type = 2 // NS
TypeCNAME Type = 5 // CNAME
TypeSOA Type = 6 // SOA
TypePTR Type = 12 // PTR
TypeMX Type = 15 // MX
TypeTXT Type = 16 // TXT
TypeAAAA Type = 28 // AAAA
TypeSRV Type = 33 // SRV
TypeOPT Type = 41 // OPT
// Question.Type
TypeWKS Type = 11 // WKS
TypeHINFO Type = 13 // HINFO
TypeMINFO Type = 14 // MINFO
TypeAXFR Type = 252 // AXFR
TypeALL Type = 255 // ALL
)
// A Class is a type of network.
type Class uint16
const (
// ResourceHeader.Class and Question.Class
ClassINET Class = 1 // INET
ClassCSNET Class = 2 // CSNET
ClassCHAOS Class = 3 // CHAOS
ClassHESIOD Class = 4 // HESIOD
// Question.Class
ClassANY Class = 255 // ANY
)
// An OpCode is a DNS operation code which specifies the type of query.
type OpCode uint16
const (
OpCodeQuery OpCode = 0 // Standard query
OpCodeInverseQuery OpCode = 1 // Inverse query
OpCodeStatus OpCode = 2 // Server status request
)
// An RCode is a DNS response status code.
type RCode uint16
const (
// No error condition.
RCodeSuccess RCode = 0 // success
// Format error - The name server was unable to interpret the query.
RCodeFormatError RCode = 1 // format error
// Server failure - The name server was unable to process this query due to a problem with the name server.
RCodeServerFailure RCode = 2 // server failure
// Name Error - Meaningful only for responses from an authoritative name server, this code signifies that the domain name referenced in the query does not exist.
RCodeNameError RCode = 3 // name error
// Not implemented - The name server does not support the requested kind of query.
RCodeNotImplemented RCode = 4 // not implemented
// Refused - The name server refuses to perform the specified operation for policy reasons. For example, a name server may not wish to provide the information to the particular requester, or a name server may not wish to perform a particular operation (e.g., zone transfer) for particular data.
RCodeRefused RCode = 5 // refused
)
func b2u8(b bool) uint8 {
if b {
return 1
}
return 0
}
+566
View File
@@ -0,0 +1,566 @@
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
}
// 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 := NewFrame(msg)
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.
for i := 0; i < int(hdr.QDCount())-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 := NewFrame(hdr[:])
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].Name.CloneFrom(questions[i].Name)
m.Questions[qoff+i].Type = questions[i].Type
m.Questions[qoff+i].Class = questions[i].Class
}
}
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) Len() uint16 {
return r.Header.Name.Len() + 10 + uint16(len(r.data))
}
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) {
r.Header.Length = uint16(len(r.data))
buf, err = r.Header.appendTo(buf)
if err != nil {
return buf, err
}
buf = append(buf, r.data...)
return buf, nil
}
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
}
// NewName parses a domain name and returns a new Name.
func NewName(domain string) (Name, error) {
if len(domain) == 1 && domain[0] == '.' {
return Name{data: []byte{0}}, 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 {
return uint16(len(n.data))
}
func (n *Name) CloneFrom(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 and 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
}
+223
View File
@@ -0,0 +1,223 @@
package dns
import (
"fmt"
"strings"
"testing"
)
var defaultMessageFlags = NewClientHeaderFlags(OpCodeQuery, true)
func TestNameString(t *testing.T) {
var name Name
domain := "foo.bar.org"
domainSplit := strings.Split(domain, ".")
for i, label := range domainSplit {
name.AddLabel(label)
s := name.String()
if s != strings.Join(domainSplit[:i+1], ".")+"." {
t.Fatalf("unexpected name string %q", s)
}
}
}
func TestNameAppendDecode(t *testing.T) {
const domain = "foo.bar.org"
name, err := NewName(domain)
if err != nil {
t.Fatal(err)
} else if name.String() != domain+"." {
t.Fatalf("unexpected name string %q", name.String())
}
var buf [512]byte
b, err := name.AppendTo(buf[:0])
if err != nil {
t.Fatal(err)
}
if uint16(len(b)) != name.Len() {
t.Fatalf("unexpected name length %d", len(b))
}
if b[len(b)-1] != 0 {
t.Fatalf("unexpected name terminator byte after construction: %q", b[len(b)-1])
}
var name2 Name
n, err := name2.Decode(b, 0)
if err != nil {
t.Fatal(err)
}
if n != name.Len() {
t.Errorf("unexpected name parsed length %q (%d), want %q (%d)", name.data, n, b, name.Len())
}
if name2.String() != name.String() {
t.Errorf("unexpected name string %q, want %q", name2.String(), name.String())
}
// Re-decode.
const okvalidName = "\x03www\x02go\x03dev\x00"
_, err = name.Decode([]byte(okvalidName), 0)
if err != nil {
t.Error("got error decoding valid name", err)
} else if name.String() != "www.go.dev." {
t.Error("unexpected name string", name.String())
}
b, err = name.AppendTo(buf[:0])
if err != nil {
t.Fatal(err)
}
if b[len(b)-1] != 0 {
t.Fatalf("unexpected name terminator byte after decoding: %q", b[len(b)-1])
}
if string(b) != okvalidName {
t.Errorf("unexpected name bytes after decode %q, want %q", b, okvalidName)
}
// Decode invalid name.
const invalidName = "\x03w.w\x02go\x03dev\x00"
_, err = name.Decode([]byte(invalidName), 0)
if err == nil {
t.Error("expected error for invalid name")
} else if err != errInvalidName {
t.Errorf("unexpected error %v, want %v", err, errInvalidName)
}
}
func TestMessageAppendEncode(t *testing.T) {
var tests = []struct {
Message Message
error error
}{
{
Message: Message{
Questions: []Question{
{
Name: MustNewName("."),
Type: TypeA,
Class: ClassINET,
},
},
Answers: []Resource{
{
Header: ResourceHeader{
Name: MustNewName("."),
Type: TypeA,
Class: ClassINET,
TTL: 256,
Length: 3,
},
data: []byte{1, 2, 3},
},
},
},
},
}
var buf [512]byte
for _, tt := range tests {
b, err := tt.Message.AppendTo(buf[:0], 123, defaultMessageFlags)
if err != nil {
t.Fatal(err)
}
var msg Message
msg.LimitResourceDecoding(uint16(len(tt.Message.Questions)), uint16(len(tt.Message.Answers)), uint16(len(tt.Message.Authorities)), uint16(len(tt.Message.Additionals)))
_, incomplete, err := msg.Decode(b)
if err != nil {
t.Fatal(err)
} else if incomplete {
t.Fatal("incomplete parse")
}
if msg.String() != tt.Message.String() {
t.Errorf("mismatch message strings after append/decode:\n%s\n%s", tt.Message.String(), msg.String())
}
}
}
func TestMessageAppendEncodeIncompleteOK(t *testing.T) {
var tests = []struct {
Message Message
error error
}{
{
Message: Message{
Questions: []Question{
{
Name: MustNewName("."),
Type: TypeA,
Class: ClassINET,
},
},
Answers: []Resource{
{
Header: ResourceHeader{
Name: MustNewName("."),
Type: TypeA,
Class: ClassINET,
TTL: 256,
Length: 3,
},
data: []byte{1, 2, 3},
},
{
Header: ResourceHeader{
Name: MustNewName("."),
Type: TypeA,
Class: ClassINET,
TTL: 256,
Length: 3,
},
data: []byte{1, 2, 3},
},
},
},
},
}
var buf [512]byte
for _, tt := range tests {
b, err := tt.Message.AppendTo(buf[:0], 123, defaultMessageFlags)
if err != nil {
t.Fatal(err)
}
var msg Message
msg.LimitResourceDecoding(uint16(len(tt.Message.Questions)), uint16(len(tt.Message.Answers)), uint16(len(tt.Message.Authorities)), uint16(len(tt.Message.Additionals)))
_, incomplete, err := msg.Decode(b)
if err != nil && !incomplete {
t.Fatal(err)
} else if !incomplete {
t.Fatal("expected incomplete parse")
}
tt.Message.Answers = tt.Message.Answers[:1] // Trim off the last answer that was not parsed.
if msg.String() != tt.Message.String() {
t.Errorf("mismatch message strings after append/decode:\n%s\n%s", tt.Message.String(), msg.String())
}
}
}
func (m *Message) String() string {
// s := fmt.Sprintf("Message: %#v\n", &m.Header)
var s string
if len(m.Questions) > 0 {
s += "-- Questions\n"
for _, q := range m.Questions {
s += fmt.Sprintf("%#v\n", q)
}
}
if len(m.Answers) > 0 {
s += "-- Answers\n"
for _, a := range m.Answers {
s += fmt.Sprintf("%#v\n", a)
}
}
if len(m.Authorities) > 0 {
s += "-- Authorities\n"
for _, ns := range m.Authorities {
s += fmt.Sprintf("%#v\n", ns)
}
}
if len(m.Additionals) > 0 {
s += "-- Additionals\n"
for _, e := range m.Additionals {
s += fmt.Sprintf("%#v\n", e)
}
}
return s
}
+141
View File
@@ -0,0 +1,141 @@
// Code generated by "stringer -type=Type,Class,RCode,OpCode -linecomment -output stringers.go ."; DO NOT EDIT.
package dns
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[TypeA-1]
_ = x[TypeNS-2]
_ = x[TypeCNAME-5]
_ = x[TypeSOA-6]
_ = x[TypePTR-12]
_ = x[TypeMX-15]
_ = x[TypeTXT-16]
_ = x[TypeAAAA-28]
_ = x[TypeSRV-33]
_ = x[TypeOPT-41]
_ = x[TypeWKS-11]
_ = x[TypeHINFO-13]
_ = x[TypeMINFO-14]
_ = x[TypeAXFR-252]
_ = x[TypeALL-255]
}
const (
_Type_name_0 = "ANS"
_Type_name_1 = "CNAMESOA"
_Type_name_2 = "WKSPTRHINFOMINFOMXTXT"
_Type_name_3 = "AAAA"
_Type_name_4 = "SRV"
_Type_name_5 = "OPT"
_Type_name_6 = "AXFR"
_Type_name_7 = "ALL"
)
var (
_Type_index_0 = [...]uint8{0, 1, 3}
_Type_index_1 = [...]uint8{0, 5, 8}
_Type_index_2 = [...]uint8{0, 3, 6, 11, 16, 18, 21}
)
func (i Type) String() string {
switch {
case 1 <= i && i <= 2:
i -= 1
return _Type_name_0[_Type_index_0[i]:_Type_index_0[i+1]]
case 5 <= i && i <= 6:
i -= 5
return _Type_name_1[_Type_index_1[i]:_Type_index_1[i+1]]
case 11 <= i && i <= 16:
i -= 11
return _Type_name_2[_Type_index_2[i]:_Type_index_2[i+1]]
case i == 28:
return _Type_name_3
case i == 33:
return _Type_name_4
case i == 41:
return _Type_name_5
case i == 252:
return _Type_name_6
case i == 255:
return _Type_name_7
default:
return "Type(" + strconv.FormatInt(int64(i), 10) + ")"
}
}
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[ClassINET-1]
_ = x[ClassCSNET-2]
_ = x[ClassCHAOS-3]
_ = x[ClassHESIOD-4]
_ = x[ClassANY-255]
}
const (
_Class_name_0 = "INETCSNETCHAOSHESIOD"
_Class_name_1 = "ANY"
)
var (
_Class_index_0 = [...]uint8{0, 4, 9, 14, 20}
)
func (i Class) String() string {
switch {
case 1 <= i && i <= 4:
i -= 1
return _Class_name_0[_Class_index_0[i]:_Class_index_0[i+1]]
case i == 255:
return _Class_name_1
default:
return "Class(" + strconv.FormatInt(int64(i), 10) + ")"
}
}
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[RCodeSuccess-0]
_ = x[RCodeFormatError-1]
_ = x[RCodeServerFailure-2]
_ = x[RCodeNameError-3]
_ = x[RCodeNotImplemented-4]
_ = x[RCodeRefused-5]
}
const _RCode_name = "successformat errorserver failurename errornot implementedrefused"
var _RCode_index = [...]uint8{0, 7, 19, 33, 43, 58, 65}
func (i RCode) String() string {
if i >= RCode(len(_RCode_index)-1) {
return "RCode(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _RCode_name[_RCode_index[i]:_RCode_index[i+1]]
}
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[OpCodeQuery-0]
_ = x[OpCodeInverseQuery-1]
_ = x[OpCodeStatus-2]
}
const _OpCode_name = "Standard queryInverse queryServer status request"
var _OpCode_index = [...]uint8{0, 14, 27, 48}
func (i OpCode) String() string {
if i >= OpCode(len(_OpCode_index)-1) {
return "OpCode(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _OpCode_name[_OpCode_index[i]:_OpCode_index[i+1]]
}