You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

680 lines
15 KiB

/////////////////////////////////////////////////////////////////////////////////
//
// tcp.cc is part of TestModbus-Server.
//
/////////////////////////////////////////////////////////////////////////////////
#include "tcp.h"
#include <stdio.h>
#if defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__)
#else
#include <unistd.h> /* close() */
#endif
#include <stdlib.h>
#include <string.h> /* memset() */
#include <errno.h>
using namespace std;
int tcpinit;
//
// convert host and port to sockaddr_in6
//
int dns_filladdr (string host, string port, int ai_family, struct sockaddr_storage *sAddr) {
struct addrinfo hints, *res;
int err;
bzero (&hints, sizeof (struct addrinfo));
hints.ai_family = ai_family;
hints.ai_socktype = SOCK_DGRAM;
if ((err = getaddrinfo (host.c_str(), port.c_str(), &hints, &res)) < 0) {
fprintf (stdout, "dns_filladdr (getaddrinfo):%s\n", gai_strerror (err));
return -1;
}
memcpy (sAddr, res->ai_addr, res->ai_addrlen);
freeaddrinfo (res);
return 1;
};
//
// convert int to char*
//
char* itoa(char* buffer, int number, int size) {
snprintf (buffer, size, "%d", number);
return buffer;
}
TCP::~TCP() {
Close();
}
TCP::TCP() {
if (tcpinit == 0) {
#ifdef BUILD_WINDOWS
WORD wVersionRequested;
WSADATA wsaData;
int err;
/* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) {
/* Tell the user that we could not find a usable */
/* Winsock DLL. */
printf("WSAStartup failed with error: %d\n", err);
return;
}
#endif
tcpinit = 1;
}
sock = 0;
writecnt = 0;
readcnt = 0;
islisten = 0;
};
TCP::TCP(int s) {
TCP();
sock = s;
// memset (&localaddr, 0x0, sizeof(localaddr));
// memset (&remoteaddr, 0x0, sizeof(remoteaddr));
writecnt = 0;
readcnt = 0;
islisten = 0;
};
TCP::TCP(string h, string p) {
TCP();
Connect (h,p);
};
int TCP::Listen(int port) {
char buffer[NET_BUFFERSIZE];
int err, i;
struct addrinfo hints, *res, *rp;
if (sock > 0) Close();
// FIXME: solution to get both (IPV4 and IPV6) to work at the same time?
bzero (&hints, sizeof (struct addrinfo));
hints.ai_flags = AI_PASSIVE;
#if defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__)
hints.ai_family = AF_INET;
#else
hints.ai_family = AF_INET6;
#endif
hints.ai_socktype = SOCK_STREAM;
if ((err = getaddrinfo (NULL, itoa(buffer, port, 32), &hints, &res)) != 0) {
return 0;
}
//
// walk through all results until we could connect
//
for (rp = res; rp != NULL; rp = rp->ai_next) {
sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (sock == -1) continue;
i = 1;
if ((err = setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, (char*)&i, sizeof (i))) != 0) {
printf ("%s:%d setsockopt error\n", __FILE__, __LINE__);
}
if ((err = bind (sock, rp->ai_addr, rp->ai_addrlen)) < 0) {
close (sock);
sock = -1;
continue;
}
if (listen (sock, 8) < 0) { // maximum of 8 connections at the time
close (sock);
sock = -1;
continue;
}
break;
}
freeaddrinfo (res);
if (rp == NULL) {
sock = -1;
return 0;
}
islisten = 1;
return 1;
};
TCP* TCP::Accept() {
fd_set rfds;
struct timeval tv;
int retval;
SOCKET newsock;
struct sockaddr_storage cliaddr;
socklen_t cliaddr_len = sizeof(struct sockaddr_storage);
TCP *tcp = NULL;
if (sock <= 0) return NULL;
FD_ZERO(&rfds);
FD_SET(sock, &rfds);
tv.tv_sec = 0;
tv.tv_usec = 1000;
retval = select (sock+1, &rfds, NULL, NULL, &tv);
if (retval == -1 && errno == EINTR) {
retval = 0;
}
else if (retval == -1) {
return NULL;
}
else if (retval) {
#if defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__)
newsock = accept (sock, (struct sockaddr *) &cliaddr, (int*) &cliaddr_len);
#else
newsock = accept (sock, (struct sockaddr *) &cliaddr, &cliaddr_len);
#endif
if (newsock < 0) return NULL;
tcp = new TCP();
tcp->SetSocket(newsock, &cliaddr, cliaddr_len);
return tcp;
}
return NULL;
}
int TCP::Connect(string h, string p) {
remote_host = h;
remote_port = p;
return Connect();
};
int TCP::Connect(string hostport, int defaultport) {
char buffer[32];
int pos = hostport.rfind(':', hostport.length());
if (pos == -1) {
remote_port = itoa (buffer, defaultport, 32);
remote_host = hostport;
}
else {
remote_port = hostport.substr (pos+1, hostport.length() - pos);
remote_host = hostport.substr (0, pos);
}
return Connect();
};
int TCP::Connect() {
int err, s = 0;
struct addrinfo hints, *res, *rp;
struct timeval timeout;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC; // use IPv4 or IPv6
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = 0;
hints.ai_protocol = 0;
err = getaddrinfo(remote_host.c_str(), remote_port.c_str(), &hints, &res);
if (err != 0) {
fprintf(stderr, "getaddrinfo: Host:'%s' Port:'%s' Error:%s\n", remote_host.c_str(), remote_port.c_str(), gai_strerror(err));
return 0;
}
//
// walk through all results until we could connect
//
for (rp = res; rp != NULL; rp = rp->ai_next) {
s = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
// setup timeout to max 2 secs
timeout.tv_sec = 2;
timeout.tv_usec = 0;
setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeout, sizeof(timeout));
if (s == -1) continue;
if (connect(s, rp->ai_addr, rp->ai_addrlen) != -1) {
sock = s;
break;
}
close(s);
}
freeaddrinfo(res);
//
// connect not successfull
//
if (rp == NULL) return 0;
writecnt = 0;
readcnt = 0;
return 1;
};
long int TCP::ReadPop (char *buffer, long int pktlen, long int bufferlen) {
memmove (buffer, buffer+pktlen, bufferlen-pktlen);
return bufferlen-pktlen;
}
long int TCP::Read(char *buffer, long int len) {
long int len_ = len;
if (sock <= 0) return -2;
len_ = recv (sock, buffer, len, 0);
if (len_ < 0 && errno == EAGAIN) len_ = 0;
else if (len_ < 0 && errno != EAGAIN) {
#ifdef BUILD_WINDOWS
printf ("%s ERROR:%s\n", __FUNCTION__, strerror (errno));
#else
fprintf (stderr, "%s ERROR:%s\n", __FUNCTION__, strerror (errno));
#endif
len_ = -2;
}
else if (len_ == 0) len_ = -1;
if (len_ < 0) Close();
readcnt += len_;
return len_;
};
long int TCP::ReadTimeout(char *buffer, long int len, int timeout) {
int data = IsData (timeout);
if (data > 0) return Read (buffer, len);
else if (data < 0) return -1;
else return 0;
};
//////////////////////////////////////////////////////////
//
// write data, generate no signal if some error occures
long int TCP::Write(char *buffer, long int len) {
int i;
int to = NET_MAX_RETRY;
if (sock <= 0) return -1;
do {
i = send (sock, buffer, len, MSG_NOSIGNAL);
} while (i == -1 && (to--) > 0 && errno == EINTR);
if (i < 0) Close ();
writecnt += i;
return i;
};
void TCP::Close() {
#ifdef BUILD_WINDOWS
closesocket(sock);
#else
if (sock > 0) close (sock);
#endif
sock = -1;
islisten = false;
};
void TCP::SetSocket(SOCKET s, struct sockaddr_storage *saddr, socklen_t saddrlen) {
char host[NET_HOSTLEN];
char port[NET_PORTLEN];
int err;
if (sock > 0) Close();
if (s > 0) {
sock = s;
if (saddr != NULL) {
memcpy (&peeraddr, saddr, sizeof(peeraddr));
if ((err = getnameinfo ((struct sockaddr*) saddr, saddrlen, host, NET_HOSTLEN,
port, NET_PORTLEN, NI_NUMERICHOST | NI_NUMERICSERV)) == 0) {
remote_host = host;
remote_port = port;
} else {
printf ("error: getnameinfo");
/* if (err == EAI_AGAIN) printf ("EAI_AGAIN\n");
if (err == EAI_BADFLAGS) printf ("EAI_BADFLAGS\n");
if (err == EAI_FAIL) printf ("EAI_FAIL\n");
if (err == EAI_FAMILY) printf ("EAI_FAMILY\n");
if (err == EAI_MEMORY) printf ("EAI_MEMORY\n");
if (err == EAI_NONAME) printf ("EAI_NONAME\n");
if (err == EAI_OVERFLOW) printf ("EAI_OVERFLOW\n");
if (err == EAI_SYSTEM) printf ("EAI_SYSTEM\n"); */ // windows seem to have different error codes
}
}
}
else sock = -1;
};
int TCP::IsConnected() {
return (sock > 0);
};
int TCP::IsData(int timeout) {
fd_set sockset;
struct timeval tval;
if (sock <= 0) return -1;
FD_ZERO (&sockset);
FD_SET (sock, &sockset);
tval.tv_sec = timeout / 1000;
tval.tv_usec = (timeout % 1000);
if ((select (sock + 1, &sockset, NULL, NULL, &tval)) != -1) {
if (FD_ISSET (sock, &sockset)) return 1;
return 0;
}
else {
if (errno == EBADF) sock = -1;
}
return 0;
};
/*
* Read a single line and split in parameter and value
* if value and parm is set to NULL this field will not be set
*/
int TCP::WebHeaderGetParamValue(std::string line, std::string *parm, std::string *value) {
size_t pos = line.find(" ");
if (pos == std::string::npos) {
fprintf (stderr, "%s error with protocol. HTTP/ without space? Line:'%s'\n",
__FUNCTION__, line.c_str());
return -1;
}
if (parm != NULL) *parm = line.substr (0, pos);
if (value != NULL) *value = line.substr (pos+1, std::string::npos);
return 0;
}
/*
* read any data from server
* Return Value:
* on error: -1
* success : size of buffer
*/
int TCP::WebGetFile (string url, char *buffer, int maxsize, char *formdata) {
char outdata[NET_BUFFERSIZE];
char indata[NET_BUFFERSIZE];
string host, port, file;
int len, line_cnt, i, buffpos = 0, c;
string protocol = "";
string type = "";
string line = "";
string lineUP = "";
string status = "";
int chunked_transfer = 0;
int chunked_bytes = 0;
//
// clear input buffer
if (buffer == NULL) return -1;
buffer[0] = '\0';
WebGetURLElements(url, &host, &port, &file);
if (!IsConnected()) {
Connect (host, port);
}
if (!IsConnected()) return -1;
//
// send http request
//
if (formdata == NULL)
snprintf (outdata, NET_BUFFERSIZE, "GET %s HTTP/1.0\r\nUser-Agent: unknown\r\nHost: %s\r\nAccept:*.*\r\n\r\n", file.c_str(), host.c_str());
else
snprintf (outdata, NET_BUFFERSIZE, "POST %s HTTP/1.1\r\nUser-Agent: unknown\r\nHost: %s\r\nContent-Length: %ld\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept:*.*\r\n\r\n%s\r\n",
file.c_str(), host.c_str(), strlen (formdata), formdata);
Write (outdata, strlen (outdata));
//
// read header
//
indata[0] = '\0';
len = ReadTimeout (indata, NET_BUFFERSIZE-1, 2000);
if (len <= 0) {
fprintf (stderr, "%s:%d reading header Error:%s\n", __FILE__, __LINE__, strerror(errno));
return -1;
}
indata[len] = 0;
//
// read all lines until an empty line is reached
for (line = "", lineUP = "", line_cnt = 0, i = 0; i < len; i++) {
if (indata[i] == '\n' || indata[i] == '\r') {
if (i < len && (indata[i+1] == '\n' || indata[i+1] == '\r')) i++;
if (line.length() == 0) {
i++;
break; // break loop, read data
}
//
// HTTP Header
else if (lineUP.find ("HTTP/") == 0) {
if (WebHeaderGetParamValue(line, &protocol, &status) < 0) {
fprintf (stderr, "%s:%d reading header Error:%s\n", __FILE__, __LINE__, strerror(errno));
return -1;
}
}
//
// Transfer Encoding
else if (lineUP.find ("TRANSFER-ENCODING:") == 0) {
std::string temp;
if (WebHeaderGetParamValue(line, NULL, &temp) < 0) {
fprintf (stderr, "%s:%d reading header Error:%s\n", __FILE__, __LINE__, strerror(errno));
return -1;
}
for (int j = 0; (long int)j < (long int) temp.length(); j++)
temp[i] = toupper(temp[i]);
if (temp.find("chunked") != std::string::npos) chunked_transfer = 1;
}
//
// type of file
else if (lineUP.find ("CONTENT-TYPE:") == 0) {
if (WebHeaderGetParamValue(line, NULL, &type) < 0) {
fprintf (stderr, "%s:%d reading header Error:%s\n", __FILE__, __LINE__, strerror(errno));
return -1;
}
}
lineUP = line = "";
line_cnt++;
}
else {
line += indata[i];
lineUP += toupper(indata[i]);
}
}
//
// chunked data?
// size we need to load
if (chunked_transfer) {
int v;
for (v = 0, line_cnt = 0; i < len; i++) {
if (indata[i] == '\r' || indata[i] == '\n') {
i++;
if (i < len && (indata[i] == '\n' || indata[i] == '\r')) i++;
chunked_bytes = v;
break;
}
else {
v = v << 4;
indata[i] = tolower(indata[i]);
if (indata[i] >= '0' && indata[i] <= '9') v += (indata[i] - '0');
else if (indata[i] >= 'a' && indata[i] <= 'f') v += (10 + indata[i] - 'a');
else {
fprintf (stderr, "%s:%d reading chunk bytes, invalid HEX code indata:'%s'\n", __FILE__, __LINE__, indata);
errno = EINVAL;
return -1;
}
}
}
//
// check bytes to read, and remove these from chunked_bytes
if (chunked_bytes < len-i) c = chunked_bytes;
else c = len-i;
chunked_bytes -= c;
}
else c = len-i;
//
// read data, but first copy the left over bytes into the output buffer
if (c > 0) memcpy (buffer, indata+i, c);
buffpos = c;
while ((len = ReadTimeout (indata, NET_BUFFERSIZE-1, 2000)) > 0) {
i = len;
if (i > maxsize-buffpos) i = maxsize-buffpos;
if (i > 0) {
if (chunked_transfer) {
if (i > chunked_bytes) i = chunked_bytes;
chunked_bytes -= i;
}
memcpy (buffer+buffpos, indata, i);
buffpos += i;
}
}
return buffpos;
};
// FIXME: everything below here is wrong...
/*
* fills the elements and returns 0 on success. Returns set bits if value was found
* 1 ... hostname
* 2 ... port
* 3 ... file
*/
enum _webgeturl_step_ {
_WEBGET_URL_PROTO = 0,
_WEBGET_URL_SERVER,
_WEBGET_URL_PORT,
_WEBGET_URL_FILE
};
int TCP::WebGetURLElements (string url, string *host, string *port, string *file) {
string h = "";
string p = "";
string f = "/";
const string prefix1 = "HTTP://";
const string prefix2 = "HTTPS://";
int retval = 0;
int i;
int curpos = 0;
int step = _WEBGET_URL_PROTO;
for (i = 0; i < 5; i++) url[i] = toupper(url[i]);
//
// try to find the protocol and //
if (url.find(prefix1) == 0) {
retval |= 2;
p = "80";
curpos = prefix1.length();
}
else if (url.find(prefix2) == 0) {
retval |= 2;
p = "443";
curpos = prefix2.length();
// FIXME: SSL needs to be implemented
}
else {
if (url.find ("//") != std::string::npos) {
errno = EPROTONOSUPPORT;
return -1;
}
else curpos = 0;
}
for (step = _WEBGET_URL_SERVER;curpos < (int)url.length(); curpos++) {
if (step == _WEBGET_URL_SERVER) {
if (url[curpos] == '/') step = _WEBGET_URL_FILE;
else if (url[curpos] == ':') {
step = _WEBGET_URL_PORT;
p = "";
}
else {
h += url[curpos];
retval |= 1;
}
}
else if (step == _WEBGET_URL_PORT) {
if (url[curpos] == '/') step = _WEBGET_URL_FILE;
else {
p += url[curpos];
retval |= 2;
}
}
else if (step == _WEBGET_URL_FILE) {
f += url[curpos];
retval |= 3;
}
}
if (host != NULL) *host = h;
if (file != NULL) *file = f;
if (port != NULL) *port = p;
return retval;
};
const string TCP::GetRemoteAddr() {
string ret = "";
socklen_t addrlen = sizeof (peeraddr);
char host[256] = "";
char port[256] = "";
if (getnameinfo ((struct sockaddr*)&peeraddr, addrlen, host, 255, port, 255, NI_NUMERICHOST | NI_NUMERICSERV) != 0) {
printf ("getnameinfo error: %s\n", strerror(errno));
}
ret = (string)host + ":" + (string)port;
return ret;
};
const string TCP::GetLocalAddr() {
string ret;
return ret;
};