//| 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 #include #include #include #include #include #include // close #include "cwTcpSocket.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 net { namespace socket { enum { kIsConnectedFl = 0x01, kIsBlockingFl = 0x02 }; typedef struct socket_str { int sockH; int fdH; unsigned createFlags; unsigned flags; struct sockaddr_in sockaddr; char ntopBuf[ INET_ADDRSTRLEN+1 ]; // use INET6_ADDRSTRLEN for IPv6 char hnameBuf[ HOST_NAME_MAX+1 ]; } socket_t; inline socket_t* _handleToPtr( handle_t h ) { return handleToPtr(h); } rc_t _destroy( socket_t* p ) { // close the fdH if( p->fdH != cwSOCKET_NULL_SOCK ) { errno = 0; if( ::close(p->fdH) != 0 ) cwLogSysError(kOpFailRC,errno,"The socket fd close failed." ); p->fdH = cwSOCKET_NULL_SOCK; } // close the socket if( p->sockH != cwSOCKET_NULL_SOCK ) { errno = 0; if( ::close(p->sockH) != 0 ) cwLogSysError(kOpFailRC,errno,"The socket close failed." ); p->sockH = cwSOCKET_NULL_SOCK; } mem::release(p); return kOkRC; } 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 == NULL ) 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( socket_t* p, 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(p->sockH, (struct sockaddr*)&addr, sizeof(addr)) == cwSOCKET_SYS_ERR ) return cwLogSysError(kOpFailRC, errno, "Socket connect failed." ); p->flags = cwSetFlag(p->flags,kIsConnectedFl); return rc; } rc_t _setTimeOutMs( socket_t* p, 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( p->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::net::socket::create( handle_t& hRef, portNumber_t port, unsigned flags, unsigned timeOutMs, const char* remoteAddr, portNumber_t remotePort, const char* localAddr) { rc_t rc; if((rc = destroy(hRef)) != kOkRC ) return rc; socket_t* p = mem::allocZ(); p->sockH = cwSOCKET_NULL_SOCK; p->fdH = cwSOCKET_NULL_SOCK; p->createFlags = flags; int type = cwIsFlag(flags,kStreamFl) ? SOCK_STREAM : SOCK_DGRAM; int protocol = cwIsFlag(flags,kTcpFl) ? 0 : IPPROTO_UDP; // get a handle to the socket if(( p->sockH = ::socket( AF_INET, type, protocol ) ) == cwSOCKET_SYS_ERR ) return cwLogSysError(kOpFailRC, errno, "Socket create failed." ); // if this socket should block if( cwIsFlag(flags,kBlockingFl)) { if( timeOutMs > 0 ) { _setTimeOutMs(p,timeOutMs); } p->flags = cwSetFlag(p->flags,kIsBlockingFl); } else { int opts; // get the socket options flags if( (opts = fcntl(p->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(p->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(p->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(p->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(p->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(p->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( p->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; } } // create the 32 bit local address if((rc = _initAddr( localAddr, port, &p->sockaddr )) != kOkRC ) goto errLabel; // bind the socket to a local address/port if( (bind( p->sockH, (struct sockaddr*)&p->sockaddr, sizeof(p->sockaddr))) == cwSOCKET_SYS_ERR ) { rc = cwLogSysError(kOpFailRC,errno,"Socket bind failed." ); goto errLabel; } // get the local address as a string if((rc = addrToString( &p->sockaddr, p->ntopBuf, sizeof(p->ntopBuf) )) != kOkRC ) goto errLabel; // if a remote addr was given connect this socket to it if( remoteAddr != NULL ) if((rc = _connect(p,remoteAddr,remotePort)) != kOkRC ) goto errLabel; // if the socket should be marked for listening if( cwIsFlag(flags,kListenFl) ) { if( ::listen(p->sockH, 10) != 0 ) { rc = cwLogSysError(kOpFailRC,errno,"Socket listen() failed."); goto errLabel; } } errLabel: if(rc != kOkRC ) _destroy(p); else hRef.set(p); return rc; } cw::rc_t cw::net::socket::destroy( handle_t& hRef ) { rc_t rc = kOkRC; if( !hRef.isValid() ) return rc; socket_t* p = _handleToPtr(hRef); if((rc = _destroy(p)) != kOkRC ) return rc; hRef.clear(); return rc; } unsigned cw::net::socket::flags( handle_t h ) { socket_t* p = _handleToPtr(h); return p->createFlags; } cw::rc_t cw::net::socket::set_multicast_time_to_live( handle_t h, unsigned seconds ) { rc_t rc = kOkRC; socket_t* p = _handleToPtr(h); if( setsockopt( p->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::net::socket::join_multicast_group( handle_t h, const char* addrStr ) { rc_t rc = kOkRC; socket_t* p = _handleToPtr(h); struct ip_mreq req; memset(&req, 0, sizeof(req)); 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(p->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; } cw::rc_t cw::net::socket::setTimeOutMs( handle_t h, unsigned timeOutMs ) { socket_t* p = _handleToPtr(h); return _setTimeOutMs(p,timeOutMs); } cw::rc_t cw::net::socket::accept( handle_t h ) { struct sockaddr_storage remoteAddr; // connector's address information socklen_t sin_size = sizeof(remoteAddr); rc_t rc = kOkRC; int fd = cwSOCKET_NULL_SOCK; socket_t* p = _handleToPtr(h); if((fd = accept(p->sockH, (struct sockaddr*)&remoteAddr, &sin_size)) < 0) { if( errno == EAGAIN || errno == EWOULDBLOCK ) rc = kTimeOutRC; else { rc = cwLogSysError(kOpFailRC,errno,"Socket accept() failed."); goto errLabel; } } else { char s[INET_ADDRSTRLEN+1]; addrToString( (struct sockaddr_in*)&remoteAddr, s, INET_ADDRSTRLEN ); if( p->fdH != cwSOCKET_NULL_SOCK ) { close(p->fdH); p->fdH = cwSOCKET_NULL_SOCK; } p->fdH = fd; p->flags = cwSetFlag(p->flags,kIsConnectedFl); /* if( false ) { struct sockaddr_in addr; char aBuf[ INET_ADDRSTRLEN+1 ]; peername( h, &addr ); addrToString( &addr, aBuf, INET_ADDRSTRLEN); printf("DNS-SD PEER: %i %s\n", addr.sin_port,aBuf ); } */ cwLogInfo("Connect:%s\n",s); } errLabel: return rc; } cw::rc_t cw::net::socket::connect( handle_t h, const char* remoteAddr, portNumber_t remotePort ) { socket_t* p = _handleToPtr(h); return _connect(p,remoteAddr,remotePort); } bool cw::net::socket::isConnected( handle_t h ) { socket_t* p = _handleToPtr(h); return cwIsFlag(p->flags,kIsConnectedFl); } cw::rc_t cw::net::socket::send( handle_t h, const void* data, unsigned dataByteCnt ) { socket_t* p = _handleToPtr(h); errno = 0; if( cwIsFlag(p->flags,kIsConnectedFl) == false ) return cwLogError(kInvalidOpRC,"socket::send() only works with connected sockets."); int fd = p->fdH != cwSOCKET_NULL_SOCK ? p->fdH : p->sockH; if( ::send( fd, data, dataByteCnt, 0 ) == cwSOCKET_SYS_ERR ) return cwLogSysError(kOpFailRC,errno,"Send failed."); return kOkRC; } cw::rc_t cw::net::socket::send( handle_t h, const void* data, unsigned dataByteCnt, const struct sockaddr_in* remoteAddr ) { socket_t* p = _handleToPtr(h); errno = 0; if( ::sendto(p->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::net::socket::send( handle_t h, const void* data, unsigned dataByteCnt, const char* remoteAddr, portNumber_t remotePort ) { rc_t rc; struct sockaddr_in addr; if((rc = _initAddr(remoteAddr,remotePort,&addr)) != kOkRC ) return rc; return send( h, data, dataByteCnt, &addr ); } cw::rc_t cw::net::socket::receive( handle_t h, char* data, unsigned dataByteCnt, unsigned* recvByteCntRef, struct sockaddr_in* fromAddr ) { socket_t* p = _handleToPtr(h); rc_t rc = kOkRC; ssize_t retVal = 0; socklen_t sizeOfRemoteAddr = fromAddr==NULL ? 0 : sizeof(struct sockaddr_in); errno = 0; if( recvByteCntRef != NULL ) *recvByteCntRef = 0; int fd = p->fdH != cwSOCKET_NULL_SOCK ? p->fdH : p->sockH; if((retVal = recvfrom(fd, data, dataByteCnt, 0, (struct sockaddr*)fromAddr, &sizeOfRemoteAddr )) == cwSOCKET_SYS_ERR ) { switch( errno ) { case EAGAIN: return kTimeOutRC; case ENOTCONN: if( cwIsFlag(p->flags,kIsConnectedFl ) ) cwLogWarning("Socket Disconnected."); p->flags = cwClrFlag(p->flags,kIsConnectedFl); } return cwLogSysError(kOpFailRC,errno,"recvfrom() failed."); } if( recvByteCntRef != NULL ) *recvByteCntRef = retVal; return rc; } cw::rc_t cw::net::socket::select_receive(handle_t h, char* buf, unsigned bufByteCnt, unsigned timeOutMs, unsigned* recvByteCntRef, struct sockaddr_in* fromAddr ) { rc_t rc = kOkRC; socket_t* p = _handleToPtr(h); fd_set rdSet; struct timeval timeOut; int fd = p->fdH != cwSOCKET_NULL_SOCK ? p->fdH : p->sockH; // setup the select() call FD_ZERO(&rdSet); FD_SET(fd, &rdSet ); timeOut.tv_sec = timeOutMs/1000; timeOut.tv_usec = (timeOutMs - (timeOut.tv_sec * 1000)) * 1000; if( recvByteCntRef != nullptr ) *recvByteCntRef = 0; // NOTE; select() takes the highest socket value plus one of all the sockets in all the sets. switch( select(fd+1,&rdSet,NULL,NULL,&timeOut) ) { case -1: // error if( errno != EINTR ) cwLogSysError(kOpFailRC,errno,"Select failed."); break; case 0: // select() timed out rc = kTimeOutRC; break; case 1: // (> 0) count of ready descripters if( FD_ISSET(fd,&rdSet) ) { socklen_t addrByteCnt = fromAddr==nullptr ? 0 : sizeof(*fromAddr); ssize_t retByteCnt; errno = 0; // recv the incoming msg into buf[] if(( retByteCnt = recvfrom( fd, buf, bufByteCnt, 0, (struct sockaddr*)fromAddr, &addrByteCnt )) == cwSOCKET_SYS_ERR ) { switch( errno ) { case ECONNRESET: if( cwIsFlag(p->flags,kIsConnectedFl) ) cwLogWarning("Socket Disconnected."); p->flags = cwClrFlag(p->flags,kIsConnectedFl); } rc = cwLogSysError(kOpFailRC,errno,"recvfrom() failed."); } else { // check for overflow if( retByteCnt == (ssize_t)bufByteCnt ) rc = cwLogError(kBufTooSmallRC,"The receive buffer requires more than %i bytes.",bufByteCnt); if( recvByteCntRef != nullptr ) *recvByteCntRef = retByteCnt; } } break; default: { cwAssert(0); } } // switch return rc; } cw::rc_t cw::net::socket::recv_from(handle_t h, char* buf, unsigned bufByteCnt, unsigned* recvByteCntRef, struct sockaddr_in* fromAddr ) { rc_t rc = kOkRC; socket_t* p = _handleToPtr(h); socklen_t addrlen = 0; int bytesN = 0; if( recvByteCntRef != nullptr ) *recvByteCntRef = 0; if( fromAddr != nullptr ) { addrlen = sizeof(*fromAddr); memset(fromAddr,0,sizeof(*fromAddr)); } int fd = p->fdH != cwSOCKET_NULL_SOCK ? p->fdH : p->sockH; if((bytesN = recvfrom(fd, buf, bufByteCnt, 0, (struct sockaddr*)fromAddr, &addrlen)) < 0 ) { // if this is a non-blocking socket then return value -1 indicates that no data is available. if( cwIsNotFlag( p->flags, kBlockingFl) && bytesN == -1) bytesN = 0; else { rc = cwLogSysError(kReadFailRC,errno,"recvfrom() failed."); goto errLabel; } } if( recvByteCntRef != nullptr ) *recvByteCntRef = bytesN; errLabel: return rc; } cw::rc_t cw::net::socket::get_mac( handle_t h, unsigned char outBuf[6], struct sockaddr_in* addr, const char* netInterfaceName ) { socket_t* p = _handleToPtr(h); return _get_info(p->sockH, outBuf, addr, netInterfaceName ); } cw::rc_t cw::net::socket::initAddr( const char* addrStr, portNumber_t portNumber, struct sockaddr_in* retAddrPtr ) { return _initAddr(addrStr,portNumber,retAddrPtr); } cw::rc_t cw::net::socket::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) == NULL) { rc = cwLogSysError(kOpFailRC,errno, "Network address to string conversion failed." ); goto errLabel; } buf[bufN-1]=0; errLabel: return rc; } bool cw::net::socket::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::net::socket::hostName( handle_t h ) { socket_t* p = _handleToPtr(h); errno = 0; if( gethostname(p->hnameBuf,HOST_NAME_MAX) != 0 ) { cwLogSysError(kOpFailRC,errno, "gethostname() failed." ); return NULL; } p->hnameBuf[HOST_NAME_MAX] = 0; return p->hnameBuf; } const char* cw::net::socket::ipAddress( handle_t h ) { socket_t* p = _handleToPtr(h); return p->ntopBuf; } unsigned cw::net::socket::inetAddress( handle_t h ) { socket_t* p = _handleToPtr(h); return p->sockaddr.sin_addr.s_addr; } cw::net::socket::portNumber_t cw::net::socket::port( handle_t h ) { socket_t* p = _handleToPtr(h); return ntohs(p->sockaddr.sin_port); } cw::rc_t cw::net::socket::peername( handle_t h, struct sockaddr_in* addr ) { rc_t rc = kOkRC; socklen_t n = sizeof(struct sockaddr_in); socket_t* p = _handleToPtr(h); if( getpeername(p->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::net::socket::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; }