467 lines
12 KiB
C++
467 lines
12 KiB
C++
//====== Copyright (c) 1996-2005, Valve Corporation, All rights reserved. ======//
|
|
//
|
|
// Purpose: Utility class to help in socket creation. Works for clients + servers
|
|
//
|
|
//===========================================================================//
|
|
|
|
#include "tier0/platform.h"
|
|
|
|
#if defined(_WIN32)
|
|
#if !defined(_X360)
|
|
#include <winsock.h>
|
|
#endif
|
|
#undef SetPort // winsock screws with the SetPort string... *sigh*
|
|
#define socklen_t int
|
|
#define MSG_NOSIGNAL 0
|
|
#elif defined(PLATFORM_POSIX)
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <netinet/tcp.h>
|
|
#include <errno.h>
|
|
#ifdef OSX
|
|
#define MSG_NOSIGNAL 0
|
|
#endif
|
|
#ifdef _PS3
|
|
// NOTE: this socket creator doesn't work on PS3
|
|
// here's a compile-hack:
|
|
#define EWOULDBLOCK EAGAIN
|
|
#else
|
|
#include <sys/ioctl.h>
|
|
#endif
|
|
#define closesocket close
|
|
#define WSAGetLastError() errno
|
|
#define ioctlsocket ioctl
|
|
#endif
|
|
#include <tier0/dbg.h>
|
|
#include "socketcreator.h"
|
|
//#include "server.h"
|
|
|
|
#if defined( _X360 )
|
|
#include "xbox/xbox_win32stubs.h"
|
|
#endif
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
/*
|
|
====================
|
|
NET_ErrorString
|
|
====================
|
|
*/
|
|
const char *NET_ErrorString (int code)
|
|
{
|
|
#if defined( _WIN32 )
|
|
switch (code)
|
|
{
|
|
case WSAEINTR: return "WSAEINTR";
|
|
case WSAEBADF: return "WSAEBADF";
|
|
case WSAEACCES: return "WSAEACCES";
|
|
case WSAEDISCON: return "WSAEDISCON";
|
|
case WSAEFAULT: return "WSAEFAULT";
|
|
case WSAEINVAL: return "WSAEINVAL";
|
|
case WSAEMFILE: return "WSAEMFILE";
|
|
case WSAEWOULDBLOCK: return "WSAEWOULDBLOCK";
|
|
case WSAEINPROGRESS: return "WSAEINPROGRESS";
|
|
case WSAEALREADY: return "WSAEALREADY";
|
|
case WSAENOTSOCK: return "WSAENOTSOCK";
|
|
case WSAEDESTADDRREQ: return "WSAEDESTADDRREQ";
|
|
case WSAEMSGSIZE: return "WSAEMSGSIZE";
|
|
case WSAEPROTOTYPE: return "WSAEPROTOTYPE";
|
|
case WSAENOPROTOOPT: return "WSAENOPROTOOPT";
|
|
case WSAEPROTONOSUPPORT: return "WSAEPROTONOSUPPORT";
|
|
case WSAESOCKTNOSUPPORT: return "WSAESOCKTNOSUPPORT";
|
|
case WSAEOPNOTSUPP: return "WSAEOPNOTSUPP";
|
|
case WSAEPFNOSUPPORT: return "WSAEPFNOSUPPORT";
|
|
case WSAEAFNOSUPPORT: return "WSAEAFNOSUPPORT";
|
|
case WSAEADDRINUSE: return "WSAEADDRINUSE";
|
|
case WSAEADDRNOTAVAIL: return "WSAEADDRNOTAVAIL";
|
|
case WSAENETDOWN: return "WSAENETDOWN";
|
|
case WSAENETUNREACH: return "WSAENETUNREACH";
|
|
case WSAENETRESET: return "WSAENETRESET";
|
|
case WSAECONNABORTED: return "WSWSAECONNABORTEDAEINTR";
|
|
case WSAECONNRESET: return "WSAECONNRESET";
|
|
case WSAENOBUFS: return "WSAENOBUFS";
|
|
case WSAEISCONN: return "WSAEISCONN";
|
|
case WSAENOTCONN: return "WSAENOTCONN";
|
|
case WSAESHUTDOWN: return "WSAESHUTDOWN";
|
|
case WSAETOOMANYREFS: return "WSAETOOMANYREFS";
|
|
case WSAETIMEDOUT: return "WSAETIMEDOUT";
|
|
case WSAECONNREFUSED: return "WSAECONNREFUSED";
|
|
case WSAELOOP: return "WSAELOOP";
|
|
case WSAENAMETOOLONG: return "WSAENAMETOOLONG";
|
|
case WSAEHOSTDOWN: return "WSAEHOSTDOWN";
|
|
case WSASYSNOTREADY: return "WSASYSNOTREADY";
|
|
case WSAVERNOTSUPPORTED: return "WSAVERNOTSUPPORTED";
|
|
case WSANOTINITIALISED: return "WSANOTINITIALISED";
|
|
case WSAHOST_NOT_FOUND: return "WSAHOST_NOT_FOUND";
|
|
case WSATRY_AGAIN: return "WSATRY_AGAIN";
|
|
case WSANO_RECOVERY: return "WSANO_RECOVERY";
|
|
case WSANO_DATA: return "WSANO_DATA";
|
|
default: return "UNKNOWN ERROR";
|
|
}
|
|
#else
|
|
return strerror( code );
|
|
#endif
|
|
}
|
|
|
|
bool SocketWouldBlock()
|
|
{
|
|
#ifdef _WIN32
|
|
return (WSAGetLastError() == WSAEWOULDBLOCK);
|
|
#elif defined(POSIX)
|
|
return (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINPROGRESS);
|
|
#else
|
|
Assert( false ); // Not implemented for this platform
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Constructor
|
|
//-----------------------------------------------------------------------------
|
|
CSocketCreator::CSocketCreator( ISocketCreatorListener *pListener )
|
|
{
|
|
m_hListenSocket = -1;
|
|
m_pListener = pListener;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Destructor
|
|
//-----------------------------------------------------------------------------
|
|
CSocketCreator::~CSocketCreator()
|
|
{
|
|
Disconnect();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: returns true if the listening socket is created and listening
|
|
//-----------------------------------------------------------------------------
|
|
bool CSocketCreator::IsListening() const
|
|
{
|
|
return m_hListenSocket != -1;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Bind to a TCP port and accept incoming connections
|
|
//-----------------------------------------------------------------------------
|
|
bool CSocketCreator::CreateListenSocket( const netadr_t &netAdr, bool bListenOnAllInterfaces )
|
|
{
|
|
CloseListenSocket();
|
|
|
|
#if PLATFORM_PS3
|
|
Assert( 0 );
|
|
return false;
|
|
#else
|
|
m_ListenAddress = netAdr;
|
|
m_hListenSocket = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
if ( m_hListenSocket == -1 )
|
|
{
|
|
Warning( "Socket unable to create socket (%s)\n", NET_ErrorString( WSAGetLastError() ) );
|
|
return false;
|
|
}
|
|
|
|
if ( !ConfigureSocket( m_hListenSocket ) )
|
|
{
|
|
CloseListenSocket();
|
|
return false;
|
|
}
|
|
|
|
struct sockaddr_in s;
|
|
m_ListenAddress.ToSockadr( (struct sockaddr *)&s );
|
|
if ( bListenOnAllInterfaces )
|
|
s.sin_addr.s_addr = INADDR_ANY;
|
|
int ret = bind( m_hListenSocket, (struct sockaddr *)&s, sizeof(struct sockaddr_in) );
|
|
if ( ret == -1 )
|
|
{
|
|
Warning( "Socket bind failed (%s)\n", NET_ErrorString( WSAGetLastError() ) );
|
|
CloseListenSocket();
|
|
return false;
|
|
}
|
|
|
|
ret = listen( m_hListenSocket, SOCKET_TCP_MAX_ACCEPTS );
|
|
if ( ret == -1 )
|
|
{
|
|
Warning( "Socket listen failed (%s)\n", NET_ErrorString( WSAGetLastError() ) );
|
|
CloseListenSocket();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Configures a socket for use
|
|
//-----------------------------------------------------------------------------
|
|
bool CSocketCreator::ConfigureSocket( int sock )
|
|
{
|
|
#if PLATFORM_PS3
|
|
Assert( 0 );
|
|
return false;
|
|
#else
|
|
// disable NAGLE (rcon cmds are small in size)
|
|
int nodelay = 1;
|
|
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char*)&nodelay, sizeof(nodelay));
|
|
|
|
nodelay = 1;
|
|
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&nodelay, sizeof(nodelay));
|
|
|
|
int opt = 1, ret;
|
|
ret = ioctlsocket( sock, FIONBIO, (unsigned long*)&opt ); // non-blocking
|
|
if ( ret == -1 )
|
|
{
|
|
Warning( "Socket accept ioctl(FIONBIO) failed (%i)\n", WSAGetLastError() );
|
|
return false;
|
|
}
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Handle a new connection
|
|
//-----------------------------------------------------------------------------
|
|
void CSocketCreator::ProcessAccept()
|
|
{
|
|
#if PLATFORM_PS3
|
|
Assert( 0 );
|
|
return;
|
|
#else
|
|
int newSocket;
|
|
sockaddr sa;
|
|
int nLengthAddr = sizeof(sa);
|
|
|
|
newSocket = accept( m_hListenSocket, &sa, (socklen_t *)&nLengthAddr );
|
|
if ( newSocket == -1 )
|
|
{
|
|
if ( !SocketWouldBlock()
|
|
#ifdef POSIX
|
|
&& errno != EINTR
|
|
#endif
|
|
)
|
|
{
|
|
Warning ("Socket ProcessAccept Error: %s\n", NET_ErrorString( WSAGetLastError() ) );
|
|
}
|
|
return;
|
|
}
|
|
|
|
if ( !ConfigureSocket( newSocket ) )
|
|
{
|
|
closesocket( newSocket );
|
|
return;
|
|
}
|
|
|
|
netadr_t adr;
|
|
adr.SetFromSockadr( &sa );
|
|
if ( m_pListener && !m_pListener->ShouldAcceptSocket( newSocket, adr ) )
|
|
{
|
|
closesocket( newSocket );
|
|
return;
|
|
}
|
|
|
|
// new connection TCP request, put in accepted queue
|
|
int nIndex = m_hAcceptedSockets.AddToTail();
|
|
AcceptedSocket_t *pNewEntry = &m_hAcceptedSockets[nIndex];
|
|
pNewEntry->m_hSocket = newSocket;
|
|
pNewEntry->m_Address = adr;
|
|
pNewEntry->m_pData = NULL;
|
|
|
|
void* pData = NULL;
|
|
if ( m_pListener )
|
|
{
|
|
m_pListener->OnSocketAccepted( newSocket, adr, &pData );
|
|
}
|
|
pNewEntry->m_pData = pData;
|
|
#endif
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: connect to the remote server
|
|
//-----------------------------------------------------------------------------
|
|
int CSocketCreator::ConnectSocket( const netadr_t &netAdr, bool bSingleSocket )
|
|
{
|
|
#if PLATFORM_PS3
|
|
Assert( 0 );
|
|
return -1;
|
|
#else
|
|
if ( bSingleSocket )
|
|
{
|
|
CloseAllAcceptedSockets();
|
|
}
|
|
|
|
SocketHandle_t hSocket = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP );
|
|
if ( hSocket == -1 )
|
|
{
|
|
Warning( "Unable to create socket (%s)\n", NET_ErrorString( WSAGetLastError() ) );
|
|
return -1;
|
|
}
|
|
|
|
int opt = 1, ret;
|
|
ret = ioctlsocket( hSocket, FIONBIO, (unsigned long*)&opt ); // non-blocking
|
|
if ( ret == -1 )
|
|
{
|
|
Warning( "Socket ioctl(FIONBIO) failed (%s)\n", NET_ErrorString( WSAGetLastError() ) );
|
|
closesocket( hSocket );
|
|
return -1;
|
|
}
|
|
|
|
// disable NAGLE (rcon cmds are small in size)
|
|
int nodelay = 1;
|
|
setsockopt( hSocket, IPPROTO_TCP, TCP_NODELAY, (char*)&nodelay, sizeof(nodelay) );
|
|
|
|
struct sockaddr_in s;
|
|
netAdr.ToSockadr( (struct sockaddr *)&s );
|
|
|
|
ret = connect( hSocket, (struct sockaddr *)&s, sizeof(s));
|
|
if ( ret == -1 )
|
|
{
|
|
if ( !SocketWouldBlock() )
|
|
{
|
|
Warning( "Socket connection failed (%s)\n", NET_ErrorString( WSAGetLastError() ) );
|
|
closesocket( hSocket );
|
|
return -1;
|
|
}
|
|
|
|
fd_set writefds;
|
|
struct timeval tv;
|
|
tv.tv_usec = 0;
|
|
tv.tv_sec = 1;
|
|
FD_ZERO( &writefds );
|
|
FD_SET( static_cast<u_int>( hSocket ), &writefds );
|
|
if ( select ( hSocket + 1, NULL, &writefds, NULL, &tv ) < 1 ) // block for at most 1 second
|
|
{
|
|
closesocket( hSocket ); // took too long to connect to, give up
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if ( m_pListener && !m_pListener->ShouldAcceptSocket( hSocket, netAdr ) )
|
|
{
|
|
closesocket( hSocket );
|
|
return -1;
|
|
}
|
|
|
|
// new connection TCP request, put in accepted queue
|
|
void *pData = NULL;
|
|
int nIndex = m_hAcceptedSockets.AddToTail();
|
|
AcceptedSocket_t *pNewEntry = &m_hAcceptedSockets[nIndex];
|
|
pNewEntry->m_hSocket = hSocket;
|
|
pNewEntry->m_Address = netAdr;
|
|
pNewEntry->m_pData = NULL;
|
|
|
|
if ( m_pListener )
|
|
{
|
|
m_pListener->OnSocketAccepted( hSocket, netAdr, &pData );
|
|
}
|
|
|
|
pNewEntry->m_pData = pData;
|
|
return nIndex;
|
|
#endif
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: close an open rcon connection
|
|
//-----------------------------------------------------------------------------
|
|
void CSocketCreator::CloseListenSocket()
|
|
{
|
|
#if PLATFORM_PS3
|
|
Assert( 0 );
|
|
return;
|
|
#else
|
|
if ( m_hListenSocket != -1 )
|
|
{
|
|
closesocket( m_hListenSocket );
|
|
m_hListenSocket = -1;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void CSocketCreator::CloseAcceptedSocket( int nIndex )
|
|
{
|
|
#if PLATFORM_PS3
|
|
Assert( 0 );
|
|
return;
|
|
#else
|
|
if ( nIndex >= m_hAcceptedSockets.Count() )
|
|
return;
|
|
|
|
AcceptedSocket_t& connected = m_hAcceptedSockets[nIndex];
|
|
if ( m_pListener )
|
|
{
|
|
m_pListener->OnSocketClosed( connected.m_hSocket, connected.m_Address, connected.m_pData );
|
|
}
|
|
closesocket( connected.m_hSocket );
|
|
m_hAcceptedSockets.Remove( nIndex );
|
|
#endif
|
|
}
|
|
|
|
void CSocketCreator::CloseAllAcceptedSockets()
|
|
{
|
|
#if PLATFORM_PS3
|
|
Warning( "CSocketCreator::CloseAllAcceptedSockets is UNIMPLEMENTED.\n" );
|
|
return;
|
|
#else
|
|
int nCount = m_hAcceptedSockets.Count();
|
|
for ( int i = 0; i < nCount; ++i )
|
|
{
|
|
AcceptedSocket_t& connected = m_hAcceptedSockets[i];
|
|
if ( m_pListener )
|
|
{
|
|
m_pListener->OnSocketClosed( connected.m_hSocket, connected.m_Address, connected.m_pData );
|
|
}
|
|
closesocket( connected.m_hSocket );
|
|
}
|
|
m_hAcceptedSockets.RemoveAll();
|
|
#endif
|
|
}
|
|
|
|
|
|
void CSocketCreator::Disconnect()
|
|
{
|
|
CloseListenSocket();
|
|
CloseAllAcceptedSockets();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: accept new connections and walk open sockets and handle any incoming data
|
|
//-----------------------------------------------------------------------------
|
|
void CSocketCreator::RunFrame()
|
|
{
|
|
if ( IsListening() )
|
|
{
|
|
ProcessAccept(); // handle any new connection requests
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Returns socket info
|
|
//-----------------------------------------------------------------------------
|
|
int CSocketCreator::GetAcceptedSocketCount() const
|
|
{
|
|
return m_hAcceptedSockets.Count();
|
|
}
|
|
|
|
SocketHandle_t CSocketCreator::GetAcceptedSocketHandle( int nIndex ) const
|
|
{
|
|
return m_hAcceptedSockets[nIndex].m_hSocket;
|
|
}
|
|
|
|
const netadr_t& CSocketCreator::GetAcceptedSocketAddress( int nIndex ) const
|
|
{
|
|
return m_hAcceptedSockets[nIndex].m_Address;
|
|
}
|
|
|
|
void* CSocketCreator::GetAcceptedSocketData( int nIndex )
|
|
{
|
|
return m_hAcceptedSockets[nIndex].m_pData;
|
|
}
|
|
|
|
|