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.
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
#include "quakedef.h"
#include "net_dgrm.h"
+#include "net_master.h"
+
+cvar_t cl_port = {CVAR_SAVE, "cl_port", "0"};
// these two macros are to make the code more readable
#define sfunc net_landrivers[sock->landriver]
{
unsigned int length;
unsigned int sequence;
- byte data[MAX_DATAGRAM];
+ qbyte data[MAX_DATAGRAM];
} packetBuffer;
-extern int m_return_state;
-extern int m_state;
-extern qboolean m_return_onerror;
-extern char m_return_reason[32];
-
//#ifdef DEBUG
char *StrAddr (struct qsockaddr *addr)
{
static char buf[34];
- byte *p = (byte *)addr;
+ qbyte *p = (qbyte *)addr;
int n;
for (n = 0; n < 16; n++)
{
char addrStr [32];
char maskStr [32];
- void (*print) (char *fmt, ...);
+ void (*print) (const char *fmt, ...);
if (cmd_source == src_command)
{
break;
case 2:
- if (Q_strcasecmp(Cmd_Argv(1), "off") == 0)
+ if (strcasecmp(Cmd_Argv(1), "off") == 0)
banAddr = 0x00000000;
else
banAddr = inet_addr(Cmd_Argv(1));
sock->canSend = false;
- if (sfunc.Write (sock->socket, (byte *)&packetBuffer, packetLen, &sock->addr) == -1)
+ if (sfunc.Write (sock->socket, (qbyte *)&packetBuffer, packetLen, &sock->addr) == -1)
return -1;
sock->lastSendTime = net_time;
sock->sendNext = false;
- if (sfunc.Write (sock->socket, (byte *)&packetBuffer, packetLen, &sock->addr) == -1)
+ if (sfunc.Write (sock->socket, (qbyte *)&packetBuffer, packetLen, &sock->addr) == -1)
return -1;
sock->lastSendTime = net_time;
sock->sendNext = false;
- if (sfunc.Write (sock->socket, (byte *)&packetBuffer, packetLen, &sock->addr) == -1)
+ if (sfunc.Write (sock->socket, (qbyte *)&packetBuffer, packetLen, &sock->addr) == -1)
return -1;
sock->lastSendTime = net_time;
packetBuffer.sequence = BigLong(sock->unreliableSendSequence++);
memcpy (packetBuffer.data, data->data, data->cursize);
- if (sfunc.Write (sock->socket, (byte *)&packetBuffer, packetLen, &sock->addr) == -1)
+ if (sfunc.Write (sock->socket, (qbyte *)&packetBuffer, packetLen, &sock->addr) == -1)
return -1;
packetsSent++;
ReSendMessage (sock);
while(1)
- {
- length = sfunc.Read (sock->socket, (byte *)&packetBuffer, NET_DATAGRAMSIZE, &readaddr);
-
-// if ((rand() & 255) > 220)
-// continue;
+ {
+ length = sfunc.Read (sock->socket, (qbyte *)&packetBuffer, NET_DATAGRAMSIZE, &readaddr);
if (length == 0)
break;
- if (length == -1)
+ if ((int)length == -1)
{
Con_Printf("Read error\n");
return -1;
if (sfunc.AddrCompare(&readaddr, &sock->addr) != 0)
{
-#ifdef DEBUG
Con_DPrintf("Forged packet received\n");
Con_DPrintf("Expected: %s\n", StrAddr (&sock->addr));
Con_DPrintf("Received: %s\n", StrAddr (&readaddr));
-#endif
continue;
}
{
packetBuffer.length = BigLong(NET_HEADERSIZE | NETFLAG_ACK);
packetBuffer.sequence = BigLong(sequence);
- sfunc.Write (sock->socket, (byte *)&packetBuffer, NET_HEADERSIZE, &readaddr);
+ sfunc.Write (sock->socket, (qbyte *)&packetBuffer, NET_HEADERSIZE, &readaddr);
if (sequence != sock->receiveSequence)
{
{
for (s = net_activeSockets; s; s = s->next)
PrintStats(s);
- for (s = net_freeSockets; s; s = s->next)
- PrintStats(s);
}
else
{
for (s = net_activeSockets; s; s = s->next)
- if (Q_strcasecmp(Cmd_Argv(1), s->address) == 0)
+ if (strcasecmp(Cmd_Argv(1), s->address) == 0)
break;
- if (s == NULL)
- for (s = net_freeSockets; s; s = s->next)
- if (Q_strcasecmp(Cmd_Argv(1), s->address) == 0)
- break;
if (s == NULL)
return;
PrintStats(s);
int colors;
int frags;
int connectTime;
- byte playerNumber;
+ qbyte playerNumber;
+ int c;
net_landriverlevel = testDriver;
while (1)
{
len = dfunc.Read (testSocket, net_message.data, net_message.maxsize, &clientaddr);
- if (len < sizeof(int))
+ if (len < (int)sizeof(int))
break;
net_message.cursize = len;
MSG_ReadLong();
if (control == -1)
break;
- if ((control & (~NETFLAG_LENGTH_MASK)) != NETFLAG_CTL)
+ if ((control & (~NETFLAG_LENGTH_MASK)) != (int)NETFLAG_CTL)
break;
if ((control & NETFLAG_LENGTH_MASK) != len)
break;
- if (MSG_ReadByte() != CCREP_PLAYER_INFO)
+ c = MSG_ReadByte();
+ if (c != CCREP_PLAYER_INFO)
Sys_Error("Unexpected repsonse to Player Info request\n");
playerNumber = MSG_ReadByte();
static void Test_f (void)
{
- char *host;
- int n;
- int max = MAX_SCOREBOARD;
+ const char *host;
+ int n, max = MAX_SCOREBOARD;
struct qsockaddr sendaddr;
if (testInProgress)
if (host && hostCacheCount)
{
for (n = 0; n < hostCacheCount; n++)
- if (Q_strcasecmp (host, hostcache[n].name) == 0)
+ if (strcasecmp (host, hostcache[n].name) == 0)
{
if (hostcache[n].driver != myDriverLevel)
continue;
struct qsockaddr clientaddr;
int control;
int len;
+ int c;
char name[256];
char value[256];
name[0] = 0;
len = dfunc.Read (test2Socket, net_message.data, net_message.maxsize, &clientaddr);
- if (len < sizeof(int))
+ if (len < (int)sizeof(int))
goto Reschedule;
net_message.cursize = len;
MSG_ReadLong();
if (control == -1)
goto Error;
- if ((control & (~NETFLAG_LENGTH_MASK)) != NETFLAG_CTL)
+ if ((control & (~NETFLAG_LENGTH_MASK)) != (int)NETFLAG_CTL)
goto Error;
if ((control & NETFLAG_LENGTH_MASK) != len)
goto Error;
- if (MSG_ReadByte() != CCREP_RULE_INFO)
+ c = MSG_ReadByte();
+ if (c != CCREP_RULE_INFO)
goto Error;
strcpy(name, MSG_ReadString());
static void Test2_f (void)
{
- char *host;
- int n;
+ const char *host;
+ int n;
struct qsockaddr sendaddr;
if (test2InProgress)
if (host && hostCacheCount)
{
for (n = 0; n < hostCacheCount; n++)
- if (Q_strcasecmp (host, hostcache[n].name) == 0)
+ if (strcasecmp (host, hostcache[n].name) == 0)
{
if (hostcache[n].driver != myDriverLevel)
continue;
myDriverLevel = net_driverlevel;
Cmd_AddCommand ("net_stats", NET_Stats_f);
+ Cvar_RegisterVariable (&cl_port);
if (COM_CheckParm("-nolan"))
return -1;
int command;
int control;
int ret;
+ int c;
acceptsock = dfunc.CheckNewConnections();
if (acceptsock == -1)
SZ_Clear(&net_message);
len = dfunc.Read (acceptsock, net_message.data, net_message.maxsize, &clientaddr);
- if (len < sizeof(int))
+ if (len < (int)sizeof(int))
return NULL;
net_message.cursize = len;
MSG_BeginReading ();
control = BigLong(*((int *)net_message.data));
MSG_ReadLong();
- if (control == -1)
+
+ // Messages starting by 0xFFFFFFFF are master server messages
+ if ((unsigned int)control == 0xFFFFFFFF)
+ {
+ int responsesize = Master_HandleMessage();
+ if (responsesize > 0)
+ {
+ dfunc.Write(acceptsock, net_message.data, responsesize, &clientaddr);
+ SZ_Clear(&net_message);
+ }
return NULL;
- if ((control & (~NETFLAG_LENGTH_MASK)) != NETFLAG_CTL)
+ }
+ if ((control & (~NETFLAG_LENGTH_MASK)) != (int)NETFLAG_CTL)
return NULL;
if ((control & NETFLAG_LENGTH_MASK) != len)
return NULL;
if (strcmp(MSG_ReadString(), "QUAKE") != 0)
return NULL;
+ Con_DPrintf("Datagram_CheckNewConnections: received CCREQ_SERVERINFO, replying.\n");
+
SZ_Clear(&net_message);
// save space for the header, filled in later
MSG_WriteLong(&net_message, 0);
int activeNumber;
int clientNumber;
client_t *client;
-
+
playerNumber = MSG_ReadByte();
activeNumber = -1;
for (clientNumber = 0, client = svs.clients; clientNumber < svs.maxclients; clientNumber++, client++)
MSG_WriteByte(&net_message, playerNumber);
MSG_WriteString(&net_message, client->name);
MSG_WriteLong(&net_message, client->colors);
- MSG_WriteLong(&net_message, (int)client->edict->v.frags);
+ MSG_WriteLong(&net_message, (int)client->edict->v->frags);
MSG_WriteLong(&net_message, (int)(net_time - client->netconnection->connecttime));
MSG_WriteString(&net_message, client->netconnection->address);
*((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
// find the search start location
prevCvarName = MSG_ReadString();
- if (*prevCvarName)
- {
- var = Cvar_FindVar (prevCvarName);
- if (!var)
- return NULL;
- var = var->next;
- }
- else
- var = cvar_vars;
-
- // search for the next server cvar
- while (var)
- {
- if (var->server)
- break;
- var = var->next;
- }
+ var = Cvar_FindVarAfter(prevCvarName, CVAR_NOTIFY);
// send the response
if (strcmp(MSG_ReadString(), "QUAKE") != 0)
return NULL;
- if (MSG_ReadByte() != NET_PROTOCOL_VERSION)
+ c = MSG_ReadByte();
+ if (c != NET_PROTOCOL_VERSION)
{
SZ_Clear(&net_message);
// save space for the header, filled in later
ret = dfunc.AddrCompare(&clientaddr, &s->addr);
if (ret >= 0)
{
- // is this a duplicate connection reqeust?
+ // is this a duplicate connection request?
if (ret == 0 && net_time - s->connecttime < 2.0)
{
// yes, so send a duplicate reply
dfunc.GetSocketAddr(s->socket, &newaddr);
MSG_WriteLong(&net_message, dfunc.GetSocketPort(&newaddr));
*((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
+ // LordHavoc: send from s->socket instead of acceptsock, this
+ // way routers usually identify the connection correctly
+ // (thanks to faded for provoking me to recite a lengthy
+ // explanation of NAT nightmares, and realize this easy
+ // workaround for quake)
+ dfunc.Write (s->socket, net_message.data, net_message.cursize, &clientaddr);
+ // LordHavoc: also send from acceptsock, for good measure
dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr);
SZ_Clear(&net_message);
return NULL;
return NULL;
}
- // everything is allocated, just fill in the details
+ // everything is allocated, just fill in the details
sock->socket = newsock;
sock->landriver = net_landriverlevel;
sock->addr = clientaddr;
MSG_WriteByte(&net_message, CCREP_ACCEPT);
dfunc.GetSocketAddr(newsock, &newaddr);
MSG_WriteLong(&net_message, dfunc.GetSocketPort(&newaddr));
-// MSG_WriteString(&net_message, dfunc.AddrToString(&newaddr));
*((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
+ // LordHavoc: send from sock->socket instead of acceptsock, this way routers
+ // usually identify the connection correctly (thanks to faded for provoking
+ // me to recite a lengthy explanation of NAT nightmares, and realize this
+ // easy workaround for quake)
+ dfunc.Write (sock->socket, net_message.data, net_message.cursize, &clientaddr);
+ // LordHavoc: also send from acceptsock, for good measure
dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr);
SZ_Clear(&net_message);
}
+static qboolean Datagram_HandleServerInfo (struct qsockaddr *readaddr)
+{
+ //struct qsockaddr myaddr;
+ int control;
+ int c, n, i;
+
+ if (net_message.cursize < (int)sizeof(int))
+ return false;
+
+ // don't answer our own query
+ //dfunc.GetSocketAddr (dfunc.controlSock, &myaddr);
+ //if (dfunc.AddrCompare(readaddr, &myaddr) >= 0)
+ // return false;
+
+ // is the cache full?
+ if (hostCacheCount == HOSTCACHESIZE)
+ return false;
+
+ MSG_BeginReading ();
+ control = BigLong(*((int *)net_message.data));
+ MSG_ReadLong();
+ if (control == -1)
+ return false;
+ if ((control & (~NETFLAG_LENGTH_MASK)) != (int)NETFLAG_CTL)
+ return false;
+ if ((control & NETFLAG_LENGTH_MASK) != net_message.cursize)
+ return false;
+
+ c = MSG_ReadByte();
+ if (c != CCREP_SERVER_INFO)
+ return false;
+
+ dfunc.GetAddrFromName(MSG_ReadString(), readaddr);
+ // search the cache for this server
+ for (n = 0; n < hostCacheCount; n++)
+ if (dfunc.AddrCompare(readaddr, &hostcache[n].addr) == 0)
+ break;
+
+ // is it already there?
+ if (n < hostCacheCount)
+ return false;;
+
+ // add it
+ hostCacheCount++;
+ strcpy(hostcache[n].name, MSG_ReadString());
+ strcpy(hostcache[n].map, MSG_ReadString());
+ hostcache[n].users = MSG_ReadByte();
+ hostcache[n].maxusers = MSG_ReadByte();
+ c = MSG_ReadByte();
+ if (c != NET_PROTOCOL_VERSION)
+ {
+ strcpy(hostcache[n].cname, hostcache[n].name);
+ hostcache[n].cname[14] = 0;
+ strcpy(hostcache[n].name, "*");
+ strcat(hostcache[n].name, hostcache[n].cname);
+ }
+ memcpy(&hostcache[n].addr, readaddr, sizeof(struct qsockaddr));
+ hostcache[n].driver = net_driverlevel;
+ hostcache[n].ldriver = net_landriverlevel;
+ strcpy(hostcache[n].cname, dfunc.AddrToString(readaddr));
+
+ // check for a name conflict
+ for (i = 0; i < hostCacheCount; i++)
+ {
+ if (i == n)
+ continue;
+ if (strcasecmp (hostcache[n].name, hostcache[i].name) == 0)
+ {
+ i = strlen(hostcache[n].name);
+ if (i < 15 && hostcache[n].name[i-1] > '8')
+ {
+ hostcache[n].name[i] = '0';
+ hostcache[n].name[i+1] = 0;
+ }
+ else
+ hostcache[n].name[i-1]++;
+ i = -1;
+ }
+ }
+
+ return true;
+}
+
+
static void _Datagram_SearchForHosts (qboolean xmit)
{
int ret;
- int n;
- int i;
struct qsockaddr readaddr;
- struct qsockaddr myaddr;
- int control;
- dfunc.GetSocketAddr (dfunc.controlSock, &myaddr);
if (xmit)
{
SZ_Clear(&net_message);
while ((ret = dfunc.Read (dfunc.controlSock, net_message.data, net_message.maxsize, &readaddr)) > 0)
{
- if (ret < sizeof(int))
- continue;
net_message.cursize = ret;
+ Datagram_HandleServerInfo (&readaddr);
+ }
+}
- // don't answer our own query
- if (dfunc.AddrCompare(&readaddr, &myaddr) >= 0)
- continue;
-
- // is the cache full?
+void Datagram_SearchForHosts (qboolean xmit)
+{
+ for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++)
+ {
if (hostCacheCount == HOSTCACHESIZE)
- continue;
-
- MSG_BeginReading ();
- control = BigLong(*((int *)net_message.data));
- MSG_ReadLong();
- if (control == -1)
- continue;
- if ((control & (~NETFLAG_LENGTH_MASK)) != NETFLAG_CTL)
- continue;
- if ((control & NETFLAG_LENGTH_MASK) != ret)
- continue;
-
- if (MSG_ReadByte() != CCREP_SERVER_INFO)
- continue;
+ break;
+ if (net_landrivers[net_landriverlevel].initialized)
+ _Datagram_SearchForHosts (xmit);
+ }
+}
- dfunc.GetAddrFromName(MSG_ReadString(), &readaddr);
- // search the cache for this server
- for (n = 0; n < hostCacheCount; n++)
- if (dfunc.AddrCompare(&readaddr, &hostcache[n].addr) == 0)
- break;
- // is it already there?
- if (n < hostCacheCount)
- continue;
+static qboolean _Datagram_SearchForInetHosts (const char *master)
+{
+ qboolean result = false;
+ struct qsockaddr masteraddr;
+ struct qsockaddr readaddr;
+ int ret;
- // add it
- hostCacheCount++;
- strcpy(hostcache[n].name, MSG_ReadString());
- strcpy(hostcache[n].map, MSG_ReadString());
- hostcache[n].users = MSG_ReadByte();
- hostcache[n].maxusers = MSG_ReadByte();
- if (MSG_ReadByte() != NET_PROTOCOL_VERSION)
+ if (master)
+ {
+ if (dfunc.GetAddrFromName(master, &masteraddr) != -1)
{
- strcpy(hostcache[n].cname, hostcache[n].name);
- hostcache[n].cname[14] = 0;
- strcpy(hostcache[n].name, "*");
- strcat(hostcache[n].name, hostcache[n].cname);
+ int portnum = 0;
+ const char* port = strrchr (master, ':');
+ if (port)
+ portnum = atoi (port + 1);
+ if (!portnum)
+ portnum = MASTER_PORT;
+ Con_DPrintf("Datagram_SearchForInetHosts: sending %d byte message to master %s port %i\n", net_message.cursize, master, portnum);
+ dfunc.SetSocketPort (&masteraddr, portnum);
+ dfunc.Write (dfunc.controlSock, net_message.data, net_message.cursize, &masteraddr);
}
- memcpy(&hostcache[n].addr, &readaddr, sizeof(struct qsockaddr));
- hostcache[n].driver = net_driverlevel;
- hostcache[n].ldriver = net_landriverlevel;
- strcpy(hostcache[n].cname, dfunc.AddrToString(&readaddr));
+ }
- // check for a name conflict
- for (i = 0; i < hostCacheCount; i++)
- {
- if (i == n)
- continue;
- if (Q_strcasecmp (hostcache[n].name, hostcache[i].name) == 0)
- {
- i = strlen(hostcache[n].name);
- if (i < 15 && hostcache[n].name[i-1] > '8')
- {
- hostcache[n].name[i] = '0';
- hostcache[n].name[i+1] = 0;
- }
- else
- hostcache[n].name[i-1]++;
- i = -1;
- }
- }
+ while ((ret = dfunc.Read (dfunc.controlSock, net_message.data, net_message.maxsize, &readaddr)) > 0)
+ {
+ net_message.cursize = ret;
+ Con_DPrintf("Datagram_SearchForInetHosts: Read received %d byte message\n", net_message.cursize);
+ if (Datagram_HandleServerInfo (&readaddr))
+ result = true;
+ else
+ Master_ParseServerList (&dfunc);
}
+
+ return result;
}
-void Datagram_SearchForHosts (qboolean xmit)
+
+qboolean Datagram_SearchForInetHosts (const char *master)
{
+ qboolean result = false;
for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++)
{
if (hostCacheCount == HOSTCACHESIZE)
break;
if (net_landrivers[net_landriverlevel].initialized)
- _Datagram_SearchForHosts (xmit);
+ if (_Datagram_SearchForInetHosts (master))
+ result = true;
}
+
+ return result;
}
-static qsocket_t *_Datagram_Connect (char *host)
+static qsocket_t *_Datagram_Connect (const char *host)
{
struct qsockaddr sendaddr;
struct qsockaddr readaddr;
+ struct qsockaddr testaddr;
qsocket_t *sock;
int newsock;
int ret;
if (dfunc.GetAddrFromName(host, &sendaddr) == -1)
return NULL;
- newsock = dfunc.OpenSocket (0);
+ newsock = dfunc.OpenSocket (cl_port.integer);
if (newsock == -1)
return NULL;
goto ErrorReturn;
// send the connection request
- Con_Printf("trying...\n"); SCR_UpdateScreen ();
+ Con_Printf("trying...\n");CL_UpdateScreen();CL_UpdateScreen();
start_time = net_time;
for (reps = 0; reps < 3; reps++)
if (ret > 0)
{
// is it from the right place?
- if (sfunc.AddrCompare(&readaddr, &sendaddr) != 0)
+ // we don't care if the port matches (this adds support for
+ // the NAT fix in the server inspired by faded)
+ memcpy(&testaddr, &sendaddr, sizeof(struct qsockaddr));
+ dfunc.SetSocketPort (&testaddr, dfunc.GetSocketPort(&readaddr));
+ if (sfunc.AddrCompare(&readaddr, &testaddr) != 0)
{
-//#ifdef DEBUG
- Con_DPrintf("wrong reply address\n");
- Con_DPrintf("Expected: %s\n", StrAddr (&sendaddr));
- Con_DPrintf("Received: %s\n", StrAddr (&readaddr));
- SCR_UpdateScreen ();
-//#endif
+ Con_Printf("wrong reply address\n");
+ Con_Printf("Expected: %s\n", StrAddr (&sendaddr));
+ Con_Printf("Received: %s\n", StrAddr (&readaddr));
+ CL_UpdateScreen ();
+ CL_UpdateScreen ();
ret = 0;
continue;
}
- if (ret < sizeof(int))
+ if (ret < (int)sizeof(int))
{
ret = 0;
continue;
ret = 0;
continue;
}
- if ((control & (~NETFLAG_LENGTH_MASK)) != NETFLAG_CTL)
+ if ((control & (~NETFLAG_LENGTH_MASK)) != (int)NETFLAG_CTL)
{
ret = 0;
continue;
while (ret == 0 && (SetNetTime() - start_time) < 2.5);
if (ret)
break;
- Con_Printf("still trying...\n"); SCR_UpdateScreen ();
+ Con_Printf("still trying...\n");CL_UpdateScreen();CL_UpdateScreen();
start_time = SetNetTime();
}
if (ret == CCREP_REJECT)
{
reason = MSG_ReadString();
- Con_Printf(reason);
+ Con_Printf("%s", reason);
strncpy(m_return_reason, reason, 31);
goto ErrorReturn;
}
dfunc.GetNameFromAddr (&sendaddr, sock->address);
- Con_Printf ("Connection accepted\n");
+ Con_Printf ("Connection accepted to %s\n", sock->address);
sock->lastMessageTime = SetNetTime();
// switch the connection to the specified address
return NULL;
}
-qsocket_t *Datagram_Connect (char *host)
+qsocket_t *Datagram_Connect (const char *host)
{
qsocket_t *ret = NULL;
break;
return ret;
}
+
+static void _Datagram_Heartbeat (const char *master)
+{
+ struct qsockaddr masteraddr;
+ int portnum;
+ const char* port;
+
+ if (dfunc.GetAddrFromName(master, &masteraddr) == -1)
+ return;
+
+ portnum = 0;
+ port = strrchr (master, ':');
+ if (port)
+ portnum = atoi (port + 1);
+ if (!portnum)
+ portnum = MASTER_PORT;
+ dfunc.SetSocketPort (&masteraddr, portnum);
+
+ // FIXME: this is the only use of UDP_Send in the entire engine, add a dfunc.acceptSock to get rid of this function!
+ dfunc.Send (net_message.data, net_message.cursize, &masteraddr);
+}
+
+void Datagram_Heartbeat (const char *master)
+{
+ for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++)
+ if (net_landrivers[net_landriverlevel].initialized)
+ _Datagram_Heartbeat (master);
+}