mirror of https://github.com/FWGS/xash3d-fwgs
2601 lines
57 KiB
C
2601 lines
57 KiB
C
/*
|
|
net_ws.c - win network interface
|
|
Copyright (C) 2007 Uncle Mike
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
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.
|
|
*/
|
|
|
|
#include "common.h"
|
|
#include "client.h" // ConnectionProgress
|
|
#include "netchan.h"
|
|
#include "xash3d_mathlib.h"
|
|
#if XASH_WIN32
|
|
// Winsock
|
|
#include <WS2tcpip.h>
|
|
typedef int WSAsize_t;
|
|
|
|
#elif !defined XASH_NO_NETWORK
|
|
// BSD sockets
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/select.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <netdb.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
|
|
#define WSAGetLastError() errno
|
|
#define WSAEINTR EINTR
|
|
#define WSAEBADF EBADF
|
|
#define WSAEACCES EACCES
|
|
#define WSAEFAULT EFAULT
|
|
#define WSAEINVAL EINVAL
|
|
#define WSAEMFILE EMFILE
|
|
#define WSAEWOULDBLOCK EWOULDBLOCK
|
|
#define WSAEINPROGRESS EINPROGRESS
|
|
#define WSAEALREADY EALREADY
|
|
#define WSAENOTSOCK ENOTSOCK
|
|
#define WSAEDESTADDRREQ EDESTADDRREQ
|
|
#define WSAEMSGSIZE EMSGSIZE
|
|
#define WSAEPROTOTYPE EPROTOTYPE
|
|
#define WSAENOPROTOOPT ENOPROTOOPT
|
|
#define WSAEPROTONOSUPPORT EPROTONOSUPPORT
|
|
#define WSAESOCKTNOSUPPORT ESOCKTNOSUPPORT
|
|
#define WSAEOPNOTSUPP EOPNOTSUPP
|
|
#define WSAEPFNOSUPPORT EPFNOSUPPORT
|
|
#define WSAEAFNOSUPPORT EAFNOSUPPORT
|
|
#define WSAEADDRINUSE EADDRINUSE
|
|
#define WSAEADDRNOTAVAIL EADDRNOTAVAIL
|
|
#define WSAENETDOWN ENETDOWN
|
|
#define WSAENETUNREACH ENETUNREACH
|
|
#define WSAENETRESET ENETRESET
|
|
#define WSAECONNABORTED ECONNABORTED
|
|
#define WSAECONNRESET ECONNRESET
|
|
#define WSAENOBUFS ENOBUFS
|
|
#define WSAEISCONN EISCONN
|
|
#define WSAENOTCONN ENOTCONN
|
|
#define WSAESHUTDOWN ESHUTDOWN
|
|
#define WSAETOOMANYREFS ETOOMANYREFS
|
|
#define WSAETIMEDOUT ETIMEDOUT
|
|
#define WSAECONNREFUSED ECONNREFUSED
|
|
#define WSAELOOP ELOOP
|
|
#define WSAENAMETOOLONG ENAMETOOLONG
|
|
#define WSAEHOSTDOWN EHOSTDOWN
|
|
|
|
|
|
#ifndef XASH_DOS4GW
|
|
#define HAVE_GETADDRINFO
|
|
#define INVALID_SOCKET -1
|
|
#define SOCKET_ERROR -1
|
|
|
|
#if XASH_EMSCRIPTEN
|
|
/* All socket operations are non-blocking already */
|
|
static int ioctl_stub( int d, unsigned long r, ... )
|
|
{
|
|
return 0;
|
|
}
|
|
#define ioctlsocket ioctl_stub
|
|
#else // XASH_EMSCRIPTEN
|
|
#define ioctlsocket ioctl
|
|
#endif // XASH_EMSCRIPTEN
|
|
#define closesocket close
|
|
#endif
|
|
#define SOCKET int
|
|
typedef int WSAsize_t;
|
|
#else
|
|
#include "platform/stub/net_stub.h"
|
|
#endif
|
|
|
|
#define NET_USE_FRAGMENTS
|
|
|
|
#define PORT_ANY -1
|
|
#define MAX_LOOPBACK 4
|
|
#define MASK_LOOPBACK (MAX_LOOPBACK - 1)
|
|
|
|
#define MAX_ROUTEABLE_PACKET 1400
|
|
#define SPLITPACKET_MIN_SIZE 508 // RFC 791: 576(min ip packet) - 60 (ip header) - 8 (udp header)
|
|
#define SPLITPACKET_MAX_SIZE 64000
|
|
#define NET_MAX_FRAGMENTS ( NET_MAX_FRAGMENT / (SPLITPACKET_MIN_SIZE - sizeof( SPLITPACKET )) )
|
|
|
|
typedef struct
|
|
{
|
|
byte data[NET_MAX_MESSAGE];
|
|
int datalen;
|
|
} net_loopmsg_t;
|
|
|
|
typedef struct
|
|
{
|
|
net_loopmsg_t msgs[MAX_LOOPBACK];
|
|
int get, send;
|
|
} net_loopback_t;
|
|
|
|
typedef struct packetlag_s
|
|
{
|
|
byte *data; // Raw stream data is stored.
|
|
int size;
|
|
netadr_t from;
|
|
float receivedtime;
|
|
struct packetlag_s *next;
|
|
struct packetlag_s *prev;
|
|
} packetlag_t;
|
|
|
|
// split long packets. Anything over 1460 is failing on some routers.
|
|
typedef struct
|
|
{
|
|
int current_sequence;
|
|
int split_count;
|
|
int total_size;
|
|
char buffer[NET_MAX_FRAGMENT];
|
|
} LONGPACKET;
|
|
|
|
// use this to pick apart the network stream, must be packed
|
|
#pragma pack(push, 1)
|
|
typedef struct
|
|
{
|
|
int net_id;
|
|
int sequence_number;
|
|
short packet_id;
|
|
} SPLITPACKET;
|
|
#pragma pack(pop)
|
|
|
|
typedef struct
|
|
{
|
|
net_loopback_t loopbacks[NS_COUNT];
|
|
packetlag_t lagdata[NS_COUNT];
|
|
int losscount[NS_COUNT];
|
|
float fakelag; // cached fakelag value
|
|
LONGPACKET split;
|
|
int split_flags[NET_MAX_FRAGMENTS];
|
|
int sequence_number;
|
|
int ip_sockets[NS_COUNT];
|
|
qboolean initialized;
|
|
qboolean threads_initialized;
|
|
qboolean configured;
|
|
qboolean allow_ip;
|
|
#if XASH_WIN32
|
|
WSADATA winsockdata;
|
|
#endif
|
|
} net_state_t;
|
|
|
|
static net_state_t net;
|
|
static convar_t *net_ipname;
|
|
static convar_t *net_hostport;
|
|
static convar_t *net_iphostport;
|
|
static convar_t *net_clientport;
|
|
static convar_t *net_ipclientport;
|
|
static convar_t *net_fakelag;
|
|
static convar_t *net_fakeloss;
|
|
static convar_t *net_address;
|
|
convar_t *net_clockwindow;
|
|
netadr_t net_local;
|
|
|
|
/*
|
|
====================
|
|
NET_ErrorString
|
|
====================
|
|
*/
|
|
char *NET_ErrorString( void )
|
|
{
|
|
#if XASH_WIN32
|
|
int err = WSANOTINITIALISED;
|
|
|
|
if( net.initialized )
|
|
err = WSAGetLastError();
|
|
|
|
switch( err )
|
|
{
|
|
case WSAEINTR: return "WSAEINTR";
|
|
case WSAEBADF: return "WSAEBADF";
|
|
case WSAEACCES: return "WSAEACCES";
|
|
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 WSAEDISCON: return "WSAEDISCON";
|
|
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 "NO ERROR";
|
|
}
|
|
#else
|
|
return strerror( errno );
|
|
#endif
|
|
}
|
|
|
|
_inline qboolean NET_IsSocketError( int retval )
|
|
{
|
|
#if XASH_WIN32 || XASH_DOS4GW
|
|
return retval == SOCKET_ERROR ? true : false;
|
|
#else
|
|
return retval < 0 ? true : false;
|
|
#endif
|
|
}
|
|
|
|
_inline qboolean NET_IsSocketValid( int socket )
|
|
{
|
|
#if XASH_WIN32 || XASH_DOS4GW
|
|
return socket != INVALID_SOCKET;
|
|
#else
|
|
return socket >= 0;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
====================
|
|
NET_NetadrToSockadr
|
|
====================
|
|
*/
|
|
static void NET_NetadrToSockadr( netadr_t *a, struct sockaddr *s )
|
|
{
|
|
memset( s, 0, sizeof( *s ));
|
|
|
|
if( a->type == NA_BROADCAST )
|
|
{
|
|
((struct sockaddr_in *)s)->sin_family = AF_INET;
|
|
((struct sockaddr_in *)s)->sin_port = a->port;
|
|
((struct sockaddr_in *)s)->sin_addr.s_addr = INADDR_BROADCAST;
|
|
}
|
|
else if( a->type == NA_IP )
|
|
{
|
|
((struct sockaddr_in *)s)->sin_family = AF_INET;
|
|
((struct sockaddr_in *)s)->sin_addr.s_addr = *(int *)&a->ip;
|
|
((struct sockaddr_in *)s)->sin_port = a->port;
|
|
}
|
|
}
|
|
|
|
/*
|
|
====================
|
|
NET_SockadrToNetAdr
|
|
====================
|
|
*/
|
|
static void NET_SockadrToNetadr( struct sockaddr *s, netadr_t *a )
|
|
{
|
|
if( s->sa_family == AF_INET )
|
|
{
|
|
a->type = NA_IP;
|
|
*(int *)&a->ip = ((struct sockaddr_in *)s)->sin_addr.s_addr;
|
|
a->port = ((struct sockaddr_in *)s)->sin_port;
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
NET_GetHostByName
|
|
============
|
|
*/
|
|
int NET_GetHostByName( const char *hostname )
|
|
{
|
|
#ifdef HAVE_GETADDRINFO
|
|
struct addrinfo *ai = NULL, *cur;
|
|
struct addrinfo hints;
|
|
int ip = 0;
|
|
|
|
memset( &hints, 0, sizeof( hints ));
|
|
hints.ai_family = AF_INET;
|
|
|
|
if( !getaddrinfo( hostname, NULL, &hints, &ai ))
|
|
{
|
|
for( cur = ai; cur; cur = cur->ai_next )
|
|
{
|
|
if( cur->ai_family == AF_INET )
|
|
{
|
|
ip = *((int*)&((struct sockaddr_in *)cur->ai_addr)->sin_addr);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( ai )
|
|
freeaddrinfo( ai );
|
|
}
|
|
|
|
return ip;
|
|
#else
|
|
struct hostent *h;
|
|
if(!( h = gethostbyname( hostname )))
|
|
return 0;
|
|
return *(int *)h->h_addr_list[0];
|
|
#endif
|
|
}
|
|
|
|
#if !defined XASH_NO_ASYNC_NS_RESOLVE && ( XASH_WIN32 || !(XASH_EMSCRIPTEN || XASH_DOS4GW) )
|
|
#define CAN_ASYNC_NS_RESOLVE
|
|
#endif
|
|
|
|
#ifdef CAN_ASYNC_NS_RESOLVE
|
|
static void NET_ResolveThread( void );
|
|
|
|
#if !XASH_WIN32
|
|
#include <pthread.h>
|
|
#define mutex_lock pthread_mutex_lock
|
|
#define mutex_unlock pthread_mutex_unlock
|
|
#define exit_thread( x ) pthread_exit(x)
|
|
#define create_thread( pfn ) !pthread_create( &nsthread.thread, NULL, (pfn), NULL )
|
|
#define detach_thread( x ) pthread_detach(x)
|
|
#define mutex_t pthread_mutex_t
|
|
#define thread_t pthread_t
|
|
void *NET_ThreadStart( void *unused )
|
|
{
|
|
NET_ResolveThread();
|
|
return NULL;
|
|
}
|
|
#else // WIN32
|
|
#define mutex_lock EnterCriticalSection
|
|
#define mutex_unlock LeaveCriticalSection
|
|
#define detach_thread( x ) CloseHandle(x)
|
|
#define create_thread( pfn ) nsthread.thread = CreateThread( NULL, 0, pfn, NULL, 0, NULL )
|
|
#define mutex_t CRITICAL_SECTION
|
|
#define thread_t HANDLE
|
|
DWORD WINAPI NET_ThreadStart( LPVOID unused )
|
|
{
|
|
NET_ResolveThread();
|
|
ExitThread(0);
|
|
return 0;
|
|
}
|
|
#endif // !_WIN32
|
|
|
|
#ifdef DEBUG_RESOLVE
|
|
#define RESOLVE_DBG(x) Sys_PrintLog(x)
|
|
#else
|
|
#define RESOLVE_DBG(x)
|
|
#endif // DEBUG_RESOLVE
|
|
|
|
static struct nsthread_s
|
|
{
|
|
mutex_t mutexns;
|
|
mutex_t mutexres;
|
|
thread_t thread;
|
|
int result;
|
|
string hostname;
|
|
qboolean busy;
|
|
} nsthread
|
|
#if !XASH_WIN32
|
|
= { PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER }
|
|
#endif
|
|
;
|
|
|
|
#if XASH_WIN32
|
|
static void NET_InitializeCriticalSections( void )
|
|
{
|
|
net.threads_initialized = true;
|
|
pInitializeCriticalSection( &nsthread.mutexns );
|
|
pInitializeCriticalSection( &nsthread.mutexres );
|
|
}
|
|
#endif
|
|
|
|
void NET_ResolveThread( void )
|
|
{
|
|
int sin_addr = 0;
|
|
|
|
RESOLVE_DBG( "[resolve thread] starting resolve for " );
|
|
RESOLVE_DBG( nsthread.hostname );
|
|
#ifdef HAVE_GETADDRINFO
|
|
RESOLVE_DBG( " with getaddrinfo\n" );
|
|
#else
|
|
RESOLVE_DBG( " with gethostbyname\n" );
|
|
#endif
|
|
|
|
sin_addr = NET_GetHostByName( nsthread.hostname );
|
|
|
|
if( sin_addr )
|
|
RESOLVE_DBG( "[resolve thread] success\n" );
|
|
else
|
|
RESOLVE_DBG( "[resolve thread] failed\n" );
|
|
mutex_lock( &nsthread.mutexres );
|
|
nsthread.result = sin_addr;
|
|
nsthread.busy = false;
|
|
RESOLVE_DBG( "[resolve thread] returning result\n" );
|
|
mutex_unlock( &nsthread.mutexres );
|
|
RESOLVE_DBG( "[resolve thread] exiting thread\n" );
|
|
}
|
|
#endif // CAN_ASYNC_NS_RESOLVE
|
|
|
|
|
|
/*
|
|
=============
|
|
NET_StringToAdr
|
|
|
|
localhost
|
|
idnewt
|
|
idnewt:28000
|
|
192.246.40.70
|
|
192.246.40.70:28000
|
|
=============
|
|
*/
|
|
static int NET_StringToSockaddr( const char *s, struct sockaddr *sadr, qboolean nonblocking )
|
|
{
|
|
int ip = 0;
|
|
char *colon;
|
|
char copy[128];
|
|
|
|
if( !net.initialized )
|
|
return false;
|
|
|
|
memset( sadr, 0, sizeof( *sadr ));
|
|
|
|
((struct sockaddr_in *)sadr)->sin_family = AF_INET;
|
|
((struct sockaddr_in *)sadr)->sin_port = 0;
|
|
|
|
Q_strncpy( copy, s, sizeof( copy ));
|
|
|
|
// strip off a trailing :port if present
|
|
for( colon = copy; *colon; colon++ )
|
|
{
|
|
if( *colon == ':' )
|
|
{
|
|
*colon = 0;
|
|
((struct sockaddr_in *)sadr)->sin_port = htons((short)Q_atoi( colon + 1 ));
|
|
}
|
|
}
|
|
|
|
if( copy[0] >= '0' && copy[0] <= '9' )
|
|
{
|
|
*(int *)&((struct sockaddr_in *)sadr)->sin_addr = inet_addr( copy );
|
|
}
|
|
else
|
|
{
|
|
qboolean asyncfailed = true;
|
|
|
|
#ifdef CAN_ASYNC_NS_RESOLVE
|
|
if( net.threads_initialized && !nonblocking )
|
|
{
|
|
mutex_lock( &nsthread.mutexres );
|
|
|
|
if( nsthread.busy )
|
|
{
|
|
mutex_unlock( &nsthread.mutexres );
|
|
return 2;
|
|
}
|
|
|
|
if( !Q_strcmp( copy, nsthread.hostname ) )
|
|
{
|
|
ip = nsthread.result;
|
|
nsthread.hostname[0] = 0;
|
|
detach_thread( nsthread.thread );
|
|
}
|
|
else
|
|
{
|
|
Q_strncpy( nsthread.hostname, copy, MAX_STRING );
|
|
nsthread.busy = true;
|
|
mutex_unlock( &nsthread.mutexres );
|
|
|
|
if( create_thread( NET_ThreadStart ) )
|
|
{
|
|
asyncfailed = false;
|
|
return 2;
|
|
}
|
|
else // failed to create thread
|
|
{
|
|
Con_Reportf( S_ERROR "NET_StringToSockaddr: failed to create thread!\n");
|
|
nsthread.busy = false;
|
|
}
|
|
}
|
|
|
|
mutex_unlock( &nsthread.mutexres );
|
|
}
|
|
#endif // CAN_ASYNC_NS_RESOLVE
|
|
|
|
if( asyncfailed )
|
|
{
|
|
ip = NET_GetHostByName( copy );
|
|
}
|
|
|
|
if( !ip )
|
|
return 0;
|
|
|
|
*(int *)&((struct sockaddr_in *)sadr)->sin_addr = ip;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
NET_AdrToString
|
|
====================
|
|
*/
|
|
const char *NET_AdrToString( const netadr_t a )
|
|
{
|
|
if( a.type == NA_LOOPBACK )
|
|
return "loopback";
|
|
return va( "%i.%i.%i.%i:%i", a.ip[0], a.ip[1], a.ip[2], a.ip[3], ntohs( a.port ));
|
|
}
|
|
|
|
/*
|
|
====================
|
|
NET_BaseAdrToString
|
|
====================
|
|
*/
|
|
const char *NET_BaseAdrToString( const netadr_t a )
|
|
{
|
|
if( a.type == NA_LOOPBACK )
|
|
return "loopback";
|
|
return va( "%i.%i.%i.%i", a.ip[0], a.ip[1], a.ip[2], a.ip[3] );
|
|
}
|
|
|
|
/*
|
|
===================
|
|
NET_CompareBaseAdr
|
|
|
|
Compares without the port
|
|
===================
|
|
*/
|
|
qboolean NET_CompareBaseAdr( const netadr_t a, const netadr_t b )
|
|
{
|
|
if( a.type != b.type )
|
|
return false;
|
|
|
|
if( a.type == NA_LOOPBACK )
|
|
return true;
|
|
|
|
if( a.type == NA_IP )
|
|
{
|
|
if( !memcmp( a.ip, b.ip, 4 ))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
NET_CompareClassBAdr
|
|
|
|
Compare local masks
|
|
====================
|
|
*/
|
|
qboolean NET_CompareClassBAdr( netadr_t a, netadr_t b )
|
|
{
|
|
if( a.type != b.type )
|
|
return false;
|
|
|
|
if( a.type == NA_LOOPBACK )
|
|
return true;
|
|
|
|
if( a.type == NA_IP )
|
|
{
|
|
if( a.ip[0] == b.ip[0] && a.ip[1] == b.ip[1] )
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
NET_IsReservedAdr
|
|
|
|
Check for reserved ip's
|
|
====================
|
|
*/
|
|
qboolean NET_IsReservedAdr( netadr_t a )
|
|
{
|
|
if( a.type == NA_LOOPBACK )
|
|
return true;
|
|
|
|
if( a.type == NA_IP )
|
|
{
|
|
if( a.ip[0] == 10 || a.ip[0] == 127 )
|
|
return true;
|
|
|
|
if( a.ip[0] == 172 && a.ip[1] >= 16 )
|
|
{
|
|
if( a.ip[1] >= 32 )
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
if( a.ip[0] == 192 && a.ip[1] >= 168 )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
NET_CompareAdr
|
|
|
|
Compare full address
|
|
====================
|
|
*/
|
|
qboolean NET_CompareAdr( const netadr_t a, const netadr_t b )
|
|
{
|
|
if( a.type != b.type )
|
|
return false;
|
|
|
|
if( a.type == NA_LOOPBACK )
|
|
return true;
|
|
|
|
if( a.type == NA_IP )
|
|
{
|
|
if(!memcmp( a.ip, b.ip, 4 ) && a.port == b.port )
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
Con_DPrintf( S_ERROR "NET_CompareAdr: bad address type\n" );
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
NET_IsLocalAddress
|
|
====================
|
|
*/
|
|
qboolean NET_IsLocalAddress( netadr_t adr )
|
|
{
|
|
return (adr.type == NA_LOOPBACK) ? true : false;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
NET_StringToAdr
|
|
|
|
idnewt
|
|
192.246.40.70
|
|
=============
|
|
*/
|
|
qboolean NET_StringToAdr( const char *string, netadr_t *adr )
|
|
{
|
|
struct sockaddr s;
|
|
|
|
memset( adr, 0, sizeof( netadr_t ));
|
|
|
|
if( !Q_stricmp( string, "localhost" ) || !Q_stricmp( string, "loopback" ) )
|
|
{
|
|
adr->type = NA_LOOPBACK;
|
|
return true;
|
|
}
|
|
|
|
if( !NET_StringToSockaddr( string, &s, false ))
|
|
return false;
|
|
NET_SockadrToNetadr( &s, adr );
|
|
|
|
return true;
|
|
}
|
|
|
|
int NET_StringToAdrNB( const char *string, netadr_t *adr )
|
|
{
|
|
struct sockaddr s;
|
|
int res;
|
|
|
|
memset( adr, 0, sizeof( netadr_t ));
|
|
if( !Q_stricmp( string, "localhost" ) || !Q_stricmp( string, "loopback" ) )
|
|
{
|
|
adr->type = NA_LOOPBACK;
|
|
return true;
|
|
}
|
|
|
|
res = NET_StringToSockaddr( string, &s, true );
|
|
|
|
if( res == 0 || res == 2 )
|
|
return res;
|
|
|
|
NET_SockadrToNetadr( &s, adr );
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=============================================================================
|
|
|
|
LOOPBACK BUFFERS FOR LOCAL PLAYER
|
|
|
|
=============================================================================
|
|
*/
|
|
/*
|
|
====================
|
|
NET_GetLoopPacket
|
|
====================
|
|
*/
|
|
static qboolean NET_GetLoopPacket( netsrc_t sock, netadr_t *from, byte *data, size_t *length )
|
|
{
|
|
net_loopback_t *loop;
|
|
int i;
|
|
|
|
if( !data || !length )
|
|
return false;
|
|
|
|
loop = &net.loopbacks[sock];
|
|
|
|
if( loop->send - loop->get > MAX_LOOPBACK )
|
|
loop->get = loop->send - MAX_LOOPBACK;
|
|
|
|
if( loop->get >= loop->send )
|
|
return false;
|
|
i = loop->get & MASK_LOOPBACK;
|
|
loop->get++;
|
|
|
|
memcpy( data, loop->msgs[i].data, loop->msgs[i].datalen );
|
|
*length = loop->msgs[i].datalen;
|
|
|
|
memset( from, 0, sizeof( *from ));
|
|
from->type = NA_LOOPBACK;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
NET_SendLoopPacket
|
|
====================
|
|
*/
|
|
static void NET_SendLoopPacket( netsrc_t sock, size_t length, const void *data, netadr_t to )
|
|
{
|
|
net_loopback_t *loop;
|
|
int i;
|
|
|
|
loop = &net.loopbacks[sock^1];
|
|
|
|
i = loop->send & MASK_LOOPBACK;
|
|
loop->send++;
|
|
|
|
memcpy( loop->msgs[i].data, data, length );
|
|
loop->msgs[i].datalen = length;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
NET_ClearLoopback
|
|
====================
|
|
*/
|
|
static void NET_ClearLoopback( void )
|
|
{
|
|
net.loopbacks[0].send = net.loopbacks[0].get = 0;
|
|
net.loopbacks[1].send = net.loopbacks[1].get = 0;
|
|
}
|
|
|
|
/*
|
|
=============================================================================
|
|
|
|
LAG & LOSS SIMULATION SYSTEM (network debugging)
|
|
|
|
=============================================================================
|
|
*/
|
|
/*
|
|
==================
|
|
NET_RemoveFromPacketList
|
|
|
|
double linked list remove entry
|
|
==================
|
|
*/
|
|
static void NET_RemoveFromPacketList( packetlag_t *p )
|
|
{
|
|
p->prev->next = p->next;
|
|
p->next->prev = p->prev;
|
|
p->prev = NULL;
|
|
p->next = NULL;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
NET_ClearLaggedList
|
|
|
|
double linked list remove queue
|
|
==================
|
|
*/
|
|
static void NET_ClearLaggedList( packetlag_t *list )
|
|
{
|
|
packetlag_t *p, *n;
|
|
|
|
p = list->next;
|
|
while( p && p != list )
|
|
{
|
|
n = p->next;
|
|
|
|
NET_RemoveFromPacketList( p );
|
|
|
|
if( p->data )
|
|
{
|
|
Mem_Free( p->data );
|
|
p->data = NULL;
|
|
}
|
|
|
|
Mem_Free( p );
|
|
p = n;
|
|
}
|
|
|
|
list->prev = list;
|
|
list->next = list;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
NET_AddToLagged
|
|
|
|
add lagged packet to stream
|
|
==================
|
|
*/
|
|
static void NET_AddToLagged( netsrc_t sock, packetlag_t *list, packetlag_t *packet, netadr_t *from, size_t length, const void *data, float timestamp )
|
|
{
|
|
byte *pStart;
|
|
|
|
if( packet->prev || packet->next )
|
|
return;
|
|
|
|
packet->prev = list->prev;
|
|
list->prev->next = packet;
|
|
list->prev = packet;
|
|
packet->next = list;
|
|
|
|
pStart = (byte *)Z_Malloc( length );
|
|
memcpy( pStart, data, length );
|
|
packet->data = pStart;
|
|
packet->size = length;
|
|
packet->receivedtime = timestamp;
|
|
memcpy( &packet->from, from, sizeof( netadr_t ));
|
|
}
|
|
|
|
/*
|
|
==================
|
|
NET_AdjustLag
|
|
|
|
adjust time to next fake lag
|
|
==================
|
|
*/
|
|
static void NET_AdjustLag( void )
|
|
{
|
|
static double lasttime = 0.0;
|
|
float diff, converge;
|
|
double dt;
|
|
|
|
dt = host.realtime - lasttime;
|
|
dt = bound( 0.0, dt, 0.1 );
|
|
lasttime = host.realtime;
|
|
|
|
if( host_developer.value || !net_fakelag->value )
|
|
{
|
|
if( net_fakelag->value != net.fakelag )
|
|
{
|
|
diff = net_fakelag->value - net.fakelag;
|
|
converge = dt * 200.0f;
|
|
if( fabs( diff ) < converge )
|
|
converge = fabs( diff );
|
|
if( diff < 0.0f )
|
|
converge = -converge;
|
|
net.fakelag += converge;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Con_Printf( "Server must enable dev-mode to activate fakelag\n" );
|
|
Cvar_SetValue( "fakelag", 0.0 );
|
|
net.fakelag = 0.0f;
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
NET_LagPacket
|
|
|
|
add fake lagged packet into rececived message
|
|
==================
|
|
*/
|
|
static qboolean NET_LagPacket( qboolean newdata, netsrc_t sock, netadr_t *from, size_t *length, void *data )
|
|
{
|
|
packetlag_t *pNewPacketLag;
|
|
packetlag_t *pPacket;
|
|
int ninterval;
|
|
float curtime;
|
|
|
|
if( net.fakelag <= 0.0f )
|
|
{
|
|
NET_ClearLagData( true, true );
|
|
return newdata;
|
|
}
|
|
|
|
curtime = host.realtime;
|
|
|
|
if( newdata )
|
|
{
|
|
if( net_fakeloss->value != 0.0f )
|
|
{
|
|
if( host_developer.value )
|
|
{
|
|
net.losscount[sock]++;
|
|
if( net_fakeloss->value <= 0.0f )
|
|
{
|
|
ninterval = fabs( net_fakeloss->value );
|
|
if( ninterval < 2 ) ninterval = 2;
|
|
|
|
if(( net.losscount[sock] % ninterval ) == 0 )
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if( COM_RandomLong( 0, 100 ) <= net_fakeloss->value )
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Cvar_SetValue( "fakeloss", 0.0 );
|
|
}
|
|
}
|
|
|
|
pNewPacketLag = (packetlag_t *)Z_Malloc( sizeof( packetlag_t ));
|
|
// queue packet to simulate fake lag
|
|
NET_AddToLagged( sock, &net.lagdata[sock], pNewPacketLag, from, *length, data, curtime );
|
|
}
|
|
|
|
pPacket = net.lagdata[sock].next;
|
|
|
|
while( pPacket != &net.lagdata[sock] )
|
|
{
|
|
if( pPacket->receivedtime <= curtime - ( net.fakelag / 1000.0f ))
|
|
break;
|
|
|
|
pPacket = pPacket->next;
|
|
}
|
|
|
|
if( pPacket == &net.lagdata[sock] )
|
|
return false;
|
|
|
|
NET_RemoveFromPacketList( pPacket );
|
|
|
|
// delivery packet from fake lag queue
|
|
memcpy( data, pPacket->data, pPacket->size );
|
|
memcpy( &net_from, &pPacket->from, sizeof( netadr_t ));
|
|
*length = pPacket->size;
|
|
|
|
if( pPacket->data )
|
|
Mem_Free( pPacket->data );
|
|
|
|
Mem_Free( pPacket );
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
NET_GetLong
|
|
|
|
receive long packet from network
|
|
==================
|
|
*/
|
|
qboolean NET_GetLong( byte *pData, int size, size_t *outSize, int splitsize )
|
|
{
|
|
int i, sequence_number, offset;
|
|
SPLITPACKET *pHeader = (SPLITPACKET *)pData;
|
|
int packet_number;
|
|
int packet_count;
|
|
short packet_id;
|
|
int body_size = splitsize - sizeof( SPLITPACKET );
|
|
|
|
if( body_size < 0 )
|
|
return false;
|
|
|
|
if( size < sizeof( SPLITPACKET ))
|
|
{
|
|
Con_Printf( S_ERROR "invalid split packet length %i\n", size );
|
|
return false;
|
|
}
|
|
|
|
sequence_number = pHeader->sequence_number;
|
|
packet_id = pHeader->packet_id;
|
|
packet_count = ( packet_id & 0xFF );
|
|
packet_number = ( packet_id >> 8 );
|
|
|
|
if( packet_number >= NET_MAX_FRAGMENTS || packet_count > NET_MAX_FRAGMENTS )
|
|
{
|
|
Con_Printf( S_ERROR "malformed packet number (%i/%i)\n", packet_number + 1, packet_count );
|
|
return false;
|
|
}
|
|
|
|
if( net.split.current_sequence == -1 || sequence_number != net.split.current_sequence )
|
|
{
|
|
net.split.current_sequence = sequence_number;
|
|
net.split.split_count = packet_count;
|
|
net.split.total_size = 0;
|
|
|
|
// clear part's sequence
|
|
for( i = 0; i < NET_MAX_FRAGMENTS; i++ )
|
|
net.split_flags[i] = -1;
|
|
|
|
if( net_showpackets && net_showpackets->value == 4.0f )
|
|
Con_Printf( "<-- Split packet restart %i count %i seq\n", net.split.split_count, sequence_number );
|
|
}
|
|
|
|
size -= sizeof( SPLITPACKET );
|
|
|
|
if( net.split_flags[packet_number] != sequence_number )
|
|
{
|
|
if( packet_number == ( packet_count - 1 ))
|
|
net.split.total_size = size + body_size * ( packet_count - 1 );
|
|
|
|
net.split.split_count--;
|
|
net.split_flags[packet_number] = sequence_number;
|
|
|
|
if( net_showpackets && net_showpackets->value == 4.0f )
|
|
Con_Printf( "<-- Split packet %i of %i, %i bytes %i seq\n", packet_number + 1, packet_count, size, sequence_number );
|
|
}
|
|
else
|
|
{
|
|
Con_DPrintf( "NET_GetLong: Ignoring duplicated split packet %i of %i ( %i bytes )\n", packet_number + 1, packet_count, size );
|
|
}
|
|
|
|
offset = (packet_number * body_size);
|
|
memcpy( net.split.buffer + offset, pData + sizeof( SPLITPACKET ), size );
|
|
|
|
// have we received all of the pieces to the packet?
|
|
if( net.split.split_count <= 0 )
|
|
{
|
|
net.split.current_sequence = -1; // Clear packet
|
|
|
|
if( net.split.total_size > sizeof( net.split.buffer ))
|
|
{
|
|
Con_Printf( "Split packet too large! %d bytes\n", net.split.total_size );
|
|
return false;
|
|
}
|
|
|
|
memcpy( pData, net.split.buffer, net.split.total_size );
|
|
*outSize = net.split.total_size;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
NET_QueuePacket
|
|
|
|
queue normal and lagged packets
|
|
==================
|
|
*/
|
|
qboolean NET_QueuePacket( netsrc_t sock, netadr_t *from, byte *data, size_t *length )
|
|
{
|
|
byte buf[NET_MAX_FRAGMENT];
|
|
int ret;
|
|
int net_socket;
|
|
WSAsize_t addr_len;
|
|
struct sockaddr addr;
|
|
|
|
*length = 0;
|
|
|
|
net_socket = net.ip_sockets[sock];
|
|
|
|
if( NET_IsSocketValid( net_socket ) )
|
|
{
|
|
addr_len = sizeof( addr );
|
|
ret = recvfrom( net_socket, buf, sizeof( buf ), 0, (struct sockaddr *)&addr, &addr_len );
|
|
|
|
if( !NET_IsSocketError( ret ) )
|
|
{
|
|
NET_SockadrToNetadr( &addr, from );
|
|
|
|
if( ret < NET_MAX_FRAGMENT )
|
|
{
|
|
// Transfer data
|
|
memcpy( data, buf, ret );
|
|
*length = ret;
|
|
#if !XASH_DEDICATED
|
|
if( CL_LegacyMode() )
|
|
return NET_LagPacket( true, sock, from, length, data );
|
|
|
|
// check for split message
|
|
if( sock == NS_CLIENT && *(int *)data == NET_HEADER_SPLITPACKET )
|
|
{
|
|
return NET_GetLong( data, ret, length, CL_GetSplitSize() );
|
|
}
|
|
#endif
|
|
// lag the packet, if needed
|
|
return NET_LagPacket( true, sock, from, length, data );
|
|
}
|
|
else
|
|
{
|
|
Con_Reportf( "NET_QueuePacket: oversize packet from %s\n", NET_AdrToString( *from ));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int err = WSAGetLastError();
|
|
|
|
switch( err )
|
|
{
|
|
case WSAEWOULDBLOCK:
|
|
case WSAECONNRESET:
|
|
case WSAECONNREFUSED:
|
|
case WSAEMSGSIZE:
|
|
case WSAETIMEDOUT:
|
|
break;
|
|
default: // let's continue even after errors
|
|
Con_DPrintf( S_ERROR "NET_QueuePacket: %s from %s\n", NET_ErrorString(), NET_AdrToString( *from ));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NET_LagPacket( false, sock, from, length, data );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
NET_GetPacket
|
|
|
|
Never called by the game logic, just the system event queing
|
|
==================
|
|
*/
|
|
qboolean NET_GetPacket( netsrc_t sock, netadr_t *from, byte *data, size_t *length )
|
|
{
|
|
if( !data || !length )
|
|
return false;
|
|
|
|
NET_AdjustLag();
|
|
|
|
if( NET_GetLoopPacket( sock, from, data, length ))
|
|
{
|
|
return NET_LagPacket( true, sock, from, length, data );
|
|
}
|
|
else
|
|
{
|
|
return NET_QueuePacket( sock, from, data, length );
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
NET_SendLong
|
|
|
|
Fragment long packets, send short directly
|
|
==================
|
|
*/
|
|
int NET_SendLong( netsrc_t sock, int net_socket, const char *buf, size_t len, int flags, const struct sockaddr *to, size_t tolen, size_t splitsize )
|
|
{
|
|
#ifdef NET_USE_FRAGMENTS
|
|
// do we need to break this packet up?
|
|
if( splitsize > sizeof( SPLITPACKET ) && sock == NS_SERVER && len > splitsize )
|
|
{
|
|
char packet[SPLITPACKET_MAX_SIZE];
|
|
int total_sent, size, packet_count;
|
|
int ret, packet_number;
|
|
int body_size = splitsize - sizeof( SPLITPACKET );
|
|
SPLITPACKET *pPacket;
|
|
|
|
net.sequence_number++;
|
|
if( net.sequence_number <= 0 )
|
|
net.sequence_number = 1;
|
|
|
|
pPacket = (SPLITPACKET *)packet;
|
|
pPacket->sequence_number = net.sequence_number;
|
|
pPacket->net_id = NET_HEADER_SPLITPACKET;
|
|
packet_number = 0;
|
|
total_sent = 0;
|
|
packet_count = (len + body_size - 1) / body_size;
|
|
|
|
while( len > 0 )
|
|
{
|
|
size = Q_min( body_size, len );
|
|
pPacket->packet_id = (packet_number << 8) + packet_count;
|
|
memcpy( packet + sizeof( SPLITPACKET ), buf + ( packet_number * body_size ), size );
|
|
|
|
if( net_showpackets && net_showpackets->value == 3.0f )
|
|
{
|
|
netadr_t adr;
|
|
|
|
memset( &adr, 0, sizeof( adr ));
|
|
NET_SockadrToNetadr((struct sockaddr *)to, &adr );
|
|
|
|
Con_Printf( "Sending split %i of %i with %i bytes and seq %i to %s\n",
|
|
packet_number + 1, packet_count, size, net.sequence_number, NET_AdrToString( adr ));
|
|
}
|
|
|
|
ret = sendto( net_socket, packet, size + sizeof( SPLITPACKET ), flags, to, tolen );
|
|
if( ret < 0 ) return ret; // error
|
|
|
|
if( ret >= size )
|
|
total_sent += size;
|
|
len -= size;
|
|
packet_number++;
|
|
Sys_Sleep( 1 );
|
|
}
|
|
|
|
return total_sent;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
// no fragmenantion for client connection
|
|
return sendto( net_socket, buf, len, flags, to, tolen );
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
NET_SendPacketEx
|
|
==================
|
|
*/
|
|
void NET_SendPacketEx( netsrc_t sock, size_t length, const void *data, netadr_t to, size_t splitsize )
|
|
{
|
|
int ret;
|
|
struct sockaddr addr;
|
|
SOCKET net_socket = 0;
|
|
|
|
if( !net.initialized || to.type == NA_LOOPBACK )
|
|
{
|
|
NET_SendLoopPacket( sock, length, data, to );
|
|
return;
|
|
}
|
|
else if( to.type == NA_BROADCAST )
|
|
{
|
|
net_socket = net.ip_sockets[sock];
|
|
if( !NET_IsSocketValid( net_socket ) )
|
|
return;
|
|
}
|
|
else if( to.type == NA_IP )
|
|
{
|
|
net_socket = net.ip_sockets[sock];
|
|
if( !NET_IsSocketValid( net_socket ) )
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
Host_Error( "NET_SendPacket: bad address type %i\n", to.type );
|
|
}
|
|
|
|
NET_NetadrToSockadr( &to, &addr );
|
|
|
|
ret = NET_SendLong( sock, net_socket, data, length, 0, &addr, sizeof( addr ), splitsize );
|
|
|
|
if( NET_IsSocketError( ret ))
|
|
{
|
|
int err = WSAGetLastError();
|
|
|
|
// WSAEWOULDBLOCK is silent
|
|
if( err == WSAEWOULDBLOCK )
|
|
return;
|
|
|
|
// some PPP links don't allow broadcasts
|
|
if( err == WSAEADDRNOTAVAIL && to.type == NA_BROADCAST )
|
|
return;
|
|
|
|
if( Host_IsDedicated() )
|
|
{
|
|
Con_DPrintf( S_ERROR "NET_SendPacket: %s to %s\n", NET_ErrorString(), NET_AdrToString( to ));
|
|
}
|
|
else if( err == WSAEADDRNOTAVAIL || err == WSAENOBUFS )
|
|
{
|
|
Con_DPrintf( S_ERROR "NET_SendPacket: %s to %s\n", NET_ErrorString(), NET_AdrToString( to ));
|
|
}
|
|
else
|
|
{
|
|
Con_Printf( S_ERROR "NET_SendPacket: %s to %s\n", NET_ErrorString(), NET_AdrToString( to ));
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
==================
|
|
NET_SendPacket
|
|
==================
|
|
*/
|
|
void NET_SendPacket( netsrc_t sock, size_t length, const void *data, netadr_t to )
|
|
{
|
|
NET_SendPacketEx( sock, length, data, to, 0 );
|
|
}
|
|
|
|
/*
|
|
====================
|
|
NET_BufferToBufferCompress
|
|
|
|
generic fast compression
|
|
====================
|
|
*/
|
|
qboolean NET_BufferToBufferCompress( byte *dest, uint *destLen, byte *source, uint sourceLen )
|
|
{
|
|
uint uCompressedLen = 0;
|
|
byte *pbOut = NULL;
|
|
|
|
memcpy( dest, source, sourceLen );
|
|
pbOut = LZSS_Compress( source, sourceLen, &uCompressedLen );
|
|
|
|
if( pbOut && uCompressedLen > 0 && uCompressedLen <= *destLen )
|
|
{
|
|
memcpy( dest, pbOut, uCompressedLen );
|
|
*destLen = uCompressedLen;
|
|
free( pbOut );
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if( pbOut ) free( pbOut );
|
|
memcpy( dest, source, sourceLen );
|
|
*destLen = sourceLen;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/*
|
|
====================
|
|
NET_BufferToBufferDecompress
|
|
|
|
generic fast decompression
|
|
====================
|
|
*/
|
|
qboolean NET_BufferToBufferDecompress( byte *dest, uint *destLen, byte *source, uint sourceLen )
|
|
{
|
|
if( LZSS_IsCompressed( source ))
|
|
{
|
|
uint uDecompressedLen = LZSS_GetActualSize( source );
|
|
|
|
if( uDecompressedLen <= *destLen )
|
|
{
|
|
*destLen = LZSS_Decompress( source, dest );
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
memcpy( dest, source, sourceLen );
|
|
*destLen = sourceLen;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
NET_Isocket
|
|
====================
|
|
*/
|
|
static int NET_Isocket( const char *net_interface, int port, qboolean multicast )
|
|
{
|
|
struct sockaddr_in addr;
|
|
int err, net_socket;
|
|
uint optval = 1;
|
|
dword _true = 1;
|
|
|
|
if( NET_IsSocketError(( net_socket = socket( PF_INET, SOCK_DGRAM, IPPROTO_UDP )) ) )
|
|
{
|
|
err = WSAGetLastError();
|
|
if( err != WSAEAFNOSUPPORT )
|
|
Con_DPrintf( S_WARN "NET_UDsocket: port: %d socket: %s\n", port, NET_ErrorString( ));
|
|
return INVALID_SOCKET;
|
|
}
|
|
|
|
if( NET_IsSocketError( ioctlsocket( net_socket, FIONBIO, (void*)&_true ) ) )
|
|
{
|
|
struct timeval timeout;
|
|
|
|
Con_DPrintf( S_WARN "NET_UDsocket: port: %d ioctl FIONBIO: %s\n", port, NET_ErrorString( ));
|
|
// try timeout instead of NBIO
|
|
timeout.tv_sec = timeout.tv_usec = 0;
|
|
setsockopt( net_socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout));
|
|
}
|
|
|
|
// make it broadcast capable
|
|
if( NET_IsSocketError( setsockopt( net_socket, SOL_SOCKET, SO_BROADCAST, (char *)&_true, sizeof( _true ) ) ) )
|
|
{
|
|
Con_DPrintf( S_WARN "NET_UDsocket: port: %d setsockopt SO_BROADCAST: %s\n", port, NET_ErrorString( ));
|
|
}
|
|
|
|
if( Sys_CheckParm( "-reuse" ) || multicast )
|
|
{
|
|
if( NET_IsSocketError( setsockopt( net_socket, SOL_SOCKET, SO_REUSEADDR, (const char *)&optval, sizeof( optval )) ) )
|
|
{
|
|
Con_DPrintf( S_WARN "NET_UDsocket: port: %d setsockopt SO_REUSEADDR: %s\n", port, NET_ErrorString( ));
|
|
closesocket( net_socket );
|
|
return INVALID_SOCKET;
|
|
}
|
|
}
|
|
|
|
if( Sys_CheckParm( "-tos" ))
|
|
{
|
|
optval = 16;
|
|
Con_Printf( "Enabling LOWDELAY TOS option\n" );
|
|
|
|
if( NET_IsSocketError( setsockopt( net_socket, IPPROTO_IP, IP_TOS, (const char *)&optval, sizeof( optval )) ) )
|
|
{
|
|
err = WSAGetLastError();
|
|
if( err != WSAENOPROTOOPT )
|
|
Con_Printf( S_WARN "NET_UDsocket: port: %d setsockopt IP_TOS: %s\n", port, NET_ErrorString( ));
|
|
closesocket( net_socket );
|
|
return INVALID_SOCKET;
|
|
}
|
|
}
|
|
|
|
if( !COM_CheckStringEmpty( net_interface ) || !Q_stricmp( net_interface, "localhost" ))
|
|
addr.sin_addr.s_addr = INADDR_ANY;
|
|
else NET_StringToSockaddr( net_interface, (struct sockaddr *)&addr, false );
|
|
|
|
if( port == PORT_ANY ) addr.sin_port = 0;
|
|
else addr.sin_port = htons((short)port);
|
|
|
|
addr.sin_family = AF_INET;
|
|
|
|
if( NET_IsSocketError( bind( net_socket, (void *)&addr, sizeof( addr )) ) )
|
|
{
|
|
Con_DPrintf( S_WARN "NET_UDsocket: port: %d bind: %s\n", port, NET_ErrorString( ));
|
|
closesocket( net_socket );
|
|
return INVALID_SOCKET;
|
|
}
|
|
|
|
if( Sys_CheckParm( "-loopback" ))
|
|
{
|
|
optval = 1;
|
|
if( NET_IsSocketError( setsockopt( net_socket, IPPROTO_IP, IP_MULTICAST_LOOP, (const char *)&optval, sizeof( optval )) ) )
|
|
Con_DPrintf( S_WARN "NET_UDsocket: port %d setsockopt IP_MULTICAST_LOOP: %s\n", port, NET_ErrorString( ));
|
|
}
|
|
|
|
return net_socket;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
NET_OpenIP
|
|
====================
|
|
*/
|
|
static void NET_OpenIP( void )
|
|
{
|
|
int port, sv_port = 0, cl_port = 0;
|
|
|
|
if( !NET_IsSocketValid( net.ip_sockets[NS_SERVER] ) )
|
|
{
|
|
port = net_iphostport->value;
|
|
if( !port ) port = net_hostport->value;
|
|
if( !port ) port = PORT_SERVER; // forcing to default
|
|
net.ip_sockets[NS_SERVER] = NET_Isocket( net_ipname->string, port, false );
|
|
|
|
if( !NET_IsSocketValid( net.ip_sockets[NS_SERVER] ) && Host_IsDedicated() )
|
|
Host_Error( "Couldn't allocate dedicated server IP port %d.\n", port );
|
|
sv_port = port;
|
|
}
|
|
|
|
// dedicated servers don't need client ports
|
|
if( Host_IsDedicated() ) return;
|
|
|
|
if( !NET_IsSocketValid( net.ip_sockets[NS_CLIENT] ) )
|
|
{
|
|
port = net_ipclientport->value;
|
|
if( !port ) port = net_clientport->value;
|
|
if( !port ) port = PORT_ANY; // forcing to default
|
|
net.ip_sockets[NS_CLIENT] = NET_Isocket( net_ipname->string, port, false );
|
|
|
|
if( !NET_IsSocketValid( net.ip_sockets[NS_CLIENT] ) )
|
|
net.ip_sockets[NS_CLIENT] = NET_Isocket( net_ipname->string, PORT_ANY, false );
|
|
cl_port = port;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
NET_GetLocalAddress
|
|
|
|
Returns the servers' ip address as a string.
|
|
================
|
|
*/
|
|
void NET_GetLocalAddress( void )
|
|
{
|
|
char buff[512];
|
|
struct sockaddr_in address;
|
|
WSAsize_t namelen;
|
|
|
|
memset( &net_local, 0, sizeof( netadr_t ));
|
|
buff[0] = '\0';
|
|
|
|
if( net.allow_ip )
|
|
{
|
|
// If we have changed the ip var from the command line, use that instead.
|
|
if( Q_strcmp( net_ipname->string, "localhost" ))
|
|
{
|
|
Q_strncpy( buff, net_ipname->string, sizeof( buff ) );
|
|
}
|
|
else
|
|
{
|
|
gethostname( buff, 512 );
|
|
|
|
// ensure that it doesn't overrun the buffer
|
|
buff[511] = 0;
|
|
}
|
|
|
|
if( NET_StringToAdr( buff, &net_local ))
|
|
{
|
|
namelen = sizeof( address );
|
|
|
|
if( NET_IsSocketError( getsockname( net.ip_sockets[NS_SERVER], (struct sockaddr *)&address, &namelen ) ) )
|
|
{
|
|
// this may happens if multiple clients running on single machine
|
|
Con_DPrintf( S_ERROR "Could not get TCP/IP address. Reason: %s\n", NET_ErrorString( ));
|
|
// net.allow_ip = false;
|
|
}
|
|
else
|
|
{
|
|
net_local.port = address.sin_port;
|
|
Con_Printf( "Server IP address %s\n", NET_AdrToString( net_local ));
|
|
Cvar_FullSet( "net_address", va( "%s", NET_AdrToString( net_local )), FCVAR_READ_ONLY );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Con_DPrintf( S_ERROR "Could not get TCP/IP address, Invalid hostname: '%s'\n", buff );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Con_Printf( "TCP/IP Disabled.\n" );
|
|
}
|
|
}
|
|
|
|
/*
|
|
====================
|
|
NET_Config
|
|
|
|
A single player game will only use the loopback code
|
|
====================
|
|
*/
|
|
void NET_Config( qboolean multiplayer )
|
|
{
|
|
static qboolean bFirst = true;
|
|
static qboolean old_config;
|
|
|
|
if( !net.initialized )
|
|
return;
|
|
|
|
if( old_config == multiplayer )
|
|
return;
|
|
|
|
old_config = multiplayer;
|
|
|
|
if( multiplayer )
|
|
{
|
|
// open sockets
|
|
if( net.allow_ip ) NET_OpenIP();
|
|
|
|
// get our local address, if possible
|
|
if( bFirst )
|
|
{
|
|
NET_GetLocalAddress();
|
|
bFirst = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int i;
|
|
|
|
// shut down any existing sockets
|
|
for( i = 0; i < NS_COUNT; i++ )
|
|
{
|
|
if( net.ip_sockets[i] != INVALID_SOCKET )
|
|
{
|
|
closesocket( net.ip_sockets[i] );
|
|
net.ip_sockets[i] = INVALID_SOCKET;
|
|
}
|
|
}
|
|
}
|
|
|
|
NET_ClearLoopback ();
|
|
|
|
net.configured = multiplayer ? true : false;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
NET_IsConfigured
|
|
|
|
Is winsock ip initialized?
|
|
====================
|
|
*/
|
|
qboolean NET_IsConfigured( void )
|
|
{
|
|
return net.configured;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
NET_IsActive
|
|
====================
|
|
*/
|
|
qboolean NET_IsActive( void )
|
|
{
|
|
return net.initialized;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
NET_Sleep
|
|
|
|
sleeps msec or until net socket is ready
|
|
====================
|
|
*/
|
|
void NET_Sleep( int msec )
|
|
{
|
|
#ifndef XASH_NO_NETWORK
|
|
struct timeval timeout;
|
|
fd_set fdset;
|
|
int i = 0;
|
|
|
|
if( !net.initialized || host.type == HOST_NORMAL )
|
|
return; // we're not a dedicated server, just run full speed
|
|
|
|
FD_ZERO( &fdset );
|
|
|
|
if( net.ip_sockets[NS_SERVER] != INVALID_SOCKET )
|
|
{
|
|
FD_SET( net.ip_sockets[NS_SERVER], &fdset ); // network socket
|
|
i = net.ip_sockets[NS_SERVER];
|
|
}
|
|
|
|
timeout.tv_sec = msec / 1000;
|
|
timeout.tv_usec = (msec % 1000) * 1000;
|
|
select( i+1, &fdset, NULL, NULL, &timeout );
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
====================
|
|
NET_ClearLagData
|
|
|
|
clear fakelag list
|
|
====================
|
|
*/
|
|
void NET_ClearLagData( qboolean bClient, qboolean bServer )
|
|
{
|
|
if( bClient ) NET_ClearLaggedList( &net.lagdata[NS_CLIENT] );
|
|
if( bServer ) NET_ClearLaggedList( &net.lagdata[NS_SERVER] );
|
|
}
|
|
|
|
/*
|
|
====================
|
|
NET_Init
|
|
====================
|
|
*/
|
|
void NET_Init( void )
|
|
{
|
|
char cmd[64];
|
|
int i = 1;
|
|
|
|
if( net.initialized ) return;
|
|
|
|
net_clockwindow = Cvar_Get( "clockwindow", "0.5", FCVAR_PRIVILEGED, "timewindow to execute client moves" );
|
|
net_address = Cvar_Get( "net_address", "0", FCVAR_READ_ONLY, "contain local address of current client" );
|
|
net_ipname = Cvar_Get( "ip", "localhost", FCVAR_READ_ONLY, "network ip address" );
|
|
net_iphostport = Cvar_Get( "ip_hostport", "0", FCVAR_READ_ONLY, "network ip host port" );
|
|
net_hostport = Cvar_Get( "hostport", va( "%i", PORT_SERVER ), FCVAR_READ_ONLY, "network default host port" );
|
|
net_ipclientport = Cvar_Get( "ip_clientport", "0", FCVAR_READ_ONLY, "network ip client port" );
|
|
net_clientport = Cvar_Get( "clientport", va( "%i", PORT_CLIENT ), FCVAR_READ_ONLY, "network default client port" );
|
|
net_fakelag = Cvar_Get( "fakelag", "0", FCVAR_PRIVILEGED, "lag all incoming network data (including loopback) by xxx ms." );
|
|
net_fakeloss = Cvar_Get( "fakeloss", "0", FCVAR_PRIVILEGED, "act like we dropped the packet this % of the time." );
|
|
|
|
// prepare some network data
|
|
for( i = 0; i < NS_COUNT; i++ )
|
|
{
|
|
net.lagdata[i].prev = &net.lagdata[i];
|
|
net.lagdata[i].next = &net.lagdata[i];
|
|
net.ip_sockets[i] = INVALID_SOCKET;
|
|
}
|
|
|
|
#if XASH_WIN32
|
|
if( WSAStartup( MAKEWORD( 1, 1 ), &net.winsockdata ) )
|
|
{
|
|
Con_DPrintf( S_ERROR "network initialization failed.\n" );
|
|
return;
|
|
}
|
|
#else
|
|
// we have pthreads by default
|
|
net.threads_initialized = true;
|
|
#endif
|
|
|
|
if( Sys_CheckParm( "-noip" ))
|
|
net.allow_ip = false;
|
|
else net.allow_ip = true;
|
|
|
|
// specify custom host port
|
|
if( Sys_GetParmFromCmdLine( "-port", cmd ) && Q_isdigit( cmd ))
|
|
Cvar_FullSet( "hostport", cmd, FCVAR_READ_ONLY );
|
|
|
|
// specify custom ip
|
|
if( Sys_GetParmFromCmdLine( "-ip", cmd ))
|
|
Cvar_FullSet( "ip", cmd, FCVAR_READ_ONLY );
|
|
|
|
// adjust clockwindow
|
|
if( Sys_GetParmFromCmdLine( "-clockwindow", cmd ))
|
|
Cvar_SetValue( "clockwindow", Q_atof( cmd ));
|
|
|
|
net.sequence_number = 1;
|
|
net.initialized = true;
|
|
Con_Reportf( "Base networking initialized.\n" );
|
|
}
|
|
|
|
|
|
/*
|
|
====================
|
|
NET_Shutdown
|
|
====================
|
|
*/
|
|
void NET_Shutdown( void )
|
|
{
|
|
if( !net.initialized )
|
|
return;
|
|
|
|
NET_ClearLagData( true, true );
|
|
|
|
NET_Config( false );
|
|
#if XASH_WIN32
|
|
WSACleanup();
|
|
#endif
|
|
net.initialized = false;
|
|
}
|
|
|
|
|
|
/*
|
|
=================================================
|
|
|
|
HTTP downloader
|
|
|
|
=================================================
|
|
*/
|
|
|
|
typedef struct httpserver_s
|
|
{
|
|
char host[256];
|
|
int port;
|
|
char path[MAX_SYSPATH];
|
|
qboolean needfree;
|
|
struct httpserver_s *next;
|
|
|
|
} httpserver_t;
|
|
|
|
enum connectionstate
|
|
{
|
|
HTTP_QUEUE = 0,
|
|
HTTP_OPENED,
|
|
HTTP_SOCKET,
|
|
HTTP_NS_RESOLVED,
|
|
HTTP_CONNECTED,
|
|
HTTP_REQUEST,
|
|
HTTP_REQUEST_SENT,
|
|
HTTP_RESPONSE_RECEIVED,
|
|
HTTP_FREE
|
|
};
|
|
|
|
typedef struct httpfile_s
|
|
{
|
|
struct httpfile_s *next;
|
|
httpserver_t *server;
|
|
char path[MAX_SYSPATH];
|
|
file_t *file;
|
|
int socket;
|
|
int size;
|
|
int downloaded;
|
|
int lastchecksize;
|
|
float checktime;
|
|
float blocktime;
|
|
int id;
|
|
enum connectionstate state;
|
|
qboolean process;
|
|
|
|
// query or response
|
|
char buf[BUFSIZ+1];
|
|
int header_size, query_length, bytes_sent;
|
|
} httpfile_t;
|
|
|
|
static struct http_static_s
|
|
{
|
|
// file and server lists
|
|
httpfile_t *first_file, *last_file;
|
|
httpserver_t *first_server, *last_server;
|
|
} http;
|
|
|
|
|
|
static convar_t *http_useragent;
|
|
static convar_t *http_autoremove;
|
|
static convar_t *http_timeout;
|
|
static convar_t *http_maxconnections;
|
|
|
|
/*
|
|
========================
|
|
HTTP_ClearCustomServers
|
|
========================
|
|
*/
|
|
void HTTP_ClearCustomServers( void )
|
|
{
|
|
if( http.first_file )
|
|
return; // may be referenced
|
|
|
|
while( http.first_server && http.first_server->needfree )
|
|
{
|
|
httpserver_t *tmp = http.first_server;
|
|
|
|
http.first_server = http.first_server->next;
|
|
Mem_Free( tmp );
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============
|
|
HTTP_FreeFile
|
|
|
|
Skip to next server/file
|
|
==============
|
|
*/
|
|
static void HTTP_FreeFile( httpfile_t *file, qboolean error )
|
|
{
|
|
char incname[256];
|
|
|
|
// Allways close file and socket
|
|
if( file->file )
|
|
FS_Close( file->file );
|
|
|
|
file->file = NULL;
|
|
|
|
if( file->socket != -1 )
|
|
closesocket( file->socket );
|
|
|
|
file->socket = -1;
|
|
|
|
Q_snprintf( incname, 256, "downloaded/%s.incomplete", file->path );
|
|
if( error )
|
|
{
|
|
// Switch to next fastdl server if present
|
|
if( file->server && ( file->state > HTTP_QUEUE ) && (file->state != HTTP_FREE ) )
|
|
{
|
|
file->server = file->server->next;
|
|
file->state = HTTP_QUEUE; // Reset download state, HTTP_Run() will open file again
|
|
return;
|
|
}
|
|
|
|
// Called because there was no servers to download, free file now
|
|
if( http_autoremove->value == 1 ) // remove broken file
|
|
FS_Delete( incname );
|
|
else // autoremove disabled, keep file
|
|
Con_Printf( "cannot download %s from any server. "
|
|
"You may remove %s now\n", file->path, incname ); // Warn about trash file
|
|
|
|
if( file->process )
|
|
CL_ProcessFile( false, file->path ); // Process file, increase counter
|
|
}
|
|
else
|
|
{
|
|
// Success, rename and process file
|
|
char name[256];
|
|
|
|
Q_snprintf( name, 256, "downloaded/%s", file->path );
|
|
FS_Rename( incname, name );
|
|
|
|
if( file->process )
|
|
CL_ProcessFile( true, name );
|
|
else
|
|
Con_Printf( "successfully downloaded %s, processing disabled!\n", name );
|
|
}
|
|
|
|
file->state = HTTP_FREE;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
HTTP_AutoClean
|
|
|
|
remove files with HTTP_FREE state from list
|
|
===================
|
|
*/
|
|
static void HTTP_AutoClean( void )
|
|
{
|
|
httpfile_t *curfile, *prevfile = 0;
|
|
|
|
// clean all files marked to free
|
|
for( curfile = http.first_file; curfile; curfile = curfile->next )
|
|
{
|
|
if( curfile->state != HTTP_FREE )
|
|
{
|
|
prevfile = curfile;
|
|
continue;
|
|
}
|
|
|
|
if( curfile == http.first_file )
|
|
{
|
|
http.first_file = http.first_file->next;
|
|
Mem_Free( curfile );
|
|
curfile = http.first_file;
|
|
if( !curfile )
|
|
break;
|
|
continue;
|
|
}
|
|
|
|
if( prevfile )
|
|
prevfile->next = curfile->next;
|
|
Mem_Free( curfile );
|
|
curfile = prevfile;
|
|
if( !curfile )
|
|
break;
|
|
}
|
|
http.last_file = prevfile;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
HTTP_ProcessStream
|
|
|
|
process incoming data
|
|
===================
|
|
*/
|
|
static qboolean HTTP_ProcessStream( httpfile_t *curfile )
|
|
{
|
|
char buf[BUFSIZ+1];
|
|
char *begin = 0;
|
|
int res;
|
|
|
|
if( curfile->header_size >= BUFSIZ )
|
|
{
|
|
Con_Reportf( S_ERROR "Header to big\n");
|
|
HTTP_FreeFile( curfile, true );
|
|
return false;
|
|
}
|
|
|
|
while( ( res = recv( curfile->socket, buf, BUFSIZ - curfile->header_size, 0 ) ) > 0) // if we got there, we are receiving data
|
|
{
|
|
curfile->blocktime = 0;
|
|
|
|
if( curfile->state < HTTP_RESPONSE_RECEIVED ) // Response still not received
|
|
{
|
|
memcpy( curfile->buf + curfile->header_size, buf, res );
|
|
curfile->buf[curfile->header_size + res] = 0;
|
|
begin = Q_strstr( curfile->buf, "\r\n\r\n" );
|
|
|
|
if( begin ) // Got full header
|
|
{
|
|
int cutheadersize = begin - curfile->buf + 4; // after that begin of data
|
|
char *length;
|
|
|
|
Con_Reportf( "HTTP: Got response!\n" );
|
|
|
|
if( !Q_strstr( curfile->buf, "200 OK" ) )
|
|
{
|
|
*begin = 0; // cut string to print out response
|
|
begin = Q_strchr( curfile->buf, '\r' );
|
|
|
|
if( !begin ) begin = Q_strchr( curfile->buf, '\n' );
|
|
if( begin )
|
|
*begin = 0;
|
|
|
|
Con_Printf( S_ERROR "%s: bad response: %s\n", curfile->path, curfile->buf );
|
|
HTTP_FreeFile( curfile, true );
|
|
return false;
|
|
}
|
|
|
|
// print size
|
|
length = Q_stristr( curfile->buf, "Content-Length: " );
|
|
if( length )
|
|
{
|
|
int size = Q_atoi( length += 16 );
|
|
|
|
Con_Reportf( "HTTP: File size is %d\n", size );
|
|
|
|
if( ( curfile->size != -1 ) && ( curfile->size != size ) ) // check size if specified, not used
|
|
Con_Reportf( S_WARN "Server reports wrong file size!\n" );
|
|
|
|
curfile->size = size;
|
|
curfile->header_size = 0;
|
|
}
|
|
|
|
if( curfile->size == -1 )
|
|
{
|
|
// Usually fastdl's reports file size if link is correct
|
|
Con_Printf( S_ERROR "file size is unknown, refusing download!\n" );
|
|
HTTP_FreeFile( curfile, true );
|
|
return false;
|
|
}
|
|
|
|
curfile->state = HTTP_RESPONSE_RECEIVED; // got response, let's start download
|
|
begin += 4;
|
|
|
|
// Write remaining message part
|
|
if( res - cutheadersize - curfile->header_size > 0 )
|
|
{
|
|
int ret = FS_Write( curfile->file, begin, res - cutheadersize - curfile->header_size );
|
|
|
|
if( ret != res - cutheadersize - curfile->header_size ) // could not write file
|
|
{
|
|
// close it and go to next
|
|
Con_Printf( S_ERROR "write failed for %s!\n", curfile->path );
|
|
HTTP_FreeFile( curfile, true );
|
|
return false;
|
|
}
|
|
curfile->downloaded += ret;
|
|
}
|
|
}
|
|
else
|
|
curfile->header_size += res;
|
|
}
|
|
else if( res > 0 )
|
|
{
|
|
// data download
|
|
int ret = FS_Write( curfile->file, buf, res );
|
|
|
|
if ( ret != res )
|
|
{
|
|
// close it and go to next
|
|
Con_Printf( S_ERROR "write failed for %s!\n", curfile->path );
|
|
curfile->state = HTTP_FREE;
|
|
HTTP_FreeFile( curfile, true );
|
|
return false;
|
|
}
|
|
|
|
curfile->downloaded += ret;
|
|
curfile->lastchecksize += ret;
|
|
|
|
// as after it will run in same frame
|
|
if( curfile->checktime > 5 )
|
|
{
|
|
float speed = (float)curfile->lastchecksize / ( 5.0f * 1024 );
|
|
|
|
curfile->checktime = 0;
|
|
Con_Reportf( "download speed %f KB/s\n", speed );
|
|
curfile->lastchecksize = 0;
|
|
}
|
|
}
|
|
}
|
|
curfile->checktime += host.frametime;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
==============
|
|
HTTP_Run
|
|
|
|
Download next file block of each active file
|
|
Call every frame
|
|
==============
|
|
*/
|
|
void HTTP_Run( void )
|
|
{
|
|
httpfile_t *curfile;
|
|
int iActiveCount = 0;
|
|
int iProgressCount = 0;
|
|
float flProgress = 0;
|
|
qboolean fResolving = false;
|
|
|
|
for( curfile = http.first_file; curfile; curfile = curfile->next )
|
|
{
|
|
int res;
|
|
struct sockaddr addr;
|
|
|
|
if( curfile->state == HTTP_FREE )
|
|
continue;
|
|
|
|
if( curfile->state == HTTP_QUEUE )
|
|
{
|
|
char name[MAX_SYSPATH];
|
|
|
|
if( iActiveCount > http_maxconnections->value )
|
|
continue;
|
|
|
|
if( !curfile->server )
|
|
{
|
|
Con_Printf( S_ERROR "no servers to download %s!\n", curfile->path );
|
|
HTTP_FreeFile( curfile, true );
|
|
break;
|
|
}
|
|
|
|
Con_Reportf( "HTTP: Starting download %s from %s\n", curfile->path, curfile->server->host );
|
|
Q_snprintf( name, sizeof( name ), "downloaded/%s.incomplete", curfile->path );
|
|
|
|
curfile->file = FS_Open( name, "wb", true );
|
|
|
|
if( !curfile->file )
|
|
{
|
|
Con_Printf( S_ERROR "cannot open %s!\n", name );
|
|
HTTP_FreeFile( curfile, true );
|
|
break;
|
|
}
|
|
|
|
curfile->state = HTTP_OPENED;
|
|
curfile->blocktime = 0;
|
|
curfile->downloaded = 0;
|
|
curfile->lastchecksize = 0;
|
|
curfile->checktime = 0;
|
|
}
|
|
|
|
iActiveCount++;
|
|
|
|
if( curfile->state < HTTP_SOCKET ) // Socket is not created
|
|
{
|
|
dword mode;
|
|
|
|
curfile->socket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
|
|
|
|
// Now set non-blocking mode
|
|
// You may skip this if not supported by system,
|
|
// but download will lock engine, maybe you will need to add manual returns
|
|
mode = 1;
|
|
ioctlsocket( curfile->socket, FIONBIO, (void*)&mode );
|
|
#if XASH_LINUX
|
|
// SOCK_NONBLOCK is not portable, so use fcntl
|
|
fcntl( curfile->socket, F_SETFL, fcntl( curfile->socket, F_GETFL, 0 ) | O_NONBLOCK );
|
|
#endif
|
|
curfile->state = HTTP_SOCKET;
|
|
}
|
|
|
|
if( curfile->state < HTTP_NS_RESOLVED )
|
|
{
|
|
if( fResolving )
|
|
continue;
|
|
|
|
res = NET_StringToSockaddr( va( "%s:%d", curfile->server->host, curfile->server->port ), &addr, true );
|
|
|
|
if( res == 2 )
|
|
{
|
|
fResolving = true;
|
|
continue;
|
|
}
|
|
|
|
if( !res )
|
|
{
|
|
Con_Printf( S_ERROR "failed to resolve server address for %s!\n", curfile->server->host );
|
|
HTTP_FreeFile( curfile, true ); // Cannot connect
|
|
break;
|
|
}
|
|
curfile->state = HTTP_NS_RESOLVED;
|
|
}
|
|
|
|
if( curfile->state < HTTP_CONNECTED ) // Connection not enstabilished
|
|
{
|
|
res = connect( curfile->socket, &addr, sizeof( struct sockaddr ) );
|
|
|
|
if( res )
|
|
{
|
|
if( WSAGetLastError() == WSAEINPROGRESS || WSAGetLastError() == WSAEWOULDBLOCK ) // Should give EWOOLDBLOCK if try recv too soon
|
|
curfile->state = HTTP_CONNECTED;
|
|
else
|
|
{
|
|
Con_Printf( S_ERROR "cannot connect to server: %s\n", NET_ErrorString( ) );
|
|
HTTP_FreeFile( curfile, true ); // Cannot connect
|
|
break;
|
|
}
|
|
continue; // skip to next file
|
|
}
|
|
curfile->state = HTTP_CONNECTED;
|
|
}
|
|
|
|
if( curfile->state < HTTP_REQUEST ) // Request not formatted
|
|
{
|
|
curfile->query_length = Q_snprintf( curfile->buf, sizeof( curfile->buf ),
|
|
"GET %s%s HTTP/1.0\r\n"
|
|
"Host: %s\r\n"
|
|
"User-Agent: %s\r\n\r\n", curfile->server->path,
|
|
curfile->path, curfile->server->host, http_useragent->string );
|
|
curfile->header_size = 0;
|
|
curfile->bytes_sent = 0;
|
|
curfile->state = HTTP_REQUEST;
|
|
}
|
|
|
|
if( curfile->state < HTTP_REQUEST_SENT ) // Request not sent
|
|
{
|
|
qboolean wait = false;
|
|
|
|
while( curfile->bytes_sent < curfile->query_length )
|
|
{
|
|
res = send( curfile->socket, curfile->buf + curfile->bytes_sent, curfile->query_length - curfile->bytes_sent, 0 );
|
|
|
|
|
|
if( res < 0 )
|
|
{
|
|
if( WSAGetLastError() != WSAEWOULDBLOCK && WSAGetLastError() != WSAENOTCONN )
|
|
{
|
|
Con_Printf( S_ERROR "failed to send request: %s\n", NET_ErrorString() );
|
|
HTTP_FreeFile( curfile, true );
|
|
wait = true;
|
|
break;
|
|
}
|
|
// blocking while waiting connection
|
|
// increase counter when blocking
|
|
curfile->blocktime += host.frametime;
|
|
wait = true;
|
|
|
|
if( curfile->blocktime > http_timeout->value )
|
|
{
|
|
Con_Printf( S_ERROR "timeout on request send:\n%s\n", curfile->buf );
|
|
HTTP_FreeFile( curfile, true );
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
curfile->bytes_sent += res;
|
|
curfile->blocktime = 0;
|
|
}
|
|
}
|
|
|
|
if( wait )
|
|
continue;
|
|
|
|
Con_Reportf( "HTTP: Request sent!\n");
|
|
memset( curfile->buf, 0, sizeof( curfile->buf ) );
|
|
curfile->state = HTTP_REQUEST_SENT;
|
|
}
|
|
|
|
if( !HTTP_ProcessStream( curfile ) )
|
|
break;
|
|
|
|
if( curfile->size > 0 )
|
|
{
|
|
flProgress += (float)curfile->downloaded / curfile->size;
|
|
iProgressCount++;
|
|
}
|
|
|
|
if( curfile->size > 0 && curfile->downloaded >= curfile->size )
|
|
{
|
|
HTTP_FreeFile( curfile, false ); // success
|
|
break;
|
|
}
|
|
else if( (WSAGetLastError() != WSAEWOULDBLOCK) && (WSAGetLastError() != WSAEINPROGRESS) )
|
|
Con_Reportf( "problem downloading %s:\n%s\n", curfile->path, NET_ErrorString() );
|
|
else
|
|
curfile->blocktime += host.frametime;
|
|
|
|
if( curfile->blocktime > http_timeout->value )
|
|
{
|
|
Con_Printf( S_ERROR "timeout on receiving data!\n");
|
|
HTTP_FreeFile( curfile, true );
|
|
break;
|
|
}
|
|
}
|
|
|
|
// update progress
|
|
if( !Host_IsDedicated() )
|
|
Cvar_SetValue( "scr_download", flProgress/iProgressCount * 100 );
|
|
|
|
HTTP_AutoClean();
|
|
}
|
|
|
|
/*
|
|
===================
|
|
HTTP_AddDownload
|
|
|
|
Add new download to end of queue
|
|
===================
|
|
*/
|
|
void HTTP_AddDownload( const char *path, int size, qboolean process )
|
|
{
|
|
httpfile_t *httpfile = Z_Calloc( sizeof( httpfile_t ) );
|
|
|
|
Con_Reportf( "File %s queued to download\n", path );
|
|
|
|
httpfile->size = size;
|
|
httpfile->downloaded = 0;
|
|
httpfile->socket = -1;
|
|
Q_strncpy ( httpfile->path, path, sizeof( httpfile->path ) );
|
|
|
|
if( http.last_file )
|
|
{
|
|
// Add next to last download
|
|
httpfile->id = http.last_file->id + 1;
|
|
http.last_file->next= httpfile;
|
|
http.last_file = httpfile;
|
|
}
|
|
else
|
|
{
|
|
// It will be the only download
|
|
httpfile->id = 0;
|
|
http.last_file = http.first_file = httpfile;
|
|
}
|
|
|
|
httpfile->file = NULL;
|
|
httpfile->next = NULL;
|
|
httpfile->state = HTTP_QUEUE;
|
|
httpfile->server = http.first_server;
|
|
httpfile->process = process;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
HTTP_Download_f
|
|
|
|
Console wrapper
|
|
===============
|
|
*/
|
|
static void HTTP_Download_f( void )
|
|
{
|
|
if( Cmd_Argc() < 2 )
|
|
{
|
|
Con_Printf( S_USAGE "download <gamedir_path>\n");
|
|
return;
|
|
}
|
|
|
|
HTTP_AddDownload( Cmd_Argv( 1 ), -1, false );
|
|
}
|
|
|
|
/*
|
|
==============
|
|
HTTP_ParseURL
|
|
==============
|
|
*/
|
|
static httpserver_t *HTTP_ParseURL( const char *url )
|
|
{
|
|
httpserver_t *server;
|
|
int i;
|
|
|
|
url = Q_strstr( url, "http://" );
|
|
|
|
if( !url )
|
|
return NULL;
|
|
|
|
url += 7;
|
|
server = Z_Calloc( sizeof( httpserver_t ) );
|
|
i = 0;
|
|
|
|
while( *url && ( *url != ':' ) && ( *url != '/' ) && ( *url != '\r' ) && ( *url != '\n' ) )
|
|
{
|
|
if( i > sizeof( server->host ) )
|
|
return NULL;
|
|
|
|
server->host[i++] = *url++;
|
|
}
|
|
|
|
server->host[i] = 0;
|
|
|
|
if( *url == ':' )
|
|
{
|
|
server->port = Q_atoi( ++url );
|
|
|
|
while( *url && ( *url != '/' ) && ( *url != '\r' ) && ( *url != '\n' ) )
|
|
url++;
|
|
}
|
|
else
|
|
server->port = 80;
|
|
|
|
i = 0;
|
|
|
|
while( *url && ( *url != '\r' ) && ( *url != '\n' ) )
|
|
{
|
|
if( i > sizeof( server->path ) )
|
|
return NULL;
|
|
|
|
server->path[i++] = *url++;
|
|
}
|
|
|
|
server->path[i] = 0;
|
|
server->next = NULL;
|
|
server->needfree = false;
|
|
|
|
return server;
|
|
}
|
|
|
|
/*
|
|
=======================
|
|
HTTP_AddCustomServer
|
|
=======================
|
|
*/
|
|
void HTTP_AddCustomServer( const char *url )
|
|
{
|
|
httpserver_t *server = HTTP_ParseURL( url );
|
|
|
|
if( !server )
|
|
{
|
|
Con_Printf( S_ERROR "\"%s\" is not valid url!\n", url );
|
|
return;
|
|
}
|
|
|
|
server->needfree = true;
|
|
server->next = http.first_server;
|
|
http.first_server = server;
|
|
}
|
|
|
|
/*
|
|
=======================
|
|
HTTP_AddCustomServer_f
|
|
=======================
|
|
*/
|
|
static void HTTP_AddCustomServer_f( void )
|
|
{
|
|
if( Cmd_Argc() == 2 )
|
|
{
|
|
HTTP_AddCustomServer( Cmd_Argv( 1 ) );
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
HTTP_Clear_f
|
|
|
|
Clear all queue
|
|
============
|
|
*/
|
|
static void HTTP_Clear_f( void )
|
|
{
|
|
http.last_file = NULL;
|
|
|
|
while( http.first_file )
|
|
{
|
|
httpfile_t *file = http.first_file;
|
|
|
|
http.first_file = http.first_file->next;
|
|
|
|
if( file->file )
|
|
FS_Close( file->file );
|
|
|
|
if( file->socket != -1 )
|
|
closesocket( file->socket );
|
|
|
|
Mem_Free( file );
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============
|
|
HTTP_Cancel_f
|
|
|
|
Stop current download, skip to next file
|
|
==============
|
|
*/
|
|
static void HTTP_Cancel_f( void )
|
|
{
|
|
if( !http.first_file )
|
|
return;
|
|
|
|
http.first_file->state = HTTP_FREE;
|
|
HTTP_FreeFile( http.first_file, true );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
HTTP_Skip_f
|
|
|
|
Stop current download, skip to next server
|
|
=============
|
|
*/
|
|
static void HTTP_Skip_f( void )
|
|
{
|
|
if( http.first_file )
|
|
HTTP_FreeFile( http.first_file, true );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
HTTP_List_f
|
|
|
|
Print all pending downloads to console
|
|
=============
|
|
*/
|
|
static void HTTP_List_f( void )
|
|
{
|
|
httpfile_t *file = http.first_file;
|
|
|
|
while( file )
|
|
{
|
|
if ( file->server )
|
|
Con_Printf ( "\t%d %d http://%s:%d/%s%s %d\n", file->id, file->state,
|
|
file->server->host, file->server->port, file->server->path,
|
|
file->path, file->downloaded );
|
|
else
|
|
Con_Printf ( "\t%d %d (no server) %s\n", file->id, file->state, file->path );
|
|
|
|
file = file->next;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
HTTP_ResetProcessState
|
|
|
|
When connected to new server, all old files should not increase counter
|
|
================
|
|
*/
|
|
void HTTP_ResetProcessState( void )
|
|
{
|
|
httpfile_t *file = http.first_file;
|
|
|
|
while( file )
|
|
{
|
|
file->process = false;
|
|
file = file->next;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
HTTP_Init
|
|
=============
|
|
*/
|
|
void HTTP_Init( void )
|
|
{
|
|
char *serverfile, *line, token[1024];
|
|
|
|
http.last_server = NULL;
|
|
|
|
http.first_file = http.last_file = NULL;
|
|
|
|
Cmd_AddRestrictedCommand("http_download", &HTTP_Download_f, "add file to download queue");
|
|
Cmd_AddRestrictedCommand("http_skip", &HTTP_Skip_f, "skip current download server");
|
|
Cmd_AddRestrictedCommand("http_cancel", &HTTP_Cancel_f, "cancel current download");
|
|
Cmd_AddRestrictedCommand("http_clear", &HTTP_Clear_f, "cancel all downloads");
|
|
Cmd_AddCommand("http_list", &HTTP_List_f, "list all queued downloads");
|
|
Cmd_AddCommand("http_addcustomserver", &HTTP_AddCustomServer_f, "add custom fastdl server");
|
|
http_useragent = Cvar_Get( "http_useragent", "xash3d", FCVAR_ARCHIVE, "User-Agent string" );
|
|
http_autoremove = Cvar_Get( "http_autoremove", "1", FCVAR_ARCHIVE, "remove broken files" );
|
|
http_timeout = Cvar_Get( "http_timeout", "45", FCVAR_ARCHIVE, "timeout for http downloader" );
|
|
http_maxconnections = Cvar_Get( "http_maxconnections", "4", FCVAR_ARCHIVE, "maximum http connection number" );
|
|
|
|
// Read servers from fastdl.txt
|
|
line = serverfile = (char *)FS_LoadFile( "fastdl.txt", 0, false );
|
|
|
|
if( serverfile )
|
|
{
|
|
while( ( line = COM_ParseFile( line, token, sizeof( token ) ) ) )
|
|
{
|
|
httpserver_t *server = HTTP_ParseURL( token );
|
|
|
|
if( !server )
|
|
continue;
|
|
|
|
if( !http.last_server )
|
|
http.last_server = http.first_server = server;
|
|
else
|
|
{
|
|
http.last_server->next = server;
|
|
http.last_server = server;
|
|
}
|
|
}
|
|
|
|
Mem_Free( serverfile );
|
|
}
|
|
}
|
|
|
|
/*
|
|
====================
|
|
HTTP_Shutdown
|
|
====================
|
|
*/
|
|
void HTTP_Shutdown( void )
|
|
{
|
|
HTTP_Clear_f();
|
|
|
|
while( http.first_server )
|
|
{
|
|
httpserver_t *tmp = http.first_server;
|
|
|
|
http.first_server = http.first_server->next;
|
|
Mem_Free( tmp );
|
|
}
|
|
|
|
http.last_server = NULL;
|
|
}
|