/* * Svc_RemoteCommand * * A client issued an rcon command. Shift down the remaining args and * redirect all output to the invoking client. */ static void Svc_RemoteCommand(void) { const boolean_t auth = Sv_RconAuthenticate(); // first print to the server console if (auth) Com_Print("Rcon from %s:\n%s\n", Net_NetaddrToString(net_from), net_message.data + 4); else Com_Print("Bad rcon from %s:\n%s\n", Net_NetaddrToString(net_from), net_message.data + 4); // then redirect the remaining output back to the client Com_BeginRedirect(RD_PACKET, sv_outputbuf, SV_OUTPUTBUF_LENGTH, Sv_FlushRedirect); if (auth) { char remaining[MAX_STRING_CHARS]; int i; remaining[0] = 0; for (i = 2; i < Cmd_Argc(); i++) { strcat(remaining, Cmd_Argv(i)); strcat(remaining, " "); } Cmd_ExecuteString(remaining); } else { Com_Print("Bad rcon_password.\n"); } Com_EndRedirect(); }
/* * Sv_ConnectionlessPacket * * A connectionless packet has four leading 0xff bytes to distinguish it from * a game channel. Clients that are in the game can still send these, and they * will be handled here. */ static void Sv_ConnectionlessPacket(void) { char *s; char *c; Msg_BeginReading(&net_message); Msg_ReadLong(&net_message); // skip the -1 marker s = Msg_ReadStringLine(&net_message); Cmd_TokenizeString(s); c = Cmd_Argv(0); Com_Debug("Packet from %s: %s\n", Net_NetaddrToString(net_from), c); if (!strcmp(c, "ping")) Svc_Ping(); else if (!strcmp(c, "ack")) Svc_Ack(); else if (!strcmp(c, "status")) Svc_Status(); else if (!strcmp(c, "info")) Svc_Info(); else if (!strcmp(c, "get_challenge")) Svc_GetChallenge(); else if (!strcmp(c, "connect")) Svc_Connect(); else if (!strcmp(c, "rcon")) Svc_RemoteCommand(); else Com_Print("Bad connectionless packet from %s:\n%s\n", Net_NetaddrToString(net_from), s); }
/* * @brief A connection-less packet has four leading 0xff bytes to distinguish * it from a game channel. Clients that are in the game can still send these, * and they will be handled here. */ static void Sv_ConnectionlessPacket(void) { Net_BeginReading(&net_message); Net_ReadLong(&net_message); // skip the -1 marker const char *s = Net_ReadStringLine(&net_message); Cmd_TokenizeString(s); const char *c = Cmd_Argv(0); const char *a = Net_NetaddrToString(&net_from); Com_Debug("Packet from %s: %s\n", a, c); if (!g_strcmp0(c, "ping")) Sv_Ping_f(); else if (!g_strcmp0(c, "ack")) Sv_Ack_f(); else if (!g_strcmp0(c, "status")) Sv_Status_f(); else if (!g_strcmp0(c, "info")) Sv_Info_f(); else if (!g_strcmp0(c, "get_challenge")) Sv_GetChallenge_f(); else if (!g_strcmp0(c, "connect")) Sv_Connect_f(); else if (!g_strcmp0(c, "rcon")) Sv_Rcon_f(); else Com_Print("Bad connectionless packet from %s:\n%s\n", a, s); }
/** * @brief */ void Cl_Ping_f(void) { net_addr_t addr; cl_server_info_t *server; if (Cmd_Argc() != 2) { Com_Print("Usage: %s <address>\n", Cmd_Argv(0)); return; } server = NULL; if (!Net_StringToNetaddr(Cmd_Argv(1), &addr)) { Com_Print("Invalid address\n"); return; } if (!addr.port) { // use default addr.port = (uint16_t) htons(PORT_SERVER); } server = Cl_ServerForNetaddr(&addr); if (!server) { // add it server = Cl_AddServer(&addr); server->source = SERVER_SOURCE_USER; } server->ping_time = quetoo.ticks; server->ping = 999; Com_Print("Pinging %s\n", Net_NetaddrToString(&server->addr)); Netchan_OutOfBandPrint(NS_UDP_CLIENT, &server->addr, "info %i", PROTOCOL_MAJOR); }
/* * @brief */ void Net_SendPacket(net_src_t source, size_t size, void *data, net_addr_t to) { struct sockaddr_in to_addr; int32_t sock, ret; if (to.type == NA_LOCAL) { Net_SendLocalPacket(source, size, data); return; } if (to.type == NA_IP_BROADCAST) { sock = ip_sockets[source]; if (!sock) return; } else if (to.type == NA_IP) { sock = ip_sockets[source]; if (!sock) return; } else { Com_Error(ERR_DROP, "Bad address type\n"); return; } Net_NetAddrToSockaddr(&to, &to_addr); ret = sendto(sock, data, size, 0, (struct sockaddr *) &to_addr, sizeof(to_addr)); if (ret == -1) Com_Warn("%s to %s.\n", Net_ErrorString(), Net_NetaddrToString(to)); }
/* * @brief Sends heartbeat messages to master servers every 300s. */ void Sv_HeartbeatMasters(void) { const char *string; int32_t i; if (!dedicated->value) return; // only dedicated servers report to masters if (!sv_public->value) return; // a private dedicated game if (!svs.initialized) // we're not up yet return; if (svs.next_heartbeat > quetoo.time) return; // not time to send yet svs.next_heartbeat = quetoo.time + HEARTBEAT_SECONDS * 1000; // send the same string that we would give for a status command string = Sv_StatusString(); // send to each master server for (i = 0; i < MAX_MASTERS; i++) { if (svs.masters[i].port) { Com_Print("Sending heartbeat to %s\n", Net_NetaddrToString(&svs.masters[i])); Netchan_OutOfBandPrint(NS_UDP_SERVER, &svs.masters[i], "heartbeat\n%s", string); } } }
/* * Cl_Ping_f */ void Cl_Ping_f(void) { net_addr_t addr; cl_server_info_t *server; if (Cmd_Argc() != 2) { Com_Print("Usage: %s <address>\n", Cmd_Argv(0)); return; } server = NULL; if (!Net_StringToNetaddr(Cmd_Argv(1), &addr)) { Com_Print("Invalid address\n"); return; } if (!addr.port) // use default addr.port = (unsigned short) BigShort(PORT_SERVER); server = Cl_ServerForNetaddr(&addr); if (!server) { // add it server = Cl_AddServer(&addr); server->source = SERVER_SOURCE_USER; } server->ping_time = cls.real_time; server->ping = 0; Com_Print("Pinging %s\n", Net_NetaddrToString(server->addr)); Netchan_OutOfBandPrint(NS_CLIENT, server->addr, "info %i", PROTOCOL); }
/** * @brief */ static cl_server_info_t *Cl_AddServer(const net_addr_t *addr) { cl_server_info_t *s; s = (cl_server_info_t *) Mem_TagMalloc(sizeof(*s), MEM_TAG_CLIENT); s->addr = *addr; g_strlcpy(s->hostname, Net_NetaddrToString(&s->addr), sizeof(s->hostname)); cls.servers = g_list_prepend(cls.servers, s); return s; }
/* * @brief */ _Bool Net_GetPacket(net_src_t source, net_addr_t *from, size_buf_t *message) { ssize_t ret; int32_t err; struct sockaddr_in from_addr; socklen_t from_len; char *s; if (Net_GetLocalPacket(source, from, message)) return true; if (!ip_sockets[source]) return false; from_len = sizeof(from_addr); ret = recvfrom(ip_sockets[source], (void *) message->data, message->max_size, 0, (struct sockaddr *) &from_addr, &from_len); Net_SockaddrToNetaddr(&from_addr, from); if (ret == -1) { err = Net_GetError(); if (err == EWOULDBLOCK || err == ECONNREFUSED) return false; // not terribly abnormal s = source == NS_SERVER ? "server" : "client"; Com_Warn("%s: %s from %s\n", s, Net_ErrorString(), Net_NetaddrToString(*from)); return false; } if (ret == ((ssize_t) message->max_size)) { Com_Warn("Oversized packet from %s\n", Net_NetaddrToString(*from)); return false; } message->size = (uint32_t) ret; return true; }
/* * Cl_AddServer */ static cl_server_info_t *Cl_AddServer(const net_addr_t *addr) { cl_server_info_t *s; s = (cl_server_info_t *) Z_Malloc(sizeof(*s)); s->next = cls.servers; cls.servers = s; s->addr = *addr; strcpy(s->hostname, Net_NetaddrToString(s->addr)); return s; }
/** * @brief */ void Cl_Servers_List_f(void) { char string[256]; GList *e = cls.servers; while (e) { const cl_server_info_t *s = (cl_server_info_t *) e->data; g_snprintf(string, sizeof(string), "%-40.40s %-20.20s %-16.16s %-24.24s %02d/%02d %5dms", s->hostname, Net_NetaddrToString(&s->addr), s->name, s->gameplay, s->clients, s->max_clients, s->ping); Com_Print("%s\n", string); e = e->next; } }
/* * @brief */ static void Sv_ReadPackets(void) { while (Net_ReceiveDatagram(NS_UDP_SERVER, &net_from, &net_message)) { // check for connectionless packet (0xffffffff) first if (*(uint32_t *) net_message.data == 0xffffffff) { Sv_ConnectionlessPacket(); continue; } // read the qport out of the message so we can fix up // stupid address translating routers Net_BeginReading(&net_message); Net_ReadLong(&net_message); // sequence number Net_ReadLong(&net_message); // sequence number const byte qport = Net_ReadByte(&net_message) & 0xff; // check for packets from connected clients sv_client_t * cl = svs.clients; for (int32_t i = 0; i < sv_max_clients->integer; i++, cl++) { if (cl->state == SV_CLIENT_FREE) continue; if (!Net_CompareClientNetaddr(&net_from, &cl->net_chan.remote_address)) continue; if (cl->net_chan.qport != qport) continue; if (cl->net_chan.remote_address.port != net_from.port) { cl->net_chan.remote_address.port = net_from.port; Com_Warn("Fixed translated port for %s\n", Net_NetaddrToString(&net_from)); } // this is a valid, sequenced packet, so process it if (Netchan_Process(&cl->net_chan, &net_message)) { cl->last_message = quetoo.time; // nudge timeout Sv_ParseClientMessage(cl); } // we've processed the packet for the correct client, so break break; } } }
/* * @brief Informs master servers that this server is halting. */ void Sv_ShutdownMasters(void) { int32_t i; if (!dedicated->value) return; // only dedicated servers send heartbeats if (!sv_public->value) return; // a private dedicated game // send to group master for (i = 0; i < MAX_MASTERS; i++) { if (svs.masters[i].port) { Com_Print("Sending shutdown to %s\n", Net_NetaddrToString(&svs.masters[i])); Netchan_OutOfBandPrint(NS_UDP_SERVER, &svs.masters[i], "shutdown"); } } }
/* * @brief Re-send a connect message if the last one has timed out. */ static void Cl_CheckForResend(void) { // if the local server is running and we aren't then connect if (Com_WasInit(QUETOO_SERVER) && g_strcmp0(cls.server_name, "localhost")) { if (cls.state > CL_DISCONNECTED) { Cl_Disconnect(); } g_strlcpy(cls.server_name, "localhost", sizeof(cls.server_name)); cls.state = CL_CONNECTING; cls.connect_time = 0; } // re-send if we haven't received a reply yet if (cls.state != CL_CONNECTING) return; // don't flood connection packets if (cls.connect_time && (quetoo.time - cls.connect_time < 1000)) return; net_addr_t addr; if (!Net_StringToNetaddr(cls.server_name, &addr)) { Com_Print("Bad server address\n"); cls.state = CL_DISCONNECTED; return; } if (addr.port == 0) addr.port = htons(PORT_SERVER); cls.connect_time = quetoo.time; // for retransmit requests const char *s = Net_NetaddrToString(&addr); if (g_strcmp0(cls.server_name, s)) { Com_Print("Connecting to %s (%s)...\n", cls.server_name, s); } else { Com_Print("Connecting to %s...\n", cls.server_name); } Netchan_OutOfBandPrint(NS_UDP_CLIENT, &addr, "get_challenge\n"); }
/** * @brief */ void Cl_ParseServerInfo(void) { char info[MAX_MSG_SIZE]; cl_server_info_t *server = Cl_ServerForNetaddr(&net_from); if (!server) { // unknown server, assumed response to broadcast server = Cl_AddServer(&net_from); server->source = SERVER_SOURCE_BCAST; server->ping_time = cls.broadcast_time; } // try to parse the info string g_strlcpy(info, Net_ReadString(&net_message), sizeof(info)); if (sscanf(info, "%63c\\%31c\\%31c\\%hu\\%hu", server->hostname, server->name, server->gameplay, &server->clients, &server->max_clients) != 5) { Com_Debug(DEBUG_CLIENT, "Failed to parse info \"%s\" for %s\n", info, Net_NetaddrToString(&server->addr)); server->hostname[0] = '\0'; server->name[0] = '\0'; server->gameplay[0] = '\0'; server->clients = 0; server->max_clients = 0; return; } g_strchomp(server->hostname); g_strchomp(server->name); g_strchomp(server->gameplay); server->hostname[sizeof(server->hostname) - 1] = '\0'; server->name[sizeof(server->name) - 1] = '\0'; server->gameplay[sizeof(server->name) - 1] = '\0'; server->ping = Clamp(quetoo.ticks - server->ping_time, 1u, 999u); Ui_UpdateBindings(); }
/* * Cl_ParseStatusMessage */ void Cl_ParseStatusMessage(void) { extern void Ui_NewServer(void); cl_server_info_t *server; char info[MAX_MSG_SIZE]; server = Cl_ServerForNetaddr(&net_from); if (!server) { // unknown server, assumed response to broadcast server = Cl_AddServer(&net_from); server->source = SERVER_SOURCE_BCAST; server->ping_time = cls.broadcast_time; } // try to parse the info string strncpy(info, Msg_ReadString(&net_message), sizeof(info) - 1); info[sizeof(info) - 1] = '\0'; if (sscanf(info, "%63c\\%31c\\%31c\\%hu\\%hu", server->hostname, server->name, server->gameplay, &server->clients, &server->max_clients) != 5) { strcpy(server->hostname, Net_NetaddrToString(server->addr)); server->name[0] = '\0'; server->gameplay[0] = '\0'; server->clients = 0; server->max_clients = 0; } server->hostname[63] = '\0'; server->name[31] = '\0'; server->gameplay[31] = '\0'; server->ping = cls.real_time - server->ping_time; if (server->ping > 1000) // clamp the ping server->ping = 999; Ui_NewServer(); }
/* * @brief A client issued an rcon command. Shift down the remaining args and * redirect all output to the invoking client. */ static void Sv_Rcon_f(void) { const _Bool auth = Sv_RconAuthenticate(); const char *addr = Net_NetaddrToString(&net_from); // first print to the server console if (auth) Com_Print("Rcon from %s:\n%s\n", addr, net_message.data + 4); else Com_Print("Bad rcon from %s:\n%s\n", addr, net_message.data + 4); // then redirect the remaining output back to the client console_t rcon = { .Append = Sv_Rcon_Print }; sv_rcon_buffer[0] = '\0'; Con_AddConsole(&rcon); if (auth) { char cmd[MAX_STRING_CHARS]; cmd[0] = '\0'; for (int32_t i = 2; i < Cmd_Argc(); i++) { g_strlcat(cmd, Cmd_Argv(i), sizeof(cmd)); g_strlcat(cmd, " ", sizeof(cmd)); } Cmd_ExecuteString(cmd); } else { Com_Print("Bad rcon_password\n"); } Netchan_OutOfBandPrint(NS_UDP_SERVER, &net_from, "print\n%s", sv_rcon_buffer); Con_RemoveConsole(&rcon); }
/* * Sv_HeartbeatMasters * * Sends heartbeat messages to master servers every 300s. */ static void Sv_HeartbeatMasters(void) { const char *string; int i; if (!dedicated->value) return; // only dedicated servers report to masters if (!sv_public->value) return; // a private dedicated game if (!svs.initialized) // we're not up yet return; if (svs.last_heartbeat > svs.real_time) // catch wraps svs.last_heartbeat = svs.real_time; if (svs.last_heartbeat) { // if we've sent one, wait a while if (svs.real_time - svs.last_heartbeat < HEARTBEAT_SECONDS * 1000) return; // not time to send yet } svs.last_heartbeat = svs.real_time; // send the same string that we would give for a status command string = Sv_StatusString(); // send to each master server for (i = 0; i < MAX_MASTERS; i++) { if (svs.masters[i].port) { Com_Print("Sending heartbeat to %s\n", Net_NetaddrToString(svs.masters[i])); Netchan_OutOfBandPrint(NS_SERVER, svs.masters[i], "heartbeat\n%s", string); } } }
/* * @brief Tries to send an unreliable message to a connection, and handles the * transmission / retransmission of the reliable messages. * * A 0 size will still generate a packet and deal with the reliable messages. */ void Netchan_Transmit(net_chan_t *chan, byte *data, size_t len) { mem_buf_t send; byte send_buffer[MAX_MSG_SIZE]; // check for message overflow if (chan->message.overflowed) { chan->fatal_error = true; Com_Print("%s:Outgoing message overflow\n", Net_NetaddrToString(&chan->remote_address)); return; } const _Bool send_reliable = Netchan_NeedReliable(chan); if (!chan->reliable_size && chan->message.size) { memcpy(chan->reliable_buffer, chan->message_buffer, chan->message.size); chan->reliable_size = chan->message.size; chan->message.size = 0; chan->reliable_sequence ^= 1; } // write the packet header Mem_InitBuffer(&send, send_buffer, sizeof(send_buffer)); const uint32_t w1 = (chan->outgoing_sequence & ~(1 << 31)) | (send_reliable << 31); const uint32_t w2 = (chan->incoming_sequence & ~(1 << 31)) | (chan->incoming_reliable_sequence << 31); chan->outgoing_sequence++; chan->last_sent = quake2world.time; Net_WriteLong(&send, w1); Net_WriteLong(&send, w2); // send the qport if we are a client if (chan->source == NS_UDP_CLIENT) Net_WriteByte(&send, chan->qport); // copy the reliable message to the packet first if (send_reliable) { Mem_WriteBuffer(&send, chan->reliable_buffer, chan->reliable_size); chan->last_reliable_sequence = chan->outgoing_sequence; } // add the unreliable part if space is available if (send.max_size - send.size >= len) Mem_WriteBuffer(&send, data, len); else Com_Warn("Netchan_Transmit: dumped unreliable\n"); // send the datagram Net_SendDatagram(chan->source, &chan->remote_address, send.data, send.size); if (net_showpackets->value) { if (send_reliable) Com_Print("Send %u bytes: s=%i reliable=%i ack=%i rack=%i\n", (uint32_t) send.size, chan->outgoing_sequence - 1, chan->reliable_sequence, chan->incoming_sequence, chan->incoming_reliable_sequence); else Com_Print("Send %u bytes : s=%i ack=%i rack=%i\n", (uint32_t) send.size, chan->outgoing_sequence - 1, chan->incoming_sequence, chan->incoming_reliable_sequence); } }
/* * @brief A convenience function for printing out client addresses. */ const char *Sv_NetaddrToString(const sv_client_t *cl) { return Net_NetaddrToString(&cl->net_chan.remote_address); }
/* * @brief A connection request that did not come from the master. */ static void Svc_Connect(void) { char user_info[MAX_USER_INFO_STRING]; sv_client_t *cl, *client; int32_t i; Com_Debug("Svc_Connect()\n"); net_addr_t *addr = &net_from; const int32_t version = strtol(Cmd_Argv(1), NULL, 0); // resolve protocol if (version != PROTOCOL) { Netchan_OutOfBandPrint(NS_UDP_SERVER, addr, "print\nServer is version %d.\n", PROTOCOL); return; } const uint8_t qport = strtoul(Cmd_Argv(2), NULL, 0); const uint32_t challenge = strtoul(Cmd_Argv(3), NULL, 0); // copy user_info, leave room for ip stuffing g_strlcpy(user_info, Cmd_Argv(4), sizeof(user_info) - 25); if (*user_info == '\0') { // catch empty user_info Com_Print("Empty user_info from %s\n", Net_NetaddrToString(addr)); Netchan_OutOfBandPrint(NS_UDP_SERVER, addr, "print\nConnection refused\n"); return; } if (strchr(user_info, '\xFF')) { // catch end of message in string exploit Com_Print("Illegal user_info contained xFF from %s\n", Net_NetaddrToString(addr)); Netchan_OutOfBandPrint(NS_UDP_SERVER, addr, "print\nConnection refused\n"); return; } if (strlen(GetUserInfo(user_info, "ip"))) { // catch spoofed ips Com_Print("Illegal user_info contained ip from %s\n", Net_NetaddrToString(addr)); Netchan_OutOfBandPrint(NS_UDP_SERVER, addr, "print\nConnection refused\n"); return; } if (!ValidateUserInfo(user_info)) { // catch otherwise invalid user_info Com_Print("Invalid user_info from %s\n", Net_NetaddrToString(addr)); Netchan_OutOfBandPrint(NS_UDP_SERVER, addr, "print\nConnection refused\n"); return; } // force the ip so the game can filter on it SetUserInfo(user_info, "ip", Net_NetaddrToString(addr)); // enforce a valid challenge to avoid denial of service attack for (i = 0; i < MAX_CHALLENGES; i++) { if (Net_CompareClientNetaddr(addr, &svs.challenges[i].addr)) { if (challenge == svs.challenges[i].challenge) { svs.challenges[i].challenge = 0; break; // good } Netchan_OutOfBandPrint(NS_UDP_SERVER, addr, "print\nBad challenge\n"); return; } } if (i == MAX_CHALLENGES) { Netchan_OutOfBandPrint(NS_UDP_SERVER, addr, "print\nNo challenge for address\n"); return; } // resolve the client slot client = NULL; // first check for an ungraceful reconnect (client crashed, perhaps) for (i = 0, cl = svs.clients; i < sv_max_clients->integer; i++, cl++) { const net_chan_t *ch = &cl->net_chan; if (cl->state == SV_CLIENT_FREE) // not in use, not interested continue; // the base address and either the qport or real port must match if (Net_CompareClientNetaddr(addr, &ch->remote_address)) { if (addr->port == ch->remote_address.port || qport == ch->qport) { client = cl; break; } } } // otherwise, treat as a fresh connect to a new slot if (!client) { for (i = 0, cl = svs.clients; i < sv_max_clients->integer; i++, cl++) { if (cl->state == SV_CLIENT_FREE && !cl->edict->ai) { // we have a free one client = cl; break; } } } // no soup for you, next!! if (!client) { Netchan_OutOfBandPrint(NS_UDP_SERVER, addr, "print\nServer is full\n"); Com_Debug("Rejected a connection\n"); return; } // give the game a chance to reject this connection or modify the user_info if (!(svs.game->ClientConnect(client->edict, user_info))) { const char *rejmsg = GetUserInfo(user_info, "rejmsg"); if (strlen(rejmsg)) { Netchan_OutOfBandPrint(NS_UDP_SERVER, addr, "print\n%s\nConnection refused\n", rejmsg); } else { Netchan_OutOfBandPrint(NS_UDP_SERVER, addr, "print\nConnection refused\n"); } Com_Debug("Game rejected a connection\n"); return; } // parse some info from the info strings g_strlcpy(client->user_info, user_info, sizeof(client->user_info)); Sv_UserInfoChanged(client); // send the connect packet to the client Netchan_OutOfBandPrint(NS_UDP_SERVER, addr, "client_connect %s", sv_download_url->string); Netchan_Setup(NS_UDP_SERVER, &client->net_chan, addr, qport); Mem_InitBuffer(&client->datagram.buffer, client->datagram.data, sizeof(client->datagram.data)); client->datagram.buffer.allow_overflow = true; client->last_message = svs.real_time; // don't timeout client->state = SV_CLIENT_CONNECTED; }
/* * @brief */ static void Svc_Ack(void) { Com_Print("Ping acknowledge from %s\n", Net_NetaddrToString(&net_from)); }
/* * @brief Called when the current net_message is from remote_address * modifies net_message so that it points to the packet payload */ _Bool Netchan_Process(net_chan_t *chan, mem_buf_t *msg) { uint32_t sequence, sequence_ack; uint32_t reliable_ack, reliable_message; // get sequence numbers Net_BeginReading(msg); sequence = Net_ReadLong(msg); sequence_ack = Net_ReadLong(msg); // read the qport if we are a server if (chan->source == NS_UDP_SERVER) Net_ReadByte(msg); reliable_message = sequence >> 31; reliable_ack = sequence_ack >> 31; sequence &= ~(1 << 31); sequence_ack &= ~(1 << 31); if (net_showpackets->value) { if (reliable_message) Com_Print("Recv %u bytes: s=%i reliable=%i ack=%i rack=%i\n", (uint32_t) msg->size, sequence, chan->incoming_reliable_sequence ^ 1, sequence_ack, reliable_ack); else Com_Print("Recv %u bytes : s=%i ack=%i rack=%i\n", (uint32_t) msg->size, sequence, sequence_ack, reliable_ack); } // discard stale or duplicated packets if (sequence <= chan->incoming_sequence) { if (net_showdrop->value) Com_Print("%s:Out of order packet %i at %i\n", Net_NetaddrToString(&chan->remote_address), sequence, chan->incoming_sequence); return false; } // dropped packets don't keep the message from being used chan->dropped = sequence - (chan->incoming_sequence + 1); if (chan->dropped > 0) { if (net_showdrop->value) Com_Print("%s:Dropped %i packets at %i\n", Net_NetaddrToString(&chan->remote_address), chan->dropped, sequence); } // if the current outgoing reliable message has been acknowledged // clear the buffer to make way for the next if (reliable_ack == chan->reliable_sequence) chan->reliable_size = 0; // it has been received // if this message contains a reliable message, bump incoming_reliable_sequence chan->incoming_sequence = sequence; chan->incoming_acknowledged = sequence_ack; chan->incoming_reliable_acknowledged = reliable_ack; if (reliable_message) { chan->incoming_reliable_sequence ^= 1; } // the message can now be read from the current message pointer chan->last_received = quake2world.time; return true; }
/* * @brief Callback which connects to the server specified in data. */ static TW_CALL void Ui_Servers_Connect(void *data) { cl_server_info_t *s = (cl_server_info_t *) data; Cbuf_AddText(va("connect %s\n", Net_NetaddrToString(&s->addr))); }