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:
co-authored by
Claude Sonnet 4.6
parent
61a2aa3988
commit
14d5351a81
@@ -26,11 +26,14 @@
|
||||
/* Standard header includes */
|
||||
/*---------------------------------------------------------------------------*/
|
||||
#include <string.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/* Project header includes */
|
||||
/*---------------------------------------------------------------------------*/
|
||||
#include "AdvancedErrorManagement.h"
|
||||
#include "BasicTCPSocket.h"
|
||||
#include "BasicUDPSocket.h"
|
||||
#include "ConfigurationDatabase.h"
|
||||
#include "GAM.h"
|
||||
@@ -1491,3 +1494,183 @@ bool UDPStreamerClientTest::TestExecute_ConnectConfigDataEndToEnd() {
|
||||
ObjectRegistryDatabase::Instance()->Purge();
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool UDPStreamerClientTest::TestExecute_MulticastReceivesDataOnInterface() {
|
||||
using namespace MARTe;
|
||||
|
||||
static const uint16 controlPort = 44730u;
|
||||
static const uint16 dataPort = 44731u;
|
||||
static const char8 *const mcGroup = "239.0.0.7";
|
||||
static const char8 *const mcIface = "127.0.0.1";
|
||||
|
||||
static const char8 *const cfg =
|
||||
"+Test = {\n"
|
||||
" Class = RealTimeApplication\n"
|
||||
" +Functions = {\n"
|
||||
" Class = ReferenceContainer\n"
|
||||
" +Reader = {\n"
|
||||
" Class = UDPStreamerClientTestGAM\n"
|
||||
" InputSignals = {\n"
|
||||
" Counter = { DataSource = ClientDS Type = uint32 }\n"
|
||||
" }\n"
|
||||
" OutputSignals = {\n"
|
||||
" Counter = { DataSource = DDB Type = uint32 }\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" +Data = {\n"
|
||||
" Class = ReferenceContainer\n"
|
||||
" DefaultDataSource = DDB\n"
|
||||
" +DDB = { Class = GAMDataSource }\n"
|
||||
" +ClientDS = {\n"
|
||||
" Class = UDPStreamerClient\n"
|
||||
" ServerAddress = \"127.0.0.1\"\n"
|
||||
" Port = 44730\n"
|
||||
" MulticastGroup = \"239.0.0.7\"\n"
|
||||
" DataPort = 44731\n"
|
||||
" Interface = \"127.0.0.1\"\n"
|
||||
" MaxPayloadSize = 1400\n"
|
||||
" Signals = {\n"
|
||||
" Counter = { Type = uint32 }\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" +Timings = { Class = TimingDataSource }\n"
|
||||
" }\n"
|
||||
" +States = {\n"
|
||||
" Class = ReferenceContainer\n"
|
||||
" +State1 = {\n"
|
||||
" Class = RealTimeState\n"
|
||||
" +Threads = {\n"
|
||||
" Class = ReferenceContainer\n"
|
||||
" +Thread1 = {\n"
|
||||
" Class = RealTimeThread\n"
|
||||
" Functions = { Reader }\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" +Scheduler = {\n"
|
||||
" Class = GAMScheduler\n"
|
||||
" TimingDataSource = Timings\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
|
||||
ReferenceT<RealTimeApplication> app = LoadApplication(cfg);
|
||||
bool ok = app.IsValid();
|
||||
|
||||
/* Open a TCP listener for the control port BEFORE PrepareNextState so we
|
||||
* never miss the client's CONNECT. */
|
||||
BasicTCPSocket tcpListener;
|
||||
if (ok) {
|
||||
ok = tcpListener.Open() && tcpListener.Listen(controlPort, 5);
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
ok = (app->PrepareNextState("State1") == ErrorManagement::NoError);
|
||||
}
|
||||
|
||||
/* Open the multicast data socket aimed at the group, with IP_MULTICAST_IF
|
||||
* set to 127.0.0.1 so the datagram leaves on loopback — exactly what
|
||||
* UDPSServer does. */
|
||||
BasicUDPSocket dataSocket;
|
||||
if (ok) {
|
||||
ok = dataSocket.Open();
|
||||
}
|
||||
if (ok) {
|
||||
struct in_addr localIf;
|
||||
localIf.s_addr = inet_addr(mcIface);
|
||||
int fd = static_cast<int>(dataSocket.GetWriteHandle());
|
||||
ok = (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF,
|
||||
&localIf, static_cast<socklen_t>(sizeof(localIf))) == 0);
|
||||
}
|
||||
if (ok) {
|
||||
ok = dataSocket.Connect(mcGroup, dataPort);
|
||||
}
|
||||
|
||||
/* Accept the client's TCP CONNECT. */
|
||||
BasicTCPSocket *clientConn = NULL_PTR(BasicTCPSocket *);
|
||||
if (ok) {
|
||||
clientConn = tcpListener.WaitConnection(TimeoutType(2000u));
|
||||
ok = (clientConn != NULL_PTR(BasicTCPSocket *));
|
||||
}
|
||||
|
||||
/* Read the CONNECT packet from the accepted TCP connection. */
|
||||
if (ok) {
|
||||
uint8 recvBuf[64u];
|
||||
uint32 recvSize = UDPS_HEADER_SIZE;
|
||||
ok = clientConn->Read(reinterpret_cast<char8 *>(recvBuf), recvSize);
|
||||
if (ok) {
|
||||
const UDPSPacketHeader *hdr = reinterpret_cast<const UDPSPacketHeader *>(recvBuf);
|
||||
ok = (hdr->magic == UDPS_MAGIC) && (hdr->type == UDPS_TYPE_CONNECT);
|
||||
}
|
||||
}
|
||||
|
||||
/* Send CONFIG: one scalar "Counter" uint32 signal, unquantised. */
|
||||
if (ok) {
|
||||
UDPSSignalDescriptor desc;
|
||||
(void) memset(&desc, 0, sizeof(desc));
|
||||
(void) strncpy(desc.name, "Counter", UDPS_MAX_SIGNAL_NAME - 1u);
|
||||
desc.typeCode = UDPS_TYPECODE_UINT32;
|
||||
desc.numRows = 1u;
|
||||
desc.numCols = 1u;
|
||||
|
||||
uint8 buf[UDPS_HEADER_SIZE + 4u + UDPS_SIGNAL_DESC_SIZE + 1u];
|
||||
const uint32 configPayloadBytes = 4u + UDPS_SIGNAL_DESC_SIZE + 1u;
|
||||
UDPSBuildHeader(buf, UDPS_TYPE_CONFIG, 1u, 0u, 1u, configPayloadBytes);
|
||||
uint32 numSigs = 1u;
|
||||
(void) memcpy(buf + UDPS_HEADER_SIZE, &numSigs, 4u);
|
||||
(void) memcpy(buf + UDPS_HEADER_SIZE + 4u, &desc, UDPS_SIGNAL_DESC_SIZE);
|
||||
buf[UDPS_HEADER_SIZE + 4u + UDPS_SIGNAL_DESC_SIZE] = UDPS_PUBLISH_STRICT;
|
||||
|
||||
uint32 sendSize = static_cast<uint32>(sizeof(buf));
|
||||
ok = clientConn->Write(reinterpret_cast<const char8 *>(buf), sendSize);
|
||||
}
|
||||
|
||||
Sleep::MSec(100u);
|
||||
|
||||
ReferenceT<UDPStreamerClient> ds;
|
||||
if (ok) {
|
||||
ds = ObjectRegistryDatabase::Instance()->Find("Test.Data.ClientDS");
|
||||
ok = ds.IsValid();
|
||||
}
|
||||
|
||||
/* Send DATA over UDP multicast. Each attempt uses a fresh packet counter
|
||||
* so UDPSClient's reassembly layer does not drop retransmissions. */
|
||||
bool gotValue = false;
|
||||
for (uint32 attempt = 0u; ok && (!gotValue) && (attempt < 30u); attempt++) {
|
||||
SynchroniseThreadArgs *syncArgs = StartSynchroniseThread(ds);
|
||||
|
||||
uint8 buf[UDPS_HEADER_SIZE + 8u + 4u];
|
||||
const uint32 dataPayloadBytes = 8u + 4u;
|
||||
UDPSBuildHeader(buf, UDPS_TYPE_DATA, 2u + attempt, 0u, 1u, dataPayloadBytes);
|
||||
(void) memset(buf + UDPS_HEADER_SIZE, 0, 8u);
|
||||
uint32 value = 424242u;
|
||||
(void) memcpy(buf + UDPS_HEADER_SIZE + 8u, &value, 4u);
|
||||
|
||||
uint32 sendSize = static_cast<uint32>(sizeof(buf));
|
||||
ok = dataSocket.Write(reinterpret_cast<const char8 *>(buf), sendSize);
|
||||
|
||||
if (ok && JoinSynchroniseThread(syncArgs)) {
|
||||
void *sigMem = NULL_PTR(void *);
|
||||
if (ds->GetSignalMemoryBuffer(0u, 0u, sigMem)) {
|
||||
uint32 decoded = 0u;
|
||||
(void) memcpy(&decoded, sigMem, sizeof(uint32));
|
||||
gotValue = (decoded == 424242u);
|
||||
}
|
||||
}
|
||||
else if (!ok) {
|
||||
(void) JoinSynchroniseThread(syncArgs);
|
||||
}
|
||||
}
|
||||
ok = ok && gotValue;
|
||||
|
||||
if (clientConn != NULL_PTR(BasicTCPSocket *)) {
|
||||
(void) clientConn->Close();
|
||||
delete clientConn;
|
||||
}
|
||||
(void) tcpListener.Close();
|
||||
(void) dataSocket.Close();
|
||||
Sleep::MSec(50u);
|
||||
ObjectRegistryDatabase::Instance()->Purge();
|
||||
return ok;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user