summaryrefslogtreecommitdiff
path: root/src/net_query.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/net_query.c')
-rw-r--r--src/net_query.c691
1 files changed, 550 insertions, 141 deletions
diff --git a/src/net_query.c b/src/net_query.c
index b50b4292..ae56dea6 100644
--- a/src/net_query.c
+++ b/src/net_query.c
@@ -37,50 +37,166 @@
#include "net_structrw.h"
#include "net_sdl.h"
-typedef struct
+// DNS address of the Internet master server.
+
+#define MASTER_SERVER_ADDRESS "master.chocolate-doom.org:2342"
+
+// Time to wait for a response before declaring a timeout.
+
+#define QUERY_TIMEOUT_SECS 2
+
+// Number of query attempts to make before giving up on a server.
+
+#define QUERY_MAX_ATTEMPTS 3
+
+typedef enum
{
+ QUERY_TARGET_SERVER, // Normal server target.
+ QUERY_TARGET_MASTER, // The master server.
+ QUERY_TARGET_BROADCAST // Send a broadcast query
+} query_target_type_t;
+
+typedef enum
+{
+ QUERY_TARGET_QUEUED, // Query not yet sent
+ QUERY_TARGET_QUERIED, // Query sent, waiting response
+ QUERY_TARGET_RESPONDED, // Response received
+ QUERY_TARGET_NO_RESPONSE
+} query_target_state_t;
+
+typedef struct
+{
+ query_target_type_t type;
+ query_target_state_t state;
net_addr_t *addr;
net_querydata_t data;
-} queryresponse_t;
+ unsigned int ping_time;
+ unsigned int query_time;
+ unsigned int query_attempts;
+ boolean printed;
+} query_target_t;
+
+// Transmit a query packet
+
+static boolean registered_with_master = false;
static net_context_t *query_context;
-static queryresponse_t *responders;
-static int num_responses;
+static query_target_t *targets;
+static int num_targets;
+
+static boolean query_loop_running = false;
+static boolean printed_header = false;
+
+// Resolve the master server address.
+
+net_addr_t *NET_Query_ResolveMaster(net_context_t *context)
+{
+ net_addr_t *addr;
+
+ addr = NET_ResolveAddress(context, MASTER_SERVER_ADDRESS);
+
+ if (addr == NULL)
+ {
+ fprintf(stderr, "Warning: Failed to resolve address "
+ "for master server: %s\n", MASTER_SERVER_ADDRESS);
+ }
+
+ return addr;
+}
-// Add a new address to the list of hosts that has responded
+// Send a registration packet to the master server to register
+// ourselves with the global list.
-static queryresponse_t *AddResponder(net_addr_t *addr,
- net_querydata_t *data)
+void NET_Query_AddToMaster(net_addr_t *master_addr)
{
- queryresponse_t *response;
+ net_packet_t *packet;
+
+ packet = NET_NewPacket(10);
+ NET_WriteInt16(packet, NET_MASTER_PACKET_TYPE_ADD);
+ NET_SendPacket(master_addr, packet);
+ NET_FreePacket(packet);
+}
+
+// Process a packet received from the master server.
+
+void NET_Query_MasterResponse(net_packet_t *packet)
+{
+ unsigned int packet_type;
+ unsigned int result;
+
+ if (!NET_ReadInt16(packet, &packet_type)
+ || !NET_ReadInt16(packet, &result))
+ {
+ return;
+ }
+
+ if (packet_type == NET_MASTER_PACKET_TYPE_ADD_RESPONSE)
+ {
+ if (result != 0)
+ {
+ // Only show the message once.
+
+ if (!registered_with_master)
+ {
+ printf("Registered with master server at %s\n",
+ MASTER_SERVER_ADDRESS);
+ registered_with_master = true;
+ }
+ }
+ else
+ {
+ // Always show rejections.
- responders = realloc(responders,
- sizeof(queryresponse_t) * (num_responses + 1));
+ printf("Failed to register with master server at %s\n",
+ MASTER_SERVER_ADDRESS);
+ }
+ }
+}
- response = &responders[num_responses];
- response->addr = addr;
- response->data = *data;
- ++num_responses;
+// Send a query to the master server.
+
+static void NET_Query_SendMasterQuery(net_addr_t *addr)
+{
+ net_packet_t *packet;
- return response;
+ packet = NET_NewPacket(10);
+ NET_WriteInt16(packet, NET_MASTER_PACKET_TYPE_QUERY);
+ NET_SendPacket(addr, packet);
+ NET_FreePacket(packet);
}
-// Returns true if the reply is from a host that has not previously
-// responded.
+// Given the specified address, find the target associated. If no
+// target is found, and 'create' is true, a new target is created.
-static boolean CheckResponder(net_addr_t *addr)
+static query_target_t *GetTargetForAddr(net_addr_t *addr, boolean create)
{
+ query_target_t *target;
int i;
- for (i=0; i<num_responses; ++i)
+ for (i=0; i<num_targets; ++i)
{
- if (responders[i].addr == addr)
+ if (targets[i].addr == addr)
{
- return false;
+ return &targets[i];
}
}
- return true;
+ if (!create)
+ {
+ return NULL;
+ }
+
+ targets = realloc(targets, sizeof(query_target_t) * (num_targets + 1));
+
+ target = &targets[num_targets];
+ target->type = QUERY_TARGET_SERVER;
+ target->state = QUERY_TARGET_QUEUED;
+ target->printed = false;
+ target->query_attempts = 0;
+ target->addr = addr;
+ ++num_targets;
+
+ return target;
}
// Transmit a query packet
@@ -104,166 +220,254 @@ static void NET_Query_SendQuery(net_addr_t *addr)
NET_FreePacket(request);
}
-static void formatted_printf(int wide, char *s, ...)
+static void NET_Query_ParseResponse(net_addr_t *addr, net_packet_t *packet,
+ net_query_callback_t callback,
+ void *user_data)
{
- va_list args;
- int i;
-
- va_start(args, s);
- i = vprintf(s, args);
- va_end(args);
+ unsigned int packet_type;
+ net_querydata_t querydata;
+ query_target_t *target;
+
+ // Read the header
- while (i < wide)
+ if (!NET_ReadInt16(packet, &packet_type)
+ || packet_type != NET_PACKET_TYPE_QUERY_RESPONSE)
{
- putchar(' ');
- ++i;
- }
-}
+ return;
+ }
-static char *GameDescription(GameMode_t mode, GameMission_t mission)
-{
- switch (mode)
+ // Read query data
+
+ if (!NET_ReadQueryData(packet, &querydata))
{
- case shareware:
- return "shareware";
- case registered:
- return "registered";
- case retail:
- return "ultimate";
- case commercial:
- if (mission == doom2)
- return "doom2";
- else if (mission == pack_tnt)
- return "tnt";
- else if (mission == pack_plut)
- return "plutonia";
- default:
- return "unknown";
+ return;
}
-}
-static void PrintHeader(void)
-{
- int i;
+ // Find the target that responded, or potentially add a new target
+ // if it was not already known (for LAN broadcast search)
- formatted_printf(18, "Address");
- formatted_printf(8, "Players");
- puts("Description");
+ target = GetTargetForAddr(addr, true);
- for (i=0; i<70; ++i)
- putchar('=');
- putchar('\n');
+ if (target->state != QUERY_TARGET_RESPONDED)
+ {
+ target->state = QUERY_TARGET_RESPONDED;
+ memcpy(&target->data, &querydata, sizeof(net_querydata_t));
+
+ // Calculate RTT.
+
+ target->ping_time = I_GetTimeMS() - target->query_time;
+
+ // Invoke callback to signal that we have a new address.
+
+ callback(addr, &target->data, target->ping_time, user_data);
+ }
}
-static void PrintResponse(queryresponse_t *response)
+// Parse a response packet from the master server.
+
+static void NET_Query_ParseMasterResponse(net_addr_t *master_addr,
+ net_packet_t *packet)
{
- formatted_printf(18, "%s: ", NET_AddrToString(response->addr));
- formatted_printf(8, "%i/%i", response->data.num_players,
- response->data.max_players);
+ unsigned int packet_type;
+ query_target_t *target;
+ char *addr_str;
+ net_addr_t *addr;
- if (response->data.gamemode != indetermined)
+ // Read the header. We are only interested in query responses.
+
+ if (!NET_ReadInt16(packet, &packet_type)
+ || packet_type != NET_MASTER_PACKET_TYPE_QUERY_RESPONSE)
{
- printf("(%s) ", GameDescription(response->data.gamemode,
- response->data.gamemission));
+ return;
}
- if (response->data.server_state)
+ // Read a list of strings containing the addresses of servers
+ // that the master knows about.
+
+ for (;;)
{
- printf("(game running) ");
+ addr_str = NET_ReadString(packet);
+
+ if (addr_str == NULL)
+ {
+ break;
+ }
+
+ // Resolve address and add to targets list if it is not already
+ // there.
+
+ addr = NET_ResolveAddress(query_context, addr_str);
+
+ if (addr != NULL)
+ {
+ GetTargetForAddr(addr, true);
+ }
}
- NET_SafePuts(response->data.description);
+ // Mark the master as having responded.
+
+ target = GetTargetForAddr(master_addr, true);
+ target->state = QUERY_TARGET_RESPONDED;
}
-static void NET_Query_ParsePacket(net_addr_t *addr, net_packet_t *packet)
+static void NET_Query_ParsePacket(net_addr_t *addr, net_packet_t *packet,
+ net_query_callback_t callback,
+ void *user_data)
{
- unsigned int packet_type;
- net_querydata_t querydata;
- queryresponse_t *response;
+ query_target_t *target;
+
+ // This might be the master server responding.
- // Have we already received a packet from this host?
+ target = GetTargetForAddr(addr, false);
- if (!CheckResponder(addr))
+ if (target != NULL && target->type == QUERY_TARGET_MASTER)
{
- return;
+ NET_Query_ParseMasterResponse(addr, packet);
}
+ else
+ {
+ NET_Query_ParseResponse(addr, packet, callback, user_data);
+ }
+}
- // Read the header
+static void NET_Query_GetResponse(net_query_callback_t callback,
+ void *user_data)
+{
+ net_addr_t *addr;
+ net_packet_t *packet;
- if (!NET_ReadInt16(packet, &packet_type)
- || packet_type != NET_PACKET_TYPE_QUERY_RESPONSE)
+ if (NET_RecvPacket(query_context, &addr, &packet))
{
- return;
+ NET_Query_ParsePacket(addr, packet, callback, user_data);
+ NET_FreePacket(packet);
}
+}
- // Read query data
+// Find a target we have not yet queried and send a query.
- if (!NET_ReadQueryData(packet, &querydata))
+static void SendOneQuery(void)
+{
+ unsigned int now;
+ unsigned int i;
+
+ now = I_GetTimeMS();
+
+ for (i = 0; i < num_targets; ++i)
+ {
+ // Not queried yet?
+ // Or last query timed out without a response?
+
+ if (targets[i].state == QUERY_TARGET_QUEUED
+ || (targets[i].state == QUERY_TARGET_QUERIED
+ && now - targets[i].query_time > QUERY_TIMEOUT_SECS * 1000))
+ {
+ break;
+ }
+ }
+
+ if (i >= num_targets)
{
return;
}
- if (num_responses <= 0)
+ // Found a target to query. Send a query; how to do this depends on
+ // the target type.
+
+ switch (targets[i].type)
{
- // If this is the first response, print the table header
+ case QUERY_TARGET_SERVER:
+ NET_Query_SendQuery(targets[i].addr);
+ break;
- PrintHeader();
+ case QUERY_TARGET_BROADCAST:
+ NET_Query_SendQuery(NULL);
+ break;
+
+ case QUERY_TARGET_MASTER:
+ NET_Query_SendMasterQuery(targets[i].addr);
+ break;
}
- response = AddResponder(addr, &querydata);
+ //printf("Queried %s\n", NET_AddrToString(targets[i].addr));
+ targets[i].state = QUERY_TARGET_QUERIED;
+ targets[i].query_time = I_GetTimeMS();
+ ++targets[i].query_attempts;
+}
+
+// Time out servers that have been queried and not responded.
- PrintResponse(response);
+static void CheckTargetTimeouts(void)
+{
+ unsigned int i;
+ unsigned int now;
+
+ now = I_GetTimeMS();
+
+ for (i = 0; i < num_targets; ++i)
+ {
+ // We declare a target to be "no response" when we've sent
+ // multiple query packets to it (QUERY_MAX_ATTEMPTS) and
+ // received no response to any of them.
+
+ if (targets[i].state == QUERY_TARGET_QUERIED
+ && targets[i].query_attempts >= QUERY_MAX_ATTEMPTS
+ && now - targets[i].query_time > QUERY_TIMEOUT_SECS * 1000)
+ {
+ targets[i].state = QUERY_TARGET_NO_RESPONSE;
+ }
+ }
}
-static void NET_Query_GetResponse(void)
+// If all targets have responded or timed out, returns true.
+
+static boolean AllTargetsDone(void)
{
- net_addr_t *addr;
- net_packet_t *packet;
+ unsigned int i;
- if (NET_RecvPacket(query_context, &addr, &packet))
+ for (i = 0; i < num_targets; ++i)
{
- NET_Query_ParsePacket(addr, packet);
- NET_FreePacket(packet);
+ if (targets[i].state != QUERY_TARGET_RESPONDED
+ && targets[i].state != QUERY_TARGET_NO_RESPONSE)
+ {
+ return false;
+ }
}
+
+ return true;
}
-static net_addr_t *NET_Query_QueryLoop(net_addr_t *addr,
- boolean find_one)
+// Stop the query loop
+
+static void NET_Query_ExitLoop(void)
{
- int start_time;
- int last_send_time;
+ query_loop_running = false;
+}
+
+// Loop waiting for responses.
+// The specified callback is invoked when a new server responds.
- last_send_time = -1;
- start_time = I_GetTimeMS();
+static void NET_Query_QueryLoop(net_query_callback_t callback,
+ void *user_data)
+{
+ query_loop_running = true;
- while (I_GetTimeMS() < start_time + 5000)
+ while (query_loop_running && !AllTargetsDone())
{
- // Send a query once every second
+ // Send a query. This will only send a single query.
+ // Because of the delay below, this is therefore rate limited.
- if (last_send_time < 0 || I_GetTimeMS() > last_send_time + 1000)
- {
- NET_Query_SendQuery(addr);
- last_send_time = I_GetTimeMS();
- }
+ SendOneQuery();
// Check for a response
- NET_Query_GetResponse();
+ NET_Query_GetResponse(callback, user_data);
- // Found a response?
-
- if (find_one && num_responses > 0)
- break;
-
// Don't thrash the CPU
-
- I_Sleep(100);
- }
- if (num_responses > 0)
- return responders[0].addr;
- else
- return NULL;
+ I_Sleep(50);
+
+ CheckTargetTimeouts();
+ }
}
void NET_Query_Init(void)
@@ -272,51 +476,256 @@ void NET_Query_Init(void)
NET_AddModule(query_context, &net_sdl_module);
net_sdl_module.InitClient();
- responders = NULL;
- num_responses = 0;
+ targets = NULL;
+ num_targets = 0;
+
+ printed_header = false;
}
-void NET_QueryAddress(char *addr)
+// Callback that exits the query loop when the first server is found.
+
+static void NET_Query_ExitCallback(net_addr_t *addr, net_querydata_t *data,
+ unsigned int ping_time, void *user_data)
{
- net_addr_t *net_addr;
-
- NET_Query_Init();
+ NET_Query_ExitLoop();
+}
- net_addr = NET_ResolveAddress(query_context, addr);
+// Search the targets list and find a target that has responded.
+// If none have responded, returns NULL.
+
+static query_target_t *FindFirstResponder(void)
+{
+ unsigned int i;
- if (net_addr == NULL)
+ for (i = 0; i < num_targets; ++i)
{
- I_Error("NET_QueryAddress: Host '%s' not found!", addr);
+ if (targets[i].type == QUERY_TARGET_SERVER
+ && targets[i].state == QUERY_TARGET_RESPONDED)
+ {
+ return &targets[i];
+ }
}
- printf("\nQuerying '%s'...\n\n", addr);
+ return NULL;
+}
- if (!NET_Query_QueryLoop(net_addr, true))
+// Return a count of the number of responses.
+
+static int GetNumResponses(void)
+{
+ unsigned int i;
+ int result;
+
+ result = 0;
+
+ for (i = 0; i < num_targets; ++i)
{
- I_Error("No response from '%s'", addr);
+ if (targets[i].type == QUERY_TARGET_SERVER
+ && targets[i].state == QUERY_TARGET_RESPONDED)
+ {
+ ++result;
+ }
}
- exit(0);
+ return result;
+}
+
+void NET_QueryAddress(char *addr_str)
+{
+ net_addr_t *addr;
+ query_target_t *target;
+
+ NET_Query_Init();
+
+ addr = NET_ResolveAddress(query_context, addr_str);
+
+ if (addr == NULL)
+ {
+ I_Error("NET_QueryAddress: Host '%s' not found!", addr_str);
+ }
+
+ // Add the address to the list of targets.
+
+ target = GetTargetForAddr(addr, true);
+
+ printf("\nQuerying '%s'...\n", addr_str);
+
+ // Run query loop.
+
+ NET_Query_QueryLoop(NET_Query_ExitCallback, NULL);
+
+ // Check if the target responded.
+
+ if (target->state == QUERY_TARGET_RESPONDED)
+ {
+ NET_QueryPrintCallback(addr, &target->data, target->ping_time, NULL);
+ }
+ else
+ {
+ I_Error("No response from '%s'", addr_str);
+ }
}
net_addr_t *NET_FindLANServer(void)
{
+ query_target_t *target;
+ query_target_t *responder;
+
+ NET_Query_Init();
+
+ // Add a broadcast target to the list.
+
+ target = GetTargetForAddr(NULL, true);
+ target->type = QUERY_TARGET_BROADCAST;
+
+ // Run the query loop, and stop at the first target found.
+
+ NET_Query_QueryLoop(NET_Query_ExitCallback, NULL);
+
+ responder = FindFirstResponder();
+
+ if (responder != NULL)
+ {
+ return responder->addr;
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+int NET_LANQuery(net_query_callback_t callback, void *user_data)
+{
+ query_target_t *target;
+
NET_Query_Init();
- return NET_Query_QueryLoop(NULL, true);
+ // Add a broadcast target to the list.
+
+ target = GetTargetForAddr(NULL, true);
+ target->type = QUERY_TARGET_BROADCAST;
+
+ NET_Query_QueryLoop(callback, user_data);
+
+ return GetNumResponses();
}
-void NET_LANQuery(void)
+int NET_MasterQuery(net_query_callback_t callback, void *user_data)
{
+ net_addr_t *master;
+ query_target_t *target;
+
NET_Query_Init();
- printf("\nSearching for servers on local LAN ...\n\n");
+ // Resolve master address and add to targets list.
+
+ master = NET_Query_ResolveMaster(query_context);
+
+ if (master == NULL)
+ {
+ return 0;
+ }
+
+ target = GetTargetForAddr(master, true);
+ target->type = QUERY_TARGET_MASTER;
+
+ NET_Query_QueryLoop(callback, user_data);
+
+ // Check that we got a response from the master, and display
+ // a warning if we didn't.
+
+ if (target->state == QUERY_TARGET_NO_RESPONSE)
+ {
+ fprintf(stderr, "NET_MasterQuery: no response from master server.\n");
+ }
+
+ return GetNumResponses();
+}
+
+static void formatted_printf(int wide, char *s, ...)
+{
+ va_list args;
+ int i;
- if (!NET_Query_QueryLoop(NULL, false))
+ va_start(args, s);
+ i = vprintf(s, args);
+ va_end(args);
+
+ while (i < wide)
{
- I_Error("No servers found");
+ putchar(' ');
+ ++i;
+ }
+}
+
+static char *GameDescription(GameMode_t mode, GameMission_t mission)
+{
+ switch (mode)
+ {
+ case shareware:
+ return "shareware";
+ case registered:
+ return "registered";
+ case retail:
+ return "ultimate";
+ case commercial:
+ if (mission == doom2)
+ return "doom2";
+ else if (mission == pack_tnt)
+ return "tnt";
+ else if (mission == pack_plut)
+ return "plutonia";
+ default:
+ return "unknown";
+ }
+}
+
+static void PrintHeader(void)
+{
+ int i;
+
+ putchar('\n');
+ formatted_printf(5, "Ping");
+ formatted_printf(18, "Address");
+ formatted_printf(8, "Players");
+ puts("Description");
+
+ for (i=0; i<70; ++i)
+ putchar('=');
+ putchar('\n');
+}
+
+// Callback function that just prints information in a table.
+
+void NET_QueryPrintCallback(net_addr_t *addr,
+ net_querydata_t *data,
+ unsigned int ping_time,
+ void *user_data)
+{
+ // If this is the first server, print the header.
+
+ if (!printed_header)
+ {
+ PrintHeader();
+ printed_header = true;
+ }
+
+ formatted_printf(5, "%4i", ping_time);
+ formatted_printf(18, "%s: ", NET_AddrToString(addr));
+ formatted_printf(8, "%i/%i", data->num_players,
+ data->max_players);
+
+ if (data->gamemode != indetermined)
+ {
+ printf("(%s) ", GameDescription(data->gamemode,
+ data->gamemission));
+ }
+
+ if (data->server_state)
+ {
+ printf("(game running) ");
}
- exit(0);
+ NET_SafePuts(data->description);
}