///////////////////////////////////////////////////////////////////////////////// // // tcp.cc is part of TestModbus-Server. // ///////////////////////////////////////////////////////////////////////////////// #include "UDPTCPNetwork.h" #include #if defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__) #else #include /* close() */ #endif #include #include /* memset() */ #include using namespace std; int __tcpinit__ = 0; 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) { return WebGetFile(url, buffer, maxsize, formdata, 20000); } int TCP::WebGetFile (string url, char *buffer, int maxsize, char *formdata, int timeout_ms) { 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, timeout_ms); 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; };