diff --git a/src/utils/netcon.cpp b/src/utils/netcon.cpp new file mode 100644 index 00000000..ecdd1fd8 --- /dev/null +++ b/src/utils/netcon.cpp @@ -0,0 +1,1189 @@ +#ifndef lint +static char rcsid [] = "@(#$Id: netcon.cpp,v 1.18 2009-02-06 06:27:08 dockes Exp $ (C) 2002 Jean-Francois Dockes"; +#endif + +// Wrapper classes for the socket interface + + +#ifndef TEST_NETCON +#include +#include +#include +#include + +#ifdef _AIX +#include +#endif // _AIX + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#ifndef NO_NAMESPACES +using namespace std; +#endif + +#include "debuglog.h" +#include "netcon.h" + +#ifndef SOCKLEN_T +#define SOCKLEN_T socklen_t +#endif + +// Need &one, &zero for setsockopt... +static const int one = 1; +static const int zero = 0; + +#define LOGSYSERR(who, call, spar) \ + LOGERR(("%s: %s(%s) errno %d (%s)\n", who, call, \ + spar, errno, strerror(errno))) + +#ifndef MIN +#define MIN(a,b) (ab?a:b) +#endif +#ifndef freeZ +#define freeZ(X) if (X) {free(X);X=0;} +#endif + +#define MILLIS(OLD, NEW) ( (long)(((NEW).tv_sec - (OLD).tv_sec) * 1000 + \ + ((NEW).tv_usec - (OLD).tv_usec) / 1000)) + +// Simplified interface to 'select()'. Only use one fd, for either +// reading or writing. This is only used when not using the +// selectloop() style of network i/o. +// Note that timeo == 0 does NOT mean wait forever but no wait at all. +int Netcon::select1(int fd, int timeo, int write) +{ + int ret; + struct timeval tv; + fd_set rd; + tv.tv_sec = timeo; + tv.tv_usec = 0; + FD_ZERO(&rd); + FD_SET(fd, &rd); + if (write) { + ret = select(fd+1, 0, &rd, 0, &tv); + } else { + ret = select(fd+1, &rd, 0, 0, &tv); + } + if (!FD_ISSET(fd, &rd)) { + LOGERR(("Netcon::select1: fd not ready after select ??\n")); + return -1; + } + return ret; +} + +// The select loop mechanism allows several netcons to be used for io +// in a program without blocking as long as there is data to be read +// or written. + +// Set by client callback to tell selectloop to return. +bool Netcon::o_selectloopDoReturn; +int Netcon::o_selectloopReturnValue; + +// Other static data for the selectloop mecanism +// Could be declared as static members, but I don't see any advantage +// to it as all code in this file is in Netcon:: anyway. + +// Map of NetconP indexed by fd +static map polldata; + +// The last time we did the periodic thing +static struct timeval lasthdlcall; +// The call back function and its parameter +static int (*periodichandler)(void *); +static void *periodicparam; +// The periodic interval +static int periodicmillis; + +void Netcon::setperiodichandler(int (*handler)(void *), void *p, int ms) +{ + periodichandler = handler; + periodicparam = p; + periodicmillis = ms; + if (periodicmillis > 0) + gettimeofday(&lasthdlcall, 0); +} + +// set the appropriate timeout so that the select call returns in time +// to call the periodic routine. +void periodictimeout(struct timeval *tv) +{ + // If periodic not set, the select call times out and we loop + // after a very long time (we'd need to pass NULL to select for an + // infinite wait, and I'm too lazy to handle it) + if (periodicmillis <= 0) { + tv->tv_sec = 10000; + tv->tv_usec = 0; + return; + } + + struct timeval mtv; + gettimeofday(&mtv, 0); + int millis = periodicmillis - MILLIS(lasthdlcall, mtv); + + // millis <= 0 means we should have already done the thing. *dont* set the + // tv to 0, which means no timeout at all ! + if (millis <= 0) + millis = 1; + tv->tv_sec = millis / 1000; + tv->tv_usec = (millis % 1000) * 1000; +} + +// Check if it's time to call the handler. selectloop will return to +// caller if it or we return 0 +int maybecallperiodic() +{ + if (periodicmillis <= 0) + return 1; + struct timeval mtv; + gettimeofday(&mtv, 0); + int millis = periodicmillis - MILLIS(lasthdlcall, mtv); + if (millis <= 0) { + gettimeofday(&lasthdlcall, 0); + if (periodichandler) + return periodichandler(periodicparam); + else + return 0; + } + return 1; +} + +int Netcon::selectloop() +{ + static int placetostart; + for (;;) { + if (o_selectloopDoReturn) { + o_selectloopDoReturn = false; + LOGDEB(("Netcon::selectloop: returning on request\n")); + return o_selectloopReturnValue; + } + int nfds; + fd_set rd, wd; + FD_ZERO(&rd); + FD_ZERO(&wd); + + // Look for all descriptors in the map and set up the read and + // write fd_sets for select() + nfds = 0; + for (map::iterator it = polldata.begin(); + it != polldata.end(); it++) { + NetconP &pll = it->second; + int fd = it->first; + LOGDEB2(("Selectloop: fd %d flags 0x%x\n",fd, pll->m_wantedEvents)); + if (pll->m_wantedEvents & NETCONPOLL_READ) { + FD_SET(fd, &rd); + nfds = MAX(nfds, fd + 1); + } + if (pll->m_wantedEvents & NETCONPOLL_WRITE) { + FD_SET(fd, &wd); + nfds = MAX(nfds, fd + 1); + } + } + + if (nfds == 0) { + // This should never happen in a server as we should at least + // always monitor the main listening server socket. For a + // client, it's up to client code to avoid or process this + // condition. + + // Just in case there would still be open fds in there + // (with no r/w flags set). Should not be needed, but safer + polldata.clear(); + LOGDEB1(("Netcon::selectloop: no fds\n")); + return 0; + } + + LOGDEB2(("Netcon::selectloop: selecting, nfds = %d\n", nfds)); + + // Compute the next timeout according to what might need to be + // done apart from waiting for data + struct timeval tv; + periodictimeout(&tv); + // Wait for something to happen + int ret = select(nfds, &rd, &wd, 0, &tv); + LOGDEB2(("Netcon::selectloop: select returns %d\n", ret)); + if (ret < 0) { + LOGSYSERR("Netcon::selectloop", "select", ""); + return -1; + } + if (periodicmillis > 0) + if (maybecallperiodic() <= 0) + return 1; + + // Timeout, do it again. + if (ret == 0) + continue; + + // We don't start the fd sweep at 0, else some fds would be advantaged. + // Note that we do an fd sweep, not a map sweep. This is + // inefficient because the fd array may be very sparse. Otoh, the + // map may change between 2 sweeps, so that we'd have to be smart + // with the iterator. As the cost per unused fd is low (just 2 bit + // flag tests), we keep it like this for now + if (placetostart >= nfds) + placetostart = 0; + int i, fd; + for (i = 0, fd = placetostart; i < nfds;i++, fd++) { + if (fd >= nfds) + fd = 0; + + int canread = FD_ISSET(fd, &rd); + int canwrite = FD_ISSET(fd, &wd); + bool none = !canread && !canwrite; + LOGDEB2(("Netcon::selectloop: fd %d %s %s %s\n", fd, + none ? "blocked" : "can" , canread ? "read" : "", + canwrite ? "write" : "")); + if (none) + continue; + + map::iterator it = polldata.find(fd); + if (it == polldata.end()) { + /// This should not happen actually + LOGDEB2(("Netcon::selectloop: fd %d not found\n", fd)); + continue; + } + + // Next start will be one beyond last serviced (modulo nfds) + placetostart = fd + 1; + NetconP &pll = it->second; + if (canread && pll->cando(NETCONPOLL_READ) <= 0) + pll->m_wantedEvents &= ~NETCONPOLL_READ; + if (canwrite && pll->cando(NETCONPOLL_WRITE) <= 0) + pll->m_wantedEvents &= ~NETCONPOLL_WRITE; + if (!(pll->m_wantedEvents & (NETCONPOLL_WRITE|NETCONPOLL_READ))) { + LOGDEB0(("Netcon::selectloop: fd %d has 0x%x mask, erasing\n", + it->first, it->second->m_wantedEvents)); + polldata.erase(it); + } + } // fd sweep + + } // forever loop + LOGERR(("Netcon::selectloop: got out of loop !\n")); + return -1; +} + +// Add a connection to the monitored set. +int Netcon::addselcon(NetconP con, int events) +{ + if (con.isNull()) return -1; + LOGDEB1(("Netcon::addselcon: fd %d\n", con->m_fd)); + con->set_nonblock(1); + con->setselevents(events); + polldata[con->m_fd] = con; + return 0; +} +// Remove a connection from the monitored set. +int Netcon::remselcon(NetconP con) +{ + if (con.isNull()) return -1; + LOGDEB1(("Netcon::remselcon: fd %d\n", con->m_fd)); + map::iterator it = polldata.find(con->m_fd); + if (it == polldata.end()) { + LOGDEB1(("Netcon::remselcon: con not found for fd %d\n", con->m_fd)); + return -1; + } + polldata.erase(it); + return 0; +} + +////////////////////////////////////////////////////////// +// Base class (Netcon) methods +Netcon::~Netcon() { + closeconn(); + if (m_peer) { + free(m_peer); + m_peer = 0; + } +} + +void Netcon::closeconn() +{ + if (m_fd >= 0) { + close(m_fd); + m_fd = -1; + } +} + +char *Netcon::sterror() +{ + return strerror(errno); +} + +void Netcon::setpeer(const char *hostname) +{ + if (m_peer) + free(m_peer); + m_peer = strdup(hostname); +} + +int Netcon::settcpnodelay(int on) +{ + LOGDEB2(( "Netcon::settcpnodelay\n" )); + if (m_fd < 0) { + LOGERR(("Netcon::settcpnodelay: connection not opened\n")); + return -1; + } + char *cp = on ? (char *)&one : (char *)&zero; + if (setsockopt(m_fd, IPPROTO_TCP, TCP_NODELAY, cp, sizeof(one)) < 0) { + LOGSYSERR("NetconCli::settcpnodelay", "setsockopt", "TCP_NODELAY"); + return -1; + } + return 0; +} + +// Set/reset non-blocking flag on fd +int Netcon::set_nonblock(int onoff) +{ + int flags = fcntl(m_fd, F_GETFL, 0); + if (flags != -1 ) { + int newflags = onoff ? flags | O_NONBLOCK : flags & ~O_NONBLOCK; + if (newflags != flags) + if (fcntl(m_fd, F_SETFL, newflags)< 0) + return -1; + } + return flags; +} + +///////////////////////////////////////////////////////////////////// +// Data socket (NetconData) methods + +NetconData::~NetconData() { + freeZ(m_buf); + m_bufbase = 0; + m_bufbytes = m_bufsize = 0; +} + +int NetconData::send(const char *buf, int cnt, int expedited) +{ + LOGDEB2(("NetconData::send: fd %d cnt %d expe %d\n", m_fd, cnt, expedited)); + int flag = 0; + if (m_fd < 0) { + LOGERR(("NetconData::send: connection not opened\n")); + return -1; + } + if (expedited) { + LOGDEB2(("NetconData::send: expedited data, count %d bytes\n", cnt)); + flag = MSG_OOB; + } + int ret; + // There is a bug in the uthread version of sendto() in FreeBSD at + // least up to 2.2.7, so avoid using it when possible + if (flag) + ret = ::send(m_fd, buf, cnt, flag); + else + ret = ::write(m_fd, buf, cnt); + + // Note: byte count may be different from cnt if fd is non-blocking + if (ret < 0) { + char fdcbuf[10];sprintf(fdcbuf, "%d", m_fd); + LOGSYSERR("NetconData::send", "send", fdcbuf); + } + return ret; +} + +// Test for data available +int NetconData::readready() +{ + LOGDEB2(("NetconData::readready\n")); + if (m_fd < 0) { + LOGERR(("NetconData::readready: connection not opened\n")); + return -1; + } + return select1(m_fd, 0); +} + +// Test for writable +int NetconData::writeready() +{ + LOGDEB2(("NetconData::writeready\n")); + if (m_fd < 0) { + LOGERR(("NetconData::writeready: connection not opened\n")); + return -1; + } + return select1(m_fd, 0, 1); +} + +// Receive at most cnt bytes (maybe less) +int NetconData::receive(char *buf, int cnt, int timeo) +{ + LOGDEB2(("NetconData::receive: cnt %d timeo %d m_buf 0x%x m_bufbytes %d\n", + cnt, timeo, m_buf, m_bufbytes)); + if (m_fd < 0) { + LOGERR(("NetconData::receive: connection not opened\n")); + return -1; + } + // Get whatever might have been left in the buffer by a previous + // getline, except if we're called to fill the buffer of course + if (m_buf && m_bufbytes > 0 && (buf < m_buf || buf > m_buf + m_bufsize)) { + int frombuf = MIN(m_bufbytes, cnt); + memcpy(buf, m_bufbase, frombuf); + m_bufbytes -= frombuf; + m_bufbase += frombuf; + cnt -= frombuf; + if (cnt <= 0) + return frombuf; + } + if (timeo > 0) { + int ret = select1(m_fd, timeo); + if (ret == 0) { + LOGDEB2(("NetconData::receive timed out\n")); + m_didtimo = 1; + return -1; + } + if (ret < 0) { + LOGSYSERR("NetconData::receive", "select", ""); + return -1; + } + } + m_didtimo = 0; + int flags = 0; + if ((cnt = read(m_fd, buf, cnt)) < 0) { + char fdcbuf[10];sprintf(fdcbuf, "%d", m_fd); + LOGSYSERR("NetconData::receive", "read", fdcbuf); + return -1; + } + LOGDEB2(("NetconData::receive: normal return, cnt %d\n", cnt)); + return cnt; +} + +// Receive exactly cnt bytes (except for timeout) +int NetconData::doreceive(char *buf, int cnt, int timeo) +{ + int got, cur; + LOGDEB2(("Netcon::doreceive: cnt %d, timeo %d\n", cnt, timeo)); + cur = 0; + while (cnt > cur) { + got = receive(buf, cnt-cur, timeo); + LOGDEB2(("Netcon::doreceive: got %d\n", got)); + if (got < 0) { + return -1; + } + if (got == 0) { + return cur; + } + cur += got; + buf += got; + } + return cur; +} + +// Read data until cnt-1 characters are read or a newline is found. Add +// null char at end of buffer and return. +// As we don't know where the newline will be and it would be inefficient to +// read a character at a time, we use a buffer +// Unlike fgets, we return an integer status: +// >0: number of characters returned, not including the final 0 +// 0: EOF reached, no chars transferred +// -1: error +static const int defbufsize = 200; +int NetconData::getline(char *buf, int cnt, int timeo) +{ + LOGDEB2(("NetconData::getline: cnt %d, timeo %d\n", cnt, timeo)); + if (m_buf == 0) { + if ((m_buf = (char *)malloc(defbufsize)) == 0) { + LOGSYSERR("NetconData::getline: Out of mem", "malloc", ""); + return -1; + } + m_bufsize = defbufsize; + m_bufbase = m_buf; + m_bufbytes = 0; + } + + char *cp = buf; + for (;;) { + // Transfer from buffer. Have to take a lot of care to keep counts and + // pointers consistant in all end cases + int maxtransf = MIN(m_bufbytes, cnt-1); + int nn = maxtransf; + LOGDEB2(("Before loop, bufbytes %d, maxtransf %d, nn: %d\n", + m_bufbytes, maxtransf, nn)); + for (nn = maxtransf; nn > 0;) { + // This is not pretty but we want nn to be decremented for each + // byte copied (even newline), and not become -1 if we go to the end + // Better ways welcome! + nn--; + if ((*cp++ = *m_bufbase++) == '\n') + break; + } + // Update counts + maxtransf -= nn; // Actual count transferred + m_bufbytes -= maxtransf; + cnt -= maxtransf; + LOGDEB2(("After transfer: actual transf %d cnt %d, m_bufbytes %d\n", + maxtransf, cnt, m_bufbytes)); + + // Finished ? + if (cnt <= 1 || (cp > buf && cp[-1] == '\n')) { + *cp = 0; + return cp - buf; + } + + // Transfer from net + m_bufbase = m_buf; + m_bufbytes = receive(m_buf, m_bufsize, timeo); + if (m_bufbytes == 0) { + // EOF + *cp = 0; + return cp - buf; + } + if (m_bufbytes < 0) { + m_bufbytes = 0; + *cp = 0; + return -1; + } + } +} + +// Called when selectloop detects that data can be read or written on +// the connection. The user callback would normally have been set +// up. If it is, call it and return. Else, perform housecleaning: read +// and discard. +int NetconData::cando(Netcon::Event reason) +{ + LOGDEB2(("NetconData::cando\n")); + if (!m_user.isNull()) { + return m_user->data(this, reason); + } + + // No user callback. Clean up by ourselves + if (reason & NETCONPOLL_READ) { +#define BS 200 + char buf[BS]; + int n; + if ((n = receive(buf, BS)) < 0) { + LOGSYSERR("NetconData::cando", "receive", ""); + return -1; + } + if (n == 0) { + // EOF + return 0; + } + } + clearselevents(NETCONPOLL_WRITE); + return 1; +} + +/////////////////////////////////////////////////////////////////////// +// Methods for a client connection (NetconCli) +int NetconCli::openconn(const char *host, unsigned int port, int timeo) +{ + int ret = -1; + LOGDEB2(("Netconcli::openconn: host %s, port %d\n", host, port)); + + closeconn(); + + struct sockaddr_in saddr; + memset(&saddr, 0, sizeof(saddr)); + saddr.sin_family = AF_INET; + saddr.sin_port = htons(port); + + // Server name may be host name or IP address + int addr; + if ((addr = inet_addr(host)) != -1) { + memcpy(&saddr.sin_addr, &addr, sizeof(addr)); + } else { + struct hostent *hp; + if ((hp = gethostbyname(host)) == 0) { + LOGERR(("NetconCli::openconn: gethostbyname(%s) failed\n", host)); + return -1; + } + memcpy(&saddr.sin_addr, hp->h_addr, hp->h_length); + } + + if ((m_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + LOGSYSERR("NetconCli::openconn", "socket", ""); + return -1; + } + if (timeo > 0) + set_nonblock(1); + + if(connect(m_fd,(struct sockaddr *) &saddr, sizeof(saddr)) < 0) { + if (timeo > 0) { + if (errno != EINPROGRESS) + goto out; + if (select1(m_fd, timeo, 1) == 1) + goto connectok; + } + if (m_silentconnectfailure == 0) { + LOGSYSERR("NetconCli", "connect", ""); + } + goto out; + } + connectok: + if (timeo > 0) + set_nonblock(0); + + LOGDEB2(("NetconCli::connect: setting keepalive\n")); + if (setsockopt(m_fd, SOL_SOCKET, SO_KEEPALIVE, + (char *)&one, sizeof(one)) < 0) { + LOGSYSERR("NetconCli::connect", "setsockopt", "KEEPALIVE"); + } + setpeer(host); + LOGDEB2(("NetconCli::openconn: connection opened ok\n")); + ret = 0; + out: + if (ret < 0) + closeconn(); + return ret; +} + +// Same as previous, but get the port number from services +int NetconCli::openconn(const char *host, char *serv, int timeo) +{ + LOGDEB2(("Netconcli::openconn: host %s, serv %s\n", host, serv)); + + struct servent *sp; + if ((sp = getservbyname(serv, "tcp")) == 0) { + LOGERR(("NetconCli::openconn: getservbyname failed for %s\n", serv)); + return -1; + } + // Callee expects the port number in host byte order + return openconn(host, ntohs(sp->s_port), timeo); +} + + +int NetconCli::setconn(int fd) +{ + LOGDEB2(("Netconcli::setconn: fd %d\n", fd)); + + closeconn(); + + m_fd = fd; + setpeer(""); + + return 0; +} + +/////////////////////////////////////////////////////////////////////// +// Methods for the main (listening) server connection + +NetconServLis::~NetconServLis() { +#ifdef NETCON_ACCESSCONTROL + freeZ(okaddrs.intarray); + freeZ(okmasks.intarray); +#endif +} + +#if 0 +// code for dumping a struct servent +static void dump_servent(struct servent *servp) { + fprintf(stderr, "Official name %s\n", servp->s_name); + for (char **cpp = servp->s_aliases;*cpp;cpp++) + fprintf(stderr, "Nickname %s\n", *cpp); + fprintf(stderr, "Port %d\n", (int)ntohs((short)servp->s_port)); + fprintf(stderr, "Proto %s\n", servp->s_proto); +} +#endif + +// Set up service. +int NetconServLis::openservice(char *serv, int backlog) +{ + int port; + struct servent *servp; + LOGDEB1(("NetconServLis::openservice: serv %s\n", serv)); +#ifdef NETCON_ACCESSCONTROL + if (initperms(serv) < 0) + return -1; +#endif + if ((servp = getservbyname(serv, "tcp")) == 0) { + LOGERR(("NetconServLis::openservice: getservbyname failed for %s\n",serv)); + return -1; + } + port = (int)ntohs((short)servp->s_port); + return openservice(port, backlog); +} + +// Port is a natural host integer value +int NetconServLis::openservice(int port, int backlog) +{ + LOGDEB1(("NetconServLis::openservice: port %d\n", port)); +#ifdef NETCON_ACCESSCONTROL + if (initperms(port) < 0) + return -1; +#endif + int ret = -1; + struct sockaddr_in ipaddr; + if ((m_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + LOGSYSERR("NetconServLis", "socket", ""); + return -1; + } + (void) setsockopt(m_fd, SOL_SOCKET, SO_REUSEADDR,(char *)&one, sizeof(one)); +#ifdef SO_REUSEPORT + (void) setsockopt(m_fd, SOL_SOCKET, SO_REUSEPORT,(char *)&one, sizeof(one)); +#endif /*SO_REUSEPORT*/ + memset(&ipaddr, 0, sizeof(ipaddr)); + ipaddr.sin_family = AF_INET; + ipaddr.sin_addr.s_addr = htonl(INADDR_ANY); + ipaddr.sin_port = htons((short)port); + if (bind(m_fd, (struct sockaddr *)&ipaddr, sizeof(ipaddr)) < 0){ + LOGSYSERR("NetconServLis", "bind", ""); + goto out; + } + if (listen(m_fd, backlog) < 0) { + LOGSYSERR("NetconServLis", "listen", ""); + goto out; + } + + LOGDEB1(("NetconServLis::openservice: service opened ok\n")); + ret = 0; + out: + if (ret < 0 && m_fd >= 0) { + close(m_fd); + m_fd = -1; + } + return ret; +} + +#ifdef NETCON_ACCESSCONTROL +int NetconServLis::initperms(int port) +{ + if (permsinit) + return 0; + + char sport[30]; + sprintf(sport, "%d", port); + return initperms(sport); +} + +// Get authorized address lists from parameter file. This is disabled for now +int NetconServLis::initperms(char *serv) +{ + if (permsinit) + return 0; + + if (serv == 0 || *serv == 0 || strlen(serv) > 80) { + LOGERR(("NetconServLis::initperms: bad service name %s\n", serv)); + return -1; + } + + char keyname[100]; + sprintf(keyname, "%s_okaddrs", serv); + if (genparams->getparam(keyname, &okaddrs, 1) < 0) { + serv = "default"; + sprintf(keyname, "%s_okaddrs", serv); + if (genparams->getparam(keyname, &okaddrs) < 0) { + LOGERR(("NetconServLis::initperms: no okaddrs found in config file\n")); + return -1; + } + } + sprintf(keyname, "%s_okmasks", serv); + if (genparams->getparam(keyname, &okmasks)) { + LOGERR(("NetconServLis::initperms: okmasks not found\n")); + return -1; + } + if (okaddrs.len == 0 || okmasks.len == 0) { + LOGERR(("NetconServLis::initperms: len 0 for okmasks or okaddrs\n")); + return -1; + } + + permsinit = 1; + return 0; +} +#endif /* NETCON_ACCESSCONTROL */ + +// Sample cando routine for server master connection: delete newly +// accepted connection. What else ? +// This is to be overriden by a derived class method for an application +// using the selectloop thing +int NetconServLis::cando(Netcon::Event reason) +{ + delete accept(); + return 1; +} + +NetconServCon * +NetconServLis::accept(int timeo) +{ + LOGDEB(("NetconServLis::accept\n")); + + if (timeo > 0) { + int ret = select1(m_fd, timeo); + if (ret == 0) { + LOGDEB2(("NetconServLis::accept timed out\n")); + m_didtimo = 1; + return 0; + } + if (ret < 0) { + LOGSYSERR("NetconServLis::accept", "select", ""); + return 0; + } + } + m_didtimo = 0; + + NetconServCon *con = 0; + int newfd = -1; + struct sockaddr_in who; + SOCKLEN_T clilen = (SOCKLEN_T)sizeof(who); + if ((newfd = ::accept(m_fd, (struct sockaddr *)&who, &clilen)) < 0) { + LOGSYSERR("NetconServCon::accept", "accept", ""); + goto out; + } +#ifdef NETCON_ACCESSCONTROL + if (checkperms(&who, clilen) < 0) { + goto out; + } +#endif + con = new NetconServCon(newfd); + if (con == 0) { + LOGERR(("NetconServLis::accept: new NetconServCon failed\n")); + goto out; + } + // Retrieve peer's host name. Errors are non fatal + struct hostent *hp; + if ((hp = gethostbyaddr((char *)&(who.sin_addr), sizeof(struct in_addr), + AF_INET)) == 0) { + LOGERR(("NetconServLis::accept: gethostbyaddr failed for addr 0x%lx\n", + who.sin_addr.s_addr)); + con->setpeer(inet_ntoa(who.sin_addr)); + } else { + con->setpeer(hp->h_name); + } + LOGDEB2(("NetconServLis::accept: setting keepalive\n")); + if (setsockopt(newfd, SOL_SOCKET, SO_KEEPALIVE, + (char *)&one, sizeof(one)) < 0) { + LOGSYSERR("NetconServLis::accept", "setsockopt", "KEEPALIVE"); + } + LOGDEB2(("NetconServLis::accept: got connect from %s\n", con->getpeer())); + + out: + if (con == 0 && newfd >= 0) + close(newfd); + return con; +} + +#ifdef NETCON_ACCESSCONTROL +int +NetconServLis::checkperms(void *cl, int) +{ + // If okmasks and addrs were not initialized, the default is allow to all + if (okmasks.len <= 0 || okaddrs.len <= 0) + return 0; + + struct sockaddr *addr = (struct sockaddr *)cl; + unsigned long ip_addr; + + if (addr->sa_family != AF_INET) { + LOGERR(("NetconServLis::checkperms: connection from non-INET addr !\n")); + return -1; + } + + ip_addr = ntohl(((struct sockaddr_in *)addr)->sin_addr.s_addr); + LOGDEB2(("checkperms: ip_addr: 0x%x\n", ip_addr)); + for (int i = 0; i < okaddrs.len; i++) { + unsigned int mask; + if (i < okmasks.len) + mask = okmasks.intarray[i]; + else + mask = okmasks.intarray[okmasks.len-1]; + LOGDEB2(("checkperms: trying okaddr 0x%x, mask 0x%x\n", + okaddrs.intarray[i], mask)); + if ((ip_addr & mask) == (okaddrs.intarray[i] & mask)) + return (0); + } + LOGERR(("NetconServLis::checkperm: connection from bad address 0x%x\n", + ip_addr)); + return -1; +} +#endif /* NETCON_ACCESSCONTROL */ + + +#else /* !TEST_NETCON */ +///////////////////////////////////////////////////////////////////////// +////////// TEST DRIVER +//////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +#include "debuglog.h" +#include "netcon.h" + +static char *thisprog; +static char usage[] = + "-c : Connects to trnetcon server, exchange message, then\n" + " sleeps 10 S, except if option -n is given (sleep forever)\n" + "\n" + "-s : open service \n" + ; +static void +Usage() +{ + fprintf(stderr, "Usage : %s:\n %s", thisprog, usage); + exit(1); +} + +static int op_flags; +#define OPT_MOINS 0x1 +#define OPT_s 0x2 /* Server */ +#define OPT_c 0x4 /* Client */ +#define OPT_n 0x8 /* Client sleeps forever */ + +extern int trycli(char *host, char *serv); +extern int tryserv(char *serv); + +int nloop = 10; + +int main(int argc, char **argv) +{ + char *host, *serv; + + thisprog = argv[0]; + argc--; argv++; + + while (argc > 0 && **argv == '-') { + (*argv)++; + if (!(**argv)) + /* Cas du "adb - core" */ + Usage(); + while (**argv) + switch (*(*argv)++) { + case 's': + op_flags |= OPT_s; + break; + case 'c': + op_flags |= OPT_c; + break; + case 'n': + op_flags |= OPT_n; + break; + default: + Usage(); + break; + } + argc--; + argv++; + } + DebugLog::setfilename("stderr"); + DebugLog::getdbl()->setloglevel(DEBINFO); + + if (op_flags & OPT_c) { + if (argc != 2) { + Usage(); + } + host = *argv++;argc--; + serv = *argv++;argc--; + exit(trycli(host, serv)); + } else if (op_flags & OPT_s) { + if (argc != 1) { + Usage(); + } + serv = *argv++;argc--; + exit(tryserv(serv)); + } else { + Usage(); + } +} + + +static char buf[1024]; +static int buflen = 1023; +static char fromcli[200]; + +class CliNetconWorker : public NetconWorker { +public: + CliNetconWorker() : m_count(0) {} + int data(NetconData *con, Netcon::Event reason) + { + LOGDEB(("clientdata\n")); + if (reason & Netcon::NETCONPOLL_WRITE) { + sprintf(fromcli, "Bonjour Bonjour client %d, count %d", + getpid(), m_count); + con->setselevents(Netcon::NETCONPOLL_READ); + if (con->send(fromcli, strlen(fromcli) + 1) < 0) { + fprintf(stderr, "send failed\n"); + return -1; + } + m_count++; + } + + if (reason & Netcon::NETCONPOLL_READ) { + con->setselevents(Netcon::NETCONPOLL_WRITE); + int n; + if ((n = con->receive(buf, buflen)) < 0) { + fprintf(stderr, "receive failed\n"); + return -1; + } + if (n == 0) { + // EOF, close connection + return -1; + } + buf[n] = 0; + fprintf(stderr, "%d received \"%s\"\n", getpid(), buf); + if (op_flags & OPT_n) { + pause(); + } else { + sleep(1); + } + } + if (m_count >= 10) { + fprintf(stderr, "Did 10, enough\n"); + Netcon::selectloopReturn(0); + } + return 0; + } +private: + int m_count; +}; + +int trycli(char *host, char *serv) +{ + sprintf(fromcli, "Bonjour Bonjour je suis le client %d", getpid()); + + NetconCli *clicon = new NetconCli(); + NetconP con(clicon); + if (con.isNull()) { + fprintf(stderr, "new NetconCli failed\n"); + return 1; + } + if (clicon->openconn(host, serv) < 0) { + fprintf(stderr, "openconn(%s, %s) failed\n", host, serv); + return 1; + } + fprintf(stderr, "openconn(%s, %s) ok\n", host, serv); +#ifdef NOSELLOOP + for (int i = 0;i < nloop;i++) { + if (con->send(fromcli, strlen(fromcli) + 1) < 0) { + fprintf(stderr, "%d: Send failed\n", getpid()); + return 1; + } + if (con->receive(buf, buflen) < 0) { + perror("receive:"); + fprintf(stderr, "%d: Receive failed\n", getpid()); + return 1; + } + fprintf(stderr, "%d Received \"%s\"\n", getpid(), buf); + if (op_flags & OPT_n) { + pause(); + } else { + sleep(1); + } + } +#else + RefCntr worker = + RefCntr(new CliNetconWorker()); + clicon->setcallback(worker); + Netcon::addselcon(con, Netcon::NETCONPOLL_WRITE); + fprintf(stderr, "client ready\n"); + int ret = Netcon::selectloop(); + if (ret < 0) { + fprintf(stderr, "selectloop failed\n"); + exit(1); + } + fprintf(stderr, "selectloop returned %d\n", ret); +#endif + return 0; +} + +////////////////////////////////////////////////////////////////// +// Server-side sample code +class ServNetconWorker : public NetconWorker { +public: + ServNetconWorker() : m_count(0) {} + int data(NetconData *con, Netcon::Event reason) + { + LOGDEB(("serverdata\n")); + if (reason & Netcon::NETCONPOLL_WRITE) { + con->setselevents(Netcon::NETCONPOLL_READ); + char fromserv[200]; + sprintf(fromserv, + "Du serveur: mon fd pour ce client est %d, mon compte %d", + con->getfd(), ++m_count); + if (con->send(fromserv, strlen(fromserv) + 1) < 0) { + fprintf(stderr, "send failed\n"); + return -1; + } + } + if (reason & Netcon::NETCONPOLL_READ) { +#define LL 200 + char buf[LL+1]; + int n; + if ((n = con->receive(buf, LL)) < 0) { + fprintf(stderr, "receive failed\n"); + return -1; + } + if (n == 0) { + return -1; + } + buf[n] = 0; + fprintf(stderr, "%d received \"%s\"\n", getpid(), buf); + con->setselevents(Netcon::NETCONPOLL_READ|Netcon::NETCONPOLL_WRITE); + } + return 0; + } +private: + int m_count; +}; + +class MyNetconServLis : public NetconServLis { +protected: + int cando(Netcon::Event reason) { + NetconServCon *con = accept(); + if (con == 0) + return -1; + RefCntr worker = + RefCntr(new ServNetconWorker()); + con->setcallback(worker); + addselcon(NetconP(con), NETCONPOLL_READ); + return 1; + } +}; + +NetconP lis; + +void +onexit(int sig) +{ + fprintf(stderr, "Onexit: "); + if (sig == SIGQUIT) + kill(getpid(), SIGKILL); + fprintf(stderr, "Exiting\n"); + exit(0); +} + +int tryserv(char *serv) +{ + signal(SIGCHLD, SIG_IGN); + MyNetconServLis *servlis = new MyNetconServLis(); + lis = NetconP(servlis); + if (lis.isNull()) { + fprintf(stderr, "new NetconServLis failed\n"); + return 1; + } + + // Prepare for cleanup + struct sigaction sa; + sa.sa_flags = 0; + sa.sa_handler = onexit; + sigemptyset(&sa.sa_mask); + sigaction(SIGINT, &sa, 0); + sigaction(SIGQUIT, &sa, 0); + sigaction(SIGTERM, &sa, 0); + + if (servlis->openservice(serv) < 0) { + fprintf(stderr, "openservice(%s) failed\n", serv); + return 1; + } + Netcon::addselcon(lis, Netcon::NETCONPOLL_READ); + fprintf(stderr, "openservice(%s) Ok\n", serv); + if (Netcon::selectloop() < 0) { + fprintf(stderr, "selectloop failed\n"); + exit(1); + } + return 0; +} + +#endif /* TEST_NETCON */ diff --git a/src/utils/netcon.h b/src/utils/netcon.h new file mode 100644 index 00000000..6d5402c6 --- /dev/null +++ b/src/utils/netcon.h @@ -0,0 +1,262 @@ +#ifndef _NETCON_H_ +#define _NETCON_H_ +/* @(#$Id: netcon.h,v 1.11 2009-02-05 14:25:24 dockes Exp $ (C) 2002 Jean-Francois Dockes */ +#include "refcntr.h" + +/// A set of classes to manage client-server communication over a +/// connection-oriented network, or a pipe. +/// +/// Currently only uses TCP. The classes include client-side and +/// server-side (accepting) endpoints, and server-side code to handle +/// a set of client connections in parallel. +/// +/// The client data transfer class can also be used for +/// timeout-protected/asynchronous io using a given fd. + +/// Base class for all network endpoints: +class Netcon; +typedef RefCntr NetconP; + +class Netcon { +public: + enum Event {NETCONPOLL_READ = 0x1, NETCONPOLL_WRITE=0x2}; + Netcon() + : m_peer(0), m_fd(-1), m_didtimo(0), m_wantedEvents(0) + {} + virtual ~Netcon(); + /// Remember whom we're talking to. We let external code do this because + /// the application may have a non-dns method to find the peer name. + virtual void setpeer(const char *hostname); + /// Retrieve the peer's hostname. Only works if it was set before ! + virtual const char *getpeer() { + return m_peer ? (const char *)m_peer : "none"; + } + /// Set or reset the TCP_NODELAY option. + virtual int settcpnodelay(int on = 1); + /// Did the last receive() call time out ? Resets the flag. + virtual int timedout() {int s = m_didtimo; m_didtimo = 0; return s;} + /// Return string version of last syscall error + virtual char *sterror(); + /// Return the socket descriptor + virtual int getfd() {return m_fd;} + /// Close the current connection if it is open + virtual void closeconn(); + /// Set/reset the non-blocking flag on the underlying fd. Returns + /// prev state The default is that sockets are blocking except + /// when added to the selectloop, or, transparently, to handle + /// connection timeout issues. + virtual int set_nonblock(int onoff); + + /// Decide what events the connection will be looking for + /// (NETCONPOLL_READ, NETCONPOLL_WRITE) + int setselevents(int evs) {return m_wantedEvents = evs;} + /// Retrieve the connection's currently monitored set of events + int getselevents() {return m_wantedEvents;} + /// Add events to current set + int addselevents(int evs) {return m_wantedEvents |= evs;} + /// Clear events from current set + int clearselevents(int evs) {return m_wantedEvents &= ~evs;} + + /// Utility function for a simplified select() interface: check one fd + /// for reading or writing, for a specified maximum number of seconds. + static int select1(int fd, int secs, int writing = 0); + + /// The selectloop interface is used to implement parallel servers. + /// All the interface is static (independant of any given object). + + /// Loop waiting for events on the connections and call the + /// cando() method on the object where something happens (this will in + /// turn typically call the app callback set on the netcon). Possibly + /// call the periodic handler (if set) at regular intervals. + /// @return -1 for error. 0 if no descriptors left for i/o. 1 for periodic + /// timeout (should call back in after processing) + static int selectloop(); + /// Call from data handler: make selectloop return the param value + static void selectloopReturn(int value) + { + o_selectloopDoReturn = true; + o_selectloopReturnValue = value; + } + /// Add a connection to be monitored (this will usually be called + /// from the server's listen connection's accept callback) + static int addselcon(NetconP con, int); + /// Remove a connection from the monitored set. Note that this is + /// automatically called from the Netcon destructor, and when EOF is + /// detected on a connection. + static int remselcon(NetconP con); + + /// Set a function to be called periodically, or a time before return. + /// @param handler the function to be called. + /// - if it is 0, selectloop() will return after ms mS (and can be called + /// again + /// - if it is not 0, it will be called at ms mS intervals. If its return + /// value is <= 0, selectloop will return. + /// @param clp client data to be passed to handler at every call. + /// @param ms milliseconds interval between handler calls or before return. + /// set ms to 0 for no periodic handler + static void setperiodichandler(int (*handler)(void *), void *clp, int ms); + +protected: + static bool o_selectloopDoReturn; + static int o_selectloopReturnValue; + char *m_peer; // Name of the connected host + int m_fd; + int m_didtimo; + // Used when part of the selectloop map. + short m_wantedEvents; + // Method called by the selectloop when something can be done with a netcon + virtual int cando(Netcon::Event reason) = 0; +}; + + +/////////////////////// +class NetconData; + +/// Class for the application callback routine (when in +/// selectloop). It would be nicer to override cando() in a subclass +/// instead of setting a callback, but this can't be done conveniently +/// because accept() always creates a base NetconData (another way +/// would be to pass a factory function function to the listener, to create +/// NetconData derivatives). +class NetconWorker { +public: + virtual ~NetconWorker() {} + // NetconP holds a NetconData oeuf corse + virtual int data(NetconData *con, Netcon::Event reason) = 0; +}; + +/// Base class for connections that actually transfer data. +class NetconData : public Netcon { +public: + NetconData() : m_buf(0), m_bufbase(0), m_bufbytes(0), m_bufsize(0) + {} + virtual ~NetconData(); + + /// Write data to the connection. + /// @param buf the data buffer + /// @param cnt the number of bytes we should try to send + /// @param expedited send data in as 'expedited' data. + /// @return the count of bytes actually transferred, -1 if an + /// error occurred. + virtual int send(const char *buf, int cnt, int expedited = 0); + + /// Read from the connection + /// @param buf the data buffer + /// @param cnt the number of bytes we should try to read + /// @param timeo maximum number of seconds we should be waiting for data. + /// @return the count of bytes actually read. 0 for timeout (call + /// didtimo() to discriminate from EOF). -1 if an error occurred. + virtual int receive(char *buf, int cnt, int timeo = -1); + /// Loop on receive until cnt bytes are actually read or a timeout occurs + virtual int doreceive(char *buf, int cnt, int timeo = -1); + /// Check for data being available for reading + virtual int readready(); + /// Check for data being available for writing + virtual int writeready(); + /// Read a line of text on an ascii connection + virtual int getline(char *buf, int cnt, int timeo = -1); + /// Set handler to be called when the connection is placed in the + /// selectloop and an event occurs. + virtual void setcallback(RefCntr user) {m_user = user;} + +private: + char *m_buf; // Buffer. Only used when doing getline()s + char *m_bufbase; // Pointer to current 1st byte of useful data + int m_bufbytes; // Bytes of data. + int m_bufsize; // Total buffer size + RefCntr m_user; + virtual int cando(Netcon::Event reason); +}; + +/// Network endpoint, client side. +class NetconCli : public NetconData { +public: + NetconCli(int silent = 0) {m_silentconnectfailure = silent;} + /// Open connection to specified host and named service. + int openconn(const char *host, char *serv, int timeo = -1); + /// Open connection to specified host and numeric port. port is in + /// HOST byte order + int openconn(const char *host, unsigned int port, int timeo = -1); + /// Reuse existing fd. We DONT take ownership of the fd, and do no closin' + /// EVEN on an explicit closeconn() (use getfd(), close, setconn(-1)). + int setconn(int fd); + /// Do not log message if openconn() fails. + void setSilentFail(int onoff) {m_silentconnectfailure = onoff;} +private: + int m_silentconnectfailure; // No logging of connection failures if set +}; + +class NetconServCon; +#ifdef NETCON_ACCESSCONTROL +struct intarrayparam { + int len; + unsigned int *intarray; +}; +#endif /* NETCON_ACCESSCONTROL */ + +/// Server listening end point. +/// +/// if NETCON_ACCESSCONTROL is defined during compilation, +/// NetconServLis has primitive access control features: okaddrs holds +/// the host addresses for the hosts which we allow to connect to +/// us. okmasks holds the masks to be used for comparison. okmasks +/// can be shorter than okaddrs, in which case we use the last entry +/// for all addrs beyond the masks array length. Both arrays are +/// retrieved from the configuration file when we create the endpoint +/// the key is either based on the service name (ex: cdpathdb_okaddrs, +/// cdpathdb_okmasks), or "default" if the service name is not found +/// (ex: default_okaddrs, default_okmasks) +class NetconServLis : public Netcon { +public: + NetconServLis() { +#ifdef NETCON_ACCESSCONTROL + permsinit = 0; + okaddrs.len = okmasks.len = 0; + okaddrs.intarray = okmasks.intarray = 0; +#endif /* NETCON_ACCESSCONTROL */ + } + ~NetconServLis(); + /// Open named service. + int openservice(char *serv, int backlog = 10); + /// Open service by port number. + int openservice(int port, int backlog = 10); + /// Wait for incoming connection. Returned connected Netcon + NetconServCon *accept(int timeo = -1); + +protected: + /// This should be overriden in a derived class to handle incoming + /// connections. It will usually call NetconServLis::accept(), and + /// insert the new connection in the selectloop. + virtual int cando(Netcon::Event reason); + +private: +#ifdef NETCON_ACCESSCONTROL + int permsinit; + struct intarrayparam okaddrs; + struct intarrayparam okmasks; + int initperms(char *servicename); + int initperms(int port); + int checkperms(void *cli, int clilen); +#endif /* NETCON_ACCESSCONTROL */ +}; + +/// Server-side accepted client connection. The only specific code +/// allows closing the listening endpoint in the child process (in the +/// case of a forking server) +class NetconServCon : public NetconData { +public: + NetconServCon(int newfd, Netcon* lis = 0) + { + m_liscon = lis; + m_fd = newfd; + } + /// This is for forked servers that want to get rid of the main socket + void closeLisCon() { + if (m_liscon) + m_liscon->closeconn(); + } +private: + Netcon* m_liscon; +}; + +#endif /* _NETCON_H_ */