mirror of
https://github.com/soypat/lneto.git
synced 2026-07-26 10:38:47 +00:00
7d5830d7ab
* 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
258 lines
8.7 KiB
Go
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
|
|
}
|