Files
pip/libs/main/io_devices/piethernet.cpp
peri4 1c7fc39b6c version 4.0.0_alpha
in almost all methods removed timeouts in milliseconds, replaced to PISystemTime
PITimer rewrite, remove internal impl, now only thread implementation, API similar to PIThread
PITimer API no longer pass void*
PIPeer, PIConnection improved stability on reinit and exit
PISystemTime new methods
pisd now exit without hanging
2024-07-30 14:18:02 +03:00

1425 lines
37 KiB
C++

/*
PIP - Platform Independent Primitives
Ethernet, UDP/TCP Broadcast/Multicast
Ivan Pelipenko peri4ko@yandex.ru, Andrey Bychkov work.a.b@yandex.ru
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "piethernet.h"
#include "piconfig.h"
#include "piconstchars.h"
#include "piincludes_p.h"
#include "piliterals.h"
#include "pipropertystorage.h"
#include "pisysteminfo.h"
// clang-format off
#ifdef QNX
# include <arpa/inet.h>
# include <fcntl.h>
# include <hw/nicinfo.h>
# include <ifaddrs.h>
# include <net/if.h>
# include <net/if_dl.h>
# include <netdb.h>
# include <netinet/in.h>
# include <sys/ioctl.h>
# include <sys/socket.h>
# include <sys/time.h>
# include <sys/types.h>
# ifdef BLACKBERRY
# include <netinet/in.h>
# else
# include <sys/dcmd_io-net.h>
# endif
# define ip_mreqn ip_mreq
# define imr_address imr_interface
#else
# ifdef WINDOWS
# include <io.h>
# include <winsock2.h>
# include <iphlpapi.h>
# include <psapi.h>
# include <ws2tcpip.h>
# define ip_mreqn ip_mreq
# define imr_address imr_interface
# else
# include <fcntl.h>
# include <sys/ioctl.h>
# include <netinet/in.h>
# include <netinet/tcp.h>
# include <arpa/inet.h>
# include <sys/socket.h>
# include <netdb.h>
# include <net/if.h>
# if !defined(ANDROID) && !defined(LWIP)
# include <ifaddrs.h>
# endif
# ifdef LWIP
# include <lwip/sockets.h>
# endif
# endif
#endif
// clang-format on
#include "piwaitevent_p.h"
#include <errno.h>
/** \class PIEthernet piethernet.h
* \brief
* Ethernet device
*
* \details
* \section PIEthernet_sec0 Synopsis
* %PIEthernet designed to work with IPv4 network via two protocols:
* UDP and TCP. This class allow you send and receive packets to/from
* another computer through network. Also it supports broadcast and
* multicast extensions.
*
* \section PIEthernet_sec1 IPv4
*
*
* \section PIEthernet_sec2 UDP
* User Datagram Protocol
*
* \section PIEthernet_sec3 TCP
* Transmission Control Protocol
*
* */
#ifndef WINDOWS
PIString getSockAddr(sockaddr * s) {
return s == 0 ? PIString() : PIStringAscii(inet_ntoa(((sockaddr_in *)s)->sin_addr));
}
#endif
REGISTER_DEVICE(PIEthernet)
PRIVATE_DEFINITION_START(PIEthernet)
sockaddr_in addr_;
sockaddr_in saddr_;
sockaddr_in raddr_;
PIWaitEvent event;
PRIVATE_DEFINITION_END(PIEthernet)
PIEthernet::PIEthernet(): PIIODevice("", ReadWrite) {
construct();
setType(UDP);
setParameters(PIEthernet::ReuseAddress | PIEthernet::MulticastLoop | PIEthernet::KeepConnection);
}
PIEthernet::PIEthernet(PIEthernet::Type type_, const PIString & ip_port, const PIFlags<PIEthernet::Parameters> params_)
: PIIODevice(ip_port, ReadWrite) {
construct();
addr_r.set(ip_port);
setType(type_);
setParameters(params_);
if (type_ != UDP) init();
}
PIEthernet::PIEthernet(int sock_, PIString ip_port): PIIODevice("", ReadWrite) {
construct();
addr_s.set(ip_port);
sock = sock_;
opened_ = connected_ = true;
init();
setParameters(PIEthernet::ReuseAddress | PIEthernet::MulticastLoop);
setType(TCP_Client, false);
setPath(ip_port);
ethNonblocking(sock);
PRIVATE->event.create();
// piCoutObj << "new tcp client" << sock_;
}
PIEthernet::~PIEthernet() {
// piCout << "~PIEthernet";
stopAndWait();
close();
PRIVATE->event.destroy();
// piCout << "~PIEthernet done";
}
void PIEthernet::construct() {
// piCout << " PIEthernet" << uint(this);
setOption(BlockingWrite);
connected_ = connecting_ = listen_threaded = server_bounded = false;
sock = sock_s = -1;
setReadTimeout(10_s);
setWriteTimeout(10_s);
setTTL(64);
setMulticastTTL(1);
server_thread_.setData(this);
server_thread_.setName("__S__server_thread"_a);
#ifdef MICRO_PIP
setThreadedReadBufferSize(512);
#else
setThreadedReadBufferSize(64_KiB);
#endif
// setPriority(piHigh);
}
bool PIEthernet::init() {
if (isOpened()) return true;
if (sock != -1) return true;
// piCout << "init " << type();
PRIVATE->event.destroy();
if (sock_s == sock) sock_s = -1;
closeSocket(sock);
closeSocket(sock_s);
int st = 0, pr = 0;
if (type() == UDP) {
st = SOCK_DGRAM;
pr = IPPROTO_UDP;
} else {
st = SOCK_STREAM;
pr = IPPROTO_TCP;
}
sock = ::socket(AF_INET, st, pr);
ethNonblocking(sock);
PRIVATE->event.create();
if (params[SeparateSockets])
sock_s = ::socket(AF_INET, st, pr);
else
sock_s = sock;
if (sock == -1 || sock_s == -1) {
piCoutObj << "Can`t create socket," << ethErrorString();
return false;
}
applyParameters();
applyTimeouts();
applyOptInt(IPPROTO_IP, IP_TTL, TTL());
// piCoutObj << "inited" << path();
return true;
}
PIString PIEthernet::macFromBytes(const PIByteArray & mac) {
PIString r;
for (int i = 0; i < mac.size_s(); ++i) {
r += PIString::fromNumber(mac[i], 16).expandLeftTo(2, '0');
if (i < mac.size_s() - 1) r += ":";
}
return r;
}
PIByteArray PIEthernet::macToBytes(const PIString & mac) {
PIByteArray r;
PIStringList sl = mac.split(PIStringAscii(":"));
piForeachC(PIString & i, sl)
r << uchar(i.toInt(16));
return r;
}
PIString PIEthernet::applyMask(const PIString & ip, const PIString & mask) {
struct in_addr ia;
ia.s_addr = inet_addr(ip.dataAscii()) & inet_addr(mask.dataAscii());
return PIStringAscii(inet_ntoa(ia));
}
PINetworkAddress PIEthernet::applyMask(const PINetworkAddress & ip, const PINetworkAddress & mask) {
PINetworkAddress ret(ip);
ret.ip_ &= mask.ip_;
return ret;
}
PIString PIEthernet::getBroadcast(const PIString & ip, const PIString & mask) {
struct in_addr ia;
ia.s_addr = inet_addr(ip.dataAscii()) | ~inet_addr(mask.dataAscii());
return PIStringAscii(inet_ntoa(ia));
}
PINetworkAddress PIEthernet::getBroadcast(const PINetworkAddress & ip, const PINetworkAddress & mask) {
PINetworkAddress ret(ip);
ret.ip_ |= ~mask.ip_;
return ret;
}
bool PIEthernet::openDevice() {
if (connected_) return true;
// piCoutObj << "open";
init();
if (sock == -1 || path().isEmpty()) return false;
addr_r.set(path());
// if (type() == TCP_Client)
// connecting_ = true;
if (type() != UDP || mode() == PIIODevice::WriteOnly) return true;
memset(&PRIVATE->addr_, 0, sizeof(PRIVATE->addr_));
PRIVATE->addr_.sin_family = AF_INET;
PRIVATE->addr_.sin_port = htons(addr_r.port());
if (params[PIEthernet::Broadcast])
PRIVATE->addr_.sin_addr.s_addr = INADDR_ANY;
else
PRIVATE->addr_.sin_addr.s_addr = addr_r.ip();
#ifdef QNX
PRIVATE->addr_.sin_len = sizeof(PRIVATE->addr_);
#endif
// piCout << "bind to" << (params[PIEthernet::Broadcast] ? "255.255.255.255" : ip_) << ":" << port_ << " ...";
int tries = 0;
while ((bind(sock, (sockaddr *)&PRIVATE->addr_, sizeof(PRIVATE->addr_)) == -1) && (tries < 2)) {
init();
tries++;
}
if (tries == 2) {
piCoutObj << "Can`t bind to" << addr_r << "," << ethErrorString();
return false;
}
opened_ = true;
while (!mcast_queue.isEmpty())
joinMulticastGroup(mcast_queue.dequeue());
applyTimeouts();
applyOptInt(IPPROTO_IP, IP_TTL, TTL());
addr_lr.clear();
return true;
}
bool PIEthernet::closeDevice() {
// piCoutObj << "close";
bool ned = connected_;
connected_ = connecting_ = false;
server_thread_.stop();
PRIVATE->event.interrupt();
if (server_thread_.isRunning()) {
if (!server_thread_.waitForFinish(1_s)) server_thread_.terminate();
}
PRIVATE->event.destroy();
if (sock_s == sock) sock_s = -1;
closeSocket(sock);
closeSocket(sock_s);
clients_mutex.lock();
piDeleteAllAndClear(clients_);
clients_mutex.unlock();
if (ned) {
// piCoutObj << "Disconnect on close";
disconnected(false);
}
return true;
}
void PIEthernet::closeSocket(int & sd) {
if (sd != -1) ethClosesocket(sd, type() != PIEthernet::UDP);
sd = -1;
}
void PIEthernet::applyTimeouts() {
if (sock < 0) return;
PISystemTime rtm = readTimeout(), wtm = writeTimeout();
applyTimeout(sock, SO_RCVTIMEO, rtm);
applyTimeout(sock, SO_SNDTIMEO, wtm);
if (sock_s != sock && sock_s != -1) {
applyTimeout(sock_s, SO_RCVTIMEO, rtm);
applyTimeout(sock_s, SO_SNDTIMEO, wtm);
}
}
void PIEthernet::applyTimeout(int fd, int opt, PISystemTime tm) {
if (fd == 0) return;
// piCoutObj << "setReadIsBlocking" << yes;
#ifdef WINDOWS
DWORD _tm = tm.toMilliseconds();
#else
timeval _tm;
_tm.tv_sec = tm.seconds;
_tm.tv_usec = tm.nanoseconds / 1000;
#endif
ethSetsockopt(fd, SOL_SOCKET, opt, &_tm, sizeof(_tm));
}
void PIEthernet::applyOptInt(int level, int opt, int val) {
if (sock != -1) ethSetsockoptInt(sock, level, opt, val);
if (sock_s != sock && sock_s != -1) ethSetsockoptInt(sock_s, level, opt, val);
}
bool PIEthernet::joinMulticastGroup(const PIString & group) {
if (sock == -1) init();
if (sock == -1) return false;
if (type() != UDP) {
piCoutObj << "Only UDP sockets can join multicast groups";
return false;
}
if (isClosed()) {
if (mcast_queue.contains(group)) return false;
mcast_queue.enqueue(group);
if (!mcast_groups.contains(group)) mcast_groups << group;
return true;
}
addr_r.set(path());
#ifndef LWIP
struct ip_mreqn mreq;
#else
struct ip_mreq mreq;
#endif
memset(&mreq, 0, sizeof(mreq));
#ifdef LINUX
// mreq.imr_address.s_addr = INADDR_ANY;
/*PIEthernet::InterfaceList il = interfaces();
const PIEthernet::Interface * ci = il.getByAddress(addr_r.ipString());
if (ci != 0) mreq.imr_ifindex = ci->index;*/
#endif
if (params[PIEthernet::Broadcast])
#ifndef LWIP
mreq.imr_address.s_addr = INADDR_ANY;
#else
mreq.imr_interface.s_addr = INADDR_ANY;
#endif
else
#ifndef LWIP
mreq.imr_address.s_addr = addr_r.ip();
#else
mreq.imr_interface.s_addr = addr_r.ip();
#endif
// piCout << "join group" << group << "ip" << ip_ << "with index" << mreq.imr_ifindex << "socket" << sock;
mreq.imr_multiaddr.s_addr = inet_addr(group.dataAscii());
if (ethSetsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) != 0) {
piCoutObj << "Can`t join multicast group" << group << "," << ethErrorString();
return false;
}
if (params[PIEthernet::MulticastLoop]) ethSetsockoptInt(sock, IPPROTO_IP, IP_MULTICAST_LOOP);
applyOptInt(IPPROTO_IP, IP_MULTICAST_TTL, multicastTTL());
if (!mcast_groups.contains(group)) mcast_groups << group;
return true;
}
bool PIEthernet::leaveMulticastGroup(const PIString & group) {
if (sock == -1) init();
if (sock == -1) return false;
if (type() != UDP) {
piCoutObj << "Only UDP sockets can leave multicast groups";
return false;
}
addr_r.set(path());
#ifndef LWIP
struct ip_mreqn mreq;
#else
struct ip_mreq mreq;
#endif
memset(&mreq, 0, sizeof(mreq));
if (params[PIEthernet::Broadcast])
#ifndef LWIP
mreq.imr_address.s_addr = INADDR_ANY;
#else
mreq.imr_interface.s_addr = INADDR_ANY;
#endif
else
#ifndef LWIP
mreq.imr_address.s_addr = addr_r.ip();
#else
mreq.imr_interface.s_addr = addr_r.ip();
#endif
mreq.imr_multiaddr.s_addr = inet_addr(group.dataAscii());
if (ethSetsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) == -1) {
piCoutObj << "Can`t leave multicast group" << group << "," << ethErrorString();
return false;
}
mcast_groups.removeAll(group);
return true;
}
bool PIEthernet::connect(bool threaded) {
if (threaded) {
connecting_ = true;
return true;
}
if (connected_) return false;
if (sock == -1) init();
if (sock == -1) return false;
memset(&PRIVATE->addr_, 0, sizeof(PRIVATE->addr_));
addr_r.set(path());
PRIVATE->addr_.sin_port = htons(addr_r.port());
PRIVATE->addr_.sin_addr.s_addr = addr_r.ip();
PRIVATE->addr_.sin_family = AF_INET;
#ifdef QNX
PRIVATE->addr_.sin_len = sizeof(PRIVATE->addr_);
#endif
connecting_ = true;
connected_ = connectTCP();
connecting_ = false;
if (!connected_) {
piCoutObj << "Can`t connect to" << addr_r << "," << ethErrorString();
}
opened_.exchange(connected_);
if (connected_) {
connected();
}
return connected_;
}
bool PIEthernet::listen(bool threaded) {
if (sock == -1) init();
if (sock == -1) return false;
if (threaded) {
if (server_thread_.isRunning()) {
if (!server_bounded) return true;
server_thread_.stop();
if (!server_thread_.waitForFinish(100_ms)) server_thread_.terminate();
}
listen_threaded = true;
server_bounded = false;
server_thread_.start(server_func);
return true;
}
listen_threaded = server_bounded = false;
addr_r.set(path());
memset(&PRIVATE->addr_, 0, sizeof(PRIVATE->addr_));
PRIVATE->addr_.sin_port = htons(addr_r.port());
PRIVATE->addr_.sin_addr.s_addr = addr_r.ip();
PRIVATE->addr_.sin_family = AF_INET;
#ifdef QNX
PRIVATE->addr_.sin_len = sizeof(PRIVATE->addr_);
#endif
opened_ = false;
int tries = 0;
while ((bind(sock, (sockaddr *)&PRIVATE->addr_, sizeof(PRIVATE->addr_)) == -1) && (tries < 2)) {
init();
tries++;
}
if (tries == 2) {
piCoutObj << "Can`t bind to" << addr_r << "," << ethErrorString();
return false;
}
if (::listen(sock, 64) == -1) {
piCoutObj << "Can`t listen on" << addr_r << "," << ethErrorString();
return false;
}
opened_ = server_bounded = true;
piCoutObj << "listen on" << path();
server_thread_.start(server_func);
return true;
}
bool PIEthernet::listen(const PINetworkAddress & addr, bool threaded) {
setReadAddress(addr);
return listen(threaded);
}
PIEthernet * PIEthernet::client(int index) {
PIMutexLocker locker(clients_mutex);
return clients_[index];
}
int PIEthernet::clientsCount() const {
PIMutexLocker locker(clients_mutex);
return clients_.size_s();
}
PIVector<PIEthernet *> PIEthernet::clients() const {
PIMutexLocker locker(clients_mutex);
return clients_;
}
bool PIEthernet::send(const void * data, int size, bool threaded) {
if (threaded) {
writeThreaded(data, size);
return true;
}
return (write(data, size) == size);
}
bool PIEthernet::send(const PINetworkAddress & addr, const void * data, int size, bool threaded) {
addr_s = addr;
if (threaded) {
writeThreaded(data, size);
return true;
}
PINetworkAddress pa = addr_s;
addr_s = addr;
int wr = write(data, size);
addr_s = pa;
return (wr == size);
}
bool PIEthernet::send(const PIByteArray & data, bool threaded) {
if (threaded) {
writeThreaded(data);
return true;
}
return (write(data) == data.size_s());
}
bool PIEthernet::send(const PINetworkAddress & addr, const PIByteArray & data, bool threaded) {
if (threaded) {
writeThreaded(data);
return true;
}
PINetworkAddress pa = addr_s;
addr_s = addr;
int wr = write(data);
addr_s = pa;
return (wr == data.size_s());
}
void PIEthernet::interrupt() {
// piCout << "interrupt";
PRIVATE->event.interrupt();
}
ssize_t PIEthernet::readDevice(void * read_to, ssize_t max_size) {
// piCout << "read" << sock;
if (sock == -1) init();
if (sock == -1 || read_to == 0) return -1;
int rs = 0, lerr = 0;
// piCoutObj << "read from " << path() << connecting_;
switch (type()) {
case TCP_Client:
if (connecting_) {
addr_r.set(path());
memset(&PRIVATE->addr_, 0, sizeof(PRIVATE->addr_));
PRIVATE->addr_.sin_port = htons(addr_r.port());
PRIVATE->addr_.sin_addr.s_addr = addr_r.ip();
PRIVATE->addr_.sin_family = AF_INET;
#ifdef QNX
PRIVATE->addr_.sin_len = sizeof(PRIVATE->addr_);
#endif
// piCoutObj << "connect to " << path() << "...";
connected_ = connectTCP();
// piCoutObj << "connect to " << path() << connected_;
if (!connected_) piCoutObj << "Can`t connect to" << addr_r << "," << ethErrorString();
opened_.exchange(connected_);
if (connected_) {
connecting_ = false;
connected();
} else
piMSleep(10);
// piCout << "connected to" << path();
}
if (!connected_) return -1;
errorClear();
#ifdef WINDOWS
{
long wr = waitForEvent(PRIVATE->event, FD_READ | FD_CLOSE);
switch (wr) {
case 0: return -1;
case FD_READ:
// piCout << "fd_read ...";
rs = ethRecv(sock, read_to, max_size);
break;
case FD_CLOSE:
// piCout << "fd_close ...";
rs = -1;
break;
default: break;
}
}
#else
if (PRIVATE->event.wait(sock)) {
errorClear();
rs = ethRecv(sock, read_to, max_size);
}
#endif
// piCoutObj << "readed" << rs;
if (rs <= 0) {
lerr = ethErrorCore();
// piCoutObj << "readed" << rs << "error" << lerr;
// async normal returns
#ifdef WINDOWS
if (lerr == WSAEWOULDBLOCK) {
#else
if (lerr == EWOULDBLOCK || lerr == EAGAIN || lerr == EINTR) {
#endif
// piCoutObj << "Ignore would_block" << lerr;
return -1;
}
// if no disconnect on timeout
if (!params[DisonnectOnTimeout]) {
#ifdef WINDOWS
if (lerr == WSAETIMEDOUT) {
#else
if (lerr == ETIMEDOUT) {
#endif
// piCoutObj << "Ignore read timeout";
return -1;
}
}
// disconnect here
// piCoutObj << "Disconnnected, check for event, connected =" << connected_;
if (connected_.exchange(false)) {
opened_ = false;
// piCoutObj << "Disconnect on read," << ethErrorString();
closeSocket(sock);
init();
disconnected(rs < 0);
}
if (params[KeepConnection]) {
connect();
}
// piCoutObj << "eth" << ip_ << "disconnected";
}
if (rs > 0) received(read_to, rs);
return rs;
case UDP: {
memset(&PRIVATE->raddr_, 0, sizeof(PRIVATE->raddr_));
// piCoutObj << "read from" << path() << "...";
#ifdef WINDOWS
long wr = waitForEvent(PRIVATE->event, FD_READ | FD_CLOSE);
switch (wr) {
case FD_READ:
// piCout << "fd_read ...";
rs = ethRecvfrom(sock, read_to, max_size, 0, (sockaddr *)&PRIVATE->raddr_);
break;
case FD_CLOSE:
// piCout << "fd_close ...";
rs = -1;
break;
default: break;
}
#else
rs = ethRecvfrom(sock, read_to, max_size, 0, (sockaddr *)&PRIVATE->raddr_);
#endif
// piCoutObj << "read from" << path() << rs << "bytes";
if (rs > 0) {
addr_lr.set(uint(PRIVATE->raddr_.sin_addr.s_addr), ntohs(PRIVATE->raddr_.sin_port));
// piCoutObj << "read from" << ip_r << ":" << port_r << rs << "bytes";
received(read_to, rs);
}
// else piCoutObj << "read returt" << rs << ", error" << ethErrorString();
return rs;
}
default: break;
}
return -1;
}
ssize_t PIEthernet::writeDevice(const void * data, ssize_t max_size) {
if (sock == -1) init();
if (sock == -1 || !isWriteable()) {
// piCoutObj << "Can`t send to uninitialized socket";
return -1;
}
// piCoutObj << "sending to " << ip_s << ":" << port_s << " " << max_size << " bytes";
int ret = 0;
switch (type()) {
case UDP:
PRIVATE->saddr_.sin_port = htons(addr_s.port());
PRIVATE->saddr_.sin_addr.s_addr = addr_s.ip();
PRIVATE->saddr_.sin_family = AF_INET;
// piCoutObj << "write to" << ip_s << ":" << port_s << "socket" << sock_s << max_size << "bytes ...";
return ethSendto(sock_s,
data,
max_size,
#ifndef WINDOWS
isOptionSet(BlockingWrite) ? 0 : MSG_DONTWAIT
#else
0
#endif
,
(sockaddr *)&PRIVATE->saddr_,
sizeof(PRIVATE->saddr_));
// piCout << "[PIEth] write to" << ip_s << ":" << port_s << "ok";
case TCP_Client: {
if (connecting_) {
memset(&PRIVATE->addr_, 0, sizeof(PRIVATE->addr_));
addr_r.set(path());
PRIVATE->addr_.sin_port = htons(addr_r.port());
PRIVATE->addr_.sin_addr.s_addr = addr_r.ip();
PRIVATE->addr_.sin_family = AF_INET;
#ifdef QNX
PRIVATE->addr_.sin_len = sizeof(PRIVATE->addr_);
#endif
// piCoutObj << "connect to " << ip << ":" << port_;
connected_ = connectTCP();
if (!connected_) piCoutObj << "Can`t connect to" << addr_r << "," << ethErrorString();
opened_.exchange(connected_);
if (connected_) {
connecting_ = false;
connected();
}
}
if (!connected_) return -1;
auto disconnectFunc = [this]() {
if (connected_.exchange(false)) {
opened_ = false;
// piCoutObj << "Disconnect on write," << ethErrorString();
closeSocket(sock);
init();
disconnected(true);
}
};
if (!isOptionSet(BlockingWrite)) {
ret = ::send(sock, (const char *)data, max_size, 0);
if (ret < 0) {
disconnectFunc();
return -1;
}
} else {
ssize_t remain_size = max_size;
const char * remain_data = (const char *)data;
while (remain_size > 0) {
int sr = ::send(sock, remain_data, remain_size, 0);
if (sr < 0) {
int err = ethErrorCore();
#ifdef WINDOWS
if (err == WSAEWOULDBLOCK) {
#else
if (err == EAGAIN || err == EWOULDBLOCK) {
#endif
piMinSleep();
// piCoutObj << "wait for write";
continue;
} else {
disconnectFunc();
return -1;
}
}
remain_data += sr;
remain_size -= sr;
}
}
return ret;
}
default: break;
}
return -1;
}
PIIODevice::DeviceInfoFlags PIEthernet::deviceInfoFlags() const {
switch (type()) {
case UDP: return 0;
case TCP_Client:
case TCP_Server: return Sequential | Reliable;
default: break;
}
return 0;
}
void PIEthernet::applyParameters() {
if (sock == -1) return;
if (params[PIEthernet::ReuseAddress]) ethSetsockoptBool(sock, SOL_SOCKET, SO_REUSEADDR);
if (params[PIEthernet::Broadcast]) ethSetsockoptBool(sock, SOL_SOCKET, SO_BROADCAST);
if (params[PIEthernet::NoDelay] && (type() == TCP_Client)) ethSetsockoptBool(sock, IPPROTO_TCP, TCP_NODELAY, true);
}
void PIEthernet::clientDeleted(PIObject * o) {
PIMutexLocker locker(clients_mutex);
clients_.removeOne((PIEthernet *)o);
}
void PIEthernet::server_func(void * eth) {
PIEthernet * ce = (PIEthernet *)eth;
if (ce->listen_threaded) {
if (!ce->server_bounded) {
if (!ce->listen(false)) {
ce->listen_threaded = true;
piMSleep(100);
return;
}
}
}
sockaddr_in client_addr;
socklen_t slen = sizeof(client_addr);
#ifdef WINDOWS
long wr = ce->waitForEvent(ce->PRIVATEWB->event, FD_ACCEPT | FD_CLOSE);
if (wr != FD_ACCEPT) {
piMSleep(10);
return;
}
#else
if (!ce->PRIVATEWB->event.wait(ce->sock)) {
piMSleep(10);
return;
}
#endif
// piCout << "server" << "accept ...";
int s = accept(ce->sock, (sockaddr *)&client_addr, &slen);
// piCout << "server" << "accept done" << ethErrorString();
if (s == -1) {
int lerr = ethErrorCore();
#ifdef WINDOWS
if (lerr == WSAETIMEDOUT) {
#elif defined(ANDROID)
if ((lerr == EAGAIN || lerr == EINTR)) {
#else
if (lerr == EAGAIN) {
#endif
piMSleep(10);
return;
}
if (ce->debug()) piCout << "[PIEthernet] Can`t accept new connection," << ethErrorString();
piMSleep(10);
return;
}
PIString ip = PIStringAscii(inet_ntoa(client_addr.sin_addr));
ip += ":" + PIString::fromNumber(htons(client_addr.sin_port));
PIEthernet * e = new PIEthernet(s, ip);
CONNECT1(void, PIObject *, e, deleted, ce, clientDeleted);
ce->clients_mutex.lock();
ce->clients_ << e;
ce->clients_mutex.unlock();
ce->newConnection(e);
// cout << "connected " << ip << endl;
}
void PIEthernet::setType(Type t, bool reopen) {
eth_type = t;
setProperty("type", (int)t);
if (reopen && isOpened()) {
closeDevice();
init();
openDevice();
}
}
bool PIEthernet::connectTCP() {
::connect(sock, (sockaddr *)&(PRIVATE->addr_), sizeof(PRIVATE->addr_));
// piCout << errorString();
#ifdef WINDOWS
long wr = waitForEvent(PRIVATE->event, FD_CONNECT | FD_CLOSE);
switch (wr) {
case FD_CONNECT:
// piCout << "fd_connect ...";
return ethIsWriteable(sock);
default: break;
}
#else
if (PRIVATE->event.wait(sock, PIWaitEvent::CheckWrite)) {
if (ethIsWriteable(sock))
return true;
else {
closeSocket(sock);
init();
}
}
#endif
return false;
}
#ifdef WINDOWS
long PIEthernet::waitForEvent(PIWaitEvent & event, long mask) {
if (!event.isCreate() || sock < 0) return 0;
if (WSAEventSelect(sock, event.getEvent(), mask) == SOCKET_ERROR) {
if (ethErrorCore() == WSAEINPROGRESS) return 0;
}
if (event.wait()) {
// DWORD wr = WSAWaitForMultipleEvents(1, &(PRIVATE->read_event), FALSE, WSA_INFINITE, TRUE);
// if (wr == WSA_WAIT_EVENT_0) {
WSANETWORKEVENTS events;
memset(&events, 0, sizeof(events));
WSAEnumNetworkEvents(sock, event.getEvent(), &events);
// piCoutObj << "wait result" << events.lNetworkEvents;
return events.lNetworkEvents;
}
return 0;
}
#endif
bool PIEthernet::configureDevice(const void * e_main, const void * e_parent) {
PIConfig::Entry * em = (PIConfig::Entry *)e_main;
PIConfig::Entry * ep = (PIConfig::Entry *)e_parent;
setReadIP(readDeviceSetting<PIString>("ip", readIP(), em, ep));
setReadPort(readDeviceSetting<int>("port", readPort(), em, ep));
setParameter(PIEthernet::Broadcast, readDeviceSetting<bool>("broadcast", isParameterSet(PIEthernet::Broadcast), em, ep));
setParameter(PIEthernet::ReuseAddress, readDeviceSetting<bool>("reuseAddress", isParameterSet(PIEthernet::ReuseAddress), em, ep));
return true;
}
void PIEthernet::propertyChanged(const char * name) {
PIConstChars pn(name);
if (pn.endsWith("Timeout")) applyTimeouts();
if (pn == "TTL") applyOptInt(IPPROTO_IP, IP_TTL, TTL());
if (pn == "MulticastTTL") applyOptInt(IPPROTO_IP, IP_MULTICAST_TTL, multicastTTL());
}
PIString PIEthernet::constructFullPathDevice() const {
PIString ret;
ret += (type() == PIEthernet::UDP ? "UDP" : "TCP");
ret += ":" + readIP() + ":" + PIString::fromNumber(readPort());
if (type() == PIEthernet::UDP) {
ret += ":" + sendIP() + ":" + PIString::fromNumber(sendPort());
piForeachC(PIString & m, multicastGroups())
ret += ":mcast:" + m;
}
return ret;
}
void PIEthernet::configureFromFullPathDevice(const PIString & full_path) {
PIStringList pl = full_path.split(":");
bool mcast = false;
for (int i = 0; i < pl.size_s(); ++i) {
PIString p(pl[i]);
switch (i) {
case 0:
p = p.toLowerCase();
if (p == "udp") setType(UDP);
if (p == "tcp") setType(TCP_Client);
break;
case 1:
setReadIP(p);
setSendIP(p);
break;
case 2:
setReadPort(p.toInt());
setSendPort(p.toInt());
break;
case 3: setSendIP(p); break;
case 4: setSendPort(p.toInt()); break;
}
if (i <= 4) continue;
if (i % 2 == 1) {
if (p.toLowerCase() == "mcast") mcast = true;
} else {
if (mcast) {
joinMulticastGroup(p);
mcast = false;
}
}
}
}
PIPropertyStorage PIEthernet::constructVariantDevice() const {
PIPropertyStorage ret;
PIVariantTypes::Enum e;
e << "UDP"
<< "TCP";
if (type() == PIEthernet::UDP)
e.selectValue(0);
else
e.selectValue(1);
ret.addProperty("protocol", e);
ret.addProperty("read IP", readIP());
ret.addProperty("read port", readPort());
ret.addProperty("send IP", sendIP());
ret.addProperty("send port", sendPort());
ret.addProperty("multicast", multicastGroups());
return ret;
}
void PIEthernet::configureFromVariantDevice(const PIPropertyStorage & d) {
setType(d.propertyValueByName("protocol").toEnum().selectedValue() == 0 ? UDP : TCP_Client);
setReadIP(d.propertyValueByName("read IP").toString());
setReadPort(d.propertyValueByName("read port").toInt());
setSendIP(d.propertyValueByName("send IP").toString());
setSendPort(d.propertyValueByName("send port").toInt());
PIStringList mcgl = d.propertyValueByName("multicast").toStringList();
piForeachC(PIString & g, mcgl) {
joinMulticastGroup(g);
}
}
PIEthernet::InterfaceList PIEthernet::interfaces() {
// piCout << "PIEthernet::interfaces()";
PIEthernet::InterfaceList il;
Interface ci;
ci.index = -1;
ci.mtu = 1500;
#ifdef WINDOWS
int ret = 0;
ulong ulOutBufLen = sizeof(IP_ADAPTER_INFO);
PIP_ADAPTER_INFO pAdapterInfo = (PIP_ADAPTER_INFO)HeapAlloc(GetProcessHeap(), 0, sizeof(IP_ADAPTER_INFO));
if (!pAdapterInfo) {
piCout << "[PIEthernet] Error allocating memory needed to call GetAdaptersInfo";
return il;
}
if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) == ERROR_BUFFER_OVERFLOW) {
HeapFree(GetProcessHeap(), 0, pAdapterInfo);
pAdapterInfo = (PIP_ADAPTER_INFO)HeapAlloc(GetProcessHeap(), 0, ulOutBufLen);
if (!pAdapterInfo) {
piCout << "[PIEthernet] Error allocating memory needed to call GetAdaptersInfo";
return il;
}
}
if ((ret = GetAdaptersInfo(pAdapterInfo, &ulOutBufLen)) == NO_ERROR) {
PIP_ADAPTER_INFO pAdapter = pAdapterInfo;
while (pAdapter) {
ci.name = PIString(pAdapter->AdapterName);
ci.index = pAdapter->Index;
ci.mac = macFromBytes(PIByteArray(pAdapter->Address, pAdapter->AddressLength));
ci.flags = PIEthernet::ifActive | PIEthernet::ifRunning;
if (pAdapter->Type == MIB_IF_TYPE_PPP) ci.flags |= PIEthernet::ifPTP;
if (pAdapter->Type == MIB_IF_TYPE_LOOPBACK) ci.flags |= PIEthernet::ifLoopback;
ci.broadcast.clear();
ci.ptp.clear();
IP_ADDR_STRING * as = &(pAdapter->IpAddressList);
while (as) {
// piCout << "[pAdapter]" << ci.name << PIString(as->IpAddress.String);
ci.address = PIStringAscii(as->IpAddress.String);
ci.netmask = PIStringAscii(as->IpMask.String);
if (ci.address == "0.0.0.0") {
as = as->Next;
continue;
}
il << ci;
as = as->Next;
}
pAdapter = pAdapter->Next;
}
} else {
switch (ret) {
case ERROR_NO_DATA: break;
case ERROR_NOT_SUPPORTED: piCout << "[PIEthernet] GetAdaptersInfo not supported"; break;
default: piCout << "[PIEthernet] GetAdaptersInfo failed with error:" << ret;
}
}
if (pAdapterInfo) HeapFree(GetProcessHeap(), 0, pAdapterInfo);
#else
# ifdef MICRO_PIP
# else
# ifdef ANDROID
struct ifconf ifc;
int s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
ifc.ifc_len = 256;
ifc.ifc_buf = new char[ifc.ifc_len];
if (ioctl(s, SIOCGIFCONF, &ifc) < 0) {
piCout << "[PIEthernet] Can`t get interfaces:" << errorString();
delete[] ifc.ifc_buf;
return il;
}
int icnt = ifc.ifc_len / sizeof(ifreq);
PIStringList inl;
struct ifreq ir;
for (int i = 0; i < icnt; ++i) {
ci.flags = 0;
PIString in = PIStringAscii(ifc.ifc_req[i].ifr_name);
if (in.isEmpty()) continue;
ci.name = in;
strcpy(ir.ifr_name, in.dataAscii());
if (ioctl(s, SIOCGIFHWADDR, &ir) == 0) ci.mac = macFromBytes(PIByteArray(ir.ifr_hwaddr.sa_data, 6));
if (ioctl(s, SIOCGIFADDR, &ir) >= 0) ci.address = getSockAddr(&ir.ifr_addr);
if (ioctl(s, SIOCGIFNETMASK, &ir) >= 0) ci.netmask = getSockAddr(&ir.ifr_addr);
ioctl(s, SIOCGIFMTU, &ci.mtu);
if (ci.address == "127.0.0.1") ci.flags |= PIEthernet::ifLoopback;
il << ci;
}
delete ifc.ifc_buf;
# else
struct ifaddrs *ret, *cif = 0;
int s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
if (getifaddrs(&ret) == 0) {
cif = ret;
while (cif != 0) {
if (cif->ifa_addr == 0) {
cif = cif->ifa_next;
continue;
}
if (cif->ifa_addr->sa_family != AF_INET) {
cif = cif->ifa_next;
continue;
}
ci.name = PIString(cif->ifa_name);
ci.address = getSockAddr(cif->ifa_addr);
ci.netmask = getSockAddr(cif->ifa_netmask);
ci.mac.clear();
# ifdef QNX
# ifndef BLACKBERRY
int fd = ::open((PIString("/dev/io-net/") + ci.name).dataAscii(), O_RDONLY);
if (fd != 0) {
nic_config_t nic;
devctl(fd, DCMD_IO_NET_GET_CONFIG, &nic, sizeof(nic), 0);
::close(fd);
ci.mac = macFromBytes(PIByteArray(nic.permanent_address, 6));
}
# endif
# else
# ifdef MAC_OS
PIString req = PISystemInfo::instance()->ifconfigPath + " " + ci.name + " | grep ether";
FILE * fp = popen(req.dataAscii(), "r");
if (fp != 0) {
char in[256];
if (fgets(in, 256, fp) != 0) {
req = PIString(in).trim();
ci.mac = req.cutLeft(req.find(" ") + 1).trim().toUpperCase();
}
pclose(fp);
}
# else
if (s != -1) {
struct ifreq ir;
strcpy(ir.ifr_name, cif->ifa_name);
if (ioctl(s, SIOCGIFHWADDR, &ir) == 0) {
ci.mac = macFromBytes(PIByteArray(ir.ifr_hwaddr.sa_data, 6));
ci.mtu = ir.ifr_mtu;
}
}
# endif
# endif
ci.flags = 0;
if (cif->ifa_flags & IFF_UP) ci.flags |= PIEthernet::ifActive;
if (cif->ifa_flags & IFF_RUNNING) ci.flags |= PIEthernet::ifRunning;
if (cif->ifa_flags & IFF_BROADCAST) ci.flags |= PIEthernet::ifBroadcast;
if (cif->ifa_flags & IFF_MULTICAST) ci.flags |= PIEthernet::ifMulticast;
if (cif->ifa_flags & IFF_LOOPBACK) ci.flags |= PIEthernet::ifLoopback;
if (cif->ifa_flags & IFF_POINTOPOINT) ci.flags |= PIEthernet::ifPTP;
ci.broadcast.clear();
ci.ptp.clear();
if (ci.flags[PIEthernet::ifBroadcast]) ci.broadcast = getSockAddr(cif->ifa_broadaddr);
if (ci.flags[PIEthernet::ifPTP]) ci.ptp = getSockAddr(cif->ifa_dstaddr);
ci.index = if_nametoindex(cif->ifa_name);
il << ci;
cif = cif->ifa_next;
}
freeifaddrs(ret);
} else
piCout << "[PIEthernet] Can`t get interfaces:" << errorString();
if (s != -1) ::close(s);
# endif
# endif
#endif
return il;
}
PINetworkAddress PIEthernet::interfaceAddress(const PIString & interface_) {
#if defined(WINDOWS) || defined(MICRO_PIP)
piCout << "[PIEthernet] Not implemented, use \"PIEthernet::allAddresses\" or \"PIEthernet::interfaces\" instead";
return PINetworkAddress();
#else
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
strcpy(ifr.ifr_name, interface_.dataAscii());
int s = ::socket(AF_INET, SOCK_DGRAM, 0);
ioctl(s, SIOCGIFADDR, &ifr);
::close(s);
struct sockaddr_in * sa = (struct sockaddr_in *)&ifr.ifr_addr;
return PINetworkAddress(uint(sa->sin_addr.s_addr));
#endif
}
PIVector<PINetworkAddress> PIEthernet::allAddresses() {
PIEthernet::InterfaceList il = interfaces();
PIVector<PINetworkAddress> ret;
bool has_127 = false;
piForeachC(PIEthernet::Interface & i, il) {
if (i.address == "127.0.0.1") has_127 = true;
PINetworkAddress a(i.address);
if (a.ip() == 0) continue;
ret << a;
}
// piCout << "[PIEthernet::allAddresses]" << al;
if (!has_127) ret << PINetworkAddress("127.0.0.1");
return ret;
}
// System wrap
int PIEthernet::ethErrorCore() {
#ifdef WINDOWS
return WSAGetLastError();
#else
return errno;
#endif
}
PIString PIEthernet::ethErrorString() {
#ifdef WINDOWS
char * msg = nullptr;
int err = WSAGetLastError();
FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
err,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPSTR)&msg,
0,
NULL);
PIString ret = PIStringAscii("code ") + PIString::fromNumber(err) + PIStringAscii(" - ");
if (msg) {
ret += PIString::fromSystem(msg).trim();
LocalFree(msg);
} else
ret += '?';
return ret;
#else
return errorString();
#endif
}
int PIEthernet::ethRecv(int sock, void * buf, int size, int flags) {
if (sock < 0) return -1;
return recv(sock,
#ifdef WINDOWS
(char *)
#endif
buf,
size,
flags);
}
int PIEthernet::ethRecvfrom(int sock, void * buf, int size, int flags, sockaddr * addr) {
if (sock < 0) return -1;
#ifdef QNX
return recv(sock, buf, size, flags);
#else
socklen_t len = sizeof(sockaddr);
return recvfrom(sock,
# ifdef WINDOWS
(char *)
# endif
buf,
size,
flags,
addr,
&len);
#endif
}
int PIEthernet::ethSendto(int sock, const void * buf, int size, int flags, sockaddr * addr, int addr_len) {
if (sock < 0) return -1;
return sendto(sock,
#ifdef WINDOWS
(const char *)
#endif
buf,
size,
flags,
addr,
addr_len);
}
void PIEthernet::ethClosesocket(int sock, bool shutdown) {
// piCout << "close socket" << sock << shutdown;
if (sock < 0) return;
if (shutdown)
::shutdown(sock,
#ifdef WINDOWS
SD_BOTH);
closesocket(sock);
#else
SHUT_RDWR);
::close(sock);
#endif
}
int PIEthernet::ethSetsockopt(int sock, int level, int optname, const void * optval, int optlen) {
if (sock < 0) return -1;
auto ret = setsockopt(sock,
level,
optname,
#ifdef WINDOWS
(char *)
#endif
optval,
optlen);
if (ret != 0) piCout << "setsockopt error:" << ethErrorString();
return ret;
}
int PIEthernet::ethSetsockoptInt(int sock, int level, int optname, int value) {
if (sock < 0) return -1;
#ifdef WINDOWS
DWORD
#else
int
#endif
so = value;
return ethSetsockopt(sock, level, optname, &so, sizeof(so));
}
int PIEthernet::ethSetsockoptBool(int sock, int level, int optname, bool value) {
if (sock < 0) return -1;
#ifdef WINDOWS
BOOL
#else
int
#endif
so = (value ? 1 : 0);
return ethSetsockopt(sock, level, optname, &so, sizeof(so));
}
void PIEthernet::ethNonblocking(int sock) {
if (sock < 0) return;
#ifdef WINDOWS
u_long mode = 1;
ioctlsocket(sock, FIONBIO, &mode);
#else
fcntl(sock, F_SETFL, O_NONBLOCK);
#endif
}
bool PIEthernet::ethIsWriteable(int sock) {
/* fd_set fd_test;
FD_ZERO(&fd_test);
FD_SET(sock, &fd_test);
int fds = 0;
#ifndef WINDOWS
fds = sock + 1;
#endif
timeval timeout;
timeout.tv_sec = timeout.tv_usec = 0;
::select(fds, nullptr, &fd_test, nullptr, &timeout);
return FD_ISSET(sock, &fd_test);*/
#ifdef WINDOWS
fd_set fd_test;
FD_ZERO(&fd_test);
FD_SET(sock, &fd_test);
timeval timeout;
timeout.tv_sec = timeout.tv_usec = 0;
::select(0, nullptr, &fd_test, nullptr, &timeout);
return FD_ISSET(sock, &fd_test);
#else
int ret = 0;
socklen_t len = sizeof(ret);
getsockopt(sock, SOL_SOCKET, SO_ERROR, (char *)&ret, &len);
return ret == 0;
#endif
}