// // Copyright (C) 2006 Ben Greear // http://www.candelatech.com // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU Library General Public License // as published by the Free Software Foundation; either version 2 // 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 Library General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define VLOG_INF(a) a #define VLOG_TRC(a) a #define VLOG_WRN(a) a #define VLOG_DBG(a) a #define VLOG_ERR(a) a #define VLOG_DIS(a) a #define TRUE 1 #define VLOG cout #define mudlog cout #define LFSTRERROR strerror(errno) #define uint32 unsigned long #define uint64 unsigned long long int do_shutdown = 0; using namespace std; /** Returns opinter to static memory, do NOT delete or modify result. */ const char* toStringIp(uint32 ip) { static char ipb[50]; sprintf(ipb, "%lu.%lu.%lu.%lu", ((ip >> 24) & 0xFF), ((ip >> 16) & 0xFF), ((ip >> 8) & 0xFF), (ip & 0xFF)); return ipb; }//toStringIp int reuseaddr_socket(int s) { int opt = 1; if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*)(&opt), sizeof(opt)) < 0) { VLOG_WRN(VLOG << "ERROR: setsockopt: " << LFSTRERROR << endl); return -1; }//if return 0; } void nonblock(int s, const char* msg) { int flags; //VLOG_TRC(VLOG << "in Linux nonblock, msg: " << msg << endl); flags = fcntl(s, F_GETFL); flags |= (O_NONBLOCK | FD_CLOEXEC); if (fcntl(s, F_SETFL, flags) < 0) { VLOG_ERR(VLOG << "ERROR: fcntl, executing nonblock/close_exec: " << LFSTRERROR << endl); } }//nonblock int bind_socket(int s, uint32 ip_addr, int ip_port, const char* dev_to_bind_to) { struct sockaddr_in my_ip_addr; if (dev_to_bind_to && strlen(dev_to_bind_to)) { // Bind to specific device. if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, dev_to_bind_to, strlen(dev_to_bind_to))) { VLOG_ERR(VLOG << "ERROR: tcp-connect, setsockopt (BINDTODEVICE): " << LFSTRERROR << " Not fatal in most cases..continuing...\n"); } }//if if (ip_addr || ip_port) { memset(&my_ip_addr, 0, sizeof(my_ip_addr)); my_ip_addr.sin_family = AF_INET; my_ip_addr.sin_addr.s_addr = htonl(ip_addr); my_ip_addr.sin_port = htons(ip_port); int r; //retval r = bind(s, (struct sockaddr*)(&my_ip_addr), sizeof(my_ip_addr)); if (r < 0) { //system("netstat -an"); VLOG_ERR(VLOG << "ERROR: tcp bind: " << LFSTRERROR << " IP: " << toStringIp(ip_addr) << " ipPort: " << ip_port << endl); return r; } else { VLOG_INF(VLOG << "Successfully bound to IP: " << toStringIp(ip_addr) << " port: " << ip_port << endl); } } return 0; } int createTcpSocket(unsigned int ip_addr, int ip_port, const char* dev_to_bind_to) { //VLOG_DBG(VLOG << "ip_addr -:" << ip_addr << ": ip_port: " << ip_port << endl); int s = socket(AF_INET, SOCK_STREAM, 0); VLOG_INF(VLOG << "createTcpSocket: ip_addr -:" << ip_addr << ": " << toStringIp(ip_addr) << ": ip_port: " << ip_port << " socket: " << s << endl); if (s < 0) { cerr << "ERROR: tcp socket: " << LFSTRERROR << endl; VLOG << "ERROR: tcp socket: " << LFSTRERROR << endl; return s; } reuseaddr_socket(s); if (bind_socket(s, ip_addr, ip_port, dev_to_bind_to) < 0) { close(s); s = -1; } else { nonblock(s, "create tcp socket"); } return s; } /** Convert to miliseconds */ uint64 tv_to_ms(const struct timeval& tv) { uint64 ms = tv.tv_usec / 1000; ms += (uint64)tv.tv_sec * (uint64)1000; return ms; } uint64 getCurMs() { struct timeval tv; gettimeofday(&tv, NULL); return tv_to_ms(tv); } int parseInt(const char* s) { //should parse HEX, Octal, and Decimal. If not decimal, must start with 0x // Use long-long because then we can get a real value for a uint32, even if // it is negative. Code using this can cast it back to un-signed int if // that makes sense. long long rv = strtoll(s, NULL, 0); return rv; } int toIpString(const char* ip, uint32& retval) { retval = 0; if (strcmp(ip, "0.0.0.0") == 0) { return 0; } if (ip[0] == 0) { return 0; } uint64 now = getCurMs(); // Debugging only #if 0 struct addrinfo aiHints; struct addrinfo *aiList = NULL; // It seems it can't resolve IP addresses, or tries too hard to find a name, // or something. Need to check for a.b.c.d retval = inet_addr(ip); if (retval != INADDR_NONE) { retval = ntohl(retval); return 0; } // Otherwise, try to resolve it below. //-------------------------------- // Setup the hints address info structure // which is passed to the getaddrinfo() function memset(&aiHints, 0, sizeof(aiHints)); aiHints.ai_family = AF_INET; aiHints.ai_socktype = SOCK_STREAM; aiHints.ai_protocol = IPPROTO_TCP; //-------------------------------- // Call getaddrinfo(). If the call succeeds, // the aiList variable will hold a linked list // of addrinfo structures containing response // information about the host if ((retval = getaddrinfo(ip, NULL, &aiHints, &aiList)) != 0) { VLOG << "ERROR: getaddrinfo failed, error: " << LFSTRERROR << endl; return retval; } uint64 n2 = getCurMs(); if (n2 - now > 10) { VLOG << "WARNING: Took " << n2 - now << "ms to gethostbyname for host/ip -:" << ip << ":-\n"; } if (aiList && aiList->ai_addr) { struct sockaddr_in* sin = (struct sockaddr_in*)(aiList->ai_addr); retval = ntohl(sin->sin_addr.s_addr); freeaddrinfo(aiList); return retval; } return -1; #endif // It seems it can't resolve IP addresses, or tries too hard to find a name, // or something. Need to check for a.b.c.d retval = inet_addr(ip); if (retval != INADDR_NONE) { retval = ntohl(retval); return 0; } // Otherwise, try to resolve it below. struct hostent *hp; hp = gethostbyname(ip); uint64 n2 = getCurMs(); if (n2 - now > 10) { VLOG << "WARNING: Took " << n2 - now << "ms to gethostbyname for host/ip -:" << ip << ":-\n"; } if (hp == NULL) { return -1; }//if else { retval = ntohl(*((unsigned int*)(hp->h_addr_list[0]))); if (retval != 0) { return 0; } else { return -1; } } return -1; } /** Convert from mili-seconds to struct timeval. */ struct timeval ms_to_tv(uint64 ms) { struct timeval retval; retval.tv_usec = (ms % 1000) * 1000; retval.tv_sec = ms / 1000; return retval; } int new_connection(int s) { struct sockaddr_in isa; socklen_t i; int t; VLOG_TRC(VLOG << "in new_connection, s: " << s << endl); i = sizeof(isa); if (getsockname(s, (struct sockaddr *) & isa, &i) < 0) { VLOG_ERR(VLOG << "ERROR: new_connection, getsockname: " << LFSTRERROR << endl); return (-1); }//if i = sizeof(isa); if ((t = accept(s, (struct sockaddr *)(&isa), &i)) < 0) { VLOG_ERR(VLOG << "ERROR: accept: " << LFSTRERROR << endl); return(-1); }//if else { VLOG_INF(VLOG << "Accepted new socket: " << t << endl); } nonblock(t, "new_connection"); return(t); }//new_connection() //hopefully trap the TERM signal and exit gracefully void sig_handler(int signo) { //reestablish signal handler cout << "Signal: " << signo << " received...\n"; if ((signo != SIGSEGV) && (signo != SIGBUS) && (signo != SIGFPE)) { if (signo != 17) { VLOG_INF(cerr << "Got signal: " << signo << " NOT setting to default handler." << endl); } signal(signo, (&sig_handler)); } else { VLOG_DIS(cerr << "Got signal: " << signo << " setting to default handler." << endl); signal(signo, SIG_DFL); } if (signo == SIGTERM) { mudlog << "Got SIGTERM, shutting down.\n"; mudlog << flush; do_shutdown = 1; } else if (signo == SIGALRM) { mudlog << "Got SIGALRM, ignoring!!\n"; mudlog << flush; } else if (signo == SIGQUIT) { mudlog << "Got SIGQUIT, stopping!!\n"; mudlog << flush; do_shutdown = 1; } else if (signo == SIGHUP) { mudlog << "Got SIGHUP, ignoring!!\n"; mudlog << flush; } else if (signo == SIGIO) { mudlog << "Got SIGIO, ignoring!!\n"; mudlog << flush; } else if (signo == SIGPIPE) { mudlog << "Got SIGPIPE, ignoring!!\n"; mudlog << flush; } // else if (signo == SIGPOLL) { // if (mudlog.ofLevel(DIS)) // mudlog << "Got SIGPOLL, ignoring!!\n"; // mudlog << flush; // } else if (signo == SIGSTOP) { mudlog << "Got SIGSTOP, shutting down.."; mudlog << flush; do_shutdown = 1; } else if (signo == SIGINT) { mudlog << "Got SIGINT, shutting down.."; mudlog << flush; do_shutdown = 1; } else if (signo == SIGTTIN) { mudlog << "Got SIGTTIN, ignoring!!\n"; mudlog << flush; } else if (signo == SIGTTOU) { mudlog << "Got SIGTTOU, ignoring!!\n"; mudlog << flush; } else if ((signo == SIGUSR1) || (signo == SIGUSR2)) { mudlog << "Got SIGUSR1 or SIGUSR2, ignoring!!\n"; mudlog << flush; } else if (signo == SIGVTALRM) { mudlog << "Got SIGVTALRM, ignoring!!\n"; mudlog << flush; } else if (signo == SIGXCPU) { mudlog << "Got SIGXCPU, shutting down..!!\n"; mudlog << flush; do_shutdown = TRUE; } else if (signo == SIGXFSZ) { mudlog << "Got SIGXFSZ, shutting down..!!\n"; mudlog << flush; do_shutdown = TRUE; } else if (signo == SIGCHLD) { VLOG_DBG( VLOG << "WARNING: Got SIGCHILD..!!\n"); waitpid(-1, NULL, WNOHANG); //Allow the system to clean up child process' resources } else { mudlog << "Got unknown SIGNAL: " << signo << " shutting down..." << endl; mudlog << flush; do_shutdown = TRUE; } }//sig_handler void showHelp() { cout << "Usage: bentcp -I [ -p local-port ] [ -P dest-port ]\n" << " [ -d local-netdev ] [ -i local_ip ] [ -r read_size ] [ -w write_size ]\n" << endl; } int main(int argc, char** argv) { int listen_fd = -1; int session_fd = -1; int r; uint64 total_read = 0; uint64 total_written = 0; int write_size = 512000; int read_size = 512000; int local_port = 6006; uint32 local_ip = 0; char* local_dev = ""; int dest_port = 6006; char* dest_ip = "0.0.0.0"; signal(SIGABRT, (&sig_handler)); signal(SIGALRM, (&sig_handler)); //signal(SIGEMT, (&sig_handler)); signal(SIGFPE, (&sig_handler)); signal(SIGHUP, (&sig_handler)); //signal(SIGILL, (&sig_handler)); signal(SIGINT, (&sig_handler)); signal(SIGIO, (&sig_handler)); //signal(SIGIOT, (&sig_handler)); signal(SIGPIPE, (&sig_handler)); //signal(SIGPOLL, (&sig_handler)); //signal(SIGPROF, (&sig_handler)); signal(SIGQUIT, (&sig_handler)); signal(SIGSTOP, (&sig_handler)); //signal(SIGSYS, (&sig_handler)); signal(SIGTERM, (&sig_handler)); // This is like: ctrl-z, no need to catch it. //signal(SIGTRAP, (&sig_handler)); //signal(SIGTSTP, (&sig_handler)); signal(SIGTTIN, (&sig_handler)); signal(SIGTTOU, (&sig_handler)); signal(SIGUSR1, (&sig_handler)); signal(SIGUSR2, (&sig_handler)); signal(SIGVTALRM, (&sig_handler)); signal(SIGXCPU, (&sig_handler)); signal(SIGXFSZ, (&sig_handler)); signal(SIGCHLD, (&sig_handler)); for (int i = 1; i= 0) { FD_SET(session_fd, &input_set); FD_SET(session_fd, &output_set); FD_SET(session_fd, &exc_set); maxdesc = max(session_fd, maxdesc); } timeout_tv = ms_to_tv(500); if (select(maxdesc + 1, &input_set, &output_set, &exc_set, &timeout_tv) < 0) { VLOG << "ERROR: select failed: " << strerror(errno) << endl; goto done; } /* New connection? */ if (FD_ISSET(listen_fd, &input_set)) { if (session_fd >= 0) { close(session_fd); } session_fd = new_connection(listen_fd); if (session_fd >= 0) { VLOG << "Accepted new connection...\n"; // Bind locally if (bind_socket(session_fd, local_ip, 0, local_dev) < 0) { close(session_fd); session_fd = -1; } else { started_at = getCurMs(); conn_state = CNS_CONNECTED; } } } if (conn_state == CNS_CONNECTED && (session_fd >= 0)) { // Try to read some large gob of data while (1) { r = read(session_fd, buf, read_size); if (r < 0) { if (errno != EAGAIN) { VLOG << "ERROR on read: " << strerror(errno) << endl; goto done; } else { break; } } else if (r == 0) { VLOG << "Read zero...connection closed.\n"; goto done; } else { total_read += r; } } // Try to write some large gob of data while (1) { r = write(session_fd, buf, write_size); if (r < 0) { if (errno != EAGAIN) { VLOG << "ERROR on write: " << strerror(errno) << endl; goto done; } else { break; } } else if (r == 0) { VLOG << "Wrote zero...connection closed.\n"; goto done; } else { total_written += r; } } } else { if (strcmp(dest_ip, "0.0.0.0") == 0) { // Nevermind continue; } // Try to connect to the peer (asynchronously) if (session_fd < 0 ) { // have not successfully connected, or a connection has dropped // try to reconnect. if ((session_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { VLOG_WRN(VLOG << "WARNING: could not create new connect socket: " << strerror(errno) << endl); return 1; } else { VLOG_INF(VLOG << "Created session_fd: " << session_fd << endl); } if (strlen(local_dev)) { // Bind to specific device. if (setsockopt(session_fd, SOL_SOCKET, SO_BINDTODEVICE, local_dev, strlen(local_dev)) < 0) { VLOG_ERR(VLOG << "ERROR: tcp-connect, setsockopt (BINDTODEVICE): " << strerror(errno) << " Not fatal in most cases..continuing...\n"); } }//if if (bind_socket(session_fd, local_ip, local_port+1, local_dev) < 0) { close(session_fd); return 4; } // Have to set the buffer size before connect, per tcp man page. //set_sock_wr_buffer_size(establish_conn_socket, max_desired_tx_rate, // rcvbuf_size, sndbuf_size); // Make the socket non-blocking... nonblock(session_fd, "session_fd"); if (reuseaddr_socket(session_fd) < 0) { VLOG_ERR(VLOG << "ERROR: tcp-connect, setsockopt: " << strerror(errno) << endl); return -1; }//if struct sockaddr_in dest_addr; memset(&dest_addr, 0, sizeof(dest_addr)); dest_addr.sin_family = AF_INET; dest_addr.sin_port = htons(dest_port); struct hostent *hp; if ( !(hp = gethostbyname(dest_ip))) { VLOG_WRN(VLOG << "could not resolve hostname: " << dest_ip << endl); return -1; } memcpy(&dest_addr.sin_addr.s_addr, hp->h_addr, hp->h_length); //cout << "dst_ip (host byte):" << toStringIp(dst_ip) << endl; //cout << "dst_ip_port:" << dst_ip_port << endl; int rv; if ((rv = connect(session_fd, (struct sockaddr*)(&dest_addr), sizeof(dest_addr))) < 0) { // Not able to connect (immediately) if (errno == EINPROGRESS) { // Tis OK... VLOG_INF(VLOG << "Connect in progress, conn_sock: " << session_fd << endl); conn_state = CNS_CONNECTING; } else { conn_state = CNS_NOT_CONNECTED; VLOG_WRN(VLOG << "INFO: could not connect to remote TCP connection [" << rv << "]: " << strerror(errno) << endl); close(session_fd); session_fd = -1; } } else { conn_state = CNS_CONNECTED; // successfully established connection. VLOG_WRN(VLOG << "establishConnection, connect worked, socket: " << session_fd << endl); started_at = getCurMs(); } }//if we don't have a socket yet.. else {//we do have a conn-socket //VLOG_INF(VLOG << "Have establish_conn_socket, checking conn_state: " // << (int)(conn_state) << endl); if (conn_state == CNS_CONNECTING) { // See if we can write to the socket...would mean connection attempt has completed. if (FD_ISSET(session_fd, &output_set) || FD_ISSET(session_fd, &exc_set)) { // Use getsockopt to see if there was an error connecting... int rv; int so_err = 0; socklen_t slen = sizeof(int); rv = getsockopt(session_fd, SOL_SOCKET, SO_ERROR, &so_err, &slen); if (rv < 0) { VLOG_ERR(VLOG << "ERROR: getsockopt failed w/val: " << rv << " : " << strerror(errno) << endl); close(session_fd); session_fd = -1; } else { if (so_err != 0) { VLOG_WRN(VLOG << " Could not establish connection: " << so_err << " : " << strerror(so_err) << " will re-try." << endl); close(session_fd); session_fd = -1; } else { conn_state = CNS_CONNECTED; // successfully established connection. VLOG_INF(VLOG << "establishConnection, connect completed, socket: " << session_fd << endl); started_at = getCurMs(); } }//got the sock-opt }//socket was writable else { // Not finished connecting yet.. } }//if we're in the connecting state else if (conn_state == CNS_CONNECTED) { VLOG_ERR(VLOG << "ERROR: Bad state, was connected state, but dev_socket is < 0.\n"); return 1; } }//else, have a socket already... } }//while done: uint64 now = getCurMs(); uint64 elapsed = now - started_at; VLOG << "\nWrote: " << total_written << " bytes (" << (total_written * 8000) / elapsed << "bps), read: " << total_read << " bytes (" << (total_read * 8000) / elapsed << "bps) in: " << elapsed << "ms\n"; }//main