Files
pip/libs/main/io_devices/piethernet.h
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

526 lines
17 KiB
C++

/*! \file piethernet.h
* \ingroup IO
* \~\brief
* \~english Ethernet device
* \~russian Устройство Ethernet
*/
/*
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/>.
*/
#ifndef PIETHERNET_H
#define PIETHERNET_H
#include "piiodevice.h"
#include "pinetworkaddress.h"
#ifdef ANDROID
struct
#else
class
#endif
sockaddr;
class PIP_EXPORT PIEthernet: public PIIODevice {
PIIODEVICE(PIEthernet, "eth");
friend class PIPeer;
public:
//! Contructs UDP %PIEthernet with empty read address
explicit PIEthernet();
//! \brief Type of %PIEthernet
enum Type {
UDP /** UDP - User Datagram Protocol */,
TCP_Client /** TCP client - allow connection to TCP server */,
TCP_Server /** TCP server - receive connections from TCP clients */
};
//! \brief Parameters of %PIEthernet
enum Parameters {
ReuseAddress /** Rebind address if there is already binded. Enabled by default */ = 0x1,
Broadcast /** Broadcast send. Disabled by default */ = 0x2,
SeparateSockets /** If this parameter is set, %PIEthernet will initialize two different sockets,
for receive and send, instead of single one. Disabled by default */
= 0x4,
MulticastLoop /** Enable receiving multicast packets from same host. Enabled by default */ = 0x8,
KeepConnection /** Automatic reconnect TCP connection on disconnect. Enabled by default */ = 0x10,
DisonnectOnTimeout /** Disconnect TCP connection on read timeout expired. Disabled by default */ = 0x20,
NoDelay /** Use NO_DELAY option. Disabled by default */ = 0x40
};
typedef ::PINetworkAddress Address DEPRECATEDM("use PINetworkAddress instead");
//! Contructs %PIEthernet with type "type", read address "ip_port" and parameters "params"
explicit PIEthernet(Type type,
const PIString & ip_port = PIString(),
const PIFlags<Parameters> params = PIEthernet::ReuseAddress | PIEthernet::MulticastLoop |
PIEthernet::KeepConnection);
virtual ~PIEthernet();
//! Set read address
void setReadAddress(const PIString & ip, int port) {
addr_r.set(ip, port);
setPath(addr_r.toString());
}
//! Set read address in format "i.i.i.i:p"
void setReadAddress(const PIString & ip_port) {
addr_r.set(ip_port);
setPath(addr_r.toString());
}
//! Set read address
void setReadAddress(const PINetworkAddress & addr) {
addr_r = addr;
setPath(addr_r.toString());
}
//! Set read IP
void setReadIP(const PIString & ip) {
addr_r.setIP(ip);
setPath(addr_r.toString());
}
//! Set read port
void setReadPort(int port) {
addr_r.setPort(port);
setPath(addr_r.toString());
}
//! Set send address
void setSendAddress(const PIString & ip, int port) { addr_s.set(ip, port); }
//! Set send address in format "i.i.i.i:p"
void setSendAddress(const PIString & ip_port) { addr_s.set(ip_port); }
//! Set send address
void setSendAddress(const PINetworkAddress & addr) { addr_s = addr; }
//! Set send IP
void setSendIP(const PIString & ip) { addr_s.setIP(ip); }
//! Set send port
void setSendPort(int port) { addr_s.setPort(port); }
//! Returns read address in format "i.i.i.i:p"
PINetworkAddress readAddress() const { return addr_r; }
//! Returns read IP
PIString readIP() const { return addr_r.ipString(); }
//! Returns read port
int readPort() const { return addr_r.port(); }
//! Returns send address in format "i.i.i.i:p"
PINetworkAddress sendAddress() const { return addr_s; }
//! Returns send IP
PIString sendIP() const { return addr_s.ipString(); }
//! Returns send port
int sendPort() const { return addr_s.port(); }
//! Returns address of last received UDP packet in format "i.i.i.i:p"
PINetworkAddress lastReadAddress() const { return addr_lr; }
//! Returns IP of last received UDP packet
PIString lastReadIP() const { return addr_lr.ipString(); }
//! Returns port of last received UDP packet
int lastReadPort() const { return addr_lr.port(); }
//! Set parameters to "parameters_". You should to reopen %PIEthernet to apply them
void setParameters(PIFlags<PIEthernet::Parameters> parameters_) {
params = parameters_;
applyParameters();
}
//! Set parameter "parameter" to state "on". You should to reopen %PIEthernet to apply this
void setParameter(PIEthernet::Parameters parameter, bool on = true) {
params.setFlag(parameter, on);
applyParameters();
}
//! Returns if parameter "parameter" is set
bool isParameterSet(PIEthernet::Parameters parameter) const { return params[parameter]; }
//! Returns parameters
PIFlags<PIEthernet::Parameters> parameters() const { return params; }
//! Returns %PIEthernet type
Type type() const { return eth_type; }
//! Returns read timeout
PISystemTime readTimeout() const { return property("readTimeout").toSystemTime(); }
//! Returns write timeout
PISystemTime writeTimeout() const { return property("writeTimeout").toSystemTime(); }
//! Set timeout for read
void setReadTimeout(PISystemTime tm) { setProperty("readTimeout", tm); }
//! Set timeout for write
void setWriteTimeout(PISystemTime tm) { setProperty("writeTimeout", tm); }
//! Returns TTL (Time To Live)
int TTL() const { return property("TTL").toInt(); }
//! Returns multicast TTL (Time To Live)
int multicastTTL() const { return property("MulticastTTL").toInt(); }
//! Set TTL (Time To Live), default is 64
void setTTL(int ttl) { setProperty("TTL", ttl); }
//! Set multicast TTL (Time To Live), default is 1
void setMulticastTTL(int ttl) { setProperty("MulticastTTL", ttl); }
//! Join to multicast group with address "group". Use only for UDP
bool joinMulticastGroup(const PIString & group);
//! Leave multicast group with address "group". Use only for UDP
bool leaveMulticastGroup(const PIString & group);
//! Returns joined multicast groups. Use only for UDP
const PIStringList & multicastGroups() const { return mcast_groups; }
//! If \"threaded\" queue connect to TCP server with address \a readAddress() in
//! any \a read() or \a write() call. Otherwise connect immediate.
//! Use only for TCP_Client
bool connect(bool threaded = true);
//! Connect to TCP server with address "ip":"port". Use only for TCP_Client
bool connect(const PIString & ip, int port, bool threaded = true) {
setPath(ip + PIStringAscii(":") + PIString::fromNumber(port));
return connect(threaded);
}
//! Connect to TCP server with address "ip_port". Use only for TCP_Client
bool connect(const PIString & ip_port, bool threaded = true) {
setPath(ip_port);
return connect(threaded);
}
//! Connect to TCP server with address "addr". Use only for TCP_Client
bool connect(const PINetworkAddress & addr, bool threaded = true) {
setPath(addr.toString());
return connect(threaded);
}
//! Returns if %PIEthernet connected to TCP server. Use only for TCP_Client
bool isConnected() const { return connected_; }
//! Returns if %PIEthernet is connecting to TCP server. Use only for TCP_Client
bool isConnecting() const { return connecting_; }
//! Start listen for incoming TCP connections on address \a readAddress(). Use only for TCP_Server
bool listen(bool threaded = false);
//! Start listen for incoming TCP connections on address "ip":"port". Use only for TCP_Server
bool listen(const PIString & ip, int port, bool threaded = false) { return listen(PINetworkAddress(ip, port), threaded); }
//! Start listen for incoming TCP connections on address "ip_port". Use only for TCP_Server
bool listen(const PIString & ip_port, bool threaded = false) { return listen(PINetworkAddress(ip_port), threaded); }
//! Start listen for incoming TCP connections on address "addr". Use only for TCP_Server
bool listen(const PINetworkAddress & addr, bool threaded = false);
PIEthernet * client(int index);
int clientsCount() const;
PIVector<PIEthernet *> clients() const;
//! Send data "data" with size "size" to address \a sendAddress() for UDP or \a readAddress() for TCP_Client
bool send(const void * data, int size, bool threaded = false);
//! Send data "data" with size "size" to address "ip":"port"
bool send(const PIString & ip, int port, const void * data, int size, bool threaded = false) {
return send(PINetworkAddress(ip, port), data, size, threaded);
}
//! Send data "data" with size "size" to address "ip_port"
bool send(const PIString & ip_port, const void * data, int size, bool threaded = false) {
return send(PINetworkAddress(ip_port), data, size, threaded);
}
//! Send data "data" with size "size" to address "addr"
bool send(const PINetworkAddress & addr, const void * data, int size, bool threaded = false);
//! Send data "data" to address \a sendAddress() for UDP or \a readAddress() for TCP_Client
bool send(const PIByteArray & data, bool threaded = false);
//! Send data "data" to address "ip":"port" for UDP
bool send(const PIString & ip, int port, const PIByteArray & data, bool threaded = false) {
return send(PINetworkAddress(ip, port), data, threaded);
}
//! Send data "data" to address "ip_port" for UDP
bool send(const PIString & ip_port, const PIByteArray & data, bool threaded = false) {
return send(PINetworkAddress(ip_port), data, threaded);
}
//! Send data "data" to address "addr" for UDP
bool send(const PINetworkAddress & addr, const PIByteArray & data, bool threaded = false);
bool canWrite() const override { return mode() & WriteOnly; }
void interrupt() override;
int socket() const { return sock; }
EVENT1(newConnection, PIEthernet *, client);
EVENT0(connected);
EVENT1(disconnected, bool, withError);
//! Flags of network interface
enum InterfaceFlag {
ifActive /** Is active */ = 0x1,
ifRunning /** Is running */ = 0x2,
ifBroadcast /** Support broadcast */ = 0x4,
ifMulticast /** Support multicast */ = 0x8,
ifLoopback /** Is loopback */ = 0x10,
ifPTP /** Is point-to-point */ = 0x20
};
//! %PIFlags of network interface flags
typedef PIFlags<InterfaceFlag> InterfaceFlags;
//! Network interface descriptor
struct PIP_EXPORT Interface {
//! System index
int index;
//! MTU
int mtu;
//! System name
PIString name;
//! MAC address in format "hh:hh:hh:hh:hh:hh" or empty if there is no MAC address
PIString mac;
//! IP address in format "i.i.i.i" or empty if there is no IP address
PIString address;
//! Netmask of IP address in format "i.i.i.i" or empty if there is no netmask
PIString netmask;
//! Broadcast address in format "i.i.i.i" or empty if there is no broadcast address
PIString broadcast;
//! Point-to-point address or empty if there is no point-to-point address
PIString ptp;
//! Flags of interface
InterfaceFlags flags;
//! Returns if interface is active
bool isActive() const { return flags[PIEthernet::ifActive]; }
//! Returns if interface is running
bool isRunning() const { return flags[PIEthernet::ifRunning]; }
//! Returns if interface support broadcast
bool isBroadcast() const { return flags[PIEthernet::ifBroadcast]; }
//! Returns if interface support multicast
bool isMulticast() const { return flags[PIEthernet::ifMulticast]; }
//! Returns if interface is loopback
bool isLoopback() const { return flags[PIEthernet::ifLoopback]; }
//! Returns if interface is point-to-point
bool isPTP() const { return flags[PIEthernet::ifPTP]; }
};
//! Array of \a Interface with some features
class PIP_EXPORT InterfaceList: public PIVector<PIEthernet::Interface> {
public:
InterfaceList(): PIVector<PIEthernet::Interface>() {}
//! Get interface with system index "index" or 0 if there is no one
const Interface * getByIndex(int index) const {
for (int i = 0; i < size_s(); ++i)
if ((*this)[i].index == index) return &((*this)[i]);
return 0;
}
//! Get interface with system name "name" or 0 if there is no one
const Interface * getByName(const PIString & name) const {
for (int i = 0; i < size_s(); ++i)
if ((*this)[i].name == name) return &((*this)[i]);
return 0;
}
//! Get interface with IP address "address" or 0 if there is no one
const Interface * getByAddress(const PIString & address) const {
for (int i = 0; i < size_s(); ++i)
if ((*this)[i].address == address) return &((*this)[i]);
return 0;
}
//! Get loopback interface or 0 if there is no one
const Interface * getLoopback() const {
for (int i = 0; i < size_s(); ++i)
if ((*this)[i].isLoopback()) return &((*this)[i]);
return 0;
}
};
//! Returns all system network interfaces
static InterfaceList interfaces();
static PINetworkAddress interfaceAddress(const PIString & interface_);
//! Returns all system network IP addresses
static PIVector<PINetworkAddress> allAddresses();
static PIString macFromBytes(const PIByteArray & mac);
static PIByteArray macToBytes(const PIString & mac);
static PIString applyMask(const PIString & ip, const PIString & mask);
static PINetworkAddress applyMask(const PINetworkAddress & ip, const PINetworkAddress & mask);
static PIString getBroadcast(const PIString & ip, const PIString & mask);
static PINetworkAddress getBroadcast(const PINetworkAddress & ip, const PINetworkAddress & mask);
//! \events
//! \{
//! \fn void newConnection(PIEthernet * client)
//! \brief Raise on new TCP connection received
//! \fn void connected()
//! \brief Raise if succesfull TCP connection
//! \fn void disconnected(bool withError)
//! \brief Raise if TCP connection was closed
//! \}
//! \ioparams
//! \{
#ifdef DOXYGEN
//! \brief read ip, default ""
string ip;
//! \brief read port, default 0
int port;
//! \brief ethernet parameters
int parameters;
//! \brief read timeout, default 10 s
PISystemTime readTimeout;
//! \brief write timeout, default 10 s
PISystemTime writeTimeout;
//! \brief time-to-live, default 64
int TTL;
//! \brief time-to-live for multicast, default 1
int multicastTTL;
#endif
//! \}
protected:
explicit PIEthernet(int sock, PIString ip_port);
void propertyChanged(const char * name) override;
PIString constructFullPathDevice() const override;
void configureFromFullPathDevice(const PIString & full_path) override;
PIPropertyStorage constructVariantDevice() const override;
void configureFromVariantDevice(const PIPropertyStorage & d) override;
bool configureDevice(const void * e_main, const void * e_parent = 0) override;
ssize_t readDevice(void * read_to, ssize_t max_size) override;
ssize_t writeDevice(const void * data, ssize_t max_size) override;
DeviceInfoFlags deviceInfoFlags() const override;
void applyParameters();
//! Executes when any read function was successful. Default implementation does nothing
virtual void received(const void * data, int size) { ; }
void construct();
bool init();
bool openDevice() override;
bool closeDevice() override;
void closeSocket(int & sd);
void applyTimeouts();
void applyTimeout(int fd, int opt, PISystemTime tm);
void applyOptInt(int level, int opt, int val);
PRIVATE_DECLARATION(PIP_EXPORT)
int sock, sock_s;
std::atomic_bool connected_, connecting_, listen_threaded, server_bounded;
mutable PINetworkAddress addr_r, addr_s, addr_lr;
Type eth_type;
PIThread server_thread_;
mutable PIMutex clients_mutex;
PIVector<PIEthernet *> clients_;
PIQueue<PIString> mcast_queue;
PIStringList mcast_groups;
PIFlags<PIEthernet::Parameters> params;
private:
EVENT_HANDLER1(void, clientDeleted, PIObject *, o);
static void server_func(void * eth);
void setType(Type t, bool reopen = true);
bool connectTCP();
#ifdef WINDOWS
long waitForEvent(PIWaitEvent & event, long mask);
#endif
static int ethErrorCore();
static PIString ethErrorString();
static int ethRecv(int sock, void * buf, int size, int flags = 0);
static int ethRecvfrom(int sock, void * buf, int size, int flags, sockaddr * addr);
static int ethSendto(int sock, const void * buf, int size, int flags, sockaddr * addr, int addr_len);
static void ethClosesocket(int sock, bool shutdown);
static int ethSetsockopt(int sock, int level, int optname, const void * optval, int optlen);
static int ethSetsockoptInt(int sock, int level, int optname, int value = 1);
static int ethSetsockoptBool(int sock, int level, int optname, bool value = true);
static void ethNonblocking(int sock);
static bool ethIsWriteable(int sock);
};
inline bool operator<(const PIEthernet::Interface & v0, const PIEthernet::Interface & v1) {
return (v0.name < v1.name);
}
inline bool operator==(const PIEthernet::Interface & v0, const PIEthernet::Interface & v1) {
return (v0.name == v1.name && v0.address == v1.address && v0.netmask == v1.netmask);
}
inline bool operator!=(const PIEthernet::Interface & v0, const PIEthernet::Interface & v1) {
return (v0.name != v1.name || v0.address != v1.address || v0.netmask != v1.netmask);
}
#endif // PIETHERNET_H