mirror of
https://github.com/soypat/lneto.git
synced 2026-09-10 16:49:37 +00:00
Add mdns package and Client implementation (#55)
* add mdns * define mdns.Client * fix some mdns stuff * refine dns package for use with mdns * remove Querier and Responder and replace with Client * add record setting methods on dns.record to reuse record buffer * protect against unbounded client answer growth * round off sharp mdns edges; reduce allocs * add mutlicast to stacks; add xnet tests for mdns * fix documentation on acceptmulticast field * mdns working
This commit is contained in:
+1
-1
@@ -34,7 +34,7 @@ func (sudp *Client) ConnectionID() *uint64 { return &sudp.connID }
|
||||
func (c *Client) StartResolve(localPort, txid uint16, cfg ResolveConfig) error {
|
||||
nd := len(cfg.Questions)
|
||||
if nd > math.MaxUint16 {
|
||||
return lneto.ErrBufferFull
|
||||
return lneto.ErrInvalidConfig
|
||||
}
|
||||
c.reset(localPort, txid, dnsSendQuery, cfg.EnableRecursion)
|
||||
c.msg.LimitResourceDecoding(uint16(nd), uint16(nd), 0, 0)
|
||||
|
||||
+115
-37
@@ -57,6 +57,13 @@ type Name struct {
|
||||
data []byte
|
||||
}
|
||||
|
||||
// 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 {
|
||||
@@ -86,83 +93,91 @@ func (r *Resource) SetEDNS0(UDPlength uint16, rcode RCode, zflags ZFlags, data [
|
||||
r.data = append(r.data[:0], data...)
|
||||
}
|
||||
|
||||
// Decode decodes the DNS message in b into m. It returns the number of bytes
|
||||
// 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.
|
||||
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()
|
||||
//
|
||||
// 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
|
||||
}
|
||||
nq := int(hdr.QDCount())
|
||||
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 > cap(m.Questions):
|
||||
case nq > caporzero(q):
|
||||
tooManyErr = errTooManyQuestions
|
||||
case hdr.ANCount() > uint16(cap(m.Answers)):
|
||||
case int(hdr.ANCount()) > caporzero(answers):
|
||||
tooManyErr = errTooManyAnswers
|
||||
case hdr.NSCount() > uint16(cap(m.Authorities)):
|
||||
case int(hdr.NSCount()) > caporzero(authorities):
|
||||
tooManyErr = errTooManyAuthorities
|
||||
case hdr.ARCount() > uint16(cap(m.Additionals)):
|
||||
case int(hdr.ARCount()) > caporzero(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
|
||||
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.
|
||||
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)
|
||||
off, err = decodeToCapResources(answers, msg, hdr.ANCount(), off)
|
||||
if err != nil {
|
||||
return off, false, err
|
||||
}
|
||||
off, err = decodeToCapResources(&m.Authorities, msg, hdr.NSCount(), off)
|
||||
off, err = decodeToCapResources(authorities, msg, hdr.NSCount(), off)
|
||||
if err != nil {
|
||||
return off, false, err
|
||||
}
|
||||
off, err = decodeToCapResources(&m.Additionals, msg, hdr.ARCount(), off)
|
||||
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 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
|
||||
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.
|
||||
@@ -468,6 +483,20 @@ func NewName(domain string) (Name, error) {
|
||||
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 i := 0; i < skip; i++ {
|
||||
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 {
|
||||
@@ -666,6 +695,48 @@ func (dst *Resource) CopyFrom(r Resource) {
|
||||
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
|
||||
@@ -673,3 +744,10 @@ func (dst *ResourceHeader) CopyFrom(rh ResourceHeader) {
|
||||
dst.TTL = rh.TTL
|
||||
dst.Length = rh.Length
|
||||
}
|
||||
|
||||
func caporzero[T any](v *[]T) int {
|
||||
if v == nil {
|
||||
return 0
|
||||
}
|
||||
return cap(*v)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user