Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
105 changes: 80 additions & 25 deletions src/ConnectionManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@
#include <cstdlib>
#include <random>

#if defined(__unix__) || defined(__APPLE__)
#include <netinet/in.h>
#include <arpa/inet.h>
#elif defined(_WIN32) || defined(WIN32) || defined(_WIN64)
#include <ws2tcpip.h>
#endif

#include "ConnectionManager.h"
#include "eip/CommonPacket.h"
#include "cip/connectionManager/ForwardOpenRequest.h"
Expand All @@ -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,
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -226,41 +259,63 @@ namespace eipScanner {
}
}

void ConnectionManager::attachIoReceiveHandler(const UDPBoundSocket::SPtr& socket) {
socket->setBeginReceiveHandler([this](BaseSocket& sock) {
auto recvData = sock.Receive(8192);
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(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(items[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<UDPBoundSocket>(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 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;
}

auto newSocket = std::make_shared<UDPBoundSocket>(groupEndPoint, /*bindToGroup=*/true);
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();
}
Expand Down
2 changes: 2 additions & 0 deletions src/ConnectionManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ namespace eipScanner {
std::map<sockets::EndPoint, std::shared_ptr<sockets::UDPBoundSocket>> _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;
};
}
Expand Down
42 changes: 37 additions & 5 deletions src/sockets/UDPBoundSocket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@
// Created by Aleksey Timin on 11/21/19.
//
#include <system_error>
#include <cstring>

//#include <sys/socket.h>
//#include <netinet/in.h>
#if defined(__unix__) || defined(__APPLE__)
#include <netinet/in.h>
#include <arpa/inet.h>
#elif defined(_WIN32) || defined(WIN32) || defined(_WIN64)
#include <ws2tcpip.h>
#endif

#include "UDPBoundSocket.h"
#include "Platform.h"
Expand All @@ -17,20 +22,47 @@ 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) {
throw std::system_error(BaseSocket::getLastError(), BaseSocket::getErrorCategory());
}

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());
}
}

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));
}
}
}
}
13 changes: 12 additions & 1 deletion src/sockets/UDPBoundSocket.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,20 @@ namespace sockets {
using WPtr = std::weak_ptr<UDPBoundSocket>;
using SPtr = std::shared_ptr<UDPBoundSocket>;

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();

// 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;
};
}
}
Expand Down