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