Fix nRF52 AsyncUDP multicast TX/RX race condition causing garbled packets (#9765)

socketSendUDP() calls yield() while waiting for SEND_OK, allowing the
cooperative scheduler to run AsyncUDP::runOnce(). When runOnce() calls
parsePacket() on the same socket during an active send, the multicast
loopback packet arriving from the switch produces a garbled 8-byte RX
header (wrong source IP, wrong payload length), leading to protobuf
decode errors in UdpMulticastHandler.

Add a volatile isSending flag that writeTo() sets around endPacket()
(the only yield point). runOnce() checks this flag and skips
parsePacket() while a send is in progress. Loopback packets stay
buffered in the W5100S RX buffer and are read cleanly on the next
poll cycle.

Also propagate writeTo() return value in UdpMulticastHandler::onSend()
instead of unconditionally returning true.

Made-with: Cursor

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
This commit is contained in:
Philip Lykov
2026-03-06 08:38:50 -06:00
committed by GitHub
co-authored by GitHub Ben Meadors
parent 5b1ea922f3
commit 86dad90573
3 changed files with 7 additions and 4 deletions
+1 -2
View File
@@ -105,8 +105,7 @@ class UdpMulticastHandler final
LOG_DEBUG("Broadcasting packet over UDP (id=%u)", mp->id);
uint8_t buffer[meshtastic_MeshPacket_size];
size_t encodedLength = pb_encode_to_bytes(buffer, sizeof(buffer), &meshtastic_MeshPacket_msg, mp);
udp.writeTo(buffer, encodedLength, udpIpAddress, UDP_MULTICAST_DEFAUL_PORT);
return true;
return udp.writeTo(buffer, encodedLength, udpIpAddress, UDP_MULTICAST_DEFAUL_PORT);
}
private:
+5 -2
View File
@@ -33,7 +33,10 @@ bool AsyncUDP::writeTo(const uint8_t *data, size_t len, IPAddress ip, uint16_t p
if (!udp.beginPacket(ip, port))
return false;
udp.write(data, len);
return udp.endPacket();
isSending = true;
bool ok = udp.endPacket();
isSending = false;
return ok;
}
void AsyncUDP::close()
@@ -70,7 +73,7 @@ const uint8_t *AsyncUDPPacket::data()
int32_t AsyncUDP::runOnce()
{
if (_onPacket && udp.parsePacket() > 0) {
if (_onPacket && !isSending && udp.parsePacket() > 0) {
AsyncUDPPacket packet(udp);
_onPacket(packet);
}
+1
View File
@@ -31,6 +31,7 @@ class AsyncUDP : public Print, private concurrency::OSThread
private:
EthernetUDP udp;
uint16_t localPort;
volatile bool isSending = false;
std::function<void(AsyncUDPPacket)> _onPacket;
virtual int32_t runOnce() override;
};