fix: UDPSClient uses two-arg Join so multicast receiver lands on the right interface

UDPSClient::ConnectMulticast was calling the single-arg BasicUDPSocket::Join,
which forwards NULL as the local interface and lets the kernel bind to
INADDR_ANY.  On a multi-homed host (or when the server sends on loopback via
Interface = "127.0.0.1") the client joins the wrong interface and silently
receives nothing.

Fix: read the optional Interface key inside the useMulticast block in
UDPSClient::Initialise; in ConnectMulticast call the two-arg
Join(group, interface) when Interface is set, and fall back to the one-arg
call otherwise to preserve the existing INADDR_ANY behaviour for configs that
omit it.

Forward the new optional Interface key through UDPStreamerClient (read from
DataSource config, written into the UDPSClient ConfigurationDatabase only
when non-empty).  Extend the "Joined multicast group" log to report the
interface name or "default".

Regression test TestExecute_MulticastReceivesDataOnInterface: mock TCP
control listener + multicast UDP DATA socket with IP_MULTICAST_IF set to
127.0.0.1, verifying a uint32 value of 424242 reaches DataSource signal
memory.  Confirmed FAILED without the Join fix and PASSED with it.

Suite: 133/133 (was 132/132 before this commit).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Martino Ferrari
2026-08-17 08:23:41 +02:00
co-authored by Claude Sonnet 4.6
parent 61a2aa3988
commit 14d5351a81
8 changed files with 270 additions and 9 deletions
@@ -244,10 +244,14 @@ bool UDPStreamerClient::Initialise(StructuredDataI &data) {
dp = port + UDPS_CLIENT_DEFAULT_DP_OFFSET;
}
dataPort = dp;
StreamString ifaceStr = "";
(void) data.Read("Interface", ifaceStr);
multicastInterface = ifaceStr;
REPORT_ERROR(ErrorManagement::Information,
"Multicast mode: group=%s, server=%s, controlPort=%u, dataPort=%u.",
"Multicast mode: group=%s, server=%s, controlPort=%u, dataPort=%u, interface=%s.",
multicastGroup.Buffer(), serverAddress.Buffer(),
static_cast<uint32>(port), static_cast<uint32>(dataPort));
static_cast<uint32>(port), static_cast<uint32>(dataPort),
(multicastInterface.Size() > 0u) ? multicastInterface.Buffer() : "default");
}
else {
useMulticast = false;
@@ -263,6 +267,9 @@ bool UDPStreamerClient::Initialise(StructuredDataI &data) {
if (ok && useMulticast) {
ok = cdb.Write("MulticastGroup", multicastGroup);
if (ok) { ok = cdb.Write("DataPort", static_cast<uint32>(dataPort)); }
if (ok && (multicastInterface.Size() > 0u)) {
ok = cdb.Write("Interface", multicastInterface);
}
}
if (ok) { ok = cdb.Write("MaxPayloadSize", maxPayloadSize); }
if (ok) { ok = cdb.Write("KeepAliveInterval", keepAliveInterval); }
@@ -178,9 +178,10 @@ private:
float32 silenceTimeout; /**< Seconds of no data before reconnect (sub-second allowed, 0 disables). */
uint32 cpuMask; /**< Background thread CPU affinity. */
uint32 stackSize; /**< Background thread stack size. */
StreamString multicastGroup; /**< Multicast group IP; empty = unicast. */
uint16 dataPort; /**< UDP port for DATA datagrams (multicast). */
bool useMulticast; /**< True when MulticastGroup is set. */
StreamString multicastGroup; /**< Multicast group IP; empty = unicast. */
StreamString multicastInterface; /**< Local IPv4 address for multicast join; empty = INADDR_ANY. */
uint16 dataPort; /**< UDP port for DATA datagrams (multicast). */
bool useMulticast; /**< True when MulticastGroup is set. */
/* Signal metadata */
uint32 numSigs; /**< Number of signals. */