zoneminder/src/zm_comms.cpp

747 lines
20 KiB
C++

//
// 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 <arpa/inet.h> // for debug output
#include <cerrno>
#include <cstdarg>
#include <cstdio> // for snprintf
#include <fcntl.h>
#include <netinet/tcp.h>
#include <sys/ioctl.h>
#include <sys/param.h>
#include <utility>
#ifdef SOLARIS
#include <sys/filio.h> // 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<CommsSet::iterator, bool> 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<CommsSet::iterator, bool> 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;
}