//| Copyright: (C) 2020-2024 Kevin Larke //| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file. #include "cwCommon.h" #include "cwLog.h" #include "cwCommonImpl.h" #include "cwMem.h" #include "cwTest.h" #include "cwObject.h" #include "cwThread.h" #include "cwText.h" #include #include #include #include #include #include #include #include // close #include #include "cwSocket.h" #define cwSOCKET_SYS_ERR (-1) #define cwSOCKET_NULL_SOCK (-1) #ifndef HOST_NAME_MAX #define HOST_NAME_MAX _POSIX_HOST_NAME_MAX #endif namespace cw { namespace sock { enum { kIsConnectedFl = 0x01, kIsBlockingFl = 0x02 }; typedef struct sock_str { unsigned userId; unsigned connId; int sockH; unsigned createFlags; unsigned flags; callbackFunc_t cbFunc; void* cbArg; struct sockaddr_in localSockAddr; struct sockaddr_in remoteSockAddr; char ntopBuf[ INET_ADDRSTRLEN+1 ]; // use INET6_ADDRSTRLEN for IPv6 char hnameBuf[ HOST_NAME_MAX+1 ]; struct pollfd* pollfd; unsigned nextConnId; struct sock_str* parent; // pointer to this socket's parent socket struct sock_str* children; // pointer to this sockets children (parent==NULL), or sibling (parent!=NULL) } sock_t; typedef struct mgr_str { uint8_t* buf; // buf[bufByteN] unsigned bufByteN; // size of buf[] struct pollfd* pollfdA; // pollfdA[ sockMaxN ] poll array sock_t* sockA; //.sockA[ sockMaxN ] unsigned sockMaxN; // sockMaxN count of elements in sockA[] and pollfdA[] unsigned sockN; // sockA[ sockN ] sock record in use } mgr_t; mgr_t* _handleToPtr( handle_t h) { return handleToPtr(h); } sock_t* _idToSock( mgr_t* p, unsigned userId, bool showErrorFl=false ) { for(unsigned i=0; isockN; ++i) if( p->sockA[i].userId == userId ) return p->sockA + i; if( showErrorFl ) cwLogError(kInvalidIdRC,"A socket with id:%i could not be found.",userId); return nullptr; } rc_t _getMgrAndSocket( handle_t h, unsigned userId, mgr_t*& p, sock_t*& s, bool showErrorFl = false ) { p = _handleToPtr(h); if((s = _idToSock(p,userId,showErrorFl)) == nullptr ) { if( showErrorFl ) return cwLogError(kInvalidArgRC,"The socket associated with id '%i' could not be found.",userId); } return kOkRC; } bool _sockIsOpen( sock_t* p ) { return p->sockH != cwSOCKET_NULL_SOCK; } void _unlinkChild( sock_t* s ) { sock_t* ps = s->parent; sock_t* cs0 = nullptr; for(sock_t* cs=ps->children; cs!=nullptr; cs=cs->children) { if( cs == s ) { if( cs0 == nullptr ) ps->children = cs->children; else cs0->children = cs->children; cs->children = nullptr; cs->parent = nullptr; break; } cs0 = cs; } } rc_t _closeSock( mgr_t* p, sock_t* s ) { rc_t rc = kOkRC; // close the socket if( s->sockH != cwSOCKET_NULL_SOCK ) { errno = 0; if( ::close(s->sockH) != 0 ) rc = cwLogSysError(kOpFailRC,errno,"The socket close failed." ); s->sockH = cwSOCKET_NULL_SOCK; } if( s->parent != nullptr ) _unlinkChild( s ); s->userId = kInvalidId; s->createFlags = 0; s->flags = 0; s->pollfd->events = 0; s->pollfd->fd = cwSOCKET_NULL_SOCK; // poll() ignores records when fd < 0 s->remoteSockAddr.sin_family = AF_UNSPEC; return rc; } rc_t _destroyMgr( mgr_t* p ) { rc_t rc = kOkRC; for(unsigned i=0; isockN; ++i) { rc_t rc0 = _closeSock(p,p->sockA + i); if( rc0 != kOkRC ) rc = rc0; } mem::release(p->sockA); mem::release(p->pollfdA); mem::release(p->buf); mem::release(p); return rc; } // Locate an available slot in p->sockA and return the index of the slot in availIdxRef. rc_t _locateAvailSlot( mgr_t* p, unsigned& availIdx_Ref ) { availIdx_Ref = kInvalidIdx; // locate an avail. socket between 0:sockN for(unsigned i=0; isockN; ++i) if( !_sockIsOpen(p->sockA + i) ) { availIdx_Ref = i; return kOkRC; } // Be sure all slots are not already in use. if( p->sockN >= p->sockMaxN ) return cwLogError(kResourceNotAvailableRC,"All socket slots are in use."); // Expand the slot array to accommodate another socket availIdx_Ref = p->sockN; p->sockN += 1; return kOkRC; } void _callback( sock_t* s, cbOpId_t opId, const struct sockaddr_in* srcAddr=nullptr, const void* buf=nullptr, unsigned bufByteN=0 ) { if( s->cbFunc != nullptr ) s->cbFunc( s->cbArg, opId, s->userId, s->connId, buf, bufByteN, srcAddr ); } rc_t _accept( mgr_t* p, sock_t* s ) { rc_t rc = kOkRC; struct sockaddr_storage remoteAddr; socklen_t sin_size = sizeof(remoteAddr); int fd; unsigned sockIdx = kInvalidIdx; sock_t* cs = nullptr; // accept the incoming connection if((fd = accept(s->sockH, (struct sockaddr*)&remoteAddr, &sin_size )) < 0) { if( errno == EAGAIN || errno == EWOULDBLOCK ) rc = kTimeOutRC; else { rc = cwLogSysError(kOpFailRC,errno,"Socket accept() failed."); goto errLabel; } } // ... then find an available socket record if((rc = _locateAvailSlot(p, sockIdx )) != kOkRC ) { rc = cwLogError(rc,"There are no available slots to 'accept' a new socket connection."); goto errLabel; } // initialize the socket record cs = p->sockA + sockIdx; cs->userId = s->userId; cs->connId = s->nextConnId++; cs->sockH = fd; cs->createFlags = 0; cs->remoteSockAddr = *(struct sockaddr_in*)&remoteAddr; cs->cbFunc = s->cbFunc; cs->cbArg = s->cbArg; cs->pollfd = p->pollfdA + sockIdx; cs->pollfd->events = POLLIN; cs->pollfd->fd = fd; cs->nextConnId = 0; cs->parent = s; cs->children = s->children; s->children = cs; if((rc = addrToString( (const struct sockaddr_in*)&remoteAddr, cs->ntopBuf, sizeof(cs->ntopBuf) )) != kOkRC ) goto errLabel; _callback( cs, kConnectCbId, (const struct sockaddr_in*)&remoteAddr); errLabel: if( rc != kOkRC ) close(fd); return rc; } // Called to read the socket when data is known to be waiting. rc_t _receive( mgr_t* p, sock_t* s, unsigned& readN_Ref, void* buf=nullptr, unsigned bufByteN=0, struct sockaddr_in* fromAddr=nullptr ) { rc_t rc = kOkRC; void* b = buf; unsigned bN = bufByteN; ssize_t bytesReadN = 0; struct sockaddr_in sockaddr; socklen_t sizeOfFromAddr = 0; memset(&sockaddr,0,sizeof(sockaddr)); // clear the count of actual bytes read readN_Ref = 0; // if the socket is not open then there is nothing to do if( !_sockIsOpen(s) ) return cwLogWarningRC( kResourceNotAvailableRC, "An attempt was made to read from a closed socket."); // if no buffer was given then use the default buffer if( b ==nullptr || bufByteN == 0 ) { b = p->buf; bN = p->bufByteN; } // if no src address buffer was given and this is a UDP socket if( fromAddr == nullptr && cwIsNotFlag(s->createFlags,kTcpFl) ) { fromAddr = &sockaddr; sizeOfFromAddr = sizeof(struct sockaddr_in); } errno = 0; // read the socket if((bytesReadN = recvfrom(s->sockH, b, bN, 0, (struct sockaddr*)fromAddr, &sizeOfFromAddr )) != cwSOCKET_SYS_ERR ) { // return the count of actual bytes read readN_Ref = bytesReadN; // if no return buffer was given and the socket has a callback function - then call it if( bytesReadN > 0 && s->cbFunc != nullptr && (buf==nullptr || bufByteN==0) ) { // if no src addr was given (because this is a TCP socket) then use the connected remote socket if( fromAddr == nullptr && s->remoteSockAddr.sin_family != AF_UNSPEC) fromAddr = &s->remoteSockAddr; _callback( s, kReceiveCbId, fromAddr, b, bytesReadN ); } } else { // an error occurred during the read operation switch( errno ) { case EAGAIN: return kTimeOutRC; case ENOTCONN: if( cwIsFlag(s->flags,kIsConnectedFl ) ) cwLogWarning("Socket Disconnected."); s->flags = cwClrFlag(s->flags,kIsConnectedFl); _callback( s, kDisconnectCbId ); } return cwLogSysError(kOpFailRC,errno,"recvfrom() failed."); } return rc; } // Block on devices waiting for data on a port. // If userId is valid then wait for data on a specific port otherwise // wait for data on all ports. rc_t _poll( mgr_t* p, unsigned timeOutMs, unsigned& readN_Ref, unsigned userId=kInvalidId, void* buf=nullptr, unsigned bufByteN=0, struct sockaddr_in* fromAddr=nullptr ) { rc_t rc = kOkRC; int sysRC; readN_Ref = 0; // if there are not ports then there is nothing to do if( p->sockN == 0 ) return rc; struct pollfd* pfd = p->pollfdA; // first struct pollfd unsigned pfdN = p->sockN; // count of pollfd's sock_t* s = p->sockA; // port assoc'd with first pollfd // if only one socket is to be read ... if( userId != kInvalidId ) { // ... then locate it if((s = _idToSock(p,userId)) == nullptr ) return kInvalidArgRC; pfd = s->pollfd; pfdN = 1; } // A port to poll must exist if( p == nullptr ) return cwLogError(kInvalidArgRC,"The port with id %i could not be found.",userId); // block waiting for data on one of the ports if((sysRC = ::poll(pfd,pfdN,timeOutMs)) == 0) { rc = kTimeOutRC; } else { // ::poll() encountered a system exception if( sysRC < 0 ) return cwLogSysError(kReadFailRC,errno,"Poll failed on socket."); // store the current sockA[] size because p->sockN may change inside the loop unsigned sockN = p->sockN; // interate through the ports looking for the sockets which have data waiting ... for(unsigned i=0; isockA[i].pollfd->revents,POLLHUP) || cwIsFlag(p->sockA[i].pollfd->revents,POLLNVAL) ) { _callback( p->sockA + i, kDisconnectCbId ); _closeSock(p,p->sockA+i); continue; } // if an error occured on this socket if( p->sockA[i].pollfd->revents & POLLERR ) { cwLogError(kOpFailRC,"ERROR on socket user id:%i conn id:%i\n",p->sockA[i].userId,p->sockA[i].connId); // TODO: should the socket be closed? marked as disconnected? } if( p->sockA[i].pollfd->revents & POLLIN ) { unsigned actualReadN = 0; sock_t* s = p->sockA + i; // If this is a listening/streaming socket then it is waiting for connections if( cwAllFlags(s->createFlags,kListenFl|kStreamFl) ) { rc_t rc0; // accept a new connection to this socket if((rc0 = _accept( p, s)) != kOkRC ) rc = rc0; } else // otherwise it is a non-listening socket that is receiving data { if((rc = _receive( p, s, actualReadN, buf, bufByteN, fromAddr )) != kOkRC ) return rc; } readN_Ref += actualReadN; } } } return rc; } rc_t _initAddr( const char* addrStr, portNumber_t portNumber, struct sockaddr_in* retAddrPtr ) { memset(retAddrPtr,0,sizeof(struct sockaddr_in)); //if( portNumber == kInvalidPortNumber ) // return cwLogError(kInvalidArgRC,"The port number %i cannot be used.",kInvalidPortNumber); if( addrStr == nullptr ) retAddrPtr->sin_addr.s_addr = htonl(INADDR_ANY); else { errno = 0; if(inet_pton(AF_INET,addrStr,&retAddrPtr->sin_addr) == 0 ) return cwLogSysError(kOpFailRC,errno, "The network address string '%s' could not be converted to a netword address structure.",cwStringNullGuard(addrStr) ); } //retAddrPtr->sin_len = sizeof(struct sockaddr_in); retAddrPtr->sin_family = AF_INET; if( portNumber != kInvalidPortNumber ) retAddrPtr->sin_port = htons(portNumber); return kOkRC; } rc_t _connect( sock_t* s, const char* remoteAddr, portNumber_t remotePort ) { struct sockaddr_in addr; rc_t rc; // create the remote address if((rc = _initAddr( remoteAddr, remotePort, &addr )) != kOkRC ) return rc; errno = 0; // ... and connect this socket to the remote address/port if( connect(s->sockH, (struct sockaddr*)&addr, sizeof(addr)) == cwSOCKET_SYS_ERR ) { if( cwIsNotFlag(s->createFlags, kBlockingFl) && errno == EINPROGRESS ) { // if the socket is non-blocking the connection will complete asynchronously } else { return cwLogSysError(kOpFailRC, errno, "Socket connect to %s:%i failed.", cwStringNullGuard(remoteAddr), remotePort ); } } s->flags = cwSetFlag(s->flags,kIsConnectedFl); s->remoteSockAddr = addr; return rc; } rc_t _setTimeOutMs( sock_t* s, unsigned timeOutMs ) { rc_t rc = kOkRC; struct timeval timeOut; // set the socket time out timeOut.tv_sec = timeOutMs/1000; timeOut.tv_usec = (timeOutMs - (timeOut.tv_sec * 1000)) * 1000; if( setsockopt( s->sockH, SOL_SOCKET, SO_RCVTIMEO, &timeOut, sizeof(timeOut) ) == cwSOCKET_SYS_ERR ) { rc = cwLogSysError(kOpFailRC,errno, "Attempt to set the socket timeout failed." ); goto errLabel; } errLabel: return rc; } rc_t _get_info( int sockH, unsigned char outBuf[6], struct sockaddr_in* addr, const char* interfaceName ) { cw::rc_t rc = kOkRC; struct ifreq ifr; struct ifconf ifc; char buf[1024]; ifc.ifc_len = sizeof(buf); ifc.ifc_buf = buf; if (ioctl(sockH, SIOCGIFCONF, &ifc) == -1) { rc = cwLogSysError(kOpFailRC,errno,"ioctl(SIOCGIFCONF) failed."); return rc; } struct ifreq* it = ifc.ifc_req; const struct ifreq* const end = it + (ifc.ifc_len / sizeof(struct ifreq)); for (; it != end; ++it) { if( strcmp(it->ifr_name,interfaceName ) == 0 ) { strcpy(ifr.ifr_name, it->ifr_name); if (ioctl(sockH, SIOCGIFFLAGS, &ifr) != 0) { rc = cwLogSysError(kOpFailRC,errno,"ioctl(SIOCGIFCONF) failed."); } else { if (! (ifr.ifr_flags & IFF_LOOPBACK)) { // don't count loopback if (ioctl(sockH, SIOCGIFHWADDR, &ifr) == 0) { memcpy(outBuf, ifr.ifr_hwaddr.sa_data, 6); if( addr != nullptr && ioctl(sockH, SIOCGIFADDR, &ifr) == 0) { addr->sin_addr = ((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr; } return kOkRC; } } } } } return cwLogError(kInvalidArgRC,"The network interface information for '%s' could not be found.", interfaceName); } } } cw::rc_t cw::sock::createMgr( handle_t& hRef, unsigned recvBufByteN, unsigned maxSockN ) { rc_t rc; if((rc = destroyMgr(hRef)) != kOkRC ) return rc; mgr_t* p = mem::allocZ(); p->buf = mem::allocZ(recvBufByteN); p->bufByteN = recvBufByteN; p->sockA = mem::allocZ( maxSockN ); p->pollfdA = mem::allocZ( maxSockN ); p->sockMaxN = maxSockN; for(unsigned i=0; isockMaxN; ++i) { p->sockA[i].sockH = cwSOCKET_NULL_SOCK; p->sockA[i].remoteSockAddr.sin_family = AF_UNSPEC; p->sockA[i].pollfd = p->pollfdA + i; } hRef.set(p); return rc; } cw::rc_t cw::sock::destroyMgr( handle_t& hRef ) { rc_t rc = kOkRC; if( ! hRef.isValid() ) return rc; mgr_t* p = _handleToPtr(hRef); if((rc = _destroyMgr(p)) != kOkRC ) return rc; hRef.clear(); return rc; } cw::rc_t cw::sock::create( handle_t h, unsigned userId, short port, unsigned flags, unsigned timeOutMs, callbackFunc_t cbFunc, void* cbArg, const char* remoteAddr, portNumber_t remotePort, const char* localAddr ) { rc_t rc = kOkRC; mgr_t* p; sock_t* s; // if a userId is being reused _getMgrAndSocket(h, userId, p, s, false ); if( s != nullptr ) if((rc = _closeSock(p,s)) != kOkRC ) return kInvalidArgRC; // if userId did not identify an existing socket ... if( s == nullptr ) { unsigned sockIdx; // ... then find an available socket record if((rc = _locateAvailSlot(p, sockIdx )) != kOkRC ) return rc; s = p->sockA + sockIdx; } int type = cwIsFlag(flags,kStreamFl) ? SOCK_STREAM : SOCK_DGRAM; int protocol = cwIsFlag(flags,kTcpFl) ? 0 : IPPROTO_UDP; // get a handle to the socket if(( s->sockH = ::socket( AF_INET, type, protocol ) ) == cwSOCKET_SYS_ERR ) return cwLogSysError(kOpFailRC, errno, "Socket create failed." ); s->userId = userId; s->connId = kInvalidId; s->createFlags = flags; s->flags = 0; s->cbFunc = cbFunc; s->cbArg = cbArg; s->pollfd = p->pollfdA + (s - p->sockA); s->pollfd->events = POLLIN; s->pollfd->fd = s->sockH; s->nextConnId = 0; // if this socket should block if( cwIsFlag(flags,kBlockingFl)) { if( timeOutMs > 0 ) { _setTimeOutMs(s,timeOutMs); } s->flags = cwSetFlag(s->flags,kIsBlockingFl); } else // otherwise this is a non-blocking socket { int opts; // get the socket options flags if( (opts = fcntl(s->sockH,F_GETFL)) < 0 ) { rc = cwLogSysError(kOpFailRC,errno, "Attempt to get the socket options flags failed." ); goto errLabel; } opts = (opts | O_NONBLOCK); // set the socket options flags if(fcntl(s->sockH,F_SETFL,opts) < 0) { rc = cwLogSysError(kOpFailRC,errno, "Attempt to set the socket to non-blocking failed." ); goto errLabel; } } if( cwIsFlag(flags,kReuseAddrFl) ) { unsigned int reuseaddr = 1; if( setsockopt(s->sockH, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuseaddr, sizeof(reuseaddr)) == cwSOCKET_SYS_ERR ) { rc = cwLogSysError(kOpFailRC,errno, "Attempt to set the socket 'reuse address' attribute failed." ); goto errLabel; } } #ifdef SO_REUSEPORT if( cwIsFlag(flags,kReusePortFl) ) { unsigned int reuseaddr = 1; if(setsockopt(s->sockH, SOL_SOCKET, SO_REUSEPORT, (const char*)&reuseaddr, sizeof(reuseaddr)) == cwSOCKET_SYS_ERR ) { rc = cwLogSysError(kOpFailRC,errno, "Attempt to set the socket 'reuse port' attribute failed." ); goto errLabel; } } #endif if( cwIsFlag(flags,kMultiCastTtlFl) ) { unsigned char ttl = 1; if( setsockopt(s->sockH, IPPROTO_IP, IP_MULTICAST_TTL, (const char*)&ttl, sizeof(ttl)) == cwSOCKET_SYS_ERR ) { rc = cwLogSysError(kOpFailRC,errno, "Attempt to set the socket 'multicast TTL' attribute failed." ); goto errLabel; } } if( cwIsFlag(flags,kMultiCastLoopFl) ) { unsigned char loopback = 1; if( setsockopt(s->sockH, IPPROTO_IP, IP_MULTICAST_LOOP, (const char*)&loopback, sizeof(loopback)) == cwSOCKET_SYS_ERR ) { rc = cwLogSysError(kOpFailRC,errno, "Attempt to set the socket 'reuse port' attribute failed." ); goto errLabel; } } // if broadcast option was requested. if( cwIsFlag(flags,kBroadcastFl) ) { int bcastFl = 1; if( setsockopt( s->sockH, SOL_SOCKET, SO_BROADCAST, &bcastFl, sizeof(bcastFl) ) == cwSOCKET_SYS_ERR ) { rc = cwLogSysError(kOpFailRC,errno, "Attempt to set the socket broadcast attribute failed." ); goto errLabel; } } if( cwIsFlag(flags,kTcpFl) && cwIsFlag(flags,kTcpNoDelayFl) ) { int nodelay_flag = 1; if( setsockopt( s->sockH, IPPROTO_TCP, TCP_NODELAY, (void *)&nodelay_flag, sizeof(nodelay_flag)) == cwSOCKET_SYS_ERR ) { rc = cwLogSysError(kOpFailRC,errno, "Attempt to set the socket NODELAY attribute failed." ); goto errLabel; } } // create the 32 bit local address if((rc = _initAddr( localAddr, port, &s->localSockAddr )) != kOkRC ) goto errLabel; // bind the socket to a local address/port if( (bind( s->sockH, (struct sockaddr*)&s->localSockAddr, sizeof(s->localSockAddr))) == cwSOCKET_SYS_ERR ) { rc = cwLogSysError(kOpFailRC,errno,"Socket bind failed." ); goto errLabel; } // get the local address as a string if((rc = addrToString( &s->localSockAddr, s->ntopBuf, sizeof(s->ntopBuf) )) != kOkRC ) goto errLabel; // if a remote addr was given connect this socket to it if( remoteAddr != nullptr ) if((rc = _connect(s,remoteAddr,remotePort)) != kOkRC ) goto errLabel; // if the socket should be marked for listening if( cwIsFlag(flags,kListenFl) ) { if( ::listen(s->sockH, 10) != 0 ) { rc = cwLogSysError(kOpFailRC,errno,"Socket listen() failed."); goto errLabel; } } errLabel: if(rc != kOkRC ) _closeSock(p,s); return rc; } cw::rc_t cw::sock::destroy( handle_t h, unsigned userId ) { rc_t rc = kOkRC; mgr_t* p; sock_t* s; if((rc = _getMgrAndSocket(h, userId, p, s )) != kOkRC ) { cwLogWarning("Socket destroy failed. The socket %i could not be found.",userId); return rc; } if((rc = _closeSock(p,s)) != kOkRC ) return rc; return rc; } cw::rc_t cw::sock::set_multicast_time_to_live( handle_t h, unsigned userId, unsigned seconds ) { rc_t rc = kOkRC; mgr_t* p; sock_t* s; if((rc = _getMgrAndSocket(h, userId, p, s )) != kOkRC ) return rc; if( setsockopt( s->sockH, IPPROTO_IP, IP_MULTICAST_TTL, &seconds, sizeof(seconds) ) == cwSOCKET_SYS_ERR ) { rc = cwLogSysError(kOpFailRC,errno, "Attempt to set the socket multicast TTLfailed." ); } return rc; } cw::rc_t cw::sock::join_multicast_group( handle_t h, unsigned userId, const char* addrStr ) { rc_t rc = kOkRC; mgr_t* p; struct ip_mreq req; sock_t* s; memset(&req, 0, sizeof(req)); if((rc = _getMgrAndSocket(h, userId, p, s )) != kOkRC ) return rc; if(inet_pton(AF_INET,addrStr,&req.imr_multiaddr.s_addr) == 0 ) { rc = cwLogSysError(kOpFailRC,errno, "The network address string '%s' could not be converted to a netword address structure.",cwStringNullGuard(addrStr) ); goto errLabel; } req.imr_interface.s_addr = INADDR_ANY; if(setsockopt(s->sockH, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&req, sizeof(req)) == cwSOCKET_SYS_ERR ) { rc = cwLogSysError(kOpFailRC,errno, "Attempt to add socket to multicast group on '%s' failed.", cwStringNullGuard(addrStr) ); goto errLabel; } errLabel: return rc; } // Set a destination address for this socket. Once a destination address is set // the caller may use send() to communicate with the specified remote socket // without having to specify a destination address on each call. cw::rc_t cw::sock::connect( handle_t h, unsigned userId, const char* remoteAddr, portNumber_t remotePort ) { rc_t rc = kOkRC; mgr_t* p; sock_t* s; if((rc = _getMgrAndSocket(h, userId, p, s )) != kOkRC ) return rc; return _connect(s,remoteAddr,remotePort); } bool cw::sock::isConnected( handle_t h, unsigned userId ) { rc_t rc = kOkRC; mgr_t* p; sock_t* s; if((rc = _getMgrAndSocket(h, userId, p, s )) != kOkRC ) return false; return cwIsFlag(s->flags,kIsConnectedFl); } cw::rc_t cw::sock::send( handle_t h, unsigned userId, unsigned connId, const void* data, unsigned dataByteCnt ) { rc_t rc = kOkRC; mgr_t* p; sock_t* s; // locate the socket to send on if((rc = _getMgrAndSocket(h, userId, p, s )) != kOkRC ) return rc; // If this a pre-connected socket ... if( cwIsFlag(s->flags,kIsConnectedFl) ) { if( ::send( s->sockH, data, dataByteCnt, 0 ) == cwSOCKET_SYS_ERR ) rc = cwLogSysError(kOpFailRC,errno,"Send failed."); } else // ... otherwise this is a listening socket with one or more child sockets { if( s->children == nullptr ) return cwLogError(kInvalidOpRC,"socket::send() only works with connected sockets."); sock_t* cs = s->children; for(; cs != nullptr; cs=cs->children) if( connId==kInvalidId || cs->connId == connId ) { errno = 0; if( ::send( cs->sockH, data, dataByteCnt, 0 ) == cwSOCKET_SYS_ERR ) rc = cwLogSysError(kOpFailRC,errno,"Send failed."); if( cs->connId == connId ) break; } } return rc; } cw::rc_t cw::sock::send( handle_t h, unsigned userId, const void* data, unsigned dataByteCnt, const struct sockaddr_in* remoteAddr ) { rc_t rc = kOkRC; mgr_t* p; sock_t* s; if((rc = _getMgrAndSocket(h, userId, p, s )) != kOkRC ) return rc; errno = 0; if( ::sendto(s->sockH, data, dataByteCnt, 0, (struct sockaddr*)remoteAddr, sizeof(*remoteAddr)) == cwSOCKET_SYS_ERR ) return cwLogSysError(kOpFailRC,errno,"Send to remote addr. failed."); return kOkRC; } cw::rc_t cw::sock::send( handle_t h, unsigned userId, const void* data, unsigned dataByteCnt, const char* remoteAddr, portNumber_t remotePort ) { rc_t rc = kOkRC; mgr_t* p; sock_t* s; struct sockaddr_in addr; if((rc = _getMgrAndSocket(h, userId, p, s )) != kOkRC ) return rc; if((rc = _initAddr(remoteAddr,remotePort,&addr)) != kOkRC ) return rc; return send( h, userId, data, dataByteCnt, &addr ); } cw::rc_t cw::sock::receive_all( handle_t h, unsigned timeOutMs, unsigned& readByteN_Ref ) { rc_t rc = kOkRC; mgr_t* p = _handleToPtr(h); if((rc = _poll( p, timeOutMs, readByteN_Ref )) != kOkRC && rc != kTimeOutRC ) return cwLogError(rc,"Socket receive failed."); return rc; } cw::rc_t cw::sock::receive(handle_t h, unsigned userId, unsigned& readByteN_Ref, void* buf, unsigned bufByteN, struct sockaddr_in* fromAddr ) { rc_t rc = kOkRC; mgr_t* p; sock_t* s; if((rc = _getMgrAndSocket(h, userId, p, s )) != kOkRC ) return rc; if((rc = _receive( p, s, readByteN_Ref, buf, bufByteN, fromAddr )) != kOkRC ) return rc; return rc; } cw::rc_t cw::sock::get_mac( handle_t h, unsigned userId, unsigned char outBuf[6], struct sockaddr_in* addr, const char* netInterfaceName ) { mgr_t* p; sock_t* s; rc_t rc; if((rc = _getMgrAndSocket(h, userId, p, s )) != kOkRC ) return rc; return _get_info(s->sockH, outBuf, addr, netInterfaceName ); } cw::rc_t cw::sock::initAddr( const char* addrStr, portNumber_t portNumber, struct sockaddr_in* retAddrPtr ) { return _initAddr(addrStr,portNumber,retAddrPtr); } cw::rc_t cw::sock::addrToString( const struct sockaddr_in* addr, char* buf, unsigned bufN ) { rc_t rc = kOkRC; errno = 0; if( inet_ntop(AF_INET, &(addr->sin_addr), buf, bufN) == nullptr) { rc = cwLogSysError(kOpFailRC,errno, "Network address to string conversion failed." ); goto errLabel; } buf[bufN-1]=0; errLabel: return rc; } bool cw::sock::addrIsEqual( const struct sockaddr_in* a0, const struct sockaddr_in* a1 ) { return a0->sin_family == a1->sin_family && a0->sin_port == a1->sin_port && memcmp(&a0->sin_addr,&a1->sin_addr,sizeof(a0->sin_addr))==0; } const char* cw::sock::hostName( handle_t h, unsigned userId ) { rc_t rc = kOkRC; mgr_t* p; sock_t* s; if((rc = _getMgrAndSocket(h, userId, p, s )) != kOkRC ) return nullptr; errno = 0; if( gethostname(s->hnameBuf,HOST_NAME_MAX) != 0 ) { cwLogSysError(kOpFailRC,errno, "gethostname() failed." ); return nullptr; } s->hnameBuf[HOST_NAME_MAX] = 0; return s->hnameBuf; } const char* cw::sock::ipAddress( handle_t h, unsigned userId ) { rc_t rc = kOkRC; mgr_t* p; sock_t* s; if((rc = _getMgrAndSocket(h, userId, p, s )) != kOkRC ) return nullptr; return s->ntopBuf; } unsigned cw::sock::inetAddress( handle_t h, unsigned userId ) { rc_t rc = kOkRC; mgr_t* p; sock_t* s; if((rc = _getMgrAndSocket(h, userId, p, s )) != kOkRC ) return 0; return s->localSockAddr.sin_addr.s_addr; } cw::sock::portNumber_t cw::sock::port( handle_t h, unsigned userId ) { rc_t rc = kOkRC; mgr_t* p; sock_t* s; if((rc = _getMgrAndSocket(h, userId, p, s )) != kOkRC ) return rc; return ntohs(s->localSockAddr.sin_port); } cw::rc_t cw::sock::peername( handle_t h, unsigned userId, struct sockaddr_in* addr ) { rc_t rc = kOkRC; socklen_t n = sizeof(struct sockaddr_in); mgr_t* p; sock_t* s; if((rc = _getMgrAndSocket(h, userId, p, s )) != kOkRC ) return rc; if( getpeername(s->sockH, (struct sockaddr*)addr, &n) == cwSOCKET_SYS_ERR ) return cwLogSysError(kOpFailRC,errno,"Get peer name failed."); addr->sin_port = ntohs(addr->sin_port); return rc; } cw::rc_t cw::sock::get_info( const char* netInterfaceName, unsigned char mac[6], char* host, unsigned hostN, struct sockaddr_in* addr ) { rc_t rc = kOkRC; int sockH; if( host != nullptr ) if( gethostname(host,hostN) != 0 ) return cwLogSysError(kOpFailRC,errno,"Unable to get the local host name."); // get a handle to the socket if(( sockH = ::socket( AF_INET, SOCK_DGRAM, 0 ) ) == cwSOCKET_SYS_ERR ) return cwLogSysError(kOpFailRC,errno,"Unable to create temporary socket."); if((rc = _get_info(sockH,mac,addr,netInterfaceName)) != kOkRC ) goto errLabel; errLabel: close(sockH); return rc; } //=============================================================================================== namespace cw { namespace socksrv { typedef struct socksrv_str { sock::handle_t sockMgrH; thread::handle_t thH; unsigned timeOutMs; } socksrv_t; socksrv_t* _handleToPtr( handle_t h ) { return handleToPtr(h); } rc_t _destroyMgrSrv( socksrv_t* p ) { rc_t rc = kOkRC; // destroy the thread if((rc = destroy(p->thH)) != kOkRC ) return rc; // destroy the socket manager if((rc = sock::destroyMgr(p->sockMgrH)) != kOkRC ) goto errLabel; mem::release(p); errLabel: return rc; } bool _threadFunc( void* arg ) { socksrv_t* p = (socksrv_t*)arg; unsigned readByteN = 0; rc_t rc; // if((rc = sock::receive_all( p->sockMgrH, p->timeOutMs, readByteN)) != kOkRC && rc != kTimeOutRC ) { cwLogError(rc,"Socket Srv receive failed."); return false; } return true; } // Callback thread used by socksrv::test() below void _socketTestCbFunc( void* cbArg, sock::cbOpId_t cbId, unsigned userId, unsigned connId, const void* byteA, unsigned byteN, const struct sockaddr_in* srcAddr ) { rc_t rc; char addr[ INET_ADDRSTRLEN+1 ]; printf("type:%i user:%i conn:%i ", cbId, userId, connId ); if( srcAddr != nullptr ) if((rc = sock::addrToString( srcAddr, addr, INET_ADDRSTRLEN )) == kOkRC ) { printf("from %s ", addr ); } if( byteA != nullptr ) printf(" : %s ", (const char*)byteA); printf("\n"); } } } cw::rc_t cw::socksrv::createMgrSrv( handle_t& hRef, unsigned timeOutMs, unsigned recvBufByteN, unsigned maxSocketN ) { cw::rc_t rc = kOkRC; if((rc = destroyMgrSrv(hRef)) != kOkRC ) return rc; // allocate the object socksrv_t* p = mem::allocZ(); // create the socket manager if((rc = createMgr( p->sockMgrH, recvBufByteN, maxSocketN )) != kOkRC ) goto errLabel; // create the thread if((rc = thread::create( p->thH, _threadFunc, p, "sock")) != kOkRC ) goto errLabel; p->timeOutMs = timeOutMs; hRef.set(p); errLabel: return rc; } cw::rc_t cw::socksrv::destroyMgrSrv( handle_t& hRef ) { rc_t rc = kOkRC; if( !hRef.isValid() ) return kOkRC; socksrv_t* p = _handleToPtr(hRef); if((rc = _destroyMgrSrv(p)) != kOkRC ) return rc; hRef.clear(); return rc; } cw::sock::handle_t cw::socksrv::mgrHandle( handle_t h ) { socksrv_t* p = _handleToPtr(h); return p->sockMgrH; } cw::rc_t cw::socksrv::start( handle_t h ) { socksrv_t* p = _handleToPtr(h); return thread::unpause( p->thH ); } cw::rc_t cw::socksrv::stop( handle_t h ) { socksrv_t* p = _handleToPtr(h); return thread::pause( p->thH ); } cw::rc_t cw::socksrv::test( const char* localNicDevice, sock::portNumber_t localPort, const char* remoteAddrIp, sock::portNumber_t remotePort, unsigned flags ) { handle_t h; rc_t rc = kOkRC; unsigned timeOutMs = 50; unsigned recvBufByteN = 2048; unsigned maxSocketN = 10; unsigned userId = 10; unsigned sockFlags = sock::kNonBlockingFl | flags; bool serverFl = remoteAddrIp == nullptr; const char* localIpAddrPtr = nullptr; char localIpAddr[ INET_ADDRSTRLEN+1 ]; const unsigned sbufN = 31; char sbuf[ sbufN+1 ]; // if a local device was given if( localNicDevice != nullptr ) { struct sockaddr_in localAddr; unsigned char mac[6]; // get the IP address of the device if((rc = sock::get_info(localNicDevice, mac, nullptr, 0, &localAddr )) != kOkRC ) return cwLogError(rc,"Unable to obtain the local address information for the device:'%s'.",localNicDevice); // convert the IP address to a string if((rc = sock::addrToString( &localAddr, localIpAddr, INET_ADDRSTRLEN )) != kOkRC ) return cwLogError(rc,"Unable to convert the local address to a string for device:'%s'.",localNicDevice); localIpAddrPtr = localIpAddr; } if( serverFl ) printf("Server listening on %s port: %i\n", localIpAddrPtr==nullptr ? "" : localIpAddrPtr, localPort ); else printf("Client connecting to server %s:%i\n", remoteAddrIp,remotePort); // create the socket manager if((rc = createMgrSrv(h, timeOutMs, recvBufByteN, maxSocketN )) != kOkRC ) return cwLogError(rc,"Socket server create failed."); // start the socket manager if((rc = start(h)) != kOkRC ) { cwLogError(rc,"Socker server start failed."); goto errLabel; } // create a socket if((rc = create( mgrHandle(h), userId, localPort, sockFlags, timeOutMs, _socketTestCbFunc, nullptr, remoteAddrIp, remotePort, localIpAddrPtr)) != kOkRC ) { cwLogError(rc,"Socket server socket create failed."); goto errLabel; } printf("'quit' to exit\n"); // readline loop while( true ) { printf("? "); if( std::fgets(sbuf,sbufN,stdin) == sbuf ) { printf("Sending:%s",sbuf); // send a message to the remote socket if( sock::send( mgrHandle(h), userId, -1, sbuf, strlen(sbuf)+1 ) != kOkRC ) printf("Send failed."); if( strcmp(sbuf,"quit\n") == 0) break; } } errLabel: rc_t rc1 = kOkRC; // destroy the socket if( h.isValid() ) rc1 = destroy( mgrHandle(h), userId ); // destroy the socket manager rc_t rc2 = destroyMgrSrv(h); return rcSelect(rc,rc1,rc2); } cw::rc_t cw::socksrv::testMain( const object_t* cfg ) { rc_t rc = kOkRC; const char* protocol_str = nullptr; sock::portNumber_t localPort = 5688; const char* remoteAddrIp = nullptr; sock::portNumber_t remotePort = 5687; const char* localNicDevice = nullptr; unsigned flags = 0; if((rc = cfg->getv("protocol",protocol_str, "localPort",localPort )) != kOkRC ) { rc = cwLogError(rc,"Required arg. parse failed."); goto errLabel; } if((rc = cfg->getv_opt("remoteAddr",remoteAddrIp, "remotePort",remotePort, "nicDev",localNicDevice )) != kOkRC ) { rc = cwLogError(rc,"Optional arg. parse failed."); goto errLabel; } if( textIsEqual(protocol_str,"tcp") ) { flags |= sock::kTcpFl | sock::kStreamFl | sock::kReuseAddrFl | sock::kReusePortFl; if( remoteAddrIp == nullptr ) flags |= sock::kListenFl; } rc = test(localNicDevice,localPort,remoteAddrIp,remotePort,flags); errLabel: return rc; }