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:
Pat Whittingslow
2026-03-16 19:48:17 +01:00
committed by GitHub
parent 376e1a0b4f
commit bd645b5e1e
14 changed files with 1402 additions and 67 deletions
+40 -2
View File
@@ -19,6 +19,7 @@ import (
"github.com/soypat/lneto/ipv4"
"github.com/soypat/lneto/ipv4/icmpv4"
"github.com/soypat/lneto/ipv6"
"github.com/soypat/lneto/mdns"
"github.com/soypat/lneto/ntp"
"github.com/soypat/lneto/tcp"
"github.com/soypat/lneto/udp"
@@ -359,7 +360,7 @@ func (pc *PacketBreakdown) CaptureUDP(dst []Frame, pkt []byte, bitOffset int) ([
srcport := ufrm.SourcePort()
if dhcpv4.PayloadIsDHCPv4(payload) {
dst, err = pc.CaptureDHCPv4(dst, pkt, end)
} else if dstport == dns.ServerPort || srcport == dns.ServerPort {
} else if dstport == dns.ServerPort || srcport == dns.ServerPort || dstport == mdns.Port || srcport == mdns.Port {
dst, err = pc.CaptureDNS(dst, pkt, end)
} else if dstport == ntp.ServerPort || srcport == ntp.ServerPort {
dst, err = pc.CaptureNTP(dst, pkt, end)
@@ -437,7 +438,7 @@ func (pc *PacketBreakdown) CaptureDNS(dst []Frame, pkt []byte, bitOffset int) ([
return dst, errNotByteAligned
}
dnsData := pkt[bitOffset/8:]
pc.dmsg.LimitResourceDecoding(20, 20, 20, 20)
pc.dmsg.LimitResourceDecoding(4, 4, 4, 4)
off, incomplete, err := pc.dmsg.Decode(dnsData)
if err != nil && !incomplete {
return dst, err
@@ -1265,6 +1266,43 @@ var baseDHCPv4Fields = [...]FrameField{
},
}
var baseDNSFields = [...]FrameField{
{
Class: FieldClassID,
FrameBitOffset: 0,
BitLength: 2 * octet,
},
{
Class: FieldClassFlags,
FrameBitOffset: 2 * octet,
BitLength: 2 * octet,
},
{
Name: "Questions",
Class: FieldClassSize,
FrameBitOffset: 4 * octet,
BitLength: 2 * octet,
},
{
Name: "Answers",
Class: FieldClassSize,
FrameBitOffset: 6 * octet,
BitLength: 2 * octet,
},
{
Name: "Authorities",
Class: FieldClassSize,
FrameBitOffset: 8 * octet,
BitLength: 2 * octet,
},
{
Name: "Additionals",
Class: FieldClassSize,
FrameBitOffset: 10 * octet,
BitLength: 2 * octet,
},
}
var baseNTPFields = [...]FrameField{
{
Name: "Mode",
+19 -11
View File
@@ -33,11 +33,12 @@ type StackEthernetConfig struct {
}
type StackEthernet struct {
connID uint64
handlers handlers
mac [6]byte
gwmac [6]byte
mtu uint16
connID uint64
handlers handlers
mac [6]byte
gwmac [6]byte
mtu uint16
acceptMulticast bool
// crcupdate set when crc32 has been configured to be appended.
crcupdate func(crc uint32, p []byte) uint32
}
@@ -50,6 +51,10 @@ func (ls *StackEthernet) Gateway6() (gw [6]byte) {
return ls.gwmac
}
func (ls *StackEthernet) SetAcceptMulticast(accept bool) {
ls.acceptMulticast = accept
}
func (ls *StackEthernet) SetHardwareAddr6(mac [6]byte) {
ls.mac = mac
}
@@ -83,11 +88,12 @@ func (ls *StackEthernet) Configure(cfg StackEthernetConfig) error {
}
ls.handlers.reset("StackEthernet", cfg.MaxNodes)
*ls = StackEthernet{
connID: ls.connID + 1,
handlers: ls.handlers,
mac: cfg.MAC,
gwmac: cfg.Gateway,
mtu: uint16(cfg.MTU),
connID: ls.connID + 1,
handlers: ls.handlers,
mac: cfg.MAC,
gwmac: cfg.Gateway,
mtu: uint16(cfg.MTU),
acceptMulticast: ls.acceptMulticast,
}
if cfg.AppendCRC32 {
ls.crcupdate = cfg.CRC32Update
@@ -121,7 +127,9 @@ func (ls *StackEthernet) Demux(carrierData []byte, frameOffset int) (err error)
dstaddr := efrm.DestinationHardwareAddr()
var vld lneto.Validator
if !efrm.IsBroadcast() && ls.mac != *dstaddr {
goto DROP
if !ls.acceptMulticast || dstaddr[0]&1 == 0 {
goto DROP
}
}
efrm.ValidateSize(&vld)
if vld.HasError() {
+19 -11
View File
@@ -16,11 +16,12 @@ import (
var _ StackNode = (*StackIP)(nil)
type StackIP struct {
connID uint64
ipID uint16
ip [4]byte
validator lneto.Validator
handlers handlers
connID uint64
ipID uint16
ip [4]byte
acceptMulticast bool
validator lneto.Validator
handlers handlers
}
func (sb *StackIP) Reset(addr netip.Addr, maxNodes int) error {
@@ -33,10 +34,11 @@ func (sb *StackIP) Reset(addr netip.Addr, maxNodes int) error {
}
sb.handlers.reset("StackIP", maxNodes)
*sb = StackIP{
connID: sb.connID + 1,
validator: sb.validator,
handlers: sb.handlers,
ip: sb.ip,
connID: sb.connID + 1,
validator: sb.validator,
handlers: sb.handlers,
ip: sb.ip,
acceptMulticast: sb.acceptMulticast,
}
return nil
}
@@ -65,6 +67,10 @@ func (sb *StackIP) Addr() netip.Addr {
return netip.AddrFrom4(sb.ip)
}
func (sb *StackIP) SetAcceptMulticast(accept bool) {
sb.acceptMulticast = accept
}
func (sb *StackIP) SetLogger(logger *slog.Logger) {
sb.handlers.log = logger
}
@@ -79,8 +85,10 @@ func (sb *StackIP) Demux(carrierData []byte, offset int) error {
}
dst := ifrm.DestinationAddr()
if sb.ip != ([4]byte{}) && *dst != sb.ip {
sb.handlers.debug("ip:not-for-us")
return lneto.ErrPacketDrop // Not meant for us.
if !sb.acceptMulticast || dst[0]&0xF0 != 0xE0 {
sb.handlers.debug("ip:not-for-us")
return lneto.ErrPacketDrop // Not meant for us.
}
}
sb.validator.ResetErr()