/*
PIP - Platform Independent Primitives
Ethernet, UDP/TCP Broadcast/Multicast
Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#include "piethernet.h"
PIEthernet::PIEthernet(void * data, ReadRetFunc slot): PIIODevice("", ReadWrite) {
piMonitor.ethernets++;
setPriority(piHigh);
type_ = UDP;
ret_data_ = data;
ip_ = ip_s = "";
port_ = port_s = 0;
sock = sock_s = -1;
ret_func_ = slot;
connected_ = false;
params = PIEthernet::ReuseAddress;
server_thread_.setData(this);
setThreadedReadBufferSize(65536);
if (type_ != UDP) init();
}
PIEthernet::PIEthernet(PIEthernet::Type type, void * data, ReadRetFunc slot): PIIODevice("", ReadWrite) {
piMonitor.ethernets++;
setPriority(piHigh);
type_ = type;
ret_data_ = data;
ip_ = ip_s = "";
port_ = port_s = 0;
sock = sock_s = -1;
ret_func_ = slot;
connected_ = false;
params = (type == UDP ? PIEthernet::ReuseAddress : 0);
server_thread_.setData(this);
setThreadedReadBufferSize(65536);
if (type_ != UDP) init();
}
PIEthernet::PIEthernet(int sock_, PIString ip_port): PIIODevice("", ReadWrite) {
piMonitor.ethernets++;
setPriority(piHigh);
type_ = TCP_Client;
path_ = ip_port;
parseAddress(ip_port, &ip_s, &port_s);
sock = sock_;
sock_s = -1;
server_thread_.setData(this);
params = PIEthernet::ReuseAddress;
init_ = opened_ = connected_ = true;
setThreadedReadBufferSize(65536);
}
PIEthernet::~PIEthernet() {
piMonitor.ethernets--;
if (server_thread_.isRunning()) server_thread_.terminate();
closeSocket(sock);
//if (buffer_ != 0) delete buffer_;
//buffer_ = 0;
}
bool PIEthernet::init() {
//cout << "init " << type_ << endl;
closeSocket(sock);
int st = 0, pr = 0;;
#ifdef WINDOWS
int flags = 0;
#else
int so = 1;
#endif
if (type_ == UDP) {
st = SOCK_DGRAM;
pr = IPPROTO_UDP;
} else {
st = SOCK_STREAM;
pr = IPPROTO_TCP;
}
#ifdef WINDOWS
if (type_ == UDP) flags = WSA_FLAG_MULTIPOINT_C_LEAF | WSA_FLAG_MULTIPOINT_D_LEAF;
if (params[ReuseAddress]) flags |= WSA_FLAG_OVERLAPPED;
sock = WSASocket(AF_INET, st, pr, NULL, 0, flags);
#else
sock = socket(AF_INET, st, pr);
#endif
if (sock == -1) {
piCout << "[PIEthernet] Cant`t create socket, " << ethErrorString();
return false;
}
#ifndef WINDOWS
if (params[PIEthernet::ReuseAddress]) setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &so, sizeof(so));
if (params[PIEthernet::Broadcast]) setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &so, sizeof(so));
#endif
//cout << "inited " << sock << ": bc = " << params << endl;
//fcntl(sock, F_SETFL, 0/*O_NONBLOCK*/);
return true;
}
void PIEthernet::parseAddress(const PIString & ipp, PIString * ip, int * port) {
if (ip != 0) *ip = ipp.left(ipp.find(":"));
if (port != 0) *port = ipp.right(ipp.length() - ipp.find(":") - 1).toInt();
}
bool PIEthernet::openDevice() {
if (connected_) return true;
init();
if (sock == -1 || path_.isEmpty()) return false;
parseAddress(path_, &ip_, &port_);
if (type_ != UDP)
return true;
//cout << " bind to " << sock << ": " << (params[PIEthernet::Broadcast] ? "0.0.0.0" : path_) << ": " << port_ << " ..." < 0) received(read_to, rs);
return rs;
//return ::read(sock, read_to, max_size);
default: break;
//return ::read(sock, (char * )read_to, max_size);
}
return -1;
}
int PIEthernet::write(const void * data, int max_size) {
if (sock == -1) init();
if (sock == -1 || !isWriteable()) {
//piCout << "[PIEthernet] Can`t send to uninitialized socket";
return -1;
}
//piCout << "[PIEthernet] sending to " << ip_s << ":" << port_s << " " << max_size << " bytes";
int ret = 0;
switch (type_) {
case TCP_SingleTCP:
memset(&addr_, 0, sizeof(addr_));
addr_.sin_port = htons(port_s);
addr_.sin_addr.s_addr = inet_addr(ip_s.data());
addr_.sin_family = AF_INET;
#ifdef QNX
addr_.sin_len = sizeof(addr_);
#endif
//piCout << "connect SingleTCP" << ip_s << ":" << port_s << "...";
if (::connect(sock, (sockaddr * )&addr_, sizeof(addr_)) != 0) {
piCout << "[PIEthernet] Cant`t connect to " << ip_s << ":" << port_s << ", " << ethErrorString();
return -1;
}
//piCout << "ok, write SingleTCP" << int(data) << max_size << "bytes ...";
ret = ::send(sock, (const char *)data, max_size, 0);
//piCout << "ok, ret" << ret;
closeSocket(sock);
init();
return ret;
case UDP:
saddr_.sin_port = htons(port_s);
if (params[PIEthernet::Broadcast]) saddr_.sin_addr.s_addr = INADDR_BROADCAST;
else saddr_.sin_addr.s_addr = inet_addr(ip_s.data());
saddr_.sin_family = AF_INET;
#ifdef WINDOWS
return sendto(sock, (const char * )data, max_size, 0, (sockaddr * )&saddr_, sizeof(saddr_));
#else
return sendto(sock, data, max_size, 0, (sockaddr * )&saddr_, sizeof(saddr_));
#endif
case TCP_Client:
return ::send(sock, (const char *)data, max_size, 0);
default: break;
//return ::read(sock, read_to, max_size);
}
return -1;
}
void PIEthernet::server_func(void * eth) {
PIEthernet * ce = (PIEthernet * )eth;
sockaddr_in client_addr;
socklen_t slen = sizeof(client_addr);
int s = accept(ce->sock, (sockaddr * )&client_addr, &slen);
if (s == -1) {
piCout << "[PIEthernet] Cant`t accept new connection, " << ethErrorString();
return;
}
PIString ip(inet_ntoa(client_addr.sin_addr));
ip += ":" + PIString::fromNumber(htons(client_addr.sin_port));
ce->clients_ << new PIEthernet(s, ip);
ce->newConnection(ce->clients_.back());
//cout << "connected " << ip << endl;
//char d[256];
//cout << " recv " << recv(s, d, 256, 0) << endl;
//cout << recv(ce->clients_.back()->sock, d, 256, 0) << endl;
}
PIStringList PIEthernet::interfaces() {
#ifdef WINDOWS
piCout << "[PIEthernet] Not implemented on Windows, use \"PIEthernet::allAddresses\" instead";
return PIStringList();
#else
# ifdef QNX
PIStringList il, sl;
/*struct if_nameindex * ni = if_nameindex();
for (int i = 0; ; ++i) {
if (ni[i].if_name == 0 || ni[i].if_index == 0) break;
sl << PIString(ni[i].if_name);
}
if_freenameindex(ni);*/
PIProcess proc;
proc.setGrabOutput(true);
proc.exec(ifconfigPath.c_str(), "-l");
if (!proc.waitForFinish(1000)) return sl;
PIString out(proc.readOutput());
il = out.split(" ");
il.removeAll("");
piForeachC (PIString & i, il) {
proc.exec(ifconfigPath.c_str(), i);
if (!proc.waitForFinish(1000)) return il;
sl << i.trimmed();
out = proc.readOutput();
int al = out.length();
al = (al - out.replaceAll("alias", "").length()) / 5;
for (int j = 0; j < al; ++j)
sl << i.trimmed() + ":" + PIString::fromNumber(j);
}
//cout << out;
//cout << sl << endl;
return sl;
# else
PIStringList sl;
/*struct if_nameindex * ni = if_nameindex();
for (int i = 0; ; ++i) {
if (ni[i].if_name == 0 || ni[i].if_index == 0) break;
sl << PIString(ni[i].if_name);
}
if_freenameindex(ni);*/
PIProcess proc;
proc.setGrabOutput(true);
proc.exec(ifconfigPath.c_str(), "-s");
if (!proc.waitForFinish(1000)) return sl;
PIString out(proc.readOutput());
//cout << out << endl;
out.cutLeft(out.find('\n') + 1);
while (!out.isEmpty()) {
sl << out.left(out.find(' '));
out.cutLeft(out.find('\n') + 1);
}
//cout << sl << endl;
return sl;
# endif
#endif
}
PIString PIEthernet::interfaceAddress(const PIString & interface_) {
#ifdef WINDOWS
piCout << "[PIEthernet] Not implemented on Windows, use \"PIEthernet::allAddresses\" instead";
return PIString();
#else
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
strcpy(ifr.ifr_name, interface_.data());
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 PIString(inet_ntoa(sa->sin_addr));
#endif
}
PIStringList PIEthernet::allAddresses() {
#ifdef WINDOWS
PIStringList al;
PIString ca;
PIP_ADAPTER_INFO pAdapterInfo, pAdapter = 0;
int ret = 0;
ulong ulOutBufLen = sizeof(IP_ADAPTER_INFO);
pAdapterInfo = (IP_ADAPTER_INFO * ) HeapAlloc(GetProcessHeap(), 0, (sizeof (IP_ADAPTER_INFO)));
if (pAdapterInfo == 0) {
piCout << "[PIEthernet] Error allocating memory needed to call GetAdaptersinfo";
return PIStringList();
}
if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) == ERROR_BUFFER_OVERFLOW) {
HeapFree(GetProcessHeap(), 0, (pAdapterInfo));
pAdapterInfo = (IP_ADAPTER_INFO *) HeapAlloc(GetProcessHeap(), 0, (ulOutBufLen));
if (pAdapterInfo == 0) {
piCout << "[PIEthernet] Error allocating memory needed to call GetAdaptersinfo";
return PIStringList();
}
}
if ((ret = GetAdaptersInfo(pAdapterInfo, &ulOutBufLen)) == NO_ERROR) {
pAdapter = pAdapterInfo;
while (pAdapter) {
/*if (pAdapter->Type != MIB_IF_TYPE_ETHERNET && pAdapter->Type != MIB_IF_TYPE_LOOPBACK) {
pAdapter = pAdapter->Next;
continue;
}*/
ca = PIString(pAdapter->IpAddressList.IpAddress.String);
if (ca != "0.0.0.0") al << ca;
pAdapter = pAdapter->Next;
}
} else
piCout << "[PIEthernet] GetAdaptersInfo failed with error: " << ret;
if (pAdapterInfo)
HeapFree(GetProcessHeap(), 0, (pAdapterInfo));
return al;
#else
PIStringList il = interfaces(), al;
piForeachC (PIString & i, il)
al << interfaceAddress(i);
return al.removeStrings("0.0.0.0");
#endif
}