mirror of
https://github.com/soypat/lneto.git
synced 2026-07-26 10:38:47 +00:00
mdns+xnet: add test that replicates issue in cyw43439
This commit is contained in:
@@ -33,6 +33,28 @@ func BroadcastAddr() [6]byte {
|
||||
return [6]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
|
||||
}
|
||||
|
||||
// IPv4MulticastToMAC maps an IPv4 multicast address to the corresponding
|
||||
// Ethernet multicast MAC address.
|
||||
//
|
||||
// If ip is not in the IPv4 multicast range (224.0.0.0/4), ok is false and
|
||||
// mac is zero.
|
||||
func MulticastAddrFrom4(ip [4]byte) (mac [6]byte, ok bool) {
|
||||
if ip[0]&0xf0 != 0xe0 {
|
||||
return mac, false
|
||||
}
|
||||
|
||||
mac[0] = 0x01
|
||||
mac[1] = 0x00
|
||||
mac[2] = 0x5e
|
||||
|
||||
// Lower 23 bits of IP
|
||||
mac[3] = ip[1] & 0x7f // drop top bit
|
||||
mac[4] = ip[2]
|
||||
mac[5] = ip[3]
|
||||
|
||||
return mac, true
|
||||
}
|
||||
|
||||
//go:generate stringer -type=Type -linecomment -output stringers.go .
|
||||
|
||||
type Type uint16
|
||||
|
||||
@@ -22,6 +22,7 @@ func NewFrame(buf []byte) (Frame, error) {
|
||||
// Frame encapsulates the raw data of an IPv4 packet
|
||||
// and provides methods for manipulating, validating and
|
||||
// retreiving fields and payload data. See [RFC791].
|
||||
// Below is an example of setting IPv4 fields for MDNS:
|
||||
//
|
||||
// [RFC791]: https://tools.ietf.org/html/rfc791
|
||||
type Frame struct {
|
||||
@@ -156,6 +157,12 @@ func (ifrm Frame) CRCWriteTCPPseudo(crc *lneto.CRC791) {
|
||||
crc.AddUint16(uint16(ifrm.Protocol()))
|
||||
}
|
||||
|
||||
// CRCWriteUDPPseudo writes the IPv4 UDP pseudo-header into crc for checksum
|
||||
// calculation. Typical usage for computing or validating a UDP checksum:
|
||||
//
|
||||
// ifrm.CRCWriteUDPPseudo(&crc, ufrm.Length())
|
||||
// // ufrm.SetCRC(0) // Zero before computing checksum for transmission.
|
||||
// crcValue := crc.PayloadSum16(ifrm.Payload()) // For received frames, crcValue should be zero if valid.
|
||||
func (ifrm Frame) CRCWriteUDPPseudo(crc *lneto.CRC791, udpLength uint16) {
|
||||
crc.WriteEven(ifrm.sourceAndDestinationAddr())
|
||||
crc.AddUint16(udpLength)
|
||||
|
||||
@@ -4,6 +4,19 @@ import (
|
||||
"github.com/soypat/lneto/dns"
|
||||
)
|
||||
|
||||
// IPv4MulticastAddr is the IPv4 multicast address used by mDNS (224.0.0.251).
|
||||
// Defined by RFC 6762. Packets sent to this address use UDP port 5353 and are
|
||||
// link-local (not routed beyond the local network segment).
|
||||
func IPv4MulticastAddr() [4]byte {
|
||||
return [4]byte{224, 0, 0, 251}
|
||||
}
|
||||
|
||||
// IPv4MulticastMAC is the Ethernet multicast MAC address corresponding to
|
||||
// 224.0.0.251 (01:00:5e:00:00:fb). Used for L2 delivery of mDNS over Ethernet.
|
||||
func IPv4MulticastMAC() [6]byte {
|
||||
return [6]byte{0x01, 0x00, 0x5e, 0x00, 0x00, 0xfb}
|
||||
}
|
||||
|
||||
// Service describes a service to advertise via mDNS.
|
||||
// A single Service produces PTR, SRV, TXT, and A resource records.
|
||||
//
|
||||
@@ -68,6 +81,23 @@ func matchQuestion(q *dns.Question, svc *Service) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
func MulticastMAC(ip [4]byte) (mac [6]byte, ok bool) {
|
||||
// Check IPv4 multicast range: 224.0.0.0/4
|
||||
if ip[0]&0xf0 != 0xe0 {
|
||||
return mac, false
|
||||
}
|
||||
|
||||
mac[0] = 0x01
|
||||
mac[1] = 0x00
|
||||
mac[2] = 0x5e
|
||||
|
||||
// Lower 23 bits of IP
|
||||
mac[3] = ip[1] & 0x7f // drop top bit
|
||||
mac[4] = ip[2]
|
||||
mac[5] = ip[3]
|
||||
|
||||
return mac, true
|
||||
}
|
||||
|
||||
// addServiceAnswers adds the appropriate answer records for a matched question.
|
||||
// It grows ans in-place, reusing existing Resource buffers when available.
|
||||
|
||||
@@ -5,9 +5,12 @@ import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/soypat/lneto"
|
||||
"github.com/soypat/lneto/dns"
|
||||
"github.com/soypat/lneto/ethernet"
|
||||
"github.com/soypat/lneto/ipv4"
|
||||
"github.com/soypat/lneto/mdns"
|
||||
"github.com/soypat/lneto/udp"
|
||||
)
|
||||
|
||||
func TestMDNS_QueryResponse(t *testing.T) {
|
||||
@@ -330,3 +333,129 @@ func mdnsQueryRespond(t *testing.T, querier, responder *StackAsync, buf []byte)
|
||||
t.Fatal("querier demux:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMDNS_RealWorldQueries(t *testing.T) {
|
||||
const MTU = 1500
|
||||
|
||||
responderMAC := [6]byte{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x01}
|
||||
querierMAC := [6]byte{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x02}
|
||||
|
||||
responderIP := netip.AddrFrom4([4]byte{192, 168, 50, 81})
|
||||
querierIP := netip.AddrFrom4([4]byte{192, 168, 50, 213})
|
||||
|
||||
mcastAddr := mdns.IPv4MulticastAddr()
|
||||
|
||||
// Service hosted by responder.
|
||||
hostName := dns.MustNewName("server.local")
|
||||
svc := mdns.Service{
|
||||
Name: hostName,
|
||||
Host: hostName,
|
||||
Addr: []byte{192, 168, 50, 81},
|
||||
Port: 80,
|
||||
}
|
||||
|
||||
responderStack, _ := newMDNSStack(t, "responder", 1,
|
||||
responderIP, responderMAC, querierMAC,
|
||||
mdns.ClientConfig{
|
||||
LocalPort: mdns.Port,
|
||||
Services: []mdns.Service{svc},
|
||||
MulticastAddr: mcastAddr[:],
|
||||
},
|
||||
)
|
||||
|
||||
pkts := []struct {
|
||||
name string
|
||||
qname string
|
||||
qtype dns.Type
|
||||
unicast bool // QU bit
|
||||
}{
|
||||
// Frame 1: QM multicast A server.local
|
||||
{"QM_A_server", "server.local", dns.TypeA, false},
|
||||
|
||||
// Frame 2: QU unicast AAAA random.local
|
||||
{"QU_AAAA_random", "rds-th-TH010-e6614864d3511735.local", dns.TypeAAAA, true},
|
||||
|
||||
// Frame 3: QU unicast A random.local
|
||||
{"QU_A_random", "rds-th-TH010-e6614864d3511735.local", dns.TypeA, true},
|
||||
}
|
||||
|
||||
var buf [MTU + ethernet.MaxOverheadSize]byte
|
||||
checkNoData := func(msg string) {
|
||||
t.Helper()
|
||||
n, err := responderStack.Encapsulate(buf[:], -1, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if n != 0 {
|
||||
t.Errorf(" %s: expected no data sent: %d", msg, n)
|
||||
}
|
||||
}
|
||||
checkNoData("before transaction")
|
||||
var msg dns.Message
|
||||
for _, q := range pkts {
|
||||
msg.Reset()
|
||||
msg.AddQuestions([]dns.Question{
|
||||
{
|
||||
Name: dns.MustNewName(q.qname),
|
||||
Type: q.qtype,
|
||||
Class: withQU(q.unicast),
|
||||
},
|
||||
})
|
||||
efrm, _ := ethernet.NewFrame(buf[:])
|
||||
*efrm.DestinationHardwareAddr(), _ = ethernet.MulticastAddrFrom4(mdns.IPv4MulticastAddr())
|
||||
*efrm.SourceHardwareAddr() = querierMAC
|
||||
efrm.SetEtherType(ethernet.TypeIPv4)
|
||||
|
||||
ifrm, _ := ipv4.NewFrame(efrm.Payload())
|
||||
ifrm.SetVersionAndIHL(4, 5) // No options. IHL=5, IPLEN=4*IHL=20
|
||||
ifrm.SetToS(ipv4.NewToS(0, 0))
|
||||
ifrm.SetTotalLength(20 + 8 + msg.Len())
|
||||
ifrm.SetID(1337)
|
||||
ifrm.SetFlags(ipv4.FlagDontFragment)
|
||||
ifrm.SetTTL(64)
|
||||
ifrm.SetProtocol(lneto.IPProtoUDP)
|
||||
|
||||
*ifrm.SourceAddr() = querierIP.As4()
|
||||
*ifrm.DestinationAddr() = mdns.IPv4MulticastAddr()
|
||||
|
||||
ifrm.SetCRC(0) // Zero CRC before calculating CRC, as custom with lneto.
|
||||
ifrm.SetCRC(ifrm.CalculateHeaderCRC())
|
||||
|
||||
ufrm, _ := udp.NewFrame(ifrm.Payload())
|
||||
ufrm.SetSourcePort(mdns.Port)
|
||||
ufrm.SetDestinationPort(mdns.Port)
|
||||
ufrm.SetLength(8 + msg.Len())
|
||||
|
||||
mdnsPayload, _ := msg.AppendTo(ufrm.Payload()[:0], 0, 0)
|
||||
if len(mdnsPayload) != int(msg.Len()) {
|
||||
t.Fatal("unreachable")
|
||||
}
|
||||
var crc lneto.CRC791
|
||||
ufrm.SetCRC(0)
|
||||
ifrm.CRCWriteUDPPseudo(&crc, ufrm.Length())
|
||||
got := crc.PayloadSum16(ifrm.Payload())
|
||||
ufrm.SetCRC(got)
|
||||
err := responderStack.Demux(buf[:14+20+8+msg.Len()], 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
n, err := responderStack.Encapsulate(buf[:], -1, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if n < 14+20+8+dns.SizeHeader {
|
||||
t.Error("expected response", n)
|
||||
}
|
||||
n, err = responderStack.Encapsulate(buf[:], -1, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if n != 0 {
|
||||
t.Error("expected single response")
|
||||
}
|
||||
}
|
||||
|
||||
func withQU(unicast bool) dns.Class {
|
||||
if !unicast {
|
||||
return dns.ClassINET
|
||||
}
|
||||
return dns.ClassINET | (1 << 15) // QU bit
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user