From 59826b11165f231253c5d13d90ee8fb230fa8ce6 Mon Sep 17 00:00:00 2001 From: chace0219 Date: Tue, 16 Jun 2026 15:43:50 +0800 Subject: [PATCH 1/5] Build as C++17 for the Luckfox/RV1106 SDK toolchain (GCC 8.3, uClibc-ng) Upstream sets CMAKE_CXX_STANDARD 20, which the SDK cross toolchain (arm-rockchip830-linux-uclibcgnueabihf-g++ 8.3.0) cannot compile. The codebase uses no C++20-only features, so dropping to C++17 is a clean change. A bare set() in CMakeLists also overrides -DCMAKE_CXX_STANDARD from the buildroot package, so the source must carry the standard. Cross-compiled clean (lib + all examples incl. implicit_messaging) for ARM with GCC 8.3. Co-Authored-By: Claude Opus 4.8 --- CMakeLists.txt | 6 +++++- README.md | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4dda3c8f..701c3299 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,11 @@ project(EIPScanner HOMEPAGE_URL "https://github.com/nimbuscontrols/EIPScanner" ) -set(CMAKE_CXX_STANDARD 20) +# C++17: the Luckfox/RV1106 SDK cross toolchain is GCC 8.3 (no C++20). The +# codebase uses no C++20-only features, so this is a clean downgrade. +# (A bare set() here would otherwise override -DCMAKE_CXX_STANDARD from buildroot.) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) option(ENABLE_VENDOR_SRC "Enable vendor source" ON) option(TEST_ENABLED "Enable unit test" OFF) option(EXAMPLE_ENABLED "Build examples" OFF) diff --git a/README.md b/README.md index 37711a14..3e8bbc70 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,8 @@ Vendor specific objects: ## Requirements * CMake 3.5 and higher -* C++20 compiler (tested with GCC and MinGW) +* C++17 compiler (this fork: downgraded from upstream C++20 for the + Luckfox/RV1106 SDK toolchain, GCC 8.3 / uClibc-ng; no C++20-only features used) * Linux, MacOS, and Windows ## Installing From ec471f46b56d08a6275817f163f5279d046017bd Mon Sep 17 00:00:00 2001 From: chace0219 Date: Tue, 16 Jun 2026 16:17:30 +0800 Subject: [PATCH 2/5] Implicit I/O: support multicast T2O reception (Story 6.6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit EIPScanner previously only set up a unicast T2O receive socket (findOrCreateSocket bound to host:2222), ignoring T2O_SOCKADDR_INFO. So adapters that produce inputs to a multicast group were never received and the scanner fell back to / required point-to-point. This adds multicast T2O receive: - UDPBoundSocket::joinMulticastGroup() — IP_ADD_MEMBERSHIP on the bound socket; IP_DROP_MEMBERSHIP in the destructor. - ConnectionManager parses T2O_SOCKADDR_INFO from the Forward_Open response; if the advertised T2O address is multicast (224/4) it creates a group-joined receive socket, else unicast (unchanged). - Shared receive handler (attachIoReceiveHandler) demuxes by T2O connection id for both unicast and multicast sockets. Selectable per connection by the originator requesting MULTICAST vs P2P t2o network connection params. Cross-compiles C++17/GCC 8.3 for ARM. Co-Authored-By: Claude Opus 4.8 --- src/ConnectionManager.cpp | 99 +++++++++++++++++++++++++--------- src/ConnectionManager.h | 2 + src/sockets/UDPBoundSocket.cpp | 33 ++++++++++-- src/sockets/UDPBoundSocket.h | 8 +++ 4 files changed, 114 insertions(+), 28 deletions(-) diff --git a/src/ConnectionManager.cpp b/src/ConnectionManager.cpp index a4c76a20..485342f6 100644 --- a/src/ConnectionManager.cpp +++ b/src/ConnectionManager.cpp @@ -6,6 +6,13 @@ #include #include +#if defined(__unix__) || defined(__APPLE__) +#include +#include +#elif defined(_WIN32) || defined(WIN32) || defined(_WIN64) +#include +#endif + #include "ConnectionManager.h" #include "eip/CommonPacket.h" #include "cip/connectionManager/ForwardOpenRequest.h" @@ -28,6 +35,12 @@ namespace eipScanner { using sockets::UDPBoundSocket; using sockets::BaseSocket; + // IPv4 multicast range 224.0.0.0 .. 239.255.255.255 (class D). + static bool isMulticastAddr(const struct in_addr& a) { + uint32_t h = ntohl(a.s_addr); + return h >= 0xE0000000u && h <= 0xEFFFFFFFu; + } + enum class ConnectionManagerServiceCodes : cip::CipUsint { FORWARD_OPEN = 0x54, LARGE_FORWARD_OPEN = 0x5B, @@ -151,7 +164,27 @@ namespace eipScanner { Logger(LogLevel::INFO) << "Open UDP socket to send data to " << ioConnection->_socket->getRemoteEndPoint().toString(); - findOrCreateSocket(sockets::EndPoint(si->getRemoteEndPoint().getHost(), EIP_DEFAULT_IMPLICIT_PORT)); + // Set up the T2O receive socket. If the target advertised a multicast + // T2O_SOCKADDR_INFO, join that group; otherwise receive unicast on the + // implicit port (legacy point-to-point behaviour). + auto t2oSockAddrInfo = std::find_if(additionalItems.begin(), additionalItems.end(), + [](auto item) { return item.getTypeId() == eip::CommonPacketItemIds::T2O_SOCKADDR_INFO; }); + + bool t2oMulticast = false; + if (t2oSockAddrInfo != additionalItems.end()) { + Buffer t2oSockAddrBuffer(t2oSockAddrInfo->getData()); + sockets::EndPoint t2oEndPoint("", 0); + t2oSockAddrBuffer >> t2oEndPoint; + + if (isMulticastAddr(t2oEndPoint.getAddr().sin_addr)) { + findOrCreateMulticastSocket(t2oEndPoint); + t2oMulticast = true; + } + } + + if (!t2oMulticast) { + findOrCreateSocket(sockets::EndPoint(si->getRemoteEndPoint().getHost(), EIP_DEFAULT_IMPLICIT_PORT)); + } auto result = _connectionMap .insert(std::make_pair(response.getT2ONetworkConnectionId(), ioConnection)); @@ -226,41 +259,57 @@ namespace eipScanner { } } + void ConnectionManager::attachIoReceiveHandler(const UDPBoundSocket::SPtr& socket) { + socket->setBeginReceiveHandler([this](BaseSocket& sock) { + auto recvData = sock.Receive(8192); + CommonPacket commonPacket; + commonPacket.expand(recvData); + + // TODO: Check TypeIDs and sequence of the packages + Buffer buffer(commonPacket.getItems().at(0).getData()); + cip::CipUdint connectionId; + buffer >> connectionId; + Logger(LogLevel::DEBUG) << "Received data from connection T2O_ID=" << connectionId; + + auto io = _connectionMap.find(connectionId); + if (io != _connectionMap.end()) { + io->second->notifyReceiveData(commonPacket.getItems().at(1).getData()); + } else { + Logger(LogLevel::ERROR) << "Received data from unknown connection T2O_ID=" << connectionId; + } + }); + } + UDPBoundSocket::SPtr ConnectionManager::findOrCreateSocket(const sockets::EndPoint& endPoint) { auto socket = _socketMap.find(endPoint); if (socket == _socketMap.end()) { auto newSocket = std::make_shared(endPoint); _socketMap[endPoint] = newSocket; - newSocket->setBeginReceiveHandler([](sockets::BaseSocket& sock) { - (void) sock; - Logger(LogLevel::DEBUG) << "Received something"; - }); - - newSocket->setBeginReceiveHandler([this](BaseSocket& sock) { - auto recvData = sock.Receive(8192); - CommonPacket commonPacket; - commonPacket.expand(recvData); - - // TODO: Check TypeIDs and sequence of the packages - Buffer buffer(commonPacket.getItems().at(0).getData()); - cip::CipUdint connectionId; - buffer >> connectionId; - Logger(LogLevel::DEBUG) << "Received data from connection T2O_ID=" << connectionId; - - auto io = _connectionMap.find(connectionId); - if (io != _connectionMap.end()) { - io->second->notifyReceiveData(commonPacket.getItems().at(1).getData()); - } else { - Logger(LogLevel::ERROR) << "Received data from unknown connection T2O_ID=" << connectionId; - } - }); - + attachIoReceiveHandler(newSocket); return newSocket; } return socket->second; } + // Receive T2O over a multicast group: bind the producer's port (INADDR_ANY) + // and join the group the target advertised in its T2O_SOCKADDR_INFO. + UDPBoundSocket::SPtr ConnectionManager::findOrCreateMulticastSocket(const sockets::EndPoint& groupEndPoint) { + auto socket = _socketMap.find(groupEndPoint); + if (socket != _socketMap.end()) { + return socket->second; + } + + auto newSocket = std::make_shared(groupEndPoint); + newSocket->joinMulticastGroup(groupEndPoint.getAddr().sin_addr); + _socketMap[groupEndPoint] = newSocket; + attachIoReceiveHandler(newSocket); + + Logger(LogLevel::INFO) << "Joined multicast group " << groupEndPoint.toString() + << " for T2O reception"; + return newSocket; + } + bool ConnectionManager::hasOpenConnections() const { return !_connectionMap.empty(); } diff --git a/src/ConnectionManager.h b/src/ConnectionManager.h index a317067a..4ff4d3e1 100644 --- a/src/ConnectionManager.h +++ b/src/ConnectionManager.h @@ -79,6 +79,8 @@ namespace eipScanner { std::map> _socketMap; sockets::UDPBoundSocket::SPtr findOrCreateSocket(const sockets::EndPoint& endPoint); + sockets::UDPBoundSocket::SPtr findOrCreateMulticastSocket(const sockets::EndPoint& groupEndPoint); + void attachIoReceiveHandler(const sockets::UDPBoundSocket::SPtr& socket); cip::CipUint _incarnationId; }; } diff --git a/src/sockets/UDPBoundSocket.cpp b/src/sockets/UDPBoundSocket.cpp index 8b03fc91..dac5b378 100644 --- a/src/sockets/UDPBoundSocket.cpp +++ b/src/sockets/UDPBoundSocket.cpp @@ -2,9 +2,14 @@ // Created by Aleksey Timin on 11/21/19. // #include +#include -//#include -//#include +#if defined(__unix__) || defined(__APPLE__) +#include +#include +#elif defined(_WIN32) || defined(WIN32) || defined(_WIN64) +#include +#endif #include "UDPBoundSocket.h" #include "Platform.h" @@ -31,6 +36,28 @@ namespace sockets { } } - sockets::UDPBoundSocket::~UDPBoundSocket() = default; + void UDPBoundSocket::joinMulticastGroup(const struct in_addr& group) { + struct ip_mreq mreq; + std::memset(&mreq, 0, sizeof(mreq)); + mreq.imr_multiaddr = group; + mreq.imr_interface.s_addr = htonl(INADDR_ANY); + if (setsockopt(_sockedFd, IPPROTO_IP, IP_ADD_MEMBERSHIP, + (char *) &mreq, sizeof(mreq)) < 0) { + throw std::system_error(BaseSocket::getLastError(), BaseSocket::getErrorCategory()); + } + _multicastGroup = group; + _joinedMulticast = true; + } + + sockets::UDPBoundSocket::~UDPBoundSocket() { + if (_joinedMulticast) { + struct ip_mreq mreq; + std::memset(&mreq, 0, sizeof(mreq)); + mreq.imr_multiaddr = _multicastGroup; + mreq.imr_interface.s_addr = htonl(INADDR_ANY); + setsockopt(_sockedFd, IPPROTO_IP, IP_DROP_MEMBERSHIP, + (char *) &mreq, sizeof(mreq)); + } + } } } diff --git a/src/sockets/UDPBoundSocket.h b/src/sockets/UDPBoundSocket.h index 3d9c3f9f..419d04a2 100644 --- a/src/sockets/UDPBoundSocket.h +++ b/src/sockets/UDPBoundSocket.h @@ -22,6 +22,14 @@ namespace sockets { explicit UDPBoundSocket(EndPoint endPoint); UDPBoundSocket(std::string host, int port); virtual ~UDPBoundSocket(); + + // Join an IPv4 multicast group on this bound socket so a target's + // T2O multicast producer is received. Dropped on destruction. + void joinMulticastGroup(const struct in_addr& group); + + private: + struct in_addr _multicastGroup{}; + bool _joinedMulticast = false; }; } } From e72fa214940b4e80aa7e65d01dc65e1936f727bf Mon Sep 17 00:00:00 2001 From: chace0219 Date: Tue, 16 Jun 2026 16:23:29 +0800 Subject: [PATCH 3/5] Multicast T2O: bind the receive socket to the group address Binding the multicast receive socket to INADDR_ANY:2222 alongside the unicast receive socket on the same port made unicast-vs-multicast delivery ambiguous in mixed deployments. Add a bindToGroup option to UDPBoundSocket and bind the multicast socket to the group address so it only receives that group's datagrams; unicast still binds INADDR_ANY. Co-Authored-By: Claude Opus 4.8 --- src/ConnectionManager.cpp | 2 +- src/sockets/UDPBoundSocket.cpp | 9 +++++++-- src/sockets/UDPBoundSocket.h | 5 ++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/ConnectionManager.cpp b/src/ConnectionManager.cpp index 485342f6..f7ba0c59 100644 --- a/src/ConnectionManager.cpp +++ b/src/ConnectionManager.cpp @@ -300,7 +300,7 @@ namespace eipScanner { return socket->second; } - auto newSocket = std::make_shared(groupEndPoint); + auto newSocket = std::make_shared(groupEndPoint, /*bindToGroup=*/true); newSocket->joinMulticastGroup(groupEndPoint.getAddr().sin_addr); _socketMap[groupEndPoint] = newSocket; attachIoReceiveHandler(newSocket); diff --git a/src/sockets/UDPBoundSocket.cpp b/src/sockets/UDPBoundSocket.cpp index dac5b378..65581bc2 100644 --- a/src/sockets/UDPBoundSocket.cpp +++ b/src/sockets/UDPBoundSocket.cpp @@ -22,7 +22,7 @@ namespace sockets { : UDPBoundSocket(EndPoint(host, port)) { } - UDPBoundSocket::UDPBoundSocket(EndPoint endPoint) + UDPBoundSocket::UDPBoundSocket(EndPoint endPoint, bool bindToGroup) : UDPSocket(std::move(endPoint)) { int on = 1; if (setsockopt(_sockedFd, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on)) < 0) { @@ -30,7 +30,12 @@ namespace sockets { } auto addr = _remoteEndPoint.getAddr(); - addr.sin_addr.s_addr = INADDR_ANY; + // Unicast: bind the port on any local address. Multicast: bind to the + // group address so the socket only receives that group (and does not + // steal unicast datagrams on the same port). + if (!bindToGroup) { + addr.sin_addr.s_addr = INADDR_ANY; + } if (bind(_sockedFd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { throw std::system_error(BaseSocket::getLastError(), BaseSocket::getErrorCategory()); } diff --git a/src/sockets/UDPBoundSocket.h b/src/sockets/UDPBoundSocket.h index 419d04a2..ebd24eb4 100644 --- a/src/sockets/UDPBoundSocket.h +++ b/src/sockets/UDPBoundSocket.h @@ -19,7 +19,10 @@ namespace sockets { using WPtr = std::weak_ptr; using SPtr = std::shared_ptr; - explicit UDPBoundSocket(EndPoint endPoint); + // bindToGroup=true binds to endPoint's address (a multicast group) so + // the socket only receives that group's datagrams; false binds + // INADDR_ANY (the legacy unicast-receive behaviour). + explicit UDPBoundSocket(EndPoint endPoint, bool bindToGroup = false); UDPBoundSocket(std::string host, int port); virtual ~UDPBoundSocket(); From 11046a5c6ed4201e03cdf80bc31d5dfef74a741d Mon Sep 17 00:00:00 2001 From: Chace0219 Date: Tue, 16 Jun 2026 17:45:55 +0800 Subject: [PATCH 4/5] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/ConnectionManager.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/ConnectionManager.cpp b/src/ConnectionManager.cpp index f7ba0c59..79bf9fb0 100644 --- a/src/ConnectionManager.cpp +++ b/src/ConnectionManager.cpp @@ -265,15 +265,21 @@ namespace eipScanner { CommonPacket commonPacket; commonPacket.expand(recvData); + const auto& items = commonPacket.getItems(); + if (items.size() < 2) { + Logger(LogLevel::WARNING) << "Received malformed I/O CommonPacket: expected >=2 items, got " << items.size(); + return; + } + // TODO: Check TypeIDs and sequence of the packages - Buffer buffer(commonPacket.getItems().at(0).getData()); + Buffer buffer(items[0].getData()); cip::CipUdint connectionId; buffer >> connectionId; Logger(LogLevel::DEBUG) << "Received data from connection T2O_ID=" << connectionId; auto io = _connectionMap.find(connectionId); if (io != _connectionMap.end()) { - io->second->notifyReceiveData(commonPacket.getItems().at(1).getData()); + io->second->notifyReceiveData(items[1].getData()); } else { Logger(LogLevel::ERROR) << "Received data from unknown connection T2O_ID=" << connectionId; } From c1c38b9f8636c2f3b6d97cf0806f62a8b6a4264b Mon Sep 17 00:00:00 2001 From: Chace0219 Date: Tue, 16 Jun 2026 17:47:18 +0800 Subject: [PATCH 5/5] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/ConnectionManager.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ConnectionManager.cpp b/src/ConnectionManager.cpp index 79bf9fb0..36574be8 100644 --- a/src/ConnectionManager.cpp +++ b/src/ConnectionManager.cpp @@ -298,9 +298,9 @@ namespace eipScanner { return socket->second; } - // Receive T2O over a multicast group: bind the producer's port (INADDR_ANY) - // and join the group the target advertised in its T2O_SOCKADDR_INFO. - UDPBoundSocket::SPtr ConnectionManager::findOrCreateMulticastSocket(const sockets::EndPoint& groupEndPoint) { + // Receive T2O over a multicast group: bind to the group address/port + // (to avoid capturing unicast datagrams on the same port) and join the group + // the target advertised in its T2O_SOCKADDR_INFO. auto socket = _socketMap.find(groupEndPoint); if (socket != _socketMap.end()) { return socket->second;