/* 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 . */ #include "piethernet.h" #ifndef PIP_NO_SOCKET # include "piconfig.h" # include "piconstchars.h" # include "piincludes_p.h" # include "piliterals.h" # include "pipropertystorage.h" # include "pisysteminfo.h" # include "pitranslator.h" # ifdef QNX # include # include # include # include # include # include # include # include # include # include # include # include # ifdef BLACKBERRY # include # else # include # endif # define ip_mreqn ip_mreq # define imr_address imr_interface # else # ifdef WINDOWS # include # include # include # include # include # define ip_mreqn ip_mreq # define imr_address imr_interface # else # include # include # include # include # include # include # include # include # if !defined(ANDROID) && !defined(LWIP) # include # endif # ifdef LWIP # include # endif # endif # endif # include "piwaitevent_p.h" # include /** \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(); eth_type = UDP; setProperty("type", (int)UDP); setParameters(PIEthernet::ReuseAddress | PIEthernet::MulticastLoop | PIEthernet::KeepConnection); } PIEthernet::PIEthernet(PIEthernet::Type type_, const PIString & ip_port, const PIFlags params_) : PIIODevice(ip_port, ReadWrite) { construct(); addr_r.set(ip_port); eth_type = type_; setProperty("type", (int)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; is_server_client = true; eth_type = TCP_Client; setProperty("type", (int)TCP_Client); init(); setParameters(PIEthernet::ReuseAddress | PIEthernet::MulticastLoop); 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::setReadTimeout(PISystemTime tm) { setProperty("readTimeout", tm); applyTimeouts(); } void PIEthernet::setWriteTimeout(PISystemTime tm) { setProperty("writeTimeout", tm); applyTimeouts(); } void PIEthernet::setReadBufferSize(int bytes) { rcv_buf = bytes; applyBuffers(); } void PIEthernet::setWriteBufferSize(int bytes) { snd_buf = bytes; applyBuffers(); } void PIEthernet::construct() { // piCout << " PIEthernet" << uint(this); setOption(BlockingWrite); setReadTimeout(10_s); setWriteTimeout(10_s); setTTL(64); setMulticastTTL(1); server_thread_.setData(this); server_thread_.setName("_S.tcpserver"_a); # ifdef LWIP setThreadedReadBufferSize(512); # else setThreadedReadBufferSize(64_KiB); # endif // setPriority(piHigh); } void PIEthernet::init() { if (isOpened() || is_server_client) return; if (sock != -1) return; // 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(); connected_ = connecting_ = opened_ = false; return; } applyParameters(); applyTimeouts(); applyBuffers(); applyOptInt(IPPROTO_IP, IP_TTL, TTL()); // piCoutObj << "inited" << path(); } 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(":")); for (const auto & 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; piZeroMemory(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(); applyBuffers(); applyOptInt(IPPROTO_IP, IP_TTL, TTL()); addr_lr.clear(); return true; } bool PIEthernet::closeDevice() { // piCoutObj << "close"; bool ned = connected_; connected_ = connecting_ = false; stopThreadedListen(); clients_mutex.lock(); auto cl = clients_; clients_.clear(); clients_mutex.unlock(); piDeleteAll(cl); 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::applyBuffers() { if (sock < 0) return; if (rcv_buf > 0) ethSetsockoptInt(sock, SOL_SOCKET, SO_RCVBUF, rcv_buf); if (snd_buf > 0) { if (sock_s != sock) { ethSetsockoptInt(sock_s, SOL_SOCKET, SO_SNDBUF, snd_buf); } else { ethSetsockoptInt(sock, SOL_SOCKET, SO_SNDBUF, snd_buf); } } } 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 piZeroMemory(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 piZeroMemory(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; piZeroMemory(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()); piZeroMemory(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); } void PIEthernet::stopThreadedListen() { 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); } 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::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()); piZeroMemory(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: { piZeroMemory(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_) { piZeroMemory(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 (params[KeepConnection]) { connect(); } } }; 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, %1"_tr("PIEthernet").arg(ethErrorString()); piMSleep(50); 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; piZeroMemory(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("ip", readIP(), em, ep)); setReadPort(readDeviceSetting("port", readPort(), em, ep)); setParameter(PIEthernet::Broadcast, readDeviceSetting("broadcast", isParameterSet(PIEthernet::Broadcast), em, ep)); setParameter(PIEthernet::ReuseAddress, readDeviceSetting("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()); for (const auto & 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].trimmed()); 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(); for (const auto & 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"_tr("PIEthernet"); 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"_tr("PIEthernet"); 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 LWIP # 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: %1"_tr("PIEthernet").arg(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; memset(&ir, 0, sizeof(ir)); strncpy(ir.ifr_name, cif->ifa_name, sizeof(ir.ifr_name)); if (ioctl(s, SIOCGIFHWADDR, &ir) == 0) { ci.mac = macFromBytes(PIByteArray(ir.ifr_hwaddr.sa_data, 6)); } if (ioctl(s, SIOCGIFMTU, &ir) == 0) { 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: %1"_tr("PIEthernet").arg(errorString()); if (s != -1) ::close(s); # endif # endif # endif return il; } PINetworkAddress PIEthernet::interfaceAddress(const PIString & interface_) { # if defined(WINDOWS) || defined(LWIP) piCout << "[PIEthernet] Not implemented, use \"PIEthernet::allAddresses\" or \"PIEthernet::interfaces\" instead"; return PINetworkAddress(); # else struct ifreq ifr; piZeroMemory(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 PIEthernet::allAddresses() { PIEthernet::InterfaceList il = interfaces(); PIVector ret; bool has_127 = false; for (const auto & i: il) { if (i.address.startsWith("127.0.0.")) 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 } #endif // PIP_NO_SOCKET