/* Copyright (C) 1997-2001 Id Software, Inc. 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 2 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. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // // net.c // #include "com_local.h" #include "protocol.h" #include "q_msg.h" #include "q_fifo.h" #include "net_sock.h" #include "net_stream.h" #ifdef _DEBUG #include "files.h" #endif #include "sys_public.h" #include "sv_public.h" #include "cl_public.h" #include "io_sleep.h" #if( defined _WIN32 ) #define WIN32_LEAN_AND_MEAN #include #include #define socklen_t int #define IOCTLSOCKET_PARAM u_long #define SETSOCKOPT_PARAM BOOL #ifdef _WIN32_WCE #define NET_GET_ERROR() ( net_error = GetLastError() ) #else #define NET_GET_ERROR() ( net_error = WSAGetLastError() ) #define NET_WOULD_BLOCK() ( NET_GET_ERROR() == WSAEWOULDBLOCK ) #endif #elif( defined __unix__ ) #include #include #include #include #include #include #include #include #include #ifdef __linux__ #include #if USE_ICMP #include #endif #endif #define SOCKET int #define INVALID_SOCKET -1 #define closesocket close #define ioctlsocket ioctl #define IOCTLSOCKET_PARAM int #define SETSOCKOPT_PARAM int #define NET_GET_ERROR() ( net_error = errno ) #define NET_WOULD_BLOCK() ( NET_GET_ERROR() == EWOULDBLOCK ) #else #error Unknown target OS #endif #if USE_CLIENT #define MAX_LOOPBACK 4 typedef struct { byte data[MAX_PACKETLEN]; size_t datalen; } loopmsg_t; typedef struct { loopmsg_t msgs[MAX_LOOPBACK]; unsigned get; unsigned send; } loopback_t; static loopback_t loopbacks[NS_COUNT]; #endif cvar_t *net_ip; cvar_t *net_port; netadr_t net_from; #if USE_CLIENT static cvar_t *net_clientport; static cvar_t *net_dropsim; #endif #ifdef _DEBUG static cvar_t *net_log_enable; static cvar_t *net_log_name; static cvar_t *net_log_flush; #endif static cvar_t *net_tcp_ip; static cvar_t *net_tcp_port; static cvar_t *net_tcp_backlog; #if USE_ICMP static cvar_t *net_ignore_icmp; #endif static netflag_t net_active; static int net_error; static const char socketNames[NS_COUNT][8] = { "Client", "Server" }; static SOCKET udp_sockets[NS_COUNT] = { INVALID_SOCKET, INVALID_SOCKET }; static SOCKET tcp_socket = INVALID_SOCKET; #ifdef _DEBUG static qhandle_t net_logFile; #endif // current rate measurement static unsigned net_rate_time; static size_t net_rate_rcvd; static size_t net_rate_sent; static size_t net_rate_dn; static size_t net_rate_up; // lifetime statistics static unsigned long long net_recv_errors; static unsigned long long net_send_errors; #if USE_ICMP static unsigned long long net_icmp_errors; #endif static unsigned long long net_bytes_rcvd; static unsigned long long net_bytes_sent; static unsigned long long net_packets_rcvd; static unsigned long long net_packets_sent; //============================================================================= /* =================== NET_NetadrToSockadr =================== */ static void NET_NetadrToSockadr( const netadr_t *a, struct sockaddr_in *s ) { memset( s, 0, sizeof( *s ) ); switch( a->type ) { case NA_BROADCAST: s->sin_family = AF_INET; s->sin_addr.s_addr = INADDR_BROADCAST; s->sin_port = a->port; break; case NA_IP: s->sin_family = AF_INET; s->sin_addr.s_addr = *( uint32_t * )&a->ip; s->sin_port = a->port; break; default: Com_Error( ERR_FATAL, "%s: bad address type", __func__ ); break; } } /* =================== NET_SockadrToNetadr =================== */ static void NET_SockadrToNetadr( const struct sockaddr_in *s, netadr_t *a ) { memset( a, 0, sizeof( *a ) ); a->type = NA_IP; *( uint32_t * )&a->ip = s->sin_addr.s_addr; a->port = s->sin_port; } /* ============= NET_StringToSockaddr localhost idnewt idnewt:28000 192.246.40.70 192.246.40.70:28000 ============= */ static qboolean NET_StringToSockaddr( const char *s, struct sockaddr_in *sadr ) { struct hostent *h; char copy[MAX_QPATH], *p; int dots; memset( sadr, 0, sizeof( *sadr ) ); sadr->sin_family = AF_INET; sadr->sin_port = 0; Q_strlcpy( copy, s, sizeof( copy ) ); // strip off a trailing :port if present p = strchr( copy, ':' ); if( p ) { *p = 0; sadr->sin_port = htons( ( u_short )atoi( p + 1 ) ); } for( p = copy, dots = 0; *p; p++ ) { if( *p == '.' ) { dots++; } else if( !Q_isdigit( *p ) ) { break; } } if( *p == 0 && dots <= 3 ) { uint32_t addr = inet_addr( copy ); if( addr == INADDR_NONE ) { return qfalse; } sadr->sin_addr.s_addr = addr; } else { if( !( h = gethostbyname( copy ) ) ) return qfalse; sadr->sin_addr.s_addr = *( uint32_t * )h->h_addr_list[0]; } return qtrue; } /* =================== NET_AdrToString =================== */ char *NET_AdrToString( const netadr_t *a ) { static char s[MAX_QPATH]; switch( a->type ) { case NA_LOOPBACK: strcpy( s, "loopback" ); return s; case NA_IP: case NA_BROADCAST: Q_snprintf( s, sizeof( s ), "%u.%u.%u.%u:%u", a->ip[0], a->ip[1], a->ip[2], a->ip[3], ntohs( a->port ) ); return s; default: Com_Error( ERR_FATAL, "%s: bad address type", __func__ ); break; } return NULL; } /* ============= NET_StringToAdr localhost idnewt idnewt:28000 192.246.40.70 192.246.40.70:28000 ============= */ qboolean NET_StringToAdr( const char *s, netadr_t *a, int port ) { struct sockaddr_in sadr; if( !NET_StringToSockaddr( s, &sadr ) ) { return qfalse; } NET_SockadrToNetadr( &sadr, a ); if( !a->port ) { a->port = BigShort( port ); } return qtrue; } //============================================================================= #ifdef _DEBUG static void logfile_close( void ) { if( !net_logFile ) { return; } Com_Printf( "Closing network log.\n" ); FS_FCloseFile( net_logFile ); net_logFile = 0; } static void logfile_open( void ) { char buffer[MAX_OSPATH]; unsigned mode; qhandle_t f; mode = net_log_enable->integer > 1 ? FS_MODE_APPEND : FS_MODE_WRITE; if( net_log_flush->integer ) { mode |= FS_FLUSH_SYNC; } f = FS_EasyOpenFile( buffer, sizeof( buffer ), mode, "logs/", net_log_name->string, ".log" ); if( !f ) { Cvar_Set( "net_log_enable", "0" ); return; } net_logFile = f; Com_Printf( "Logging network packets to %s\n", buffer ); } static void net_log_enable_changed( cvar_t *self ) { logfile_close(); if( self->integer ) { logfile_open(); } } static void net_log_param_changed( cvar_t *self ) { if( net_log_enable->integer ) { logfile_close(); logfile_open(); } } /* ============= NET_LogPacket ============= */ static void NET_LogPacket( const netadr_t *address, const char *prefix, const byte *data, size_t length ) { int numRows; int i, j, c; if( !net_logFile ) { return; } FS_FPrintf( net_logFile, "%s : %s\n", prefix, NET_AdrToString( address ) ); numRows = ( length + 15 ) / 16; for( i = 0; i < numRows; i++ ) { FS_FPrintf( net_logFile, "%04x : ", i * 16 ); for( j = 0; j < 16; j++ ) { if( i * 16 + j < length ) { FS_FPrintf( net_logFile, "%02x ", data[i * 16 + j] ); } else { FS_FPrintf( net_logFile, " " ); } } FS_FPrintf( net_logFile, ": " ); for( j = 0; j < 16; j++ ) { if( i * 16 + j < length ) { c = data[i * 16 + j]; FS_FPrintf( net_logFile, "%c", ( c < 32 || c > 127 ) ? '.' : c ); } else { FS_FPrintf( net_logFile, " " ); } } FS_FPrintf( net_logFile, "\n" ); } FS_FPrintf( net_logFile, "\n" ); } #endif #define RATE_SECS 3 static void NET_UpdateStats( void ) { unsigned diff; if( net_rate_time > com_eventTime ) { net_rate_time = com_eventTime; } diff = com_eventTime - net_rate_time; if( diff < RATE_SECS * 1000 ) { return; } net_rate_time = com_eventTime; net_rate_dn = net_rate_rcvd / RATE_SECS; net_rate_up = net_rate_sent / RATE_SECS; net_rate_sent = 0; net_rate_rcvd = 0; } #if USE_CLIENT /* ============= NET_GetLoopPacket ============= */ qboolean NET_GetLoopPacket( netsrc_t sock ) { loopback_t *loop; loopmsg_t *loopmsg; NET_UpdateStats(); loop = &loopbacks[sock]; if( loop->send - loop->get > MAX_LOOPBACK - 1 ) { loop->get = loop->send - MAX_LOOPBACK + 1; } if( loop->get >= loop->send ) { return qfalse; } loopmsg = &loop->msgs[loop->get & (MAX_LOOPBACK-1)]; loop->get++; memcpy( msg_read_buffer, loopmsg->data, loopmsg->datalen ); #ifdef _DEBUG if( net_log_enable->integer ) { NET_LogPacket( &net_from, "LP recv", loopmsg->data, loopmsg->datalen ); } #endif if( sock == NS_CLIENT ) { net_rate_rcvd += loopmsg->datalen; } SZ_Init( &msg_read, msg_read_buffer, sizeof( msg_read_buffer ) ); msg_read.cursize = loopmsg->datalen; return qtrue; } #endif #if USE_ICMP // prevents infinite retry loops caused by broken TCP/IP stacks #define MAX_ERROR_RETRIES 64 static void icmp_error_event( netsrc_t sock, int info ) { if( net_ignore_icmp->integer > 0 ) { return; } Com_DPrintf( "%s: %s from %s\n", __func__, NET_ErrorString(), NET_AdrToString( &net_from ) ); net_icmp_errors++; switch( sock ) { case NS_SERVER: SV_ErrorEvent( info ); break; #if USE_CLIENT case NS_CLIENT: CL_ErrorEvent(); break; #endif default: break; } } #ifdef __linux__ // Linux at least supports receiving ICMP errors on unconnected UDP sockets // via IP_RECVERR cruft below... What about BSD? // // Returns true if failed socket operation should be retried, extremely hacky :/ static qboolean process_error_queue( netsrc_t sock, struct sockaddr_in *to ) { byte buffer[1024]; struct sockaddr_in from; struct msghdr msg; struct cmsghdr *cmsg; struct sock_extended_err *ee; int info; int tries; qboolean found; tries = 0; found = qfalse; retry: memset( &from, 0, sizeof( from ) ); memset( &msg, 0, sizeof( msg ) ); msg.msg_name = &from; msg.msg_namelen = sizeof( from ); msg.msg_control = buffer; msg.msg_controllen = sizeof( buffer ); if( recvmsg( udp_sockets[sock], &msg, MSG_ERRQUEUE ) == -1 ) { if( NET_WOULD_BLOCK() ) { // wouldblock is silent goto finish; } Com_EPrintf( "%s: %s\n", __func__, NET_ErrorString() ); goto finish; } if( !( msg.msg_flags & MSG_ERRQUEUE ) ) { Com_DPrintf( "%s: no extended error received\n", __func__ ); goto finish; } // find an ICMP error message for( cmsg = CMSG_FIRSTHDR( &msg ); cmsg != NULL; cmsg = CMSG_NXTHDR( &msg, cmsg ) ) { if( cmsg->cmsg_level != IPPROTO_IP ) { continue; } if( cmsg->cmsg_type != IP_RECVERR ) { continue; } ee = ( struct sock_extended_err * )CMSG_DATA( cmsg ); if( ee->ee_origin == SO_EE_ORIGIN_ICMP ) { break; } } if( !cmsg ) { Com_DPrintf( "%s: no ICMP error found\n", __func__ ); goto finish; } NET_SockadrToNetadr( &from, &net_from ); // check if this error was caused by a packet sent to the given address // if so, do not retry send operation to prevent infinite loop if( to != NULL && from.sin_addr.s_addr == to->sin_addr.s_addr && ( !from.sin_port || from.sin_port == to->sin_port ) ) { Com_DPrintf( "%s: found offending address %s:%d\n", __func__, inet_ntoa( from.sin_addr ), BigShort( from.sin_port ) ); found = qtrue; } // handle ICMP errors net_error = ee->ee_errno; info = 0; #if USE_PMTUDISC // for EMSGSIZE ee_info should hold discovered MTU if( net_error == EMSGSIZE && ee->ee_info >= 576 && ee->ee_info < 4096 ) { info = ee->ee_info; } #endif icmp_error_event( sock, info ); if( ++tries < MAX_ERROR_RETRIES ) { goto retry; } finish: return !!tries && !found; } #endif // __linux__ #endif // USE_ICMP /* ============= NET_GetPacket Fills msg_read_buffer with packet contents, net_from variable receives source address. ============= */ qboolean NET_GetPacket( netsrc_t sock ) { struct sockaddr_in from; socklen_t fromlen; int ret; #if USE_ICMP int tries; int saved_error; #endif ioentry_t *e; if( udp_sockets[sock] == INVALID_SOCKET ) { return qfalse; } NET_UpdateStats(); e = IO_Get( udp_sockets[sock] ); if( !e->canread ) { return qfalse; } #if USE_ICMP tries = 0; retry: #endif memset( &from, 0, sizeof( from ) ); fromlen = sizeof( from ); ret = recvfrom( udp_sockets[sock], ( void * )msg_read_buffer, MAX_PACKETLEN, 0, ( struct sockaddr * )&from, &fromlen ); if( !ret ) { return qfalse; } NET_SockadrToNetadr( &from, &net_from ); if( ret == -1 ) { NET_GET_ERROR(); #ifdef _WIN32 switch( net_error ) { case WSAEWOULDBLOCK: // wouldblock is silent e->canread = qfalse; break; #if USE_ICMP case WSAECONNRESET: case WSAENETRESET: // winsock has already provided us with // a valid address from ICMP error packet icmp_error_event( sock, 0 ); if( ++tries < MAX_ERROR_RETRIES ) { goto retry; } // intentional fallthrough #endif // USE_ICMP default: Com_DPrintf( "%s: %s from %s\n", __func__, NET_ErrorString(), NET_AdrToString( &net_from ) ); net_recv_errors++; break; } #else // _WIN32 switch( net_error ) { case EWOULDBLOCK: // wouldblock is silent e->canread = qfalse; break; default: #if USE_ICMP saved_error = net_error; // recvfrom() fails on Linux if there's an ICMP originated // pending error on socket. suck up error queue and retry... if( process_error_queue( sock, NULL ) ) { if( ++tries < MAX_ERROR_RETRIES ) { goto retry; } } #endif net_error = saved_error; Com_DPrintf( "%s: %s from %s\n", __func__, NET_ErrorString(), NET_AdrToString( &net_from ) ); net_recv_errors++; break; } #endif // !_WIN32 return qfalse; } if( ret > MAX_PACKETLEN ) { Com_EPrintf( "%s: oversize packet from %s\n", __func__, NET_AdrToString( &net_from ) ); return qfalse; } #ifdef _DEBUG if( net_log_enable->integer ) { NET_LogPacket( &net_from, "UDP recv", msg_read_buffer, ret ); } #endif SZ_Init( &msg_read, msg_read_buffer, sizeof( msg_read_buffer ) ); msg_read.cursize = ret; net_rate_rcvd += ret; net_bytes_rcvd += ret; net_packets_rcvd++; return qtrue; } //============================================================================= /* ============= NET_SendPacket ============= */ qboolean NET_SendPacket( netsrc_t sock, const netadr_t *to, size_t length, const void *data ) { struct sockaddr_in addr; int ret; #if USE_ICMP && ( defined __linux__ ) int tries; int saved_error; #endif if( !length ) { return qfalse; } if( length > MAX_PACKETLEN ) { Com_EPrintf( "%s: oversize packet to %s\n", __func__, NET_AdrToString( to ) ); return qfalse; } switch( to->type ) { #if USE_CLIENT case NA_LOOPBACK: { loopback_t *loop; loopmsg_t *msg; if( net_dropsim->integer > 0 && ( rand() % 100 ) < net_dropsim->integer ) { return NET_AGAIN; } loop = &loopbacks[sock ^ 1]; msg = &loop->msgs[loop->send & ( MAX_LOOPBACK - 1 )]; loop->send++; memcpy( msg->data, data, length ); msg->datalen = length; #ifdef _DEBUG if( net_log_enable->integer ) { NET_LogPacket( to, "LB send", data, length ); } #endif if( sock == NS_CLIENT ) { net_rate_sent += length; } } return qtrue; #endif case NA_IP: case NA_BROADCAST: break; default: Com_Error( ERR_FATAL, "%s: bad address type", __func__ ); break; } if( udp_sockets[sock] == INVALID_SOCKET ) { return qfalse; } NET_NetadrToSockadr( to, &addr ); #if USE_ICMP && ( defined __linux__ ) tries = 0; retry: #endif ret = sendto( udp_sockets[sock], data, length, 0, ( struct sockaddr * )&addr, sizeof( addr ) ); if( ret == -1 ) { NET_GET_ERROR(); #ifdef _WIN32 switch( net_error ) { case WSAEWOULDBLOCK: case WSAEINTR: // wouldblock is silent break; case WSAEADDRNOTAVAIL: // some PPP links do not allow broadcasts if( to->type == NA_BROADCAST ) { break; } // intentional fallthrough default: Com_DPrintf( "%s: %s to %s\n", __func__, NET_ErrorString(), NET_AdrToString( to ) ); net_send_errors++; break; } #else // _WIN32 switch( net_error ) { case EWOULDBLOCK: // wouldblock is silent break; default: #if USE_ICMP saved_error = net_error; // sendto() fails on Linux if there's an ICMP originated // pending error on socket. suck up error queue and retry... // // this one is especially lame - how do I distingiush between // a failure caused by completely unrelated ICMP error sitting // in the queue and an error explicit to this sendto() call? // // on one hand, I don't want to drop packets to legitimate // clients because of this, and have to retry sendto() after // processing error queue, on another hand, infinite loop should be // avoided if this sendto() regenerates a message in error queue // // this mess is worked around by passing destination address // to process_error_queue() and checking if this address/port // pair is found in the queue. if it is found, or the queue // is empty, do not retry if( process_error_queue( sock, &addr ) ) { if( ++tries < MAX_ERROR_RETRIES ) { goto retry; } } #endif net_error = saved_error; Com_DPrintf( "%s: %s to %s\n", __func__, NET_ErrorString(), NET_AdrToString( to ) ); net_send_errors++; break; } #endif // !_WIN32 return qfalse; } if( ret != length ) { Com_WPrintf( "%s: short send to %s\n", __func__, NET_AdrToString( to ) ); } #ifdef _DEBUG if( net_log_enable->integer ) { NET_LogPacket( to, "UDP send", data, ret ); } #endif net_rate_sent += ret; net_bytes_sent += ret; net_packets_sent++; return qtrue; } //============================================================================= const char *NET_ErrorString( void ) { #ifdef _WIN32 switch( net_error ) { case S_OK: return "NO ERROR"; default: return "UNKNOWN ERROR"; #include "wsaerr.h" } #else return strerror( net_error ); #endif } static qboolean get_bind_addr( const char *iface, int port, struct sockaddr_in *sadr ) { if( *iface ) { if( !NET_StringToSockaddr( iface, sadr ) ) { return qfalse; } } else { // empty string binds to all interfaces memset( sadr, 0, sizeof( *sadr ) ); sadr->sin_family = AF_INET; sadr->sin_addr.s_addr = INADDR_ANY; } if( port != PORT_ANY ) { sadr->sin_port = htons( ( u_short )port ); } return qtrue; } static SOCKET create_socket( int type, int proto ) { SOCKET ret = socket( PF_INET, type, proto ); NET_GET_ERROR(); return ret; } static int set_option( SOCKET s, int level, int optname, int value ) { SETSOCKOPT_PARAM _value = value; int ret = setsockopt( s, level, optname, ( char * )&_value, sizeof( _value ) ); NET_GET_ERROR(); return ret; } #define enable_option(s,level,optname) set_option(s,level,optname,1) static int make_nonblock( SOCKET s ) { IOCTLSOCKET_PARAM _true = 1; int ret = ioctlsocket( s, FIONBIO, &_true ); NET_GET_ERROR(); return ret; } static int bind_socket( SOCKET s, struct sockaddr_in *sadr ) { int ret = bind( s, ( struct sockaddr * )sadr, sizeof( *sadr ) ); NET_GET_ERROR(); return ret; } static SOCKET UDP_OpenSocket( const char *iface, int port ) { SOCKET s; struct sockaddr_in sadr; #ifdef __linux__ int pmtudisc; #endif Com_DPrintf( "Opening UDP socket: %s:%i\n", iface, port ); s = create_socket( SOCK_DGRAM, IPPROTO_UDP ); if( s == INVALID_SOCKET ) { Com_EPrintf( "%s: %s:%d: can't create socket: %s\n", __func__, iface, port, NET_ErrorString() ); return INVALID_SOCKET; } // make it non-blocking if( make_nonblock( s ) == -1 ) { Com_EPrintf( "%s: %s:%d: can't make socket non-blocking: %s\n", __func__, iface, port, NET_ErrorString() ); goto fail; } // make it broadcast capable if( enable_option( s, SOL_SOCKET, SO_BROADCAST ) == -1 ) { Com_WPrintf( "%s: %s:%d: can't make socket broadcast capable: %s\n", __func__, iface, port, NET_ErrorString() ); } #ifdef __linux__ pmtudisc = IP_PMTUDISC_DONT; #if USE_ICMP // enable ICMP error queue if( net_ignore_icmp->integer <= 0 ) { if( enable_option( s, IPPROTO_IP, IP_RECVERR ) == -1 ) { Com_WPrintf( "%s: %s:%d: can't enable ICMP error queue: %s\n", __func__, iface, port, NET_ErrorString() ); Cvar_Set( "net_ignore_icmp", "1" ); } #if USE_PMTUDISC // overload negative values to enable path MTU discovery switch( net_ignore_icmp->integer ) { case -1: pmtudisc = IP_PMTUDISC_WANT; break; case -2: pmtudisc = IP_PMTUDISC_DO; break; #ifdef IP_PMTUDISC_PROBE case -3: pmtudisc = IP_PMTUDISC_PROBE; break; #endif } #endif // USE_PMTUDISC } #endif // USE_ICMP // disable or enable path MTU discovery if( set_option( s, IPPROTO_IP, IP_MTU_DISCOVER, pmtudisc ) == -1 ) { Com_WPrintf( "%s: %s:%d: can't %sable path MTU discovery: %s\n", __func__, iface, port, pmtudisc == IP_PMTUDISC_DONT ? "dis" : "en", NET_ErrorString() ); } #endif // __linux__ // resolve iface sadr if( !get_bind_addr( iface, port, &sadr ) ) { Com_EPrintf( "%s: %s:%d: bad interface address\n", __func__, iface, port ); goto fail; } if( bind_socket( s, &sadr ) == -1 ) { Com_EPrintf( "%s: %s:%d: can't bind socket: %s\n", __func__, iface, port, NET_ErrorString() ); goto fail; } return s; fail: closesocket( s ); return INVALID_SOCKET; } static SOCKET TCP_OpenSocket( const char *iface, int port, netsrc_t who ) { SOCKET s; struct sockaddr_in sadr; Com_DPrintf( "Opening TCP socket: %s:%i\n", iface, port ); s = create_socket( SOCK_STREAM, IPPROTO_TCP ); if( s == INVALID_SOCKET ) { Com_EPrintf( "%s: %s:%d: can't create socket: %s\n", __func__, iface, port, NET_ErrorString() ); return INVALID_SOCKET; } // make it non-blocking if( make_nonblock( s ) == -1 ) { Com_EPrintf( "%s: %s:%d: can't make socket non-blocking: %s\n", __func__, iface, port, NET_ErrorString() ); goto fail; } // give it a chance to reuse previous port if( who == NS_SERVER ) { if( enable_option( s, SOL_SOCKET, SO_REUSEADDR ) == -1 ) { Com_WPrintf( "%s: %s:%d: can't force socket to reuse address: %s\n", __func__, iface, port, NET_ErrorString() ); } } if( !get_bind_addr( iface, port, &sadr ) ) { Com_EPrintf( "%s: %s:%d: bad interface address\n", __func__, iface, port ); goto fail; } if( bind_socket( s, &sadr ) == -1 ) { Com_EPrintf( "%s: %s:%d: can't bind socket: %s\n", __func__, iface, port, NET_ErrorString() ); goto fail; } return s; fail: closesocket( s ); return INVALID_SOCKET; } static void NET_OpenServer( void ) { static int saved_port; ioentry_t *e; SOCKET s; s = UDP_OpenSocket( net_ip->string, net_port->integer ); if( s != INVALID_SOCKET ) { saved_port = net_port->integer; udp_sockets[NS_SERVER] = s; e = IO_Add( s ); e->wantread = qtrue; return; } if( saved_port && saved_port != net_port->integer ) { // revert to the last valid port Com_Printf( "Reverting to the last valid port %d...\n", saved_port ); Cbuf_AddText( &cmd_buffer, va( "set net_port %d\n", saved_port ) ); return; } #if USE_CLIENT if( !dedicated->integer ) { Com_WPrintf( "Couldn't open server UDP port.\n" ); return; } #endif Com_Error( ERR_FATAL, "Couldn't open dedicated server UDP port" ); } #if USE_CLIENT static void NET_OpenClient( void ) { ioentry_t *e; SOCKET s; struct sockaddr_in sadr; socklen_t len; s = UDP_OpenSocket( net_ip->string, net_clientport->integer ); if( s == INVALID_SOCKET ) { // now try with random port if( net_clientport->integer != PORT_ANY ) { s = UDP_OpenSocket( net_ip->string, PORT_ANY ); } if( s == INVALID_SOCKET ) { Com_WPrintf( "Couldn't open client UDP port.\n" ); return; } len = sizeof( sadr ); getsockname( s, ( struct sockaddr * )&sadr, &len ); Com_WPrintf( "Client bound to UDP port %d.\n", ntohs( sadr.sin_port ) ); Cvar_SetByVar( net_clientport, va( "%d", PORT_ANY ), FROM_CODE ); } udp_sockets[NS_CLIENT] = s; e = IO_Add( s ); e->wantread = qtrue; } #endif //============================================================================= void NET_Close( netstream_t *s ) { if( !s->state ) { return; } IO_Remove( s->socket ); closesocket( s->socket ); s->socket = INVALID_SOCKET; s->state = NS_DISCONNECTED; } neterr_t NET_Listen( qboolean arg ) { SOCKET s; ioentry_t *e; if( !arg ) { if( tcp_socket != INVALID_SOCKET ) { IO_Remove( tcp_socket ); closesocket( tcp_socket ); tcp_socket = INVALID_SOCKET; } return NET_OK; } if( tcp_socket != INVALID_SOCKET ) { return NET_OK; } s = TCP_OpenSocket( net_tcp_ip->string, net_tcp_port->integer, NS_SERVER ); if( s == INVALID_SOCKET ) { return NET_ERROR; } if( listen( s, net_tcp_backlog->integer ) == -1 ) { NET_GET_ERROR(); closesocket( s ); return NET_ERROR; } tcp_socket = s; e = IO_Add( s ); e->wantread = qtrue; return NET_OK; } // net_from variable receives source address neterr_t NET_Accept( netstream_t *s ) { struct sockaddr_in from; socklen_t fromlen; SOCKET newsocket; ioentry_t *e; if( tcp_socket == INVALID_SOCKET ) { return NET_AGAIN; } e = IO_Get( tcp_socket ); if( !e->canread ) { return NET_AGAIN; } memset( &from, 0, sizeof( from ) ); fromlen = sizeof( from ); newsocket = accept( tcp_socket, ( struct sockaddr * )&from, &fromlen ); NET_SockadrToNetadr( &from, &net_from ); if( newsocket == -1 ) { if( NET_WOULD_BLOCK() ) { // wouldblock is silent e->canread = qfalse; return NET_AGAIN; } return NET_ERROR; } // make it non-blocking if( make_nonblock( newsocket ) == -1 ) { closesocket( newsocket ); return NET_ERROR; } // initialize stream memset( s, 0, sizeof( *s ) ); s->socket = newsocket; s->address = net_from; s->state = NS_CONNECTED; // initialize io entry e = IO_Add( newsocket ); //e->wantwrite = qtrue; e->wantread = qtrue; return NET_OK; } neterr_t NET_Connect( const netadr_t *peer, netstream_t *s ) { SOCKET socket; ioentry_t *e; struct sockaddr_in sadr; int ret; // always bind to `net_ip' for outgoing TCP connections // to avoid problems with AC or MVD/GTV auth on a multi IP system socket = TCP_OpenSocket( net_ip->string, PORT_ANY, NS_CLIENT ); if( socket == INVALID_SOCKET ) { return NET_ERROR; } NET_NetadrToSockadr( peer, &sadr ); ret = connect( socket, ( struct sockaddr * )&sadr, sizeof( sadr ) ); if( ret == -1 ) { #ifdef _WIN32 if( NET_GET_ERROR() != WSAEWOULDBLOCK ) { #else if( NET_GET_ERROR() != EINPROGRESS ) { #endif // wouldblock is silent closesocket( socket ); return NET_ERROR; } } // initialize stream memset( s, 0, sizeof( *s ) ); s->state = NS_CONNECTING; s->address = *peer; s->socket = socket; // initialize io entry e = IO_Add( socket ); e->wantwrite = qtrue; #ifdef _WIN32 e->wantexcept = qtrue; #endif return NET_OK; } neterr_t NET_RunConnect( netstream_t *s ) { socklen_t len; int ret, err; ioentry_t *e; if( s->state != NS_CONNECTING ) { return NET_AGAIN; } e = IO_Get( s->socket ); if( !e->canwrite #ifdef _WIN32 && !e->canexcept #endif ) { return NET_AGAIN; } len = sizeof( err ); ret = getsockopt( s->socket, SOL_SOCKET, SO_ERROR, ( char * )&err, &len ); if( ret == -1 ) { goto error1; } if( err ) { net_error = err; goto error2; } e->wantwrite = qfalse; e->wantread = qtrue; #ifdef _WIN32 e->wantexcept = qfalse; #endif s->state = NS_CONNECTED; return NET_OK; error1: NET_GET_ERROR(); error2: s->state = NS_BROKEN; e->wantwrite = qfalse; e->wantread = qfalse; #ifdef _WIN32 e->wantexcept = qfalse; #endif return NET_ERROR; } // updates wantread/wantwrite void NET_UpdateStream( netstream_t *s ) { size_t len; ioentry_t *e; if( s->state != NS_CONNECTED ) { return; } e = IO_Get( s->socket ); FIFO_Reserve( &s->recv, &len ); e->wantread = len ? qtrue : qfalse; FIFO_Peek( &s->send, &len ); e->wantwrite = len ? qtrue : qfalse; } // returns NET_OK only when there was some data read neterr_t NET_RunStream( netstream_t *s ) { int ret; size_t len; void *data; neterr_t result = NET_AGAIN; ioentry_t *e; if( s->state != NS_CONNECTED ) { return result; } e = IO_Get( s->socket ); if( e->wantread && e->canread ) { // read as much as we can data = FIFO_Reserve( &s->recv, &len ); if( len ) { ret = recv( s->socket, data, len, 0 ); if( !ret ) { goto closed; } if( ret == -1 ) { if( NET_WOULD_BLOCK() ) { // wouldblock is silent e->canread = qfalse; } else { goto error; } } else { FIFO_Commit( &s->recv, ret ); #if _DEBUG if( net_log_enable->integer ) { NET_LogPacket( &s->address, "TCP recv", data, ret ); } #endif net_rate_rcvd += ret; net_bytes_rcvd += ret; result = NET_OK; // now see if there's more space to read FIFO_Reserve( &s->recv, &len ); if( !len ) { e->wantread = qfalse; } } } } if( e->wantwrite && e->canwrite ) { // write as much as we can data = FIFO_Peek( &s->send, &len ); if( len ) { ret = send( s->socket, data, len, 0 ); if( !ret ) { goto closed; } if( ret == -1 ) { if( NET_WOULD_BLOCK() ) { // wouldblock is silent e->canwrite = qfalse; } else { goto error; } } else { FIFO_Decommit( &s->send, ret ); #if _DEBUG if( net_log_enable->integer ) { NET_LogPacket( &s->address, "TCP send", data, ret ); } #endif net_rate_sent += ret; net_bytes_sent += ret; //result = NET_OK; // now see if there's more data to write FIFO_Peek( &s->send, &len ); if( !len ) { e->wantwrite = qfalse; } } } } return result; closed: s->state = NS_CLOSED; e->wantread = qfalse; return NET_CLOSED; error: s->state = NS_BROKEN; e->wantread = qfalse; e->wantwrite = qfalse; return NET_ERROR; } //=================================================================== /* ==================== NET_Stats_f ==================== */ static void NET_Stats_f( void ) { time_t diff, now = time( NULL ); char buffer[MAX_QPATH]; if( com_startTime > now ) { com_startTime = now; } diff = now - com_startTime; if( diff < 1 ) { diff = 1; } Com_FormatTime( buffer, sizeof( buffer ), diff ); Com_Printf( "Network uptime: %s\n", buffer ); Com_Printf( "Bytes sent: %llu (%llu bytes/sec)\n", net_bytes_sent, net_bytes_sent / diff ); Com_Printf( "Bytes rcvd: %llu (%llu bytes/sec)\n", net_bytes_rcvd, net_bytes_rcvd / diff ); Com_Printf( "Packets sent: %llu (%llu packets/sec)\n", net_packets_sent, net_packets_sent / diff ); Com_Printf( "Packets rcvd: %llu (%llu packets/sec)\n", net_packets_rcvd, net_packets_rcvd / diff ); #if USE_ICMP Com_Printf( "Total errors: %llu/%llu/%llu (send/recv/icmp)\n", net_send_errors, net_recv_errors, net_icmp_errors ); #else Com_Printf( "Total errors: %llu/%llu (send/recv)\n", net_send_errors, net_recv_errors ); #endif Com_Printf( "Current upload rate: %"PRIz" bytes/sec\n", net_rate_up ); Com_Printf( "Current download rate: %"PRIz" bytes/sec\n", net_rate_dn ); } /* ==================== NET_DumpHostInfo ==================== */ static void NET_DumpHostInfo( struct hostent *h ) { byte **list; int i; Com_Printf( "Hostname: %s\n", h->h_name ); list = ( byte ** )h->h_aliases; for( i = 0; list[i]; i++ ) { Com_Printf( "Alias : %s\n", list[i] ); } list = ( byte ** )h->h_addr_list; for( i = 0; list[i]; i++ ) { Com_Printf( "IP : %u.%u.%u.%u\n", list[i][0], list[i][1], list[i][2], list[i][3] ); } } static void dump_socket( SOCKET s, const char *s1, const char *s2 ) { struct sockaddr_in sadr; socklen_t len; netadr_t adr; len = sizeof( sadr ); if( getsockname( s, ( struct sockaddr * )&sadr, &len ) == -1 ) { NET_GET_ERROR(); Com_EPrintf( "%s: getsockname: %s\n", __func__, NET_ErrorString() ); return; } NET_SockadrToNetadr( &sadr, &adr ); Com_Printf( "%s %s socket bound to %s\n", s1, s2, NET_AdrToString( &adr ) ); } /* ==================== NET_ShowIP_f ==================== */ static void NET_ShowIP_f( void ) { char buffer[256]; struct hostent *h; netsrc_t sock; if( gethostname( buffer, sizeof( buffer ) ) == -1 ) { NET_GET_ERROR(); Com_EPrintf( "%s: gethostname: %s\n", __func__, NET_ErrorString() ); return; } if( !( h = gethostbyname( buffer ) ) ) { NET_GET_ERROR(); Com_EPrintf( "%s: gethostbyname: %s\n", __func__, NET_ErrorString() ); return; } NET_DumpHostInfo( h ); for( sock = 0; sock < NS_COUNT; sock++ ) { if( udp_sockets[sock] != INVALID_SOCKET ) { dump_socket( udp_sockets[sock], socketNames[sock], "UDP" ); } } if( tcp_socket != INVALID_SOCKET ) { dump_socket( tcp_socket, socketNames[NS_SERVER], "TCP" ); } } /* ==================== NET_Dns_f ==================== */ static void NET_Dns_f( void ) { char buffer[MAX_QPATH]; char *p; struct hostent *h; u_long address; if( Cmd_Argc() != 2 ) { Com_Printf( "Usage: %s
\n", Cmd_Argv( 0 ) ); return; } Cmd_ArgvBuffer( 1, buffer, sizeof( buffer ) ); if( ( p = strchr( buffer, ':' ) ) != NULL ) { *p = 0; } if( ( address = inet_addr( buffer ) ) != INADDR_NONE ) { h = gethostbyaddr( ( const char * )&address, sizeof( address ), AF_INET ); } else { h = gethostbyname( buffer ); } if( !h ) { Com_Printf( "Couldn't resolve %s\n", buffer ); return; } NET_DumpHostInfo( h ); } /* ==================== NET_Restart_f ==================== */ static void NET_Restart_f( void ) { netflag_t flag = net_active; qboolean listen = tcp_socket != INVALID_SOCKET; Com_DPrintf( "%s\n", __func__ ); if( listen ) { NET_Listen( qfalse ); } NET_Config( NET_NONE ); NET_Config( flag ); if( listen ) { NET_Listen( qtrue ); } #if USE_SYSCON SV_SetConsoleTitle(); #endif } /* ==================== NET_Config ==================== */ void NET_Config( netflag_t flag ) { netsrc_t sock; if( flag == net_active ) { return; } if( flag == NET_NONE ) { // shut down any existing sockets for( sock = 0; sock < NS_COUNT; sock++ ) { if( udp_sockets[sock] != INVALID_SOCKET ) { IO_Remove( udp_sockets[sock] ); closesocket( udp_sockets[sock] ); udp_sockets[sock] = INVALID_SOCKET; } } net_active = NET_NONE; return; } #if USE_CLIENT if( ( flag & NET_CLIENT ) && udp_sockets[NS_CLIENT] == INVALID_SOCKET ) { NET_OpenClient(); } #endif if( ( flag & NET_SERVER ) && udp_sockets[NS_SERVER] == INVALID_SOCKET ) { NET_OpenServer(); } net_active |= flag; } qboolean NET_GetAddress( netsrc_t sock, netadr_t *adr ) { struct sockaddr_in sadr; socklen_t len; SOCKET s = udp_sockets[sock]; if( s == INVALID_SOCKET ) { return qfalse; } len = sizeof( sadr ); if( getsockname( s, ( struct sockaddr * )&sadr, &len ) == -1 ) { return qfalse; } NET_SockadrToNetadr( &sadr, adr ); return qtrue; } static size_t NET_UpRate_m( char *buffer, size_t size ) { return Q_scnprintf( buffer, size, "%"PRIz, net_rate_up ); } static size_t NET_DnRate_m( char *buffer, size_t size ) { return Q_scnprintf( buffer, size, "%"PRIz, net_rate_dn ); } static void net_udp_param_changed( cvar_t *self ) { // keep TCP socket vars in sync unless modified by user if( !( net_tcp_ip->flags & CVAR_MODIFIED ) ) { Cvar_SetByVar( net_tcp_ip, net_ip->string, FROM_CODE ); } if( !( net_tcp_port->flags & CVAR_MODIFIED ) ) { Cvar_SetByVar( net_tcp_port, net_port->string, FROM_CODE ); } NET_Restart_f(); } static void net_tcp_param_changed( cvar_t *self ) { if( tcp_socket != INVALID_SOCKET ) { NET_Listen( qfalse ); NET_Listen( qtrue ); } } /* ==================== NET_Init ==================== */ void NET_Init( void ) { #ifdef _WIN32 WSADATA ws; int ret; ret = WSAStartup( MAKEWORD( 1, 1 ), &ws ); if( ret ) { Com_Error( ERR_FATAL, "Winsock initialization failed, returned %d", ret ); } Com_DPrintf( "Winsock Initialized\n" ); #endif net_ip = Cvar_Get( "net_ip", "", 0 ); net_ip->changed = net_udp_param_changed; net_port = Cvar_Get( "net_port", PORT_SERVER_STRING, 0 ); net_port->changed = net_udp_param_changed; #if USE_CLIENT net_clientport = Cvar_Get( "net_clientport", PORT_ANY_STRING, 0 ); net_clientport->changed = net_udp_param_changed; net_dropsim = Cvar_Get( "net_dropsim", "0", 0 ); #endif #if _DEBUG net_log_enable = Cvar_Get( "net_log_enable", "0", 0 ); net_log_enable->changed = net_log_enable_changed; net_log_name = Cvar_Get( "net_log_name", "network", 0 ); net_log_name->changed = net_log_param_changed; net_log_flush = Cvar_Get( "net_log_flush", "0", 0 ); net_log_flush->changed = net_log_param_changed; #endif #if USE_ICMP net_ignore_icmp = Cvar_Get( "net_ignore_icmp", "0", 0 ); #endif net_tcp_ip = Cvar_Get( "net_tcp_ip", net_ip->string, 0 ); net_tcp_ip->changed = net_tcp_param_changed; net_tcp_port = Cvar_Get( "net_tcp_port", net_port->string, 0 ); net_tcp_port->changed = net_tcp_param_changed; net_tcp_backlog = Cvar_Get( "net_tcp_backlog", "4", 0 ); #if _DEBUG net_log_enable_changed( net_log_enable ); #endif net_rate_time = com_eventTime; Cmd_AddCommand( "net_restart", NET_Restart_f ); Cmd_AddCommand( "net_stats", NET_Stats_f ); Cmd_AddCommand( "showip", NET_ShowIP_f ); Cmd_AddCommand( "dns", NET_Dns_f ); Cmd_AddMacro( "net_uprate", NET_UpRate_m ); Cmd_AddMacro( "net_dnrate", NET_DnRate_m ); } /* ==================== NET_Shutdown ==================== */ void NET_Shutdown( void ) { #if _DEBUG if( net_logFile ) { FS_FCloseFile( net_logFile ); net_logFile = 0; } #endif NET_Listen( qfalse ); NET_Config( NET_NONE ); #ifdef _WIN32 WSACleanup(); #endif Cmd_RemoveCommand( "net_restart" ); Cmd_RemoveCommand( "net_stats" ); Cmd_RemoveCommand( "showip" ); Cmd_RemoveCommand( "dns" ); }