/* ================== SV_CheckTimeouts If a packet has not been received from a client for timeout->value seconds, drop the conneciton. Server frames are used instead of realtime to avoid dropping the local client while debugging. When a client is normally dropped, the client_t goes into a zombie state for a few seconds to make sure any final reliable message gets resent if necessary ================== */ void SV_CheckTimeouts (void) { int32_t i; client_t *cl; int32_t droppoint; int32_t zombiepoint; droppoint = svs.realtime - 1000*timeout->value; zombiepoint = svs.realtime - 1000*zombietime->value; for (i=0,cl=svs.clients ; i<maxclients->value ; i++,cl++) { // message times may be wrong across a changelevel if (cl->lastmessage > svs.realtime) cl->lastmessage = svs.realtime; if (cl->state == cs_zombie && cl->lastmessage < zombiepoint) { SV_CleanClient (cl); // r1ch fix: make sure client is cleaned up cl->state = cs_free; // can now be reused continue; } if ( (cl->state == cs_connected || cl->state == cs_spawned) && cl->lastmessage < droppoint) { // r1ch fix: only message if they spawned (less spam plz) if (cl->state == cs_spawned && cl->name[0]) SV_BroadcastPrintf (PRINT_HIGH, "%s timed out\n", cl->name); SV_DropClient (cl); cl->state = cs_free; // don't bother with zombie state } } }
/* * ================== * SVC_DirectConnect * * A connection request that did not come from the master * ================== */ void SVC_DirectConnect(void) { char userinfo[MAX_INFO_STRING]; netadr_t adr; int i; client_t *cl, *newcl; client_t temp; edict_t *ent; int edictnum; int version; int qport; int challenge; int previousclients; // rich: connection limit per IP adr = net_from; Com_DPrintf("SVC_DirectConnect ()\n"); version = atoi(Cmd_Argv(1)); if (version != PROTOCOL_VERSION) { Netchan_OutOfBandPrint(NS_SERVER, adr, "print\nServer is version %4.2f.\n", VERSION); Com_DPrintf(" rejected connect from version %i\n", version); return; } qport = atoi(Cmd_Argv(2)); challenge = atoi(Cmd_Argv(3)); // r1ch: limit connections from a single IP previousclients = 0; for (i = 0, cl = svs.clients; i < (int)maxclients->value; i++, cl++) { if (cl->state == cs_free) { continue; } if (NET_CompareBaseAdr(adr, cl->netchan.remote_address)) { // r1ch: zombies are less dangerous if (cl->state == cs_zombie) { previousclients++; } else { previousclients += 2; } } } if (previousclients >= (int)sv_iplimit->value * 2) { Netchan_OutOfBandPrint(NS_SERVER, adr, "print\nToo many connections from your host.\n"); Com_DPrintf(" too many connections\n"); return; } // end r1ch fix strncpy(userinfo, Cmd_Argv(4), sizeof(userinfo) - 1); userinfo[sizeof(userinfo) - 1] = 0; // force the IP key/value pair so the game can filter based on ip Info_SetValueForKey(userinfo, "ip", NET_AdrToString(net_from)); // attractloop servers are ONLY for local clients if (sv.attractloop) { if (!NET_IsLocalAddress(adr)) { Com_Printf("Remote connect in attract loop. Ignored.\n"); Netchan_OutOfBandPrint(NS_SERVER, adr, "print\nConnection refused.\n"); return; } } // see if the challenge is valid if (!NET_IsLocalAddress(adr)) { for (i = 0; i < MAX_CHALLENGES; i++) { if (NET_CompareBaseAdr(net_from, svs.challenges[i].adr)) { if (challenge == svs.challenges[i].challenge) { break; // good } Netchan_OutOfBandPrint(NS_SERVER, adr, "print\nBad challenge.\n"); return; } } if (i == MAX_CHALLENGES) { Netchan_OutOfBandPrint(NS_SERVER, adr, "print\nNo challenge for address.\n"); return; } } newcl = &temp; memset(newcl, 0, sizeof(client_t)); // if there is already a slot for this ip, reuse it for (i = 0, cl = svs.clients; i < maxclients->value; i++, cl++) { if (cl->state == cs_free) { continue; } if (NET_CompareBaseAdr(adr, cl->netchan.remote_address) && ((cl->netchan.qport == qport) || (adr.port == cl->netchan.remote_address.port))) { if (!NET_IsLocalAddress(adr) && ((svs.realtime - cl->lastconnect) < ((int)sv_reconnect_limit->value * 1000))) { Com_DPrintf("%s:reconnect rejected : too soon\n", NET_AdrToString(adr)); return; } // r1ch: !! fix nasty bug where non-disconnected clients (from dropped disconnect // packets) could be overwritten! if (cl->state != cs_zombie) { Com_DPrintf(" client already found\n"); // If we legitly get here, spoofed udp isn't possible (passed challenge) and client addr/port combo // is exactly the same, so we can assume its really a dropped/crashed client. i hope... Com_Printf("Dropping %s, ghost reconnect\n", cl->name); SV_DropClient(cl); } // end r1ch fix Com_Printf("%s:reconnect\n", NET_AdrToString(adr)); SV_CleanClient(cl); // r1ch: clean up last client data newcl = cl; goto gotnewcl; } } // find a client slot newcl = NULL; for (i = 0, cl = svs.clients; i < maxclients->value; i++, cl++) { if (cl->state == cs_free) { newcl = cl; break; } } if (!newcl) { Netchan_OutOfBandPrint(NS_SERVER, adr, "print\nServer is full.\n"); Com_DPrintf("Rejected a connection.\n"); return; } gotnewcl: // build a new connection // accept the new client // this is the only place a client_t is ever initialized *newcl = temp; sv_client = newcl; edictnum = (newcl - svs.clients) + 1; ent = EDICT_NUM(edictnum); newcl->edict = ent; newcl->challenge = challenge; // save challenge for checksumming // get the game a chance to reject this connection or modify the userinfo if (!(ge->ClientConnect(ent, userinfo))) { if (*Info_ValueForKey(userinfo, "rejmsg")) { Netchan_OutOfBandPrint(NS_SERVER, adr, "print\n%s\nConnection refused.\n", Info_ValueForKey(userinfo, "rejmsg")); } else { Netchan_OutOfBandPrint(NS_SERVER, adr, "print\nConnection refused.\n"); } Com_DPrintf("Game rejected a connection.\n"); return; } // parse some info from the info strings strncpy(newcl->userinfo, userinfo, sizeof(newcl->userinfo) - 1); SV_UserinfoChanged(newcl); // send the connect packet to the client Netchan_OutOfBandPrint(NS_SERVER, adr, "client_connect"); Netchan_Setup(NS_SERVER, &newcl->netchan, adr, qport); newcl->state = cs_connected; SZ_Init(&newcl->datagram, newcl->datagram_buf, sizeof(newcl->datagram_buf)); newcl->datagram.allowoverflow = true; newcl->lastmessage = svs.realtime; // don't timeout newcl->lastconnect = svs.realtime; }