// // ZoneMinder Communications Class Implementation, $Date$, $Revision$ // Copyright (C) 2001-2008 Philip Coombes // // 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 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 General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #include "zm_comms.h" #include "zm_logger.h" #include // for debug output #include #include #include // for snprintf #include #include #include #include #include #ifdef SOLARIS #include // define FIONREAD #endif int ZM::CommsBase::readV(int iovcnt, /* const void *, int, */ ...) { va_list arg_ptr; struct iovec iov[iovcnt]; va_start(arg_ptr, iovcnt); for ( int i = 0; i < iovcnt; i++ ) { iov[i].iov_base = va_arg(arg_ptr, void *); iov[i].iov_len = va_arg(arg_ptr, int); } va_end(arg_ptr); int nBytes = ::readv(mRd, iov, iovcnt); if ( nBytes < 0 ) Debug(1, "Readv of %d buffers max on rd %d failed: %s", iovcnt, mRd, strerror(errno)); return nBytes; } int ZM::CommsBase::writeV(int iovcnt, /* const void *, int, */ ...) { va_list arg_ptr; struct iovec iov[iovcnt]; va_start(arg_ptr, iovcnt); for ( int i = 0; i < iovcnt; i++ ) { iov[i].iov_base = va_arg(arg_ptr, void *); iov[i].iov_len = va_arg(arg_ptr, int); } va_end(arg_ptr); ssize_t nBytes = ::writev(mWd, iov, iovcnt); if ( nBytes < 0 ) Debug(1, "Writev of %d buffers on wd %d failed: %s", iovcnt, mWd, strerror(errno)); return nBytes; } bool ZM::Pipe::open() { if ( ::pipe(mFd) < 0 ) { Error("pipe(), errno = %d, error = %s", errno, strerror(errno)); return false; } return true; } bool ZM::Pipe::close() { if ( mFd[0] > -1 ) ::close( mFd[0] ); mFd[0] = -1; if ( mFd[1] > -1 ) ::close( mFd[1] ); mFd[1] = -1; return true; } bool ZM::Pipe::setBlocking(bool blocking) { int flags; /* Now set it for non-blocking I/O */ if ( (flags = fcntl(mFd[1], F_GETFL)) < 0 ) { Error("fcntl(), errno = %d, error = %s", errno, strerror(errno)); return false; } if ( blocking ) { flags &= ~O_NONBLOCK; } else { flags |= O_NONBLOCK; } if ( fcntl(mFd[1], F_SETFL, flags) < 0 ) { Error("fcntl(), errno = %d, error = %s", errno, strerror(errno)); return false; } return true; } ZM::SockAddr::SockAddr(const struct sockaddr *addr) : mAddr(addr) { } ZM::SockAddr *ZM::SockAddr::newSockAddr(const struct sockaddr &addr, socklen_t len) { if ( (addr.sa_family == AF_INET) && (len == SockAddrInet::addrSize()) ) { return new SockAddrInet((const struct sockaddr_in *)&addr); } else if ( (addr.sa_family == AF_UNIX) && (len == SockAddrUnix::addrSize()) ) { return new SockAddrUnix((const struct sockaddr_un *)&addr); } Error("Unable to create new SockAddr from addr family %d with size %d", addr.sa_family, len); return nullptr; } ZM::SockAddr *ZM::SockAddr::newSockAddr(const SockAddr *addr) { if ( !addr ) return nullptr; if ( addr->getDomain() == AF_INET ) { return new SockAddrInet(*(SockAddrInet *)addr); } else if ( addr->getDomain() == AF_UNIX ) { return new SockAddrUnix(*(SockAddrUnix *)addr); } Error("Unable to create new SockAddr from addr family %d", addr->getDomain()); return nullptr; } ZM::SockAddrInet::SockAddrInet() : SockAddr( (struct sockaddr *)&mAddrIn ) { } bool ZM::SockAddrInet::resolve(const char *host, const char *serv, const char *proto) { memset(&mAddrIn, 0, sizeof(mAddrIn)); struct hostent *hostent = nullptr; if ( !(hostent = ::gethostbyname(host) ) ) { Error("gethostbyname(%s), h_errno = %d", host, h_errno); return false; } struct servent *servent = nullptr; if ( !(servent = ::getservbyname(serv, proto) ) ) { Error("getservbyname( %s ), errno = %d, error = %s", serv, errno, strerror(errno)); return false; } mAddrIn.sin_port = servent->s_port; mAddrIn.sin_family = AF_INET; mAddrIn.sin_addr.s_addr = ((struct in_addr *)(hostent->h_addr))->s_addr; return true; } bool ZM::SockAddrInet::resolve(const char *host, int port, const char *proto) { memset(&mAddrIn, 0, sizeof(mAddrIn)); struct hostent *hostent = nullptr; if ( !(hostent = ::gethostbyname(host)) ) { Error("gethostbyname(%s), h_errno = %d", host, h_errno); return false; } mAddrIn.sin_port = htons(port); mAddrIn.sin_family = AF_INET; mAddrIn.sin_addr.s_addr = ((struct in_addr *)(hostent->h_addr))->s_addr; return true; } bool ZM::SockAddrInet::resolve(const char *serv, const char *proto) { memset(&mAddrIn, 0, sizeof(mAddrIn)); struct servent *servent = nullptr; if ( !(servent = ::getservbyname(serv, proto)) ) { Error("getservbyname(%s), errno = %d, error = %s", serv, errno, strerror(errno)); return false; } mAddrIn.sin_port = servent->s_port; mAddrIn.sin_family = AF_INET; mAddrIn.sin_addr.s_addr = INADDR_ANY; return true; } bool ZM::SockAddrInet::resolve(int port, const char *proto) { memset(&mAddrIn, 0, sizeof(mAddrIn)); mAddrIn.sin_port = htons(port); mAddrIn.sin_family = AF_INET; mAddrIn.sin_addr.s_addr = INADDR_ANY; return true; } ZM::SockAddrUnix::SockAddrUnix() : SockAddr((struct sockaddr *)&mAddrUn ) { } bool ZM::SockAddrUnix::resolve(const char *path, const char *proto) { memset(&mAddrUn, 0, sizeof(mAddrUn)); strncpy(mAddrUn.sun_path, path, sizeof(mAddrUn.sun_path)); mAddrUn.sun_family = AF_UNIX; return true; } bool ZM::Socket::socket() { if ( mSd >= 0 ) return true; if ( (mSd = ::socket(getDomain(), getType(), 0) ) < 0 ) { Error("socket(), errno = %d, error = %s", errno, strerror(errno)); return false; } int val = 1; (void)::setsockopt(mSd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)); (void)::setsockopt(mSd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)); mState = DISCONNECTED; return true; } bool ZM::Socket::connect() { if ( !socket() ) return false; if ( ::connect(mSd, mRemoteAddr->getAddr(), getAddrSize()) == -1 ) { Error("connect(), errno = %d, error = %s", errno, strerror(errno)); close(); return false; } mState = CONNECTED; return true; } bool ZM::Socket::bind() { if ( !socket() ) return false; if ( ::bind(mSd, mLocalAddr->getAddr(), getAddrSize()) == -1 ) { Error("bind(), errno = %d, error = %s", errno, strerror(errno)); close(); return false; } return true; } bool ZM::Socket::listen() { if ( ::listen(mSd, SOMAXCONN) == -1 ) { Error("listen(), errno = %d, error = %s", errno, strerror(errno)); close(); return false; } mState = LISTENING; return true; } bool ZM::Socket::accept() { struct sockaddr *rem_addr = mLocalAddr->getTempAddr(); socklen_t rem_addr_size = getAddrSize(); int newSd = -1; if ( (newSd = ::accept(mSd, rem_addr, &rem_addr_size)) == -1 ) { Error("accept(), errno = %d, error = %s", errno, strerror(errno)); close(); return false; } ::close(mSd); mSd = newSd; mState = CONNECTED; return true; } bool ZM::Socket::accept(int &newSd) { struct sockaddr *rem_addr = mLocalAddr->getTempAddr(); socklen_t rem_addr_size = getAddrSize(); newSd = -1; if ( (newSd = ::accept(mSd, rem_addr, &rem_addr_size)) == -1 ) { Error("accept(), errno = %d, error = %s", errno, strerror(errno)); close(); return false; } return true; } bool ZM::Socket::close() { if ( mSd > -1 ) ::close(mSd); mSd = -1; mState = CLOSED; return true; } int ZM::Socket::bytesToRead() const { int bytes_to_read = 0; if ( ioctl(mSd, FIONREAD, &bytes_to_read) < 0 ) { Error("ioctl(), errno = %d, error = %s", errno, strerror(errno)); return -1; } return bytes_to_read; } bool ZM::Socket::getBlocking(bool &blocking) { int flags; if ( (flags = fcntl(mSd, F_GETFL)) < 0 ) { Error("fcntl(), errno = %d, error = %s", errno, strerror(errno)); return false; } blocking = flags & O_NONBLOCK; return true; } bool ZM::Socket::setBlocking(bool blocking) { int flags; /* Now set it for non-blocking I/O */ if ( (flags = fcntl(mSd, F_GETFL)) < 0 ) { Error("fcntl(), errno = %d, error = %s", errno, strerror(errno)); return false; } if ( blocking ) { flags &= ~O_NONBLOCK; } else { flags |= O_NONBLOCK; } if ( fcntl(mSd, F_SETFL, flags) < 0 ) { Error("fcntl(), errno = %d, error = %s", errno, strerror(errno)); return false; } return true; } bool ZM::Socket::getSendBufferSize(int &buffersize) const { socklen_t optlen = sizeof(buffersize); if ( getsockopt(mSd, SOL_SOCKET, SO_SNDBUF, &buffersize, &optlen) < 0 ) { Error("getsockopt(), errno = %d, error = %s", errno, strerror(errno)); return -1; } return buffersize; } bool ZM::Socket::getRecvBufferSize(int &buffersize) const { socklen_t optlen = sizeof(buffersize); if ( getsockopt(mSd, SOL_SOCKET, SO_RCVBUF, &buffersize, &optlen) < 0 ) { Error("getsockopt(), errno = %d, error = %s", errno, strerror(errno)); return -1; } return buffersize; } bool ZM::Socket::setSendBufferSize(int buffersize) { if ( setsockopt(mSd, SOL_SOCKET, SO_SNDBUF, (char *)&buffersize, sizeof(buffersize)) < 0 ) { Error("setsockopt(), errno = %d, error = %s", errno, strerror(errno)); return false; } return true; } bool ZM::Socket::setRecvBufferSize(int buffersize) { if ( setsockopt( mSd, SOL_SOCKET, SO_RCVBUF, (char *)&buffersize, sizeof(buffersize)) < 0 ) { Error("setsockopt(), errno = %d, error = %s", errno, strerror(errno)); return false; } return true; } bool ZM::Socket::getRouting(bool &route) const { int dontRoute; socklen_t optlen = sizeof(dontRoute); if ( getsockopt(mSd, SOL_SOCKET, SO_DONTROUTE, &dontRoute, &optlen) < 0 ) { Error("getsockopt(), errno = %d, error = %s", errno, strerror(errno)); return false; } route = !dontRoute; return true; } bool ZM::Socket::setRouting(bool route) { int dontRoute = !route; if ( setsockopt(mSd, SOL_SOCKET, SO_DONTROUTE, (char *)&dontRoute, sizeof(dontRoute)) < 0 ) { Error("setsockopt(), errno = %d, error = %s", errno, strerror(errno)); return false; } return true; } bool ZM::Socket::getNoDelay(bool &nodelay) const { int int_nodelay; socklen_t optlen = sizeof(int_nodelay); if ( getsockopt(mSd, IPPROTO_TCP, TCP_NODELAY, &int_nodelay, &optlen) < 0 ) { Error("getsockopt(), errno = %d, error = %s", errno, strerror(errno)); return false; } nodelay = int_nodelay; return true; } bool ZM::Socket::setNoDelay(bool nodelay) { int int_nodelay = nodelay; if ( setsockopt(mSd, IPPROTO_TCP, TCP_NODELAY, (char *)&int_nodelay, sizeof(int_nodelay)) < 0 ) { Error("setsockopt(), errno = %d, error = %s", errno, strerror(errno)); return false; } return true; } bool ZM::InetSocket::connect(const char *host, const char *serv) { struct addrinfo hints; struct addrinfo *result, *rp; int s; char buf[255]; mAddressFamily = AF_UNSPEC; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ hints.ai_socktype = getType(); hints.ai_flags = 0; hints.ai_protocol = 0; /* Any protocol */ s = getaddrinfo(host, serv, &hints, &result); if ( s != 0 ) { Error("connect(): getaddrinfo: %s", gai_strerror(s)); return false; } /* getaddrinfo() returns a list of address structures. * Try each address until we successfully connect(2). * If socket(2) (or connect(2)) fails, we (close the socket * and) try the next address. */ for ( rp = result; rp != nullptr; rp = rp->ai_next ) { if ( mSd != -1 ) { if ( ::connect(mSd, rp->ai_addr, rp->ai_addrlen) != -1 ) break; /* Success */ continue; } memset(&buf, 0, sizeof(buf)); if ( rp->ai_family == AF_INET ) { inet_ntop(AF_INET, &((struct sockaddr_in *)rp->ai_addr)->sin_addr, buf, sizeof(buf)-1); } else if (rp->ai_family == AF_INET6) { inet_ntop(AF_INET6, &((struct sockaddr_in6 *)rp->ai_addr)->sin6_addr, buf, sizeof(buf)-1); } else { strncpy(buf, "n/a", sizeof(buf)-1); } Debug(1, "connect(): Trying '%s', family '%d', proto '%d'", buf, rp->ai_family, rp->ai_protocol); mSd = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if ( mSd == -1 ) continue; int val = 1; (void)::setsockopt(mSd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)); (void)::setsockopt(mSd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)); mAddressFamily = rp->ai_family; /* save AF_ for ctrl and data connections */ if ( ::connect(mSd, rp->ai_addr, rp->ai_addrlen) != -1 ) break; /* Success */ ::close(mSd); } // end for freeaddrinfo(result); /* No longer needed */ if ( rp == nullptr ) { /* No address succeeded */ Error("connect(), Could not connect"); mAddressFamily = AF_UNSPEC; return false; } mState = CONNECTED; return true; } bool ZM::InetSocket::connect(const char *host, int port) { char serv[8]; snprintf(serv, sizeof(serv), "%d", port); return connect(host, serv); } bool ZM::InetSocket::bind(const char * host, const char * serv) { struct addrinfo hints; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ hints.ai_socktype = getType(); hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */ hints.ai_protocol = 0; /* Any protocol */ hints.ai_canonname = nullptr; hints.ai_addr = nullptr; hints.ai_next = nullptr; struct addrinfo *result, *rp; int s = getaddrinfo(host, serv, &hints, &result); if ( s != 0 ) { Error("bind(): getaddrinfo: %s", gai_strerror(s)); return false; } char buf[255]; /* getaddrinfo() returns a list of address structures. * Try each address until we successfully bind(2). * If socket(2) (or bind(2)) fails, we (close the socket * and) try the next address. */ for ( rp = result; rp != nullptr; rp = rp->ai_next ) { memset(&buf, 0, sizeof(buf)); if ( rp->ai_family == AF_INET ) { inet_ntop(AF_INET, &((struct sockaddr_in *)rp->ai_addr)->sin_addr, buf, sizeof(buf)-1); } else if ( rp->ai_family == AF_INET6 ) { inet_ntop(AF_INET6, &((struct sockaddr_in6 *)rp->ai_addr)->sin6_addr, buf, sizeof(buf)-1); } else { strncpy(buf, "n/a", sizeof(buf)-1); } Debug(1, "bind(): Trying '%s', family '%d', proto '%d'", buf, rp->ai_family, rp->ai_protocol); mSd = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if ( mSd == -1 ) continue; mState = DISCONNECTED; if ( ::bind(mSd, rp->ai_addr, rp->ai_addrlen) == 0 ) break; /* Success */ ::close(mSd); mSd = -1; } // end foreach result if ( rp == nullptr ) { /* No address succeeded */ Error("bind(), Could not bind"); return false; } freeaddrinfo(result); /* No longer needed */ return true; } bool ZM::InetSocket::bind(const char * serv) { return bind(nullptr, serv); } bool ZM::InetSocket::bind(const char * host, int port) { char serv[8]; snprintf(serv, sizeof(serv), "%d", port); return bind(host, serv); } bool ZM::InetSocket::bind(int port) { char serv[8]; snprintf(serv, sizeof(serv), "%d", port); return bind(nullptr, serv); } bool ZM::TcpInetServer::listen() { return Socket::listen(); } bool ZM::TcpInetServer::accept() { return Socket::accept(); } bool ZM::TcpInetServer::accept(TcpInetSocket *&newSocket) { int newSd = -1; newSocket = nullptr; if ( !Socket::accept(newSd) ) return false; newSocket = new TcpInetSocket(*this, newSd); return true; } bool ZM::TcpUnixServer::accept(TcpUnixSocket *&newSocket) { int newSd = -1; newSocket = nullptr; if ( !Socket::accept(newSd) ) return false; newSocket = new TcpUnixSocket(*this, newSd); return true; } ZM::Select::Select() : mHasTimeout(false), mMaxFd(-1) { } ZM::Select::Select(struct timeval timeout) : mMaxFd(-1) { setTimeout(timeout); } ZM::Select::Select(int timeout) : mMaxFd(-1) { setTimeout(timeout); } ZM::Select::Select(double timeout) : mMaxFd(-1) { setTimeout(timeout); } void ZM::Select::setTimeout(int timeout) { mTimeout.tv_sec = timeout; mTimeout.tv_usec = 0; mHasTimeout = true; } void ZM::Select::setTimeout(double timeout) { mTimeout.tv_sec = int(timeout); mTimeout.tv_usec = suseconds_t((timeout-mTimeout.tv_sec)*1000000.0); mHasTimeout = true; } void ZM::Select::setTimeout(struct timeval timeout) { mTimeout = timeout; mHasTimeout = true; } void ZM::Select::clearTimeout() { mHasTimeout = false; } void ZM::Select::calcMaxFd() { mMaxFd = -1; for ( CommsSet::iterator iter = mReaders.begin(); iter != mReaders.end(); ++iter ) { if ( (*iter)->getMaxDesc() > mMaxFd ) mMaxFd = (*iter)->getMaxDesc(); } for ( CommsSet::iterator iter = mWriters.begin(); iter != mWriters.end(); ++iter ) { if ( (*iter)->getMaxDesc() > mMaxFd ) mMaxFd = (*iter)->getMaxDesc(); } } bool ZM::Select::addReader(CommsBase *comms) { if ( !comms->isOpen() ) { Error("Unable to add closed reader"); return false; } std::pair result = mReaders.insert(comms); if ( result.second ) { if ( comms->getMaxDesc() > mMaxFd ) mMaxFd = comms->getMaxDesc(); } return result.second; } bool ZM::Select::deleteReader(CommsBase *comms) { if ( !comms->isOpen() ) { Error("Unable to add closed reader"); return false; } if ( mReaders.erase(comms) ) { calcMaxFd(); return true; } return false; } void ZM::Select::clearReaders() { mReaders.clear(); mMaxFd = -1; } bool ZM::Select::addWriter(CommsBase *comms) { std::pair result = mWriters.insert(comms); if ( result.second ) { if ( comms->getMaxDesc() > mMaxFd ) mMaxFd = comms->getMaxDesc(); } return result.second; } bool ZM::Select::deleteWriter(CommsBase *comms) { if ( mWriters.erase(comms) ) { calcMaxFd(); return true; } return false; } void ZM::Select::clearWriters() { mWriters.clear(); mMaxFd = -1; } int ZM::Select::wait() { struct timeval tempTimeout = mTimeout; struct timeval *selectTimeout = mHasTimeout?&tempTimeout:nullptr; fd_set rfds; fd_set wfds; mReadable.clear(); FD_ZERO(&rfds); for ( CommsSet::iterator iter = mReaders.begin(); iter != mReaders.end(); ++iter ) FD_SET((*iter)->getReadDesc(), &rfds); mWriteable.clear(); FD_ZERO(&wfds); for ( CommsSet::iterator iter = mWriters.begin(); iter != mWriters.end(); ++iter ) FD_SET((*iter)->getWriteDesc(), &wfds); int nFound = select(mMaxFd+1, &rfds, &wfds, nullptr, selectTimeout); if ( nFound == 0 ) { Debug(1, "Select timed out"); } else if ( nFound < 0 ) { Error("Select error: %s", strerror(errno)); } else { for ( CommsSet::iterator iter = mReaders.begin(); iter != mReaders.end(); ++iter ) if ( FD_ISSET((*iter)->getReadDesc(), &rfds) ) mReadable.push_back(*iter); for ( CommsSet::iterator iter = mWriters.begin(); iter != mWriters.end(); ++iter ) if ( FD_ISSET((*iter)->getWriteDesc(), &rfds) ) mWriteable.push_back(*iter); } return nFound; } const ZM::Select::CommsList &ZM::Select::getReadable() const { return mReadable; } const ZM::Select::CommsList &ZM::Select::getWriteable() const { return mWriteable; }