Hi James,
As discussed I implemented and tested (server-side only) the addition
of the metaserver listing for INL server feature.
I followed your suggestion and used this as my .metaservers file:
metaserver.us.netrek.org 3521 60 120 home.nl.netrek.org I 4566 4000 open
metaserver.us.netrek.org 3521 60 120 away.nl.netrek.org I 4577 5000 open
metaserver2.us.netrek.org 3521 60 120 home.nl.netrek.org I 4566 4000 open
metaserver2.us.netrek.org 3521 60 120 away.nl.netrek.org I 4577 5000 open
The server now gets properly listed on the metaserver:
Server Host Port Ago Status Flags
--------------------------------------- -------- ---- ----------------- -------
-h away.nl.netrek.org -p 4577 43 Nobody playing I
-h home.nl.netrek.org -p 4566 0 OPEN: 1 player I
Since I dislike having all INL servers show up on the metaserver list
even when there are no players on it (which is 99.9% of the time), I
made it so that only when there are actually players on it, the metaserver
is informed. When the last player logs out it will send a final players=0
update and that's it. That means that after 60 minutes (this I tested) the
entry will be automatically removed from the metaserver.
So how does the server get listed in the first place then you may wonder?
Well, I hope that the old clue players know how to use -h host -p port
command line options. They will probably be the ones to call a clue game,
so they will enter first too. And that triggers the metaserver listing.
Client changes required!
I see NetrekXP already shows entries of type "I" (=INL). Had not expected
that. Anyway. the change required is stripping off "home." and "away."
from the start of the hostname and only *displaying* that on screen.
Should be trivial. I will do that later for NetrekXP.
Attached you find 2 files: solicit.c and solicit.h. In the latter I had
to increase MAXMETASERVERS to 4, so that my 4 entries would all work
(2 metaservers, each has a home/away).
There is a small 64bit bugfix in there too. Someone did a nice pointer
copy and thought only of 32bit ;-)
James: there's still a lot of extraneous fprintf() statements in. If
you apply the patch, you can safely remove them..
Greetx, Erik
James Cameron wrote:
> Erik,
>
> I've had a look at the metaserver code just now, at scan.c and
> disp_udp.c, and it would be quite simple to adjust that to account for
> INL servers. I made the UDP protocol extensible.
>
> But you mentioned port 3521. Does the NetrekXP client use the UDP or
> TCP connection? If TCP, then I don't think I'd like to change the
> format. I'd really like Stas to use UDP if he can. ;-)
>
> To hack it now without changing any code, in the backwards compatible
> way, the .metaservers file for the INL server should contain one line
> for each port, using a DNS alias.
>
> For example;
>
> metaserver.us.netrek.org 3521 60 120 pickup.nl.netrek.org B 2592 2593 open
> metaserver.us.netrek.org 3521 60 120 home.nl.netrek.org B 4566 4000 open
> metaserver.us.netrek.org 3521 60 120 away.nl.netrek.org B 4577 5000 open
> metaserver.us.netrek.org 3521 60 120 home-observers.nl.netrek.org B 4000
> ...
>
> We should add a server type for an INL server, and include INL servers
> that have solicited in the response to the client. I'd want to make a
> new server side protocol version to include all four ports. I'd expect
> the client developers to accept four ports and figure a way to display
> it!
>
> Erik wrote:
>
>> b) Currently only the player port is listed!
>
>
> Yes, that's insufficient.
>
> We didn't design in a protocol version number for the client UDP query,
> but there's room; only the first character is checked now and it must be
> a question mark. We could add a version number after that, and have the
> later version cause more ports to be listed.
>
> I'm happy to change COW.
>
>
>>3) Problem is I have not yet found the point where to put the
>> call to solicit(). How can the server check if it is an INL
>> server?
>
>
> (status->gameup & GU_INROBOT)
>
> The flag is set by the INL robot.
>
>
>> Which process owns that metaservers[] table?
>
>
> Daemon.
>
>
>> Should the check be done in deamon (which has the solicit() call),
>> or in netrekd when a player enters?
>
>
> Daemon. It won't be running until a player enters.
>
>
>>Any feedback is appreciated. I am really convinced this is a
>>necessary feature to promote clue netrek to midbies who simply
>>happen to not be a computer wizard ;-)
>
>
> I agree.
>
-------------- next part --------------
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/time.h>
#include "defs.h"
#include "struct.h"
#include "data.h"
#include "solicit.h"
/* our copy of .metaservers file */
static struct metaserver metaservers[MAXMETASERVERS];
/* initialisation done flag */
static int initialised = 0;
/* remove non-printable chars in a string. Lifted from tools/players.c */
/* with a minor addition: error checking on the strdup */
static char *name_fix(char *name)
{
char *new = strdup(name); /* xx, never freed */
register
char *r = new;
if(!new) return new; /* don't play with null ptr */
while(*name){
*r++ = (*name <= 32)?'_':*name;
name++;
}
*r = 0;
return new;
}
/* attach to a metaserver, i.e. prepare the socket */
static int udp_attach(struct metaserver *m)
{
char *our_server = m->ours;
/* create the socket structure */
m->sock = socket(AF_INET, SOCK_DGRAM, 0);
if (m->sock < 0) {
perror("solicit: udp_attach: socket");
return 0;
}
/* bind the local socket */
/* bind to interface attatched to this.host.name */
/* first check if perhaps it is a fake INL server address */
if( ( m->type[0] == 'I' || m->type[0] == 'i' ) &&
( strncasecmp( m->ours, "home.", 5 ) == 0 ||
strncasecmp( m->ours, "away.", 5 ) == 0 ) )
{
our_server += 5;
}
/* numeric first */
if( (m->address.sin_addr.s_addr = inet_addr( our_server )) == -1 ) {
struct hostent *hp;
/* then name */
if ((hp = gethostbyname( our_server )) == NULL) {
/* bad hostname or unable to get ip address */
fprintf(stderr, "Bad this.host.name field in .metaservers.\n");
return 0;
} else {
// m->address.sin_addr.s_addr = *(long *) hp->h_addr; NOT 64BIT SAFE
memcpy( &(m->address.sin_addr.s_addr), hp->h_addr, 4);
}
}
m->address.sin_family = AF_INET;
m->address.sin_port = 0;
if (bind(m->sock,(struct sockaddr *)&m->address, sizeof(m->address)) < 0) {
perror("solicit: udp_attach: unable to bind to desired interface (this.host.name field)");
return 0;
}
/* build the destination address */
m->address.sin_family = AF_INET;
m->address.sin_port = htons(m->port);
/* attempt numeric translation first */
if ((m->address.sin_addr.s_addr = inet_addr(m->host)) == -1) {
struct hostent *hp;
/* then translation by name */
if ((hp = gethostbyname(m->host)) == NULL) {
/* if it didn't work, return failure and warning */
fprintf(stderr, "solicit: udp_attach: host %s not known\n", m->host);
return 0;
} else {
// m->address.sin_addr.s_addr = *(long *) hp->h_addr; NOT 64BIT SAFE
memcpy( &(m->address.sin_addr.s_addr), hp->h_addr, 4);
}
}
return 1;
}
/* transmit a packet to the metaserver */
static int udp_tx(struct metaserver *m, char *buffer, int length)
{
int i;
/* send the packet */
fprintf(stderr, "solicit: udp_tx: sendto (size:%d)\n", length );
if (sendto(m->sock, buffer, length, 0, (struct sockaddr *)&m->address,
sizeof(m->address)) < 0) {
perror("solicit: udp_tx: sendto");
return 0;
}
return 1;
}
void solicit(int force)
{
int i, nplayers=0, nfree=0;
char packet[MAXMETABYTES];
/* static char prior[MAXMETABYTES];*/
char *fixed_name, *fixed_login; /* name/login stripped of unprintables */
char *name, *login; /* name and login guaranteed not blank */
char unknown[] = "unknown"; /* unknown player/login string */
char *here = packet;
time_t now = time(NULL);
int gamefull = 0; /* is the game full? */
int isrsa = 0; /* is this server RSA? */
/* perform first time initialisation */
if (initialised == 0) {
FILE *file;
/* clear metaserver socket list */
for (i=0; i<MAXMETASERVERS; i++) metaservers[i].sock = -1;
/* open the metaserver list file */
file = fopen(".metaservers", "r"); /* ??? LIBDIR prefix? */
if (file == NULL) {
initialised++;
return;
}
/* read the metaserver list file */
for (i=0; i<MAXMETASERVERS; i++) {
struct metaserver *m = &metaservers[i];
char buffer[256]; /* where to hold the .metaservers line */
char *line; /* return from fgets() */
char *token; /* current line token */
/* read a line */
line = fgets(buffer, 256, file);
/* if end of file reached, stop */
if (line == NULL) break;
if (feof(file)) break;
/* parse each field, ignore the line if insufficient fields found */
token = strtok(line, " "); /* meta host name */
if (token == NULL) continue;
strncpy(m->host, token, 32);
token = strtok(NULL, " "); /* meta port */
if (token == NULL) continue;
m->port = atoi(token);
token = strtok(NULL, " "); /* min solicit time */
if (token == NULL) continue;
m->minimum = atoi(token);
token = strtok(NULL, " "); /* max solicit time */
if (token == NULL) continue;
m->maximum = atoi(token);
token = strtok(NULL, " "); /* our host name */
if (token == NULL) continue;
strncpy(m->ours, token, 32);
token = strtok(NULL, " "); /* server type */
if (token == NULL) continue;
strncpy(m->type, token, 2);
token = strtok(NULL, " "); /* player port */
if (token == NULL) continue;
m->pport = atoi(token);
token = strtok(NULL, " "); /* observer port */
if (token == NULL) continue;
m->oport = atoi(token);
token = strtok(NULL, "\n"); /* comment text */
if (token == NULL) continue;
strncpy(m->comment, token, 32);
/* force minimum and maximum delays (see note on #define) */
if (m->minimum < META_MINIMUM_DELAY)
m->minimum = META_MINIMUM_DELAY;
if (m->maximum > META_MAXIMUM_DELAY)
m->maximum = META_MAXIMUM_DELAY;
/* attach to the metaserver (DNS lookup is only delay) */
udp_attach(m);
/* place metaserver addresses in /etc/hosts to speed this */
/* use numeric metaserver address to speed this */
/* initialise the other parts of the structure */
m->sent = 0;
strcpy(m->prior, "");
}
initialised++;
fclose(file);
}
printf("solicit call!\n\n" );
/* update each metaserver */
for (i=0; i<MAXMETASERVERS; i++) {
struct metaserver *m = &metaservers[i];
int j;
fprintf(stderr, "solicit[%d](ours:'%s' type='%s' pport=%d oport=%d meta='%s' mport=%d)\n", i, m->ours, m->type, m->pport, m->oport, m->host, m->port );
/* skip empty metaserver entries */
if (m->sock == -1)
{
fprintf(stderr, " skip empty metaserver entries\n" );
continue;
}
/* only process entries with the correct server type */
if( status->gameup & GU_INROBOT && m->type[0] != 'i' && m->type[0] != 'I' )
{
fprintf(stderr, " skip metaserver entries with non-INL type during INL mode\n" );
continue;
}
else if( !(status->gameup & GU_INROBOT) && ( m->type[0] == 'i' || m->type[0] == 'I' ) )
{
fprintf(stderr, " skip metaserver entries with INL type during non-INL mode\n" );
continue;
}
/* if we told metaserver recently, don't speak yet */
if (!force)
if ((now-m->sent) < m->minimum) continue;
/* don't remake the packet unless necessary */
/* for INL the always recreate because we have multiple ports */
if ( status->gameup & GU_INROBOT )
{
here = packet; nplayers=0; nfree=0; gamefull=0; isrsa=0;
}
if ( here == packet ) {
if (status->gameup & GU_NEWBIE)
{
for (j = 0; j < queues[QU_NEWBIE_PLR].high_slot; j++)
{
if (players[j].p_status == PFREE)
nfree++;
else
nplayers++;
}
}
else if (status->gameup & GU_PRET)
{
for (j = 0; j < queues[QU_PRET_PLR].high_slot; j++)
{
if (players[j].p_status == PFREE)
nfree++;
else
nplayers++;
}
}
else if (status->gameup & GU_INROBOT &&
strncasecmp( m->ours, "home.", 5 ) == 0 )
{
for (j = queues[QU_HOME].low_slot; j < queues[QU_HOME].high_slot; j++)
{
if (players[j].p_status == PFREE)
nfree++;
else
nplayers++;
}
}
else if (status->gameup & GU_INROBOT &&
strncasecmp( m->ours, "away.", 5 ) == 0 )
{
for (j = queues[QU_AWAY].low_slot; j < queues[QU_AWAY].high_slot; j++)
{
if (players[j].p_status == PFREE)
nfree++;
else
nplayers++;
}
}
else
{
for (j = 0; j < queues[QU_PICKUP].high_slot; j++)
{
if (players[j].p_status == PFREE)
nfree++;
else
nplayers++;
}
}
fprintf(stderr, "before: nfree=%d nplayers=%d gamefull=%d\n", nfree, nplayers, gamefull );
/* Special case: do *not* report anything for INL servers if nplayers=0,
except one last time when players are leaving. Actually i just want to
delist the server. is there a better way? ehb */
if( nplayers == 0 &&
status->gameup & GU_INROBOT &&
( strcmp(packet, m->prior) == 0 || strcmp( m->prior, "") == 0 ) )
{
fprintf(stderr, " skip metaserver entries during INL mode if zero players\n" );
continue;
}
/* if the free slots are zero, translate it to a queue length */
/* and report that the game is full */
if (nfree == 0)
{
if (status->gameup & GU_NEWBIE)
nfree = -queues[QU_NEWBIE_PLR].count;
else if (status->gameup & GU_PRET)
nfree = -queues[QU_PRET_PLR].count;
else if (status->gameup & GU_INROBOT && strncasecmp( m->ours, "home.", 5 ) == 0 )
nfree = -queues[QU_HOME].count;
else if (status->gameup & GU_INROBOT && strncasecmp( m->ours, "away.", 5 ) == 0 )
nfree = -queues[QU_AWAY].count;
else
nfree = -queues[QU_PICKUP].count;
gamefull++;
}
fprintf(stderr, " nfree=%d nplayers=%d gamefull=%d\n", nfree, nplayers, gamefull );
#ifdef RSA
isrsa++; /* this is an RSA server */
#endif
/* build start of the packet, the server information */
sprintf(here, "%s\n%s\n%s\n%d\n%d\n%d\n%d\n%s\n%s\n%s\n%s\n",
/* version */ "b",
/* address */ m->ours,
/* type */ m->type,
/* port */ m->pport,
/* observe */ m->oport,
/* players */ nplayers,
/* free */ nfree,
/* t-mode */ status->tourn ? "y" : "n",
/* RSA */ isrsa ? "y" : "n",
/* full */ gamefull ? "y" : "n",
/* comment */ m->comment
);
here += strlen(here);
/* now append per-player information to the packet */
for (j=0; j<MAXPLAYER; j++) {
/* ignore free slots */
if (players[j].p_status == PFREE ||
#ifdef LTD_STATS
ltd_ticks(&(players[j]), LTD_TOTAL) == 0)
#else
players[j].p_stats.st_tticks == 0)
#endif
continue;
fixed_name = name_fix(players[j].p_name); /*get rid of non-printables*/
fixed_login = name_fix(players[j].p_login);
/* make sure name_fix() doesn't return NULL */
name = ( fixed_name != NULL ) ? fixed_name : players[j].p_name;
login = ( fixed_login != NULL ) ? fixed_login : players[j].p_login;
/* if string is empty, report "unknown" */
name = ( *(name) == 0 ) ? unknown : name;
login = ( *(login) == 0 ) ? unknown : login;
sprintf(here, "%c\n%c\n%d\n%d\n%s\n%s@%s\n",
/* number */ players[j].p_mapchars[1],
/* team */ players[j].p_mapchars[0],
/* class */ players[j].p_ship.s_type,
/* ??? note change from design, ship type number not string */
/* rank */ players[j].p_stats.st_rank,
/* ??? note change from design, rank number not string */
/* name */ name,
/* user */ login,
/* host */ players[j].p_monitor );
here += strlen(here);
free(fixed_name); /*because name_fix malloc()s a string */
free(fixed_login);
}
}
fprintf( stderr, " packet created for sending:\n%s\n", packet );
/* if we have exceeded the maximum time, force an update */
if ((now-m->sent) > m->maximum) force=1;
/* if we are not forcing an update, and nothing has changed, drop */
if (!force)
if (!strcmp(packet, m->prior))
{
fprintf( stderr, " No change in packet since last time. dont send.\n" );
continue;
}
/* send the packet */
if (udp_tx(m, packet, here-packet)) {
m->sent=time(NULL);
strcpy(m->prior, packet);
}
}
}
-------------- next part --------------
/* bytes to reserve for outgoing packet to metaserver */
#define MAXMETABYTES 2048
/* maximum number of metaservers supported */
#define MAXMETASERVERS 4
/* minimum allowable delay between updates to metaserver */
#define META_MINIMUM_DELAY 60
/* Note: changing this may cause the metaserver to delist your server */
/* maximum delay between updates to metaserver */
#define META_MAXIMUM_DELAY 900
/* ship classes (wish these were in data.c/data.h) */
/* static char *ships[] = {"SC", "DD", "CA", "BB", "AS", "SB", "GA"}; */
/* structure of information about a single metaserver */
struct metaserver
{
/* data items derived from metaservers file */
char host[32]; /* address of metaserver (DNS) */
int port; /* port of metaserver */
int minimum; /* minimum update time */
int maximum; /* maximum update time */
char ours[32]; /* DNS address of server */
char type[2]; /* server type code (B/P/C/H/?) */
int pport; /* server main player port (e.g. 2592) */
int oport; /* server observer player port */
char comment[32]; /* comment string */
/* our own data about the communication with the metaserver */
int sock; /* our socket number */
struct sockaddr_in address; /* address of metaserver */
time_t sent; /* date time metaserver last updated */
char prior[MAXMETABYTES]; /* prior packet sent */
};
void solicit(int force);
-------------- next part --------------
_______________________________________________
vanilla-devel mailing list
vanilla-devel at us.netrek.org
https://mailman.real-time.com/mailman/listinfo/vanilla-devel