Files
lneto/dns/definitions.go
Pat Whittingslow 7d5830d7ab V2 Netbird integration - UDP MIMO/SIMO, ICMPv6, DHCPv6 implementations (#106)
* begin adding udp.MuxHandler

* add udp MuxHandlerSIMO/MIMO

* add tcp rx shutdown

* icmpv6 client

* icmpv6 Client shared NDP/Echo preparation

* icmpv6 client ndp/echo split

* icmpv6 client ndp/echo split done

* icmpv6 adjustments

* add dhcpv6 stubs

* dhcpv4 preliminary revision

* add dns.NextLabel

* dns label name tweaks

* dns begin work on TCP client

* add dnstcp package

* apply gofmt changes

* add udp mux tests

* clean up, remove StackBig for now

* remove dnstcp so as to merged confident parts and we continue dnstcp work elsewhere
2026-05-10 12:16:30 -03:00

258 lines
8.7 KiB
Go

package dns
import (
"encoding/binary"
"errors"
"github.com/soypat/lneto"
)
//go:generate stringer -type=Type,Class,RCode,OpCode -linecomment -output stringers.go .
// common errors. Taken from golang.org/x/net/dns/dnsmessage module.
var (
errNoResponse = errors.New("no DNS response")
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")
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 = lneto.ErrExhausted
errTooManyAnswers = lneto.ErrExhausted
errTooManyAuthorities = lneto.ErrExhausted
errTooManyAdditionals = lneto.ErrExhausted
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")
errEmptyDomainName = errors.New("empty domain name")
)
// 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, error) {
if len(buf) < SizeHeader {
return Frame{}, lneto.ErrTruncatedFrame
}
return Frame{buf: buf}, nil
}
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 RCode) Error() string {
return flags.String()
}
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
TypeHTTPS Type = 65 // HTTPS SSE
// 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
)
// StateClientQuery is the lifecycle state of a single DNS query.
type StateClientQuery uint8
const (
CQueryIdle StateClientQuery = iota // no active query (zero value)
CQueryPending // query built, not yet transmitted
CQueryOutstanding // transmitted; awaiting response (RFC 7766 §9.3)
CQueryDone // response received and decoded
CQueryAborted // query abandoned (connection error or caller abort)
)
func b2u8(b bool) uint8 {
if b {
return 1
}
return 0
}