You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
610 lines
12 KiB
610 lines
12 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
// |
|
//=============================================================================// |
|
|
|
#pragma warning (disable:4127) |
|
#include <winsock2.h> |
|
#include <ws2tcpip.h> |
|
#pragma warning (default:4127) |
|
|
|
#include "iphelpers.h" |
|
#include "basetypes.h" |
|
#include <assert.h> |
|
#include "utllinkedlist.h" |
|
#include "utlvector.h" |
|
#include "tier1/strtools.h" |
|
|
|
|
|
// This automatically calls WSAStartup for the app at startup. |
|
class CIPStarter |
|
{ |
|
public: |
|
CIPStarter() |
|
{ |
|
WSADATA wsaData; |
|
WSAStartup( WINSOCK_VERSION, &wsaData ); |
|
} |
|
}; |
|
static CIPStarter g_Starter; |
|
|
|
|
|
unsigned long SampleMilliseconds() |
|
{ |
|
CCycleCount cnt; |
|
cnt.Sample(); |
|
return cnt.GetMilliseconds(); |
|
} |
|
|
|
|
|
// ------------------------------------------------------------------------------------------ // |
|
// CChunkWalker. |
|
// ------------------------------------------------------------------------------------------ // |
|
|
|
CChunkWalker::CChunkWalker( void const * const *pChunks, const int *pChunkLengths, int nChunks ) |
|
{ |
|
m_TotalLength = 0; |
|
for ( int i=0; i < nChunks; i++ ) |
|
m_TotalLength += pChunkLengths[i]; |
|
|
|
m_iCurChunk = 0; |
|
m_iCurChunkPos = 0; |
|
m_pChunks = pChunks; |
|
m_pChunkLengths = pChunkLengths; |
|
m_nChunks = nChunks; |
|
} |
|
|
|
int CChunkWalker::GetTotalLength() const |
|
{ |
|
return m_TotalLength; |
|
} |
|
|
|
void CChunkWalker::CopyTo( void *pOut, int nBytes ) |
|
{ |
|
unsigned char *pOutPos = (unsigned char*)pOut; |
|
|
|
int nBytesLeft = nBytes; |
|
while ( nBytesLeft > 0 ) |
|
{ |
|
int toCopy = nBytesLeft; |
|
int curChunkLen = m_pChunkLengths[m_iCurChunk]; |
|
|
|
int amtLeft = curChunkLen - m_iCurChunkPos; |
|
if ( nBytesLeft > amtLeft ) |
|
{ |
|
toCopy = amtLeft; |
|
} |
|
|
|
unsigned char *pCurChunkData = (unsigned char*)m_pChunks[m_iCurChunk]; |
|
memcpy( pOutPos, &pCurChunkData[m_iCurChunkPos], toCopy ); |
|
nBytesLeft -= toCopy; |
|
pOutPos += toCopy; |
|
|
|
// Slide up to the next chunk if we're done with the one we're on. |
|
m_iCurChunkPos += toCopy; |
|
assert( m_iCurChunkPos <= curChunkLen ); |
|
if ( m_iCurChunkPos == curChunkLen ) |
|
{ |
|
++m_iCurChunk; |
|
m_iCurChunkPos = 0; |
|
if ( m_iCurChunk == m_nChunks ) |
|
{ |
|
assert( nBytesLeft == 0 ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
// ------------------------------------------------------------------------------------------ // |
|
// CWaitTimer |
|
// ------------------------------------------------------------------------------------------ // |
|
|
|
bool g_bForceWaitTimers = false; |
|
|
|
CWaitTimer::CWaitTimer( double flSeconds ) |
|
{ |
|
m_StartTime = SampleMilliseconds(); |
|
m_WaitMS = (unsigned long)( flSeconds * 1000.0 ); |
|
} |
|
|
|
|
|
bool CWaitTimer::ShouldKeepWaiting() |
|
{ |
|
if ( m_WaitMS == 0 ) |
|
{ |
|
return false; |
|
} |
|
else |
|
{ |
|
return ( SampleMilliseconds() - m_StartTime ) <= m_WaitMS || g_bForceWaitTimers; |
|
} |
|
} |
|
|
|
|
|
|
|
// ------------------------------------------------------------------------------------------ // |
|
// CIPAddr. |
|
// ------------------------------------------------------------------------------------------ // |
|
|
|
CIPAddr::CIPAddr() |
|
{ |
|
Init( 0, 0, 0, 0, 0 ); |
|
} |
|
|
|
|
|
CIPAddr::CIPAddr( const int inputIP[4], const int inputPort ) |
|
{ |
|
Init( inputIP[0], inputIP[1], inputIP[2], inputIP[3], inputPort ); |
|
} |
|
|
|
|
|
CIPAddr::CIPAddr( int ip0, int ip1, int ip2, int ip3, int ipPort ) |
|
{ |
|
Init( ip0, ip1, ip2, ip3, ipPort ); |
|
} |
|
|
|
|
|
void CIPAddr::Init( int ip0, int ip1, int ip2, int ip3, int ipPort ) |
|
{ |
|
ip[0] = (unsigned char)ip0; |
|
ip[1] = (unsigned char)ip1; |
|
ip[2] = (unsigned char)ip2; |
|
ip[3] = (unsigned char)ip3; |
|
port = (unsigned short)ipPort; |
|
} |
|
|
|
bool CIPAddr::operator==( const CIPAddr &o ) const |
|
{ |
|
return ip[0] == o.ip[0] && ip[1] == o.ip[1] && ip[2] == o.ip[2] && ip[3] == o.ip[3] && port == o.port; |
|
} |
|
|
|
|
|
bool CIPAddr::operator!=( const CIPAddr &o ) const |
|
{ |
|
return !( *this == o ); |
|
} |
|
|
|
|
|
void CIPAddr::SetupLocal( int inPort ) |
|
{ |
|
ip[0] = 0x7f; |
|
ip[1] = 0; |
|
ip[2] = 0; |
|
ip[3] = 1; |
|
port = inPort; |
|
} |
|
|
|
|
|
// ------------------------------------------------------------------------------------------ // |
|
// Static helpers. |
|
// ------------------------------------------------------------------------------------------ // |
|
|
|
static double IP_FloatTime() |
|
{ |
|
CCycleCount cnt; |
|
cnt.Sample(); |
|
return cnt.GetSeconds(); |
|
} |
|
|
|
TIMEVAL SetupTimeVal( double flTimeout ) |
|
{ |
|
TIMEVAL timeVal; |
|
timeVal.tv_sec = (long)flTimeout; |
|
timeVal.tv_usec = (long)( (flTimeout - (long)flTimeout) * 1000.0 ); |
|
return timeVal; |
|
} |
|
|
|
// Convert a CIPAddr to a sockaddr_in. |
|
void IPAddrToInAddr( const CIPAddr *pIn, in_addr *pOut ) |
|
{ |
|
u_char *p = (u_char*)pOut; |
|
p[0] = pIn->ip[0]; |
|
p[1] = pIn->ip[1]; |
|
p[2] = pIn->ip[2]; |
|
p[3] = pIn->ip[3]; |
|
} |
|
|
|
// Convert a CIPAddr to a sockaddr_in. |
|
void IPAddrToSockAddr( const CIPAddr *pIn, struct sockaddr_in *pOut ) |
|
{ |
|
memset( pOut, 0, sizeof(*pOut) ); |
|
pOut->sin_family = AF_INET; |
|
pOut->sin_port = htons( pIn->port ); |
|
|
|
IPAddrToInAddr( pIn, &pOut->sin_addr ); |
|
} |
|
|
|
// Convert a CIPAddr to a sockaddr_in. |
|
void SockAddrToIPAddr( const struct sockaddr_in *pIn, CIPAddr *pOut ) |
|
{ |
|
const u_char *p = (const u_char*)&pIn->sin_addr; |
|
pOut->ip[0] = p[0]; |
|
pOut->ip[1] = p[1]; |
|
pOut->ip[2] = p[2]; |
|
pOut->ip[3] = p[3]; |
|
pOut->port = ntohs( pIn->sin_port ); |
|
} |
|
|
|
|
|
class CIPSocket : public ISocket |
|
{ |
|
public: |
|
CIPSocket() |
|
{ |
|
m_Socket = INVALID_SOCKET; |
|
m_bSetupToBroadcast = false; |
|
} |
|
|
|
virtual ~CIPSocket() |
|
{ |
|
Term(); |
|
} |
|
|
|
|
|
// ISocket implementation. |
|
public: |
|
|
|
virtual void Release() |
|
{ |
|
delete this; |
|
} |
|
|
|
|
|
virtual bool CreateSocket() |
|
{ |
|
// Clear any old socket we had around. |
|
Term(); |
|
|
|
// Create a socket to send and receive through. |
|
SOCKET sock = socket( AF_INET, SOCK_DGRAM, IPPROTO_IP ); |
|
if ( sock == INVALID_SOCKET ) |
|
{ |
|
Assert( false ); |
|
return false; |
|
} |
|
|
|
// Nonblocking please.. |
|
int status; |
|
DWORD val = 1; |
|
status = ioctlsocket( sock, FIONBIO, &val ); |
|
if ( status != 0 ) |
|
{ |
|
assert( false ); |
|
closesocket( sock ); |
|
return false; |
|
} |
|
|
|
m_Socket = sock; |
|
return true; |
|
} |
|
|
|
|
|
// Called after we have a socket. |
|
virtual bool BindPart2( const CIPAddr *pAddr ) |
|
{ |
|
Assert( m_Socket != INVALID_SOCKET ); |
|
|
|
// bind to it! |
|
sockaddr_in addr; |
|
IPAddrToSockAddr( pAddr, &addr ); |
|
|
|
int status = bind( m_Socket, (sockaddr*)&addr, sizeof(addr) ); |
|
if ( status == 0 ) |
|
{ |
|
return true; |
|
} |
|
else |
|
{ |
|
Term(); |
|
return false; |
|
} |
|
} |
|
|
|
|
|
virtual bool Bind( const CIPAddr *pAddr ) |
|
{ |
|
if ( !CreateSocket() ) |
|
return false; |
|
|
|
return BindPart2( pAddr ); |
|
} |
|
|
|
virtual bool BindToAny( const unsigned short port ) |
|
{ |
|
// (INADDR_ANY) |
|
CIPAddr addr; |
|
addr.ip[0] = addr.ip[1] = addr.ip[2] = addr.ip[3] = 0; |
|
addr.port = port; |
|
return Bind( &addr ); |
|
} |
|
|
|
virtual bool ListenToMulticastStream( const CIPAddr &addr, const CIPAddr &localInterface ) |
|
{ |
|
ip_mreq mr; |
|
IPAddrToInAddr( &addr, &mr.imr_multiaddr ); |
|
IPAddrToInAddr( &localInterface, &mr.imr_interface ); |
|
|
|
// This helps a lot if the stream is sending really fast. |
|
int rcvBuf = 1024*1024*2; |
|
setsockopt( m_Socket, SOL_SOCKET, SO_RCVBUF, (char*)&rcvBuf, sizeof( rcvBuf ) ); |
|
|
|
if ( setsockopt( m_Socket, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&mr, sizeof( mr ) ) == 0 ) |
|
{ |
|
// Remember this so we do IP_DEL_MEMBERSHIP on shutdown. |
|
m_bMulticastGroupMembership = true; |
|
m_MulticastGroupMREQ = mr; |
|
return true; |
|
} |
|
else |
|
{ |
|
return false; |
|
} |
|
} |
|
|
|
virtual bool Broadcast( const void *pData, const int len, const unsigned short port ) |
|
{ |
|
assert( m_Socket != INVALID_SOCKET ); |
|
|
|
// Make sure we're setup to broadcast. |
|
if ( !m_bSetupToBroadcast ) |
|
{ |
|
BOOL bBroadcast = true; |
|
if ( setsockopt( m_Socket, SOL_SOCKET, SO_BROADCAST, (char*)&bBroadcast, sizeof( bBroadcast ) ) != 0 ) |
|
{ |
|
assert( false ); |
|
return false; |
|
} |
|
|
|
m_bSetupToBroadcast = true; |
|
} |
|
|
|
CIPAddr addr; |
|
addr.ip[0] = addr.ip[1] = addr.ip[2] = addr.ip[3] = 0xFF; |
|
addr.port = port; |
|
return SendTo( &addr, pData, len ); |
|
} |
|
|
|
virtual bool SendTo( const CIPAddr *pAddr, const void *pData, const int len ) |
|
{ |
|
return SendChunksTo( pAddr, &pData, &len, 1 ); |
|
} |
|
|
|
virtual bool SendChunksTo( const CIPAddr *pAddr, void const * const *pChunks, const int *pChunkLengths, int nChunks ) |
|
{ |
|
WSABUF bufs[32]; |
|
if ( nChunks > 32 ) |
|
{ |
|
Error( "CIPSocket::SendChunksTo: too many chunks (%d).", nChunks ); |
|
} |
|
|
|
int nTotalBytes = 0; |
|
for ( int i=0; i < nChunks; i++ ) |
|
{ |
|
bufs[i].len = pChunkLengths[i]; |
|
bufs[i].buf = (char*)pChunks[i]; |
|
nTotalBytes += pChunkLengths[i]; |
|
} |
|
|
|
assert( m_Socket != INVALID_SOCKET ); |
|
|
|
// Translate the address. |
|
sockaddr_in addr; |
|
IPAddrToSockAddr( pAddr, &addr ); |
|
|
|
DWORD dwNumBytesSent = 0; |
|
DWORD ret = WSASendTo( |
|
m_Socket, |
|
bufs, |
|
nChunks, |
|
&dwNumBytesSent, |
|
0, |
|
(sockaddr*)&addr, |
|
sizeof( addr ), |
|
NULL, |
|
NULL |
|
); |
|
|
|
return ret == 0 && (int)dwNumBytesSent == nTotalBytes; |
|
} |
|
|
|
virtual int RecvFrom( void *pData, int maxDataLen, CIPAddr *pFrom ) |
|
{ |
|
assert( m_Socket != INVALID_SOCKET ); |
|
|
|
fd_set readSet; |
|
readSet.fd_count = 1; |
|
readSet.fd_array[0] = m_Socket; |
|
|
|
TIMEVAL timeVal = SetupTimeVal( 0 ); |
|
|
|
// See if it has a packet waiting. |
|
int status = select( 0, &readSet, NULL, NULL, &timeVal ); |
|
if ( status == 0 || status == SOCKET_ERROR ) |
|
return -1; |
|
|
|
// Get the data. |
|
sockaddr_in sender; |
|
int fromSize = sizeof( sockaddr_in ); |
|
status = recvfrom( m_Socket, (char*)pData, maxDataLen, 0, (struct sockaddr*)&sender, &fromSize ); |
|
if ( status == 0 || status == SOCKET_ERROR ) |
|
{ |
|
return -1; |
|
} |
|
else |
|
{ |
|
if ( pFrom ) |
|
{ |
|
SockAddrToIPAddr( &sender, pFrom ); |
|
} |
|
|
|
m_flLastRecvTime = IP_FloatTime(); |
|
return status; |
|
} |
|
} |
|
|
|
virtual double GetRecvTimeout() |
|
{ |
|
return IP_FloatTime() - m_flLastRecvTime; |
|
} |
|
|
|
|
|
private: |
|
|
|
void Term() |
|
{ |
|
if ( m_Socket != INVALID_SOCKET ) |
|
{ |
|
if ( m_bMulticastGroupMembership ) |
|
{ |
|
// Undo our multicast group membership. |
|
setsockopt( m_Socket, IPPROTO_IP, IP_DROP_MEMBERSHIP, (char*)&m_MulticastGroupMREQ, sizeof( m_MulticastGroupMREQ ) ); |
|
} |
|
|
|
closesocket( m_Socket ); |
|
m_Socket = INVALID_SOCKET; |
|
} |
|
|
|
m_bSetupToBroadcast = false; |
|
m_bMulticastGroupMembership = false; |
|
} |
|
|
|
|
|
private: |
|
|
|
SOCKET m_Socket; |
|
|
|
bool m_bMulticastGroupMembership; // Did we join a multicast group? |
|
ip_mreq m_MulticastGroupMREQ; |
|
|
|
bool m_bSetupToBroadcast; |
|
double m_flLastRecvTime; |
|
bool m_bListenSocket; |
|
}; |
|
|
|
|
|
|
|
ISocket* CreateIPSocket() |
|
{ |
|
return new CIPSocket; |
|
} |
|
|
|
|
|
ISocket* CreateMulticastListenSocket( |
|
const CIPAddr &addr, |
|
const CIPAddr &localInterface ) |
|
{ |
|
CIPSocket *pSocket = new CIPSocket; |
|
|
|
CIPAddr bindAddr = localInterface; |
|
bindAddr.port = addr.port; |
|
|
|
if ( pSocket->Bind( &bindAddr ) && |
|
pSocket->ListenToMulticastStream( addr, localInterface ) |
|
) |
|
{ |
|
return pSocket; |
|
} |
|
else |
|
{ |
|
pSocket->Release(); |
|
return NULL; |
|
} |
|
} |
|
|
|
|
|
bool ConvertStringToIPAddr( const char *pStr, CIPAddr *pOut ) |
|
{ |
|
char ipStr[512]; |
|
|
|
const char *pColon = strchr( pStr, ':' ); |
|
if ( pColon ) |
|
{ |
|
int toCopy = pColon - pStr; |
|
if ( toCopy < 2 || toCopy > sizeof(ipStr)-1 ) |
|
{ |
|
assert( false ); |
|
return false; |
|
} |
|
|
|
memcpy( ipStr, pStr, toCopy ); |
|
ipStr[toCopy] = 0; |
|
|
|
pOut->port = (unsigned short)atoi( pColon+1 ); |
|
} |
|
else |
|
{ |
|
strncpy( ipStr, pStr, sizeof( ipStr ) ); |
|
ipStr[ sizeof(ipStr)-1 ] = 0; |
|
} |
|
|
|
if ( ipStr[0] >= '0' && ipStr[0] <= '9' ) |
|
{ |
|
// It's numbers. |
|
int ip[4]; |
|
sscanf( ipStr, "%d.%d.%d.%d", &ip[0], &ip[1], &ip[2], &ip[3] ); |
|
pOut->ip[0] = (unsigned char)ip[0]; |
|
pOut->ip[1] = (unsigned char)ip[1]; |
|
pOut->ip[2] = (unsigned char)ip[2]; |
|
pOut->ip[3] = (unsigned char)ip[3]; |
|
} |
|
else |
|
{ |
|
// It's a text string. |
|
struct hostent *pHost = gethostbyname( ipStr ); |
|
if( !pHost ) |
|
return false; |
|
|
|
pOut->ip[0] = pHost->h_addr_list[0][0]; |
|
pOut->ip[1] = pHost->h_addr_list[0][1]; |
|
pOut->ip[2] = pHost->h_addr_list[0][2]; |
|
pOut->ip[3] = pHost->h_addr_list[0][3]; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
|
|
bool ConvertIPAddrToString( const CIPAddr *pIn, char *pOut, int outLen ) |
|
{ |
|
in_addr addr; |
|
addr.S_un.S_un_b.s_b1 = pIn->ip[0]; |
|
addr.S_un.S_un_b.s_b2 = pIn->ip[1]; |
|
addr.S_un.S_un_b.s_b3 = pIn->ip[2]; |
|
addr.S_un.S_un_b.s_b4 = pIn->ip[3]; |
|
|
|
HOSTENT *pEnt = gethostbyaddr( (char*)&addr, sizeof( addr ), AF_INET ); |
|
if ( pEnt ) |
|
{ |
|
Q_strncpy( pOut, pEnt->h_name, outLen ); |
|
return true; |
|
} |
|
else |
|
{ |
|
return false; |
|
} |
|
} |
|
|
|
|
|
void IP_GetLastErrorString( char *pStr, int maxLen ) |
|
{ |
|
char *lpMsgBuf; |
|
FormatMessage( |
|
FORMAT_MESSAGE_ALLOCATE_BUFFER | |
|
FORMAT_MESSAGE_FROM_SYSTEM | |
|
FORMAT_MESSAGE_IGNORE_INSERTS, |
|
NULL, |
|
GetLastError(), |
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language |
|
(LPTSTR) &lpMsgBuf, |
|
0, |
|
NULL |
|
); |
|
|
|
Q_strncpy( pStr, lpMsgBuf, maxLen ); |
|
LocalFree( lpMsgBuf ); |
|
} |
|
|
|
|