static qboolean SV_IsBanned( netadr_t *from, qboolean isexception ) { int index; serverBan_t *curban; if ( !serverBansCount ) { return qfalse; } if ( !isexception ) { // If this is a query for a ban, first check whether the client is excepted if ( SV_IsBanned( from, qtrue ) ) return qfalse; } for ( index = 0; index < serverBansCount; index++ ) { curban = &serverBans[index]; if ( curban->isexception == isexception ) { if ( NET_CompareBaseAdrMask( curban->ip, *from, curban->subnet ) ) return qtrue; } } return qfalse; }
peer_t *FWD_peer_new(const char *remote_host, int remote_port, struct sockaddr_in *from, const char *userinfo, int qport, protocol_t proto, qbool link) { peer_t *p; struct sockaddr_in to; int s = INVALID_SOCKET; qbool new_peer = false; if (!NET_GetSockAddrIn_ByHostAndPort(&to, remote_host, remote_port)) return NULL; // failed to resolve host name? // check for bans. if (SV_IsBanned(&to)) return NULL; // we probably already have such peer, reuse it then p = FWD_peer_by_addr( from ); // next check for NEW peer only if ( !p ) { new_peer = true; // it will be new peer if (FWD_peers_count() >= maxclients->integer) return NULL; // we already full! // NOTE: socket allocated here! Do not forget free it!!! if ((s = NET_UDP_OpenSocket(NULL, 0, false)) == INVALID_SOCKET) return NULL; // out of sockets? p = Sys_malloc(sizeof(*p)); // alloc peer if needed } p->s = ( new_peer ) ? s : p->s; // reuse socket in case of reusing p->from = *from; p->to = to; p->ps = ( !new_peer && proto == pr_q3 ) ? p->ps : ps_challenge; // do not reset state for q3 in case of peer reusing p->qport = qport; p->proto = proto; strlcpy(p->userinfo, userinfo, sizeof(p->userinfo)); Info_ValueForKey(userinfo, "name", p->name, sizeof(p->name)); p->userid = ( new_peer ) ? ++userid : p->userid; // do not bump userid in case of peer reusing time(&p->last); // link only new peer, in case of reusing it already done... if (new_peer && link) { p->next = peers; peers = p; } return p; }
/* ================== SV_DirectConnect A "connect" OOB command has been received ================== */ void SV_DirectConnect( netadr_t from ) { char userinfo[MAX_INFO_STRING]; int i; client_t *cl, *newcl; client_t temp; sharedEntity_t *ent; int clientNum; int version; int qport; int challenge; char *password; int startIndex; char *denied; int count; char *ip; Com_DPrintf ("SVC_DirectConnect ()\n"); // Check whether this client is banned. if ( SV_IsBanned( &from, qfalse ) ) { NET_OutOfBandPrint( NS_SERVER, from, "print\nYou are banned from this server.\n" ); Com_DPrintf( " rejected connect from %s (banned)\n", NET_AdrToString(from) ); return; } Q_strncpyz( userinfo, Cmd_Argv(1), sizeof(userinfo) ); version = atoi( Info_ValueForKey( userinfo, "protocol" ) ); if ( version != PROTOCOL_VERSION ) { NET_OutOfBandPrint( NS_SERVER, from, "print\nServer uses protocol version %i (yours is %i).\n", PROTOCOL_VERSION, version ); Com_DPrintf (" rejected connect from version %i\n", version); return; } challenge = atoi( Info_ValueForKey( userinfo, "challenge" ) ); qport = atoi( Info_ValueForKey( userinfo, "qport" ) ); // quick reject for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { /* This was preventing sv_reconnectlimit from working. It seems like commenting this out has solved the problem. HOwever, if there is a future problem then it could be this. if ( cl->state == CS_FREE ) { continue; } */ if ( NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) && ( cl->netchan.qport == qport || from.port == cl->netchan.remoteAddress.port ) ) { if (( svs.time - cl->lastConnectTime) < (sv_reconnectlimit->integer * 1000)) { NET_OutOfBandPrint( NS_SERVER, from, "print\nReconnect rejected : too soon\n" ); Com_DPrintf ("%s:reconnect rejected : too soon\n", NET_AdrToString (from)); return; } break; } } // don't let "ip" overflow userinfo string if ( NET_IsLocalAddress (from) ) ip = "localhost"; else ip = (char *)NET_AdrToString( from ); if( ( strlen( ip ) + strlen( userinfo ) + 4 ) >= MAX_INFO_STRING ) { NET_OutOfBandPrint( NS_SERVER, from, "print\nUserinfo string length exceeded. " "Try removing setu cvars from your config.\n" ); return; } Info_SetValueForKey( userinfo, "ip", ip ); // see if the challenge is valid (localhost clients don't need to challenge) if (!NET_IsLocalAddress(from)) { // Verify the received challenge against the expected challenge if (!SV_VerifyChallenge(challenge, from)) { NET_OutOfBandPrint( NS_SERVER, from, "print\nIncorrect challenge for your address.\n" ); return; } } newcl = &temp; Com_Memset (newcl, 0, sizeof(client_t)); // if there is already a slot for this ip, reuse it for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { if ( cl->state == CS_FREE ) { continue; } if ( NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) && ( cl->netchan.qport == qport || from.port == cl->netchan.remoteAddress.port ) ) { Com_Printf ("%s:reconnect\n", NET_AdrToString (from)); newcl = cl; // VVFIXME - both SOF2 and Wolf remove this call, claiming it blows away the user's info // disconnect the client from the game first so any flags the // player might have are dropped GVM_ClientDisconnect( newcl - svs.clients ); // goto gotnewcl; } } // find a client slot // if "sv_privateClients" is set > 0, then that number // of client slots will be reserved for connections that // have "password" set to the value of "sv_privatePassword" // Info requests will report the maxclients as if the private // slots didn't exist, to prevent people from trying to connect // to a full server. // This is to allow us to reserve a couple slots here on our // servers so we can play without having to kick people. // check for privateClient password password = Info_ValueForKey( userinfo, "password" ); if ( !strcmp( password, sv_privatePassword->string ) ) { startIndex = 0; } else { // skip past the reserved slots startIndex = sv_privateClients->integer; } newcl = NULL; for ( i = startIndex; i < sv_maxclients->integer ; i++ ) { cl = &svs.clients[i]; if (cl->state == CS_FREE) { newcl = cl; break; } } if ( !newcl ) { if ( NET_IsLocalAddress( from ) ) { count = 0; for ( i = startIndex; i < sv_maxclients->integer ; i++ ) { cl = &svs.clients[i]; if (cl->netchan.remoteAddress.type == NA_BOT) { count++; } } // if they're all bots if (count >= sv_maxclients->integer - startIndex) { SV_DropClient(&svs.clients[sv_maxclients->integer - 1], "only bots on server"); newcl = &svs.clients[sv_maxclients->integer - 1]; } else { Com_Error( ERR_FATAL, "server is full on local connect\n" ); return; } } else { const char *SV_GetStringEdString(char *refSection, char *refName); NET_OutOfBandPrint( NS_SERVER, from, va("print\n%s\n", SV_GetStringEdString("MP_SVGAME","SERVER_IS_FULL"))); Com_DPrintf ("Rejected a connection.\n"); return; } } // we got a newcl, so reset the reliableSequence and reliableAcknowledge cl->reliableAcknowledge = 0; cl->reliableSequence = 0; gotnewcl: // build a new connection // accept the new client // this is the only place a client_t is ever initialized *newcl = temp; clientNum = newcl - svs.clients; ent = SV_GentityNum( clientNum ); newcl->gentity = ent; // save the challenge newcl->challenge = challenge; // save the address Netchan_Setup (NS_SERVER, &newcl->netchan , from, qport); // save the userinfo Q_strncpyz( newcl->userinfo, userinfo, sizeof(newcl->userinfo) ); // get the game a chance to reject this connection or modify the userinfo denied = GVM_ClientConnect( clientNum, qtrue, qfalse ); // firstTime = qtrue if ( denied ) { NET_OutOfBandPrint( NS_SERVER, from, "print\n%s\n", denied ); Com_DPrintf ("Game rejected a connection: %s.\n", denied); return; } SV_UserinfoChanged( newcl ); // send the connect packet to the client NET_OutOfBandPrint( NS_SERVER, from, "connectResponse" ); Com_DPrintf( "Going from CS_FREE to CS_CONNECTED for %s\n", newcl->name ); newcl->state = CS_CONNECTED; newcl->nextSnapshotTime = svs.time; newcl->lastPacketTime = svs.time; newcl->lastConnectTime = svs.time; // when we receive the first packet from the client, we will // notice that it is from a different serverid and that the // gamestate message was not just sent, forcing a retransmit newcl->gamestateMessageNum = -1; newcl->lastUserInfoChange = 0; //reset the delay newcl->lastUserInfoCount = 0; //reset the count // if this was the first client on the server, or the last client // the server can hold, send a heartbeat to the master. count = 0; for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { if ( svs.clients[i].state >= CS_CONNECTED ) { count++; } } if ( count == 1 || count == sv_maxclients->integer ) { SV_Heartbeat_f(); } }
void SV_DirectConnect( netadr_t from ) { char userinfo[MAX_INFO_STRING]; int i; client_t *cl, *newcl; client_t temp; sharedEntity_t *ent; int clientNum; int version; int qport; int challenge; char *password; int startIndex; intptr_t denied; int count; char *ip; Com_DPrintf ("SVC_DirectConnect ()\n"); // Check whether this client is banned. if(SV_IsBanned(&from, qfalse)) { NET_OutOfBandPrint(NS_SERVER, from, "print\nYou are banned from this server.\n"); return; } Q_strncpyz( userinfo, Cmd_Argv(1), sizeof(userinfo) ); version = atoi( Info_ValueForKey( userinfo, "protocol" ) ); if ( version != PROTOCOL_VERSION ) { NET_OutOfBandPrint( NS_SERVER, from, "print\nServer uses protocol version %i.\n", PROTOCOL_VERSION ); Com_DPrintf (" rejected connect from version %i\n", version); return; } challenge = atoi( Info_ValueForKey( userinfo, "challenge" ) ); qport = atoi( Info_ValueForKey( userinfo, "qport" ) ); // quick reject for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { if ( cl->state == CS_FREE ) { continue; } if ( NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) && ( cl->netchan.qport == qport || from.port == cl->netchan.remoteAddress.port ) ) { if (( svs.time - cl->lastConnectTime) < (sv_reconnectlimit->integer * 1000)) { Com_DPrintf ("%s:reconnect rejected : too soon\n", NET_AdrToString (from)); return; } break; } } // don't let "ip" overflow userinfo string if ( NET_IsLocalAddress (from) ) ip = "localhost"; else ip = (char *)NET_AdrToString( from ); if( ( strlen( ip ) + strlen( userinfo ) + 4 ) >= MAX_INFO_STRING ) { NET_OutOfBandPrint( NS_SERVER, from, "print\nUserinfo string length exceeded. " "Try removing setu cvars from your config.\n" ); return; } Info_SetValueForKey( userinfo, "ip", ip ); // see if the challenge is valid (LAN clients don't need to challenge) if (!NET_IsLocalAddress(from)) { int ping; challenge_t *challengeptr; for (i=0; i<MAX_CHALLENGES; i++) { if (NET_CompareAdr(from, svs.challenges[i].adr)) { if(challenge == svs.challenges[i].challenge) break; } } if (i == MAX_CHALLENGES) { NET_OutOfBandPrint( NS_SERVER, from, "print\nNo or bad challenge for your address.\n" ); return; } challengeptr = &svs.challenges[i]; if(challengeptr->wasrefused) { // Return silently, so that error messages written by the server keep being displayed. return; } ping = svs.time - challengeptr->pingTime; // never reject a LAN client based on ping if ( !Sys_IsLANAddress( from ) ) { if ( sv_minPing->value && ping < sv_minPing->value ) { NET_OutOfBandPrint( NS_SERVER, from, "print\nServer is for high pings only\n" ); Com_DPrintf ("Client %i rejected on a too low ping\n", i); challengeptr->wasrefused = qtrue; return; } if ( sv_maxPing->value && ping > sv_maxPing->value ) { NET_OutOfBandPrint( NS_SERVER, from, "print\nServer is for low pings only\n" ); Com_DPrintf ("Client %i rejected on a too high ping\n", i); challengeptr->wasrefused = qtrue; return; } } Com_Printf("Client %i connecting with %i challenge ping\n", i, ping); challengeptr->connected = qtrue; } newcl = &temp; Com_Memset (newcl, 0, sizeof(client_t)); // if there is already a slot for this ip, reuse it for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { if ( cl->state == CS_FREE ) { continue; } if ( NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) && ( cl->netchan.qport == qport || from.port == cl->netchan.remoteAddress.port ) ) { Com_Printf ("%s:reconnect\n", NET_AdrToString (from)); newcl = cl; // this doesn't work because it nukes the players userinfo // // disconnect the client from the game first so any flags the // // player might have are dropped // VM_Call( gvm, GAME_CLIENT_DISCONNECT, newcl - svs.clients ); // goto gotnewcl; } } // find a client slot // if "sv_privateClients" is set > 0, then that number // of client slots will be reserved for connections that // have "password" set to the value of "sv_privatePassword" // Info requests will report the maxclients as if the private // slots didn't exist, to prevent people from trying to connect // to a full server. // This is to allow us to reserve a couple slots here on our // servers so we can play without having to kick people. // check for privateClient password password = Info_ValueForKey( userinfo, "password" ); if ( !strcmp( password, sv_privatePassword->string ) ) { startIndex = 0; } else { // skip past the reserved slots startIndex = sv_privateClients->integer; } newcl = NULL; for ( i = startIndex; i < sv_maxclients->integer ; i++ ) { cl = &svs.clients[i]; if (cl->state == CS_FREE) { newcl = cl; break; } } if ( !newcl ) { if ( NET_IsLocalAddress( from ) ) { count = 0; for ( i = startIndex; i < sv_maxclients->integer ; i++ ) { cl = &svs.clients[i]; if (cl->netchan.remoteAddress.type == NA_BOT) { count++; } } // if they're all bots if (count >= sv_maxclients->integer - startIndex) { SV_DropClient(&svs.clients[sv_maxclients->integer - 1], "only bots on server"); newcl = &svs.clients[sv_maxclients->integer - 1]; } else { Com_Error( ERR_FATAL, "server is full on local connect\n" ); return; } } else { NET_OutOfBandPrint( NS_SERVER, from, "print\nServer is full.\n" ); Com_DPrintf ("Rejected a connection.\n"); return; } } // we got a newcl, so reset the reliableSequence and reliableAcknowledge cl->reliableAcknowledge = 0; cl->reliableSequence = 0; gotnewcl: // build a new connection // accept the new client // this is the only place a client_t is ever initialized *newcl = temp; clientNum = newcl - svs.clients; ent = SV_GentityNum( clientNum ); newcl->gentity = ent; // save the challenge newcl->challenge = challenge; // save the address Netchan_Setup (NS_SERVER, &newcl->netchan , from, qport); // init the netchan queue newcl->netchan_end_queue = &newcl->netchan_start_queue; // save the userinfo Q_strncpyz( newcl->userinfo, userinfo, sizeof(newcl->userinfo) ); // get the game a chance to reject this connection or modify the userinfo denied = VM_Call( gvm, GAME_CLIENT_CONNECT, clientNum, qtrue, qfalse ); // firstTime = qtrue if ( denied ) { // we can't just use VM_ArgPtr, because that is only valid inside a VM_Call char *str = VM_ExplicitArgPtr( gvm, denied ); NET_OutOfBandPrint( NS_SERVER, from, "print\n%s\n", str ); Com_DPrintf ("Game rejected a connection: %s.\n", str); return; } SV_UserinfoChanged( newcl ); // send the connect packet to the client NET_OutOfBandPrint( NS_SERVER, from, "connectResponse" ); Com_DPrintf( "Going from CS_FREE to CS_CONNECTED for %s\n", newcl->name ); newcl->state = CS_CONNECTED; newcl->nextSnapshotTime = svs.time; newcl->lastPacketTime = svs.time; newcl->lastConnectTime = svs.time; // when we receive the first packet from the client, we will // notice that it is from a different serverid and that the // gamestate message was not just sent, forcing a retransmit newcl->gamestateMessageNum = -1; // if this was the first client on the server, or the last client // the server can hold, send a heartbeat to the master. count = 0; for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { if ( svs.clients[i].state >= CS_CONNECTED ) { count++; } } if ( count == 1 || count == sv_maxclients->integer ) { SV_Heartbeat_f(); } }
static void FWD_network_update(void) { fd_set rfds; struct timeval tv; int retval; int i1; peer_t *p; FD_ZERO(&rfds); // select on main server socket FD_SET(net_socket, &rfds); i1 = net_socket + 1; for (p = peers; p; p = p->next) { // select on peers sockets FD_SET(p->s, &rfds); if (p->s >= i1) i1 = p->s + 1; } // if not DLL - read stdin #ifndef APP_DLL #ifndef _WIN32 // try read stdin only if connected to a terminal. if (isatty(STDIN) && isatty(STDOUT)) { FD_SET(STDIN, &rfds); if (STDIN >= i1) i1 = STDIN + 1; } #endif // _WIN32 #endif /* Sleep for some time, wake up immidiately if there input packet. */ tv.tv_sec = 0; tv.tv_usec = 100000; // 100 ms retval = select(i1, &rfds, (fd_set *)0, (fd_set *)0, &tv); // read console input. // NOTE: we do not do that if we are in DLL mode... Sys_ReadSTDIN(&ps, rfds); if (retval <= 0) return; // if we have input packet on main server/proxy socket, then read it if(FD_ISSET(net_socket, &rfds)) { qbool connectionless; int cnt; // read it for(;;) { if (!NET_GetPacket(net_socket, &net_message)) break; // check for bans. if (SV_IsBanned(&net_from)) continue; if (net_message.cursize == 1 && net_message.data[0] == A2A_ACK) { QRY_SV_PingReply(); continue; } MSG_BeginReading(); connectionless = (MSG_ReadLong() == -1); if (connectionless) { if (MSG_BadRead()) continue; if (!SV_ConnectionlessPacket()) continue; // seems we do not need forward it } // search in peers for (p = peers; p; p = p->next) { // we have this peer already, so forward/send packet to remote server if (NET_CompareAddress(&p->from, &net_from)) break; } // peer was not found if (!p) continue; // forward data to the server/proxy if (p->ps >= ps_connected) { cnt = 1; // one packet by default // check for "drop" aka client disconnect, // first 10 bytes for NON connectionless packet is netchan related shit in QW if (p->proto == pr_qw && !connectionless && net_message.cursize > 10 && net_message.data[10] == clc_stringcmd) { if (!strcmp((char*)net_message.data + 10 + 1, "drop")) { // Sys_Printf("peer drop detected\n"); p->ps = ps_drop; // drop peer ASAP cnt = 3; // send few packets due to possibile packet lost } } for ( ; cnt > 0; cnt--) NET_SendPacket(p->s, net_message.cursize, net_message.data, &p->to); } time(&p->last); } } // now lets check peers sockets, perhaps we have input packets too for (p = peers; p; p = p->next) { if(FD_ISSET(p->s, &rfds)) { // yeah, we have packet, read it then for (;;) { if (!NET_GetPacket(p->s, &net_message)) break; // check for bans. if (SV_IsBanned(&net_from)) continue; // we should check is this packet from remote server, this may be some evil packet from haxors... if (!NET_CompareAddress(&p->to, &net_from)) continue; MSG_BeginReading(); if (MSG_ReadLong() == -1) { if (MSG_BadRead()) continue; if (!CL_ConnectionlessPacket(p)) continue; // seems we do not need forward it NET_SendPacket(net_socket, net_message.cursize, net_message.data, &p->from); continue; } if (p->ps >= ps_connected) NET_SendPacket(net_socket, net_message.cursize, net_message.data, &p->from); // qqshka: commented out // time(&p->last); } // for (;;) } // if(FD_ISSET(p->s, &rfds)) if (p->ps == ps_challenge) { // send challenge time to time if (time(NULL) - p->connect > 2) { p->connect = time(NULL); Netchan_OutOfBandPrint(p->s, &p->to, "getchallenge%s", p->proto == pr_qw ? "\n" : ""); } } } // for (p = peers; p; p = p->next) }