//------------------------------------------------------------------------- // Desc: TCP/IP networking. // Tabs: 3 // // Copyright (c) 1998-2006 Novell, Inc. All Rights Reserved. // // This program is free software; you can redistribute it and/or // modify it under the terms of version 2 of the GNU General Public // License as published by the Free Software Foundation. // // 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, contact Novell, Inc. // // To contact Novell about this file by physical or electronic mail, // you may find current contact information at www.novell.com // // $Id: fcs_tcp.cpp 12329 2006-01-20 17:49:30 -0700 (Fri, 20 Jan 2006) ahodgkinson $ //------------------------------------------------------------------------- // These must be defined BEFORE any includes. Unfortunately, this // also means that we can't use our FLM_HPUX define because it hasn't // been set yet... #if defined( __hpux) || defined( hpux) #define _XOPEN_SOURCE_EXTENDED 1 #define _INCLUDE_HPUX_SOURCE #endif #include "flaimsys.h" #if defined( FLM_NLM) && !defined ( __MWERKS__) // Disable errors for "expression for 'while' is always false" // Needed for FD_SET macro #pragma warning 555 9 #endif #ifdef FLM_WIN #pragma warning(disable : 4127) // conditional expression is constant (from FD_SET()) #endif /******************************************************************** Desc: Constructor *********************************************************************/ FCS_TCP::FCS_TCP( void) { m_pszIp[ 0] = '\0'; m_pszName[ 0] = '\0'; m_pszPeerIp[ 0] = '\0'; m_pszPeerName[ 0] = '\0'; m_uiIOTimeout = 10; m_iSocket = INVALID_SOCKET; m_ulRemoteAddr = 0; m_bInitialized = FALSE; m_bConnected = FALSE; #ifndef FLM_UNIX if( !WSAStartup( MAKEWORD(2, 0), &m_wsaData)) { m_bInitialized = TRUE; } #endif } /******************************************************************** Desc: Destructor *********************************************************************/ FCS_TCP::~FCS_TCP( void ) { if( m_bConnected) { close(); } #ifndef FLM_UNIX if( m_bInitialized) { WSACleanup(); } #endif } /******************************************************************** Desc: Gets information about the local host machine. *********************************************************************/ RCODE FCS_TCP::_GetLocalInfo( void) { struct hostent * pHostEnt; FLMUINT32 ui32IPAddr; RCODE rc = FERR_OK; m_pszIp[ 0] = '\0'; m_pszName[ 0] = '\0'; if( m_pszName[ 0] == '\0') { if( gethostname( m_pszName, (unsigned)sizeof( m_pszName))) { rc = RC_SET( FERR_SVR_SOCK_FAIL); goto Exit; } } if( m_pszIp[ 0] == '\0' && (pHostEnt = gethostbyname( m_pszName)) != NULL) { ui32IPAddr = (FLMUINT32)(*((u_long*)pHostEnt->h_addr)); if( ui32IPAddr != (FLMUINT32)-1) { struct in_addr InAddr; InAddr.s_addr = ui32IPAddr; f_strcpy( m_pszIp, inet_ntoa( InAddr)); } } Exit: return( rc); } /******************************************************************** Desc: Gets information about the remote machine. *********************************************************************/ RCODE FCS_TCP::_GetRemoteInfo( void) { struct sockaddr_in SockAddrIn; char * InetAddr = NULL; struct hostent * HostsName; RCODE rc = FERR_OK; m_pszPeerIp[ 0] = '\0'; m_pszPeerName[ 0] = '\0'; SockAddrIn.sin_addr.s_addr = (unsigned)m_ulRemoteAddr; /* inet_ntoa() - converts a 32-bit value in in_addr format into an ASCII string representing the address in dotted notation. VISIT: NetWare: Macro in arpa/inet.h. "Apps with multiple threads should use NWinet_ntoa instead of inet_ntoa. Then we can get rid of the semaphore! */ InetAddr = inet_ntoa( SockAddrIn.sin_addr ); f_strcpy( m_pszPeerIp, InetAddr ); /* Try to get the peer's host name by looking up his IP address. If found, copy IP Host name "BEVIS@NOVELL.COM" to TCPInfo otherwise, use his IP address as IP name. VISIT: Netware: "If your app has multiple threads, use either NWgethostbyaddr or NetDBgethostbyaddr(). This does the blocking? This may be done already in netdb.h - it is hard to tell. */ HostsName = gethostbyaddr( (char *)&SockAddrIn.sin_addr.s_addr, (unsigned)sizeof( u_long), AF_INET ); if( HostsName != NULL) { f_strcpy( m_pszPeerName, (char*) HostsName->h_name ); } else { if (!InetAddr) { InetAddr = inet_ntoa( SockAddrIn.sin_addr); } f_strcpy( m_pszPeerName, InetAddr ); } return( rc); } /******************************************************************** Desc: Tests for socket data readiness *********************************************************************/ RCODE FCS_TCP::_SocketPeek( FLMINT iTimeoutVal, FLMBOOL bPeekRead ) { struct timeval TimeOut; int iMaxDescs; fd_set GenDescriptors; fd_set * DescrRead; fd_set * DescrWrt; RCODE rc = FERR_OK; if( m_iSocket != INVALID_SOCKET) { FD_ZERO( &GenDescriptors ); FD_SET( m_iSocket, &GenDescriptors ); iMaxDescs = (int)(m_iSocket + 1); DescrRead = bPeekRead ? &GenDescriptors : NULL; DescrWrt = bPeekRead ? NULL : &GenDescriptors; TimeOut.tv_sec = (long)iTimeoutVal; TimeOut.tv_usec = (long)0; if( select( iMaxDescs, DescrRead, DescrWrt, NULL, &TimeOut) < 0 ) { rc = RC_SET( FERR_SVR_SELECT_ERR); goto Exit; } else { if( !FD_ISSET( m_iSocket, &GenDescriptors)) { rc = bPeekRead ? RC_SET( FERR_SVR_READ_TIMEOUT) : RC_SET( FERR_SVR_WRT_TIMEOUT); } } } else { rc = RC_SET( FERR_SVR_CONNECT_FAIL); } Exit: return( rc); } /******************************************************************** Desc: Writes data to the connection. *********************************************************************/ RCODE FCS_TCP::write( FLMBYTE * pucDataBuffer, FLMUINT uiDataCnt, FLMUINT * puiWrtCnt) { FLMUINT uiPartialCnt; FLMUINT uiToWrite; FLMUINT uiHaveWritten = 0; RCODE rc = FERR_OK; if( m_iSocket == INVALID_SOCKET) { rc = RC_SET( FERR_SVR_CONNECT_FAIL); } uiToWrite = uiDataCnt; *puiWrtCnt = 0; while( uiToWrite > 0) { /* The internal write call checks the arguments. */ if( RC_BAD( rc = _write( pucDataBuffer, uiToWrite, &uiPartialCnt))) { goto Exit; } pucDataBuffer += uiPartialCnt; uiHaveWritten += uiPartialCnt; uiToWrite = (FLMUINT)(uiDataCnt - uiHaveWritten); *puiWrtCnt = uiHaveWritten; } Exit: return( rc); } RCODE FCS_TCP::_write( FLMBYTE * pucBuffer, FLMUINT uiDataCnt, FLMUINT *puiWrtCnt) { FLMINT iRetryCount = 0; FLMINT iWrtCnt = 0; RCODE rc = FERR_OK; flmAssert( m_iSocket != INVALID_SOCKET && pucBuffer && uiDataCnt); Retry: *puiWrtCnt = 0; if ( RC_OK( rc = _SocketPeek( m_uiIOTimeout, FALSE))) { iWrtCnt = send( m_iSocket, (char *)pucBuffer, (int)uiDataCnt, 0 ); switch ( iWrtCnt ) { case -1: *puiWrtCnt = 0; rc = RC_SET( FERR_SVR_WRT_FAIL); break; case 0: rc = RC_SET( FERR_SVR_DISCONNECT); break; default: *puiWrtCnt = (FLMUINT)iWrtCnt; break; } } if( RC_BAD( rc) && rc != FERR_SVR_WRT_TIMEOUT) { #ifndef FLM_UNIX FLMINT iSockErr = WSAGetLastError(); #else FLMINT iSockErr = errno; #endif #if defined( FLM_WIN) || defined( FLM_NLM) if( iSockErr == WSAECONNABORTED) #else if( iSockErr == ECONNABORTED) #endif { rc = RC_SET( FERR_SVR_DISCONNECT); } #if defined( FLM_WIN) || defined( FLM_NLM) else if( iSockErr == WSAEWOULDBLOCK && iRetryCount < 5) #else else if( iSockErr == EWOULDBLOCK && iRetryCount < 5) #endif { iRetryCount++; f_sleep( (FLMUINT)(100 * iRetryCount)); goto Retry; } } return( rc); } /******************************************************************** Desc: Reads data from the connection *********************************************************************/ RCODE FCS_TCP::read( FLMBYTE * pucBuffer, FLMUINT uiDataCnt, FLMUINT * puiReadCnt) { FLMINT iReadCnt = 0; RCODE rc = FERR_OK; flmAssert( m_bConnected && pucBuffer && uiDataCnt); if( RC_OK( rc = _SocketPeek( m_uiIOTimeout, TRUE))) { iReadCnt = (FLMINT)recv( m_iSocket, (char *)pucBuffer, (int)uiDataCnt, 0); switch ( iReadCnt) { case -1: iReadCnt = 0; #if defined( FLM_WIN) || defined( FLM_NLM) if ( WSAGetLastError() == WSAECONNRESET) #else if( errno == ECONNRESET) #endif { rc = RC_SET( FERR_SVR_DISCONNECT); } else { rc = RC_SET( FERR_SVR_READ_FAIL); } break; case 0: rc = RC_SET( FERR_SVR_DISCONNECT); break; default: break; } } if( puiReadCnt) { *puiReadCnt = (FLMUINT)iReadCnt; } return( rc); } /******************************************************************** Desc: Reads data from the connection - Timeout valkue is zero, no error is generated if timeout occurs. *********************************************************************/ RCODE FCS_TCP::readNoWait( FLMBYTE * pucBuffer, FLMUINT uiDataCnt, FLMUINT * puiReadCnt) { FLMINT iReadCnt = 0; RCODE rc = FERR_OK; flmAssert( m_bConnected && pucBuffer && uiDataCnt); if( puiReadCnt) { *puiReadCnt = 0; } if( RC_OK( rc = _SocketPeek( (FLMUINT)0, TRUE))) { iReadCnt = recv( m_iSocket, (char *)pucBuffer, (int)uiDataCnt, 0); switch ( iReadCnt) { case -1: *puiReadCnt = 0; #if defined( FLM_WIN) || defined( FLM_NLM) if ( WSAGetLastError() == WSAECONNRESET) #else if( errno == ECONNRESET) #endif { rc = RC_SET( FERR_SVR_DISCONNECT); } else { rc = RC_SET( FERR_SVR_READ_FAIL); } goto Exit; case 0: rc = RC_SET( FERR_SVR_DISCONNECT); goto Exit; default: break; } } else if (rc == FERR_SVR_READ_TIMEOUT) { rc = FERR_OK; } if( puiReadCnt) { *puiReadCnt = (FLMUINT)iReadCnt; } Exit: return( rc); } /******************************************************************** Desc: Reads data and does not return until all requested data has been read or a timeout error has been encountered. *********************************************************************/ RCODE FCS_TCP::readAll( FLMBYTE * pucBuffer, FLMUINT uiDataCnt, FLMUINT * puiReadCnt) { FLMUINT uiToRead = 0; FLMUINT uiHaveRead = 0; FLMUINT uiPartialCnt; RCODE rc = FERR_OK; flmAssert( m_bConnected && pucBuffer && uiDataCnt); uiToRead = uiDataCnt; while( uiToRead) { if( RC_BAD( rc = read( pucBuffer, uiToRead, &uiPartialCnt))) { goto Exit; } pucBuffer += uiPartialCnt; uiHaveRead += uiPartialCnt; uiToRead = (FLMUINT)(uiDataCnt - uiHaveRead); if( puiReadCnt) { *puiReadCnt = uiHaveRead; } } Exit: return( rc); } /******************************************************************** Desc: Enables or disables Nagle's algorithm *********************************************************************/ RCODE FCS_TCP::setTcpDelay( FLMBOOL bOn) { RCODE rc = FERR_OK; int iOn; if( m_iSocket != INVALID_SOCKET) { iOn = bOn ? 1 : 0; if( (setsockopt( m_iSocket, IPPROTO_TCP, TCP_NODELAY, (char *)&iOn, (unsigned)sizeof( iOn) )) < 0) { rc = RC_SET( FERR_SVR_SOCKOPT_FAIL); goto Exit; } } else { rc = RC_SET( FERR_SVR_ALREADY_CLOSED); } Exit: return( rc); } /******************************************************************** Desc: Closes any open connections *********************************************************************/ void FCS_TCP::close( FLMBOOL bForce) { if( m_iSocket == INVALID_SOCKET) { goto Exit; } #ifdef FLM_NLM F_UNREFERENCED_PARM( bForce); #else if( !bForce) { char ucTmpBuf[ 128]; struct timeval tv; fd_set fds; fd_set fds_read; fd_set fds_err; // Close our half of the connection shutdown( m_iSocket, 1); // Set up to wait for readable data on the socket FD_ZERO( &fds); FD_SET( m_iSocket, &fds); tv.tv_sec = 10; tv.tv_usec = 0; fds_read = fds; fds_err = fds; // Wait for data or an error while( select( m_iSocket + 1, &fds_read, NULL, &fds_err, &tv) > 0) { if( recv( m_iSocket, ucTmpBuf, sizeof( ucTmpBuf), 0) <= 0) { break; } fds_read = fds; fds_err = fds; } shutdown( m_iSocket, 2); } #endif #ifndef FLM_UNIX closesocket( m_iSocket); #else ::close( m_iSocket); #endif Exit: m_iSocket = INVALID_SOCKET; m_bConnected = FALSE; } /******************************************************************** Desc: Creates a client object *********************************************************************/ FCS_TCP_CLIENT::FCS_TCP_CLIENT( void) : FCS_TCP() { m_bConnected = FALSE; } /******************************************************************** Desc: Closes any connections and frees client resources *********************************************************************/ FCS_TCP_CLIENT::~FCS_TCP_CLIENT( void ) { (void)close(); } /******************************************************************** Desc: Opens a new connection *********************************************************************/ RCODE FCS_TCP_CLIENT::openConnection( const char * pucHostName, FLMUINT uiPort, FLMUINT uiConnectTimeout, FLMUINT uiDataTimeout) { FLMINT iSockErr; FLMINT iTries; FLMINT iMaxTries = 5; struct sockaddr_in address; struct hostent * pHostEntry; u_long ulIPAddr; RCODE rc = FERR_OK; flmAssert( !m_bConnected); m_iSocket = INVALID_SOCKET; if( pucHostName && pucHostName[ 0] != '\0') { ulIPAddr = inet_addr( (char *)pucHostName); if( ulIPAddr == (u_long)INADDR_NONE) { pHostEntry = gethostbyname( (char *)pucHostName); if( !pHostEntry) { rc = RC_SET( FERR_SVR_NOIP_ADDR); goto Exit; } else { ulIPAddr = *((u_long*)pHostEntry->h_addr); } } } else { ulIPAddr = inet_addr( (char *)"127.0.0.1"); } /******************************************************/ /* Fill in the Socket structure with family type */ /******************************************************/ f_memset( (char*)&address, 0, sizeof( struct sockaddr_in)); address.sin_family = AF_INET; address.sin_addr.s_addr = (unsigned)ulIPAddr; address.sin_port = htons( (u_short)uiPort); /* Allocate a socket, then attempt to connect to it! */ if( (m_iSocket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) { rc = RC_SET( FERR_SVR_SOCK_FAIL); goto Exit; } /******************************************************/ /* Now attempt to connect with the specified */ /* partner host, time-out if connection */ /* doesn't complete within alloted time */ /******************************************************/ #ifdef FLM_WIN /* ** */ if ( uiConnectTimeout ) { if ( uiConnectTimeout < 5 ) { iMaxTries = (iMaxTries * uiConnectTimeout) / 5; uiConnectTimeout = 5; } } else { iMaxTries = 1; } #endif for( iTries = 0; iTries < iMaxTries; iTries++ ) { iSockErr = 0; if( connect( m_iSocket, (struct sockaddr *)&address, (unsigned)sizeof(struct sockaddr)) >= 0) { /* SUCCESS! */ break; } #ifndef FLM_UNIX iSockErr = WSAGetLastError(); #else iSockErr = errno; #endif #ifdef FLM_WIN /* In WIN, we sometimes get WSAEINVAL when, if we keep trying, we will eventually connect. Therefore, here we'll treat WSAEINVAL as EINPROGRESS. */ if( iSockErr == WSAEINVAL) { #ifndef FLM_UNIX closesocket( m_iSocket); #else ::close( m_iSocket); #endif if( (m_iSocket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) { rc = RC_SET( FERR_SVR_SOCK_FAIL); goto Exit; } #if defined( FLM_WIN) || defined( FLM_NLM) iSockErr = WSAEINPROGRESS; #else iSockErr = EINPROGRESS; #endif continue; } #endif #if defined( FLM_WIN) || defined( FLM_NLM) if( iSockErr == WSAEISCONN ) #else if( iSockErr == EISCONN ) #endif { break; } #if defined( FLM_WIN) || defined( FLM_NLM) else if( iSockErr == WSAEWOULDBLOCK) #else else if( iSockErr == EWOULDBLOCK) #endif { /* ** Let's wait a split second to give the connection ** request a chance. */ f_sleep( 100 ); continue; } #if defined( FLM_WIN) || defined( FLM_NLM) else if( iSockErr == WSAEINPROGRESS) #else else if( iSockErr == EINPROGRESS) #endif { if( RC_OK( rc = _SocketPeek( uiConnectTimeout, FALSE))) { /* ** Let's wait a split second to give the connection ** request a chance. */ f_sleep( 100 ); continue; } } rc = RC_SET( FERR_SVR_CONNECT_FAIL); } if( RC_BAD( rc)) { if( m_iSocket != INVALID_SOCKET) { #ifndef FLM_UNIX closesocket( m_iSocket); #else ::close( m_iSocket); #endif m_iSocket = INVALID_SOCKET; } goto Exit; } m_uiIOTimeout = uiDataTimeout; setTcpDelay( TRUE); m_bConnected = TRUE; Exit: return( rc); } /******************************************************************** Desc: Constructor *********************************************************************/ FCS_TCP_SERVER::FCS_TCP_SERVER( void) : FCS_TCP() { m_bBound = FALSE; } /******************************************************************** Desc: Destructor *********************************************************************/ FCS_TCP_SERVER::~FCS_TCP_SERVER( void) { if( m_bBound) { close( TRUE); } } /******************************************************************** Desc: Bind to a port prior to listening for connections *********************************************************************/ RCODE FCS_TCP_SERVER::bind( FLMUINT uiBindPort, FLMBYTE * pucBindAddr) { struct sockaddr_in address; RCODE rc = FERR_OK; if( m_bBound) { rc = RC_SET( FERR_SVR_SOCK_FAIL); goto Exit; } if( (m_iSocket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) { rc = RC_SET( FERR_SVR_SOCK_FAIL); goto Exit; } f_memset( &address, 0, sizeof( address)); address.sin_family = AF_INET; if( !pucBindAddr) { address.sin_addr.s_addr = htonl( INADDR_ANY); } else { address.sin_addr.s_addr = inet_addr( (char *)pucBindAddr); } address.sin_port = htons( (u_short)uiBindPort); // Bind to the address+port if( ::bind( m_iSocket, (struct sockaddr *)&address, (unsigned)sizeof( address)) != 0) { rc = RC_SET( FERR_SVR_BIND_FAIL); goto Exit; } /* ** Bind succeeded, ** listen() prepares a socket to accept a connection and specifies a ** queue limit for incoming connections. The accept() accepts the connection. ** Listen returns immediatly. ** Duane: Note for NetWare I spoke with Sravan Vadlakonda in San Jose, ** Netware allows 32 not 5 as the max. We set this high because the ** nonpreemptive nature of NLMs means we might not get back to this ** thread in time to accept all of the pending connections. As of ** Aug 97 the tcpip.nlm displays an error when we don't clean the q ** of pending connections fast enough. */ #ifdef FLM_NLM if( listen( m_iSocket, 32 ) < 0) #endif { if( listen( m_iSocket, 5 ) < 0) { rc = RC_SET( FERR_SVR_LISTEN_FAIL); goto Exit; } } /* Disable the packet send delay. */ setTcpDelay( TRUE); m_bBound = TRUE; Exit: if( RC_BAD( rc) && m_iSocket != INVALID_SOCKET) { #ifndef FLM_UNIX closesocket( m_iSocket); #else ::close( m_iSocket); #endif m_iSocket = INVALID_SOCKET; } return( rc); } /******************************************************************** Desc: Wait for and accept a client connection *********************************************************************/ RCODE FCS_TCP_SERVER::connectClient( FCS_TCP * pClient, FLMINT uiConnectTimeout, FLMINT uiDataTimeout) { SOCKET iSocket; #if defined( FLM_UNIX) socklen_t iAddrLen; #else int iAddrLen; #endif struct sockaddr_in address; RCODE rc = FERR_OK; if( !m_bBound) { rc = RC_SET( FERR_SVR_BIND_FAIL); goto Exit; } if( RC_BAD( rc = _SocketPeek( uiConnectTimeout, TRUE))) { goto Exit; } iAddrLen = (int)sizeof( struct sockaddr); if( (iSocket = accept( m_iSocket, (struct sockaddr *)&address, &iAddrLen)) == INVALID_SOCKET) { rc = RC_SET( FERR_SVR_ACCEPT_FAIL); goto Exit; } pClient->m_ulRemoteAddr = address.sin_addr.s_addr; pClient->m_iSocket = iSocket; pClient->m_bConnected = TRUE; pClient->m_uiIOTimeout = uiDataTimeout; pClient->setTcpDelay( TRUE); Exit: return( rc); }